@mariozechner/pi-coding-agent 0.49.2 → 0.50.0

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 (237) hide show
  1. package/CHANGELOG.md +126 -1
  2. package/README.md +310 -1229
  3. package/dist/cli/args.d.ts +5 -0
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +57 -22
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/cli/config-selector.d.ts +14 -0
  8. package/dist/cli/config-selector.d.ts.map +1 -0
  9. package/dist/cli/config-selector.js +31 -0
  10. package/dist/cli/config-selector.js.map +1 -0
  11. package/dist/cli/session-picker.d.ts.map +1 -1
  12. package/dist/cli/session-picker.js +1 -1
  13. package/dist/cli/session-picker.js.map +1 -1
  14. package/dist/config.d.ts +2 -0
  15. package/dist/config.d.ts.map +1 -1
  16. package/dist/config.js +6 -0
  17. package/dist/config.js.map +1 -1
  18. package/dist/core/agent-session.d.ts +53 -34
  19. package/dist/core/agent-session.d.ts.map +1 -1
  20. package/dist/core/agent-session.js +262 -67
  21. package/dist/core/agent-session.js.map +1 -1
  22. package/dist/core/auth-storage.d.ts +8 -18
  23. package/dist/core/auth-storage.d.ts.map +1 -1
  24. package/dist/core/auth-storage.js +39 -55
  25. package/dist/core/auth-storage.js.map +1 -1
  26. package/dist/core/bash-executor.d.ts.map +1 -1
  27. package/dist/core/bash-executor.js +2 -1
  28. package/dist/core/bash-executor.js.map +1 -1
  29. package/dist/core/diagnostics.d.ts +15 -0
  30. package/dist/core/diagnostics.d.ts.map +1 -0
  31. package/dist/core/diagnostics.js +2 -0
  32. package/dist/core/diagnostics.js.map +1 -0
  33. package/dist/core/export-html/template.css +9 -0
  34. package/dist/core/export-html/template.js +6 -4
  35. package/dist/core/extensions/index.d.ts +1 -1
  36. package/dist/core/extensions/index.d.ts.map +1 -1
  37. package/dist/core/extensions/index.js.map +1 -1
  38. package/dist/core/extensions/loader.d.ts +1 -1
  39. package/dist/core/extensions/loader.d.ts.map +1 -1
  40. package/dist/core/extensions/loader.js +10 -1
  41. package/dist/core/extensions/loader.js.map +1 -1
  42. package/dist/core/extensions/runner.d.ts +9 -3
  43. package/dist/core/extensions/runner.d.ts.map +1 -1
  44. package/dist/core/extensions/runner.js +39 -12
  45. package/dist/core/extensions/runner.js.map +1 -1
  46. package/dist/core/extensions/types.d.ts +112 -1
  47. package/dist/core/extensions/types.d.ts.map +1 -1
  48. package/dist/core/extensions/types.js.map +1 -1
  49. package/dist/core/footer-data-provider.d.ts +9 -2
  50. package/dist/core/footer-data-provider.d.ts.map +1 -1
  51. package/dist/core/footer-data-provider.js +13 -0
  52. package/dist/core/footer-data-provider.js.map +1 -1
  53. package/dist/core/model-registry.d.ts +42 -2
  54. package/dist/core/model-registry.d.ts.map +1 -1
  55. package/dist/core/model-registry.js +154 -44
  56. package/dist/core/model-registry.js.map +1 -1
  57. package/dist/core/model-resolver.d.ts.map +1 -1
  58. package/dist/core/model-resolver.js +3 -2
  59. package/dist/core/model-resolver.js.map +1 -1
  60. package/dist/core/package-manager.d.ts +129 -0
  61. package/dist/core/package-manager.d.ts.map +1 -0
  62. package/dist/core/package-manager.js +1148 -0
  63. package/dist/core/package-manager.js.map +1 -0
  64. package/dist/core/prompt-templates.d.ts +6 -0
  65. package/dist/core/prompt-templates.d.ts.map +1 -1
  66. package/dist/core/prompt-templates.js +114 -54
  67. package/dist/core/prompt-templates.js.map +1 -1
  68. package/dist/core/resource-loader.d.ts +160 -0
  69. package/dist/core/resource-loader.d.ts.map +1 -0
  70. package/dist/core/resource-loader.js +604 -0
  71. package/dist/core/resource-loader.js.map +1 -0
  72. package/dist/core/sdk.d.ts +14 -105
  73. package/dist/core/sdk.d.ts.map +1 -1
  74. package/dist/core/sdk.js +52 -304
  75. package/dist/core/sdk.js.map +1 -1
  76. package/dist/core/session-manager.d.ts.map +1 -1
  77. package/dist/core/session-manager.js +45 -1
  78. package/dist/core/session-manager.js.map +1 -1
  79. package/dist/core/settings-manager.d.ts +39 -16
  80. package/dist/core/settings-manager.d.ts.map +1 -1
  81. package/dist/core/settings-manager.js +107 -25
  82. package/dist/core/settings-manager.js.map +1 -1
  83. package/dist/core/skills.d.ts +18 -10
  84. package/dist/core/skills.d.ts.map +1 -1
  85. package/dist/core/skills.js +126 -93
  86. package/dist/core/skills.js.map +1 -1
  87. package/dist/core/system-prompt.d.ts +3 -27
  88. package/dist/core/system-prompt.d.ts.map +1 -1
  89. package/dist/core/system-prompt.js +16 -103
  90. package/dist/core/system-prompt.js.map +1 -1
  91. package/dist/core/tools/bash.d.ts.map +1 -1
  92. package/dist/core/tools/bash.js +2 -1
  93. package/dist/core/tools/bash.js.map +1 -1
  94. package/dist/core/tools/read.d.ts.map +1 -1
  95. package/dist/core/tools/read.js +4 -4
  96. package/dist/core/tools/read.js.map +1 -1
  97. package/dist/index.d.ts +12 -7
  98. package/dist/index.d.ts.map +1 -1
  99. package/dist/index.js +8 -6
  100. package/dist/index.js.map +1 -1
  101. package/dist/main.d.ts.map +1 -1
  102. package/dist/main.js +209 -97
  103. package/dist/main.js.map +1 -1
  104. package/dist/modes/interactive/components/assistant-message.d.ts +3 -2
  105. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  106. package/dist/modes/interactive/components/assistant-message.js +5 -3
  107. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  108. package/dist/modes/interactive/components/bordered-loader.d.ts +5 -1
  109. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
  110. package/dist/modes/interactive/components/bordered-loader.js +29 -9
  111. package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
  112. package/dist/modes/interactive/components/branch-summary-message.d.ts +3 -2
  113. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  114. package/dist/modes/interactive/components/branch-summary-message.js +4 -2
  115. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  116. package/dist/modes/interactive/components/compaction-summary-message.d.ts +3 -2
  117. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  118. package/dist/modes/interactive/components/compaction-summary-message.js +4 -2
  119. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  120. package/dist/modes/interactive/components/config-selector.d.ts +71 -0
  121. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -0
  122. package/dist/modes/interactive/components/config-selector.js +468 -0
  123. package/dist/modes/interactive/components/config-selector.js.map +1 -0
  124. package/dist/modes/interactive/components/custom-message.d.ts +3 -2
  125. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
  126. package/dist/modes/interactive/components/custom-message.js +4 -2
  127. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  128. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  129. package/dist/modes/interactive/components/footer.js +9 -0
  130. package/dist/modes/interactive/components/footer.js.map +1 -1
  131. package/dist/modes/interactive/components/index.d.ts +1 -0
  132. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  133. package/dist/modes/interactive/components/index.js +1 -0
  134. package/dist/modes/interactive/components/index.js.map +1 -1
  135. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  136. package/dist/modes/interactive/components/oauth-selector.js +3 -4
  137. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  138. package/dist/modes/interactive/components/session-selector.d.ts +18 -1
  139. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  140. package/dist/modes/interactive/components/session-selector.js +195 -87
  141. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  142. package/dist/modes/interactive/components/skill-invocation-message.d.ts +17 -0
  143. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -0
  144. package/dist/modes/interactive/components/skill-invocation-message.js +47 -0
  145. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -0
  146. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  147. package/dist/modes/interactive/components/tool-execution.js +12 -5
  148. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  149. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  150. package/dist/modes/interactive/components/tree-selector.js +2 -2
  151. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  152. package/dist/modes/interactive/components/user-message.d.ts +2 -2
  153. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  154. package/dist/modes/interactive/components/user-message.js +2 -2
  155. package/dist/modes/interactive/components/user-message.js.map +1 -1
  156. package/dist/modes/interactive/interactive-mode.d.ts +47 -2
  157. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  158. package/dist/modes/interactive/interactive-mode.js +566 -211
  159. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  160. package/dist/modes/interactive/theme/dark.json +1 -1
  161. package/dist/modes/interactive/theme/light.json +1 -1
  162. package/dist/modes/interactive/theme/theme-schema.json +8 -1
  163. package/dist/modes/interactive/theme/theme.d.ts +8 -1
  164. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  165. package/dist/modes/interactive/theme/theme.js +79 -28
  166. package/dist/modes/interactive/theme/theme.js.map +1 -1
  167. package/dist/modes/print-mode.d.ts.map +1 -1
  168. package/dist/modes/print-mode.js +25 -89
  169. package/dist/modes/print-mode.js.map +1 -1
  170. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  171. package/dist/modes/rpc/rpc-mode.js +32 -92
  172. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  173. package/dist/utils/git.d.ts +2 -0
  174. package/dist/utils/git.d.ts.map +1 -0
  175. package/dist/utils/git.js +6 -0
  176. package/dist/utils/git.js.map +1 -0
  177. package/dist/utils/shell.d.ts +1 -0
  178. package/dist/utils/shell.d.ts.map +1 -1
  179. package/dist/utils/shell.js +16 -2
  180. package/dist/utils/shell.js.map +1 -1
  181. package/dist/utils/sleep.d.ts +5 -0
  182. package/dist/utils/sleep.d.ts.map +1 -0
  183. package/dist/utils/sleep.js +17 -0
  184. package/dist/utils/sleep.js.map +1 -0
  185. package/docs/compaction.md +23 -21
  186. package/docs/custom-provider.md +538 -0
  187. package/docs/development.md +69 -0
  188. package/docs/extensions.md +180 -118
  189. package/docs/images/doom-extension.png +0 -0
  190. package/docs/images/interactive-mode.png +0 -0
  191. package/docs/images/tree-view.png +0 -0
  192. package/docs/json.md +79 -0
  193. package/docs/keybindings.md +162 -0
  194. package/docs/models.md +193 -0
  195. package/docs/packages.md +163 -0
  196. package/docs/prompt-templates.md +67 -0
  197. package/docs/providers.md +147 -0
  198. package/docs/sdk.md +111 -178
  199. package/docs/session.md +167 -16
  200. package/docs/settings.md +216 -0
  201. package/docs/shell-aliases.md +13 -0
  202. package/docs/skills.md +111 -202
  203. package/docs/terminal-setup.md +65 -0
  204. package/docs/themes.md +295 -0
  205. package/docs/tui.md +36 -5
  206. package/docs/windows.md +17 -0
  207. package/examples/README.md +1 -0
  208. package/examples/extensions/README.md +24 -2
  209. package/examples/extensions/antigravity-image-gen.ts +413 -0
  210. package/examples/extensions/bookmark.ts +50 -0
  211. package/examples/extensions/custom-provider-anthropic/index.ts +604 -0
  212. package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
  213. package/examples/extensions/custom-provider-anthropic/package.json +19 -0
  214. package/examples/extensions/custom-provider-gitlab-duo/index.ts +349 -0
  215. package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -0
  216. package/examples/extensions/custom-provider-gitlab-duo/test.ts +82 -0
  217. package/examples/extensions/doom-overlay/doom/build.sh +1 -1
  218. package/examples/extensions/event-bus.ts +43 -0
  219. package/examples/extensions/inline-bash.ts +94 -0
  220. package/examples/extensions/message-renderer.ts +59 -0
  221. package/examples/extensions/session-name.ts +27 -0
  222. package/examples/extensions/space-invaders.ts +560 -0
  223. package/examples/extensions/with-deps/package-lock.json +2 -2
  224. package/examples/extensions/with-deps/package.json +1 -1
  225. package/examples/sdk/02-custom-model.ts +3 -3
  226. package/examples/sdk/03-custom-prompt.ts +20 -9
  227. package/examples/sdk/04-skills.ts +26 -27
  228. package/examples/sdk/06-extensions.ts +15 -6
  229. package/examples/sdk/07-context-files.ts +22 -18
  230. package/examples/sdk/08-prompt-templates.ts +19 -14
  231. package/examples/sdk/09-api-keys-and-oauth.ts +5 -12
  232. package/examples/sdk/10-settings.ts +3 -3
  233. package/examples/sdk/12-full-control.ts +16 -7
  234. package/examples/sdk/README.md +24 -30
  235. package/package.json +4 -4
  236. package/docs/theme.md +0 -617
  237. package/examples/extensions/chalk-logger.ts +0 -26
