@gajae-code/coding-agent 0.9.6 → 0.10.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 (193) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist/types/cli/classify-install-target.d.ts +0 -10
  3. package/dist/types/cli/daemon-cli.d.ts +2 -0
  4. package/dist/types/cli/initial-message.d.ts +1 -1
  5. package/dist/types/cli/malloc-env-guard.d.ts +65 -0
  6. package/dist/types/cli/malloc-env-guard.test.d.ts +1 -0
  7. package/dist/types/cli/notify-cli.d.ts +3 -1
  8. package/dist/types/cli/session-picker.d.ts +4 -3
  9. package/dist/types/cli/update-cli.d.ts +22 -3
  10. package/dist/types/cli.d.ts +4 -0
  11. package/dist/types/commands/daemon.d.ts +6 -2
  12. package/dist/types/commands/launch.d.ts +1 -0
  13. package/dist/types/commands/notify.d.ts +6 -0
  14. package/dist/types/commit/analysis/conventional.d.ts +1 -1
  15. package/dist/types/commit/analysis/summary.d.ts +1 -1
  16. package/dist/types/commit/changelog/generate.d.ts +1 -1
  17. package/dist/types/commit/changelog/index.d.ts +2 -2
  18. package/dist/types/commit/map-reduce/map-phase.d.ts +1 -1
  19. package/dist/types/commit/map-reduce/reduce-phase.d.ts +1 -1
  20. package/dist/types/config/keybindings.d.ts +5 -0
  21. package/dist/types/config/model-profile-activation.d.ts +1 -0
  22. package/dist/types/config/settings-schema.d.ts +47 -7
  23. package/dist/types/coordinator-mcp/model-preset.d.ts +45 -0
  24. package/dist/types/coordinator-mcp/server.d.ts +67 -6
  25. package/dist/types/daemon/control-types.d.ts +10 -0
  26. package/dist/types/daemon/operator-contract.d.ts +47 -0
  27. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.d.ts +4 -0
  28. package/dist/types/discovery/helpers.d.ts +1 -1
  29. package/dist/types/discovery/substitute-plugin-root.d.ts +0 -4
  30. package/dist/types/gjc-runtime/launch-tmux.d.ts +26 -7
  31. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +34 -3
  32. package/dist/types/gjc-runtime/tmux-common.d.ts +6 -0
  33. package/dist/types/gjc-runtime/tmux-owner-isolation-cli.d.ts +5 -0
  34. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +309 -0
  35. package/dist/types/gjc-runtime/tmux-sessions.d.ts +49 -17
  36. package/dist/types/internal-urls/local-protocol.d.ts +13 -9
  37. package/dist/types/main.d.ts +66 -2
  38. package/dist/types/modes/acp/acp-agent.d.ts +19 -2
  39. package/dist/types/modes/acp/acp-mode.d.ts +12 -0
  40. package/dist/types/modes/components/assistant-message.d.ts +1 -1
  41. package/dist/types/modes/components/custom-editor.d.ts +2 -0
  42. package/dist/types/modes/components/gajae-pet-widget.d.ts +59 -0
  43. package/dist/types/modes/components/hook-selector.d.ts +5 -0
  44. package/dist/types/modes/components/irc-sidebar.d.ts +32 -0
  45. package/dist/types/modes/components/pet-selector.d.ts +11 -0
  46. package/dist/types/modes/components/session-selector.d.ts +22 -9
  47. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  48. package/dist/types/modes/components/tool-execution.d.ts +1 -0
  49. package/dist/types/modes/components/user-message.d.ts +2 -1
  50. package/dist/types/modes/controllers/event-controller.d.ts +4 -0
  51. package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
  52. package/dist/types/modes/interactive-mode.d.ts +24 -2
  53. package/dist/types/modes/irc-observation-ledger.d.ts +22 -0
  54. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +8 -0
  55. package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +5 -0
  56. package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +45 -0
  57. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +42 -2
  58. package/dist/types/modes/theme/defaults/index.d.ts +568 -568
  59. package/dist/types/modes/types.d.ts +28 -2
  60. package/dist/types/modes/utils/abort-message.d.ts +1 -1
  61. package/dist/types/modes/utils/irc-message.d.ts +38 -0
  62. package/dist/types/modes/utils/ui-helpers.d.ts +13 -4
  63. package/dist/types/notifications/config.d.ts +34 -0
  64. package/dist/types/notifications/daemon-paths.d.ts +7 -0
  65. package/dist/types/notifications/index.d.ts +27 -0
  66. package/dist/types/notifications/lifecycle-control-runtime.d.ts +12 -4
  67. package/dist/types/notifications/notification-service.d.ts +144 -0
  68. package/dist/types/notifications/rate-limit-pool.d.ts +27 -9
  69. package/dist/types/notifications/telegram-daemon-control.d.ts +6 -0
  70. package/dist/types/notifications/telegram-daemon.d.ts +130 -3
  71. package/dist/types/notifications/telegram-reference.d.ts +19 -2
  72. package/dist/types/notifications/topic-registry.d.ts +24 -4
  73. package/dist/types/registry/agent-registry.d.ts +2 -0
  74. package/dist/types/rlm/index.d.ts +1 -1
  75. package/dist/types/sdk.d.ts +2 -0
  76. package/dist/types/secrets/obfuscator.d.ts +1 -1
  77. package/dist/types/session/agent-session.d.ts +26 -2
  78. package/dist/types/session/cache-economics.d.ts +79 -0
  79. package/dist/types/session/provider-safety-stop.d.ts +5 -0
  80. package/dist/types/session/session-manager.d.ts +124 -4
  81. package/dist/types/session/session-storage.d.ts +155 -13
  82. package/dist/types/setup/credential-auto-import.d.ts +2 -2
  83. package/dist/types/task/executor.d.ts +3 -0
  84. package/dist/types/task/receipt.d.ts +3 -1
  85. package/dist/types/task/types.d.ts +8 -0
  86. package/dist/types/tools/ask-answer-registry.d.ts +5 -0
  87. package/dist/types/tools/ask.d.ts +25 -1
  88. package/dist/types/tools/bash.d.ts +1 -1
  89. package/dist/types/tools/browser/tab-supervisor.d.ts +6 -0
  90. package/dist/types/tools/index.d.ts +58 -6
  91. package/dist/types/tools/telegram-send.d.ts +7 -0
  92. package/dist/types/utils/external-editor.d.ts +2 -0
  93. package/package.json +9 -9
  94. package/scripts/g004-tmux-smoke.ts +196 -36
  95. package/src/cli/daemon-cli.ts +19 -29
  96. package/src/cli/malloc-env-guard.test.ts +104 -0
  97. package/src/cli/malloc-env-guard.ts +149 -0
  98. package/src/cli/notify-cli.ts +78 -14
  99. package/src/cli/session-picker.ts +23 -40
  100. package/src/cli/update-cli.ts +56 -37
  101. package/src/cli.ts +50 -10
  102. package/src/commands/daemon.ts +19 -5
  103. package/src/commands/harness.ts +515 -47
  104. package/src/commands/launch.ts +11 -3
  105. package/src/commands/notify.ts +5 -1
  106. package/src/commands/session.ts +6 -1
  107. package/src/config/keybindings.ts +5 -0
  108. package/src/config/model-profile-activation.ts +1 -1
  109. package/src/config/model-profiles.ts +14 -14
  110. package/src/config/settings-schema.ts +41 -1
  111. package/src/coordinator-mcp/model-preset.ts +100 -0
  112. package/src/coordinator-mcp/server.ts +2653 -425
  113. package/src/daemon/control-types.ts +11 -0
  114. package/src/daemon/operator-contract.ts +121 -0
  115. package/src/deep-interview/render-middleware.ts +67 -2
  116. package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +57 -17
  117. package/src/defaults/gjc/extensions/grok-cli-vendor/src/payload/sanitize.ts +15 -2
  118. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +5 -1
  119. package/src/gjc-runtime/launch-tmux.ts +702 -135
  120. package/src/gjc-runtime/session-state-sidecar.ts +756 -135
  121. package/src/gjc-runtime/team-runtime.ts +6 -0
  122. package/src/gjc-runtime/tmux-common.ts +26 -7
  123. package/src/gjc-runtime/tmux-gc.ts +52 -9
  124. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +185 -0
  125. package/src/gjc-runtime/tmux-owner-isolation.ts +2060 -0
  126. package/src/gjc-runtime/tmux-sessions.ts +871 -68
  127. package/src/internal-urls/docs-index.generated.ts +9 -8
  128. package/src/internal-urls/local-protocol.ts +29 -10
  129. package/src/main.ts +298 -75
  130. package/src/modes/acp/acp-agent.ts +728 -98
  131. package/src/modes/acp/acp-mode.ts +29 -2
  132. package/src/modes/components/assistant-message.ts +32 -3
  133. package/src/modes/components/bash-execution.ts +43 -13
  134. package/src/modes/components/custom-editor.ts +5 -0
  135. package/src/modes/components/gajae-pet-widget.ts +360 -0
  136. package/src/modes/components/hook-selector.ts +12 -0
  137. package/src/modes/components/irc-sidebar.ts +206 -0
  138. package/src/modes/components/pet-selector.ts +55 -0
  139. package/src/modes/components/session-selector.ts +222 -221
  140. package/src/modes/components/settings-selector.ts +10 -0
  141. package/src/modes/components/tool-execution.ts +47 -6
  142. package/src/modes/components/user-message.ts +26 -9
  143. package/src/modes/controllers/command-controller.ts +28 -2
  144. package/src/modes/controllers/event-controller.ts +159 -36
  145. package/src/modes/controllers/extension-ui-controller.ts +36 -17
  146. package/src/modes/controllers/input-controller.ts +8 -2
  147. package/src/modes/controllers/selector-controller.ts +62 -15
  148. package/src/modes/interactive-mode.ts +183 -7
  149. package/src/modes/irc-observation-ledger.ts +127 -0
  150. package/src/modes/shared/agent-wire/deep-interview-gate.ts +45 -2
  151. package/src/modes/shared/agent-wire/unattended-session.ts +288 -1
  152. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +130 -20
  153. package/src/modes/types.ts +34 -2
  154. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  155. package/src/modes/utils/irc-message.ts +138 -0
  156. package/src/modes/utils/ui-helpers.ts +216 -50
  157. package/src/notifications/config.ts +44 -0
  158. package/src/notifications/daemon-paths.ts +8 -0
  159. package/src/notifications/index.ts +535 -75
  160. package/src/notifications/lifecycle-control-runtime.ts +930 -77
  161. package/src/notifications/notification-service.ts +710 -0
  162. package/src/notifications/rate-limit-pool.ts +46 -12
  163. package/src/notifications/telegram-daemon-cli.ts +2 -0
  164. package/src/notifications/telegram-daemon-control.ts +27 -2
  165. package/src/notifications/telegram-daemon.ts +635 -94
  166. package/src/notifications/telegram-reference.ts +73 -13
  167. package/src/notifications/topic-registry.ts +86 -9
  168. package/src/prompts/system/irc-peer-roster.md +1 -0
  169. package/src/prompts/system/subagent-system-prompt.md +1 -0
  170. package/src/prompts/system/system-prompt.md +1 -0
  171. package/src/prompts/tools/irc.md +1 -1
  172. package/src/registry/agent-registry.ts +3 -0
  173. package/src/sdk.ts +52 -1
  174. package/src/secrets/obfuscator.ts +4 -2
  175. package/src/session/agent-session.ts +396 -145
  176. package/src/session/cache-economics.ts +275 -0
  177. package/src/session/provider-safety-stop.ts +9 -0
  178. package/src/session/session-dump-format.ts +6 -0
  179. package/src/session/session-manager.ts +809 -39
  180. package/src/session/session-storage.ts +582 -38
  181. package/src/slash-commands/builtin-registry.ts +143 -9
  182. package/src/task/executor.ts +12 -3
  183. package/src/task/index.ts +8 -0
  184. package/src/task/receipt.ts +7 -2
  185. package/src/task/types.ts +26 -0
  186. package/src/tools/ask-answer-registry.ts +25 -0
  187. package/src/tools/ask.ts +355 -33
  188. package/src/tools/browser/tab-supervisor.ts +63 -6
  189. package/src/tools/browser.ts +8 -3
  190. package/src/tools/index.ts +81 -6
  191. package/src/tools/sqlite-reader.ts +8 -4
  192. package/src/tui/output-block.ts +18 -6
  193. package/src/utils/external-editor.ts +6 -1
