oira666_pi-subagent 0.2.6 → 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.
Files changed (3) hide show
  1. package/index.ts +35 -1
  2. package/package.json +1 -1
  3. package/runner.ts +20 -0
package/index.ts CHANGED
@@ -425,6 +425,35 @@ function formatModelFlag(model: any): string | undefined {
425
425
  return `${model.provider}/${model.id}`;
426
426
  }
427
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
+
428
457
  // ---------------------------------------------------------------------------
429
458
  // Extension entry point
430
459
  // ---------------------------------------------------------------------------
@@ -584,6 +613,11 @@ export default function (pi: ExtensionAPI) {
584
613
  );
585
614
  }
586
615
 
616
+ const restorableModel = getRestorableModel(ctx);
617
+ if (ctx.model?.provider === RESUME_PROVIDER && restorableModel) {
618
+ await pi.setModel(restorableModel);
619
+ }
620
+
587
621
  const resumeDisabled = parseBooleanEnv(process.env[SUBAGENT_RESUME_DISABLE_ENV]) === true;
588
622
  if (resumeDisabled || (event.reason !== "resume" && event.reason !== "startup")) return;
589
623
 
@@ -605,7 +639,7 @@ export default function (pi: ExtensionAPI) {
605
639
  resumeState.plan = plan;
606
640
  resumeState.phase = "tool";
607
641
  resumeState.trigger = hasCliInitialPrompt(process.argv) ? "nextRequest" : "resumePrompt";
608
- modelToRestoreAfterResume = ctx.model;
642
+ modelToRestoreAfterResume = restorableModel ?? ctx.model;
609
643
  resumeModelRegistry = ctx.modelRegistry;
610
644
  ensureSubagentToolActive(pi);
611
645
  const resumeModel = ctx.modelRegistry.find(RESUME_PROVIDER, RESUME_MODEL_ID);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oira666_pi-subagent",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "Subagent extension for Pi coding agent. Delegate tasks to specialized agents.",
5
5
  "type": "module",
6
6
  "main": "index.ts",
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) {