pi-subagents 0.81.0 → 0.82.1
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/CHANGELOG.md +55 -0
- package/README.md +1 -1
- package/bundle/agents.js +3 -3
- package/bundle/child-tool-plan.js +2 -2
- package/bundle/{chunk-L5ZHE4L4.js → chunk-3JCDWF4V.js} +4 -4
- package/bundle/{chunk-LSRUTHT7.js → chunk-43SCHIMD.js} +1 -1
- package/bundle/{chunk-VJHOELQP.js → chunk-46XKCPJR.js} +193 -81
- package/bundle/{chunk-G5DKFLLI.js → chunk-4SDU2BZX.js} +15 -10
- package/bundle/{chunk-2ZSNH26H.js → chunk-4Y2ROR7L.js} +7 -7
- package/bundle/{chunk-V2JWDB5U.js → chunk-52UBALJL.js} +2 -2
- package/bundle/{chunk-3DC5KBVP.js → chunk-5TYMSZX3.js} +1 -1
- package/bundle/{chunk-NNLASTQE.js → chunk-6XJB7PIX.js} +20 -6
- package/bundle/{chunk-ZBC44JJ3.js → chunk-CIZQXJ5Q.js} +22 -9
- package/bundle/{chunk-KSWQVGDW.js → chunk-FXUXCX2K.js} +225 -18
- package/bundle/{chunk-563BHAZ5.js → chunk-G4GZD3OJ.js} +35 -26
- package/bundle/{chunk-ARRKBB2B.js → chunk-GJECHSAR.js} +2 -2
- package/bundle/{chunk-V3BIUPLU.js → chunk-IVAE5Z6Q.js} +256 -52
- package/bundle/{chunk-V6MR5TOQ.js → chunk-JYUKW2SY.js} +25 -19
- package/bundle/{chunk-BJN5LUQY.js → chunk-OYF6AFDY.js} +1 -1
- package/bundle/{chunk-CEKS3TFO.js → chunk-PBK2I7WY.js} +6 -6
- package/bundle/{chunk-TUWLXTAM.js → chunk-PDZWJCHD.js} +681 -320
- package/bundle/{chunk-EYNJ6VL6.js → chunk-PLCCBLPY.js} +2 -2
- package/bundle/{chunk-XMFLCKMQ.js → chunk-QE4AGIMG.js} +339 -132
- package/bundle/{chunk-74RT5YQE.js → chunk-RMQFMMAT.js} +123 -33
- package/bundle/{chunk-WSBNENFS.js → chunk-UCPP4GNU.js} +14 -4
- package/bundle/{chunk-2XYWN7QJ.js → chunk-UTT254JE.js} +2 -2
- package/bundle/{chunk-BPS2AGBZ.js → chunk-VSUSLDNW.js} +93 -52
- package/bundle/{chunk-ZP3HODFH.js → chunk-XSWOTIA3.js} +1 -1
- package/bundle/{chunk-3IF2PGW7.js → chunk-YKEWIB4M.js} +1 -1
- package/bundle/{chunk-26C3WLYQ.js → chunk-ZIV4J4HG.js} +498 -274
- package/bundle/control-channel.js +2 -2
- package/bundle/index.js +1 -1
- package/bundle/intercom-bridge.js +2 -2
- package/bundle/preflight.js +8 -8
- package/bundle/project-panes.js +3 -3
- package/docs/agents.md +37 -7
- package/docs/configuration.md +9 -1
- package/docs/tool-reference.md +3 -1
- package/docs/workflows.md +16 -0
- package/package.json +2 -2
- package/skills/council-mode/SKILL.md +1 -4
- package/skills/pi-subagents/SKILL.md +130 -107
- package/skills/pi-subagents/references/orchestration.md +124 -0
- package/skills/pi-subagents/references/prompting-and-roles.md +96 -295
- package/src/agents/agent-management.js +3 -3
- package/src/agents/agent-memory.js +3 -1
- package/src/agents/agents.js +6 -6
- package/src/agents/frontmatter.d.ts +2 -0
- package/src/agents/frontmatter.js +15 -1
- package/src/agents/skills.js +2 -1
- package/src/extension/config.js +3 -0
- package/src/extension/herdr-pi-bridge.js +17 -10
- package/src/extension/index.js +82 -2
- package/src/extension/reply-workflow-script.js +5 -2
- package/src/extension/schemas.js +3 -3
- package/src/extension/subagent-guide.js +1 -2
- package/src/intercom/native-supervisor-channel.js +20 -0
- package/src/missions/lifecycle.js +7 -1
- package/src/runs/background/active-async-capacity.d.ts +1 -0
- package/src/runs/background/active-async-capacity.js +9 -3
- package/src/runs/background/async-execution.js +20 -10
- package/src/runs/background/async-job-tracker.d.ts +6 -1
- package/src/runs/background/async-job-tracker.js +9 -4
- package/src/runs/background/async-status.d.ts +4 -0
- package/src/runs/background/async-status.js +22 -3
- package/src/runs/background/await-async-run.js +3 -7
- package/src/runs/background/boot-id.d.ts +12 -0
- package/src/runs/background/boot-id.js +47 -0
- package/src/runs/background/interrupted-elsewhere.d.ts +22 -0
- package/src/runs/background/interrupted-elsewhere.js +68 -0
- package/src/runs/background/notify.d.ts +2 -0
- package/src/runs/background/notify.js +13 -0
- package/src/runs/background/result-delivery-ownership.d.ts +6 -0
- package/src/runs/background/result-delivery-ownership.js +19 -1
- package/src/runs/background/result-watcher.d.ts +3 -1
- package/src/runs/background/result-watcher.js +24 -8
- package/src/runs/background/run-child-session.d.ts +1 -6
- package/src/runs/background/run-child-session.js +1 -1
- package/src/runs/background/run-status.js +2 -1
- package/src/runs/background/stale-run-reconciler.d.ts +25 -0
- package/src/runs/background/stale-run-reconciler.js +52 -13
- package/src/runs/background/subagent-runner-bootstrap.js +3 -0
- package/src/runs/background/subagent-runner.d.ts +4 -0
- package/src/runs/background/subagent-runner.js +13 -5
- package/src/runs/background/subagent-wait.d.ts +1 -1
- package/src/runs/background/subagent-wait.js +6 -1
- package/src/runs/background/terminal-run-index.d.ts +2 -0
- package/src/runs/background/terminal-run-index.js +44 -3
- package/src/runs/foreground/async-stop-action.js +3 -1
- package/src/runs/foreground/execution.js +6 -3
- package/src/runs/foreground/foreground-history.d.ts +16 -2
- package/src/runs/foreground/foreground-history.js +133 -31
- package/src/runs/foreground/subagent-executor.d.ts +11 -0
- package/src/runs/foreground/subagent-executor.js +277 -74
- package/src/runs/shared/child-launch.d.ts +2 -2
- package/src/runs/shared/child-session.d.ts +3 -2
- package/src/runs/shared/child-session.js +51 -8
- package/src/runs/shared/child-tool-plan.d.ts +7 -0
- package/src/runs/shared/child-tool-plan.js +21 -6
- package/src/runs/shared/herdr-connection.d.ts +17 -8
- package/src/runs/shared/herdr-connection.js +19 -7
- package/src/runs/shared/herdr-external-adapters.d.ts +3 -2
- package/src/runs/shared/herdr-external-adapters.js +1 -1
- package/src/runs/shared/herdr-machine.d.ts +12 -2
- package/src/runs/shared/herdr-machine.js +55 -9
- package/src/runs/shared/herdr-pane-host.d.ts +88 -0
- package/src/runs/shared/herdr-pane-host.js +155 -0
- package/src/runs/shared/herdr-placed-run.d.ts +27 -150
- package/src/runs/shared/herdr-placed-run.js +48 -395
- package/src/runs/shared/local-machine-access.d.ts +5 -0
- package/src/runs/shared/local-machine-access.js +28 -0
- package/src/runs/shared/orca-progress-tabs.d.ts +0 -1
- package/src/runs/shared/orca-progress-tabs.js +1 -24
- package/src/runs/shared/parallel-utils.d.ts +1 -1
- package/src/runs/shared/placed-run-owner.d.ts +49 -0
- package/src/runs/shared/placed-run-owner.js +93 -0
- package/src/runs/shared/placement-host.d.ts +99 -0
- package/src/runs/shared/placement-host.js +157 -0
- package/src/runs/shared/rex-machine.d.ts +25 -0
- package/src/runs/shared/rex-machine.js +83 -0
- package/src/runs/shared/rex-pane-host.d.ts +62 -0
- package/src/runs/shared/rex-pane-host.js +327 -0
- package/src/shared/completion-owner.d.ts +5 -0
- package/src/shared/completion-owner.js +5 -0
- package/src/shared/session-identity.d.ts +2 -0
- package/src/shared/session-identity.js +26 -0
- package/src/shared/types.d.ts +45 -6
- package/src/tui/fleet.d.ts +1 -0
- package/src/tui/fleet.js +25 -15
- package/src/tui/render.d.ts +6 -0
- package/src/tui/render.js +3 -3
- package/src/tui/subagent-messages.js +1 -0
- package/src/workflows/scripted-workflow.d.ts +1 -1
- package/src/workflows/workflow-auto-resume.d.ts +32 -0
- package/src/workflows/workflow-auto-resume.js +102 -0
- package/src/workflows/workflow-launch-record.d.ts +27 -0
- package/src/workflows/workflow-launch-record.js +48 -0
- package/src/workflows/workflow-resume-claim.d.ts +82 -0
- package/src/workflows/workflow-resume-claim.js +210 -0
- package/src/workflows/workflow-resume.d.ts +56 -0
- package/src/workflows/workflow-resume.js +106 -0
- package/src/workflows/workflow-reuse.d.ts +15 -9
- package/src/workflows/workflow-reuse.js +77 -14
- package/src/workflows/workflow-revival.d.ts +2 -0
- package/src/workflows/workflow-revival.js +2 -1
- package/src/workflows/workflow-stop-cause.d.ts +6 -0
- package/src/workflows/workflow-stop-cause.js +8 -0
- package/skills/pi-subagents/references/constraints-and-recipes.md +0 -74
- package/skills/pi-subagents/references/execution-controls.md +0 -563
- package/skills/pi-subagents/references/management-authoring-rpc.md +0 -165
- package/skills/pi-subagents/references/multi-lane-orchestration.md +0 -61
- package/skills/pi-subagents/references/review-and-validation.md +0 -73
|
@@ -11,27 +11,35 @@ import {
|
|
|
11
11
|
startWatchdogDiffBaselineCapture,
|
|
12
12
|
writeUserWatchdogEnabled,
|
|
13
13
|
writeWatchdogModelSettings
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-VSUSLDNW.js";
|
|
15
15
|
import {
|
|
16
16
|
resolveGitRepositoryIdentity
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-FXUXCX2K.js";
|
|
18
18
|
import {
|
|
19
19
|
listAsyncRuns,
|
|
20
|
+
ownsRun,
|
|
20
21
|
parallelHandoffPath,
|
|
21
22
|
parseWorkflowChildSummary,
|
|
22
23
|
readAsyncRecoveryDescriptor,
|
|
23
|
-
resolveRetainedWorktreeCwd
|
|
24
|
-
|
|
25
|
-
} from "./chunk-NNLASTQE.js";
|
|
24
|
+
resolveRetainedWorktreeCwd
|
|
25
|
+
} from "./chunk-6XJB7PIX.js";
|
|
26
26
|
import {
|
|
27
27
|
deepFreezeWorkflowArgs,
|
|
28
28
|
normalizeWorkflowArgs
|
|
29
29
|
} from "./chunk-NF7IEKBH.js";
|
|
30
30
|
import {
|
|
31
31
|
HOST_STEP_MAX_COUNT,
|
|
32
|
+
WORKFLOW_STOP_CAUSE_OWNER_EXITED,
|
|
33
|
+
WORKFLOW_STOP_CAUSE_RUNTIME_REPLACED,
|
|
32
34
|
assertUniqueHostStepIds,
|
|
35
|
+
checkRecordedPidLiveness,
|
|
36
|
+
currentBootId,
|
|
37
|
+
currentPidNamespaceScope,
|
|
38
|
+
currentRecordedPid,
|
|
33
39
|
hasLiveNestedDescendants,
|
|
34
40
|
isActiveAsyncState,
|
|
41
|
+
isRecordedPid,
|
|
42
|
+
isReusableWorkflowStopCause,
|
|
35
43
|
normalizeWorktreeBaseRef,
|
|
36
44
|
parseHostStepNode,
|
|
37
45
|
previewSimpleWorkflowRun,
|
|
@@ -41,23 +49,22 @@ import {
|
|
|
41
49
|
reconcileAsyncRun,
|
|
42
50
|
resolveMissionStoreLocation,
|
|
43
51
|
validateMissionId,
|
|
44
|
-
validateModelResponseAliases,
|
|
45
52
|
workflowRunParamsFingerprint
|
|
46
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-46XKCPJR.js";
|
|
47
54
|
import {
|
|
48
55
|
requestAsyncCommand,
|
|
49
56
|
watchAsyncControlInbox
|
|
50
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-OYF6AFDY.js";
|
|
51
58
|
import {
|
|
52
59
|
supervisorChannelDir
|
|
53
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-4SDU2BZX.js";
|
|
54
61
|
import {
|
|
55
62
|
createHerdrClient,
|
|
56
63
|
detectHerdr
|
|
57
|
-
} from "./chunk-
|
|
64
|
+
} from "./chunk-52UBALJL.js";
|
|
58
65
|
import {
|
|
59
66
|
getProjectSubagentsDir
|
|
60
|
-
} from "./chunk-
|
|
67
|
+
} from "./chunk-5TYMSZX3.js";
|
|
61
68
|
import {
|
|
62
69
|
ExternalJobProviderError,
|
|
63
70
|
getExternalJobProvider,
|
|
@@ -67,7 +74,7 @@ import {
|
|
|
67
74
|
} from "./chunk-YYREO77V.js";
|
|
68
75
|
import {
|
|
69
76
|
mergeRuntimeAgents
|
|
70
|
-
} from "./chunk-
|
|
77
|
+
} from "./chunk-YKEWIB4M.js";
|
|
71
78
|
import {
|
|
72
79
|
CODE_OWNED_EXTERNAL_CLI_ADAPTER_LABEL,
|
|
73
80
|
PI_CODING_AGENT_PACKAGE,
|
|
@@ -98,8 +105,8 @@ import {
|
|
|
98
105
|
normalizeModelSegment,
|
|
99
106
|
normalizeWorkflowLaneMetadata,
|
|
100
107
|
parseFrontmatter,
|
|
101
|
-
parseFrontmatterList,
|
|
102
108
|
parsePackageName,
|
|
109
|
+
parseUnquotedList,
|
|
103
110
|
readStatus,
|
|
104
111
|
removeBuiltinAgentOverride,
|
|
105
112
|
removeBuiltinAgentOverrideFields,
|
|
@@ -122,7 +129,7 @@ import {
|
|
|
122
129
|
withFileLease,
|
|
123
130
|
writeAtomicJson,
|
|
124
131
|
writePrivateAtomicJson
|
|
125
|
-
} from "./chunk-
|
|
132
|
+
} from "./chunk-CIZQXJ5Q.js";
|
|
126
133
|
import {
|
|
127
134
|
DIRS,
|
|
128
135
|
INTERCOM_DETACH_REQUEST_EVENT,
|
|
@@ -3082,8 +3089,8 @@ function withDeclaredExtensionPaths(config, filePath) {
|
|
|
3082
3089
|
const { extensions: _extensions, subagentOnlyExtensions: _subagentOnlyExtensions, ...withoutResolvedExtensions } = config;
|
|
3083
3090
|
return {
|
|
3084
3091
|
...withoutResolvedExtensions,
|
|
3085
|
-
...frontmatter.extensions !== void 0 ? { extensions:
|
|
3086
|
-
...frontmatter.subagentOnlyExtensions !== void 0 ? { subagentOnlyExtensions:
|
|
3092
|
+
...frontmatter.extensions !== void 0 ? { extensions: parseUnquotedList(frontmatter.extensions) } : {},
|
|
3093
|
+
...frontmatter.subagentOnlyExtensions !== void 0 ? { subagentOnlyExtensions: parseUnquotedList(frontmatter.subagentOnlyExtensions) } : {}
|
|
3087
3094
|
};
|
|
3088
3095
|
}
|
|
3089
3096
|
function editableAgentConfig(agent) {
|
|
@@ -4200,8 +4207,7 @@ var SUBAGENT_GUIDE_SECTION_MAX_CHARS = 8e3;
|
|
|
4200
4207
|
var packageRoot = path7.resolve(path7.dirname(fileURLToPath(new URL("../src/extension/subagent-guide.js", import.meta.url).href)), "..", "..");
|
|
4201
4208
|
var COUNCIL_FILES = [
|
|
4202
4209
|
"skills/council-mode/SKILL.md",
|
|
4203
|
-
"skills/council-mode/references/pass-contracts.md"
|
|
4204
|
-
"skills/pi-subagents/references/execution-controls.md"
|
|
4210
|
+
"skills/council-mode/references/pass-contracts.md"
|
|
4205
4211
|
];
|
|
4206
4212
|
function isGuideTopic(value) {
|
|
4207
4213
|
return SUBAGENT_GUIDE_TOPICS.includes(value);
|
|
@@ -5071,6 +5077,197 @@ function createParentWake(pi, now = Date.now) {
|
|
|
5071
5077
|
};
|
|
5072
5078
|
}
|
|
5073
5079
|
|
|
5080
|
+
// src/workflows/workflow-launch-record.ts
|
|
5081
|
+
import * as fs9 from "node:fs";
|
|
5082
|
+
import * as path8 from "node:path";
|
|
5083
|
+
var WORKFLOW_LAUNCH_RECORD_FILE = "launch.json";
|
|
5084
|
+
function launchRecordPath(asyncDir) {
|
|
5085
|
+
return path8.join(asyncDir, WORKFLOW_LAUNCH_RECORD_FILE);
|
|
5086
|
+
}
|
|
5087
|
+
function writeWorkflowLaunchRecord(asyncDir, record) {
|
|
5088
|
+
writeAtomicJson(launchRecordPath(asyncDir), record);
|
|
5089
|
+
}
|
|
5090
|
+
function isPlainObject(value) {
|
|
5091
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
5092
|
+
}
|
|
5093
|
+
function invalid(error2) {
|
|
5094
|
+
return { ok: false, missing: false, error: error2 };
|
|
5095
|
+
}
|
|
5096
|
+
function readWorkflowLaunchRecord(asyncDir) {
|
|
5097
|
+
let text2;
|
|
5098
|
+
try {
|
|
5099
|
+
text2 = fs9.readFileSync(launchRecordPath(asyncDir), "utf-8");
|
|
5100
|
+
} catch (error2) {
|
|
5101
|
+
if (error2.code === "ENOENT") return { ok: false, missing: true, error: "launch.json is missing" };
|
|
5102
|
+
return invalid(`could not be read: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
5103
|
+
}
|
|
5104
|
+
let value;
|
|
5105
|
+
try {
|
|
5106
|
+
value = JSON.parse(text2);
|
|
5107
|
+
} catch (error2) {
|
|
5108
|
+
return invalid(`not valid JSON: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
5109
|
+
}
|
|
5110
|
+
if (!isPlainObject(value)) return invalid("not a JSON object");
|
|
5111
|
+
if (value.version !== 1) return invalid("version must be 1");
|
|
5112
|
+
if (typeof value.scriptDigest !== "string" || !value.scriptDigest) return invalid("scriptDigest must be a non-empty string");
|
|
5113
|
+
if (value.source === "structured") return { ok: true, record: { version: 1, source: "structured", scriptDigest: value.scriptDigest } };
|
|
5114
|
+
if (value.source !== "script" && value.source !== "resource") return invalid('source must be "script", "resource", or "structured"');
|
|
5115
|
+
if (!isPlainObject(value.params)) return invalid("params must be an object");
|
|
5116
|
+
return { ok: true, record: { version: 1, source: value.source, params: value.params, scriptDigest: value.scriptDigest } };
|
|
5117
|
+
}
|
|
5118
|
+
|
|
5119
|
+
// src/workflows/workflow-resume-claim.ts
|
|
5120
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
5121
|
+
import * as fs10 from "node:fs";
|
|
5122
|
+
import * as path9 from "node:path";
|
|
5123
|
+
var CLAIM_FILE_PATTERN = /^resumed-by\.([1-9][0-9]*)\.json$/;
|
|
5124
|
+
var RELEASED_CLAIM_FILE_PATTERN = /^resumed-by\.([1-9][0-9]*)\.json\.released-/;
|
|
5125
|
+
var REFUSAL_FILE = "resume-refused.json";
|
|
5126
|
+
var MAX_CLAIM_ROUNDS = 8;
|
|
5127
|
+
var heldClaimsKey = /* @__PURE__ */ Symbol.for("pi-subagents.workflow-resume-claims.v1");
|
|
5128
|
+
var heldClaimsGlobal = globalThis;
|
|
5129
|
+
var heldClaims = heldClaimsGlobal[heldClaimsKey] ?? (heldClaimsGlobal[heldClaimsKey] = /* @__PURE__ */ new Set());
|
|
5130
|
+
function claimFilePath(asyncDir, generation) {
|
|
5131
|
+
return path9.join(asyncDir, `resumed-by.${generation}.json`);
|
|
5132
|
+
}
|
|
5133
|
+
function isClaim(value) {
|
|
5134
|
+
if (!isRecordedPid(value) || Array.isArray(value)) return false;
|
|
5135
|
+
const claim = value;
|
|
5136
|
+
return claim.version === 1 && typeof claim.claimedAt === "number" && typeof claim.newRunId === "string" && claim.newRunId.length > 0 && (claim.trigger === "auto" || claim.trigger === "manual" || claim.trigger === "reuse");
|
|
5137
|
+
}
|
|
5138
|
+
function readTopClaim(asyncDir, retries = 3) {
|
|
5139
|
+
let generation = 0;
|
|
5140
|
+
let releasedGeneration = 0;
|
|
5141
|
+
try {
|
|
5142
|
+
for (const name of fs10.readdirSync(asyncDir)) {
|
|
5143
|
+
const live = CLAIM_FILE_PATTERN.exec(name);
|
|
5144
|
+
if (live) generation = Math.max(generation, Number(live[1]));
|
|
5145
|
+
const released = RELEASED_CLAIM_FILE_PATTERN.exec(name);
|
|
5146
|
+
if (released) releasedGeneration = Math.max(releasedGeneration, Number(released[1]));
|
|
5147
|
+
}
|
|
5148
|
+
} catch (error2) {
|
|
5149
|
+
return error2.code === "ENOENT" ? { kind: "open", generation: 0 } : { kind: "unreadable" };
|
|
5150
|
+
}
|
|
5151
|
+
if (releasedGeneration > generation || generation === 0) return { kind: "open", generation: Math.max(releasedGeneration, generation) };
|
|
5152
|
+
const filePath = claimFilePath(asyncDir, generation);
|
|
5153
|
+
let text2;
|
|
5154
|
+
try {
|
|
5155
|
+
text2 = fs10.readFileSync(filePath, "utf-8");
|
|
5156
|
+
} catch (error2) {
|
|
5157
|
+
if (error2.code === "ENOENT" && retries > 0) return readTopClaim(asyncDir, retries - 1);
|
|
5158
|
+
return { kind: "unreadable" };
|
|
5159
|
+
}
|
|
5160
|
+
try {
|
|
5161
|
+
const value = JSON.parse(text2);
|
|
5162
|
+
return isClaim(value) ? { kind: "claim", generation, filePath, claim: value } : { kind: "open", generation };
|
|
5163
|
+
} catch {
|
|
5164
|
+
return { kind: "open", generation };
|
|
5165
|
+
}
|
|
5166
|
+
}
|
|
5167
|
+
function readWorkflowResumeClaim(asyncDir) {
|
|
5168
|
+
const top = readTopClaim(asyncDir);
|
|
5169
|
+
return top.kind === "claim" ? top.claim : void 0;
|
|
5170
|
+
}
|
|
5171
|
+
function resumedRunStarted(sourceAsyncDir, newRunId) {
|
|
5172
|
+
return path9.basename(newRunId) === newRunId && fs10.existsSync(path9.join(path9.dirname(sourceAsyncDir), newRunId, "status.json"));
|
|
5173
|
+
}
|
|
5174
|
+
function claimantLiveness(filePath, claim, liveness) {
|
|
5175
|
+
if (claim.pid === process.pid && claim.bootId === currentBootId() && claim.pidNamespaceScope === currentPidNamespaceScope()) return heldClaims.has(filePath) ? "alive" : "dead";
|
|
5176
|
+
return liveness ? liveness(claim) : checkRecordedPidLiveness(claim);
|
|
5177
|
+
}
|
|
5178
|
+
function workflowResumeClaimState(asyncDir, options = {}) {
|
|
5179
|
+
const top = readTopClaim(asyncDir);
|
|
5180
|
+
if (top.kind === "unreadable") return { kind: "unreadable" };
|
|
5181
|
+
if (top.kind !== "claim") return { kind: "none" };
|
|
5182
|
+
if (resumedRunStarted(asyncDir, top.claim.newRunId)) return { kind: "resumed", newRunId: top.claim.newRunId };
|
|
5183
|
+
return claimantLiveness(top.filePath, top.claim, options.liveness) === "dead" ? { kind: "none" } : { kind: "busy", pid: top.claim.pid, newRunId: top.claim.newRunId };
|
|
5184
|
+
}
|
|
5185
|
+
function createClaimFile(filePath, claim) {
|
|
5186
|
+
const tempPath = `${filePath}.${process.pid}.${randomUUID3()}.tmp`;
|
|
5187
|
+
try {
|
|
5188
|
+
fs10.writeFileSync(tempPath, JSON.stringify(claim, null, 2), "utf-8");
|
|
5189
|
+
fs10.linkSync(tempPath, filePath);
|
|
5190
|
+
return true;
|
|
5191
|
+
} catch (error2) {
|
|
5192
|
+
if (error2.code === "EEXIST") return false;
|
|
5193
|
+
throw error2;
|
|
5194
|
+
} finally {
|
|
5195
|
+
fs10.rmSync(tempPath, { force: true });
|
|
5196
|
+
}
|
|
5197
|
+
}
|
|
5198
|
+
function claimWorkflowResume(asyncDir, input) {
|
|
5199
|
+
const claim = {
|
|
5200
|
+
version: 1,
|
|
5201
|
+
...currentRecordedPid(),
|
|
5202
|
+
claimedAt: input.now ?? Date.now(),
|
|
5203
|
+
newRunId: input.newRunId,
|
|
5204
|
+
trigger: input.trigger
|
|
5205
|
+
};
|
|
5206
|
+
for (let round = 0; round < MAX_CLAIM_ROUNDS; round++) {
|
|
5207
|
+
const top2 = readTopClaim(asyncDir);
|
|
5208
|
+
if (top2.kind === "unreadable") return { kind: "unreadable" };
|
|
5209
|
+
if (top2.kind === "claim") {
|
|
5210
|
+
if (top2.claim.newRunId === input.newRunId && top2.claim.pid === process.pid) {
|
|
5211
|
+
heldClaims.add(top2.filePath);
|
|
5212
|
+
return { kind: "owner" };
|
|
5213
|
+
}
|
|
5214
|
+
if (resumedRunStarted(asyncDir, top2.claim.newRunId)) return { kind: "resumed", newRunId: top2.claim.newRunId };
|
|
5215
|
+
if (claimantLiveness(top2.filePath, top2.claim, input.liveness) !== "dead") return { kind: "busy", pid: top2.claim.pid };
|
|
5216
|
+
}
|
|
5217
|
+
const filePath = claimFilePath(asyncDir, top2.generation + 1);
|
|
5218
|
+
if (createClaimFile(filePath, claim)) {
|
|
5219
|
+
heldClaims.add(filePath);
|
|
5220
|
+
return { kind: "owner" };
|
|
5221
|
+
}
|
|
5222
|
+
}
|
|
5223
|
+
const top = readTopClaim(asyncDir);
|
|
5224
|
+
return top.kind === "claim" ? { kind: "busy", pid: top.claim.pid } : { kind: "unreadable" };
|
|
5225
|
+
}
|
|
5226
|
+
function releaseWorkflowResumeClaim(asyncDir, newRunId) {
|
|
5227
|
+
const top = readTopClaim(asyncDir);
|
|
5228
|
+
if (top.kind !== "claim" || top.claim.newRunId !== newRunId || top.claim.pid !== process.pid) return;
|
|
5229
|
+
try {
|
|
5230
|
+
fs10.renameSync(top.filePath, `${top.filePath}.released-${randomUUID3()}`);
|
|
5231
|
+
} catch (error2) {
|
|
5232
|
+
if (error2.code !== "ENOENT") throw error2;
|
|
5233
|
+
}
|
|
5234
|
+
heldClaims.delete(top.filePath);
|
|
5235
|
+
}
|
|
5236
|
+
function recordWorkflowResumeRefusal(asyncDir, refusal, now = Date.now()) {
|
|
5237
|
+
writeAtomicJson(path9.join(asyncDir, REFUSAL_FILE), { version: 1, ...refusal, refusedAt: now });
|
|
5238
|
+
}
|
|
5239
|
+
function readWorkflowResumeRefusal(asyncDir) {
|
|
5240
|
+
try {
|
|
5241
|
+
const value = JSON.parse(fs10.readFileSync(path9.join(asyncDir, REFUSAL_FILE), "utf-8"));
|
|
5242
|
+
return value?.version === 1 && typeof value.reason === "string" && typeof value.byHand === "string" && typeof value.refusedAt === "number" && (value.newRunId === void 0 || typeof value.newRunId === "string") ? value : void 0;
|
|
5243
|
+
} catch {
|
|
5244
|
+
return void 0;
|
|
5245
|
+
}
|
|
5246
|
+
}
|
|
5247
|
+
function resumeByHandText(runId) {
|
|
5248
|
+
return `Resume by hand: subagent({ action: "resume", id: "${runId}" })`;
|
|
5249
|
+
}
|
|
5250
|
+
function relaunchByHandText(runId) {
|
|
5251
|
+
return `Relaunch it by hand with options: { reuseFrom: "${runId}" }.`;
|
|
5252
|
+
}
|
|
5253
|
+
function refusalSentence(reason, byHand) {
|
|
5254
|
+
return `${reason.replace(/\.$/, "")}.${byHand ? ` ${byHand}` : ""}`;
|
|
5255
|
+
}
|
|
5256
|
+
function hasWorkflowResumeRefusal(asyncDir) {
|
|
5257
|
+
return fs10.existsSync(path9.join(asyncDir, REFUSAL_FILE));
|
|
5258
|
+
}
|
|
5259
|
+
var RESUMED_AS = { auto: "Auto-resumed as", manual: "Resumed as", reuse: "Relaunched as" };
|
|
5260
|
+
function describeWorkflowResume(asyncDir, runId) {
|
|
5261
|
+
const claim = readWorkflowResumeClaim(asyncDir);
|
|
5262
|
+
const refusal = readWorkflowResumeRefusal(asyncDir);
|
|
5263
|
+
if (refusal && (!claim || refusal.newRunId === claim.newRunId)) {
|
|
5264
|
+
return `Not auto-resumed: ${refusalSentence(refusal.reason, refusal.byHand)}`;
|
|
5265
|
+
}
|
|
5266
|
+
if (claim) return `${RESUMED_AS[claim.trigger]} ${claim.newRunId}.`;
|
|
5267
|
+
const launch = readWorkflowLaunchRecord(asyncDir);
|
|
5268
|
+
return launch.ok && launch.record.source !== "structured" ? resumeByHandText(runId) : relaunchByHandText(runId);
|
|
5269
|
+
}
|
|
5270
|
+
|
|
5074
5271
|
// src/runs/background/notify.ts
|
|
5075
5272
|
import { statSync as statSync2 } from "node:fs";
|
|
5076
5273
|
import { debuglog } from "node:util";
|
|
@@ -5250,15 +5447,15 @@ function nonemptyPath(value) {
|
|
|
5250
5447
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
5251
5448
|
}
|
|
5252
5449
|
function retainedChildPath(value, producerConfirmed = false) {
|
|
5253
|
-
const
|
|
5254
|
-
if (!
|
|
5255
|
-
if (producerConfirmed) return { status: "verified", path:
|
|
5450
|
+
const path17 = nonemptyPath(value);
|
|
5451
|
+
if (!path17) return { status: "unavailable" };
|
|
5452
|
+
if (producerConfirmed) return { status: "verified", path: path17 };
|
|
5256
5453
|
try {
|
|
5257
|
-
return statSync2(
|
|
5454
|
+
return statSync2(path17).isFile() ? { status: "verified", path: path17 } : { status: "unavailable" };
|
|
5258
5455
|
} catch (error2) {
|
|
5259
5456
|
const code = typeof error2 === "object" && error2 !== null && "code" in error2 && typeof error2.code === "string" ? error2.code : void 0;
|
|
5260
5457
|
if (code === "ENOENT" || code === "ENOTDIR") return { status: "unavailable" };
|
|
5261
|
-
return { status: "error", error: { path:
|
|
5458
|
+
return { status: "error", error: { path: path17, ...code ? { code } : {}, message: error2 instanceof Error ? error2.message : String(error2) } };
|
|
5262
5459
|
}
|
|
5263
5460
|
}
|
|
5264
5461
|
function formatRetainedPathError(label, error2) {
|
|
@@ -5362,10 +5559,12 @@ function formatCorrelationLines(details) {
|
|
|
5362
5559
|
return [
|
|
5363
5560
|
details.asyncDir ? `Retention-managed async directory: ${boundedSafeText(details.asyncDir)}` : void 0,
|
|
5364
5561
|
details.workflowRunId ? `Workflow run: ${details.workflowRunId}` : void 0,
|
|
5562
|
+
details.resumeNotice,
|
|
5365
5563
|
details.childRuns?.length ? `Child runs: ${details.childRuns.map(formatChildRun).join(", ")}` : void 0,
|
|
5366
5564
|
details.reconciledFromDetachedChild ? `Reconciled detached child: ${details.reconciledFromDetachedChild}` : void 0
|
|
5367
5565
|
].filter((line) => line !== void 0);
|
|
5368
5566
|
}
|
|
5567
|
+
var RESUME_NOTICE_PREFIXES = ["Auto-resumed as ", "Resumed as ", "Relaunched as ", "Not auto-resumed: ", "Resume by hand: ", "Relaunch it by hand with "];
|
|
5369
5568
|
var WATCHDOG_BLOCKERS_HEADING = "Watchdog blockers:";
|
|
5370
5569
|
function formatWatchdogBlockerLines(details) {
|
|
5371
5570
|
if (!details.watchdogBlockers?.length) return [];
|
|
@@ -5448,6 +5647,8 @@ function parseSubagentNotifyContent(content) {
|
|
|
5448
5647
|
const workflowRunIndex = body.findIndex((line) => line.startsWith("Workflow run: "));
|
|
5449
5648
|
const childRunsIndex = body.findIndex((line) => line.startsWith("Child runs: "));
|
|
5450
5649
|
const reconciledIndex = body.findIndex((line) => line.startsWith("Reconciled detached child: "));
|
|
5650
|
+
const resumeNoticeLine = workflowRunIndex >= 0 ? body[workflowRunIndex + 1] : void 0;
|
|
5651
|
+
const resumeNotice = resumeNoticeLine && RESUME_NOTICE_PREFIXES.some((prefix) => resumeNoticeLine.startsWith(prefix)) ? resumeNoticeLine : void 0;
|
|
5451
5652
|
const watchdogIndex = body.findIndex((line) => line === WATCHDOG_BLOCKERS_HEADING);
|
|
5452
5653
|
const watchdogBlockers = watchdogIndex >= 0 ? parseWatchdogBlockerLines(body.slice(watchdogIndex + 1)) : [];
|
|
5453
5654
|
const metadataIndexes = [sessionIndex, handoffIndex, workflowRunIndex, childRunsIndex, reconciledIndex, watchdogIndex].filter((index) => index >= 0);
|
|
@@ -5481,6 +5682,7 @@ function parseSubagentNotifyContent(content) {
|
|
|
5481
5682
|
resultPreview,
|
|
5482
5683
|
...handoffPath ? { handoffPath } : {},
|
|
5483
5684
|
...workflowRunId ? { workflowRunId } : {},
|
|
5685
|
+
...resumeNotice ? { resumeNotice } : {},
|
|
5484
5686
|
...childRuns?.length ? { childRuns } : {},
|
|
5485
5687
|
...reconciledFromDetachedChild ? { reconciledFromDetachedChild } : {},
|
|
5486
5688
|
...watchdogBlockers.length ? { watchdogBlockers } : {},
|
|
@@ -5706,6 +5908,8 @@ ${outputPreview}` : outputPreview;
|
|
|
5706
5908
|
}) : void 0;
|
|
5707
5909
|
const reconciledFromDetachedChild = typeof result5.reconciledFromDetachedChild === "string" ? result5.reconciledFromDetachedChild : void 0;
|
|
5708
5910
|
const asyncDir = nonemptyPath(result5.asyncDir);
|
|
5911
|
+
const stopCause = result5.workflow?.stopCause;
|
|
5912
|
+
const resumeNotice = workflowRunId && asyncDir && isReusableWorkflowStopCause(stopCause) ? describeWorkflowResume(asyncDir, workflowRunId) : void 0;
|
|
5709
5913
|
const watchdogBlockers = [];
|
|
5710
5914
|
const watchdogConcerns = [];
|
|
5711
5915
|
const collectWatchdogFindings = (owner, progress) => {
|
|
@@ -5738,6 +5942,7 @@ ${watchdogConcerns.map((warning) => `- ${warning}`).join("\n")}` : resultPreview
|
|
|
5738
5942
|
...handoffPath ? { handoffPath } : {},
|
|
5739
5943
|
...rawRunId ? { runId: rawRunId } : {},
|
|
5740
5944
|
...workflowRunId ? { workflowRunId } : {},
|
|
5945
|
+
...resumeNotice ? { resumeNotice } : {},
|
|
5741
5946
|
...childRuns.length ? { childRuns } : {},
|
|
5742
5947
|
...childOutputs?.length ? { childOutputs } : {},
|
|
5743
5948
|
...reconciledFromDetachedChild ? { reconciledFromDetachedChild } : {},
|
|
@@ -6391,9 +6596,9 @@ function registerPiWebSessionLiveness(registration) {
|
|
|
6391
6596
|
}
|
|
6392
6597
|
|
|
6393
6598
|
// src/runs/background/wait-subscriptions.ts
|
|
6394
|
-
import * as
|
|
6395
|
-
import * as
|
|
6396
|
-
import { randomUUID as
|
|
6599
|
+
import * as fs11 from "node:fs";
|
|
6600
|
+
import * as path10 from "node:path";
|
|
6601
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
6397
6602
|
var SUBSCRIPTION_VERSION = 1;
|
|
6398
6603
|
var STATUS_OUTCOMES = /* @__PURE__ */ new Set(["timed out", "could not be reconciled", "needs attention", "reconciliation failed"]);
|
|
6399
6604
|
var RECONCILE_INTERVAL_MS = 1e3;
|
|
@@ -6412,7 +6617,7 @@ function needsAttention(run) {
|
|
|
6412
6617
|
return run.activityState === "needs_attention" || run.steps.some((step) => step.activityState === "needs_attention");
|
|
6413
6618
|
}
|
|
6414
6619
|
function subscriptionFile(dir, token) {
|
|
6415
|
-
return
|
|
6620
|
+
return path10.join(dir, `${token}.json`);
|
|
6416
6621
|
}
|
|
6417
6622
|
function formatWaitSubscriptions(state, now = Date.now()) {
|
|
6418
6623
|
const subscriptions = [...state.waitSubscriptions?.values() ?? []].sort((left, right) => left.createdAt - right.createdAt);
|
|
@@ -6426,7 +6631,7 @@ function formatWaitSubscriptions(state, now = Date.now()) {
|
|
|
6426
6631
|
function createWaitSubscriptionManager(pi, state, options = {}) {
|
|
6427
6632
|
const asyncDirRoot = options.asyncDirRoot ?? DIRS.async;
|
|
6428
6633
|
const resultsDir = options.resultsDir ?? DIRS.results;
|
|
6429
|
-
const subscriptionsDir = options.subscriptionsDir ??
|
|
6634
|
+
const subscriptionsDir = options.subscriptionsDir ?? path10.join(path10.dirname(asyncDirRoot), "wait-subscriptions");
|
|
6430
6635
|
const now = options.now ?? Date.now;
|
|
6431
6636
|
const subscriptions = state.waitSubscriptions ?? /* @__PURE__ */ new Map();
|
|
6432
6637
|
state.waitSubscriptions = subscriptions;
|
|
@@ -6440,16 +6645,16 @@ function createWaitSubscriptionManager(pi, state, options = {}) {
|
|
|
6440
6645
|
lastForeignSweepAt = sweptAt;
|
|
6441
6646
|
let files;
|
|
6442
6647
|
try {
|
|
6443
|
-
files =
|
|
6648
|
+
files = fs11.readdirSync(subscriptionsDir).filter((file) => file.endsWith(".json"));
|
|
6444
6649
|
} catch (error2) {
|
|
6445
6650
|
if (!isNotFound(error2)) console.error(`Failed to scan wait subscriptions in '${subscriptionsDir}':`, error2);
|
|
6446
6651
|
return;
|
|
6447
6652
|
}
|
|
6448
6653
|
for (const file of files) {
|
|
6449
|
-
const filePath =
|
|
6654
|
+
const filePath = path10.join(subscriptionsDir, file);
|
|
6450
6655
|
let record;
|
|
6451
6656
|
try {
|
|
6452
|
-
record = parseRecord(JSON.parse(
|
|
6657
|
+
record = parseRecord(JSON.parse(fs11.readFileSync(filePath, "utf-8")));
|
|
6453
6658
|
} catch (error2) {
|
|
6454
6659
|
console.error(`Ignoring invalid wait subscription '${filePath}':`, error2);
|
|
6455
6660
|
continue;
|
|
@@ -6458,7 +6663,7 @@ function createWaitSubscriptionManager(pi, state, options = {}) {
|
|
|
6458
6663
|
if (record.sessionId === state.currentSessionId) continue;
|
|
6459
6664
|
if (sweptAt < record.expiresAt + FOREIGN_SWEEP_GRACE_MS) continue;
|
|
6460
6665
|
try {
|
|
6461
|
-
|
|
6666
|
+
fs11.unlinkSync(filePath);
|
|
6462
6667
|
} catch (error2) {
|
|
6463
6668
|
if (!isNotFound(error2)) console.error(`Failed to remove expired wait subscription '${filePath}':`, error2);
|
|
6464
6669
|
}
|
|
@@ -6466,7 +6671,7 @@ function createWaitSubscriptionManager(pi, state, options = {}) {
|
|
|
6466
6671
|
};
|
|
6467
6672
|
const remove = (record) => {
|
|
6468
6673
|
try {
|
|
6469
|
-
|
|
6674
|
+
fs11.unlinkSync(subscriptionFile(subscriptionsDir, record.token));
|
|
6470
6675
|
} catch (error2) {
|
|
6471
6676
|
if (!isNotFound(error2)) throw error2;
|
|
6472
6677
|
}
|
|
@@ -6587,7 +6792,7 @@ Parent action: ${STATUS_OUTCOMES.has(outcome) ? STATUS_ACTION : COMPLETION_ACTIO
|
|
|
6587
6792
|
const createdAt = now();
|
|
6588
6793
|
const record = {
|
|
6589
6794
|
version: SUBSCRIPTION_VERSION,
|
|
6590
|
-
token:
|
|
6795
|
+
token: randomUUID4(),
|
|
6591
6796
|
sessionId,
|
|
6592
6797
|
targetKind: input.targetKind,
|
|
6593
6798
|
runId: input.runId,
|
|
@@ -6595,7 +6800,7 @@ Parent action: ${STATUS_OUTCOMES.has(outcome) ? STATUS_ACTION : COMPLETION_ACTIO
|
|
|
6595
6800
|
createdAt,
|
|
6596
6801
|
expiresAt: createdAt + input.timeoutMs
|
|
6597
6802
|
};
|
|
6598
|
-
|
|
6803
|
+
fs11.mkdirSync(subscriptionsDir, { recursive: true });
|
|
6599
6804
|
writeAtomicJson(subscriptionFile(subscriptionsDir, record.token), record);
|
|
6600
6805
|
subscriptions.set(record.token, record);
|
|
6601
6806
|
return record;
|
|
@@ -6607,21 +6812,21 @@ Parent action: ${STATUS_OUTCOMES.has(outcome) ? STATUS_ACTION : COMPLETION_ACTIO
|
|
|
6607
6812
|
if (!state.currentSessionId) return;
|
|
6608
6813
|
let files;
|
|
6609
6814
|
try {
|
|
6610
|
-
files =
|
|
6815
|
+
files = fs11.readdirSync(subscriptionsDir).filter((file) => file.endsWith(".json"));
|
|
6611
6816
|
} catch (error2) {
|
|
6612
6817
|
if (isNotFound(error2)) return;
|
|
6613
6818
|
throw error2;
|
|
6614
6819
|
}
|
|
6615
6820
|
for (const file of files) {
|
|
6616
6821
|
try {
|
|
6617
|
-
const record = parseRecord(JSON.parse(
|
|
6822
|
+
const record = parseRecord(JSON.parse(fs11.readFileSync(path10.join(subscriptionsDir, file), "utf-8")));
|
|
6618
6823
|
if (!record || file !== `${record.token}.json`) continue;
|
|
6619
6824
|
if (record.sessionId === state.currentSessionId) {
|
|
6620
6825
|
subscriptions.set(record.token, record);
|
|
6621
6826
|
if (record.targetKind === "foreground" && !state.foregroundRuns?.has(record.runId)) unresolvedRestoredForegroundTokens.add(record.token);
|
|
6622
6827
|
}
|
|
6623
6828
|
} catch (error2) {
|
|
6624
|
-
console.error(`Ignoring invalid wait subscription '${
|
|
6829
|
+
console.error(`Ignoring invalid wait subscription '${path10.join(subscriptionsDir, file)}':`, error2);
|
|
6625
6830
|
}
|
|
6626
6831
|
}
|
|
6627
6832
|
reconcile();
|
|
@@ -6644,25 +6849,25 @@ Parent action: ${STATUS_OUTCOMES.has(outcome) ? STATUS_ACTION : COMPLETION_ACTIO
|
|
|
6644
6849
|
}
|
|
6645
6850
|
|
|
6646
6851
|
// src/runs/shared/child-session.ts
|
|
6647
|
-
import * as
|
|
6648
|
-
import * as
|
|
6852
|
+
import * as fs14 from "node:fs";
|
|
6853
|
+
import * as path13 from "node:path";
|
|
6649
6854
|
import { pathToFileURL } from "node:url";
|
|
6650
6855
|
|
|
6651
6856
|
// src/runs/shared/child-commands.ts
|
|
6652
|
-
import { randomUUID as
|
|
6653
|
-
import * as
|
|
6654
|
-
import * as
|
|
6857
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
6858
|
+
import * as fs12 from "node:fs";
|
|
6859
|
+
import * as path11 from "node:path";
|
|
6655
6860
|
import { Type } from "typebox";
|
|
6656
6861
|
var CHILD_COMMAND_TOOL = "subagent_command";
|
|
6657
6862
|
var writeAtomicJson2 = createAtomicJsonWriter({ mode: 384 });
|
|
6658
6863
|
var MAX_OUTPUT_BYTES = 8 * 1024;
|
|
6659
6864
|
var RECENT_COMMANDS = 20;
|
|
6660
6865
|
function childCommandStatePath(channelDir) {
|
|
6661
|
-
return
|
|
6866
|
+
return path11.join(channelDir, "commands.json");
|
|
6662
6867
|
}
|
|
6663
6868
|
function readChildCommandState(channelDir) {
|
|
6664
6869
|
try {
|
|
6665
|
-
return JSON.parse(
|
|
6870
|
+
return JSON.parse(fs12.readFileSync(childCommandStatePath(channelDir), "utf8"));
|
|
6666
6871
|
} catch (error2) {
|
|
6667
6872
|
if (error2.code === "ENOENT") return void 0;
|
|
6668
6873
|
throw error2;
|
|
@@ -6678,7 +6883,7 @@ function text(result5) {
|
|
|
6678
6883
|
return Buffer.from(result5.content.filter((part) => part.type === "text").map((part) => part.text).join("\n")).subarray(-MAX_OUTPUT_BYTES).toString("utf8");
|
|
6679
6884
|
}
|
|
6680
6885
|
function createChildCommandRuntime(channelDir) {
|
|
6681
|
-
const ownerId =
|
|
6886
|
+
const ownerId = randomUUID5();
|
|
6682
6887
|
const jobs = /* @__PURE__ */ new Map();
|
|
6683
6888
|
const usedIds = /* @__PURE__ */ new Set();
|
|
6684
6889
|
let closed = false;
|
|
@@ -6834,7 +7039,7 @@ async function waitForCommand(settled, waitMs, signal) {
|
|
|
6834
7039
|
}
|
|
6835
7040
|
}
|
|
6836
7041
|
function commandReplyPath(channelDir, id) {
|
|
6837
|
-
return
|
|
7042
|
+
return path11.join(channelDir, "command-replies", `${id}.json`);
|
|
6838
7043
|
}
|
|
6839
7044
|
async function controlChildCommand(channelDir, operation, toolCallId, signal) {
|
|
6840
7045
|
const current = readChildCommandState(channelDir);
|
|
@@ -6849,7 +7054,7 @@ async function controlChildCommand(channelDir, operation, toolCallId, signal) {
|
|
|
6849
7054
|
throw new Error("Child command controller is closed.");
|
|
6850
7055
|
}
|
|
6851
7056
|
if (operation !== "status" && !toolCallId) throw new Error("An exact toolCallId is required; command controls never target the next command.");
|
|
6852
|
-
const id =
|
|
7057
|
+
const id = randomUUID5();
|
|
6853
7058
|
const deadlineAt = Date.now() + 5e3;
|
|
6854
7059
|
const request = { type: "command", id, ownerId: current.ownerId, operation, ...toolCallId ? { toolCallId } : {}, deadlineAt };
|
|
6855
7060
|
requestAsyncCommand(channelDir, request);
|
|
@@ -6858,7 +7063,7 @@ async function controlChildCommand(channelDir, operation, toolCallId, signal) {
|
|
|
6858
7063
|
while (Date.now() < deadlineAt) {
|
|
6859
7064
|
if (signal?.aborted) throw new Error("Command control wait aborted; delivery may still be pending.");
|
|
6860
7065
|
try {
|
|
6861
|
-
const reply = JSON.parse(
|
|
7066
|
+
const reply = JSON.parse(fs12.readFileSync(replyPath, "utf8"));
|
|
6862
7067
|
if (reply.error) throw new Error(reply.error);
|
|
6863
7068
|
if (reply.state) return reply.state;
|
|
6864
7069
|
} catch (error2) {
|
|
@@ -6868,7 +7073,7 @@ async function controlChildCommand(channelDir, operation, toolCallId, signal) {
|
|
|
6868
7073
|
}
|
|
6869
7074
|
throw new Error("Command control acknowledgement timed out. Inspect command status; cancellation is not confirmed.");
|
|
6870
7075
|
} finally {
|
|
6871
|
-
|
|
7076
|
+
fs12.rmSync(replyPath, { force: true });
|
|
6872
7077
|
}
|
|
6873
7078
|
}
|
|
6874
7079
|
|
|
@@ -6892,8 +7097,8 @@ function pinChildCacheRetention(agent, env) {
|
|
|
6892
7097
|
}
|
|
6893
7098
|
|
|
6894
7099
|
// src/runs/background/runner-aliases.ts
|
|
6895
|
-
import * as
|
|
6896
|
-
import * as
|
|
7100
|
+
import * as fs13 from "node:fs";
|
|
7101
|
+
import * as path12 from "node:path";
|
|
6897
7102
|
var JITI_ALIAS_ENV = "JITI_ALIAS";
|
|
6898
7103
|
var HOST_PEER_ALIASES = [
|
|
6899
7104
|
{ specifier: "@earendil-works/pi-coding-agent", pkg: "@earendil-works/pi-coding-agent", subpath: "." },
|
|
@@ -6916,7 +7121,7 @@ var CHORD_PEER_ALIASES = [
|
|
|
6916
7121
|
];
|
|
6917
7122
|
function readManifest(packageDir) {
|
|
6918
7123
|
try {
|
|
6919
|
-
return JSON.parse(
|
|
7124
|
+
return JSON.parse(fs13.readFileSync(path12.join(packageDir, "package.json"), "utf-8"));
|
|
6920
7125
|
} catch {
|
|
6921
7126
|
return void 0;
|
|
6922
7127
|
}
|
|
@@ -6953,7 +7158,7 @@ function resolvePackageSubpath(packageDir, subpath) {
|
|
|
6953
7158
|
const map = typeof exportsField === "string" || Array.isArray(exportsField) || exportsField && typeof exportsField === "object" && !Object.keys(exportsField).some((key) => key.startsWith(".")) ? { ".": exportsField } : exportsField;
|
|
6954
7159
|
if (subpath in map) {
|
|
6955
7160
|
const exact = exportTarget(map[subpath]);
|
|
6956
|
-
return typeof exact === "string" ?
|
|
7161
|
+
return typeof exact === "string" ? path12.resolve(packageDir, exact) : void 0;
|
|
6957
7162
|
}
|
|
6958
7163
|
let best;
|
|
6959
7164
|
for (const [pattern, entry] of Object.entries(map)) {
|
|
@@ -6968,23 +7173,23 @@ function resolvePackageSubpath(packageDir, subpath) {
|
|
|
6968
7173
|
const target = exportTarget(best.entry);
|
|
6969
7174
|
if (typeof target !== "string") return void 0;
|
|
6970
7175
|
const wildcard = subpath.slice(best.prefix.length, subpath.length - best.suffix.length);
|
|
6971
|
-
return
|
|
7176
|
+
return path12.resolve(packageDir, target.replaceAll("*", () => wildcard));
|
|
6972
7177
|
}
|
|
6973
7178
|
if (subpath !== ".") return void 0;
|
|
6974
|
-
return
|
|
7179
|
+
return path12.resolve(packageDir, typeof manifest.main === "string" && manifest.main.trim() ? manifest.main : "index.js");
|
|
6975
7180
|
}
|
|
6976
7181
|
function findPeerPackageDir(piPackageRoot, pkg, hostName) {
|
|
6977
7182
|
if (hostName === pkg) return piPackageRoot;
|
|
6978
|
-
const candidates = [
|
|
7183
|
+
const candidates = [path12.join(piPackageRoot, "node_modules", pkg)];
|
|
6979
7184
|
let dir = piPackageRoot;
|
|
6980
7185
|
for (; ; ) {
|
|
6981
|
-
const parent =
|
|
7186
|
+
const parent = path12.dirname(dir);
|
|
6982
7187
|
if (parent === dir) break;
|
|
6983
|
-
if (
|
|
6984
|
-
const modulesRoot =
|
|
6985
|
-
candidates.push(
|
|
7188
|
+
if (path12.basename(parent) === "node_modules" || path12.basename(path12.dirname(parent)) === "node_modules") {
|
|
7189
|
+
const modulesRoot = path12.basename(parent) === "node_modules" ? parent : path12.dirname(parent);
|
|
7190
|
+
candidates.push(path12.join(modulesRoot, pkg));
|
|
6986
7191
|
}
|
|
6987
|
-
candidates.push(
|
|
7192
|
+
candidates.push(path12.join(parent, "node_modules", pkg));
|
|
6988
7193
|
dir = parent;
|
|
6989
7194
|
}
|
|
6990
7195
|
return candidates.find((candidate) => readManifest(candidate)?.name === pkg);
|
|
@@ -7001,7 +7206,7 @@ function resolveHostPeerAliases(piPackageRoot) {
|
|
|
7001
7206
|
const packageDir = findPeerPackageDir(piPackageRoot, pkg, hostManifest?.name);
|
|
7002
7207
|
const target = packageDir ? resolvePackageSubpath(packageDir, subpath) : void 0;
|
|
7003
7208
|
if (optional && packageDir && target === void 0) continue;
|
|
7004
|
-
if (target &&
|
|
7209
|
+
if (target && fs13.existsSync(target)) aliases[specifier] = fs13.realpathSync(target);
|
|
7005
7210
|
else if (pkg !== CHORD_PACKAGE || chordRequired) missing.push(specifier);
|
|
7006
7211
|
}
|
|
7007
7212
|
return { aliases, missing };
|
|
@@ -7049,14 +7254,36 @@ function inheritParentProviders(modelRuntime, parentProviders, claimedProviderId
|
|
|
7049
7254
|
}
|
|
7050
7255
|
var CHILD_PROMPT_RUNTIME_EXTENSION_PATH = "<inline:pi-subagents:prompt-runtime>";
|
|
7051
7256
|
var CHILD_PROMPT_BOUNDARY_EXTENSION_PATH = "<inline:pi-subagents:prompt-boundary>";
|
|
7257
|
+
var PI_UNICODE_SPACES = new RegExp("[\\u00A0\\u2000-\\u200A\\u202F\\u205F\\u3000]", "g");
|
|
7258
|
+
function childExtensionFileIdentity(extensionPath) {
|
|
7259
|
+
if (!path13.isAbsolute(extensionPath)) return void 0;
|
|
7260
|
+
try {
|
|
7261
|
+
const stat = fs14.statSync(extensionPath, { bigint: true });
|
|
7262
|
+
return stat.isFile() ? `${stat.dev}:${stat.ino}` : void 0;
|
|
7263
|
+
} catch {
|
|
7264
|
+
return void 0;
|
|
7265
|
+
}
|
|
7266
|
+
}
|
|
7267
|
+
function createRequiredChildExtensionIdentity(requiredPaths) {
|
|
7268
|
+
const aliases = new Map([...requiredPaths].map((extensionPath) => [extensionPath, childExtensionFileIdentity(extensionPath) ?? extensionPath]));
|
|
7269
|
+
const identities = new Set(aliases.values());
|
|
7270
|
+
return (extensionPath) => {
|
|
7271
|
+
const known = aliases.get(extensionPath);
|
|
7272
|
+
if (known !== void 0) return known;
|
|
7273
|
+
const identity = childExtensionFileIdentity(extensionPath);
|
|
7274
|
+
if (identity === void 0 || !identities.has(identity)) return void 0;
|
|
7275
|
+
aliases.set(extensionPath, identity);
|
|
7276
|
+
return identity;
|
|
7277
|
+
};
|
|
7278
|
+
}
|
|
7052
7279
|
function orderChildPromptHooks(result5) {
|
|
7053
|
-
const runtime = result5.extensions.filter(({ path:
|
|
7054
|
-
const boundary = result5.extensions.filter(({ path:
|
|
7055
|
-
const others = result5.extensions.filter(({ path:
|
|
7280
|
+
const runtime = result5.extensions.filter(({ path: path17 }) => path17 === CHILD_PROMPT_RUNTIME_EXTENSION_PATH);
|
|
7281
|
+
const boundary = result5.extensions.filter(({ path: path17 }) => path17 === CHILD_PROMPT_BOUNDARY_EXTENSION_PATH);
|
|
7282
|
+
const others = result5.extensions.filter(({ path: path17 }) => path17 !== CHILD_PROMPT_RUNTIME_EXTENSION_PATH && path17 !== CHILD_PROMPT_BOUNDARY_EXTENSION_PATH);
|
|
7056
7283
|
return { ...result5, extensions: [...runtime, ...others, ...boundary] };
|
|
7057
7284
|
}
|
|
7058
7285
|
function prioritizeChildCommandRuntime(result5) {
|
|
7059
|
-
const index = result5.extensions.findIndex(({ path:
|
|
7286
|
+
const index = result5.extensions.findIndex(({ path: path17 }) => path17 === "<inline:pi-subagents:commands>");
|
|
7060
7287
|
if (index < 0) return result5;
|
|
7061
7288
|
if (result5.extensions.some((extension, otherIndex) => otherIndex !== index && extension.tools?.has("bash"))) {
|
|
7062
7289
|
const extensions2 = result5.extensions.filter((_, otherIndex) => otherIndex !== index);
|
|
@@ -7160,7 +7387,7 @@ function missingBuiltinMcpTools(session, names) {
|
|
|
7160
7387
|
const direct = new Set(session.getAllTools().filter((tool) => tool.exposure === "direct").map(({ name }) => name));
|
|
7161
7388
|
return names.filter((name) => !active2.has(name) || !direct.has(name));
|
|
7162
7389
|
}
|
|
7163
|
-
function flushQueuedProviderRegistrations(loader, modelRuntime, onError,
|
|
7390
|
+
function flushQueuedProviderRegistrations(loader, modelRuntime, onError, isRequiredSource) {
|
|
7164
7391
|
const claimedProviderIds = /* @__PURE__ */ new Set();
|
|
7165
7392
|
if (!("getExtensions" in loader) || typeof loader.getExtensions !== "function") return { claimedProviderIds, registered: false };
|
|
7166
7393
|
const { runtime } = loader.getExtensions();
|
|
@@ -7172,7 +7399,7 @@ function flushQueuedProviderRegistrations(loader, modelRuntime, onError, require
|
|
|
7172
7399
|
registered = true;
|
|
7173
7400
|
} catch (error2) {
|
|
7174
7401
|
onError?.({ extensionPath, event: "register_provider", error: error2 });
|
|
7175
|
-
if (
|
|
7402
|
+
if (isRequiredSource(extensionPath)) throw new Error(`Required child extension provider registration failed for '${extensionPath}': ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
7176
7403
|
}
|
|
7177
7404
|
}
|
|
7178
7405
|
if (Array.isArray(runtime.pendingProviderRegistrations)) runtime.pendingProviderRegistrations = [];
|
|
@@ -7183,7 +7410,7 @@ function flushQueuedProviderRegistrations(loader, modelRuntime, onError, require
|
|
|
7183
7410
|
registered = true;
|
|
7184
7411
|
} catch (error2) {
|
|
7185
7412
|
onError?.({ extensionPath, event: "register_provider", error: error2 });
|
|
7186
|
-
if (
|
|
7413
|
+
if (isRequiredSource(extensionPath)) throw new Error(`Required child extension provider registration failed for '${extensionPath}': ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
7187
7414
|
}
|
|
7188
7415
|
}
|
|
7189
7416
|
if (Array.isArray(runtime.pendingNativeProviderRegistrations)) runtime.pendingNativeProviderRegistrations = [];
|
|
@@ -7197,7 +7424,7 @@ function flushQueuedProviderRegistrations(loader, modelRuntime, onError, require
|
|
|
7197
7424
|
registered = true;
|
|
7198
7425
|
} catch (error2) {
|
|
7199
7426
|
onError?.({ extensionPath, event: "register_virtual_model", error: error2 });
|
|
7200
|
-
if (
|
|
7427
|
+
if (isRequiredSource(extensionPath)) throw new Error(`Required child extension virtual model registration failed for '${extensionPath}': ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
7201
7428
|
}
|
|
7202
7429
|
}
|
|
7203
7430
|
if (Array.isArray(virtualModelQueue.pendingVirtualModelRegistrations)) virtualModelQueue.pendingVirtualModelRegistrations = [];
|
|
@@ -7207,14 +7434,14 @@ async function loadHostPiCodingAgent() {
|
|
|
7207
7434
|
const overrideRoot = process.env[PI_CODING_AGENT_PACKAGE_ROOT_ENV]?.trim() || void 0;
|
|
7208
7435
|
const root = resolveHostPiPackageRoot();
|
|
7209
7436
|
if (root) {
|
|
7210
|
-
const entry =
|
|
7437
|
+
const entry = fs14.realpathSync(resolveHostPackageEntry(root, root === overrideRoot ? overrideRoot : void 0));
|
|
7211
7438
|
return import(pathToFileURL(entry).href);
|
|
7212
7439
|
}
|
|
7213
7440
|
return import(PI_CODING_AGENT_PACKAGE);
|
|
7214
7441
|
}
|
|
7215
7442
|
function resolveHostPackageEntry(root, overrideRoot) {
|
|
7216
|
-
const packageJson =
|
|
7217
|
-
const source =
|
|
7443
|
+
const packageJson = path13.join(root, "package.json");
|
|
7444
|
+
const source = fs14.readFileSync(packageJson, "utf8");
|
|
7218
7445
|
let parsed;
|
|
7219
7446
|
try {
|
|
7220
7447
|
parsed = JSON.parse(source);
|
|
@@ -7275,6 +7502,12 @@ function createDefaultChildSessionFactory(options = {}) {
|
|
|
7275
7502
|
return typeof value === "function" ? value.bind(target) : value;
|
|
7276
7503
|
}
|
|
7277
7504
|
})) }] : [];
|
|
7505
|
+
const requiredPaths = new Set((launch.requiredExtensions ?? []).map(({ path: path17 }) => path17));
|
|
7506
|
+
const requiredExtensionIdentity = createRequiredChildExtensionIdentity(requiredPaths);
|
|
7507
|
+
const resolvedExtensionPaths = /* @__PURE__ */ new Map();
|
|
7508
|
+
const isRequiredSource = (extensionPath) => requiredPaths.size > 0 && requiredExtensionIdentity(
|
|
7509
|
+
resolvedExtensionPaths.get(extensionPath) ?? extensionPath.replace(PI_UNICODE_SPACES, " ")
|
|
7510
|
+
) !== void 0;
|
|
7278
7511
|
const loader = new pi.DefaultResourceLoader({
|
|
7279
7512
|
cwd: launch.cwd,
|
|
7280
7513
|
agentDir,
|
|
@@ -7311,13 +7544,18 @@ function createDefaultChildSessionFactory(options = {}) {
|
|
|
7311
7544
|
};
|
|
7312
7545
|
let inheritedProviders = /* @__PURE__ */ new Set();
|
|
7313
7546
|
const open = async () => {
|
|
7314
|
-
const requiredPaths = new Set((launch.requiredExtensions ?? []).map(({ path: path15 }) => path15));
|
|
7315
7547
|
applyProcessEnv(launch.processEnv);
|
|
7316
7548
|
if (!resetExtensionCacheOnReload(loader) && (launch.ambientExtensions || launch.extensionPaths.length)) launch.onExtensionError?.({ extensionPath: "<loader>", event: "load", error: new Error("pi's extension cache reset is unavailable; extensions loaded into this child share module state with other sessions in this process.") });
|
|
7317
7549
|
await loader.reload();
|
|
7318
|
-
|
|
7319
|
-
|
|
7320
|
-
|
|
7550
|
+
if (requiredPaths.size > 0) for (const extension of loader.getExtensions().extensions) resolvedExtensionPaths.set(extension.path, extension.resolvedPath);
|
|
7551
|
+
const loadErrors = requiredPaths.size > 0 ? loader.getExtensions().errors.filter(({ path: path17 }) => isRequiredSource(path17)) : [];
|
|
7552
|
+
if (loadErrors.length > 0) throw new Error(`Required child extension failed to load: ${loadErrors.map(({ path: path17, error: error2 }) => `${path17}: ${error2}`).join("; ")}`);
|
|
7553
|
+
const queued = flushQueuedProviderRegistrations(loader, modelRuntime, launch.onExtensionError, isRequiredSource);
|
|
7554
|
+
if (requiredPaths.size > 0) {
|
|
7555
|
+
const loadedIdentities = new Set(loader.getExtensions().extensions.map(({ resolvedPath }) => requiredExtensionIdentity(resolvedPath)));
|
|
7556
|
+
const missing = [...requiredPaths].filter((extensionPath) => !loadedIdentities.has(requiredExtensionIdentity(extensionPath)));
|
|
7557
|
+
if (missing.length) throw new Error(`Required child extension failed to load: ${missing.join(", ")}`);
|
|
7558
|
+
}
|
|
7321
7559
|
if (launch.parentProviderRegistry) inheritedProviders = inheritParentProviders(modelRuntime, launch.parentProviderRegistry, queued.claimedProviderIds, launch.onExtensionError);
|
|
7322
7560
|
if (queued.registered || inheritedProviders.size > 0) {
|
|
7323
7561
|
try {
|
|
@@ -7328,7 +7566,7 @@ function createDefaultChildSessionFactory(options = {}) {
|
|
|
7328
7566
|
}
|
|
7329
7567
|
}
|
|
7330
7568
|
const newSessionOptions = launch.parentSessionFile ? { parentSession: launch.parentSessionFile } : void 0;
|
|
7331
|
-
const newSessionFile = launch.storage.kind === "file" && !
|
|
7569
|
+
const newSessionFile = launch.storage.kind === "file" && !fs14.existsSync(launch.storage.sessionFile);
|
|
7332
7570
|
const sessionManager = launch.storage.kind === "file" ? pi.SessionManager.open(launch.storage.sessionFile, void 0, launch.cwd) : launch.storage.kind === "dir" ? pi.SessionManager.create(launch.cwd, launch.storage.sessionDir, newSessionOptions) : launch.storage.kind === "memory" ? pi.SessionManager.inMemory(launch.cwd, newSessionOptions) : pi.SessionManager.create(launch.cwd, void 0, newSessionOptions);
|
|
7333
7571
|
const header = newSessionFile && launch.parentSessionFile ? sessionManager.getHeader() : null;
|
|
7334
7572
|
if (header) header.parentSession = launch.parentSessionFile;
|
|
@@ -7354,7 +7592,7 @@ function createDefaultChildSessionFactory(options = {}) {
|
|
|
7354
7592
|
await session2.bindExtensions({
|
|
7355
7593
|
mode: "print",
|
|
7356
7594
|
onError: (error2) => {
|
|
7357
|
-
if (binding &&
|
|
7595
|
+
if (binding && isRequiredSource(error2.extensionPath)) requiredStartupErrors.push(`${error2.extensionPath} (${error2.event}): ${error2.error}`);
|
|
7358
7596
|
launch.onExtensionError?.({ extensionPath: error2.extensionPath, event: error2.event, error: error2.error });
|
|
7359
7597
|
}
|
|
7360
7598
|
});
|
|
@@ -7382,7 +7620,7 @@ function createDefaultChildSessionFactory(options = {}) {
|
|
|
7382
7620
|
if (disposals !== disposalsAtStart) cancel.abort();
|
|
7383
7621
|
const ready = (async () => {
|
|
7384
7622
|
try {
|
|
7385
|
-
await builtinMcp.waitForTools(signal, () => loader.getExtensions().errors.find(({ path:
|
|
7623
|
+
await builtinMcp.waitForTools(signal, () => loader.getExtensions().errors.find(({ path: path17 }) => path17 === "builtin:mcp")?.error);
|
|
7386
7624
|
if (disposals !== disposalsAtStart) throw new Error("The child session factory was disposed while the child waited for its MCP tools.");
|
|
7387
7625
|
if (signal.aborted) throw new Error("The child launch was cancelled while waiting for its MCP tools.");
|
|
7388
7626
|
const missing = missingBuiltinMcpTools(session, builtinMcpTools.map(({ name }) => name));
|
|
@@ -7471,7 +7709,7 @@ function childSessionFactory() {
|
|
|
7471
7709
|
}
|
|
7472
7710
|
function createLazyPlacementFactory(local) {
|
|
7473
7711
|
let placed;
|
|
7474
|
-
const factory = async () => placed ??= (await import("./chunk-
|
|
7712
|
+
const factory = async () => placed ??= (await import("./chunk-PDZWJCHD.js")).createPlacementAwareChildSessionFactory(local);
|
|
7475
7713
|
return {
|
|
7476
7714
|
async create(launch) {
|
|
7477
7715
|
return launch.machine ? (await factory()).create(launch) : local.create(launch);
|
|
@@ -7493,13 +7731,10 @@ async function disposeChildSessions() {
|
|
|
7493
7731
|
|
|
7494
7732
|
// src/workflows/workflow-reuse.ts
|
|
7495
7733
|
import { createHash as createHash3 } from "node:crypto";
|
|
7496
|
-
import * as
|
|
7497
|
-
import * as
|
|
7498
|
-
var WORKFLOW_STOP_CAUSE_RUNTIME_REPLACED = "runtime-replaced";
|
|
7734
|
+
import * as fs15 from "node:fs";
|
|
7735
|
+
import * as path14 from "node:path";
|
|
7499
7736
|
var WORKFLOW_CHILD_JOURNAL_FILE = "workflow-children.jsonl";
|
|
7500
|
-
|
|
7501
|
-
return `Async children that were still running keep running. Relaunching the same workflow script with the same args from this session reuses finished children and re-attaches to running ones; from another session, add options: { reuseFrom: "${runId}" }.`;
|
|
7502
|
-
}
|
|
7737
|
+
var WORKFLOW_RUNTIME_REPLACED_STOP_NOTICE = "Async children that were still running keep running, and finished children are kept.";
|
|
7503
7738
|
function runtimeReplacedAbortReason() {
|
|
7504
7739
|
return Object.assign(new Error("Workflow stopped because the extension session was replaced or reloaded."), { workflowStopCause: WORKFLOW_STOP_CAUSE_RUNTIME_REPLACED });
|
|
7505
7740
|
}
|
|
@@ -7516,9 +7751,9 @@ function workflowChildFingerprint(params) {
|
|
|
7516
7751
|
return sha256(workflowRunParamsFingerprint(params));
|
|
7517
7752
|
}
|
|
7518
7753
|
function appendWorkflowChildJournal(workflowAsyncDir, record) {
|
|
7519
|
-
const journalPath =
|
|
7754
|
+
const journalPath = path14.join(workflowAsyncDir, WORKFLOW_CHILD_JOURNAL_FILE);
|
|
7520
7755
|
try {
|
|
7521
|
-
|
|
7756
|
+
fs15.appendFileSync(journalPath, `${JSON.stringify(record)}
|
|
7522
7757
|
`, "utf-8");
|
|
7523
7758
|
} catch (error2) {
|
|
7524
7759
|
console.error(`Failed to append workflow child journal '${journalPath}':`, error2);
|
|
@@ -7528,7 +7763,7 @@ function readWorkflowChildJournal(runId, workflowAsyncDir) {
|
|
|
7528
7763
|
const source = { runId, started: /* @__PURE__ */ new Map(), settled: /* @__PURE__ */ new Map() };
|
|
7529
7764
|
let text2 = "";
|
|
7530
7765
|
try {
|
|
7531
|
-
text2 =
|
|
7766
|
+
text2 = fs15.readFileSync(path14.join(workflowAsyncDir, WORKFLOW_CHILD_JOURNAL_FILE), "utf-8");
|
|
7532
7767
|
} catch {
|
|
7533
7768
|
return source;
|
|
7534
7769
|
}
|
|
@@ -7548,6 +7783,44 @@ function readWorkflowChildJournal(runId, workflowAsyncDir) {
|
|
|
7548
7783
|
}
|
|
7549
7784
|
return source;
|
|
7550
7785
|
}
|
|
7786
|
+
var WORKFLOW_REUSE_CHAIN_MAX_LINKS = 16;
|
|
7787
|
+
function readWorkflowReuseChain(asyncDirRoot, headRunId, headStatus) {
|
|
7788
|
+
const source = readWorkflowChildJournal(headRunId, path14.join(asyncDirRoot, headRunId));
|
|
7789
|
+
const visited = /* @__PURE__ */ new Set([headRunId]);
|
|
7790
|
+
let next = headStatus.workflow?.reusedFrom;
|
|
7791
|
+
for (let links = 0; isRunId(next) && links < WORKFLOW_REUSE_CHAIN_MAX_LINKS && !visited.has(next); links++) {
|
|
7792
|
+
visited.add(next);
|
|
7793
|
+
const asyncDir = path14.join(asyncDirRoot, next);
|
|
7794
|
+
let status;
|
|
7795
|
+
try {
|
|
7796
|
+
status = readStatus(asyncDir);
|
|
7797
|
+
} catch {
|
|
7798
|
+
break;
|
|
7799
|
+
}
|
|
7800
|
+
if (status?.mode !== "workflow" || status.workflow?.scriptDigest !== headStatus.workflow?.scriptDigest) break;
|
|
7801
|
+
const link = readWorkflowChildJournal(next, asyncDir);
|
|
7802
|
+
for (const key of /* @__PURE__ */ new Set([...link.started.keys(), ...link.settled.keys()])) {
|
|
7803
|
+
if (source.started.has(key) || source.settled.has(key)) continue;
|
|
7804
|
+
const started = link.started.get(key);
|
|
7805
|
+
const settled = link.settled.get(key);
|
|
7806
|
+
if (started) source.started.set(key, started);
|
|
7807
|
+
if (settled) source.settled.set(key, settled);
|
|
7808
|
+
}
|
|
7809
|
+
next = status.workflow?.reusedFrom;
|
|
7810
|
+
}
|
|
7811
|
+
return source;
|
|
7812
|
+
}
|
|
7813
|
+
function claimedWorkflowRunRefusal(runId, claim, named) {
|
|
7814
|
+
const run = `${named ? "options.reuseFrom: workflow" : "Workflow"} run '${runId}'`;
|
|
7815
|
+
if (claim.kind === "resumed") return `${run} was resumed as ${claim.newRunId}; wait on it or stop it.`;
|
|
7816
|
+
if (claim.kind === "busy") return `${run} is being resumed by pid ${claim.pid}; wait for it.`;
|
|
7817
|
+
return `${run} has a resume claim that cannot be read; wait or remove it.`;
|
|
7818
|
+
}
|
|
7819
|
+
function assertWorkflowRunNotClaimed(asyncDir, runId, named, claimOwnerRunId) {
|
|
7820
|
+
const claim = workflowResumeClaimState(asyncDir);
|
|
7821
|
+
if (claim.kind === "none" || claim.kind !== "unreadable" && claim.newRunId === claimOwnerRunId) return;
|
|
7822
|
+
throw new Error(claimedWorkflowRunRefusal(runId, claim, named));
|
|
7823
|
+
}
|
|
7551
7824
|
function findWorkflowReuseSource(asyncDirRoot, sessionId, scriptDigest, argsDigest) {
|
|
7552
7825
|
let runIds;
|
|
7553
7826
|
try {
|
|
@@ -7556,7 +7829,7 @@ function findWorkflowReuseSource(asyncDirRoot, sessionId, scriptDigest, argsDige
|
|
|
7556
7829
|
return void 0;
|
|
7557
7830
|
}
|
|
7558
7831
|
for (const runId of runIds) {
|
|
7559
|
-
const asyncDir =
|
|
7832
|
+
const asyncDir = path14.join(asyncDirRoot, runId);
|
|
7560
7833
|
let status;
|
|
7561
7834
|
try {
|
|
7562
7835
|
status = readStatus(asyncDir);
|
|
@@ -7564,18 +7837,23 @@ function findWorkflowReuseSource(asyncDirRoot, sessionId, scriptDigest, argsDige
|
|
|
7564
7837
|
continue;
|
|
7565
7838
|
}
|
|
7566
7839
|
if (status?.mode !== "workflow" || status.workflow?.scriptDigest !== scriptDigest || status.workflow.argsDigest !== argsDigest) continue;
|
|
7567
|
-
|
|
7840
|
+
if (!isReusableWorkflowStopCause(status.workflow.stopCause)) return void 0;
|
|
7841
|
+
assertWorkflowRunNotClaimed(asyncDir, runId, false);
|
|
7842
|
+
return readWorkflowReuseChain(asyncDirRoot, runId, status);
|
|
7568
7843
|
}
|
|
7569
7844
|
return void 0;
|
|
7570
7845
|
}
|
|
7571
|
-
function readNamedWorkflowReuseSource(asyncDirRoot, runId, scriptDigest, cwd) {
|
|
7572
|
-
const
|
|
7846
|
+
function readNamedWorkflowReuseSource(asyncDirRoot, runId, scriptDigest, cwd, claimOwnerRunId) {
|
|
7847
|
+
const runName = isRunId(runId);
|
|
7848
|
+
if (runName) reconcileAsyncRun(path14.join(asyncDirRoot, runId));
|
|
7849
|
+
const status = runName ? readStatus(path14.join(asyncDirRoot, runId)) : null;
|
|
7573
7850
|
if (!status) throw new Error(`options.reuseFrom: workflow run '${runId}' was not found.`);
|
|
7574
7851
|
if (status.mode !== "workflow") throw new Error(`options.reuseFrom: run '${runId}' is not a workflow run.`);
|
|
7575
|
-
if (status.workflow?.stopCause
|
|
7852
|
+
if (!isReusableWorkflowStopCause(status.workflow?.stopCause)) throw new Error(`options.reuseFrom: workflow run '${runId}' did not stop because its session was replaced or its Pi process exited, so its children cannot be reused.`);
|
|
7576
7853
|
if (status.workflow.scriptDigest !== scriptDigest) throw new Error(`options.reuseFrom: workflow run '${runId}' ran a different workflow script.`);
|
|
7577
7854
|
if (status.cwd !== cwd) throw new Error(`options.reuseFrom: workflow run '${runId}' ran in '${status.cwd}', not '${cwd}'.`);
|
|
7578
|
-
|
|
7855
|
+
assertWorkflowRunNotClaimed(path14.join(asyncDirRoot, runId), runId, true, claimOwnerRunId);
|
|
7856
|
+
return readWorkflowReuseChain(asyncDirRoot, runId, status);
|
|
7579
7857
|
}
|
|
7580
7858
|
function matchWorkflowReuse(source, key, fingerprint) {
|
|
7581
7859
|
const settled = source.settled.get(key);
|
|
@@ -7584,6 +7862,74 @@ function matchWorkflowReuse(source, key, fingerprint) {
|
|
|
7584
7862
|
return started?.fingerprint === fingerprint ? { kind: "started", runId: started.runId } : void 0;
|
|
7585
7863
|
}
|
|
7586
7864
|
|
|
7865
|
+
// src/workflows/workflow-resume.ts
|
|
7866
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
7867
|
+
import * as path15 from "node:path";
|
|
7868
|
+
var MAX_WORKFLOW_RESUME_ATTEMPTS = 3;
|
|
7869
|
+
function workflowResumeRefusal(code, reason, runId) {
|
|
7870
|
+
const byHand = code === "launch-missing" || code === "launch-invalid" || code === "structured" || code === "digest" || code === "authority" ? relaunchByHandText(runId) : code === "capacity" || code === "attempts" || code === "launch-failed" ? resumeByHandText(runId) : "";
|
|
7871
|
+
return { code, reason, byHand };
|
|
7872
|
+
}
|
|
7873
|
+
function manualWorkflowResumeRefusalText(runId, refusal) {
|
|
7874
|
+
return `Cannot resume workflow '${runId}': ${refusalSentence(refusal.reason, refusal.byHand === relaunchByHandText(runId) ? refusal.byHand : "")}`;
|
|
7875
|
+
}
|
|
7876
|
+
function workflowResumeTargetId(asyncDirRoot, params) {
|
|
7877
|
+
if (params.message !== void 0 || params.task !== void 0 || params.index !== void 0) return void 0;
|
|
7878
|
+
if (Array.isArray(params.chain) && params.chain.length > 0) return void 0;
|
|
7879
|
+
const id = params.id ?? params.runId;
|
|
7880
|
+
if (!isRunId(id)) return void 0;
|
|
7881
|
+
let status;
|
|
7882
|
+
try {
|
|
7883
|
+
status = readStatus(path15.join(asyncDirRoot, id));
|
|
7884
|
+
} catch {
|
|
7885
|
+
return void 0;
|
|
7886
|
+
}
|
|
7887
|
+
return status?.mode === "workflow" && status.parentWorkflowRunId === void 0 ? id : void 0;
|
|
7888
|
+
}
|
|
7889
|
+
function prepareWorkflowResume(input) {
|
|
7890
|
+
const { asyncDirRoot, sourceRunId, trigger } = input;
|
|
7891
|
+
const refuse = (code, reason) => ({ ok: false, ...workflowResumeRefusal(code, reason, sourceRunId) });
|
|
7892
|
+
const sourceAsyncDir = path15.join(asyncDirRoot, sourceRunId);
|
|
7893
|
+
const stored = readStatus(sourceAsyncDir);
|
|
7894
|
+
if (stored && !ownsRun(input.currentSessionId, stored.sessionId)) return refuse("not-owned", "it was started in another session; reopen that session to resume it");
|
|
7895
|
+
if (trigger === "manual") reconcileAsyncRun(sourceAsyncDir);
|
|
7896
|
+
const status = readStatus(sourceAsyncDir);
|
|
7897
|
+
if (!status || status.mode !== "workflow" || !isReusableWorkflowStopCause(status.workflow?.stopCause)) return refuse("not-interrupted", "not interrupted; pass message to revive one of its children");
|
|
7898
|
+
const attempt = (status.workflow?.resumeOrigin?.attempt ?? 0) + (status.workflow?.stopCause === WORKFLOW_STOP_CAUSE_OWNER_EXITED ? 1 : 0);
|
|
7899
|
+
if (trigger === "auto" && attempt > MAX_WORKFLOW_RESUME_ATTEMPTS) return refuse("attempts", `${MAX_WORKFLOW_RESUME_ATTEMPTS} resumes already followed Pi exits`);
|
|
7900
|
+
const read = readWorkflowLaunchRecord(sourceAsyncDir);
|
|
7901
|
+
if (!read.ok) return read.missing ? refuse("launch-missing", "launch.json is missing (the run started before launches were saved)") : refuse("launch-invalid", `launch.json is invalid: ${read.error}`);
|
|
7902
|
+
const record = read.record;
|
|
7903
|
+
if (record.source === "structured") return refuse("structured", "launched from tasks/chain");
|
|
7904
|
+
if (record.source === "script" && (typeof record.params.workflowScript !== "string" || workflowScriptDigest(record.params.workflowScript) !== record.scriptDigest)) {
|
|
7905
|
+
return refuse("launch-invalid", "launch.json is invalid: its script does not match its scriptDigest");
|
|
7906
|
+
}
|
|
7907
|
+
const normalized = normalizePublicSubagentExecution(record.params);
|
|
7908
|
+
if (!normalized.ok) return refuse("launch-invalid", `launch.json is invalid: ${normalized.error}`);
|
|
7909
|
+
const newRunId = input.newRunId ?? randomUUID6();
|
|
7910
|
+
const claim = claimWorkflowResume(sourceAsyncDir, { newRunId, trigger, now: input.now });
|
|
7911
|
+
if (claim.kind === "resumed") return refuse("claim-resumed", `resumed as ${claim.newRunId}; wait on it or stop it`);
|
|
7912
|
+
if (claim.kind === "busy") return refuse("claim-busy", `being resumed by pid ${claim.pid}`);
|
|
7913
|
+
if (claim.kind === "unreadable") return refuse("claim-busy", "its resume claim cannot be read; wait or remove it");
|
|
7914
|
+
return {
|
|
7915
|
+
ok: true,
|
|
7916
|
+
prepared: {
|
|
7917
|
+
sourceRunId,
|
|
7918
|
+
sourceAsyncDir,
|
|
7919
|
+
newRunId,
|
|
7920
|
+
trigger,
|
|
7921
|
+
params: {
|
|
7922
|
+
...record.params,
|
|
7923
|
+
async: true,
|
|
7924
|
+
reuseFrom: sourceRunId,
|
|
7925
|
+
...status.admissionCapabilityCeiling ? { capabilityCeiling: status.admissionCapabilityCeiling } : {}
|
|
7926
|
+
},
|
|
7927
|
+
expectedScriptDigest: record.scriptDigest,
|
|
7928
|
+
resumeOrigin: { sourceRunId, attempt }
|
|
7929
|
+
}
|
|
7930
|
+
};
|
|
7931
|
+
}
|
|
7932
|
+
|
|
7587
7933
|
// src/extension/subagent-options.ts
|
|
7588
7934
|
import { isDeepStrictEqual } from "node:util";
|
|
7589
7935
|
import { Compile } from "typebox/compile";
|
|
@@ -7622,140 +7968,8 @@ function flattenSubagentToolOptions(params, disabled) {
|
|
|
7622
7968
|
return { ...topLevel, ...options };
|
|
7623
7969
|
}
|
|
7624
7970
|
|
|
7625
|
-
// src/runs/foreground/foreground-history.ts
|
|
7626
|
-
import * as fs14 from "node:fs";
|
|
7627
|
-
import * as path13 from "node:path";
|
|
7628
|
-
var MAX_REMEMBERED_FOREGROUND_RUNS = 50;
|
|
7629
|
-
var HISTORY_VERSION = 1;
|
|
7630
|
-
var MAX_INLINE_OUTPUT_BYTES = 64 * 1024;
|
|
7631
|
-
var MAX_RESUME_CONTRACT_BYTES = 64 * 1024;
|
|
7632
|
-
function historyPath(resultsDir) {
|
|
7633
|
-
return path13.join(resultsDir, "foreground-history.json");
|
|
7634
|
-
}
|
|
7635
|
-
function boundedTail(value) {
|
|
7636
|
-
return utf8Tail(value, MAX_INLINE_OUTPUT_BYTES).text;
|
|
7637
|
-
}
|
|
7638
|
-
function compactChild(child) {
|
|
7639
|
-
const outputPath = child.artifactPaths?.outputPath ?? child.savedOutputPath;
|
|
7640
|
-
return {
|
|
7641
|
-
agent: child.agent,
|
|
7642
|
-
index: child.index,
|
|
7643
|
-
...child.sessionName ? { sessionName: child.sessionName } : {},
|
|
7644
|
-
...child.context ? { context: child.context } : {},
|
|
7645
|
-
...child.sessionFile ? { sessionFile: child.sessionFile } : {},
|
|
7646
|
-
...child.model ? { model: child.model } : {},
|
|
7647
|
-
...child.thinking ? { thinking: child.thinking } : {},
|
|
7648
|
-
status: child.status,
|
|
7649
|
-
...child.activityState ? { activityState: child.activityState } : {},
|
|
7650
|
-
...child.lastActivityAt !== void 0 ? { lastActivityAt: child.lastActivityAt } : {},
|
|
7651
|
-
...child.currentTool ? { currentTool: child.currentTool } : {},
|
|
7652
|
-
...child.currentToolStartedAt !== void 0 ? { currentToolStartedAt: child.currentToolStartedAt } : {},
|
|
7653
|
-
...child.currentPath ? { currentPath: child.currentPath } : {},
|
|
7654
|
-
...child.turnCount !== void 0 ? { turnCount: child.turnCount } : {},
|
|
7655
|
-
...child.tokens !== void 0 ? { tokens: child.tokens } : {},
|
|
7656
|
-
...child.toolCount !== void 0 ? { toolCount: child.toolCount } : {},
|
|
7657
|
-
...child.exitCode !== void 0 ? { exitCode: child.exitCode } : {},
|
|
7658
|
-
...child.error ? { error: child.error } : {},
|
|
7659
|
-
...!outputPath && child.finalOutput ? { finalOutput: boundedTail(child.finalOutput) } : {},
|
|
7660
|
-
...child.outputState ? { outputState: child.outputState } : {},
|
|
7661
|
-
...child.outputPartial ? { outputPartial: true } : {},
|
|
7662
|
-
...child.outputMode ? { outputMode: child.outputMode } : {},
|
|
7663
|
-
...child.savedOutputPath ? { savedOutputPath: child.savedOutputPath } : {},
|
|
7664
|
-
...child.outputSaveError ? { outputSaveError: child.outputSaveError } : {},
|
|
7665
|
-
...child.artifactPaths ? { artifactPaths: child.artifactPaths } : {},
|
|
7666
|
-
...child.transcriptPath ? { transcriptPath: child.transcriptPath } : {},
|
|
7667
|
-
...child.transcriptError ? { transcriptError: child.transcriptError } : {},
|
|
7668
|
-
...child.acceptance ? { acceptance: child.acceptance } : {},
|
|
7669
|
-
...child.resumeContract ? { resumeContract: child.resumeContract } : {},
|
|
7670
|
-
...child.launchContractDigest ? { launchContractDigest: child.launchContractDigest } : {},
|
|
7671
|
-
...child.extensionBindings ? { extensionBindings: child.extensionBindings } : {},
|
|
7672
|
-
...child.requiredExtensions ? { requiredExtensions: child.requiredExtensions } : {},
|
|
7673
|
-
...child.capabilityCeiling ? { capabilityCeiling: child.capabilityCeiling } : {},
|
|
7674
|
-
...child.updatedAt !== void 0 ? { updatedAt: child.updatedAt } : {}
|
|
7675
|
-
};
|
|
7676
|
-
}
|
|
7677
|
-
function isRestorableForegroundStatus(status) {
|
|
7678
|
-
return status === "completed" || status === "failed" || status === "paused" || status === "stopped";
|
|
7679
|
-
}
|
|
7680
|
-
function isRestorableResumeContract(value) {
|
|
7681
|
-
if (value === void 0) return true;
|
|
7682
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
7683
|
-
try {
|
|
7684
|
-
if (Buffer.byteLength(JSON.stringify(value), "utf8") > MAX_RESUME_CONTRACT_BYTES) return false;
|
|
7685
|
-
validateModelResponseAliases(value?.modelResponseAliases);
|
|
7686
|
-
} catch {
|
|
7687
|
-
return false;
|
|
7688
|
-
}
|
|
7689
|
-
const contract = value;
|
|
7690
|
-
if (Object.keys(contract).some((key) => !["modelResponseAliases", "outputSchema", "agentContract", "acceptance", "output", "outputMode"].includes(key))) return false;
|
|
7691
|
-
if (contract.outputSchema !== void 0 && contract.outputSchema !== false && (!contract.outputSchema || typeof contract.outputSchema !== "object" || Array.isArray(contract.outputSchema))) return false;
|
|
7692
|
-
if (contract.agentContract !== void 0 && (!contract.agentContract || typeof contract.agentContract !== "object" || Array.isArray(contract.agentContract) || contract.agentContract.version !== 1)) return false;
|
|
7693
|
-
if (validateAcceptanceInput(contract.acceptance).length > 0) return false;
|
|
7694
|
-
if (contract.output !== void 0 && typeof contract.output !== "string" && typeof contract.output !== "boolean") return false;
|
|
7695
|
-
return contract.outputMode === void 0 || contract.outputMode === "inline" || contract.outputMode === "file-only";
|
|
7696
|
-
}
|
|
7697
|
-
function compactRun(run) {
|
|
7698
|
-
if (!run.sessionId) return void 0;
|
|
7699
|
-
if (run.children.length === 0 || !run.children.every((child) => isRestorableForegroundStatus(child.status) && isRestorableResumeContract(child.resumeContract))) return void 0;
|
|
7700
|
-
return {
|
|
7701
|
-
runId: run.runId,
|
|
7702
|
-
mode: run.mode,
|
|
7703
|
-
cwd: run.cwd,
|
|
7704
|
-
sessionId: run.sessionId,
|
|
7705
|
-
updatedAt: run.updatedAt,
|
|
7706
|
-
children: run.children.map(compactChild)
|
|
7707
|
-
};
|
|
7708
|
-
}
|
|
7709
|
-
function readIndex(resultsDir) {
|
|
7710
|
-
const filePath = historyPath(resultsDir);
|
|
7711
|
-
if (!fs14.existsSync(filePath)) return { version: HISTORY_VERSION, runs: [] };
|
|
7712
|
-
try {
|
|
7713
|
-
const parsed = JSON.parse(fs14.readFileSync(filePath, "utf-8"));
|
|
7714
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return { version: HISTORY_VERSION, runs: [] };
|
|
7715
|
-
const record = parsed;
|
|
7716
|
-
if (record.version !== HISTORY_VERSION || !Array.isArray(record.runs)) return { version: HISTORY_VERSION, runs: [] };
|
|
7717
|
-
return { version: HISTORY_VERSION, runs: record.runs.filter(isRestorableRun) };
|
|
7718
|
-
} catch {
|
|
7719
|
-
return { version: HISTORY_VERSION, runs: [] };
|
|
7720
|
-
}
|
|
7721
|
-
}
|
|
7722
|
-
function isRestorableRun(value) {
|
|
7723
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
7724
|
-
const run = value;
|
|
7725
|
-
return typeof run.runId === "string" && Boolean(run.runId) && (run.mode === "single" || run.mode === "parallel" || run.mode === "chain") && typeof run.cwd === "string" && Boolean(run.cwd) && typeof run.sessionId === "string" && Boolean(run.sessionId) && typeof run.updatedAt === "number" && Number.isFinite(run.updatedAt) && Array.isArray(run.children) && run.children.length > 0 && run.children.every((child) => Boolean(child && typeof child === "object" && !Array.isArray(child) && typeof child.agent === "string" && typeof child.index === "number" && isRestorableResumeContract(child.resumeContract) && isRestorableForegroundStatus(child.status)));
|
|
7726
|
-
}
|
|
7727
|
-
function sortAndBound(runs, limit) {
|
|
7728
|
-
return [...runs].sort((left, right) => right.updatedAt - left.updatedAt).slice(0, limit);
|
|
7729
|
-
}
|
|
7730
|
-
function persistForegroundRunHistory(state, options = {}) {
|
|
7731
|
-
const resultsDir = options.resultsDir ?? DIRS.results;
|
|
7732
|
-
const limit = options.limit ?? MAX_REMEMBERED_FOREGROUND_RUNS;
|
|
7733
|
-
const existing = readIndex(resultsDir);
|
|
7734
|
-
const merged = new Map(existing.runs.map((run) => [run.runId, run]));
|
|
7735
|
-
for (const run of state.foregroundRuns?.values() ?? []) {
|
|
7736
|
-
const compact = compactRun(run);
|
|
7737
|
-
if (compact) merged.set(compact.runId, compact);
|
|
7738
|
-
}
|
|
7739
|
-
const runs = sortAndBound([...merged.values()], limit);
|
|
7740
|
-
writePrivateAtomicJson(historyPath(resultsDir), { version: HISTORY_VERSION, runs });
|
|
7741
|
-
}
|
|
7742
|
-
function restoreForegroundRunHistory(state, options = {}) {
|
|
7743
|
-
const sessionId = options.sessionId ?? state.currentSessionId;
|
|
7744
|
-
if (!sessionId) return 0;
|
|
7745
|
-
const index = readIndex(options.resultsDir ?? DIRS.results);
|
|
7746
|
-
const runs = sortAndBound(index.runs.filter((run) => run.sessionId === sessionId), options.limit ?? MAX_REMEMBERED_FOREGROUND_RUNS);
|
|
7747
|
-
state.foregroundRuns ??= /* @__PURE__ */ new Map();
|
|
7748
|
-
let restored = 0;
|
|
7749
|
-
for (const run of runs) {
|
|
7750
|
-
if (state.foregroundRuns.has(run.runId)) continue;
|
|
7751
|
-
state.foregroundRuns.set(run.runId, run);
|
|
7752
|
-
restored += 1;
|
|
7753
|
-
}
|
|
7754
|
-
return restored;
|
|
7755
|
-
}
|
|
7756
|
-
|
|
7757
7971
|
// src/runs/background/retained-children.ts
|
|
7758
|
-
import * as
|
|
7972
|
+
import * as fs16 from "node:fs";
|
|
7759
7973
|
var MAX_RETAINED_CHILDREN = 10;
|
|
7760
7974
|
var MAX_RETAINED_CHILD_CANDIDATES = 100;
|
|
7761
7975
|
var MAX_TASK_SUMMARY_LENGTH = 120;
|
|
@@ -7769,7 +7983,7 @@ function retainedSessionFile(sessionFile) {
|
|
|
7769
7983
|
if (!sessionFile) return { state: "not-resumable", reason: "no persisted session file" };
|
|
7770
7984
|
if (!sessionFile.endsWith(".jsonl")) return { state: "not-resumable", reason: "persisted session file is not a .jsonl file" };
|
|
7771
7985
|
try {
|
|
7772
|
-
const stat =
|
|
7986
|
+
const stat = fs16.lstatSync(sessionFile);
|
|
7773
7987
|
if (!stat.isFile() || stat.isSymbolicLink()) return { state: "not-resumable", reason: "persisted session file is not a regular file" };
|
|
7774
7988
|
return { state: "resumable", sessionPath: sessionFile };
|
|
7775
7989
|
} catch (error2) {
|
|
@@ -7796,7 +8010,7 @@ function childResumability(run, step) {
|
|
|
7796
8010
|
let requiredCwd;
|
|
7797
8011
|
try {
|
|
7798
8012
|
requiredCwd = resolveRetainedWorktreeCwd(parallelHandoffPath(run.asyncDir), run.id, step.index) ?? recoveryDescriptor.cwd ?? run.cwd;
|
|
7799
|
-
if (!requiredCwd || !
|
|
8013
|
+
if (!requiredCwd || !fs16.statSync(requiredCwd).isDirectory()) return { state: "not-resumable", reason: `required cwd is missing: ${requiredCwd ?? "unknown"}` };
|
|
7800
8014
|
} catch (error2) {
|
|
7801
8015
|
return { state: "not-resumable", reason: `resume dependency unavailable: ${error2 instanceof Error ? error2.message : String(error2)}` };
|
|
7802
8016
|
}
|
|
@@ -7857,52 +8071,52 @@ function formatRetainedChildren(children) {
|
|
|
7857
8071
|
}
|
|
7858
8072
|
|
|
7859
8073
|
// src/runs/shared/external-job-bridge.ts
|
|
7860
|
-
import { randomUUID as
|
|
7861
|
-
import * as
|
|
8074
|
+
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
8075
|
+
import * as fs17 from "node:fs";
|
|
7862
8076
|
import * as os2 from "node:os";
|
|
7863
|
-
import * as
|
|
8077
|
+
import * as path16 from "node:path";
|
|
7864
8078
|
var EXTERNAL_JOB_BRIDGE_REQUEST_DIR = "external-job-requests";
|
|
7865
8079
|
var EXTERNAL_JOB_BRIDGE_RESPONSE_DIR = "external-job-responses";
|
|
7866
8080
|
var CHILD_BRIDGE_SWEEP_INTERVAL_MS = 1e3;
|
|
7867
8081
|
var MAX_REQUESTS_PER_SWEEP = 100;
|
|
7868
8082
|
var inFlight = /* @__PURE__ */ new Set();
|
|
7869
8083
|
function requestDir(asyncDir) {
|
|
7870
|
-
return
|
|
8084
|
+
return path16.join(asyncDir, EXTERNAL_JOB_BRIDGE_REQUEST_DIR);
|
|
7871
8085
|
}
|
|
7872
8086
|
function responseDir(asyncDir) {
|
|
7873
|
-
return
|
|
8087
|
+
return path16.join(asyncDir, EXTERNAL_JOB_BRIDGE_RESPONSE_DIR);
|
|
7874
8088
|
}
|
|
7875
8089
|
function requestPath(asyncDir, id) {
|
|
7876
|
-
return
|
|
8090
|
+
return path16.join(requestDir(asyncDir), `${id}.json`);
|
|
7877
8091
|
}
|
|
7878
8092
|
function isDispatchOperation(operation) {
|
|
7879
8093
|
return operation === "start" || operation === "follow-up";
|
|
7880
8094
|
}
|
|
7881
8095
|
function dispatchClaimDir(asyncDir, id) {
|
|
7882
|
-
return
|
|
8096
|
+
return path16.join(requestDir(asyncDir), `${id}.claim`);
|
|
7883
8097
|
}
|
|
7884
8098
|
function dispatchClaimTempDir(asyncDir, id) {
|
|
7885
|
-
return
|
|
8099
|
+
return path16.join(requestDir(asyncDir), `${id}.claim.tmp-${randomUUID7()}`);
|
|
7886
8100
|
}
|
|
7887
8101
|
function dispatchClaimCompletedPath(claimDir) {
|
|
7888
|
-
return
|
|
8102
|
+
return path16.join(claimDir, "completed.json");
|
|
7889
8103
|
}
|
|
7890
8104
|
function dispatchClaimHandlePath(claimDir) {
|
|
7891
|
-
return
|
|
8105
|
+
return path16.join(claimDir, "handle.json");
|
|
7892
8106
|
}
|
|
7893
8107
|
function completedDispatchClaimExists(asyncDir, id) {
|
|
7894
|
-
return
|
|
8108
|
+
return fs17.existsSync(dispatchClaimCompletedPath(dispatchClaimDir(asyncDir, id)));
|
|
7895
8109
|
}
|
|
7896
8110
|
function responsePath(asyncDir, id) {
|
|
7897
|
-
return
|
|
8111
|
+
return path16.join(responseDir(asyncDir), `${id}.json`);
|
|
7898
8112
|
}
|
|
7899
8113
|
function readJson2(filePath) {
|
|
7900
|
-
return JSON.parse(
|
|
8114
|
+
return JSON.parse(fs17.readFileSync(filePath, "utf-8"));
|
|
7901
8115
|
}
|
|
7902
8116
|
function processStartIdentity(pid) {
|
|
7903
8117
|
if (process.platform === "linux") {
|
|
7904
8118
|
try {
|
|
7905
|
-
const stat =
|
|
8119
|
+
const stat = fs17.readFileSync(`/proc/${pid}/stat`, "utf-8");
|
|
7906
8120
|
const commandEnd = stat.lastIndexOf(")");
|
|
7907
8121
|
if (commandEnd === -1) return void 0;
|
|
7908
8122
|
const fields = stat.slice(commandEnd + 1).trim().split(/\s+/);
|
|
@@ -8022,16 +8236,16 @@ function claimDispatchRequest(asyncDir, filePath, request) {
|
|
|
8022
8236
|
const claimDir = dispatchClaimDir(asyncDir, request.id);
|
|
8023
8237
|
const tempClaimDir = dispatchClaimTempDir(asyncDir, request.id);
|
|
8024
8238
|
try {
|
|
8025
|
-
|
|
8026
|
-
writeAtomicJson(
|
|
8027
|
-
writeAtomicJson(
|
|
8028
|
-
|
|
8239
|
+
fs17.mkdirSync(tempClaimDir);
|
|
8240
|
+
writeAtomicJson(path16.join(tempClaimDir, "owner.json"), owner);
|
|
8241
|
+
writeAtomicJson(path16.join(tempClaimDir, "request.json"), claimed);
|
|
8242
|
+
fs17.renameSync(tempClaimDir, claimDir);
|
|
8029
8243
|
} catch (error2) {
|
|
8030
|
-
|
|
8244
|
+
fs17.rmSync(tempClaimDir, { recursive: true, force: true });
|
|
8031
8245
|
if (isClaimConflictError(error2)) return void 0;
|
|
8032
8246
|
throw error2;
|
|
8033
8247
|
}
|
|
8034
|
-
|
|
8248
|
+
fs17.rmSync(filePath, { force: true });
|
|
8035
8249
|
return { request: claimed, filePath: claimDir };
|
|
8036
8250
|
}
|
|
8037
8251
|
function parseClaimOwner(value) {
|
|
@@ -8051,7 +8265,7 @@ function claimOwnerIsDead(owner) {
|
|
|
8051
8265
|
}
|
|
8052
8266
|
function readClaimOwner(claimDir) {
|
|
8053
8267
|
try {
|
|
8054
|
-
return parseClaimOwner(readJson2(
|
|
8268
|
+
return parseClaimOwner(readJson2(path16.join(claimDir, "owner.json")));
|
|
8055
8269
|
} catch {
|
|
8056
8270
|
return void 0;
|
|
8057
8271
|
}
|
|
@@ -8113,23 +8327,23 @@ function createChildExternalJobBridgeSweeper() {
|
|
|
8113
8327
|
function serviceExternalJobBridgeRequests(asyncDir) {
|
|
8114
8328
|
let files;
|
|
8115
8329
|
try {
|
|
8116
|
-
files =
|
|
8330
|
+
files = fs17.readdirSync(requestDir(asyncDir), { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json") && !completedDispatchClaimExists(asyncDir, entry.name.replace(/\.json$/, "")) || entry.isDirectory() && entry.name.endsWith(".claim") && !fs17.existsSync(dispatchClaimCompletedPath(path16.join(requestDir(asyncDir), entry.name)))).map((entry) => entry.name).slice(0, MAX_REQUESTS_PER_SWEEP);
|
|
8117
8331
|
} catch (error2) {
|
|
8118
8332
|
if (error2.code === "ENOENT") return;
|
|
8119
8333
|
throw error2;
|
|
8120
8334
|
}
|
|
8121
|
-
|
|
8335
|
+
fs17.mkdirSync(responseDir(asyncDir), { recursive: true });
|
|
8122
8336
|
for (const file of files) {
|
|
8123
8337
|
serviceExternalJobBridgeRequestFile(asyncDir, file);
|
|
8124
8338
|
}
|
|
8125
8339
|
}
|
|
8126
8340
|
function serviceExternalJobBridgeRequestFile(asyncDir, file) {
|
|
8127
|
-
|
|
8341
|
+
fs17.mkdirSync(responseDir(asyncDir), { recursive: true });
|
|
8128
8342
|
if (file.endsWith(".claim")) {
|
|
8129
8343
|
serviceExternalJobDispatchClaim(asyncDir, file);
|
|
8130
8344
|
return;
|
|
8131
8345
|
}
|
|
8132
|
-
const filePath =
|
|
8346
|
+
const filePath = path16.join(requestDir(asyncDir), file);
|
|
8133
8347
|
let request;
|
|
8134
8348
|
try {
|
|
8135
8349
|
request = assertRequest(readJson2(filePath), filePath);
|
|
@@ -8145,14 +8359,14 @@ function serviceExternalJobBridgeRequestFile(asyncDir, file) {
|
|
|
8145
8359
|
message: error2 instanceof Error ? error2.message : String(error2),
|
|
8146
8360
|
completedAt: Date.now()
|
|
8147
8361
|
});
|
|
8148
|
-
|
|
8362
|
+
fs17.rmSync(filePath, { force: true });
|
|
8149
8363
|
return;
|
|
8150
8364
|
}
|
|
8151
8365
|
if (isDispatchOperation(request.operation) && completedDispatchClaimExists(asyncDir, request.id)) {
|
|
8152
|
-
|
|
8366
|
+
fs17.rmSync(filePath, { force: true });
|
|
8153
8367
|
return;
|
|
8154
8368
|
}
|
|
8155
|
-
if (inFlight.has(request.id) ||
|
|
8369
|
+
if (inFlight.has(request.id) || fs17.existsSync(responsePath(asyncDir, request.id))) return;
|
|
8156
8370
|
if (isDispatchOperation(request.operation) && request.claimedAt !== void 0) return;
|
|
8157
8371
|
const claimed = isDispatchOperation(request.operation) ? claimDispatchRequest(asyncDir, filePath, request) : { request, filePath };
|
|
8158
8372
|
if (!claimed) return;
|
|
@@ -8163,7 +8377,7 @@ function serviceExternalJobBridgeRequestFile(asyncDir, file) {
|
|
|
8163
8377
|
if (isDispatchOperation(claimedRequest.operation)) {
|
|
8164
8378
|
writeAtomicJson(dispatchClaimCompletedPath(claimed.filePath), { completedAt: Date.now() });
|
|
8165
8379
|
} else {
|
|
8166
|
-
|
|
8380
|
+
fs17.rmSync(claimed.filePath, { recursive: true, force: true });
|
|
8167
8381
|
}
|
|
8168
8382
|
}).catch((error2) => {
|
|
8169
8383
|
writeAtomicJson(responsePath(asyncDir, claimedRequest.id), {
|
|
@@ -8181,21 +8395,21 @@ function serviceExternalJobBridgeRequestFile(asyncDir, file) {
|
|
|
8181
8395
|
});
|
|
8182
8396
|
}
|
|
8183
8397
|
function serviceExternalJobDispatchClaim(asyncDir, file) {
|
|
8184
|
-
const claimDir =
|
|
8185
|
-
if (
|
|
8398
|
+
const claimDir = path16.join(requestDir(asyncDir), file);
|
|
8399
|
+
if (fs17.existsSync(dispatchClaimCompletedPath(claimDir))) return;
|
|
8186
8400
|
const owner = readClaimOwner(claimDir);
|
|
8187
|
-
if (!owner &&
|
|
8188
|
-
|
|
8401
|
+
if (!owner && fs17.existsSync(requestPath(asyncDir, file.replace(/\.claim$/, ""))) && !fs17.existsSync(responsePath(asyncDir, file.replace(/\.claim$/, "")))) {
|
|
8402
|
+
fs17.rmSync(claimDir, { recursive: true, force: true });
|
|
8189
8403
|
return;
|
|
8190
8404
|
}
|
|
8191
8405
|
if (!claimOwnerIsDead(owner)) return;
|
|
8192
8406
|
let request;
|
|
8193
8407
|
try {
|
|
8194
|
-
request = assertRequest(readJson2(
|
|
8408
|
+
request = assertRequest(readJson2(path16.join(claimDir, "request.json")), path16.join(claimDir, "request.json"));
|
|
8195
8409
|
} catch {
|
|
8196
8410
|
return;
|
|
8197
8411
|
}
|
|
8198
|
-
if (!isDispatchOperation(request.operation) ||
|
|
8412
|
+
if (!isDispatchOperation(request.operation) || fs17.existsSync(responsePath(asyncDir, request.id))) return;
|
|
8199
8413
|
const handle = readClaimHandle(request.provider, claimDir);
|
|
8200
8414
|
if (handle) {
|
|
8201
8415
|
writeAtomicJson(responsePath(asyncDir, request.id), {
|
|
@@ -8207,7 +8421,7 @@ function serviceExternalJobDispatchClaim(asyncDir, file) {
|
|
|
8207
8421
|
completedAt: Date.now()
|
|
8208
8422
|
});
|
|
8209
8423
|
writeAtomicJson(dispatchClaimCompletedPath(claimDir), { completedAt: Date.now() });
|
|
8210
|
-
|
|
8424
|
+
fs17.rmSync(requestPath(asyncDir, request.id), { force: true });
|
|
8211
8425
|
return;
|
|
8212
8426
|
}
|
|
8213
8427
|
writeAtomicJson(responsePath(asyncDir, request.id), {
|
|
@@ -8219,8 +8433,8 @@ function serviceExternalJobDispatchClaim(asyncDir, file) {
|
|
|
8219
8433
|
message: `External-job ${request.operation} for provider '${request.provider}' was claimed by a host process that is no longer alive before a provider job id was committed. Refusing to redispatch the prompt automatically.`,
|
|
8220
8434
|
completedAt: Date.now()
|
|
8221
8435
|
});
|
|
8222
|
-
|
|
8223
|
-
|
|
8436
|
+
fs17.rmSync(claimDir, { recursive: true, force: true });
|
|
8437
|
+
fs17.rmSync(requestPath(asyncDir, request.id), { force: true });
|
|
8224
8438
|
}
|
|
8225
8439
|
|
|
8226
8440
|
// src/shared/provider-diagnostics.ts
|
|
@@ -8349,6 +8563,14 @@ export {
|
|
|
8349
8563
|
COMPLETION_ACTION,
|
|
8350
8564
|
STATUS_ACTION,
|
|
8351
8565
|
createParentWake,
|
|
8566
|
+
writeWorkflowLaunchRecord,
|
|
8567
|
+
resumedRunStarted,
|
|
8568
|
+
workflowResumeClaimState,
|
|
8569
|
+
claimWorkflowResume,
|
|
8570
|
+
releaseWorkflowResumeClaim,
|
|
8571
|
+
recordWorkflowResumeRefusal,
|
|
8572
|
+
refusalSentence,
|
|
8573
|
+
hasWorkflowResumeRefusal,
|
|
8352
8574
|
incrementalChildCompletionTriggersTurn,
|
|
8353
8575
|
formatIncrementalChildCompletion,
|
|
8354
8576
|
parseSubagentNotifyContent,
|
|
@@ -8370,19 +8592,21 @@ export {
|
|
|
8370
8592
|
childSessionFactory,
|
|
8371
8593
|
childSessionFactoryModule,
|
|
8372
8594
|
disposeChildSessions,
|
|
8373
|
-
|
|
8595
|
+
WORKFLOW_RUNTIME_REPLACED_STOP_NOTICE,
|
|
8374
8596
|
runtimeReplacedAbortReason,
|
|
8375
8597
|
workflowStopCause,
|
|
8376
8598
|
workflowScriptDigest,
|
|
8377
8599
|
workflowChildFingerprint,
|
|
8378
8600
|
appendWorkflowChildJournal,
|
|
8601
|
+
claimedWorkflowRunRefusal,
|
|
8379
8602
|
findWorkflowReuseSource,
|
|
8380
8603
|
readNamedWorkflowReuseSource,
|
|
8381
8604
|
matchWorkflowReuse,
|
|
8605
|
+
workflowResumeRefusal,
|
|
8606
|
+
manualWorkflowResumeRefusalText,
|
|
8607
|
+
workflowResumeTargetId,
|
|
8608
|
+
prepareWorkflowResume,
|
|
8382
8609
|
flattenSubagentToolOptions,
|
|
8383
|
-
MAX_REMEMBERED_FOREGROUND_RUNS,
|
|
8384
|
-
persistForegroundRunHistory,
|
|
8385
|
-
restoreForegroundRunHistory,
|
|
8386
8610
|
listRetainedChildren,
|
|
8387
8611
|
formatRetainedChildren
|
|
8388
8612
|
};
|