@hirey-ai/hirey 1.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +85 -0
  2. package/dist/clients.d.ts +23 -0
  3. package/dist/clients.d.ts.map +1 -0
  4. package/dist/clients.js +47 -0
  5. package/dist/clients.js.map +1 -0
  6. package/dist/index.d.ts +3 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +92 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/routes/webhook.d.ts +6 -0
  11. package/dist/routes/webhook.d.ts.map +1 -0
  12. package/dist/routes/webhook.js +76 -0
  13. package/dist/routes/webhook.js.map +1 -0
  14. package/dist/services/agent-events.d.ts +3 -0
  15. package/dist/services/agent-events.d.ts.map +1 -0
  16. package/dist/services/agent-events.js +267 -0
  17. package/dist/services/agent-events.js.map +1 -0
  18. package/dist/state.d.ts +61 -0
  19. package/dist/state.d.ts.map +1 -0
  20. package/dist/state.js +128 -0
  21. package/dist/state.js.map +1 -0
  22. package/dist/tools/capabilities.d.ts +4 -0
  23. package/dist/tools/capabilities.d.ts.map +1 -0
  24. package/dist/tools/capabilities.js +93 -0
  25. package/dist/tools/capabilities.js.map +1 -0
  26. package/dist/tools/control.d.ts +8 -0
  27. package/dist/tools/control.d.ts.map +1 -0
  28. package/dist/tools/control.js +508 -0
  29. package/dist/tools/control.js.map +1 -0
  30. package/dist/types.d.ts +63 -0
  31. package/dist/types.d.ts.map +1 -0
  32. package/dist/types.js +10 -0
  33. package/dist/types.js.map +1 -0
  34. package/dist/utils/openclaw-config.d.ts +30 -0
  35. package/dist/utils/openclaw-config.d.ts.map +1 -0
  36. package/dist/utils/openclaw-config.js +162 -0
  37. package/dist/utils/openclaw-config.js.map +1 -0
  38. package/dist/utils/openclaw-hooks-payload.d.ts +18 -0
  39. package/dist/utils/openclaw-hooks-payload.d.ts.map +1 -0
  40. package/dist/utils/openclaw-hooks-payload.js +52 -0
  41. package/dist/utils/openclaw-hooks-payload.js.map +1 -0
  42. package/openclaw.plugin.json +83 -0
  43. package/package.json +65 -0
