pi-subagents 0.41.0 → 0.42.1
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/README.md +54 -1600
- package/inspector-runner.mjs +11 -0
- package/package.json +3 -3
- package/skills/pi-subagents/SKILL.md +5 -3
- package/skills/pi-subagents/references/constraints-and-recipes.md +5 -6
- package/skills/pi-subagents/references/execution-controls.md +6 -5
- package/skills/pi-subagents/references/prompting-and-roles.md +5 -3
- package/src/extension/schemas.ts +4 -4
- package/src/extension/tool-description.ts +4 -4
- package/src/inspectors/herdr/actions.ts +2 -2
- package/src/missions/actions.ts +7 -5
- package/src/runs/background/async-execution.ts +12 -1
- package/src/runs/background/async-job-tracker.ts +17 -2
- package/src/runs/foreground/chain-clarify.ts +13 -16
- package/src/runs/foreground/execution.ts +19 -7
- package/src/runs/foreground/subagent-executor.ts +20 -9
- package/src/runs/shared/acceptance.ts +2 -2
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +2 -0
- package/src/runs/shared/pi-args.ts +465 -116
- package/src/shared/artifacts.ts +125 -0
- package/src/shared/types.ts +1 -1
- package/src/slash/slash-commands.ts +3 -3
- package/src/tui/fleet.ts +1 -1
- package/src/tui/render.ts +78 -43
- package/src/types/pi-runtime-compat.d.ts +0 -6
- package/src/workflows/chat-progress.ts +2 -2
- package/src/workflows/scripted-workflow.ts +6 -4
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createJiti } from "jiti";
|
|
2
|
+
|
|
3
|
+
const jiti = createJiti(import.meta.url);
|
|
4
|
+
const { runInspector } = await jiti.import("./src/inspectors/herdr/inspector-runner.ts");
|
|
5
|
+
|
|
6
|
+
try {
|
|
7
|
+
runInspector();
|
|
8
|
+
} catch (cause) {
|
|
9
|
+
process.stderr.write(`Herdr inspector failed: ${cause instanceof Error ? cause.message : String(cause)}\n`);
|
|
10
|
+
process.exitCode = 1;
|
|
11
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-subagents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.42.1",
|
|
4
4
|
"description": "Pi extension for single-agent delegation and scripted multi-agent workflows",
|
|
5
5
|
"author": "Nico Bailon",
|
|
6
6
|
"license": "MIT",
|
|
@@ -94,9 +94,9 @@
|
|
|
94
94
|
"devDependencies": {
|
|
95
95
|
"@earendil-works/pi-agent-core": "0.81.0",
|
|
96
96
|
"@earendil-works/pi-ai": "0.81.0",
|
|
97
|
-
"@earendil-works/pi-coding-agent": "0.81.0",
|
|
98
97
|
"@earendil-works/pi-tui": "0.81.0",
|
|
99
98
|
"@types/node": "24.13.3",
|
|
100
|
-
"typescript": "5.9.3"
|
|
99
|
+
"typescript": "5.9.3",
|
|
100
|
+
"@earendil-works/pi-coding-agent": "file:./test/fixtures/pi-coding-agent-shim"
|
|
101
101
|
}
|
|
102
102
|
}
|
|
@@ -31,9 +31,11 @@ For broad or uncertain requests, read more than one reference. For complex work,
|
|
|
31
31
|
|
|
32
32
|
- Keep the parent as orchestrator and final decision-maker.
|
|
33
33
|
- Use one writer per cwd/worktree unless isolated worktrees are intentional.
|
|
34
|
-
- For
|
|
34
|
+
- For cross-codebase work, record the target repo, explicit `cwd`, authority boundary, and expected output before launch. Do not assume the parent session cwd is the child repo.
|
|
35
|
+
- For parallel fanout, compare child prompts before launch. Do not send clone prompts with only issue numbers, titles, or broad file globs swapped; each child needs a lane-specific task, source seam, prior evidence, and decision that remains distinct without the item number. Launch that fanout as one async `workflowScript` with stable keys and aggregate output unless there is truly only one child.
|
|
35
36
|
- Prefer fresh-context review/validation fanout, then synthesize and apply fixes in the parent.
|
|
36
|
-
- Use async/background
|
|
37
|
+
- Use async/background by default when work can proceed independently; do not poll just to wait. For adaptive gates, branch in `workflowScript`. Approval controls remain available only for already-running durable legacy chains.
|
|
37
38
|
- Preserve capability ceilings, including child tool restrictions and session-scoped allowed-agent restrictions.
|
|
38
|
-
- Escalate unresolved product, architecture, or safety decisions upward instead of letting a child decide silently.
|
|
39
|
+
- Escalate unresolved product, architecture, authority, release, merge, or safety decisions upward instead of letting a child decide silently.
|
|
40
|
+
- Treat receipts, CI, review bots, and external-run records as evidence, not authority to merge, close, comment, publish, or release.
|
|
39
41
|
- As a conservative orchestration policy, do not pass `turnBudget`, a hard `toolBudget`, or a tight `usageBudget` to mutation-capable workers. The default tool budget blocks read/search tools rather than mutation tools, and reported usage has no reservation model. If a worker is interrupted after a tool call starts, checkpoint after the current tool returns with changed files, build/test state, and commit or PR state.
|
|
@@ -44,7 +44,7 @@ If config or `PI_SUBAGENT_WAIT_TOOL_ENABLED` disables blocking behavior, direct
|
|
|
44
44
|
|
|
45
45
|
### Keep writes single-threaded by default
|
|
46
46
|
|
|
47
|
-
A strong pattern is one main decision-maker plus advisory/research/review/validation subagents around it. Use `oracle` for advice and `worker` for the actual write path. Parallelize reading, review, validation, and synthesis support, not normal writes, unless you deliberately isolate writers with worktrees. A child that writes should report what changed, what was left undone, commands run with exit codes, validation evidence, surprises, and any decisions that need parent approval.
|
|
47
|
+
A strong pattern is one main decision-maker plus advisory/research/review/validation subagents around it. Use `oracle` for advice and `worker` for the actual write path. Parallelize reading, review, validation, and synthesis support, not normal writes, unless you deliberately isolate writers with worktrees. Across repositories, each repo/worktree still gets at most one writer, with explicit `cwd` and authority in the child prompt. A child that writes should report what changed, what was left undone, commands run with exit codes, validation evidence, surprises, and any decisions that need parent approval.
|
|
48
48
|
|
|
49
49
|
### Use fork for branched advisory or execution threads
|
|
50
50
|
|
|
@@ -61,8 +61,7 @@ Give subagents specific tasks rather than vague mandates.
|
|
|
61
61
|
|
|
62
62
|
### Escalate decisions upward
|
|
63
63
|
|
|
64
|
-
If a subagent encounters an unapproved product, architecture, or
|
|
65
|
-
it should use `contact_supervisor` and wait for the reply instead of deciding alone. Generic `intercom` is a fallback only when the bridge-provided supervisor tool is unavailable.
|
|
64
|
+
If a subagent encounters an unapproved product, architecture, scope, merge, release, credential, or authority choice, it should use `contact_supervisor` and wait for the reply instead of deciding alone. Generic `intercom` is a fallback only when the bridge-provided supervisor tool is unavailable. External checks, receipts, and review bots provide evidence only; they do not grant authority.
|
|
66
65
|
|
|
67
66
|
### Intervene only on clear control signals
|
|
68
67
|
|
|
@@ -91,12 +90,12 @@ Fable mode is the default orchestration posture for complex work. It is not a se
|
|
|
91
90
|
Run the work through seven gated phases:
|
|
92
91
|
|
|
93
92
|
1. **Understand** — use `scout` or `context-builder` fanout for breadth, but the parent personally reads the load-bearing files and lets direct source reading decide disagreements. Gate: the parent can quote the exact code or behavior being changed and knows the repo's verification harness.
|
|
94
|
-
2. **Decide** — separate user-owned decisions from implementation judgments. Use `interview` for product, naming, cost, taste, or
|
|
93
|
+
2. **Decide** — separate user-owned decisions from implementation judgments. Use `interview` for product, naming, cost, taste, risk, release, merge, or authority decisions; decide routine engineering details in the parent and state them. Gate: every user-owned decision needed for design is answered or recorded as a blocked follow-up.
|
|
95
94
|
3. **Design** — use `planner`, `context-builder`, or read-only design/review children for parallel perspectives. Before parallel workstreams, write seam contracts: ownership boundaries, composition points, assumptions, and validation handoffs. Gate: one parent-synthesized plan and written seams for parallel work.
|
|
96
|
-
4. **Implement** — capture a baseline first, then launch one async `worker` as the sole writer for the active worktree unless isolated worktrees were intentionally requested. Break large work into serial milestones instead of concurrent writes. Gate: build/typecheck is green and every output or diff delta is characterized as intended or fixed.
|
|
95
|
+
4. **Implement** — capture a baseline first, then launch one async `worker` as the sole writer for the active worktree unless isolated worktrees were intentionally requested. For cross-codebase work, launch separate async workers only when each has its own repo/worktree, explicit `cwd`, and non-overlapping authority. Break large work into serial milestones instead of concurrent writes. Gate: build/typecheck is green and every output or diff delta is characterized as intended or fixed.
|
|
97
96
|
5. **Verify** — climb the spend ladder: static checks, free end-to-end/dry-run, cheapest live probe, targeted changed-path live test, then full realistic run when warranted. Observe the artifact itself, not only exit codes or scores, and confirm the changed code actually executed. Gate: the highest necessary rung has directly observed evidence matching intent.
|
|
98
97
|
6. **Iterate** — when a gate or reviewer finds a defect, the parent names the failure class, searches for siblings, synthesizes fixes, and sends exactly one fix worker for accepted changes. For LLM judges, gates, or detectors, trigger on concrete findings rather than scores, record pass/violations/error verdicts, cache nondeterministic verdicts by input hash, budget enough output tokens, and sanitize judge text before reusing it downstream. Gate: the class is fixed or explicitly bounded, and recurrence detection exists when feasible.
|
|
99
|
-
7. **Ship** — run adversarial fresh-context review/validation outside the implementation path, disposition every finding, rerun affected gates, then have the parent inspect the final diff. Commit, push, release, or open PRs only inside user-approved boundaries. Gate: findings are dispositioned, gates re-pass, and the final summary names evidence, artifacts, residual risks, and output paths.
|
|
98
|
+
7. **Ship** — run adversarial fresh-context review/validation outside the implementation path, disposition every finding, rerun affected gates, then have the parent inspect the final diff. Commit, push, comment, close, merge, release, or open PRs only inside user-approved boundaries for that repo. Gate: findings are dispositioned, gates re-pass, and the final summary names evidence, artifacts, residual risks, and output paths.
|
|
100
99
|
|
|
101
100
|
### Clarify → Plan → Implement → Review (self-orchestrated workflow)
|
|
102
101
|
|
|
@@ -56,7 +56,7 @@ its resolved launch context as `[fresh]` or `[fork]`. Aggregate headers show
|
|
|
56
56
|
|
|
57
57
|
### Scripted workflows
|
|
58
58
|
|
|
59
|
-
`workflowScript` is the sole public orchestration surface. Use `runs.run(key, { agent, task, ... })` for one child, `runs.all([...])` for parallel children, and ordinary JavaScript for sequence, branching, filtering, retries, and aggregation. Prefer a single scripted workflow whenever the parent is starting a coordinated wave, such as multiple reviews, review plus gate monitor, worker then monitor setup, or a fanout that the parent will consume together. Use a direct `{ agent, task }` call only for one isolated child with no sibling work or aggregate handoff.
|
|
59
|
+
`workflowScript` is the sole public orchestration surface. Use `runs.run(key, { agent, task, ... })` for one child, `runs.all([...])` for parallel children, and ordinary JavaScript for sequence, branching, filtering, retries, and aggregation. Prefer a single scripted workflow whenever the parent is starting a coordinated wave, such as multiple reviews, review plus gate monitor, worker then monitor setup, cross-repo prep lanes, or a fanout that the parent will consume together. Use a direct `{ agent, task }` call only for one isolated child with no sibling work or aggregate handoff.
|
|
60
60
|
|
|
61
61
|
```js
|
|
62
62
|
subagent({
|
|
@@ -93,7 +93,7 @@ subagent({
|
|
|
93
93
|
})
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
-
File-only output mode works for async single runs and workflowScript child launches. Use distinct absolute or durable output paths when later script steps need stable references.
|
|
96
|
+
File-only output mode works for async single runs and workflowScript child launches. Use distinct absolute or durable output paths when later script steps need stable references. For cross-codebase waves, include the repo slug or lane key in each output path so reports from different repositories cannot collide.
|
|
97
97
|
|
|
98
98
|
For review fanout where the parent continues a local audit:
|
|
99
99
|
|
|
@@ -298,10 +298,11 @@ After compaction, restart, or confusing history, recover from durable state firs
|
|
|
298
298
|
|
|
299
299
|
Routing rule:
|
|
300
300
|
- Same project: ordinary mission-backed subagents.
|
|
301
|
-
- Different project, small/bounded task: ordinary subagent with explicit `cwd
|
|
302
|
-
-
|
|
301
|
+
- Different project, small/bounded task: ordinary async subagent with explicit `cwd`, an authority boundary, and durable output.
|
|
302
|
+
- Several projects with independent work: one async `workflowScript` whose child keys include repo slugs and whose child calls set explicit `cwd`; keep publication and merge decisions serial per repo.
|
|
303
|
+
- Different project, substantial or long-running work: open a project-owned Herdr pane rooted there when a separate visible project session is useful, then give that project Pi session a narrow mission/result contract. Do not model it as ordinary child nesting, and do not expect existing headless runs to move into the pane.
|
|
303
304
|
|
|
304
|
-
Project panes run a separate Pi session from the target directory. Subagents launched inside that pane use that project's config, agents, skills, files, git state, and mission records. The pane binding lives under `<projectRoot>/.pi-subagents/project-panes/herdr.json`.
|
|
305
|
+
Project panes run a separate Pi session from the target directory. Subagents launched inside that pane use that project's config, agents, skills, files, git state, and mission records. The pane binding lives under `<projectRoot>/.pi-subagents/project-panes/herdr.json`. For ordinary headless delegation to another repo, prefer explicit `cwd` first; reserve project panes for visible or persistent project ownership.
|
|
305
306
|
|
|
306
307
|
```typescript
|
|
307
308
|
subagent({ action: "mission.create", mission: { title: "Ship auth refresh", goal: "Implement and validate refresh handling" } })
|
|
@@ -50,7 +50,7 @@ Packaged prompt shortcuts are also available for repeatable workflows. Treat the
|
|
|
50
50
|
|
|
51
51
|
## Applying Prompt Techniques Without Slash Commands
|
|
52
52
|
|
|
53
|
-
The prompt templates in `prompts/` encode workflows the parent agent can run on demand. If the user provides a URL, issue, PR, plan, local file, screenshot, or freeform target, treat that target as the primary scope: read or fetch it before launching children, then include it explicitly in every child task. Do not depend on the parent conversation history when the recipe calls for fresh context.
|
|
53
|
+
The prompt templates in `prompts/` encode workflows the parent agent can run on demand. If the user provides a URL, issue, PR, plan, local file, screenshot, or freeform target, treat that target as the primary scope: read or fetch it before launching children, then include it explicitly in every child task. For targets outside the parent cwd, include the exact repository, explicit `cwd`, authority boundary, and expected output path in each child task. Do not depend on the parent conversation history when the recipe calls for fresh context.
|
|
54
54
|
|
|
55
55
|
### Parallel review technique
|
|
56
56
|
|
|
@@ -212,12 +212,14 @@ Builtin role agents inherit the current Pi default model unless you override the
|
|
|
212
212
|
|
|
213
213
|
A strong subagent prompt usually includes:
|
|
214
214
|
- **Goal**: the concrete outcome the child should produce.
|
|
215
|
+
- **Target**: repository, explicit `cwd`, branch/ref/head, and source seam when the target is not the parent cwd.
|
|
216
|
+
- **Authority boundary**: whether the child may read, edit, commit, push, comment, close, merge, publish, or release. Omit or forbid actions that are not approved.
|
|
215
217
|
- **Context/evidence**: relevant plan paths, files, diffs, decisions, or user constraints already approved.
|
|
216
218
|
- **Success criteria**: what must be true before the child can finish.
|
|
217
219
|
- **Hard constraints**: true invariants only, such as no edits for review-only tasks, one writer thread, child must not run subagents unless it is an explicitly assigned `tools: subagent` fanout child, or escalation for unapproved decisions.
|
|
218
220
|
- **Validation**: targeted checks to run, or the next-best check when validation is impossible.
|
|
219
|
-
- **Output**: the expected summary shape, artifact path, or finding format.
|
|
220
|
-
- **Stop rules**: when to ask via `intercom`, when to stop after enough evidence, and when not to keep searching.
|
|
221
|
+
- **Output**: the expected summary shape, artifact path, or finding format. Use repo-qualified durable output paths for cross-codebase waves.
|
|
222
|
+
- **Stop rules**: when to ask via `intercom` or `contact_supervisor`, when to stop after enough evidence, and when not to keep searching.
|
|
221
223
|
|
|
222
224
|
Avoid carrying over old prompt habits that over-specify every step. Use `must`, `always`, and `never` for real invariants; for judgment calls, give decision rules. For example, tell a reviewer to inspect the staged diff directly and report only evidence-backed findings, rather than prescribing every file or command. Tell a researcher the retrieval budget: start with broad targeted searches, fetch only the strongest sources, search again only when a required fact is missing, then stop.
|
|
223
225
|
|
package/src/extension/schemas.ts
CHANGED
|
@@ -313,8 +313,8 @@ const SubagentParamsSchema = Type.Object({
|
|
|
313
313
|
],
|
|
314
314
|
description: "Agent/chain config for create/update. Object or JSON string; presence of steps creates a chain."
|
|
315
315
|
})),
|
|
316
|
-
workflowScript: Type.Optional(Type.String({ minLength: 1, description: "Trusted inline JavaScript orchestration. Starts asynchronously by default; pass async:false for a small foreground run. Use await runs.run(key, {agent, task, worktree?}), runs.all([...]), runs.status(id), runs.ref(s), emit(value), console, and return. Set worktree:true at workflow or child level for a separate managed worktree per child; child fields override workflow defaults. runs.run accepts one child only. No filesystem, shell, Pi tools, or host globals." })),
|
|
317
|
-
chatProgress: Type.Optional(Type.String({ enum: ["auto", "off", "
|
|
316
|
+
workflowScript: Type.Optional(Type.String({ minLength: 1, description: "Trusted inline JavaScript orchestration. Starts asynchronously by default; pass async:false for a small foreground run. Use await runs.run(key, {agent, task, worktree?}), runs.all([...]), runs.status(id), runs.ref(s), emit(value), console, and return. Use ordinary JavaScript loops, branches, awaits, and arrays to mix sequential and parallel phases dynamically. Set worktree:true at workflow or child level for a separate managed worktree per child; child fields override workflow defaults. runs.run accepts one child only. No filesystem, shell, Pi tools, or host globals." })),
|
|
317
|
+
chatProgress: Type.Optional(Type.String({ enum: ["auto", "off", "live-card"], description: "WorkflowScript chat progress projection. auto shows a live in-chat card only for watched foreground workflows in the same Git repository; it is off otherwise." })),
|
|
318
318
|
worktree: Type.Optional(Type.Boolean({ description: "Managed child isolation. true gives a direct single child or each workflow child a separate git worktree; an individual runs.run/runs.all item can override a workflow default with worktree:false." })),
|
|
319
319
|
step: Type.Optional(Type.Unsafe({ ...ChainItem, description: "One chain step for action='append-step' only. Not an execution mode." })),
|
|
320
320
|
context: Type.Optional(Type.String({
|
|
@@ -322,8 +322,8 @@ const SubagentParamsSchema = Type.Object({
|
|
|
322
322
|
description: "'fresh' or 'fork' to branch from parent session. Explicit context overrides every child in the invocation. If omitted, each requested agent uses its own defaultContext; agents without defaultContext: 'fork' run fresh.",
|
|
323
323
|
})),
|
|
324
324
|
async: Type.Optional(Type.Boolean({ description: "Run in background (default: false, or per config)" })),
|
|
325
|
-
timeoutMs: Type.Optional(Type.Integer({ minimum: 1, description: "
|
|
326
|
-
maxRuntimeMs: Type.Optional(Type.Integer({ minimum: 1, description: "Alias timeoutMs for foreground and async/background runs
|
|
325
|
+
timeoutMs: Type.Optional(Type.Integer({ minimum: 1, description: "Optional timeout for foreground and async/background runs. Foreground workflows default to 30m; async workflows have no default timeout. Alias maxRuntimeMs." })),
|
|
326
|
+
maxRuntimeMs: Type.Optional(Type.Integer({ minimum: 1, description: "Alias timeoutMs for foreground and async/background runs. Foreground workflows default to 30m; async workflows have no default timeout." })),
|
|
327
327
|
turnBudget: Type.Optional(TurnBudgetOverride),
|
|
328
328
|
toolBudget: Type.Optional(ToolBudgetOverride),
|
|
329
329
|
usageBudget: Type.Optional(UsageBudgetOverride),
|
|
@@ -19,10 +19,10 @@ export const FULL_SUBAGENT_TOOL_DESCRIPTION = `Delegate one child with { agent,
|
|
|
19
19
|
EXECUTION (use exactly one mode):
|
|
20
20
|
• Before executing, use { action: "list" } and run only executable/non-disabled configured agents.
|
|
21
21
|
• SINGLE: { agent, task? } launches one child. Omit task for a self-contained agent.
|
|
22
|
-
• SCRIPTED WORKFLOW: { workflowScript: "const scan = await runs.run('scan', {agent:'agent-a', task:'...'}); return scan.output" }. Use stable-key runs.run for one child and runs.all for parallel children; ordinary JavaScript provides sequence, branching, filtering, retries, and aggregation. 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,
|
|
22
|
+
• SCRIPTED WORKFLOW: { workflowScript: "const scan = await runs.run('scan', {agent:'agent-a', task:'...'}); return scan.output" }. Use stable-key runs.run for one child and runs.all for parallel children; ordinary JavaScript provides sequence, branching, filtering, retries, and aggregation. 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, or live-card to control that projection. Workflow-level child controls default onto each runs.run launch, and explicit child fields override them. Direct single-child calls also support worktree:true; use workflowScript only when coordination is needed. For repository mutation lanes, set worktree:true on a direct single child, workflow, or individual runs.run/runs.all item for managed isolation instead of manual Git worktrees; 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, emit, console, and standard JavaScript only. Scripts cannot access filesystem, shell, arbitrary Pi tools, or host globals.
|
|
23
23
|
• Sequential replacement: { 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" }
|
|
24
24
|
• Parallel replacement: { 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}" }
|
|
25
|
-
• Optional context is "fresh" or "fork". timeoutMs/maxRuntimeMs apply to foreground and async runs. Omit acceptance for reviewer/read-only calls; evidence levels end at verified, and acceptance.review.required requests independent writer review.
|
|
25
|
+
• Optional context is "fresh" or "fork". timeoutMs/maxRuntimeMs apply to foreground and async runs; foreground workflows default to 30 minutes and async workflows have no default timeout. Omit acceptance for reviewer/read-only calls; evidence levels end at verified, and acceptance.review.required requests independent writer review.
|
|
26
26
|
• Durable mission attachment is automatic by default. Use missionId to attach an existing mission, mission:{...} to override auto-create, or mission:false for ephemeral work.
|
|
27
27
|
|
|
28
28
|
MANAGEMENT / CONTROL (use action; omit execution fields):
|
|
@@ -38,9 +38,9 @@ export const COMPACT_SUBAGENT_TOOL_DESCRIPTION = `Delegate one child with { agen
|
|
|
38
38
|
|
|
39
39
|
EXECUTE:
|
|
40
40
|
• Call { action:"list" } first and use only executable/non-disabled agents.
|
|
41
|
-
• SINGLE {agent, task?}; SCRIPT {workflowScript:"..."} with stable-key runs.run for one child and runs.all for parallel work. Use JavaScript for sequence, branching, retries, and aggregation. For repository mutation lanes, use worktree:true on a direct single child or runs.run/runs.all item for managed isolation instead of manual Git worktrees. 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
|
|
41
|
+
• SINGLE {agent, task?}; SCRIPT {workflowScript:"..."} with stable-key runs.run for one child and runs.all for parallel work. Use JavaScript for sequence, branching, retries, and aggregation. For repository mutation lanes, use worktree:true on a direct single child or runs.run/runs.all item for managed isolation instead of manual Git worktrees. 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.
|
|
42
42
|
• 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]"}
|
|
43
|
-
• context can be fresh or fork. timeoutMs/maxRuntimeMs apply to foreground and async runs. Omit acceptance for reviewer/read-only calls.
|
|
43
|
+
• context can be fresh or fork. timeoutMs/maxRuntimeMs apply to foreground and async runs; foreground workflows default to 30 minutes and async workflows have no default timeout. Omit acceptance for reviewer/read-only calls.
|
|
44
44
|
|
|
45
45
|
MANAGE / CONTROL:
|
|
46
46
|
• Use action without execution fields for list/get/models/authoring, mission, watchdog, status, interrupt, stop, resume, steer, scheduling, diagnostics, and other management actions.
|
|
@@ -90,7 +90,7 @@ function shellQuote(value: string): string {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
function inspectorCommand(input: { runnerPath: string; asyncDir: string; runId: string; index?: number; missionPath?: string; allowSteer: boolean; allowStop: boolean }): string {
|
|
93
|
-
const args = [process.execPath,
|
|
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)];
|
|
94
94
|
if (input.index !== undefined) args.push("--index", String(input.index));
|
|
95
95
|
if (input.missionPath) args.push("--mission-path", input.missionPath);
|
|
96
96
|
return args.map(shellQuote).join(" ");
|
|
@@ -195,7 +195,7 @@ export async function handleHerdrInspectorAction(action: HerdrInspectorAction, p
|
|
|
195
195
|
const paneId = extractPaneId(split.data);
|
|
196
196
|
if (!paneId) return result("Herdr inspector error (PANE_GONE): pane split returned no pane id.", true);
|
|
197
197
|
const mission = missionForRun(target.asyncDir, deps.cwd, deps.missions, target.runId);
|
|
198
|
-
const runnerPath = deps.runnerPath ?? fileURLToPath(new URL("
|
|
198
|
+
const runnerPath = deps.runnerPath ?? fileURLToPath(new URL("../../../inspector-runner.mjs", import.meta.url));
|
|
199
199
|
const command = inspectorCommand({
|
|
200
200
|
runnerPath,
|
|
201
201
|
asyncDir: target.asyncDir,
|
package/src/missions/actions.ts
CHANGED
|
@@ -101,17 +101,19 @@ export function validateMissionLaunch(value: unknown): MissionLaunchInput {
|
|
|
101
101
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("mission must be an object");
|
|
102
102
|
const input = value as Record<string, unknown>;
|
|
103
103
|
for (const key of Object.keys(input)) {
|
|
104
|
-
if (key !== "title" && key !== "goal" && key !== "labels") throw new Error(`mission.${key} is unknown`);
|
|
104
|
+
if (key !== "title" && key !== "summary" && key !== "goal" && key !== "labels") throw new Error(`mission.${key} is unknown`);
|
|
105
105
|
}
|
|
106
|
-
if (
|
|
106
|
+
if (input.title !== undefined && input.summary !== undefined) throw new Error("mission.title and mission.summary cannot both be set");
|
|
107
|
+
const title = input.title ?? input.summary;
|
|
108
|
+
if (typeof title !== "string" || !title.trim()) throw new Error("mission.title or mission.summary must be a non-empty string");
|
|
107
109
|
if (input.goal !== undefined && (typeof input.goal !== "string" || !input.goal.trim())) throw new Error("mission.goal must be a non-empty string");
|
|
108
110
|
if (input.labels !== undefined && (!Array.isArray(input.labels) || input.labels.some((label) => typeof label !== "string" || !label.trim()))) {
|
|
109
111
|
throw new Error("mission.labels must contain only non-empty strings");
|
|
110
112
|
}
|
|
111
113
|
return {
|
|
112
|
-
title:
|
|
113
|
-
...(
|
|
114
|
-
...(
|
|
114
|
+
title: title.trim(),
|
|
115
|
+
...(input.goal !== undefined ? { goal: input.goal.trim() } : {}),
|
|
116
|
+
...(input.labels !== undefined ? { labels: input.labels.map((label) => label.trim()) } : {}),
|
|
115
117
|
};
|
|
116
118
|
}
|
|
117
119
|
|
|
@@ -28,7 +28,7 @@ import { resolveExpectedWorktreeAgentCwd } from "../shared/worktree.ts";
|
|
|
28
28
|
import { buildWorkflowGraphSnapshot } from "../shared/workflow-graph.ts";
|
|
29
29
|
import { ChainOutputValidationError, validateChainOutputBindings } from "../shared/chain-outputs.ts";
|
|
30
30
|
import { createStructuredOutputRuntime } from "../shared/structured-output.ts";
|
|
31
|
-
import { resolveEffectiveAcceptance } from "../shared/acceptance.ts";
|
|
31
|
+
import { resolveEffectiveAcceptance, validateAcceptanceInput, validateExecutionAcceptance } from "../shared/acceptance.ts";
|
|
32
32
|
import {
|
|
33
33
|
type AcceptanceInput,
|
|
34
34
|
type AgentContract,
|
|
@@ -943,6 +943,15 @@ export function executeAsyncChain(
|
|
|
943
943
|
nestedRoute,
|
|
944
944
|
} = params;
|
|
945
945
|
const resultMode = params.resultMode ?? "chain";
|
|
946
|
+
const acceptanceErrors = validateExecutionAcceptance({
|
|
947
|
+
chain: chain.map((step) => {
|
|
948
|
+
if (isCheckpointStep(step)) return {};
|
|
949
|
+
if (isParallelStep(step)) return { parallel: step.parallel };
|
|
950
|
+
if (isDynamicParallelStep(step)) return { acceptance: step.acceptance, parallel: step.parallel };
|
|
951
|
+
return { acceptance: step.acceptance };
|
|
952
|
+
}),
|
|
953
|
+
});
|
|
954
|
+
if (acceptanceErrors.length > 0) return formatAsyncStartError(resultMode, acceptanceErrors.join(" "));
|
|
946
955
|
const capabilityCeiling = params.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(ctx.currentSessionId);
|
|
947
956
|
const inheritedNestedRoute = resolveInheritedNestedRouteFromEnv();
|
|
948
957
|
const nestedAddress = inheritedNestedRoute ? resolveNestedParentAddressFromEnv() : undefined;
|
|
@@ -1209,6 +1218,8 @@ export function executeAsyncSingle(
|
|
|
1209
1218
|
nestedRoute,
|
|
1210
1219
|
} = params;
|
|
1211
1220
|
const task = params.task ?? "";
|
|
1221
|
+
const acceptanceErrors = validateAcceptanceInput(params.acceptance);
|
|
1222
|
+
if (acceptanceErrors.length > 0) return formatAsyncStartError("single", acceptanceErrors.join(" "));
|
|
1212
1223
|
const externalRunner = agentConfig.runner?.type === "external-cli";
|
|
1213
1224
|
const permissionRules = resolvePermissionRules(ctx.permissions, agentConfig.permissions);
|
|
1214
1225
|
if (externalRunner) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import { renderWidget, widgetRenderKey } from "../../tui/render.ts";
|
|
4
|
+
import { renderWidget, requestWidgetRender, widgetRenderKey } from "../../tui/render.ts";
|
|
5
5
|
import { formatControlNoticeMessage } from "../shared/subagent-control.ts";
|
|
6
6
|
import {
|
|
7
7
|
type AsyncJobState,
|
|
@@ -34,6 +34,7 @@ const CONTROL_EVENT_READ_CHUNK_BYTES = 64 * 1024;
|
|
|
34
34
|
const MAX_CONTROL_EVENT_LINE_BYTES = 1024 * 1024;
|
|
35
35
|
const CONTROL_EVENT_SCAN_WINDOW_BYTES = 2 * 1024 * 1024;
|
|
36
36
|
const MAX_RECENT_FLEET_JOBS = 20;
|
|
37
|
+
const WIDGET_ANIMATION_REFRESH_MS = 500;
|
|
37
38
|
|
|
38
39
|
function rememberFleetJob(state: SubagentState, job: AsyncJobState): void {
|
|
39
40
|
state.fleetJobs ??= new Map();
|
|
@@ -56,9 +57,14 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
56
57
|
const pollIntervalMs = options.pollIntervalMs ?? POLL_INTERVAL_MS;
|
|
57
58
|
const resultsDir = options.resultsDir ?? DIRS.results;
|
|
58
59
|
const steeringNoticeSeen = new Map<string, number>();
|
|
60
|
+
let lastWidgetAnimationAt = 0;
|
|
61
|
+
const requestStatusRender = (ctx: ExtensionContext) => {
|
|
62
|
+
if (requestWidgetRender()) return;
|
|
63
|
+
(ctx.ui as { requestRender?: () => void }).requestRender?.();
|
|
64
|
+
};
|
|
59
65
|
const rerenderWidget = (ctx: ExtensionContext, jobs = Array.from(state.asyncJobs.values())) => {
|
|
60
66
|
renderWidget(ctx, options.widgetEnabled === false ? [] : jobs);
|
|
61
|
-
ctx
|
|
67
|
+
requestStatusRender(ctx);
|
|
62
68
|
};
|
|
63
69
|
const rerenderLastWidget = (jobs = Array.from(state.asyncJobs.values())) => {
|
|
64
70
|
const ctx = state.lastUiContext;
|
|
@@ -74,6 +80,14 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
74
80
|
}
|
|
75
81
|
};
|
|
76
82
|
const refreshWidget = (ctx: ExtensionContext) => rerenderWidget(ctx);
|
|
83
|
+
const hasRunningWidgetJobs = () => options.widgetEnabled !== false && [...state.asyncJobs.values()].some((job) => job.status === "running");
|
|
84
|
+
const refreshWidgetAnimation = () => {
|
|
85
|
+
if (!hasRunningWidgetJobs()) return;
|
|
86
|
+
const now = Date.now();
|
|
87
|
+
if (now - lastWidgetAnimationAt < WIDGET_ANIMATION_REFRESH_MS) return;
|
|
88
|
+
lastWidgetAnimationAt = now;
|
|
89
|
+
requestWidgetRender();
|
|
90
|
+
};
|
|
77
91
|
const restoredControlEventCursor = (asyncDir: string) => {
|
|
78
92
|
try {
|
|
79
93
|
return fs.statSync(path.join(asyncDir, "events.jsonl")).size;
|
|
@@ -400,6 +414,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
400
414
|
}
|
|
401
415
|
|
|
402
416
|
if (widgetChanged) rerenderLastWidget();
|
|
417
|
+
else refreshWidgetAnimation();
|
|
403
418
|
}, pollIntervalMs);
|
|
404
419
|
state.poller.unref?.();
|
|
405
420
|
};
|
|
@@ -882,25 +882,22 @@ export class ChainClarifyComponent implements Component {
|
|
|
882
882
|
}
|
|
883
883
|
}
|
|
884
884
|
|
|
885
|
-
render(
|
|
885
|
+
render(width: number): string[] {
|
|
886
|
+
let lines: string[] = [];
|
|
886
887
|
if (this.editingStep !== null) {
|
|
887
|
-
if (this.editMode === "model")
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
888
|
+
if (this.editMode === "model") lines = this.renderModelSelector();
|
|
889
|
+
else if (this.editMode === "thinking") lines = this.renderThinkingSelector();
|
|
890
|
+
else if (this.editMode === "skills") lines = this.renderSkillSelector();
|
|
891
|
+
else lines = this.renderFullEditMode();
|
|
892
|
+
} else {
|
|
893
|
+
switch (this.mode) {
|
|
894
|
+
case 'single': lines = this.renderSingleMode(); break;
|
|
895
|
+
case 'parallel': lines = this.renderParallelMode(); break;
|
|
896
|
+
case 'chain': lines = this.renderChainMode(); break;
|
|
895
897
|
}
|
|
896
|
-
return this.renderFullEditMode();
|
|
897
|
-
}
|
|
898
|
-
// Mode-based navigation rendering
|
|
899
|
-
switch (this.mode) {
|
|
900
|
-
case 'single': return this.renderSingleMode();
|
|
901
|
-
case 'parallel': return this.renderParallelMode();
|
|
902
|
-
case 'chain': return this.renderChainMode();
|
|
903
898
|
}
|
|
899
|
+
const renderWidth = Math.max(0, Math.min(this.width, Math.floor(width)));
|
|
900
|
+
return lines.map((line) => truncateToWidth(line, renderWidth));
|
|
904
901
|
}
|
|
905
902
|
|
|
906
903
|
/** Render the model selector view */
|
|
@@ -87,7 +87,7 @@ import {
|
|
|
87
87
|
shouldEscalateMutatingFailures,
|
|
88
88
|
summarizeRecentMutatingFailures,
|
|
89
89
|
} from "../shared/long-running-guard.ts";
|
|
90
|
-
import { acceptanceFailureMessage, buildSkippedAcceptanceLedger, evaluateAcceptance, formatAcceptancePrompt, resolveEffectiveAcceptance, stripAcceptanceReport } from "../shared/acceptance.ts";
|
|
90
|
+
import { acceptanceFailureMessage, buildSkippedAcceptanceLedger, evaluateAcceptance, formatAcceptancePrompt, resolveEffectiveAcceptance, stripAcceptanceReport, validateAcceptanceInput } from "../shared/acceptance.ts";
|
|
91
91
|
import { attachContractProjections, isAgentContractV1 } from "../shared/agent-contract.ts";
|
|
92
92
|
import { appendTurnBudgetSystemPrompt, formatTurnBudgetOutput, initialTurnBudgetState, turnBudgetDecision, turnBudgetDeferredNote, turnBudgetDeferredState, turnBudgetExceededMessage, turnBudgetSoftNote, turnBudgetState } from "../shared/turn-budget.ts";
|
|
93
93
|
import { initialToolBudgetState, toolBudgetState } from "../shared/tool-budget.ts";
|
|
@@ -984,14 +984,14 @@ async function runSingleAttempt(
|
|
|
984
984
|
}
|
|
985
985
|
};
|
|
986
986
|
|
|
987
|
-
|
|
987
|
+
fireUpdate();
|
|
988
|
+
if (controlConfig.enabled || options.onUpdate) {
|
|
988
989
|
activityTimer = setInterval(() => {
|
|
989
|
-
if (processClosed || lifecycleFinished)
|
|
990
|
-
|
|
991
|
-
if (updateActivityState(now)) {
|
|
992
|
-
progress.durationMs = now - startTime;
|
|
993
|
-
fireUpdate();
|
|
990
|
+
if (processClosed || lifecycleFinished) {
|
|
991
|
+
return;
|
|
994
992
|
}
|
|
993
|
+
updateActivityState(Date.now());
|
|
994
|
+
fireUpdate();
|
|
995
995
|
}, 1000);
|
|
996
996
|
activityTimer.unref?.();
|
|
997
997
|
}
|
|
@@ -1368,6 +1368,18 @@ async function runSyncCompletion(
|
|
|
1368
1368
|
...(options.capabilityCeiling ? { capabilityCeiling: options.capabilityCeiling } : {}),
|
|
1369
1369
|
}, options.context);
|
|
1370
1370
|
}
|
|
1371
|
+
const acceptanceErrors = validateAcceptanceInput(options.acceptance);
|
|
1372
|
+
if (acceptanceErrors.length > 0) {
|
|
1373
|
+
return withRunContext({
|
|
1374
|
+
index: options.index ?? 0,
|
|
1375
|
+
agent: agentName,
|
|
1376
|
+
task,
|
|
1377
|
+
exitCode: 1,
|
|
1378
|
+
messages: [],
|
|
1379
|
+
usage: emptyUsage(),
|
|
1380
|
+
error: acceptanceErrors.join(" "),
|
|
1381
|
+
}, options.context);
|
|
1382
|
+
}
|
|
1371
1383
|
const outputModeValidationError = validateFileOnlyOutputMode(options.outputMode, options.outputPath, `Single run (${agentName})`);
|
|
1372
1384
|
if (outputModeValidationError) {
|
|
1373
1385
|
return withRunContext({
|
|
@@ -4,7 +4,7 @@ import * as path from "node:path";
|
|
|
4
4
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
5
5
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { resolveAgentName, type AgentConfig, type AgentScope } from "../../agents/agents.ts";
|
|
7
|
-
import { getArtifactsDir, getChainRunsDir } from "../../shared/artifacts.ts";
|
|
7
|
+
import { getArtifactsDir, getChainRunsDir, getProjectArtifactPackagingWarning } from "../../shared/artifacts.ts";
|
|
8
8
|
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
9
9
|
import { ChainClarifyComponent, type ChainClarifyResult } from "./chain-clarify.ts";
|
|
10
10
|
import { resolveEffectiveThinking, toModelInfo, type ModelInfo } from "../../shared/model-info.ts";
|
|
@@ -212,7 +212,7 @@ export interface SubagentParamsLike {
|
|
|
212
212
|
message?: string;
|
|
213
213
|
steeringRecovery?: boolean;
|
|
214
214
|
workflowScript?: string;
|
|
215
|
-
chatProgress?: "auto" | "off" | "
|
|
215
|
+
chatProgress?: "auto" | "off" | "live-card";
|
|
216
216
|
step?: ChainStep;
|
|
217
217
|
/** Internal workflow ownership metadata; not part of the public schema. */
|
|
218
218
|
workflowParentRunId?: string;
|
|
@@ -1631,6 +1631,7 @@ async function maybeBuildForegroundIntercomReceipt(input: {
|
|
|
1631
1631
|
mode: SubagentRunMode;
|
|
1632
1632
|
details: Details;
|
|
1633
1633
|
nestedChildren?: NestedRunSummary[];
|
|
1634
|
+
preserveDetailsOutputs?: boolean;
|
|
1634
1635
|
}): Promise<{ text: string; details: Details } | null> {
|
|
1635
1636
|
const payload = await emitForegroundResultIntercom({
|
|
1636
1637
|
pi: input.pi,
|
|
@@ -1645,7 +1646,7 @@ async function maybeBuildForegroundIntercomReceipt(input: {
|
|
|
1645
1646
|
if (!payload) return null;
|
|
1646
1647
|
return {
|
|
1647
1648
|
text: formatSubagentResultReceipt({ mode: input.mode, runId: input.runId, payload }),
|
|
1648
|
-
details: stripDetailsOutputsForIntercomReceipt(input.details),
|
|
1649
|
+
details: input.preserveDetailsOutputs ? input.details : stripDetailsOutputsForIntercomReceipt(input.details),
|
|
1649
1650
|
};
|
|
1650
1651
|
}
|
|
1651
1652
|
|
|
@@ -3450,6 +3451,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3450
3451
|
runId,
|
|
3451
3452
|
mode: "parallel",
|
|
3452
3453
|
details,
|
|
3454
|
+
...(params.workflowParentRunId !== undefined ? { preserveDetailsOutputs: true } : {}),
|
|
3453
3455
|
...(foregroundControl?.nestedChildren?.length ? { nestedChildren: foregroundControl.nestedChildren } : {}),
|
|
3454
3456
|
});
|
|
3455
3457
|
if (intercomReceipt) {
|
|
@@ -3810,6 +3812,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3810
3812
|
runId,
|
|
3811
3813
|
mode: "single",
|
|
3812
3814
|
details,
|
|
3815
|
+
...(params.workflowParentRunId !== undefined ? { preserveDetailsOutputs: true } : {}),
|
|
3813
3816
|
...(foregroundControl?.nestedChildren?.length ? { nestedChildren: foregroundControl.nestedChildren } : {}),
|
|
3814
3817
|
});
|
|
3815
3818
|
if (intercomReceipt) {
|
|
@@ -3866,7 +3869,10 @@ function duplicateSubagentCallResult(params: SubagentParamsLike): AgentToolResul
|
|
|
3866
3869
|
}
|
|
3867
3870
|
|
|
3868
3871
|
function workflowChildResult(key: string, result: AgentToolResult<Details>): WorkflowScriptChildResult {
|
|
3869
|
-
const
|
|
3872
|
+
const receiptOutput = result.content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("\n");
|
|
3873
|
+
const output = result.details.results.length === 1 && result.details.results[0]?.finalOutput !== undefined
|
|
3874
|
+
? result.details.results[0].finalOutput
|
|
3875
|
+
: receiptOutput;
|
|
3870
3876
|
const artifactPaths = new Set<string>();
|
|
3871
3877
|
if (result.details.asyncDir) artifactPaths.add(result.details.asyncDir);
|
|
3872
3878
|
if (result.details.parallelHandoff?.path) artifactPaths.add(result.details.parallelHandoff.path);
|
|
@@ -3881,7 +3887,7 @@ function workflowChildResult(key: string, result: AgentToolResult<Details>): Wor
|
|
|
3881
3887
|
ok: result.isError !== true,
|
|
3882
3888
|
...(result.details.runId || result.details.asyncId ? { runId: result.details.runId ?? result.details.asyncId } : {}),
|
|
3883
3889
|
output,
|
|
3884
|
-
...(result.isError === true ? { error: output || "Child run failed." } : {}),
|
|
3890
|
+
...(result.isError === true ? { error: receiptOutput || output || "Child run failed." } : {}),
|
|
3885
3891
|
...(structured.length === 1 ? { structuredOutput: structured[0] } : structured.length > 1 ? { structuredOutput: structured } : {}),
|
|
3886
3892
|
artifactPaths: [...artifactPaths],
|
|
3887
3893
|
results: result.details.results,
|
|
@@ -4018,6 +4024,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4018
4024
|
} {
|
|
4019
4025
|
const delegatedThinkingOverrides = new WeakMap<object, AgentConfig["thinking"]>();
|
|
4020
4026
|
const delegatedZeroToolBudgets = new WeakSet<object>();
|
|
4027
|
+
const warnedArtifactPackageDirs = new Set<string>();
|
|
4021
4028
|
const scheduledOwnerExecutors = new Map<string, ReturnType<typeof createSubagentExecutor>>();
|
|
4022
4029
|
const execute = async (
|
|
4023
4030
|
_id: string,
|
|
@@ -4042,7 +4049,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4042
4049
|
if (requestParams.clarify === true) {
|
|
4043
4050
|
return { content: [{ type: "text", text: "workflowScript does not support clarify UI." }], isError: true, details: { mode: "workflow", results: [] } };
|
|
4044
4051
|
}
|
|
4045
|
-
const timeout = requestParams.timeoutMs ?? requestParams.maxRuntimeMs ?? DEFAULT_FOREGROUND_TIMEOUT_MS;
|
|
4052
|
+
const timeout = requestParams.timeoutMs ?? requestParams.maxRuntimeMs ?? (requestParams.async === false ? DEFAULT_FOREGROUND_TIMEOUT_MS : undefined);
|
|
4046
4053
|
const workflowUsageBudget = validateUsageBudgetConfig(requestParams.usageBudget ?? deps.config.usageBudget, requestParams.usageBudget ? "usageBudget" : "config.usageBudget");
|
|
4047
4054
|
if (workflowUsageBudget.error) return buildRequestedModeError(requestParams, workflowUsageBudget.error);
|
|
4048
4055
|
const workflowCwd = resolveRequestedCwd(ctx.cwd, requestParams.cwd);
|
|
@@ -4069,8 +4076,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4069
4076
|
state: "running",
|
|
4070
4077
|
startedAt,
|
|
4071
4078
|
lastUpdate: startedAt,
|
|
4072
|
-
deadlineAt: startedAt + timeout,
|
|
4073
|
-
timeoutMs: timeout,
|
|
4079
|
+
...(timeout !== undefined ? { deadlineAt: startedAt + timeout, timeoutMs: timeout } : {}),
|
|
4074
4080
|
cwd: ctx.cwd,
|
|
4075
4081
|
pid: process.pid,
|
|
4076
4082
|
steps: [],
|
|
@@ -4094,7 +4100,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4094
4100
|
job.workflow = status.workflow;
|
|
4095
4101
|
}
|
|
4096
4102
|
};
|
|
4097
|
-
const workflowJob: AsyncJobState = { asyncId: workflowRunId, asyncDir, cwd: ctx.cwd, status: "running", sessionId: currentSessionId ?? undefined, mode: "workflow", agents: [], steps: [], startedAt, updatedAt: startedAt, timeoutMs: timeout, deadlineAt: startedAt + timeout, workflow: status.workflow };
|
|
4103
|
+
const workflowJob: AsyncJobState = { asyncId: workflowRunId, asyncDir, cwd: ctx.cwd, status: "running", sessionId: currentSessionId ?? undefined, mode: "workflow", agents: [], steps: [], startedAt, updatedAt: startedAt, ...(timeout !== undefined ? { timeoutMs: timeout, deadlineAt: startedAt + timeout } : {}), workflow: status.workflow };
|
|
4098
4104
|
deps.state.asyncJobs.set(workflowRunId, workflowJob);
|
|
4099
4105
|
deps.state.fleetJobs ??= new Map();
|
|
4100
4106
|
deps.state.fleetJobs.set(workflowRunId, workflowJob);
|
|
@@ -4902,6 +4908,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4902
4908
|
dir: deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir,
|
|
4903
4909
|
});
|
|
4904
4910
|
const artifactsDir = getArtifactsDir(parentSessionFile, effectiveCwd, artifactConfig.dir);
|
|
4911
|
+
if (artifactConfig.dir === "project" && !warnedArtifactPackageDirs.has(effectiveCwd)) {
|
|
4912
|
+
warnedArtifactPackageDirs.add(effectiveCwd);
|
|
4913
|
+
const warning = getProjectArtifactPackagingWarning(effectiveCwd);
|
|
4914
|
+
if (warning) console.warn(`[pi-subagents] ${warning}`);
|
|
4915
|
+
}
|
|
4905
4916
|
|
|
4906
4917
|
let sessionRoot: string;
|
|
4907
4918
|
if (effectiveParams.sessionDir) {
|
|
@@ -169,7 +169,7 @@ export function validateAcceptanceInput(input: unknown, pathLabel = "acceptance"
|
|
|
169
169
|
if (input === "reviewed") errors.push(`${pathLabel} ${EXPLICIT_REVIEWED_UNAVAILABLE}`);
|
|
170
170
|
else if (!VALID_LEVELS.has(input as AcceptanceLevel)) errors.push(`${pathLabel} has invalid level '${input}'.`);
|
|
171
171
|
else if (input === "none") errors.push(`${pathLabel} level "none" requires a reason; use { level: "none", reason: "..." }.`);
|
|
172
|
-
else if (input === "verified") errors.push(`${pathLabel} level "verified" requires object form with at least one verify command.`);
|
|
172
|
+
else if (input === "verified") errors.push(`${pathLabel} level "verified" requires object form with at least one runtime verify command. Use level "checked" or provide a non-empty acceptance.verify array.`);
|
|
173
173
|
return errors;
|
|
174
174
|
}
|
|
175
175
|
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
@@ -234,7 +234,7 @@ export function validateAcceptanceInput(input: unknown, pathLabel = "acceptance"
|
|
|
234
234
|
errors.push(`${pathLabel}.evidence must be an array. ${ACCEPTANCE_EVIDENCE_HELP}`);
|
|
235
235
|
}
|
|
236
236
|
if (value.level === "verified" && (!Array.isArray(value.verify) || value.verify.length === 0)) {
|
|
237
|
-
errors.push(`${pathLabel}.verify must contain at least one command when level is verified.`);
|
|
237
|
+
errors.push(`${pathLabel}.verify must contain at least one runtime command when level is verified. Use level "checked" or provide a non-empty acceptance.verify array.`);
|
|
238
238
|
} else if (value.verify !== undefined && !Array.isArray(value.verify)) {
|
|
239
239
|
errors.push(`${pathLabel}.verify must be an array.`);
|
|
240
240
|
}
|
|
@@ -38,6 +38,7 @@ interface ServerEntry {
|
|
|
38
38
|
exposeResources?: boolean;
|
|
39
39
|
includeTools?: string[];
|
|
40
40
|
excludeTools?: string[];
|
|
41
|
+
protocolVersion?: string;
|
|
41
42
|
directTools?: boolean | string[];
|
|
42
43
|
}
|
|
43
44
|
|
|
@@ -284,6 +285,7 @@ export function computeMcpServerHash(definition: ServerEntry): string {
|
|
|
284
285
|
exposeResources: definition.exposeResources,
|
|
285
286
|
includeTools: definition.includeTools,
|
|
286
287
|
excludeTools: definition.excludeTools,
|
|
288
|
+
protocolVersion: definition.protocolVersion,
|
|
287
289
|
};
|
|
288
290
|
return createHash("sha256").update(stableStringify(identity)).digest("hex");
|
|
289
291
|
}
|