@@ -25,6 +25,7 @@ import type { InteractiveModeContext } from "../../modes/types";
25
25
  import { setSessionTerminalTitle, setTerminalTitle } from "../../utils/title-generator";
26
26
  import { applyInjectedUserSubmission } from "../utils/injected-user-submission";
27
27
  import { classifyHookSelectorBellEvent, ringTerminalBell } from "../utils/terminal-bell";
28
+ import { prepareTranscriptRebuild } from "../utils/ui-helpers";
28
29
 
29
30
  const MAX_WIDGET_LINES = 10;
30
31
  const HOOK_SELECTOR_CHROME_ROWS = 7;
@@ -150,9 +151,11 @@ export class ExtensionUiController {
150
151
  getContextUsage: () => this.ctx.session.getContextUsage(),
151
152
  waitForIdle: () => this.ctx.session.agent.waitForIdle(),
152
153
  reload: async () => {
154
+ const previousSessionId = this.ctx.sessionManager.getSessionId();
153
155
  await this.ctx.session.reload();
154
- this.ctx.chatContainer.clear();
155
- this.ctx.renderInitialMessages();
156
+ const sessionIdentityChanged = previousSessionId !== this.ctx.sessionManager.getSessionId();
157
+ if (sessionIdentityChanged) this.ctx.resetIrcSidebarSession();
158
+ this.ctx.rebuildInitialMessages(sessionIdentityChanged ? "replace-identity" : "reconcile-same-transcript");
156
159
  await this.ctx.reloadTodos();
157
160
  this.ctx.showStatus("Reloaded session");
158
161
  },
@@ -171,6 +174,8 @@ export class ExtensionUiController {
171
174
  if (!success) {
172
175
  return { cancelled: true };
173
176
  }
177
+ this.ctx.resetIrcSidebarSession();
178
+
174
179
  setSessionTerminalTitle(this.ctx.sessionManager.getSessionName(), this.ctx.sessionManager.getCwd());
175
180
 
176
181
  // Call setup callback if provided
@@ -185,6 +190,7 @@ export class ExtensionUiController {
185
190
  this.ctx.ui.requestRender();
186
191
 
187
192
  // Clear UI state
193
+ prepareTranscriptRebuild(this.ctx.ui, "replace-identity");
188
194
  this.ctx.chatContainer.clear();
189
195
  this.ctx.pendingMessagesContainer.clear();
190
196
  this.ctx.compactionQueuedMessages = [];
@@ -205,10 +211,10 @@ export class ExtensionUiController {
205
211
  if (result.cancelled) {
206
212
  return { cancelled: true };
207
213
  }
214
+ this.ctx.resetIrcSidebarSession();
208
215
 
209
216
  // Update UI
210
- this.ctx.chatContainer.clear();
211
- this.ctx.renderInitialMessages();
217
+ this.ctx.rebuildInitialMessages("replace-identity");
212
218
  await this.ctx.reloadTodos();
213
219
  this.ctx.editor.setText(result.selectedText);
214
220
  this.ctx.showStatus("Branched to new session");
@@ -222,8 +228,7 @@ export class ExtensionUiController {
222
228
  }
223
229
 
224
230
  // Update UI
225
- this.ctx.chatContainer.clear();
226
- this.ctx.renderInitialMessages();
231
+ this.ctx.rebuildInitialMessages("reconcile-same-transcript");
227
232
  await this.ctx.reloadTodos();
228
233
  if (result.editorText && !this.ctx.editor.getText().trim()) {
229
234
  this.ctx.editor.setText(result.editorText);
@@ -234,14 +239,20 @@ export class ExtensionUiController {
234
239
  },
235
240
  compact: async instructionsOrOptions => this.#handleInteractiveCompact(instructionsOrOptions),
236
241
  switchSession: async sessionPath => {
242
+ const previousSessionId = this.ctx.sessionManager.getSessionId();
243
+
237
244
  this.clearHookWidgets();
238
245
  const result = await this.ctx.session.switchSession(sessionPath);
239
246
  if (!result) {
240
247
  return { cancelled: true };
241
248
  }
249
+ const switchingToDifferentSession = previousSessionId !== this.ctx.sessionManager.getSessionId();
250
+ if (switchingToDifferentSession) this.ctx.resetIrcSidebarSession();
251
+
242
252
  setSessionTerminalTitle(this.ctx.sessionManager.getSessionName(), this.ctx.sessionManager.getCwd());
243
- this.ctx.chatContainer.clear();
244
- this.ctx.renderInitialMessages();
253
+ this.ctx.rebuildInitialMessages(
254
+ switchingToDifferentSession ? "replace-identity" : "reconcile-same-transcript",
255
+ );
245
256
  await this.ctx.reloadTodos();
246
257
  return { cancelled: false };
247
258
  },
@@ -392,9 +403,11 @@ export class ExtensionUiController {
392
403
  if (this.ctx.isBackgrounded) {
393
404
  return;
394
405
  }
406
+ const previousSessionId = this.ctx.sessionManager.getSessionId();
395
407
  await this.ctx.session.reload();
396
- this.ctx.chatContainer.clear();
397
- this.ctx.renderInitialMessages();
408
+ const sessionIdentityChanged = previousSessionId !== this.ctx.sessionManager.getSessionId();
409
+ if (sessionIdentityChanged) this.ctx.resetIrcSidebarSession();
410
+ this.ctx.rebuildInitialMessages(sessionIdentityChanged ? "replace-identity" : "reconcile-same-transcript");
398
411
  await this.ctx.reloadTodos();
399
412
  this.ctx.showStatus("Reloaded session");
400
413
  },
@@ -416,6 +429,7 @@ export class ExtensionUiController {
416
429
  if (!success) {
417
430
  return { cancelled: true };
418
431
  }
432
+ this.ctx.resetIrcSidebarSession();
419
433
 
420
434
  // Call setup callback if provided
421
435
  if (options?.setup) {
@@ -423,6 +437,7 @@ export class ExtensionUiController {
423
437
  }
424
438
 
425
439
  // Clear UI state
440
+ prepareTranscriptRebuild(this.ctx.ui, "replace-identity");
426
441
  this.ctx.chatContainer.clear();
427
442
  this.ctx.pendingMessagesContainer.clear();
428
443
  this.ctx.compactionQueuedMessages = [];
@@ -446,10 +461,10 @@ export class ExtensionUiController {
446
461
  if (result.cancelled) {
447
462
  return { cancelled: true };
448
463
  }
464
+ this.ctx.resetIrcSidebarSession();
449
465
 
450
466
  // Update UI
451
- this.ctx.chatContainer.clear();
452
- this.ctx.renderInitialMessages();
467
+ this.ctx.rebuildInitialMessages("replace-identity");
453
468
  await this.ctx.reloadTodos();
454
469
  this.ctx.editor.setText(result.selectedText);
455
470
  this.ctx.showStatus("Branched to new session");
@@ -466,8 +481,7 @@ export class ExtensionUiController {
466
481
  }
467
482
 
468
483
  // Update UI
469
- this.ctx.chatContainer.clear();
470
- this.ctx.renderInitialMessages();
484
+ this.ctx.rebuildInitialMessages("reconcile-same-transcript");
471
485
  await this.ctx.reloadTodos();
472
486
  if (result.editorText && !this.ctx.editor.getText().trim()) {
473
487
  this.ctx.editor.setText(result.editorText);
@@ -481,13 +495,18 @@ export class ExtensionUiController {
481
495
  if (this.ctx.isBackgrounded) {
482
496
  return { cancelled: true };
483
497
  }
498
+ const previousSessionId = this.ctx.sessionManager.getSessionId();
499
+
484
500
  this.clearHookWidgets();
485
501
  const result = await this.ctx.session.switchSession(sessionPath);
486
502
  if (!result) {
487
503
  return { cancelled: true };
488
504
  }
489
- this.ctx.chatContainer.clear();
490
- this.ctx.renderInitialMessages();
505
+ const switchingToDifferentSession = previousSessionId !== this.ctx.sessionManager.getSessionId();
506
+ if (switchingToDifferentSession) this.ctx.resetIrcSidebarSession();
507
+ this.ctx.rebuildInitialMessages(
508
+ switchingToDifferentSession ? "replace-identity" : "reconcile-same-transcript",
509
+ );
491
510
  await this.ctx.reloadTodos();
492
511
  return { cancelled: false };
493
512
  },
@@ -970,7 +989,7 @@ export class ExtensionUiController {
970
989
  // For non-streaming cases with display=true, update UI
971
990
  // (streaming cases update via message_end event)
972
991
  if (!this.ctx.isBackgrounded && !wasStreaming && shouldDisplay) {
973
- this.ctx.rebuildChatFromMessages();
992
+ this.ctx.rebuildChatFromMessages("reconcile-same-transcript");
974
993
  }
975
994
  }
976
995
 
@@ -331,6 +331,13 @@ export class InputController {
331
331
  // Wire up extension shortcuts
332
332
  this.registerExtensionShortcuts();
333
333
 
334
+ for (const key of this.ctx.keybindings.getKeys("app.irc.sidebar.toggle")) {
335
+ this.ctx.editor.setCustomKeyHandler(key, () => {
336
+ this.ctx.toggleIrcSidebar();
337
+ return true;
338
+ });
339
+ }
340
+
334
341
  const planModeKeys = this.ctx.keybindings.getKeys("app.plan.toggle");
335
342
  for (const key of planModeKeys) {
336
343
  this.ctx.editor.setCustomKeyHandler(key, () => void this.ctx.handlePlanModeCommand());
@@ -1344,8 +1351,7 @@ export class InputController {
1344
1351
  if (this.ctx.streamingComponent) {
1345
1352
  this.ctx.chatContainer.detachChild(this.ctx.streamingComponent);
1346
1353
  }
1347
- this.ctx.chatContainer.clear();
1348
- this.ctx.rebuildChatFromMessages();
1354
+ this.ctx.rebuildChatFromMessages("reconcile-same-transcript");
1349
1355
 
1350
1356
  // If streaming, re-add the streaming component with updated visibility and re-render
1351
1357
  if (this.ctx.streamingComponent && this.ctx.streamingMessage) {
@@ -2,7 +2,7 @@ import { ThinkingLevel } from "@gajae-code/agent-core";
2
2
  import { getOAuthProviders } from "@gajae-code/ai/utils/oauth";
3
3
  import type { OAuthProvider } from "@gajae-code/ai/utils/oauth/types";
4
4
  import type { Component, OverlayHandle } from "@gajae-code/tui";
5
- import { Input, Loader, Spacer, Text } from "@gajae-code/tui";
5
+ import { Input, isPetMode, Loader, Spacer, Text } from "@gajae-code/tui";
6
6
  import { getAgentDbPath, getProjectDir } from "@gajae-code/utils";
7
7
  import {
8
8
  activateModelProfile,
@@ -74,10 +74,12 @@ import {
74
74
  } from "../components/custom-model-preset-wizard";
75
75
  import { CustomProviderWizardComponent, type CustomProviderWizardSubmit } from "../components/custom-provider-wizard";
76
76
  import { ExtensionDashboard } from "../components/extensions";
77
+ import type { PetMode } from "../components/gajae-pet-widget";
77
78
  import { HistorySearchComponent } from "../components/history-search";
78
79
  import { JobsOverlayComponent } from "../components/jobs-overlay";
79
80
  import { ModelSelectorComponent } from "../components/model-selector";
80
81
  import { OAuthSelectorComponent } from "../components/oauth-selector";
82
+ import { PetSelectorComponent } from "../components/pet-selector";
81
83
  import { PluginSelectorComponent } from "../components/plugin-selector";
82
84
  import {
83
85
  type ProviderOnboardingAction,
@@ -158,9 +160,15 @@ export class SelectorController {
158
160
  */
159
161
  showSelector(create: (done: () => void) => { component: Component; focus: Component }): void {
160
162
  const done = () => {
161
- this.ctx.editorContainer.clear();
162
- this.ctx.editorContainer.addChild(this.ctx.editor);
163
- this.ctx.ui.setFocus(this.ctx.editor);
163
+ // Prefer the pet-aware composer restore (InteractiveMode.restoreComposer); fall back
164
+ // to a plain editor swap for contexts that predate it (e.g. lightweight test doubles).
165
+ if (typeof this.ctx.restoreComposer === "function") {
166
+ this.ctx.restoreComposer();
167
+ } else {
168
+ this.ctx.editorContainer.clear();
169
+ this.ctx.editorContainer.addChild(this.ctx.editor);
170
+ this.ctx.ui.setFocus(this.ctx.editor);
171
+ }
164
172
  };
165
173
  const { component, focus } = create(done);
166
174
  this.ctx.editorContainer.clear();
@@ -503,6 +511,9 @@ export class SelectorController {
503
511
  this.#refreshThemeUi();
504
512
  });
505
513
  },
514
+ onPetPreview: mode => {
515
+ this.ctx.previewPetMode(mode as PetMode);
516
+ },
506
517
  onStatusLinePreview: previewSettings => {
507
518
  // Update status line with preview settings
508
519
  this.ctx.statusLine.updateSettings({
@@ -577,6 +588,29 @@ export class SelectorController {
577
588
  });
578
589
  }
579
590
 
591
+ showPetSelector(): void {
592
+ const stored = settings.get("pet.mode");
593
+ const initial: PetMode = isPetMode(stored) ? stored : "off";
594
+ this.showSelector(done => {
595
+ // Live-preview via previewMode (no editor re-mount, so the overlay stays);
596
+ // Enter commits + persists, Esc restores the initial skin.
597
+ const selector = new PetSelectorComponent(
598
+ initial,
599
+ mode => {
600
+ this.ctx.setPetMode(mode);
601
+ done();
602
+ },
603
+ () => {
604
+ this.ctx.previewPetMode(initial);
605
+ done();
606
+ },
607
+ mode => {
608
+ this.ctx.previewPetMode(mode);
609
+ },
610
+ );
611
+ return { component: selector, focus: selector.getSelectList() };
612
+ });
613
+ }
580
614
  showHistorySearch(): void {
581
615
  const historyStorage = this.ctx.historyStorage;
582
616
  if (!historyStorage) return;
@@ -715,8 +749,7 @@ export class SelectorController {
715
749
  child.setHideThinkingBlock(value as boolean);
716
750
  }
717
751
  }
718
- this.ctx.chatContainer.clear();
719
- this.ctx.rebuildChatFromMessages();
752
+ this.ctx.rebuildChatFromMessages("reconcile-same-transcript");
720
753
  break;
721
754
  case "theme": {
722
755
  setTheme(value as string, true).then(result => {
@@ -729,6 +762,12 @@ export class SelectorController {
729
762
  });
730
763
  break;
731
764
  }
765
+ case "pet.mode":
766
+ // The settings submenu already persisted the value; apply it to the live
767
+ // widget via previewMode (the settings overlay is still open, so a full
768
+ // re-mount would tear it down — restoreComposer re-mounts on close).
769
+ this.ctx.previewPetMode(value as PetMode);
770
+ break;
732
771
  case "symbolPreset": {
733
772
  setSymbolPreset(value as "unicode" | "nerd" | "ascii").then(() => {
734
773
  this.ctx.statusLine.invalidate();
@@ -800,6 +839,12 @@ export class SelectorController {
800
839
  this.ctx.ui.requestRender();
801
840
  break;
802
841
  }
842
+ case "irc.enabled":
843
+ case "irc.sidebar.enabled":
844
+ this.ctx.applyIrcSidebarAvailability(
845
+ this.ctx.settings.get("irc.enabled") === true && this.ctx.settings.get("irc.sidebar.enabled") === true,
846
+ );
847
+ break;
803
848
 
804
849
  // Provider settings - update runtime preferences
805
850
  case "providers.webSearch":
@@ -1173,9 +1218,9 @@ export class SelectorController {
1173
1218
  this.ctx.ui.requestRender();
1174
1219
  return;
1175
1220
  }
1221
+ this.ctx.resetIrcSidebarSession();
1176
1222
 
1177
- this.ctx.chatContainer.clear();
1178
- this.ctx.renderInitialMessages();
1223
+ this.ctx.rebuildInitialMessages("replace-identity");
1179
1224
  this.ctx.editor.setText(result.selectedText);
1180
1225
  done();
1181
1226
  this.ctx.showStatus("Branched to new session");
@@ -1285,8 +1330,7 @@ export class SelectorController {
1285
1330
  }
1286
1331
 
1287
1332
  // Update UI — pass the context built by navigateTree to skip a second O(N) walk.
1288
- this.ctx.chatContainer.clear();
1289
- this.ctx.renderInitialMessages(result.sessionContext);
1333
+ this.ctx.rebuildInitialMessages("reconcile-same-transcript", result.sessionContext);
1290
1334
  await this.ctx.reloadTodos();
1291
1335
  if (result.editorText && !this.ctx.editor.getText().trim()) {
1292
1336
  this.ctx.editor.setText(result.editorText);
@@ -1387,6 +1431,8 @@ export class SelectorController {
1387
1431
  if (!detached) {
1388
1432
  return false;
1389
1433
  }
1434
+ this.ctx.resetIrcSidebarSession();
1435
+
1390
1436
  this.#refreshSessionTerminalTitle();
1391
1437
 
1392
1438
  this.#clearTransientSessionUi();
@@ -1394,23 +1440,24 @@ export class SelectorController {
1394
1440
  this.ctx.statusLine.setSessionStartTime(Date.now());
1395
1441
  this.ctx.updateEditorTopBorder();
1396
1442
  this.ctx.updateEditorBorderColor();
1397
- this.ctx.renderInitialMessages();
1443
+ this.ctx.rebuildInitialMessages("replace-identity");
1398
1444
  await this.ctx.reloadTodos();
1399
1445
  this.ctx.ui.requestRender();
1400
1446
  return true;
1401
1447
  }
1402
1448
 
1403
1449
  async handleResumeSession(sessionPath: string): Promise<void> {
1450
+ const previousSessionId = this.ctx.sessionManager.getSessionId();
1404
1451
  this.#clearTransientSessionUi();
1405
1452
 
1406
1453
  // Switch session via AgentSession (emits hook and tool session events)
1407
- await this.ctx.session.switchSession(sessionPath);
1454
+ if (!(await this.ctx.session.switchSession(sessionPath))) return;
1455
+ const switchingToDifferentSession = previousSessionId !== this.ctx.sessionManager.getSessionId();
1456
+ if (switchingToDifferentSession) this.ctx.resetIrcSidebarSession();
1408
1457
  this.#refreshSessionTerminalTitle();
1409
1458
  this.ctx.updateEditorBorderColor();
1410
1459
 
1411
- // Clear and re-render the chat
1412
- this.ctx.chatContainer.clear();
1413
- this.ctx.renderInitialMessages();
1460
+ this.ctx.rebuildInitialMessages(switchingToDifferentSession ? "replace-identity" : "reconcile-same-transcript");
1414
1461
  await this.ctx.reloadTodos();
1415
1462
  this.ctx.showStatus("Resumed session");
1416
1463
  }