@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,22 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { getAgentDir } from "../shared/utils.js";
|
|
4
|
+
export function loadConfig() {
|
|
5
|
+
const configPath = path.join(getAgentDir(), "extensions", "subagent", "config.json");
|
|
6
|
+
try {
|
|
7
|
+
if (fs.existsSync(configPath)) {
|
|
8
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
9
|
+
const trust = config.projectTrust;
|
|
10
|
+
const childRuns = typeof trust === "object" && trust !== null ? trust.childRuns : trust;
|
|
11
|
+
if (childRuns !== undefined && childRuns !== "inherit" && childRuns !== "approve" && childRuns !== "no-approve") {
|
|
12
|
+
console.error(`Invalid projectTrust in '${configPath}'; child runs will use no-approve.`);
|
|
13
|
+
config.projectTrust = "no-approve";
|
|
14
|
+
}
|
|
15
|
+
return config;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
console.error(`Failed to load subagent config from '${configPath}':`, error);
|
|
20
|
+
}
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { controlNotificationKey, formatControlNoticeMessage, isObsoleteIdleNotice } from "../runs/shared/subagent-control.js";
|
|
2
|
+
export const SUBAGENT_CONTROL_MESSAGE_TYPE = "subagent_control_notice";
|
|
3
|
+
export function controlNoticeTarget(details) {
|
|
4
|
+
return details.childIntercomTarget;
|
|
5
|
+
}
|
|
6
|
+
export function formatSubagentControlNotice(details, content) {
|
|
7
|
+
return details.noticeText ?? content ?? formatControlNoticeMessage(details.event, controlNoticeTarget(details));
|
|
8
|
+
}
|
|
9
|
+
export function handleSubagentControlNotice(input) {
|
|
10
|
+
if (!input.details?.event || isObsoleteIdleNotice(input.details))
|
|
11
|
+
return;
|
|
12
|
+
const childIntercomTarget = controlNoticeTarget(input.details);
|
|
13
|
+
const key = controlNotificationKey(input.details.event, childIntercomTarget);
|
|
14
|
+
if (input.visibleControlNotices.has(key))
|
|
15
|
+
return;
|
|
16
|
+
input.visibleControlNotices.add(key);
|
|
17
|
+
const noticeText = input.details.noticeText ?? formatControlNoticeMessage(input.details.event, childIntercomTarget);
|
|
18
|
+
input.pi.sendMessage({
|
|
19
|
+
customType: SUBAGENT_CONTROL_MESSAGE_TYPE,
|
|
20
|
+
content: noticeText,
|
|
21
|
+
display: true,
|
|
22
|
+
details: { ...input.details, childIntercomTarget, noticeText },
|
|
23
|
+
},
|
|
24
|
+
// Async completion-guard notices stay visible but let the matching completion
|
|
25
|
+
// result deliver the single automatic wakeup. In a parallel group that result
|
|
26
|
+
// arrives only after all siblings finish, so reaction to a mid-group guard
|
|
27
|
+
// notice is bounded by the longest-running sibling.
|
|
28
|
+
{ triggerTurn: !(input.details.source === "async" && input.details.event.reason === "completion_guard") });
|
|
29
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { VERSION as PI_VERSION, getPackageDir } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { EXTENSION_BUILD } from "./build-info.js";
|
|
6
|
+
import { discoverAgentsAll } from "../agents/agents.js";
|
|
7
|
+
import { discoverAvailableSkills } from "../agents/skills.js";
|
|
8
|
+
import { ASYNC_DIR, CHAIN_RUNS_DIR, RESULTS_DIR, TEMP_ROOT_DIR, } from "../shared/types.js";
|
|
9
|
+
const PI_PACKAGE_DIR = getPackageDir();
|
|
10
|
+
const EXTENSION_MODULE = fileURLToPath(import.meta.url);
|
|
11
|
+
function errorText(error) {
|
|
12
|
+
return error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
13
|
+
}
|
|
14
|
+
function lineFromCheck(label, check) {
|
|
15
|
+
try {
|
|
16
|
+
return check();
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
return `- ${label}: failed — ${errorText(error)}`;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function formatExistingDirectory(label, dirPath) {
|
|
23
|
+
try {
|
|
24
|
+
if (!fs.existsSync(dirPath))
|
|
25
|
+
return `- ${label}: missing (${dirPath})`;
|
|
26
|
+
const stats = fs.statSync(dirPath);
|
|
27
|
+
if (!stats.isDirectory())
|
|
28
|
+
throw new Error(`not a directory: ${dirPath}`);
|
|
29
|
+
fs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);
|
|
30
|
+
return `- ${label}: ok (${dirPath})`;
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
return `- ${label}: failed (${dirPath}) — ${errorText(error)}`;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function formatSourceCounts(counts) {
|
|
37
|
+
return `builtin ${counts.builtin}, package ${counts.package}, user ${counts.user}, project ${counts.project}`;
|
|
38
|
+
}
|
|
39
|
+
function formatSkillSourceCounts(skills) {
|
|
40
|
+
const counts = new Map();
|
|
41
|
+
for (const skill of skills)
|
|
42
|
+
counts.set(skill.source, (counts.get(skill.source) ?? 0) + 1);
|
|
43
|
+
const ordered = [
|
|
44
|
+
"project",
|
|
45
|
+
"project-settings",
|
|
46
|
+
"project-package",
|
|
47
|
+
"user",
|
|
48
|
+
"user-settings",
|
|
49
|
+
"user-package",
|
|
50
|
+
"extension",
|
|
51
|
+
"builtin",
|
|
52
|
+
"unknown",
|
|
53
|
+
];
|
|
54
|
+
const parts = ordered
|
|
55
|
+
.map((source) => `${source} ${counts.get(source) ?? 0}`)
|
|
56
|
+
.filter((part) => !part.endsWith(" 0"));
|
|
57
|
+
return parts.length > 0 ? parts.join(", ") : "none";
|
|
58
|
+
}
|
|
59
|
+
function formatConfiguredSessionDir(input) {
|
|
60
|
+
if (input.requestedSessionDir) {
|
|
61
|
+
return path.resolve(input.expandTilde?.(input.requestedSessionDir) ?? input.requestedSessionDir);
|
|
62
|
+
}
|
|
63
|
+
if (input.config.defaultSessionDir) {
|
|
64
|
+
return path.resolve(input.expandTilde?.(input.config.defaultSessionDir) ?? input.config.defaultSessionDir);
|
|
65
|
+
}
|
|
66
|
+
return "not configured";
|
|
67
|
+
}
|
|
68
|
+
function formatSessionLines(input) {
|
|
69
|
+
const sessionFile = input.currentSessionFile ?? null;
|
|
70
|
+
const lines = [
|
|
71
|
+
lineFromCheck("configured session dir", () => `- configured session dir: ${formatConfiguredSessionDir(input)}`),
|
|
72
|
+
`- current session file: ${sessionFile ?? "not available"}`,
|
|
73
|
+
`- current session dir: ${sessionFile ? path.dirname(sessionFile) : "not available"}`,
|
|
74
|
+
`- current session id: ${input.currentSessionId ?? input.state.currentSessionId ?? "not available"}`,
|
|
75
|
+
];
|
|
76
|
+
if (input.sessionError)
|
|
77
|
+
lines.push(`- session manager: failed — ${input.sessionError}`);
|
|
78
|
+
return lines;
|
|
79
|
+
}
|
|
80
|
+
function formatDiscovery(input) {
|
|
81
|
+
return [
|
|
82
|
+
lineFromCheck("agents/chains", () => {
|
|
83
|
+
const discovered = discoverAgentsAll(input.cwd, { projectTrusted: input.projectTrusted ?? true });
|
|
84
|
+
const agentCounts = {
|
|
85
|
+
builtin: discovered.builtin.length,
|
|
86
|
+
package: discovered.package.length,
|
|
87
|
+
user: discovered.user.length,
|
|
88
|
+
project: discovered.project.length,
|
|
89
|
+
};
|
|
90
|
+
const chainCounts = discovered.chains.reduce((counts, chain) => {
|
|
91
|
+
counts[chain.source] += 1;
|
|
92
|
+
return counts;
|
|
93
|
+
}, { builtin: 0, package: 0, user: 0, project: 0 });
|
|
94
|
+
return [
|
|
95
|
+
`- agents: total ${agentCounts.builtin + agentCounts.package + agentCounts.user + agentCounts.project} (${formatSourceCounts(agentCounts)})`,
|
|
96
|
+
`- chains: total ${discovered.chains.length} (${formatSourceCounts(chainCounts)})`,
|
|
97
|
+
].join("\n");
|
|
98
|
+
}),
|
|
99
|
+
lineFromCheck("skills", () => {
|
|
100
|
+
const skills = discoverAvailableSkills(input.cwd, { projectTrusted: input.projectTrusted ?? true });
|
|
101
|
+
return `- skills: total ${skills.length} (${formatSkillSourceCounts(skills)})`;
|
|
102
|
+
}),
|
|
103
|
+
];
|
|
104
|
+
}
|
|
105
|
+
function formatIntercomSection(input) {
|
|
106
|
+
return [
|
|
107
|
+
`- bridge: ${input.connection ? "responding" : "unavailable (no live health response)"}`,
|
|
108
|
+
`- connection: ${input.connection?.status ?? "unknown"}${input.connection?.reason ? ` — ${input.connection.reason}` : ""}`,
|
|
109
|
+
`- broker session id: ${input.connection?.sessionId ?? "not available"}`,
|
|
110
|
+
`- orchestrator target: ${input.orchestratorTarget?.trim() || "not available"}`,
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
export function buildDoctorReport(input) {
|
|
114
|
+
const lines = [
|
|
115
|
+
"Subagents doctor report",
|
|
116
|
+
"",
|
|
117
|
+
"Runtime",
|
|
118
|
+
`- Native session cwd: ${input.nativeSessionCwd ?? input.cwd}`,
|
|
119
|
+
...(input.nativeSessionCwd && input.nativeSessionCwd !== input.cwd ? [`- Requested cwd: ${input.cwd}`] : []),
|
|
120
|
+
`- Node: ${process.version}`,
|
|
121
|
+
`- process: ${process.pid} (${process.execPath})`,
|
|
122
|
+
`- loaded Pi version: ${PI_VERSION}`,
|
|
123
|
+
`- Pi package directory: ${PI_PACKAGE_DIR} (Pi resource path; may be overridden)`,
|
|
124
|
+
"- native queue contract: not verified (version alone does not identify fork patches)",
|
|
125
|
+
`- loaded pi-subagents build: ${EXTENSION_BUILD.version && EXTENSION_BUILD.sha256 ? `${EXTENSION_BUILD.version} (runtime SHA-256 ${EXTENSION_BUILD.sha256})` : "unknown (unbuilt source)"}`,
|
|
126
|
+
`- extension module: ${EXTENSION_MODULE}`,
|
|
127
|
+
"- async support: available (Node >=24)",
|
|
128
|
+
...formatSessionLines(input),
|
|
129
|
+
"",
|
|
130
|
+
"Filesystem",
|
|
131
|
+
formatExistingDirectory("temp root", TEMP_ROOT_DIR),
|
|
132
|
+
formatExistingDirectory("async runs", ASYNC_DIR),
|
|
133
|
+
formatExistingDirectory("results", RESULTS_DIR),
|
|
134
|
+
formatExistingDirectory("chain runs", CHAIN_RUNS_DIR),
|
|
135
|
+
"",
|
|
136
|
+
"Discovery",
|
|
137
|
+
...formatDiscovery(input),
|
|
138
|
+
"",
|
|
139
|
+
"Intercom",
|
|
140
|
+
...formatIntercomSection(input),
|
|
141
|
+
];
|
|
142
|
+
return lines.join("\n");
|
|
143
|
+
}
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { discoverAgents } from "../agents/agents.js";
|
|
5
|
+
import { getArtifactsDir } from "../shared/artifacts.js";
|
|
6
|
+
import { createSubagentExecutor, normalizeSubagentParamsLike } from "../runs/foreground/subagent-executor.js";
|
|
7
|
+
import { interruptAsyncRun } from "../runs/foreground/foreground-control.js";
|
|
8
|
+
import { SUBAGENT_CHILD_ENV, SUBAGENT_FANOUT_CHILD_ENV, SUBAGENT_EAGER_TOOL_ENV, SUBAGENT_PARENT_CHILD_INDEX_ENV } from "../runs/shared/pi-args.js";
|
|
9
|
+
import { readNestedControlRequests, resolveNestedRouteFromEnv, writeNestedControlResult } from "../runs/shared/nested-events.js";
|
|
10
|
+
import { deliverSubagentIntercomMessageEvent } from "../intercom/result-intercom.js";
|
|
11
|
+
import { resolveSubagentIntercomTarget } from "../intercom/intercom-bridge.js";
|
|
12
|
+
import { readStatus } from "../shared/utils.js";
|
|
13
|
+
import { SubagentParams } from "./schemas.js";
|
|
14
|
+
import { loadConfig } from "./config.js";
|
|
15
|
+
import { registerCompactSubagentTools } from "./compact-tools.js";
|
|
16
|
+
import { registerToolResultAdapter } from "./tool-result.js";
|
|
17
|
+
import { renderSubagentResult } from "../tui/render.js";
|
|
18
|
+
import {} from "../shared/types.js";
|
|
19
|
+
import { finalizedChildUsage, registerParentUsage } from "../runs/shared/parent-usage.js";
|
|
20
|
+
import { resolveCurrentSessionId } from "../shared/session-identity.js";
|
|
21
|
+
import { OWNED_RUN_ENTRY, ownedRunList, restoreOwnedRunsAsync } from "../runs/shared/run-records.js";
|
|
22
|
+
import { closeRunHistory, startRunHistory } from "../runs/shared/history-index.js";
|
|
23
|
+
import { createCompletionDelivery } from "../runs/background/completion-delivery.js";
|
|
24
|
+
function getSubagentSessionRoot(parentSessionFile) {
|
|
25
|
+
if (parentSessionFile) {
|
|
26
|
+
const baseName = path.basename(parentSessionFile, ".jsonl");
|
|
27
|
+
const sessionsDir = path.dirname(parentSessionFile);
|
|
28
|
+
return path.join(sessionsDir, baseName);
|
|
29
|
+
}
|
|
30
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-session-"));
|
|
31
|
+
}
|
|
32
|
+
function expandTilde(p) {
|
|
33
|
+
return p.startsWith("~/") ? path.join(os.homedir(), p.slice(2)) : p;
|
|
34
|
+
}
|
|
35
|
+
function createChildSafeState() {
|
|
36
|
+
return {
|
|
37
|
+
baseCwd: "",
|
|
38
|
+
currentSessionId: null,
|
|
39
|
+
asyncJobs: new Map(),
|
|
40
|
+
foregroundRuns: new Map(),
|
|
41
|
+
cleanupTimers: new Map(),
|
|
42
|
+
lastUiContext: null,
|
|
43
|
+
poller: null,
|
|
44
|
+
completionSeen: new Map(),
|
|
45
|
+
watcher: null,
|
|
46
|
+
watcherRestartTimer: null,
|
|
47
|
+
resultFileCoalescer: {
|
|
48
|
+
schedule: () => false,
|
|
49
|
+
clear: () => { },
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function addBounded(set, value, max = 1000) {
|
|
54
|
+
set.add(value);
|
|
55
|
+
while (set.size > max)
|
|
56
|
+
set.delete(set.values().next().value);
|
|
57
|
+
}
|
|
58
|
+
function startNestedControlInboxListener(pi, state, isRestoring) {
|
|
59
|
+
let route;
|
|
60
|
+
try {
|
|
61
|
+
route = resolveNestedRouteFromEnv();
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
if (!route)
|
|
67
|
+
return undefined;
|
|
68
|
+
const seen = new Set();
|
|
69
|
+
const seenFiles = new Set();
|
|
70
|
+
const inFlight = new Set();
|
|
71
|
+
const pendingResults = new Map();
|
|
72
|
+
const parsedChildIndex = Number(process.env[SUBAGENT_PARENT_CHILD_INDEX_ENV]);
|
|
73
|
+
const localChildIndex = Number.isInteger(parsedChildIndex) && parsedChildIndex >= 0 ? parsedChildIndex : undefined;
|
|
74
|
+
const timer = setInterval(() => {
|
|
75
|
+
if (isRestoring())
|
|
76
|
+
return;
|
|
77
|
+
try {
|
|
78
|
+
for (const request of readNestedControlRequests(route, seenFiles)) {
|
|
79
|
+
if (seen.has(request.requestId) || inFlight.has(request.requestId))
|
|
80
|
+
continue;
|
|
81
|
+
if (request.targetChildIndex !== undefined && request.targetChildIndex !== localChildIndex)
|
|
82
|
+
continue;
|
|
83
|
+
const owned = state.ownedRuns?.get(request.targetRunId);
|
|
84
|
+
const asyncJob = state.asyncJobs.get(request.targetRunId);
|
|
85
|
+
if (!owned && !asyncJob && (request.targetChildIndex !== undefined || Date.now() - request.ts < 400))
|
|
86
|
+
continue;
|
|
87
|
+
inFlight.add(request.requestId);
|
|
88
|
+
void (async () => {
|
|
89
|
+
const claimPath = `${request.filePath}.claimed`;
|
|
90
|
+
try {
|
|
91
|
+
let result = pendingResults.get(request.requestId);
|
|
92
|
+
if (!result && pendingResults.size >= 100) {
|
|
93
|
+
result = {
|
|
94
|
+
ts: Date.now(),
|
|
95
|
+
requestId: request.requestId,
|
|
96
|
+
targetRunId: request.targetRunId,
|
|
97
|
+
ok: false,
|
|
98
|
+
message: "Nested control result queue is full; retry after pending results are delivered.",
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (!result) {
|
|
102
|
+
try {
|
|
103
|
+
const claimFd = fs.openSync(claimPath, "wx", 0o600);
|
|
104
|
+
try {
|
|
105
|
+
fs.writeFileSync(claimFd, `${request.requestId}\n`, "utf-8");
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
fs.closeSync(claimFd);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
const code = error && typeof error === "object" && "code" in error ? error.code : undefined;
|
|
113
|
+
result = {
|
|
114
|
+
ts: Date.now(),
|
|
115
|
+
requestId: request.requestId,
|
|
116
|
+
targetRunId: request.targetRunId,
|
|
117
|
+
ok: false,
|
|
118
|
+
message: code === "EEXIST"
|
|
119
|
+
? "Nested control request was already claimed; refusing to execute it again because the original outcome is unavailable."
|
|
120
|
+
: `Failed to claim nested control request safely: ${error instanceof Error ? error.message : String(error)}`,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (!result) {
|
|
125
|
+
let ok = false;
|
|
126
|
+
let message = "Control request failed.";
|
|
127
|
+
try {
|
|
128
|
+
const asyncDir = state.ownedRuns?.get(request.targetRunId)?.asyncDir ?? state.asyncJobs.get(request.targetRunId)?.asyncDir;
|
|
129
|
+
const status = asyncDir ? readStatus(asyncDir) : null;
|
|
130
|
+
if (!status || status.state !== "running") {
|
|
131
|
+
message = `Nested run ${request.targetRunId} is not active in this fanout child.`;
|
|
132
|
+
}
|
|
133
|
+
else if (request.action === "interrupt") {
|
|
134
|
+
const receipt = interruptAsyncRun(state, request.targetRunId, request.index);
|
|
135
|
+
ok = Boolean(receipt && !receipt.isError);
|
|
136
|
+
message = receipt?.content.map((part) => part.type === "text" ? part.text : "").join("\n") ?? "Nested run is not interruptible.";
|
|
137
|
+
}
|
|
138
|
+
else if (!request.message?.trim()) {
|
|
139
|
+
message = "Nested resume requires message.";
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
const index = request.index ?? status.steps?.findIndex((step) => step.status === "running") ?? -1;
|
|
143
|
+
const step = status.steps?.[index];
|
|
144
|
+
const agent = step?.status === "running" ? step.agent : undefined;
|
|
145
|
+
if (!agent) {
|
|
146
|
+
message = `Nested run ${request.targetRunId} has no active child message route.`;
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
const target = resolveSubagentIntercomTarget(request.targetRunId, agent, index);
|
|
150
|
+
ok = await deliverSubagentIntercomMessageEvent(pi.events, target, `Follow-up for nested run ${request.targetRunId} (${agent}):\n\n${request.message.trim()}`, 500, { source: "nested-resume", runId: request.targetRunId, agent, index });
|
|
151
|
+
message = ok
|
|
152
|
+
? `Delivered follow-up to live nested run ${request.targetRunId}.`
|
|
153
|
+
: `Nested child intercom target is not registered: ${target}`;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
message = error instanceof Error ? error.message : String(error);
|
|
159
|
+
}
|
|
160
|
+
result = { ts: Date.now(), requestId: request.requestId, targetRunId: request.targetRunId, ok, message };
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
writeNestedControlResult(route, result);
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
if (pendingResults.size < 100 || pendingResults.has(request.requestId))
|
|
167
|
+
pendingResults.set(request.requestId, result);
|
|
168
|
+
console.error(`Failed to write nested control result for request '${request.requestId}' targeting '${request.targetRunId}' via inbox '${route.controlInbox}'; keeping request for retry:`, error);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
pendingResults.delete(request.requestId);
|
|
172
|
+
addBounded(seen, request.requestId);
|
|
173
|
+
addBounded(seenFiles, path.basename(request.filePath));
|
|
174
|
+
let requestRemoved = false;
|
|
175
|
+
try {
|
|
176
|
+
fs.unlinkSync(request.filePath);
|
|
177
|
+
requestRemoved = true;
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
const code = error && typeof error === "object" && "code" in error ? error.code : undefined;
|
|
181
|
+
requestRemoved = code === "ENOENT";
|
|
182
|
+
}
|
|
183
|
+
if (requestRemoved) {
|
|
184
|
+
try {
|
|
185
|
+
fs.unlinkSync(claimPath);
|
|
186
|
+
}
|
|
187
|
+
catch { }
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
finally {
|
|
191
|
+
inFlight.delete(request.requestId);
|
|
192
|
+
}
|
|
193
|
+
})();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
console.error(`Failed to poll nested control inbox '${route.controlInbox}' for root '${route.rootRunId}':`, error);
|
|
198
|
+
}
|
|
199
|
+
}, 200);
|
|
200
|
+
timer.unref?.();
|
|
201
|
+
return timer;
|
|
202
|
+
}
|
|
203
|
+
export default function registerFanoutChildSubagentExtension(pi) {
|
|
204
|
+
if (process.env[SUBAGENT_CHILD_ENV] !== "1" || process.env[SUBAGENT_FANOUT_CHILD_ENV] !== "1")
|
|
205
|
+
return;
|
|
206
|
+
const globalStore = globalThis;
|
|
207
|
+
const registeredKey = "__piSubagentFanoutChildRegisteredApis";
|
|
208
|
+
const registeredApis = globalStore[registeredKey] instanceof WeakSet
|
|
209
|
+
? globalStore[registeredKey]
|
|
210
|
+
: new WeakSet();
|
|
211
|
+
globalStore[registeredKey] = registeredApis;
|
|
212
|
+
if (registeredApis.has(pi))
|
|
213
|
+
return;
|
|
214
|
+
registeredApis.add(pi);
|
|
215
|
+
const controlInboxCleanupStoreKey = "__piSubagentFanoutChildControlInboxCleanup";
|
|
216
|
+
const previousControlInboxCleanup = globalStore[controlInboxCleanupStoreKey];
|
|
217
|
+
if (typeof previousControlInboxCleanup === "function") {
|
|
218
|
+
try {
|
|
219
|
+
previousControlInboxCleanup();
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
// Best effort cleanup for stale timers from an older reload.
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const config = loadConfig();
|
|
226
|
+
const state = createChildSafeState();
|
|
227
|
+
state.persistOwnedRun = (run) => pi.appendEntry(OWNED_RUN_ENTRY, run);
|
|
228
|
+
let sessionReset;
|
|
229
|
+
const ensureSessionState = (ctx) => {
|
|
230
|
+
const sessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
231
|
+
if (sessionReset)
|
|
232
|
+
return sessionReset;
|
|
233
|
+
if (state.currentSessionId === sessionId) {
|
|
234
|
+
state.lastUiContext = ctx;
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
sessionReset = (async () => {
|
|
238
|
+
await completionDelivery.stopAndJoin();
|
|
239
|
+
state.lastUiContext = ctx;
|
|
240
|
+
void closeRunHistory(state).catch((error) => console.error("Could not close child history:", error));
|
|
241
|
+
state.foregroundRuns?.clear();
|
|
242
|
+
await restoreOwnedRunsAsync(state, ctx);
|
|
243
|
+
state.currentSessionId = sessionId;
|
|
244
|
+
startRunHistory(state, ctx);
|
|
245
|
+
completionDelivery.start();
|
|
246
|
+
})().finally(() => { sessionReset = undefined; });
|
|
247
|
+
return sessionReset;
|
|
248
|
+
};
|
|
249
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
250
|
+
await ensureSessionState(ctx);
|
|
251
|
+
});
|
|
252
|
+
const executor = createSubagentExecutor({
|
|
253
|
+
pi,
|
|
254
|
+
state,
|
|
255
|
+
config,
|
|
256
|
+
// Keep the stock nested default foreground so evidence returns before this caller exits.
|
|
257
|
+
asyncByDefault: config.asyncByDefault === true,
|
|
258
|
+
tempArtifactsDir: getArtifactsDir(null),
|
|
259
|
+
getSubagentSessionRoot,
|
|
260
|
+
expandTilde,
|
|
261
|
+
discoverAgents,
|
|
262
|
+
allowMutatingManagementActions: false,
|
|
263
|
+
ensureSessionState,
|
|
264
|
+
});
|
|
265
|
+
const compact = config.compactChildTools !== false;
|
|
266
|
+
const toolNames = compact ? ["subagent", "delegate", "agent_runs"] : ["subagent"];
|
|
267
|
+
const parentUsage = registerParentUsage(pi, toolNames);
|
|
268
|
+
const completionDelivery = createCompletionDelivery(pi, state, parentUsage);
|
|
269
|
+
const adaptToolResult = registerToolResultAdapter(pi, toolNames);
|
|
270
|
+
const toRegisteredToolResult = (result, ctx) => adaptToolResult(result.details.wait?.status === "completed" && result.details.run?.ownerSessionId === ctx.sessionManager.getSessionId()
|
|
271
|
+
? parentUsage.attach(result, finalizedChildUsage(result.details.run.children, result.details.wait.index), ctx)
|
|
272
|
+
: result);
|
|
273
|
+
const acceptanceGuidelines = [
|
|
274
|
+
"For goal-style requests such as /goal, goal, active goal, or work until evidence says done, use explicit acceptance on the delegated run: criteria for the target, evidence/verify for proof, stopRules for constraints, and maxFinalizationTurns for the bounded loop.",
|
|
275
|
+
"For implementation handoffs from a plan, PRD, spec, issue, or broad fix, put implementation instructions and plan paths in task, and put the definition of done, evidence, verification commands, constraints, and loop cap in acceptance.",
|
|
276
|
+
];
|
|
277
|
+
const guidelines = [
|
|
278
|
+
"Delegate useful helper work within your assigned task when it saves time or improves quality; the original parent owns integration and final delivery.",
|
|
279
|
+
"Nested execution defaults to foreground unless configuration explicitly opts into async. Set async:false whenever the nested result must appear in this child's report; use async:true only for intentionally detached work.",
|
|
280
|
+
compact
|
|
281
|
+
? "Use load_subagent({advanced:false}), then agent_runs({action:'profiles'}) before delegation unless the executable agent is already known. Use load_subagent for advanced workflows."
|
|
282
|
+
: "Use subagent action:list before nested execution unless the executable nested agent is already known from the task context.",
|
|
283
|
+
"Do not use subagent child-safe mode for agent config mutation actions; create, update, and delete are blocked here.",
|
|
284
|
+
];
|
|
285
|
+
if (compact)
|
|
286
|
+
registerCompactSubagentTools(pi, { executor, state, adapt: toRegisteredToolResult, guidelines: [...guidelines, ...acceptanceGuidelines], childSafe: true,
|
|
287
|
+
asyncByDefault: config.asyncByDefault === true, keepAdvancedActive: process.env[SUBAGENT_EAGER_TOOL_ENV] === "1",
|
|
288
|
+
listRuns: async (params, ctx) => { await ensureSessionState(ctx); return ownedRunList(state, params); },
|
|
289
|
+
});
|
|
290
|
+
const tool = {
|
|
291
|
+
name: "subagent",
|
|
292
|
+
defaultActive: !compact,
|
|
293
|
+
label: "Subagent",
|
|
294
|
+
description: [
|
|
295
|
+
"Delegate to subagents from child-safe fanout mode.",
|
|
296
|
+
...acceptanceGuidelines,
|
|
297
|
+
"Allowed management/control actions: list, get, status, history, search, nudge, interrupt, extend, resume, questions, answer, review, doctor. History/search browse only directly owned saved work; bounded previews and index freshness are not canonical proof. Exact status is concise; full:true includes the full task/configuration. Review notes are parent-only, not sent to children. Put actionable instructions in resume/nudge. Resume/answer overrides do not amend live acceptance.",
|
|
298
|
+
"Agent config mutation actions create, update, and delete are blocked in this mode.",
|
|
299
|
+
].join("\n"),
|
|
300
|
+
promptSnippet: "Delegate nested child-safe subagent work from an explicitly allowed fanout child.",
|
|
301
|
+
promptGuidelines: guidelines,
|
|
302
|
+
parameters: SubagentParams,
|
|
303
|
+
async execute(id, params, signal, onUpdate, ctx) {
|
|
304
|
+
return toRegisteredToolResult(await executor.execute(id, normalizeSubagentParamsLike(params), signal, onUpdate, ctx), ctx);
|
|
305
|
+
},
|
|
306
|
+
renderResult: renderSubagentResult,
|
|
307
|
+
};
|
|
308
|
+
pi.registerTool(tool);
|
|
309
|
+
const controlInboxTimer = startNestedControlInboxListener(pi, state, () => sessionReset !== undefined);
|
|
310
|
+
const clearControlInboxTimer = () => {
|
|
311
|
+
if (controlInboxTimer)
|
|
312
|
+
clearInterval(controlInboxTimer);
|
|
313
|
+
completionDelivery.stop();
|
|
314
|
+
return closeRunHistory(state);
|
|
315
|
+
};
|
|
316
|
+
globalStore[controlInboxCleanupStoreKey] = clearControlInboxTimer;
|
|
317
|
+
pi.on("session_shutdown", async () => {
|
|
318
|
+
if (controlInboxTimer)
|
|
319
|
+
clearInterval(controlInboxTimer);
|
|
320
|
+
await completionDelivery.stopAndJoin({ preservePending: true });
|
|
321
|
+
await closeRunHistory(state);
|
|
322
|
+
if (globalStore[controlInboxCleanupStoreKey] === clearControlInboxTimer) {
|
|
323
|
+
delete globalStore[controlInboxCleanupStoreKey];
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
}
|