@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,197 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { readLinuxProcess } from "../pi-intercom/broker/pid.js";
|
|
3
|
+
/** Capture the detached group leader's birth identity; Pi may replace its command-line title. */
|
|
4
|
+
export function readChildProcessIdentity(pid) {
|
|
5
|
+
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
6
|
+
return undefined;
|
|
7
|
+
if (process.platform === "linux")
|
|
8
|
+
return readLinuxProcess(pid)?.identity;
|
|
9
|
+
try {
|
|
10
|
+
const identity = execFileSync("ps", ["-p", String(pid), "-o", "lstart=", "-o", "pgid="], {
|
|
11
|
+
encoding: "utf8", timeout: 1000, stdio: ["ignore", "pipe", "ignore"],
|
|
12
|
+
env: { ...process.env, LC_ALL: "C" },
|
|
13
|
+
}).trim().replace(/\s+/g, " ");
|
|
14
|
+
return Number(identity.split(" ").at(-1)) === pid ? identity : undefined;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function trySignalChild(child, signal) {
|
|
21
|
+
try {
|
|
22
|
+
return child.kill(signal);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function isChildTreeAlive(child) {
|
|
29
|
+
if (child.pid) {
|
|
30
|
+
try {
|
|
31
|
+
process.kill(-child.pid, 0);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (error.code === "EPERM")
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
return child.kill(0);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Signal the detached child's process group, falling back if it is not a group leader. */
|
|
47
|
+
export function trySignalChildTree(child, signal) {
|
|
48
|
+
if (child.pid) {
|
|
49
|
+
try {
|
|
50
|
+
process.kill(-child.pid, signal);
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Fall back when the child is not a process-group leader.
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return trySignalChild(child, signal);
|
|
58
|
+
}
|
|
59
|
+
/** One lifetime for the owned process group, including descendants holding its pipes open. */
|
|
60
|
+
export function attachChildProcessLifecycle(child) {
|
|
61
|
+
let closed = false;
|
|
62
|
+
let exited = false;
|
|
63
|
+
let stopping = false;
|
|
64
|
+
let settledCleanup = false;
|
|
65
|
+
let agentProcessExit;
|
|
66
|
+
let drainTimer;
|
|
67
|
+
let escalationTimer;
|
|
68
|
+
const clearDrain = () => {
|
|
69
|
+
clearTimeout(drainTimer);
|
|
70
|
+
drainTimer = undefined;
|
|
71
|
+
};
|
|
72
|
+
const clearTimers = () => {
|
|
73
|
+
clearDrain();
|
|
74
|
+
clearTimeout(escalationTimer);
|
|
75
|
+
escalationTimer = undefined;
|
|
76
|
+
};
|
|
77
|
+
const signalTree = () => {
|
|
78
|
+
// Pi print mode uses SIGTERM to dispose its own detached tool processes.
|
|
79
|
+
const sent = trySignalChildTree(child, "SIGTERM");
|
|
80
|
+
escalationTimer = setTimeout(() => {
|
|
81
|
+
if (isChildTreeAlive(child))
|
|
82
|
+
trySignalChildTree(child, "SIGKILL");
|
|
83
|
+
}, 3000);
|
|
84
|
+
escalationTimer.unref();
|
|
85
|
+
return sent;
|
|
86
|
+
};
|
|
87
|
+
const terminate = () => {
|
|
88
|
+
// Cancellation overrides a pending or already-started successful settlement cleanup.
|
|
89
|
+
settledCleanup = false;
|
|
90
|
+
clearDrain();
|
|
91
|
+
if (closed || stopping)
|
|
92
|
+
return;
|
|
93
|
+
stopping = true;
|
|
94
|
+
signalTree();
|
|
95
|
+
};
|
|
96
|
+
const clearStdioGuard = attachPostExitStdioGuard(child, { idleMs: 2000, hardMs: 8000 });
|
|
97
|
+
child.on("exit", (code, signal) => {
|
|
98
|
+
exited = true;
|
|
99
|
+
agentProcessExit = { pid: child.pid, code, signal, at: Date.now() };
|
|
100
|
+
clearTimers();
|
|
101
|
+
// The leader may exit before resistant descendants release inherited stdio.
|
|
102
|
+
trySignalChildTree(child, "SIGKILL");
|
|
103
|
+
});
|
|
104
|
+
const dispose = () => {
|
|
105
|
+
closed = true;
|
|
106
|
+
clearTimers();
|
|
107
|
+
clearStdioGuard();
|
|
108
|
+
};
|
|
109
|
+
child.on("close", dispose);
|
|
110
|
+
child.on("error", dispose);
|
|
111
|
+
return {
|
|
112
|
+
terminate,
|
|
113
|
+
get stopping() { return stopping; },
|
|
114
|
+
get agentProcessExit() { return agentProcessExit; },
|
|
115
|
+
get settledCleanup() { return settledCleanup; },
|
|
116
|
+
observeEvent(type) {
|
|
117
|
+
if (closed || exited || stopping)
|
|
118
|
+
return;
|
|
119
|
+
if (type === "agent_settled") {
|
|
120
|
+
if (drainTimer)
|
|
121
|
+
return;
|
|
122
|
+
drainTimer = setTimeout(() => {
|
|
123
|
+
drainTimer = undefined;
|
|
124
|
+
stopping = true;
|
|
125
|
+
settledCleanup = signalTree();
|
|
126
|
+
}, 1000);
|
|
127
|
+
drainTimer.unref();
|
|
128
|
+
}
|
|
129
|
+
else if (type === "agent_start" || type === "turn_start" || type?.startsWith("message_") || type?.startsWith("tool_execution_")) {
|
|
130
|
+
clearDrain();
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
export function attachPostExitStdioGuard(child, options) {
|
|
136
|
+
const { idleMs, hardMs } = options;
|
|
137
|
+
let exited = false;
|
|
138
|
+
let stdoutEnded = false;
|
|
139
|
+
let stderrEnded = false;
|
|
140
|
+
let idleTimer;
|
|
141
|
+
let hardTimer;
|
|
142
|
+
const destroyUnendedStdio = () => {
|
|
143
|
+
if (!stdoutEnded) {
|
|
144
|
+
try {
|
|
145
|
+
child.stdout?.destroy();
|
|
146
|
+
}
|
|
147
|
+
catch { }
|
|
148
|
+
}
|
|
149
|
+
if (!stderrEnded) {
|
|
150
|
+
try {
|
|
151
|
+
child.stderr?.destroy();
|
|
152
|
+
}
|
|
153
|
+
catch { }
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
const clearTimers = () => {
|
|
157
|
+
if (idleTimer) {
|
|
158
|
+
clearTimeout(idleTimer);
|
|
159
|
+
idleTimer = undefined;
|
|
160
|
+
}
|
|
161
|
+
if (hardTimer) {
|
|
162
|
+
clearTimeout(hardTimer);
|
|
163
|
+
hardTimer = undefined;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
const armIdleTimer = () => {
|
|
167
|
+
if (!exited)
|
|
168
|
+
return;
|
|
169
|
+
if (idleTimer)
|
|
170
|
+
clearTimeout(idleTimer);
|
|
171
|
+
idleTimer = setTimeout(destroyUnendedStdio, idleMs);
|
|
172
|
+
idleTimer.unref?.();
|
|
173
|
+
};
|
|
174
|
+
child.stdout?.on("data", armIdleTimer);
|
|
175
|
+
child.stderr?.on("data", armIdleTimer);
|
|
176
|
+
child.stdout?.on("end", () => {
|
|
177
|
+
stdoutEnded = true;
|
|
178
|
+
if (stdoutEnded && stderrEnded)
|
|
179
|
+
clearTimers();
|
|
180
|
+
});
|
|
181
|
+
child.stderr?.on("end", () => {
|
|
182
|
+
stderrEnded = true;
|
|
183
|
+
if (stdoutEnded && stderrEnded)
|
|
184
|
+
clearTimers();
|
|
185
|
+
});
|
|
186
|
+
child.on("exit", () => {
|
|
187
|
+
exited = true;
|
|
188
|
+
armIdleTimer();
|
|
189
|
+
if (hardTimer)
|
|
190
|
+
return;
|
|
191
|
+
hardTimer = setTimeout(destroyUnendedStdio, hardMs);
|
|
192
|
+
hardTimer.unref?.();
|
|
193
|
+
});
|
|
194
|
+
child.on("close", clearTimers);
|
|
195
|
+
child.on("error", clearTimers);
|
|
196
|
+
return clearTimers;
|
|
197
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function setPromptSection(options, name, content) {
|
|
2
|
+
options.sections[name] = content;
|
|
3
|
+
// Pi projects an earlier exact override instead of sections. Preserve its content;
|
|
4
|
+
// this compatibility path replaces the leading prompt and cannot preserve its cache prefix.
|
|
5
|
+
if (options.forceSystemPrompt !== undefined) {
|
|
6
|
+
options.forceSystemPrompt += `\n\n<${name}>\n${content}\n</${name}>`;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** Follow append-only parent links; rescan when entries were replaced or appended off the active branch. */
|
|
2
|
+
export class SessionEntryCursor {
|
|
3
|
+
manager;
|
|
4
|
+
sessionId;
|
|
5
|
+
file;
|
|
6
|
+
known = new Map();
|
|
7
|
+
anchor;
|
|
8
|
+
reset() {
|
|
9
|
+
this.manager = undefined;
|
|
10
|
+
this.known.clear();
|
|
11
|
+
this.anchor = undefined;
|
|
12
|
+
}
|
|
13
|
+
read(manager) {
|
|
14
|
+
const sessionId = manager.getSessionId(), file = manager.getSessionFile?.(), count = manager.getEntryCount?.();
|
|
15
|
+
let reset = this.manager !== manager || this.sessionId !== sessionId || this.file !== file
|
|
16
|
+
|| count !== undefined && count < this.known.size
|
|
17
|
+
|| Boolean(this.anchor && manager.getEntry && manager.getEntry(this.anchor.id) !== this.known.get(this.anchor.id));
|
|
18
|
+
let entries;
|
|
19
|
+
if (!reset && count !== undefined && manager.getEntry && manager.getLeafId) {
|
|
20
|
+
const appended = [], visited = new Set();
|
|
21
|
+
let id = manager.getLeafId();
|
|
22
|
+
while (id && !this.known.has(id) && !visited.has(id)) {
|
|
23
|
+
visited.add(id);
|
|
24
|
+
const entry = manager.getEntry(id);
|
|
25
|
+
if (!entry)
|
|
26
|
+
break;
|
|
27
|
+
appended.push(entry);
|
|
28
|
+
id = entry.parentId;
|
|
29
|
+
}
|
|
30
|
+
if (count === this.known.size + appended.length && (!id || this.known.has(id)))
|
|
31
|
+
entries = appended.reverse();
|
|
32
|
+
}
|
|
33
|
+
if (!entries) {
|
|
34
|
+
const all = manager.getEntries();
|
|
35
|
+
entries = all.filter((entry) => !this.known.has(entry.id));
|
|
36
|
+
if (all.length !== this.known.size + entries.length
|
|
37
|
+
|| all.some((entry) => this.known.has(entry.id) && this.known.get(entry.id) !== entry))
|
|
38
|
+
reset = true;
|
|
39
|
+
if (reset) {
|
|
40
|
+
this.known.clear();
|
|
41
|
+
entries = all;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
for (const entry of entries)
|
|
45
|
+
this.known.set(entry.id, entry);
|
|
46
|
+
this.anchor = entries.at(-1) ?? (reset ? undefined : this.anchor);
|
|
47
|
+
this.manager = manager;
|
|
48
|
+
this.sessionId = sessionId;
|
|
49
|
+
this.file = file;
|
|
50
|
+
return { entries, reset };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Only delegation supplies this identity; Pi's ordinary fork/clone ancestry does not.
|
|
2
|
+
export function resolveRootSessionId(sessionManager, env = process.env) {
|
|
3
|
+
const rootId = env.PI_SUBAGENT_CHILD === "1" ? env.PI_SUBAGENT_ROOT_SESSION_ID : undefined;
|
|
4
|
+
const sessionId = rootId || sessionManager.getSessionId();
|
|
5
|
+
if (!sessionId)
|
|
6
|
+
throw new Error("Root session identity is unavailable.");
|
|
7
|
+
return sessionId;
|
|
8
|
+
}
|
|
9
|
+
export function resolveCurrentSessionId(sessionManager) {
|
|
10
|
+
const sessionId = sessionManager.getSessionId();
|
|
11
|
+
if (!sessionId)
|
|
12
|
+
throw new Error("Current session identity is unavailable.");
|
|
13
|
+
return sessionId;
|
|
14
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chain behavior, template resolution, and directory management
|
|
3
|
+
*/
|
|
4
|
+
import * as fs from "node:fs";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
import { normalizeSkillInput } from "../agents/skills.js";
|
|
7
|
+
import { CHAIN_RUNS_DIR } from "./types.js";
|
|
8
|
+
import { ensureSafeTempPath, ensureTempRoot } from "./temp-root.js";
|
|
9
|
+
const CHAIN_DIR_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
10
|
+
const INITIAL_PROGRESS_CONTENT = "# Progress\n\n## Status\nIn Progress\n\n## Tasks\n\n## Files Changed\n\n## Notes\n";
|
|
11
|
+
function normalizeOutputOverride(output) {
|
|
12
|
+
return output === "false" ? false : output;
|
|
13
|
+
}
|
|
14
|
+
// =============================================================================
|
|
15
|
+
// Type Guards
|
|
16
|
+
// =============================================================================
|
|
17
|
+
export function isParallelStep(step) {
|
|
18
|
+
return "parallel" in step && Array.isArray(step.parallel);
|
|
19
|
+
}
|
|
20
|
+
export function isDynamicParallelStep(step) {
|
|
21
|
+
return "expand" in step && "collect" in step && "parallel" in step && !Array.isArray(step.parallel);
|
|
22
|
+
}
|
|
23
|
+
/** Get all agent names in a step (single for sequential, multiple for parallel) */
|
|
24
|
+
export function getStepAgents(step) {
|
|
25
|
+
if (isParallelStep(step)) {
|
|
26
|
+
return step.parallel.map((t) => t.agent);
|
|
27
|
+
}
|
|
28
|
+
if (isDynamicParallelStep(step)) {
|
|
29
|
+
return [step.parallel.agent];
|
|
30
|
+
}
|
|
31
|
+
return [step.agent];
|
|
32
|
+
}
|
|
33
|
+
export function collectInvocationAgentNames(params) {
|
|
34
|
+
const names = [];
|
|
35
|
+
if (params.agent)
|
|
36
|
+
names.push(params.agent);
|
|
37
|
+
for (const task of params.tasks ?? [])
|
|
38
|
+
names.push(task.agent);
|
|
39
|
+
for (const step of params.chain ?? [])
|
|
40
|
+
names.push(...getStepAgents(step));
|
|
41
|
+
return names;
|
|
42
|
+
}
|
|
43
|
+
// =============================================================================
|
|
44
|
+
// Chain Directory Management
|
|
45
|
+
// =============================================================================
|
|
46
|
+
export function createChainDir(runId, baseDir, cwd = process.cwd()) {
|
|
47
|
+
if (!baseDir)
|
|
48
|
+
ensureTempRoot();
|
|
49
|
+
const chainDir = path.join(baseDir ? path.resolve(cwd, baseDir) : CHAIN_RUNS_DIR, runId);
|
|
50
|
+
if (!baseDir)
|
|
51
|
+
ensureSafeTempPath(chainDir);
|
|
52
|
+
fs.mkdirSync(chainDir, { recursive: true });
|
|
53
|
+
return chainDir;
|
|
54
|
+
}
|
|
55
|
+
export function removeChainDir(chainDir) {
|
|
56
|
+
try {
|
|
57
|
+
fs.rmSync(chainDir, { recursive: true });
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// Chain cleanup is best-effort. Runs can already have cleaned their temp dir.
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export async function cleanupOldChainDirs() {
|
|
64
|
+
ensureSafeTempPath(CHAIN_RUNS_DIR);
|
|
65
|
+
const now = Date.now();
|
|
66
|
+
let dirs;
|
|
67
|
+
try {
|
|
68
|
+
dirs = await fs.promises.readdir(CHAIN_RUNS_DIR);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Startup cleanup is best-effort. If the scoped temp root is unreadable,
|
|
72
|
+
// skip cleanup instead of failing extension startup.
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
for (const dir of dirs) {
|
|
76
|
+
try {
|
|
77
|
+
const dirPath = path.join(CHAIN_RUNS_DIR, dir);
|
|
78
|
+
const stat = await fs.promises.lstat(dirPath);
|
|
79
|
+
if (stat.isDirectory() && now - stat.mtimeMs > CHAIN_DIR_MAX_AGE_MS) {
|
|
80
|
+
await fs.promises.rm(dirPath, { recursive: true });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// Skip directories that can't be processed; continue with others
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Resolve templates for a chain with parallel step support.
|
|
90
|
+
* Returns string for sequential steps, string[] for parallel steps.
|
|
91
|
+
*/
|
|
92
|
+
export function resolveChainTemplates(steps) {
|
|
93
|
+
return steps.map((step, i) => {
|
|
94
|
+
if (isParallelStep(step)) {
|
|
95
|
+
// Parallel step: resolve each task's template
|
|
96
|
+
return step.parallel.map((task) => {
|
|
97
|
+
if (task.task)
|
|
98
|
+
return task.task;
|
|
99
|
+
// Default for parallel tasks is {previous}
|
|
100
|
+
return "{previous}";
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
if (isDynamicParallelStep(step)) {
|
|
104
|
+
return step.parallel.task ?? "{previous}";
|
|
105
|
+
}
|
|
106
|
+
// Sequential step: existing logic
|
|
107
|
+
const seq = step;
|
|
108
|
+
if (seq.task)
|
|
109
|
+
return seq.task;
|
|
110
|
+
// Default: first step uses {task}, others use {previous}
|
|
111
|
+
return i === 0 ? "{task}" : "{previous}";
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
// =============================================================================
|
|
115
|
+
// Behavior Resolution
|
|
116
|
+
// =============================================================================
|
|
117
|
+
/**
|
|
118
|
+
* Resolve effective chain behavior per step.
|
|
119
|
+
* Priority: step override > agent frontmatter > false (disabled)
|
|
120
|
+
*/
|
|
121
|
+
export function resolveStepBehavior(agentConfig, stepOverrides, chainSkills) {
|
|
122
|
+
// Output: step override > frontmatter > false (no output)
|
|
123
|
+
const stepOutput = normalizeOutputOverride(stepOverrides.output);
|
|
124
|
+
const output = stepOutput !== undefined
|
|
125
|
+
? stepOutput
|
|
126
|
+
: normalizeOutputOverride(agentConfig.output) ?? false;
|
|
127
|
+
// Reads: step override > frontmatter defaultReads > false (no reads)
|
|
128
|
+
const reads = stepOverrides.reads !== undefined
|
|
129
|
+
? stepOverrides.reads
|
|
130
|
+
: agentConfig.defaultReads ?? false;
|
|
131
|
+
// Progress: step override > frontmatter defaultProgress > false
|
|
132
|
+
const progress = stepOverrides.progress !== undefined
|
|
133
|
+
? stepOverrides.progress
|
|
134
|
+
: agentConfig.defaultProgress ?? false;
|
|
135
|
+
let skills;
|
|
136
|
+
if (chainSkills === false || stepOverrides.skills === false) {
|
|
137
|
+
skills = false;
|
|
138
|
+
}
|
|
139
|
+
else if (stepOverrides.skills !== undefined) {
|
|
140
|
+
skills = [...stepOverrides.skills];
|
|
141
|
+
if (chainSkills && chainSkills.length > 0) {
|
|
142
|
+
skills = [...new Set([...skills, ...chainSkills])];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
skills = agentConfig.skills ? [...agentConfig.skills] : [];
|
|
147
|
+
if (chainSkills && chainSkills.length > 0) {
|
|
148
|
+
skills = [...new Set([...skills, ...chainSkills])];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const outputMode = stepOverrides.outputMode ?? "inline";
|
|
152
|
+
const model = stepOverrides.model ?? agentConfig.model;
|
|
153
|
+
return { output, outputMode, reads, progress, skills, model };
|
|
154
|
+
}
|
|
155
|
+
export function resolveTaskTextForFileUpdatePolicy(task, originalTask) {
|
|
156
|
+
if (!task)
|
|
157
|
+
return originalTask;
|
|
158
|
+
return originalTask ? task.replaceAll("{task}", () => originalTask) : task;
|
|
159
|
+
}
|
|
160
|
+
export function taskDisallowsFileUpdates(task) {
|
|
161
|
+
if (!task)
|
|
162
|
+
return false;
|
|
163
|
+
return /\breview[- ]only\b/i.test(task)
|
|
164
|
+
|| /\bread[- ]only\s+(?:review|audit|inspection|pass)\b/i.test(task)
|
|
165
|
+
|| /\b(?:no|without)\s+(?:file\s+)?edits?\b/i.test(task)
|
|
166
|
+
|| /\b(?:do not|don't|must not)\s+(?:edit|modify|write|touch)\b/i.test(task)
|
|
167
|
+
|| /\bleave\s+files?\s+unchanged\b/i.test(task);
|
|
168
|
+
}
|
|
169
|
+
export function suppressProgressForReadOnlyTask(behavior, task, originalTask) {
|
|
170
|
+
const policyTask = resolveTaskTextForFileUpdatePolicy(task, originalTask);
|
|
171
|
+
return behavior.progress && taskDisallowsFileUpdates(policyTask) ? { ...behavior, progress: false } : behavior;
|
|
172
|
+
}
|
|
173
|
+
// =============================================================================
|
|
174
|
+
// Chain Instruction Injection
|
|
175
|
+
// =============================================================================
|
|
176
|
+
/**
|
|
177
|
+
* Resolve a file path: absolute paths pass through, relative paths get chainDir prepended.
|
|
178
|
+
*/
|
|
179
|
+
function resolveChainPath(filePath, chainDir) {
|
|
180
|
+
return path.isAbsolute(filePath) ? filePath : path.join(chainDir, filePath);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Build chain instructions from resolved behavior.
|
|
184
|
+
* These are appended to the task to tell the agent what to read/write.
|
|
185
|
+
*/
|
|
186
|
+
export function writeInitialProgressFile(progressDir) {
|
|
187
|
+
fs.mkdirSync(progressDir, { recursive: true });
|
|
188
|
+
try {
|
|
189
|
+
fs.writeFileSync(path.join(progressDir, "progress.md"), INITIAL_PROGRESS_CONTENT, { encoding: "utf-8", flag: "wx" });
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
if (error.code !== "EEXIST")
|
|
193
|
+
throw error;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
export function buildChainInstructions(behavior, chainDir, isFirstProgressAgent) {
|
|
197
|
+
const prefixParts = [];
|
|
198
|
+
const suffixParts = [];
|
|
199
|
+
// READS - prepend to override any hardcoded filenames in task text
|
|
200
|
+
if (behavior.reads && behavior.reads.length > 0) {
|
|
201
|
+
const files = behavior.reads.map((f) => resolveChainPath(f, chainDir));
|
|
202
|
+
prefixParts.push(`[Read from: ${files.join(", ")}]`);
|
|
203
|
+
}
|
|
204
|
+
// OUTPUT - prepend so agent knows where to write
|
|
205
|
+
if (behavior.output) {
|
|
206
|
+
const outputPath = resolveChainPath(behavior.output, chainDir);
|
|
207
|
+
prefixParts.push(`[Write to: ${outputPath}]`);
|
|
208
|
+
}
|
|
209
|
+
// Progress instructions in suffix (less critical)
|
|
210
|
+
if (behavior.progress) {
|
|
211
|
+
const progressPath = path.join(chainDir, "progress.md");
|
|
212
|
+
if (isFirstProgressAgent) {
|
|
213
|
+
suffixParts.push(`Create and maintain progress at: ${progressPath}`);
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
suffixParts.push(`Update progress at: ${progressPath}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const prefix = prefixParts.length > 0
|
|
220
|
+
? prefixParts.join("\n") + "\n\n"
|
|
221
|
+
: "";
|
|
222
|
+
const suffix = suffixParts.length > 0
|
|
223
|
+
? "\n\n---\n" + suffixParts.join("\n")
|
|
224
|
+
: "";
|
|
225
|
+
return { prefix, suffix };
|
|
226
|
+
}
|
|
227
|
+
// =============================================================================
|
|
228
|
+
// Parallel Step Support
|
|
229
|
+
// =============================================================================
|
|
230
|
+
/**
|
|
231
|
+
* Resolve behaviors for all tasks in a parallel step.
|
|
232
|
+
* Creates namespaced output paths to avoid collisions.
|
|
233
|
+
*/
|
|
234
|
+
export function resolveParallelBehaviors(tasks, agentConfigs, stepIndex, chainSkills) {
|
|
235
|
+
return tasks.map((task, taskIndex) => {
|
|
236
|
+
const config = agentConfigs.find((a) => a.name === task.agent);
|
|
237
|
+
if (!config) {
|
|
238
|
+
throw new Error(`Unknown agent: ${task.agent}`);
|
|
239
|
+
}
|
|
240
|
+
const behavior = resolveStepBehavior(config, {
|
|
241
|
+
output: task.output,
|
|
242
|
+
outputMode: task.outputMode,
|
|
243
|
+
reads: task.reads,
|
|
244
|
+
progress: task.progress,
|
|
245
|
+
skills: normalizeSkillInput(task.skill),
|
|
246
|
+
model: task.model,
|
|
247
|
+
}, chainSkills);
|
|
248
|
+
return { ...behavior, output: namespaceParallelOutput(behavior.output, task.agent, stepIndex, taskIndex) };
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
export function namespaceParallelOutput(output, agent, stepIndex, taskIndex) {
|
|
252
|
+
return output ? path.isAbsolute(output) ? output : path.join(`parallel-${stepIndex}`, `${taskIndex}-${agent}`, output) : false;
|
|
253
|
+
}
|
|
254
|
+
export { aggregateParallelOutputs } from "../runs/shared/parallel-utils.js";
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { loadConfig } from "../extension/config.js";
|
|
2
|
+
export function formatAgentProcessExit(exit) {
|
|
3
|
+
return exit ? `Agent process exited (${exit.signal ? `signal ${exit.signal}` : `code ${exit.code ?? "unrecorded"}`}). Command and descendant exit are not implied.`
|
|
4
|
+
: "Agent process exit not recorded. Command exit is unconfirmed.";
|
|
5
|
+
}
|
|
6
|
+
export function formatRunAction(action, id, fields = {}, childSafe = false) {
|
|
7
|
+
const legacyChild = childSafe && loadConfig().compactChildTools === false;
|
|
8
|
+
const parentAction = { status: "inspect", resume: "continue", interrupt: "stop" };
|
|
9
|
+
const name = legacyChild ? action : parentAction[action] ?? action;
|
|
10
|
+
const args = Object.entries({ action: name, ...(id !== undefined ? { id } : {}), ...fields }).map(([key, value]) => `${key}: ${JSON.stringify(value)}`).join(", ");
|
|
11
|
+
const call = `${legacyChild || action === "extend" ? "subagent" : "agent_runs"}({ ${args} })`;
|
|
12
|
+
return action === "extend" && !legacyChild ? `load_subagent({}), then ${call}` : call;
|
|
13
|
+
}
|
|
14
|
+
export function buildManagementControl(input) {
|
|
15
|
+
const capabilities = ["status"];
|
|
16
|
+
if (input.state === "live") {
|
|
17
|
+
if (input.canNudge)
|
|
18
|
+
capabilities.push("nudge");
|
|
19
|
+
if (input.canResume)
|
|
20
|
+
capabilities.push("resume");
|
|
21
|
+
if (input.canInterrupt)
|
|
22
|
+
capabilities.push("interrupt");
|
|
23
|
+
}
|
|
24
|
+
else if (input.canResume)
|
|
25
|
+
capabilities.push("resume");
|
|
26
|
+
if (input.state === "live" && input.canExtend)
|
|
27
|
+
capabilities.push("extend");
|
|
28
|
+
if (input.canReview)
|
|
29
|
+
capabilities.push("review");
|
|
30
|
+
return {
|
|
31
|
+
state: input.state,
|
|
32
|
+
runId: input.runId,
|
|
33
|
+
capabilities,
|
|
34
|
+
nextActions: capabilities.map((action) => ({
|
|
35
|
+
action,
|
|
36
|
+
runId: input.runId,
|
|
37
|
+
...(input.index !== undefined && (action === "nudge" || action === "resume") ? { index: input.index } : {}),
|
|
38
|
+
...(input.intercomTarget && (action === "nudge" || action === "resume") ? { intercomTarget: input.intercomTarget } : {}),
|
|
39
|
+
})),
|
|
40
|
+
...(input.unavailableActions ? { unavailableActions: input.unavailableActions } : {}),
|
|
41
|
+
...(input.revivedFromRunId ? { revivedFromRunId: input.revivedFromRunId, pendingReplyContextValid: false } : {}),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function formatActivityAge(ms) {
|
|
45
|
+
if (ms < 1000)
|
|
46
|
+
return "now";
|
|
47
|
+
if (ms < 60000)
|
|
48
|
+
return `${Math.floor(ms / 1000)}s`;
|
|
49
|
+
return `${Math.floor(ms / 60000)}m`;
|
|
50
|
+
}
|
|
51
|
+
export function formatActivityLabel(lastActivityAt, activityState, now = Date.now()) {
|
|
52
|
+
if (lastActivityAt === undefined) {
|
|
53
|
+
if (activityState === "needs_attention")
|
|
54
|
+
return "needs attention";
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
const age = formatActivityAge(Math.max(0, now - lastActivityAt));
|
|
58
|
+
if (activityState === "needs_attention")
|
|
59
|
+
return `no activity for ${age}`;
|
|
60
|
+
return age === "now" ? "active now" : `active ${age} ago`;
|
|
61
|
+
}
|
|
62
|
+
function isCompletedStepStatus(status) {
|
|
63
|
+
return status === "complete" || status === "completed";
|
|
64
|
+
}
|
|
65
|
+
export function aggregateStepStatus(steps) {
|
|
66
|
+
if (steps.some((step) => step.status === "running"))
|
|
67
|
+
return "running";
|
|
68
|
+
if (steps.some((step) => step.status === "failed"))
|
|
69
|
+
return "failed";
|
|
70
|
+
if (steps.some((step) => step.status === "blocked"))
|
|
71
|
+
return "blocked";
|
|
72
|
+
if (steps.some((step) => step.status === "paused"))
|
|
73
|
+
return "paused";
|
|
74
|
+
if (steps.length > 0 && steps.every((step) => isCompletedStepStatus(step.status)))
|
|
75
|
+
return "complete";
|
|
76
|
+
return "pending";
|
|
77
|
+
}
|
|
78
|
+
export function formatAgentRunningLabel(count) {
|
|
79
|
+
return count === 1 ? "1 agent running" : `${count} agents running`;
|
|
80
|
+
}
|
|
81
|
+
export function formatLiveIntercomActionLines(input) {
|
|
82
|
+
const fields = { ...(input.index !== undefined ? { index: input.index } : {}), message: "What are you blocked on?" };
|
|
83
|
+
const healthText = !input.health
|
|
84
|
+
? "unknown"
|
|
85
|
+
: input.health.status === "registered"
|
|
86
|
+
? `registered${input.health.sessionStatus ? `, ${input.health.sessionStatus}` : ""}${input.health.acceptsAsks !== undefined ? `, accepts_asks:${input.health.acceptsAsks}` : ""}${input.health.pendingAsks !== undefined ? `, pending_asks:${input.health.pendingAsks}` : ""}`
|
|
87
|
+
: input.health.status.replace(/_/g, " ");
|
|
88
|
+
const indent = input.indent ?? "";
|
|
89
|
+
return [
|
|
90
|
+
`${indent}Intercom: ${healthText} (${input.target})`,
|
|
91
|
+
`${indent}Nudge (preferred live coordination): ${formatRunAction("nudge", input.runId, fields, input.childSafe)}`,
|
|
92
|
+
`${indent}Ask (blocking wait only; parent must remain alive): intercom({ action: "ask", to: "${input.target}", delivery: "steer", message: "What are you blocked on?" })`,
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
export function formatParallelOutcome(steps, total, options = {}) {
|
|
96
|
+
const running = steps.filter((step) => step.status === "running").length;
|
|
97
|
+
const succeeded = steps.filter((step) => isCompletedStepStatus(step.status)).length;
|
|
98
|
+
const failed = steps.filter((step) => step.status === "failed").length;
|
|
99
|
+
const paused = steps.filter((step) => step.status === "paused").length;
|
|
100
|
+
const blocked = steps.filter((step) => step.status === "blocked").length;
|
|
101
|
+
const parts = [`${succeeded}/${total} succeeded`];
|
|
102
|
+
if (options.showRunning !== false && running > 0)
|
|
103
|
+
parts.unshift(formatAgentRunningLabel(running));
|
|
104
|
+
if (failed > 0)
|
|
105
|
+
parts.push(`${failed} failed`);
|
|
106
|
+
if (paused > 0)
|
|
107
|
+
parts.push(`${paused} paused`);
|
|
108
|
+
if (blocked > 0)
|
|
109
|
+
parts.push(`${blocked} need human action`);
|
|
110
|
+
return parts.join(" · ");
|
|
111
|
+
}
|