@oh-my-pi/pi-coding-agent 11.8.2 → 11.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/docs/tui.md +9 -9
  2. package/package.json +7 -7
  3. package/src/cli/file-processor.ts +8 -13
  4. package/src/cli/oclif-help.ts +1 -1
  5. package/src/cli.ts +14 -0
  6. package/src/commit/git/index.ts +16 -16
  7. package/src/config/keybindings.ts +11 -11
  8. package/src/config/model-registry.ts +31 -66
  9. package/src/config/settings.ts +88 -95
  10. package/src/config.ts +2 -2
  11. package/src/cursor.ts +4 -4
  12. package/src/debug/index.ts +28 -28
  13. package/src/discovery/codex.ts +5 -13
  14. package/src/discovery/cursor.ts +2 -7
  15. package/src/exa/mcp-client.ts +2 -2
  16. package/src/exa/websets.ts +2 -2
  17. package/src/export/html/index.ts +3 -3
  18. package/src/export/ttsr.ts +27 -27
  19. package/src/extensibility/custom-tools/loader.ts +9 -9
  20. package/src/extensibility/extensions/runner.ts +64 -64
  21. package/src/extensibility/hooks/runner.ts +46 -46
  22. package/src/extensibility/plugins/manager.ts +49 -49
  23. package/src/index.ts +0 -1
  24. package/src/internal-urls/router.ts +5 -5
  25. package/src/ipy/kernel.ts +61 -57
  26. package/src/lsp/client.ts +1 -1
  27. package/src/lsp/clients/biome-client.ts +2 -2
  28. package/src/lsp/clients/lsp-linter-client.ts +7 -7
  29. package/src/lsp/index.ts +9 -9
  30. package/src/mcp/manager.ts +47 -47
  31. package/src/mcp/tool-bridge.ts +12 -12
  32. package/src/mcp/transports/http.ts +34 -34
  33. package/src/mcp/transports/stdio.ts +47 -47
  34. package/src/modes/components/assistant-message.ts +25 -25
  35. package/src/modes/components/bash-execution.ts +51 -51
  36. package/src/modes/components/bordered-loader.ts +7 -7
  37. package/src/modes/components/branch-summary-message.ts +7 -7
  38. package/src/modes/components/compaction-summary-message.ts +7 -7
  39. package/src/modes/components/countdown-timer.ts +15 -15
  40. package/src/modes/components/custom-editor.ts +22 -22
  41. package/src/modes/components/custom-message.ts +21 -21
  42. package/src/modes/components/dynamic-border.ts +3 -3
  43. package/src/modes/components/extensions/extension-dashboard.ts +72 -72
  44. package/src/modes/components/extensions/extension-list.ts +99 -97
  45. package/src/modes/components/extensions/inspector-panel.ts +26 -26
  46. package/src/modes/components/footer.ts +36 -36
  47. package/src/modes/components/history-search.ts +52 -52
  48. package/src/modes/components/hook-editor.ts +20 -20
  49. package/src/modes/components/hook-input.ts +20 -20
  50. package/src/modes/components/hook-message.ts +22 -22
  51. package/src/modes/components/hook-selector.ts +52 -52
  52. package/src/modes/components/index.ts +0 -1
  53. package/src/modes/components/login-dialog.ts +57 -57
  54. package/src/modes/components/model-selector.ts +173 -173
  55. package/src/modes/components/oauth-selector.ts +45 -45
  56. package/src/modes/components/plugin-settings.ts +52 -52
  57. package/src/modes/components/python-execution.ts +53 -53
  58. package/src/modes/components/queue-mode-selector.ts +7 -7
  59. package/src/modes/components/read-tool-group.ts +23 -23
  60. package/src/modes/components/session-selector.ts +40 -37
  61. package/src/modes/components/settings-selector.ts +80 -80
  62. package/src/modes/components/show-images-selector.ts +7 -7
  63. package/src/modes/components/skill-message.ts +27 -27
  64. package/src/modes/components/status-line-segment-editor.ts +81 -81
  65. package/src/modes/components/status-line.ts +73 -73
  66. package/src/modes/components/theme-selector.ts +11 -11
  67. package/src/modes/components/thinking-selector.ts +7 -7
  68. package/src/modes/components/todo-display.ts +19 -19
  69. package/src/modes/components/todo-reminder.ts +9 -9
  70. package/src/modes/components/tool-execution.ts +204 -196
  71. package/src/modes/components/tree-selector.ts +144 -144
  72. package/src/modes/components/ttsr-notification.ts +17 -17
  73. package/src/modes/components/user-message-selector.ts +18 -18
  74. package/src/modes/components/welcome.ts +10 -10
  75. package/src/modes/controllers/command-controller.ts +0 -7
  76. package/src/modes/controllers/event-controller.ts +23 -23
  77. package/src/modes/controllers/extension-ui-controller.ts +13 -13
  78. package/src/modes/controllers/input-controller.ts +4 -9
  79. package/src/modes/interactive-mode.ts +234 -241
  80. package/src/modes/rpc/rpc-client.ts +77 -77
  81. package/src/modes/rpc/rpc-mode.ts +5 -5
  82. package/src/modes/theme/theme.ts +113 -113
  83. package/src/modes/types.ts +0 -1
  84. package/src/patch/index.ts +45 -45
  85. package/src/prompts/tools/task.md +22 -2
  86. package/src/session/agent-session.ts +463 -476
  87. package/src/session/agent-storage.ts +72 -75
  88. package/src/session/auth-storage.ts +186 -252
  89. package/src/session/history-storage.ts +36 -38
  90. package/src/session/session-manager.ts +300 -299
  91. package/src/session/session-storage.ts +65 -90
  92. package/src/ssh/connection-manager.ts +9 -9
  93. package/src/task/agents.ts +1 -1
  94. package/src/task/executor.ts +2 -2
  95. package/src/task/index.ts +13 -12
  96. package/src/task/subprocess-tool-registry.ts +5 -5
  97. package/src/tools/ask.ts +7 -7
  98. package/src/tools/bash.ts +8 -7
  99. package/src/tools/browser.ts +123 -123
  100. package/src/tools/calculator.ts +46 -46
  101. package/src/tools/context.ts +9 -9
  102. package/src/tools/exit-plan-mode.ts +5 -5
  103. package/src/tools/fetch.ts +5 -5
  104. package/src/tools/find.ts +16 -16
  105. package/src/tools/grep.ts +10 -10
  106. package/src/tools/notebook.ts +6 -6
  107. package/src/tools/output-meta.ts +10 -2
  108. package/src/tools/python.ts +12 -11
  109. package/src/tools/read.ts +17 -17
  110. package/src/tools/ssh.ts +9 -9
  111. package/src/tools/submit-result.ts +13 -13
  112. package/src/tools/todo-write.ts +6 -6
  113. package/src/tools/write.ts +10 -10
  114. package/src/tui/output-block.ts +6 -6
  115. package/src/tui/utils.ts +9 -9
  116. package/src/utils/event-bus.ts +10 -10
  117. package/src/utils/frontmatter.ts +1 -1
  118. package/src/utils/ignore-files.ts +1 -1
  119. package/src/web/search/index.ts +5 -5
  120. package/src/web/search/providers/anthropic.ts +7 -2
  121. package/examples/hooks/snake.ts +0 -342
  122. package/src/modes/components/armin.ts +0 -379
@@ -846,89 +846,89 @@ async function prepareEntryForPersistence(entry: FileEntry, blobStore: BlobStore
846
846
  }
847
847
 
