@useorgx/openclaw-plugin 0.4.8 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -0
- package/dashboard/dist/assets/BJgZIVUQ.js +53 -0
- package/dashboard/dist/assets/BJgZIVUQ.js.br +0 -0
- package/dashboard/dist/assets/BJgZIVUQ.js.gz +0 -0
- package/dashboard/dist/assets/BXWDRGm-.js +1 -0
- package/dashboard/dist/assets/BXWDRGm-.js.br +0 -0
- package/dashboard/dist/assets/BXWDRGm-.js.gz +0 -0
- package/dashboard/dist/assets/BgOYB78t.js +4 -0
- package/dashboard/dist/assets/BgOYB78t.js.br +0 -0
- package/dashboard/dist/assets/BgOYB78t.js.gz +0 -0
- package/dashboard/dist/assets/C-KIc3Wc.js.br +0 -0
- package/dashboard/dist/assets/C-KIc3Wc.js.gz +0 -0
- package/dashboard/dist/assets/CE38zU4U.js +1 -0
- package/dashboard/dist/assets/CE38zU4U.js.br +0 -0
- package/dashboard/dist/assets/CE38zU4U.js.gz +0 -0
- package/dashboard/dist/assets/CFGKRAzG.js +1 -0
- package/dashboard/dist/assets/CFGKRAzG.js.br +0 -0
- package/dashboard/dist/assets/CFGKRAzG.js.gz +0 -0
- package/dashboard/dist/assets/CGGR2GZh.js +1 -0
- package/dashboard/dist/assets/CGGR2GZh.js.br +0 -0
- package/dashboard/dist/assets/CGGR2GZh.js.gz +0 -0
- package/dashboard/dist/assets/CL_wXqR7.js +1 -0
- package/dashboard/dist/assets/CL_wXqR7.js.br +0 -0
- package/dashboard/dist/assets/CL_wXqR7.js.gz +0 -0
- package/dashboard/dist/assets/CPFiTmlw.js +8 -0
- package/dashboard/dist/assets/CPFiTmlw.js.br +0 -0
- package/dashboard/dist/assets/CPFiTmlw.js.gz +0 -0
- package/dashboard/dist/assets/CZZTvkQZ.js +1 -0
- package/dashboard/dist/assets/CZZTvkQZ.js.br +0 -0
- package/dashboard/dist/assets/CZZTvkQZ.js.gz +0 -0
- package/dashboard/dist/assets/{CpJsfbXo.js → CxQ08qFN.js} +2 -2
- package/dashboard/dist/assets/CxQ08qFN.js.br +0 -0
- package/dashboard/dist/assets/CxQ08qFN.js.gz +0 -0
- package/dashboard/dist/assets/D-bf6hEI.js +213 -0
- package/dashboard/dist/assets/D-bf6hEI.js.br +0 -0
- package/dashboard/dist/assets/D-bf6hEI.js.gz +0 -0
- package/dashboard/dist/assets/DG6y9wJI.js +2 -0
- package/dashboard/dist/assets/DG6y9wJI.js.br +0 -0
- package/dashboard/dist/assets/DG6y9wJI.js.gz +0 -0
- package/dashboard/dist/assets/DNxKz-GV.js +1 -0
- package/dashboard/dist/assets/DNxKz-GV.js.br +0 -0
- package/dashboard/dist/assets/DNxKz-GV.js.gz +0 -0
- package/dashboard/dist/assets/DW_rKUic.js +11 -0
- package/dashboard/dist/assets/DW_rKUic.js.br +0 -0
- package/dashboard/dist/assets/DW_rKUic.js.gz +0 -0
- package/dashboard/dist/assets/DbNoijHm.js +1 -0
- package/dashboard/dist/assets/DbNoijHm.js.br +0 -0
- package/dashboard/dist/assets/DbNoijHm.js.gz +0 -0
- package/dashboard/dist/assets/DjcdE6jC.js +2 -0
- package/dashboard/dist/assets/DjcdE6jC.js.br +0 -0
- package/dashboard/dist/assets/DjcdE6jC.js.gz +0 -0
- package/dashboard/dist/assets/FZYuCDnt.js +1 -0
- package/dashboard/dist/assets/FZYuCDnt.js.br +0 -0
- package/dashboard/dist/assets/FZYuCDnt.js.gz +0 -0
- package/dashboard/dist/assets/PAUiij_z.js +1 -0
- package/dashboard/dist/assets/PAUiij_z.js.br +0 -0
- package/dashboard/dist/assets/PAUiij_z.js.gz +0 -0
- package/dashboard/dist/assets/cNrhgGc1.js +8 -0
- package/dashboard/dist/assets/cNrhgGc1.js.br +0 -0
- package/dashboard/dist/assets/cNrhgGc1.js.gz +0 -0
- package/dashboard/dist/assets/h5biQs2I.css +1 -0
- package/dashboard/dist/assets/h5biQs2I.css.br +0 -0
- package/dashboard/dist/assets/h5biQs2I.css.gz +0 -0
- package/dashboard/dist/assets/ic2FaMnh.js +1 -0
- package/dashboard/dist/assets/ic2FaMnh.js.br +0 -0
- package/dashboard/dist/assets/ic2FaMnh.js.gz +0 -0
- package/dashboard/dist/assets/nByHNHoW.js +1 -0
- package/dashboard/dist/assets/nByHNHoW.js.br +0 -0
- package/dashboard/dist/assets/nByHNHoW.js.gz +0 -0
- package/dashboard/dist/assets/qm8xLgv-.css +1 -0
- package/dashboard/dist/assets/qm8xLgv-.css.br +0 -0
- package/dashboard/dist/assets/qm8xLgv-.css.gz +0 -0
- package/dashboard/dist/assets/tS9mbYZi.js +1 -0
- package/dashboard/dist/assets/tS9mbYZi.js.br +0 -0
- package/dashboard/dist/assets/tS9mbYZi.js.gz +0 -0
- package/dashboard/dist/brand/anthropic-mark.svg.br +0 -0
- package/dashboard/dist/brand/anthropic-mark.svg.gz +0 -0
- package/dashboard/dist/brand/openai-mark.svg.br +0 -0
- package/dashboard/dist/brand/openai-mark.svg.gz +0 -0
- package/dashboard/dist/brand/openclaw-mark.svg.br +0 -0
- package/dashboard/dist/brand/openclaw-mark.svg.gz +0 -0
- package/dashboard/dist/brand/xandy-orchestrator.png +0 -0
- package/dashboard/dist/index.html +7 -5
- package/dashboard/dist/index.html.br +0 -0
- package/dashboard/dist/index.html.gz +0 -0
- package/dist/activity-actor-fields.js +26 -4
- package/dist/activity-store.js +38 -26
- package/dist/agent-context-store.js +84 -42
- package/dist/agent-run-store.js +49 -28
- package/dist/agent-suite.d.ts +9 -0
- package/dist/agent-suite.js +150 -17
- package/dist/artifacts/artifact-domain-schemas.d.ts +66 -0
- package/dist/artifacts/artifact-domain-schemas.js +357 -0
- package/dist/artifacts/register-artifact.d.ts +4 -3
- package/dist/artifacts/register-artifact.js +170 -57
- package/dist/auth/flows.d.ts +47 -0
- package/dist/auth/flows.js +169 -0
- package/dist/auth-store.js +6 -26
- package/dist/byok-store.js +5 -19
- package/dist/chat-store.d.ts +157 -0
- package/dist/chat-store.js +586 -0
- package/dist/cli/orgx.d.ts +66 -0
- package/dist/cli/orgx.js +102 -0
- package/dist/config/refresh.d.ts +32 -0
- package/dist/config/refresh.js +55 -0
- package/dist/config/resolution.d.ts +37 -0
- package/dist/config/resolution.js +178 -0
- package/dist/contracts/client.d.ts +43 -3
- package/dist/contracts/client.js +159 -30
- package/dist/contracts/retro-schema.d.ts +81 -0
- package/dist/contracts/retro-schema.js +80 -0
- package/dist/contracts/shared-types.d.ts +306 -0
- package/dist/contracts/shared-types.js +179 -0
- package/dist/contracts/skill-pack-schema.d.ts +192 -0
- package/dist/contracts/skill-pack-schema.js +180 -0
- package/dist/contracts/types.d.ts +224 -132
- package/dist/contracts/types.js +5 -0
- package/dist/entities/auto-assignment.d.ts +36 -0
- package/dist/entities/auto-assignment.js +141 -0
- package/dist/entity-comment-store.js +5 -25
- package/dist/event-sanitization.d.ts +11 -0
- package/dist/event-sanitization.js +113 -0
- package/dist/fs-utils.js +13 -1
- package/dist/gateway-watchdog.d.ts +5 -0
- package/dist/gateway-watchdog.js +50 -0
- package/dist/hash-utils.d.ts +2 -0
- package/dist/hash-utils.js +12 -0
- package/dist/hooks/post-reporting-event.mjs +1 -5
- package/dist/http/helpers/activity-headline.d.ts +10 -0
- package/dist/http/helpers/activity-headline.js +73 -0
- package/dist/http/helpers/artifact-fallback.d.ts +13 -0
- package/dist/http/helpers/artifact-fallback.js +148 -0
- package/dist/http/helpers/auto-continue-engine.d.ts +486 -0
- package/dist/http/helpers/auto-continue-engine.js +3563 -0
- package/dist/http/helpers/autopilot-operations.d.ts +176 -0
- package/dist/http/helpers/autopilot-operations.js +554 -0
- package/dist/http/helpers/autopilot-runtime.d.ts +43 -0
- package/dist/http/helpers/autopilot-runtime.js +607 -0
- package/dist/http/helpers/autopilot-slice-utils.d.ts +56 -0
- package/dist/http/helpers/autopilot-slice-utils.js +899 -0
- package/dist/http/helpers/decision-mapper.d.ts +52 -0
- package/dist/http/helpers/decision-mapper.js +260 -0
- package/dist/http/helpers/dispatch-lifecycle.d.ts +119 -0
- package/dist/http/helpers/dispatch-lifecycle.js +809 -0
- package/dist/http/helpers/hash-utils.d.ts +1 -0
- package/dist/http/helpers/hash-utils.js +1 -0
- package/dist/http/helpers/kickoff-context.d.ts +12 -0
- package/dist/http/helpers/kickoff-context.js +228 -0
- package/dist/http/helpers/llm-client.d.ts +47 -0
- package/dist/http/helpers/llm-client.js +256 -0
- package/dist/http/helpers/mission-control.d.ts +193 -0
- package/dist/http/helpers/mission-control.js +1383 -0
- package/dist/http/helpers/openclaw-cli.d.ts +37 -0
- package/dist/http/helpers/openclaw-cli.js +283 -0
- package/dist/http/helpers/runtime-sse.d.ts +20 -0
- package/dist/http/helpers/runtime-sse.js +110 -0
- package/dist/http/helpers/sentinel-catalog.d.ts +23 -0
- package/dist/http/helpers/sentinel-catalog.js +193 -0
- package/dist/http/helpers/session-classification.d.ts +9 -0
- package/dist/http/helpers/session-classification.js +564 -0
- package/dist/http/helpers/slice-experience-v2.d.ts +137 -0
- package/dist/http/helpers/slice-experience-v2.js +677 -0
- package/dist/http/helpers/slice-run-projections.d.ts +72 -0
- package/dist/http/helpers/slice-run-projections.js +860 -0
- package/dist/http/helpers/triage-mapper.d.ts +43 -0
- package/dist/http/helpers/triage-mapper.js +549 -0
- package/dist/http/helpers/value-utils.d.ts +6 -0
- package/dist/http/helpers/value-utils.js +72 -0
- package/dist/http/helpers/workspace-scope.d.ts +15 -0
- package/dist/http/helpers/workspace-scope.js +170 -0
- package/dist/http/index.d.ts +88 -0
- package/dist/http/index.js +3610 -0
- package/dist/http/router.d.ts +23 -0
- package/dist/http/router.js +23 -0
- package/dist/http/routes/agent-control.d.ts +79 -0
- package/dist/http/routes/agent-control.js +684 -0
- package/dist/http/routes/agent-suite.d.ts +38 -0
- package/dist/http/routes/agent-suite.js +397 -0
- package/dist/http/routes/agents-catalog.d.ts +40 -0
- package/dist/http/routes/agents-catalog.js +128 -0
- package/dist/http/routes/billing.d.ts +23 -0
- package/dist/http/routes/billing.js +55 -0
- package/dist/http/routes/chat.d.ts +19 -0
- package/dist/http/routes/chat.js +522 -0
- package/dist/http/routes/debug.d.ts +14 -0
- package/dist/http/routes/debug.js +21 -0
- package/dist/http/routes/decision-actions.d.ts +20 -0
- package/dist/http/routes/decision-actions.js +103 -0
- package/dist/http/routes/delegation.d.ts +19 -0
- package/dist/http/routes/delegation.js +32 -0
- package/dist/http/routes/dispatch-gateway-envelope.d.ts +25 -0
- package/dist/http/routes/dispatch-gateway-envelope.js +26 -0
- package/dist/http/routes/entities.d.ts +63 -0
- package/dist/http/routes/entities.js +440 -0
- package/dist/http/routes/entity-dynamic.d.ts +25 -0
- package/dist/http/routes/entity-dynamic.js +191 -0
- package/dist/http/routes/health.d.ts +22 -0
- package/dist/http/routes/health.js +49 -0
- package/dist/http/routes/live-legacy.d.ts +115 -0
- package/dist/http/routes/live-legacy.js +112 -0
- package/dist/http/routes/live-misc.d.ts +81 -0
- package/dist/http/routes/live-misc.js +426 -0
- package/dist/http/routes/live-snapshot.d.ts +136 -0
- package/dist/http/routes/live-snapshot.js +916 -0
- package/dist/http/routes/live-terminal.d.ts +11 -0
- package/dist/http/routes/live-terminal.js +261 -0
- package/dist/http/routes/live-triage.d.ts +61 -0
- package/dist/http/routes/live-triage.js +248 -0
- package/dist/http/routes/mission-control-actions.d.ts +131 -0
- package/dist/http/routes/mission-control-actions.js +1791 -0
- package/dist/http/routes/mission-control-read.d.ts +73 -0
- package/dist/http/routes/mission-control-read.js +1640 -0
- package/dist/http/routes/onboarding.d.ts +34 -0
- package/dist/http/routes/onboarding.js +101 -0
- package/dist/http/routes/realtime-orchestrator.d.ts +10 -0
- package/dist/http/routes/realtime-orchestrator.js +74 -0
- package/dist/http/routes/run-control.d.ts +27 -0
- package/dist/http/routes/run-control.js +96 -0
- package/dist/http/routes/runtime-hooks.d.ts +69 -0
- package/dist/http/routes/runtime-hooks.js +437 -0
- package/dist/http/routes/sentinels-catalog.d.ts +7 -0
- package/dist/http/routes/sentinels-catalog.js +24 -0
- package/dist/http/routes/settings-byok.d.ts +23 -0
- package/dist/http/routes/settings-byok.js +163 -0
- package/dist/http/routes/summary.d.ts +18 -0
- package/dist/http/routes/summary.js +49 -0
- package/dist/http/routes/usage.d.ts +24 -0
- package/dist/http/routes/usage.js +362 -0
- package/dist/http/routes/work-artifacts.d.ts +9 -0
- package/dist/http/routes/work-artifacts.js +55 -0
- package/dist/http/shared-state.d.ts +16 -0
- package/dist/http/shared-state.js +1 -0
- package/dist/http-handler.d.ts +1 -88
- package/dist/http-handler.js +1 -10605
- package/dist/index.js +287 -2284
- package/dist/json-utils.d.ts +1 -0
- package/dist/json-utils.js +8 -0
- package/dist/local-openclaw.js +29 -6
- package/dist/mcp-client-setup.js +3 -3
- package/dist/mcp-http-handler.js +33 -59
- package/dist/next-up-queue-store.d.ts +16 -1
- package/dist/next-up-queue-store.js +93 -25
- package/dist/outbox.d.ts +5 -0
- package/dist/outbox.js +113 -9
- package/dist/paths.js +24 -5
- package/dist/reporting/rollups.d.ts +53 -0
- package/dist/reporting/rollups.js +148 -0
- package/dist/retro/domain-templates.d.ts +45 -0
- package/dist/retro/domain-templates.js +297 -0
- package/dist/retro/quality-rubric.d.ts +33 -0
- package/dist/retro/quality-rubric.js +213 -0
- package/dist/runtime-cleanup.d.ts +18 -0
- package/dist/runtime-cleanup.js +87 -0
- package/dist/runtime-instance-store.js +5 -31
- package/dist/services/background.d.ts +34 -0
- package/dist/services/background.js +45 -0
- package/dist/services/experiment-randomization.d.ts +21 -0
- package/dist/services/experiment-randomization.js +63 -0
- package/dist/services/instrumentation.d.ts +29 -0
- package/dist/services/instrumentation.js +136 -0
- package/dist/skill-pack-state.d.ts +36 -5
- package/dist/skill-pack-state.js +273 -29
- package/dist/snapshot-store.js +5 -25
- package/dist/stores/json-store.d.ts +11 -0
- package/dist/stores/json-store.js +42 -0
- package/dist/sync/local-agent-telemetry.d.ts +13 -0
- package/dist/sync/local-agent-telemetry.js +128 -0
- package/dist/sync/outbox-replay.d.ts +55 -0
- package/dist/sync/outbox-replay.js +621 -0
- package/dist/team-context-store.d.ts +23 -0
- package/dist/team-context-store.js +116 -0
- package/dist/telemetry/posthog.js +4 -2
- package/dist/tools/core-tools.d.ts +72 -0
- package/dist/tools/core-tools.js +2270 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.js +2 -0
- package/dist/worker-supervisor.js +23 -0
- package/package.json +14 -4
- package/dashboard/dist/assets/B3ziCA02.js +0 -8
- package/dashboard/dist/assets/BNeJ0kpF.js +0 -1
- package/dashboard/dist/assets/BzkiMPmM.js +0 -215
- package/dashboard/dist/assets/CUV9IHHi.js +0 -1
- package/dashboard/dist/assets/Ie7d9Iq2.css +0 -1
- package/dashboard/dist/assets/sAhvFnpk.js +0 -4
|
@@ -0,0 +1,2270 @@
|
|
|
1
|
+
import { randomUUID as randomUuidFn } from "node:crypto";
|
|
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";
|
|
5
|
+
import { appendToOutbox } from "../outbox.js";
|
|
6
|
+
export function registerCoreTools(deps) {
|
|
7
|
+
const { registerTool, client, config, getCachedSnapshot, getLastSnapshotAt, doSync, text, json, formatSnapshot, autoAssignEntityForCreate, toReportingPhase, inferReportingInitiativeId, isUuid, pickNonEmptyString, resolveReportingContext, readSkillPackState, updateSkillPackPolicy, rollbackSkillPackPolicy, } = deps;
|
|
8
|
+
const randomUUID = deps.randomUUID ?? randomUuidFn;
|
|
9
|
+
const mcpToolRegistry = new Map();
|
|
10
|
+
const registerMcpTool = (tool, options) => {
|
|
11
|
+
mcpToolRegistry.set(tool.name, tool);
|
|
12
|
+
registerTool(tool, options);
|
|
13
|
+
};
|
|
14
|
+
// --- orgx_status ---
|
|
15
|
+
registerMcpTool({
|
|
16
|
+
name: "orgx_status",
|
|
17
|
+
description: "Get current OrgX org status: active initiatives, agent states, pending decisions, active tasks.",
|
|
18
|
+
parameters: {
|
|
19
|
+
type: "object",
|
|
20
|
+
properties: {},
|
|
21
|
+
additionalProperties: false,
|
|
22
|
+
},
|
|
23
|
+
async execute(_callId) {
|
|
24
|
+
let snapshot = getCachedSnapshot();
|
|
25
|
+
if (!snapshot || Date.now() - getLastSnapshotAt() > config.syncIntervalMs) {
|
|
26
|
+
await doSync();
|
|
27
|
+
snapshot = getCachedSnapshot();
|
|
28
|
+
}
|
|
29
|
+
if (!snapshot) {
|
|
30
|
+
return text("❌ Failed to fetch OrgX status. Check API key and connectivity.");
|
|
31
|
+
}
|
|
32
|
+
return text(formatSnapshot(snapshot));
|
|
33
|
+
},
|
|
34
|
+
}, { optional: true });
|
|
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 });
|
|
64
|
+
registerMcpTool({
|
|
65
|
+
name: "orgx_sync",
|
|
66
|
+
description: "Push/pull memory sync with OrgX. Send local memory/daily log; receive initiatives, tasks, decisions, model routing policy.",
|
|
67
|
+
parameters: {
|
|
68
|
+
type: "object",
|
|
69
|
+
properties: {
|
|
70
|
+
memory: {
|
|
71
|
+
type: "string",
|
|
72
|
+
description: "Local memory snapshot to push",
|
|
73
|
+
},
|
|
74
|
+
dailyLog: {
|
|
75
|
+
type: "string",
|
|
76
|
+
description: "Today's session log to push",
|
|
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
|
+
},
|
|
98
|
+
},
|
|
99
|
+
additionalProperties: false,
|
|
100
|
+
},
|
|
101
|
+
async execute(_callId, params = {}) {
|
|
102
|
+
try {
|
|
103
|
+
const resp = await client.syncMemory({
|
|
104
|
+
memory: params.memory,
|
|
105
|
+
dailyLog: params.dailyLog,
|
|
106
|
+
agents: params.agents,
|
|
107
|
+
});
|
|
108
|
+
return json("Sync complete:", resp);
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
return text(`❌ Sync failed: ${err instanceof Error ? err.message : err}`);
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
}, { optional: true });
|
|
115
|
+
// --- orgx_delegation_preflight ---
|
|
116
|
+
registerMcpTool({
|
|
117
|
+
name: "orgx_delegation_preflight",
|
|
118
|
+
description: "Run delegation preflight to score scope quality, estimate ETA/cost, and suggest a split before autonomous execution.",
|
|
119
|
+
parameters: {
|
|
120
|
+
type: "object",
|
|
121
|
+
properties: {
|
|
122
|
+
intent: {
|
|
123
|
+
type: "string",
|
|
124
|
+
description: "Task intent in natural language",
|
|
125
|
+
},
|
|
126
|
+
acceptanceCriteria: {
|
|
127
|
+
type: "array",
|
|
128
|
+
items: { type: "string" },
|
|
129
|
+
description: "Optional acceptance criteria to reduce ambiguity",
|
|
130
|
+
},
|
|
131
|
+
constraints: {
|
|
132
|
+
type: "array",
|
|
133
|
+
items: { type: "string" },
|
|
134
|
+
description: "Optional constraints (deadline, stack, policy)",
|
|
135
|
+
},
|
|
136
|
+
domains: {
|
|
137
|
+
type: "array",
|
|
138
|
+
items: { type: "string" },
|
|
139
|
+
description: "Optional preferred owner domains",
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
required: ["intent"],
|
|
143
|
+
additionalProperties: false,
|
|
144
|
+
},
|
|
145
|
+
async execute(_callId, params = { intent: "" }) {
|
|
146
|
+
try {
|
|
147
|
+
const result = await client.delegationPreflight({
|
|
148
|
+
intent: params.intent,
|
|
149
|
+
acceptanceCriteria: Array.isArray(params.acceptanceCriteria)
|
|
150
|
+
? params.acceptanceCriteria.filter((item) => typeof item === "string")
|
|
151
|
+
: undefined,
|
|
152
|
+
constraints: Array.isArray(params.constraints)
|
|
153
|
+
? params.constraints.filter((item) => typeof item === "string")
|
|
154
|
+
: undefined,
|
|
155
|
+
domains: Array.isArray(params.domains)
|
|
156
|
+
? params.domains.filter((item) => typeof item === "string")
|
|
157
|
+
: undefined,
|
|
158
|
+
});
|
|
159
|
+
return json("Delegation preflight:", result.data ?? result);
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
return text(`❌ Delegation preflight failed: ${err instanceof Error ? err.message : err}`);
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
}, { optional: true });
|
|
166
|
+
// --- orgx_run_action ---
|
|
167
|
+
registerMcpTool({
|
|
168
|
+
name: "orgx_run_action",
|
|
169
|
+
description: "Apply a control action to a run: pause, resume, cancel, or rollback (rollback requires checkpointId).",
|
|
170
|
+
parameters: {
|
|
171
|
+
type: "object",
|
|
172
|
+
properties: {
|
|
173
|
+
runId: {
|
|
174
|
+
type: "string",
|
|
175
|
+
description: "Run UUID",
|
|
176
|
+
},
|
|
177
|
+
action: {
|
|
178
|
+
type: "string",
|
|
179
|
+
enum: ["pause", "resume", "cancel", "rollback"],
|
|
180
|
+
description: "Control action",
|
|
181
|
+
},
|
|
182
|
+
checkpointId: {
|
|
183
|
+
type: "string",
|
|
184
|
+
description: "Checkpoint UUID (required for rollback)",
|
|
185
|
+
},
|
|
186
|
+
reason: {
|
|
187
|
+
type: "string",
|
|
188
|
+
description: "Optional reason for audit trail",
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
required: ["runId", "action"],
|
|
192
|
+
additionalProperties: false,
|
|
193
|
+
},
|
|
194
|
+
async execute(_callId, params = { runId: "", action: "pause" }) {
|
|
195
|
+
try {
|
|
196
|
+
if (params.action === "rollback" && !params.checkpointId) {
|
|
197
|
+
return text("❌ rollback requires checkpointId");
|
|
198
|
+
}
|
|
199
|
+
const result = await client.runAction(params.runId, params.action, {
|
|
200
|
+
checkpointId: params.checkpointId,
|
|
201
|
+
reason: params.reason,
|
|
202
|
+
});
|
|
203
|
+
return json("Run action applied:", result.data ?? result);
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
return text(`❌ Run action failed: ${err instanceof Error ? err.message : err}`);
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
}, { optional: true });
|
|
210
|
+
// --- orgx_checkpoints_list ---
|
|
211
|
+
registerMcpTool({
|
|
212
|
+
name: "orgx_checkpoints_list",
|
|
213
|
+
description: "List checkpoints for a run.",
|
|
214
|
+
parameters: {
|
|
215
|
+
type: "object",
|
|
216
|
+
properties: {
|
|
217
|
+
runId: {
|
|
218
|
+
type: "string",
|
|
219
|
+
description: "Run UUID",
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
required: ["runId"],
|
|
223
|
+
additionalProperties: false,
|
|
224
|
+
},
|
|
225
|
+
async execute(_callId, params = { runId: "" }) {
|
|
226
|
+
try {
|
|
227
|
+
const result = await client.listRunCheckpoints(params.runId);
|
|
228
|
+
return json("Run checkpoints:", result.data ?? result);
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
return text(`❌ Failed to list checkpoints: ${err instanceof Error ? err.message : err}`);
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
}, { optional: true });
|
|
235
|
+
// --- orgx_checkpoint_restore ---
|
|
236
|
+
registerMcpTool({
|
|
237
|
+
name: "orgx_checkpoint_restore",
|
|
238
|
+
description: "Restore a run to a specific checkpoint.",
|
|
239
|
+
parameters: {
|
|
240
|
+
type: "object",
|
|
241
|
+
properties: {
|
|
242
|
+
runId: {
|
|
243
|
+
type: "string",
|
|
244
|
+
description: "Run UUID",
|
|
245
|
+
},
|
|
246
|
+
checkpointId: {
|
|
247
|
+
type: "string",
|
|
248
|
+
description: "Checkpoint UUID",
|
|
249
|
+
},
|
|
250
|
+
reason: {
|
|
251
|
+
type: "string",
|
|
252
|
+
description: "Optional restoration reason",
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
required: ["runId", "checkpointId"],
|
|
256
|
+
additionalProperties: false,
|
|
257
|
+
},
|
|
258
|
+
async execute(_callId, params = {
|
|
259
|
+
runId: "",
|
|
260
|
+
checkpointId: "",
|
|
261
|
+
}) {
|
|
262
|
+
try {
|
|
263
|
+
const result = await client.restoreRunCheckpoint(params.runId, {
|
|
264
|
+
checkpointId: params.checkpointId,
|
|
265
|
+
reason: params.reason,
|
|
266
|
+
});
|
|
267
|
+
return json("Checkpoint restored:", result.data ?? result);
|
|
268
|
+
}
|
|
269
|
+
catch (err) {
|
|
270
|
+
return text(`❌ Checkpoint restore failed: ${err instanceof Error ? err.message : err}`);
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
}, { optional: true });
|
|
274
|
+
// --- orgx_spawn_check ---
|
|
275
|
+
registerMcpTool({
|
|
276
|
+
name: "orgx_spawn_check",
|
|
277
|
+
description: "Check quality gate + get model routing before spawning a sub-agent. Returns allowed/denied, model tier, and check details.",
|
|
278
|
+
parameters: {
|
|
279
|
+
type: "object",
|
|
280
|
+
properties: {
|
|
281
|
+
domain: {
|
|
282
|
+
type: "string",
|
|
283
|
+
description: "Agent domain (engineering, product, marketing, data, operations, design)",
|
|
284
|
+
},
|
|
285
|
+
taskId: {
|
|
286
|
+
type: "string",
|
|
287
|
+
description: "OrgX task ID to check",
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
required: ["domain"],
|
|
291
|
+
},
|
|
292
|
+
async execute(_callId, params = { domain: "" }) {
|
|
293
|
+
try {
|
|
294
|
+
const result = await client.checkSpawnGuard(params.domain, params.taskId);
|
|
295
|
+
const status = result.allowed ? "✅ Allowed" : "🚫 Blocked";
|
|
296
|
+
return json(`${status} — model tier: ${result.modelTier}`, result);
|
|
297
|
+
}
|
|
298
|
+
catch (err) {
|
|
299
|
+
return text(`❌ Spawn check failed: ${err instanceof Error ? err.message : err}`);
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
}, { optional: true });
|
|
303
|
+
// --- orgx_quality_score ---
|
|
304
|
+
registerMcpTool({
|
|
305
|
+
name: "orgx_quality_score",
|
|
306
|
+
description: "Record a quality score (1-5) for completed agent work. Used to gate future spawns and track performance.",
|
|
307
|
+
parameters: {
|
|
308
|
+
type: "object",
|
|
309
|
+
properties: {
|
|
310
|
+
taskId: {
|
|
311
|
+
type: "string",
|
|
312
|
+
description: "ID of the completed task",
|
|
313
|
+
},
|
|
314
|
+
domain: {
|
|
315
|
+
type: "string",
|
|
316
|
+
description: "Agent domain that did the work",
|
|
317
|
+
},
|
|
318
|
+
score: {
|
|
319
|
+
type: "number",
|
|
320
|
+
description: "Quality 1 (poor) to 5 (excellent)",
|
|
321
|
+
minimum: 1,
|
|
322
|
+
maximum: 5,
|
|
323
|
+
},
|
|
324
|
+
notes: {
|
|
325
|
+
type: "string",
|
|
326
|
+
description: "Notes on the assessment",
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
required: ["taskId", "domain", "score"],
|
|
330
|
+
},
|
|
331
|
+
async execute(_callId, params = { taskId: "", domain: "", score: 0 }) {
|
|
332
|
+
try {
|
|
333
|
+
await client.recordQuality(params);
|
|
334
|
+
return text(`✅ Quality score recorded: ${params.score}/5 for task ${params.taskId} (${params.domain})`);
|
|
335
|
+
}
|
|
336
|
+
catch (err) {
|
|
337
|
+
return text(`❌ Quality recording failed: ${err instanceof Error ? err.message : err}`);
|
|
338
|
+
}
|
|
339
|
+
},
|
|
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 });
|
|
570
|
+
// --- orgx_create_entity ---
|
|
571
|
+
registerMcpTool({
|
|
572
|
+
name: "orgx_create_entity",
|
|
573
|
+
description: "Create an OrgX entity (initiative, workstream, task, decision, milestone, etc.).",
|
|
574
|
+
parameters: {
|
|
575
|
+
type: "object",
|
|
576
|
+
properties: {
|
|
577
|
+
type: {
|
|
578
|
+
type: "string",
|
|
579
|
+
description: "Entity type: initiative, workstream, task, decision, milestone, artifact, agent, blocker",
|
|
580
|
+
},
|
|
581
|
+
title: {
|
|
582
|
+
type: "string",
|
|
583
|
+
description: "Entity title",
|
|
584
|
+
},
|
|
585
|
+
summary: {
|
|
586
|
+
type: "string",
|
|
587
|
+
description: "Description",
|
|
588
|
+
},
|
|
589
|
+
status: {
|
|
590
|
+
type: "string",
|
|
591
|
+
description: "Initial status (active, not_started, todo)",
|
|
592
|
+
},
|
|
593
|
+
initiative_id: {
|
|
594
|
+
type: "string",
|
|
595
|
+
description: "Parent initiative ID (for workstreams/tasks)",
|
|
596
|
+
},
|
|
597
|
+
workstream_id: {
|
|
598
|
+
type: "string",
|
|
599
|
+
description: "Parent workstream ID (for tasks)",
|
|
600
|
+
},
|
|
601
|
+
workspace_id: {
|
|
602
|
+
type: "string",
|
|
603
|
+
description: "Workspace ID (canonical; preferred for new callers)",
|
|
604
|
+
},
|
|
605
|
+
command_center_id: {
|
|
606
|
+
type: "string",
|
|
607
|
+
description: "Deprecated alias for workspace_id",
|
|
608
|
+
},
|
|
609
|
+
},
|
|
610
|
+
required: ["type", "title"],
|
|
611
|
+
},
|
|
612
|
+
async execute(_callId, params = {}) {
|
|
613
|
+
try {
|
|
614
|
+
const { type, ...data } = params;
|
|
615
|
+
let entity = await client.createEntity(type, data);
|
|
616
|
+
let assignmentSummary = null;
|
|
617
|
+
const entityType = String(type ?? "");
|
|
618
|
+
if (entityType === "initiative" || entityType === "workstream") {
|
|
619
|
+
const entityRecord = entity;
|
|
620
|
+
const assignment = await autoAssignEntityForCreate({
|
|
621
|
+
entityType,
|
|
622
|
+
entityId: String(entityRecord.id ?? ""),
|
|
623
|
+
initiativeId: entityType === "initiative"
|
|
624
|
+
? String(entityRecord.id ?? "")
|
|
625
|
+
: (typeof data.initiative_id === "string"
|
|
626
|
+
? data.initiative_id
|
|
627
|
+
: null),
|
|
628
|
+
title: (typeof entityRecord.title === "string" && entityRecord.title) ||
|
|
629
|
+
(typeof entityRecord.name === "string" && entityRecord.name) ||
|
|
630
|
+
(typeof data.title === "string" && data.title) ||
|
|
631
|
+
"Untitled",
|
|
632
|
+
summary: (typeof entityRecord.summary === "string" && entityRecord.summary) ||
|
|
633
|
+
(typeof data.summary === "string" && data.summary) ||
|
|
634
|
+
null,
|
|
635
|
+
});
|
|
636
|
+
if (assignment.updatedEntity) {
|
|
637
|
+
entity = assignment.updatedEntity;
|
|
638
|
+
}
|
|
639
|
+
assignmentSummary = {
|
|
640
|
+
assignment_source: assignment.assignmentSource,
|
|
641
|
+
assigned_agents: assignment.assignedAgents,
|
|
642
|
+
warnings: assignment.warnings,
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
return json(`✅ Created ${type}: ${entity.title ?? entity.id}`, {
|
|
646
|
+
entity,
|
|
647
|
+
...(assignmentSummary
|
|
648
|
+
? {
|
|
649
|
+
auto_assignment: assignmentSummary,
|
|
650
|
+
}
|
|
651
|
+
: {}),
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
catch (err) {
|
|
655
|
+
return text(`❌ Creation failed: ${err instanceof Error ? err.message : err}`);
|
|
656
|
+
}
|
|
657
|
+
},
|
|
658
|
+
}, { optional: true });
|
|
659
|
+
// --- orgx_update_entity ---
|
|
660
|
+
registerMcpTool({
|
|
661
|
+
name: "orgx_update_entity",
|
|
662
|
+
description: "Update an existing OrgX entity by type and ID.",
|
|
663
|
+
parameters: {
|
|
664
|
+
type: "object",
|
|
665
|
+
properties: {
|
|
666
|
+
type: {
|
|
667
|
+
type: "string",
|
|
668
|
+
description: "Entity type",
|
|
669
|
+
},
|
|
670
|
+
id: {
|
|
671
|
+
type: "string",
|
|
672
|
+
description: "Entity UUID",
|
|
673
|
+
},
|
|
674
|
+
status: {
|
|
675
|
+
type: "string",
|
|
676
|
+
description: "New status",
|
|
677
|
+
},
|
|
678
|
+
title: {
|
|
679
|
+
type: "string",
|
|
680
|
+
description: "New title",
|
|
681
|
+
},
|
|
682
|
+
summary: {
|
|
683
|
+
type: "string",
|
|
684
|
+
description: "New summary",
|
|
685
|
+
},
|
|
686
|
+
},
|
|
687
|
+
required: ["type", "id"],
|
|
688
|
+
},
|
|
689
|
+
async execute(_callId, params = {}) {
|
|
690
|
+
try {
|
|
691
|
+
const { type, id, ...updates } = params;
|
|
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);
|
|
701
|
+
}
|
|
702
|
+
catch (err) {
|
|
703
|
+
return text(`❌ Update failed: ${err instanceof Error ? err.message : err}`);
|
|
704
|
+
}
|
|
705
|
+
},
|
|
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 });
|
|
1223
|
+
// --- orgx_list_entities ---
|
|
1224
|
+
registerMcpTool({
|
|
1225
|
+
name: "orgx_list_entities",
|
|
1226
|
+
description: "List OrgX entities of a given type with optional status filter.",
|
|
1227
|
+
parameters: {
|
|
1228
|
+
type: "object",
|
|
1229
|
+
properties: {
|
|
1230
|
+
type: {
|
|
1231
|
+
type: "string",
|
|
1232
|
+
description: "Entity type: initiative, workstream, task, decision, agent",
|
|
1233
|
+
},
|
|
1234
|
+
status: {
|
|
1235
|
+
type: "string",
|
|
1236
|
+
description: "Filter by status",
|
|
1237
|
+
},
|
|
1238
|
+
limit: {
|
|
1239
|
+
type: "number",
|
|
1240
|
+
description: "Max results (default 20)",
|
|
1241
|
+
default: 20,
|
|
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
|
+
},
|
|
1259
|
+
},
|
|
1260
|
+
required: ["type"],
|
|
1261
|
+
},
|
|
1262
|
+
async execute(_callId, params = { type: "" }) {
|
|
1263
|
+
try {
|
|
1264
|
+
const { type, ...filters } = params;
|
|
1265
|
+
const resp = await client.listEntities(type, filters);
|
|
1266
|
+
const entities = resp.data ?? resp;
|
|
1267
|
+
const count = Array.isArray(entities) ? entities.length : "?";
|
|
1268
|
+
return json(`${count} ${type}(s):`, entities);
|
|
1269
|
+
}
|
|
1270
|
+
catch (err) {
|
|
1271
|
+
return text(`❌ List failed: ${err instanceof Error ? err.message : err}`);
|
|
1272
|
+
}
|
|
1273
|
+
},
|
|
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 });
|
|
1554
|
+
function withProvenanceMetadata(metadata) {
|
|
1555
|
+
const input = metadata ?? {};
|
|
1556
|
+
const out = { ...input };
|
|
1557
|
+
if (out.orgx_plugin_version === undefined) {
|
|
1558
|
+
out.orgx_plugin_version = (config.pluginVersion ?? "").trim() || null;
|
|
1559
|
+
}
|
|
1560
|
+
try {
|
|
1561
|
+
const state = readSkillPackState();
|
|
1562
|
+
const overrides = state.overrides;
|
|
1563
|
+
if (out.skill_pack_name === undefined) {
|
|
1564
|
+
out.skill_pack_name = overrides?.name ?? state.pack?.name ?? null;
|
|
1565
|
+
}
|
|
1566
|
+
if (out.skill_pack_version === undefined) {
|
|
1567
|
+
out.skill_pack_version = overrides?.version ?? state.pack?.version ?? null;
|
|
1568
|
+
}
|
|
1569
|
+
if (out.skill_pack_checksum === undefined) {
|
|
1570
|
+
out.skill_pack_checksum = overrides?.checksum ?? state.pack?.checksum ?? null;
|
|
1571
|
+
}
|
|
1572
|
+
if (out.skill_pack_source === undefined) {
|
|
1573
|
+
out.skill_pack_source = overrides?.source ?? null;
|
|
1574
|
+
}
|
|
1575
|
+
if (out.skill_pack_etag === undefined) {
|
|
1576
|
+
out.skill_pack_etag = state.etag ?? null;
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
catch {
|
|
1580
|
+
// best effort
|
|
1581
|
+
}
|
|
1582
|
+
if (out.orgx_provenance === undefined) {
|
|
1583
|
+
out.orgx_provenance = {
|
|
1584
|
+
plugin_version: out.orgx_plugin_version ?? null,
|
|
1585
|
+
skill_pack: {
|
|
1586
|
+
name: out.skill_pack_name ?? null,
|
|
1587
|
+
version: out.skill_pack_version ?? null,
|
|
1588
|
+
checksum: out.skill_pack_checksum ?? null,
|
|
1589
|
+
source: out.skill_pack_source ?? null,
|
|
1590
|
+
etag: out.skill_pack_etag ?? null,
|
|
1591
|
+
},
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1594
|
+
return out;
|
|
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
|
+
}
|
|
1616
|
+
async function emitActivityWithFallback(source, payload) {
|
|
1617
|
+
if (!payload.message || payload.message.trim().length === 0) {
|
|
1618
|
+
return text("❌ message is required");
|
|
1619
|
+
}
|
|
1620
|
+
const context = resolveReportingContext(payload);
|
|
1621
|
+
if (!context.ok) {
|
|
1622
|
+
return text(`❌ ${context.error}`);
|
|
1623
|
+
}
|
|
1624
|
+
const now = new Date().toISOString();
|
|
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
|
+
};
|
|
1640
|
+
const normalizedPayload = {
|
|
1641
|
+
initiative_id: context.value.initiativeId,
|
|
1642
|
+
run_id: context.value.runId,
|
|
1643
|
+
correlation_id: context.value.correlationId,
|
|
1644
|
+
source_client: context.value.sourceClient,
|
|
1645
|
+
message: payload.message,
|
|
1646
|
+
phase: payload.phase ?? "execution",
|
|
1647
|
+
progress_pct: payload.progress_pct,
|
|
1648
|
+
level: payload.level ?? "info",
|
|
1649
|
+
next_step: payload.next_step,
|
|
1650
|
+
metadata: withProvenanceMetadata({
|
|
1651
|
+
...canonicalMetadata,
|
|
1652
|
+
...(payload.metadata ?? {}),
|
|
1653
|
+
source,
|
|
1654
|
+
}),
|
|
1655
|
+
};
|
|
1656
|
+
const activityItem = {
|
|
1657
|
+
id,
|
|
1658
|
+
type: "delegation",
|
|
1659
|
+
title: payload.message,
|
|
1660
|
+
description: payload.next_step ?? null,
|
|
1661
|
+
agentId,
|
|
1662
|
+
agentName,
|
|
1663
|
+
requesterAgentId: agentId,
|
|
1664
|
+
requesterAgentName: agentName,
|
|
1665
|
+
executorAgentId: agentId,
|
|
1666
|
+
executorAgentName: agentName,
|
|
1667
|
+
runId: context.value.runId ?? null,
|
|
1668
|
+
initiativeId: context.value.initiativeId,
|
|
1669
|
+
timestamp: now,
|
|
1670
|
+
phase: normalizedPayload.phase,
|
|
1671
|
+
summary: payload.next_step ? `Next: ${payload.next_step}` : payload.message,
|
|
1672
|
+
metadata: normalizedPayload.metadata,
|
|
1673
|
+
};
|
|
1674
|
+
try {
|
|
1675
|
+
const result = await client.emitActivity(normalizedPayload);
|
|
1676
|
+
return text(`Activity emitted: ${payload.message} [${normalizedPayload.phase}${payload.progress_pct != null ? ` ${payload.progress_pct}%` : ""}] (run ${result.run_id.slice(0, 8)}...)`);
|
|
1677
|
+
}
|
|
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
|
+
}
|
|
1701
|
+
await appendToOutbox("progress", {
|
|
1702
|
+
id,
|
|
1703
|
+
type: "progress",
|
|
1704
|
+
timestamp: now,
|
|
1705
|
+
payload: normalizedPayload,
|
|
1706
|
+
activityItem,
|
|
1707
|
+
});
|
|
1708
|
+
return text(`Activity saved locally: ${payload.message} [${normalizedPayload.phase}${payload.progress_pct != null ? ` ${payload.progress_pct}%` : ""}] (will sync when connected)`);
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
async function applyChangesetWithFallback(source, payload) {
|
|
1712
|
+
const context = resolveReportingContext(payload);
|
|
1713
|
+
if (!context.ok) {
|
|
1714
|
+
return text(`❌ ${context.error}`);
|
|
1715
|
+
}
|
|
1716
|
+
if (!Array.isArray(payload.operations) || payload.operations.length === 0) {
|
|
1717
|
+
return text("❌ operations must contain at least one change");
|
|
1718
|
+
}
|
|
1719
|
+
const idempotencyKey = pickNonEmptyString(payload.idempotency_key) ??
|
|
1720
|
+
`${source}:${Date.now()}:${randomUUID().slice(0, 8)}`;
|
|
1721
|
+
const requestPayload = {
|
|
1722
|
+
initiative_id: context.value.initiativeId,
|
|
1723
|
+
run_id: context.value.runId,
|
|
1724
|
+
correlation_id: context.value.correlationId,
|
|
1725
|
+
source_client: context.value.sourceClient,
|
|
1726
|
+
idempotency_key: idempotencyKey,
|
|
1727
|
+
operations: payload.operations,
|
|
1728
|
+
};
|
|
1729
|
+
const now = new Date().toISOString();
|
|
1730
|
+
const id = `changeset:${randomUUID().slice(0, 8)}`;
|
|
1731
|
+
const { agentId, agentName } = deriveAgentIdentity();
|
|
1732
|
+
const activityItem = {
|
|
1733
|
+
id,
|
|
1734
|
+
type: "milestone_completed",
|
|
1735
|
+
title: "Changeset queued",
|
|
1736
|
+
description: `${payload.operations.length} operation${payload.operations.length === 1 ? "" : "s"}`,
|
|
1737
|
+
agentId,
|
|
1738
|
+
agentName,
|
|
1739
|
+
requesterAgentId: agentId,
|
|
1740
|
+
requesterAgentName: agentName,
|
|
1741
|
+
executorAgentId: agentId,
|
|
1742
|
+
executorAgentName: agentName,
|
|
1743
|
+
runId: context.value.runId ?? null,
|
|
1744
|
+
initiativeId: context.value.initiativeId,
|
|
1745
|
+
timestamp: now,
|
|
1746
|
+
phase: "review",
|
|
1747
|
+
summary: `${payload.operations.length} operation${payload.operations.length === 1 ? "" : "s"}`,
|
|
1748
|
+
metadata: withProvenanceMetadata({
|
|
1749
|
+
source,
|
|
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 } : {}),
|
|
1755
|
+
}),
|
|
1756
|
+
};
|
|
1757
|
+
try {
|
|
1758
|
+
const result = await client.applyChangeset(requestPayload);
|
|
1759
|
+
return text(`Changeset ${result.replayed ? "replayed" : "applied"}: ${result.applied_count} op${result.applied_count === 1 ? "" : "s"} (run ${result.run_id.slice(0, 8)}...)`);
|
|
1760
|
+
}
|
|
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
|
+
}
|
|
1779
|
+
await appendToOutbox("decisions", {
|
|
1780
|
+
id,
|
|
1781
|
+
type: "changeset",
|
|
1782
|
+
timestamp: now,
|
|
1783
|
+
payload: requestPayload,
|
|
1784
|
+
activityItem,
|
|
1785
|
+
});
|
|
1786
|
+
return text(`Changeset saved locally (${payload.operations.length} op${payload.operations.length === 1 ? "" : "s"}) (will sync when connected)`);
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
// --- orgx_emit_activity ---
|
|
1790
|
+
registerMcpTool({
|
|
1791
|
+
name: "orgx_emit_activity",
|
|
1792
|
+
description: "Emit append-only OrgX activity telemetry (launch reporting contract primary write tool).",
|
|
1793
|
+
parameters: {
|
|
1794
|
+
type: "object",
|
|
1795
|
+
properties: {
|
|
1796
|
+
initiative_id: {
|
|
1797
|
+
type: "string",
|
|
1798
|
+
description: "Initiative UUID (required unless ORGX_INITIATIVE_ID is set)",
|
|
1799
|
+
},
|
|
1800
|
+
message: {
|
|
1801
|
+
type: "string",
|
|
1802
|
+
description: "Human-readable activity update",
|
|
1803
|
+
},
|
|
1804
|
+
run_id: {
|
|
1805
|
+
type: "string",
|
|
1806
|
+
description: "Optional run UUID",
|
|
1807
|
+
},
|
|
1808
|
+
correlation_id: {
|
|
1809
|
+
type: "string",
|
|
1810
|
+
description: "Required when run_id is omitted",
|
|
1811
|
+
},
|
|
1812
|
+
source_client: {
|
|
1813
|
+
type: "string",
|
|
1814
|
+
enum: ["openclaw", "codex", "claude-code", "api"],
|
|
1815
|
+
description: "Required when run_id is omitted",
|
|
1816
|
+
},
|
|
1817
|
+
phase: {
|
|
1818
|
+
type: "string",
|
|
1819
|
+
enum: ["intent", "execution", "blocked", "review", "handoff", "completed"],
|
|
1820
|
+
description: "Reporting phase",
|
|
1821
|
+
},
|
|
1822
|
+
progress_pct: {
|
|
1823
|
+
type: "number",
|
|
1824
|
+
minimum: 0,
|
|
1825
|
+
maximum: 100,
|
|
1826
|
+
description: "Optional progress percentage",
|
|
1827
|
+
},
|
|
1828
|
+
level: {
|
|
1829
|
+
type: "string",
|
|
1830
|
+
enum: ["info", "warn", "error"],
|
|
1831
|
+
description: "Optional level (default info)",
|
|
1832
|
+
},
|
|
1833
|
+
next_step: {
|
|
1834
|
+
type: "string",
|
|
1835
|
+
description: "Optional next step",
|
|
1836
|
+
},
|
|
1837
|
+
metadata: {
|
|
1838
|
+
type: "object",
|
|
1839
|
+
description: "Optional structured metadata",
|
|
1840
|
+
},
|
|
1841
|
+
},
|
|
1842
|
+
required: ["message"],
|
|
1843
|
+
additionalProperties: false,
|
|
1844
|
+
},
|
|
1845
|
+
async execute(_callId, params = { message: "" }) {
|
|
1846
|
+
return emitActivityWithFallback("orgx_emit_activity", params);
|
|
1847
|
+
},
|
|
1848
|
+
}, { optional: true });
|
|
1849
|
+
// --- orgx_apply_changeset ---
|
|
1850
|
+
registerMcpTool({
|
|
1851
|
+
name: "orgx_apply_changeset",
|
|
1852
|
+
description: "Apply an idempotent transactional OrgX changeset (launch reporting contract primary mutation tool).",
|
|
1853
|
+
parameters: {
|
|
1854
|
+
type: "object",
|
|
1855
|
+
properties: {
|
|
1856
|
+
initiative_id: {
|
|
1857
|
+
type: "string",
|
|
1858
|
+
description: "Initiative UUID (required unless ORGX_INITIATIVE_ID is set)",
|
|
1859
|
+
},
|
|
1860
|
+
idempotency_key: {
|
|
1861
|
+
type: "string",
|
|
1862
|
+
description: "Idempotency key (<=120 chars). Auto-generated if omitted.",
|
|
1863
|
+
},
|
|
1864
|
+
operations: {
|
|
1865
|
+
type: "array",
|
|
1866
|
+
minItems: 1,
|
|
1867
|
+
maxItems: 25,
|
|
1868
|
+
description: "Changeset operations (task.create, task.update, milestone.update, decision.create)",
|
|
1869
|
+
items: { type: "object" },
|
|
1870
|
+
},
|
|
1871
|
+
run_id: {
|
|
1872
|
+
type: "string",
|
|
1873
|
+
description: "Optional run UUID",
|
|
1874
|
+
},
|
|
1875
|
+
correlation_id: {
|
|
1876
|
+
type: "string",
|
|
1877
|
+
description: "Required when run_id is omitted",
|
|
1878
|
+
},
|
|
1879
|
+
source_client: {
|
|
1880
|
+
type: "string",
|
|
1881
|
+
enum: ["openclaw", "codex", "claude-code", "api"],
|
|
1882
|
+
description: "Required when run_id is omitted",
|
|
1883
|
+
},
|
|
1884
|
+
},
|
|
1885
|
+
required: ["operations"],
|
|
1886
|
+
additionalProperties: false,
|
|
1887
|
+
},
|
|
1888
|
+
async execute(_callId, params = { operations: [] }) {
|
|
1889
|
+
return applyChangesetWithFallback("orgx_apply_changeset", params);
|
|
1890
|
+
},
|
|
1891
|
+
}, { optional: true });
|
|
1892
|
+
// --- orgx_report_progress (alias -> orgx_emit_activity) ---
|
|
1893
|
+
registerMcpTool({
|
|
1894
|
+
name: "orgx_report_progress",
|
|
1895
|
+
description: "Alias for orgx_emit_activity. Report progress at key milestones so the team can track your work.",
|
|
1896
|
+
parameters: {
|
|
1897
|
+
type: "object",
|
|
1898
|
+
properties: {
|
|
1899
|
+
initiative_id: {
|
|
1900
|
+
type: "string",
|
|
1901
|
+
description: "Initiative UUID (required unless ORGX_INITIATIVE_ID is set)",
|
|
1902
|
+
},
|
|
1903
|
+
run_id: {
|
|
1904
|
+
type: "string",
|
|
1905
|
+
description: "Optional run UUID",
|
|
1906
|
+
},
|
|
1907
|
+
correlation_id: {
|
|
1908
|
+
type: "string",
|
|
1909
|
+
description: "Required when run_id is omitted",
|
|
1910
|
+
},
|
|
1911
|
+
source_client: {
|
|
1912
|
+
type: "string",
|
|
1913
|
+
enum: ["openclaw", "codex", "claude-code", "api"],
|
|
1914
|
+
},
|
|
1915
|
+
summary: {
|
|
1916
|
+
type: "string",
|
|
1917
|
+
description: "What was accomplished (1-2 sentences, human-readable)",
|
|
1918
|
+
},
|
|
1919
|
+
phase: {
|
|
1920
|
+
type: "string",
|
|
1921
|
+
enum: ["researching", "implementing", "testing", "reviewing", "blocked"],
|
|
1922
|
+
description: "Current work phase",
|
|
1923
|
+
},
|
|
1924
|
+
progress_pct: {
|
|
1925
|
+
type: "number",
|
|
1926
|
+
description: "Progress percentage (0-100)",
|
|
1927
|
+
minimum: 0,
|
|
1928
|
+
maximum: 100,
|
|
1929
|
+
},
|
|
1930
|
+
next_step: {
|
|
1931
|
+
type: "string",
|
|
1932
|
+
description: "What you plan to do next",
|
|
1933
|
+
},
|
|
1934
|
+
},
|
|
1935
|
+
required: ["summary", "phase"],
|
|
1936
|
+
additionalProperties: false,
|
|
1937
|
+
},
|
|
1938
|
+
async execute(_callId, params = { summary: "", phase: "implementing" }) {
|
|
1939
|
+
return emitActivityWithFallback("orgx_report_progress", {
|
|
1940
|
+
initiative_id: params.initiative_id,
|
|
1941
|
+
run_id: params.run_id,
|
|
1942
|
+
correlation_id: params.correlation_id,
|
|
1943
|
+
source_client: params.source_client,
|
|
1944
|
+
message: params.summary,
|
|
1945
|
+
phase: toReportingPhase(params.phase, params.progress_pct),
|
|
1946
|
+
progress_pct: params.progress_pct,
|
|
1947
|
+
next_step: params.next_step,
|
|
1948
|
+
level: params.phase === "blocked" ? "warn" : "info",
|
|
1949
|
+
metadata: {
|
|
1950
|
+
legacy_phase: params.phase,
|
|
1951
|
+
},
|
|
1952
|
+
});
|
|
1953
|
+
},
|
|
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 });
|
|
2018
|
+
// --- orgx_request_decision (alias -> orgx_apply_changeset decision.create) ---
|
|
2019
|
+
registerMcpTool({
|
|
2020
|
+
name: "orgx_request_decision",
|
|
2021
|
+
description: "Alias for orgx_apply_changeset with decision.create. Request a human decision before proceeding.",
|
|
2022
|
+
parameters: {
|
|
2023
|
+
type: "object",
|
|
2024
|
+
properties: {
|
|
2025
|
+
initiative_id: {
|
|
2026
|
+
type: "string",
|
|
2027
|
+
description: "Initiative UUID (required unless ORGX_INITIATIVE_ID is set)",
|
|
2028
|
+
},
|
|
2029
|
+
run_id: {
|
|
2030
|
+
type: "string",
|
|
2031
|
+
description: "Optional run UUID",
|
|
2032
|
+
},
|
|
2033
|
+
correlation_id: {
|
|
2034
|
+
type: "string",
|
|
2035
|
+
description: "Required when run_id is omitted",
|
|
2036
|
+
},
|
|
2037
|
+
source_client: {
|
|
2038
|
+
type: "string",
|
|
2039
|
+
enum: ["openclaw", "codex", "claude-code", "api"],
|
|
2040
|
+
},
|
|
2041
|
+
question: {
|
|
2042
|
+
type: "string",
|
|
2043
|
+
description: "The decision question (e.g., 'Deploy to production?')",
|
|
2044
|
+
},
|
|
2045
|
+
context: {
|
|
2046
|
+
type: "string",
|
|
2047
|
+
description: "Background context to help the human decide",
|
|
2048
|
+
},
|
|
2049
|
+
options: {
|
|
2050
|
+
type: "array",
|
|
2051
|
+
items: { type: "string" },
|
|
2052
|
+
description: "Available choices (e.g., ['Yes, deploy now', 'Wait for more testing', 'Cancel'])",
|
|
2053
|
+
},
|
|
2054
|
+
urgency: {
|
|
2055
|
+
type: "string",
|
|
2056
|
+
enum: ["low", "medium", "high", "urgent"],
|
|
2057
|
+
description: "How urgent this decision is",
|
|
2058
|
+
},
|
|
2059
|
+
blocking: {
|
|
2060
|
+
type: "boolean",
|
|
2061
|
+
description: "Whether work should pause until this is decided (default: true)",
|
|
2062
|
+
},
|
|
2063
|
+
},
|
|
2064
|
+
required: ["question", "urgency"],
|
|
2065
|
+
additionalProperties: false,
|
|
2066
|
+
},
|
|
2067
|
+
async execute(_callId, params = { question: "", urgency: "medium" }) {
|
|
2068
|
+
const requestId = `decision:${randomUUID().slice(0, 8)}`;
|
|
2069
|
+
const changesetResult = await applyChangesetWithFallback("orgx_request_decision", {
|
|
2070
|
+
initiative_id: params.initiative_id,
|
|
2071
|
+
run_id: params.run_id,
|
|
2072
|
+
correlation_id: params.correlation_id,
|
|
2073
|
+
source_client: params.source_client,
|
|
2074
|
+
idempotency_key: `decision:${requestId}`,
|
|
2075
|
+
operations: [
|
|
2076
|
+
{
|
|
2077
|
+
op: "decision.create",
|
|
2078
|
+
title: params.question,
|
|
2079
|
+
summary: params.context,
|
|
2080
|
+
urgency: params.urgency,
|
|
2081
|
+
options: params.options,
|
|
2082
|
+
blocking: params.blocking ?? true,
|
|
2083
|
+
},
|
|
2084
|
+
],
|
|
2085
|
+
});
|
|
2086
|
+
await emitActivityWithFallback("orgx_request_decision", {
|
|
2087
|
+
initiative_id: params.initiative_id,
|
|
2088
|
+
run_id: params.run_id,
|
|
2089
|
+
correlation_id: params.correlation_id,
|
|
2090
|
+
source_client: params.source_client,
|
|
2091
|
+
message: `Decision requested: ${params.question}`,
|
|
2092
|
+
phase: "review",
|
|
2093
|
+
level: "info",
|
|
2094
|
+
metadata: {
|
|
2095
|
+
urgency: params.urgency,
|
|
2096
|
+
blocking: params.blocking ?? true,
|
|
2097
|
+
options: params.options ?? [],
|
|
2098
|
+
},
|
|
2099
|
+
});
|
|
2100
|
+
return changesetResult;
|
|
2101
|
+
},
|
|
2102
|
+
}, { optional: true });
|
|
2103
|
+
// --- orgx_register_artifact ---
|
|
2104
|
+
registerMcpTool({
|
|
2105
|
+
name: "orgx_register_artifact",
|
|
2106
|
+
description: "Register a work output (PR, document, config change, report, etc.) as a work_artifact in OrgX. Makes it visible in the dashboard activity timeline and entity detail modals.",
|
|
2107
|
+
parameters: {
|
|
2108
|
+
type: "object",
|
|
2109
|
+
properties: {
|
|
2110
|
+
initiative_id: {
|
|
2111
|
+
type: "string",
|
|
2112
|
+
description: "Convenience: initiative UUID. Used as entity_type='initiative', entity_id=<this> when entity_type/entity_id are not provided.",
|
|
2113
|
+
},
|
|
2114
|
+
entity_type: {
|
|
2115
|
+
type: "string",
|
|
2116
|
+
enum: ["initiative", "milestone", "task", "decision", "project"],
|
|
2117
|
+
description: "The type of entity this artifact is attached to",
|
|
2118
|
+
},
|
|
2119
|
+
entity_id: {
|
|
2120
|
+
type: "string",
|
|
2121
|
+
description: "UUID of the entity this artifact is attached to",
|
|
2122
|
+
},
|
|
2123
|
+
name: {
|
|
2124
|
+
type: "string",
|
|
2125
|
+
description: "Human-readable artifact name (e.g., 'PR #107: Fix build size')",
|
|
2126
|
+
},
|
|
2127
|
+
artifact_type: {
|
|
2128
|
+
type: "string",
|
|
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.",
|
|
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
|
+
},
|
|
2137
|
+
description: {
|
|
2138
|
+
type: "string",
|
|
2139
|
+
description: "What this artifact is and why it matters",
|
|
2140
|
+
},
|
|
2141
|
+
url: {
|
|
2142
|
+
type: "string",
|
|
2143
|
+
description: "External link to the artifact (PR URL, file path, etc.)",
|
|
2144
|
+
},
|
|
2145
|
+
content: {
|
|
2146
|
+
type: "string",
|
|
2147
|
+
description: "Inline preview content (markdown/text). At least one of url or content is required.",
|
|
2148
|
+
},
|
|
2149
|
+
},
|
|
2150
|
+
required: ["name", "artifact_type"],
|
|
2151
|
+
additionalProperties: false,
|
|
2152
|
+
},
|
|
2153
|
+
async execute(_callId, params = { name: "", artifact_type: "other" }) {
|
|
2154
|
+
const now = new Date().toISOString();
|
|
2155
|
+
const id = `artifact:${randomUUID().slice(0, 8)}`;
|
|
2156
|
+
// Resolve entity association: explicit entity_type+entity_id > initiative_id > inferred
|
|
2157
|
+
let resolvedEntityType = null;
|
|
2158
|
+
let resolvedEntityId = null;
|
|
2159
|
+
if (params.entity_type && isUuid(params.entity_id)) {
|
|
2160
|
+
resolvedEntityType = params.entity_type;
|
|
2161
|
+
resolvedEntityId = params.entity_id;
|
|
2162
|
+
}
|
|
2163
|
+
else if (isUuid(params.initiative_id)) {
|
|
2164
|
+
resolvedEntityType = "initiative";
|
|
2165
|
+
resolvedEntityId = params.initiative_id;
|
|
2166
|
+
}
|
|
2167
|
+
else {
|
|
2168
|
+
const inferred = inferReportingInitiativeId(params);
|
|
2169
|
+
if (inferred) {
|
|
2170
|
+
resolvedEntityType = "initiative";
|
|
2171
|
+
resolvedEntityId = inferred;
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
if (!resolvedEntityType || !resolvedEntityId) {
|
|
2175
|
+
return text("❌ Cannot register artifact: provide entity_type + entity_id, or initiative_id, so the artifact can be attached to an entity.");
|
|
2176
|
+
}
|
|
2177
|
+
if (!params.url && !params.content) {
|
|
2178
|
+
return text("❌ Cannot register artifact: provide at least one of url or content.");
|
|
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;
|
|
2187
|
+
const baseUrl = client.getBaseUrl();
|
|
2188
|
+
const artifactId = randomUUID();
|
|
2189
|
+
const { agentId, agentName } = deriveAgentIdentity(params);
|
|
2190
|
+
const activityItem = {
|
|
2191
|
+
id,
|
|
2192
|
+
type: "artifact_created",
|
|
2193
|
+
title: params.name,
|
|
2194
|
+
description: params.description ?? null,
|
|
2195
|
+
agentId,
|
|
2196
|
+
agentName,
|
|
2197
|
+
requesterAgentId: agentId,
|
|
2198
|
+
requesterAgentName: agentName,
|
|
2199
|
+
executorAgentId: agentId,
|
|
2200
|
+
executorAgentName: agentName,
|
|
2201
|
+
runId: null,
|
|
2202
|
+
initiativeId: resolvedEntityType === "initiative" ? resolvedEntityId : null,
|
|
2203
|
+
timestamp: now,
|
|
2204
|
+
summary: params.url ?? null,
|
|
2205
|
+
metadata: withProvenanceMetadata({
|
|
2206
|
+
source: "orgx_register_artifact",
|
|
2207
|
+
artifact_type: params.artifact_type,
|
|
2208
|
+
confidence_score: confidenceScore,
|
|
2209
|
+
url: params.url,
|
|
2210
|
+
entity_type: resolvedEntityType,
|
|
2211
|
+
entity_id: resolvedEntityId,
|
|
2212
|
+
...(agentId ? { agent_id: agentId } : {}),
|
|
2213
|
+
...(agentName ? { agent_name: agentName } : {}),
|
|
2214
|
+
}),
|
|
2215
|
+
};
|
|
2216
|
+
try {
|
|
2217
|
+
const result = await registerArtifact(client, baseUrl, {
|
|
2218
|
+
artifact_id: artifactId,
|
|
2219
|
+
entity_type: resolvedEntityType,
|
|
2220
|
+
entity_id: resolvedEntityId,
|
|
2221
|
+
name: params.name,
|
|
2222
|
+
artifact_type: params.artifact_type,
|
|
2223
|
+
confidence_score: confidenceScore,
|
|
2224
|
+
description: params.description ?? null,
|
|
2225
|
+
external_url: params.url ?? null,
|
|
2226
|
+
preview_markdown: params.content ?? null,
|
|
2227
|
+
status: "draft",
|
|
2228
|
+
metadata: {
|
|
2229
|
+
source: "orgx_register_artifact",
|
|
2230
|
+
artifact_id: artifactId,
|
|
2231
|
+
confidence_score: confidenceScore,
|
|
2232
|
+
},
|
|
2233
|
+
validate_persistence: true,
|
|
2234
|
+
});
|
|
2235
|
+
if (!result.ok) {
|
|
2236
|
+
throw new Error(result.persistence.last_error ?? "Artifact registration failed");
|
|
2237
|
+
}
|
|
2238
|
+
activityItem.metadata = withProvenanceMetadata({
|
|
2239
|
+
...activityItem.metadata,
|
|
2240
|
+
artifact_id: result.artifact_id,
|
|
2241
|
+
entity_type: resolvedEntityType,
|
|
2242
|
+
entity_id: resolvedEntityId,
|
|
2243
|
+
});
|
|
2244
|
+
return json(`Artifact registered: ${params.name} [${params.artifact_type}] → ${resolvedEntityType}/${resolvedEntityId} (id: ${result.artifact_id})`, result);
|
|
2245
|
+
}
|
|
2246
|
+
catch (firstError) {
|
|
2247
|
+
// Outbox fallback for offline/error scenarios
|
|
2248
|
+
await appendToOutbox("artifacts", {
|
|
2249
|
+
id,
|
|
2250
|
+
type: "artifact",
|
|
2251
|
+
timestamp: now,
|
|
2252
|
+
payload: {
|
|
2253
|
+
artifact_id: artifactId,
|
|
2254
|
+
name: params.name,
|
|
2255
|
+
artifact_type: params.artifact_type,
|
|
2256
|
+
confidence_score: confidenceScore,
|
|
2257
|
+
description: params.description,
|
|
2258
|
+
url: params.url,
|
|
2259
|
+
content: params.content,
|
|
2260
|
+
entity_type: resolvedEntityType,
|
|
2261
|
+
entity_id: resolvedEntityId,
|
|
2262
|
+
},
|
|
2263
|
+
activityItem,
|
|
2264
|
+
});
|
|
2265
|
+
return text(`Artifact saved locally: ${params.name} [${params.artifact_type}] (will sync when connected)`);
|
|
2266
|
+
}
|
|
2267
|
+
},
|
|
2268
|
+
}, { optional: true });
|
|
2269
|
+
return mcpToolRegistry;
|
|
2270
|
+
}
|