@caupulican/pi-adaptative 0.78.2 → 0.78.3

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 (101) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +14 -12
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +1 -1
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/core/agent-session.d.ts +1 -0
  7. package/dist/core/agent-session.d.ts.map +1 -1
  8. package/dist/core/agent-session.js +34 -12
  9. package/dist/core/agent-session.js.map +1 -1
  10. package/dist/core/extensions/types.d.ts +16 -1
  11. package/dist/core/extensions/types.d.ts.map +1 -1
  12. package/dist/core/extensions/types.js.map +1 -1
  13. package/dist/core/model-registry.d.ts.map +1 -1
  14. package/dist/core/model-registry.js +10 -0
  15. package/dist/core/model-registry.js.map +1 -1
  16. package/dist/core/model-resolver.d.ts.map +1 -1
  17. package/dist/core/model-resolver.js +1 -0
  18. package/dist/core/model-resolver.js.map +1 -1
  19. package/dist/core/prompt-templates.d.ts +3 -2
  20. package/dist/core/prompt-templates.d.ts.map +1 -1
  21. package/dist/core/prompt-templates.js +8 -3
  22. package/dist/core/prompt-templates.js.map +1 -1
  23. package/dist/core/provider-display-names.d.ts.map +1 -1
  24. package/dist/core/provider-display-names.js +1 -0
  25. package/dist/core/provider-display-names.js.map +1 -1
  26. package/dist/core/resource-loader.d.ts +5 -5
  27. package/dist/core/resource-loader.d.ts.map +1 -1
  28. package/dist/core/resource-loader.js +1 -9
  29. package/dist/core/resource-loader.js.map +1 -1
  30. package/dist/core/session-manager.d.ts.map +1 -1
  31. package/dist/core/session-manager.js +169 -80
  32. package/dist/core/session-manager.js.map +1 -1
  33. package/dist/core/settings-manager.d.ts +23 -0
  34. package/dist/core/settings-manager.d.ts.map +1 -1
  35. package/dist/core/settings-manager.js +19 -0
  36. package/dist/core/settings-manager.js.map +1 -1
  37. package/dist/core/skills.d.ts.map +1 -1
  38. package/dist/core/skills.js +3 -7
  39. package/dist/core/skills.js.map +1 -1
  40. package/dist/core/slash-commands.d.ts.map +1 -1
  41. package/dist/core/slash-commands.js +6 -3
  42. package/dist/core/slash-commands.js.map +1 -1
  43. package/dist/core/system-prompt.d.ts +3 -3
  44. package/dist/core/system-prompt.d.ts.map +1 -1
  45. package/dist/core/system-prompt.js +27 -18
  46. package/dist/core/system-prompt.js.map +1 -1
  47. package/dist/core/tools/find.d.ts.map +1 -1
  48. package/dist/core/tools/find.js +4 -3
  49. package/dist/core/tools/find.js.map +1 -1
  50. package/dist/core/tools/grep.d.ts.map +1 -1
  51. package/dist/core/tools/grep.js +4 -3
  52. package/dist/core/tools/grep.js.map +1 -1
  53. package/dist/core/tools/ls.d.ts.map +1 -1
  54. package/dist/core/tools/ls.js +1 -0
  55. package/dist/core/tools/ls.js.map +1 -1
  56. package/dist/core/tools/render-utils.d.ts +1 -1
  57. package/dist/core/tools/render-utils.d.ts.map +1 -1
  58. package/dist/core/tools/render-utils.js +29 -4
  59. package/dist/core/tools/render-utils.js.map +1 -1
  60. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  61. package/dist/modes/interactive/components/footer.js +2 -2
  62. package/dist/modes/interactive/components/footer.js.map +1 -1
  63. package/dist/modes/interactive/components/index.d.ts +2 -0
  64. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  65. package/dist/modes/interactive/components/index.js +2 -0
  66. package/dist/modes/interactive/components/index.js.map +1 -1
  67. package/dist/modes/interactive/components/tool-execution.d.ts +7 -0
  68. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  69. package/dist/modes/interactive/components/tool-execution.js +66 -8
  70. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  71. package/dist/modes/interactive/components/tool-group.d.ts +17 -0
  72. package/dist/modes/interactive/components/tool-group.d.ts.map +1 -0
  73. package/dist/modes/interactive/components/tool-group.js +63 -0
  74. package/dist/modes/interactive/components/tool-group.js.map +1 -0
  75. package/dist/modes/interactive/components/tool-panel-registry.d.ts +23 -0
  76. package/dist/modes/interactive/components/tool-panel-registry.d.ts.map +1 -0
  77. package/dist/modes/interactive/components/tool-panel-registry.js +70 -0
  78. package/dist/modes/interactive/components/tool-panel-registry.js.map +1 -0
  79. package/dist/modes/interactive/interactive-mode.d.ts +6 -1
  80. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  81. package/dist/modes/interactive/interactive-mode.js +119 -60
  82. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  83. package/dist/utils/paths.d.ts.map +1 -1
  84. package/dist/utils/paths.js +4 -1
  85. package/dist/utils/paths.js.map +1 -1
  86. package/docs/extensions.md +6 -3
  87. package/docs/quickstart.md +3 -3
  88. package/docs/sdk.md +1 -1
  89. package/docs/settings.md +50 -0
  90. package/docs/skills.md +3 -3
  91. package/docs/usage.md +3 -3
  92. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  93. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  94. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  95. package/examples/extensions/sandbox/package-lock.json +2 -2
  96. package/examples/extensions/sandbox/package.json +1 -1
  97. package/examples/extensions/with-deps/package-lock.json +2 -2
  98. package/examples/extensions/with-deps/package.json +1 -1
  99. package/examples/sdk/07-context-files.ts +3 -14
  100. package/npm-shrinkwrap.json +388 -2704
  101. package/package.json +5 -5
