feat: implement OpenClaw integration

Ports the OMX OpenClaw module into oh-my-openagent as a first-class integration.
This integration allows forwarding internal events (session lifecycle, tool execution) to external gateways (HTTP or command-based).

- Added `src/openclaw` directory with implementation:
  - `dispatcher.ts`: Handles HTTP/Command dispatching with interpolation
  - `types.ts`: TypeScript definitions
  - `client.ts`: Main entry point `wakeOpenClaw`
  - `index.ts`: Public API
- Added `src/config/schema/openclaw.ts` for Zod schema validation
- Updated `src/config/schema/oh-my-opencode-config.ts` to include `openclaw` config
- Added `src/hooks/openclaw-sender/index.ts` to listen for events
- Registered the hook in `src/plugin/hooks/create-session-hooks.ts`
- Added unit tests in `src/openclaw/__tests__`

Events handled:
- `session-start` (via `session.created`)
- `session-end` (via `session.deleted`)
- `session-idle` (via `session.idle`)
- `ask-user-question` (via `tool.execute.before` for `ask_user_question`)
- `stop` (via `tool.execute.before` for `stop-continuation` command)
This commit is contained in:
YeonGyu-Kim
2026-03-16 17:21:56 +09:00
parent 84fb1113f1
commit 03b346ba51
14 changed files with 1006 additions and 0 deletions
+41
View File
@@ -0,0 +1,41 @@
import { describe, it, expect } from "bun:test";
import { resolveGateway } from "../client";
import { type OpenClawConfig } from "../types";
describe("OpenClaw Client", () => {
describe("resolveGateway", () => {
const config: OpenClawConfig = {
enabled: true,
gateways: {
foo: { type: "command", command: "echo foo" },
bar: { type: "http", url: "https://example.com" },
},
hooks: {
"session-start": {
gateway: "foo",
instruction: "start",
enabled: true,
},
"session-end": { gateway: "bar", instruction: "end", enabled: true },
stop: { gateway: "foo", instruction: "stop", enabled: false },
},
};
it("resolves valid mapping", () => {
const result = resolveGateway(config, "session-start");
expect(result).not.toBeNull();
expect(result?.gatewayName).toBe("foo");
expect(result?.instruction).toBe("start");
});
it("returns null for disabled hook", () => {
const result = resolveGateway(config, "stop");
expect(result).toBeNull();
});
it("returns null for unmapped event", () => {
const result = resolveGateway(config, "ask-user-question");
expect(result).toBeNull();
});
});
});