@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,204 @@
|
|
|
1
|
+
import { execFileSync, type ChildProcess } from "node:child_process";
|
|
2
|
+
import { readLinuxProcess } from "../pi-intercom/broker/pid.ts";
|
|
3
|
+
import type { AgentProcessExit } from "./types.ts";
|
|
4
|
+
|
|
5
|
+
interface PostExitStdioGuardOptions {
|
|
6
|
+
idleMs: number;
|
|
7
|
+
hardMs: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface ChildWithPipedStdio {
|
|
11
|
+
stdout: ChildProcess["stdout"];
|
|
12
|
+
stderr: ChildProcess["stderr"];
|
|
13
|
+
on: ChildProcess["on"];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface ChildWithKill {
|
|
17
|
+
pid?: number;
|
|
18
|
+
kill(signal?: NodeJS.Signals | number): boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Capture the detached group leader's birth identity; Pi may replace its command-line title. */
|
|
22
|
+
export function readChildProcessIdentity(pid: number): string | undefined {
|
|
23
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) return undefined;
|
|
24
|
+
if (process.platform === "linux") return readLinuxProcess(pid)?.identity;
|
|
25
|
+
try {
|
|
26
|
+
const identity = execFileSync("ps", ["-p", String(pid), "-o", "lstart=", "-o", "pgid="], {
|
|
27
|
+
encoding: "utf8", timeout: 1000, stdio: ["ignore", "pipe", "ignore"],
|
|
28
|
+
env: { ...process.env, LC_ALL: "C" },
|
|
29
|
+
}).trim().replace(/\s+/g, " ");
|
|
30
|
+
return Number(identity.split(" ").at(-1)) === pid ? identity : undefined;
|
|
31
|
+
} catch {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function trySignalChild(child: ChildWithKill, signal: NodeJS.Signals): boolean {
|
|
37
|
+
try {
|
|
38
|
+
return child.kill(signal);
|
|
39
|
+
} catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function isChildTreeAlive(child: ChildWithKill): boolean {
|
|
45
|
+
if (child.pid) {
|
|
46
|
+
try {
|
|
47
|
+
process.kill(-child.pid, 0);
|
|
48
|
+
return true;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if ((error as NodeJS.ErrnoException).code === "EPERM") return true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
return child.kill(0);
|
|
55
|
+
} catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Signal the detached child's process group, falling back if it is not a group leader. */
|
|
61
|
+
export function trySignalChildTree(child: ChildWithKill, signal: NodeJS.Signals): boolean {
|
|
62
|
+
if (child.pid) {
|
|
63
|
+
try {
|
|
64
|
+
process.kill(-child.pid, signal);
|
|
65
|
+
return true;
|
|
66
|
+
} catch {
|
|
67
|
+
// Fall back when the child is not a process-group leader.
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return trySignalChild(child, signal);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** One lifetime for the owned process group, including descendants holding its pipes open. */
|
|
74
|
+
export function attachChildProcessLifecycle(child: ChildWithPipedStdio & ChildWithKill) {
|
|
75
|
+
let closed = false;
|
|
76
|
+
let exited = false;
|
|
77
|
+
let stopping = false;
|
|
78
|
+
let settledCleanup = false;
|
|
79
|
+
let agentProcessExit: AgentProcessExit | undefined;
|
|
80
|
+
let drainTimer: NodeJS.Timeout | undefined;
|
|
81
|
+
let escalationTimer: NodeJS.Timeout | undefined;
|
|
82
|
+
const clearDrain = () => {
|
|
83
|
+
clearTimeout(drainTimer);
|
|
84
|
+
drainTimer = undefined;
|
|
85
|
+
};
|
|
86
|
+
const clearTimers = () => {
|
|
87
|
+
clearDrain();
|
|
88
|
+
clearTimeout(escalationTimer);
|
|
89
|
+
escalationTimer = undefined;
|
|
90
|
+
};
|
|
91
|
+
const signalTree = () => {
|
|
92
|
+
// Pi print mode uses SIGTERM to dispose its own detached tool processes.
|
|
93
|
+
const sent = trySignalChildTree(child, "SIGTERM");
|
|
94
|
+
escalationTimer = setTimeout(() => {
|
|
95
|
+
if (isChildTreeAlive(child)) trySignalChildTree(child, "SIGKILL");
|
|
96
|
+
}, 3000);
|
|
97
|
+
escalationTimer.unref();
|
|
98
|
+
return sent;
|
|
99
|
+
};
|
|
100
|
+
const terminate = () => {
|
|
101
|
+
// Cancellation overrides a pending or already-started successful settlement cleanup.
|
|
102
|
+
settledCleanup = false;
|
|
103
|
+
clearDrain();
|
|
104
|
+
if (closed || stopping) return;
|
|
105
|
+
stopping = true;
|
|
106
|
+
signalTree();
|
|
107
|
+
};
|
|
108
|
+
const clearStdioGuard = attachPostExitStdioGuard(child, { idleMs: 2000, hardMs: 8000 });
|
|
109
|
+
child.on("exit", (code, signal) => {
|
|
110
|
+
exited = true;
|
|
111
|
+
agentProcessExit = { pid: child.pid, code, signal, at: Date.now() };
|
|
112
|
+
clearTimers();
|
|
113
|
+
// The leader may exit before resistant descendants release inherited stdio.
|
|
114
|
+
trySignalChildTree(child, "SIGKILL");
|
|
115
|
+
});
|
|
116
|
+
const dispose = () => {
|
|
117
|
+
closed = true;
|
|
118
|
+
clearTimers();
|
|
119
|
+
clearStdioGuard();
|
|
120
|
+
};
|
|
121
|
+
child.on("close", dispose);
|
|
122
|
+
child.on("error", dispose);
|
|
123
|
+
return {
|
|
124
|
+
terminate,
|
|
125
|
+
get stopping() { return stopping; },
|
|
126
|
+
get agentProcessExit() { return agentProcessExit; },
|
|
127
|
+
get settledCleanup() { return settledCleanup; },
|
|
128
|
+
observeEvent(type: string | undefined) {
|
|
129
|
+
if (closed || exited || stopping) return;
|
|
130
|
+
if (type === "agent_settled") {
|
|
131
|
+
if (drainTimer) return;
|
|
132
|
+
drainTimer = setTimeout(() => {
|
|
133
|
+
drainTimer = undefined;
|
|
134
|
+
stopping = true;
|
|
135
|
+
settledCleanup = signalTree();
|
|
136
|
+
}, 1000);
|
|
137
|
+
drainTimer.unref();
|
|
138
|
+
} else if (type === "agent_start" || type === "turn_start" || type?.startsWith("message_") || type?.startsWith("tool_execution_")) {
|
|
139
|
+
clearDrain();
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function attachPostExitStdioGuard(
|
|
146
|
+
child: ChildWithPipedStdio,
|
|
147
|
+
options: PostExitStdioGuardOptions,
|
|
148
|
+
): () => void {
|
|
149
|
+
const { idleMs, hardMs } = options;
|
|
150
|
+
let exited = false;
|
|
151
|
+
let stdoutEnded = false;
|
|
152
|
+
let stderrEnded = false;
|
|
153
|
+
let idleTimer: NodeJS.Timeout | undefined;
|
|
154
|
+
let hardTimer: NodeJS.Timeout | undefined;
|
|
155
|
+
|
|
156
|
+
const destroyUnendedStdio = () => {
|
|
157
|
+
if (!stdoutEnded) {
|
|
158
|
+
try { child.stdout?.destroy(); } catch {}
|
|
159
|
+
}
|
|
160
|
+
if (!stderrEnded) {
|
|
161
|
+
try { child.stderr?.destroy(); } catch {}
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const clearTimers = () => {
|
|
166
|
+
if (idleTimer) {
|
|
167
|
+
clearTimeout(idleTimer);
|
|
168
|
+
idleTimer = undefined;
|
|
169
|
+
}
|
|
170
|
+
if (hardTimer) {
|
|
171
|
+
clearTimeout(hardTimer);
|
|
172
|
+
hardTimer = undefined;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const armIdleTimer = () => {
|
|
177
|
+
if (!exited) return;
|
|
178
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
179
|
+
idleTimer = setTimeout(destroyUnendedStdio, idleMs);
|
|
180
|
+
idleTimer.unref?.();
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
child.stdout?.on("data", armIdleTimer);
|
|
184
|
+
child.stderr?.on("data", armIdleTimer);
|
|
185
|
+
child.stdout?.on("end", () => {
|
|
186
|
+
stdoutEnded = true;
|
|
187
|
+
if (stdoutEnded && stderrEnded) clearTimers();
|
|
188
|
+
});
|
|
189
|
+
child.stderr?.on("end", () => {
|
|
190
|
+
stderrEnded = true;
|
|
191
|
+
if (stdoutEnded && stderrEnded) clearTimers();
|
|
192
|
+
});
|
|
193
|
+
child.on("exit", () => {
|
|
194
|
+
exited = true;
|
|
195
|
+
armIdleTimer();
|
|
196
|
+
if (hardTimer) return;
|
|
197
|
+
hardTimer = setTimeout(destroyUnendedStdio, hardMs);
|
|
198
|
+
hardTimer.unref?.();
|
|
199
|
+
});
|
|
200
|
+
child.on("close", clearTimers);
|
|
201
|
+
child.on("error", clearTimers);
|
|
202
|
+
|
|
203
|
+
return clearTimers;
|
|
204
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { NormalizedBuildSystemPromptOptions } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
export function setPromptSection(options: NormalizedBuildSystemPromptOptions, name: string, content: string): void {
|
|
4
|
+
options.sections[name] = content;
|
|
5
|
+
// Pi projects an earlier exact override instead of sections. Preserve its content;
|
|
6
|
+
// this compatibility path replaces the leading prompt and cannot preserve its cache prefix.
|
|
7
|
+
if (options.forceSystemPrompt !== undefined) {
|
|
8
|
+
options.forceSystemPrompt += `\n\n<${name}>\n${content}\n</${name}>`;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { SessionManager, SessionEntry } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
type EntrySource = Pick<SessionManager, "getEntries" | "getSessionId"> & Partial<Pick<SessionManager, "getSessionFile" | "getEntry" | "getLeafId" | "getEntryCount">>;
|
|
4
|
+
|
|
5
|
+
/** Follow append-only parent links; rescan when entries were replaced or appended off the active branch. */
|
|
6
|
+
export class SessionEntryCursor {
|
|
7
|
+
private manager?: EntrySource;
|
|
8
|
+
private sessionId?: string;
|
|
9
|
+
private file?: string | null;
|
|
10
|
+
private known = new Map<string, SessionEntry>();
|
|
11
|
+
private anchor?: SessionEntry;
|
|
12
|
+
|
|
13
|
+
reset(): void {
|
|
14
|
+
this.manager = undefined;
|
|
15
|
+
this.known.clear();
|
|
16
|
+
this.anchor = undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
read(manager: EntrySource): { entries: SessionEntry[]; reset: boolean } {
|
|
20
|
+
const sessionId = manager.getSessionId(), file = manager.getSessionFile?.(), count = manager.getEntryCount?.();
|
|
21
|
+
let reset = this.manager !== manager || this.sessionId !== sessionId || this.file !== file
|
|
22
|
+
|| count !== undefined && count < this.known.size
|
|
23
|
+
|| Boolean(this.anchor && manager.getEntry && manager.getEntry(this.anchor.id) !== this.known.get(this.anchor.id));
|
|
24
|
+
let entries: SessionEntry[] | undefined;
|
|
25
|
+
if (!reset && count !== undefined && manager.getEntry && manager.getLeafId) {
|
|
26
|
+
const appended: SessionEntry[] = [], visited = new Set<string>();
|
|
27
|
+
let id = manager.getLeafId();
|
|
28
|
+
while (id && !this.known.has(id) && !visited.has(id)) {
|
|
29
|
+
visited.add(id);
|
|
30
|
+
const entry = manager.getEntry(id);
|
|
31
|
+
if (!entry) break;
|
|
32
|
+
appended.push(entry);
|
|
33
|
+
id = entry.parentId;
|
|
34
|
+
}
|
|
35
|
+
if (count === this.known.size + appended.length && (!id || this.known.has(id))) entries = appended.reverse();
|
|
36
|
+
}
|
|
37
|
+
if (!entries) {
|
|
38
|
+
const all = manager.getEntries();
|
|
39
|
+
entries = all.filter((entry) => !this.known.has(entry.id));
|
|
40
|
+
if (all.length !== this.known.size + entries.length
|
|
41
|
+
|| all.some((entry) => this.known.has(entry.id) && this.known.get(entry.id) !== entry)) reset = true;
|
|
42
|
+
if (reset) { this.known.clear(); entries = all; }
|
|
43
|
+
}
|
|
44
|
+
for (const entry of entries) this.known.set(entry.id, entry);
|
|
45
|
+
this.anchor = entries.at(-1) ?? (reset ? undefined : this.anchor);
|
|
46
|
+
this.manager = manager; this.sessionId = sessionId; this.file = file;
|
|
47
|
+
return { entries, reset };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface SessionIdentityManager {
|
|
2
|
+
getSessionFile(): string | null | undefined;
|
|
3
|
+
getSessionId(): string | null | undefined;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
// Only delegation supplies this identity; Pi's ordinary fork/clone ancestry does not.
|
|
7
|
+
export function resolveRootSessionId(sessionManager: SessionIdentityManager, env: NodeJS.ProcessEnv = process.env): string {
|
|
8
|
+
const rootId = env.PI_SUBAGENT_CHILD === "1" ? env.PI_SUBAGENT_ROOT_SESSION_ID : undefined;
|
|
9
|
+
const sessionId = rootId || sessionManager.getSessionId();
|
|
10
|
+
if (!sessionId) throw new Error("Root session identity is unavailable.");
|
|
11
|
+
return sessionId;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function resolveCurrentSessionId(sessionManager: SessionIdentityManager): string {
|
|
15
|
+
const sessionId = sessionManager.getSessionId();
|
|
16
|
+
if (!sessionId) throw new Error("Current session identity is unavailable.");
|
|
17
|
+
return sessionId;
|
|
18
|
+
}
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chain behavior, template resolution, and directory management
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as fs from "node:fs";
|
|
6
|
+
import * as path from "node:path";
|
|
7
|
+
import type { AgentConfig } from "../agents/agents.ts";
|
|
8
|
+
import { normalizeSkillInput } from "../agents/skills.ts";
|
|
9
|
+
import { CHAIN_RUNS_DIR, type AcceptanceInput, type JsonSchemaObject, type OutputMode } from "./types.ts";
|
|
10
|
+
import { ensureSafeTempPath, ensureTempRoot } from "./temp-root.ts";
|
|
11
|
+
const CHAIN_DIR_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
12
|
+
const INITIAL_PROGRESS_CONTENT = "# Progress\n\n## Status\nIn Progress\n\n## Tasks\n\n## Files Changed\n\n## Notes\n";
|
|
13
|
+
|
|
14
|
+
// =============================================================================
|
|
15
|
+
// Behavior Resolution Types
|
|
16
|
+
// =============================================================================
|
|
17
|
+
|
|
18
|
+
export interface ResolvedStepBehavior {
|
|
19
|
+
output: string | false;
|
|
20
|
+
outputMode: OutputMode;
|
|
21
|
+
reads: string[] | false;
|
|
22
|
+
progress: boolean;
|
|
23
|
+
skills: string[] | false;
|
|
24
|
+
model?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type StepOverrides = Partial<ResolvedStepBehavior>;
|
|
28
|
+
|
|
29
|
+
function normalizeOutputOverride(output: string | false | undefined): string | false | undefined {
|
|
30
|
+
return output === "false" ? false : output;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// =============================================================================
|
|
34
|
+
// Chain Step Types
|
|
35
|
+
// =============================================================================
|
|
36
|
+
|
|
37
|
+
/** Sequential step: single agent execution */
|
|
38
|
+
export interface SequentialStep {
|
|
39
|
+
agent: string;
|
|
40
|
+
task?: string;
|
|
41
|
+
phase?: string;
|
|
42
|
+
label?: string;
|
|
43
|
+
as?: string;
|
|
44
|
+
outputSchema?: JsonSchemaObject;
|
|
45
|
+
cwd?: string;
|
|
46
|
+
output?: string | false;
|
|
47
|
+
outputMode?: OutputMode;
|
|
48
|
+
outputFromAgentDefault?: boolean;
|
|
49
|
+
reads?: string[] | false;
|
|
50
|
+
progress?: boolean;
|
|
51
|
+
skill?: string | string[] | false;
|
|
52
|
+
model?: string;
|
|
53
|
+
acceptance?: AcceptanceInput;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Parallel task item within a parallel step */
|
|
57
|
+
export interface ParallelTaskItem extends SequentialStep {
|
|
58
|
+
count?: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface DynamicExpandSpec {
|
|
62
|
+
from: {
|
|
63
|
+
output: string;
|
|
64
|
+
path: string;
|
|
65
|
+
};
|
|
66
|
+
item?: string;
|
|
67
|
+
key?: string;
|
|
68
|
+
maxItems?: number;
|
|
69
|
+
onEmpty?: "skip" | "fail";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type DynamicParallelTemplate = Omit<ParallelTaskItem, "as" | "count">;
|
|
73
|
+
|
|
74
|
+
export interface DynamicCollectSpec {
|
|
75
|
+
as: string;
|
|
76
|
+
outputSchema?: JsonSchemaObject;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface DynamicParallelStep {
|
|
80
|
+
expand: DynamicExpandSpec;
|
|
81
|
+
parallel: DynamicParallelTemplate;
|
|
82
|
+
collect: DynamicCollectSpec;
|
|
83
|
+
concurrency?: number;
|
|
84
|
+
failFast?: boolean;
|
|
85
|
+
phase?: string;
|
|
86
|
+
label?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Parallel step: multiple agents running concurrently */
|
|
90
|
+
export interface ParallelStep {
|
|
91
|
+
parallel: ParallelTaskItem[];
|
|
92
|
+
concurrency?: number;
|
|
93
|
+
failFast?: boolean;
|
|
94
|
+
worktree?: boolean;
|
|
95
|
+
cwd?: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Union type for chain steps */
|
|
99
|
+
export type ChainStep = SequentialStep | ParallelStep | DynamicParallelStep;
|
|
100
|
+
|
|
101
|
+
// =============================================================================
|
|
102
|
+
// Type Guards
|
|
103
|
+
// =============================================================================
|
|
104
|
+
|
|
105
|
+
export function isParallelStep(step: ChainStep): step is ParallelStep {
|
|
106
|
+
return "parallel" in step && Array.isArray((step as ParallelStep).parallel);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function isDynamicParallelStep(step: ChainStep): step is DynamicParallelStep {
|
|
110
|
+
return "expand" in step && "collect" in step && "parallel" in step && !Array.isArray((step as { parallel?: unknown }).parallel);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Get all agent names in a step (single for sequential, multiple for parallel) */
|
|
114
|
+
export function getStepAgents(step: ChainStep): string[] {
|
|
115
|
+
if (isParallelStep(step)) {
|
|
116
|
+
return step.parallel.map((t) => t.agent);
|
|
117
|
+
}
|
|
118
|
+
if (isDynamicParallelStep(step)) {
|
|
119
|
+
return [step.parallel.agent];
|
|
120
|
+
}
|
|
121
|
+
return [step.agent];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function collectInvocationAgentNames(params: { agent?: string; tasks?: Array<{ agent: string }>; chain?: ChainStep[] }): string[] {
|
|
125
|
+
const names: string[] = [];
|
|
126
|
+
if (params.agent) names.push(params.agent);
|
|
127
|
+
for (const task of params.tasks ?? []) names.push(task.agent);
|
|
128
|
+
for (const step of params.chain ?? []) names.push(...getStepAgents(step));
|
|
129
|
+
return names;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// =============================================================================
|
|
133
|
+
// Chain Directory Management
|
|
134
|
+
// =============================================================================
|
|
135
|
+
|
|
136
|
+
export function createChainDir(runId: string, baseDir?: string, cwd = process.cwd()): string {
|
|
137
|
+
if (!baseDir) ensureTempRoot();
|
|
138
|
+
const chainDir = path.join(baseDir ? path.resolve(cwd, baseDir) : CHAIN_RUNS_DIR, runId);
|
|
139
|
+
if (!baseDir) ensureSafeTempPath(chainDir);
|
|
140
|
+
fs.mkdirSync(chainDir, { recursive: true });
|
|
141
|
+
return chainDir;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function removeChainDir(chainDir: string): void {
|
|
145
|
+
try {
|
|
146
|
+
fs.rmSync(chainDir, { recursive: true });
|
|
147
|
+
} catch {
|
|
148
|
+
// Chain cleanup is best-effort. Runs can already have cleaned their temp dir.
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export async function cleanupOldChainDirs(): Promise<void> {
|
|
153
|
+
ensureSafeTempPath(CHAIN_RUNS_DIR);
|
|
154
|
+
const now = Date.now();
|
|
155
|
+
let dirs: string[];
|
|
156
|
+
try {
|
|
157
|
+
dirs = await fs.promises.readdir(CHAIN_RUNS_DIR);
|
|
158
|
+
} catch {
|
|
159
|
+
// Startup cleanup is best-effort. If the scoped temp root is unreadable,
|
|
160
|
+
// skip cleanup instead of failing extension startup.
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
for (const dir of dirs) {
|
|
165
|
+
try {
|
|
166
|
+
const dirPath = path.join(CHAIN_RUNS_DIR, dir);
|
|
167
|
+
const stat = await fs.promises.lstat(dirPath);
|
|
168
|
+
if (stat.isDirectory() && now - stat.mtimeMs > CHAIN_DIR_MAX_AGE_MS) {
|
|
169
|
+
await fs.promises.rm(dirPath, { recursive: true });
|
|
170
|
+
}
|
|
171
|
+
} catch {
|
|
172
|
+
// Skip directories that can't be processed; continue with others
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// =============================================================================
|
|
178
|
+
// Template Resolution
|
|
179
|
+
// =============================================================================
|
|
180
|
+
|
|
181
|
+
/** Resolved templates for a chain - string for sequential, string[] for parallel */
|
|
182
|
+
export type ResolvedTemplates = (string | string[])[];
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Resolve templates for a chain with parallel step support.
|
|
186
|
+
* Returns string for sequential steps, string[] for parallel steps.
|
|
187
|
+
*/
|
|
188
|
+
export function resolveChainTemplates(
|
|
189
|
+
steps: ChainStep[],
|
|
190
|
+
): ResolvedTemplates {
|
|
191
|
+
return steps.map((step, i) => {
|
|
192
|
+
if (isParallelStep(step)) {
|
|
193
|
+
// Parallel step: resolve each task's template
|
|
194
|
+
return step.parallel.map((task) => {
|
|
195
|
+
if (task.task) return task.task;
|
|
196
|
+
// Default for parallel tasks is {previous}
|
|
197
|
+
return "{previous}";
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
if (isDynamicParallelStep(step)) {
|
|
201
|
+
return step.parallel.task ?? "{previous}";
|
|
202
|
+
}
|
|
203
|
+
// Sequential step: existing logic
|
|
204
|
+
const seq = step as SequentialStep;
|
|
205
|
+
if (seq.task) return seq.task;
|
|
206
|
+
// Default: first step uses {task}, others use {previous}
|
|
207
|
+
return i === 0 ? "{task}" : "{previous}";
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// =============================================================================
|
|
212
|
+
// Behavior Resolution
|
|
213
|
+
// =============================================================================
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Resolve effective chain behavior per step.
|
|
217
|
+
* Priority: step override > agent frontmatter > false (disabled)
|
|
218
|
+
*/
|
|
219
|
+
export function resolveStepBehavior(
|
|
220
|
+
agentConfig: AgentConfig,
|
|
221
|
+
stepOverrides: StepOverrides,
|
|
222
|
+
chainSkills?: string[] | false,
|
|
223
|
+
): ResolvedStepBehavior {
|
|
224
|
+
// Output: step override > frontmatter > false (no output)
|
|
225
|
+
const stepOutput = normalizeOutputOverride(stepOverrides.output);
|
|
226
|
+
const output =
|
|
227
|
+
stepOutput !== undefined
|
|
228
|
+
? stepOutput
|
|
229
|
+
: normalizeOutputOverride(agentConfig.output) ?? false;
|
|
230
|
+
|
|
231
|
+
// Reads: step override > frontmatter defaultReads > false (no reads)
|
|
232
|
+
const reads =
|
|
233
|
+
stepOverrides.reads !== undefined
|
|
234
|
+
? stepOverrides.reads
|
|
235
|
+
: agentConfig.defaultReads ?? false;
|
|
236
|
+
|
|
237
|
+
// Progress: step override > frontmatter defaultProgress > false
|
|
238
|
+
const progress =
|
|
239
|
+
stepOverrides.progress !== undefined
|
|
240
|
+
? stepOverrides.progress
|
|
241
|
+
: agentConfig.defaultProgress ?? false;
|
|
242
|
+
|
|
243
|
+
let skills: string[] | false;
|
|
244
|
+
if (chainSkills === false || stepOverrides.skills === false) {
|
|
245
|
+
skills = false;
|
|
246
|
+
} else if (stepOverrides.skills !== undefined) {
|
|
247
|
+
skills = [...stepOverrides.skills];
|
|
248
|
+
if (chainSkills && chainSkills.length > 0) {
|
|
249
|
+
skills = [...new Set([...skills, ...chainSkills])];
|
|
250
|
+
}
|
|
251
|
+
} else {
|
|
252
|
+
skills = agentConfig.skills ? [...agentConfig.skills] : [];
|
|
253
|
+
if (chainSkills && chainSkills.length > 0) {
|
|
254
|
+
skills = [...new Set([...skills, ...chainSkills])];
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const outputMode = stepOverrides.outputMode ?? "inline";
|
|
259
|
+
const model = stepOverrides.model ?? agentConfig.model;
|
|
260
|
+
return { output, outputMode, reads, progress, skills, model };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function resolveTaskTextForFileUpdatePolicy(task: string | undefined, originalTask?: string): string | undefined {
|
|
264
|
+
if (!task) return originalTask;
|
|
265
|
+
return originalTask ? task.replaceAll("{task}", () => originalTask) : task;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function taskDisallowsFileUpdates(task: string | undefined): boolean {
|
|
269
|
+
if (!task) return false;
|
|
270
|
+
return /\breview[- ]only\b/i.test(task)
|
|
271
|
+
|| /\bread[- ]only\s+(?:review|audit|inspection|pass)\b/i.test(task)
|
|
272
|
+
|| /\b(?:no|without)\s+(?:file\s+)?edits?\b/i.test(task)
|
|
273
|
+
|| /\b(?:do not|don't|must not)\s+(?:edit|modify|write|touch)\b/i.test(task)
|
|
274
|
+
|| /\bleave\s+files?\s+unchanged\b/i.test(task);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export function suppressProgressForReadOnlyTask(behavior: ResolvedStepBehavior, task: string | undefined, originalTask?: string): ResolvedStepBehavior {
|
|
278
|
+
const policyTask = resolveTaskTextForFileUpdatePolicy(task, originalTask);
|
|
279
|
+
return behavior.progress && taskDisallowsFileUpdates(policyTask) ? { ...behavior, progress: false } : behavior;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// =============================================================================
|
|
283
|
+
// Chain Instruction Injection
|
|
284
|
+
// =============================================================================
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Resolve a file path: absolute paths pass through, relative paths get chainDir prepended.
|
|
288
|
+
*/
|
|
289
|
+
function resolveChainPath(filePath: string, chainDir: string): string {
|
|
290
|
+
return path.isAbsolute(filePath) ? filePath : path.join(chainDir, filePath);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Build chain instructions from resolved behavior.
|
|
295
|
+
* These are appended to the task to tell the agent what to read/write.
|
|
296
|
+
*/
|
|
297
|
+
export function writeInitialProgressFile(progressDir: string): void {
|
|
298
|
+
fs.mkdirSync(progressDir, { recursive: true });
|
|
299
|
+
try {
|
|
300
|
+
fs.writeFileSync(path.join(progressDir, "progress.md"), INITIAL_PROGRESS_CONTENT, { encoding: "utf-8", flag: "wx" });
|
|
301
|
+
} catch (error) {
|
|
302
|
+
if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export function buildChainInstructions(
|
|
307
|
+
behavior: ResolvedStepBehavior,
|
|
308
|
+
chainDir: string,
|
|
309
|
+
isFirstProgressAgent: boolean,
|
|
310
|
+
): { prefix: string; suffix: string } {
|
|
311
|
+
const prefixParts: string[] = [];
|
|
312
|
+
const suffixParts: string[] = [];
|
|
313
|
+
|
|
314
|
+
// READS - prepend to override any hardcoded filenames in task text
|
|
315
|
+
if (behavior.reads && behavior.reads.length > 0) {
|
|
316
|
+
const files = behavior.reads.map((f) => resolveChainPath(f, chainDir));
|
|
317
|
+
prefixParts.push(`[Read from: ${files.join(", ")}]`);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// OUTPUT - prepend so agent knows where to write
|
|
321
|
+
if (behavior.output) {
|
|
322
|
+
const outputPath = resolveChainPath(behavior.output, chainDir);
|
|
323
|
+
prefixParts.push(`[Write to: ${outputPath}]`);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Progress instructions in suffix (less critical)
|
|
327
|
+
if (behavior.progress) {
|
|
328
|
+
const progressPath = path.join(chainDir, "progress.md");
|
|
329
|
+
if (isFirstProgressAgent) {
|
|
330
|
+
suffixParts.push(`Create and maintain progress at: ${progressPath}`);
|
|
331
|
+
} else {
|
|
332
|
+
suffixParts.push(`Update progress at: ${progressPath}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const prefix = prefixParts.length > 0
|
|
337
|
+
? prefixParts.join("\n") + "\n\n"
|
|
338
|
+
: "";
|
|
339
|
+
|
|
340
|
+
const suffix = suffixParts.length > 0
|
|
341
|
+
? "\n\n---\n" + suffixParts.join("\n")
|
|
342
|
+
: "";
|
|
343
|
+
|
|
344
|
+
return { prefix, suffix };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// =============================================================================
|
|
348
|
+
// Parallel Step Support
|
|
349
|
+
// =============================================================================
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Resolve behaviors for all tasks in a parallel step.
|
|
353
|
+
* Creates namespaced output paths to avoid collisions.
|
|
354
|
+
*/
|
|
355
|
+
export function resolveParallelBehaviors(
|
|
356
|
+
tasks: ParallelTaskItem[],
|
|
357
|
+
agentConfigs: AgentConfig[],
|
|
358
|
+
stepIndex: number,
|
|
359
|
+
chainSkills?: string[] | false,
|
|
360
|
+
): ResolvedStepBehavior[] {
|
|
361
|
+
return tasks.map((task, taskIndex) => {
|
|
362
|
+
const config = agentConfigs.find((a) => a.name === task.agent);
|
|
363
|
+
if (!config) {
|
|
364
|
+
throw new Error(`Unknown agent: ${task.agent}`);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const behavior = resolveStepBehavior(config, {
|
|
368
|
+
output: task.output,
|
|
369
|
+
outputMode: task.outputMode,
|
|
370
|
+
reads: task.reads,
|
|
371
|
+
progress: task.progress,
|
|
372
|
+
skills: normalizeSkillInput(task.skill),
|
|
373
|
+
model: task.model,
|
|
374
|
+
}, chainSkills);
|
|
375
|
+
return { ...behavior, output: namespaceParallelOutput(behavior.output, task.agent, stepIndex, taskIndex) };
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export function namespaceParallelOutput(output: string | false | undefined, agent: string, stepIndex: number, taskIndex: number): string | false {
|
|
380
|
+
return output ? path.isAbsolute(output) ? output : path.join(`parallel-${stepIndex}`, `${taskIndex}-${agent}`, output) : false;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export type { ParallelTaskResult } from "../runs/shared/parallel-utils.ts";
|
|
384
|
+
export { aggregateParallelOutputs } from "../runs/shared/parallel-utils.ts";
|