package/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # hi-openclaw-plugin
2
+
3
+ Hirey Hi as a **native OpenClaw plugin**. Registers Hi's tools, agent-events claim service, and webhook ingress directly inside the OpenClaw gateway process — zero independent daemons, no `mcp.servers.hi` indirection, and no per-run frozen tool inventory boundary.
4
+
5
+ This is the OpenClaw 5.4+ first-class path, published to ClawHub as **`clawhub:hirey`** (ClawPack code-plugin) and to npm as **`hirey`**. OpenClaw 4.23 ~ 5.3 hosts cannot load this ClawPack format and must install the prod bundle plugin **`clawhub:hirey-compatible`** instead (zip + skill + scripts wrapping `@hirey-ai/mcp-server` + `@hirey-ai/agent-receiver`).
6
+
7
+ ## Why this exists
8
+
9
+ The bundle + spawn model needs:
10
+
11
+ - one stdio child process for the MCP server (`@hirey-ai/mcp-server`)
12
+ - one long-running daemon (`@hirey-ai/agent-receiver`) for cloud-to-host event delivery
13
+ - a host installer mjs that uses `child_process` to run `npm install` + `openclaw config set` (which trips OpenClaw's pre-4.23 install scanner)
14
+ - a two-message install flow because the LLM run that wrote `mcp.servers.hi` cannot call the just-installed tools in the same outer run (per-run frozen tool inventory)
15
+ - `hooks.token` / `hooks.path` / `hooks.allowedSessionKeyPrefixes` / `/hooks/agent` plumbing on the OpenClaw side
16
+
17
+ This native plugin replaces all of the above with three OpenClaw plugin SDK calls running inside the gateway process:
18
+
19
+ - `api.registerTool(...)` for every Hi tool — exposed to the LLM directly, no MCP layer
20
+ - `api.registerService(...)` for the agent-events claim loop — gateway owns the lifecycle, no orphan daemon
21
+ - `api.registerHttpRoute(...)` for the webhook ingress — uses gateway's HTTP server, no separate hooks token
22
+
23
+ ## Distribution paths
24
+
25
+ | Path | Audience |
26
+ |---|---|
27
+ | `clawhub:hirey` (this package, ClawPack code-plugin) | OpenClaw **5.4+**. Best UX, in-process, no boundary friction. |
28
+ | `clawhub:hirey-compatible` (prod bundle plugin from `hi-platform`, zip + skills + scripts) | OpenClaw **4.23 ~ 5.3**. Wraps `@hirey-ai/mcp-server` + `@hirey-ai/agent-receiver` because these hosts cannot load ClawPack. |
29
+ | `@hirey-ai/mcp-server` + `@hirey-ai/agent-receiver` (npm, raw) | Claude Desktop, Cursor, VS Code MCP, any other MCP host. Stable cross-host transport. Independent of OpenClaw. |
30
+
31
+ Business logic (`@hirey-ai/agent-sdk`, `@hirey-ai/agent-contracts`) is fully shared; only the wiring layer differs.
32
+
33
+ ## Supported OpenClaw versions
34
+
35
+ | OpenClaw version | `clawhub:hirey` (ClawPack) | `clawhub:hirey-compatible` (bundle) | Notes |
36
+ |---|---|---|---|
37
+ | **2026.5.4+** | ✅ recommended | ✅ works but skips native plugin benefits | ClawPack first-class path |
38
+ | **2026.5.2 ~ 2026.5.3** | ❌ ClawHub server doesn't expose fallback `sha256hash` + `files[]` metadata to old client | ✅ | bundle only |
39
+ | **2026.4.23 ~ 2026.5.1** | ❌ runtime expects date-format `pluginApi`, rejects semantic `1.0` | ✅ | bundle only |
40
+ | **2026.4.14 ~ 2026.4.22** | ❌ same as above | ❌ install scanner flags `child_process` in installer mjs | unsupported; must upgrade OpenClaw |
41
+ | **< 2026.4.14** | ❌ | ❌ | unsupported |
42
+
43
+ ## Install
44
+
45
+ OpenClaw is a personal agent driven by natural language; the LLM picks which command to run. The recommended user-facing prompt that works across versions:
46
+
47
+ ```
48
+ Install Hirey AI on this OpenClaw host. First check `openclaw --version`:
49
+ - If 2026.5.4 or higher, run `openclaw plugins install clawhub:hirey`.
50
+ - Otherwise, run `openclaw plugins install clawhub:hirey-compatible --dangerously-force-unsafe-install`.
51
+ The `--dangerously-force-unsafe-install` flag is required only for the bundle path: it is OpenClaw's own break-glass override that lets community bundles ship a setup-time `child_process` helper for `npm install` + `openclaw config set`. The ClawPack path is in-process and does not need the flag.
52
+ After install, call hi_agent_install (don't fabricate an agent id). Report the real agent_id and hooks_ready status.
53
+ ```
54
+
55
+ Direct CLI for OpenClaw 5.4+ users:
56
+
57
+ ```bash
58
+ openclaw plugins install clawhub:hirey
59
+ openclaw gateway restart
60
+ ```
61
+
62
+ For OpenClaw 4.23 ~ 5.3 users (bundle path):
63
+
64
+ ```bash
65
+ openclaw plugins install clawhub:hirey-compatible --dangerously-force-unsafe-install
66
+ openclaw gateway restart
67
+ ```
68
+
69
+ The `--dangerously-force-unsafe-install` flag is required because OpenClaw 4.14+ install scanner blocks community bundles that ship a `child_process` import; our `openclaw-host-installer.mjs` legitimately uses `child_process.execFile` to spawn `npm install` and `openclaw config set` during setup. This is the OpenClaw-provided override flag (docs/cli/plugins.md), not a bypass of security controls.
70
+
71
+ After restart, ask OpenClaw "Hi 健康吗?" or "post a Hi listing for me" — the LLM will see the registered Hi tools and run them directly.
72
+
73
+ ## Development
74
+
75
+ ```bash
76
+ npm install
77
+ npm run build
78
+ npm pack # emits hirey-<version>.tgz
79
+ ```
80
+
81
+ Use `openclaw plugins install -l <local-dir>` for local link-mode testing (only on OpenClaw 5.4+).
82
+
83
+ ## License
84
+
85
+ UNLICENSED (private; published under unscoped `hirey` on the public npm registry but the source is not open source).
@@ -0,0 +1,23 @@
1
+ import { type HiAgentGatewayClient, type HiAgentPlatformClient, type HiAgentPlatformWellKnown } from '@hirey-ai/agent-sdk';
2
+ import { type HiPersistedState, type StaleIdentityQuarantine } from './state.js';
3
+ export type HiAuthorizedClients = {
4
+ state: HiPersistedState;
5
+ accessToken: string;
6
+ gateway: HiAgentGatewayClient;
7
+ platform: HiAgentPlatformClient;
8
+ wellKnown: HiAgentPlatformWellKnown;
9
+ quarantined: StaleIdentityQuarantine | null;
10
+ };
11
+ export declare function peekQuarantineNotice(): StaleIdentityQuarantine | null;
12
+ export declare function loadStateWithQuarantine(stateDir: string, profile: string, currentPlatformBaseUrl: string): Promise<HiPersistedState>;
13
+ export declare function buildAuthorizedClients(args: {
14
+ stateDir: string;
15
+ profile: string;
16
+ platformBaseUrl: string;
17
+ }): Promise<HiAuthorizedClients>;
18
+ export declare function buildPublicClients(platformBaseUrl: string): Promise<{
19
+ gateway: HiAgentGatewayClient;
20
+ platform: HiAgentPlatformClient;
21
+ wellKnown: HiAgentPlatformWellKnown;
22
+ }>;
23
+ //# sourceMappingURL=clients.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clients.d.ts","sourceRoot":"","sources":["../src/clients.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC9B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAGL,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,EAC7B,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,gBAAgB,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,oBAAoB,CAAC;IAC9B,QAAQ,EAAE,qBAAqB,CAAC;IAChC,SAAS,EAAE,wBAAwB,CAAC;IACpC,WAAW,EAAE,uBAAuB,GAAG,IAAI,CAAC;CAC7C,CAAC;AAMF,wBAAgB,oBAAoB,IAAI,uBAAuB,GAAG,IAAI,CAErE;AAED,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,sBAAsB,EAAE,MAAM,GAC7B,OAAO,CAAC,gBAAgB,CAAC,CAO3B;AAED,wBAAsB,sBAAsB,CAAC,IAAI,EAAE;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAsB/B;AAGD,wBAAsB,kBAAkB,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC;IACzE,OAAO,EAAE,oBAAoB,CAAC;IAC9B,QAAQ,EAAE,qBAAqB,CAAC;IAChC,SAAS,EAAE,wBAAwB,CAAC;CACrC,CAAC,CAGD"}
@@ -0,0 +1,47 @@
1
+ // 包装 @hirey-ai/agent-sdk 的客户端构造,加 stale-identity quarantine。
2
+ // hi platform 用 OAuth client_credentials grant:identity 里的 client_id/secret 拿 access_token,
3
+ // 然后用 access_token 调 gateway/platform。
4
+ import { createHiAgentClients, exchangeHiAgentClientCredentialsToken, } from '@hirey-ai/agent-sdk';
5
+ import { quarantineStaleIdentityIfNeeded, readState, } from './state.js';
6
+ // 模块级 quarantine 通知:load state 时如果发现 stale,挂一个 in-memory flag,
7
+ // 让所有后续 tool response 都能 surface 一次(直到 plugin restart 自然清掉)。
8
+ let _lastQuarantineNotice = null;
9
+ export function peekQuarantineNotice() {
10
+ return _lastQuarantineNotice;
11
+ }
12
+ export async function loadStateWithQuarantine(stateDir, profile, currentPlatformBaseUrl) {
13
+ const raw = await readState(stateDir, profile);
14
+ const { state, quarantined } = await quarantineStaleIdentityIfNeeded(stateDir, profile, raw, currentPlatformBaseUrl);
15
+ if (quarantined)
16
+ _lastQuarantineNotice = quarantined;
17
+ return state;
18
+ }
19
+ export async function buildAuthorizedClients(args) {
20
+ const state = await loadStateWithQuarantine(args.stateDir, args.profile, args.platformBaseUrl);
21
+ if (!state.identity) {
22
+ throw new Error('hi_identity_missing: run hi_agent_install before calling authorized tools');
23
+ }
24
+ const token = await exchangeHiAgentClientCredentialsToken({
25
+ tokenUrl: state.identity.token_url,
26
+ clientId: state.identity.client_id,
27
+ clientSecret: state.identity.client_secret,
28
+ });
29
+ const clients = await createHiAgentClients({
30
+ platformBaseUrl: args.platformBaseUrl,
31
+ token: token.access_token,
32
+ });
33
+ return {
34
+ state,
35
+ accessToken: token.access_token,
36
+ gateway: clients.gateway,
37
+ platform: clients.platform,
38
+ wellKnown: clients.wellKnown,
39
+ quarantined: peekQuarantineNotice(),
40
+ };
41
+ }
42
+ // 不需要 OAuth 的端点(register / well-known / public capabilities listing),直接构造 client。
43
+ export async function buildPublicClients(platformBaseUrl) {
44
+ const clients = await createHiAgentClients({ platformBaseUrl, token: '' });
45
+ return clients;
46
+ }
47
+ //# sourceMappingURL=clients.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clients.js","sourceRoot":"","sources":["../src/clients.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,4FAA4F;AAC5F,uCAAuC;AAEvC,OAAO,EACL,oBAAoB,EACpB,qCAAqC,GAItC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,+BAA+B,EAC/B,SAAS,GAGV,MAAM,YAAY,CAAC;AAWpB,+DAA+D;AAC/D,6DAA6D;AAC7D,IAAI,qBAAqB,GAAmC,IAAI,CAAC;AAEjE,MAAM,UAAU,oBAAoB;IAClC,OAAO,qBAAqB,CAAC;AAC/B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,QAAgB,EAChB,OAAe,EACf,sBAA8B;IAE9B,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC/C,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,MAAM,+BAA+B,CAClE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,sBAAsB,CAC/C,CAAC;IACF,IAAI,WAAW;QAAE,qBAAqB,GAAG,WAAW,CAAC;IACrD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,IAI5C;IACC,MAAM,KAAK,GAAG,MAAM,uBAAuB,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;IAC/F,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,2EAA2E,CAAC,CAAC;IAC/F,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,qCAAqC,CAAC;QACxD,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,SAAS;QAClC,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,SAAS;QAClC,YAAY,EAAE,KAAK,CAAC,QAAQ,CAAC,aAAa;KAC3C,CAAC,CAAC;IACH,MAAM,OAAO,GAAG,MAAM,oBAAoB,CAAC;QACzC,eAAe,EAAE,IAAI,CAAC,eAAe;QACrC,KAAK,EAAE,KAAK,CAAC,YAAY;KAC1B,CAAC,CAAC;IACH,OAAO;QACL,KAAK;QACL,WAAW,EAAE,KAAK,CAAC,YAAY;QAC/B,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,WAAW,EAAE,oBAAoB,EAAE;KACpC,CAAC;AACJ,CAAC;AAED,kFAAkF;AAClF,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,eAAuB;IAK9D,MAAM,OAAO,GAAG,MAAM,oBAAoB,CAAC,EAAE,eAAe,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;IAC3E,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { PluginRegisterApi } from './types.js';
2
+ export default function registerHiOpenClawPlugin(api: PluginRegisterApi): void;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EACV,iBAAiB,EAGlB,MAAM,YAAY,CAAC;AAoBpB,MAAM,CAAC,OAAO,UAAU,wBAAwB,CAAC,GAAG,EAAE,iBAAiB,GAAG,IAAI,CA6E7E"}
package/dist/index.js ADDED
@@ -0,0 +1,92 @@
1
+ // hi-openclaw-plugin entry: register Hirey AI 的 control tools + 14 platform capabilities +
2
+ // agent-events claim service + webhook ingress route 进 OpenClaw gateway 进程。
3
+ //
4
+ // 设计原则:
5
+ // 1. Idempotent:plugin loader 可能用不同 api 实例多次调本入口(懒加载、CLI 热加载、gateway
6
+ // hot-apply)。WeakSet<api> 守 register 不要重复。
7
+ // 2. 缺能力 fail-soft:老 host 可能没 registerService 或 registerHttpRoute;feature-detect 后
8
+ // skip + warn,不要 throw 让 host 起不来。
9
+ // 3. 真业务全部委托给 @hirey-ai/agent-sdk + 平台 /v1/capabilities/<id>/call,本文件只做 wiring。
10
+ import { buildAllControlTools } from './tools/control.js';
11
+ import { buildAllCapabilityTools } from './tools/capabilities.js';
12
+ import { buildAgentEventsService } from './services/agent-events.js';
13
+ import { buildWebhookRoute } from './routes/webhook.js';
14
+ import { ensurePluginToolsAlsoAllowed } from './utils/openclaw-config.js';
15
+ const _registeredApis = new WeakSet();
16
+ function defaultedConfig(raw) {
17
+ return {
18
+ platformBaseUrl: raw?.platformBaseUrl?.trim() || 'https://hi.hirey.ai',
19
+ profile: raw?.profile?.trim() || 'openclaw-main',
20
+ stateDir: raw?.stateDir?.trim() || '',
21
+ webhookPath: raw?.webhookPath?.trim() || '/hi/webhook',
22
+ claimPollIntervalMs: Math.max(250, Number(raw?.claimPollIntervalMs ?? 1500)),
23
+ claimLeaseMs: Math.max(1000, Number(raw?.claimLeaseMs ?? 60000)),
24
+ };
25
+ }
26
+ export default function registerHiOpenClawPlugin(api) {
27
+ if (_registeredApis.has(api))
28
+ return;
29
+ _registeredApis.add(api);
30
+ const logger = api.logger ?? console;
31
+ const config = defaultedConfig(api.pluginConfig);
32
+ // ---- tools ----
33
+ // OpenClaw SDK 的 registerTool 签名是:
34
+ // api.registerTool(factory: (ctx) => Tool | null, options: { names: [singleName] })
35
+ // 每次调一个 tool。factory 在每个 LLM session 启动时被调一次,返回该 session 可见的 tool 实例
36
+ // (可以根据 ctx.agentId / ctx.sessionKey 决定是否暴露)。这里我们让每个 hi tool 总是 visible(无 gating)。
37
+ if (typeof api.registerTool !== 'function') {
38
+ logger.warn?.('[hi-openclaw-plugin] host does not expose api.registerTool; tools will not be visible. Upgrade OpenClaw to >=2026.4.23.');
39
+ }
40
+ else {
41
+ const controlTools = buildAllControlTools(config);
42
+ const capabilityTools = buildAllCapabilityTools(config);
43
+ const allTools = [...controlTools, ...capabilityTools];
44
+ for (const tool of allTools) {
45
+ api.registerTool(() => tool, { names: [tool.name] });
46
+ }
47
+ logger.info?.('[hi-openclaw-plugin] registered tools', { count: allTools.length, names: allTools.map(t => t.name) });
48
+ }
49
+ // ---- service / route ----
50
+ // 1.0.9 起恢复 daemon claim/SSE 模式,但走 push 推送架构(拉到 event 后 POST hooks/agent
51
+ // loopback,OpenClaw 自动按 channel/to 把 LLM 输出推到用户 iMessage/Telegram 等已配置的
52
+ // channel)。
53
+ //
54
+ // 修 OOM 的关键不在去循环(业务上 push 必须长跑,pull 模型是错的),而在:
55
+ // - 主路径换成 SSE pull_stream 长连接(1 个 conn hold 60+ 秒,重连周期分钟级),不再
56
+ // 每 1.5s 新建 fetch agent
57
+ // - 启动 + 重连前先 claim drain backlog 兜底
58
+ // - 不缓存 client(每次重连重建,跟官方 hirey-ai-agent-receiver runStreamLoop 等价)
59
+ // - hooks/agent 投递的 fetch 是 fire-and-forget,不长占 socket
60
+ if (typeof api.registerService === 'function') {
61
+ api.registerService(buildAgentEventsService(config));
62
+ }
63
+ else {
64
+ logger.warn?.('[hi-openclaw-plugin] host does not expose api.registerService; agent-events daemon will not run, push delivery disabled. Upgrade OpenClaw to >=2026.4.23.');
65
+ }
66
+ if (typeof api.registerHttpRoute === 'function') {
67
+ api.registerHttpRoute(buildWebhookRoute(config, logger));
68
+ }
69
+ // ---- profile self-heal ----
70
+ // OpenClaw 的 tools.profile=coding 默认不让 plugin tools 出现在 LLM 的 toolbox。要让用户
71
+ // 装完 plugin 立刻可用,第一次 register 时主动检查 + patch tools.alsoAllow 加 group:plugins。
72
+ // 这件事不能等 LLM 调 hi_agent_install 时才做:那时 LLM 早已没看见 hi_agent_install。
73
+ // 也不能依赖 LLM 自己读懂 OpenClaw 配置语义后改:之前实测 LLM 把 alsoAllow 写成 allow,
74
+ // explicit allow override 把 read/exec/sessions_* 等内置工具全 filter 掉,整个 LLM run 没工具用。
75
+ // 因此 plugin 自己幂等 patch,atomic write,patch 完不立刻 restart gateway —— config hot-reload
76
+ // watcher 会自然 pick up,下一个 LLM session 起来 tool inventory 就 contains hi_*。
77
+ void ensurePluginToolsAlsoAllowed()
78
+ .then((res) => {
79
+ if (res.changed) {
80
+ logger.info?.('[hi-openclaw-plugin] auto-patched tools.alsoAllow=group:plugins so plugin tools become visible to LLM in coding profile', {
81
+ before: res.also_allow_before, after: res.also_allow_after,
82
+ });
83
+ }
84
+ })
85
+ .catch((err) => {
86
+ logger.warn?.('[hi-openclaw-plugin] tools.alsoAllow auto-patch failed (LLM may need to do it manually)', {
87
+ error: String(err?.message || err),
88
+ });
89
+ });
90
+ logger.info?.('[hi-openclaw-plugin] registered v1.0.0', { platform: config.platformBaseUrl, profile: config.profile, webhook: config.webhookPath });
91
+ }
92
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,4EAA4E;AAC5E,EAAE;AACF,QAAQ;AACR,qEAAqE;AACrE,8CAA8C;AAC9C,mFAAmF;AACnF,sCAAsC;AACtC,gFAAgF;AAOhF,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAC;AAE1E,MAAM,eAAe,GAAG,IAAI,OAAO,EAAqB,CAAC;AAEzD,SAAS,eAAe,CAAC,GAAuC;IAC9D,OAAO;QACL,eAAe,EAAE,GAAG,EAAE,eAAe,EAAE,IAAI,EAAE,IAAI,qBAAqB;QACtE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,eAAe;QAChD,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE;QACrC,WAAW,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,aAAa;QACtD,mBAAmB,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,mBAAmB,IAAI,IAAI,CAAC,CAAC;QAC5E,YAAY,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,EAAE,YAAY,IAAI,KAAK,CAAC,CAAC;KACjE,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,OAAO,UAAU,wBAAwB,CAAC,GAAsB;IACrE,IAAI,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO;IACrC,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAEzB,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,OAAO,CAAC;IACrC,MAAM,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAEjD,kBAAkB;IAClB,mCAAmC;IACnC,sFAAsF;IACtF,qEAAqE;IACrE,mFAAmF;IACnF,IAAI,OAAO,GAAG,CAAC,YAAY,KAAK,UAAU,EAAE,CAAC;QAC3C,MAAM,CAAC,IAAI,EAAE,CACX,yHAAyH,CAC1H,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,MAAM,YAAY,GAAG,oBAAoB,CAAC,MAAM,CAAC,CAAC;QAClD,MAAM,eAAe,GAAG,uBAAuB,CAAC,MAAM,CAAC,CAAC;QACxD,MAAM,QAAQ,GAA2B,CAAC,GAAG,YAAY,EAAE,GAAG,eAAe,CAAC,CAAC;QAC/E,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;YAC5B,GAAG,CAAC,YAAY,CACd,GAAG,EAAE,CAAC,IAAI,EACV,EAAE,KAAK,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACvB,CAAC;QACJ,CAAC;QACD,MAAM,CAAC,IAAI,EAAE,CAAC,uCAAuC,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACvH,CAAC;IAED,4BAA4B;IAC5B,yEAAyE;IACzE,wEAAwE;IACxE,YAAY;IACZ,EAAE;IACF,+CAA+C;IAC/C,gEAAgE;IAChE,4BAA4B;IAC5B,uCAAuC;IACvC,sEAAsE;IACtE,yDAAyD;IACzD,IAAI,OAAO,GAAG,CAAC,eAAe,KAAK,UAAU,EAAE,CAAC;QAC9C,GAAG,CAAC,eAAe,CAAC,uBAAuB,CAAC,MAAM,CAAC,CAAC,CAAC;IACvD,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,IAAI,EAAE,CACX,2JAA2J,CAC5J,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,GAAG,CAAC,iBAAiB,KAAK,UAAU,EAAE,CAAC;QAChD,GAAG,CAAC,iBAAiB,CAAC,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3D,CAAC;IAED,8BAA8B;IAC9B,2EAA2E;IAC3E,6EAA6E;IAC7E,mEAAmE;IACnE,gEAAgE;IAChE,kFAAkF;IAClF,oFAAoF;IACpF,yEAAyE;IACzE,KAAK,4BAA4B,EAAE;SAChC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;QACZ,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;YAChB,MAAM,CAAC,IAAI,EAAE,CAAC,yHAAyH,EAAE;gBACvI,MAAM,EAAE,GAAG,CAAC,iBAAiB,EAAE,KAAK,EAAE,GAAG,CAAC,gBAAgB;aAC3D,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC;SACD,KAAK,CAAC,CAAC,GAAQ,EAAE,EAAE;QAClB,MAAM,CAAC,IAAI,EAAE,CAAC,yFAAyF,EAAE;YACvG,KAAK,EAAE,MAAM,CAAC,GAAG,EAAE,OAAO,IAAI,GAAG,CAAC;SACnC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEL,MAAM,CAAC,IAAI,EAAE,CACX,wCAAwC,EACxC,EAAE,QAAQ,EAAE,MAAM,CAAC,eAAe,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,WAAW,EAAE,CAC3F,CAAC;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { PluginHttpRouteDefinition, PluginLogger, HiOpenClawPluginConfig } from '../types.js';
2
+ export declare function getQueueSnapshot(): Array<Record<string, unknown>>;
3
+ export declare function clearQueue(): void;
4
+ export declare function pushEventToQueue(ev: Record<string, unknown>): void;
5
+ export declare function buildWebhookRoute(config: Required<HiOpenClawPluginConfig>, logger: PluginLogger): PluginHttpRouteDefinition;
6
+ //# sourceMappingURL=webhook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/routes/webhook.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,yBAAyB,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAOnG,wBAAgB,gBAAgB,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAEjE;AAED,wBAAgB,UAAU,IAAI,IAAI,CAEjC;AAID,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAGlE;AAYD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,QAAQ,CAAC,sBAAsB,CAAC,EACxC,MAAM,EAAE,YAAY,GACnB,yBAAyB,CAwC3B"}
@@ -0,0 +1,76 @@
1
+ // 接收来自 platform 的 agent events(通过 agent-events service loopback 或 platform 直接 push)。
2
+ // 单进程内执行:read body → parse envelope → 决定怎么把 event 桥接到当前 LLM session。
3
+ //
4
+ // 当前 1.0.0 版本:route 接受 POST + 校验 envelope shape,把 event 写入 plugin 自己的 in-memory
5
+ // queue。下一阶段(plugin SDK 提供 api.dispatch / api.deliverEvent 时)真正 bridge 进 LLM session。
6
+ // 现阶段配 hi_agent_events_wait 之类的 polling tool 让 LLM 主动 fetch(避免阻塞 plugin SDK 决定)。
7
+ // in-memory event queue(process-lifetime;plugin restart 后清空,但 platform 那侧 events 仍可
8
+ // 通过 claim loop 重新拉到,因为 ack 了才算 consumed)。
9
+ const _queue = [];
10
+ const QUEUE_MAX = 500;
11
+ export function getQueueSnapshot() {
12
+ return [..._queue];
13
+ }
14
+ export function clearQueue() {
15
+ _queue.length = 0;
16
+ }
17
+ // 给 services/agent-events.ts 用 —— 进程内直推 event,不走 fetch 也不读 env,避开 OpenClaw
18
+ // install scanner 的 "credential harvesting (env + network)" 误报。
19
+ export function pushEventToQueue(ev) {
20
+ _queue.push(ev);
21
+ while (_queue.length > QUEUE_MAX)
22
+ _queue.shift();
23
+ }
24
+ async function readBody(req) {
25
+ return new Promise((resolve, reject) => {
26
+ let raw = '';
27
+ req.setEncoding?.('utf8');
28
+ req.on('data', (chunk) => { raw += chunk; });
29
+ req.on('end', () => resolve(raw));
30
+ req.on('error', reject);
31
+ });
32
+ }
33
+ export function buildWebhookRoute(config, logger) {
34
+ return {
35
+ path: config.webhookPath,
36
+ auth: 'plugin',
37
+ match: 'exact',
38
+ handler: async (req, res) => {
39
+ if (req.method !== 'POST') {
40
+ res.statusCode = 405;
41
+ res.setHeader('content-type', 'application/json');
42
+ res.end(JSON.stringify({ ok: false, error: 'method_not_allowed' }));
43
+ return;
44
+ }
45
+ try {
46
+ const raw = await readBody(req);
47
+ const env = raw ? JSON.parse(raw) : {};
48
+ if (typeof env !== 'object' || !env) {
49
+ res.statusCode = 400;
50
+ res.setHeader('content-type', 'application/json');
51
+ res.end(JSON.stringify({ ok: false, error: 'invalid_envelope' }));
52
+ return;
53
+ }
54
+ // queue with bounded size (drop oldest if overflow)
55
+ _queue.push(env);
56
+ while (_queue.length > QUEUE_MAX)
57
+ _queue.shift();
58
+ logger.info?.('[hi-openclaw-plugin] webhook event queued', {
59
+ topic: env?.topic ?? null,
60
+ event_id: env?.event_id ?? null,
61
+ queue_size: _queue.length,
62
+ });
63
+ res.statusCode = 200;
64
+ res.setHeader('content-type', 'application/json');
65
+ res.end(JSON.stringify({ ok: true, queued: true, queue_size: _queue.length }));
66
+ }
67
+ catch (err) {
68
+ logger.warn?.('[hi-openclaw-plugin] webhook handler error', { error: String(err?.message || err) });
69
+ res.statusCode = 500;
70
+ res.setHeader('content-type', 'application/json');
71
+ res.end(JSON.stringify({ ok: false, error: 'webhook_handler_error', detail: String(err?.message || err) }));
72
+ }
73
+ },
74
+ };
75
+ }
76
+ //# sourceMappingURL=webhook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../src/routes/webhook.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { PluginServiceDefinition, HiOpenClawPluginConfig } from '../types.js';
2
+ export declare function buildAgentEventsService(config: Required<HiOpenClawPluginConfig>): PluginServiceDefinition;
3
+ //# sourceMappingURL=agent-events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-events.d.ts","sourceRoot":"","sources":["../../src/services/agent-events.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAEV,uBAAuB,EACvB,sBAAsB,EACvB,MAAM,aAAa,CAAC;AAwDrB,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,QAAQ,CAAC,sBAAsB,CAAC,GACvC,uBAAuB,CAmNzB"}
@@ -0,0 +1,267 @@
1
+ // Hi push 推送 daemon —— OpenClaw native plugin 内的 in-process 长跑 service。
2
+ //
3
+ // 等价于老 hirey-ai-agent-receiver 的 runStreamLoop:
4
+ // 1. 启动 → 先 claim drain backlog(把暂存的 owner-actionable events 清掉)
5
+ // 2. 主路径连 SSE pull_stream(一个长连接 hold 60+ 秒,server 主动推 event id)
6
+ // 3. 收到 SSE event id → fetch event detail → 投递给 OpenClaw → ack consumed
7
+ // 4. SSE 断了 → backoff + 回到第 1 步重新 drain + 重连
8
+ //
9
+ // 这种 outbound-initiated 长连接是 NAT 后面 local agent 收云上 push 的业界 best practice
10
+ // (2026 年 webhook 主导期已经被 SSE / claim 取代),跟 hirey-ai-agent-receiver 的官方 daemon 一致。
11
+ //
12
+ // 投递路径:daemon 拉到 event 后通过 buildOpenClawHookPayloadWithRoute 转成跟老 receiver
13
+ // 完全一致的 hook payload,POST 到本机 OpenClaw gateway 的 /<hooks.path>/agent 端点。
14
+ // gateway 收到 → dispatchAgentHook → runCronIsolatedAgentTurn → LLM 跑 turn
15
+ // → OpenClaw 按 hook payload 里的 channel/to 自动通过 iMessage / Telegram 等已注册 channel
16
+ // 把 LLM 输出送到用户。这一段 OpenClaw 自己负责,plugin 不参与。
17
+ //
18
+ // 关键设计:每次 SSE 重连 / drain 都 fresh 重建 OAuth + clients,不缓存,跟 receiver 完全
19
+ // 等价。SSE 断重连周期是分钟级(不是秒级),所以重建 client 的 GC 压力小,不会像 1.0.4 那样
20
+ // 每秒新 fetch agent 把 gateway 进程 4 GB heap 撑爆。
21
+ import { buildAuthorizedClients, } from '../clients.js';
22
+ import { resolveStateDir, readState, updateState } from '../state.js';
23
+ import { buildOpenClawHookPayloadWithRoute } from '../utils/openclaw-hooks-payload.js';
24
+ import { streamAgentEvents } from '@hirey-ai/agent-sdk';
25
+ // SSE 重连之间的最小等待,避免连接抖动时疯狂重连。线性 backoff 上限。
26
+ const RECONNECT_BASE_MS = 2_000;
27
+ const RECONNECT_MAX_MS = 30_000;
28
+ // 没装 identity / hooks 配置时的 idle backoff。daemon 不会强求 install,等 install_tool 跑完。
29
+ const IDLE_BACKOFF_MS = 30_000;
30
+ function resolveHooksUrl(rt) {
31
+ const path = rt.hooks_path.startsWith('/') ? rt.hooks_path : `/${rt.hooks_path}`;
32
+ // path 形如 "/hooks";最终 endpoint 是 "/hooks/agent"
33
+ const cleanedPath = path.endsWith('/') ? path.slice(0, -1) : path;
34
+ return `http://127.0.0.1:${rt.gateway_port}${cleanedPath}/agent`;
35
+ }
36
+ async function deliverEventToHooks(args) {
37
+ // 投递 hook payload:跟老 receiver 完全同形态。
38
+ const body = buildOpenClawHookPayloadWithRoute({ event: args.event, config: null });
39
+ const response = await fetch(args.hooksUrl, {
40
+ method: 'POST',
41
+ headers: {
42
+ 'content-type': 'application/json',
43
+ 'authorization': `Bearer ${args.hooksToken}`,
44
+ },
45
+ body: JSON.stringify(body),
46
+ });
47
+ const text = await response.text();
48
+ if (!response.ok) {
49
+ args.logger.warn?.('[hi-openclaw-plugin] hook delivery failed', {
50
+ status: response.status,
51
+ url: args.hooksUrl,
52
+ body_preview: text.slice(0, 240),
53
+ });
54
+ return { ok: false, status: response.status, body: text };
55
+ }
56
+ return { ok: true, status: response.status, body: text };
57
+ }
58
+ export function buildAgentEventsService(config) {
59
+ const stateDir = config.stateDir || resolveStateDir(config.profile);
60
+ let stopped = false;
61
+ let activeAbort = null;
62
+ return {
63
+ id: 'hi-agent-events',
64
+ async start(ctx) {
65
+ stopped = false;
66
+ const logger = ctx.logger;
67
+ logger.info?.('[hi-openclaw-plugin] agent-events service starting (sse pull_stream + claim drain)', {
68
+ profile: config.profile,
69
+ platform: config.platformBaseUrl,
70
+ });
71
+ const isReady = async () => {
72
+ const state = await readState(stateDir, config.profile);
73
+ if (!state.identity)
74
+ return { auth: null, rt: null };
75
+ const inst = state.runtime?.install;
76
+ if (!inst?.hooks_token || !inst?.hooks_path || !inst?.gateway_port) {
77
+ return { auth: null, rt: null };
78
+ }
79
+ const auth = await buildAuthorizedClients({
80
+ stateDir, profile: config.profile, platformBaseUrl: config.platformBaseUrl,
81
+ }).catch((err) => {
82
+ const msg = String(err?.message || err);
83
+ if (!msg.includes('hi_identity_missing')) {
84
+ logger.warn?.('[hi-openclaw-plugin] auth client build failed', { error: msg });
85
+ }
86
+ return null;
87
+ });
88
+ if (!auth)
89
+ return { auth: null, rt: null };
90
+ return {
91
+ auth,
92
+ rt: {
93
+ hooks_token: inst.hooks_token,
94
+ hooks_path: inst.hooks_path,
95
+ gateway_port: inst.gateway_port,
96
+ },
97
+ };
98
+ };
99
+ // 把一个 event snapshot 走完整的 deliver-then-ack 流程。
100
+ const deliverAndAck = async (params) => {
101
+ const { auth, rt, event, leaseId } = params;
102
+ const hooksUrl = resolveHooksUrl(rt);
103
+ let result = null;
104
+ try {
105
+ const r = await deliverEventToHooks({
106
+ hooksUrl, hooksToken: rt.hooks_token, event, logger,
107
+ });
108
+ result = r;
109
+ }
110
+ catch (err) {
111
+ // 投递异常 → ack failed,平台后续重投。
112
+ await auth.gateway.ackEvents({
113
+ ...(leaseId ? { lease_id: leaseId } : {}),
114
+ acks: [{
115
+ event_id: event.event_id,
116
+ status: 'failed',
117
+ last_error: String(err?.message || err),
118
+ retry_after_ms: 60_000,
119
+ }],
120
+ }).catch(() => { });
121
+ return;
122
+ }
123
+ if (!result.ok) {
124
+ await auth.gateway.ackEvents({
125
+ ...(leaseId ? { lease_id: leaseId } : {}),
126
+ acks: [{
127
+ event_id: event.event_id,
128
+ status: 'failed',
129
+ last_error: `hook_delivery_${result.status}`,
130
+ retry_after_ms: 60_000,
131
+ }],
132
+ }).catch(() => { });
133
+ return;
134
+ }
135
+ await auth.gateway.ackEvents({
136
+ ...(leaseId ? { lease_id: leaseId } : {}),
137
+ acks: [{
138
+ event_id: event.event_id,
139
+ status: 'consumed',
140
+ }],
141
+ }).catch((err) => {
142
+ logger.warn?.('[hi-openclaw-plugin] event ack failed', { error: String(err?.message || err) });
143
+ });
144
+ // bump runtime cursor
145
+ await updateState(stateDir, config.profile, (cur) => ({
146
+ ...cur,
147
+ runtime: {
148
+ ...cur.runtime,
149
+ last_consumed_stream_seq: Math.max(cur.runtime.last_consumed_stream_seq, Number(event.stream_seq) || 0),
150
+ updated_at: new Date().toISOString(),
151
+ },
152
+ })).catch(() => { });
153
+ };
154
+ // 启动 / 重连前 drain backlog —— 用 short claim 把已经累积的 events 清完。
155
+ const drainBacklog = async (auth, rt) => {
156
+ while (!stopped) {
157
+ const state = await readState(stateDir, config.profile);
158
+ const claimed = await auth.gateway.claimEvents({
159
+ after_seq: state.runtime.last_consumed_stream_seq || 0,
160
+ limit: 20,
161
+ ...(state.runtime.last_claim_lease_id ? { claim_lease_id: state.runtime.last_claim_lease_id } : {}),
162
+ });
163
+ await updateState(stateDir, config.profile, (cur) => ({
164
+ ...cur,
165
+ runtime: {
166
+ ...cur.runtime,
167
+ last_claim_lease_id: claimed.claim_lease_id || cur.runtime.last_claim_lease_id,
168
+ },
169
+ })).catch(() => { });
170
+ const items = (claimed?.items || []);
171
+ if (items.length === 0)
172
+ return;
173
+ logger.info?.(`[hi-openclaw-plugin] drained ${items.length} backlog event(s)`, {
174
+ first_topic: items[0]?.topic,
175
+ });
176
+ for (const ev of items) {
177
+ if (stopped)
178
+ return;
179
+ await deliverAndAck({ auth, rt, event: ev, leaseId: claimed.claim_lease_id });
180
+ }
181
+ }
182
+ };
183
+ // 一次 SSE 长连接 session。流抽干 / 断 / throw 时返回,外层做重连。
184
+ const runOneSseSession = async (auth, rt) => {
185
+ const state = await readState(stateDir, config.profile);
186
+ const lastSeq = state.runtime.last_consumed_stream_seq || 0;
187
+ const streamUrl = auth.gateway.streamUrl
188
+ ? auth.gateway.streamUrl(lastSeq)
189
+ : `${config.platformBaseUrl}/v1/agent-events/stream${lastSeq ? `?after_seq=${lastSeq}` : ''}`;
190
+ logger.info?.('[hi-openclaw-plugin] sse connect', { url: streamUrl, after_seq: lastSeq });
191
+ for await (const envelope of streamAgentEvents({
192
+ url: streamUrl,
193
+ token: auth.accessToken,
194
+ })) {
195
+ if (stopped)
196
+ return;
197
+ if (envelope.event !== 'agent_event')
198
+ continue;
199
+ const eventId = envelope.data?.event_id;
200
+ if (!eventId || typeof eventId !== 'string')
201
+ continue;
202
+ // SSE envelope 里通常是 envelope(精简);一些字段(reply_route_snapshot / payload)可能要 fetch
203
+ // 完整 snapshot 才有,跟 receiver 一致。
204
+ let fullEvent = envelope.data;
205
+ if (typeof auth.gateway.fetchEvent === 'function') {
206
+ try {
207
+ const fetched = await auth.gateway.fetchEvent(eventId);
208
+ if (fetched?.ok && fetched?.event)
209
+ fullEvent = fetched.event;
210
+ }
211
+ catch (err) {
212
+ logger.warn?.('[hi-openclaw-plugin] fetchEvent failed, using envelope only', {
213
+ event_id: eventId,
214
+ error: String(err?.message || err),
215
+ });
216
+ }
217
+ }
218
+ await deliverAndAck({ auth, rt, event: fullEvent, leaseId: null });
219
+ }
220
+ };
221
+ // 主循环:drain → SSE → drain → SSE …
222
+ const runMainLoop = async () => {
223
+ let backoffMs = RECONNECT_BASE_MS;
224
+ while (!stopped) {
225
+ const ready = await isReady();
226
+ if (!ready.auth || !ready.rt) {
227
+ await sleep(IDLE_BACKOFF_MS);
228
+ continue;
229
+ }
230
+ try {
231
+ await drainBacklog(ready.auth, ready.rt);
232
+ backoffMs = RECONNECT_BASE_MS;
233
+ await runOneSseSession(ready.auth, ready.rt);
234
+ // 流自然结束(远端关闭),立刻重新 drain + 重连
235
+ backoffMs = RECONNECT_BASE_MS;
236
+ }
237
+ catch (err) {
238
+ const msg = String(err?.message || err);
239
+ logger.warn?.('[hi-openclaw-plugin] sse loop error, will reconnect', { error: msg, backoff_ms: backoffMs });
240
+ await sleep(backoffMs);
241
+ backoffMs = Math.min(backoffMs * 2, RECONNECT_MAX_MS);
242
+ }
243
+ }
244
+ };
245
+ // 用 abort signal 让 stop() 能干净中断 SSE / sleep
246
+ const abort = new AbortController();
247
+ activeAbort = abort;
248
+ void runMainLoop();
249
+ logger.info?.('[hi-openclaw-plugin] agent-events service started');
250
+ },
251
+ async stop(ctx) {
252
+ stopped = true;
253
+ if (activeAbort) {
254
+ try {
255
+ activeAbort.abort();
256
+ }
257
+ catch { }
258
+ activeAbort = null;
259
+ }
260
+ ctx.logger.info?.('[hi-openclaw-plugin] agent-events service stopped');
261
+ },
262
+ };
263
+ }
264
+ function sleep(ms) {
265
+ return new Promise((resolve) => setTimeout(resolve, ms));
266
+ }
267
+ //# sourceMappingURL=agent-events.js.map