@useorgx/openclaw-plugin 0.4.9 → 0.7.2

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 (224) hide show
  1. package/README.md +77 -11
  2. package/dashboard/dist/assets/6mILZQ2a.js +1 -0
  3. package/dashboard/dist/assets/6mILZQ2a.js.br +0 -0
  4. package/dashboard/dist/assets/6mILZQ2a.js.gz +0 -0
  5. package/dashboard/dist/assets/8dksYiq4.js +2 -0
  6. package/dashboard/dist/assets/8dksYiq4.js.br +0 -0
  7. package/dashboard/dist/assets/8dksYiq4.js.gz +0 -0
  8. package/dashboard/dist/assets/B5zYRHc3.js +1 -0
  9. package/dashboard/dist/assets/B5zYRHc3.js.br +0 -0
  10. package/dashboard/dist/assets/B5zYRHc3.js.gz +0 -0
  11. package/dashboard/dist/assets/B6wPWJ35.js +1 -0
  12. package/dashboard/dist/assets/B6wPWJ35.js.br +0 -0
  13. package/dashboard/dist/assets/B6wPWJ35.js.gz +0 -0
  14. package/dashboard/dist/assets/BJgZIVUQ.js +53 -0
  15. package/dashboard/dist/assets/BJgZIVUQ.js.br +0 -0
  16. package/dashboard/dist/assets/BJgZIVUQ.js.gz +0 -0
  17. package/dashboard/dist/assets/BWEwjt1W.js +1 -0
  18. package/dashboard/dist/assets/BWEwjt1W.js.br +0 -0
  19. package/dashboard/dist/assets/BWEwjt1W.js.gz +0 -0
  20. package/dashboard/dist/assets/BgOYB78t.js +4 -0
  21. package/dashboard/dist/assets/BgOYB78t.js.br +0 -0
  22. package/dashboard/dist/assets/BgOYB78t.js.gz +0 -0
  23. package/dashboard/dist/assets/BzRbDCAD.css +1 -0
  24. package/dashboard/dist/assets/BzRbDCAD.css.br +0 -0
  25. package/dashboard/dist/assets/BzRbDCAD.css.gz +0 -0
  26. package/dashboard/dist/assets/C-KIc3Wc.js.br +0 -0
  27. package/dashboard/dist/assets/C-KIc3Wc.js.gz +0 -0
  28. package/dashboard/dist/assets/C8uM3AX8.js +1 -0
  29. package/dashboard/dist/assets/C8uM3AX8.js.br +0 -0
  30. package/dashboard/dist/assets/C8uM3AX8.js.gz +0 -0
  31. package/dashboard/dist/assets/C9jy61eu.js +212 -0
  32. package/dashboard/dist/assets/C9jy61eu.js.br +0 -0
  33. package/dashboard/dist/assets/C9jy61eu.js.gz +0 -0
  34. package/dashboard/dist/assets/CC63EwFD.js +1 -0
  35. package/dashboard/dist/assets/CC63EwFD.js.br +0 -0
  36. package/dashboard/dist/assets/CC63EwFD.js.gz +0 -0
  37. package/dashboard/dist/assets/CL_wXqR7.js +1 -0
  38. package/dashboard/dist/assets/CL_wXqR7.js.br +0 -0
  39. package/dashboard/dist/assets/CL_wXqR7.js.gz +0 -0
  40. package/dashboard/dist/assets/CZaT3ob_.js +1 -0
  41. package/dashboard/dist/assets/CZaT3ob_.js.br +0 -0
  42. package/dashboard/dist/assets/CZaT3ob_.js.gz +0 -0
  43. package/dashboard/dist/assets/CgaottFX.js +1 -0
  44. package/dashboard/dist/assets/CgaottFX.js.br +0 -0
  45. package/dashboard/dist/assets/CgaottFX.js.gz +0 -0
  46. package/dashboard/dist/assets/{CpJsfbXo.js → CxQ08qFN.js} +2 -2
  47. package/dashboard/dist/assets/CxQ08qFN.js.br +0 -0
  48. package/dashboard/dist/assets/CxQ08qFN.js.gz +0 -0
  49. package/dashboard/dist/assets/CzCxAZlW.js +1 -0
  50. package/dashboard/dist/assets/CzCxAZlW.js.br +0 -0
  51. package/dashboard/dist/assets/CzCxAZlW.js.gz +0 -0
  52. package/dashboard/dist/assets/D3iMTYEj.js +1 -0
  53. package/dashboard/dist/assets/D3iMTYEj.js.br +0 -0
  54. package/dashboard/dist/assets/D3iMTYEj.js.gz +0 -0
  55. package/dashboard/dist/assets/D8JNX8kq.js +2 -0
  56. package/dashboard/dist/assets/D8JNX8kq.js.br +0 -0
  57. package/dashboard/dist/assets/D8JNX8kq.js.gz +0 -0
  58. package/dashboard/dist/assets/DnA8dpj6.js +1 -0
  59. package/dashboard/dist/assets/DnA8dpj6.js.br +0 -0
  60. package/dashboard/dist/assets/DnA8dpj6.js.gz +0 -0
  61. package/dashboard/dist/assets/IUexzymk.js +1 -0
  62. package/dashboard/dist/assets/IUexzymk.js.br +0 -0
  63. package/dashboard/dist/assets/IUexzymk.js.gz +0 -0
  64. package/dashboard/dist/assets/cNrhgGc1.js +8 -0
  65. package/dashboard/dist/assets/cNrhgGc1.js.br +0 -0
  66. package/dashboard/dist/assets/cNrhgGc1.js.gz +0 -0
  67. package/dashboard/dist/assets/ic2FaMnh.js +1 -0
  68. package/dashboard/dist/assets/ic2FaMnh.js.br +0 -0
  69. package/dashboard/dist/assets/ic2FaMnh.js.gz +0 -0
  70. package/dashboard/dist/assets/qm8xLgv-.css +1 -0
  71. package/dashboard/dist/assets/qm8xLgv-.css.br +0 -0
  72. package/dashboard/dist/assets/qm8xLgv-.css.gz +0 -0
  73. package/dashboard/dist/assets/rttbDbEx.js +1 -0
  74. package/dashboard/dist/assets/rttbDbEx.js.br +0 -0
  75. package/dashboard/dist/assets/rttbDbEx.js.gz +0 -0
  76. package/dashboard/dist/brand/anthropic-mark.svg.br +0 -0
  77. package/dashboard/dist/brand/anthropic-mark.svg.gz +0 -0
  78. package/dashboard/dist/brand/openai-mark.svg.br +0 -0
  79. package/dashboard/dist/brand/openai-mark.svg.gz +0 -0
  80. package/dashboard/dist/brand/openclaw-mark.svg.br +0 -0
  81. package/dashboard/dist/brand/openclaw-mark.svg.gz +0 -0
  82. package/dashboard/dist/brand/xandy-orchestrator.png +0 -0
  83. package/dashboard/dist/index.html +7 -5
  84. package/dashboard/dist/index.html.br +0 -0
  85. package/dashboard/dist/index.html.gz +0 -0
  86. package/dist/activity-actor-fields.js +26 -4
  87. package/dist/activity-store.js +34 -8
  88. package/dist/agent-context-store.js +79 -17
  89. package/dist/agent-run-store.js +44 -3
  90. package/dist/agent-suite.d.ts +9 -0
  91. package/dist/agent-suite.js +149 -9
  92. package/dist/artifacts/artifact-domain-schemas.d.ts +66 -0
  93. package/dist/artifacts/artifact-domain-schemas.js +357 -0
  94. package/dist/artifacts/register-artifact.d.ts +4 -3
  95. package/dist/artifacts/register-artifact.js +170 -57
  96. package/dist/chat-store.d.ts +157 -0
  97. package/dist/chat-store.js +586 -0
  98. package/dist/cli/orgx.js +11 -0
  99. package/dist/contracts/client.d.ts +43 -3
  100. package/dist/contracts/client.js +159 -30
  101. package/dist/contracts/practice-exercise-schema.d.ts +216 -0
  102. package/dist/contracts/practice-exercise-schema.js +314 -0
  103. package/dist/contracts/retro-schema.d.ts +81 -0
  104. package/dist/contracts/retro-schema.js +80 -0
  105. package/dist/contracts/shared-types.d.ts +159 -0
  106. package/dist/contracts/shared-types.js +199 -1
  107. package/dist/contracts/skill-pack-schema.d.ts +192 -0
  108. package/dist/contracts/skill-pack-schema.js +180 -0
  109. package/dist/contracts/types.d.ts +247 -2
  110. package/dist/entities/auto-assignment.js +43 -17
  111. package/dist/event-sanitization.d.ts +11 -0
  112. package/dist/event-sanitization.js +113 -0
  113. package/dist/gateway-watchdog.d.ts +5 -0
  114. package/dist/gateway-watchdog.js +50 -0
  115. package/dist/hooks/post-reporting-event.mjs +1 -5
  116. package/dist/http/helpers/activity-headline.js +13 -132
  117. package/dist/http/helpers/auto-continue-engine.d.ts +198 -10
  118. package/dist/http/helpers/auto-continue-engine.js +3145 -186
  119. package/dist/http/helpers/autopilot-operations.d.ts +19 -0
  120. package/dist/http/helpers/autopilot-operations.js +182 -31
  121. package/dist/http/helpers/autopilot-runtime.d.ts +1 -0
  122. package/dist/http/helpers/autopilot-runtime.js +328 -25
  123. package/dist/http/helpers/autopilot-slice-utils.d.ts +18 -0
  124. package/dist/http/helpers/autopilot-slice-utils.js +514 -93
  125. package/dist/http/helpers/decision-mapper.d.ts +40 -0
  126. package/dist/http/helpers/decision-mapper.js +223 -7
  127. package/dist/http/helpers/dispatch-lifecycle.d.ts +19 -2
  128. package/dist/http/helpers/dispatch-lifecycle.js +242 -37
  129. package/dist/http/helpers/kickoff-context.js +104 -0
  130. package/dist/http/helpers/llm-client.d.ts +47 -0
  131. package/dist/http/helpers/llm-client.js +256 -0
  132. package/dist/http/helpers/mission-control.d.ts +102 -3
  133. package/dist/http/helpers/mission-control.js +498 -9
  134. package/dist/http/helpers/sentinel-catalog.d.ts +23 -0
  135. package/dist/http/helpers/sentinel-catalog.js +193 -0
  136. package/dist/http/helpers/session-classification.d.ts +9 -0
  137. package/dist/http/helpers/session-classification.js +564 -0
  138. package/dist/http/helpers/slice-experience-v2.d.ts +137 -0
  139. package/dist/http/helpers/slice-experience-v2.js +677 -0
  140. package/dist/http/helpers/slice-run-projections.d.ts +72 -0
  141. package/dist/http/helpers/slice-run-projections.js +877 -0
  142. package/dist/http/helpers/triage-mapper.d.ts +43 -0
  143. package/dist/http/helpers/triage-mapper.js +549 -0
  144. package/dist/http/helpers/value-utils.js +7 -2
  145. package/dist/http/helpers/workspace-scope.d.ts +15 -0
  146. package/dist/http/helpers/workspace-scope.js +170 -0
  147. package/dist/http/index.js +1420 -105
  148. package/dist/http/routes/agent-suite.d.ts +9 -0
  149. package/dist/http/routes/agent-suite.js +294 -8
  150. package/dist/http/routes/agents-catalog.js +64 -19
  151. package/dist/http/routes/chat.d.ts +19 -0
  152. package/dist/http/routes/chat.js +522 -0
  153. package/dist/http/routes/decision-actions.d.ts +8 -1
  154. package/dist/http/routes/decision-actions.js +42 -5
  155. package/dist/http/routes/dispatch-gateway-envelope.d.ts +25 -0
  156. package/dist/http/routes/dispatch-gateway-envelope.js +26 -0
  157. package/dist/http/routes/entities.d.ts +16 -0
  158. package/dist/http/routes/entities.js +232 -6
  159. package/dist/http/routes/live-legacy.d.ts +5 -0
  160. package/dist/http/routes/live-legacy.js +23 -509
  161. package/dist/http/routes/live-misc.d.ts +12 -0
  162. package/dist/http/routes/live-misc.js +251 -31
  163. package/dist/http/routes/live-snapshot.d.ts +49 -2
  164. package/dist/http/routes/live-snapshot.js +653 -23
  165. package/dist/http/routes/live-terminal.d.ts +11 -0
  166. package/dist/http/routes/live-terminal.js +154 -0
  167. package/dist/http/routes/live-triage.d.ts +61 -0
  168. package/dist/http/routes/live-triage.js +192 -0
  169. package/dist/http/routes/mission-control-actions.d.ts +49 -1
  170. package/dist/http/routes/mission-control-actions.js +1246 -84
  171. package/dist/http/routes/mission-control-read.d.ts +48 -3
  172. package/dist/http/routes/mission-control-read.js +1658 -20
  173. package/dist/http/routes/realtime-orchestrator.d.ts +10 -0
  174. package/dist/http/routes/realtime-orchestrator.js +74 -0
  175. package/dist/http/routes/run-control.d.ts +5 -2
  176. package/dist/http/routes/run-control.js +10 -0
  177. package/dist/http/routes/sentinels-catalog.d.ts +7 -0
  178. package/dist/http/routes/sentinels-catalog.js +24 -0
  179. package/dist/http/routes/summary.js +10 -3
  180. package/dist/http/routes/usage.d.ts +24 -0
  181. package/dist/http/routes/usage.js +362 -0
  182. package/dist/http/routes/work-artifacts.js +28 -9
  183. package/dist/index.js +165 -27
  184. package/dist/local-openclaw.js +29 -6
  185. package/dist/mcp-client-setup.js +3 -3
  186. package/dist/mcp-http-handler.d.ts +3 -0
  187. package/dist/mcp-http-handler.js +34 -60
  188. package/dist/next-up-queue-store.d.ts +16 -1
  189. package/dist/next-up-queue-store.js +89 -7
  190. package/dist/outbox.d.ts +5 -0
  191. package/dist/outbox.js +113 -9
  192. package/dist/paths.js +36 -5
  193. package/dist/reporting/rollups.d.ts +41 -0
  194. package/dist/reporting/rollups.js +113 -0
  195. package/dist/retro/domain-templates.d.ts +45 -0
  196. package/dist/retro/domain-templates.js +297 -0
  197. package/dist/retro/quality-rubric.d.ts +33 -0
  198. package/dist/retro/quality-rubric.js +213 -0
  199. package/dist/runtime-cleanup.d.ts +18 -0
  200. package/dist/runtime-cleanup.js +87 -0
  201. package/dist/services/background.d.ts +11 -0
  202. package/dist/services/background.js +22 -0
  203. package/dist/services/experiment-randomization.d.ts +21 -0
  204. package/dist/services/experiment-randomization.js +63 -0
  205. package/dist/skill-pack-state.d.ts +36 -5
  206. package/dist/skill-pack-state.js +273 -29
  207. package/dist/sync/local-agent-telemetry.d.ts +13 -0
  208. package/dist/sync/local-agent-telemetry.js +128 -0
  209. package/dist/sync/outbox-replay.js +131 -24
  210. package/dist/team-context-store.d.ts +23 -0
  211. package/dist/team-context-store.js +116 -0
  212. package/dist/telemetry/posthog.js +4 -2
  213. package/dist/tools/core-tools.d.ts +10 -14
  214. package/dist/tools/core-tools.js +1289 -24
  215. package/dist/types.d.ts +2 -0
  216. package/dist/types.js +2 -0
  217. package/dist/worker-supervisor.js +23 -0
  218. package/package.json +20 -6
  219. package/dashboard/dist/assets/B3ziCA02.js +0 -8
  220. package/dashboard/dist/assets/B5NEElEI.css +0 -1
  221. package/dashboard/dist/assets/BhapSNAs.js +0 -215
  222. package/dashboard/dist/assets/iFdvE7lx.js +0 -1
  223. package/dashboard/dist/assets/jRJsmpYM.js +0 -1
  224. package/dashboard/dist/assets/sAhvFnpk.js +0 -4
