@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,201 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { Type } from "../../shared/native-typebox.ts";
|
|
5
|
+
import { SUBAGENT_FANOUT_CHILD_ENV } from "./pi-args.ts";
|
|
6
|
+
import { setPromptSection } from "../../shared/prompt-sections.ts";
|
|
7
|
+
import { loadConfig } from "../../extension/config.ts";
|
|
8
|
+
import { registerChildExecutionCwd } from "./child-execution-cwd.ts";
|
|
9
|
+
import { STRUCTURED_OUTPUT_CAPTURE_ENV, STRUCTURED_OUTPUT_SCHEMA_ENV, validateStructuredOutputValue } from "./structured-output.ts";
|
|
10
|
+
import type { JsonSchemaObject } from "../../shared/types.ts";
|
|
11
|
+
import { SessionEntryCursor } from "../../shared/session-entries.ts";
|
|
12
|
+
|
|
13
|
+
const SUBAGENT_INHERIT_PROJECT_CONTEXT_ENV = "PI_SUBAGENT_INHERIT_PROJECT_CONTEXT";
|
|
14
|
+
const SUBAGENT_INHERIT_SKILLS_ENV = "PI_SUBAGENT_INHERIT_SKILLS";
|
|
15
|
+
export const SUBAGENT_INTERCOM_SESSION_NAME_ENV = "PI_SUBAGENT_INTERCOM_SESSION_NAME";
|
|
16
|
+
|
|
17
|
+
const STRUCTURED_OUTPUT_INSTRUCTIONS = [
|
|
18
|
+
"This subagent step has a strict structured output contract.",
|
|
19
|
+
"Your final action must be to call the `structured_output` tool with JSON matching the provided schema.",
|
|
20
|
+
"Do not rely on prose-only completion; if you do not call `structured_output`, the parent will fail this step.",
|
|
21
|
+
].join("\n");
|
|
22
|
+
|
|
23
|
+
export const CHILD_SUBAGENT_BOUNDARY_INSTRUCTIONS = [
|
|
24
|
+
"You are a child subagent, not the parent orchestrator.",
|
|
25
|
+
"The parent session owns delegation, orchestration, review fanout, and follow-up worker launches.",
|
|
26
|
+
"Ignore prior parent-only orchestration instructions in inherited conversation history.",
|
|
27
|
+
"Do not propose or run subagents. Complete only your assigned role-specific task with the tools available to you.",
|
|
28
|
+
"If you need to edit files, call the actual edit/write tools. Do not print tool-call syntax, patches, or pseudo-tool calls as text.",
|
|
29
|
+
].join("\n");
|
|
30
|
+
|
|
31
|
+
export const CHILD_FANOUT_BOUNDARY_INSTRUCTIONS = [
|
|
32
|
+
"You are a child subagent with delegation enabled for your assigned task.",
|
|
33
|
+
"You may delegate useful helper work within that task when it saves time or improves quality, using the available delegation tools.",
|
|
34
|
+
"You remain responsible for your assigned result. The original parent owns integration, review synthesis, and final delivery.",
|
|
35
|
+
"Do not broaden the assigned scope or repeat approval requests for already-authorized work.",
|
|
36
|
+
"The native allowSubagents and maxSubagentDepth settings still apply.",
|
|
37
|
+
"If you need to edit files, call the actual edit/write tools. Do not print tool-call syntax, patches, or pseudo-tool calls as text.",
|
|
38
|
+
].join("\n");
|
|
39
|
+
|
|
40
|
+
const PARENT_ONLY_CUSTOM_MESSAGE_TYPES = new Set([
|
|
41
|
+
"subagent-orchestration-instructions",
|
|
42
|
+
"subagent-slash-result",
|
|
43
|
+
"subagent-notify",
|
|
44
|
+
"subagent_control_notice",
|
|
45
|
+
"subagent-control",
|
|
46
|
+
"subagent-control-notice",
|
|
47
|
+
]);
|
|
48
|
+
const ORCHESTRATION_TOOLS = new Set(["subagent", "delegate", "agent_runs", "load_subagent"]);
|
|
49
|
+
|
|
50
|
+
function readBooleanEnv(name: string): boolean | undefined {
|
|
51
|
+
const value = process.env[name];
|
|
52
|
+
if (value === undefined) return undefined;
|
|
53
|
+
return value !== "0";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isParentOnlySubagentMessage(message: unknown): boolean {
|
|
57
|
+
const m = message as { role?: string; customType?: string };
|
|
58
|
+
return m?.role === "custom"
|
|
59
|
+
&& typeof m.customType === "string"
|
|
60
|
+
&& PARENT_ONLY_CUSTOM_MESSAGE_TYPES.has(m.customType);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isSubagentExecutionResultMessage(message: unknown): boolean {
|
|
64
|
+
const m = message as { role?: string; toolName?: string };
|
|
65
|
+
return m?.role === "toolResult" && typeof m.toolName === "string" && ORCHESTRATION_TOOLS.has(m.toolName);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function isSubagentToolCallBlock(block: unknown): boolean {
|
|
69
|
+
const b = block as { type?: string; name?: string };
|
|
70
|
+
return b?.type === "toolCall" && typeof b.name === "string" && ORCHESTRATION_TOOLS.has(b.name);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function stripAssistantSubagentToolCallBlocks(message: unknown): unknown | undefined {
|
|
74
|
+
const m = message as { role?: string; content?: unknown };
|
|
75
|
+
if (m?.role !== "assistant" || !Array.isArray(m.content)) return message;
|
|
76
|
+
const filteredContent = m.content.filter((block) => !isSubagentToolCallBlock(block));
|
|
77
|
+
if (filteredContent.length === m.content.length) return message;
|
|
78
|
+
if (filteredContent.length === 0) return undefined;
|
|
79
|
+
return { ...m, content: filteredContent };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function stripParentOnlySubagentMessages<T>(messages: T[], fanoutChild = false): T[] {
|
|
83
|
+
let changed = false;
|
|
84
|
+
const filtered: T[] = [];
|
|
85
|
+
for (const message of messages) {
|
|
86
|
+
if (isParentOnlySubagentMessage(message) || (!fanoutChild && isSubagentExecutionResultMessage(message))) {
|
|
87
|
+
changed = true;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const stripped = fanoutChild ? message : stripAssistantSubagentToolCallBlocks(message);
|
|
91
|
+
if (stripped === undefined) {
|
|
92
|
+
changed = true;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (stripped !== message) changed = true;
|
|
96
|
+
filtered.push(stripped as T);
|
|
97
|
+
}
|
|
98
|
+
return changed ? filtered : messages;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export default function registerSubagentPromptRuntime(pi: ExtensionAPI): void {
|
|
102
|
+
registerChildExecutionCwd(pi);
|
|
103
|
+
// Print-mode observation uses the existing child runtime. turn_end follows native
|
|
104
|
+
// message append; message_end itself is deliberately never called a commit receipt.
|
|
105
|
+
let observedMessages = 0;
|
|
106
|
+
const cursor = new SessionEntryCursor();
|
|
107
|
+
let previousIds = new Set<string>();
|
|
108
|
+
pi.on("session_start", (_event, ctx) => {
|
|
109
|
+
cursor.reset();
|
|
110
|
+
observedMessages = 0;
|
|
111
|
+
const { entries } = cursor.read(ctx.sessionManager);
|
|
112
|
+
const baseline = process.env.PI_SUBAGENT_NATIVE_BASELINE_COUNT;
|
|
113
|
+
if (baseline && /^\d+$/.test(baseline)) process.stdout.write(`${JSON.stringify({ type: "subagent.native_baseline", sessionId: ctx.sessionManager.getSessionId(), entryIds: entries.slice(0, Number(baseline)).map((entry) => entry.id) })}\n`);
|
|
114
|
+
const file = ctx.sessionManager.getSessionFile();
|
|
115
|
+
previousIds = file && fs.existsSync(file) ? new Set(entries.map((entry) => entry.id)) : new Set();
|
|
116
|
+
if (!file || !fs.existsSync(file)) cursor.reset();
|
|
117
|
+
});
|
|
118
|
+
pi.on("message_end", (event) => { if (["assistant", "user", "toolResult"].includes(event.message.role)) observedMessages++; });
|
|
119
|
+
const observe = (ctx: import("@earendil-works/pi-coding-agent").ExtensionContext, boundary: string) => {
|
|
120
|
+
if (process.env.PI_SUBAGENT_CHILD !== "1") return;
|
|
121
|
+
const entries = [];
|
|
122
|
+
for (const entry of cursor.read(ctx.sessionManager).entries) if (!previousIds.has(entry.id)) {
|
|
123
|
+
previousIds.add(entry.id);
|
|
124
|
+
const message = entry.type === "message" ? entry.message : undefined;
|
|
125
|
+
entries.push({ id: entry.id, type: entry.type, parentId: entry.parentId,
|
|
126
|
+
...("checkpoint" in entry ? { checkpoint: entry.checkpoint } : {}),
|
|
127
|
+
...(entry.type === "usage" || entry.type === "compaction" || entry.type === "branch_summary" ? { usage: entry.usage } : {}),
|
|
128
|
+
...(entry.type === "usage" ? { provider: entry.provider, model: entry.model } : {}),
|
|
129
|
+
...(message ? { message: { role: message.role, timestamp: message.timestamp,
|
|
130
|
+
...("usage" in message ? { usage: message.usage } : {}),
|
|
131
|
+
...(message.role === "assistant" ? { provider: message.provider, model: message.model, responseModel: message.responseModel } : {}),
|
|
132
|
+
...("toolCallId" in message ? { toolCallId: message.toolCallId } : {}) } } : {}) });
|
|
133
|
+
}
|
|
134
|
+
const sessionFile = ctx.sessionManager.getSessionFile();
|
|
135
|
+
process.stdout.write(`${JSON.stringify({ type: "subagent.native", boundary, entries,
|
|
136
|
+
sessionId: ctx.sessionManager.getSessionId(), sessionFile, leafId: ctx.sessionManager.getLeafId(),
|
|
137
|
+
persisted: Boolean(sessionFile && fs.existsSync(sessionFile)), messageCount: observedMessages,
|
|
138
|
+
configuration: { ...(ctx.model ? { model: `${ctx.model.provider}/${ctx.model.id}` } : {}), thinking: pi.getThinkingLevel() } })}\n`);
|
|
139
|
+
};
|
|
140
|
+
pi.on("turn_end", (_event, ctx) => observe(ctx, "turn"));
|
|
141
|
+
pi.on("agent_settled", (_event, ctx) => observe(ctx, "settled"));
|
|
142
|
+
const structuredOutputPath = process.env[STRUCTURED_OUTPUT_CAPTURE_ENV];
|
|
143
|
+
const structuredSchemaPath = process.env[STRUCTURED_OUTPUT_SCHEMA_ENV];
|
|
144
|
+
if (structuredOutputPath && structuredSchemaPath) {
|
|
145
|
+
const schema = JSON.parse(fs.readFileSync(structuredSchemaPath, "utf-8")) as JsonSchemaObject;
|
|
146
|
+
const parameters = Type.Object({ value: Type.Unsafe(schema) }, { additionalProperties: false });
|
|
147
|
+
pi.registerTool({
|
|
148
|
+
name: "structured_output",
|
|
149
|
+
label: "Structured Output",
|
|
150
|
+
description: "Submit the required final structured output for this subagent step. This terminates the step.",
|
|
151
|
+
parameters,
|
|
152
|
+
constrainedSampling: { type: "json_schema", strict: "prefer" },
|
|
153
|
+
async execute(_id: string, params: { value: unknown }) {
|
|
154
|
+
const validation = validateStructuredOutputValue(schema, params.value);
|
|
155
|
+
if (validation.status === "invalid") {
|
|
156
|
+
throw new Error(`Structured output validation failed: ${validation.message}`);
|
|
157
|
+
}
|
|
158
|
+
fs.mkdirSync(path.dirname(structuredOutputPath), { recursive: true });
|
|
159
|
+
fs.writeFileSync(structuredOutputPath, JSON.stringify(params.value), { mode: 0o600 });
|
|
160
|
+
return {
|
|
161
|
+
content: [{ type: "text", text: "Structured output captured." }],
|
|
162
|
+
details: { path: structuredOutputPath },
|
|
163
|
+
terminate: true,
|
|
164
|
+
};
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
pi.on("session_start", () => {
|
|
168
|
+
pi.setActiveTools([...new Set([...pi.getActiveTools(), "structured_output"])]);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
pi.on("context", (event) => {
|
|
173
|
+
// Filtering changes the provider prefix, never the saved journal. Fanout children
|
|
174
|
+
// need their own nested calls/results on later turns and resumes, so retain tool history.
|
|
175
|
+
const messages = stripParentOnlySubagentMessages(event.messages, readBooleanEnv(SUBAGENT_FANOUT_CHILD_ENV) === true);
|
|
176
|
+
if (messages === event.messages) return;
|
|
177
|
+
return { messages };
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
pi.on("before_agent_start", (event) => {
|
|
181
|
+
const intercomSessionName = process.env[SUBAGENT_INTERCOM_SESSION_NAME_ENV]?.trim();
|
|
182
|
+
if (intercomSessionName && typeof pi.setSessionName === "function") {
|
|
183
|
+
pi.setSessionName(intercomSessionName);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const inheritProjectContext = readBooleanEnv(SUBAGENT_INHERIT_PROJECT_CONTEXT_ENV);
|
|
187
|
+
const inheritSkills = readBooleanEnv(SUBAGENT_INHERIT_SKILLS_ENV);
|
|
188
|
+
const fanoutChild = readBooleanEnv(SUBAGENT_FANOUT_CHILD_ENV);
|
|
189
|
+
if (inheritProjectContext === undefined && inheritSkills === undefined && fanoutChild === undefined) return;
|
|
190
|
+
const options = event.systemPromptOptions;
|
|
191
|
+
// --no-skills/--no-context-files govern discovery. Keep explicitly selected
|
|
192
|
+
// skill bodies and context intact; never parse the rendered prompt to remove resources.
|
|
193
|
+
options.skills = options.skills.filter((skill) => skill.name !== "pi-subagents");
|
|
194
|
+
setPromptSection(options, "subagent_role", fanoutChild === true
|
|
195
|
+
? `${CHILD_FANOUT_BOUNDARY_INSTRUCTIONS}\n${loadConfig().compactChildTools === false
|
|
196
|
+
? "Use subagent({action:'list'}) to discover agents before delegation."
|
|
197
|
+
: "Use load_subagent({advanced:false}), then agent_runs({action:'profiles'}) to discover agents, delegate for ordinary work, and load_subagent for advanced workflows."}`
|
|
198
|
+
: CHILD_SUBAGENT_BOUNDARY_INSTRUCTIONS);
|
|
199
|
+
if (structuredOutputPath) setPromptSection(options, "subagent_output", STRUCTURED_OUTPUT_INSTRUCTIONS);
|
|
200
|
+
});
|
|
201
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
const REPEATED_SUBAGENT_CALL_LIMIT = 5;
|
|
4
|
+
const SUBAGENT_CALL_WINDOW_SIZE = REPEATED_SUBAGENT_CALL_LIMIT * 2 - 1;
|
|
5
|
+
|
|
6
|
+
interface SubagentCallStart {
|
|
7
|
+
callKey: string;
|
|
8
|
+
toolName: unknown;
|
|
9
|
+
failed?: boolean;
|
|
10
|
+
toolCallId?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface RepeatedSubagentCallGuardState {
|
|
14
|
+
recentStarts: boolean[];
|
|
15
|
+
recentSubagentCalls: SubagentCallStart[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createRepeatedSubagentCallGuardState(): RepeatedSubagentCallGuardState {
|
|
19
|
+
return { recentStarts: [], recentSubagentCalls: [] };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
23
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function stableStringify(value: unknown): string {
|
|
27
|
+
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
28
|
+
if (!isRecord(value)) return JSON.stringify(value) ?? "undefined";
|
|
29
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(",")}}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function subagentCallKey(toolName: unknown, args: unknown): string | undefined {
|
|
33
|
+
if ((toolName !== "subagent" && toolName !== "delegate" && toolName !== "agent_runs") || !isRecord(args)) return undefined;
|
|
34
|
+
try {
|
|
35
|
+
return createHash("sha256").update(`${toolName}:${stableStringify(args)}`).digest("hex");
|
|
36
|
+
} catch {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function recordToolStartForSubagentLoopGuard(input: {
|
|
42
|
+
state: RepeatedSubagentCallGuardState;
|
|
43
|
+
toolCallId?: unknown;
|
|
44
|
+
toolName: unknown;
|
|
45
|
+
args: unknown;
|
|
46
|
+
}): string | undefined {
|
|
47
|
+
const args = input.args;
|
|
48
|
+
const isList = isRecord(args) && (
|
|
49
|
+
input.toolName === "subagent" && args.action === "list"
|
|
50
|
+
|| input.toolName === "agent_runs" && args.action === "profiles"
|
|
51
|
+
);
|
|
52
|
+
input.state.recentStarts.push(isList);
|
|
53
|
+
if (input.state.recentStarts.length > SUBAGENT_CALL_WINDOW_SIZE) input.state.recentStarts.shift();
|
|
54
|
+
const callKey = subagentCallKey(input.toolName, input.args);
|
|
55
|
+
if (callKey) {
|
|
56
|
+
input.state.recentSubagentCalls.push({
|
|
57
|
+
callKey,
|
|
58
|
+
toolName: input.toolName,
|
|
59
|
+
toolCallId: typeof input.toolCallId === "string" ? input.toolCallId : undefined,
|
|
60
|
+
});
|
|
61
|
+
if (input.state.recentSubagentCalls.length > SUBAGENT_CALL_WINDOW_SIZE) input.state.recentSubagentCalls.shift();
|
|
62
|
+
}
|
|
63
|
+
if (!isList) return undefined;
|
|
64
|
+
const listCount = input.state.recentStarts.filter(Boolean).length;
|
|
65
|
+
if (listCount >= REPEATED_SUBAGENT_CALL_LIMIT) {
|
|
66
|
+
return `Child appears stuck repeating ${input.toolName}({ action: "${args.action}" }) ${listCount} times. Stopping to avoid a tool loop.`;
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function recordToolEndForSubagentLoopGuard(input: {
|
|
72
|
+
state: RepeatedSubagentCallGuardState;
|
|
73
|
+
toolCallId?: unknown;
|
|
74
|
+
toolName: unknown;
|
|
75
|
+
isError: unknown;
|
|
76
|
+
}): string | undefined {
|
|
77
|
+
if (input.isError !== true) return undefined;
|
|
78
|
+
const toolCallId = typeof input.toolCallId === "string" ? input.toolCallId : undefined;
|
|
79
|
+
const entry = [...input.state.recentSubagentCalls].reverse().find((candidate) =>
|
|
80
|
+
candidate.toolName === input.toolName && candidate.failed === undefined
|
|
81
|
+
&& (!toolCallId || !candidate.toolCallId || candidate.toolCallId === toolCallId));
|
|
82
|
+
if (!entry) return undefined;
|
|
83
|
+
entry.failed = true;
|
|
84
|
+
const failedCount = input.state.recentSubagentCalls.filter((candidate) => candidate.failed && candidate.callKey === entry.callKey).length;
|
|
85
|
+
if (failedCount >= REPEATED_SUBAGENT_CALL_LIMIT) {
|
|
86
|
+
return `Child appears stuck repeating the same failed ${input.toolName} call ${failedCount} times. Stopping to avoid a tool loop.`;
|
|
87
|
+
}
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|