@nklisch/pi-enhanced 0.2.7 → 0.3.1
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 +16 -0
- package/node_modules/@nklisch/pi-clearance/native/clearance-core.linux-x64-gnu.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 -2
- package/node_modules/@nklisch/pi-plugins/dist/hooks.js +81 -8
- package/node_modules/@nklisch/pi-plugins/dist/hooks.js.map +1 -1
- package/node_modules/@nklisch/pi-plugins/dist/mcp.js +32 -1
- package/node_modules/@nklisch/pi-plugins/dist/mcp.js.map +1 -1
- package/node_modules/@nklisch/pi-plugins/dist/pi/plugin-manager.d.ts +10 -4
- package/node_modules/@nklisch/pi-plugins/dist/pi/plugin-manager.js +144 -10
- package/node_modules/@nklisch/pi-plugins/dist/pi/plugin-manager.js.map +1 -1
- package/node_modules/@nklisch/pi-plugins/dist/runtime-discovery.js +40 -14
- package/node_modules/@nklisch/pi-plugins/dist/runtime-discovery.js.map +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/CHANGELOG.md +27 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/README.md +76 -47
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/dist/public.d.ts +171 -140
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/dist/settings.d.ts +2 -2
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/FORK-MAINTENANCE.md +8 -7
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/VISION.md +12 -10
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/architecture/architecture.md +118 -104
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/comparison-with-upstream.md +3 -3
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/decisions/0004-reconsider-ui-direction.md +5 -0
- 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/config/custom-agents.ts +31 -4
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/config/default-agents.ts +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/config/invocation-config.ts +7 -4
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/index.ts +18 -9
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/layered-settings.ts +2 -2
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/child-lifecycle.ts +10 -63
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/concurrency-limiter.ts +122 -76
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/create-subagent-session.ts +3 -5
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/lifecycle-interceptor.ts +4 -3
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/run-listeners.ts +2 -23
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent-manager.ts +177 -252
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent-session.ts +90 -280
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent-state.ts +153 -239
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent.ts +569 -700
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/workspace.ts +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/notification.ts +78 -122
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/renderer.ts +15 -42
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/subagent-events-observer.ts +44 -95
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/service/service-adapter.ts +157 -90
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/service/service.ts +60 -68
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/session/conversation.ts +3 -3
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/session/query-source.ts +28 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/session/query.ts +488 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/settings.ts +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/agent-tool.ts +171 -249
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/get-result-tool.ts +51 -94
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/helpers.ts +9 -6
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/list-tool.ts +46 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/parent-tool-registry.ts +17 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/query-session-tool.ts +412 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/result-renderer.ts +27 -111
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/resume-tool.ts +105 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/spawn-config.ts +26 -8
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/steer-tool.ts +27 -111
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/stop-tool.ts +53 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/types.ts +11 -5
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/agent-widget.ts +33 -27
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/display.ts +6 -3
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/session-navigation.ts +121 -10
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/session-navigator.ts +430 -112
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/subagents-settings.ts +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/widget-renderer.ts +8 -9
- package/node_modules/@nklisch/pi-plugins/package.json +2 -2
- package/package.json +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/background-spawner.ts +0 -81
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/foreground-runner.ts +0 -149
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/get-result-report.ts +0 -77
package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/agent-widget.ts
CHANGED
|
@@ -87,14 +87,14 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
87
87
|
private widgetFrame = 0;
|
|
88
88
|
private widgetInterval: ReturnType<typeof setInterval> | undefined;
|
|
89
89
|
/**
|
|
90
|
-
* Bounded reactive read model: lifecycle callbacks add active
|
|
90
|
+
* Bounded reactive read model: lifecycle callbacks add active detached
|
|
91
91
|
* records and the small terminal linger set; render ticks never ask the
|
|
92
92
|
* manager to clone or sort its retained history.
|
|
93
93
|
*/
|
|
94
|
-
private readonly
|
|
94
|
+
private readonly detachedAgents = new Map<string, Subagent>();
|
|
95
95
|
/** Tracks how many turns each finished agent has survived. */
|
|
96
96
|
private finishedTurnAge = new Map<string, number>();
|
|
97
|
-
/** How many extra turns errors/
|
|
97
|
+
/** How many extra turns errors/stopped agents linger (completed agents clear after 1 turn). */
|
|
98
98
|
private static readonly ERROR_LINGER_TURNS = 2;
|
|
99
99
|
/** Pi renders the whole component tree for each requestRender call. */
|
|
100
100
|
private static readonly STATUS_REFRESH_INTERVAL_MS = 500;
|
|
@@ -122,7 +122,7 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
122
122
|
// Lifecycle events can arrive before the first tool gives us a UI context.
|
|
123
123
|
// Refresh when that context becomes available, but let update() decide
|
|
124
124
|
// whether any active record actually needs an animation timer.
|
|
125
|
-
if (contextChanged && this.uiCtx && this.
|
|
125
|
+
if (contextChanged && this.uiCtx && this.detachedAgents.size > 0) this.refresh();
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
/**
|
|
@@ -131,13 +131,13 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
131
131
|
*/
|
|
132
132
|
onTurnStart() {
|
|
133
133
|
// Age terminal records and remove them before the next render. Completed
|
|
134
|
-
// records linger for one turn; errors and
|
|
134
|
+
// records linger for one turn; errors and stopped records linger for two.
|
|
135
135
|
for (const [id, age] of this.finishedTurnAge) {
|
|
136
|
-
const record = this.
|
|
136
|
+
const record = this.detachedAgents.get(id);
|
|
137
137
|
const nextAge = age + 1;
|
|
138
138
|
if (!record || nextAge >= this.maxFinishedAge(record.status)) {
|
|
139
139
|
this.finishedTurnAge.delete(id);
|
|
140
|
-
this.
|
|
140
|
+
this.detachedAgents.delete(id);
|
|
141
141
|
} else {
|
|
142
142
|
this.finishedTurnAge.set(id, nextAge);
|
|
143
143
|
}
|
|
@@ -149,47 +149,47 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
149
149
|
|
|
150
150
|
/** A subagent started running — ensure the update loop is live and render. */
|
|
151
151
|
onSubagentStarted(record: Subagent) {
|
|
152
|
-
if (!this.
|
|
152
|
+
if (!this.trackDetached(record)) return;
|
|
153
153
|
this.finishedTurnAge.delete(record.id);
|
|
154
154
|
this.startLoop();
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
/** A
|
|
157
|
+
/** A detached subagent was created (queued) — ensure the loop is live and render. */
|
|
158
158
|
onSubagentCreated(record: Subagent) {
|
|
159
|
-
if (!this.
|
|
159
|
+
if (!this.trackDetached(record)) return;
|
|
160
160
|
this.finishedTurnAge.delete(record.id);
|
|
161
161
|
this.startLoop();
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
/** A subagent completed — seed its bounded linger entry and render. */
|
|
165
165
|
onSubagentCompleted(record: Subagent) {
|
|
166
|
-
if (!this.
|
|
166
|
+
if (!this.trackDetached(record)) return;
|
|
167
167
|
this.finishedTurnAge.set(record.id, 0);
|
|
168
168
|
this.refresh();
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
/** A resumed subagent started — ensure the update loop is live and render. */
|
|
172
172
|
onSubagentResumedStarted(record: Subagent) {
|
|
173
|
-
if (!this.
|
|
173
|
+
if (!this.trackDetached(record)) return;
|
|
174
174
|
this.finishedTurnAge.delete(record.id);
|
|
175
175
|
this.startLoop();
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
/** A resumed subagent settled — seed its terminal linger entry. */
|
|
179
179
|
onSubagentResumed(record: Subagent) {
|
|
180
|
-
if (!this.
|
|
180
|
+
if (!this.trackDetached(record)) return;
|
|
181
181
|
this.finishedTurnAge.set(record.id, 0);
|
|
182
182
|
this.refresh();
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
/** A subagent's session compacted — render to refresh the compaction count. */
|
|
186
186
|
onSubagentCompacted(record: Subagent, _info: CompactionInfo) {
|
|
187
|
-
if (this.
|
|
187
|
+
if (this.trackDetached(record)) this.refresh();
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
/** Remove terminal state when the manager clears a parent session. */
|
|
191
191
|
onSubagentCleared(record: Subagent) {
|
|
192
|
-
if (!this.
|
|
192
|
+
if (!this.detachedAgents.delete(record.id)) return;
|
|
193
193
|
this.finishedTurnAge.delete(record.id);
|
|
194
194
|
this.refresh();
|
|
195
195
|
}
|
|
@@ -226,19 +226,23 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
226
226
|
return ERROR_STATUSES.has(status) ? AgentWidget.ERROR_LINGER_TURNS : 1;
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
-
/** Add a record only when its
|
|
230
|
-
private
|
|
231
|
-
if (record.
|
|
232
|
-
|
|
229
|
+
/** Add a record only when its current run mode is detached. */
|
|
230
|
+
private trackDetached(record: Subagent): boolean {
|
|
231
|
+
if (record.mode !== "detached") {
|
|
232
|
+
this.detachedAgents.delete(record.id);
|
|
233
|
+
this.finishedTurnAge.delete(record.id);
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
this.detachedAgents.set(record.id, record);
|
|
233
237
|
return true;
|
|
234
238
|
}
|
|
235
239
|
|
|
236
240
|
/** Drop terminal entries whose turn-based linger window has already elapsed. */
|
|
237
241
|
private pruneExpiredFinished(): void {
|
|
238
|
-
for (const [id, record] of this.
|
|
242
|
+
for (const [id, record] of this.detachedAgents) {
|
|
239
243
|
if (record.isActive()) continue;
|
|
240
244
|
if (record.completedAt == null || !this.shouldShowFinished(id, record.status)) {
|
|
241
|
-
this.
|
|
245
|
+
this.detachedAgents.delete(id);
|
|
242
246
|
this.finishedTurnAge.delete(id);
|
|
243
247
|
}
|
|
244
248
|
}
|
|
@@ -250,12 +254,14 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
250
254
|
id: record.id,
|
|
251
255
|
type: record.type,
|
|
252
256
|
status: record.status,
|
|
257
|
+
terminalReason: record.stateTerminalReason,
|
|
253
258
|
description: record.description,
|
|
254
259
|
modelLabel: record.modelLabel,
|
|
255
260
|
thinkingLevel: record.effectiveThinkingLevel,
|
|
256
261
|
toolUses: record.toolUses,
|
|
257
262
|
startedAt: record.startedAt,
|
|
258
263
|
completedAt: record.completedAt,
|
|
264
|
+
activeRuntimeMs: record.activeRuntimeMs,
|
|
259
265
|
error: record.error,
|
|
260
266
|
lifetimeUsage: record.lifetimeUsage,
|
|
261
267
|
compactionCount: record.compactionCount,
|
|
@@ -270,7 +276,7 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
270
276
|
/** Delegate rendering to the pure widget-renderer module. */
|
|
271
277
|
private renderWidget(tui: any, theme: Theme): string[] {
|
|
272
278
|
return renderWidgetLines({
|
|
273
|
-
agents: [...this.
|
|
279
|
+
agents: [...this.detachedAgents.values()].map(r => this.toWidgetAgent(r)),
|
|
274
280
|
registry: this.registry,
|
|
275
281
|
spinnerFrame: this.widgetFrame,
|
|
276
282
|
terminalWidth: tui.terminal.columns,
|
|
@@ -295,7 +301,7 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
295
301
|
this.lastStatusText = undefined;
|
|
296
302
|
}
|
|
297
303
|
this.stopTimer();
|
|
298
|
-
this.
|
|
304
|
+
this.detachedAgents.clear();
|
|
299
305
|
this.finishedTurnAge.clear();
|
|
300
306
|
}
|
|
301
307
|
|
|
@@ -316,8 +322,8 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
316
322
|
if (!this.uiCtx) return;
|
|
317
323
|
|
|
318
324
|
this.pruneExpiredFinished();
|
|
319
|
-
const
|
|
320
|
-
const state = assembleWidgetState(
|
|
325
|
+
const detachedAgents = [...this.detachedAgents.values()];
|
|
326
|
+
const state = assembleWidgetState(detachedAgents, (id, status) => this.shouldShowFinished(id, status));
|
|
321
327
|
|
|
322
328
|
if (!state.hasActive && !state.hasFinished) {
|
|
323
329
|
this.clearWidget();
|
|
@@ -329,7 +335,7 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
329
335
|
// static completion widget remains registered.
|
|
330
336
|
if (state.hasActive) this.ensureTimer();
|
|
331
337
|
else this.stopTimer();
|
|
332
|
-
this.updateStatusBar(state,
|
|
338
|
+
this.updateStatusBar(state, detachedAgents);
|
|
333
339
|
this.widgetFrame++;
|
|
334
340
|
|
|
335
341
|
// Register widget callback once; subsequent updates use requestRender()
|
|
@@ -387,7 +393,7 @@ export class AgentWidget implements SubagentManagerObserver {
|
|
|
387
393
|
this.widgetRegistered = false;
|
|
388
394
|
this.tui = undefined;
|
|
389
395
|
this.lastStatusText = undefined;
|
|
390
|
-
this.
|
|
396
|
+
this.detachedAgents.clear();
|
|
391
397
|
this.finishedTurnAge.clear();
|
|
392
398
|
}
|
|
393
399
|
}
|
package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/display.ts
CHANGED
|
@@ -7,6 +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 { SubagentTerminalReason } from "#src/lifecycle/subagent-state";
|
|
10
11
|
import type { AgentInvocation, SubagentType, ThinkingLevel } from "#src/types";
|
|
11
12
|
|
|
12
13
|
// ---- Types ----
|
|
@@ -24,7 +25,8 @@ export interface AgentDetails {
|
|
|
24
25
|
toolUses: number;
|
|
25
26
|
tokens: string;
|
|
26
27
|
durationMs: number;
|
|
27
|
-
status: "queued" | "running" | "completed" | "
|
|
28
|
+
status: "queued" | "running" | "completed" | "stopped" | "error";
|
|
29
|
+
terminalReason?: SubagentTerminalReason;
|
|
28
30
|
/** Human-readable description of what the agent is currently doing. */
|
|
29
31
|
activity?: string;
|
|
30
32
|
/** Current spinner frame index (for animated running indicator). */
|
|
@@ -49,7 +51,7 @@ export interface AgentDetails {
|
|
|
49
51
|
export const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
50
52
|
|
|
51
53
|
/** Statuses that indicate an error/non-success outcome (used for linger behavior and icon rendering). */
|
|
52
|
-
export const ERROR_STATUSES = new Set(["error", "
|
|
54
|
+
export const ERROR_STATUSES = new Set(["error", "stopped"]);
|
|
53
55
|
|
|
54
56
|
/** Tool name → human-readable action for activity descriptions. */
|
|
55
57
|
const TOOL_DISPLAY: Record<string, string> = {
|
|
@@ -146,8 +148,9 @@ export function buildInvocationTags(
|
|
|
146
148
|
if (!invocation) return { tags };
|
|
147
149
|
if (invocation.thinking) tags.push(`thinking: ${invocation.thinking}`);
|
|
148
150
|
if (invocation.inheritContext) tags.push("inherit context");
|
|
149
|
-
if (invocation.
|
|
151
|
+
if (invocation.mode) tags.push(`mode: ${invocation.mode}`);
|
|
150
152
|
if (invocation.maxTurns != null) tags.push(`max turns: ${invocation.maxTurns}`);
|
|
153
|
+
if (invocation.timeoutSeconds != null) tags.push(`timeout: ${invocation.timeoutSeconds}s`);
|
|
151
154
|
return { modelName: invocation.modelName, tags };
|
|
152
155
|
}
|
|
153
156
|
|
|
@@ -3,20 +3,22 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Splits the unit-testable core of the `/subagents:sessions` command from its TUI
|
|
5
5
|
* wiring (`session-navigator.ts`): which subagents are navigable and how a picked
|
|
6
|
-
* agent's transcript is sourced (live
|
|
6
|
+
* agent's transcript is sourced (live or a retained file snapshot).
|
|
7
7
|
*
|
|
8
8
|
* The `TranscriptSource` seam decouples *how messages are sourced* (live record
|
|
9
|
-
*
|
|
9
|
+
* or a retained file snapshot) from *how they render* — the renderer
|
|
10
10
|
* (`session-navigator.ts`, which mounts Pi's per-entry components) talks only to
|
|
11
11
|
* this seam. Rendering lives in the SDK/TUI module because the per-entry
|
|
12
|
-
* components require a `TUI`, `cwd`, and markdown theme.
|
|
12
|
+
* components require a `TUI`, `cwd`, and markdown theme. The query tool uses
|
|
13
|
+
* the same JSONL adapter so file and live reads cannot drift.
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
|
-
import
|
|
16
|
+
import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
16
17
|
import type { AgentConfigLookup } from "#src/config/agent-types";
|
|
17
18
|
import type { SubagentStatus } from "#src/lifecycle/subagent-state";
|
|
18
19
|
import type { AgentSessionEvent, SessionMessage, SubagentType, ThinkingLevel } from "#src/types";
|
|
19
20
|
import { formatDuration, formatModelThinking, getDisplayName } from "#src/ui/display";
|
|
21
|
+
import { parseSessionFileMessages } from "#src/session/query-source";
|
|
20
22
|
|
|
21
23
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
22
24
|
|
|
@@ -37,14 +39,17 @@ export interface NavigableSubagent {
|
|
|
37
39
|
readonly agentMessages: readonly SessionMessage[];
|
|
38
40
|
isSessionReady(): boolean;
|
|
39
41
|
subscribeToUpdates(fn: (event: AgentSessionEvent) => void): (() => void) | undefined;
|
|
42
|
+
/** Notifies consumers when a retained session is released and changes source. */
|
|
43
|
+
subscribeToRecordUpdates?: (fn: () => void) => () => void;
|
|
40
44
|
getToolDefinition(name: string): ToolDefinition | undefined;
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
/**
|
|
44
48
|
* A navigable entry plus the label shown in the picker.
|
|
45
49
|
*
|
|
46
|
-
* A `live` entry sources its transcript from the in-memory session
|
|
47
|
-
* `evicted`-kind entry is a persisted
|
|
50
|
+
* A `live` entry sources its transcript from the in-memory session and can
|
|
51
|
+
* transition to its retained file; an `evicted`-kind entry is a persisted
|
|
52
|
+
* snapshot for a released live session.
|
|
48
53
|
*/
|
|
49
54
|
export interface RunDisplayMetadata {
|
|
50
55
|
readonly modelLabel: string;
|
|
@@ -75,6 +80,12 @@ export interface StreamingState {
|
|
|
75
80
|
readonly responseText: string;
|
|
76
81
|
}
|
|
77
82
|
|
|
83
|
+
/** Source state shown when a live record is released or its snapshot cannot load. */
|
|
84
|
+
export type TranscriptSourceAvailability =
|
|
85
|
+
| { readonly kind: "live"; readonly path?: string }
|
|
86
|
+
| { readonly kind: "file"; readonly path: string }
|
|
87
|
+
| { readonly kind: "unavailable"; readonly path?: string; readonly error?: string };
|
|
88
|
+
|
|
78
89
|
/** Liveness-agnostic transcript source consumed by the renderer. */
|
|
79
90
|
export interface TranscriptSource {
|
|
80
91
|
/** Current message history. */
|
|
@@ -85,6 +96,8 @@ export interface TranscriptSource {
|
|
|
85
96
|
streaming(): StreamingState | undefined;
|
|
86
97
|
/** Resolve a registered tool definition by name, for Pi's tool-execution components. */
|
|
87
98
|
getToolDefinition(name: string): ToolDefinition | undefined;
|
|
99
|
+
/** Current source state; unavailable retains the last readable messages. */
|
|
100
|
+
availability?(): TranscriptSourceAvailability;
|
|
88
101
|
}
|
|
89
102
|
|
|
90
103
|
/**
|
|
@@ -126,18 +139,17 @@ export function fileSnapshotSource(
|
|
|
126
139
|
outputFile: string,
|
|
127
140
|
readFile: (path: string) => string,
|
|
128
141
|
): TranscriptSource {
|
|
129
|
-
const
|
|
130
|
-
const sessionEntries = entries.filter((entry): entry is SessionEntry => entry.type !== "session");
|
|
131
|
-
const { messages } = buildSessionContext(sessionEntries);
|
|
142
|
+
const messages = parseSessionFileMessages(readFile(outputFile));
|
|
132
143
|
return {
|
|
133
144
|
getMessages: () => messages,
|
|
134
145
|
subscribe: () => undefined,
|
|
135
146
|
streaming: () => undefined,
|
|
136
147
|
getToolDefinition: () => undefined,
|
|
148
|
+
availability: () => ({ kind: "file", path: outputFile }),
|
|
137
149
|
};
|
|
138
150
|
}
|
|
139
151
|
|
|
140
|
-
/** Source a transcript live from an in-memory record
|
|
152
|
+
/** Source a transcript live from an in-memory record. */
|
|
141
153
|
export function liveSource(record: NavigableSubagent): TranscriptSource {
|
|
142
154
|
return {
|
|
143
155
|
getMessages: () => record.agentMessages,
|
|
@@ -147,6 +159,105 @@ export function liveSource(record: NavigableSubagent): TranscriptSource {
|
|
|
147
159
|
? { activeTools: record.activeTools, responseText: record.responseText }
|
|
148
160
|
: undefined,
|
|
149
161
|
getToolDefinition: (name) => record.getToolDefinition(name),
|
|
162
|
+
availability: () => ({ kind: "live", ...(record.outputFile ? { path: record.outputFile } : {}) }),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Keep a live viewer useful across retention release. The source swaps to the
|
|
168
|
+
* persisted snapshot on the release notification and retains the last live
|
|
169
|
+
* content when that snapshot cannot be read.
|
|
170
|
+
*/
|
|
171
|
+
export function liveFileSource(
|
|
172
|
+
record: NavigableSubagent,
|
|
173
|
+
readFile: (path: string) => string,
|
|
174
|
+
): TranscriptSource {
|
|
175
|
+
let mode: "live" | "file" | "unavailable" = record.isSessionReady() ? "live" : "unavailable";
|
|
176
|
+
let messages: readonly SessionMessage[] = record.agentMessages;
|
|
177
|
+
let error: string | undefined;
|
|
178
|
+
let fileAttempted = false;
|
|
179
|
+
|
|
180
|
+
const swapIfReleased = (): void => {
|
|
181
|
+
if (record.isSessionReady()) {
|
|
182
|
+
mode = "live";
|
|
183
|
+
error = undefined;
|
|
184
|
+
messages = record.agentMessages;
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (fileAttempted || !record.outputFile) {
|
|
188
|
+
if (!record.outputFile && mode === "live") mode = "unavailable";
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
fileAttempted = true;
|
|
192
|
+
try {
|
|
193
|
+
messages = parseSessionFileMessages(readFile(record.outputFile));
|
|
194
|
+
mode = "file";
|
|
195
|
+
error = undefined;
|
|
196
|
+
} catch (cause) {
|
|
197
|
+
mode = "unavailable";
|
|
198
|
+
error = cause instanceof Error ? cause.message : String(cause);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const notify = (onChange: () => void): void => {
|
|
203
|
+
try {
|
|
204
|
+
// Do not replace the last live snapshot with Subagent's empty post-release
|
|
205
|
+
// getter before the file adapter succeeds; the degraded path must retain
|
|
206
|
+
// content that was already visible.
|
|
207
|
+
if (mode === "live" && record.isSessionReady()) messages = record.agentMessages;
|
|
208
|
+
swapIfReleased();
|
|
209
|
+
onChange();
|
|
210
|
+
} catch (cause) {
|
|
211
|
+
mode = "unavailable";
|
|
212
|
+
error = cause instanceof Error ? cause.message : String(cause);
|
|
213
|
+
onChange();
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
getMessages: () => {
|
|
219
|
+
try {
|
|
220
|
+
if (mode === "live") {
|
|
221
|
+
if (!record.isSessionReady()) swapIfReleased();
|
|
222
|
+
else messages = record.agentMessages;
|
|
223
|
+
} else swapIfReleased();
|
|
224
|
+
} catch (cause) {
|
|
225
|
+
mode = "unavailable";
|
|
226
|
+
error = cause instanceof Error ? cause.message : String(cause);
|
|
227
|
+
}
|
|
228
|
+
return messages;
|
|
229
|
+
},
|
|
230
|
+
subscribe: (onChange) => {
|
|
231
|
+
const unsubscribers: Array<() => void> = [];
|
|
232
|
+
try {
|
|
233
|
+
const unsubscribe = record.subscribeToUpdates(() => notify(onChange));
|
|
234
|
+
if (unsubscribe) unsubscribers.push(unsubscribe);
|
|
235
|
+
} catch (cause) {
|
|
236
|
+
mode = "unavailable";
|
|
237
|
+
error = cause instanceof Error ? cause.message : String(cause);
|
|
238
|
+
}
|
|
239
|
+
if (record.subscribeToRecordUpdates) {
|
|
240
|
+
try {
|
|
241
|
+
unsubscribers.push(record.subscribeToRecordUpdates(() => notify(onChange)));
|
|
242
|
+
} catch (cause) {
|
|
243
|
+
mode = "unavailable";
|
|
244
|
+
error = cause instanceof Error ? cause.message : String(cause);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return unsubscribers.length === 0 ? undefined : () => {
|
|
248
|
+
for (const unsubscribe of unsubscribers) unsubscribe();
|
|
249
|
+
};
|
|
250
|
+
},
|
|
251
|
+
streaming: () => mode === "live" && record.status === "running"
|
|
252
|
+
? { activeTools: record.activeTools, responseText: record.responseText }
|
|
253
|
+
: undefined,
|
|
254
|
+
getToolDefinition: (name) => mode === "live" ? record.getToolDefinition(name) : undefined,
|
|
255
|
+
availability: () => {
|
|
256
|
+
swapIfReleased();
|
|
257
|
+
if (mode === "live") return { kind: "live", ...(record.outputFile ? { path: record.outputFile } : {}) };
|
|
258
|
+
if (mode === "file" && record.outputFile) return { kind: "file", path: record.outputFile };
|
|
259
|
+
return { kind: "unavailable", ...(record.outputFile ? { path: record.outputFile } : {}), ...(error ? { error } : {}) };
|
|
260
|
+
},
|
|
150
261
|
};
|
|
151
262
|
}
|
|
152
263
|
|