pi-subagents 0.80.0 → 0.81.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 +28 -0
- package/bundle/agents.js +18 -0
- package/bundle/background-work.js +16 -0
- package/bundle/capability-ceiling.js +22 -0
- package/bundle/child-tool-plan.js +11 -0
- package/bundle/chunk-26C3WLYQ.js +8388 -0
- package/bundle/chunk-2XYWN7QJ.js +128 -0
- package/bundle/chunk-2ZSNH26H.js +4164 -0
- package/bundle/chunk-3DC5KBVP.js +235 -0
- package/bundle/chunk-3IF2PGW7.js +380 -0
- package/bundle/chunk-4QFZZV3O.js +57 -0
- package/bundle/chunk-563BHAZ5.js +1365 -0
- package/bundle/chunk-5LFLVSTM.js +260 -0
- package/bundle/chunk-62OCY2S4.js +14 -0
- package/bundle/chunk-74RT5YQE.js +326 -0
- package/bundle/chunk-7F6RKT6W.js +132 -0
- package/bundle/chunk-7J4ZXMY7.js +18 -0
- package/bundle/chunk-ARRKBB2B.js +179 -0
- package/bundle/chunk-BJN5LUQY.js +458 -0
- package/bundle/chunk-BPS2AGBZ.js +5421 -0
- package/bundle/chunk-CEKS3TFO.js +1337 -0
- package/bundle/chunk-CTPAE36Y.js +94 -0
- package/bundle/chunk-D4EHT5SJ.js +14 -0
- package/bundle/chunk-EYNJ6VL6.js +526 -0
- package/bundle/chunk-G5DKFLLI.js +1185 -0
- package/bundle/chunk-KSWQVGDW.js +1027 -0
- package/bundle/chunk-L5ZHE4L4.js +946 -0
- package/bundle/chunk-LSRUTHT7.js +57 -0
- package/bundle/chunk-N2BN7BF5.js +159 -0
- package/bundle/chunk-NF7IEKBH.js +490 -0
- package/bundle/chunk-NIXWDZZV.js +9 -0
- package/bundle/chunk-NNLASTQE.js +4082 -0
- package/bundle/chunk-TUWLXTAM.js +935 -0
- package/bundle/chunk-UKG4V45O.js +228 -0
- package/bundle/chunk-V2JWDB5U.js +753 -0
- package/bundle/chunk-V3BIUPLU.js +9163 -0
- package/bundle/chunk-V6MR5TOQ.js +270 -0
- package/bundle/chunk-VJHOELQP.js +8245 -0
- package/bundle/chunk-WSBNENFS.js +732 -0
- package/bundle/chunk-XMFLCKMQ.js +16664 -0
- package/bundle/chunk-YFCK7H2S.js +129 -0
- package/bundle/chunk-YYREO77V.js +137 -0
- package/bundle/chunk-Z7KVHQHL.js +173 -0
- package/bundle/chunk-ZBC44JJ3.js +8606 -0
- package/bundle/chunk-ZP3HODFH.js +175 -0
- package/bundle/control-channel.js +11 -0
- package/bundle/delegation.js +14 -0
- package/bundle/external-job-provider.js +20 -0
- package/bundle/external-runs.js +21 -0
- package/bundle/index.js +30 -0
- package/bundle/inspectors.js +8 -0
- package/bundle/intercom-bridge.js +11 -0
- package/bundle/preflight.js +425 -0
- package/bundle/project-panes.js +28 -0
- package/bundle/required-child-extensions.js +6 -0
- package/bundle/shared-types.js +8 -0
- package/bundle/workflow-resources.js +7 -0
- package/docs/extension-api.md +27 -0
- package/docs/observability.md +6 -2
- package/package.json +18 -18
- package/src/agents/frontmatter.d.ts +2 -0
- package/src/agents/frontmatter.js +9 -2
- package/src/api/delegation.d.ts +20 -0
- package/src/extension/index.js +27 -7
- package/src/runs/background/run-child-session.js +18 -0
- package/src/runs/background/stale-run-reconciler.js +2 -0
- package/src/runs/foreground/execution.js +39 -2
- package/src/runs/shared/structured-output.d.ts +1 -0
- package/src/runs/shared/structured-output.js +1 -0
- package/src/shared/accessible-dir.js +4 -1
- package/src/shared/provider-diagnostics.d.ts +9 -0
- package/src/shared/provider-diagnostics.js +75 -0
- package/src/shared/types.d.ts +3 -0
- package/src/slash/delegation-adapters.d.ts +3 -1
- package/src/slash/delegation-adapters.js +7 -0
- package/src/slash/prompt-template-bridge.js +3 -1
- package/src/slash/subagents-admin.d.ts +1 -0
- package/src/slash/subagents-admin.js +1 -1
- package/src/tui/fleet-layout.d.ts +20 -0
- package/src/tui/fleet-layout.js +17 -0
- package/src/tui/fleet-viewport.d.ts +12 -0
- package/src/tui/fleet-viewport.js +35 -0
- package/src/tui/fleet.d.ts +5 -6
- package/src/tui/fleet.js +69 -51
- package/src/workflows/structured-workflow-scripts.d.ts +1 -0
- package/src/workflows/structured-workflow-scripts.js +5 -4
- package/src/workflows/workflow-resources.js +2 -2
|
@@ -0,0 +1,1027 @@
|
|
|
1
|
+
import {
|
|
2
|
+
contextModeLabel,
|
|
3
|
+
findAsyncRunPrefixMatches,
|
|
4
|
+
formatActivityLabel,
|
|
5
|
+
formatAsyncRunOutputPath,
|
|
6
|
+
formatAsyncRunProgressLabel,
|
|
7
|
+
formatNestedRunStatusLines,
|
|
8
|
+
isStoppableAsyncStatusStep,
|
|
9
|
+
listAsyncRuns,
|
|
10
|
+
ownsRun,
|
|
11
|
+
resolveAsyncStatusChild,
|
|
12
|
+
stopStoppableAsyncStatusChildren,
|
|
13
|
+
summarizeContextModes,
|
|
14
|
+
workflowPreflightLaneForRuntimeKey
|
|
15
|
+
} from "./chunk-NNLASTQE.js";
|
|
16
|
+
import {
|
|
17
|
+
assertSafeNestedId,
|
|
18
|
+
findNestedRunMatchesById,
|
|
19
|
+
readActiveRunToolCallIndex,
|
|
20
|
+
readProcessTerminal,
|
|
21
|
+
readTerminalRunToolCallIndex,
|
|
22
|
+
reconcileAsyncRun,
|
|
23
|
+
resultCandidateFilesForToolCall,
|
|
24
|
+
resultFilePath,
|
|
25
|
+
resultPayloadFileForSessionRun,
|
|
26
|
+
resultPayloadPathForIndexedRun,
|
|
27
|
+
resultPayloadPathForSessionRun,
|
|
28
|
+
updateActiveRunIndex,
|
|
29
|
+
writeAsyncResultFile
|
|
30
|
+
} from "./chunk-VJHOELQP.js";
|
|
31
|
+
import {
|
|
32
|
+
deliverStopRequest,
|
|
33
|
+
stopInboxClosedPath
|
|
34
|
+
} from "./chunk-BJN5LUQY.js";
|
|
35
|
+
import {
|
|
36
|
+
SUBAGENT_STOPPED_MESSAGE,
|
|
37
|
+
formatDuration,
|
|
38
|
+
formatModelThinking,
|
|
39
|
+
formatTokens,
|
|
40
|
+
readStatus,
|
|
41
|
+
shortenPath,
|
|
42
|
+
writeAtomicJson
|
|
43
|
+
} from "./chunk-ZBC44JJ3.js";
|
|
44
|
+
import {
|
|
45
|
+
DIRS
|
|
46
|
+
} from "./chunk-5LFLVSTM.js";
|
|
47
|
+
import {
|
|
48
|
+
safeTerminalText,
|
|
49
|
+
truncateDisplayText
|
|
50
|
+
} from "./chunk-YFCK7H2S.js";
|
|
51
|
+
|
|
52
|
+
// src/shared/completion-owner.ts
|
|
53
|
+
import { randomUUID } from "node:crypto";
|
|
54
|
+
var COMPLETION_OWNER_KEY = /* @__PURE__ */ Symbol.for("pi-subagents.completion-owner-id");
|
|
55
|
+
function currentCompletionOwnerId() {
|
|
56
|
+
const runtime = globalThis;
|
|
57
|
+
runtime[COMPLETION_OWNER_KEY] ??= randomUUID();
|
|
58
|
+
return runtime[COMPLETION_OWNER_KEY];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/workflows/chat-progress.ts
|
|
62
|
+
import { spawnSync } from "node:child_process";
|
|
63
|
+
import * as fs from "node:fs";
|
|
64
|
+
import * as path from "node:path";
|
|
65
|
+
var WORKFLOW_CHAT_PROGRESS_MODES = ["auto", "off", "live-card"];
|
|
66
|
+
function git(cwd, args) {
|
|
67
|
+
const result = spawnSync("git", ["-C", cwd, ...args], { encoding: "utf-8", windowsHide: true });
|
|
68
|
+
if (result.status !== 0) return void 0;
|
|
69
|
+
const output = result.stdout.trim();
|
|
70
|
+
return output || void 0;
|
|
71
|
+
}
|
|
72
|
+
function realPath(value) {
|
|
73
|
+
try {
|
|
74
|
+
return fs.realpathSync.native(value);
|
|
75
|
+
} catch {
|
|
76
|
+
return path.resolve(value);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function resolveGitRepositoryIdentity(cwd) {
|
|
80
|
+
if (git(cwd, ["rev-parse", "--is-inside-work-tree"]) !== "true") return void 0;
|
|
81
|
+
const root = git(cwd, ["rev-parse", "--show-toplevel"]);
|
|
82
|
+
const commonDir = git(cwd, ["rev-parse", "--git-common-dir"]);
|
|
83
|
+
if (!root || !commonDir) return void 0;
|
|
84
|
+
const commonDirPath = path.isAbsolute(commonDir) ? commonDir : [path.resolve(cwd, commonDir), path.resolve(root, commonDir)].find((candidate) => fs.existsSync(candidate)) ?? path.resolve(root, commonDir);
|
|
85
|
+
return {
|
|
86
|
+
root: realPath(root),
|
|
87
|
+
commonDir: realPath(commonDirPath)
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function normalizeRequestedMode(value) {
|
|
91
|
+
if (value === void 0) return { mode: "auto" };
|
|
92
|
+
if (typeof value !== "string" || !WORKFLOW_CHAT_PROGRESS_MODES.includes(value)) {
|
|
93
|
+
return { error: `chatProgress must be one of: ${WORKFLOW_CHAT_PROGRESS_MODES.join(", ")}.` };
|
|
94
|
+
}
|
|
95
|
+
return { mode: value };
|
|
96
|
+
}
|
|
97
|
+
function resolveWorkflowChatProgress(input) {
|
|
98
|
+
const requested = normalizeRequestedMode(input.requested);
|
|
99
|
+
if (requested.error) return { error: requested.error };
|
|
100
|
+
const parentIdentity = resolveGitRepositoryIdentity(input.parentCwd);
|
|
101
|
+
const workflowIdentity = resolveGitRepositoryIdentity(input.workflowCwd);
|
|
102
|
+
const sameRepo = !!(parentIdentity && workflowIdentity && (parentIdentity.commonDir === workflowIdentity.commonDir || parentIdentity.root === workflowIdentity.root));
|
|
103
|
+
const repoLabel = workflowIdentity ? path.basename(workflowIdentity.root) : void 0;
|
|
104
|
+
const repoRelation = sameRepo ? "same" : "other";
|
|
105
|
+
const requestedMode = requested.mode ?? "auto";
|
|
106
|
+
let mode;
|
|
107
|
+
if (requestedMode === "auto") mode = sameRepo && !input.background ? "live-card" : "off";
|
|
108
|
+
else mode = requestedMode;
|
|
109
|
+
if (mode === "live-card" && !sameRepo) return { error: "chatProgress: 'live-card' is only available for workflow script runs in the same Git repository." };
|
|
110
|
+
if (mode === "live-card" && input.background) return { error: "chatProgress: 'live-card' is unavailable for async workflow scripts. Async workflows have no inline live card; omit chatProgress or use auto/off. Use async:false only when the parent must block." };
|
|
111
|
+
return { projection: { mode, repoRelation, ...repoLabel ? { repoLabel } : {} } };
|
|
112
|
+
}
|
|
113
|
+
function cleanLabel(value) {
|
|
114
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
115
|
+
}
|
|
116
|
+
function buildWorkflowChatProgressRows(trace, preflight) {
|
|
117
|
+
const rows = /* @__PURE__ */ new Map();
|
|
118
|
+
for (const entry of trace) {
|
|
119
|
+
if (entry.operation !== "run") continue;
|
|
120
|
+
const existing = rows.get(entry.key);
|
|
121
|
+
if (entry.state === "reused") {
|
|
122
|
+
if (existing) {
|
|
123
|
+
const label2 = cleanLabel(entry.label);
|
|
124
|
+
const phase2 = cleanLabel(entry.phase);
|
|
125
|
+
if (label2) existing.label = label2;
|
|
126
|
+
if (phase2) existing.phase = phase2;
|
|
127
|
+
}
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const lane = workflowPreflightLaneForRuntimeKey(preflight, entry.key, [entry.generatedLaneKey]);
|
|
131
|
+
const next = existing ?? { key: entry.key, state: "running" };
|
|
132
|
+
if (lane && !next.preflight) next.preflight = lane;
|
|
133
|
+
next.state = entry.state === "completed" ? "complete" : entry.state === "failed" ? "failed" : entry.state === "detached" ? "detached" : entry.state === "stopped" ? "stopped" : "running";
|
|
134
|
+
const label = cleanLabel(entry.label);
|
|
135
|
+
const phase = cleanLabel(entry.phase);
|
|
136
|
+
if (label) next.label = label;
|
|
137
|
+
if (phase) next.phase = phase;
|
|
138
|
+
if (entry.runId === void 0) delete next.runId;
|
|
139
|
+
else next.runId = entry.runId;
|
|
140
|
+
if (entry.durationMs === void 0) delete next.durationMs;
|
|
141
|
+
else next.durationMs = entry.durationMs;
|
|
142
|
+
if (entry.error === void 0) delete next.error;
|
|
143
|
+
else next.error = entry.error;
|
|
144
|
+
rows.set(entry.key, next);
|
|
145
|
+
}
|
|
146
|
+
return [...rows.values()];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/runs/background/fleet-view.ts
|
|
150
|
+
import * as fs3 from "node:fs";
|
|
151
|
+
import * as path3 from "node:path";
|
|
152
|
+
|
|
153
|
+
// src/shared/session-file-trust.ts
|
|
154
|
+
import * as fs2 from "node:fs";
|
|
155
|
+
import * as path2 from "node:path";
|
|
156
|
+
function pathWithin(base, candidate) {
|
|
157
|
+
const resolvedBase = path2.resolve(base);
|
|
158
|
+
const resolvedCandidate = path2.resolve(candidate);
|
|
159
|
+
return resolvedCandidate === resolvedBase || resolvedCandidate.startsWith(`${resolvedBase}${path2.sep}`);
|
|
160
|
+
}
|
|
161
|
+
function isTrustedRecordedSessionFile(realPath2, recordedFiles, sessionsBase) {
|
|
162
|
+
if (!sessionsBase || recordedFiles.length === 0) return false;
|
|
163
|
+
try {
|
|
164
|
+
const realSessionsBase = fs2.realpathSync(sessionsBase);
|
|
165
|
+
if (!pathWithin(realSessionsBase, realPath2)) return false;
|
|
166
|
+
return recordedFiles.some((file) => fs2.realpathSync(path2.resolve(file)) === realPath2);
|
|
167
|
+
} catch {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// src/runs/background/fleet-view.ts
|
|
173
|
+
var DEFAULT_TRANSCRIPT_LINES = 80;
|
|
174
|
+
var MAX_TRANSCRIPT_LINES = 500;
|
|
175
|
+
var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
|
|
176
|
+
var MAX_TRANSCRIPT_LINE_CHARS = 2e3;
|
|
177
|
+
var MAX_TRANSCRIPT_BODY_BYTES = 32 * 1024;
|
|
178
|
+
var TRANSCRIPT_LINE_OMISSION = "\u2026 [content omitted]";
|
|
179
|
+
var TRANSCRIPT_BODY_OMISSION = " \u2026 [earlier lines omitted]";
|
|
180
|
+
function transcriptLineLimit(value) {
|
|
181
|
+
if (value === void 0) return DEFAULT_TRANSCRIPT_LINES;
|
|
182
|
+
if (!Number.isFinite(value)) return DEFAULT_TRANSCRIPT_LINES;
|
|
183
|
+
return Math.max(1, Math.min(MAX_TRANSCRIPT_LINES, Math.trunc(value)));
|
|
184
|
+
}
|
|
185
|
+
function uniqueStrings(values) {
|
|
186
|
+
const seen = /* @__PURE__ */ new Set();
|
|
187
|
+
const result = [];
|
|
188
|
+
for (const value of values) {
|
|
189
|
+
if (!value || seen.has(value)) continue;
|
|
190
|
+
seen.add(value);
|
|
191
|
+
result.push(value);
|
|
192
|
+
}
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
function fleetChildDisplayName(child, fallback = "subagent") {
|
|
196
|
+
return child.sessionName?.trim() || child.agent || fallback;
|
|
197
|
+
}
|
|
198
|
+
function fleetStepDisplayName(step) {
|
|
199
|
+
return step.sessionName?.trim() || (step.label ? `${step.label} (${step.agent})` : step.agent);
|
|
200
|
+
}
|
|
201
|
+
function resolveMaybeRelative(asyncDir, filePath) {
|
|
202
|
+
if (!filePath) return void 0;
|
|
203
|
+
return path3.resolve(asyncDir, filePath);
|
|
204
|
+
}
|
|
205
|
+
function pathWithin2(base, candidate) {
|
|
206
|
+
const resolvedBase = path3.resolve(base);
|
|
207
|
+
const resolvedCandidate = path3.resolve(candidate);
|
|
208
|
+
return resolvedCandidate === resolvedBase || resolvedCandidate.startsWith(`${resolvedBase}${path3.sep}`);
|
|
209
|
+
}
|
|
210
|
+
function getErrorMessage(error) {
|
|
211
|
+
return error instanceof Error ? error.message : String(error);
|
|
212
|
+
}
|
|
213
|
+
function isNotFoundError(error) {
|
|
214
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
215
|
+
}
|
|
216
|
+
function readTextTail(filePath, maxLines) {
|
|
217
|
+
let stat;
|
|
218
|
+
try {
|
|
219
|
+
stat = fs3.statSync(filePath);
|
|
220
|
+
} catch (error) {
|
|
221
|
+
if (isNotFoundError(error)) return { path: filePath, lines: [], truncated: false };
|
|
222
|
+
return { path: filePath, lines: [], truncated: false, error: getErrorMessage(error) };
|
|
223
|
+
}
|
|
224
|
+
if (stat.size === 0) return { path: filePath, lines: [], truncated: false };
|
|
225
|
+
let fd;
|
|
226
|
+
try {
|
|
227
|
+
const bytesToRead = Math.min(stat.size, TRANSCRIPT_TAIL_BYTES);
|
|
228
|
+
const start = stat.size - bytesToRead;
|
|
229
|
+
const buffer = Buffer.alloc(bytesToRead);
|
|
230
|
+
fd = fs3.openSync(filePath, "r");
|
|
231
|
+
const bytesRead = fs3.readSync(fd, buffer, 0, bytesToRead, start);
|
|
232
|
+
const content = buffer.subarray(0, bytesRead).toString("utf-8");
|
|
233
|
+
let lines = content.split(/\r?\n/);
|
|
234
|
+
if (start > 0 && lines.length > 0) lines = lines.slice(1);
|
|
235
|
+
if (lines.at(-1) === "") lines = lines.slice(0, -1);
|
|
236
|
+
return { path: filePath, lines: lines.slice(-maxLines), truncated: start > 0 || lines.length > maxLines };
|
|
237
|
+
} catch (error) {
|
|
238
|
+
return { path: filePath, lines: [], truncated: false, error: getErrorMessage(error) };
|
|
239
|
+
} finally {
|
|
240
|
+
if (fd !== void 0) fs3.closeSync(fd);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function readContainedTextTail(filePath, maxLines, trustedRoots, label, trustedFiles = [], trustedFileRoot) {
|
|
244
|
+
if (trustedRoots.length === 0 && (!trustedFileRoot || trustedFiles.length === 0)) return { path: filePath, lines: [], truncated: false, error: `Refusing to read ${label} transcript path without a trusted root: ${filePath}` };
|
|
245
|
+
const resolvedPath = path3.resolve(filePath);
|
|
246
|
+
const recordedCandidate = trustedFileRoot && pathWithin2(trustedFileRoot, resolvedPath) && trustedFiles.some((file) => path3.resolve(file) === resolvedPath);
|
|
247
|
+
if (!trustedRoots.some((root) => pathWithin2(root, resolvedPath)) && !recordedCandidate) {
|
|
248
|
+
return { path: filePath, lines: [], truncated: false, error: `Refusing to read ${label} transcript path outside trusted roots: ${filePath}` };
|
|
249
|
+
}
|
|
250
|
+
let lstat;
|
|
251
|
+
try {
|
|
252
|
+
lstat = fs3.lstatSync(resolvedPath);
|
|
253
|
+
} catch (error) {
|
|
254
|
+
if (isNotFoundError(error)) return { path: filePath, lines: [], truncated: false };
|
|
255
|
+
return { path: filePath, lines: [], truncated: false, error: getErrorMessage(error) };
|
|
256
|
+
}
|
|
257
|
+
if (lstat.isSymbolicLink()) return { path: filePath, lines: [], truncated: false, error: `Refusing to read symlink ${label} transcript path: ${filePath}` };
|
|
258
|
+
if (!lstat.isFile()) return { path: filePath, lines: [], truncated: false, error: `Refusing to read non-file ${label} transcript path: ${filePath}` };
|
|
259
|
+
let realPath2;
|
|
260
|
+
let realRoots;
|
|
261
|
+
try {
|
|
262
|
+
realPath2 = fs3.realpathSync(resolvedPath);
|
|
263
|
+
realRoots = trustedRoots.filter((root) => fs3.existsSync(root)).map((root) => fs3.realpathSync(root));
|
|
264
|
+
} catch (error) {
|
|
265
|
+
return { path: filePath, lines: [], truncated: false, error: getErrorMessage(error) };
|
|
266
|
+
}
|
|
267
|
+
if (!realRoots.some((root) => pathWithin2(root, realPath2)) && !isTrustedRecordedSessionFile(realPath2, trustedFiles, trustedFileRoot)) {
|
|
268
|
+
return { path: filePath, lines: [], truncated: false, error: `Refusing to read ${label} transcript path outside trusted roots: ${filePath}` };
|
|
269
|
+
}
|
|
270
|
+
return readTextTail(resolvedPath, maxLines);
|
|
271
|
+
}
|
|
272
|
+
function stringifyJsonPreview(value, maxLength = 240) {
|
|
273
|
+
let raw;
|
|
274
|
+
if (typeof value === "string") raw = value;
|
|
275
|
+
else raw = JSON.stringify(value);
|
|
276
|
+
return raw.length > maxLength ? `${raw.slice(0, maxLength)}\u2026` : raw;
|
|
277
|
+
}
|
|
278
|
+
function sessionMessageParts(record) {
|
|
279
|
+
if (!record || typeof record !== "object") return [];
|
|
280
|
+
const outer = record;
|
|
281
|
+
const message = outer.message && typeof outer.message === "object" ? outer.message : outer;
|
|
282
|
+
const role = typeof message.role === "string" ? message.role : void 0;
|
|
283
|
+
if (!role) return [];
|
|
284
|
+
const content = message.content;
|
|
285
|
+
if (typeof content === "string") return content.trim() ? [{ role, kind: "text", text: content }] : [];
|
|
286
|
+
if (!Array.isArray(content)) return [];
|
|
287
|
+
const parts = [];
|
|
288
|
+
for (const part of content) {
|
|
289
|
+
if (!part || typeof part !== "object") continue;
|
|
290
|
+
const entry = part;
|
|
291
|
+
if (typeof entry.text === "string") {
|
|
292
|
+
if (entry.text.trim()) parts.push({ role, kind: "text", text: entry.text });
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
if (entry.type === "toolCall" || entry.type === "tool_call") {
|
|
296
|
+
const name = typeof entry.name === "string" ? entry.name : typeof entry.toolName === "string" ? entry.toolName : "tool";
|
|
297
|
+
parts.push({ role, kind: "toolCall", name, text: `[tool: ${name}${entry.args === void 0 ? "" : ` ${stringifyJsonPreview(entry.args)}`}]` });
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (entry.type === "toolResult" || entry.type === "tool_result") {
|
|
301
|
+
const name = typeof entry.name === "string" ? entry.name : typeof entry.toolName === "string" ? entry.toolName : void 0;
|
|
302
|
+
parts.push({ role, kind: "toolResult", ...name ? { name } : {}, ...entry.isError === true ? { isError: true } : {}, text: `[tool result${entry.result === void 0 ? "" : `: ${stringifyJsonPreview(entry.result)}`}]` });
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (entry.content !== void 0) {
|
|
306
|
+
const preview = stringifyJsonPreview(entry.content);
|
|
307
|
+
if (preview.trim()) parts.push({ role, kind: "text", text: preview });
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return parts;
|
|
311
|
+
}
|
|
312
|
+
function sessionMessageLine(record) {
|
|
313
|
+
const parts = sessionMessageParts(record);
|
|
314
|
+
if (parts.length === 0) return void 0;
|
|
315
|
+
const text = parts.map((part) => part.text).join("\n").trim();
|
|
316
|
+
if (!text) return void 0;
|
|
317
|
+
return `${parts[0].role}: ${text}`;
|
|
318
|
+
}
|
|
319
|
+
function readSessionMessagesTail(sessionFile, maxMessages, trustedRoots, trustedFiles = [], trustedFileRoot) {
|
|
320
|
+
const tail = readContainedTextTail(sessionFile, Math.max(maxMessages * 4, maxMessages), trustedRoots, "session", trustedFiles, trustedFileRoot);
|
|
321
|
+
const warnings = [];
|
|
322
|
+
if (tail.error) warnings.push(`Session read failed for ${sessionFile}: ${tail.error}`);
|
|
323
|
+
const parsedMessages = [];
|
|
324
|
+
let malformed = 0;
|
|
325
|
+
let recordIndex = 0;
|
|
326
|
+
for (const line of tail.lines) {
|
|
327
|
+
if (!line.trim()) continue;
|
|
328
|
+
try {
|
|
329
|
+
const parsed = JSON.parse(line);
|
|
330
|
+
for (const part of sessionMessageParts(parsed)) parsedMessages.push({ ...part, recordIndex });
|
|
331
|
+
} catch {
|
|
332
|
+
malformed++;
|
|
333
|
+
}
|
|
334
|
+
recordIndex++;
|
|
335
|
+
}
|
|
336
|
+
if (malformed > 0) warnings.push(`Skipped ${malformed} malformed session tail line${malformed === 1 ? "" : "s"}.`);
|
|
337
|
+
const messages = parsedMessages.slice(-maxMessages);
|
|
338
|
+
return { messages, warnings, truncated: tail.truncated || parsedMessages.length > messages.length };
|
|
339
|
+
}
|
|
340
|
+
function readSessionTranscriptTail(sessionFile, maxLines, trustedRoots, trustedFiles = [], trustedFileRoot) {
|
|
341
|
+
const tail = readContainedTextTail(sessionFile, Math.max(maxLines * 4, maxLines), trustedRoots, "session", trustedFiles, trustedFileRoot);
|
|
342
|
+
const warnings = [];
|
|
343
|
+
if (tail.error) warnings.push(`Session read failed for ${sessionFile}: ${tail.error}`);
|
|
344
|
+
const lines = [];
|
|
345
|
+
let malformed = 0;
|
|
346
|
+
for (const line of tail.lines) {
|
|
347
|
+
if (!line.trim()) continue;
|
|
348
|
+
try {
|
|
349
|
+
const parsed = JSON.parse(line);
|
|
350
|
+
const messageLine = sessionMessageLine(parsed);
|
|
351
|
+
if (messageLine) lines.push(...messageLine.split(/\r?\n/));
|
|
352
|
+
} catch {
|
|
353
|
+
malformed++;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (malformed > 0) warnings.push(`Skipped ${malformed} malformed session tail line${malformed === 1 ? "" : "s"}.`);
|
|
357
|
+
return { lines: lines.slice(-maxLines), warnings };
|
|
358
|
+
}
|
|
359
|
+
function formatActivityFacts(input) {
|
|
360
|
+
const facts = [];
|
|
361
|
+
if (input.currentTool && input.currentToolStartedAt !== void 0) facts.push(`tool ${input.currentTool} ${formatDuration(Math.max(0, Date.now() - input.currentToolStartedAt))}`);
|
|
362
|
+
else if (input.currentTool) facts.push(`tool ${input.currentTool}`);
|
|
363
|
+
if (input.currentPath) facts.push(shortenPath(input.currentPath));
|
|
364
|
+
if (input.turnCount !== void 0) facts.push(`${input.turnCount} turns`);
|
|
365
|
+
if (input.toolCount !== void 0) facts.push(`${input.toolCount} tools`);
|
|
366
|
+
if (input.tokens?.total) facts.push(`${formatTokens(input.tokens.total)} tok`);
|
|
367
|
+
const activity = formatActivityLabel(input.lastActivityAt, input.activityState);
|
|
368
|
+
return activity || facts.length ? [activity, ...facts].filter(Boolean).join(" | ") : void 0;
|
|
369
|
+
}
|
|
370
|
+
function foregroundModeName(control) {
|
|
371
|
+
const currentDisplayName = control.sessionName?.trim() || control.currentAgent;
|
|
372
|
+
if (control.mode === "single" && currentDisplayName) return currentDisplayName;
|
|
373
|
+
return control.mode;
|
|
374
|
+
}
|
|
375
|
+
function formatForegroundFleetLines(controls) {
|
|
376
|
+
if (controls.length === 0) return [];
|
|
377
|
+
const lines = ["Foreground runs:"];
|
|
378
|
+
const ordered = [...controls].sort((left, right) => right.updatedAt - left.updatedAt);
|
|
379
|
+
for (const control of ordered) {
|
|
380
|
+
const activity = formatActivityFacts({
|
|
381
|
+
activityState: control.currentActivityState,
|
|
382
|
+
lastActivityAt: control.lastActivityAt,
|
|
383
|
+
currentTool: control.currentTool,
|
|
384
|
+
currentToolStartedAt: control.currentToolStartedAt,
|
|
385
|
+
currentPath: control.currentPath,
|
|
386
|
+
turnCount: control.turnCount,
|
|
387
|
+
toolCount: control.toolCount,
|
|
388
|
+
...control.tokens !== void 0 ? { tokens: { total: control.tokens } } : {}
|
|
389
|
+
});
|
|
390
|
+
const currentDisplayName = control.sessionName?.trim() || control.currentAgent;
|
|
391
|
+
const current = currentDisplayName ? ` | ${currentDisplayName}${control.currentIndex !== void 0 ? ` #${control.currentIndex}` : ""}` : "";
|
|
392
|
+
lines.push(`- ${control.runId} | running | ${foregroundModeName(control)}${current}${activity ? ` | ${activity}` : ""}`);
|
|
393
|
+
lines.push(` status: subagent({ action: "status", id: "${control.runId}" })`);
|
|
394
|
+
lines.push(" transcript: live in the expanded foreground result; persisted session transcript appears after completion when sessions are enabled.");
|
|
395
|
+
lines.push(...formatNestedRunStatusLines(control.nestedChildren, { indent: " ", commandHints: true, maxLines: 12 }));
|
|
396
|
+
}
|
|
397
|
+
return lines;
|
|
398
|
+
}
|
|
399
|
+
function formatDetachedForegroundFleetLines(runs) {
|
|
400
|
+
if (runs.length === 0) return [];
|
|
401
|
+
const lines = ["Detached foreground runs:"];
|
|
402
|
+
const ordered = [...runs].sort((left, right) => right.updatedAt - left.updatedAt);
|
|
403
|
+
for (const run of ordered) {
|
|
404
|
+
const detachedChildren = run.children.filter((child) => child.status === "detached");
|
|
405
|
+
const childSummary = detachedChildren.map((child) => `${fleetChildDisplayName(child)} #${child.index}`).join(", ");
|
|
406
|
+
lines.push(`- ${run.runId} | detached | ${run.mode}${childSummary ? ` | ${childSummary}` : ""}`);
|
|
407
|
+
lines.push(` status: subagent({ action: "status", id: "${run.runId}" })`);
|
|
408
|
+
lines.push(` recovery: reply to the supervisor request first, then wait with bg_wait({ id: "${run.runId}" }); do not resume or launch a replacement while any child remains detached.`);
|
|
409
|
+
}
|
|
410
|
+
return lines;
|
|
411
|
+
}
|
|
412
|
+
function externalCliStepFacts(step, now) {
|
|
413
|
+
if (step.runner?.type !== "external-cli") return void 0;
|
|
414
|
+
const process = step.externalProcess;
|
|
415
|
+
const elapsed = process ? process.durationMs ?? Math.max(0, (process.endedAt ?? now) - process.startedAt) : void 0;
|
|
416
|
+
return `external-cli${elapsed !== void 0 ? ` \xB7 ${formatDuration(elapsed)}` : ""}`;
|
|
417
|
+
}
|
|
418
|
+
function formatAsyncFleetLines(runs, now = Date.now()) {
|
|
419
|
+
if (runs.length === 0) return [];
|
|
420
|
+
const lines = ["Async runs:"];
|
|
421
|
+
for (const run of runs) {
|
|
422
|
+
const progress = formatAsyncRunProgressLabel(run);
|
|
423
|
+
const activity = formatActivityFacts(run);
|
|
424
|
+
const cwd = run.cwd ? shortenPath(run.cwd) : shortenPath(run.asyncDir);
|
|
425
|
+
const pending = run.pendingAppends ? ` | ${run.pendingAppends} pending append${run.pendingAppends === 1 ? "" : "s"}` : "";
|
|
426
|
+
const runContext = contextModeLabel(run.context);
|
|
427
|
+
lines.push(`- ${run.id} | ${run.state}${activity ? ` | ${activity}` : ""} | ${run.mode}${runContext ? ` ${runContext}` : ""} | ${progress}${pending} | ${cwd}`);
|
|
428
|
+
lines.push(` status: subagent({ action: "status", id: "${run.id}" })`);
|
|
429
|
+
lines.push(` transcript: subagent({ action: "status", id: "${run.id}", options: { view: "transcript" } })`);
|
|
430
|
+
for (const step of run.steps) {
|
|
431
|
+
const display = fleetStepDisplayName(step);
|
|
432
|
+
const stepContext = contextModeLabel(step.context);
|
|
433
|
+
const phase = step.phase ? `[${step.phase}] ` : "";
|
|
434
|
+
const stepActivity = formatActivityFacts(step);
|
|
435
|
+
const modelThinking = formatModelThinking(step.model, step.thinking);
|
|
436
|
+
const parts = [`${step.index}. ${phase}${display}${stepContext ? ` ${stepContext}` : ""}`, step.status, externalCliStepFacts(step, now), stepActivity, modelThinking].filter(Boolean);
|
|
437
|
+
lines.push(` ${parts.join(" | ")}`);
|
|
438
|
+
const output = path3.join(run.asyncDir, `output-${step.index}.log`);
|
|
439
|
+
if (fs3.existsSync(output)) lines.push(` output: ${shortenPath(output)}`);
|
|
440
|
+
if (step.sessionFile) lines.push(` session: ${shortenPath(step.sessionFile)}`);
|
|
441
|
+
if (step.status === "running" || step.recentOutput?.length || fs3.existsSync(output)) {
|
|
442
|
+
lines.push(` transcript: subagent({ action: "status", id: "${run.id}", options: { index: ${step.index}, view: "transcript" } })`);
|
|
443
|
+
}
|
|
444
|
+
lines.push(...formatNestedRunStatusLines(step.children, { indent: " ", commandHints: true, maxLines: 12 }));
|
|
445
|
+
}
|
|
446
|
+
const attached = new Set(run.steps.flatMap((step) => step.children?.map((child) => child.id) ?? []));
|
|
447
|
+
const unattached = run.nestedChildren?.filter((child) => !attached.has(child.id)) ?? [];
|
|
448
|
+
lines.push(...formatNestedRunStatusLines(unattached, { indent: " ", commandHints: true, maxLines: 12 }));
|
|
449
|
+
if (run.error) lines.push(` error: ${run.error}`);
|
|
450
|
+
for (const warning of run.nestedWarnings ?? []) lines.push(` warning: ${warning}`);
|
|
451
|
+
const outputPath = formatAsyncRunOutputPath(run);
|
|
452
|
+
if (outputPath) lines.push(` output: ${shortenPath(outputPath)}`);
|
|
453
|
+
if (run.sessionFile) lines.push(` session: ${shortenPath(run.sessionFile)}`);
|
|
454
|
+
}
|
|
455
|
+
return lines;
|
|
456
|
+
}
|
|
457
|
+
function inspectSubagentFleet(_params, deps = {}) {
|
|
458
|
+
if (deps.childSafe) {
|
|
459
|
+
return {
|
|
460
|
+
content: [{ type: "text", text: 'Child-safe subagent fleet view is unavailable without an explicit run id. Use subagent({ action: "status", id: "..." }) for the delegated run you can see.' }],
|
|
461
|
+
isError: true,
|
|
462
|
+
details: { mode: "management", results: [] }
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
let asyncRuns;
|
|
466
|
+
try {
|
|
467
|
+
asyncRuns = listAsyncRuns(deps.asyncDirRoot ?? DIRS.async, {
|
|
468
|
+
states: ["queued", "running"],
|
|
469
|
+
sessionId: deps.state?.currentSessionId ?? void 0,
|
|
470
|
+
resultsDir: deps.resultsDir ?? DIRS.results,
|
|
471
|
+
kill: deps.kill,
|
|
472
|
+
now: deps.now
|
|
473
|
+
});
|
|
474
|
+
} catch (error) {
|
|
475
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
476
|
+
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
477
|
+
}
|
|
478
|
+
const foregroundControls = deps.state ? [...deps.state.foregroundControls.values()] : [];
|
|
479
|
+
const activeForegroundIds = new Set(foregroundControls.map((control) => control.runId));
|
|
480
|
+
const detachedForegroundRuns = deps.state?.foregroundRuns ? [...deps.state.foregroundRuns.values()].filter((run) => run.sessionId === deps.state?.currentSessionId && !activeForegroundIds.has(run.runId) && run.children.some((child) => child.status === "detached")) : [];
|
|
481
|
+
const total = foregroundControls.length + detachedForegroundRuns.length + asyncRuns.length;
|
|
482
|
+
if (total === 0) {
|
|
483
|
+
return {
|
|
484
|
+
content: [{ type: "text", text: 'No active subagent fleet. Background runs that already finished are available through completion notifications or subagent({ action: "status", id: "..." }).' }],
|
|
485
|
+
details: { mode: "management", results: [] }
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
const lines = [`Subagent fleet: ${total} tracked`, ""];
|
|
489
|
+
const foregroundLines = formatForegroundFleetLines(foregroundControls);
|
|
490
|
+
if (foregroundLines.length) lines.push(...foregroundLines, "");
|
|
491
|
+
const detachedForegroundLines = formatDetachedForegroundFleetLines(detachedForegroundRuns);
|
|
492
|
+
if (detachedForegroundLines.length) lines.push(...detachedForegroundLines, "");
|
|
493
|
+
const asyncLines = formatAsyncFleetLines(asyncRuns, deps.now?.() ?? Date.now());
|
|
494
|
+
if (asyncLines.length) lines.push(...asyncLines, "");
|
|
495
|
+
lines.push("Commands:");
|
|
496
|
+
lines.push(' Refresh fleet: subagent({ action: "status", options: { view: "fleet" } })');
|
|
497
|
+
lines.push(' Tail run transcript: subagent({ action: "status", id: "<run-id>", options: { view: "transcript" } })');
|
|
498
|
+
lines.push(' Tail child transcript: subagent({ action: "status", id: "<run-id>", options: { index: 0, view: "transcript" } })');
|
|
499
|
+
return { content: [{ type: "text", text: lines.join("\n").trimEnd() }], details: { mode: "management", results: [] } };
|
|
500
|
+
}
|
|
501
|
+
function validateTranscriptIndex(index, steps) {
|
|
502
|
+
if (index === void 0) return void 0;
|
|
503
|
+
if (!Number.isInteger(index)) throw new Error("Transcript index must be an integer.");
|
|
504
|
+
if (index < 0 || index >= steps.length) throw new Error(`Transcript index ${index} is out of range for ${steps.length} child step${steps.length === 1 ? "" : "s"}.`);
|
|
505
|
+
return index;
|
|
506
|
+
}
|
|
507
|
+
function selectTranscriptStep(status, options) {
|
|
508
|
+
const steps = status.steps ?? [];
|
|
509
|
+
let selectedIndex = validateTranscriptIndex(options.index, steps);
|
|
510
|
+
if (selectedIndex === void 0) {
|
|
511
|
+
if (status.state === "running" && typeof status.currentStep === "number" && status.currentStep >= 0 && status.currentStep < steps.length) {
|
|
512
|
+
selectedIndex = status.currentStep;
|
|
513
|
+
} else if (steps.length === 1) {
|
|
514
|
+
selectedIndex = 0;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
const step = selectedIndex !== void 0 ? steps[selectedIndex] : void 0;
|
|
518
|
+
const hint = options.index === void 0 && steps.length > 1 ? `Tip: pass index to inspect a specific child transcript (${steps.map((candidate, index) => `${index}=${fleetChildDisplayName(candidate)}`).join(", ")}).` : void 0;
|
|
519
|
+
return { index: selectedIndex, step, hint };
|
|
520
|
+
}
|
|
521
|
+
function stepStateLine(mode, index, step) {
|
|
522
|
+
if (index === void 0 || !step) return void 0;
|
|
523
|
+
const modelThinking = formatModelThinking(step.model, step.thinking);
|
|
524
|
+
const context = contextModeLabel(step.context);
|
|
525
|
+
const parts = [
|
|
526
|
+
`${mode === "parallel" ? "Agent" : "Step"}: ${index} (${fleetChildDisplayName(step)})${context ? ` ${context}` : ""}`,
|
|
527
|
+
step.status,
|
|
528
|
+
formatActivityFacts(step),
|
|
529
|
+
modelThinking,
|
|
530
|
+
step.error ? `error: ${step.error}` : void 0
|
|
531
|
+
].filter(Boolean);
|
|
532
|
+
return parts.join(" | ");
|
|
533
|
+
}
|
|
534
|
+
function appendKnownArtifacts(lines, input) {
|
|
535
|
+
const artifacts = [];
|
|
536
|
+
for (const outputPath of input.outputPaths) artifacts.push(`Output: ${outputPath}`);
|
|
537
|
+
if (input.sessionFile) artifacts.push(`Session: ${input.sessionFile}`);
|
|
538
|
+
if (input.eventsPath) artifacts.push(`Events: ${input.eventsPath}`);
|
|
539
|
+
if (input.logPath) artifacts.push(`Log: ${input.logPath}`);
|
|
540
|
+
if (input.resultPath) artifacts.push(`Result: ${input.resultPath}`);
|
|
541
|
+
if (!artifacts.length) return;
|
|
542
|
+
lines.push("Artifacts:");
|
|
543
|
+
for (const artifact of artifacts) lines.push(` ${artifact}`);
|
|
544
|
+
}
|
|
545
|
+
function safeTranscriptLines(lines) {
|
|
546
|
+
return lines.map((line) => safeTerminalText(line)).join("\n");
|
|
547
|
+
}
|
|
548
|
+
function appendTranscriptBody(lines, sourceLabel, sourceLines, truncated) {
|
|
549
|
+
lines.push(`${sourceLabel}${truncated ? " (tail truncated)" : ""}:`);
|
|
550
|
+
if (sourceLines.length === 0) {
|
|
551
|
+
lines.push(" (no transcript lines available yet)");
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
const body = [];
|
|
555
|
+
let bytes = 0;
|
|
556
|
+
for (let index = sourceLines.length - 1; index >= 0; index--) {
|
|
557
|
+
const safe = safeTerminalText(` ${sourceLines[index]}`);
|
|
558
|
+
const lineLimit = MAX_TRANSCRIPT_LINE_CHARS + 2;
|
|
559
|
+
const line = safe.length <= lineLimit ? safe : truncateDisplayText(safe, lineLimit - TRANSCRIPT_LINE_OMISSION.length) + TRANSCRIPT_LINE_OMISSION;
|
|
560
|
+
const size = Buffer.byteLength(line) + 1;
|
|
561
|
+
if (bytes + size > MAX_TRANSCRIPT_BODY_BYTES) {
|
|
562
|
+
const markerBytes = Buffer.byteLength(TRANSCRIPT_BODY_OMISSION) + 1;
|
|
563
|
+
while (bytes + markerBytes > MAX_TRANSCRIPT_BODY_BYTES) bytes -= Buffer.byteLength(body.pop()) + 1;
|
|
564
|
+
body.push(TRANSCRIPT_BODY_OMISSION);
|
|
565
|
+
break;
|
|
566
|
+
}
|
|
567
|
+
body.push(line);
|
|
568
|
+
bytes += size;
|
|
569
|
+
}
|
|
570
|
+
lines.push(...body.reverse());
|
|
571
|
+
}
|
|
572
|
+
function resolveWorkflowTranscriptChild(status, asyncDir, options) {
|
|
573
|
+
if (status.mode !== "workflow" || options.index === void 0) return void 0;
|
|
574
|
+
const step = status.steps?.[options.index];
|
|
575
|
+
if (step?.async !== true || typeof step.runId !== "string" || !/^[A-Za-z0-9_-]+$/.test(step.runId) || step.runId === status.runId) return void 0;
|
|
576
|
+
try {
|
|
577
|
+
const asyncRoot = fs3.realpathSync(path3.dirname(asyncDir));
|
|
578
|
+
const childDir = path3.join(asyncRoot, step.runId);
|
|
579
|
+
if (fs3.realpathSync(childDir) !== childDir || fs3.lstatSync(path3.join(childDir, "status.json")).isSymbolicLink()) return void 0;
|
|
580
|
+
const childStatus = readStatus(childDir);
|
|
581
|
+
return childStatus?.runId === step.runId && childStatus.parentWorkflowRunId === status.runId && typeof step.workflowKey === "string" && step.workflowKey.length > 0 && childStatus.workflowKey === step.workflowKey && typeof status.sessionId === "string" && status.sessionId.length > 0 && childStatus.sessionId === status.sessionId ? { status: childStatus, asyncDir: childDir } : void 0;
|
|
582
|
+
} catch (error) {
|
|
583
|
+
if (isNotFoundError(error)) return void 0;
|
|
584
|
+
throw error;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
function formatAsyncRunTranscript(status, asyncDir, options = {}) {
|
|
588
|
+
const workflowChild = resolveWorkflowTranscriptChild(status, asyncDir, options);
|
|
589
|
+
if (workflowChild) return formatAsyncRunTranscript(workflowChild.status, workflowChild.asyncDir, { ...options, index: void 0 });
|
|
590
|
+
const lineLimit = transcriptLineLimit(options.lines);
|
|
591
|
+
const selected = selectTranscriptStep(status, options);
|
|
592
|
+
const stepOutputPath = selected.index !== void 0 ? path3.join(asyncDir, `output-${selected.index}.log`) : void 0;
|
|
593
|
+
const runOutputPath = resolveMaybeRelative(asyncDir, status.outputFile);
|
|
594
|
+
const logPath = path3.join(asyncDir, `subagent-log-${status.runId}.md`);
|
|
595
|
+
const outputPaths = selected.index !== void 0 ? uniqueStrings([selected.step?.externalProcess?.finalOutputPath, stepOutputPath, runOutputPath && stepOutputPath && path3.resolve(runOutputPath) === path3.resolve(stepOutputPath) ? runOutputPath : void 0]) : uniqueStrings([runOutputPath]);
|
|
596
|
+
const sessionFile = selected.index !== void 0 ? selected.step?.sessionFile : status.sessionFile;
|
|
597
|
+
const eventsPath = path3.join(asyncDir, "events.jsonl");
|
|
598
|
+
const context = contextModeLabel(status.context ?? summarizeContextModes((status.steps ?? []).map((step) => step.context)));
|
|
599
|
+
const lines = [
|
|
600
|
+
`Run: ${status.runId}`,
|
|
601
|
+
`State: ${status.state}`,
|
|
602
|
+
`Mode: ${status.mode}${context ? ` ${context}` : ""}`,
|
|
603
|
+
stepStateLine(status.mode, selected.index, selected.step),
|
|
604
|
+
selected.hint
|
|
605
|
+
].filter((line) => Boolean(line));
|
|
606
|
+
appendKnownArtifacts(lines, { outputPaths: outputPaths.filter((outputPath) => fs3.existsSync(outputPath)), sessionFile, eventsPath: fs3.existsSync(eventsPath) ? eventsPath : void 0, logPath: fs3.existsSync(logPath) ? logPath : void 0 });
|
|
607
|
+
const warnings = [];
|
|
608
|
+
let transcriptLines = [];
|
|
609
|
+
let transcriptSource = "Transcript tail";
|
|
610
|
+
let truncated = false;
|
|
611
|
+
const externalProcess = selected.step?.runner?.type === "external-cli" ? selected.step.externalProcess : void 0;
|
|
612
|
+
const externalLogPaths = externalProcess ? [
|
|
613
|
+
{ path: externalProcess.stderrPath, label: "External stderr tail" },
|
|
614
|
+
{ path: externalProcess.stdoutPath, label: "External stdout tail" }
|
|
615
|
+
] : [];
|
|
616
|
+
const runningExternal = selected.step?.status === "running" && externalProcess;
|
|
617
|
+
const sources = runningExternal ? [...externalLogPaths, ...outputPaths.map((outputPath) => ({ path: outputPath, label: "Transcript tail" }))] : [...outputPaths.map((outputPath) => ({ path: outputPath, label: "Transcript tail" })), ...externalLogPaths];
|
|
618
|
+
for (const source of sources) {
|
|
619
|
+
const outputPath = source.path;
|
|
620
|
+
const tail = readContainedTextTail(outputPath, lineLimit, [asyncDir], "output");
|
|
621
|
+
if (tail.error) warnings.push(`Output read failed for ${tail.path}: ${tail.error}`);
|
|
622
|
+
if (!tail.lines.some((line) => line.trim().length > 0)) continue;
|
|
623
|
+
transcriptLines = tail.lines;
|
|
624
|
+
transcriptSource = `${source.label} from ${tail.path}`;
|
|
625
|
+
truncated = tail.truncated;
|
|
626
|
+
break;
|
|
627
|
+
}
|
|
628
|
+
if (transcriptLines.length === 0 && selected.step?.recentOutput?.length) {
|
|
629
|
+
transcriptLines = selected.step.recentOutput.slice(-lineLimit);
|
|
630
|
+
transcriptSource = "Recent output from status.json";
|
|
631
|
+
}
|
|
632
|
+
if (transcriptLines.length === 0 && sessionFile) {
|
|
633
|
+
const sessionTail = readSessionTranscriptTail(sessionFile, lineLimit, options.sessionRoots ?? [], options.trustedSessionFiles, options.trustedSessionFileRoot);
|
|
634
|
+
transcriptLines = sessionTail.lines;
|
|
635
|
+
warnings.push(...sessionTail.warnings);
|
|
636
|
+
if (transcriptLines.length > 0) transcriptSource = `Session transcript tail from ${sessionFile}`;
|
|
637
|
+
}
|
|
638
|
+
if (warnings.length) {
|
|
639
|
+
lines.push("Warnings:");
|
|
640
|
+
for (const warning of warnings) lines.push(` ${warning}`);
|
|
641
|
+
}
|
|
642
|
+
appendTranscriptBody(lines, transcriptSource, transcriptLines, truncated);
|
|
643
|
+
return safeTranscriptLines(lines);
|
|
644
|
+
}
|
|
645
|
+
function formatNestedRunTranscript(run, options = {}) {
|
|
646
|
+
if (run.asyncDir) {
|
|
647
|
+
const status = readStatus(run.asyncDir);
|
|
648
|
+
if (status) return formatAsyncRunTranscript(status, run.asyncDir, options);
|
|
649
|
+
}
|
|
650
|
+
const lineLimit = transcriptLineLimit(options.lines);
|
|
651
|
+
const lines = [
|
|
652
|
+
`Nested run: ${run.id}`,
|
|
653
|
+
`State: ${run.state}`,
|
|
654
|
+
run.mode ? `Mode: ${run.mode}` : void 0,
|
|
655
|
+
run.sessionName?.trim() ? `Agent: ${run.sessionName.trim()}` : run.agent ? `Agent: ${run.agent}` : run.agents?.length ? `Agents: ${run.agents.join(", ")}` : void 0
|
|
656
|
+
].filter((line) => Boolean(line));
|
|
657
|
+
appendKnownArtifacts(lines, { outputPaths: [], sessionFile: run.sessionFile });
|
|
658
|
+
if (!run.sessionFile) {
|
|
659
|
+
appendTranscriptBody(lines, "Transcript tail", [], false);
|
|
660
|
+
return safeTranscriptLines(lines);
|
|
661
|
+
}
|
|
662
|
+
const sessionTail = readSessionTranscriptTail(run.sessionFile, lineLimit, options.sessionRoots ?? [], options.trustedSessionFiles, options.trustedSessionFileRoot);
|
|
663
|
+
if (sessionTail.warnings.length) {
|
|
664
|
+
lines.push("Warnings:");
|
|
665
|
+
for (const warning of sessionTail.warnings) lines.push(` ${warning}`);
|
|
666
|
+
}
|
|
667
|
+
appendTranscriptBody(lines, `Session transcript tail from ${run.sessionFile}`, sessionTail.lines, false);
|
|
668
|
+
return safeTranscriptLines(lines);
|
|
669
|
+
}
|
|
670
|
+
function formatAsyncResultTranscript(data, resultPath, options = {}) {
|
|
671
|
+
const lineLimit = transcriptLineLimit(options.lines);
|
|
672
|
+
const runId = data.runId ?? data.id ?? path3.basename(resultPath, ".json");
|
|
673
|
+
const children = Array.isArray(data.results) ? data.results : data.agent ? [{ agent: data.agent, sessionName: void 0, output: data.output, summary: data.summary, sessionFile: data.sessionFile, state: data.state, success: data.success, exitCode: data.exitCode }] : [];
|
|
674
|
+
let index = options.index;
|
|
675
|
+
if (index !== void 0 && !Number.isInteger(index)) throw new Error("Transcript index must be an integer.");
|
|
676
|
+
if (index === void 0 && children.length === 1) index = 0;
|
|
677
|
+
if (index !== void 0 && (index < 0 || index >= children.length)) throw new Error(`Transcript index ${index} is out of range for ${children.length} result child${children.length === 1 ? "" : "ren"}.`);
|
|
678
|
+
const child = index !== void 0 ? children[index] : void 0;
|
|
679
|
+
const output = index !== void 0 ? child?.output ?? child?.summary ?? (children.length === 1 ? data.output ?? data.summary : void 0) ?? "" : data.output ?? data.summary ?? "";
|
|
680
|
+
const transcriptLines = output.split(/\r?\n/).slice(-lineLimit);
|
|
681
|
+
const sessionFile = child?.sessionFile ?? data.sessionFile;
|
|
682
|
+
const lines = [
|
|
683
|
+
`Run: ${runId}`,
|
|
684
|
+
`State: ${data.state ?? (data.success ? "complete" : "failed")}`,
|
|
685
|
+
index !== void 0 && child ? `Child: ${index} (${fleetChildDisplayName(child)})` : void 0,
|
|
686
|
+
index === void 0 && children.length > 1 ? `Tip: pass index to inspect a specific child transcript (${children.map((candidate, childIndex) => `${childIndex}=${fleetChildDisplayName(candidate)}`).join(", ")}).` : void 0
|
|
687
|
+
].filter((line) => Boolean(line));
|
|
688
|
+
appendKnownArtifacts(lines, { outputPaths: [], sessionFile, resultPath });
|
|
689
|
+
appendTranscriptBody(lines, "Result transcript tail", transcriptLines.filter((line) => line.trim()), output.split(/\r?\n/).length > lineLimit);
|
|
690
|
+
return safeTranscriptLines(lines);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// src/runs/background/run-id-resolver.ts
|
|
694
|
+
import * as fs4 from "node:fs";
|
|
695
|
+
import * as path4 from "node:path";
|
|
696
|
+
function exactAsyncLocation(id, asyncDirRoot, resultsDir) {
|
|
697
|
+
const asyncDir = path4.join(asyncDirRoot, id);
|
|
698
|
+
const hasStatus = fs4.existsSync(path4.join(asyncDir, "status.json"));
|
|
699
|
+
const resultPath = resultPathFor(resultsDir, id);
|
|
700
|
+
if (!hasStatus && !resultPath) return void 0;
|
|
701
|
+
return {
|
|
702
|
+
asyncDir: hasStatus ? asyncDir : null,
|
|
703
|
+
resultPath,
|
|
704
|
+
resolvedId: id
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
function readWorkflowResultIdentity(resultPath) {
|
|
708
|
+
let parsed;
|
|
709
|
+
try {
|
|
710
|
+
parsed = JSON.parse(fs4.readFileSync(resultPath, "utf-8"));
|
|
711
|
+
} catch {
|
|
712
|
+
return void 0;
|
|
713
|
+
}
|
|
714
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
|
|
715
|
+
const record = parsed;
|
|
716
|
+
return {
|
|
717
|
+
...typeof record.id === "string" ? { id: record.id } : {},
|
|
718
|
+
...typeof record.runId === "string" ? { runId: record.runId } : {},
|
|
719
|
+
...typeof record.toolCallId === "string" ? { toolCallId: record.toolCallId } : {}
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
function resultPathFor(resultsDir, runId) {
|
|
723
|
+
const resultPath = resultPayloadPathForIndexedRun(resultsDir, runId) ?? resultFilePath(resultsDir, runId);
|
|
724
|
+
return fs4.existsSync(resultPath) ? resultPath : null;
|
|
725
|
+
}
|
|
726
|
+
function toolCallIdMatches(value, query) {
|
|
727
|
+
return value === query;
|
|
728
|
+
}
|
|
729
|
+
function indexedToolCallIdAsyncLocations(toolCallId, asyncDirRoot, resultsDir) {
|
|
730
|
+
const byId = /* @__PURE__ */ new Map();
|
|
731
|
+
for (const entry of [...readActiveRunToolCallIndex(asyncDirRoot, toolCallId), ...readTerminalRunToolCallIndex(asyncDirRoot, toolCallId)]) {
|
|
732
|
+
const asyncDir = path4.join(asyncDirRoot, entry);
|
|
733
|
+
const status = readStatus(asyncDir);
|
|
734
|
+
if (!status || !toolCallIdMatches(status.toolCallId, toolCallId)) continue;
|
|
735
|
+
const runId = status.runId || entry;
|
|
736
|
+
byId.set(runId, { asyncDir, resultPath: resultPathFor(resultsDir, runId), resolvedId: runId });
|
|
737
|
+
}
|
|
738
|
+
for (const entry of resultCandidateFilesForToolCall(resultsDir, toolCallId)) {
|
|
739
|
+
const runIdFromFile = entry.slice(0, -".json".length);
|
|
740
|
+
const resultPath = resultPayloadPathForIndexedRun(resultsDir, runIdFromFile) ?? path4.join(resultsDir, entry);
|
|
741
|
+
const identity = readWorkflowResultIdentity(resultPath);
|
|
742
|
+
if (!identity || !toolCallIdMatches(identity.toolCallId, toolCallId)) continue;
|
|
743
|
+
const runId = identity.runId ?? identity.id ?? runIdFromFile;
|
|
744
|
+
const asyncDir = path4.join(asyncDirRoot, runId);
|
|
745
|
+
byId.set(runId, { asyncDir: fs4.existsSync(asyncDir) ? asyncDir : null, resultPath, resolvedId: runId });
|
|
746
|
+
}
|
|
747
|
+
return [...byId.entries()].map(([id, location]) => ({ id, location }));
|
|
748
|
+
}
|
|
749
|
+
function foregroundIds(state) {
|
|
750
|
+
if (!state) return [];
|
|
751
|
+
const remembered = state.currentSessionId ? [...state.foregroundRuns?.values() ?? []].filter((run) => run.sessionId === state.currentSessionId).map((run) => run.runId) : [];
|
|
752
|
+
return [.../* @__PURE__ */ new Set([...state.foregroundControls.keys(), ...remembered])];
|
|
753
|
+
}
|
|
754
|
+
function hasExactForegroundId(state, id) {
|
|
755
|
+
if (!state) return false;
|
|
756
|
+
if (state.foregroundControls.has(id)) return true;
|
|
757
|
+
const remembered = state.foregroundRuns?.get(id);
|
|
758
|
+
return Boolean(remembered && state.currentSessionId && remembered.sessionId === state.currentSessionId);
|
|
759
|
+
}
|
|
760
|
+
function exactLiveAsyncToolCallMatch(state, toolCallId, asyncDirRoot, resultsDir) {
|
|
761
|
+
if (!state?.asyncJobs) return void 0;
|
|
762
|
+
const matches = [...state.asyncJobs.values()].filter((job) => job.toolCallId === toolCallId);
|
|
763
|
+
if (matches.length > 1) throw new Error(`Subagent tool-call id '${toolCallId}' is ambiguous across async runs. Use the returned asyncId instead.`);
|
|
764
|
+
const match = matches[0];
|
|
765
|
+
if (!match) return void 0;
|
|
766
|
+
return {
|
|
767
|
+
id: match.asyncId,
|
|
768
|
+
location: {
|
|
769
|
+
asyncDir: fs4.existsSync(match.asyncDir) ? match.asyncDir : path4.join(asyncDirRoot, match.asyncId),
|
|
770
|
+
resultPath: resultPathFor(resultsDir, match.asyncId),
|
|
771
|
+
resolvedId: match.asyncId
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
function nestedScopeFromState(state) {
|
|
776
|
+
if (!state) return void 0;
|
|
777
|
+
const routes = [];
|
|
778
|
+
const seen = /* @__PURE__ */ new Set();
|
|
779
|
+
const add = (route) => {
|
|
780
|
+
if (!route) return;
|
|
781
|
+
const key = `${route.rootRunId}:${route.eventSink}:${route.controlInbox}`;
|
|
782
|
+
if (seen.has(key)) return;
|
|
783
|
+
seen.add(key);
|
|
784
|
+
routes.push(route);
|
|
785
|
+
};
|
|
786
|
+
for (const control of state.foregroundControls.values()) add(control.nestedRoute);
|
|
787
|
+
for (const job of state.asyncJobs.values()) add(job.nestedRoute);
|
|
788
|
+
if (state.retainedNestedLookupRoutes?.sessionId === state.currentSessionId) {
|
|
789
|
+
for (const route of state.retainedNestedLookupRoutes.routes.values()) add(route);
|
|
790
|
+
}
|
|
791
|
+
return { routes };
|
|
792
|
+
}
|
|
793
|
+
function asyncPrefixMatches(prefix, asyncDirRoot, resultsDir) {
|
|
794
|
+
return findAsyncRunPrefixMatches(prefix, asyncDirRoot, resultsDir).filter(
|
|
795
|
+
(match) => match.location.resultPath || match.location.asyncDir && fs4.existsSync(path4.join(match.location.asyncDir, "status.json"))
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
function resolveSubagentRunId(id, deps = {}) {
|
|
799
|
+
assertSafeNestedId("id", id);
|
|
800
|
+
const asyncDirRoot = deps.asyncDirRoot ?? DIRS.async;
|
|
801
|
+
const resultsDir = deps.resultsDir ?? DIRS.results;
|
|
802
|
+
if (hasExactForegroundId(deps.state, id)) return { kind: "foreground", id };
|
|
803
|
+
const exactAsync = exactAsyncLocation(id, asyncDirRoot, resultsDir);
|
|
804
|
+
if (exactAsync) return { kind: "async", id, location: exactAsync };
|
|
805
|
+
const toolCallMatches = new Map(indexedToolCallIdAsyncLocations(id, asyncDirRoot, resultsDir).map((match) => [match.id, match]));
|
|
806
|
+
const exactLiveToolCallMatch = exactLiveAsyncToolCallMatch(deps.state, id, asyncDirRoot, resultsDir);
|
|
807
|
+
if (exactLiveToolCallMatch) toolCallMatches.set(exactLiveToolCallMatch.id, exactLiveToolCallMatch);
|
|
808
|
+
if (toolCallMatches.size > 1) throw new Error(`Subagent tool-call id '${id}' is ambiguous across async runs. Use the returned asyncId instead.`);
|
|
809
|
+
const [toolCallMatch] = toolCallMatches.values();
|
|
810
|
+
if (toolCallMatch) return { kind: "async", id: toolCallMatch.id, location: toolCallMatch.location };
|
|
811
|
+
const nestedScope = deps.nested ?? nestedScopeFromState(deps.state);
|
|
812
|
+
const exactNested = findNestedRunMatchesById(id, nestedScope ? { scope: nestedScope } : {});
|
|
813
|
+
if (exactNested.length > 1) throw new Error(`Nested run id '${id}' is ambiguous across authorized registries. Provide the full id after stale registries are cleaned up.`);
|
|
814
|
+
if (exactNested[0]) return { kind: "nested", id, match: exactNested[0] };
|
|
815
|
+
const matches = [];
|
|
816
|
+
for (const foregroundId of foregroundIds(deps.state).filter((candidate) => candidate.startsWith(id))) {
|
|
817
|
+
matches.push({ kind: "foreground", id: foregroundId });
|
|
818
|
+
}
|
|
819
|
+
for (const match of asyncPrefixMatches(id, asyncDirRoot, resultsDir)) {
|
|
820
|
+
matches.push({ kind: "async", id: match.id, location: match.location });
|
|
821
|
+
}
|
|
822
|
+
for (const match of findNestedRunMatchesById(id, nestedScope ? { prefix: true, scope: nestedScope } : { prefix: true })) {
|
|
823
|
+
matches.push({ kind: "nested", id: match.run.id, match });
|
|
824
|
+
}
|
|
825
|
+
const unique = new Map(matches.map((match) => [`${match.kind}:${match.id}`, match]));
|
|
826
|
+
const values = [...unique.values()];
|
|
827
|
+
if (values.length > 1) {
|
|
828
|
+
throw new Error(`Ambiguous subagent run id prefix '${id}' matched: ${values.map((match) => `${match.kind}:${match.id}`).join(", ")}. Provide a longer id.`);
|
|
829
|
+
}
|
|
830
|
+
return values[0];
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// src/runs/foreground/async-stop-action.ts
|
|
834
|
+
import * as fs5 from "node:fs";
|
|
835
|
+
import * as path5 from "node:path";
|
|
836
|
+
function getAsyncStopTarget(state, runId, location) {
|
|
837
|
+
if (location?.asyncDir) {
|
|
838
|
+
return {
|
|
839
|
+
asyncId: location.resolvedId ?? runId ?? path5.basename(location.asyncDir),
|
|
840
|
+
asyncDir: location.asyncDir
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
if (!runId) return void 0;
|
|
844
|
+
const direct = state.asyncJobs.get(runId);
|
|
845
|
+
return direct ? { asyncId: direct.asyncId, asyncDir: direct.asyncDir } : void 0;
|
|
846
|
+
}
|
|
847
|
+
function sealPausedRun(asyncDir, status, resultsDir) {
|
|
848
|
+
const runnerProcessInstanceId = status.processTerminal?.runnerProcessInstanceId;
|
|
849
|
+
if (!runnerProcessInstanceId) return "runner process identity is missing";
|
|
850
|
+
const proof = readProcessTerminal(asyncDir, { runId: status.runId, runnerProcessInstanceId });
|
|
851
|
+
if (proof?.state !== "observed" || proof.runId !== status.runId || proof.runnerProcessInstanceId !== runnerProcessInstanceId) {
|
|
852
|
+
return `process-terminal proof is ${proof?.state === "unknown" ? `unknown (${proof.reason})` : proof?.state ?? "missing"}`;
|
|
853
|
+
}
|
|
854
|
+
if (!status.sessionId) return "session identity is missing";
|
|
855
|
+
const existingResultPath = resultPayloadPathForSessionRun(resultsDir, status.sessionId, status.runId) ?? resultPayloadFileForSessionRun(resultsDir, status.sessionId, status.runId);
|
|
856
|
+
let existingResult;
|
|
857
|
+
if (!existingResultPath) {
|
|
858
|
+
existingResult = {
|
|
859
|
+
id: status.runId,
|
|
860
|
+
mode: status.mode,
|
|
861
|
+
sessionId: status.sessionId,
|
|
862
|
+
asyncDir,
|
|
863
|
+
...status.completionOwnerId ? { completionOwnerId: status.completionOwnerId } : {},
|
|
864
|
+
...status.toolCallId ? { toolCallId: status.toolCallId } : {},
|
|
865
|
+
results: (status.steps ?? []).map((step) => ({
|
|
866
|
+
agent: step.agent,
|
|
867
|
+
success: step.status === "complete" || step.status === "completed",
|
|
868
|
+
...step.error ? { error: step.error } : {},
|
|
869
|
+
...step.exitCode !== void 0 ? { exitCode: step.exitCode } : {}
|
|
870
|
+
}))
|
|
871
|
+
};
|
|
872
|
+
} else {
|
|
873
|
+
try {
|
|
874
|
+
const parsed = JSON.parse(fs5.readFileSync(existingResultPath, "utf-8"));
|
|
875
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("result is not an object");
|
|
876
|
+
existingResult = parsed;
|
|
877
|
+
} catch (error) {
|
|
878
|
+
return `paused result is unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
const resultRunId = typeof existingResult.runId === "string" ? existingResult.runId : existingResult.id;
|
|
882
|
+
if (resultRunId !== status.runId || existingResult.sessionId !== status.sessionId) {
|
|
883
|
+
return "paused result identity does not match the run";
|
|
884
|
+
}
|
|
885
|
+
const now = Date.now();
|
|
886
|
+
const steps = (status.steps ?? []).map((step) => {
|
|
887
|
+
if (step.status !== "pending" && step.status !== "running" && step.status !== "paused") return step;
|
|
888
|
+
const { activityState: _activityState, ...rest } = step;
|
|
889
|
+
return {
|
|
890
|
+
...rest,
|
|
891
|
+
status: "stopped",
|
|
892
|
+
error: SUBAGENT_STOPPED_MESSAGE,
|
|
893
|
+
exitCode: 1,
|
|
894
|
+
stopped: true,
|
|
895
|
+
endedAt: now,
|
|
896
|
+
durationMs: step.startedAt ? now - step.startedAt : 0,
|
|
897
|
+
lastActivityAt: now
|
|
898
|
+
};
|
|
899
|
+
});
|
|
900
|
+
const results = Array.isArray(existingResult.results) ? existingResult.results.map((entry, index) => {
|
|
901
|
+
const stepState = status.steps?.[index]?.status;
|
|
902
|
+
if (stepState !== "pending" && stepState !== "running" && stepState !== "paused" || !entry || typeof entry !== "object" || Array.isArray(entry)) return entry;
|
|
903
|
+
const { interrupted: _interrupted, ...result } = entry;
|
|
904
|
+
return { ...result, output: SUBAGENT_STOPPED_MESSAGE, error: SUBAGENT_STOPPED_MESSAGE, success: false, exitCode: 1, stopped: true };
|
|
905
|
+
}) : existingResult.results;
|
|
906
|
+
const stoppedResult = {
|
|
907
|
+
...existingResult,
|
|
908
|
+
success: false,
|
|
909
|
+
state: "stopped",
|
|
910
|
+
summary: SUBAGENT_STOPPED_MESSAGE,
|
|
911
|
+
error: SUBAGENT_STOPPED_MESSAGE,
|
|
912
|
+
stopped: true,
|
|
913
|
+
exitCode: 1,
|
|
914
|
+
timestamp: now,
|
|
915
|
+
...Array.isArray(existingResult.results) ? { results } : {}
|
|
916
|
+
};
|
|
917
|
+
const stoppedStatus = {
|
|
918
|
+
...status,
|
|
919
|
+
state: "stopped",
|
|
920
|
+
stopped: true,
|
|
921
|
+
error: SUBAGENT_STOPPED_MESSAGE,
|
|
922
|
+
endedAt: now,
|
|
923
|
+
lastUpdate: now,
|
|
924
|
+
steps
|
|
925
|
+
};
|
|
926
|
+
delete stoppedStatus.activityState;
|
|
927
|
+
writeAsyncResultFile(resultFilePath(resultsDir, status.runId), stoppedResult);
|
|
928
|
+
writeAtomicJson(path5.join(asyncDir, "status.json"), stoppedStatus);
|
|
929
|
+
updateActiveRunIndex(asyncDir, "stopped", status.toolCallId, { retryCapacityErrors: true, terminalIndexBeforeRelease: true });
|
|
930
|
+
}
|
|
931
|
+
function deliverAsyncRunStop(input) {
|
|
932
|
+
const { status, resultsDir = DIRS.results, ...delivery } = input;
|
|
933
|
+
const pausedWholeRun = status.state === "paused" && delivery.childId === void 0 && delivery.targetIndex === void 0;
|
|
934
|
+
if (!(pausedWholeRun && fs5.existsSync(stopInboxClosedPath(delivery.asyncDir)))) deliverStopRequest(delivery);
|
|
935
|
+
if (pausedWholeRun) return sealPausedRun(delivery.asyncDir, status, resultsDir);
|
|
936
|
+
}
|
|
937
|
+
function stopAsyncRun(state, runId, kill, location, childId) {
|
|
938
|
+
const target = getAsyncStopTarget(state, runId, location);
|
|
939
|
+
const workflowRunId = target?.asyncId ?? runId;
|
|
940
|
+
const workflowController = childId === void 0 && workflowRunId ? state.workflowControllers?.get(workflowRunId) : void 0;
|
|
941
|
+
const workflowAsyncDir = workflowController && workflowRunId ? target?.asyncDir ?? state.asyncJobs.get(workflowRunId)?.asyncDir : void 0;
|
|
942
|
+
const workflowStatus = workflowAsyncDir ? readStatus(workflowAsyncDir) : void 0;
|
|
943
|
+
const foreignWorkflow = Boolean(state.currentSessionId && workflowStatus && workflowStatus.sessionId !== state.currentSessionId);
|
|
944
|
+
if (workflowController && workflowRunId && !foreignWorkflow) {
|
|
945
|
+
if (workflowStatus) stopStoppableAsyncStatusChildren(workflowStatus, state.workflowChildStops?.get(workflowRunId), "Workflow stopped.");
|
|
946
|
+
workflowController.abort(new Error("Workflow stopped."));
|
|
947
|
+
return { content: [{ type: "text", text: `Stop requested for async workflow ${workflowRunId}.` }], details: { mode: "management", results: [] } };
|
|
948
|
+
}
|
|
949
|
+
if (!target) return null;
|
|
950
|
+
const status = reconcileAsyncRun(target.asyncDir, { kill }).status;
|
|
951
|
+
if (state.currentSessionId && !ownsRun(state.currentSessionId, status?.sessionId) && location?.reattachedRunId !== target.asyncId) {
|
|
952
|
+
return {
|
|
953
|
+
content: [{ type: "text", text: `Async run '${target.asyncId}' was not found in the active session.` }],
|
|
954
|
+
isError: true,
|
|
955
|
+
details: { mode: "management", results: [] }
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
const pausedWholeRun = status?.state === "paused" && childId === void 0;
|
|
959
|
+
if (!status || status.state !== "running" && status.state !== "queued" && !pausedWholeRun) {
|
|
960
|
+
return {
|
|
961
|
+
content: [{ type: "text", text: `No running or queued async run was found for '${runId ?? "current"}'.` }],
|
|
962
|
+
isError: true,
|
|
963
|
+
details: { mode: "management", results: [] }
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
let child;
|
|
967
|
+
if (childId !== void 0) {
|
|
968
|
+
const resolution = resolveAsyncStatusChild(status, childId);
|
|
969
|
+
if (!resolution.ok) {
|
|
970
|
+
return {
|
|
971
|
+
content: [{ type: "text", text: resolution.message }],
|
|
972
|
+
isError: true,
|
|
973
|
+
details: { mode: "management", results: [] }
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
child = resolution.child;
|
|
977
|
+
if (!isStoppableAsyncStatusStep(child.step)) {
|
|
978
|
+
return {
|
|
979
|
+
content: [{ type: "text", text: `Child '${childId}' in async run '${status.runId}' is ${child.step.status}; stop only supports pending or running children.` }],
|
|
980
|
+
isError: true,
|
|
981
|
+
details: { mode: "management", results: [] }
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
try {
|
|
986
|
+
const failure = deliverAsyncRunStop({ asyncDir: target.asyncDir, status, pid: typeof status.pid === "number" ? status.pid : void 0, kill, source: "stop-action", targetIndex: child?.index, childId: child?.id ?? childId });
|
|
987
|
+
if (failure) {
|
|
988
|
+
return {
|
|
989
|
+
content: [{ type: "text", text: `Stop request persisted for paused async run ${target.asyncId}, but terminal proof is not ready (${failure}). Retry stop after runner shutdown is observed.` }],
|
|
990
|
+
isError: true,
|
|
991
|
+
details: { mode: "management", results: [] }
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
const tracked = state.asyncJobs.get(target.asyncId);
|
|
995
|
+
if (tracked) {
|
|
996
|
+
tracked.activityState = void 0;
|
|
997
|
+
tracked.updatedAt = Date.now();
|
|
998
|
+
}
|
|
999
|
+
return {
|
|
1000
|
+
content: [{ type: "text", text: pausedWholeRun ? `Stopped paused async run ${target.asyncId}.` : child ? `Stop requested for child ${child.id} in async run ${target.asyncId}.` : `Stop requested for async run ${target.asyncId}.` }],
|
|
1001
|
+
details: { mode: "management", results: [] }
|
|
1002
|
+
};
|
|
1003
|
+
} catch (error) {
|
|
1004
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1005
|
+
return {
|
|
1006
|
+
content: [{ type: "text", text: `Failed to stop async run ${target.asyncId}: ${message}` }],
|
|
1007
|
+
isError: true,
|
|
1008
|
+
details: { mode: "management", results: [] }
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
export {
|
|
1014
|
+
currentCompletionOwnerId,
|
|
1015
|
+
resolveGitRepositoryIdentity,
|
|
1016
|
+
resolveWorkflowChatProgress,
|
|
1017
|
+
buildWorkflowChatProgressRows,
|
|
1018
|
+
isTrustedRecordedSessionFile,
|
|
1019
|
+
readSessionMessagesTail,
|
|
1020
|
+
inspectSubagentFleet,
|
|
1021
|
+
formatAsyncRunTranscript,
|
|
1022
|
+
formatNestedRunTranscript,
|
|
1023
|
+
formatAsyncResultTranscript,
|
|
1024
|
+
resolveSubagentRunId,
|
|
1025
|
+
deliverAsyncRunStop,
|
|
1026
|
+
stopAsyncRun
|
|
1027
|
+
};
|