@@ -1,8 +1,10 @@
1
1
  import { randomUUID as randomUuidFn } from "node:crypto";
2
2
  import { registerArtifact } from "../artifacts/register-artifact.js";
3
+ import { validateOpenClawSkillPackManifest } from "../contracts/skill-pack-schema.js";
4
+ import { listBuiltInSentinels } from "../http/helpers/sentinel-catalog.js";
3
5
  import { appendToOutbox } from "../outbox.js";
4
6
  export function registerCoreTools(deps) {
5
- const { registerTool, client, config, getCachedSnapshot, getLastSnapshotAt, doSync, text, json, formatSnapshot, autoAssignEntityForCreate, toReportingPhase, inferReportingInitiativeId, isUuid, pickNonEmptyString, resolveReportingContext, readSkillPackState, } = deps;
7
+ const { registerTool, client, config, getCachedSnapshot, getLastSnapshotAt, doSync, text, json, formatSnapshot, autoAssignEntityForCreate, toReportingPhase, inferReportingInitiativeId, isUuid, pickNonEmptyString, resolveReportingContext, readSkillPackState, updateSkillPackPolicy, rollbackSkillPackPolicy, } = deps;
6
8
  const randomUUID = deps.randomUUID ?? randomUuidFn;
7
9
  const mcpToolRegistry = new Map();
8
10
  const registerMcpTool = (tool, options) => {
@@ -31,6 +33,34 @@ export function registerCoreTools(deps) {
31
33
  },
32
34
  }, { optional: true });
33
35
  // --- orgx_sync ---
36
+ registerMcpTool({
37
+ name: "orgx_sentinel_catalog",
38
+ description: "List built-in proactive sentinel templates. Supports optional domain filter.",
39
+ parameters: {
40
+ type: "object",
41
+ properties: {
42
+ domain: {
43
+ type: "string",
44
+ description: "Optional domain filter (engineering, sales, operations, product).",
45
+ },
46
+ },
47
+ additionalProperties: false,
48
+ },
49
+ async execute(_callId, params = {}) {
50
+ try {
51
+ const sentinels = listBuiltInSentinels({ domain: params.domain });
52
+ return json("Sentinel catalog:", {
53
+ generatedAt: new Date().toISOString(),
54
+ domain: params.domain ?? null,
55
+ count: sentinels.length,
56
+ sentinels,
57
+ });
58
+ }
59
+ catch (err) {
60
+ return text(`❌ Failed to load sentinel catalog: ${err instanceof Error ? err.message : err}`);
61
+ }
62
+ },
63
+ }, { optional: true });
34
64
  registerMcpTool({
35
65
  name: "orgx_sync",
36
66
  description: "Push/pull memory sync with OrgX. Send local memory/daily log; receive initiatives, tasks, decisions, model routing policy.",
@@ -45,13 +75,35 @@ export function registerCoreTools(deps) {
45
75
  type: "string",
46
76
  description: "Today's session log to push",
47
77
  },
78
+ agents: {
79
+ type: "array",
80
+ description: "Optional local agent states to sync into OrgX",
81
+ items: {
82
+ type: "object",
83
+ properties: {
84
+ id: { type: "string" },
85
+ name: { type: "string" },
86
+ domain: { type: "string" },
87
+ status: {
88
+ type: "string",
89
+ enum: ["active", "idle", "throttled"],
90
+ },
91
+ currentTask: { type: "string" },
92
+ lastActive: { type: "string" },
93
+ },
94
+ required: ["id", "name", "domain", "status"],
95
+ additionalProperties: false,
96
+ },
97
+ },
48
98
  },
99
+ additionalProperties: false,
49
100
  },
50
101
  async execute(_callId, params = {}) {
51
102
  try {
52
103
  const resp = await client.syncMemory({
53
104
  memory: params.memory,
54
105
  dailyLog: params.dailyLog,
106
+ agents: params.agents,
55
107
  });
56
108
  return json("Sync complete:", resp);
57
109
  }
@@ -286,6 +338,235 @@ export function registerCoreTools(deps) {
286
338
  }
287
339
  },
288
340
  }, { optional: true });
