@henryqw/pi-subagent 26.5.0 → 26.5.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/dist/direct-herdr.d.ts +0 -1
- package/dist/direct-herdr.js +0 -3
- package/dist/ephemeral.js +1 -9
- package/dist/herdr-runtime.d.ts +0 -1
- package/dist/herdr-runtime.js +9 -12
- package/dist/launch-runtime.js +5 -8
- package/dist/schema.js +2 -5
- package/extensions/admission.ts +0 -4
- package/extensions/isolated.ts +0 -5
- package/extensions/subagent.ts +3 -8
- package/extensions/workflow.ts +1 -6
- package/package.json +1 -1
package/dist/direct-herdr.d.ts
CHANGED
|
@@ -26,7 +26,6 @@ export declare function directSessionUsage(jsonl: string, prompt: string): {
|
|
|
26
26
|
tokens?: number;
|
|
27
27
|
activity?: DirectToolActivity;
|
|
28
28
|
};
|
|
29
|
-
export declare function directSessionTokens(jsonl: string, prompt: string): number | undefined;
|
|
30
29
|
export type DirectTab = Pick<DirectHandle, "name" | "tabId" | "paneId" | "sessionFile"> & {
|
|
31
30
|
leasePath: string;
|
|
32
31
|
};
|
package/dist/direct-herdr.js
CHANGED
|
@@ -160,9 +160,6 @@ export function directSessionUsage(jsonl, prompt) {
|
|
|
160
160
|
return { tokens: observed ? total : undefined,
|
|
161
161
|
activity: pending.size || latest ? { pending: [...pending.values()], latest } : undefined };
|
|
162
162
|
}
|
|
163
|
-
export function directSessionTokens(jsonl, prompt) {
|
|
164
|
-
return directSessionUsage(jsonl, prompt).tokens;
|
|
165
|
-
}
|
|
166
163
|
export class DirectAllocationError extends Error {
|
|
167
164
|
allocation;
|
|
168
165
|
constructor(allocation, cause) {
|
package/dist/ephemeral.js
CHANGED
|
@@ -110,13 +110,7 @@ function validateRunInput(value) {
|
|
|
110
110
|
if (input.onActivity !== undefined && typeof input.onActivity !== "function") {
|
|
111
111
|
throw new TypeError("run.onActivity must be a function.");
|
|
112
112
|
}
|
|
113
|
-
return
|
|
114
|
-
signal: input.signal,
|
|
115
|
-
prepare: input.prepare,
|
|
116
|
-
onUpdate: input.onUpdate,
|
|
117
|
-
onTokens: input.onTokens,
|
|
118
|
-
onActivity: input.onActivity,
|
|
119
|
-
};
|
|
113
|
+
return value;
|
|
120
114
|
}
|
|
121
115
|
function record(value, field) {
|
|
122
116
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
@@ -217,8 +211,6 @@ export function createEphemeralSubagentExecutor(options) {
|
|
|
217
211
|
throw abortError(input.signal, cause);
|
|
218
212
|
throw new EphemeralSubagentError("prepare", cause instanceof Error ? cause.message : String(cause), cause);
|
|
219
213
|
}
|
|
220
|
-
if (input.signal?.aborted)
|
|
221
|
-
throw abortError(input.signal);
|
|
222
214
|
return await runPi(prepared, input, validated, invocation);
|
|
223
215
|
}
|
|
224
216
|
finally {
|
package/dist/herdr-runtime.d.ts
CHANGED
|
@@ -126,7 +126,6 @@ export declare class HerdrHostRuntime implements HostRuntime {
|
|
|
126
126
|
private workerTurnFinished;
|
|
127
127
|
private removeWorkerSessionFile;
|
|
128
128
|
private assertLeasePath;
|
|
129
|
-
private assertPrivateLease;
|
|
130
129
|
private scanLease;
|
|
131
130
|
private signalExactHolders;
|
|
132
131
|
private listTabs;
|
package/dist/herdr-runtime.js
CHANGED
|
@@ -438,7 +438,7 @@ export class HerdrHostRuntime {
|
|
|
438
438
|
assertAgentIntent(allocation, input.attempt);
|
|
439
439
|
this.assertLeasePath(allocation.leasePath, input.intent.token);
|
|
440
440
|
try {
|
|
441
|
-
await
|
|
441
|
+
await assertPrivateLease(allocation.leasePath, false);
|
|
442
442
|
const agents = (await this.listAgents(allocation.worktreeCwd, context)).map((agent) => {
|
|
443
443
|
const name = agent.name;
|
|
444
444
|
if (name !== undefined && name !== null && typeof name !== "string")
|
|
@@ -547,7 +547,7 @@ export class HerdrHostRuntime {
|
|
|
547
547
|
if (input.workerId !== allocation.agentName)
|
|
548
548
|
throw new Error("Worker termination identity does not match the exact saved agent.");
|
|
549
549
|
this.assertLeasePath(allocation.leasePath, allocation.token);
|
|
550
|
-
await
|
|
550
|
+
await assertPrivateLease(allocation.leasePath, false);
|
|
551
551
|
const agents = (await this.listAgents(allocation.worktreeCwd, context)).map((agent) => ({
|
|
552
552
|
name: typeof agent.name === "string" ? agent.name : undefined,
|
|
553
553
|
paneId: exactString(agent.pane_id, "Herdr listed agent pane ID"),
|
|
@@ -604,7 +604,7 @@ export class HerdrHostRuntime {
|
|
|
604
604
|
if (input.workerId !== allocation.agentName)
|
|
605
605
|
throw new Error("Saved termination worker does not match the exact owned agent.");
|
|
606
606
|
this.assertLeasePath(allocation.leasePath, allocation.token);
|
|
607
|
-
await
|
|
607
|
+
await assertPrivateLease(allocation.leasePath, false);
|
|
608
608
|
const agents = (await this.listAgents(allocation.worktreeCwd, context)).map((agent) => ({
|
|
609
609
|
name: typeof agent.name === "string" ? agent.name : undefined,
|
|
610
610
|
paneId: exactString(agent.pane_id, "Herdr listed agent pane ID"),
|
|
@@ -649,7 +649,7 @@ export class HerdrHostRuntime {
|
|
|
649
649
|
assertAgentIntent(allocation, attempt);
|
|
650
650
|
this.assertLeasePath(allocation.leasePath, attempt.correlationToken);
|
|
651
651
|
await this.assertPrivateLeaseDirectories(allocation.leasePath, true);
|
|
652
|
-
await
|
|
652
|
+
await assertPrivateLease(allocation.leasePath, true);
|
|
653
653
|
const agents = (await this.listAgents(allocation.worktreeCwd, context)).map((agent) => ({
|
|
654
654
|
name: typeof agent.name === "string" ? agent.name : undefined,
|
|
655
655
|
paneId: exactString(agent.pane_id, "Herdr listed agent pane ID"),
|
|
@@ -826,7 +826,7 @@ export class HerdrHostRuntime {
|
|
|
826
826
|
if (!acquireLaunch)
|
|
827
827
|
throw new Error("Agent start requires immediate Role launch acquisition.");
|
|
828
828
|
this.assertLeasePath(allocation.leasePath, allocation.token);
|
|
829
|
-
await
|
|
829
|
+
await assertPrivateLease(allocation.leasePath, false);
|
|
830
830
|
if ((await this.scanLease(allocation.leasePath, allocation.worktreeCwd, context)).length) {
|
|
831
831
|
throw new Error("Agent start requires an empty exact process lease.");
|
|
832
832
|
}
|
|
@@ -1057,12 +1057,12 @@ export class HerdrHostRuntime {
|
|
|
1057
1057
|
finally {
|
|
1058
1058
|
await file?.close();
|
|
1059
1059
|
}
|
|
1060
|
-
await
|
|
1060
|
+
await assertPrivateLease(path, false);
|
|
1061
1061
|
}
|
|
1062
1062
|
async removeLeaseArtifacts(allocation, token, paneId, tabId, context) {
|
|
1063
1063
|
this.assertLeasePath(allocation.leasePath, token);
|
|
1064
1064
|
await this.assertPrivateLeaseDirectories(allocation.leasePath, true);
|
|
1065
|
-
const leaseWasPresent = await
|
|
1065
|
+
const leaseWasPresent = await assertPrivateLease(allocation.leasePath, true);
|
|
1066
1066
|
if ((await this.scanLease(allocation.leasePath, allocation.worktreeCwd, context, undefined, true)).length) {
|
|
1067
1067
|
throw new Error("Exact process lease still has a holder during cleanup.");
|
|
1068
1068
|
}
|
|
@@ -1080,10 +1080,10 @@ export class HerdrHostRuntime {
|
|
|
1080
1080
|
if (leaseWasPresent) {
|
|
1081
1081
|
if (!directoryPresent)
|
|
1082
1082
|
throw new Error("Exact process lease directory disappeared during cleanup.");
|
|
1083
|
-
await
|
|
1083
|
+
await assertPrivateLease(allocation.leasePath, false);
|
|
1084
1084
|
await unlink(allocation.leasePath);
|
|
1085
1085
|
}
|
|
1086
|
-
else if (await
|
|
1086
|
+
else if (await assertPrivateLease(allocation.leasePath, true)) {
|
|
1087
1087
|
throw new Error("Exact process lease appeared during cleanup.");
|
|
1088
1088
|
}
|
|
1089
1089
|
if (!directoryPresent)
|
|
@@ -1193,9 +1193,6 @@ export class HerdrHostRuntime {
|
|
|
1193
1193
|
throw new Error("Process lease path is not the exact random path owned by this task token.");
|
|
1194
1194
|
}
|
|
1195
1195
|
}
|
|
1196
|
-
async assertPrivateLease(path, allowMissing) {
|
|
1197
|
-
return assertPrivateLease(path, allowMissing);
|
|
1198
|
-
}
|
|
1199
1196
|
async scanLease(path, cwd, context, pid, allowMissing = false) {
|
|
1200
1197
|
return scanProcessLease(path, (args) => this.execute(this.lsofCommand, args, this.processOptions(cwd, context, LSOF_OPERATION_CAP_MS)), pid, allowMissing);
|
|
1201
1198
|
}
|
package/dist/launch-runtime.js
CHANGED
|
@@ -14,9 +14,6 @@ export const ISOLATED_MODEL_TASK = {
|
|
|
14
14
|
purpose: "Resolve one task-scoped isolated Role launch.",
|
|
15
15
|
defaultProfile: "balanced",
|
|
16
16
|
};
|
|
17
|
-
function abortIfNeeded(signal) {
|
|
18
|
-
signal?.throwIfAborted();
|
|
19
|
-
}
|
|
20
17
|
function isMissing(error) {
|
|
21
18
|
return Boolean(error && typeof error === "object" && error.code === "ENOENT");
|
|
22
19
|
}
|
|
@@ -46,7 +43,7 @@ async function removeTransientLaunch(directory, promptPath, promptRequired) {
|
|
|
46
43
|
throw new AggregateError(failures, "Transient Role launch cleanup was incomplete.");
|
|
47
44
|
}
|
|
48
45
|
export async function materializeTransientLaunch(prepared, signal) {
|
|
49
|
-
|
|
46
|
+
signal?.throwIfAborted();
|
|
50
47
|
const promptBytes = Buffer.from(prepared.prompt, "utf8");
|
|
51
48
|
const created = await mkdtemp(join(tmpdir(), "pi-subagent-role-"));
|
|
52
49
|
let directory = normalize(created);
|
|
@@ -66,7 +63,7 @@ export async function materializeTransientLaunch(prepared, signal) {
|
|
|
66
63
|
file = await open(promptPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW, PROMPT_MODE);
|
|
67
64
|
promptCreated = true;
|
|
68
65
|
await file.chmod(PROMPT_MODE);
|
|
69
|
-
|
|
66
|
+
signal?.throwIfAborted();
|
|
70
67
|
await file.writeFile(promptBytes, { signal });
|
|
71
68
|
await file.sync();
|
|
72
69
|
await file.close();
|
|
@@ -124,7 +121,7 @@ export class RoleLaunchRuntime {
|
|
|
124
121
|
return this.options.randomToken?.() ?? randomBytes(12).toString("hex");
|
|
125
122
|
}
|
|
126
123
|
async prepareLaunch(role, modelClass, context) {
|
|
127
|
-
|
|
124
|
+
context.signal?.throwIfAborted();
|
|
128
125
|
const prepared = await resolveConfiguredRoleLaunch(this.options.pi, this.options.context(), { role, modelClass });
|
|
129
126
|
return Object.freeze({
|
|
130
127
|
launch: Object.freeze({
|
|
@@ -141,7 +138,7 @@ export class RoleLaunchRuntime {
|
|
|
141
138
|
});
|
|
142
139
|
}
|
|
143
140
|
async preflight(input, context) {
|
|
144
|
-
|
|
141
|
+
context.signal?.throwIfAborted();
|
|
145
142
|
const resolvedRoot = await this.options.resolveRoot(input.cwd, context);
|
|
146
143
|
if (typeof resolvedRoot !== "string" || !isAbsolute(resolvedRoot) || resolvedRoot.includes("\0")) {
|
|
147
144
|
throw new Error("Pi Subagent root resolver must return an absolute canonical path.");
|
|
@@ -152,7 +149,7 @@ export class RoleLaunchRuntime {
|
|
|
152
149
|
const rootInfo = await lstat(root);
|
|
153
150
|
if (!rootInfo.isDirectory())
|
|
154
151
|
throw new Error("Pi Subagent root must be an existing local directory.");
|
|
155
|
-
|
|
152
|
+
context.signal?.throwIfAborted();
|
|
156
153
|
if (input.request.tasks.some((task) => task.kind === "changeset")) {
|
|
157
154
|
await this.options.preflightHost?.({ request: input.request, cwd: input.cwd, root }, context);
|
|
158
155
|
}
|
package/dist/schema.js
CHANGED
|
@@ -492,22 +492,19 @@ function normalizeText(value, field) {
|
|
|
492
492
|
throw new Error(`${field} must be non-empty text without NUL bytes.`);
|
|
493
493
|
return normalized;
|
|
494
494
|
}
|
|
495
|
-
function normalizeRole(value, field) {
|
|
496
|
-
return parseRoleName(value, field);
|
|
497
|
-
}
|
|
498
495
|
function normalizeJudgment(judgment, field) {
|
|
499
496
|
if (!judgment)
|
|
500
497
|
return;
|
|
501
498
|
return {
|
|
502
499
|
...judgment,
|
|
503
|
-
role:
|
|
500
|
+
role: parseRoleName(judgment.role, `${field}.role`),
|
|
504
501
|
criterion: normalizeText(judgment.criterion, `${field}.criterion`),
|
|
505
502
|
};
|
|
506
503
|
}
|
|
507
504
|
function normalizeTask(task, index) {
|
|
508
505
|
const fields = {
|
|
509
506
|
id: task.id,
|
|
510
|
-
role:
|
|
507
|
+
role: parseRoleName(task.role, `tasks[${index}].role`),
|
|
511
508
|
modelClass: task.modelClass,
|
|
512
509
|
requirements: normalizeText(task.requirements, `tasks[${index}].requirements`),
|
|
513
510
|
deliverable: normalizeText(task.deliverable, `tasks[${index}].deliverable`),
|
package/extensions/admission.ts
CHANGED
|
@@ -12,10 +12,6 @@ export function roleCanWrite(role: Role): boolean {
|
|
|
12
12
|
return role.tools.some((tool) => !READ_ONLY_TOOLS.has(tool)) || role.extensions.length > 0 || Boolean(role.mcps?.length);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export function roleIsReadOnlyScout(role: Role): boolean {
|
|
16
|
-
return !roleCanWrite(role) && role.extensions.length === 0 && !role.mcps?.length;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
15
|
async function checkoutKey(pi: ExtensionAPI, ctx: ExtensionContext): Promise<string> {
|
|
20
16
|
const cwd = await realpath(ctx.cwd);
|
|
21
17
|
const result = await pi.exec("git", ["rev-parse", "--show-toplevel"], { cwd, timeout: 5_000 });
|
package/extensions/isolated.ts
CHANGED
|
@@ -2,7 +2,6 @@ import type { JsonValue } from "@earendil-works/pi-ai";
|
|
|
2
2
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
4
4
|
import type { EphemeralSubagentExecutor } from "../dist/ephemeral.js";
|
|
5
|
-
import { ROLE_TOOL_POLICY_FLAG } from "../dist/index.js";
|
|
6
5
|
import type { EffectiveExecutionPolicy } from "./config.ts";
|
|
7
6
|
import {
|
|
8
7
|
createCanonicalGitRootResolver,
|
|
@@ -430,10 +429,6 @@ export interface RegisterIsolatedOptions {
|
|
|
430
429
|
|
|
431
430
|
/** Register lifecycle surfaces; delegate_task remains the only start tool. */
|
|
432
431
|
export function registerIsolatedExtension(pi: ExtensionAPI, options: RegisterIsolatedOptions): IsolatedSurface {
|
|
433
|
-
if (process.argv.includes(`--${ROLE_TOOL_POLICY_FLAG}`)) {
|
|
434
|
-
const denied = (): never => { throw new Error("Child Roles cannot access isolated delegation."); };
|
|
435
|
-
return { execute: async () => denied(), inventory: async () => denied(), recover: async () => denied(), inspect: async () => denied(), canFollowup: denied, enqueue: denied, drain: denied };
|
|
436
|
-
}
|
|
437
432
|
const componentsFactory = options.componentsFactory ?? createIsolatedComponents;
|
|
438
433
|
let latestCtx: ExtensionContext | undefined;
|
|
439
434
|
let components: IsolatedExtensionComponents | undefined;
|
package/extensions/subagent.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { type Component, Text, type TUI, truncateToWidth, visibleWidth } from "@
|
|
|
4
4
|
import { availableTaskModels, loadTaskModelsConfig, modelReference, registerModelTask, resolveAvailableModel, type ResolvedTaskRoute, taskThinkingLevels } from "@henryqw/pi-task-models";
|
|
5
5
|
import { capEphemeralSubagentOutput as capOutput, createEphemeralSubagentExecutor, DELEGATE_TASK, formatDuration, loadRoles, prepareRoleLaunch, resolveRolePackageResources, ROLE_TOOL_POLICY_FLAG, type Role } from "@henryqw/pi-subagent";
|
|
6
6
|
import { readSubagentConfig, resolveExecutionPolicy, type EffectiveExecutionPolicy } from "./config.ts";
|
|
7
|
-
import { registerCheckoutAdmission, roleCanWrite
|
|
7
|
+
import { registerCheckoutAdmission, roleCanWrite } from "./admission.ts";
|
|
8
8
|
import { registerIsolatedExtension } from "./isolated.ts";
|
|
9
9
|
import { directRef, registerSubagentCommand, type DirectDisplay, type DirectTask } from "./subagent-command.ts";
|
|
10
10
|
import { MODEL_CLASS_GUIDANCE } from "./model-class-policy.ts";
|
|
@@ -278,7 +278,7 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
278
278
|
const snapshot = notices.join("\n");
|
|
279
279
|
if (Buffer.byteLength(snapshot, "utf8") > 32_000) throw new Error("Status exceeds the 32 KiB inspection limit; use subagent_status for exact evidence.");
|
|
280
280
|
const role = loadRoles().find((candidate) => candidate.name === "scout");
|
|
281
|
-
if (!role ||
|
|
281
|
+
if (!role || roleCanWrite(role)) throw new Error("Status inspection requires a configured read-only scout Role without extensions or MCP servers.");
|
|
282
282
|
const prepared = prepareRoleLaunch(pi, ctx, { role, task: DELEGATE_TASK, modelClass: "fast" });
|
|
283
283
|
const herdr = createDirectHerdr(pi, ctx.cwd, currentPolicy().childIdleMs);
|
|
284
284
|
const controller = new AbortController();
|
|
@@ -712,12 +712,7 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
712
712
|
throw error;
|
|
713
713
|
} finally { release(); }
|
|
714
714
|
}, controller.signal);
|
|
715
|
-
} catch
|
|
716
|
-
if (!controller.signal.aborted) {
|
|
717
|
-
const state = [...states.values()].find(({ status }) => status === "running" || status === "pending");
|
|
718
|
-
if (state) states.set(state.id, { id: state.id, index: state.index, name: state.name, role: state.role, status: "rejected", failure: capOutput(String(error)) });
|
|
719
|
-
}
|
|
720
|
-
} finally {
|
|
715
|
+
} catch { /* aborted */ } finally {
|
|
721
716
|
for (const [id, state] of states) {
|
|
722
717
|
if (state.status === "pending" || state.status === "running") states.set(id, {
|
|
723
718
|
id: state.id, index: state.index, name: state.name, role: state.role,
|
package/extensions/workflow.ts
CHANGED
|
@@ -148,12 +148,7 @@ export async function runForegroundWorkflow(
|
|
|
148
148
|
signal?: AbortSignal,
|
|
149
149
|
): Promise<void> {
|
|
150
150
|
signal?.throwIfAborted();
|
|
151
|
-
if (mode === "single") {
|
|
152
|
-
await runEntry(entries[0]!, run);
|
|
153
|
-
signal?.throwIfAborted();
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
if (mode === "parallel") {
|
|
151
|
+
if (mode === "single" || mode === "parallel") {
|
|
157
152
|
await Promise.all(entries.map((entry) => runEntry(entry, run)));
|
|
158
153
|
signal?.throwIfAborted();
|
|
159
154
|
return;
|