@nklisch/pi-enhanced 0.3.0 → 0.4.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +1 -0
  3. package/node_modules/@nklisch/pi-astral-pocket/LICENSE +3 -0
  4. package/node_modules/@nklisch/pi-astral-pocket/README.md +82 -0
  5. package/node_modules/@nklisch/pi-astral-pocket/package.json +52 -0
  6. package/node_modules/@nklisch/pi-astral-pocket/src/activation.ts +44 -0
  7. package/node_modules/@nklisch/pi-astral-pocket/src/config.ts +78 -0
  8. package/node_modules/@nklisch/pi-astral-pocket/src/distiller.ts +162 -0
  9. package/node_modules/@nklisch/pi-astral-pocket/src/guidance.ts +59 -0
  10. package/node_modules/@nklisch/pi-astral-pocket/src/index.ts +127 -0
  11. package/node_modules/@nklisch/pi-astral-pocket/src/sessions.ts +225 -0
  12. package/node_modules/@nklisch/pi-astral-pocket/src/store.ts +227 -0
  13. package/node_modules/@nklisch/pi-astral-pocket/src/tools.ts +112 -0
  14. package/node_modules/@nklisch/pi-clearance/native/clearance-core.darwin-arm64.node +0 -0
  15. package/node_modules/@nklisch/pi-clearance/native/clearance-core.darwin-x64.node +0 -0
  16. package/node_modules/@nklisch/pi-clearance/native/clearance-core.linux-arm64-gnu.node +0 -0
  17. package/node_modules/@nklisch/pi-clearance/native/clearance-core.win32-x64-msvc.node +0 -0
  18. package/node_modules/@nklisch/pi-conveniences/extensions/agents-context.ts +4 -6
  19. package/node_modules/@nklisch/pi-conveniences/extensions/context-window-footer.ts +38 -22
  20. package/node_modules/@nklisch/pi-conveniences/package.json +1 -1
  21. package/node_modules/@nklisch/pi-plugins/README.md +12 -6
  22. package/node_modules/@nklisch/pi-plugins/dist/catalog.js +11 -0
  23. package/node_modules/@nklisch/pi-plugins/dist/catalog.js.map +1 -1
  24. package/node_modules/@nklisch/pi-plugins/dist/host.js +85 -29
  25. package/node_modules/@nklisch/pi-plugins/dist/host.js.map +1 -1
  26. package/node_modules/@nklisch/pi-plugins/dist/pi/plugin-manager-layout.d.ts +4 -0
  27. package/node_modules/@nklisch/pi-plugins/dist/pi/plugin-manager-layout.js +24 -0
  28. package/node_modules/@nklisch/pi-plugins/dist/pi/plugin-manager-layout.js.map +1 -0
  29. package/node_modules/@nklisch/pi-plugins/dist/pi/plugin-manager.d.ts +6 -0
  30. package/node_modules/@nklisch/pi-plugins/dist/pi/plugin-manager.js +130 -47
  31. package/node_modules/@nklisch/pi-plugins/dist/pi/plugin-manager.js.map +1 -1
  32. package/node_modules/@nklisch/pi-plugins/dist/plugin-metadata.d.ts +9 -0
  33. package/node_modules/@nklisch/pi-plugins/dist/plugin-metadata.js +37 -0
  34. package/node_modules/@nklisch/pi-plugins/dist/plugin-metadata.js.map +1 -0
  35. package/node_modules/@nklisch/pi-plugins/dist/runtime-discovery.js +2 -0
  36. package/node_modules/@nklisch/pi-plugins/dist/runtime-discovery.js.map +1 -1
  37. package/node_modules/@nklisch/pi-plugins/dist/types.d.ts +4 -1
  38. package/node_modules/@nklisch/pi-plugins/dist/types.js.map +1 -1
  39. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/CHANGELOG.md +8 -0
  40. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/README.md +2 -1
  41. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/architecture/architecture.md +12 -5
  42. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/package.json +1 -1
  43. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/session/query.ts +229 -68
  44. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/query-session-tool.ts +115 -33
  45. package/node_modules/@nklisch/pi-plugins/package.json +2 -2
  46. package/package.json +4 -1
@@ -19,7 +19,7 @@ const MAX_QUERY_OUTPUT_LINES = 2_000;
19
19
  // Leave room for the summary, source path, omission tail, and a final line/
20
20
  // byte boundary. The entry budget is intentionally smaller than Pi's limits;
21
21
  // entries are additionally checked against the actual fixed text below.
