@fleetagent/pi-coding-agent 0.0.1 → 0.0.4

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 (125) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/core/agent-session.d.ts +32 -7
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +225 -12
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/compaction/branch-summarization.d.ts +2 -2
  7. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  8. package/dist/core/compaction/branch-summarization.js.map +1 -1
  9. package/dist/core/compaction/compaction.d.ts +2 -2
  10. package/dist/core/compaction/compaction.d.ts.map +1 -1
  11. package/dist/core/compaction/compaction.js +1 -1
  12. package/dist/core/compaction/compaction.js.map +1 -1
  13. package/dist/core/extensions/runner.d.ts +3 -3
  14. package/dist/core/extensions/runner.d.ts.map +1 -1
  15. package/dist/core/extensions/runner.js +5 -5
  16. package/dist/core/extensions/runner.js.map +1 -1
  17. package/dist/core/extensions/types.d.ts +15 -8
  18. package/dist/core/extensions/types.d.ts.map +1 -1
  19. package/dist/core/extensions/types.js.map +1 -1
  20. package/dist/core/footer-data-provider.d.ts +1 -1
  21. package/dist/core/footer-data-provider.d.ts.map +1 -1
  22. package/dist/core/footer-data-provider.js +1 -1
  23. package/dist/core/footer-data-provider.js.map +1 -1
  24. package/dist/core/messages.d.ts +1 -0
  25. package/dist/core/messages.d.ts.map +1 -1
  26. package/dist/core/messages.js +4 -0
  27. package/dist/core/messages.js.map +1 -1
  28. package/dist/core/pi-agent.d.ts +5 -3
  29. package/dist/core/pi-agent.d.ts.map +1 -1
  30. package/dist/core/pi-agent.js +64 -18
  31. package/dist/core/pi-agent.js.map +1 -1
  32. package/dist/core/session/in-memory-session-manager.d.ts.map +1 -1
  33. package/dist/core/session/in-memory-session-manager.js +5 -7
  34. package/dist/core/session/in-memory-session-manager.js.map +1 -1
  35. package/dist/core/session/in-memory-session.d.ts +3 -1
  36. package/dist/core/session/in-memory-session.d.ts.map +1 -1
  37. package/dist/core/session/in-memory-session.js +5 -2
  38. package/dist/core/session/in-memory-session.js.map +1 -1
  39. package/dist/core/session/index.d.ts +2 -2
  40. package/dist/core/session/index.d.ts.map +1 -1
  41. package/dist/core/session/index.js.map +1 -1
  42. package/dist/core/session/jsonl-helpers.d.ts.map +1 -1
  43. package/dist/core/session/jsonl-helpers.js +4 -4
  44. package/dist/core/session/jsonl-helpers.js.map +1 -1
  45. package/dist/core/session/local-session-manager.d.ts.map +1 -1
  46. package/dist/core/session/local-session-manager.js +12 -11
  47. package/dist/core/session/local-session-manager.js.map +1 -1
  48. package/dist/core/session/local-session.d.ts +3 -1
  49. package/dist/core/session/local-session.d.ts.map +1 -1
  50. package/dist/core/session/local-session.js +7 -2
  51. package/dist/core/session/local-session.js.map +1 -1
  52. package/dist/core/session/remote-session-client.d.ts +6 -1
  53. package/dist/core/session/remote-session-client.d.ts.map +1 -1
  54. package/dist/core/session/remote-session-client.js.map +1 -1
  55. package/dist/core/session/remote-session-manager.d.ts.map +1 -1
  56. package/dist/core/session/remote-session-manager.js +28 -7
  57. package/dist/core/session/remote-session-manager.js.map +1 -1
  58. package/dist/core/session/remote-session.d.ts +3 -0
  59. package/dist/core/session/remote-session.d.ts.map +1 -1
  60. package/dist/core/session/remote-session.js +4 -1
  61. package/dist/core/session/remote-session.js.map +1 -1
  62. package/dist/core/session/session.d.ts +9 -3
  63. package/dist/core/session/session.d.ts.map +1 -1
  64. package/dist/core/session/session.js +64 -10
  65. package/dist/core/session/session.js.map +1 -1
  66. package/dist/core/session/stores/in-memory-session-store.d.ts +6 -14
  67. package/dist/core/session/stores/in-memory-session-store.d.ts.map +1 -1
  68. package/dist/core/session/stores/in-memory-session-store.js +8 -34
  69. package/dist/core/session/stores/in-memory-session-store.js.map +1 -1
  70. package/dist/core/session/stores/jsonl-session-store.d.ts +14 -14
  71. package/dist/core/session/stores/jsonl-session-store.d.ts.map +1 -1
  72. package/dist/core/session/stores/jsonl-session-store.js +153 -162
  73. package/dist/core/session/stores/jsonl-session-store.js.map +1 -1
  74. package/dist/core/session/stores/remote-session-store.d.ts +4 -6
  75. package/dist/core/session/stores/remote-session-store.d.ts.map +1 -1
  76. package/dist/core/session/stores/remote-session-store.js +18 -30
  77. package/dist/core/session/stores/remote-session-store.js.map +1 -1
  78. package/dist/core/session/stores/session-store.d.ts +1 -15
  79. package/dist/core/session/stores/session-store.d.ts.map +1 -1
  80. package/dist/core/session/stores/session-store.js.map +1 -1
  81. package/dist/core/session-cwd.d.ts +2 -2
  82. package/dist/core/session-cwd.d.ts.map +1 -1
  83. package/dist/core/session-cwd.js +5 -5
  84. package/dist/core/session-cwd.js.map +1 -1
  85. package/dist/index.d.ts +1 -1
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js.map +1 -1
  88. package/dist/modes/interactive/interactive-mode.d.ts +1 -1
  89. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  90. package/dist/modes/interactive/interactive-mode.js +39 -37
  91. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  92. package/docs/extensions.md +35 -32
  93. package/docs/index.md +1 -1
  94. package/docs/sdk.md +2 -0
  95. package/docs/session-format.md +21 -21
  96. package/docs/sessions.md +2 -2
  97. package/docs/tui.md +1 -1
  98. package/examples/README.md +3 -0
  99. package/examples/extensions/README.md +1 -1
  100. package/examples/extensions/auto-commit-on-exit.ts +1 -1
  101. package/examples/extensions/bookmark.ts +3 -3
  102. package/examples/extensions/confirm-destructive.ts +1 -1
  103. package/examples/extensions/custom-compaction.ts +1 -1
  104. package/examples/extensions/custom-footer.ts +2 -2
  105. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  106. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  107. package/examples/extensions/git-checkpoint.ts +1 -1
  108. package/examples/extensions/handoff.ts +2 -2
  109. package/examples/extensions/plan-mode/index.ts +1 -1
  110. package/examples/extensions/preset.ts +1 -1
  111. package/examples/extensions/qna.ts +1 -1
  112. package/examples/extensions/sandbox/package.json +1 -1
  113. package/examples/extensions/snake.ts +1 -1
  114. package/examples/extensions/space-invaders.ts +1 -1
  115. package/examples/extensions/summarize.ts +1 -1
  116. package/examples/extensions/tic-tac-toe.ts +1 -1
  117. package/examples/extensions/todo.ts +1 -1
  118. package/examples/extensions/tools.ts +1 -1
  119. package/examples/extensions/with-deps/package.json +1 -1
  120. package/examples/remote-session-server/README.md +66 -0
  121. package/examples/remote-session-server/server.ts +359 -0
  122. package/examples/sdk/11-sessions.ts +3 -3
  123. package/examples/sdk/13-session-runtime.ts +6 -6
  124. package/npm-shrinkwrap.json +12 -12
  125. package/package.json +4 -4
@@ -7,7 +7,7 @@ export { buildSessionContext, getLatestCompactionEntry } from "./context.js";
7
7
  export { migrateSessionEntries, parseSessionEntries } from "./migrations.js";
8
8
  export { findMostRecentSession, getDefaultSessionDir, loadEntriesFromFile } from "./jsonl-helpers.js";
9
9
  /**
10
- * Manages conversation sessions as append-only trees stored in JSONL files.
10
+ * Represents one active conversation session as an append-only tree.
11
11
  *
12
12
  * Each session entry has an id and parentId forming a tree structure. The "leaf"
13
13
  * pointer tracks the current position. Appending creates a child of the current leaf.
@@ -22,10 +22,12 @@ export class Session {
22
22
  sessionDir;
23
23
  cwd;
24
24
  store;
25
- constructor(cwd, sessionDir, sessionReference, store) {
25
+ sessionManager;
26
+ constructor(cwd, sessionDir, sessionReference, store, sessionManager) {
26
27
  this.cwd = cwd;
27
28
  this.sessionDir = sessionDir;
28
29
  this.store = store;
30
+ this.sessionManager = sessionManager;
29
31
  if (sessionDir) {
30
32
  this.store.ensureDir(sessionDir);
31
33
  }
@@ -38,14 +40,14 @@ export class Session {
38
40
  }
39
41
  /** Switch to a different session reference (used for resume and branching). */
