@arhen/pi-core-subagent 1.3.56 → 1.3.58
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/format.ts +7 -0
- package/src/index.ts +2 -1
- package/src/manager.ts +50 -12
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.58",
|
|
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/format.ts
CHANGED
|
@@ -111,10 +111,17 @@ export function isTalking(task: TaskSnapshot): boolean {
|
|
|
111
111
|
return TALK_TOOLS.some((t) => a.startsWith(t));
|
|
112
112
|
}
|
|
113
113
|
let pulsePhase = 0;
|
|
114
|
+
const NOTE_CAP = 240;
|
|
115
|
+
function noteSnippet(note: string): string {
|
|
116
|
+
return note.length > NOTE_CAP ? `${note.slice(0, NOTE_CAP)}…` : note;
|
|
117
|
+
}
|
|
114
118
|
export function compactLines(run: RunSnapshot): string[] {
|
|
115
119
|
const lines: string[] = [];
|
|
116
120
|
for (const task of run.tasks.slice(0, MAX_TASKS)) {
|
|
117
121
|
lines.push(taskLine(task));
|
|
122
|
+
// a swapped model or toolset changes what the task did, so it cannot stay out of the status view
|
|
123
|
+
if (task.modelNote) lines.push(` ↳ Model: ${noteSnippet(task.modelNote)}`);
|
|
124
|
+
if (task.toolsNote) lines.push(` ↳ Tools: ${noteSnippet(task.toolsNote)}`);
|
|
118
125
|
}
|
|
119
126
|
if (run.tasks.length > MAX_TASKS) lines.push(`… +${run.tasks.length - MAX_TASKS} more`);
|
|
120
127
|
return lines;
|
package/src/index.ts
CHANGED
|
@@ -299,7 +299,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
299
299
|
? `\nApplied IN PLACE (no branch) — ${t.isolationReason ?? "worktree unavailable"}. The changes are already in your working tree.`
|
|
300
300
|
: "";
|
|
301
301
|
const wtErr = t.worktreeError ? `\nWorktree: ${t.worktreeError}` : "";
|
|
302
|
-
|
|
302
|
+
const modelNote = t.modelNote ? `\nModel: ${t.modelNote}` : "";
|
|
303
|
+
return `\n## ${t.agent} ${statusIcon(t.status)}\nGoal: ${truncateText(t.task, 300)}\n${t.error ? `Error: ${t.error}` : t.finalText || "(no output yet)"}${wt}${wtErr}${modelNote}\n${formatUsage(t.usage)}`;
|
|
303
304
|
}),
|
|
304
305
|
].join("\n");
|
|
305
306
|
return { content: [{ type: "text", text: truncateText(text) }], details: { run: cloneRun(run) } };
|
package/src/manager.ts
CHANGED
|
@@ -2,7 +2,13 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, rmSync
|
|
|
2
2
|
import { rename, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, join, relative, sep } from "node:path";
|
|
4
4
|
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
5
|
-
import
|
|
5
|
+
import {
|
|
6
|
+
type Api,
|
|
7
|
+
type AssistantMessage,
|
|
8
|
+
clampThinkingLevel,
|
|
9
|
+
getSupportedThinkingLevels,
|
|
10
|
+
type Model,
|
|
11
|
+
} from "@earendil-works/pi-ai";
|
|
6
12
|
import {
|
|
7
13
|
type AgentSessionEvent,
|
|
8
14
|
createAgentSession,
|
|
@@ -157,16 +163,36 @@ export function resolveChildModel(ctx: ExtensionContext, explicit: string | unde
|
|
|
157
163
|
throw new Error(`Model not found: ${ref}`);
|
|
158
164
|
}
|
|
159
165
|
|
|
166
|
+
const PROBE_THINKING_LEVELS: ThinkingLevel[] = ["low", "minimal", "medium", "high", "xhigh", "max"];
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Thinking level for the usability probe: exactly what the child session will send — the clamped
|
|
170
|
+
* requested level, or the cheapest the model accepts when none was requested. Probing without a
|
|
171
|
+
* level makes adaptive-thinking providers reject the request (9router claude models answer
|
|
172
|
+
* "thinking.type.disabled is not supported"), which used to make every preflight fail and fall
|
|
173
|
+
* back to the session model.
|
|
174
|
+
*/
|
|
175
|
+
function probeThinking(model: Model<Api>, thinking?: string): ThinkingLevel | undefined {
|
|
176
|
+
if (!model.reasoning) return undefined;
|
|
177
|
+
const supported = getSupportedThinkingLevels(model);
|
|
178
|
+
if (thinking && thinking !== "off") return clampThinkingLevel(model, thinking as ThinkingLevel);
|
|
179
|
+
// the child clamps an unsupported "off" up to its cheapest level, so probe that instead
|
|
180
|
+
if (thinking === "off") return supported.includes("off") ? undefined : supported[0];
|
|
181
|
+
return PROBE_THINKING_LEVELS.find((level) => supported.includes(level));
|
|
182
|
+
}
|
|
183
|
+
|
|
160
184
|
async function probeModel(
|
|
161
185
|
ctx: ExtensionContext,
|
|
162
186
|
model: Model<Api>,
|
|
163
187
|
signal: AbortSignal | undefined,
|
|
188
|
+
thinking?: string,
|
|
164
189
|
): Promise<string | undefined> {
|
|
165
190
|
try {
|
|
191
|
+
const reasoningEffort = probeThinking(model, thinking);
|
|
166
192
|
const reply = await ctx.modelRegistry.complete(
|
|
167
193
|
model,
|
|
168
194
|
{ messages: [{ role: "user", content: "ping", timestamp: Date.now() }] },
|
|
169
|
-
{ maxTokens: 16, signal },
|
|
195
|
+
{ maxTokens: 16, signal, ...(reasoningEffort ? { reasoningEffort } : {}) },
|
|
170
196
|
);
|
|
171
197
|
return reply.stopReason === "error" ? (reply.errorMessage ?? "provider returned an error") : undefined;
|
|
172
198
|
} catch (err) {
|
|
@@ -178,11 +204,12 @@ export async function ensureUsableModel(
|
|
|
178
204
|
ctx: ExtensionContext,
|
|
179
205
|
model: Model<Api> | undefined,
|
|
180
206
|
signal: AbortSignal | undefined,
|
|
207
|
+
thinking?: string,
|
|
181
208
|
): Promise<{ model: Model<Api> | undefined; note?: string }> {
|
|
182
209
|
const session = ctx.model;
|
|
183
210
|
if (!model || !ctx.modelRegistry) return { model };
|
|
184
211
|
if (session && model.provider === session.provider && model.id === session.id) return { model };
|
|
185
|
-
const error = await probeModel(ctx, model, signal);
|
|
212
|
+
const error = await probeModel(ctx, model, signal, thinking);
|
|
186
213
|
if (!error) return { model };
|
|
187
214
|
if (model.provider === "opencode-go" && /MissingSessionID|x-opencode-session/i.test(error)) {
|
|
188
215
|
// ponytail: opencode-go rejects stateless probes but accepts AgentSession requests, which add the session header.
|
|
@@ -302,10 +329,10 @@ export class SubagentManager {
|
|
|
302
329
|
return false;
|
|
303
330
|
}
|
|
304
331
|
|
|
305
|
-
clearWidget(ctx
|
|
332
|
+
clearWidget(ctx?: ExtensionContext): void {
|
|
306
333
|
this.widgetRuns = [];
|
|
307
334
|
this.widgetTui = null;
|
|
308
|
-
if (ctx
|
|
335
|
+
if (ctx?.hasUI) {
|
|
309
336
|
try {
|
|
310
337
|
ctx.ui.setWidget("subagents", undefined);
|
|
311
338
|
} catch {}
|
|
@@ -483,8 +510,14 @@ export class SubagentManager {
|
|
|
483
510
|
}
|
|
484
511
|
|
|
485
512
|
private widgetTui: TUI | null = null;
|
|
513
|
+
|
|
514
|
+
/** Settled runs are history (subagent_status/result still reach them) — the widget shows live work only. */
|
|
515
|
+
private pruneSettledWidget(): void {
|
|
516
|
+
this.widgetRuns = this.widgetRuns.filter((r) => !TERMINAL.includes(r.status));
|
|
517
|
+
}
|
|
486
518
|
private upsertWidgetRun(run: RunSnapshot | undefined): void {
|
|
487
|
-
|
|
519
|
+
this.pruneSettledWidget();
|
|
520
|
+
if (!run || TERMINAL.includes(run.status)) return;
|
|
488
521
|
const idx = this.widgetRuns.findIndex((r) => r.id === run.id);
|
|
489
522
|
if (idx >= 0) this.widgetRuns[idx] = run;
|
|
490
523
|
else this.widgetRuns.push(run);
|
|
@@ -524,12 +557,17 @@ export class SubagentManager {
|
|
|
524
557
|
this.widgetTimers.delete(run.id);
|
|
525
558
|
}
|
|
526
559
|
}
|
|
527
|
-
|
|
528
|
-
if (
|
|
529
|
-
this.
|
|
530
|
-
|
|
560
|
+
this.pruneSettledWidget();
|
|
561
|
+
if (this.widgetRuns.length === 0) {
|
|
562
|
+
if (this.widgetTui) this.clearWidget(ctx);
|
|
563
|
+
} else {
|
|
564
|
+
if (ctx?.hasUI) {
|
|
565
|
+
this.ensureWidget(ctx);
|
|
566
|
+
this.widgetTui?.requestRender();
|
|
567
|
+
}
|
|
568
|
+
this.maybePulse(ctx);
|
|
531
569
|
}
|
|
532
|
-
|
|
570
|
+
if (!run) return;
|
|
533
571
|
|
|
534
572
|
onUpdate?.({
|
|
535
573
|
content: [
|
|
@@ -747,7 +785,7 @@ export class SubagentManager {
|
|
|
747
785
|
model = resolveChildModel(ctx, file?.model ?? input.model);
|
|
748
786
|
validateThinking(model, thinking);
|
|
749
787
|
|
|
750
|
-
const checked = await ensureUsableModel(ctx, model, signal);
|
|
788
|
+
const checked = await ensureUsableModel(ctx, model, signal, thinking);
|
|
751
789
|
model = checked.model;
|
|
752
790
|
if (checked.note) {
|
|
753
791
|
task.modelNote = checked.note;
|