@useorgx/openclaw-plugin 0.7.6 → 0.7.11
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/dashboard/dist/assets/{CnitK1MX.js → AqVoI3SF.js} +1 -1
- package/dashboard/dist/assets/AqVoI3SF.js.br +0 -0
- package/dashboard/dist/assets/AqVoI3SF.js.gz +0 -0
- package/dashboard/dist/assets/BC4WvnHJ.js +1 -0
- package/dashboard/dist/assets/BC4WvnHJ.js.br +0 -0
- package/dashboard/dist/assets/BC4WvnHJ.js.gz +0 -0
- package/dashboard/dist/assets/{DOFL9l8s.js → BG5mwTkg.js} +1 -1
- package/dashboard/dist/assets/BG5mwTkg.js.br +0 -0
- package/dashboard/dist/assets/BG5mwTkg.js.gz +0 -0
- package/dashboard/dist/assets/BNh-XYPV.js +1 -0
- package/dashboard/dist/assets/BNh-XYPV.js.br +0 -0
- package/dashboard/dist/assets/BNh-XYPV.js.gz +0 -0
- package/dashboard/dist/assets/{CFwPph5U.js → BepW_590.js} +1 -1
- package/dashboard/dist/assets/BepW_590.js.br +0 -0
- package/dashboard/dist/assets/BepW_590.js.gz +0 -0
- package/dashboard/dist/assets/BerAfzjq.js +1 -0
- package/dashboard/dist/assets/BerAfzjq.js.br +0 -0
- package/dashboard/dist/assets/BerAfzjq.js.gz +0 -0
- package/dashboard/dist/assets/Bp3N-QL5.js +212 -0
- package/dashboard/dist/assets/Bp3N-QL5.js.br +0 -0
- package/dashboard/dist/assets/Bp3N-QL5.js.gz +0 -0
- package/dashboard/dist/assets/C3dZRz9P.css +1 -0
- package/dashboard/dist/assets/C3dZRz9P.css.br +0 -0
- package/dashboard/dist/assets/C3dZRz9P.css.gz +0 -0
- package/dashboard/dist/assets/CD-q5mdP.js +1 -0
- package/dashboard/dist/assets/CD-q5mdP.js.br +0 -0
- package/dashboard/dist/assets/CD-q5mdP.js.gz +0 -0
- package/dashboard/dist/assets/{BgcAY5rE.js → CdvjC9G9.js} +1 -1
- package/dashboard/dist/assets/CdvjC9G9.js.br +0 -0
- package/dashboard/dist/assets/CdvjC9G9.js.gz +0 -0
- package/dashboard/dist/assets/Ck2agw-s.js +1 -0
- package/dashboard/dist/assets/Ck2agw-s.js.br +0 -0
- package/dashboard/dist/assets/Ck2agw-s.js.gz +0 -0
- package/dashboard/dist/assets/{D7DHFX0D.js → D2CH1H6k.js} +1 -1
- package/dashboard/dist/assets/D2CH1H6k.js.br +0 -0
- package/dashboard/dist/assets/D2CH1H6k.js.gz +0 -0
- package/dashboard/dist/assets/D9esz7jd.js +1 -0
- package/dashboard/dist/assets/D9esz7jd.js.br +0 -0
- package/dashboard/dist/assets/D9esz7jd.js.gz +0 -0
- package/dashboard/dist/assets/{77gGFBt6.js → DCP-C7fn.js} +1 -1
- package/dashboard/dist/assets/DCP-C7fn.js.br +0 -0
- package/dashboard/dist/assets/DCP-C7fn.js.gz +0 -0
- package/dashboard/dist/assets/{CSr2ZnTV.js → DJASCd69.js} +1 -1
- package/dashboard/dist/assets/DJASCd69.js.br +0 -0
- package/dashboard/dist/assets/DJASCd69.js.gz +0 -0
- package/dashboard/dist/assets/Dm9AybAp.js +1 -0
- package/dashboard/dist/assets/Dm9AybAp.js.br +0 -0
- package/dashboard/dist/assets/Dm9AybAp.js.gz +0 -0
- package/dashboard/dist/assets/{DxKG5zy8.js → Du1wfrXa.js} +1 -1
- package/dashboard/dist/assets/Du1wfrXa.js.br +0 -0
- package/dashboard/dist/assets/Du1wfrXa.js.gz +0 -0
- package/dashboard/dist/assets/{DpuQm1oF.js → beHYBbh6.js} +1 -1
- package/dashboard/dist/assets/beHYBbh6.js.br +0 -0
- package/dashboard/dist/assets/beHYBbh6.js.gz +0 -0
- package/dashboard/dist/index.html +2 -2
- package/dashboard/dist/index.html.br +0 -0
- package/dashboard/dist/index.html.gz +0 -0
- package/dist/hash-utils.js +2 -1
- package/dist/http/helpers/auto-continue-engine.d.ts +36 -0
- package/dist/http/helpers/auto-continue-engine.js +83 -17
- package/dist/http/helpers/autopilot-runtime.d.ts +1 -0
- package/dist/http/helpers/autopilot-runtime.js +31 -3
- package/dist/http/helpers/autopilot-slice-utils.d.ts +10 -0
- package/dist/http/helpers/autopilot-slice-utils.js +58 -0
- package/dist/http/helpers/humanize-slice-failure.d.ts +35 -0
- package/dist/http/helpers/humanize-slice-failure.js +137 -0
- package/dist/http/helpers/mission-control.d.ts +1 -0
- package/dist/http/helpers/mission-control.js +72 -5
- package/dist/http/index.js +65 -3
- package/dist/http/routes/live-misc.js +12 -4
- package/dist/http/routes/live-snapshot.js +10 -4
- package/dist/http/routes/mission-control-actions.js +5 -0
- package/dist/http/routes/mission-control-read.d.ts +1 -0
- package/dist/http/routes/mission-control-read.js +65 -4
- package/dist/index.d.ts +8 -1
- package/dist/index.js +21 -1
- package/dist/mcp-http-handler.js +6 -0
- package/dist/openclaw.plugin.json +1 -1
- package/dist/tools/core-tools.d.ts +27 -0
- package/dist/tools/core-tools.js +89 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/dashboard/dist/assets/77gGFBt6.js.br +0 -0
- package/dashboard/dist/assets/77gGFBt6.js.gz +0 -0
- package/dashboard/dist/assets/BBpTN_SR.js +0 -1
- package/dashboard/dist/assets/BBpTN_SR.js.br +0 -0
- package/dashboard/dist/assets/BBpTN_SR.js.gz +0 -0
- package/dashboard/dist/assets/BVShoyjA.js +0 -1
- package/dashboard/dist/assets/BVShoyjA.js.br +0 -0
- package/dashboard/dist/assets/BVShoyjA.js.gz +0 -0
- package/dashboard/dist/assets/BgcAY5rE.js.br +0 -0
- package/dashboard/dist/assets/BgcAY5rE.js.gz +0 -0
- package/dashboard/dist/assets/C-PAoJF-.js +0 -1
- package/dashboard/dist/assets/C-PAoJF-.js.br +0 -0
- package/dashboard/dist/assets/C-PAoJF-.js.gz +0 -0
- package/dashboard/dist/assets/C0nA-iUG.js +0 -1
- package/dashboard/dist/assets/C0nA-iUG.js.br +0 -0
- package/dashboard/dist/assets/C0nA-iUG.js.gz +0 -0
- package/dashboard/dist/assets/C6GO-FKy.js +0 -1
- package/dashboard/dist/assets/C6GO-FKy.js.br +0 -0
- package/dashboard/dist/assets/C6GO-FKy.js.gz +0 -0
- package/dashboard/dist/assets/CFwPph5U.js.br +0 -0
- package/dashboard/dist/assets/CFwPph5U.js.gz +0 -0
- package/dashboard/dist/assets/CPjsbbgZ.js +0 -212
- package/dashboard/dist/assets/CPjsbbgZ.js.br +0 -0
- package/dashboard/dist/assets/CPjsbbgZ.js.gz +0 -0
- package/dashboard/dist/assets/CSr2ZnTV.js.br +0 -0
- package/dashboard/dist/assets/CSr2ZnTV.js.gz +0 -0
- package/dashboard/dist/assets/CgQDT6yL.js +0 -1
- package/dashboard/dist/assets/CgQDT6yL.js.br +0 -0
- package/dashboard/dist/assets/CgQDT6yL.js.gz +0 -0
- package/dashboard/dist/assets/CnitK1MX.js.br +0 -0
- package/dashboard/dist/assets/CnitK1MX.js.gz +0 -0
- package/dashboard/dist/assets/D7DHFX0D.js.br +0 -0
- package/dashboard/dist/assets/D7DHFX0D.js.gz +0 -0
- package/dashboard/dist/assets/DEip7uko.js +0 -1
- package/dashboard/dist/assets/DEip7uko.js.br +0 -0
- package/dashboard/dist/assets/DEip7uko.js.gz +0 -0
- package/dashboard/dist/assets/DHUSLc01.css +0 -1
- package/dashboard/dist/assets/DHUSLc01.css.br +0 -0
- package/dashboard/dist/assets/DHUSLc01.css.gz +0 -0
- package/dashboard/dist/assets/DOFL9l8s.js.br +0 -0
- package/dashboard/dist/assets/DOFL9l8s.js.gz +0 -0
- package/dashboard/dist/assets/DpuQm1oF.js.br +0 -0
- package/dashboard/dist/assets/DpuQm1oF.js.gz +0 -0
- package/dashboard/dist/assets/DxKG5zy8.js.br +0 -0
- package/dashboard/dist/assets/DxKG5zy8.js.gz +0 -0
|
@@ -9,10 +9,11 @@ import { appendTeamCompletion } from "../../team-context-store.js";
|
|
|
9
9
|
import { readOpenClawGatewayPort, readOpenClawSettingsSnapshot, } from "../../openclaw-settings.js";
|
|
10
10
|
import { resolveRuntimeHookToken, } from "../../runtime-instance-store.js";
|
|
11
11
|
import { detectMcpHandshakeFailure, shouldKillWorker } from "../../worker-supervisor.js";
|
|
12
|
+
import { humanizeSliceFailure, humanizeSliceFailureSummary } from "./humanize-slice-failure.js";
|
|
12
13
|
import { getOrgxPluginConfigDir } from "../../paths.js";
|
|
13
14
|
import { buildMissionControlGraph, DEFAULT_TOKEN_BUDGET_ASSUMPTIONS, dedupeStrings, detectBehaviorConfigDrift, deriveBehaviorAutomationLevel, deriveBehaviorConfigContext, deriveExecutionPolicy, evaluateScopeCompletion, isDispatchableWorkstreamStatus, isDoneStatus, isTodoStatus, readBudgetEnvNumber, selectSliceTasksByScope, SLICE_SCOPE_TIMEOUT_MULTIPLIER, spawnGuardIsRateLimited, summarizeSpawnGuardBlockReason, } from "./mission-control.js";
|
|
14
15
|
import { createAutopilotRuntime } from "./autopilot-runtime.js";
|
|
15
|
-
import { buildScopeDirective, buildSliceOutputInstructions, buildWorkstreamSlicePrompt, createCodexBinResolver, ensureAutopilotSliceSchemaPath, fileUpdatedAtEpochMs, parseSliceResult, readFileTailSafe, readSliceOutputFile, } from "./autopilot-slice-utils.js";
|
|
16
|
+
import { buildScopeDirective, buildSliceOutputInstructions, buildWorkstreamSlicePrompt, createCodexBinResolver, ensureAutopilotSliceSchemaPath, extractSessionIdFromLog, extractSessionIdFromOutput, fileUpdatedAtEpochMs, parseSliceResult, readFileTailSafe, readSliceOutputFile, } from "./autopilot-slice-utils.js";
|
|
16
17
|
import { pickString } from "./value-utils.js";
|
|
17
18
|
function resolveAutopilotDefaultCwd(filename) {
|
|
18
19
|
let cursor = dirname(filename);
|
|
@@ -63,6 +64,35 @@ export function createAutoContinueEngine(deps) {
|
|
|
63
64
|
? "reject"
|
|
64
65
|
: "approve";
|
|
65
66
|
const autoContinueSliceRuns = new Map();
|
|
67
|
+
const workstreamSessionStore = new Map();
|
|
68
|
+
function sessionResumeEnabled() {
|
|
69
|
+
const raw = (process.env.ORGX_AUTOPILOT_SESSION_RESUME ?? "").trim().toLowerCase();
|
|
70
|
+
if (!raw)
|
|
71
|
+
return false;
|
|
72
|
+
return !(raw === "0" || raw === "false" || raw === "no" || raw === "off");
|
|
73
|
+
}
|
|
74
|
+
function setWorkstreamSession(workstreamId, entry) {
|
|
75
|
+
workstreamSessionStore.set(workstreamId, entry);
|
|
76
|
+
}
|
|
77
|
+
function getWorkstreamSession(workstreamId) {
|
|
78
|
+
return workstreamSessionStore.get(workstreamId) ?? null;
|
|
79
|
+
}
|
|
80
|
+
function clearWorkstreamSession(initiativeId) {
|
|
81
|
+
for (const [key, entry] of workstreamSessionStore.entries()) {
|
|
82
|
+
if (entry.initiativeId === initiativeId) {
|
|
83
|
+
workstreamSessionStore.delete(key);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function listWorkstreamSessions(initiativeId) {
|
|
88
|
+
const results = [];
|
|
89
|
+
for (const entry of workstreamSessionStore.values()) {
|
|
90
|
+
if (!initiativeId || entry.initiativeId === initiativeId) {
|
|
91
|
+
results.push(entry);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return results;
|
|
95
|
+
}
|
|
66
96
|
/** Spread into any metadata object to flag mock-worker activity. */
|
|
67
97
|
function mockMeta(slice) {
|
|
68
98
|
return slice.isMockWorker ? { mock: true } : {};
|
|
@@ -1700,8 +1730,8 @@ export function createAutoContinueEngine(deps) {
|
|
|
1700
1730
|
const decisionResult = await requestDecisionQueued({
|
|
1701
1731
|
initiativeId: run.initiativeId,
|
|
1702
1732
|
correlationId: slice.runId,
|
|
1703
|
-
title: `
|
|
1704
|
-
summary: `MCP handshake failed${mcpHandshake.server ? ` for ${mcpHandshake.server}` : ""}
|
|
1733
|
+
title: `Agent couldn't connect to tools: ${slice.workstreamTitle ?? slice.workstreamId}`,
|
|
1734
|
+
summary: humanizeSliceFailureSummary(`MCP handshake failed${mcpHandshake.server ? ` for ${mcpHandshake.server}` : ""}.`),
|
|
1705
1735
|
urgency: "high",
|
|
1706
1736
|
options: [
|
|
1707
1737
|
"Retry this workstream slice",
|
|
@@ -1819,8 +1849,8 @@ export function createAutoContinueEngine(deps) {
|
|
|
1819
1849
|
const decisionResult = await requestDecisionQueued({
|
|
1820
1850
|
initiativeId: run.initiativeId,
|
|
1821
1851
|
correlationId: slice.runId,
|
|
1822
|
-
title: `
|
|
1823
|
-
summary:
|
|
1852
|
+
title: `Agent ${humanLabel === "timed out" ? "ran out of time" : "stopped making progress"}: ${slice.workstreamTitle ?? slice.workstreamId}`,
|
|
1853
|
+
summary: humanizeSliceFailureSummary(slice.lastError ?? `Autopilot slice ${humanLabel}`),
|
|
1824
1854
|
urgency: "high",
|
|
1825
1855
|
options: [
|
|
1826
1856
|
"Retry this workstream slice",
|
|
@@ -1904,6 +1934,25 @@ export function createAutoContinueEngine(deps) {
|
|
|
1904
1934
|
const raw = readSliceOutputFile(slice.outputPath);
|
|
1905
1935
|
const parsed = raw ? parseSliceResult(raw) : null;
|
|
1906
1936
|
const parsedStatus = parsed?.status ?? "error";
|
|
1937
|
+
// Session capture: extract CLI session ID from output or log for future resume.
|
|
1938
|
+
if (sessionResumeEnabled()) {
|
|
1939
|
+
const outputSessionId = raw ? extractSessionIdFromOutput(raw, slice.sourceClient) : null;
|
|
1940
|
+
const logSessionId = outputSessionId
|
|
1941
|
+
? null
|
|
1942
|
+
: extractSessionIdFromLog(readFileTailSafe(slice.logPath, 32_000), slice.sourceClient);
|
|
1943
|
+
const capturedSessionId = outputSessionId ?? logSessionId ?? null;
|
|
1944
|
+
if (capturedSessionId) {
|
|
1945
|
+
slice.cliSessionId = capturedSessionId;
|
|
1946
|
+
setWorkstreamSession(slice.workstreamId, {
|
|
1947
|
+
sessionId: capturedSessionId,
|
|
1948
|
+
workstreamId: slice.workstreamId,
|
|
1949
|
+
initiativeId: slice.initiativeId,
|
|
1950
|
+
sourceClient: slice.sourceClient,
|
|
1951
|
+
capturedAt: new Date().toISOString(),
|
|
1952
|
+
fromRunId: slice.runId,
|
|
1953
|
+
});
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1907
1956
|
const defaultDecisionBlocking = parsedStatus === "completed" ? false : true;
|
|
1908
1957
|
const allDecisions = Array.isArray(parsed?.decisions_needed)
|
|
1909
1958
|
? (parsed?.decisions_needed ?? [])
|
|
@@ -2335,17 +2384,18 @@ export function createAutoContinueEngine(deps) {
|
|
|
2335
2384
|
};
|
|
2336
2385
|
if (!blockingDecisionQueued) {
|
|
2337
2386
|
const blockedLike = slice.status === "blocked";
|
|
2387
|
+
const fallbackRawError = parsed?.summary ?? slice.lastError ??
|
|
2388
|
+
(blockedLike
|
|
2389
|
+
? "Execution is blocked and needs intervention."
|
|
2390
|
+
: "Agent process exited without a valid output contract.");
|
|
2391
|
+
const fallbackHumanized = humanizeSliceFailure(fallbackRawError);
|
|
2338
2392
|
fallbackDecisionResult = await requestDecisionQueued({
|
|
2339
2393
|
initiativeId: run.initiativeId,
|
|
2340
2394
|
correlationId: slice.runId,
|
|
2341
2395
|
title: blockedLike
|
|
2342
|
-
? `
|
|
2343
|
-
:
|
|
2344
|
-
summary:
|
|
2345
|
-
slice.lastError ??
|
|
2346
|
-
(blockedLike
|
|
2347
|
-
? "The slice reported a blocked/decision-required state without a blocking decision payload. Review logs/output and decide whether to retry, unblock, or skip."
|
|
2348
|
-
: "The slice failed without producing a valid output contract. Review logs/output and decide whether to retry or pause autopilot."),
|
|
2396
|
+
? `Agent needs your help: ${slice.workstreamTitle ?? slice.workstreamId}`
|
|
2397
|
+
: `${fallbackHumanized.headline}: ${slice.workstreamTitle ?? slice.workstreamId}`,
|
|
2398
|
+
summary: fallbackHumanized.explanation,
|
|
2349
2399
|
urgency: "high",
|
|
2350
2400
|
options: [
|
|
2351
2401
|
"Retry this workstream slice",
|
|
@@ -2421,12 +2471,14 @@ export function createAutoContinueEngine(deps) {
|
|
|
2421
2471
|
decisions.length === 0 &&
|
|
2422
2472
|
statusUpdateResult.applied === 0;
|
|
2423
2473
|
if (!parsed || parsedStatus === "error" || completionHadNoOutcome) {
|
|
2474
|
+
const rawError = slice.lastError ?? (completionHadNoOutcome
|
|
2475
|
+
? "Completed without verifiable outcomes or artifacts."
|
|
2476
|
+
: "Agent process exited without a valid output contract.");
|
|
2477
|
+
const humanized = humanizeSliceFailure(rawError);
|
|
2424
2478
|
const attentionTitle = completionHadNoOutcome
|
|
2425
|
-
? `
|
|
2426
|
-
:
|
|
2427
|
-
const attentionSummary =
|
|
2428
|
-
? "The slice reported completion but did not produce artifacts or status updates. Decide whether to retry, request stronger output, or mark tasks manually."
|
|
2429
|
-
: "The slice exited without a valid output contract. Review logs/output and decide whether to retry or pause autopilot.";
|
|
2479
|
+
? `Agent finished but produced nothing: ${slice.workstreamTitle ?? slice.workstreamId}`
|
|
2480
|
+
: `${humanized.headline}: ${slice.workstreamTitle ?? slice.workstreamId}`;
|
|
2481
|
+
const attentionSummary = humanized.explanation;
|
|
2430
2482
|
const decisionResult = await requestDecisionQueued({
|
|
2431
2483
|
initiativeId: run.initiativeId,
|
|
2432
2484
|
correlationId: slice.runId,
|
|
@@ -3463,6 +3515,9 @@ export function createAutoContinueEngine(deps) {
|
|
|
3463
3515
|
catch {
|
|
3464
3516
|
// best effort
|
|
3465
3517
|
}
|
|
3518
|
+
// Session resume: check if a previous session exists for this workstream.
|
|
3519
|
+
const priorSession = sessionResumeEnabled() ? getWorkstreamSession(selectedWorkstreamId) : null;
|
|
3520
|
+
const resumedFromSessionId = priorSession?.sessionId ?? null;
|
|
3466
3521
|
const spawned = spawnCodexSliceWorker({
|
|
3467
3522
|
runId: sliceRunId,
|
|
3468
3523
|
prompt,
|
|
@@ -3470,6 +3525,7 @@ export function createAutoContinueEngine(deps) {
|
|
|
3470
3525
|
logPath,
|
|
3471
3526
|
outputPath,
|
|
3472
3527
|
outputSchemaPath: schemaPath,
|
|
3528
|
+
resumeSessionId: resumedFromSessionId,
|
|
3473
3529
|
env: {
|
|
3474
3530
|
ORGX_SOURCE_CLIENT: executorSourceClient,
|
|
3475
3531
|
ORGX_RUN_ID: sliceRunId,
|
|
@@ -3523,6 +3579,8 @@ export function createAutoContinueEngine(deps) {
|
|
|
3523
3579
|
scopeMilestoneIds: scopeMilestoneIds,
|
|
3524
3580
|
lastError: null,
|
|
3525
3581
|
isMockWorker: workerKind === "mock",
|
|
3582
|
+
cliSessionId: null,
|
|
3583
|
+
resumedFromSessionId,
|
|
3526
3584
|
};
|
|
3527
3585
|
autoContinueSliceRuns.set(sliceRunId, slice);
|
|
3528
3586
|
try {
|
|
@@ -4094,6 +4152,7 @@ export function createAutoContinueEngine(deps) {
|
|
|
4094
4152
|
run.activeTaskId = null;
|
|
4095
4153
|
run.activeRunId = null;
|
|
4096
4154
|
run.activeTaskTokenEstimate = null;
|
|
4155
|
+
clearWorkstreamSession(input.initiativeId);
|
|
4097
4156
|
}
|
|
4098
4157
|
syncLegacyRunPointers(run);
|
|
4099
4158
|
autoContinueRuns.set(input.initiativeId, run);
|
|
@@ -4191,5 +4250,12 @@ export function createAutoContinueEngine(deps) {
|
|
|
4191
4250
|
getAutoContinueLaneForWorkstream,
|
|
4192
4251
|
scheduleAutoFixForWorkstream,
|
|
4193
4252
|
startAutoContinueRun,
|
|
4253
|
+
// Session store (for resume support)
|
|
4254
|
+
workstreamSessionStore,
|
|
4255
|
+
getWorkstreamSession,
|
|
4256
|
+
setWorkstreamSession,
|
|
4257
|
+
clearWorkstreamSession,
|
|
4258
|
+
listWorkstreamSessions,
|
|
4259
|
+
sessionResumeEnabled,
|
|
4194
4260
|
};
|
|
4195
4261
|
}
|
|
@@ -190,6 +190,12 @@ export function createAutopilotRuntime(deps) {
|
|
|
190
190
|
return false;
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
|
+
function sessionResumeEnabled() {
|
|
194
|
+
const raw = (process.env.ORGX_AUTOPILOT_SESSION_RESUME ?? "").trim().toLowerCase();
|
|
195
|
+
if (!raw)
|
|
196
|
+
return false;
|
|
197
|
+
return !(raw === "0" || raw === "false" || raw === "no" || raw === "off");
|
|
198
|
+
}
|
|
193
199
|
function spawnCodexSliceWorker(input) {
|
|
194
200
|
ensurePrivateDirForFile(input.logPath);
|
|
195
201
|
ensurePrivateDirForFile(input.outputPath);
|
|
@@ -335,8 +341,15 @@ export function createAutopilotRuntime(deps) {
|
|
|
335
341
|
claudeExtraArgs.push("--print");
|
|
336
342
|
if (!hasOutputFormat)
|
|
337
343
|
claudeExtraArgs.push("--output-format", "json");
|
|
338
|
-
|
|
344
|
+
// Session resume: when resumeSessionId is provided, use --resume and skip --no-session-persistence.
|
|
345
|
+
// When feature is enabled but no resume, omit --no-session-persistence to persist for future resume.
|
|
346
|
+
const resumeId = typeof input.resumeSessionId === "string" ? input.resumeSessionId.trim() : "";
|
|
347
|
+
if (resumeId) {
|
|
348
|
+
claudeExtraArgs.push("--resume", resumeId);
|
|
349
|
+
}
|
|
350
|
+
else if (!sessionResumeEnabled() && !hasNoSessionPersistence) {
|
|
339
351
|
claudeExtraArgs.push("--no-session-persistence");
|
|
352
|
+
}
|
|
340
353
|
if (!hasPermissionMode)
|
|
341
354
|
claudeExtraArgs.push("--permission-mode", "bypassPermissions");
|
|
342
355
|
if (!hasDangerousSkipPermissions && !hasAllowDangerousSkipPermissions) {
|
|
@@ -469,10 +482,25 @@ export function createAutopilotRuntime(deps) {
|
|
|
469
482
|
}
|
|
470
483
|
const codexInfo = deps.resolveCodexBinInfo();
|
|
471
484
|
const codexBin = codexInfo.bin;
|
|
472
|
-
const
|
|
485
|
+
const codexResumeId = typeof input.resumeSessionId === "string" ? input.resumeSessionId.trim() : "";
|
|
486
|
+
// Session resume: use "resume <id>" subcommand instead of "exec --ephemeral"
|
|
487
|
+
// When feature enabled but no resume ID: omit --ephemeral to persist for future resume
|
|
488
|
+
let rawArgs;
|
|
489
|
+
let defaultArgs;
|
|
490
|
+
if (codexResumeId) {
|
|
491
|
+
rawArgs = "";
|
|
492
|
+
defaultArgs = ["resume", codexResumeId, "--full-auto", "--skip-git-repo-check"];
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
rawArgs = (process.env.ORGX_CODEX_ARGS ?? "").trim();
|
|
496
|
+
const featureEnabled = sessionResumeEnabled();
|
|
497
|
+
defaultArgs = featureEnabled
|
|
498
|
+
? ["exec", "--full-auto", "--skip-git-repo-check"]
|
|
499
|
+
: ["exec", "--ephemeral", "--full-auto", "--skip-git-repo-check"];
|
|
500
|
+
}
|
|
473
501
|
const normalizedArgs = normalizeCodexArgs(rawArgs.length > 0
|
|
474
502
|
? rawArgs.split(/\s+/).filter(Boolean)
|
|
475
|
-
:
|
|
503
|
+
: defaultArgs);
|
|
476
504
|
const args = hasExplicitCodexSubcommand(normalizedArgs)
|
|
477
505
|
? normalizedArgs
|
|
478
506
|
: ["exec", ...normalizedArgs];
|
|
@@ -2,6 +2,16 @@ export declare function ensureAutopilotSliceSchemaPath(schemaFilename: string):
|
|
|
2
2
|
export declare function parseSliceResult<T extends object>(raw: string): T | null;
|
|
3
3
|
export declare function readSliceOutputFile(pathname: string): string | null;
|
|
4
4
|
export declare function readFileTailSafe(pathname: string, maxChars?: number): string;
|
|
5
|
+
/**
|
|
6
|
+
* Extract a CLI session ID from structured output JSON (Claude/Codex envelope).
|
|
7
|
+
* Looks for `session_id`, `sessionId`, or `conversation_id` fields.
|
|
8
|
+
*/
|
|
9
|
+
export declare function extractSessionIdFromOutput(raw: string, _sourceClient?: string): string | null;
|
|
10
|
+
/**
|
|
11
|
+
* Extract a CLI session ID from worker log text.
|
|
12
|
+
* Matches patterns emitted by Claude Code and Codex CLIs.
|
|
13
|
+
*/
|
|
14
|
+
export declare function extractSessionIdFromLog(logContent: string, _sourceClient?: string): string | null;
|
|
5
15
|
export declare function fileUpdatedAtEpochMs(pathname: string, fallbackEpochMs: number): number;
|
|
6
16
|
export type CodexBinInfo = {
|
|
7
17
|
bin: string;
|
|
@@ -470,6 +470,64 @@ export function readFileTailSafe(pathname, maxChars = 64_000) {
|
|
|
470
470
|
return "";
|
|
471
471
|
}
|
|
472
472
|
}
|
|
473
|
+
// ---------------------------------------------------------------------------
|
|
474
|
+
// Session ID extraction (for session resume support)
|
|
475
|
+
// ---------------------------------------------------------------------------
|
|
476
|
+
const UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;
|
|
477
|
+
/**
|
|
478
|
+
* Extract a CLI session ID from structured output JSON (Claude/Codex envelope).
|
|
479
|
+
* Looks for `session_id`, `sessionId`, or `conversation_id` fields.
|
|
480
|
+
*/
|
|
481
|
+
export function extractSessionIdFromOutput(raw, _sourceClient) {
|
|
482
|
+
if (!raw || typeof raw !== "string")
|
|
483
|
+
return null;
|
|
484
|
+
const parsed = parseJsonSafe(raw.trim());
|
|
485
|
+
if (parsed && typeof parsed === "object") {
|
|
486
|
+
// Walk common envelope shapes: top-level, result, structured_output, final_output
|
|
487
|
+
const candidates = [
|
|
488
|
+
parsed,
|
|
489
|
+
parsed.result,
|
|
490
|
+
parsed.structured_output,
|
|
491
|
+
parsed.final_output,
|
|
492
|
+
];
|
|
493
|
+
for (const obj of candidates) {
|
|
494
|
+
if (!obj || typeof obj !== "object")
|
|
495
|
+
continue;
|
|
496
|
+
const record = obj;
|
|
497
|
+
for (const key of ["session_id", "sessionId", "conversation_id"]) {
|
|
498
|
+
const value = record[key];
|
|
499
|
+
if (typeof value === "string" && UUID_RE.test(value))
|
|
500
|
+
return value.trim();
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
// Fallback: scan raw text for session_id: <uuid> pattern
|
|
505
|
+
const inline = raw.match(/session_id\s*[:=]\s*"?([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})"?/i);
|
|
506
|
+
if (inline && typeof inline[1] === "string")
|
|
507
|
+
return inline[1];
|
|
508
|
+
return null;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Extract a CLI session ID from worker log text.
|
|
512
|
+
* Matches patterns emitted by Claude Code and Codex CLIs.
|
|
513
|
+
*/
|
|
514
|
+
export function extractSessionIdFromLog(logContent, _sourceClient) {
|
|
515
|
+
if (!logContent || typeof logContent !== "string")
|
|
516
|
+
return null;
|
|
517
|
+
// Claude Code: "Resume this session with: claude --resume <uuid>"
|
|
518
|
+
const claudeResume = logContent.match(/claude\s+--resume\s+([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i);
|
|
519
|
+
if (claudeResume && typeof claudeResume[1] === "string")
|
|
520
|
+
return claudeResume[1];
|
|
521
|
+
// Codex: "codex resume <uuid>"
|
|
522
|
+
const codexResume = logContent.match(/codex\s+resume\s+([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i);
|
|
523
|
+
if (codexResume && typeof codexResume[1] === "string")
|
|
524
|
+
return codexResume[1];
|
|
525
|
+
// Generic: "Session: <uuid>", "session_id: <uuid>", "saving session <uuid>"
|
|
526
|
+
const generic = logContent.match(/(?:Session|session_id|saving\s+session)\s*[:=]?\s*([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i);
|
|
527
|
+
if (generic && typeof generic[1] === "string")
|
|
528
|
+
return generic[1];
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
473
531
|
export function fileUpdatedAtEpochMs(pathname, fallbackEpochMs) {
|
|
474
532
|
try {
|
|
475
533
|
const st = statSync(pathname);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translates raw auto-continue engine error strings into human-readable
|
|
3
|
+
* decision context suitable for operator display.
|
|
4
|
+
*
|
|
5
|
+
* Used at decision creation time to enrich the `summary` field and
|
|
6
|
+
* generate structured options when the engine produces terse diagnostics.
|
|
7
|
+
*/
|
|
8
|
+
export interface HumanizedSliceFailure {
|
|
9
|
+
/** One-line human headline, e.g. "Agent work stopped unexpectedly" */
|
|
10
|
+
headline: string;
|
|
11
|
+
/** Multi-sentence explanation for operators */
|
|
12
|
+
explanation: string;
|
|
13
|
+
/** Parsed structured data extracted from the raw string */
|
|
14
|
+
structuredDetails: {
|
|
15
|
+
exitCode?: string;
|
|
16
|
+
signal?: string;
|
|
17
|
+
elapsedMs?: string;
|
|
18
|
+
idleMs?: string;
|
|
19
|
+
server?: string;
|
|
20
|
+
reason?: string;
|
|
21
|
+
};
|
|
22
|
+
/** Severity for UI theming */
|
|
23
|
+
severity: "critical" | "warning" | "info";
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Translate a raw auto-continue error/context string into a human-friendly
|
|
27
|
+
* decision description. Falls back gracefully for unrecognised patterns.
|
|
28
|
+
*/
|
|
29
|
+
export declare function humanizeSliceFailure(raw: string): HumanizedSliceFailure;
|
|
30
|
+
/**
|
|
31
|
+
* Generate a human-friendly summary string from a raw error, suitable for
|
|
32
|
+
* the `summary` field when creating decisions. Combines the headline and
|
|
33
|
+
* explanation into a concise paragraph.
|
|
34
|
+
*/
|
|
35
|
+
export declare function humanizeSliceFailureSummary(raw: string): string;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translates raw auto-continue engine error strings into human-readable
|
|
3
|
+
* decision context suitable for operator display.
|
|
4
|
+
*
|
|
5
|
+
* Used at decision creation time to enrich the `summary` field and
|
|
6
|
+
* generate structured options when the engine produces terse diagnostics.
|
|
7
|
+
*/
|
|
8
|
+
const PATTERNS = [
|
|
9
|
+
{
|
|
10
|
+
test: /Worker exited without structured output.*?code=(\S+?),\s*signal=(\S+?)\)/i,
|
|
11
|
+
headline: "Agent work stopped unexpectedly",
|
|
12
|
+
explain: (_raw, m) => {
|
|
13
|
+
const code = m[1] === "null" ? "none" : m[1];
|
|
14
|
+
const signal = m[2] === "null" ? "none" : m[2];
|
|
15
|
+
const parts = [
|
|
16
|
+
"The agent process exited before producing results.",
|
|
17
|
+
];
|
|
18
|
+
if (signal !== "none" && signal !== "null") {
|
|
19
|
+
parts.push(`It was terminated by signal ${signal}, which usually means the worker was stopped externally or ran out of resources.`);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
parts.push("This usually means the worker crashed or was terminated before completing.");
|
|
23
|
+
}
|
|
24
|
+
parts.push(`Exit code: ${code}. Signal: ${signal}.`);
|
|
25
|
+
return parts.join(" ");
|
|
26
|
+
},
|
|
27
|
+
severity: "critical",
|
|
28
|
+
extractDetails: (_raw, m) => ({
|
|
29
|
+
exitCode: m[1] === "null" ? undefined : m[1],
|
|
30
|
+
signal: m[2] === "null" ? undefined : m[2],
|
|
31
|
+
}),
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
test: /MCP handshake failed(?:\s+for\s+(\S+))?/i,
|
|
35
|
+
headline: "Agent couldn't connect to tools",
|
|
36
|
+
explain: (_raw, m) => {
|
|
37
|
+
const server = m[1] ? ` (${m[1]})` : "";
|
|
38
|
+
return `The agent failed to establish a connection with its tool server${server}. This typically means the MCP server is unreachable, misconfigured, or took too long to respond. Retrying often resolves transient connection issues.`;
|
|
39
|
+
},
|
|
40
|
+
severity: "critical",
|
|
41
|
+
extractDetails: (_raw, m) => ({
|
|
42
|
+
server: m[1] || undefined,
|
|
43
|
+
}),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
test: /(?:Slice|Autopilot slice) timed out after (\d+)/i,
|
|
47
|
+
headline: "Agent ran out of time",
|
|
48
|
+
explain: (_raw, m) => {
|
|
49
|
+
const ms = parseInt(m[1], 10);
|
|
50
|
+
const mins = Math.round(ms / 60_000);
|
|
51
|
+
return `The agent exceeded its ${mins > 0 ? `${mins}-minute` : `${ms}ms`} time limit without completing. This can happen when the task is too large for a single work slice or when the agent is waiting on a slow external resource. Consider breaking the work into smaller steps or increasing the time budget.`;
|
|
52
|
+
},
|
|
53
|
+
severity: "warning",
|
|
54
|
+
extractDetails: (_raw, m) => ({
|
|
55
|
+
elapsedMs: m[1],
|
|
56
|
+
}),
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
test: /(?:Slice|Autopilot slice) stalled.*?(?:no progress for (\d+))?/i,
|
|
60
|
+
headline: "Agent stopped making progress",
|
|
61
|
+
explain: (_raw, m) => {
|
|
62
|
+
const mins = m[1] ? Math.round(parseInt(m[1], 10) / 60_000) : null;
|
|
63
|
+
const duration = mins ? ` for ${mins} minute${mins !== 1 ? "s" : ""}` : "";
|
|
64
|
+
return `The agent stopped producing output${duration}. This can indicate it's stuck in a loop, waiting on an unresponsive resource, or unable to proceed with the current approach. Reviewing the session logs may reveal the specific bottleneck.`;
|
|
65
|
+
},
|
|
66
|
+
severity: "warning",
|
|
67
|
+
extractDetails: (_raw, m) => ({
|
|
68
|
+
idleMs: m[1] || undefined,
|
|
69
|
+
}),
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
test: /blocked.*?(?:needs|requires?) intervention/i,
|
|
73
|
+
headline: "Agent needs your help to continue",
|
|
74
|
+
explain: () => "The agent reached a point where it cannot proceed without human guidance. This may be a question about requirements, a permission needed, or an ambiguity that needs resolution.",
|
|
75
|
+
severity: "warning",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
test: /blocked.*?without.*?(?:blocking )?decision payload/i,
|
|
79
|
+
headline: "Agent paused without clear reason",
|
|
80
|
+
explain: () => "The agent reported it was blocked but did not specify what decision is needed. Review the session logs for context about what the agent was working on when it paused.",
|
|
81
|
+
severity: "warning",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
test: /code=(\d+)/i,
|
|
85
|
+
headline: "Agent encountered an error",
|
|
86
|
+
explain: (_raw, m) => `The agent process exited with error code ${m[1]}. This indicates the agent hit an unexpected condition during execution. Reviewing the session logs will show what operation failed.`,
|
|
87
|
+
severity: "critical",
|
|
88
|
+
extractDetails: (_raw, m) => ({
|
|
89
|
+
exitCode: m[1],
|
|
90
|
+
}),
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
test: /completed.*?(?:without|no) (?:verifiable )?(?:outcomes?|artifacts?|status updates?)/i,
|
|
94
|
+
headline: "Agent finished but produced nothing",
|
|
95
|
+
explain: () => "The agent reported completion but did not produce any artifacts, status updates, or measurable outcomes. This may mean the work was done informally (e.g., analysis without a written output) or the agent couldn't find actionable work.",
|
|
96
|
+
severity: "info",
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
test: /Spawn guard denied/i,
|
|
100
|
+
headline: "Agent dispatch was blocked by safety check",
|
|
101
|
+
explain: (_raw) => "The system's safety checks prevented this agent from starting. This is typically due to capacity limits, domain restrictions, or quality gate requirements that haven't been met.",
|
|
102
|
+
severity: "warning",
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
|
+
/**
|
|
106
|
+
* Translate a raw auto-continue error/context string into a human-friendly
|
|
107
|
+
* decision description. Falls back gracefully for unrecognised patterns.
|
|
108
|
+
*/
|
|
109
|
+
export function humanizeSliceFailure(raw) {
|
|
110
|
+
for (const pattern of PATTERNS) {
|
|
111
|
+
const match = raw.match(pattern.test);
|
|
112
|
+
if (match) {
|
|
113
|
+
return {
|
|
114
|
+
headline: pattern.headline,
|
|
115
|
+
explanation: pattern.explain(raw, match),
|
|
116
|
+
structuredDetails: pattern.extractDetails?.(raw, match) ?? {},
|
|
117
|
+
severity: pattern.severity,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Fallback for unrecognised patterns
|
|
122
|
+
return {
|
|
123
|
+
headline: "Agent work needs attention",
|
|
124
|
+
explanation: raw,
|
|
125
|
+
structuredDetails: {},
|
|
126
|
+
severity: "warning",
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Generate a human-friendly summary string from a raw error, suitable for
|
|
131
|
+
* the `summary` field when creating decisions. Combines the headline and
|
|
132
|
+
* explanation into a concise paragraph.
|
|
133
|
+
*/
|
|
134
|
+
export function humanizeSliceFailureSummary(raw) {
|
|
135
|
+
const h = humanizeSliceFailure(raw);
|
|
136
|
+
return `${h.headline}. ${h.explanation}`;
|
|
137
|
+
}
|
|
@@ -71,6 +71,7 @@ export interface MissionControlExecutionPolicy {
|
|
|
71
71
|
export declare const SLICE_SCOPE_MAX_TASKS: Record<SliceScope, number>;
|
|
72
72
|
export declare const SLICE_SCOPE_TIMEOUT_MULTIPLIER: Record<SliceScope, number>;
|
|
73
73
|
export declare const ORGX_SKILL_BY_DOMAIN: Record<string, string>;
|
|
74
|
+
export declare function safeErrorMessage(err: unknown): string;
|
|
74
75
|
interface BudgetEnvBounds {
|
|
75
76
|
min?: number;
|
|
76
77
|
max?: number;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { pickNumber, pickString, toIsoString } from "./value-utils.js";
|
|
2
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
2
3
|
export const SLICE_SCOPE_MAX_TASKS = {
|
|
3
4
|
task: 6,
|
|
4
5
|
milestone: 15,
|
|
@@ -18,11 +19,74 @@ export const ORGX_SKILL_BY_DOMAIN = {
|
|
|
18
19
|
design: "orgx-design-agent",
|
|
19
20
|
orchestration: "orgx-orchestrator-agent",
|
|
20
21
|
};
|
|
21
|
-
function safeErrorMessage(err) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
export function safeErrorMessage(err) {
|
|
23
|
+
const raw = err instanceof Error
|
|
24
|
+
? err.message
|
|
25
|
+
: typeof err === "string"
|
|
26
|
+
? err
|
|
27
|
+
: err && typeof err === "object" && "message" in err && typeof err.message === "string"
|
|
28
|
+
? (err.message ?? "")
|
|
29
|
+
: "";
|
|
30
|
+
const parseStructuredMessage = (value) => {
|
|
31
|
+
const trimmed = value.trim();
|
|
32
|
+
if (!trimmed)
|
|
33
|
+
return null;
|
|
34
|
+
const parseObjectMessage = (parsed) => {
|
|
35
|
+
if (!parsed || typeof parsed !== "object")
|
|
36
|
+
return null;
|
|
37
|
+
const root = parsed;
|
|
38
|
+
const nested = root.error && typeof root.error === "object" ? root.error : null;
|
|
39
|
+
const envelope = nested ?? root;
|
|
40
|
+
return ((typeof envelope.message === "string" && envelope.message.trim()) ||
|
|
41
|
+
(typeof envelope.detail === "string" && envelope.detail.trim()) ||
|
|
42
|
+
(!nested && typeof root.error === "string" && root.error.trim()) ||
|
|
43
|
+
null);
|
|
44
|
+
};
|
|
45
|
+
try {
|
|
46
|
+
const parsed = JSON.parse(trimmed);
|
|
47
|
+
const direct = parseObjectMessage(parsed);
|
|
48
|
+
if (direct)
|
|
49
|
+
return direct;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// fall through and attempt extraction of embedded JSON blocks
|
|
53
|
+
}
|
|
54
|
+
const firstBrace = trimmed.indexOf("{");
|
|
55
|
+
const lastBrace = trimmed.lastIndexOf("}");
|
|
56
|
+
if (firstBrace >= 0 && lastBrace > firstBrace) {
|
|
57
|
+
const candidate = trimmed.slice(firstBrace, lastBrace + 1);
|
|
58
|
+
try {
|
|
59
|
+
const parsed = JSON.parse(candidate);
|
|
60
|
+
return parseObjectMessage(parsed);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
};
|
|
68
|
+
const stripStructuredNoise = (value) => value
|
|
69
|
+
.replace(/"requestId"\s*:\s*"[^"]*"/gi, "")
|
|
70
|
+
.replace(/"timestamp"\s*:\s*"[^"]*"/gi, "")
|
|
71
|
+
.replace(/"docsUrl"\s*:\s*"[^"]*"/gi, "")
|
|
72
|
+
.replace(/\brequest[_\s-]?id[:=]\s*[\w-]+/gi, "")
|
|
73
|
+
.replace(/\btimestamp[:=]\s*\S+/gi, "")
|
|
74
|
+
.replace(/\bdocsUrl[:=]\s*\S+/gi, "")
|
|
75
|
+
.replace(/[{}]/g, " ")
|
|
76
|
+
.replace(/\s{2,}/g, " ")
|
|
77
|
+
.trim();
|
|
78
|
+
const normalizedMessage = parseStructuredMessage(raw) ?? raw;
|
|
79
|
+
const sanitized = stripStructuredNoise(normalizedMessage);
|
|
80
|
+
const normalized = sanitized.toLowerCase();
|
|
81
|
+
if (normalized.length > 0) {
|
|
82
|
+
if (normalized.includes("internal_error") || normalized.includes("internal server error")) {
|
|
83
|
+
return "temporary server issue";
|
|
84
|
+
}
|
|
85
|
+
if (normalized.includes("failed to list decision") || normalized.includes("failed to load decision")) {
|
|
86
|
+
return "decision data temporarily unavailable";
|
|
87
|
+
}
|
|
88
|
+
return sanitized;
|
|
89
|
+
}
|
|
26
90
|
return "Unexpected error";
|
|
27
91
|
}
|
|
28
92
|
function toNullableBoolean(value) {
|
|
@@ -641,6 +705,9 @@ export async function listEntitiesSafe(client, type, filters) {
|
|
|
641
705
|
}
|
|
642
706
|
}
|
|
643
707
|
export async function buildMissionControlGraph(client, initiativeId, options) {
|
|
708
|
+
if (!UUID_RE.test(initiativeId)) {
|
|
709
|
+
throw new Error(`buildMissionControlGraph: initiativeId must be a UUID, got "${initiativeId}"`);
|
|
710
|
+
}
|
|
644
711
|
const degraded = [];
|
|
645
712
|
const preloadedInitiative = options?.initiativeEntity ?? null;
|
|
646
713
|
const [initiativeResult, workstreamResult, milestoneResult, taskResult] = await Promise.all([
|