pi-subagents 0.82.1 → 0.83.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 +49 -0
- package/bundle/agents.js +5 -5
- package/bundle/background-work.js +1 -1
- package/bundle/capability-ceiling.js +1 -1
- package/bundle/child-tool-plan.js +4 -4
- package/bundle/{chunk-RMQFMMAT.js → chunk-2D52JFLF.js} +231 -10
- package/bundle/{chunk-52UBALJL.js → chunk-2DHZTCOL.js} +2 -2
- package/bundle/{chunk-43SCHIMD.js → chunk-2S4K53MV.js} +1 -1
- package/bundle/{chunk-IVAE5Z6Q.js → chunk-5YIITCPD.js} +31 -27
- package/bundle/{chunk-JYUKW2SY.js → chunk-AHIFYGR3.js} +13 -13
- package/bundle/{chunk-XSWOTIA3.js → chunk-C5P5CHYK.js} +1 -1
- package/bundle/{chunk-PDZWJCHD.js → chunk-C7JMPU5F.js} +10 -7
- package/bundle/{chunk-5TYMSZX3.js → chunk-D3JAFGMO.js} +2 -2
- package/bundle/{chunk-OYF6AFDY.js → chunk-F4ASL6OV.js} +2 -2
- package/bundle/{chunk-ZIV4J4HG.js → chunk-F5DBXJ2G.js} +51 -34
- package/bundle/{chunk-YKEWIB4M.js → chunk-G2Z6ZH26.js} +1 -1
- package/bundle/{chunk-G4GZD3OJ.js → chunk-IPGZMQXV.js} +11 -11
- package/bundle/{chunk-5LFLVSTM.js → chunk-JEZOSEWG.js} +2 -0
- package/bundle/{chunk-PLCCBLPY.js → chunk-LFDBD4PW.js} +2 -2
- package/bundle/{chunk-CIZQXJ5Q.js → chunk-LNRJCP2Y.js} +56 -20
- package/bundle/{chunk-UTT254JE.js → chunk-MABL3FUM.js} +8 -4
- package/bundle/{chunk-3JCDWF4V.js → chunk-NPIYWVOD.js} +12 -18
- package/bundle/{chunk-UCPP4GNU.js → chunk-QP5ZWNIS.js} +12 -5
- package/bundle/{chunk-Z7KVHQHL.js → chunk-QXASAHIA.js} +2 -2
- package/bundle/{chunk-46XKCPJR.js → chunk-RNIZLSEA.js} +2221 -3260
- package/bundle/{chunk-GJECHSAR.js → chunk-SSG7IEP5.js} +2 -2
- package/bundle/{chunk-VSUSLDNW.js → chunk-UFL7IYCW.js} +10 -8
- package/bundle/{chunk-QE4AGIMG.js → chunk-UUP372DR.js} +482 -705
- package/bundle/{chunk-6XJB7PIX.js → chunk-UYK5MZWE.js} +16 -12
- package/bundle/{chunk-4SDU2BZX.js → chunk-V2BZYLEP.js} +3 -3
- package/bundle/{chunk-FXUXCX2K.js → chunk-VLFZIN6D.js} +6 -6
- package/bundle/{chunk-N2BN7BF5.js → chunk-XA3DY6UG.js} +7 -5
- package/bundle/{chunk-PBK2I7WY.js → chunk-YXFJVCOL.js} +6 -6
- package/bundle/{chunk-4Y2ROR7L.js → chunk-ZVOAMTUQ.js} +5 -5
- package/bundle/control-channel.js +4 -4
- package/bundle/index.js +1 -1
- package/bundle/intercom-bridge.js +4 -4
- package/bundle/preflight.js +10 -10
- package/bundle/project-panes.js +5 -5
- package/bundle/shared-types.js +1 -1
- package/docs/configuration.md +13 -2
- package/docs/extension-api.md +21 -4
- package/docs/missions.md +2 -0
- package/docs/observability.md +1 -0
- package/docs/tool-reference.md +2 -2
- package/package.json +1 -1
- package/skills/pi-subagents/SKILL.md +6 -6
- package/src/agents/agent-management.d.ts +1 -1
- package/src/agents/agent-management.js +10 -16
- package/src/agents/agent-serializer.js +1 -2
- package/src/agents/agents.js +20 -5
- package/src/agents/frontmatter.js +22 -18
- package/src/api/background-work.d.ts +9 -6
- package/src/api/background-work.js +14 -7
- package/src/extension/index.js +6 -3
- 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/missions/actions.js +7 -2
- package/src/missions/lifecycle.d.ts +11 -0
- package/src/missions/lifecycle.js +37 -6
- package/src/missions/store.js +1 -0
- package/src/missions/types.d.ts +2 -0
- package/src/runs/background/async-execution.d.ts +1 -1
- package/src/runs/background/async-execution.js +7 -4
- package/src/runs/background/async-job-tracker.js +2 -1
- package/src/runs/background/async-resume.js +3 -2
- package/src/runs/background/auto-drain.js +2 -3
- package/src/runs/background/fleet-view.js +1 -1
- package/src/runs/background/retained-children.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 +26 -56
- package/src/runs/shared/capability-ceiling.d.ts +2 -1
- package/src/runs/shared/capability-ceiling.js +3 -2
- 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/parallel-handoff.d.ts +1 -1
- package/src/runs/shared/parallel-handoff.js +10 -6
- 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 +4 -2
- package/src/shared/types.js +2 -0
- package/src/workflows/scripted-workflow.js +13 -1071
- package/workflow-script-worker.cjs +1068 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,55 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.83.1] - 2026-10-27
|
|
6
|
+
|
|
7
|
+
### Highlights
|
|
8
|
+
|
|
9
|
+
- Stopping, interrupting, or resuming a subagent can no longer act on the wrong run: an interrupt with no id is refused, and resuming a worktree child while it is still cleaning up no longer runs it in your main checkout.
|
|
10
|
+
- A mission you start on purpose stays open until its result is checked, instead of showing `completed` on the writer's own report.
|
|
11
|
+
- Capability limits registered by session id, as the docs show, now cover background runs and workflow children, not only foreground launches.
|
|
12
|
+
- Agent files with a blank line inside an indented block are read in full, so settings below that line are no longer dropped.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- `subagent({ action: "interrupt" })` without an id is now refused with "action='interrupt' requires id.", the same as `stop`, `steer`, and `resume`. Before, it interrupted the last foreground run or the newest background run, so a call that lost its id could interrupt the wrong child. When an id matches no run, `stop`, `interrupt`, and `resume` now name that id in the error. Thanks to [@mannainsect](https://github.com/mannainsect) for the report. (#3081)
|
|
17
|
+
- Resuming a background child that ran in its own worktree (`worktree: true`) no longer continues it in your main checkout. If you resumed it in the few seconds while it was saving its changes and removing its worktree, the child could start in your main checkout and commit or switch branches there, or start in a deleted worktree. That resume is now refused with a message to retry once the run finishes cleaning up. Thanks to [@peskycipher](https://github.com/peskycipher) for the report and [@isjiajia01](https://github.com/isjiajia01) for narrowing it down. (#3083)
|
|
18
|
+
- A mission you start with `mission` or `missionId` no longer becomes `completed` when nothing confirmed its result. Before, a background writer run with only its own report marked its mission `completed`, so a claimed result looked like a proven one. When the run, or any workflow child in the mission, ends with acceptance `review-required`, `rejected`, `claimed`, or `pending`, the mission now stays `waiting`, and `mission.show` lists each child's acceptance status. Close it with `mission.close` after you check the result. A `gate` or `acceptance.verify` command alone does not complete a writer run's mission, because writer runs also need a review; add `review: { required: false }` to the `acceptance` policy when the command is enough ([missions.md](docs/missions.md)). Missions created automatically for ordinary launches still complete as before. Thanks to [@kiote](https://github.com/kiote). (#3078)
|
|
19
|
+
- A capability ceiling registered the way the docs show, with `ctx.sessionManager.getSessionId()`, now applies in saved sessions. Before, most checks looked it up by session file path, so background launches, workflow children, and `{ action: "list" }` ignored it and only foreground launches were blocked. Every check now uses the session id, so a ceiling registered by session file path no longer applies. Thanks to [@corobis](https://github.com/corobis) for the report. (#3087)
|
|
20
|
+
- A blank line inside an indented block in an agent file's frontmatter, such as an `external-cli` script argument with an empty line, cut the value off there and dropped every setting below it. The whole block is now read. Lists that used to stop at a blank line, such as `tools:`, now include their later entries, so an agent can get tools it did not get before. `|` and `>` blocks are unchanged. Thanks to [@jbachorik](https://github.com/jbachorik). (#3089)
|
|
21
|
+
- A workflow script that called `runs.run({ key, agent, task })` with a single object passed validation and then failed with "runs.run has an invalid key.". Validation now rejects that call, and both the validation error and the running script show the right forms: `runs.run("plan", { agent, task })` or `runs.all([{ key, agent, task }])`. Thanks to [@brunovcosta](https://github.com/brunovcosta) for the report. (#3088)
|
|
22
|
+
- In a project you have not trusted, agent lookup no longer scans the project's `.git`, `.pi`, and `.agents` folders on every call. It already ignored what was in them, but it still listed them to decide when to refresh, so ordinary git activity made it rebuild its agent list. Thanks to [@ArietidsZ](https://github.com/ArietidsZ). (#3080)
|
|
23
|
+
- Long errors and verdicts in the `runs.lanes` result are now cut at a whole character, so they no longer end in a broken character or go over the 256-byte limit. Thanks to [@XLi-hub](https://github.com/XLi-hub). (#3097)
|
|
24
|
+
|
|
25
|
+
## [0.83.0] - 2026-10-25
|
|
26
|
+
|
|
27
|
+
### Highlights
|
|
28
|
+
|
|
29
|
+
- When an extension or tool search changes the system prompt or tools during a session, subagent results no longer make every later turn miss the prompt cache, so those turns are cheaper and faster again. A child that runs its own subagents keeps its cache too.
|
|
30
|
+
- Subagents that finished their work always report it: background runs no longer end as "exited with code 0 before writing a result", and foreground calls no longer hang when the child stops after its final reply.
|
|
31
|
+
- Children placed on another machine (a Rex tab, Rex over SSH, or a Herdr pane) launch instead of being refused, and workflows no longer crash on Windows standalone Pi builds.
|
|
32
|
+
- Extensions and editors that show subagent activity can refresh on a new event instead of polling, and `bg_wait` now waits for background jobs that other extensions report, such as surf-cli's.
|
|
33
|
+
- A typo in an agent override in settings, such as `modle` or a thinking level like `hgih`, now fails with a "Did you mean" hint instead of being ignored.
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- Extensions and editor integrations that show background subagent activity no longer need to poll for changes. A new `subagent:jobs-changed` event tells them to read `status` again whenever a background run starts, moves to its next step, reports progress, or finishes, including chain and parallel step changes that no event reported before. The RPC `ping` reply now lists it, along with the existing `subagent:async-started` event, and [extension-api.md](docs/extension-api.md#refresh-a-host-view-without-polling) shows how to use them. Thanks to [@raviy06](https://github.com/raviy06) for the proposal. (#3068)
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- When an extension or tool search changed the system prompt or tools during a session, the first request of every new turn after the first subagent notice missed the prompt cache, so most of the context was sent uncached again each turn. A child that had called `subagent` missed it on every later request. On models where Pi adds those changes further down the conversation instead of rewriting the start of the prompt (Anthropic and OpenAI models, among others), the way pi-subagents marked its notices and removed parent-only subagent history made Pi move them all back into the start of the prompt, which then changed every turn. Both now leave those changes where they are. Thanks to [@adameq](https://github.com/adameq) for the report. (#3058, #3063)
|
|
42
|
+
- Background subagents that finished their work no longer end as "exited with code 0 before writing a result". When a child stopped after its final reply without reporting that it had settled, or when one of its extensions hung on shutdown, the background runner process exited with the result still unwritten. It now ends the child after its final reply, waits for any compaction that runs after that reply, and waits at most 5 seconds for shutdown handlers. A foreground subagent whose child stops the same way now returns its reply too, instead of the call never returning. Thanks to [@perriea](https://github.com/perriea). (#3051)
|
|
43
|
+
- Background jobs that other extensions report to `bg_wait`, such as surf-cli's or pi-agent-wait's, are now waited for in saved sessions. Before, `bg_wait` and the wait at the end of a `pi -p` run matched a job only when the extension named the session by its file path, so a job named by the session id was ignored and they returned while it was still running. Both forms are now accepted. Thanks to [@cad0p](https://github.com/cad0p) for the report. (#3071)
|
|
44
|
+
- With features turned off in `disabledFeatures`, every `guide` topic now starts with a notice listing what is disabled, and every section with a one-line pointer to that list, not only the `tool-reference` topic. Before, an agent reading the workflow guide with `workflow-scripts` disabled was taught `workflow: true` and `args`, and its call was rejected. With workflow scripts disabled, the notice also says to use the top-level `tasks` or `chain` fields instead. Thanks to [@jefftheai](https://github.com/jefftheai) for the report. (#3057)
|
|
45
|
+
- When text written after a tool call reaches Pi only as a summary, as happens through the Claude Agent SDK used by pi-claude-bridge, a lost ```` ```js workflow ```` block no longer means writing the script to a file first. The error now says to resend the source as `options: { script: "..." }`, which runs it directly. (#3056)
|
|
46
|
+
- A fresh child placed on a machine (a Rex tab, a Rex machine over SSH, or a Herdr pane) launches. Every such launch was refused with "does not support fork, resume, or revival" because each child is handed a local session file path, which placement read as a request to restore a transcript. Builtin agents with a tools list, such as `scout`, can be placed too: their required tools are checked against the tools the remote Pi activates. Forked context is still refused on a machine, and so is a resume that would place the child again, since a placed child's transcript stays on that machine. Thanks to [@honbolau](https://github.com/honbolau) for the report and [@isjiajia01](https://github.com/isjiajia01) for tracing it. (#3043)
|
|
47
|
+
- A misspelled field in a `subagents.agentOverrides` or `agentOverridesByProvider` entry, such as `modle`, was ignored; loading now fails with the settings file, the field, and the nearest known name (`Did you mean 'model'?`). An override's `thinking` must now be a known level (`off` through `max`) or `false`; a typo such as `hgih` used to be appended to the child's model id. Creating or updating an agent with the `subagent` tool now rejects such a level too, instead of writing it into the agent file. Thanks to [@isjiajia01](https://github.com/isjiajia01) for the report. (#3050)
|
|
48
|
+
- Creating an agent with `thinking: off`, or updating one to ` off ` with spaces, now saves `thinking: off` in the agent file. Before, the line was left out, so the agent used the default thinking level instead of none.
|
|
49
|
+
- `extensionBindings` now reach foreground children. Before, an extension in a foreground child saw no `PI_SUBAGENT_EXTENSION_BINDINGS`, or the bindings of the background child that launched it. A foreground child now gets its own bindings while its extensions load and run `session_start`, and the parent's environment is restored afterwards. Thanks to [@crisboarna](https://github.com/crisboarna). (#3049)
|
|
50
|
+
- Workflows no longer crash on Windows standalone Pi builds (Bun 1.3.14) once Bun's transpiler cache is warm. The workflow worker now starts from a file in the package. Thanks to [@temoc](https://github.com/temoc). (#3055)
|
|
51
|
+
- `subagent` calls from providers that send nested tool values as strings, such as `options: { capabilities: "true", timeoutMs: "120000" }`, no longer fail with "must be boolean" or "must be integer". Values inside `options` now get the same type conversion Pi already applies to top-level parameters. Thanks to [@5hiri](https://github.com/5hiri) for the report and [@isjiajia01](https://github.com/isjiajia01) for narrowing it down. (#3048)
|
|
52
|
+
- Child transcripts in `/subagents-fleet`, `subagent` status and inspect replies show each tool call's input (`[tool: bash {"command":"ls"}]`) instead of only the tool name. Thanks to [@kunkun9527](https://github.com/kunkun9527) for the report. (#3053)
|
|
53
|
+
|
|
5
54
|
## [0.82.1] - 2026-10-24
|
|
6
55
|
|
|
7
56
|
### Highlights
|
package/bundle/agents.js
CHANGED
|
@@ -2,14 +2,14 @@ import {
|
|
|
2
2
|
RUNTIME_AGENT_REGISTER_EVENT,
|
|
3
3
|
RUNTIME_AGENT_REGISTER_VERSION,
|
|
4
4
|
registerAgentViaEvents
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-2S4K53MV.js";
|
|
6
6
|
import {
|
|
7
7
|
registerRuntimeAgent
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-G2Z6ZH26.js";
|
|
9
|
+
import "./chunk-LNRJCP2Y.js";
|
|
10
|
+
import "./chunk-JEZOSEWG.js";
|
|
11
11
|
import "./chunk-YFCK7H2S.js";
|
|
12
|
-
import "./chunk-
|
|
12
|
+
import "./chunk-QXASAHIA.js";
|
|
13
13
|
export {
|
|
14
14
|
RUNTIME_AGENT_REGISTER_EVENT,
|
|
15
15
|
RUNTIME_AGENT_REGISTER_VERSION,
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
registerSubagentCapabilityCeiling,
|
|
9
9
|
resolveCurrentSubagentCapabilityCeiling,
|
|
10
10
|
resolveSubagentCapabilityCeiling
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-QXASAHIA.js";
|
|
12
12
|
export {
|
|
13
13
|
SUBAGENT_CAPABILITY_CEILING_REGISTRY_KEY,
|
|
14
14
|
SUBAGENT_CAPABILITY_CEILING_VERSION,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
resolvePiLaunchToolPlan
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-V2BZYLEP.js";
|
|
4
4
|
import "./chunk-CTPAE36Y.js";
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
import "./chunk-LNRJCP2Y.js";
|
|
6
|
+
import "./chunk-JEZOSEWG.js";
|
|
7
7
|
import "./chunk-YFCK7H2S.js";
|
|
8
|
-
import "./chunk-
|
|
8
|
+
import "./chunk-QXASAHIA.js";
|
|
9
9
|
export {
|
|
10
10
|
resolvePiLaunchToolPlan
|
|
11
11
|
};
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CODE_OWNED_EXTERNAL_CLI_ADAPTER_IDS,
|
|
3
|
+
getAgentDir,
|
|
4
|
+
getProjectConfigDir
|
|
5
|
+
} from "./chunk-LNRJCP2Y.js";
|
|
6
|
+
|
|
1
7
|
// src/runs/shared/rex-machine.ts
|
|
2
8
|
import { spawnSync } from "node:child_process";
|
|
3
9
|
import * as fs from "node:fs";
|
|
@@ -67,6 +73,11 @@ function resolveRexLocalPlacement(input) {
|
|
|
67
73
|
return machine;
|
|
68
74
|
}
|
|
69
75
|
|
|
76
|
+
// src/runs/shared/herdr-machine.ts
|
|
77
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
78
|
+
import * as fs3 from "node:fs";
|
|
79
|
+
import * as path3 from "node:path";
|
|
80
|
+
|
|
70
81
|
// src/runs/shared/herdr-connection.ts
|
|
71
82
|
import { spawn, spawnSync as spawnSync2 } from "node:child_process";
|
|
72
83
|
import * as fs2 from "node:fs";
|
|
@@ -127,7 +138,7 @@ function runHerdrRemoteCommandAsync(machine, command, options = {}) {
|
|
|
127
138
|
return runBoundedCommand(options.sshBin ?? "ssh", [...herdrSshArgs(options.env), machine.target, command], { env: hardenedSshEnv(options.env), timeout: options.timeout, maxBuffer: options.maxBuffer });
|
|
128
139
|
}
|
|
129
140
|
function runBoundedCommand(file, args, options) {
|
|
130
|
-
return new Promise((
|
|
141
|
+
return new Promise((resolve3) => {
|
|
131
142
|
const child = spawn(file, args, { env: options.env, stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
|
|
132
143
|
const stdout = [], stderr = [], maxBuffer = options.maxBuffer ?? MAX_DISCOVERY_BYTES;
|
|
133
144
|
let stdoutBytes = 0, stderrBytes = 0, error, settled = false, escalation;
|
|
@@ -136,7 +147,7 @@ function runBoundedCommand(file, args, options) {
|
|
|
136
147
|
settled = true;
|
|
137
148
|
clearTimeout(timer);
|
|
138
149
|
if (escalation) clearTimeout(escalation);
|
|
139
|
-
|
|
150
|
+
resolve3({ status, stdout: Buffer.concat(stdout).toString("utf8"), stderr: Buffer.concat(stderr).toString("utf8"), ...spawnError ? { error: spawnError } : {} });
|
|
140
151
|
};
|
|
141
152
|
const terminate = () => {
|
|
142
153
|
child.kill("SIGTERM");
|
|
@@ -194,7 +205,7 @@ var SocketRpcClient = class {
|
|
|
194
205
|
this.#subscriptionAckTimeoutMs = subscriptionAckTimeoutMs;
|
|
195
206
|
}
|
|
196
207
|
call(method, params = {}, timeoutMs = 15e3) {
|
|
197
|
-
return new Promise((
|
|
208
|
+
return new Promise((resolve3, reject) => {
|
|
198
209
|
const socket = net.createConnection(this.#socketPath);
|
|
199
210
|
let bytes = 0;
|
|
200
211
|
let data = Buffer.alloc(0);
|
|
@@ -207,7 +218,7 @@ var SocketRpcClient = class {
|
|
|
207
218
|
finished = true;
|
|
208
219
|
clearTimeout(timer);
|
|
209
220
|
socket.destroy();
|
|
210
|
-
error ? reject(error) :
|
|
221
|
+
error ? reject(error) : resolve3(value);
|
|
211
222
|
};
|
|
212
223
|
socket.on("connect", () => socket.write(`${JSON.stringify({ id, method, params })}
|
|
213
224
|
`));
|
|
@@ -244,8 +255,8 @@ var SocketRpcClient = class {
|
|
|
244
255
|
const id = String(++this.#next);
|
|
245
256
|
let buffer = Buffer.alloc(0), bytes = 0, acknowledged = false, intentional = false, notified = false, settled = false;
|
|
246
257
|
let resolveAck, rejectAck;
|
|
247
|
-
const acknowledgement = new Promise((
|
|
248
|
-
resolveAck =
|
|
258
|
+
const acknowledgement = new Promise((resolve3, reject) => {
|
|
259
|
+
resolveAck = resolve3;
|
|
249
260
|
rejectAck = reject;
|
|
250
261
|
});
|
|
251
262
|
const failHandshake = (error) => {
|
|
@@ -339,12 +350,12 @@ async function waitForSocket(socketPath, child) {
|
|
|
339
350
|
}
|
|
340
351
|
} catch {
|
|
341
352
|
}
|
|
342
|
-
await new Promise((
|
|
353
|
+
await new Promise((resolve3) => setTimeout(resolve3, 25));
|
|
343
354
|
}
|
|
344
355
|
throw new Error("SSH StreamLocal forwarding did not create its local socket.");
|
|
345
356
|
}
|
|
346
357
|
async function terminateChild(child) {
|
|
347
|
-
const wait = (ms) => child.exitCode !== null || child.signalCode !== null ? Promise.resolve() : Promise.race([new Promise((
|
|
358
|
+
const wait = (ms) => child.exitCode !== null || child.signalCode !== null ? Promise.resolve() : Promise.race([new Promise((resolve3) => child.once("exit", () => resolve3())), new Promise((resolve3) => setTimeout(resolve3, ms))]);
|
|
348
359
|
if (child.exitCode === null && child.signalCode === null) child.kill("SIGTERM");
|
|
349
360
|
await wait(1e3);
|
|
350
361
|
if (child.exitCode === null && child.signalCode === null) {
|
|
@@ -397,12 +408,220 @@ async function connectHerdrMachine(machine, options = {}) {
|
|
|
397
408
|
return { endpoint, socketPath: primary.socketPath, client: new SocketRpcClient(primary.socketPath), forwardRemoteSocket: sockets.forwardRemoteSocket, close: sockets.close };
|
|
398
409
|
}
|
|
399
410
|
|
|
411
|
+
// src/runs/shared/herdr-machine.ts
|
|
412
|
+
var MAX_MACHINE_NAME_LENGTH = 128;
|
|
413
|
+
var HERDR_MACHINE_LIST_TIMEOUT_MS = 7500;
|
|
414
|
+
var MAX_HERDR_MACHINE_LIST_BYTES = 1024 * 1024;
|
|
415
|
+
var CONTROL_CHARS = /[\u0000-\u001f\u007f]/u;
|
|
416
|
+
var SUPPORTED_MACHINE_ADAPTERS = new Set(CODE_OWNED_EXTERNAL_CLI_ADAPTER_IDS);
|
|
417
|
+
function validateMachineName(value) {
|
|
418
|
+
const trimmed = value.trim();
|
|
419
|
+
if (!trimmed) throw new Error("Herdr machine id or label is required.");
|
|
420
|
+
if (trimmed.length > MAX_MACHINE_NAME_LENGTH) throw new Error(`Herdr machine '${trimmed.slice(0, 24)}\u2026' exceeds ${MAX_MACHINE_NAME_LENGTH} characters.`);
|
|
421
|
+
if (CONTROL_CHARS.test(trimmed)) throw new Error("Herdr machine id or label contains control characters.");
|
|
422
|
+
return trimmed;
|
|
423
|
+
}
|
|
424
|
+
function validateTarget(value, requested) {
|
|
425
|
+
const target = value.trim();
|
|
426
|
+
if (!target || target.startsWith("-") || /[\s\u0000-\u001f\u007f]/u.test(target)) {
|
|
427
|
+
throw new Error(`Herdr machine '${requested}' has an ssh target that cannot be passed safely: ${JSON.stringify(target)}.`);
|
|
428
|
+
}
|
|
429
|
+
return target;
|
|
430
|
+
}
|
|
431
|
+
function isRemoteAbsolute(value) {
|
|
432
|
+
return value.startsWith("/") || value === "~" || value.startsWith("~/");
|
|
433
|
+
}
|
|
434
|
+
function validateRemoteCwd(value, requested) {
|
|
435
|
+
const cwd = value.trim();
|
|
436
|
+
if (!isRemoteAbsolute(cwd)) throw new Error(`Herdr machine '${requested}' cwd must be an absolute POSIX path or start with '~': ${JSON.stringify(cwd)}.`);
|
|
437
|
+
if (CONTROL_CHARS.test(cwd)) throw new Error(`Herdr machine '${requested}' cwd contains control characters.`);
|
|
438
|
+
return cwd.length > 1 ? cwd.replace(/\/+$/u, "") : cwd;
|
|
439
|
+
}
|
|
440
|
+
function parseMachineCatalog(json) {
|
|
441
|
+
let parsed;
|
|
442
|
+
try {
|
|
443
|
+
parsed = JSON.parse(json);
|
|
444
|
+
} catch (error) {
|
|
445
|
+
throw new Error(`Failed to parse herdr machine list --json: ${error instanceof Error ? error.message : String(error)}`);
|
|
446
|
+
}
|
|
447
|
+
const entries = Array.isArray(parsed) ? parsed : parsed && typeof parsed === "object" && Array.isArray(parsed.machines) ? parsed.machines : void 0;
|
|
448
|
+
if (!entries) throw new Error("herdr machine list --json returned no machine list.");
|
|
449
|
+
return entries.flatMap((entry) => {
|
|
450
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return [];
|
|
451
|
+
const record = entry;
|
|
452
|
+
if (typeof record.id !== "string" || !record.id.trim() || typeof record.target !== "string") return [];
|
|
453
|
+
return [{
|
|
454
|
+
id: record.id.trim(),
|
|
455
|
+
...typeof record.label === "string" && record.label.trim() ? { label: record.label.trim() } : {},
|
|
456
|
+
target: record.target,
|
|
457
|
+
...typeof record.session === "string" && record.session.trim() ? { session: record.session.trim() } : {},
|
|
458
|
+
enabled: record.enabled !== false
|
|
459
|
+
}];
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
function readHerdrMachineCatalog(env, herdrBin) {
|
|
463
|
+
const result = spawnSync3(herdrBin, ["machine", "list", "--json"], {
|
|
464
|
+
env,
|
|
465
|
+
encoding: "utf-8",
|
|
466
|
+
maxBuffer: MAX_HERDR_MACHINE_LIST_BYTES,
|
|
467
|
+
timeout: HERDR_MACHINE_LIST_TIMEOUT_MS,
|
|
468
|
+
windowsHide: true
|
|
469
|
+
});
|
|
470
|
+
if (result.error) {
|
|
471
|
+
const code = result.error.code;
|
|
472
|
+
if (code === "ENOENT") throw new Error(`Herdr CLI '${herdrBin}' was not found on PATH. Saved-machine placement needs Herdr installed locally.`);
|
|
473
|
+
throw new Error(`Failed to run herdr machine list --json: ${result.error.message}`);
|
|
474
|
+
}
|
|
475
|
+
if (result.status !== 0) throw new Error(`herdr machine list --json exited with code ${result.status}: ${(result.stderr || result.stdout).trim()}`);
|
|
476
|
+
return parseMachineCatalog(result.stdout);
|
|
477
|
+
}
|
|
478
|
+
function selectMachine(catalog, requested) {
|
|
479
|
+
const byId = catalog.find((entry) => entry.id === requested);
|
|
480
|
+
const matches = byId ? [byId] : catalog.filter((entry) => entry.label === requested);
|
|
481
|
+
if (matches.length > 1) throw new Error(`Machine label '${requested}' is ambiguous; use its profile ID.`);
|
|
482
|
+
const machine = matches[0];
|
|
483
|
+
if (!machine) {
|
|
484
|
+
const saved = catalog.filter((entry) => entry.enabled).map((entry) => entry.label ?? entry.id);
|
|
485
|
+
throw new Error(`Herdr machine '${requested}' was not found. Saved machines: ${saved.length ? saved.join(", ") : "none"}. Add one with herdr machine add <target> --label <name>.`);
|
|
486
|
+
}
|
|
487
|
+
if (!machine.enabled) throw new Error(`Machine '${requested}' is disabled. Run herdr machine enable ${machine.id}.`);
|
|
488
|
+
return machine;
|
|
489
|
+
}
|
|
490
|
+
function readJsonObject(filePath) {
|
|
491
|
+
if (!fs3.existsSync(filePath)) return {};
|
|
492
|
+
let parsed;
|
|
493
|
+
try {
|
|
494
|
+
parsed = JSON.parse(fs3.readFileSync(filePath, "utf-8"));
|
|
495
|
+
} catch (error) {
|
|
496
|
+
throw new Error(`Failed to read settings file '${filePath}': ${error instanceof Error ? error.message : String(error)}`);
|
|
497
|
+
}
|
|
498
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
499
|
+
}
|
|
500
|
+
function machineSettingsFrom(settings, keys, filePath) {
|
|
501
|
+
const subagents = settings.subagents;
|
|
502
|
+
if (!subagents || typeof subagents !== "object" || Array.isArray(subagents)) return void 0;
|
|
503
|
+
const machines = subagents.machines;
|
|
504
|
+
if (machines === void 0) return void 0;
|
|
505
|
+
if (!machines || typeof machines !== "object" || Array.isArray(machines)) throw new Error(`Subagent settings in '${filePath}' have invalid 'machines'; expected an object keyed by machine label or id.`);
|
|
506
|
+
const key = keys.find((candidate) => candidate in machines);
|
|
507
|
+
if (key === void 0) return void 0;
|
|
508
|
+
const value = machines[key];
|
|
509
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`Subagent settings in '${filePath}' have invalid 'machines.${key}'; expected an object with 'cwd' and optional 'env'.`);
|
|
510
|
+
const record = value;
|
|
511
|
+
const entry = {};
|
|
512
|
+
if (record.cwd !== void 0) {
|
|
513
|
+
if (typeof record.cwd !== "string" || !record.cwd.trim()) throw new Error(`Subagent settings in '${filePath}' have invalid 'machines.${key}.cwd'; expected a non-empty string.`);
|
|
514
|
+
entry.cwd = validateRemoteCwd(record.cwd, key);
|
|
515
|
+
}
|
|
516
|
+
if (record.env !== void 0) {
|
|
517
|
+
if (!record.env || typeof record.env !== "object" || Array.isArray(record.env) || Object.entries(record.env).some(([name, item]) => !/^[A-Za-z_][A-Za-z0-9_]*$/u.test(name) || typeof item !== "string")) {
|
|
518
|
+
throw new Error(`Subagent settings in '${filePath}' have invalid 'machines.${key}.env'; expected an object of string values keyed by variable name.`);
|
|
519
|
+
}
|
|
520
|
+
entry.env = { ...record.env };
|
|
521
|
+
if (Object.keys(entry.env).length > 0) {
|
|
522
|
+
throw new Error(`Subagent settings in '${filePath}' set 'machines.${key}.env'. Saved-machine runs use the remote Herdr/Pi environment; configure credentials and environment on '${key}' and remove the local env map.`);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
if (record.rex !== void 0) {
|
|
526
|
+
const rex = record.rex, ssh = rex && typeof rex === "object" && !Array.isArray(rex) && Object.keys(rex).every((field) => field === "ssh") ? rex.ssh : void 0;
|
|
527
|
+
if (typeof ssh !== "string") throw new Error(`Subagent settings in '${filePath}' have invalid 'machines.${key}.rex'; expected { "ssh": "<ssh target>" }.`);
|
|
528
|
+
entry.rex = { ssh: validateTarget(ssh, key) };
|
|
529
|
+
}
|
|
530
|
+
return entry;
|
|
531
|
+
}
|
|
532
|
+
function readMachineSettings(cwd, keys) {
|
|
533
|
+
const userPath = path3.join(getAgentDir(), "settings.json");
|
|
534
|
+
const projectPath = path3.join(getProjectConfigDir(findProjectRootForSettings(cwd)), "settings.json");
|
|
535
|
+
const user = machineSettingsFrom(readJsonObject(userPath), keys, userPath);
|
|
536
|
+
const project = projectPath === userPath ? void 0 : machineSettingsFrom(readJsonObject(projectPath), keys, projectPath);
|
|
537
|
+
if (project?.rex) throw new Error(`Project settings '${projectPath}' set a Rex SSH host in 'machines'. Move the rex entry to your user settings '${userPath}'; only user settings choose which host a task runs on.`);
|
|
538
|
+
if (!user && !project) return void 0;
|
|
539
|
+
return { ...user, ...project };
|
|
540
|
+
}
|
|
541
|
+
function userRexMachine(machine) {
|
|
542
|
+
const userPath = path3.join(getAgentDir(), "settings.json");
|
|
543
|
+
return machineSettingsFrom(readJsonObject(userPath), [machine], userPath)?.rex !== void 0;
|
|
544
|
+
}
|
|
545
|
+
function findProjectRootForSettings(cwd) {
|
|
546
|
+
let current = path3.resolve(cwd);
|
|
547
|
+
while (true) {
|
|
548
|
+
if (fs3.existsSync(path3.join(getProjectConfigDir(current), "settings.json"))) return current;
|
|
549
|
+
const parent = path3.dirname(current);
|
|
550
|
+
if (parent === current) return path3.resolve(cwd);
|
|
551
|
+
current = parent;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
function remoteMachineCwd(stepCwd, root, name) {
|
|
555
|
+
const step = stepCwd?.trim();
|
|
556
|
+
if (step && isRemoteAbsolute(step)) return step;
|
|
557
|
+
if (!root) throw new Error(`No root for ${name} in this repo. Set subagents.machines.${name}.cwd in .pi/settings.json or pass an absolute cwd on that machine.`);
|
|
558
|
+
return step ? path3.posix.join(root, step) : root;
|
|
559
|
+
}
|
|
560
|
+
function resolveHerdrMachinePlacement(input) {
|
|
561
|
+
const requested = validateMachineName(input.machine);
|
|
562
|
+
const catalog = input.catalogJson !== void 0 ? parseMachineCatalog(input.catalogJson) : readHerdrMachineCatalog(input.env ?? process.env, input.herdrBin ?? process.env.HERDR_BIN ?? "herdr");
|
|
563
|
+
const selected = selectMachine(catalog, requested);
|
|
564
|
+
const name = selected.label ?? selected.id;
|
|
565
|
+
const keys = [.../* @__PURE__ */ new Set([requested, ...selected.label ? [selected.label] : [], selected.id])];
|
|
566
|
+
const settings = input.settings ?? readMachineSettings(input.cwd, keys);
|
|
567
|
+
if (settings?.env && Object.keys(settings.env).length > 0) {
|
|
568
|
+
throw new Error(`Saved-machine environment for '${name}' must be configured remotely. Remove machines.${name}.env and configure the remote Herdr/Pi session instead.`);
|
|
569
|
+
}
|
|
570
|
+
const cwd = remoteMachineCwd(input.stepCwd, settings?.cwd, name);
|
|
571
|
+
return {
|
|
572
|
+
machine: {
|
|
573
|
+
provider: "herdr",
|
|
574
|
+
id: selected.id,
|
|
575
|
+
...selected.label ? { label: selected.label } : {},
|
|
576
|
+
target: validateTarget(selected.target, requested),
|
|
577
|
+
...selected.session && selected.session !== "default" ? { session: selected.session } : {},
|
|
578
|
+
cwd: validateRemoteCwd(cwd, requested)
|
|
579
|
+
},
|
|
580
|
+
...settings?.env ? { env: settings.env } : {}
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
function resolveMachinePlacement(input) {
|
|
584
|
+
const requested = validateMachineName(input.machine);
|
|
585
|
+
const settings = input.settings ?? readMachineSettings(input.cwd, [requested]);
|
|
586
|
+
if (requested === REX_LOCAL_MACHINE) {
|
|
587
|
+
if (settings?.rex) throw new Error("Machine 'local' is reserved for a Rex pane on this machine; give the Rex SSH host in subagents.machines.local.rex another name.");
|
|
588
|
+
return { machine: resolveRexLocalPlacement({ cwd: input.cwd, stepCwd: input.stepCwd, env: input.env }) };
|
|
589
|
+
}
|
|
590
|
+
if (settings?.rex) return { machine: { provider: "rex", id: requested, target: settings.rex.ssh, cwd: validateRemoteCwd(remoteMachineCwd(input.stepCwd, settings.cwd, requested), requested) } };
|
|
591
|
+
return resolveHerdrMachinePlacement(input);
|
|
592
|
+
}
|
|
593
|
+
var PLACED_RESTORE_UNSUPPORTED = "Pane-native remote Pi does not support fork, resume, or revival; use fresh context.";
|
|
594
|
+
function formatHerdrMachineRunnerUnsupported(input) {
|
|
595
|
+
if (input.machine === void 0) return void 0;
|
|
596
|
+
const name = input.machine.trim();
|
|
597
|
+
let rex;
|
|
598
|
+
try {
|
|
599
|
+
rex = name === REX_LOCAL_MACHINE || userRexMachine(name);
|
|
600
|
+
} catch (error) {
|
|
601
|
+
return error instanceof Error ? error.message : String(error);
|
|
602
|
+
}
|
|
603
|
+
if (rex) {
|
|
604
|
+
if (input.runnerType !== void 0 && input.runnerType !== "pi") return `Agent '${input.agentName}' requested machine '${name}', but Rex supports native Pi children only. Remove the machine or use a native Pi agent.`;
|
|
605
|
+
if (input.worktree === true) return `Agent '${input.agentName}' requested machine '${name}', but managed worktrees cannot be combined with machine placement.`;
|
|
606
|
+
if (process.platform === "win32") return `Machine '${name}' runs the child in a Rex pane, which is not supported on Windows.`;
|
|
607
|
+
return void 0;
|
|
608
|
+
}
|
|
609
|
+
if (input.runnerType !== void 0 && input.runnerType !== "pi" && input.runnerType !== "external-cli") {
|
|
610
|
+
return `Agent '${input.agentName}' requested machine '${input.machine}', but this runner cannot use pane-native Herdr placement. Use native Pi or a built-in Claude, Codex, or Cursor profile.`;
|
|
611
|
+
}
|
|
612
|
+
if (input.runnerType === "external-cli" && (input.adapter === void 0 || !SUPPORTED_MACHINE_ADAPTERS.has(input.adapter))) {
|
|
613
|
+
return `Agent '${input.agentName}' requested machine '${input.machine}', but generic external-cli commands cannot be remote-wrapped safely. Use claude-code, claude-code-writer, codex-exec, codex-exec-writer, cursor-agent, or cursor-agent-writer.`;
|
|
614
|
+
}
|
|
615
|
+
if (input.worktree === true) return `Agent '${input.agentName}' requested machine '${input.machine}', but managed worktrees are local git operations and cannot be combined with a Herdr saved machine.`;
|
|
616
|
+
if (process.platform === "win32") return "Herdr saved-machine pane transport requires hardened OpenSSH StreamLocal forwarding, which is not supported from a Windows host yet.";
|
|
617
|
+
return void 0;
|
|
618
|
+
}
|
|
619
|
+
|
|
400
620
|
export {
|
|
401
621
|
remoteShellCommand,
|
|
402
622
|
runBoundedCommand,
|
|
403
623
|
sshMachineAccess,
|
|
404
624
|
connectHerdrMachine,
|
|
405
|
-
REX_LOCAL_MACHINE,
|
|
406
625
|
REX_EXIT_GONE,
|
|
407
626
|
REX_EXIT_UNREACHABLE,
|
|
408
627
|
isRexLocalMachine,
|
|
@@ -412,5 +631,7 @@ export {
|
|
|
412
631
|
rexGlobalArgs,
|
|
413
632
|
resolveRexBinary,
|
|
414
633
|
rexCommandEnv,
|
|
415
|
-
|
|
634
|
+
resolveMachinePlacement,
|
|
635
|
+
PLACED_RESTORE_UNSUPPORTED,
|
|
636
|
+
formatHerdrMachineRunnerUnsupported
|
|
416
637
|
};
|
|
@@ -3,11 +3,11 @@ import {
|
|
|
3
3
|
} from "./chunk-7J4ZXMY7.js";
|
|
4
4
|
import {
|
|
5
5
|
getProjectSubagentsDir
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-D3JAFGMO.js";
|
|
7
7
|
import {
|
|
8
8
|
getPiSpawnCommand,
|
|
9
9
|
writeAtomicJson
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-LNRJCP2Y.js";
|
|
11
11
|
|
|
12
12
|
// src/inspectors/herdr/project-panes.ts
|
|
13
13
|
import * as fs from "node:fs";
|