40
42
  setSessionReference(sessionReference) {
41
- const opened = this.store.openSession(sessionReference);
42
- if (opened.exists) {
43
- this.store.setEntries(opened.entries);
43
+ this.store.setSessionReference(sessionReference);
44
+ if (this.store.exists(sessionReference)) {
45
+ this.store.setEntries(this.store.load(sessionReference));
44
46
  // If the opened session has no valid header, start fresh to avoid
45
47
  // appending messages without a session header.
46
48
  if (this.store.getFileEntries().length === 0) {
47
49
  this.newSession();
48
- this.store.setSessionReference(opened.reference);
50
+ this.store.setSessionReference(sessionReference);
49
51
  this.store.saveSnapshot();
50
52
  return;
51
53
  }
@@ -58,7 +60,7 @@ export class Session {
58
60
  }
59
61
  else {
60
62
  this.newSession();
61
- this.store.setSessionReference(opened.reference); // preserve explicit path from --session flag
63
+ this.store.setSessionReference(sessionReference); // preserve explicit reference from --session flag
62
64
  }
63
65
  }
64
66
  newSession(options) {
@@ -72,8 +74,12 @@ export class Session {
72
74
  cwd: this.cwd,
73
75
  parentSession: options?.parentSession,
74
76
  };
77
+ const sessionReference = this.prepareNewSessionReference(this.getSessionDir(), this.sessionId, timestamp);
78
+ if (sessionReference) {
79
+ this.store.setSessionReference(sessionReference);
80
+ }
75
81
  this.store.setEntries([header]);
76
- return this.store.prepareSessionReference(this.getSessionDir(), this.sessionId, timestamp);
82
+ return sessionReference;
77
83
  }
78
84
  isPersisted() {
79
85
  return this.store.isPersisted();
@@ -335,7 +341,7 @@ export class Session {
335
341
  * Returns the new session reference, or undefined if the store does not expose one.
336
342
  */
337
343
  createBranchedSession(leafId) {
338
- const parentSession = this.store.getParentSessionReference();
344
+ const parentSession = this.getSessionReference();
339
345
  const path = this.getBranch(leafId);
340
346
  if (path.length === 0) {
341
347
  throw new Error(`Entry ${leafId} not found`);
@@ -344,7 +350,10 @@ export class Session {
344
350
  const pathWithoutLabels = path.filter((e) => e.type !== "label");
345
351
  const newSessionId = createSessionId();
346
352
  const timestamp = new Date().toISOString();
347
- const newSessionReference = this.store.prepareSessionReference(this.getSessionDir(), newSessionId, timestamp);
353
+ const newSessionReference = this.prepareNewSessionReference(this.getSessionDir(), newSessionId, timestamp);
354
+ if (newSessionReference) {
355
+ this.store.setSessionReference(newSessionReference);
356
+ }
348
357
  const header = {
349
358
  type: "session",
350
359
  version: CURRENT_SESSION_VERSION,
@@ -376,5 +385,50 @@ export class Session {
376
385
  this.store.commitSnapshot();
377
386
  return newSessionReference;
378
387
  }
388
+ copyBranchFrom(source, leafId, parentSession) {
389
+ const path = source.getBranch(leafId);
390
+ if (path.length === 0) {
391
+ throw new Error(`Entry ${leafId} not found`);
392
+ }
393
+ const header = this.getHeader();
394
+ if (!header) {
395
+ throw new Error("Target session has no header");
396
+ }
397
+ const pathWithoutLabels = structuredClone(path.filter((entry) => entry.type !== "label"));
398
+ const pathEntryIds = new Set(pathWithoutLabels.map((entry) => entry.id));
399
+ const labelsToWrite = source.store.getLabelsForEntryIds(pathEntryIds);
400
+ const labelEntries = [];
401
+ let nextParentId = pathWithoutLabels[pathWithoutLabels.length - 1]?.id ?? null;
402
+ for (const { targetId, label, timestamp } of labelsToWrite) {
403
+ const labelEntry = {
404
+ type: "label",
405
+ id: generateId(new Set([...pathEntryIds, ...labelEntries.map((entry) => entry.id)])),
406
+ parentId: nextParentId,
407
+ timestamp,
408
+ targetId,
409
+ label,
410
+ };
411
+ pathEntryIds.add(labelEntry.id);
412
+ labelEntries.push(labelEntry);
413
+ nextParentId = labelEntry.id;
414
+ }
415
+ this.store.setEntries([{ ...header, parentSession }, ...pathWithoutLabels, ...labelEntries]);
416
+ this.store.commitSnapshot();
417
+ }
418
+ createSubSession(options) {
419
+ if (!this.sessionManager) {
420
+ throw new Error("Session manager unavailable");
421
+ }
422
+ return this.sessionManager.create({
423
+ ...options,
424
+ parentSession: options?.parentSession ?? this.getSessionReference(),
425
+ });
426
+ }
427
+ forkSubSession(targetLeafId) {
428
+ if (!this.sessionManager) {
429
+ throw new Error("Session manager unavailable");
430
+ }
431
+ return this.sessionManager.forkSession(this, targetLeafId);
432
+ }
379
433
  }
380
434
  //# sourceMappingURL=session.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"session.js","sourceRoot":"","sources":["../../../src/core/session/session.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ImageContent, Message, TextContent } from \"@fleetagent/pi-ai\";\nimport type { BashExecutionMessage, CustomMessage } from \"../messages.ts\";\nimport { CURRENT_SESSION_VERSION } from \"./constants.ts\";\nimport { buildSessionContext } from \"./context.ts\";\nimport { createSessionId, generateId } from \"./ids.ts\";\nimport { migrateToCurrentVersion } from \"./migrations.ts\";\nimport type { SessionStore } from \"./stores/session-store.ts\";\nimport type {\n\tBranchSummaryEntry,\n\tCompactionEntry,\n\tCustomEntry,\n\tCustomMessageEntry,\n\tLabelEntry,\n\tModelChangeEntry,\n\tNewSessionOptions,\n\tSessionContext,\n\tSessionEntry,\n\tSessionHeader,\n\tSessionInfoEntry,\n\tSessionMessageEntry,\n\tSessionTreeNode,\n\tThinkingLevelChangeEntry,\n} from \"./types.ts\";\n\nexport { CURRENT_SESSION_VERSION } from \"./constants.ts\";\nexport { buildSessionContext, getLatestCompactionEntry } from \"./context.ts\";\nexport { migrateSessionEntries, parseSessionEntries } from \"./migrations.ts\";\n\nexport type {\n\tBranchSummaryEntry,\n\tCompactionEntry,\n\tCustomEntry,\n\tCustomMessageEntry,\n\tFileEntry,\n\tLabelEntry,\n\tModelChangeEntry,\n\tNewSessionOptions,\n\tSessionContext,\n\tSessionEntry,\n\tSessionEntryBase,\n\tSessionHeader,\n\tSessionInfo,\n\tSessionInfoEntry,\n\tSessionListProgress,\n\tSessionMessageEntry,\n\tSessionTreeNode,\n\tThinkingLevelChangeEntry,\n} from \"./types.ts\";\n\nexport type ReadonlySessionManager = Pick<\n\tSession,\n\t| \"getCwd\"\n\t| \"getSessionDir\"\n\t| \"getSessionId\"\n\t| \"getSessionReference\"\n\t| \"getLeafId\"\n\t| \"getLeafEntry\"\n\t| \"getEntry\"\n\t| \"getLabel\"\n\t| \"getBranch\"\n\t| \"getHeader\"\n\t| \"getEntries\"\n\t| \"getTree\"\n\t| \"getSessionName\"\n>;\n\nexport { findMostRecentSession, getDefaultSessionDir, loadEntriesFromFile } from \"./jsonl-helpers.ts\";\n\n/**\n * Manages conversation sessions as append-only trees stored in JSONL files.\n *\n * Each session entry has an id and parentId forming a tree structure. The \"leaf\"\n * pointer tracks the current position. Appending creates a child of the current leaf.\n * Branching moves the leaf to an earlier entry, allowing new branches without\n * modifying history.\n *\n * Use buildSessionContext() to get the resolved message list for the LLM, which\n * handles compaction summaries and follows the path from root to current leaf.\n */\nexport abstract class Session {\n\tprivate sessionId: string = \"\";\n\tprivate sessionDir: string;\n\tprivate cwd: string;\n\tprivate store: SessionStore;\n\n\tprotected constructor(cwd: string, sessionDir: string, sessionReference: string | undefined, store: SessionStore) {\n\t\tthis.cwd = cwd;\n\t\tthis.sessionDir = sessionDir;\n\t\tthis.store = store;\n\t\tif (sessionDir) {\n\t\t\tthis.store.ensureDir(sessionDir);\n\t\t}\n\n\t\tif (sessionReference) {\n\t\t\tthis.setSessionReference(sessionReference);\n\t\t} else {\n\t\t\tthis.newSession();\n\t\t}\n\t}\n\n\t/** Switch to a different session reference (used for resume and branching). */\n\tsetSessionReference(sessionReference: string): void {\n\t\tconst opened = this.store.openSession(sessionReference);\n\t\tif (opened.exists) {\n\t\t\tthis.store.setEntries(opened.entries);\n\n\t\t\t// If the opened session has no valid header, start fresh to avoid\n\t\t\t// appending messages without a session header.\n\t\t\tif (this.store.getFileEntries().length === 0) {\n\t\t\t\tthis.newSession();\n\t\t\t\tthis.store.setSessionReference(opened.reference);\n\t\t\t\tthis.store.saveSnapshot();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst header = this.store.getHeader();\n\t\t\tthis.sessionId = header?.id ?? createSessionId();\n\n\t\t\tif (migrateToCurrentVersion(this.store.getFileEntries())) {\n\t\t\t\tthis.store.setEntries(this.store.getFileEntries());\n\t\t\t\tthis.store.saveSnapshot();\n\t\t\t}\n\t\t} else {\n\t\t\tthis.newSession();\n\t\t\tthis.store.setSessionReference(opened.reference); // preserve explicit path from --session flag\n\t\t}\n\t}\n\n\tnewSession(options?: NewSessionOptions): string | undefined {\n\t\tthis.sessionId = options?.id ?? createSessionId();\n\t\tconst timestamp = new Date().toISOString();\n\t\tconst header: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tversion: CURRENT_SESSION_VERSION,\n\t\t\tid: this.sessionId,\n\t\t\ttimestamp,\n\t\t\tcwd: this.cwd,\n\t\t\tparentSession: options?.parentSession,\n\t\t};\n\t\tthis.store.setEntries([header]);\n\t\treturn this.store.prepareSessionReference(this.getSessionDir(), this.sessionId, timestamp);\n\t}\n\n\tisPersisted(): boolean {\n\t\treturn this.store.isPersisted();\n\t}\n\n\tgetCwd(): string {\n\t\treturn this.cwd;\n\t}\n\n\tgetSessionDir(): string {\n\t\treturn this.sessionDir;\n\t}\n\n\tgetSessionId(): string {\n\t\treturn this.sessionId;\n\t}\n\n\tgetSessionReference(): string | undefined {\n\t\treturn this.store.getSessionReference();\n\t}\n\n\tprivate _appendEntry(entry: SessionEntry): void {\n\t\tthis.store.appendEntry(entry);\n\t}\n\n\t/** Append a message as child of current leaf, then advance leaf. Returns entry id.\n\t * Does not allow writing CompactionSummaryMessage and BranchSummaryMessage directly.\n\t * Reason: we want these to be top-level entries in the session, not message session entries,\n\t * so it is easier to find them.\n\t * These need to be appended via appendCompaction() and appendBranchSummary() methods.\n\t */\n\tappendMessage(message: Message | CustomMessage | BashExecutionMessage): string {\n\t\tconst entry: SessionMessageEntry = {\n\t\t\ttype: \"message\",\n\t\t\tid: generateId(this.store),\n\t\t\tparentId: this.store.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tmessage,\n\t\t};\n\t\tthis._appendEntry(entry);\n\t\treturn entry.id;\n\t}\n\n\t/** Append a thinking level change as child of current leaf, then advance leaf. Returns entry id. */\n\tappendThinkingLevelChange(thinkingLevel: string): string {\n\t\tconst entry: ThinkingLevelChangeEntry = {\n\t\t\ttype: \"thinking_level_change\",\n\t\t\tid: generateId(this.store),\n\t\t\tparentId: this.store.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tthinkingLevel,\n\t\t};\n\t\tthis._appendEntry(entry);\n\t\treturn entry.id;\n\t}\n\n\t/** Append a model change as child of current leaf, then advance leaf. Returns entry id. */\n\tappendModelChange(provider: string, modelId: string): string {\n\t\tconst entry: ModelChangeEntry = {\n\t\t\ttype: \"model_change\",\n\t\t\tid: generateId(this.store),\n\t\t\tparentId: this.store.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tprovider,\n\t\t\tmodelId,\n\t\t};\n\t\tthis._appendEntry(entry);\n\t\treturn entry.id;\n\t}\n\n\t/** Append a compaction summary as child of current leaf, then advance leaf. Returns entry id. */\n\tappendCompaction<T = unknown>(\n\t\tsummary: string,\n\t\tfirstKeptEntryId: string,\n\t\ttokensBefore: number,\n\t\tdetails?: T,\n\t\tfromHook?: boolean,\n\t): string {\n\t\tconst entry: CompactionEntry<T> = {\n\t\t\ttype: \"compaction\",\n\t\t\tid: generateId(this.store),\n\t\t\tparentId: this.store.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tsummary,\n\t\t\tfirstKeptEntryId,\n\t\t\ttokensBefore,\n\t\t\tdetails,\n\t\t\tfromHook,\n\t\t};\n\t\tthis._appendEntry(entry);\n\t\treturn entry.id;\n\t}\n\n\t/** Append a custom entry (for extensions) as child of current leaf, then advance leaf. Returns entry id. */\n\tappendCustomEntry(customType: string, data?: unknown): string {\n\t\tconst entry: CustomEntry = {\n\t\t\ttype: \"custom\",\n\t\t\tcustomType,\n\t\t\tdata,\n\t\t\tid: generateId(this.store),\n\t\t\tparentId: this.store.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t};\n\t\tthis._appendEntry(entry);\n\t\treturn entry.id;\n\t}\n\n\t/** Append a session info entry (e.g., display name). Returns entry id. */\n\tappendSessionInfo(name: string): string {\n\t\tconst entry: SessionInfoEntry = {\n\t\t\ttype: \"session_info\",\n\t\t\tid: generateId(this.store),\n\t\t\tparentId: this.store.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tname: name.trim(),\n\t\t};\n\t\tthis._appendEntry(entry);\n\t\treturn entry.id;\n\t}\n\n\t/** Get the current session name from the latest session_info entry, if any. */\n\tgetSessionName(): string | undefined {\n\t\treturn this.store.getSessionName();\n\t}\n\n\t/**\n\t * Append a custom message entry (for extensions) that participates in LLM context.\n\t * @param customType Extension identifier for filtering on reload\n\t * @param content Message content (string or TextContent/ImageContent array)\n\t * @param display Whether to show in TUI (true = styled display, false = hidden)\n\t * @param details Optional extension-specific metadata (not sent to LLM)\n\t * @returns Entry id\n\t */\n\tappendCustomMessageEntry<T = unknown>(\n\t\tcustomType: string,\n\t\tcontent: string | (TextContent | ImageContent)[],\n\t\tdisplay: boolean,\n\t\tdetails?: T,\n\t): string {\n\t\tconst entry: CustomMessageEntry<T> = {\n\t\t\ttype: \"custom_message\",\n\t\t\tcustomType,\n\t\t\tcontent,\n\t\t\tdisplay,\n\t\t\tdetails,\n\t\t\tid: generateId(this.store),\n\t\t\tparentId: this.store.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t};\n\t\tthis._appendEntry(entry);\n\t\treturn entry.id;\n\t}\n\n\t// =========================================================================\n\t// Tree Traversal\n\t// =========================================================================\n\n\tgetLeafId(): string | null {\n\t\treturn this.store.getLeafId();\n\t}\n\n\tgetLeafEntry(): SessionEntry | undefined {\n\t\treturn this.store.getLeafEntry();\n\t}\n\n\tgetEntry(id: string): SessionEntry | undefined {\n\t\treturn this.store.getEntry(id);\n\t}\n\n\t/**\n\t * Get all direct children of an entry.\n\t */\n\tgetChildren(parentId: string): SessionEntry[] {\n\t\treturn this.store.getChildren(parentId);\n\t}\n\n\t/**\n\t * Get the label for an entry, if any.\n\t */\n\tgetLabel(id: string): string | undefined {\n\t\treturn this.store.getLabel(id);\n\t}\n\n\t/**\n\t * Set or clear a label on an entry.\n\t * Labels are user-defined markers for bookmarking/navigation.\n\t * Pass undefined or empty string to clear the label.\n\t */\n\tappendLabelChange(targetId: string, label: string | undefined): string {\n\t\tif (!this.store.has(targetId)) {\n\t\t\tthrow new Error(`Entry ${targetId} not found`);\n\t\t}\n\t\tconst entry: LabelEntry = {\n\t\t\ttype: \"label\",\n\t\t\tid: generateId(this.store),\n\t\t\tparentId: this.store.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\ttargetId,\n\t\t\tlabel,\n\t\t};\n\t\tthis._appendEntry(entry);\n\t\treturn entry.id;\n\t}\n\n\t/**\n\t * Walk from entry to root, returning all entries in path order.\n\t * Includes all entry types (messages, compaction, model changes, etc.).\n\t * Use buildSessionContext() to get the resolved messages for the LLM.\n\t */\n\tgetBranch(fromId?: string): SessionEntry[] {\n\t\treturn this.store.getBranch(fromId);\n\t}\n\n\t/**\n\t * Build the session context (what gets sent to the LLM).\n\t * Uses tree traversal from current leaf.\n\t */\n\tbuildSessionContext(): SessionContext {\n\t\treturn buildSessionContext(this.getEntries(), this.store.getLeafId(), this.store.getEntryIndex());\n\t}\n\n\t/**\n\t * Get session header.\n\t */\n\tgetHeader(): SessionHeader | null {\n\t\treturn this.store.getHeader();\n\t}\n\n\t/**\n\t * Get all session entries (excludes header). Returns a shallow copy.\n\t * The session is append-only: use appendXXX() to add entries, branch() to\n\t * change the leaf pointer. Entries cannot be modified or deleted.\n\t */\n\tgetEntries(): SessionEntry[] {\n\t\treturn this.store.getEntries();\n\t}\n\n\t/**\n\t * Get the session as a tree structure. Returns a shallow defensive copy of all entries.\n\t * A well-formed session has exactly one root (first entry with parentId === null).\n\t * Orphaned entries (broken parent chain) are also returned as roots.\n\t */\n\tgetTree(): SessionTreeNode[] {\n\t\treturn this.store.getTree();\n\t}\n\n\t// =========================================================================\n\t// Branching\n\t// =========================================================================\n\n\t/**\n\t * Start a new branch from an earlier entry.\n\t * Moves the leaf pointer to the specified entry. The next appendXXX() call\n\t * will create a child of that entry, forming a new branch. Existing entries\n\t * are not modified or deleted.\n\t */\n\tbranch(branchFromId: string): void {\n\t\tif (!this.store.has(branchFromId)) {\n\t\t\tthrow new Error(`Entry ${branchFromId} not found`);\n\t\t}\n\t\tthis.store.setLeafId(branchFromId);\n\t}\n\n\t/**\n\t * Reset the leaf pointer to null (before any entries).\n\t * The next appendXXX() call will create a new root entry (parentId = null).\n\t * Use this when navigating to re-edit the first user message.\n\t */\n\tresetLeaf(): void {\n\t\tthis.store.setLeafId(null);\n\t}\n\n\t/**\n\t * Start a new branch with a summary of the abandoned path.\n\t * Same as branch(), but also appends a branch_summary entry that captures\n\t * context from the abandoned conversation path.\n\t */\n\tbranchWithSummary(branchFromId: string | null, summary: string, details?: unknown, fromHook?: boolean): string {\n\t\tif (branchFromId !== null && !this.store.has(branchFromId)) {\n\t\t\tthrow new Error(`Entry ${branchFromId} not found`);\n\t\t}\n\t\tthis.store.setLeafId(branchFromId);\n\t\tconst entry: BranchSummaryEntry = {\n\t\t\ttype: \"branch_summary\",\n\t\t\tid: generateId(this.store),\n\t\t\tparentId: branchFromId,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tfromId: branchFromId ?? \"root\",\n\t\t\tsummary,\n\t\t\tdetails,\n\t\t\tfromHook,\n\t\t};\n\t\tthis._appendEntry(entry);\n\t\treturn entry.id;\n\t}\n\n\t/**\n\t * Create a new session reference containing only the path from root to the specified leaf.\n\t * Useful for extracting a single conversation path from a branched session.\n\t * Returns the new session reference, or undefined if the store does not expose one.\n\t */\n\tcreateBranchedSession(leafId: string): string | undefined {\n\t\tconst parentSession = this.store.getParentSessionReference();\n\t\tconst path = this.getBranch(leafId);\n\t\tif (path.length === 0) {\n\t\t\tthrow new Error(`Entry ${leafId} not found`);\n\t\t}\n\n\t\t// Filter out LabelEntry from path - we'll recreate them from the resolved map\n\t\tconst pathWithoutLabels = path.filter((e) => e.type !== \"label\");\n\n\t\tconst newSessionId = createSessionId();\n\t\tconst timestamp = new Date().toISOString();\n\t\tconst newSessionReference = this.store.prepareSessionReference(this.getSessionDir(), newSessionId, timestamp);\n\n\t\tconst header: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tversion: CURRENT_SESSION_VERSION,\n\t\t\tid: newSessionId,\n\t\t\ttimestamp,\n\t\t\tcwd: this.cwd,\n\t\t\tparentSession,\n\t\t};\n\n\t\t// Collect labels for entries in the path\n\t\tconst pathEntryIds = new Set(pathWithoutLabels.map((e) => e.id));\n\t\tconst labelsToWrite = this.store.getLabelsForEntryIds(pathEntryIds);\n\n\t\tconst labelEntries: LabelEntry[] = [];\n\t\tlet parentId = pathWithoutLabels[pathWithoutLabels.length - 1]?.id || null;\n\t\tfor (const { targetId, label, timestamp: labelTimestamp } of labelsToWrite) {\n\t\t\tconst labelEntry: LabelEntry = {\n\t\t\t\ttype: \"label\",\n\t\t\t\tid: generateId(new Set([...pathEntryIds, ...labelEntries.map((e) => e.id)])),\n\t\t\t\tparentId,\n\t\t\t\ttimestamp: labelTimestamp,\n\t\t\t\ttargetId,\n\t\t\t\tlabel,\n\t\t\t};\n\t\t\tpathEntryIds.add(labelEntry.id);\n\t\t\tlabelEntries.push(labelEntry);\n\t\t\tparentId = labelEntry.id;\n\t\t}\n\t\tthis.store.setEntries([header, ...pathWithoutLabels, ...labelEntries]);\n\t\tthis.sessionId = newSessionId;\n\t\tthis.store.commitSnapshot();\n\t\treturn newSessionReference;\n\t}\n}\n"]}
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../../src/core/session/session.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ImageContent, Message, TextContent } from \"@fleetagent/pi-ai\";\nimport type { BashExecutionMessage, CustomMessage } from \"../messages.ts\";\nimport { CURRENT_SESSION_VERSION } from \"./constants.ts\";\nimport { buildSessionContext } from \"./context.ts\";\nimport { createSessionId, generateId } from \"./ids.ts\";\nimport { migrateToCurrentVersion } from \"./migrations.ts\";\nimport type { SessionManager, SessionResult } from \"./session-manager.ts\";\nimport type { SessionStore } from \"./stores/session-store.ts\";\nimport type {\n\tBranchSummaryEntry,\n\tCompactionEntry,\n\tCustomEntry,\n\tCustomMessageEntry,\n\tLabelEntry,\n\tModelChangeEntry,\n\tNewSessionOptions,\n\tSessionContext,\n\tSessionEntry,\n\tSessionHeader,\n\tSessionInfoEntry,\n\tSessionMessageEntry,\n\tSessionTreeNode,\n\tThinkingLevelChangeEntry,\n} from \"./types.ts\";\n\nexport { CURRENT_SESSION_VERSION } from \"./constants.ts\";\nexport { buildSessionContext, getLatestCompactionEntry } from \"./context.ts\";\nexport { migrateSessionEntries, parseSessionEntries } from \"./migrations.ts\";\n\nexport type {\n\tBranchSummaryEntry,\n\tCompactionEntry,\n\tCustomEntry,\n\tCustomMessageEntry,\n\tFileEntry,\n\tLabelEntry,\n\tModelChangeEntry,\n\tNewSessionOptions,\n\tSessionContext,\n\tSessionEntry,\n\tSessionEntryBase,\n\tSessionHeader,\n\tSessionInfo,\n\tSessionInfoEntry,\n\tSessionListProgress,\n\tSessionMessageEntry,\n\tSessionTreeNode,\n\tThinkingLevelChangeEntry,\n} from \"./types.ts\";\n\nexport type ReadonlySession = Pick<\n\tSession,\n\t| \"getCwd\"\n\t| \"getSessionDir\"\n\t| \"getSessionId\"\n\t| \"getSessionReference\"\n\t| \"getLeafId\"\n\t| \"getLeafEntry\"\n\t| \"getEntry\"\n\t| \"getLabel\"\n\t| \"getBranch\"\n\t| \"getHeader\"\n\t| \"getEntries\"\n\t| \"getTree\"\n\t| \"getSessionName\"\n>;\n\nexport { findMostRecentSession, getDefaultSessionDir, loadEntriesFromFile } from \"./jsonl-helpers.ts\";\n\n/**\n * Represents one active conversation session as an append-only tree.\n *\n * Each session entry has an id and parentId forming a tree structure. The \"leaf\"\n * pointer tracks the current position. Appending creates a child of the current leaf.\n * Branching moves the leaf to an earlier entry, allowing new branches without\n * modifying history.\n *\n * Use buildSessionContext() to get the resolved message list for the LLM, which\n * handles compaction summaries and follows the path from root to current leaf.\n */\nexport abstract class Session {\n\tprivate sessionId: string = \"\";\n\tprivate sessionDir: string;\n\tprivate cwd: string;\n\tprivate store: SessionStore;\n\tprivate sessionManager: SessionManager | undefined;\n\n\tprotected constructor(\n\t\tcwd: string,\n\t\tsessionDir: string,\n\t\tsessionReference: string | undefined,\n\t\tstore: SessionStore,\n\t\tsessionManager?: SessionManager,\n\t) {\n\t\tthis.cwd = cwd;\n\t\tthis.sessionDir = sessionDir;\n\t\tthis.store = store;\n\t\tthis.sessionManager = sessionManager;\n\t\tif (sessionDir) {\n\t\t\tthis.store.ensureDir(sessionDir);\n\t\t}\n\n\t\tif (sessionReference) {\n\t\t\tthis.setSessionReference(sessionReference);\n\t\t} else {\n\t\t\tthis.newSession();\n\t\t}\n\t}\n\n\tprotected abstract prepareNewSessionReference(\n\t\tsessionDir: string,\n\t\tsessionId: string,\n\t\ttimestamp: string,\n\t): string | undefined;\n\n\t/** Switch to a different session reference (used for resume and branching). */\n\tsetSessionReference(sessionReference: string): void {\n\t\tthis.store.setSessionReference(sessionReference);\n\t\tif (this.store.exists(sessionReference)) {\n\t\t\tthis.store.setEntries(this.store.load(sessionReference));\n\n\t\t\t// If the opened session has no valid header, start fresh to avoid\n\t\t\t// appending messages without a session header.\n\t\t\tif (this.store.getFileEntries().length === 0) {\n\t\t\t\tthis.newSession();\n\t\t\t\tthis.store.setSessionReference(sessionReference);\n\t\t\t\tthis.store.saveSnapshot();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst header = this.store.getHeader();\n\t\t\tthis.sessionId = header?.id ?? createSessionId();\n\n\t\t\tif (migrateToCurrentVersion(this.store.getFileEntries())) {\n\t\t\t\tthis.store.setEntries(this.store.getFileEntries());\n\t\t\t\tthis.store.saveSnapshot();\n\t\t\t}\n\t\t} else {\n\t\t\tthis.newSession();\n\t\t\tthis.store.setSessionReference(sessionReference); // preserve explicit reference from --session flag\n\t\t}\n\t}\n\n\tnewSession(options?: NewSessionOptions): string | undefined {\n\t\tthis.sessionId = options?.id ?? createSessionId();\n\t\tconst timestamp = new Date().toISOString();\n\t\tconst header: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tversion: CURRENT_SESSION_VERSION,\n\t\t\tid: this.sessionId,\n\t\t\ttimestamp,\n\t\t\tcwd: this.cwd,\n\t\t\tparentSession: options?.parentSession,\n\t\t};\n\t\tconst sessionReference = this.prepareNewSessionReference(this.getSessionDir(), this.sessionId, timestamp);\n\t\tif (sessionReference) {\n\t\t\tthis.store.setSessionReference(sessionReference);\n\t\t}\n\t\tthis.store.setEntries([header]);\n\t\treturn sessionReference;\n\t}\n\n\tisPersisted(): boolean {\n\t\treturn this.store.isPersisted();\n\t}\n\n\tgetCwd(): string {\n\t\treturn this.cwd;\n\t}\n\n\tgetSessionDir(): string {\n\t\treturn this.sessionDir;\n\t}\n\n\tgetSessionId(): string {\n\t\treturn this.sessionId;\n\t}\n\n\tgetSessionReference(): string | undefined {\n\t\treturn this.store.getSessionReference();\n\t}\n\n\tprivate _appendEntry(entry: SessionEntry): void {\n\t\tthis.store.appendEntry(entry);\n\t}\n\n\t/** Append a message as child of current leaf, then advance leaf. Returns entry id.\n\t * Does not allow writing CompactionSummaryMessage and BranchSummaryMessage directly.\n\t * Reason: we want these to be top-level entries in the session, not message session entries,\n\t * so it is easier to find them.\n\t * These need to be appended via appendCompaction() and appendBranchSummary() methods.\n\t */\n\tappendMessage(message: Message | CustomMessage | BashExecutionMessage): string {\n\t\tconst entry: SessionMessageEntry = {\n\t\t\ttype: \"message\",\n\t\t\tid: generateId(this.store),\n\t\t\tparentId: this.store.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tmessage,\n\t\t};\n\t\tthis._appendEntry(entry);\n\t\treturn entry.id;\n\t}\n\n\t/** Append a thinking level change as child of current leaf, then advance leaf. Returns entry id. */\n\tappendThinkingLevelChange(thinkingLevel: string): string {\n\t\tconst entry: ThinkingLevelChangeEntry = {\n\t\t\ttype: \"thinking_level_change\",\n\t\t\tid: generateId(this.store),\n\t\t\tparentId: this.store.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tthinkingLevel,\n\t\t};\n\t\tthis._appendEntry(entry);\n\t\treturn entry.id;\n\t}\n\n\t/** Append a model change as child of current leaf, then advance leaf. Returns entry id. */\n\tappendModelChange(provider: string, modelId: string): string {\n\t\tconst entry: ModelChangeEntry = {\n\t\t\ttype: \"model_change\",\n\t\t\tid: generateId(this.store),\n\t\t\tparentId: this.store.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tprovider,\n\t\t\tmodelId,\n\t\t};\n\t\tthis._appendEntry(entry);\n\t\treturn entry.id;\n\t}\n\n\t/** Append a compaction summary as child of current leaf, then advance leaf. Returns entry id. */\n\tappendCompaction<T = unknown>(\n\t\tsummary: string,\n\t\tfirstKeptEntryId: string,\n\t\ttokensBefore: number,\n\t\tdetails?: T,\n\t\tfromHook?: boolean,\n\t): string {\n\t\tconst entry: CompactionEntry<T> = {\n\t\t\ttype: \"compaction\",\n\t\t\tid: generateId(this.store),\n\t\t\tparentId: this.store.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tsummary,\n\t\t\tfirstKeptEntryId,\n\t\t\ttokensBefore,\n\t\t\tdetails,\n\t\t\tfromHook,\n\t\t};\n\t\tthis._appendEntry(entry);\n\t\treturn entry.id;\n\t}\n\n\t/** Append a custom entry (for extensions) as child of current leaf, then advance leaf. Returns entry id. */\n\tappendCustomEntry(customType: string, data?: unknown): string {\n\t\tconst entry: CustomEntry = {\n\t\t\ttype: \"custom\",\n\t\t\tcustomType,\n\t\t\tdata,\n\t\t\tid: generateId(this.store),\n\t\t\tparentId: this.store.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t};\n\t\tthis._appendEntry(entry);\n\t\treturn entry.id;\n\t}\n\n\t/** Append a session info entry (e.g., display name). Returns entry id. */\n\tappendSessionInfo(name: string): string {\n\t\tconst entry: SessionInfoEntry = {\n\t\t\ttype: \"session_info\",\n\t\t\tid: generateId(this.store),\n\t\t\tparentId: this.store.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tname: name.trim(),\n\t\t};\n\t\tthis._appendEntry(entry);\n\t\treturn entry.id;\n\t}\n\n\t/** Get the current session name from the latest session_info entry, if any. */\n\tgetSessionName(): string | undefined {\n\t\treturn this.store.getSessionName();\n\t}\n\n\t/**\n\t * Append a custom message entry (for extensions) that participates in LLM context.\n\t * @param customType Extension identifier for filtering on reload\n\t * @param content Message content (string or TextContent/ImageContent array)\n\t * @param display Whether to show in TUI (true = styled display, false = hidden)\n\t * @param details Optional extension-specific metadata (not sent to LLM)\n\t * @returns Entry id\n\t */\n\tappendCustomMessageEntry<T = unknown>(\n\t\tcustomType: string,\n\t\tcontent: string | (TextContent | ImageContent)[],\n\t\tdisplay: boolean,\n\t\tdetails?: T,\n\t): string {\n\t\tconst entry: CustomMessageEntry<T> = {\n\t\t\ttype: \"custom_message\",\n\t\t\tcustomType,\n\t\t\tcontent,\n\t\t\tdisplay,\n\t\t\tdetails,\n\t\t\tid: generateId(this.store),\n\t\t\tparentId: this.store.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t};\n\t\tthis._appendEntry(entry);\n\t\treturn entry.id;\n\t}\n\n\t// =========================================================================\n\t// Tree Traversal\n\t// =========================================================================\n\n\tgetLeafId(): string | null {\n\t\treturn this.store.getLeafId();\n\t}\n\n\tgetLeafEntry(): SessionEntry | undefined {\n\t\treturn this.store.getLeafEntry();\n\t}\n\n\tgetEntry(id: string): SessionEntry | undefined {\n\t\treturn this.store.getEntry(id);\n\t}\n\n\t/**\n\t * Get all direct children of an entry.\n\t */\n\tgetChildren(parentId: string): SessionEntry[] {\n\t\treturn this.store.getChildren(parentId);\n\t}\n\n\t/**\n\t * Get the label for an entry, if any.\n\t */\n\tgetLabel(id: string): string | undefined {\n\t\treturn this.store.getLabel(id);\n\t}\n\n\t/**\n\t * Set or clear a label on an entry.\n\t * Labels are user-defined markers for bookmarking/navigation.\n\t * Pass undefined or empty string to clear the label.\n\t */\n\tappendLabelChange(targetId: string, label: string | undefined): string {\n\t\tif (!this.store.has(targetId)) {\n\t\t\tthrow new Error(`Entry ${targetId} not found`);\n\t\t}\n\t\tconst entry: LabelEntry = {\n\t\t\ttype: \"label\",\n\t\t\tid: generateId(this.store),\n\t\t\tparentId: this.store.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\ttargetId,\n\t\t\tlabel,\n\t\t};\n\t\tthis._appendEntry(entry);\n\t\treturn entry.id;\n\t}\n\n\t/**\n\t * Walk from entry to root, returning all entries in path order.\n\t * Includes all entry types (messages, compaction, model changes, etc.).\n\t * Use buildSessionContext() to get the resolved messages for the LLM.\n\t */\n\tgetBranch(fromId?: string): SessionEntry[] {\n\t\treturn this.store.getBranch(fromId);\n\t}\n\n\t/**\n\t * Build the session context (what gets sent to the LLM).\n\t * Uses tree traversal from current leaf.\n\t */\n\tbuildSessionContext(): SessionContext {\n\t\treturn buildSessionContext(this.getEntries(), this.store.getLeafId(), this.store.getEntryIndex());\n\t}\n\n\t/**\n\t * Get session header.\n\t */\n\tgetHeader(): SessionHeader | null {\n\t\treturn this.store.getHeader();\n\t}\n\n\t/**\n\t * Get all session entries (excludes header). Returns a shallow copy.\n\t * The session is append-only: use appendXXX() to add entries, branch() to\n\t * change the leaf pointer. Entries cannot be modified or deleted.\n\t */\n\tgetEntries(): SessionEntry[] {\n\t\treturn this.store.getEntries();\n\t}\n\n\t/**\n\t * Get the session as a tree structure. Returns a shallow defensive copy of all entries.\n\t * A well-formed session has exactly one root (first entry with parentId === null).\n\t * Orphaned entries (broken parent chain) are also returned as roots.\n\t */\n\tgetTree(): SessionTreeNode[] {\n\t\treturn this.store.getTree();\n\t}\n\n\t// =========================================================================\n\t// Branching\n\t// =========================================================================\n\n\t/**\n\t * Start a new branch from an earlier entry.\n\t * Moves the leaf pointer to the specified entry. The next appendXXX() call\n\t * will create a child of that entry, forming a new branch. Existing entries\n\t * are not modified or deleted.\n\t */\n\tbranch(branchFromId: string): void {\n\t\tif (!this.store.has(branchFromId)) {\n\t\t\tthrow new Error(`Entry ${branchFromId} not found`);\n\t\t}\n\t\tthis.store.setLeafId(branchFromId);\n\t}\n\n\t/**\n\t * Reset the leaf pointer to null (before any entries).\n\t * The next appendXXX() call will create a new root entry (parentId = null).\n\t * Use this when navigating to re-edit the first user message.\n\t */\n\tresetLeaf(): void {\n\t\tthis.store.setLeafId(null);\n\t}\n\n\t/**\n\t * Start a new branch with a summary of the abandoned path.\n\t * Same as branch(), but also appends a branch_summary entry that captures\n\t * context from the abandoned conversation path.\n\t */\n\tbranchWithSummary(branchFromId: string | null, summary: string, details?: unknown, fromHook?: boolean): string {\n\t\tif (branchFromId !== null && !this.store.has(branchFromId)) {\n\t\t\tthrow new Error(`Entry ${branchFromId} not found`);\n\t\t}\n\t\tthis.store.setLeafId(branchFromId);\n\t\tconst entry: BranchSummaryEntry = {\n\t\t\ttype: \"branch_summary\",\n\t\t\tid: generateId(this.store),\n\t\t\tparentId: branchFromId,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tfromId: branchFromId ?? \"root\",\n\t\t\tsummary,\n\t\t\tdetails,\n\t\t\tfromHook,\n\t\t};\n\t\tthis._appendEntry(entry);\n\t\treturn entry.id;\n\t}\n\n\t/**\n\t * Create a new session reference containing only the path from root to the specified leaf.\n\t * Useful for extracting a single conversation path from a branched session.\n\t * Returns the new session reference, or undefined if the store does not expose one.\n\t */\n\tcreateBranchedSession(leafId: string): string | undefined {\n\t\tconst parentSession = this.getSessionReference();\n\t\tconst path = this.getBranch(leafId);\n\t\tif (path.length === 0) {\n\t\t\tthrow new Error(`Entry ${leafId} not found`);\n\t\t}\n\n\t\t// Filter out LabelEntry from path - we'll recreate them from the resolved map\n\t\tconst pathWithoutLabels = path.filter((e) => e.type !== \"label\");\n\n\t\tconst newSessionId = createSessionId();\n\t\tconst timestamp = new Date().toISOString();\n\t\tconst newSessionReference = this.prepareNewSessionReference(this.getSessionDir(), newSessionId, timestamp);\n\t\tif (newSessionReference) {\n\t\t\tthis.store.setSessionReference(newSessionReference);\n\t\t}\n\n\t\tconst header: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tversion: CURRENT_SESSION_VERSION,\n\t\t\tid: newSessionId,\n\t\t\ttimestamp,\n\t\t\tcwd: this.cwd,\n\t\t\tparentSession,\n\t\t};\n\n\t\t// Collect labels for entries in the path\n\t\tconst pathEntryIds = new Set(pathWithoutLabels.map((e) => e.id));\n\t\tconst labelsToWrite = this.store.getLabelsForEntryIds(pathEntryIds);\n\n\t\tconst labelEntries: LabelEntry[] = [];\n\t\tlet parentId = pathWithoutLabels[pathWithoutLabels.length - 1]?.id || null;\n\t\tfor (const { targetId, label, timestamp: labelTimestamp } of labelsToWrite) {\n\t\t\tconst labelEntry: LabelEntry = {\n\t\t\t\ttype: \"label\",\n\t\t\t\tid: generateId(new Set([...pathEntryIds, ...labelEntries.map((e) => e.id)])),\n\t\t\t\tparentId,\n\t\t\t\ttimestamp: labelTimestamp,\n\t\t\t\ttargetId,\n\t\t\t\tlabel,\n\t\t\t};\n\t\t\tpathEntryIds.add(labelEntry.id);\n\t\t\tlabelEntries.push(labelEntry);\n\t\t\tparentId = labelEntry.id;\n\t\t}\n\t\tthis.store.setEntries([header, ...pathWithoutLabels, ...labelEntries]);\n\t\tthis.sessionId = newSessionId;\n\t\tthis.store.commitSnapshot();\n\t\treturn newSessionReference;\n\t}\n\n\tcopyBranchFrom(source: Session, leafId: string, parentSession?: string): void {\n\t\tconst path = source.getBranch(leafId);\n\t\tif (path.length === 0) {\n\t\t\tthrow new Error(`Entry ${leafId} not found`);\n\t\t}\n\n\t\tconst header = this.getHeader();\n\t\tif (!header) {\n\t\t\tthrow new Error(\"Target session has no header\");\n\t\t}\n\n\t\tconst pathWithoutLabels = structuredClone(path.filter((entry) => entry.type !== \"label\"));\n\t\tconst pathEntryIds = new Set(pathWithoutLabels.map((entry) => entry.id));\n\t\tconst labelsToWrite = source.store.getLabelsForEntryIds(pathEntryIds);\n\t\tconst labelEntries: LabelEntry[] = [];\n\t\tlet nextParentId = pathWithoutLabels[pathWithoutLabels.length - 1]?.id ?? null;\n\t\tfor (const { targetId, label, timestamp } of labelsToWrite) {\n\t\t\tconst labelEntry: LabelEntry = {\n\t\t\t\ttype: \"label\",\n\t\t\t\tid: generateId(new Set([...pathEntryIds, ...labelEntries.map((entry) => entry.id)])),\n\t\t\t\tparentId: nextParentId,\n\t\t\t\ttimestamp,\n\t\t\t\ttargetId,\n\t\t\t\tlabel,\n\t\t\t};\n\t\t\tpathEntryIds.add(labelEntry.id);\n\t\t\tlabelEntries.push(labelEntry);\n\t\t\tnextParentId = labelEntry.id;\n\t\t}\n\n\t\tthis.store.setEntries([{ ...header, parentSession }, ...pathWithoutLabels, ...labelEntries]);\n\t\tthis.store.commitSnapshot();\n\t}\n\n\tcreateSubSession(options?: NewSessionOptions): SessionResult {\n\t\tif (!this.sessionManager) {\n\t\t\tthrow new Error(\"Session manager unavailable\");\n\t\t}\n\t\treturn this.sessionManager.create({\n\t\t\t...options,\n\t\t\tparentSession: options?.parentSession ?? this.getSessionReference(),\n\t\t});\n\t}\n\n\tforkSubSession(targetLeafId: string | null): SessionResult {\n\t\tif (!this.sessionManager) {\n\t\t\tthrow new Error(\"Session manager unavailable\");\n\t\t}\n\t\treturn this.sessionManager.forkSession(this, targetLeafId);\n\t}\n}\n"]}
@@ -1,26 +1,18 @@
1
- import type { FileEntry, SessionEntry, SessionHeader, SessionInfo, SessionListProgress, SessionTreeNode } from "../types.ts";
2
- import type { SessionOpenResult, SessionStore } from "./session-store.ts";
1
+ import type { FileEntry, SessionEntry, SessionHeader, SessionTreeNode } from "../types.ts";
2
+ import type { SessionStore } from "./session-store.ts";
3
3
  export declare class InMemorySessionStore implements SessionStore {
4
4
  protected fileEntries: FileEntry[];
5
5
  protected byId: Map<string, SessionEntry>;
6
6
  protected labelsById: Map<string, string>;
7
7
  protected labelTimestampsById: Map<string, string>;
8
8
  protected leafId: string | null;
9
+ private sessionReference;
9
10
  isPersisted(): boolean;
10
11
  getSessionReference(): string | undefined;
11
- setSessionReference(_reference: string): void;
12
- openSession(reference: string): SessionOpenResult;
13
- getSessionDirForReference(_reference: string): string;
14
- getDefaultSessionDir(_cwd: string, _agentDir?: string): string;
15
- getSessionsRoot(): string;
16
- prepareSessionReference(_sessionDir: string, _sessionId: string, _timestamp: string): string | undefined;
17
- getParentSessionReference(): string | undefined;
18
- exists(_path: string): boolean;
12
+ setSessionReference(reference: string): void;
13
+ exists(reference: string): boolean;
19
14
  ensureDir(_path: string): void;
20
- load(_filePath: string): FileEntry[];
21
- findMostRecent(_sessionDir: string): string | null;
22
- list(_dir: string, _onProgress?: SessionListProgress): Promise<SessionInfo[]>;
23
- listAll(_sessionsDir: string, _onProgress?: SessionListProgress): Promise<SessionInfo[]>;
15
+ load(reference: string): FileEntry[];
24
16
  setEntries(entries: FileEntry[]): void;
25
17
  getFileEntries(): FileEntry[];
26
18
  getHeader(): SessionHeader | null;
@@ -1 +1 @@
1
- {"version":3,"file":"in-memory-session-store.d.ts","sourceRoot":"","sources":["../../../../src/core/session/stores/in-memory-session-store.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n\tFileEntry,\n\tLabelEntry,\n\tSessionEntry,\n\tSessionHeader,\n\tSessionInfo,\n\tSessionListProgress,\n\tSessionTreeNode,\n} from \"../types.ts\";\nimport type { SessionOpenResult, SessionStore } from \"./session-store.ts\";\n\nexport class InMemorySessionStore implements SessionStore {\n\tprotected fileEntries: FileEntry[] = [];\n\tprotected byId: Map<string, SessionEntry> = new Map();\n\tprotected labelsById: Map<string, string> = new Map();\n\tprotected labelTimestampsById: Map<string, string> = new Map();\n\tprotected leafId: string | null = null;\n\n\tisPersisted(): boolean {\n\t\treturn false;\n\t}\n\n\tgetSessionReference(): string | undefined {\n\t\treturn undefined;\n\t}\n\n\tsetSessionReference(_reference: string): void {\n\t\t// No-op for in-memory sessions.\n\t}\n\n\topenSession(reference: string): SessionOpenResult {\n\t\treturn { reference, exists: false, entries: [] };\n\t}\n\n\tgetSessionDirForReference(_reference: string): string {\n\t\treturn \"\";\n\t}\n\n\tgetDefaultSessionDir(_cwd: string, _agentDir?: string): string {\n\t\treturn \"\";\n\t}\n\n\tgetSessionsRoot(): string {\n\t\treturn \"\";\n\t}\n\n\tprepareSessionReference(_sessionDir: string, _sessionId: string, _timestamp: string): string | undefined {\n\t\treturn undefined;\n\t}\n\n\tgetParentSessionReference(): string | undefined {\n\t\treturn undefined;\n\t}\n\n\texists(_path: string): boolean {\n\t\treturn false;\n\t}\n\n\tensureDir(_path: string): void {\n\t\t// No-op for in-memory sessions.\n\t}\n\n\tload(_filePath: string): FileEntry[] {\n\t\treturn [];\n\t}\n\n\tfindMostRecent(_sessionDir: string): string | null {\n\t\treturn null;\n\t}\n\n\tasync list(_dir: string, _onProgress?: SessionListProgress): Promise<SessionInfo[]> {\n\t\treturn [];\n\t}\n\n\tasync listAll(_sessionsDir: string, _onProgress?: SessionListProgress): Promise<SessionInfo[]> {\n\t\treturn [];\n\t}\n\n\tsetEntries(entries: FileEntry[]): void {\n\t\tthis.fileEntries = entries;\n\t\tthis.rebuildIndex();\n\t}\n\n\tgetFileEntries(): FileEntry[] {\n\t\treturn this.fileEntries;\n\t}\n\n\tgetHeader(): SessionHeader | null {\n\t\tconst header = this.fileEntries.find((entry) => entry.type === \"session\");\n\t\treturn header ? (header as SessionHeader) : null;\n\t}\n\n\tgetEntries(): SessionEntry[] {\n\t\treturn this.fileEntries.filter((entry): entry is SessionEntry => entry.type !== \"session\");\n\t}\n\n\tgetEntryIndex(): Map<string, SessionEntry> {\n\t\treturn this.byId;\n\t}\n\n\thas(id: string): boolean {\n\t\treturn this.byId.has(id);\n\t}\n\n\tappendEntry(entry: SessionEntry): void {\n\t\tthis.fileEntries.push(entry);\n\t\tthis.byId.set(entry.id, entry);\n\t\tthis.leafId = entry.id;\n\t\tthis.applyLabelEntry(entry);\n\t}\n\n\tsaveSnapshot(): void {\n\t\t// No-op for in-memory sessions.\n\t}\n\n\tcommitSnapshot(): void {\n\t\t// No-op for in-memory sessions.\n\t}\n\n\tgetLeafId(): string | null {\n\t\treturn this.leafId;\n\t}\n\n\tsetLeafId(leafId: string | null): void {\n\t\tthis.leafId = leafId;\n\t}\n\n\tgetLeafEntry(): SessionEntry | undefined {\n\t\treturn this.leafId ? this.byId.get(this.leafId) : undefined;\n\t}\n\n\tgetEntry(id: string): SessionEntry | undefined {\n\t\treturn this.byId.get(id);\n\t}\n\n\tgetChildren(parentId: string): SessionEntry[] {\n\t\tconst children: SessionEntry[] = [];\n\t\tfor (const entry of this.byId.values()) {\n\t\t\tif (entry.parentId === parentId) {\n\t\t\t\tchildren.push(entry);\n\t\t\t}\n\t\t}\n\t\treturn children;\n\t}\n\n\tgetLabel(id: string): string | undefined {\n\t\treturn this.labelsById.get(id);\n\t}\n\n\tgetBranch(fromId?: string): SessionEntry[] {\n\t\tconst path: SessionEntry[] = [];\n\t\tconst startId = fromId ?? this.leafId;\n\t\tlet current = startId ? this.byId.get(startId) : undefined;\n\t\twhile (current) {\n\t\t\tpath.unshift(current);\n\t\t\tcurrent = current.parentId ? this.byId.get(current.parentId) : undefined;\n\t\t}\n\t\treturn path;\n\t}\n\n\tgetTree(): SessionTreeNode[] {\n\t\tconst entries = this.getEntries();\n\t\tconst nodeMap = new Map<string, SessionTreeNode>();\n\t\tconst roots: SessionTreeNode[] = [];\n\n\t\tfor (const entry of entries) {\n\t\t\tconst label = this.labelsById.get(entry.id);\n\t\t\tconst labelTimestamp = this.labelTimestampsById.get(entry.id);\n\t\t\tnodeMap.set(entry.id, { entry, children: [], label, labelTimestamp });\n\t\t}\n\n\t\tfor (const entry of entries) {\n\t\t\tconst node = nodeMap.get(entry.id)!;\n\t\t\tif (entry.parentId === null || entry.parentId === entry.id) {\n\t\t\t\troots.push(node);\n\t\t\t} else {\n\t\t\t\tconst parent = nodeMap.get(entry.parentId);\n\t\t\t\tif (parent) {\n\t\t\t\t\tparent.children.push(node);\n\t\t\t\t} else {\n\t\t\t\t\troots.push(node);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst stack: SessionTreeNode[] = [...roots];\n\t\twhile (stack.length > 0) {\n\t\t\tconst node = stack.pop()!;\n\t\t\tnode.children.sort((a, b) => new Date(a.entry.timestamp).getTime() - new Date(b.entry.timestamp).getTime());\n\t\t\tstack.push(...node.children);\n\t\t}\n\n\t\treturn roots;\n\t}\n\n\tgetSessionName(): string | undefined {\n\t\tconst entries = this.getEntries();\n\t\tfor (let i = entries.length - 1; i >= 0; i--) {\n\t\t\tconst entry = entries[i];\n\t\t\tif (entry.type === \"session_info\") {\n\t\t\t\treturn entry.name?.trim() || undefined;\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tgetLabelsForEntryIds(entryIds: Set<string>): Array<{ targetId: string; label: string; timestamp: string }> {\n\t\tconst labels: Array<{ targetId: string; label: string; timestamp: string }> = [];\n\t\tfor (const [targetId, label] of this.labelsById) {\n\t\t\tif (entryIds.has(targetId)) {\n\t\t\t\tlabels.push({ targetId, label, timestamp: this.labelTimestampsById.get(targetId)! });\n\t\t\t}\n\t\t}\n\t\treturn labels;\n\t}\n\n\thasAssistantMessage(): boolean {\n\t\treturn this.fileEntries.some((entry) => entry.type === \"message\" && entry.message.role === \"assistant\");\n\t}\n\n\tprivate rebuildIndex(): void {\n\t\tthis.byId.clear();\n\t\tthis.labelsById.clear();\n\t\tthis.labelTimestampsById.clear();\n\t\tthis.leafId = null;\n\t\tfor (const entry of this.fileEntries) {\n\t\t\tif (entry.type === \"session\") continue;\n\t\t\tthis.byId.set(entry.id, entry);\n\t\t\tthis.leafId = entry.id;\n\t\t\tthis.applyLabelEntry(entry);\n\t\t}\n\t}\n\n\tprivate applyLabelEntry(entry: SessionEntry): void {\n\t\tif (entry.type !== \"label\") return;\n\t\tconst labelEntry = entry as LabelEntry;\n\t\tif (labelEntry.label) {\n\t\t\tthis.labelsById.set(labelEntry.targetId, labelEntry.label);\n\t\t\tthis.labelTimestampsById.set(labelEntry.targetId, labelEntry.timestamp);\n\t\t} else {\n\t\t\tthis.labelsById.delete(labelEntry.targetId);\n\t\t\tthis.labelTimestampsById.delete(labelEntry.targetId);\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"in-memory-session-store.d.ts","sourceRoot":"","sources":["../../../../src/core/session/stores/in-memory-session-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAc,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACvG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,qBAAa,oBAAqB,YAAW,YAAY;IACxD,SAAS,CAAC,WAAW,EAAE,SAAS,EAAE,CAAM;IACxC,SAAS,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAa;IACtD,SAAS,CAAC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAa;IACtD,SAAS,CAAC,mBAAmB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAa;IAC/D,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAQ;IACvC,OAAO,CAAC,gBAAgB,CAAqB;IAE7C,WAAW,IAAI,OAAO,CAErB;IAED,mBAAmB,IAAI,MAAM,GAAG,SAAS,CAExC;IAED,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAE3C;IAED,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAEjC;IAED,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAE7B;IAED,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,CAEnC;IAED,UAAU,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,IAAI,CAGrC;IAED,cAAc,IAAI,SAAS,EAAE,CAE5B;IAED,SAAS,IAAI,aAAa,GAAG,IAAI,CAGhC;IAED,UAAU,IAAI,YAAY,EAAE,CAE3B;IAED,aAAa,IAAI,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAEzC;IAED,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAEvB;IAED,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAKrC;IAED,YAAY,IAAI,IAAI,CAEnB;IAED,cAAc,IAAI,IAAI,CAErB;IAED,SAAS,IAAI,MAAM,GAAG,IAAI,CAEzB;IAED,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAErC;IAED,YAAY,IAAI,YAAY,GAAG,SAAS,CAEvC;IAED,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAE7C;IAED,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,EAAE,CAQ5C;IAED,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEvC;IAED,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,YAAY,EAAE,CASzC;IAED,OAAO,IAAI,eAAe,EAAE,CAiC3B;IAED,cAAc,IAAI,MAAM,GAAG,SAAS,CASnC;IAED,oBAAoB,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAQzG;IAED,mBAAmB,IAAI,OAAO,CAE7B;IAED,OAAO,CAAC,YAAY;IAapB,OAAO,CAAC,eAAe;CAWvB","sourcesContent":["import type { FileEntry, LabelEntry, SessionEntry, SessionHeader, SessionTreeNode } from \"../types.ts\";\nimport type { SessionStore } from \"./session-store.ts\";\n\nexport class InMemorySessionStore implements SessionStore {\n\tprotected fileEntries: FileEntry[] = [];\n\tprotected byId: Map<string, SessionEntry> = new Map();\n\tprotected labelsById: Map<string, string> = new Map();\n\tprotected labelTimestampsById: Map<string, string> = new Map();\n\tprotected leafId: string | null = null;\n\tprivate sessionReference: string | undefined;\n\n\tisPersisted(): boolean {\n\t\treturn false;\n\t}\n\n\tgetSessionReference(): string | undefined {\n\t\treturn this.sessionReference;\n\t}\n\n\tsetSessionReference(reference: string): void {\n\t\tthis.sessionReference = reference;\n\t}\n\n\texists(reference: string): boolean {\n\t\treturn reference === this.sessionReference && this.fileEntries.length > 0;\n\t}\n\n\tensureDir(_path: string): void {\n\t\t// No-op for in-memory sessions.\n\t}\n\n\tload(reference: string): FileEntry[] {\n\t\treturn reference === this.sessionReference ? [...this.fileEntries] : [];\n\t}\n\n\tsetEntries(entries: FileEntry[]): void {\n\t\tthis.fileEntries = entries;\n\t\tthis.rebuildIndex();\n\t}\n\n\tgetFileEntries(): FileEntry[] {\n\t\treturn this.fileEntries;\n\t}\n\n\tgetHeader(): SessionHeader | null {\n\t\tconst header = this.fileEntries.find((entry) => entry.type === \"session\");\n\t\treturn header ? (header as SessionHeader) : null;\n\t}\n\n\tgetEntries(): SessionEntry[] {\n\t\treturn this.fileEntries.filter((entry): entry is SessionEntry => entry.type !== \"session\");\n\t}\n\n\tgetEntryIndex(): Map<string, SessionEntry> {\n\t\treturn this.byId;\n\t}\n\n\thas(id: string): boolean {\n\t\treturn this.byId.has(id);\n\t}\n\n\tappendEntry(entry: SessionEntry): void {\n\t\tthis.fileEntries.push(entry);\n\t\tthis.byId.set(entry.id, entry);\n\t\tthis.leafId = entry.id;\n\t\tthis.applyLabelEntry(entry);\n\t}\n\n\tsaveSnapshot(): void {\n\t\t// No-op for in-memory sessions.\n\t}\n\n\tcommitSnapshot(): void {\n\t\t// No-op for in-memory sessions.\n\t}\n\n\tgetLeafId(): string | null {\n\t\treturn this.leafId;\n\t}\n\n\tsetLeafId(leafId: string | null): void {\n\t\tthis.leafId = leafId;\n\t}\n\n\tgetLeafEntry(): SessionEntry | undefined {\n\t\treturn this.leafId ? this.byId.get(this.leafId) : undefined;\n\t}\n\n\tgetEntry(id: string): SessionEntry | undefined {\n\t\treturn this.byId.get(id);\n\t}\n\n\tgetChildren(parentId: string): SessionEntry[] {\n\t\tconst children: SessionEntry[] = [];\n\t\tfor (const entry of this.byId.values()) {\n\t\t\tif (entry.parentId === parentId) {\n\t\t\t\tchildren.push(entry);\n\t\t\t}\n\t\t}\n\t\treturn children;\n\t}\n\n\tgetLabel(id: string): string | undefined {\n\t\treturn this.labelsById.get(id);\n\t}\n\n\tgetBranch(fromId?: string): SessionEntry[] {\n\t\tconst path: SessionEntry[] = [];\n\t\tconst startId = fromId ?? this.leafId;\n\t\tlet current = startId ? this.byId.get(startId) : undefined;\n\t\twhile (current) {\n\t\t\tpath.unshift(current);\n\t\t\tcurrent = current.parentId ? this.byId.get(current.parentId) : undefined;\n\t\t}\n\t\treturn path;\n\t}\n\n\tgetTree(): SessionTreeNode[] {\n\t\tconst entries = this.getEntries();\n\t\tconst nodeMap = new Map<string, SessionTreeNode>();\n\t\tconst roots: SessionTreeNode[] = [];\n\n\t\tfor (const entry of entries) {\n\t\t\tconst label = this.labelsById.get(entry.id);\n\t\t\tconst labelTimestamp = this.labelTimestampsById.get(entry.id);\n\t\t\tnodeMap.set(entry.id, { entry, children: [], label, labelTimestamp });\n\t\t}\n\n\t\tfor (const entry of entries) {\n\t\t\tconst node = nodeMap.get(entry.id)!;\n\t\t\tif (entry.parentId === null || entry.parentId === entry.id) {\n\t\t\t\troots.push(node);\n\t\t\t} else {\n\t\t\t\tconst parent = nodeMap.get(entry.parentId);\n\t\t\t\tif (parent) {\n\t\t\t\t\tparent.children.push(node);\n\t\t\t\t} else {\n\t\t\t\t\troots.push(node);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst stack: SessionTreeNode[] = [...roots];\n\t\twhile (stack.length > 0) {\n\t\t\tconst node = stack.pop()!;\n\t\t\tnode.children.sort((a, b) => new Date(a.entry.timestamp).getTime() - new Date(b.entry.timestamp).getTime());\n\t\t\tstack.push(...node.children);\n\t\t}\n\n\t\treturn roots;\n\t}\n\n\tgetSessionName(): string | undefined {\n\t\tconst entries = this.getEntries();\n\t\tfor (let i = entries.length - 1; i >= 0; i--) {\n\t\t\tconst entry = entries[i];\n\t\t\tif (entry.type === \"session_info\") {\n\t\t\t\treturn entry.name?.trim() || undefined;\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tgetLabelsForEntryIds(entryIds: Set<string>): Array<{ targetId: string; label: string; timestamp: string }> {\n\t\tconst labels: Array<{ targetId: string; label: string; timestamp: string }> = [];\n\t\tfor (const [targetId, label] of this.labelsById) {\n\t\t\tif (entryIds.has(targetId)) {\n\t\t\t\tlabels.push({ targetId, label, timestamp: this.labelTimestampsById.get(targetId)! });\n\t\t\t}\n\t\t}\n\t\treturn labels;\n\t}\n\n\thasAssistantMessage(): boolean {\n\t\treturn this.fileEntries.some((entry) => entry.type === \"message\" && entry.message.role === \"assistant\");\n\t}\n\n\tprivate rebuildIndex(): void {\n\t\tthis.byId.clear();\n\t\tthis.labelsById.clear();\n\t\tthis.labelTimestampsById.clear();\n\t\tthis.leafId = null;\n\t\tfor (const entry of this.fileEntries) {\n\t\t\tif (entry.type === \"session\") continue;\n\t\t\tthis.byId.set(entry.id, entry);\n\t\t\tthis.leafId = entry.id;\n\t\t\tthis.applyLabelEntry(entry);\n\t\t}\n\t}\n\n\tprivate applyLabelEntry(entry: SessionEntry): void {\n\t\tif (entry.type !== \"label\") return;\n\t\tconst labelEntry = entry as LabelEntry;\n\t\tif (labelEntry.label) {\n\t\t\tthis.labelsById.set(labelEntry.targetId, labelEntry.label);\n\t\t\tthis.labelTimestampsById.set(labelEntry.targetId, labelEntry.timestamp);\n\t\t} else {\n\t\t\tthis.labelsById.delete(labelEntry.targetId);\n\t\t\tthis.labelTimestampsById.delete(labelEntry.targetId);\n\t\t}\n\t}\n}\n"]}
@@ -4,50 +4,24 @@ export class InMemorySessionStore {
4
4
  labelsById = new Map();
5
5
  labelTimestampsById = new Map();
6
6
  leafId = null;
7
+ sessionReference;
7
8
  isPersisted() {
8
9
  return false;
9
10
  }
10
11
  getSessionReference() {
11
- return undefined;
12
- }
13
- setSessionReference(_reference) {
14
- // No-op for in-memory sessions.
15
- }
16
- openSession(reference) {
17
- return { reference, exists: false, entries: [] };
18
- }
19
- getSessionDirForReference(_reference) {
20
- return "";
21
- }
22
- getDefaultSessionDir(_cwd, _agentDir) {
23
- return "";
24
- }
25
- getSessionsRoot() {
26
- return "";
12
+ return this.sessionReference;
27
13
  }
28
- prepareSessionReference(_sessionDir, _sessionId, _timestamp) {
29
- return undefined;
30
- }
31
- getParentSessionReference() {
32
- return undefined;
14
+ setSessionReference(reference) {
15
+ this.sessionReference = reference;
33
16
  }
34
- exists(_path) {
35
- return false;
17
+ exists(reference) {
18
+ return reference === this.sessionReference && this.fileEntries.length > 0;
36
19
  }
37
20
  ensureDir(_path) {
38
21
  // No-op for in-memory sessions.
39
22
  }
40
- load(_filePath) {
41
- return [];
42
- }
43
- findMostRecent(_sessionDir) {
44
- return null;
45
- }
46
- async list(_dir, _onProgress) {
47
- return [];
48
- }
49
- async listAll(_sessionsDir, _onProgress) {
50
- return [];
23
+ load(reference) {
24
+ return reference === this.sessionReference ? [...this.fileEntries] : [];
51
25
  }
52
26
  setEntries(entries) {
53
27
  this.fileEntries = entries;
@@ -1 +1 @@
1
- {"version":3,"file":"in-memory-session-store.js","sourceRoot":"","sources":["../../../../src/core/session/stores/in-memory-session-store.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n\tFileEntry,\n\tLabelEntry,\n\tSessionEntry,\n\tSessionHeader,\n\tSessionInfo,\n\tSessionListProgress,\n\tSessionTreeNode,\n} from \"../types.ts\";\nimport type { SessionOpenResult, SessionStore } from \"./session-store.ts\";\n\nexport class InMemorySessionStore implements SessionStore {\n\tprotected fileEntries: FileEntry[] = [];\n\tprotected byId: Map<string, SessionEntry> = new Map();\n\tprotected labelsById: Map<string, string> = new Map();\n\tprotected labelTimestampsById: Map<string, string> = new Map();\n\tprotected leafId: string | null = null;\n\n\tisPersisted(): boolean {\n\t\treturn false;\n\t}\n\n\tgetSessionReference(): string | undefined {\n\t\treturn undefined;\n\t}\n\n\tsetSessionReference(_reference: string): void {\n\t\t// No-op for in-memory sessions.\n\t}\n\n\topenSession(reference: string): SessionOpenResult {\n\t\treturn { reference, exists: false, entries: [] };\n\t}\n\n\tgetSessionDirForReference(_reference: string): string {\n\t\treturn \"\";\n\t}\n\n\tgetDefaultSessionDir(_cwd: string, _agentDir?: string): string {\n\t\treturn \"\";\n\t}\n\n\tgetSessionsRoot(): string {\n\t\treturn \"\";\n\t}\n\n\tprepareSessionReference(_sessionDir: string, _sessionId: string, _timestamp: string): string | undefined {\n\t\treturn undefined;\n\t}\n\n\tgetParentSessionReference(): string | undefined {\n\t\treturn undefined;\n\t}\n\n\texists(_path: string): boolean {\n\t\treturn false;\n\t}\n\n\tensureDir(_path: string): void {\n\t\t// No-op for in-memory sessions.\n\t}\n\n\tload(_filePath: string): FileEntry[] {\n\t\treturn [];\n\t}\n\n\tfindMostRecent(_sessionDir: string): string | null {\n\t\treturn null;\n\t}\n\n\tasync list(_dir: string, _onProgress?: SessionListProgress): Promise<SessionInfo[]> {\n\t\treturn [];\n\t}\n\n\tasync listAll(_sessionsDir: string, _onProgress?: SessionListProgress): Promise<SessionInfo[]> {\n\t\treturn [];\n\t}\n\n\tsetEntries(entries: FileEntry[]): void {\n\t\tthis.fileEntries = entries;\n\t\tthis.rebuildIndex();\n\t}\n\n\tgetFileEntries(): FileEntry[] {\n\t\treturn this.fileEntries;\n\t}\n\n\tgetHeader(): SessionHeader | null {\n\t\tconst header = this.fileEntries.find((entry) => entry.type === \"session\");\n\t\treturn header ? (header as SessionHeader) : null;\n\t}\n\n\tgetEntries(): SessionEntry[] {\n\t\treturn this.fileEntries.filter((entry): entry is SessionEntry => entry.type !== \"session\");\n\t}\n\n\tgetEntryIndex(): Map<string, SessionEntry> {\n\t\treturn this.byId;\n\t}\n\n\thas(id: string): boolean {\n\t\treturn this.byId.has(id);\n\t}\n\n\tappendEntry(entry: SessionEntry): void {\n\t\tthis.fileEntries.push(entry);\n\t\tthis.byId.set(entry.id, entry);\n\t\tthis.leafId = entry.id;\n\t\tthis.applyLabelEntry(entry);\n\t}\n\n\tsaveSnapshot(): void {\n\t\t// No-op for in-memory sessions.\n\t}\n\n\tcommitSnapshot(): void {\n\t\t// No-op for in-memory sessions.\n\t}\n\n\tgetLeafId(): string | null {\n\t\treturn this.leafId;\n\t}\n\n\tsetLeafId(leafId: string | null): void {\n\t\tthis.leafId = leafId;\n\t}\n\n\tgetLeafEntry(): SessionEntry | undefined {\n\t\treturn this.leafId ? this.byId.get(this.leafId) : undefined;\n\t}\n\n\tgetEntry(id: string): SessionEntry | undefined {\n\t\treturn this.byId.get(id);\n\t}\n\n\tgetChildren(parentId: string): SessionEntry[] {\n\t\tconst children: SessionEntry[] = [];\n\t\tfor (const entry of this.byId.values()) {\n\t\t\tif (entry.parentId === parentId) {\n\t\t\t\tchildren.push(entry);\n\t\t\t}\n\t\t}\n\t\treturn children;\n\t}\n\n\tgetLabel(id: string): string | undefined {\n\t\treturn this.labelsById.get(id);\n\t}\n\n\tgetBranch(fromId?: string): SessionEntry[] {\n\t\tconst path: SessionEntry[] = [];\n\t\tconst startId = fromId ?? this.leafId;\n\t\tlet current = startId ? this.byId.get(startId) : undefined;\n\t\twhile (current) {\n\t\t\tpath.unshift(current);\n\t\t\tcurrent = current.parentId ? this.byId.get(current.parentId) : undefined;\n\t\t}\n\t\treturn path;\n\t}\n\n\tgetTree(): SessionTreeNode[] {\n\t\tconst entries = this.getEntries();\n\t\tconst nodeMap = new Map<string, SessionTreeNode>();\n\t\tconst roots: SessionTreeNode[] = [];\n\n\t\tfor (const entry of entries) {\n\t\t\tconst label = this.labelsById.get(entry.id);\n\t\t\tconst labelTimestamp = this.labelTimestampsById.get(entry.id);\n\t\t\tnodeMap.set(entry.id, { entry, children: [], label, labelTimestamp });\n\t\t}\n\n\t\tfor (const entry of entries) {\n\t\t\tconst node = nodeMap.get(entry.id)!;\n\t\t\tif (entry.parentId === null || entry.parentId === entry.id) {\n\t\t\t\troots.push(node);\n\t\t\t} else {\n\t\t\t\tconst parent = nodeMap.get(entry.parentId);\n\t\t\t\tif (parent) {\n\t\t\t\t\tparent.children.push(node);\n\t\t\t\t} else {\n\t\t\t\t\troots.push(node);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst stack: SessionTreeNode[] = [...roots];\n\t\twhile (stack.length > 0) {\n\t\t\tconst node = stack.pop()!;\n\t\t\tnode.children.sort((a, b) => new Date(a.entry.timestamp).getTime() - new Date(b.entry.timestamp).getTime());\n\t\t\tstack.push(...node.children);\n\t\t}\n\n\t\treturn roots;\n\t}\n\n\tgetSessionName(): string | undefined {\n\t\tconst entries = this.getEntries();\n\t\tfor (let i = entries.length - 1; i >= 0; i--) {\n\t\t\tconst entry = entries[i];\n\t\t\tif (entry.type === \"session_info\") {\n\t\t\t\treturn entry.name?.trim() || undefined;\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tgetLabelsForEntryIds(entryIds: Set<string>): Array<{ targetId: string; label: string; timestamp: string }> {\n\t\tconst labels: Array<{ targetId: string; label: string; timestamp: string }> = [];\n\t\tfor (const [targetId, label] of this.labelsById) {\n\t\t\tif (entryIds.has(targetId)) {\n\t\t\t\tlabels.push({ targetId, label, timestamp: this.labelTimestampsById.get(targetId)! });\n\t\t\t}\n\t\t}\n\t\treturn labels;\n\t}\n\n\thasAssistantMessage(): boolean {\n\t\treturn this.fileEntries.some((entry) => entry.type === \"message\" && entry.message.role === \"assistant\");\n\t}\n\n\tprivate rebuildIndex(): void {\n\t\tthis.byId.clear();\n\t\tthis.labelsById.clear();\n\t\tthis.labelTimestampsById.clear();\n\t\tthis.leafId = null;\n\t\tfor (const entry of this.fileEntries) {\n\t\t\tif (entry.type === \"session\") continue;\n\t\t\tthis.byId.set(entry.id, entry);\n\t\t\tthis.leafId = entry.id;\n\t\t\tthis.applyLabelEntry(entry);\n\t\t}\n\t}\n\n\tprivate applyLabelEntry(entry: SessionEntry): void {\n\t\tif (entry.type !== \"label\") return;\n\t\tconst labelEntry = entry as LabelEntry;\n\t\tif (labelEntry.label) {\n\t\t\tthis.labelsById.set(labelEntry.targetId, labelEntry.label);\n\t\t\tthis.labelTimestampsById.set(labelEntry.targetId, labelEntry.timestamp);\n\t\t} else {\n\t\t\tthis.labelsById.delete(labelEntry.targetId);\n\t\t\tthis.labelTimestampsById.delete(labelEntry.targetId);\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"in-memory-session-store.js","sourceRoot":"","sources":["../../../../src/core/session/stores/in-memory-session-store.ts"],"names":[],"mappings":"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","sourcesContent":["import type { FileEntry, LabelEntry, SessionEntry, SessionHeader, SessionTreeNode } from \"../types.ts\";\nimport type { SessionStore } from \"./session-store.ts\";\n\nexport class InMemorySessionStore implements SessionStore {\n\tprotected fileEntries: FileEntry[] = [];\n\tprotected byId: Map<string, SessionEntry> = new Map();\n\tprotected labelsById: Map<string, string> = new Map();\n\tprotected labelTimestampsById: Map<string, string> = new Map();\n\tprotected leafId: string | null = null;\n\tprivate sessionReference: string | undefined;\n\n\tisPersisted(): boolean {\n\t\treturn false;\n\t}\n\n\tgetSessionReference(): string | undefined {\n\t\treturn this.sessionReference;\n\t}\n\n\tsetSessionReference(reference: string): void {\n\t\tthis.sessionReference = reference;\n\t}\n\n\texists(reference: string): boolean {\n\t\treturn reference === this.sessionReference && this.fileEntries.length > 0;\n\t}\n\n\tensureDir(_path: string): void {\n\t\t// No-op for in-memory sessions.\n\t}\n\n\tload(reference: string): FileEntry[] {\n\t\treturn reference === this.sessionReference ? [...this.fileEntries] : [];\n\t}\n\n\tsetEntries(entries: FileEntry[]): void {\n\t\tthis.fileEntries = entries;\n\t\tthis.rebuildIndex();\n\t}\n\n\tgetFileEntries(): FileEntry[] {\n\t\treturn this.fileEntries;\n\t}\n\n\tgetHeader(): SessionHeader | null {\n\t\tconst header = this.fileEntries.find((entry) => entry.type === \"session\");\n\t\treturn header ? (header as SessionHeader) : null;\n\t}\n\n\tgetEntries(): SessionEntry[] {\n\t\treturn this.fileEntries.filter((entry): entry is SessionEntry => entry.type !== \"session\");\n\t}\n\n\tgetEntryIndex(): Map<string, SessionEntry> {\n\t\treturn this.byId;\n\t}\n\n\thas(id: string): boolean {\n\t\treturn this.byId.has(id);\n\t}\n\n\tappendEntry(entry: SessionEntry): void {\n\t\tthis.fileEntries.push(entry);\n\t\tthis.byId.set(entry.id, entry);\n\t\tthis.leafId = entry.id;\n\t\tthis.applyLabelEntry(entry);\n\t}\n\n\tsaveSnapshot(): void {\n\t\t// No-op for in-memory sessions.\n\t}\n\n\tcommitSnapshot(): void {\n\t\t// No-op for in-memory sessions.\n\t}\n\n\tgetLeafId(): string | null {\n\t\treturn this.leafId;\n\t}\n\n\tsetLeafId(leafId: string | null): void {\n\t\tthis.leafId = leafId;\n\t}\n\n\tgetLeafEntry(): SessionEntry | undefined {\n\t\treturn this.leafId ? this.byId.get(this.leafId) : undefined;\n\t}\n\n\tgetEntry(id: string): SessionEntry | undefined {\n\t\treturn this.byId.get(id);\n\t}\n\n\tgetChildren(parentId: string): SessionEntry[] {\n\t\tconst children: SessionEntry[] = [];\n\t\tfor (const entry of this.byId.values()) {\n\t\t\tif (entry.parentId === parentId) {\n\t\t\t\tchildren.push(entry);\n\t\t\t}\n\t\t}\n\t\treturn children;\n\t}\n\n\tgetLabel(id: string): string | undefined {\n\t\treturn this.labelsById.get(id);\n\t}\n\n\tgetBranch(fromId?: string): SessionEntry[] {\n\t\tconst path: SessionEntry[] = [];\n\t\tconst startId = fromId ?? this.leafId;\n\t\tlet current = startId ? this.byId.get(startId) : undefined;\n\t\twhile (current) {\n\t\t\tpath.unshift(current);\n\t\t\tcurrent = current.parentId ? this.byId.get(current.parentId) : undefined;\n\t\t}\n\t\treturn path;\n\t}\n\n\tgetTree(): SessionTreeNode[] {\n\t\tconst entries = this.getEntries();\n\t\tconst nodeMap = new Map<string, SessionTreeNode>();\n\t\tconst roots: SessionTreeNode[] = [];\n\n\t\tfor (const entry of entries) {\n\t\t\tconst label = this.labelsById.get(entry.id);\n\t\t\tconst labelTimestamp = this.labelTimestampsById.get(entry.id);\n\t\t\tnodeMap.set(entry.id, { entry, children: [], label, labelTimestamp });\n\t\t}\n\n\t\tfor (const entry of entries) {\n\t\t\tconst node = nodeMap.get(entry.id)!;\n\t\t\tif (entry.parentId === null || entry.parentId === entry.id) {\n\t\t\t\troots.push(node);\n\t\t\t} else {\n\t\t\t\tconst parent = nodeMap.get(entry.parentId);\n\t\t\t\tif (parent) {\n\t\t\t\t\tparent.children.push(node);\n\t\t\t\t} else {\n\t\t\t\t\troots.push(node);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst stack: SessionTreeNode[] = [...roots];\n\t\twhile (stack.length > 0) {\n\t\t\tconst node = stack.pop()!;\n\t\t\tnode.children.sort((a, b) => new Date(a.entry.timestamp).getTime() - new Date(b.entry.timestamp).getTime());\n\t\t\tstack.push(...node.children);\n\t\t}\n\n\t\treturn roots;\n\t}\n\n\tgetSessionName(): string | undefined {\n\t\tconst entries = this.getEntries();\n\t\tfor (let i = entries.length - 1; i >= 0; i--) {\n\t\t\tconst entry = entries[i];\n\t\t\tif (entry.type === \"session_info\") {\n\t\t\t\treturn entry.name?.trim() || undefined;\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tgetLabelsForEntryIds(entryIds: Set<string>): Array<{ targetId: string; label: string; timestamp: string }> {\n\t\tconst labels: Array<{ targetId: string; label: string; timestamp: string }> = [];\n\t\tfor (const [targetId, label] of this.labelsById) {\n\t\t\tif (entryIds.has(targetId)) {\n\t\t\t\tlabels.push({ targetId, label, timestamp: this.labelTimestampsById.get(targetId)! });\n\t\t\t}\n\t\t}\n\t\treturn labels;\n\t}\n\n\thasAssistantMessage(): boolean {\n\t\treturn this.fileEntries.some((entry) => entry.type === \"message\" && entry.message.role === \"assistant\");\n\t}\n\n\tprivate rebuildIndex(): void {\n\t\tthis.byId.clear();\n\t\tthis.labelsById.clear();\n\t\tthis.labelTimestampsById.clear();\n\t\tthis.leafId = null;\n\t\tfor (const entry of this.fileEntries) {\n\t\t\tif (entry.type === \"session\") continue;\n\t\t\tthis.byId.set(entry.id, entry);\n\t\t\tthis.leafId = entry.id;\n\t\t\tthis.applyLabelEntry(entry);\n\t\t}\n\t}\n\n\tprivate applyLabelEntry(entry: SessionEntry): void {\n\t\tif (entry.type !== \"label\") return;\n\t\tconst labelEntry = entry as LabelEntry;\n\t\tif (labelEntry.label) {\n\t\t\tthis.labelsById.set(labelEntry.targetId, labelEntry.label);\n\t\t\tthis.labelTimestampsById.set(labelEntry.targetId, labelEntry.timestamp);\n\t\t} else {\n\t\t\tthis.labelsById.delete(labelEntry.targetId);\n\t\t\tthis.labelTimestampsById.delete(labelEntry.targetId);\n\t\t}\n\t}\n}\n"]}
@@ -1,30 +1,30 @@
1
1
  import type { FileEntry, SessionEntry, SessionHeader, SessionInfo, SessionListProgress } from "../types.ts";
2
2
  import { InMemorySessionStore } from "./in-memory-session-store.ts";
3
- import type { SessionOpenResult } from "./session-store.ts";
3
+ export declare function getSessionDirForReference(reference: string): string;
4
+ export declare function getDefaultSessionDir(cwd: string, agentDir?: string): string;
5
+ export declare function getSessionsRoot(): string;
6
+ export declare function prepareSessionReference(sessionDir: string, sessionId: string, timestamp: string): string;
7
+ export declare function exists(path: string): boolean;
8
+ export declare function ensureDir(path: string): void;
9
+ export declare function load(filePath: string): FileEntry[];
10
+ export declare function append(filePath: string, entry: FileEntry): void;
11
+ export declare function rewrite(filePath: string, entries: FileEntry[]): void;
12
+ export declare function forkSession(sessionDir: string, header: SessionHeader, sourceEntries: FileEntry[]): string;
13
+ export declare function findMostRecent(sessionDir: string): string | null;
14
+ export declare function list(dir: string, onProgress?: SessionListProgress): Promise<SessionInfo[]>;
15
+ export declare function listAll(sessionsDir: string, onProgress?: SessionListProgress): Promise<SessionInfo[]>;
4
16
  export declare class JsonlSessionStore extends InMemorySessionStore {
5
- private sessionReference;
17
+ private reference;
6
18
  private flushed;
7
19
  isPersisted(): boolean;
8
20
  getSessionReference(): string | undefined;
9
21
  setSessionReference(reference: string): void;
10
- openSession(reference: string): SessionOpenResult;
11
- getSessionDirForReference(reference: string): string;
12
- getDefaultSessionDir(cwd: string, agentDir?: string): string;
13
- getSessionsRoot(): string;
14
- prepareSessionReference(sessionDir: string, sessionId: string, timestamp: string): string | undefined;
15
- getParentSessionReference(): string | undefined;
16
22
  exists(path: string): boolean;
17
23
  ensureDir(path: string): void;
18
24
  load(filePath: string): FileEntry[];
19
- append(filePath: string, entry: FileEntry): void;
20
- rewrite(filePath: string, entries: FileEntry[]): void;
21
25
  appendEntry(entry: SessionEntry): void;
22
26
  private persistAppendedEntry;
23
27
  saveSnapshot(): void;
24
28
  commitSnapshot(): void;
25
- forkSession(sessionDir: string, header: SessionHeader, sourceEntries: FileEntry[]): string;
26
- findMostRecent(sessionDir: string): string | null;
27
- list(dir: string, onProgress?: SessionListProgress): Promise<SessionInfo[]>;
28
- listAll(sessionsDir: string, onProgress?: SessionListProgress): Promise<SessionInfo[]>;
29
29
  }
30
30
  //# sourceMappingURL=jsonl-session-store.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"jsonl-session-store.d.ts","sourceRoot":"","sources":["../../../../src/core/session/stores/jsonl-session-store.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EACX,SAAS,EACT,YAAY,EAEZ,aAAa,EACb,WAAW,EAEX,mBAAmB,EAEnB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAsL5D,qBAAa,iBAAkB,SAAQ,oBAAoB;IAC1D,OAAO,CAAC,gBAAgB,CAAqB;IAC7C,OAAO,CAAC,OAAO,CAAS;IAExB,WAAW,IAAI,OAAO,CAErB;IAED,mBAAmB,IAAI,MAAM,GAAG,SAAS,CAExC;IAED,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAG3C;IAED,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,iBAAiB,CAShD;IAED,yBAAyB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEnD;IAED,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,GAAE,MAA6B,GAAG,MAAM,CAKjF;IAED,eAAe,IAAI,MAAM,CAExB;IAED,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAKpG;IAED,yBAAyB,IAAI,MAAM,GAAG,SAAS,CAE9C;IAED,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE5B;IAED,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAI5B;IAED,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,CAyBlC;IAED,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,CAE/C;IAED,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,IAAI,CAGpD;IAED,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAGrC;IAED,OAAO,CAAC,oBAAoB;IAkB5B,YAAY,IAAI,IAAI,CAInB;IAED,cAAc,IAAI,IAAI,CAMrB;IAED,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,GAAG,MAAM,CAczF;IAED,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAahD;IAEK,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAwBhF;IAEK,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAsC3F;CACD","sourcesContent":["import type { AgentMessage } from \"@fleetagent/pi-agent-core\";\nimport type { Message, TextContent } from \"@fleetagent/pi-ai\";\nimport {\n\tappendFileSync,\n\tcloseSync,\n\texistsSync,\n\tmkdirSync,\n\topenSync,\n\treaddirSync,\n\treadFileSync,\n\treadSync,\n\tstatSync,\n\twriteFileSync,\n} from \"fs\";\nimport { readdir, readFile, stat } from \"fs/promises\";\nimport { join, resolve } from \"path\";\nimport { getAgentDir as getDefaultAgentDir, getSessionsDir } from \"../../../config.ts\";\nimport type {\n\tFileEntry,\n\tSessionEntry,\n\tSessionEntryBase,\n\tSessionHeader,\n\tSessionInfo,\n\tSessionInfoEntry,\n\tSessionListProgress,\n\tSessionMessageEntry,\n} from \"../types.ts\";\nimport { InMemorySessionStore } from \"./in-memory-session-store.ts\";\nimport type { SessionOpenResult } from \"./session-store.ts\";\n\nfunction isMessageWithContent(message: AgentMessage): message is Message {\n\treturn typeof (message as Message).role === \"string\" && \"content\" in message;\n}\n\nfunction extractTextContent(message: Message): string {\n\tconst content = message.content;\n\tif (typeof content === \"string\") {\n\t\treturn content;\n\t}\n\treturn content\n\t\t.filter((block): block is TextContent => block.type === \"text\")\n\t\t.map((block) => block.text)\n\t\t.join(\" \");\n}\n\nfunction getLastActivityTime(entries: FileEntry[]): number | undefined {\n\tlet lastActivityTime: number | undefined;\n\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"message\") continue;\n\n\t\tconst message = (entry as SessionMessageEntry).message;\n\t\tif (!isMessageWithContent(message)) continue;\n\t\tif (message.role !== \"user\" && message.role !== \"assistant\") continue;\n\n\t\tconst msgTimestamp = (message as { timestamp?: number }).timestamp;\n\t\tif (typeof msgTimestamp === \"number\") {\n\t\t\tlastActivityTime = Math.max(lastActivityTime ?? 0, msgTimestamp);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst entryTimestamp = (entry as SessionEntryBase).timestamp;\n\t\tif (typeof entryTimestamp === \"string\") {\n\t\t\tconst t = new Date(entryTimestamp).getTime();\n\t\t\tif (!Number.isNaN(t)) {\n\t\t\t\tlastActivityTime = Math.max(lastActivityTime ?? 0, t);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn lastActivityTime;\n}\n\nfunction getSessionModifiedDate(entries: FileEntry[], header: SessionHeader, statsMtime: Date): Date {\n\tconst lastActivityTime = getLastActivityTime(entries);\n\tif (typeof lastActivityTime === \"number\" && lastActivityTime > 0) {\n\t\treturn new Date(lastActivityTime);\n\t}\n\n\tconst headerTime = typeof header.timestamp === \"string\" ? new Date(header.timestamp).getTime() : NaN;\n\treturn !Number.isNaN(headerTime) ? new Date(headerTime) : statsMtime;\n}\n\nfunction isValidSessionFile(filePath: string): boolean {\n\ttry {\n\t\tconst fd = openSync(filePath, \"r\");\n\t\tconst buffer = Buffer.alloc(512);\n\t\tconst bytesRead = readSync(fd, buffer, 0, 512, 0);\n\t\tcloseSync(fd);\n\t\tconst firstLine = buffer.toString(\"utf8\", 0, bytesRead).split(\"\\n\")[0];\n\t\tif (!firstLine) return false;\n\t\tconst header = JSON.parse(firstLine) as Partial<SessionHeader>;\n\t\treturn header.type === \"session\" && typeof header.id === \"string\";\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nasync function buildSessionInfo(filePath: string): Promise<SessionInfo | null> {\n\ttry {\n\t\tconst content = await readFile(filePath, \"utf8\");\n\t\tconst entries: FileEntry[] = [];\n\t\tconst lines = content.trim().split(\"\\n\");\n\n\t\tfor (const line of lines) {\n\t\t\tif (!line.trim()) continue;\n\t\t\ttry {\n\t\t\t\tentries.push(JSON.parse(line) as FileEntry);\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\n\t\tif (entries.length === 0) return null;\n\t\tconst header = entries[0];\n\t\tif (header.type !== \"session\") return null;\n\n\t\tconst stats = await stat(filePath);\n\t\tlet messageCount = 0;\n\t\tlet firstMessage = \"\";\n\t\tconst allMessages: string[] = [];\n\t\tlet name: string | undefined;\n\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.type === \"session_info\") {\n\t\t\t\tconst infoEntry = entry as SessionInfoEntry;\n\t\t\t\tname = infoEntry.name?.trim() || undefined;\n\t\t\t}\n\n\t\t\tif (entry.type !== \"message\") continue;\n\t\t\tmessageCount++;\n\n\t\t\tconst message = (entry as SessionMessageEntry).message;\n\t\t\tif (!isMessageWithContent(message)) continue;\n\t\t\tif (message.role !== \"user\" && message.role !== \"assistant\") continue;\n\n\t\t\tconst textContent = extractTextContent(message);\n\t\t\tif (!textContent) continue;\n\n\t\t\tallMessages.push(textContent);\n\t\t\tif (!firstMessage && message.role === \"user\") {\n\t\t\t\tfirstMessage = textContent;\n\t\t\t}\n\t\t}\n\n\t\tconst sessionHeader = header as SessionHeader;\n\t\tconst cwd = typeof sessionHeader.cwd === \"string\" ? sessionHeader.cwd : \"\";\n\t\tconst parentSessionPath = sessionHeader.parentSession;\n\t\tconst modified = getSessionModifiedDate(entries, sessionHeader, stats.mtime);\n\n\t\treturn {\n\t\t\treference: filePath,\n\t\t\tpath: filePath,\n\t\t\tid: sessionHeader.id,\n\t\t\tcwd,\n\t\t\tname,\n\t\t\tparentSessionPath,\n\t\t\tcreated: new Date(sessionHeader.timestamp),\n\t\t\tmodified,\n\t\t\tmessageCount,\n\t\t\tfirstMessage: firstMessage || \"(no messages)\",\n\t\t\tallMessagesText: allMessages.join(\" \"),\n\t\t};\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nconst MAX_CONCURRENT_SESSION_INFO_LOADS = 10;\n\nasync function buildSessionInfosWithConcurrency(\n\tfiles: string[],\n\tonLoaded: () => void,\n): Promise<(SessionInfo | null)[]> {\n\tconst results: (SessionInfo | null)[] = new Array(files.length).fill(null);\n\tconst inFlight = new Set<Promise<void>>();\n\tlet nextIndex = 0;\n\n\tconst startNext = (): void => {\n\t\tconst index = nextIndex++;\n\t\tconst file = files[index];\n\t\tif (!file) return;\n\n\t\tlet task: Promise<void>;\n\t\ttask = buildSessionInfo(file)\n\t\t\t.then((info) => {\n\t\t\t\tresults[index] = info;\n\t\t\t})\n\t\t\t.catch(() => {\n\t\t\t\tresults[index] = null;\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\tinFlight.delete(task);\n\t\t\t\tonLoaded();\n\t\t\t});\n\t\tinFlight.add(task);\n\t};\n\n\twhile (nextIndex < files.length || inFlight.size > 0) {\n\t\twhile (nextIndex < files.length && inFlight.size < MAX_CONCURRENT_SESSION_INFO_LOADS) {\n\t\t\tstartNext();\n\t\t}\n\t\tif (inFlight.size > 0) {\n\t\t\tawait Promise.race(inFlight);\n\t\t}\n\t}\n\n\treturn results;\n}\n\nexport class JsonlSessionStore extends InMemorySessionStore {\n\tprivate sessionReference: string | undefined;\n\tprivate flushed = false;\n\n\tisPersisted(): boolean {\n\t\treturn true;\n\t}\n\n\tgetSessionReference(): string | undefined {\n\t\treturn this.sessionReference;\n\t}\n\n\tsetSessionReference(reference: string): void {\n\t\tthis.sessionReference = resolve(reference);\n\t\tthis.flushed = false;\n\t}\n\n\topenSession(reference: string): SessionOpenResult {\n\t\tthis.setSessionReference(reference);\n\t\tconst currentReference = this.sessionReference!;\n\t\tconst fileExists = this.exists(currentReference);\n\t\treturn {\n\t\t\treference: currentReference,\n\t\t\texists: fileExists,\n\t\t\tentries: fileExists ? this.load(currentReference) : [],\n\t\t};\n\t}\n\n\tgetSessionDirForReference(reference: string): string {\n\t\treturn resolve(reference, \"..\");\n\t}\n\n\tgetDefaultSessionDir(cwd: string, agentDir: string = getDefaultAgentDir()): string {\n\t\tconst safePath = `--${cwd.replace(/^[/\\\\]/, \"\").replace(/[/\\\\:]/g, \"-\")}--`;\n\t\tconst sessionDir = join(agentDir, \"sessions\", safePath);\n\t\tthis.ensureDir(sessionDir);\n\t\treturn sessionDir;\n\t}\n\n\tgetSessionsRoot(): string {\n\t\treturn getSessionsDir();\n\t}\n\n\tprepareSessionReference(sessionDir: string, sessionId: string, timestamp: string): string | undefined {\n\t\tconst fileTimestamp = timestamp.replace(/[:.]/g, \"-\");\n\t\tthis.sessionReference = join(sessionDir, `${fileTimestamp}_${sessionId}.jsonl`);\n\t\tthis.flushed = false;\n\t\treturn this.sessionReference;\n\t}\n\n\tgetParentSessionReference(): string | undefined {\n\t\treturn this.sessionReference;\n\t}\n\n\texists(path: string): boolean {\n\t\treturn existsSync(path);\n\t}\n\n\tensureDir(path: string): void {\n\t\tif (!existsSync(path)) {\n\t\t\tmkdirSync(path, { recursive: true });\n\t\t}\n\t}\n\n\tload(filePath: string): FileEntry[] {\n\t\tif (!existsSync(filePath)) return [];\n\t\tthis.flushed = true;\n\n\t\tconst content = readFileSync(filePath, \"utf8\");\n\t\tconst entries: FileEntry[] = [];\n\t\tconst lines = content.trim().split(\"\\n\");\n\n\t\tfor (const line of lines) {\n\t\t\tif (!line.trim()) continue;\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line) as FileEntry;\n\t\t\t\tentries.push(entry);\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\n\t\tif (entries.length === 0) return entries;\n\t\tconst header = entries[0];\n\t\tif (header.type !== \"session\" || typeof (header as Partial<SessionHeader>).id !== \"string\") {\n\t\t\treturn [];\n\t\t}\n\n\t\treturn entries;\n\t}\n\n\tappend(filePath: string, entry: FileEntry): void {\n\t\tappendFileSync(filePath, `${JSON.stringify(entry)}\\n`);\n\t}\n\n\trewrite(filePath: string, entries: FileEntry[]): void {\n\t\tconst content = `${entries.map((entry) => JSON.stringify(entry)).join(\"\\n\")}\\n`;\n\t\twriteFileSync(filePath, content);\n\t}\n\n\tappendEntry(entry: SessionEntry): void {\n\t\tsuper.appendEntry(entry);\n\t\tthis.persistAppendedEntry(entry);\n\t}\n\n\tprivate persistAppendedEntry(entry: SessionEntry): void {\n\t\tif (!this.sessionReference) return;\n\n\t\tif (!this.hasAssistantMessage()) {\n\t\t\tthis.flushed = false;\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.flushed) {\n\t\t\tfor (const fileEntry of this.getFileEntries()) {\n\t\t\t\tthis.append(this.sessionReference, fileEntry);\n\t\t\t}\n\t\t\tthis.flushed = true;\n\t\t} else {\n\t\t\tthis.append(this.sessionReference, entry);\n\t\t}\n\t}\n\n\tsaveSnapshot(): void {\n\t\tif (!this.sessionReference) return;\n\t\tthis.rewrite(this.sessionReference, this.getFileEntries());\n\t\tthis.flushed = true;\n\t}\n\n\tcommitSnapshot(): void {\n\t\tif (!this.hasAssistantMessage()) {\n\t\t\tthis.flushed = false;\n\t\t\treturn;\n\t\t}\n\t\tthis.saveSnapshot();\n\t}\n\n\tforkSession(sessionDir: string, header: SessionHeader, sourceEntries: FileEntry[]): string {\n\t\tthis.prepareSessionReference(sessionDir, header.id, header.timestamp);\n\t\tconst reference = this.getSessionReference();\n\t\tif (!reference) {\n\t\t\tthrow new Error(\"Failed to prepare forked session reference\");\n\t\t}\n\t\tthis.append(reference, header);\n\t\tfor (const entry of sourceEntries) {\n\t\t\tif (entry.type !== \"session\") {\n\t\t\t\tthis.append(reference, entry);\n\t\t\t}\n\t\t}\n\t\tthis.flushed = true;\n\t\treturn reference;\n\t}\n\n\tfindMostRecent(sessionDir: string): string | null {\n\t\ttry {\n\t\t\tconst files = readdirSync(sessionDir)\n\t\t\t\t.filter((file) => file.endsWith(\".jsonl\"))\n\t\t\t\t.map((file) => join(sessionDir, file))\n\t\t\t\t.filter(isValidSessionFile)\n\t\t\t\t.map((path) => ({ path, mtime: statSync(path).mtime }))\n\t\t\t\t.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());\n\n\t\t\treturn files[0]?.path || null;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tasync list(dir: string, onProgress?: SessionListProgress): Promise<SessionInfo[]> {\n\t\tconst sessions: SessionInfo[] = [];\n\t\tif (!existsSync(dir)) {\n\t\t\treturn sessions;\n\t\t}\n\n\t\ttry {\n\t\t\tconst dirEntries = await readdir(dir);\n\t\t\tconst files = dirEntries.filter((file) => file.endsWith(\".jsonl\")).map((file) => join(dir, file));\n\t\t\tlet loaded = 0;\n\t\t\tconst results = await buildSessionInfosWithConcurrency(files, () => {\n\t\t\t\tloaded++;\n\t\t\t\tonProgress?.(loaded, files.length);\n\t\t\t});\n\t\t\tfor (const info of results) {\n\t\t\t\tif (info) {\n\t\t\t\t\tsessions.push(info);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Return empty list on error\n\t\t}\n\n\t\treturn sessions;\n\t}\n\n\tasync listAll(sessionsDir: string, onProgress?: SessionListProgress): Promise<SessionInfo[]> {\n\t\ttry {\n\t\t\tif (!existsSync(sessionsDir)) {\n\t\t\t\treturn [];\n\t\t\t}\n\t\t\tconst entries = await readdir(sessionsDir, { withFileTypes: true });\n\t\t\tconst dirs = entries.filter((entry) => entry.isDirectory()).map((entry) => join(sessionsDir, entry.name));\n\n\t\t\tlet totalFiles = 0;\n\t\t\tconst dirFiles: string[][] = [];\n\t\t\tfor (const dir of dirs) {\n\t\t\t\ttry {\n\t\t\t\t\tconst files = (await readdir(dir)).filter((file) => file.endsWith(\".jsonl\"));\n\t\t\t\t\tdirFiles.push(files.map((file) => join(dir, file)));\n\t\t\t\t\ttotalFiles += files.length;\n\t\t\t\t} catch {\n\t\t\t\t\tdirFiles.push([]);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet loaded = 0;\n\t\t\tconst sessions: SessionInfo[] = [];\n\t\t\tconst allFiles = dirFiles.flat();\n\t\t\tconst results = await buildSessionInfosWithConcurrency(allFiles, () => {\n\t\t\t\tloaded++;\n\t\t\t\tonProgress?.(loaded, totalFiles);\n\t\t\t});\n\n\t\t\tfor (const info of results) {\n\t\t\t\tif (info) {\n\t\t\t\t\tsessions.push(info);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn sessions;\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"jsonl-session-store.d.ts","sourceRoot":"","sources":["../../../../src/core/session/stores/jsonl-session-store.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EACX,SAAS,EACT,YAAY,EAEZ,aAAa,EACb,WAAW,EAEX,mBAAmB,EAEnB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AA8IpE,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEnE;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,GAAE,MAA6B,GAAG,MAAM,CAKjG;AAED,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAGxG;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE5C;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAI5C;AAED,wBAAgB,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,CAwBlD;AAED,wBAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,CAE/D;AAED,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,IAAI,CAGpE;AAED,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,GAAG,MAAM,CASzG;AAED,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAahE;AAED,wBAAsB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAwBhG;AAED,wBAAsB,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAsC3G;AA0CD,qBAAa,iBAAkB,SAAQ,oBAAoB;IAC1D,OAAO,CAAC,SAAS,CAAqB;IACtC,OAAO,CAAC,OAAO,CAAS;IAExB,WAAW,IAAI,OAAO,CAErB;IAED,mBAAmB,IAAI,MAAM,GAAG,SAAS,CAExC;IAED,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAG3C;IAED,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE5B;IAED,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAE5B;IAED,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,CAIlC;IAED,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAGrC;IAED,OAAO,CAAC,oBAAoB;IAkB5B,YAAY,IAAI,IAAI,CAInB;IAED,cAAc,IAAI,IAAI,CAMrB;CACD","sourcesContent":["import type { AgentMessage } from \"@fleetagent/pi-agent-core\";\nimport type { Message, TextContent } from \"@fleetagent/pi-ai\";\nimport {\n\tappendFileSync,\n\tcloseSync,\n\texistsSync,\n\tmkdirSync,\n\topenSync,\n\treaddirSync,\n\treadFileSync,\n\treadSync,\n\tstatSync,\n\twriteFileSync,\n} from \"fs\";\nimport { readdir, readFile, stat } from \"fs/promises\";\nimport { join, resolve } from \"path\";\nimport { getAgentDir as getDefaultAgentDir, getSessionsDir } from \"../../../config.ts\";\nimport type {\n\tFileEntry,\n\tSessionEntry,\n\tSessionEntryBase,\n\tSessionHeader,\n\tSessionInfo,\n\tSessionInfoEntry,\n\tSessionListProgress,\n\tSessionMessageEntry,\n} from \"../types.ts\";\nimport { InMemorySessionStore } from \"./in-memory-session-store.ts\";\n\nfunction isMessageWithContent(message: AgentMessage): message is Message {\n\treturn typeof (message as Message).role === \"string\" && \"content\" in message;\n}\n\nfunction extractTextContent(message: Message): string {\n\tconst content = message.content;\n\tif (typeof content === \"string\") {\n\t\treturn content;\n\t}\n\treturn content\n\t\t.filter((block): block is TextContent => block.type === \"text\")\n\t\t.map((block) => block.text)\n\t\t.join(\" \");\n}\n\nfunction getLastActivityTime(entries: FileEntry[]): number | undefined {\n\tlet lastActivityTime: number | undefined;\n\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"message\") continue;\n\n\t\tconst message = (entry as SessionMessageEntry).message;\n\t\tif (!isMessageWithContent(message)) continue;\n\t\tif (message.role !== \"user\" && message.role !== \"assistant\") continue;\n\n\t\tconst msgTimestamp = (message as { timestamp?: number }).timestamp;\n\t\tif (typeof msgTimestamp === \"number\") {\n\t\t\tlastActivityTime = Math.max(lastActivityTime ?? 0, msgTimestamp);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst entryTimestamp = (entry as SessionEntryBase).timestamp;\n\t\tif (typeof entryTimestamp === \"string\") {\n\t\t\tconst t = new Date(entryTimestamp).getTime();\n\t\t\tif (!Number.isNaN(t)) {\n\t\t\t\tlastActivityTime = Math.max(lastActivityTime ?? 0, t);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn lastActivityTime;\n}\n\nfunction getSessionModifiedDate(entries: FileEntry[], header: SessionHeader, statsMtime: Date): Date {\n\tconst lastActivityTime = getLastActivityTime(entries);\n\tif (typeof lastActivityTime === \"number\" && lastActivityTime > 0) {\n\t\treturn new Date(lastActivityTime);\n\t}\n\n\tconst headerTime = typeof header.timestamp === \"string\" ? new Date(header.timestamp).getTime() : NaN;\n\treturn !Number.isNaN(headerTime) ? new Date(headerTime) : statsMtime;\n}\n\nfunction isValidSessionFile(filePath: string): boolean {\n\ttry {\n\t\tconst fd = openSync(filePath, \"r\");\n\t\tconst buffer = Buffer.alloc(512);\n\t\tconst bytesRead = readSync(fd, buffer, 0, 512, 0);\n\t\tcloseSync(fd);\n\t\tconst firstLine = buffer.toString(\"utf8\", 0, bytesRead).split(\"\\n\")[0];\n\t\tif (!firstLine) return false;\n\t\tconst header = JSON.parse(firstLine) as Partial<SessionHeader>;\n\t\treturn header.type === \"session\" && typeof header.id === \"string\";\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nasync function buildSessionInfo(filePath: string): Promise<SessionInfo | null> {\n\ttry {\n\t\tconst content = await readFile(filePath, \"utf8\");\n\t\tconst entries: FileEntry[] = [];\n\t\tconst lines = content.trim().split(\"\\n\");\n\n\t\tfor (const line of lines) {\n\t\t\tif (!line.trim()) continue;\n\t\t\ttry {\n\t\t\t\tentries.push(JSON.parse(line) as FileEntry);\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\n\t\tif (entries.length === 0) return null;\n\t\tconst header = entries[0];\n\t\tif (header.type !== \"session\") return null;\n\n\t\tconst stats = await stat(filePath);\n\t\tlet messageCount = 0;\n\t\tlet firstMessage = \"\";\n\t\tconst allMessages: string[] = [];\n\t\tlet name: string | undefined;\n\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.type === \"session_info\") {\n\t\t\t\tconst infoEntry = entry as SessionInfoEntry;\n\t\t\t\tname = infoEntry.name?.trim() || undefined;\n\t\t\t}\n\n\t\t\tif (entry.type !== \"message\") continue;\n\t\t\tmessageCount++;\n\n\t\t\tconst message = (entry as SessionMessageEntry).message;\n\t\t\tif (!isMessageWithContent(message)) continue;\n\t\t\tif (message.role !== \"user\" && message.role !== \"assistant\") continue;\n\n\t\t\tconst textContent = extractTextContent(message);\n\t\t\tif (!textContent) continue;\n\n\t\t\tallMessages.push(textContent);\n\t\t\tif (!firstMessage && message.role === \"user\") {\n\t\t\t\tfirstMessage = textContent;\n\t\t\t}\n\t\t}\n\n\t\tconst sessionHeader = header as SessionHeader;\n\t\tconst cwd = typeof sessionHeader.cwd === \"string\" ? sessionHeader.cwd : \"\";\n\t\tconst parentSessionPath = sessionHeader.parentSession;\n\t\tconst modified = getSessionModifiedDate(entries, sessionHeader, stats.mtime);\n\n\t\treturn {\n\t\t\treference: filePath,\n\t\t\tpath: filePath,\n\t\t\tid: sessionHeader.id,\n\t\t\tcwd,\n\t\t\tname,\n\t\t\tparentSessionPath,\n\t\t\tcreated: new Date(sessionHeader.timestamp),\n\t\t\tmodified,\n\t\t\tmessageCount,\n\t\t\tfirstMessage: firstMessage || \"(no messages)\",\n\t\t\tallMessagesText: allMessages.join(\" \"),\n\t\t};\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nconst MAX_CONCURRENT_SESSION_INFO_LOADS = 10;\n\nexport function getSessionDirForReference(reference: string): string {\n\treturn resolve(reference, \"..\");\n}\n\nexport function getDefaultSessionDir(cwd: string, agentDir: string = getDefaultAgentDir()): string {\n\tconst safePath = `--${cwd.replace(/^[/\\\\]/, \"\").replace(/[/\\\\:]/g, \"-\")}--`;\n\tconst sessionDir = join(agentDir, \"sessions\", safePath);\n\tensureDir(sessionDir);\n\treturn sessionDir;\n}\n\nexport function getSessionsRoot(): string {\n\treturn getSessionsDir();\n}\n\nexport function prepareSessionReference(sessionDir: string, sessionId: string, timestamp: string): string {\n\tconst fileTimestamp = timestamp.replace(/[:.]/g, \"-\");\n\treturn join(sessionDir, `${fileTimestamp}_${sessionId}.jsonl`);\n}\n\nexport function exists(path: string): boolean {\n\treturn existsSync(path);\n}\n\nexport function ensureDir(path: string): void {\n\tif (!existsSync(path)) {\n\t\tmkdirSync(path, { recursive: true });\n\t}\n}\n\nexport function load(filePath: string): FileEntry[] {\n\tif (!existsSync(filePath)) return [];\n\n\tconst content = readFileSync(filePath, \"utf8\");\n\tconst entries: FileEntry[] = [];\n\tconst lines = content.trim().split(\"\\n\");\n\n\tfor (const line of lines) {\n\t\tif (!line.trim()) continue;\n\t\ttry {\n\t\t\tconst entry = JSON.parse(line) as FileEntry;\n\t\t\tentries.push(entry);\n\t\t} catch {\n\t\t\t// Skip malformed lines\n\t\t}\n\t}\n\n\tif (entries.length === 0) return entries;\n\tconst header = entries[0];\n\tif (header.type !== \"session\" || typeof (header as Partial<SessionHeader>).id !== \"string\") {\n\t\treturn [];\n\t}\n\n\treturn entries;\n}\n\nexport function append(filePath: string, entry: FileEntry): void {\n\tappendFileSync(filePath, `${JSON.stringify(entry)}\\n`);\n}\n\nexport function rewrite(filePath: string, entries: FileEntry[]): void {\n\tconst content = `${entries.map((entry) => JSON.stringify(entry)).join(\"\\n\")}\\n`;\n\twriteFileSync(filePath, content);\n}\n\nexport function forkSession(sessionDir: string, header: SessionHeader, sourceEntries: FileEntry[]): string {\n\tconst reference = prepareSessionReference(sessionDir, header.id, header.timestamp);\n\tappend(reference, header);\n\tfor (const entry of sourceEntries) {\n\t\tif (entry.type !== \"session\") {\n\t\t\tappend(reference, entry);\n\t\t}\n\t}\n\treturn reference;\n}\n\nexport function findMostRecent(sessionDir: string): string | null {\n\ttry {\n\t\tconst files = readdirSync(sessionDir)\n\t\t\t.filter((file) => file.endsWith(\".jsonl\"))\n\t\t\t.map((file) => join(sessionDir, file))\n\t\t\t.filter(isValidSessionFile)\n\t\t\t.map((path) => ({ path, mtime: statSync(path).mtime }))\n\t\t\t.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());\n\n\t\treturn files[0]?.path || null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nexport async function list(dir: string, onProgress?: SessionListProgress): Promise<SessionInfo[]> {\n\tconst sessions: SessionInfo[] = [];\n\tif (!existsSync(dir)) {\n\t\treturn sessions;\n\t}\n\n\ttry {\n\t\tconst dirEntries = await readdir(dir);\n\t\tconst files = dirEntries.filter((file) => file.endsWith(\".jsonl\")).map((file) => join(dir, file));\n\t\tlet loaded = 0;\n\t\tconst results = await buildSessionInfosWithConcurrency(files, () => {\n\t\t\tloaded++;\n\t\t\tonProgress?.(loaded, files.length);\n\t\t});\n\t\tfor (const info of results) {\n\t\t\tif (info) {\n\t\t\t\tsessions.push(info);\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// Return empty list on error\n\t}\n\n\treturn sessions;\n}\n\nexport async function listAll(sessionsDir: string, onProgress?: SessionListProgress): Promise<SessionInfo[]> {\n\ttry {\n\t\tif (!existsSync(sessionsDir)) {\n\t\t\treturn [];\n\t\t}\n\t\tconst entries = await readdir(sessionsDir, { withFileTypes: true });\n\t\tconst dirs = entries.filter((entry) => entry.isDirectory()).map((entry) => join(sessionsDir, entry.name));\n\n\t\tlet totalFiles = 0;\n\t\tconst dirFiles: string[][] = [];\n\t\tfor (const dir of dirs) {\n\t\t\ttry {\n\t\t\t\tconst files = (await readdir(dir)).filter((file) => file.endsWith(\".jsonl\"));\n\t\t\t\tdirFiles.push(files.map((file) => join(dir, file)));\n\t\t\t\ttotalFiles += files.length;\n\t\t\t} catch {\n\t\t\t\tdirFiles.push([]);\n\t\t\t}\n\t\t}\n\n\t\tlet loaded = 0;\n\t\tconst sessions: SessionInfo[] = [];\n\t\tconst allFiles = dirFiles.flat();\n\t\tconst results = await buildSessionInfosWithConcurrency(allFiles, () => {\n\t\t\tloaded++;\n\t\t\tonProgress?.(loaded, totalFiles);\n\t\t});\n\n\t\tfor (const info of results) {\n\t\t\tif (info) {\n\t\t\t\tsessions.push(info);\n\t\t\t}\n\t\t}\n\n\t\treturn sessions;\n\t} catch {\n\t\treturn [];\n\t}\n}\n\nasync function buildSessionInfosWithConcurrency(\n\tfiles: string[],\n\tonLoaded: () => void,\n): Promise<(SessionInfo | null)[]> {\n\tconst results: (SessionInfo | null)[] = new Array(files.length).fill(null);\n\tconst inFlight = new Set<Promise<void>>();\n\tlet nextIndex = 0;\n\n\tconst startNext = (): void => {\n\t\tconst index = nextIndex++;\n\t\tconst file = files[index];\n\t\tif (!file) return;\n\n\t\tlet task: Promise<void>;\n\t\ttask = buildSessionInfo(file)\n\t\t\t.then((info) => {\n\t\t\t\tresults[index] = info;\n\t\t\t})\n\t\t\t.catch(() => {\n\t\t\t\tresults[index] = null;\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\tinFlight.delete(task);\n\t\t\t\tonLoaded();\n\t\t\t});\n\t\tinFlight.add(task);\n\t};\n\n\twhile (nextIndex < files.length || inFlight.size > 0) {\n\t\twhile (nextIndex < files.length && inFlight.size < MAX_CONCURRENT_SESSION_INFO_LOADS) {\n\t\t\tstartNext();\n\t\t}\n\t\tif (inFlight.size > 0) {\n\t\t\tawait Promise.race(inFlight);\n\t\t}\n\t}\n\n\treturn results;\n}\n\nexport class JsonlSessionStore extends InMemorySessionStore {\n\tprivate reference: string | undefined;\n\tprivate flushed = false;\n\n\tisPersisted(): boolean {\n\t\treturn true;\n\t}\n\n\tgetSessionReference(): string | undefined {\n\t\treturn this.reference;\n\t}\n\n\tsetSessionReference(reference: string): void {\n\t\tthis.reference = resolve(reference);\n\t\tthis.flushed = false;\n\t}\n\n\texists(path: string): boolean {\n\t\treturn exists(path);\n\t}\n\n\tensureDir(path: string): void {\n\t\tensureDir(path);\n\t}\n\n\tload(filePath: string): FileEntry[] {\n\t\tif (!exists(filePath)) return [];\n\t\tthis.flushed = true;\n\t\treturn load(filePath);\n\t}\n\n\tappendEntry(entry: SessionEntry): void {\n\t\tsuper.appendEntry(entry);\n\t\tthis.persistAppendedEntry(entry);\n\t}\n\n\tprivate persistAppendedEntry(entry: SessionEntry): void {\n\t\tif (!this.reference) return;\n\n\t\tif (!this.hasAssistantMessage()) {\n\t\t\tthis.flushed = false;\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.flushed) {\n\t\t\tfor (const fileEntry of this.getFileEntries()) {\n\t\t\t\tappend(this.reference, fileEntry);\n\t\t\t}\n\t\t\tthis.flushed = true;\n\t\t} else {\n\t\t\tappend(this.reference, entry);\n\t\t}\n\t}\n\n\tsaveSnapshot(): void {\n\t\tif (!this.reference) return;\n\t\trewrite(this.reference, this.getFileEntries());\n\t\tthis.flushed = true;\n\t}\n\n\tcommitSnapshot(): void {\n\t\tif (!this.hasAssistantMessage()) {\n\t\t\tthis.flushed = false;\n\t\t\treturn;\n\t\t}\n\t\tthis.saveSnapshot();\n\t}\n}\n"]}