@ken-jo/agent-connector 0.1.0

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 (203) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +6 -0
  3. package/README.md +348 -0
  4. package/dist/amp-QSFH2V7Y.js +217 -0
  5. package/dist/amp-QSFH2V7Y.js.map +1 -0
  6. package/dist/amp-TULUPXXL.js +237 -0
  7. package/dist/amp-TULUPXXL.js.map +1 -0
  8. package/dist/antigravity-CX2YLSY7.js +17 -0
  9. package/dist/antigravity-CX2YLSY7.js.map +1 -0
  10. package/dist/antigravity-STHZWMP3.js +171 -0
  11. package/dist/antigravity-STHZWMP3.js.map +1 -0
  12. package/dist/antigravity-cli-P6HADOAO.js +121 -0
  13. package/dist/antigravity-cli-P6HADOAO.js.map +1 -0
  14. package/dist/antigravity-cli-YQDFVV4U.js +86 -0
  15. package/dist/antigravity-cli-YQDFVV4U.js.map +1 -0
  16. package/dist/chunk-54EMKTTX.js +191 -0
  17. package/dist/chunk-54EMKTTX.js.map +1 -0
  18. package/dist/chunk-7L6ITC4T.js +54 -0
  19. package/dist/chunk-7L6ITC4T.js.map +1 -0
  20. package/dist/chunk-7MSDGHJW.js +316 -0
  21. package/dist/chunk-7MSDGHJW.js.map +1 -0
  22. package/dist/chunk-BCHWFTV4.js +196 -0
  23. package/dist/chunk-BCHWFTV4.js.map +1 -0
  24. package/dist/chunk-DBQMX75U.js +36 -0
  25. package/dist/chunk-DBQMX75U.js.map +1 -0
  26. package/dist/chunk-EXLS7BSI.js +29 -0
  27. package/dist/chunk-EXLS7BSI.js.map +1 -0
  28. package/dist/chunk-JNM3TPLX.js +593 -0
  29. package/dist/chunk-JNM3TPLX.js.map +1 -0
  30. package/dist/chunk-JWNT5KW7.js +197 -0
  31. package/dist/chunk-JWNT5KW7.js.map +1 -0
  32. package/dist/chunk-KHO5WNTP.js +30 -0
  33. package/dist/chunk-KHO5WNTP.js.map +1 -0
  34. package/dist/chunk-LTT56GI3.js +155 -0
  35. package/dist/chunk-LTT56GI3.js.map +1 -0
  36. package/dist/chunk-MEH7GC7R.js +111 -0
  37. package/dist/chunk-MEH7GC7R.js.map +1 -0
  38. package/dist/chunk-MQPNU2CY.js +64 -0
  39. package/dist/chunk-MQPNU2CY.js.map +1 -0
  40. package/dist/chunk-OPXAP3AC.js +12 -0
  41. package/dist/chunk-OPXAP3AC.js.map +1 -0
  42. package/dist/chunk-RK6VBLXP.js +50 -0
  43. package/dist/chunk-RK6VBLXP.js.map +1 -0
  44. package/dist/chunk-TIIP3RG2.js +420 -0
  45. package/dist/chunk-TIIP3RG2.js.map +1 -0
  46. package/dist/chunk-TRKWUNO4.js +702 -0
  47. package/dist/chunk-TRKWUNO4.js.map +1 -0
  48. package/dist/chunk-TRQKHP7D.js +93 -0
  49. package/dist/chunk-TRQKHP7D.js.map +1 -0
  50. package/dist/chunk-WCETQWWK.js +59 -0
  51. package/dist/chunk-WCETQWWK.js.map +1 -0
  52. package/dist/chunk-WV63X7IK.js +357 -0
  53. package/dist/chunk-WV63X7IK.js.map +1 -0
  54. package/dist/chunk-XD7MY6HB.js +343 -0
  55. package/dist/chunk-XD7MY6HB.js.map +1 -0
  56. package/dist/chunk-XNAM7F35.js +276 -0
  57. package/dist/chunk-XNAM7F35.js.map +1 -0
  58. package/dist/chunk-XUCWLJY5.js +238 -0
  59. package/dist/chunk-XUCWLJY5.js.map +1 -0
  60. package/dist/chunk-YNZU6PAV.js +800 -0
  61. package/dist/chunk-YNZU6PAV.js.map +1 -0
  62. package/dist/chunk-YT43MUSJ.js +146 -0
  63. package/dist/chunk-YT43MUSJ.js.map +1 -0
  64. package/dist/claude-code-HKDV7T32.js +637 -0
  65. package/dist/claude-code-HKDV7T32.js.map +1 -0
  66. package/dist/claude-code-W74MNTAC.js +652 -0
  67. package/dist/claude-code-W74MNTAC.js.map +1 -0
  68. package/dist/cli/sdk.d.ts +54 -0
  69. package/dist/cli/sdk.js +82 -0
  70. package/dist/cli/sdk.js.map +1 -0
  71. package/dist/cli.d.ts +2 -0
  72. package/dist/cli.js +15 -0
  73. package/dist/cli.js.map +1 -0
  74. package/dist/codebuff-3PMZJJHP.js +307 -0
  75. package/dist/codebuff-3PMZJJHP.js.map +1 -0
  76. package/dist/codebuff-QX57FJDN.js +220 -0
  77. package/dist/codebuff-QX57FJDN.js.map +1 -0
  78. package/dist/codex-JFQTS6LU.js +593 -0
  79. package/dist/codex-JFQTS6LU.js.map +1 -0
  80. package/dist/codex-Q3ATRRX3.js +364 -0
  81. package/dist/codex-Q3ATRRX3.js.map +1 -0
  82. package/dist/copilot-cli-MTCGHZ2T.js +622 -0
  83. package/dist/copilot-cli-MTCGHZ2T.js.map +1 -0
  84. package/dist/copilot-cli-SJGIXVSD.js +369 -0
  85. package/dist/copilot-cli-SJGIXVSD.js.map +1 -0
  86. package/dist/crush-I7PK3FGI.js +360 -0
  87. package/dist/crush-I7PK3FGI.js.map +1 -0
  88. package/dist/crush-LN5QFFR2.js +183 -0
  89. package/dist/crush-LN5QFFR2.js.map +1 -0
  90. package/dist/cursor-4TMXKJOZ.js +603 -0
  91. package/dist/cursor-4TMXKJOZ.js.map +1 -0
  92. package/dist/cursor-QZ6MTO74.js +194 -0
  93. package/dist/cursor-QZ6MTO74.js.map +1 -0
  94. package/dist/detect-WIAGVYS4.js +68 -0
  95. package/dist/detect-WIAGVYS4.js.map +1 -0
  96. package/dist/doctor-EZW2K4NI.js +372 -0
  97. package/dist/doctor-EZW2K4NI.js.map +1 -0
  98. package/dist/droid-KN6VRMMT.js +424 -0
  99. package/dist/droid-KN6VRMMT.js.map +1 -0
  100. package/dist/droid-W5WF5XEX.js +224 -0
  101. package/dist/droid-W5WF5XEX.js.map +1 -0
  102. package/dist/gemini-cli-CIRW37TU.js +369 -0
  103. package/dist/gemini-cli-CIRW37TU.js.map +1 -0
  104. package/dist/gemini-cli-Y35FFK4M.js +705 -0
  105. package/dist/gemini-cli-Y35FFK4M.js.map +1 -0
  106. package/dist/goose-A3M5GODH.js +212 -0
  107. package/dist/goose-A3M5GODH.js.map +1 -0
  108. package/dist/goose-ZTGTSUFJ.js +480 -0
  109. package/dist/goose-ZTGTSUFJ.js.map +1 -0
  110. package/dist/hermes-H2KV44GK.js +470 -0
  111. package/dist/hermes-H2KV44GK.js.map +1 -0
  112. package/dist/hermes-ZYLMOFLZ.js +196 -0
  113. package/dist/hermes-ZYLMOFLZ.js.map +1 -0
  114. package/dist/hook-YVDILAMZ.js +75 -0
  115. package/dist/hook-YVDILAMZ.js.map +1 -0
  116. package/dist/index.d.ts +22 -0
  117. package/dist/index.js +10 -0
  118. package/dist/index.js.map +1 -0
  119. package/dist/install-HQRYZOQ2.js +60 -0
  120. package/dist/install-HQRYZOQ2.js.map +1 -0
  121. package/dist/jetbrains-copilot-HWA3DMH7.js +633 -0
  122. package/dist/jetbrains-copilot-HWA3DMH7.js.map +1 -0
  123. package/dist/kilo-NSWAVP6P.js +235 -0
  124. package/dist/kilo-NSWAVP6P.js.map +1 -0
  125. package/dist/kilo-QXJRCSLT.js +384 -0
  126. package/dist/kilo-QXJRCSLT.js.map +1 -0
  127. package/dist/kilo-cli-EAQTHXUW.js +587 -0
  128. package/dist/kilo-cli-EAQTHXUW.js.map +1 -0
  129. package/dist/kilo-cli-EVBKUODA.js +160 -0
  130. package/dist/kilo-cli-EVBKUODA.js.map +1 -0
  131. package/dist/kimi-EI3UM3ZV.js +155 -0
  132. package/dist/kimi-EI3UM3ZV.js.map +1 -0
  133. package/dist/kimi-GQ7VXNYT.js +423 -0
  134. package/dist/kimi-GQ7VXNYT.js.map +1 -0
  135. package/dist/kiro-UCLSLBW2.js +217 -0
  136. package/dist/kiro-UCLSLBW2.js.map +1 -0
  137. package/dist/kiro-VQTT6LO2.js +451 -0
  138. package/dist/kiro-VQTT6LO2.js.map +1 -0
  139. package/dist/leaderboard-K6ON3SWW.js +137 -0
  140. package/dist/leaderboard-K6ON3SWW.js.map +1 -0
  141. package/dist/mux-T2TV4CIQ.js +211 -0
  142. package/dist/mux-T2TV4CIQ.js.map +1 -0
  143. package/dist/mux-ZMLVAXOO.js +96 -0
  144. package/dist/mux-ZMLVAXOO.js.map +1 -0
  145. package/dist/omp-PB4VDKS6.js +586 -0
  146. package/dist/omp-PB4VDKS6.js.map +1 -0
  147. package/dist/openclaw-ETN2M63O.js +751 -0
  148. package/dist/openclaw-ETN2M63O.js.map +1 -0
  149. package/dist/openclaw-IYA7ASL6.js +150 -0
  150. package/dist/openclaw-IYA7ASL6.js.map +1 -0
  151. package/dist/opencode-FZ33JKUZ.js +734 -0
  152. package/dist/opencode-FZ33JKUZ.js.map +1 -0
  153. package/dist/opencode-HWT3RSMN.js +216 -0
  154. package/dist/opencode-HWT3RSMN.js.map +1 -0
  155. package/dist/package-DLOVE2E4.js +1132 -0
  156. package/dist/package-DLOVE2E4.js.map +1 -0
  157. package/dist/pi-CIU2QHT4.js +208 -0
  158. package/dist/pi-CIU2QHT4.js.map +1 -0
  159. package/dist/pi-UFX2OYFV.js +139 -0
  160. package/dist/pi-UFX2OYFV.js.map +1 -0
  161. package/dist/qwen-code-5POCNU6P.js +573 -0
  162. package/dist/qwen-code-5POCNU6P.js.map +1 -0
  163. package/dist/qwen-code-W7NUCDEN.js +134 -0
  164. package/dist/qwen-code-W7NUCDEN.js.map +1 -0
  165. package/dist/roo-code-IOIUXRDE.js +242 -0
  166. package/dist/roo-code-IOIUXRDE.js.map +1 -0
  167. package/dist/roo-code-ZLKH4A7N.js +229 -0
  168. package/dist/roo-code-ZLKH4A7N.js.map +1 -0
  169. package/dist/runtime/index.d.ts +157 -0
  170. package/dist/runtime/index.js +20 -0
  171. package/dist/runtime/index.js.map +1 -0
  172. package/dist/serve-HVWWY4B6.js +65 -0
  173. package/dist/serve-HVWWY4B6.js.map +1 -0
  174. package/dist/status-FE7TECZD.js +128 -0
  175. package/dist/status-FE7TECZD.js.map +1 -0
  176. package/dist/synthetic-UKR6SMGS.js +178 -0
  177. package/dist/synthetic-UKR6SMGS.js.map +1 -0
  178. package/dist/telemetry-36GESKQQ.js +349 -0
  179. package/dist/telemetry-36GESKQQ.js.map +1 -0
  180. package/dist/trae-JSNDZFKL.js +204 -0
  181. package/dist/trae-JSNDZFKL.js.map +1 -0
  182. package/dist/trae-PA26DPBW.js +157 -0
  183. package/dist/trae-PA26DPBW.js.map +1 -0
  184. package/dist/types-CYUB1OTr.d.ts +427 -0
  185. package/dist/uninstall-IGCKNOSF.js +72 -0
  186. package/dist/uninstall-IGCKNOSF.js.map +1 -0
  187. package/dist/upgrade-JV2VK242.js +104 -0
  188. package/dist/upgrade-JV2VK242.js.map +1 -0
  189. package/dist/usage-6ER6EMEY.js +324 -0
  190. package/dist/usage-6ER6EMEY.js.map +1 -0
  191. package/dist/usage-event-JKBI6XCF.js +48 -0
  192. package/dist/usage-event-JKBI6XCF.js.map +1 -0
  193. package/dist/vscode-copilot-CK3NO54M.js +715 -0
  194. package/dist/vscode-copilot-CK3NO54M.js.map +1 -0
  195. package/dist/warp-JNQA7PBU.js +126 -0
  196. package/dist/warp-JNQA7PBU.js.map +1 -0
  197. package/dist/warp-QNVDXOG4.js +199 -0
  198. package/dist/warp-QNVDXOG4.js.map +1 -0
  199. package/dist/zed-NNNX5MNT.js +272 -0
  200. package/dist/zed-NNNX5MNT.js.map +1 -0
  201. package/dist/zed-VRJKWYUR.js +209 -0
  202. package/dist/zed-VRJKWYUR.js.map +1 -0
  203. package/package.json +79 -0