341
+ // --- orgx_proof_status ---
342
+ registerMcpTool({
343
+ name: "orgx_proof_status",
344
+ description: "Check the proof chain status for a task or run. Returns a checklist of proof levels (L1-L7) with gaps highlighted. Use this to verify work is provably complete before marking done.",
345
+ parameters: {
346
+ type: "object",
347
+ properties: {
348
+ task_id: {
349
+ type: "string",
350
+ description: "ID of the task to check proof for",
351
+ },
352
+ run_id: {
353
+ type: "string",
354
+ description: "ID of the run to check proof for",
355
+ },
356
+ },
357
+ },
358
+ async execute(_callId, params = {}) {
359
+ const taskId = params.task_id || undefined;
360
+ const runId = params.run_id || undefined;
361
+ if (!taskId && !runId) {
362
+ return text("❌ Provide at least one of task_id or run_id.");
363
+ }
364
+ try {
365
+ const qp = new URLSearchParams();
366
+ if (taskId)
367
+ qp.set("task_id", taskId);
368
+ if (runId)
369
+ qp.set("run_id", runId);
370
+ const result = await client.rawRequest("GET", `/api/flywheel/proof-status?${qp.toString()}`);
371
+ // Format proof chain as readable checklist
372
+ const levels = Array.isArray(result?.levels) ? result.levels : [];
373
+ const overall = result?.overall_passed === true;
374
+ const reasonCodes = Array.isArray(result?.reason_codes) ? result.reason_codes : [];
375
+ if (levels.length === 0) {
376
+ // Server may not have proof-status route yet (phase 1)
377
+ return json("Proof status (local evaluation)", {
378
+ task_id: taskId,
379
+ run_id: runId,
380
+ note: "Proof-status API not available. Register artifacts with atomic_unit_type metadata and use orgx_quality_score to build proof chain.",
381
+ overall_passed: false,
382
+ reason_codes: ["proof_api_not_available"],
383
+ });
384
+ }
385
+ const lines = [];
386
+ lines.push(overall ? "✅ Proof chain complete" : "⚠️ Proof chain incomplete");
387
+ lines.push("");
388
+ for (const lvl of levels) {
389
+ const icon = lvl.passed ? "✅" : lvl.enforcement === "soft_warn" ? "⚠️" : "❌";
390
+ lines.push(`${icon} ${lvl.label || lvl.level}: ${lvl.passed ? "passed" : "missing"}`);
391
+ if (Array.isArray(lvl.missingFields) && lvl.missingFields.length > 0) {
392
+ lines.push(` Missing: ${lvl.missingFields.join(", ")}`);
393
+ }
394
+ }
395
+ if (reasonCodes.length > 0) {
396
+ lines.push("");
397
+ lines.push(`Reason codes: ${reasonCodes.join(", ")}`);
398
+ }
399
+ return json(lines.join("\n"), result);
400
+ }
401
+ catch (err) {
402
+ // Graceful degradation: if proof API is not deployed, return helpful guidance
403
+ const msg = err instanceof Error ? err.message : String(err);
404
+ if (msg.includes("404") || msg.includes("not found") || msg.includes("Not Found")) {
405
+ return json("Proof status (server not ready)", {
406
+ task_id: taskId,
407
+ run_id: runId,
408
+ note: "Proof-status API not deployed yet. Ensure artifacts are registered with atomic_unit_type metadata to build the proof chain.",
409
+ overall_passed: false,
410
+ reason_codes: ["proof_api_not_deployed"],
411
+ });
412
+ }
413
+ return text(`❌ Proof status check failed: ${msg}`);
414
+ }
415
+ },
416
+ }, { optional: true });
417
+ // --- orgx_verify_completion ---
418
+ registerMcpTool({
419
+ name: "orgx_verify_completion",
420
+ description: "Verify that an entity (task/milestone/workstream) meets completion requirements including proof chain. Returns structured result with any blocking issues.",
421
+ parameters: {
422
+ type: "object",
423
+ properties: {
424
+ entity_type: {
425
+ type: "string",
426
+ description: "Entity type: task, milestone, workstream",
427
+ },
428
+ entity_id: {
429
+ type: "string",
430
+ description: "ID of the entity to verify",
431
+ },
432
+ },
433
+ required: ["entity_type", "entity_id"],
434
+ },
435
+ async execute(_callId, params = {
436
+ entity_type: "",
437
+ entity_id: "",
438
+ }) {
439
+ try {
440
+ const result = await client.rawRequest("POST", "/api/client/entities/verify-completion", {
441
+ entity_type: params.entity_type,
442
+ entity_id: params.entity_id,
443
+ });
444
+ const ready = result?.ready === true;
445
+ return json(ready
446
+ ? `✅ ${params.entity_type} ${params.entity_id} is ready for completion.`
447
+ : `⚠️ ${params.entity_type} ${params.entity_id} has blocking issues.`, result);
448
+ }
449
+ catch (err) {
450
+ const msg = err instanceof Error ? err.message : String(err);
451
+ if (msg.includes("404") || msg.includes("not found") || msg.includes("Not Found")) {
452
+ return json("Completion verification (server not ready)", {
453
+ entity_type: params.entity_type,
454
+ entity_id: params.entity_id,
455
+ ready: true,
456
+ note: "Completion verification API not deployed. Proceeding with standard completion.",
457
+ });
458
+ }
459
+ return text(`❌ Completion verification failed: ${msg}`);
460
+ }
461
+ },
462
+ }, { optional: true });
463
+ // --- orgx_record_outcome ---
464
+ registerMcpTool({
465
+ name: "orgx_record_outcome",
466
+ description: "Record an outcome event for a completed run, linking execution to business value. Required for L5 (Impact) proof level.",
467
+ parameters: {
468
+ type: "object",
469
+ properties: {
470
+ initiative_id: {
471
+ type: "string",
472
+ description: "Initiative ID this outcome belongs to",
473
+ },
474
+ execution_id: {
475
+ type: "string",
476
+ description: "Execution/run ID that produced this outcome",
477
+ },
478
+ agent_id: {
479
+ type: "string",
480
+ description: "Agent that did the work",
481
+ },
482
+ success: {
483
+ type: "boolean",
484
+ description: "Whether the execution was successful",
485
+ },
486
+ quality_score: {
487
+ type: "number",
488
+ description: "Quality score 1-5",
489
+ },
490
+ domain: {
491
+ type: "string",
492
+ description: "Agent domain",
493
+ },
494
+ metadata: {
495
+ type: "object",
496
+ description: "Additional outcome metadata",
497
+ },
498
+ },
499
+ required: ["initiative_id", "execution_id", "agent_id", "success"],
500
+ },
501
+ async execute(_callId, params = {
502
+ initiative_id: "",
503
+ execution_id: "",
504
+ agent_id: "",
505
+ success: false,
506
+ }) {
507
+ try {
508
+ const result = await client.recordRunOutcome({
509
+ initiative_id: params.initiative_id,
510
+ execution_id: params.execution_id,
511
+ execution_type: "agent_run",
512
+ agent_id: params.agent_id,
513
+ success: params.success,
514
+ quality_score: params.quality_score,
515
+ domain: params.domain,
516
+ metadata: params.metadata,
517
+ });
518
+ return json(`✅ Outcome recorded for run ${result.run_id} (event: ${result.event_id})`, result);
519
+ }
520
+ catch (err) {
521
+ return text(`❌ Outcome recording failed: ${err instanceof Error ? err.message : err}`);
522
+ }
523
+ },
524
+ }, { optional: true });
525
+ // --- orgx_get_outcome_attribution ---
526
+ registerMcpTool({
527
+ name: "orgx_get_outcome_attribution",
528
+ description: "Get outcome attribution data for a task or run. Shows what value was attributed to the work and with what confidence. Required for L6 (Economics) proof level.",
529
+ parameters: {
530
+ type: "object",
531
+ properties: {
532
+ task_id: {
533
+ type: "string",
534
+ description: "Task ID to get attribution for",
535
+ },
536
+ run_id: {
537
+ type: "string",
538
+ description: "Run ID to get attribution for",
539
+ },
540
+ },
541
+ },
542
+ async execute(_callId, params = {}) {
543
+ const taskId = params.task_id || undefined;
544
+ const runId = params.run_id || undefined;
545
+ if (!taskId && !runId) {
546
+ return text("❌ Provide at least one of task_id or run_id.");
547
+ }
548
+ try {
549
+ const qp = new URLSearchParams();
550
+ if (taskId)
551
+ qp.set("task_id", taskId);
552
+ if (runId)
553
+ qp.set("run_id", runId);
554
+ const result = await client.rawRequest("GET", `/api/flywheel/outcome-attribution?${qp.toString()}`);
555
+ return json("Outcome attribution", result);
556
+ }
557
+ catch (err) {
558
+ const msg = err instanceof Error ? err.message : String(err);
559
+ if (msg.includes("404") || msg.includes("not found") || msg.includes("Not Found")) {
560
+ return json("Outcome attribution (not available)", {
561
+ task_id: taskId,
562
+ run_id: runId,
563
+ note: "Attribution API not deployed yet.",
564
+ });
565
+ }
566
+ return text(`❌ Attribution lookup failed: ${msg}`);
567
+ }
568
+ },
569
+ }, { optional: true });
289
570
  // --- orgx_create_entity ---
290
571
  registerMcpTool({
291
572
  name: "orgx_create_entity",
@@ -317,9 +598,13 @@ export function registerCoreTools(deps) {
317
598
  type: "string",
318
599
  description: "Parent workstream ID (for tasks)",
319
600
  },
601
+ workspace_id: {
602
+ type: "string",
603
+ description: "Workspace ID (canonical; preferred for new callers)",
604
+ },
320
605
  command_center_id: {
321
606
  type: "string",
322
- description: "Command center ID (for initiatives)",
607
+ description: "Deprecated alias for workspace_id",
323
608
  },
324
609
  },
325
610
  required: ["type", "title"],
@@ -404,14 +689,537 @@ export function registerCoreTools(deps) {
404
689
  async execute(_callId, params = {}) {
405
690
  try {
406
691
  const { type, id, ...updates } = params;
407
- const entity = await client.updateEntity(type, id, updates);
408
- return json(`✅ Updated ${type} ${id.slice(0, 8)}`, entity);
692
+ const result = await client.updateEntityDetailed(type, id, updates);
693
+ const payload = result.reassignment || result.initiative_reassignment
694
+ ? {
695
+ entity: result.entity,
696
+ reassignment: result.reassignment ?? null,
697
+ initiative_reassignment: result.initiative_reassignment ?? null,
698
+ }
699
+ : result.entity;
700
+ return json(`✅ Updated ${type} ${id.slice(0, 8)}`, payload);
409
701
  }
410
702
  catch (err) {
411
703
  return text(`❌ Update failed: ${err instanceof Error ? err.message : err}`);
412
704
  }
413
705
  },
414
706
  }, { optional: true });
