@fitchmultz/pi-subagents 0.0.0-stage → 0.44.5
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 +1654 -0
- package/LICENSE +21 -0
- package/README.md +1533 -2
- package/agents/context-builder.md +97 -0
- package/agents/debugger.md +51 -0
- package/agents/delegate.md +13 -0
- package/agents/fixer.md +37 -0
- package/agents/oracle.md +71 -0
- package/agents/planner.md +72 -0
- package/agents/researcher.md +51 -0
- package/agents/reviewer-claude.md +69 -0
- package/agents/reviewer-gpt.md +68 -0
- package/agents/reviewer-ponytail.md +82 -0
- package/agents/reviewer-security.md +81 -0
- package/agents/reviewer.md +69 -0
- package/agents/scout.md +66 -0
- package/agents/ui-designer.md +51 -0
- package/agents/watcher.md +26 -0
- package/agents/worker.md +46 -0
- package/agents/writer.md +36 -0
- package/dist/agents/agent-management.js +923 -0
- package/dist/agents/agent-scope.js +7 -0
- package/dist/agents/agent-selection.js +20 -0
- package/dist/agents/agent-serializer.js +100 -0
- package/dist/agents/agents.js +752 -0
- package/dist/agents/chain-serializer.js +367 -0
- package/dist/agents/frontmatter.js +21 -0
- package/dist/agents/identity.js +29 -0
- package/dist/agents/skills.js +570 -0
- package/dist/extension/build-info.js +1 -0
- package/dist/extension/compact-tools.js +112 -0
- package/dist/extension/config.js +22 -0
- package/dist/extension/control-notices.js +29 -0
- package/dist/extension/doctor.js +143 -0
- package/dist/extension/fanout-child.js +326 -0
- package/dist/extension/index.js +577 -0
- package/dist/extension/schemas.js +416 -0
- package/dist/extension/tool-input.js +20 -0
- package/dist/extension/tool-result.js +14 -0
- package/dist/history/canonical-result.js +81 -0
- package/dist/history/database.js +72 -0
- package/dist/history/index.js +199 -0
- package/dist/history/ingest.js +468 -0
- package/dist/history/queries.js +350 -0
- package/dist/history/store.js +216 -0
- package/dist/history/types.js +4 -0
- package/dist/history/worker.js +466 -0
- package/dist/intercom/intercom-bridge.js +112 -0
- package/dist/intercom/live-intercom.js +94 -0
- package/dist/intercom/result-intercom.js +373 -0
- package/dist/pi-intercom/agent-dir.js +10 -0
- package/dist/pi-intercom/bridge.js +750 -0
- package/dist/pi-intercom/broker/broker.js +426 -0
- package/dist/pi-intercom/broker/client.js +492 -0
- package/dist/pi-intercom/broker/framing.js +65 -0
- package/dist/pi-intercom/broker/paths.js +42 -0
- package/dist/pi-intercom/broker/pid.js +66 -0
- package/dist/pi-intercom/broker/spawn.js +221 -0
- package/dist/pi-intercom/config.js +102 -0
- package/dist/pi-intercom/index.js +2942 -0
- package/dist/pi-intercom/reply-tracker.js +182 -0
- package/dist/pi-intercom/session-targets.js +128 -0
- package/dist/pi-intercom/subagent-live-events.js +132 -0
- package/dist/pi-intercom/topics.js +157 -0
- package/dist/pi-intercom/types.js +163 -0
- package/dist/pi-intercom/ui/compose.js +269 -0
- package/dist/pi-intercom/ui/inline-message.js +80 -0
- package/dist/pi-intercom/ui/session-list.js +124 -0
- package/dist/runs/background/async-control.js +95 -0
- package/dist/runs/background/async-execution.js +676 -0
- package/dist/runs/background/async-job-tracker.js +383 -0
- package/dist/runs/background/async-result-file.js +58 -0
- package/dist/runs/background/async-resume.js +305 -0
- package/dist/runs/background/async-status.js +442 -0
- package/dist/runs/background/completion-dedupe.js +69 -0
- package/dist/runs/background/completion-delivery.js +245 -0
- package/dist/runs/background/notify.js +71 -0
- package/dist/runs/background/parallel-groups.js +55 -0
- package/dist/runs/background/result-watcher.js +426 -0
- package/dist/runs/background/run-id-resolver.js +64 -0
- package/dist/runs/background/run-status.js +382 -0
- package/dist/runs/background/stale-run-reconciler.js +335 -0
- package/dist/runs/background/subagent-runner-launcher.js +55 -0
- package/dist/runs/background/subagent-runner.js +1872 -0
- package/dist/runs/background/top-level-async.js +5 -0
- package/dist/runs/foreground/chain-clarify.js +1152 -0
- package/dist/runs/foreground/clarify-invocation.js +72 -0
- package/dist/runs/foreground/execution-input.js +423 -0
- package/dist/runs/foreground/foreground-control.js +817 -0
- package/dist/runs/foreground/question-control.js +88 -0
- package/dist/runs/foreground/run-async-path.js +205 -0
- package/dist/runs/foreground/subagent-executor.js +442 -0
- package/dist/runs/foreground/subagent-params.js +165 -0
- package/dist/runs/foreground/wait-run.js +141 -0
- package/dist/runs/shared/acceptance-contract.js +253 -0
- package/dist/runs/shared/acceptance-evaluation.js +256 -0
- package/dist/runs/shared/acceptance-finalization.js +280 -0
- package/dist/runs/shared/acceptance-reports.js +147 -0
- package/dist/runs/shared/acceptance.js +4 -0
- package/dist/runs/shared/chain-outputs.js +110 -0
- package/dist/runs/shared/child-attempt.js +693 -0
- package/dist/runs/shared/child-execution-cwd.js +96 -0
- package/dist/runs/shared/child-observations.js +114 -0
- package/dist/runs/shared/claude-code.js +199 -0
- package/dist/runs/shared/completion-guard.js +31 -0
- package/dist/runs/shared/dynamic-fanout.js +274 -0
- package/dist/runs/shared/history-index.js +72 -0
- package/dist/runs/shared/mcp-direct-tool-allowlist.js +334 -0
- package/dist/runs/shared/model-fallback.js +185 -0
- package/dist/runs/shared/mutating-tool-guard.js +170 -0
- package/dist/runs/shared/native-finalization.js +101 -0
- package/dist/runs/shared/native-usage.js +153 -0
- package/dist/runs/shared/nested-events.js +700 -0
- package/dist/runs/shared/nested-path.js +48 -0
- package/dist/runs/shared/nested-render.js +108 -0
- package/dist/runs/shared/parallel-utils.js +61 -0
- package/dist/runs/shared/parent-receipts.js +68 -0
- package/dist/runs/shared/parent-usage.js +121 -0
- package/dist/runs/shared/pi-args.js +310 -0
- package/dist/runs/shared/pi-spawn.js +52 -0
- package/dist/runs/shared/run-history-queries.js +45 -0
- package/dist/runs/shared/run-history.js +110 -0
- package/dist/runs/shared/run-id-ambiguity.js +4 -0
- package/dist/runs/shared/run-records.js +679 -0
- package/dist/runs/shared/session-cwd-preload.js +33 -0
- package/dist/runs/shared/single-output.js +222 -0
- package/dist/runs/shared/streaming-text.js +15 -0
- package/dist/runs/shared/structured-output.js +55 -0
- package/dist/runs/shared/subagent-control.js +179 -0
- package/dist/runs/shared/subagent-prompt-runtime.js +197 -0
- package/dist/runs/shared/subagent-tool-loop-guard.js +66 -0
- package/dist/runs/shared/supervisor-questions.js +350 -0
- package/dist/runs/shared/workflow-graph.js +214 -0
- package/dist/runs/shared/workflow-policy.js +84 -0
- package/dist/runs/shared/worktree.js +525 -0
- package/dist/shared/agent-context-policy.js +126 -0
- package/dist/shared/artifacts.js +96 -0
- package/dist/shared/atomic-json.js +13 -0
- package/dist/shared/cooperative.js +20 -0
- package/dist/shared/execution-cwd.js +14 -0
- package/dist/shared/file-coalescer.js +21 -0
- package/dist/shared/fork-context.js +49 -0
- package/dist/shared/formatters.js +85 -0
- package/dist/shared/journal-reader.js +407 -0
- package/dist/shared/lazy-tools.js +32 -0
- package/dist/shared/model-info.js +68 -0
- package/dist/shared/native-session.js +15 -0
- package/dist/shared/native-tui.js +18 -0
- package/dist/shared/native-typebox.js +20 -0
- package/dist/shared/post-exit-stdio-guard.js +197 -0
- package/dist/shared/prompt-sections.js +8 -0
- package/dist/shared/session-entries.js +52 -0
- package/dist/shared/session-identity.js +14 -0
- package/dist/shared/settings.js +254 -0
- package/dist/shared/status-format.js +111 -0
- package/dist/shared/temp-root.js +114 -0
- package/dist/shared/types.js +207 -0
- package/dist/shared/ui-mode.js +3 -0
- package/dist/shared/utils.js +334 -0
- package/dist/slash/prompt-template-bridge.js +298 -0
- package/dist/slash/slash-bridge.js +116 -0
- package/dist/slash/slash-commands.js +607 -0
- package/dist/slash/slash-live-state.js +246 -0
- package/dist/tui/action-hints.js +75 -0
- package/dist/tui/agent-history.js +197 -0
- package/dist/tui/agent-view.js +1656 -0
- package/dist/tui/render.js +1515 -0
- package/docs/intercom-bridge.md +212 -0
- package/docs/intercom.md +581 -0
- package/docs/performance.md +121 -0
- package/package.json +102 -4
- package/prompts/gather-context-and-clarify.md +14 -0
- package/prompts/parallel-cleanup.md +62 -0
- package/prompts/parallel-context-build.md +56 -0
- package/prompts/parallel-handoff-plan.md +62 -0
- package/prompts/parallel-research.md +51 -0
- package/prompts/parallel-review.md +59 -0
- package/prompts/review-loop.md +42 -0
- package/scripts/build.mjs +134 -0
- package/scripts/check-compat.mjs +48 -0
- package/scripts/compat-host.mjs +19 -0
- package/scripts/intercom-bridge-credentials.mjs +143 -0
- package/scripts/intercom-remote.mjs +103 -0
- package/scripts/linux-smoke.sh +51 -0
- package/scripts/local-install-smoke.mjs +66 -0
- package/scripts/native-package-smoke.mjs +82 -0
- package/scripts/package-smoke.mjs +209 -0
- package/scripts/prepare.mjs +67 -0
- package/scripts/real-pi-smoke.mjs +415 -0
- package/scripts/run-tests.mjs +149 -0
- package/skills/pi-intercom/SKILL.md +113 -0
- package/skills/pi-intercom/evals/evals.json +29 -0
- package/skills/pi-intercom/evals/trigger-evals.json +47 -0
- package/skills/pi-intercom/references/peer-sessions.md +69 -0
- package/skills/pi-subagents/SKILL.md +76 -0
- package/skills/pi-subagents/evals/evals.json +29 -0
- package/skills/pi-subagents/evals/trigger-evals.json +42 -0
- package/skills/pi-subagents/references/full-orchestration-guide.md +863 -0
- package/src/agents/agent-management.ts +813 -0
- package/src/agents/agent-scope.ts +7 -0
- package/src/agents/agent-selection.ts +23 -0
- package/src/agents/agent-serializer.ts +99 -0
- package/src/agents/agents.ts +916 -0
- package/src/agents/chain-serializer.ts +329 -0
- package/src/agents/frontmatter.ts +21 -0
- package/src/agents/identity.ts +30 -0
- package/src/agents/skills.ts +637 -0
- package/src/extension/build-info.ts +2 -0
- package/src/extension/compact-tools.ts +114 -0
- package/src/extension/config.ts +23 -0
- package/src/extension/control-notices.ts +47 -0
- package/src/extension/doctor.ts +171 -0
- package/src/extension/fanout-child.ts +312 -0
- package/src/extension/index.ts +649 -0
- package/src/extension/schemas.ts +444 -0
- package/src/extension/tool-input.ts +19 -0
- package/src/extension/tool-result.ts +16 -0
- package/src/history/canonical-result.ts +62 -0
- package/src/history/database.ts +59 -0
- package/src/history/index.ts +156 -0
- package/src/history/ingest.ts +349 -0
- package/src/history/queries.ts +241 -0
- package/src/history/store.ts +203 -0
- package/src/history/types.ts +166 -0
- package/src/history/worker.ts +321 -0
- package/src/intercom/intercom-bridge.ts +123 -0
- package/src/intercom/live-intercom.ts +104 -0
- package/src/intercom/result-intercom.ts +429 -0
- package/src/pi-intercom/agent-dir.ts +9 -0
- package/src/pi-intercom/bridge.ts +668 -0
- package/src/pi-intercom/broker/broker.ts +463 -0
- package/src/pi-intercom/broker/client.ts +558 -0
- package/src/pi-intercom/broker/framing.ts +77 -0
- package/src/pi-intercom/broker/paths.ts +47 -0
- package/src/pi-intercom/broker/pid.ts +59 -0
- package/src/pi-intercom/broker/spawn.ts +248 -0
- package/src/pi-intercom/config.ts +142 -0
- package/src/pi-intercom/index.ts +2929 -0
- package/src/pi-intercom/reply-tracker.ts +211 -0
- package/src/pi-intercom/session-targets.ts +172 -0
- package/src/pi-intercom/subagent-live-events.ts +133 -0
- package/src/pi-intercom/topics.ts +125 -0
- package/src/pi-intercom/types.ts +271 -0
- package/src/pi-intercom/ui/compose.ts +289 -0
- package/src/pi-intercom/ui/inline-message.ts +92 -0
- package/src/pi-intercom/ui/session-list.ts +139 -0
- package/src/runs/background/async-control.ts +89 -0
- package/src/runs/background/async-execution.ts +850 -0
- package/src/runs/background/async-job-tracker.ts +385 -0
- package/src/runs/background/async-result-file.ts +62 -0
- package/src/runs/background/async-resume.ts +333 -0
- package/src/runs/background/async-status.ts +481 -0
- package/src/runs/background/completion-dedupe.ts +79 -0
- package/src/runs/background/completion-delivery.ts +195 -0
- package/src/runs/background/notify.ts +123 -0
- package/src/runs/background/parallel-groups.ts +53 -0
- package/src/runs/background/result-watcher.ts +423 -0
- package/src/runs/background/run-id-resolver.ts +69 -0
- package/src/runs/background/run-status.ts +384 -0
- package/src/runs/background/stale-run-reconciler.ts +369 -0
- package/src/runs/background/subagent-runner-launcher.ts +56 -0
- package/src/runs/background/subagent-runner.ts +2053 -0
- package/src/runs/background/top-level-async.ts +13 -0
- package/src/runs/foreground/chain-clarify.ts +1340 -0
- package/src/runs/foreground/clarify-invocation.ts +77 -0
- package/src/runs/foreground/execution-input.ts +467 -0
- package/src/runs/foreground/foreground-control.ts +821 -0
- package/src/runs/foreground/question-control.ts +82 -0
- package/src/runs/foreground/run-async-path.ts +243 -0
- package/src/runs/foreground/subagent-executor.ts +501 -0
- package/src/runs/foreground/subagent-params.ts +320 -0
- package/src/runs/foreground/wait-run.ts +124 -0
- package/src/runs/shared/acceptance-contract.ts +257 -0
- package/src/runs/shared/acceptance-evaluation.ts +275 -0
- package/src/runs/shared/acceptance-finalization.ts +353 -0
- package/src/runs/shared/acceptance-reports.ts +152 -0
- package/src/runs/shared/acceptance.ts +33 -0
- package/src/runs/shared/chain-outputs.ts +114 -0
- package/src/runs/shared/child-attempt.ts +623 -0
- package/src/runs/shared/child-execution-cwd.ts +91 -0
- package/src/runs/shared/child-observations.ts +99 -0
- package/src/runs/shared/claude-code.ts +243 -0
- package/src/runs/shared/completion-guard.ts +35 -0
- package/src/runs/shared/dynamic-fanout.ts +295 -0
- package/src/runs/shared/history-index.ts +69 -0
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +376 -0
- package/src/runs/shared/model-fallback.ts +219 -0
- package/src/runs/shared/mutating-tool-guard.ts +194 -0
- package/src/runs/shared/native-finalization.ts +128 -0
- package/src/runs/shared/native-usage.ts +113 -0
- package/src/runs/shared/nested-events.ts +752 -0
- package/src/runs/shared/nested-path.ts +52 -0
- package/src/runs/shared/nested-render.ts +119 -0
- package/src/runs/shared/parallel-utils.ts +153 -0
- package/src/runs/shared/parent-receipts.ts +59 -0
- package/src/runs/shared/parent-usage.ts +121 -0
- package/src/runs/shared/pi-args.ts +350 -0
- package/src/runs/shared/pi-spawn.ts +52 -0
- package/src/runs/shared/run-history-queries.ts +44 -0
- package/src/runs/shared/run-history.ts +100 -0
- package/src/runs/shared/run-id-ambiguity.ts +4 -0
- package/src/runs/shared/run-records.ts +607 -0
- package/src/runs/shared/session-cwd-preload.ts +27 -0
- package/src/runs/shared/single-output.ts +267 -0
- package/src/runs/shared/streaming-text.ts +18 -0
- package/src/runs/shared/structured-output.ts +68 -0
- package/src/runs/shared/subagent-control.ts +219 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +201 -0
- package/src/runs/shared/subagent-tool-loop-guard.ts +89 -0
- package/src/runs/shared/supervisor-questions.ts +415 -0
- package/src/runs/shared/workflow-graph.ts +220 -0
- package/src/runs/shared/workflow-policy.ts +125 -0
- package/src/runs/shared/worktree.ts +635 -0
- package/src/shared/agent-context-policy.ts +184 -0
- package/src/shared/artifacts.ts +95 -0
- package/src/shared/atomic-json.ts +16 -0
- package/src/shared/cooperative.ts +21 -0
- package/src/shared/execution-cwd.ts +14 -0
- package/src/shared/file-coalescer.ts +29 -0
- package/src/shared/fork-context.ts +68 -0
- package/src/shared/formatters.ts +84 -0
- package/src/shared/journal-reader.ts +333 -0
- package/src/shared/lazy-tools.ts +36 -0
- package/src/shared/model-info.ts +81 -0
- package/src/shared/native-session.ts +8 -0
- package/src/shared/native-tui.ts +10 -0
- package/src/shared/native-typebox.ts +14 -0
- package/src/shared/post-exit-stdio-guard.ts +204 -0
- package/src/shared/prompt-sections.ts +10 -0
- package/src/shared/session-entries.ts +49 -0
- package/src/shared/session-identity.ts +18 -0
- package/src/shared/settings.ts +384 -0
- package/src/shared/status-format.ts +123 -0
- package/src/shared/temp-root.ts +107 -0
- package/src/shared/types.ts +1260 -0
- package/src/shared/ui-mode.ts +5 -0
- package/src/shared/utils.ts +342 -0
- package/src/slash/prompt-template-bridge.ts +408 -0
- package/src/slash/slash-bridge.ts +178 -0
- package/src/slash/slash-commands.ts +617 -0
- package/src/slash/slash-live-state.ts +292 -0
- package/src/tui/action-hints.ts +69 -0
- package/src/tui/agent-history.ts +210 -0
- package/src/tui/agent-view.ts +1323 -0
- package/src/tui/render.ts +1550 -0
- package/tsconfig.build.json +12 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { NativeJournal, journalStamp, readOutputPage } from "../../shared/journal-reader.js";
|
|
4
|
+
import { snapshotNativeUsage, readNativeUsage } from "./native-usage.js";
|
|
5
|
+
import { runHistoryIndex, updateRunHistory } from "./history-index.js";
|
|
6
|
+
import { resolveCurrentSessionId } from "../../shared/session-identity.js";
|
|
7
|
+
import { writeAtomicJson } from "../../shared/atomic-json.js";
|
|
8
|
+
import { runCooperatively, runSynchronously } from "../../shared/cooperative.js";
|
|
9
|
+
import { compactForegroundResult, getFinalOutput, getSingleResultOutput, readStatus } from "../../shared/utils.js";
|
|
10
|
+
import { resolveSubagentResultStatus } from "../../intercom/result-intercom.js";
|
|
11
|
+
import { buildManagementControl, formatAgentProcessExit, formatRunAction } from "../../shared/status-format.js";
|
|
12
|
+
import { resolveSubagentIntercomTarget } from "../../intercom/intercom-bridge.js";
|
|
13
|
+
import { isDurableRun, readAsyncResultFile } from "../background/async-result-file.js";
|
|
14
|
+
import { exactAsyncRunLocation } from "../background/async-resume.js";
|
|
15
|
+
import { createAsyncRunDiscovery } from "../background/async-status.js";
|
|
16
|
+
import { reconcileAsyncRun } from "../background/stale-run-reconciler.js";
|
|
17
|
+
import { acceptanceHumanAction } from "./acceptance-evaluation.js";
|
|
18
|
+
import { formatRunIdAmbiguity } from "./run-id-ambiguity.js";
|
|
19
|
+
import { resolveFinalizationOutput } from "./acceptance-finalization.js";
|
|
20
|
+
import { parseAcceptanceReport, validateAcceptanceReportShape } from "./acceptance-reports.js";
|
|
21
|
+
import { sumAttemptUsage } from "./model-fallback.js";
|
|
22
|
+
import { workflowAgentNodes } from "./workflow-graph.js";
|
|
23
|
+
import { collectInvocationAgentNames } from "../../shared/settings.js";
|
|
24
|
+
import { compactOwnerResult, getRunMetadataDir, listOwnedRunQuestions, migrateSupervisorQuestionSteps, questionProcessAlive, readQuestionContract, readRunJson, saveAsyncRunResult, saveRunStatus, saveQuestionOwner, saveQuestionContract } from "./supervisor-questions.js";
|
|
25
|
+
import { ASYNC_DIR, DEFAULT_MAX_OUTPUT, RESULTS_DIR, SLASH_RESULT_TYPE, truncateOutput } from "../../shared/types.js";
|
|
26
|
+
export const OWNED_RUN_ENTRY = "subagent-run";
|
|
27
|
+
const failedAccountingSources = new Map();
|
|
28
|
+
/** Repair only recorded native billing evidence; never execute or verify work again. */
|
|
29
|
+
export function repairOwnedRunAccounting(run) {
|
|
30
|
+
const file = path.join(getRunMetadataDir(run.runId), "result.json");
|
|
31
|
+
if (!fs.existsSync(file))
|
|
32
|
+
return;
|
|
33
|
+
const saved = readAsyncResultFile(file);
|
|
34
|
+
let changed = false;
|
|
35
|
+
saved.results?.forEach((child, index) => {
|
|
36
|
+
if (child.accounting?.state !== "incomplete")
|
|
37
|
+
return;
|
|
38
|
+
const contract = readQuestionContract(run.runId, index, undefined, { readConfiguration: false });
|
|
39
|
+
const terminalEntryId = child.terminalEntryId ?? contract?.terminalEntryId;
|
|
40
|
+
if (!contract?.attemptBaseline || !terminalEntryId)
|
|
41
|
+
return;
|
|
42
|
+
const source = child.sessionFile ?? contract.sessionFile;
|
|
43
|
+
if (!source)
|
|
44
|
+
return;
|
|
45
|
+
const stat = fs.statSync(source, { bigint: true, throwIfNoEntry: false });
|
|
46
|
+
const key = `${run.runId}:${index}`, stamp = `${stat ? journalStamp(stat) : "missing"}:${terminalEntryId}:${JSON.stringify(contract.attemptBaseline)}`;
|
|
47
|
+
if (failedAccountingSources.get(key) === stamp)
|
|
48
|
+
return;
|
|
49
|
+
let usage;
|
|
50
|
+
try {
|
|
51
|
+
usage = readNativeUsage(source, new Set(contract.attemptBaseline), [], { terminalEntryId })?.[0];
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
failedAccountingSources.set(key, stamp);
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
if (!usage)
|
|
58
|
+
return;
|
|
59
|
+
failedAccountingSources.delete(key);
|
|
60
|
+
child.usage = usage;
|
|
61
|
+
child.accounting = { state: "complete" };
|
|
62
|
+
changed = true;
|
|
63
|
+
if (contract.result)
|
|
64
|
+
saveQuestionContract(run.runId, index, { accounting: child.accounting, result: { ...contract.result, usage, accounting: child.accounting } });
|
|
65
|
+
});
|
|
66
|
+
if (changed)
|
|
67
|
+
saveAsyncRunResult(run.runId, saved);
|
|
68
|
+
}
|
|
69
|
+
export function rememberOwnedRun(state, run) {
|
|
70
|
+
const previous = state.ownedRuns?.get(run.runId);
|
|
71
|
+
(state.ownedRuns ??= new Map()).set(run.runId, run);
|
|
72
|
+
if (JSON.stringify(previous) !== JSON.stringify(run)) {
|
|
73
|
+
state.persistOwnedRun?.(run);
|
|
74
|
+
updateRunHistory(state, run);
|
|
75
|
+
state.onRunsChanged?.();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export function resolveOwnedRun(state, requested) {
|
|
79
|
+
const id = requested.trim();
|
|
80
|
+
getRunMetadataDir(id); // Same ID boundary as the question and result files.
|
|
81
|
+
const exact = state.ownedRuns?.get(id);
|
|
82
|
+
if (exact && id !== "latest" && id !== "last")
|
|
83
|
+
return exact;
|
|
84
|
+
const runs = [...(state.ownedRuns?.values() ?? [])];
|
|
85
|
+
if (id === "latest" || id === "last")
|
|
86
|
+
return runs.sort((a, b) => b.startedAt - a.startedAt)[0];
|
|
87
|
+
const matches = runs.filter((run) => run.runId.startsWith(id));
|
|
88
|
+
if (matches.length > 1)
|
|
89
|
+
throw new Error(formatRunIdAmbiguity("owned", id, matches.map((run) => run.runId)));
|
|
90
|
+
return matches[0];
|
|
91
|
+
}
|
|
92
|
+
export function saveForegroundRun(input) {
|
|
93
|
+
const run = {
|
|
94
|
+
runId: input.runId, mode: input.mode, cwd: input.cwd, updatedAt: Date.now(),
|
|
95
|
+
...(input.error ? { error: input.error } : {}),
|
|
96
|
+
...(input.pausedReason ? { pausedReason: input.pausedReason } : {}),
|
|
97
|
+
children: input.results.map((result, index) => ({
|
|
98
|
+
agent: result.agent, index,
|
|
99
|
+
status: resolveSubagentResultStatus(result),
|
|
100
|
+
...(!result.detached ? { summary: (getSingleResultOutput(result) || result.error)?.slice(-8192) } : {}),
|
|
101
|
+
artifactPath: result.artifactPaths?.outputPath,
|
|
102
|
+
sessionFile: result.sessionFile,
|
|
103
|
+
effectiveAcceptance: result.acceptance?.effectiveAcceptance,
|
|
104
|
+
result: compactOwnerResult(input.runId, index, compactForegroundResult(result)),
|
|
105
|
+
})),
|
|
106
|
+
};
|
|
107
|
+
writeAtomicJson(path.join(getRunMetadataDir(input.runId), "foreground.json"), run);
|
|
108
|
+
return run;
|
|
109
|
+
}
|
|
110
|
+
function receiptDetails(entry) {
|
|
111
|
+
if (entry.type === "message" && entry.message.role === "toolResult" && ["subagent", "delegate", "agent_runs"].includes(entry.message.toolName))
|
|
112
|
+
return entry.message.details;
|
|
113
|
+
if (entry.type === "custom_message" && entry.customType === SLASH_RESULT_TYPE) {
|
|
114
|
+
const details = entry.details;
|
|
115
|
+
return details?.result?.details;
|
|
116
|
+
}
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
function sessionFiles(root) {
|
|
120
|
+
if (!fs.existsSync(root))
|
|
121
|
+
return [];
|
|
122
|
+
return fs.readdirSync(root, { withFileTypes: true }).flatMap((entry) => entry.isDirectory()
|
|
123
|
+
? sessionFiles(path.join(root, entry.name))
|
|
124
|
+
: entry.name.endsWith(".jsonl") ? [path.join(root, entry.name)] : []);
|
|
125
|
+
}
|
|
126
|
+
function recoverOutput(sessionFile, outputFile, endedAt) {
|
|
127
|
+
if (outputFile && fs.existsSync(outputFile))
|
|
128
|
+
return readOutputPage(outputFile).text;
|
|
129
|
+
if (!sessionFile || !fs.existsSync(sessionFile))
|
|
130
|
+
return undefined;
|
|
131
|
+
const journal = new NativeJournal(sessionFile);
|
|
132
|
+
for (const record of journal.branch(undefined, endedAt).reverse()) {
|
|
133
|
+
if (record.value.type !== "message" || record.value.message?.role !== "assistant")
|
|
134
|
+
continue;
|
|
135
|
+
const output = getFinalOutput([journal.body(record).message]);
|
|
136
|
+
if (output)
|
|
137
|
+
return output;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function recoverLegacyTerminalOutput(sessionFile, startedAt, endedAt, acceptance) {
|
|
141
|
+
const review = acceptance?.finalization;
|
|
142
|
+
const reviewedOutput = review?.status === "completed" ? review.turns.at(-1)?.rawOutput : undefined;
|
|
143
|
+
if (reviewedOutput?.trim())
|
|
144
|
+
return resolveFinalizationOutput(reviewedOutput, "") || undefined;
|
|
145
|
+
// Live and finalization logs are separate, mutable streams, not a final-answer receipt.
|
|
146
|
+
if (endedAt === undefined || !Number.isFinite(endedAt) || !sessionFile || !fs.existsSync(sessionFile))
|
|
147
|
+
return;
|
|
148
|
+
const journal = new NativeJournal(sessionFile);
|
|
149
|
+
const records = journal.branch(undefined, endedAt).filter(({ value }) => value.type === "message" && Date.parse(value.timestamp) >= startedAt);
|
|
150
|
+
const lastAssistant = records.findLastIndex(({ value }) => value.message?.role === "assistant");
|
|
151
|
+
const messages = records.map((record, index) => index === lastAssistant ? journal.body(record).message : record.value.message);
|
|
152
|
+
const index = messages.findLastIndex((message) => message.role === "assistant");
|
|
153
|
+
const last = messages[index];
|
|
154
|
+
if (last?.role !== "assistant" || last.errorMessage || !["stop", "toolUse"].includes(last.stopReason) || !Array.isArray(last.content))
|
|
155
|
+
return;
|
|
156
|
+
const calls = last.content.filter((part) => part.type === "toolCall");
|
|
157
|
+
if (!calls.length)
|
|
158
|
+
return index === messages.length - 1 && last.stopReason === "stop" ? resolveFinalizationOutput(getFinalOutput([last]), "") || undefined : undefined;
|
|
159
|
+
if (calls.length !== 1 || calls[0].name !== "structured_output")
|
|
160
|
+
return;
|
|
161
|
+
const following = messages.slice(index + 1);
|
|
162
|
+
const result = following.at(-1);
|
|
163
|
+
if (following.some((message) => message.role !== "toolResult") || result?.role !== "toolResult" || result.toolCallId !== calls[0].id || result.toolName !== "structured_output" || result.isError !== false)
|
|
164
|
+
return;
|
|
165
|
+
const value = calls[0].arguments.value;
|
|
166
|
+
if (!value || typeof value !== "object" || !("report" in value))
|
|
167
|
+
return;
|
|
168
|
+
if (typeof value.report === "string" && parseAcceptanceReport(value.report).report)
|
|
169
|
+
return resolveFinalizationOutput(value.report, "") || undefined;
|
|
170
|
+
if ("answer" in value && typeof value.answer === "string" && value.answer.trim() && !validateAcceptanceReportShape(value.report))
|
|
171
|
+
return value.answer;
|
|
172
|
+
}
|
|
173
|
+
export function workflowChildren(children, graph) {
|
|
174
|
+
if (!graph || (graph.mode !== "chain" && !children.some((child) => child.workflowNodeId)))
|
|
175
|
+
return children;
|
|
176
|
+
return workflowAgentNodes(graph).map((node, index) => {
|
|
177
|
+
const declared = children.find((child) => child.workflowNodeId === node.id);
|
|
178
|
+
return { ...declared, index, workflowNodeId: node.id, agent: node.agent ?? declared?.agent ?? "unknown",
|
|
179
|
+
...(node.itemKey !== undefined ? { label: node.label } : {}) };
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
function savedWorkflowNodes(status) {
|
|
183
|
+
if (!status?.workflowGraph || status.workflowGraph.runId !== status.runId)
|
|
184
|
+
return;
|
|
185
|
+
const nodes = workflowAgentNodes(status.workflowGraph);
|
|
186
|
+
if (nodes.length !== status.steps?.length || new Set(nodes.map((node) => node.id)).size !== nodes.length)
|
|
187
|
+
return;
|
|
188
|
+
if (nodes.some((node, index) => typeof node.id !== "string" || !node.id || (node.agent && node.agent !== status.steps[index].agent)))
|
|
189
|
+
return;
|
|
190
|
+
return nodes;
|
|
191
|
+
}
|
|
192
|
+
export function restoreOwnedRuns(state, ctx) {
|
|
193
|
+
return runSynchronously(restoreOwnedRunSteps(state, ctx));
|
|
194
|
+
}
|
|
195
|
+
export async function restoreOwnedRunsAsync(state, ctx) {
|
|
196
|
+
const onRunsChanged = state.onRunsChanged;
|
|
197
|
+
// Publish readiness once restoration is complete, before starting browse/UI work.
|
|
198
|
+
state.onRunsChanged = undefined;
|
|
199
|
+
try {
|
|
200
|
+
return await runCooperatively(restoreOwnedRunSteps(state, ctx));
|
|
201
|
+
}
|
|
202
|
+
finally {
|
|
203
|
+
state.onRunsChanged = onRunsChanged;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function* restoreOwnedRunSteps(state, ctx) {
|
|
207
|
+
const startedAt = Date.now();
|
|
208
|
+
const ownerSessionId = ctx.sessionManager.getSessionId();
|
|
209
|
+
const entries = ctx.sessionManager.getEntries();
|
|
210
|
+
state.ownedRuns = new Map();
|
|
211
|
+
yield* migrateSupervisorQuestionSteps(ownerSessionId);
|
|
212
|
+
for (const entry of entries) {
|
|
213
|
+
yield;
|
|
214
|
+
if (entry.type !== "custom" || entry.customType !== OWNED_RUN_ENTRY)
|
|
215
|
+
continue;
|
|
216
|
+
const run = ctx.sessionManager.getEntry(entry.id)?.data;
|
|
217
|
+
if (run?.ownerSessionId === ownerSessionId && run.runId && Array.isArray(run.children))
|
|
218
|
+
state.ownedRuns.set(run.runId, run);
|
|
219
|
+
}
|
|
220
|
+
// Forks copy old entries. Their old receipts are evidence, not ownership for the new parent.
|
|
221
|
+
const inheritedIds = new Set();
|
|
222
|
+
const parentFile = ctx.sessionManager.getHeader()?.parentSession;
|
|
223
|
+
if (parentFile && fs.existsSync(parentFile)) {
|
|
224
|
+
for (const id of snapshotNativeUsage(parentFile))
|
|
225
|
+
inheritedIds.add(id);
|
|
226
|
+
}
|
|
227
|
+
const recoveredReceipts = [];
|
|
228
|
+
for (const entry of entries) {
|
|
229
|
+
yield;
|
|
230
|
+
if (!(entry.type === "message" && entry.message.role === "toolResult" && ["subagent", "delegate", "agent_runs"].includes(entry.message.toolName)
|
|
231
|
+
|| entry.type === "custom_message" && entry.customType === SLASH_RESULT_TYPE))
|
|
232
|
+
continue;
|
|
233
|
+
const receipt = ctx.sessionManager.getEntry(entry.id);
|
|
234
|
+
if (receipt)
|
|
235
|
+
recoveredReceipts.push(receipt);
|
|
236
|
+
}
|
|
237
|
+
const calls = new Map();
|
|
238
|
+
const needsLegacyCalls = recoveredReceipts.some((entry) => { const details = receiptDetails(entry); return details && !state.ownedRuns.has(details.runId ?? details.asyncId ?? ""); });
|
|
239
|
+
for (const metadata of needsLegacyCalls ? entries : []) {
|
|
240
|
+
yield;
|
|
241
|
+
if (metadata.type !== "message" || metadata.message.role !== "assistant")
|
|
242
|
+
continue;
|
|
243
|
+
const entry = ctx.sessionManager.getEntry(metadata.id);
|
|
244
|
+
if (entry?.type !== "message" || entry.message.role !== "assistant" || !Array.isArray(entry.message.content))
|
|
245
|
+
continue;
|
|
246
|
+
for (const part of entry.message.content)
|
|
247
|
+
if (part.type === "toolCall" && ["subagent", "delegate"].includes(part.name))
|
|
248
|
+
calls.set(part.id, part.arguments);
|
|
249
|
+
}
|
|
250
|
+
for (const entry of recoveredReceipts) {
|
|
251
|
+
yield;
|
|
252
|
+
if (inheritedIds.has(entry.id) || (parentFile && !fs.existsSync(parentFile)))
|
|
253
|
+
continue;
|
|
254
|
+
const details = receiptDetails(entry);
|
|
255
|
+
const runId = details?.runId ?? details?.asyncId;
|
|
256
|
+
if (!runId || !details || !Array.isArray(details.results) || (details.mode !== "single" && details.mode !== "parallel" && details.mode !== "chain") || state.ownedRuns.has(runId))
|
|
257
|
+
continue;
|
|
258
|
+
const cwd = details.results.find((result) => result.sessionFile)?.sessionFile;
|
|
259
|
+
const request = entry.type === "message" && entry.message.role === "toolResult" ? calls.get(entry.message.toolCallId) : undefined;
|
|
260
|
+
const run = {
|
|
261
|
+
runId, ownerSessionId, rootRunId: details.managementControl?.revivedFromRunId ?? runId,
|
|
262
|
+
predecessorRunId: details.managementControl?.revivedFromRunId,
|
|
263
|
+
source: details.asyncId ? "async" : "foreground", mode: details.mode,
|
|
264
|
+
cwd: request?.cwd ? path.resolve(ctx.cwd, request.cwd) : ctx.cwd, task: details.results[0]?.task ?? request?.task ?? "Recovered delegated run",
|
|
265
|
+
startedAt: Date.parse(entry.timestamp), asyncDir: details.asyncDir, legacy: true,
|
|
266
|
+
children: details.results.length ? details.results.map((result, index) => ({ agent: result.agent, index, task: result.task, sessionFile: result.sessionFile })) : collectInvocationAgentNames(request ?? {}).map((agent, index) => ({ agent, index })),
|
|
267
|
+
};
|
|
268
|
+
try {
|
|
269
|
+
if (cwd && fs.existsSync(cwd)) {
|
|
270
|
+
const header = new NativeJournal(cwd).records[0]?.value;
|
|
271
|
+
if (header?.type === "session" && header.cwd)
|
|
272
|
+
run.cwd = header.cwd;
|
|
273
|
+
}
|
|
274
|
+
if (run.source === "foreground")
|
|
275
|
+
saveForegroundRun({ ...run, results: details.results.map((result) => ({ ...result, finalOutput: result.finalOutput ?? recoverOutput(result.sessionFile, result.artifactPaths?.outputPath, Date.parse(entry.timestamp)) })) });
|
|
276
|
+
let owner;
|
|
277
|
+
try {
|
|
278
|
+
owner = readRunJson(path.join(getRunMetadataDir(runId), "question-owner.json"));
|
|
279
|
+
}
|
|
280
|
+
catch { /* Unusable metadata can be repaired from the genuine receipt. */ }
|
|
281
|
+
if (typeof owner?.sessionId !== "string" || !owner.sessionId.trim())
|
|
282
|
+
saveQuestionOwner(runId, ownerSessionId);
|
|
283
|
+
rememberOwnedRun(state, run);
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
// The genuine receipt still establishes ownership. Keep completion
|
|
287
|
+
// unconfirmed when its supplemental output/context cannot be recovered.
|
|
288
|
+
rememberOwnedRun(state, { ...run, recoveryError: `Saved child recovery remains incomplete: ${String(error)}` });
|
|
289
|
+
console.error(`Could not recover legacy receipt ${runId}: ${String(error)}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// Pre-update background runs may have no parent tool receipt (for example slash launches).
|
|
293
|
+
const scan = createAsyncRunDiscovery(ASYNC_DIR, {
|
|
294
|
+
sessionId: ctx.sessionManager.getSessionFile() ?? resolveCurrentSessionId(ctx.sessionManager), ownerSessionId,
|
|
295
|
+
receiptRunIds: () => state.ownedRuns.keys(), skipInvalid: true,
|
|
296
|
+
});
|
|
297
|
+
function* discoverSteps() {
|
|
298
|
+
const records = yield* scan.steps();
|
|
299
|
+
for (const { location, status, durable } of records) {
|
|
300
|
+
yield;
|
|
301
|
+
try {
|
|
302
|
+
const asyncDir = location.asyncDir;
|
|
303
|
+
if (!asyncDir || !status)
|
|
304
|
+
continue;
|
|
305
|
+
const terminal = !["running", "queued"].includes(status.state);
|
|
306
|
+
if (!durable && terminal)
|
|
307
|
+
saveRunStatus(status.runId, status);
|
|
308
|
+
const old = state.ownedRuns.get(status.runId);
|
|
309
|
+
const nodes = savedWorkflowNodes(status);
|
|
310
|
+
const declared = status.mode === "chain" && nodes && !old?.children.some((child) => child.workflowNodeId) ? [] : old?.children ?? [];
|
|
311
|
+
const children = workflowChildren(declared, nodes ? status.workflowGraph : undefined);
|
|
312
|
+
rememberOwnedRun(state, {
|
|
313
|
+
...old, runId: status.runId, ownerSessionId, rootRunId: old?.rootRunId ?? status.runId,
|
|
314
|
+
source: "async", mode: status.mode, cwd: status.cwd ?? old?.cwd ?? ctx.cwd,
|
|
315
|
+
task: old?.task ?? "Recovered background run", startedAt: status.startedAt,
|
|
316
|
+
asyncDir, pid: status.pid, legacy: old?.legacy ?? !durable,
|
|
317
|
+
children: (status.steps ?? []).map((step, index) => ({ ...children.find((child) => child.index === index), agent: step.agent, index, ...(status.mode === "chain" && nodes?.[index] ? { workflowNodeId: nodes[index].id } : {}), label: step.label ?? children[index]?.label, sessionFile: step.sessionFile ?? (status.steps?.length === 1 ? status.sessionFile : undefined) })),
|
|
318
|
+
});
|
|
319
|
+
const resultPath = path.join(RESULTS_DIR, `${status.runId}.json`);
|
|
320
|
+
if (!durable && terminal && !fs.existsSync(path.join(getRunMetadataDir(status.runId), "result.json"))) {
|
|
321
|
+
if (fs.existsSync(resultPath))
|
|
322
|
+
saveAsyncRunResult(status.runId, readAsyncResultFile(resultPath));
|
|
323
|
+
else if (status.steps?.length && status.steps.every((step) => !["running", "pending"].includes(step.status))) {
|
|
324
|
+
const endedAt = status.endedAt ?? status.lastUpdate ?? status.startedAt;
|
|
325
|
+
const results = status.steps.map((step) => {
|
|
326
|
+
const sessionFile = step.sessionFile ?? (status.steps.length === 1 ? status.sessionFile : undefined);
|
|
327
|
+
return { agent: step.agent, sessionFile, model: step.model, acceptance: step.acceptance,
|
|
328
|
+
exitCode: step.exitCode, agentProcessExit: step.agentProcessExit, success: step.status === "complete" || step.status === "completed",
|
|
329
|
+
interrupted: step.status === "paused" || undefined, timedOut: step.status === "timed-out" || undefined, error: step.error,
|
|
330
|
+
output: recoverLegacyTerminalOutput(sessionFile, step.startedAt ?? status.startedAt, step.endedAt ?? status.endedAt, step.acceptance) ?? "" };
|
|
331
|
+
});
|
|
332
|
+
saveAsyncRunResult(status.runId, { id: status.runId, sessionId: status.sessionId, mode: status.mode, state: status.state,
|
|
333
|
+
success: status.state === "complete", error: status.error, timestamp: endedAt, cwd: status.cwd, asyncDir, sessionFile: status.sessionFile, results });
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
console.error(`Could not recover owned async metadata for '${location.resolvedId}':`, error);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return records;
|
|
342
|
+
}
|
|
343
|
+
const discover = () => runSynchronously(discoverSteps());
|
|
344
|
+
const records = yield* discoverSteps();
|
|
345
|
+
for (const run of state.ownedRuns.values()) {
|
|
346
|
+
yield;
|
|
347
|
+
try {
|
|
348
|
+
const stored = readRunJson(path.join(getRunMetadataDir(run.runId), "foreground.json"));
|
|
349
|
+
if (stored)
|
|
350
|
+
(state.foregroundRuns ??= new Map()).set(run.runId, stored);
|
|
351
|
+
if (run.children.some((child) => child.sessionFile) || !run.legacy)
|
|
352
|
+
continue;
|
|
353
|
+
const file = ctx.sessionManager.getSessionFile();
|
|
354
|
+
if (!file)
|
|
355
|
+
continue;
|
|
356
|
+
const root = path.join(path.dirname(file), path.basename(file, ".jsonl"), run.runId);
|
|
357
|
+
const files = sessionFiles(root).sort();
|
|
358
|
+
if (files.length)
|
|
359
|
+
rememberOwnedRun(state, { ...run, children: files.map((sessionFile, index) => ({ agent: run.children[index]?.agent ?? "unknown", index, sessionFile })) });
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
console.error(`Could not recover foreground owner ${run.runId}: ${String(error)}`);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return { startedAt, records, discover };
|
|
366
|
+
}
|
|
367
|
+
function normalizedState(value) {
|
|
368
|
+
if (value === "running" || value === "queued")
|
|
369
|
+
return "live";
|
|
370
|
+
if (value === "complete" || value === "completed")
|
|
371
|
+
return "completed";
|
|
372
|
+
if (value === "failed" || value === "timed-out")
|
|
373
|
+
return "failed";
|
|
374
|
+
if (value === "blocked")
|
|
375
|
+
return "blocked";
|
|
376
|
+
if (value === "paused")
|
|
377
|
+
return "paused";
|
|
378
|
+
return "unknown";
|
|
379
|
+
}
|
|
380
|
+
function processAlive(pid) {
|
|
381
|
+
return Boolean(pid && Number.isSafeInteger(pid) && pid > 0 && questionProcessAlive({ pid }));
|
|
382
|
+
}
|
|
383
|
+
function asyncChildResult(child, task) {
|
|
384
|
+
const { output, ...result } = child;
|
|
385
|
+
return {
|
|
386
|
+
...result, agent: child.agent ?? "unknown", task: child.task ?? task, exitCode: child.exitCode ?? (child.success ? 0 : 1),
|
|
387
|
+
finalOutput: child.finalOutput ?? output, usage: child.usage ?? sumAttemptUsage(child.modelAttempts ?? []),
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
function runAttention(run, executionState, pendingInput) {
|
|
391
|
+
return [
|
|
392
|
+
...(pendingInput ? ["awaiting_input"] : []),
|
|
393
|
+
...(run.review?.decision === "needs_changes" ? ["needs_changes"] : []),
|
|
394
|
+
...(run.review?.decision !== "accepted" && ["failed", "blocked", "paused", "unknown"].includes(executionState) ? [executionState] : []),
|
|
395
|
+
...(executionState === "completed" && !run.review ? ["unreviewed"] : []),
|
|
396
|
+
];
|
|
397
|
+
}
|
|
398
|
+
export function ownedRunView(run, state, options = {}) {
|
|
399
|
+
run = state.ownedRuns?.get(run.runId) ?? run;
|
|
400
|
+
const root = getRunMetadataDir(run.runId);
|
|
401
|
+
const foreground = readRunJson(path.join(root, "foreground.json")) ?? state.foregroundRuns?.get(run.runId);
|
|
402
|
+
const resultPath = path.join(root, "result.json");
|
|
403
|
+
const result = fs.existsSync(resultPath) ? readAsyncResultFile(resultPath) : undefined;
|
|
404
|
+
const location = exactAsyncRunLocation(run.runId, ASYNC_DIR, RESULTS_DIR);
|
|
405
|
+
const asyncDir = location.asyncDir ?? run.asyncDir;
|
|
406
|
+
const liveStatus = asyncDir ? readStatus(asyncDir) : null;
|
|
407
|
+
const savedStatus = liveStatus ?? readRunJson(path.join(root, "status.json"));
|
|
408
|
+
const durable = isDurableRun(savedStatus) || isDurableRun(readRunJson(path.join(root, "launch.json")));
|
|
409
|
+
const reconciliation = durable && options.reconcile !== false ? reconcileAsyncRun(asyncDir ?? root) : undefined;
|
|
410
|
+
const status = reconciliation?.status ?? savedStatus;
|
|
411
|
+
const contracts = new Map();
|
|
412
|
+
const contractDir = path.join(root, "contracts");
|
|
413
|
+
for (const name of fs.existsSync(contractDir) ? fs.readdirSync(contractDir) : []) {
|
|
414
|
+
if (!/^\d+\.json$/.test(name))
|
|
415
|
+
continue;
|
|
416
|
+
const index = Number(name.slice(0, -5));
|
|
417
|
+
const contract = readQuestionContract(run.runId, index, undefined, { endedAt: status?.steps?.[index]?.endedAt ?? result?.timestamp ?? foreground?.updatedAt, readConfiguration: options.readConfiguration });
|
|
418
|
+
if (contract)
|
|
419
|
+
contracts.set(index, contract);
|
|
420
|
+
}
|
|
421
|
+
const nodes = savedWorkflowNodes(status);
|
|
422
|
+
const declarations = workflowChildren(run.children, nodes ? status?.workflowGraph : undefined);
|
|
423
|
+
// Terminal snapshots contain materialized children; declared slots can include an empty fanout or unstarted downstream steps.
|
|
424
|
+
const terminalIndices = result?.results?.map((_, index) => index) ?? foreground?.children.map((child) => child.index);
|
|
425
|
+
const indices = new Set(terminalIndices ?? [...declarations.map((child) => child.index), ...contracts.keys(), ...(status?.steps?.map((_, index) => index) ?? [])]);
|
|
426
|
+
const sessions = new Map([...indices].map((index) => [index, foreground?.children.find((child) => child.index === index)?.sessionFile ?? result?.results?.[index]?.sessionFile ?? status?.steps?.[index]?.sessionFile ?? contracts.get(index)?.sessionFile]));
|
|
427
|
+
const sessionUses = new Map();
|
|
428
|
+
for (const file of sessions.values())
|
|
429
|
+
if (file)
|
|
430
|
+
sessionUses.set(file, (sessionUses.get(file) ?? 0) + 1);
|
|
431
|
+
const uncertainIndices = run.mode === "chain" && (!run.children.some((child) => child.workflowNodeId) || (run.source === "async" && status && !nodes));
|
|
432
|
+
const children = [...indices].sort((a, b) => a - b).map((index) => {
|
|
433
|
+
const boundSession = sessions.get(index);
|
|
434
|
+
const declared = uncertainIndices && !nodes
|
|
435
|
+
? run.children.find((child) => boundSession && sessionUses.get(boundSession) === 1 && child.sessionFile === boundSession)
|
|
436
|
+
: declarations.find((child) => child.index === index);
|
|
437
|
+
const contract = contracts.get(index);
|
|
438
|
+
const fg = foreground?.children.find((child) => child.index === index);
|
|
439
|
+
const bg = result?.results?.[index];
|
|
440
|
+
const step = status?.steps?.[index];
|
|
441
|
+
const sessionFile = boundSession ?? declared?.sessionFile;
|
|
442
|
+
const live = processAlive(contract?.pid) || ((!step || step.status === "running" || step.status === "pending") && processAlive(status?.pid ?? run.pid));
|
|
443
|
+
const pending = !fg && !bg && !contract?.pid && !contract?.result && ((!step || step.status === "pending") && processAlive(status?.pid ?? run.pid));
|
|
444
|
+
const childState = bg ? normalizedState(resolveSubagentResultStatus({ success: bg.success, exitCode: bg.exitCode ?? undefined, interrupted: bg.interrupted, acceptance: bg.acceptance, state: typeof bg.success !== "boolean" && bg.exitCode == null ? result?.terminalState : undefined }))
|
|
445
|
+
: fg && fg.status !== "detached" ? normalizedState(fg.status)
|
|
446
|
+
: contract?.result ? normalizedState(resolveSubagentResultStatus(contract.result))
|
|
447
|
+
: live || pending ? "live"
|
|
448
|
+
: step && !["running", "pending"].includes(step.status) ? normalizedState(step.status) : "unknown";
|
|
449
|
+
const task = contract?.task ?? declared?.task ?? fg?.result?.task ?? (run.children.length === 1 ? run.task : undefined);
|
|
450
|
+
const selection = contract?.modelSelection ?? step ?? fg?.result?.progress;
|
|
451
|
+
return {
|
|
452
|
+
agent: fg?.agent ?? bg?.agent ?? step?.agent ?? contract?.launch?.agent.name ?? declared?.agent ?? "unknown", index, workflowNodeId: declared?.workflowNodeId, sessionFile,
|
|
453
|
+
task, label: contract?.label ?? step?.label ?? declared?.label,
|
|
454
|
+
...(run.mode === "chain" && !declared?.workflowNodeId && (!boundSession || sessionUses.get(boundSession) !== 1) ? { identityUnavailable: true } : {}),
|
|
455
|
+
modelSelection: selection ? { model: selection.model, thinking: selection.thinking, modelStartedAt: selection.modelStartedAt } : undefined,
|
|
456
|
+
activity: childState === "live" ? (pending ? { ...step, status: "pending" } : step) : undefined,
|
|
457
|
+
state: childState, result: fg?.status !== "detached" && fg?.result ? fg.result : bg ? asyncChildResult(bg, task ?? "Original child assignment unavailable") : contract?.result ?? fg?.result,
|
|
458
|
+
launch: contract?.launch, configuration: contract?.launch ? "saved" : "legacy-partial",
|
|
459
|
+
...(sessionFile && !fs.existsSync(sessionFile) ? { missingSession: true } : {}),
|
|
460
|
+
};
|
|
461
|
+
});
|
|
462
|
+
const live = children.some((child) => child.state === "live") || (!result && (!status || status.state === "running" || status.state === "queued") && processAlive(status?.pid ?? run.pid));
|
|
463
|
+
const error = result?.error ?? foreground?.error ?? run.error ?? (status && !["running", "queued"].includes(status.state) ? status.error : undefined);
|
|
464
|
+
const executionState = error ? "failed" : result ? normalizedState(result.terminalState)
|
|
465
|
+
: durable && !live ? "unknown"
|
|
466
|
+
: live ? "live"
|
|
467
|
+
: children.some((child) => child.state === "failed") ? "failed"
|
|
468
|
+
: children.some((child) => child.state === "blocked") ? "blocked"
|
|
469
|
+
: children.some((child) => child.state === "paused") ? "paused"
|
|
470
|
+
: children.length && children.every((child) => child.state === "completed") ? (foreground?.pausedReason ? "paused" : "completed")
|
|
471
|
+
: status && !["running", "queued"].includes(status.state) ? normalizedState(status.state) : "unknown";
|
|
472
|
+
const pendingInput = options.pendingInput ?? listOwnedRunQuestions(run.ownerSessionId, run.runId).some((question) => question.state === "awaiting_input" || question.state === "answer_pending");
|
|
473
|
+
return {
|
|
474
|
+
...run, state: executionState, children, attention: runAttention(run, executionState, pendingInput),
|
|
475
|
+
canInterrupt: pendingInput || (live && savedStatus?.state === "running" && savedStatus.runId === run.runId),
|
|
476
|
+
updatedAt: result?.timestamp ?? status?.lastUpdate ?? foreground?.updatedAt ?? run.startedAt,
|
|
477
|
+
continuations: options.includeContinuations === false ? [] : [...(state.ownedRuns?.values() ?? [])].filter((candidate) => candidate.rootRunId === run.rootRunId && candidate.predecessorRunId).sort((a, b) => a.startedAt - b.startedAt).map((candidate) => ({ runId: candidate.runId, predecessorRunId: candidate.predecessorRunId, predecessorIndex: candidate.predecessorIndex })),
|
|
478
|
+
...(result ? { resultPath } : foreground ? { resultPath: path.join(root, "foreground.json") } : {}),
|
|
479
|
+
...(error ? { diagnosis: error } : executionState === "paused" && foreground?.pausedReason ? { diagnosis: foreground.pausedReason } : executionState === "unknown" ? { diagnosis: run.recoveryError ?? reconciliation?.message ?? "Completion is unconfirmed. Saved sessions are context, not proof of successful execution." } : {}),
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
function workflowDetails(graph) {
|
|
483
|
+
if (!graph)
|
|
484
|
+
return {};
|
|
485
|
+
if (graph.mode !== "chain")
|
|
486
|
+
return { workflowGraph: graph };
|
|
487
|
+
const current = graph.nodes.find((node) => node.id === graph.currentNodeId || node.children?.some((child) => child.id === graph.currentNodeId));
|
|
488
|
+
return { workflowGraph: graph, chainAgents: graph.nodes.map((node) => node.agent ?? node.label), totalSteps: graph.nodes.length,
|
|
489
|
+
...(current?.stepIndex !== undefined ? { currentStepIndex: current.stepIndex } : {}) };
|
|
490
|
+
}
|
|
491
|
+
export function ownedRunExecutionResult(run, state, index, includeProgress = false) {
|
|
492
|
+
try {
|
|
493
|
+
repairOwnedRunAccounting(run);
|
|
494
|
+
}
|
|
495
|
+
catch (error) {
|
|
496
|
+
console.error(`Native accounting for ${run.runId} remains incomplete:`, error);
|
|
497
|
+
}
|
|
498
|
+
const view = ownedRunView(run, state);
|
|
499
|
+
const location = exactAsyncRunLocation(run.runId, ASYNC_DIR, RESULTS_DIR);
|
|
500
|
+
const saved = location.resultPath ? readAsyncResultFile(location.resultPath) : undefined;
|
|
501
|
+
const limits = { ...DEFAULT_MAX_OUTPUT, ...(saved?.maxOutput ?? view.children[0]?.launch?.maxOutput) };
|
|
502
|
+
const children = view.children.filter((child) => index === undefined || child.index === index);
|
|
503
|
+
const projected = view.children.map((child) => {
|
|
504
|
+
if (!child.result)
|
|
505
|
+
return { ...child, result: undefined };
|
|
506
|
+
const { artifactPaths, ...result } = child.result;
|
|
507
|
+
const finalOutput = getSingleResultOutput(result);
|
|
508
|
+
const truncation = result.outputMode === "file-only" && result.exitCode === 0 && result.outputReference
|
|
509
|
+
? { text: result.outputReference.message, truncated: false } : truncateOutput(finalOutput, limits, artifactPaths?.outputPath);
|
|
510
|
+
const compacted = compactForegroundResult({ ...result, finalOutput: truncation.text,
|
|
511
|
+
...(truncation.truncated ? { truncation } : {}),
|
|
512
|
+
...(result.initialOutput ? { initialOutput: truncateOutput(result.initialOutput, limits, artifactPaths?.outputPath).text } : {}),
|
|
513
|
+
});
|
|
514
|
+
return { ...child, result: { ...compacted, ...(artifactPaths ? { artifactPaths } : {}) } };
|
|
515
|
+
});
|
|
516
|
+
const results = projected.filter((child) => index === undefined || child.index === index).flatMap((child) => {
|
|
517
|
+
if (!child.result)
|
|
518
|
+
return [];
|
|
519
|
+
const { artifactPaths, ...result } = child.result;
|
|
520
|
+
return [{ ...result, ...(artifactPaths?.inputPath && artifactPaths.outputPath && artifactPaths.metadataPath
|
|
521
|
+
? { artifactPaths: { inputPath: artifactPaths.inputPath, outputPath: artifactPaths.outputPath, metadataPath: artifactPaths.metadataPath } } : {}) }];
|
|
522
|
+
});
|
|
523
|
+
let text = (index === undefined ? saved?.summary : undefined) || [...children.map((child) => child.result ? getSingleResultOutput(child.result) || child.result.error : undefined), view.diagnosis].filter(Boolean).join("\n\n");
|
|
524
|
+
const failed = index === undefined ? ["failed", "unknown"].includes(view.state) : children.length === 0 || children.some((child) => ["failed", "unknown"].includes(child.state));
|
|
525
|
+
if (failed && saved?.error && !text.includes(saved.error))
|
|
526
|
+
text = `${saved.error}\n\n${text}`;
|
|
527
|
+
const logPath = path.join(location.asyncDir ?? run.asyncDir ?? getRunMetadataDir(run.runId), `subagent-log-${run.runId}.md`);
|
|
528
|
+
const artifactPath = run.mode === "single" ? results[0]?.artifactPaths?.outputPath : fs.existsSync(logPath) ? logPath : undefined;
|
|
529
|
+
const referenceOnly = view.state === "completed" && run.mode === "single" && results.length === 1 && results[0]?.outputMode === "file-only" && results[0].outputReference;
|
|
530
|
+
const truncation = referenceOnly ? { text: referenceOnly.message, truncated: false }
|
|
531
|
+
: view.state === "blocked" ? { text, truncated: false } : truncateOutput(text, limits, artifactPath);
|
|
532
|
+
const files = results.flatMap((result) => result.artifactPaths ? [result.artifactPaths] : []);
|
|
533
|
+
const progressSummary = { toolCount: results.reduce((total, result) => total + (result.progressSummary?.toolCount ?? 0), 0),
|
|
534
|
+
tokens: results.reduce((total, result) => total + (result.progressSummary?.tokens ?? 0), 0),
|
|
535
|
+
durationMs: saved?.durationMs ?? Math.max(0, ...results.map((result) => result.progressSummary?.durationMs ?? 0)) };
|
|
536
|
+
const share = saved?.shareUrl ? `Session: ${saved.shareUrl}` : saved?.shareError ? `Session share error: ${saved.shareError}` : undefined;
|
|
537
|
+
const identity = run.predecessorRunId ? `Run: ${run.runId}\nPredecessor: ${run.predecessorRunId} (child ${run.predecessorIndex ?? 0})` : undefined;
|
|
538
|
+
return { content: [{ type: "text", text: [identity, truncation.text || `Run ${run.runId}: ${view.state}.`, share].filter(Boolean).join("\n\n") }],
|
|
539
|
+
...(failed ? { isError: true } : {}),
|
|
540
|
+
details: { mode: run.mode, runId: run.runId, asyncId: run.runId, asyncDir: location.asyncDir ?? run.asyncDir, results,
|
|
541
|
+
run: { ...view, children: projected }, progressSummary,
|
|
542
|
+
...(includeProgress ? { progress: ownedRunProgressResult(run, state, index).details.progress } : {}),
|
|
543
|
+
...(saved?.shareUrl ? { shareUrl: saved.shareUrl } : {}),
|
|
544
|
+
...(saved?.gistUrl ? { gistUrl: saved.gistUrl } : {}),
|
|
545
|
+
...(saved?.shareError ? { shareError: saved.shareError } : {}),
|
|
546
|
+
...(files.length ? { artifacts: { dir: saved?.artifactsDir ?? path.dirname(files[0].outputPath), files } } : {}),
|
|
547
|
+
...(truncation.truncated ? { truncation } : {}),
|
|
548
|
+
...(saved?.outputs ? { outputs: saved.outputs } : {}), ...workflowDetails(saved?.workflowGraph) } };
|
|
549
|
+
}
|
|
550
|
+
/** Project the run owner's native activity for waiting callers without copying its journal. */
|
|
551
|
+
export function ownedRunProgressResult(run, state, index, view = ownedRunView(run, state, { readConfiguration: false, includeContinuations: false })) {
|
|
552
|
+
const status = readStatus(run.asyncDir ?? getRunMetadataDir(run.runId));
|
|
553
|
+
const children = view.children.filter((child) => index === undefined || child.index === index);
|
|
554
|
+
const progress = children.map((child) => {
|
|
555
|
+
const step = status?.steps?.[child.index];
|
|
556
|
+
const live = child.state === "live";
|
|
557
|
+
return {
|
|
558
|
+
index: child.index, agent: child.agent, task: child.task ?? run.task,
|
|
559
|
+
status: step?.status ?? (child.state === "live" ? "pending" : child.state === "unknown" ? "failed" : child.state),
|
|
560
|
+
model: step?.model, thinking: step?.thinking, modelStartedAt: step?.modelStartedAt,
|
|
561
|
+
activityState: step?.activityState, lastActivityAt: step?.lastActivityAt, skills: step?.skills,
|
|
562
|
+
...(live ? { currentTool: step?.currentTool, currentToolArgs: step?.currentToolArgs, currentToolStartedAt: step?.currentToolStartedAt,
|
|
563
|
+
currentPath: step?.currentPath, streamingText: step?.streamingText } : {}),
|
|
564
|
+
recentTools: live ? step?.recentTools?.slice(-10) ?? [] : [], recentOutput: live ? step?.recentOutput?.slice(-10) ?? [] : [],
|
|
565
|
+
toolCount: step?.toolCount ?? 0, turnCount: step?.turnCount, tokens: step?.tokens?.total ?? 0,
|
|
566
|
+
durationMs: step?.durationMs ?? Math.max(0, Date.now() - (step?.startedAt ?? run.startedAt)), error: step?.error,
|
|
567
|
+
};
|
|
568
|
+
});
|
|
569
|
+
const results = progress.map((item, position) => ({
|
|
570
|
+
agent: item.agent, task: item.task, exitCode: children[position]?.result?.exitCode ?? 0,
|
|
571
|
+
usage: children[position]?.result?.usage ?? { input: status?.steps?.[item.index]?.tokens?.input ?? 0, output: status?.steps?.[item.index]?.tokens?.output ?? 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: item.turnCount ?? 0 },
|
|
572
|
+
progress: item, model: item.model, sessionFile: children[position]?.sessionFile,
|
|
573
|
+
finalOutput: getSingleResultOutput(children[position]?.result ?? {}) || item.streamingText || item.recentOutput.at(-1),
|
|
574
|
+
}));
|
|
575
|
+
return { content: [{ type: "text", text: progress.map((item) => `${item.agent}: ${item.status}${item.currentTool ? ` — ${item.currentTool}${item.currentToolArgs ? ` ${item.currentToolArgs}` : ""}` : ""}`).join("\n") }],
|
|
576
|
+
details: { mode: run.mode, runId: run.runId, asyncId: run.runId, asyncDir: run.asyncDir, results, progress, ...workflowDetails(status?.workflowGraph) } };
|
|
577
|
+
}
|
|
578
|
+
function compact(value, max = 180) {
|
|
579
|
+
const text = value.replace(/\s+/g, " ").trim();
|
|
580
|
+
return text.length > max ? `${text.slice(0, max - 1)}…` : text;
|
|
581
|
+
}
|
|
582
|
+
function ownedRunControl(view) {
|
|
583
|
+
const active = view.children.find((child) => child.state === "live" && child.activity?.status !== "pending");
|
|
584
|
+
const resumable = active ?? view.children.find((child) => child.state !== "live" && child.sessionFile && !child.missingSession);
|
|
585
|
+
return buildManagementControl({ state: view.state, runId: view.runId, index: resumable?.index, canReview: view.state !== "live", canResume: Boolean(resumable), canNudge: Boolean(active), canInterrupt: view.canInterrupt, intercomTarget: active ? resolveSubagentIntercomTarget(view.runId, active.agent, active.index) : undefined });
|
|
586
|
+
}
|
|
587
|
+
export function ownedRunStatusResult(run, state, runtime, options = {}) {
|
|
588
|
+
const view = ownedRunView(run, state);
|
|
589
|
+
const liveControl = view.state === "live" && runtime?.details.managementControl?.state === "live" ? runtime.details.managementControl : undefined;
|
|
590
|
+
const control = liveControl ?? ownedRunControl(view);
|
|
591
|
+
const lines = [
|
|
592
|
+
`Run: ${run.runId}`, `State: ${view.state}`, `Mode: ${run.mode} (${run.source})`, `Task: ${options.full ? run.task : compact(run.task)}`, `Launch cwd: ${run.cwd}`,
|
|
593
|
+
`Root: ${run.rootRunId}`, ...(run.predecessorRunId ? [`Predecessor: ${run.predecessorRunId} (child ${run.predecessorIndex ?? 0})`] : []),
|
|
594
|
+
`Parent review (not sent to child): ${run.review?.decision ?? "unreviewed"}${run.review?.message ? ` — ${options.full ? run.review.message : compact(run.review.message)}` : ""}`,
|
|
595
|
+
`Notification: ${run.delivery ? `recorded ${new Date(run.delivery.notifiedAt).toISOString()}; intercom ${run.delivery.intercomDelivered ? "delivered" : "not confirmed"}` : "not recorded"}`,
|
|
596
|
+
...(view.attention.length ? [`Attention: ${view.attention.join(", ")}`] : []),
|
|
597
|
+
...(view.resultPath ? [`Result: ${view.resultPath}`] : []), ...(view.diagnosis ? [view.diagnosis] : []),
|
|
598
|
+
...(runtime && !runtime.isError && (run.source === "async" || liveControl) ? runtime.content.flatMap((part) => part.type === "text" ? [part.text] : []) : []),
|
|
599
|
+
];
|
|
600
|
+
for (const child of view.children) {
|
|
601
|
+
lines.push(`Child ${child.index}: ${child.agent} | ${child.state}${child.result?.acceptance ? ` | validation: ${child.result.acceptance.status}` : ""}`);
|
|
602
|
+
const humanAction = acceptanceHumanAction(child.result?.acceptance);
|
|
603
|
+
if (humanAction)
|
|
604
|
+
lines.push(` Needs your action — acceptance incomplete:\n${humanAction}`);
|
|
605
|
+
if (child.state !== "live")
|
|
606
|
+
lines.push(` ${formatAgentProcessExit(child.result?.agentProcessExit)}`);
|
|
607
|
+
if (child.sessionFile)
|
|
608
|
+
lines.push(` Session: ${child.sessionFile}${child.missingSession ? " (missing; continuation unavailable)" : ""}`);
|
|
609
|
+
if (child.result?.auditPath)
|
|
610
|
+
lines.push(` Child observations / uncommitted output: ${child.result.auditPath}`);
|
|
611
|
+
if (child.result?.fullOutputPath)
|
|
612
|
+
lines.push(` Full output: ${child.result.fullOutputPath}`);
|
|
613
|
+
if (child.result?.accounting?.state === "incomplete")
|
|
614
|
+
lines.push(` Accounting incomplete: ${child.result.accounting.error ?? "Native billing evidence is unavailable; no work was repeated."}`);
|
|
615
|
+
const artifact = child.result?.artifactPaths?.outputPath;
|
|
616
|
+
if (artifact)
|
|
617
|
+
lines.push(` Artifact: ${artifact}${fs.existsSync(artifact) ? "" : " (missing)"}`);
|
|
618
|
+
const metadata = child.result?.artifactPaths?.metadataPath;
|
|
619
|
+
if (metadata)
|
|
620
|
+
lines.push(` Result metadata (acceptance details when configured): ${metadata}${fs.existsSync(metadata) ? "" : " (missing)"}`);
|
|
621
|
+
if (child.launch) {
|
|
622
|
+
const launch = child.launch;
|
|
623
|
+
lines.push(` Effective model: ${launch.model ?? "native default"}; thinking: ${launch.thinking ?? "native default"}`, ` Output: ${launch.output || "disabled"} (${launch.outputMode}); configuration: saved launch snapshot`);
|
|
624
|
+
if (options.full)
|
|
625
|
+
lines.push(` Saved launch configuration:\n${JSON.stringify(launch, null, 2)}`);
|
|
626
|
+
}
|
|
627
|
+
else
|
|
628
|
+
lines.push(" Configuration: legacy-partial; original profile snapshot was not recorded.");
|
|
629
|
+
const output = child.result && getSingleResultOutput(child.result);
|
|
630
|
+
if (output)
|
|
631
|
+
lines.push(` Result: ${compact(output, 600)}`);
|
|
632
|
+
if (child.result?.error)
|
|
633
|
+
lines.push(` Error: ${child.result.error}`);
|
|
634
|
+
}
|
|
635
|
+
if (control.capabilities.includes("review"))
|
|
636
|
+
lines.push(`Review (parent-only, not sent to child): ${formatRunAction("review", run.runId, { decision: "accepted" }, options.childSafe)} or decision: "needs_changes".`);
|
|
637
|
+
if (view.continuations.length)
|
|
638
|
+
lines.push("Continuation history:", ...view.continuations.map((next) => ` ${next.predecessorRunId}:${next.predecessorIndex ?? 0} -> ${next.runId}`));
|
|
639
|
+
if (control.capabilities.includes("resume"))
|
|
640
|
+
lines.push(`Continue: ${formatRunAction("resume", run.runId, { ...(view.children.length > 1 ? { index: control.nextActions.find((action) => action.action === "resume")?.index ?? 0 } : {}), message: "..." }, options.childSafe)}`);
|
|
641
|
+
if (!options.full)
|
|
642
|
+
lines.push(`Full task/configuration: ${formatRunAction("status", run.runId, { full: true }, options.childSafe)}`);
|
|
643
|
+
return {
|
|
644
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
645
|
+
details: { ...runtime?.details, mode: "management", results: [], run: view, managementControl: control },
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
function unavailableRunView(run, error) {
|
|
649
|
+
return { ...run, state: "unknown", updatedAt: run.startedAt, attention: ["unknown"], canInterrupt: false, continuations: [],
|
|
650
|
+
children: run.children.map((child) => ({ ...child, state: "unknown", configuration: "legacy-partial" })),
|
|
651
|
+
diagnosis: `Saved owner records unavailable: ${String(error)}. Completion is unconfirmed.` };
|
|
652
|
+
}
|
|
653
|
+
export async function ownedRunList(state, params) {
|
|
654
|
+
const limit = params.limit ?? 20, sort = params.sort;
|
|
655
|
+
if (sort === "relevance")
|
|
656
|
+
throw new Error("Run list sort must be attention, newest, or oldest.");
|
|
657
|
+
const indexed = await (await runHistoryIndex(state, true)).listRuns({ ...params, sort });
|
|
658
|
+
// The index orders observations; only the bounded selected page gets authoritative controls.
|
|
659
|
+
const page = indexed.rows.map((row) => {
|
|
660
|
+
const run = state.ownedRuns?.get(row.runId);
|
|
661
|
+
if (!run || run.ownerSessionId !== row.ownerSessionId)
|
|
662
|
+
throw new Error("Owning session changed while listing runs.");
|
|
663
|
+
try {
|
|
664
|
+
return ownedRunView(run, state, { includeContinuations: false, readConfiguration: false });
|
|
665
|
+
}
|
|
666
|
+
catch (error) {
|
|
667
|
+
return unavailableRunView(run, error);
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
const owned = [...(state.ownedRuns?.values() ?? [])];
|
|
671
|
+
const runs = page.map(({ runId, source, mode, cwd, task, state: runState, updatedAt, attention, review, rootRunId, predecessorRunId, predecessorIndex, children }) => ({ runId, source, mode, cwd, task: compact(task, 2048), state: runState, updatedAt, attention, review, rootRunId, predecessorRunId, predecessorIndex, continuations: owned.filter((candidate) => candidate.predecessorRunId === runId).map((candidate) => candidate.runId), summary: compact(children.map((child) => child.result ? getSingleResultOutput(child.result) || child.result.error || "" : "").filter(Boolean).join(" | ")) }));
|
|
672
|
+
const controls = page.map(ownedRunControl);
|
|
673
|
+
const { offset, nextOffset, nextCursor, total, freshness, version } = indexed;
|
|
674
|
+
const next = nextCursor ? { action: "list", cursor: nextCursor, limit, ...(sort ? { sort } : {}), ...(params.agent ? { agent: params.agent } : {}), ...(params.state ? { state: params.state } : {}), ...(params.text ? { text: params.text } : {}) } : undefined;
|
|
675
|
+
return {
|
|
676
|
+
content: [{ type: "text", text: total ? [`Owned runs: ${total} (showing ${page.length ? `${offset + 1}–${offset + page.length}` : "none"}; ${params.sort ?? "attention"} order)`, ...(freshness.state !== "current" ? [`Browse index: ${freshness.state}; ordering/filter observations may be incomplete. Selected controls are checked against owner records.`] : []), ...runs.map((run) => `- ${run.runId} | ${run.state}${run.attention.length ? ` | ${run.attention.join(", ")}` : ""} | ${compact(run.task)}${run.summary ? ` | ${run.summary}` : ""} | Launch cwd: ${run.cwd}${run.predecessorRunId ? ` | from ${run.predecessorRunId}:${run.predecessorIndex ?? 0}` : ""}${run.continuations.length ? ` | continued as ${run.continuations.join(", ")} (separate results/reviews)` : ""}`), ...(next ? [`Next: agent_runs(${JSON.stringify(next)})`] : [])].join("\n") : "No delegated runs match in this owning session." }],
|
|
677
|
+
details: { mode: "management", results: [], runs, managementControls: controls, managementControl: controls.find((control) => control.state === "live"), runList: { total, offset, limit, version, freshness, ...(nextOffset !== undefined ? { nextOffset } : {}), ...(nextCursor ? { nextCursor } : {}) } },
|
|
678
|
+
};
|
|
679
|
+
}
|