@henryqw/pi-subagent 25.1.0 → 25.1.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.js +0 -5
- package/dist/ephemeral.js +14 -20
- package/dist/git-process.js +2 -9
- package/dist/index.js +2 -3
- package/dist/launch-runtime.js +1 -5
- package/dist/process.d.ts +1 -1
- package/dist/runner.js +8 -7
- package/dist/store.js +1 -1
- package/extensions/isolated.ts +3 -4
- package/extensions/workflow.ts +1 -3
- package/package.json +1 -1
package/dist/direct-herdr.js
CHANGED
|
@@ -243,11 +243,6 @@ export function createDirectHerdr(pi, cwd, idleMs) {
|
|
|
243
243
|
const waited = await herdr.exec(args, options(signal));
|
|
244
244
|
if (waited.code !== 0 || waited.killed) {
|
|
245
245
|
if (!waited.killed && hasHerdrErrorCode(waited, "timeout")) {
|
|
246
|
-
if (current === "idle") {
|
|
247
|
-
const answer = await maybeAnswer();
|
|
248
|
-
if (answer !== undefined)
|
|
249
|
-
return answer;
|
|
250
|
-
}
|
|
251
246
|
continue;
|
|
252
247
|
}
|
|
253
248
|
throw new Error(herdrCommandFailure(args, waited));
|
package/dist/ephemeral.js
CHANGED
|
@@ -429,9 +429,8 @@ async function runPi(prepared, input, budget, invocation) {
|
|
|
429
429
|
let spawnError;
|
|
430
430
|
let protocolError;
|
|
431
431
|
let aborted = false;
|
|
432
|
-
let
|
|
432
|
+
let limit;
|
|
433
433
|
let tokenBudget = "within";
|
|
434
|
-
const tokenLimited = () => tokenBudget === "limited";
|
|
435
434
|
let startedTurns = 0;
|
|
436
435
|
let lastEventAt = startedAt;
|
|
437
436
|
let deadline = Math.min(startedAt + timeoutPolicy.idleMs, maxDeadline ?? startedAt + timeoutPolicy.idleMs);
|
|
@@ -514,15 +513,12 @@ async function runPi(prepared, input, budget, invocation) {
|
|
|
514
513
|
: `Subagent timed out after ${formatDuration(timeoutPolicy.idleMs)} without a recognized Pi event.`;
|
|
515
514
|
reject(new EphemeralSubagentError("timeout", message, new Error(message), accumulatedUsage()));
|
|
516
515
|
}
|
|
517
|
-
else if (
|
|
518
|
-
const summary =
|
|
516
|
+
else if (limit) {
|
|
517
|
+
const summary = limit === "turn_limit"
|
|
518
|
+
? `Subagent reached its maximum turn limit of ${budget.maxTurns}.`
|
|
519
|
+
: `Subagent reached its maximum token limit of ${budget.maxTokens}.`;
|
|
519
520
|
const message = output ? capEphemeralSubagentOutput(`${summary}\n\nLast assistant output:\n${output}`) : summary;
|
|
520
|
-
reject(new EphemeralSubagentError(
|
|
521
|
-
}
|
|
522
|
-
else if (tokenLimited()) {
|
|
523
|
-
const summary = `Subagent reached its maximum token limit of ${budget.maxTokens}.`;
|
|
524
|
-
const message = output ? capEphemeralSubagentOutput(`${summary}\n\nLast assistant output:\n${output}`) : summary;
|
|
525
|
-
reject(new EphemeralSubagentError("token_limit", message, new Error(message), accumulatedUsage(), output));
|
|
521
|
+
reject(new EphemeralSubagentError(limit, message, new Error(message), accumulatedUsage(), output));
|
|
526
522
|
}
|
|
527
523
|
else if (protocolError) {
|
|
528
524
|
reject(new EphemeralSubagentError("protocol", protocolError.message, protocolError, accumulatedUsage()));
|
|
@@ -585,7 +581,7 @@ async function runPi(prepared, input, budget, invocation) {
|
|
|
585
581
|
deadlineTimer.unref();
|
|
586
582
|
};
|
|
587
583
|
const observeEvent = () => {
|
|
588
|
-
if (callbackFailure || aborted || timedOutAfterMs !== undefined ||
|
|
584
|
+
if (callbackFailure || aborted || timedOutAfterMs !== undefined || limit || childExited)
|
|
589
585
|
return;
|
|
590
586
|
const now = Date.now();
|
|
591
587
|
if (now >= deadline) {
|
|
@@ -609,17 +605,15 @@ async function runPi(prepared, input, budget, invocation) {
|
|
|
609
605
|
case "final_turn":
|
|
610
606
|
if (event === "turn_start") {
|
|
611
607
|
if (!callbackFailure && !aborted && timedOutAfterMs === undefined)
|
|
612
|
-
|
|
608
|
+
limit = "token_limit";
|
|
613
609
|
stop(true);
|
|
614
610
|
return true;
|
|
615
611
|
}
|
|
616
612
|
return false;
|
|
617
|
-
case "limited":
|
|
618
|
-
return true;
|
|
619
613
|
}
|
|
620
614
|
};
|
|
621
615
|
const processLine = (line) => {
|
|
622
|
-
if (
|
|
616
|
+
if (limit || !line.trim())
|
|
623
617
|
return;
|
|
624
618
|
let event;
|
|
625
619
|
try {
|
|
@@ -637,7 +631,7 @@ async function runPi(prepared, input, budget, invocation) {
|
|
|
637
631
|
if (record.type === "turn_start") {
|
|
638
632
|
if (++startedTurns > budget.maxTurns) {
|
|
639
633
|
if (!callbackFailure && !aborted && timedOutAfterMs === undefined)
|
|
640
|
-
|
|
634
|
+
limit = "turn_limit";
|
|
641
635
|
stop(true);
|
|
642
636
|
return;
|
|
643
637
|
}
|
|
@@ -775,7 +769,7 @@ async function runPi(prepared, input, budget, invocation) {
|
|
|
775
769
|
};
|
|
776
770
|
onStdoutData = (data) => {
|
|
777
771
|
armPostExitIdleDeadline();
|
|
778
|
-
if (callbackFailure || protocolError ||
|
|
772
|
+
if (callbackFailure || protocolError || limit)
|
|
779
773
|
return;
|
|
780
774
|
let offset = 0;
|
|
781
775
|
while (offset < data.length) {
|
|
@@ -816,7 +810,7 @@ async function runPi(prepared, input, budget, invocation) {
|
|
|
816
810
|
invokeCallback("onActivity", input.onActivity, activity);
|
|
817
811
|
}
|
|
818
812
|
}
|
|
819
|
-
if (callbackFailure ||
|
|
813
|
+
if (callbackFailure || limit)
|
|
820
814
|
return;
|
|
821
815
|
lineParts = [];
|
|
822
816
|
lineBytes = 0;
|
|
@@ -856,13 +850,13 @@ async function runPi(prepared, input, budget, invocation) {
|
|
|
856
850
|
killTimer.unref();
|
|
857
851
|
}
|
|
858
852
|
const abort = () => {
|
|
859
|
-
if (timedOutAfterMs !== undefined ||
|
|
853
|
+
if (timedOutAfterMs !== undefined || limit || childExited)
|
|
860
854
|
return;
|
|
861
855
|
aborted = true;
|
|
862
856
|
stop();
|
|
863
857
|
};
|
|
864
858
|
function timeout(afterMs, reason) {
|
|
865
|
-
if (timedOutAfterMs !== undefined ||
|
|
859
|
+
if (timedOutAfterMs !== undefined || limit || childExited)
|
|
866
860
|
return;
|
|
867
861
|
if (reason === "maximum") {
|
|
868
862
|
if (!aborted) {
|
package/dist/git-process.js
CHANGED
|
@@ -1,17 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { runProcess } from "./process.js";
|
|
2
2
|
const GIT_TIMEOUT_MS = 30_000;
|
|
3
|
-
const GIT_OUTPUT_LIMIT_BYTES = 1 * 1024 * 1024;
|
|
4
3
|
const GIT_DIAGNOSTIC_LIMIT = 200;
|
|
5
4
|
/** Run one ordinary Git command with bounded output; transport failures never throw. */
|
|
6
5
|
export const runGit = async (args, cwd, signal) => {
|
|
7
6
|
try {
|
|
8
|
-
const result = await
|
|
9
|
-
cwd,
|
|
10
|
-
signal,
|
|
11
|
-
timeoutMs: GIT_TIMEOUT_MS,
|
|
12
|
-
stdoutLimitBytes: GIT_OUTPUT_LIMIT_BYTES,
|
|
13
|
-
stderrLimitBytes: GIT_OUTPUT_LIMIT_BYTES,
|
|
14
|
-
});
|
|
7
|
+
const result = await runProcess("git", ["--no-pager", ...args], { cwd, signal, timeoutMs: GIT_TIMEOUT_MS });
|
|
15
8
|
if (result.killed) {
|
|
16
9
|
return {
|
|
17
10
|
code: -1,
|
package/dist/index.js
CHANGED
|
@@ -374,10 +374,9 @@ export async function resolveConfiguredRoleLaunch(pi, ctx, input) {
|
|
|
374
374
|
const roleName = parseRoleName(input.role);
|
|
375
375
|
if (input.modelClass === undefined)
|
|
376
376
|
throw new Error("Configured Role launch requires an explicit modelClass.");
|
|
377
|
-
const
|
|
378
|
-
if (
|
|
377
|
+
const role = loadRoles().find((role) => role.name === roleName);
|
|
378
|
+
if (!role)
|
|
379
379
|
throw new Error(`Required configured Role ${roleName} is missing or ambiguous.`);
|
|
380
|
-
const role = matches[0];
|
|
381
380
|
if (role.mcps?.length)
|
|
382
381
|
loadRoleMcpConfig(getAgentDir(), role.mcps, { codemode: roleActivatesCodemode(role.tools) });
|
|
383
382
|
const resources = await resolveRolePackageResources(role, ctx);
|
package/dist/launch-runtime.js
CHANGED
|
@@ -125,11 +125,7 @@ export class RoleLaunchRuntime {
|
|
|
125
125
|
}
|
|
126
126
|
async prepareLaunch(role, modelClass, context) {
|
|
127
127
|
abortIfNeeded(context.signal);
|
|
128
|
-
const
|
|
129
|
-
const prepared = await resolveConfiguredRoleLaunch(this.options.pi, this.options.context(), input);
|
|
130
|
-
if (prepared.missingSkills.length) {
|
|
131
|
-
throw new Error(`Role ${role} requires missing Skills: ${prepared.missingSkills.join(", ")}.`);
|
|
132
|
-
}
|
|
128
|
+
const prepared = await resolveConfiguredRoleLaunch(this.options.pi, this.options.context(), { role, modelClass });
|
|
133
129
|
return Object.freeze({
|
|
134
130
|
launch: Object.freeze({
|
|
135
131
|
role: prepared.role,
|
package/dist/process.d.ts
CHANGED
package/dist/runner.js
CHANGED
|
@@ -131,12 +131,12 @@ class FollowupControl {
|
|
|
131
131
|
if (this.queue.length >= MAX_QUEUED_FOLLOWUPS)
|
|
132
132
|
throw new Error(`A task may queue at most ${MAX_QUEUED_FOLLOWUPS} follow-ups.`);
|
|
133
133
|
this.validateFollowup(instruction);
|
|
134
|
-
this.queue.push(
|
|
134
|
+
this.queue.push(instruction);
|
|
135
135
|
}
|
|
136
136
|
drainPending() {
|
|
137
137
|
if (this.sealed)
|
|
138
138
|
throw new Error("This task candidate is already sealed for integration.");
|
|
139
|
-
return this.queue.splice(0)
|
|
139
|
+
return this.queue.splice(0);
|
|
140
140
|
}
|
|
141
141
|
takeQueuedFollowup() {
|
|
142
142
|
if (this.sealed)
|
|
@@ -578,7 +578,7 @@ export class IsolatedRunner {
|
|
|
578
578
|
}
|
|
579
579
|
drainFollowups(root, requestId, taskId) {
|
|
580
580
|
const active = this.activeControl(realpathSync.native(root), requestId, taskId);
|
|
581
|
-
if (!active ||
|
|
581
|
+
if (!active || active.task.status !== "working" || active.control.isSealed) {
|
|
582
582
|
throw new Error(`Task ${taskId} is not an active unsealed changeset.`);
|
|
583
583
|
}
|
|
584
584
|
return active.control.drainPending();
|
|
@@ -589,7 +589,7 @@ export class IsolatedRunner {
|
|
|
589
589
|
throw new Error("Follow-up instruction must be non-empty exact text of at most 32000 characters.");
|
|
590
590
|
}
|
|
591
591
|
const active = this.activeControl(realpathSync.native(root), requestId, taskId);
|
|
592
|
-
if (!active ||
|
|
592
|
+
if (!active || active.task.status !== "working" || active.control.isSealed) {
|
|
593
593
|
throw new Error(`Task ${taskId} is not an active unsealed changeset.`);
|
|
594
594
|
}
|
|
595
595
|
if (latestAttempt(active.task).prompts.length + active.control.pendingFollowups >= MAX_WORKER_PROMPTS) {
|
|
@@ -1093,7 +1093,8 @@ export class IsolatedRunner {
|
|
|
1093
1093
|
return await this.withProductiveRun(root, async (lifecycle) => {
|
|
1094
1094
|
const handle = await this.store.withLock(root, async () => await this.store.load(root, action.id), { productiveRunLease: lifecycle.lease });
|
|
1095
1095
|
const state = handle.state;
|
|
1096
|
-
|
|
1096
|
+
// Loading state has already normalized optional request fields, including finalChecks.
|
|
1097
|
+
const request = state.request;
|
|
1097
1098
|
const generation = state.integration.generations.at(-1);
|
|
1098
1099
|
if (action.action === "release") {
|
|
1099
1100
|
const scope = new ProductiveScope(outerSignal);
|
|
@@ -2300,7 +2301,7 @@ export class IsolatedRunner {
|
|
|
2300
2301
|
const queuedFollowup = control.takeQueuedFollowupOrSeal();
|
|
2301
2302
|
if (queuedFollowup) {
|
|
2302
2303
|
kind = "followup";
|
|
2303
|
-
instruction = queuedFollowup
|
|
2304
|
+
instruction = queuedFollowup;
|
|
2304
2305
|
task.status = "working";
|
|
2305
2306
|
continue;
|
|
2306
2307
|
}
|
|
@@ -2336,7 +2337,7 @@ export class IsolatedRunner {
|
|
|
2336
2337
|
const queuedFollowup = control.takeQueuedFollowup();
|
|
2337
2338
|
if (queuedFollowup) {
|
|
2338
2339
|
kind = "followup";
|
|
2339
|
-
instruction = queuedFollowup
|
|
2340
|
+
instruction = queuedFollowup;
|
|
2340
2341
|
task.status = "working";
|
|
2341
2342
|
continue;
|
|
2342
2343
|
}
|
package/dist/store.js
CHANGED
|
@@ -61,7 +61,7 @@ async function canonicalPlannedPath(path) {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
function serialize(state, maxBytes = STATE_MAX_BYTES) {
|
|
64
|
-
const validated = parseRunState(
|
|
64
|
+
const validated = parseRunState(state);
|
|
65
65
|
const contents = `${JSON.stringify(validated, null, 2)}\n`;
|
|
66
66
|
if (Buffer.byteLength(contents, "utf8") > maxBytes) {
|
|
67
67
|
throw new Error(`pi-subagent state exceeds ${maxBytes} bytes.`);
|
package/extensions/isolated.ts
CHANGED
|
@@ -23,12 +23,12 @@ import {
|
|
|
23
23
|
IntegrationActionParameters,
|
|
24
24
|
parseIntegrationAction,
|
|
25
25
|
parseStageRequest,
|
|
26
|
-
parseExecuteRequest,
|
|
27
26
|
parseIdOnly,
|
|
28
27
|
parseResumeRequest,
|
|
29
28
|
sameIdentity,
|
|
30
29
|
StatusOutputSchema,
|
|
31
30
|
type CheckBatchEvidence,
|
|
31
|
+
type ExecuteRequest,
|
|
32
32
|
type ModelClass,
|
|
33
33
|
type ReviewEvidence,
|
|
34
34
|
type RunState,
|
|
@@ -412,7 +412,7 @@ function toolResult(response: RunResponse, ctx: ExtensionContext, rowsByRequest:
|
|
|
412
412
|
}
|
|
413
413
|
|
|
414
414
|
export interface IsolatedSurface {
|
|
415
|
-
execute(
|
|
415
|
+
execute(request: ExecuteRequest, signal: AbortSignal | undefined, ctx: ExtensionContext): Promise<ReturnType<typeof toolResult>>;
|
|
416
416
|
inventory(cwd: string, current?: () => boolean): Promise<IsolatedInventory>;
|
|
417
417
|
recover(cwd: string): Promise<string>;
|
|
418
418
|
inspect(root: string, requestId: string): Promise<readonly string[]>;
|
|
@@ -767,10 +767,9 @@ export function registerIsolatedExtension(pi: ExtensionAPI, options: RegisterIso
|
|
|
767
767
|
requireCurrent(current);
|
|
768
768
|
return getComponents().runner.drainFollowups(root, requestId, taskId);
|
|
769
769
|
},
|
|
770
|
-
async execute(
|
|
770
|
+
async execute(request, signal, ctx) {
|
|
771
771
|
latestCtx = ctx;
|
|
772
772
|
latestContext();
|
|
773
|
-
const request = parseExecuteRequest(params);
|
|
774
773
|
const root = await lookupRoot(ctx.cwd, signal);
|
|
775
774
|
return await startInSession(request.id, root, signal, ctx,
|
|
776
775
|
(state) => state.status === "pending" && state.createdAt === state.updatedAt
|
package/extensions/workflow.ts
CHANGED
|
@@ -42,8 +42,7 @@ export const DirectWorkflowSchema = Type.Object({
|
|
|
42
42
|
chain: Type.Optional(Type.Array(DelegationSchema, { minItems: 1, maxItems: MAX_WORKFLOW_ENTRIES })),
|
|
43
43
|
}, { additionalProperties: false, description: "Direct mode: exactly one compact single, tasks, or chain workflow" });
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
// Keep the strict mode union for parsing; expose a plain object root to tool providers.
|
|
45
|
+
// Parse each mode with its strict schema; expose a plain object root to tool providers.
|
|
47
46
|
export const DelegateTaskParameters = Type.Object({
|
|
48
47
|
...DirectWorkflowSchema.properties,
|
|
49
48
|
mode: Type.Union([DirectWorkflowSchema.properties.mode, ExecuteRequestSchema.properties.mode]),
|
|
@@ -56,7 +55,6 @@ export const DelegateTaskParameters = Type.Object({
|
|
|
56
55
|
finalChecks: ExecuteRequestSchema.properties.finalChecks,
|
|
57
56
|
finalJudgment: ExecuteRequestSchema.properties.finalJudgment,
|
|
58
57
|
}, { additionalProperties: false });
|
|
59
|
-
export const WorkflowSchema = DirectWorkflowSchema;
|
|
60
58
|
|
|
61
59
|
export type Delegation = Static<typeof DelegationSchema> & { kind: "text" };
|
|
62
60
|
export type WorkflowMode = "single" | "parallel" | "chain";
|