@@ -1,8 +1,10 @@
1
1
  import { uuidv7 } from "@earendil-works/pi-agent-core";
2
2
  import { randomUUID } from "crypto";
3
- import { appendFileSync, closeSync, existsSync, mkdirSync, openSync, readdirSync, readFileSync, readSync, statSync, writeFileSync, } from "fs";
4
- import { readdir, readFile, stat } from "fs/promises";
3
+ import { appendFileSync, closeSync, createReadStream, existsSync, mkdirSync, openSync, readdirSync, readSync, statSync, writeFileSync, } from "fs";
4
+ import { readdir, stat } from "fs/promises";
5
5
  import { join, resolve } from "path";
6
+ import { createInterface } from "readline";
7
+ import { StringDecoder } from "string_decoder";
6
8
  import { getAgentDir as getDefaultAgentDir, getSessionsDir } from "../config.js";
7
9
  import { normalizePath, resolvePath } from "../utils/paths.js";
8
10
  import { createBranchSummaryMessage, createCompactionSummaryMessage, createCustomMessage, } from "./messages.js";
@@ -215,12 +217,24 @@ export function buildSessionContext(entries, leafId, byId) {
215
217
  * Compute the default session directory for a cwd.
216
218
  * Encodes cwd into a safe directory name under ~/.pi/agent/sessions/.
217
219
  */
218
- function getDefaultSessionDirPath(cwd, agentDir = getDefaultAgentDir()) {
220
+ function getEncodedSessionDirName(cwd) {
221
+ return `--${encodeURIComponent(resolvePath(cwd))}--`;
222
+ }
223
+ function getLegacySessionDirPath(cwd, agentDir = getDefaultAgentDir()) {
219
224
  const resolvedCwd = resolvePath(cwd);
220
225
  const resolvedAgentDir = resolvePath(agentDir);
221
226
  const safePath = `--${resolvedCwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
222
227
  return join(resolvedAgentDir, "sessions", safePath);
223
228
  }
229
+ function getDefaultSessionDirPath(cwd, agentDir = getDefaultAgentDir()) {
230
+ const resolvedAgentDir = resolvePath(agentDir);
231
+ return join(resolvedAgentDir, "sessions", getEncodedSessionDirName(cwd));
232
+ }
233
+ function getDefaultSessionDirCandidates(cwd, agentDir = getDefaultAgentDir()) {
234
+ const current = getDefaultSessionDirPath(cwd, agentDir);
235
+ const legacy = getLegacySessionDirPath(cwd, agentDir);
236
+ return current === legacy ? [current] : [current, legacy];
237
+ }
224
238
  export function getDefaultSessionDir(cwd, agentDir = getDefaultAgentDir()) {
225
239
  const sessionDir = getDefaultSessionDirPath(cwd, agentDir);
226
240
  if (!existsSync(sessionDir)) {
@@ -228,24 +242,52 @@ export function getDefaultSessionDir(cwd, agentDir = getDefaultAgentDir()) {
228
242
  }
229
243
  return sessionDir;
230
244
  }
245
+ const SESSION_READ_BUFFER_SIZE = 1024 * 1024;
246
+ function parseSessionEntryLine(line) {
247
+ if (!line.trim())
248
+ return null;
249
+ try {
250
+ return JSON.parse(line);
251
+ }
252
+ catch {
253
+ // Skip malformed lines
254
+ return null;
255
+ }
256
+ }
231
257
  /** Exported for testing */
232
258
  export function loadEntriesFromFile(filePath) {
233
259
  const resolvedFilePath = normalizePath(filePath);
234
260
  if (!existsSync(resolvedFilePath))
235
261
  return [];
236
- const content = readFileSync(resolvedFilePath, "utf8");
237
262
  const entries = [];
238
- const lines = content.trim().split("\n");
239
- for (const line of lines) {
240
- if (!line.trim())
241
- continue;
242
- try {
243
- const entry = JSON.parse(line);
244
- entries.push(entry);
245
- }
246
- catch {
247
- // Skip malformed lines
263
+ const fd = openSync(resolvedFilePath, "r");
264
+ try {
265
+ const decoder = new StringDecoder("utf8");
266
+ const buffer = Buffer.allocUnsafe(SESSION_READ_BUFFER_SIZE);
267
+ let pending = "";
268
+ while (true) {
269
+ const bytesRead = readSync(fd, buffer, 0, buffer.length, null);
270
+ if (bytesRead === 0)
271
+ break;
272
+ pending += decoder.write(buffer.subarray(0, bytesRead));
273
+ let lineStart = 0;
274
+ let newlineIndex = pending.indexOf("\n", lineStart);
275
+ while (newlineIndex !== -1) {
276
+ const entry = parseSessionEntryLine(pending.slice(lineStart, newlineIndex));
277
+ if (entry)
278
+ entries.push(entry);
279
+ lineStart = newlineIndex + 1;
280
+ newlineIndex = pending.indexOf("\n", lineStart);
281
+ }
282
+ pending = pending.slice(lineStart);
248
283
  }
284
+ pending += decoder.end();
285
+ const finalEntry = parseSessionEntryLine(pending);
286
+ if (finalEntry)
287
+ entries.push(finalEntry);
288
+ }
289
+ finally {
290
+ closeSync(fd);
249
291
  }
250
292
  // Validate session header
251
293
  if (entries.length === 0)
@@ -301,6 +343,24 @@ export function findMostRecentSession(sessionDir, cwd) {
301
343
  return null;
302
344
  }
303
345
  }
346
+ function findMostRecentSessionInDirs(sessionDirs, cwd) {
347
+ let mostRecent = null;
348
+ for (const dir of sessionDirs) {
349
+ const sessionPath = findMostRecentSession(dir, cwd);
350
+ if (!sessionPath)
351
+ continue;
352
+ try {
353
+ const mtime = statSync(sessionPath).mtime.getTime();
354
+ if (!mostRecent || mtime > mostRecent.mtime) {
355
+ mostRecent = { path: sessionPath, mtime };
356
+ }
357
+ }
358
+ catch {
359
+ // Ignore races/deleted files
360
+ }
361
+ }
362
+ return mostRecent?.path ?? null;
363
+ }
304
364
  function isMessageWithContent(message) {
305
365
  return typeof message.role === "string" && "content" in message;
306
366
  }
@@ -314,73 +374,64 @@ function extractTextContent(message) {
314
374
  .map((block) => block.text)
315
375
  .join(" ");
316
376
  }
317
- function getLastActivityTime(entries) {
318
- let lastActivityTime;
319
- for (const entry of entries) {
320
- if (entry.type !== "message")
321
- continue;
322
- const message = entry.message;
323
- if (!isMessageWithContent(message))
324
- continue;
325
- if (message.role !== "user" && message.role !== "assistant")
326
- continue;
327
- const msgTimestamp = message.timestamp;
328
- if (typeof msgTimestamp === "number") {
329
- lastActivityTime = Math.max(lastActivityTime ?? 0, msgTimestamp);
330
- continue;
331
- }
332
- const entryTimestamp = entry.timestamp;
333
- if (typeof entryTimestamp === "string") {
334
- const t = new Date(entryTimestamp).getTime();
335
- if (!Number.isNaN(t)) {
336
- lastActivityTime = Math.max(lastActivityTime ?? 0, t);
337
- }
338
- }
339
- }
340
- return lastActivityTime;
341
- }
342
- function getSessionModifiedDate(entries, header, statsMtime) {
343
- const lastActivityTime = getLastActivityTime(entries);
344
- if (typeof lastActivityTime === "number" && lastActivityTime > 0) {
345
- return new Date(lastActivityTime);
377
+ const MAX_SESSION_SEARCH_TEXT_CHARS = 20_000;
378
+ function getMessageActivityTime(entry) {
379
+ const message = entry.message;
380
+ if (!isMessageWithContent(message))
381
+ return undefined;
382
+ if (message.role !== "user" && message.role !== "assistant")
383
+ return undefined;
384
+ const msgTimestamp = message.timestamp;
385
+ if (typeof msgTimestamp === "number") {
386
+ return msgTimestamp;
346
387
  }
347
- const headerTime = typeof header.timestamp === "string" ? new Date(header.timestamp).getTime() : NaN;
348
- return !Number.isNaN(headerTime) ? new Date(headerTime) : statsMtime;
388
+ const t = new Date(entry.timestamp).getTime();
389
+ return Number.isNaN(t) ? undefined : t;
349
390
  }
350
391
  async function buildSessionInfo(filePath) {
351
392
  try {
352
- const content = await readFile(filePath, "utf8");
353
- const entries = [];
354
- const lines = content.trim().split("\n");
355
- for (const line of lines) {
356
- if (!line.trim())
357
- continue;
358
- try {
359
- entries.push(JSON.parse(line));
360
- }
361
- catch {
362
- // Skip malformed lines
363
- }
364
- }
365
- if (entries.length === 0)
366
- return null;
367
- const header = entries[0];
368
- if (header.type !== "session")
369
- return null;
370
393
  const stats = await stat(filePath);
394
+ let header = null;
371
395
  let messageCount = 0;
372
396
  let firstMessage = "";
373
- const allMessages = [];
397
+ let allMessagesText = "";
374
398
  let name;
375
- for (const entry of entries) {
399
+ let lastActivityTime;
400
+ const appendSearchText = (text) => {
401
+ if (!text || allMessagesText.length >= MAX_SESSION_SEARCH_TEXT_CHARS)
402
+ return;
403
+ const prefix = allMessagesText.length === 0 ? "" : " ";
404
+ const remaining = MAX_SESSION_SEARCH_TEXT_CHARS - allMessagesText.length - prefix.length;
405
+ if (remaining <= 0)
406
+ return;
407
+ allMessagesText += `${prefix}${text.slice(0, remaining)}`;
408
+ };
409
+ const rl = createInterface({
410
+ input: createReadStream(filePath, { encoding: "utf8" }),
411
+ crlfDelay: Infinity,
412
+ });
413
+ for await (const line of rl) {
414
+ const entry = parseSessionEntryLine(line);
415
+ if (!entry)
416
+ continue;
417
+ if (!header) {
418
+ if (entry.type !== "session")
419
+ return null;
420
+ header = entry;
421
+ continue;
422
+ }
376
423
  // Extract session name (use latest, including explicit clears)
377
424
  if (entry.type === "session_info") {
378
- const infoEntry = entry;
379
- name = infoEntry.name?.trim() || undefined;
425
+ name = entry.name?.trim() || undefined;
426
+ continue;
380
427
  }
381
428
  if (entry.type !== "message")
382
429
  continue;
383
430
  messageCount++;
431
+ const activityTime = getMessageActivityTime(entry);
432
+ if (typeof activityTime === "number") {
433
+ lastActivityTime = Math.max(lastActivityTime ?? 0, activityTime);
434
+ }
384
435
  const message = entry.message;
385
436
  if (!isMessageWithContent(message))
386
437
  continue;
@@ -389,14 +440,21 @@ async function buildSessionInfo(filePath) {
389
440
  const textContent = extractTextContent(message);
390
441
  if (!textContent)
391
442
  continue;
392
- allMessages.push(textContent);
443
+ appendSearchText(textContent);
393
444
  if (!firstMessage && message.role === "user") {
394
445
  firstMessage = textContent;
395
446
  }
396
447
  }
448
+ if (!header)
449
+ return null;
397
450
  const cwd = typeof header.cwd === "string" ? header.cwd : "";
398
451
  const parentSessionPath = header.parentSession;
399
- const modified = getSessionModifiedDate(entries, header, stats.mtime);
452
+ const headerTime = typeof header.timestamp === "string" ? new Date(header.timestamp).getTime() : NaN;
453
+ const modified = typeof lastActivityTime === "number" && lastActivityTime > 0
454
+ ? new Date(lastActivityTime)
455
+ : !Number.isNaN(headerTime)
456
+ ? new Date(headerTime)
457
+ : stats.mtime;
400
458
  return {
401
459
  path: filePath,
402
460
  id: header.id,
@@ -407,7 +465,7 @@ async function buildSessionInfo(filePath) {
407
465
  modified,
408
466
  messageCount,
409
467
  firstMessage: firstMessage || "(no messages)",
410
- allMessagesText: allMessages.join(" "),
468
+ allMessagesText,
411
469
  };
412
470
  }
413
471
  catch {
@@ -448,6 +506,17 @@ async function buildSessionInfosWithConcurrency(files, onLoaded) {
448
506
  }
449
507
  return results;
450
508
  }
509
+ async function countSessionFiles(dir) {
510
+ if (!existsSync(dir))
511
+ return 0;
512
+ try {
513
+ const dirEntries = await readdir(dir);
514
+ return dirEntries.filter((f) => f.endsWith(".jsonl")).length;
515
+ }
516
+ catch {
517
+ return 0;
518
+ }
519
+ }
451
520
  async function listSessionsFromDir(dir, onProgress, progressOffset = 0, progressTotal) {
452
521
  const sessions = [];
453
522
  if (!existsSync(dir)) {
@@ -589,8 +658,15 @@ export class SessionManager {
589
658
  _rewriteFile() {
590
659
  if (!this.persist || !this.sessionFile)
591
660
  return;
592
- const content = `${this.fileEntries.map((e) => JSON.stringify(e)).join("\n")}\n`;
593
- writeFileSync(this.sessionFile, content);
661
+ const fd = openSync(this.sessionFile, "w");
662
+ try {
663
+ for (const entry of this.fileEntries) {
664
+ writeFileSync(fd, `${JSON.stringify(entry)}\n`);
665
+ }
666
+ }
667
+ finally {
668
+ closeSync(fd);
669
+ }
594
670
  }
595
671
  isPersisted() {
596
672
  return this.persist;
@@ -602,7 +678,7 @@ export class SessionManager {
602
678
  return this.sessionDir;
603
679
  }
604
680
  usesDefaultSessionDir() {
605
- return this.sessionDir === getDefaultSessionDirPath(this.cwd);
681
+ return getDefaultSessionDirCandidates(this.cwd).includes(this.sessionDir);
606
682
  }
607
683
  getSessionId() {
608
684
  return this.sessionId;
@@ -1069,10 +1145,12 @@ export class SessionManager {
1069
1145
  */
1070
1146
  static continueRecent(cwd, sessionDir) {
1071
1147
  const dir = sessionDir ? normalizePath(sessionDir) : getDefaultSessionDir(cwd);
1072
- const filterCwd = sessionDir !== undefined && dir !== getDefaultSessionDirPath(cwd);
1073
- const mostRecent = findMostRecentSession(dir, filterCwd ? cwd : undefined);
1148
+ const defaultDirs = getDefaultSessionDirCandidates(cwd);
1149
+ const filterCwd = sessionDir !== undefined && !defaultDirs.includes(dir);
1150
+ const searchDirs = sessionDir ? [dir] : defaultDirs;
1151
+ const mostRecent = findMostRecentSessionInDirs(searchDirs, filterCwd ? cwd : undefined);
1074
1152
  if (mostRecent) {
1075
- return new SessionManager(cwd, dir, mostRecent, true);
1153
+ return new SessionManager(cwd, resolve(mostRecent, ".."), mostRecent, true);
1076
1154
  }
1077
1155
  return new SessionManager(cwd, dir, undefined, true);
1078
1156
  }
@@ -1136,11 +1214,22 @@ export class SessionManager {
1136
1214
  */
1137
1215
  static async list(cwd, sessionDir, onProgress) {
1138
1216
  const dir = sessionDir ? normalizePath(sessionDir) : getDefaultSessionDir(cwd);
1139
- const filterCwd = sessionDir !== undefined && dir !== getDefaultSessionDirPath(cwd);
1217
+ const defaultDirs = getDefaultSessionDirCandidates(cwd);
1218
+ const filterCwd = sessionDir !== undefined && !defaultDirs.includes(dir);
1140
1219
  const resolvedCwd = resolvePath(cwd);
1141
- const sessions = (await listSessionsFromDir(dir, onProgress)).filter((session) => !filterCwd || sessionCwdMatches(session.cwd, resolvedCwd));
1142
- sessions.sort((a, b) => b.modified.getTime() - a.modified.getTime());
1143
- return sessions;
1220
+ const dirs = sessionDir ? [dir] : defaultDirs;
1221
+ const progressCounts = await Promise.all(dirs.map((candidateDir) => countSessionFiles(candidateDir)));
1222
+ const progressTotal = progressCounts.reduce((sum, count) => sum + count, 0);
1223
+ const allSessions = [];
1224
+ let progressOffset = 0;
1225
+ for (let i = 0; i < dirs.length; i++) {
1226
+ const candidateDir = dirs[i];
1227
+ const sessions = (await listSessionsFromDir(candidateDir, onProgress, progressOffset, progressTotal)).filter((session) => !filterCwd || sessionCwdMatches(session.cwd, resolvedCwd));
1228
+ progressOffset += progressCounts[i];
1229
+ allSessions.push(...sessions);
1230
+ }
1231
+ allSessions.sort((a, b) => b.modified.getTime() - a.modified.getTime());
1232
+ return allSessions;
1144
1233
  }
1145
1234
  static async listAll(sessionDirOrOnProgress, onProgress) {
1146
1235
  const customSessionDir = typeof sessionDirOrOnProgress === "string" ? normalizePath(sessionDirOrOnProgress) : undefined;