@nklisch/pi-enhanced 0.1.8 → 0.1.9
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/CHANGELOG.md +6 -0
- package/node_modules/@nklisch/pi-clearance/native/clearance-core.win32-arm64-msvc.node +0 -0
- package/node_modules/@nklisch/pi-clearance/native/clearance-core.win32-x64-msvc.node +0 -0
- package/node_modules/@nklisch/pi-plugins/README.md +4 -4
- package/node_modules/@nklisch/pi-plugins/dist/application/plugin-lifecycle-service.js +19 -2
- package/node_modules/@nklisch/pi-plugins/dist/application/plugin-lifecycle-service.js.map +1 -1
- package/node_modules/@nklisch/pi-plugins/dist/application/ports/lifecycle-transition-store.d.ts +12 -0
- package/node_modules/@nklisch/pi-plugins/dist/application/ports/lifecycle-transition-store.js.map +1 -1
- package/node_modules/@nklisch/pi-plugins/dist/infrastructure/recovery/create-node-recovery-adapters.js +2 -1
- package/node_modules/@nklisch/pi-plugins/dist/infrastructure/recovery/create-node-recovery-adapters.js.map +1 -1
- package/node_modules/@nklisch/pi-plugins/dist/infrastructure/recovery/sqlite-transition-journal.d.ts +4 -0
- package/node_modules/@nklisch/pi-plugins/dist/infrastructure/recovery/sqlite-transition-journal.js +28 -1
- package/node_modules/@nklisch/pi-plugins/dist/infrastructure/recovery/sqlite-transition-journal.js.map +1 -1
- package/node_modules/@nklisch/pi-plugins/dist/runtime/mcp/pi-mcp-adapter-package.js +1 -1
- package/node_modules/@nklisch/pi-plugins/dist/runtime/mcp/pi-mcp-adapter-runtime.js +1 -1
- package/node_modules/@nklisch/pi-plugins/dist/runtime/subagents/pi-subagents-package.js +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/CHANGELOG.md +11 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/dist/public.d.ts +3 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/FORK-MAINTENANCE.md +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/VISION.md +2 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/architecture/architecture.md +4 -4
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/architecture/client-server-opportunities.md +4 -4
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/comparison-with-upstream.md +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/package.json +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/index.ts +8 -8
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/create-subagent-session.ts +1 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/parent-snapshot.ts +3 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent-manager.ts +16 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent-session.ts +12 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent.ts +18 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/composite-subagent-observer.ts +8 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/notification.ts +3 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/renderer.ts +2 -2
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/runtime.ts +1 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/service/service-adapter.ts +11 -3
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/session/session-config.ts +7 -3
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/session/thinking-level.ts +42 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/agent-tool.ts +3 -3
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/background-spawner.ts +7 -2
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/foreground-runner.ts +7 -5
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/get-result-report.ts +4 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/get-result-tool.ts +1 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/helpers.ts +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/result-renderer.ts +7 -4
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/spawn-config.ts +15 -2
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/steer-tool.ts +3 -3
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/types.ts +6 -2
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/agent-widget.ts +117 -62
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/display.ts +8 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/session-navigation.ts +7 -3
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/session-navigator.ts +2 -2
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/widget-renderer.ts +6 -4
- package/node_modules/@nklisch/pi-plugins/package.json +3 -3
- package/package.json +2 -2
|
@@ -21,7 +21,7 @@ A subagent **is** a child `AgentSession`. pi-subagents already creates one via `
|
|
|
21
21
|
Today that child session is an in-process object visible only through machinery pi-subagents had to invent because Pi has no session-sync client:
|
|
22
22
|
|
|
23
23
|
- `record-observer` re-derives live activity from raw session events.
|
|
24
|
-
- the
|
|
24
|
+
- the lifecycle-fed widget read model and 500 ms refresh cadence.
|
|
25
25
|
- the [#277] Law-of-Demeter accessors (`Subagent.getConversation()`, `.messages`, `.subscribeToUpdates()`, `.getContextPercent()`) that re-expose session internals.
|
|
26
26
|
- the bespoke `ConversationViewer`, and [ADR-0004]'s replacement, native session navigation.
|
|
27
27
|
- [ADR-0004]'s dual-source-by-liveness split (tracked agent → in-memory record; evicted → file snapshot).
|
|
@@ -40,9 +40,9 @@ What changes versus today:
|
|
|
40
40
|
The snapshot carries in-flight state, so an operator attaching to an already-running subagent immediately sees its current streaming message and pending tools — not just future events.
|
|
41
41
|
Today the conversation viewer only catches future deltas plus whatever happens to be in the in-memory record.
|
|
42
42
|
2. **The widget and viewer become thin renderers** of synchronized session state.
|
|
43
|
-
The
|
|
43
|
+
The lifecycle-fed widget read model, `record-observer`, and the [#277] accessors disappear — they were all substitutes for `subscribeSession`.
|
|
44
44
|
3. **A unified, reconnect-safe live session list.**
|
|
45
|
-
The global event scope (`session_created`, `session_status_changed` idle/busy) means the operator sees every subagent the instant it spawns, with a live status badge per agent — replacing
|
|
45
|
+
The global event scope (`session_created`, `session_status_changed` idle/busy) means the operator sees every subagent the instant it spawns, with a live status badge per agent — replacing the widget's lifecycle-fed read model and the hand-rolled `subagents:*` broadcast tier.
|
|
46
46
|
4. **Multi-session client state** lets one operator client hold synchronized caches for N subagents at once.
|
|
47
47
|
This is the clean foundation for the parallel-agent navigation gesture that [ADR-0004]'s spike (entry-criterion #3) struggled to design — tabbed or split views of multiple live subagents fall out of the model with no redesign.
|
|
48
48
|
|
|
@@ -96,7 +96,7 @@ This validates and sharpens the direction the architecture doc is already headin
|
|
|
96
96
|
| Today (pi-subagents reinvents it) | Under the server architecture |
|
|
97
97
|
| ------------------------------------------------------------------------ | ---------------------------------------------------------- |
|
|
98
98
|
| `record-observer` re-deriving live activity | `subscribeSession` delta stream |
|
|
99
|
-
|
|
|
99
|
+
| Lifecycle-fed widget read model + 500 ms refresh | `session_created` / `session_status_changed` global events |
|
|
100
100
|
| [#277] accessors (`messages`, `subscribeToUpdates`, `getContextPercent`) | `SessionSnapshot` + deltas |
|
|
101
101
|
| `ConversationViewer` / native session navigation | client renders the snapshot through Pi's own components |
|
|
102
102
|
| dual-source-by-liveness split | server rehydrates; client sees one session shape |
|
|
@@ -21,7 +21,7 @@ The direct `gotgenes` lineage and this fork also share a focused-core architectu
|
|
|
21
21
|
This fork retains several contracts that must survive selective upstream ports:
|
|
22
22
|
|
|
23
23
|
- ordered lifecycle interception for prompt, result, and continuation decisions;
|
|
24
|
-
- exact `provider/id
|
|
24
|
+
- exact `provider/id`, exact effective thinking level, and elapsed or final runtime on every operator status surface;
|
|
25
25
|
- a typed service and deterministic child-session lifecycle;
|
|
26
26
|
- only `general-purpose` and `Explore` as built-in agents;
|
|
27
27
|
- model runtime inheritance for runtime-registered providers and authentication;
|
|
@@ -56,9 +56,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
56
56
|
const runtime = createSubagentRuntime();
|
|
57
57
|
|
|
58
58
|
// ---- Notification system ----
|
|
59
|
-
// Owns completion nudges and live-activity cleanup. The widget
|
|
60
|
-
//
|
|
61
|
-
//
|
|
59
|
+
// Owns completion nudges and live-activity cleanup. The widget owns its
|
|
60
|
+
// bounded lifecycle read model, so NotificationManager has no widget
|
|
61
|
+
// dependency — keeping the construction graph a cycle-free DAG.
|
|
62
62
|
const notifications = new NotificationManager(
|
|
63
63
|
(msg, opts) => pi.sendMessage(msg, opts),
|
|
64
64
|
);
|
|
@@ -84,8 +84,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
84
84
|
|
|
85
85
|
// Fan-out observer: lets the widget subscribe as a second lifecycle consumer
|
|
86
86
|
// while the manager keeps its single-observer contract. The widget is added
|
|
87
|
-
//
|
|
88
|
-
// only at spawn time, so registering late is safe.
|
|
87
|
+
// before any spawn, so lifecycle callbacks populate its bounded read model.
|
|
89
88
|
const observer = new CompositeSubagentObserver([eventsObserver]);
|
|
90
89
|
|
|
91
90
|
const subagentSessionDeps: SubagentSessionDeps = {
|
|
@@ -134,10 +133,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
134
133
|
pi.on("session_before_switch", () => lifecycle.handleSessionBeforeSwitch());
|
|
135
134
|
pi.on("session_shutdown", () => lifecycle.handleSessionShutdown());
|
|
136
135
|
|
|
137
|
-
// Live widget:
|
|
138
|
-
//
|
|
139
|
-
const widget = new AgentWidget(
|
|
136
|
+
// Live widget: a lifecycle observer with a bounded reactive read model; it
|
|
137
|
+
// never rescans the manager's retained terminal-record history.
|
|
138
|
+
const widget = new AgentWidget(registry);
|
|
140
139
|
observer.add(widget);
|
|
140
|
+
pi.on("session_shutdown", () => widget.dispose());
|
|
141
141
|
|
|
142
142
|
// Grab UI context from first tool execution + clear lingering widget on new turn
|
|
143
143
|
const toolStart = new ToolStartHandler(widget);
|
|
@@ -16,6 +16,8 @@ export interface ParentSnapshot {
|
|
|
16
16
|
systemPrompt: string;
|
|
17
17
|
/** Parent's current model instance (fallback when agent config has no model). */
|
|
18
18
|
model: unknown;
|
|
19
|
+
/** Parent's current effective thinking level, used when the child inherits it. */
|
|
20
|
+
thinkingLevel?: SessionContext["thinkingLevel"];
|
|
19
21
|
/** Model registry compatibility facade used for synchronous model lookup. */
|
|
20
22
|
modelRegistry: {
|
|
21
23
|
find(provider: string, modelId: string): unknown;
|
|
@@ -48,6 +50,7 @@ export function buildParentSnapshot(
|
|
|
48
50
|
cwd: ctx.cwd,
|
|
49
51
|
systemPrompt: ctx.getSystemPrompt(),
|
|
50
52
|
model: ctx.model,
|
|
53
|
+
thinkingLevel: ctx.thinkingLevel,
|
|
51
54
|
modelRegistry,
|
|
52
55
|
modelRuntime,
|
|
53
56
|
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- || intentional: converts empty string to undefined as well as null/undefined
|
|
@@ -31,7 +31,11 @@ import type { AgentInvocation, CompactionInfo, ParentSessionInfo, SubagentType,
|
|
|
31
31
|
export interface SubagentManagerObserver {
|
|
32
32
|
onSubagentStarted(record: Subagent): void;
|
|
33
33
|
onSubagentCompleted(record: Subagent): void;
|
|
34
|
+
/** Fires when a retained session begins a resumed turn. */
|
|
35
|
+
onSubagentResumedStarted?(record: Subagent): void;
|
|
34
36
|
onSubagentResumed?(record: Subagent): void;
|
|
37
|
+
/** Fires when clearCompleted removes a terminal record from the parent session. */
|
|
38
|
+
onSubagentCleared?(record: Subagent): void;
|
|
35
39
|
onSubagentCompacted(record: Subagent, info: CompactionInfo): void;
|
|
36
40
|
/** Fires synchronously after a background agent record is created (before run). */
|
|
37
41
|
onSubagentCreated(record: Subagent): void;
|
|
@@ -240,7 +244,13 @@ export class SubagentManager {
|
|
|
240
244
|
): Promise<Subagent | undefined> {
|
|
241
245
|
const agent = this.agents.get(id);
|
|
242
246
|
if (!agent?.isSessionReady()) return undefined;
|
|
243
|
-
|
|
247
|
+
const resumed = agent.resume(prompt, signal);
|
|
248
|
+
try {
|
|
249
|
+
this.observer?.onSubagentResumedStarted?.(agent);
|
|
250
|
+
} catch (err) {
|
|
251
|
+
debugLog("onSubagentResumedStarted observer", err);
|
|
252
|
+
}
|
|
253
|
+
await resumed;
|
|
244
254
|
return agent;
|
|
245
255
|
}
|
|
246
256
|
|
|
@@ -301,6 +311,11 @@ export class SubagentManager {
|
|
|
301
311
|
clearCompleted(): void {
|
|
302
312
|
for (const [id, record] of this.agents) {
|
|
303
313
|
if (record.status === "running" || record.status === "queued") continue;
|
|
314
|
+
try {
|
|
315
|
+
this.observer?.onSubagentCleared?.(record);
|
|
316
|
+
} catch (err) {
|
|
317
|
+
debugLog("onSubagentCleared observer", err);
|
|
318
|
+
}
|
|
304
319
|
this.removeRecord(id, record);
|
|
305
320
|
}
|
|
306
321
|
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
type AgentSessionEvent,
|
|
16
16
|
type ToolDefinition,
|
|
17
17
|
} from "@earendil-works/pi-coding-agent";
|
|
18
|
+
import type { Model } from "@earendil-works/pi-ai";
|
|
18
19
|
import type { ChildLifecyclePublisher } from "#src/lifecycle/child-lifecycle";
|
|
19
20
|
import {
|
|
20
21
|
type SubagentLifecycleOutcome,
|
|
@@ -24,7 +25,7 @@ import { normalizeMaxTurns } from "#src/lifecycle/turn-limits";
|
|
|
24
25
|
import { getSessionContextPercent, type SessionStatsLike } from "#src/lifecycle/usage";
|
|
25
26
|
import { extractText } from "#src/session/context";
|
|
26
27
|
import { getAgentConversation } from "#src/session/conversation";
|
|
27
|
-
import type { SessionMessage } from "#src/types";
|
|
28
|
+
import type { SessionMessage, ThinkingLevel } from "#src/types";
|
|
28
29
|
|
|
29
30
|
/** Outcome of one turn loop. */
|
|
30
31
|
export interface TurnLoopResult {
|
|
@@ -94,6 +95,16 @@ export class SubagentSession {
|
|
|
94
95
|
return this.meta.sessionId;
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
/** Current model selected by the child session. */
|
|
99
|
+
get model(): Model<any> | undefined {
|
|
100
|
+
return this._session.model;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Effective level after the child session applied defaults and clamping. */
|
|
104
|
+
get thinkingLevel(): ThinkingLevel {
|
|
105
|
+
return this._session.thinkingLevel;
|
|
106
|
+
}
|
|
107
|
+
|
|
97
108
|
/** Drive the initial run's turn loop; emits `completed` on accepted success. */
|
|
98
109
|
async runTurnLoop(prompt: string, opts: TurnLoopOptions): Promise<TurnLoopResult> {
|
|
99
110
|
const session = this._session;
|
|
@@ -26,6 +26,7 @@ import { WorkspaceBracket } from "#src/lifecycle/workspace-bracket";
|
|
|
26
26
|
import { subscribeSubagentObserver } from "#src/observation/record-observer";
|
|
27
27
|
import type { RunConfig } from "#src/runtime";
|
|
28
28
|
import { formatModelLabel } from "#src/session/model-label";
|
|
29
|
+
import { resolveEffectiveThinkingLevel } from "#src/session/thinking-level";
|
|
29
30
|
import type { AgentInvocation, CompactionInfo, ParentSessionInfo, SessionMessage, SubagentType, ThinkingLevel } from "#src/types";
|
|
30
31
|
|
|
31
32
|
/** Per-subagent lifecycle observer — created by SubagentManager for each spawn. */
|
|
@@ -126,13 +127,17 @@ export class Subagent {
|
|
|
126
127
|
get responseText(): string { return this.state.responseText; }
|
|
127
128
|
get maxTurns(): number | undefined { return this.execution.maxTurns; }
|
|
128
129
|
/** Exact effective model label used by every operator-facing status surface. */
|
|
129
|
-
get modelLabel(): string { return
|
|
130
|
+
get modelLabel(): string { return this._modelLabel; }
|
|
131
|
+
/** Exact effective thinking level used by every operator-facing status surface. */
|
|
132
|
+
get effectiveThinkingLevel(): ThinkingLevel { return this._effectiveThinkingLevel; }
|
|
130
133
|
|
|
131
134
|
abortController: AbortController;
|
|
132
135
|
private _promise?: Promise<void>;
|
|
133
136
|
get promise(): Promise<void> | undefined { return this._promise; }
|
|
134
137
|
|
|
135
138
|
private readonly execution: SubagentExecution;
|
|
139
|
+
private _modelLabel: string;
|
|
140
|
+
private _effectiveThinkingLevel: ThinkingLevel;
|
|
136
141
|
private readonly listeners = new RunListeners();
|
|
137
142
|
private readonly workspaceBracket: WorkspaceBracket;
|
|
138
143
|
|
|
@@ -235,6 +240,12 @@ export class Subagent {
|
|
|
235
240
|
|
|
236
241
|
// Execution machinery — a single mandatory collaborator
|
|
237
242
|
this.execution = init.execution;
|
|
243
|
+
this._modelLabel = formatModelLabel(this.execution.model ?? this.execution.snapshot.model);
|
|
244
|
+
this._effectiveThinkingLevel = resolveEffectiveThinkingLevel(
|
|
245
|
+
this.execution.model ?? this.execution.snapshot.model,
|
|
246
|
+
this.execution.thinkingLevel,
|
|
247
|
+
this.execution.snapshot.thinkingLevel,
|
|
248
|
+
);
|
|
238
249
|
|
|
239
250
|
// Per-run lifecycle collaborators
|
|
240
251
|
this.workspaceBracket = new WorkspaceBracket(
|
|
@@ -291,6 +302,12 @@ export class Subagent {
|
|
|
291
302
|
return;
|
|
292
303
|
}
|
|
293
304
|
|
|
305
|
+
// The SDK session is authoritative after creation: it has applied its
|
|
306
|
+
// defaults and model-capability clamp. Keep the record as the one source
|
|
307
|
+
// consumed by every operator-facing status surface.
|
|
308
|
+
this._modelLabel = formatModelLabel(this.subagentSession.model ?? this.execution.model ?? this.execution.snapshot.model);
|
|
309
|
+
this._effectiveThinkingLevel = this.subagentSession.thinkingLevel ?? this._effectiveThinkingLevel;
|
|
310
|
+
|
|
294
311
|
this.flushPendingSteers();
|
|
295
312
|
this.listeners.attachObserver(subscribeSubagentObserver(this.subagentSession, this.state, {
|
|
296
313
|
onCompact: (info) => this.execution.observer?.onCompacted?.(this, info),
|
|
@@ -33,10 +33,18 @@ export class CompositeSubagentObserver implements SubagentManagerObserver {
|
|
|
33
33
|
this.dispatch((o) => o.onSubagentCompleted(record), "onSubagentCompleted");
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
onSubagentResumedStarted(record: Subagent): void {
|
|
37
|
+
this.dispatch((o) => o.onSubagentResumedStarted?.(record), "onSubagentResumedStarted");
|
|
38
|
+
}
|
|
39
|
+
|
|
36
40
|
onSubagentResumed(record: Subagent): void {
|
|
37
41
|
this.dispatch((o) => o.onSubagentResumed?.(record), "onSubagentResumed");
|
|
38
42
|
}
|
|
39
43
|
|
|
44
|
+
onSubagentCleared(record: Subagent): void {
|
|
45
|
+
this.dispatch((o) => o.onSubagentCleared?.(record), "onSubagentCleared");
|
|
46
|
+
}
|
|
47
|
+
|
|
40
48
|
onSubagentCompacted(record: Subagent, info: CompactionInfo): void {
|
|
41
49
|
this.dispatch((o) => o.onSubagentCompacted(record, info), "onSubagentCompacted");
|
|
42
50
|
}
|
|
@@ -7,6 +7,7 @@ export interface NotificationDetails {
|
|
|
7
7
|
id: string;
|
|
8
8
|
description: string;
|
|
9
9
|
modelLabel: string;
|
|
10
|
+
thinkingLevel: Subagent["effectiveThinkingLevel"];
|
|
10
11
|
status: string;
|
|
11
12
|
toolUses: number;
|
|
12
13
|
turnCount: number;
|
|
@@ -72,6 +73,7 @@ export function formatTaskNotification(record: Subagent, resultMaxLen: number):
|
|
|
72
73
|
outputFile ? `<output-file>${escapeXml(outputFile)}</output-file>` : null,
|
|
73
74
|
`<status>${escapeXml(status)}</status>`,
|
|
74
75
|
`<model>${escapeXml(record.modelLabel)}</model>`,
|
|
76
|
+
`<thinking_level>${escapeXml(record.effectiveThinkingLevel)}</thinking_level>`,
|
|
75
77
|
`<summary>Subagent "${escapeXml(record.description)}" ${record.status}</summary>`,
|
|
76
78
|
`<result>${escapeXml(resultPreview)}</result>`,
|
|
77
79
|
`<usage><total_tokens>${totalTokens}</total_tokens><tool_uses>${record.toolUses}</tool_uses>${ctxXml}${compactXml}<duration_ms>${durationMs}</duration_ms></usage>`,
|
|
@@ -92,6 +94,7 @@ export function buildNotificationDetails(
|
|
|
92
94
|
id: record.id,
|
|
93
95
|
description: record.description,
|
|
94
96
|
modelLabel: record.modelLabel,
|
|
97
|
+
thinkingLevel: record.effectiveThinkingLevel,
|
|
95
98
|
status: record.status,
|
|
96
99
|
toolUses: record.toolUses,
|
|
97
100
|
turnCount: record.turnCount,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Text } from "@earendil-works/pi-tui";
|
|
2
2
|
import type { NotificationDetails } from "#src/observation/notification";
|
|
3
|
-
import { formatMs, formatTokens, formatTurns } from "#src/ui/display";
|
|
3
|
+
import { formatModelThinking, formatMs, formatTokens, formatTurns } from "#src/ui/display";
|
|
4
4
|
|
|
5
5
|
/** Narrow theme interface — only the methods the renderer actually calls. */
|
|
6
6
|
interface RendererTheme {
|
|
@@ -39,7 +39,7 @@ export function createNotificationRenderer() {
|
|
|
39
39
|
let line = `${icon} ${theme.bold(d.description)} ${theme.fg("dim", statusText)}`;
|
|
40
40
|
|
|
41
41
|
// Line 2: stats
|
|
42
|
-
const parts: string[] = [d.modelLabel];
|
|
42
|
+
const parts: string[] = [formatModelThinking(d.modelLabel, d.thinkingLevel)];
|
|
43
43
|
if (d.turnCount > 0) parts.push(formatTurns(d.turnCount, d.maxTurns));
|
|
44
44
|
if (d.toolUses > 0) parts.push(`${d.toolUses} tool use${d.toolUses === 1 ? "" : "s"}`);
|
|
45
45
|
if (d.totalTokens > 0) parts.push(formatTokens(d.totalTokens));
|
|
@@ -16,9 +16,10 @@ import type { ParentSnapshot } from "#src/lifecycle/parent-snapshot";
|
|
|
16
16
|
import type { WorkspaceProvider } from "#src/lifecycle/workspace";
|
|
17
17
|
import type { SpawnOptions, SubagentRecord, SubagentsService } from "#src/service/service";
|
|
18
18
|
import { formatModelLabel } from "#src/session/model-label";
|
|
19
|
+
import { resolveEffectiveThinkingLevel } from "#src/session/thinking-level";
|
|
19
20
|
import type { ModelRegistry } from "#src/session/model-resolver";
|
|
20
21
|
import { resolveDefaultModel } from "#src/session/session-config";
|
|
21
|
-
import type { AgentInvocation, SessionContext, Subagent } from "#src/types";
|
|
22
|
+
import type { AgentInvocation, SessionContext, Subagent, ThinkingLevel } from "#src/types";
|
|
22
23
|
|
|
23
24
|
/** Narrow interface for the SubagentManager — avoids coupling to the concrete class. */
|
|
24
25
|
export interface SubagentManagerLike {
|
|
@@ -71,6 +72,14 @@ export class SubagentsServiceAdapter implements SubagentsService {
|
|
|
71
72
|
: this.resolveTypeDefaultModel(resolvedType);
|
|
72
73
|
const description = options?.description ?? prompt.slice(0, 80);
|
|
73
74
|
const isBackground = !(options?.foreground ?? false);
|
|
75
|
+
const snapshot = this.runtime.buildSnapshot(options?.inheritContext ?? false);
|
|
76
|
+
const requestedThinking = (options?.thinkingLevel
|
|
77
|
+
?? this.agentRegistry?.resolveAgentConfig(resolvedType).thinking) as ThinkingLevel | undefined;
|
|
78
|
+
const effectiveThinkingLevel = resolveEffectiveThinkingLevel(
|
|
79
|
+
model,
|
|
80
|
+
requestedThinking,
|
|
81
|
+
snapshot.thinkingLevel,
|
|
82
|
+
);
|
|
74
83
|
const invocation: AgentInvocation = {
|
|
75
84
|
modelName: formatModelLabel(model),
|
|
76
85
|
maxTurns: options?.maxTurns,
|
|
@@ -78,13 +87,12 @@ export class SubagentsServiceAdapter implements SubagentsService {
|
|
|
78
87
|
runInBackground: isBackground,
|
|
79
88
|
};
|
|
80
89
|
|
|
81
|
-
const snapshot = this.runtime.buildSnapshot(options?.inheritContext ?? false);
|
|
82
90
|
const parent = this.runtime.getSessionInfo();
|
|
83
91
|
return this.manager.spawn(snapshot, resolvedType, prompt, {
|
|
84
92
|
description,
|
|
85
93
|
model,
|
|
86
94
|
maxTurns: options?.maxTurns,
|
|
87
|
-
thinkingLevel:
|
|
95
|
+
thinkingLevel: effectiveThinkingLevel,
|
|
88
96
|
inheritContext: options?.inheritContext,
|
|
89
97
|
bypassQueue: options?.bypassQueue,
|
|
90
98
|
isBackground,
|
|
@@ -50,6 +50,8 @@ export interface AssemblerContext {
|
|
|
50
50
|
parentSystemPrompt: string;
|
|
51
51
|
/** Parent's current model instance (fallback when agent config has no model). */
|
|
52
52
|
parentModel?: unknown;
|
|
53
|
+
/** Parent's current effective level for inherited child thinking. */
|
|
54
|
+
parentThinkingLevel?: ThinkingLevel;
|
|
53
55
|
/** Model registry for resolving config.model strings. */
|
|
54
56
|
modelRegistry: {
|
|
55
57
|
find(provider: string, modelId: string): unknown;
|
|
@@ -88,7 +90,7 @@ export interface SessionConfig {
|
|
|
88
90
|
* Caller casts to the SDK’s Model<any> at the session-creation boundary.
|
|
89
91
|
*/
|
|
90
92
|
model: unknown;
|
|
91
|
-
/** Resolved thinking level
|
|
93
|
+
/** Resolved thinking level; undefined only when no parent level is available. */
|
|
92
94
|
thinkingLevel: ThinkingLevel | undefined;
|
|
93
95
|
/** Per-agent configured max turns (from agentConfig.maxTurns). */
|
|
94
96
|
agentMaxTurns: number | undefined;
|
|
@@ -175,8 +177,10 @@ export function assembleSessionConfig(
|
|
|
175
177
|
options.model ??
|
|
176
178
|
resolveDefaultModel(ctx.parentModel, ctx.modelRegistry, agentConfig.model);
|
|
177
179
|
|
|
178
|
-
// Thinking level: explicit option > agent config >
|
|
179
|
-
|
|
180
|
+
// Thinking level: explicit option > agent config > parent level (inherit).
|
|
181
|
+
// Passing the inherited value explicitly lets queued status use the same
|
|
182
|
+
// value that the child session will receive once it is admitted.
|
|
183
|
+
const thinkingLevel = options.thinkingLevel ?? agentConfig.thinking ?? ctx.parentThinkingLevel;
|
|
180
184
|
|
|
181
185
|
// Per-agent max turns (combined with per-call maxTurns and defaultMaxTurns by SubagentSession.runTurnLoop)
|
|
182
186
|
const agentMaxTurns = agentConfig.maxTurns;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { clampThinkingLevel, getSupportedThinkingLevels } from "@earendil-works/pi-ai/compat";
|
|
2
|
+
import type { Model } from "@earendil-works/pi-ai";
|
|
3
|
+
import type { ThinkingLevel } from "#src/types";
|
|
4
|
+
|
|
5
|
+
/** Pi's default when no session setting is available. */
|
|
6
|
+
export const DEFAULT_THINKING_LEVEL: ThinkingLevel = "medium";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Resolve the level Pi will use for a model before its AgentSession exists.
|
|
10
|
+
*
|
|
11
|
+
* The session factory passes this resolved value to Pi, while the Subagent
|
|
12
|
+
* record replaces it with AgentSession.thinkingLevel after construction. That
|
|
13
|
+
* two-step flow keeps queued/launch status useful without allowing a local
|
|
14
|
+
* approximation to outrank the SDK's authoritative value.
|
|
15
|
+
*/
|
|
16
|
+
export function resolveEffectiveThinkingLevel(
|
|
17
|
+
model: unknown,
|
|
18
|
+
requested?: ThinkingLevel,
|
|
19
|
+
inherited?: ThinkingLevel,
|
|
20
|
+
): ThinkingLevel {
|
|
21
|
+
const level = requested ?? inherited ?? DEFAULT_THINKING_LEVEL;
|
|
22
|
+
// With no model yet, preserve the level Pi would receive rather than forcing
|
|
23
|
+
// `off`: createAgentSession may still resolve a configured default model.
|
|
24
|
+
// The created child session remains authoritative and supplies the clamp.
|
|
25
|
+
if (model == null) return level;
|
|
26
|
+
if (!isThinkingModel(model)) return "off";
|
|
27
|
+
|
|
28
|
+
const supported = getSupportedThinkingLevels(model);
|
|
29
|
+
return supported.includes(level)
|
|
30
|
+
? level
|
|
31
|
+
: clampThinkingLevel(model, level);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isThinkingModel(value: unknown): value is Model<any> {
|
|
35
|
+
if (typeof value !== "object" || value === null) return false;
|
|
36
|
+
const candidate = value as { provider?: unknown; id?: unknown; reasoning?: unknown };
|
|
37
|
+
return typeof candidate.provider === "string"
|
|
38
|
+
&& candidate.provider.length > 0
|
|
39
|
+
&& typeof candidate.id === "string"
|
|
40
|
+
&& candidate.id.length > 0
|
|
41
|
+
&& typeof candidate.reasoning === "boolean";
|
|
42
|
+
}
|
package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/agent-tool.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { buildAgentGuidelines, buildDetails, buildTypeListText, textResult } fro
|
|
|
13
13
|
import { renderAgentResult } from "#src/tools/result-renderer";
|
|
14
14
|
import { type ModelInfo, resolveSpawnConfig } from "#src/tools/spawn-config";
|
|
15
15
|
import type { ParentSessionInfo, Subagent } from "#src/types";
|
|
16
|
-
import { type AgentDetails, formatDuration, getDisplayName } from "#src/ui/display";
|
|
16
|
+
import { type AgentDetails, formatDuration, formatModelThinking, getDisplayName } from "#src/ui/display";
|
|
17
17
|
|
|
18
18
|
// ---- Deps interfaces ----
|
|
19
19
|
|
|
@@ -113,9 +113,9 @@ export class AgentTool {
|
|
|
113
113
|
: `Error: ${record.error}`
|
|
114
114
|
: partial ?? "No output.";
|
|
115
115
|
return textResult(
|
|
116
|
-
`Model: ${record.modelLabel}\nRuntime: ${formatDuration(record.startedAt, record.completedAt)}\n\n${resultText}`,
|
|
116
|
+
`Model: ${formatModelThinking(record.modelLabel, record.effectiveThinkingLevel)}\nRuntime: ${formatDuration(record.startedAt, record.completedAt)}\n\n${resultText}`,
|
|
117
117
|
buildDetails(
|
|
118
|
-
{ ...config.presentation.detailBase, modelName: record.modelLabel },
|
|
118
|
+
{ ...config.presentation.detailBase, modelName: record.modelLabel, thinkingLevel: record.effectiveThinkingLevel },
|
|
119
119
|
record,
|
|
120
120
|
),
|
|
121
121
|
);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ParentSnapshot } from "#src/lifecycle/parent-snapshot";
|
|
2
2
|
import type { AgentSpawnConfig } from "#src/lifecycle/subagent-manager";
|
|
3
3
|
import { textResult } from "#src/tools/helpers";
|
|
4
|
+
import { formatModelThinking } from "#src/ui/display";
|
|
4
5
|
import type { ResolvedSpawnConfig } from "#src/tools/spawn-config";
|
|
5
6
|
import type { ParentSessionInfo, Subagent } from "#src/types";
|
|
6
7
|
|
|
@@ -36,7 +37,7 @@ export function spawnBackground(
|
|
|
36
37
|
model: execution.model,
|
|
37
38
|
maxTurns: execution.effectiveMaxTurns,
|
|
38
39
|
inheritContext: execution.inheritContext,
|
|
39
|
-
thinkingLevel: execution.
|
|
40
|
+
thinkingLevel: execution.effectiveThinkingLevel,
|
|
40
41
|
isBackground: true,
|
|
41
42
|
origin: "tool",
|
|
42
43
|
invocation: execution.agentInvocation,
|
|
@@ -52,7 +53,10 @@ export function spawnBackground(
|
|
|
52
53
|
`Agent ${isQueued ? "queued" : "started"} in background.\n` +
|
|
53
54
|
`Agent ID: ${id}\n` +
|
|
54
55
|
`Type: ${identity.displayName}\n` +
|
|
55
|
-
`Model: ${
|
|
56
|
+
`Model: ${formatModelThinking(
|
|
57
|
+
record?.modelLabel ?? presentation.modelName,
|
|
58
|
+
record?.effectiveThinkingLevel ?? execution.effectiveThinkingLevel,
|
|
59
|
+
)}\n` +
|
|
56
60
|
`Runtime: 0.0s\n` +
|
|
57
61
|
`Description: ${execution.description}\n` +
|
|
58
62
|
(identity.fellBack ? `Fallback: requested ${identity.rawType}; using ${identity.subagentType}\n` : "") +
|
|
@@ -66,6 +70,7 @@ export function spawnBackground(
|
|
|
66
70
|
`Do not duplicate this agent's work.`,
|
|
67
71
|
{
|
|
68
72
|
...presentation.detailBase,
|
|
73
|
+
thinkingLevel: record?.effectiveThinkingLevel ?? execution.effectiveThinkingLevel,
|
|
69
74
|
toolUses: 0,
|
|
70
75
|
tokens: "",
|
|
71
76
|
durationMs: 0,
|
|
@@ -12,6 +12,7 @@ import type { ParentSessionInfo, Subagent } from "#src/types";
|
|
|
12
12
|
import {
|
|
13
13
|
type AgentDetails,
|
|
14
14
|
describeActivity,
|
|
15
|
+
formatModelThinking,
|
|
15
16
|
formatMs,
|
|
16
17
|
SPINNER,
|
|
17
18
|
} from "#src/ui/display";
|
|
@@ -73,11 +74,12 @@ export async function runForeground(
|
|
|
73
74
|
});
|
|
74
75
|
};
|
|
75
76
|
|
|
76
|
-
//
|
|
77
|
+
// Each update traverses the parent TUI tree; avoid doing that at animation
|
|
78
|
+
// speed while the foreground tool already owns the progress surface.
|
|
77
79
|
const spinnerInterval = setInterval(() => {
|
|
78
80
|
spinnerFrame++;
|
|
79
81
|
streamUpdate();
|
|
80
|
-
},
|
|
82
|
+
}, 500);
|
|
81
83
|
|
|
82
84
|
streamUpdate();
|
|
83
85
|
|
|
@@ -92,7 +94,7 @@ export async function runForeground(
|
|
|
92
94
|
model: execution.model,
|
|
93
95
|
maxTurns: execution.effectiveMaxTurns,
|
|
94
96
|
inheritContext: execution.inheritContext,
|
|
95
|
-
thinkingLevel: execution.
|
|
97
|
+
thinkingLevel: execution.effectiveThinkingLevel,
|
|
96
98
|
origin: "tool",
|
|
97
99
|
invocation: execution.agentInvocation,
|
|
98
100
|
signal,
|
|
@@ -125,7 +127,7 @@ export async function runForeground(
|
|
|
125
127
|
if (record.status === "error") {
|
|
126
128
|
const partial = record.result?.trim();
|
|
127
129
|
return textResult(
|
|
128
|
-
`${fallbackNote}Model: ${record.modelLabel}\nRuntime: ${formatMs(details.durationMs)}\nAgent failed: ${record.error}` +
|
|
130
|
+
`${fallbackNote}Model: ${formatModelThinking(record.modelLabel, record.effectiveThinkingLevel)}\nRuntime: ${formatMs(details.durationMs)}\nAgent failed: ${record.error}` +
|
|
129
131
|
(partial ? `\n\nPartial output before the failure:\n${partial}` : ""),
|
|
130
132
|
details,
|
|
131
133
|
);
|
|
@@ -135,7 +137,7 @@ export async function runForeground(
|
|
|
135
137
|
const statsParts = [`${record.toolUses} tool uses`];
|
|
136
138
|
if (tokenText) statsParts.push(tokenText);
|
|
137
139
|
return textResult(
|
|
138
|
-
`${fallbackNote}Model: ${record.modelLabel}\nRuntime: ${formatMs(durationMs)}\nAgent completed (${statsParts.join(", ")})${getStatusNote(record.status)}.\n\n` +
|
|
140
|
+
`${fallbackNote}Model: ${formatModelThinking(record.modelLabel, record.effectiveThinkingLevel)}\nRuntime: ${formatMs(durationMs)}\nAgent completed (${statsParts.join(", ")})${getStatusNote(record.status)}.\n\n` +
|
|
139
141
|
(record.result?.trim() ?? "No output."),
|
|
140
142
|
details,
|
|
141
143
|
);
|
|
@@ -8,12 +8,15 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { SubagentStatus } from "#src/lifecycle/subagent";
|
|
11
|
+
import type { ThinkingLevel } from "#src/types";
|
|
12
|
+
import { formatModelThinking } from "#src/ui/display";
|
|
11
13
|
|
|
12
14
|
/** The data a get_subagent_result report renders from — only what the formatter reads. */
|
|
13
15
|
export interface AgentReport {
|
|
14
16
|
id: string;
|
|
15
17
|
displayName: string;
|
|
16
18
|
modelLabel: string;
|
|
19
|
+
thinkingLevel: ThinkingLevel;
|
|
17
20
|
status: SubagentStatus;
|
|
18
21
|
toolUses: number;
|
|
19
22
|
/** Pre-formatted lifetime token total; "" when zero. */
|
|
@@ -63,7 +66,7 @@ export function renderReportBody(report: AgentReport): string {
|
|
|
63
66
|
export function formatAgentReport(report: AgentReport): string {
|
|
64
67
|
let output =
|
|
65
68
|
`Agent: ${report.id}\n` +
|
|
66
|
-
`Type: ${report.displayName} | Model: ${report.modelLabel} | Status: ${report.status} | ${renderStatsParts(report).join(" | ")}\n` +
|
|
69
|
+
`Type: ${report.displayName} | Model: ${formatModelThinking(report.modelLabel, report.thinkingLevel)} | Status: ${report.status} | ${renderStatsParts(report).join(" | ")}\n` +
|
|
67
70
|
`Description: ${report.description}\n\n`;
|
|
68
71
|
output += renderReportBody(report);
|
|
69
72
|
if (report.conversation) {
|
|
@@ -47,6 +47,7 @@ export class GetResultTool {
|
|
|
47
47
|
id: record.id,
|
|
48
48
|
displayName: getDisplayName(record.type, this.registry),
|
|
49
49
|
modelLabel: record.modelLabel,
|
|
50
|
+
thinkingLevel: record.effectiveThinkingLevel,
|
|
50
51
|
status: record.status,
|
|
51
52
|
toolUses: record.toolUses,
|
|
52
53
|
tokens: formatLifetimeTokens(record),
|
package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/helpers.ts
CHANGED
|
@@ -18,7 +18,7 @@ export function getStatusNote(status: string): string {
|
|
|
18
18
|
|
|
19
19
|
/** Build AgentDetails from a base + record-specific fields. */
|
|
20
20
|
export function buildDetails(
|
|
21
|
-
base: Pick<AgentDetails, "displayName" | "description" | "subagentType" | "modelName" | "tags">,
|
|
21
|
+
base: Pick<AgentDetails, "displayName" | "description" | "subagentType" | "modelName" | "thinkingLevel" | "tags">,
|
|
22
22
|
record: {
|
|
23
23
|
toolUses: number;
|
|
24
24
|
startedAt: number;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { AgentDetails, Theme } from "#src/ui/display";
|
|
10
|
-
import { formatMs, formatTurns, SPINNER } from "#src/ui/display";
|
|
10
|
+
import { formatModelThinking, formatMs, formatTurns, SPINNER } from "#src/ui/display";
|
|
11
11
|
|
|
12
12
|
// ---- Dispatcher ----
|
|
13
13
|
|
|
@@ -42,7 +42,10 @@ export function renderRunning(details: AgentDetails, theme: Theme): string {
|
|
|
42
42
|
|
|
43
43
|
/** Render background launch status. */
|
|
44
44
|
export function renderBackground(details: AgentDetails, theme: Theme): string {
|
|
45
|
-
const identity = [
|
|
45
|
+
const identity = [
|
|
46
|
+
formatModelThinking(details.modelName ?? "unknown model", details.thinkingLevel),
|
|
47
|
+
formatMs(details.durationMs),
|
|
48
|
+
].join(" · ");
|
|
46
49
|
return theme.fg("dim", ` \u23BF Running in background (ID: ${details.agentId}) · ${identity}`);
|
|
47
50
|
}
|
|
48
51
|
|
|
@@ -113,8 +116,8 @@ export function renderFailed(details: AgentDetails, theme: Theme): string {
|
|
|
113
116
|
*/
|
|
114
117
|
export function renderStats(details: AgentDetails, theme: Theme): string {
|
|
115
118
|
const parts: string[] = [];
|
|
116
|
-
|
|
117
|
-
if (details.tags) parts.push(...details.tags);
|
|
119
|
+
parts.push(formatModelThinking(details.modelName ?? "unknown model", details.thinkingLevel));
|
|
120
|
+
if (details.tags) parts.push(...details.tags.filter((tag) => !tag.startsWith("thinking: ")));
|
|
118
121
|
if (details.turnCount != null && details.turnCount > 0) {
|
|
119
122
|
parts.push(formatTurns(details.turnCount, details.maxTurns));
|
|
120
123
|
}
|