pi-subagents 0.82.1 → 0.83.0
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 +29 -0
- package/bundle/agents.js +4 -4
- package/bundle/background-work.js +1 -1
- package/bundle/child-tool-plan.js +3 -3
- package/bundle/{chunk-FXUXCX2K.js → chunk-4BW55BTL.js} +6 -6
- package/bundle/{chunk-PBK2I7WY.js → chunk-527E3D2J.js} +6 -6
- package/bundle/{chunk-46XKCPJR.js → chunk-5ESXK62X.js} +7 -1073
- package/bundle/{chunk-UTT254JE.js → chunk-6B4ITO2W.js} +8 -4
- package/bundle/{chunk-CIZQXJ5Q.js → chunk-7F5SK2U3.js} +39 -6
- package/bundle/{chunk-OYF6AFDY.js → chunk-BKFWQ642.js} +2 -2
- package/bundle/{chunk-ZIV4J4HG.js → chunk-CDI3GMNV.js} +47 -30
- package/bundle/{chunk-3JCDWF4V.js → chunk-CGXWSNPA.js} +12 -18
- package/bundle/{chunk-JYUKW2SY.js → chunk-CNLDYEH4.js} +12 -12
- package/bundle/{chunk-4SDU2BZX.js → chunk-CSSZRTKY.js} +2 -2
- package/bundle/{chunk-VSUSLDNW.js → chunk-CZTMUBUI.js} +10 -8
- package/bundle/{chunk-IVAE5Z6Q.js → chunk-HJJAVHFB.js} +28 -24
- package/bundle/{chunk-GJECHSAR.js → chunk-HLE22FSH.js} +2 -2
- package/bundle/{chunk-4Y2ROR7L.js → chunk-HXVKGS33.js} +5 -5
- package/bundle/{chunk-5LFLVSTM.js → chunk-JEZOSEWG.js} +2 -0
- package/bundle/{chunk-YKEWIB4M.js → chunk-JHZ6DZ5M.js} +1 -1
- package/bundle/{chunk-QE4AGIMG.js → chunk-LDMXJIA6.js} +457 -657
- package/bundle/{chunk-43SCHIMD.js → chunk-MBDOYZ2V.js} +1 -1
- package/bundle/{chunk-XSWOTIA3.js → chunk-MHX22USZ.js} +1 -1
- package/bundle/{chunk-6XJB7PIX.js → chunk-OOFW75PV.js} +4 -4
- package/bundle/{chunk-RMQFMMAT.js → chunk-P2NCCTZP.js} +231 -10
- package/bundle/{chunk-UCPP4GNU.js → chunk-RZJN63GU.js} +12 -5
- package/bundle/{chunk-G4GZD3OJ.js → chunk-TJQJ3MEA.js} +10 -10
- package/bundle/{chunk-5TYMSZX3.js → chunk-VTMNBPCQ.js} +2 -2
- package/bundle/{chunk-52UBALJL.js → chunk-WG2OTTAK.js} +2 -2
- package/bundle/{chunk-N2BN7BF5.js → chunk-XA3DY6UG.js} +7 -5
- package/bundle/{chunk-PDZWJCHD.js → chunk-XXKG3P2W.js} +9 -6
- package/bundle/{chunk-PLCCBLPY.js → chunk-Y3I6H6DV.js} +2 -2
- package/bundle/control-channel.js +3 -3
- package/bundle/index.js +1 -1
- package/bundle/intercom-bridge.js +3 -3
- package/bundle/preflight.js +9 -9
- package/bundle/project-panes.js +4 -4
- package/bundle/shared-types.js +1 -1
- package/docs/configuration.md +1 -1
- package/docs/extension-api.md +19 -4
- package/docs/observability.md +1 -0
- package/docs/tool-reference.md +1 -1
- package/package.json +1 -1
- package/skills/pi-subagents/SKILL.md +6 -6
- package/src/agents/agent-management.js +8 -14
- package/src/agents/agent-serializer.js +1 -2
- package/src/agents/agents.js +19 -3
- package/src/api/background-work.d.ts +9 -6
- package/src/api/background-work.js +14 -7
- package/src/extension/index.js +4 -1
- package/src/extension/public-execution.d.ts +2 -2
- package/src/extension/public-execution.js +3 -3
- package/src/extension/reply-workflow-script.js +4 -5
- package/src/extension/rpc.js +3 -1
- package/src/extension/subagent-options.js +17 -4
- package/src/runs/background/async-execution.js +4 -1
- package/src/runs/background/async-job-tracker.js +2 -1
- package/src/runs/background/auto-drain.js +2 -3
- package/src/runs/background/fleet-view.js +1 -1
- package/src/runs/background/run-child-session.js +22 -6
- package/src/runs/background/subagent-wait.d.ts +2 -1
- package/src/runs/background/subagent-wait.js +5 -2
- package/src/runs/foreground/execution.js +22 -6
- package/src/runs/foreground/subagent-executor.js +2 -5
- package/src/runs/shared/child-launch.js +5 -2
- package/src/runs/shared/child-session.d.ts +6 -0
- package/src/runs/shared/child-session.js +20 -4
- package/src/runs/shared/herdr-machine.d.ts +2 -0
- package/src/runs/shared/herdr-machine.js +2 -0
- package/src/runs/shared/herdr-placed-run.js +11 -4
- package/src/runs/shared/subagent-prompt-runtime.js +11 -17
- package/src/shared/disabled-features.d.ts +3 -1
- package/src/shared/disabled-features.js +11 -3
- package/src/shared/types.d.ts +3 -1
- package/src/shared/types.js +2 -0
- package/src/workflows/scripted-workflow.js +9 -1070
- package/workflow-script-worker.cjs +1067 -0
package/bundle/preflight.js
CHANGED
|
@@ -2,22 +2,22 @@ import {
|
|
|
2
2
|
buildEffectiveSystemPrompt,
|
|
3
3
|
canPreferForkFromSnapshot,
|
|
4
4
|
resolveSubagentLaunchContext
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-HLE22FSH.js";
|
|
6
|
+
import "./chunk-Y3I6H6DV.js";
|
|
7
7
|
import {
|
|
8
8
|
loadConfig
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-RZJN63GU.js";
|
|
10
10
|
import {
|
|
11
11
|
nestedResultsPath,
|
|
12
12
|
processTerminalCandidatePath,
|
|
13
13
|
processTerminalPath,
|
|
14
14
|
resultFilePath
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-5ESXK62X.js";
|
|
16
16
|
import {
|
|
17
17
|
applyIntercomBridgeToAgent,
|
|
18
18
|
resolveIntercomBridge,
|
|
19
19
|
validateIntercomBridgeConfig
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-MHX22USZ.js";
|
|
21
21
|
import {
|
|
22
22
|
AGENT_DEFINITION_PROJECTION_VERSION,
|
|
23
23
|
resolveLaunchBinding,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
import {
|
|
27
27
|
applyThinkingSuffix,
|
|
28
28
|
resolvePiLaunchToolPlan
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-CSSZRTKY.js";
|
|
30
30
|
import {
|
|
31
31
|
assertRequiredChildExtensionsAdmitted,
|
|
32
32
|
resolveRequiredChildExtensions
|
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
import {
|
|
35
35
|
getArtifactPaths,
|
|
36
36
|
getArtifactsDir
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-VTMNBPCQ.js";
|
|
38
38
|
import {
|
|
39
39
|
assertClaudeCodeModelScope,
|
|
40
40
|
assertThinkingWithinCeiling,
|
|
@@ -61,12 +61,12 @@ import {
|
|
|
61
61
|
resolveSkillsWithFallback,
|
|
62
62
|
resolveStepBehavior,
|
|
63
63
|
unknownAgentDiagnosticContext
|
|
64
|
-
} from "./chunk-
|
|
64
|
+
} from "./chunk-7F5SK2U3.js";
|
|
65
65
|
import {
|
|
66
66
|
DIRS,
|
|
67
67
|
SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
68
68
|
TEMP_ROOT_DIR
|
|
69
|
-
} from "./chunk-
|
|
69
|
+
} from "./chunk-JEZOSEWG.js";
|
|
70
70
|
import "./chunk-YFCK7H2S.js";
|
|
71
71
|
import {
|
|
72
72
|
capabilityCeilingAgentRestrictionMessage,
|
package/bundle/project-panes.js
CHANGED
|
@@ -8,11 +8,11 @@ import {
|
|
|
8
8
|
openProjectPane,
|
|
9
9
|
projectPaneBindingPath,
|
|
10
10
|
readProjectPaneBinding
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-WG2OTTAK.js";
|
|
12
12
|
import "./chunk-7J4ZXMY7.js";
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
15
|
-
import "./chunk-
|
|
13
|
+
import "./chunk-VTMNBPCQ.js";
|
|
14
|
+
import "./chunk-7F5SK2U3.js";
|
|
15
|
+
import "./chunk-JEZOSEWG.js";
|
|
16
16
|
import "./chunk-YFCK7H2S.js";
|
|
17
17
|
import "./chunk-Z7KVHQHL.js";
|
|
18
18
|
export {
|
package/bundle/shared-types.js
CHANGED
package/docs/configuration.md
CHANGED
|
@@ -124,7 +124,7 @@ Pi records each tool definition and prompt section a session declares, and decla
|
|
|
124
124
|
{ "disabledFeatures": ["watchdog", "panes", "preflight", "lane-metadata", "gates"] }
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
-
Removes feature groups you do not use from the `subagent` tool. Each listed feature loses its top-level parameters from the model-facing schema and its `options` keys are rejected, and any request that still uses one of its parameters or actions fails with an error naming this setting. The check covers the parent tool, fanout-child tools, RPC, slash commands, prompt templates, scheduled launches, delegated launches, and workflow `runs.run`/`runs.all`/`runs.lanes` children, which are rejected before they launch. The built-in tool descriptions, the unknown-action list, the fanout-child tool description, and RPC `ping` no longer mention disabled features
|
|
127
|
+
Removes feature groups you do not use from the `subagent` tool. Each listed feature loses its top-level parameters from the model-facing schema and its `options` keys are rejected, and any request that still uses one of its parameters or actions fails with an error naming this setting. The check covers the parent tool, fanout-child tools, RPC, slash commands, prompt templates, scheduled launches, delegated launches, and workflow `runs.run`/`runs.all`/`runs.lanes` children, which are rejected before they launch. The built-in tool descriptions, the unknown-action list, the fanout-child tool description, and RPC `ping` no longer mention disabled features. Every `{ action: "guide" }` response starts with a notice: a topic lists what is disabled, and a section points to the `tool-reference` topic for that list. With `workflow-scripts` disabled, the notice also says to use the top-level `tasks` or `chain` fields instead. Custom tool descriptions are not changed. Nothing is disabled by default, and the default schema and description are unchanged. An unknown or duplicate feature name fails config loading rather than silently re-enabling every feature.
|
|
128
128
|
|
|
129
129
|
| Feature | Parameters removed | Actions rejected |
|
|
130
130
|
|---|---|---|
|
package/docs/extension-api.md
CHANGED
|
@@ -129,6 +129,7 @@ Method notes:
|
|
|
129
129
|
Capability advertisements on `ping`:
|
|
130
130
|
|
|
131
131
|
- `events.asyncComplete` — exact process-local completion correlation after RPC `spawn`.
|
|
132
|
+
- `events.jobsChanged`, `events.asyncStarted`, `events.childStatus`, `events.processTerminal` — the event names a host can listen to instead of polling `status` (see [Refresh a host view without polling](#refresh-a-host-view-without-polling)).
|
|
132
133
|
- `managementActions` — exact schedule management actions accepted by RPC `manage`.
|
|
133
134
|
- `launchResolvedExtensions` — the optional launch-resolved extension projection in status details.
|
|
134
135
|
- `runtimeAcknowledgedExtensions` — the optional child-runtime acknowledgement projection and event name.
|
|
@@ -141,6 +142,20 @@ Capability advertisements on `ping`:
|
|
|
141
142
|
|
|
142
143
|
Structured delegation progress updates carry `runId` as soon as foreground execution allocates it, so a caller can retain the package-owned revival target even if its own tool turn is interrupted before the terminal response. Foreground `details.results[]` rows also include a numeric `index` that is unique within the run and stable across partial progress snapshots and the final result; use `(runId, index)` instead of row position to correlate single, counted parallel, and chain children.
|
|
143
144
|
|
|
145
|
+
### Refresh a host view without polling
|
|
146
|
+
|
|
147
|
+
A host that shows background subagent activity (an editor panel, a footer, a status bar) can read `status` when something changed instead of on a timer:
|
|
148
|
+
|
|
149
|
+
1. Subscribe to the names under `ping.events`: `jobsChanged`, `asyncStarted`, `childStatus`, `asyncComplete`, and `processTerminal`.
|
|
150
|
+
2. Read `status` once, when the host starts or reconnects. Events are not replayed after a host restart, and subscribing first means a change during that read still triggers another one.
|
|
151
|
+
3. When any of the events fires, read `status` again. Events can arrive in bursts and can repeat, so coalesce them in the host, for example one `status` read per 250 ms, and keep a refresh that arrives while a read is in flight for after it ends.
|
|
152
|
+
|
|
153
|
+
`subagent:jobs-changed` carries only `{ version: 1 }`. It fires whenever the parent's background job view changes: a run starts, moves to another step (no other event reports a chain or parallel run's step changes), reports new activity (a tool, a turn, tokens, or output), finishes, or is cleared from the widget. While a child streams output this can be about once per second per run. Starts and finishes fire right away; step and activity changes fire when the parent next reads the run's status file, which on macOS is a check every 5 seconds. It fires only in sessions with a UI, which covers interactive and RPC mode; print mode (`pi -p`) and SDK embeddings without a UI do not get it. It fires even when the async widget is turned off.
|
|
154
|
+
|
|
155
|
+
Foreground children are not background jobs. Their progress arrives as updates to the parent's `subagent` tool call (Pi's `tool_execution_update` events), with the `details.results[]` rows described above.
|
|
156
|
+
|
|
157
|
+
To rebuild what ran in a session after a reload, read the records the session file already holds: the `subagent` tool result's `details.results[]` for foreground runs and the `subagent-notify` message's `details.runs[]` for background runs (see [Host session lifetime and completion wakes](#host-session-lifetime-and-completion-wakes)). The `cost` method returns per-child rows built from the same records.
|
|
158
|
+
|
|
144
159
|
### Direct async launch correlation
|
|
145
160
|
|
|
146
161
|
Direct async runs retain their originating `toolCallId` in status and result
|
|
@@ -440,16 +455,16 @@ const dispose = registerBackgroundWorkProvider({
|
|
|
440
455
|
name: "my-background-extension",
|
|
441
456
|
wakeChannels: ["my-extension:job-finished"],
|
|
442
457
|
listActiveWork: (context) => jobs
|
|
443
|
-
.filter((job) => job.status === "running" && (!context || job.ownerSessionId
|
|
458
|
+
.filter((job) => job.status === "running" && (!context || context.sessionIds.includes(job.ownerSessionId)))
|
|
444
459
|
.map((job) => ({ id: job.id, sessionId: job.ownerSessionId })),
|
|
445
|
-
reconcile: ({
|
|
460
|
+
reconcile: ({ sessionIds, nowMs }) => reconcileJobs(sessionIds, nowMs),
|
|
446
461
|
});
|
|
447
462
|
```
|
|
448
463
|
|
|
449
464
|
Semantics:
|
|
450
465
|
|
|
451
|
-
- Each item needs a stable provider-local ID and the
|
|
452
|
-
- `listActiveWork` receives an optional `{ sessionId, nowMs }` context during snapshots.
|
|
466
|
+
- Each item needs a stable provider-local ID and the session that owns it, as either `ctx.sessionManager.getSessionId()` or the session file path (`ctx.sessionManager.getSessionFile()`); both are accepted. `bg_wait` captures those identities rather than a count, so one job finishing while another starts still satisfies first-completion waits without losing the replacement.
|
|
467
|
+
- `listActiveWork` receives an optional `{ sessionId, sessionIds, nowMs }` context during snapshots, and `reconcile` receives the same fields. `sessionIds` holds every form of the current session (its file path when it has one, and its session id); `sessionId` is the first of them. Providers can use them to avoid scanning unrelated work; existing zero-argument `() => items` providers continue to work, and returned items are still validated and filtered to the current session. Each provider is called once per snapshot.
|
|
453
468
|
- It filters snapshots to the active session, fails closed if a provider disappears while its work is tracked, and surfaces malformed snapshots or provider errors with provider context.
|
|
454
469
|
- Wake channels only shorten polling; validated snapshots remain authoritative.
|
|
455
470
|
- Providers share a registry through `Symbol.for("pi-subagents.background-work.v1")`, allowing independently loaded extension modules to meet in one Pi process.
|
package/docs/observability.md
CHANGED
|
@@ -334,6 +334,7 @@ Async events:
|
|
|
334
334
|
|
|
335
335
|
- `subagent:async-started`
|
|
336
336
|
- `subagent:async-complete`
|
|
337
|
+
- `subagent:jobs-changed` (see [extension-api.md](extension-api.md#refresh-a-host-view-without-polling))
|
|
337
338
|
|
|
338
339
|
For regular async starts, the `subagent:async-started` payload includes redacted `task` and `goal` prompt fields. Async workflow script roots emit the same event with `mode: "workflow"` after their initial `status.json` is durable; workflow roots can omit `task`, and any included prompt fields are redacted. Their keyed children are then announced dynamically through `subagent:child-status`. Companion UI extensions can combine those hints with the authoritative live lifecycle artifacts under `asyncDir` without scraping terminal output.
|
|
339
340
|
|
package/docs/tool-reference.md
CHANGED
|
@@ -110,7 +110,7 @@ The `subagent` tool schema lists `agent`, `task`, `workflow`, `args`, `async`, `
|
|
|
110
110
|
| `options.checkpointBeforeDeadlineMs` | number | none | Async single-agent runs only. The runner requests that the child "checkpoint and stop" this many milliseconds before the run deadline (finish the current tool call, report changed files, build/test state, remaining work, commit/PR state; start no new work). This best-effort steer uses the normal steering lifecycle at the next tool boundary, so the receipt is visible in status and events; the ordinary deadline kill still applies. Precedence: call value → config `checkpointBeforeDeadlineMs`. Disarmed when the deadline leaves under one second before the checkpoint. |
|
|
111
111
|
| `options.toolBudget` | object | none | Optional child tool-call budget `{ soft?, hard, block? }`: `hard` is a positive integer and `soft`, if set, is a positive integer no greater than `hard`. At `soft` the child is nudged to finalize. After `hard`, configured tools are blocked; `block` defaults to `read`, `grep`, `find`, and `ls`, and accepts either a nonempty array of tool names or `"*"` for every tool call. Final assistant text is never blocked. |
|
|
112
112
|
| `options.usageBudget` | object | none | Optional root-only reported-usage budget `{ tokens?: { soft?, hard }, costUsd?: { soft?, hard } }`. Include at least one metric; each `hard` must be positive and each optional `soft` positive and no greater than its `hard`. Soft limits are status-only. Hard limits prevent later child launches after reported usage is reconciled; already-running children are not stopped and no reservations are made. |
|
|
113
|
-
| `options.extensionBindings` | object | none | Child-only plain JSON keyed by namespaces like `package.name/1` (positive version): at most 16 namespaces, nesting depth 16, 256 total object properties, and 16 KiB of canonical UTF-8 JSON. Native Pi launches only; external runners and retained resumes reject it. |
|
|
113
|
+
| `options.extensionBindings` | object | none | Child-only plain JSON keyed by namespaces like `package.name/1` (positive version): at most 16 namespaces, nesting depth 16, 256 total object properties, and 16 KiB of canonical UTF-8 JSON. Native Pi launches only; external runners and retained resumes reject it. A child's extensions read it as canonical JSON from `PI_SUBAGENT_EXTENSION_BINDINGS` in their factory or `session_start`; a foreground child sees it only until its session has started, after which the parent's environment is restored. |
|
|
114
114
|
| `options.preflight` | object | none | Advisory lane hints for raw scripts (`workflow: true` or a script path) only; not accepted with named workflows or direct children. See [bounded workflows](workflows.md#opt-in-bounded-workflows). |
|
|
115
115
|
| `cwd` | string | runtime cwd | Override working directory. With `machine`, the directory on that machine. |
|
|
116
116
|
| `options.machine` | string | - | Herdr saved machine (label or profile id) for external-cli agents; see [agents.md](agents.md#running-external-cli-agents-on-a-herdr-saved-machine). |
|
package/package.json
CHANGED
|
@@ -48,12 +48,12 @@ needs.
|
|
|
48
48
|
Use a script when later steps depend on earlier results, or for keyed,
|
|
49
49
|
retried, aggregated, or resumed children. Write one ```` ```js workflow ````
|
|
50
50
|
block in the reply and call `subagent({ workflow: true })` in the same reply.
|
|
51
|
-
For a long script,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
the
|
|
56
|
-
the
|
|
51
|
+
For a long script, write it to a file and pass `workflow: "./path.js"`; calls in
|
|
52
|
+
one reply run at the same time, so write the file in one reply and `validate` or
|
|
53
|
+
launch it in the next. When a `workflow: true` call reports the block missing,
|
|
54
|
+
pass the source as `options: { script: "..." }` in place of `workflow: true` for
|
|
55
|
+
the rest of the turn: on some hosts, text after a tool call reaches the session
|
|
56
|
+
as a summary without the block.
|
|
57
57
|
|
|
58
58
|
```js workflow
|
|
59
59
|
const scan = await runs.run("scan", { label: "Map auth flow", agent: "scout", task: "..." });
|
|
@@ -6,7 +6,7 @@ import { mergeAgentsForScope } from "./agent-selection.js";
|
|
|
6
6
|
import { discoverAvailableSkills, resolveSkills } from "./skills.js";
|
|
7
7
|
import { buildProactiveSkillSubagentRecommendationLines, } from "./proactive-skills.js";
|
|
8
8
|
import { parseFrontmatter, parseUnquotedList } from "./frontmatter.js";
|
|
9
|
-
import { resolveEffectiveThinking, toModelInfo } from "../shared/model-info.js";
|
|
9
|
+
import { resolveEffectiveThinking, THINKING_LEVELS, toModelInfo } from "../shared/model-info.js";
|
|
10
10
|
import { resolveSubagentModelOverride } from "../runs/shared/model-resolution.js";
|
|
11
11
|
import { validateToolBudgetConfig } from "../runs/shared/tool-budget.js";
|
|
12
12
|
import { formatReviewGateLabel, validateAcceptanceInput } from "../runs/shared/acceptance.js";
|
|
@@ -279,11 +279,8 @@ export function preservedAgentFrontmatterFields(agent, cfg) {
|
|
|
279
279
|
changed("subagentOnlyExtensions");
|
|
280
280
|
if (hasKey(cfg, "mutationTools"))
|
|
281
281
|
changed("mutationTools");
|
|
282
|
-
if (hasKey(cfg, "thinking"))
|
|
282
|
+
if (hasKey(cfg, "thinking"))
|
|
283
283
|
changed("thinking");
|
|
284
|
-
if (cfg.thinking === "off")
|
|
285
|
-
fields.add("thinking");
|
|
286
|
-
}
|
|
287
284
|
if (hasKey(cfg, "systemPromptMode")) {
|
|
288
285
|
changed("systemPromptMode");
|
|
289
286
|
fields.add("systemPromptMode");
|
|
@@ -528,17 +525,14 @@ function applyAgentConfig(target, cfg) {
|
|
|
528
525
|
return "config.mutationTools must be a comma-separated string, empty string, or false when provided.";
|
|
529
526
|
}
|
|
530
527
|
if (hasKey(cfg, "thinking")) {
|
|
531
|
-
|
|
528
|
+
const thinking = typeof cfg.thinking === "string" ? cfg.thinking.trim() : cfg.thinking;
|
|
529
|
+
const level = THINKING_LEVELS.find((candidate) => candidate === thinking);
|
|
530
|
+
if (thinking === false || thinking === "")
|
|
532
531
|
delete target.thinking;
|
|
533
|
-
else if (
|
|
534
|
-
|
|
535
|
-
if (thinking)
|
|
536
|
-
target.thinking = thinking;
|
|
537
|
-
else
|
|
538
|
-
delete target.thinking;
|
|
539
|
-
}
|
|
532
|
+
else if (level)
|
|
533
|
+
target.thinking = level;
|
|
540
534
|
else
|
|
541
|
-
return
|
|
535
|
+
return `config.thinking must be one of ${THINKING_LEVELS.join(", ")}, or false when provided.`;
|
|
542
536
|
}
|
|
543
537
|
if (hasKey(cfg, "systemPromptMode")) {
|
|
544
538
|
if (cfg.systemPromptMode === "append" || cfg.systemPromptMode === "replace")
|
|
@@ -91,9 +91,8 @@ export function serializeAgent(config, options = {}) {
|
|
|
91
91
|
lines.push(`model: ${config.model ?? ""}`);
|
|
92
92
|
if (config.fast === true || preserve("fast"))
|
|
93
93
|
lines.push(`fast: ${config.fast === undefined ? "" : config.fast ? "true" : "false"}`);
|
|
94
|
-
if (
|
|
94
|
+
if (config.thinking || preserve("thinking"))
|
|
95
95
|
lines.push(`thinking: ${config.thinking ?? ""}`);
|
|
96
|
-
}
|
|
97
96
|
if (!preservingExistingFrontmatter || preserve("systemPromptMode"))
|
|
98
97
|
lines.push(`systemPromptMode: ${config.systemPromptMode}`);
|
|
99
98
|
if (!preservingExistingFrontmatter || preserve("inheritProjectContext"))
|
package/src/agents/agents.js
CHANGED
|
@@ -28,6 +28,7 @@ import { parseMemoryFrontmatter } from "./agent-memory.js";
|
|
|
28
28
|
import { validateAcceptanceInput } from "../runs/shared/acceptance.js";
|
|
29
29
|
import { validatePermissionRules } from "../runs/shared/permissions.js";
|
|
30
30
|
import { parseThinkingLevel } from "../shared/thinking-ceiling.js";
|
|
31
|
+
import { THINKING_LEVELS } from "../shared/model-info.js";
|
|
31
32
|
import { assertJsonSchemaObject } from "../runs/shared/structured-output.js";
|
|
32
33
|
import { normalizeCapabilityCeilingAllowedAgents } from "../runs/shared/capability-ceiling.js";
|
|
33
34
|
export function defaultSystemPromptMode(name) {
|
|
@@ -39,6 +40,14 @@ export function defaultInheritProjectContext(name) {
|
|
|
39
40
|
export function defaultInheritSkills() {
|
|
40
41
|
return false;
|
|
41
42
|
}
|
|
43
|
+
/** Every field an `agentOverrides` entry may set; the Record type keeps it equal to BuiltinAgentOverrideConfig. */
|
|
44
|
+
const BUILTIN_OVERRIDE_FIELDS = {
|
|
45
|
+
description: true, advertise: true, machine: true, output: true, outputMode: true, defaultReads: true, model: true,
|
|
46
|
+
defaultProvider: true, fast: true, thinking: true, systemPromptMode: true, inheritProjectContext: true,
|
|
47
|
+
inheritGlobalContext: true, inheritSkills: true, defaultContext: true, acceptanceRole: true, disabled: true,
|
|
48
|
+
systemPrompt: true, skills: true, tools: true, excludeTools: true, allowNestedSubagents: true, allowedAgents: true,
|
|
49
|
+
extensions: true, subagentOnlyExtensions: true, mutationTools: true, toolBudget: true,
|
|
50
|
+
};
|
|
42
51
|
const EMPTY_SUBAGENT_SETTINGS = { overrides: {}, providerOverrides: {} };
|
|
43
52
|
const agentFrontmatterFields = new WeakMap();
|
|
44
53
|
const AGENT_SOURCE_PRIORITY = {
|
|
@@ -763,6 +772,12 @@ function parseBuiltinOverrideEntry(name, value, filePath) {
|
|
|
763
772
|
if (Object.hasOwn(input, "launcher")) {
|
|
764
773
|
throw new Error(`Builtin override '${name}' in '${filePath}' sets 'launcher', which settings overrides do not support; set 'launcher' in a user agent file instead.`);
|
|
765
774
|
}
|
|
775
|
+
for (const key of Object.keys(input)) {
|
|
776
|
+
if (Object.hasOwn(BUILTIN_OVERRIDE_FIELDS, key))
|
|
777
|
+
continue;
|
|
778
|
+
const suggestion = closestMatch(key, Object.keys(BUILTIN_OVERRIDE_FIELDS));
|
|
779
|
+
throw new Error(`Builtin override '${name}' in '${filePath}' has unknown field '${key}'.${suggestion ? ` Did you mean '${suggestion}'?` : ""}`);
|
|
780
|
+
}
|
|
766
781
|
const override = {};
|
|
767
782
|
if ("description" in input) {
|
|
768
783
|
if (typeof input.description === "string" && input.description.trim()) {
|
|
@@ -805,10 +820,11 @@ function parseBuiltinOverrideEntry(name, value, filePath) {
|
|
|
805
820
|
throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'fast'; expected a boolean.`);
|
|
806
821
|
}
|
|
807
822
|
if ("thinking" in input) {
|
|
808
|
-
|
|
809
|
-
|
|
823
|
+
const level = THINKING_LEVELS.find((candidate) => candidate === input.thinking);
|
|
824
|
+
if (level !== undefined || input.thinking === false)
|
|
825
|
+
override.thinking = level ?? false;
|
|
810
826
|
else
|
|
811
|
-
throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'thinking'; expected
|
|
827
|
+
throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'thinking'; expected one of ${THINKING_LEVELS.join(", ")}, or false.`);
|
|
812
828
|
}
|
|
813
829
|
if ("systemPromptMode" in input) {
|
|
814
830
|
if (input.systemPromptMode === "append" || input.systemPromptMode === "replace") {
|
|
@@ -4,14 +4,13 @@ export interface BackgroundWorkItem {
|
|
|
4
4
|
id: string;
|
|
5
5
|
sessionId: string;
|
|
6
6
|
}
|
|
7
|
-
export interface BackgroundWorkReconcileContext {
|
|
8
|
-
sessionId: string;
|
|
9
|
-
nowMs: number;
|
|
10
|
-
}
|
|
11
7
|
export interface BackgroundWorkListContext {
|
|
12
8
|
sessionId: string;
|
|
9
|
+
/** Every form the current session is known by: its file path when it has one, and `getSessionId()`. Includes `sessionId`. */
|
|
10
|
+
sessionIds: readonly string[];
|
|
13
11
|
nowMs: number;
|
|
14
12
|
}
|
|
13
|
+
export type BackgroundWorkReconcileContext = BackgroundWorkListContext;
|
|
15
14
|
export interface BackgroundWorkProvider {
|
|
16
15
|
name: string;
|
|
17
16
|
listActiveWork(context?: BackgroundWorkListContext): readonly BackgroundWorkItem[];
|
|
@@ -34,5 +33,9 @@ export declare function registerBackgroundWorkProvider(provider: BackgroundWorkP
|
|
|
34
33
|
export declare function listBackgroundWorkProviders(): readonly BackgroundWorkProvider[];
|
|
35
34
|
/** Read validated provider wake channels without reconciling or listing work. */
|
|
36
35
|
export declare function listBackgroundWorkWakeChannels(): readonly string[];
|
|
37
|
-
/**
|
|
38
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Reconcile and snapshot active provider work owned by one exact Pi session.
|
|
38
|
+
* `sessionIds` lists that session's identity forms; the first is passed to
|
|
39
|
+
* providers as `sessionId`, and items keyed by any of them are kept.
|
|
40
|
+
*/
|
|
41
|
+
export declare function snapshotBackgroundWork(sessionIds: readonly string[], nowMs?: number): BackgroundWorkSnapshot;
|
|
@@ -4,7 +4,7 @@ const MAX_PROVIDER_NAME_LENGTH = 128;
|
|
|
4
4
|
const MAX_PROVIDERS = 100;
|
|
5
5
|
const MAX_ITEM_ID_LENGTH = 256;
|
|
6
6
|
/**
|
|
7
|
-
* A
|
|
7
|
+
* A session can be identified by its file path, which routinely exceeds a short
|
|
8
8
|
* identity budget in nested worktrees, so it is bounded like the other paths.
|
|
9
9
|
*/
|
|
10
10
|
const MAX_SESSION_ID_LENGTH = 4_096;
|
|
@@ -120,22 +120,29 @@ export function listBackgroundWorkWakeChannels() {
|
|
|
120
120
|
}
|
|
121
121
|
return [...channels];
|
|
122
122
|
}
|
|
123
|
-
/**
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
/**
|
|
124
|
+
* Reconcile and snapshot active provider work owned by one exact Pi session.
|
|
125
|
+
* `sessionIds` lists that session's identity forms; the first is passed to
|
|
126
|
+
* providers as `sessionId`, and items keyed by any of them are kept.
|
|
127
|
+
*/
|
|
128
|
+
export function snapshotBackgroundWork(sessionIds, nowMs = Date.now()) {
|
|
129
|
+
const accepted = [...new Set(sessionIds.map((value) => validateString(value, "Background-work snapshot sessionId", MAX_SESSION_ID_LENGTH)))];
|
|
130
|
+
const sessionId = accepted[0];
|
|
131
|
+
if (sessionId === undefined)
|
|
132
|
+
throw new Error("Background-work snapshot needs at least one session id.");
|
|
126
133
|
const providers = listBackgroundWorkProviders();
|
|
127
134
|
const items = [];
|
|
128
135
|
const identities = new Set();
|
|
129
136
|
for (const provider of providers) {
|
|
130
137
|
try {
|
|
131
|
-
provider.reconcile?.({ sessionId, nowMs });
|
|
138
|
+
provider.reconcile?.({ sessionId, sessionIds: [...accepted], nowMs });
|
|
132
139
|
}
|
|
133
140
|
catch (error) {
|
|
134
141
|
throw new Error(`Background-work provider '${provider.name}' reconcile failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
135
142
|
}
|
|
136
143
|
let active;
|
|
137
144
|
try {
|
|
138
|
-
active = provider.listActiveWork({ sessionId, nowMs });
|
|
145
|
+
active = provider.listActiveWork({ sessionId, sessionIds: [...accepted], nowMs });
|
|
139
146
|
}
|
|
140
147
|
catch (error) {
|
|
141
148
|
throw new Error(`Background-work provider '${provider.name}' listActiveWork failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
@@ -153,7 +160,7 @@ export function snapshotBackgroundWork(sessionId, nowMs = Date.now()) {
|
|
|
153
160
|
throw new Error(`Background-work provider '${provider.name}' returned duplicate item '${item.id}' for session '${item.sessionId}'.`);
|
|
154
161
|
}
|
|
155
162
|
identities.add(identity);
|
|
156
|
-
if (item.sessionId
|
|
163
|
+
if (accepted.includes(item.sessionId))
|
|
157
164
|
items.push({ provider: provider.name, ...item });
|
|
158
165
|
});
|
|
159
166
|
}
|
package/src/extension/index.js
CHANGED
|
@@ -566,8 +566,11 @@ export default function registerSubagentExtension(pi) {
|
|
|
566
566
|
// Pi sends custom messages to the model as plain user messages, so every model-visible
|
|
567
567
|
// pi-subagents message is wrapped in a marker naming its source. The marker depends only on
|
|
568
568
|
// the message, so history renders the same bytes every turn and cached prefixes stay valid.
|
|
569
|
+
// It runs on context_with_system, not context: Pi treats any change a context handler makes
|
|
570
|
+
// to the message list as a rewritten conversation and folds every mid-conversation system
|
|
571
|
+
// message into the leading one, which misses the prompt cache on every later turn.
|
|
569
572
|
const markedMessageTypes = new Set([...SUBAGENT_MESSAGE_TYPES, "subagent-compaction-resume", SLASH_RESULT_TYPE, SLASH_TEXT_RESULT_TYPE, ADMIN_MESSAGE_TYPE]);
|
|
570
|
-
pi.on("
|
|
573
|
+
pi.on("context_with_system", (event) => {
|
|
571
574
|
const messages = [];
|
|
572
575
|
for (const message of event.messages) {
|
|
573
576
|
if (message.role !== "custom" || !markedMessageTypes.has(message.customType)) {
|
|
@@ -57,8 +57,8 @@ export type PublicSubagentExecutionNormalization<T> = {
|
|
|
57
57
|
/** A workflow value containing "/" or "\" is a script path; named workflow resource names contain neither. */
|
|
58
58
|
export declare function isWorkflowScriptPath(workflow: string): boolean;
|
|
59
59
|
/**
|
|
60
|
-
* Model tool calls supply script text
|
|
61
|
-
* workflowScript
|
|
60
|
+
* Model tool calls supply script text through workflow: true (the reply block), a script path, or options.script,
|
|
61
|
+
* which flattenSubagentToolOptions lowers to workflowScript, the internal carrier for every other launch path too.
|
|
62
62
|
*/
|
|
63
63
|
export declare function removedModelWorkflowFieldError(params: object): string | undefined;
|
|
64
64
|
export declare function validateWorkflowCapacityOverrides(params: PublicSubagentExecutionParams): string | undefined;
|
|
@@ -7,8 +7,8 @@ export function isWorkflowScriptPath(workflow) {
|
|
|
7
7
|
return workflow.includes("/") || workflow.includes("\\");
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
|
-
* Model tool calls supply script text
|
|
11
|
-
* workflowScript
|
|
10
|
+
* Model tool calls supply script text through workflow: true (the reply block), a script path, or options.script,
|
|
11
|
+
* which flattenSubagentToolOptions lowers to workflowScript, the internal carrier for every other launch path too.
|
|
12
12
|
*/
|
|
13
13
|
export function removedModelWorkflowFieldError(params) {
|
|
14
14
|
if (Object.hasOwn(params, "workflowScript"))
|
|
@@ -56,7 +56,7 @@ export function normalizePublicSubagentExecution(params, options = {}) {
|
|
|
56
56
|
return { ok: false, error: "workflow must be true (the ```js workflow block in this reply), a script path containing '/', or a named workflow resource.", mode: "workflow" };
|
|
57
57
|
}
|
|
58
58
|
const hasNamedWorkflow = typeof workflow === "string" && !isWorkflowScriptPath(workflow);
|
|
59
|
-
// workflowScript is the internal script carrier; model tool calls
|
|
59
|
+
// workflowScript is the internal script carrier; model tool calls set it only through options.script.
|
|
60
60
|
if (workflow !== undefined && params.workflowScript !== undefined) {
|
|
61
61
|
return { ok: false, error: "workflow cannot be combined with an internal workflowScript.", mode: "workflow" };
|
|
62
62
|
}
|
|
@@ -2,10 +2,9 @@ const WORKFLOW_FENCE = /^```(?:js|javascript) workflow[ \t]*$/;
|
|
|
2
2
|
const PLAIN_JS_FENCE = /^```(?:js|javascript)[ \t]*$/;
|
|
3
3
|
const OPEN_FENCE = /^(`{3,}|~{3,})/;
|
|
4
4
|
const CLOSE_FENCE = /^(`{3,}|~{3,})[ \t]*$/;
|
|
5
|
-
//
|
|
6
|
-
// server summary, which drops fenced blocks
|
|
7
|
-
const SUMMARY_NOTE = "On some
|
|
8
|
-
const FILE_FALLBACK = `If the block is in your reply: ${SUMMARY_NOTE}`;
|
|
5
|
+
// Through the Claude Agent SDK (pi-claude-bridge#35), text written after a tool call in the same turn reaches the session
|
|
6
|
+
// only as a server summary, which drops fenced blocks; retrying the block fails again. Tool arguments arrive intact.
|
|
7
|
+
const SUMMARY_NOTE = "On some hosts, text written after a tool call in the same turn reaches the session only as a summary without code blocks, so for the rest of this turn pass the script's source as a tool argument: options: { script: \"...\" } in place of workflow: true, with the call's other fields unchanged.";
|
|
9
8
|
/**
|
|
10
9
|
* Pi persists the whole assistant message before running its tool calls, so the
|
|
11
10
|
* message that issued this subagent call is on the branch and carries the script.
|
|
@@ -40,7 +39,7 @@ function scriptFromReply(content) {
|
|
|
40
39
|
const blocks = tagged.length > 0 ? tagged : plain.length === 1 ? plain : [];
|
|
41
40
|
if (blocks.length !== 1) {
|
|
42
41
|
const untagged = tagged.length === 0 && plain.length > 1 ? ` This reply has ${plain.length} untagged js blocks; open the script's block with the line "\`\`\`js workflow".` : "";
|
|
43
|
-
return { error: `workflow: true needs one fenced block in the same reply as the tool call, opened with the exact line "\`\`\`js workflow" and closed with "\`\`\`"; found ${tagged.length}.${untagged} ${
|
|
42
|
+
return { error: `workflow: true needs one fenced block in the same reply as the tool call, opened with the exact line "\`\`\`js workflow" and closed with "\`\`\`"; found ${tagged.length}.${untagged} If the block is in your reply: ${SUMMARY_NOTE}` };
|
|
44
43
|
}
|
|
45
44
|
const script = blocks[0];
|
|
46
45
|
if (!script.trim())
|
package/src/extension/rpc.js
CHANGED
|
@@ -5,7 +5,7 @@ import { deliverAsyncRunStop } from "../runs/foreground/async-stop-action.js";
|
|
|
5
5
|
import { reconcileAsyncRun } from "../runs/background/stale-run-reconciler.js";
|
|
6
6
|
import { resolveCurrentSessionId } from "../shared/session-identity.js";
|
|
7
7
|
import { ownerSessionIds, ownsRun } from "../runs/shared/run-ownership.js";
|
|
8
|
-
import { DIRS, SUBAGENT_ASYNC_COMPLETE_EVENT, SUBAGENT_CHILD_STATUS_EVENT, SUBAGENT_PROCESS_TERMINAL_EVENT, SUBAGENT_LIFECYCLE_ARTIFACT_VERSION, } from "../shared/types.js";
|
|
8
|
+
import { DIRS, SUBAGENT_ASYNC_COMPLETE_EVENT, SUBAGENT_ASYNC_STARTED_EVENT, SUBAGENT_CHILD_STATUS_EVENT, SUBAGENT_JOBS_CHANGED_EVENT, SUBAGENT_PROCESS_TERMINAL_EVENT, SUBAGENT_LIFECYCLE_ARTIFACT_VERSION, } from "../shared/types.js";
|
|
9
9
|
import { sanitizeDisplayText, truncateDisplayText } from "../shared/display-text.js";
|
|
10
10
|
import { readStatus } from "../shared/utils.js";
|
|
11
11
|
import { SubagentFlatParams } from "./schemas.js";
|
|
@@ -336,8 +336,10 @@ function pingData(ctx, options) {
|
|
|
336
336
|
request: SUBAGENT_RPC_REQUEST_EVENT,
|
|
337
337
|
replyPrefix: SUBAGENT_RPC_REPLY_EVENT_PREFIX,
|
|
338
338
|
asyncComplete: SUBAGENT_ASYNC_COMPLETE_EVENT,
|
|
339
|
+
asyncStarted: SUBAGENT_ASYNC_STARTED_EVENT,
|
|
339
340
|
childStatus: SUBAGENT_CHILD_STATUS_EVENT,
|
|
340
341
|
processTerminal: SUBAGENT_PROCESS_TERMINAL_EVENT,
|
|
342
|
+
jobsChanged: SUBAGENT_JOBS_CHANGED_EVENT,
|
|
341
343
|
},
|
|
342
344
|
session: sessionData(ctx),
|
|
343
345
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from "node:util";
|
|
2
2
|
import { Compile } from "typebox/compile";
|
|
3
|
+
import { Value } from "typebox/value";
|
|
3
4
|
import { closestMatch } from "../shared/edit-distance.js";
|
|
4
5
|
import { removedModelWorkflowFieldError } from "./public-execution.js";
|
|
5
6
|
import { SUBAGENT_OPTION_KEYS, SubagentOptionParams, createSubagentParamsSchema } from "./schemas.js";
|
|
@@ -22,6 +23,15 @@ export function flattenSubagentToolOptions(params, disabled) {
|
|
|
22
23
|
const removedField = removedModelWorkflowFieldError(options);
|
|
23
24
|
if (removedField)
|
|
24
25
|
throw new Error(removedField);
|
|
26
|
+
// options.script carries script text as a tool argument for hosts that drop the reply's ```js block (SUMMARY_NOTE).
|
|
27
|
+
const { script, ...rest } = options;
|
|
28
|
+
if (script !== undefined) {
|
|
29
|
+
if (typeof script !== "string" || !script.trim())
|
|
30
|
+
throw new Error("subagent: options.script must be the workflow script's source text.");
|
|
31
|
+
if (topLevel.workflow !== undefined)
|
|
32
|
+
throw new Error("subagent: options.script replaces workflow; pass only one of them.");
|
|
33
|
+
return { ...flattenSubagentToolOptions({ ...topLevel, options: rest }, disabled), workflowScript: script };
|
|
34
|
+
}
|
|
25
35
|
const entries = Object.entries(options);
|
|
26
36
|
const enabledKeys = SUBAGENT_OPTION_KEYS.filter((key) => !disabled?.params.has(key));
|
|
27
37
|
const unknown = entries.map(([key]) => key).filter((key) => !optionKeys.has(key));
|
|
@@ -40,12 +50,15 @@ export function flattenSubagentToolOptions(params, disabled) {
|
|
|
40
50
|
const disabledKey = entries.find(([key]) => disabled?.params.has(key))?.[0];
|
|
41
51
|
if (disabledKey)
|
|
42
52
|
throw new Error(`subagent option 'options.${disabledKey}' is disabled by config ${disabled.params.get(disabledKey)}.`);
|
|
43
|
-
|
|
44
|
-
|
|
53
|
+
// Pi converts only the declared top-level parameters ("true" -> true); `options` is an untyped bag in the
|
|
54
|
+
// tool schema, so its values get the same conversion here. Some providers send nested scalars as strings.
|
|
55
|
+
const converted = Value.Convert(SubagentOptionParams, structuredClone(options));
|
|
56
|
+
if (!optionsValidator.Check(converted)) {
|
|
57
|
+
const errors = [...optionsValidator.Errors(converted)].slice(0, 4).map((error) => `options${error.instancePath.replaceAll("/", ".")} ${error.message}`);
|
|
45
58
|
throw new Error(`subagent: ${errors.join("; ")}.`);
|
|
46
59
|
}
|
|
47
|
-
const conflicts = entries.filter(([key, value]) => topLevel[key] !== undefined && !isDeepStrictEqual(topLevel[key], value)).map(([key]) => `'${key}'`);
|
|
60
|
+
const conflicts = Object.entries(converted).filter(([key, value]) => topLevel[key] !== undefined && !isDeepStrictEqual(topLevel[key], value)).map(([key]) => `'${key}'`);
|
|
48
61
|
if (conflicts.length > 0)
|
|
49
62
|
throw new Error(`subagent: ${conflicts.join(", ")} given both at the top level and in options with different values; give each once.`);
|
|
50
|
-
return { ...topLevel, ...
|
|
63
|
+
return { ...topLevel, ...converted };
|
|
51
64
|
}
|
|
@@ -13,7 +13,7 @@ import { childCacheRetentionEnv } from "../../shared/child-cache-retention.js";
|
|
|
13
13
|
import { buildEffectiveSystemPrompt } from "../shared/effective-system-prompt.js";
|
|
14
14
|
import { currentCompletionOwnerId } from "../../shared/completion-owner.js";
|
|
15
15
|
import { planChildLaunch, projectChainOutputSchemas, resolveStepBehavior, suppressProgressForReadOnlyTask } from "../shared/child-launch-plan.js";
|
|
16
|
-
import { formatHerdrMachineRunnerUnsupported, resolveMachinePlacement } from "../shared/herdr-machine.js";
|
|
16
|
+
import { formatHerdrMachineRunnerUnsupported, PLACED_RESTORE_UNSUPPORTED, resolveMachinePlacement } from "../shared/herdr-machine.js";
|
|
17
17
|
import { applyThinkingSuffix, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan } from "../shared/child-tool-plan.js";
|
|
18
18
|
import { assertClaudeCodeModelScope, isClaudeCodeAdapterId, resolveClaudeCodeOverride } from "../shared/claude-code-adapter.js";
|
|
19
19
|
import { injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.js";
|
|
@@ -1612,6 +1612,9 @@ export function executeAsyncSingle(id, params) {
|
|
|
1612
1612
|
const machineUnsupported = formatHerdrMachineRunnerUnsupported({ machine: requestedMachine, agentName: agentConfig.name, runnerType: agentConfig.runner?.type, adapter: agentConfig.runner?.type === "external-cli" ? agentConfig.runner.adapter : undefined, worktree: params.worktree });
|
|
1613
1613
|
if (machineUnsupported)
|
|
1614
1614
|
return formatAsyncStartError("single", machineUnsupported);
|
|
1615
|
+
// Refuse before the runner starts; the placed launch would refuse only after the run exists.
|
|
1616
|
+
if (requestedMachine && params.revivalLease)
|
|
1617
|
+
return formatAsyncStartError("single", PLACED_RESTORE_UNSUPPORTED);
|
|
1615
1618
|
const resolvedLauncher = resolveAgentRunnerLauncher(agentConfig, ctx.runnerLaunchers, requestedMachine);
|
|
1616
1619
|
if (resolvedLauncher.error)
|
|
1617
1620
|
return formatAsyncStartError("single", resolvedLauncher.error);
|
|
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { renderWidget, widgetRenderKey } from "../../tui/render.js";
|
|
4
4
|
import { formatControlNoticeMessage } from "../shared/subagent-control.js";
|
|
5
|
-
import { DIRS, SUBAGENT_CHILD_STATUS_EVENT, SUBAGENT_CONTROL_EVENT, SUBAGENT_CONTROL_INTERCOM_EVENT, SUBAGENT_STEERING_NOTICE_EVENT, WIDGET_ANIMATION_INTERVAL_MS, } from "../../shared/types.js";
|
|
5
|
+
import { DIRS, SUBAGENT_CHILD_STATUS_EVENT, SUBAGENT_CONTROL_EVENT, SUBAGENT_CONTROL_INTERCOM_EVENT, SUBAGENT_JOBS_CHANGED_EVENT, SUBAGENT_STEERING_NOTICE_EVENT, WIDGET_ANIMATION_INTERVAL_MS, } from "../../shared/types.js";
|
|
6
6
|
import { readStatus, resolveWatchPath } from "../../shared/utils.js";
|
|
7
7
|
import { normalizeParallelGroups } from "./parallel-groups.js";
|
|
8
8
|
import { reconcileAsyncRun, reconcileNestedAsyncDescendants } from "./stale-run-reconciler.js";
|
|
@@ -63,6 +63,7 @@ export function createAsyncJobTracker(pi, state, asyncDirRoot, options = {}) {
|
|
|
63
63
|
};
|
|
64
64
|
const rerenderWidget = (ctx, jobs = Array.from(state.asyncJobs.values())) => {
|
|
65
65
|
options.onJobsChanged?.();
|
|
66
|
+
pi.events.emit(SUBAGENT_JOBS_CHANGED_EVENT, { version: 1 });
|
|
66
67
|
if (state.widgetsSuspended)
|
|
67
68
|
return;
|
|
68
69
|
renderWidget(ctx, options.widgetEnabled === false ? [] : jobs, options.widgetCollapsed, options.widgetLayout);
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { snapshotBackgroundWork } from "../../api/background-work.js";
|
|
2
1
|
import { listAsyncRuns } from "./async-status.js";
|
|
3
|
-
import { waitForSubagents, waitRunScopes } from "./subagent-wait.js";
|
|
2
|
+
import { backgroundWorkForSession, waitForSubagents, waitRunScopes } from "./subagent-wait.js";
|
|
4
3
|
export const DEFAULT_AUTO_DRAIN_TIMEOUT_MS = 30 * 60 * 1000;
|
|
5
4
|
function resultText(value) {
|
|
6
5
|
return value.content.map((part) => part.type === "text" ? part.text : "").join(" ").trim();
|
|
@@ -14,7 +13,7 @@ function hasOutstandingWork(deps, sessionId, nowMs, observation) {
|
|
|
14
13
|
includeNested: false,
|
|
15
14
|
}, observation?.status));
|
|
16
15
|
const detachedForeground = [...(deps.state.foregroundRuns?.values() ?? [])].some((run) => run.sessionId === sessionId && run.children.some((child) => child.status === "detached"));
|
|
17
|
-
return asyncRuns.length > 0 ||
|
|
16
|
+
return asyncRuns.length > 0 || backgroundWorkForSession(deps, nowMs).items.length > 0 || detachedForeground;
|
|
18
17
|
}
|
|
19
18
|
/** Drain all work owned by the current headless session, including work added while draining. */
|
|
20
19
|
export async function drainOutstandingWork(deps, observation) {
|
|
@@ -164,7 +164,7 @@ function sessionMessageParts(record) {
|
|
|
164
164
|
}
|
|
165
165
|
if (entry.type === "toolCall" || entry.type === "tool_call") {
|
|
166
166
|
const name = typeof entry.name === "string" ? entry.name : typeof entry.toolName === "string" ? entry.toolName : "tool";
|
|
167
|
-
parts.push({ role, kind: "toolCall", name, text: `[tool: ${name}${entry.
|
|
167
|
+
parts.push({ role, kind: "toolCall", name, text: `[tool: ${name}${entry.arguments === undefined ? "" : ` ${stringifyJsonPreview(entry.arguments)}`}]` });
|
|
168
168
|
continue;
|
|
169
169
|
}
|
|
170
170
|
if (entry.type === "toolResult" || entry.type === "tool_result") {
|