@selesai/code 0.8.5 → 0.8.7
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 +28 -0
- package/dist/core/agent-session.js +13 -1
- package/dist/core/extensions/types.d.ts +2 -0
- package/dist/core/provider-composer.js +17 -4
- package/dist/core/settings-manager.d.ts +2 -0
- package/dist/core/settings-manager.js +3 -0
- package/dist/core/system-prompt.js +2 -8
- package/dist/core/system-prompt.test.js +6 -0
- package/dist/core/tools/index.d.ts +1 -0
- package/dist/core/tools/index.js +1 -0
- package/dist/core/tools/schema-prune.d.ts +16 -0
- package/dist/core/tools/schema-prune.js +39 -0
- package/dist/core/tools/schema-prune.test.d.ts +1 -0
- package/dist/core/tools/schema-prune.test.js +111 -0
- package/dist/defaults/models.json +5 -0
- package/dist/defaults/settings.json +1 -0
- package/dist/extensions/agent-browser.test.ts +178 -2
- package/dist/extensions/agent-browser.ts +2 -0
- package/dist/extensions/copy-turn.test.ts +229 -0
- package/dist/extensions/copy-turn.ts +11 -0
- package/dist/extensions/grep-app/index.test.ts +356 -1
- package/dist/extensions/grep-app/index.ts +6 -1
- package/dist/extensions/handoff-new.test.ts +351 -161
- package/dist/extensions/handoff-new.ts +3 -0
- package/dist/extensions/inline-skills.test.ts +93 -0
- package/dist/extensions/inline-skills.ts +3 -0
- package/dist/extensions/model-prompt-injector/config.json +10 -0
- package/dist/extensions/model-prompt-injector/index.test.ts +112 -0
- package/dist/extensions/model-prompt-injector/index.ts +151 -0
- package/dist/extensions/package.json +1 -0
- package/dist/extensions/pi-subagents/CHANGELOG.md +158 -90
- package/dist/extensions/pi-subagents/UPSTREAM-V0.50-MAPPING.md +172 -0
- package/dist/extensions/pi-subagents/agents/builder.md +5 -2
- package/dist/extensions/pi-subagents/agents/commentator.md +1 -1
- package/dist/extensions/pi-subagents/agents/oracle.md +7 -5
- package/dist/extensions/pi-subagents/agents/reviewer.md +2 -2
- package/dist/extensions/pi-subagents/agents/scout.md +1 -1
- package/dist/extensions/pi-subagents/agents/worker.md +1 -1
- package/dist/extensions/pi-subagents/docs/configuration.md +78 -11
- package/dist/extensions/pi-subagents/docs/extension-api.md +36 -0
- package/dist/extensions/pi-subagents/docs/missions.md +5 -3
- package/dist/extensions/pi-subagents/docs/models.md +1 -1
- package/dist/extensions/pi-subagents/docs/observability.md +42 -2
- package/dist/extensions/pi-subagents/docs/tool-reference.md +20 -3
- package/dist/extensions/pi-subagents/docs/workflows.md +4 -4
- package/dist/extensions/pi-subagents/install.mjs +2 -2
- package/dist/extensions/pi-subagents/package-lock.json +1470 -1472
- package/dist/extensions/pi-subagents/package.json +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +5 -4
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +16 -15
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +46 -26
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +2 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +37 -12
- package/dist/extensions/pi-subagents/src/api/external-runs.ts +174 -84
- package/dist/extensions/pi-subagents/src/api/preflight.ts +13 -7
- package/dist/extensions/pi-subagents/src/extension/config.ts +59 -0
- package/dist/extensions/pi-subagents/src/extension/index.ts +146 -23
- package/dist/extensions/pi-subagents/src/extension/public-execution.ts +34 -6
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +5 -1
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +31 -30
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +10 -9
- package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +13 -8
- package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +16 -3
- package/dist/extensions/pi-subagents/src/inspectors/herdr/project-panes.ts +2 -6
- package/dist/extensions/pi-subagents/src/inspectors/herdr/shell-command.ts +16 -0
- package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +5 -4
- package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +19 -42
- package/dist/extensions/pi-subagents/src/missions/goal-driver.ts +3 -1
- package/dist/extensions/pi-subagents/src/missions/store.ts +8 -3
- package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +82 -25
- package/dist/extensions/pi-subagents/src/runs/background/active-run-index.ts +71 -1
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +73 -43
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +17 -6
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +14 -6
- package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +277 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +11 -3
- package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/background/completion-replay.ts +11 -1
- package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +21 -6
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +5 -2
- package/dist/extensions/pi-subagents/src/runs/background/result-files.ts +437 -0
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +224 -42
- package/dist/extensions/pi-subagents/src/runs/background/resume-guidance.ts +27 -7
- package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +76 -19
- package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +35 -26
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +108 -9
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +54 -28
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +27 -13
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +298 -33
- package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +5 -2
- package/dist/extensions/pi-subagents/src/runs/background/wait-subscriptions.ts +2 -1
- package/dist/extensions/pi-subagents/src/runs/foreground/async-dismiss-action.ts +2 -1
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +16 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +219 -15
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +7 -4
- package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +4 -3
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +368 -50
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +107 -1
- package/dist/extensions/pi-subagents/src/runs/shared/external-cli-runner.ts +4 -0
- package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +39 -23
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +16 -2
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +66 -62
- package/dist/extensions/pi-subagents/src/runs/shared/orca-progress-tabs.ts +376 -0
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +15 -0
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +1 -9
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +12 -0
- package/dist/extensions/pi-subagents/src/runs/shared/tool-timeout.ts +95 -0
- package/dist/extensions/pi-subagents/src/shared/agent-stream-options.ts +5 -0
- package/dist/extensions/pi-subagents/src/shared/artifacts.ts +0 -4
- package/dist/extensions/pi-subagents/src/shared/display-text.ts +50 -0
- package/dist/extensions/pi-subagents/src/shared/node-executable.ts +21 -0
- package/dist/extensions/pi-subagents/src/shared/session-lineage.ts +71 -0
- package/dist/extensions/pi-subagents/src/shared/types.ts +66 -4
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +34 -25
- package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +4 -0
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +160 -45
- package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +1 -48
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +129 -17
- package/dist/extensions/pi-subagents/src/tui/render.ts +122 -44
- package/dist/extensions/pi-subagents/src/watchdog/permission-arbiter.ts +2 -1
- package/dist/extensions/pi-subagents/src/watchdog/review.ts +4 -3
- package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +10 -2
- package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +272 -76
- package/dist/extensions/pi-subagents/src/workflows/workflow-auto-relaunch.ts +28 -0
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/types.d.ts +2 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +68 -9
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +27 -1
- package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +49 -0
- package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +26 -1
- package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +144 -0
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +26 -0
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +369 -98
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +111 -78
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +49 -1
- package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +12 -0
- package/dist/extensions/pi-subagents/test/support/node-command.ts +15 -0
- package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +50 -0
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +28 -0
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +61 -0
- package/dist/extensions/pi-subagents/test/unit/agent-stream-options.test.ts +14 -0
- package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +76 -0
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +2 -0
- package/dist/extensions/pi-subagents/test/unit/async-status-snapshot.test.ts +162 -0
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +209 -10
- package/dist/extensions/pi-subagents/test/unit/completion-replay.test.ts +41 -1
- package/dist/extensions/pi-subagents/test/unit/external-runs.test.ts +135 -42
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +252 -1
- package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +192 -6
- package/dist/extensions/pi-subagents/test/unit/handoff-adoption.test.ts +103 -0
- package/dist/extensions/pi-subagents/test/unit/herdr-inspector-bootstrap.test.ts +32 -0
- package/dist/extensions/pi-subagents/test/unit/herdr-shell-command.test.ts +59 -0
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +91 -0
- package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +23 -4
- package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +56 -1
- package/dist/extensions/pi-subagents/test/unit/mission-goal-driver.test.ts +67 -3
- package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +14 -0
- package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +18 -0
- package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +25 -2
- package/dist/extensions/pi-subagents/test/unit/node-executable.test.ts +26 -0
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +8 -0
- package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +348 -0
- package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +5 -2
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +34 -0
- package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +6 -1
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +92 -0
- package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +175 -0
- package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +127 -17
- package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +44 -0
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +247 -0
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +107 -2
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +5 -8
- package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +456 -4
- package/dist/extensions/pi-subagents/test/unit/session-lineage.test.ts +73 -0
- package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +128 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +28 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +16 -7
- package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +15 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +79 -15
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +12 -12
- package/dist/extensions/pi-subagents/test/unit/tool-timeout.test.ts +109 -0
- package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +34 -0
- package/dist/extensions/pi-subagents/test/unit/workflow-auto-relaunch.test.ts +45 -0
- package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +94 -1
- package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +42 -2
- package/dist/extensions/ponytail/index.js +11 -0
- package/dist/extensions/ponytail/ponytail-config.cjs +2 -0
- package/dist/extensions/ponytail/ponytail-instructions.cjs +6 -0
- package/dist/extensions/ponytail/test/extension.test.js +274 -140
- package/dist/extensions/ponytail/test/helpers.test.js +280 -92
- package/dist/extensions/question/index.ts +33 -0
- package/dist/extensions/question/question-list.ts +28 -0
- package/dist/extensions/question/row-layout.ts +3 -0
- package/dist/extensions/question/tests/batch.test.ts +103 -65
- package/dist/extensions/question/tests/exp.test.ts +70 -0
- package/dist/extensions/question/tests/exp2.test.ts +59 -0
- package/dist/extensions/question/tests/exp3.test.ts +47 -0
- package/dist/extensions/question/tests/helpers.test.ts +75 -35
- package/dist/extensions/question/tests/question-list.test.ts +640 -198
- package/dist/extensions/question/tests/row-layout.test.ts +193 -114
- package/dist/extensions/question/tests/schemas.test.ts +42 -19
- package/dist/extensions/question/tests/selection-mode.test.ts +54 -0
- package/dist/extensions/question/tests/shortcuts.test.ts +106 -84
- package/dist/extensions/question/tests/wizard.test.ts +771 -50
- package/dist/extensions/question/tests/zz-ig3.test.ts +20 -0
- package/dist/extensions/question/tests/zz-probe.test.ts +29 -0
- package/dist/extensions/rtk.test.ts +180 -1
- package/dist/extensions/tokenin-onboarding.ts +3 -0
- package/dist/extensions/undo.test.ts +720 -0
- package/dist/extensions/undo.ts +6 -0
- package/dist/extensions/web-agent-onboarding.test.ts +415 -0
- package/dist/extensions/workflow/extension.ts +3 -3
- package/dist/extensions/workflow/modes.ts +41 -19
- package/dist/skills/pi-subagents/SKILL.md +1 -1
- package/dist/skills/pi-subagents/references/constraints-and-recipes.md +5 -4
- package/dist/skills/pi-subagents/references/execution-controls.md +16 -15
- package/dist/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
- package/dist/skills/pi-subagents/references/prompting-and-roles.md +46 -26
- package/docs/plans/workflow-autoloop-reference.md +1 -2
- package/docs/plans/workflow-handoff-carryover.md +108 -0
- package/docs/settings.md +6 -0
- package/package.json +4 -1
|
@@ -8,11 +8,12 @@ const CUSTOM_TOOL_DESCRIPTION_MAX_BYTES = 50 * 1024;
|
|
|
8
8
|
|
|
9
9
|
export const SUBAGENT_SAFETY_GUIDANCE = `SAFETY-CRITICAL SUBAGENT GUIDANCE:
|
|
10
10
|
• Use { action: "list" } before execution and only run executable/non-disabled agents.
|
|
11
|
-
• Keep execution and management separate: omit action for workflowScript execution; use action only for management/control.
|
|
12
|
-
• Async/background runs are the default. Use async:false only when a blocking foreground result is needed. Do not sleep or poll status just to wait; use subagent_wait only when the current request must finish in this turn.
|
|
11
|
+
• Keep execution and management separate: omit action for structured single-child or workflowScript execution; use action only for management/control.
|
|
12
|
+
• Async/background runs are the default. Use async:false only when a blocking foreground result is needed. After an async launch, continue independent work only until its next dependency barrier; consume the result before work that depends on it. Do not sleep or poll status just to wait; use subagent_wait only when the current request must finish in this turn.
|
|
13
13
|
• Ordinary child subagents are not orchestrators. Only explicitly configured fanout children may use the child-safe subagent tool, still bounded by depth/session limits.
|
|
14
|
+
• Oracle/advisor consultations should use supervisor dialogue for material unknowns when available; request one-shot only when desired.
|
|
14
15
|
• Keep one writer for the same cwd/worktree. Use fresh-context read-only reviewers for independent review, then have the parent synthesize and apply fixes.
|
|
15
|
-
• Async runs expose asyncId/asyncDir with status.json, events.jsonl, output logs,
|
|
16
|
+
• Async runs expose asyncId/asyncDir with status.json, events.jsonl, output logs, status via { action: "status", id }, and lifecycle diagnostics via { action: "debug.run", id }. Include output paths and residual risks when reporting results.`;
|
|
16
17
|
|
|
17
18
|
export const FULL_SUBAGENT_TOOL_DESCRIPTION = `Delegate one child with { agent, task }, compose steps with { chain }, fan out with { tasks }, or orchestrate with { workflowScript }; omit action. Use action only for management/control actions.
|
|
18
19
|
|
|
@@ -21,7 +22,7 @@ EXECUTION (use exactly one mode):
|
|
|
21
22
|
• SINGLE: { agent, task? } launches one child. Omit task for a self-contained agent.
|
|
22
23
|
• CHAIN: { chain: [{ agent: "explorer", task: "..." }, { agent: "builder", task: "Implement from {previous}" }] } runs sequential steps. Steps support template variables {task}, {previous}, {chain_dir}, and {outputs.name}; use as: "name" on a step to bind {outputs.name} for later steps, phase/label for status readability, outputSchema for structured results, and { checkpoint: "name", message?: "..." } for human gates paused until approve-checkpoint / reject-checkpoint. Set gateOn: "acceptance" to advance on acceptance instead of execution. agentContract: { version: 1 } gives generic result projections.
|
|
23
24
|
• PARALLEL: { tasks: [{ agent, task }, ...], concurrency? } runs top-level parallel children (count: N repeats one template). Parallel groups also work inside chain steps with { parallel: [...] }, plus dynamic fanout via { expand: { from: { output, path }, item?, maxItems, parallel: {...}, collect: { as, outputSchema? } } }.
|
|
24
|
-
• SCRIPTED WORKFLOW: { workflowScript: "return runs.run('main', {agent:'worker', task:'...'})" }. Use stable-key runs.run for one child and runs.all for parallel children; ordinary JavaScript provides sequence, branching, filtering, retries, and aggregation. workflowScript is an ordinary JavaScript statement body, so use an explicit return for a useful result. For task text with Markdown fences or shell blocks, build quoted lines instead of nesting raw template literals: \`const task=["Run:","\`\`\`bash","npm test","\`\`\`"].join("
|
|
25
|
+
• SCRIPTED WORKFLOW: { workflowScript: "return runs.run('main', {agent:'worker', task:'...'})" }. Use stable-key runs.run for one child and runs.all for parallel children; ordinary JavaScript provides sequence, branching, filtering, retries, and aggregation. workflowScript is an ordinary JavaScript statement body, so use an explicit return for a useful result. For task text with Markdown fences or shell blocks, build quoted lines instead of nesting raw template literals: \`const task=["Run:","\`\`\`bash","npm test","\`\`\`"].join("\n")\`. Scripts start asynchronously by default; pass async:false only for a small foreground run. Same-repo foreground workflows default to a live in-chat card; set chatProgress to auto, off, terminal, milestones, or live-card to control that projection. Workflow-level child controls default onto each runs.run launch, and explicit child fields override them. Use await prompts.render("package:name" | "user:name" | "project:name", vars?) for reusable plain task text, then pass the result explicitly as task. Use {action:"children.list"} to list recent retained children with resumable/not-resumable states. Resume only rows reported resumable; {action:"resume", id:"run-id", message:"..."} keeps the stored agent/model/tool contract, or use runs.run(key, {resume:"run-id", task:"follow-up"}) inside workflowScript. Workflow resumes wait for completed output, and loops must continue from each latest returned runId. If no resumable child is listed, launch a same-role fallback challenge and label it as fallback. For repository mutation lanes, set worktree:true on a direct single child, chain, workflow, or individual runs.run/runs.all item for managed isolation; each parallel child gets a separate worktree and handoff artifact. A workflow usageBudget is enforced once across the workflow. Available globals are runs.run, runs.all, runs.status, runs.ref/refs, prompts.render, emit, console, and standard JavaScript only. Workflows get async state.get(key) and state.set(key, JSONValue) through their automatic or explicit mission; mission:false workflows do not have a state global. Scripts cannot access filesystem, shell, arbitrary Pi tools, or host globals.
|
|
25
26
|
• Sequential example: { workflowScript: "const a = await runs.run('analyze', {agent:'agent-a', task:'Analyze the request'}); return (await runs.run('plan', {agent:'agent-b', task:'Plan from: '+a.output})).output" }
|
|
26
27
|
• Parallel example: { workflowScript: "const [a,b] = await runs.all([{key:'correctness',agent:'agent-a',task:'Review correctness'},{key:'tests',agent:'agent-b',task:'Review tests'}]); return {correctness:a.output,tests:b.output}" }
|
|
27
28
|
• Optional context is "fresh" or "fork". timeoutMs/maxRuntimeMs apply to foreground and async runs; foreground defaults to 30 minutes absent call/agent. Omit acceptance for read-only calls; evidence levels end at verified, and acceptance.review.required requests independent writer review. For one host-run verification command, gate: "npm test" on a runs.run/runs.all item is shorthand; it cannot be combined with acceptance.
|
|
@@ -41,7 +42,7 @@ export const COMPACT_SUBAGENT_TOOL_DESCRIPTION = `Delegate one child with { agen
|
|
|
41
42
|
EXECUTE:
|
|
42
43
|
• Call { action:"list" } first and use only executable/non-disabled agents.
|
|
43
44
|
• SINGLE {agent, task?}; CHAIN {chain:[{agent,task,...}]} with {previous}/{outputs.name} templates and { checkpoint } human gates; PARALLEL {tasks:[...], concurrency?} with optional count and in-chain { parallel: [...] } / dynamic { expand }+{ collect } fanout.
|
|
44
|
-
• SCRIPT {workflowScript:"return runs.run('main', {agent:'worker', task:'...'})"}. Use stable-key runs.run for one child and runs.all for parallel work. Use await prompts.render("package:name" | "user:name" | "project:name", vars?) for reusable task text and pass it explicitly to runs.run. Use {action:"children.list"} for
|
|
45
|
+
• SCRIPT {workflowScript:"return runs.run('main', {agent:'worker', task:'...'})"}. Use stable-key runs.run for one child and runs.all for parallel work. Use await prompts.render("package:name" | "user:name" | "project:name", vars?) for reusable task text and pass it explicitly to runs.run. Use {action:"children.list"} for recent retained children and resume only rows reported resumable, with {action:"resume", id, message} or runs.run(key,{resume:"run-id",task:"follow-up"}); workflow resumes wait for completion and loops continue from the latest returned runId. Workflows get async state.get/state.set through their automatic or explicit mission; mission:false does not. Scripts are ordinary JavaScript statement bodies; use explicit return for a useful result. For task text with Markdown fences or shell blocks, build quoted lines instead of nesting raw template literals: \`const task=["Run:","\`\`\`bash","npm test","\`\`\`"].join("\n")\`. Use JavaScript for sequence, branching, retries, and aggregation. For repository mutation lanes, use worktree:true on a direct single child, chain, or runs.run/runs.all item for managed isolation. Scripts start async by default; async:false is the foreground escape hatch and auto-enables a same-repo live chat card unless chatProgress is off/terminal/milestones.
|
|
45
46
|
• Example: {workflowScript:"const [a,b]=await runs.all([{key:'a',agent:'agent-a',task:'Implement A',worktree:true},{key:'b',agent:'agent-b',task:'Implement B',worktree:true}]); return [a.output,b.output]"}
|
|
46
47
|
• context can be fresh or fork. timeoutMs/maxRuntimeMs apply to foreground and async runs; foreground defaults to 30 minutes absent call/agent. Omit acceptance for read-only calls.
|
|
47
48
|
|
|
@@ -72,10 +73,10 @@ export interface ToolDescriptionOptions {
|
|
|
72
73
|
|
|
73
74
|
export function resolveToolDescriptionMode(config: Pick<ExtensionConfig, "toolDescriptionMode">, options?: ToolDescriptionOptions): ToolDescriptionMode {
|
|
74
75
|
const mode = config.toolDescriptionMode;
|
|
75
|
-
if (mode === undefined) return "
|
|
76
|
+
if (mode === undefined) return "compact";
|
|
76
77
|
if (isToolDescriptionMode(mode)) return mode;
|
|
77
78
|
warn(options, `Ignoring invalid toolDescriptionMode ${JSON.stringify(mode)}; expected "full", "compact", or "custom".`);
|
|
78
|
-
return "
|
|
79
|
+
return "compact";
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
function customDescriptionPaths(options?: ToolDescriptionOptions): string[] {
|
|
@@ -178,8 +179,8 @@ export function buildSubagentToolDescription(config: Pick<ExtensionConfig, "tool
|
|
|
178
179
|
const custom = loadCustomToolDescription(options);
|
|
179
180
|
if (custom) description = withMandatorySafetyGuidance(custom);
|
|
180
181
|
else {
|
|
181
|
-
warn(options, `${CUSTOM_TOOL_DESCRIPTION_FILE} was not found or valid for toolDescriptionMode "custom"; using
|
|
182
|
-
description =
|
|
182
|
+
warn(options, `${CUSTOM_TOOL_DESCRIPTION_FILE} was not found or valid for toolDescriptionMode "custom"; using compact description.`);
|
|
183
|
+
description = COMPACT_SUBAGENT_TOOL_DESCRIPTION;
|
|
183
184
|
}
|
|
184
185
|
} else description = FULL_SUBAGENT_TOOL_DESCRIPTION;
|
|
185
186
|
return config.legacyChainControls === false ? withoutLegacyChainControlGuidance(description) : description;
|
|
@@ -10,7 +10,9 @@ import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
|
10
10
|
import { DIRS, type Details, type SubagentState } from "../../shared/types.ts";
|
|
11
11
|
import { readStatus } from "../../shared/utils.ts";
|
|
12
12
|
import { resolveSubagentRunId } from "../../runs/background/run-id-resolver.ts";
|
|
13
|
+
import { resolveNodeExecutable } from "../../shared/node-executable.ts";
|
|
13
14
|
import { createHerdrClient, detectHerdr, type HerdrClient, type HerdrErrorCode, type HerdrResult } from "./client.ts";
|
|
15
|
+
import { formatShellCommand } from "./shell-command.ts";
|
|
14
16
|
|
|
15
17
|
export const HERDR_INSPECTOR_ACTIONS = ["inspector.open", "inspector.status", "inspector.close"] as const;
|
|
16
18
|
export type HerdrInspectorAction = typeof HERDR_INSPECTOR_ACTIONS[number];
|
|
@@ -45,6 +47,7 @@ interface InspectorDeps {
|
|
|
45
47
|
client?: HerdrClient;
|
|
46
48
|
missions?: MissionStoreConfig;
|
|
47
49
|
authorityPolicy?: AuthorityPolicyConfig;
|
|
50
|
+
sessionRoots?: string[];
|
|
48
51
|
cwd: string;
|
|
49
52
|
signal?: AbortSignal;
|
|
50
53
|
now?: () => Date;
|
|
@@ -84,16 +87,11 @@ function extractPaneId(value: unknown): string | undefined {
|
|
|
84
87
|
return undefined;
|
|
85
88
|
}
|
|
86
89
|
|
|
87
|
-
function
|
|
88
|
-
|
|
89
|
-
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function inspectorCommand(input: { runnerPath: string; asyncDir: string; runId: string; index?: number; missionPath?: string; allowSteer: boolean; allowStop: boolean }): string {
|
|
93
|
-
const args = [process.execPath, input.runnerPath, "--async-dir", input.asyncDir, "--run-id", input.runId, "--allow-steer", String(input.allowSteer), "--allow-stop", String(input.allowStop)];
|
|
90
|
+
function inspectorCommand(input: { runnerPath: string; asyncDir: string; runId: string; index?: number; missionPath?: string; allowSteer: boolean; allowStop: boolean; sessionRoots: string[] }): string {
|
|
91
|
+
const args = [input.runnerPath, "--async-dir", input.asyncDir, "--run-id", input.runId, "--allow-steer", String(input.allowSteer), "--allow-stop", String(input.allowStop), "--session-roots", JSON.stringify(input.sessionRoots)];
|
|
94
92
|
if (input.index !== undefined) args.push("--index", String(input.index));
|
|
95
93
|
if (input.missionPath) args.push("--mission-path", input.missionPath);
|
|
96
|
-
return
|
|
94
|
+
return formatShellCommand(resolveNodeExecutable(), args);
|
|
97
95
|
}
|
|
98
96
|
|
|
99
97
|
function missionForRun(asyncDir: string, cwd: string, config: MissionStoreConfig | undefined, runId: string): { id: string; path: string } | undefined {
|
|
@@ -114,6 +112,12 @@ function pathWithin(base: string, candidate: string): boolean {
|
|
|
114
112
|
return resolvedCandidate === resolvedBase || resolvedCandidate.startsWith(`${resolvedBase}${path.sep}`);
|
|
115
113
|
}
|
|
116
114
|
|
|
115
|
+
function herdrSessionRoots(target: { runId: string }, deps: InspectorDeps): string[] {
|
|
116
|
+
const roots = deps.sessionRoots ?? deps.state?.trustedSessionRoots ?? [];
|
|
117
|
+
const job = deps.state?.asyncJobs.get(target.runId) ?? deps.state?.fleetJobs?.get(target.runId);
|
|
118
|
+
return [...new Set([...roots, ...(job?.sessionRoot ? [job.sessionRoot] : [])])];
|
|
119
|
+
}
|
|
120
|
+
|
|
117
121
|
function isTrustedAsyncDir(asyncDir: string, deps: InspectorDeps): boolean {
|
|
118
122
|
try {
|
|
119
123
|
if (fs.lstatSync(asyncDir).isSymbolicLink() || !fs.statSync(asyncDir).isDirectory()) return false;
|
|
@@ -204,6 +208,7 @@ export async function handleHerdrInspectorAction(action: HerdrInspectorAction, p
|
|
|
204
208
|
missionPath: mission?.path,
|
|
205
209
|
allowSteer: resolveAuthorityDecision({ action: "steerRun", policy: deps.authorityPolicy }) === "auto",
|
|
206
210
|
allowStop: resolveAuthorityDecision({ action: "stopRun", policy: deps.authorityPolicy }) === "auto",
|
|
211
|
+
sessionRoots: herdrSessionRoots(target, deps),
|
|
207
212
|
});
|
|
208
213
|
const started = await client.run(["pane", "run", paneId, command], { timeoutMs: 15_000, signal: deps.signal });
|
|
209
214
|
if (started.ok === false) {
|
|
@@ -17,6 +17,7 @@ export interface RunnerOptions {
|
|
|
17
17
|
refreshMs: number;
|
|
18
18
|
allowSteer?: boolean;
|
|
19
19
|
allowStop?: boolean;
|
|
20
|
+
sessionRoots: string[];
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
function readMission(filePath: string | undefined): MissionRecord | undefined {
|
|
@@ -24,7 +25,7 @@ function readMission(filePath: string | undefined): MissionRecord | undefined {
|
|
|
24
25
|
try { return parseMissionRecord(JSON.parse(fs.readFileSync(filePath, "utf-8")), filePath); } catch { return undefined; }
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
export function formatInspectorDashboard(input: { status: AsyncStatus; asyncDir: string; index?: number; mission?: MissionRecord; allowSteer?: boolean; allowStop?: boolean }): string {
|
|
28
|
+
export function formatInspectorDashboard(input: { status: AsyncStatus; asyncDir: string; index?: number; mission?: MissionRecord; allowSteer?: boolean; allowStop?: boolean; sessionRoots?: string[] }): string {
|
|
28
29
|
const { status, asyncDir, mission } = input;
|
|
29
30
|
const lines = [
|
|
30
31
|
`pi-subagents inspector for ${status.runId}`,
|
|
@@ -37,7 +38,7 @@ export function formatInspectorDashboard(input: { status: AsyncStatus; asyncDir:
|
|
|
37
38
|
if (open.length) lines.push(`Open decisions: ${open.map((decision) => `${decision.id}: ${decision.title}`).join(" | ")}`);
|
|
38
39
|
lines.push("");
|
|
39
40
|
}
|
|
40
|
-
lines.push(formatAsyncRunTranscript(status, asyncDir, { index: input.index, lines: 60 }));
|
|
41
|
+
lines.push(formatAsyncRunTranscript(status, asyncDir, { index: input.index, lines: 60, sessionRoots: input.sessionRoots }));
|
|
41
42
|
const controls = [input.allowSteer === false ? undefined : "steer <message>", input.allowStop === false ? undefined : "stop", "status"].filter(Boolean);
|
|
42
43
|
lines.push("", `Controls: ${controls.join(" | ")}`, "Supervisor replies remain in the parent Pi session (subagent_supervisor/intercom).");
|
|
43
44
|
return lines.join("\n");
|
|
@@ -57,6 +58,17 @@ function parseArgs(argv: string[]): RunnerOptions {
|
|
|
57
58
|
const indexRaw = values.get("--index");
|
|
58
59
|
const childIndex = indexRaw === undefined ? undefined : Number(indexRaw);
|
|
59
60
|
if (childIndex !== undefined && (!Number.isInteger(childIndex) || childIndex < 0)) throw new Error("--index must be a non-negative integer.");
|
|
61
|
+
const sessionRootsRaw = values.get("--session-roots");
|
|
62
|
+
let sessionRoots: string[] = [];
|
|
63
|
+
if (sessionRootsRaw !== undefined) {
|
|
64
|
+
try {
|
|
65
|
+
const parsed = JSON.parse(sessionRootsRaw) as unknown;
|
|
66
|
+
if (!Array.isArray(parsed) || parsed.some((root) => typeof root !== "string")) throw new Error();
|
|
67
|
+
sessionRoots = parsed;
|
|
68
|
+
} catch {
|
|
69
|
+
throw new Error("--session-roots must be a JSON array of strings.");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
60
72
|
const refreshRaw = values.get("--refresh-ms");
|
|
61
73
|
const refreshMs = refreshRaw === undefined ? 1_500 : Number(refreshRaw);
|
|
62
74
|
if (!Number.isInteger(refreshMs) || refreshMs < 250) throw new Error("--refresh-ms must be an integer >= 250.");
|
|
@@ -65,6 +77,7 @@ function parseArgs(argv: string[]): RunnerOptions {
|
|
|
65
77
|
runId,
|
|
66
78
|
...(childIndex !== undefined ? { index: childIndex } : {}),
|
|
67
79
|
...(values.get("--mission-path") ? { missionPath: path.resolve(values.get("--mission-path")!) } : {}),
|
|
80
|
+
sessionRoots,
|
|
68
81
|
refreshMs,
|
|
69
82
|
allowSteer: values.get("--allow-steer") !== "false",
|
|
70
83
|
allowStop: values.get("--allow-stop") !== "false",
|
|
@@ -115,7 +128,7 @@ export function runInspector(argv = process.argv.slice(2)): void {
|
|
|
115
128
|
process.stdout.write(`\x1b[2J\x1b[Hpi-subagents inspector\n\nLifecycle status for ${options.runId} is unavailable.\n`);
|
|
116
129
|
return;
|
|
117
130
|
}
|
|
118
|
-
process.stdout.write(`\x1b[2J\x1b[H${formatInspectorDashboard({ status, asyncDir: options.asyncDir, index: options.index, mission: readMission(options.missionPath), allowSteer: options.allowSteer, allowStop: options.allowStop })}${notice ? `\n\n${notice}` : ""}\n> `);
|
|
131
|
+
process.stdout.write(`\x1b[2J\x1b[H${formatInspectorDashboard({ status, asyncDir: options.asyncDir, index: options.index, mission: readMission(options.missionPath), allowSteer: options.allowSteer, allowStop: options.allowStop, sessionRoots: options.sessionRoots })}${notice ? `\n\n${notice}` : ""}\n> `);
|
|
119
132
|
if (isTerminal(status) && timer) {
|
|
120
133
|
clearInterval(timer);
|
|
121
134
|
timer = undefined;
|
|
@@ -6,6 +6,7 @@ import { getProjectSubagentsDir } from "../../shared/artifacts.ts";
|
|
|
6
6
|
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
7
7
|
import type { Details } from "../../shared/types.ts";
|
|
8
8
|
import { createHerdrClient, detectHerdr, type HerdrClient, type HerdrErrorCode } from "./client.ts";
|
|
9
|
+
import { formatShellCommand } from "./shell-command.ts";
|
|
9
10
|
|
|
10
11
|
export const HERDR_PROJECT_PANE_ACTIONS = ["project.open", "project.status", "project.close"] as const;
|
|
11
12
|
export type HerdrProjectPaneAction = typeof HERDR_PROJECT_PANE_ACTIONS[number];
|
|
@@ -257,11 +258,6 @@ function projectPaneRuntime(value: unknown): ProjectPaneRuntime | undefined {
|
|
|
257
258
|
};
|
|
258
259
|
}
|
|
259
260
|
|
|
260
|
-
function shellQuote(value: string): string {
|
|
261
|
-
if (process.platform === "win32") return `"${value.replaceAll('"', '\\"')}"`;
|
|
262
|
-
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
261
|
function resolveProjectRoot(requested: string): ProjectPaneResult<string> {
|
|
266
262
|
const resolved = path.resolve(requested);
|
|
267
263
|
try {
|
|
@@ -284,7 +280,7 @@ async function inspectPane(client: HerdrClient, paneId: string, signal?: AbortSi
|
|
|
284
280
|
function projectPaneCommand(message: string | undefined): string {
|
|
285
281
|
const args = message?.trim() ? [message.trim()] : [];
|
|
286
282
|
const command = getPiSpawnCommand(args);
|
|
287
|
-
return
|
|
283
|
+
return formatShellCommand(command.command, command.args);
|
|
288
284
|
}
|
|
289
285
|
|
|
290
286
|
function canonicalRuntimePath(value: string | undefined): string | undefined {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
function shellQuote(value: string, platform: NodeJS.Platform): string {
|
|
2
|
+
if (platform === "win32") return `"${value.replaceAll('"', '\\"')}"`;
|
|
3
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
function isBareExecutable(value: string): boolean {
|
|
7
|
+
return /^[\w./@:-]+$/.test(value);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function formatShellCommand(exe: string, args: readonly string[], platform: NodeJS.Platform = process.platform): string {
|
|
11
|
+
const quotedArgs = args.map((arg) => shellQuote(arg, platform));
|
|
12
|
+
if (platform === "win32") return `& ${[shellQuote(exe, platform), ...quotedArgs].join(" ")}`;
|
|
13
|
+
// Nushell treats a leading quoted token as a string, so use a bare invoker for paths that need quoting.
|
|
14
|
+
const invocation = isBareExecutable(exe) ? exe : `sh -c 'exec "$0" "$@"' ${shellQuote(exe, platform)}`;
|
|
15
|
+
return [invocation, ...quotedArgs].join(" ");
|
|
16
|
+
}
|
|
@@ -26,9 +26,8 @@ Use contact_supervisor first. It resolves the supervisor session "{orchestratorT
|
|
|
26
26
|
- After contact_supervisor with reason "need_decision" or "interview_request", stay alive and continue only after the reply arrives. Do not finish your final response with a choose-one question.
|
|
27
27
|
- Do not ask for clarification when the only conflict is review-only/no-edit versus progress-writing or artifact-writing instructions. If an output path is configured but no write-capable tool is available, return the complete artifact in your final response; the runtime will persist it. Do not contact the supervisor merely because you cannot write that output path directly.
|
|
28
28
|
- Meaningful progress or unexpected discoveries that change the plan: contact_supervisor({ reason: "progress_update", message: "UPDATE: <summary>" })
|
|
29
|
-
- Generic intercom is lower-level plumbing/fallback only: intercom({ action: "ask", to: "{orchestratorTarget}", message: "<question>" })
|
|
30
29
|
|
|
31
|
-
Do not use contact_supervisor
|
|
30
|
+
Do not use contact_supervisor for routine completion handoffs. If no coordination is needed, return a focused task result.`;
|
|
32
31
|
|
|
33
32
|
export interface IntercomBridgeState {
|
|
34
33
|
active: boolean;
|
|
@@ -51,6 +50,8 @@ export interface IntercomBridgeDiagnostic {
|
|
|
51
50
|
|
|
52
51
|
interface ResolveIntercomBridgeInput {
|
|
53
52
|
config: ExtensionConfig["intercomBridge"];
|
|
53
|
+
/** Per-run config replaces the global config when supplied. */
|
|
54
|
+
override?: IntercomBridgeConfig;
|
|
54
55
|
context: "fresh" | "fork" | undefined;
|
|
55
56
|
orchestratorTarget?: string;
|
|
56
57
|
cwd?: string;
|
|
@@ -143,7 +144,7 @@ export function diagnoseIntercomBridge(input: ResolveIntercomBridgeInput): Inter
|
|
|
143
144
|
}
|
|
144
145
|
|
|
145
146
|
export function resolveIntercomBridge(input: ResolveIntercomBridgeInput): IntercomBridgeState {
|
|
146
|
-
const config = resolveIntercomBridgeConfig(input.config);
|
|
147
|
+
const config = resolveIntercomBridgeConfig(input.override !== undefined ? input.override : input.config);
|
|
147
148
|
const mode = config.mode;
|
|
148
149
|
const orchestratorTarget = input.orchestratorTarget?.trim();
|
|
149
150
|
const agentDir = path.resolve(input.agentDir ?? defaultAgentDir());
|
|
@@ -169,7 +170,7 @@ export function resolveIntercomBridge(input: ResolveIntercomBridgeInput): Interc
|
|
|
169
170
|
export function applyIntercomBridgeToAgent(agent: AgentConfig, bridge: IntercomBridgeState): AgentConfig {
|
|
170
171
|
if (!bridge.active || !bridge.orchestratorTarget) return agent;
|
|
171
172
|
|
|
172
|
-
const bridgeTools = ["
|
|
173
|
+
const bridgeTools = ["contact_supervisor"];
|
|
173
174
|
const tools = agent.tools && agent.tools.length > 0
|
|
174
175
|
? [...agent.tools, ...bridgeTools.filter((tool) => !agent.tools?.includes(tool))]
|
|
175
176
|
: agent.tools;
|
|
@@ -295,38 +295,18 @@ function hasTool(pi: ExtensionAPI, name: string): boolean {
|
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
-
export function registerNativeSupervisorClient(pi: ExtensionAPI
|
|
299
|
-
if (!readChildMetadata()) return;
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
};
|
|
311
|
-
pi.registerTool(tool);
|
|
312
|
-
}
|
|
313
|
-
if (includeIntercomFallback && !hasTool(pi, "intercom")) {
|
|
314
|
-
const tool: ToolDefinition<typeof IntercomParamsSchema, Record<string, unknown>> = {
|
|
315
|
-
name: "intercom",
|
|
316
|
-
label: "Intercom",
|
|
317
|
-
description: "Native supervisor-channel intercom fallback for subagents. Prefer contact_supervisor when available.",
|
|
318
|
-
parameters: IntercomParamsSchema,
|
|
319
|
-
async execute(_id, params, signal) {
|
|
320
|
-
const action = (params as IntercomParams).action;
|
|
321
|
-
if (action === "status") return { content: [{ type: "text", text: "Native supervisor channel is active." }], details: { active: true } };
|
|
322
|
-
if (action === "list") return { content: [{ type: "text", text: "Supervisor session available through contact_supervisor." }], details: { sessions: [] } };
|
|
323
|
-
if (action === "send") return sendSupervisorRequest({ reason: "progress_update", message: (params as IntercomParams).message ?? "" }, signal);
|
|
324
|
-
if (action === "ask") return sendSupervisorRequest({ reason: "need_decision", message: (params as IntercomParams).message ?? "" }, signal);
|
|
325
|
-
throw new Error("Native child intercom supports status, list, send, and ask. Use parent intercom reply from the supervisor session.");
|
|
326
|
-
},
|
|
327
|
-
};
|
|
328
|
-
pi.registerTool(tool);
|
|
329
|
-
}
|
|
298
|
+
export function registerNativeSupervisorClient(pi: ExtensionAPI): void {
|
|
299
|
+
if (!readChildMetadata() || hasTool(pi, "contact_supervisor")) return;
|
|
300
|
+
const tool: ToolDefinition<typeof ContactSupervisorParamsSchema, Record<string, unknown>> = {
|
|
301
|
+
name: "contact_supervisor",
|
|
302
|
+
label: "Contact Supervisor",
|
|
303
|
+
description: "Contact the parent/supervisor session for a blocking decision, structured interview, or progress update.",
|
|
304
|
+
parameters: ContactSupervisorParamsSchema,
|
|
305
|
+
execute(_id, params, signal) {
|
|
306
|
+
return sendSupervisorRequest(params as ContactSupervisorParams, signal);
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
pi.registerTool(tool);
|
|
330
310
|
}
|
|
331
311
|
|
|
332
312
|
function parseRequestFile(file: string, channelDir: string): PendingSupervisorRequest | undefined {
|
|
@@ -601,13 +581,11 @@ function publicPendingRequests(pending: Map<string, PendingSupervisorRequest>):
|
|
|
601
581
|
}));
|
|
602
582
|
}
|
|
603
583
|
|
|
604
|
-
function
|
|
584
|
+
function buildParentSupervisorTool(pending: Map<string, PendingSupervisorRequest>, state: SubagentState): ToolDefinition<typeof IntercomParamsSchema, Record<string, unknown>> {
|
|
605
585
|
return {
|
|
606
|
-
name,
|
|
607
|
-
label:
|
|
608
|
-
description:
|
|
609
|
-
? "Native pi-subagents supervisor channel. Use reply/pending/status to answer child subagent requests."
|
|
610
|
-
: "Native pi-subagents supervisor channel. Use reply/pending/status to answer child subagent requests without overriding pi-intercom.",
|
|
586
|
+
name: NATIVE_SUPERVISOR_TOOL_NAME,
|
|
587
|
+
label: "Subagent Supervisor",
|
|
588
|
+
description: "Native pi-subagents supervisor channel. Use reply/pending/status to answer child subagent requests without overriding pi-intercom.",
|
|
611
589
|
parameters: IntercomParamsSchema,
|
|
612
590
|
async execute(_id, params) {
|
|
613
591
|
refreshPendingRequests(pending, state, state.lastUiContext ?? undefined);
|
|
@@ -627,9 +605,9 @@ function buildParentIntercomTool(pending: Map<string, PendingSupervisorRequest>,
|
|
|
627
605
|
return { content: [{ type: "text", text: `Replied to supervisor request ${request.id}.` }], details: { replyTo: request.id, runId: request.runId, agent: request.agent } };
|
|
628
606
|
}
|
|
629
607
|
if (input.action === "send" || input.action === "ask") {
|
|
630
|
-
throw new Error("
|
|
608
|
+
throw new Error("The native subagent supervisor handles replies only. Child agents initiate asks with contact_supervisor.");
|
|
631
609
|
}
|
|
632
|
-
throw new Error(`Unsupported
|
|
610
|
+
throw new Error(`Unsupported supervisor action: ${input.action}`);
|
|
633
611
|
},
|
|
634
612
|
};
|
|
635
613
|
}
|
|
@@ -647,8 +625,7 @@ export function createNativeSupervisorChannel(pi: ExtensionAPI, state: SubagentS
|
|
|
647
625
|
let lastStaleCleanupAt = 0;
|
|
648
626
|
|
|
649
627
|
const registerParentTools = (): void => {
|
|
650
|
-
if (!hasTool(pi, NATIVE_SUPERVISOR_TOOL_NAME)) pi.registerTool(
|
|
651
|
-
if (!hasTool(pi, "intercom")) pi.registerTool(buildParentIntercomTool(pending, state));
|
|
628
|
+
if (!hasTool(pi, NATIVE_SUPERVISOR_TOOL_NAME)) pi.registerTool(buildParentSupervisorTool(pending, state));
|
|
652
629
|
};
|
|
653
630
|
|
|
654
631
|
const cleanupStaleChannelsIfDue = (): void => {
|
|
@@ -99,7 +99,9 @@ function missionStateAction(location: MissionStoreLocation, record: MissionRecor
|
|
|
99
99
|
function retainedResumeTarget(record: MissionRecord, retainedChildren: RetainedChild[]): RetainedChild | undefined {
|
|
100
100
|
const latestRun = record.runs.at(-1);
|
|
101
101
|
if (!latestRun) return undefined;
|
|
102
|
-
return retainedChildren.find((child) =>
|
|
102
|
+
return retainedChildren.find((child) =>
|
|
103
|
+
child.resumability.state === "resumable" && (child.runId === latestRun.runId || child.parentRunId === latestRun.runId)
|
|
104
|
+
);
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
function nextReadyAction(location: MissionStoreLocation, record: MissionRecord, retainedChildren: RetainedChild[]): string {
|
|
@@ -2,7 +2,6 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
-
import { getProjectSubagentsDir } from "../shared/artifacts.ts";
|
|
6
5
|
import { writePrivateAtomicJson } from "../shared/atomic-json.ts";
|
|
7
6
|
import { getAgentDir } from "../shared/utils.ts";
|
|
8
7
|
import {
|
|
@@ -259,6 +258,11 @@ function expandConfiguredPath(value: string, projectRoot: string): string {
|
|
|
259
258
|
return path.isAbsolute(expanded) ? path.normalize(expanded) : path.resolve(projectRoot, expanded);
|
|
260
259
|
}
|
|
261
260
|
|
|
261
|
+
function projectMissionDirectory(agentDir: string, projectRoot: string): string {
|
|
262
|
+
const projectKey = createHash("sha256").update(projectRoot).digest("hex");
|
|
263
|
+
return path.join(agentDir, "missions", "projects", projectKey);
|
|
264
|
+
}
|
|
265
|
+
|
|
262
266
|
export function validateMissionStoreConfig(value: unknown, label = "config.missions"): MissionStoreConfig | undefined {
|
|
263
267
|
if (value === undefined) return undefined;
|
|
264
268
|
const input = asObject(value, label);
|
|
@@ -289,12 +293,13 @@ export function resolveMissionStoreLocation(input: {
|
|
|
289
293
|
agentDir?: string;
|
|
290
294
|
}): MissionStoreLocation {
|
|
291
295
|
const projectRoot = path.resolve(input.projectRoot);
|
|
296
|
+
const agentDir = input.agentDir ?? getAgentDir();
|
|
292
297
|
const missionDir = input.config?.directory
|
|
293
298
|
? expandConfiguredPath(input.config.directory, projectRoot)
|
|
294
|
-
:
|
|
299
|
+
: projectMissionDirectory(agentDir, projectRoot);
|
|
295
300
|
const globalIndexDir = input.config?.globalIndexDir
|
|
296
301
|
? expandConfiguredPath(input.config.globalIndexDir, projectRoot)
|
|
297
|
-
: path.join(
|
|
302
|
+
: path.join(agentDir, "missions", "index");
|
|
298
303
|
return {
|
|
299
304
|
projectRoot,
|
|
300
305
|
missionDir,
|
|
@@ -39,6 +39,18 @@ interface CapacityOptions {
|
|
|
39
39
|
afterSlotRename?: (releasedDir: string) => void;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
export type ActiveAsyncCapacityReleaseVerdict =
|
|
43
|
+
| { state: "releasable"; reason: string }
|
|
44
|
+
| { state: "retained"; reason: string }
|
|
45
|
+
| { state: "not-owned"; reason: string };
|
|
46
|
+
|
|
47
|
+
export interface ActiveAsyncCapacityInspection {
|
|
48
|
+
owner?: ActiveAsyncCapacityOwnerV1;
|
|
49
|
+
relation: "current" | "source" | "none";
|
|
50
|
+
slotDir?: string;
|
|
51
|
+
release: ActiveAsyncCapacityReleaseVerdict;
|
|
52
|
+
}
|
|
53
|
+
|
|
42
54
|
export class ActiveAsyncCapacityError extends Error {
|
|
43
55
|
readonly snapshot: ActiveAsyncCapacitySnapshot;
|
|
44
56
|
|
|
@@ -160,55 +172,100 @@ function terminalState(state: AsyncStatus["state"]): boolean {
|
|
|
160
172
|
return state !== "queued" && state !== "running" && state !== "paused";
|
|
161
173
|
}
|
|
162
174
|
|
|
163
|
-
function
|
|
164
|
-
if (!
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
175
|
+
function runnerReleaseVerdict(owner: ActiveAsyncCapacityOwnerV1, status: AsyncStatus | null): ActiveAsyncCapacityReleaseVerdict {
|
|
176
|
+
if (!status) return { state: "retained", reason: "status file is missing or unreadable" };
|
|
177
|
+
if (!owner.runnerProcessInstanceId) return { state: "retained", reason: "runner process identity has not been recorded" };
|
|
178
|
+
if (status.sessionId !== owner.ownerSessionId) return { state: "retained", reason: `status session ${status.sessionId ?? "unknown"} does not match owner session ${owner.ownerSessionId}` };
|
|
179
|
+
if (status.runId !== owner.runId) return { state: "retained", reason: `status run ${status.runId} does not match owner run ${owner.runId}` };
|
|
180
|
+
if (!terminalState(status.state)) return { state: "retained", reason: `run is still ${status.state}` };
|
|
168
181
|
if (status.processTerminal?.state === "not-started"
|
|
169
182
|
&& status.processTerminal.runId === owner.runId
|
|
170
183
|
&& status.processTerminal.runnerProcessInstanceId === owner.runnerProcessInstanceId
|
|
171
184
|
&& typeof status.error === "string"
|
|
172
|
-
&& status.error) return
|
|
185
|
+
&& status.error) return { state: "releasable", reason: "run failed before child startup completed" };
|
|
173
186
|
const proof = readProcessTerminal(owner.asyncDir, {
|
|
174
187
|
runId: owner.runId,
|
|
175
188
|
runnerProcessInstanceId: owner.runnerProcessInstanceId,
|
|
176
189
|
});
|
|
177
190
|
return proof?.state === "observed"
|
|
178
191
|
&& proof.runId === owner.runId
|
|
179
|
-
&& proof.runnerProcessInstanceId === owner.runnerProcessInstanceId
|
|
192
|
+
&& proof.runnerProcessInstanceId === owner.runnerProcessInstanceId
|
|
193
|
+
? { state: "releasable", reason: "matching observed process-terminal proof is present" }
|
|
194
|
+
: { state: "retained", reason: `process-terminal proof is ${proof?.state ?? "missing"}` };
|
|
180
195
|
}
|
|
181
196
|
|
|
182
|
-
function
|
|
183
|
-
if (status
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
197
|
+
function workflowReleaseVerdict(owner: ActiveAsyncCapacityOwnerV1, status: AsyncStatus | null, liveWorkflowRunIds: ReadonlySet<string>): ActiveAsyncCapacityReleaseVerdict {
|
|
198
|
+
if (!status) return { state: "retained", reason: "status file is missing or unreadable" };
|
|
199
|
+
if (status.sessionId !== owner.ownerSessionId) return { state: "retained", reason: `status session ${status.sessionId ?? "unknown"} does not match owner session ${owner.ownerSessionId}` };
|
|
200
|
+
if (status.runId !== owner.runId) return { state: "retained", reason: `status run ${status.runId} does not match owner run ${owner.runId}` };
|
|
201
|
+
if (status.mode !== "workflow") return { state: "retained", reason: `status mode is ${status.mode}, not workflow` };
|
|
202
|
+
if (!terminalState(status.state)) return { state: "retained", reason: `workflow is still ${status.state}` };
|
|
203
|
+
if (liveWorkflowRunIds.has(owner.runId)) return { state: "retained", reason: "workflow controller is still live" };
|
|
188
204
|
for (const step of status.steps ?? []) {
|
|
189
|
-
|
|
190
|
-
if (
|
|
205
|
+
const label = step.workflowKey ?? step.agent;
|
|
206
|
+
if (step.status === "pending" || step.status === "running" || step.status === "paused") return { state: "retained", reason: `workflow child ${label} is still ${step.status}` };
|
|
207
|
+
if (typeof step.async !== "boolean") return { state: "retained", reason: `workflow child ${label} is missing async classification` };
|
|
191
208
|
if (!step.async) continue;
|
|
192
|
-
if (!step.runId) return
|
|
209
|
+
if (!step.runId) return { state: "retained", reason: `async workflow child ${label} is missing run id` };
|
|
193
210
|
const childDir = path.join(path.dirname(owner.asyncDir), step.runId);
|
|
194
|
-
if (!fs.existsSync(childDir)) return
|
|
211
|
+
if (!fs.existsSync(childDir)) return { state: "retained", reason: `async workflow child ${label} directory is missing` };
|
|
195
212
|
const childStatus = readStatus(childDir);
|
|
196
|
-
if (!childStatus
|
|
213
|
+
if (!childStatus) return { state: "retained", reason: `async workflow child ${label} status is missing or unreadable` };
|
|
214
|
+
if (!terminalState(childStatus.state)) return { state: "retained", reason: `async workflow child ${label} is still ${childStatus.state}` };
|
|
215
|
+
if (!childStatus.processTerminal?.runnerProcessInstanceId) return { state: "retained", reason: `async workflow child ${label} has no runner process identity` };
|
|
197
216
|
const proof = readProcessTerminal(childDir, {
|
|
198
217
|
runId: step.runId,
|
|
199
218
|
runnerProcessInstanceId: childStatus.processTerminal.runnerProcessInstanceId,
|
|
200
219
|
});
|
|
201
|
-
if (proof?.state !== "observed" || proof.runId !== step.runId) return
|
|
220
|
+
if (proof?.state !== "observed" || proof.runId !== step.runId) return { state: "retained", reason: `async workflow child ${label} process-terminal proof is ${proof?.state ?? "missing"}` };
|
|
202
221
|
}
|
|
203
|
-
return
|
|
222
|
+
return { state: "releasable", reason: "workflow is terminal, controller is gone, and async children have observed proof" };
|
|
204
223
|
}
|
|
205
224
|
|
|
206
|
-
function
|
|
225
|
+
function ownerReleaseVerdict(owner: ActiveAsyncCapacityOwnerV1, liveWorkflowRunIds: ReadonlySet<string>): ActiveAsyncCapacityReleaseVerdict {
|
|
207
226
|
const status = readStatus(owner.asyncDir);
|
|
208
|
-
if (!status) return false;
|
|
209
227
|
return owner.kind === "runner"
|
|
210
|
-
?
|
|
211
|
-
:
|
|
228
|
+
? runnerReleaseVerdict(owner, status)
|
|
229
|
+
: workflowReleaseVerdict(owner, status, liveWorkflowRunIds);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function capacitySessionDirs(rootDir: string, sessionId?: string): string[] {
|
|
233
|
+
if (sessionId) return [sessionDir(sessionId, rootDir)];
|
|
234
|
+
try {
|
|
235
|
+
return fs.readdirSync(rootDir, { withFileTypes: true })
|
|
236
|
+
.filter((entry) => entry.isDirectory())
|
|
237
|
+
.map((entry) => path.join(rootDir, entry.name));
|
|
238
|
+
} catch (error) {
|
|
239
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function inspectActiveAsyncCapacityOwner(
|
|
245
|
+
input: { runId: string; sessionId?: string; asyncDir?: string },
|
|
246
|
+
options: CapacityOptions & { liveWorkflowRunIds?: ReadonlySet<string> } = {},
|
|
247
|
+
): ActiveAsyncCapacityInspection {
|
|
248
|
+
const rootDir = options.rootDir ?? ACTIVE_ASYNC_CAPACITY_DIR;
|
|
249
|
+
const liveWorkflowRunIds = options.liveWorkflowRunIds ?? new Set<string>();
|
|
250
|
+
for (const poolDir of capacitySessionDirs(rootDir, input.sessionId)) {
|
|
251
|
+
for (const dir of occupiedSlots(poolDir)) {
|
|
252
|
+
const owner = readOwner(dir);
|
|
253
|
+
if (!owner) continue;
|
|
254
|
+
const sameRun = owner.runId === input.runId || (input.asyncDir !== undefined && path.resolve(owner.asyncDir) === path.resolve(input.asyncDir));
|
|
255
|
+
const sourceRun = owner.sourceRunId === input.runId;
|
|
256
|
+
if (!sameRun && !sourceRun) continue;
|
|
257
|
+
if (sourceRun && !sameRun) {
|
|
258
|
+
return {
|
|
259
|
+
owner,
|
|
260
|
+
relation: "source",
|
|
261
|
+
slotDir: dir,
|
|
262
|
+
release: { state: "not-owned", reason: `slot was transferred to ${owner.runId}` },
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
return { owner, relation: "current", slotDir: dir, release: ownerReleaseVerdict(owner, liveWorkflowRunIds) };
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return { relation: "none", release: { state: "not-owned", reason: "no active-capacity slot records this run" } };
|
|
212
269
|
}
|
|
213
270
|
|
|
214
271
|
export function reconcileActiveAsyncCapacity(
|
|
@@ -225,7 +282,7 @@ export function reconcileActiveAsyncCapacity(
|
|
|
225
282
|
|| owner.ownerSessionId !== sessionId
|
|
226
283
|
|| owner.ownerSessionKey !== activeAsyncCapacitySessionKey(sessionId)
|
|
227
284
|
|| path.basename(dir) !== `slot-${owner.slot}`
|
|
228
|
-
||
|
|
285
|
+
|| ownerReleaseVerdict(owner, liveWorkflowRunIds).state !== "releasable") continue;
|
|
229
286
|
removeOwnedSlot(dir, owner, options);
|
|
230
287
|
}
|
|
231
288
|
return snapshotFor(sessionId, limit, rootDir);
|