@antelopejs/dms-ai 0.9.0 → 0.9.1

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 (208) hide show
  1. package/README.md +2 -1
  2. package/dist/lifecycle/skill-sources.js +19 -146
  3. package/dist/lifecycle/skill-sources.js.map +1 -1
  4. package/dist/pages/settings/availability.js +8 -1
  5. package/dist/pages/settings/availability.js.map +1 -1
  6. package/dist/pages/settings/options.d.ts +2 -1
  7. package/dist/pages/settings/options.js +29 -8
  8. package/dist/pages/settings/options.js.map +1 -1
  9. package/dist/pages/settings/sections.js +15 -4
  10. package/dist/pages/settings/sections.js.map +1 -1
  11. package/dist/routes/builder.d.ts +3 -3
  12. package/dist/routes/builder.js +7 -2
  13. package/dist/routes/builder.js.map +1 -1
  14. package/dist/routes/settings.js +1 -1
  15. package/dist/routes/settings.js.map +1 -1
  16. package/dist/sidecar/settings-values.d.ts +3 -0
  17. package/dist/sidecar/settings-values.js +33 -1
  18. package/dist/sidecar/settings-values.js.map +1 -1
  19. package/dist/vocabulary/enumerations.js +2 -0
  20. package/dist/vocabulary/enumerations.js.map +1 -1
  21. package/frontend-vue/app/chat/components/ChatView.vue +16 -49
  22. package/frontend-vue/app/chat/components/Composer.vue +23 -39
  23. package/frontend-vue/app/chat/components/NoticeCard.vue +9 -0
  24. package/frontend-vue/app/chat/components/ProviderNotice.vue +14 -3
  25. package/frontend-vue/app/chat/composables/conversation-events.ts +0 -3
  26. package/frontend-vue/app/chat/composables/useConversationMode.ts +8 -11
  27. package/frontend-vue/app/chat/composables/useFollowScroll.ts +77 -0
  28. package/frontend-vue/app/chat/constants/modes.ts +15 -0
  29. package/frontend-vue/app/chat/constants/protocol.ts +0 -1
  30. package/frontend-vue/app/chat/constants/settings.ts +1 -1
  31. package/frontend-vue/app/chat/types/protocol.ts +11 -9
  32. package/frontend-vue/app/chat/types/settings.ts +7 -2
  33. package/frontend-vue/app/chat/utils/permission-view.ts +1 -0
  34. package/frontend-vue/app/views/constants.ts +2 -0
  35. package/frontend-vue/app/views/types.ts +3 -1
  36. package/frontend-vue/i18n/locales/dms-ai-pages-en-GB.json +21 -11
  37. package/frontend-vue/i18n/locales/dms-ai-pages-fr-FR.json +21 -11
  38. package/frontend-vue/i18n/locales/dms-ai-panel-en-GB.json +11 -21
  39. package/frontend-vue/i18n/locales/dms-ai-panel-fr-FR.json +11 -21
  40. package/frontend-vue/i18n/locales/dms-ai-views-en-GB.json +2 -0
  41. package/frontend-vue/i18n/locales/dms-ai-views-fr-FR.json +2 -0
  42. package/frontend-vue/tests/chat/chat-scroll.test.ts +251 -0
  43. package/frontend-vue/tests/chat/conversation-events.test.ts +1 -3
  44. package/frontend-vue/tests/chat/provider-notice.test.ts +33 -0
  45. package/frontend-vue/tests/chat-panel.test.ts +2 -4
  46. package/frontend-vue/tests/panel-modes.test.ts +146 -0
  47. package/frontend-vue/tests/panel-questions.test.ts +1 -19
  48. package/frontend-vue/tests/panel-states.test.ts +3 -15
  49. package/frontend-vue/tests/support/fake-layout.ts +119 -0
  50. package/frontend-vue/tests/support/panel-harness.ts +14 -6
  51. package/frontend-vue/tests/support/setup-layout.ts +3 -0
  52. package/frontend-vue/tests/support/stubs.ts +2 -0
  53. package/frontend-vue/vitest.config.ts +1 -0
  54. package/package.json +4 -3
  55. package/sidecar/dist/agent/always-ask-files.d.ts +2 -0
  56. package/sidecar/dist/agent/always-ask-files.js +34 -0
  57. package/sidecar/dist/agent/always-ask-files.js.map +1 -0
  58. package/sidecar/dist/agent/conversation-modes.d.ts +3 -12
  59. package/sidecar/dist/agent/conversation-modes.js +16 -69
  60. package/sidecar/dist/agent/conversation-modes.js.map +1 -1
  61. package/sidecar/dist/agent/effective-mode.d.ts +2 -5
  62. package/sidecar/dist/agent/effective-mode.js +1 -15
  63. package/sidecar/dist/agent/effective-mode.js.map +1 -1
  64. package/sidecar/dist/agent/permission-bus.d.ts +1 -2
  65. package/sidecar/dist/agent/permission-bus.js +1 -3
  66. package/sidecar/dist/agent/permission-bus.js.map +1 -1
  67. package/sidecar/dist/agent/permission-preview.d.ts +6 -0
  68. package/sidecar/dist/agent/permission-preview.js +35 -10
  69. package/sidecar/dist/agent/permission-preview.js.map +1 -1
  70. package/sidecar/dist/agent/permission-rules.js +2 -2
  71. package/sidecar/dist/agent/permission-rules.js.map +1 -1
  72. package/sidecar/dist/agent/project-secrets.d.ts +7 -0
  73. package/sidecar/dist/agent/project-secrets.js +61 -0
  74. package/sidecar/dist/agent/project-secrets.js.map +1 -0
  75. package/sidecar/dist/agent/provider.d.ts +5 -4
  76. package/sidecar/dist/agent/runner.d.ts +3 -2
  77. package/sidecar/dist/agent/runner.js +9 -3
  78. package/sidecar/dist/agent/runner.js.map +1 -1
  79. package/sidecar/dist/agent/session.d.ts +3 -3
  80. package/sidecar/dist/agent/session.js.map +1 -1
  81. package/sidecar/dist/agent/tool-kinds.d.ts +7 -1
  82. package/sidecar/dist/agent/tool-kinds.js +24 -5
  83. package/sidecar/dist/agent/tool-kinds.js.map +1 -1
  84. package/sidecar/dist/audit/activity-query.js +2 -0
  85. package/sidecar/dist/audit/activity-query.js.map +1 -1
  86. package/sidecar/dist/audit/activity.js +1 -0
  87. package/sidecar/dist/audit/activity.js.map +1 -1
  88. package/sidecar/dist/builder/builder-client.js +13 -1
  89. package/sidecar/dist/builder/builder-client.js.map +1 -1
  90. package/sidecar/dist/constants/audit.d.ts +1 -3
  91. package/sidecar/dist/constants/audit.js +2 -1
  92. package/sidecar/dist/constants/audit.js.map +1 -1
  93. package/sidecar/dist/constants/builder.d.ts +3 -0
  94. package/sidecar/dist/constants/builder.js +3 -0
  95. package/sidecar/dist/constants/builder.js.map +1 -1
  96. package/sidecar/dist/constants/claude.d.ts +6 -0
  97. package/sidecar/dist/constants/claude.js +6 -0
  98. package/sidecar/dist/constants/claude.js.map +1 -1
  99. package/sidecar/dist/constants/codex.d.ts +4 -0
  100. package/sidecar/dist/constants/codex.js +4 -0
  101. package/sidecar/dist/constants/codex.js.map +1 -1
  102. package/sidecar/dist/constants/permissions.d.ts +2 -0
  103. package/sidecar/dist/constants/permissions.js +23 -0
  104. package/sidecar/dist/constants/permissions.js.map +1 -1
  105. package/sidecar/dist/constants/project-secrets.d.ts +5 -0
  106. package/sidecar/dist/constants/project-secrets.js +20 -0
  107. package/sidecar/dist/constants/project-secrets.js.map +1 -0
  108. package/sidecar/dist/constants/providers.d.ts +2 -0
  109. package/sidecar/dist/constants/providers.js +1 -0
  110. package/sidecar/dist/constants/providers.js.map +1 -1
  111. package/sidecar/dist/constants/settings.d.ts +1 -2
  112. package/sidecar/dist/constants/settings.js +2 -9
  113. package/sidecar/dist/constants/settings.js.map +1 -1
  114. package/sidecar/dist/constants/tool-kinds.d.ts +67 -2
  115. package/sidecar/dist/constants/tool-kinds.js +111 -34
  116. package/sidecar/dist/constants/tool-kinds.js.map +1 -1
  117. package/sidecar/dist/mcp/tools/builder-gate.js +3 -1
  118. package/sidecar/dist/mcp/tools/builder-gate.js.map +1 -1
  119. package/sidecar/dist/mcp/tools/builder-project.js +65 -3
  120. package/sidecar/dist/mcp/tools/builder-project.js.map +1 -1
  121. package/sidecar/dist/protocol/events.d.ts +97 -63
  122. package/sidecar/dist/protocol/events.js +14 -8
  123. package/sidecar/dist/protocol/events.js.map +1 -1
  124. package/sidecar/dist/protocol/messages.d.ts +54 -49
  125. package/sidecar/dist/protocol/messages.js +8 -13
  126. package/sidecar/dist/protocol/messages.js.map +1 -1
  127. package/sidecar/dist/providers/claude/config.d.ts +3 -3
  128. package/sidecar/dist/providers/claude/config.js +4 -2
  129. package/sidecar/dist/providers/claude/config.js.map +1 -1
  130. package/sidecar/dist/providers/claude/provider.js +20 -26
  131. package/sidecar/dist/providers/claude/provider.js.map +1 -1
  132. package/sidecar/dist/providers/claude/safe-mode.d.ts +5 -5
  133. package/sidecar/dist/providers/claude/safe-mode.js +49 -30
  134. package/sidecar/dist/providers/claude/safe-mode.js.map +1 -1
  135. package/sidecar/dist/providers/claude/secret-files.d.ts +2 -0
  136. package/sidecar/dist/providers/claude/secret-files.js +50 -0
  137. package/sidecar/dist/providers/claude/secret-files.js.map +1 -0
  138. package/sidecar/dist/providers/claude/session-stream.d.ts +4 -9
  139. package/sidecar/dist/providers/claude/session-stream.js +22 -0
  140. package/sidecar/dist/providers/claude/session-stream.js.map +1 -1
  141. package/sidecar/dist/providers/claude/tool-guard.d.ts +14 -0
  142. package/sidecar/dist/providers/claude/tool-guard.js +112 -0
  143. package/sidecar/dist/providers/claude/tool-guard.js.map +1 -0
  144. package/sidecar/dist/providers/codex/adapter.d.ts +10 -0
  145. package/sidecar/dist/providers/codex/adapter.js +20 -5
  146. package/sidecar/dist/providers/codex/adapter.js.map +1 -1
  147. package/sidecar/dist/providers/codex/config.d.ts +6 -17
  148. package/sidecar/dist/providers/codex/config.js +9 -56
  149. package/sidecar/dist/providers/codex/config.js.map +1 -1
  150. package/sidecar/dist/providers/codex/mcp-elicitation.d.ts +21 -0
  151. package/sidecar/dist/providers/codex/mcp-elicitation.js +66 -0
  152. package/sidecar/dist/providers/codex/mcp-elicitation.js.map +1 -0
  153. package/sidecar/dist/providers/codex/module.js +1 -1
  154. package/sidecar/dist/providers/codex/module.js.map +1 -1
  155. package/sidecar/dist/providers/codex/patch-directories.d.ts +6 -0
  156. package/sidecar/dist/providers/codex/patch-directories.js +53 -0
  157. package/sidecar/dist/providers/codex/patch-directories.js.map +1 -0
  158. package/sidecar/dist/providers/codex/permission-profiles.d.ts +11 -0
  159. package/sidecar/dist/providers/codex/permission-profiles.js +37 -0
  160. package/sidecar/dist/providers/codex/permission-profiles.js.map +1 -0
  161. package/sidecar/dist/providers/codex/permissions.d.ts +13 -4
  162. package/sidecar/dist/providers/codex/permissions.js +28 -2
  163. package/sidecar/dist/providers/codex/permissions.js.map +1 -1
  164. package/sidecar/dist/providers/codex/process.d.ts +1 -0
  165. package/sidecar/dist/providers/codex/process.js +3 -1
  166. package/sidecar/dist/providers/codex/process.js.map +1 -1
  167. package/sidecar/dist/providers/codex/protocol/common.d.ts +16 -4
  168. package/sidecar/dist/providers/codex/protocol/index.d.ts +2 -2
  169. package/sidecar/dist/providers/codex/protocol/index.js +1 -1
  170. package/sidecar/dist/providers/codex/protocol/index.js.map +1 -1
  171. package/sidecar/dist/providers/codex/protocol/v2.d.ts +23 -32
  172. package/sidecar/dist/providers/codex/provider.js +13 -12
  173. package/sidecar/dist/providers/codex/provider.js.map +1 -1
  174. package/sidecar/dist/providers/codex/secret-file-globs.d.ts +2 -0
  175. package/sidecar/dist/providers/codex/secret-file-globs.js +45 -0
  176. package/sidecar/dist/providers/codex/secret-file-globs.js.map +1 -0
  177. package/sidecar/dist/providers/types.d.ts +3 -1
  178. package/sidecar/dist/providers/types.js +2 -2
  179. package/sidecar/dist/providers/types.js.map +1 -1
  180. package/sidecar/dist/server/http-routes.js +1 -1
  181. package/sidecar/dist/server/http-routes.js.map +1 -1
  182. package/sidecar/dist/server/permission-events.d.ts +1 -1
  183. package/sidecar/dist/server/permission-events.js +5 -8
  184. package/sidecar/dist/server/permission-events.js.map +1 -1
  185. package/sidecar/dist/server/routing.js +0 -5
  186. package/sidecar/dist/server/routing.js.map +1 -1
  187. package/sidecar/dist/server/tool-calls.d.ts +0 -2
  188. package/sidecar/dist/server/tool-calls.js +7 -8
  189. package/sidecar/dist/server/tool-calls.js.map +1 -1
  190. package/sidecar/dist/server/turns.js +9 -1
  191. package/sidecar/dist/server/turns.js.map +1 -1
  192. package/sidecar/dist/server/ws.js +4 -15
  193. package/sidecar/dist/server/ws.js.map +1 -1
  194. package/sidecar/dist/skills/skill-usage.js +2 -2
  195. package/sidecar/dist/skills/skill-usage.js.map +1 -1
  196. package/sidecar/dist/state/settings-store.d.ts +13 -5
  197. package/sidecar/dist/state/settings-store.js +33 -8
  198. package/sidecar/dist/state/settings-store.js.map +1 -1
  199. package/sidecar/dist/state/settings-types.d.ts +15 -2
  200. package/sidecar/dist/state/settings-types.js +14 -1
  201. package/sidecar/dist/state/settings-types.js.map +1 -1
  202. package/sidecar/dist/state/types.d.ts +7 -6
  203. package/sidecar/dist/state/types.js.map +1 -1
  204. package/sidecar/package.json +1 -1
  205. package/skills/dms-ai/SKILL.md +2 -1
  206. package/skills/dms-external-database/SKILL.md +5 -2
  207. package/frontend-vue/app/chat/components/FullAutoBanner.vue +0 -76
  208. package/frontend-vue/app/chat/components/FullAutoDialog.vue +0 -103
