@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
@@ -47,19 +47,19 @@ interface ToolCallInfo {
47
47
  }
48
48
 
49
49
  class TreeList implements Component {
50
- private flatNodes: FlatNode[] = [];
51
- private filteredNodes: FlatNode[] = [];
52
- private selectedIndex = 0;
53
- private filterMode: FilterMode = "default";
54
- private searchQuery = "";
55
- private toolCallMap: Map<string, ToolCallInfo> = new Map();
56
- private multipleRoots = false;
57
- private activePathIds: Set<string> = new Set();
58
- private lastSelectedId: string | null = null;
59
-
60
- public onSelect?: (entryId: string) => void;
61
- public onCancel?: () => void;
62
- public onLabelEdit?: (entryId: string, currentLabel: string | undefined) => void;
50
+ #flatNodes: FlatNode[] = [];
51
+ #filteredNodes: FlatNode[] = [];
52
+ #selectedIndex = 0;
53
+ #filterMode: FilterMode = "default";
54
+ #searchQuery = "";
55
+ #toolCallMap: Map<string, ToolCallInfo> = new Map();
56
+ #multipleRoots = false;
57
+ #activePathIds: Set<string> = new Set();
58
+ #lastSelectedId: string | null = null;
59
+
60
+ onSelect?: (entryId: string) => void;
61
+ onCancel?: () => void;
62
+ onLabelEdit?: (entryId: string, currentLabel: string | undefined) => void;
63
63
 
64
64
  constructor(
65
65
  tree: SessionTreeNode[],
@@ -67,32 +67,32 @@ class TreeList implements Component {
67
67
  private readonly maxVisibleLines: number,
68
68
  initialSelectedId?: string,
69
69
  ) {
70
- this.multipleRoots = tree.length > 1;
71
- this.flatNodes = this.flattenTree(tree);
72
- this.buildActivePath();
73
- this.applyFilter();
70
+ this.#multipleRoots = tree.length > 1;
71
+ this.#flatNodes = this.#flattenTree(tree);
72
+ this.#buildActivePath();
73
+ this.#applyFilter();
74
74
 
75
75
  // Start with initialSelectedId if provided, otherwise current leaf
76
76
  const targetId = initialSelectedId ?? currentLeafId;
77
- this.selectedIndex = this.findNearestVisibleIndex(targetId);
78
- this.lastSelectedId = this.filteredNodes[this.selectedIndex]?.node.entry.id ?? null;
77
+ this.#selectedIndex = this.#findNearestVisibleIndex(targetId);
78
+ this.#lastSelectedId = this.#filteredNodes[this.#selectedIndex]?.node.entry.id ?? null;
79
79
  }
80
80
 
81
81
  /** Build the set of entry IDs on the path from root to current leaf */
82
- private buildActivePath(): void {
83
- this.activePathIds.clear();
82
+ #buildActivePath(): void {
83
+ this.#activePathIds.clear();
84
84
  if (!this.currentLeafId) return;
85
85
 
86
86
  // Build a map of id -> entry for parent lookup
87
87
  const entryMap = new Map<string, FlatNode>();
88
- for (const flatNode of this.flatNodes) {
88
+ for (const flatNode of this.#flatNodes) {
89
89
  entryMap.set(flatNode.node.entry.id, flatNode);
90
90
  }
91
91
 
92
92
  // Walk from leaf to root
93
93
  let currentId: string | null = this.currentLeafId;
94
94
  while (currentId) {
95
- this.activePathIds.add(currentId);
95
+ this.#activePathIds.add(currentId);
96
96
  const node = entryMap.get(currentId);
97
97
  if (!node) break;
98
98
  currentId = node.node.entry.parentId ?? null;
@@ -103,17 +103,17 @@ class TreeList implements Component {
103
103
  * Find the index of the nearest visible entry, walking up the parent chain if needed.
104
104
  * Returns the index in filteredNodes, or the last index as fallback.
105
105
  */
106
- private findNearestVisibleIndex(entryId: string | null): number {
107
- if (this.filteredNodes.length === 0) return 0;
106
+ #findNearestVisibleIndex(entryId: string | null): number {
107
+ if (this.#filteredNodes.length === 0) return 0;
108
108
 
109
109
  // Build a map for parent lookup
110
110
  const entryMap = new Map<string, FlatNode>();
111
- for (const flatNode of this.flatNodes) {
111
+ for (const flatNode of this.#flatNodes) {
112
112
  entryMap.set(flatNode.node.entry.id, flatNode);
113
113
  }
114
114
 
115
115
  // Build a map of visible entry IDs to their indices in filteredNodes
116
- const visibleIdToIndex = new Map<string, number>(this.filteredNodes.map((node, i) => [node.node.entry.id, i]));
116
+ const visibleIdToIndex = new Map<string, number>(this.#filteredNodes.map((node, i) => [node.node.entry.id, i]));
117
117
 
118
118
  // Walk from entryId up to root, looking for a visible entry
119
119
  let currentId = entryId;
@@ -126,12 +126,12 @@ class TreeList implements Component {
126
126
  }
127
127
 
128
128
  // Fallback: last visible entry
129
- return this.filteredNodes.length - 1;
129
+ return this.#filteredNodes.length - 1;
130
130
  }
131
131
 
132
- private flattenTree(roots: SessionTreeNode[]): FlatNode[] {
132
+ #flattenTree(roots: SessionTreeNode[]): FlatNode[] {
133
133
  const result: FlatNode[] = [];
134
- this.toolCallMap.clear();
134
+ this.#toolCallMap.clear();
135
135
 
136
136
  // Indentation rules:
137
137
  // - At indent 0: stay at 0 unless parent has >1 children (then +1)
@@ -191,7 +191,7 @@ class TreeList implements Component {
191
191
  for (const block of content) {
192
192
  if (typeof block === "object" && block !== null && "type" in block && block.type === "toolCall") {
193
193
  const tc = block as { id: string; name: string; arguments: Record<string, unknown> };
194
- this.toolCallMap.set(tc.id, { name: tc.name, arguments: tc.arguments });
194
+ this.#toolCallMap.set(tc.id, { name: tc.name, arguments: tc.arguments });
195
195
  }
196
196
  }
197
197
  }
@@ -235,7 +235,7 @@ class TreeList implements Component {
235
235
  const connectorDisplayed = showConnector && !isVirtualRootChild;
236
236
  // When connector is displayed, add a gutter entry at the connector's position
237
237
  // Connector is at position (displayIndent - 1), so gutter should be there too
238
- const currentDisplayIndent = this.multipleRoots ? Math.max(0, indent - 1) : indent;
238
+ const currentDisplayIndent = this.#multipleRoots ? Math.max(0, indent - 1) : indent;
239
239
  const connectorPosition = Math.max(0, currentDisplayIndent - 1);
240
240
  const childGutters: GutterInfo[] = connectorDisplayed
241
241
  ? [...gutters, { position: connectorPosition, show: !isLast }]
@@ -259,16 +259,16 @@ class TreeList implements Component {
259
259
  return result;
260
260
  }
261
261
 
262
- private applyFilter(): void {
262
+ #applyFilter(): void {
263
263
  // Update lastSelectedId only when we have a valid selection (non-empty list)
264
264
  // This preserves the selection when switching through empty filter results
265
- if (this.filteredNodes.length > 0) {
266
- this.lastSelectedId = this.filteredNodes[this.selectedIndex]?.node.entry.id ?? this.lastSelectedId;
265
+ if (this.#filteredNodes.length > 0) {
266
+ this.#lastSelectedId = this.#filteredNodes[this.#selectedIndex]?.node.entry.id ?? this.#lastSelectedId;
267
267
  }
268
268
 
269
- const searchTokens = this.searchQuery.toLowerCase().split(/\s+/).filter(Boolean);
269
+ const searchTokens = this.#searchQuery.toLowerCase().split(/\s+/).filter(Boolean);
270
270
 
271
- this.filteredNodes = this.flatNodes.filter(flatNode => {
271
+ this.#filteredNodes = this.#flatNodes.filter(flatNode => {
272
272
  const entry = flatNode.node.entry;
273
273
  const isCurrentLeaf = entry.id === this.currentLeafId;
274
274
 
@@ -276,7 +276,7 @@ class TreeList implements Component {
276
276
  // Always show current leaf so active position is visible
277
277
  if (entry.type === "message" && entry.message.role === "assistant" && !isCurrentLeaf) {
278
278
  const msg = entry.message as { stopReason?: string; content?: unknown };
279
- const hasText = this.hasTextContent(msg.content);
279
+ const hasText = this.#hasTextContent(msg.content);
280
280
  const isErrorOrAborted = msg.stopReason && msg.stopReason !== "stop" && msg.stopReason !== "toolUse";
281
281
  // Only hide if no text AND not an error/aborted message
282
282
  if (!hasText && !isErrorOrAborted) {
@@ -293,7 +293,7 @@ class TreeList implements Component {
293
293
  entry.type === "model_change" ||
294
294
  entry.type === "thinking_level_change";
295
295
 
296
- switch (this.filterMode) {
296
+ switch (this.#filterMode) {
297
297
  case "user-only":
298
298
  // Just user messages
299
299
  passesFilter = entry.type === "message" && entry.message.role === "user";
@@ -320,7 +320,7 @@ class TreeList implements Component {
320
320
 
321
321
  // Apply search filter
322
322
  if (searchTokens.length > 0) {
323
- const nodeText = this.getSearchableText(flatNode.node).toLowerCase();
323
+ const nodeText = this.#getSearchableText(flatNode.node).toLowerCase();
324
324
  return searchTokens.every(token => nodeText.includes(token));
325
325
  }
326
326
 
@@ -328,21 +328,21 @@ class TreeList implements Component {
328
328
  });
329
329
 
330
330
  // Try to preserve cursor on the same node, or find nearest visible ancestor
331
- if (this.lastSelectedId) {
332
- this.selectedIndex = this.findNearestVisibleIndex(this.lastSelectedId);
333
- } else if (this.selectedIndex >= this.filteredNodes.length) {
331
+ if (this.#lastSelectedId) {
332
+ this.#selectedIndex = this.#findNearestVisibleIndex(this.#lastSelectedId);
333
+ } else if (this.#selectedIndex >= this.#filteredNodes.length) {
334
334
  // Clamp index if out of bounds
335
- this.selectedIndex = Math.max(0, this.filteredNodes.length - 1);
335
+ this.#selectedIndex = Math.max(0, this.#filteredNodes.length - 1);
336
336
  }
337
337
 
338
338
  // Update lastSelectedId to the actual selection (may have changed due to parent walk)
339
- if (this.filteredNodes.length > 0) {
340
- this.lastSelectedId = this.filteredNodes[this.selectedIndex]?.node.entry.id ?? this.lastSelectedId;
339
+ if (this.#filteredNodes.length > 0) {
340
+ this.#lastSelectedId = this.#filteredNodes[this.#selectedIndex]?.node.entry.id ?? this.#lastSelectedId;
341
341
  }
342
342
  }
343
343
 
344
344
  /** Get searchable text content from a node */
345
- private getSearchableText(node: SessionTreeNode): string {
345
+ #getSearchableText(node: SessionTreeNode): string {
346
346
  const entry = node.entry;
347
347
  const parts: string[] = [];
348
348
 
@@ -355,7 +355,7 @@ class TreeList implements Component {
355
355
  const msg = entry.message;
356
356
  parts.push(msg.role);
357
357
  if ("content" in msg && msg.content) {
358
- parts.push(this.extractContent(msg.content));
358
+ parts.push(this.#extractContent(msg.content));
359
359
  }
360
360
  if (msg.role === "bashExecution") {
361
361
  const bashMsg = msg as { command?: string };
@@ -368,7 +368,7 @@ class TreeList implements Component {
368
368
  if (typeof entry.content === "string") {
369
369
  parts.push(entry.content);
370
370
  } else {
371
- parts.push(this.extractContent(entry.content));
371
+ parts.push(this.#extractContent(entry.content));
372
372
  }
373
373
  break;
374
374
  }
@@ -398,15 +398,15 @@ class TreeList implements Component {
398
398
  invalidate(): void {}
399
399
 
400
400
  getSearchQuery(): string {
401
- return this.searchQuery;
401
+ return this.#searchQuery;
402
402
  }
403
403
 
404
404
  getSelectedNode(): SessionTreeNode | undefined {
405
- return this.filteredNodes[this.selectedIndex]?.node;
405
+ return this.#filteredNodes[this.#selectedIndex]?.node;
406
406
  }
407
407
 
408
408
  updateNodeLabel(entryId: string, label: string | undefined): void {
409
- for (const flatNode of this.flatNodes) {
409
+ for (const flatNode of this.#flatNodes) {
410
410
  if (flatNode.node.entry.id === entryId) {
411
411
  flatNode.node.label = label;
412
412
  break;
@@ -414,8 +414,8 @@ class TreeList implements Component {
414
414
  }
415
415
  }
416
416
 
417
- private getFilterLabel(): string {
418
- switch (this.filterMode) {
417
+ #getFilterLabel(): string {
418
+ switch (this.#filterMode) {
419
419
  case "no-tools":
420
420
  return " [no-tools]";
421
421
  case "user-only":
@@ -432,31 +432,31 @@ class TreeList implements Component {
432
432
  render(width: number): string[] {
433
433
  const lines: string[] = [];
434
434
 
435
- if (this.filteredNodes.length === 0) {
435
+ if (this.#filteredNodes.length === 0) {
436
436
  lines.push(truncateToWidth(theme.fg("muted", " No entries found"), width));
437
- lines.push(truncateToWidth(theme.fg("muted", ` (0/0)${this.getFilterLabel()}`), width));
437
+ lines.push(truncateToWidth(theme.fg("muted", ` (0/0)${this.#getFilterLabel()}`), width));
438
438
  return lines;
439
439
  }
440
440
 
441
441
  const startIndex = Math.max(
442
442
  0,
443
443
  Math.min(
444
- this.selectedIndex - Math.floor(this.maxVisibleLines / 2),
445
- this.filteredNodes.length - this.maxVisibleLines,
444
+ this.#selectedIndex - Math.floor(this.maxVisibleLines / 2),
445
+ this.#filteredNodes.length - this.maxVisibleLines,
446
446
  ),
447
447
  );
448
- const endIndex = Math.min(startIndex + this.maxVisibleLines, this.filteredNodes.length);
448
+ const endIndex = Math.min(startIndex + this.maxVisibleLines, this.#filteredNodes.length);
449
449
 
450
450
  for (let i = startIndex; i < endIndex; i++) {
451
- const flatNode = this.filteredNodes[i];
451
+ const flatNode = this.#filteredNodes[i];
452
452
  const entry = flatNode.node.entry;
453
- const isSelected = i === this.selectedIndex;
453
+ const isSelected = i === this.#selectedIndex;
454
454
 
455
455
  // Build line: cursor + prefix + path marker + label + content
456
456
  const cursor = isSelected ? theme.fg("accent", "› ") : " ";
457
457
 
458
458
  // If multiple roots, shift display (roots at 0, not 1)
459
- const displayIndent = this.multipleRoots ? Math.max(0, flatNode.indent - 1) : flatNode.indent;
459
+ const displayIndent = this.#multipleRoots ? Math.max(0, flatNode.indent - 1) : flatNode.indent;
460
460
 
461
461
  // Build prefix with gutters at their correct positions
462
462
  // Each gutter has a position (displayIndent where its connector was shown)
@@ -496,11 +496,11 @@ class TreeList implements Component {
496
496
  const prefix = prefixChars.join("");
497
497
 
498
498
  // Active path marker - shown right before the entry text
499
- const isOnActivePath = this.activePathIds.has(entry.id);
499
+ const isOnActivePath = this.#activePathIds.has(entry.id);
500
500
  const pathMarker = isOnActivePath ? theme.fg("accent", `${theme.md.bullet} `) : "";
501
501
 
502
502
  const label = flatNode.node.label ? theme.fg("warning", `[${flatNode.node.label}] `) : "";
503
- const content = this.getEntryDisplayText(flatNode.node, isSelected);
503
+ const content = this.#getEntryDisplayText(flatNode.node, isSelected);
504
504
 
505
505
  let line = cursor + theme.fg("dim", prefix) + pathMarker + label + content;
506
506
  if (isSelected) {
@@ -511,7 +511,7 @@ class TreeList implements Component {
511
511
 
512
512
  lines.push(
513
513
  truncateToWidth(
514
- theme.fg("muted", ` (${this.selectedIndex + 1}/${this.filteredNodes.length})${this.getFilterLabel()}`),
514
+ theme.fg("muted", ` (${this.#selectedIndex + 1}/${this.#filteredNodes.length})${this.#getFilterLabel()}`),
515
515
  width,
516
516
  ),
517
517
  );
@@ -519,7 +519,7 @@ class TreeList implements Component {
519
519
  return lines;
520
520
  }
521
521
 
522
- private getEntryDisplayText(node: SessionTreeNode, isSelected: boolean): string {
522
+ #getEntryDisplayText(node: SessionTreeNode, isSelected: boolean): string {
523
523
  const entry = node.entry;
524
524
  let result: string;
525
525
 
@@ -531,11 +531,11 @@ class TreeList implements Component {
531
531
  const role = msg.role;
532
532
  if (role === "user") {
533
533
  const msgWithContent = msg as { content?: unknown };
534
- const content = normalize(this.extractContent(msgWithContent.content));
534
+ const content = normalize(this.#extractContent(msgWithContent.content));
535
535
  result = theme.fg("accent", "user: ") + content;
536
536
  } else if (role === "assistant") {
537
537
  const msgWithContent = msg as { content?: unknown; stopReason?: string; errorMessage?: string };
538
- const textContent = normalize(this.extractContent(msgWithContent.content));
538
+ const textContent = normalize(this.#extractContent(msgWithContent.content));
539
539
  if (textContent) {
540
540
  result = theme.fg("success", "assistant: ") + textContent;
541
541
  } else if (msgWithContent.stopReason === "aborted") {
@@ -548,9 +548,9 @@ class TreeList implements Component {
548
548
  }
549
549
  } else if (role === "toolResult") {
550
550
  const toolMsg = msg as { toolCallId?: string; toolName?: string };
551
- const toolCall = toolMsg.toolCallId ? this.toolCallMap.get(toolMsg.toolCallId) : undefined;
551
+ const toolCall = toolMsg.toolCallId ? this.#toolCallMap.get(toolMsg.toolCallId) : undefined;
552
552
  if (toolCall) {
553
- result = theme.fg("muted", this.formatToolCall(toolCall.name, toolCall.arguments));
553
+ result = theme.fg("muted", this.#formatToolCall(toolCall.name, toolCall.arguments));
554
554
  } else {
555
555
  result = theme.fg("muted", `[${toolMsg.toolName ?? "tool"}]`);
556
556
  }
@@ -600,7 +600,7 @@ class TreeList implements Component {
600
600
  return isSelected ? theme.bold(result) : result;
601
601
  }
602
602
 
603
- private extractContent(content: unknown): string {
603
+ #extractContent(content: unknown): string {
604
604
  const maxLen = 200;
605
605
  if (typeof content === "string") return content.slice(0, maxLen);
606
606
  if (Array.isArray(content)) {
@@ -616,7 +616,7 @@ class TreeList implements Component {
616
616
  return "";
617
617
  }
618
618
 
619
- private hasTextContent(content: unknown): boolean {
619
+ #hasTextContent(content: unknown): boolean {
620
620
  if (typeof content === "string") return content.trim().length > 0;
621
621
  if (Array.isArray(content)) {
622
622
  for (const c of content) {
@@ -629,7 +629,7 @@ class TreeList implements Component {
629
629
  return false;
630
630
  }
631
631
 
632
- private formatToolCall(name: string, args: Record<string, unknown>): string {
632
+ #formatToolCall(name: string, args: Record<string, unknown>): string {
633
633
  switch (name) {
634
634
  case "read": {
635
635
  const path = shortenPath(String(args.path || args.file_path || ""));
@@ -683,24 +683,24 @@ class TreeList implements Component {
683
683
 
684
684
  handleInput(keyData: string): void {
685
685
  if (matchesKey(keyData, "up")) {
686
- this.selectedIndex = this.selectedIndex === 0 ? this.filteredNodes.length - 1 : this.selectedIndex - 1;
686
+ this.#selectedIndex = this.#selectedIndex === 0 ? this.#filteredNodes.length - 1 : this.#selectedIndex - 1;
687
687
  } else if (matchesKey(keyData, "down")) {
688
- this.selectedIndex = this.selectedIndex === this.filteredNodes.length - 1 ? 0 : this.selectedIndex + 1;
688
+ this.#selectedIndex = this.#selectedIndex === this.#filteredNodes.length - 1 ? 0 : this.#selectedIndex + 1;
689
689
  } else if (matchesKey(keyData, "left")) {
690
690
  // Page up
691
- this.selectedIndex = Math.max(0, this.selectedIndex - this.maxVisibleLines);
691
+ this.#selectedIndex = Math.max(0, this.#selectedIndex - this.maxVisibleLines);
692
692
  } else if (matchesKey(keyData, "right")) {
693
693
  // Page down
694
- this.selectedIndex = Math.min(this.filteredNodes.length - 1, this.selectedIndex + this.maxVisibleLines);
694
+ this.#selectedIndex = Math.min(this.#filteredNodes.length - 1, this.#selectedIndex + this.maxVisibleLines);
695
695
  } else if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
696
- const selected = this.filteredNodes[this.selectedIndex];
696
+ const selected = this.#filteredNodes[this.#selectedIndex];
697
697
  if (selected && this.onSelect) {
698
698
  this.onSelect(selected.node.entry.id);
699
699
  }
700
700
  } else if (matchesKey(keyData, "escape") || matchesKey(keyData, "esc")) {
701
- if (this.searchQuery) {
702
- this.searchQuery = "";
703
- this.applyFilter();
701
+ if (this.#searchQuery) {
702
+ this.#searchQuery = "";
703
+ this.#applyFilter();
704
704
  } else {
705
705
  this.onCancel?.();
706
706
  }
@@ -709,37 +709,37 @@ class TreeList implements Component {
709
709
  } else if (matchesKey(keyData, "shift+ctrl+o") || matchesKey(keyData, "ctrl+shift+o")) {
710
710
  // Cycle filter backwards
711
711
  const modes: FilterMode[] = ["default", "no-tools", "user-only", "labeled-only", "all"];
712
- const currentIndex = modes.indexOf(this.filterMode);
713
- this.filterMode = modes[(currentIndex - 1 + modes.length) % modes.length];
714
- this.applyFilter();
712
+ const currentIndex = modes.indexOf(this.#filterMode);
713
+ this.#filterMode = modes[(currentIndex - 1 + modes.length) % modes.length];
714
+ this.#applyFilter();
715
715
  } else if (matchesKey(keyData, "ctrl+o")) {
716
716
  // Cycle filter forwards: default → no-tools → user-only → labeled-only → all → default
717
717
  const modes: FilterMode[] = ["default", "no-tools", "user-only", "labeled-only", "all"];
718
- const currentIndex = modes.indexOf(this.filterMode);
719
- this.filterMode = modes[(currentIndex + 1) % modes.length];
720
- this.applyFilter();
718
+ const currentIndex = modes.indexOf(this.#filterMode);
719
+ this.#filterMode = modes[(currentIndex + 1) % modes.length];
720
+ this.#applyFilter();
721
721
  } else if (matchesKey(keyData, "alt+d")) {
722
- this.filterMode = "default";
723
- this.applyFilter();
722
+ this.#filterMode = "default";
723
+ this.#applyFilter();
724
724
  } else if (matchesKey(keyData, "alt+t")) {
725
- this.filterMode = "no-tools";
726
- this.applyFilter();
725
+ this.#filterMode = "no-tools";
726
+ this.#applyFilter();
727
727
  } else if (matchesKey(keyData, "alt+u")) {
728
- this.filterMode = "user-only";
729
- this.applyFilter();
728
+ this.#filterMode = "user-only";
729
+ this.#applyFilter();
730
730
  } else if (matchesKey(keyData, "alt+l")) {
731
- this.filterMode = "labeled-only";
732
- this.applyFilter();
731
+ this.#filterMode = "labeled-only";
732
+ this.#applyFilter();
733
733
  } else if (matchesKey(keyData, "alt+a")) {
734
- this.filterMode = "all";
735
- this.applyFilter();
734
+ this.#filterMode = "all";
735
+ this.#applyFilter();
736
736
  } else if (matchesKey(keyData, "backspace")) {
737
- if (this.searchQuery.length > 0) {
738
- this.searchQuery = this.searchQuery.slice(0, -1);
739
- this.applyFilter();
737
+ if (this.#searchQuery.length > 0) {
738
+ this.#searchQuery = this.#searchQuery.slice(0, -1);
739
+ this.#applyFilter();
740
740
  }
741
- } else if (matchesKey(keyData, "shift+l") && !this.searchQuery) {
742
- const selected = this.filteredNodes[this.selectedIndex];
741
+ } else if (matchesKey(keyData, "shift+l") && !this.#searchQuery) {
742
+ const selected = this.#filteredNodes[this.#selectedIndex];
743
743
  if (selected && this.onLabelEdit) {
744
744
  this.onLabelEdit(selected.node.entry.id, selected.node.label);
745
745
  }
@@ -749,8 +749,8 @@ class TreeList implements Component {
749
749
  return code < 32 || code === 0x7f || (code >= 0x80 && code <= 0x9f);
750
750
  });
751
751
  if (!hasControlChars && keyData.length > 0) {
752
- this.searchQuery += keyData;
753
- this.applyFilter();
752
+ this.#searchQuery += keyData;
753
+ this.#applyFilter();
754
754
  }
755
755
  }
756
756
  }
@@ -775,17 +775,17 @@ class SearchLine implements Component {
775
775
 
776
776
  /** Label input component shown when editing a label */
777
777
  class LabelInput implements Component {
778
- private input: Input;
779
- public onSubmit?: (entryId: string, label: string | undefined) => void;
780
- public onCancel?: () => void;
778
+ #input: Input;
779
+ onSubmit?: (entryId: string, label: string | undefined) => void;
780
+ onCancel?: () => void;
781
781
 
782
782
  constructor(
783
783
  private readonly entryId: string,
784
784
  currentLabel: string | undefined,
785
785
  ) {
786
- this.input = new Input();
786
+ this.#input = new Input();
787
787
  if (currentLabel) {
788
- this.input.setValue(currentLabel);
788
+ this.#input.setValue(currentLabel);
789
789
  }
790
790
  }
791
791
 
@@ -796,19 +796,19 @@ class LabelInput implements Component {
796
796
  const indent = " ";
797
797
  const availableWidth = width - indent.length;
798
798
  lines.push(truncateToWidth(`${indent}${theme.fg("muted", "Label (empty to remove):")}`, width));
799
- lines.push(...this.input.render(availableWidth).map(line => truncateToWidth(`${indent}${line}`, width)));
799
+ lines.push(...this.#input.render(availableWidth).map(line => truncateToWidth(`${indent}${line}`, width)));
800
800
  lines.push(truncateToWidth(`${indent}${theme.fg("dim", "enter: save esc: cancel")}`, width));
801
801
  return lines;
802
802
  }
803
803
 
804
804
  handleInput(keyData: string): void {
805
805
  if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
806
- const value = this.input.getValue().trim();
806
+ const value = this.#input.getValue().trim();
807
807
  this.onSubmit?.(this.entryId, value || undefined);
808
808
  } else if (matchesKey(keyData, "escape") || matchesKey(keyData, "esc")) {
809
809
  this.onCancel?.();
810
810
  } else {
811
- this.input.handleInput(keyData);
811
+ this.#input.handleInput(keyData);
812
812
  }
813
813
  }
814
814
  }
@@ -817,10 +817,10 @@ class LabelInput implements Component {
817
817
  * Component that renders a session tree selector for navigation
818
818
  */
819
819
  export class TreeSelectorComponent extends Container {
820
- private treeList: TreeList;
821
- private labelInput: LabelInput | null = null;
822
- private labelInputContainer: Container;
823
- private treeContainer: Container;
820
+ #treeList: TreeList;
821
+ #labelInput: LabelInput | null = null;
822
+ #labelInputContainer: Container;
823
+ #treeContainer: Container;
824
824
 
825
825
  constructor(
826
826
  tree: SessionTreeNode[],
@@ -833,15 +833,15 @@ export class TreeSelectorComponent extends Container {
833
833
  super();
834
834
  const maxVisibleLines = Math.max(5, Math.floor(terminalHeight / 2));
835
835
 
836
- this.treeList = new TreeList(tree, currentLeafId, maxVisibleLines);
837
- this.treeList.onSelect = onSelect;
838
- this.treeList.onCancel = onCancel;
839
- this.treeList.onLabelEdit = (entryId, currentLabel) => this.showLabelInput(entryId, currentLabel);
836
+ this.#treeList = new TreeList(tree, currentLeafId, maxVisibleLines);
837
+ this.#treeList.onSelect = onSelect;
838
+ this.#treeList.onCancel = onCancel;
839
+ this.#treeList.onLabelEdit = (entryId, currentLabel) => this.#showLabelInput(entryId, currentLabel);
840
840
 
841
- this.treeContainer = new Container();
842
- this.treeContainer.addChild(this.treeList);
841
+ this.#treeContainer = new Container();
842
+ this.#treeContainer.addChild(this.#treeList);
843
843
 
844
- this.labelInputContainer = new Container();
844
+ this.#labelInputContainer = new Container();
845
845
 
846
846
  this.addChild(new Spacer(1));
847
847
  this.addChild(new DynamicBorder());
@@ -856,11 +856,11 @@ export class TreeSelectorComponent extends Container {
856
856
  0,
857
857
  ),
858
858
  );
859
- this.addChild(new SearchLine(this.treeList));
859
+ this.addChild(new SearchLine(this.#treeList));
860
860
  this.addChild(new DynamicBorder());
861
861
  this.addChild(new Spacer(1));
862
- this.addChild(this.treeContainer);
863
- this.addChild(this.labelInputContainer);
862
+ this.addChild(this.#treeContainer);
863
+ this.addChild(this.#labelInputContainer);
864
864
  this.addChild(new Spacer(1));
865
865
  this.addChild(new DynamicBorder());
866
866
 
@@ -869,36 +869,36 @@ export class TreeSelectorComponent extends Container {
869
869
  }
870
870
  }
871
871
 
872
- private showLabelInput(entryId: string, currentLabel: string | undefined): void {
873
- this.labelInput = new LabelInput(entryId, currentLabel);
874
- this.labelInput.onSubmit = (id, label) => {
875
- this.treeList.updateNodeLabel(id, label);
872
+ #showLabelInput(entryId: string, currentLabel: string | undefined): void {
873
+ this.#labelInput = new LabelInput(entryId, currentLabel);
874
+ this.#labelInput.onSubmit = (id, label) => {
875
+ this.#treeList.updateNodeLabel(id, label);
876
876
  this.onLabelChangeCallback?.(id, label);
877
- this.hideLabelInput();
877
+ this.#hideLabelInput();
878
878
  };
879
- this.labelInput.onCancel = () => this.hideLabelInput();
879
+ this.#labelInput.onCancel = () => this.#hideLabelInput();
880
880
 
881
- this.treeContainer.clear();
882
- this.labelInputContainer.clear();
883
- this.labelInputContainer.addChild(this.labelInput);
881
+ this.#treeContainer.clear();
882
+ this.#labelInputContainer.clear();
883
+ this.#labelInputContainer.addChild(this.#labelInput);
884
884
  }
885
885
 
886
- private hideLabelInput(): void {
887
- this.labelInput = null;
888
- this.labelInputContainer.clear();
889
- this.treeContainer.clear();
890
- this.treeContainer.addChild(this.treeList);
886
+ #hideLabelInput(): void {
887
+ this.#labelInput = null;
888
+ this.#labelInputContainer.clear();
889
+ this.#treeContainer.clear();
890
+ this.#treeContainer.addChild(this.#treeList);
891
891
  }
892
892
 
893
893
  handleInput(keyData: string): void {
894
- if (this.labelInput) {
895
- this.labelInput.handleInput(keyData);
894
+ if (this.#labelInput) {
895
+ this.#labelInput.handleInput(keyData);
896
896
  } else {
897
- this.treeList.handleInput(keyData);
897
+ this.#treeList.handleInput(keyData);
898
898
  }
899
899
  }
900
900
 
901
901
  getTreeList(): TreeList {
902
- return this.treeList;
902
+ return this.#treeList;
903
903
  }
904
904
  }