Преглед на файлове

feat(app): follow-up behavior (#17233)

Adam преди 1 месец
родител
ревизия
42a5af6c8f
променени са 45 файла, в които са добавени 1164 реда и са изтрити 183 реда
  1. 48 1
      packages/app/src/components/prompt-input.tsx
  2. 181 48
      packages/app/src/components/prompt-input/submit.ts
  3. 76 54
      packages/app/src/components/settings-general.tsx
  4. 6 0
      packages/app/src/context/settings.tsx
  5. 29 0
      packages/app/src/i18n/ar.ts
  6. 31 0
      packages/app/src/i18n/br.ts
  7. 30 0
      packages/app/src/i18n/bs.ts
  8. 30 0
      packages/app/src/i18n/da.ts
  9. 33 0
      packages/app/src/i18n/de.ts
  10. 12 0
      packages/app/src/i18n/en.ts
  11. 32 1
      packages/app/src/i18n/es.ts
  12. 32 0
      packages/app/src/i18n/fr.ts
  13. 30 0
      packages/app/src/i18n/ja.ts
  14. 29 0
      packages/app/src/i18n/ko.ts
  15. 31 2
      packages/app/src/i18n/no.ts
  16. 30 0
      packages/app/src/i18n/pl.ts
  17. 47 16
      packages/app/src/i18n/ru.ts
  18. 29 1
      packages/app/src/i18n/th.ts
  19. 27 0
      packages/app/src/i18n/tr.ts
  20. 29 0
      packages/app/src/i18n/zh.ts
  21. 31 2
      packages/app/src/i18n/zht.ts
  22. 158 1
      packages/app/src/pages/session.tsx
  23. 26 0
      packages/app/src/pages/session/composer/session-composer-region.tsx
  24. 109 0
      packages/app/src/pages/session/composer/session-followup-dock.tsx
  25. 2 2
      packages/app/src/pages/session/composer/session-revert-dock.tsx
  26. 0 7
      packages/app/src/pages/session/message-timeline.tsx
  27. 0 17
      packages/ui/src/components/message-part.css
  28. 1 10
      packages/ui/src/components/message-part.tsx
  29. 2 20
      packages/ui/src/components/session-turn.tsx
  30. 2 0
      packages/ui/src/i18n/ar.ts
  31. 2 0
      packages/ui/src/i18n/br.ts
  32. 2 0
      packages/ui/src/i18n/bs.ts
  33. 2 0
      packages/ui/src/i18n/da.ts
  34. 2 0
      packages/ui/src/i18n/de.ts
  35. 2 0
      packages/ui/src/i18n/es.ts
  36. 2 0
      packages/ui/src/i18n/fr.ts
  37. 2 0
      packages/ui/src/i18n/ja.ts
  38. 2 0
      packages/ui/src/i18n/ko.ts
  39. 3 1
      packages/ui/src/i18n/no.ts
  40. 2 0
      packages/ui/src/i18n/pl.ts
  41. 2 0
      packages/ui/src/i18n/ru.ts
  42. 2 0
      packages/ui/src/i18n/th.ts
  43. 12 0
      packages/ui/src/i18n/tr.ts
  44. 2 0
      packages/ui/src/i18n/zh.ts
  45. 2 0
      packages/ui/src/i18n/zht.ts

+ 48 - 1
packages/app/src/components/prompt-input.tsx

@@ -48,7 +48,7 @@ import {
   type PromptHistoryStoredEntry,
   promptLength,
 } from "./prompt-input/history"
-import { createPromptSubmit } from "./prompt-input/submit"
+import { createPromptSubmit, type FollowupDraft } from "./prompt-input/submit"
 import { PromptPopover, type AtOption, type SlashCommand } from "./prompt-input/slash-popover"
 import { PromptContextItems } from "./prompt-input/context-items"
 import { PromptImageAttachments } from "./prompt-input/image-attachments"
@@ -61,6 +61,11 @@ interface PromptInputProps {
   ref?: (el: HTMLDivElement) => void
   newSessionWorktree?: string
   onNewSessionWorktreeReset?: () => void
+  edit?: { id: string; prompt: Prompt; context: FollowupDraft["context"] }
+  onEditLoaded?: () => void
+  shouldQueue?: () => boolean
+  onQueue?: (draft: FollowupDraft) => void
+  onAbort?: () => void
   onSubmit?: () => void
 }
 
@@ -947,6 +952,45 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
     setCurrentHistory("entries", next)
   }
 
