oira666_pi-subagent 0.2.5 → 0.2.7
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/index.ts +43 -3
- package/package.json +1 -1
- package/runner.ts +20 -0
package/index.ts
CHANGED
|
@@ -376,18 +376,20 @@ const RESUME_STATE_KEY = "__piSubagentResumeState";
|
|
|
376
376
|
type SyntheticResumeState = {
|
|
377
377
|
plan: ResumableSubagentCall | null;
|
|
378
378
|
phase: "tool" | "final";
|
|
379
|
+
trigger: "resumePrompt" | "nextRequest";
|
|
379
380
|
};
|
|
380
381
|
|
|
381
382
|
function clearSyntheticResumeState(): void {
|
|
382
383
|
const state = getSyntheticResumeState();
|
|
383
384
|
state.plan = null;
|
|
384
385
|
state.phase = "tool";
|
|
386
|
+
state.trigger = "resumePrompt";
|
|
385
387
|
}
|
|
386
388
|
|
|
387
389
|
function getSyntheticResumeState(): SyntheticResumeState {
|
|
388
390
|
const g = globalThis as any;
|
|
389
391
|
if (!g[RESUME_STATE_KEY]) {
|
|
390
|
-
g[RESUME_STATE_KEY] = { plan: null, phase: "tool" } satisfies SyntheticResumeState;
|
|
392
|
+
g[RESUME_STATE_KEY] = { plan: null, phase: "tool", trigger: "resumePrompt" } satisfies SyntheticResumeState;
|
|
391
393
|
}
|
|
392
394
|
return g[RESUME_STATE_KEY] as SyntheticResumeState;
|
|
393
395
|
}
|
|
@@ -423,6 +425,35 @@ function formatModelFlag(model: any): string | undefined {
|
|
|
423
425
|
return `${model.provider}/${model.id}`;
|
|
424
426
|
}
|
|
425
427
|
|
|
428
|
+
function findLastNonResumeModel(ctx: any): any | undefined {
|
|
429
|
+
const entries = (() => {
|
|
430
|
+
const leafId = ctx.sessionManager?.getLeafId?.();
|
|
431
|
+
if (leafId) {
|
|
432
|
+
const branch = ctx.sessionManager?.getBranch?.(leafId);
|
|
433
|
+
if (Array.isArray(branch)) return branch;
|
|
434
|
+
}
|
|
435
|
+
const all = ctx.sessionManager?.getEntries?.();
|
|
436
|
+
return Array.isArray(all) ? all : [];
|
|
437
|
+
})();
|
|
438
|
+
|
|
439
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
440
|
+
const entry = entries[i];
|
|
441
|
+
if (entry?.type !== "model_change") continue;
|
|
442
|
+
const provider = entry.provider;
|
|
443
|
+
const modelId = entry.modelId;
|
|
444
|
+
if (provider === RESUME_PROVIDER) continue;
|
|
445
|
+
if (typeof provider !== "string" || typeof modelId !== "string") continue;
|
|
446
|
+
const model = ctx.modelRegistry?.find?.(provider, modelId);
|
|
447
|
+
if (model) return model;
|
|
448
|
+
}
|
|
449
|
+
return undefined;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function getRestorableModel(ctx: any): any | undefined {
|
|
453
|
+
if (ctx.model?.provider && ctx.model.provider !== RESUME_PROVIDER) return ctx.model;
|
|
454
|
+
return findLastNonResumeModel(ctx);
|
|
455
|
+
}
|
|
456
|
+
|
|
426
457
|
// ---------------------------------------------------------------------------
|
|
427
458
|
// Extension entry point
|
|
428
459
|
// ---------------------------------------------------------------------------
|
|
@@ -449,7 +480,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
449
480
|
const state = getSyntheticResumeState();
|
|
450
481
|
const plan = state.plan;
|
|
451
482
|
const phase = state.phase;
|
|
452
|
-
|
|
483
|
+
const triggerMatches =
|
|
484
|
+
state.trigger === "nextRequest" ||
|
|
485
|
+
(plan ? isSyntheticResumePrompt(context, plan.tasks.length) : false);
|
|
486
|
+
if (plan && phase === "tool" && triggerMatches) {
|
|
453
487
|
state.phase = "final";
|
|
454
488
|
const toolCall = {
|
|
455
489
|
type: "toolCall" as const,
|
|
@@ -579,6 +613,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
579
613
|
);
|
|
580
614
|
}
|
|
581
615
|
|
|
616
|
+
const restorableModel = getRestorableModel(ctx);
|
|
617
|
+
if (ctx.model?.provider === RESUME_PROVIDER && restorableModel) {
|
|
618
|
+
await pi.setModel(restorableModel);
|
|
619
|
+
}
|
|
620
|
+
|
|
582
621
|
const resumeDisabled = parseBooleanEnv(process.env[SUBAGENT_RESUME_DISABLE_ENV]) === true;
|
|
583
622
|
if (resumeDisabled || (event.reason !== "resume" && event.reason !== "startup")) return;
|
|
584
623
|
|
|
@@ -599,7 +638,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
599
638
|
const resumeState = getSyntheticResumeState();
|
|
600
639
|
resumeState.plan = plan;
|
|
601
640
|
resumeState.phase = "tool";
|
|
602
|
-
|
|
641
|
+
resumeState.trigger = hasCliInitialPrompt(process.argv) ? "nextRequest" : "resumePrompt";
|
|
642
|
+
modelToRestoreAfterResume = restorableModel ?? ctx.model;
|
|
603
643
|
resumeModelRegistry = ctx.modelRegistry;
|
|
604
644
|
ensureSubagentToolActive(pi);
|
|
605
645
|
const resumeModel = ctx.modelRegistry.find(RESUME_PROVIDER, RESUME_MODEL_ID);
|
package/package.json
CHANGED
package/runner.ts
CHANGED
|
@@ -48,11 +48,24 @@ const TERMINAL_STOP_REASONS = new Set(["end_turn", "stop", "max_tokens", "error"
|
|
|
48
48
|
function isTerminalStopReason(reason: string | undefined): boolean {
|
|
49
49
|
return reason !== undefined && TERMINAL_STOP_REASONS.has(reason);
|
|
50
50
|
}
|
|
51
|
+
|
|
52
|
+
function endedWithEmptySyntheticResume(messages: Message[]): boolean {
|
|
53
|
+
const lastAssistant = [...messages].reverse().find((message: any) => message?.role === "assistant") as any;
|
|
54
|
+
return (
|
|
55
|
+
lastAssistant?.provider === RESUME_PROVIDER &&
|
|
56
|
+
lastAssistant?.model === RESUME_MODEL_ID &&
|
|
57
|
+
lastAssistant?.stopReason === "stop" &&
|
|
58
|
+
Array.isArray(lastAssistant.content) &&
|
|
59
|
+
lastAssistant.content.length === 0
|
|
60
|
+
);
|
|
61
|
+
}
|
|
51
62
|
const SUBAGENT_DEPTH_ENV = "PI_SUBAGENT_DEPTH";
|
|
52
63
|
const SUBAGENT_MAX_DEPTH_ENV = "PI_SUBAGENT_MAX_DEPTH";
|
|
53
64
|
const SUBAGENT_STACK_ENV = "PI_SUBAGENT_STACK";
|
|
54
65
|
const SUBAGENT_PREVENT_CYCLES_ENV = "PI_SUBAGENT_PREVENT_CYCLES";
|
|
55
66
|
const SUBAGENT_FALLBACK_MODEL_ENV = "PI_SUBAGENT_FALLBACK_MODEL";
|
|
67
|
+
const RESUME_PROVIDER = "pi-subagent-resume";
|
|
68
|
+
const RESUME_MODEL_ID = "synthetic-tool-call";
|
|
56
69
|
// PI_OFFLINE intentionally removed: setting it on child processes blocks all API
|
|
57
70
|
// calls and renders subagents unable to do any LLM work. Children inherit the
|
|
58
71
|
// parent's PI_OFFLINE value via process.env spread if needed.
|
|
@@ -730,6 +743,13 @@ export async function runAgentSubprocess(opts: RunAgentOptions): Promise<SingleR
|
|
|
730
743
|
if (!result.stderr.trim()) result.stderr = "Subagent was aborted.";
|
|
731
744
|
}
|
|
732
745
|
|
|
746
|
+
if (result.exitCode === 0 && endedWithEmptySyntheticResume(result.messages)) {
|
|
747
|
+
result.exitCode = 1;
|
|
748
|
+
result.stopReason = "error";
|
|
749
|
+
result.errorMessage = "Subagent resume failed before the real model continued.";
|
|
750
|
+
if (!result.stderr.trim()) result.stderr = result.errorMessage;
|
|
751
|
+
}
|
|
752
|
+
|
|
733
753
|
if (result.exitCode === 0) {
|
|
734
754
|
const nestedErrorSummary = getNestedSubagentErrorSummary(result.messages);
|
|
735
755
|
if (nestedErrorSummary) {
|