@useorgx/openclaw-plugin 0.4.8 → 0.4.9

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 (125) hide show
  1. package/dashboard/dist/assets/B5NEElEI.css +1 -0
  2. package/dashboard/dist/assets/BhapSNAs.js +215 -0
  3. package/dashboard/dist/assets/{BNeJ0kpF.js → iFdvE7lx.js} +1 -1
  4. package/dashboard/dist/assets/{CUV9IHHi.js → jRJsmpYM.js} +1 -1
  5. package/dashboard/dist/index.html +2 -2
  6. package/dist/activity-store.js +4 -18
  7. package/dist/agent-context-store.js +5 -25
  8. package/dist/agent-run-store.js +5 -25
  9. package/dist/agent-suite.js +1 -8
  10. package/dist/auth/flows.d.ts +47 -0
  11. package/dist/auth/flows.js +169 -0
  12. package/dist/auth-store.js +6 -26
  13. package/dist/byok-store.js +5 -19
  14. package/dist/cli/orgx.d.ts +66 -0
  15. package/dist/cli/orgx.js +91 -0
  16. package/dist/config/refresh.d.ts +32 -0
  17. package/dist/config/refresh.js +55 -0
  18. package/dist/config/resolution.d.ts +37 -0
  19. package/dist/config/resolution.js +178 -0
  20. package/dist/contracts/shared-types.d.ts +147 -0
  21. package/dist/contracts/shared-types.js +3 -0
  22. package/dist/contracts/types.d.ts +1 -134
  23. package/dist/contracts/types.js +5 -0
  24. package/dist/entities/auto-assignment.d.ts +36 -0
  25. package/dist/entities/auto-assignment.js +115 -0
  26. package/dist/entity-comment-store.js +5 -25
  27. package/dist/hash-utils.d.ts +2 -0
  28. package/dist/hash-utils.js +12 -0
  29. package/dist/http/helpers/activity-headline.d.ts +10 -0
  30. package/dist/http/helpers/activity-headline.js +192 -0
  31. package/dist/http/helpers/artifact-fallback.d.ts +13 -0
  32. package/dist/http/helpers/artifact-fallback.js +148 -0
  33. package/dist/http/helpers/auto-continue-engine.d.ts +298 -0
  34. package/dist/http/helpers/auto-continue-engine.js +1218 -0
  35. package/dist/http/helpers/autopilot-operations.d.ts +157 -0
  36. package/dist/http/helpers/autopilot-operations.js +403 -0
  37. package/dist/http/helpers/autopilot-runtime.d.ts +42 -0
  38. package/dist/http/helpers/autopilot-runtime.js +319 -0
  39. package/dist/http/helpers/autopilot-slice-utils.d.ts +38 -0
  40. package/dist/http/helpers/autopilot-slice-utils.js +476 -0
  41. package/dist/http/helpers/decision-mapper.d.ts +12 -0
  42. package/dist/http/helpers/decision-mapper.js +44 -0
  43. package/dist/http/helpers/dispatch-lifecycle.d.ts +102 -0
  44. package/dist/http/helpers/dispatch-lifecycle.js +604 -0
  45. package/dist/http/helpers/hash-utils.d.ts +1 -0
  46. package/dist/http/helpers/hash-utils.js +1 -0
  47. package/dist/http/helpers/kickoff-context.d.ts +12 -0
  48. package/dist/http/helpers/kickoff-context.js +154 -0
  49. package/dist/http/helpers/mission-control.d.ts +94 -0
  50. package/dist/http/helpers/mission-control.js +894 -0
  51. package/dist/http/helpers/openclaw-cli.d.ts +37 -0
  52. package/dist/http/helpers/openclaw-cli.js +283 -0
  53. package/dist/http/helpers/runtime-sse.d.ts +20 -0
  54. package/dist/http/helpers/runtime-sse.js +110 -0
  55. package/dist/http/helpers/value-utils.d.ts +6 -0
  56. package/dist/http/helpers/value-utils.js +67 -0
  57. package/dist/http/index.d.ts +88 -0
  58. package/dist/http/index.js +2353 -0
  59. package/dist/http/router.d.ts +23 -0
  60. package/dist/http/router.js +23 -0
  61. package/dist/http/routes/agent-control.d.ts +79 -0
  62. package/dist/http/routes/agent-control.js +684 -0
  63. package/dist/http/routes/agent-suite.d.ts +29 -0
  64. package/dist/http/routes/agent-suite.js +198 -0
  65. package/dist/http/routes/agents-catalog.d.ts +40 -0
  66. package/dist/http/routes/agents-catalog.js +83 -0
  67. package/dist/http/routes/billing.d.ts +23 -0
  68. package/dist/http/routes/billing.js +55 -0
  69. package/dist/http/routes/debug.d.ts +14 -0
  70. package/dist/http/routes/debug.js +21 -0
  71. package/dist/http/routes/decision-actions.d.ts +13 -0
  72. package/dist/http/routes/decision-actions.js +66 -0
  73. package/dist/http/routes/delegation.d.ts +19 -0
  74. package/dist/http/routes/delegation.js +32 -0
  75. package/dist/http/routes/entities.d.ts +47 -0
  76. package/dist/http/routes/entities.js +152 -0
  77. package/dist/http/routes/entity-dynamic.d.ts +25 -0
  78. package/dist/http/routes/entity-dynamic.js +191 -0
  79. package/dist/http/routes/health.d.ts +22 -0
  80. package/dist/http/routes/health.js +49 -0
  81. package/dist/http/routes/live-legacy.d.ts +110 -0
  82. package/dist/http/routes/live-legacy.js +598 -0
  83. package/dist/http/routes/live-misc.d.ts +69 -0
  84. package/dist/http/routes/live-misc.js +206 -0
  85. package/dist/http/routes/live-snapshot.d.ts +90 -0
  86. package/dist/http/routes/live-snapshot.js +297 -0
  87. package/dist/http/routes/mission-control-actions.d.ts +83 -0
  88. package/dist/http/routes/mission-control-actions.js +541 -0
  89. package/dist/http/routes/mission-control-read.d.ts +28 -0
  90. package/dist/http/routes/mission-control-read.js +67 -0
  91. package/dist/http/routes/onboarding.d.ts +34 -0
  92. package/dist/http/routes/onboarding.js +101 -0
  93. package/dist/http/routes/run-control.d.ts +24 -0
  94. package/dist/http/routes/run-control.js +86 -0
  95. package/dist/http/routes/runtime-hooks.d.ts +69 -0
  96. package/dist/http/routes/runtime-hooks.js +437 -0
  97. package/dist/http/routes/settings-byok.d.ts +23 -0
  98. package/dist/http/routes/settings-byok.js +163 -0
  99. package/dist/http/routes/summary.d.ts +18 -0
  100. package/dist/http/routes/summary.js +42 -0
  101. package/dist/http/routes/work-artifacts.d.ts +9 -0
  102. package/dist/http/routes/work-artifacts.js +36 -0
  103. package/dist/http/shared-state.d.ts +16 -0
  104. package/dist/http/shared-state.js +1 -0
  105. package/dist/http-handler.d.ts +1 -88
  106. package/dist/http-handler.js +1 -10605
  107. package/dist/index.js +108 -2243
  108. package/dist/json-utils.d.ts +1 -0
  109. package/dist/json-utils.js +8 -0
  110. package/dist/next-up-queue-store.js +4 -18
  111. package/dist/runtime-instance-store.js +5 -31
  112. package/dist/services/background.d.ts +23 -0
  113. package/dist/services/background.js +23 -0
  114. package/dist/services/instrumentation.d.ts +29 -0
  115. package/dist/services/instrumentation.js +136 -0
  116. package/dist/snapshot-store.js +5 -25
  117. package/dist/stores/json-store.d.ts +11 -0
  118. package/dist/stores/json-store.js +42 -0
  119. package/dist/sync/outbox-replay.d.ts +55 -0
  120. package/dist/sync/outbox-replay.js +514 -0
  121. package/dist/tools/core-tools.d.ts +76 -0
  122. package/dist/tools/core-tools.js +1005 -0
  123. package/package.json +1 -1
  124. package/dashboard/dist/assets/BzkiMPmM.js +0 -215
  125. package/dashboard/dist/assets/Ie7d9Iq2.css +0 -1