22
- const MAX_QUERY_ENTRY_BYTES = 40 * 1024;
22
+ const MAX_QUERY_ENTRY_BYTES = 39 * 1024;
23
23
  const MAX_QUERY_ENTRY_LINES = 1_800;
24
24
  const MAX_QUERY_METADATA_LINE_BYTES = 2_048;
25
25
  const MAX_QUERY_TRANSCRIPT_PATH_BYTES = 1_024;
@@ -27,7 +27,7 @@ const utf8Encoder = new TextEncoder();
27
27
  const VALID_KINDS = new Set<SessionQueryKind>(["all", "messages", "tool_calls", "tool_results"]);
28
28
  const VALID_ORDERS = new Set<SessionQueryOrder>(["newest", "oldest"]);
29
29
 
30
- export type QuerySessionOutcome = "matches" | "no_matches" | "transcript_unavailable" | "not_found" | "read_error";
30
+ export type QuerySessionOutcome = "matches" | "no_matches" | "page_out_of_range" | "transcript_unavailable" | "not_found" | "read_error";
31
31
 
32
32
  export interface QuerySessionDetails {
33
33
  readonly outcome: QuerySessionOutcome;
@@ -46,10 +46,17 @@ export interface QuerySessionDetails {
46
46
  readonly kind?: SessionQueryKind;
47
47
  readonly order?: SessionQueryOrder;
48
48
  readonly limit?: number;
49
+ readonly offset?: number;
49
50
  readonly entries?: readonly SessionQueryEntry[];
50
51
  readonly totalMatches?: number;
52
+ readonly returnedCount?: number;
53
+ readonly nextOffset?: number;
54
+ readonly previousOffset?: number;
55
+ readonly omittedBefore?: number;
56
+ readonly omittedAfter?: number;
51
57
  readonly hasMore?: boolean;
52
- readonly omittedCount?: number;
58
+ /** True means every searchable field was examined, not just a preview prefix. */
59
+ readonly searchComplete?: boolean;
53
60
  readonly truncation?: {
54
61
  readonly output: boolean;
55
62
  readonly omittedMatches: number;
@@ -79,6 +86,7 @@ export class QuerySessionTool {
79
86
  kind?: SessionQueryKind;
80
87
  order?: SessionQueryOrder;
81
88
  limit?: number;
89
+ offset?: number;
82
90
  },
83
91
  _signal: AbortSignal | undefined,
84
92
  _onUpdate: unknown,
@@ -131,18 +139,26 @@ export class QuerySessionTool {
131
139
  kind: params.kind,
132
140
  order: params.order,
133
141
  limit: params.limit,
142
+ offset: params.offset,
134
143
  });
135
144
  const summary = boundedOutputLine(summaryLine(base), MAX_QUERY_METADATA_LINE_BYTES);
136
145
  const sourceDescription = boundedOutputLine(
137
146
  `${source}${record.outputFile ? ` (${record.outputFile})` : ""}`,
138
147
  MAX_QUERY_METADATA_LINE_BYTES,
139
148
  );
140
- const prefix = `${summary}\nSource: ${sourceDescription}\n`;
141
- // Reserve the largest possible omission tail before selecting entries. This
142
- // prevents a later bounds notice from pushing an otherwise-fitting body
143
- // over Pi's byte or line ceiling.
144
- const maximumOmittedCount = result.omittedCount + result.entries.length;
145
- const reservedTail = omissionTail(maximumOmittedCount, record.outputFile);
149
+ const prefix = `${summary}\nSource: ${sourceDescription}\nSearch: complete (full transcript fields).\n`;
150
+ // Reserve the largest possible omission/navigation tail before selecting
151
+ // entries. This keeps a later bounds notice from pushing the body over
152
+ // Pi's byte or line ceiling.
153
+ const maximumOmittedCount = result.omittedBefore + result.omittedAfter + result.entries.length;
154
+ const reservedTail = omissionTail(
155
+ maximumOmittedCount,
156
+ maximumOmittedCount,
157
+ maximumOmittedCount,
158
+ result.nextOffset,
159
+ result.previousOffset,
160
+ record.outputFile,
161
+ );
146
162
  const output = formatEntries(
147
163
  result.entries,
148
164
  Math.min(
@@ -154,8 +170,16 @@ export class QuerySessionTool {
154
170
  Math.max(0, MAX_QUERY_OUTPUT_LINES - lineCount(prefix) - lineCount(reservedTail) - 1),
155
171
  ),
156
172
  );
157
- const omittedCount = result.omittedCount + output.omittedCount;
158
- const outcome: QuerySessionOutcome = result.entries.length > 0 ? "matches" : "no_matches";
173
+ const returnedCount = output.entries.length;
174
+ const omittedBefore = result.omittedBefore;
175
+ const omittedAfter = Math.max(0, result.totalMatches - result.offset - returnedCount);
176
+ // Recompute this from entries that really made it into the output. If the
177
+ // byte/line budget drops entries from a page, the next request starts after
178
+ // only those entries and cannot skip an unseen match.
179
+ const nextOffset = result.outcome === "matches" && returnedCount > 0 && result.offset + returnedCount < result.totalMatches
180
+ ? result.offset + returnedCount
181
+ : undefined;
182
+ const outcome = result.outcome;
159
183
  const details: QuerySessionDetails = {
160
184
  ...base,
161
185
  outcome,
@@ -165,22 +189,37 @@ export class QuerySessionTool {
165
189
  kind: params.kind ?? "all",
166
190
  order: params.order ?? "newest",
167
191
  limit: params.limit ?? DEFAULT_QUERY_LIMIT,
192
+ offset: result.offset,
168
193
  entries: output.entries,
169
194
  totalMatches: result.totalMatches,
170
- hasMore: omittedCount > 0,
171
- omittedCount,
172
- ...(omittedCount > 0 ? {
195
+ returnedCount,
196
+ ...(nextOffset === undefined ? {} : { nextOffset }),
197
+ ...(result.previousOffset === undefined ? {} : { previousOffset: result.previousOffset }),
198
+ omittedBefore,
199
+ omittedAfter,
200
+ hasMore: nextOffset !== undefined,
201
+ searchComplete: true,
202
+ ...(output.omittedCount > 0 ? {
173
203
  truncation: {
174
- output: output.omittedCount > 0,
175
- omittedMatches: omittedCount,
204
+ output: true,
205
+ omittedMatches: output.omittedCount,
176
206
  ...(record.outputFile ? { transcriptPath: record.outputFile } : {}),
177
207
  },
178
208
  } : {}),
179
209
  };
180
- const body = result.entries.length === 0
210
+ const body = outcome === "no_matches"
181
211
  ? "No transcript entries match the requested query."
182
- : output.lines.join("\n");
183
- const tail = omissionTail(omittedCount, record.outputFile);
212
+ : outcome === "page_out_of_range"
213
+ ? `No transcript page starts at offset ${result.offset}; ${result.totalMatches} matching entr${result.totalMatches === 1 ? "y is" : "ies are"} available.`
214
+ : output.lines.join("\n");
215
+ const tail = omissionTail(
216
+ omittedBefore,
217
+ omittedAfter,
218
+ output.omittedCount,
219
+ nextOffset,
220
+ result.previousOffset,
221
+ record.outputFile,
222
+ );
184
223
  return textResult(`${prefix}${body}${tail}`, details);
185
224
  }
186
225
 
@@ -189,10 +228,10 @@ export class QuerySessionTool {
189
228
  name: "query_subagent_session" as const,
190
229
  label: "Query Subagent Session",
191
230
  promptSnippet: "query_subagent_session: Search a bounded child transcript without steering it.",
192
- description: "Query a subagent's recent or matching messages and tool calls. Search is case-insensitive literal text, bounded, and read-only; use get_subagent_result for the final result and list_subagents for fleet triage.",
231
+ description: "Query a subagent's recent or matching messages and tool calls. Search is complete, case-insensitive literal text over full fields, bounded only in returned excerpts/output, and read-only; use get_subagent_result for the final result and list_subagents for fleet triage.",
193
232
  parameters: Type.Object({
194
233
  agent_id: Type.String({ description: "The subagent ID to inspect." }),
195
- query: Type.Optional(Type.String({ description: "Case-insensitive literal text. Omit or use an empty string for recent entries." })),
234
+ query: Type.Optional(Type.String({ description: "Case-insensitive literal text. Omit or use an empty string for beginning-of-entry previews." })),
196
235
  kind: Type.Optional(Type.Union([
197
236
  Type.Literal("all"),
198
237
  Type.Literal("messages"),
@@ -204,6 +243,7 @@ export class QuerySessionTool {
204
243
  Type.Literal("oldest"),
205
244
  ], { description: "Result order. Defaults to newest." })),
206
245
  limit: Type.Optional(Type.Integer({ minimum: MIN_QUERY_LIMIT, maximum: MAX_QUERY_LIMIT, description: "Maximum entries, 1-50; defaults to 20." })),
246
+ offset: Type.Optional(Type.Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER, description: "Non-negative safe integer offset into the ordered matching entries; defaults to 0." })),
207
247
  }),
208
248
  execute: (toolCallId: string, params: {
209
249
  agent_id?: string;
@@ -211,6 +251,7 @@ export class QuerySessionTool {
211
251
  kind?: SessionQueryKind;
212
252
  order?: SessionQueryOrder;
213
253
  limit?: number;
254
+ offset?: number;
214
255
  }, signal: AbortSignal | undefined, onUpdate: unknown, ctx: unknown) => this.execute(toolCallId, params, signal, onUpdate, ctx),
215
256
  });
216
257
  }
@@ -222,6 +263,7 @@ function validateParams(params: {
222
263
  kind?: SessionQueryKind;
223
264
  order?: SessionQueryOrder;
224
265
  limit?: number;
266
+ offset?: number;
225
267
  }): string | undefined {
226
268
  if (typeof params.agent_id !== "string" || params.agent_id.length === 0) return "agent_id must be a non-empty string";
227
269
  if (params.query !== undefined && typeof params.query !== "string") return "query must be a string";
@@ -230,6 +272,8 @@ function validateParams(params: {
230
272
  if (params.limit !== undefined && (!Number.isInteger(params.limit) || params.limit < MIN_QUERY_LIMIT || params.limit > MAX_QUERY_LIMIT)) {
231
273
  return `limit must be an integer from ${MIN_QUERY_LIMIT} to ${MAX_QUERY_LIMIT}`;
232
274
  }
275
+ if (params.offset !== undefined && !Number.isSafeInteger(params.offset)) return "offset must be a non-negative safe integer";
276
+ if (params.offset !== undefined && params.offset < 0) return "offset must be a non-negative safe integer";
233
277
  return undefined;
234
278
  }
235
279
 
@@ -277,7 +321,25 @@ function formatEntries(
277
321
  const addition = selected.length === 0 ? line : `\n${line}`;
278
322
  const additionBytes = utf8ByteLength(addition);
279
323
  const additionLines = lineCount(addition);
280
- if (bytes + additionBytes > maxBytes || lineTotal + additionLines > maxLines) break;
324
+ if (bytes + additionBytes > maxBytes || lineTotal + additionLines > maxLines) {
325
+ // A newline-dense first entry can exceed the line budget even though its
326
+ // projection is character-bounded. Compact only this fallback
327
+ // representation so a real match can always make progress; later pages
328
+ // still use the normal output bounds and the returned count remains exact.
329
+ if (selected.length === 0) {
330
+ const compactLine = formatEntry(entry, true);
331
+ const compactAddition = compactLine;
332
+ const compactBytes = utf8ByteLength(compactAddition);
333
+ const compactLines = lineCount(compactAddition);
334
+ if (compactBytes <= maxBytes && compactLines <= maxLines) {
335
+ selected.push(entry);
336
+ lines.push(compactLine);
337
+ bytes = compactBytes;
338
+ lineTotal = compactLines;
339
+ }
340
+ }
341
+ break;
342
+ }
281
343
  selected.push(entry);
282
344
  lines.push(line);
283
345
  bytes += additionBytes;
@@ -286,13 +348,21 @@ function formatEntries(
286
348
  return { entries: selected, lines, omittedCount: entries.length - selected.length };
287
349
  }
288
350
 
289
- function formatEntry(entry: SessionQueryEntry): string {
351
+ function formatEntry(entry: SessionQueryEntry, compact = false): string {
352
+ const display = (value: string): string => compact ? compactLineBreaks(value) : value;
353
+ const match = entry.match === undefined
354
+ ? ""
355
+ : ` · match=${entry.match.field} [${entry.match.sourceRange.start},${entry.match.sourceRange.end})`;
290
356
  if (entry.kind === "message") {
291
- return `[${entry.role}] ${entry.id}\n${entry.text}`;
357
+ return `[${entry.role}] ${entry.id}${match}\n${display(entry.text)}`;
292
358
  }
293
- const callId = entry.toolCallId ? ` id=${entry.toolCallId}` : "";
294
- const result = entry.result === undefined ? "" : `\nresult: ${entry.result}`;
295
- return `[tool ${entry.toolName}${callId} · ${entry.state}] ${entry.id}\narguments: ${entry.arguments}${result}`;
359
+ const callIdentity = entry.toolCallId ?? entry.id;
360
+ const result = entry.result === undefined ? "" : `\nresult: ${display(entry.result)}`;
361
+ return `[tool ${display(entry.toolName)} · ${entry.state}] id=${display(callIdentity)}${match}\narguments: ${display(entry.arguments)}${result}`;
362
+ }
363
+
364
+ function compactLineBreaks(value: string): string {
365
+ return value.replace(/\r\n|\r|\n/g, " ");
296
366
  }
297
367
 
298
368
  function utf8ByteLength(value: string): number {
@@ -321,10 +391,22 @@ function boundedOutputLine(value: string, maxBytes: number): string {
321
391
  return `${output}${ellipsis}`;
322
392
  }
323
393
 
324
- function omissionTail(omittedCount: number, transcriptPath: string | undefined): string {
325
- if (omittedCount <= 0) return "";
326
- const path = transcriptPath
327
- ? ` Full transcript: ${boundedOutputLine(transcriptPath, MAX_QUERY_TRANSCRIPT_PATH_BYTES)}`
328
- : "";
329
- return `\n\n${omittedCount} matching entr${omittedCount === 1 ? "y was" : "ies were"} omitted by the bounds.${path}`;
394
+ function omissionTail(
395
+ omittedBefore: number,
396
+ omittedAfter: number,
397
+ outputOmitted: number,
398
+ nextOffset: number | undefined,
399
+ previousOffset: number | undefined,
400
+ transcriptPath: string | undefined,
401
+ ): string {
402
+ const lines: string[] = [];
403
+ if (omittedBefore > 0) lines.push(`${omittedBefore} matching entr${omittedBefore === 1 ? "y precedes" : "ies precede"} this page.`);
404
+ if (omittedAfter > 0) lines.push(`${omittedAfter} matching entr${omittedAfter === 1 ? "y remains" : "ies remain"} after this page.`);
405
+ if (outputOmitted > 0) lines.push(`${outputOmitted} entries from this page were omitted by the output bounds.`);
406
+ if (nextOffset !== undefined) lines.push(`More matches available; repeat with offset: ${nextOffset}.`);
407
+ if (previousOffset !== undefined) lines.push(`Previous page available; repeat with offset: ${previousOffset}.`);
408
+ if (transcriptPath && outputOmitted > 0) {
409
+ lines.push(`Full transcript: ${boundedOutputLine(transcriptPath, MAX_QUERY_TRANSCRIPT_PATH_BYTES)}`);
410
+ }
411
+ return lines.length === 0 ? "" : `\n\n${lines.join("\n")}`;
330
412
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nklisch/pi-plugins",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "private": false,
5
5
  "description": "A small filesystem-first marketplace and plugin host for Pi.",
6
6
  "license": "MIT",
@@ -63,7 +63,7 @@
63
63
  ],
64
64
  "dependencies": {
65
65
  "@nklisch/pi-mcp-adapter": "2.21.0-nklisch.2",
66
- "@nklisch/pi-subagents": "18.2.0-nklisch.0",
66
+ "@nklisch/pi-subagents": "18.2.0-nklisch.1",
67
67
  "jiti": "2.7.0"
68
68
  },
69
69
  "devDependencies": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nklisch/pi-enhanced",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
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"
@@ -29,6 +29,7 @@
29
29
  "pi": {
30
30
  "extensions": [
31
31
  "./node_modules/@nklisch/pi-conveniences/extensions",
32
+ "./node_modules/@nklisch/pi-astral-pocket/src/index.ts",
32
33
  "./node_modules/@nklisch/pi-fff-compat/extensions/fff-compat-search.ts",
33
34
  "./node_modules/@nklisch/pi-model-modes/extensions/index.ts",
34
35
  "./node_modules/@nklisch/pi-background-tasks/extensions/background-tasks.ts",
@@ -66,6 +67,7 @@
66
67
  "@juicesharp/rpiv-todo": "^2.1.0",
67
68
  "@mozilla/readability": "^0.6.0",
68
69
  "@narumitw/pi-usage": "^0.28.0",
70
+ "@nklisch/pi-astral-pocket": "^0",
69
71
  "@nklisch/pi-background-tasks": "^0",
70
72
  "@nklisch/pi-clearance": "^0",
71
73
  "@nklisch/pi-conveniences": "^0",
@@ -93,6 +95,7 @@
93
95
  "@juicesharp/rpiv-ask-user-question",
94
96
  "@juicesharp/rpiv-todo",
95
97
  "@narumitw/pi-usage",
98
+ "@nklisch/pi-astral-pocket",
96
99
  "@nklisch/pi-background-tasks",
97
100
  "@nklisch/pi-clearance",
98
101
  "@nklisch/pi-conveniences",