@@ -2,10 +2,10 @@ import { spawnSync } from "node:child_process";
2
2
  import { existsSync } from "node:fs";
3
3
  import { unlink } from "node:fs/promises";
4
4
  import * as os from "node:os";
5
- import { Container, getEditorKeybindings, Input, matchesKey, Spacer, truncateToWidth, visibleWidth, } from "@mariozechner/pi-tui";
5
+ import { Container, getEditorKeybindings, Input, matchesKey, Spacer, Text, truncateToWidth, visibleWidth, } from "@mariozechner/pi-tui";
6
6
  import { theme } from "../theme/theme.js";
7
7
  import { DynamicBorder } from "./dynamic-border.js";
8
- import { keyHint, rawKeyHint } from "./keybinding-hints.js";
8
+ import { keyHint } from "./keybinding-hints.js";
9
9
  import { filterAndSortSessions } from "./session-selector-search.js";
10
10
  function shortenPath(path) {
11
11
  const home = os.homedir();
@@ -44,6 +44,7 @@ class SessionSelectorHeader {
44
44
  confirmingDeletePath = null;
45
45
  statusMessage = null;
46
46
  statusTimeout = null;
47
+ showRenameHint = false;
47
48
  constructor(scope, sortMode, requestRender) {
48
49
  this.scope = scope;
49
50
  this.sortMode = sortMode;
@@ -66,6 +67,9 @@ class SessionSelectorHeader {
66
67
  setShowPath(showPath) {
67
68
  this.showPath = showPath;
68
69
  }
70
+ setShowRenameHint(show) {
71
+ this.showRenameHint = show;
72
+ }
69
73
  setConfirmingDeletePath(path) {
70
74
  this.confirmingDeletePath = path;
71
75
  }
@@ -124,11 +128,15 @@ class SessionSelectorHeader {
124
128
  const pathState = this.showPath ? "(on)" : "(off)";
125
129
  const sep = theme.fg("muted", " · ");
126
130
  const hint1 = keyHint("tab", "scope") + sep + theme.fg("muted", 're:<pattern> regex · "phrase" exact');
127
- const hint2 = rawKeyHint("ctrl+r", "sort") +
128
- sep +
129
- rawKeyHint("ctrl+d", "delete") +
130
- sep +
131
- rawKeyHint("ctrl+p", `path ${pathState}`);
131
+ const hint2Parts = [
132
+ keyHint("toggleSessionSort", "sort"),
133
+ keyHint("deleteSession", "delete"),
134
+ keyHint("toggleSessionPath", `path ${pathState}`),
135
+ ];
136
+ if (this.showRenameHint) {
137
+ hint2Parts.push(keyHint("renameSession", "rename"));
138
+ }
139
+ const hint2 = hint2Parts.join(sep);
132
140
  hintLine1 = truncateToWidth(hint1, width, "…");
133
141
  hintLine2 = truncateToWidth(hint2, width, "…");
134
142
  }
@@ -139,6 +147,10 @@ class SessionSelectorHeader {
139
147
  * Custom session list component with multi-line items and search
140
148
  */
141
149
  class SessionList {
150
+ getSelectedSessionPath() {
151
+ const selected = this.filteredSessions[this.selectedIndex];
152
+ return selected?.path;
153
+ }
142
154
  allSessions = [];
143
155
  filteredSessions = [];
144
156
  selectedIndex = 0;
@@ -156,6 +168,7 @@ class SessionList {
156
168
  onTogglePath;
157
169
  onDeleteConfirmationChange;
158
170
  onDeleteSession;
171
+ onRenameSession;
159
172
  onError;
160
173
  maxVisible = 5; // Max sessions visible (each session: message + metadata + optional path + blank)
161
174
  // Focusable implementation - propagate to searchInput for IME cursor positioning
@@ -311,24 +324,32 @@ class SessionList {
311
324
  }
312
325
  return;
313
326
  }
314
- if (matchesKey(keyData, "ctrl+r")) {
327
+ if (kb.matches(keyData, "toggleSessionSort")) {
315
328
  this.onToggleSort?.();
316
329
  return;
317
330
  }
318
331
  // Ctrl+P: toggle path display
319
- if (matchesKey(keyData, "ctrl+p")) {
332
+ if (kb.matches(keyData, "toggleSessionPath")) {
320
333
  this.showPath = !this.showPath;
321
334
  this.onTogglePath?.(this.showPath);
322
335
  return;
323
336
  }
324
337
  // Ctrl+D: initiate delete confirmation (useful on terminals that don't distinguish Ctrl+Backspace from Backspace)
325
- if (matchesKey(keyData, "ctrl+d")) {
338
+ if (kb.matches(keyData, "deleteSession")) {
326
339
  this.startDeleteConfirmationForSelectedSession();
327
340
  return;
328
341
  }
342
+ // Ctrl+R: rename selected session
343
+ if (matchesKey(keyData, "ctrl+r")) {
344
+ const selected = this.filteredSessions[this.selectedIndex];
345
+ if (selected) {
346
+ this.onRenameSession?.(selected.path);
347
+ }
348
+ return;
349
+ }
329
350
  // Ctrl+Backspace: non-invasive convenience alias for delete
330
351
  // Only triggers deletion when the query is empty; otherwise it is forwarded to the input
331
- if (matchesKey(keyData, "ctrl+backspace")) {
352
+ if (kb.matches(keyData, "deleteSessionNoninvasive")) {
332
353
  if (this.searchInput.getValue().length > 0) {
333
354
  this.searchInput.handleInput(keyData);
334
355
  this.filterSessions(this.searchInput.getValue());
@@ -413,6 +434,19 @@ async function deleteSessionFile(sessionPath) {
413
434
  * Component that renders a session selector
414
435
  */
415
436
  export class SessionSelectorComponent extends Container {
437
+ handleInput(data) {
438
+ if (this.mode === "rename") {
439
+ const kb = getEditorKeybindings();
440
+ if (kb.matches(data, "selectCancel") || matchesKey(data, "ctrl+c")) {
441
+ this.exitRenameMode();
442
+ return;
443
+ }
444
+ this.renameInput.handleInput(data);
445
+ return;
446
+ }
447
+ this.sessionList.handleInput(data);
448
+ }
449
+ canRename = true;
416
450
  sessionList;
417
451
  header;
418
452
  scope = "current";
@@ -423,9 +457,13 @@ export class SessionSelectorComponent extends Container {
423
457
  allSessionsLoader;
424
458
  onCancel;
425
459
  requestRender;
460
+ renameSession;
426
461
  currentLoading = false;
427
462
  allLoading = false;
428
463
  allLoadSeq = 0;
464
+ mode = "list";
465
+ renameInput = new Input();
466
+ renameTargetPath = null;
429
467
  // Focusable implementation - propagate to sessionList for IME cursor positioning
430
468
  _focused = false;
431
469
  get focused() {
@@ -434,22 +472,41 @@ export class SessionSelectorComponent extends Container {
434
472
  set focused(value) {
435
473
  this._focused = value;
436
474
  this.sessionList.focused = value;
475
+ this.renameInput.focused = value;
476
+ if (value && this.mode === "rename") {
477
+ this.renameInput.focused = true;
478
+ }
437
479
  }
438
- constructor(currentSessionsLoader, allSessionsLoader, onSelect, onCancel, onExit, requestRender, currentSessionFilePath) {
480
+ buildBaseLayout(content, options) {
481
+ this.clear();
482
+ this.addChild(new Spacer(1));
483
+ this.addChild(new DynamicBorder((s) => theme.fg("accent", s)));
484
+ this.addChild(new Spacer(1));
485
+ if (options?.showHeader ?? true) {
486
+ this.addChild(this.header);
487
+ this.addChild(new Spacer(1));
488
+ }
489
+ this.addChild(content);
490
+ this.addChild(new Spacer(1));
491
+ this.addChild(new DynamicBorder((s) => theme.fg("accent", s)));
492
+ }
493
+ constructor(currentSessionsLoader, allSessionsLoader, onSelect, onCancel, onExit, requestRender, options, currentSessionFilePath) {
439
494
  super();
440
495
  this.currentSessionsLoader = currentSessionsLoader;
441
496
  this.allSessionsLoader = allSessionsLoader;
442
497
  this.onCancel = onCancel;
443
498
  this.requestRender = requestRender;
444
499
  this.header = new SessionSelectorHeader(this.scope, this.sortMode, this.requestRender);
445
- // Add header
446
- this.addChild(new Spacer(1));
447
- this.addChild(new DynamicBorder());
448
- this.addChild(new Spacer(1));
449
- this.addChild(this.header);
450
- this.addChild(new Spacer(1));
500
+ const renameSession = options?.renameSession;
501
+ this.renameSession = renameSession;
502
+ this.canRename = !!renameSession;
503
+ this.header.setShowRenameHint(options?.showRenameHint ?? this.canRename);
451
504
  // Create session list (starts empty, will be populated after load)
452
505
  this.sessionList = new SessionList([], false, this.sortMode, currentSessionFilePath);
506
+ this.buildBaseLayout(this.sessionList);
507
+ this.renameInput.onSubmit = (value) => {
508
+ void this.confirmRename(value);
509
+ };
453
510
  // Ensure header status timeouts are cleared when leaving the selector
454
511
  const clearStatusMessage = () => this.header.setStatusMessage(null);
455
512
  this.sessionList.onSelect = (sessionPath) => {
@@ -466,6 +523,17 @@ export class SessionSelectorComponent extends Container {
466
523
  };
467
524
  this.sessionList.onToggleScope = () => this.toggleScope();
468
525
  this.sessionList.onToggleSort = () => this.toggleSortMode();
526
+ this.sessionList.onRenameSession = (sessionPath) => {
527
+ if (!renameSession)
528
+ return;
529
+ if (this.scope === "current" && this.currentLoading)
530
+ return;
531
+ if (this.scope === "all" && this.allLoading)
532
+ return;
533
+ const sessions = this.scope === "all" ? (this.allSessions ?? []) : (this.currentSessions ?? []);
534
+ const session = sessions.find((s) => s.path === sessionPath);
535
+ this.enterRenameMode(sessionPath, session?.name);
536
+ };
469
537
  // Sync list events to header
470
538
  this.sessionList.onTogglePath = (showPath) => {
471
539
  this.header.setShowPath(showPath);
@@ -494,6 +562,7 @@ export class SessionSelectorComponent extends Container {
494
562
  this.sessionList.setSessions(sessions, showCwd);
495
563
  const msg = result.method === "trash" ? "Session moved to trash" : "Session deleted";
496
564
  this.header.setStatusMessage({ type: "info", message: msg }, 2000);
565
+ await this.refreshSessionsAfterMutation();
497
566
  }
498
567
  else {
499
568
  const errorMessage = result.error ?? "Unknown error";
@@ -501,43 +570,118 @@ export class SessionSelectorComponent extends Container {
501
570
  }
502
571
  this.requestRender();
503
572
  };
504
- this.addChild(this.sessionList);
505
- // Add bottom border
506
- this.addChild(new Spacer(1));
507
- this.addChild(new DynamicBorder());
508
573
  // Start loading current sessions immediately
509
574
  this.loadCurrentSessions();
510
575
  }
511
576
  loadCurrentSessions() {
512
- this.currentLoading = true;
513
- this.header.setScope("current");
577
+ void this.loadScope("current", "initial");
578
+ }
579
+ enterRenameMode(sessionPath, currentName) {
580
+ this.mode = "rename";
581
+ this.renameTargetPath = sessionPath;
582
+ this.renameInput.setValue(currentName ?? "");
583
+ this.renameInput.focused = true;
584
+ const panel = new Container();
585
+ panel.addChild(new Text(theme.bold("Rename Session"), 1, 0));
586
+ panel.addChild(new Spacer(1));
587
+ panel.addChild(this.renameInput);
588
+ panel.addChild(new Spacer(1));
589
+ panel.addChild(new Text(theme.fg("muted", "Enter to save · Esc/Ctrl+C to cancel"), 1, 0));
590
+ this.buildBaseLayout(panel, { showHeader: false });
591
+ this.requestRender();
592
+ }
593
+ exitRenameMode() {
594
+ this.mode = "list";
595
+ this.renameTargetPath = null;
596
+ this.buildBaseLayout(this.sessionList);
597
+ this.requestRender();
598
+ }
599
+ async confirmRename(value) {
600
+ const next = value.trim();
601
+ if (!next)
602
+ return;
603
+ const target = this.renameTargetPath;
604
+ if (!target) {
605
+ this.exitRenameMode();
606
+ return;
607
+ }
608
+ // Find current name for callback
609
+ const renameSession = this.renameSession;
610
+ if (!renameSession) {
611
+ this.exitRenameMode();
612
+ return;
613
+ }
614
+ try {
615
+ await renameSession(target, next);
616
+ await this.refreshSessionsAfterMutation();
617
+ }
618
+ finally {
619
+ this.exitRenameMode();
620
+ }
621
+ }
622
+ async loadScope(scope, reason) {
623
+ const showCwd = scope === "all";
624
+ // Mark loading
625
+ if (scope === "current") {
626
+ this.currentLoading = true;
627
+ }
628
+ else {
629
+ this.allLoading = true;
630
+ }
631
+ const seq = scope === "all" ? ++this.allLoadSeq : undefined;
632
+ this.header.setScope(scope);
514
633
  this.header.setLoading(true);
515
634
  this.requestRender();
516
- this.currentSessionsLoader((loaded, total) => {
517
- if (this.scope !== "current")
635
+ const onProgress = (loaded, total) => {
636
+ if (scope !== this.scope)
637
+ return;
638
+ if (seq !== undefined && seq !== this.allLoadSeq)
518
639
  return;
519
640
  this.header.setProgress(loaded, total);
520
641
  this.requestRender();
521
- })
522
- .then((sessions) => {
523
- this.currentSessions = sessions;
524
- this.currentLoading = false;
525
- if (this.scope !== "current")
642
+ };
643
+ try {
644
+ const sessions = await (scope === "current"
645
+ ? this.currentSessionsLoader(onProgress)
646
+ : this.allSessionsLoader(onProgress));
647
+ if (scope === "current") {
648
+ this.currentSessions = sessions;
649
+ this.currentLoading = false;
650
+ }
651
+ else {
652
+ this.allSessions = sessions;
653
+ this.allLoading = false;
654
+ }
655
+ if (scope !== this.scope)
656
+ return;
657
+ if (seq !== undefined && seq !== this.allLoadSeq)
526
658
  return;
527
659
  this.header.setLoading(false);
528
- this.sessionList.setSessions(sessions, false);
660
+ this.sessionList.setSessions(sessions, showCwd);
529
661
  this.requestRender();
530
- })
531
- .catch((error) => {
532
- this.currentLoading = false;
533
- const message = error instanceof Error ? error.message : String(error);
534
- if (this.scope !== "current")
662
+ if (scope === "all" && sessions.length === 0 && (this.currentSessions?.length ?? 0) === 0) {
663
+ this.onCancel();
664
+ }
665
+ }
666
+ catch (err) {
667
+ if (scope === "current") {
668
+ this.currentLoading = false;
669
+ }
670
+ else {
671
+ this.allLoading = false;
672
+ }
673
+ if (scope !== this.scope)
535
674
  return;
675
+ if (seq !== undefined && seq !== this.allLoadSeq)
676
+ return;
677
+ const message = err instanceof Error ? err.message : String(err);
536
678
  this.header.setLoading(false);
537
679
  this.header.setStatusMessage({ type: "error", message: `Failed to load sessions: ${message}` }, 4000);
538
- this.sessionList.setSessions([], false);
680
+ if (reason === "initial") {
681
+ this.sessionList.setSessions([], showCwd);
682
+ }
539
683
  this.requestRender();
540
- });
684
+ }
541
685
  }
542
686
  toggleSortMode() {
543
687
  this.sortMode = this.sortMode === "recent" ? "relevance" : "recent";
@@ -545,6 +689,9 @@ export class SessionSelectorComponent extends Container {
545
689
  this.sessionList.setSortMode(this.sortMode);
546
690
  this.requestRender();
547
691
  }
692
+ async refreshSessionsAfterMutation() {
693
+ await this.loadScope(this.scope, "refresh");
694
+ }
548
695
  toggleScope() {
549
696
  if (this.scope === "current") {
550
697
  this.scope = "all";
@@ -555,55 +702,16 @@ export class SessionSelectorComponent extends Container {
555
702
  this.requestRender();
556
703
  return;
557
704
  }
558
- this.header.setLoading(true);
559
- this.sessionList.setSessions([], true);
560
- this.requestRender();
561
- if (this.allLoading)
562
- return;
563
- this.allLoading = true;
564
- const seq = ++this.allLoadSeq;
565
- this.allSessionsLoader((loaded, total) => {
566
- if (seq !== this.allLoadSeq)
567
- return;
568
- if (this.scope !== "all")
569
- return;
570
- this.header.setProgress(loaded, total);
571
- this.requestRender();
572
- })
573
- .then((sessions) => {
574
- this.allSessions = sessions;
575
- this.allLoading = false;
576
- if (seq !== this.allLoadSeq)
577
- return;
578
- if (this.scope !== "all")
579
- return;
580
- this.header.setLoading(false);
581
- this.sessionList.setSessions(sessions, true);
582
- this.requestRender();
583
- if (sessions.length === 0 && (this.currentSessions?.length ?? 0) === 0) {
584
- this.onCancel();
585
- }
586
- })
587
- .catch((error) => {
588
- this.allLoading = false;
589
- const message = error instanceof Error ? error.message : String(error);
590
- if (seq !== this.allLoadSeq)
591
- return;
592
- if (this.scope !== "all")
593
- return;
594
- this.header.setLoading(false);
595
- this.header.setStatusMessage({ type: "error", message: `Failed to load sessions: ${message}` }, 4000);
596
- this.sessionList.setSessions([], true);
597
- this.requestRender();
598
- });
599
- }
600
- else {
601
- this.scope = "current";
602
- this.header.setScope(this.scope);
603
- this.header.setLoading(this.currentLoading);
604
- this.sessionList.setSessions(this.currentSessions ?? [], false);
605
- this.requestRender();
705
+ if (!this.allLoading) {
706
+ void this.loadScope("all", "toggle");
707
+ }
708
+ return;
606
709
  }
710
+ this.scope = "current";
711
+ this.header.setScope(this.scope);
712
+ this.header.setLoading(this.currentLoading);
713
+ this.sessionList.setSessions(this.currentSessions ?? [], false);
714
+ this.requestRender();
607
715
  }
608
716
  getSessionList() {
609
717
  return this.sessionList;