848
848
  class NdjsonFileWriter {
849
- private writer: SessionStorageWriter;
850
- private closed = false;
851
- private closing = false;
852
- private error: Error | undefined;
853
- private pendingWrites: Promise<void> = Promise.resolve();
854
- private onError: ((err: Error) => void) | undefined;
849
+ #writer: SessionStorageWriter;
850
+ #closed = false;
851
+ #closing = false;
852
+ #error: Error | undefined;
853
+ #pendingWrites: Promise<void> = Promise.resolve();
854
+ #onError: ((err: Error) => void) | undefined;
855
855
 
856
856
  constructor(storage: SessionStorage, path: string, options?: { flags?: "a" | "w"; onError?: (err: Error) => void }) {
857
- this.onError = options?.onError;
858
- this.writer = storage.openWriter(path, {
857
+ this.#onError = options?.onError;
858
+ this.#writer = storage.openWriter(path, {
859
859
  flags: options?.flags ?? "a",
860
- onError: (err: Error) => this.recordError(err),
860
+ onError: (err: Error) => this.#recordError(err),
861
861
  });
862
862
  }
863
863
 
864
- private recordError(err: unknown): Error {
864
+ #recordError(err: unknown): Error {
865
865
  const writeErr = toError(err);
866
- if (!this.error) this.error = writeErr;
867
- this.onError?.(writeErr);
866
+ if (!this.#error) this.#error = writeErr;
867
+ this.#onError?.(writeErr);
868
868
  return writeErr;
869
869
  }
870
870
 
871
- private enqueue(task: () => Promise<void>): Promise<void> {
871
+ #enqueue(task: () => Promise<void>): Promise<void> {
872
872
  const run = async () => {
873
- if (this.error) throw this.error;
873
+ if (this.#error) throw this.#error;
874
874
  await task();
875
875
  };
876
- const next = this.pendingWrites.then(run);
876
+ const next = this.#pendingWrites.then(run);
877
877
  void next.catch((err: unknown) => {
878
- if (!this.error) this.error = toError(err);
878
+ if (!this.#error) this.#error = toError(err);
879
879
  });
880
- this.pendingWrites = next;
880
+ this.#pendingWrites = next;
881
881
  return next;
882
882
  }
883
883
 
884
- private async writeLine(line: string): Promise<void> {
885
- if (this.error) throw this.error;
884
+ async #writeLine(line: string): Promise<void> {
885
+ if (this.#error) throw this.#error;
886
886
  try {
887
- await this.writer.writeLine(line);
887
+ await this.#writer.writeLine(line);
888
888
  } catch (err) {
889
- throw this.recordError(err);
889
+ throw this.#recordError(err);
890
890
  }
891
891
  }
892
892
 
893
893
  /** Queue a write. Returns a promise so callers can await if needed. */
894
894
  write(entry: FileEntry): Promise<void> {
895
- if (this.closed || this.closing) throw new Error("Writer closed");
896
- if (this.error) throw this.error;
895
+ if (this.#closed || this.#closing) throw new Error("Writer closed");
896
+ if (this.#error) throw this.#error;
897
897
  const line = `${JSON.stringify(entry)}\n`;
898
- return this.enqueue(() => this.writeLine(line));
898
+ return this.#enqueue(() => this.#writeLine(line));
899
899
  }
900
900
 
901
901
  /** Flush all buffered data to disk. Waits for all queued writes. */
902
902
  async flush(): Promise<void> {
903
- if (this.closed) return;
904
- if (this.error) throw this.error;
903
+ if (this.#closed) return;
904
+ if (this.#error) throw this.#error;
905
905
 
906
- await this.enqueue(async () => {});
906
+ await this.#enqueue(async () => {});
907
907
 
908
- if (this.error) throw this.error;
908
+ if (this.#error) throw this.#error;
909
909
 
910
910
  try {
911
- await this.writer.flush();
911
+ await this.#writer.flush();
912
912
  } catch (err) {
913
- throw this.recordError(err);
913
+ throw this.#recordError(err);
914
914
  }
915
915
  }
916
916
 
917
917
  /** Sync data to persistent storage. */
918
918
  async fsync(): Promise<void> {
919
- if (this.closed) return;
920
- if (this.error) throw this.error;
919
+ if (this.#closed) return;
920
+ if (this.#error) throw this.#error;
921
921
  try {
922
- await this.writer.fsync();
922
+ await this.#writer.fsync();
923
923
  } catch (err) {
924
- throw this.recordError(err);
924
+ throw this.#recordError(err);
925
925
  }
926
926
  }
927
927
 
928
928
  /** Close the writer, flushing all data. */
929
929
  async close(): Promise<void> {
930
- if (this.closed || this.closing) return;
931
- this.closing = true;
930
+ if (this.#closed || this.#closing) return;
931
+ this.#closing = true;
932
932
 
933
933
  let closeError: Error | undefined;
934
934
  try {
@@ -938,27 +938,27 @@ class NdjsonFileWriter {
938
938
  }
939
939
 
940
940
  try {
941
- await this.pendingWrites;
941
+ await this.#pendingWrites;
942
942
  } catch (err) {
943
943
  if (!closeError) closeError = toError(err);
944
944
  }
945
945
 
946
946
  try {
947
- await this.writer.close();
947
+ await this.#writer.close();
948
948
  } catch (err) {
949
- const endErr = this.recordError(err);
949
+ const endErr = this.#recordError(err);
950
950
  if (!closeError) closeError = endErr;
951
951
  }
952
952
 
953
- this.closed = true;
953
+ this.#closed = true;
954
954
 
955
- if (!closeError && this.error) closeError = this.error;
955
+ if (!closeError && this.#error) closeError = this.#error;
956
956
  if (closeError) throw closeError;
957
957
  }
958
958
 
959
959
  /** Check if there's a stored error. */
960
960
  getError(): Error | undefined {
961
- return this.error;
961
+ return this.#error;
962
962
  }
963
963
  }
964
964
 
@@ -1076,21 +1076,21 @@ async function collectSessionsFromFiles(files: string[], storage: SessionStorage
1076
1076
  }
1077
1077
 
1078
1078
  export class SessionManager {
1079
- private sessionId: string = "";
1080
- private sessionName: string | undefined;
1081
- private sessionFile: string | undefined;
1082
- private flushed: boolean = false;
1083
- private fileEntries: FileEntry[] = [];
1084
- private byId: Map<string, SessionEntry> = new Map();
1085
- private labelsById: Map<string, string> = new Map();
1086
- private leafId: string | null = null;
1087
- private usageStatistics: UsageStatistics = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
1088
- private persistWriter: NdjsonFileWriter | undefined;
1089
- private persistWriterPath: string | undefined;
1090
- private persistChain: Promise<void> = Promise.resolve();
1091
- private persistError: Error | undefined;
1092
- private persistErrorReported = false;
1093
- private readonly blobStore: BlobStore;
1079
+ #sessionId: string = "";
1080
+ #sessionName: string | undefined;
1081
+ #sessionFile: string | undefined;
1082
+ #flushed: boolean = false;
1083
+ #fileEntries: FileEntry[] = [];
1084
+ #byId: Map<string, SessionEntry> = new Map();
1085
+ #labelsById: Map<string, string> = new Map();
1086
+ #leafId: string | null = null;
1087
+ #usageStatistics: UsageStatistics = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
1088
+ #persistWriter: NdjsonFileWriter | undefined;
1089
+ #persistWriterPath: string | undefined;
1090
+ #persistChain: Promise<void> = Promise.resolve();
1091
+ #persistError: Error | undefined;
1092
+ #persistErrorReported = false;
1093
+ readonly #blobStore: BlobStore;
1094
1094
 
1095
1095
  private constructor(
1096
1096
  private readonly cwd: string,
@@ -1098,7 +1098,7 @@ export class SessionManager {
1098
1098
  private readonly persist: boolean,
1099
1099
  private readonly storage: SessionStorage,
1100
1100
  ) {
1101
- this.blobStore = new BlobStore(getBlobsDir());
1101
+ this.#blobStore = new BlobStore(getBlobsDir());
1102
1102
  if (persist && sessionDir) {
1103
1103
  this.storage.ensureDirSync(sessionDir);
1104
1104
  }
@@ -1107,54 +1107,54 @@ export class SessionManager {
1107
1107
 
1108
1108
  /** Puts a binary blob into the blob store and returns the blob reference */
1109
1109
  async putBlob(data: Buffer): Promise<BlobPutResult> {
1110
- return this.blobStore.put(data);
1110
+ return this.#blobStore.put(data);
1111
1111
  }
1112
1112
 
1113
1113
  /** Initialize with a specific session file (used by factory methods) */
1114
- private async _initSessionFile(sessionFile: string): Promise<void> {
1114
+ async #initSessionFile(sessionFile: string): Promise<void> {
1115
1115
  await this.setSessionFile(sessionFile);
1116
1116
  }
1117
1117
 
1118
1118
  /** Initialize with a new session (used by factory methods) */
1119
- private _initNewSession(): void {
1120
- this._newSessionSync();
1119
+ #initNewSession(): void {
1120
+ this.#newSessionSync();
1121
1121
  }
1122
1122
 
1123
1123
  /** Switch to a different session file (used for resume and branching) */
1124
1124
  async setSessionFile(sessionFile: string): Promise<void> {
1125
- await this._closePersistWriter();
1126
- this.persistError = undefined;
1127
- this.persistErrorReported = false;
1128
- this.sessionFile = path.resolve(sessionFile);
1129
- writeTerminalBreadcrumb(this.cwd, this.sessionFile);
1130
- this.fileEntries = await loadEntriesFromFile(this.sessionFile, this.storage);
1131
- if (this.fileEntries.length > 0) {
1132
- const header = this.fileEntries.find(e => e.type === "session") as SessionHeader | undefined;
1133
- this.sessionId = header?.id ?? Snowflake.next();
1134
- this.sessionName = header?.title;
1135
-
1136
- if (migrateToCurrentVersion(this.fileEntries)) {
1137
- await this._rewriteFile();
1125
+ await this.#closePersistWriter();
1126
+ this.#persistError = undefined;
1127
+ this.#persistErrorReported = false;
1128
+ this.#sessionFile = path.resolve(sessionFile);
1129
+ writeTerminalBreadcrumb(this.cwd, this.#sessionFile);
1130
+ this.#fileEntries = await loadEntriesFromFile(this.#sessionFile, this.storage);
1131
+ if (this.#fileEntries.length > 0) {
1132
+ const header = this.#fileEntries.find(e => e.type === "session") as SessionHeader | undefined;
1133
+ this.#sessionId = header?.id ?? Snowflake.next();
1134
+ this.#sessionName = header?.title;
1135
+
1136
+ if (migrateToCurrentVersion(this.#fileEntries)) {
1137
+ await this.#rewriteFile();
1138
1138
  }
1139
1139
 
1140
- await resolveBlobRefsInEntries(this.fileEntries, this.blobStore);
1140
+ await resolveBlobRefsInEntries(this.#fileEntries, this.#blobStore);
1141
1141
 
1142
- this._buildIndex();
1143
- this.flushed = true;
1142
+ this.#buildIndex();
1143
+ this.#flushed = true;
1144
1144
  } else {
1145
- const explicitPath = this.sessionFile;
1146
- this._newSessionSync();
1147
- this.sessionFile = explicitPath; // preserve explicit path from --session flag
1148
- await this._rewriteFile();
1149
- this.flushed = true;
1145
+ const explicitPath = this.#sessionFile;
1146
+ this.#newSessionSync();
1147
+ this.#sessionFile = explicitPath; // preserve explicit path from --session flag
1148
+ await this.#rewriteFile();
1149
+ this.#flushed = true;
1150
1150
  return;
1151
1151
  }
1152
1152
  }
1153
1153
 
1154
1154
  /** Start a new session. Closes any existing writer first. */
1155
1155
  async newSession(options?: NewSessionOptions): Promise<string | undefined> {
1156
- await this._closePersistWriter();
1157
- return this._newSessionSync(options);
1156
+ await this.#closePersistWriter();
1157
+ return this.#newSessionSync(options);
1158
1158
  }
1159
1159
 
1160
1160
  /**
@@ -1163,125 +1163,125 @@ export class SessionManager {
1163
1163
  * @returns { oldSessionFile, newSessionFile } or undefined if not persisting
1164
1164
  */
1165
1165
  async fork(): Promise<{ oldSessionFile: string; newSessionFile: string } | undefined> {
1166
- if (!this.persist || !this.sessionFile) {
1166
+ if (!this.persist || !this.#sessionFile) {
1167
1167
  return undefined;
1168
1168
  }
1169
1169
 
1170
- const oldSessionFile = this.sessionFile;
1171
- const oldSessionId = this.sessionId;
1170
+ const oldSessionFile = this.#sessionFile;
1171
+ const oldSessionId = this.#sessionId;
1172
1172
 
1173
1173
  // Close the current writer
1174
- await this._closePersistWriter();
1175
- this.persistChain = Promise.resolve();
1176
- this.persistError = undefined;
1177
- this.persistErrorReported = false;
1174
+ await this.#closePersistWriter();
1175
+ this.#persistChain = Promise.resolve();
1176
+ this.#persistError = undefined;
1177
+ this.#persistErrorReported = false;
1178
1178
 
1179
1179
  // Create new session ID and header
1180
- this.sessionId = Snowflake.next();
1180
+ this.#sessionId = Snowflake.next();
1181
1181
  const timestamp = new Date().toISOString();
1182
1182
  const fileTimestamp = timestamp.replace(/[:.]/g, "-");
1183
- this.sessionFile = path.join(this.getSessionDir(), `${fileTimestamp}_${this.sessionId}.jsonl`);
1183
+ this.#sessionFile = path.join(this.getSessionDir(), `${fileTimestamp}_${this.#sessionId}.jsonl`);
1184
1184
 
1185
1185
  // Update the header with new ID but keep all entries
1186
- const oldHeader = this.fileEntries.find(e => e.type === "session") as SessionHeader | undefined;
1186
+ const oldHeader = this.#fileEntries.find(e => e.type === "session") as SessionHeader | undefined;
1187
1187
  const newHeader: SessionHeader = {
1188
1188
  type: "session",
1189
1189
  version: CURRENT_SESSION_VERSION,
1190
- id: this.sessionId,
1191
- title: oldHeader?.title ?? this.sessionName,
1190
+ id: this.#sessionId,
1191
+ title: oldHeader?.title ?? this.#sessionName,
1192
1192
  timestamp,
1193
1193
  cwd: this.cwd,
1194
1194
  parentSession: oldSessionId,
1195
1195
  };
1196
- this.sessionName = newHeader.title;
1196
+ this.#sessionName = newHeader.title;
1197
1197
 
1198
1198
  // Replace the header in fileEntries
1199
- const entries = this.fileEntries.filter(e => e.type !== "session") as SessionEntry[];
1200
- this.fileEntries = [newHeader, ...entries];
1199
+ const entries = this.#fileEntries.filter(e => e.type !== "session") as SessionEntry[];
1200
+ this.#fileEntries = [newHeader, ...entries];
1201
1201
 
1202
1202
  // Write the new session file
1203
- this.flushed = false;
1204
- await this._rewriteFile();
1203
+ this.#flushed = false;
1204
+ await this.#rewriteFile();
1205
1205
 
1206
- return { oldSessionFile, newSessionFile: this.sessionFile };
1206
+ return { oldSessionFile, newSessionFile: this.#sessionFile };
1207
1207
  }
1208
1208
 
1209
1209
  /** Sync version for initial creation (no existing writer to close) */
1210
- private _newSessionSync(options?: NewSessionOptions): string | undefined {
1211
- this.persistChain = Promise.resolve();
1212
- this.persistError = undefined;
1213
- this.persistErrorReported = false;
1214
- this.sessionId = Snowflake.next();
1215
- this.sessionName = undefined;
1210
+ #newSessionSync(options?: NewSessionOptions): string | undefined {
1211
+ this.#persistChain = Promise.resolve();
1212
+ this.#persistError = undefined;
1213
+ this.#persistErrorReported = false;
1214
+ this.#sessionId = Snowflake.next();
1215
+ this.#sessionName = undefined;
1216
1216
  const timestamp = new Date().toISOString();
1217
1217
  const header: SessionHeader = {
1218
1218
  type: "session",
1219
1219
  version: CURRENT_SESSION_VERSION,
1220
- id: this.sessionId,
1220
+ id: this.#sessionId,
1221
1221
  timestamp,
1222
1222
  cwd: this.cwd,
1223
1223
  parentSession: options?.parentSession,
1224
1224
  };
1225
- this.fileEntries = [header];
1226
- this.byId.clear();
1227
- this.labelsById.clear();
1228
- this.leafId = null;
1229
- this.flushed = false;
1230
- this.usageStatistics = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
1225
+ this.#fileEntries = [header];
1226
+ this.#byId.clear();
1227
+ this.#labelsById.clear();
1228
+ this.#leafId = null;
1229
+ this.#flushed = false;
1230
+ this.#usageStatistics = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
1231
1231
 
1232
1232
  if (this.persist) {
1233
1233
  const fileTimestamp = timestamp.replace(/[:.]/g, "-");
1234
- this.sessionFile = path.join(this.getSessionDir(), `${fileTimestamp}_${this.sessionId}.jsonl`);
1235
- writeTerminalBreadcrumb(this.cwd, this.sessionFile);
1234
+ this.#sessionFile = path.join(this.getSessionDir(), `${fileTimestamp}_${this.#sessionId}.jsonl`);
1235
+ writeTerminalBreadcrumb(this.cwd, this.#sessionFile);
1236
1236
  }
1237
- return this.sessionFile;
1237
+ return this.#sessionFile;
1238
1238
  }
1239
1239
 
1240
- private _buildIndex(): void {
1241
- this.byId.clear();
1242
- this.labelsById.clear();
1243
- this.leafId = null;
1244
- this.usageStatistics = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
1245
- for (const entry of this.fileEntries) {
1240
+ #buildIndex(): void {
1241
+ this.#byId.clear();
1242
+ this.#labelsById.clear();
1243
+ this.#leafId = null;
1244
+ this.#usageStatistics = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
1245
+ for (const entry of this.#fileEntries) {
1246
1246
  if (entry.type === "session") continue;
1247
- this.byId.set(entry.id, entry);
1248
- this.leafId = entry.id;
1247
+ this.#byId.set(entry.id, entry);
1248
+ this.#leafId = entry.id;
1249
1249
  if (entry.type === "label") {
1250
1250
  if (entry.label) {
1251
- this.labelsById.set(entry.targetId, entry.label);
1251
+ this.#labelsById.set(entry.targetId, entry.label);
1252
1252
  } else {
1253
- this.labelsById.delete(entry.targetId);
1253
+ this.#labelsById.delete(entry.targetId);
1254
1254
  }
1255
1255
  }
1256
1256
  if (entry.type === "message" && entry.message.role === "assistant") {
1257
1257
  const usage = entry.message.usage;
1258
- this.usageStatistics.input += usage.input;
1259
- this.usageStatistics.output += usage.output;
1260
- this.usageStatistics.cacheRead += usage.cacheRead;
1261
- this.usageStatistics.cacheWrite += usage.cacheWrite;
1262
- this.usageStatistics.cost += usage.cost.total;
1258
+ this.#usageStatistics.input += usage.input;
1259
+ this.#usageStatistics.output += usage.output;
1260
+ this.#usageStatistics.cacheRead += usage.cacheRead;
1261
+ this.#usageStatistics.cacheWrite += usage.cacheWrite;
1262
+ this.#usageStatistics.cost += usage.cost.total;
1263
1263
  }
1264
1264
 
1265
1265
  if (entry.type === "message" && entry.message.role === "toolResult" && entry.message.toolName === "task") {
1266
1266
  const usage = getTaskToolUsage(entry.message.details);
1267
1267
  if (usage) {
1268
- this.usageStatistics.input += usage.input;
1269
- this.usageStatistics.output += usage.output;
1270
- this.usageStatistics.cacheRead += usage.cacheRead;
1271
- this.usageStatistics.cacheWrite += usage.cacheWrite;
1272
- this.usageStatistics.cost += usage.cost.total;
1268
+ this.#usageStatistics.input += usage.input;
1269
+ this.#usageStatistics.output += usage.output;
1270
+ this.#usageStatistics.cacheRead += usage.cacheRead;
1271
+ this.#usageStatistics.cacheWrite += usage.cacheWrite;
1272
+ this.#usageStatistics.cost += usage.cost.total;
1273
1273
  }
1274
1274
  }
1275
1275
  }
1276
1276
  }
1277
1277
 
1278
- private _recordPersistError(err: unknown): Error {
1278
+ #recordPersistError(err: unknown): Error {
1279
1279
  const normalized = toError(err);
1280
- if (!this.persistError) this.persistError = normalized;
1281
- if (!this.persistErrorReported) {
1282
- this.persistErrorReported = true;
1280
+ if (!this.#persistError) this.#persistError = normalized;
1281
+ if (!this.#persistErrorReported) {
1282
+ this.#persistErrorReported = true;
1283
1283
  logger.error("Session persistence error.", {
1284
- sessionFile: this.sessionFile,
1284
+ sessionFile: this.#sessionFile,
1285
1285
  error: normalized.message,
1286
1286
  stack: normalized.stack,
1287
1287
  });
@@ -1289,52 +1289,52 @@ export class SessionManager {
1289
1289
  return normalized;
1290
1290
  }
1291
1291
 
1292
- private _queuePersistTask(task: () => Promise<void>, options?: { ignoreError?: boolean }): Promise<void> {
1293
- const next = this.persistChain.then(async () => {
1294
- if (this.persistError && !options?.ignoreError) throw this.persistError;
1292
+ #queuePersistTask(task: () => Promise<void>, options?: { ignoreError?: boolean }): Promise<void> {
1293
+ const next = this.#persistChain.then(async () => {
1294
+ if (this.#persistError && !options?.ignoreError) throw this.#persistError;
1295
1295
  await task();
1296
1296
  });
1297
- this.persistChain = next.catch(err => {
1298
- this._recordPersistError(err);
1297
+ this.#persistChain = next.catch(err => {
1298
+ this.#recordPersistError(err);
1299
1299
  });
1300
1300
  return next;
1301
1301
  }
1302
1302
 
1303
- private _ensurePersistWriter(): NdjsonFileWriter | undefined {
1304
- if (!this.persist || !this.sessionFile) return undefined;
1305
- if (this.persistError) throw this.persistError;
1306
- if (this.persistWriter && this.persistWriterPath === this.sessionFile) return this.persistWriter;
1303
+ #ensurePersistWriter(): NdjsonFileWriter | undefined {
1304
+ if (!this.persist || !this.#sessionFile) return undefined;
1305
+ if (this.#persistError) throw this.#persistError;
1306
+ if (this.#persistWriter && this.#persistWriterPath === this.#sessionFile) return this.#persistWriter;
1307
1307
  // Note: caller must await _closePersistWriter() before calling this if switching files
1308
- this.persistWriter = new NdjsonFileWriter(this.storage, this.sessionFile, {
1308
+ this.#persistWriter = new NdjsonFileWriter(this.storage, this.#sessionFile, {
1309
1309
  onError: err => {
1310
- this._recordPersistError(err);
1310
+ this.#recordPersistError(err);
1311
1311
  },
1312
1312
  });
1313
- this.persistWriterPath = this.sessionFile;
1314
- return this.persistWriter;
1313
+ this.#persistWriterPath = this.#sessionFile;
1314
+ return this.#persistWriter;
1315
1315
  }
1316
1316
 
1317
- private async _closePersistWriterInternal(): Promise<void> {
1318
- if (this.persistWriter) {
1319
- await this.persistWriter.close();
1320
- this.persistWriter = undefined;
1317
+ async #closePersistWriterInternal(): Promise<void> {
1318
+ if (this.#persistWriter) {
1319
+ await this.#persistWriter.close();
1320
+ this.#persistWriter = undefined;
1321
1321
  }
1322
- this.persistWriterPath = undefined;
1322
+ this.#persistWriterPath = undefined;
1323
1323
  }
1324
1324
 
1325
- private async _closePersistWriter(): Promise<void> {
1326
- await this._queuePersistTask(
1325
+ async #closePersistWriter(): Promise<void> {
1326
+ await this.#queuePersistTask(
1327
1327
  async () => {
1328
- await this._closePersistWriterInternal();
1328
+ await this.#closePersistWriterInternal();
1329
1329
  },
1330
1330
  { ignoreError: true },
1331
1331
  );
1332
1332
  }
1333
1333
 
1334
- private async _writeEntriesAtomically(entries: FileEntry[]): Promise<void> {
1335
- if (!this.sessionFile) return;
1336
- const dir = path.resolve(this.sessionFile, "..");
1337
- const tempPath = path.join(dir, `.${path.basename(this.sessionFile)}.${Snowflake.next()}.tmp`);
1334
+ async #writeEntriesAtomically(entries: FileEntry[]): Promise<void> {
1335
+ if (!this.#sessionFile) return;
1336
+ const dir = path.resolve(this.#sessionFile, "..");
1337
+ const tempPath = path.join(dir, `.${path.basename(this.#sessionFile)}.${Snowflake.next()}.tmp`);
1338
1338
  const writer = new NdjsonFileWriter(this.storage, tempPath, { flags: "w" });
1339
1339
  try {
1340
1340
  for (const entry of entries) {
@@ -1343,8 +1343,7 @@ export class SessionManager {
1343
1343
  await writer.flush();
1344
1344
  await writer.fsync();
1345
1345
  await writer.close();
1346
- await this.storage.rename(tempPath, this.sessionFile);
1347
- this.storage.fsyncDirSync(dir);
1346
+ await this.storage.rename(tempPath, this.#sessionFile);
1348
1347
  } catch (err) {
1349
1348
  try {
1350
1349
  await writer.close();
@@ -1360,15 +1359,15 @@ export class SessionManager {
1360
1359
  }
1361
1360
  }
1362
1361
 
1363
- private async _rewriteFile(): Promise<void> {
1364
- if (!this.persist || !this.sessionFile) return;
1365
- await this._queuePersistTask(async () => {
1366
- await this._closePersistWriterInternal();
1362
+ async #rewriteFile(): Promise<void> {
1363
+ if (!this.persist || !this.#sessionFile) return;
1364
+ await this.#queuePersistTask(async () => {
1365
+ await this.#closePersistWriterInternal();
1367
1366
  const entries = await Promise.all(
1368
- this.fileEntries.map(entry => prepareEntryForPersistence(entry, this.blobStore)),
1367
+ this.#fileEntries.map(entry => prepareEntryForPersistence(entry, this.#blobStore)),
1369
1368
  );
1370
- await this._writeEntriesAtomically(entries);
1371
- this.flushed = true;
1369
+ await this.#writeEntriesAtomically(entries);
1370
+ this.#flushed = true;
1372
1371
  });
1373
1372
  }
1374
1373
 
@@ -1378,14 +1377,14 @@ export class SessionManager {
1378
1377
 
1379
1378
  /** Flush pending writes to disk. Call before switching sessions or on shutdown. */
1380
1379
  async flush(): Promise<void> {
1381
- if (!this.persistWriter) return;
1382
- await this._queuePersistTask(async () => {
1383
- if (this.persistWriter) {
1384
- await this.persistWriter.flush();
1385
- await this.persistWriter.fsync();
1380
+ if (!this.#persistWriter) return;
1381
+ await this.#queuePersistTask(async () => {
1382
+ if (this.#persistWriter) {
1383
+ await this.#persistWriter.flush();
1384
+ await this.#persistWriter.fsync();
1386
1385
  }
1387
1386
  });
1388
- if (this.persistError) throw this.persistError;
1387
+ if (this.#persistError) throw this.#persistError;
1389
1388
  }
1390
1389
 
1391
1390
  getCwd(): string {
@@ -1394,7 +1393,7 @@ export class SessionManager {
1394
1393
 
1395
1394
  /** Get usage statistics across all assistant messages in the session. */
1396
1395
  getUsageStatistics(): UsageStatistics {
1397
- return this.usageStatistics;
1396
+ return this.#usageStatistics;
1398
1397
  }
1399
1398
 
1400
1399
  getSessionDir(): string {
@@ -1402,86 +1401,88 @@ export class SessionManager {
1402
1401
  }
1403
1402
 
1404
1403
  getSessionId(): string {
1405
- return this.sessionId;
1404
+ return this.#sessionId;
1406
1405
  }
1407
1406
 
1408
1407
  getSessionFile(): string | undefined {
1409
- return this.sessionFile;
1408
+ return this.#sessionFile;
1410
1409
  }
1411
1410
 
1412
1411
  getSessionName(): string | undefined {
1413
- return this.sessionName;
1412
+ return this.#sessionName;
1414
1413
  }
1415
1414
 
1416
1415
  async setSessionName(name: string): Promise<void> {
1417
- this.sessionName = name;
1416
+ this.#sessionName = name;
1418
1417
 
1419
1418
  // Update the in-memory header (so first flush includes title)
1420
- const header = this.fileEntries.find(e => e.type === "session") as SessionHeader | undefined;
1419
+ const header = this.#fileEntries.find(e => e.type === "session") as SessionHeader | undefined;
1421
1420
  if (header) {
1422
1421
  header.title = name;
1423
1422
  }
1424
1423
 
1425
1424
  // Update the session file header with the title (if already flushed)
1426
- const sessionFile = this.sessionFile;
1425
+ const sessionFile = this.#sessionFile;
1427
1426
  if (this.persist && sessionFile && this.storage.existsSync(sessionFile)) {
1428
- await this._rewriteFile();
1427
+ await this.#rewriteFile();
1429
1428
  }
1430
1429
  }
1431
1430
 
1432
1431
  _persist(entry: SessionEntry): void {
1433
- if (!this.persist || !this.sessionFile) return;
1434
- if (this.persistError) throw this.persistError;
1432
+ if (!this.persist || !this.#sessionFile) return;
1433
+ if (this.#persistError) throw this.#persistError;
1435
1434
 
1436
- const hasAssistant = this.fileEntries.some(e => e.type === "message" && e.message.role === "assistant");
1435
+ const hasAssistant = this.#fileEntries.some(e => e.type === "message" && e.message.role === "assistant");
1437
1436
  if (!hasAssistant) {
1438
1437
  // Mark as not flushed so when assistant arrives, all entries get written
1439
- this.flushed = false;
1438
+ this.#flushed = false;
1440
1439
  return;
1441
1440
  }
1442
1441
 
1443
- if (!this.flushed) {
1444
- this.flushed = true;
1445
- void this._queuePersistTask(async () => {
1446
- const writer = this._ensurePersistWriter();
1442
+ if (!this.#flushed) {
1443
+ this.#flushed = true;
1444
+ void this.#queuePersistTask(async () => {
1445
+ const writer = this.#ensurePersistWriter();
1447
1446
  if (!writer) return;
1448
- const entries = await Promise.all(this.fileEntries.map(e => prepareEntryForPersistence(e, this.blobStore)));
1447
+ const entries = await Promise.all(
1448
+ this.#fileEntries.map(e => prepareEntryForPersistence(e, this.#blobStore)),
1449
+ );
1449
1450
  for (const persistedEntry of entries) {
1450
1451
  await writer.write(persistedEntry);
1451
1452
  }
1452
1453
  });
1453
1454
  } else {
1454
- void this._queuePersistTask(async () => {
1455
- const writer = this._ensurePersistWriter();
1455
+ void this.#queuePersistTask(async () => {
1456
+ const writer = this.#ensurePersistWriter();
1456
1457
  if (!writer) return;
1457
- const persistedEntry = await prepareEntryForPersistence(entry, this.blobStore);
1458
+ const persistedEntry = await prepareEntryForPersistence(entry, this.#blobStore);
1458
1459
  await writer.write(persistedEntry);
1459
1460
  });
1460
1461
  }
1461
1462
  }
1462
1463
 
1463
- private _appendEntry(entry: SessionEntry): void {
1464
- this.fileEntries.push(entry);
1465
- this.byId.set(entry.id, entry);
1466
- this.leafId = entry.id;
1464
+ #appendEntry(entry: SessionEntry): void {
1465
+ this.#fileEntries.push(entry);
1466
+ this.#byId.set(entry.id, entry);
1467
+ this.#leafId = entry.id;
1467
1468
  this._persist(entry);
1468
1469
  if (entry.type === "message" && entry.message.role === "assistant") {
1469
1470
  const usage = entry.message.usage;
1470
- this.usageStatistics.input += usage.input;
1471
- this.usageStatistics.output += usage.output;
1472
- this.usageStatistics.cacheRead += usage.cacheRead;
1473
- this.usageStatistics.cacheWrite += usage.cacheWrite;
1474
- this.usageStatistics.cost += usage.cost.total;
1471
+ this.#usageStatistics.input += usage.input;
1472
+ this.#usageStatistics.output += usage.output;
1473
+ this.#usageStatistics.cacheRead += usage.cacheRead;
1474
+ this.#usageStatistics.cacheWrite += usage.cacheWrite;
1475
+ this.#usageStatistics.cost += usage.cost.total;
1475
1476
  }
1476
1477
 
1477
1478
  if (entry.type === "message" && entry.message.role === "toolResult" && entry.message.toolName === "task") {
1478
1479
  const usage = getTaskToolUsage(entry.message.details);
1479
1480
  if (usage) {
1480
- this.usageStatistics.input += usage.input;
1481
- this.usageStatistics.output += usage.output;
1482
- this.usageStatistics.cacheRead += usage.cacheRead;
1483
- this.usageStatistics.cacheWrite += usage.cacheWrite;
1484
- this.usageStatistics.cost += usage.cost.total;
1481
+ this.#usageStatistics.input += usage.input;
1482
+ this.#usageStatistics.output += usage.output;
1483
+ this.#usageStatistics.cacheRead += usage.cacheRead;
1484
+ this.#usageStatistics.cacheWrite += usage.cacheWrite;
1485
+ this.#usageStatistics.cost += usage.cost.total;
1485
1486
  }
1486
1487
  }
1487
1488
  }
@@ -1503,12 +1504,12 @@ export class SessionManager {
1503
1504
  ): string {
1504
1505
  const entry: SessionMessageEntry = {
1505
1506
  type: "message",
1506
- id: generateId(this.byId),
1507
- parentId: this.leafId,
1507
+ id: generateId(this.#byId),
1508
+ parentId: this.#leafId,
1508
1509
  timestamp: new Date().toISOString(),
1509
1510
  message,
1510
1511
  };
1511
- this._appendEntry(entry);
1512
+ this.#appendEntry(entry);
1512
1513
  return entry.id;
1513
1514
  }
1514
1515
 
@@ -1516,12 +1517,12 @@ export class SessionManager {
1516
1517
  appendThinkingLevelChange(thinkingLevel: string): string {
1517
1518
  const entry: ThinkingLevelChangeEntry = {
1518
1519
  type: "thinking_level_change",
1519
- id: generateId(this.byId),
1520
- parentId: this.leafId,
1520
+ id: generateId(this.#byId),
1521
+ parentId: this.#leafId,
1521
1522
  timestamp: new Date().toISOString(),
1522
1523
  thinkingLevel,
1523
1524
  };
1524
- this._appendEntry(entry);
1525
+ this.#appendEntry(entry);
1525
1526
  return entry.id;
1526
1527
  }
1527
1528
 
@@ -1529,13 +1530,13 @@ export class SessionManager {
1529
1530
  appendModeChange(mode: string, data?: Record<string, unknown>): string {
1530
1531
  const entry: ModeChangeEntry = {
1531
1532
  type: "mode_change",
1532
- id: generateId(this.byId),
1533
- parentId: this.leafId,
1533
+ id: generateId(this.#byId),
1534
+ parentId: this.#leafId,
1534
1535
  timestamp: new Date().toISOString(),
1535
1536
  mode,
1536
1537
  data,
1537
1538
  };
1538
- this._appendEntry(entry);
1539
+ this.#appendEntry(entry);
1539
1540
  return entry.id;
1540
1541
  }
1541
1542
 
@@ -1547,13 +1548,13 @@ export class SessionManager {
1547
1548
  appendModelChange(model: string, role?: string): string {
1548
1549
  const entry: ModelChangeEntry = {
1549
1550
  type: "model_change",
1550
- id: generateId(this.byId),
1551
- parentId: this.leafId,
1551
+ id: generateId(this.#byId),
1552
+ parentId: this.#leafId,
1552
1553
  timestamp: new Date().toISOString(),
1553
1554
  model,
1554
1555
  role,
1555
1556
  };
1556
- this._appendEntry(entry);
1557
+ this.#appendEntry(entry);
1557
1558
  return entry.id;
1558
1559
  }
1559
1560
 
@@ -1561,12 +1562,12 @@ export class SessionManager {
1561
1562
  appendSessionInit(init: { systemPrompt: string; task: string; tools: string[]; outputSchema?: unknown }): string {
1562
1563
  const entry: SessionInitEntry = {
1563
1564
  type: "session_init",
1564
- id: generateId(this.byId),
1565
- parentId: this.leafId,
1565
+ id: generateId(this.#byId),
1566
+ parentId: this.#leafId,
1566
1567
  timestamp: new Date().toISOString(),
1567
1568
  ...init,
1568
1569
  };
1569
- this._appendEntry(entry);
1570
+ this.#appendEntry(entry);
1570
1571
  return entry.id;
1571
1572
  }
1572
1573
 
@@ -1582,8 +1583,8 @@ export class SessionManager {
1582
1583
  ): string {
1583
1584
  const entry: CompactionEntry<T> = {
1584
1585
  type: "compaction",
1585
- id: generateId(this.byId),
1586
- parentId: this.leafId,
1586
+ id: generateId(this.#byId),
1587
+ parentId: this.#leafId,
1587
1588
  timestamp: new Date().toISOString(),
1588
1589
  summary,
1589
1590
  shortSummary,
@@ -1593,7 +1594,7 @@ export class SessionManager {
1593
1594
  fromExtension,
1594
1595
  preserveData,
1595
1596
  };
1596
- this._appendEntry(entry);
1597
+ this.#appendEntry(entry);
1597
1598
  return entry.id;
1598
1599
  }
1599
1600
 
@@ -1603,11 +1604,11 @@ export class SessionManager {
1603
1604
  type: "custom",
1604
1605
  customType,
1605
1606
  data,
1606
- id: generateId(this.byId),
1607
- parentId: this.leafId,
1607
+ id: generateId(this.#byId),
1608
+ parentId: this.#leafId,
1608
1609
  timestamp: new Date().toISOString(),
1609
1610
  };
1610
- this._appendEntry(entry);
1611
+ this.#appendEntry(entry);
1611
1612
  return entry.id;
1612
1613
  }
1613
1614
 
@@ -1616,8 +1617,8 @@ export class SessionManager {
1616
1617
  * Use sparingly (e.g., pruning old tool outputs).
1617
1618
  */
1618
1619
  async rewriteEntries(): Promise<void> {
1619
- if (!this.persist || !this.sessionFile) return;
1620
- await this._rewriteFile();
1620
+ if (!this.persist || !this.#sessionFile) return;
1621
+ await this.#rewriteFile();
1621
1622
  }
1622
1623
 
1623
1624
  /**
@@ -1626,8 +1627,8 @@ export class SessionManager {
1626
1627
  */
1627
1628
  async rewriteAssistantToolCallArgs(toolCallId: string, args: Record<string, unknown>): Promise<boolean> {
1628
1629
  let updated = false;
1629
- for (let i = this.fileEntries.length - 1; i >= 0; i--) {
1630
- const entry = this.fileEntries[i];
1630
+ for (let i = this.#fileEntries.length - 1; i >= 0; i--) {
1631
+ const entry = this.#fileEntries[i];
1631
1632
  if (entry.type !== "message" || entry.message.role !== "assistant") continue;
1632
1633
  const message = entry.message as { content?: unknown };
1633
1634
  if (!Array.isArray(message.content)) continue;
@@ -1644,8 +1645,8 @@ export class SessionManager {
1644
1645
  if (updated) break;
1645
1646
  }
1646
1647
 
1647
- if (updated && this.persist && this.sessionFile) {
1648
- await this._rewriteFile();
1648
+ if (updated && this.persist && this.#sessionFile) {
1649
+ await this.#rewriteFile();
1649
1650
  }
1650
1651
  return updated;
1651
1652
  }
@@ -1670,11 +1671,11 @@ export class SessionManager {
1670
1671
  content,
1671
1672
  display,
1672
1673
  details,
1673
- id: generateId(this.byId),
1674
- parentId: this.leafId,
1674
+ id: generateId(this.#byId),
1675
+ parentId: this.#leafId,
1675
1676
  timestamp: new Date().toISOString(),
1676
1677
  };
1677
- this._appendEntry(entry);
1678
+ this.#appendEntry(entry);
1678
1679
  return entry.id;
1679
1680
  }
1680
1681
 
@@ -1690,12 +1691,12 @@ export class SessionManager {
1690
1691
  appendTtsrInjection(ruleNames: string[]): string {
1691
1692
  const entry: TtsrInjectionEntry = {
1692
1693
  type: "ttsr_injection",
1693
- id: generateId(this.byId),
1694
- parentId: this.leafId,
1694
+ id: generateId(this.#byId),
1695
+ parentId: this.#leafId,
1695
1696
  timestamp: new Date().toISOString(),
1696
1697
  injectedRules: ruleNames,
1697
1698
  };
1698
- this._appendEntry(entry);
1699
+ this.#appendEntry(entry);
1699
1700
  return entry.id;
1700
1701
  }
1701
1702
 
@@ -1721,11 +1722,11 @@ export class SessionManager {
1721
1722
  // =========================================================================
1722
1723
 
1723
1724
  getLeafId(): string | null {
1724
- return this.leafId;
1725
+ return this.#leafId;
1725
1726
  }
1726
1727
 
1727
1728
  getLeafEntry(): SessionEntry | undefined {
1728
- return this.leafId ? this.byId.get(this.leafId) : undefined;
1729
+ return this.#leafId ? this.#byId.get(this.#leafId) : undefined;
1729
1730
  }
1730
1731
 
1731
1732
  /**
@@ -1738,13 +1739,13 @@ export class SessionManager {
1738
1739
  if (current.type === "model_change") {
1739
1740
  return current.role ?? "default";
1740
1741
  }
1741
- current = current.parentId ? this.byId.get(current.parentId) : undefined;
1742
+ current = current.parentId ? this.#byId.get(current.parentId) : undefined;
1742
1743
  }
1743
1744
  return undefined;
1744
1745
  }
1745
1746
 
1746
1747
  getEntry(id: string): SessionEntry | undefined {
1747
- return this.byId.get(id);
1748
+ return this.#byId.get(id);
1748
1749
  }
1749
1750
 
1750
1751
  /**
@@ -1752,7 +1753,7 @@ export class SessionManager {
1752
1753
  */
1753
1754
  getChildren(parentId: string): SessionEntry[] {
1754
1755
  const children: SessionEntry[] = [];
1755
- for (const entry of this.byId.values()) {
1756
+ for (const entry of this.#byId.values()) {
1756
1757
  if (entry.parentId === parentId) {
1757
1758
  children.push(entry);
1758
1759
  }
@@ -1764,7 +1765,7 @@ export class SessionManager {
1764
1765
  * Get the label for an entry, if any.
1765
1766
  */
1766
1767
  getLabel(id: string): string | undefined {
1767
- return this.labelsById.get(id);
1768
+ return this.#labelsById.get(id);
1768
1769
  }
1769
1770
 
1770
1771
  /**
@@ -1773,22 +1774,22 @@ export class SessionManager {
1773
1774
  * Pass undefined or empty string to clear the label.
1774
1775
  */
1775
1776
  appendLabelChange(targetId: string, label: string | undefined): string {
1776
- if (!this.byId.has(targetId)) {
1777
+ if (!this.#byId.has(targetId)) {
1777
1778
  throw new Error(`Entry ${targetId} not found`);
1778
1779
  }
1779
1780
  const entry: LabelEntry = {
1780
1781
  type: "label",
1781
- id: generateId(this.byId),
1782
- parentId: this.leafId,
1782
+ id: generateId(this.#byId),
1783
+ parentId: this.#leafId,
1783
1784
  timestamp: new Date().toISOString(),
1784
1785
  targetId,
1785
1786
  label,
1786
1787
  };
1787
- this._appendEntry(entry);
1788
+ this.#appendEntry(entry);
1788
1789
  if (label) {
1789
- this.labelsById.set(targetId, label);
1790
+ this.#labelsById.set(targetId, label);
1790
1791
  } else {
1791
- this.labelsById.delete(targetId);
1792
+ this.#labelsById.delete(targetId);
1792
1793
  }
1793
1794
  return entry.id;
1794
1795
  }
@@ -1800,11 +1801,11 @@ export class SessionManager {
1800
1801
  */
1801
1802
  getBranch(fromId?: string): SessionEntry[] {
1802
1803
  const path: SessionEntry[] = [];
1803
- const startId = fromId ?? this.leafId;
1804
- let current = startId ? this.byId.get(startId) : undefined;
1804
+ const startId = fromId ?? this.#leafId;
1805
+ let current = startId ? this.#byId.get(startId) : undefined;
1805
1806
  while (current) {
1806
1807
  path.unshift(current);
1807
- current = current.parentId ? this.byId.get(current.parentId) : undefined;
1808
+ current = current.parentId ? this.#byId.get(current.parentId) : undefined;
1808
1809
  }
1809
1810
  return path;
1810
1811
  }
@@ -1814,14 +1815,14 @@ export class SessionManager {
1814
1815
  * Uses tree traversal from current leaf.
1815
1816
  */
1816
1817
  buildSessionContext(): SessionContext {
1817
- return buildSessionContext(this.getEntries(), this.leafId, this.byId);
1818
+ return buildSessionContext(this.getEntries(), this.#leafId, this.#byId);
1818
1819
  }
1819
1820
 
1820
1821
  /**
1821
1822
  * Get session header.
1822
1823
  */
1823
1824
  getHeader(): SessionHeader | null {
1824
- const h = this.fileEntries.find(e => e.type === "session");
1825
+ const h = this.#fileEntries.find(e => e.type === "session");
1825
1826
  return h ? (h as SessionHeader) : null;
1826
1827
  }
1827
1828
 
@@ -1831,7 +1832,7 @@ export class SessionManager {
1831
1832
  * change the leaf pointer. Entries cannot be modified or deleted.
1832
1833
  */
1833
1834
  getEntries(): SessionEntry[] {
1834
- return this.fileEntries.filter((e): e is SessionEntry => e.type !== "session");
1835
+ return this.#fileEntries.filter((e): e is SessionEntry => e.type !== "session");
1835
1836
  }
1836
1837
 
1837
1838
  /**
@@ -1846,7 +1847,7 @@ export class SessionManager {
1846
1847
 
1847
1848
  // Create nodes with resolved labels
1848
1849
  for (const entry of entries) {
1849
- const label = this.labelsById.get(entry.id);
1850
+ const label = this.#labelsById.get(entry.id);
1850
1851
  nodeMap.set(entry.id, { entry, children: [], label });
1851
1852
  }
1852
1853
 
@@ -1889,10 +1890,10 @@ export class SessionManager {
1889
1890
  * are not modified or deleted.
1890
1891
  */
1891
1892
  branch(branchFromId: string): void {
1892
- if (!this.byId.has(branchFromId)) {
1893
+ if (!this.#byId.has(branchFromId)) {
1893
1894
  throw new Error(`Entry ${branchFromId} not found`);
1894
1895
  }
1895
- this.leafId = branchFromId;
1896
+ this.#leafId = branchFromId;
1896
1897
  }
1897
1898
 
1898
1899
  /**
@@ -1901,7 +1902,7 @@ export class SessionManager {
1901
1902
  * Use this when navigating to re-edit the first user message.
1902
1903
  */
1903
1904
  resetLeaf(): void {
1904
- this.leafId = null;
1905
+ this.#leafId = null;
1905
1906
  }
1906
1907
 
1907
1908
  /**
@@ -1910,13 +1911,13 @@ export class SessionManager {
1910
1911
  * context from the abandoned conversation path.
1911
1912
  */
1912
1913
  branchWithSummary(branchFromId: string | null, summary: string, details?: unknown, fromExtension?: boolean): string {
1913
- if (branchFromId !== null && !this.byId.has(branchFromId)) {
1914
+ if (branchFromId !== null && !this.#byId.has(branchFromId)) {
1914
1915
  throw new Error(`Entry ${branchFromId} not found`);
1915
1916
  }
1916
- this.leafId = branchFromId;
1917
+ this.#leafId = branchFromId;
1917
1918
  const entry: BranchSummaryEntry = {
1918
1919
  type: "branch_summary",
1919
- id: generateId(this.byId),
1920
+ id: generateId(this.#byId),
1920
1921
  parentId: branchFromId,
1921
1922
  timestamp: new Date().toISOString(),
1922
1923
  fromId: branchFromId ?? "root",
@@ -1924,7 +1925,7 @@ export class SessionManager {
1924
1925
  details,
1925
1926
  fromExtension,
1926
1927
  };
1927
- this._appendEntry(entry);
1928
+ this.#appendEntry(entry);
1928
1929
  return entry.id;
1929
1930
  }
1930
1931
 
@@ -1934,7 +1935,7 @@ export class SessionManager {
1934
1935
  * Returns the new session file path, or undefined if not persisting.
1935
1936
  */
1936
1937
  createBranchedSession(leafId: string): string | undefined {
1937
- const previousSessionFile = this.sessionFile;
1938
+ const previousSessionFile = this.#sessionFile;
1938
1939
  const branchPath = this.getBranch(leafId);
1939
1940
  if (branchPath.length === 0) {
1940
1941
  throw new Error(`Entry ${leafId} not found`);
@@ -1960,7 +1961,7 @@ export class SessionManager {
1960
1961
  // Collect labels for entries in the path
1961
1962
  const pathEntryIds = new Set(pathWithoutLabels.map(e => e.id));
1962
1963
  const labelsToWrite: Array<{ targetId: string; label: string }> = [];
1963
- for (const [targetId, label] of this.labelsById) {
1964
+ for (const [targetId, label] of this.#labelsById) {
1964
1965
  if (pathEntryIds.has(targetId)) {
1965
1966
  labelsToWrite.push({ targetId, label });
1966
1967
  }
@@ -1991,11 +1992,11 @@ export class SessionManager {
1991
1992
  parentId = labelEntry.id;
1992
1993
  }
1993
1994
  this.storage.writeTextSync(newSessionFile, `${lines.join("\n")}\n`);
1994
- this.fileEntries = [header, ...pathWithoutLabels, ...labelEntries];
1995
- this.sessionId = newSessionId;
1996
- this.sessionFile = newSessionFile;
1997
- this.flushed = true;
1998
- this._buildIndex();
1995
+ this.#fileEntries = [header, ...pathWithoutLabels, ...labelEntries];
1996
+ this.#sessionId = newSessionId;
1997
+ this.#sessionFile = newSessionFile;
1998
+ this.#flushed = true;
1999
+ this.#buildIndex();
1999
2000
  return newSessionFile;
2000
2001
  }
2001
2002
 
@@ -2014,9 +2015,9 @@ export class SessionManager {
2014
2015
  labelEntries.push(labelEntry);
2015
2016
  parentId = labelEntry.id;
2016
2017
  }
2017
- this.fileEntries = [header, ...pathWithoutLabels, ...labelEntries];
2018
- this.sessionId = newSessionId;
2019
- this._buildIndex();
2018
+ this.#fileEntries = [header, ...pathWithoutLabels, ...labelEntries];
2019
+ this.#sessionId = newSessionId;
2020
+ this.#buildIndex();
2020
2021
  return undefined;
2021
2022
  }
2022
2023
 
@@ -2028,7 +2029,7 @@ export class SessionManager {
2028
2029
  static create(cwd: string, sessionDir?: string, storage: SessionStorage = new FileSessionStorage()): SessionManager {
2029
2030
  const dir = sessionDir ?? getDefaultSessionDir(cwd, storage);
2030
2031
  const manager = new SessionManager(cwd, dir, true, storage);
2031
- manager._initNewSession();
2032
+ manager.#initNewSession();
2032
2033
  return manager;
2033
2034
  }
2034
2035
 
@@ -2046,16 +2047,16 @@ export class SessionManager {
2046
2047
  const manager = new SessionManager(cwd, dir, true, storage);
2047
2048
  const forkEntries = structuredClone(await loadEntriesFromFile(sourcePath, storage)) as FileEntry[];
2048
2049
  migrateToCurrentVersion(forkEntries);
2049
- await resolveBlobRefsInEntries(forkEntries, manager.blobStore);
2050
+ await resolveBlobRefsInEntries(forkEntries, manager.#blobStore);
2050
2051
  const sourceHeader = forkEntries.find(e => e.type === "session") as SessionHeader | undefined;
2051
2052
  const historyEntries = forkEntries.filter(entry => entry.type !== "session") as SessionEntry[];
2052
- manager._newSessionSync({ parentSession: sourceHeader?.id });
2053
- const newHeader = manager.fileEntries[0] as SessionHeader;
2053
+ manager.#newSessionSync({ parentSession: sourceHeader?.id });
2054
+ const newHeader = manager.#fileEntries[0] as SessionHeader;
2054
2055
  newHeader.title = sourceHeader?.title;
2055
- manager.fileEntries = [newHeader, ...historyEntries];
2056
- manager.sessionName = newHeader.title;
2057
- manager._buildIndex();
2058
- await manager._rewriteFile();
2056
+ manager.#fileEntries = [newHeader, ...historyEntries];
2057
+ manager.#sessionName = newHeader.title;
2058
+ manager.#buildIndex();
2059
+ await manager.#rewriteFile();
2059
2060
  return manager;
2060
2061
  }
2061
2062
 
@@ -2076,7 +2077,7 @@ export class SessionManager {
2076
2077
  // If no sessionDir provided, derive from file's parent directory
2077
2078
  const dir = sessionDir ?? path.resolve(filePath, "..");
2078
2079
  const manager = new SessionManager(cwd, dir, true, storage);
2079
- await manager._initSessionFile(filePath);
2080
+ await manager.#initSessionFile(filePath);
2080
2081
  return manager;
2081
2082
  }
2082
2083
 
@@ -2096,9 +2097,9 @@ export class SessionManager {
2096
2097
  const mostRecent = terminalSession ?? (await findMostRecentSession(dir, storage));
2097
2098
  const manager = new SessionManager(cwd, dir, true, storage);
2098
2099
  if (mostRecent) {
2099
- await manager._initSessionFile(mostRecent);
2100
+ await manager.#initSessionFile(mostRecent);
2100
2101
  } else {
2101
- manager._initNewSession();
2102
+ manager.#initNewSession();
2102
2103
  }
2103
2104
  return manager;
2104
2105
  }
@@ -2106,7 +2107,7 @@ export class SessionManager {
2106
2107
  /** Create an in-memory session (no file persistence) */
2107
2108
  static inMemory(cwd: string = process.cwd(), storage: SessionStorage = new MemorySessionStorage()): SessionManager {
2108
2109
  const manager = new SessionManager(cwd, "", false, storage);
2109
- manager._initNewSession();
2110
+ manager.#initNewSession();
2110
2111
  return manager;
2111
2112
  }
2112
2113