@@ -0,0 +1,751 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ resolveEnvRefsDeep
4
+ } from "./chunk-DBQMX75U.js";
5
+ import {
6
+ BaseAdapter,
7
+ parseJsonc
8
+ } from "./chunk-TIIP3RG2.js";
9
+ import "./chunk-7L6ITC4T.js";
10
+ import {
11
+ buildServeWrapperCommand,
12
+ shouldWrapForTelemetry
13
+ } from "./chunk-MEH7GC7R.js";
14
+ import "./chunk-TRQKHP7D.js";
15
+
16
+ // src/adapters/openclaw/index.ts
17
+ import {
18
+ chmodSync,
19
+ existsSync,
20
+ mkdirSync,
21
+ readFileSync,
22
+ rmSync,
23
+ writeFileSync
24
+ } from "fs";
25
+ import { homedir } from "os";
26
+ import { dirname, join, resolve } from "path";
27
+ var HOST = "openclaw";
28
+ var MCP_ROOT_KEY = "mcp";
29
+ var MCP_SERVERS_KEY = "servers";
30
+ var PLUGINS_KEY = "plugins";
31
+ var PLUGINS_ENTRIES_KEY = "entries";
32
+ var PLUGINS_LOAD_KEY = "load";
33
+ var PLUGINS_LOAD_PATHS_KEY = "paths";
34
+ var PLUGIN_MANIFEST_FILE = "openclaw.plugin.json";
35
+ var EVENT_TO_OPENCLAW = {
36
+ PreToolUse: "before_tool_call",
37
+ PostToolUse: "after_tool_call",
38
+ SessionStart: "session_start"
39
+ };
40
+ var OpenClawAdapter = class extends BaseAdapter {
41
+ id = HOST;
42
+ name = "OpenClaw";
43
+ paradigm = "ts-plugin";
44
+ capabilities = {
45
+ preToolUse: true,
46
+ postToolUse: true,
47
+ // OpenClaw compaction is owned by a context engine, not wired through the
48
+ // universal bridge here.
49
+ preCompact: false,
50
+ sessionStart: true,
51
+ sessionEnd: false,
52
+ userPromptSubmit: false,
53
+ stop: false,
54
+ notification: false,
55
+ // before_tool_call mutates event.params in place → input rewrite supported.
56
+ canModifyArgs: true,
57
+ // after_tool_call observes but cannot rewrite the tool result upstream.
58
+ canModifyOutput: false,
59
+ // injected via before_prompt_build at prompt assembly.
60
+ canInjectSessionContext: true,
61
+ // OpenClaw's MCP sidecar registration is stdio-only in practice.
62
+ transports: ["stdio"]
63
+ };
64
+ // ── Tolerant JSON5/JSONC read (override base strict JSON.parse) ──────────
65
+ /**
66
+ * openclaw.json is officially JSON5 (comments + trailing commas allowed). A
67
+ * strict JSON.parse false-fails a perfectly valid commented file, so we strip
68
+ * comments and trailing commas before parsing. Writes remain strict JSON
69
+ * (writeJson in BaseAdapter), which is itself valid JSON5 — so round-trips
70
+ * stay safe and idempotent.
71
+ */
72
+ readJson(path) {
73
+ if (!existsSync(path)) return null;
74
+ let raw;
75
+ try {
76
+ raw = readFileSync(path, "utf8");
77
+ } catch {
78
+ return null;
79
+ }
80
+ try {
81
+ return parseJsonc(raw);
82
+ } catch {
83
+ return null;
84
+ }
85
+ }
86
+ // ── Detection ──────────────────────────────────────────────────────────
87
+ detectInstalled(projectDir) {
88
+ const userConfig = resolveOpenClawConfigPath();
89
+ const userDir = join(homedir(), ".openclaw");
90
+ const projectConfig = join(projectDir, "openclaw.json");
91
+ const userInstalled = existsSync(userConfig) || existsSync(userDir);
92
+ const projInstalled = existsSync(projectConfig);
93
+ const installed = userInstalled || projInstalled;
94
+ const scope = projInstalled && !userInstalled ? "project" : "user";
95
+ const configPath = scope === "project" ? projectConfig : userConfig;
96
+ return {
97
+ id: this.id,
98
+ name: this.name,
99
+ installed,
100
+ paradigm: this.paradigm,
101
+ capabilities: this.capabilities,
102
+ configPath,
103
+ scope,
104
+ reason: installed ? scope === "project" ? `found project OpenClaw config at ${projectConfig}` : `found OpenClaw config at ${configPath}` : `no OpenClaw config at ${userConfig} or ${userDir}`,
105
+ confidence: installed ? "high" : "low"
106
+ };
107
+ }
108
+ // ── Native paths ───────────────────────────────────────────────────────
109
+ getConfigDir(ctx) {
110
+ return ctx.scope === "project" ? ctx.projectDir : dirname(resolveOpenClawConfigPath());
111
+ }
112
+ getServerConfigPath(ctx) {
113
+ return ctx.scope === "project" ? join(ctx.projectDir, "openclaw.json") : resolveOpenClawConfigPath();
114
+ }
115
+ /**
116
+ * For ts-plugin hosts the "hook config path" is the generated plugin FILE.
117
+ * OpenClaw additionally needs the plugins.entries reference written into
118
+ * openclaw.json (handled in installHooks); this is the module on disk.
119
+ */
120
+ getHookConfigPath(ctx) {
121
+ return join(this.pluginDir(ctx), this.pluginFileName());
122
+ }
123
+ /** Extensions directory the synthesized plugin module is written into. */
124
+ pluginDir(ctx) {
125
+ const base = ctx.scope === "project" ? join(ctx.projectDir, ".openclaw") : dirname(resolveOpenClawConfigPath());
126
+ return join(base, "extensions", ctx.connector.id);
127
+ }
128
+ /** Plugin module file name (ESM so the gateway loads it without a loader). */
129
+ pluginFileName() {
130
+ return "index.mjs";
131
+ }
132
+ // ── MCP server install / uninstall (mcp.servers.<id>) ───────────────────
133
+ installServer(ctx) {
134
+ const server = this.effectiveServer(ctx);
135
+ const serverPath = this.getServerConfigPath(ctx);
136
+ if (!server) {
137
+ return [
138
+ {
139
+ platform: this.id,
140
+ action: "skip",
141
+ path: serverPath,
142
+ detail: ctx.connector.server ? "server registration disabled for openclaw" : "connector declares no MCP server"
143
+ }
144
+ ];
145
+ }
146
+ const entry = this.renderServerEntry(ctx, server);
147
+ return [this.upsertNestedServer(serverPath, ctx.connector.id, entry, ctx.dryRun)];
148
+ }
149
+ uninstallServer(ctx) {
150
+ const serverPath = this.getServerConfigPath(ctx);
151
+ return [this.removeNestedServer(serverPath, ctx.connector.id, ctx.dryRun)];
152
+ }
153
+ /** Resolve the per-platform server override into an effective ServerDef. */
154
+ effectiveServer(ctx) {
155
+ const override = ctx.connector.platforms[HOST]?.server;
156
+ if (override === false) return void 0;
157
+ const base = ctx.connector.server;
158
+ if (!base) return void 0;
159
+ return override && typeof override === "object" ? { ...base, ...override } : base;
160
+ }
161
+ /**
162
+ * Render a normalized ServerDef into OpenClaw's native mcp.servers.<id> entry.
163
+ *
164
+ * stdio → { command, args?, env? } (NO transport key — a stdio sidecar is
165
+ * inferred from `command`; OpenClaw's validator rejects
166
+ * transport:"stdio" and only accepts "sse"/"streamable-http")
167
+ * remote → { url, transport: "sse"|"http", headers? }
168
+ *
169
+ * OpenClaw documents no native ${env:VAR} token, so refs resolve to literals.
170
+ */
171
+ renderServerEntry(ctx, server) {
172
+ if (server.transport === "stdio") {
173
+ let command = server.command ?? "";
174
+ let args = [...server.args ?? []];
175
+ if (shouldWrapForTelemetry(server, ctx.connector.telemetry)) {
176
+ const wrapped = buildServeWrapperCommand(
177
+ ctx.homeBinPath,
178
+ ctx.connector.id,
179
+ command,
180
+ args,
181
+ ctx.scope,
182
+ this.id
183
+ );
184
+ command = wrapped.command;
185
+ args = wrapped.args;
186
+ }
187
+ const entry2 = {
188
+ command: resolveEnvRefsDeep(command)
189
+ };
190
+ const resolvedArgs = resolveEnvRefsDeep(args).filter((s) => s !== "");
191
+ if (resolvedArgs.length > 0) entry2.args = resolvedArgs;
192
+ const env = this.renderEnv(server.env);
193
+ if (env) entry2.env = env;
194
+ if (server.enabled === false) entry2.enabled = false;
195
+ return entry2;
196
+ }
197
+ const transport = server.transport === "http" ? "http" : "sse";
198
+ const entry = {
199
+ url: resolveEnvRefsDeep(server.url ?? ""),
200
+ transport
201
+ };
202
+ if (server.headers && Object.keys(server.headers).length > 0) {
203
+ entry.headers = resolveEnvRefsDeep({ ...server.headers });
204
+ }
205
+ if (server.enabled === false) entry.enabled = false;
206
+ return entry;
207
+ }
208
+ /** Resolve every ${env:VAR} in env values to literals (no native token). */
209
+ renderEnv(env) {
210
+ if (!env || Object.keys(env).length === 0) return void 0;
211
+ return resolveEnvRefsDeep({ ...env });
212
+ }
213
+ /** Upsert config.mcp.servers.<id> idempotently. */
214
+ upsertNestedServer(path, id, entry, dryRun) {
215
+ if (this.isPresentButUnparseable(path)) {
216
+ return {
217
+ platform: this.id,
218
+ action: "warn",
219
+ path,
220
+ detail: `existing ${path} is not parseable; left untouched (back it up / fix it, then re-run)`
221
+ };
222
+ }
223
+ const cfg = this.readJson(path) ?? {};
224
+ const servers = this.nestedServersBucket(cfg);
225
+ const before = JSON.stringify(servers[id]);
226
+ const after = JSON.stringify(entry);
227
+ let action;
228
+ if (before === void 0) action = "create";
229
+ else if (before === after) action = "skip";
230
+ else action = "update";
231
+ if (action !== "skip") {
232
+ servers[id] = entry;
233
+ this.writeJson(path, cfg, dryRun);
234
+ }
235
+ return {
236
+ platform: this.id,
237
+ action,
238
+ path,
239
+ detail: `${MCP_ROOT_KEY}.${MCP_SERVERS_KEY}.${id}`
240
+ };
241
+ }
242
+ /** Remove config.mcp.servers.<id>. */
243
+ removeNestedServer(path, id, dryRun) {
244
+ const cfg = this.readJson(path);
245
+ const mcp = cfg?.[MCP_ROOT_KEY];
246
+ const servers = mcp && typeof mcp === "object" && !Array.isArray(mcp) ? mcp[MCP_SERVERS_KEY] : void 0;
247
+ if (!cfg || !servers || !(id in servers)) {
248
+ return {
249
+ platform: this.id,
250
+ action: "skip",
251
+ path,
252
+ detail: `${MCP_ROOT_KEY}.${MCP_SERVERS_KEY}.${id} absent`
253
+ };
254
+ }
255
+ delete servers[id];
256
+ this.writeJson(path, cfg, dryRun);
257
+ return {
258
+ platform: this.id,
259
+ action: "remove",
260
+ path,
261
+ detail: `${MCP_ROOT_KEY}.${MCP_SERVERS_KEY}.${id}`
262
+ };
263
+ }
264
+ /** Get-or-create config.mcp.servers as a mutable object. */
265
+ nestedServersBucket(cfg) {
266
+ let mcp = cfg[MCP_ROOT_KEY];
267
+ if (!mcp || typeof mcp !== "object" || Array.isArray(mcp)) {
268
+ mcp = {};
269
+ cfg[MCP_ROOT_KEY] = mcp;
270
+ }
271
+ const mcpObj = mcp;
272
+ let servers = mcpObj[MCP_SERVERS_KEY];
273
+ if (!servers || typeof servers !== "object" || Array.isArray(servers)) {
274
+ servers = {};
275
+ mcpObj[MCP_SERVERS_KEY] = servers;
276
+ }
277
+ return servers;
278
+ }
279
+ // ── Hook install / uninstall (ts-plugin + DUAL REGISTRATION) ────────────
280
+ installHooks(ctx) {
281
+ const pluginPath = this.getHookConfigPath(ctx);
282
+ const configPath = this.getServerConfigPath(ctx);
283
+ if (ctx.connector.platforms[HOST]?.hooks === false) {
284
+ return [
285
+ { platform: this.id, action: "skip", path: pluginPath, detail: "hooks disabled for openclaw" }
286
+ ];
287
+ }
288
+ if (ctx.connector.hookEvents.length === 0) {
289
+ return [
290
+ { platform: this.id, action: "skip", path: pluginPath, detail: "connector declares no hooks" }
291
+ ];
292
+ }
293
+ const changes = [];
294
+ for (const file of this.synthesizePlugin(ctx)) {
295
+ const before = existsSync(file.path) ? this.safeRead(file.path) : void 0;
296
+ let action;
297
+ if (before === void 0) action = "create";
298
+ else if (before === file.contents) action = "skip";
299
+ else action = "update";
300
+ if (action !== "skip" && !ctx.dryRun) {
301
+ ensureDir(dirname(file.path));
302
+ writeFileSync(file.path, file.contents, "utf8");
303
+ chmodSync(file.path, file.executable ? 493 : 420);
304
+ }
305
+ changes.push({
306
+ platform: this.id,
307
+ action,
308
+ path: file.path,
309
+ detail: `openclaw plugin module (${this.hookDetail(ctx)})`
310
+ });
311
+ }
312
+ changes.push(this.upsertPluginEntry(configPath, ctx, this.pluginDir(ctx)));
313
+ return changes;
314
+ }
315
+ /**
316
+ * Human-facing summary of which declared events the synthesized module ACTUALLY
317
+ * wires. Only events present in EVENT_TO_OPENCLAW are mapped/wired; any declared
318
+ * event with no OpenClaw mapping (e.g. UserPromptSubmit) is reported separately
319
+ * as "unsupported here" so the detail never overstates coverage.
320
+ */
321
+ hookDetail(ctx) {
322
+ const declared = ctx.connector.hookEvents;
323
+ const mapped = declared.filter((e) => EVENT_TO_OPENCLAW[e] !== void 0);
324
+ const unsupported = declared.filter((e) => EVENT_TO_OPENCLAW[e] === void 0);
325
+ const base = mapped.join(",");
326
+ return unsupported.length > 0 ? `${base}; unsupported here: ${unsupported.join(",")}` : base;
327
+ }
328
+ uninstallHooks(ctx) {
329
+ const pluginPath = this.getHookConfigPath(ctx);
330
+ const configPath = this.getServerConfigPath(ctx);
331
+ const changes = [];
332
+ changes.push(
333
+ this.removePluginEntry(configPath, ctx.connector.id, this.pluginDir(ctx), ctx.dryRun)
334
+ );
335
+ const manifestPath = join(this.pluginDir(ctx), PLUGIN_MANIFEST_FILE);
336
+ for (const [path, label] of [
337
+ [pluginPath, "openclaw plugin module"],
338
+ [manifestPath, "openclaw plugin manifest"]
339
+ ]) {
340
+ if (existsSync(path)) {
341
+ if (!ctx.dryRun) rmSync(path, { force: true });
342
+ changes.push({ platform: this.id, action: "remove", path, detail: label });
343
+ } else {
344
+ changes.push({
345
+ platform: this.id,
346
+ action: "skip",
347
+ path,
348
+ detail: `no ${label} present`
349
+ });
350
+ }
351
+ }
352
+ changes.push(this.removeDirIfEmpty(this.pluginDir(ctx), ctx.dryRun));
353
+ return changes;
354
+ }
355
+ /**
356
+ * Upsert plugins.entries.<id> = { enabled: true } AND add `pluginDir` to
357
+ * plugins.load.paths idempotently.
358
+ *
359
+ * `openclaw config validate` rejects a per-entry `module` field, so the entry
360
+ * carries ONLY `{ enabled: true }`. The gateway discovers the synthesized
361
+ * module by scanning the directories in plugins.load.paths (the dir holding
362
+ * index.mjs + openclaw.plugin.json), so the dir is added there.
363
+ */
364
+ upsertPluginEntry(configPath, ctx, pluginDir) {
365
+ if (this.isPresentButUnparseable(configPath)) {
366
+ return {
367
+ platform: this.id,
368
+ action: "warn",
369
+ path: configPath,
370
+ detail: `existing ${configPath} is not parseable; left untouched (back it up / fix it, then re-run)`
371
+ };
372
+ }
373
+ const cfg = this.readJson(configPath) ?? {};
374
+ const entries = this.pluginEntriesBucket(cfg);
375
+ const id = ctx.connector.id;
376
+ const desired = { enabled: true };
377
+ const entryBefore = JSON.stringify(entries[id]);
378
+ const entryAfter = JSON.stringify(desired);
379
+ const loadPaths = this.pluginLoadPathsBucket(cfg);
380
+ const pathPresent = loadPaths.includes(pluginDir);
381
+ const entryChanged = entryBefore !== entryAfter;
382
+ const pathChanged = !pathPresent;
383
+ let action;
384
+ if (entryBefore === void 0) action = "create";
385
+ else if (!entryChanged && !pathChanged) action = "skip";
386
+ else action = "update";
387
+ if (action !== "skip") {
388
+ entries[id] = desired;
389
+ if (!pathPresent) loadPaths.push(pluginDir);
390
+ this.writeJson(configPath, cfg, ctx.dryRun);
391
+ }
392
+ return {
393
+ platform: this.id,
394
+ action,
395
+ path: configPath,
396
+ detail: `${PLUGINS_KEY}.${PLUGINS_ENTRIES_KEY}.${id} + ${PLUGINS_KEY}.${PLUGINS_LOAD_KEY}.${PLUGINS_LOAD_PATHS_KEY}`
397
+ };
398
+ }
399
+ /** Remove plugins.entries.<id> AND drop `pluginDir` from plugins.load.paths. */
400
+ removePluginEntry(configPath, id, pluginDir, dryRun) {
401
+ const cfg = this.readJson(configPath);
402
+ const plugins = cfg?.[PLUGINS_KEY];
403
+ const pluginsObj = plugins && typeof plugins === "object" && !Array.isArray(plugins) ? plugins : void 0;
404
+ const entries = pluginsObj?.[PLUGINS_ENTRIES_KEY];
405
+ const load = pluginsObj?.[PLUGINS_LOAD_KEY];
406
+ const loadPaths = load && Array.isArray(load[PLUGINS_LOAD_PATHS_KEY]) ? load[PLUGINS_LOAD_PATHS_KEY] : void 0;
407
+ const entryPresent = Boolean(entries && id in entries);
408
+ const pathIdx = loadPaths ? loadPaths.indexOf(pluginDir) : -1;
409
+ if (!cfg || !entryPresent && pathIdx < 0) {
410
+ return {
411
+ platform: this.id,
412
+ action: "skip",
413
+ path: configPath,
414
+ detail: `${PLUGINS_KEY}.${PLUGINS_ENTRIES_KEY}.${id} absent`
415
+ };
416
+ }
417
+ if (entryPresent && entries) delete entries[id];
418
+ if (loadPaths && pathIdx >= 0) loadPaths.splice(pathIdx, 1);
419
+ this.writeJson(configPath, cfg, dryRun);
420
+ return {
421
+ platform: this.id,
422
+ action: "remove",
423
+ path: configPath,
424
+ detail: `${PLUGINS_KEY}.${PLUGINS_ENTRIES_KEY}.${id} + ${PLUGINS_KEY}.${PLUGINS_LOAD_KEY}.${PLUGINS_LOAD_PATHS_KEY}`
425
+ };
426
+ }
427
+ /** Get-or-create config.plugins.entries as a mutable object. */
428
+ pluginEntriesBucket(cfg) {
429
+ let plugins = cfg[PLUGINS_KEY];
430
+ if (!plugins || typeof plugins !== "object" || Array.isArray(plugins)) {
431
+ plugins = {};
432
+ cfg[PLUGINS_KEY] = plugins;
433
+ }
434
+ const pluginsObj = plugins;
435
+ let entries = pluginsObj[PLUGINS_ENTRIES_KEY];
436
+ if (!entries || typeof entries !== "object" || Array.isArray(entries)) {
437
+ entries = {};
438
+ pluginsObj[PLUGINS_ENTRIES_KEY] = entries;
439
+ }
440
+ return entries;
441
+ }
442
+ /** Get-or-create config.plugins.load.paths as a mutable string array. */
443
+ pluginLoadPathsBucket(cfg) {
444
+ let plugins = cfg[PLUGINS_KEY];
445
+ if (!plugins || typeof plugins !== "object" || Array.isArray(plugins)) {
446
+ plugins = {};
447
+ cfg[PLUGINS_KEY] = plugins;
448
+ }
449
+ const pluginsObj = plugins;
450
+ let load = pluginsObj[PLUGINS_LOAD_KEY];
451
+ if (!load || typeof load !== "object" || Array.isArray(load)) {
452
+ load = {};
453
+ pluginsObj[PLUGINS_LOAD_KEY] = load;
454
+ }
455
+ const loadObj = load;
456
+ const existing = loadObj[PLUGINS_LOAD_PATHS_KEY];
457
+ const paths = Array.isArray(existing) ? existing.filter((p) => typeof p === "string") : [];
458
+ loadObj[PLUGINS_LOAD_PATHS_KEY] = paths;
459
+ return paths;
460
+ }
461
+ // ── ts-plugin synthesis ────────────────────────────────────────────────
462
+ /**
463
+ * Build ONE self-contained ESM plugin module for OpenClaw.
464
+ *
465
+ * The module imports nothing from agent-connector. It embeds two constants
466
+ * (the absolute home-bin path and the connector id) and a `bridge()` helper
467
+ * that shells out to the universal hook entrypoint via execFileSync, feeding
468
+ * the OpenClaw event payload on stdin and JSON.parsing the normalized
469
+ * HookResponse back from stdout (fail-open: any error → null). Its default
470
+ * export is the OpenClaw plugin definition { id, name, configSchema,
471
+ * register(api) }; register wires api.on(...) handlers for the events this
472
+ * connector actually declares.
473
+ */
474
+ synthesizePlugin(ctx) {
475
+ const path = this.getHookConfigPath(ctx);
476
+ const contents = this.buildPluginSource(ctx);
477
+ const manifestPath = join(this.pluginDir(ctx), PLUGIN_MANIFEST_FILE);
478
+ const manifest = this.buildPluginManifest(ctx);
479
+ return [
480
+ { path, contents, executable: false },
481
+ { path: manifestPath, contents: manifest, executable: false }
482
+ ];
483
+ }
484
+ /**
485
+ * Build the openclaw.plugin.json manifest the gateway reads inside a
486
+ * plugins.load.paths directory. It names the plugin and points at the ESM
487
+ * module entry (index.mjs) so the loader knows what to import.
488
+ */
489
+ buildPluginManifest(ctx) {
490
+ const manifest = {
491
+ id: ctx.connector.id,
492
+ name: ctx.connector.displayName || ctx.connector.id,
493
+ main: this.pluginFileName(),
494
+ enabled: true
495
+ };
496
+ return `${JSON.stringify(manifest, null, 2)}
497
+ `;
498
+ }
499
+ /** Compose the generated plugin source with plain string concatenation. */
500
+ buildPluginSource(ctx) {
501
+ const homeBin = JSON.stringify(ctx.homeBinPath);
502
+ const connectorId = JSON.stringify(ctx.connector.id);
503
+ const connectorName = JSON.stringify(ctx.connector.displayName || ctx.connector.id);
504
+ const events = ctx.connector.hookEvents.filter(
505
+ (e) => EVENT_TO_OPENCLAW[e] !== void 0
506
+ );
507
+ const has = (e) => events.includes(e);
508
+ const header = "/**\n * AUTO-GENERATED by agent-connector \u2014 DO NOT EDIT.\n *\n * Self-contained OpenClaw plugin bridge for connector " + ctx.connector.id + `.
509
+ * It imports nothing from agent-connector: every hook invocation shells out
510
+ * to the stable home binary's universal entrypoint and JSON-parses the
511
+ * normalized response. Fail-open: any bridge error degrades to a no-op.
512
+ *
513
+ * Registered via DUAL REGISTRATION in openclaw.json: plugins.entries.<id>
514
+ * (loads this module) + mcp.servers.<id> (surfaces the MCP tools). The
515
+ * gateway hot-reloads its config on SIGUSR1.
516
+ */
517
+ import { execFileSync, execSync } from "node:child_process";
518
+
519
+ const HOME_BIN = ` + homeBin + ";\nconst CONNECTOR_ID = " + connectorId + `;
520
+
521
+ /**
522
+ * Invoke the universal hook entrypoint for one canonical event.
523
+ * @param {string} event canonical event name (PreToolUse|PostToolUse|SessionStart)
524
+ * @param {object} payload OpenClaw-shaped payload posted on stdin
525
+ * @returns {object|null} normalized HookResponse, or null on any failure
526
+ */
527
+ function bridge(event, payload) {
528
+ try {
529
+ // On Windows HOME_BIN is the agent-connector.cmd launcher: Node cannot
530
+ // execFile a batch file, and shell+args is deprecated (DEP0190), so run
531
+ // one quoted command line via a shell. POSIX keeps the direct execFile.
532
+ const args = ["hook", "openclaw", event, "--connector", CONNECTOR_ID];
533
+ const opts = { input: JSON.stringify(payload), encoding: "utf8" };
534
+ const stdout =
535
+ process.platform === "win32"
536
+ ? execSync([HOME_BIN, ...args].map((a) => '"' + a + '"').join(" "), opts)
537
+ : execFileSync(HOME_BIN, args, opts);
538
+ const text = (stdout || "").trim();
539
+ if (text === "") return { decision: "allow" };
540
+ return JSON.parse(text);
541
+ } catch {
542
+ // Fail-open \u2014 never wedge a tool call on a bridge error.
543
+ return null;
544
+ }
545
+ }
546
+
547
+ // Module-scoped context the prompt-build hook injects at session start.
548
+ // before_prompt_build is the verified injection point (session_start itself
549
+ // returns no context payload). The flag prevents double-injection.
550
+ let PROJECT_DIR = process.cwd();
551
+ let SESSION_ID = "";
552
+ let pendingContext = null;
553
+ let contextInjected = false;
554
+
555
+ `;
556
+ const reg = [];
557
+ reg.push(
558
+ ' register(api) {\n PROJECT_DIR = process.cwd();\n const on = (event, handler) => {\n if (typeof api?.on === "function") {\n try { api.on(event, handler); return; } catch { /* fall through */ }\n }\n if (typeof api?.registerHook === "function") {\n try {\n api.registerHook(event, handler, { name: CONNECTOR_ID + ":" + event, description: "agent-connector bridge" });\n } catch { /* best effort */ }\n }\n };\n'
559
+ );
560
+ if (has("PreToolUse")) {
561
+ reg.push(
562
+ '\n // PreToolUse \u2192 before_tool_call: block (return { block, blockReason })\n // for deny/ask; rewrite args by mutating event.params in place for modify.\n on("before_tool_call", async (event) => {\n const e = event || {};\n const toolInput = e.params || {};\n const res = bridge("PreToolUse", {\n toolName: e.toolName || "",\n toolInput,\n sessionId: SESSION_ID,\n projectDir: PROJECT_DIR,\n });\n if (!res) return undefined;\n // OpenClaw has no separate ask gate \u2014 deny and ask both block (safe).\n if (res.decision === "deny" || res.decision === "ask") {\n return { block: true, blockReason: res.reason || ("Blocked by " + CONNECTOR_ID) };\n }\n if (res.decision === "modify" && res.updatedInput && typeof toolInput === "object") {\n Object.assign(toolInput, res.updatedInput);\n }\n return undefined;\n });\n'
563
+ );
564
+ }
565
+ if (has("PostToolUse")) {
566
+ reg.push(
567
+ '\n // PostToolUse \u2192 after_tool_call: observe the completed tool call.\n // OpenClaw exposes the result (v2+) or output (older builds); it cannot\n // rewrite the tool result, so updatedOutput is intentionally dropped.\n on("after_tool_call", async (event) => {\n const e = event || {};\n const rawResult = e.result !== undefined ? e.result : e.output;\n const toolOutput =\n typeof rawResult === "string"\n ? rawResult\n : rawResult != null\n ? JSON.stringify(rawResult)\n : undefined;\n bridge("PostToolUse", {\n toolName: e.toolName || "",\n toolInput: e.params || {},\n toolOutput,\n isError: Boolean(e.error || e.isError),\n sessionId: SESSION_ID,\n projectDir: PROJECT_DIR,\n });\n return undefined;\n });\n'
568
+ );
569
+ }
570
+ if (has("SessionStart")) {
571
+ reg.push(
572
+ '\n // SessionStart \u2192 session_start records the real session id and fetches\n // the additionalContext to inject; before_prompt_build performs the\n // injection (the verified context-injection point upstream).\n on("session_start", async (event) => {\n const e = event || {};\n SESSION_ID = e.sessionId || e.sessionKey || SESSION_ID;\n const res = bridge("SessionStart", {\n sessionId: SESSION_ID,\n source: e.resumedFrom ? "resume" : "startup",\n projectDir: PROJECT_DIR,\n });\n contextInjected = false;\n pendingContext =\n res && typeof res.additionalContext === "string" && res.additionalContext\n ? res.additionalContext\n : null;\n return undefined;\n });\n\n // Inject the session-start context once, at prompt-build time.\n on("before_prompt_build", () => {\n if (contextInjected || !pendingContext) return undefined;\n contextInjected = true;\n return { appendSystemContext: pendingContext };\n });\n'
573
+ );
574
+ }
575
+ reg.push(" },\n");
576
+ const definition = "const plugin = {\n id: CONNECTOR_ID,\n name: " + connectorName + ',\n configSchema: {\n type: "object",\n properties: {\n enabled: {\n type: "boolean",\n default: true,\n description: "Enable or disable the " + CONNECTOR_ID + " plugin.",\n },\n },\n additionalProperties: false,\n },\n' + reg.join("") + "};\n\nexport default plugin;\n";
577
+ return header + definition;
578
+ }
579
+ // ── Runtime: parse OUR bridge payload → normalized event ───────────────
580
+ /**
581
+ * `raw` is the payload OUR generated plugin posts (NOT a host-native shape):
582
+ * { toolName, toolInput, toolOutput?, isError?, sessionId, source?, projectDir }
583
+ * so this maps straight through.
584
+ */
585
+ parseEvent(event, raw) {
586
+ const input = raw ?? {};
587
+ const base = {
588
+ hostPlatform: HOST,
589
+ connectorId: "",
590
+ sessionId: typeof input.sessionId === "string" ? input.sessionId : "",
591
+ raw,
592
+ ...typeof input.projectDir === "string" ? { projectDir: input.projectDir } : {}
593
+ };
594
+ switch (event) {
595
+ case "PreToolUse": {
596
+ const ev = {
597
+ ...base,
598
+ toolName: input.toolName ?? "",
599
+ toolInput: input.toolInput ?? {}
600
+ };
601
+ return ev;
602
+ }
603
+ case "PostToolUse": {
604
+ const ev = {
605
+ ...base,
606
+ toolName: input.toolName ?? "",
607
+ toolInput: input.toolInput ?? {},
608
+ ...typeof input.toolOutput === "string" ? { toolOutput: input.toolOutput } : {},
609
+ ...typeof input.isError === "boolean" ? { isError: input.isError } : {}
610
+ };
611
+ return ev;
612
+ }
613
+ case "SessionStart": {
614
+ const source = input.source === "resume" ? "resume" : input.source === "compact" ? "compact" : input.source === "clear" ? "clear" : "startup";
615
+ const ev = { ...base, source };
616
+ return ev;
617
+ }
618
+ default:
619
+ return { ...base, source: "startup" };
620
+ }
621
+ }
622
+ // ── Runtime: normalized response → reply the generated bridge parses ───
623
+ /**
624
+ * Unlike json-stdio hosts (whose reply is the host's NATIVE control payload),
625
+ * OUR generated bridge consumes this stdout directly. So the reply body is the
626
+ * NORMALIZED HookResponse itself — the bridge JSON.parses it and reads
627
+ * decision / updatedInput / additionalContext.
628
+ */
629
+ formatReply(_event, response) {
630
+ return {
631
+ exitCode: 0,
632
+ stdout: JSON.stringify(response ?? { decision: "allow" })
633
+ };
634
+ }
635
+ // ── Diagnostics ────────────────────────────────────────────────────────
636
+ getHealthChecks(ctx) {
637
+ const configPath = this.getServerConfigPath(ctx);
638
+ const pluginPath = this.getHookConfigPath(ctx);
639
+ const pluginDir = this.pluginDir(ctx);
640
+ const id = ctx.connector.id;
641
+ const hasHooks = ctx.connector.hookEvents.length > 0;
642
+ const hasServer = Boolean(ctx.connector.server);
643
+ return [
644
+ {
645
+ name: `${this.name}: openclaw.json present`,
646
+ check: () => existsSync(configPath) ? { status: "OK", detail: configPath } : { status: "FAIL", detail: `not found: ${configPath}` }
647
+ },
648
+ {
649
+ // HARDEST PART — dual registration must be CONSISTENT. The plugin only
650
+ // delivers tools when it is LOADED (plugins.entries.<id> enabled AND its
651
+ // dir in plugins.load.paths) AND in mcp.servers. A config with one half
652
+ // but not the other is a tool-less (or never-loaded) install, so FAIL on
653
+ // loaded XOR mcp.servers. (We assert plugins.load.paths — NOT a per-entry
654
+ // `module` field, which openclaw config validate rejects.)
655
+ name: `${this.name}: dual registration (plugins.entries+load.paths + mcp.servers.${id})`,
656
+ check: () => {
657
+ const cfg = this.readJson(configPath);
658
+ if (!cfg) return { status: "FAIL", detail: `cannot read ${configPath}` };
659
+ const inEntries = this.hasPluginEntry(cfg, id) && this.hasLoadPath(cfg, pluginDir);
660
+ const inMcp = this.hasMcpServer(cfg, id);
661
+ if (!hasServer) {
662
+ return inEntries || !hasHooks ? { status: "OK", detail: "hooks-only connector (no MCP server)" } : {
663
+ status: "FAIL",
664
+ detail: `${id} not enabled in ${PLUGINS_KEY}.${PLUGINS_ENTRIES_KEY} + ${PLUGINS_KEY}.${PLUGINS_LOAD_KEY}.${PLUGINS_LOAD_PATHS_KEY} (plugin will not load)`
665
+ };
666
+ }
667
+ if (inEntries && inMcp) {
668
+ return {
669
+ status: "OK",
670
+ detail: `${PLUGINS_KEY}.${PLUGINS_ENTRIES_KEY}.${id}+${PLUGINS_KEY}.${PLUGINS_LOAD_KEY}.${PLUGINS_LOAD_PATHS_KEY} + ${MCP_ROOT_KEY}.${MCP_SERVERS_KEY}.${id}`
671
+ };
672
+ }
673
+ if (inEntries && !inMcp) {
674
+ return {
675
+ status: "FAIL",
676
+ detail: `loaded (${PLUGINS_KEY}.${PLUGINS_ENTRIES_KEY}+${PLUGINS_LOAD_KEY}.${PLUGINS_LOAD_PATHS_KEY}) but missing from ${MCP_ROOT_KEY}.${MCP_SERVERS_KEY} \u2014 plugin loads but no tools reach the agent`
677
+ };
678
+ }
679
+ if (!inEntries && inMcp) {
680
+ return {
681
+ status: "FAIL",
682
+ detail: `in ${MCP_ROOT_KEY}.${MCP_SERVERS_KEY} but not loaded (missing ${PLUGINS_KEY}.${PLUGINS_ENTRIES_KEY} or ${PLUGINS_LOAD_KEY}.${PLUGINS_LOAD_PATHS_KEY}) \u2014 tools declared but plugin never loads`
683
+ };
684
+ }
685
+ return {
686
+ status: "FAIL",
687
+ detail: `not loaded (${PLUGINS_KEY}.${PLUGINS_ENTRIES_KEY}+${PLUGINS_LOAD_KEY}.${PLUGINS_LOAD_PATHS_KEY}) and not in ${MCP_ROOT_KEY}.${MCP_SERVERS_KEY}`
688
+ };
689
+ }
690
+ },
691
+ {
692
+ name: `${this.name}: plugin module present`,
693
+ check: () => {
694
+ if (!hasHooks) return { status: "OK", detail: "no hooks declared" };
695
+ return existsSync(pluginPath) ? { status: "OK", detail: pluginPath } : { status: "FAIL", detail: `not found: ${pluginPath}` };
696
+ }
697
+ }
698
+ ];
699
+ }
700
+ /** True when config.plugins.entries.<id> exists (and is not disabled). */
701
+ hasPluginEntry(cfg, id) {
702
+ const plugins = cfg[PLUGINS_KEY];
703
+ if (!plugins || typeof plugins !== "object" || Array.isArray(plugins)) return false;
704
+ const entries = plugins[PLUGINS_ENTRIES_KEY];
705
+ if (!entries || typeof entries !== "object" || Array.isArray(entries)) return false;
706
+ const entry = entries[id];
707
+ if (entry === void 0) return false;
708
+ return !(entry && typeof entry === "object" && entry.enabled === false);
709
+ }
710
+ /** True when config.plugins.load.paths includes `pluginDir`. */
711
+ hasLoadPath(cfg, pluginDir) {
712
+ const plugins = cfg[PLUGINS_KEY];
713
+ if (!plugins || typeof plugins !== "object" || Array.isArray(plugins)) return false;
714
+ const load = plugins[PLUGINS_LOAD_KEY];
715
+ if (!load || typeof load !== "object" || Array.isArray(load)) return false;
716
+ const paths = load[PLUGINS_LOAD_PATHS_KEY];
717
+ return Array.isArray(paths) && paths.includes(pluginDir);
718
+ }
719
+ /** True when config.mcp.servers.<id> exists. */
720
+ hasMcpServer(cfg, id) {
721
+ const mcp = cfg[MCP_ROOT_KEY];
722
+ if (!mcp || typeof mcp !== "object" || Array.isArray(mcp)) return false;
723
+ const servers = mcp[MCP_SERVERS_KEY];
724
+ if (!servers || typeof servers !== "object" || Array.isArray(servers)) return false;
725
+ return id in servers;
726
+ }
727
+ /** Read a file, returning undefined on any error (idempotency compare). */
728
+ safeRead(path) {
729
+ try {
730
+ return readFileSync(path, "utf8");
731
+ } catch {
732
+ return void 0;
733
+ }
734
+ }
735
+ };
736
+ function resolveOpenClawConfigPath(env = process.env) {
737
+ if (env.OPENCLAW_CONFIG_PATH) return resolve(env.OPENCLAW_CONFIG_PATH);
738
+ const stateDir = env.OPENCLAW_STATE_DIR ? resolve(env.OPENCLAW_STATE_DIR) : resolve(homedir(), ".openclaw");
739
+ return resolve(stateDir, "openclaw.json");
740
+ }
741
+ function ensureDir(dir) {
742
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
743
+ }
744
+ var adapter = new OpenClawAdapter();
745
+ var openclaw_default = adapter;
746
+ export {
747
+ OpenClawAdapter,
748
+ adapter,
749
+ openclaw_default as default
750
+ };
751
+ //# sourceMappingURL=openclaw-ETN2M63O.js.map