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
+66
View File
@@ -0,0 +1,66 @@
import { wakeOpenClaw } from "../../openclaw/client";
import type { OpenClawConfig, OpenClawContext } from "../../openclaw/types";
import { getMainSessionID } from "../../features/claude-code-session-state";
import type { PluginContext } from "../../plugin/types";
export function createOpenClawSenderHook(
ctx: PluginContext,
config: OpenClawConfig
) {
return {
event: async (input: {
event: { type: string; properties?: Record<string, unknown> };
}) => {
const { type, properties } = input.event;
const context: OpenClawContext = {
sessionId: (properties?.sessionID as string) || getMainSessionID(),
projectPath: ctx.directory,
};
if (type === "session.created") {
await wakeOpenClaw("session-start", context, config);
} else if (type === "session.idle") {
await wakeOpenClaw("session-idle", context, config);
} else if (type === "session.deleted") {
await wakeOpenClaw("session-end", context, config);
}
},
"tool.execute.before": async (
input: { tool: string; sessionID: string },
output: { args: Record<string, unknown> }
) => {
const toolName = input.tool.toLowerCase();
const context: OpenClawContext = {
sessionId: input.sessionID,
projectPath: ctx.directory,
};
if (
toolName === "ask_user_question" ||
toolName === "askuserquestion" ||
toolName === "question"
) {
const question =
typeof output.args.question === "string"
? output.args.question
: undefined;
await wakeOpenClaw(
"ask-user-question",
{
...context,
question,
},
config
);
} else if (toolName === "skill") {
const rawName =
typeof output.args.name === "string" ? output.args.name : undefined;
const command = rawName?.replace(/^\//, "").toLowerCase();
if (command === "stop-continuation") {
await wakeOpenClaw("stop", context, config);
}
}
},
};
}