@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
|
@@ -14,6 +14,7 @@ import { normalizeMaxTurns } from "#src/lifecycle/turn-limits";
|
|
|
14
14
|
import type { ModelRegistry } from "#src/session/model-resolver";
|
|
15
15
|
import { formatModelLabel } from "#src/session/model-label";
|
|
16
16
|
import { resolveInvocationModel } from "#src/session/model-resolver";
|
|
17
|
+
import { resolveEffectiveThinkingLevel } from "#src/session/thinking-level";
|
|
17
18
|
import type { AgentInvocation, SubagentType, ThinkingLevel } from "#src/types";
|
|
18
19
|
import {
|
|
19
20
|
type AgentDetails,
|
|
@@ -25,6 +26,8 @@ import {
|
|
|
25
26
|
/** Model info extracted from the parent session context. */
|
|
26
27
|
export interface ModelInfo {
|
|
27
28
|
parentModel: Model<any> | undefined;
|
|
29
|
+
/** Parent's current level for children that inherit thinking settings. */
|
|
30
|
+
parentThinkingLevel?: ThinkingLevel;
|
|
28
31
|
modelRegistry: ModelRegistry | undefined;
|
|
29
32
|
}
|
|
30
33
|
|
|
@@ -42,7 +45,10 @@ export interface SpawnExecution {
|
|
|
42
45
|
description: string;
|
|
43
46
|
model: Model<any> | undefined;
|
|
44
47
|
effectiveMaxTurns: number | undefined;
|
|
48
|
+
/** Requested/configured level before model-capability clamping. */
|
|
45
49
|
thinking: ThinkingLevel | undefined;
|
|
50
|
+
/** Exact level passed to the child session after model-capability clamping. */
|
|
51
|
+
effectiveThinkingLevel: ThinkingLevel;
|
|
46
52
|
inheritContext: boolean;
|
|
47
53
|
runInBackground: boolean;
|
|
48
54
|
agentInvocation: AgentInvocation;
|
|
@@ -50,9 +56,9 @@ export interface SpawnExecution {
|
|
|
50
56
|
|
|
51
57
|
/** Presentation: display/UI values derived from identity and execution. */
|
|
52
58
|
export interface SpawnPresentation {
|
|
53
|
-
modelName: string
|
|
59
|
+
modelName: string;
|
|
54
60
|
agentTags: string[];
|
|
55
|
-
detailBase: Pick<AgentDetails, "displayName" | "description" | "subagentType" | "modelName" | "tags">;
|
|
61
|
+
detailBase: Pick<AgentDetails, "displayName" | "description" | "subagentType" | "modelName" | "thinkingLevel" | "tags">;
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
/** Fully resolved config for spawning an agent — composed of domain-aligned sub-interfaces. */
|
|
@@ -110,6 +116,11 @@ export function resolveSpawnConfig(
|
|
|
110
116
|
const model = resolution.model;
|
|
111
117
|
|
|
112
118
|
const thinking = resolvedConfig.thinking;
|
|
119
|
+
const effectiveThinkingLevel = resolveEffectiveThinkingLevel(
|
|
120
|
+
model,
|
|
121
|
+
thinking,
|
|
122
|
+
modelInfo.parentThinkingLevel,
|
|
123
|
+
);
|
|
113
124
|
const inheritContext = resolvedConfig.inheritContext;
|
|
114
125
|
const runInBackground = resolvedConfig.runInBackground;
|
|
115
126
|
|
|
@@ -137,6 +148,7 @@ export function resolveSpawnConfig(
|
|
|
137
148
|
description: params.description as string,
|
|
138
149
|
subagentType,
|
|
139
150
|
modelName,
|
|
151
|
+
thinkingLevel: effectiveThinkingLevel,
|
|
140
152
|
tags: agentTags.length > 0 ? agentTags : undefined,
|
|
141
153
|
};
|
|
142
154
|
|
|
@@ -148,6 +160,7 @@ export function resolveSpawnConfig(
|
|
|
148
160
|
model,
|
|
149
161
|
effectiveMaxTurns,
|
|
150
162
|
thinking,
|
|
163
|
+
effectiveThinkingLevel,
|
|
151
164
|
inheritContext,
|
|
152
165
|
runInBackground,
|
|
153
166
|
agentInvocation,
|
package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/steer-tool.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
|
2
2
|
import { Type } from "@sinclair/typebox";
|
|
3
3
|
import { formatLifetimeTokens, textResult } from "#src/tools/helpers";
|
|
4
4
|
import type { SteerOutcome, Subagent } from "#src/types";
|
|
5
|
-
import { formatDuration } from "#src/ui/display";
|
|
5
|
+
import { formatDuration, formatModelThinking } from "#src/ui/display";
|
|
6
6
|
|
|
7
7
|
// ---- Deps interfaces ----
|
|
8
8
|
|
|
@@ -68,7 +68,7 @@ export class SteerTool {
|
|
|
68
68
|
private renderDelivered(record: Subagent) {
|
|
69
69
|
const tokens = formatLifetimeTokens(record);
|
|
70
70
|
const contextPercent = record.getContextPercent();
|
|
71
|
-
const stateParts: string[] = [record.modelLabel, formatDuration(record.startedAt, record.completedAt)];
|
|
71
|
+
const stateParts: string[] = [formatModelThinking(record.modelLabel, record.effectiveThinkingLevel), formatDuration(record.startedAt, record.completedAt)];
|
|
72
72
|
if (tokens) stateParts.push(tokens);
|
|
73
73
|
stateParts.push(`${record.toolUses} tool ${record.toolUses === 1 ? "use" : "uses"}`);
|
|
74
74
|
if (contextPercent !== null)
|
|
@@ -84,7 +84,7 @@ export class SteerTool {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
private renderRunIdentity(record: Subagent): string {
|
|
87
|
-
return `Model: ${record.modelLabel}\nRuntime: ${formatDuration(record.startedAt, record.completedAt)}`;
|
|
87
|
+
return `Model: ${formatModelThinking(record.modelLabel, record.effectiveThinkingLevel)}\nRuntime: ${formatDuration(record.startedAt, record.completedAt)}`;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
toToolDefinition() {
|
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
* types.ts — Type definitions for the subagent system.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type {
|
|
5
|
+
import type { ModelThinkingLevel } from "@earendil-works/pi-ai";
|
|
6
6
|
import type { AgentSessionEvent, SessionContext as SdkSessionContext } from "@earendil-works/pi-coding-agent";
|
|
7
7
|
import type { ModelRegistry } from "#src/session/model-resolver";
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
export type { SteerOutcome } from "#src/lifecycle/subagent";
|
|
11
11
|
export { Subagent } from "#src/lifecycle/subagent";
|
|
12
|
-
export type { AgentSessionEvent
|
|
12
|
+
export type { AgentSessionEvent };
|
|
13
|
+
/** Pi's model-capability thinking levels, including the explicit `off` value. */
|
|
14
|
+
export type ThinkingLevel = ModelThinkingLevel;
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* One message in a child session's history, typed from Pi's `SessionContext`.
|
|
@@ -86,6 +88,8 @@ export interface AgentInvocation {
|
|
|
86
88
|
export interface SessionContext {
|
|
87
89
|
readonly cwd: string;
|
|
88
90
|
readonly model: unknown;
|
|
91
|
+
/** Parent session's current effective thinking level for inherited children. */
|
|
92
|
+
readonly thinkingLevel?: ThinkingLevel;
|
|
89
93
|
readonly modelRegistry: ModelRegistry | undefined;
|
|
90
94
|
getSystemPrompt(): string;
|
|
91
95
|
readonly sessionManager: {
|
package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/agent-widget.ts
CHANGED
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
import { AgentTypeRegistry } from "#src/config/agent-types";
|
|
10
10
|
import type { Subagent } from "#src/lifecycle/subagent";
|
|
11
|
-
import type {
|
|
11
|
+
import type { SubagentManagerObserver } from "#src/lifecycle/subagent-manager";
|
|
12
12
|
import type { CompactionInfo } from "#src/types";
|
|
13
|
-
import { ERROR_STATUSES, formatMs, type Theme } from "#src/ui/display";
|
|
13
|
+
import { ERROR_STATUSES, formatModelThinking, formatMs, type Theme } from "#src/ui/display";
|
|
14
14
|
import { renderWidgetLines, type WidgetAgent } from "#src/ui/widget-renderer";
|
|
15
15
|
|
|
16
16
|
// ---- Types ----
|
|
@@ -54,7 +54,7 @@ export function assembleWidgetState(
|
|
|
54
54
|
/** Render the aggregate status bar without hiding per-agent model/runtime details. */
|
|
55
55
|
export function formatStatusBar(
|
|
56
56
|
state: WidgetState,
|
|
57
|
-
agents: readonly Pick<Subagent, "status" | "modelLabel" | "startedAt">[],
|
|
57
|
+
agents: readonly Pick<Subagent, "status" | "modelLabel" | "effectiveThinkingLevel" | "startedAt">[],
|
|
58
58
|
now = Date.now(),
|
|
59
59
|
): string | undefined {
|
|
60
60
|
if (!state.hasActive) return undefined;
|
|
@@ -65,8 +65,8 @@ export function formatStatusBar(
|
|
|
65
65
|
const activeDetails = agents
|
|
66
66
|
.filter((agent) => agent.status === "running" || agent.status === "queued")
|
|
67
67
|
.map((agent) => agent.status === "running"
|
|
68
|
-
? `${agent.modelLabel} ${formatMs(now - agent.startedAt)}`
|
|
69
|
-
: `${agent.modelLabel} queued`);
|
|
68
|
+
? `${formatModelThinking(agent.modelLabel, agent.effectiveThinkingLevel)} ${formatMs(now - agent.startedAt)}`
|
|
69
|
+
: `${formatModelThinking(agent.modelLabel, agent.effectiveThinkingLevel)} queued`);
|
|
70
70
|
return `${statusParts.join(", ")} agent${total === 1 ? "" : "s"} · ${activeDetails.join(", ")}`;
|
|
71
71
|
}
|
|
72
72
|
|
|
@@ -85,10 +85,18 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
85
85
|
private uiCtx: UICtx | undefined;
|
|
86
86
|
private widgetFrame = 0;
|
|
87
87
|
private widgetInterval: ReturnType<typeof setInterval> | undefined;
|
|
88
|
-
/**
|
|
88
|
+
/**
|
|
89
|
+
* Bounded reactive read model: lifecycle callbacks add active background
|
|
90
|
+
* records and the small terminal linger set; render ticks never ask the
|
|
91
|
+
* manager to clone or sort its retained history.
|
|
92
|
+
*/
|
|
93
|
+
private readonly backgroundAgents = new Map<string, Subagent>();
|
|
94
|
+
/** Tracks how many turns each finished agent has survived. */
|
|
89
95
|
private finishedTurnAge = new Map<string, number>();
|
|
90
96
|
/** How many extra turns errors/aborted agents linger (completed agents clear after 1 turn). */
|
|
91
97
|
private static readonly ERROR_LINGER_TURNS = 2;
|
|
98
|
+
/** Pi renders the whole component tree for each requestRender call. */
|
|
99
|
+
private static readonly STATUS_REFRESH_INTERVAL_MS = 500;
|
|
92
100
|
|
|
93
101
|
/** Whether the widget callback is currently registered with the TUI. */
|
|
94
102
|
private widgetRegistered = false;
|
|
@@ -97,14 +105,12 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
97
105
|
/** Last status bar text, used to avoid redundant setStatus calls. */
|
|
98
106
|
private lastStatusText: string | undefined;
|
|
99
107
|
|
|
100
|
-
constructor(
|
|
101
|
-
private manager: SubagentManager,
|
|
102
|
-
private registry: AgentTypeRegistry,
|
|
103
|
-
) {}
|
|
108
|
+
constructor(private registry: AgentTypeRegistry) {}
|
|
104
109
|
|
|
105
110
|
/** Set the UI context (grabbed from first tool execution). */
|
|
106
111
|
setUICtx(ctx: UICtx) {
|
|
107
|
-
|
|
112
|
+
const contextChanged = ctx !== this.uiCtx;
|
|
113
|
+
if (contextChanged) {
|
|
108
114
|
// UICtx changed — the widget registered on the old context is gone.
|
|
109
115
|
// Force re-registration on next update().
|
|
110
116
|
this.uiCtx = ctx;
|
|
@@ -112,6 +118,10 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
112
118
|
this.tui = undefined;
|
|
113
119
|
this.lastStatusText = undefined;
|
|
114
120
|
}
|
|
121
|
+
// Lifecycle events can arrive before the first tool gives us a UI context.
|
|
122
|
+
// Refresh when that context becomes available, but let update() decide
|
|
123
|
+
// whether any active record actually needs an animation timer.
|
|
124
|
+
if (contextChanged && this.uiCtx && this.backgroundAgents.size > 0) this.update();
|
|
115
125
|
}
|
|
116
126
|
|
|
117
127
|
/**
|
|
@@ -119,62 +129,118 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
119
129
|
* Ages finished agents and clears those that have lingered long enough.
|
|
120
130
|
*/
|
|
121
131
|
onTurnStart() {
|
|
122
|
-
// Age
|
|
132
|
+
// Age terminal records and remove them before the next render. Completed
|
|
133
|
+
// records linger for one turn; errors and aborted records linger for two.
|
|
123
134
|
for (const [id, age] of this.finishedTurnAge) {
|
|
124
|
-
this.
|
|
135
|
+
const record = this.backgroundAgents.get(id);
|
|
136
|
+
const nextAge = age + 1;
|
|
137
|
+
if (!record || nextAge >= this.maxFinishedAge(record.status)) {
|
|
138
|
+
this.finishedTurnAge.delete(id);
|
|
139
|
+
this.backgroundAgents.delete(id);
|
|
140
|
+
} else {
|
|
141
|
+
this.finishedTurnAge.set(id, nextAge);
|
|
142
|
+
}
|
|
125
143
|
}
|
|
126
|
-
// Trigger a widget refresh (will filter out expired agents)
|
|
127
144
|
this.update();
|
|
128
145
|
}
|
|
129
146
|
|
|
130
147
|
// ---- SubagentManagerObserver: react to lifecycle, self-drive the timer ----
|
|
131
148
|
|
|
132
149
|
/** A subagent started running — ensure the update loop is live and render. */
|
|
133
|
-
onSubagentStarted(
|
|
150
|
+
onSubagentStarted(record: Subagent) {
|
|
151
|
+
if (!this.trackBackground(record)) return;
|
|
152
|
+
this.finishedTurnAge.delete(record.id);
|
|
134
153
|
this.startLoop();
|
|
135
154
|
}
|
|
136
155
|
|
|
137
156
|
/** A background subagent was created (queued) — ensure the loop is live and render. */
|
|
138
|
-
onSubagentCreated(
|
|
157
|
+
onSubagentCreated(record: Subagent) {
|
|
158
|
+
if (!this.trackBackground(record)) return;
|
|
159
|
+
this.finishedTurnAge.delete(record.id);
|
|
160
|
+
this.startLoop();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** A subagent completed — seed its bounded linger entry and render. */
|
|
164
|
+
onSubagentCompleted(record: Subagent) {
|
|
165
|
+
if (!this.trackBackground(record)) return;
|
|
166
|
+
this.finishedTurnAge.set(record.id, 0);
|
|
167
|
+
this.update();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** A resumed subagent started — ensure the update loop is live and render. */
|
|
171
|
+
onSubagentResumedStarted(record: Subagent) {
|
|
172
|
+
if (!this.trackBackground(record)) return;
|
|
173
|
+
this.finishedTurnAge.delete(record.id);
|
|
139
174
|
this.startLoop();
|
|
140
175
|
}
|
|
141
176
|
|
|
142
|
-
/** A subagent
|
|
143
|
-
|
|
177
|
+
/** A resumed subagent settled — seed its terminal linger entry. */
|
|
178
|
+
onSubagentResumed(record: Subagent) {
|
|
179
|
+
if (!this.trackBackground(record)) return;
|
|
180
|
+
this.finishedTurnAge.set(record.id, 0);
|
|
144
181
|
this.update();
|
|
145
182
|
}
|
|
146
183
|
|
|
147
184
|
/** A subagent's session compacted — render to refresh the compaction count. */
|
|
148
|
-
onSubagentCompacted(
|
|
185
|
+
onSubagentCompacted(record: Subagent, _info: CompactionInfo) {
|
|
186
|
+
if (this.trackBackground(record)) this.update();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Remove terminal state when the manager clears a parent session. */
|
|
190
|
+
onSubagentCleared(record: Subagent) {
|
|
191
|
+
if (!this.backgroundAgents.delete(record.id)) return;
|
|
192
|
+
this.finishedTurnAge.delete(record.id);
|
|
149
193
|
this.update();
|
|
150
194
|
}
|
|
151
195
|
|
|
152
|
-
/**
|
|
196
|
+
/** Refresh immediately; update() owns the timer lifecycle. */
|
|
153
197
|
private startLoop() {
|
|
154
|
-
this.
|
|
198
|
+
if (!this.uiCtx) return;
|
|
155
199
|
this.update();
|
|
156
200
|
}
|
|
157
201
|
|
|
158
|
-
/** Ensure the widget update timer is running. */
|
|
202
|
+
/** Ensure the widget update timer is running while active records animate. */
|
|
159
203
|
private ensureTimer() {
|
|
160
|
-
this.widgetInterval ??= setInterval(
|
|
204
|
+
this.widgetInterval ??= setInterval(
|
|
205
|
+
() => this.update(),
|
|
206
|
+
AgentWidget.STATUS_REFRESH_INTERVAL_MS,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Stop animated refreshes while leaving any static finished widget intact. */
|
|
211
|
+
private stopTimer() {
|
|
212
|
+
if (this.widgetInterval) {
|
|
213
|
+
clearInterval(this.widgetInterval);
|
|
214
|
+
this.widgetInterval = undefined;
|
|
215
|
+
}
|
|
161
216
|
}
|
|
162
217
|
|
|
163
218
|
/** Check if a finished agent should still be shown in the widget. */
|
|
164
219
|
private shouldShowFinished(agentId: string, status: string): boolean {
|
|
165
220
|
const age = this.finishedTurnAge.get(agentId) ?? 0;
|
|
166
|
-
|
|
167
|
-
return age < maxAge;
|
|
221
|
+
return age < this.maxFinishedAge(status);
|
|
168
222
|
}
|
|
169
223
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
224
|
+
private maxFinishedAge(status: string): number {
|
|
225
|
+
return ERROR_STATUSES.has(status) ? AgentWidget.ERROR_LINGER_TURNS : 1;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Add a record only when its immutable invocation snapshot marks it background. */
|
|
229
|
+
private trackBackground(record: Subagent): boolean {
|
|
230
|
+
if (record.invocation?.runInBackground !== true) return false;
|
|
231
|
+
this.backgroundAgents.set(record.id, record);
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Drop terminal entries whose turn-based linger window has already elapsed. */
|
|
236
|
+
private pruneExpiredFinished(): void {
|
|
237
|
+
for (const [id, record] of this.backgroundAgents) {
|
|
238
|
+
if (record.isActive()) continue;
|
|
239
|
+
if (record.completedAt == null || !this.shouldShowFinished(id, record.status)) {
|
|
240
|
+
this.backgroundAgents.delete(id);
|
|
241
|
+
this.finishedTurnAge.delete(id);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
178
244
|
}
|
|
179
245
|
|
|
180
246
|
/** Project a live Subagent record onto a pure-data WidgetAgent snapshot. */
|
|
@@ -185,6 +251,7 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
185
251
|
status: record.status,
|
|
186
252
|
description: record.description,
|
|
187
253
|
modelLabel: record.modelLabel,
|
|
254
|
+
thinkingLevel: record.effectiveThinkingLevel,
|
|
188
255
|
toolUses: record.toolUses,
|
|
189
256
|
startedAt: record.startedAt,
|
|
190
257
|
completedAt: record.completedAt,
|
|
@@ -202,7 +269,7 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
202
269
|
/** Delegate rendering to the pure widget-renderer module. */
|
|
203
270
|
private renderWidget(tui: any, theme: Theme): string[] {
|
|
204
271
|
return renderWidgetLines({
|
|
205
|
-
agents: this.
|
|
272
|
+
agents: [...this.backgroundAgents.values()].map(r => this.toWidgetAgent(r)),
|
|
206
273
|
registry: this.registry,
|
|
207
274
|
spinnerFrame: this.widgetFrame,
|
|
208
275
|
terminalWidth: tui.terminal.columns,
|
|
@@ -213,10 +280,10 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
213
280
|
|
|
214
281
|
/**
|
|
215
282
|
* Unregister the widget, clear the status bar, stop the interval timer, and
|
|
216
|
-
*
|
|
283
|
+
* release the bounded read model once no visible agents remain.
|
|
217
284
|
* Called only from `update`'s idle path — not from `dispose`.
|
|
218
285
|
*/
|
|
219
|
-
private clearWidget(
|
|
286
|
+
private clearWidget(): void {
|
|
220
287
|
if (this.widgetRegistered) {
|
|
221
288
|
this.uiCtx!.setWidget("agents", undefined);
|
|
222
289
|
this.widgetRegistered = false;
|
|
@@ -226,10 +293,9 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
226
293
|
this.uiCtx!.setStatus("subagents", undefined);
|
|
227
294
|
this.lastStatusText = undefined;
|
|
228
295
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}
|
|
296
|
+
this.stopTimer();
|
|
297
|
+
this.backgroundAgents.clear();
|
|
298
|
+
this.finishedTurnAge.clear();
|
|
233
299
|
}
|
|
234
300
|
|
|
235
301
|
/**
|
|
@@ -244,34 +310,24 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
244
310
|
}
|
|
245
311
|
}
|
|
246
312
|
|
|
247
|
-
/**
|
|
248
|
-
* Seed linger tracking for any newly-observed finished agent.
|
|
249
|
-
* The widget owns detection of completions it observes via `listAgents()`,
|
|
250
|
-
* so no external bookkeeping call is needed.
|
|
251
|
-
* Idempotent — only seeds when an entry is absent, so repeated updates within
|
|
252
|
-
* a turn neither reset nor advance the age.
|
|
253
|
-
*/
|
|
254
|
-
private seedFinishedAgents(agents: readonly AgentSummary[]): void {
|
|
255
|
-
for (const a of agents) {
|
|
256
|
-
if (a.completedAt && !this.finishedTurnAge.has(a.id)) {
|
|
257
|
-
this.finishedTurnAge.set(a.id, 0);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
313
|
/** Force an immediate widget update. */
|
|
263
314
|
update() {
|
|
264
315
|
if (!this.uiCtx) return;
|
|
265
316
|
|
|
266
|
-
|
|
267
|
-
this.
|
|
317
|
+
this.pruneExpiredFinished();
|
|
318
|
+
const backgroundAgents = [...this.backgroundAgents.values()];
|
|
268
319
|
const state = assembleWidgetState(backgroundAgents, (id, status) => this.shouldShowFinished(id, status));
|
|
269
320
|
|
|
270
321
|
if (!state.hasActive && !state.hasFinished) {
|
|
271
|
-
this.clearWidget(
|
|
322
|
+
this.clearWidget();
|
|
272
323
|
return;
|
|
273
324
|
}
|
|
274
325
|
|
|
326
|
+
// Finished records remain renderable for their turn-based linger window,
|
|
327
|
+
// but they have no changing state to animate. Stop the interval while the
|
|
328
|
+
// static completion widget remains registered.
|
|
329
|
+
if (state.hasActive) this.ensureTimer();
|
|
330
|
+
else this.stopTimer();
|
|
275
331
|
this.updateStatusBar(state, backgroundAgents);
|
|
276
332
|
this.widgetFrame++;
|
|
277
333
|
|
|
@@ -298,10 +354,7 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
298
354
|
|
|
299
355
|
// fallow-ignore-next-line unused-class-member
|
|
300
356
|
dispose() {
|
|
301
|
-
|
|
302
|
-
clearInterval(this.widgetInterval);
|
|
303
|
-
this.widgetInterval = undefined;
|
|
304
|
-
}
|
|
357
|
+
this.stopTimer();
|
|
305
358
|
if (this.uiCtx) {
|
|
306
359
|
this.uiCtx.setWidget("agents", undefined);
|
|
307
360
|
this.uiCtx.setStatus("subagents", undefined);
|
|
@@ -309,5 +362,7 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
309
362
|
this.widgetRegistered = false;
|
|
310
363
|
this.tui = undefined;
|
|
311
364
|
this.lastStatusText = undefined;
|
|
365
|
+
this.backgroundAgents.clear();
|
|
366
|
+
this.finishedTurnAge.clear();
|
|
312
367
|
}
|
|
313
368
|
}
|
package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/display.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { AgentConfigLookup } from "#src/config/agent-types";
|
|
9
9
|
import { GLYPHS } from "#src/ui/glyphs";
|
|
10
|
-
import type { AgentInvocation, SubagentType } from "#src/types";
|
|
10
|
+
import type { AgentInvocation, SubagentType, ThinkingLevel } from "#src/types";
|
|
11
11
|
|
|
12
12
|
// ---- Types ----
|
|
13
13
|
|
|
@@ -31,6 +31,8 @@ export interface AgentDetails {
|
|
|
31
31
|
spinnerFrame?: number;
|
|
32
32
|
/** Exact effective model label in `provider/id` form. */
|
|
33
33
|
modelName?: string;
|
|
34
|
+
/** Exact effective thinking level used by the child session. */
|
|
35
|
+
thinkingLevel: ThinkingLevel;
|
|
34
36
|
/** Notable config tags (e.g. ["thinking: high", "inherit context"]). */
|
|
35
37
|
tags?: string[];
|
|
36
38
|
/** Current turn count. */
|
|
@@ -106,6 +108,11 @@ export function formatTurns(turnCount: number, maxTurns?: number | null): string
|
|
|
106
108
|
: `${GLYPHS.turns}${turnCount}`;
|
|
107
109
|
}
|
|
108
110
|
|
|
111
|
+
/** Format an exact model identity and its effective thinking level together. */
|
|
112
|
+
export function formatModelThinking(modelLabel: string, thinkingLevel: ThinkingLevel): string {
|
|
113
|
+
return `${modelLabel} · thinking: ${thinkingLevel}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
109
116
|
/** Format milliseconds as human-readable duration. */
|
|
110
117
|
export function formatMs(ms: number): string {
|
|
111
118
|
return `${(ms / 1000).toFixed(1)}s`;
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
import { buildSessionContext, parseSessionEntries, type SessionEntry, type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
16
16
|
import type { AgentConfigLookup } from "#src/config/agent-types";
|
|
17
17
|
import type { SubagentStatus } from "#src/lifecycle/subagent-state";
|
|
18
|
-
import type { AgentSessionEvent, SessionMessage, SubagentType } from "#src/types";
|
|
19
|
-
import { formatDuration, getDisplayName } from "#src/ui/display";
|
|
18
|
+
import type { AgentSessionEvent, SessionMessage, SubagentType, ThinkingLevel } from "#src/types";
|
|
19
|
+
import { formatDuration, formatModelThinking, getDisplayName } from "#src/ui/display";
|
|
20
20
|
|
|
21
21
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
22
22
|
|
|
@@ -26,6 +26,7 @@ export interface NavigableSubagent {
|
|
|
26
26
|
readonly type: SubagentType;
|
|
27
27
|
readonly description: string;
|
|
28
28
|
readonly modelLabel: string;
|
|
29
|
+
readonly effectiveThinkingLevel: ThinkingLevel;
|
|
29
30
|
readonly status: SubagentStatus;
|
|
30
31
|
readonly startedAt: number;
|
|
31
32
|
readonly completedAt: number | undefined;
|
|
@@ -47,6 +48,7 @@ export interface NavigableSubagent {
|
|
|
47
48
|
*/
|
|
48
49
|
export interface RunDisplayMetadata {
|
|
49
50
|
readonly modelLabel: string;
|
|
51
|
+
readonly thinkingLevel: ThinkingLevel;
|
|
50
52
|
readonly startedAt: number;
|
|
51
53
|
readonly completedAt: () => number | undefined;
|
|
52
54
|
}
|
|
@@ -60,6 +62,7 @@ interface LabelFields {
|
|
|
60
62
|
readonly type: SubagentType;
|
|
61
63
|
readonly description: string;
|
|
62
64
|
readonly modelLabel: string;
|
|
65
|
+
readonly effectiveThinkingLevel: ThinkingLevel;
|
|
63
66
|
readonly status: SubagentStatus;
|
|
64
67
|
readonly startedAt: number;
|
|
65
68
|
readonly completedAt: number | undefined;
|
|
@@ -95,6 +98,7 @@ export function listNavigableAgents(
|
|
|
95
98
|
const live = agents.flatMap((record): NavigationEntry[] => {
|
|
96
99
|
const run = {
|
|
97
100
|
modelLabel: record.modelLabel,
|
|
101
|
+
thinkingLevel: record.effectiveThinkingLevel,
|
|
98
102
|
startedAt: record.startedAt,
|
|
99
103
|
completedAt: () => record.completedAt,
|
|
100
104
|
};
|
|
@@ -150,5 +154,5 @@ function buildLabel(fields: LabelFields, registry: AgentConfigLookup, evicted =
|
|
|
150
154
|
const name = getDisplayName(fields.type, registry);
|
|
151
155
|
const duration = formatDuration(fields.startedAt, fields.completedAt);
|
|
152
156
|
const marker = evicted ? " · released (snapshot)" : "";
|
|
153
|
-
return `${name} (${fields.description}) · ${fields.modelLabel} · ${fields.toolUses} tools · ${fields.status} · ${duration}${marker}`;
|
|
157
|
+
return `${name} (${fields.description}) · ${formatModelThinking(fields.modelLabel, fields.effectiveThinkingLevel)} · ${fields.toolUses} tools · ${fields.status} · ${duration}${marker}`;
|
|
154
158
|
}
|
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
} from "@earendil-works/pi-tui";
|
|
40
40
|
import type { AgentConfigLookup } from "#src/config/agent-types";
|
|
41
41
|
import type { SessionMessage } from "#src/types";
|
|
42
|
-
import { describeActivity, formatDuration, type Theme } from "#src/ui/display";
|
|
42
|
+
import { describeActivity, formatDuration, formatModelThinking, type Theme } from "#src/ui/display";
|
|
43
43
|
import { fileSnapshotSource, listNavigableAgents, liveSource, type NavigableSubagent, type RunDisplayMetadata, type TranscriptSource } from "#src/ui/session-navigation";
|
|
44
44
|
|
|
45
45
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -225,7 +225,7 @@ export class TranscriptOverlay implements Component {
|
|
|
225
225
|
|
|
226
226
|
lines.push(hrTop);
|
|
227
227
|
const runtime = formatDuration(this.run.startedAt, this.run.completedAt());
|
|
228
|
-
lines.push(row(th.bold(`Subagent session · ${this.run.modelLabel} · ${runtime}`)));
|
|
228
|
+
lines.push(row(th.bold(`Subagent session · ${formatModelThinking(this.run.modelLabel, this.run.thinkingLevel)} · ${runtime}`)));
|
|
229
229
|
lines.push(hrMid);
|
|
230
230
|
|
|
231
231
|
const contentLines = this.buildContentLines(innerW);
|
|
@@ -9,9 +9,10 @@ import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
|
9
9
|
import type { AgentConfigLookup } from "#src/config/agent-types";
|
|
10
10
|
import type { LifetimeUsage } from "#src/lifecycle/usage";
|
|
11
11
|
import { getLifetimeTotal } from "#src/lifecycle/usage";
|
|
12
|
-
import type { SubagentType } from "#src/types";
|
|
12
|
+
import type { SubagentType, ThinkingLevel } from "#src/types";
|
|
13
13
|
import {
|
|
14
14
|
describeActivity,
|
|
15
|
+
formatModelThinking,
|
|
15
16
|
formatMs,
|
|
16
17
|
formatSessionTokens,
|
|
17
18
|
formatTurns,
|
|
@@ -30,6 +31,7 @@ export interface WidgetAgent {
|
|
|
30
31
|
readonly status: string;
|
|
31
32
|
readonly description: string;
|
|
32
33
|
readonly modelLabel: string;
|
|
34
|
+
readonly thinkingLevel: ThinkingLevel;
|
|
33
35
|
readonly toolUses: number;
|
|
34
36
|
readonly startedAt: number;
|
|
35
37
|
readonly completedAt?: number;
|
|
@@ -78,7 +80,7 @@ export function renderFinishedLine(
|
|
|
78
80
|
statusText = theme.fg("warning", " aborted");
|
|
79
81
|
}
|
|
80
82
|
|
|
81
|
-
const parts: string[] = [agent.modelLabel];
|
|
83
|
+
const parts: string[] = [formatModelThinking(agent.modelLabel, agent.thinkingLevel)];
|
|
82
84
|
parts.push(formatTurns(agent.turnCount, agent.maxTurns));
|
|
83
85
|
if (agent.toolUses > 0) parts.push(`${agent.toolUses} tool use${agent.toolUses === 1 ? "" : "s"}`);
|
|
84
86
|
parts.push(duration);
|
|
@@ -102,7 +104,7 @@ export function renderRunningLines(
|
|
|
102
104
|
const tokens = getLifetimeTotal(agent.lifetimeUsage);
|
|
103
105
|
const tokenText = tokens > 0 ? formatSessionTokens(tokens, agent.contextPercent, theme, agent.compactionCount) : "";
|
|
104
106
|
|
|
105
|
-
const parts: string[] = [agent.modelLabel];
|
|
107
|
+
const parts: string[] = [formatModelThinking(agent.modelLabel, agent.thinkingLevel)];
|
|
106
108
|
parts.push(formatTurns(agent.turnCount, agent.maxTurns));
|
|
107
109
|
if (agent.toolUses > 0) parts.push(`${agent.toolUses} tool use${agent.toolUses === 1 ? "" : "s"}`);
|
|
108
110
|
if (tokenText) parts.push(tokenText);
|
|
@@ -176,7 +178,7 @@ function buildSections(
|
|
|
176
178
|
truncate(
|
|
177
179
|
theme.fg("dim", "\u251C\u2500")
|
|
178
180
|
+ ` ${theme.fg("muted", "\u25E6")} ${theme.fg("dim", getDisplayName(agent.type, registry))}`
|
|
179
|
-
+ ` ${theme.fg("dim", "·")} ${theme.fg("dim", agent.modelLabel)}`
|
|
181
|
+
+ ` ${theme.fg("dim", "·")} ${theme.fg("dim", formatModelThinking(agent.modelLabel, agent.thinkingLevel))}`
|
|
180
182
|
+ ` ${theme.fg("dim", "·")} ${theme.fg("dim", agent.description)}`
|
|
181
183
|
+ ` ${theme.fg("dim", "· queued")}`,
|
|
182
184
|
),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nklisch/pi-plugins",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Native marketplace and plugin lifecycle management for Pi, with Claude Code and Codex compatibility.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -67,8 +67,8 @@
|
|
|
67
67
|
"@nklisch/pi-subagents"
|
|
68
68
|
],
|
|
69
69
|
"dependencies": {
|
|
70
|
-
"@nklisch/pi-mcp-adapter": "2.20.1-nklisch.
|
|
71
|
-
"@nklisch/pi-subagents": "18.1.0-nklisch.
|
|
70
|
+
"@nklisch/pi-mcp-adapter": "2.20.1-nklisch.1",
|
|
71
|
+
"@nklisch/pi-subagents": "18.1.0-nklisch.1",
|
|
72
72
|
"jiti": "2.7.0",
|
|
73
73
|
"semver": "7.8.1",
|
|
74
74
|
"smol-toml": "^1.7.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nklisch/pi-enhanced",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Pi, enhanced — one install for nklisch's full harness: policy-gated command review, plugin marketplace, subagents, background tasks, research tools, search, model modes, and a curated UX set.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "nklisch"
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"@nklisch/pi-clearance": "^0",
|
|
68
68
|
"@nklisch/pi-conveniences": "^0",
|
|
69
69
|
"@nklisch/pi-fff-compat": "^0",
|
|
70
|
-
"@nklisch/pi-mcp-adapter": "2.20.1-nklisch.
|
|
70
|
+
"@nklisch/pi-mcp-adapter": "2.20.1-nklisch.1",
|
|
71
71
|
"@nklisch/pi-model-modes": "^0",
|
|
72
72
|
"@nklisch/pi-plugins": "^0",
|
|
73
73
|
"@sinclair/typebox": "^0.34.49",
|