@@ -26,6 +26,7 @@ import {
26
26
  } from "../composables/useConversationId";
27
27
  import { useConversationList } from "../composables/useConversationList";
28
28
  import { useConversationMode } from "../composables/useConversationMode";
29
+ import { useFollowScroll } from "../composables/useFollowScroll";
29
30
  import { usePermissionQueue } from "../composables/usePermissionQueue";
30
31
  import { useQuestionQueue } from "../composables/useQuestionQueue";
31
32
  import { useQueueActions } from "../composables/useQueueActions";
@@ -55,7 +56,6 @@ import ChatEmpty from "./ChatEmpty.vue";
55
56
  import Composer from "./Composer.vue";
56
57
  import ConnectionBanner from "./ConnectionBanner.vue";
57
58
  import ConversationDrawer from "./ConversationDrawer.vue";
58
- import FullAutoBanner from "./FullAutoBanner.vue";
59
59
  import MessageList from "./MessageList.vue";
60
60
  import PanelHeader, { type PanelStatusKind } from "./PanelHeader.vue";
61
61
  import QuestionDock from "./QuestionDock.vue";
@@ -94,7 +94,6 @@ const { t } = useChatI18n();
94
94
 
95
95
  const HISTORY_KEY = "j";
96
96
  const ESCAPE_KEY = "Escape";
