@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
package/dist/index.js
CHANGED
|
@@ -13,202 +13,39 @@
|
|
|
13
13
|
import { OrgXClient } from "./api.js";
|
|
14
14
|
import { createHttpHandler } from "./http-handler.js";
|
|
15
15
|
import { applyOrgxAgentSuitePlan, computeOrgxAgentSuitePlan } from "./agent-suite.js";
|
|
16
|
-
import {
|
|
16
|
+
import { autoAssignEntityForCreate as autoAssignEntityForCreateWithClient, } from "./entities/auto-assignment.js";
|
|
17
17
|
import { existsSync, readFileSync } from "node:fs";
|
|
18
18
|
import { join } from "node:path";
|
|
19
19
|
import { homedir } from "node:os";
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import { clearPersistedApiKey, loadAuthStore, resolveInstallationId, saveAuthStore, } from "./auth-store.js";
|
|
20
|
+
import { randomUUID } from "node:crypto";
|
|
21
|
+
import { clearPersistedApiKey, loadAuthStore, resolveInstallationId, } from "./auth-store.js";
|
|
23
22
|
import { clearPersistedSnapshot, readPersistedSnapshot, writePersistedSnapshot, } from "./snapshot-store.js";
|
|
24
|
-
import { appendToOutbox,
|
|
23
|
+
import { appendToOutbox, readOutboxSummary, } from "./outbox.js";
|
|
25
24
|
import { getAgentContext, readAgentContexts } from "./agent-context-store.js";
|
|
26
25
|
import { readAgentRuns, markAgentRunStopped } from "./agent-run-store.js";
|
|
27
|
-
import {
|
|
28
|
-
import { ensureGatewayWatchdog } from "./gateway-watchdog.js";
|
|
26
|
+
import { ensureGatewayWatchdog, stopGatewayWatchdog } from "./gateway-watchdog.js";
|
|
29
27
|
import { createMcpHttpHandler, } from "./mcp-http-handler.js";
|
|
30
|
-
import { autoConfigureDetectedMcpClients } from "./mcp-client-setup.js";
|
|
31
|
-
import { readOpenClawGatewayPort, readOpenClawSettingsSnapshot } from "./openclaw-settings.js";
|
|
32
28
|
import { posthogCapture } from "./telemetry/posthog.js";
|
|
33
|
-
import { readSkillPackState, refreshSkillPackState } from "./skill-pack-state.js";
|
|
29
|
+
import { readSkillPackState, refreshSkillPackState, rollbackSkillPackPolicy, updateSkillPackPolicy, } from "./skill-pack-state.js";
|
|
30
|
+
import { resolveConfig, resolveDocsUrl, resolveRuntimeUserId, } from "./config/resolution.js";
|
|
31
|
+
import { refreshResolvedConfig } from "./config/refresh.js";
|
|
32
|
+
import { applyRuntimeApiKey, buildManualKeyConnectUrl as buildManualKeyConnectUrlForBase, fetchOrgxJson as fetchOrgxJsonRequest, isAuthRequiredError, } from "./auth/flows.js";
|
|
33
|
+
import { registerOrgxCli } from "./cli/orgx.js";
|
|
34
|
+
import { instrumentPluginApi } from "./services/instrumentation.js";
|
|
35
|
+
import { registerSyncService } from "./services/background.js";
|
|
36
|
+
import { stopDetachedProcess } from "./http/helpers/openclaw-cli.js";
|
|
37
|
+
import { createOutboxReplayer } from "./sync/outbox-replay.js";
|
|
38
|
+
import { buildLocalAgentMirrorsFromSnapshot, buildLocalSyncAgentsFromRuns, } from "./sync/local-agent-telemetry.js";
|
|
39
|
+
import { registerCoreTools } from "./tools/core-tools.js";
|
|
40
|
+
import { stableHash } from "./hash-utils.js";
|
|
41
|
+
import { RETRO_ARTIFACT_SCHEMA_VERSION } from "./contracts/retro-schema.js";
|
|
42
|
+
import { buildRetroWithLlm } from "./retro/domain-templates.js";
|
|
43
|
+
import { computeRetroQualityRubricScore } from "./retro/quality-rubric.js";
|
|
34
44
|
export { OrgXClient } from "./api.js";
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
function resolveRuntimeUserId(apiKey, candidates) {
|
|
41
|
-
if (isUserScopedApiKey(apiKey)) {
|
|
42
|
-
// For oxk_ keys, the OrgX API ignores X-Orgx-User-Id, but we still keep a UUID
|
|
43
|
-
// around for created_by_id on certain entity writes (e.g., work_artifacts).
|
|
44
|
-
for (const candidate of candidates) {
|
|
45
|
-
if (typeof candidate !== "string")
|
|
46
|
-
continue;
|
|
47
|
-
const trimmed = candidate.trim();
|
|
48
|
-
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(trimmed)) {
|
|
49
|
-
return trimmed;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return "";
|
|
53
|
-
}
|
|
54
|
-
for (const candidate of candidates) {
|
|
55
|
-
if (typeof candidate === "string") {
|
|
56
|
-
const trimmed = candidate.trim();
|
|
57
|
-
if (trimmed.length > 0)
|
|
58
|
-
return trimmed;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return "";
|
|
62
|
-
}
|
|
63
|
-
function normalizeHost(value) {
|
|
64
|
-
return value.trim().toLowerCase().replace(/^\[|\]$/g, "");
|
|
65
|
-
}
|
|
66
|
-
function isLoopbackHostname(hostname) {
|
|
67
|
-
const normalized = normalizeHost(hostname);
|
|
68
|
-
return normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1";
|
|
69
|
-
}
|
|
70
|
-
function normalizeBaseUrl(raw) {
|
|
71
|
-
const candidate = raw?.trim() ?? "";
|
|
72
|
-
if (!candidate) {
|
|
73
|
-
return DEFAULT_BASE_URL;
|
|
74
|
-
}
|
|
75
|
-
try {
|
|
76
|
-
const parsed = new URL(candidate);
|
|
77
|
-
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
78
|
-
return DEFAULT_BASE_URL;
|
|
79
|
-
}
|
|
80
|
-
// Do not allow credential-bearing URLs.
|
|
81
|
-
if (parsed.username || parsed.password) {
|
|
82
|
-
return DEFAULT_BASE_URL;
|
|
83
|
-
}
|
|
84
|
-
// Plain HTTP is only allowed for local loopback development.
|
|
85
|
-
if (parsed.protocol === "http:" && !isLoopbackHostname(parsed.hostname)) {
|
|
86
|
-
return DEFAULT_BASE_URL;
|
|
87
|
-
}
|
|
88
|
-
parsed.search = "";
|
|
89
|
-
parsed.hash = "";
|
|
90
|
-
const normalizedPath = parsed.pathname.replace(/\/+$/, "");
|
|
91
|
-
parsed.pathname = normalizedPath;
|
|
92
|
-
const normalized = parsed.toString().replace(/\/+$/, "");
|
|
93
|
-
return normalized.length > 0 ? normalized : DEFAULT_BASE_URL;
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
return DEFAULT_BASE_URL;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
function readLegacyEnvValue(keyPattern) {
|
|
100
|
-
try {
|
|
101
|
-
const envPath = join(homedir(), "Code", "orgx", "orgx", ".env.local");
|
|
102
|
-
const envContent = readFileSync(envPath, "utf-8");
|
|
103
|
-
const match = envContent.match(keyPattern);
|
|
104
|
-
return match?.[1]?.trim() ?? "";
|
|
105
|
-
}
|
|
106
|
-
catch {
|
|
107
|
-
return "";
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
function readOpenClawOrgxConfig() {
|
|
111
|
-
try {
|
|
112
|
-
const configPath = join(homedir(), ".openclaw", "openclaw.json");
|
|
113
|
-
const raw = readFileSync(configPath, "utf8");
|
|
114
|
-
const parsed = JSON.parse(raw);
|
|
115
|
-
const plugins = parsed.plugins && typeof parsed.plugins === "object"
|
|
116
|
-
? parsed.plugins
|
|
117
|
-
: {};
|
|
118
|
-
const entries = plugins.entries && typeof plugins.entries === "object"
|
|
119
|
-
? plugins.entries
|
|
120
|
-
: {};
|
|
121
|
-
const orgx = entries.orgx && typeof entries.orgx === "object"
|
|
122
|
-
? entries.orgx
|
|
123
|
-
: {};
|
|
124
|
-
const config = orgx.config && typeof orgx.config === "object"
|
|
125
|
-
? orgx.config
|
|
126
|
-
: {};
|
|
127
|
-
const apiKey = typeof config.apiKey === "string" ? config.apiKey.trim() : "";
|
|
128
|
-
const userId = typeof config.userId === "string" ? config.userId.trim() : "";
|
|
129
|
-
const baseUrl = typeof config.baseUrl === "string" ? config.baseUrl.trim() : "";
|
|
130
|
-
return { apiKey, userId, baseUrl };
|
|
131
|
-
}
|
|
132
|
-
catch {
|
|
133
|
-
return { apiKey: "", userId: "", baseUrl: "" };
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
function resolveApiKey(pluginConf, persistedApiKey) {
|
|
137
|
-
if (pluginConf.apiKey && pluginConf.apiKey.trim().length > 0) {
|
|
138
|
-
return { value: pluginConf.apiKey.trim(), source: "config" };
|
|
139
|
-
}
|
|
140
|
-
if (process.env.ORGX_API_KEY && process.env.ORGX_API_KEY.trim().length > 0) {
|
|
141
|
-
return { value: process.env.ORGX_API_KEY.trim(), source: "environment" };
|
|
142
|
-
}
|
|
143
|
-
if (persistedApiKey && persistedApiKey.trim().length > 0) {
|
|
144
|
-
return { value: persistedApiKey.trim(), source: "persisted" };
|
|
145
|
-
}
|
|
146
|
-
const openclaw = readOpenClawOrgxConfig();
|
|
147
|
-
if (openclaw.apiKey) {
|
|
148
|
-
return { value: openclaw.apiKey, source: "openclaw-config-file" };
|
|
149
|
-
}
|
|
150
|
-
// For local dev convenience we read `ORGX_API_KEY` from `~/Code/orgx/orgx/.env.local`.
|
|
151
|
-
// Do not auto-consume `ORGX_SERVICE_KEY` because service keys often require `X-Orgx-User-Id`,
|
|
152
|
-
// and the dashboard/client flows are intended to run on user-scoped keys (`oxk_...`).
|
|
153
|
-
const legacy = readLegacyEnvValue(/^ORGX_API_KEY=["']?([^"'\n]+)["']?$/m);
|
|
154
|
-
if (legacy) {
|
|
155
|
-
return { value: legacy, source: "legacy-dev" };
|
|
156
|
-
}
|
|
157
|
-
return { value: "", source: "none" };
|
|
158
|
-
}
|
|
159
|
-
function resolvePluginVersion() {
|
|
160
|
-
try {
|
|
161
|
-
const packagePath = fileURLToPath(new URL("../package.json", import.meta.url));
|
|
162
|
-
const parsed = JSON.parse(readFileSync(packagePath, "utf8"));
|
|
163
|
-
return parsed.version && parsed.version.trim().length > 0
|
|
164
|
-
? parsed.version
|
|
165
|
-
: "dev";
|
|
166
|
-
}
|
|
167
|
-
catch {
|
|
168
|
-
return "dev";
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
function resolveDocsUrl(baseUrl) {
|
|
172
|
-
const normalized = baseUrl.replace(/\/+$/, "");
|
|
173
|
-
try {
|
|
174
|
-
const parsed = new URL(normalized);
|
|
175
|
-
if (isLoopbackHostname(parsed.hostname)) {
|
|
176
|
-
return `${normalized}/docs/mintlify/guides/openclaw-plugin-setup`;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
catch {
|
|
180
|
-
return DEFAULT_DOCS_URL;
|
|
181
|
-
}
|
|
182
|
-
return DEFAULT_DOCS_URL;
|
|
183
|
-
}
|
|
184
|
-
function resolveConfig(api, input) {
|
|
185
|
-
const pluginConf = api.config?.plugins?.entries?.orgx?.config ?? {};
|
|
186
|
-
const openclaw = readOpenClawOrgxConfig();
|
|
187
|
-
const apiKeyResolution = resolveApiKey(pluginConf, input.persistedApiKey);
|
|
188
|
-
const apiKey = apiKeyResolution.value;
|
|
189
|
-
// Resolve user ID for X-Orgx-User-Id header
|
|
190
|
-
const userId = resolveRuntimeUserId(apiKey, [
|
|
191
|
-
pluginConf.userId,
|
|
192
|
-
process.env.ORGX_USER_ID,
|
|
193
|
-
input.persistedUserId,
|
|
194
|
-
openclaw.userId,
|
|
195
|
-
readLegacyEnvValue(/^ORGX_USER_ID=["']?([^"'\n]+)["']?$/m),
|
|
196
|
-
]);
|
|
197
|
-
const baseUrl = normalizeBaseUrl(pluginConf.baseUrl || process.env.ORGX_BASE_URL || openclaw.baseUrl || DEFAULT_BASE_URL);
|
|
198
|
-
return {
|
|
199
|
-
apiKey,
|
|
200
|
-
userId,
|
|
201
|
-
baseUrl,
|
|
202
|
-
syncIntervalMs: pluginConf.syncIntervalMs ?? 300_000,
|
|
203
|
-
enabled: pluginConf.enabled ?? true,
|
|
204
|
-
autoInstallAgentSuiteOnConnect: pluginConf.autoInstallAgentSuiteOnConnect ?? true,
|
|
205
|
-
dashboardEnabled: pluginConf.dashboardEnabled ?? true,
|
|
206
|
-
installationId: input.installationId,
|
|
207
|
-
pluginVersion: resolvePluginVersion(),
|
|
208
|
-
docsUrl: resolveDocsUrl(baseUrl),
|
|
209
|
-
apiKeySource: apiKeyResolution.source,
|
|
210
|
-
};
|
|
211
|
-
}
|
|
45
|
+
const ORGX_CANONICAL_BASE_URL = "https://www.useorgx.com";
|
|
46
|
+
// =============================================================================
|
|
47
|
+
// HELPERS
|
|
48
|
+
// =============================================================================
|
|
212
49
|
function text(s) {
|
|
213
50
|
return { content: [{ type: "text", text: s }] };
|
|
214
51
|
}
|
|
@@ -407,55 +244,17 @@ export default function register(api) {
|
|
|
407
244
|
const defaultReportingCorrelationId = pickNonEmptyString(process.env.ORGX_CORRELATION_ID) ??
|
|
408
245
|
`openclaw-${config.installationId}`;
|
|
409
246
|
function refreshConfigFromSources(input) {
|
|
410
|
-
const
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
});
|
|
422
|
-
const nextApiKey = allowApiKeyChanges ? next.apiKey : previousApiKey;
|
|
423
|
-
const nextUserId = allowApiKeyChanges ? next.userId : previousUserId;
|
|
424
|
-
const changed = nextApiKey !== previousApiKey ||
|
|
425
|
-
next.baseUrl !== previousBaseUrl ||
|
|
426
|
-
nextUserId !== previousUserId ||
|
|
427
|
-
next.docsUrl !== previousDocsUrl ||
|
|
428
|
-
next.apiKeySource !== previousKeySource;
|
|
429
|
-
if (!changed) {
|
|
430
|
-
return false;
|
|
431
|
-
}
|
|
432
|
-
if (allowApiKeyChanges) {
|
|
433
|
-
config.apiKey = nextApiKey;
|
|
434
|
-
config.userId = nextUserId;
|
|
435
|
-
config.apiKeySource = next.apiKeySource;
|
|
436
|
-
}
|
|
437
|
-
config.baseUrl = next.baseUrl;
|
|
438
|
-
config.docsUrl = next.docsUrl;
|
|
439
|
-
baseApiUrl = config.baseUrl.replace(/\/+$/, "");
|
|
440
|
-
client.setCredentials({
|
|
441
|
-
apiKey: config.apiKey,
|
|
442
|
-
userId: config.userId,
|
|
443
|
-
baseUrl: config.baseUrl,
|
|
444
|
-
});
|
|
445
|
-
// Keep onboarding state aligned with what's actually configured (without forcing a status transition).
|
|
446
|
-
updateOnboardingState({
|
|
447
|
-
hasApiKey: Boolean(config.apiKey),
|
|
448
|
-
keySource: config.apiKeySource,
|
|
449
|
-
docsUrl: config.docsUrl,
|
|
450
|
-
installationId: config.installationId,
|
|
451
|
-
});
|
|
452
|
-
api.log?.info?.("[orgx] Config refreshed", {
|
|
453
|
-
reason: input?.reason ?? "runtime_refresh",
|
|
454
|
-
baseUrl: config.baseUrl,
|
|
455
|
-
hasApiKey: Boolean(config.apiKey),
|
|
456
|
-
apiKeySource: config.apiKeySource,
|
|
457
|
-
});
|
|
458
|
-
return true;
|
|
247
|
+
const refreshed = refreshResolvedConfig({
|
|
248
|
+
api,
|
|
249
|
+
config,
|
|
250
|
+
loadAuthStore,
|
|
251
|
+
resolveConfig,
|
|
252
|
+
updateOnboardingState,
|
|
253
|
+
setCredentials: (credentials) => client.setCredentials(credentials),
|
|
254
|
+
logInfo: api.log?.info,
|
|
255
|
+
}, input);
|
|
256
|
+
baseApiUrl = refreshed.baseApiUrl;
|
|
257
|
+
return refreshed.changed;
|
|
459
258
|
}
|
|
460
259
|
function resolveReportingContext(input) {
|
|
461
260
|
let initiativeId = pickNonEmptyString(input.initiative_id, input.initiativeId, process.env.ORGX_INITIATIVE_ID);
|
|
@@ -505,9 +304,6 @@ export default function register(api) {
|
|
|
505
304
|
return err.message;
|
|
506
305
|
return typeof err === "string" ? err : "Unexpected error";
|
|
507
306
|
}
|
|
508
|
-
function stableHash(value) {
|
|
509
|
-
return createHash("sha256").update(value).digest("hex");
|
|
510
|
-
}
|
|
511
307
|
function isAuthFailure(err) {
|
|
512
308
|
const message = toErrorMessage(err).toLowerCase();
|
|
513
309
|
return (message.includes("401") ||
|
|
@@ -515,140 +311,12 @@ export default function register(api) {
|
|
|
515
311
|
message.includes("invalid_token") ||
|
|
516
312
|
message.includes("invalid api key"));
|
|
517
313
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
525
|
-
execute: async (callId, params) => {
|
|
526
|
-
const startedAt = Date.now();
|
|
527
|
-
void posthogCapture({
|
|
528
|
-
event: "openclaw_tool_called",
|
|
529
|
-
distinctId: config.installationId,
|
|
530
|
-
properties: {
|
|
531
|
-
tool_name: toolName,
|
|
532
|
-
tool_optional: optional,
|
|
533
|
-
call_id: callId,
|
|
534
|
-
plugin_version: config.pluginVersion,
|
|
535
|
-
},
|
|
536
|
-
}).catch(() => {
|
|
537
|
-
// best effort
|
|
538
|
-
});
|
|
539
|
-
try {
|
|
540
|
-
const result = await tool.execute(callId, params);
|
|
541
|
-
const durationMs = Date.now() - startedAt;
|
|
542
|
-
void posthogCapture({
|
|
543
|
-
event: "openclaw_tool_succeeded",
|
|
544
|
-
distinctId: config.installationId,
|
|
545
|
-
properties: {
|
|
546
|
-
tool_name: toolName,
|
|
547
|
-
tool_optional: optional,
|
|
548
|
-
call_id: callId,
|
|
549
|
-
duration_ms: durationMs,
|
|
550
|
-
plugin_version: config.pluginVersion,
|
|
551
|
-
},
|
|
552
|
-
}).catch(() => {
|
|
553
|
-
// best effort
|
|
554
|
-
});
|
|
555
|
-
return result;
|
|
556
|
-
}
|
|
557
|
-
catch (err) {
|
|
558
|
-
const durationMs = Date.now() - startedAt;
|
|
559
|
-
void posthogCapture({
|
|
560
|
-
event: "openclaw_tool_failed",
|
|
561
|
-
distinctId: config.installationId,
|
|
562
|
-
properties: {
|
|
563
|
-
tool_name: toolName,
|
|
564
|
-
tool_optional: optional,
|
|
565
|
-
call_id: callId,
|
|
566
|
-
duration_ms: durationMs,
|
|
567
|
-
plugin_version: config.pluginVersion,
|
|
568
|
-
error: toErrorMessage(err),
|
|
569
|
-
},
|
|
570
|
-
}).catch(() => {
|
|
571
|
-
// best effort
|
|
572
|
-
});
|
|
573
|
-
throw err;
|
|
574
|
-
}
|
|
575
|
-
},
|
|
576
|
-
}, options);
|
|
577
|
-
};
|
|
578
|
-
const registerService = api.registerService.bind(api);
|
|
579
|
-
api.registerService = (service) => {
|
|
580
|
-
registerService({
|
|
581
|
-
...service,
|
|
582
|
-
start: async () => {
|
|
583
|
-
const startedAt = Date.now();
|
|
584
|
-
try {
|
|
585
|
-
await service.start();
|
|
586
|
-
const durationMs = Date.now() - startedAt;
|
|
587
|
-
void posthogCapture({
|
|
588
|
-
event: "openclaw_service_started",
|
|
589
|
-
distinctId: config.installationId,
|
|
590
|
-
properties: {
|
|
591
|
-
service_id: service.id,
|
|
592
|
-
duration_ms: durationMs,
|
|
593
|
-
plugin_version: config.pluginVersion,
|
|
594
|
-
},
|
|
595
|
-
}).catch(() => {
|
|
596
|
-
// best effort
|
|
597
|
-
});
|
|
598
|
-
}
|
|
599
|
-
catch (err) {
|
|
600
|
-
const durationMs = Date.now() - startedAt;
|
|
601
|
-
void posthogCapture({
|
|
602
|
-
event: "openclaw_service_start_failed",
|
|
603
|
-
distinctId: config.installationId,
|
|
604
|
-
properties: {
|
|
605
|
-
service_id: service.id,
|
|
606
|
-
duration_ms: durationMs,
|
|
607
|
-
plugin_version: config.pluginVersion,
|
|
608
|
-
error: toErrorMessage(err),
|
|
609
|
-
},
|
|
610
|
-
}).catch(() => {
|
|
611
|
-
// best effort
|
|
612
|
-
});
|
|
613
|
-
throw err;
|
|
614
|
-
}
|
|
615
|
-
},
|
|
616
|
-
stop: async () => {
|
|
617
|
-
const startedAt = Date.now();
|
|
618
|
-
try {
|
|
619
|
-
await service.stop();
|
|
620
|
-
const durationMs = Date.now() - startedAt;
|
|
621
|
-
void posthogCapture({
|
|
622
|
-
event: "openclaw_service_stopped",
|
|
623
|
-
distinctId: config.installationId,
|
|
624
|
-
properties: {
|
|
625
|
-
service_id: service.id,
|
|
626
|
-
duration_ms: durationMs,
|
|
627
|
-
plugin_version: config.pluginVersion,
|
|
628
|
-
},
|
|
629
|
-
}).catch(() => {
|
|
630
|
-
// best effort
|
|
631
|
-
});
|
|
632
|
-
}
|
|
633
|
-
catch (err) {
|
|
634
|
-
const durationMs = Date.now() - startedAt;
|
|
635
|
-
void posthogCapture({
|
|
636
|
-
event: "openclaw_service_stop_failed",
|
|
637
|
-
distinctId: config.installationId,
|
|
638
|
-
properties: {
|
|
639
|
-
service_id: service.id,
|
|
640
|
-
duration_ms: durationMs,
|
|
641
|
-
plugin_version: config.pluginVersion,
|
|
642
|
-
error: toErrorMessage(err),
|
|
643
|
-
},
|
|
644
|
-
}).catch(() => {
|
|
645
|
-
// best effort
|
|
646
|
-
});
|
|
647
|
-
throw err;
|
|
648
|
-
}
|
|
649
|
-
},
|
|
650
|
-
});
|
|
651
|
-
};
|
|
314
|
+
instrumentPluginApi({
|
|
315
|
+
api,
|
|
316
|
+
installationId: config.installationId,
|
|
317
|
+
pluginVersion: config.pluginVersion,
|
|
318
|
+
toErrorMessage,
|
|
319
|
+
});
|
|
652
320
|
function clearPairingState() {
|
|
653
321
|
activePairing = null;
|
|
654
322
|
updateOnboardingState({
|
|
@@ -658,166 +326,55 @@ export default function register(api) {
|
|
|
658
326
|
pollIntervalMs: null,
|
|
659
327
|
});
|
|
660
328
|
}
|
|
661
|
-
function isAuthRequiredError(result) {
|
|
662
|
-
if (result.status !== 401) {
|
|
663
|
-
return false;
|
|
664
|
-
}
|
|
665
|
-
return /auth|unauthorized|token/i.test(result.error);
|
|
666
|
-
}
|
|
667
329
|
function buildManualKeyConnectUrl() {
|
|
668
|
-
|
|
669
|
-
// Deep-link into the Security section where API keys live.
|
|
670
|
-
return new URL("/settings#security", baseApiUrl).toString();
|
|
671
|
-
}
|
|
672
|
-
catch {
|
|
673
|
-
return "https://www.useorgx.com/settings#security";
|
|
674
|
-
}
|
|
330
|
+
return buildManualKeyConnectUrlForBase(baseApiUrl);
|
|
675
331
|
}
|
|
676
332
|
async function fetchOrgxJson(method, path, body, options) {
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
try {
|
|
686
|
-
response = await fetch(`${baseApiUrl}${path}`, {
|
|
687
|
-
method,
|
|
688
|
-
signal: controller.signal,
|
|
689
|
-
headers: {
|
|
690
|
-
Accept: "application/json",
|
|
691
|
-
"Content-Type": "application/json",
|
|
692
|
-
},
|
|
693
|
-
body: body ? JSON.stringify(body) : undefined,
|
|
694
|
-
});
|
|
695
|
-
rawText = await response.text().catch(() => "");
|
|
696
|
-
}
|
|
697
|
-
finally {
|
|
698
|
-
clearTimeout(timeout);
|
|
699
|
-
}
|
|
700
|
-
const payload = (() => {
|
|
701
|
-
if (!rawText)
|
|
702
|
-
return null;
|
|
703
|
-
try {
|
|
704
|
-
return JSON.parse(rawText);
|
|
705
|
-
}
|
|
706
|
-
catch {
|
|
707
|
-
return null;
|
|
708
|
-
}
|
|
709
|
-
})();
|
|
710
|
-
if (!response.ok) {
|
|
711
|
-
const rawError = payload?.error ?? payload?.message;
|
|
712
|
-
let errorMessage;
|
|
713
|
-
if (typeof rawError === "string") {
|
|
714
|
-
errorMessage = rawError;
|
|
715
|
-
}
|
|
716
|
-
else if (rawError &&
|
|
717
|
-
typeof rawError === "object" &&
|
|
718
|
-
"message" in rawError &&
|
|
719
|
-
typeof rawError.message === "string") {
|
|
720
|
-
errorMessage = rawError.message;
|
|
721
|
-
}
|
|
722
|
-
else if (rawText && rawText.trim().length > 0) {
|
|
723
|
-
// Avoid dumping HTML (Cloudflare / Next.js error pages) into UI; keep it short.
|
|
724
|
-
const sanitized = rawText
|
|
725
|
-
.replace(/\s+/g, " ")
|
|
726
|
-
.replace(/<[^>]+>/g, "")
|
|
727
|
-
.trim();
|
|
728
|
-
errorMessage = sanitized.length > 0 ? sanitized.slice(0, 180) : `OrgX request failed (${response.status})`;
|
|
729
|
-
}
|
|
730
|
-
else {
|
|
731
|
-
errorMessage = `OrgX request failed (${response.status})`;
|
|
732
|
-
}
|
|
733
|
-
const statusToken = `HTTP ${response.status}`;
|
|
734
|
-
if (response.status &&
|
|
735
|
-
!errorMessage.toLowerCase().includes(statusToken.toLowerCase()) &&
|
|
736
|
-
!errorMessage.includes(`(${response.status})`)) {
|
|
737
|
-
errorMessage = `${errorMessage} (HTTP ${response.status})`;
|
|
738
|
-
}
|
|
739
|
-
const debugParts = [];
|
|
740
|
-
const requestId = response.headers.get("x-request-id");
|
|
741
|
-
const vercelId = response.headers.get("x-vercel-id");
|
|
742
|
-
const cfRay = response.headers.get("cf-ray");
|
|
743
|
-
const clerkStatus = response.headers.get("x-clerk-auth-status");
|
|
744
|
-
const clerkReason = response.headers.get("x-clerk-auth-reason");
|
|
745
|
-
if (requestId)
|
|
746
|
-
debugParts.push(`req=${requestId}`);
|
|
747
|
-
if (vercelId && vercelId !== requestId)
|
|
748
|
-
debugParts.push(`vercel=${vercelId}`);
|
|
749
|
-
if (cfRay)
|
|
750
|
-
debugParts.push(`cf-ray=${cfRay}`);
|
|
751
|
-
if (clerkStatus)
|
|
752
|
-
debugParts.push(`clerk=${clerkStatus}`);
|
|
753
|
-
if (clerkReason)
|
|
754
|
-
debugParts.push(`clerk_reason=${clerkReason}`);
|
|
755
|
-
const debugSuffix = debugParts.length > 0 ? ` (${debugParts.join(", ")})` : "";
|
|
756
|
-
return {
|
|
757
|
-
ok: false,
|
|
758
|
-
status: response.status,
|
|
759
|
-
error: `${errorMessage}${debugSuffix}`,
|
|
760
|
-
};
|
|
761
|
-
}
|
|
762
|
-
if (payload?.data !== undefined) {
|
|
763
|
-
return { ok: true, data: payload.data };
|
|
764
|
-
}
|
|
765
|
-
if (payload !== null) {
|
|
766
|
-
return { ok: true, data: payload };
|
|
767
|
-
}
|
|
768
|
-
return { ok: true, data: rawText };
|
|
769
|
-
}
|
|
770
|
-
catch (err) {
|
|
771
|
-
const message = err &&
|
|
772
|
-
typeof err === "object" &&
|
|
773
|
-
"name" in err &&
|
|
774
|
-
err.name === "AbortError"
|
|
775
|
-
? `OrgX request timed out (method=${method}, path=${path})`
|
|
776
|
-
: toErrorMessage(err);
|
|
777
|
-
return { ok: false, status: 0, error: message };
|
|
778
|
-
}
|
|
333
|
+
return fetchOrgxJsonRequest({
|
|
334
|
+
baseApiUrl,
|
|
335
|
+
method,
|
|
336
|
+
path,
|
|
337
|
+
body,
|
|
338
|
+
options,
|
|
339
|
+
toErrorMessage,
|
|
340
|
+
});
|
|
779
341
|
}
|
|
780
|
-
function
|
|
781
|
-
const
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
342
|
+
function looksLikeTransientFetchFailure(message) {
|
|
343
|
+
const normalized = message.toLowerCase();
|
|
344
|
+
return (normalized.includes("fetch failed") ||
|
|
345
|
+
normalized.includes("networkerror") ||
|
|
346
|
+
normalized.includes("econnrefused") ||
|
|
347
|
+
normalized.includes("enotfound") ||
|
|
348
|
+
normalized.includes("eai_again"));
|
|
349
|
+
}
|
|
350
|
+
function applyRuntimeBaseUrl(nextBaseUrl) {
|
|
351
|
+
const normalized = nextBaseUrl.trim().replace(/\/+$/, "");
|
|
352
|
+
if (!normalized)
|
|
353
|
+
return;
|
|
354
|
+
if (config.baseUrl === normalized && baseApiUrl === normalized)
|
|
355
|
+
return;
|
|
356
|
+
config.baseUrl = normalized;
|
|
357
|
+
baseApiUrl = normalized;
|
|
785
358
|
client.setCredentials({
|
|
786
359
|
apiKey: config.apiKey,
|
|
787
360
|
userId: config.userId,
|
|
788
361
|
baseUrl: config.baseUrl,
|
|
789
362
|
});
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
363
|
+
updateOnboardingState({ docsUrl: resolveDocsUrl(config.baseUrl) });
|
|
364
|
+
}
|
|
365
|
+
function setRuntimeApiKey(input) {
|
|
366
|
+
applyRuntimeApiKey({
|
|
367
|
+
config,
|
|
368
|
+
apiKey: input.apiKey,
|
|
796
369
|
source: input.source,
|
|
370
|
+
workspaceName: input.workspaceName,
|
|
371
|
+
keyPrefix: input.keyPrefix,
|
|
372
|
+
userId: input.userId,
|
|
373
|
+
currentWorkspaceName: onboardingState.workspaceName,
|
|
374
|
+
updateOnboardingState,
|
|
375
|
+
setCredentials: (credentials) => client.setCredentials(credentials),
|
|
376
|
+
logger: api.log ?? {},
|
|
797
377
|
});
|
|
798
|
-
updateOnboardingState({
|
|
799
|
-
hasApiKey: true,
|
|
800
|
-
keySource: "persisted",
|
|
801
|
-
installationId: config.installationId,
|
|
802
|
-
workspaceName: input.workspaceName ?? onboardingState.workspaceName,
|
|
803
|
-
});
|
|
804
|
-
if (input.source === "browser_pairing" &&
|
|
805
|
-
process.env.ORGX_DISABLE_MCP_CLIENT_AUTOCONFIG !== "1") {
|
|
806
|
-
try {
|
|
807
|
-
const snapshot = readOpenClawSettingsSnapshot();
|
|
808
|
-
const port = readOpenClawGatewayPort(snapshot.raw);
|
|
809
|
-
const localMcpUrl = `http://127.0.0.1:${port}/orgx/mcp`;
|
|
810
|
-
void autoConfigureDetectedMcpClients({
|
|
811
|
-
localMcpUrl,
|
|
812
|
-
logger: api.log ?? {},
|
|
813
|
-
}).catch(() => {
|
|
814
|
-
// best effort
|
|
815
|
-
});
|
|
816
|
-
}
|
|
817
|
-
catch {
|
|
818
|
-
// best effort
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
378
|
}
|
|
822
379
|
// ---------------------------------------------------------------------------
|
|
823
380
|
// 1. Background Sync Service
|
|
@@ -825,6 +382,7 @@ export default function register(api) {
|
|
|
825
382
|
let syncTimer = null;
|
|
826
383
|
let syncInFlight = null;
|
|
827
384
|
let syncServiceRunning = false;
|
|
385
|
+
let localAgentMirrors = [];
|
|
828
386
|
let outboxReplayState = {
|
|
829
387
|
status: "idle",
|
|
830
388
|
lastReplayAttemptAt: null,
|
|
@@ -1133,6 +691,24 @@ export default function register(api) {
|
|
|
1133
691
|
const completedAt = stopped.stoppedAt ?? new Date().toISOString();
|
|
1134
692
|
const success = !summary.hadError;
|
|
1135
693
|
const correlationId = stopped.runId;
|
|
694
|
+
const retroTemplate = await buildRetroWithLlm({
|
|
695
|
+
agentId: stopped.agentId,
|
|
696
|
+
success,
|
|
697
|
+
taskId: stopped.taskId,
|
|
698
|
+
runId: stopped.runId,
|
|
699
|
+
errorMessage: summary.errorMessage,
|
|
700
|
+
});
|
|
701
|
+
const retroSummary = retroTemplate.summary;
|
|
702
|
+
const retroQuality = computeRetroQualityRubricScore({
|
|
703
|
+
success,
|
|
704
|
+
hadError: summary.hadError,
|
|
705
|
+
errorMessage: summary.errorMessage,
|
|
706
|
+
tokens: summary.tokens,
|
|
707
|
+
costUsd: summary.costUsd,
|
|
708
|
+
decisionsCount: retroTemplate.decisions.length,
|
|
709
|
+
followUpsCount: retroTemplate.followUps.length,
|
|
710
|
+
whatWentWrongCount: retroTemplate.whatWentWrong.length,
|
|
711
|
+
});
|
|
1136
712
|
const outcomePayload = {
|
|
1137
713
|
initiative_id: initiativeId,
|
|
1138
714
|
correlation_id: correlationId,
|
|
@@ -1154,6 +730,7 @@ export default function register(api) {
|
|
|
1154
730
|
},
|
|
1155
731
|
steps: [],
|
|
1156
732
|
success,
|
|
733
|
+
quality_score: retroQuality.score,
|
|
1157
734
|
human_interventions: 0,
|
|
1158
735
|
errors: summary.errorMessage ? [summary.errorMessage] : [],
|
|
1159
736
|
metadata: {
|
|
@@ -1168,9 +745,6 @@ export default function register(api) {
|
|
|
1168
745
|
? "task"
|
|
1169
746
|
: "initiative";
|
|
1170
747
|
const retroEntityId = stopped.taskId ?? initiativeId;
|
|
1171
|
-
const retroSummary = stopped.taskId
|
|
1172
|
-
? `OpenClaw ${success ? "completed" : "blocked"} task ${stopped.taskId}.`
|
|
1173
|
-
: `OpenClaw run ${success ? "completed" : "blocked"} (session ${stopped.runId}).`;
|
|
1174
748
|
const retroPayload = {
|
|
1175
749
|
initiative_id: initiativeId,
|
|
1176
750
|
correlation_id: correlationId,
|
|
@@ -1180,21 +754,12 @@ export default function register(api) {
|
|
|
1180
754
|
title: stopped.taskId ?? stopped.runId,
|
|
1181
755
|
idempotency_key: `retro:${stopped.runId}`,
|
|
1182
756
|
retro: {
|
|
757
|
+
schema_version: RETRO_ARTIFACT_SCHEMA_VERSION,
|
|
1183
758
|
summary: retroSummary,
|
|
1184
|
-
what_went_well:
|
|
1185
|
-
what_went_wrong:
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
decisions: [],
|
|
1189
|
-
follow_ups: success
|
|
1190
|
-
? []
|
|
1191
|
-
: [
|
|
1192
|
-
{
|
|
1193
|
-
title: "Investigate OpenClaw session failure and unblock task",
|
|
1194
|
-
priority: "p0",
|
|
1195
|
-
reason: summary.errorMessage ?? "Session ended with error.",
|
|
1196
|
-
},
|
|
1197
|
-
],
|
|
759
|
+
what_went_well: retroTemplate.whatWentWell,
|
|
760
|
+
what_went_wrong: retroTemplate.whatWentWrong,
|
|
761
|
+
decisions: retroTemplate.decisions,
|
|
762
|
+
follow_ups: retroTemplate.followUps,
|
|
1198
763
|
signals: {
|
|
1199
764
|
tokens: summary.tokens,
|
|
1200
765
|
cost_usd: summary.costUsd,
|
|
@@ -1203,8 +768,11 @@ export default function register(api) {
|
|
|
1203
768
|
session_id: stopped.runId,
|
|
1204
769
|
task_id: stopped.taskId,
|
|
1205
770
|
workstream_id: stopped.workstreamId,
|
|
771
|
+
domain: retroTemplate.domain,
|
|
1206
772
|
provider: stopped.provider,
|
|
1207
773
|
model: stopped.model,
|
|
774
|
+
quality_score: retroQuality.score,
|
|
775
|
+
quality_rubric_reasons: retroQuality.reasons,
|
|
1208
776
|
source: "openclaw_agent_run_reconcile",
|
|
1209
777
|
},
|
|
1210
778
|
},
|
|
@@ -1283,541 +851,75 @@ export default function register(api) {
|
|
|
1283
851
|
// best effort
|
|
1284
852
|
}
|
|
1285
853
|
}
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
854
|
+
const { flushOutboxQueues } = createOutboxReplayer({
|
|
855
|
+
client,
|
|
856
|
+
logger: api.log ?? {},
|
|
857
|
+
toErrorMessage,
|
|
858
|
+
stableHash,
|
|
859
|
+
resolveReportingContext,
|
|
860
|
+
pickStringField,
|
|
861
|
+
pickStringArrayField,
|
|
862
|
+
toReportingPhase,
|
|
863
|
+
parseRetroEntityType,
|
|
864
|
+
isUuid,
|
|
865
|
+
readOutboxReplayState: () => outboxReplayState,
|
|
866
|
+
writeOutboxReplayState: (next) => {
|
|
867
|
+
outboxReplayState = next;
|
|
868
|
+
},
|
|
869
|
+
});
|
|
870
|
+
async function doSync() {
|
|
871
|
+
if (syncInFlight) {
|
|
872
|
+
return syncInFlight;
|
|
1301
873
|
}
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
874
|
+
syncInFlight = (async () => {
|
|
875
|
+
if (!config.apiKey) {
|
|
876
|
+
refreshConfigFromSources({ reason: "sync_no_api_key" });
|
|
877
|
+
}
|
|
878
|
+
if (!config.apiKey) {
|
|
879
|
+
updateOnboardingState({
|
|
880
|
+
status: "idle",
|
|
881
|
+
hasApiKey: false,
|
|
882
|
+
connectionVerified: false,
|
|
883
|
+
nextAction: "connect",
|
|
1307
884
|
});
|
|
1308
885
|
return;
|
|
1309
886
|
}
|
|
1310
|
-
const context = resolveReportingContext(payload);
|
|
1311
|
-
if (!context.ok) {
|
|
1312
|
-
throw new Error(context.error);
|
|
1313
|
-
}
|
|
1314
|
-
const rawPhase = pickStringField(payload, "phase") ?? "implementing";
|
|
1315
|
-
const progressPct = typeof payload.progress_pct === "number"
|
|
1316
|
-
? payload.progress_pct
|
|
1317
|
-
: typeof payload.progressPct === "number"
|
|
1318
|
-
? payload.progressPct
|
|
1319
|
-
: undefined;
|
|
1320
|
-
const phase = rawPhase === "intent" ||
|
|
1321
|
-
rawPhase === "execution" ||
|
|
1322
|
-
rawPhase === "blocked" ||
|
|
1323
|
-
rawPhase === "review" ||
|
|
1324
|
-
rawPhase === "handoff" ||
|
|
1325
|
-
rawPhase === "completed"
|
|
1326
|
-
? rawPhase
|
|
1327
|
-
: toReportingPhase(rawPhase, progressPct);
|
|
1328
|
-
const metaRaw = payload.metadata;
|
|
1329
|
-
const meta = metaRaw && typeof metaRaw === "object" && !Array.isArray(metaRaw)
|
|
1330
|
-
? metaRaw
|
|
1331
|
-
: {};
|
|
1332
|
-
const baseMetadata = {
|
|
1333
|
-
...meta,
|
|
1334
|
-
source: "orgx_openclaw_outbox_replay",
|
|
1335
|
-
outbox_event_id: event.id,
|
|
1336
|
-
};
|
|
1337
|
-
let emitPayload = {
|
|
1338
|
-
initiative_id: context.value.initiativeId,
|
|
1339
|
-
run_id: context.value.runId,
|
|
1340
|
-
correlation_id: context.value.correlationId,
|
|
1341
|
-
source_client: context.value.sourceClient,
|
|
1342
|
-
message,
|
|
1343
|
-
phase,
|
|
1344
|
-
progress_pct: progressPct,
|
|
1345
|
-
level: pickStringField(payload, "level"),
|
|
1346
|
-
next_step: pickStringField(payload, "next_step") ??
|
|
1347
|
-
pickStringField(payload, "nextStep") ??
|
|
1348
|
-
undefined,
|
|
1349
|
-
metadata: baseMetadata,
|
|
1350
|
-
};
|
|
1351
|
-
// Locally-buffered progress events often store a local UUID in run_id. OrgX may reject
|
|
1352
|
-
// unknown run IDs on replay; prefer a deterministic non-UUID correlation key instead.
|
|
1353
|
-
if (emitPayload.run_id && !emitPayload.correlation_id) {
|
|
1354
|
-
const replayCorrelationId = `openclaw_run_${stableHash(emitPayload.run_id).slice(0, 24)}`;
|
|
1355
|
-
emitPayload = {
|
|
1356
|
-
...emitPayload,
|
|
1357
|
-
run_id: undefined,
|
|
1358
|
-
correlation_id: replayCorrelationId,
|
|
1359
|
-
metadata: {
|
|
1360
|
-
...(emitPayload.metadata ?? {}),
|
|
1361
|
-
replay_run_id_as_correlation: true,
|
|
1362
|
-
},
|
|
1363
|
-
};
|
|
1364
|
-
}
|
|
1365
887
|
try {
|
|
1366
|
-
await
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
const msg = toErrorMessage(err);
|
|
1374
|
-
if (emitPayload.run_id &&
|
|
1375
|
-
/^404\\b/.test(msg) &&
|
|
1376
|
-
/\\brun\\b/i.test(msg) &&
|
|
1377
|
-
/not found/i.test(msg)) {
|
|
1378
|
-
const replayCorrelationId = `openclaw_run_${stableHash(emitPayload.run_id).slice(0, 24)}`;
|
|
1379
|
-
await client.emitActivity({
|
|
1380
|
-
...emitPayload,
|
|
1381
|
-
run_id: undefined,
|
|
1382
|
-
correlation_id: replayCorrelationId,
|
|
1383
|
-
metadata: {
|
|
1384
|
-
...(emitPayload.metadata ?? {}),
|
|
1385
|
-
replay_run_id_as_correlation: true,
|
|
1386
|
-
},
|
|
888
|
+
await reconcileStoppedAgentRuns();
|
|
889
|
+
let snapshotError = null;
|
|
890
|
+
try {
|
|
891
|
+
const snapshot = await client.getOrgSnapshot();
|
|
892
|
+
updateCachedSnapshot(snapshot);
|
|
893
|
+
localAgentMirrors = buildLocalAgentMirrorsFromSnapshot({
|
|
894
|
+
agents: snapshot.agents,
|
|
1387
895
|
});
|
|
1388
896
|
}
|
|
1389
|
-
|
|
1390
|
-
|
|
897
|
+
catch (err) {
|
|
898
|
+
if (isAuthFailure(err)) {
|
|
899
|
+
throw err;
|
|
900
|
+
}
|
|
901
|
+
snapshotError = toErrorMessage(err);
|
|
902
|
+
api.log?.warn?.("[orgx] Snapshot sync failed (continuing)", {
|
|
903
|
+
error: snapshotError,
|
|
904
|
+
});
|
|
1391
905
|
}
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
if (event.type === "decision") {
|
|
1396
|
-
const question = pickStringField(payload, "question");
|
|
1397
|
-
if (!question) {
|
|
1398
|
-
api.log?.warn?.("[orgx] Dropping invalid decision outbox event", {
|
|
1399
|
-
eventId: event.id,
|
|
1400
|
-
});
|
|
1401
|
-
return;
|
|
1402
|
-
}
|
|
1403
|
-
const context = resolveReportingContext(payload);
|
|
1404
|
-
if (!context.ok) {
|
|
1405
|
-
throw new Error(context.error);
|
|
1406
|
-
}
|
|
1407
|
-
const runFields = normalizeRunFields({
|
|
1408
|
-
runId: context.value.runId,
|
|
1409
|
-
correlationId: context.value.correlationId,
|
|
1410
|
-
});
|
|
1411
|
-
// Payloads should include a stable idempotency_key when enqueued, but older
|
|
1412
|
-
// events may not. Derive a deterministic fallback so outbox replay won't
|
|
1413
|
-
// double-create the same remote decision.
|
|
1414
|
-
const fallbackKey = stableHash(JSON.stringify({
|
|
1415
|
-
t: "decision",
|
|
1416
|
-
initiative_id: context.value.initiativeId,
|
|
1417
|
-
run_id: context.value.runId ?? null,
|
|
1418
|
-
correlation_id: context.value.correlationId ?? null,
|
|
1419
|
-
question,
|
|
1420
|
-
})).slice(0, 24);
|
|
1421
|
-
const resolvedIdempotencyKey = pickStringField(payload, "idempotency_key") ??
|
|
1422
|
-
pickStringField(payload, "idempotencyKey") ??
|
|
1423
|
-
`openclaw:decision:${fallbackKey}`;
|
|
1424
|
-
await client.applyChangeset({
|
|
1425
|
-
initiative_id: context.value.initiativeId,
|
|
1426
|
-
run_id: runFields.run_id,
|
|
1427
|
-
correlation_id: runFields.correlation_id,
|
|
1428
|
-
source_client: context.value.sourceClient,
|
|
1429
|
-
idempotency_key: resolvedIdempotencyKey,
|
|
1430
|
-
operations: [
|
|
1431
|
-
{
|
|
1432
|
-
op: "decision.create",
|
|
1433
|
-
title: question,
|
|
1434
|
-
summary: pickStringField(payload, "context") ?? undefined,
|
|
1435
|
-
urgency: pickStringField(payload, "urgency") ?? "medium",
|
|
1436
|
-
options: pickStringArrayField(payload, "options"),
|
|
1437
|
-
blocking: typeof payload.blocking === "boolean" ? payload.blocking : true,
|
|
1438
|
-
},
|
|
1439
|
-
],
|
|
1440
|
-
});
|
|
1441
|
-
return;
|
|
1442
|
-
}
|
|
1443
|
-
if (event.type === "changeset") {
|
|
1444
|
-
const context = resolveReportingContext(payload);
|
|
1445
|
-
if (!context.ok) {
|
|
1446
|
-
throw new Error(context.error);
|
|
1447
|
-
}
|
|
1448
|
-
const runFields = normalizeRunFields({
|
|
1449
|
-
runId: context.value.runId,
|
|
1450
|
-
correlationId: context.value.correlationId,
|
|
1451
|
-
});
|
|
1452
|
-
const operations = Array.isArray(payload.operations)
|
|
1453
|
-
? payload.operations
|
|
1454
|
-
: [];
|
|
1455
|
-
if (operations.length === 0) {
|
|
1456
|
-
api.log?.warn?.("[orgx] Dropping invalid changeset outbox event", {
|
|
1457
|
-
eventId: event.id,
|
|
906
|
+
const localAgents = buildLocalSyncAgentsFromRuns({
|
|
907
|
+
...readAgentRuns(),
|
|
908
|
+
mirrors: localAgentMirrors,
|
|
1458
909
|
});
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
const taskId = typeof record.task_id === "string" ? record.task_id.trim() : "";
|
|
1472
|
-
const statusRaw = typeof record.status === "string" ? record.status.trim() : "";
|
|
1473
|
-
const normalized = statusRaw.toLowerCase().replace(/\s+/g, "_");
|
|
1474
|
-
const status = normalized === "completed" || normalized === "complete" || normalized === "finished"
|
|
1475
|
-
? "done"
|
|
1476
|
-
: normalized === "inprogress"
|
|
1477
|
-
? "in_progress"
|
|
1478
|
-
: normalized;
|
|
1479
|
-
if (!taskId || !status)
|
|
1480
|
-
return null;
|
|
1481
|
-
return { type: "task", id: taskId, status };
|
|
1482
|
-
}
|
|
1483
|
-
if (kind === "milestone.update") {
|
|
1484
|
-
const milestoneId = typeof record.milestone_id === "string" ? record.milestone_id.trim() : "";
|
|
1485
|
-
const statusRaw = typeof record.status === "string" ? record.status.trim() : "";
|
|
1486
|
-
const normalized = statusRaw.toLowerCase().replace(/\s+/g, "_");
|
|
1487
|
-
const status = normalized === "done" || normalized === "complete" || normalized === "finished"
|
|
1488
|
-
? "completed"
|
|
1489
|
-
: normalized === "inprogress"
|
|
1490
|
-
? "in_progress"
|
|
1491
|
-
: normalized === "todo" || normalized === "not_started" || normalized === "pending"
|
|
1492
|
-
? "planned"
|
|
1493
|
-
: normalized === "blocked" || normalized === "stuck"
|
|
1494
|
-
? "at_risk"
|
|
1495
|
-
: normalized;
|
|
1496
|
-
if (!milestoneId || !status)
|
|
1497
|
-
return null;
|
|
1498
|
-
return { type: "milestone", id: milestoneId, status };
|
|
1499
|
-
}
|
|
1500
|
-
return null;
|
|
1501
|
-
})
|
|
1502
|
-
.filter((item) => Boolean(item));
|
|
1503
|
-
if (statusOps.length === operations.length) {
|
|
1504
|
-
for (const op of statusOps) {
|
|
1505
|
-
await client.updateEntity(op.type, op.id, { status: op.status });
|
|
1506
|
-
}
|
|
1507
|
-
return;
|
|
1508
|
-
}
|
|
1509
|
-
// Payloads should include a stable idempotency_key when enqueued, but older
|
|
1510
|
-
// events may not. Derive a deterministic fallback so outbox replay won't
|
|
1511
|
-
// double-create the same remote change.
|
|
1512
|
-
const fallbackKey = stableHash(JSON.stringify({
|
|
1513
|
-
t: "changeset",
|
|
1514
|
-
initiative_id: context.value.initiativeId,
|
|
1515
|
-
run_id: context.value.runId ?? null,
|
|
1516
|
-
correlation_id: context.value.correlationId ?? null,
|
|
1517
|
-
operations,
|
|
1518
|
-
})).slice(0, 24);
|
|
1519
|
-
const resolvedIdempotencyKey = pickStringField(payload, "idempotency_key") ??
|
|
1520
|
-
pickStringField(payload, "idempotencyKey") ??
|
|
1521
|
-
`openclaw:changeset:${fallbackKey}`;
|
|
1522
|
-
await client.applyChangeset({
|
|
1523
|
-
initiative_id: context.value.initiativeId,
|
|
1524
|
-
run_id: runFields.run_id,
|
|
1525
|
-
correlation_id: runFields.correlation_id,
|
|
1526
|
-
source_client: context.value.sourceClient,
|
|
1527
|
-
idempotency_key: resolvedIdempotencyKey,
|
|
1528
|
-
operations,
|
|
1529
|
-
});
|
|
1530
|
-
return;
|
|
1531
|
-
}
|
|
1532
|
-
if (event.type === "outcome") {
|
|
1533
|
-
const context = resolveReportingContext(payload);
|
|
1534
|
-
if (!context.ok) {
|
|
1535
|
-
throw new Error(context.error);
|
|
1536
|
-
}
|
|
1537
|
-
const runFields = normalizeRunFields({
|
|
1538
|
-
runId: context.value.runId,
|
|
1539
|
-
correlationId: context.value.correlationId,
|
|
1540
|
-
});
|
|
1541
|
-
const executionId = pickStringField(payload, "execution_id") ??
|
|
1542
|
-
pickStringField(payload, "executionId");
|
|
1543
|
-
const executionType = pickStringField(payload, "execution_type") ??
|
|
1544
|
-
pickStringField(payload, "executionType");
|
|
1545
|
-
const agentId = pickStringField(payload, "agent_id") ??
|
|
1546
|
-
pickStringField(payload, "agentId");
|
|
1547
|
-
const success = typeof payload.success === "boolean"
|
|
1548
|
-
? payload.success
|
|
1549
|
-
: null;
|
|
1550
|
-
if (!executionId || !executionType || !agentId || success === null) {
|
|
1551
|
-
api.log?.warn?.("[orgx] Dropping invalid outcome outbox event", {
|
|
1552
|
-
eventId: event.id,
|
|
1553
|
-
});
|
|
1554
|
-
return;
|
|
1555
|
-
}
|
|
1556
|
-
const metaRaw = payload.metadata;
|
|
1557
|
-
const meta = metaRaw && typeof metaRaw === "object" && !Array.isArray(metaRaw)
|
|
1558
|
-
? metaRaw
|
|
1559
|
-
: {};
|
|
1560
|
-
await client.recordRunOutcome({
|
|
1561
|
-
initiative_id: context.value.initiativeId,
|
|
1562
|
-
run_id: runFields.run_id,
|
|
1563
|
-
correlation_id: runFields.correlation_id,
|
|
1564
|
-
source_client: context.value.sourceClient,
|
|
1565
|
-
execution_id: executionId,
|
|
1566
|
-
execution_type: executionType,
|
|
1567
|
-
agent_id: agentId,
|
|
1568
|
-
task_type: pickStringField(payload, "task_type") ??
|
|
1569
|
-
pickStringField(payload, "taskType") ??
|
|
1570
|
-
undefined,
|
|
1571
|
-
domain: pickStringField(payload, "domain") ?? undefined,
|
|
1572
|
-
started_at: pickStringField(payload, "started_at") ??
|
|
1573
|
-
pickStringField(payload, "startedAt") ??
|
|
1574
|
-
undefined,
|
|
1575
|
-
completed_at: pickStringField(payload, "completed_at") ??
|
|
1576
|
-
pickStringField(payload, "completedAt") ??
|
|
1577
|
-
undefined,
|
|
1578
|
-
inputs: payload.inputs && typeof payload.inputs === "object"
|
|
1579
|
-
? payload.inputs
|
|
1580
|
-
: undefined,
|
|
1581
|
-
outputs: payload.outputs && typeof payload.outputs === "object"
|
|
1582
|
-
? payload.outputs
|
|
1583
|
-
: undefined,
|
|
1584
|
-
steps: Array.isArray(payload.steps)
|
|
1585
|
-
? payload.steps
|
|
1586
|
-
: undefined,
|
|
1587
|
-
success,
|
|
1588
|
-
quality_score: typeof payload.quality_score === "number"
|
|
1589
|
-
? payload.quality_score
|
|
1590
|
-
: typeof payload.qualityScore === "number"
|
|
1591
|
-
? payload.qualityScore
|
|
1592
|
-
: undefined,
|
|
1593
|
-
duration_vs_estimate: typeof payload.duration_vs_estimate === "number"
|
|
1594
|
-
? payload.duration_vs_estimate
|
|
1595
|
-
: typeof payload.durationVsEstimate === "number"
|
|
1596
|
-
? payload.durationVsEstimate
|
|
1597
|
-
: undefined,
|
|
1598
|
-
cost_vs_budget: typeof payload.cost_vs_budget === "number"
|
|
1599
|
-
? payload.cost_vs_budget
|
|
1600
|
-
: typeof payload.costVsBudget === "number"
|
|
1601
|
-
? payload.costVsBudget
|
|
1602
|
-
: undefined,
|
|
1603
|
-
human_interventions: typeof payload.human_interventions === "number"
|
|
1604
|
-
? payload.human_interventions
|
|
1605
|
-
: typeof payload.humanInterventions === "number"
|
|
1606
|
-
? payload.humanInterventions
|
|
1607
|
-
: undefined,
|
|
1608
|
-
user_satisfaction: typeof payload.user_satisfaction === "number"
|
|
1609
|
-
? payload.user_satisfaction
|
|
1610
|
-
: typeof payload.userSatisfaction === "number"
|
|
1611
|
-
? payload.userSatisfaction
|
|
1612
|
-
: undefined,
|
|
1613
|
-
errors: Array.isArray(payload.errors)
|
|
1614
|
-
? payload.errors.filter((e) => typeof e === "string")
|
|
1615
|
-
: undefined,
|
|
1616
|
-
metadata: {
|
|
1617
|
-
...meta,
|
|
1618
|
-
source: "orgx_openclaw_outbox_replay",
|
|
1619
|
-
outbox_event_id: event.id,
|
|
1620
|
-
},
|
|
1621
|
-
});
|
|
1622
|
-
return;
|
|
1623
|
-
}
|
|
1624
|
-
if (event.type === "retro") {
|
|
1625
|
-
const context = resolveReportingContext(payload);
|
|
1626
|
-
if (!context.ok) {
|
|
1627
|
-
throw new Error(context.error);
|
|
1628
|
-
}
|
|
1629
|
-
const runFields = normalizeRunFields({
|
|
1630
|
-
runId: context.value.runId,
|
|
1631
|
-
correlationId: context.value.correlationId,
|
|
1632
|
-
});
|
|
1633
|
-
const retro = payload.retro && typeof payload.retro === "object" && !Array.isArray(payload.retro)
|
|
1634
|
-
? payload.retro
|
|
1635
|
-
: null;
|
|
1636
|
-
const summary = retro && typeof retro.summary === "string" ? retro.summary.trim() : "";
|
|
1637
|
-
if (!retro || !summary) {
|
|
1638
|
-
api.log?.warn?.("[orgx] Dropping invalid retro outbox event", {
|
|
1639
|
-
eventId: event.id,
|
|
1640
|
-
});
|
|
1641
|
-
return;
|
|
1642
|
-
}
|
|
1643
|
-
const entityTypeRaw = pickStringField(payload, "entity_type") ??
|
|
1644
|
-
pickStringField(payload, "entityType");
|
|
1645
|
-
const parsedEntityType = parseRetroEntityType(entityTypeRaw) ?? null;
|
|
1646
|
-
// Server-side enum parity can lag behind local clients. Only attach to the
|
|
1647
|
-
// entity types that are guaranteed to exist today.
|
|
1648
|
-
const entityType = parsedEntityType === "initiative" || parsedEntityType === "task"
|
|
1649
|
-
? parsedEntityType
|
|
1650
|
-
: null;
|
|
1651
|
-
const entityIdRaw = pickStringField(payload, "entity_id") ??
|
|
1652
|
-
pickStringField(payload, "entityId") ??
|
|
1653
|
-
null;
|
|
1654
|
-
const entityId = isUuid(entityIdRaw ?? undefined) ? entityIdRaw : null;
|
|
1655
|
-
await client.recordRunRetro({
|
|
1656
|
-
initiative_id: context.value.initiativeId,
|
|
1657
|
-
run_id: runFields.run_id,
|
|
1658
|
-
correlation_id: runFields.correlation_id,
|
|
1659
|
-
source_client: context.value.sourceClient,
|
|
1660
|
-
entity_type: entityType && entityId ? entityType : undefined,
|
|
1661
|
-
entity_id: entityType && entityId ? entityId : undefined,
|
|
1662
|
-
title: pickStringField(payload, "title") ?? undefined,
|
|
1663
|
-
idempotency_key: pickStringField(payload, "idempotency_key") ??
|
|
1664
|
-
pickStringField(payload, "idempotencyKey") ??
|
|
1665
|
-
undefined,
|
|
1666
|
-
retro: retro,
|
|
1667
|
-
markdown: pickStringField(payload, "markdown") ?? undefined,
|
|
1668
|
-
});
|
|
1669
|
-
return;
|
|
1670
|
-
}
|
|
1671
|
-
if (event.type === "artifact") {
|
|
1672
|
-
// Artifacts are first-class UX loop closure (activity stream + entity modals).
|
|
1673
|
-
// Try to persist upstream; if this fails, keep the event queued for retry.
|
|
1674
|
-
const payload = event.payload && typeof event.payload === "object" && !Array.isArray(event.payload)
|
|
1675
|
-
? event.payload
|
|
1676
|
-
: {};
|
|
1677
|
-
const name = pickStringField(payload, "name") ?? pickStringField(payload, "title") ?? "";
|
|
1678
|
-
const artifactType = pickStringField(payload, "artifact_type") ?? "other";
|
|
1679
|
-
const entityType = pickStringField(payload, "entity_type") ?? "";
|
|
1680
|
-
const entityId = pickStringField(payload, "entity_id") ?? "";
|
|
1681
|
-
const artifactId = pickStringField(payload, "artifact_id") ?? null;
|
|
1682
|
-
const description = pickStringField(payload, "description") ?? undefined;
|
|
1683
|
-
const externalUrl = pickStringField(payload, "url") ?? pickStringField(payload, "artifact_url") ?? null;
|
|
1684
|
-
const content = pickStringField(payload, "content") ?? pickStringField(payload, "preview_markdown") ?? null;
|
|
1685
|
-
const allowedEntityType = entityType === "initiative" ||
|
|
1686
|
-
entityType === "milestone" ||
|
|
1687
|
-
entityType === "task" ||
|
|
1688
|
-
entityType === "decision" ||
|
|
1689
|
-
entityType === "project"
|
|
1690
|
-
? entityType
|
|
1691
|
-
: null;
|
|
1692
|
-
if (!allowedEntityType || !entityId.trim() || !name.trim()) {
|
|
1693
|
-
api.log?.warn?.("[orgx] Dropping invalid artifact outbox event", {
|
|
1694
|
-
eventId: event.id,
|
|
1695
|
-
entityType,
|
|
1696
|
-
entityId,
|
|
1697
|
-
});
|
|
1698
|
-
return;
|
|
1699
|
-
}
|
|
1700
|
-
const result = await registerArtifact(client, client.getBaseUrl(), {
|
|
1701
|
-
artifact_id: artifactId,
|
|
1702
|
-
entity_type: allowedEntityType,
|
|
1703
|
-
entity_id: entityId,
|
|
1704
|
-
name: name.trim(),
|
|
1705
|
-
artifact_type: artifactType.trim() || "other",
|
|
1706
|
-
description,
|
|
1707
|
-
external_url: externalUrl,
|
|
1708
|
-
preview_markdown: content,
|
|
1709
|
-
status: "draft",
|
|
1710
|
-
metadata: {
|
|
1711
|
-
source: "outbox_replay",
|
|
1712
|
-
outbox_event_id: event.id,
|
|
1713
|
-
...(payload.metadata && typeof payload.metadata === "object" && !Array.isArray(payload.metadata)
|
|
1714
|
-
? payload.metadata
|
|
1715
|
-
: {}),
|
|
1716
|
-
},
|
|
1717
|
-
validate_persistence: process.env.ORGX_VALIDATE_ARTIFACT_PERSISTENCE === "1",
|
|
1718
|
-
});
|
|
1719
|
-
if (!result.ok) {
|
|
1720
|
-
throw new Error(result.persistence.last_error ?? "artifact registration failed");
|
|
1721
|
-
}
|
|
1722
|
-
return;
|
|
1723
|
-
}
|
|
1724
|
-
}
|
|
1725
|
-
async function flushOutboxQueues() {
|
|
1726
|
-
const attemptAt = new Date().toISOString();
|
|
1727
|
-
outboxReplayState = {
|
|
1728
|
-
...outboxReplayState,
|
|
1729
|
-
status: "running",
|
|
1730
|
-
lastReplayAttemptAt: attemptAt,
|
|
1731
|
-
lastReplayError: null,
|
|
1732
|
-
};
|
|
1733
|
-
let hadReplayFailure = false;
|
|
1734
|
-
let lastReplayError = null;
|
|
1735
|
-
// Outbox files are keyed by *session id* (e.g. initiative/run correlation),
|
|
1736
|
-
// not by event type.
|
|
1737
|
-
const outboxSummary = await readOutboxSummary();
|
|
1738
|
-
const queues = Object.entries(outboxSummary.pendingByQueue)
|
|
1739
|
-
.filter(([, count]) => typeof count === "number" && count > 0)
|
|
1740
|
-
.map(([queueId]) => queueId)
|
|
1741
|
-
.sort();
|
|
1742
|
-
for (const queue of queues) {
|
|
1743
|
-
const pending = await readOutbox(queue);
|
|
1744
|
-
if (pending.length === 0) {
|
|
1745
|
-
continue;
|
|
1746
|
-
}
|
|
1747
|
-
const remaining = [];
|
|
1748
|
-
for (const event of pending) {
|
|
1749
|
-
try {
|
|
1750
|
-
await replayOutboxEvent(event);
|
|
1751
|
-
}
|
|
1752
|
-
catch (err) {
|
|
1753
|
-
hadReplayFailure = true;
|
|
1754
|
-
lastReplayError = toErrorMessage(err);
|
|
1755
|
-
remaining.push(event);
|
|
1756
|
-
api.log?.warn?.("[orgx] Outbox replay failed", {
|
|
1757
|
-
queue,
|
|
1758
|
-
eventId: event.id,
|
|
1759
|
-
error: lastReplayError,
|
|
1760
|
-
});
|
|
1761
|
-
}
|
|
1762
|
-
}
|
|
1763
|
-
await replaceOutbox(queue, remaining);
|
|
1764
|
-
const replayedCount = pending.length - remaining.length;
|
|
1765
|
-
if (replayedCount > 0) {
|
|
1766
|
-
api.log?.info?.("[orgx] Replayed buffered outbox events", {
|
|
1767
|
-
queue,
|
|
1768
|
-
replayed: replayedCount,
|
|
1769
|
-
remaining: remaining.length,
|
|
1770
|
-
});
|
|
1771
|
-
}
|
|
1772
|
-
}
|
|
1773
|
-
if (hadReplayFailure) {
|
|
1774
|
-
outboxReplayState = {
|
|
1775
|
-
...outboxReplayState,
|
|
1776
|
-
status: "error",
|
|
1777
|
-
lastReplayFailureAt: new Date().toISOString(),
|
|
1778
|
-
lastReplayError,
|
|
1779
|
-
};
|
|
1780
|
-
}
|
|
1781
|
-
else {
|
|
1782
|
-
outboxReplayState = {
|
|
1783
|
-
...outboxReplayState,
|
|
1784
|
-
status: "success",
|
|
1785
|
-
lastReplaySuccessAt: new Date().toISOString(),
|
|
1786
|
-
lastReplayError: null,
|
|
1787
|
-
};
|
|
1788
|
-
}
|
|
1789
|
-
}
|
|
1790
|
-
async function doSync() {
|
|
1791
|
-
if (syncInFlight) {
|
|
1792
|
-
return syncInFlight;
|
|
1793
|
-
}
|
|
1794
|
-
syncInFlight = (async () => {
|
|
1795
|
-
if (!config.apiKey) {
|
|
1796
|
-
refreshConfigFromSources({ reason: "sync_no_api_key" });
|
|
1797
|
-
}
|
|
1798
|
-
if (!config.apiKey) {
|
|
1799
|
-
updateOnboardingState({
|
|
1800
|
-
status: "idle",
|
|
1801
|
-
hasApiKey: false,
|
|
1802
|
-
connectionVerified: false,
|
|
1803
|
-
nextAction: "connect",
|
|
1804
|
-
});
|
|
1805
|
-
return;
|
|
1806
|
-
}
|
|
1807
|
-
try {
|
|
1808
|
-
await reconcileStoppedAgentRuns();
|
|
1809
|
-
let snapshotError = null;
|
|
1810
|
-
try {
|
|
1811
|
-
updateCachedSnapshot(await client.getOrgSnapshot());
|
|
1812
|
-
}
|
|
1813
|
-
catch (err) {
|
|
1814
|
-
if (isAuthFailure(err)) {
|
|
1815
|
-
throw err;
|
|
910
|
+
if (localAgents.length > 0) {
|
|
911
|
+
try {
|
|
912
|
+
await client.syncMemory({ agents: localAgents });
|
|
913
|
+
}
|
|
914
|
+
catch (err) {
|
|
915
|
+
if (isAuthFailure(err)) {
|
|
916
|
+
throw err;
|
|
917
|
+
}
|
|
918
|
+
api.log?.warn?.("[orgx] Local agent telemetry sync failed (continuing)", {
|
|
919
|
+
error: toErrorMessage(err),
|
|
920
|
+
count: localAgents.length,
|
|
921
|
+
});
|
|
1816
922
|
}
|
|
1817
|
-
snapshotError = toErrorMessage(err);
|
|
1818
|
-
api.log?.warn?.("[orgx] Snapshot sync failed (continuing)", {
|
|
1819
|
-
error: snapshotError,
|
|
1820
|
-
});
|
|
1821
923
|
}
|
|
1822
924
|
// Best-effort: poll the canonical OrgX SkillPack so the dashboard/install path
|
|
1823
925
|
// can apply it without blocking on an on-demand fetch.
|
|
@@ -1947,16 +1049,48 @@ export default function register(api) {
|
|
|
1947
1049
|
lastError: null,
|
|
1948
1050
|
nextAction: "connect",
|
|
1949
1051
|
});
|
|
1950
|
-
const
|
|
1052
|
+
const pairingPayload = {
|
|
1951
1053
|
installationId: config.installationId,
|
|
1952
1054
|
pluginVersion: config.pluginVersion,
|
|
1953
1055
|
openclawVersion: input.openclawVersion,
|
|
1954
1056
|
platform: input.platform || process.platform,
|
|
1955
1057
|
deviceName: input.deviceName,
|
|
1956
|
-
}
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1058
|
+
};
|
|
1059
|
+
const requestPairing = (targetBaseApiUrl) => fetchOrgxJsonRequest({
|
|
1060
|
+
baseApiUrl: targetBaseApiUrl,
|
|
1061
|
+
method: "POST",
|
|
1062
|
+
path: "/api/plugin/openclaw/pairings",
|
|
1063
|
+
body: pairingPayload,
|
|
1064
|
+
// Pairing can hit a cold serverless boot + supabase insert + rate-limit checks.
|
|
1065
|
+
// Give it more headroom than typical lightweight API calls.
|
|
1066
|
+
options: { timeoutMs: 30_000 },
|
|
1067
|
+
toErrorMessage,
|
|
1068
|
+
});
|
|
1069
|
+
let started = await requestPairing(baseApiUrl);
|
|
1070
|
+
const shouldRetryAgainstCanonical = !started.ok &&
|
|
1071
|
+
started.status === 0 &&
|
|
1072
|
+
looksLikeTransientFetchFailure(started.error) &&
|
|
1073
|
+
baseApiUrl.replace(/\/+$/, "") !== ORGX_CANONICAL_BASE_URL;
|
|
1074
|
+
if (shouldRetryAgainstCanonical) {
|
|
1075
|
+
const initialStatus = started.ok ? 0 : started.status;
|
|
1076
|
+
const initialError = started.ok ? "Pairing request failed" : started.error;
|
|
1077
|
+
const fallbackStarted = await requestPairing(ORGX_CANONICAL_BASE_URL);
|
|
1078
|
+
if (fallbackStarted.ok) {
|
|
1079
|
+
api.log?.info?.("[orgx] Pairing start succeeded via canonical OrgX base URL", {
|
|
1080
|
+
previousBaseUrl: baseApiUrl,
|
|
1081
|
+
nextBaseUrl: ORGX_CANONICAL_BASE_URL,
|
|
1082
|
+
});
|
|
1083
|
+
applyRuntimeBaseUrl(ORGX_CANONICAL_BASE_URL);
|
|
1084
|
+
started = fallbackStarted;
|
|
1085
|
+
}
|
|
1086
|
+
else {
|
|
1087
|
+
started = {
|
|
1088
|
+
ok: false,
|
|
1089
|
+
status: fallbackStarted.status || initialStatus,
|
|
1090
|
+
error: `${initialError}; fallback via ${ORGX_CANONICAL_BASE_URL} failed: ${fallbackStarted.error}`,
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1960
1094
|
if (!started.ok) {
|
|
1961
1095
|
if (isAuthRequiredError(started)) {
|
|
1962
1096
|
clearPairingState();
|
|
@@ -1981,7 +1115,10 @@ export default function register(api) {
|
|
|
1981
1115
|
};
|
|
1982
1116
|
}
|
|
1983
1117
|
const statusLabel = started.status ? ` (HTTP ${started.status})` : "";
|
|
1984
|
-
const
|
|
1118
|
+
const networkHint = started.status === 0
|
|
1119
|
+
? ` Could not reach OrgX at ${baseApiUrl}. Check network/VPN/firewall or update ORGX_BASE_URL.`
|
|
1120
|
+
: "";
|
|
1121
|
+
const message = `Pairing start failed${statusLabel}: ${started.error}${networkHint}`;
|
|
1985
1122
|
updateOnboardingState({
|
|
1986
1123
|
status: "error",
|
|
1987
1124
|
hasApiKey: Boolean(config.apiKey),
|
|
@@ -2154,1235 +1291,101 @@ export default function register(api) {
|
|
|
2154
1291
|
keySource: "none",
|
|
2155
1292
|
});
|
|
2156
1293
|
}
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
1294
|
+
async function stopTrackedAgentRunsOnPluginStop() {
|
|
1295
|
+
const runs = Object.values(readAgentRuns().runs ?? {}).filter((run) => run?.status === "running");
|
|
1296
|
+
let attempted = 0;
|
|
1297
|
+
let stopped = 0;
|
|
1298
|
+
let failed = 0;
|
|
1299
|
+
let markedStopped = 0;
|
|
1300
|
+
for (const run of runs) {
|
|
1301
|
+
if (!run || typeof run !== "object")
|
|
1302
|
+
continue;
|
|
1303
|
+
attempted += 1;
|
|
1304
|
+
let runStopped = false;
|
|
1305
|
+
if (typeof run.pid === "number" && Number.isFinite(run.pid) && run.pid > 0) {
|
|
1306
|
+
try {
|
|
1307
|
+
const result = await stopDetachedProcess(run.pid);
|
|
1308
|
+
runStopped = Boolean(result.stopped);
|
|
1309
|
+
}
|
|
1310
|
+
catch {
|
|
1311
|
+
runStopped = false;
|
|
1312
|
+
}
|
|
2166
1313
|
}
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
1314
|
+
else {
|
|
1315
|
+
// No tracked PID means there is no local process to stop.
|
|
1316
|
+
runStopped = true;
|
|
1317
|
+
}
|
|
1318
|
+
if (runStopped) {
|
|
1319
|
+
stopped += 1;
|
|
1320
|
+
}
|
|
1321
|
+
else {
|
|
1322
|
+
failed += 1;
|
|
1323
|
+
}
|
|
1324
|
+
const marked = markAgentRunStopped(run.runId);
|
|
1325
|
+
if (marked) {
|
|
1326
|
+
markedStopped += 1;
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
return { attempted, stopped, failed, markedStopped };
|
|
1330
|
+
}
|
|
1331
|
+
registerSyncService({
|
|
1332
|
+
api,
|
|
1333
|
+
syncIntervalMs: config.syncIntervalMs,
|
|
1334
|
+
ensureGatewayWatchdog: (logger) => ensureGatewayWatchdog(logger),
|
|
1335
|
+
stopGatewayWatchdog: () => stopGatewayWatchdog((api.log ?? {})),
|
|
1336
|
+
stopTrackedAgentRuns: stopTrackedAgentRunsOnPluginStop,
|
|
1337
|
+
doSync,
|
|
1338
|
+
scheduleNextSync,
|
|
1339
|
+
setSyncServiceRunning: (running) => {
|
|
1340
|
+
syncServiceRunning = running;
|
|
2172
1341
|
},
|
|
2173
|
-
|
|
2174
|
-
syncServiceRunning = false;
|
|
1342
|
+
clearSyncTimer: () => {
|
|
2175
1343
|
if (syncTimer)
|
|
2176
1344
|
clearTimeout(syncTimer);
|
|
2177
1345
|
syncTimer = null;
|
|
2178
1346
|
},
|
|
2179
1347
|
});
|
|
2180
1348
|
async function autoAssignEntityForCreate(input) {
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
byKey.set(key, agent);
|
|
2187
|
-
};
|
|
2188
|
-
let liveAgents = [];
|
|
2189
|
-
try {
|
|
2190
|
-
const agentResp = await client.getLiveAgents({
|
|
2191
|
-
initiative: input.initiativeId,
|
|
2192
|
-
includeIdle: true,
|
|
2193
|
-
});
|
|
2194
|
-
liveAgents = (Array.isArray(agentResp.agents) ? agentResp.agents : [])
|
|
2195
|
-
.map((raw) => {
|
|
2196
|
-
if (!raw || typeof raw !== "object")
|
|
2197
|
-
return null;
|
|
2198
|
-
const record = raw;
|
|
2199
|
-
const id = (typeof record.id === "string" && record.id.trim()) ||
|
|
2200
|
-
(typeof record.agentId === "string" && record.agentId.trim()) ||
|
|
2201
|
-
"";
|
|
2202
|
-
const name = (typeof record.name === "string" && record.name.trim()) ||
|
|
2203
|
-
(typeof record.agentName === "string" && record.agentName.trim()) ||
|
|
2204
|
-
id;
|
|
2205
|
-
if (!name)
|
|
2206
|
-
return null;
|
|
2207
|
-
return {
|
|
2208
|
-
id: id || `name:${name}`,
|
|
2209
|
-
name,
|
|
2210
|
-
domain: (typeof record.domain === "string" && record.domain.trim()) ||
|
|
2211
|
-
(typeof record.role === "string" && record.role.trim()) ||
|
|
2212
|
-
null,
|
|
2213
|
-
status: (typeof record.status === "string" && record.status.trim()) || null,
|
|
2214
|
-
};
|
|
2215
|
-
})
|
|
2216
|
-
.filter((item) => item !== null);
|
|
2217
|
-
}
|
|
2218
|
-
catch (err) {
|
|
2219
|
-
warnings.push(`live agents unavailable (${toErrorMessage(err)})`);
|
|
2220
|
-
}
|
|
2221
|
-
const orchestrator = liveAgents.find((agent) => /holt|orchestrator/i.test(agent.name) ||
|
|
2222
|
-
/orchestrator/i.test(agent.domain ?? ""));
|
|
2223
|
-
if (orchestrator)
|
|
2224
|
-
addAgent(orchestrator);
|
|
2225
|
-
let assignmentSource = "fallback";
|
|
2226
|
-
try {
|
|
2227
|
-
const preflight = await client.delegationPreflight({
|
|
2228
|
-
intent: `${input.title}${input.summary ? `: ${input.summary}` : ""}`,
|
|
2229
|
-
});
|
|
2230
|
-
const recommendations = preflight.data?.recommended_split ?? [];
|
|
2231
|
-
const recommendedDomains = [
|
|
2232
|
-
...new Set(recommendations
|
|
2233
|
-
.map((entry) => String(entry.owner_domain ?? "").trim().toLowerCase())
|
|
2234
|
-
.filter(Boolean)),
|
|
2235
|
-
];
|
|
2236
|
-
for (const domain of recommendedDomains) {
|
|
2237
|
-
const match = liveAgents.find((agent) => (agent.domain ?? "").toLowerCase().includes(domain));
|
|
2238
|
-
if (match)
|
|
2239
|
-
addAgent(match);
|
|
2240
|
-
}
|
|
2241
|
-
if (recommendedDomains.length > 0) {
|
|
2242
|
-
assignmentSource = "orchestrator";
|
|
2243
|
-
}
|
|
2244
|
-
}
|
|
2245
|
-
catch (err) {
|
|
2246
|
-
warnings.push(`delegation preflight failed (${toErrorMessage(err)})`);
|
|
2247
|
-
}
|
|
2248
|
-
if (byKey.size === 0) {
|
|
2249
|
-
const haystack = `${input.title} ${input.summary ?? ""}`.toLowerCase();
|
|
2250
|
-
const domainHints = [];
|
|
2251
|
-
if (/market|campaign|thread|article|tweet|copy/.test(haystack)) {
|
|
2252
|
-
domainHints.push("marketing");
|
|
2253
|
-
}
|
|
2254
|
-
else if (/design|ux|ui|a11y/.test(haystack)) {
|
|
2255
|
-
domainHints.push("design");
|
|
2256
|
-
}
|
|
2257
|
-
else if (/ops|runbook|incident|reliability/.test(haystack)) {
|
|
2258
|
-
domainHints.push("operations");
|
|
2259
|
-
}
|
|
2260
|
-
else if (/sales|deal|pipeline/.test(haystack)) {
|
|
2261
|
-
domainHints.push("sales");
|
|
2262
|
-
}
|
|
2263
|
-
else {
|
|
2264
|
-
domainHints.push("engineering", "product");
|
|
2265
|
-
}
|
|
2266
|
-
for (const domain of domainHints) {
|
|
2267
|
-
const match = liveAgents.find((agent) => (agent.domain ?? "").toLowerCase().includes(domain));
|
|
2268
|
-
if (match)
|
|
2269
|
-
addAgent(match);
|
|
2270
|
-
}
|
|
2271
|
-
}
|
|
2272
|
-
if (byKey.size === 0 && liveAgents.length > 0) {
|
|
2273
|
-
addAgent(liveAgents[0]);
|
|
2274
|
-
warnings.push("fallback selected first available live agent");
|
|
2275
|
-
}
|
|
2276
|
-
const assignedAgents = Array.from(byKey.values());
|
|
2277
|
-
let updatedEntity = null;
|
|
2278
|
-
try {
|
|
2279
|
-
updatedEntity = (await client.updateEntity(input.entityType, input.entityId, {
|
|
2280
|
-
assigned_agent_ids: assignedAgents.map((agent) => agent.id),
|
|
2281
|
-
assigned_agent_names: assignedAgents.map((agent) => agent.name),
|
|
2282
|
-
assignment_source: assignmentSource,
|
|
2283
|
-
}));
|
|
2284
|
-
}
|
|
2285
|
-
catch (err) {
|
|
2286
|
-
warnings.push(`assignment update failed (${toErrorMessage(err)})`);
|
|
2287
|
-
}
|
|
2288
|
-
return {
|
|
2289
|
-
assignmentSource,
|
|
2290
|
-
assignedAgents,
|
|
2291
|
-
warnings,
|
|
2292
|
-
updatedEntity,
|
|
2293
|
-
};
|
|
1349
|
+
return autoAssignEntityForCreateWithClient({
|
|
1350
|
+
client,
|
|
1351
|
+
toErrorMessage,
|
|
1352
|
+
...input,
|
|
1353
|
+
});
|
|
2294
1354
|
}
|
|
2295
1355
|
// ---------------------------------------------------------------------------
|
|
2296
1356
|
// 2. MCP Tools (Model Context Protocol compatible)
|
|
2297
1357
|
// ---------------------------------------------------------------------------
|
|
2298
|
-
const mcpToolRegistry =
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
}
|
|
2320
|
-
return text(formatSnapshot(cachedSnapshot));
|
|
2321
|
-
},
|
|
2322
|
-
}, { optional: true });
|
|
2323
|
-
// --- orgx_sync ---
|
|
2324
|
-
registerMcpTool({
|
|
2325
|
-
name: "orgx_sync",
|
|
2326
|
-
description: "Push/pull memory sync with OrgX. Send local memory/daily log; receive initiatives, tasks, decisions, model routing policy.",
|
|
2327
|
-
parameters: {
|
|
2328
|
-
type: "object",
|
|
2329
|
-
properties: {
|
|
2330
|
-
memory: {
|
|
2331
|
-
type: "string",
|
|
2332
|
-
description: "Local memory snapshot to push",
|
|
2333
|
-
},
|
|
2334
|
-
dailyLog: {
|
|
2335
|
-
type: "string",
|
|
2336
|
-
description: "Today's session log to push",
|
|
2337
|
-
},
|
|
2338
|
-
},
|
|
2339
|
-
},
|
|
2340
|
-
async execute(_callId, params = {}) {
|
|
2341
|
-
try {
|
|
2342
|
-
const resp = await client.syncMemory({
|
|
2343
|
-
memory: params.memory,
|
|
2344
|
-
dailyLog: params.dailyLog,
|
|
2345
|
-
});
|
|
2346
|
-
return json("Sync complete:", resp);
|
|
2347
|
-
}
|
|
2348
|
-
catch (err) {
|
|
2349
|
-
return text(`❌ Sync failed: ${err instanceof Error ? err.message : err}`);
|
|
2350
|
-
}
|
|
2351
|
-
},
|
|
2352
|
-
}, { optional: true });
|
|
2353
|
-
// --- orgx_delegation_preflight ---
|
|
2354
|
-
registerMcpTool({
|
|
2355
|
-
name: "orgx_delegation_preflight",
|
|
2356
|
-
description: "Run delegation preflight to score scope quality, estimate ETA/cost, and suggest a split before autonomous execution.",
|
|
2357
|
-
parameters: {
|
|
2358
|
-
type: "object",
|
|
2359
|
-
properties: {
|
|
2360
|
-
intent: {
|
|
2361
|
-
type: "string",
|
|
2362
|
-
description: "Task intent in natural language",
|
|
2363
|
-
},
|
|
2364
|
-
acceptanceCriteria: {
|
|
2365
|
-
type: "array",
|
|
2366
|
-
items: { type: "string" },
|
|
2367
|
-
description: "Optional acceptance criteria to reduce ambiguity",
|
|
2368
|
-
},
|
|
2369
|
-
constraints: {
|
|
2370
|
-
type: "array",
|
|
2371
|
-
items: { type: "string" },
|
|
2372
|
-
description: "Optional constraints (deadline, stack, policy)",
|
|
2373
|
-
},
|
|
2374
|
-
domains: {
|
|
2375
|
-
type: "array",
|
|
2376
|
-
items: { type: "string" },
|
|
2377
|
-
description: "Optional preferred owner domains",
|
|
2378
|
-
},
|
|
2379
|
-
},
|
|
2380
|
-
required: ["intent"],
|
|
2381
|
-
additionalProperties: false,
|
|
2382
|
-
},
|
|
2383
|
-
async execute(_callId, params = { intent: "" }) {
|
|
2384
|
-
try {
|
|
2385
|
-
const result = await client.delegationPreflight({
|
|
2386
|
-
intent: params.intent,
|
|
2387
|
-
acceptanceCriteria: Array.isArray(params.acceptanceCriteria)
|
|
2388
|
-
? params.acceptanceCriteria.filter((item) => typeof item === "string")
|
|
2389
|
-
: undefined,
|
|
2390
|
-
constraints: Array.isArray(params.constraints)
|
|
2391
|
-
? params.constraints.filter((item) => typeof item === "string")
|
|
2392
|
-
: undefined,
|
|
2393
|
-
domains: Array.isArray(params.domains)
|
|
2394
|
-
? params.domains.filter((item) => typeof item === "string")
|
|
2395
|
-
: undefined,
|
|
2396
|
-
});
|
|
2397
|
-
return json("Delegation preflight:", result.data ?? result);
|
|
2398
|
-
}
|
|
2399
|
-
catch (err) {
|
|
2400
|
-
return text(`❌ Delegation preflight failed: ${err instanceof Error ? err.message : err}`);
|
|
2401
|
-
}
|
|
2402
|
-
},
|
|
2403
|
-
}, { optional: true });
|
|
2404
|
-
// --- orgx_run_action ---
|
|
2405
|
-
registerMcpTool({
|
|
2406
|
-
name: "orgx_run_action",
|
|
2407
|
-
description: "Apply a control action to a run: pause, resume, cancel, or rollback (rollback requires checkpointId).",
|
|
2408
|
-
parameters: {
|
|
2409
|
-
type: "object",
|
|
2410
|
-
properties: {
|
|
2411
|
-
runId: {
|
|
2412
|
-
type: "string",
|
|
2413
|
-
description: "Run UUID",
|
|
2414
|
-
},
|
|
2415
|
-
action: {
|
|
2416
|
-
type: "string",
|
|
2417
|
-
enum: ["pause", "resume", "cancel", "rollback"],
|
|
2418
|
-
description: "Control action",
|
|
2419
|
-
},
|
|
2420
|
-
checkpointId: {
|
|
2421
|
-
type: "string",
|
|
2422
|
-
description: "Checkpoint UUID (required for rollback)",
|
|
2423
|
-
},
|
|
2424
|
-
reason: {
|
|
2425
|
-
type: "string",
|
|
2426
|
-
description: "Optional reason for audit trail",
|
|
2427
|
-
},
|
|
2428
|
-
},
|
|
2429
|
-
required: ["runId", "action"],
|
|
2430
|
-
additionalProperties: false,
|
|
2431
|
-
},
|
|
2432
|
-
async execute(_callId, params = { runId: "", action: "pause" }) {
|
|
2433
|
-
try {
|
|
2434
|
-
if (params.action === "rollback" && !params.checkpointId) {
|
|
2435
|
-
return text("❌ rollback requires checkpointId");
|
|
2436
|
-
}
|
|
2437
|
-
const result = await client.runAction(params.runId, params.action, {
|
|
2438
|
-
checkpointId: params.checkpointId,
|
|
2439
|
-
reason: params.reason,
|
|
2440
|
-
});
|
|
2441
|
-
return json("Run action applied:", result.data ?? result);
|
|
2442
|
-
}
|
|
2443
|
-
catch (err) {
|
|
2444
|
-
return text(`❌ Run action failed: ${err instanceof Error ? err.message : err}`);
|
|
2445
|
-
}
|
|
2446
|
-
},
|
|
2447
|
-
}, { optional: true });
|
|
2448
|
-
// --- orgx_checkpoints_list ---
|
|
2449
|
-
registerMcpTool({
|
|
2450
|
-
name: "orgx_checkpoints_list",
|
|
2451
|
-
description: "List checkpoints for a run.",
|
|
2452
|
-
parameters: {
|
|
2453
|
-
type: "object",
|
|
2454
|
-
properties: {
|
|
2455
|
-
runId: {
|
|
2456
|
-
type: "string",
|
|
2457
|
-
description: "Run UUID",
|
|
2458
|
-
},
|
|
2459
|
-
},
|
|
2460
|
-
required: ["runId"],
|
|
2461
|
-
additionalProperties: false,
|
|
2462
|
-
},
|
|
2463
|
-
async execute(_callId, params = { runId: "" }) {
|
|
2464
|
-
try {
|
|
2465
|
-
const result = await client.listRunCheckpoints(params.runId);
|
|
2466
|
-
return json("Run checkpoints:", result.data ?? result);
|
|
2467
|
-
}
|
|
2468
|
-
catch (err) {
|
|
2469
|
-
return text(`❌ Failed to list checkpoints: ${err instanceof Error ? err.message : err}`);
|
|
2470
|
-
}
|
|
2471
|
-
},
|
|
2472
|
-
}, { optional: true });
|
|
2473
|
-
// --- orgx_checkpoint_restore ---
|
|
2474
|
-
registerMcpTool({
|
|
2475
|
-
name: "orgx_checkpoint_restore",
|
|
2476
|
-
description: "Restore a run to a specific checkpoint.",
|
|
2477
|
-
parameters: {
|
|
2478
|
-
type: "object",
|
|
2479
|
-
properties: {
|
|
2480
|
-
runId: {
|
|
2481
|
-
type: "string",
|
|
2482
|
-
description: "Run UUID",
|
|
2483
|
-
},
|
|
2484
|
-
checkpointId: {
|
|
2485
|
-
type: "string",
|
|
2486
|
-
description: "Checkpoint UUID",
|
|
2487
|
-
},
|
|
2488
|
-
reason: {
|
|
2489
|
-
type: "string",
|
|
2490
|
-
description: "Optional restoration reason",
|
|
2491
|
-
},
|
|
2492
|
-
},
|
|
2493
|
-
required: ["runId", "checkpointId"],
|
|
2494
|
-
additionalProperties: false,
|
|
2495
|
-
},
|
|
2496
|
-
async execute(_callId, params = {
|
|
2497
|
-
runId: "",
|
|
2498
|
-
checkpointId: "",
|
|
2499
|
-
}) {
|
|
2500
|
-
try {
|
|
2501
|
-
const result = await client.restoreRunCheckpoint(params.runId, {
|
|
2502
|
-
checkpointId: params.checkpointId,
|
|
2503
|
-
reason: params.reason,
|
|
2504
|
-
});
|
|
2505
|
-
return json("Checkpoint restored:", result.data ?? result);
|
|
2506
|
-
}
|
|
2507
|
-
catch (err) {
|
|
2508
|
-
return text(`❌ Checkpoint restore failed: ${err instanceof Error ? err.message : err}`);
|
|
2509
|
-
}
|
|
2510
|
-
},
|
|
2511
|
-
}, { optional: true });
|
|
2512
|
-
// --- orgx_spawn_check ---
|
|
2513
|
-
registerMcpTool({
|
|
2514
|
-
name: "orgx_spawn_check",
|
|
2515
|
-
description: "Check quality gate + get model routing before spawning a sub-agent. Returns allowed/denied, model tier, and check details.",
|
|
2516
|
-
parameters: {
|
|
2517
|
-
type: "object",
|
|
2518
|
-
properties: {
|
|
2519
|
-
domain: {
|
|
2520
|
-
type: "string",
|
|
2521
|
-
description: "Agent domain (engineering, product, marketing, data, operations, design)",
|
|
2522
|
-
},
|
|
2523
|
-
taskId: {
|
|
2524
|
-
type: "string",
|
|
2525
|
-
description: "OrgX task ID to check",
|
|
2526
|
-
},
|
|
2527
|
-
},
|
|
2528
|
-
required: ["domain"],
|
|
2529
|
-
},
|
|
2530
|
-
async execute(_callId, params = { domain: "" }) {
|
|
2531
|
-
try {
|
|
2532
|
-
const result = await client.checkSpawnGuard(params.domain, params.taskId);
|
|
2533
|
-
const status = result.allowed ? "✅ Allowed" : "🚫 Blocked";
|
|
2534
|
-
return json(`${status} — model tier: ${result.modelTier}`, result);
|
|
2535
|
-
}
|
|
2536
|
-
catch (err) {
|
|
2537
|
-
return text(`❌ Spawn check failed: ${err instanceof Error ? err.message : err}`);
|
|
2538
|
-
}
|
|
2539
|
-
},
|
|
2540
|
-
}, { optional: true });
|
|
2541
|
-
// --- orgx_quality_score ---
|
|
2542
|
-
registerMcpTool({
|
|
2543
|
-
name: "orgx_quality_score",
|
|
2544
|
-
description: "Record a quality score (1-5) for completed agent work. Used to gate future spawns and track performance.",
|
|
2545
|
-
parameters: {
|
|
2546
|
-
type: "object",
|
|
2547
|
-
properties: {
|
|
2548
|
-
taskId: {
|
|
2549
|
-
type: "string",
|
|
2550
|
-
description: "ID of the completed task",
|
|
2551
|
-
},
|
|
2552
|
-
domain: {
|
|
2553
|
-
type: "string",
|
|
2554
|
-
description: "Agent domain that did the work",
|
|
2555
|
-
},
|
|
2556
|
-
score: {
|
|
2557
|
-
type: "number",
|
|
2558
|
-
description: "Quality 1 (poor) to 5 (excellent)",
|
|
2559
|
-
minimum: 1,
|
|
2560
|
-
maximum: 5,
|
|
2561
|
-
},
|
|
2562
|
-
notes: {
|
|
2563
|
-
type: "string",
|
|
2564
|
-
description: "Notes on the assessment",
|
|
2565
|
-
},
|
|
2566
|
-
},
|
|
2567
|
-
required: ["taskId", "domain", "score"],
|
|
2568
|
-
},
|
|
2569
|
-
async execute(_callId, params = { taskId: "", domain: "", score: 0 }) {
|
|
2570
|
-
try {
|
|
2571
|
-
await client.recordQuality(params);
|
|
2572
|
-
return text(`✅ Quality score recorded: ${params.score}/5 for task ${params.taskId} (${params.domain})`);
|
|
2573
|
-
}
|
|
2574
|
-
catch (err) {
|
|
2575
|
-
return text(`❌ Quality recording failed: ${err instanceof Error ? err.message : err}`);
|
|
2576
|
-
}
|
|
2577
|
-
},
|
|
2578
|
-
}, { optional: true });
|
|
2579
|
-
// --- orgx_create_entity ---
|
|
2580
|
-
registerMcpTool({
|
|
2581
|
-
name: "orgx_create_entity",
|
|
2582
|
-
description: "Create an OrgX entity (initiative, workstream, task, decision, milestone, etc.).",
|
|
2583
|
-
parameters: {
|
|
2584
|
-
type: "object",
|
|
2585
|
-
properties: {
|
|
2586
|
-
type: {
|
|
2587
|
-
type: "string",
|
|
2588
|
-
description: "Entity type: initiative, workstream, task, decision, milestone, artifact, agent, blocker",
|
|
2589
|
-
},
|
|
2590
|
-
title: {
|
|
2591
|
-
type: "string",
|
|
2592
|
-
description: "Entity title",
|
|
2593
|
-
},
|
|
2594
|
-
summary: {
|
|
2595
|
-
type: "string",
|
|
2596
|
-
description: "Description",
|
|
2597
|
-
},
|
|
2598
|
-
status: {
|
|
2599
|
-
type: "string",
|
|
2600
|
-
description: "Initial status (active, not_started, todo)",
|
|
2601
|
-
},
|
|
2602
|
-
initiative_id: {
|
|
2603
|
-
type: "string",
|
|
2604
|
-
description: "Parent initiative ID (for workstreams/tasks)",
|
|
2605
|
-
},
|
|
2606
|
-
workstream_id: {
|
|
2607
|
-
type: "string",
|
|
2608
|
-
description: "Parent workstream ID (for tasks)",
|
|
2609
|
-
},
|
|
2610
|
-
command_center_id: {
|
|
2611
|
-
type: "string",
|
|
2612
|
-
description: "Command center ID (for initiatives)",
|
|
2613
|
-
},
|
|
2614
|
-
},
|
|
2615
|
-
required: ["type", "title"],
|
|
2616
|
-
},
|
|
2617
|
-
async execute(_callId, params = {}) {
|
|
2618
|
-
try {
|
|
2619
|
-
const { type, ...data } = params;
|
|
2620
|
-
let entity = await client.createEntity(type, data);
|
|
2621
|
-
let assignmentSummary = null;
|
|
2622
|
-
const entityType = String(type ?? "");
|
|
2623
|
-
if (entityType === "initiative" || entityType === "workstream") {
|
|
2624
|
-
const entityRecord = entity;
|
|
2625
|
-
const assignment = await autoAssignEntityForCreate({
|
|
2626
|
-
entityType,
|
|
2627
|
-
entityId: String(entityRecord.id ?? ""),
|
|
2628
|
-
initiativeId: entityType === "initiative"
|
|
2629
|
-
? String(entityRecord.id ?? "")
|
|
2630
|
-
: (typeof data.initiative_id === "string"
|
|
2631
|
-
? data.initiative_id
|
|
2632
|
-
: null),
|
|
2633
|
-
title: (typeof entityRecord.title === "string" && entityRecord.title) ||
|
|
2634
|
-
(typeof entityRecord.name === "string" && entityRecord.name) ||
|
|
2635
|
-
(typeof data.title === "string" && data.title) ||
|
|
2636
|
-
"Untitled",
|
|
2637
|
-
summary: (typeof entityRecord.summary === "string" && entityRecord.summary) ||
|
|
2638
|
-
(typeof data.summary === "string" && data.summary) ||
|
|
2639
|
-
null,
|
|
2640
|
-
});
|
|
2641
|
-
if (assignment.updatedEntity) {
|
|
2642
|
-
entity = assignment.updatedEntity;
|
|
2643
|
-
}
|
|
2644
|
-
assignmentSummary = {
|
|
2645
|
-
assignment_source: assignment.assignmentSource,
|
|
2646
|
-
assigned_agents: assignment.assignedAgents,
|
|
2647
|
-
warnings: assignment.warnings,
|
|
2648
|
-
};
|
|
2649
|
-
}
|
|
2650
|
-
return json(`✅ Created ${type}: ${entity.title ?? entity.id}`, {
|
|
2651
|
-
entity,
|
|
2652
|
-
...(assignmentSummary
|
|
2653
|
-
? {
|
|
2654
|
-
auto_assignment: assignmentSummary,
|
|
2655
|
-
}
|
|
2656
|
-
: {}),
|
|
2657
|
-
});
|
|
2658
|
-
}
|
|
2659
|
-
catch (err) {
|
|
2660
|
-
return text(`❌ Creation failed: ${err instanceof Error ? err.message : err}`);
|
|
2661
|
-
}
|
|
2662
|
-
},
|
|
2663
|
-
}, { optional: true });
|
|
2664
|
-
// --- orgx_update_entity ---
|
|
2665
|
-
registerMcpTool({
|
|
2666
|
-
name: "orgx_update_entity",
|
|
2667
|
-
description: "Update an existing OrgX entity by type and ID.",
|
|
2668
|
-
parameters: {
|
|
2669
|
-
type: "object",
|
|
2670
|
-
properties: {
|
|
2671
|
-
type: {
|
|
2672
|
-
type: "string",
|
|
2673
|
-
description: "Entity type",
|
|
2674
|
-
},
|
|
2675
|
-
id: {
|
|
2676
|
-
type: "string",
|
|
2677
|
-
description: "Entity UUID",
|
|
2678
|
-
},
|
|
2679
|
-
status: {
|
|
2680
|
-
type: "string",
|
|
2681
|
-
description: "New status",
|
|
2682
|
-
},
|
|
2683
|
-
title: {
|
|
2684
|
-
type: "string",
|
|
2685
|
-
description: "New title",
|
|
2686
|
-
},
|
|
2687
|
-
summary: {
|
|
2688
|
-
type: "string",
|
|
2689
|
-
description: "New summary",
|
|
2690
|
-
},
|
|
2691
|
-
},
|
|
2692
|
-
required: ["type", "id"],
|
|
2693
|
-
},
|
|
2694
|
-
async execute(_callId, params = {}) {
|
|
2695
|
-
try {
|
|
2696
|
-
const { type, id, ...updates } = params;
|
|
2697
|
-
const entity = await client.updateEntity(type, id, updates);
|
|
2698
|
-
return json(`✅ Updated ${type} ${id.slice(0, 8)}`, entity);
|
|
2699
|
-
}
|
|
2700
|
-
catch (err) {
|
|
2701
|
-
return text(`❌ Update failed: ${err instanceof Error ? err.message : err}`);
|
|
2702
|
-
}
|
|
2703
|
-
},
|
|
2704
|
-
}, { optional: true });
|
|
2705
|
-
// --- orgx_list_entities ---
|
|
2706
|
-
registerMcpTool({
|
|
2707
|
-
name: "orgx_list_entities",
|
|
2708
|
-
description: "List OrgX entities of a given type with optional status filter.",
|
|
2709
|
-
parameters: {
|
|
2710
|
-
type: "object",
|
|
2711
|
-
properties: {
|
|
2712
|
-
type: {
|
|
2713
|
-
type: "string",
|
|
2714
|
-
description: "Entity type: initiative, workstream, task, decision, agent",
|
|
2715
|
-
},
|
|
2716
|
-
status: {
|
|
2717
|
-
type: "string",
|
|
2718
|
-
description: "Filter by status",
|
|
2719
|
-
},
|
|
2720
|
-
limit: {
|
|
2721
|
-
type: "number",
|
|
2722
|
-
description: "Max results (default 20)",
|
|
2723
|
-
default: 20,
|
|
2724
|
-
},
|
|
2725
|
-
},
|
|
2726
|
-
required: ["type"],
|
|
2727
|
-
},
|
|
2728
|
-
async execute(_callId, params = { type: "" }) {
|
|
2729
|
-
try {
|
|
2730
|
-
const { type, ...filters } = params;
|
|
2731
|
-
const resp = await client.listEntities(type, filters);
|
|
2732
|
-
const entities = resp.data ?? resp;
|
|
2733
|
-
const count = Array.isArray(entities) ? entities.length : "?";
|
|
2734
|
-
return json(`${count} ${type}(s):`, entities);
|
|
2735
|
-
}
|
|
2736
|
-
catch (err) {
|
|
2737
|
-
return text(`❌ List failed: ${err instanceof Error ? err.message : err}`);
|
|
2738
|
-
}
|
|
2739
|
-
},
|
|
2740
|
-
}, { optional: true });
|
|
2741
|
-
function withProvenanceMetadata(metadata) {
|
|
2742
|
-
const input = metadata ?? {};
|
|
2743
|
-
const out = { ...input };
|
|
2744
|
-
if (out.orgx_plugin_version === undefined) {
|
|
2745
|
-
out.orgx_plugin_version = (config.pluginVersion ?? "").trim() || null;
|
|
2746
|
-
}
|
|
2747
|
-
try {
|
|
2748
|
-
const state = readSkillPackState();
|
|
2749
|
-
const overrides = state.overrides;
|
|
2750
|
-
if (out.skill_pack_name === undefined) {
|
|
2751
|
-
out.skill_pack_name = overrides?.name ?? state.pack?.name ?? null;
|
|
2752
|
-
}
|
|
2753
|
-
if (out.skill_pack_version === undefined) {
|
|
2754
|
-
out.skill_pack_version = overrides?.version ?? state.pack?.version ?? null;
|
|
2755
|
-
}
|
|
2756
|
-
if (out.skill_pack_checksum === undefined) {
|
|
2757
|
-
out.skill_pack_checksum = overrides?.checksum ?? state.pack?.checksum ?? null;
|
|
2758
|
-
}
|
|
2759
|
-
if (out.skill_pack_source === undefined) {
|
|
2760
|
-
out.skill_pack_source = overrides?.source ?? null;
|
|
2761
|
-
}
|
|
2762
|
-
if (out.skill_pack_etag === undefined) {
|
|
2763
|
-
out.skill_pack_etag = state.etag ?? null;
|
|
2764
|
-
}
|
|
2765
|
-
}
|
|
2766
|
-
catch {
|
|
2767
|
-
// best effort
|
|
2768
|
-
}
|
|
2769
|
-
if (out.orgx_provenance === undefined) {
|
|
2770
|
-
out.orgx_provenance = {
|
|
2771
|
-
plugin_version: out.orgx_plugin_version ?? null,
|
|
2772
|
-
skill_pack: {
|
|
2773
|
-
name: out.skill_pack_name ?? null,
|
|
2774
|
-
version: out.skill_pack_version ?? null,
|
|
2775
|
-
checksum: out.skill_pack_checksum ?? null,
|
|
2776
|
-
source: out.skill_pack_source ?? null,
|
|
2777
|
-
etag: out.skill_pack_etag ?? null,
|
|
2778
|
-
},
|
|
2779
|
-
};
|
|
2780
|
-
}
|
|
2781
|
-
return out;
|
|
2782
|
-
}
|
|
2783
|
-
async function emitActivityWithFallback(source, payload) {
|
|
2784
|
-
if (!payload.message || payload.message.trim().length === 0) {
|
|
2785
|
-
return text("❌ message is required");
|
|
2786
|
-
}
|
|
2787
|
-
const context = resolveReportingContext(payload);
|
|
2788
|
-
if (!context.ok) {
|
|
2789
|
-
return text(`❌ ${context.error}`);
|
|
2790
|
-
}
|
|
2791
|
-
const now = new Date().toISOString();
|
|
2792
|
-
const id = `progress:${randomUUID().slice(0, 8)}`;
|
|
2793
|
-
const normalizedPayload = {
|
|
2794
|
-
initiative_id: context.value.initiativeId,
|
|
2795
|
-
run_id: context.value.runId,
|
|
2796
|
-
correlation_id: context.value.correlationId,
|
|
2797
|
-
source_client: context.value.sourceClient,
|
|
2798
|
-
message: payload.message,
|
|
2799
|
-
phase: payload.phase ?? "execution",
|
|
2800
|
-
progress_pct: payload.progress_pct,
|
|
2801
|
-
level: payload.level ?? "info",
|
|
2802
|
-
next_step: payload.next_step,
|
|
2803
|
-
metadata: withProvenanceMetadata({
|
|
2804
|
-
...(payload.metadata ?? {}),
|
|
2805
|
-
source,
|
|
2806
|
-
}),
|
|
2807
|
-
};
|
|
2808
|
-
const activityItem = {
|
|
2809
|
-
id,
|
|
2810
|
-
type: "delegation",
|
|
2811
|
-
title: payload.message,
|
|
2812
|
-
description: payload.next_step ?? null,
|
|
2813
|
-
agentId: null,
|
|
2814
|
-
agentName: null,
|
|
2815
|
-
requesterAgentId: null,
|
|
2816
|
-
requesterAgentName: null,
|
|
2817
|
-
executorAgentId: null,
|
|
2818
|
-
executorAgentName: null,
|
|
2819
|
-
runId: context.value.runId ?? null,
|
|
2820
|
-
initiativeId: context.value.initiativeId,
|
|
2821
|
-
timestamp: now,
|
|
2822
|
-
phase: normalizedPayload.phase,
|
|
2823
|
-
summary: payload.next_step ? `Next: ${payload.next_step}` : payload.message,
|
|
2824
|
-
metadata: normalizedPayload.metadata,
|
|
2825
|
-
};
|
|
2826
|
-
try {
|
|
2827
|
-
const result = await client.emitActivity(normalizedPayload);
|
|
2828
|
-
return text(`Activity emitted: ${payload.message} [${normalizedPayload.phase}${payload.progress_pct != null ? ` ${payload.progress_pct}%` : ""}] (run ${result.run_id.slice(0, 8)}...)`);
|
|
2829
|
-
}
|
|
2830
|
-
catch {
|
|
2831
|
-
await appendToOutbox("progress", {
|
|
2832
|
-
id,
|
|
2833
|
-
type: "progress",
|
|
2834
|
-
timestamp: now,
|
|
2835
|
-
payload: normalizedPayload,
|
|
2836
|
-
activityItem,
|
|
2837
|
-
});
|
|
2838
|
-
return text(`Activity saved locally: ${payload.message} [${normalizedPayload.phase}${payload.progress_pct != null ? ` ${payload.progress_pct}%` : ""}] (will sync when connected)`);
|
|
2839
|
-
}
|
|
2840
|
-
}
|
|
2841
|
-
async function applyChangesetWithFallback(source, payload) {
|
|
2842
|
-
const context = resolveReportingContext(payload);
|
|
2843
|
-
if (!context.ok) {
|
|
2844
|
-
return text(`❌ ${context.error}`);
|
|
2845
|
-
}
|
|
2846
|
-
if (!Array.isArray(payload.operations) || payload.operations.length === 0) {
|
|
2847
|
-
return text("❌ operations must contain at least one change");
|
|
2848
|
-
}
|
|
2849
|
-
const idempotencyKey = pickNonEmptyString(payload.idempotency_key) ??
|
|
2850
|
-
`${source}:${Date.now()}:${randomUUID().slice(0, 8)}`;
|
|
2851
|
-
const requestPayload = {
|
|
2852
|
-
initiative_id: context.value.initiativeId,
|
|
2853
|
-
run_id: context.value.runId,
|
|
2854
|
-
correlation_id: context.value.correlationId,
|
|
2855
|
-
source_client: context.value.sourceClient,
|
|
2856
|
-
idempotency_key: idempotencyKey,
|
|
2857
|
-
operations: payload.operations,
|
|
2858
|
-
};
|
|
2859
|
-
const now = new Date().toISOString();
|
|
2860
|
-
const id = `changeset:${randomUUID().slice(0, 8)}`;
|
|
2861
|
-
const activityItem = {
|
|
2862
|
-
id,
|
|
2863
|
-
type: "milestone_completed",
|
|
2864
|
-
title: "Changeset queued",
|
|
2865
|
-
description: `${payload.operations.length} operation${payload.operations.length === 1 ? "" : "s"}`,
|
|
2866
|
-
agentId: null,
|
|
2867
|
-
agentName: null,
|
|
2868
|
-
requesterAgentId: null,
|
|
2869
|
-
requesterAgentName: null,
|
|
2870
|
-
executorAgentId: null,
|
|
2871
|
-
executorAgentName: null,
|
|
2872
|
-
runId: context.value.runId ?? null,
|
|
2873
|
-
initiativeId: context.value.initiativeId,
|
|
2874
|
-
timestamp: now,
|
|
2875
|
-
phase: "review",
|
|
2876
|
-
summary: `${payload.operations.length} operation${payload.operations.length === 1 ? "" : "s"}`,
|
|
2877
|
-
metadata: withProvenanceMetadata({
|
|
2878
|
-
source,
|
|
2879
|
-
idempotency_key: idempotencyKey,
|
|
2880
|
-
}),
|
|
2881
|
-
};
|
|
2882
|
-
try {
|
|
2883
|
-
const result = await client.applyChangeset(requestPayload);
|
|
2884
|
-
return text(`Changeset ${result.replayed ? "replayed" : "applied"}: ${result.applied_count} op${result.applied_count === 1 ? "" : "s"} (run ${result.run_id.slice(0, 8)}...)`);
|
|
2885
|
-
}
|
|
2886
|
-
catch {
|
|
2887
|
-
await appendToOutbox("decisions", {
|
|
2888
|
-
id,
|
|
2889
|
-
type: "changeset",
|
|
2890
|
-
timestamp: now,
|
|
2891
|
-
payload: requestPayload,
|
|
2892
|
-
activityItem,
|
|
2893
|
-
});
|
|
2894
|
-
return text(`Changeset saved locally (${payload.operations.length} op${payload.operations.length === 1 ? "" : "s"}) (will sync when connected)`);
|
|
2895
|
-
}
|
|
2896
|
-
}
|
|
2897
|
-
// --- orgx_emit_activity ---
|
|
2898
|
-
registerMcpTool({
|
|
2899
|
-
name: "orgx_emit_activity",
|
|
2900
|
-
description: "Emit append-only OrgX activity telemetry (launch reporting contract primary write tool).",
|
|
2901
|
-
parameters: {
|
|
2902
|
-
type: "object",
|
|
2903
|
-
properties: {
|
|
2904
|
-
initiative_id: {
|
|
2905
|
-
type: "string",
|
|
2906
|
-
description: "Initiative UUID (required unless ORGX_INITIATIVE_ID is set)",
|
|
2907
|
-
},
|
|
2908
|
-
message: {
|
|
2909
|
-
type: "string",
|
|
2910
|
-
description: "Human-readable activity update",
|
|
2911
|
-
},
|
|
2912
|
-
run_id: {
|
|
2913
|
-
type: "string",
|
|
2914
|
-
description: "Optional run UUID",
|
|
2915
|
-
},
|
|
2916
|
-
correlation_id: {
|
|
2917
|
-
type: "string",
|
|
2918
|
-
description: "Required when run_id is omitted",
|
|
2919
|
-
},
|
|
2920
|
-
source_client: {
|
|
2921
|
-
type: "string",
|
|
2922
|
-
enum: ["openclaw", "codex", "claude-code", "api"],
|
|
2923
|
-
description: "Required when run_id is omitted",
|
|
2924
|
-
},
|
|
2925
|
-
phase: {
|
|
2926
|
-
type: "string",
|
|
2927
|
-
enum: ["intent", "execution", "blocked", "review", "handoff", "completed"],
|
|
2928
|
-
description: "Reporting phase",
|
|
2929
|
-
},
|
|
2930
|
-
progress_pct: {
|
|
2931
|
-
type: "number",
|
|
2932
|
-
minimum: 0,
|
|
2933
|
-
maximum: 100,
|
|
2934
|
-
description: "Optional progress percentage",
|
|
2935
|
-
},
|
|
2936
|
-
level: {
|
|
2937
|
-
type: "string",
|
|
2938
|
-
enum: ["info", "warn", "error"],
|
|
2939
|
-
description: "Optional level (default info)",
|
|
2940
|
-
},
|
|
2941
|
-
next_step: {
|
|
2942
|
-
type: "string",
|
|
2943
|
-
description: "Optional next step",
|
|
2944
|
-
},
|
|
2945
|
-
metadata: {
|
|
2946
|
-
type: "object",
|
|
2947
|
-
description: "Optional structured metadata",
|
|
2948
|
-
},
|
|
2949
|
-
},
|
|
2950
|
-
required: ["message"],
|
|
2951
|
-
additionalProperties: false,
|
|
2952
|
-
},
|
|
2953
|
-
async execute(_callId, params = { message: "" }) {
|
|
2954
|
-
return emitActivityWithFallback("orgx_emit_activity", params);
|
|
2955
|
-
},
|
|
2956
|
-
}, { optional: true });
|
|
2957
|
-
// --- orgx_apply_changeset ---
|
|
2958
|
-
registerMcpTool({
|
|
2959
|
-
name: "orgx_apply_changeset",
|
|
2960
|
-
description: "Apply an idempotent transactional OrgX changeset (launch reporting contract primary mutation tool).",
|
|
2961
|
-
parameters: {
|
|
2962
|
-
type: "object",
|
|
2963
|
-
properties: {
|
|
2964
|
-
initiative_id: {
|
|
2965
|
-
type: "string",
|
|
2966
|
-
description: "Initiative UUID (required unless ORGX_INITIATIVE_ID is set)",
|
|
2967
|
-
},
|
|
2968
|
-
idempotency_key: {
|
|
2969
|
-
type: "string",
|
|
2970
|
-
description: "Idempotency key (<=120 chars). Auto-generated if omitted.",
|
|
2971
|
-
},
|
|
2972
|
-
operations: {
|
|
2973
|
-
type: "array",
|
|
2974
|
-
minItems: 1,
|
|
2975
|
-
maxItems: 25,
|
|
2976
|
-
description: "Changeset operations (task.create, task.update, milestone.update, decision.create)",
|
|
2977
|
-
items: { type: "object" },
|
|
2978
|
-
},
|
|
2979
|
-
run_id: {
|
|
2980
|
-
type: "string",
|
|
2981
|
-
description: "Optional run UUID",
|
|
2982
|
-
},
|
|
2983
|
-
correlation_id: {
|
|
2984
|
-
type: "string",
|
|
2985
|
-
description: "Required when run_id is omitted",
|
|
2986
|
-
},
|
|
2987
|
-
source_client: {
|
|
2988
|
-
type: "string",
|
|
2989
|
-
enum: ["openclaw", "codex", "claude-code", "api"],
|
|
2990
|
-
description: "Required when run_id is omitted",
|
|
2991
|
-
},
|
|
2992
|
-
},
|
|
2993
|
-
required: ["operations"],
|
|
2994
|
-
additionalProperties: false,
|
|
2995
|
-
},
|
|
2996
|
-
async execute(_callId, params = { operations: [] }) {
|
|
2997
|
-
return applyChangesetWithFallback("orgx_apply_changeset", params);
|
|
2998
|
-
},
|
|
2999
|
-
}, { optional: true });
|
|
3000
|
-
// --- orgx_report_progress (alias -> orgx_emit_activity) ---
|
|
3001
|
-
registerMcpTool({
|
|
3002
|
-
name: "orgx_report_progress",
|
|
3003
|
-
description: "Alias for orgx_emit_activity. Report progress at key milestones so the team can track your work.",
|
|
3004
|
-
parameters: {
|
|
3005
|
-
type: "object",
|
|
3006
|
-
properties: {
|
|
3007
|
-
initiative_id: {
|
|
3008
|
-
type: "string",
|
|
3009
|
-
description: "Initiative UUID (required unless ORGX_INITIATIVE_ID is set)",
|
|
3010
|
-
},
|
|
3011
|
-
run_id: {
|
|
3012
|
-
type: "string",
|
|
3013
|
-
description: "Optional run UUID",
|
|
3014
|
-
},
|
|
3015
|
-
correlation_id: {
|
|
3016
|
-
type: "string",
|
|
3017
|
-
description: "Required when run_id is omitted",
|
|
3018
|
-
},
|
|
3019
|
-
source_client: {
|
|
3020
|
-
type: "string",
|
|
3021
|
-
enum: ["openclaw", "codex", "claude-code", "api"],
|
|
3022
|
-
},
|
|
3023
|
-
summary: {
|
|
3024
|
-
type: "string",
|
|
3025
|
-
description: "What was accomplished (1-2 sentences, human-readable)",
|
|
3026
|
-
},
|
|
3027
|
-
phase: {
|
|
3028
|
-
type: "string",
|
|
3029
|
-
enum: ["researching", "implementing", "testing", "reviewing", "blocked"],
|
|
3030
|
-
description: "Current work phase",
|
|
3031
|
-
},
|
|
3032
|
-
progress_pct: {
|
|
3033
|
-
type: "number",
|
|
3034
|
-
description: "Progress percentage (0-100)",
|
|
3035
|
-
minimum: 0,
|
|
3036
|
-
maximum: 100,
|
|
3037
|
-
},
|
|
3038
|
-
next_step: {
|
|
3039
|
-
type: "string",
|
|
3040
|
-
description: "What you plan to do next",
|
|
3041
|
-
},
|
|
3042
|
-
},
|
|
3043
|
-
required: ["summary", "phase"],
|
|
3044
|
-
additionalProperties: false,
|
|
3045
|
-
},
|
|
3046
|
-
async execute(_callId, params = { summary: "", phase: "implementing" }) {
|
|
3047
|
-
return emitActivityWithFallback("orgx_report_progress", {
|
|
3048
|
-
initiative_id: params.initiative_id,
|
|
3049
|
-
run_id: params.run_id,
|
|
3050
|
-
correlation_id: params.correlation_id,
|
|
3051
|
-
source_client: params.source_client,
|
|
3052
|
-
message: params.summary,
|
|
3053
|
-
phase: toReportingPhase(params.phase, params.progress_pct),
|
|
3054
|
-
progress_pct: params.progress_pct,
|
|
3055
|
-
next_step: params.next_step,
|
|
3056
|
-
level: params.phase === "blocked" ? "warn" : "info",
|
|
3057
|
-
metadata: {
|
|
3058
|
-
legacy_phase: params.phase,
|
|
3059
|
-
},
|
|
3060
|
-
});
|
|
3061
|
-
},
|
|
3062
|
-
}, { optional: true });
|
|
3063
|
-
// --- orgx_request_decision (alias -> orgx_apply_changeset decision.create) ---
|
|
3064
|
-
registerMcpTool({
|
|
3065
|
-
name: "orgx_request_decision",
|
|
3066
|
-
description: "Alias for orgx_apply_changeset with decision.create. Request a human decision before proceeding.",
|
|
3067
|
-
parameters: {
|
|
3068
|
-
type: "object",
|
|
3069
|
-
properties: {
|
|
3070
|
-
initiative_id: {
|
|
3071
|
-
type: "string",
|
|
3072
|
-
description: "Initiative UUID (required unless ORGX_INITIATIVE_ID is set)",
|
|
3073
|
-
},
|
|
3074
|
-
run_id: {
|
|
3075
|
-
type: "string",
|
|
3076
|
-
description: "Optional run UUID",
|
|
3077
|
-
},
|
|
3078
|
-
correlation_id: {
|
|
3079
|
-
type: "string",
|
|
3080
|
-
description: "Required when run_id is omitted",
|
|
3081
|
-
},
|
|
3082
|
-
source_client: {
|
|
3083
|
-
type: "string",
|
|
3084
|
-
enum: ["openclaw", "codex", "claude-code", "api"],
|
|
3085
|
-
},
|
|
3086
|
-
question: {
|
|
3087
|
-
type: "string",
|
|
3088
|
-
description: "The decision question (e.g., 'Deploy to production?')",
|
|
3089
|
-
},
|
|
3090
|
-
context: {
|
|
3091
|
-
type: "string",
|
|
3092
|
-
description: "Background context to help the human decide",
|
|
3093
|
-
},
|
|
3094
|
-
options: {
|
|
3095
|
-
type: "array",
|
|
3096
|
-
items: { type: "string" },
|
|
3097
|
-
description: "Available choices (e.g., ['Yes, deploy now', 'Wait for more testing', 'Cancel'])",
|
|
3098
|
-
},
|
|
3099
|
-
urgency: {
|
|
3100
|
-
type: "string",
|
|
3101
|
-
enum: ["low", "medium", "high", "urgent"],
|
|
3102
|
-
description: "How urgent this decision is",
|
|
3103
|
-
},
|
|
3104
|
-
blocking: {
|
|
3105
|
-
type: "boolean",
|
|
3106
|
-
description: "Whether work should pause until this is decided (default: true)",
|
|
3107
|
-
},
|
|
3108
|
-
},
|
|
3109
|
-
required: ["question", "urgency"],
|
|
3110
|
-
additionalProperties: false,
|
|
3111
|
-
},
|
|
3112
|
-
async execute(_callId, params = { question: "", urgency: "medium" }) {
|
|
3113
|
-
const requestId = `decision:${randomUUID().slice(0, 8)}`;
|
|
3114
|
-
const changesetResult = await applyChangesetWithFallback("orgx_request_decision", {
|
|
3115
|
-
initiative_id: params.initiative_id,
|
|
3116
|
-
run_id: params.run_id,
|
|
3117
|
-
correlation_id: params.correlation_id,
|
|
3118
|
-
source_client: params.source_client,
|
|
3119
|
-
idempotency_key: `decision:${requestId}`,
|
|
3120
|
-
operations: [
|
|
3121
|
-
{
|
|
3122
|
-
op: "decision.create",
|
|
3123
|
-
title: params.question,
|
|
3124
|
-
summary: params.context,
|
|
3125
|
-
urgency: params.urgency,
|
|
3126
|
-
options: params.options,
|
|
3127
|
-
blocking: params.blocking ?? true,
|
|
3128
|
-
},
|
|
3129
|
-
],
|
|
3130
|
-
});
|
|
3131
|
-
await emitActivityWithFallback("orgx_request_decision", {
|
|
3132
|
-
initiative_id: params.initiative_id,
|
|
3133
|
-
run_id: params.run_id,
|
|
3134
|
-
correlation_id: params.correlation_id,
|
|
3135
|
-
source_client: params.source_client,
|
|
3136
|
-
message: `Decision requested: ${params.question}`,
|
|
3137
|
-
phase: "review",
|
|
3138
|
-
level: "info",
|
|
3139
|
-
metadata: {
|
|
3140
|
-
urgency: params.urgency,
|
|
3141
|
-
blocking: params.blocking ?? true,
|
|
3142
|
-
options: params.options ?? [],
|
|
3143
|
-
},
|
|
3144
|
-
});
|
|
3145
|
-
return changesetResult;
|
|
3146
|
-
},
|
|
3147
|
-
}, { optional: true });
|
|
3148
|
-
// --- orgx_register_artifact ---
|
|
3149
|
-
registerMcpTool({
|
|
3150
|
-
name: "orgx_register_artifact",
|
|
3151
|
-
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.",
|
|
3152
|
-
parameters: {
|
|
3153
|
-
type: "object",
|
|
3154
|
-
properties: {
|
|
3155
|
-
initiative_id: {
|
|
3156
|
-
type: "string",
|
|
3157
|
-
description: "Convenience: initiative UUID. Used as entity_type='initiative', entity_id=<this> when entity_type/entity_id are not provided.",
|
|
3158
|
-
},
|
|
3159
|
-
entity_type: {
|
|
3160
|
-
type: "string",
|
|
3161
|
-
enum: ["initiative", "milestone", "task", "decision", "project"],
|
|
3162
|
-
description: "The type of entity this artifact is attached to",
|
|
3163
|
-
},
|
|
3164
|
-
entity_id: {
|
|
3165
|
-
type: "string",
|
|
3166
|
-
description: "UUID of the entity this artifact is attached to",
|
|
3167
|
-
},
|
|
3168
|
-
name: {
|
|
3169
|
-
type: "string",
|
|
3170
|
-
description: "Human-readable artifact name (e.g., 'PR #107: Fix build size')",
|
|
3171
|
-
},
|
|
3172
|
-
artifact_type: {
|
|
3173
|
-
type: "string",
|
|
3174
|
-
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.",
|
|
3175
|
-
},
|
|
3176
|
-
description: {
|
|
3177
|
-
type: "string",
|
|
3178
|
-
description: "What this artifact is and why it matters",
|
|
3179
|
-
},
|
|
3180
|
-
url: {
|
|
3181
|
-
type: "string",
|
|
3182
|
-
description: "External link to the artifact (PR URL, file path, etc.)",
|
|
3183
|
-
},
|
|
3184
|
-
content: {
|
|
3185
|
-
type: "string",
|
|
3186
|
-
description: "Inline preview content (markdown/text). At least one of url or content is required.",
|
|
3187
|
-
},
|
|
3188
|
-
},
|
|
3189
|
-
required: ["name", "artifact_type"],
|
|
3190
|
-
additionalProperties: false,
|
|
3191
|
-
},
|
|
3192
|
-
async execute(_callId, params = { name: "", artifact_type: "other" }) {
|
|
3193
|
-
const now = new Date().toISOString();
|
|
3194
|
-
const id = `artifact:${randomUUID().slice(0, 8)}`;
|
|
3195
|
-
// Resolve entity association: explicit entity_type+entity_id > initiative_id > inferred
|
|
3196
|
-
let resolvedEntityType = null;
|
|
3197
|
-
let resolvedEntityId = null;
|
|
3198
|
-
if (params.entity_type && isUuid(params.entity_id)) {
|
|
3199
|
-
resolvedEntityType = params.entity_type;
|
|
3200
|
-
resolvedEntityId = params.entity_id;
|
|
3201
|
-
}
|
|
3202
|
-
else if (isUuid(params.initiative_id)) {
|
|
3203
|
-
resolvedEntityType = "initiative";
|
|
3204
|
-
resolvedEntityId = params.initiative_id;
|
|
3205
|
-
}
|
|
3206
|
-
else {
|
|
3207
|
-
const inferred = inferReportingInitiativeId(params);
|
|
3208
|
-
if (inferred) {
|
|
3209
|
-
resolvedEntityType = "initiative";
|
|
3210
|
-
resolvedEntityId = inferred;
|
|
3211
|
-
}
|
|
3212
|
-
}
|
|
3213
|
-
if (!resolvedEntityType || !resolvedEntityId) {
|
|
3214
|
-
return text("❌ Cannot register artifact: provide entity_type + entity_id, or initiative_id, so the artifact can be attached to an entity.");
|
|
3215
|
-
}
|
|
3216
|
-
if (!params.url && !params.content) {
|
|
3217
|
-
return text("❌ Cannot register artifact: provide at least one of url or content.");
|
|
3218
|
-
}
|
|
3219
|
-
const baseUrl = client.getBaseUrl();
|
|
3220
|
-
const artifactId = randomUUID();
|
|
3221
|
-
const activityItem = {
|
|
3222
|
-
id,
|
|
3223
|
-
type: "artifact_created",
|
|
3224
|
-
title: params.name,
|
|
3225
|
-
description: params.description ?? null,
|
|
3226
|
-
agentId: null,
|
|
3227
|
-
agentName: null,
|
|
3228
|
-
requesterAgentId: null,
|
|
3229
|
-
requesterAgentName: null,
|
|
3230
|
-
executorAgentId: null,
|
|
3231
|
-
executorAgentName: null,
|
|
3232
|
-
runId: null,
|
|
3233
|
-
initiativeId: resolvedEntityType === "initiative" ? resolvedEntityId : null,
|
|
3234
|
-
timestamp: now,
|
|
3235
|
-
summary: params.url ?? null,
|
|
3236
|
-
metadata: withProvenanceMetadata({
|
|
3237
|
-
source: "orgx_register_artifact",
|
|
3238
|
-
artifact_type: params.artifact_type,
|
|
3239
|
-
url: params.url,
|
|
3240
|
-
entity_type: resolvedEntityType,
|
|
3241
|
-
entity_id: resolvedEntityId,
|
|
3242
|
-
}),
|
|
3243
|
-
};
|
|
3244
|
-
try {
|
|
3245
|
-
const result = await registerArtifact(client, baseUrl, {
|
|
3246
|
-
artifact_id: artifactId,
|
|
3247
|
-
entity_type: resolvedEntityType,
|
|
3248
|
-
entity_id: resolvedEntityId,
|
|
3249
|
-
name: params.name,
|
|
3250
|
-
artifact_type: params.artifact_type,
|
|
3251
|
-
description: params.description ?? null,
|
|
3252
|
-
external_url: params.url ?? null,
|
|
3253
|
-
preview_markdown: params.content ?? null,
|
|
3254
|
-
status: "draft",
|
|
3255
|
-
metadata: {
|
|
3256
|
-
source: "orgx_register_artifact",
|
|
3257
|
-
artifact_id: artifactId,
|
|
3258
|
-
},
|
|
3259
|
-
validate_persistence: true,
|
|
3260
|
-
});
|
|
3261
|
-
if (!result.ok) {
|
|
3262
|
-
throw new Error(result.persistence.last_error ?? "Artifact registration failed");
|
|
3263
|
-
}
|
|
3264
|
-
activityItem.metadata = withProvenanceMetadata({
|
|
3265
|
-
...activityItem.metadata,
|
|
3266
|
-
artifact_id: result.artifact_id,
|
|
3267
|
-
entity_type: resolvedEntityType,
|
|
3268
|
-
entity_id: resolvedEntityId,
|
|
3269
|
-
});
|
|
3270
|
-
return json(`Artifact registered: ${params.name} [${params.artifact_type}] → ${resolvedEntityType}/${resolvedEntityId} (id: ${result.artifact_id})`, result);
|
|
3271
|
-
}
|
|
3272
|
-
catch (firstError) {
|
|
3273
|
-
// Outbox fallback for offline/error scenarios
|
|
3274
|
-
await appendToOutbox("artifacts", {
|
|
3275
|
-
id,
|
|
3276
|
-
type: "artifact",
|
|
3277
|
-
timestamp: now,
|
|
3278
|
-
payload: {
|
|
3279
|
-
artifact_id: artifactId,
|
|
3280
|
-
name: params.name,
|
|
3281
|
-
artifact_type: params.artifact_type,
|
|
3282
|
-
description: params.description,
|
|
3283
|
-
url: params.url,
|
|
3284
|
-
content: params.content,
|
|
3285
|
-
entity_type: resolvedEntityType,
|
|
3286
|
-
entity_id: resolvedEntityId,
|
|
3287
|
-
},
|
|
3288
|
-
activityItem,
|
|
3289
|
-
});
|
|
3290
|
-
return text(`Artifact saved locally: ${params.name} [${params.artifact_type}] (will sync when connected)`);
|
|
3291
|
-
}
|
|
3292
|
-
},
|
|
3293
|
-
}, { optional: true });
|
|
1358
|
+
const mcpToolRegistry = registerCoreTools({
|
|
1359
|
+
registerTool: api.registerTool.bind(api),
|
|
1360
|
+
client,
|
|
1361
|
+
config,
|
|
1362
|
+
getCachedSnapshot: () => cachedSnapshot,
|
|
1363
|
+
getLastSnapshotAt: () => lastSnapshotAt,
|
|
1364
|
+
doSync,
|
|
1365
|
+
text,
|
|
1366
|
+
json,
|
|
1367
|
+
formatSnapshot,
|
|
1368
|
+
autoAssignEntityForCreate,
|
|
1369
|
+
toReportingPhase,
|
|
1370
|
+
inferReportingInitiativeId,
|
|
1371
|
+
isUuid,
|
|
1372
|
+
pickNonEmptyString,
|
|
1373
|
+
resolveReportingContext,
|
|
1374
|
+
readSkillPackState,
|
|
1375
|
+
updateSkillPackPolicy,
|
|
1376
|
+
rollbackSkillPackPolicy,
|
|
1377
|
+
randomUUID,
|
|
1378
|
+
});
|
|
3294
1379
|
// ---------------------------------------------------------------------------
|
|
3295
1380
|
// 3. CLI Command
|
|
3296
1381
|
// ---------------------------------------------------------------------------
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
const snap = await client.getOrgSnapshot();
|
|
3305
|
-
console.log(formatSnapshot(snap));
|
|
3306
|
-
}
|
|
3307
|
-
catch (err) {
|
|
3308
|
-
console.error(`Error: ${err instanceof Error ? err.message : err}`);
|
|
3309
|
-
process.exit(1);
|
|
3310
|
-
}
|
|
3311
|
-
});
|
|
3312
|
-
cmd
|
|
3313
|
-
.command("sync")
|
|
3314
|
-
.description("Trigger manual memory sync")
|
|
3315
|
-
.option("--memory <text>", "Memory to push")
|
|
3316
|
-
.option("--daily-log <text>", "Daily log to push")
|
|
3317
|
-
.action(async (opts = {}) => {
|
|
3318
|
-
try {
|
|
3319
|
-
const resp = await client.syncMemory({
|
|
3320
|
-
memory: opts.memory,
|
|
3321
|
-
dailyLog: opts.dailyLog,
|
|
3322
|
-
});
|
|
3323
|
-
console.log("Sync complete:");
|
|
3324
|
-
console.log(` Initiatives: ${resp.initiatives?.length ?? 0}`);
|
|
3325
|
-
console.log(` Active tasks: ${resp.activeTasks?.length ?? 0}`);
|
|
3326
|
-
console.log(` Pending decisions: ${resp.pendingDecisions?.length ?? 0}`);
|
|
3327
|
-
}
|
|
3328
|
-
catch (err) {
|
|
3329
|
-
console.error(`Sync failed: ${err instanceof Error ? err.message : err}`);
|
|
3330
|
-
process.exit(1);
|
|
3331
|
-
}
|
|
3332
|
-
});
|
|
3333
|
-
cmd
|
|
3334
|
-
.command("doctor")
|
|
3335
|
-
.description("Run plugin diagnostics and connectivity checks")
|
|
3336
|
-
.option("--json", "Print the report as JSON")
|
|
3337
|
-
.option("--no-remote", "Skip remote OrgX API reachability probe")
|
|
3338
|
-
.action(async (opts = {}) => {
|
|
3339
|
-
try {
|
|
3340
|
-
const report = await buildHealthReport({
|
|
3341
|
-
probeRemote: opts.remote !== false,
|
|
3342
|
-
});
|
|
3343
|
-
if (opts.json) {
|
|
3344
|
-
console.log(JSON.stringify(report, null, 2));
|
|
3345
|
-
if (!report.ok)
|
|
3346
|
-
process.exit(1);
|
|
3347
|
-
return;
|
|
3348
|
-
}
|
|
3349
|
-
console.log("OrgX Doctor");
|
|
3350
|
-
console.log(` Status: ${report.status.toUpperCase()}`);
|
|
3351
|
-
console.log(` Plugin: v${report.plugin.version}`);
|
|
3352
|
-
console.log(` Base URL: ${report.plugin.baseUrl}`);
|
|
3353
|
-
console.log(` API Key Source: ${apiKeySourceLabel(report.auth.keySource)}`);
|
|
3354
|
-
console.log(` Outbox Pending: ${report.outbox.pendingTotal}`);
|
|
3355
|
-
console.log("");
|
|
3356
|
-
console.log("Checks:");
|
|
3357
|
-
for (const check of report.checks) {
|
|
3358
|
-
const prefix = check.status === "pass"
|
|
3359
|
-
? "[PASS]"
|
|
3360
|
-
: check.status === "warn"
|
|
3361
|
-
? "[WARN]"
|
|
3362
|
-
: "[FAIL]";
|
|
3363
|
-
console.log(` ${prefix} ${check.message}`);
|
|
3364
|
-
}
|
|
3365
|
-
if (report.remote.enabled) {
|
|
3366
|
-
if (report.remote.reachable === true) {
|
|
3367
|
-
console.log(` Remote probe latency: ${report.remote.latencyMs ?? "?"}ms`);
|
|
3368
|
-
}
|
|
3369
|
-
else if (report.remote.reachable === false) {
|
|
3370
|
-
console.log(` Remote probe error: ${report.remote.error ?? "Unknown error"}`);
|
|
3371
|
-
}
|
|
3372
|
-
else {
|
|
3373
|
-
console.log(" Remote probe: skipped");
|
|
3374
|
-
}
|
|
3375
|
-
}
|
|
3376
|
-
if (!report.ok) {
|
|
3377
|
-
process.exit(1);
|
|
3378
|
-
}
|
|
3379
|
-
}
|
|
3380
|
-
catch (err) {
|
|
3381
|
-
console.error(`Doctor failed: ${err instanceof Error ? err.message : err}`);
|
|
3382
|
-
process.exit(1);
|
|
3383
|
-
}
|
|
3384
|
-
});
|
|
3385
|
-
}, { commands: ["orgx"] });
|
|
1382
|
+
registerOrgxCli({
|
|
1383
|
+
registerCli: api.registerCli.bind(api),
|
|
1384
|
+
client,
|
|
1385
|
+
formatSnapshot,
|
|
1386
|
+
buildHealthReport,
|
|
1387
|
+
apiKeySourceLabel,
|
|
1388
|
+
});
|
|
3386
1389
|
// ---------------------------------------------------------------------------
|
|
3387
1390
|
// 4. HTTP Handler — Dashboard + API proxy
|
|
3388
1391
|
// ---------------------------------------------------------------------------
|