707
+ // --- orgx_reassign_stream ---
708
+ registerMcpTool({
709
+ name: "orgx_reassign_stream",
710
+ description: "Reassign a workstream's stream ownership/agents and return reassignment scheduling details.",
711
+ parameters: {
712
+ type: "object",
713
+ properties: {
714
+ workstream_id: {
715
+ type: "string",
716
+ description: "Workstream UUID to reassign",
717
+ minLength: 1,
718
+ },
719
+ initiative_id: {
720
+ type: "string",
721
+ description: "Parent initiative UUID",
722
+ minLength: 1,
723
+ },
724
+ status: {
725
+ type: "string",
726
+ description: "Optional workstream status override (active, in_progress, pending, etc.)",
727
+ minLength: 1,
728
+ },
729
+ domain: {
730
+ type: "string",
731
+ description: "Optional target domain for the reassigned stream",
732
+ minLength: 1,
733
+ },
734
+ role: {
735
+ type: "string",
736
+ description: "Optional role hint for assignment routing",
737
+ minLength: 1,
738
+ },
739
+ assigned_agent_ids: {
740
+ type: "array",
741
+ items: { type: "string", minLength: 1 },
742
+ description: "Optional assigned agent IDs",
743
+ },
744
+ assignedAgentIds: {
745
+ type: "array",
746
+ items: { type: "string", minLength: 1 },
747
+ description: "Alias for assigned_agent_ids",
748
+ },
749
+ assigned_agent_names: {
750
+ type: "array",
751
+ items: { type: "string", minLength: 1 },
752
+ description: "Optional assigned agent display names",
753
+ },
754
+ assignedAgentNames: {
755
+ type: "array",
756
+ items: { type: "string", minLength: 1 },
757
+ description: "Alias for assigned_agent_names",
758
+ },
759
+ assigned_agents: {
760
+ type: "array",
761
+ description: "Optional structured assigned agent list",
762
+ items: {
763
+ type: "object",
764
+ properties: {
765
+ id: { type: "string" },
766
+ name: { type: "string" },
767
+ domain: { type: "string" },
768
+ role: { type: "string" },
769
+ },
770
+ additionalProperties: false,
771
+ },
772
+ },
773
+ },
774
+ required: ["workstream_id", "initiative_id"],
775
+ additionalProperties: false,
776
+ },
777
+ async execute(_callId, params = {}) {
778
+ try {
779
+ const workstreamId = pickNonEmptyString(params.workstream_id);
780
+ const initiativeId = pickNonEmptyString(params.initiative_id);
781
+ if (!workstreamId || !initiativeId) {
782
+ return text("❌ workstream_id and initiative_id are required.");
783
+ }
784
+ if (!isUuid(workstreamId)) {
785
+ return text("❌ workstream_id must be a valid UUID.");
786
+ }
787
+ if (!isUuid(initiativeId)) {
788
+ return text("❌ initiative_id must be a valid UUID.");
789
+ }
790
+ const normalizeOptionalString = (value, field) => {
791
+ if (value === undefined)
792
+ return { ok: true, value: undefined };
793
+ if (typeof value !== "string") {
794
+ return { ok: false, error: `❌ ${field} must be a string when provided.` };
795
+ }
796
+ const trimmed = value.trim();
797
+ return { ok: true, value: trimmed || undefined };
798
+ };
799
+ const normalizeStringArray = (value, field) => {
800
+ if (value === undefined)
801
+ return { ok: true, value: undefined };
802
+ if (!Array.isArray(value)) {
803
+ return { ok: false, error: `❌ ${field} must be an array of strings.` };
804
+ }
805
+ const normalized = [];
806
+ for (const entry of value) {
807
+ if (typeof entry !== "string" || !entry.trim()) {
808
+ return { ok: false, error: `❌ ${field} entries must be non-empty strings.` };
809
+ }
810
+ normalized.push(entry.trim());
811
+ }
812
+ return { ok: true, value: normalized };
813
+ };
814
+ const normalizeAssignedAgents = (value) => {
815
+ if (value === undefined)
816
+ return { ok: true, value: undefined };
817
+ if (!Array.isArray(value)) {
818
+ return { ok: false, error: "❌ assigned_agents must be an array when provided." };
819
+ }
820
+ const normalized = [];
821
+ for (const entry of value) {
822
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
823
+ return {
824
+ ok: false,
825
+ error: "❌ assigned_agents entries must be objects with id, name, domain, or role.",
826
+ };
827
+ }
828
+ const candidate = entry;
829
+ const normalizedEntry = {};
830
+ for (const key of ["id", "name", "domain", "role"]) {
831
+ if (!(key in candidate) || candidate[key] === undefined || candidate[key] === null)
832
+ continue;
833
+ const valueAtKey = candidate[key];
834
+ if (typeof valueAtKey !== "string" || !valueAtKey.trim()) {
835
+ return {
836
+ ok: false,
837
+ error: `❌ assigned_agents.${key} must be a non-empty string when provided.`,
838
+ };
839
+ }
840
+ normalizedEntry[key] = valueAtKey.trim();
841
+ }
842
+ if (normalizedEntry.id === undefined &&
843
+ normalizedEntry.name === undefined &&
844
+ normalizedEntry.domain === undefined &&
845
+ normalizedEntry.role === undefined) {
846
+ return {
847
+ ok: false,
848
+ error: "❌ assigned_agents entries must include at least one of id, name, domain, or role.",
849
+ };
850
+ }
851
+ normalized.push(normalizedEntry);
852
+ }
853
+ return { ok: true, value: normalized };
854
+ };
855
+ const arraysEqual = (left, right) => left.length === right.length && left.every((value, idx) => value === right[idx]);
856
+ const statusInput = normalizeOptionalString(params.status, "status");
857
+ if (!statusInput.ok)
858
+ return text(statusInput.error);
859
+ const domainInput = normalizeOptionalString(params.domain, "domain");
860
+ if (!domainInput.ok)
861
+ return text(domainInput.error);
862
+ const roleInput = normalizeOptionalString(params.role, "role");
863
+ if (!roleInput.ok)
864
+ return text(roleInput.error);
865
+ const assignedAgentIdsSnake = normalizeStringArray(params.assigned_agent_ids, "assigned_agent_ids");
866
+ if (!assignedAgentIdsSnake.ok)
867
+ return text(assignedAgentIdsSnake.error);
868
+ const assignedAgentIdsCamel = normalizeStringArray(params.assignedAgentIds, "assignedAgentIds");
869
+ if (!assignedAgentIdsCamel.ok)
870
+ return text(assignedAgentIdsCamel.error);
871
+ const assignedAgentNamesSnake = normalizeStringArray(params.assigned_agent_names, "assigned_agent_names");
872
+ if (!assignedAgentNamesSnake.ok)
873
+ return text(assignedAgentNamesSnake.error);
874
+ const assignedAgentNamesCamel = normalizeStringArray(params.assignedAgentNames, "assignedAgentNames");
875
+ if (!assignedAgentNamesCamel.ok)
876
+ return text(assignedAgentNamesCamel.error);
877
+ const assignedAgentsInput = normalizeAssignedAgents(params.assigned_agents);
878
+ if (!assignedAgentsInput.ok)
879
+ return text(assignedAgentsInput.error);
880
+ if (assignedAgentIdsSnake.value &&
881
+ assignedAgentIdsCamel.value &&
882
+ !arraysEqual(assignedAgentIdsSnake.value, assignedAgentIdsCamel.value)) {
883
+ return text("❌ assigned_agent_ids and assignedAgentIds must match when both are provided.");
884
+ }
885
+ if (assignedAgentNamesSnake.value &&
886
+ assignedAgentNamesCamel.value &&
887
+ !arraysEqual(assignedAgentNamesSnake.value, assignedAgentNamesCamel.value)) {
888
+ return text("❌ assigned_agent_names and assignedAgentNames must match when both are provided.");
889
+ }
890
+ const normalizedAssignedAgentIds = assignedAgentIdsSnake.value ?? assignedAgentIdsCamel.value;
891
+ const normalizedAssignedAgentNames = assignedAgentNamesSnake.value ?? assignedAgentNamesCamel.value;
892
+ const hasReassignmentMutation = domainInput.value !== undefined ||
893
+ roleInput.value !== undefined ||
894
+ assignedAgentsInput.value !== undefined ||
895
+ normalizedAssignedAgentIds !== undefined ||
896
+ normalizedAssignedAgentNames !== undefined;
897
+ if (!hasReassignmentMutation) {
898
+ return text("❌ Include at least one reassignment field: domain, role, assigned_agents, assigned_agent_ids, or assigned_agent_names.");
899
+ }
900
+ const updates = {
901
+ initiative_id: initiativeId,
902
+ };
903
+ if (statusInput.value !== undefined)
904
+ updates.status = statusInput.value;
905
+ if (domainInput.value !== undefined)
906
+ updates.domain = domainInput.value;
907
+ if (roleInput.value !== undefined)
908
+ updates.role = roleInput.value;
909
+ if (assignedAgentsInput.value !== undefined)
910
+ updates.assigned_agents = assignedAgentsInput.value;
911
+ if (normalizedAssignedAgentIds !== undefined) {
912
+ updates.assigned_agent_ids = normalizedAssignedAgentIds;
913
+ }
914
+ if (normalizedAssignedAgentNames !== undefined) {
915
+ updates.assigned_agent_names = normalizedAssignedAgentNames;
916
+ }
917
+ let beforeDomainFromSnapshot = null;
918
+ if (typeof client.listEntities === "function") {
919
+ try {
920
+ const currentWorkstreams = await client.listEntities("workstream", {
921
+ initiative_id: initiativeId,
922
+ limit: 200,
923
+ });
924
+ const rows = currentWorkstreams &&
925
+ typeof currentWorkstreams === "object" &&
926
+ Array.isArray(currentWorkstreams.data)
927
+ ? currentWorkstreams.data
928
+ : [];
929
+ for (const row of rows) {
930
+ if (!row || typeof row !== "object" || Array.isArray(row))
931
+ continue;
932
+ const record = row;
933
+ const id = pickNonEmptyString(record.id);
934
+ if (id !== workstreamId)
935
+ continue;
936
+ beforeDomainFromSnapshot = pickNonEmptyString(record.domain) ?? null;
937
+ break;
938
+ }
939
+ }
940
+ catch {
941
+ // Best effort snapshot for before/after confirmation.
942
+ }
943
+ }
944
+ const result = await client.updateEntityDetailed("workstream", workstreamId, updates);
945
+ const entityRecord = result.entity && typeof result.entity === "object" && !Array.isArray(result.entity)
946
+ ? result.entity
947
+ : null;
948
+ const reassignmentRecord = result.reassignment &&
949
+ typeof result.reassignment === "object" &&
950
+ !Array.isArray(result.reassignment)
951
+ ? result.reassignment
952
+ : null;
953
+ const beforeDomain = pickNonEmptyString(beforeDomainFromSnapshot, reassignmentRecord?.before_domain, reassignmentRecord?.previous_domain, reassignmentRecord?.from_domain, entityRecord?.before_domain, entityRecord?.previous_domain, entityRecord?.from_domain) ?? null;
954
+ const afterDomain = pickNonEmptyString(reassignmentRecord?.after_domain, reassignmentRecord?.new_domain, reassignmentRecord?.to_domain, entityRecord?.after_domain, entityRecord?.new_domain, entityRecord?.to_domain, entityRecord?.domain, domainInput.value) ?? null;
955
+ return json(`✅ Reassigned workstream ${workstreamId.slice(0, 8)}`, {
956
+ workstream_id: workstreamId,
957
+ before_domain: beforeDomain,
958
+ after_domain: afterDomain,
959
+ entity: result.entity,
960
+ reassignment: result.reassignment ?? null,
961
+ initiative_reassignment: result.initiative_reassignment ?? null,
962
+ });
963
+ }
964
+ catch (err) {
965
+ return text(`❌ Stream reassignment failed: ${err instanceof Error ? err.message : err}`);
966
+ }
967
+ },
968
+ }, { optional: true });
969
+ // --- orgx_reassign_streams ---
970
+ registerMcpTool({
971
+ name: "orgx_reassign_streams",
972
+ description: "Convenience batch reassignment tool. Takes initiative_id and a workstream-to-domain mapping, then updates all listed workstreams.",
973
+ parameters: {
974
+ type: "object",
975
+ properties: {
976
+ initiative_id: {
977
+ type: "string",
978
+ description: "Parent initiative UUID for all workstream updates",
979
+ minLength: 1,
980
+ },
981
+ workstream_domains: {
982
+ type: "object",
983
+ description: "Mapping of workstream UUID -> target domain",
984
+ additionalProperties: {
985
+ type: "string",
986
+ minLength: 1,
987
+ },
988
+ },
989
+ mapping: {
990
+ type: "object",
991
+ description: "Alias for workstream_domains: mapping of workstream UUID -> target domain",
992
+ additionalProperties: {
993
+ type: "string",
994
+ minLength: 1,
995
+ },
996
+ },
997
+ mappings: {
998
+ type: "array",
999
+ description: "Optional array mapping for advanced routing fields (workstream_id, domain, role, status).",
1000
+ items: {
1001
+ type: "object",
1002
+ properties: {
1003
+ workstream_id: {
1004
+ type: "string",
1005
+ description: "Workstream UUID to reassign",
1006
+ minLength: 1,
1007
+ },
1008
+ domain: {
1009
+ type: "string",
1010
+ description: "Target domain for reassignment",
1011
+ minLength: 1,
1012
+ },
1013
+ role: {
1014
+ type: "string",
1015
+ description: "Optional role hint for assignment routing",
1016
+ minLength: 1,
1017
+ },
1018
+ status: {
1019
+ type: "string",
1020
+ description: "Optional workstream status override (active, in_progress, pending, etc.)",
1021
+ minLength: 1,
1022
+ },
1023
+ },
1024
+ required: ["workstream_id", "domain"],
1025
+ additionalProperties: false,
1026
+ },
1027
+ },
1028
+ status: {
1029
+ type: "string",
1030
+ description: "Optional workstream status override applied to each reassigned stream",
1031
+ minLength: 1,
1032
+ },
1033
+ },
1034
+ required: ["initiative_id"],
1035
+ additionalProperties: false,
1036
+ },
1037
+ async execute(_callId, params = {}) {
1038
+ try {
1039
+ const initiativeId = pickNonEmptyString(params.initiative_id);
1040
+ if (!initiativeId) {
1041
+ return text("❌ initiative_id is required.");
1042
+ }
1043
+ if (!isUuid(initiativeId)) {
1044
+ return text("❌ initiative_id must be a valid UUID.");
1045
+ }
1046
+ const statusInput = params.status;
1047
+ if (statusInput !== undefined && typeof statusInput !== "string") {
1048
+ return text("❌ status must be a string when provided.");
1049
+ }
1050
+ const normalizedStatus = typeof statusInput === "string" && statusInput.trim().length > 0
1051
+ ? statusInput.trim()
1052
+ : undefined;
1053
+ const normalizedMappings = [];
1054
+ const seenWorkstreams = new Set();
1055
+ const addMapping = (workstreamIdRaw, domainRaw, options) => {
1056
+ const workstreamId = workstreamIdRaw.trim();
1057
+ if (!workstreamId || !isUuid(workstreamId)) {
1058
+ return `❌ workstream identifier '${workstreamIdRaw}' must be a valid UUID.`;
1059
+ }
1060
+ const domain = domainRaw.trim();
1061
+ if (!domain) {
1062
+ return `❌ workstream '${workstreamIdRaw}' must map to a non-empty domain string.`;
1063
+ }
1064
+ if (seenWorkstreams.has(workstreamId)) {
1065
+ return "❌ Duplicate workstream mapping detected.";
1066
+ }
1067
+ seenWorkstreams.add(workstreamId);
1068
+ normalizedMappings.push({
1069
+ workstreamId,
1070
+ domain,
1071
+ ...(options?.role?.trim() ? { role: options.role.trim() } : {}),
1072
+ ...(options?.status?.trim() ? { status: options.status.trim() } : {}),
1073
+ });
1074
+ return null;
1075
+ };
1076
+ if (params.workstream_domains !== undefined) {
1077
+ if (!params.workstream_domains ||
1078
+ typeof params.workstream_domains !== "object" ||
1079
+ Array.isArray(params.workstream_domains)) {
1080
+ return text("❌ workstream_domains must be an object mapping workstream_id to domain.");
1081
+ }
1082
+ const mappingEntries = Object.entries(params.workstream_domains);
1083
+ for (const [workstreamId, domainValue] of mappingEntries) {
1084
+ if (typeof domainValue !== "string") {
1085
+ return text(`❌ workstream_domains['${workstreamId}'] must be a non-empty domain string.`);
1086
+ }
1087
+ const error = addMapping(workstreamId, domainValue);
1088
+ if (error)
1089
+ return text(error);
1090
+ }
1091
+ }
1092
+ if (params.mapping !== undefined) {
1093
+ if (!params.mapping ||
1094
+ typeof params.mapping !== "object" ||
1095
+ Array.isArray(params.mapping)) {
1096
+ return text("❌ mapping must be an object of workstream_id -> domain.");
1097
+ }
1098
+ const mappingEntries = Object.entries(params.mapping);
1099
+ for (const [workstreamId, domainValue] of mappingEntries) {
1100
+ if (typeof domainValue !== "string") {
1101
+ return text(`❌ mapping['${workstreamId}'] must be a non-empty domain string.`);
1102
+ }
1103
+ const error = addMapping(workstreamId, domainValue);
1104
+ if (error)
1105
+ return text(error);
1106
+ }
1107
+ }
1108
+ if (params.mappings !== undefined) {
1109
+ if (!Array.isArray(params.mappings)) {
1110
+ return text("❌ mappings must be an array when provided.");
1111
+ }
1112
+ for (const entry of params.mappings) {
1113
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
1114
+ return text("❌ mappings entries must be objects with workstream_id and domain.");
1115
+ }
1116
+ const candidate = entry;
1117
+ const workstreamId = pickNonEmptyString(candidate.workstream_id);
1118
+ const domain = pickNonEmptyString(candidate.domain);
1119
+ const role = pickNonEmptyString(candidate.role);
1120
+ const status = pickNonEmptyString(candidate.status);
1121
+ if (!workstreamId || !domain) {
1122
+ return text("❌ Each mappings entry requires workstream_id and domain.");
1123
+ }
1124
+ const error = addMapping(workstreamId, domain, { role, status });
1125
+ if (error)
1126
+ return text(error);
1127
+ }
1128
+ }
1129
+ if (normalizedMappings.length === 0) {
1130
+ return text("❌ Include workstream_domains, mapping, or mappings with at least one workstream reassignment.");
1131
+ }
1132
+ const updates = [];
1133
+ const domainBeforeByWorkstreamId = new Map();
1134
+ if (typeof client.listEntities === "function") {
1135
+ try {
1136
+ const currentWorkstreams = await client.listEntities("workstream", {
1137
+ initiative_id: initiativeId,
1138
+ limit: 200,
1139
+ });
1140
+ const rows = currentWorkstreams &&
1141
+ typeof currentWorkstreams === "object" &&
1142
+ Array.isArray(currentWorkstreams.data)
1143
+ ? currentWorkstreams.data
1144
+ : [];
1145
+ for (const row of rows) {
1146
+ if (!row || typeof row !== "object" || Array.isArray(row))
1147
+ continue;
1148
+ const record = row;
1149
+ const id = pickNonEmptyString(record.id);
1150
+ if (!id)
1151
+ continue;
1152
+ domainBeforeByWorkstreamId.set(id, pickNonEmptyString(record.domain) ?? null);
1153
+ }
1154
+ }
1155
+ catch {
1156
+ // Best effort snapshot for before/after confirmation.
1157
+ }
1158
+ }
1159
+ for (const mapping of normalizedMappings) {
1160
+ const workstreamId = mapping.workstreamId;
1161
+ const domain = mapping.domain;
1162
+ const beforeDomain = domainBeforeByWorkstreamId.get(workstreamId) ?? null;
1163
+ const payload = {
1164
+ initiative_id: initiativeId,
1165
+ domain,
1166
+ };
1167
+ if (mapping.role !== undefined)
1168
+ payload.role = mapping.role;
1169
+ if (mapping.status !== undefined) {
1170
+ payload.status = mapping.status;
1171
+ }
1172
+ else if (normalizedStatus !== undefined) {
1173
+ payload.status = normalizedStatus;
1174
+ }
1175
+ try {
1176
+ const result = await client.updateEntityDetailed("workstream", workstreamId, payload);
1177
+ const entityRecord = result.entity && typeof result.entity === "object" && !Array.isArray(result.entity)
1178
+ ? result.entity
1179
+ : null;
1180
+ const reassignmentRecord = result.reassignment &&
1181
+ typeof result.reassignment === "object" &&
1182
+ !Array.isArray(result.reassignment)
1183
+ ? result.reassignment
1184
+ : null;
1185
+ const resolvedBeforeDomain = pickNonEmptyString(beforeDomain, reassignmentRecord?.before_domain, reassignmentRecord?.previous_domain, reassignmentRecord?.from_domain, entityRecord?.before_domain, entityRecord?.previous_domain, entityRecord?.from_domain) ?? null;
1186
+ const afterDomain = pickNonEmptyString(reassignmentRecord?.after_domain, reassignmentRecord?.new_domain, reassignmentRecord?.to_domain, entityRecord?.after_domain, entityRecord?.new_domain, entityRecord?.to_domain, entityRecord?.domain, domain) ?? null;
1187
+ updates.push({
1188
+ workstream_id: workstreamId,
1189
+ domain,
1190
+ before_domain: resolvedBeforeDomain,
1191
+ after_domain: afterDomain,
1192
+ ok: true,
1193
+ entity: result.entity,
1194
+ reassignment: result.reassignment ?? null,
1195
+ initiative_reassignment: result.initiative_reassignment ?? null,
1196
+ });
1197
+ }
1198
+ catch (updateErr) {
1199
+ updates.push({
1200
+ workstream_id: workstreamId,
1201
+ domain,
1202
+ before_domain: beforeDomain,
1203
+ after_domain: null,
1204
+ ok: false,
1205
+ error: updateErr instanceof Error ? updateErr.message : String(updateErr),
1206
+ });
1207
+ }
1208
+ }
1209
+ const succeeded = updates.filter((entry) => entry.ok).length;
1210
+ const failed = updates.length - succeeded;
1211
+ return json(`✅ Reassigned ${succeeded}/${updates.length} workstream(s)`, {
1212
+ initiative_id: initiativeId,
1213
+ updated_count: succeeded,
1214
+ failed_count: failed,
1215
+ updates,
1216
+ });
1217
+ }
1218
+ catch (err) {
1219
+ return text(`❌ Batch stream reassignment failed: ${err instanceof Error ? err.message : err}`);
1220
+ }
1221
+ },
1222
+ }, { optional: true });
415
1223
  // --- orgx_list_entities ---
