@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,48 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
const MAX_NESTED_ID_LENGTH = 128;
|
|
3
|
+
export const MAX_NESTED_PATH_ENTRIES = 4;
|
|
4
|
+
export function isSafeNestedPathId(value) {
|
|
5
|
+
return typeof value === "string"
|
|
6
|
+
&& value.length > 0
|
|
7
|
+
&& value.length <= MAX_NESTED_ID_LENGTH
|
|
8
|
+
&& !path.isAbsolute(value)
|
|
9
|
+
&& !value.includes("/")
|
|
10
|
+
&& !value.includes("\\")
|
|
11
|
+
&& !value.includes("..");
|
|
12
|
+
}
|
|
13
|
+
function stepIndex(value) {
|
|
14
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : undefined;
|
|
15
|
+
}
|
|
16
|
+
function nonEmptyString(value, max) {
|
|
17
|
+
return typeof value === "string" && value.length > 0 ? value.slice(0, max) : undefined;
|
|
18
|
+
}
|
|
19
|
+
export function sanitizeNestedPath(value) {
|
|
20
|
+
if (!Array.isArray(value))
|
|
21
|
+
return [];
|
|
22
|
+
return value.map((part) => {
|
|
23
|
+
if (!part || typeof part !== "object")
|
|
24
|
+
return undefined;
|
|
25
|
+
const record = part;
|
|
26
|
+
if (!isSafeNestedPathId(record.runId))
|
|
27
|
+
return undefined;
|
|
28
|
+
return {
|
|
29
|
+
runId: record.runId,
|
|
30
|
+
...(stepIndex(record.stepIndex) !== undefined ? { stepIndex: stepIndex(record.stepIndex) } : {}),
|
|
31
|
+
...(nonEmptyString(record.agent, 128) ? { agent: nonEmptyString(record.agent, 128) } : {}),
|
|
32
|
+
};
|
|
33
|
+
}).filter((part) => Boolean(part)).slice(0, MAX_NESTED_PATH_ENTRIES);
|
|
34
|
+
}
|
|
35
|
+
export function parseNestedPathEnv(value) {
|
|
36
|
+
if (!value)
|
|
37
|
+
return [];
|
|
38
|
+
try {
|
|
39
|
+
return sanitizeNestedPath(JSON.parse(value));
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export function encodeNestedPathEnv(value) {
|
|
46
|
+
const sanitized = sanitizeNestedPath(value);
|
|
47
|
+
return sanitized.length ? JSON.stringify(sanitized) : "";
|
|
48
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { formatDuration, formatTokens, shortenPath } from "../../shared/formatters.js";
|
|
2
|
+
import { formatActivityLabel, formatRunAction } from "../../shared/status-format.js";
|
|
3
|
+
export function countNestedRuns(children) {
|
|
4
|
+
const counts = { total: 0, running: 0, paused: 0, complete: 0, failed: 0, blocked: 0, queued: 0 };
|
|
5
|
+
for (const child of children ?? []) {
|
|
6
|
+
counts.total++;
|
|
7
|
+
counts[child.state]++;
|
|
8
|
+
const nested = countNestedRuns([...(child.children ?? []), ...(child.steps?.flatMap((step) => step.children ?? []) ?? [])]);
|
|
9
|
+
counts.total += nested.total;
|
|
10
|
+
counts.running += nested.running;
|
|
11
|
+
counts.paused += nested.paused;
|
|
12
|
+
counts.complete += nested.complete;
|
|
13
|
+
counts.failed += nested.failed;
|
|
14
|
+
counts.blocked += nested.blocked;
|
|
15
|
+
counts.queued += nested.queued;
|
|
16
|
+
}
|
|
17
|
+
return counts;
|
|
18
|
+
}
|
|
19
|
+
export function formatNestedAggregate(children) {
|
|
20
|
+
const counts = countNestedRuns(children);
|
|
21
|
+
if (counts.total === 0)
|
|
22
|
+
return undefined;
|
|
23
|
+
const parts = [
|
|
24
|
+
counts.running > 0 ? `${counts.running} running` : "",
|
|
25
|
+
counts.paused > 0 ? `${counts.paused} paused` : "",
|
|
26
|
+
counts.failed > 0 ? `${counts.failed} failed` : "",
|
|
27
|
+
counts.blocked > 0 ? `${counts.blocked} need human action` : "",
|
|
28
|
+
counts.complete > 0 ? `${counts.complete} complete` : "",
|
|
29
|
+
counts.queued > 0 ? `${counts.queued} queued` : "",
|
|
30
|
+
].filter(Boolean);
|
|
31
|
+
return `+${counts.total} nested run${counts.total === 1 ? "" : "s"}${parts.length ? ` (${parts.join(", ")})` : ""}`;
|
|
32
|
+
}
|
|
33
|
+
function nestedRunLabel(run) {
|
|
34
|
+
if (run.agent)
|
|
35
|
+
return run.agent;
|
|
36
|
+
if (run.agents?.length)
|
|
37
|
+
return run.agents.length === 1 ? run.agents[0] : `${run.agents.slice(0, 2).join(", ")}${run.agents.length > 2 ? ` +${run.agents.length - 2}` : ""}`;
|
|
38
|
+
return run.id;
|
|
39
|
+
}
|
|
40
|
+
function formatNestedActivity(input) {
|
|
41
|
+
const facts = [];
|
|
42
|
+
if (input.currentTool && input.currentToolStartedAt !== undefined)
|
|
43
|
+
facts.push(`tool ${input.currentTool} ${formatDuration(Math.max(0, Date.now() - input.currentToolStartedAt))}`);
|
|
44
|
+
else if (input.currentTool)
|
|
45
|
+
facts.push(`tool ${input.currentTool}`);
|
|
46
|
+
if (input.currentPath)
|
|
47
|
+
facts.push(shortenPath(input.currentPath));
|
|
48
|
+
if (input.turnCount !== undefined)
|
|
49
|
+
facts.push(`${input.turnCount} turns`);
|
|
50
|
+
if (input.toolCount !== undefined)
|
|
51
|
+
facts.push(`${input.toolCount} tools`);
|
|
52
|
+
if (input.totalTokens)
|
|
53
|
+
facts.push(`${formatTokens(input.totalTokens.total)} tok`);
|
|
54
|
+
const activity = formatActivityLabel(input.lastActivityAt, input.activityState);
|
|
55
|
+
return activity || facts.length ? [activity, ...facts].filter(Boolean).join(" | ") : undefined;
|
|
56
|
+
}
|
|
57
|
+
function formatNestedRunLines(children, options) {
|
|
58
|
+
const lines = [];
|
|
59
|
+
const append = (items, depth, indent) => {
|
|
60
|
+
if (!items?.length || lines.length >= options.maxLines)
|
|
61
|
+
return;
|
|
62
|
+
if (depth > options.maxDepth) {
|
|
63
|
+
const aggregate = formatNestedAggregate(items);
|
|
64
|
+
if (aggregate && lines.length < options.maxLines)
|
|
65
|
+
lines.push(`${indent}↳ ${aggregate}`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
for (let index = 0; index < items.length; index++) {
|
|
69
|
+
const child = items[index];
|
|
70
|
+
if (lines.length >= options.maxLines) {
|
|
71
|
+
const aggregate = formatNestedAggregate(items.slice(index));
|
|
72
|
+
if (aggregate)
|
|
73
|
+
lines[lines.length - 1] = `${indent}↳ ${aggregate}`;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const activity = child.state === "running" ? formatNestedActivity(child) : undefined;
|
|
77
|
+
const error = child.error ? ` | error: ${child.error}` : "";
|
|
78
|
+
lines.push(`${indent}↳ ${nestedRunLabel(child)} [${child.id}] ${child.state}${activity ? ` | ${activity}` : ""}${error}`);
|
|
79
|
+
if (options.commandHints && lines.length < options.maxLines)
|
|
80
|
+
lines.push(`${indent} Status: ${formatRunAction("status", child.id, {}, options.childSafe)}`);
|
|
81
|
+
if (depth === options.maxDepth) {
|
|
82
|
+
const aggregate = formatNestedAggregate([...(child.steps?.flatMap((step) => step.children ?? []) ?? []), ...(child.children ?? [])]);
|
|
83
|
+
if (aggregate && lines.length < options.maxLines)
|
|
84
|
+
lines.push(`${indent} ↳ ${aggregate}`);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
for (const [stepIndex, step] of (child.steps ?? []).entries()) {
|
|
88
|
+
if (lines.length >= options.maxLines)
|
|
89
|
+
return;
|
|
90
|
+
const stepActivity = step.status === "running" ? formatNestedActivity(step) : undefined;
|
|
91
|
+
lines.push(`${indent} ${stepIndex + 1}. ${step.agent} ${step.status}${stepActivity ? ` | ${stepActivity}` : ""}${step.error ? ` | error: ${step.error}` : ""}`);
|
|
92
|
+
append(step.children, depth + 1, `${indent} `);
|
|
93
|
+
}
|
|
94
|
+
append(child.children, depth + 1, `${indent} `);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
append(children, 0, options.indent);
|
|
98
|
+
return lines;
|
|
99
|
+
}
|
|
100
|
+
export function formatNestedRunStatusLines(children, options = {}) {
|
|
101
|
+
return formatNestedRunLines(children, {
|
|
102
|
+
indent: options.indent ?? " ",
|
|
103
|
+
maxDepth: options.maxDepth ?? 2,
|
|
104
|
+
maxLines: options.maxLines ?? 40,
|
|
105
|
+
commandHints: options.commandHints ?? false,
|
|
106
|
+
childSafe: options.childSafe,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export function isParallelGroup(step) {
|
|
2
|
+
return "parallel" in step && Array.isArray(step.parallel);
|
|
3
|
+
}
|
|
4
|
+
export function isDynamicRunnerGroup(step) {
|
|
5
|
+
return "expand" in step && "collect" in step && "parallel" in step && !Array.isArray(step.parallel);
|
|
6
|
+
}
|
|
7
|
+
export function flattenSteps(steps) {
|
|
8
|
+
const flat = [];
|
|
9
|
+
for (const step of steps) {
|
|
10
|
+
if (isParallelGroup(step)) {
|
|
11
|
+
for (const task of step.parallel)
|
|
12
|
+
flat.push(task);
|
|
13
|
+
}
|
|
14
|
+
else if (isDynamicRunnerGroup(step)) {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
flat.push(step);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return flat;
|
|
22
|
+
}
|
|
23
|
+
export async function mapConcurrent(items, limit, fn) {
|
|
24
|
+
const safeLimit = Math.max(1, Math.floor(limit) || 1);
|
|
25
|
+
const results = new Array(items.length);
|
|
26
|
+
let next = 0;
|
|
27
|
+
async function worker(_workerIndex) {
|
|
28
|
+
while (next < items.length) {
|
|
29
|
+
const i = next++;
|
|
30
|
+
results[i] = await fn(items[i], i);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
await Promise.all(Array.from({ length: Math.min(safeLimit, items.length) }, (_, wi) => worker(wi)));
|
|
34
|
+
return results;
|
|
35
|
+
}
|
|
36
|
+
export function aggregateParallelOutputs(results, headerFormat = (i, agent) => `=== Parallel Task ${i + 1} (${agent}) ===`) {
|
|
37
|
+
return results
|
|
38
|
+
.map((r, i) => {
|
|
39
|
+
const header = headerFormat(r.taskIndex ?? i, r.agent);
|
|
40
|
+
const hasOutput = Boolean(r.output?.trim());
|
|
41
|
+
const status = r.exitCode === -1
|
|
42
|
+
? `SKIPPED${r.error ? `: ${r.error}` : ""}`
|
|
43
|
+
: r.exitCode !== 0 && r.exitCode !== null
|
|
44
|
+
? `FAILED (exit code ${r.exitCode})${r.error ? `: ${r.error}` : ""}`
|
|
45
|
+
: r.error
|
|
46
|
+
? `WARNING: ${r.error}`
|
|
47
|
+
: !hasOutput && r.outputTargetPath && r.outputTargetExists === false
|
|
48
|
+
? `EMPTY OUTPUT (expected output file missing: ${r.outputTargetPath})`
|
|
49
|
+
: !hasOutput && !r.outputTargetPath
|
|
50
|
+
? "EMPTY OUTPUT (no textual response returned)"
|
|
51
|
+
: "";
|
|
52
|
+
const body = status ? (hasOutput ? `${status}\n${r.output}` : status) : r.output;
|
|
53
|
+
return `${header}\n${body}`;
|
|
54
|
+
})
|
|
55
|
+
.join("\n\n");
|
|
56
|
+
}
|
|
57
|
+
export const FAIL_FAST_REASON = "subagent-fail-fast";
|
|
58
|
+
export function isFailFastAbort(signal) {
|
|
59
|
+
return signal?.aborted === true && signal.reason === FAIL_FAST_REASON;
|
|
60
|
+
}
|
|
61
|
+
export const MAX_PARALLEL_CONCURRENCY = 4;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import { journalStamp, scanJournal } from "../../shared/journal-reader.js";
|
|
4
|
+
function hashRange(fd, start, end, hash) {
|
|
5
|
+
const bytes = Buffer.allocUnsafe(64 * 1024);
|
|
6
|
+
while (start < end) {
|
|
7
|
+
const count = fs.readSync(fd, bytes, 0, Math.min(bytes.length, end - start), start);
|
|
8
|
+
if (!count)
|
|
9
|
+
throw new Error("Parent journal truncated during inspection");
|
|
10
|
+
hash.update(bytes.subarray(0, count));
|
|
11
|
+
start += count;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const receiptProjection = (path) => {
|
|
15
|
+
if (!path.length || ["type", "id", "timestamp", "customType"].includes(String(path[0])))
|
|
16
|
+
return true;
|
|
17
|
+
if (path[0] === "details")
|
|
18
|
+
return path.length === 1 || ["completion", "subagentCompletion", "result"].includes(String(path[1]))
|
|
19
|
+
&& (path[1] !== "result" || path.length <= 3 || path[3] === "wait");
|
|
20
|
+
if (path[0] === "message")
|
|
21
|
+
return path.length === 1 || ["role", "toolName", "usage"].includes(String(path[1]))
|
|
22
|
+
|| path[1] === "details" && (path.length === 2 || ["wait", "parentUsage"].includes(String(path[2])));
|
|
23
|
+
return false;
|
|
24
|
+
};
|
|
25
|
+
/** Cache only LF-published receipt fields, never transcripts or accepted-but-unsaved messages. */
|
|
26
|
+
export function createParentReceiptReader(policy) {
|
|
27
|
+
let cache;
|
|
28
|
+
return {
|
|
29
|
+
clear() { cache = undefined; },
|
|
30
|
+
read(file) {
|
|
31
|
+
if (!file || !fs.existsSync(file)) {
|
|
32
|
+
cache = undefined;
|
|
33
|
+
return new Map();
|
|
34
|
+
}
|
|
35
|
+
const fd = fs.openSync(file, "r");
|
|
36
|
+
try {
|
|
37
|
+
const stat = fs.fstatSync(fd, { bigint: true }), stamp = journalStamp(stat), identity = `${stat.dev}:${stat.ino}`;
|
|
38
|
+
let hash = createHash("sha256");
|
|
39
|
+
let append = cache?.file === file && cache.identity === identity && Number(stat.size) >= cache.end;
|
|
40
|
+
if (append) {
|
|
41
|
+
// Coarse timestamps can hide same-size edits; even a stat hit needs verified bytes.
|
|
42
|
+
hashRange(fd, 0, cache.end, hash);
|
|
43
|
+
append = hash.copy().digest("hex") === cache.digest;
|
|
44
|
+
}
|
|
45
|
+
if (append && Number(stat.size) === cache.end) {
|
|
46
|
+
if (journalStamp(fs.fstatSync(fd, { bigint: true })) !== stamp)
|
|
47
|
+
throw new Error("Parent journal changed during inspection");
|
|
48
|
+
return cache.records;
|
|
49
|
+
}
|
|
50
|
+
if (!append)
|
|
51
|
+
hash = createHash("sha256");
|
|
52
|
+
const start = append ? cache.end : 0, records = append ? new Map(cache.records) : new Map();
|
|
53
|
+
const end = scanJournal(fd, receiptProjection, ({ value }) => {
|
|
54
|
+
if (value.type === "custom_message" || value.type === "message" && value.message?.role === "toolResult")
|
|
55
|
+
records.set(value.id, value);
|
|
56
|
+
}, { policy, requireNewline: true, start, end: Number(stat.size) });
|
|
57
|
+
hashRange(fd, start, end, hash);
|
|
58
|
+
if (journalStamp(fs.fstatSync(fd, { bigint: true })) !== stamp)
|
|
59
|
+
throw new Error("Parent journal changed during inspection");
|
|
60
|
+
cache = { file, identity, end, digest: hash.digest("hex"), records };
|
|
61
|
+
return records;
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
fs.closeSync(fd);
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
import { SessionEntryCursor } from "../../shared/session-entries.js";
|
|
3
|
+
import { createParentReceiptReader } from "./parent-receipts.js";
|
|
4
|
+
import { validateNativeUsage } from "./native-usage.js";
|
|
5
|
+
const PREFIX = "subagent:";
|
|
6
|
+
const UNATTRIBUTED = "unattributed";
|
|
7
|
+
export function finalizedChildUsage(children, index) {
|
|
8
|
+
return children.flatMap((child) => (index === undefined || child.index === index) && child.state !== "live" && child.state !== "unknown" && !child.result?.detached && child.result?.accounting?.state !== "incomplete"
|
|
9
|
+
? child.result?.usage.contributions ?? [] : []);
|
|
10
|
+
}
|
|
11
|
+
function sumUsage(contributions) {
|
|
12
|
+
const total = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0,
|
|
13
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } };
|
|
14
|
+
for (const { usage } of contributions) {
|
|
15
|
+
for (const key of ["input", "output", "cacheRead", "cacheWrite", "totalTokens"])
|
|
16
|
+
total[key] += usage[key];
|
|
17
|
+
for (const key of ["input", "output", "cacheRead", "cacheWrite", "total"])
|
|
18
|
+
total.cost[key] += usage.cost[key];
|
|
19
|
+
// These are subsets of output/cacheWrite, never additional total tokens.
|
|
20
|
+
for (const key of ["reasoning", "cacheWrite1h"])
|
|
21
|
+
if (usage[key] !== undefined)
|
|
22
|
+
total[key] = (total[key] ?? 0) + usage[key];
|
|
23
|
+
}
|
|
24
|
+
return total;
|
|
25
|
+
}
|
|
26
|
+
function sameUsage(a, b) {
|
|
27
|
+
return isDeepStrictEqual({ ...a, reasoning: a.reasoning, cacheWrite1h: a.cacheWrite1h }, { ...b, reasoning: b.reasoning, cacheWrite1h: b.cacheWrite1h });
|
|
28
|
+
}
|
|
29
|
+
function checkContribution(previous, next) {
|
|
30
|
+
if (previous && ((previous.provider ?? UNATTRIBUTED) !== (next.provider ?? UNATTRIBUTED)
|
|
31
|
+
|| (previous.model ?? UNATTRIBUTED) !== (next.model ?? UNATTRIBUTED) || !sameUsage(previous.usage, next.usage))) {
|
|
32
|
+
throw new Error(`Conflicting subagent usage contribution: ${next.id}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function uniqueContributions(contributions) {
|
|
36
|
+
const unique = new Map();
|
|
37
|
+
for (const contribution of contributions) {
|
|
38
|
+
if (!contribution.id.trim())
|
|
39
|
+
throw new Error("Subagent usage requires a stable native contribution ID");
|
|
40
|
+
validateNativeUsage(contribution.usage);
|
|
41
|
+
checkContribution(unique.get(contribution.id), contribution);
|
|
42
|
+
unique.set(contribution.id, contribution);
|
|
43
|
+
}
|
|
44
|
+
return [...unique.values()];
|
|
45
|
+
}
|
|
46
|
+
function createReceiptIndex(toolNames) {
|
|
47
|
+
const cursor = new SessionEntryCursor();
|
|
48
|
+
const received = new Map();
|
|
49
|
+
return (manager, saved) => {
|
|
50
|
+
const changes = cursor.read(manager);
|
|
51
|
+
if (changes.reset)
|
|
52
|
+
received.clear();
|
|
53
|
+
for (const metadata of changes.entries) {
|
|
54
|
+
if (metadata.type === "usage") {
|
|
55
|
+
const id = metadata.contributionId;
|
|
56
|
+
if (id?.startsWith(PREFIX))
|
|
57
|
+
received.set(id.slice(PREFIX.length), { id: id.slice(PREFIX.length), provider: metadata.provider, model: metadata.model, usage: metadata.usage });
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (metadata.type !== "message" || metadata.message.role !== "toolResult" || !toolNames.includes(metadata.message.toolName))
|
|
61
|
+
continue;
|
|
62
|
+
const entry = saved.get(metadata.id) ?? (metadata.message.details !== undefined ? metadata : manager.getEntry(metadata.id) ?? metadata);
|
|
63
|
+
if (entry.type !== "message" || entry.message.role !== "toolResult")
|
|
64
|
+
continue;
|
|
65
|
+
const contributions = entry.message.details?.parentUsage?.contributions;
|
|
66
|
+
// Details/custom notifications alone are never evidence that native accounting ran.
|
|
67
|
+
if (Array.isArray(contributions) && entry.message.usage && sameUsage(entry.message.usage, sumUsage(contributions))) {
|
|
68
|
+
for (const contribution of contributions)
|
|
69
|
+
received.set(contribution.id, contribution);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return received;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function unrecorded(contributions, received) {
|
|
76
|
+
return uniqueContributions(contributions).filter((contribution) => {
|
|
77
|
+
const previous = received.get(contribution.id);
|
|
78
|
+
checkContribution(previous, contribution);
|
|
79
|
+
return !previous;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Register once per extension instance. Attach only to finalized wait/execution
|
|
84
|
+
* results, never inspection or streaming updates. Legacy tool aliases and saved
|
|
85
|
+
* native usage entries still deduplicate old-session accounting.
|
|
86
|
+
*/
|
|
87
|
+
export function registerParentUsage(pi, toolNames) {
|
|
88
|
+
const toolReceipts = createParentReceiptReader("inspect");
|
|
89
|
+
const receipts = createReceiptIndex(toolNames);
|
|
90
|
+
pi.on("message_end", (event, ctx) => {
|
|
91
|
+
const message = event.message;
|
|
92
|
+
if (message.role !== "toolResult" || !toolNames.includes(message.toolName))
|
|
93
|
+
return;
|
|
94
|
+
const details = message.details;
|
|
95
|
+
if (!details?.parentUsage)
|
|
96
|
+
return;
|
|
97
|
+
const pending = unrecorded(details.parentUsage.contributions, receipts(ctx.sessionManager, toolReceipts.read(ctx.sessionManager.getSessionFile())));
|
|
98
|
+
const { usage: _usage, ...rest } = message;
|
|
99
|
+
const { parentUsage: _parentUsage, ...restDetails } = details;
|
|
100
|
+
// Public replacement hook: native emits/persists final tool messages serially,
|
|
101
|
+
// even for concurrent tools. No in-memory reservation can outlive an aborted result.
|
|
102
|
+
return { message: { ...rest, details: JSON.parse(JSON.stringify({ ...restDetails, ...(pending.length ? { parentUsage: { contributions: pending } } : {}) })),
|
|
103
|
+
...(pending.length ? { usage: sumUsage(pending) } : {}) } };
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
isRecorded(contributions, ctx, saved) {
|
|
107
|
+
return unrecorded(contributions, receipts(ctx.sessionManager, saved)).length === 0;
|
|
108
|
+
},
|
|
109
|
+
attach(result, contributions, ctx) {
|
|
110
|
+
const { usage: _usage, ...rest } = result;
|
|
111
|
+
const { parentUsage: _parentUsage, ...details } = result.details;
|
|
112
|
+
if (!contributions.length)
|
|
113
|
+
return { ...rest, details };
|
|
114
|
+
// Required identity/conflict rejection precedes any optional host I/O.
|
|
115
|
+
const saved = toolReceipts.read(ctx.sessionManager.getSessionFile());
|
|
116
|
+
const pending = unrecorded(contributions, receipts(ctx.sessionManager, saved));
|
|
117
|
+
// Intent only. Top-level usage is added at final message_end, immediately before native persistence.
|
|
118
|
+
return { ...rest, details: { ...details, ...(pending.length ? { parentUsage: { contributions: pending } } : {}) } };
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|