@arhen/pi-core-subagent 1.3.59 → 1.3.60

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arhen/pi-core-subagent",
3
- "version": "1.3.59",
3
+ "version": "1.3.60",
4
4
  "type": "module",
5
5
  "description": "pi extension: fast in-process subagents with a dependency-graph scheduler (needs edges gate tasks and carry upstream output into dependent prompts), plus background runs, intercom and agent-to-agent mailbox. Leader defines agents inline.",
6
6
  "license": "MIT",
package/src/index.ts CHANGED
@@ -381,23 +381,24 @@ export default function (pi: ExtensionAPI) {
381
381
  name: "resume_subagent",
382
382
  label: "Resume Subagent",
383
383
  description:
384
- "Revive a failed/aborted task in its original session (full context + worktree branch preserved). Optional `model` swaps provider (e.g. after a rate limit); optional `message` replaces the default 'recap and continue' prompt. Refuses tasks that never started — respawn those.",
384
+ "Revive a failed/aborted task in its original session (full context + worktree branch preserved). Optional `model` swaps provider (e.g. after a rate limit); optional `thinking` sets the effort — the stored level is clamped to what the target model accepts, so a resume never dies on an unsupported effort; optional `message` replaces the default 'recap and continue' prompt. Refuses tasks that never started — respawn those.",
385
385
  parameters: ResumeParam,
386
386
  async execute(_id, params, _signal, _onUpdate, ctx) {
387
- const { runId, taskId, message, model } = params as {
387
+ const { runId, taskId, message, model, thinking } = params as {
388
388
  runId: string;
389
389
  taskId: string;
390
390
  message?: string;
391
391
  model?: string;
392
+ thinking?: string;
392
393
  };
393
- const res = manager.resumeTask(runId, taskId, ctx, { message, model });
394
+ const res = manager.resumeTask(runId, taskId, ctx, { message, model, thinking });
394
395
  if (!res.ok) return { content: [{ type: "text", text: res.reason }], isError: true, details: {} };
395
396
  const run = manager.getRun(runId);
396
397
  return {
397
398
  content: [
398
399
  {
399
400
  type: "text",
400
- text: `Resumed ${runId}/${taskId} (${res.task.agent})${model ? ` on ${model}` : ""} from ${res.task.sessionFile}${res.task.branch ? `, branch ${res.task.branch}` : ""}.\nNext: subagent_status("${runId}") to confirm it is running; completion will notify you.`,
401
+ text: `Resumed ${runId}/${taskId} (${res.task.agent})${model ? ` on ${model}` : ""} from ${res.task.sessionFile}${res.task.branch ? `, branch ${res.task.branch}` : ""}.${res.note ? ` Adjusted ${res.note}.` : ""}\nNext: subagent_status("${runId}") to confirm it is running; completion will notify you.`,
401
402
  },
402
403
  ],
403
404
  details: { run: run ? cloneRun(run) : undefined },
package/src/manager.ts CHANGED
@@ -68,8 +68,8 @@ const DEFAULT_RUNTIME_MS = 3_600_000;
68
68
  const UNLIMITED_RUNTIME_MS = 21_600_000;
69
69
  const PARENT_REPLY_TIMEOUT_MS = 600_000;
70
70
  const PARKED_MSG_CAP = 24;
71
- const READONLY_TOOLS = ["read", "grep", "find", "ls"];
72
- const WRITE_TOOLS = ["read", "grep", "find", "ls", "bash", "edit", "write"];
71
+ const READONLY_TOOLS = ["read", "grep", "find", "ls", "codemode"];
72
+ const WRITE_TOOLS = ["read", "grep", "find", "ls", "bash", "edit", "write", "codemode"];
73
73
  const WRITE_CAPABLE = ["bash", "edit", "write"];
74
74
  const SAFE_TASK_ID = /^[A-Za-z0-9_-]{1,64}$/;
75
75
  const WIDGET_THROTTLE_MS = 150;
@@ -77,6 +77,25 @@ const WIDGET_THROTTLE_MS = 150;
77
77
  function newId(prefix: string): string {
78
78
  return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
79
79
  }
80
+
81
+ type ChildExtensionFactories = ConstructorParameters<typeof DefaultResourceLoader>[0]["extensionFactories"];
82
+
83
+ /**
84
+ * Children run with `noExtensions`, so they get none of the configured extensions — codemode is the
85
+ * one exception, because it is how a child batches tool calls. `createCodemodeExtension()` is the
86
+ * supported factory (pi >= 1.0); hosts that do not export it simply give children no codemode.
87
+ */
88
+ async function codemodeFactories(): Promise<ChildExtensionFactories> {
89
+ try {
90
+ const host = (await import("@earendil-works/pi-coding-agent")) as unknown as {
91
+ createCodemodeExtension?: () => unknown;
92
+ };
93
+ const factory = host.createCodemodeExtension?.();
94
+ return factory ? ([factory] as ChildExtensionFactories) : [];
95
+ } catch {
96
+ return [];
97
+ }
98
+ }
80
99
  function emptyUsage(): UsageStats {
81
100
  return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
82
101
  }
@@ -143,6 +162,18 @@ function updateUsageFromMessage(task: TaskSnapshot, message: AssistantMessage):
143
162
  export function cloneRun(run: RunSnapshot): RunSnapshot {
144
163
  return JSON.parse(JSON.stringify(run)) as RunSnapshot;
145
164
  }
165
+ /**
166
+ * A resumed task keeps its stored thinking level, clamped to what the target model accepts — a
167
+ * resume that swaps model must not fail on an effort the new model does not define.
168
+ */
169
+ export function clampResumeThinking(
170
+ model: Model<Api> | undefined,
171
+ thinking: ThinkingLevel | undefined,
172
+ ): ThinkingLevel | undefined {
173
+ if (!thinking || !model) return thinking;
174
+ return clampThinkingLevel(model, thinking) as ThinkingLevel;
175
+ }
176
+
146
177
  export function resolveChildModel(ctx: ExtensionContext, explicit: string | undefined) {
147
178
  if (!explicit?.trim()) return ctx.model;
148
179
  const ref = explicit.trim();
@@ -900,6 +931,7 @@ export class SubagentManager {
900
931
  cwd: childCwd,
901
932
  agentDir: getAgentDir(),
902
933
  noExtensions: true,
934
+ extensionFactories: await codemodeFactories(),
903
935
  appendSystemPromptOverride: (base) => [
904
936
  ...base,
905
937
  [prompt?.trim(), subagentInstruction].filter(Boolean).join("\n\n"),
@@ -1380,8 +1412,8 @@ export class SubagentManager {
1380
1412
  runId: string,
1381
1413
  taskId: string,
1382
1414
  ctx: ExtensionContext,
1383
- opts: { message?: string; model?: string } = {},
1384
- ): { ok: true; task: TaskSnapshot } | { ok: false; reason: string } {
1415
+ opts: { message?: string; model?: string; thinking?: string } = {},
1416
+ ): { ok: true; task: TaskSnapshot; note?: string } | { ok: false; reason: string } {
1385
1417
  const run = this.runs.get(runId);
1386
1418
  const task = run?.tasks.find((t) => t.id === taskId);
1387
1419
  if (!run || !task) return { ok: false, reason: `Unknown ${runId}/${taskId}.` };
@@ -1399,6 +1431,19 @@ export class SubagentManager {
1399
1431
 
1400
1432
  const tools = task.tools?.filter((t) => !(CHILD_TALK_TOOLS as readonly string[]).includes(t));
1401
1433
  const write = tools?.some((t) => WRITE_CAPABLE.includes(t)) ?? false;
1434
+ // A resume may swap the model, so the level stored on the task can be one the new model
1435
+ // rejects (a mode-clamped xhigh onto a model that only takes low|high|max). Clamp it, or take
1436
+ // the caller's explicit level and clamp that.
1437
+ let resumeModel: Model<Api> | undefined;
1438
+ try {
1439
+ resumeModel = resolveChildModel(ctx, opts.model ?? task.model);
1440
+ } catch {}
1441
+ const requestedThinking = (opts.thinking ?? task.thinking) as ThinkingLevel | undefined;
1442
+ const thinking = clampResumeThinking(resumeModel, requestedThinking);
1443
+ const thinkingNote =
1444
+ requestedThinking && thinking !== requestedThinking
1445
+ ? `thinking ${requestedThinking} → ${thinking} (${resumeModel?.provider}/${resumeModel?.id} does not accept ${requestedThinking})`
1446
+ : undefined;
1402
1447
  const input: TaskInput = {
1403
1448
  id: task.id,
1404
1449
  agent: task.agent,
@@ -1407,7 +1452,7 @@ export class SubagentManager {
1407
1452
  write,
1408
1453
  tools: tools?.length ? tools : undefined,
1409
1454
  model: opts.model ?? task.model,
1410
- thinking: task.thinking as TaskInput["thinking"],
1455
+ thinking: thinking as TaskInput["thinking"],
1411
1456
  needs: task.needs,
1412
1457
  };
1413
1458
  const resume: ResumeInput = {
@@ -1422,6 +1467,7 @@ export class SubagentManager {
1422
1467
  this.turnActivity = true;
1423
1468
  Object.assign(task, {
1424
1469
  status: "queued" as TaskStatus,
1470
+ thinking,
1425
1471
  error: undefined,
1426
1472
  endedAt: undefined,
1427
1473
  finalText: undefined,
@@ -1454,7 +1500,7 @@ export class SubagentManager {
1454
1500
  if (run.notifyPerTask) this.notifyTask(run, task, task.status as "completed" | "failed" | "aborted");
1455
1501
  this.finishRunIfSettled(run, ctx);
1456
1502
  });
1457
- return { ok: true, task };
1503
+ return { ok: true, task, note: thinkingNote };
1458
1504
  }
1459
1505
 
1460
1506
  private finishRunIfSettled(run: RunSnapshot, ctx: ExtensionContext): void {
package/src/schemas.ts CHANGED
@@ -89,6 +89,12 @@ export const ResumeParam = Type.Object({
89
89
  "Model override for the resumed session (provider/model-id) — use when the original provider is rate-limited",
90
90
  }),
91
91
  ),
92
+ thinking: Type.Optional(
93
+ StringEnum(THINKING_LEVELS, {
94
+ description:
95
+ "Thinking level for the resumed session. Default: the task's stored level, clamped to what the target model accepts",
96
+ }),
97
+ ),
92
98
  });
93
99
  export const SteerParam = Type.Object({
94
100
  runId: Type.String(),