@nklisch/pi-enhanced 0.2.7 → 0.3.0

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.
Files changed (70) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/node_modules/@nklisch/pi-clearance/native/clearance-core.linux-x64-gnu.node +0 -0
  3. package/node_modules/@nklisch/pi-clearance/native/clearance-core.win32-x64-msvc.node +0 -0
  4. package/node_modules/@nklisch/pi-plugins/README.md +4 -2
  5. package/node_modules/@nklisch/pi-plugins/dist/hooks.js +81 -8
  6. package/node_modules/@nklisch/pi-plugins/dist/hooks.js.map +1 -1
  7. package/node_modules/@nklisch/pi-plugins/dist/mcp.js +32 -1
  8. package/node_modules/@nklisch/pi-plugins/dist/mcp.js.map +1 -1
  9. package/node_modules/@nklisch/pi-plugins/dist/pi/plugin-manager.d.ts +10 -4
  10. package/node_modules/@nklisch/pi-plugins/dist/pi/plugin-manager.js +144 -10
  11. package/node_modules/@nklisch/pi-plugins/dist/pi/plugin-manager.js.map +1 -1
  12. package/node_modules/@nklisch/pi-plugins/dist/runtime-discovery.js +40 -14
  13. package/node_modules/@nklisch/pi-plugins/dist/runtime-discovery.js.map +1 -1
  14. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/CHANGELOG.md +19 -0
  15. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/README.md +75 -47
  16. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/dist/public.d.ts +171 -140
  17. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/dist/settings.d.ts +2 -2
  18. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/FORK-MAINTENANCE.md +8 -7
  19. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/VISION.md +12 -10
  20. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/architecture/architecture.md +111 -104
  21. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/comparison-with-upstream.md +3 -3
  22. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/decisions/0004-reconsider-ui-direction.md +5 -0
  23. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/package.json +1 -1
  24. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/config/custom-agents.ts +31 -4
  25. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/config/default-agents.ts +1 -1
  26. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/config/invocation-config.ts +7 -4
  27. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/index.ts +18 -9
  28. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/layered-settings.ts +2 -2
  29. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/child-lifecycle.ts +10 -63
  30. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/concurrency-limiter.ts +122 -76
  31. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/create-subagent-session.ts +3 -5
  32. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/lifecycle-interceptor.ts +4 -3
  33. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/run-listeners.ts +2 -23
  34. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent-manager.ts +177 -252
  35. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent-session.ts +90 -280
  36. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent-state.ts +153 -239
  37. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent.ts +569 -700
  38. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/workspace.ts +1 -1
  39. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/notification.ts +78 -122
  40. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/renderer.ts +15 -42
  41. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/subagent-events-observer.ts +44 -95
  42. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/service/service-adapter.ts +157 -90
  43. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/service/service.ts +60 -68
  44. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/session/conversation.ts +3 -3
  45. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/session/query-source.ts +28 -0
  46. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/session/query.ts +327 -0
  47. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/settings.ts +1 -1
  48. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/agent-tool.ts +171 -249
  49. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/get-result-tool.ts +51 -94
  50. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/helpers.ts +9 -6
  51. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/list-tool.ts +46 -0
  52. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/parent-tool-registry.ts +17 -0
  53. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/query-session-tool.ts +330 -0
  54. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/result-renderer.ts +27 -111
  55. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/resume-tool.ts +105 -0
  56. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/spawn-config.ts +26 -8
  57. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/steer-tool.ts +27 -111
  58. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/stop-tool.ts +53 -0
  59. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/types.ts +11 -5
  60. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/agent-widget.ts +33 -27
  61. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/display.ts +6 -3
  62. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/session-navigation.ts +121 -10
  63. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/session-navigator.ts +430 -112
  64. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/subagents-settings.ts +1 -1
  65. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/widget-renderer.ts +8 -9
  66. package/node_modules/@nklisch/pi-plugins/package.json +2 -2
  67. package/package.json +1 -1
  68. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/background-spawner.ts +0 -81
  69. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/foreground-runner.ts +0 -149
  70. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/get-result-report.ts +0 -77
@@ -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, in this slice).
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
- * here; a file snapshot in a follow-up) from *how they render* — the renderer
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 { buildSessionContext, parseSessionEntries, type SessionEntry, type ToolDefinition } from "@earendil-works/pi-coding-agent";
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; an
47
- * `evicted`-kind entry is a persisted snapshot for a released live session.
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 entries = parseSessionEntries(readFile(outputFile));
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 (this slice's only source). */
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