97
- const SCROLL_STICKY_THRESHOLD_PX = 80;
98
97
  const NAVIGATION_TARGETS: Record<string, string> = {
99
98
  changes: CHANGES_PAGE_PATH,
100
99
  activity: ACTIVITY_PAGE_PATH,
@@ -145,6 +144,7 @@ const nowMs = useRunClock(computed(() => true));
145
144
  const composerRef = ref<FocusableComposer | null>(null);
146
145
  const approvalDockRef = ref<FocusableDock | null>(null);
147
146
  const scrollEl = ref<HTMLElement | null>(null);
147
+ const { follow } = useFollowScroll(scrollEl);
148
148
 
149
149
  const isConnected = channel.isConnected;
150
150
  const isRunning = conversation.isRunning;
@@ -216,19 +216,10 @@ const isEmpty = computed(
216
216
  () => conversation.messages.value.length === 0 && !isRunning.value,
217
217
  );
218
218
 
219
- // Sent on the tab's stream itself: the chat's own channel is already closed
220
- // when the panel unmounts it.
221
- function leave(conversationId: string): void {
222
- props.transport.send({
223
- type: CLIENT_MESSAGE_TYPES.LEAVE_CONVERSATION,
224
- conversationId,
225
- });
226
- }
227
-
228
219
  function switchConversation(id: string): void {
220
+ follow();
229
221
  isDrawerOpen.value = false;
230
222
  if (id === activeId.value) return;
231
- leave(activeId.value);
232
223
  activeId.value = id;
233
224
  persistConversationId(id);
234
225
  conversation.reset();
@@ -265,12 +256,19 @@ function reviewChangeSet(changeSetId: string): void {
265
256
  navigate(`${CHANGES_PAGE_PATH}?${query.toString()}`);
266
257
  }
267
258
 
259
+ function sendUserMessage(
260
+ ...args: Parameters<typeof conversation.sendUserMessage>
261
+ ): void {
262
+ follow();
263
+ conversation.sendUserMessage(...args);
264
+ }
265
+
268
266
  function submit(
269
267
  content: string,
270
268
  attachments: PendingAttachment[],
271
269
  includePage: boolean,
272
270
  ): void {
273
- conversation.sendUserMessage(
271
+ sendUserMessage(
274
272
  content,
275
273
  attachments,
276
274
  includePage ? {} : { includePageContext: false },
@@ -294,9 +292,7 @@ function stopAutoFix(): void {
294
292
  }
295
293
 
296
294
  function askAgain(summary: string): void {
297
- conversation.sendUserMessage(
298
- t("dms_ai.panel.approvals.ask_again_message", { summary }),
299
- );
295
+ sendUserMessage(t("dms_ai.panel.approvals.ask_again_message", { summary }));
300
296
  }
301
297
 
302
298
  function askAgainExpired(request: ExpiredRequest): void {
@@ -313,7 +309,7 @@ function runPlan(): void {
313
309
  permissions.respond(planRequest.requestId, { decision: "allow_once" });
314
310
  return;
315
311
  }
316
- conversation.sendUserMessage(t("dms_ai.panel.plan.go_ahead"));
312
+ sendUserMessage(t("dms_ai.panel.plan.go_ahead"));
317
313
  }
318
314
 
319
315
  function setDraft(text: string): void {
@@ -341,7 +337,7 @@ function startConversation(intent: StartConversationIntent): void {
341
337
  if (next !== undefined || sends)
342
338
  whenConnected(() => {
343
339
  if (next !== undefined) mode.setMode(next);
344
- if (sends) conversation.sendUserMessage(prompt);
340
+ if (sends) sendUserMessage(prompt);
345
341
  });
346
342
  if (sends) return;
347
343
  if (prompt) void nextTick(() => setDraft(prompt));
@@ -407,26 +403,6 @@ function refreshListAfterTurn(msg: unknown): void {
407
403
 
408
404
  channel.onMessage(refreshListAfterTurn);
409
405
 
410
- function isNearBottom(el: HTMLElement): boolean {
411
- return (
412
- el.scrollHeight - el.scrollTop - el.clientHeight <=
413
- SCROLL_STICKY_THRESHOLD_PX
414
- );
415
- }
416
-
417
- function scrollToBottom(): void {
418
- const el = scrollEl.value;
419
- if (el !== null) el.scrollTop = el.scrollHeight;
420
- }
421
-
422
- watch(
423
- () => [conversation.messages.value, isRunning.value],
424
- () => {
425
- const el = scrollEl.value;
426
- if (el === null || isNearBottom(el)) void nextTick(scrollToBottom);
427
- },
428
- );
429
-
430
406
  // The DMS mounts the panel open on a reload, often before the tab's stream
431
407
  // is up: the list (and the chat's title) is asked for again once it is.
432
408
  watch(isConnected, (connected) => {
@@ -436,14 +412,10 @@ watch(isConnected, (connected) => {
436
412
  onMounted(() => {
437
413
  document.addEventListener("keydown", onDocumentKeydown);
438
414
  list.refresh();
439
- void nextTick(scrollToBottom);
440
415
  });
441
416
 
442
- // The DMS unmounts the panel's content when the panel closes: the chat is
443
- // left then, which ends a Full auto granted until the chat closes.
444
417
  onBeforeUnmount(() => {
445
418
  document.removeEventListener("keydown", onDocumentKeydown);
446
- leave(activeId.value);
447
419
  });
448
420
  </script>
449
421
 
@@ -466,12 +438,6 @@ onBeforeUnmount(() => {
466
438
  :is-reviving="isReviving"
467
439
  @reconnect="channel.reconnect"
468
440
  />
469
- <FullAutoBanner
470
- v-if="mode.current.value.fullAuto"
471
- :full-auto="mode.current.value.fullAuto"
472
- :now-ms="nowMs"
473
- @turn-off="mode.stopFullAuto"
474
- />
475
441
 
476
442
  <section
477
443
  ref="scrollEl"
@@ -511,6 +477,7 @@ onBeforeUnmount(() => {
511
477
  v-if="providerProblem"
512
478
  :agent-name="agentName"
513
479
  :reason="settings.providers[settings.provider]?.reason ?? ''"
480
+ :code="settings.providers[settings.provider]?.code"
514
481
  @open-settings="navigate(SETTINGS_PAGE_PATH)"
515
482
  />
516
483
  </section>
@@ -553,6 +520,7 @@ onBeforeUnmount(() => {
553
520
  :is-disabled="!isConnected || providerProblem"
554
521
  :is-running="isRunning"
555
522
  :mode="mode.current.value"
523
+ :provider="settings.provider"
556
524
  :builder-available="settings.builderAvailable"
557
525
  :page="page"
558
526
  :usage-label="usageLabel"
@@ -561,7 +529,6 @@ onBeforeUnmount(() => {
561
529
  @stop="stopTurn"
562
530
  @set-scope="mode.setScope"
563
531
  @set-mode="mode.setMode"
564
- @start-full-auto="mode.startFullAuto"
565
532
  @open-settings="navigate(SETTINGS_PAGE_PATH)"
566
533
  />
567
534
  </div>
@@ -4,13 +4,14 @@ import {
4
4
  NEWLINE_HOTKEY_MODIFIER,
5
5
  SEND_HOTKEY,
6
6
  } from "../constants/conversation";
7
+ import { MODES_BY_PROVIDER } from "../constants/modes";
7
8
  import { useChatI18n } from "../composables/useChatI18n";
8
9
  import { useComposerAttachments } from "../composables/useComposerAttachments";
9
10
  import type { CurrentPage } from "../types/conversation";
10
11
  import type {
11
12
  ApprovalMode,
12
13
  ConversationModeState,
13
- FullAutoDuration,
14
+ ProviderName,
14
15
  Scope,
15
16
  } from "../types/protocol";
16
17
  import {
@@ -20,12 +21,12 @@ import {
20
21
  } from "../utils/attachments";
21
22
  import { shortcutLabel } from "../utils/platform";
22
23
  import { pageChipLabel } from "../utils/suggestions";
23
- import FullAutoDialog from "./FullAutoDialog.vue";
24
24
 
25
25
  interface Props {
26
26
  isDisabled: boolean;
27
27
  isRunning: boolean;
28
28
  mode: ConversationModeState;
29
+ provider: ProviderName;
29
30
  builderAvailable: boolean;
30
31
  page: CurrentPage | null;
31
32
  /** "9.6k tokens · this chat", "new chat", "offline". */
@@ -42,7 +43,6 @@ interface Emits {
42
43
  stop: [];
43
44
  setScope: [scope: Scope];
44
45
  setMode: [mode: ApprovalMode];
45
- startFullAuto: [duration: FullAutoDuration];
46
46
  openSettings: [];
47
47
  }
48
48
 
@@ -50,6 +50,8 @@ interface ModeLook {
50
50
  icon: string;
51
51
  labelKey: string;
52
52
  descriptionKey: string;
53
+ /** Where Codex behaves differently from Claude Code in this mode. */
54
+ codexDescriptionKey?: string;
53
55
  }
54
56
 
55
57
  const props = defineProps<Props>();
@@ -58,17 +60,22 @@ const { t } = useChatI18n();
58
60
 
59
61
  const MAX_HEIGHT_PX = 160;
60
62
  const SCOPES: readonly Scope[] = ["safe", "vibe"];
61
- const MODES: readonly ApprovalMode[] = ["normal", "acceptEdits", "plan"];
62
63
  const MODE_LOOKS: Record<ApprovalMode, ModeLook> = {
63
64
  normal: {
64
65
  icon: "i-ph-hand-palm",
65
66
  labelKey: "dms_ai.common.mode.normal",
66
67
  descriptionKey: "dms_ai.panel.composer.mode_desc.normal",
67
68
  },
69
+ auto: {
70
+ icon: "i-ph-seal-check",
71
+ labelKey: "dms_ai.common.mode.auto",
72
+ descriptionKey: "dms_ai.panel.composer.mode_desc.auto",
73
+ },
68
74
  acceptEdits: {
69
75
  icon: "i-ph-pencil-simple-line",
70
76
  labelKey: "dms_ai.common.mode.acceptEdits",
71
77
  descriptionKey: "dms_ai.panel.composer.mode_desc.acceptEdits",
78
+ codexDescriptionKey: "dms_ai.panel.composer.mode_desc.acceptEdits_codex",
72
79
  },
73
80
  plan: {
74
81
  icon: "i-ph-list-checks",
@@ -85,7 +92,6 @@ const historyKbd = shortcutLabel(HISTORY_KEY);
85
92
 
86
93
  const draft = ref("");
87
94
  const isPageIncluded = ref(true);
88
- const isFullAutoOpen = ref(false);
89
95
  const isFocused = ref(false);
90
96
  const textareaRef = ref<HTMLTextAreaElement | null>(null);
91
97
  const fileInputRef = ref<HTMLInputElement | null>(null);
@@ -100,7 +106,6 @@ const isSendDisabled = computed(
100
106
  const modeLook = computed(
101
107
  () => MODE_LOOKS[props.mode.mode] ?? MODE_LOOKS.normal,
102
108
  );
103
- const isFullAuto = computed(() => props.mode.fullAuto !== null);
104
109
 
105
110
  watch(
106
111
  () => props.page?.path,
@@ -123,27 +128,22 @@ const scopeItems = computed(() =>
123
128
  })),
124
129
  );
125
130
 
131
+ function modeDescriptionKey(look: ModeLook): string {
132
+ return props.provider === "codex"
133
+ ? (look.codexDescriptionKey ?? look.descriptionKey)
134
+ : look.descriptionKey;
135
+ }
136
+
126
137
  const modeItems = computed(() => [
127
138
  [{ type: "label" as const, label: t("dms_ai.panel.composer.mode_menu") }],
128
- MODES.map((mode) => ({
139
+ MODES_BY_PROVIDER[props.provider].map((mode) => ({
129
140
  label: t(MODE_LOOKS[mode].labelKey),
130
- description: t(MODE_LOOKS[mode].descriptionKey),
141
+ description: t(modeDescriptionKey(MODE_LOOKS[mode])),
131
142
  icon: MODE_LOOKS[mode].icon,
132
143
  type: "checkbox" as const,
133
- checked: !isFullAuto.value && props.mode.mode === mode,
144
+ checked: props.mode.mode === mode,
134
145
  onSelect: (): void => emit("setMode", mode),
135
146
  })),
136
- [
137
- {
138
- label: t("dms_ai.panel.composer.full_auto_item"),
139
- description: t("dms_ai.panel.composer.mode_desc.auto"),
140
- icon: "i-ph-lightning",
141
- color: "error" as const,
142
- onSelect: (): void => {
143
- isFullAutoOpen.value = true;
144
- },
145
- },
146
- ],
147
147
  [
148
148
  {
149
149
  label: t("dms_ai.panel.composer.default_mode"),
@@ -310,15 +310,9 @@ defineExpose({
310
310
  :items="modeItems"
311
311
  :content="{ side: 'top', align: 'start' }"
312
312
  >
313
- <button
314
- type="button"
315
- class="cb-mode-btn"
316
- :class="{ 'is-danger': isFullAuto }"
317
- >
318
- <UIcon :name="isFullAuto ? 'i-ph-lightning' : modeLook.icon" />
319
- {{
320
- isFullAuto ? t("dms_ai.common.mode.auto") : t(modeLook.labelKey)
321
- }}
313
+ <button type="button" class="cb-mode-btn">
314
+ <UIcon :name="modeLook.icon" />
315
+ {{ t(modeLook.labelKey) }}
322
316
  <UIcon name="i-ph-caret-down" />
323
317
  </button>
324
318
  </UDropdownMenu>
@@ -388,10 +382,6 @@ defineExpose({
388
382
  </span>
389
383
  <span class="cb-composer__usage">{{ usageLabel }}</span>
390
384
  </div>
391
- <FullAutoDialog
392
- v-model:open="isFullAutoOpen"
393
- @confirm="(duration) => emit('startFullAuto', duration)"
394
- />
395
385
  </form>
396
386
  </template>
397
387
 
@@ -556,12 +546,6 @@ defineExpose({
556
546
  color: var(--ui-success);
557
547
  }
558
548
 
559
- .cb-mode-btn.is-danger {
560
- border-color: var(--dms-error-line);
561
- background: var(--dms-error-tint);
562
- color: var(--ui-error);
563
- }
564
-
565
549
  .cb-composer__file {
566
550
  display: none;
567
551
  }
@@ -31,6 +31,7 @@ const LOOKS: Record<Notice["kind"], NoticeLook> = {
31
31
  question_skipped: { tone: "neutral", icon: "i-ph-arrow-bend-down-right" },
32
32
  full_auto_ended: { tone: "neutral", icon: "i-ph-lightning-slash" },
33
33
  model_fallback: { tone: "warning", icon: "i-ph-swap" },
34
+ auto_unavailable: { tone: "warning", icon: "i-ph-seal-warning" },
34
35
  };
35
36
 
36
37
  const showErrors = ref(false);
@@ -132,6 +133,14 @@ const expiredTool = computed<string>(() => {
132
133
  })
133
134
  }}
134
135
  </template>
136
+ <template v-else-if="notice.kind === 'auto_unavailable'">
137
+ <b>{{ t("dms_ai.panel.notice.auto_unavailable_title") }}</b>
138
+ {{
139
+ t("dms_ai.panel.notice.auto_unavailable_text", {
140
+ model: notice.model,
141
+ })
142
+ }}
143
+ </template>
135
144
  <template v-else>
136
145
  {{ t("dms_ai.panel.notice.full_auto_ended", { time }) }}
137
146
  </template>
@@ -1,19 +1,30 @@
1
1
  <script setup lang="ts">
2
+ import { computed } from "vue";
2
3
  import { useChatI18n } from "../composables/useChatI18n";
4
+ import type { ProviderUnavailableCode } from "../types/settings";
3
5
 
4
6
  interface Props {
5
7
  agentName: string;
6
- /** Why the provider cannot run, as the sidecar reported it. */
8
+ /** Why the provider cannot run, as the sidecar reported it, in English. */
7
9
  reason: string;
10
+ /** Set when the panel words the reason itself. */
11
+ code?: ProviderUnavailableCode;
8
12
  }
9
13
 
14
+ const REASON_KEYS: Record<ProviderUnavailableCode, string> = {
15
+ codex_not_signed_in: "dms_ai.panel.errors.codex_not_signed_in",
16
+ };
17
+
10
18
  interface Emits {
11
19
  openSettings: [];
12
20
  }
13
21
 
14
- defineProps<Props>();
22
+ const props = defineProps<Props>();
15
23
  const emit = defineEmits<Emits>();
16
24
  const { t } = useChatI18n();
25
+ const reasonText = computed(() =>
26
+ props.code === undefined ? props.reason : t(REASON_KEYS[props.code]),
27
+ );
17
28
  </script>
18
29
 
19
30
  <template>
@@ -24,7 +35,7 @@ const { t } = useChatI18n();
24
35
  <b>
25
36
  {{ t("dms_ai.panel.errors.agent_cant_run", { agent: agentName }) }}
26
37
  </b>
27
- {{ reason }}
38
+ {{ reasonText }}
28
39
  {{ t("dms_ai.panel.errors.agent_no_fallback") }}
29
40
  <div class="cb-provider__row">
30
41
  <UButton
@@ -10,7 +10,6 @@ import type {
10
10
  BlockPreview,
11
11
  ChangeSetSummary,
12
12
  ConversationModeState,
13
- FullAutoState,
14
13
  Notice,
15
14
  QueuedItemWire,
16
15
  SnapshotMessage,
@@ -65,7 +64,6 @@ interface RunProgressEvent {
65
64
  interface SnapshotModeWire {
66
65
  mode?: ConversationModeState["mode"];
67
66
  generationMode?: ConversationModeState["generationMode"];
68
- fullAuto?: FullAutoState | null;
69
67
  }
70
68
 
71
69
  interface SnapshotEvent {
@@ -263,7 +261,6 @@ function toModeState(wire: SnapshotModeWire | undefined) {
263
261
  return {
264
262
  mode: wire.mode,
265
263
  generationMode: wire.generationMode,
266
- fullAuto: wire.fullAuto ?? null,
267
264
  };
268
265
  }
269
266
 
@@ -1,9 +1,9 @@
1
1
  import { computed, type ComputedRef, type Ref } from "vue";
2
+ import { modeFor } from "../constants/modes";
2
3
  import { CLIENT_MESSAGE_TYPES } from "../constants/protocol";
3
4
  import type {
4
5
  ApprovalMode,
5
6
  ConversationModeState,
6
- FullAutoDuration,
7
7
  Scope,
8
8
  } from "../types/protocol";
9
9
  import type { AppSettings } from "../types/settings";
@@ -17,12 +17,10 @@ export interface UseConversationModeOptions {
17
17
  }
18
18
 
19
19
  export interface UseConversationModeResult {
20
- /** The chat's mode, its scope and Full auto, defaults until the sidecar says. */
20
+ /** The chat's mode and its scope, defaults until the sidecar says. */
21
21
  current: ComputedRef<ConversationModeState>;
22
22
  setMode: (mode: ApprovalMode) => void;
23
23
  setScope: (scope: Scope) => void;
24
- startFullAuto: (duration: FullAutoDuration) => void;
25
- stopFullAuto: () => void;
26
24
  }
27
25
 
28
26
  function effectiveScope(scope: Scope, builderAvailable: boolean): Scope {
@@ -30,9 +28,10 @@ function effectiveScope(scope: Scope, builderAvailable: boolean): Scope {
30
28
  }
31
29
 
32
30
  /**
33
- * The active chat's approval mode, scope and Full auto: what the sidecar says,
34
- * or the defaults for new chats from the settings until it has. Changes apply
35
- * at once here and are sent for the sidecar to confirm.
31
+ * The active chat's approval mode and scope: what the sidecar says,
32
+ * or the current provider's default from the settings until it has. A mode the
33
+ * provider does not have reads as Ask first. Changes apply at once here and
34
+ * are sent for the sidecar to confirm.
36
35
  */
37
36
  export function useConversationMode(
38
37
  options: UseConversationModeOptions,
@@ -40,12 +39,12 @@ export function useConversationMode(
40
39
  const current = computed<ConversationModeState>(() => {
41
40
  const { settings, mode } = options;
42
41
  const base: ConversationModeState = mode.value ?? {
43
- mode: settings.value.mode,
42
+ mode: settings.value.defaultModes[settings.value.provider],
44
43
  generationMode: settings.value.generationMode,
45
- fullAuto: null,
46
44
  };
47
45
  return {
48
46
  ...base,
47
+ mode: modeFor(settings.value.provider, base.mode),
49
48
  generationMode: effectiveScope(
50
49
  base.generationMode,
51
50
  settings.value.builderAvailable,
@@ -67,7 +66,5 @@ export function useConversationMode(
67
66
  current,
68
67
  setMode: (mode) => change({ mode }),
69
68
  setScope: (generationMode) => change({ generationMode }),
70
- startFullAuto: (duration) => change({ fullAuto: { duration } }),
71
- stopFullAuto: () => change({ fullAuto: null }),
72
69
  };
73
70
  }
@@ -0,0 +1,77 @@
1
+ import { type Ref, watch } from "vue";
2
+
3
+ const BOTTOM_EPSILON_PX = 4;
4
+
5
+ function distanceFromBottom(el: HTMLElement): number {
6
+ return el.scrollHeight - el.scrollTop - el.clientHeight;
7
+ }
8
+
9
+ function scrollToBottom(el: HTMLElement): void {
10
+ el.scrollTop = el.scrollHeight;
11
+ }
12
+
13
+ /**
14
+ * Keeps a scrolling container on its newest content while it follows. The
15
+ * view follows from the start. An upward scroll that leaves it more than a few
16
+ * pixels from the bottom stops the following, whatever caused it (wheel, keys,
17
+ * scrollbar, find in page, text selection). A scroll that reaches the bottom,
18
+ * or a call to `follow`, starts it again. This holds because the scrolls made
19
+ * here only go down, and a scroll the browser makes when content shrinks
20
+ * still ends at the bottom. A resize of the container or of anything inside
21
+ * it re-pins the bottom, so growth that changes no reactive data (docks,
22
+ * images, wrapping, cards that open) is followed too.
23
+ */
24
+ export function useFollowScroll(scrollEl: Ref<HTMLElement | null>): {
25
+ /** Jumps to the bottom and follows from there. */
26
+ follow: () => void;
27
+ } {
28
+ let following = true;
29
+
30
+ function follow(): void {
31
+ following = true;
32
+ if (scrollEl.value !== null) scrollToBottom(scrollEl.value);
33
+ }
34
+
35
+ function attach(el: HTMLElement): () => void {
36
+ let lastTop = el.scrollTop;
37
+
38
+ const onScroll = (): void => {
39
+ const top = el.scrollTop;
40
+ const movedUp = top < lastTop;
41
+ lastTop = top;
42
+ if (distanceFromBottom(el) <= BOTTOM_EPSILON_PX) following = true;
43
+ else if (movedUp) following = false;
44
+ };
45
+
46
+ const resizes = new ResizeObserver(() => {
47
+ if (following) scrollToBottom(el);
48
+ });
49
+ const observeContent = (): void => {
50
+ resizes.disconnect();
51
+ resizes.observe(el);
52
+ for (const child of el.children) resizes.observe(child);
53
+ };
54
+ const children = new MutationObserver(observeContent);
55
+ observeContent();
56
+ children.observe(el, { childList: true });
57
+
58
+ el.addEventListener("scroll", onScroll, { passive: true });
59
+
60
+ return () => {
61
+ resizes.disconnect();
62
+ children.disconnect();
63
+ el.removeEventListener("scroll", onScroll);
64
+ };
65
+ }
66
+
67
+ watch(
68
+ scrollEl,
69
+ (el, _previous, onCleanup) => {
70
+ if (el === null) return;
71
+ onCleanup(attach(el));
72
+ },
73
+ { immediate: true, flush: "post" },
74
+ );
75
+
76
+ return { follow };
77
+ }
@@ -0,0 +1,15 @@
1
+ import type { ApprovalMode, ProviderName } from "../types/protocol";
2
+
3
+ export const MODES_BY_PROVIDER: Record<ProviderName, readonly ApprovalMode[]> =
4
+ {
5
+ claude: ["normal", "auto", "acceptEdits", "plan"],
6
+ codex: ["normal", "acceptEdits", "plan"],
7
+ };
8
+
9
+ /** The mode as the provider has it: one it lacks reads as Ask first. */
10
+ export function modeFor(
11
+ provider: ProviderName,
12
+ mode: ApprovalMode,
13
+ ): ApprovalMode {
14
+ return MODES_BY_PROVIDER[provider].includes(mode) ? mode : "normal";
15
+ }
@@ -14,7 +14,6 @@ export const CLIENT_MESSAGE_TYPES = {
14
14
  QUEUE_CLEAR: "queue_clear",
15
15
  REVOKE_RULE: "revoke_rule",
16
16
  SET_CONVERSATION_MODE: "set_conversation_mode",
17
- LEAVE_CONVERSATION: "leave_conversation",
18
17
  STOP_AUTOFIX: "stop_autofix",
19
18
  RETRY_TURN: "retry_turn",
20
19
  CHANGE_SET_ACTION: "change_set_action",
@@ -4,7 +4,7 @@ const DEFAULT_REQUEST_TIMEOUT_MINUTES = 5;
4
4
 
5
5
  export const DEFAULT_SETTINGS: AppSettings = {
6
6
  provider: "claude",
7
- mode: "normal",
7
+ defaultModes: { claude: "auto", codex: "normal" },
8
8
  thinking: "medium",
9
9
  generationMode: "safe",
10
10
  allowLocalSkills: false,
@@ -5,9 +5,8 @@
5
5
  */
6
6
 
7
7
  export type ProviderName = "claude" | "codex";
8
- export type ApprovalMode = "normal" | "acceptEdits" | "plan";
8
+ export type ApprovalMode = "normal" | "auto" | "acceptEdits" | "plan";
9
9
  export type Scope = "safe" | "vibe";
10
- export type FullAutoDuration = "turn" | "30m" | "chat";
11
10
 
12
11
  export type ToolOutcome = "done" | "failed" | "denied" | "blocked" | "stopped";
13
12
 
@@ -17,6 +16,8 @@ export type AllowedBy =
17
16
  | "approved"
18
17
  | "rule"
19
18
  | "full_auto"
19
+ | "auto_approved"
20
+ | "auto_denied"
20
21
  | "blocked"
21
22
  | "denied"
22
23
  | "expired";
@@ -172,22 +173,23 @@ export interface ModelFallbackNotice {
172
173
  timestampMs: number;
173
174
  }
174
175
 
176
+ export interface AutoUnavailableNotice {
177
+ kind: "auto_unavailable";
178
+ model: string;
179
+ timestampMs: number;
180
+ }
181
+
175
182
  export type Notice =
176
183
  | AutoFixNotice
177
184
  | PermissionExpiredNotice
178
185
  | QuestionSkippedNotice
179
186
  | FullAutoEndedNotice
180
- | ModelFallbackNotice;
181
-
182
- export interface FullAutoState {
183
- duration: FullAutoDuration;
184
- untilMs?: number;
185
- }
187
+ | ModelFallbackNotice
188
+ | AutoUnavailableNotice;
186
189
 
187
190
  export interface ConversationModeState {
188
191
  mode: ApprovalMode;
189
192
  generationMode: Scope;
190
- fullAuto: FullAutoState | null;
191
193
  }
192
194
 
193
195
  export interface QuestionAnswerRecord {
@@ -3,16 +3,21 @@ import type { ApprovalMode, ProviderName, Scope } from "./protocol";
3
3
  export type ThinkingLevel = "off" | "low" | "medium" | "high";
4
4
  export type { ProviderName };
5
5
 
6
+ /** The unavailability reasons the panel words itself (sidecar `PROVIDER_UNAVAILABLE_CODES`). */
7
+ export type ProviderUnavailableCode = "codex_not_signed_in";
8
+
6
9
  export interface ProviderAvailability {
7
10
  available: boolean;
8
- /** Why the provider cannot be picked; absent when it is available. */
11
+ /** Why the provider cannot be picked, in English; absent when it is available. */
9
12
  reason?: string;
13
+ /** Names the reason when the panel has its own wording for it. */
14
+ code?: ProviderUnavailableCode;
10
15
  }
11
16
 
12
17
  export interface AppSettings {
13
18
  provider: ProviderName;
14
19
  /** The default approval mode of new chats. */
15
- mode: ApprovalMode;
20
+ defaultModes: Record<ProviderName, ApprovalMode>;
16
21
  thinking: ThinkingLevel;
17
22
  /** The default scope of new chats. */
18
23
  generationMode: Scope;
@@ -90,6 +90,7 @@ const DESTRUCTIVE_TITLE_KEYS: Record<string, string> = {
90
90
  BuilderRemoveField: "dms_ai.panel.approvals.destructive.remove_field",
91
91
  BuilderDeleteCategory: "dms_ai.panel.approvals.destructive.delete_category",
92
92
  BuilderRemoveQuery: "dms_ai.panel.approvals.destructive.remove_query",
93
+ BuilderMapResources: "dms_ai.panel.approvals.destructive.map_resources",
93
94
  };
94
95
 
95
96
  function destructiveTitle(