416
1224
  registerMcpTool({
417
1225
  name: "orgx_list_entities",
@@ -432,6 +1240,22 @@ export function registerCoreTools(deps) {
432
1240
  description: "Max results (default 20)",
433
1241
  default: 20,
434
1242
  },
1243
+ initiative_id: {
1244
+ type: "string",
1245
+ description: "Filter by initiative ID",
1246
+ },
1247
+ project_id: {
1248
+ type: "string",
1249
+ description: "Legacy project/workspace scope filter",
1250
+ },
1251
+ workspace_id: {
1252
+ type: "string",
1253
+ description: "Workspace ID (canonical scope)",
1254
+ },
1255
+ command_center_id: {
1256
+ type: "string",
1257
+ description: "Deprecated alias for workspace_id",
1258
+ },
435
1259
  },
436
1260
  required: ["type"],
437
1261
  },
@@ -448,6 +1272,285 @@ export function registerCoreTools(deps) {
448
1272
  }
449
1273
  },
450
1274
  }, { optional: true });
1275
+ function evalGateErrorForPackStatus(status) {
1276
+ if (typeof status !== "string")
1277
+ return null;
1278
+ const normalized = status.trim().toLowerCase();
1279
+ if (!normalized || normalized === "approved")
1280
+ return null;
1281
+ return `SkillPack eval gate blocked activation: status '${status}' is not approved`;
1282
+ }
1283
+ function manifestValidationErrorForPackManifest(manifest) {
1284
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
1285
+ return null;
1286
+ }
1287
+ const validation = validateOpenClawSkillPackManifest(manifest);
1288
+ if (validation.errors.length === 0)
1289
+ return null;
1290
+ return `SkillPack manifest validation errors: ${validation.errors.join("; ")}`;
1291
+ }
1292
+ function computeSkillPackUpdateAvailable(state) {
1293
+ return Boolean(state.remote?.checksum &&
1294
+ state.pack?.checksum &&
1295
+ state.remote.checksum !== state.pack.checksum);
1296
+ }
1297
+ function resolveAgentConfigId(input) {
1298
+ const value = typeof input === "string" ? input.trim() : "";
1299
+ if (!value || value === "default") {
1300
+ return "default";
1301
+ }
1302
+ return null;
1303
+ }
1304
+ function resolveAgentConfigTemplateId(input) {
1305
+ const value = typeof input === "string" ? input.trim().toLowerCase() : "";
1306
+ if (value === "startup-speed") {
1307
+ return "startup-speed";
1308
+ }
1309
+ return null;
1310
+ }
1311
+ function buildAgentConfigSnapshot(state) {
1312
+ return {
1313
+ config_id: "default",
1314
+ config_type: "skill_pack_policy",
1315
+ policy: state.policy,
1316
+ pack: state.pack,
1317
+ remote: state.remote,
1318
+ update_available: computeSkillPackUpdateAvailable(state),
1319
+ last_checked_at: state.lastCheckedAt,
1320
+ last_error: state.lastError,
1321
+ updated_at: state.updatedAt,
1322
+ };
1323
+ }
1324
+ async function readAgentConfigState(input) {
1325
+ const state = readSkillPackState();
1326
+ if (!input?.refreshRemote) {
1327
+ return state;
1328
+ }
1329
+ const getSkillPack = client.getSkillPack;
1330
+ if (typeof getSkillPack !== "function") {
1331
+ return state;
1332
+ }
1333
+ try {
1334
+ const response = await getSkillPack({
1335
+ name: state.pack?.name ?? state.remote?.name ?? "orgx-agent-suite",
1336
+ ifNoneMatch: null,
1337
+ });
1338
+ const checkedAt = new Date().toISOString();
1339
+ if (!response.ok) {
1340
+ return {
1341
+ ...state,
1342
+ updatedAt: checkedAt,
1343
+ lastCheckedAt: checkedAt,
1344
+ lastError: response.error ?? state.lastError,
1345
+ };
1346
+ }
1347
+ if (response.notModified || !response.pack) {
1348
+ return {
1349
+ ...state,
1350
+ updatedAt: checkedAt,
1351
+ lastCheckedAt: checkedAt,
1352
+ lastError: null,
1353
+ etag: response.etag ?? state.etag,
1354
+ };
1355
+ }
1356
+ const remote = {
1357
+ name: response.pack.name,
1358
+ version: response.pack.version,
1359
+ checksum: response.pack.checksum,
1360
+ updated_at: response.pack.updated_at ?? null,
1361
+ };
1362
+ const evalGateError = evalGateErrorForPackStatus(response.pack.status);
1363
+ const manifestValidationError = manifestValidationErrorForPackManifest(response.pack.manifest);
1364
+ const activationValidationError = evalGateError ?? manifestValidationError;
1365
+ const shouldApplyPack = !activationValidationError &&
1366
+ (!state.policy.pinnedChecksum || state.policy.pinnedChecksum === response.pack.checksum);
1367
+ return {
1368
+ ...state,
1369
+ updatedAt: checkedAt,
1370
+ lastCheckedAt: checkedAt,
1371
+ lastError: activationValidationError,
1372
+ etag: response.etag ?? state.etag,
1373
+ remote,
1374
+ pack: shouldApplyPack ? remote : state.pack,
1375
+ };
1376
+ }
1377
+ catch {
1378
+ return state;
1379
+ }
1380
+ }
1381
+ // --- list_agent_configs ---
1382
+ registerMcpTool({
1383
+ name: "list_agent_configs",
1384
+ description: "List available agent behavior configs managed by this plugin.",
1385
+ parameters: {
1386
+ type: "object",
1387
+ properties: {
1388
+ refresh_remote: {
1389
+ type: "boolean",
1390
+ description: "When true, re-fetch behavior config policy from OrgX before returning data.",
1391
+ },
1392
+ },
1393
+ additionalProperties: false,
1394
+ },
1395
+ async execute(_callId, params = {}) {
1396
+ try {
1397
+ const state = await readAgentConfigState({
1398
+ refreshRemote: params.refresh_remote === true,
1399
+ });
1400
+ return json("Agent configs:", {
1401
+ total: 1,
1402
+ configs: [buildAgentConfigSnapshot(state)],
1403
+ });
1404
+ }
1405
+ catch (err) {
1406
+ return text(`❌ Failed to list agent configs: ${err instanceof Error ? err.message : err}`);
1407
+ }
1408
+ },
1409
+ }, { optional: true });
1410
+ // --- get_agent_config ---
1411
+ registerMcpTool({
1412
+ name: "get_agent_config",
1413
+ description: "Get a single agent behavior config by id (default only).",
1414
+ parameters: {
1415
+ type: "object",
1416
+ properties: {
1417
+ config_id: {
1418
+ type: "string",
1419
+ description: "Agent config identifier. Use 'default'.",
1420
+ },
1421
+ refresh_remote: {
1422
+ type: "boolean",
1423
+ description: "When true, re-fetch behavior config policy from OrgX before returning data.",
1424
+ },
1425
+ },
1426
+ additionalProperties: false,
1427
+ },
1428
+ async execute(_callId, params = {}) {
1429
+ const configId = resolveAgentConfigId(params.config_id);
1430
+ if (!configId) {
1431
+ return text("❌ config_id must be 'default'.");
1432
+ }
1433
+ try {
1434
+ const state = await readAgentConfigState({
1435
+ refreshRemote: params.refresh_remote === true,
1436
+ });
1437
+ return json("Agent config:", buildAgentConfigSnapshot(state));
1438
+ }
1439
+ catch (err) {
1440
+ return text(`❌ Failed to read agent config: ${err instanceof Error ? err.message : err}`);
1441
+ }
1442
+ },
1443
+ }, { optional: true });
1444
+ // --- update_agent_config ---
1445
+ registerMcpTool({
1446
+ name: "update_agent_config",
1447
+ description: "Update plugin-managed agent behavior config policy (default config only).",
1448
+ parameters: {
1449
+ type: "object",
1450
+ properties: {
1451
+ config_id: {
1452
+ type: "string",
1453
+ description: "Agent config identifier. Use 'default'.",
1454
+ },
1455
+ frozen: {
1456
+ type: "boolean",
1457
+ description: "Freeze remote skill-pack refresh when true.",
1458
+ },
1459
+ pinned_checksum: {
1460
+ type: ["string", "null"],
1461
+ description: "Pin behavior policy to this exact checksum.",
1462
+ },
1463
+ pin_to_current: {
1464
+ type: "boolean",
1465
+ description: "Pin to current local/remote checksum.",
1466
+ },
1467
+ clear_pin: {
1468
+ type: "boolean",
1469
+ description: "Clear any pinned checksum.",
1470
+ },
1471
+ action: {
1472
+ type: "string",
1473
+ description: "Use 'rollback' to revert to the previous config version.",
1474
+ },
1475
+ rollback_to_audit_id: {
1476
+ type: "string",
1477
+ description: "Optional audit entry id to rollback to. When omitted with action='rollback', uses the most recent audit entry.",
1478
+ },
1479
+ template_id: {
1480
+ type: "string",
1481
+ description: "Optional preset template id. Currently supports 'startup-speed'.",
1482
+ },
1483
+ },
1484
+ additionalProperties: false,
1485
+ },
1486
+ async execute(_callId, params = {}) {
1487
+ const configId = resolveAgentConfigId(params.config_id);
1488
+ if (!configId) {
1489
+ return text("❌ config_id must be 'default'.");
1490
+ }
1491
+ const templateId = resolveAgentConfigTemplateId(params.template_id);
1492
+ if (typeof params.template_id === "string" && !templateId) {
1493
+ return text("❌ template_id must be 'startup-speed' when provided.");
1494
+ }
1495
+ let hasFrozen = typeof params.frozen === "boolean";
1496
+ let hasPinnedChecksum = typeof params.pinned_checksum === "string" || params.pinned_checksum === null;
1497
+ let hasPinToCurrent = params.pin_to_current === true;
1498
+ let hasClearPin = params.clear_pin === true;
1499
+ const action = typeof params.action === "string" ? params.action.trim().toLowerCase() : "";
1500
+ const rollbackToAuditId = typeof params.rollback_to_audit_id === "string" && params.rollback_to_audit_id.trim()
1501
+ ? params.rollback_to_audit_id.trim()
1502
+ : undefined;
1503
+ const isRollback = action === "rollback" || typeof rollbackToAuditId === "string";
1504
+ if (action.length > 0 && action !== "rollback") {
1505
+ return text("❌ action must be 'rollback' when provided.");
1506
+ }
1507
+ if (templateId === "startup-speed") {
1508
+ // Shared default config fans out to all OrgX agent domains.
1509
+ params.frozen = false;
1510
+ params.clear_pin = true;
1511
+ params.pin_to_current = false;
1512
+ params.pinned_checksum = undefined;
1513
+ hasFrozen = true;
1514
+ hasPinnedChecksum = false;
1515
+ hasPinToCurrent = false;
1516
+ hasClearPin = true;
1517
+ }
1518
+ if (isRollback) {
1519
+ if (hasFrozen || hasPinnedChecksum || hasPinToCurrent || hasClearPin || templateId) {
1520
+ return text("❌ Rollback requests cannot include update fields (frozen, pinned_checksum, pin_to_current, clear_pin, template_id).");
1521
+ }
1522
+ }
1523
+ else if (!hasFrozen && !hasPinnedChecksum && !hasPinToCurrent && !hasClearPin) {
1524
+ return text("❌ Include at least one mutable field: frozen, pinned_checksum, pin_to_current, clear_pin, or template_id.");
1525
+ }
1526
+ if (hasPinToCurrent && hasClearPin) {
1527
+ return text("❌ pin_to_current and clear_pin cannot both be true.");
1528
+ }
1529
+ if (typeof params.pinned_checksum === "string" && !params.pinned_checksum.trim()) {
1530
+ return text("❌ pinned_checksum must be a non-empty string when provided.");
1531
+ }
1532
+ try {
1533
+ const state = isRollback
1534
+ ? rollbackSkillPackPolicy({
1535
+ auditId: rollbackToAuditId,
1536
+ })
1537
+ : updateSkillPackPolicy({
1538
+ frozen: hasFrozen ? params.frozen : undefined,
1539
+ pinnedChecksum: typeof params.pinned_checksum === "string"
1540
+ ? params.pinned_checksum.trim()
1541
+ : params.pinned_checksum === null
1542
+ ? null
1543
+ : undefined,
1544
+ pinToCurrent: hasPinToCurrent,
1545
+ clearPin: hasClearPin,
1546
+ });
1547
+ return json(isRollback ? "Agent config rolled back:" : "Agent config updated:", buildAgentConfigSnapshot(state));
1548
+ }
1549
+ catch (err) {
1550
+ return text(`❌ Failed to ${isRollback ? "rollback" : "update"} agent config: ${err instanceof Error ? err.message : err}`);
1551
+ }
1552
+ },
1553
+ }, { optional: true });
451
1554
  function withProvenanceMetadata(metadata) {
452
1555
  const input = metadata ?? {};
453
1556
  const out = { ...input };
@@ -490,6 +1593,26 @@ export function registerCoreTools(deps) {
490
1593
  }
491
1594
  return out;
492
1595
  }
