pi-subagents 0.81.0 → 0.82.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/bundle/agents.js +3 -3
- package/bundle/child-tool-plan.js +2 -2
- package/bundle/{chunk-LSRUTHT7.js → chunk-43SCHIMD.js} +1 -1
- package/bundle/{chunk-G5DKFLLI.js → chunk-4SDU2BZX.js} +15 -10
- package/bundle/{chunk-V2JWDB5U.js → chunk-52UBALJL.js} +2 -2
- package/bundle/{chunk-3DC5KBVP.js → chunk-5TYMSZX3.js} +1 -1
- package/bundle/{chunk-CEKS3TFO.js → chunk-7IVSUMQQ.js} +6 -6
- package/bundle/{chunk-KSWQVGDW.js → chunk-7PCKZHR5.js} +225 -18
- package/bundle/{chunk-563BHAZ5.js → chunk-7YMXOYEU.js} +33 -25
- package/bundle/{chunk-XMFLCKMQ.js → chunk-B5EKXDHE.js} +336 -130
- package/bundle/{chunk-ZBC44JJ3.js → chunk-CIZQXJ5Q.js} +22 -9
- package/bundle/{chunk-L5ZHE4L4.js → chunk-GC4DSPMH.js} +4 -4
- package/bundle/{chunk-ARRKBB2B.js → chunk-GJECHSAR.js} +2 -2
- package/bundle/{chunk-2ZSNH26H.js → chunk-M2CTFYT3.js} +7 -7
- package/bundle/{chunk-TUWLXTAM.js → chunk-MHZ4G23A.js} +681 -320
- package/bundle/{chunk-WSBNENFS.js → chunk-NGGAFDL6.js} +14 -4
- package/bundle/{chunk-VJHOELQP.js → chunk-NRQ75MGH.js} +160 -78
- package/bundle/{chunk-BJN5LUQY.js → chunk-OYF6AFDY.js} +1 -1
- package/bundle/{chunk-EYNJ6VL6.js → chunk-PLCCBLPY.js} +2 -2
- package/bundle/{chunk-74RT5YQE.js → chunk-RMQFMMAT.js} +123 -33
- package/bundle/{chunk-V6MR5TOQ.js → chunk-SIJ4S3EN.js} +25 -19
- package/bundle/{chunk-NNLASTQE.js → chunk-VH5OAWVG.js} +19 -5
- package/bundle/{chunk-BPS2AGBZ.js → chunk-W2SZ22EM.js} +93 -52
- package/bundle/{chunk-V3BIUPLU.js → chunk-WVD4UEL4.js} +256 -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-ZLINIYTP.js} +497 -272
- package/bundle/{chunk-2XYWN7QJ.js → chunk-ZQGSDHL6.js} +2 -2
- 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 +31 -0
- 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/pi-subagents/references/execution-controls.md +5 -4
- 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/schemas.js +3 -3
- 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 +2 -0
- package/src/runs/background/async-status.js +21 -2
- 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 +1 -0
- package/src/runs/background/terminal-run-index.js +4 -2
- 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 +24 -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
|
@@ -11,27 +11,35 @@ import {
|
|
|
11
11
|
startWatchdogDiffBaselineCapture,
|
|
12
12
|
writeUserWatchdogEnabled,
|
|
13
13
|
writeWatchdogModelSettings
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-W2SZ22EM.js";
|
|
15
15
|
import {
|
|
16
16
|
resolveGitRepositoryIdentity
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-7PCKZHR5.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-VH5OAWVG.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-NRQ75MGH.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) {
|
|
@@ -5071,6 +5078,197 @@ function createParentWake(pi, now = Date.now) {
|
|
|
5071
5078
|
};
|
|
5072
5079
|
}
|
|
5073
5080
|
|
|
5081
|
+
// src/workflows/workflow-launch-record.ts
|
|
5082
|
+
import * as fs9 from "node:fs";
|
|
5083
|
+
import * as path8 from "node:path";
|
|
5084
|
+
var WORKFLOW_LAUNCH_RECORD_FILE = "launch.json";
|
|
5085
|
+
function launchRecordPath(asyncDir) {
|
|
5086
|
+
return path8.join(asyncDir, WORKFLOW_LAUNCH_RECORD_FILE);
|
|
5087
|
+
}
|
|
5088
|
+
function writeWorkflowLaunchRecord(asyncDir, record) {
|
|
5089
|
+
writeAtomicJson(launchRecordPath(asyncDir), record);
|
|
5090
|
+
}
|
|
5091
|
+
function isPlainObject(value) {
|
|
5092
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
5093
|
+
}
|
|
5094
|
+
function invalid(error2) {
|
|
5095
|
+
return { ok: false, missing: false, error: error2 };
|
|
5096
|
+
}
|
|
5097
|
+
function readWorkflowLaunchRecord(asyncDir) {
|
|
5098
|
+
let text2;
|
|
5099
|
+
try {
|
|
5100
|
+
text2 = fs9.readFileSync(launchRecordPath(asyncDir), "utf-8");
|
|
5101
|
+
} catch (error2) {
|
|
5102
|
+
if (error2.code === "ENOENT") return { ok: false, missing: true, error: "launch.json is missing" };
|
|
5103
|
+
return invalid(`could not be read: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
5104
|
+
}
|
|
5105
|
+
let value;
|
|
5106
|
+
try {
|
|
5107
|
+
value = JSON.parse(text2);
|
|
5108
|
+
} catch (error2) {
|
|
5109
|
+
return invalid(`not valid JSON: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
5110
|
+
}
|
|
5111
|
+
if (!isPlainObject(value)) return invalid("not a JSON object");
|
|
5112
|
+
if (value.version !== 1) return invalid("version must be 1");
|
|
5113
|
+
if (typeof value.scriptDigest !== "string" || !value.scriptDigest) return invalid("scriptDigest must be a non-empty string");
|
|
5114
|
+
if (value.source === "structured") return { ok: true, record: { version: 1, source: "structured", scriptDigest: value.scriptDigest } };
|
|
5115
|
+
if (value.source !== "script" && value.source !== "resource") return invalid('source must be "script", "resource", or "structured"');
|
|
5116
|
+
if (!isPlainObject(value.params)) return invalid("params must be an object");
|
|
5117
|
+
return { ok: true, record: { version: 1, source: value.source, params: value.params, scriptDigest: value.scriptDigest } };
|
|
5118
|
+
}
|
|
5119
|
+
|
|
5120
|
+
// src/workflows/workflow-resume-claim.ts
|
|
5121
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
5122
|
+
import * as fs10 from "node:fs";
|
|
5123
|
+
import * as path9 from "node:path";
|
|
5124
|
+
var CLAIM_FILE_PATTERN = /^resumed-by\.([1-9][0-9]*)\.json$/;
|
|
5125
|
+
var RELEASED_CLAIM_FILE_PATTERN = /^resumed-by\.([1-9][0-9]*)\.json\.released-/;
|
|
5126
|
+
var REFUSAL_FILE = "resume-refused.json";
|
|
5127
|
+
var MAX_CLAIM_ROUNDS = 8;
|
|
5128
|
+
var heldClaimsKey = /* @__PURE__ */ Symbol.for("pi-subagents.workflow-resume-claims.v1");
|
|
5129
|
+
var heldClaimsGlobal = globalThis;
|
|
5130
|
+
var heldClaims = heldClaimsGlobal[heldClaimsKey] ?? (heldClaimsGlobal[heldClaimsKey] = /* @__PURE__ */ new Set());
|
|
5131
|
+
function claimFilePath(asyncDir, generation) {
|
|
5132
|
+
return path9.join(asyncDir, `resumed-by.${generation}.json`);
|
|
5133
|
+
}
|
|
5134
|
+
function isClaim(value) {
|
|
5135
|
+
if (!isRecordedPid(value) || Array.isArray(value)) return false;
|
|
5136
|
+
const claim = value;
|
|
5137
|
+
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");
|
|
5138
|
+
}
|
|
5139
|
+
function readTopClaim(asyncDir, retries = 3) {
|
|
5140
|
+
let generation = 0;
|
|
5141
|
+
let releasedGeneration = 0;
|
|
5142
|
+
try {
|
|
5143
|
+
for (const name of fs10.readdirSync(asyncDir)) {
|
|
5144
|
+
const live = CLAIM_FILE_PATTERN.exec(name);
|
|
5145
|
+
if (live) generation = Math.max(generation, Number(live[1]));
|
|
5146
|
+
const released = RELEASED_CLAIM_FILE_PATTERN.exec(name);
|
|
5147
|
+
if (released) releasedGeneration = Math.max(releasedGeneration, Number(released[1]));
|
|
5148
|
+
}
|
|
5149
|
+
} catch (error2) {
|
|
5150
|
+
return error2.code === "ENOENT" ? { kind: "open", generation: 0 } : { kind: "unreadable" };
|
|
5151
|
+
}
|
|
5152
|
+
if (releasedGeneration > generation || generation === 0) return { kind: "open", generation: Math.max(releasedGeneration, generation) };
|
|
5153
|
+
const filePath = claimFilePath(asyncDir, generation);
|
|
5154
|
+
let text2;
|
|
5155
|
+
try {
|
|
5156
|
+
text2 = fs10.readFileSync(filePath, "utf-8");
|
|
5157
|
+
} catch (error2) {
|
|
5158
|
+
if (error2.code === "ENOENT" && retries > 0) return readTopClaim(asyncDir, retries - 1);
|
|
5159
|
+
return { kind: "unreadable" };
|
|
5160
|
+
}
|
|
5161
|
+
try {
|
|
5162
|
+
const value = JSON.parse(text2);
|
|
5163
|
+
return isClaim(value) ? { kind: "claim", generation, filePath, claim: value } : { kind: "open", generation };
|
|
5164
|
+
} catch {
|
|
5165
|
+
return { kind: "open", generation };
|
|
5166
|
+
}
|
|
5167
|
+
}
|
|
5168
|
+
function readWorkflowResumeClaim(asyncDir) {
|
|
5169
|
+
const top = readTopClaim(asyncDir);
|
|
5170
|
+
return top.kind === "claim" ? top.claim : void 0;
|
|
5171
|
+
}
|
|
5172
|
+
function resumedRunStarted(sourceAsyncDir, newRunId) {
|
|
5173
|
+
return path9.basename(newRunId) === newRunId && fs10.existsSync(path9.join(path9.dirname(sourceAsyncDir), newRunId, "status.json"));
|
|
5174
|
+
}
|
|
5175
|
+
function claimantLiveness(filePath, claim, liveness) {
|
|
5176
|
+
if (claim.pid === process.pid && claim.bootId === currentBootId() && claim.pidNamespaceScope === currentPidNamespaceScope()) return heldClaims.has(filePath) ? "alive" : "dead";
|
|
5177
|
+
return liveness ? liveness(claim) : checkRecordedPidLiveness(claim);
|
|
5178
|
+
}
|
|
5179
|
+
function workflowResumeClaimState(asyncDir, options = {}) {
|
|
5180
|
+
const top = readTopClaim(asyncDir);
|
|
5181
|
+
if (top.kind === "unreadable") return { kind: "unreadable" };
|
|
5182
|
+
if (top.kind !== "claim") return { kind: "none" };
|
|
5183
|
+
if (resumedRunStarted(asyncDir, top.claim.newRunId)) return { kind: "resumed", newRunId: top.claim.newRunId };
|
|
5184
|
+
return claimantLiveness(top.filePath, top.claim, options.liveness) === "dead" ? { kind: "none" } : { kind: "busy", pid: top.claim.pid, newRunId: top.claim.newRunId };
|
|
5185
|
+
}
|
|
5186
|
+
function createClaimFile(filePath, claim) {
|
|
5187
|
+
const tempPath = `${filePath}.${process.pid}.${randomUUID3()}.tmp`;
|
|
5188
|
+
try {
|
|
5189
|
+
fs10.writeFileSync(tempPath, JSON.stringify(claim, null, 2), "utf-8");
|
|
5190
|
+
fs10.linkSync(tempPath, filePath);
|
|
5191
|
+
return true;
|
|
5192
|
+
} catch (error2) {
|
|
5193
|
+
if (error2.code === "EEXIST") return false;
|
|
5194
|
+
throw error2;
|
|
5195
|
+
} finally {
|
|
5196
|
+
fs10.rmSync(tempPath, { force: true });
|
|
5197
|
+
}
|
|
5198
|
+
}
|
|
5199
|
+
function claimWorkflowResume(asyncDir, input) {
|
|
5200
|
+
const claim = {
|
|
5201
|
+
version: 1,
|
|
5202
|
+
...currentRecordedPid(),
|
|
5203
|
+
claimedAt: input.now ?? Date.now(),
|
|
5204
|
+
newRunId: input.newRunId,
|
|
5205
|
+
trigger: input.trigger
|
|
5206
|
+
};
|
|
5207
|
+
for (let round = 0; round < MAX_CLAIM_ROUNDS; round++) {
|
|
5208
|
+
const top2 = readTopClaim(asyncDir);
|
|
5209
|
+
if (top2.kind === "unreadable") return { kind: "unreadable" };
|
|
5210
|
+
if (top2.kind === "claim") {
|
|
5211
|
+
if (top2.claim.newRunId === input.newRunId && top2.claim.pid === process.pid) {
|
|
5212
|
+
heldClaims.add(top2.filePath);
|
|
5213
|
+
return { kind: "owner" };
|
|
5214
|
+
}
|
|
5215
|
+
if (resumedRunStarted(asyncDir, top2.claim.newRunId)) return { kind: "resumed", newRunId: top2.claim.newRunId };
|
|
5216
|
+
if (claimantLiveness(top2.filePath, top2.claim, input.liveness) !== "dead") return { kind: "busy", pid: top2.claim.pid };
|
|
5217
|
+
}
|
|
5218
|
+
const filePath = claimFilePath(asyncDir, top2.generation + 1);
|
|
5219
|
+
if (createClaimFile(filePath, claim)) {
|
|
5220
|
+
heldClaims.add(filePath);
|
|
5221
|
+
return { kind: "owner" };
|
|
5222
|
+
}
|
|
5223
|
+
}
|
|
5224
|
+
const top = readTopClaim(asyncDir);
|
|
5225
|
+
return top.kind === "claim" ? { kind: "busy", pid: top.claim.pid } : { kind: "unreadable" };
|
|
5226
|
+
}
|
|
5227
|
+
function releaseWorkflowResumeClaim(asyncDir, newRunId) {
|
|
5228
|
+
const top = readTopClaim(asyncDir);
|
|
5229
|
+
if (top.kind !== "claim" || top.claim.newRunId !== newRunId || top.claim.pid !== process.pid) return;
|
|
5230
|
+
try {
|
|
5231
|
+
fs10.renameSync(top.filePath, `${top.filePath}.released-${randomUUID3()}`);
|
|
5232
|
+
} catch (error2) {
|
|
5233
|
+
if (error2.code !== "ENOENT") throw error2;
|
|
5234
|
+
}
|
|
5235
|
+
heldClaims.delete(top.filePath);
|
|
5236
|
+
}
|
|
5237
|
+
function recordWorkflowResumeRefusal(asyncDir, refusal, now = Date.now()) {
|
|
5238
|
+
writeAtomicJson(path9.join(asyncDir, REFUSAL_FILE), { version: 1, ...refusal, refusedAt: now });
|
|
5239
|
+
}
|
|
5240
|
+
function readWorkflowResumeRefusal(asyncDir) {
|
|
5241
|
+
try {
|
|
5242
|
+
const value = JSON.parse(fs10.readFileSync(path9.join(asyncDir, REFUSAL_FILE), "utf-8"));
|
|
5243
|
+
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;
|
|
5244
|
+
} catch {
|
|
5245
|
+
return void 0;
|
|
5246
|
+
}
|
|
5247
|
+
}
|
|
5248
|
+
function resumeByHandText(runId) {
|
|
5249
|
+
return `Resume by hand: subagent({ action: "resume", id: "${runId}" })`;
|
|
5250
|
+
}
|
|
5251
|
+
function relaunchByHandText(runId) {
|
|
5252
|
+
return `Relaunch it by hand with options: { reuseFrom: "${runId}" }.`;
|
|
5253
|
+
}
|
|
5254
|
+
function refusalSentence(reason, byHand) {
|
|
5255
|
+
return `${reason.replace(/\.$/, "")}.${byHand ? ` ${byHand}` : ""}`;
|
|
5256
|
+
}
|
|
5257
|
+
function hasWorkflowResumeRefusal(asyncDir) {
|
|
5258
|
+
return fs10.existsSync(path9.join(asyncDir, REFUSAL_FILE));
|
|
5259
|
+
}
|
|
5260
|
+
var RESUMED_AS = { auto: "Auto-resumed as", manual: "Resumed as", reuse: "Relaunched as" };
|
|
5261
|
+
function describeWorkflowResume(asyncDir, runId) {
|
|
5262
|
+
const claim = readWorkflowResumeClaim(asyncDir);
|
|
5263
|
+
const refusal = readWorkflowResumeRefusal(asyncDir);
|
|
5264
|
+
if (refusal && (!claim || refusal.newRunId === claim.newRunId)) {
|
|
5265
|
+
return `Not auto-resumed: ${refusalSentence(refusal.reason, refusal.byHand)}`;
|
|
5266
|
+
}
|
|
5267
|
+
if (claim) return `${RESUMED_AS[claim.trigger]} ${claim.newRunId}.`;
|
|
5268
|
+
const launch = readWorkflowLaunchRecord(asyncDir);
|
|
5269
|
+
return launch.ok && launch.record.source !== "structured" ? resumeByHandText(runId) : relaunchByHandText(runId);
|
|
5270
|
+
}
|
|
5271
|
+
|
|
5074
5272
|
// src/runs/background/notify.ts
|
|
5075
5273
|
import { statSync as statSync2 } from "node:fs";
|
|
5076
5274
|
import { debuglog } from "node:util";
|
|
@@ -5250,15 +5448,15 @@ function nonemptyPath(value) {
|
|
|
5250
5448
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
5251
5449
|
}
|
|
5252
5450
|
function retainedChildPath(value, producerConfirmed = false) {
|
|
5253
|
-
const
|
|
5254
|
-
if (!
|
|
5255
|
-
if (producerConfirmed) return { status: "verified", path:
|
|
5451
|
+
const path17 = nonemptyPath(value);
|
|
5452
|
+
if (!path17) return { status: "unavailable" };
|
|
5453
|
+
if (producerConfirmed) return { status: "verified", path: path17 };
|
|
5256
5454
|
try {
|
|
5257
|
-
return statSync2(
|
|
5455
|
+
return statSync2(path17).isFile() ? { status: "verified", path: path17 } : { status: "unavailable" };
|
|
5258
5456
|
} catch (error2) {
|
|
5259
5457
|
const code = typeof error2 === "object" && error2 !== null && "code" in error2 && typeof error2.code === "string" ? error2.code : void 0;
|
|
5260
5458
|
if (code === "ENOENT" || code === "ENOTDIR") return { status: "unavailable" };
|
|
5261
|
-
return { status: "error", error: { path:
|
|
5459
|
+
return { status: "error", error: { path: path17, ...code ? { code } : {}, message: error2 instanceof Error ? error2.message : String(error2) } };
|
|
5262
5460
|
}
|
|
5263
5461
|
}
|
|
5264
5462
|
function formatRetainedPathError(label, error2) {
|
|
@@ -5362,10 +5560,12 @@ function formatCorrelationLines(details) {
|
|
|
5362
5560
|
return [
|
|
5363
5561
|
details.asyncDir ? `Retention-managed async directory: ${boundedSafeText(details.asyncDir)}` : void 0,
|
|
5364
5562
|
details.workflowRunId ? `Workflow run: ${details.workflowRunId}` : void 0,
|
|
5563
|
+
details.resumeNotice,
|
|
5365
5564
|
details.childRuns?.length ? `Child runs: ${details.childRuns.map(formatChildRun).join(", ")}` : void 0,
|
|
5366
5565
|
details.reconciledFromDetachedChild ? `Reconciled detached child: ${details.reconciledFromDetachedChild}` : void 0
|
|
5367
5566
|
].filter((line) => line !== void 0);
|
|
5368
5567
|
}
|
|
5568
|
+
var RESUME_NOTICE_PREFIXES = ["Auto-resumed as ", "Resumed as ", "Relaunched as ", "Not auto-resumed: ", "Resume by hand: ", "Relaunch it by hand with "];
|
|
5369
5569
|
var WATCHDOG_BLOCKERS_HEADING = "Watchdog blockers:";
|
|
5370
5570
|
function formatWatchdogBlockerLines(details) {
|
|
5371
5571
|
if (!details.watchdogBlockers?.length) return [];
|
|
@@ -5448,6 +5648,8 @@ function parseSubagentNotifyContent(content) {
|
|
|
5448
5648
|
const workflowRunIndex = body.findIndex((line) => line.startsWith("Workflow run: "));
|
|
5449
5649
|
const childRunsIndex = body.findIndex((line) => line.startsWith("Child runs: "));
|
|
5450
5650
|
const reconciledIndex = body.findIndex((line) => line.startsWith("Reconciled detached child: "));
|
|
5651
|
+
const resumeNoticeLine = workflowRunIndex >= 0 ? body[workflowRunIndex + 1] : void 0;
|
|
5652
|
+
const resumeNotice = resumeNoticeLine && RESUME_NOTICE_PREFIXES.some((prefix) => resumeNoticeLine.startsWith(prefix)) ? resumeNoticeLine : void 0;
|
|
5451
5653
|
const watchdogIndex = body.findIndex((line) => line === WATCHDOG_BLOCKERS_HEADING);
|
|
5452
5654
|
const watchdogBlockers = watchdogIndex >= 0 ? parseWatchdogBlockerLines(body.slice(watchdogIndex + 1)) : [];
|
|
5453
5655
|
const metadataIndexes = [sessionIndex, handoffIndex, workflowRunIndex, childRunsIndex, reconciledIndex, watchdogIndex].filter((index) => index >= 0);
|
|
@@ -5481,6 +5683,7 @@ function parseSubagentNotifyContent(content) {
|
|
|
5481
5683
|
resultPreview,
|
|
5482
5684
|
...handoffPath ? { handoffPath } : {},
|
|
5483
5685
|
...workflowRunId ? { workflowRunId } : {},
|
|
5686
|
+
...resumeNotice ? { resumeNotice } : {},
|
|
5484
5687
|
...childRuns?.length ? { childRuns } : {},
|
|
5485
5688
|
...reconciledFromDetachedChild ? { reconciledFromDetachedChild } : {},
|
|
5486
5689
|
...watchdogBlockers.length ? { watchdogBlockers } : {},
|
|
@@ -5706,6 +5909,8 @@ ${outputPreview}` : outputPreview;
|
|
|
5706
5909
|
}) : void 0;
|
|
5707
5910
|
const reconciledFromDetachedChild = typeof result5.reconciledFromDetachedChild === "string" ? result5.reconciledFromDetachedChild : void 0;
|
|
5708
5911
|
const asyncDir = nonemptyPath(result5.asyncDir);
|
|
5912
|
+
const stopCause = result5.workflow?.stopCause;
|
|
5913
|
+
const resumeNotice = workflowRunId && asyncDir && isReusableWorkflowStopCause(stopCause) ? describeWorkflowResume(asyncDir, workflowRunId) : void 0;
|
|
5709
5914
|
const watchdogBlockers = [];
|
|
5710
5915
|
const watchdogConcerns = [];
|
|
5711
5916
|
const collectWatchdogFindings = (owner, progress) => {
|
|
@@ -5738,6 +5943,7 @@ ${watchdogConcerns.map((warning) => `- ${warning}`).join("\n")}` : resultPreview
|
|
|
5738
5943
|
...handoffPath ? { handoffPath } : {},
|
|
5739
5944
|
...rawRunId ? { runId: rawRunId } : {},
|
|
5740
5945
|
...workflowRunId ? { workflowRunId } : {},
|
|
5946
|
+
...resumeNotice ? { resumeNotice } : {},
|
|
5741
5947
|
...childRuns.length ? { childRuns } : {},
|
|
5742
5948
|
...childOutputs?.length ? { childOutputs } : {},
|
|
5743
5949
|
...reconciledFromDetachedChild ? { reconciledFromDetachedChild } : {},
|
|
@@ -6391,9 +6597,9 @@ function registerPiWebSessionLiveness(registration) {
|
|
|
6391
6597
|
}
|
|
6392
6598
|
|
|
6393
6599
|
// src/runs/background/wait-subscriptions.ts
|
|
6394
|
-
import * as
|
|
6395
|
-
import * as
|
|
6396
|
-
import { randomUUID as
|
|
6600
|
+
import * as fs11 from "node:fs";
|
|
6601
|
+
import * as path10 from "node:path";
|
|
6602
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
6397
6603
|
var SUBSCRIPTION_VERSION = 1;
|
|
6398
6604
|
var STATUS_OUTCOMES = /* @__PURE__ */ new Set(["timed out", "could not be reconciled", "needs attention", "reconciliation failed"]);
|
|
6399
6605
|
var RECONCILE_INTERVAL_MS = 1e3;
|
|
@@ -6412,7 +6618,7 @@ function needsAttention(run) {
|
|
|
6412
6618
|
return run.activityState === "needs_attention" || run.steps.some((step) => step.activityState === "needs_attention");
|
|
6413
6619
|
}
|
|
6414
6620
|
function subscriptionFile(dir, token) {
|
|
6415
|
-
return
|
|
6621
|
+
return path10.join(dir, `${token}.json`);
|
|
6416
6622
|
}
|
|
6417
6623
|
function formatWaitSubscriptions(state, now = Date.now()) {
|
|
6418
6624
|
const subscriptions = [...state.waitSubscriptions?.values() ?? []].sort((left, right) => left.createdAt - right.createdAt);
|
|
@@ -6426,7 +6632,7 @@ function formatWaitSubscriptions(state, now = Date.now()) {
|
|
|
6426
6632
|
function createWaitSubscriptionManager(pi, state, options = {}) {
|
|
6427
6633
|
const asyncDirRoot = options.asyncDirRoot ?? DIRS.async;
|
|
6428
6634
|
const resultsDir = options.resultsDir ?? DIRS.results;
|
|
6429
|
-
const subscriptionsDir = options.subscriptionsDir ??
|
|
6635
|
+
const subscriptionsDir = options.subscriptionsDir ?? path10.join(path10.dirname(asyncDirRoot), "wait-subscriptions");
|
|
6430
6636
|
const now = options.now ?? Date.now;
|
|
6431
6637
|
const subscriptions = state.waitSubscriptions ?? /* @__PURE__ */ new Map();
|
|
6432
6638
|
state.waitSubscriptions = subscriptions;
|
|
@@ -6440,16 +6646,16 @@ function createWaitSubscriptionManager(pi, state, options = {}) {
|
|
|
6440
6646
|
lastForeignSweepAt = sweptAt;
|
|
6441
6647
|
let files;
|
|
6442
6648
|
try {
|
|
6443
|
-
files =
|
|
6649
|
+
files = fs11.readdirSync(subscriptionsDir).filter((file) => file.endsWith(".json"));
|
|
6444
6650
|
} catch (error2) {
|
|
6445
6651
|
if (!isNotFound(error2)) console.error(`Failed to scan wait subscriptions in '${subscriptionsDir}':`, error2);
|
|
6446
6652
|
return;
|
|
6447
6653
|
}
|
|
6448
6654
|
for (const file of files) {
|
|
6449
|
-
const filePath =
|
|
6655
|
+
const filePath = path10.join(subscriptionsDir, file);
|
|
6450
6656
|
let record;
|
|
6451
6657
|
try {
|
|
6452
|
-
record = parseRecord(JSON.parse(
|
|
6658
|
+
record = parseRecord(JSON.parse(fs11.readFileSync(filePath, "utf-8")));
|
|
6453
6659
|
} catch (error2) {
|
|
6454
6660
|
console.error(`Ignoring invalid wait subscription '${filePath}':`, error2);
|
|
6455
6661
|
continue;
|
|
@@ -6458,7 +6664,7 @@ function createWaitSubscriptionManager(pi, state, options = {}) {
|
|
|
6458
6664
|
if (record.sessionId === state.currentSessionId) continue;
|
|
6459
6665
|
if (sweptAt < record.expiresAt + FOREIGN_SWEEP_GRACE_MS) continue;
|
|
6460
6666
|
try {
|
|
6461
|
-
|
|
6667
|
+
fs11.unlinkSync(filePath);
|
|
6462
6668
|
} catch (error2) {
|
|
6463
6669
|
if (!isNotFound(error2)) console.error(`Failed to remove expired wait subscription '${filePath}':`, error2);
|
|
6464
6670
|
}
|
|
@@ -6466,7 +6672,7 @@ function createWaitSubscriptionManager(pi, state, options = {}) {
|
|
|
6466
6672
|
};
|
|
6467
6673
|
const remove = (record) => {
|
|
6468
6674
|
try {
|
|
6469
|
-
|
|
6675
|
+
fs11.unlinkSync(subscriptionFile(subscriptionsDir, record.token));
|
|
6470
6676
|
} catch (error2) {
|
|
6471
6677
|
if (!isNotFound(error2)) throw error2;
|
|
6472
6678
|
}
|
|
@@ -6587,7 +6793,7 @@ Parent action: ${STATUS_OUTCOMES.has(outcome) ? STATUS_ACTION : COMPLETION_ACTIO
|
|
|
6587
6793
|
const createdAt = now();
|
|
6588
6794
|
const record = {
|
|
6589
6795
|
version: SUBSCRIPTION_VERSION,
|
|
6590
|
-
token:
|
|
6796
|
+
token: randomUUID4(),
|
|
6591
6797
|
sessionId,
|
|
6592
6798
|
targetKind: input.targetKind,
|
|
6593
6799
|
runId: input.runId,
|
|
@@ -6595,7 +6801,7 @@ Parent action: ${STATUS_OUTCOMES.has(outcome) ? STATUS_ACTION : COMPLETION_ACTIO
|
|
|
6595
6801
|
createdAt,
|
|
6596
6802
|
expiresAt: createdAt + input.timeoutMs
|
|
6597
6803
|
};
|
|
6598
|
-
|
|
6804
|
+
fs11.mkdirSync(subscriptionsDir, { recursive: true });
|
|
6599
6805
|
writeAtomicJson(subscriptionFile(subscriptionsDir, record.token), record);
|
|
6600
6806
|
subscriptions.set(record.token, record);
|
|
6601
6807
|
return record;
|
|
@@ -6607,21 +6813,21 @@ Parent action: ${STATUS_OUTCOMES.has(outcome) ? STATUS_ACTION : COMPLETION_ACTIO
|
|
|
6607
6813
|
if (!state.currentSessionId) return;
|
|
6608
6814
|
let files;
|
|
6609
6815
|
try {
|
|
6610
|
-
files =
|
|
6816
|
+
files = fs11.readdirSync(subscriptionsDir).filter((file) => file.endsWith(".json"));
|
|
6611
6817
|
} catch (error2) {
|
|
6612
6818
|
if (isNotFound(error2)) return;
|
|
6613
6819
|
throw error2;
|
|
6614
6820
|
}
|
|
6615
6821
|
for (const file of files) {
|
|
6616
6822
|
try {
|
|
6617
|
-
const record = parseRecord(JSON.parse(
|
|
6823
|
+
const record = parseRecord(JSON.parse(fs11.readFileSync(path10.join(subscriptionsDir, file), "utf-8")));
|
|
6618
6824
|
if (!record || file !== `${record.token}.json`) continue;
|
|
6619
6825
|
if (record.sessionId === state.currentSessionId) {
|
|
6620
6826
|
subscriptions.set(record.token, record);
|
|
6621
6827
|
if (record.targetKind === "foreground" && !state.foregroundRuns?.has(record.runId)) unresolvedRestoredForegroundTokens.add(record.token);
|
|
6622
6828
|
}
|
|
6623
6829
|
} catch (error2) {
|
|
6624
|
-
console.error(`Ignoring invalid wait subscription '${
|
|
6830
|
+
console.error(`Ignoring invalid wait subscription '${path10.join(subscriptionsDir, file)}':`, error2);
|
|
6625
6831
|
}
|
|
6626
6832
|
}
|
|
6627
6833
|
reconcile();
|
|
@@ -6644,25 +6850,25 @@ Parent action: ${STATUS_OUTCOMES.has(outcome) ? STATUS_ACTION : COMPLETION_ACTIO
|
|
|
6644
6850
|
}
|
|
6645
6851
|
|
|
6646
6852
|
// src/runs/shared/child-session.ts
|
|
6647
|
-
import * as
|
|
6648
|
-
import * as
|
|
6853
|
+
import * as fs14 from "node:fs";
|
|
6854
|
+
import * as path13 from "node:path";
|
|
6649
6855
|
import { pathToFileURL } from "node:url";
|
|
6650
6856
|
|
|
6651
6857
|
// src/runs/shared/child-commands.ts
|
|
6652
|
-
import { randomUUID as
|
|
6653
|
-
import * as
|
|
6654
|
-
import * as
|
|
6858
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
6859
|
+
import * as fs12 from "node:fs";
|
|
6860
|
+
import * as path11 from "node:path";
|
|
6655
6861
|
import { Type } from "typebox";
|
|
6656
6862
|
var CHILD_COMMAND_TOOL = "subagent_command";
|
|
6657
6863
|
var writeAtomicJson2 = createAtomicJsonWriter({ mode: 384 });
|
|
6658
6864
|
var MAX_OUTPUT_BYTES = 8 * 1024;
|
|
6659
6865
|
var RECENT_COMMANDS = 20;
|
|
6660
6866
|
function childCommandStatePath(channelDir) {
|
|
6661
|
-
return
|
|
6867
|
+
return path11.join(channelDir, "commands.json");
|
|
6662
6868
|
}
|
|
6663
6869
|
function readChildCommandState(channelDir) {
|
|
6664
6870
|
try {
|
|
6665
|
-
return JSON.parse(
|
|
6871
|
+
return JSON.parse(fs12.readFileSync(childCommandStatePath(channelDir), "utf8"));
|
|
6666
6872
|
} catch (error2) {
|
|
6667
6873
|
if (error2.code === "ENOENT") return void 0;
|
|
6668
6874
|
throw error2;
|
|
@@ -6678,7 +6884,7 @@ function text(result5) {
|
|
|
6678
6884
|
return Buffer.from(result5.content.filter((part) => part.type === "text").map((part) => part.text).join("\n")).subarray(-MAX_OUTPUT_BYTES).toString("utf8");
|
|
6679
6885
|
}
|
|
6680
6886
|
function createChildCommandRuntime(channelDir) {
|
|
6681
|
-
const ownerId =
|
|
6887
|
+
const ownerId = randomUUID5();
|
|
6682
6888
|
const jobs = /* @__PURE__ */ new Map();
|
|
6683
6889
|
const usedIds = /* @__PURE__ */ new Set();
|
|
6684
6890
|
let closed = false;
|
|
@@ -6834,7 +7040,7 @@ async function waitForCommand(settled, waitMs, signal) {
|
|
|
6834
7040
|
}
|
|
6835
7041
|
}
|
|
6836
7042
|
function commandReplyPath(channelDir, id) {
|
|
6837
|
-
return
|
|
7043
|
+
return path11.join(channelDir, "command-replies", `${id}.json`);
|
|
6838
7044
|
}
|
|
6839
7045
|
async function controlChildCommand(channelDir, operation, toolCallId, signal) {
|
|
6840
7046
|
const current = readChildCommandState(channelDir);
|
|
@@ -6849,7 +7055,7 @@ async function controlChildCommand(channelDir, operation, toolCallId, signal) {
|
|
|
6849
7055
|
throw new Error("Child command controller is closed.");
|
|
6850
7056
|
}
|
|
6851
7057
|
if (operation !== "status" && !toolCallId) throw new Error("An exact toolCallId is required; command controls never target the next command.");
|
|
6852
|
-
const id =
|
|
7058
|
+
const id = randomUUID5();
|
|
6853
7059
|
const deadlineAt = Date.now() + 5e3;
|
|
6854
7060
|
const request = { type: "command", id, ownerId: current.ownerId, operation, ...toolCallId ? { toolCallId } : {}, deadlineAt };
|
|
6855
7061
|
requestAsyncCommand(channelDir, request);
|
|
@@ -6858,7 +7064,7 @@ async function controlChildCommand(channelDir, operation, toolCallId, signal) {
|
|
|
6858
7064
|
while (Date.now() < deadlineAt) {
|
|
6859
7065
|
if (signal?.aborted) throw new Error("Command control wait aborted; delivery may still be pending.");
|
|
6860
7066
|
try {
|
|
6861
|
-
const reply = JSON.parse(
|
|
7067
|
+
const reply = JSON.parse(fs12.readFileSync(replyPath, "utf8"));
|
|
6862
7068
|
if (reply.error) throw new Error(reply.error);
|
|
6863
7069
|
if (reply.state) return reply.state;
|
|
6864
7070
|
} catch (error2) {
|
|
@@ -6868,7 +7074,7 @@ async function controlChildCommand(channelDir, operation, toolCallId, signal) {
|
|
|
6868
7074
|
}
|
|
6869
7075
|
throw new Error("Command control acknowledgement timed out. Inspect command status; cancellation is not confirmed.");
|
|
6870
7076
|
} finally {
|
|
6871
|
-
|
|
7077
|
+
fs12.rmSync(replyPath, { force: true });
|
|
6872
7078
|
}
|
|
6873
7079
|
}
|
|
6874
7080
|
|
|
@@ -6892,8 +7098,8 @@ function pinChildCacheRetention(agent, env) {
|
|
|
6892
7098
|
}
|
|
6893
7099
|
|
|
6894
7100
|
// src/runs/background/runner-aliases.ts
|
|
6895
|
-
import * as
|
|
6896
|
-
import * as
|
|
7101
|
+
import * as fs13 from "node:fs";
|
|
7102
|
+
import * as path12 from "node:path";
|
|
6897
7103
|
var JITI_ALIAS_ENV = "JITI_ALIAS";
|
|
6898
7104
|
var HOST_PEER_ALIASES = [
|
|
6899
7105
|
{ specifier: "@earendil-works/pi-coding-agent", pkg: "@earendil-works/pi-coding-agent", subpath: "." },
|
|
@@ -6916,7 +7122,7 @@ var CHORD_PEER_ALIASES = [
|
|
|
6916
7122
|
];
|
|
6917
7123
|
function readManifest(packageDir) {
|
|
6918
7124
|
try {
|
|
6919
|
-
return JSON.parse(
|
|
7125
|
+
return JSON.parse(fs13.readFileSync(path12.join(packageDir, "package.json"), "utf-8"));
|
|
6920
7126
|
} catch {
|
|
6921
7127
|
return void 0;
|
|
6922
7128
|
}
|
|
@@ -6953,7 +7159,7 @@ function resolvePackageSubpath(packageDir, subpath) {
|
|
|
6953
7159
|
const map = typeof exportsField === "string" || Array.isArray(exportsField) || exportsField && typeof exportsField === "object" && !Object.keys(exportsField).some((key) => key.startsWith(".")) ? { ".": exportsField } : exportsField;
|
|
6954
7160
|
if (subpath in map) {
|
|
6955
7161
|
const exact = exportTarget(map[subpath]);
|
|
6956
|
-
return typeof exact === "string" ?
|
|
7162
|
+
return typeof exact === "string" ? path12.resolve(packageDir, exact) : void 0;
|
|
6957
7163
|
}
|
|
6958
7164
|
let best;
|
|
6959
7165
|
for (const [pattern, entry] of Object.entries(map)) {
|
|
@@ -6968,23 +7174,23 @@ function resolvePackageSubpath(packageDir, subpath) {
|
|
|
6968
7174
|
const target = exportTarget(best.entry);
|
|
6969
7175
|
if (typeof target !== "string") return void 0;
|
|
6970
7176
|
const wildcard = subpath.slice(best.prefix.length, subpath.length - best.suffix.length);
|
|
6971
|
-
return
|
|
7177
|
+
return path12.resolve(packageDir, target.replaceAll("*", () => wildcard));
|
|
6972
7178
|
}
|
|
6973
7179
|
if (subpath !== ".") return void 0;
|
|
6974
|
-
return
|
|
7180
|
+
return path12.resolve(packageDir, typeof manifest.main === "string" && manifest.main.trim() ? manifest.main : "index.js");
|
|
6975
7181
|
}
|
|
6976
7182
|
function findPeerPackageDir(piPackageRoot, pkg, hostName) {
|
|
6977
7183
|
if (hostName === pkg) return piPackageRoot;
|
|
6978
|
-
const candidates = [
|
|
7184
|
+
const candidates = [path12.join(piPackageRoot, "node_modules", pkg)];
|
|
6979
7185
|
let dir = piPackageRoot;
|
|
6980
7186
|
for (; ; ) {
|
|
6981
|
-
const parent =
|
|
7187
|
+
const parent = path12.dirname(dir);
|
|
6982
7188
|
if (parent === dir) break;
|
|
6983
|
-
if (
|
|
6984
|
-
const modulesRoot =
|
|
6985
|
-
candidates.push(
|
|
7189
|
+
if (path12.basename(parent) === "node_modules" || path12.basename(path12.dirname(parent)) === "node_modules") {
|
|
7190
|
+
const modulesRoot = path12.basename(parent) === "node_modules" ? parent : path12.dirname(parent);
|
|
7191
|
+
candidates.push(path12.join(modulesRoot, pkg));
|
|
6986
7192
|
}
|
|
6987
|
-
candidates.push(
|
|
7193
|
+
candidates.push(path12.join(parent, "node_modules", pkg));
|
|
6988
7194
|
dir = parent;
|
|
6989
7195
|
}
|
|
6990
7196
|
return candidates.find((candidate) => readManifest(candidate)?.name === pkg);
|
|
@@ -7001,7 +7207,7 @@ function resolveHostPeerAliases(piPackageRoot) {
|
|
|
7001
7207
|
const packageDir = findPeerPackageDir(piPackageRoot, pkg, hostManifest?.name);
|
|
7002
7208
|
const target = packageDir ? resolvePackageSubpath(packageDir, subpath) : void 0;
|
|
7003
7209
|
if (optional && packageDir && target === void 0) continue;
|
|
7004
|
-
if (target &&
|
|
7210
|
+
if (target && fs13.existsSync(target)) aliases[specifier] = fs13.realpathSync(target);
|
|
7005
7211
|
else if (pkg !== CHORD_PACKAGE || chordRequired) missing.push(specifier);
|
|
7006
7212
|
}
|
|
7007
7213
|
return { aliases, missing };
|
|
@@ -7049,14 +7255,36 @@ function inheritParentProviders(modelRuntime, parentProviders, claimedProviderId
|
|
|
7049
7255
|
}
|
|
7050
7256
|
var CHILD_PROMPT_RUNTIME_EXTENSION_PATH = "<inline:pi-subagents:prompt-runtime>";
|
|
7051
7257
|
var CHILD_PROMPT_BOUNDARY_EXTENSION_PATH = "<inline:pi-subagents:prompt-boundary>";
|
|
7258
|
+
var PI_UNICODE_SPACES = new RegExp("[\\u00A0\\u2000-\\u200A\\u202F\\u205F\\u3000]", "g");
|
|
7259
|
+
function childExtensionFileIdentity(extensionPath) {
|
|
7260
|
+
if (!path13.isAbsolute(extensionPath)) return void 0;
|
|
7261
|
+
try {
|
|
7262
|
+
const stat = fs14.statSync(extensionPath, { bigint: true });
|
|
7263
|
+
return stat.isFile() ? `${stat.dev}:${stat.ino}` : void 0;
|
|
7264
|
+
} catch {
|
|
7265
|
+
return void 0;
|
|
7266
|
+
}
|
|
7267
|
+
}
|
|
7268
|
+
function createRequiredChildExtensionIdentity(requiredPaths) {
|
|
7269
|
+
const aliases = new Map([...requiredPaths].map((extensionPath) => [extensionPath, childExtensionFileIdentity(extensionPath) ?? extensionPath]));
|
|
7270
|
+
const identities = new Set(aliases.values());
|
|
7271
|
+
return (extensionPath) => {
|
|
7272
|
+
const known = aliases.get(extensionPath);
|
|
7273
|
+
if (known !== void 0) return known;
|
|
7274
|
+
const identity = childExtensionFileIdentity(extensionPath);
|
|
7275
|
+
if (identity === void 0 || !identities.has(identity)) return void 0;
|
|
7276
|
+
aliases.set(extensionPath, identity);
|
|
7277
|
+
return identity;
|
|
7278
|
+
};
|
|
7279
|
+
}
|
|
7052
7280
|
function orderChildPromptHooks(result5) {
|
|
7053
|
-
const runtime = result5.extensions.filter(({ path:
|
|
7054
|
-
const boundary = result5.extensions.filter(({ path:
|
|
7055
|
-
const others = result5.extensions.filter(({ path:
|
|
7281
|
+
const runtime = result5.extensions.filter(({ path: path17 }) => path17 === CHILD_PROMPT_RUNTIME_EXTENSION_PATH);
|
|
7282
|
+
const boundary = result5.extensions.filter(({ path: path17 }) => path17 === CHILD_PROMPT_BOUNDARY_EXTENSION_PATH);
|
|
7283
|
+
const others = result5.extensions.filter(({ path: path17 }) => path17 !== CHILD_PROMPT_RUNTIME_EXTENSION_PATH && path17 !== CHILD_PROMPT_BOUNDARY_EXTENSION_PATH);
|
|
7056
7284
|
return { ...result5, extensions: [...runtime, ...others, ...boundary] };
|
|
7057
7285
|
}
|
|
7058
7286
|
function prioritizeChildCommandRuntime(result5) {
|
|
7059
|
-
const index = result5.extensions.findIndex(({ path:
|
|
7287
|
+
const index = result5.extensions.findIndex(({ path: path17 }) => path17 === "<inline:pi-subagents:commands>");
|
|
7060
7288
|
if (index < 0) return result5;
|
|
7061
7289
|
if (result5.extensions.some((extension, otherIndex) => otherIndex !== index && extension.tools?.has("bash"))) {
|
|
7062
7290
|
const extensions2 = result5.extensions.filter((_, otherIndex) => otherIndex !== index);
|
|
@@ -7160,7 +7388,7 @@ function missingBuiltinMcpTools(session, names) {
|
|
|
7160
7388
|
const direct = new Set(session.getAllTools().filter((tool) => tool.exposure === "direct").map(({ name }) => name));
|
|
7161
7389
|
return names.filter((name) => !active2.has(name) || !direct.has(name));
|
|
7162
7390
|
}
|
|
7163
|
-
function flushQueuedProviderRegistrations(loader, modelRuntime, onError,
|
|
7391
|
+
function flushQueuedProviderRegistrations(loader, modelRuntime, onError, isRequiredSource) {
|
|
7164
7392
|
const claimedProviderIds = /* @__PURE__ */ new Set();
|
|
7165
7393
|
if (!("getExtensions" in loader) || typeof loader.getExtensions !== "function") return { claimedProviderIds, registered: false };
|
|
7166
7394
|
const { runtime } = loader.getExtensions();
|
|
@@ -7172,7 +7400,7 @@ function flushQueuedProviderRegistrations(loader, modelRuntime, onError, require
|
|
|
7172
7400
|
registered = true;
|
|
7173
7401
|
} catch (error2) {
|
|
7174
7402
|
onError?.({ extensionPath, event: "register_provider", error: error2 });
|
|
7175
|
-
if (
|
|
7403
|
+
if (isRequiredSource(extensionPath)) throw new Error(`Required child extension provider registration failed for '${extensionPath}': ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
7176
7404
|
}
|
|
7177
7405
|
}
|
|
7178
7406
|
if (Array.isArray(runtime.pendingProviderRegistrations)) runtime.pendingProviderRegistrations = [];
|
|
@@ -7183,7 +7411,7 @@ function flushQueuedProviderRegistrations(loader, modelRuntime, onError, require
|
|
|
7183
7411
|
registered = true;
|
|
7184
7412
|
} catch (error2) {
|
|
7185
7413
|
onError?.({ extensionPath, event: "register_provider", error: error2 });
|
|
7186
|
-
if (
|
|
7414
|
+
if (isRequiredSource(extensionPath)) throw new Error(`Required child extension provider registration failed for '${extensionPath}': ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
7187
7415
|
}
|
|
7188
7416
|
}
|
|
7189
7417
|
if (Array.isArray(runtime.pendingNativeProviderRegistrations)) runtime.pendingNativeProviderRegistrations = [];
|
|
@@ -7197,7 +7425,7 @@ function flushQueuedProviderRegistrations(loader, modelRuntime, onError, require
|
|
|
7197
7425
|
registered = true;
|
|
7198
7426
|
} catch (error2) {
|
|
7199
7427
|
onError?.({ extensionPath, event: "register_virtual_model", error: error2 });
|
|
7200
|
-
if (
|
|
7428
|
+
if (isRequiredSource(extensionPath)) throw new Error(`Required child extension virtual model registration failed for '${extensionPath}': ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
7201
7429
|
}
|
|
7202
7430
|
}
|
|
7203
7431
|
if (Array.isArray(virtualModelQueue.pendingVirtualModelRegistrations)) virtualModelQueue.pendingVirtualModelRegistrations = [];
|
|
@@ -7207,14 +7435,14 @@ async function loadHostPiCodingAgent() {
|
|
|
7207
7435
|
const overrideRoot = process.env[PI_CODING_AGENT_PACKAGE_ROOT_ENV]?.trim() || void 0;
|
|
7208
7436
|
const root = resolveHostPiPackageRoot();
|
|
7209
7437
|
if (root) {
|
|
7210
|
-
const entry =
|
|
7438
|
+
const entry = fs14.realpathSync(resolveHostPackageEntry(root, root === overrideRoot ? overrideRoot : void 0));
|
|
7211
7439
|
return import(pathToFileURL(entry).href);
|
|
7212
7440
|
}
|
|
7213
7441
|
return import(PI_CODING_AGENT_PACKAGE);
|
|
7214
7442
|
}
|
|
7215
7443
|
function resolveHostPackageEntry(root, overrideRoot) {
|
|
7216
|
-
const packageJson =
|
|
7217
|
-
const source =
|
|
7444
|
+
const packageJson = path13.join(root, "package.json");
|
|
7445
|
+
const source = fs14.readFileSync(packageJson, "utf8");
|
|
7218
7446
|
let parsed;
|
|
7219
7447
|
try {
|
|
7220
7448
|
parsed = JSON.parse(source);
|
|
@@ -7275,6 +7503,12 @@ function createDefaultChildSessionFactory(options = {}) {
|
|
|
7275
7503
|
return typeof value === "function" ? value.bind(target) : value;
|
|
7276
7504
|
}
|
|
7277
7505
|
})) }] : [];
|
|
7506
|
+
const requiredPaths = new Set((launch.requiredExtensions ?? []).map(({ path: path17 }) => path17));
|
|
7507
|
+
const requiredExtensionIdentity = createRequiredChildExtensionIdentity(requiredPaths);
|
|
7508
|
+
const resolvedExtensionPaths = /* @__PURE__ */ new Map();
|
|
7509
|
+
const isRequiredSource = (extensionPath) => requiredPaths.size > 0 && requiredExtensionIdentity(
|
|
7510
|
+
resolvedExtensionPaths.get(extensionPath) ?? extensionPath.replace(PI_UNICODE_SPACES, " ")
|
|
7511
|
+
) !== void 0;
|
|
7278
7512
|
const loader = new pi.DefaultResourceLoader({
|
|
7279
7513
|
cwd: launch.cwd,
|
|
7280
7514
|
agentDir,
|
|
@@ -7311,13 +7545,18 @@ function createDefaultChildSessionFactory(options = {}) {
|
|
|
7311
7545
|
};
|
|
7312
7546
|
let inheritedProviders = /* @__PURE__ */ new Set();
|
|
7313
7547
|
const open = async () => {
|
|
7314
|
-
const requiredPaths = new Set((launch.requiredExtensions ?? []).map(({ path: path15 }) => path15));
|
|
7315
7548
|
applyProcessEnv(launch.processEnv);
|
|
7316
7549
|
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
7550
|
await loader.reload();
|
|
7318
|
-
|
|
7319
|
-
|
|
7320
|
-
|
|
7551
|
+
if (requiredPaths.size > 0) for (const extension of loader.getExtensions().extensions) resolvedExtensionPaths.set(extension.path, extension.resolvedPath);
|
|
7552
|
+
const loadErrors = requiredPaths.size > 0 ? loader.getExtensions().errors.filter(({ path: path17 }) => isRequiredSource(path17)) : [];
|
|
7553
|
+
if (loadErrors.length > 0) throw new Error(`Required child extension failed to load: ${loadErrors.map(({ path: path17, error: error2 }) => `${path17}: ${error2}`).join("; ")}`);
|
|
7554
|
+
const queued = flushQueuedProviderRegistrations(loader, modelRuntime, launch.onExtensionError, isRequiredSource);
|
|
7555
|
+
if (requiredPaths.size > 0) {
|
|
7556
|
+
const loadedIdentities = new Set(loader.getExtensions().extensions.map(({ resolvedPath }) => requiredExtensionIdentity(resolvedPath)));
|
|
7557
|
+
const missing = [...requiredPaths].filter((extensionPath) => !loadedIdentities.has(requiredExtensionIdentity(extensionPath)));
|
|
7558
|
+
if (missing.length) throw new Error(`Required child extension failed to load: ${missing.join(", ")}`);
|
|
7559
|
+
}
|
|
7321
7560
|
if (launch.parentProviderRegistry) inheritedProviders = inheritParentProviders(modelRuntime, launch.parentProviderRegistry, queued.claimedProviderIds, launch.onExtensionError);
|
|
7322
7561
|
if (queued.registered || inheritedProviders.size > 0) {
|
|
7323
7562
|
try {
|
|
@@ -7328,7 +7567,7 @@ function createDefaultChildSessionFactory(options = {}) {
|
|
|
7328
7567
|
}
|
|
7329
7568
|
}
|
|
7330
7569
|
const newSessionOptions = launch.parentSessionFile ? { parentSession: launch.parentSessionFile } : void 0;
|
|
7331
|
-
const newSessionFile = launch.storage.kind === "file" && !
|
|
7570
|
+
const newSessionFile = launch.storage.kind === "file" && !fs14.existsSync(launch.storage.sessionFile);
|
|
7332
7571
|
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
7572
|
const header = newSessionFile && launch.parentSessionFile ? sessionManager.getHeader() : null;
|
|
7334
7573
|
if (header) header.parentSession = launch.parentSessionFile;
|
|
@@ -7354,7 +7593,7 @@ function createDefaultChildSessionFactory(options = {}) {
|
|
|
7354
7593
|
await session2.bindExtensions({
|
|
7355
7594
|
mode: "print",
|
|
7356
7595
|
onError: (error2) => {
|
|
7357
|
-
if (binding &&
|
|
7596
|
+
if (binding && isRequiredSource(error2.extensionPath)) requiredStartupErrors.push(`${error2.extensionPath} (${error2.event}): ${error2.error}`);
|
|
7358
7597
|
launch.onExtensionError?.({ extensionPath: error2.extensionPath, event: error2.event, error: error2.error });
|
|
7359
7598
|
}
|
|
7360
7599
|
});
|
|
@@ -7382,7 +7621,7 @@ function createDefaultChildSessionFactory(options = {}) {
|
|
|
7382
7621
|
if (disposals !== disposalsAtStart) cancel.abort();
|
|
7383
7622
|
const ready = (async () => {
|
|
7384
7623
|
try {
|
|
7385
|
-
await builtinMcp.waitForTools(signal, () => loader.getExtensions().errors.find(({ path:
|
|
7624
|
+
await builtinMcp.waitForTools(signal, () => loader.getExtensions().errors.find(({ path: path17 }) => path17 === "builtin:mcp")?.error);
|
|
7386
7625
|
if (disposals !== disposalsAtStart) throw new Error("The child session factory was disposed while the child waited for its MCP tools.");
|
|
7387
7626
|
if (signal.aborted) throw new Error("The child launch was cancelled while waiting for its MCP tools.");
|
|
7388
7627
|
const missing = missingBuiltinMcpTools(session, builtinMcpTools.map(({ name }) => name));
|
|
@@ -7471,7 +7710,7 @@ function childSessionFactory() {
|
|
|
7471
7710
|
}
|
|
7472
7711
|
function createLazyPlacementFactory(local) {
|
|
7473
7712
|
let placed;
|
|
7474
|
-
const factory = async () => placed ??= (await import("./chunk-
|
|
7713
|
+
const factory = async () => placed ??= (await import("./chunk-MHZ4G23A.js")).createPlacementAwareChildSessionFactory(local);
|
|
7475
7714
|
return {
|
|
7476
7715
|
async create(launch) {
|
|
7477
7716
|
return launch.machine ? (await factory()).create(launch) : local.create(launch);
|
|
@@ -7493,13 +7732,10 @@ async function disposeChildSessions() {
|
|
|
7493
7732
|
|
|
7494
7733
|
// src/workflows/workflow-reuse.ts
|
|
7495
7734
|
import { createHash as createHash3 } from "node:crypto";
|
|
7496
|
-
import * as
|
|
7497
|
-
import * as
|
|
7498
|
-
var WORKFLOW_STOP_CAUSE_RUNTIME_REPLACED = "runtime-replaced";
|
|
7735
|
+
import * as fs15 from "node:fs";
|
|
7736
|
+
import * as path14 from "node:path";
|
|
7499
7737
|
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
|
-
}
|
|
7738
|
+
var WORKFLOW_RUNTIME_REPLACED_STOP_NOTICE = "Async children that were still running keep running, and finished children are kept.";
|
|
7503
7739
|
function runtimeReplacedAbortReason() {
|
|
7504
7740
|
return Object.assign(new Error("Workflow stopped because the extension session was replaced or reloaded."), { workflowStopCause: WORKFLOW_STOP_CAUSE_RUNTIME_REPLACED });
|
|
7505
7741
|
}
|
|
@@ -7516,9 +7752,9 @@ function workflowChildFingerprint(params) {
|
|
|
7516
7752
|
return sha256(workflowRunParamsFingerprint(params));
|
|
7517
7753
|
}
|
|
7518
7754
|
function appendWorkflowChildJournal(workflowAsyncDir, record) {
|
|
7519
|
-
const journalPath =
|
|
7755
|
+
const journalPath = path14.join(workflowAsyncDir, WORKFLOW_CHILD_JOURNAL_FILE);
|
|
7520
7756
|
try {
|
|
7521
|
-
|
|
7757
|
+
fs15.appendFileSync(journalPath, `${JSON.stringify(record)}
|
|
7522
7758
|
`, "utf-8");
|
|
7523
7759
|
} catch (error2) {
|
|
7524
7760
|
console.error(`Failed to append workflow child journal '${journalPath}':`, error2);
|
|
@@ -7528,7 +7764,7 @@ function readWorkflowChildJournal(runId, workflowAsyncDir) {
|
|
|
7528
7764
|
const source = { runId, started: /* @__PURE__ */ new Map(), settled: /* @__PURE__ */ new Map() };
|
|
7529
7765
|
let text2 = "";
|
|
7530
7766
|
try {
|
|
7531
|
-
text2 =
|
|
7767
|
+
text2 = fs15.readFileSync(path14.join(workflowAsyncDir, WORKFLOW_CHILD_JOURNAL_FILE), "utf-8");
|
|
7532
7768
|
} catch {
|
|
7533
7769
|
return source;
|
|
7534
7770
|
}
|
|
@@ -7548,6 +7784,44 @@ function readWorkflowChildJournal(runId, workflowAsyncDir) {
|
|
|
7548
7784
|
}
|
|
7549
7785
|
return source;
|
|
7550
7786
|
}
|
|
7787
|
+
var WORKFLOW_REUSE_CHAIN_MAX_LINKS = 16;
|
|
7788
|
+
function readWorkflowReuseChain(asyncDirRoot, headRunId, headStatus) {
|
|
7789
|
+
const source = readWorkflowChildJournal(headRunId, path14.join(asyncDirRoot, headRunId));
|
|
7790
|
+
const visited = /* @__PURE__ */ new Set([headRunId]);
|
|
7791
|
+
let next = headStatus.workflow?.reusedFrom;
|
|
7792
|
+
for (let links = 0; isRunId(next) && links < WORKFLOW_REUSE_CHAIN_MAX_LINKS && !visited.has(next); links++) {
|
|
7793
|
+
visited.add(next);
|
|
7794
|
+
const asyncDir = path14.join(asyncDirRoot, next);
|
|
7795
|
+
let status;
|
|
7796
|
+
try {
|
|
7797
|
+
status = readStatus(asyncDir);
|
|
7798
|
+
} catch {
|
|
7799
|
+
break;
|
|
7800
|
+
}
|
|
7801
|
+
if (status?.mode !== "workflow" || status.workflow?.scriptDigest !== headStatus.workflow?.scriptDigest) break;
|
|
7802
|
+
const link = readWorkflowChildJournal(next, asyncDir);
|
|
7803
|
+
for (const key of /* @__PURE__ */ new Set([...link.started.keys(), ...link.settled.keys()])) {
|
|
7804
|
+
if (source.started.has(key) || source.settled.has(key)) continue;
|
|
7805
|
+
const started = link.started.get(key);
|
|
7806
|
+
const settled = link.settled.get(key);
|
|
7807
|
+
if (started) source.started.set(key, started);
|
|
7808
|
+
if (settled) source.settled.set(key, settled);
|
|
7809
|
+
}
|
|
7810
|
+
next = status.workflow?.reusedFrom;
|
|
7811
|
+
}
|
|
7812
|
+
return source;
|
|
7813
|
+
}
|
|
7814
|
+
function claimedWorkflowRunRefusal(runId, claim, named) {
|
|
7815
|
+
const run = `${named ? "options.reuseFrom: workflow" : "Workflow"} run '${runId}'`;
|
|
7816
|
+
if (claim.kind === "resumed") return `${run} was resumed as ${claim.newRunId}; wait on it or stop it.`;
|
|
7817
|
+
if (claim.kind === "busy") return `${run} is being resumed by pid ${claim.pid}; wait for it.`;
|
|
7818
|
+
return `${run} has a resume claim that cannot be read; wait or remove it.`;
|
|
7819
|
+
}
|
|
7820
|
+
function assertWorkflowRunNotClaimed(asyncDir, runId, named, claimOwnerRunId) {
|
|
7821
|
+
const claim = workflowResumeClaimState(asyncDir);
|
|
7822
|
+
if (claim.kind === "none" || claim.kind !== "unreadable" && claim.newRunId === claimOwnerRunId) return;
|
|
7823
|
+
throw new Error(claimedWorkflowRunRefusal(runId, claim, named));
|
|
7824
|
+
}
|
|
7551
7825
|
function findWorkflowReuseSource(asyncDirRoot, sessionId, scriptDigest, argsDigest) {
|
|
7552
7826
|
let runIds;
|
|
7553
7827
|
try {
|
|
@@ -7556,7 +7830,7 @@ function findWorkflowReuseSource(asyncDirRoot, sessionId, scriptDigest, argsDige
|
|
|
7556
7830
|
return void 0;
|
|
7557
7831
|
}
|
|
7558
7832
|
for (const runId of runIds) {
|
|
7559
|
-
const asyncDir =
|
|
7833
|
+
const asyncDir = path14.join(asyncDirRoot, runId);
|
|
7560
7834
|
let status;
|
|
7561
7835
|
try {
|
|
7562
7836
|
status = readStatus(asyncDir);
|
|
@@ -7564,18 +7838,23 @@ function findWorkflowReuseSource(asyncDirRoot, sessionId, scriptDigest, argsDige
|
|
|
7564
7838
|
continue;
|
|
7565
7839
|
}
|
|
7566
7840
|
if (status?.mode !== "workflow" || status.workflow?.scriptDigest !== scriptDigest || status.workflow.argsDigest !== argsDigest) continue;
|
|
7567
|
-
|
|
7841
|
+
if (!isReusableWorkflowStopCause(status.workflow.stopCause)) return void 0;
|
|
7842
|
+
assertWorkflowRunNotClaimed(asyncDir, runId, false);
|
|
7843
|
+
return readWorkflowReuseChain(asyncDirRoot, runId, status);
|
|
7568
7844
|
}
|
|
7569
7845
|
return void 0;
|
|
7570
7846
|
}
|
|
7571
|
-
function readNamedWorkflowReuseSource(asyncDirRoot, runId, scriptDigest, cwd) {
|
|
7572
|
-
const
|
|
7847
|
+
function readNamedWorkflowReuseSource(asyncDirRoot, runId, scriptDigest, cwd, claimOwnerRunId) {
|
|
7848
|
+
const runName = isRunId(runId);
|
|
7849
|
+
if (runName) reconcileAsyncRun(path14.join(asyncDirRoot, runId));
|
|
7850
|
+
const status = runName ? readStatus(path14.join(asyncDirRoot, runId)) : null;
|
|
7573
7851
|
if (!status) throw new Error(`options.reuseFrom: workflow run '${runId}' was not found.`);
|
|
7574
7852
|
if (status.mode !== "workflow") throw new Error(`options.reuseFrom: run '${runId}' is not a workflow run.`);
|
|
7575
|
-
if (status.workflow?.stopCause
|
|
7853
|
+
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
7854
|
if (status.workflow.scriptDigest !== scriptDigest) throw new Error(`options.reuseFrom: workflow run '${runId}' ran a different workflow script.`);
|
|
7577
7855
|
if (status.cwd !== cwd) throw new Error(`options.reuseFrom: workflow run '${runId}' ran in '${status.cwd}', not '${cwd}'.`);
|
|
7578
|
-
|
|
7856
|
+
assertWorkflowRunNotClaimed(path14.join(asyncDirRoot, runId), runId, true, claimOwnerRunId);
|
|
7857
|
+
return readWorkflowReuseChain(asyncDirRoot, runId, status);
|
|
7579
7858
|
}
|
|
7580
7859
|
function matchWorkflowReuse(source, key, fingerprint) {
|
|
7581
7860
|
const settled = source.settled.get(key);
|
|
@@ -7584,6 +7863,74 @@ function matchWorkflowReuse(source, key, fingerprint) {
|
|
|
7584
7863
|
return started?.fingerprint === fingerprint ? { kind: "started", runId: started.runId } : void 0;
|
|
7585
7864
|
}
|
|
7586
7865
|
|
|
7866
|
+
// src/workflows/workflow-resume.ts
|
|
7867
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
7868
|
+
import * as path15 from "node:path";
|
|
7869
|
+
var MAX_WORKFLOW_RESUME_ATTEMPTS = 3;
|
|
7870
|
+
function workflowResumeRefusal(code, reason, runId) {
|
|
7871
|
+
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) : "";
|
|
7872
|
+
return { code, reason, byHand };
|
|
7873
|
+
}
|
|
7874
|
+
function manualWorkflowResumeRefusalText(runId, refusal) {
|
|
7875
|
+
return `Cannot resume workflow '${runId}': ${refusalSentence(refusal.reason, refusal.byHand === relaunchByHandText(runId) ? refusal.byHand : "")}`;
|
|
7876
|
+
}
|
|
7877
|
+
function workflowResumeTargetId(asyncDirRoot, params) {
|
|
7878
|
+
if (params.message !== void 0 || params.task !== void 0 || params.index !== void 0) return void 0;
|
|
7879
|
+
if (Array.isArray(params.chain) && params.chain.length > 0) return void 0;
|
|
7880
|
+
const id = params.id ?? params.runId;
|
|
7881
|
+
if (!isRunId(id)) return void 0;
|
|
7882
|
+
let status;
|
|
7883
|
+
try {
|
|
7884
|
+
status = readStatus(path15.join(asyncDirRoot, id));
|
|
7885
|
+
} catch {
|
|
7886
|
+
return void 0;
|
|
7887
|
+
}
|
|
7888
|
+
return status?.mode === "workflow" && status.parentWorkflowRunId === void 0 ? id : void 0;
|
|
7889
|
+
}
|
|
7890
|
+
function prepareWorkflowResume(input) {
|
|
7891
|
+
const { asyncDirRoot, sourceRunId, trigger } = input;
|
|
7892
|
+
const refuse = (code, reason) => ({ ok: false, ...workflowResumeRefusal(code, reason, sourceRunId) });
|
|
7893
|
+
const sourceAsyncDir = path15.join(asyncDirRoot, sourceRunId);
|
|
7894
|
+
const stored = readStatus(sourceAsyncDir);
|
|
7895
|
+
if (stored && !ownsRun(input.currentSessionId, stored.sessionId)) return refuse("not-owned", "it was started in another session; reopen that session to resume it");
|
|
7896
|
+
if (trigger === "manual") reconcileAsyncRun(sourceAsyncDir);
|
|
7897
|
+
const status = readStatus(sourceAsyncDir);
|
|
7898
|
+
if (!status || status.mode !== "workflow" || !isReusableWorkflowStopCause(status.workflow?.stopCause)) return refuse("not-interrupted", "not interrupted; pass message to revive one of its children");
|
|
7899
|
+
const attempt = (status.workflow?.resumeOrigin?.attempt ?? 0) + (status.workflow?.stopCause === WORKFLOW_STOP_CAUSE_OWNER_EXITED ? 1 : 0);
|
|
7900
|
+
if (trigger === "auto" && attempt > MAX_WORKFLOW_RESUME_ATTEMPTS) return refuse("attempts", `${MAX_WORKFLOW_RESUME_ATTEMPTS} resumes already followed Pi exits`);
|
|
7901
|
+
const read = readWorkflowLaunchRecord(sourceAsyncDir);
|
|
7902
|
+
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}`);
|
|
7903
|
+
const record = read.record;
|
|
7904
|
+
if (record.source === "structured") return refuse("structured", "launched from tasks/chain");
|
|
7905
|
+
if (record.source === "script" && (typeof record.params.workflowScript !== "string" || workflowScriptDigest(record.params.workflowScript) !== record.scriptDigest)) {
|
|
7906
|
+
return refuse("launch-invalid", "launch.json is invalid: its script does not match its scriptDigest");
|
|
7907
|
+
}
|
|
7908
|
+
const normalized = normalizePublicSubagentExecution(record.params);
|
|
7909
|
+
if (!normalized.ok) return refuse("launch-invalid", `launch.json is invalid: ${normalized.error}`);
|
|
7910
|
+
const newRunId = input.newRunId ?? randomUUID6();
|
|
7911
|
+
const claim = claimWorkflowResume(sourceAsyncDir, { newRunId, trigger, now: input.now });
|
|
7912
|
+
if (claim.kind === "resumed") return refuse("claim-resumed", `resumed as ${claim.newRunId}; wait on it or stop it`);
|
|
7913
|
+
if (claim.kind === "busy") return refuse("claim-busy", `being resumed by pid ${claim.pid}`);
|
|
7914
|
+
if (claim.kind === "unreadable") return refuse("claim-busy", "its resume claim cannot be read; wait or remove it");
|
|
7915
|
+
return {
|
|
7916
|
+
ok: true,
|
|
7917
|
+
prepared: {
|
|
7918
|
+
sourceRunId,
|
|
7919
|
+
sourceAsyncDir,
|
|
7920
|
+
newRunId,
|
|
7921
|
+
trigger,
|
|
7922
|
+
params: {
|
|
7923
|
+
...record.params,
|
|
7924
|
+
async: true,
|
|
7925
|
+
reuseFrom: sourceRunId,
|
|
7926
|
+
...status.admissionCapabilityCeiling ? { capabilityCeiling: status.admissionCapabilityCeiling } : {}
|
|
7927
|
+
},
|
|
7928
|
+
expectedScriptDigest: record.scriptDigest,
|
|
7929
|
+
resumeOrigin: { sourceRunId, attempt }
|
|
7930
|
+
}
|
|
7931
|
+
};
|
|
7932
|
+
}
|
|
7933
|
+
|
|
7587
7934
|
// src/extension/subagent-options.ts
|
|
7588
7935
|
import { isDeepStrictEqual } from "node:util";
|
|
7589
7936
|
import { Compile } from "typebox/compile";
|
|
@@ -7622,140 +7969,8 @@ function flattenSubagentToolOptions(params, disabled) {
|
|
|
7622
7969
|
return { ...topLevel, ...options };
|
|
7623
7970
|
}
|
|
7624
7971
|
|
|
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
7972
|
// src/runs/background/retained-children.ts
|
|
7758
|
-
import * as
|
|
7973
|
+
import * as fs16 from "node:fs";
|
|
7759
7974
|
var MAX_RETAINED_CHILDREN = 10;
|
|
7760
7975
|
var MAX_RETAINED_CHILD_CANDIDATES = 100;
|
|
7761
7976
|
var MAX_TASK_SUMMARY_LENGTH = 120;
|
|
@@ -7769,7 +7984,7 @@ function retainedSessionFile(sessionFile) {
|
|
|
7769
7984
|
if (!sessionFile) return { state: "not-resumable", reason: "no persisted session file" };
|
|
7770
7985
|
if (!sessionFile.endsWith(".jsonl")) return { state: "not-resumable", reason: "persisted session file is not a .jsonl file" };
|
|
7771
7986
|
try {
|
|
7772
|
-
const stat =
|
|
7987
|
+
const stat = fs16.lstatSync(sessionFile);
|
|
7773
7988
|
if (!stat.isFile() || stat.isSymbolicLink()) return { state: "not-resumable", reason: "persisted session file is not a regular file" };
|
|
7774
7989
|
return { state: "resumable", sessionPath: sessionFile };
|
|
7775
7990
|
} catch (error2) {
|
|
@@ -7796,7 +8011,7 @@ function childResumability(run, step) {
|
|
|
7796
8011
|
let requiredCwd;
|
|
7797
8012
|
try {
|
|
7798
8013
|
requiredCwd = resolveRetainedWorktreeCwd(parallelHandoffPath(run.asyncDir), run.id, step.index) ?? recoveryDescriptor.cwd ?? run.cwd;
|
|
7799
|
-
if (!requiredCwd || !
|
|
8014
|
+
if (!requiredCwd || !fs16.statSync(requiredCwd).isDirectory()) return { state: "not-resumable", reason: `required cwd is missing: ${requiredCwd ?? "unknown"}` };
|
|
7800
8015
|
} catch (error2) {
|
|
7801
8016
|
return { state: "not-resumable", reason: `resume dependency unavailable: ${error2 instanceof Error ? error2.message : String(error2)}` };
|
|
7802
8017
|
}
|
|
@@ -7857,52 +8072,52 @@ function formatRetainedChildren(children) {
|
|
|
7857
8072
|
}
|
|
7858
8073
|
|
|
7859
8074
|
// src/runs/shared/external-job-bridge.ts
|
|
7860
|
-
import { randomUUID as
|
|
7861
|
-
import * as
|
|
8075
|
+
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
8076
|
+
import * as fs17 from "node:fs";
|
|
7862
8077
|
import * as os2 from "node:os";
|
|
7863
|
-
import * as
|
|
8078
|
+
import * as path16 from "node:path";
|
|
7864
8079
|
var EXTERNAL_JOB_BRIDGE_REQUEST_DIR = "external-job-requests";
|
|
7865
8080
|
var EXTERNAL_JOB_BRIDGE_RESPONSE_DIR = "external-job-responses";
|
|
7866
8081
|
var CHILD_BRIDGE_SWEEP_INTERVAL_MS = 1e3;
|
|
7867
8082
|
var MAX_REQUESTS_PER_SWEEP = 100;
|
|
7868
8083
|
var inFlight = /* @__PURE__ */ new Set();
|
|
7869
8084
|
function requestDir(asyncDir) {
|
|
7870
|
-
return
|
|
8085
|
+
return path16.join(asyncDir, EXTERNAL_JOB_BRIDGE_REQUEST_DIR);
|
|
7871
8086
|
}
|
|
7872
8087
|
function responseDir(asyncDir) {
|
|
7873
|
-
return
|
|
8088
|
+
return path16.join(asyncDir, EXTERNAL_JOB_BRIDGE_RESPONSE_DIR);
|
|
7874
8089
|
}
|
|
7875
8090
|
function requestPath(asyncDir, id) {
|
|
7876
|
-
return
|
|
8091
|
+
return path16.join(requestDir(asyncDir), `${id}.json`);
|
|
7877
8092
|
}
|
|
7878
8093
|
function isDispatchOperation(operation) {
|
|
7879
8094
|
return operation === "start" || operation === "follow-up";
|
|
7880
8095
|
}
|
|
7881
8096
|
function dispatchClaimDir(asyncDir, id) {
|
|
7882
|
-
return
|
|
8097
|
+
return path16.join(requestDir(asyncDir), `${id}.claim`);
|
|
7883
8098
|
}
|
|
7884
8099
|
function dispatchClaimTempDir(asyncDir, id) {
|
|
7885
|
-
return
|
|
8100
|
+
return path16.join(requestDir(asyncDir), `${id}.claim.tmp-${randomUUID7()}`);
|
|
7886
8101
|
}
|
|
7887
8102
|
function dispatchClaimCompletedPath(claimDir) {
|
|
7888
|
-
return
|
|
8103
|
+
return path16.join(claimDir, "completed.json");
|
|
7889
8104
|
}
|
|
7890
8105
|
function dispatchClaimHandlePath(claimDir) {
|
|
7891
|
-
return
|
|
8106
|
+
return path16.join(claimDir, "handle.json");
|
|
7892
8107
|
}
|
|
7893
8108
|
function completedDispatchClaimExists(asyncDir, id) {
|
|
7894
|
-
return
|
|
8109
|
+
return fs17.existsSync(dispatchClaimCompletedPath(dispatchClaimDir(asyncDir, id)));
|
|
7895
8110
|
}
|
|
7896
8111
|
function responsePath(asyncDir, id) {
|
|
7897
|
-
return
|
|
8112
|
+
return path16.join(responseDir(asyncDir), `${id}.json`);
|
|
7898
8113
|
}
|
|
7899
8114
|
function readJson2(filePath) {
|
|
7900
|
-
return JSON.parse(
|
|
8115
|
+
return JSON.parse(fs17.readFileSync(filePath, "utf-8"));
|
|
7901
8116
|
}
|
|
7902
8117
|
function processStartIdentity(pid) {
|
|
7903
8118
|
if (process.platform === "linux") {
|
|
7904
8119
|
try {
|
|
7905
|
-
const stat =
|
|
8120
|
+
const stat = fs17.readFileSync(`/proc/${pid}/stat`, "utf-8");
|
|
7906
8121
|
const commandEnd = stat.lastIndexOf(")");
|
|
7907
8122
|
if (commandEnd === -1) return void 0;
|
|
7908
8123
|
const fields = stat.slice(commandEnd + 1).trim().split(/\s+/);
|
|
@@ -8022,16 +8237,16 @@ function claimDispatchRequest(asyncDir, filePath, request) {
|
|
|
8022
8237
|
const claimDir = dispatchClaimDir(asyncDir, request.id);
|
|
8023
8238
|
const tempClaimDir = dispatchClaimTempDir(asyncDir, request.id);
|
|
8024
8239
|
try {
|
|
8025
|
-
|
|
8026
|
-
writeAtomicJson(
|
|
8027
|
-
writeAtomicJson(
|
|
8028
|
-
|
|
8240
|
+
fs17.mkdirSync(tempClaimDir);
|
|
8241
|
+
writeAtomicJson(path16.join(tempClaimDir, "owner.json"), owner);
|
|
8242
|
+
writeAtomicJson(path16.join(tempClaimDir, "request.json"), claimed);
|
|
8243
|
+
fs17.renameSync(tempClaimDir, claimDir);
|
|
8029
8244
|
} catch (error2) {
|
|
8030
|
-
|
|
8245
|
+
fs17.rmSync(tempClaimDir, { recursive: true, force: true });
|
|
8031
8246
|
if (isClaimConflictError(error2)) return void 0;
|
|
8032
8247
|
throw error2;
|
|
8033
8248
|
}
|
|
8034
|
-
|
|
8249
|
+
fs17.rmSync(filePath, { force: true });
|
|
8035
8250
|
return { request: claimed, filePath: claimDir };
|
|
8036
8251
|
}
|
|
8037
8252
|
function parseClaimOwner(value) {
|
|
@@ -8051,7 +8266,7 @@ function claimOwnerIsDead(owner) {
|
|
|
8051
8266
|
}
|
|
8052
8267
|
function readClaimOwner(claimDir) {
|
|
8053
8268
|
try {
|
|
8054
|
-
return parseClaimOwner(readJson2(
|
|
8269
|
+
return parseClaimOwner(readJson2(path16.join(claimDir, "owner.json")));
|
|
8055
8270
|
} catch {
|
|
8056
8271
|
return void 0;
|
|
8057
8272
|
}
|
|
@@ -8113,23 +8328,23 @@ function createChildExternalJobBridgeSweeper() {
|
|
|
8113
8328
|
function serviceExternalJobBridgeRequests(asyncDir) {
|
|
8114
8329
|
let files;
|
|
8115
8330
|
try {
|
|
8116
|
-
files =
|
|
8331
|
+
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
8332
|
} catch (error2) {
|
|
8118
8333
|
if (error2.code === "ENOENT") return;
|
|
8119
8334
|
throw error2;
|
|
8120
8335
|
}
|
|
8121
|
-
|
|
8336
|
+
fs17.mkdirSync(responseDir(asyncDir), { recursive: true });
|
|
8122
8337
|
for (const file of files) {
|
|
8123
8338
|
serviceExternalJobBridgeRequestFile(asyncDir, file);
|
|
8124
8339
|
}
|
|
8125
8340
|
}
|
|
8126
8341
|
function serviceExternalJobBridgeRequestFile(asyncDir, file) {
|
|
8127
|
-
|
|
8342
|
+
fs17.mkdirSync(responseDir(asyncDir), { recursive: true });
|
|
8128
8343
|
if (file.endsWith(".claim")) {
|
|
8129
8344
|
serviceExternalJobDispatchClaim(asyncDir, file);
|
|
8130
8345
|
return;
|
|
8131
8346
|
}
|
|
8132
|
-
const filePath =
|
|
8347
|
+
const filePath = path16.join(requestDir(asyncDir), file);
|
|
8133
8348
|
let request;
|
|
8134
8349
|
try {
|
|
8135
8350
|
request = assertRequest(readJson2(filePath), filePath);
|
|
@@ -8145,14 +8360,14 @@ function serviceExternalJobBridgeRequestFile(asyncDir, file) {
|
|
|
8145
8360
|
message: error2 instanceof Error ? error2.message : String(error2),
|
|
8146
8361
|
completedAt: Date.now()
|
|
8147
8362
|
});
|
|
8148
|
-
|
|
8363
|
+
fs17.rmSync(filePath, { force: true });
|
|
8149
8364
|
return;
|
|
8150
8365
|
}
|
|
8151
8366
|
if (isDispatchOperation(request.operation) && completedDispatchClaimExists(asyncDir, request.id)) {
|
|
8152
|
-
|
|
8367
|
+
fs17.rmSync(filePath, { force: true });
|
|
8153
8368
|
return;
|
|
8154
8369
|
}
|
|
8155
|
-
if (inFlight.has(request.id) ||
|
|
8370
|
+
if (inFlight.has(request.id) || fs17.existsSync(responsePath(asyncDir, request.id))) return;
|
|
8156
8371
|
if (isDispatchOperation(request.operation) && request.claimedAt !== void 0) return;
|
|
8157
8372
|
const claimed = isDispatchOperation(request.operation) ? claimDispatchRequest(asyncDir, filePath, request) : { request, filePath };
|
|
8158
8373
|
if (!claimed) return;
|
|
@@ -8163,7 +8378,7 @@ function serviceExternalJobBridgeRequestFile(asyncDir, file) {
|
|
|
8163
8378
|
if (isDispatchOperation(claimedRequest.operation)) {
|
|
8164
8379
|
writeAtomicJson(dispatchClaimCompletedPath(claimed.filePath), { completedAt: Date.now() });
|
|
8165
8380
|
} else {
|
|
8166
|
-
|
|
8381
|
+
fs17.rmSync(claimed.filePath, { recursive: true, force: true });
|
|
8167
8382
|
}
|
|
8168
8383
|
}).catch((error2) => {
|
|
8169
8384
|
writeAtomicJson(responsePath(asyncDir, claimedRequest.id), {
|
|
@@ -8181,21 +8396,21 @@ function serviceExternalJobBridgeRequestFile(asyncDir, file) {
|
|
|
8181
8396
|
});
|
|
8182
8397
|
}
|
|
8183
8398
|
function serviceExternalJobDispatchClaim(asyncDir, file) {
|
|
8184
|
-
const claimDir =
|
|
8185
|
-
if (
|
|
8399
|
+
const claimDir = path16.join(requestDir(asyncDir), file);
|
|
8400
|
+
if (fs17.existsSync(dispatchClaimCompletedPath(claimDir))) return;
|
|
8186
8401
|
const owner = readClaimOwner(claimDir);
|
|
8187
|
-
if (!owner &&
|
|
8188
|
-
|
|
8402
|
+
if (!owner && fs17.existsSync(requestPath(asyncDir, file.replace(/\.claim$/, ""))) && !fs17.existsSync(responsePath(asyncDir, file.replace(/\.claim$/, "")))) {
|
|
8403
|
+
fs17.rmSync(claimDir, { recursive: true, force: true });
|
|
8189
8404
|
return;
|
|
8190
8405
|
}
|
|
8191
8406
|
if (!claimOwnerIsDead(owner)) return;
|
|
8192
8407
|
let request;
|
|
8193
8408
|
try {
|
|
8194
|
-
request = assertRequest(readJson2(
|
|
8409
|
+
request = assertRequest(readJson2(path16.join(claimDir, "request.json")), path16.join(claimDir, "request.json"));
|
|
8195
8410
|
} catch {
|
|
8196
8411
|
return;
|
|
8197
8412
|
}
|
|
8198
|
-
if (!isDispatchOperation(request.operation) ||
|
|
8413
|
+
if (!isDispatchOperation(request.operation) || fs17.existsSync(responsePath(asyncDir, request.id))) return;
|
|
8199
8414
|
const handle = readClaimHandle(request.provider, claimDir);
|
|
8200
8415
|
if (handle) {
|
|
8201
8416
|
writeAtomicJson(responsePath(asyncDir, request.id), {
|
|
@@ -8207,7 +8422,7 @@ function serviceExternalJobDispatchClaim(asyncDir, file) {
|
|
|
8207
8422
|
completedAt: Date.now()
|
|
8208
8423
|
});
|
|
8209
8424
|
writeAtomicJson(dispatchClaimCompletedPath(claimDir), { completedAt: Date.now() });
|
|
8210
|
-
|
|
8425
|
+
fs17.rmSync(requestPath(asyncDir, request.id), { force: true });
|
|
8211
8426
|
return;
|
|
8212
8427
|
}
|
|
8213
8428
|
writeAtomicJson(responsePath(asyncDir, request.id), {
|
|
@@ -8219,8 +8434,8 @@ function serviceExternalJobDispatchClaim(asyncDir, file) {
|
|
|
8219
8434
|
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
8435
|
completedAt: Date.now()
|
|
8221
8436
|
});
|
|
8222
|
-
|
|
8223
|
-
|
|
8437
|
+
fs17.rmSync(claimDir, { recursive: true, force: true });
|
|
8438
|
+
fs17.rmSync(requestPath(asyncDir, request.id), { force: true });
|
|
8224
8439
|
}
|
|
8225
8440
|
|
|
8226
8441
|
// src/shared/provider-diagnostics.ts
|
|
@@ -8349,6 +8564,14 @@ export {
|
|
|
8349
8564
|
COMPLETION_ACTION,
|
|
8350
8565
|
STATUS_ACTION,
|
|
8351
8566
|
createParentWake,
|
|
8567
|
+
writeWorkflowLaunchRecord,
|
|
8568
|
+
resumedRunStarted,
|
|
8569
|
+
workflowResumeClaimState,
|
|
8570
|
+
claimWorkflowResume,
|
|
8571
|
+
releaseWorkflowResumeClaim,
|
|
8572
|
+
recordWorkflowResumeRefusal,
|
|
8573
|
+
refusalSentence,
|
|
8574
|
+
hasWorkflowResumeRefusal,
|
|
8352
8575
|
incrementalChildCompletionTriggersTurn,
|
|
8353
8576
|
formatIncrementalChildCompletion,
|
|
8354
8577
|
parseSubagentNotifyContent,
|
|
@@ -8370,19 +8593,21 @@ export {
|
|
|
8370
8593
|
childSessionFactory,
|
|
8371
8594
|
childSessionFactoryModule,
|
|
8372
8595
|
disposeChildSessions,
|
|
8373
|
-
|
|
8596
|
+
WORKFLOW_RUNTIME_REPLACED_STOP_NOTICE,
|
|
8374
8597
|
runtimeReplacedAbortReason,
|
|
8375
8598
|
workflowStopCause,
|
|
8376
8599
|
workflowScriptDigest,
|
|
8377
8600
|
workflowChildFingerprint,
|
|
8378
8601
|
appendWorkflowChildJournal,
|
|
8602
|
+
claimedWorkflowRunRefusal,
|
|
8379
8603
|
findWorkflowReuseSource,
|
|
8380
8604
|
readNamedWorkflowReuseSource,
|
|
8381
8605
|
matchWorkflowReuse,
|
|
8606
|
+
workflowResumeRefusal,
|
|
8607
|
+
manualWorkflowResumeRefusalText,
|
|
8608
|
+
workflowResumeTargetId,
|
|
8609
|
+
prepareWorkflowResume,
|
|
8382
8610
|
flattenSubagentToolOptions,
|
|
8383
|
-
MAX_REMEMBERED_FOREGROUND_RUNS,
|
|
8384
|
-
persistForegroundRunHistory,
|
|
8385
|
-
restoreForegroundRunHistory,
|
|
8386
8611
|
listRetainedChildren,
|
|
8387
8612
|
formatRetainedChildren
|
|
8388
8613
|
};
|