@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,700 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { RESULTS_DIR, TEMP_ROOT_DIR, } from "../../shared/types.js";
|
|
5
|
+
import { ensureTempRoot } from "../../shared/temp-root.js";
|
|
6
|
+
import { isSafeNestedPathId, parseNestedPathEnv, sanitizeNestedPath } from "./nested-path.js";
|
|
7
|
+
import { SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV, SUBAGENT_PARENT_CHILD_INDEX_ENV, SUBAGENT_PARENT_CONTROL_INBOX_ENV, SUBAGENT_PARENT_DEPTH_ENV, SUBAGENT_PARENT_EVENT_SINK_ENV, SUBAGENT_PARENT_PATH_ENV, SUBAGENT_PARENT_ROOT_RUN_ID_ENV, SUBAGENT_PARENT_RUN_ID_ENV, } from "./pi-args.js";
|
|
8
|
+
import { writeAtomicJson } from "../../shared/atomic-json.js";
|
|
9
|
+
import { getRunMetadataDir, readRunJson } from "./supervisor-questions.js";
|
|
10
|
+
import { acceptanceHumanAction } from "./acceptance-evaluation.js";
|
|
11
|
+
export const NESTED_EVENTS_DIR = path.join(TEMP_ROOT_DIR, "nested-subagent-events");
|
|
12
|
+
const ROUTE_FILE = "route.json";
|
|
13
|
+
const REGISTRY_FILE = "registry.json";
|
|
14
|
+
const MAX_EVENT_BYTES = 64 * 1024;
|
|
15
|
+
const MAX_STEPS = 12;
|
|
16
|
+
const MAX_CHILDREN = 16;
|
|
17
|
+
const MAX_DEPTH = 3;
|
|
18
|
+
export function isSafeNestedId(value) {
|
|
19
|
+
return isSafeNestedPathId(value);
|
|
20
|
+
}
|
|
21
|
+
export function assertSafeNestedId(label, value) {
|
|
22
|
+
if (!isSafeNestedId(value))
|
|
23
|
+
throw new Error(`${label} must be a non-empty safe id token.`);
|
|
24
|
+
}
|
|
25
|
+
function assertSafeId(label, value) {
|
|
26
|
+
assertSafeNestedId(label, value);
|
|
27
|
+
}
|
|
28
|
+
function containedPath(base, candidate) {
|
|
29
|
+
const resolvedBase = path.resolve(base);
|
|
30
|
+
const resolvedCandidate = path.resolve(candidate);
|
|
31
|
+
return resolvedCandidate === resolvedBase || resolvedCandidate.startsWith(`${resolvedBase}${path.sep}`);
|
|
32
|
+
}
|
|
33
|
+
function commonRouteRoot(route) {
|
|
34
|
+
return path.dirname(path.resolve(route.eventSink));
|
|
35
|
+
}
|
|
36
|
+
function validateRouteShape(route) {
|
|
37
|
+
assertSafeId("rootRunId", route.rootRunId);
|
|
38
|
+
assertSafeId("capabilityToken", route.capabilityToken);
|
|
39
|
+
if (!containedPath(NESTED_EVENTS_DIR, route.eventSink))
|
|
40
|
+
throw new Error("Nested event sink is outside the subagent nested event root.");
|
|
41
|
+
if (!containedPath(NESTED_EVENTS_DIR, route.controlInbox))
|
|
42
|
+
throw new Error("Nested control inbox is outside the subagent nested event root.");
|
|
43
|
+
if (commonRouteRoot(route) !== path.dirname(path.resolve(route.controlInbox)))
|
|
44
|
+
throw new Error("Nested event sink and control inbox must share one route root.");
|
|
45
|
+
}
|
|
46
|
+
export function createNestedRoute(rootRunId) {
|
|
47
|
+
assertSafeId("rootRunId", rootRunId);
|
|
48
|
+
ensureTempRoot();
|
|
49
|
+
const capabilityToken = randomUUID();
|
|
50
|
+
const routeRoot = path.join(NESTED_EVENTS_DIR, `${rootRunId}-${capabilityToken}`);
|
|
51
|
+
const eventSink = path.join(routeRoot, "events");
|
|
52
|
+
const controlInbox = path.join(routeRoot, "controls");
|
|
53
|
+
fs.mkdirSync(eventSink, { recursive: true, mode: 0o700 });
|
|
54
|
+
fs.mkdirSync(controlInbox, { recursive: true, mode: 0o700 });
|
|
55
|
+
fs.writeFileSync(path.join(routeRoot, ROUTE_FILE), `${JSON.stringify({ rootRunId, capabilityToken, createdAt: Date.now() })}\n`, { mode: 0o600 });
|
|
56
|
+
return { rootRunId, eventSink, controlInbox, capabilityToken };
|
|
57
|
+
}
|
|
58
|
+
export function resolveNestedRouteFromEnv(env = process.env) {
|
|
59
|
+
const rootRunId = env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV];
|
|
60
|
+
const eventSink = env[SUBAGENT_PARENT_EVENT_SINK_ENV];
|
|
61
|
+
const controlInbox = env[SUBAGENT_PARENT_CONTROL_INBOX_ENV];
|
|
62
|
+
const capabilityToken = env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV];
|
|
63
|
+
if (!rootRunId || !eventSink || !controlInbox || !capabilityToken)
|
|
64
|
+
return undefined;
|
|
65
|
+
const route = { rootRunId, eventSink, controlInbox, capabilityToken };
|
|
66
|
+
validateRouteShape(route);
|
|
67
|
+
const routeFile = path.join(commonRouteRoot(route), ROUTE_FILE);
|
|
68
|
+
const metadata = JSON.parse(fs.readFileSync(routeFile, "utf-8"));
|
|
69
|
+
if (metadata.rootRunId !== rootRunId || metadata.capabilityToken !== capabilityToken) {
|
|
70
|
+
throw new Error("Nested event route metadata does not match the provided root id and capability token.");
|
|
71
|
+
}
|
|
72
|
+
return route;
|
|
73
|
+
}
|
|
74
|
+
export function resolveInheritedNestedRouteFromEnv(env = process.env) {
|
|
75
|
+
try {
|
|
76
|
+
return resolveNestedRouteFromEnv(env);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
console.error("Ignoring invalid nested subagent event route:", error);
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export function resolveNestedParentAddressFromEnv(env = process.env) {
|
|
84
|
+
const parentRunId = env[SUBAGENT_PARENT_RUN_ID_ENV];
|
|
85
|
+
if (!isSafeNestedId(parentRunId))
|
|
86
|
+
return undefined;
|
|
87
|
+
const rawIndex = env[SUBAGENT_PARENT_CHILD_INDEX_ENV];
|
|
88
|
+
const parentStepIndex = rawIndex && /^\d+$/.test(rawIndex) ? Number(rawIndex) : undefined;
|
|
89
|
+
const depth = Math.min(Math.max(1, clampNumber(Number(env[SUBAGENT_PARENT_DEPTH_ENV])) ?? 1), MAX_DEPTH);
|
|
90
|
+
const parsedPath = parseNestedPathEnv(env[SUBAGENT_PARENT_PATH_ENV]);
|
|
91
|
+
const nestedPath = parsedPath.length ? parsedPath : [{ runId: parentRunId, ...(parentStepIndex !== undefined ? { stepIndex: parentStepIndex } : {}) }];
|
|
92
|
+
return { parentRunId, ...(parentStepIndex !== undefined ? { parentStepIndex } : {}), depth, path: nestedPath };
|
|
93
|
+
}
|
|
94
|
+
export function resolveNestedAsyncDir(rootRunId, run) {
|
|
95
|
+
if (!run.asyncDir)
|
|
96
|
+
return undefined;
|
|
97
|
+
const resolved = path.resolve(run.asyncDir);
|
|
98
|
+
if (resolved === getRunMetadataDir(run.id)) {
|
|
99
|
+
const launch = readRunJson(path.join(resolved, "launch.json"));
|
|
100
|
+
return launch?.runtimeVersion === 2 && launch.nestedRoute?.rootRunId === rootRunId && launch.nestedSelf?.parentRunId === run.parentRunId ? resolved : undefined;
|
|
101
|
+
}
|
|
102
|
+
const nestedRoot = path.resolve(TEMP_ROOT_DIR, "nested-subagent-runs", rootRunId, run.id);
|
|
103
|
+
const relative = path.relative(nestedRoot, resolved);
|
|
104
|
+
return resolved === nestedRoot || (!relative.startsWith("..") && !path.isAbsolute(relative)) ? resolved : undefined;
|
|
105
|
+
}
|
|
106
|
+
function clampNumber(value) {
|
|
107
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
108
|
+
}
|
|
109
|
+
function stringValue(value, max = 512) {
|
|
110
|
+
return typeof value === "string" && value.length > 0 ? value.slice(0, max) : undefined;
|
|
111
|
+
}
|
|
112
|
+
function sanitizeTokenUsage(value) {
|
|
113
|
+
if (!value || typeof value !== "object")
|
|
114
|
+
return undefined;
|
|
115
|
+
const raw = value;
|
|
116
|
+
const input = clampNumber(raw.input);
|
|
117
|
+
const output = clampNumber(raw.output);
|
|
118
|
+
const total = clampNumber(raw.total);
|
|
119
|
+
return input !== undefined && output !== undefined && total !== undefined
|
|
120
|
+
? { input, output, total }
|
|
121
|
+
: undefined;
|
|
122
|
+
}
|
|
123
|
+
function sanitizeState(value, fallback) {
|
|
124
|
+
return value === "queued" || value === "running" || value === "complete" || value === "failed" || value === "blocked" || value === "paused"
|
|
125
|
+
? value
|
|
126
|
+
: fallback;
|
|
127
|
+
}
|
|
128
|
+
function sanitizeStep(input, depth) {
|
|
129
|
+
if (!input || typeof input !== "object")
|
|
130
|
+
return undefined;
|
|
131
|
+
const raw = input;
|
|
132
|
+
const agent = stringValue(raw.agent, 128);
|
|
133
|
+
if (!agent)
|
|
134
|
+
return undefined;
|
|
135
|
+
const status = raw.status === "pending" || raw.status === "running" || raw.status === "complete" || raw.status === "completed" || raw.status === "failed" || raw.status === "blocked" || raw.status === "paused"
|
|
136
|
+
? raw.status
|
|
137
|
+
: "pending";
|
|
138
|
+
return {
|
|
139
|
+
agent,
|
|
140
|
+
status,
|
|
141
|
+
...(stringValue(raw.sessionFile, 2048) ? { sessionFile: stringValue(raw.sessionFile, 2048) } : {}),
|
|
142
|
+
...(raw.activityState === "needs_attention" ? { activityState: raw.activityState } : {}),
|
|
143
|
+
...(clampNumber(raw.lastActivityAt) !== undefined ? { lastActivityAt: clampNumber(raw.lastActivityAt) } : {}),
|
|
144
|
+
...(stringValue(raw.currentTool, 128) ? { currentTool: stringValue(raw.currentTool, 128) } : {}),
|
|
145
|
+
...(clampNumber(raw.currentToolStartedAt) !== undefined ? { currentToolStartedAt: clampNumber(raw.currentToolStartedAt) } : {}),
|
|
146
|
+
...(stringValue(raw.currentPath, 2048) ? { currentPath: stringValue(raw.currentPath, 2048) } : {}),
|
|
147
|
+
...(clampNumber(raw.turnCount) !== undefined ? { turnCount: clampNumber(raw.turnCount) } : {}),
|
|
148
|
+
...(clampNumber(raw.toolCount) !== undefined ? { toolCount: clampNumber(raw.toolCount) } : {}),
|
|
149
|
+
...(clampNumber(raw.startedAt) !== undefined ? { startedAt: clampNumber(raw.startedAt) } : {}),
|
|
150
|
+
...(clampNumber(raw.endedAt) !== undefined ? { endedAt: clampNumber(raw.endedAt) } : {}),
|
|
151
|
+
...(stringValue(raw.error, 1024) ? { error: stringValue(raw.error, 1024) } : {}),
|
|
152
|
+
...(depth < MAX_DEPTH && Array.isArray(raw.children) ? { children: raw.children.map((child) => sanitizeSummary(child, depth + 1)).filter((child) => Boolean(child)).slice(0, MAX_CHILDREN) } : {}),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
export function sanitizeSummary(input, depth = 0) {
|
|
156
|
+
if (!input || typeof input !== "object")
|
|
157
|
+
return undefined;
|
|
158
|
+
const raw = input;
|
|
159
|
+
if (!isSafeNestedId(raw.id) || !isSafeNestedId(raw.parentRunId))
|
|
160
|
+
return undefined;
|
|
161
|
+
const pathParts = sanitizeNestedPath(raw.path);
|
|
162
|
+
const steps = Array.isArray(raw.steps)
|
|
163
|
+
? raw.steps.map((step) => sanitizeStep(step, depth + 1)).filter((step) => Boolean(step)).slice(0, MAX_STEPS)
|
|
164
|
+
: undefined;
|
|
165
|
+
const totalTokens = sanitizeTokenUsage(raw.totalTokens);
|
|
166
|
+
return {
|
|
167
|
+
id: raw.id,
|
|
168
|
+
parentRunId: raw.parentRunId,
|
|
169
|
+
...(clampNumber(raw.parentStepIndex) !== undefined ? { parentStepIndex: clampNumber(raw.parentStepIndex) } : {}),
|
|
170
|
+
...(stringValue(raw.parentAgent, 128) ? { parentAgent: stringValue(raw.parentAgent, 128) } : {}),
|
|
171
|
+
depth: Math.min(Math.max(0, clampNumber(raw.depth) ?? 0), MAX_DEPTH),
|
|
172
|
+
path: pathParts,
|
|
173
|
+
state: sanitizeState(raw.state, "running"),
|
|
174
|
+
...(raw.indexedControl === true ? { indexedControl: true } : {}),
|
|
175
|
+
...(stringValue(raw.asyncDir, 2048) ? { asyncDir: stringValue(raw.asyncDir, 2048) } : {}),
|
|
176
|
+
...(clampNumber(raw.pid) !== undefined && clampNumber(raw.pid) > 0 && Number.isInteger(clampNumber(raw.pid)) ? { pid: clampNumber(raw.pid) } : {}),
|
|
177
|
+
...(stringValue(raw.sessionId, 256) ? { sessionId: stringValue(raw.sessionId, 256) } : {}),
|
|
178
|
+
...(stringValue(raw.sessionFile, 2048) ? { sessionFile: stringValue(raw.sessionFile, 2048) } : {}),
|
|
179
|
+
...(stringValue(raw.intercomTarget, 256) ? { intercomTarget: stringValue(raw.intercomTarget, 256) } : {}),
|
|
180
|
+
...(stringValue(raw.ownerIntercomTarget, 256) ? { ownerIntercomTarget: stringValue(raw.ownerIntercomTarget, 256) } : {}),
|
|
181
|
+
...(stringValue(raw.leafIntercomTarget, 256) ? { leafIntercomTarget: stringValue(raw.leafIntercomTarget, 256) } : {}),
|
|
182
|
+
...(raw.ownerState === "live" || raw.ownerState === "gone" || raw.ownerState === "unknown" ? { ownerState: raw.ownerState } : {}),
|
|
183
|
+
...(stringValue(raw.controlInbox, 2048) ? { controlInbox: stringValue(raw.controlInbox, 2048) } : {}),
|
|
184
|
+
...(stringValue(raw.capabilityToken, 128) ? { capabilityToken: stringValue(raw.capabilityToken, 128) } : {}),
|
|
185
|
+
...(raw.mode === "single" || raw.mode === "parallel" || raw.mode === "chain" ? { mode: raw.mode } : {}),
|
|
186
|
+
...(stringValue(raw.agent, 128) ? { agent: stringValue(raw.agent, 128) } : {}),
|
|
187
|
+
...(Array.isArray(raw.agents) ? { agents: raw.agents.map((agent) => stringValue(agent, 128)).filter((agent) => Boolean(agent)).slice(0, MAX_STEPS) } : {}),
|
|
188
|
+
...(clampNumber(raw.currentStep) !== undefined ? { currentStep: clampNumber(raw.currentStep) } : {}),
|
|
189
|
+
...(clampNumber(raw.chainStepCount) !== undefined ? { chainStepCount: clampNumber(raw.chainStepCount) } : {}),
|
|
190
|
+
...(raw.activityState === "needs_attention" ? { activityState: raw.activityState } : {}),
|
|
191
|
+
...(clampNumber(raw.lastActivityAt) !== undefined ? { lastActivityAt: clampNumber(raw.lastActivityAt) } : {}),
|
|
192
|
+
...(stringValue(raw.currentTool, 128) ? { currentTool: stringValue(raw.currentTool, 128) } : {}),
|
|
193
|
+
...(clampNumber(raw.currentToolStartedAt) !== undefined ? { currentToolStartedAt: clampNumber(raw.currentToolStartedAt) } : {}),
|
|
194
|
+
...(stringValue(raw.currentPath, 2048) ? { currentPath: stringValue(raw.currentPath, 2048) } : {}),
|
|
195
|
+
...(clampNumber(raw.turnCount) !== undefined ? { turnCount: clampNumber(raw.turnCount) } : {}),
|
|
196
|
+
...(clampNumber(raw.toolCount) !== undefined ? { toolCount: clampNumber(raw.toolCount) } : {}),
|
|
197
|
+
...(totalTokens ? { totalTokens } : {}),
|
|
198
|
+
...(clampNumber(raw.startedAt) !== undefined ? { startedAt: clampNumber(raw.startedAt) } : {}),
|
|
199
|
+
...(clampNumber(raw.endedAt) !== undefined ? { endedAt: clampNumber(raw.endedAt) } : {}),
|
|
200
|
+
...(clampNumber(raw.lastUpdate) !== undefined ? { lastUpdate: clampNumber(raw.lastUpdate) } : {}),
|
|
201
|
+
...(stringValue(raw.error, 1024) ? { error: stringValue(raw.error, 1024) } : {}),
|
|
202
|
+
...(steps && steps.length > 0 ? { steps } : {}),
|
|
203
|
+
...(depth < MAX_DEPTH && Array.isArray(raw.children) ? { children: raw.children.map((child) => sanitizeSummary(child, depth + 1)).filter((child) => Boolean(child)).slice(0, MAX_CHILDREN) } : {}),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function parseRecord(content, route) {
|
|
207
|
+
if (Buffer.byteLength(content, "utf-8") > MAX_EVENT_BYTES)
|
|
208
|
+
return undefined;
|
|
209
|
+
let parsed;
|
|
210
|
+
try {
|
|
211
|
+
parsed = JSON.parse(content);
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
if (!parsed || typeof parsed !== "object")
|
|
217
|
+
return undefined;
|
|
218
|
+
const raw = parsed;
|
|
219
|
+
if (raw.type !== "subagent.nested.started" && raw.type !== "subagent.nested.updated" && raw.type !== "subagent.nested.completed")
|
|
220
|
+
return undefined;
|
|
221
|
+
if (raw.rootRunId !== route.rootRunId || raw.capabilityToken !== route.capabilityToken)
|
|
222
|
+
return undefined;
|
|
223
|
+
if (!isSafeNestedId(raw.parentRunId))
|
|
224
|
+
return undefined;
|
|
225
|
+
const ts = clampNumber(raw.ts);
|
|
226
|
+
if (ts === undefined)
|
|
227
|
+
return undefined;
|
|
228
|
+
const child = sanitizeSummary(raw.child);
|
|
229
|
+
if (!child || child.id === route.rootRunId)
|
|
230
|
+
return undefined;
|
|
231
|
+
const routedChild = {
|
|
232
|
+
...child,
|
|
233
|
+
controlInbox: route.controlInbox,
|
|
234
|
+
capabilityToken: route.capabilityToken,
|
|
235
|
+
ownerState: child.ownerState ?? "unknown",
|
|
236
|
+
};
|
|
237
|
+
return {
|
|
238
|
+
type: raw.type,
|
|
239
|
+
ts,
|
|
240
|
+
rootRunId: route.rootRunId,
|
|
241
|
+
parentRunId: raw.parentRunId,
|
|
242
|
+
...(clampNumber(raw.parentStepIndex) !== undefined ? { parentStepIndex: clampNumber(raw.parentStepIndex) } : {}),
|
|
243
|
+
capabilityToken: route.capabilityToken,
|
|
244
|
+
child: routedChild,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function parseNestedEventRecords(content, route) {
|
|
248
|
+
if (!content.includes("\n")) {
|
|
249
|
+
const record = parseRecord(content.trim(), route);
|
|
250
|
+
return record ? [record] : [];
|
|
251
|
+
}
|
|
252
|
+
return content.split("\n")
|
|
253
|
+
.slice(0, content.endsWith("\n") ? undefined : -1)
|
|
254
|
+
.map((line) => line.trim() ? parseRecord(line, route) : undefined)
|
|
255
|
+
.filter((event) => Boolean(event));
|
|
256
|
+
}
|
|
257
|
+
function terminal(state) {
|
|
258
|
+
return state === "complete" || state === "failed" || state === "blocked" || state === "paused";
|
|
259
|
+
}
|
|
260
|
+
function mergeSummary(existing, event) {
|
|
261
|
+
const incomingState = event.type === "subagent.nested.completed" && event.child.state === "running" ? "complete" : event.child.state;
|
|
262
|
+
const incoming = { ...event.child, state: incomingState, lastUpdate: event.child.lastUpdate ?? event.ts };
|
|
263
|
+
if (!existing)
|
|
264
|
+
return incoming;
|
|
265
|
+
const existingUpdate = existing.lastUpdate ?? 0;
|
|
266
|
+
const incomingUpdate = incoming.lastUpdate ?? event.ts;
|
|
267
|
+
if (incomingUpdate < existingUpdate)
|
|
268
|
+
return existing;
|
|
269
|
+
if (terminal(existing.state) && !terminal(incoming.state))
|
|
270
|
+
return existing;
|
|
271
|
+
if (terminal(existing.state) && terminal(incoming.state) && incomingUpdate === existingUpdate)
|
|
272
|
+
return existing;
|
|
273
|
+
return { ...existing, ...incoming, state: incoming.state, lastUpdate: Math.max(existingUpdate, incomingUpdate) };
|
|
274
|
+
}
|
|
275
|
+
function attachChild(children, event) {
|
|
276
|
+
let updated = false;
|
|
277
|
+
const walk = (items) => items.map((item) => {
|
|
278
|
+
if (item.id === event.parentRunId) {
|
|
279
|
+
const existingChildren = item.children ?? [];
|
|
280
|
+
const childIndex = existingChildren.findIndex((child) => child.id === event.child.id);
|
|
281
|
+
const nextChild = mergeSummary(childIndex >= 0 ? existingChildren[childIndex] : undefined, event);
|
|
282
|
+
const nextChildren = childIndex >= 0
|
|
283
|
+
? existingChildren.map((child, index) => index === childIndex ? nextChild : child)
|
|
284
|
+
: [...existingChildren, nextChild];
|
|
285
|
+
updated = true;
|
|
286
|
+
return { ...item, children: nextChildren.slice(0, MAX_CHILDREN), lastUpdate: Math.max(item.lastUpdate ?? 0, event.ts) };
|
|
287
|
+
}
|
|
288
|
+
if (!item.children?.length)
|
|
289
|
+
return item;
|
|
290
|
+
const nextChildren = walk(item.children);
|
|
291
|
+
return nextChildren === item.children ? item : { ...item, children: nextChildren };
|
|
292
|
+
});
|
|
293
|
+
const next = walk(children);
|
|
294
|
+
if (updated)
|
|
295
|
+
return next;
|
|
296
|
+
const childIndex = next.findIndex((child) => child.id === event.child.id);
|
|
297
|
+
const nextChild = mergeSummary(childIndex >= 0 ? next[childIndex] : undefined, event);
|
|
298
|
+
return childIndex >= 0
|
|
299
|
+
? next.map((child, index) => index === childIndex ? nextChild : child)
|
|
300
|
+
: [...next, nextChild].slice(0, MAX_CHILDREN);
|
|
301
|
+
}
|
|
302
|
+
export function applyNestedEvent(registry, event) {
|
|
303
|
+
return {
|
|
304
|
+
...registry,
|
|
305
|
+
updatedAt: Math.max(registry.updatedAt, event.ts),
|
|
306
|
+
children: attachChild(registry.children, event),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
function registryPath(route) {
|
|
310
|
+
return path.join(commonRouteRoot(route), REGISTRY_FILE);
|
|
311
|
+
}
|
|
312
|
+
function readNestedRoute(routeRoot) {
|
|
313
|
+
const metadata = JSON.parse(fs.readFileSync(path.join(routeRoot, ROUTE_FILE), "utf-8"));
|
|
314
|
+
if (typeof metadata.rootRunId !== "string" || typeof metadata.capabilityToken !== "string")
|
|
315
|
+
throw new Error("Invalid nested route metadata.");
|
|
316
|
+
const route = {
|
|
317
|
+
rootRunId: metadata.rootRunId,
|
|
318
|
+
eventSink: path.join(routeRoot, "events"),
|
|
319
|
+
controlInbox: path.join(routeRoot, "controls"),
|
|
320
|
+
capabilityToken: metadata.capabilityToken,
|
|
321
|
+
};
|
|
322
|
+
validateRouteShape(route);
|
|
323
|
+
return route;
|
|
324
|
+
}
|
|
325
|
+
function listNestedRoutes(rootRunId) {
|
|
326
|
+
let entries;
|
|
327
|
+
try {
|
|
328
|
+
entries = fs.readdirSync(NESTED_EVENTS_DIR);
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
if (error.code === "ENOENT")
|
|
332
|
+
return [];
|
|
333
|
+
throw error;
|
|
334
|
+
}
|
|
335
|
+
const routes = [];
|
|
336
|
+
for (const entry of entries) {
|
|
337
|
+
if (rootRunId && !entry.startsWith(`${rootRunId}-`))
|
|
338
|
+
continue;
|
|
339
|
+
try {
|
|
340
|
+
const route = readNestedRoute(path.join(NESTED_EVENTS_DIR, entry));
|
|
341
|
+
if (!rootRunId || route.rootRunId === rootRunId)
|
|
342
|
+
routes.push(route);
|
|
343
|
+
}
|
|
344
|
+
catch {
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return routes;
|
|
349
|
+
}
|
|
350
|
+
export function findNestedRouteForRootId(rootRunId) {
|
|
351
|
+
assertSafeId("rootRunId", rootRunId);
|
|
352
|
+
return listNestedRoutes(rootRunId)[0];
|
|
353
|
+
}
|
|
354
|
+
export function projectNestedRegistryForRoot(rootRunId) {
|
|
355
|
+
const route = findNestedRouteForRootId(rootRunId);
|
|
356
|
+
return route ? projectNestedEvents(route) : undefined;
|
|
357
|
+
}
|
|
358
|
+
export function findNestedRun(children, id) {
|
|
359
|
+
if (!children?.length)
|
|
360
|
+
return undefined;
|
|
361
|
+
for (const child of children) {
|
|
362
|
+
if (child.id === id)
|
|
363
|
+
return child;
|
|
364
|
+
const nested = findNestedRun(child.children, id) ?? findNestedRun(child.steps?.flatMap((step) => step.children ?? []), id);
|
|
365
|
+
if (nested)
|
|
366
|
+
return nested;
|
|
367
|
+
}
|
|
368
|
+
return undefined;
|
|
369
|
+
}
|
|
370
|
+
function collectNestedRuns(children, output = []) {
|
|
371
|
+
for (const child of children ?? []) {
|
|
372
|
+
output.push(child);
|
|
373
|
+
collectNestedRuns(child.children, output);
|
|
374
|
+
collectNestedRuns(child.steps?.flatMap((step) => step.children ?? []), output);
|
|
375
|
+
}
|
|
376
|
+
return output;
|
|
377
|
+
}
|
|
378
|
+
function collectScopedNestedRuns(children, scope, output = []) {
|
|
379
|
+
if (!scope)
|
|
380
|
+
return collectNestedRuns(children, output);
|
|
381
|
+
for (const child of children ?? []) {
|
|
382
|
+
if (child.parentRunId === scope.parentRunId && (scope.parentStepIndex === undefined || child.parentStepIndex === scope.parentStepIndex)) {
|
|
383
|
+
collectNestedRuns([child], output);
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
collectScopedNestedRuns(child.children, scope, output);
|
|
387
|
+
collectScopedNestedRuns(child.steps?.flatMap((step) => step.children ?? []), scope, output);
|
|
388
|
+
}
|
|
389
|
+
return output;
|
|
390
|
+
}
|
|
391
|
+
export function findNestedRunMatchesById(id, options = {}) {
|
|
392
|
+
assertSafeId("id", id);
|
|
393
|
+
const matches = [];
|
|
394
|
+
for (const route of options.scope?.routes ?? listNestedRoutes()) {
|
|
395
|
+
try {
|
|
396
|
+
const registry = projectNestedEvents(route);
|
|
397
|
+
for (const run of collectScopedNestedRuns(registry.children, options.scope?.descendantOf)) {
|
|
398
|
+
if (options.prefix ? run.id.startsWith(id) : run.id === id)
|
|
399
|
+
matches.push({ rootRunId: route.rootRunId, route, run });
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
catch {
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return matches;
|
|
407
|
+
}
|
|
408
|
+
export function readNestedRegistry(route) {
|
|
409
|
+
validateRouteShape(route);
|
|
410
|
+
try {
|
|
411
|
+
const parsed = JSON.parse(fs.readFileSync(registryPath(route), "utf-8"));
|
|
412
|
+
return {
|
|
413
|
+
rootRunId: route.rootRunId,
|
|
414
|
+
updatedAt: typeof parsed.updatedAt === "number" ? parsed.updatedAt : 0,
|
|
415
|
+
children: Array.isArray(parsed.children) ? parsed.children.map((child) => sanitizeSummary(child)).filter((child) => Boolean(child)) : [],
|
|
416
|
+
processedEvents: Array.isArray(parsed.processedEvents) ? parsed.processedEvents.filter((item) => typeof item === "string") : [],
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
catch (error) {
|
|
420
|
+
if (error.code !== "ENOENT")
|
|
421
|
+
throw error;
|
|
422
|
+
return { rootRunId: route.rootRunId, updatedAt: 0, children: [], processedEvents: [] };
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
function readRouteFiles(dir, include) {
|
|
426
|
+
let entries;
|
|
427
|
+
try {
|
|
428
|
+
entries = fs.readdirSync(dir).filter(include).sort();
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
if (error.code === "ENOENT")
|
|
432
|
+
return [];
|
|
433
|
+
throw error;
|
|
434
|
+
}
|
|
435
|
+
return entries.flatMap((entry) => {
|
|
436
|
+
const filePath = path.join(dir, entry);
|
|
437
|
+
if (!containedPath(dir, filePath))
|
|
438
|
+
return [];
|
|
439
|
+
try {
|
|
440
|
+
const stat = fs.statSync(filePath);
|
|
441
|
+
return stat.isFile() && stat.size <= MAX_EVENT_BYTES ? [{ entry, filePath, content: fs.readFileSync(filePath, "utf-8") }] : [];
|
|
442
|
+
}
|
|
443
|
+
catch {
|
|
444
|
+
return [];
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
export function projectNestedEvents(route) {
|
|
449
|
+
validateRouteShape(route);
|
|
450
|
+
let registry = readNestedRegistry(route);
|
|
451
|
+
const seen = new Set(registry.processedEvents);
|
|
452
|
+
let changed = false;
|
|
453
|
+
for (const { entry, content } of readRouteFiles(route.eventSink, (name) => !seen.has(name) && (name.endsWith(".json") || name.endsWith(".jsonl")))) {
|
|
454
|
+
for (const event of parseNestedEventRecords(content, route)) {
|
|
455
|
+
registry = applyNestedEvent(registry, event);
|
|
456
|
+
changed = true;
|
|
457
|
+
}
|
|
458
|
+
seen.add(entry);
|
|
459
|
+
changed = true;
|
|
460
|
+
}
|
|
461
|
+
if (changed) {
|
|
462
|
+
// ponytail: directory enumeration and durable IDs still grow with route history.
|
|
463
|
+
// Archive immutable files with an atomic registry checkpoint if that becomes costly.
|
|
464
|
+
registry = { ...registry, processedEvents: [...seen] };
|
|
465
|
+
// Parent projection is the only writer to this sidecar registry. Child and
|
|
466
|
+
// runner processes only create immutable event files, so parent status.json
|
|
467
|
+
// remains owned by the existing runner writer and is never rewritten here.
|
|
468
|
+
writeAtomicJson(registryPath(route), registry);
|
|
469
|
+
}
|
|
470
|
+
return registry;
|
|
471
|
+
}
|
|
472
|
+
function writeRouteRecord(dir, ts, payload) {
|
|
473
|
+
const content = `${JSON.stringify(payload)}\n`;
|
|
474
|
+
if (Buffer.byteLength(content, "utf-8") > MAX_EVENT_BYTES)
|
|
475
|
+
throw new Error("Nested route record exceeds the maximum size.");
|
|
476
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
477
|
+
const name = `${String(ts).padStart(13, "0")}-${randomUUID()}.json`;
|
|
478
|
+
const tmp = path.join(dir, `.${name}.tmp`);
|
|
479
|
+
const finalPath = path.join(dir, name);
|
|
480
|
+
fs.writeFileSync(tmp, content, { mode: 0o600 });
|
|
481
|
+
fs.renameSync(tmp, finalPath);
|
|
482
|
+
return finalPath;
|
|
483
|
+
}
|
|
484
|
+
export function writeNestedEvent(route, event) {
|
|
485
|
+
validateRouteShape(route);
|
|
486
|
+
const record = {
|
|
487
|
+
...event,
|
|
488
|
+
rootRunId: route.rootRunId,
|
|
489
|
+
capabilityToken: route.capabilityToken,
|
|
490
|
+
};
|
|
491
|
+
const sanitized = parseRecord(JSON.stringify(record), route);
|
|
492
|
+
if (!sanitized)
|
|
493
|
+
throw new Error("Nested event record failed validation.");
|
|
494
|
+
writeRouteRecord(route.eventSink, sanitized.ts, sanitized);
|
|
495
|
+
}
|
|
496
|
+
function parseControlRequest(content, route) {
|
|
497
|
+
if (Buffer.byteLength(content, "utf-8") > MAX_EVENT_BYTES)
|
|
498
|
+
return undefined;
|
|
499
|
+
let parsed;
|
|
500
|
+
try {
|
|
501
|
+
parsed = JSON.parse(content);
|
|
502
|
+
}
|
|
503
|
+
catch {
|
|
504
|
+
return undefined;
|
|
505
|
+
}
|
|
506
|
+
if (!parsed || typeof parsed !== "object")
|
|
507
|
+
return undefined;
|
|
508
|
+
const raw = parsed;
|
|
509
|
+
if (raw.type !== "subagent.nested.control-request")
|
|
510
|
+
return undefined;
|
|
511
|
+
if (raw.rootRunId !== route.rootRunId || raw.capabilityToken !== route.capabilityToken)
|
|
512
|
+
return undefined;
|
|
513
|
+
if (!isSafeNestedId(raw.requestId) || !isSafeNestedId(raw.targetRunId))
|
|
514
|
+
return undefined;
|
|
515
|
+
if (raw.action !== "interrupt" && raw.action !== "resume")
|
|
516
|
+
return undefined;
|
|
517
|
+
const ts = clampNumber(raw.ts);
|
|
518
|
+
if (ts === undefined)
|
|
519
|
+
return undefined;
|
|
520
|
+
if (raw.targetChildIndex !== undefined && (typeof raw.targetChildIndex !== "number" || !Number.isInteger(raw.targetChildIndex) || raw.targetChildIndex < 0))
|
|
521
|
+
return undefined;
|
|
522
|
+
if (raw.index !== undefined && (typeof raw.index !== "number" || !Number.isSafeInteger(raw.index) || raw.index < 0))
|
|
523
|
+
return undefined;
|
|
524
|
+
return {
|
|
525
|
+
type: "subagent.nested.control-request",
|
|
526
|
+
ts,
|
|
527
|
+
rootRunId: route.rootRunId,
|
|
528
|
+
capabilityToken: route.capabilityToken,
|
|
529
|
+
requestId: raw.requestId,
|
|
530
|
+
targetRunId: raw.targetRunId,
|
|
531
|
+
...(typeof raw.targetChildIndex === "number" && Number.isInteger(raw.targetChildIndex) && raw.targetChildIndex >= 0 ? { targetChildIndex: raw.targetChildIndex } : {}),
|
|
532
|
+
...(typeof raw.index === "number" ? { index: raw.index } : {}),
|
|
533
|
+
action: raw.action,
|
|
534
|
+
...(stringValue(raw.message, 16_000) ? { message: stringValue(raw.message, 16_000) } : {}),
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
function parseControlResult(content, route) {
|
|
538
|
+
if (Buffer.byteLength(content, "utf-8") > MAX_EVENT_BYTES)
|
|
539
|
+
return undefined;
|
|
540
|
+
let parsed;
|
|
541
|
+
try {
|
|
542
|
+
parsed = JSON.parse(content);
|
|
543
|
+
}
|
|
544
|
+
catch {
|
|
545
|
+
return undefined;
|
|
546
|
+
}
|
|
547
|
+
if (!parsed || typeof parsed !== "object")
|
|
548
|
+
return undefined;
|
|
549
|
+
const raw = parsed;
|
|
550
|
+
if (raw.type !== "subagent.nested.control-result")
|
|
551
|
+
return undefined;
|
|
552
|
+
if (raw.rootRunId !== route.rootRunId || raw.capabilityToken !== route.capabilityToken)
|
|
553
|
+
return undefined;
|
|
554
|
+
if (!isSafeNestedId(raw.requestId) || !isSafeNestedId(raw.targetRunId))
|
|
555
|
+
return undefined;
|
|
556
|
+
const ts = clampNumber(raw.ts);
|
|
557
|
+
if (ts === undefined || typeof raw.ok !== "boolean")
|
|
558
|
+
return undefined;
|
|
559
|
+
return {
|
|
560
|
+
type: "subagent.nested.control-result",
|
|
561
|
+
ts,
|
|
562
|
+
rootRunId: route.rootRunId,
|
|
563
|
+
capabilityToken: route.capabilityToken,
|
|
564
|
+
requestId: raw.requestId,
|
|
565
|
+
targetRunId: raw.targetRunId,
|
|
566
|
+
ok: raw.ok,
|
|
567
|
+
message: stringValue(raw.message, 16_000) ?? (raw.ok ? "Control request completed." : "Control request failed."),
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
export function writeNestedControlRequest(route, request) {
|
|
571
|
+
validateRouteShape(route);
|
|
572
|
+
assertSafeId("requestId", request.requestId);
|
|
573
|
+
assertSafeId("targetRunId", request.targetRunId);
|
|
574
|
+
const record = {
|
|
575
|
+
type: "subagent.nested.control-request",
|
|
576
|
+
...request,
|
|
577
|
+
rootRunId: route.rootRunId,
|
|
578
|
+
capabilityToken: route.capabilityToken,
|
|
579
|
+
};
|
|
580
|
+
const sanitized = parseControlRequest(JSON.stringify(record), route);
|
|
581
|
+
if (!sanitized)
|
|
582
|
+
throw new Error("Nested control request failed validation.");
|
|
583
|
+
return writeRouteRecord(route.controlInbox, sanitized.ts, sanitized);
|
|
584
|
+
}
|
|
585
|
+
export function readNestedControlRequests(route, skipFiles = new Set()) {
|
|
586
|
+
validateRouteShape(route);
|
|
587
|
+
return readRouteFiles(route.controlInbox, (entry) => entry.endsWith(".json") && !skipFiles.has(entry)).flatMap(({ filePath, content }) => {
|
|
588
|
+
const request = parseControlRequest(content, route);
|
|
589
|
+
return request ? [{ ...request, filePath }] : [];
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
export function writeNestedControlResult(route, result) {
|
|
593
|
+
validateRouteShape(route);
|
|
594
|
+
assertSafeId("requestId", result.requestId);
|
|
595
|
+
assertSafeId("targetRunId", result.targetRunId);
|
|
596
|
+
const record = {
|
|
597
|
+
type: "subagent.nested.control-result",
|
|
598
|
+
...result,
|
|
599
|
+
rootRunId: route.rootRunId,
|
|
600
|
+
capabilityToken: route.capabilityToken,
|
|
601
|
+
};
|
|
602
|
+
const sanitized = parseControlResult(JSON.stringify(record), route);
|
|
603
|
+
if (!sanitized)
|
|
604
|
+
throw new Error("Nested control result failed validation.");
|
|
605
|
+
writeRouteRecord(route.eventSink, sanitized.ts, sanitized);
|
|
606
|
+
}
|
|
607
|
+
export function readNestedControlResults(route) {
|
|
608
|
+
validateRouteShape(route);
|
|
609
|
+
return readRouteFiles(route.eventSink, (entry) => entry.endsWith(".json") || entry.endsWith(".jsonl")).flatMap(({ content }) => (content.includes("\n") ? content.split("\n").filter((line) => line.trim()) : [content])
|
|
610
|
+
.map((line) => parseControlResult(line, route))
|
|
611
|
+
.filter((result) => Boolean(result)));
|
|
612
|
+
}
|
|
613
|
+
export function attachRootChildrenToSteps(rootRunId, steps, children) {
|
|
614
|
+
if (!steps?.length)
|
|
615
|
+
return;
|
|
616
|
+
for (const step of steps) {
|
|
617
|
+
step.children = undefined;
|
|
618
|
+
}
|
|
619
|
+
if (!children?.length)
|
|
620
|
+
return;
|
|
621
|
+
for (const child of children) {
|
|
622
|
+
if (child.parentRunId !== rootRunId || child.parentStepIndex === undefined)
|
|
623
|
+
continue;
|
|
624
|
+
const step = steps.find((candidate, index) => (candidate.index ?? index) === child.parentStepIndex);
|
|
625
|
+
if (!step)
|
|
626
|
+
continue;
|
|
627
|
+
step.children ??= [];
|
|
628
|
+
step.children = [...step.children.filter((existing) => existing.id !== child.id), child].slice(0, MAX_CHILDREN);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
export function updateAsyncJobNestedProjection(job) {
|
|
632
|
+
if (!job.nestedRoute)
|
|
633
|
+
return;
|
|
634
|
+
const registry = projectNestedEvents(job.nestedRoute);
|
|
635
|
+
job.nestedChildren = registry.children;
|
|
636
|
+
attachRootChildrenToSteps(job.asyncId, job.steps, registry.children);
|
|
637
|
+
}
|
|
638
|
+
export function hasLiveNestedDescendants(children) {
|
|
639
|
+
if (!children?.length)
|
|
640
|
+
return false;
|
|
641
|
+
for (const child of children) {
|
|
642
|
+
if (!terminal(child.state))
|
|
643
|
+
return true;
|
|
644
|
+
if (hasLiveNestedDescendants(child.children))
|
|
645
|
+
return true;
|
|
646
|
+
if (hasLiveNestedDescendants(child.steps?.flatMap((step) => step.children ?? [])))
|
|
647
|
+
return true;
|
|
648
|
+
}
|
|
649
|
+
return false;
|
|
650
|
+
}
|
|
651
|
+
export function nestedSummaryFromAsyncStatus(status, asyncDir, fallback) {
|
|
652
|
+
return {
|
|
653
|
+
id: status.runId || fallback.id,
|
|
654
|
+
indexedControl: status.indexedControl,
|
|
655
|
+
parentRunId: fallback.parentRunId,
|
|
656
|
+
...(fallback.parentStepIndex !== undefined ? { parentStepIndex: fallback.parentStepIndex } : {}),
|
|
657
|
+
depth: fallback.depth,
|
|
658
|
+
path: fallback.path ?? [{ runId: fallback.parentRunId, ...(fallback.parentStepIndex !== undefined ? { stepIndex: fallback.parentStepIndex } : {}) }],
|
|
659
|
+
asyncDir,
|
|
660
|
+
...(status.pid ? { pid: status.pid } : {}),
|
|
661
|
+
...(status.sessionId ? { sessionId: status.sessionId } : {}),
|
|
662
|
+
mode: status.mode ?? fallback.mode,
|
|
663
|
+
state: status.state,
|
|
664
|
+
error: status.error ?? (status.state === "blocked" ? status.steps?.map((step) => acceptanceHumanAction(step.acceptance)).filter(Boolean).join("\n") : undefined),
|
|
665
|
+
...(status.currentStep !== undefined ? { currentStep: status.currentStep } : {}),
|
|
666
|
+
...(status.chainStepCount !== undefined ? { chainStepCount: status.chainStepCount } : {}),
|
|
667
|
+
...(status.activityState ? { activityState: status.activityState } : {}),
|
|
668
|
+
...(status.lastActivityAt !== undefined ? { lastActivityAt: status.lastActivityAt } : {}),
|
|
669
|
+
...(status.currentTool ? { currentTool: status.currentTool } : {}),
|
|
670
|
+
...(status.currentToolStartedAt !== undefined ? { currentToolStartedAt: status.currentToolStartedAt } : {}),
|
|
671
|
+
...(status.currentPath ? { currentPath: status.currentPath } : {}),
|
|
672
|
+
...(status.turnCount !== undefined ? { turnCount: status.turnCount } : {}),
|
|
673
|
+
...(status.toolCount !== undefined ? { toolCount: status.toolCount } : {}),
|
|
674
|
+
...(status.totalTokens ? { totalTokens: status.totalTokens } : {}),
|
|
675
|
+
...(status.startedAt !== undefined ? { startedAt: status.startedAt } : { startedAt: fallback.ts }),
|
|
676
|
+
...(status.endedAt !== undefined ? { endedAt: status.endedAt } : {}),
|
|
677
|
+
lastUpdate: status.lastUpdate ?? fallback.ts,
|
|
678
|
+
...(status.sessionFile ? { sessionFile: status.sessionFile } : {}),
|
|
679
|
+
...(status.steps?.length ? { steps: status.steps.map((step) => ({
|
|
680
|
+
agent: step.agent,
|
|
681
|
+
status: step.status,
|
|
682
|
+
...(step.sessionFile ? { sessionFile: step.sessionFile } : {}),
|
|
683
|
+
...(step.activityState ? { activityState: step.activityState } : {}),
|
|
684
|
+
...(step.lastActivityAt !== undefined ? { lastActivityAt: step.lastActivityAt } : {}),
|
|
685
|
+
...(step.currentTool ? { currentTool: step.currentTool } : {}),
|
|
686
|
+
...(step.currentToolStartedAt !== undefined ? { currentToolStartedAt: step.currentToolStartedAt } : {}),
|
|
687
|
+
...(step.currentPath ? { currentPath: step.currentPath } : {}),
|
|
688
|
+
...(step.turnCount !== undefined ? { turnCount: step.turnCount } : {}),
|
|
689
|
+
...(step.toolCount !== undefined ? { toolCount: step.toolCount } : {}),
|
|
690
|
+
...(step.startedAt !== undefined ? { startedAt: step.startedAt } : {}),
|
|
691
|
+
...(step.endedAt !== undefined ? { endedAt: step.endedAt } : {}),
|
|
692
|
+
error: step.error ?? acceptanceHumanAction(step.acceptance),
|
|
693
|
+
})).slice(0, MAX_STEPS) } : {}),
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
export function nestedResultsPath(rootRunId, id) {
|
|
697
|
+
assertSafeId("rootRunId", rootRunId);
|
|
698
|
+
assertSafeId("id", id);
|
|
699
|
+
return path.join(RESULTS_DIR, "nested", rootRunId, `${id}.json`);
|
|
700
|
+
}
|