@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 +1 -1
- package/src/index.ts +5 -4
- package/src/manager.ts +52 -6
- package/src/schemas.ts +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arhen/pi-core-subagent",
|
|
3
|
-
"version": "1.3.
|
|
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}` : ""}
|
|
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:
|
|
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(),
|