@@ -0,0 +1,437 @@
1
+ import { backupCorruptFileSync, writeFileAtomicSync } from "../../fs-utils.js";
2
+ import { readOpenClawGatewayPort, readOpenClawSettingsSnapshot } from "../../openclaw-settings.js";
3
+ import { getOrgxPluginConfigDir } from "../../paths.js";
4
+ import { existsSync, mkdirSync, readFileSync } from "node:fs";
5
+ import { dirname, join, resolve } from "node:path";
6
+ import { homedir } from "node:os";
7
+ import { fileURLToPath } from "node:url";
8
+ export function registerRuntimeHookRoutes(router, deps) {
9
+ async function renderRuntimeHookConfig(res) {
10
+ try {
11
+ const snapshot = readOpenClawSettingsSnapshot();
12
+ const port = readOpenClawGatewayPort(snapshot.raw);
13
+ const runtimeHookUrl = `http://127.0.0.1:${port}/orgx/api/hooks/runtime`;
14
+ const hookToken = deps.resolveRuntimeHookToken();
15
+ const hooksDir = join(getOrgxPluginConfigDir(), "hooks");
16
+ const hookScriptPath = join(hooksDir, "post-reporting-event.mjs");
17
+ const hookScriptInstalled = existsSync(hookScriptPath);
18
+ const codexHome = (process.env.CODEX_HOME ?? "").trim();
19
+ const codexCandidates = [
20
+ codexHome ? join(codexHome, "config.toml") : null,
21
+ join(homedir(), ".codex", "config.toml"),
22
+ join(homedir(), ".config", "codex", "config.toml"),
23
+ ].filter(Boolean);
24
+ const codexConfigPath = codexCandidates.find((candidate) => existsSync(candidate)) ??
25
+ (codexCandidates[0] ?? null);
26
+ let codexInstalled = false;
27
+ let codexHasNotify = false;
28
+ if (codexConfigPath && existsSync(codexConfigPath)) {
29
+ const raw = readFileSync(codexConfigPath, "utf8");
30
+ codexHasNotify = /^\s*notify\s*=/m.test(raw);
31
+ codexInstalled =
32
+ raw.includes("post-reporting-event.mjs") && raw.includes("--source_client=codex");
33
+ }
34
+ const codexNotifyConflict = Boolean(codexHasNotify && !codexInstalled);
35
+ const claudeCandidates = [
36
+ join(homedir(), ".claude", "settings.json"),
37
+ join(homedir(), ".config", "claude", "settings.json"),
38
+ ];
39
+ const claudeSettingsPath = claudeCandidates.find((candidate) => existsSync(candidate)) ?? claudeCandidates[0];
40
+ let claudeInstalled = false;
41
+ if (claudeSettingsPath && existsSync(claudeSettingsPath)) {
42
+ const raw = readFileSync(claudeSettingsPath, "utf8");
43
+ claudeInstalled =
44
+ raw.includes("post-reporting-event.mjs") &&
45
+ raw.includes("--source_client=claude-code");
46
+ }
47
+ deps.sendJson(res, 200, {
48
+ ok: true,
49
+ runtimeHookUrl,
50
+ hookToken,
51
+ hookTokenHint: deps.maskSecret(hookToken),
52
+ paths: {
53
+ hookScriptPath,
54
+ codexConfigPath,
55
+ claudeSettingsPath,
56
+ },
57
+ installed: {
58
+ hookScript: hookScriptInstalled,
59
+ codex: codexInstalled,
60
+ claudeCode: claudeInstalled,
61
+ },
62
+ conflicts: {
63
+ codexNotify: codexNotifyConflict,
64
+ },
65
+ });
66
+ }
67
+ catch (err) {
68
+ deps.sendJson(res, 500, {
69
+ ok: false,
70
+ error: deps.safeErrorMessage(err),
71
+ });
72
+ }
73
+ }
74
+ router.add("GET", "hooks/runtime/config", async ({ res }) => renderRuntimeHookConfig(res), "Get runtime hook wiring config");
75
+ router.add("HEAD", "hooks/runtime/config", async ({ res }) => renderRuntimeHookConfig(res), "Get runtime hook wiring config (HEAD)");
76
+ router.add("POST", "hooks/runtime/setup", async ({ req, res }) => {
77
+ try {
78
+ const payloadRecord = await deps.parseJsonRequest(req);
79
+ const requestedTargets = Array.isArray(payloadRecord.targets)
80
+ ? payloadRecord.targets
81
+ : [];
82
+ const requested = requestedTargets
83
+ .map((value) => typeof value === "string" ? value.trim().toLowerCase() : "")
84
+ .filter((value) => value.length > 0);
85
+ const targets = new Set();
86
+ for (const value of requested) {
87
+ if (value === "codex")
88
+ targets.add("codex");
89
+ if (value === "claude" || value === "claude-code" || value === "claude_code") {
90
+ targets.add("claude-code");
91
+ }
92
+ }
93
+ if (targets.size === 0) {
94
+ targets.add("codex");
95
+ targets.add("claude-code");
96
+ }
97
+ const snapshot = readOpenClawSettingsSnapshot();
98
+ const port = readOpenClawGatewayPort(snapshot.raw);
99
+ const runtimeHookUrl = `http://127.0.0.1:${port}/orgx/api/hooks/runtime`;
100
+ const hookToken = deps.resolveRuntimeHookToken();
101
+ const hooksDir = join(getOrgxPluginConfigDir(), "hooks");
102
+ mkdirSync(hooksDir, { recursive: true, mode: 0o700 });
103
+ const hookScriptPath = join(hooksDir, "post-reporting-event.mjs");
104
+ const handlerFilename = fileURLToPath(import.meta.url);
105
+ const distDir = resolve(join(handlerFilename, ".."));
106
+ const bundledScriptPath = resolve(distDir, "hooks", "post-reporting-event.mjs");
107
+ const fallbackScriptPath = resolve(distDir, "..", "templates", "hooks", "scripts", "post-reporting-event.mjs");
108
+ let scriptContent = "";
109
+ let hookScriptSourcePath = bundledScriptPath;
110
+ try {
111
+ scriptContent = readFileSync(bundledScriptPath, "utf8");
112
+ }
113
+ catch {
114
+ hookScriptSourcePath = fallbackScriptPath;
115
+ scriptContent = readFileSync(fallbackScriptPath, "utf8");
116
+ }
117
+ writeFileAtomicSync(hookScriptPath, scriptContent, {
118
+ mode: 0o700,
119
+ encoding: "utf8",
120
+ });
121
+ const result = {
122
+ ok: true,
123
+ runtimeHookUrl,
124
+ hookTokenHint: deps.maskSecret(hookToken),
125
+ hookScriptPath,
126
+ hookScriptSourcePath,
127
+ targets: {
128
+ codex: targets.has("codex"),
129
+ claudeCode: targets.has("claude-code"),
130
+ },
131
+ codex: {
132
+ path: null,
133
+ installed: false,
134
+ conflict: false,
135
+ },
136
+ claudeCode: {
137
+ path: null,
138
+ installed: false,
139
+ },
140
+ };
141
+ if (targets.has("codex")) {
142
+ const codexHome = (process.env.CODEX_HOME ?? "").trim();
143
+ const codexCandidates = [
144
+ codexHome ? join(codexHome, "config.toml") : null,
145
+ join(homedir(), ".codex", "config.toml"),
146
+ join(homedir(), ".config", "codex", "config.toml"),
147
+ ].filter(Boolean);
148
+ const codexConfigPath = codexCandidates.find((candidate) => existsSync(candidate)) ??
149
+ codexCandidates[0];
150
+ result.codex.path = codexConfigPath;
151
+ const notifySnippet = [
152
+ "",
153
+ "# OrgX runtime telemetry (installed by OpenClaw plugin)",
154
+ "notify = [",
155
+ ' "node",',
156
+ ` "${hookScriptPath}",`,
157
+ ' "--event=heartbeat",',
158
+ ' "--source_client=codex",',
159
+ ' "--phase=execution",',
160
+ ' "--message=Codex heartbeat",',
161
+ ` "--runtime_hook_url=${runtimeHookUrl}",`,
162
+ ` "--hook_token=${hookToken}",`,
163
+ "]",
164
+ "",
165
+ ].join("\n");
166
+ if (!existsSync(codexConfigPath)) {
167
+ mkdirSync(dirname(codexConfigPath), { recursive: true, mode: 0o700 });
168
+ const initial = [
169
+ "# Codex config.toml",
170
+ "# Auto-generated OrgX hook wiring (safe to edit).",
171
+ notifySnippet.trimEnd(),
172
+ "",
173
+ ].join("\n");
174
+ writeFileAtomicSync(codexConfigPath, initial, {
175
+ mode: 0o600,
176
+ encoding: "utf8",
177
+ });
178
+ result.codex.installed = true;
179
+ }
180
+ else {
181
+ const raw = readFileSync(codexConfigPath, "utf8");
182
+ const alreadyInstalled = raw.includes("post-reporting-event.mjs") &&
183
+ raw.includes("--source_client=codex");
184
+ const hasNotify = /^\s*notify\s*=/m.test(raw);
185
+ if (alreadyInstalled) {
186
+ result.codex.installed = true;
187
+ }
188
+ else if (hasNotify) {
189
+ result.codex.conflict = true;
190
+ }
191
+ else {
192
+ const next = raw.replace(/\s*$/, "") + notifySnippet;
193
+ writeFileAtomicSync(codexConfigPath, next, {
194
+ mode: 0o600,
195
+ encoding: "utf8",
196
+ });
197
+ result.codex.installed = true;
198
+ }
199
+ }
200
+ }
201
+ if (targets.has("claude-code")) {
202
+ const claudeCandidates = [
203
+ join(homedir(), ".claude", "settings.json"),
204
+ join(homedir(), ".config", "claude", "settings.json"),
205
+ ];
206
+ const claudeSettingsPath = claudeCandidates.find((candidate) => existsSync(candidate)) ??
207
+ claudeCandidates[0];
208
+ result.claudeCode.path = claudeSettingsPath;
209
+ mkdirSync(dirname(claudeSettingsPath), { recursive: true, mode: 0o700 });
210
+ let settings = {};
211
+ if (existsSync(claudeSettingsPath)) {
212
+ const raw = readFileSync(claudeSettingsPath, "utf8");
213
+ const parsed = deps.parseJsonSafe(raw);
214
+ if (!parsed) {
215
+ backupCorruptFileSync(claudeSettingsPath);
216
+ }
217
+ else {
218
+ settings = parsed;
219
+ }
220
+ }
221
+ const hooksRoot = settings.hooks &&
222
+ typeof settings.hooks === "object" &&
223
+ !Array.isArray(settings.hooks)
224
+ ? settings.hooks
225
+ : {};
226
+ settings.hooks = hooksRoot;
227
+ const ensureClaudeHook = (hookName, matcher, command) => {
228
+ const list = Array.isArray(hooksRoot[hookName])
229
+ ? hooksRoot[hookName]
230
+ : [];
231
+ let rule = list.find((entry) => entry &&
232
+ typeof entry === "object" &&
233
+ entry.matcher === matcher);
234
+ if (!rule) {
235
+ rule = { matcher, hooks: [] };
236
+ list.push(rule);
237
+ }
238
+ if (!Array.isArray(rule.hooks)) {
239
+ rule.hooks = [];
240
+ }
241
+ const hooks = rule.hooks;
242
+ const already = hooks.some((entry) => entry &&
243
+ entry.type === "command" &&
244
+ typeof entry.command === "string" &&
245
+ entry.command.includes("post-reporting-event.mjs") &&
246
+ entry.command.includes(command));
247
+ if (!already) {
248
+ hooks.push({ type: "command", command });
249
+ }
250
+ hooksRoot[hookName] = list;
251
+ };
252
+ const baseArgs = `--runtime_hook_url=${runtimeHookUrl} --hook_token=${hookToken}`;
253
+ const startCmd = `node ${hookScriptPath} --event=session_start --source_client=claude-code --phase=intent --message=\"Claude session started\" ${baseArgs}`;
254
+ const toolCmd = `node ${hookScriptPath} --event=task_update --source_client=claude-code --phase=execution --message=\"Claude tool completed\" ${baseArgs}`;
255
+ const stopCmd = `node ${hookScriptPath} --event=session_stop --source_client=claude-code --phase=completed --message=\"Claude session completed\" ${baseArgs}`;
256
+ ensureClaudeHook("SessionStart", "", startCmd);
257
+ ensureClaudeHook("PostToolUse", "Write|Edit|Bash", toolCmd);
258
+ ensureClaudeHook("Stop", "", stopCmd);
259
+ writeFileAtomicSync(claudeSettingsPath, `${JSON.stringify(settings, null, 2)}\n`, {
260
+ mode: 0o600,
261
+ encoding: "utf8",
262
+ });
263
+ result.claudeCode.installed = true;
264
+ }
265
+ deps.sendJson(res, 200, result);
266
+ }
267
+ catch (err) {
268
+ deps.sendJson(res, 500, {
269
+ ok: false,
270
+ error: deps.safeErrorMessage(err),
271
+ });
272
+ }
273
+ }, "Install runtime hooks for Codex/Claude Code");
274
+ router.add("*", "hooks/runtime/setup", ({ res }) => {
275
+ deps.sendJson(res, 405, {
276
+ ok: false,
277
+ error: "Use POST /orgx/api/hooks/runtime/setup",
278
+ });
279
+ }, "Reject unsupported methods for hooks/runtime/setup");
280
+ async function renderRuntimeStream(req, res) {
281
+ const write = res.write?.bind(res);
282
+ if (!write) {
283
+ deps.sendJson(res, 501, { ok: false, error: "Streaming not supported" });
284
+ return;
285
+ }
286
+ res.writeHead(200, {
287
+ "Content-Type": "text/event-stream; charset=utf-8",
288
+ "Cache-Control": "no-cache, no-transform",
289
+ Connection: "keep-alive",
290
+ ...deps.securityHeaders,
291
+ ...deps.corsHeaders,
292
+ });
293
+ const subscriberId = deps.randomUUID();
294
+ const subscriber = {
295
+ id: subscriberId,
296
+ write: (chunk) => write(chunk) !== false,
297
+ end: () => {
298
+ if (!res.writableEnded) {
299
+ res.end();
300
+ }
301
+ },
302
+ };
303
+ deps.runtimeStreamSubscribers.set(subscriberId, subscriber);
304
+ deps.ensureRuntimeStreamTimers();
305
+ try {
306
+ const initial = deps.listRuntimeInstances({ limit: 320 });
307
+ deps.writeRuntimeSseEvent(subscriber, "runtime.updated", initial);
308
+ }
309
+ catch {
310
+ // ignore
311
+ }
312
+ const close = () => {
313
+ deps.runtimeStreamSubscribers.delete(subscriberId);
314
+ try {
315
+ subscriber.end();
316
+ }
317
+ catch {
318
+ // ignore
319
+ }
320
+ if (deps.runtimeStreamSubscribers.size === 0) {
321
+ deps.stopRuntimeStreamTimers();
322
+ }
323
+ };
324
+ req.on?.("close", close);
325
+ req.on?.("aborted", close);
326
+ res.on?.("close", close);
327
+ res.on?.("finish", close);
328
+ }
329
+ router.add("GET", "hooks/runtime/stream", async ({ req, res }) => renderRuntimeStream(req, res), "Subscribe to runtime stream");
330
+ router.add("HEAD", "hooks/runtime/stream", async ({ req, res }) => renderRuntimeStream(req, res), "Subscribe to runtime stream (HEAD)");
331
+ router.add("POST", "hooks/runtime", async ({ req, query, res }) => {
332
+ const reqWithHeaders = req;
333
+ const expectedHookToken = deps.resolveRuntimeHookToken();
334
+ const providedHookToken = deps.pickHeaderString(reqWithHeaders.headers ?? {}, [
335
+ "x-orgx-hook-token",
336
+ "x-hook-token",
337
+ ]) ??
338
+ query.get("hook_token") ??
339
+ query.get("token");
340
+ if (!providedHookToken || providedHookToken.trim() !== expectedHookToken) {
341
+ deps.sendJson(res, 401, {
342
+ ok: false,
343
+ error: "Invalid hook token",
344
+ });
345
+ return;
346
+ }
347
+ try {
348
+ const payloadRecord = await deps.parseJsonRequest(req);
349
+ const payload = {
350
+ source_client: deps.pickString(payloadRecord, ["source_client", "sourceClient"]) ?? "unknown",
351
+ event: deps.pickString(payloadRecord, ["event", "hook_event"]) ?? "heartbeat",
352
+ run_id: deps.pickString(payloadRecord, [
353
+ "run_id",
354
+ "runId",
355
+ "session_id",
356
+ "sessionId",
357
+ ]),
358
+ correlation_id: deps.pickString(payloadRecord, ["correlation_id", "correlationId"]),
359
+ initiative_id: deps.pickString(payloadRecord, ["initiative_id", "initiativeId"]),
360
+ workstream_id: deps.pickString(payloadRecord, ["workstream_id", "workstreamId"]),
361
+ task_id: deps.pickString(payloadRecord, ["task_id", "taskId"]),
362
+ agent_id: deps.pickString(payloadRecord, ["agent_id", "agentId"]),
363
+ agent_name: deps.pickString(payloadRecord, ["agent_name", "agentName"]),
364
+ phase: deps.pickString(payloadRecord, ["phase"]),
365
+ progress_pct: deps.pickNumber(payloadRecord, ["progress_pct", "progressPct"]) ?? null,
366
+ message: deps.pickString(payloadRecord, ["message", "summary"]),
367
+ metadata: payloadRecord.metadata && typeof payloadRecord.metadata === "object"
368
+ ? payloadRecord.metadata
369
+ : null,
370
+ timestamp: deps.pickString(payloadRecord, ["timestamp", "time", "ts"]),
371
+ };
372
+ const instance = deps.upsertRuntimeInstanceFromHook(payload);
373
+ deps.broadcastRuntimeSse("runtime.updated", instance);
374
+ deps.clearSnapshotResponseCache();
375
+ const fallbackPhaseByEvent = {
376
+ session_start: "intent",
377
+ heartbeat: "execution",
378
+ progress: "execution",
379
+ task_update: "execution",
380
+ session_stop: "completed",
381
+ error: "blocked",
382
+ };
383
+ const phase = deps.normalizeHookPhase(payload.phase ?? fallbackPhaseByEvent[instance.event] ?? "execution");
384
+ const level = instance.event === "error" ? "error" : phase === "blocked" ? "warn" : "info";
385
+ const message = payload.message ?? `${instance.displayName} ${instance.event.replace(/_/g, " ")}`;
386
+ let forwarded = false;
387
+ let forwardError = null;
388
+ if (instance.initiativeId) {
389
+ try {
390
+ await deps.emitActivity({
391
+ initiative_id: instance.initiativeId,
392
+ run_id: instance.runId ?? undefined,
393
+ correlation_id: instance.runId
394
+ ? undefined
395
+ : (instance.correlationId ?? undefined),
396
+ source_client: deps.normalizeRuntimeSourceForReporting(instance.sourceClient),
397
+ message,
398
+ phase,
399
+ progress_pct: instance.progressPct ?? undefined,
400
+ level,
401
+ metadata: {
402
+ source: "runtime_hook_relay",
403
+ hook_event: instance.event,
404
+ instance_id: instance.id,
405
+ runtime_client: instance.sourceClient,
406
+ task_id: instance.taskId,
407
+ workstream_id: instance.workstreamId,
408
+ ...(instance.metadata ?? {}),
409
+ },
410
+ });
411
+ forwarded = true;
412
+ }
413
+ catch (err) {
414
+ forwardError = deps.safeErrorMessage(err);
415
+ }
416
+ }
417
+ deps.sendJson(res, 200, {
418
+ ok: true,
419
+ instance_id: instance.id,
420
+ state: instance.state,
421
+ last_seen_at: instance.lastHeartbeatAt ?? instance.lastEventAt,
422
+ run_id: instance.runId ?? null,
423
+ forwarded,
424
+ forward_error: forwardError,
425
+ });
426
+ }
427
+ catch (err) {
428
+ deps.sendJson(res, 500, {
429
+ ok: false,
430
+ error: deps.safeErrorMessage(err),
431
+ });
432
+ }
433
+ }, "Ingest runtime hook events");
434
+ router.add("*", "hooks/runtime", ({ res }) => {
435
+ deps.sendJson(res, 405, { ok: false, error: "Use POST /orgx/api/hooks/runtime" });
436
+ }, "Reject unsupported methods for hooks/runtime");
437
+ }
@@ -0,0 +1,23 @@
1
+ import type { ByokKeysRecord } from "../../byok-store.js";
2
+ import type { Router } from "../router.js";
3
+ type JsonRecord = Record<string, unknown>;
4
+ type RegisterSettingsByokRoutesDeps<TReq, TRes> = {
5
+ parseJsonRequest: (req: TReq) => Promise<JsonRecord>;
6
+ readByokKeys: () => ByokKeysRecord | null;
7
+ writeByokKeys: (input: Partial<ByokKeysRecord>) => ByokKeysRecord;
8
+ maskSecret: (value: string | null) => string | null;
9
+ listAgents: () => Promise<Array<{
10
+ id?: string;
11
+ isDefault?: boolean;
12
+ }>>;
13
+ listOpenClawProviderModels: (input: {
14
+ agentId: string;
15
+ provider: "openai" | "anthropic" | "openrouter";
16
+ }) => Promise<Array<{
17
+ key: string;
18
+ }>>;
19
+ sendJson: (res: TRes, status: number, payload: unknown) => void;
20
+ safeErrorMessage: (err: unknown) => string;
21
+ };
22
+ export declare function registerSettingsByokRoutes<TReq, TRes>(router: Router<Record<string, never>, TReq, TRes>, deps: RegisterSettingsByokRoutesDeps<TReq, TRes>): void;
23
+ export {};
@@ -0,0 +1,163 @@
1
+ function readEnvByokKeys() {
2
+ return {
3
+ openai: process.env.OPENAI_API_KEY ?? null,
4
+ anthropic: process.env.ANTHROPIC_API_KEY ?? null,
5
+ openrouter: process.env.OPENROUTER_API_KEY ?? null,
6
+ };
7
+ }
8
+ export function registerSettingsByokRoutes(router, deps) {
9
+ async function renderByokSettings(req, method, res) {
10
+ const stored = deps.readByokKeys();
11
+ const envKeys = readEnvByokKeys();
12
+ const effectiveOpenai = stored?.openaiApiKey ?? envKeys.openai ?? null;
13
+ const effectiveAnthropic = stored?.anthropicApiKey ?? envKeys.anthropic ?? null;
14
+ const effectiveOpenrouter = stored?.openrouterApiKey ?? envKeys.openrouter ?? null;
15
+ const toProvider = (input) => {
16
+ const hasStored = typeof input.storedValue === "string" && input.storedValue.trim().length > 0;
17
+ const hasEnv = typeof input.envValue === "string" && input.envValue.trim().length > 0;
18
+ const source = hasStored ? "stored" : hasEnv ? "env" : "none";
19
+ return {
20
+ configured: Boolean(input.effective && input.effective.trim().length > 0),
21
+ source,
22
+ masked: deps.maskSecret(input.effective),
23
+ };
24
+ };
25
+ if (method === "POST") {
26
+ try {
27
+ const payload = await deps.parseJsonRequest(req);
28
+ const updates = {};
29
+ const setIfPresent = (key, aliases) => {
30
+ for (const alias of aliases) {
31
+ if (!Object.prototype.hasOwnProperty.call(payload, alias))
32
+ continue;
33
+ const raw = payload[alias];
34
+ if (raw === null || typeof raw === "string") {
35
+ updates[key] = raw;
36
+ return;
37
+ }
38
+ }
39
+ };
40
+ setIfPresent("openaiApiKey", [
41
+ "openaiApiKey",
42
+ "openai_api_key",
43
+ "openaiKey",
44
+ "openai_key",
45
+ ]);
46
+ setIfPresent("anthropicApiKey", [
47
+ "anthropicApiKey",
48
+ "anthropic_api_key",
49
+ "anthropicKey",
50
+ "anthropic_key",
51
+ ]);
52
+ setIfPresent("openrouterApiKey", [
53
+ "openrouterApiKey",
54
+ "openrouter_api_key",
55
+ "openrouterKey",
56
+ "openrouter_key",
57
+ ]);
58
+ const saved = deps.writeByokKeys(updates);
59
+ const nextEffectiveOpenai = saved.openaiApiKey ?? envKeys.openai ?? null;
60
+ const nextEffectiveAnthropic = saved.anthropicApiKey ?? envKeys.anthropic ?? null;
61
+ const nextEffectiveOpenrouter = saved.openrouterApiKey ?? envKeys.openrouter ?? null;
62
+ deps.sendJson(res, 200, {
63
+ ok: true,
64
+ updatedAt: saved.updatedAt,
65
+ providers: {
66
+ openai: toProvider({
67
+ storedValue: saved.openaiApiKey,
68
+ envValue: envKeys.openai,
69
+ effective: nextEffectiveOpenai,
70
+ }),
71
+ anthropic: toProvider({
72
+ storedValue: saved.anthropicApiKey,
73
+ envValue: envKeys.anthropic,
74
+ effective: nextEffectiveAnthropic,
75
+ }),
76
+ openrouter: toProvider({
77
+ storedValue: saved.openrouterApiKey,
78
+ envValue: envKeys.openrouter,
79
+ effective: nextEffectiveOpenrouter,
80
+ }),
81
+ },
82
+ });
83
+ }
84
+ catch (err) {
85
+ deps.sendJson(res, 500, { ok: false, error: deps.safeErrorMessage(err) });
86
+ }
87
+ return;
88
+ }
89
+ deps.sendJson(res, 200, {
90
+ ok: true,
91
+ updatedAt: stored?.updatedAt ?? null,
92
+ providers: {
93
+ openai: toProvider({
94
+ storedValue: stored?.openaiApiKey,
95
+ envValue: envKeys.openai,
96
+ effective: effectiveOpenai,
97
+ }),
98
+ anthropic: toProvider({
99
+ storedValue: stored?.anthropicApiKey,
100
+ envValue: envKeys.anthropic,
101
+ effective: effectiveAnthropic,
102
+ }),
103
+ openrouter: toProvider({
104
+ storedValue: stored?.openrouterApiKey,
105
+ envValue: envKeys.openrouter,
106
+ effective: effectiveOpenrouter,
107
+ }),
108
+ },
109
+ });
110
+ }
111
+ async function renderByokHealth(query, res) {
112
+ let agentId = (query.get("agentId") ?? query.get("agent_id") ?? "").trim();
113
+ if (!agentId) {
114
+ try {
115
+ const agents = await deps.listAgents();
116
+ const defaultAgent = agents.find((entry) => Boolean(entry.isDefault)) ?? agents[0] ?? null;
117
+ const candidate = defaultAgent && typeof defaultAgent.id === "string"
118
+ ? defaultAgent.id.trim()
119
+ : "";
120
+ if (candidate)
121
+ agentId = candidate;
122
+ }
123
+ catch {
124
+ // ignore
125
+ }
126
+ }
127
+ if (!agentId)
128
+ agentId = "main";
129
+ const providers = {};
130
+ for (const provider of ["openai", "anthropic", "openrouter"]) {
131
+ try {
132
+ const models = await deps.listOpenClawProviderModels({ agentId, provider });
133
+ providers[provider] = {
134
+ ok: true,
135
+ modelCount: models.length,
136
+ sample: models.slice(0, 4).map((model) => model.key),
137
+ };
138
+ }
139
+ catch (err) {
140
+ providers[provider] = {
141
+ ok: false,
142
+ error: deps.safeErrorMessage(err),
143
+ };
144
+ }
145
+ }
146
+ deps.sendJson(res, 200, {
147
+ ok: true,
148
+ agentId,
149
+ providers,
150
+ });
151
+ }
152
+ router.add("GET", "settings/byok", async ({ req, res }) => renderByokSettings(req, "GET", res), "Read BYOK settings");
153
+ router.add("POST", "settings/byok", async ({ req, res }) => renderByokSettings(req, "POST", res), "Write BYOK settings");
154
+ router.add("HEAD", "settings/byok", async ({ req, res }) => renderByokSettings(req, "GET", res), "Read BYOK settings (HEAD)");
155
+ router.add("GET", "settings/byok/health", async ({ query, res }) => renderByokHealth(query, res), "Probe BYOK provider health");
156
+ router.add("HEAD", "settings/byok/health", async ({ query, res }) => renderByokHealth(query, res), "Probe BYOK provider health (HEAD)");
157
+ router.add("*", "settings/byok", ({ res }) => {
158
+ deps.sendJson(res, 405, { ok: false, error: "Method not allowed" });
159
+ }, "Reject unsupported methods for settings/byok");
160
+ router.add("*", "settings/byok/health", ({ res }) => {
161
+ deps.sendJson(res, 405, { ok: false, error: "Method not allowed" });
162
+ }, "Reject unsupported methods for settings/byok/health");
163
+ }
@@ -0,0 +1,18 @@
1
+ import type { OrgSnapshot } from "../../types.js";
2
+ import type { Router } from "../router.js";
3
+ type SummaryRoutesDeps<TRes> = {
4
+ getSnapshot: () => OrgSnapshot | null;
5
+ getOrgSnapshot: () => Promise<OrgSnapshot>;
6
+ sendJson: (res: TRes, status: number, payload: unknown) => void;
7
+ writeHead: (res: TRes, status: number, headers: Record<string, string>) => void;
8
+ end: (res: TRes) => void;
9
+ securityHeaders: Record<string, string>;
10
+ corsHeaders: Record<string, string>;
11
+ formatStatus: (snapshot: OrgSnapshot | null) => unknown;
12
+ formatAgents: (snapshot: OrgSnapshot | null) => unknown;
13
+ formatActivity: (snapshot: OrgSnapshot | null) => unknown;
14
+ formatInitiatives: (snapshot: OrgSnapshot | null) => unknown;
15
+ getOnboardingState: () => Promise<unknown>;
16
+ };
17
+ export declare function registerSummaryRoutes<TReq, TRes>(router: Router<Record<string, never>, TReq, TRes>, deps: SummaryRoutesDeps<TRes>): void;
18
+ export {};