+  createEffect(
+    on(
+      () => props.edit?.id,
+      (id) => {
+        const edit = props.edit
+        if (!id || !edit) return
+
+        for (const item of prompt.context.items()) {
+          prompt.context.remove(item.key)
+        }
+
+        for (const item of edit.context) {
+          prompt.context.add({
+            type: item.type,
+            path: item.path,
+            selection: item.selection,
+            comment: item.comment,
+            commentID: item.commentID,
+            commentOrigin: item.commentOrigin,
+            preview: item.preview,
+          })
+        }
+
+        setStore("mode", "normal")
+        setStore("popover", null)
+        setStore("historyIndex", -1)
+        setStore("savedPrompt", null)
+        prompt.set(edit.prompt, promptLength(edit.prompt))
+        requestAnimationFrame(() => {
+          editorRef.focus()
+          setCursorPosition(editorRef, promptLength(edit.prompt))
+          queueScroll()
+        })
+        props.onEditLoaded?.()
+      },
+      { defer: true },
+    ),
+  )
+
   const navigateHistory = (direction: "up" | "down") => {
     const result = navigatePromptHistory({
       direction,
@@ -1001,6 +1045,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
     setPopover: (popover) => setStore("popover", popover),
     newSessionWorktree: () => props.newSessionWorktree,
     onNewSessionWorktreeReset: props.onNewSessionWorktreeReset,
+    shouldQueue: props.shouldQueue,
+    onQueue: props.onQueue,
+    onAbort: props.onAbort,
     onSubmit: props.onSubmit,
   })
 

+ 181 - 48
packages/app/src/components/prompt-input/submit.ts

@@ -9,7 +9,7 @@ import { useLanguage } from "@/context/language"
 import { useLayout } from "@/context/layout"
 import { useLocal } from "@/context/local"
 import { usePermission } from "@/context/permission"
-import { type ImageAttachmentPart, type Prompt, usePrompt } from "@/context/prompt"
+import { type ContextItem, type ImageAttachmentPart, type Prompt, usePrompt } from "@/context/prompt"
 import { useSDK } from "@/context/sdk"
 import { useSync } from "@/context/sync"
 import { Identifier } from "@/utils/id"
@@ -25,6 +25,145 @@ type PendingPrompt = {
 
 const pending = new Map<string, PendingPrompt>()
 
+export type FollowupDraft = {
+  sessionID: string
+  sessionDirectory: string
+  prompt: Prompt
+  context: (ContextItem & { key: string })[]
+  agent: string
+  model: { providerID: string; modelID: string }
+  variant?: string
+}
+
+type FollowupSendInput = {
+  client: ReturnType<typeof useSDK>["client"]
+  globalSync: ReturnType<typeof useGlobalSync>
+  sync: ReturnType<typeof useSync>
+  draft: FollowupDraft
+  messageID?: string
+  optimisticBusy?: boolean
+  before?: () => Promise<boolean> | boolean
+}
+
+const draftText = (prompt: Prompt) => prompt.map((part) => ("content" in part ? part.content : "")).join("")
+
+const draftImages = (prompt: Prompt) => prompt.filter((part): part is ImageAttachmentPart => part.type === "image")
+
+export async function sendFollowupDraft(input: FollowupSendInput) {
+  const text = draftText(input.draft.prompt)
+  const images = draftImages(input.draft.prompt)
+  const [, setStore] = input.globalSync.child(input.draft.sessionDirectory)
+
+  const setBusy = () => {
+    if (!input.optimisticBusy) return
+    setStore("session_status", input.draft.sessionID, { type: "busy" })
+  }
+
+  const setIdle = () => {
+    if (!input.optimisticBusy) return
+    setStore("session_status", input.draft.sessionID, { type: "idle" })
+  }
+
+  const wait = async () => {
+    const ok = await input.before?.()
+    if (ok === false) return false
+    return true
+  }
+
+  const [head, ...tail] = text.split(" ")
+  const cmd = head?.startsWith("/") ? head.slice(1) : undefined
+  if (cmd && input.sync.data.command.find((item) => item.name === cmd)) {
+    setBusy()
+    try {
+      if (!(await wait())) {
+        setIdle()
+        return false
+      }
+
+      await input.client.session.command({
+        sessionID: input.draft.sessionID,
+        command: cmd,
+        arguments: tail.join(" "),
+        agent: input.draft.agent,
+        model: `${input.draft.model.providerID}/${input.draft.model.modelID}`,
+        variant: input.draft.variant,
+        parts: images.map((attachment) => ({
+          id: Identifier.ascending("part"),
+          type: "file" as const,
+          mime: attachment.mime,
+          url: attachment.dataUrl,
+          filename: attachment.filename,
+        })),
+      })
+      return true
+    } catch (err) {
+      setIdle()
+      throw err
+    }
+  }
+
+  const messageID = input.messageID ?? Identifier.ascending("message")
+  const { requestParts, optimisticParts } = buildRequestParts({
+    prompt: input.draft.prompt,
+    context: input.draft.context,
+    images,
+    text,
+    sessionID: input.draft.sessionID,
+    messageID,
+    sessionDirectory: input.draft.sessionDirectory,
+  })
+
+  const message: Message = {
+    id: messageID,
+    sessionID: input.draft.sessionID,
+    role: "user",
+    time: { created: Date.now() },
+    agent: input.draft.agent,
+    model: input.draft.model,
+    variant: input.draft.variant,
+  }
+
+  const add = () =>
+    input.sync.session.optimistic.add({
+      directory: input.draft.sessionDirectory,
+      sessionID: input.draft.sessionID,
+      message,
+      parts: optimisticParts,
+    })
+
+  const remove = () =>
+    input.sync.session.optimistic.remove({
+      directory: input.draft.sessionDirectory,
+      sessionID: input.draft.sessionID,
+      messageID,
+    })
+
+  setBusy()
+  add()
+
+  try {
+    if (!(await wait())) {
+      setIdle()
+      remove()
+      return false
+    }
+
+    await input.client.session.promptAsync({
+      sessionID: input.draft.sessionID,
+      agent: input.draft.agent,
+      model: input.draft.model,
+      messageID,
+      parts: requestParts,
+      variant: input.draft.variant,
+    })
+    return true
+  } catch (err) {
+    setIdle()
+    remove()
+    throw err
+  }
+}
+
 type PromptSubmitInput = {
   info: Accessor<{ id: string } | undefined>
   imageAttachments: Accessor<ImageAttachmentPart[]>
@@ -41,6 +180,9 @@ type PromptSubmitInput = {
   setPopover: (popover: "at" | "slash" | null) => void
   newSessionWorktree?: Accessor<string | undefined>
   onNewSessionWorktreeReset?: () => void
+  shouldQueue?: Accessor<boolean>
+  onQueue?: (draft: FollowupDraft) => void
+  onAbort?: () => void
   onSubmit?: () => void
 }
 
@@ -82,6 +224,8 @@ export function createPromptSubmit(input: PromptSubmitInput) {
     const [, setStore] = globalSync.child(sdk.directory)
     setStore("todo", sessionID, [])
 
+    input.onAbort?.()
+
     const queued = pending.get(sessionID)
     if (queued) {
       queued.abort.abort()
@@ -116,6 +260,12 @@ export function createPromptSubmit(input: PromptSubmitInput) {
     }
   }
 
+  const clearContext = () => {
+    for (const item of prompt.context.items()) {
+      prompt.context.remove(item.key)
+    }
+  }
+
   const handleSubmit = async (event: Event) => {
     event.preventDefault()
 
@@ -215,14 +365,22 @@ export function createPromptSubmit(input: PromptSubmitInput) {
       return
     }
 
-    input.onSubmit?.()
-
     const model = {
       modelID: currentModel.id,
       providerID: currentModel.provider.id,
     }
     const agent = currentAgent.name
     const variant = local.model.variant.current()
+    const context = prompt.context.items().slice()
+    const draft: FollowupDraft = {
+      sessionID: session.id,
+      sessionDirectory,
+      prompt: currentPrompt,
+      context,
+      agent,
+      model,
+      variant,
+    }
 
     const clearInput = () => {
       prompt.reset()
@@ -243,6 +401,15 @@ export function createPromptSubmit(input: PromptSubmitInput) {
       })
     }
 
+    if (!isNewSession && mode === "normal" && input.shouldQueue?.()) {
+      input.onQueue?.(draft)
+      clearContext()
+      clearInput()
+      return
+    }
+
+    input.onSubmit?.()
+
     if (mode === "shell") {
       clearInput()
       client.session
@@ -295,48 +462,19 @@ export function createPromptSubmit(input: PromptSubmitInput) {
       }
     }
 
-    const context = prompt.context.items().slice()
     const commentItems = context.filter((item) => item.type === "file" && !!item.comment?.trim())
-
     const messageID = Identifier.ascending("message")
-    const { requestParts, optimisticParts } = buildRequestParts({
-      prompt: currentPrompt,
-      context,
-      images,
-      text,
-      sessionID: session.id,
-      messageID,
-      sessionDirectory,
-    })
-
-    const optimisticMessage: Message = {
-      id: messageID,
-      sessionID: session.id,
-      role: "user",
-      time: { created: Date.now() },
-      agent,
-      model,
-      variant,
-    }
-
-    const addOptimisticMessage = () =>
-      sync.session.optimistic.add({
-        directory: sessionDirectory,
-        sessionID: session.id,
-        message: optimisticMessage,
-        parts: optimisticParts,
-      })
 
-    const removeOptimisticMessage = () =>
+    const removeOptimisticMessage = () => {
       sync.session.optimistic.remove({
         directory: sessionDirectory,
         sessionID: session.id,
         messageID,
       })
+    }
 
     removeCommentItems(commentItems)
     clearInput()
-    addOptimisticMessage()
 
     const waitForWorktree = async () => {
       const worktree = WorktreeState.get(sessionDirectory)
@@ -393,20 +531,15 @@ export function createPromptSubmit(input: PromptSubmitInput) {
       return true
     }
 
-    const send = async () => {
-      const ok = await waitForWorktree()
-      if (!ok) return
-      await client.session.promptAsync({
-        sessionID: session.id,
-        agent,
-        model,
-        messageID,
-        parts: requestParts,
-        variant,
-      })
-    }
-
-    void send().catch((err) => {
+    void sendFollowupDraft({
+      client,
+      sync,
+      globalSync,
+      draft,
+      messageID,
+      optimisticBusy: sessionDirectory === projectDirectory,
+      before: waitForWorktree,
+    }).catch((err) => {
       pending.delete(session.id)
       if (sessionDirectory === projectDirectory) {
         sync.set("session_status", session.id, { type: "idle" })

+ 76 - 54
packages/app/src/components/settings-general.tsx

@@ -113,6 +113,11 @@ export const SettingsGeneral: Component = () => {
     { value: "dark", label: language.t("theme.scheme.dark") },
   ])
 
+  const followupOptions = createMemo((): { value: "queue" | "steer"; label: string }[] => [
+    { value: "queue", label: language.t("settings.general.row.followup.option.queue") },
+    { value: "steer", label: language.t("settings.general.row.followup.option.steer") },
+  ])
+
   const languageOptions = createMemo(() =>
     language.locales.map((locale) => ({
       value: locale,
@@ -170,10 +175,8 @@ export const SettingsGeneral: Component = () => {
     triggerVariant: "settings" as const,
   })
 
-  const AppearanceSection = () => (
+  const GeneralSection = () => (
     <div class="flex flex-col gap-1">
-      <h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.general.section.appearance")}</h3>
-
       <div class="bg-surface-raised-base px-4 rounded-lg">
         <SettingsRow
           title={language.t("settings.general.row.language.title")}
@@ -193,8 +196,70 @@ export const SettingsGeneral: Component = () => {
         </SettingsRow>
 
         <SettingsRow
-          title={language.t("settings.general.row.appearance.title")}
-          description={language.t("settings.general.row.appearance.description")}
+          title={language.t("settings.general.row.reasoningSummaries.title")}
+          description={language.t("settings.general.row.reasoningSummaries.description")}
+        >
+          <div data-action="settings-feed-reasoning-summaries">
+            <Switch
+              checked={settings.general.showReasoningSummaries()}
+              onChange={(checked) => settings.general.setShowReasoningSummaries(checked)}
+            />
+          </div>
+        </SettingsRow>
+
+        <SettingsRow
+          title={language.t("settings.general.row.shellToolPartsExpanded.title")}
+          description={language.t("settings.general.row.shellToolPartsExpanded.description")}
+        >
+          <div data-action="settings-feed-shell-tool-parts-expanded">
+            <Switch
+              checked={settings.general.shellToolPartsExpanded()}
+              onChange={(checked) => settings.general.setShellToolPartsExpanded(checked)}
+            />
+          </div>
+        </SettingsRow>
+
+        <SettingsRow
+          title={language.t("settings.general.row.editToolPartsExpanded.title")}
+          description={language.t("settings.general.row.editToolPartsExpanded.description")}
+        >
+          <div data-action="settings-feed-edit-tool-parts-expanded">
+            <Switch
+              checked={settings.general.editToolPartsExpanded()}
+              onChange={(checked) => settings.general.setEditToolPartsExpanded(checked)}
+            />
+          </div>
+        </SettingsRow>
+
+        <SettingsRow
+          title={language.t("settings.general.row.followup.title")}
+          description={language.t("settings.general.row.followup.description")}
+        >
+          <Select
+            data-action="settings-followup"
+            options={followupOptions()}
+            current={followupOptions().find((o) => o.value === settings.general.followup())}
+            value={(o) => o.value}
+            label={(o) => o.label}
+            onSelect={(option) => option && settings.general.setFollowup(option.value)}
+            variant="secondary"
+            size="small"
+            triggerVariant="settings"
+            triggerStyle={{ "min-width": "180px" }}
+          />
+        </SettingsRow>
+      </div>
+    </div>
+  )
+
+  const AppearanceSection = () => (
+    <div class="flex flex-col gap-1">
+      <h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.general.section.appearance")}</h3>
+
+      <div class="bg-surface-raised-base px-4 rounded-lg">
+        <SettingsRow
+          title={language.t("settings.general.row.colorScheme.title")}
+          description={language.t("settings.general.row.colorScheme.description")}
         >
           <Select
             data-action="settings-color-scheme"
@@ -211,6 +276,7 @@ export const SettingsGeneral: Component = () => {
             variant="secondary"
             size="small"
             triggerVariant="settings"
+            triggerStyle={{ "min-width": "220px" }}
           />
         </SettingsRow>
 
@@ -271,50 +337,6 @@ export const SettingsGeneral: Component = () => {
     </div>
   )
 
-  const FeedSection = () => (
-    <div class="flex flex-col gap-1">
-      <h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.general.section.feed")}</h3>
-
-      <div class="bg-surface-raised-base px-4 rounded-lg">
-        <SettingsRow
-          title={language.t("settings.general.row.reasoningSummaries.title")}
-          description={language.t("settings.general.row.reasoningSummaries.description")}
-        >
-          <div data-action="settings-feed-reasoning-summaries">
-            <Switch
-              checked={settings.general.showReasoningSummaries()}
-              onChange={(checked) => settings.general.setShowReasoningSummaries(checked)}
-            />
-          </div>
-        </SettingsRow>
-
-        <SettingsRow
-          title={language.t("settings.general.row.shellToolPartsExpanded.title")}
-          description={language.t("settings.general.row.shellToolPartsExpanded.description")}
-        >
-          <div data-action="settings-feed-shell-tool-parts-expanded">
-            <Switch
-              checked={settings.general.shellToolPartsExpanded()}
-              onChange={(checked) => settings.general.setShellToolPartsExpanded(checked)}
-            />
-          </div>
-        </SettingsRow>
-
-        <SettingsRow
-          title={language.t("settings.general.row.editToolPartsExpanded.title")}
-          description={language.t("settings.general.row.editToolPartsExpanded.description")}
-        >
-          <div data-action="settings-feed-edit-tool-parts-expanded">
-            <Switch
-              checked={settings.general.editToolPartsExpanded()}
-              onChange={(checked) => settings.general.setEditToolPartsExpanded(checked)}
-            />
-          </div>
-        </SettingsRow>
-      </div>
-    </div>
-  )
-
   const NotificationsSection = () => (
     <div class="flex flex-col gap-1">
       <h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.general.section.notifications")}</h3>
@@ -465,9 +487,9 @@ export const SettingsGeneral: Component = () => {
       </div>
 
       <div class="flex flex-col gap-8 w-full">
-        <AppearanceSection />
+        <GeneralSection />
 
-        <FeedSection />
+        <AppearanceSection />
 
         <NotificationsSection />
 
@@ -551,12 +573,12 @@ interface SettingsRowProps {
 
 const SettingsRow: Component<SettingsRowProps> = (props) => {
   return (
-    <div class="flex flex-wrap items-center justify-between gap-4 py-3 border-b border-border-weak-base last:border-none">
-      <div class="flex flex-col gap-0.5 min-w-0">
+    <div class="flex flex-wrap items-center gap-4 py-3 border-b border-border-weak-base last:border-none sm:flex-nowrap">
+      <div class="flex min-w-0 flex-1 flex-col gap-0.5">
         <span class="text-14-medium text-text-strong">{props.title}</span>
         <span class="text-12-regular text-text-weak">{props.description}</span>
       </div>
-      <div class="flex-shrink-0">{props.children}</div>
+      <div class="flex w-full justify-end sm:w-auto sm:shrink-0">{props.children}</div>
     </div>
   )
 }

+ 6 - 0
packages/app/src/context/settings.tsx

@@ -22,6 +22,7 @@ export interface Settings {
   general: {
     autoSave: boolean
     releaseNotes: boolean
+    followup: "queue" | "steer"
     showReasoningSummaries: boolean
     shellToolPartsExpanded: boolean
     editToolPartsExpanded: boolean
@@ -45,6 +46,7 @@ const defaultSettings: Settings = {
   general: {
     autoSave: true,
     releaseNotes: true,
+    followup: "steer",
     showReasoningSummaries: false,
     shellToolPartsExpanded: true,
     editToolPartsExpanded: false,
@@ -126,6 +128,10 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
         setReleaseNotes(value: boolean) {
           setStore("general", "releaseNotes", value)
         },
+        followup: withFallback(() => store.general?.followup, defaultSettings.general.followup),
+        setFollowup(value: "queue" | "steer") {
+          setStore("general", "followup", value)
+        },
         showReasoningSummaries: withFallback(
           () => store.general?.showReasoningSummaries,
           defaultSettings.general.showReasoningSummaries,

+ 29 - 0
packages/app/src/i18n/ar.ts

@@ -104,6 +104,7 @@ export const dict = {
   "dialog.model.empty": "لا توجد نتائج للنماذج",
   "dialog.model.manage": "إدارة النماذج",
   "dialog.model.manage.description": "تخصيص النماذج التي تظهر في محدد النماذج.",
+  "dialog.model.manage.provider.toggle": "تبديل جميع نماذج {{provider}}",
   "dialog.model.unpaid.freeModels.title": "نماذج مجانية مقدمة من OpenCode",
   "dialog.model.unpaid.addMore.title": "إضافة المزيد من النماذج من موفرين مشهورين",
   "dialog.provider.viewAll": "عرض المزيد من الموفرين",
@@ -288,6 +289,11 @@ export const dict = {
   "dialog.server.add.error": "تعذر الاتصال بالخادم",
   "dialog.server.add.checking": "جارٍ التحقق...",
   "dialog.server.add.button": "إضافة خادم",
+  "dialog.server.add.name": "اسم الخادم (اختياري)",
+  "dialog.server.add.namePlaceholder": "Localhost",
+  "dialog.server.add.username": "اسم المستخدم (اختياري)",
+  "dialog.server.add.password": "كلمة المرور (اختياري)",
+  "dialog.server.edit.title": "تحرير الخادم",
   "dialog.server.default.title": "الخادم الافتراضي",
   "dialog.server.default.description":
     "الاتصال بهذا الخادم عند بدء تشغيل التطبيق بدلاً من بدء خادم محلي. يتطلب إعادة التشغيل.",
@@ -358,6 +364,7 @@ export const dict = {
   "language.br": "Português (Brasil)",
   "language.bs": "Bosanski",
   "language.th": "ไทย",
+  "language.tr": "Türkçe",
   "toast.language.title": "لغة",
   "toast.language.description": "تم التبديل إلى {{language}}",
   "toast.theme.title": "تم تبديل السمة",
@@ -444,8 +451,11 @@ export const dict = {
   "session.review.loadingChanges": "جارٍ تحميل التغييرات...",
   "session.review.empty": "لا توجد تغييرات في هذه الجلسة بعد",
   "session.review.noChanges": "لا توجد تغييرات",
+  "session.review.noVcs": "لم يتم اكتشاف نظام التحكم في الإصدار Git، لن يتم عرض التغييرات",
+  "session.review.noSnapshot": "تم تعطيل تتبع اللقطات في التكوين، لذا فإن تغييرات الجلسة غير متوفرة",
   "session.files.selectToOpen": "اختر ملفًا لفتحه",
   "session.files.all": "كل الملفات",
+  "session.files.empty": "لا توجد ملفات",
   "session.files.binaryContent": "ملف ثنائي (لا يمكن عرض المحتوى)",
   "session.messages.renderEarlier": "عرض الرسائل السابقة",
   "session.messages.loadingEarlier": "جارٍ تحميل الرسائل السابقة...",
@@ -456,6 +466,17 @@ export const dict = {
   "session.todo.title": "المهام",
   "session.todo.collapse": "طي",
   "session.todo.expand": "توسيع",
+  "session.followupDock.summary.one": "{{count}} رسالة في الانتظار",
+  "session.followupDock.summary.other": "{{count}} رسائل في الانتظار",
+  "session.followupDock.sendNow": "إرسال الآن",
+  "session.followupDock.edit": "تحرير",
+  "session.followupDock.collapse": "طي الرسائل المنتظرة",
+  "session.followupDock.expand": "توسيع الرسائل المنتظرة",
+  "session.revertDock.summary.one": "{{count}} رسالة تم التراجع عنها",
+  "session.revertDock.summary.other": "{{count}} رسائل تم التراجع عنها",
+  "session.revertDock.collapse": "طي الرسائل التي تم التراجع عنها",
+  "session.revertDock.expand": "توسيع الرسائل التي تم التراجع عنها",
+  "session.revertDock.restore": "استعادة الرسالة",
   "session.new.title": "ابنِ أي شيء",
   "session.new.worktree.main": "الفرع الرئيسي",
   "session.new.worktree.mainWithBranch": "الفرع الرئيسي ({{branch}})",
@@ -538,10 +559,18 @@ export const dict = {
   "settings.general.row.language.description": "تغيير لغة العرض لـ OpenCode",
   "settings.general.row.appearance.title": "المظهر",
   "settings.general.row.appearance.description": "تخصيص كيفية ظهور OpenCode على جهازك",
+  "settings.general.row.colorScheme.title": "مخطط الألوان",
+  "settings.general.row.colorScheme.description": "اختر ما إذا كان OpenCode يتبع سمة النظام أو الفاتح أو الداكن",
   "settings.general.row.theme.title": "السمة",
   "settings.general.row.theme.description": "تخصيص سمة OpenCode.",
   "settings.general.row.font.title": "الخط",
   "settings.general.row.font.description": "تخصيص الخط الأحادي المستخدم في كتل التعليمات البرمجية",
+  "settings.general.row.followup.title": "سلوك المتابعة",
+  "settings.general.row.followup.description": "اختر ما إذا كانت طلبات المتابعة توجه فورًا أو تنتظر في قائمة انتظار",
+  "settings.general.row.followup.option.queue": "قائمة انتظار",
+  "settings.general.row.followup.option.steer": "توجيه",
+  "settings.general.row.reasoningSummaries.title": "إظهار ملخصات الاستنتاج",
+  "settings.general.row.reasoningSummaries.description": "عرض ملخصات استنتاج النموذج في الشريط الزمني",
   "settings.general.row.shellToolPartsExpanded.title": "توسيع أجزاء أداة shell",
   "settings.general.row.shellToolPartsExpanded.description":
     "إظهار أجزاء أداة shell موسعة بشكل افتراضي في الشريط الزمني",

+ 31 - 0
packages/app/src/i18n/br.ts

@@ -104,6 +104,7 @@ export const dict = {
   "dialog.model.empty": "Nenhum resultado de modelo",
   "dialog.model.manage": "Gerenciar modelos",
   "dialog.model.manage.description": "Personalizar quais modelos aparecem no seletor de modelos.",
+  "dialog.model.manage.provider.toggle": "Alternar todos os modelos {{provider}}",
   "dialog.model.unpaid.freeModels.title": "Modelos gratuitos fornecidos pelo OpenCode",
   "dialog.model.unpaid.addMore.title": "Adicionar mais modelos de provedores populares",
   "dialog.provider.viewAll": "Ver mais provedores",
@@ -288,6 +289,11 @@ export const dict = {
   "dialog.server.add.error": "Não foi possível conectar ao servidor",
   "dialog.server.add.checking": "Verificando...",
   "dialog.server.add.button": "Adicionar",
+  "dialog.server.add.name": "Nome do servidor (opcional)",
+  "dialog.server.add.namePlaceholder": "Localhost",
+  "dialog.server.add.username": "Nome de usuário (opcional)",
+  "dialog.server.add.password": "Senha (opcional)",
+  "dialog.server.edit.title": "Editar servidor",
   "dialog.server.default.title": "Servidor padrão",
   "dialog.server.default.description":
     "Conectar a este servidor na inicialização do aplicativo ao invés de iniciar um servidor local. Requer reinicialização.",
@@ -359,6 +365,7 @@ export const dict = {
   "language.br": "Português (Brasil)",
   "language.bs": "Bosanski",
   "language.th": "ไทย",
+  "language.tr": "Türkçe",
   "toast.language.title": "Idioma",
   "toast.language.description": "Alterado para {{language}}",
   "toast.theme.title": "Tema alterado",
@@ -446,9 +453,13 @@ export const dict = {
   "session.review.change.other": "Alterações",
   "session.review.loadingChanges": "Carregando alterações...",
   "session.review.empty": "Nenhuma alteração nesta sessão ainda",
+  "session.review.noVcs": "Nenhum Sistema de Controle de Versão Git detectado, alterações não exibidas",
+  "session.review.noSnapshot":
+    "O rastreamento de snapshot está desabilitado na configuração, então as alterações da sessão estão indisponíveis",
   "session.review.noChanges": "Sem alterações",
   "session.files.selectToOpen": "Selecione um arquivo para abrir",
   "session.files.all": "Todos os arquivos",
+  "session.files.empty": "Nenhum arquivo",
   "session.files.binaryContent": "Arquivo binário (conteúdo não pode ser exibido)",
   "session.messages.renderEarlier": "Renderizar mensagens anteriores",
   "session.messages.loadingEarlier": "Carregando mensagens anteriores...",
@@ -459,6 +470,17 @@ export const dict = {
   "session.todo.title": "Tarefas",
   "session.todo.collapse": "Recolher",
   "session.todo.expand": "Expandir",
+  "session.followupDock.summary.one": "{{count}} mensagem na fila",
+  "session.followupDock.summary.other": "{{count}} mensagens na fila",
+  "session.followupDock.sendNow": "Enviar agora",
+  "session.followupDock.edit": "Editar",
+  "session.followupDock.collapse": "Recolher mensagens na fila",
+  "session.followupDock.expand": "Expandir mensagens na fila",
+  "session.revertDock.summary.one": "{{count}} mensagem revertida",
+  "session.revertDock.summary.other": "{{count}} mensagens revertidas",
+  "session.revertDock.collapse": "Recolher mensagens revertidas",
+  "session.revertDock.expand": "Expandir mensagens revertidas",
+  "session.revertDock.restore": "Restaurar mensagem",
   "session.new.title": "Crie qualquer coisa",
   "session.new.worktree.main": "Branch principal",
   "session.new.worktree.mainWithBranch": "Branch principal ({{branch}})",
@@ -544,10 +566,19 @@ export const dict = {
   "settings.general.row.language.description": "Alterar o idioma de exibição do OpenCode",
   "settings.general.row.appearance.title": "Aparência",
   "settings.general.row.appearance.description": "Personalize como o OpenCode aparece no seu dispositivo",
+  "settings.general.row.colorScheme.title": "Esquema de cores",
+  "settings.general.row.colorScheme.description": "Escolha se o OpenCode segue o tema do sistema, claro ou escuro",
   "settings.general.row.theme.title": "Tema",
   "settings.general.row.theme.description": "Personalize como o OpenCode é tematizado.",
   "settings.general.row.font.title": "Fonte",
   "settings.general.row.font.description": "Personalize a fonte monoespaçada usada em blocos de código",
+  "settings.general.row.followup.title": "Comportamento de acompanhamento",
+  "settings.general.row.followup.description":
+    "Escolha se os prompts de acompanhamento orientam imediatamente ou esperam na fila",
+  "settings.general.row.followup.option.queue": "Fila",
+  "settings.general.row.followup.option.steer": "Orientar",
+  "settings.general.row.reasoningSummaries.title": "Mostrar resumos de raciocínio",
+  "settings.general.row.reasoningSummaries.description": "Exibir resumos de raciocínio do modelo na linha do tempo",
   "settings.general.row.shellToolPartsExpanded.title": "Expandir partes da ferramenta shell",
   "settings.general.row.shellToolPartsExpanded.description":
     "Mostrar partes da ferramenta shell expandidas por padrão na linha do tempo",

+ 30 - 0
packages/app/src/i18n/bs.ts

@@ -113,6 +113,7 @@ export const dict = {
   "dialog.model.empty": "Nema rezultata za modele",
   "dialog.model.manage": "Upravljaj modelima",
   "dialog.model.manage.description": "Prilagodi koji se modeli prikazuju u izborniku modela.",
+  "dialog.model.manage.provider.toggle": "Uključi/isključi sve {{provider}} modele",
 
   "dialog.model.unpaid.freeModels.title": "Besplatni modeli koje obezbjeđuje OpenCode",
   "dialog.model.unpaid.addMore.title": "Dodaj još modela od popularnih provajdera",
@@ -315,6 +316,11 @@ export const dict = {
   "dialog.server.add.error": "Nije moguće povezati se na server",
   "dialog.server.add.checking": "Provjera...",
   "dialog.server.add.button": "Dodaj server",
+  "dialog.server.add.name": "Ime servera (opcionalno)",
+  "dialog.server.add.namePlaceholder": "Localhost",
+  "dialog.server.add.username": "Korisničko ime (opcionalno)",
+  "dialog.server.add.password": "Lozinka (opcionalno)",
+  "dialog.server.edit.title": "Uredi server",
   "dialog.server.default.title": "Podrazumijevani server",
   "dialog.server.default.description":
     "Poveži se na ovaj server pri pokretanju aplikacije umjesto pokretanja lokalnog servera. Potreban je restart.",
@@ -393,6 +399,7 @@ export const dict = {
   "language.br": "Português (Brasil)",
   "language.bs": "Bosanski",
   "language.th": "ไทย",
+  "language.tr": "Türkçe",
 
   "toast.language.title": "Jezik",
   "toast.language.description": "Prebačeno na {{language}}",
@@ -498,10 +505,14 @@ export const dict = {
   "session.review.change.other": "Izmjene",
   "session.review.loadingChanges": "Učitavanje izmjena...",
   "session.review.empty": "Još nema izmjena u ovoj sesiji",
+  "session.review.noVcs": "Nije detektovan Git sistem kontrole verzija, promjene se ne prikazuju",
+  "session.review.noSnapshot":
+    "Praćenje snimaka (snapshot) je onemogućeno u konfiguraciji, pa promjene sesije nisu dostupne",
   "session.review.noChanges": "Nema izmjena",
 
   "session.files.selectToOpen": "Odaberi datoteku za otvaranje",
   "session.files.all": "Sve datoteke",
+  "session.files.empty": "Nema datoteka",
   "session.files.binaryContent": "Binarna datoteka (sadržaj se ne može prikazati)",
 
   "session.messages.renderEarlier": "Prikaži ranije poruke",
@@ -514,6 +525,17 @@ export const dict = {
   "session.todo.title": "Zadaci",
   "session.todo.collapse": "Sažmi",
   "session.todo.expand": "Proširi",
+  "session.followupDock.summary.one": "{{count}} poruka na čekanju",
+  "session.followupDock.summary.other": "{{count}} poruka na čekanju",
+  "session.followupDock.sendNow": "Pošalji sada",
+  "session.followupDock.edit": "Uredi",
+  "session.followupDock.collapse": "Sažmi poruke na čekanju",
+  "session.followupDock.expand": "Proširi poruke na čekanju",
+  "session.revertDock.summary.one": "{{count}} vraćena poruka",
+  "session.revertDock.summary.other": "{{count}} vraćenih poruka",
+  "session.revertDock.collapse": "Sažmi vraćene poruke",
+  "session.revertDock.expand": "Proširi vraćene poruke",
+  "session.revertDock.restore": "Vrati poruku",
 
   "session.new.title": "Napravi bilo šta",
   "session.new.worktree.main": "Glavna grana",
@@ -609,10 +631,18 @@ export const dict = {
   "settings.general.row.language.description": "Promijeni jezik prikaza u OpenCode-u",
   "settings.general.row.appearance.title": "Izgled",
   "settings.general.row.appearance.description": "Prilagodi kako OpenCode izgleda na tvom uređaju",
+  "settings.general.row.colorScheme.title": "Šema boja",
+  "settings.general.row.colorScheme.description": "Odaberi da li OpenCode prati sistemsku, svijetlu ili tamnu temu",
   "settings.general.row.theme.title": "Tema",
   "settings.general.row.theme.description": "Prilagodi temu OpenCode-a.",
   "settings.general.row.font.title": "Font",
   "settings.general.row.font.description": "Prilagodi monospace font koji se koristi u blokovima koda",
+  "settings.general.row.followup.title": "Ponašanje nadovezivanja",
+  "settings.general.row.followup.description": "Odaberi da li upiti nadovezivanja usmjeravaju odmah ili čekaju u redu",
+  "settings.general.row.followup.option.queue": "Red čekanja",
+  "settings.general.row.followup.option.steer": "Usmjeri",
+  "settings.general.row.reasoningSummaries.title": "Prikaži sažetke rasuđivanja",
+  "settings.general.row.reasoningSummaries.description": "Prikaži sažetke rasuđivanja modela na vremenskoj traci",
 
   "settings.general.row.shellToolPartsExpanded.title": "Proširi dijelove shell alata",
   "settings.general.row.shellToolPartsExpanded.description":

+ 30 - 0
packages/app/src/i18n/da.ts

@@ -113,6 +113,7 @@ export const dict = {
   "dialog.model.empty": "Ingen modeller fundet",
   "dialog.model.manage": "Administrer modeller",
   "dialog.model.manage.description": "Tilpas hvilke modeller der vises i modelvælgeren.",
+  "dialog.model.manage.provider.toggle": "Skift alle {{provider}}-modeller",
 
   "dialog.model.unpaid.freeModels.title": "Gratis modeller leveret af OpenCode",
   "dialog.model.unpaid.addMore.title": "Tilføj flere modeller fra populære udbydere",
@@ -313,6 +314,11 @@ export const dict = {
   "dialog.server.add.error": "Kunne ikke forbinde til server",
   "dialog.server.add.checking": "Tjekker...",
   "dialog.server.add.button": "Tilføj server",
+  "dialog.server.add.name": "Servernavn (valgfrit)",
+  "dialog.server.add.namePlaceholder": "Localhost",
+  "dialog.server.add.username": "Brugernavn (valgfrit)",
+  "dialog.server.add.password": "Adgangskode (valgfrit)",
+  "dialog.server.edit.title": "Rediger server",
   "dialog.server.default.title": "Standardserver",
   "dialog.server.default.description":
     "Forbind til denne server ved start af app i stedet for at starte en lokal server. Kræver genstart.",
@@ -391,6 +397,7 @@ export const dict = {
   "language.br": "Português (Brasil)",
   "language.bs": "Bosanski",
   "language.th": "ไทย",
+  "language.tr": "Türkçe",
 
   "toast.language.title": "Sprog",
   "toast.language.description": "Skiftede til {{language}}",
@@ -495,9 +502,13 @@ export const dict = {
   "session.review.change.other": "Ændringer",
   "session.review.loadingChanges": "Indlæser ændringer...",
   "session.review.empty": "Ingen ændringer i denne session endnu",
+  "session.review.noVcs": "Intet Git versionsstyringssystem fundet, ændringer vises ikke",
+  "session.review.noSnapshot":
+    "Snapshot-sporing er deaktiveret i konfigurationen, så sessionsændringer er ikke tilgængelige",
   "session.review.noChanges": "Ingen ændringer",
   "session.files.selectToOpen": "Vælg en fil at åbne",
   "session.files.all": "Alle filer",
+  "session.files.empty": "Ingen filer",
   "session.files.binaryContent": "Binær fil (indhold kan ikke vises)",
   "session.messages.renderEarlier": "Vis tidligere beskeder",
   "session.messages.loadingEarlier": "Indlæser tidligere beskeder...",
@@ -509,6 +520,17 @@ export const dict = {
   "session.todo.title": "Opgaver",
   "session.todo.collapse": "Skjul",
   "session.todo.expand": "Udvid",
+  "session.followupDock.summary.one": "{{count}} besked i kø",
+  "session.followupDock.summary.other": "{{count}} beskeder i kø",
+  "session.followupDock.sendNow": "Send nu",
+  "session.followupDock.edit": "Rediger",
+  "session.followupDock.collapse": "Skjul beskeder i kø",
+  "session.followupDock.expand": "Udvid beskeder i kø",
+  "session.revertDock.summary.one": "{{count}} tilbagerullet besked",
+  "session.revertDock.summary.other": "{{count}} tilbagerullede beskeder",
+  "session.revertDock.collapse": "Skjul tilbagerullede beskeder",
+  "session.revertDock.expand": "Udvid tilbagerullede beskeder",
+  "session.revertDock.restore": "Gendan besked",
 
   "session.new.title": "Byg hvad som helst",
   "session.new.worktree.main": "Hovedgren",
@@ -604,10 +626,18 @@ export const dict = {
   "settings.general.row.language.description": "Ændr visningssproget for OpenCode",
   "settings.general.row.appearance.title": "Udseende",
   "settings.general.row.appearance.description": "Tilpas hvordan OpenCode ser ud på din enhed",
+  "settings.general.row.colorScheme.title": "Farveskema",
+  "settings.general.row.colorScheme.description": "Vælg om OpenCode følger systemets, lyst eller mørkt tema",
   "settings.general.row.theme.title": "Tema",
   "settings.general.row.theme.description": "Tilpas hvordan OpenCode er temabestemt.",
   "settings.general.row.font.title": "Skrifttype",
   "settings.general.row.font.description": "Tilpas mono-skrifttypen brugt i kodeblokke",
+  "settings.general.row.followup.title": "Opfølgningsadfærd",
+  "settings.general.row.followup.description": "Vælg om opfølgende forespørgsler skal styre straks eller vente i kø",
+  "settings.general.row.followup.option.queue": "Kø",
+  "settings.general.row.followup.option.steer": "Styr",
+  "settings.general.row.reasoningSummaries.title": "Vis tænkeoversigter",
+  "settings.general.row.reasoningSummaries.description": "Vis model tænkeoversigter i tidslinjen",
 
   "settings.general.row.shellToolPartsExpanded.title": "Udvid shell-værktøjsdele",
   "settings.general.row.shellToolPartsExpanded.description": "Vis shell-værktøjsdele udvidet som standard i tidslinjen",

+ 33 - 0
packages/app/src/i18n/de.ts

@@ -108,6 +108,7 @@ export const dict = {
   "dialog.model.empty": "Keine Modellergebnisse",
   "dialog.model.manage": "Modelle verwalten",
   "dialog.model.manage.description": "Anpassen, welche Modelle in der Modellauswahl erscheinen.",
+  "dialog.model.manage.provider.toggle": "Alle {{provider}}-Modelle umschalten",
   "dialog.model.unpaid.freeModels.title": "Kostenlose Modelle von OpenCode",
   "dialog.model.unpaid.addMore.title": "Weitere Modelle von beliebten Anbietern hinzufügen",
   "dialog.provider.viewAll": "Mehr Anbieter anzeigen",
@@ -294,6 +295,11 @@ export const dict = {
   "dialog.server.add.error": "Verbindung zum Server fehlgeschlagen",
   "dialog.server.add.checking": "Prüfen...",
   "dialog.server.add.button": "Server hinzufügen",
+  "dialog.server.add.name": "Servername (optional)",
+  "dialog.server.add.namePlaceholder": "Localhost",
+  "dialog.server.add.username": "Benutzername (optional)",
+  "dialog.server.add.password": "Passwort (optional)",
+  "dialog.server.edit.title": "Server bearbeiten",
   "dialog.server.default.title": "Standardserver",
   "dialog.server.default.description":
     "Beim App-Start mit diesem Server verbinden, anstatt einen lokalen Server zu starten. Erfordert Neustart.",
@@ -366,6 +372,7 @@ export const dict = {
   "language.br": "Português (Brasil)",
   "language.bs": "Bosanski",
   "language.th": "ไทย",
+  "language.tr": "Türkçe",
   "toast.language.title": "Sprache",
   "toast.language.description": "Zu {{language}} gewechselt",
   "toast.theme.title": "Thema gewechselt",
@@ -454,9 +461,13 @@ export const dict = {
   "session.review.change.other": "Änderungen",
   "session.review.loadingChanges": "Lade Änderungen...",
   "session.review.empty": "Noch keine Änderungen in dieser Sitzung",
+  "session.review.noVcs": "Kein Git-Versionskontrollsystem erkannt, Änderungen werden nicht angezeigt",
+  "session.review.noSnapshot":
+    "Snapshot-Tracking ist in der Konfiguration deaktiviert, daher sind Sitzungsänderungen nicht verfügbar",
   "session.review.noChanges": "Keine Änderungen",
   "session.files.selectToOpen": "Datei zum Öffnen auswählen",
   "session.files.all": "Alle Dateien",
+  "session.files.empty": "Keine Dateien",
   "session.files.binaryContent": "Binärdatei (Inhalt kann nicht angezeigt werden)",
   "session.messages.renderEarlier": "Frühere Nachrichten rendern",
   "session.messages.loadingEarlier": "Lade frühere Nachrichten...",
@@ -467,6 +478,17 @@ export const dict = {
   "session.todo.title": "Aufgaben",
   "session.todo.collapse": "Einklappen",
   "session.todo.expand": "Ausklappen",
+  "session.followupDock.summary.one": "{{count}} Nachricht in der Warteschlange",
+  "session.followupDock.summary.other": "{{count}} Nachrichten in der Warteschlange",
+  "session.followupDock.sendNow": "Jetzt senden",
+  "session.followupDock.edit": "Bearbeiten",
+  "session.followupDock.collapse": "Warteschlange einklappen",
+  "session.followupDock.expand": "Warteschlange ausklappen",
+  "session.revertDock.summary.one": "{{count}} zurückgesetzte Nachricht",
+  "session.revertDock.summary.other": "{{count}} zurückgesetzte Nachrichten",
+  "session.revertDock.collapse": "Zurückgesetzte Nachrichten einklappen",
+  "session.revertDock.expand": "Zurückgesetzte Nachrichten ausklappen",
+  "session.revertDock.restore": "Nachricht wiederherstellen",
   "session.new.title": "Baue, was du willst",
   "session.new.worktree.main": "Haupt-Branch",
   "session.new.worktree.mainWithBranch": "Haupt-Branch ({{branch}})",
@@ -553,10 +575,21 @@ export const dict = {
   "settings.general.row.language.description": "Die Anzeigesprache für OpenCode ändern",
   "settings.general.row.appearance.title": "Erscheinungsbild",
   "settings.general.row.appearance.description": "Anpassen, wie OpenCode auf Ihrem Gerät aussieht",
+  "settings.general.row.colorScheme.title": "Farbschema",
+  "settings.general.row.colorScheme.description":
+    "Wählen Sie, ob OpenCode dem System-, hellen oder dunklen Thema folgt",
   "settings.general.row.theme.title": "Thema",
   "settings.general.row.theme.description": "Das Thema von OpenCode anpassen.",
   "settings.general.row.font.title": "Schriftart",
   "settings.general.row.font.description": "Die in Codeblöcken verwendete Monospace-Schriftart anpassen",
+  "settings.general.row.followup.title": "Verhalten bei Folgefragen",
+  "settings.general.row.followup.description":
+    "Wählen Sie, ob Folgefragen sofort steuern oder in einer Warteschlange warten",
+  "settings.general.row.followup.option.queue": "Warteschlange",
+  "settings.general.row.followup.option.steer": "Steuern",
+  "settings.general.row.reasoningSummaries.title": "Reasoning-Zusammenfassungen anzeigen",
+  "settings.general.row.reasoningSummaries.description":
+    "Zusammenfassungen des Modell-Reasonings in der Timeline anzeigen",
   "settings.general.row.shellToolPartsExpanded.title": "Shell-Tool-Abschnitte ausklappen",
   "settings.general.row.shellToolPartsExpanded.description":
     "Shell-Tool-Abschnitte standardmäßig in der Timeline ausgeklappt anzeigen",

+ 12 - 0
packages/app/src/i18n/en.ts

@@ -530,6 +530,12 @@ export const dict = {
   "session.todo.title": "Todos",
   "session.todo.collapse": "Collapse",
   "session.todo.expand": "Expand",
+  "session.followupDock.summary.one": "{{count}} queued message",
+  "session.followupDock.summary.other": "{{count}} queued messages",
+  "session.followupDock.sendNow": "Send now",
+  "session.followupDock.edit": "Edit",
+  "session.followupDock.collapse": "Collapse queued messages",
+  "session.followupDock.expand": "Expand queued messages",
   "session.revertDock.summary.one": "{{count}} rolled back message",
   "session.revertDock.summary.other": "{{count}} rolled back messages",
   "session.revertDock.collapse": "Collapse rolled back messages",
@@ -638,10 +644,16 @@ export const dict = {
   "settings.general.row.language.description": "Change the display language for OpenCode",
   "settings.general.row.appearance.title": "Appearance",
   "settings.general.row.appearance.description": "Customise how OpenCode looks on your device",
+  "settings.general.row.colorScheme.title": "Color scheme",
+  "settings.general.row.colorScheme.description": "Choose whether OpenCode follows the system, light, or dark theme",
   "settings.general.row.theme.title": "Theme",
   "settings.general.row.theme.description": "Customise how OpenCode is themed.",
   "settings.general.row.font.title": "Font",
   "settings.general.row.font.description": "Customise the mono font used in code blocks",
+  "settings.general.row.followup.title": "Follow-up behavior",
+  "settings.general.row.followup.description": "Choose whether follow-up prompts steer immediately or wait in a queue",
+  "settings.general.row.followup.option.queue": "Queue",
+  "settings.general.row.followup.option.steer": "Steer",
   "settings.general.row.reasoningSummaries.title": "Show reasoning summaries",
   "settings.general.row.reasoningSummaries.description": "Display model reasoning summaries in the timeline",
   "settings.general.row.shellToolPartsExpanded.title": "Expand shell tool parts",

+ 32 - 1
packages/app/src/i18n/es.ts

@@ -113,6 +113,7 @@ export const dict = {
   "dialog.model.empty": "Sin resultados de modelos",
   "dialog.model.manage": "Gestionar modelos",
   "dialog.model.manage.description": "Personalizar qué modelos aparecen en el selector de modelos.",
+  "dialog.model.manage.provider.toggle": "Alternar todos los modelos de {{provider}}",
 
   "dialog.model.unpaid.freeModels.title": "Modelos gratuitos proporcionados por OpenCode",
   "dialog.model.unpaid.addMore.title": "Añadir más modelos de proveedores populares",
@@ -314,6 +315,11 @@ export const dict = {
   "dialog.server.add.error": "No se pudo conectar al servidor",
   "dialog.server.add.checking": "Comprobando...",
   "dialog.server.add.button": "Añadir servidor",
+  "dialog.server.add.name": "Nombre del servidor (opcional)",
+  "dialog.server.add.namePlaceholder": "Localhost",
+  "dialog.server.add.username": "Nombre de usuario (opcional)",
+  "dialog.server.add.password": "Contraseña (opcional)",
+  "dialog.server.edit.title": "Editar servidor",
   "dialog.server.default.title": "Servidor predeterminado",
   "dialog.server.default.description":
     "Conectar a este servidor al iniciar la app en lugar de iniciar un servidor local. Requiere reinicio.",
@@ -393,6 +399,7 @@ export const dict = {
   "language.br": "Português (Brasil)",
   "language.bs": "Bosanski",
   "language.th": "ไทย",
+  "language.tr": "Türkçe",
 
   "toast.language.title": "Idioma",
   "toast.language.description": "Cambiado a {{language}}",
@@ -499,10 +506,14 @@ export const dict = {
   "session.review.change.other": "Cambios",
   "session.review.loadingChanges": "Cargando cambios...",
   "session.review.empty": "No hay cambios en esta sesión aún",
+  "session.review.noVcs": "No se detectó Sistema de Control de Versiones Git, los cambios no se muestran",
+  "session.review.noSnapshot":
+    "El seguimiento de instantáneas está deshabilitado en la configuración, por lo que los cambios de sesión no están disponibles",
   "session.review.noChanges": "Sin cambios",
 
   "session.files.selectToOpen": "Selecciona un archivo para abrir",
   "session.files.all": "Todos los archivos",
+  "session.files.empty": "Sin archivos",
   "session.files.binaryContent": "Archivo binario (el contenido no puede ser mostrado)",
 
   "session.messages.renderEarlier": "Renderizar mensajes anteriores",
@@ -515,6 +526,17 @@ export const dict = {
   "session.todo.title": "Tareas",
   "session.todo.collapse": "Contraer",
   "session.todo.expand": "Expandir",
+  "session.followupDock.summary.one": "{{count}} mensaje en cola",
+  "session.followupDock.summary.other": "{{count}} mensajes en cola",
+  "session.followupDock.sendNow": "Enviar ahora",
+  "session.followupDock.edit": "Editar",
+  "session.followupDock.collapse": "Contraer mensajes en cola",
+  "session.followupDock.expand": "Expandir mensajes en cola",
+  "session.revertDock.summary.one": "{{count}} mensaje revertido",
+  "session.revertDock.summary.other": "{{count}} mensajes revertidos",
+  "session.revertDock.collapse": "Contraer mensajes revertidos",
+  "session.revertDock.expand": "Expandir mensajes revertidos",
+  "session.revertDock.restore": "Restaurar mensaje",
 
   "session.new.title": "Construye lo que quieras",
   "session.new.worktree.main": "Rama principal",
@@ -612,11 +634,20 @@ export const dict = {
   "settings.general.row.language.description": "Cambiar el idioma de visualización para OpenCode",
   "settings.general.row.appearance.title": "Apariencia",
   "settings.general.row.appearance.description": "Personaliza cómo se ve OpenCode en tu dispositivo",
+  "settings.general.row.colorScheme.title": "Esquema de color",
+  "settings.general.row.colorScheme.description": "Elige si OpenCode sigue el tema del sistema, claro u oscuro",
   "settings.general.row.theme.title": "Tema",
   "settings.general.row.theme.description": "Personaliza el tema de OpenCode.",
   "settings.general.row.font.title": "Fuente",
   "settings.general.row.font.description": "Personaliza la fuente monoespaciada usada en bloques de código",
-
+  "settings.general.row.followup.title": "Comportamiento de seguimiento",
+  "settings.general.row.followup.description":
+    "Elige si los prompts de seguimiento se dirigen inmediatamente o esperan en una cola",
+  "settings.general.row.followup.option.queue": "Cola",
+  "settings.general.row.followup.option.steer": "Dirigir",
+  "settings.general.row.reasoningSummaries.title": "Mostrar resúmenes de razonamiento",
+  "settings.general.row.reasoningSummaries.description":
+    "Mostrar resúmenes del razonamiento del modelo en la línea de tiempo",
   "settings.general.row.shellToolPartsExpanded.title": "Expandir partes de la herramienta shell",
   "settings.general.row.shellToolPartsExpanded.description":
     "Mostrar las partes de la herramienta shell expandidas por defecto en la línea de tiempo",

+ 32 - 0
packages/app/src/i18n/fr.ts

@@ -104,6 +104,7 @@ export const dict = {
   "dialog.model.empty": "Aucun résultat de modèle",
   "dialog.model.manage": "Gérer les modèles",
   "dialog.model.manage.description": "Personnalisez les modèles qui apparaissent dans le sélecteur.",
+  "dialog.model.manage.provider.toggle": "Basculer tous les modèles {{provider}}",
   "dialog.model.unpaid.freeModels.title": "Modèles gratuits fournis par OpenCode",
   "dialog.model.unpaid.addMore.title": "Ajouter plus de modèles de fournisseurs populaires",
   "dialog.provider.viewAll": "Voir plus de fournisseurs",
@@ -288,6 +289,11 @@ export const dict = {
   "dialog.server.add.error": "Impossible de se connecter au serveur",
   "dialog.server.add.checking": "Vérification...",
   "dialog.server.add.button": "Ajouter un serveur",
+  "dialog.server.add.name": "Nom du serveur (optionnel)",
+  "dialog.server.add.namePlaceholder": "Localhost",
+  "dialog.server.add.username": "Nom d'utilisateur (optionnel)",
+  "dialog.server.add.password": "Mot de passe (optionnel)",
+  "dialog.server.edit.title": "Modifier le serveur",
   "dialog.server.default.title": "Serveur par défaut",
   "dialog.server.default.description":
     "Se connecter à ce serveur au lancement de l'application au lieu de démarrer un serveur local. Nécessite un redémarrage.",
@@ -360,6 +366,7 @@ export const dict = {
   "language.br": "Português (Brasil)",
   "language.bs": "Bosanski",
   "language.th": "ไทย",
+  "language.tr": "Türkçe",
   "toast.language.title": "Langue",
   "toast.language.description": "Passé à {{language}}",
   "toast.theme.title": "Thème changé",
@@ -451,8 +458,12 @@ export const dict = {
   "session.review.loadingChanges": "Chargement des modifications...",
   "session.review.empty": "Aucune modification dans cette session pour l'instant",
   "session.review.noChanges": "Aucune modification",
+  "session.review.noVcs": "Aucun système de contrôle de version Git détecté, modifications non affichées",
+  "session.review.noSnapshot":
+    "Le suivi des instantanés est désactivé dans la configuration, les modifications de session sont donc indisponibles",
   "session.files.selectToOpen": "Sélectionnez un fichier à ouvrir",
   "session.files.all": "Tous les fichiers",
+  "session.files.empty": "Aucun fichier",
   "session.files.binaryContent": "Fichier binaire (le contenu ne peut pas être affiché)",
   "session.messages.renderEarlier": "Afficher les messages précédents",
   "session.messages.loadingEarlier": "Chargement des messages précédents...",
@@ -463,6 +474,17 @@ export const dict = {
   "session.todo.title": "Tâches",
   "session.todo.collapse": "Réduire",
   "session.todo.expand": "Développer",
+  "session.followupDock.summary.one": "{{count}} message en file d'attente",
+  "session.followupDock.summary.other": "{{count}} messages en file d'attente",
+  "session.followupDock.sendNow": "Envoyer maintenant",
+  "session.followupDock.edit": "Modifier",
+  "session.followupDock.collapse": "Réduire les messages en file d'attente",
+  "session.followupDock.expand": "Développer les messages en file d'attente",
+  "session.revertDock.summary.one": "{{count}} message annulé",
+  "session.revertDock.summary.other": "{{count}} messages annulés",
+  "session.revertDock.collapse": "Réduire les messages annulés",
+  "session.revertDock.expand": "Développer les messages annulés",
+  "session.revertDock.restore": "Restaurer le message",
   "session.new.title": "Créez ce que vous voulez",
   "session.new.worktree.main": "Branche principale",
   "session.new.worktree.mainWithBranch": "Branche principale ({{branch}})",
@@ -550,10 +572,20 @@ export const dict = {
   "settings.general.row.language.description": "Changer la langue d'affichage pour OpenCode",
   "settings.general.row.appearance.title": "Apparence",
   "settings.general.row.appearance.description": "Personnaliser l'apparence d'OpenCode sur votre appareil",
+  "settings.general.row.colorScheme.title": "Schéma de couleurs",
+  "settings.general.row.colorScheme.description": "Choisissez si OpenCode suit le thème système, clair ou sombre",
   "settings.general.row.theme.title": "Thème",
   "settings.general.row.theme.description": "Personnaliser le thème d'OpenCode.",
   "settings.general.row.font.title": "Police",
   "settings.general.row.font.description": "Personnaliser la police mono utilisée dans les blocs de code",
+  "settings.general.row.followup.title": "Comportement de suivi",
+  "settings.general.row.followup.description":
+    "Choisissez si les messages de suivi dirigent immédiatement ou attendent dans une file d'attente",
+  "settings.general.row.followup.option.queue": "File d'attente",
+  "settings.general.row.followup.option.steer": "Diriger",
+  "settings.general.row.reasoningSummaries.title": "Afficher les résumés de raisonnement",
+  "settings.general.row.reasoningSummaries.description":
+    "Afficher les résumés de raisonnement du modèle dans la chronologie",
   "settings.general.row.shellToolPartsExpanded.title": "Développer les parties de l'outil shell",
   "settings.general.row.shellToolPartsExpanded.description":
     "Afficher les parties de l'outil shell développées par défaut dans la chronologie",

+ 30 - 0
packages/app/src/i18n/ja.ts

@@ -104,6 +104,7 @@ export const dict = {
   "dialog.model.empty": "モデルが見つかりません",
   "dialog.model.manage": "モデルを管理",
   "dialog.model.manage.description": "モデルセレクターに表示するモデルをカスタマイズします。",
+  "dialog.model.manage.provider.toggle": "すべての{{provider}}モデルを切り替え",
   "dialog.model.unpaid.freeModels.title": "OpenCodeが提供する無料モデル",
   "dialog.model.unpaid.addMore.title": "人気のプロバイダーからモデルを追加",
   "dialog.provider.viewAll": "さらにプロバイダーを表示",
@@ -287,6 +288,11 @@ export const dict = {
   "dialog.server.add.error": "サーバーに接続できませんでした",
   "dialog.server.add.checking": "確認中...",
   "dialog.server.add.button": "サーバーを追加",
+  "dialog.server.add.name": "サーバー名 (オプション)",
+  "dialog.server.add.namePlaceholder": "Localhost",
+  "dialog.server.add.username": "ユーザー名 (オプション)",
+  "dialog.server.add.password": "パスワード (オプション)",
+  "dialog.server.edit.title": "サーバーを編集",
   "dialog.server.default.title": "デフォルトサーバー",
   "dialog.server.default.description":
     "ローカルサーバーを起動する代わりに、アプリ起動時にこのサーバーに接続します。再起動が必要です。",
@@ -358,6 +364,7 @@ export const dict = {
   "language.br": "Português (Brasil)",
   "language.bs": "Bosanski",
   "language.th": "ไทย",
+  "language.tr": "Türkçe",
   "toast.language.title": "言語",
   "toast.language.description": "{{language}}に切り替えました",
   "toast.theme.title": "テーマが切り替わりました",
@@ -444,9 +451,12 @@ export const dict = {
   "session.review.change.other": "変更",
   "session.review.loadingChanges": "変更を読み込み中...",
   "session.review.empty": "このセッションでの変更はまだありません",
+  "session.review.noVcs": "Gitバージョン管理システムが検出されないため、変更は表示されません",
+  "session.review.noSnapshot": "設定でスナップショット追跡が無効になっているため、セッションの変更は利用できません",
   "session.review.noChanges": "変更なし",
   "session.files.selectToOpen": "開くファイルを選択",
   "session.files.all": "すべてのファイル",
+  "session.files.empty": "ファイルなし",
   "session.files.binaryContent": "バイナリファイル(内容を表示できません)",
   "session.messages.renderEarlier": "以前のメッセージを表示",
   "session.messages.loadingEarlier": "以前のメッセージを読み込み中...",
@@ -457,6 +467,17 @@ export const dict = {
   "session.todo.title": "ToDo",
   "session.todo.collapse": "折りたたむ",
   "session.todo.expand": "展開",
+  "session.followupDock.summary.one": "{{count}} 件のメッセージが待機中",
+  "session.followupDock.summary.other": "{{count}} 件のメッセージが待機中",
+  "session.followupDock.sendNow": "今すぐ送信",
+  "session.followupDock.edit": "編集",
+  "session.followupDock.collapse": "待機中のメッセージを折りたたむ",
+  "session.followupDock.expand": "待機中のメッセージを展開",
+  "session.revertDock.summary.one": "{{count}} 件のロールバックされたメッセージ",
+  "session.revertDock.summary.other": "{{count}} 件のロールバックされたメッセージ",
+  "session.revertDock.collapse": "ロールバックされたメッセージを折りたたむ",
+  "session.revertDock.expand": "ロールバックされたメッセージを展開",
+  "session.revertDock.restore": "メッセージを復元",
   "session.new.title": "何でも作る",
   "session.new.worktree.main": "メインブランチ",
   "session.new.worktree.mainWithBranch": "メインブランチ ({{branch}})",
@@ -542,10 +563,19 @@ export const dict = {
   "settings.general.row.language.description": "OpenCodeの表示言語を変更します",
   "settings.general.row.appearance.title": "外観",
   "settings.general.row.appearance.description": "デバイスでのOpenCodeの表示をカスタマイズします",
+  "settings.general.row.colorScheme.title": "配色",
+  "settings.general.row.colorScheme.description": "OpenCodeがシステム、ライト、またはダークテーマに従うかを選択します",
   "settings.general.row.theme.title": "テーマ",
   "settings.general.row.theme.description": "OpenCodeのテーマをカスタマイズします。",
   "settings.general.row.font.title": "フォント",
   "settings.general.row.font.description": "コードブロックで使用する等幅フォントをカスタマイズします",
+  "settings.general.row.followup.title": "フォローアップの動作",
+  "settings.general.row.followup.description":
+    "フォローアッププロンプトを即座に実行するか、キューで待機させるかを選択します",
+  "settings.general.row.followup.option.queue": "キューに追加",
+  "settings.general.row.followup.option.steer": "即座に実行 (Steer)",
+  "settings.general.row.reasoningSummaries.title": "推論の要約を表示",
+  "settings.general.row.reasoningSummaries.description": "タイムラインにモデルの推論の要約を表示します",
   "settings.general.row.shellToolPartsExpanded.title": "shell ツールパーツを展開",
   "settings.general.row.shellToolPartsExpanded.description":
     "タイムラインで shell ツールパーツをデフォルトで展開して表示します",

+ 29 - 0
packages/app/src/i18n/ko.ts

@@ -108,6 +108,7 @@ export const dict = {
   "dialog.model.empty": "모델 결과 없음",
   "dialog.model.manage": "모델 관리",
   "dialog.model.manage.description": "모델 선택기에 표시할 모델 사용자 지정",
+  "dialog.model.manage.provider.toggle": "모든 {{provider}} 모델 토글",
   "dialog.model.unpaid.freeModels.title": "OpenCode에서 제공하는 무료 모델",
   "dialog.model.unpaid.addMore.title": "인기 공급자의 모델 추가",
   "dialog.provider.viewAll": "더 많은 공급자 보기",
@@ -291,6 +292,11 @@ export const dict = {
   "dialog.server.add.error": "서버에 연결할 수 없습니다",
   "dialog.server.add.checking": "확인 중...",
   "dialog.server.add.button": "서버 추가",
+  "dialog.server.add.name": "서버 이름 (선택 사항)",
+  "dialog.server.add.namePlaceholder": "Localhost",
+  "dialog.server.add.username": "사용자 이름 (선택 사항)",
+  "dialog.server.add.password": "비밀번호 (선택 사항)",
+  "dialog.server.edit.title": "서버 편집",
   "dialog.server.default.title": "기본 서버",
   "dialog.server.default.description":
     "로컬 서버를 시작하는 대신 앱 실행 시 이 서버에 연결합니다. 다시 시작해야 합니다.",
@@ -361,6 +367,7 @@ export const dict = {
   "language.br": "Português (Brasil)",
   "language.bs": "Bosanski",
   "language.th": "ไทย",
+  "language.tr": "Türkçe",
   "toast.language.title": "언어",
   "toast.language.description": "{{language}}(으)로 전환됨",
   "toast.theme.title": "테마 전환됨",
@@ -446,9 +453,12 @@ export const dict = {
   "session.review.change.other": "변경",
   "session.review.loadingChanges": "변경 사항 로드 중...",
   "session.review.empty": "이 세션에 변경 사항이 아직 없습니다",
+  "session.review.noVcs": "Git 버전 관리 시스템이 감지되지 않아 변경 사항이 표시되지 않습니다",
+  "session.review.noSnapshot": "구성에서 스냅샷 추적이 비활성화되어 있어 세션 변경 사항을 사용할 수 없습니다",
   "session.review.noChanges": "변경 없음",
   "session.files.selectToOpen": "열 파일을 선택하세요",
   "session.files.all": "모든 파일",
+  "session.files.empty": "파일 없음",
   "session.files.binaryContent": "바이너리 파일 (내용을 표시할 수 없음)",
   "session.messages.renderEarlier": "이전 메시지 렌더링",
   "session.messages.loadingEarlier": "이전 메시지 로드 중...",
@@ -459,6 +469,17 @@ export const dict = {
   "session.todo.title": "할 일",
   "session.todo.collapse": "접기",
   "session.todo.expand": "펼치기",
+  "session.followupDock.summary.one": "{{count}}개의 대기 중인 메시지",
+  "session.followupDock.summary.other": "{{count}}개의 대기 중인 메시지",
+  "session.followupDock.sendNow": "지금 전송",
+  "session.followupDock.edit": "편집",
+  "session.followupDock.collapse": "대기 중인 메시지 접기",
+  "session.followupDock.expand": "대기 중인 메시지 펼치기",
+  "session.revertDock.summary.one": "{{count}}개의 롤백된 메시지",
+  "session.revertDock.summary.other": "{{count}}개의 롤백된 메시지",
+  "session.revertDock.collapse": "롤백된 메시지 접기",
+  "session.revertDock.expand": "롤백된 메시지 펼치기",
+  "session.revertDock.restore": "메시지 복원",
   "session.new.title": "무엇이든 만들기",
   "session.new.worktree.main": "메인 브랜치",
   "session.new.worktree.mainWithBranch": "메인 브랜치 ({{branch}})",
@@ -543,10 +564,18 @@ export const dict = {
   "settings.general.row.language.description": "OpenCode 표시 언어 변경",
   "settings.general.row.appearance.title": "모양",
   "settings.general.row.appearance.description": "기기에서 OpenCode가 보이는 방식 사용자 지정",
+  "settings.general.row.colorScheme.title": "색상 테마",
+  "settings.general.row.colorScheme.description": "OpenCode가 시스템, 라이트 또는 다크 테마를 따를지 선택하세요",
   "settings.general.row.theme.title": "테마",
   "settings.general.row.theme.description": "OpenCode 테마 사용자 지정",
   "settings.general.row.font.title": "글꼴",
   "settings.general.row.font.description": "코드 블록에 사용되는 고정폭 글꼴 사용자 지정",
+  "settings.general.row.followup.title": "후속 조치 동작",
+  "settings.general.row.followup.description": "후속 프롬프트를 즉시 실행할지 대기열에 넣을지 선택하세요",
+  "settings.general.row.followup.option.queue": "대기열",
+  "settings.general.row.followup.option.steer": "조종",
+  "settings.general.row.reasoningSummaries.title": "추론 요약 표시",
+  "settings.general.row.reasoningSummaries.description": "타임라인에 모델 추론 요약 표시",
   "settings.general.row.shellToolPartsExpanded.title": "shell 도구 파트 펼치기",
   "settings.general.row.shellToolPartsExpanded.description":
     "타임라인에서 기본적으로 shell 도구 파트를 펼친 상태로 표시합니다",

+ 31 - 2
packages/app/src/i18n/no.ts

@@ -116,6 +116,7 @@ export const dict = {
   "dialog.model.empty": "Ingen modellresultater",
   "dialog.model.manage": "Administrer modeller",
   "dialog.model.manage.description": "Tilpass hvilke modeller som vises i modellvelgeren.",
+  "dialog.model.manage.provider.toggle": "Veksle alle {{provider}}-modeller",
 
   "dialog.model.unpaid.freeModels.title": "Gratis modeller levert av OpenCode",
   "dialog.model.unpaid.addMore.title": "Legg til flere modeller fra populære leverandører",
@@ -216,7 +217,7 @@ export const dict = {
 
   "common.search.placeholder": "Søk",
   "common.goBack": "Gå tilbake",
-  "common.goForward": "Navigate forward",
+  "common.goForward": "Gå frem",
   "common.loading": "Laster",
   "common.loading.ellipsis": "...",
   "common.cancel": "Avbryt",
@@ -317,6 +318,11 @@ export const dict = {
   "dialog.server.add.error": "Kunne ikke koble til server",
   "dialog.server.add.checking": "Sjekker...",
   "dialog.server.add.button": "Legg til server",
+  "dialog.server.add.name": "Servernavn (valgfritt)",
+  "dialog.server.add.namePlaceholder": "Localhost",
+  "dialog.server.add.username": "Brukernavn (valgfritt)",
+  "dialog.server.add.password": "Passord (valgfritt)",
+  "dialog.server.edit.title": "Rediger server",
   "dialog.server.default.title": "Standardserver",
   "dialog.server.default.description":
     "Koble til denne serveren ved oppstart i stedet for å starte en lokal server. Krever omstart.",
@@ -394,6 +400,7 @@ export const dict = {
   "language.br": "Português (Brasil)",
   "language.bs": "Bosanski",
   "language.th": "ไทย",
+  "language.tr": "Türkçe",
 
   "toast.language.title": "Språk",
   "toast.language.description": "Byttet til {{language}}",
@@ -499,10 +506,14 @@ export const dict = {
   "session.review.change.other": "Endringer",
   "session.review.loadingChanges": "Laster endringer...",
   "session.review.empty": "Ingen endringer i denne sesjonen ennå",
+  "session.review.noVcs": "Ingen Git-versjonskontrollsystem oppdaget, endringer vises ikke",
+  "session.review.noSnapshot":
+    "Snapshot-sporing er deaktivert i konfigurasjonen, så sesjonsendringer er ikke tilgjengelige",
   "session.review.noChanges": "Ingen endringer",
 
   "session.files.selectToOpen": "Velg en fil å åpne",
   "session.files.all": "Alle filer",
+  "session.files.empty": "Ingen filer",
   "session.files.binaryContent": "Binær fil (innhold kan ikke vises)",
 
   "session.messages.renderEarlier": "Vis tidligere meldinger",
@@ -515,6 +526,17 @@ export const dict = {
   "session.todo.title": "Oppgaver",
   "session.todo.collapse": "Skjul",
   "session.todo.expand": "Utvid",
+  "session.followupDock.summary.one": "{{count}} melding i kø",
+  "session.followupDock.summary.other": "{{count}} meldinger i kø",
+  "session.followupDock.sendNow": "Send nå",
+  "session.followupDock.edit": "Rediger",
+  "session.followupDock.collapse": "Skjul meldinger i kø",
+  "session.followupDock.expand": "Utvid meldinger i kø",
+  "session.revertDock.summary.one": "{{count}} tilbakestilt melding",
+  "session.revertDock.summary.other": "{{count}} tilbakestilte meldinger",
+  "session.revertDock.collapse": "Skjul tilbakestilte meldinger",
+  "session.revertDock.expand": "Utvid tilbakestilte meldinger",
+  "session.revertDock.restore": "Gjenopprett melding",
 
   "session.new.title": "Bygg hva som helst",
   "session.new.worktree.main": "Hovedgren",
@@ -612,11 +634,18 @@ export const dict = {
   "settings.general.row.language.description": "Endre visningsspråket for OpenCode",
   "settings.general.row.appearance.title": "Utseende",
   "settings.general.row.appearance.description": "Tilpass hvordan OpenCode ser ut på enheten din",
+  "settings.general.row.colorScheme.title": "Fargevalg",
+  "settings.general.row.colorScheme.description": "Velg om OpenCode skal følge systemets, lyst eller mørkt tema",
   "settings.general.row.theme.title": "Tema",
   "settings.general.row.theme.description": "Tilpass hvordan OpenCode er tematisert.",
   "settings.general.row.font.title": "Skrift",
   "settings.general.row.font.description": "Tilpass mono-skriften som brukes i kodeblokker",
-
+  "settings.general.row.followup.title": "Oppfølgingsadferd",
+  "settings.general.row.followup.description": "Velg om oppfølgingsspørsmål skal kjøres umiddelbart eller vente i kø",
+  "settings.general.row.followup.option.queue": "Kø",
+  "settings.general.row.followup.option.steer": "Styr",
+  "settings.general.row.reasoningSummaries.title": "Vis resonneringssammendrag",
+  "settings.general.row.reasoningSummaries.description": "Vis sammendrag av modellresonnering i tidslinjen",
   "settings.general.row.shellToolPartsExpanded.title": "Utvid shell-verktøydeler",
   "settings.general.row.shellToolPartsExpanded.description": "Vis shell-verktøydeler utvidet som standard i tidslinjen",
   "settings.general.row.editToolPartsExpanded.title": "Utvid edit-verktøydeler",

+ 30 - 0
packages/app/src/i18n/pl.ts

@@ -104,6 +104,7 @@ export const dict = {
   "dialog.model.empty": "Brak wyników modelu",
   "dialog.model.manage": "Zarządzaj modelami",
   "dialog.model.manage.description": "Dostosuj, które modele pojawiają się w wyborze modelu.",
+  "dialog.model.manage.provider.toggle": "Przełącz wszystkie modele {{provider}}",
   "dialog.model.unpaid.freeModels.title": "Darmowe modele dostarczane przez OpenCode",
   "dialog.model.unpaid.addMore.title": "Dodaj więcej modeli od popularnych dostawców",
   "dialog.provider.viewAll": "Zobacz więcej dostawców",
@@ -289,6 +290,11 @@ export const dict = {
   "dialog.server.add.error": "Nie można połączyć się z serwerem",
   "dialog.server.add.checking": "Sprawdzanie...",
   "dialog.server.add.button": "Dodaj serwer",
+  "dialog.server.add.name": "Nazwa serwera (opcjonalnie)",
+  "dialog.server.add.namePlaceholder": "Localhost",
+  "dialog.server.add.username": "Nazwa użytkownika (opcjonalnie)",
+  "dialog.server.add.password": "Hasło (opcjonalnie)",
+  "dialog.server.edit.title": "Edytuj serwer",
   "dialog.server.default.title": "Domyślny serwer",
   "dialog.server.default.description":
     "Połącz z tym serwerem przy uruchomieniu aplikacji zamiast uruchamiać lokalny serwer. Wymaga restartu.",
@@ -359,6 +365,7 @@ export const dict = {
   "language.br": "Português (Brasil)",
   "language.bs": "Bosanski",
   "language.th": "ไทย",
+  "language.tr": "Türkçe",
   "toast.language.title": "Język",
   "toast.language.description": "Przełączono na {{language}}",
   "toast.theme.title": "Przełączono motyw",
@@ -445,9 +452,12 @@ export const dict = {
   "session.review.change.other": "Zmiany",
   "session.review.loadingChanges": "Ładowanie zmian...",
   "session.review.empty": "Brak zmian w tej sesji",
+  "session.review.noVcs": "Nie wykryto systemu kontroli wersji Git, zmiany nie są wyświetlane",
+  "session.review.noSnapshot": "Śledzenie migawek jest wyłączone w konfiguracji, więc zmiany w sesji są niedostępne",
   "session.review.noChanges": "Brak zmian",
   "session.files.selectToOpen": "Wybierz plik do otwarcia",
   "session.files.all": "Wszystkie pliki",
+  "session.files.empty": "Brak plików",
   "session.files.binaryContent": "Plik binarny (zawartość nie może być wyświetlona)",
   "session.messages.renderEarlier": "Renderuj wcześniejsze wiadomości",
   "session.messages.loadingEarlier": "Ładowanie wcześniejszych wiadomości...",
@@ -458,6 +468,17 @@ export const dict = {
   "session.todo.title": "Zadania",
   "session.todo.collapse": "Zwiń",
   "session.todo.expand": "Rozwiń",
+  "session.followupDock.summary.one": "{{count}} wiadomość w kolejce",
+  "session.followupDock.summary.other": "{{count}} wiadomości w kolejce",
+  "session.followupDock.sendNow": "Wyślij teraz",
+  "session.followupDock.edit": "Edytuj",
+  "session.followupDock.collapse": "Zwiń wiadomości w kolejce",
+  "session.followupDock.expand": "Rozwiń wiadomości w kolejce",
+  "session.revertDock.summary.one": "{{count}} cofnięta wiadomość",
+  "session.revertDock.summary.other": "{{count}} cofnięte wiadomości",
+  "session.revertDock.collapse": "Zwiń cofnięte wiadomości",
+  "session.revertDock.expand": "Rozwiń cofnięte wiadomości",
+  "session.revertDock.restore": "Przywróć wiadomość",
   "session.new.title": "Zbuduj cokolwiek",
   "session.new.worktree.main": "Główna gałąź",
   "session.new.worktree.mainWithBranch": "Główna gałąź ({{branch}})",
@@ -543,10 +564,19 @@ export const dict = {
   "settings.general.row.language.description": "Zmień język wyświetlania dla OpenCode",
   "settings.general.row.appearance.title": "Wygląd",
   "settings.general.row.appearance.description": "Dostosuj wygląd OpenCode na swoim urządzeniu",
+  "settings.general.row.colorScheme.title": "Schemat kolorów",
+  "settings.general.row.colorScheme.description":
+    "Wybierz, czy OpenCode ma używać motywu systemowego, jasnego czy ciemnego",
   "settings.general.row.theme.title": "Motyw",
   "settings.general.row.theme.description": "Dostosuj motyw OpenCode.",
   "settings.general.row.font.title": "Czcionka",
   "settings.general.row.font.description": "Dostosuj czcionkę mono używaną w blokach kodu",
+  "settings.general.row.followup.title": "Zachowanie kontynuacji",
+  "settings.general.row.followup.description": "Wybierz, czy kontynuacja ma być natychmiastowa, czy czekać w kolejce",
+  "settings.general.row.followup.option.queue": "Kolejka",
+  "settings.general.row.followup.option.steer": "Sterowanie",
+  "settings.general.row.reasoningSummaries.title": "Pokaż podsumowania wnioskowania",
+  "settings.general.row.reasoningSummaries.description": "Wyświetlaj podsumowania wnioskowania modelu na osi czasu",
   "settings.general.row.shellToolPartsExpanded.title": "Rozwijaj elementy narzędzia shell",
   "settings.general.row.shellToolPartsExpanded.description":
     "Domyślnie pokazuj rozwinięte elementy narzędzia shell na osi czasu",

+ 47 - 16
packages/app/src/i18n/ru.ts

@@ -113,6 +113,7 @@ export const dict = {
   "dialog.model.empty": "Модели не найдены",
   "dialog.model.manage": "Управление моделями",
   "dialog.model.manage.description": "Настройте какие модели появляются в выборе модели",
+  "dialog.model.manage.provider.toggle": "Переключить все модели {{provider}}",
 
   "dialog.model.unpaid.freeModels.title": "Бесплатные модели от OpenCode",
   "dialog.model.unpaid.addMore.title": "Добавьте больше моделей от популярных провайдеров",
@@ -314,6 +315,11 @@ export const dict = {
   "dialog.server.add.error": "Не удалось подключиться к серверу",
   "dialog.server.add.checking": "Проверка...",
   "dialog.server.add.button": "Добавить сервер",
+  "dialog.server.add.name": "Имя сервера (необязательно)",
+  "dialog.server.add.namePlaceholder": "Localhost",
+  "dialog.server.add.username": "Имя пользователя (необязательно)",
+  "dialog.server.add.password": "Пароль (необязательно)",
+  "dialog.server.edit.title": "Редактировать сервер",
   "dialog.server.default.title": "Сервер по умолчанию",
   "dialog.server.default.description":
     "Подключаться к этому серверу при запуске приложения вместо запуска локального сервера. Требуется перезапуск.",
@@ -393,6 +399,7 @@ export const dict = {
   "language.br": "Português (Brasil)",
   "language.bs": "Bosanski",
   "language.th": "ไทย",
+  "language.tr": "Türkçe",
 
   "toast.language.title": "Язык",
   "toast.language.description": "Переключено на {{language}}",
@@ -499,9 +506,12 @@ export const dict = {
   "session.review.change.other": "Изменения",
   "session.review.loadingChanges": "Загрузка изменений...",
   "session.review.empty": "Изменений в этой сессии пока нет",
+  "session.review.noVcs": "Система контроля версий Git не обнаружена, изменения не отображаются",
+  "session.review.noSnapshot": "Отслеживание снимков отключено в настройках, поэтому изменения сессии недоступны",
   "session.review.noChanges": "Нет изменений",
   "session.files.selectToOpen": "Выберите файл, чтобы открыть",
   "session.files.all": "Все файлы",
+  "session.files.empty": "Нет файлов",
   "session.files.binaryContent": "Двоичный файл (содержимое не может быть отображено)",
   "session.messages.renderEarlier": "Показать предыдущие сообщения",
   "session.messages.loadingEarlier": "Загрузка предыдущих сообщений...",
@@ -513,6 +523,17 @@ export const dict = {
   "session.todo.title": "Задачи",
   "session.todo.collapse": "Свернуть",
   "session.todo.expand": "Развернуть",
+  "session.followupDock.summary.one": "{{count}} сообщение в очереди",
+  "session.followupDock.summary.other": "{{count}} сообщений в очереди",
+  "session.followupDock.sendNow": "Отправить сейчас",
+  "session.followupDock.edit": "Редактировать",
+  "session.followupDock.collapse": "Свернуть сообщения в очереди",
+  "session.followupDock.expand": "Развернуть сообщения в очереди",
+  "session.revertDock.summary.one": "{{count}} сообщение возвращено",
+  "session.revertDock.summary.other": "{{count}} сообщений возвращено",
+  "session.revertDock.collapse": "Свернуть возвращённые сообщения",
+  "session.revertDock.expand": "Развернуть возвращённые сообщения",
+  "session.revertDock.restore": "Восстановить сообщение",
 
   "session.new.title": "Создавайте что угодно",
   "session.new.worktree.main": "Основная ветка",
@@ -610,10 +631,19 @@ export const dict = {
   "settings.general.row.language.description": "Изменить язык отображения OpenCode",
   "settings.general.row.appearance.title": "Внешний вид",
   "settings.general.row.appearance.description": "Настройте как OpenCode выглядит на вашем устройстве",
+  "settings.general.row.colorScheme.title": "Цветовая схема",
+  "settings.general.row.colorScheme.description": "Выберите, следует ли OpenCode системной, светлой или тёмной теме",
   "settings.general.row.theme.title": "Тема",
   "settings.general.row.theme.description": "Настройте оформление OpenCode.",
   "settings.general.row.font.title": "Шрифт",
   "settings.general.row.font.description": "Настройте моноширинный шрифт для блоков кода",
+  "settings.general.row.followup.title": "Поведение уточняющих вопросов",
+  "settings.general.row.followup.description":
+    "Выберите, отправлять ли уточняющие вопросы сразу или помещать их в очередь",
+  "settings.general.row.followup.option.queue": "Очередь",
+  "settings.general.row.followup.option.steer": "Направлять",
+  "settings.general.row.reasoningSummaries.title": "Показывать сводки рассуждений",
+  "settings.general.row.reasoningSummaries.description": "Отображать сводки рассуждений модели в ленте",
 
   "settings.general.row.shellToolPartsExpanded.title": "Разворачивать элементы инструмента shell",
   "settings.general.row.shellToolPartsExpanded.description":
@@ -767,30 +797,31 @@ export const dict = {
   "settings.permissions.tool.glob.description": "Сопоставление файлов по паттернам glob",
   "settings.permissions.tool.grep.title": "Grep",
   "settings.permissions.tool.grep.description": "Поиск по содержимому файлов с использованием регулярных выражений",
-  "settings.permissions.tool.list.title": "Список",
+  "settings.permissions.tool.list.title": "List",
   "settings.permissions.tool.list.description": "Список файлов в директории",
   "settings.permissions.tool.bash.title": "Bash",
-  "settings.permissions.tool.bash.description": "Выполнение команд оболочки",
+  "settings.permissions.tool.bash.description": "Запуск команд оболочки",
   "settings.permissions.tool.task.title": "Task",
-  "settings.permissions.tool.task.description": "Запуск под-агентов",
+  "settings.permissions.tool.task.description": "Запуск подагентов",
   "settings.permissions.tool.skill.title": "Skill",
-  "settings.permissions.tool.skill.description": "Загрузить навык по имени",
+  "settings.permissions.tool.skill.description": "Загрузка навыка по имени",
   "settings.permissions.tool.lsp.title": "LSP",
-  "settings.permissions.tool.lsp.description": "Выполнение запросов к языковому серверу",
-  "settings.permissions.tool.todoread.title": "Чтение списка задач",
+  "settings.permissions.tool.lsp.description": "Запросы к языковому серверу",
+  "settings.permissions.tool.todoread.title": "Todo Read",
   "settings.permissions.tool.todoread.description": "Чтение списка задач",
-  "settings.permissions.tool.todowrite.title": "Запись списка задач",
+  "settings.permissions.tool.todowrite.title": "Todo Write",
   "settings.permissions.tool.todowrite.description": "Обновление списка задач",
   "settings.permissions.tool.webfetch.title": "Web Fetch",
-  "settings.permissions.tool.webfetch.description": "Получить содержимое по URL",
+  "settings.permissions.tool.webfetch.description": "Получение контента по URL",
   "settings.permissions.tool.websearch.title": "Web Search",
   "settings.permissions.tool.websearch.description": "Поиск в интернете",
-  "settings.permissions.tool.codesearch.title": "Поиск кода",
+  "settings.permissions.tool.codesearch.title": "Code Search",
   "settings.permissions.tool.codesearch.description": "Поиск кода в интернете",
   "settings.permissions.tool.external_directory.title": "Внешняя директория",
   "settings.permissions.tool.external_directory.description": "Доступ к файлам вне директории проекта",
   "settings.permissions.tool.doom_loop.title": "Doom Loop",
-  "settings.permissions.tool.doom_loop.description": "Обнаружение повторных вызовов инструментов с одинаковым вводом",
+  "settings.permissions.tool.doom_loop.description":
+    "Обнаружение повторяющихся вызовов инструментов с одинаковыми входными данными",
 
   "session.delete.failed.title": "Не удалось удалить сессию",
   "session.delete.title": "Удалить сессию",
@@ -807,21 +838,21 @@ export const dict = {
   "workspace.reset.failed.title": "Не удалось сбросить рабочее пространство",
   "workspace.reset.success.title": "Рабочее пространство сброшено",
   "workspace.reset.success.description": "Рабочее пространство теперь соответствует ветке по умолчанию.",
-  "workspace.error.stillPreparing": "Рабочее пространство всё ещё готовится",
-  "workspace.status.checking": "Проверка наличия неслитых изменений...",
+  "workspace.error.stillPreparing": "Рабочее пространство всё ещё подготавливается",
+  "workspace.status.checking": "Проверка незафиксированных изменений...",
   "workspace.status.error": "Не удалось проверить статус git.",
-  "workspace.status.clean": "Неслитые изменения не обнаружены.",
-  "workspace.status.dirty": "Обнаружены неслитые изменения в этом рабочем пространстве.",
+  "workspace.status.clean": "Незафиксированных изменений не обнаружено.",
+  "workspace.status.dirty": "В этом рабочем пространстве обнаружены незафиксированные изменения.",
   "workspace.delete.title": "Удалить рабочее пространство",
   "workspace.delete.confirm": 'Удалить рабочее пространство "{{name}}"?',
   "workspace.delete.button": "Удалить рабочее пространство",
   "workspace.reset.title": "Сбросить рабочее пространство",
   "workspace.reset.confirm": 'Сбросить рабочее пространство "{{name}}"?',
   "workspace.reset.button": "Сбросить рабочее пространство",
-  "workspace.reset.archived.none": "Никакие активные сессии не будут архивированы.",
+  "workspace.reset.archived.none": "Активные сессии не будут архивированы.",
   "workspace.reset.archived.one": "1 сессия будет архивирована.",
   "workspace.reset.archived.many": "{{count}} сессий будет архивировано.",
-  "workspace.reset.note": "Рабочее пространство будет сброшено в соответствие с веткой по умолчанию.",
+  "workspace.reset.note": "Это сбросит рабочее пространство до соответствия ветке по умолчанию.",
   "common.open": "Открыть",
   "dialog.releaseNotes.action.getStarted": "Начать",
   "dialog.releaseNotes.action.next": "Далее",

+ 29 - 1
packages/app/src/i18n/th.ts

@@ -113,6 +113,7 @@ export const dict = {
   "dialog.model.empty": "ไม่พบผลลัพธ์โมเดล",
   "dialog.model.manage": "จัดการโมเดล",
   "dialog.model.manage.description": "ปรับแต่งโมเดลที่จะปรากฏในตัวเลือกโมเดล",
+  "dialog.model.manage.provider.toggle": "สลับโมเดลทั้งหมดของ {{provider}}",
 
   "dialog.model.unpaid.freeModels.title": "โมเดลฟรีที่จัดหาให้โดย OpenCode",
   "dialog.model.unpaid.addMore.title": "เพิ่มโมเดลเพิ่มเติมจากผู้ให้บริการยอดนิยม",
@@ -314,6 +315,11 @@ export const dict = {
   "dialog.server.add.error": "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์",
   "dialog.server.add.checking": "กำลังตรวจสอบ...",
   "dialog.server.add.button": "เพิ่มเซิร์ฟเวอร์",
+  "dialog.server.add.name": "ชื่อเซิร์ฟเวอร์ (ไม่บังคับ)",
+  "dialog.server.add.namePlaceholder": "Localhost",
+  "dialog.server.add.username": "ชื่อผู้ใช้ (ไม่บังคับ)",
+  "dialog.server.add.password": "รหัสผ่าน (ไม่บังคับ)",
+  "dialog.server.edit.title": "แก้ไขเซิร์ฟเวอร์",
   "dialog.server.default.title": "เซิร์ฟเวอร์เริ่มต้น",
   "dialog.server.default.description":
     "เชื่อมต่อกับเซิร์ฟเวอร์นี้เมื่อเปิดแอปแทนการเริ่มเซิร์ฟเวอร์ในเครื่อง ต้องรีสตาร์ท",
@@ -391,6 +397,7 @@ export const dict = {
   "language.br": "Português (Brasil)",
   "language.bs": "Bosanski",
   "language.th": "ไทย",
+  "language.tr": "Türkçe",
 
   "toast.language.title": "ภาษา",
   "toast.language.description": "สลับไปที่ {{language}}",
@@ -494,9 +501,12 @@ export const dict = {
   "session.review.change.other": "การเปลี่ยนแปลง",
   "session.review.loadingChanges": "กำลังโหลดการเปลี่ยนแปลง...",
   "session.review.empty": "ยังไม่มีการเปลี่ยนแปลงในเซสชันนี้",
+  "session.review.noVcs": "ไม่ตรวจพบระบบควบคุมเวอร์ชัน Git การเปลี่ยนแปลงจะไม่แสดง",
+  "session.review.noSnapshot": "การติดตามสแนปชอตถูกปิดใช้งานในการกำหนดค่า ดังนั้นการเปลี่ยนแปลงเซสชันจึงไม่พร้อมใช้งาน",
   "session.review.noChanges": "ไม่มีการเปลี่ยนแปลง",
 
   "session.files.selectToOpen": "เลือกไฟล์เพื่อเปิด",
+  "session.files.empty": "ไม่มีไฟล์",
   "session.files.all": "ไฟล์ทั้งหมด",
   "session.files.binaryContent": "ไฟล์ไบนารี (ไม่สามารถแสดงเนื้อหาได้)",
 
@@ -510,6 +520,17 @@ export const dict = {
   "session.todo.title": "สิ่งที่ต้องทำ",
   "session.todo.collapse": "ย่อ",
   "session.todo.expand": "ขยาย",
+  "session.followupDock.summary.one": "{{count}} ข้อความในคิว",
+  "session.followupDock.summary.other": "{{count}} ข้อความในคิว",
+  "session.followupDock.sendNow": "ส่งทันที",
+  "session.followupDock.edit": "แก้ไข",
+  "session.followupDock.collapse": "ย่อข้อความในคิว",
+  "session.followupDock.expand": "ขยายข้อความในคิว",
+  "session.revertDock.summary.one": "{{count}} ข้อความที่ถูกย้อนกลับ",
+  "session.revertDock.summary.other": "{{count}} ข้อความที่ถูกย้อนกลับ",
+  "session.revertDock.collapse": "ย่อข้อความที่ถูกย้อนกลับ",
+  "session.revertDock.expand": "ขยายข้อความที่ถูกย้อนกลับ",
+  "session.revertDock.restore": "กู้คืนข้อความ",
 
   "session.new.title": "สร้างอะไรก็ได้",
   "session.new.worktree.main": "สาขาหลัก",
@@ -604,11 +625,18 @@ export const dict = {
   "settings.general.row.language.description": "เปลี่ยนภาษาที่แสดงสำหรับ OpenCode",
   "settings.general.row.appearance.title": "รูปลักษณ์",
   "settings.general.row.appearance.description": "ปรับแต่งวิธีการที่ OpenCode มีลักษณะบนอุปกรณ์ของคุณ",
+  "settings.general.row.colorScheme.title": "โทนสี",
+  "settings.general.row.colorScheme.description": "เลือกว่าจะให้ OpenCode ใช้ธีมตามระบบ สว่าง หรือมืด",
   "settings.general.row.theme.title": "ธีม",
   "settings.general.row.theme.description": "ปรับแต่งวิธีการที่ OpenCode มีธีม",
   "settings.general.row.font.title": "ฟอนต์",
   "settings.general.row.font.description": "ปรับแต่งฟอนต์โมโนที่ใช้ในบล็อกโค้ด",
-
+  "settings.general.row.followup.title": "พฤติกรรมการติดตามผล",
+  "settings.general.row.followup.description": "เลือกว่าจะให้พร้อมท์ติดตามผลทำงานทันทีหรือรอในคิว",
+  "settings.general.row.followup.option.queue": "คิว",
+  "settings.general.row.followup.option.steer": "นำทาง",
+  "settings.general.row.reasoningSummaries.title": "แสดงสรุปการใช้เหตุผล",
+  "settings.general.row.reasoningSummaries.description": "แสดงสรุปการใช้เหตุผลของโมเดลในไทม์ไลน์",
   "settings.general.row.shellToolPartsExpanded.title": "ขยายส่วนเครื่องมือ shell",
   "settings.general.row.shellToolPartsExpanded.description": "แสดงส่วนเครื่องมือ shell แบบขยายตามค่าเริ่มต้นในไทม์ไลน์",
   "settings.general.row.editToolPartsExpanded.title": "ขยายส่วนเครื่องมือ edit",

+ 27 - 0
packages/app/src/i18n/tr.ts

@@ -320,6 +320,11 @@ export const dict = {
   "dialog.server.add.error": "Sunucuya bağlanılamadı",
   "dialog.server.add.checking": "Kontrol ediliyor...",
   "dialog.server.add.button": "Sunucu ekle",
+  "dialog.server.add.name": "Sunucu adı (isteğe bağlı)",
+  "dialog.server.add.namePlaceholder": "Localhost",
+  "dialog.server.add.username": "Kullanıcı adı (isteğe bağlı)",
+  "dialog.server.add.password": "Şifre (isteğe bağlı)",
+  "dialog.server.edit.title": "Sunucuyu düzenle",
   "dialog.server.default.title": "Varsayılan sunucu",
   "dialog.server.default.description":
     "Uygulama başlatıldığında yerel sunucu başlatmak yerine bu sunucuya bağlan. Yeniden başlatma gerektirir.",
@@ -506,10 +511,13 @@ export const dict = {
   "session.review.loadingChanges": "Değişiklikler yükleniyor...",
   "session.review.empty": "Bu oturumda henüz değişiklik yok",
   "session.review.noVcs": "Git VCS algılanamadı, oturum değişiklikleri tespit edilemeyecek",
+  "session.review.noSnapshot":
+    "Yapılandırmada anlık görüntü takibi devre dışı bırakıldı, bu nedenle oturum değişiklikleri kullanılamıyor",
   "session.review.noChanges": "Değişiklik yok",
 
   "session.files.selectToOpen": "Açmak için bir dosya seçin",
   "session.files.all": "Tüm dosyalar",
+  "session.files.empty": "Dosya yok",
   "session.files.binaryContent": "İkili dosya (içerik görüntülenemiyor)",
 
   "session.messages.renderEarlier": "Önceki mesajları göster",
@@ -522,6 +530,17 @@ export const dict = {
   "session.todo.title": "Görevler",
   "session.todo.collapse": "Daralt",
   "session.todo.expand": "Genişlet",
+  "session.followupDock.summary.one": "{{count}} sıradaki mesaj",
+  "session.followupDock.summary.other": "{{count}} sıradaki mesaj",
+  "session.followupDock.sendNow": "Şimdi gönder",
+  "session.followupDock.edit": "Düzenle",
+  "session.followupDock.collapse": "Sıradaki mesajları daralt",
+  "session.followupDock.expand": "Sıradaki mesajları genişlet",
+  "session.revertDock.summary.one": "{{count}} geri alınan mesaj",
+  "session.revertDock.summary.other": "{{count}} geri alınan mesaj",
+  "session.revertDock.collapse": "Geri alınan mesajları daralt",
+  "session.revertDock.expand": "Geri alınan mesajları genişlet",
+  "session.revertDock.restore": "Mesajı geri yükle",
 
   "session.new.title": "İstediğini yap",
   "session.new.worktree.main": "Ana dal",
@@ -618,10 +637,18 @@ export const dict = {
   "settings.general.row.language.description": "OpenCode'un görünüm dilini değiştirin",
   "settings.general.row.appearance.title": "Görünüm",
   "settings.general.row.appearance.description": "OpenCode'un cihazınızdaki görünümünü özelleştirin",
+  "settings.general.row.colorScheme.title": "Renk şeması",
+  "settings.general.row.colorScheme.description":
+    "OpenCode'un sistem, açık veya koyu temayı takip etip etmeyeceğini seçin",
   "settings.general.row.theme.title": "Tema",
   "settings.general.row.theme.description": "OpenCode'un temasını özelleştirin.",
   "settings.general.row.font.title": "Yazı Tipi",
   "settings.general.row.font.description": "Kod bloklarında kullanılan monospace yazı tipini özelleştirin",
+  "settings.general.row.followup.title": "Takip davranışı",
+  "settings.general.row.followup.description":
+    "Takip komutlarının hemen yönlendirilmesini mi yoksa sırada beklemesini mi istediğinizi seçin",
+  "settings.general.row.followup.option.queue": "Sıra",
+  "settings.general.row.followup.option.steer": "Yönlendir",
   "settings.general.row.reasoningSummaries.title": "Akıl yürütme özetlerini göster",
   "settings.general.row.reasoningSummaries.description": "Zaman çizelgesinde model akıl yürütme özetlerini görüntüle",
   "settings.general.row.shellToolPartsExpanded.title": "Kabuk araç bileşenlerini genişlet",

+ 29 - 0
packages/app/src/i18n/zh.ts

@@ -140,6 +140,7 @@ export const dict = {
   "dialog.model.empty": "未找到模型",
   "dialog.model.manage": "管理模型",
   "dialog.model.manage.description": "自定义模型选择器中显示的模型。",
+  "dialog.model.manage.provider.toggle": "切换所有 {{provider}} 模型",
   "dialog.model.unpaid.freeModels.title": "OpenCode 提供的免费模型",
   "dialog.model.unpaid.addMore.title": "从热门提供商添加更多模型",
 
@@ -334,6 +335,11 @@ export const dict = {
   "dialog.server.add.error": "无法连接到服务器",
   "dialog.server.add.checking": "检查中...",
   "dialog.server.add.button": "添加服务器",
+  "dialog.server.add.name": "服务器名称(可选)",
+  "dialog.server.add.namePlaceholder": "Localhost",
+  "dialog.server.add.username": "用户名(可选)",
+  "dialog.server.add.password": "密码(可选)",
+  "dialog.server.edit.title": "编辑服务器",
   "dialog.server.default.title": "默认服务器",
   "dialog.server.default.description": "应用启动时连接此服务器,而不是启动本地服务器。需要重启。",
   "dialog.server.default.none": "未选择服务器",
@@ -406,6 +412,7 @@ export const dict = {
   "language.br": "Português (Brasil)",
   "language.bs": "Bosanski",
   "language.th": "ไทย",
+  "language.tr": "Türkçe",
 
   "toast.language.title": "语言",
   "toast.language.description": "已切换到{{language}}",
@@ -497,9 +504,12 @@ export const dict = {
   "session.review.change.other": "更改",
   "session.review.loadingChanges": "正在加载更改...",
   "session.review.empty": "此会话暂无更改",
+  "session.review.noVcs": "未检测到 Git 版本控制系统,无法显示更改",
+  "session.review.noSnapshot": "配置中已禁用快照跟踪,因此会话更改不可用",
   "session.review.noChanges": "无更改",
   "session.files.selectToOpen": "选择要打开的文件",
   "session.files.all": "所有文件",
+  "session.files.empty": "无文件",
   "session.files.binaryContent": "二进制文件(无法显示内容)",
   "session.messages.renderEarlier": "显示更早的消息",
   "session.messages.loadingEarlier": "正在加载更早的消息...",
@@ -510,6 +520,17 @@ export const dict = {
   "session.todo.title": "待办事项",
   "session.todo.collapse": "折叠",
   "session.todo.expand": "展开",
+  "session.followupDock.summary.one": "{{count}} 条排队消息",
+  "session.followupDock.summary.other": "{{count}} 条排队消息",
+  "session.followupDock.sendNow": "立即发送",
+  "session.followupDock.edit": "编辑",
+  "session.followupDock.collapse": "折叠排队消息",
+  "session.followupDock.expand": "展开排队消息",
+  "session.revertDock.summary.one": "{{count}} 条已回滚消息",
+  "session.revertDock.summary.other": "{{count}} 条已回滚消息",
+  "session.revertDock.collapse": "折叠已回滚消息",
+  "session.revertDock.expand": "展开已回滚消息",
+  "session.revertDock.restore": "恢复消息",
   "session.new.title": "构建任何东西",
   "session.new.worktree.main": "主分支",
   "session.new.worktree.mainWithBranch": "主分支({{branch}})",
@@ -604,10 +625,18 @@ export const dict = {
   "settings.general.row.language.description": "更改 OpenCode 的显示语言",
   "settings.general.row.appearance.title": "外观",
   "settings.general.row.appearance.description": "自定义 OpenCode 在你的设备上的外观",
+  "settings.general.row.colorScheme.title": "配色方案",
+  "settings.general.row.colorScheme.description": "选择 OpenCode 跟随系统、浅色或深色主题",
   "settings.general.row.theme.title": "主题",
   "settings.general.row.theme.description": "自定义 OpenCode 的主题。",
   "settings.general.row.font.title": "字体",
   "settings.general.row.font.description": "自定义代码块使用的等宽字体",
+  "settings.general.row.followup.title": "跟进消息行为",
+  "settings.general.row.followup.description": "选择跟进提示是立即引导还是在队列中等待",
+  "settings.general.row.followup.option.queue": "排队",
+  "settings.general.row.followup.option.steer": "引导",
+  "settings.general.row.reasoningSummaries.title": "显示推理摘要",
+  "settings.general.row.reasoningSummaries.description": "在时间线中显示模型推理摘要",
   "settings.general.row.shellToolPartsExpanded.title": "展开 shell 工具部分",
   "settings.general.row.shellToolPartsExpanded.description": "默认在时间线中展开 shell 工具部分",
   "settings.general.row.editToolPartsExpanded.title": "展开编辑工具部分",

+ 31 - 2
packages/app/src/i18n/zht.ts

@@ -117,6 +117,7 @@ export const dict = {
   "dialog.model.empty": "找不到模型",
   "dialog.model.manage": "管理模型",
   "dialog.model.manage.description": "自訂模型選擇器中顯示的模型。",
+  "dialog.model.manage.provider.toggle": "切換所有 {{provider}} 模型",
 
   "dialog.model.unpaid.freeModels.title": "OpenCode 提供的免費模型",
   "dialog.model.unpaid.addMore.title": "從熱門提供者新增更多模型",
@@ -314,6 +315,11 @@ export const dict = {
   "dialog.server.add.error": "無法連線到伺服器",
   "dialog.server.add.checking": "檢查中...",
   "dialog.server.add.button": "新增伺服器",
+  "dialog.server.add.name": "伺服器名稱(選填)",
+  "dialog.server.add.namePlaceholder": "Localhost",
+  "dialog.server.add.username": "使用者名稱(選填)",
+  "dialog.server.add.password": "密碼(選填)",
+  "dialog.server.edit.title": "編輯伺服器",
   "dialog.server.default.title": "預設伺服器",
   "dialog.server.default.description": "應用程式啟動時連線此伺服器,而不是啟動本地伺服器。需要重新啟動。",
   "dialog.server.default.none": "未選擇伺服器",
@@ -390,6 +396,7 @@ export const dict = {
   "language.br": "Português (Brasil)",
   "language.bs": "Bosanski",
   "language.th": "ไทย",
+  "language.tr": "Türkçe",
 
   "toast.language.title": "語言",
   "toast.language.description": "已切換到 {{language}}",
@@ -493,8 +500,11 @@ export const dict = {
   "session.review.loadingChanges": "正在載入變更...",
   "session.review.empty": "此工作階段暫無變更",
   "session.review.noChanges": "沒有變更",
+  "session.review.noVcs": "未偵測到 Git 版本控制系統,無法顯示變更",
+  "session.review.noSnapshot": "設定中已停用快照追蹤,因此無法使用工作階段變更",
   "session.files.selectToOpen": "選取要開啟的檔案",
   "session.files.all": "所有檔案",
+  "session.files.empty": "沒有檔案",
   "session.files.binaryContent": "二進位檔案(無法顯示內容)",
   "session.messages.renderEarlier": "顯示更早的訊息",
   "session.messages.loadingEarlier": "正在載入更早的訊息...",
@@ -506,6 +516,17 @@ export const dict = {
   "session.todo.title": "待辦事項",
   "session.todo.collapse": "折疊",
   "session.todo.expand": "展開",
+  "session.followupDock.summary.one": "{{count}} 則佇列訊息",
+  "session.followupDock.summary.other": "{{count}} 則佇列訊息",
+  "session.followupDock.sendNow": "立即傳送",
+  "session.followupDock.edit": "編輯",
+  "session.followupDock.collapse": "收合佇列訊息",
+  "session.followupDock.expand": "展開佇列訊息",
+  "session.revertDock.summary.one": "{{count}} 則已回復訊息",
+  "session.revertDock.summary.other": "{{count}} 則已回復訊息",
+  "session.revertDock.collapse": "收合已回復訊息",
+  "session.revertDock.expand": "展開已回復訊息",
+  "session.revertDock.restore": "還原訊息",
 
   "session.new.title": "建構任何東西",
   "session.new.worktree.main": "主分支",
@@ -585,8 +606,8 @@ export const dict = {
   "settings.tab.general": "一般",
   "settings.tab.shortcuts": "快速鍵",
   "settings.desktop.section.wsl": "WSL",
-  "settings.desktop.wsl.title": "WSL integration",
-  "settings.desktop.wsl.description": "Run the OpenCode server inside WSL on Windows.",
+  "settings.desktop.wsl.title": "WSL 整合",
+  "settings.desktop.wsl.description": "在 Windows 上的 WSL 中執行 OpenCode 伺服器。",
 
   "settings.general.section.appearance": "外觀",
   "settings.general.section.notifications": "系統通知",
@@ -599,10 +620,18 @@ export const dict = {
   "settings.general.row.language.description": "變更 OpenCode 的顯示語言",
   "settings.general.row.appearance.title": "外觀",
   "settings.general.row.appearance.description": "自訂 OpenCode 在你的裝置上的外觀",
+  "settings.general.row.colorScheme.title": "配色方案",
+  "settings.general.row.colorScheme.description": "選擇 OpenCode 要跟隨系統、淺色或深色主題",
   "settings.general.row.theme.title": "主題",
   "settings.general.row.theme.description": "自訂 OpenCode 的主題。",
   "settings.general.row.font.title": "字型",
   "settings.general.row.font.description": "自訂程式碼區塊使用的等寬字型",
+  "settings.general.row.followup.title": "後續追問行為",
+  "settings.general.row.followup.description": "選擇後續追問提示是立即引導還是進入佇列等待",
+  "settings.general.row.followup.option.queue": "佇列",
+  "settings.general.row.followup.option.steer": "引導",
+  "settings.general.row.reasoningSummaries.title": "顯示推理摘要",
+  "settings.general.row.reasoningSummaries.description": "在時間軸中顯示模型推理摘要",
 
   "settings.general.row.shellToolPartsExpanded.title": "展開 shell 工具區塊",
   "settings.general.row.shellToolPartsExpanded.description": "在時間軸中預設展開 shell 工具區塊",

+ 158 - 1
packages/app/src/pages/session.tsx

@@ -35,8 +35,10 @@ import { useLanguage } from "@/context/language"
 import { useLayout } from "@/context/layout"
 import { usePrompt } from "@/context/prompt"
 import { useSDK } from "@/context/sdk"
+import { useSettings } from "@/context/settings"
 import { useSync } from "@/context/sync"
 import { useTerminal } from "@/context/terminal"
+import { type FollowupDraft, sendFollowupDraft } from "@/components/prompt-input/submit"
 import { createSessionComposerState, SessionComposerRegion } from "@/pages/session/composer"
 import { createOpenReviewFile, createSessionTabs, createSizing, focusTerminalById } from "@/pages/session/helpers"
 import { MessageTimeline } from "@/pages/session/message-timeline"
@@ -47,11 +49,13 @@ import { SessionSidePanel } from "@/pages/session/session-side-panel"
 import { TerminalPanel } from "@/pages/session/terminal-panel"
 import { useSessionCommands } from "@/pages/session/use-session-commands"
 import { useSessionHashScroll } from "@/pages/session/use-session-hash-scroll"
+import { Identifier } from "@/utils/id"
 import { extractPromptFromParts } from "@/utils/prompt"
 import { same } from "@/utils/same"
 import { formatServerError } from "@/utils/server-errors"
 
 const emptyUserMessages: UserMessage[] = []
+const emptyFollowups: (FollowupDraft & { id: string })[] = []
 
 type SessionHistoryWindowInput = {
   sessionID: () => string | undefined
@@ -270,6 +274,7 @@ export default function Page() {
   const language = useLanguage()
   const navigate = useNavigate()
   const sdk = useSDK()
+  const settings = useSettings()
   const prompt = usePrompt()
   const comments = useComments()
   const terminal = useTerminal()
@@ -466,6 +471,17 @@ export default function Page() {
     deferRender: false,
   })
 
+  const [followup, setFollowup] = createStore({
+    items: {} as Record<string, (FollowupDraft & { id: string })[] | undefined>,
+    sending: {} as Record<string, string | undefined>,
+    failed: {} as Record<string, string | undefined>,
+    paused: {} as Record<string, boolean | undefined>,
+    edit: {} as Record<
+      string,
+      { id: string; prompt: FollowupDraft["prompt"]; context: FollowupDraft["context"] } | undefined
+    >,
+  })
+
   createComputed((prev) => {
     const key = sessionKey()
     if (key !== prev) {
@@ -1264,12 +1280,117 @@ export default function Page() {
     })
 
   const busy = (sessionID: string) => {
-    if (sync.data.session_status[sessionID]?.type !== "idle") return true
+    if ((sync.data.session_status[sessionID] ?? { type: "idle" as const }).type !== "idle") return true
     return (sync.data.message[sessionID] ?? []).some(
       (item) => item.role === "assistant" && typeof item.time.completed !== "number",
     )
   }
 
+  const queuedFollowups = createMemo(() => {
+    const id = params.id
+    if (!id) return emptyFollowups
+    return followup.items[id] ?? emptyFollowups
+  })
+
+  const editingFollowup = createMemo(() => {
+    const id = params.id
+    if (!id) return
+    return followup.edit[id]
+  })
+
+  const sendingFollowup = createMemo(() => {
+    const id = params.id
+    if (!id) return
+    return followup.sending[id]
+  })
+
+  const queueEnabled = createMemo(() => {
+    const id = params.id
+    if (!id) return false
+    return settings.general.followup() === "queue" && busy(id) && !composer.blocked()
+  })
+
+  const followupText = (item: FollowupDraft) => {
+    const text = item.prompt
+      .map((part) => {
+        if (part.type === "image") return `[image:${part.filename}]`
+        if (part.type === "file") return `[file:${part.path}]`
+        if (part.type === "agent") return `@${part.name}`
+        return part.content
+      })
+      .join("")
+      .split(/\r?\n/)
+      .map((line) => line.trim())
+      .find((line) => !!line)
+
+    if (text) return text
+    return `[${language.t("common.attachment")}]`
+  }
+
+  const queueFollowup = (draft: FollowupDraft) => {
+    setFollowup("items", draft.sessionID, (items) => [
+      ...(items ?? []),
+      { id: Identifier.ascending("message"), ...draft },
+    ])
+    setFollowup("failed", draft.sessionID, undefined)
+    setFollowup("paused", draft.sessionID, undefined)
+  }
+
+  const followupDock = createMemo(() => queuedFollowups().map((item) => ({ id: item.id, text: followupText(item) })))
+
+  const sendFollowup = (sessionID: string, id: string, opts?: { manual?: boolean }) => {
+    const item = (followup.items[sessionID] ?? []).find((entry) => entry.id === id)
+    if (!item) return Promise.resolve()
+    if (followup.sending[sessionID]) return Promise.resolve()
+
+    if (opts?.manual) setFollowup("paused", sessionID, undefined)
+    setFollowup("sending", sessionID, id)
+    setFollowup("failed", sessionID, undefined)
+
+    return sendFollowupDraft({
+      client: sdk.client,
+      sync,
+      globalSync,
+      draft: item,
+      optimisticBusy: item.sessionDirectory === sdk.directory,
+    })
+      .then((ok) => {
+        if (ok === false) return
+        setFollowup("items", sessionID, (items) => (items ?? []).filter((entry) => entry.id !== id))
+        if (opts?.manual) resumeScroll()
+      })
+      .catch((err) => {
+        setFollowup("failed", sessionID, id)
+        fail(err)
+      })
+      .finally(() => {
+        setFollowup("sending", sessionID, (value) => (value === id ? undefined : value))
+      })
+  }
+
+  const editFollowup = (id: string) => {
+    const sessionID = params.id
+    if (!sessionID) return
+    if (followup.sending[sessionID]) return
+
+    const item = queuedFollowups().find((entry) => entry.id === id)
+    if (!item) return
+
+    setFollowup("items", sessionID, (items) => (items ?? []).filter((entry) => entry.id !== id))
+    setFollowup("failed", sessionID, (value) => (value === id ? undefined : value))
+    setFollowup("edit", sessionID, {
+      id: item.id,
+      prompt: item.prompt,
+      context: item.context,
+    })
+  }
+
+  const clearFollowupEdit = () => {
+    const id = params.id
+    if (!id) return
+    setFollowup("edit", id, undefined)
+  }
+
   const halt = (sessionID: string) =>
     busy(sessionID) ? sdk.client.session.abort({ sessionID }).catch(() => {}) : Promise.resolve()
 
@@ -1378,6 +1499,21 @@ export default function Page() {
 
   const actions = { fork, revert }
 
+  createEffect(() => {
+    const sessionID = params.id
+    if (!sessionID) return
+
+    const item = queuedFollowups()[0]
+    if (!item) return
+    if (followup.sending[sessionID]) return
+    if (followup.failed[sessionID] === item.id) return
+    if (followup.paused[sessionID]) return
+    if (composer.blocked()) return
+    if (busy(sessionID)) return
+
+    void sendFollowup(sessionID, item.id)
+  })
+
   createResizeObserver(
     () => promptDock,
     ({ height }) => {
@@ -1537,6 +1673,27 @@ export default function Page() {
               resumeScroll()
             }}
             onResponseSubmit={resumeScroll}
+            followup={
+              params.id
+                ? {
+                    queue: queueEnabled,
+                    items: followupDock(),
+                    sending: sendingFollowup(),
+                    edit: editingFollowup(),
+                    onQueue: queueFollowup,
+                    onAbort: () => {
+                      const id = params.id
+                      if (!id) return
+                      setFollowup("paused", id, true)
+                    },
+                    onSend: (id) => {
+                      void sendFollowup(params.id!, id, { manual: true })
+                    },
+                    onEdit: editFollowup,
+                    onEditLoaded: clearFollowupEdit,
+                  }
+                : undefined
+            }
             revert={
               rolled().length > 0
                 ? {

+ 26 - 0
packages/app/src/pages/session/composer/session-composer-region.tsx

@@ -8,9 +8,11 @@ import { getSessionHandoff, setSessionHandoff } from "@/pages/session/handoff"
 import { useSessionKey } from "@/pages/session/session-layout"
 import { SessionPermissionDock } from "@/pages/session/composer/session-permission-dock"
 import { SessionQuestionDock } from "@/pages/session/composer/session-question-dock"
+import { SessionFollowupDock } from "@/pages/session/composer/session-followup-dock"
 import { SessionRevertDock } from "@/pages/session/composer/session-revert-dock"
 import type { SessionComposerState } from "@/pages/session/composer/session-composer-state"
 import { SessionTodoDock } from "@/pages/session/composer/session-todo-dock"
+import type { FollowupDraft } from "@/components/prompt-input/submit"
 
 export function SessionComposerRegion(props: {
   state: SessionComposerState
@@ -21,6 +23,17 @@ export function SessionComposerRegion(props: {
   onNewSessionWorktreeReset: () => void
   onSubmit: () => void
   onResponseSubmit: () => void
+  followup?: {
+    queue: () => boolean
+    items: { id: string; text: string }[]
+    sending?: string
+    edit?: { id: string; prompt: FollowupDraft["prompt"]; context: FollowupDraft["context"] }
+    onQueue: (draft: FollowupDraft) => void
+    onAbort: () => void
+    onSend: (id: string) => void
+    onEdit: (id: string) => void
+    onEditLoaded: () => void
+  }
   revert?: {
     items: { id: string; text: string }[]
     restoring?: string
@@ -214,10 +227,23 @@ export function SessionComposerRegion(props: {
                 "margin-top": `${-lift()}px`,
               }}
             >
+              <Show when={props.followup?.items.length}>
+                <SessionFollowupDock
+                  items={props.followup!.items}
+                  sending={props.followup!.sending}
+                  onSend={props.followup!.onSend}
+                  onEdit={props.followup!.onEdit}
+                />
+              </Show>
               <PromptInput
                 ref={props.inputRef}
                 newSessionWorktree={props.newSessionWorktree}
                 onNewSessionWorktreeReset={props.onNewSessionWorktreeReset}
+                edit={props.followup?.edit}
+                onEditLoaded={props.followup?.onEditLoaded}
+                shouldQueue={props.followup?.queue}
+                onQueue={props.followup?.onQueue}
+                onAbort={props.followup?.onAbort}
                 onSubmit={props.onSubmit}
               />
             </div>

+ 109 - 0
packages/app/src/pages/session/composer/session-followup-dock.tsx

@@ -0,0 +1,109 @@
+import { For, Show, createMemo } from "solid-js"
+import { createStore } from "solid-js/store"
+import { Button } from "@opencode-ai/ui/button"
+import { DockTray } from "@opencode-ai/ui/dock-surface"
+import { IconButton } from "@opencode-ai/ui/icon-button"
+import { useLanguage } from "@/context/language"
+
+export function SessionFollowupDock(props: {
+  items: { id: string; text: string }[]
+  sending?: string
+  onSend: (id: string) => void
+  onEdit: (id: string) => void
+}) {
+  const language = useLanguage()
+  const [store, setStore] = createStore({
+    collapsed: false,
+  })
+
+  const toggle = () => setStore("collapsed", (value) => !value)
+  const total = createMemo(() => props.items.length)
+  const label = createMemo(() =>
+    language.t(total() === 1 ? "session.followupDock.summary.one" : "session.followupDock.summary.other", {
+      count: total(),
+    }),
+  )
+  const preview = createMemo(() => props.items[0]?.text ?? "")
+
+  return (
+    <DockTray
+      data-component="session-followup-dock"
+      style={{
+        "margin-bottom": "-0.875rem",
+        "border-bottom-left-radius": 0,
+        "border-bottom-right-radius": 0,
+      }}
+    >
+      <div
+        class="pl-3 pr-2 py-2 flex items-center gap-2"
+        role="button"
+        tabIndex={0}
+        onClick={toggle}
+        onKeyDown={(event) => {
+          if (event.key !== "Enter" && event.key !== " ") return
+          event.preventDefault()
+          toggle()
+        }}
+      >
+        <span class="shrink-0 text-13-medium text-text-strong cursor-default">{label()}</span>
+        <Show when={store.collapsed && preview()}>
+          <span class="min-w-0 flex-1 truncate text-13-regular text-text-base cursor-default">{preview()}</span>
+        </Show>
+        <div class="ml-auto shrink-0">
+          <IconButton
+            data-collapsed={store.collapsed ? "true" : "false"}
+            icon="chevron-down"
+            size="normal"
+            variant="ghost"
+            style={{ transform: `rotate(${store.collapsed ? 180 : 0}deg)` }}
+            onMouseDown={(event) => {
+              event.preventDefault()
+              event.stopPropagation()
+            }}
+            onClick={(event) => {
+              event.stopPropagation()
+              toggle()
+            }}
+            aria-label={
+              store.collapsed ? language.t("session.followupDock.expand") : language.t("session.followupDock.collapse")
+            }
+          />
+        </div>
+      </div>
+
+      <Show when={store.collapsed}>
+        <div class="h-5" aria-hidden="true" />
+      </Show>
+
+      <Show when={!store.collapsed}>
+        <div class="px-3 pb-7 flex flex-col gap-1.5 max-h-42 overflow-y-auto no-scrollbar">
+          <For each={props.items}>
+            {(item) => (
+              <div class="flex items-center gap-2 min-w-0 py-1">
+                <span class="min-w-0 flex-1 truncate text-13-regular text-text-strong">{item.text}</span>
+                <Button
+                  size="small"
+                  variant="secondary"
+                  class="shrink-0"
+                  disabled={!!props.sending}
+                  onClick={() => props.onSend(item.id)}
+                >
+                  {language.t("session.followupDock.sendNow")}
+                </Button>
+                <Button
+                  size="small"
+                  variant="ghost"
+                  class="shrink-0"
+                  disabled={!!props.sending}
+                  onClick={() => props.onEdit(item.id)}
+                >
+                  {language.t("session.followupDock.edit")}
+                </Button>
+              </div>
+            )}
+          </For>
+        </div>
+      </Show>
+    </DockTray>
+  )
+}

+ 2 - 2
packages/app/src/pages/session/composer/session-revert-dock.tsx

@@ -75,10 +75,10 @@ export function SessionRevertDock(props: {
       </Show>
 
       <Show when={!store.collapsed}>
-        <div class="px-3 pb-11 flex flex-col gap-1.5 max-h-42 overflow-y-auto no-scrollbar">
+        <div class="px-3 pb-7 flex flex-col gap-1.5 max-h-42 overflow-y-auto no-scrollbar">
           <For each={props.items}>
             {(item) => (
-              <div class="flex items-center gap-2 min-w-0 rounded-[10px] border border-border-weak-base bg-background-stronger px-2.5 py-2">
+              <div class="flex items-center gap-2 min-w-0 py-1">
                 <span class="min-w-0 flex-1 truncate text-13-regular text-text-strong">{item.text}</span>
                 <Button
                   size="small"

+ 0 - 7
packages/app/src/pages/session/message-timeline.tsx

@@ -942,12 +942,6 @@ export function MessageTimeline(props: {
               <For each={rendered()}>
                 {(messageID) => {
                   const active = createMemo(() => activeMessageID() === messageID)
-                  const queued = createMemo(() => {
-                    if (active()) return false
-                    const activeID = activeMessageID()
-                    if (activeID) return messageID > activeID
-                    return false
-                  })
                   const comments = createMemo(() => messageComments(sync.data.part[messageID] ?? []), [], {
                     equals: (a, b) => JSON.stringify(a) === JSON.stringify(b),
                   })
@@ -1007,7 +1001,6 @@ export function MessageTimeline(props: {
                         messageID={messageID}
                         actions={props.actions}
                         active={active()}
-                        queued={queued()}
                         status={active() ? sessionStatus() : undefined}
                         showReasoningSummaries={settings.general.showReasoningSummaries()}
                         shellToolDefaultOpen={settings.general.shellToolPartsExpanded()}

+ 0 - 17
packages/ui/src/components/message-part.css

@@ -54,10 +54,6 @@
       border-color: var(--border-strong-base);
     }
 
-    &[data-queued] {
-      opacity: 0.6;
-    }
-
     &[data-type="image"] {
       width: 48px;
       height: 48px;
@@ -111,11 +107,6 @@
     border: 1px solid var(--border-weak-base);
     padding: 8px 12px;
     border-radius: 6px;
-    transition: opacity 0.3s ease;
-
-    &[data-queued] {
-      opacity: 0.6;
-    }
 
     [data-highlight="file"] {
       color: var(--syntax-property);
@@ -128,14 +119,6 @@
     max-width: 100%;
   }
 
-  [data-slot="user-message-queued-indicator"] {
-    margin-top: 6px;
-    margin-right: 2px;
-    font-size: var(--font-size-small);
-    color: var(--text-weak);
-    user-select: none;
-  }
-
   [data-slot="user-message-copy-wrapper"] {
     min-height: 24px;
     margin-top: 4px;

+ 1 - 10
packages/ui/src/components/message-part.tsx

@@ -132,7 +132,6 @@ export interface MessageProps {
   actions?: UserActions
   showAssistantCopyPartID?: string | null
   interrupted?: boolean
-  queued?: boolean
   showReasoningSummaries?: boolean
 }
 
@@ -686,7 +685,6 @@ export function Message(props: MessageProps) {
             parts={props.parts}
             actions={props.actions}
             interrupted={props.interrupted}
-            queued={props.queued}
           />
         )}
       </Match>
@@ -883,7 +881,6 @@ export function UserMessageDisplay(props: {
   parts: PartType[]
   actions?: UserActions
   interrupted?: boolean
-  queued?: boolean
 }) {
   const data = useData()
   const dialog = useDialog()
@@ -981,7 +978,6 @@ export function UserMessageDisplay(props: {
               <div
                 data-slot="user-message-attachment"
                 data-type={file.mime.startsWith("image/") ? "image" : "file"}
-                data-queued={props.queued ? "" : undefined}
                 onClick={() => {
                   if (file.mime.startsWith("image/") && file.url) {
                     openImagePreview(file.url, file.filename)
@@ -1010,14 +1006,9 @@ export function UserMessageDisplay(props: {
       <Show when={text()}>
         <>
           <div data-slot="user-message-body">
-            <div data-slot="user-message-text" data-queued={props.queued ? "" : undefined}>
+            <div data-slot="user-message-text">
               <HighlightedText text={text()} references={inlineFiles()} agents={agents()} />
             </div>
-            <Show when={props.queued}>
-              <div data-slot="user-message-queued-indicator">
-                <TextShimmer text={i18n.t("ui.message.queued")} />
-              </div>
-            </Show>
           </div>
           <div data-slot="user-message-copy-wrapper" data-interrupted={props.interrupted ? "" : undefined}>
             <Show when={metaHead() || metaTail()}>

+ 2 - 20
packages/ui/src/components/session-turn.tsx

@@ -146,7 +146,6 @@ export function SessionTurn(
     shellToolDefaultOpen?: boolean
     editToolDefaultOpen?: boolean
     active?: boolean
-    queued?: boolean
     status?: SessionStatus
     onUserInteracted?: () => void
     classes?: {
@@ -193,7 +192,7 @@ export function SessionTurn(
   })
 
   const pending = createMemo(() => {
-    if (typeof props.active === "boolean" && typeof props.queued === "boolean") return
+    if (typeof props.active === "boolean") return
     const messages = allMessages() ?? emptyMessages
     return messages.findLast(
       (item): item is AssistantMessage => item.role === "assistant" && typeof item.time.completed !== "number",
@@ -218,16 +217,6 @@ export function SessionTurn(
     return parent.id === msg.id
   })
 
-  const queued = createMemo(() => {
-    if (typeof props.queued === "boolean") return props.queued
-    const id = message()?.id
-    if (!id) return false
-    if (!pendingUser()) return false
-    const item = pending()
-    if (!item) return false
-    return id > item.id
-  })
-
   const parts = createMemo(() => {
     const msg = message()
     if (!msg) return emptyParts
@@ -367,7 +356,6 @@ export function SessionTurn(
   )
   const showThinking = createMemo(() => {
     if (!working() || !!error()) return false
-    if (queued()) return false
     if (status().type === "retry") return false
     if (showReasoningSummaries()) return assistantVisible() === 0
     return true
@@ -396,13 +384,7 @@ export function SessionTurn(
               class={props.classes?.container}
             >
               <div data-slot="session-turn-message-content" aria-live="off">
-                <Message
-                  message={message()!}
-                  parts={parts()}
-                  actions={props.actions}
-                  interrupted={interrupted()}
-                  queued={queued()}
-                />
+                <Message message={message()!} parts={parts()} actions={props.actions} interrupted={interrupted()} />
               </div>
               <Show when={compaction()}>
                 <div data-slot="session-turn-compaction">

+ 2 - 0
packages/ui/src/i18n/ar.ts

@@ -126,6 +126,8 @@ export const dict = {
   "ui.message.collapse": "طي الرسالة",
   "ui.message.copy": "نسخ",
   "ui.message.copyMessage": "نسخ الرسالة",
+  "ui.message.forkMessage": "تشعب إلى جلسة جديدة",
+  "ui.message.revertMessage": "إعادة التعيين إلى هذه النقطة",
   "ui.message.copyResponse": "نسخ الرد",
   "ui.message.copied": "تم النسخ!",
   "ui.message.interrupted": "تمت المقاطعة",

+ 2 - 0
packages/ui/src/i18n/br.ts

@@ -126,6 +126,8 @@ export const dict = {
   "ui.message.collapse": "Recolher mensagem",
   "ui.message.copy": "Copiar",
   "ui.message.copyMessage": "Copiar mensagem",
+  "ui.message.forkMessage": "Bifurcar para nova sessão",
+  "ui.message.revertMessage": "Redefinir para este ponto",
   "ui.message.copyResponse": "Copiar resposta",
   "ui.message.copied": "Copiado!",
   "ui.message.interrupted": "Interrompido",

+ 2 - 0
packages/ui/src/i18n/bs.ts

@@ -130,6 +130,8 @@ export const dict = {
   "ui.message.collapse": "Sažmi poruku",
   "ui.message.copy": "Kopiraj",
   "ui.message.copyMessage": "Kopiraj poruku",
+  "ui.message.forkMessage": "Forkaj u novu sesiju",
+  "ui.message.revertMessage": "Resetuj na ovu tačku",
   "ui.message.copyResponse": "Kopiraj odgovor",
   "ui.message.copied": "Kopirano!",
   "ui.message.interrupted": "Prekinuto",

+ 2 - 0
packages/ui/src/i18n/da.ts

@@ -125,6 +125,8 @@ export const dict = {
   "ui.message.collapse": "Skjul besked",
   "ui.message.copy": "Kopier",
   "ui.message.copyMessage": "Kopier besked",
+  "ui.message.forkMessage": "Forgren til ny session",
+  "ui.message.revertMessage": "Nulstil til dette punkt",
   "ui.message.copyResponse": "Kopier svar",
   "ui.message.copied": "Kopieret!",
   "ui.message.interrupted": "Afbrudt",

+ 2 - 0
packages/ui/src/i18n/de.ts

@@ -131,6 +131,8 @@ export const dict = {
   "ui.message.collapse": "Nachricht reduzieren",
   "ui.message.copy": "Kopieren",
   "ui.message.copyMessage": "Nachricht kopieren",
+  "ui.message.forkMessage": "In neue Sitzung abzweigen",
+  "ui.message.revertMessage": "Auf diesen Punkt zurücksetzen",
   "ui.message.copyResponse": "Antwort kopieren",
   "ui.message.copied": "Kopiert!",
   "ui.message.interrupted": "Unterbrochen",

+ 2 - 0
packages/ui/src/i18n/es.ts

@@ -126,6 +126,8 @@ export const dict = {
   "ui.message.collapse": "Colapsar mensaje",
   "ui.message.copy": "Copiar",
   "ui.message.copyMessage": "Copiar mensaje",
+  "ui.message.forkMessage": "Bifurcar a nueva sesión",
+  "ui.message.revertMessage": "Restablecer a este punto",
   "ui.message.copyResponse": "Copiar respuesta",
   "ui.message.copied": "¡Copiado!",
   "ui.message.interrupted": "Interrumpido",

+ 2 - 0
packages/ui/src/i18n/fr.ts

@@ -126,6 +126,8 @@ export const dict = {
   "ui.message.collapse": "Réduire le message",
   "ui.message.copy": "Copier",
   "ui.message.copyMessage": "Copier le message",
+  "ui.message.forkMessage": "Bifurquer vers une nouvelle session",
+  "ui.message.revertMessage": "Réinitialiser à ce point",
   "ui.message.copyResponse": "Copier la réponse",
   "ui.message.copied": "Copié !",
   "ui.message.interrupted": "Interrompu",

+ 2 - 0
packages/ui/src/i18n/ja.ts

@@ -125,6 +125,8 @@ export const dict = {
   "ui.message.collapse": "メッセージを折りたたむ",
   "ui.message.copy": "コピー",
   "ui.message.copyMessage": "メッセージをコピー",
+  "ui.message.forkMessage": "新しいセッションにフォーク",
+  "ui.message.revertMessage": "この時点までリセット",
   "ui.message.copyResponse": "応答をコピー",
   "ui.message.copied": "コピーしました!",
   "ui.message.interrupted": "中断",

+ 2 - 0
packages/ui/src/i18n/ko.ts

@@ -126,6 +126,8 @@ export const dict = {
   "ui.message.collapse": "메시지 접기",
   "ui.message.copy": "복사",
   "ui.message.copyMessage": "메시지 복사",
+  "ui.message.forkMessage": "새 세션으로 분기",
+  "ui.message.revertMessage": "이 시점으로 초기화",
   "ui.message.copyResponse": "응답 복사",
   "ui.message.copied": "복사됨!",
   "ui.message.interrupted": "중단됨",

+ 3 - 1
packages/ui/src/i18n/no.ts

@@ -105,7 +105,7 @@ export const dict: Record<Keys, string> = {
   "ui.tool.todos.read": "Les gjøremål",
   "ui.tool.questions": "Spørsmål",
   "ui.tool.agent": "{{type}}-agent",
-  "ui.tool.agent.default": "agent",
+  "ui.tool.agent.default": "Agent",
 
   "ui.common.file.one": "fil",
   "ui.common.file.other": "filer",
@@ -129,6 +129,8 @@ export const dict: Record<Keys, string> = {
   "ui.message.collapse": "Skjul melding",
   "ui.message.copy": "Kopier",
   "ui.message.copyMessage": "Kopier melding",
+  "ui.message.forkMessage": "Forgren til ny sesjon",
+  "ui.message.revertMessage": "Tilbakestill til dette punktet",
   "ui.message.copyResponse": "Kopier svar",
   "ui.message.copied": "Kopiert!",
   "ui.message.interrupted": "Avbrutt",

+ 2 - 0
packages/ui/src/i18n/pl.ts

@@ -125,6 +125,8 @@ export const dict = {
   "ui.message.collapse": "Zwiń wiadomość",
   "ui.message.copy": "Kopiuj",
   "ui.message.copyMessage": "Kopiuj wiadomość",
+  "ui.message.forkMessage": "Rozwidlij do nowej sesji",
+  "ui.message.revertMessage": "Zresetuj do tego punktu",
   "ui.message.copyResponse": "Kopiuj odpowiedź",
   "ui.message.copied": "Skopiowano!",
   "ui.message.interrupted": "Przerwano",

+ 2 - 0
packages/ui/src/i18n/ru.ts

@@ -125,6 +125,8 @@ export const dict = {
   "ui.message.collapse": "Свернуть сообщение",
   "ui.message.copy": "Копировать",
   "ui.message.copyMessage": "Копировать сообщение",
+  "ui.message.forkMessage": "Ответвить в новую сессию",
+  "ui.message.revertMessage": "Сбросить до этого момента",
   "ui.message.copyResponse": "Копировать ответ",
   "ui.message.copied": "Скопировано!",
   "ui.message.interrupted": "Прервано",

+ 2 - 0
packages/ui/src/i18n/th.ts

@@ -127,6 +127,8 @@ export const dict = {
   "ui.message.collapse": "ย่อข้อความ",
   "ui.message.copy": "คัดลอก",
   "ui.message.copyMessage": "คัดลอกข้อความ",
+  "ui.message.forkMessage": "แตกแขนงไปยังเซสชันใหม่",
+  "ui.message.revertMessage": "รีเซ็ตไปยังจุดนี้",
   "ui.message.copyResponse": "คัดลอกคำตอบ",
   "ui.message.copied": "คัดลอกแล้ว!",
   "ui.message.interrupted": "ถูกขัดจังหวะ",

+ 12 - 0
packages/ui/src/i18n/tr.ts

@@ -22,6 +22,16 @@ export const dict = {
   "ui.sessionReview.largeDiff.meta": "Limit: {{limit}} değişen satır. Mevcut: {{current}} değişen satır.",
   "ui.sessionReview.largeDiff.renderAnyway": "Yine de göster",
 
+  "ui.fileMedia.kind.image": "görsel",
+  "ui.fileMedia.kind.audio": "ses",
+  "ui.fileMedia.state.removed": "{{kind}} dosyası kaldırıldı.",
+  "ui.fileMedia.state.loading": "{{kind}} yükleniyor...",
+  "ui.fileMedia.state.error": "{{kind}} yüklenemedi.",
+  "ui.fileMedia.state.unavailable": "{{kind}} önizlemesi kullanılamıyor.",
+  "ui.fileMedia.binary.title": "İkili dosya",
+  "ui.fileMedia.binary.description.path": "{{path}} ikili dosyadır.",
+  "ui.fileMedia.binary.description.default": "İkili içerik",
+
   "ui.lineComment.label.prefix": "Yorum: ",
   "ui.lineComment.label.suffix": "",
   "ui.lineComment.editorLabel.prefix": "Yorum yapılıyor: ",
@@ -122,6 +132,8 @@ export const dict = {
   "ui.message.collapse": "Mesajı daralt",
   "ui.message.copy": "Kopyala",
   "ui.message.copyMessage": "Mesajı kopyala",
+  "ui.message.forkMessage": "Yeni oturuma dallandır",
+  "ui.message.revertMessage": "Bu noktaya sıfırla",
   "ui.message.copyResponse": "Yanıtı kopyala",
   "ui.message.copied": "Kopyalandı",
   "ui.message.interrupted": "Kesildi",

+ 2 - 0
packages/ui/src/i18n/zh.ts

@@ -130,6 +130,8 @@ export const dict = {
   "ui.message.collapse": "收起消息",
   "ui.message.copy": "复制",
   "ui.message.copyMessage": "复制消息",
+  "ui.message.forkMessage": "分叉到新会话",
+  "ui.message.revertMessage": "重置到此点",
   "ui.message.copyResponse": "复制回复",
   "ui.message.copied": "已复制!",
   "ui.message.interrupted": "已中断",

+ 2 - 0
packages/ui/src/i18n/zht.ts

@@ -130,6 +130,8 @@ export const dict = {
   "ui.message.collapse": "收合訊息",
   "ui.message.copy": "複製",
   "ui.message.copyMessage": "複製訊息",
+  "ui.message.forkMessage": "分支到新工作階段",
+  "ui.message.revertMessage": "重設至此點",
   "ui.message.copyResponse": "複製回覆",
   "ui.message.copied": "已複製!",
   "ui.message.interrupted": "已中斷",