1596
+ function deriveAgentIdentity(payload) {
1597
+ const envAgentId = pickNonEmptyString(process.env.ORGX_AGENT_ID) ?? null;
1598
+ const envAgentName = pickNonEmptyString(process.env.ORGX_AGENT_NAME) ?? null;
1599
+ const payloadRecord = payload && typeof payload === "object" && !Array.isArray(payload)
1600
+ ? payload
1601
+ : null;
1602
+ const metadataRaw = payloadRecord?.metadata;
1603
+ const metadata = metadataRaw && typeof metadataRaw === "object" && !Array.isArray(metadataRaw)
1604
+ ? metadataRaw
1605
+ : {};
1606
+ const metadataAgentId = pickNonEmptyString(metadata.agent_id, metadata.agentId, metadata.executor_agent_id, metadata.executorAgentId, metadata.requested_by_agent_id, metadata.requestedByAgentId) ?? null;
1607
+ const metadataAgentName = pickNonEmptyString(metadata.agent_name, metadata.agentName, metadata.executor_agent_name, metadata.executorAgentName, metadata.requested_by_agent_name, metadata.requestedByAgentName) ?? null;
1608
+ return {
1609
+ agentId: envAgentId ?? metadataAgentId,
1610
+ agentName: envAgentName ?? metadataAgentName,
1611
+ };
1612
+ }
1613
+ function deriveCorrelationFromRun(runId) {
1614
+ return `openclaw_run_${runId.replace(/[^a-zA-Z0-9]/g, "").slice(0, 24)}`;
1615
+ }
493
1616
  async function emitActivityWithFallback(source, payload) {
494
1617
  if (!payload.message || payload.message.trim().length === 0) {
495
1618
  return text("❌ message is required");
@@ -500,6 +1623,20 @@ export function registerCoreTools(deps) {
500
1623
  }
501
1624
  const now = new Date().toISOString();
502
1625
  const id = `progress:${randomUUID().slice(0, 8)}`;
1626
+ const envWorkstreamId = pickNonEmptyString(process.env.ORGX_WORKSTREAM_ID);
1627
+ const envTaskId = pickNonEmptyString(process.env.ORGX_TASK_ID);
1628
+ const { agentId, agentName } = deriveAgentIdentity(payload);
1629
+ const canonicalMetadata = {
1630
+ initiative_id: context.value.initiativeId,
1631
+ run_id: context.value.runId ?? null,
1632
+ slice_run_id: context.value.runId ?? null,
1633
+ correlation_id: context.value.correlationId ?? null,
1634
+ source_client: context.value.sourceClient ?? null,
1635
+ ...(envWorkstreamId ? { workstream_id: envWorkstreamId } : {}),
1636
+ ...(envTaskId ? { task_id: envTaskId } : {}),
1637
+ ...(agentId ? { agent_id: agentId } : {}),
1638
+ ...(agentName ? { agent_name: agentName } : {}),
1639
+ };
503
1640
  const normalizedPayload = {
504
1641
  initiative_id: context.value.initiativeId,
505
1642
  run_id: context.value.runId,
@@ -511,6 +1648,7 @@ export function registerCoreTools(deps) {
511
1648
  level: payload.level ?? "info",
512
1649
  next_step: payload.next_step,
513
1650
  metadata: withProvenanceMetadata({
1651
+ ...canonicalMetadata,
514
1652
  ...(payload.metadata ?? {}),
515
1653
  source,
516
1654
  }),
@@ -520,12 +1658,12 @@ export function registerCoreTools(deps) {
520
1658
  type: "delegation",
521
1659
  title: payload.message,
522
1660
  description: payload.next_step ?? null,
523
- agentId: null,
524
- agentName: null,
525
- requesterAgentId: null,
526
- requesterAgentName: null,
527
- executorAgentId: null,
528
- executorAgentName: null,
1661
+ agentId,
1662
+ agentName,
1663
+ requesterAgentId: agentId,
1664
+ requesterAgentName: agentName,
1665
+ executorAgentId: agentId,
1666
+ executorAgentName: agentName,
529
1667
  runId: context.value.runId ?? null,
530
1668
  initiativeId: context.value.initiativeId,
531
1669
  timestamp: now,
@@ -537,7 +1675,29 @@ export function registerCoreTools(deps) {
537
1675
  const result = await client.emitActivity(normalizedPayload);
538
1676
  return text(`Activity emitted: ${payload.message} [${normalizedPayload.phase}${payload.progress_pct != null ? ` ${payload.progress_pct}%` : ""}] (run ${result.run_id.slice(0, 8)}...)`);
539
1677
  }
540
- catch {
1678
+ catch (err) {
1679
+ const errMsg = err instanceof Error ? err.message : String(err);
1680
+ if (normalizedPayload.run_id &&
1681
+ /^404\b/.test(errMsg) &&
1682
+ /\brun\b/i.test(errMsg) &&
1683
+ /not found/i.test(errMsg)) {
1684
+ try {
1685
+ const retryCorrelation = normalizedPayload.correlation_id ?? deriveCorrelationFromRun(normalizedPayload.run_id);
1686
+ const retryResult = await client.emitActivity({
1687
+ ...normalizedPayload,
1688
+ run_id: undefined,
1689
+ correlation_id: retryCorrelation,
1690
+ metadata: withProvenanceMetadata({
1691
+ ...(normalizedPayload.metadata ?? {}),
1692
+ replay_run_id_as_correlation: true,
1693
+ }),
1694
+ });
1695
+ return text(`Activity emitted: ${payload.message} [${normalizedPayload.phase}${payload.progress_pct != null ? ` ${payload.progress_pct}%` : ""}] (run ${retryResult.run_id.slice(0, 8)}...)`);
1696
+ }
1697
+ catch {
1698
+ // Fall through to local outbox buffering.
1699
+ }
1700
+ }
541
1701
  await appendToOutbox("progress", {
542
1702
  id,
543
1703
  type: "progress",
@@ -568,17 +1728,18 @@ export function registerCoreTools(deps) {
568
1728
  };
569
1729
  const now = new Date().toISOString();
570
1730
  const id = `changeset:${randomUUID().slice(0, 8)}`;
1731
+ const { agentId, agentName } = deriveAgentIdentity();
571
1732
  const activityItem = {
572
1733
  id,
573
1734
  type: "milestone_completed",
574
1735
  title: "Changeset queued",
575
1736
  description: `${payload.operations.length} operation${payload.operations.length === 1 ? "" : "s"}`,
576
- agentId: null,
577
- agentName: null,
578
- requesterAgentId: null,
579
- requesterAgentName: null,
580
- executorAgentId: null,
581
- executorAgentName: null,
1737
+ agentId,
1738
+ agentName,
1739
+ requesterAgentId: agentId,
1740
+ requesterAgentName: agentName,
1741
+ executorAgentId: agentId,
1742
+ executorAgentName: agentName,
582
1743
  runId: context.value.runId ?? null,
583
1744
  initiativeId: context.value.initiativeId,
584
1745
  timestamp: now,
@@ -587,13 +1748,34 @@ export function registerCoreTools(deps) {
587
1748
  metadata: withProvenanceMetadata({
588
1749
  source,
589
1750
  idempotency_key: idempotencyKey,
1751
+ run_id: context.value.runId ?? null,
1752
+ correlation_id: context.value.correlationId ?? null,
1753
+ ...(agentId ? { agent_id: agentId } : {}),
1754
+ ...(agentName ? { agent_name: agentName } : {}),
590
1755
  }),
591
1756
  };
592
1757
  try {
593
1758
  const result = await client.applyChangeset(requestPayload);
594
1759
  return text(`Changeset ${result.replayed ? "replayed" : "applied"}: ${result.applied_count} op${result.applied_count === 1 ? "" : "s"} (run ${result.run_id.slice(0, 8)}...)`);
595
1760
  }
596
- catch {
1761
+ catch (err) {
1762
+ const errMsg = err instanceof Error ? err.message : String(err);
1763
+ if (requestPayload.run_id &&
1764
+ /^404\b/.test(errMsg) &&
1765
+ /\brun\b/i.test(errMsg) &&
1766
+ /not found/i.test(errMsg)) {
1767
+ try {
1768
+ const retryResult = await client.applyChangeset({
1769
+ ...requestPayload,
1770
+ run_id: undefined,
1771
+ correlation_id: requestPayload.correlation_id ?? deriveCorrelationFromRun(requestPayload.run_id),
1772
+ });
1773
+ return text(`Changeset ${retryResult.replayed ? "replayed" : "applied"}: ${retryResult.applied_count} op${retryResult.applied_count === 1 ? "" : "s"} (run ${retryResult.run_id.slice(0, 8)}...)`);
1774
+ }
1775
+ catch {
1776
+ // Fall through to local outbox buffering.
1777
+ }
1778
+ }
597
1779
  await appendToOutbox("decisions", {
598
1780
  id,
599
1781
  type: "changeset",
@@ -770,6 +1952,69 @@ export function registerCoreTools(deps) {
770
1952
  });
771
1953
  },
772
1954
  }, { optional: true });
1955
+ // --- update_stream_progress (legacy alias -> orgx_report_progress) ---
1956
+ registerMcpTool({
1957
+ name: "update_stream_progress",
1958
+ description: "Legacy alias for orgx_report_progress. Report progress at key milestones so the team can track your work.",
1959
+ parameters: {
1960
+ type: "object",
1961
+ properties: {
1962
+ initiative_id: {
1963
+ type: "string",
1964
+ description: "Initiative UUID (required unless ORGX_INITIATIVE_ID is set)",
1965
+ },
1966
+ run_id: {
1967
+ type: "string",
1968
+ description: "Optional run UUID",
1969
+ },
1970
+ correlation_id: {
1971
+ type: "string",
1972
+ description: "Required when run_id is omitted",
1973
+ },
1974
+ source_client: {
1975
+ type: "string",
1976
+ enum: ["openclaw", "codex", "claude-code", "api"],
1977
+ },
1978
+ summary: {
1979
+ type: "string",
1980
+ description: "What was accomplished (1-2 sentences, human-readable)",
1981
+ },
1982
+ phase: {
1983
+ type: "string",
1984
+ enum: ["researching", "implementing", "testing", "reviewing", "blocked"],
1985
+ description: "Current work phase",
1986
+ },
1987
+ progress_pct: {
1988
+ type: "number",
1989
+ description: "Progress percentage (0-100)",
1990
+ minimum: 0,
1991
+ maximum: 100,
1992
+ },
1993
+ next_step: {
1994
+ type: "string",
1995
+ description: "What you plan to do next",
1996
+ },
1997
+ },
1998
+ required: ["summary", "phase"],
1999
+ additionalProperties: false,
2000
+ },
2001
+ async execute(_callId, params = { summary: "", phase: "implementing" }) {
2002
+ return emitActivityWithFallback("update_stream_progress", {
2003
+ initiative_id: params.initiative_id,
2004
+ run_id: params.run_id,
2005
+ correlation_id: params.correlation_id,
2006
+ source_client: params.source_client,
2007
+ message: params.summary,
2008
+ phase: toReportingPhase(params.phase, params.progress_pct),
2009
+ progress_pct: params.progress_pct,
2010
+ next_step: params.next_step,
2011
+ level: params.phase === "blocked" ? "warn" : "info",
2012
+ metadata: {
2013
+ legacy_phase: params.phase,
2014
+ },
2015
+ });
2016
+ },
2017
+ }, { optional: true });
773
2018
  // --- orgx_request_decision (alias -> orgx_apply_changeset decision.create) ---
774
2019
  registerMcpTool({
775
2020
  name: "orgx_request_decision",
@@ -883,6 +2128,12 @@ export function registerCoreTools(deps) {
883
2128
  type: "string",
884
2129
  description: "Artifact type code (e.g., 'eng.diff_pack', 'pr', 'document'). Falls back to 'shared.project_handbook' if the type is not recognized by OrgX.",
885
2130
  },
2131
+ confidence_score: {
2132
+ type: "number",
2133
+ minimum: 0,
2134
+ maximum: 1,
2135
+ description: "Self-assessed confidence for this artifact in [0,1].",
2136
+ },
886
2137
  description: {
887
2138
  type: "string",
888
2139
  description: "What this artifact is and why it matters",
@@ -926,19 +2177,27 @@ export function registerCoreTools(deps) {
926
2177
  if (!params.url && !params.content) {
927
2178
  return text("❌ Cannot register artifact: provide at least one of url or content.");
928
2179
  }
2180
+ if (typeof params.confidence_score !== "undefined" &&
2181
+ (!Number.isFinite(params.confidence_score) ||
2182
+ params.confidence_score < 0 ||
2183
+ params.confidence_score > 1)) {
2184
+ return text("❌ Cannot register artifact: confidence_score must be a number between 0 and 1.");
2185
+ }
2186
+ const confidenceScore = typeof params.confidence_score === "number" ? params.confidence_score : null;
929
2187
  const baseUrl = client.getBaseUrl();
930
2188
  const artifactId = randomUUID();
2189
+ const { agentId, agentName } = deriveAgentIdentity(params);
931
2190
  const activityItem = {
932
2191
  id,
933
2192
  type: "artifact_created",
934
2193
  title: params.name,
935
2194
  description: params.description ?? null,
936
- agentId: null,
937
- agentName: null,
938
- requesterAgentId: null,
939
- requesterAgentName: null,
940
- executorAgentId: null,
941
- executorAgentName: null,
2195
+ agentId,
2196
+ agentName,
2197
+ requesterAgentId: agentId,
2198
+ requesterAgentName: agentName,
2199
+ executorAgentId: agentId,
2200
+ executorAgentName: agentName,
942
2201
  runId: null,
943
2202
  initiativeId: resolvedEntityType === "initiative" ? resolvedEntityId : null,
944
2203
  timestamp: now,
@@ -946,9 +2205,12 @@ export function registerCoreTools(deps) {
946
2205
  metadata: withProvenanceMetadata({
947
2206
  source: "orgx_register_artifact",
948
2207
  artifact_type: params.artifact_type,
2208
+ confidence_score: confidenceScore,
949
2209
  url: params.url,
950
2210
  entity_type: resolvedEntityType,
951
2211
  entity_id: resolvedEntityId,
2212
+ ...(agentId ? { agent_id: agentId } : {}),
2213
+ ...(agentName ? { agent_name: agentName } : {}),
952
2214
  }),
953
2215
  };
954
2216
  try {
@@ -958,6 +2220,7 @@ export function registerCoreTools(deps) {
958
2220
  entity_id: resolvedEntityId,
959
2221
  name: params.name,
960
2222
  artifact_type: params.artifact_type,
2223
+ confidence_score: confidenceScore,
961
2224
  description: params.description ?? null,
962
2225
  external_url: params.url ?? null,
963
2226
  preview_markdown: params.content ?? null,
@@ -965,6 +2228,7 @@ export function registerCoreTools(deps) {
965
2228
  metadata: {
966
2229
  source: "orgx_register_artifact",
967
2230
  artifact_id: artifactId,
2231
+ confidence_score: confidenceScore,
968
2232
  },
969
2233
  validate_persistence: true,
970
2234
  });
@@ -989,6 +2253,7 @@ export function registerCoreTools(deps) {
989
2253
  artifact_id: artifactId,
990
2254
  name: params.name,
991
2255
  artifact_type: params.artifact_type,
2256
+ confidence_score: confidenceScore,
992
2257
  description: params.description,
993
2258
  url: params.url,
994
2259
  content: params.content,