From d3246539f1d5155583a4341b44bda851915f96aa Mon Sep 17 00:00:00 2001 From: Leo Li Date: Sun, 27 Sep 2026 07:09:45 -0400 Subject: [PATCH 01/11] agent-chat: read-only chat view of a terminal agent's transcript Adds /terminal/ to the agent-chat sidecar. It resolves the surface's Claude Code or Codex session from the hook session stores, tails the agent's own JSONL transcript, and normalizes it into AgentEvents so the existing chat renderer draws the running TUI session without a second agent process. The view is read-only; replies and prompts stay in the terminal. Adds an "Open terminal as chat" palette command behind the Agent Chat UI flag that opens the view as a split next to the focused terminal, and cmux-chat --terminal. Co-Authored-By: Claude Opus 5.5 --- Resources/Localizable.xcstrings | 59 +++ Sources/AppDelegate+AgentChat.swift | 19 + .../ContentView+AgentChatCommandPalette.swift | 24 + Sources/Workspace+AgentChat.swift | 39 ++ agent-chat/README.md | 6 + agent-chat/adapters/transcript.ts | 461 ++++++++++++++++++ agent-chat/cmux-chat | 12 +- agent-chat/public/app.css | 8 + agent-chat/server.ts | 110 ++++- agent-chat/src/components/Chat.tsx | 18 +- agent-chat/src/i18n.ts | 44 +- agent-chat/src/session.ts | 10 + agent-chat/test/i18n.test.ts | 2 + agent-chat/test/transcript.test.ts | 203 ++++++++ agent-chat/transcript-sources.ts | 175 +++++++ 15 files changed, 1178 insertions(+), 12 deletions(-) create mode 100644 agent-chat/adapters/transcript.ts create mode 100644 agent-chat/test/transcript.test.ts create mode 100644 agent-chat/transcript-sources.ts diff --git a/Resources/Localizable.xcstrings b/Resources/Localizable.xcstrings index cd140ec39ae4..11318c057501 100644 --- a/Resources/Localizable.xcstrings +++ b/Resources/Localizable.xcstrings @@ -145148,6 +145148,65 @@ } } }, + "command.openTerminalChatView.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open terminal as chat" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ターミナルをチャットで表示" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "以对话形式查看终端" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Terminal als Chat öffnen" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir le terminal en discussion" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح الطرفية كدردشة" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir la terminal como chat" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "以對話形式檢視終端機" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "터미널을 채팅으로 열기" + } + } + } + }, "command.openDiffViewer.title": { "extractionState": "manual", "localizations": { diff --git a/Sources/AppDelegate+AgentChat.swift b/Sources/AppDelegate+AgentChat.swift index 6de9213ebdf9..90999bf556e9 100644 --- a/Sources/AppDelegate+AgentChat.swift +++ b/Sources/AppDelegate+AgentChat.swift @@ -164,6 +164,25 @@ extension AppDelegate { return true } + /// Starts or reuses the agent-chat sidecar and returns its page base URL, + /// or nil when Agent Chat is off or the sidecar is unreachable. + func agentChatBrowserBaseURL(tabManager: TabManager, preferredWindow: NSWindow?) async -> URL? { + guard CmuxFeatureFlags.shared.isAgentChatUIEnabled, + BrowserAvailabilitySettings.isEnabled(), + AgentChatActionInFlightGate.begin() else { return nil } + defer { AgentChatActionInFlightGate.end() } + let store = mainWindowContext(for: tabManager)?.cmuxConfigStore + let agentChat = store?.agentChat ?? .default + AgentChatThemeSync.start() + let availability = await ensureAgentChatServerAvailable( + agentChat, + globalConfigPath: store?.globalConfigPath, + preferredWindow: preferredWindow + ) + AgentChatThemeSync.syncNow(agentChat: agentChat) + return availability.isReachable ? availability.browserURL : nil + } + @discardableResult private func openAgentChatWorkspace( tabManager: TabManager, diff --git a/Sources/ContentView+AgentChatCommandPalette.swift b/Sources/ContentView+AgentChatCommandPalette.swift index 9ab90cf3c846..bfd90bec56a6 100644 --- a/Sources/ContentView+AgentChatCommandPalette.swift +++ b/Sources/ContentView+AgentChatCommandPalette.swift @@ -33,6 +33,15 @@ extension ContentView { subtitle: { _ in String(localized: "command.newAgentChat.subtitle", defaultValue: "Agent Chat") }, keywords: ["create", "new", "agent", "chat", "browser", "codex", "claude"], when: { !$0.bool(CommandPaletteContextKeys.browserDisabled) } + ), CommandPaletteCommandContribution( + commandId: "palette.openTerminalChatView", + title: { _ in String(localized: "command.openTerminalChatView.title", defaultValue: "Open terminal as chat") }, + subtitle: { _ in String(localized: "command.newAgentChat.subtitle", defaultValue: "Agent Chat") }, + keywords: ["terminal", "chat", "view", "transcript", "gui", "agent", "claude", "codex"], + when: { + $0.bool(CommandPaletteContextKeys.panelIsTerminal) + && !$0.bool(CommandPaletteContextKeys.browserDisabled) + } )] } @@ -55,6 +64,21 @@ extension ContentView { NSSound.beep() } } + registerTerminalChatViewCommandPaletteHandler(®istry) + } + + /// Registers the read-only chat view for the focused terminal's agent. + func registerTerminalChatViewCommandPaletteHandler(_ registry: inout CommandPaletteHandlerRegistry) { + registry.register(commandId: "palette.openTerminalChatView") { + guard let context = focusedPanelContext, context.panel.panelType == .terminal else { + NSSound.beep() + return + } + context.workspace.openTerminalChatView( + terminalPanelId: context.panelId, + presentingWindow: AppDelegate.shared?.mainWindow(for: windowId) + ) + } } /// Palette context key indicating that the selected workspace is remotely managed. diff --git a/Sources/Workspace+AgentChat.swift b/Sources/Workspace+AgentChat.swift index ac4dc7ca9419..ca3c1a8568f8 100644 --- a/Sources/Workspace+AgentChat.swift +++ b/Sources/Workspace+AgentChat.swift @@ -9,4 +9,43 @@ extension Workspace { preferredWindow: presentingWindow ) } + + /// Opens a read-only chat view of the agent running in a terminal panel. + /// The agent-chat sidecar renders the agent's own transcript, so the + /// terminal process stays the only agent. + func openTerminalChatView(terminalPanelId: UUID, presentingWindow: NSWindow?) { + guard let owningTabManager, let appDelegate = AppDelegate.shared else { + NSSound.beep() + return + } + Task { @MainActor [weak self, weak owningTabManager] in + guard let owningTabManager, + let base = await appDelegate.agentChatBrowserBaseURL( + tabManager: owningTabManager, + preferredWindow: presentingWindow + ), + let self, + let url = Self.terminalChatViewURL(base: base, terminalPanelId: terminalPanelId), + self.newBrowserSplit( + from: terminalPanelId, + orientation: .horizontal, + url: url, + transparentBackground: true + ) != nil else { + NSSound.beep() + return + } + } + } + + /// `/terminal/?transparent=1`; the sidecar maps the + /// panel to its agent session through the hook session stores. + static func terminalChatViewURL(base: URL, terminalPanelId: UUID) -> URL? { + let path = base + .appendingPathComponent("terminal", isDirectory: true) + .appendingPathComponent(terminalPanelId.uuidString) + var components = URLComponents(url: path, resolvingAgainstBaseURL: false) + components?.queryItems = [URLQueryItem(name: "transparent", value: "1")] + return components?.url + } } diff --git a/agent-chat/README.md b/agent-chat/README.md index 7a756fa94e80..17e4ad5ab957 100644 --- a/agent-chat/README.md +++ b/agent-chat/README.md @@ -12,6 +12,12 @@ Three entrypoints, all landing on the same server: One page = one session: `/` is the composer, `/s/` a chat. When `CMUX_AGENT_CHAT_TOKEN` or `--token` is configured, every HTTP route, static asset, API route, and WebSocket upgrade except `/healthz` must be under `//...`; missing or wrong tokens return 404. There is deliberately no in-page session list or header; each chat is its own cmux workspace tab (page title = first prompt), so cmux's sidebar is the session list. +## Terminal chat view + +`/terminal/` shows a Claude Code or Codex session that is already running in a cmux terminal as a read-only chat, without starting another agent. The sidecar looks the surface up in the hook session stores (`~/.cmuxterm/claude-hook-sessions.json`, `~/.cmuxterm/codex-hook-sessions.json`), tails the agent's own transcript (Claude `projects//.jsonl`, Codex `sessions/YYYY/MM/DD/rollout-*-.jsonl`), and normalizes it into `AgentEvent`s with `adapters/transcript.ts`, so the regular chat renderer draws it. The page lives at `/s/t-`, which re-resolves after a sidecar restart; a view with no open page stops tailing after five minutes. + +The terminal stays the source of truth. The view has no composer: replies, permission prompts, and pickers stay in the terminal, and the footer says so. Open it with "Open terminal as chat" in the command palette (Agent Chat UI flag) or `cmux-chat --terminal --surface ` from a shell. + ## Model catalog The sidecar fetches the model catalog from `https://cmux.dev/api/agent-models` (`CMUX_AGENT_MODELS_URL` overrides it for development), revalidates it with ETags after a one-hour TTL, and caches the last-good response at `~/.cache/cmux-agent-chat/models.json` for offline startup. Refreshes happen in the background; changed catalogs are pushed to open pages so model pickers update without reloading. diff --git a/agent-chat/adapters/transcript.ts b/agent-chat/adapters/transcript.ts new file mode 100644 index 000000000000..406d07120a53 --- /dev/null +++ b/agent-chat/adapters/transcript.ts @@ -0,0 +1,461 @@ +// Read-only transcript view: renders the JSONL transcript a terminal agent +// (Claude Code, Codex) writes as it runs, so a cmux terminal can be shown as a +// chat without starting a second agent process. The terminal stays the source +// of truth; this adapter only tails the file and normalizes it into AgentEvent. +import { open, stat } from "node:fs/promises"; +import type { Adapter, AgentEvent, OptionValue, SessionCtx } from "../types"; +import { tryParse, truncate } from "./lines"; + +export type TranscriptAgent = "claude" | "codex"; + +export const TRANSCRIPT_READ_ONLY_MESSAGE = + "This view mirrors the terminal session. Switch to the terminal to reply, approve permissions, or use slash commands."; + +export interface TranscriptParser { + readonly agent: TranscriptAgent; + /** Latest session title the transcript reported (Claude `ai-title`, Codex thread name). */ + title?: string; + /** Working directory the transcript reported, when any. */ + cwd?: string; + parse(line: string): AgentEvent[]; +} + +export function transcriptParser(agent: TranscriptAgent): TranscriptParser { + return agent === "codex" ? new CodexTranscriptParser() : new ClaudeTranscriptParser(); +} + +/** Parses a whole transcript; used by tests and the initial load. */ +export function parseTranscriptText(agent: TranscriptAgent, text: string): { events: AgentEvent[]; title?: string; cwd?: string } { + const parser = transcriptParser(agent); + const events: AgentEvent[] = []; + for (const line of text.split("\n")) { + if (line.trim()) events.push(...parser.parse(line)); + } + return { events, title: parser.title, cwd: parser.cwd }; +} + +// Tool inputs worth showing as the one-line activity detail, in priority order. +const TOOL_DETAIL_KEYS = ["command", "cmd", "file_path", "path", "pattern", "url", "query", "description", "prompt", "skill"]; + +export function toolDetail(input: unknown): string { + if (input == null) return ""; + if (typeof input === "string") { + const parsed = tryParse(input); + if (parsed && typeof parsed === "object") return toolDetail(parsed); + return truncate(input); + } + if (typeof input !== "object") return truncate(String(input)); + const record = input as Record; + for (const key of TOOL_DETAIL_KEYS) { + const value = record[key]; + if (typeof value === "string" && value.trim()) return truncate(value); + if (Array.isArray(value) && value.every((v) => typeof v === "string") && value.length) return truncate(value.join(" ")); + } + return truncate(JSON.stringify(record)); +} + +function textOf(content: unknown): string { + if (typeof content === "string") return content; + if (!Array.isArray(content)) return ""; + return content + .map((c: any) => (typeof c === "string" ? c : typeof c?.text === "string" ? c.text : c?.type === "image" ? "[image]" : "")) + .filter(Boolean) + .join("\n"); +} + +function tagValue(text: string, tag: string): string | undefined { + const match = text.match(new RegExp(`<${tag}>([\\s\\S]*?)`)); + return match?.[1]?.trim(); +} + +// Claude Code wraps harness bookkeeping in pseudo-XML user messages. These are +// never something the person typed, so they are hidden from the transcript. +const CLAUDE_HIDDEN_USER_TAGS = /^<(system-reminder|local-command-caveat|user-prompt-submit-hook|agent-message)>/; + +class ClaudeTranscriptParser implements TranscriptParser { + readonly agent = "claude" as const; + title?: string; + cwd?: string; + private seen = new Set(); + + parse(line: string): AgentEvent[] { + const ev = tryParse(line); + if (!ev || typeof ev !== "object") return []; + if (ev.isSidechain) return []; + if (typeof ev.cwd === "string" && ev.cwd) this.cwd = ev.cwd; + // Claude rewrites nothing, but resumed sessions can replay an entry; a + // uuid seen before is ignored so the view does not duplicate turns. + if (typeof ev.uuid === "string") { + if (this.seen.has(ev.uuid)) return []; + this.seen.add(ev.uuid); + } + switch (ev.type) { + case "ai-title": + if (typeof ev.aiTitle === "string" && ev.aiTitle.trim()) this.title = ev.aiTitle.trim(); + return []; + case "summary": + if (typeof ev.summary === "string" && ev.summary.trim() && !this.title) this.title = ev.summary.trim(); + return []; + case "user": + return this.user(ev); + case "assistant": + return this.assistant(ev); + case "system": + return this.system(ev); + default: + return []; + } + } + + private user(ev: any): AgentEvent[] { + if (ev.isMeta || ev.isCompactSummary || ev.isVisibleInTranscriptOnly) return []; + const originKind = ev.origin?.kind; + const content = ev.message?.content; + if (originKind === "task-notification") { + const text = typeof content === "string" ? content : textOf(content); + const summary = tagValue(text, "summary") ?? tagValue(text, "status"); + return [{ kind: "status", text: summary ? `Background task: ${truncate(summary, 160)}` : "Background task update" }]; + } + if (originKind && originKind !== "human") return []; + const out: AgentEvent[] = []; + if (Array.isArray(content)) { + for (const block of content) { + if (block?.type !== "tool_result") continue; + const result = textOf(block.content); + out.push({ kind: "tool-end", toolId: String(block.tool_use_id ?? ""), ok: !block.is_error, detail: truncate(result, 400) }); + } + const prompt = content.filter((b: any) => b?.type === "text" || b?.type === "image"); + if (prompt.length) out.push(...this.prompt(textOf(prompt))); + return out; + } + if (typeof content === "string") return this.prompt(content); + return out; + } + + private prompt(raw: string): AgentEvent[] { + const text = raw.trim(); + if (!text) return []; + if (text.startsWith("[Request interrupted by user")) return [{ kind: "status", text: "Interrupted" }]; + if (CLAUDE_HIDDEN_USER_TAGS.test(text)) return []; + const command = tagValue(text, "command-name"); + if (command) { + const args = tagValue(text, "command-args"); + return [{ kind: "user", text: args ? `${command} ${args}` : command }]; + } + const stdout = tagValue(text, "local-command-stdout"); + if (stdout !== undefined) return stdout ? [{ kind: "status", text: truncate(stdout, 200) }] : []; + const bash = tagValue(text, "bash-input"); + if (bash !== undefined) return [{ kind: "user", text: `! ${bash}` }]; + if (text.startsWith("") || text.startsWith("")) { + const output = [tagValue(text, "bash-stdout"), tagValue(text, "bash-stderr")].filter(Boolean).join("\n"); + return output ? [{ kind: "status", text: truncate(output, 200) }] : []; + } + return [{ kind: "user", text }]; + } + + private assistant(ev: any): AgentEvent[] { + const out: AgentEvent[] = []; + const content = ev.message?.content; + if (ev.isApiErrorMessage) { + const message = textOf(content); + return message ? [{ kind: "error", message: truncate(message, 400) }] : []; + } + if (typeof content === "string") return content.trim() ? [{ kind: "assistant", text: content }] : []; + if (!Array.isArray(content)) return out; + for (const block of content) { + if (block?.type === "text" && typeof block.text === "string" && block.text.trim()) { + out.push({ kind: "assistant", text: block.text }); + } else if (block?.type === "thinking" && typeof block.thinking === "string" && block.thinking.trim()) { + out.push({ kind: "thinking", text: block.thinking }); + } else if (block?.type === "tool_use" || block?.type === "server_tool_use") { + out.push({ kind: "tool-start", toolId: String(block.id ?? ""), name: String(block.name ?? "tool"), detail: toolDetail(block.input) }); + } + } + return out; + } + + private system(ev: any): AgentEvent[] { + switch (ev.subtype) { + case "turn_duration": { + const ms = Number(ev.durationMs); + return [{ kind: "done", stats: Number.isFinite(ms) && ms > 0 ? `${(ms / 1000).toFixed(1)}s` : undefined }]; + } + case "compact_boundary": + return [{ kind: "status", text: "Conversation compacted" }]; + case "api_error": + return [{ kind: "error", message: truncate(String(ev.content ?? ev.error?.message ?? "API error"), 400) }]; + default: + return []; + } + } +} + +// Codex rollout files mix three generations of records: `event_msg` user and +// agent messages, `response_item` model items, and newer `item_completed` +// events. Model items carry tools and reasoning; user prompts come from the +// event records because response_item user messages include injected context. +class CodexTranscriptParser implements TranscriptParser { + readonly agent = "codex" as const; + title?: string; + cwd?: string; + private lastUser?: string; + private sinceUser = 0; + + parse(line: string): AgentEvent[] { + const ev = tryParse(line); + if (!ev || typeof ev !== "object") return []; + const payload = ev.payload ?? {}; + switch (ev.type) { + case "session_meta": + if (typeof payload.cwd === "string") this.cwd = payload.cwd; + return []; + case "turn_context": + if (typeof payload.cwd === "string") this.cwd = payload.cwd; + return []; + case "compacted": + return this.track([{ kind: "status", text: "Conversation compacted" }]); + case "event_msg": + return this.track(this.eventMsg(payload)); + case "response_item": + return this.track(this.responseItem(payload)); + default: + return []; + } + } + + private track(events: AgentEvent[]): AgentEvent[] { + for (const evt of events) { + if (evt.kind === "user") { + this.lastUser = evt.text; + this.sinceUser = 0; + } else { + this.sinceUser++; + } + } + return events; + } + + private user(text: unknown): AgentEvent[] { + const value = typeof text === "string" ? text.trim() : ""; + if (!value) return []; + // The same prompt is recorded by both `user_message` and `item_completed`. + if (value === this.lastUser && this.sinceUser === 0) return []; + return [{ kind: "user", text: value }]; + } + + private eventMsg(payload: any): AgentEvent[] { + switch (payload.type) { + case "user_message": + return this.user(payload.message); + case "item_completed": + if (payload.item?.type === "UserMessage") return this.user(textOf(payload.item.content)); + return []; + case "task_complete": + return [{ kind: "done" }]; + case "turn_aborted": + return [{ kind: "status", text: "Interrupted" }, { kind: "done" }]; + case "error": + return [{ kind: "error", message: truncate(String(payload.message ?? "error"), 400) }]; + case "thread_name_updated": + if (typeof payload.thread_name === "string" && payload.thread_name.trim()) this.title = payload.thread_name.trim(); + return []; + default: + return []; + } + } + + private responseItem(payload: any): AgentEvent[] { + switch (payload.type) { + case "message": { + if (payload.role !== "assistant") return []; + const text = textOf(payload.content).trim(); + return text ? [{ kind: "assistant", text }] : []; + } + case "reasoning": { + const text = Array.isArray(payload.summary) ? textOf(payload.summary).trim() : ""; + return text ? [{ kind: "thinking", text }] : []; + } + case "function_call": + case "custom_tool_call": + case "local_shell_call": { + const input = payload.arguments ?? payload.input ?? payload.action; + return [{ kind: "tool-start", toolId: String(payload.call_id ?? payload.id ?? ""), name: String(payload.name ?? "shell"), detail: toolDetail(input) }]; + } + case "function_call_output": + case "custom_tool_call_output": { + const output = typeof payload.output === "string" ? payload.output : textOf(payload.output?.content ?? payload.output); + return [{ kind: "tool-end", toolId: String(payload.call_id ?? ""), detail: truncate(output, 400) }]; + } + default: + return []; + } + } +} + +// Initial loads read at most this much from the end of a transcript; long +// sessions start at a line boundary inside the window. +export const TRANSCRIPT_INITIAL_WINDOW_BYTES = 8 * 1024 * 1024; +const TRANSCRIPT_POLL_MS = 500; +const TRANSCRIPT_READ_CHUNK = 1024 * 1024; + +/** Follows an append-only JSONL file by offset, delivering complete lines. */ +export class TranscriptTail { + private offset = -1; + private pending = ""; + private timer: ReturnType | null = null; + private reading = false; + private decoder = new TextDecoder(); + + constructor( + readonly path: string, + private readonly onLines: (lines: string[]) => void, + private readonly opts: { pollMs?: number; initialWindowBytes?: number } = {}, + ) {} + + start() { + if (this.timer) return; + void this.poll(); + this.timer = setInterval(() => void this.poll(), this.opts.pollMs ?? TRANSCRIPT_POLL_MS); + } + + stop() { + if (this.timer) clearInterval(this.timer); + this.timer = null; + } + + /** Reads everything appended since the last poll. Exposed for tests. */ + async poll(): Promise { + if (this.reading) return; + this.reading = true; + try { + const info = await stat(this.path).catch(() => null); + if (!info) return; + let skipPartialFirstLine = false; + if (this.offset < 0) { + const window = this.opts.initialWindowBytes ?? TRANSCRIPT_INITIAL_WINDOW_BYTES; + this.offset = Math.max(0, info.size - window); + skipPartialFirstLine = this.offset > 0; + } else if (info.size < this.offset) { + // Truncated or replaced: follow the new file from its start. + this.offset = 0; + this.pending = ""; + this.decoder = new TextDecoder(); + } + if (info.size === this.offset) return; + const handle = await open(this.path, "r"); + try { + const buf = new Uint8Array(TRANSCRIPT_READ_CHUNK); + while (this.offset < info.size) { + const { bytesRead } = await handle.read(buf, 0, Math.min(buf.length, info.size - this.offset), this.offset); + if (bytesRead <= 0) break; + this.offset += bytesRead; + this.pending += this.decoder.decode(buf.subarray(0, bytesRead), { stream: true }); + if (skipPartialFirstLine) { + const nl = this.pending.indexOf("\n"); + if (nl < 0) continue; + this.pending = this.pending.slice(nl + 1); + skipPartialFirstLine = false; + } + const lastNl = this.pending.lastIndexOf("\n"); + if (lastNl < 0) continue; + const lines = this.pending.slice(0, lastNl).split("\n").filter((l) => l.trim()); + this.pending = this.pending.slice(lastNl + 1); + if (lines.length) this.onLines(lines); + } + } finally { + await handle.close(); + } + } finally { + this.reading = false; + } + } +} + +// A turn counts as running while its last event is not a turn end and the +// transcript was written recently; Claude and Codex both flush per item. +export const TRANSCRIPT_ACTIVE_WINDOW_MS = 30_000; + +export function transcriptLooksRunning(events: AgentEvent[], lastWriteMs: number, now = Date.now()): boolean { + if (now - lastWriteMs > TRANSCRIPT_ACTIVE_WINDOW_MS) return false; + for (let i = events.length - 1; i >= 0; i--) { + const kind = events[i].kind; + if (kind === "done") return false; + if (kind === "user" || kind === "tool-start" || kind === "tool-end" || kind === "thinking" || kind === "assistant" || kind === "status") return true; + } + return false; +} + +interface TranscriptState { + tail: TranscriptTail; + parser: TranscriptParser; + lastWriteMs: number; + statusTimer: ReturnType; +} + +function transcriptState(sess: SessionCtx): TranscriptState | undefined { + return sess.internal.transcript as TranscriptState | undefined; +} + +/** + * Starts tailing `path` into `sess`. `onTitle` fires when the transcript + * reports a new title so the server can refresh the page title. + */ +export function attachTranscript( + sess: SessionCtx, + agent: TranscriptAgent, + path: string, + onTitle?: (title: string) => void, + opts: { pollMs?: number; initialWindowBytes?: number } = {}, +): TranscriptTail { + const parser = transcriptParser(agent); + const refreshStatus = () => { + const st = transcriptState(sess); + if (!st) return; + sess.setStatus(transcriptLooksRunning(sess.events, st.lastWriteMs) ? "running" : "idle"); + }; + const tail = new TranscriptTail(path, (lines) => { + const st = transcriptState(sess); + if (st) st.lastWriteMs = Date.now(); + const title = parser.title; + for (const line of lines) { + for (const evt of parser.parse(line)) sess.emit(evt); + } + if (parser.title && parser.title !== title) onTitle?.(parser.title); + refreshStatus(); + }, opts); + const state: TranscriptState = { + tail, + parser, + lastWriteMs: 0, + statusTimer: setInterval(refreshStatus, 5_000), + }; + sess.internal.transcript = state; + sess.internal.transcriptPath = path; + // Seed the write time from the file so a session that is mid-turn right + // now renders as running on first paint. + stat(path).then((info) => { + if (transcriptState(sess) === state && !state.lastWriteMs) state.lastWriteMs = info.mtimeMs; + }, () => {}); + tail.start(); + return tail; +} + +export const transcriptAdapter: Adapter = { + send(sess: SessionCtx) { + sess.emit({ kind: "status", text: TRANSCRIPT_READ_ONLY_MESSAGE }); + }, + stop() { + // The agent runs in the terminal; interrupting it belongs there. + }, + dispose(sess: SessionCtx) { + const st = transcriptState(sess); + if (!st) return; + st.tail.stop(); + clearInterval(st.statusTimer); + delete sess.internal.transcript; + }, + async setOption(_sess: SessionCtx, _id: string, _value: OptionValue) { + throw new Error("operation is not supported in a transcript view"); + }, + capabilities: { options: [], triggers: [] }, +}; diff --git a/agent-chat/cmux-chat b/agent-chat/cmux-chat index 7cb212a3e809..61ea595fbc33 100755 --- a/agent-chat/cmux-chat +++ b/agent-chat/cmux-chat @@ -6,6 +6,8 @@ # cmux-chat -p codex refactor foo pick provider (claude/codex/opencode/pi/gemini) # cmux-chat --split open as a split in the current workspace instead # cmux-chat --no-open ... print the URL only +# cmux-chat --terminal [--surface ID] read-only chat view of the agent in a +# terminal ($CMUX_SURFACE_ID), as a split # # Starts the sidecar server (bun server.ts) if it isn't running. set -euo pipefail @@ -23,6 +25,7 @@ if [ -n "${CMUX_AGENT_CHAT_STATE_FILE:-}" ]; then fi provider="claude" cwd="" open_mode="tab" auto_approve=true no_open=false +terminal=false surface="${CMUX_SURFACE_ID:-}" prompt=() while [ $# -gt 0 ]; do case "$1" in @@ -31,8 +34,10 @@ while [ $# -gt 0 ]; do --split) open_mode="split"; shift ;; --tab) open_mode="tab"; shift ;; --no-open) no_open=true; shift ;; + --terminal) terminal=true; open_mode="split"; shift ;; + --surface) surface="$2"; shift 2 ;; --no-auto-approve) auto_approve=false; shift ;; - -h|--help) sed -n '2,10p' "$SELF" | sed 's/^# \{0,1\}//'; exit 0 ;; + -h|--help) sed -n '2,12p' "$SELF" | sed 's/^# \{0,1\}//'; exit 0 ;; *) prompt+=("$1"); shift ;; esac done @@ -49,7 +54,10 @@ if ! curl -fsS --max-time 1 "$BASE/healthz" >/dev/null 2>&1; then fi url="$BASE/" -if [ ${#prompt[@]} -gt 0 ]; then +if $terminal; then + [ -n "$surface" ] || { echo "--terminal needs --surface or CMUX_SURFACE_ID" >&2; exit 1; } + url="$BASE/terminal/$surface" +elif [ ${#prompt[@]} -gt 0 ]; then body=$(printf '%s' "${prompt[*]}" | \ CC_PROVIDER="$provider" CC_CWD="${cwd:-$PWD}" CC_AUTO="$auto_approve" python3 -c ' import json, sys, os diff --git a/agent-chat/public/app.css b/agent-chat/public/app.css index da0ef07ff13a..0ff5198358bb 100644 --- a/agent-chat/public/app.css +++ b/agent-chat/public/app.css @@ -670,6 +670,14 @@ details.thinking .t-body { white-space: pre-wrap; font-size: calc(var(--font-siz background: color-mix(in srgb, var(--accent) 10%, var(--bg-input)); color: var(--text-dim); font: 400 calc(var(--font-size-base) - 2px) var(--font-sans); } +.transcript-notice { + display: flex; align-items: center; gap: 8px; padding: 9px 12px; border-radius: 12px; + background: var(--bg-input); color: var(--text-dim); font: 400 calc(var(--font-size-base) - 2px) var(--font-sans); +} +.transcript-dot { flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--text-faint); } +.transcript-dot.running { background: var(--accent); animation: transcript-pulse 1.4s ease-in-out infinite; } +@keyframes transcript-pulse { 50% { opacity: 0.35; } } +@media (prefers-reduced-motion: reduce) { .transcript-dot.running { animation: none; } } #chat-card { display: flex; flex-direction: column; gap: 6px; padding: 6px; background: var(--bg-input); border: 0; border-radius: 14px; diff --git a/agent-chat/server.ts b/agent-chat/server.ts index a86b88690ece..4d4671570bbc 100644 --- a/agent-chat/server.ts +++ b/agent-chat/server.ts @@ -16,6 +16,8 @@ import { claudeAdapter } from "./adapters/claude"; import { codexAdapter } from "./adapters/codex"; import { piAdapter } from "./adapters/pi"; import { makeAcpAdapter } from "./adapters/acp"; +import { attachTranscript, transcriptAdapter, type TranscriptAgent } from "./adapters/transcript"; +import { resolveSessionTranscript, resolveSurfaceTranscript, type TranscriptSource } from "./transcript-sources"; import { pickAccentColor, resolveGhosttyTheme, resolveGhosttyThemeAsync, type GhosttyTheme } from "./theme"; import { agentModelCatalog, type AgentModelProviderCatalog } from "./catalog"; import { discoverHarnesses } from "./harnesses"; @@ -171,6 +173,8 @@ interface Session extends SessionCtx { adapter: Adapter; sockets: Set>; createdAt: number; + /** Set for read-only views of a terminal agent's transcript. */ + transcript?: { agent: TranscriptAgent; path: string; disposeTimer?: ReturnType }; } interface WsData { subscribed: string | null; @@ -237,7 +241,8 @@ function sessionSummary(s: Session) { parentSessionId: s.parentSessionId, parentConversationId: s.parentConversationId, startRequestId: s.startRequestId, - capabilities: capabilitiesFor(s.provider), + capabilities: s.transcript ? s.adapter.capabilities : capabilitiesFor(s.provider), + ...(s.transcript ? { mode: "transcript" as const } : {}), }; } @@ -346,10 +351,11 @@ function createSession( parentConversationId?: string; startRequestId?: string; } = {}, + override: { id?: string; adapter?: Adapter } = {}, ): Session { - const adapter = adapters.get(provider); + const adapter = override.adapter ?? adapters.get(provider); if (!adapter) throw new Error(`unknown provider: ${provider}`); - const id = crypto.randomUUID().slice(0, 8); + const id = override.id ?? crypto.randomUUID().slice(0, 8); const sess: Session = { id, provider, @@ -563,6 +569,11 @@ function emitDoneAfterFiles(sess: Session, evt: InternalDoneEvent) { } function sendPrompt(sess: Session, prompt: string, requestId = crypto.randomUUID()) { + if (sess.transcript) { + // Transcript views have no agent process; the adapter explains that. + void sess.adapter.send(sess, prompt); + return; + } emitRouting(sess, { phase: "started", requestId, attempt: 1, provider: sess.provider }); const activeGeneration = activeAttributionGeneration(sess); if (adapterAttributionMode(sess) === "current-turn" && activeGeneration) { @@ -602,6 +613,75 @@ function sendPrompt(sess: Session, prompt: string, requestId = crypto.randomUUID }); } +// Transcript views are keyed by the agent's own session id so a reload after +// a sidecar restart can re-resolve the same view from the hook stores. +const TRANSCRIPT_SESSION_PREFIX = "t-"; +const TRANSCRIPT_IDLE_DISPOSE_MS = 5 * 60_000; + +function transcriptSessionId(source: TranscriptSource): string { + return `${TRANSCRIPT_SESSION_PREFIX}${source.sessionId}`; +} + +function transcriptTitle(source: TranscriptSource): string { + const label = source.agent === "codex" ? "Codex" : "Claude Code"; + return source.cwd ? `${label} · ${pathBasename(source.cwd)}` : label; +} + +function ensureTranscriptSession(source: TranscriptSource): Session { + const id = transcriptSessionId(source); + const existing = sessions.get(id); + if (existing?.transcript?.path === source.path) return existing; + if (existing) { + existing.adapter.dispose(existing); + sessions.delete(id); + } + const sess = createSession(source.agent, source.cwd ?? DEFAULT_CWD, false, transcriptTitle(source), {}, {}, { + id, + adapter: transcriptAdapter, + }); + sess.transcript = { agent: source.agent, path: source.path }; + attachTranscript(sess, source.agent, source.path, (title) => { + if (sess.title === title) return; + sess.title = title; + broadcastSessions(); + const payload = JSON.stringify({ kind: "session-title", sessionId: sess.id, title }); + for (const ws of sess.sockets) ws.send(payload); + }); + return sess; +} + +function resolveTranscriptSessionById(id: string): Session | undefined { + if (!id.startsWith(TRANSCRIPT_SESSION_PREFIX)) return undefined; + const source = resolveSessionTranscript(id.slice(TRANSCRIPT_SESSION_PREFIX.length)); + return source ? ensureTranscriptSession(source) : undefined; +} + +function cancelTranscriptDispose(sess: Session) { + if (sess.transcript?.disposeTimer) clearTimeout(sess.transcript.disposeTimer); + if (sess.transcript) sess.transcript.disposeTimer = undefined; +} + +// A transcript view with no open page stops tailing after a grace period; the +// next visit re-resolves it from the hook stores. +function scheduleTranscriptDispose(sess: Session) { + if (!sess.transcript || sess.sockets.size) return; + cancelTranscriptDispose(sess); + sess.transcript.disposeTimer = setTimeout(() => { + if (sess.sockets.size || sessions.get(sess.id) !== sess) return; + sess.adapter.dispose(sess); + sessions.delete(sess.id); + broadcastSessions(); + }, TRANSCRIPT_IDLE_DISPOSE_MS); +} + +function transcriptNotFoundPage(): Response { + const body = `No agent session + +

No Claude Code or Codex session is recorded for this terminal yet.

+

Start the agent in the terminal (cmux hooks must be enabled), then reopen the chat view.

`; + return new Response(body, { status: 404, headers: { "content-type": "text/html; charset=utf-8" } }); +} + function refreshSession(sess: Session) { Promise.resolve(sess.adapter.refreshOptions?.(sess)).catch((err) => { console.error("[agent-chat] refresh-options failed", err); @@ -1916,6 +1996,14 @@ function startServer() { if (prompt) sendPrompt(sess, prompt); return Response.json({ id: sess.id, url: `http://127.0.0.1:${server.port}${prefixedPath(`/s/${sess.id}`)}` }); } + const terminalMatch = url.pathname.match(/^\/terminal\/([0-9A-Fa-f-]{8,64})\/?$/); + if (terminalMatch && req.method === "GET") { + const source = resolveSurfaceTranscript(terminalMatch[1]); + if (!source) return transcriptNotFoundPage(); + const sess = ensureTranscriptSession(source); + scheduleTranscriptDispose(sess); + return new Response(null, { status: 302, headers: { location: `${prefixedPath(`/s/${sess.id}`)}${url.search}` } }); + } if (url.pathname === "/api/sessions" && req.method === "GET") { return Response.json([...sessions.values()].sort((a, b) => b.createdAt - a.createdAt).map(sessionSummary)); } @@ -1944,7 +2032,11 @@ function startServer() { close(ws) { allSockets.delete(ws); const sid = ws.data.subscribed; - if (sid) sessions.get(sid)?.sockets.delete(ws); + const sess = sid ? sessions.get(sid) : undefined; + if (sess) { + sess.sockets.delete(ws); + scheduleTranscriptDispose(sess); + } }, message(ws, raw) { let msg: any; @@ -2101,7 +2193,8 @@ function handleMessage(ws: Bun.ServerWebSocket, msg: any) { break; } case "subscribe": { - const sess = sessions.get(String(msg.sessionId)); + const sessionId = String(msg.sessionId); + const sess = sessions.get(sessionId) ?? resolveTranscriptSessionById(sessionId); if (!sess) { ws.send(JSON.stringify({ kind: "no-session", sessionId: msg.sessionId })); return; @@ -2233,9 +2326,14 @@ function handleMessage(ws: Bun.ServerWebSocket, msg: any) { function subscribe(ws: Bun.ServerWebSocket, sess: Session) { const prev = ws.data.subscribed; - if (prev) sessions.get(prev)?.sockets.delete(ws); + const prevSess = prev ? sessions.get(prev) : undefined; + if (prevSess && prevSess !== sess) { + prevSess.sockets.delete(ws); + scheduleTranscriptDispose(prevSess); + } ws.data.subscribed = sess.id; sess.sockets.add(ws); + cancelTranscriptDispose(sess); } process.on("SIGINT", () => { diff --git a/agent-chat/src/components/Chat.tsx b/agent-chat/src/components/Chat.tsx index f81e28078ee9..634fb26ac637 100644 --- a/agent-chat/src/components/Chat.tsx +++ b/agent-chat/src/components/Chat.tsx @@ -2,7 +2,7 @@ import { useEffect, useLayoutEffect, useMemo, useRef, useState, type RefObject } import { useCtx } from "../context"; import { agentChatText } from "../i18n"; import { readStoredProviderOptions, persistOptionsSnapshot, updateStoredProviderOption } from "../options-store"; -import type { OptionValue, SessionOption } from "../session"; +import { routedToTranscript, type OptionValue, type SessionOption } from "../session"; import { ArrowUp } from "./icons"; import { isCtrlJ, insertNewlineAtCaret, useCommandMenu } from "./CommandMenu"; import { optionAcceptsValue, optionsForSelectedModel } from "./options"; @@ -72,12 +72,15 @@ export function Chat() { [providerOptions, providers], ); const running = session?.status === "running"; + // Transcript views mirror a terminal agent: no composer, catalogs, or files. + const transcriptView = session ? session.mode === "transcript" : routedToTranscript; + const catalogCwd = transcriptView ? "" : cwd; const resolvedOptions = useMemo(() => optionsForSelectedModel(options), [options]); useRestoreModelScopedOptions({ provider: session?.provider, options: resolvedOptions, setOption, pendingModelRestoreRef }); usePersistSessionOptions(session?.provider, resolvedOptions, pendingModelRestoreRef.current !== null); - useProviderCatalogs(ready, connectionEpoch, providers, session?.provider ?? "", cwd, requestProviderOptions, requestProviderCommands); - useFileCatalog(ready, connectionEpoch, cwd, requestFiles); + useProviderCatalogs(ready, connectionEpoch, providers, session?.provider ?? "", catalogCwd, requestProviderOptions, requestProviderCommands); + useFileCatalog(ready, connectionEpoch, catalogCwd, requestFiles); useStickToBottom(scrollRef, stickRef, blocks, running); useKeymap({ options: resolvedOptions, @@ -140,6 +143,14 @@ export function Chat() { onFileDiff={(path) => { if (session) requestFileDiff(session.id, path); }} /> + {transcriptView ? ( +
+
+
+
+ ) : (
{routing?.phase === "handoff" ? (
{agentChatText("continuedNewChat")}
@@ -189,6 +200,7 @@ export function Chat() { />
+ )} {helpOpen ? setHelpOpen(false)} /> : null} ); diff --git a/agent-chat/src/i18n.ts b/agent-chat/src/i18n.ts index 149d8c2449e6..44a66867a2b6 100644 --- a/agent-chat/src/i18n.ts +++ b/agent-chat/src/i18n.ts @@ -26,7 +26,9 @@ export type AgentChatTextKey = | "continuedNewChat" | "movedServingRoute" | "continueNewChat" - | "continueElsewhere"; + | "continueElsewhere" + | "transcriptViewRunning" + | "transcriptViewIdle"; const COPY: Record> = { en: { @@ -34,120 +36,160 @@ const COPY: Record> = { movedServingRoute: "Moved to another serving route. Conversation context was preserved.", continueNewChat: "Continue in new chat", continueElsewhere: "Continue elsewhere", + transcriptViewRunning: "Agent is working in the terminal. Switch to the terminal to reply or approve.", + transcriptViewIdle: "Mirroring the terminal session. Switch to the terminal to reply, approve, or run commands.", }, ja: { continuedNewChat: "新しいチャットで続行しました。以前のコンテキストはリンクされています。", movedServingRoute: "別の配信ルートに移動しました。会話のコンテキストは保持されています。", continueNewChat: "新しいチャットで続行", continueElsewhere: "別の場所で続行", + transcriptViewRunning: "エージェントがターミナルで作業中です。返信や承認はターミナルで行ってください。", + transcriptViewIdle: "ターミナルのセッションを表示しています。返信、承認、コマンド実行はターミナルで行ってください。", }, "zh-CN": { continuedNewChat: "已在新聊天中继续。之前的上下文已关联。", movedServingRoute: "已切换到其他服务路由。对话上下文已保留。", continueNewChat: "在新聊天中继续", continueElsewhere: "在其他位置继续", + transcriptViewRunning: "代理正在终端中工作。请切换到终端进行回复或批准。", + transcriptViewIdle: "正在镜像终端会话。请切换到终端进行回复、批准或运行命令。", }, "zh-TW": { continuedNewChat: "已在新聊天中繼續。先前的內容已連結。", movedServingRoute: "已切換至其他服務路由。對話內容已保留。", continueNewChat: "在新聊天中繼續", continueElsewhere: "在其他位置繼續", + transcriptViewRunning: "代理程式正在終端機中工作。請切換到終端機回覆或核准。", + transcriptViewIdle: "正在鏡像終端機工作階段。請切換到終端機回覆、核准或執行指令。", }, ko: { continuedNewChat: "새 채팅에서 계속합니다. 이전 컨텍스트가 연결되어 있습니다.", movedServingRoute: "다른 서비스 경로로 이동했습니다. 대화 컨텍스트는 유지되었습니다.", continueNewChat: "새 채팅에서 계속", continueElsewhere: "다른 곳에서 계속", + transcriptViewRunning: "에이전트가 터미널에서 작업 중입니다. 답장하거나 승인하려면 터미널로 전환하세요.", + transcriptViewIdle: "터미널 세션을 미러링하고 있습니다. 답장, 승인, 명령 실행은 터미널에서 하세요.", }, de: { continuedNewChat: "In einem neuen Chat fortgesetzt. Der vorherige Kontext ist verknüpft.", movedServingRoute: "Auf eine andere Bereitstellungsroute gewechselt. Der Gesprächskontext wurde beibehalten.", continueNewChat: "In neuem Chat fortfahren", continueElsewhere: "Anderswo fortfahren", + transcriptViewRunning: "Der Agent arbeitet im Terminal. Zum Antworten oder Freigeben ins Terminal wechseln.", + transcriptViewIdle: "Spiegelt die Terminalsitzung. Zum Antworten, Freigeben oder für Befehle ins Terminal wechseln.", }, es: { continuedNewChat: "Se continuó en un chat nuevo. El contexto anterior está vinculado.", movedServingRoute: "Se cambió a otra ruta de servicio. Se conservó el contexto de la conversación.", continueNewChat: "Continuar en un chat nuevo", continueElsewhere: "Continuar en otro lugar", + transcriptViewRunning: "El agente está trabajando en la terminal. Cambia a la terminal para responder o aprobar.", + transcriptViewIdle: "Reflejando la sesión de la terminal. Cambia a la terminal para responder, aprobar o ejecutar comandos.", }, fr: { continuedNewChat: "La conversation continue dans un nouveau chat. Le contexte précédent est lié.", movedServingRoute: "Passage à une autre route de service. Le contexte de la conversation a été conservé.", continueNewChat: "Continuer dans un nouveau chat", continueElsewhere: "Continuer ailleurs", + transcriptViewRunning: "L’agent travaille dans le terminal. Passez au terminal pour répondre ou approuver.", + transcriptViewIdle: "Miroir de la session du terminal. Passez au terminal pour répondre, approuver ou lancer des commandes.", }, it: { continuedNewChat: "Continuazione in una nuova chat. Il contesto precedente è collegato.", movedServingRoute: "Passaggio a un altro percorso di servizio. Il contesto della conversazione è stato mantenuto.", continueNewChat: "Continua in una nuova chat", continueElsewhere: "Continua altrove", + transcriptViewRunning: "L’agente sta lavorando nel terminale. Passa al terminale per rispondere o approvare.", + transcriptViewIdle: "Rispecchia la sessione del terminale. Passa al terminale per rispondere, approvare o eseguire comandi.", }, da: { continuedNewChat: "Fortsat i en ny chat. Den tidligere kontekst er knyttet til.", movedServingRoute: "Flyttet til en anden serveringsrute. Samtalens kontekst blev bevaret.", continueNewChat: "Fortsæt i ny chat", continueElsewhere: "Fortsæt et andet sted", + transcriptViewRunning: "Agenten arbejder i terminalen. Skift til terminalen for at svare eller godkende.", + transcriptViewIdle: "Spejler terminalsessionen. Skift til terminalen for at svare, godkende eller køre kommandoer.", }, pl: { continuedNewChat: "Kontynuowano w nowym czacie. Poprzedni kontekst jest połączony.", movedServingRoute: "Przeniesiono na inną trasę obsługi. Kontekst rozmowy został zachowany.", continueNewChat: "Kontynuuj w nowym czacie", continueElsewhere: "Kontynuuj gdzie indziej", + transcriptViewRunning: "Agent pracuje w terminalu. Przełącz się do terminala, aby odpowiedzieć lub zatwierdzić.", + transcriptViewIdle: "Odzwierciedla sesję terminala. Przełącz się do terminala, aby odpowiedzieć, zatwierdzić lub uruchomić polecenia.", }, ru: { continuedNewChat: "Продолжено в новом чате. Предыдущий контекст связан.", movedServingRoute: "Переключено на другой маршрут обслуживания. Контекст разговора сохранён.", continueNewChat: "Продолжить в новом чате", continueElsewhere: "Продолжить в другом месте", + transcriptViewRunning: "Агент работает в терминале. Перейдите в терминал, чтобы ответить или подтвердить.", + transcriptViewIdle: "Отображается сеанс терминала. Перейдите в терминал, чтобы ответить, подтвердить или выполнить команды.", }, bs: { continuedNewChat: "Nastavljeno u novom chatu. Prethodni kontekst je povezan.", movedServingRoute: "Prebačeno na drugu servisnu rutu. Kontekst razgovora je sačuvan.", continueNewChat: "Nastavi u novom chatu", continueElsewhere: "Nastavi drugdje", + transcriptViewRunning: "Agent radi u terminalu. Pređite u terminal da odgovorite ili odobrite.", + transcriptViewIdle: "Prikazuje se sesija terminala. Pređite u terminal da odgovorite, odobrite ili pokrenete komande.", }, ar: { continuedNewChat: "تمت المتابعة في محادثة جديدة. السياق السابق مرتبط.", movedServingRoute: "تم الانتقال إلى مسار خدمة آخر. تم الاحتفاظ بسياق المحادثة.", continueNewChat: "المتابعة في محادثة جديدة", continueElsewhere: "المتابعة في مكان آخر", + transcriptViewRunning: "الوكيل يعمل في الطرفية. انتقل إلى الطرفية للرد أو الموافقة.", + transcriptViewIdle: "يتم عرض جلسة الطرفية. انتقل إلى الطرفية للرد أو الموافقة أو تشغيل الأوامر.", }, no: { continuedNewChat: "Fortsatt i en ny chat. Tidligere kontekst er koblet til.", movedServingRoute: "Flyttet til en annen serveringsrute. Samtalekonteksten ble bevart.", continueNewChat: "Fortsett i ny chat", continueElsewhere: "Fortsett et annet sted", + transcriptViewRunning: "Agenten jobber i terminalen. Bytt til terminalen for å svare eller godkjenne.", + transcriptViewIdle: "Speiler terminaløkten. Bytt til terminalen for å svare, godkjenne eller kjøre kommandoer.", }, "pt-BR": { continuedNewChat: "Continuado em um novo chat. O contexto anterior está vinculado.", movedServingRoute: "Movido para outra rota de atendimento. O contexto da conversa foi preservado.", continueNewChat: "Continuar em um novo chat", continueElsewhere: "Continuar em outro lugar", + transcriptViewRunning: "O agente está trabalhando no terminal. Mude para o terminal para responder ou aprovar.", + transcriptViewIdle: "Espelhando a sessão do terminal. Mude para o terminal para responder, aprovar ou executar comandos.", }, th: { continuedNewChat: "ดำเนินการต่อในแชทใหม่แล้ว โดยเชื่อมโยงบริบทก่อนหน้าไว้", movedServingRoute: "ย้ายไปยังเส้นทางการให้บริการอื่นแล้ว โดยยังคงบริบทการสนทนาไว้", continueNewChat: "ดำเนินการต่อในแชทใหม่", continueElsewhere: "ดำเนินการต่อที่อื่น", + transcriptViewRunning: "เอเจนต์กำลังทำงานในเทอร์มินัล สลับไปที่เทอร์มินัลเพื่อตอบกลับหรืออนุมัติ", + transcriptViewIdle: "กำลังแสดงเซสชันเทอร์มินัล สลับไปที่เทอร์มินัลเพื่อตอบกลับ อนุมัติ หรือเรียกใช้คำสั่ง", }, tr: { continuedNewChat: "Yeni bir sohbette devam edildi. Önceki bağlam bağlantılı.", movedServingRoute: "Başka bir hizmet rotasına geçildi. Konuşma bağlamı korundu.", continueNewChat: "Yeni sohbette devam et", continueElsewhere: "Başka yerde devam et", + transcriptViewRunning: "Ajan terminalde çalışıyor. Yanıtlamak veya onaylamak için terminale geçin.", + transcriptViewIdle: "Terminal oturumu yansıtılıyor. Yanıtlamak, onaylamak veya komut çalıştırmak için terminale geçin.", }, km: { continuedNewChat: "បានបន្តនៅក្នុងការជជែកថ្មី។ បរិបទមុនត្រូវបានភ្ជាប់។", movedServingRoute: "បានប្តូរទៅផ្លូវបម្រើផ្សេង។ បរិបទនៃការសន្ទនាត្រូវបានរក្សាទុក។", continueNewChat: "បន្តក្នុងការជជែកថ្មី", continueElsewhere: "បន្តនៅកន្លែងផ្សេង", + transcriptViewRunning: "ភ្នាក់ងារកំពុងធ្វើការនៅក្នុងទែមីណាល់។ ប្ដូរទៅទែមីណាល់ដើម្បីឆ្លើយតប ឬអនុម័ត។", + transcriptViewIdle: "កំពុងបង្ហាញវគ្គទែមីណាល់។ ប្ដូរទៅទែមីណាល់ដើម្បីឆ្លើយតប អនុម័ត ឬដំណើរការពាក្យបញ្ជា។", }, uk: { continuedNewChat: "Продовжено в новому чаті. Попередній контекст пов’язано.", movedServingRoute: "Перемкнено на інший маршрут обслуговування. Контекст розмови збережено.", continueNewChat: "Продовжити в новому чаті", continueElsewhere: "Продовжити в іншому місці", + transcriptViewRunning: "Агент працює в терміналі. Перейдіть у термінал, щоб відповісти або підтвердити.", + transcriptViewIdle: "Відображається сеанс термінала. Перейдіть у термінал, щоб відповісти, підтвердити або виконати команди.", }, }; diff --git a/agent-chat/src/session.ts b/agent-chat/src/session.ts index e509426cad0b..812571689d34 100644 --- a/agent-chat/src/session.ts +++ b/agent-chat/src/session.ts @@ -155,6 +155,8 @@ export interface SessionSummary { parentSessionId?: string; parentConversationId?: string; startRequestId?: string; + /** "transcript": a read-only view of an agent running in a cmux terminal. */ + mode?: "transcript"; } export type CtrlJMode = "newline" | "menu"; @@ -273,6 +275,8 @@ function appPath(path: string): string { } const routedSessionId = (routePath().match(/^\/s\/([\w-]+)/) || [])[1] || null; +/** Transcript views use `t-`; known before history arrives. */ +export const routedToTranscript = routedSessionId?.startsWith("t-") ?? false; export const composerDraftKey = "agentui.draft"; const PENDING_START_TIMEOUT_MS = 30_000; @@ -502,6 +506,12 @@ export function useSession(): SessionState { setSession((s) => (s ? { ...s, status: msg.status } : s)); } break; + case "session-title": + if (msg.sessionId === sessionIdRef.current && typeof msg.title === "string") { + document.title = msg.title || "cmux agent"; + setSession((s) => (s ? { ...s, title: msg.title } : s)); + } + break; case "event": if (msg.sessionId === sessionIdRef.current) { const evt = msg.evt as AgentEvent; diff --git a/agent-chat/test/i18n.test.ts b/agent-chat/test/i18n.test.ts index a929a5e1e9a8..ada885c02f0a 100644 --- a/agent-chat/test/i18n.test.ts +++ b/agent-chat/test/i18n.test.ts @@ -19,6 +19,8 @@ const keys: AgentChatTextKey[] = [ "movedServingRoute", "continueNewChat", "continueElsewhere", + "transcriptViewRunning", + "transcriptViewIdle", ]; const english = agentChatCopyForLocale("en"); diff --git a/agent-chat/test/transcript.test.ts b/agent-chat/test/transcript.test.ts new file mode 100644 index 000000000000..387d3afc629d --- /dev/null +++ b/agent-chat/test/transcript.test.ts @@ -0,0 +1,203 @@ +import { afterEach, describe, expect, test } from "bun:test"; +import { appendFileSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { parseTranscriptText, TranscriptTail, transcriptLooksRunning, toolDetail } from "../adapters/transcript"; +import { claudeProjectSlug, resolveSessionTranscript, resolveSurfaceTranscript } from "../transcript-sources"; +import type { AgentEvent } from "../types"; + +const jsonl = (...rows: unknown[]) => rows.map((r) => JSON.stringify(r)).join("\n") + "\n"; + +const temps: string[] = []; +function tempDir(): string { + const dir = mkdtempSync(join(tmpdir(), "agent-chat-transcript-")); + temps.push(dir); + return dir; +} +afterEach(() => { + for (const dir of temps.splice(0)) rmSync(dir, { recursive: true, force: true }); +}); + +describe("Claude transcript parser", () => { + const claude = jsonl( + { type: "user", uuid: "u1", cwd: "/work/app", message: { role: "user", content: "fix the flaky test" }, origin: { kind: "human" } }, + { type: "user", uuid: "m1", isMeta: true, message: { role: "user", content: "hidden" } }, + { type: "assistant", uuid: "a1", message: { content: [{ type: "thinking", thinking: "look at the test first" }] } }, + { type: "assistant", uuid: "a2", message: { content: [{ type: "tool_use", id: "t1", name: "Bash", input: { command: "bun test flaky", description: "run" } }] } }, + { type: "assistant", uuid: "a2", message: { content: [{ type: "tool_use", id: "t1", name: "Bash", input: { command: "bun test flaky" } }] } }, + { type: "user", uuid: "r1", message: { role: "user", content: [{ type: "tool_result", tool_use_id: "t1", content: "1 fail", is_error: true }] } }, + { type: "assistant", uuid: "side", isSidechain: true, message: { content: [{ type: "text", text: "subagent chatter" }] } }, + { type: "assistant", uuid: "a3", message: { content: [{ type: "text", text: "Fixed the race in `setup`." }] } }, + { type: "system", uuid: "s1", subtype: "turn_duration", durationMs: 4200 }, + { type: "ai-title", aiTitle: "Fix flaky test" }, + { type: "user", uuid: "u2", message: { role: "user", content: "/model\nmodel\nopus" } }, + { type: "user", uuid: "u3", message: { role: "user", content: "Set model to opus" } }, + { type: "user", uuid: "u4", origin: { kind: "task-notification" }, message: { role: "user", content: "Build finished" } }, + { type: "user", uuid: "u5", origin: { kind: "peer" }, message: { role: "user", content: "Another session sent a message" } }, + { type: "user", uuid: "u6", message: { role: "user", content: [{ type: "text", text: "[Request interrupted by user]" }] } }, + { type: "assistant", uuid: "e1", isApiErrorMessage: true, message: { content: [{ type: "text", text: "API Error: overloaded" }] } }, + { type: "system", uuid: "c1", subtype: "compact_boundary", content: "Conversation compacted" }, + { type: "user", uuid: "c2", isCompactSummary: true, message: { role: "user", content: "This session is being continued..." } }, + "not json", + ); + + test("normalizes a session into AgentEvents", () => { + const { events, title, cwd } = parseTranscriptText("claude", claude); + expect(title).toBe("Fix flaky test"); + expect(cwd).toBe("/work/app"); + expect(events).toEqual([ + { kind: "user", text: "fix the flaky test" }, + { kind: "thinking", text: "look at the test first" }, + { kind: "tool-start", toolId: "t1", name: "Bash", detail: "bun test flaky" }, + { kind: "tool-end", toolId: "t1", ok: false, detail: "1 fail" }, + { kind: "assistant", text: "Fixed the race in `setup`." }, + { kind: "done", stats: "4.2s" }, + { kind: "user", text: "/model opus" }, + { kind: "status", text: "Set model to opus" }, + { kind: "status", text: "Background task: Build finished" }, + { kind: "status", text: "Interrupted" }, + { kind: "error", message: "API Error: overloaded" }, + { kind: "status", text: "Conversation compacted" }, + ] satisfies AgentEvent[]); + }); + + test("tool detail prefers the meaningful input field", () => { + expect(toolDetail({ file_path: "/a/b.ts", limit: 20 })).toBe("/a/b.ts"); + expect(toolDetail('{"cmd":["ls","-la"]}')).toBe("ls -la"); + expect(toolDetail({ other: 1 })).toBe('{"other":1}'); + }); +}); + +describe("Codex rollout parser", () => { + const codex = jsonl( + { type: "session_meta", payload: { id: "abc", cwd: "/work/api" } }, + { type: "response_item", payload: { type: "message", role: "user", content: [{ type: "input_text", text: "..." }] } }, + { type: "event_msg", payload: { type: "user_message", message: "add a health route" } }, + { type: "event_msg", payload: { type: "item_completed", item: { type: "UserMessage", content: [{ type: "text", text: "add a health route" }] } } }, + { type: "response_item", payload: { type: "reasoning", summary: [{ type: "summary_text", text: "Check the router" }], encrypted_content: "x" } }, + { type: "response_item", payload: { type: "reasoning", summary: [], encrypted_content: "x" } }, + { type: "response_item", payload: { type: "function_call", name: "exec_command", arguments: '{"cmd":"rg router"}', call_id: "c1" } }, + { type: "response_item", payload: { type: "function_call_output", call_id: "c1", output: "src/router.ts" } }, + { type: "response_item", payload: { type: "custom_tool_call", name: "apply_patch", input: "*** Begin Patch", call_id: "c2" } }, + { type: "response_item", payload: { type: "custom_tool_call_output", call_id: "c2", output: [{ type: "input_text", text: "Success" }] } }, + { type: "response_item", payload: { type: "message", role: "assistant", content: [{ type: "output_text", text: "Added `/health`." }] } }, + { type: "event_msg", payload: { type: "task_complete" } }, + { type: "event_msg", payload: { type: "user_message", message: "add a health route" } }, + ); + + test("normalizes a rollout into AgentEvents", () => { + const { events, cwd } = parseTranscriptText("codex", codex); + expect(cwd).toBe("/work/api"); + expect(events).toEqual([ + { kind: "user", text: "add a health route" }, + { kind: "thinking", text: "Check the router" }, + { kind: "tool-start", toolId: "c1", name: "exec_command", detail: "rg router" }, + { kind: "tool-end", toolId: "c1", detail: "src/router.ts" }, + { kind: "tool-start", toolId: "c2", name: "apply_patch", detail: "*** Begin Patch" }, + { kind: "tool-end", toolId: "c2", detail: "Success" }, + { kind: "assistant", text: "Added `/health`." }, + { kind: "done" }, + // A repeated prompt in a later turn is a real message, not a duplicate. + { kind: "user", text: "add a health route" }, + ] satisfies AgentEvent[]); + }); +}); + +describe("TranscriptTail", () => { + test("delivers complete lines as the file grows", async () => { + const path = join(tempDir(), "t.jsonl"); + writeFileSync(path, '{"a":1}\n{"b":'); + const seen: string[] = []; + const tail = new TranscriptTail(path, (lines) => seen.push(...lines)); + await tail.poll(); + expect(seen).toEqual(['{"a":1}']); + appendFileSync(path, '2}\n{"c":3}\n'); + await tail.poll(); + expect(seen).toEqual(['{"a":1}', '{"b":2}', '{"c":3}']); + await tail.poll(); + expect(seen.length).toBe(3); + }); + + test("starts long files at a line boundary inside the initial window", async () => { + const path = join(tempDir(), "t.jsonl"); + writeFileSync(path, `${"x".repeat(100)}\n{"keep":1}\n`); + const seen: string[] = []; + const tail = new TranscriptTail(path, (lines) => seen.push(...lines), { initialWindowBytes: 30 }); + await tail.poll(); + expect(seen).toEqual(['{"keep":1}']); + }); + + test("follows a truncated file from its start", async () => { + const path = join(tempDir(), "t.jsonl"); + writeFileSync(path, '{"old":1}\n{"old":2}\n'); + const seen: string[] = []; + const tail = new TranscriptTail(path, (lines) => seen.push(...lines)); + await tail.poll(); + writeFileSync(path, '{"new":1}\n'); + await tail.poll(); + expect(seen).toEqual(['{"old":1}', '{"old":2}', '{"new":1}']); + }); +}); + +test("running state follows the last turn boundary and recent writes", () => { + const now = 1_000_000; + const turn: AgentEvent[] = [{ kind: "user", text: "go" }, { kind: "tool-start", toolId: "1", name: "Bash" }]; + expect(transcriptLooksRunning(turn, now - 1_000, now)).toBe(true); + expect(transcriptLooksRunning([...turn, { kind: "done" }], now - 1_000, now)).toBe(false); + expect(transcriptLooksRunning(turn, now - 120_000, now)).toBe(false); +}); + +describe("transcript sources", () => { + function fixture() { + const home = tempDir(); + const claudeConfigDir = join(home, "claude-config"); + const codexHome = join(home, "codex-home"); + mkdirSync(join(home, ".cmuxterm"), { recursive: true }); + const recorded = join(home, "recorded.jsonl"); + writeFileSync(recorded, ""); + const projectDir = join(claudeConfigDir, "projects", claudeProjectSlug("/work/app.v2")); + mkdirSync(projectDir, { recursive: true }); + writeFileSync(join(projectDir, "claude-fallback-0001.jsonl"), ""); + const rolloutDir = join(codexHome, "sessions", "2026", "09", "27"); + mkdirSync(rolloutDir, { recursive: true }); + const rollout = join(rolloutDir, "rollout-2026-09-27T10-00-00-codex-session-0001.jsonl"); + writeFileSync(rollout, ""); + writeFileSync(join(home, ".cmuxterm", "claude-hook-sessions.json"), JSON.stringify({ + activeSessionsBySurface: { "aaaaaaaa-0000-0000-0000-000000000001": { sessionId: "claude-active-0001" } }, + sessions: { + "claude-active-0001": { surfaceId: "AAAAAAAA-0000-0000-0000-000000000001", transcriptPath: recorded, updatedAt: 10 }, + "claude-newer-0001": { surfaceId: "AAAAAAAA-0000-0000-0000-000000000001", transcriptPath: recorded, updatedAt: 99 }, + "claude-fallback-0001": { surfaceId: "BBBBBBBB-0000-0000-0000-000000000002", cwd: "/work/app.v2", updatedAt: 5 }, + "claude-missing-0001": { surfaceId: "DDDDDDDD-0000-0000-0000-000000000004", transcriptPath: join(home, "gone.jsonl"), updatedAt: 5 }, + "../escape": { surfaceId: "EEEEEEEE-0000-0000-0000-000000000005", transcriptPath: recorded, updatedAt: 5 }, + }, + })); + writeFileSync(join(home, ".cmuxterm", "codex-hook-sessions.json"), JSON.stringify({ + sessions: { + "codex-session-0001": { surfaceId: "CCCCCCCC-0000-0000-0000-000000000003", cwd: "/work/api", updatedAt: 7 }, + }, + })); + return { env: { home, claudeConfigDir, codexHome }, recorded, projectDir, rollout }; + } + + test("the surface's active binding wins over newer history", () => { + const { env, recorded } = fixture(); + const source = resolveSurfaceTranscript("aaaaaaaa-0000-0000-0000-000000000001", env); + expect(source).toMatchObject({ agent: "claude", sessionId: "claude-active-0001", path: recorded }); + }); + + test("falls back to each agent's default transcript layout", () => { + const { env, projectDir, rollout } = fixture(); + expect(resolveSurfaceTranscript("BBBBBBBB-0000-0000-0000-000000000002", env)?.path).toBe(join(projectDir, "claude-fallback-0001.jsonl")); + expect(resolveSurfaceTranscript("CCCCCCCC-0000-0000-0000-000000000003", env)).toMatchObject({ agent: "codex", path: rollout, cwd: "/work/api" }); + expect(resolveSessionTranscript("codex-session-0001", env)?.path).toBe(rollout); + }); + + test("returns nothing for unknown surfaces, missing files, and invalid ids", () => { + const { env } = fixture(); + expect(resolveSurfaceTranscript("FFFFFFFF-0000-0000-0000-000000000006", env)).toBeNull(); + expect(resolveSurfaceTranscript("DDDDDDDD-0000-0000-0000-000000000004", env)).toBeNull(); + expect(resolveSurfaceTranscript("EEEEEEEE-0000-0000-0000-000000000005", env)).toBeNull(); + expect(resolveSessionTranscript("../escape", env)).toBeNull(); + }); +}); diff --git a/agent-chat/transcript-sources.ts b/agent-chat/transcript-sources.ts new file mode 100644 index 000000000000..2a14a05910bd --- /dev/null +++ b/agent-chat/transcript-sources.ts @@ -0,0 +1,175 @@ +// Resolves which transcript a cmux terminal is running, using the hook session +// stores cmux's agent hooks maintain (`~/.cmuxterm/-hook-sessions.json`). +// Paths only ever come from those stores or the agents' own default layouts, so +// the transcript view cannot be pointed at an arbitrary file. +import { readFileSync, readdirSync, statSync } from "node:fs"; +import { homedir } from "node:os"; +import { join } from "node:path"; +import type { TranscriptAgent } from "./adapters/transcript"; + +export interface TranscriptSource { + agent: TranscriptAgent; + sessionId: string; + path: string; + cwd?: string; + surfaceId?: string; + updatedAt: number; +} + +interface HookEntry { + agent: TranscriptAgent; + sessionId: string; + surfaceId?: string; + cwd?: string; + transcriptPath?: string; + updatedAt: number; +} + +const AGENTS: TranscriptAgent[] = ["claude", "codex"]; +const SESSION_ID = /^[\w-]{8,128}$/; + +export interface TranscriptSourceEnv { + home?: string; + claudeConfigDir?: string; + codexHome?: string; +} + +function readStore(home: string, agent: TranscriptAgent): { entries: HookEntry[]; activeBySurface: Map } { + const file = join(home, ".cmuxterm", `${agent}-hook-sessions.json`); + const activeBySurface = new Map(); + let root: any; + try { + root = JSON.parse(readFileSync(file, "utf8")); + } catch { + return { entries: [], activeBySurface }; + } + if (!root || typeof root !== "object") return { entries: [], activeBySurface }; + const active = root.activeSessionsBySurface; + if (active && typeof active === "object") { + for (const [surface, value] of Object.entries(active)) { + const sid = (value as any)?.sessionId; + if (typeof sid === "string") activeBySurface.set(surface.toUpperCase(), sid); + } + } + const sessions = root.sessions && typeof root.sessions === "object" ? root.sessions : root; + const entries: HookEntry[] = []; + for (const [key, value] of Object.entries(sessions)) { + if (!value || typeof value !== "object" || !SESSION_ID.test(key)) continue; + const v = value as Record; + entries.push({ + agent, + sessionId: key, + surfaceId: typeof v.surfaceId === "string" && v.surfaceId ? v.surfaceId.toUpperCase() : undefined, + cwd: typeof v.cwd === "string" && v.cwd ? v.cwd : undefined, + transcriptPath: typeof v.transcriptPath === "string" && v.transcriptPath ? v.transcriptPath : undefined, + updatedAt: typeof v.updatedAt === "number" ? v.updatedAt : 0, + }); + } + return { entries, activeBySurface }; +} + +/** Claude's project directory name for a cwd (`/a/b.c` -> `-a-b-c`). */ +export function claudeProjectSlug(cwd: string): string { + return cwd.replace(/[^a-zA-Z0-9]/g, "-"); +} + +function isFile(path: string): boolean { + try { + return statSync(path).isFile(); + } catch { + return false; + } +} + +// Codex rollouts live under sessions/YYYY/MM/DD; walk newest days first and +// stop after a bounded number of day directories. +function findCodexRollout(codexHome: string, sessionId: string, maxDays = 120): string | undefined { + const root = join(codexHome, "sessions"); + const suffix = `-${sessionId}.jsonl`; + const sortedDirs = (dir: string) => { + try { + return readdirSync(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse(); + } catch { + return []; + } + }; + let days = 0; + for (const y of sortedDirs(root)) { + for (const m of sortedDirs(join(root, y))) { + for (const d of sortedDirs(join(root, y, m))) { + if (++days > maxDays) return undefined; + const dir = join(root, y, m, d); + let names: string[] = []; + try { + names = readdirSync(dir); + } catch { + continue; + } + const hit = names.find((n) => n.startsWith("rollout-") && n.endsWith(suffix)); + if (hit) return join(dir, hit); + } + } + } + return undefined; +} + +function transcriptPathFor(entry: HookEntry, env: Required): string | undefined { + if (entry.transcriptPath && entry.transcriptPath.endsWith(".jsonl") && isFile(entry.transcriptPath)) return entry.transcriptPath; + if (entry.agent === "claude") { + if (!entry.cwd) return undefined; + const path = join(env.claudeConfigDir, "projects", claudeProjectSlug(entry.cwd), `${entry.sessionId}.jsonl`); + return isFile(path) ? path : undefined; + } + return findCodexRollout(env.codexHome, entry.sessionId); +} + +function resolvedEnv(env: TranscriptSourceEnv): Required { + const home = env.home ?? homedir(); + return { + home, + claudeConfigDir: env.claudeConfigDir ?? process.env.CLAUDE_CONFIG_DIR ?? join(home, ".claude"), + codexHome: env.codexHome ?? process.env.CODEX_HOME ?? join(home, ".codex"), + }; +} + +function toSource(entry: HookEntry, env: Required): TranscriptSource | null { + const path = transcriptPathFor(entry, env); + if (!path) return null; + return { agent: entry.agent, sessionId: entry.sessionId, path, cwd: entry.cwd, surfaceId: entry.surfaceId, updatedAt: entry.updatedAt }; +} + +/** + * The transcript for the agent session most recently bound to a terminal + * surface. A surface's active Claude binding wins over older sessions. + */ +export function resolveSurfaceTranscript(surfaceId: string, env: TranscriptSourceEnv = {}): TranscriptSource | null { + const resolved = resolvedEnv(env); + const surface = surfaceId.trim().toUpperCase(); + if (!surface) return null; + const candidates: { entry: HookEntry; active: boolean }[] = []; + for (const agent of AGENTS) { + const store = readStore(resolved.home, agent); + const activeId = store.activeBySurface.get(surface); + for (const entry of store.entries) { + const active = entry.sessionId === activeId; + if (active || entry.surfaceId === surface) candidates.push({ entry, active }); + } + } + candidates.sort((a, b) => Number(b.active) - Number(a.active) || b.entry.updatedAt - a.entry.updatedAt); + for (const { entry } of candidates) { + const source = toSource(entry, resolved); + if (source) return source; + } + return null; +} + +/** The transcript for a known agent session id, from either hook store. */ +export function resolveSessionTranscript(sessionId: string, env: TranscriptSourceEnv = {}): TranscriptSource | null { + if (!SESSION_ID.test(sessionId)) return null; + const resolved = resolvedEnv(env); + for (const agent of AGENTS) { + const entry = readStore(resolved.home, agent).entries.find((e) => e.sessionId === sessionId); + if (entry) return toSource(entry, resolved); + } + return null; +} From 3c7d157948f29edd443b8713e239daba20f897c6 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Sun, 27 Sep 2026 07:22:20 -0400 Subject: [PATCH 02/11] agent-chat: fix transcript view review findings Keep Claude prompts stored after injected reminder blocks, end Claude turns at the final message's stop reason and on interrupts, base the working state on the transcript's write time so old sessions open idle, re-point a moved transcript without orphaning open pages, cache session-id misses, and honor cmux's hook state path overrides. Co-Authored-By: Claude Opus 5.5 --- agent-chat/adapters/transcript.ts | 64 +++++++++++++++++++++++------- agent-chat/server.ts | 17 ++++++-- agent-chat/test/transcript.test.ts | 56 +++++++++++++++++++++++++- agent-chat/transcript-sources.ts | 38 +++++++++++++++--- 4 files changed, 151 insertions(+), 24 deletions(-) diff --git a/agent-chat/adapters/transcript.ts b/agent-chat/adapters/transcript.ts index 406d07120a53..8ed743ce10e6 100644 --- a/agent-chat/adapters/transcript.ts +++ b/agent-chat/adapters/transcript.ts @@ -77,8 +77,25 @@ class ClaudeTranscriptParser implements TranscriptParser { title?: string; cwd?: string; private seen = new Set(); + private turnOpen = false; + private endedMessageIds = new Set(); parse(line: string): AgentEvent[] { + const events = this.parseLine(line); + for (const evt of events) { + if (evt.kind === "done") this.turnOpen = false; + else if (evt.kind !== "status") this.turnOpen = true; + } + return events; + } + + /** Appends a turn end unless the turn already ended. */ + private endTurn(events: AgentEvent[], stats?: string): AgentEvent[] { + const open = this.turnOpen || events.some((e) => e.kind !== "status"); + return open ? [...events, stats ? { kind: "done", stats } : { kind: "done" }] : events; + } + + private parseLine(line: string): AgentEvent[] { const ev = tryParse(line); if (!ev || typeof ev !== "object") return []; if (ev.isSidechain) return []; @@ -124,7 +141,10 @@ class ClaudeTranscriptParser implements TranscriptParser { const result = textOf(block.content); out.push({ kind: "tool-end", toolId: String(block.tool_use_id ?? ""), ok: !block.is_error, detail: truncate(result, 400) }); } - const prompt = content.filter((b: any) => b?.type === "text" || b?.type === "image"); + // Claude often stores a typed prompt after injected reminder blocks in + // the same message, so hidden blocks are dropped one by one. + const prompt = content.filter((b: any) => + b?.type === "image" || (b?.type === "text" && typeof b.text === "string" && !CLAUDE_HIDDEN_USER_TAGS.test(b.text.trim()))); if (prompt.length) out.push(...this.prompt(textOf(prompt))); return out; } @@ -135,7 +155,7 @@ class ClaudeTranscriptParser implements TranscriptParser { private prompt(raw: string): AgentEvent[] { const text = raw.trim(); if (!text) return []; - if (text.startsWith("[Request interrupted by user")) return [{ kind: "status", text: "Interrupted" }]; + if (text.startsWith("[Request interrupted by user")) return this.endTurn([{ kind: "status", text: "Interrupted" }]); if (CLAUDE_HIDDEN_USER_TAGS.test(text)) return []; const command = tagValue(text, "command-name"); if (command) { @@ -160,8 +180,27 @@ class ClaudeTranscriptParser implements TranscriptParser { const message = textOf(content); return message ? [{ kind: "error", message: truncate(message, 400) }] : []; } - if (typeof content === "string") return content.trim() ? [{ kind: "assistant", text: content }] : []; - if (!Array.isArray(content)) return out; + if (typeof content === "string") { + if (content.trim()) out.push({ kind: "assistant", text: content }); + } else if (Array.isArray(content)) { + out.push(...this.assistantBlocks(content)); + } + // `turn_duration` is only written for some turns; the final assistant + // message's stop reason marks the end of every turn. Each content block + // is its own line carrying the message's stop reason, and thinking comes + // before the text, so the turn ends at the message's first text line. + const stop = ev.message?.stop_reason; + const messageId = String(ev.message?.id ?? ev.uuid ?? ""); + const hasText = out.some((e) => e.kind === "assistant"); + if ((stop === "end_turn" || stop === "stop_sequence") && hasText && !this.endedMessageIds.has(messageId)) { + this.endedMessageIds.add(messageId); + return this.endTurn(out); + } + return out; + } + + private assistantBlocks(content: any[]): AgentEvent[] { + const out: AgentEvent[] = []; for (const block of content) { if (block?.type === "text" && typeof block.text === "string" && block.text.trim()) { out.push({ kind: "assistant", text: block.text }); @@ -178,7 +217,7 @@ class ClaudeTranscriptParser implements TranscriptParser { switch (ev.subtype) { case "turn_duration": { const ms = Number(ev.durationMs); - return [{ kind: "done", stats: Number.isFinite(ms) && ms > 0 ? `${(ms / 1000).toFixed(1)}s` : undefined }]; + return this.endTurn([], Number.isFinite(ms) && ms > 0 ? `${(ms / 1000).toFixed(1)}s` : undefined); } case "compact_boundary": return [{ kind: "status", text: "Conversation compacted" }]; @@ -308,7 +347,7 @@ export class TranscriptTail { constructor( readonly path: string, - private readonly onLines: (lines: string[]) => void, + private readonly onLines: (lines: string[], mtimeMs: number) => void, private readonly opts: { pollMs?: number; initialWindowBytes?: number } = {}, ) {} @@ -360,7 +399,7 @@ export class TranscriptTail { if (lastNl < 0) continue; const lines = this.pending.slice(0, lastNl).split("\n").filter((l) => l.trim()); this.pending = this.pending.slice(lastNl + 1); - if (lines.length) this.onLines(lines); + if (lines.length) this.onLines(lines, info.mtimeMs); } } finally { await handle.close(); @@ -413,9 +452,11 @@ export function attachTranscript( if (!st) return; sess.setStatus(transcriptLooksRunning(sess.events, st.lastWriteMs) ? "running" : "idle"); }; - const tail = new TranscriptTail(path, (lines) => { + const tail = new TranscriptTail(path, (lines, mtimeMs) => { const st = transcriptState(sess); - if (st) st.lastWriteMs = Date.now(); + // Activity comes from the file's own write time, so a transcript that + // went idle long ago does not look busy when its history first loads. + if (st) st.lastWriteMs = mtimeMs; const title = parser.title; for (const line of lines) { for (const evt of parser.parse(line)) sess.emit(evt); @@ -431,11 +472,6 @@ export function attachTranscript( }; sess.internal.transcript = state; sess.internal.transcriptPath = path; - // Seed the write time from the file so a session that is mid-turn right - // now renders as running on first paint. - stat(path).then((info) => { - if (transcriptState(sess) === state && !state.lastWriteMs) state.lastWriteMs = info.mtimeMs; - }, () => {}); tail.start(); return tail; } diff --git a/agent-chat/server.ts b/agent-chat/server.ts index 4d4671570bbc..8fefb9a0b78c 100644 --- a/agent-chat/server.ts +++ b/agent-chat/server.ts @@ -631,15 +631,27 @@ function ensureTranscriptSession(source: TranscriptSource): Session { const id = transcriptSessionId(source); const existing = sessions.get(id); if (existing?.transcript?.path === source.path) return existing; - if (existing) { + if (existing?.transcript) { + // The agent's transcript moved (for example a resolved fallback path): + // re-point the same session so open pages stay subscribed. existing.adapter.dispose(existing); - sessions.delete(id); + existing.events.length = 0; + delete existing.internal.eventGenerations; + existing.transcript.path = source.path; + startTranscriptTail(existing, source); + broadcastSessionHistory(existing); + return existing; } const sess = createSession(source.agent, source.cwd ?? DEFAULT_CWD, false, transcriptTitle(source), {}, {}, { id, adapter: transcriptAdapter, }); sess.transcript = { agent: source.agent, path: source.path }; + startTranscriptTail(sess, source); + return sess; +} + +function startTranscriptTail(sess: Session, source: TranscriptSource) { attachTranscript(sess, source.agent, source.path, (title) => { if (sess.title === title) return; sess.title = title; @@ -647,7 +659,6 @@ function ensureTranscriptSession(source: TranscriptSource): Session { const payload = JSON.stringify({ kind: "session-title", sessionId: sess.id, title }); for (const ws of sess.sockets) ws.send(payload); }); - return sess; } function resolveTranscriptSessionById(id: string): Session | undefined { diff --git a/agent-chat/test/transcript.test.ts b/agent-chat/test/transcript.test.ts index 387d3afc629d..5d3d3c993ae8 100644 --- a/agent-chat/test/transcript.test.ts +++ b/agent-chat/test/transcript.test.ts @@ -2,7 +2,9 @@ import { afterEach, describe, expect, test } from "bun:test"; import { appendFileSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { parseTranscriptText, TranscriptTail, transcriptLooksRunning, toolDetail } from "../adapters/transcript"; +import { attachTranscript, parseTranscriptText, transcriptAdapter, TranscriptTail, transcriptLooksRunning, toolDetail } from "../adapters/transcript"; +import { utimesSync } from "node:fs"; +import type { SessionCtx, SessionStatus } from "../types"; import { claudeProjectSlug, resolveSessionTranscript, resolveSurfaceTranscript } from "../transcript-sources"; import type { AgentEvent } from "../types"; @@ -56,11 +58,43 @@ describe("Claude transcript parser", () => { { kind: "status", text: "Set model to opus" }, { kind: "status", text: "Background task: Build finished" }, { kind: "status", text: "Interrupted" }, + { kind: "done" }, { kind: "error", message: "API Error: overloaded" }, { kind: "status", text: "Conversation compacted" }, ] satisfies AgentEvent[]); }); + test("keeps a typed prompt stored after injected reminder blocks", () => { + const { events } = parseTranscriptText("claude", jsonl( + { type: "user", uuid: "p1", origin: { kind: "human" }, message: { role: "user", content: [ + { type: "text", text: "context" }, + { type: "text", text: "ship it" }, + ] } }, + )); + expect(events).toEqual([{ kind: "user", text: "ship it" }]); + }); + + test("ends a turn at the final message's stop reason, once", () => { + const { events } = parseTranscriptText("claude", jsonl( + { type: "user", uuid: "p1", message: { role: "user", content: "hi" } }, + { type: "assistant", uuid: "x1", message: { id: "m1", stop_reason: "end_turn", content: [{ type: "thinking", thinking: "greet" }] } }, + { type: "assistant", uuid: "x2", message: { id: "m1", stop_reason: "end_turn", content: [{ type: "text", text: "Hello." }] } }, + { type: "system", uuid: "x3", subtype: "turn_duration", durationMs: 900 }, + { type: "user", uuid: "p2", message: { role: "user", content: "again" } }, + { type: "assistant", uuid: "x4", message: { id: "m2", stop_reason: "tool_use", content: [{ type: "text", text: "Checking." }] } }, + { type: "system", uuid: "x5", subtype: "turn_duration", durationMs: 1500 }, + )); + expect(events).toEqual([ + { kind: "user", text: "hi" }, + { kind: "thinking", text: "greet" }, + { kind: "assistant", text: "Hello." }, + { kind: "done" }, + { kind: "user", text: "again" }, + { kind: "assistant", text: "Checking." }, + { kind: "done", stats: "1.5s" }, + ] satisfies AgentEvent[]); + }); + test("tool detail prefers the meaningful input field", () => { expect(toolDetail({ file_path: "/a/b.ts", limit: 20 })).toBe("/a/b.ts"); expect(toolDetail('{"cmd":["ls","-la"]}')).toBe("ls -la"); @@ -147,6 +181,26 @@ test("running state follows the last turn boundary and recent writes", () => { expect(transcriptLooksRunning(turn, now - 120_000, now)).toBe(false); }); +test("an old transcript opens idle even when its last turn has no end", async () => { + const path = join(tempDir(), "old.jsonl"); + writeFileSync(path, jsonl({ type: "user", uuid: "p1", message: { role: "user", content: "hi" } })); + const dayAgo = (Date.now() - 86_400_000) / 1000; + utimesSync(path, dayAgo, dayAgo); + const statuses: SessionStatus[] = []; + const sess = { + events: [] as AgentEvent[], + internal: {} as Record, + emit(evt: AgentEvent) { this.events.push(evt); }, + setStatus(status: SessionStatus) { statuses.push(status); }, + } as unknown as SessionCtx; + const tail = attachTranscript(sess, "claude", path); + await tail.poll(); + await new Promise((r) => setTimeout(r, 20)); + transcriptAdapter.dispose(sess); + expect(sess.events).toEqual([{ kind: "user", text: "hi" }]); + expect(statuses).not.toContain("running"); +}); + describe("transcript sources", () => { function fixture() { const home = tempDir(); diff --git a/agent-chat/transcript-sources.ts b/agent-chat/transcript-sources.ts index 2a14a05910bd..aba07da025e1 100644 --- a/agent-chat/transcript-sources.ts +++ b/agent-chat/transcript-sources.ts @@ -30,12 +30,22 @@ const SESSION_ID = /^[\w-]{8,128}$/; export interface TranscriptSourceEnv { home?: string; + /** Directory holding `-hook-sessions.json` (cmux's CMUX_AGENT_HOOK_STATE_DIR). */ + hookStateDir?: string; + /** Claude store override (cmux's CMUX_CLAUDE_HOOK_STATE_PATH). */ + claudeHookStatePath?: string; claudeConfigDir?: string; codexHome?: string; } -function readStore(home: string, agent: TranscriptAgent): { entries: HookEntry[]; activeBySurface: Map } { - const file = join(home, ".cmuxterm", `${agent}-hook-sessions.json`); +function expandHome(path: string, home: string): string { + return path === "~" || path.startsWith("~/") ? join(home, path.slice(1)) : path; +} + +function readStore(env: Required, agent: TranscriptAgent): { entries: HookEntry[]; activeBySurface: Map } { + const file = agent === "claude" && env.claudeHookStatePath + ? env.claudeHookStatePath + : join(env.hookStateDir, `${agent}-hook-sessions.json`); const activeBySurface = new Map(); let root: any; try { @@ -125,8 +135,11 @@ function transcriptPathFor(entry: HookEntry, env: Required) function resolvedEnv(env: TranscriptSourceEnv): Required { const home = env.home ?? homedir(); + const nonEmpty = (v: string | undefined) => (v?.trim() ? expandHome(v.trim(), home) : undefined); return { home, + hookStateDir: env.hookStateDir ?? nonEmpty(process.env.CMUX_AGENT_HOOK_STATE_DIR) ?? join(home, ".cmuxterm"), + claudeHookStatePath: env.claudeHookStatePath ?? nonEmpty(process.env.CMUX_CLAUDE_HOOK_STATE_PATH) ?? "", claudeConfigDir: env.claudeConfigDir ?? process.env.CLAUDE_CONFIG_DIR ?? join(home, ".claude"), codexHome: env.codexHome ?? process.env.CODEX_HOME ?? join(home, ".codex"), }; @@ -148,7 +161,7 @@ export function resolveSurfaceTranscript(surfaceId: string, env: TranscriptSourc if (!surface) return null; const candidates: { entry: HookEntry; active: boolean }[] = []; for (const agent of AGENTS) { - const store = readStore(resolved.home, agent); + const store = readStore(resolved, agent); const activeId = store.activeBySurface.get(surface); for (const entry of store.entries) { const active = entry.sessionId === activeId; @@ -163,13 +176,26 @@ export function resolveSurfaceTranscript(surfaceId: string, env: TranscriptSourc return null; } +// Misses are remembered briefly: page reconnects retry unknown ids, and a +// Codex miss walks the sessions tree synchronously. +const MISS_TTL_MS = 30_000; +const misses = new Map(); + /** The transcript for a known agent session id, from either hook store. */ -export function resolveSessionTranscript(sessionId: string, env: TranscriptSourceEnv = {}): TranscriptSource | null { +export function resolveSessionTranscript(sessionId: string, env: TranscriptSourceEnv = {}, now = Date.now()): TranscriptSource | null { if (!SESSION_ID.test(sessionId)) return null; + const missedAt = misses.get(sessionId); + if (missedAt !== undefined && now - missedAt < MISS_TTL_MS) return null; const resolved = resolvedEnv(env); for (const agent of AGENTS) { - const entry = readStore(resolved.home, agent).entries.find((e) => e.sessionId === sessionId); - if (entry) return toSource(entry, resolved); + const entry = readStore(resolved, agent).entries.find((e) => e.sessionId === sessionId); + const source = entry ? toSource(entry, resolved) : null; + if (source) { + misses.delete(sessionId); + return source; + } } + if (misses.size > 256) misses.clear(); + misses.set(sessionId, now); return null; } From 41516ec1826149af594b638d6aecfc72339877ae Mon Sep 17 00:00:00 2001 From: Leo Li Date: Sun, 27 Sep 2026 08:05:45 -0400 Subject: [PATCH 03/11] test: expect the terminal chat view palette command behind the Agent Chat flag Co-Authored-By: Claude Opus 5.5 --- cmuxTests/CmuxAgentChatConfigTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmuxTests/CmuxAgentChatConfigTests.swift b/cmuxTests/CmuxAgentChatConfigTests.swift index 43147e16416b..7d996b988414 100644 --- a/cmuxTests/CmuxAgentChatConfigTests.swift +++ b/cmuxTests/CmuxAgentChatConfigTests.swift @@ -462,7 +462,7 @@ struct CmuxAgentChatConfigTests { try withAgentChatUIFlag(true) { let contributions = ContentView.commandPaletteNewAgentChatContributions() - #expect(contributions.map(\.commandId) == ["palette.newAgentChat"]) + #expect(contributions.map(\.commandId) == ["palette.newAgentChat", "palette.openTerminalChatView"]) } } From 5a0ebad5c4c202821206a380a10869826f3605b2 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Sun, 27 Sep 2026 08:38:44 -0400 Subject: [PATCH 04/11] agent-chat: let transcript polls join a read in flight A poll that raced the tail's own first read returned before any lines arrived; it now waits for that read. Co-Authored-By: Claude Opus 5.5 --- agent-chat/adapters/transcript.ts | 85 ++++++++++++++++--------------- 1 file changed, 44 insertions(+), 41 deletions(-) diff --git a/agent-chat/adapters/transcript.ts b/agent-chat/adapters/transcript.ts index 8ed743ce10e6..f1eb867ebdff 100644 --- a/agent-chat/adapters/transcript.ts +++ b/agent-chat/adapters/transcript.ts @@ -342,7 +342,7 @@ export class TranscriptTail { private offset = -1; private pending = ""; private timer: ReturnType | null = null; - private reading = false; + private inflight: Promise | null = null; private decoder = new TextDecoder(); constructor( @@ -362,50 +362,53 @@ export class TranscriptTail { this.timer = null; } - /** Reads everything appended since the last poll. Exposed for tests. */ - async poll(): Promise { - if (this.reading) return; - this.reading = true; + /** Reads everything appended since the last poll; joins a read in flight. */ + poll(): Promise { + if (!this.inflight) { + this.inflight = this.read().finally(() => { + this.inflight = null; + }); + } + return this.inflight; + } + + private async read(): Promise { + const info = await stat(this.path).catch(() => null); + if (!info) return; + let skipPartialFirstLine = false; + if (this.offset < 0) { + const window = this.opts.initialWindowBytes ?? TRANSCRIPT_INITIAL_WINDOW_BYTES; + this.offset = Math.max(0, info.size - window); + skipPartialFirstLine = this.offset > 0; + } else if (info.size < this.offset) { + // Truncated or replaced: follow the new file from its start. + this.offset = 0; + this.pending = ""; + this.decoder = new TextDecoder(); + } + if (info.size === this.offset) return; + const handle = await open(this.path, "r"); try { - const info = await stat(this.path).catch(() => null); - if (!info) return; - let skipPartialFirstLine = false; - if (this.offset < 0) { - const window = this.opts.initialWindowBytes ?? TRANSCRIPT_INITIAL_WINDOW_BYTES; - this.offset = Math.max(0, info.size - window); - skipPartialFirstLine = this.offset > 0; - } else if (info.size < this.offset) { - // Truncated or replaced: follow the new file from its start. - this.offset = 0; - this.pending = ""; - this.decoder = new TextDecoder(); - } - if (info.size === this.offset) return; - const handle = await open(this.path, "r"); - try { - const buf = new Uint8Array(TRANSCRIPT_READ_CHUNK); - while (this.offset < info.size) { - const { bytesRead } = await handle.read(buf, 0, Math.min(buf.length, info.size - this.offset), this.offset); - if (bytesRead <= 0) break; - this.offset += bytesRead; - this.pending += this.decoder.decode(buf.subarray(0, bytesRead), { stream: true }); - if (skipPartialFirstLine) { - const nl = this.pending.indexOf("\n"); - if (nl < 0) continue; - this.pending = this.pending.slice(nl + 1); - skipPartialFirstLine = false; - } - const lastNl = this.pending.lastIndexOf("\n"); - if (lastNl < 0) continue; - const lines = this.pending.slice(0, lastNl).split("\n").filter((l) => l.trim()); - this.pending = this.pending.slice(lastNl + 1); - if (lines.length) this.onLines(lines, info.mtimeMs); + const buf = new Uint8Array(TRANSCRIPT_READ_CHUNK); + while (this.offset < info.size) { + const { bytesRead } = await handle.read(buf, 0, Math.min(buf.length, info.size - this.offset), this.offset); + if (bytesRead <= 0) break; + this.offset += bytesRead; + this.pending += this.decoder.decode(buf.subarray(0, bytesRead), { stream: true }); + if (skipPartialFirstLine) { + const nl = this.pending.indexOf("\n"); + if (nl < 0) continue; + this.pending = this.pending.slice(nl + 1); + skipPartialFirstLine = false; } - } finally { - await handle.close(); + const lastNl = this.pending.lastIndexOf("\n"); + if (lastNl < 0) continue; + const lines = this.pending.slice(0, lastNl).split("\n").filter((l) => l.trim()); + this.pending = this.pending.slice(lastNl + 1); + if (lines.length) this.onLines(lines, info.mtimeMs); } } finally { - this.reading = false; + await handle.close(); } } } From 31e3a7fe804e057daa7a3350f3601f0dba34f2f5 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Sun, 27 Sep 2026 09:04:37 -0400 Subject: [PATCH 05/11] Localizable: re-apply the terminal chat view string on main's catalog Co-Authored-By: Claude Opus 5.5 --- Resources/Localizable.xcstrings | 1180 +++++++++++++++---------------- 1 file changed, 590 insertions(+), 590 deletions(-) diff --git a/Resources/Localizable.xcstrings b/Resources/Localizable.xcstrings index 13131902e8d4..f66229720a7d 100644 --- a/Resources/Localizable.xcstrings +++ b/Resources/Localizable.xcstrings @@ -1,6 +1,65 @@ { "sourceLanguage": "en", "strings": { + "cli.session.usage": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Usage: cmux session move --to [options]\n\nMove a stopped Claude Code session to another machine and resume it there.\nExit the agent first: the move refuses while the session runs on either side.\n\nThe move carries:\n - the cwd's git checkout: HEAD (on the same branch when safe) plus modified,\n deleted and untracked non-ignored files; a worktree is added when the\n repository exists on the destination but the path does not\n - the transcript, its session directory, file history, and the project\n memory directory (merged both ways, newest wins, nothing deleted)\n\nIt then opens a workspace on the destination (`cmux ssh` for a host, a local\nworkspace for `local`) that resumes the session with its recorded launcher.\nWhen the destination home is not at the same absolute path, paths under the\nhome are mapped and the project is re-slugged.\n\nOptions:\n --to SSH destination (user@host or ssh_config alias), or local\n --from Where the session is now (default: where the last move put it, else local)\n --name Workspace title (default: the first 8 characters of the id)\n --no-code Do not carry the git checkout\n --port <n> SSH port\n --identity <path> SSH identity file\n --ssh-option <opt> Extra SSH -o option (repeatable)\n --no-focus Open the workspace without switching to it\n\nExamples:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Verwendung: cmux session move <session-id> --to <ssh-destination|local> [options]\n\nVerschiebt eine beendete Claude Code-Sitzung auf eine andere Maschine und setzt sie dort fort.\nBeende zuerst den Agenten: Die Verschiebung wird abgelehnt, solange die Sitzung auf einer Seite läuft.\n\nDie Verschiebung überträgt:\n - den git-Checkout des cwd: HEAD (auf demselben Branch, wenn das sicher ist) sowie geänderte,\n gelöschte und nicht ignorierte, nicht verfolgte Dateien; ein Worktree wird hinzugefügt, wenn\n das Repository am Ziel existiert, der Pfad aber nicht\n - das Transkript, sein Sitzungsverzeichnis, den Dateiverlauf und das Projektgedächtnis\n (in beide Richtungen zusammengeführt, das Neueste gewinnt, nichts wird gelöscht)\n\nDanach öffnet sie am Ziel einen Workspace (`cmux ssh` für einen Host, einen lokalen Workspace\nfür `local`), der die Sitzung mit ihrem aufgezeichneten Launcher fortsetzt. Liegt das Home-\nVerzeichnis am Ziel nicht unter demselben absoluten Pfad, werden Pfade darunter umgeschrieben\nund das Projekt erhält einen neuen Slug.\n\nOptionen:\n --to <destination> SSH-Ziel (user@host oder ssh_config-Alias) oder local\n --from <destination> Wo die Sitzung jetzt ist (Standard: wohin die letzte Verschiebung sie gebracht hat, sonst local)\n --name <title> Workspace-Titel (Standard: die ersten 8 Zeichen der ID)\n --no-code Den git-Checkout nicht übertragen\n --port <n> SSH-Port\n --identity <path> SSH-Identitätsdatei\n --ssh-option <opt> Zusätzliche SSH-Option -o (wiederholbar)\n --no-focus Workspace öffnen, ohne dorthin zu wechseln\n\nBeispiele:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Utilisation: cmux session move <session-id> --to <ssh-destination|local> [options]\n\nDéplace une session Claude Code arrêtée vers une autre machine et la reprend là-bas.\nQuittez d'abord l'agent : le déplacement est refusé tant que la session s'exécute d'un côté ou de l'autre.\n\nLe déplacement transfère :\n - le checkout git du cwd : HEAD (sur la même branche quand c'est sûr) ainsi que les fichiers\n modifiés, supprimés et non suivis non ignorés ; un worktree est ajouté quand le dépôt existe\n sur la destination mais pas le chemin\n - la transcription, son répertoire de session, l'historique des fichiers et la mémoire du\n projet (fusionnée dans les deux sens, le plus récent l'emporte, rien n'est supprimé)\n\nIl ouvre ensuite un espace de travail sur la destination (`cmux ssh` pour un hôte, un espace\nlocal pour `local`) qui reprend la session avec son lanceur enregistré. Quand le dossier\npersonnel de destination n'a pas le même chemin absolu, les chemins qu'il contient sont\nconvertis et le projet reçoit un nouveau slug.\n\nOptions :\n --to <destination> Destination SSH (user@host ou alias ssh_config), ou local\n --from <destination> Où se trouve la session (par défaut : là où l'a mise le dernier déplacement, sinon local)\n --name <title> Titre de l'espace de travail (par défaut : les 8 premiers caractères de l'id)\n --no-code Ne pas transférer le checkout git\n --port <n> Port SSH\n --identity <path> Fichier d'identité SSH\n --ssh-option <opt> Option SSH -o supplémentaire (répétable)\n --no-focus Ouvrir l'espace de travail sans y basculer\n\nExemples :\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الاستخدام: cmux session move <session-id> --to <ssh-destination|local> [options]\n\nينقل جلسة Claude Code متوقفة إلى جهاز آخر ويستأنفها هناك.\nأغلق الوكيل أولًا: يُرفض النقل ما دامت الجلسة تعمل على أي من الجانبين.\n\nينقل الأمر:\n - checkout الخاص بـ git في cwd: HEAD (على الفرع نفسه عندما يكون ذلك آمنًا) والملفات المعدّلة\n والمحذوفة وغير المتتبعة وغير المتجاهلة؛ ويُضاف worktree عندما يوجد المستودع في الوجهة\n ولا يوجد المسار\n - نص الجلسة ودليل الجلسة وسجل الملفات ودليل ذاكرة المشروع\n (مدمج في الاتجاهين، الأحدث يفوز، ولا يُحذف شيء)\n\nثم يفتح مساحة عمل في الوجهة (`cmux ssh` لمضيف، أو مساحة عمل محلية لـ `local`)\nتستأنف الجلسة بالمشغّل المسجّل لها. عندما لا يكون المجلد الرئيسي في الوجهة\nعلى المسار المطلق نفسه، تُحوَّل المسارات داخله\nويُعاد إنشاء slug للمشروع.\n\nالخيارات:\n --to <destination> وجهة SSH (user@host أو اسم مستعار من ssh_config)، أو local\n --from <destination> مكان الجلسة الآن (الافتراضي: حيث وضعها آخر نقل، وإلا local)\n --name <title> عنوان مساحة العمل (الافتراضي: أول 8 أحرف من المعرّف)\n --no-code لا تنقل checkout الخاص بـ git\n --port <n> منفذ SSH\n --identity <path> ملف هوية SSH\n --ssh-option <opt> خيار SSH إضافي -o (قابل للتكرار)\n --no-focus افتح مساحة العمل دون التبديل إليها\n\nأمثلة:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Uso: cmux session move <session-id> --to <ssh-destination|local> [options]\n\nMueve una sesión detenida de Claude Code a otra máquina y la reanuda allí.\nSal primero del agente: el movimiento se rechaza mientras la sesión se ejecute en cualquiera de los lados.\n\nEl movimiento lleva:\n - el checkout de git del cwd: HEAD (en la misma rama cuando es seguro) más los archivos\n modificados, eliminados y sin seguimiento no ignorados; se añade un worktree cuando el\n repositorio existe en el destino pero la ruta no\n - la transcripción, su directorio de sesión, el historial de archivos y la memoria del\n proyecto (combinada en ambos sentidos, gana la más reciente, no se borra nada)\n\nDespués abre un espacio de trabajo en el destino (`cmux ssh` para un host, uno local para\n`local`) que reanuda la sesión con su lanzador registrado. Cuando el home del destino no está\nen la misma ruta absoluta, las rutas bajo él se convierten\ny el proyecto recibe un nuevo slug.\n\nOpciones:\n --to <destination> Destino SSH (user@host o alias de ssh_config), o local\n --from <destination> Dónde está la sesión ahora (por defecto: donde la dejó el último movimiento, si no local)\n --name <title> Título del espacio de trabajo (por defecto: los primeros 8 caracteres del id)\n --no-code No llevar el checkout de git\n --port <n> Puerto SSH\n --identity <path> Archivo de identidad SSH\n --ssh-option <opt> Opción SSH -o adicional (repetible)\n --no-focus Abrir el espacio de trabajo sin cambiar a él\n\nEjemplos:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "用法: cmux session move <session-id> --to <ssh-destination|local> [options]\n\n將已停止的 Claude Code 工作階段移到另一台機器並在那裡恢復。\n請先結束代理:只要工作階段仍在任一端執行,移動就會被拒絕。\n\n移動會帶上:\n - cwd 的 git checkout:HEAD(安全時使用同一分支)加上已修改、已刪除\n 和未追蹤且未忽略的檔案;若目的地有儲存庫\n 但沒有該路徑,會新增 worktree\n - 對話紀錄、其工作階段目錄、檔案歷史與專案記憶目錄\n (雙向合併,較新者優先,不刪除任何內容)\n\n接著在目的地開啟工作區(主機用 `cmux ssh`,`local` 則開本機工作區),\n以記錄的啟動器恢復工作階段。\n若目的地的主目錄不在相同的絕對路徑,其下的路徑會被對應,\n專案也會重新產生 slug。\n\n選項:\n --to <destination> SSH 目的地(user@host 或 ssh_config 別名),或 local\n --from <destination> 工作階段目前所在位置(預設:上次移動所放的位置,否則為 local)\n --name <title> 工作區標題(預設:ID 的前 8 個字元)\n --no-code 不帶上 git checkout\n --port <n> SSH 連接埠\n --identity <path> SSH 身分檔案\n --ssh-option <opt> 額外的 SSH -o 選項(可重複)\n --no-focus 開啟工作區但不切換過去\n\n範例:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "用法: cmux session move <session-id> --to <ssh-destination|local> [options]\n\n将已停止的 Claude Code 会话移到另一台机器并在那里恢复。\n请先退出代理:只要会话仍在任一端运行,移动就会被拒绝。\n\n移动会带上:\n - cwd 的 git 检出:HEAD(安全时使用同一分支)以及已修改、已删除\n 和未跟踪且未忽略的文件;若目标上有仓库\n 但没有该路径,会添加 worktree\n - 会话记录、其会话目录、文件历史和项目记忆目录\n (双向合并,较新者优先,不删除任何内容)\n\n随后在目标上打开工作区(主机用 `cmux ssh`,`local` 则打开本地工作区),\n用记录的启动器恢复会话。\n若目标的主目录不在相同的绝对路径,其下的路径会被映射,\n项目也会重新生成 slug。\n\n选项:\n --to <destination> SSH 目标(user@host 或 ssh_config 别名),或 local\n --from <destination> 会话当前所在位置(默认:上次移动放置的位置,否则为 local)\n --name <title> 工作区标题(默认:ID 的前 8 个字符)\n --no-code 不带上 git 检出\n --port <n> SSH 端口\n --identity <path> SSH 身份文件\n --ssh-option <opt> 额外的 SSH -o 选项(可重复)\n --no-focus 打开工作区但不切换过去\n\n示例:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사용법: cmux session move <session-id> --to <ssh-destination|local> [options]\n\n중지된 Claude Code 세션을 다른 머신으로 옮기고 그곳에서 재개합니다.\n먼저 에이전트를 종료하세요. 어느 쪽에서든 세션이 실행 중이면 이동이 거부됩니다.\n\n이동하는 항목:\n - cwd의 git 체크아웃: HEAD(안전하면 같은 브랜치)와 수정, 삭제,\n 무시되지 않은 추적 안 된 파일. 대상에 저장소는 있지만\n 경로가 없으면 worktree를 추가합니다\n - 트랜스크립트, 세션 디렉터리, 파일 기록, 프로젝트 메모리 디렉터리\n (양방향 병합, 최신 우선, 삭제 없음)\n\n그런 다음 대상에서 작업 공간(호스트는 `cmux ssh`, `local`은 로컬 작업 공간)을 열고\n기록된 런처로 세션을 재개합니다.\n대상의 홈이 같은 절대 경로에 있지 않으면 홈 아래 경로를 변환하고\n프로젝트 slug를 다시 만듭니다.\n\n옵션:\n --to <destination> SSH 대상(user@host 또는 ssh_config 별칭) 또는 local\n --from <destination> 세션의 현재 위치(기본값: 마지막 이동으로 옮긴 곳, 없으면 local)\n --name <title> 작업 공간 제목(기본값: ID의 처음 8자)\n --no-code git 체크아웃을 옮기지 않음\n --port <n> SSH 포트\n --identity <path> SSH ID 파일\n --ssh-option <opt> 추가 SSH -o 옵션(반복 가능)\n --no-focus 작업 공간으로 전환하지 않고 열기\n\n예시:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "使用方法: cmux session move <session-id> --to <ssh-destination|local> [options]\n\n停止した Claude Code セッションを別のマシンへ移動し、そこで再開します。\n先にエージェントを終了してください。どちらかでセッションが実行中の間は移動を拒否します。\n\n移動する内容:\n - cwd の git チェックアウト: HEAD (安全なら同じブランチ) と、変更・削除されたファイル、\n 無視されていない未追跡ファイル。移動先にリポジトリはあるが\n パスがない場合は worktree を追加します\n - トランスクリプト、セッションディレクトリ、ファイル履歴、プロジェクトのメモリディレクトリ\n (双方向にマージ、新しいほうを優先、削除はしません)\n\nその後、移動先でワークスペース (ホストなら `cmux ssh`、`local` ならローカルのワークスペース) を\n開き、記録されたランチャーでセッションを再開します。\n移動先のホームが同じ絶対パスにない場合は、ホーム配下のパスを変換し、\nプロジェクトの slug を作り直します。\n\nオプション:\n --to <destination> SSH 接続先 (user@host または ssh_config のエイリアス)、または local\n --from <destination> セッションの現在の場所 (既定: 前回の移動先、なければ local)\n --name <title> ワークスペースのタイトル (既定: ID の先頭 8 文字)\n --no-code git チェックアウトを移動しない\n --port <n> SSH ポート\n --identity <path> SSH の ID ファイル\n --ssh-option <opt> 追加の SSH -o オプション (繰り返し可)\n --no-focus ワークスペースに切り替えずに開く\n\n例:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" + } + } + } + }, "settings.automation.rules.createFailed.title": { "extractionState": "manual", "localizations": { @@ -347718,6 +347777,65 @@ } } }, + "settings.notifications.sound.preview": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Preview Sound" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Ton anhören" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Écouter le son" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "معاينة الصوت" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Escuchar sonido" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "試聽音效" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "试听声音" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "소리 미리 듣기" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サウンドをプレビュー" + } + } + } + }, "settings.notifications.sound.subtitle": { "extractionState": "manual", "localizations": { @@ -557844,596 +557962,6 @@ } } }, - "browser.omnibar.accessibilityLabel": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Address and search bar" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Adress- und Suchleiste" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Barre d’adresse et de recherche" - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "شريط العنوان والبحث" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Barra de direcciones y búsqueda" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "網址與搜尋列" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "地址和搜索栏" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "주소 및 검색 막대" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "アドレスと検索バー" - } - } - } - }, - "projectPanel.reload": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Reload Project" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Projekt neu laden" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Recharger le projet" - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "إعادة تحميل المشروع" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Recargar proyecto" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "重新載入專案" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "重新加载项目" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "프로젝트 다시 불러오기" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "プロジェクトを再読み込み" - } - } - } - }, - "projectPanel.reloadErrors": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Reload returned errors: %@" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Beim Neuladen sind Fehler aufgetreten: %@" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Le rechargement a renvoyé des erreurs : %@" - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "أعادت إعادة التحميل أخطاء: %@" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "La recarga devolvió errores: %@" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "重新載入時發生錯誤:%@" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "重新加载时出现错误:%@" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "다시 불러오기 중 오류 발생: %@" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "再読み込みでエラーが発生しました: %@" - } - } - } - }, - "projectPanel.dismissReloadErrors": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Dismiss Errors" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Fehler ausblenden" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ignorer les erreurs" - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "تجاهل الأخطاء" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Descartar errores" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "關閉錯誤" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "关闭错误" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "오류 닫기" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "エラーを閉じる" - } - } - } - }, - "projectPanel.scheme": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Scheme" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Schema" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Schéma" - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "المخطط" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Esquema" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "方案" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "方案" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "스킴" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "スキーム" - } - } - } - }, - "projectPanel.configuration": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Configuration" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Konfiguration" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Configuration de build" - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "التكوين" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Configuración" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "組態" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "配置" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "구성" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "構成" - } - } - } - }, - "projectPanel.loadingFormat": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Loading %@" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "%@ wird geladen" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Chargement de %@" - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "جارٍ تحميل %@" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Cargando %@" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "正在載入 %@" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "正在加载 %@" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "%@ 불러오는 중" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "%@ を読み込み中" - } - } - } - }, - "projectPanel.failedFormat": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Failed: %@" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Fehlgeschlagen: %@" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Échec : %@" - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "فشل: %@" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Error: %@" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "失敗:%@" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "失败:%@" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "실패: %@" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "失敗: %@" - } - } - } - }, - "cli.session.usage": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Usage: cmux session move <session-id> --to <ssh-destination|local> [options]\n\nMove a stopped Claude Code session to another machine and resume it there.\nExit the agent first: the move refuses while the session runs on either side.\n\nThe move carries:\n - the cwd's git checkout: HEAD (on the same branch when safe) plus modified,\n deleted and untracked non-ignored files; a worktree is added when the\n repository exists on the destination but the path does not\n - the transcript, its session directory, file history, and the project\n memory directory (merged both ways, newest wins, nothing deleted)\n\nIt then opens a workspace on the destination (`cmux ssh` for a host, a local\nworkspace for `local`) that resumes the session with its recorded launcher.\nWhen the destination home is not at the same absolute path, paths under the\nhome are mapped and the project is re-slugged.\n\nOptions:\n --to <destination> SSH destination (user@host or ssh_config alias), or local\n --from <destination> Where the session is now (default: where the last move put it, else local)\n --name <title> Workspace title (default: the first 8 characters of the id)\n --no-code Do not carry the git checkout\n --port <n> SSH port\n --identity <path> SSH identity file\n --ssh-option <opt> Extra SSH -o option (repeatable)\n --no-focus Open the workspace without switching to it\n\nExamples:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Verwendung: cmux session move <session-id> --to <ssh-destination|local> [options]\n\nVerschiebt eine beendete Claude Code-Sitzung auf eine andere Maschine und setzt sie dort fort.\nBeende zuerst den Agenten: Die Verschiebung wird abgelehnt, solange die Sitzung auf einer Seite läuft.\n\nDie Verschiebung überträgt:\n - den git-Checkout des cwd: HEAD (auf demselben Branch, wenn das sicher ist) sowie geänderte,\n gelöschte und nicht ignorierte, nicht verfolgte Dateien; ein Worktree wird hinzugefügt, wenn\n das Repository am Ziel existiert, der Pfad aber nicht\n - das Transkript, sein Sitzungsverzeichnis, den Dateiverlauf und das Projektgedächtnis\n (in beide Richtungen zusammengeführt, das Neueste gewinnt, nichts wird gelöscht)\n\nDanach öffnet sie am Ziel einen Workspace (`cmux ssh` für einen Host, einen lokalen Workspace\nfür `local`), der die Sitzung mit ihrem aufgezeichneten Launcher fortsetzt. Liegt das Home-\nVerzeichnis am Ziel nicht unter demselben absoluten Pfad, werden Pfade darunter umgeschrieben\nund das Projekt erhält einen neuen Slug.\n\nOptionen:\n --to <destination> SSH-Ziel (user@host oder ssh_config-Alias) oder local\n --from <destination> Wo die Sitzung jetzt ist (Standard: wohin die letzte Verschiebung sie gebracht hat, sonst local)\n --name <title> Workspace-Titel (Standard: die ersten 8 Zeichen der ID)\n --no-code Den git-Checkout nicht übertragen\n --port <n> SSH-Port\n --identity <path> SSH-Identitätsdatei\n --ssh-option <opt> Zusätzliche SSH-Option -o (wiederholbar)\n --no-focus Workspace öffnen, ohne dorthin zu wechseln\n\nBeispiele:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Utilisation: cmux session move <session-id> --to <ssh-destination|local> [options]\n\nDéplace une session Claude Code arrêtée vers une autre machine et la reprend là-bas.\nQuittez d'abord l'agent : le déplacement est refusé tant que la session s'exécute d'un côté ou de l'autre.\n\nLe déplacement transfère :\n - le checkout git du cwd : HEAD (sur la même branche quand c'est sûr) ainsi que les fichiers\n modifiés, supprimés et non suivis non ignorés ; un worktree est ajouté quand le dépôt existe\n sur la destination mais pas le chemin\n - la transcription, son répertoire de session, l'historique des fichiers et la mémoire du\n projet (fusionnée dans les deux sens, le plus récent l'emporte, rien n'est supprimé)\n\nIl ouvre ensuite un espace de travail sur la destination (`cmux ssh` pour un hôte, un espace\nlocal pour `local`) qui reprend la session avec son lanceur enregistré. Quand le dossier\npersonnel de destination n'a pas le même chemin absolu, les chemins qu'il contient sont\nconvertis et le projet reçoit un nouveau slug.\n\nOptions :\n --to <destination> Destination SSH (user@host ou alias ssh_config), ou local\n --from <destination> Où se trouve la session (par défaut : là où l'a mise le dernier déplacement, sinon local)\n --name <title> Titre de l'espace de travail (par défaut : les 8 premiers caractères de l'id)\n --no-code Ne pas transférer le checkout git\n --port <n> Port SSH\n --identity <path> Fichier d'identité SSH\n --ssh-option <opt> Option SSH -o supplémentaire (répétable)\n --no-focus Ouvrir l'espace de travail sans y basculer\n\nExemples :\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "الاستخدام: cmux session move <session-id> --to <ssh-destination|local> [options]\n\nينقل جلسة Claude Code متوقفة إلى جهاز آخر ويستأنفها هناك.\nأغلق الوكيل أولًا: يُرفض النقل ما دامت الجلسة تعمل على أي من الجانبين.\n\nينقل الأمر:\n - checkout الخاص بـ git في cwd: HEAD (على الفرع نفسه عندما يكون ذلك آمنًا) والملفات المعدّلة\n والمحذوفة وغير المتتبعة وغير المتجاهلة؛ ويُضاف worktree عندما يوجد المستودع في الوجهة\n ولا يوجد المسار\n - نص الجلسة ودليل الجلسة وسجل الملفات ودليل ذاكرة المشروع\n (مدمج في الاتجاهين، الأحدث يفوز، ولا يُحذف شيء)\n\nثم يفتح مساحة عمل في الوجهة (`cmux ssh` لمضيف، أو مساحة عمل محلية لـ `local`)\nتستأنف الجلسة بالمشغّل المسجّل لها. عندما لا يكون المجلد الرئيسي في الوجهة\nعلى المسار المطلق نفسه، تُحوَّل المسارات داخله\nويُعاد إنشاء slug للمشروع.\n\nالخيارات:\n --to <destination> وجهة SSH (user@host أو اسم مستعار من ssh_config)، أو local\n --from <destination> مكان الجلسة الآن (الافتراضي: حيث وضعها آخر نقل، وإلا local)\n --name <title> عنوان مساحة العمل (الافتراضي: أول 8 أحرف من المعرّف)\n --no-code لا تنقل checkout الخاص بـ git\n --port <n> منفذ SSH\n --identity <path> ملف هوية SSH\n --ssh-option <opt> خيار SSH إضافي -o (قابل للتكرار)\n --no-focus افتح مساحة العمل دون التبديل إليها\n\nأمثلة:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Uso: cmux session move <session-id> --to <ssh-destination|local> [options]\n\nMueve una sesión detenida de Claude Code a otra máquina y la reanuda allí.\nSal primero del agente: el movimiento se rechaza mientras la sesión se ejecute en cualquiera de los lados.\n\nEl movimiento lleva:\n - el checkout de git del cwd: HEAD (en la misma rama cuando es seguro) más los archivos\n modificados, eliminados y sin seguimiento no ignorados; se añade un worktree cuando el\n repositorio existe en el destino pero la ruta no\n - la transcripción, su directorio de sesión, el historial de archivos y la memoria del\n proyecto (combinada en ambos sentidos, gana la más reciente, no se borra nada)\n\nDespués abre un espacio de trabajo en el destino (`cmux ssh` para un host, uno local para\n`local`) que reanuda la sesión con su lanzador registrado. Cuando el home del destino no está\nen la misma ruta absoluta, las rutas bajo él se convierten\ny el proyecto recibe un nuevo slug.\n\nOpciones:\n --to <destination> Destino SSH (user@host o alias de ssh_config), o local\n --from <destination> Dónde está la sesión ahora (por defecto: donde la dejó el último movimiento, si no local)\n --name <title> Título del espacio de trabajo (por defecto: los primeros 8 caracteres del id)\n --no-code No llevar el checkout de git\n --port <n> Puerto SSH\n --identity <path> Archivo de identidad SSH\n --ssh-option <opt> Opción SSH -o adicional (repetible)\n --no-focus Abrir el espacio de trabajo sin cambiar a él\n\nEjemplos:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "用法: cmux session move <session-id> --to <ssh-destination|local> [options]\n\n將已停止的 Claude Code 工作階段移到另一台機器並在那裡恢復。\n請先結束代理:只要工作階段仍在任一端執行,移動就會被拒絕。\n\n移動會帶上:\n - cwd 的 git checkout:HEAD(安全時使用同一分支)加上已修改、已刪除\n 和未追蹤且未忽略的檔案;若目的地有儲存庫\n 但沒有該路徑,會新增 worktree\n - 對話紀錄、其工作階段目錄、檔案歷史與專案記憶目錄\n (雙向合併,較新者優先,不刪除任何內容)\n\n接著在目的地開啟工作區(主機用 `cmux ssh`,`local` 則開本機工作區),\n以記錄的啟動器恢復工作階段。\n若目的地的主目錄不在相同的絕對路徑,其下的路徑會被對應,\n專案也會重新產生 slug。\n\n選項:\n --to <destination> SSH 目的地(user@host 或 ssh_config 別名),或 local\n --from <destination> 工作階段目前所在位置(預設:上次移動所放的位置,否則為 local)\n --name <title> 工作區標題(預設:ID 的前 8 個字元)\n --no-code 不帶上 git checkout\n --port <n> SSH 連接埠\n --identity <path> SSH 身分檔案\n --ssh-option <opt> 額外的 SSH -o 選項(可重複)\n --no-focus 開啟工作區但不切換過去\n\n範例:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "用法: cmux session move <session-id> --to <ssh-destination|local> [options]\n\n将已停止的 Claude Code 会话移到另一台机器并在那里恢复。\n请先退出代理:只要会话仍在任一端运行,移动就会被拒绝。\n\n移动会带上:\n - cwd 的 git 检出:HEAD(安全时使用同一分支)以及已修改、已删除\n 和未跟踪且未忽略的文件;若目标上有仓库\n 但没有该路径,会添加 worktree\n - 会话记录、其会话目录、文件历史和项目记忆目录\n (双向合并,较新者优先,不删除任何内容)\n\n随后在目标上打开工作区(主机用 `cmux ssh`,`local` 则打开本地工作区),\n用记录的启动器恢复会话。\n若目标的主目录不在相同的绝对路径,其下的路径会被映射,\n项目也会重新生成 slug。\n\n选项:\n --to <destination> SSH 目标(user@host 或 ssh_config 别名),或 local\n --from <destination> 会话当前所在位置(默认:上次移动放置的位置,否则为 local)\n --name <title> 工作区标题(默认:ID 的前 8 个字符)\n --no-code 不带上 git 检出\n --port <n> SSH 端口\n --identity <path> SSH 身份文件\n --ssh-option <opt> 额外的 SSH -o 选项(可重复)\n --no-focus 打开工作区但不切换过去\n\n示例:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "사용법: cmux session move <session-id> --to <ssh-destination|local> [options]\n\n중지된 Claude Code 세션을 다른 머신으로 옮기고 그곳에서 재개합니다.\n먼저 에이전트를 종료하세요. 어느 쪽에서든 세션이 실행 중이면 이동이 거부됩니다.\n\n이동하는 항목:\n - cwd의 git 체크아웃: HEAD(안전하면 같은 브랜치)와 수정, 삭제,\n 무시되지 않은 추적 안 된 파일. 대상에 저장소는 있지만\n 경로가 없으면 worktree를 추가합니다\n - 트랜스크립트, 세션 디렉터리, 파일 기록, 프로젝트 메모리 디렉터리\n (양방향 병합, 최신 우선, 삭제 없음)\n\n그런 다음 대상에서 작업 공간(호스트는 `cmux ssh`, `local`은 로컬 작업 공간)을 열고\n기록된 런처로 세션을 재개합니다.\n대상의 홈이 같은 절대 경로에 있지 않으면 홈 아래 경로를 변환하고\n프로젝트 slug를 다시 만듭니다.\n\n옵션:\n --to <destination> SSH 대상(user@host 또는 ssh_config 별칭) 또는 local\n --from <destination> 세션의 현재 위치(기본값: 마지막 이동으로 옮긴 곳, 없으면 local)\n --name <title> 작업 공간 제목(기본값: ID의 처음 8자)\n --no-code git 체크아웃을 옮기지 않음\n --port <n> SSH 포트\n --identity <path> SSH ID 파일\n --ssh-option <opt> 추가 SSH -o 옵션(반복 가능)\n --no-focus 작업 공간으로 전환하지 않고 열기\n\n예시:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "使用方法: cmux session move <session-id> --to <ssh-destination|local> [options]\n\n停止した Claude Code セッションを別のマシンへ移動し、そこで再開します。\n先にエージェントを終了してください。どちらかでセッションが実行中の間は移動を拒否します。\n\n移動する内容:\n - cwd の git チェックアウト: HEAD (安全なら同じブランチ) と、変更・削除されたファイル、\n 無視されていない未追跡ファイル。移動先にリポジトリはあるが\n パスがない場合は worktree を追加します\n - トランスクリプト、セッションディレクトリ、ファイル履歴、プロジェクトのメモリディレクトリ\n (双方向にマージ、新しいほうを優先、削除はしません)\n\nその後、移動先でワークスペース (ホストなら `cmux ssh`、`local` ならローカルのワークスペース) を\n開き、記録されたランチャーでセッションを再開します。\n移動先のホームが同じ絶対パスにない場合は、ホーム配下のパスを変換し、\nプロジェクトの slug を作り直します。\n\nオプション:\n --to <destination> SSH 接続先 (user@host または ssh_config のエイリアス)、または local\n --from <destination> セッションの現在の場所 (既定: 前回の移動先、なければ local)\n --name <title> ワークスペースのタイトル (既定: ID の先頭 8 文字)\n --no-code git チェックアウトを移動しない\n --port <n> SSH ポート\n --identity <path> SSH の ID ファイル\n --ssh-option <opt> 追加の SSH -o オプション (繰り返し可)\n --no-focus ワークスペースに切り替えずに開く\n\n例:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" - } - } - } - }, - "settings.notifications.sound.preview": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Preview Sound" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Ton anhören" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Écouter le son" - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "معاينة الصوت" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Escuchar sonido" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "試聽音效" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "试听声音" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "소리 미리 듣기" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "サウンドをプレビュー" - } - } - } - }, "cli.session.error.unknownSubcommand": { "extractionState": "manual", "localizations": { @@ -560262,6 +559790,478 @@ } } } + }, + "browser.omnibar.accessibilityLabel": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Address and search bar" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Adress- und Suchleiste" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Barre d’adresse et de recherche" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "شريط العنوان والبحث" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Barra de direcciones y búsqueda" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "網址與搜尋列" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "地址和搜索栏" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "주소 및 검색 막대" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アドレスと検索バー" + } + } + } + }, + "projectPanel.reload": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reload Project" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Projekt neu laden" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Recharger le projet" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إعادة تحميل المشروع" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Recargar proyecto" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重新載入專案" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重新加载项目" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "프로젝트 다시 불러오기" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "プロジェクトを再読み込み" + } + } + } + }, + "projectPanel.reloadErrors": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reload returned errors: %@" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Beim Neuladen sind Fehler aufgetreten: %@" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Le rechargement a renvoyé des erreurs : %@" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "أعادت إعادة التحميل أخطاء: %@" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "La recarga devolvió errores: %@" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重新載入時發生錯誤:%@" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重新加载时出现错误:%@" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "다시 불러오기 중 오류 발생: %@" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "再読み込みでエラーが発生しました: %@" + } + } + } + }, + "projectPanel.dismissReloadErrors": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dismiss Errors" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Fehler ausblenden" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ignorer les erreurs" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تجاهل الأخطاء" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Descartar errores" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "關閉錯誤" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "关闭错误" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "오류 닫기" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "エラーを閉じる" + } + } + } + }, + "projectPanel.scheme": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Scheme" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Schema" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Schéma" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "المخطط" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Esquema" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "方案" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "方案" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "스킴" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "スキーム" + } + } + } + }, + "projectPanel.configuration": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Configuration" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Konfiguration" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Configuration de build" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "التكوين" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Configuración" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "組態" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "配置" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "구성" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "構成" + } + } + } + }, + "projectPanel.loadingFormat": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Loading %@" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "%@ wird geladen" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Chargement de %@" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "جارٍ تحميل %@" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cargando %@" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "正在載入 %@" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "正在加载 %@" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "%@ 불러오는 중" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "%@ を読み込み中" + } + } + } + }, + "projectPanel.failedFormat": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Failed: %@" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Fehlgeschlagen: %@" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Échec : %@" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فشل: %@" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Error: %@" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "失敗:%@" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "失败:%@" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "실패: %@" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "失敗: %@" + } + } + } } }, "version": "1.0" From 1711be36c4c89dcfd13bae95d53af8b1240d4c17 Mon Sep 17 00:00:00 2001 From: Leo Li <cheerleaderleo@outlook.com> Date: Sun, 27 Sep 2026 10:12:22 -0400 Subject: [PATCH 06/11] Terminal chat view: composer, interrupt, and pending-input banner The view is no longer read-only. The composer types the prompt into the terminal's agent through mobile.chat.send (the iOS chat's bracketed-paste delivery), and Stop or Esc sends mobile.chat.interrupt. Both are now reachable on the local control socket (worker lane) and the sidecar calls them through cmux rpc with the socket and CLI paths the app passes it. The prompt renders at once and the transcript's copy replaces it. When the hook store says the agent needs input, the view shows the request with an "Answer in terminal" button that focuses the terminal pane. Also fixes two rendering bugs: the collapsed turn summary counted every tool call as a command (only shell tools count now; structured tools are reported by what they did), and a running turn's thinking and interim text rendered outside the centered column. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --- ...ControlCommandCoordinator+MobileHost.swift | 4 + .../MobileHost/ControlMobileHostContext.swift | 31 ++++++ .../Wire/ControlCommandExecutionPolicy.swift | 6 +- ...rolCommandCoordinatorMobileHostTests.swift | 24 ++++ Sources/AppDelegate+AgentChat.swift | 3 + Sources/TerminalController+Capabilities.swift | 2 + ...lController+ControlMobileHostContext.swift | 14 +++ Sources/TerminalController.swift | 2 +- agent-chat/README.md | 4 +- agent-chat/adapters/transcript.ts | 61 +++++++++-- agent-chat/cmux-chat | 2 +- agent-chat/cmux-rpc.ts | 38 +++++++ agent-chat/public/app.css | 24 +++- agent-chat/server.ts | 41 +++++-- agent-chat/src/components/Chat.tsx | 73 +++++++------ agent-chat/src/components/Transcript.tsx | 8 +- agent-chat/src/i18n.ts | 103 +++++++++++------- agent-chat/src/session.ts | 25 ++++- agent-chat/src/turns.ts | 21 +++- agent-chat/test/i18n.test.ts | 1 + agent-chat/test/transcript.test.ts | 47 +++++++- agent-chat/test/turns.test.ts | 16 +++ agent-chat/transcript-sources.ts | 17 +++ 23 files changed, 459 insertions(+), 108 deletions(-) create mode 100644 agent-chat/cmux-rpc.ts diff --git a/Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/MobileHost/ControlCommandCoordinator+MobileHost.swift b/Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/MobileHost/ControlCommandCoordinator+MobileHost.swift index 12a5e864c25b..5cf9694838c5 100644 --- a/Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/MobileHost/ControlCommandCoordinator+MobileHost.swift +++ b/Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/MobileHost/ControlCommandCoordinator+MobileHost.swift @@ -40,6 +40,10 @@ extension ControlCommandCoordinator { return await context?.controlMobileTaskModelsList( params: request.params ) + case "mobile.chat.send": + return await context?.controlMobileChatSend(params: request.params) + case "mobile.chat.interrupt": + return await context?.controlMobileChatInterrupt(params: request.params) default: return nil } diff --git a/Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/MobileHost/ControlMobileHostContext.swift b/Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/MobileHost/ControlMobileHostContext.swift index e2c845c91c88..3906eab8e4d8 100644 --- a/Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/MobileHost/ControlMobileHostContext.swift +++ b/Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/MobileHost/ControlMobileHostContext.swift @@ -136,6 +136,23 @@ public protocol ControlMobileHostContext: AnyObject { params: [String: JSONValue] ) async -> ControlCallResult + /// `mobile.chat.send` — types a prompt into an agent session's terminal, + /// the same delivery the iOS chat uses. + /// + /// - Parameter params: The decoded request params (`session_id`, `text`). + /// - Returns: The fully-built command result. + nonisolated func controlMobileChatSend( + params: [String: JSONValue] + ) async -> ControlCallResult + + /// `mobile.chat.interrupt` — interrupts an agent session's running turn. + /// + /// - Parameter params: The decoded request params (`session_id`, `hard`). + /// - Returns: The fully-built command result. + nonisolated func controlMobileChatInterrupt( + params: [String: JSONValue] + ) async -> ControlCallResult + /// `chat.sessions.dump` (local debug socket) — the full chat-session registry /// dump, for diagnosing inconsistent phone-side chat state. /// @@ -154,4 +171,18 @@ public extension ControlMobileHostContext { data: nil ) } + + /// Default for test and partial contexts without agent chat delivery. + nonisolated func controlMobileChatSend( + params: [String: JSONValue] + ) async -> ControlCallResult { + .err(code: "method_not_found", message: "Agent chat is unavailable", data: nil) + } + + /// Default for test and partial contexts without agent chat delivery. + nonisolated func controlMobileChatInterrupt( + params: [String: JSONValue] + ) async -> ControlCallResult { + .err(code: "method_not_found", message: "Agent chat is unavailable", data: nil) + } } diff --git a/Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Wire/ControlCommandExecutionPolicy.swift b/Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Wire/ControlCommandExecutionPolicy.swift index 1047c0bd46b5..4e73bdd3fefe 100644 --- a/Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Wire/ControlCommandExecutionPolicy.swift +++ b/Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Wire/ControlCommandExecutionPolicy.swift @@ -107,9 +107,9 @@ public enum ControlCommandExecutionPolicy: Sendable, Equatable { "browser.profiles.delete", "browser.import.cookies", "mobile.attach_ticket.create", - // Provider discovery may read configuration or run `opencode models`; - // it must never hold the main actor while waiting for process I/O. - "mobile.task.models.list", + // Provider discovery reads config or runs `opencode models`; chat + // send/interrupt await terminal input. Neither may hold the main actor. + "mobile.task.models.list", "mobile.chat.send", "mobile.chat.interrupt", // `mobile.terminal.set_font` only validates params and emits a push // event via thread-safe MobileHostService statics, so it runs on the worker // like the other mobile data-plane verbs. Without this entry the policy diff --git a/Packages/macOS/CmuxControlSocket/Tests/CmuxControlSocketTests/ControlCommandCoordinatorMobileHostTests.swift b/Packages/macOS/CmuxControlSocket/Tests/CmuxControlSocketTests/ControlCommandCoordinatorMobileHostTests.swift index 6989e69173d6..beebb1461d2b 100644 --- a/Packages/macOS/CmuxControlSocket/Tests/CmuxControlSocketTests/ControlCommandCoordinatorMobileHostTests.swift +++ b/Packages/macOS/CmuxControlSocket/Tests/CmuxControlSocketTests/ControlCommandCoordinatorMobileHostTests.swift @@ -69,6 +69,18 @@ private final class FakeMobileHostControlCommandContext: ControlCommandContext { } } + nonisolated func controlMobileChatSend( + params: [String: JSONValue] + ) async -> ControlCallResult { + await MainActor.run { record("chat.send", params) } + } + + nonisolated func controlMobileChatInterrupt( + params: [String: JSONValue] + ) async -> ControlCallResult { + await MainActor.run { record("chat.interrupt", params) } + } + func controlMobileChatSessionsDump() -> ControlCallResult { record("chat.sessions.dump", [:]) } @@ -127,6 +139,18 @@ struct ControlCommandCoordinatorMobileHostTests { ) } + @Test func workerSurfaceRoutesChatSendAndInterruptThroughAsyncSeam() async { + let (coordinator, context) = makeCoordinator() + let params: [String: JSONValue] = ["session_id": .string("abc"), "text": .string("hi")] + #expect(await coordinator.handleMobileHostAsync(request("mobile.chat.send", params), context: context) != nil) + #expect(context.lastMarker == "chat.send") + #expect(context.lastParams == params) + #expect(await coordinator.handleMobileHostAsync(request("mobile.chat.interrupt"), context: context) != nil) + #expect(context.lastMarker == "chat.interrupt") + #expect(ControlCommandExecutionPolicy(forMethod: "mobile.chat.send") == .socketWorker(mainThreadCallable: false)) + #expect(ControlCommandExecutionPolicy(forMethod: "mobile.chat.interrupt") == .socketWorker(mainThreadCallable: false)) + } + @Test func v2SurfaceUsesPrivateHostStatusVariant() { let (coordinator, context) = makeCoordinator() #expect(coordinator.handle(request("mobile.host.status")) != nil) diff --git a/Sources/AppDelegate+AgentChat.swift b/Sources/AppDelegate+AgentChat.swift index 90999bf556e9..7bea893d0794 100644 --- a/Sources/AppDelegate+AgentChat.swift +++ b/Sources/AppDelegate+AgentChat.swift @@ -339,6 +339,9 @@ extension AppDelegate { "CMUX_AGENT_CHAT_PORT": "0", "CMUX_AGENT_CHAT_STATE_FILE": stateFileURL.path, "CMUX_AGENT_CHAT_LAUNCH_ID": launchId, + // Terminal chat views deliver prompts over this app's socket. + "CMUX_SOCKET_PATH": TerminalController.shared.activeSocketPath(preferredPath: SocketControlSettings.socketPath()), + "CMUX_BUNDLED_CLI_PATH": CLIForwardingLaunchRouter.bundledCLIURL()?.path ?? "", ] ) else { return AgentChatServerAvailability(isReachable: false, browserURL: agentChat.url) diff --git a/Sources/TerminalController+Capabilities.swift b/Sources/TerminalController+Capabilities.swift index e49a038c2480..8845d116da0f 100644 --- a/Sources/TerminalController+Capabilities.swift +++ b/Sources/TerminalController+Capabilities.swift @@ -57,6 +57,8 @@ extension TerminalController { "mobile.compatible_tags.set", "mobile.task.attachment.upload", "mobile.task.models.list", + "mobile.chat.send", + "mobile.chat.interrupt", // Socket-reachable panel artifact reads (worker lane); fetch stays // behind the authenticated mobile execution context. "mobile.panel.artifact.stat", diff --git a/Sources/TerminalController+ControlMobileHostContext.swift b/Sources/TerminalController+ControlMobileHostContext.swift index 299f74563eb9..7fb2e2dccfeb 100644 --- a/Sources/TerminalController+ControlMobileHostContext.swift +++ b/Sources/TerminalController+ControlMobileHostContext.swift @@ -77,6 +77,20 @@ extension TerminalController: ControlMobileHostContext { ) } + nonisolated func controlMobileChatSend( + params: [String: JSONValue] + ) async -> ControlCallResult { + let params = foundationParams(params) + return bridgeMobileResult(await v2MobileChatSend(params: params)) + } + + nonisolated func controlMobileChatInterrupt( + params: [String: JSONValue] + ) async -> ControlCallResult { + let params = foundationParams(params) + return bridgeMobileResult(await v2MobileChatInterrupt(params: params)) + } + func controlMobileChatSessionsDump() -> ControlCallResult { bridgeMobileResult(v2ChatSessionsDump()) } diff --git a/Sources/TerminalController.swift b/Sources/TerminalController.swift index 260c8dfbaee0..dd26fdf3a48d 100644 --- a/Sources/TerminalController.swift +++ b/Sources/TerminalController.swift @@ -1308,7 +1308,7 @@ class TerminalController { await self.v2SurfaceReadSelection(params: parsedRequest.params) } } - if request.method == "mobile.task.models.list" { + if ["mobile.task.models.list", "mobile.chat.send", "mobile.chat.interrupt"].contains(request.method) { return v2AsyncResultCall( id: request.id, timeoutSeconds: 7 diff --git a/agent-chat/README.md b/agent-chat/README.md index 17e4ad5ab957..31413b30cb76 100644 --- a/agent-chat/README.md +++ b/agent-chat/README.md @@ -14,9 +14,9 @@ One page = one session: `/` is the composer, `/s/<id>` a chat. When `CMUX_AGENT_ ## Terminal chat view -`/terminal/<surface-id>` shows a Claude Code or Codex session that is already running in a cmux terminal as a read-only chat, without starting another agent. The sidecar looks the surface up in the hook session stores (`~/.cmuxterm/claude-hook-sessions.json`, `~/.cmuxterm/codex-hook-sessions.json`), tails the agent's own transcript (Claude `projects/<slug>/<session>.jsonl`, Codex `sessions/YYYY/MM/DD/rollout-*-<session>.jsonl`), and normalizes it into `AgentEvent`s with `adapters/transcript.ts`, so the regular chat renderer draws it. The page lives at `/s/t-<agent session id>`, which re-resolves after a sidecar restart; a view with no open page stops tailing after five minutes. +`/terminal/<surface-id>` shows a Claude Code or Codex session that is already running in a cmux terminal as a chat, without starting another agent. The sidecar looks the surface up in the hook session stores (`~/.cmuxterm/claude-hook-sessions.json`, `~/.cmuxterm/codex-hook-sessions.json`), tails the agent's own transcript (Claude `projects/<slug>/<session>.jsonl`, Codex `sessions/YYYY/MM/DD/rollout-*-<session>.jsonl`), and normalizes it into `AgentEvent`s with `adapters/transcript.ts`, so the regular chat renderer draws it. The page lives at `/s/t-<agent session id>`, which re-resolves after a sidecar restart; a view with no open page stops tailing after five minutes. -The terminal stays the source of truth. The view has no composer: replies, permission prompts, and pickers stay in the terminal, and the footer says so. Open it with "Open terminal as chat" in the command palette (Agent Chat UI flag) or `cmux-chat --terminal --surface <surface-id>` from a shell. +The terminal stays the source of truth. The composer types the prompt into the terminal's agent with `mobile.chat.send` (bracketed paste and submit, the delivery the iOS chat uses), and Stop or Esc sends `mobile.chat.interrupt`; both go over the cmux control socket through `cmux rpc` (`cmux-rpc.ts`, using the `CMUX_BUNDLED_CLI_PATH` and `CMUX_SOCKET_PATH` the app passes to the sidecar). The prompt shows immediately and is replaced by the transcript's own copy when it lands. Permission prompts, questions, and pickers stay in the terminal: when the hook store says the agent needs input, the view shows the request with an "Answer in terminal" button that focuses the terminal pane. Open the view with "Open terminal as chat" in the command palette (Agent Chat UI flag) or `cmux-chat --terminal --surface <surface-id>` from a shell. ## Model catalog diff --git a/agent-chat/adapters/transcript.ts b/agent-chat/adapters/transcript.ts index f1eb867ebdff..b9b6fb0680fe 100644 --- a/agent-chat/adapters/transcript.ts +++ b/agent-chat/adapters/transcript.ts @@ -1,15 +1,38 @@ -// Read-only transcript view: renders the JSONL transcript a terminal agent -// (Claude Code, Codex) writes as it runs, so a cmux terminal can be shown as a -// chat without starting a second agent process. The terminal stays the source -// of truth; this adapter only tails the file and normalizes it into AgentEvent. +// Terminal chat view: renders the JSONL transcript a terminal agent (Claude +// Code, Codex) writes as it runs, so a cmux terminal can be shown as a chat +// without starting a second agent process. The terminal stays the source of +// truth: this adapter tails the file and normalizes it into AgentEvent, and +// prompts and interrupts go to the terminal through the cmux control socket. import { open, stat } from "node:fs/promises"; import type { Adapter, AgentEvent, OptionValue, SessionCtx } from "../types"; +import { cmuxRpc, type CmuxRpcResult } from "../cmux-rpc"; import { tryParse, truncate } from "./lines"; export type TranscriptAgent = "claude" | "codex"; -export const TRANSCRIPT_READ_ONLY_MESSAGE = - "This view mirrors the terminal session. Switch to the terminal to reply, approve permissions, or use slash commands."; +/** The terminal agent session a transcript view drives. */ +export interface TranscriptTarget { + agentSessionId: string; + surfaceId?: string; +} + +type Rpc = (method: string, params: Record<string, unknown>) => Promise<CmuxRpcResult>; +let rpc: Rpc = cmuxRpc; + +export function setTranscriptRpcForTest(next: Rpc | null) { + rpc = next ?? cmuxRpc; +} + +export function transcriptTarget(sess: SessionCtx): TranscriptTarget | undefined { + return sess.internal.transcriptTarget as TranscriptTarget | undefined; +} + +/** Focuses the terminal pane that runs the agent (for prompts the view cannot answer). */ +export async function focusTranscriptTerminal(sess: SessionCtx): Promise<CmuxRpcResult> { + const surfaceId = transcriptTarget(sess)?.surfaceId; + if (!surfaceId) return { ok: false, error: "The terminal for this session is unknown." }; + return rpc("surface.focus", { surface_id: surfaceId }); +} export interface TranscriptParser { readonly agent: TranscriptAgent; @@ -447,13 +470,14 @@ export function attachTranscript( agent: TranscriptAgent, path: string, onTitle?: (title: string) => void, - opts: { pollMs?: number; initialWindowBytes?: number } = {}, + opts: { pollMs?: number; initialWindowBytes?: number; onTick?: () => void } = {}, ): TranscriptTail { const parser = transcriptParser(agent); const refreshStatus = () => { const st = transcriptState(sess); if (!st) return; sess.setStatus(transcriptLooksRunning(sess.events, st.lastWriteMs) ? "running" : "idle"); + opts.onTick?.(); }; const tail = new TranscriptTail(path, (lines, mtimeMs) => { const st = transcriptState(sess); @@ -471,7 +495,7 @@ export function attachTranscript( tail, parser, lastWriteMs: 0, - statusTimer: setInterval(refreshStatus, 5_000), + statusTimer: setInterval(refreshStatus, 2_000), }; sess.internal.transcript = state; sess.internal.transcriptPath = path; @@ -480,11 +504,24 @@ export function attachTranscript( } export const transcriptAdapter: Adapter = { - send(sess: SessionCtx) { - sess.emit({ kind: "status", text: TRANSCRIPT_READ_ONLY_MESSAGE }); + // The prompt is typed into the terminal's agent (bracketed paste + submit, + // the delivery the iOS chat uses). The transcript then records it, which is + // what renders the user message, so nothing is emitted here on success. + async send(sess: SessionCtx, prompt: string) { + const target = transcriptTarget(sess); + if (!target) { + sess.emit({ kind: "error", message: "This view is not attached to a terminal session." }); + return; + } + const res = await rpc("mobile.chat.send", { session_id: target.agentSessionId, text: prompt }); + if (!res.ok) sess.emit({ kind: "error", message: `Couldn't send to the terminal: ${res.error}` }); }, - stop() { - // The agent runs in the terminal; interrupting it belongs there. + stop(sess: SessionCtx) { + const target = transcriptTarget(sess); + if (!target) return; + void rpc("mobile.chat.interrupt", { session_id: target.agentSessionId }).then((res) => { + if (!res.ok) sess.emit({ kind: "error", message: `Couldn't interrupt the terminal: ${res.error}` }); + }); }, dispose(sess: SessionCtx) { const st = transcriptState(sess); diff --git a/agent-chat/cmux-chat b/agent-chat/cmux-chat index 61ea595fbc33..10edb23cfbdb 100755 --- a/agent-chat/cmux-chat +++ b/agent-chat/cmux-chat @@ -6,7 +6,7 @@ # cmux-chat -p codex refactor foo pick provider (claude/codex/opencode/pi/gemini) # cmux-chat --split open as a split in the current workspace instead # cmux-chat --no-open ... print the URL only -# cmux-chat --terminal [--surface ID] read-only chat view of the agent in a +# cmux-chat --terminal [--surface ID] chat view of the agent running in a # terminal ($CMUX_SURFACE_ID), as a split # # Starts the sidecar server (bun server.ts) if it isn't running. diff --git a/agent-chat/cmux-rpc.ts b/agent-chat/cmux-rpc.ts new file mode 100644 index 000000000000..b3d94af89e73 --- /dev/null +++ b/agent-chat/cmux-rpc.ts @@ -0,0 +1,38 @@ +// Calls the cmux app's control socket through the bundled CLI (`cmux rpc`). +// The app passes CMUX_BUNDLED_CLI_PATH and CMUX_SOCKET_PATH to the sidecar it +// launches; a manually started sidecar falls back to `cmux` on PATH. + +export interface CmuxRpcResult { ok: boolean; result?: unknown; error?: string } + +const RPC_TIMEOUT_MS = 10_000; + +export async function cmuxRpc(method: string, params: Record<string, unknown>): Promise<CmuxRpcResult> { + const cli = process.env.CMUX_BUNDLED_CLI_PATH?.trim() || "cmux"; + let proc: ReturnType<typeof Bun.spawn>; + try { + proc = Bun.spawn([cli, "rpc", method, JSON.stringify(params)], { + stdin: "ignore", + stdout: "pipe", + stderr: "pipe", + env: process.env, + }); + } catch (err) { + return { ok: false, error: `cmux CLI unavailable: ${err instanceof Error ? err.message : String(err)}` }; + } + const timer = setTimeout(() => proc.kill(), RPC_TIMEOUT_MS); + try { + const [stdout, stderr, code] = await Promise.all([ + new Response(proc.stdout as ReadableStream).text(), + new Response(proc.stderr as ReadableStream).text(), + proc.exited, + ]); + if (code !== 0) return { ok: false, error: (stderr.trim() || stdout.trim() || `cmux rpc exited ${code}`).slice(0, 300) }; + try { + return { ok: true, result: JSON.parse(stdout) }; + } catch { + return { ok: true, result: stdout.trim() }; + } + } finally { + clearTimeout(timer); + } +} diff --git a/agent-chat/public/app.css b/agent-chat/public/app.css index 0ff5198358bb..d383b75fd991 100644 --- a/agent-chat/public/app.css +++ b/agent-chat/public/app.css @@ -610,6 +610,9 @@ details.thinking .t-body { white-space: pre-wrap; font-size: calc(var(--font-siz white-space: pre-wrap; color: var(--text-faint); font: calc(var(--font-size-base) - 2px)/1.5 var(--font-sans); padding: 4px 6px 6px; } +/* A running turn renders its activity directly in the turn, outside the + collapsed summary; keep thinking and interim text in the content column. */ +.turn-live-activity { max-width: 760px; margin: 0 auto 10px; padding: 0 24px; } .turn-actions { max-width: 760px; margin: -8px auto 18px; padding: 0 24px; display: flex; align-items: center; gap: 6px; @@ -670,14 +673,29 @@ details.thinking .t-body { white-space: pre-wrap; font-size: calc(var(--font-siz background: color-mix(in srgb, var(--accent) 10%, var(--bg-input)); color: var(--text-dim); font: 400 calc(var(--font-size-base) - 2px) var(--font-sans); } -.transcript-notice { - display: flex; align-items: center; gap: 8px; padding: 9px 12px; border-radius: 12px; - background: var(--bg-input); color: var(--text-dim); font: 400 calc(var(--font-size-base) - 2px) var(--font-sans); +.transcript-composer-row { + display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 32px; padding: 0 0 1px 8px; +} +.transcript-hint { + display: inline-flex; align-items: center; gap: 8px; min-width: 0; + color: var(--text-faint); font: 400 calc(var(--font-size-base) - 2px) var(--font-sans); + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .transcript-dot { flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--text-faint); } .transcript-dot.running { background: var(--accent); animation: transcript-pulse 1.4s ease-in-out infinite; } @keyframes transcript-pulse { 50% { opacity: 0.35; } } @media (prefers-reduced-motion: reduce) { .transcript-dot.running { animation: none; } } +.terminal-attention { + display: flex; align-items: center; gap: 10px; margin: 0 0 7px; padding: 8px 8px 8px 12px; border-radius: 10px; + background: color-mix(in srgb, var(--accent) 12%, var(--bg-input)); + color: var(--text); font: 400 calc(var(--font-size-base) - 1.5px) var(--font-sans); +} +.terminal-attention-text { flex: 1; min-width: 0; overflow-wrap: anywhere; } +.terminal-attention-btn { + flex: none; height: 28px; padding: 0 11px; border: 0; border-radius: 8px; cursor: default; + background: var(--accent); color: #fff; font: 500 calc(var(--font-size-base) - 2px) var(--font-sans); +} +.terminal-attention-btn:hover { filter: brightness(1.08); } #chat-card { display: flex; flex-direction: column; gap: 6px; padding: 6px; background: var(--bg-input); border: 0; border-radius: 14px; diff --git a/agent-chat/server.ts b/agent-chat/server.ts index 8fefb9a0b78c..462af4ee68c3 100644 --- a/agent-chat/server.ts +++ b/agent-chat/server.ts @@ -16,8 +16,8 @@ import { claudeAdapter } from "./adapters/claude"; import { codexAdapter } from "./adapters/codex"; import { piAdapter } from "./adapters/pi"; import { makeAcpAdapter } from "./adapters/acp"; -import { attachTranscript, transcriptAdapter, type TranscriptAgent } from "./adapters/transcript"; -import { resolveSessionTranscript, resolveSurfaceTranscript, type TranscriptSource } from "./transcript-sources"; +import { attachTranscript, focusTranscriptTerminal, transcriptAdapter, type TranscriptAgent } from "./adapters/transcript"; +import { resolveSessionTranscript, resolveSurfaceTranscript, transcriptAttention, type TranscriptSource } from "./transcript-sources"; import { pickAccentColor, resolveGhosttyTheme, resolveGhosttyThemeAsync, type GhosttyTheme } from "./theme"; import { agentModelCatalog, type AgentModelProviderCatalog } from "./catalog"; import { discoverHarnesses } from "./harnesses"; @@ -173,8 +173,14 @@ interface Session extends SessionCtx { adapter: Adapter; sockets: Set<Bun.ServerWebSocket<WsData>>; createdAt: number; - /** Set for read-only views of a terminal agent's transcript. */ - transcript?: { agent: TranscriptAgent; path: string; disposeTimer?: ReturnType<typeof setTimeout> }; + /** Set for chat views of an agent running in a cmux terminal. */ + transcript?: { + agent: TranscriptAgent; + path: string; + disposeTimer?: ReturnType<typeof setTimeout>; + /** What the agent is waiting on in the terminal (permission, question), if anything. */ + attention?: string | null; + }; } interface WsData { subscribed: string | null; @@ -242,7 +248,7 @@ function sessionSummary(s: Session) { parentConversationId: s.parentConversationId, startRequestId: s.startRequestId, capabilities: s.transcript ? s.adapter.capabilities : capabilitiesFor(s.provider), - ...(s.transcript ? { mode: "transcript" as const } : {}), + ...(s.transcript ? { mode: "transcript" as const, attention: s.transcript.attention ?? null } : {}), }; } @@ -570,7 +576,7 @@ function emitDoneAfterFiles(sess: Session, evt: InternalDoneEvent) { function sendPrompt(sess: Session, prompt: string, requestId = crypto.randomUUID()) { if (sess.transcript) { - // Transcript views have no agent process; the adapter explains that. + // Typed into the terminal's agent; the transcript records the prompt. void sess.adapter.send(sess, prompt); return; } @@ -638,6 +644,7 @@ function ensureTranscriptSession(source: TranscriptSource): Session { existing.events.length = 0; delete existing.internal.eventGenerations; existing.transcript.path = source.path; + existing.internal.transcriptTarget = { agentSessionId: source.sessionId, surfaceId: source.surfaceId }; startTranscriptTail(existing, source); broadcastSessionHistory(existing); return existing; @@ -647,6 +654,7 @@ function ensureTranscriptSession(source: TranscriptSource): Session { adapter: transcriptAdapter, }); sess.transcript = { agent: source.agent, path: source.path }; + sess.internal.transcriptTarget = { agentSessionId: source.sessionId, surfaceId: source.surfaceId }; startTranscriptTail(sess, source); return sess; } @@ -658,7 +666,18 @@ function startTranscriptTail(sess: Session, source: TranscriptSource) { broadcastSessions(); const payload = JSON.stringify({ kind: "session-title", sessionId: sess.id, title }); for (const ws of sess.sockets) ws.send(payload); - }); + }, { onTick: () => refreshTranscriptAttention(sess, source) }); +} + +// Permission prompts, questions, and pickers live in the terminal and are not +// in the transcript until answered; the hook store says when the agent waits. +function refreshTranscriptAttention(sess: Session, source: TranscriptSource) { + if (!sess.transcript || !sess.sockets.size) return; + const attention = transcriptAttention(source.agent, source.sessionId); + if ((sess.transcript.attention ?? null) === attention) return; + sess.transcript.attention = attention; + const payload = JSON.stringify({ kind: "session-attention", sessionId: sess.id, attention }); + for (const ws of sess.sockets) ws.send(payload); } function resolveTranscriptSessionById(id: string): Session | undefined { @@ -2220,6 +2239,14 @@ function handleMessage(ws: Bun.ServerWebSocket<WsData>, msg: any) { refreshSession(sess); break; } + case "focus-terminal": { + const sess = sessions.get(String(msg.sessionId)); + if (!sess?.transcript) return; + Promise.resolve(focusTranscriptTerminal(sess)).then((res) => { + if (!res.ok) sess.emit({ kind: "error", message: `Couldn't focus the terminal: ${res.error}` }); + }); + break; + } case "stop": { const sess = sessions.get(String(msg.sessionId)); sess?.adapter.stop(sess); diff --git a/agent-chat/src/components/Chat.tsx b/agent-chat/src/components/Chat.tsx index 634fb26ac637..bd609edba2a9 100644 --- a/agent-chat/src/components/Chat.tsx +++ b/agent-chat/src/components/Chat.tsx @@ -55,7 +55,7 @@ function useStickToBottom(scrollRef: RefObject<HTMLDivElement | null>, stickRef: } export function Chat() { - const { ready, connectionEpoch, providers, capabilities, providerOptions, session, routing, blocks, options, actions, commands, filesByCwd, fileDiffs, ctrlJ, forkPending, handoffPending, reply, stop, setOption, fork, handoff, compose, requestProviderOptions, requestProviderCommands, requestFiles, requestFileDiff } = useCtx(); + const { ready, connectionEpoch, providers, capabilities, providerOptions, session, routing, blocks, options, actions, commands, filesByCwd, fileDiffs, ctrlJ, forkPending, handoffPending, reply, stop, focusTerminal, setOption, fork, handoff, compose, requestProviderOptions, requestProviderCommands, requestFiles, requestFileDiff } = useCtx(); const [text, setText] = useState(""); const [openOptionId, setOpenOptionId] = useState<string | null>(null); const [helpOpen, setHelpOpen] = useState(false); @@ -128,6 +128,15 @@ export function Chat() { compose(); }; + const chatActions = ( + <div className="chat-actions"> + {running ? <button id="stop-btn" type="button" onClick={stop}>Stop</button> : null} + <button className="send" type="button" aria-label="Send" disabled={!text.trim()} onClick={submit}> + <ArrowUp /> + </button> + </div> + ); + return ( <section id="chat-view"> <div id="messages" ref={scrollRef} onScroll={onScroll}> @@ -143,20 +152,18 @@ export function Chat() { onFileDiff={(path) => { if (session) requestFileDiff(session.id, path); }} /> </div> - {transcriptView ? ( - <div id="chat-input-row"> - <div className="transcript-notice" role="status"> - <span className={running ? "transcript-dot running" : "transcript-dot"} aria-hidden="true" /> - <span>{agentChatText(running ? "transcriptViewRunning" : "transcriptViewIdle")}</span> - </div> - </div> - ) : ( <div id="chat-input-row"> {routing?.phase === "handoff" ? ( <div className="routing-notice" role="status">{agentChatText("continuedNewChat")}</div> ) : routing?.phase === "rerouted" ? ( <div className="routing-notice" role="status">{agentChatText("movedServingRoute")}</div> ) : null} + {transcriptView && session?.attention ? ( + <div className="terminal-attention" role="status"> + <span className="terminal-attention-text">{session.attention}</span> + <button className="terminal-attention-btn" type="button" onClick={focusTerminal}>{agentChatText("answerInTerminal")}</button> + </div> + ) : null} <div id="chat-card"> <div className="input-wrap chat-text-wrap"> <textarea @@ -177,30 +184,34 @@ export function Chat() { /> {commandMenu.menu} </div> - <StatusRow - provider={session?.provider ?? "agent"} - providers={providers} - allProviderOptions={allProviderOptions} - loadingProviderIds={loadingProviderIds} - onProviderModelChange={switchHarnessModel} - cwd={session?.cwd ?? ""} - options={resolvedOptions} - onChange={setOption} - openOptionId={openOptionId} - setOpenOptionId={setOpenOptionId} - running={running} - trailing={( - <div className="chat-actions"> - {running ? <button id="stop-btn" type="button" onClick={stop}>Stop</button> : null} - <button className="send" type="button" aria-label="Send" disabled={!text.trim()} onClick={submit}> - <ArrowUp /> - </button> - </div> - )} - /> + {transcriptView ? ( + // Terminal chat views drive the agent in the terminal: no model or + // option controls, just where the message goes and send/stop. + <div className="transcript-composer-row"> + <span className="transcript-hint"> + <span className={running ? "transcript-dot running" : "transcript-dot"} aria-hidden="true" /> + <span>{agentChatText(running ? "transcriptViewRunning" : "transcriptViewIdle")}</span> + </span> + {chatActions} + </div> + ) : ( + <StatusRow + provider={session?.provider ?? "agent"} + providers={providers} + allProviderOptions={allProviderOptions} + loadingProviderIds={loadingProviderIds} + onProviderModelChange={switchHarnessModel} + cwd={session?.cwd ?? ""} + options={resolvedOptions} + onChange={setOption} + openOptionId={openOptionId} + setOpenOptionId={setOpenOptionId} + running={running} + trailing={chatActions} + /> + )} </div> </div> - )} {helpOpen ? <ShortcutOverlay provider={session?.provider ?? "agent"} options={resolvedOptions} running={running} ctrlJ={ctrlJ} onClose={() => setHelpOpen(false)} /> : null} </section> ); diff --git a/agent-chat/src/components/Transcript.tsx b/agent-chat/src/components/Transcript.tsx index 631bacff7390..670d9b6751cb 100644 --- a/agent-chat/src/components/Transcript.tsx +++ b/agent-chat/src/components/Transcript.tsx @@ -870,7 +870,13 @@ function TurnGroupView({ {group.user ? <div className="msg user"><div className="body selectable">{group.user.text}</div></div> : null} {live ? group.activity.map((block, i) => ( - <ActivityBlock key={i} block={block} fileDiffs={fileDiffs} onFileDiff={onFileDiff} thinkingDefaultOpen={thinkingDefaultOpen} /> + block.kind === "thinking" || block.kind === "assistant" + ? ( + <div className="turn-live-activity" key={i}> + <ActivityBlock block={block} fileDiffs={fileDiffs} onFileDiff={onFileDiff} thinkingDefaultOpen={thinkingDefaultOpen} /> + </div> + ) + : <ActivityBlock key={i} block={block} fileDiffs={fileDiffs} onFileDiff={onFileDiff} thinkingDefaultOpen={thinkingDefaultOpen} /> )) : ( <TurnActivity diff --git a/agent-chat/src/i18n.ts b/agent-chat/src/i18n.ts index 44a66867a2b6..5d0db3479f43 100644 --- a/agent-chat/src/i18n.ts +++ b/agent-chat/src/i18n.ts @@ -28,7 +28,8 @@ export type AgentChatTextKey = | "continueNewChat" | "continueElsewhere" | "transcriptViewRunning" - | "transcriptViewIdle"; + | "transcriptViewIdle" + | "answerInTerminal"; const COPY: Record<AgentChatLocale, Record<AgentChatTextKey, string>> = { en: { @@ -36,160 +37,180 @@ const COPY: Record<AgentChatLocale, Record<AgentChatTextKey, string>> = { movedServingRoute: "Moved to another serving route. Conversation context was preserved.", continueNewChat: "Continue in new chat", continueElsewhere: "Continue elsewhere", - transcriptViewRunning: "Agent is working in the terminal. Switch to the terminal to reply or approve.", - transcriptViewIdle: "Mirroring the terminal session. Switch to the terminal to reply, approve, or run commands.", + transcriptViewRunning: "Working in the terminal · Esc to interrupt", + transcriptViewIdle: "Sends to the agent in this terminal", + answerInTerminal: "Answer in terminal", }, ja: { continuedNewChat: "新しいチャットで続行しました。以前のコンテキストはリンクされています。", movedServingRoute: "別の配信ルートに移動しました。会話のコンテキストは保持されています。", continueNewChat: "新しいチャットで続行", continueElsewhere: "別の場所で続行", - transcriptViewRunning: "エージェントがターミナルで作業中です。返信や承認はターミナルで行ってください。", - transcriptViewIdle: "ターミナルのセッションを表示しています。返信、承認、コマンド実行はターミナルで行ってください。", + transcriptViewRunning: "ターミナルで作業中 · Esc で中断", + transcriptViewIdle: "このターミナルのエージェントに送信します", + answerInTerminal: "ターミナルで回答", }, "zh-CN": { continuedNewChat: "已在新聊天中继续。之前的上下文已关联。", movedServingRoute: "已切换到其他服务路由。对话上下文已保留。", continueNewChat: "在新聊天中继续", continueElsewhere: "在其他位置继续", - transcriptViewRunning: "代理正在终端中工作。请切换到终端进行回复或批准。", - transcriptViewIdle: "正在镜像终端会话。请切换到终端进行回复、批准或运行命令。", + transcriptViewRunning: "正在终端中工作 · 按 Esc 中断", + transcriptViewIdle: "发送给此终端中的代理", + answerInTerminal: "在终端中回答", }, "zh-TW": { continuedNewChat: "已在新聊天中繼續。先前的內容已連結。", movedServingRoute: "已切換至其他服務路由。對話內容已保留。", continueNewChat: "在新聊天中繼續", continueElsewhere: "在其他位置繼續", - transcriptViewRunning: "代理程式正在終端機中工作。請切換到終端機回覆或核准。", - transcriptViewIdle: "正在鏡像終端機工作階段。請切換到終端機回覆、核准或執行指令。", + transcriptViewRunning: "正在終端機中工作 · 按 Esc 中斷", + transcriptViewIdle: "傳送給此終端機中的代理程式", + answerInTerminal: "在終端機中回答", }, ko: { continuedNewChat: "새 채팅에서 계속합니다. 이전 컨텍스트가 연결되어 있습니다.", movedServingRoute: "다른 서비스 경로로 이동했습니다. 대화 컨텍스트는 유지되었습니다.", continueNewChat: "새 채팅에서 계속", continueElsewhere: "다른 곳에서 계속", - transcriptViewRunning: "에이전트가 터미널에서 작업 중입니다. 답장하거나 승인하려면 터미널로 전환하세요.", - transcriptViewIdle: "터미널 세션을 미러링하고 있습니다. 답장, 승인, 명령 실행은 터미널에서 하세요.", + transcriptViewRunning: "터미널에서 작업 중 · Esc로 중단", + transcriptViewIdle: "이 터미널의 에이전트에게 보냅니다", + answerInTerminal: "터미널에서 답변", }, de: { continuedNewChat: "In einem neuen Chat fortgesetzt. Der vorherige Kontext ist verknüpft.", movedServingRoute: "Auf eine andere Bereitstellungsroute gewechselt. Der Gesprächskontext wurde beibehalten.", continueNewChat: "In neuem Chat fortfahren", continueElsewhere: "Anderswo fortfahren", - transcriptViewRunning: "Der Agent arbeitet im Terminal. Zum Antworten oder Freigeben ins Terminal wechseln.", - transcriptViewIdle: "Spiegelt die Terminalsitzung. Zum Antworten, Freigeben oder für Befehle ins Terminal wechseln.", + transcriptViewRunning: "Arbeitet im Terminal · Esc zum Unterbrechen", + transcriptViewIdle: "Wird an den Agenten in diesem Terminal gesendet", + answerInTerminal: "Im Terminal antworten", }, es: { continuedNewChat: "Se continuó en un chat nuevo. El contexto anterior está vinculado.", movedServingRoute: "Se cambió a otra ruta de servicio. Se conservó el contexto de la conversación.", continueNewChat: "Continuar en un chat nuevo", continueElsewhere: "Continuar en otro lugar", - transcriptViewRunning: "El agente está trabajando en la terminal. Cambia a la terminal para responder o aprobar.", - transcriptViewIdle: "Reflejando la sesión de la terminal. Cambia a la terminal para responder, aprobar o ejecutar comandos.", + transcriptViewRunning: "Trabajando en la terminal · Esc para interrumpir", + transcriptViewIdle: "Se envía al agente de esta terminal", + answerInTerminal: "Responder en la terminal", }, fr: { continuedNewChat: "La conversation continue dans un nouveau chat. Le contexte précédent est lié.", movedServingRoute: "Passage à une autre route de service. Le contexte de la conversation a été conservé.", continueNewChat: "Continuer dans un nouveau chat", continueElsewhere: "Continuer ailleurs", - transcriptViewRunning: "L’agent travaille dans le terminal. Passez au terminal pour répondre ou approuver.", - transcriptViewIdle: "Miroir de la session du terminal. Passez au terminal pour répondre, approuver ou lancer des commandes.", + transcriptViewRunning: "Travaille dans le terminal · Échap pour interrompre", + transcriptViewIdle: "Envoyé à l’agent de ce terminal", + answerInTerminal: "Répondre dans le terminal", }, it: { continuedNewChat: "Continuazione in una nuova chat. Il contesto precedente è collegato.", movedServingRoute: "Passaggio a un altro percorso di servizio. Il contesto della conversazione è stato mantenuto.", continueNewChat: "Continua in una nuova chat", continueElsewhere: "Continua altrove", - transcriptViewRunning: "L’agente sta lavorando nel terminale. Passa al terminale per rispondere o approvare.", - transcriptViewIdle: "Rispecchia la sessione del terminale. Passa al terminale per rispondere, approvare o eseguire comandi.", + transcriptViewRunning: "Al lavoro nel terminale · Esc per interrompere", + transcriptViewIdle: "Inviato all’agente di questo terminale", + answerInTerminal: "Rispondi nel terminale", }, da: { continuedNewChat: "Fortsat i en ny chat. Den tidligere kontekst er knyttet til.", movedServingRoute: "Flyttet til en anden serveringsrute. Samtalens kontekst blev bevaret.", continueNewChat: "Fortsæt i ny chat", continueElsewhere: "Fortsæt et andet sted", - transcriptViewRunning: "Agenten arbejder i terminalen. Skift til terminalen for at svare eller godkende.", - transcriptViewIdle: "Spejler terminalsessionen. Skift til terminalen for at svare, godkende eller køre kommandoer.", + transcriptViewRunning: "Arbejder i terminalen · Esc for at afbryde", + transcriptViewIdle: "Sendes til agenten i denne terminal", + answerInTerminal: "Svar i terminalen", }, pl: { continuedNewChat: "Kontynuowano w nowym czacie. Poprzedni kontekst jest połączony.", movedServingRoute: "Przeniesiono na inną trasę obsługi. Kontekst rozmowy został zachowany.", continueNewChat: "Kontynuuj w nowym czacie", continueElsewhere: "Kontynuuj gdzie indziej", - transcriptViewRunning: "Agent pracuje w terminalu. Przełącz się do terminala, aby odpowiedzieć lub zatwierdzić.", - transcriptViewIdle: "Odzwierciedla sesję terminala. Przełącz się do terminala, aby odpowiedzieć, zatwierdzić lub uruchomić polecenia.", + transcriptViewRunning: "Pracuje w terminalu · Esc, aby przerwać", + transcriptViewIdle: "Wysyłane do agenta w tym terminalu", + answerInTerminal: "Odpowiedz w terminalu", }, ru: { continuedNewChat: "Продолжено в новом чате. Предыдущий контекст связан.", movedServingRoute: "Переключено на другой маршрут обслуживания. Контекст разговора сохранён.", continueNewChat: "Продолжить в новом чате", continueElsewhere: "Продолжить в другом месте", - transcriptViewRunning: "Агент работает в терминале. Перейдите в терминал, чтобы ответить или подтвердить.", - transcriptViewIdle: "Отображается сеанс терминала. Перейдите в терминал, чтобы ответить, подтвердить или выполнить команды.", + transcriptViewRunning: "Работает в терминале · Esc для прерывания", + transcriptViewIdle: "Отправляется агенту в этом терминале", + answerInTerminal: "Ответить в терминале", }, bs: { continuedNewChat: "Nastavljeno u novom chatu. Prethodni kontekst je povezan.", movedServingRoute: "Prebačeno na drugu servisnu rutu. Kontekst razgovora je sačuvan.", continueNewChat: "Nastavi u novom chatu", continueElsewhere: "Nastavi drugdje", - transcriptViewRunning: "Agent radi u terminalu. Pređite u terminal da odgovorite ili odobrite.", - transcriptViewIdle: "Prikazuje se sesija terminala. Pređite u terminal da odgovorite, odobrite ili pokrenete komande.", + transcriptViewRunning: "Radi u terminalu · Esc za prekid", + transcriptViewIdle: "Šalje se agentu u ovom terminalu", + answerInTerminal: "Odgovori u terminalu", }, ar: { continuedNewChat: "تمت المتابعة في محادثة جديدة. السياق السابق مرتبط.", movedServingRoute: "تم الانتقال إلى مسار خدمة آخر. تم الاحتفاظ بسياق المحادثة.", continueNewChat: "المتابعة في محادثة جديدة", continueElsewhere: "المتابعة في مكان آخر", - transcriptViewRunning: "الوكيل يعمل في الطرفية. انتقل إلى الطرفية للرد أو الموافقة.", - transcriptViewIdle: "يتم عرض جلسة الطرفية. انتقل إلى الطرفية للرد أو الموافقة أو تشغيل الأوامر.", + transcriptViewRunning: "يعمل في الطرفية · Esc للمقاطعة", + transcriptViewIdle: "يُرسل إلى الوكيل في هذه الطرفية", + answerInTerminal: "أجب في الطرفية", }, no: { continuedNewChat: "Fortsatt i en ny chat. Tidligere kontekst er koblet til.", movedServingRoute: "Flyttet til en annen serveringsrute. Samtalekonteksten ble bevart.", continueNewChat: "Fortsett i ny chat", continueElsewhere: "Fortsett et annet sted", - transcriptViewRunning: "Agenten jobber i terminalen. Bytt til terminalen for å svare eller godkjenne.", - transcriptViewIdle: "Speiler terminaløkten. Bytt til terminalen for å svare, godkjenne eller kjøre kommandoer.", + transcriptViewRunning: "Jobber i terminalen · Esc for å avbryte", + transcriptViewIdle: "Sendes til agenten i denne terminalen", + answerInTerminal: "Svar i terminalen", }, "pt-BR": { continuedNewChat: "Continuado em um novo chat. O contexto anterior está vinculado.", movedServingRoute: "Movido para outra rota de atendimento. O contexto da conversa foi preservado.", continueNewChat: "Continuar em um novo chat", continueElsewhere: "Continuar em outro lugar", - transcriptViewRunning: "O agente está trabalhando no terminal. Mude para o terminal para responder ou aprovar.", - transcriptViewIdle: "Espelhando a sessão do terminal. Mude para o terminal para responder, aprovar ou executar comandos.", + transcriptViewRunning: "Trabalhando no terminal · Esc para interromper", + transcriptViewIdle: "Enviado ao agente deste terminal", + answerInTerminal: "Responder no terminal", }, th: { continuedNewChat: "ดำเนินการต่อในแชทใหม่แล้ว โดยเชื่อมโยงบริบทก่อนหน้าไว้", movedServingRoute: "ย้ายไปยังเส้นทางการให้บริการอื่นแล้ว โดยยังคงบริบทการสนทนาไว้", continueNewChat: "ดำเนินการต่อในแชทใหม่", continueElsewhere: "ดำเนินการต่อที่อื่น", - transcriptViewRunning: "เอเจนต์กำลังทำงานในเทอร์มินัล สลับไปที่เทอร์มินัลเพื่อตอบกลับหรืออนุมัติ", - transcriptViewIdle: "กำลังแสดงเซสชันเทอร์มินัล สลับไปที่เทอร์มินัลเพื่อตอบกลับ อนุมัติ หรือเรียกใช้คำสั่ง", + transcriptViewRunning: "กำลังทำงานในเทอร์มินัล · กด Esc เพื่อขัดจังหวะ", + transcriptViewIdle: "ส่งถึงเอเจนต์ในเทอร์มินัลนี้", + answerInTerminal: "ตอบในเทอร์มินัล", }, tr: { continuedNewChat: "Yeni bir sohbette devam edildi. Önceki bağlam bağlantılı.", movedServingRoute: "Başka bir hizmet rotasına geçildi. Konuşma bağlamı korundu.", continueNewChat: "Yeni sohbette devam et", continueElsewhere: "Başka yerde devam et", - transcriptViewRunning: "Ajan terminalde çalışıyor. Yanıtlamak veya onaylamak için terminale geçin.", - transcriptViewIdle: "Terminal oturumu yansıtılıyor. Yanıtlamak, onaylamak veya komut çalıştırmak için terminale geçin.", + transcriptViewRunning: "Terminalde çalışıyor · Kesmek için Esc", + transcriptViewIdle: "Bu terminaldeki ajana gönderilir", + answerInTerminal: "Terminalde yanıtla", }, km: { continuedNewChat: "បានបន្តនៅក្នុងការជជែកថ្មី។ បរិបទមុនត្រូវបានភ្ជាប់។", movedServingRoute: "បានប្តូរទៅផ្លូវបម្រើផ្សេង។ បរិបទនៃការសន្ទនាត្រូវបានរក្សាទុក។", continueNewChat: "បន្តក្នុងការជជែកថ្មី", continueElsewhere: "បន្តនៅកន្លែងផ្សេង", - transcriptViewRunning: "ភ្នាក់ងារកំពុងធ្វើការនៅក្នុងទែមីណាល់។ ប្ដូរទៅទែមីណាល់ដើម្បីឆ្លើយតប ឬអនុម័ត។", - transcriptViewIdle: "កំពុងបង្ហាញវគ្គទែមីណាល់។ ប្ដូរទៅទែមីណាល់ដើម្បីឆ្លើយតប អនុម័ត ឬដំណើរការពាក្យបញ្ជា។", + transcriptViewRunning: "កំពុងធ្វើការនៅក្នុងទែមីណាល់ · Esc ដើម្បីរំខាន", + transcriptViewIdle: "ផ្ញើទៅភ្នាក់ងារនៅក្នុងទែមីណាល់នេះ", + answerInTerminal: "ឆ្លើយនៅក្នុងទែមីណាល់", }, uk: { continuedNewChat: "Продовжено в новому чаті. Попередній контекст пов’язано.", movedServingRoute: "Перемкнено на інший маршрут обслуговування. Контекст розмови збережено.", continueNewChat: "Продовжити в новому чаті", continueElsewhere: "Продовжити в іншому місці", - transcriptViewRunning: "Агент працює в терміналі. Перейдіть у термінал, щоб відповісти або підтвердити.", - transcriptViewIdle: "Відображається сеанс термінала. Перейдіть у термінал, щоб відповісти, підтвердити або виконати команди.", + transcriptViewRunning: "Працює в терміналі · Esc, щоб перервати", + transcriptViewIdle: "Надсилається агентові в цьому терміналі", + answerInTerminal: "Відповісти в терміналі", }, }; diff --git a/agent-chat/src/session.ts b/agent-chat/src/session.ts index 812571689d34..89598ea55615 100644 --- a/agent-chat/src/session.ts +++ b/agent-chat/src/session.ts @@ -155,8 +155,10 @@ export interface SessionSummary { parentSessionId?: string; parentConversationId?: string; startRequestId?: string; - /** "transcript": a read-only view of an agent running in a cmux terminal. */ + /** "transcript": a chat view of an agent running in a cmux terminal. */ mode?: "transcript"; + /** What that agent is waiting on in the terminal (permission, question). */ + attention?: string | null; } export type CtrlJMode = "newline" | "menu"; @@ -241,6 +243,8 @@ export interface SessionState { compose(): void; reply(text: string): void; stop(): void; + /** Focuses the terminal pane behind a terminal chat view. */ + focusTerminal(): void; setOption(id: string, value: OptionValue): void; fork(): void; handoff(): void; @@ -351,6 +355,10 @@ export function useSession(): SessionState { } | null>(null); const pendingStartTimeoutRef = useRef<number | null>(null); const optimisticUsersRef = useRef<string[]>([]); + const sessionModeRef = useRef<SessionSummary["mode"]>(routedToTranscript ? "transcript" : undefined); + useEffect(() => { + if (session) sessionModeRef.current = session.mode; + }, [session]); const latestCwdRequestRef = useRef<CwdHarnessRequest | null>(null); const closeHandoffWindow = useCallback(() => { @@ -506,6 +514,11 @@ export function useSession(): SessionState { setSession((s) => (s ? { ...s, status: msg.status } : s)); } break; + case "session-attention": + if (msg.sessionId === sessionIdRef.current) { + setSession((s) => (s ? { ...s, attention: typeof msg.attention === "string" ? msg.attention : null } : s)); + } + break; case "session-title": if (msg.sessionId === sessionIdRef.current && typeof msg.title === "string") { document.title = msg.title || "cmux agent"; @@ -691,9 +704,18 @@ export function useSession(): SessionState { if (sessionIdRef.current) { if (sendRaw({ op: "send", sessionId: sessionIdRef.current, requestId: newClientRequestId("turn"), prompt: text })) { setSession((s) => (s ? { ...s, status: "running" } : s)); + // A terminal view's prompt only reaches the event log when the agent's + // transcript records it; show it now and drop that echo when it lands. + if (sessionModeRef.current === "transcript") { + optimisticUsersRef.current.push(text); + setBlocks((bs) => [...closeStreaming(bs), { kind: "user", text }]); + } } } }, [sendRaw, start]); + const focusTerminal = useCallback(() => { + if (sessionIdRef.current) sendRaw({ op: "focus-terminal", sessionId: sessionIdRef.current }); + }, [sendRaw]); const stop = useCallback(() => { if (sessionIdRef.current) sendRaw({ op: "stop", sessionId: sessionIdRef.current }); }, [sendRaw]); @@ -771,6 +793,7 @@ export function useSession(): SessionState { compose, reply, stop, + focusTerminal, setOption, fork, handoff, diff --git a/agent-chat/src/turns.ts b/agent-chat/src/turns.ts index 196589205c7f..9adcc4914607 100644 --- a/agent-chat/src/turns.ts +++ b/agent-chat/src/turns.ts @@ -70,6 +70,19 @@ function sentenceCase(text: string) { return text ? text[0].toUpperCase() + text.slice(1) : text; } +// Structured tools (Claude Code's Read/Edit/Grep, Codex's apply_patch, MCP +// tools) are not shell commands. Everything else is a command: Claude's Bash, +// Codex's exec_command, and adapters that name the tool after the program. +const STRUCTURED_TOOLS = new Set([ + "Read", "Write", "Edit", "MultiEdit", "NotebookEdit", "NotebookRead", "Grep", "Glob", "LS", + "WebFetch", "WebSearch", "Task", "Agent", "TodoWrite", "TodoRead", "Skill", "ToolSearch", "ExitPlanMode", + "apply_patch", "update_plan", "view_image", "web_search", "spawn_agent", +]); + +export function isShellCommandTool(name: string): boolean { + return !STRUCTURED_TOOLS.has(name) && !name.startsWith("mcp__"); +} + export function summarizeTurnActivity(blocks: Block[]): string { let edited = 0; let read = 0; @@ -77,6 +90,7 @@ export function summarizeTurnActivity(blocks: Block[]): string { let searched = false; let listed = false; let other = 0; + let tools = 0; for (const block of blocks) { if (block.kind === "assistant") continue; if (block.kind === "files") { @@ -87,12 +101,14 @@ export function summarizeTurnActivity(blocks: Block[]): string { other++; continue; } - commands++; + const shell = isShellCommandTool(block.name); + if (shell) commands++; const text = `${block.name} ${block.detail ?? ""}`.toLowerCase(); if (/\b(read|cat|sed|nl|open)\b/.test(text)) read++; else if (/\b(rg|grep|search)\b/.test(text)) searched = true; else if (/\b(ls|find|list)\b/.test(text)) listed = true; else if (/\b(edit|write|apply_patch|patch)\b/.test(text)) edited++; + else if (!shell) tools++; } const parts: string[] = []; if (edited) parts.push(`edited ${plural(edited, "file")}`); @@ -100,7 +116,8 @@ export function summarizeTurnActivity(blocks: Block[]): string { if (searched) parts.push("searched code"); if (listed) parts.push("listed files"); if (commands) parts.push(`ran ${plural(commands, "command")}`); - if (!commands && other) parts.push(`processed ${plural(other, "event")}`); + if (tools) parts.push(`used ${plural(tools, "tool")}`); + if (!commands && !tools && other) parts.push(`processed ${plural(other, "event")}`); return sentenceCase(joinSentence(parts)); } diff --git a/agent-chat/test/i18n.test.ts b/agent-chat/test/i18n.test.ts index ada885c02f0a..7ca494ddd613 100644 --- a/agent-chat/test/i18n.test.ts +++ b/agent-chat/test/i18n.test.ts @@ -21,6 +21,7 @@ const keys: AgentChatTextKey[] = [ "continueElsewhere", "transcriptViewRunning", "transcriptViewIdle", + "answerInTerminal", ]; const english = agentChatCopyForLocale("en"); diff --git a/agent-chat/test/transcript.test.ts b/agent-chat/test/transcript.test.ts index 5d3d3c993ae8..136ffce6c72e 100644 --- a/agent-chat/test/transcript.test.ts +++ b/agent-chat/test/transcript.test.ts @@ -2,10 +2,10 @@ import { afterEach, describe, expect, test } from "bun:test"; import { appendFileSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { attachTranscript, parseTranscriptText, transcriptAdapter, TranscriptTail, transcriptLooksRunning, toolDetail } from "../adapters/transcript"; +import { attachTranscript, focusTranscriptTerminal, parseTranscriptText, setTranscriptRpcForTest, transcriptAdapter, TranscriptTail, transcriptLooksRunning, toolDetail } from "../adapters/transcript"; import { utimesSync } from "node:fs"; import type { SessionCtx, SessionStatus } from "../types"; -import { claudeProjectSlug, resolveSessionTranscript, resolveSurfaceTranscript } from "../transcript-sources"; +import { claudeProjectSlug, resolveSessionTranscript, resolveSurfaceTranscript, transcriptAttention } from "../transcript-sources"; import type { AgentEvent } from "../types"; const jsonl = (...rows: unknown[]) => rows.map((r) => JSON.stringify(r)).join("\n") + "\n"; @@ -201,6 +201,41 @@ test("an old transcript opens idle even when its last turn has no end", async () expect(statuses).not.toContain("running"); }); +describe("terminal delivery", () => { + function fakeSession(target?: { agentSessionId: string; surfaceId?: string }) { + return { + events: [] as AgentEvent[], + internal: { transcriptTarget: target } as Record<string, unknown>, + emit(evt: AgentEvent) { this.events.push(evt); }, + setStatus() {}, + } as unknown as SessionCtx; + } + afterEach(() => setTranscriptRpcForTest(null)); + + test("send types the prompt into the terminal's agent session and echoes nothing itself", async () => { + const calls: [string, Record<string, unknown>][] = []; + setTranscriptRpcForTest(async (method, params) => { calls.push([method, params]); return { ok: true }; }); + const sess = fakeSession({ agentSessionId: "claude-1234", surfaceId: "SURF" }); + await transcriptAdapter.send(sess, "ship it"); + transcriptAdapter.stop(sess); + await focusTranscriptTerminal(sess); + expect(calls).toEqual([ + ["mobile.chat.send", { session_id: "claude-1234", text: "ship it" }], + ["mobile.chat.interrupt", { session_id: "claude-1234" }], + ["surface.focus", { surface_id: "SURF" }], + ]); + expect(sess.events).toEqual([]); + }); + + test("delivery failures surface as errors in the view", async () => { + setTranscriptRpcForTest(async () => ({ ok: false, error: "not_found" })); + const sess = fakeSession({ agentSessionId: "claude-1234" }); + await transcriptAdapter.send(sess, "hi"); + expect(sess.events).toEqual([{ kind: "error", message: "Couldn't send to the terminal: not_found" }]); + expect((await focusTranscriptTerminal(sess)).ok).toBe(false); + }); +}); + describe("transcript sources", () => { function fixture() { const home = tempDir(); @@ -222,7 +257,7 @@ describe("transcript sources", () => { "claude-active-0001": { surfaceId: "AAAAAAAA-0000-0000-0000-000000000001", transcriptPath: recorded, updatedAt: 10 }, "claude-newer-0001": { surfaceId: "AAAAAAAA-0000-0000-0000-000000000001", transcriptPath: recorded, updatedAt: 99 }, "claude-fallback-0001": { surfaceId: "BBBBBBBB-0000-0000-0000-000000000002", cwd: "/work/app.v2", updatedAt: 5 }, - "claude-missing-0001": { surfaceId: "DDDDDDDD-0000-0000-0000-000000000004", transcriptPath: join(home, "gone.jsonl"), updatedAt: 5 }, + "claude-missing-0001": { surfaceId: "DDDDDDDD-0000-0000-0000-000000000004", transcriptPath: join(home, "gone.jsonl"), updatedAt: 5, runtimeStatus: "needsInput", lastBody: "Claude needs your permission to use Bash" }, "../escape": { surfaceId: "EEEEEEEE-0000-0000-0000-000000000005", transcriptPath: recorded, updatedAt: 5 }, }, })); @@ -247,6 +282,12 @@ describe("transcript sources", () => { expect(resolveSessionTranscript("codex-session-0001", env)?.path).toBe(rollout); }); + test("reports what the agent waits on in the terminal", () => { + const { env } = fixture(); + expect(transcriptAttention("claude", "claude-missing-0001", env)).toBe("Claude needs your permission to use Bash"); + expect(transcriptAttention("claude", "claude-active-0001", env)).toBeNull(); + }); + test("returns nothing for unknown surfaces, missing files, and invalid ids", () => { const { env } = fixture(); expect(resolveSurfaceTranscript("FFFFFFFF-0000-0000-0000-000000000006", env)).toBeNull(); diff --git a/agent-chat/test/turns.test.ts b/agent-chat/test/turns.test.ts index 8215ca31ddfc..3068e52a690f 100644 --- a/agent-chat/test/turns.test.ts +++ b/agent-chat/test/turns.test.ts @@ -22,6 +22,22 @@ if (summary !== "Edited 2 files, read 1 file, searched code, listed files, and r if (/Read 1 File|Searched Code|Listed Files|Ran 3 Commands/.test(summary)) { throw new Error(`summary regressed to title case: ${summary}`); } +// Claude Code turn: only Bash calls are shell commands; structured tools are +// reported by what they did (regression: this read "ran 6 commands"). +const claudeTurn: Block[] = [ + { kind: "tool", toolId: "g", name: "Grep", detail: "func runExport", status: "ok" }, + { kind: "tool", toolId: "r", name: "Read", detail: "/work/Export.swift", status: "ok" }, + { kind: "tool", toolId: "e1", name: "Edit", detail: "/work/Export.swift", status: "ok" }, + { kind: "tool", toolId: "b1", name: "Bash", detail: "swift test --filter ExportTests", status: "ok" }, + { kind: "tool", toolId: "e2", name: "Edit", detail: "/work/Row.swift", status: "ok" }, + { kind: "tool", toolId: "b2", name: "Bash", detail: "swift test --filter ExportTests", status: "ok" }, + { kind: "tool", toolId: "w", name: "WebFetch", detail: "https://example.com", status: "ok" }, +]; +const claudeSummary = summarizeTurnActivity(claudeTurn); +if (claudeSummary !== "Edited 2 files, read 1 file, searched code, ran 2 commands, and used 1 tool") { + throw new Error(`unexpected Claude summary: ${claudeSummary}`); +} + const labels = activity.map((block) => activityRowLabel(block)); if (labels.join("|") !== "Read AGENTS.md|Searched RepositoryPicker|Listed Sources|Edited 2 files") { throw new Error(`unexpected activity labels: ${labels.join("|")}`); diff --git a/agent-chat/transcript-sources.ts b/agent-chat/transcript-sources.ts index aba07da025e1..74b8e6f8689b 100644 --- a/agent-chat/transcript-sources.ts +++ b/agent-chat/transcript-sources.ts @@ -23,6 +23,10 @@ interface HookEntry { cwd?: string; transcriptPath?: string; updatedAt: number; + /** Hook runtime status: running, idle, needsInput, error. */ + runtimeStatus?: string; + /** Latest notification body, e.g. the permission the agent is asking for. */ + lastBody?: string; } const AGENTS: TranscriptAgent[] = ["claude", "codex"]; @@ -73,6 +77,8 @@ function readStore(env: Required<TranscriptSourceEnv>, agent: TranscriptAgent): cwd: typeof v.cwd === "string" && v.cwd ? v.cwd : undefined, transcriptPath: typeof v.transcriptPath === "string" && v.transcriptPath ? v.transcriptPath : undefined, updatedAt: typeof v.updatedAt === "number" ? v.updatedAt : 0, + runtimeStatus: typeof v.runtimeStatus === "string" ? v.runtimeStatus : undefined, + lastBody: typeof v.lastBody === "string" ? v.lastBody : undefined, }); } return { entries, activeBySurface }; @@ -199,3 +205,14 @@ export function resolveSessionTranscript(sessionId: string, env: TranscriptSourc misses.set(sessionId, now); return null; } + +/** + * What the agent is waiting on in its terminal (a permission prompt, a + * question, a picker), from the hook store, or null when it is not waiting. + */ +export function transcriptAttention(agent: TranscriptAgent, sessionId: string, env: TranscriptSourceEnv = {}): string | null { + if (!SESSION_ID.test(sessionId)) return null; + const entry = readStore(resolvedEnv(env), agent).entries.find((e) => e.sessionId === sessionId); + if (entry?.runtimeStatus !== "needsInput") return null; + return entry.lastBody?.trim() || "The agent is waiting for input in the terminal."; +} From d1cd8275c09801827210ef957e7efe92cf9bf1da Mon Sep 17 00:00:00 2001 From: Leo Li <cheerleaderleo@outlook.com> Date: Sun, 27 Sep 2026 13:30:22 -0400 Subject: [PATCH 07/11] agent-chat: label shell and structured tool rows by what they did Bash and exec_command rows show the command itself, structured tools that are not reads, searches, or edits read as "Used", Grep rows show the pattern, and live thinking lines up with the tool rows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --- agent-chat/adapters/transcript.ts | 2 +- agent-chat/public/app.css | 1 + agent-chat/src/turns.ts | 7 ++++++- agent-chat/test/turns.test.ts | 5 +++++ 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/agent-chat/adapters/transcript.ts b/agent-chat/adapters/transcript.ts index b9b6fb0680fe..dd93d8921a5d 100644 --- a/agent-chat/adapters/transcript.ts +++ b/agent-chat/adapters/transcript.ts @@ -58,7 +58,7 @@ export function parseTranscriptText(agent: TranscriptAgent, text: string): { eve } // Tool inputs worth showing as the one-line activity detail, in priority order. -const TOOL_DETAIL_KEYS = ["command", "cmd", "file_path", "path", "pattern", "url", "query", "description", "prompt", "skill"]; +const TOOL_DETAIL_KEYS = ["command", "cmd", "file_path", "pattern", "path", "url", "query", "description", "prompt", "skill"]; export function toolDetail(input: unknown): string { if (input == null) return ""; diff --git a/agent-chat/public/app.css b/agent-chat/public/app.css index d383b75fd991..1a80e824dcc5 100644 --- a/agent-chat/public/app.css +++ b/agent-chat/public/app.css @@ -613,6 +613,7 @@ details.thinking .t-body { white-space: pre-wrap; font-size: calc(var(--font-siz /* A running turn renders its activity directly in the turn, outside the collapsed summary; keep thinking and interim text in the content column. */ .turn-live-activity { max-width: 760px; margin: 0 auto 10px; padding: 0 24px; } +.turn-live-activity .turn-thinking-detail { padding-left: 0; padding-right: 0; } .turn-actions { max-width: 760px; margin: -8px auto 18px; padding: 0 24px; display: flex; align-items: center; gap: 6px; diff --git a/agent-chat/src/turns.ts b/agent-chat/src/turns.ts index 9adcc4914607..495588061914 100644 --- a/agent-chat/src/turns.ts +++ b/agent-chat/src/turns.ts @@ -79,6 +79,8 @@ const STRUCTURED_TOOLS = new Set([ "apply_patch", "update_plan", "view_image", "web_search", "spawn_agent", ]); +const SHELL_WRAPPER_TOOLS = new Set(["Bash", "bash", "shell", "exec_command", "local_shell"]); + export function isShellCommandTool(name: string): boolean { return !STRUCTURED_TOOLS.has(name) && !name.startsWith("mcp__"); } @@ -130,7 +132,10 @@ export function activityRowLabel(block: Block): string { if (/\b(read|cat|sed|nl|open)\b/.test(lower)) return `Read ${detail.trim() || name}`; if (/\b(ls|find|list)\b/.test(lower)) return `Listed ${detail.trim() || name}`; if (/\b(edit|write|apply_patch|patch)\b/.test(lower)) return `Edited ${detail.trim() || name}`; - return `Ran ${name}${detail}`; + // Shell wrappers (Claude's Bash, Codex's exec_command) read better as the + // command itself. + if (SHELL_WRAPPER_TOOLS.has(name) && block.detail) return `Ran ${block.detail}`; + return `${isShellCommandTool(name) ? "Ran" : "Used"} ${name}${detail}`; } if (block.kind === "files") return `Edited ${plural(block.files.length, "file")}`; if (block.kind === "thinking") return "Reasoned"; diff --git a/agent-chat/test/turns.test.ts b/agent-chat/test/turns.test.ts index 3068e52a690f..ac2bf534dae4 100644 --- a/agent-chat/test/turns.test.ts +++ b/agent-chat/test/turns.test.ts @@ -38,6 +38,11 @@ if (claudeSummary !== "Edited 2 files, read 1 file, searched code, ran 2 command throw new Error(`unexpected Claude summary: ${claudeSummary}`); } +const claudeLabels = claudeTurn.map((block) => activityRowLabel(block)).join("|"); +if (claudeLabels !== "Searched func runExport|Read /work/Export.swift|Edited /work/Export.swift|Ran swift test --filter ExportTests|Edited /work/Row.swift|Ran swift test --filter ExportTests|Used WebFetch https://example.com") { + throw new Error(`unexpected Claude activity labels: ${claudeLabels}`); +} + const labels = activity.map((block) => activityRowLabel(block)); if (labels.join("|") !== "Read AGENTS.md|Searched RepositoryPicker|Listed Sources|Edited 2 files") { throw new Error(`unexpected activity labels: ${labels.join("|")}`); From 29b7a832e0fadf9ac70c5a574e34e4bc3c00fa8c Mon Sep 17 00:00:00 2001 From: Leo Li <cheerleaderleo@outlook.com> Date: Sun, 27 Sep 2026 13:30:36 -0400 Subject: [PATCH 08/11] Localizable: re-apply the terminal chat view string on main's catalog Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --- Resources/Localizable.xcstrings | 9844 +++++++++++++++---------------- 1 file changed, 4922 insertions(+), 4922 deletions(-) diff --git a/Resources/Localizable.xcstrings b/Resources/Localizable.xcstrings index 242b946961ec..1f1ce0cb41cb 100644 --- a/Resources/Localizable.xcstrings +++ b/Resources/Localizable.xcstrings @@ -1,61 +1,120 @@ { "sourceLanguage": "en", "strings": { - "cli.session.usage": { + "cli.help.send": { "extractionState": "manual", "localizations": { - "en": { + "ar": { "stringUnit": { "state": "translated", - "value": "Usage: cmux session move <session-id> --to <ssh-destination|local> [options]\n\nMove a stopped Claude Code session to another machine and resume it there.\nExit the agent first: the move refuses while the session runs on either side.\n\nThe move carries:\n - the cwd's git checkout: HEAD (on the same branch when safe) plus modified,\n deleted and untracked non-ignored files; a worktree is added when the\n repository exists on the destination but the path does not\n - the transcript, its session directory, file history, and the project\n memory directory (merged both ways, newest wins, nothing deleted)\n\nIt then opens a workspace on the destination (`cmux ssh` for a host, a local\nworkspace for `local`) that resumes the session with its recorded launcher.\nWhen the destination home is not at the same absolute path, paths under the\nhome are mapped and the project is re-slugged.\n\nOptions:\n --to <destination> SSH destination (user@host or ssh_config alias), or local\n --from <destination> Where the session is now (default: where the last move put it, else local)\n --name <title> Workspace title (default: the first 8 characters of the id)\n --no-code Do not carry the git checkout\n --port <n> SSH port\n --identity <path> SSH identity file\n --ssh-option <opt> Extra SSH -o option (repeatable)\n --no-focus Open the workspace without switching to it\n\nExamples:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" + "value": "الاستخدام: cmux send [flags] [--] <text>\n\nيرسل نصًا إلى سطح طرفية كضغطات مفاتيح. تسلسلات الهروب: \\n و\\r ترسلان Enter، و\\t ترسل Tab.\n\nللنصوص الكبيرة (مثلًا أكثر من 4 كيلوبايت، أو عدة أسطر موجهة إلى وكيل)، استخدم --paste أو cmux paste. تضغط ضغطات المفاتيح Enter عند كل سطر جديد، مما قد يرسل كل سطر بمفرده.\n\nمع --paste، يمر النص دون تغيير عبر مسار اللصق نفسه الذي يستخدمه cmux paste وCmd+V: لا تُفسَّر تسلسلات الهروب مثل \\n، وتُستبدل أحرف التحكم مثل Esc وCtrl-C بمسافات. يجب أن يأتي --paste قبل النص.\n\nالخيارات:\n --workspace <id|ref|index> مساحة العمل المستهدفة (الافتراضي: $CMUX_WORKSPACE_ID)\n --surface <id|ref|index> السطح المستهدف (الافتراضي: $CMUX_SURFACE_ID)\n --window <id|ref|index> سياق النافذة لمراجع وفهارس مساحة العمل/السطح\n --paste يلصق النص بدلًا من كتابته\n\nمثال:\n cmux send \"echo hello\"\n cmux send --surface surface:2 \"ls -la\\n\"\n cmux send --paste --surface surface:2 \"$(cat notes.md)\"" } }, "de": { "stringUnit": { "state": "translated", - "value": "Verwendung: cmux session move <session-id> --to <ssh-destination|local> [options]\n\nVerschiebt eine beendete Claude Code-Sitzung auf eine andere Maschine und setzt sie dort fort.\nBeende zuerst den Agenten: Die Verschiebung wird abgelehnt, solange die Sitzung auf einer Seite läuft.\n\nDie Verschiebung überträgt:\n - den git-Checkout des cwd: HEAD (auf demselben Branch, wenn das sicher ist) sowie geänderte,\n gelöschte und nicht ignorierte, nicht verfolgte Dateien; ein Worktree wird hinzugefügt, wenn\n das Repository am Ziel existiert, der Pfad aber nicht\n - das Transkript, sein Sitzungsverzeichnis, den Dateiverlauf und das Projektgedächtnis\n (in beide Richtungen zusammengeführt, das Neueste gewinnt, nichts wird gelöscht)\n\nDanach öffnet sie am Ziel einen Workspace (`cmux ssh` für einen Host, einen lokalen Workspace\nfür `local`), der die Sitzung mit ihrem aufgezeichneten Launcher fortsetzt. Liegt das Home-\nVerzeichnis am Ziel nicht unter demselben absoluten Pfad, werden Pfade darunter umgeschrieben\nund das Projekt erhält einen neuen Slug.\n\nOptionen:\n --to <destination> SSH-Ziel (user@host oder ssh_config-Alias) oder local\n --from <destination> Wo die Sitzung jetzt ist (Standard: wohin die letzte Verschiebung sie gebracht hat, sonst local)\n --name <title> Workspace-Titel (Standard: die ersten 8 Zeichen der ID)\n --no-code Den git-Checkout nicht übertragen\n --port <n> SSH-Port\n --identity <path> SSH-Identitätsdatei\n --ssh-option <opt> Zusätzliche SSH-Option -o (wiederholbar)\n --no-focus Workspace öffnen, ohne dorthin zu wechseln\n\nBeispiele:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" + "value": "Verwendung: cmux send [flags] [--] <text>\n\nSendet Text als Tastatureingaben an eine Terminal-Oberfläche. Escape-Sequenzen: \\n und \\r senden Enter, \\t sendet Tab.\n\nFür große Texte (zum Beispiel über 4 KB oder mehrere Zeilen an einen Agenten) --paste oder cmux paste verwenden. Tastatureingaben drücken bei jedem Zeilenumbruch Enter, wodurch jede Zeile einzeln abgesendet werden kann.\n\nMit --paste geht der Text unverändert über denselben Einfügepfad wie cmux paste und Cmd+V: Escape-Sequenzen wie \\n werden nicht interpretiert, und Steuerzeichen wie Esc und Strg-C werden durch Leerzeichen ersetzt. --paste muss vor dem Text stehen.\n\nFlags:\n --workspace <id|ref|index> Ziel-Workspace (Standard: $CMUX_WORKSPACE_ID)\n --surface <id|ref|index> Ziel-Oberfläche (Standard: $CMUX_SURFACE_ID)\n --window <id|ref|index> Fensterkontext für Workspace-/Oberflächen-Refs und -Indizes\n --paste Fügt den Text ein, statt ihn zu tippen\n\nBeispiel:\n cmux send \"echo hello\"\n cmux send --surface surface:2 \"ls -la\\n\"\n cmux send --paste --surface surface:2 \"$(cat notes.md)\"" + } + }, + "en": { + "stringUnit": { + "state": "translated", + "value": "Usage: cmux send [flags] [--] <text>\n\nSend text to a terminal surface as keystrokes. Escape sequences: \\n and \\r send Enter, \\t sends Tab.\n\nFor large text (for example over 4 KB, or several lines going to an agent), use --paste or cmux paste. Keystrokes press Enter at every newline, which can submit each line on its own.\n\nWith --paste, the text goes through the same paste path as cmux paste and Cmd+V, unchanged: escape sequences such as \\n are not interpreted, and control characters such as Esc and Ctrl-C are replaced with spaces. --paste must come before the text.\n\nFlags:\n --workspace <id|ref|index> Target workspace (default: $CMUX_WORKSPACE_ID)\n --surface <id|ref|index> Target surface (default: $CMUX_SURFACE_ID)\n --window <id|ref|index> Window context for workspace/surface refs and indexes\n --paste Paste the text instead of typing it\n\nExample:\n cmux send \"echo hello\"\n cmux send --surface surface:2 \"ls -la\\n\"\n cmux send --paste --surface surface:2 \"$(cat notes.md)\"" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Uso: cmux send [flags] [--] <text>\n\nEnvía texto a una superficie de terminal como pulsaciones de teclas. Secuencias de escape: \\n y \\r envían Intro, \\t envía Tab.\n\nPara textos grandes (por ejemplo, más de 4 KB o varias líneas dirigidas a un agente), usa --paste o cmux paste. Las pulsaciones presionan Intro en cada salto de línea, lo que puede enviar cada línea por separado.\n\nCon --paste, el texto pasa sin cambios por la misma ruta de pegado que cmux paste y Cmd+V: las secuencias de escape como \\n no se interpretan y los caracteres de control como Esc y Ctrl-C se reemplazan por espacios. --paste debe ir antes del texto.\n\nOpciones:\n --workspace <id|ref|index> Espacio de trabajo de destino (predeterminado: $CMUX_WORKSPACE_ID)\n --surface <id|ref|index> Superficie de destino (predeterminada: $CMUX_SURFACE_ID)\n --window <id|ref|index> Contexto de ventana para referencias e índices de espacio de trabajo/superficie\n --paste Pega el texto en lugar de escribirlo\n\nEjemplo:\n cmux send \"echo hello\"\n cmux send --surface surface:2 \"ls -la\\n\"\n cmux send --paste --surface surface:2 \"$(cat notes.md)\"" } }, "fr": { "stringUnit": { "state": "translated", - "value": "Utilisation: cmux session move <session-id> --to <ssh-destination|local> [options]\n\nDéplace une session Claude Code arrêtée vers une autre machine et la reprend là-bas.\nQuittez d'abord l'agent : le déplacement est refusé tant que la session s'exécute d'un côté ou de l'autre.\n\nLe déplacement transfère :\n - le checkout git du cwd : HEAD (sur la même branche quand c'est sûr) ainsi que les fichiers\n modifiés, supprimés et non suivis non ignorés ; un worktree est ajouté quand le dépôt existe\n sur la destination mais pas le chemin\n - la transcription, son répertoire de session, l'historique des fichiers et la mémoire du\n projet (fusionnée dans les deux sens, le plus récent l'emporte, rien n'est supprimé)\n\nIl ouvre ensuite un espace de travail sur la destination (`cmux ssh` pour un hôte, un espace\nlocal pour `local`) qui reprend la session avec son lanceur enregistré. Quand le dossier\npersonnel de destination n'a pas le même chemin absolu, les chemins qu'il contient sont\nconvertis et le projet reçoit un nouveau slug.\n\nOptions :\n --to <destination> Destination SSH (user@host ou alias ssh_config), ou local\n --from <destination> Où se trouve la session (par défaut : là où l'a mise le dernier déplacement, sinon local)\n --name <title> Titre de l'espace de travail (par défaut : les 8 premiers caractères de l'id)\n --no-code Ne pas transférer le checkout git\n --port <n> Port SSH\n --identity <path> Fichier d'identité SSH\n --ssh-option <opt> Option SSH -o supplémentaire (répétable)\n --no-focus Ouvrir l'espace de travail sans y basculer\n\nExemples :\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" + "value": "Utilisation : cmux send [flags] [--] <text>\n\nEnvoie du texte à une surface de terminal sous forme de frappes. Séquences d’échappement : \\n et \\r envoient Entrée, \\t envoie Tab.\n\nPour un texte volumineux (par exemple plus de 4 Ko, ou plusieurs lignes destinées à un agent), utilisez --paste ou cmux paste. Les frappes appuient sur Entrée à chaque saut de ligne, ce qui peut envoyer chaque ligne séparément.\n\nAvec --paste, le texte passe tel quel par le même chemin de collage que cmux paste et Cmd+V : les séquences d’échappement comme \\n ne sont pas interprétées, et les caractères de contrôle comme Échap et Ctrl-C sont remplacés par des espaces. --paste doit précéder le texte.\n\nOptions :\n --workspace <id|ref|index> Espace de travail cible (par défaut : $CMUX_WORKSPACE_ID)\n --surface <id|ref|index> Surface cible (par défaut : $CMUX_SURFACE_ID)\n --window <id|ref|index> Contexte de fenêtre pour les références et index d’espace de travail/surface\n --paste Colle le texte au lieu de le taper\n\nExemple :\n cmux send \"echo hello\"\n cmux send --surface surface:2 \"ls -la\\n\"\n cmux send --paste --surface surface:2 \"$(cat notes.md)\"" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "使用方法: cmux send [flags] [--] <text>\n\nテキストをキー入力としてターミナルサーフェスに送信します。エスケープシーケンス: \\n と \\r は Enter、\\t は Tab を送信します。\n\n大きなテキスト(たとえば 4 KB を超えるもの、またはエージェントに送る複数行)には --paste または cmux paste を使ってください。キー入力では改行ごとに Enter が押されるため、各行が個別に送信されることがあります。\n\n--paste を使うと、テキストはそのまま cmux paste や Cmd+V と同じ貼り付け経路で送られます。\\n などのエスケープシーケンスは解釈されず、Esc や Ctrl-C などの制御文字はスペースに置き換えられます。--paste はテキストより前に指定してください。\n\nフラグ:\n --workspace <id|ref|index> 対象ワークスペース(既定: $CMUX_WORKSPACE_ID)\n --surface <id|ref|index> 対象サーフェス(既定: $CMUX_SURFACE_ID)\n --window <id|ref|index> ワークスペース/サーフェスの参照とインデックスに使うウィンドウコンテキスト\n --paste テキストを入力する代わりに貼り付けます\n\n例:\n cmux send \"echo hello\"\n cmux send --surface surface:2 \"ls -la\\n\"\n cmux send --paste --surface surface:2 \"$(cat notes.md)\"" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사용법: cmux send [flags] [--] <text>\n\n텍스트를 키 입력으로 터미널 서피스에 보냅니다. 이스케이프 시퀀스: \\n과 \\r은 Enter, \\t는 Tab을 보냅니다.\n\n큰 텍스트(예: 4 KB 초과 또는 에이전트로 보내는 여러 줄)에는 --paste 또는 cmux paste를 사용하세요. 키 입력은 줄바꿈마다 Enter를 누르므로 각 줄이 따로 제출될 수 있습니다.\n\n--paste를 쓰면 텍스트가 변경 없이 cmux paste 및 Cmd+V와 같은 붙여넣기 경로로 전달됩니다. \\n 같은 이스케이프 시퀀스는 해석되지 않으며, Esc와 Ctrl-C 같은 제어 문자는 공백으로 바뀝니다. --paste는 텍스트보다 앞에 와야 합니다.\n\n플래그:\n --workspace <id|ref|index> 대상 워크스페이스 (기본값: $CMUX_WORKSPACE_ID)\n --surface <id|ref|index> 대상 서피스 (기본값: $CMUX_SURFACE_ID)\n --window <id|ref|index> 워크스페이스/서피스 참조와 인덱스에 사용할 창 컨텍스트\n --paste 텍스트를 입력하는 대신 붙여넣습니다\n\n예:\n cmux send \"echo hello\"\n cmux send --surface surface:2 \"ls -la\\n\"\n cmux send --paste --surface surface:2 \"$(cat notes.md)\"" } }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "用法:cmux send [flags] [--] <text>\n\n以按键方式将文本发送到终端表面。转义序列:\\n 和 \\r 发送 Enter,\\t 发送 Tab。\n\n对于大量文本(例如超过 4 KB,或要发给代理的多行文本),请使用 --paste 或 cmux paste。按键方式会在每个换行处按下 Enter,可能让每一行单独提交。\n\n使用 --paste 时,文本会原样通过与 cmux paste 和 Cmd+V 相同的粘贴路径发送:不会解释 \\n 等转义序列,Esc 和 Ctrl-C 等控制字符会被替换为空格。--paste 必须放在文本之前。\n\n标志:\n --workspace <id|ref|index> 目标工作区(默认:$CMUX_WORKSPACE_ID)\n --surface <id|ref|index> 目标表面(默认:$CMUX_SURFACE_ID)\n --window <id|ref|index> 工作区/表面引用与索引的窗口上下文\n --paste 粘贴文本,而不是逐键输入\n\n示例:\n cmux send \"echo hello\"\n cmux send --surface surface:2 \"ls -la\\n\"\n cmux send --paste --surface surface:2 \"$(cat notes.md)\"" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "用法:cmux send [flags] [--] <text>\n\n以按鍵方式將文字傳送到終端機表面。跳脫序列:\\n 和 \\r 送出 Enter,\\t 送出 Tab。\n\n若是大量文字(例如超過 4 KB,或要送給代理的多行文字),請使用 --paste 或 cmux paste。按鍵方式會在每個換行處按下 Enter,可能讓每一行各自送出。\n\n使用 --paste 時,文字會原封不動地透過與 cmux paste 和 Cmd+V 相同的貼上路徑送出:不會解譯 \\n 等跳脫序列,Esc 和 Ctrl-C 等控制字元會被替換為空格。--paste 必須放在文字之前。\n\n旗標:\n --workspace <id|ref|index> 目標工作區(預設:$CMUX_WORKSPACE_ID)\n --surface <id|ref|index> 目標表面(預設:$CMUX_SURFACE_ID)\n --window <id|ref|index> 工作區/表面參照與索引所用的視窗上下文\n --paste 貼上文字,而不是逐鍵輸入\n\n範例:\n cmux send \"echo hello\"\n cmux send --surface surface:2 \"ls -la\\n\"\n cmux send --paste --surface surface:2 \"$(cat notes.md)\"" + } + } + } + }, + "cli.send.hint.usePaste": { + "extractionState": "manual", + "localizations": { "ar": { "stringUnit": { "state": "translated", - "value": "الاستخدام: cmux session move <session-id> --to <ssh-destination|local> [options]\n\nينقل جلسة Claude Code متوقفة إلى جهاز آخر ويستأنفها هناك.\nأغلق الوكيل أولًا: يُرفض النقل ما دامت الجلسة تعمل على أي من الجانبين.\n\nينقل الأمر:\n - checkout الخاص بـ git في cwd: HEAD (على الفرع نفسه عندما يكون ذلك آمنًا) والملفات المعدّلة\n والمحذوفة وغير المتتبعة وغير المتجاهلة؛ ويُضاف worktree عندما يوجد المستودع في الوجهة\n ولا يوجد المسار\n - نص الجلسة ودليل الجلسة وسجل الملفات ودليل ذاكرة المشروع\n (مدمج في الاتجاهين، الأحدث يفوز، ولا يُحذف شيء)\n\nثم يفتح مساحة عمل في الوجهة (`cmux ssh` لمضيف، أو مساحة عمل محلية لـ `local`)\nتستأنف الجلسة بالمشغّل المسجّل لها. عندما لا يكون المجلد الرئيسي في الوجهة\nعلى المسار المطلق نفسه، تُحوَّل المسارات داخله\nويُعاد إنشاء slug للمشروع.\n\nالخيارات:\n --to <destination> وجهة SSH (user@host أو اسم مستعار من ssh_config)، أو local\n --from <destination> مكان الجلسة الآن (الافتراضي: حيث وضعها آخر نقل، وإلا local)\n --name <title> عنوان مساحة العمل (الافتراضي: أول 8 أحرف من المعرّف)\n --no-code لا تنقل checkout الخاص بـ git\n --port <n> منفذ SSH\n --identity <path> ملف هوية SSH\n --ssh-option <opt> خيار SSH إضافي -o (قابل للتكرار)\n --no-focus افتح مساحة العمل دون التبديل إليها\n\nأمثلة:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" + "value": "تلميح: كُتب كل سطر جديد كضغطة Enter؛ للنصوص الكبيرة متعددة الأسطر استخدم cmux send --paste أو cmux paste لإرسالها كلصقة واحدة" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Hinweis: Jeder Zeilenumbruch wurde als Enter getippt; für große mehrzeilige Texte cmux send --paste oder cmux paste verwenden, um sie als ein einziges Einfügen zu senden" + } + }, + "en": { + "stringUnit": { + "state": "translated", + "value": "hint: each newline was typed as Enter; for large multi-line text use cmux send --paste or cmux paste to deliver it as one paste" } }, "es": { "stringUnit": { "state": "translated", - "value": "Uso: cmux session move <session-id> --to <ssh-destination|local> [options]\n\nMueve una sesión detenida de Claude Code a otra máquina y la reanuda allí.\nSal primero del agente: el movimiento se rechaza mientras la sesión se ejecute en cualquiera de los lados.\n\nEl movimiento lleva:\n - el checkout de git del cwd: HEAD (en la misma rama cuando es seguro) más los archivos\n modificados, eliminados y sin seguimiento no ignorados; se añade un worktree cuando el\n repositorio existe en el destino pero la ruta no\n - la transcripción, su directorio de sesión, el historial de archivos y la memoria del\n proyecto (combinada en ambos sentidos, gana la más reciente, no se borra nada)\n\nDespués abre un espacio de trabajo en el destino (`cmux ssh` para un host, uno local para\n`local`) que reanuda la sesión con su lanzador registrado. Cuando el home del destino no está\nen la misma ruta absoluta, las rutas bajo él se convierten\ny el proyecto recibe un nuevo slug.\n\nOpciones:\n --to <destination> Destino SSH (user@host o alias de ssh_config), o local\n --from <destination> Dónde está la sesión ahora (por defecto: donde la dejó el último movimiento, si no local)\n --name <title> Título del espacio de trabajo (por defecto: los primeros 8 caracteres del id)\n --no-code No llevar el checkout de git\n --port <n> Puerto SSH\n --identity <path> Archivo de identidad SSH\n --ssh-option <opt> Opción SSH -o adicional (repetible)\n --no-focus Abrir el espacio de trabajo sin cambiar a él\n\nEjemplos:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" + "value": "sugerencia: cada salto de línea se escribió como Intro; para textos grandes de varias líneas usa cmux send --paste o cmux paste para enviarlos como un solo pegado" } }, - "zh-Hant": { + "fr": { "stringUnit": { "state": "translated", - "value": "用法: cmux session move <session-id> --to <ssh-destination|local> [options]\n\n將已停止的 Claude Code 工作階段移到另一台機器並在那裡恢復。\n請先結束代理:只要工作階段仍在任一端執行,移動就會被拒絕。\n\n移動會帶上:\n - cwd 的 git checkout:HEAD(安全時使用同一分支)加上已修改、已刪除\n 和未追蹤且未忽略的檔案;若目的地有儲存庫\n 但沒有該路徑,會新增 worktree\n - 對話紀錄、其工作階段目錄、檔案歷史與專案記憶目錄\n (雙向合併,較新者優先,不刪除任何內容)\n\n接著在目的地開啟工作區(主機用 `cmux ssh`,`local` 則開本機工作區),\n以記錄的啟動器恢復工作階段。\n若目的地的主目錄不在相同的絕對路徑,其下的路徑會被對應,\n專案也會重新產生 slug。\n\n選項:\n --to <destination> SSH 目的地(user@host 或 ssh_config 別名),或 local\n --from <destination> 工作階段目前所在位置(預設:上次移動所放的位置,否則為 local)\n --name <title> 工作區標題(預設:ID 的前 8 個字元)\n --no-code 不帶上 git checkout\n --port <n> SSH 連接埠\n --identity <path> SSH 身分檔案\n --ssh-option <opt> 額外的 SSH -o 選項(可重複)\n --no-focus 開啟工作區但不切換過去\n\n範例:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" + "value": "astuce : chaque saut de ligne a été tapé comme Entrée ; pour un texte volumineux sur plusieurs lignes, utilisez cmux send --paste ou cmux paste pour l’envoyer en un seul collage" } }, - "zh-Hans": { + "ja": { "stringUnit": { "state": "translated", - "value": "用法: cmux session move <session-id> --to <ssh-destination|local> [options]\n\n将已停止的 Claude Code 会话移到另一台机器并在那里恢复。\n请先退出代理:只要会话仍在任一端运行,移动就会被拒绝。\n\n移动会带上:\n - cwd 的 git 检出:HEAD(安全时使用同一分支)以及已修改、已删除\n 和未跟踪且未忽略的文件;若目标上有仓库\n 但没有该路径,会添加 worktree\n - 会话记录、其会话目录、文件历史和项目记忆目录\n (双向合并,较新者优先,不删除任何内容)\n\n随后在目标上打开工作区(主机用 `cmux ssh`,`local` 则打开本地工作区),\n用记录的启动器恢复会话。\n若目标的主目录不在相同的绝对路径,其下的路径会被映射,\n项目也会重新生成 slug。\n\n选项:\n --to <destination> SSH 目标(user@host 或 ssh_config 别名),或 local\n --from <destination> 会话当前所在位置(默认:上次移动放置的位置,否则为 local)\n --name <title> 工作区标题(默认:ID 的前 8 个字符)\n --no-code 不带上 git 检出\n --port <n> SSH 端口\n --identity <path> SSH 身份文件\n --ssh-option <opt> 额外的 SSH -o 选项(可重复)\n --no-focus 打开工作区但不切换过去\n\n示例:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" + "value": "ヒント: 各改行は Enter として入力されました。大きな複数行テキストは cmux send --paste または cmux paste を使うと 1 回の貼り付けとして送れます" } }, "ko": { "stringUnit": { "state": "translated", - "value": "사용법: cmux session move <session-id> --to <ssh-destination|local> [options]\n\n중지된 Claude Code 세션을 다른 머신으로 옮기고 그곳에서 재개합니다.\n먼저 에이전트를 종료하세요. 어느 쪽에서든 세션이 실행 중이면 이동이 거부됩니다.\n\n이동하는 항목:\n - cwd의 git 체크아웃: HEAD(안전하면 같은 브랜치)와 수정, 삭제,\n 무시되지 않은 추적 안 된 파일. 대상에 저장소는 있지만\n 경로가 없으면 worktree를 추가합니다\n - 트랜스크립트, 세션 디렉터리, 파일 기록, 프로젝트 메모리 디렉터리\n (양방향 병합, 최신 우선, 삭제 없음)\n\n그런 다음 대상에서 작업 공간(호스트는 `cmux ssh`, `local`은 로컬 작업 공간)을 열고\n기록된 런처로 세션을 재개합니다.\n대상의 홈이 같은 절대 경로에 있지 않으면 홈 아래 경로를 변환하고\n프로젝트 slug를 다시 만듭니다.\n\n옵션:\n --to <destination> SSH 대상(user@host 또는 ssh_config 별칭) 또는 local\n --from <destination> 세션의 현재 위치(기본값: 마지막 이동으로 옮긴 곳, 없으면 local)\n --name <title> 작업 공간 제목(기본값: ID의 처음 8자)\n --no-code git 체크아웃을 옮기지 않음\n --port <n> SSH 포트\n --identity <path> SSH ID 파일\n --ssh-option <opt> 추가 SSH -o 옵션(반복 가능)\n --no-focus 작업 공간으로 전환하지 않고 열기\n\n예시:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" + "value": "힌트: 각 줄바꿈이 Enter로 입력되었습니다. 큰 여러 줄 텍스트는 cmux send --paste 또는 cmux paste를 사용해 한 번의 붙여넣기로 보내세요" } }, - "ja": { + "zh-Hans": { "stringUnit": { "state": "translated", - "value": "使用方法: cmux session move <session-id> --to <ssh-destination|local> [options]\n\n停止した Claude Code セッションを別のマシンへ移動し、そこで再開します。\n先にエージェントを終了してください。どちらかでセッションが実行中の間は移動を拒否します。\n\n移動する内容:\n - cwd の git チェックアウト: HEAD (安全なら同じブランチ) と、変更・削除されたファイル、\n 無視されていない未追跡ファイル。移動先にリポジトリはあるが\n パスがない場合は worktree を追加します\n - トランスクリプト、セッションディレクトリ、ファイル履歴、プロジェクトのメモリディレクトリ\n (双方向にマージ、新しいほうを優先、削除はしません)\n\nその後、移動先でワークスペース (ホストなら `cmux ssh`、`local` ならローカルのワークスペース) を\n開き、記録されたランチャーでセッションを再開します。\n移動先のホームが同じ絶対パスにない場合は、ホーム配下のパスを変換し、\nプロジェクトの slug を作り直します。\n\nオプション:\n --to <destination> SSH 接続先 (user@host または ssh_config のエイリアス)、または local\n --from <destination> セッションの現在の場所 (既定: 前回の移動先、なければ local)\n --name <title> ワークスペースのタイトル (既定: ID の先頭 8 文字)\n --no-code git チェックアウトを移動しない\n --port <n> SSH ポート\n --identity <path> SSH の ID ファイル\n --ssh-option <opt> 追加の SSH -o オプション (繰り返し可)\n --no-focus ワークスペースに切り替えずに開く\n\n例:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" + "value": "提示:每个换行都作为 Enter 输入;对于大量多行文本,请使用 cmux send --paste 或 cmux paste 以一次粘贴发送" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "提示:每個換行都以 Enter 輸入;若是大量多行文字,請使用 cmux send --paste 或 cmux paste 以單次貼上送出" } } } @@ -11836,6 +11895,65 @@ } } }, + "settings.computerUse.enabled.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Lets supported agents see and control apps on this Mac. An agent's first Computer Use request starts setup automatically." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Unterstützte Agenten können Apps auf diesem Mac sehen und steuern. Die erste Computer-Use-Anfrage eines Agenten startet die Einrichtung automatisch." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Les agents pris en charge peuvent voir et contrôler les apps de ce Mac. La première requête Computer Use d’un agent lance automatiquement la configuration." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يمكن للوكلاء المدعومين رؤية التطبيقات على جهاز Mac هذا والتحكم فيها. ويبدأ الإعداد تلقائيًا عند أول طلب Computer Use من أحد الوكلاء." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Los agentes compatibles pueden ver y controlar apps en este Mac. La primera solicitud de Computer Use de un agente inicia la configuración automáticamente." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "允許支援的代理程式查看及操作此 Mac 上的 App。代理程式第一次發出 Computer Use 要求時會自動開始設定。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "允许受支持的代理查看并控制这台 Mac 上的 App。代理第一次发出 Computer Use 请求时会自动开始设置。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "지원되는 에이전트가 이 Mac의 앱을 보고 제어할 수 있게 합니다. 에이전트가 처음 Computer Use를 요청하면 설정이 자동으로 시작됩니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "対応するエージェントが、この Mac のアプリの画面を見て操作できるようにします。エージェントが最初に Computer Use を要求すると、セットアップが自動的に始まります。" + } + } + } + }, "settings.computerUse.permission.accessibility": { "extractionState": "manual", "localizations": { @@ -227637,65 +227755,6 @@ } } }, - "globalSearch.kind.terminal": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Terminal" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ターミナル" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "터미널" - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "طرفية" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "終端" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "终端" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Terminal" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Terminal" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Terminal" - } - } - } - }, "globalSearch.kind.title": { "extractionState": "manual", "localizations": { @@ -291373,6 +291432,65 @@ } } }, + "settings.app.closeWorkspaceOnLastSurfaceShortcut.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Closing the last surface in a workspace keeps the workspace open." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Der Arbeitsbereich bleibt geöffnet, auch wenn seine letzte Oberfläche geschlossen wird." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "L’espace de travail reste ouvert après la fermeture de sa dernière surface d’affichage." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تبقى مساحة العمل مفتوحة عند إغلاق آخر سطح فيها." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "El espacio de trabajo permanece abierto aunque se cierre su última superficie." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "即使關閉最後一個介面,工作區仍會保持開啟。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "即使关闭最后一个表面,工作区也会保持打开。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "마지막 서피스를 닫아도 워크스페이스는 닫히지 않습니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "最後のサーフェスを閉じても、ワークスペースは閉じません。" + } + } + } + }, "settings.app.commandPaletteSearchAllSurfaces": { "extractionState": "manual", "localizations": { @@ -291438,6 +291556,65 @@ } } }, + "settings.app.commandPaletteSearchAllSurfaces.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Includes terminal, browser, and Markdown surfaces from every workspace in command palette results." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Die Befehlspalette findet auch Terminal-, Browser- und Markdown-Oberflächen in allen Arbeitsbereichen." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "La palette de commandes affiche aussi les surfaces d’affichage de terminal, de navigateur et Markdown de tous les espaces de travail." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تعرض نتائج لوحة الأوامر أسطح الطرفية والمتصفح وMarkdown من جميع مساحات العمل." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "La paleta de comandos también muestra las superficies de terminal, navegador y Markdown de todos los espacios de trabajo." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在命令面板中也能找到所有工作區的終端機、瀏覽器和 Markdown 介面。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在命令面板中也能找到所有工作区的终端、浏览器和 Markdown 表面。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "명령어 팔레트에서 모든 워크스페이스의 터미널, 브라우저, Markdown 서피스도 찾을 수 있습니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "コマンドパレットで、すべてのワークスペースのターミナル、ブラウザ、Markdown のサーフェスも検索できます。" + } + } + } + }, "settings.app.configWindow": { "extractionState": "manual", "localizations": { @@ -293256,6 +293433,65 @@ } } }, + "settings.app.fileDrop.defaultBehavior.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Choose what dropping files on a terminal or editor does. Holding Shift while dragging does the other." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Wählen Sie, was beim Ablegen von Dateien auf einem Terminal oder Editor passiert. Wenn Sie beim Ziehen die Umschalttaste gedrückt halten, gilt die andere Option." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Choisissez ce qui se passe lorsque vous déposez des fichiers sur un terminal ou un éditeur. Si vous maintenez la touche Maj enfoncée pendant le glissement, l’autre option s’applique." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "اختر ما يحدث عند إفلات الملفات على طرفية أو محرر. يؤدي الضغط مع الاستمرار على مفتاح Shift أثناء السحب إلى تطبيق الخيار الآخر." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Elige qué ocurre al soltar archivos en una terminal o un editor. Si mantienes pulsada la tecla Mayúsculas al arrastrar, se usa la otra opción." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "選擇將檔案拖放到終端機或編輯器時要執行的動作。拖移時按住 Shift 鍵可改用另一個動作。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "选择将文件拖放到终端或编辑器时执行的操作。拖动时按住 Shift 键可改用另一种操作。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "파일을 터미널이나 편집기에 드롭했을 때 수행할 동작을 선택합니다. 드래그 중에 Shift 키를 누르고 있으면 다른 쪽 동작을 수행합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ファイルをターミナルやエディタにドロップしたときの動作を選択します。ドラッグ中に Shift キーを押したままにすると、もう一方の動作になります。" + } + } + } + }, "settings.app.fileDrop.defaultBehavior.preview": { "localizations": { "ar": { @@ -293905,6 +294141,65 @@ } } }, + "settings.app.focusHistoryIncludesPanesAndTabs.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Using Back and Forward also returns to previous panes and tabs." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Mit „Zurück“ und „Vor“ kehren Sie auch zu vorherigen Teilfenstern und Tabs zurück." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Précédent et Suivant ramènent aussi aux volets et aux onglets précédents." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "ينتقل «رجوع» و«تقدم» أيضًا إلى الأجزاء وعلامات التبويب السابقة." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Atrás y Adelante también vuelven a los paneles y pestañas anteriores." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "使用「返回」與「前進」時,也會回到先前的窗格與標籤頁。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "“后退”和“前进”也会回到之前的窗格和标签页。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "뒤로 가기와 앞으로 가기로 이전 분할 창과 탭에도 돌아갈 수 있습니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "「戻る」と「進む」で、以前のペインやタブにも戻れます。" + } + } + } + }, "settings.app.forkConversationDefaultDestination": { "extractionState": "manual", "localizations": { @@ -294214,6 +294509,65 @@ } } }, + "settings.app.globalFontMagnification.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Scales all text in cmux. Command-Plus and Command-Minus still zoom the focused pane." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Ändert die Größe des gesamten Texts in cmux. Mit Befehlstaste-Plus und Befehlstaste-Minus lässt sich das fokussierte Teilfenster weiterhin zoomen." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Modifie la taille de tout le texte de cmux. Commande + Plus et Commande + Moins continuent de régler le zoom du volet actif." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يغيّر حجم جميع النصوص في cmux. ولا يزال الاختصاران Command-علامة الجمع وCommand-علامة الطرح يكبّران الجزء المركّز عليه ويصغّرانه." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cambia el tamaño de todo el texto de cmux. Comando + Más y Comando + Menos siguen ajustando el zoom del panel enfocado." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "調整 cmux 中所有文字的大小。Command + 加號和 Command + 減號仍可用來縮放目前聚焦的窗格。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "调整 cmux 中所有文本的大小。Command + 加号和 Command + 减号仍可用于缩放当前聚焦的窗格。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "cmux의 모든 텍스트 크기를 조절합니다. 포커스된 분할 창은 계속 Command-더하기와 Command-빼기로 확대/축소할 수 있습니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux 内のすべてのテキストの大きさを変更します。フォーカス中のペインは、これまでどおり Command + プラスと Command + マイナスで拡大縮小できます。" + } + } + } + }, "settings.app.hideAllSidebarDetails": { "extractionState": "manual", "localizations": { @@ -294279,6 +294633,65 @@ } } }, + "settings.app.hideAllSidebarDetails.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Shows only workspace titles in the sidebar." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Zeigt in der Seitenleiste nur die Titel der Arbeitsbereiche an." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Affiche uniquement les titres des espaces de travail dans la barre latérale." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يعرض عناوين مساحات العمل فقط في الشريط الجانبي." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Muestra solo los títulos de los espacios de trabajo en la barra lateral." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "側邊欄只顯示工作區標題。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "侧边栏只显示工作区标题。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사이드바에 워크스페이스 제목만 표시합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーにワークスペースのタイトルだけを表示します。" + } + } + } + }, "settings.app.hideTabCloseButton": { "extractionState": "manual", "localizations": { @@ -294404,6 +294817,65 @@ } } }, + "settings.app.hideTabCloseButton.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Removes the close button from tabs." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Tabs werden ohne Schließen-Taste angezeigt." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Les onglets s’affichent sans bouton de fermeture." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تظهر علامات التبويب دون زر إغلاق." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Las pestañas se muestran sin el botón de cerrar." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "標籤頁上不顯示關閉按鈕。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "标签页上不显示关闭按钮。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "탭에 닫기 버튼을 표시하지 않습니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブの閉じるボタンを非表示にします。" + } + } + } + }, "settings.app.iMessageMode": { "extractionState": "manual", "localizations": { @@ -296663,6 +297135,65 @@ } } }, + "settings.app.minimalMode.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Hides the workspace title bar and shows its controls in the sidebar." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Blendet die Titelleiste des Arbeitsbereichs aus und zeigt ihre Steuerelemente in der Seitenleiste an." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Masque la barre de titre de l’espace de travail et affiche ses commandes dans la barre latérale." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يخفي شريط عنوان مساحة العمل ويعرض عناصر التحكم الخاصة به في الشريط الجانبي." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Oculta la barra de título del espacio de trabajo y muestra sus controles en la barra lateral." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "隱藏工作區標題列,改在側邊欄中顯示其控制項。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "隐藏工作区标题栏,改为在侧边栏中显示其控件。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "워크스페이스 제목 막대를 숨기고, 제목 막대의 컨트롤을 사이드바에 표시합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースのタイトルバーを非表示にし、そのコントロールをサイドバーに表示します。" + } + } + } + }, "settings.app.newWorkspacePlacement": { "extractionState": "manual", "localizations": { @@ -296782,6 +297313,65 @@ } } }, + "settings.app.newWorkspacePlacement.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Choose where new workspaces appear in the sidebar." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Wählen Sie, wo neue Arbeitsbereiche in der Seitenleiste erscheinen." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Choisissez où les nouveaux espaces de travail apparaissent dans la barre latérale." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "اختر مكان ظهور مساحات العمل الجديدة في الشريط الجانبي." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Elige dónde aparecen los nuevos espacios de trabajo en la barra lateral." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "選擇新工作區要顯示在側邊欄的哪個位置。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "选择新工作区在侧边栏中的显示位置。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "새 작업 공간을 사이드바의 어디에 표시할지 선택합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新しいワークスペースをサイドバーのどこに表示するかを選択します。" + } + } + } + }, "settings.app.notificationBadgePosition": { "extractionState": "manual", "localizations": { @@ -297505,6 +298095,65 @@ } } }, + "settings.app.openSidebarPRLinks.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clicking a pull request link in the sidebar opens it in the cmux browser." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Pull-Request-Links in der Seitenleiste werden per Klick im cmux-Browser geöffnet." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Un clic sur un lien de pull request de la barre latérale l’ouvre dans le navigateur cmux." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تُفتح روابط طلبات السحب في الشريط الجانبي في متصفح cmux عند النقر عليها." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Los enlaces de pull request de la barra lateral se abren en el navegador de cmux al hacer clic en ellos." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "側邊欄中的 PR 連結會在 cmux 瀏覽器中開啟。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "侧边栏中的 PR 链接会在 cmux 浏览器中打开。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사이드바의 풀 리퀘스트 링크를 클릭하면 cmux 브라우저에서 열립니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーのプルリクエストのリンクをクリックすると、cmuxブラウザで開きます。" + } + } + } + }, "settings.app.openSidebarPortLinks": { "extractionState": "manual", "localizations": { @@ -297624,6 +298273,65 @@ } } }, + "settings.app.openSidebarPortLinks.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clicking a port link in the sidebar opens it in the cmux browser." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Port-Links in der Seitenleiste werden per Klick im cmux-Browser geöffnet." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Un clic sur un lien de port de la barre latérale l’ouvre dans le navigateur cmux." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تُفتح روابط المنافذ في الشريط الجانبي في متصفح cmux عند النقر عليها." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Los enlaces de puerto de la barra lateral se abren en el navegador de cmux al hacer clic en ellos." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "側邊欄中的連接埠連結會在 cmux 瀏覽器中開啟。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "侧边栏中的端口链接会在 cmux 浏览器中打开。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사이드바의 포트 링크를 클릭하면 cmux 브라우저에서 열립니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーのポートリンクをクリックすると、cmuxブラウザで開きます。" + } + } + } + }, "settings.app.openSupportedFilesInCmux": { "extractionState": "manual", "localizations": { @@ -297807,6 +298515,65 @@ } } }, + "settings.app.paneFirstClickFocus.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clicking a pane while cmux is in the background focuses that pane right away." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Ist cmux im Hintergrund, wird ein Teilfenster schon beim ersten Klick fokussiert." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Lorsque cmux est en arrière-plan, un clic sur un volet l’active immédiatement." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عندما يكون cmux في الخلفية، يكفي النقر على جزء للتركيز عليه فورًا." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Si cmux está en segundo plano, un clic en un panel lo enfoca de inmediato." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "即使 cmux 在背景執行,按一下窗格也會立即將焦點移到該窗格。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "即使 cmux 在后台,点按窗格也会立即将焦点移到该窗格。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "cmux가 백그라운드에 있어도 분할 창을 클릭하면 바로 그 분할 창에 포커스가 이동합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux がバックグラウンドにあっても、ペインをクリックするとすぐにそのペインにフォーカスが移ります。" + } + } + } + }, "settings.app.pathLastSegmentOnly": { "extractionState": "manual", "localizations": { @@ -297866,6 +298633,65 @@ } } }, + "settings.app.pathLastSegmentOnly.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Shortens long paths from the start to keep their end visible." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Kürzt lange Pfade am Anfang, damit ihr Ende sichtbar bleibt." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Raccourcit les chemins longs par le début pour que leur fin reste visible." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يختصر المسارات الطويلة من بدايتها لتبقى نهايتها ظاهرة." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Acorta las rutas largas por el inicio para que su final siga visible." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "長路徑會省略開頭,保留結尾部分。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "长路径会省略开头,保留结尾部分。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "긴 경로는 앞부분을 생략하여 끝부분이 보이게 합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "長いパスは先頭を省略して、末尾が見えるようにします。" + } + } + } + }, "settings.app.preferredEditor": { "extractionState": "manual", "localizations": { @@ -298162,6 +298988,65 @@ } } }, + "settings.app.renameSelectsName.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Renaming from the command palette selects the current name." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Beim Umbenennen über die Befehlspalette ist der aktuelle Name bereits ausgewählt." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Lors d’un renommage depuis la palette de commandes, le nom actuel est sélectionné." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عند إعادة التسمية من لوحة الأوامر، يكون الاسم الحالي محددًا." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Al cambiar el nombre desde la paleta de comandos, el nombre actual aparece seleccionado." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "從命令面板重新命名時,會選取目前的名稱。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "从命令面板重命名时,会选中当前名称。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "명령어 팔레트에서 이름을 변경할 때 현재 이름이 선택된 상태로 표시됩니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "コマンドパレットで名前を変更するとき、現在の名前が選択された状態になります。" + } + } + } + }, "settings.app.reorderOnNotification": { "extractionState": "manual", "localizations": { @@ -301006,6 +301891,65 @@ } } }, + "settings.app.sidebarBranchLayout.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Choose whether branches share one line or each get their own line." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Wählen Sie, ob sich Branches eine Zeile teilen oder jeweils eine eigene Zeile erhalten." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Choisissez si les branches partagent une même ligne ou ont chacune leur propre ligne." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "اختر ما إذا كانت الفروع تتشارك سطرًا واحدًا أو يحصل كل منها على سطر خاص به." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Elige si las ramas comparten una sola línea o cada una ocupa su propia línea." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "選擇分支要共用一行,還是各自獨立一行。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "选择分支是共用一行,还是各占一行。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "브랜치를 한 줄에 함께 표시할지, 각각 별도의 줄에 표시할지 선택합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブランチを 1 行にまとめて表示するか、それぞれ別の行に表示するかを選択します。" + } + } + } + }, "settings.app.sidebarBranchLayout.inline": { "extractionState": "manual", "localizations": { @@ -301303,6 +302247,65 @@ } } }, + "settings.app.stackBranchDirectory.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Shows the branch and directory on separate lines." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Zeigt Branch und Verzeichnis in getrennten Zeilen an." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Affiche la branche et le répertoire sur des lignes distinctes." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يعرض الفرع والدليل في سطرين منفصلين." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Muestra la rama y el directorio en líneas separadas." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "分支與目錄分行顯示。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "分支和目录分行显示。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "브랜치와 디렉터리를 각각 다른 줄에 표시합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブランチとディレクトリを別々の行に表示します。" + } + } + } + }, "settings.app.telemetry": { "extractionState": "manual", "localizations": { @@ -301838,6 +302841,65 @@ } } }, + "settings.app.warnBeforeClosingTab.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Closing a tab asks for confirmation first." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Beim Schließen eines Tabs wird zuerst nach einer Bestätigung gefragt." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Une confirmation est demandée avant la fermeture d’un onglet." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يُطلب التأكيد قبل إغلاق علامة التبويب." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Se pide confirmación antes de cerrar una pestaña." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "關閉標籤頁前先要求確認。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "关闭标签页前先要求确认。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "탭을 닫기 전에 확인을 요청합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブを閉じる前に確認します。" + } + } + } + }, "settings.app.warnBeforeClosingTabXButton": { "extractionState": "manual", "localizations": { @@ -302022,6 +303084,65 @@ } } }, + "settings.app.warnBeforeClosingTabXButton.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clicking a tab's close button asks for confirmation first." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Beim Klicken auf die Schließen-Taste eines Tabs wird zuerst nach einer Bestätigung gefragt." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Une confirmation est demandée avant la fermeture d’un onglet avec son bouton de fermeture." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يُطلب التأكيد قبل إغلاق علامة التبويب بالنقر على زر الإغلاق الخاص بها." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Se pide confirmación antes de cerrar una pestaña con su botón de cerrar." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "按一下標籤頁上的關閉按鈕後,會先要求確認再關閉。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "点按标签页上的关闭按钮后,会先要求确认再关闭。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "탭의 닫기 버튼을 클릭하면 탭을 닫기 전에 확인을 요청합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブの閉じるボタンをクリックしたとき、閉じる前に確認します。" + } + } + } + }, "settings.app.warnBeforeQuit": { "extractionState": "manual", "localizations": { @@ -302141,6 +303262,65 @@ } } }, + "settings.app.warnBeforeQuit.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Choose when quitting with Command-Q asks for confirmation. Dirty Only asks only when a workspace would ask before closing." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Wählen Sie, wann beim Beenden mit Befehlstaste-Q nach einer Bestätigung gefragt wird. „Nur bei Bedarf“ fragt nur, wenn ein Arbeitsbereich vor dem Schließen nachfragen würde." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Choisissez quand une confirmation est demandée pour quitter avec Commande + Q. « Si nécessaire » ne la demande que si un espace de travail la demanderait avant sa fermeture." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "اختر متى يُطلب التأكيد عند الإنهاء باستخدام Command-Q. يطلب خيار «عند الحاجة فقط» التأكيد فقط عندما تطلبه مساحة عمل قبل إغلاقها." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Elige cuándo se pide confirmación al salir con Comando + Q. «Solo si hace falta» solo la pide cuando un espacio de trabajo la pediría antes de cerrarse." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "選擇按 Command + Q 結束時,在什麼情況下要求確認。「僅在需要時」只會在某個工作區關閉前需要確認時才詢問。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "选择按 Command + Q 退出时,在什么情况下要求确认。“仅在需要时”只会在某个工作区关闭前需要确认时才询问。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "Command-Q로 종료할 때 확인을 요청할 조건을 선택합니다. ‘필요 시에만’은 닫기 전에 확인이 필요한 작업 공간이 있을 때만 확인을 요청합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Command + Q で終了するときに、どのような場合に確認するかを選択します。「必要時のみ」では、閉じる前に確認が必要なワークスペースがある場合だけ確認します。" + } + } + } + }, "settings.app.watchGitStatus": { "extractionState": "manual", "localizations": { @@ -302563,6 +303743,65 @@ } } }, + "settings.app.workspaceInheritWorkingDirectory.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Starts new workspaces in the working directory of the current workspace." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Startet neue Arbeitsbereiche im Arbeitsverzeichnis des aktuellen Arbeitsbereichs." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvre les nouveaux espaces de travail dans le répertoire de travail de l’espace de travail actuel." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يبدأ مساحات العمل الجديدة في دليل العمل الخاص بمساحة العمل الحالية." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Inicia los nuevos espacios de trabajo en el directorio de trabajo del espacio de trabajo actual." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "新工作區會沿用目前工作區的工作目錄。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "新工作区会沿用当前工作区的工作目录。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "새 작업 공간은 현재 작업 공간의 작업 디렉터리에서 시작됩니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新しいワークスペースは、現在のワークスペースと同じ作業ディレクトリで開始します。" + } + } + } + }, "settings.app.workspaceLayouts": { "extractionState": "manual", "localizations": { @@ -302865,6 +304104,65 @@ } } }, + "settings.app.wrapWorkspaceTitles.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Shows long workspace titles in full on multiple lines." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Zeigt lange Arbeitsbereichstitel vollständig in mehreren Zeilen an." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Affiche en entier les titres longs des espaces de travail sur plusieurs lignes." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يعرض عناوين مساحات العمل الطويلة كاملةً على عدة أسطر." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Muestra completos los títulos largos de los espacios de trabajo en varias líneas." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "較長的工作區標題會換行完整顯示。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "较长的工作区标题会换行完整显示。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "긴 워크스페이스 제목을 줄바꿈하여 모두 표시합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "長いワークスペースタイトルを省略せずに複数行で表示します。" + } + } + } + }, "settings.automation.amp": { "extractionState": "manual", "localizations": { @@ -303049,6 +304347,65 @@ } } }, + "settings.automation.amp.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Shows Amp status and notifications in the sidebar." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Zeigt den Status und die Benachrichtigungen von Amp in der Seitenleiste an." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Affiche l’état et les notifications d’Amp dans la barre latérale." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يعرض حالة Amp وإشعاراته في الشريط الجانبي." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Muestra el estado y las notificaciones de Amp en la barra lateral." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在側邊欄中顯示 Amp 的狀態與通知。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在侧边栏中显示 Amp 的状态和通知。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사이드바에 Amp 상태와 알림을 표시합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーにAmpのステータスと通知を表示します。" + } + } + } + }, "settings.automation.autoNamingAgent": { "extractionState": "manual", "localizations": { @@ -303174,6 +304531,65 @@ } } }, + "settings.automation.autoNamingAgent.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Automatic uses each session's own agent. Other supported agents need their command-line tool on the PATH." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "„Automatisch“ verwendet den Agenten der jeweiligen Sitzung. Andere unterstützte Agenten benötigen ihr Befehlszeilentool im PATH." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "« Automatique » utilise l’agent propre à chaque session. Les autres agents pris en charge nécessitent que leur outil en ligne de commande soit dans le PATH." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يستخدم خيار «تلقائي» الوكيل الخاص بكل جلسة. يحتاج الوكلاء المدعومون الآخرون إلى وجود أداة سطر الأوامر الخاصة بهم في PATH." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "«Automático» usa el agente de cada sesión. Los demás agentes compatibles necesitan su herramienta de línea de comandos en el PATH." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "「自動」會使用各工作階段本身的代理。若要使用其他支援的代理,其命令列工具必須位於 PATH 中。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "“自动”会使用各会话自身的代理。要使用其他受支持的代理,其命令行工具必须位于 PATH 中。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "‘자동’은 세션마다 해당 세션의 에이전트를 사용합니다. 지원되는 다른 에이전트를 사용하려면 해당 명령줄 도구가 PATH에 있어야 합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "「自動」では、各セッションのエージェントを使用します。ほかの対応エージェントを使うには、そのコマンドラインツールが PATH に含まれている必要があります。" + } + } + } + }, "settings.automation.autoNamingAgent.auto": { "extractionState": "manual", "localizations": { @@ -304009,6 +305425,65 @@ } } }, + "settings.automation.claudeCode.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Shows Claude Code status and notifications in the sidebar." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Zeigt den Status und die Benachrichtigungen von Claude Code in der Seitenleiste an." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Affiche l’état et les notifications de Claude Code dans la barre latérale." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يعرض حالة Claude Code وإشعاراته في الشريط الجانبي." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Muestra el estado y las notificaciones de Claude Code en la barra lateral." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在側邊欄中顯示 Claude Code 的狀態與通知。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在侧边栏中显示 Claude Code 的状态和通知。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사이드바에 Claude Code 상태와 알림을 표시합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーにClaude Codeのステータスと通知を表示します。" + } + } + } + }, "settings.automation.codex": { "extractionState": "manual", "localizations": { @@ -304127,6 +305602,65 @@ } } }, + "settings.automation.codex.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Shows Codex status and notifications in the sidebar." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Zeigt den Status und die Benachrichtigungen von Codex in der Seitenleiste an." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Affiche l’état et les notifications de Codex dans la barre latérale." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يعرض حالة Codex وإشعاراته في الشريط الجانبي." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Muestra el estado y las notificaciones de Codex en la barra lateral." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在側邊欄中顯示 Codex 的狀態與通知。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在侧边栏中显示 Codex 的状态和通知。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사이드바에 Codex 상태와 알림을 표시합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーにCodexのステータスと通知を表示します。" + } + } + } + }, "settings.automation.cursor": { "extractionState": "manual", "localizations": { @@ -304245,6 +305779,65 @@ } } }, + "settings.automation.cursor.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Shows Cursor status and notifications in the sidebar." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Zeigt den Status und die Benachrichtigungen von Cursor in der Seitenleiste an." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Affiche l’état et les notifications de Cursor dans la barre latérale." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يعرض حالة Cursor وإشعاراته في الشريط الجانبي." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Muestra el estado y las notificaciones de Cursor en la barra lateral." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在側邊欄中顯示 Cursor 的狀態與通知。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在侧边栏中显示 Cursor 的状态和通知。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사이드바에 Cursor 상태와 알림을 표시합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーにCursorのステータスと通知を表示します。" + } + } + } + }, "settings.automation.gemini": { "extractionState": "manual", "localizations": { @@ -304363,6 +305956,65 @@ } } }, + "settings.automation.gemini.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Shows Gemini CLI status and notifications in the sidebar." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Zeigt den Status und die Benachrichtigungen von Gemini CLI in der Seitenleiste an." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Affiche l’état et les notifications de Gemini CLI dans la barre latérale." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يعرض حالة Gemini CLI وإشعاراته في الشريط الجانبي." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Muestra el estado y las notificaciones de Gemini CLI en la barra lateral." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在側邊欄中顯示 Gemini CLI 的狀態與通知。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在侧边栏中显示 Gemini CLI 的状态和通知。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사이드바에 Gemini CLI 상태와 알림을 표시합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーにGemini CLIのステータスと通知を表示します。" + } + } + } + }, "settings.automation.kiro": { "extractionState": "manual", "localizations": { @@ -304776,6 +306428,65 @@ } } }, + "settings.automation.kiro.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Shows Kiro CLI status and notifications in the sidebar, and its tool events in Feed." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Zeigt den Status und die Benachrichtigungen von Kiro CLI in der Seitenleiste und seine Tool-Ereignisse im Feed an." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Affiche l’état et les notifications de Kiro CLI dans la barre latérale, et ses événements d’outils dans le Flux." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يعرض حالة Kiro CLI وإشعاراته في الشريط الجانبي، وأحداث أدواته في Feed." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Muestra el estado y las notificaciones de Kiro CLI en la barra lateral, y sus eventos de herramientas en el Feed." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在側邊欄中顯示 Kiro CLI 的狀態與通知,並在 Feed 中顯示其工具事件。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在侧边栏中显示 Kiro CLI 的状态和通知,并在 Feed 中显示其工具事件。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사이드바에 Kiro CLI 상태와 알림을 표시하고, 피드에 해당 도구 이벤트를 표시합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーにKiro CLIのステータスと通知を、フィードにそのツールイベントを表示します。" + } + } + } + }, "settings.automation.openAccess.dialog.cancel": { "extractionState": "manual", "localizations": { @@ -306262,6 +307973,65 @@ } } }, + "settings.automation.socketMode.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Choose which local processes can control cmux." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Wählen Sie, welche lokalen Prozesse cmux steuern können." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Choisissez quels processus locaux peuvent contrôler cmux." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "اختر العمليات المحلية التي يمكنها التحكم في cmux." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Elige qué procesos locales pueden controlar cmux." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "選擇哪些本機程序可以控制 cmux。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "选择哪些本地进程可以控制 cmux。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "cmux를 제어할 수 있는 로컬 프로세스를 선택합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux を制御できるローカルプロセスを選択します。" + } + } + } + }, "settings.automation.socketMode.note": { "extractionState": "manual", "localizations": { @@ -308165,6 +309935,65 @@ } } }, + "settings.automation.suppressSubagentNotifications.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Lists subagent completions in Feed without sending notifications." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Abgeschlossene Subagenten erscheinen im Feed, ohne dass Benachrichtigungen gesendet werden." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Les sous-agents terminés apparaissent dans le Flux, sans notification." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يظهر اكتمال مهام الوكلاء الفرعيين في Feed دون إرسال إشعارات." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Los subagentes finalizados aparecen en el Feed sin enviar notificaciones." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "子代理完成時只會列在 Feed 中,不會傳送通知。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "子代理完成时只会列在 Feed 中,不会发送通知。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "하위 에이전트가 완료되면 알림을 보내지 않고 피드에만 표시합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サブエージェントが完了しても通知は送らず、フィードにのみ表示します。" + } + } + } + }, "settings.automation.workspaceAutoNaming": { "extractionState": "manual", "localizations": { @@ -308540,6 +310369,65 @@ } } }, + "settings.automation.workspaceAutoNaming.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Generates workspace and tab titles from agent conversations." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Benennt Arbeitsbereiche und Tabs anhand von Agentengesprächen." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Nomme les espaces de travail et les onglets d’après les conversations des agents." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يسمّي مساحات العمل وعلامات التبويب بناءً على محادثات الوكلاء." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Pone nombre a los espacios de trabajo y las pestañas según las conversaciones de los agentes." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "根據代理對話產生工作區和標籤頁標題。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "根据代理对话生成工作区和标签页标题。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "에이전트 대화를 바탕으로 작업 공간과 탭 제목을 생성합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "エージェントの会話からワークスペースとタブのタイトルを生成します。" + } + } + } + }, "settings.betaFeatures.cloudMachines": { "extractionState": "manual", "localizations": { @@ -308599,6 +310487,65 @@ } } }, + "settings.betaFeatures.cloudMachines.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Adds Cloud Machines to the right sidebar, Settings, the command palette, and the new workspace menu. Cloud Machines also require a remote rollout; with this off, the Cloud tunnel and fleet polling stay off." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Fügt Cloud-Maschinen zur rechten Seitenleiste, zu den Einstellungen, zur Befehlspalette und zum Menü für neue Arbeitsbereiche hinzu. Cloud-Maschinen erfordern außerdem ein Remote-Rollout; solange diese Option deaktiviert ist, bleiben Cloud-Tunnel und Flottenabfrage aus." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ajoute les machines cloud à la barre latérale droite, aux Réglages, à la palette de commandes et au menu de nouvel espace de travail. Les machines cloud nécessitent aussi un déploiement à distance ; si cette option est désactivée, le tunnel Cloud et l’interrogation de la flotte restent arrêtés." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يضيف الأجهزة السحابية إلى الشريط الجانبي الأيمن والإعدادات ولوحة الأوامر وقائمة مساحة العمل الجديدة. وتتطلب الأجهزة السحابية أيضًا طرحًا عن بُعد؛ وعند إيقاف هذا الخيار يظل نفق Cloud واستطلاع الأسطول متوقفين." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Añade las máquinas en la nube a la barra lateral derecha, a Ajustes, a la paleta de comandos y al menú de nuevo espacio de trabajo. Las máquinas en la nube también requieren un despliegue remoto; con esta opción desactivada, el túnel de Cloud y el sondeo de la flota permanecen desactivados." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在右側邊欄、設定、命令面板和新增工作區選單中加入雲端機器。雲端機器也需要遠端推出才能使用;關閉此選項時,Cloud 通道和機群輪詢都會保持停用。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在右侧边栏、设置、命令面板和新建工作区菜单中添加云主机。云主机还需要远程推出才能使用;关闭此选项时,Cloud 隧道和机群轮询都会保持停用。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "오른쪽 사이드바, 설정, 명령어 팔레트, 새 작업 공간 메뉴에 클라우드 머신을 추가합니다. 클라우드 머신은 원격 출시도 필요하며, 이 옵션이 꺼져 있으면 Cloud 터널과 플릿 폴링도 꺼진 상태로 유지됩니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "右サイドバー、設定、コマンドパレット、新規ワークスペースメニューにクラウドマシンを追加します。クラウドマシンにはリモートでの提供開始も必要です。この設定がオフの間は、Cloud トンネルとフリートのポーリングも停止したままです。" + } + } + } + }, "settings.betaFeatures.customSidebars": { "extractionState": "manual", "localizations": { @@ -308658,6 +310605,65 @@ } } }, + "settings.betaFeatures.customSidebars.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Adds sidebars from ~/.config/cmux/sidebars to the sidebar picker." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Fügt Seitenleisten aus ~/.config/cmux/sidebars zur Seitenleistenauswahl hinzu." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ajoute les barres latérales de ~/.config/cmux/sidebars au sélecteur de barre latérale." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يضيف الأشرطة الجانبية من ~/.config/cmux/sidebars إلى منتقي الشريط الجانبي." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Añade las barras laterales de ~/.config/cmux/sidebars al selector de barra lateral." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在側邊欄選取器中加入 ~/.config/cmux/sidebars 裡的側邊欄。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在侧边栏选择器中添加 ~/.config/cmux/sidebars 里的侧边栏。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "~/.config/cmux/sidebars의 사이드바를 사이드바 선택기에 추가합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "~/.config/cmux/sidebars のサイドバーをサイドバーピッカーに追加します。" + } + } + } + }, "settings.betaFeatures.dock": { "extractionState": "manual", "localizations": { @@ -308717,6 +310723,65 @@ } } }, + "settings.betaFeatures.dock.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Adds Dock to the right sidebar for custom terminal controls." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Fügt der rechten Seitenleiste das Dock für benutzerdefinierte Terminal-Steuerelemente hinzu." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ajoute le Dock à la barre latérale droite pour les commandes de terminal personnalisées." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يضيف Dock إلى الشريط الجانبي الأيمن لعناصر التحكم المخصصة في الطرفية." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Añade el Dock a la barra lateral derecha para controles de terminal personalizados." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在右側邊欄加入 Dock,用來放置自訂的終端機控制項。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在右侧边栏添加 Dock,用来放置自定义的终端控件。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "오른쪽 사이드바에 Dock을 추가해 터미널 컨트롤을 직접 구성할 수 있습니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "右サイドバーに Dock を追加し、ターミナル用のカスタムコントロールを配置できるようにします。" + } + } + } + }, "settings.betaFeatures.extensions": { "extractionState": "manual", "localizations": { @@ -308776,6 +310841,65 @@ } } }, + "settings.betaFeatures.extensions.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Adds the extensions button and lets cmux install and run sidebar extensions." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Fügt die Taste „Erweiterungen“ hinzu und erlaubt cmux, Seitenleisten-Erweiterungen zu installieren und auszuführen." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ajoute le bouton des extensions et permet à cmux d’installer et d’exécuter des extensions de barre latérale." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يضيف زر الامتدادات ويتيح لـ cmux تثبيت امتدادات الشريط الجانبي وتشغيلها." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Añade el botón de extensiones y permite que cmux instale y ejecute extensiones de barra lateral." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "加入擴充功能按鈕,並讓 cmux 可安裝及執行側邊欄擴充功能。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "添加扩展按钮,并允许 cmux 安装和运行侧边栏扩展。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "확장 기능 버튼을 추가하고 cmux가 사이드바 확장 기능을 설치하고 실행할 수 있게 합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "エクステンションボタンを追加し、cmuxでサイドバーエクステンションをインストールして実行できるようにします。" + } + } + } + }, "settings.betaFeatures.feed": { "extractionState": "manual", "localizations": { @@ -308835,6 +310959,65 @@ } } }, + "settings.betaFeatures.feed.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Adds Feed to the right sidebar for answering agent requests." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Fügt der rechten Seitenleiste den Feed zum Beantworten von Agentenanfragen hinzu." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ajoute le Flux à la barre latérale droite pour répondre aux demandes des agents." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يضيف Feed إلى الشريط الجانبي الأيمن للرد على طلبات الوكلاء." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Añade el Feed a la barra lateral derecha para responder a las solicitudes de los agentes." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在右側邊欄加入 Feed,用於回覆代理程式的請求。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在右侧边栏添加 Feed,用于回复代理请求。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "오른쪽 사이드바에 피드를 추가해 에이전트 요청에 응답할 수 있습니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "右サイドバーにフィードを追加し、エージェントからのリクエストに応答できるようにします。" + } + } + } + }, "settings.betaFeatures.remoteTmux": { "extractionState": "manual", "localizations": { @@ -308894,6 +311077,65 @@ } } }, + "settings.betaFeatures.remoteTmux.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Shows tmux sessions on remote hosts as workspaces in the sidebar. The sessions keep running after cmux quits." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Zeigt tmux-Sitzungen auf Remote-Hosts als Arbeitsbereiche in der Seitenleiste an. Die Sitzungen laufen auch nach dem Beenden von cmux weiter." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Affiche les sessions tmux des hôtes distants comme des espaces de travail dans la barre latérale. Les sessions continuent de s’exécuter après la fermeture de cmux." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يعرض جلسات tmux على المضيفين البعيدين كمساحات عمل في الشريط الجانبي. وتستمر الجلسات في العمل حتى بعد إنهاء cmux." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Muestra las sesiones de tmux de hosts remotos como espacios de trabajo en la barra lateral. Las sesiones siguen ejecutándose al salir de cmux." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在側邊欄中將遠端主機上的 tmux 工作階段顯示為工作區。結束 cmux 後,這些工作階段仍會繼續執行。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在侧边栏中将远程主机上的 tmux 会话显示为工作区。退出 cmux 后,这些会话仍会继续运行。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "원격 호스트의 tmux 세션을 사이드바에 작업 공간으로 표시합니다. cmux를 종료해도 세션은 계속 실행됩니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "リモートホストの tmux セッションをサイドバーにワークスペースとして表示します。cmux を終了してもセッションは動作し続けます。" + } + } + } + }, "settings.betaFeatures.warning": { "extractionState": "manual", "localizations": { @@ -309012,6 +311254,65 @@ } } }, + "settings.betaFeatures.workspaceTodoControls.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Shows Add Checklist Item and status controls on workspaces. A remote rollout can turn them on even while this is off." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Zeigt „Checklisten-Eintrag hinzufügen“ und Statussteuerelemente bei Arbeitsbereichen an. Ein Remote-Rollout kann sie auch dann aktivieren, wenn diese Option deaktiviert ist." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Affiche « Ajouter un élément à la liste de contrôle » et les commandes d’état sur les espaces de travail. Un déploiement à distance peut les activer même si cette option est désactivée." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يعرض «إضافة عنصر قائمة تحقق» وعناصر التحكم في الحالة على مساحات العمل. ويمكن لطرح عن بُعد تفعيلها حتى عند إيقاف هذا الخيار." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Muestra «Añadir elemento a la lista de verificación» y los controles de estado en los espacios de trabajo. Un despliegue remoto puede activarlos aunque esta opción esté desactivada." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在工作區上顯示「新增檢查清單項目」和狀態控制項。即使關閉此選項,遠端推出仍可能啟用這些控制項。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在工作区上显示“添加清单项”和状态控件。即使关闭此选项,远程推出仍可能启用这些控件。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간에 ‘체크리스트 항목 추가’ 및 상태 컨트롤을 표시합니다. 이 옵션이 꺼져 있어도 원격 출시로 이 컨트롤이 켜질 수 있습니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースに「チェックリスト項目を追加」とステータスコントロールを表示します。この設定がオフでも、リモートでの提供開始によって有効になることがあります。" + } + } + } + }, "settings.betaFeatures.workspaceTodosChecklistStyle": { "extractionState": "manual", "localizations": { @@ -309071,6 +311372,65 @@ } } }, + "settings.betaFeatures.workspaceTodosChecklistStyle.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Choose whether clicking a workspace checklist opens a popover or expands it under the row." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Wählen Sie, ob sich beim Klicken auf die Checkliste eines Arbeitsbereichs ein Popover öffnet oder die Checkliste unter der Zeile aufklappt." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Choisissez si un clic sur la liste de contrôle d’un espace de travail ouvre une fenêtre contextuelle ou la déplie sous la ligne." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "اختر ما إذا كان النقر على قائمة التحقق الخاصة بمساحة العمل يفتح نافذة منبثقة أو يوسّعها أسفل الصف." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Elige si al hacer clic en la lista de verificación de un espacio de trabajo se abre un popover o se despliega bajo la fila." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "選擇按一下工作區檢查清單時,要在彈出視窗中開啟,還是在該列下方展開。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "选择点按工作区清单时,是在浮层中打开,还是在该行下方展开。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "작업 공간 체크리스트를 클릭했을 때 팝오버로 열지, 행 아래에 펼칠지 선택합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースのチェックリストをクリックしたときに、ポップオーバーで開くか、行の下に展開するかを選択します。" + } + } + } + }, "settings.betaFeatures.workspaceTodosChecklistStyle.inline": { "extractionState": "manual", "localizations": { @@ -310351,6 +312711,65 @@ } } }, + "settings.browser.enabled.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Opens browser tabs and links from terminals in the cmux browser." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Browser-Tabs und Links aus Terminals werden im cmux-Browser geöffnet." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvre les onglets de navigateur et les liens des terminaux dans le navigateur cmux." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يفتح علامات تبويب المتصفح والروابط من الطرفيات في متصفح cmux." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abre pestañas del navegador y enlaces de las terminales en el navegador de cmux." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "瀏覽器標籤頁和從終端機開啟的連結都會在 cmux 瀏覽器中開啟。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "浏览器标签页和从终端打开的链接都会在 cmux 浏览器中打开。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "브라우저 탭과 터미널에서 연 링크를 cmux 브라우저에서 엽니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザタブとターミナルから開いたリンクをcmuxブラウザで開きます。" + } + } + } + }, "settings.browser.externalPatterns": { "extractionState": "manual", "localizations": { @@ -310648,6 +313067,65 @@ } } }, + "settings.browser.hiddenWebViewDiscard.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Frees memory from browser tabs hidden longer than the delay. They reload when shown again." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Browser-Tabs, die länger als die festgelegte Zeit ausgeblendet sind, geben ihren Speicher frei und werden beim erneuten Anzeigen neu geladen." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Libère la mémoire des onglets de navigateur masqués depuis plus longtemps que le délai défini. Ils se rechargent lorsqu’ils réapparaissent." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يحرر ذاكرة علامات تبويب المتصفح المخفية لمدة تتجاوز المهلة المحددة، ويُعاد تحميلها عند عرضها مجددًا." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Libera la memoria de las pestañas del navegador que llevan ocultas más tiempo del indicado. Se vuelven a cargar al mostrarse de nuevo." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "瀏覽器標籤頁隱藏超過延遲時間後,會釋放其記憶體。再次顯示時會重新載入。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "浏览器标签页隐藏超过延迟时间后,会释放其内存。再次显示时会重新加载。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "브라우저 탭이 지연 시간보다 오래 숨겨져 있으면 메모리를 해제합니다. 탭을 다시 표시하면 새로 고칩니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "非表示の時間が遅延時間を超えたブラウザタブのメモリを解放します。タブは再び表示したときに再読み込みされます。" + } + } + } + }, "settings.browser.hiddenWebViewDiscardDelay": { "extractionState": "manual", "localizations": { @@ -317885,6 +320363,65 @@ } } }, + "settings.browser.theme.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Choose light or dark pages for sites that support both. System matches the app appearance." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Wählen Sie helle oder dunkle Seiten für Websites, die beides unterstützen. „System“ folgt dem Erscheinungsbild der App." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Choisissez des pages claires ou sombres pour les sites qui prennent en charge les deux. « Système » suit l’apparence de l’app." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "اختر الصفحات الفاتحة أو الداكنة للمواقع التي تدعم الوضعين. يتبع خيار «النظام» مظهر التطبيق." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Elige páginas claras u oscuras para los sitios compatibles con ambos modos. «Sistema» sigue el aspecto de la app." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "網站同時支援淺色與深色時,選擇要使用淺色或深色頁面。「系統」會跟隨 App 的外觀。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "网站同时支持浅色和深色时,选择使用浅色还是深色页面。“系统”会跟随应用外观。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "라이트와 다크를 모두 지원하는 사이트에서 표시할 페이지 모드를 선택합니다. ‘시스템’은 앱의 외관 설정을 따릅니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ライトとダークの両方に対応したサイトで、どちらのページを表示するかを選択します。「システム」はアプリの外観に合わせます。" + } + } + } + }, "settings.cloudMachines.panel.open": { "extractionState": "manual", "localizations": { @@ -320371,6 +322908,65 @@ } } }, + "settings.customSidebars.enabled.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Adds sidebars from ~/.config/cmux/sidebars to the sidebar picker." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Fügt Seitenleisten aus ~/.config/cmux/sidebars zur Seitenleistenauswahl hinzu." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ajoute les barres latérales de ~/.config/cmux/sidebars au sélecteur de barre latérale." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يضيف الأشرطة الجانبية من ~/.config/cmux/sidebars إلى منتقي الشريط الجانبي." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Añade las barras laterales de ~/.config/cmux/sidebars al selector de barra lateral." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在側邊欄選取器中加入 ~/.config/cmux/sidebars 裡的側邊欄。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在侧边栏选择器中添加 ~/.config/cmux/sidebars 里的侧边栏。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "~/.config/cmux/sidebars의 사이드바를 사이드바 선택기에 추가합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "~/.config/cmux/sidebars のサイドバーをサイドバーピッカーに追加します。" + } + } + } + }, "settings.customSidebars.note": { "extractionState": "manual", "localizations": { @@ -320489,6 +323085,65 @@ } } }, + "settings.customSidebars.renderer.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Isolated process protects cmux from a faulty sidebar but accepts clicks only. In-app also accepts hover, focus, and typing." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "„Isolierter Prozess“ schützt cmux vor einer fehlerhaften Seitenleiste, unterstützt aber nur Klicks. „In-App“ unterstützt zusätzlich Hover, Fokus und Texteingabe." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "« Processus isolé » protège cmux d’une barre latérale défectueuse, mais n’accepte que les clics. « Dans l’application » accepte aussi le survol, le focus et la saisie." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يحمي خيار «عملية معزولة» cmux من الشريط الجانبي المعيب، لكنه لا يقبل سوى النقرات. أما خيار «داخل التطبيق» فيقبل أيضًا تمرير المؤشر والتركيز والكتابة." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "«Proceso aislado» protege cmux de una barra lateral defectuosa, pero solo admite clics. «En la aplicación» también admite pasar el puntero por encima, enfocar y escribir." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "「隔離程序」可保護 cmux 不受有問題的側邊欄影響,但只接受點擊。「應用程式內」還接受游標停留、聚焦和輸入。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "“隔离进程”可保护 cmux 不受有问题的侧边栏影响,但只接受点击。“应用内”还接受悬停、聚焦和输入。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "‘격리된 프로세스’는 결함이 있는 사이드바로부터 cmux를 보호하지만 클릭만 허용합니다. ‘앱 내부’는 포인터 올려놓기, 포커스, 키보드 입력도 허용합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "「分離プロセス」は問題のあるサイドバーから cmux を保護しますが、クリックしか受け付けません。「アプリ内」では、ホバー、フォーカス、入力も受け付けます。" + } + } + } + }, "settings.error.alert.dismiss": { "localizations": { "en": { @@ -320681,6 +323336,65 @@ } } }, + "settings.globalHotkey.enable.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Pressing the shortcut in any app shows or hides all cmux windows." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Das Tastaturkürzel blendet in jeder App alle cmux-Fenster ein oder aus." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Le raccourci affiche ou masque toutes les fenêtres cmux, quelle que soit l’app active." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يُظهر الاختصار جميع نوافذ cmux أو يخفيها من أي تطبيق." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "El atajo muestra u oculta todas las ventanas de cmux desde cualquier app." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在任何 App 中按下快速鍵,即可顯示或隱藏所有 cmux 視窗。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在任意 App 中按下快捷键,即可显示或隐藏所有 cmux 窗口。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "어느 앱에서든 단축키를 눌러 모든 cmux 윈도우를 표시하거나 숨길 수 있습니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "どのアプリを使用中でも、ショートカットを押すと cmux のすべてのウィンドウを表示または非表示にできます。" + } + } + } + }, "settings.globalHotkey.note": { "extractionState": "manual", "localizations": { @@ -322464,6 +325178,65 @@ } } }, + "settings.mobile.artifactFolderAccess.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Choose how much of a folder iPhone and iPad can browse. One Level shows only the items directly inside it." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Wählen Sie, wie tief iPhone und iPad einen Ordner durchsuchen können. „Eine Ebene“ zeigt nur die Objekte direkt darin." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Choisissez jusqu’où l’iPhone et l’iPad peuvent parcourir un dossier. « Un niveau » n’affiche que les éléments qui s’y trouvent directement." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "اختر مدى عمق تصفح iPhone وiPad داخل المجلد. يعرض خيار «مستوى واحد» العناصر الموجودة مباشرةً داخله فقط." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Elige hasta qué nivel pueden explorar una carpeta el iPhone y el iPad. «Un nivel» solo muestra los elementos que contiene directamente." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "選擇 iPhone 和 iPad 可瀏覽資料夾的範圍。「單一層級」只顯示該資料夾第一層的項目。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "选择 iPhone 和 iPad 可浏览文件夹的范围。“一层”只显示该文件夹第一层的项目。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "iPhone 및 iPad에서 폴더를 어디까지 탐색할 수 있는지 선택합니다. ‘한 단계’는 폴더 바로 아래에 있는 항목만 표시합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "iPhone と iPad からフォルダをどこまで参照できるかを選択します。「1 階層」では、そのフォルダの直下にある項目のみが表示されます。" + } + } + } + }, "settings.mobile.artifactFolderAccess.oneLevel": { "extractionState": "manual", "localizations": { @@ -322936,6 +325709,65 @@ } } }, + "settings.mobile.iOSPairingHost.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Lets iPhone and iPad pair with and connect to this Mac." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "iPhone und iPad können sich mit diesem Mac koppeln und verbinden." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "L’iPhone et l’iPad peuvent se jumeler avec ce Mac et s’y connecter." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يمكن لـ iPhone وiPad الاقتران بجهاز Mac هذا والاتصال به." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "El iPhone y el iPad pueden emparejarse con este Mac y conectarse a él." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "允許 iPhone 和 iPad 與此 Mac 配對及連線。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "允许 iPhone 和 iPad 与这台 Mac 配对并连接。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "iPhone과 iPad가 이 Mac과 페어링하고 연결할 수 있게 합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "iPhone と iPad がこの Mac とペアリングして接続できるようにします。" + } + } + } + }, "settings.mobile.managedByOrganization": { "extractionState": "manual", "localizations": { @@ -323231,6 +326063,65 @@ } } }, + "settings.mobile.phonePush.forwarding.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sends agent notifications from this Mac to cmux on iPhone and iPad." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Leitet Agentenbenachrichtigungen von diesem Mac an cmux auf iPhone und iPad weiter." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Transfère les notifications des agents de ce Mac vers cmux sur iPhone et iPad." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يوجّه إشعارات الوكلاء من جهاز Mac هذا إلى cmux على iPhone وiPad." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Reenvía las notificaciones de agentes de este Mac a cmux en iPhone y iPad." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "將此 Mac 上的代理程式通知傳送至 iPhone 和 iPad 上的 cmux。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "将这台 Mac 上的代理通知发送到 iPhone 和 iPad 上的 cmux。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "이 Mac의 에이전트 알림을 iPhone 및 iPad의 cmux로 보냅니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "この Mac からエージェントの通知を iPhone と iPad の cmux に送信します。" + } + } + } + }, "settings.mobile.phonePush.hideContent": { "extractionState": "manual", "localizations": { @@ -340352,65 +343243,6 @@ } } }, - "settings.notifications.sound.preview": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Preview Sound" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Ton anhören" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Écouter le son" - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "معاينة الصوت" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Escuchar sonido" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "試聽音效" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "试听声音" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "소리 미리 듣기" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "サウンドをプレビュー" - } - } - } - }, "settings.notifications.sound.subtitle": { "extractionState": "manual", "localizations": { @@ -359004,6 +361836,65 @@ } } }, + "settings.shortcuts.showModifierHoldHints.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Holding Command or Control shows shortcut hints." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Hinweise zu Tastaturkürzeln erscheinen, solange die Befehlstaste oder die Control-Taste gedrückt ist." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Les indices de raccourcis apparaissent tant que la touche Commande ou Contrôle est maintenue enfoncée." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تظهر تلميحات الاختصارات عند الضغط مع الاستمرار على Command أو Control." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Las sugerencias de atajos aparecen mientras se mantiene presionada la tecla Comando o Control." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "按住 Command 或 Control 鍵時,會顯示快速鍵提示。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "按住 Command 或 Control 键时,会显示快捷键提示。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "Command 또는 Control 키를 누르고 있으면 단축키 힌트가 표시됩니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Command キーまたは Control キーを押したままにすると、ショートカットヒントが表示されます。" + } + } + } + }, "settings.sidebar.rightMaxWidth": { "extractionState": "manual", "localizations": { @@ -359129,6 +362020,65 @@ } } }, + "settings.sidebar.rightMaxWidth.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Lets the Dock in the right sidebar grow up to this width while leaving room for terminals." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Das Dock in der rechten Seitenleiste kann bis zu dieser Breite wachsen, lässt aber Platz für Terminals." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Le Dock de la barre latérale droite peut s’élargir jusqu’à cette largeur tout en laissant de la place aux terminaux." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يمكن أن يتسع Dock في الشريط الجانبي الأيمن حتى هذا العرض مع ترك مساحة للطرفيات." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "El Dock de la barra lateral derecha puede ampliarse hasta este ancho, pero deja espacio para las terminales." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "右側邊欄中的 Dock 最寬可擴展至此寬度,並為終端機保留空間。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "右侧边栏中的 Dock 最宽可扩展到此宽度,同时为终端保留空间。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "오른쪽 사이드바의 Dock은 터미널 공간을 남겨 둔 채 이 너비까지 넓어질 수 있습니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "右サイドバーの Dock は、ターミナルの領域を残したまま、この幅まで広げられます。" + } + } + } + }, "settings.sidebar.rightMaxWidth.toggle": { "extractionState": "manual", "localizations": { @@ -361950,6 +364900,65 @@ } } }, + "settings.terminal.adaptiveDefaultTheme.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Matches terminal colors to the light or dark appearance when no Ghostty theme or colors are set." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Passt die Terminalfarben an das helle oder dunkle Erscheinungsbild an, wenn in Ghostty kein Theme und keine Farben festgelegt sind." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Si aucun thème ni aucune couleur Ghostty n’est défini, les couleurs du terminal suivent l’apparence claire ou sombre." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عندما لا تكون هناك سمة أو ألوان محددة في Ghostty، تتوافق ألوان الطرفية مع المظهر الفاتح أو الداكن." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Si no hay ningún tema ni colores de Ghostty definidos, los colores de la terminal siguen la apariencia clara u oscura." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "未設定 Ghostty 主題或顏色時,終端機顏色會配合淺色或深色外觀。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "未设置 Ghostty 主题或颜色时,终端颜色会随浅色或深色外观变化。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "Ghostty 테마나 색상이 설정되어 있지 않으면 터미널 색상을 라이트 또는 다크 모드에 맞춥니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Ghostty のテーマや色が設定されていない場合、ターミナルの色をライトまたはダークの外観に合わせます。" + } + } + } + }, "settings.terminal.agentAutoResume": { "extractionState": "manual", "localizations": { @@ -362009,6 +365018,65 @@ } } }, + "settings.terminal.agentAutoResume.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reopening cmux resumes agent sessions automatically." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Beim erneuten Öffnen von cmux werden Agenten-Sitzungen automatisch fortgesetzt." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Les sessions d’agent reprennent automatiquement à la réouverture de cmux." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تُستأنف جلسات الوكلاء تلقائيًا عند إعادة فتح cmux." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Las sesiones de agentes se reanudan automáticamente al volver a abrir cmux." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "重新開啟 cmux 時,自動繼續先前的代理工作階段。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "重新打开 cmux 时,自动恢复之前的代理会话。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "cmux를 다시 열면 에이전트 세션이 자동으로 재개됩니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux を開き直すと、エージェントのセッションが自動的に再開されます。" + } + } + } + }, "settings.terminal.agentHibernation": { "extractionState": "manual", "localizations": { @@ -362304,6 +365372,65 @@ } } }, + "settings.terminal.agentHibernation.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Hibernates idle background agent terminals above the live terminal limit. Even when this is off, cmux may hibernate them under critical memory pressure." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Versetzt inaktive Agenten-Terminals im Hintergrund in den Ruhezustand, sobald das Limit für aktive Terminals überschritten ist. Auch wenn diese Option deaktiviert ist, kann cmux sie bei kritischer Speicherauslastung in den Ruhezustand versetzen." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Met en hibernation les terminaux d’agent inactifs en arrière-plan au-delà de la limite de terminaux actifs. Même si cette option est désactivée, cmux peut les mettre en hibernation en cas de pression mémoire critique." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يُدخل طرفيات الوكلاء الخاملة في الخلفية في السبات عند تجاوز حد الطرفيات النشطة. وحتى عند إيقاف هذا الخيار، قد يُدخلها cmux في السبات عند ضغط الذاكرة الحرج." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Pone en hibernación las terminales de agentes inactivas en segundo plano que superan el límite de terminales activas. Aunque esta opción esté desactivada, cmux puede ponerlas en hibernación si la presión de memoria es crítica." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "作用中終端機超過上限時,閒置的背景代理終端機會進入休眠。即使關閉此選項,cmux 仍可能在記憶體壓力達到嚴重程度時讓它們休眠。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "活动终端超过上限时,空闲的后台代理终端会进入休眠。即使关闭此选项,cmux 仍可能在内存压力达到严重程度时让它们休眠。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "활성 터미널 수가 한도를 넘으면 백그라운드에서 유휴 상태인 에이전트 터미널을 최대 절전 모드로 전환합니다. 이 옵션을 꺼도 메모리 압박이 심각하면 cmux가 해당 터미널을 최대 절전 모드로 전환할 수 있습니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "実行中のターミナル数が上限を超えると、アイドル状態のバックグラウンドエージェントのターミナルを休止させます。この設定がオフでも、メモリ逼迫が深刻な場合は cmux がこれらを休止させることがあります。" + } + } + } + }, "settings.terminal.copyOnSelect": { "extractionState": "manual", "localizations": { @@ -362429,6 +365556,65 @@ } } }, + "settings.terminal.copyOnSelect.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Selecting text in a terminal copies it to the clipboard." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Im Terminal ausgewählter Text wird in die Zwischenablage kopiert." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Le texte sélectionné dans un terminal est copié dans le presse-papiers." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يُنسخ النص المحدد في الطرفية إلى الحافظة." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "El texto seleccionado en una terminal se copia al portapapeles." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在終端機中選取文字時,會將文字複製到剪貼簿。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在终端中选择文本时,会将文本复制到剪贴板。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "터미널에서 텍스트를 선택하면 클립보드에 복사됩니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ターミナルでテキストを選択すると、クリップボードにコピーされます。" + } + } + } + }, "settings.terminal.reflowHardWrapOnCopy": { "extractionState": "manual", "localizations": { @@ -362488,6 +365674,242 @@ } } }, + "settings.terminal.reflowHardWrapOnCopy.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Copying text also joins line breaks a program printed at the full terminal width and drops a short continuation indent. Soft-wrapped lines always copy as one line." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Beim Kopieren werden auch Zeilenumbrüche zusammengefügt, die ein Programm bei voller Terminalbreite ausgegeben hat, und ein kurzer Fortsetzungseinzug wird entfernt. Weich umbrochene Zeilen werden immer als eine Zeile kopiert." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Lors de la copie, les sauts de ligne qu’un programme a insérés à pleine largeur du terminal sont aussi supprimés, ainsi qu’un court retrait de continuation. Les lignes à retour automatique sont toujours copiées sur une seule ligne." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عند النسخ، تُدمج أيضًا فواصل الأسطر التي طبعها برنامج عند العرض الكامل للطرفية، وتُحذف مسافة البادئة القصيرة للسطر التالي. أما الأسطر الملتفّة تلقائيًا فتُنسخ دائمًا كسطر واحد." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Al copiar, también se unen los saltos de línea que un programa imprimió al ancho completo de la terminal y se quita una sangría de continuación corta. Las líneas con ajuste automático siempre se copian como una sola línea." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "複製文字時,也會合併程式在終端機滿寬處輸出的換行,並移除較短的續行縮排。自動換行的行一律會複製為一行。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "复制文本时,也会合并程序在终端满宽处输出的换行,并移除较短的续行缩进。自动换行的行始终会复制为一行。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "텍스트를 복사할 때 프로그램이 터미널 전체 너비에서 넣은 줄바꿈도 합치고 짧은 연속 들여쓰기를 제거합니다. 자동 줄바꿈된 줄은 항상 한 줄로 복사됩니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "テキストをコピーするとき、プログラムがターミナルの幅いっぱいで出力した改行もつなげ、短い継続インデントを取り除きます。ソフトラップされた行は常に 1 行としてコピーされます。" + } + } + } + }, + "settings.terminal.importFromTerminal": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Import from Another Terminal" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "別のターミナルから読み込む" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "从其他终端导入" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Aus anderem Terminal importieren" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Importer depuis un autre terminal" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "استيراد من طرفية أخرى" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Importar desde otra terminal" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "從其他終端機匯入" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "다른 터미널에서 가져오기" + } + } + } + }, + "settings.terminal.importFromTerminal.button": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Import…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "読み込む…" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "导入…" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Importieren…" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Importer…" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "استيراد…" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Importar…" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "匯入…" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "가져오기…" + } + } + } + }, + "settings.terminal.importFromTerminal.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Bring over your font, colors, cursor and more from iTerm2, Terminal, Alacritty, Kitty, WezTerm or Warp." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "iTerm2、ターミナル、Alacritty、Kitty、WezTerm、Warp からフォント、カラー、カーソルなどを引き継ぎます。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "从 iTerm2、终端、Alacritty、Kitty、WezTerm 或 Warp 导入字体、颜色、光标等设置。" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Übernimmt Schrift, Farben, Cursor und mehr aus iTerm2, Terminal, Alacritty, Kitty, WezTerm oder Warp." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Reprend la police, les couleurs, le curseur et plus encore depuis iTerm2, Terminal, Alacritty, Kitty, WezTerm ou Warp." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "انقل الخط والألوان والمؤشر وغيرها من iTerm2 أو Terminal أو Alacritty أو Kitty أو WezTerm أو Warp." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Trae la fuente, los colores, el cursor y más desde iTerm2, Terminal, Alacritty, Kitty, WezTerm o Warp." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "從 iTerm2、終端機、Alacritty、Kitty、WezTerm 或 Warp 匯入字體、顏色、游標等設定。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "iTerm2, 터미널, Alacritty, Kitty, WezTerm, Warp에서 글꼴, 색상, 커서 등을 가져옵니다." + } + } + } + }, "settings.terminal.memoryGuardrail": { "extractionState": "manual", "localizations": { @@ -362547,6 +365969,65 @@ } } }, + "settings.terminal.memoryGuardrail.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Warns when the processes in one pane use too much memory." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Warnt, wenn die Prozesse in einem Teilfenster zu viel Speicher verbrauchen." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Avertit lorsque les processus d’un volet utilisent trop de mémoire." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "ينبّه عندما تستهلك العمليات في جزء واحد ذاكرة أكثر من اللازم." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Avisa cuando los procesos de un panel usan demasiada memoria." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "單一窗格中的處理程序使用過多記憶體時發出警告。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "某个窗格中的进程占用过多内存时发出警告。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "한 분할 창의 프로세스가 메모리를 너무 많이 사용하면 경고합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "1 つのペインのプロセスがメモリを使いすぎると警告します。" + } + } + } + }, "settings.terminal.memoryGuardrail.threshold": { "extractionState": "manual", "localizations": { @@ -362960,6 +366441,65 @@ } } }, + "settings.terminal.rendererRealization.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Frees graphics memory from terminals that are out of view. Their processes keep running." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Gibt den Grafikspeicher nicht sichtbarer Terminals frei. Ihre Prozesse laufen weiter." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Libère la mémoire graphique des terminaux hors de vue. Leurs processus continuent de s’exécuter." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يحرر ذاكرة الرسومات للطرفيات غير الظاهرة، مع استمرار تشغيل عملياتها." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Libera la memoria gráfica de las terminales fuera de la vista. Sus procesos siguen ejecutándose." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "釋放不在畫面上的終端機所使用的圖形記憶體,其處理程序仍會繼續執行。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "释放不在屏幕上的终端所占用的图形内存,其进程仍会继续运行。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "화면에 보이지 않는 터미널의 그래픽 메모리를 해제합니다. 해당 프로세스는 계속 실행됩니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "表示されていないターミナルのグラフィックスメモリを解放します。プロセスは動作し続けます。" + } + } + } + }, "settings.terminal.resumeCommands": { "extractionState": "manual", "localizations": { @@ -364199,6 +367739,65 @@ } } }, + "settings.terminal.scrollBar.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Shows a scroll bar in terminals, except in full-screen apps." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Zeigt in Terminals eine Bildlaufleiste an, außer in Vollbild-Apps." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Affiche une barre de défilement dans les terminaux, sauf dans les apps en plein écran." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يعرض شريط تمرير في الطرفيات، إلا في التطبيقات بملء الشاشة." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Muestra una barra de desplazamiento en las terminales, excepto en las apps a pantalla completa." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在終端機中顯示捲軸(全螢幕 App 除外)。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在终端中显示滚动条(全屏 App 除外)。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "터미널에 스크롤 바를 표시합니다(전체 화면 앱 제외)." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ターミナルにスクロールバーを表示します(フルスクリーンアプリを除く)。" + } + } + } + }, "settings.terminal.scrollSpeed": { "extractionState": "manual", "localizations": { @@ -364747,6 +368346,65 @@ } } }, + "settings.terminal.sessionContentWidth.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Wraps terminal and agent chat content at this width. Narrow panes still use their full width." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Terminal- und Agenten-Chat-Inhalte werden bei dieser Breite umbrochen. Schmalere Teilfenster nutzen weiterhin ihre volle Breite." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Le contenu du terminal et du chat de l’agent revient à la ligne à cette largeur. Les volets plus étroits conservent toute leur largeur." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يلتف محتوى الطرفية ودردشة الوكيل عند هذا العرض. أما الأجزاء الأضيق فتستخدم عرضها الكامل." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "El contenido de la terminal y del chat de agentes se ajusta a este ancho. Los paneles más estrechos siguen usando todo su ancho." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "終端機和代理對話的內容會在此寬度換行。較窄的窗格仍使用完整寬度。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "终端和代理对话的内容会在此宽度处换行。较窄的窗格仍使用全部宽度。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "터미널과 에이전트 채팅 내용을 이 너비에서 줄바꿈합니다. 이보다 좁은 분할 창에서는 전체 너비를 사용합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ターミナルとエージェントチャットの内容をこの幅で折り返します。これより狭いペインでは、ペインの幅いっぱいに表示します。" + } + } + } + }, "settings.terminal.sessionContentWidth.toggle": { "extractionState": "manual", "localizations": { @@ -365385,6 +369043,65 @@ } } }, + "settings.terminal.textEditingGestures.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Pressing Command or Option with an arrow key or Delete moves or deletes by line or word. Full-screen terminal apps receive the rewritten keys too, so turn this off for apps that need the original keys." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Mit der Befehls- oder Wahltaste und einer Pfeiltaste oder der Löschtaste bewegen Sie die Einfügemarke bzw. löschen Text zeilen- oder wortweise. Auch Terminal-Apps im Vollbildmodus erhalten die umgeschriebenen Tasten. Deaktivieren Sie diese Option für Apps, die die ursprünglichen Tasten benötigen." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Commande ou Option associée à une touche fléchée ou à Supprimer déplace le point d’insertion ou supprime du texte par ligne ou par mot. Les apps de terminal en plein écran reçoivent aussi les touches réécrites : désactivez cette option pour les apps qui ont besoin des touches d’origine." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يؤدي الضغط على Command أو Option مع مفتاح سهم أو مفتاح الحذف إلى التنقل أو الحذف سطرًا سطرًا أو كلمةً كلمة. وتتلقى تطبيقات الطرفية بملء الشاشة أيضًا المفاتيح المعاد كتابتها، لذا أوقف هذا الخيار للتطبيقات التي تحتاج إلى المفاتيح الأصلية." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Comando u Opción junto con una tecla de flecha o Eliminar mueve el punto de inserción o borra por líneas o palabras. Las apps de terminal a pantalla completa también reciben las teclas reescritas, así que desactiva esta opción para las apps que necesitan las teclas originales." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "按住 Command 或 Option 鍵再按方向鍵或 Delete 鍵,可按行或字詞移動或刪除。全螢幕終端機 App 也會收到改寫後的按鍵,因此若 App 需要原始按鍵,請關閉此選項。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "按住 Command 或 Option 键再按箭头键或 Delete 键,可按行或按词移动或删除。全屏终端 App 也会收到改写后的按键,因此如果 App 需要原始按键,请关闭此选项。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "Command 또는 Option 키를 누른 채 화살표 키나 Delete 키를 누르면 줄 또는 단어 단위로 이동하거나 삭제합니다. 전체 화면 터미널 앱에도 변환된 키가 전달되므로, 원래 키가 필요한 앱에서는 이 옵션을 끄세요." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Command キーまたは Option キーを押しながら矢印キーや Delete キーを押すと、行単位または単語単位で移動・削除します。フルスクリーンのターミナルアプリにも書き換えたキーが送られるため、元のキーが必要なアプリではこの設定をオフにしてください。" + } + } + } + }, "settings.textBox.betaWarning": { "extractionState": "manual", "localizations": { @@ -365753,6 +369470,65 @@ } } }, + "settings.textBox.focusOnNewTerminals.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Opening a terminal tab, split, or workspace puts keyboard focus in the TextBox." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Wenn ein Terminal-Tab, eine Teilung oder ein Arbeitsbereich geöffnet wird, liegt der Tastaturfokus in der TextBox." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "À l’ouverture d’un onglet, d’une division ou d’un espace de travail de terminal, le focus du clavier est placé dans la TextBox." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عند فتح علامة تبويب طرفية أو تقسيم أو مساحة عمل، ينتقل تركيز لوحة المفاتيح إلى TextBox." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Al abrir una pestaña de terminal, una división o un espacio de trabajo, el enfoque del teclado se sitúa en el TextBox." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "開啟終端機標籤頁、分割或工作區時,將鍵盤焦點移到 TextBox。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "打开终端标签页、分屏或工作区时,将键盘焦点移到 TextBox。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "터미널 탭, 분할 또는 작업 공간을 열면 키보드 포커스가 TextBox로 이동합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ターミナルタブ、分割、またはワークスペースを開くと、キーボードフォーカスが TextBox に移動します。" + } + } + } + }, "settings.textBox.maxLines": { "extractionState": "manual", "localizations": { @@ -366128,6 +369904,65 @@ } } }, + "settings.textBox.showOnNewTerminals.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Opening a terminal tab, split, or workspace shows the TextBox." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Beim Öffnen eines Terminal-Tabs, einer Teilung oder eines Arbeitsbereichs erscheint die TextBox." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "À l’ouverture d’un onglet, d’une division ou d’un espace de travail de terminal, la TextBox s’affiche." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "عند فتح علامة تبويب طرفية أو تقسيم أو مساحة عمل، يظهر TextBox." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Al abrir una pestaña de terminal, una división o un espacio de trabajo, se muestra el TextBox." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "開啟終端機標籤頁、分割或工作區時顯示 TextBox。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "打开终端标签页、分屏或工作区时显示 TextBox。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "터미널 탭, 분할 또는 작업 공간을 열면 TextBox가 표시됩니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ターミナルタブ、分割、またはワークスペースを開くと、TextBox が表示されます。" + } + } + } + }, "settings.textBox.submitAction.claude": { "extractionState": "manual", "localizations": { @@ -465919,6 +469754,301 @@ } } }, + "workspaceGroup.error.customColorIsNotAnInputKey": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "unknown key custom_color; set_color accepts hex or color" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "不明なキー custom_color です。set_color は hex または color を受け付けます" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "未知的键 custom_color;set_color 接受 hex 或 color" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Unbekannter Schlüssel custom_color; set_color akzeptiert hex oder color" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Clé inconnue custom_color ; set_color accepte hex ou color" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مفتاح غير معروف custom_color؛ يقبل set_color المفتاح hex أو color" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Clave desconocida custom_color; set_color acepta hex o color" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "未知的鍵 custom_color;set_color 接受 hex 或 color" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "알 수 없는 키 custom_color입니다. set_color는 hex 또는 color를 받습니다" + } + } + } + }, + "workspaceGroup.error.colorMustBeHex": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "color must be a 6-digit hex color like #FF3EA5 (leading # optional)" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "color は #FF3EA5 のような 6 桁の 16 進カラーである必要があります(先頭の # は省略可)" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "color 必须是 6 位十六进制颜色,例如 #FF3EA5(开头的 # 可省略)" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "color muss eine 6-stellige Hex-Farbe wie #FF3EA5 sein (führendes # optional)" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "color doit être une couleur hexadécimale à 6 chiffres comme #FF3EA5 (# initial facultatif)" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يجب أن تكون قيمة color لونًا سداسيًا عشريًا من 6 خانات مثل #FF3EA5 (الرمز # في البداية اختياري)" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "color debe ser un color hexadecimal de 6 dígitos como #FF3EA5 (el # inicial es opcional)" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "color 必須是 6 位十六進位顏色,例如 #FF3EA5(開頭的 # 可省略)" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "color는 #FF3EA5 같은 6자리 16진수 색상이어야 합니다(앞의 #는 생략 가능)" + } + } + } + }, + "workspaceGroup.error.colorMustBeString": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "color must be a string holding a hex color" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "color は 16 進カラーを含む文字列である必要があります" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "color 必须是包含十六进制颜色的字符串" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "color muss ein String mit einer Hex-Farbe sein" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "color doit être une chaîne contenant une couleur hexadécimale" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يجب أن تكون قيمة color سلسلة نصية تحتوي على لون سداسي عشري" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "color debe ser una cadena de texto con un color hexadecimal" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "color 必須是包含十六進位顏色的字串" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "color는 16진수 색상을 담은 문자열이어야 합니다" + } + } + } + }, + "workspaceGroup.error.iconSymbolIsNotAnInputKey": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "unknown key icon_symbol; set_icon accepts symbol or icon" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "不明なキー icon_symbol です。set_icon は symbol または icon を受け付けます" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "未知的键 icon_symbol;set_icon 接受 symbol 或 icon" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Unbekannter Schlüssel icon_symbol; set_icon akzeptiert symbol oder icon" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Clé inconnue icon_symbol ; set_icon accepte symbol ou icon" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "مفتاح غير معروف icon_symbol؛ يقبل set_icon المفتاح symbol أو icon" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Clave desconocida icon_symbol; set_icon acepta symbol o icon" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "未知的鍵 icon_symbol;set_icon 接受 symbol 或 icon" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "알 수 없는 키 icon_symbol입니다. set_icon은 symbol 또는 icon을 받습니다" + } + } + } + }, + "workspaceGroup.error.symbolMustBeString": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "symbol must be a string" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "symbol は文字列である必要があります" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "symbol 必须是字符串" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "symbol muss ein String sein" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "symbol doit être une chaîne de caractères" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يجب أن تكون قيمة symbol سلسلة نصية" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "symbol debe ser una cadena de texto" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "symbol 必須是字串" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "symbol은 문자열이어야 합니다" + } + } + } + }, "workspaceGroup.error.idempotencyKeyMustBeString": { "extractionState": "manual", "localizations": { @@ -545305,6 +549435,65 @@ } } }, + "settings.betaFeatures.predictedEcho.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Shows typed characters right away on slow remote connections. They stay underlined until the remote host confirms them. Password prompts and full-screen apps are excluded." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Getippte Zeichen erscheinen bei langsamen Remoteverbindungen sofort. Sie bleiben unterstrichen, bis der Remote-Host sie bestätigt. Ausgenommen sind Passwortabfragen und Vollbild-Apps." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Les caractères saisis s’affichent immédiatement sur les connexions distantes lentes. Ils restent soulignés jusqu’à ce que l’hôte distant les confirme. Cela ne s’applique ni aux invites de mot de passe ni aux apps en plein écran." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تظهر الأحرف المكتوبة فورًا في الاتصالات البعيدة البطيئة، وتبقى مسطّرة حتى يؤكدها المضيف البعيد. ولا ينطبق ذلك على مطالبات كلمة المرور والتطبيقات بملء الشاشة." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Los caracteres escritos aparecen al instante en conexiones remotas lentas y permanecen subrayados hasta que el host remoto los confirma. No se aplica a solicitudes de contraseña ni a apps a pantalla completa." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "遠端連線較慢時,輸入的字元會立即顯示並加上底線,直到遠端主機確認為止。密碼提示和全螢幕 App 不適用此功能。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "远程连接较慢时,输入的字符会立即显示并带有下划线,直到远程主机确认为止。密码提示和全屏 App 不适用此功能。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "원격 연결이 느릴 때 입력한 문자를 바로 표시하고, 원격 호스트가 확인할 때까지 밑줄로 표시합니다. 비밀번호 프롬프트와 전체 화면 앱에는 적용되지 않습니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "リモート接続が遅いとき、入力した文字をすぐに表示し、リモートホストが確認するまで下線を付けます。パスワードの入力やフルスクリーンアプリには適用されません。" + } + } + } + }, "fileExplorer.error.cloudPreviewTooLarge": { "extractionState": "manual", "localizations": { @@ -546761,297 +550950,61 @@ } } }, - "terminal.passwordInput.indicator": { + "settings.app.equalizeSplitsOnCreate.subtitle": { "extractionState": "manual", "localizations": { - "ar": { - "stringUnit": { - "state": "translated", - "value": "إدخال كلمة المرور" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Passworteingabe" - } - }, "en": { "stringUnit": { "state": "translated", - "value": "Password input" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Entrada de contraseña" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Saisie du mot de passe" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "パスワード入力" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "비밀번호 입력" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "密码输入" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "密碼輸入" - } - } - } - }, - "settings.terminal.showPasswordInputIndicator": { - "extractionState": "manual", - "localizations": { - "ar": { - "stringUnit": { - "state": "translated", - "value": "مؤشر إدخال كلمة المرور" + "value": "Splitting a pane makes the panes in that direction equal in size." } }, "de": { "stringUnit": { "state": "translated", - "value": "Anzeige für Passworteingabe" - } - }, - "en": { - "stringUnit": { - "state": "translated", - "value": "Password Input Indicator" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Indicador de entrada de contraseña" + "value": "Wird ein Teilfenster geteilt, werden alle Teilfenster in dieser Richtung gleich groß." } }, "fr": { "stringUnit": { "state": "translated", - "value": "Indicateur de saisie du mot de passe" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "パスワード入力インジケーター" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "비밀번호 입력 표시" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "密码输入指示器" + "value": "Lorsqu’un volet est divisé, les volets dans cette direction prennent la même taille." } }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "密碼輸入指示器" - } - } - } - }, - "settings.terminal.showPasswordInputDots": { - "extractionState": "manual", - "localizations": { "ar": { "stringUnit": { "state": "translated", - "value": "إظهار نقاط للأحرف المكتوبة" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Punkte für eingegebene Zeichen anzeigen" - } - }, - "en": { - "stringUnit": { - "state": "translated", - "value": "Show Typed Password Dots" + "value": "عند تقسيم جزء، تصبح الأجزاء في ذلك الاتجاه متساوية الحجم." } }, "es": { "stringUnit": { "state": "translated", - "value": "Mostrar puntos por los caracteres escritos" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Afficher des points pour les caractères saisis" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "入力したパスワードのドットを表示" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "입력한 비밀번호 점 표시" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "显示已输入的密码圆点" + "value": "Al dividir un panel, los paneles en esa dirección se ajustan al mismo tamaño." } }, "zh-Hant": { "stringUnit": { "state": "translated", - "value": "顯示已輸入的密碼圓點" - } - } - } - }, - "settings.search.alias.setting.terminal.password-input-indicator": { - "extractionState": "manual", - "localizations": { - "ar": { - "stringUnit": { - "state": "translated", - "value": "terminal.showPasswordInputIndicator password input indicator secure input echo off lock badge sudo ssh passwd gpg prompt كلمة المرور إدخال مؤشر قفل" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "terminal.showPasswordInputIndicator password input indicator secure input echo off lock badge sudo ssh passwd gpg prompt passwort eingabe anzeige schloss" - } - }, - "en": { - "stringUnit": { - "state": "translated", - "value": "terminal.showPasswordInputIndicator password input indicator secure input echo off lock badge sudo ssh passwd gpg prompt" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "terminal.showPasswordInputIndicator password input indicator secure input echo off lock badge sudo ssh passwd gpg prompt contraseña entrada indicador candado eco" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "terminal.showPasswordInputIndicator password input indicator secure input echo off lock badge sudo ssh passwd gpg prompt mot de passe saisie indicateur cadenas écho" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "terminal.showPasswordInputIndicator password input indicator secure input echo off lock badge sudo ssh passwd gpg prompt パスワード 入力 インジケーター 鍵 エコー" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "terminal.showPasswordInputIndicator password input indicator secure input echo off lock badge sudo ssh passwd gpg prompt 비밀번호 입력 표시 자물쇠 에코" + "value": "分割窗格時,同一方向的窗格會調整為相同大小。" } }, "zh-Hans": { "stringUnit": { "state": "translated", - "value": "terminal.showPasswordInputIndicator password input indicator secure input echo off lock badge sudo ssh passwd gpg prompt 密码 输入 指示器 锁 回显" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "terminal.showPasswordInputIndicator password input indicator secure input echo off lock badge sudo ssh passwd gpg prompt 密碼 輸入 指示器 鎖頭 回顯" - } - } - } - }, - "settings.search.alias.setting.terminal.password-input-dots": { - "extractionState": "manual", - "localizations": { - "ar": { - "stringUnit": { - "state": "translated", - "value": "terminal.showPasswordInputDots password dots typed characters count bullets feedback sudo ssh prompt كلمة المرور نقاط أحرف" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "terminal.showPasswordInputDots password dots typed characters count bullets feedback sudo ssh prompt passwort punkte zeichen anzahl" - } - }, - "en": { - "stringUnit": { - "state": "translated", - "value": "terminal.showPasswordInputDots password dots typed characters count bullets feedback sudo ssh prompt" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "terminal.showPasswordInputDots password dots typed characters count bullets feedback sudo ssh prompt contraseña puntos caracteres" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "terminal.showPasswordInputDots password dots typed characters count bullets feedback sudo ssh prompt mot de passe points caractères" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "terminal.showPasswordInputDots password dots typed characters count bullets feedback sudo ssh prompt パスワード ドット 文字" + "value": "拆分窗格时,同一方向的窗格会调整为相同大小。" } }, "ko": { "stringUnit": { "state": "translated", - "value": "terminal.showPasswordInputDots password dots typed characters count bullets feedback sudo ssh prompt 비밀번호 점 문자" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "terminal.showPasswordInputDots password dots typed characters count bullets feedback sudo ssh prompt 密码 圆点 字符" + "value": "창을 분할하면 같은 방향의 분할 창이 모두 같은 크기가 됩니다." } }, - "zh-Hant": { + "ja": { "stringUnit": { "state": "translated", - "value": "terminal.showPasswordInputDots password dots typed characters count bullets feedback sudo ssh prompt 密碼 圓點 字元" + "value": "ペインを分割すると、その方向のペインが同じサイズになります。" } } } @@ -547528,6 +551481,596 @@ } } }, + "globalSearch.kind.terminal": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Terminal" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ターミナル" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "터미널" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "طرفية" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "終端" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "终端" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Terminal" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Terminal" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Terminal" + } + } + } + }, + "terminal.passwordInput.indicator": { + "extractionState": "manual", + "localizations": { + "ar": { + "stringUnit": { + "state": "translated", + "value": "إدخال كلمة المرور" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Passworteingabe" + } + }, + "en": { + "stringUnit": { + "state": "translated", + "value": "Password input" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Entrada de contraseña" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Saisie du mot de passe" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "パスワード入力" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "비밀번호 입력" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "密码输入" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "密碼輸入" + } + } + } + }, + "settings.terminal.showPasswordInputIndicator": { + "extractionState": "manual", + "localizations": { + "ar": { + "stringUnit": { + "state": "translated", + "value": "مؤشر إدخال كلمة المرور" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Anzeige für Passworteingabe" + } + }, + "en": { + "stringUnit": { + "state": "translated", + "value": "Password Input Indicator" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Indicador de entrada de contraseña" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Indicateur de saisie du mot de passe" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "パスワード入力インジケーター" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "비밀번호 입력 표시" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "密码输入指示器" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "密碼輸入指示器" + } + } + } + }, + "settings.terminal.showPasswordInputIndicator.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Shows a lock badge in the terminal corner while a program such as sudo or ssh reads a password with echo off." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Zeigt in der Terminalecke ein Schloss-Symbol an, während ein Programm wie sudo oder ssh ein Passwort ohne Echo einliest." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Affiche un badge de cadenas dans le coin du terminal pendant qu'un programme comme sudo ou ssh lit un mot de passe sans écho." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يعرض شارة قفل في زاوية الطرفية أثناء قراءة برنامج مثل sudo أو ssh لكلمة مرور مع إيقاف الصدى." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Muestra una insignia de candado en la esquina del terminal mientras un programa como sudo o ssh lee una contraseña sin eco." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "當 sudo 或 ssh 等程式在關閉回顯的情況下讀取密碼時,在終端機角落顯示鎖頭標記。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "当 sudo 或 ssh 等程序在关闭回显的情况下读取密码时,在终端角落显示锁形标记。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "sudo나 ssh 같은 프로그램이 에코를 끈 상태로 비밀번호를 읽는 동안 터미널 모서리에 자물쇠 배지를 표시합니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "sudo や ssh などのプログラムがエコーをオフにしてパスワードを読み取っている間、ターミナルの隅に鍵のバッジを表示します。" + } + } + } + }, + "settings.terminal.showPasswordInputDots": { + "extractionState": "manual", + "localizations": { + "ar": { + "stringUnit": { + "state": "translated", + "value": "إظهار نقاط للأحرف المكتوبة" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Punkte für eingegebene Zeichen anzeigen" + } + }, + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Typed Password Dots" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Mostrar puntos por los caracteres escritos" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Afficher des points pour les caractères saisis" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "入力したパスワードのドットを表示" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "입력한 비밀번호 점 표시" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "显示已输入的密码圆点" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "顯示已輸入的密碼圓點" + } + } + } + }, + "settings.terminal.showPasswordInputDots.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Shows one dot in the badge per typed character. cmux keeps only a count, never the characters." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Zeigt im Symbol einen Punkt pro eingegebenem Zeichen. cmux speichert nur die Anzahl, niemals die Zeichen." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Affiche dans le badge un point par caractère saisi. cmux ne conserve qu'un compteur, jamais les caractères." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يعرض في الشارة نقطة واحدة لكل حرف مكتوب. يحتفظ cmux بالعدد فقط، ولا يحتفظ بالأحرف أبدًا." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Muestra en la insignia un punto por cada carácter escrito. cmux solo guarda un recuento, nunca los caracteres." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在標記中為每個已輸入的字元顯示一個圓點。cmux 只保留數量,絕不保留字元本身。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在标记中为每个已输入的字符显示一个圆点。cmux 只保留数量,绝不保留字符本身。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "배지에 입력한 문자마다 점을 하나씩 표시합니다. cmux는 개수만 유지하며 문자는 절대 보관하지 않습니다." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "入力した文字ごとにバッジにドットを 1 つ表示します。cmux が保持するのは数だけで、文字そのものは保持しません。" + } + } + } + }, + "settings.search.alias.setting.terminal.password-input-indicator": { + "extractionState": "manual", + "localizations": { + "ar": { + "stringUnit": { + "state": "translated", + "value": "terminal.showPasswordInputIndicator password input indicator secure input echo off lock badge sudo ssh passwd gpg prompt كلمة المرور إدخال مؤشر قفل" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "terminal.showPasswordInputIndicator password input indicator secure input echo off lock badge sudo ssh passwd gpg prompt passwort eingabe anzeige schloss" + } + }, + "en": { + "stringUnit": { + "state": "translated", + "value": "terminal.showPasswordInputIndicator password input indicator secure input echo off lock badge sudo ssh passwd gpg prompt" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "terminal.showPasswordInputIndicator password input indicator secure input echo off lock badge sudo ssh passwd gpg prompt contraseña entrada indicador candado eco" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "terminal.showPasswordInputIndicator password input indicator secure input echo off lock badge sudo ssh passwd gpg prompt mot de passe saisie indicateur cadenas écho" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "terminal.showPasswordInputIndicator password input indicator secure input echo off lock badge sudo ssh passwd gpg prompt パスワード 入力 インジケーター 鍵 エコー" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "terminal.showPasswordInputIndicator password input indicator secure input echo off lock badge sudo ssh passwd gpg prompt 비밀번호 입력 표시 자물쇠 에코" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "terminal.showPasswordInputIndicator password input indicator secure input echo off lock badge sudo ssh passwd gpg prompt 密码 输入 指示器 锁 回显" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "terminal.showPasswordInputIndicator password input indicator secure input echo off lock badge sudo ssh passwd gpg prompt 密碼 輸入 指示器 鎖頭 回顯" + } + } + } + }, + "settings.search.alias.setting.terminal.password-input-dots": { + "extractionState": "manual", + "localizations": { + "ar": { + "stringUnit": { + "state": "translated", + "value": "terminal.showPasswordInputDots password dots typed characters count bullets feedback sudo ssh prompt كلمة المرور نقاط أحرف" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "terminal.showPasswordInputDots password dots typed characters count bullets feedback sudo ssh prompt passwort punkte zeichen anzahl" + } + }, + "en": { + "stringUnit": { + "state": "translated", + "value": "terminal.showPasswordInputDots password dots typed characters count bullets feedback sudo ssh prompt" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "terminal.showPasswordInputDots password dots typed characters count bullets feedback sudo ssh prompt contraseña puntos caracteres" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "terminal.showPasswordInputDots password dots typed characters count bullets feedback sudo ssh prompt mot de passe points caractères" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "terminal.showPasswordInputDots password dots typed characters count bullets feedback sudo ssh prompt パスワード ドット 文字" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "terminal.showPasswordInputDots password dots typed characters count bullets feedback sudo ssh prompt 비밀번호 점 문자" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "terminal.showPasswordInputDots password dots typed characters count bullets feedback sudo ssh prompt 密码 圆点 字符" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "terminal.showPasswordInputDots password dots typed characters count bullets feedback sudo ssh prompt 密碼 圓點 字元" + } + } + } + }, + "cli.session.usage": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Usage: cmux session move <session-id> --to <ssh-destination|local> [options]\n\nMove a stopped Claude Code session to another machine and resume it there.\nExit the agent first: the move refuses while the session runs on either side.\n\nThe move carries:\n - the cwd's git checkout: HEAD (on the same branch when safe) plus modified,\n deleted and untracked non-ignored files; a worktree is added when the\n repository exists on the destination but the path does not\n - the transcript, its session directory, file history, and the project\n memory directory (merged both ways, newest wins, nothing deleted)\n\nIt then opens a workspace on the destination (`cmux ssh` for a host, a local\nworkspace for `local`) that resumes the session with its recorded launcher.\nWhen the destination home is not at the same absolute path, paths under the\nhome are mapped and the project is re-slugged.\n\nOptions:\n --to <destination> SSH destination (user@host or ssh_config alias), or local\n --from <destination> Where the session is now (default: where the last move put it, else local)\n --name <title> Workspace title (default: the first 8 characters of the id)\n --no-code Do not carry the git checkout\n --port <n> SSH port\n --identity <path> SSH identity file\n --ssh-option <opt> Extra SSH -o option (repeatable)\n --no-focus Open the workspace without switching to it\n\nExamples:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Verwendung: cmux session move <session-id> --to <ssh-destination|local> [options]\n\nVerschiebt eine beendete Claude Code-Sitzung auf eine andere Maschine und setzt sie dort fort.\nBeende zuerst den Agenten: Die Verschiebung wird abgelehnt, solange die Sitzung auf einer Seite läuft.\n\nDie Verschiebung überträgt:\n - den git-Checkout des cwd: HEAD (auf demselben Branch, wenn das sicher ist) sowie geänderte,\n gelöschte und nicht ignorierte, nicht verfolgte Dateien; ein Worktree wird hinzugefügt, wenn\n das Repository am Ziel existiert, der Pfad aber nicht\n - das Transkript, sein Sitzungsverzeichnis, den Dateiverlauf und das Projektgedächtnis\n (in beide Richtungen zusammengeführt, das Neueste gewinnt, nichts wird gelöscht)\n\nDanach öffnet sie am Ziel einen Workspace (`cmux ssh` für einen Host, einen lokalen Workspace\nfür `local`), der die Sitzung mit ihrem aufgezeichneten Launcher fortsetzt. Liegt das Home-\nVerzeichnis am Ziel nicht unter demselben absoluten Pfad, werden Pfade darunter umgeschrieben\nund das Projekt erhält einen neuen Slug.\n\nOptionen:\n --to <destination> SSH-Ziel (user@host oder ssh_config-Alias) oder local\n --from <destination> Wo die Sitzung jetzt ist (Standard: wohin die letzte Verschiebung sie gebracht hat, sonst local)\n --name <title> Workspace-Titel (Standard: die ersten 8 Zeichen der ID)\n --no-code Den git-Checkout nicht übertragen\n --port <n> SSH-Port\n --identity <path> SSH-Identitätsdatei\n --ssh-option <opt> Zusätzliche SSH-Option -o (wiederholbar)\n --no-focus Workspace öffnen, ohne dorthin zu wechseln\n\nBeispiele:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Utilisation: cmux session move <session-id> --to <ssh-destination|local> [options]\n\nDéplace une session Claude Code arrêtée vers une autre machine et la reprend là-bas.\nQuittez d'abord l'agent : le déplacement est refusé tant que la session s'exécute d'un côté ou de l'autre.\n\nLe déplacement transfère :\n - le checkout git du cwd : HEAD (sur la même branche quand c'est sûr) ainsi que les fichiers\n modifiés, supprimés et non suivis non ignorés ; un worktree est ajouté quand le dépôt existe\n sur la destination mais pas le chemin\n - la transcription, son répertoire de session, l'historique des fichiers et la mémoire du\n projet (fusionnée dans les deux sens, le plus récent l'emporte, rien n'est supprimé)\n\nIl ouvre ensuite un espace de travail sur la destination (`cmux ssh` pour un hôte, un espace\nlocal pour `local`) qui reprend la session avec son lanceur enregistré. Quand le dossier\npersonnel de destination n'a pas le même chemin absolu, les chemins qu'il contient sont\nconvertis et le projet reçoit un nouveau slug.\n\nOptions :\n --to <destination> Destination SSH (user@host ou alias ssh_config), ou local\n --from <destination> Où se trouve la session (par défaut : là où l'a mise le dernier déplacement, sinon local)\n --name <title> Titre de l'espace de travail (par défaut : les 8 premiers caractères de l'id)\n --no-code Ne pas transférer le checkout git\n --port <n> Port SSH\n --identity <path> Fichier d'identité SSH\n --ssh-option <opt> Option SSH -o supplémentaire (répétable)\n --no-focus Ouvrir l'espace de travail sans y basculer\n\nExemples :\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "الاستخدام: cmux session move <session-id> --to <ssh-destination|local> [options]\n\nينقل جلسة Claude Code متوقفة إلى جهاز آخر ويستأنفها هناك.\nأغلق الوكيل أولًا: يُرفض النقل ما دامت الجلسة تعمل على أي من الجانبين.\n\nينقل الأمر:\n - checkout الخاص بـ git في cwd: HEAD (على الفرع نفسه عندما يكون ذلك آمنًا) والملفات المعدّلة\n والمحذوفة وغير المتتبعة وغير المتجاهلة؛ ويُضاف worktree عندما يوجد المستودع في الوجهة\n ولا يوجد المسار\n - نص الجلسة ودليل الجلسة وسجل الملفات ودليل ذاكرة المشروع\n (مدمج في الاتجاهين، الأحدث يفوز، ولا يُحذف شيء)\n\nثم يفتح مساحة عمل في الوجهة (`cmux ssh` لمضيف، أو مساحة عمل محلية لـ `local`)\nتستأنف الجلسة بالمشغّل المسجّل لها. عندما لا يكون المجلد الرئيسي في الوجهة\nعلى المسار المطلق نفسه، تُحوَّل المسارات داخله\nويُعاد إنشاء slug للمشروع.\n\nالخيارات:\n --to <destination> وجهة SSH (user@host أو اسم مستعار من ssh_config)، أو local\n --from <destination> مكان الجلسة الآن (الافتراضي: حيث وضعها آخر نقل، وإلا local)\n --name <title> عنوان مساحة العمل (الافتراضي: أول 8 أحرف من المعرّف)\n --no-code لا تنقل checkout الخاص بـ git\n --port <n> منفذ SSH\n --identity <path> ملف هوية SSH\n --ssh-option <opt> خيار SSH إضافي -o (قابل للتكرار)\n --no-focus افتح مساحة العمل دون التبديل إليها\n\nأمثلة:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Uso: cmux session move <session-id> --to <ssh-destination|local> [options]\n\nMueve una sesión detenida de Claude Code a otra máquina y la reanuda allí.\nSal primero del agente: el movimiento se rechaza mientras la sesión se ejecute en cualquiera de los lados.\n\nEl movimiento lleva:\n - el checkout de git del cwd: HEAD (en la misma rama cuando es seguro) más los archivos\n modificados, eliminados y sin seguimiento no ignorados; se añade un worktree cuando el\n repositorio existe en el destino pero la ruta no\n - la transcripción, su directorio de sesión, el historial de archivos y la memoria del\n proyecto (combinada en ambos sentidos, gana la más reciente, no se borra nada)\n\nDespués abre un espacio de trabajo en el destino (`cmux ssh` para un host, uno local para\n`local`) que reanuda la sesión con su lanzador registrado. Cuando el home del destino no está\nen la misma ruta absoluta, las rutas bajo él se convierten\ny el proyecto recibe un nuevo slug.\n\nOpciones:\n --to <destination> Destino SSH (user@host o alias de ssh_config), o local\n --from <destination> Dónde está la sesión ahora (por defecto: donde la dejó el último movimiento, si no local)\n --name <title> Título del espacio de trabajo (por defecto: los primeros 8 caracteres del id)\n --no-code No llevar el checkout de git\n --port <n> Puerto SSH\n --identity <path> Archivo de identidad SSH\n --ssh-option <opt> Opción SSH -o adicional (repetible)\n --no-focus Abrir el espacio de trabajo sin cambiar a él\n\nEjemplos:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "用法: cmux session move <session-id> --to <ssh-destination|local> [options]\n\n將已停止的 Claude Code 工作階段移到另一台機器並在那裡恢復。\n請先結束代理:只要工作階段仍在任一端執行,移動就會被拒絕。\n\n移動會帶上:\n - cwd 的 git checkout:HEAD(安全時使用同一分支)加上已修改、已刪除\n 和未追蹤且未忽略的檔案;若目的地有儲存庫\n 但沒有該路徑,會新增 worktree\n - 對話紀錄、其工作階段目錄、檔案歷史與專案記憶目錄\n (雙向合併,較新者優先,不刪除任何內容)\n\n接著在目的地開啟工作區(主機用 `cmux ssh`,`local` 則開本機工作區),\n以記錄的啟動器恢復工作階段。\n若目的地的主目錄不在相同的絕對路徑,其下的路徑會被對應,\n專案也會重新產生 slug。\n\n選項:\n --to <destination> SSH 目的地(user@host 或 ssh_config 別名),或 local\n --from <destination> 工作階段目前所在位置(預設:上次移動所放的位置,否則為 local)\n --name <title> 工作區標題(預設:ID 的前 8 個字元)\n --no-code 不帶上 git checkout\n --port <n> SSH 連接埠\n --identity <path> SSH 身分檔案\n --ssh-option <opt> 額外的 SSH -o 選項(可重複)\n --no-focus 開啟工作區但不切換過去\n\n範例:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "用法: cmux session move <session-id> --to <ssh-destination|local> [options]\n\n将已停止的 Claude Code 会话移到另一台机器并在那里恢复。\n请先退出代理:只要会话仍在任一端运行,移动就会被拒绝。\n\n移动会带上:\n - cwd 的 git 检出:HEAD(安全时使用同一分支)以及已修改、已删除\n 和未跟踪且未忽略的文件;若目标上有仓库\n 但没有该路径,会添加 worktree\n - 会话记录、其会话目录、文件历史和项目记忆目录\n (双向合并,较新者优先,不删除任何内容)\n\n随后在目标上打开工作区(主机用 `cmux ssh`,`local` 则打开本地工作区),\n用记录的启动器恢复会话。\n若目标的主目录不在相同的绝对路径,其下的路径会被映射,\n项目也会重新生成 slug。\n\n选项:\n --to <destination> SSH 目标(user@host 或 ssh_config 别名),或 local\n --from <destination> 会话当前所在位置(默认:上次移动放置的位置,否则为 local)\n --name <title> 工作区标题(默认:ID 的前 8 个字符)\n --no-code 不带上 git 检出\n --port <n> SSH 端口\n --identity <path> SSH 身份文件\n --ssh-option <opt> 额外的 SSH -o 选项(可重复)\n --no-focus 打开工作区但不切换过去\n\n示例:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "사용법: cmux session move <session-id> --to <ssh-destination|local> [options]\n\n중지된 Claude Code 세션을 다른 머신으로 옮기고 그곳에서 재개합니다.\n먼저 에이전트를 종료하세요. 어느 쪽에서든 세션이 실행 중이면 이동이 거부됩니다.\n\n이동하는 항목:\n - cwd의 git 체크아웃: HEAD(안전하면 같은 브랜치)와 수정, 삭제,\n 무시되지 않은 추적 안 된 파일. 대상에 저장소는 있지만\n 경로가 없으면 worktree를 추가합니다\n - 트랜스크립트, 세션 디렉터리, 파일 기록, 프로젝트 메모리 디렉터리\n (양방향 병합, 최신 우선, 삭제 없음)\n\n그런 다음 대상에서 작업 공간(호스트는 `cmux ssh`, `local`은 로컬 작업 공간)을 열고\n기록된 런처로 세션을 재개합니다.\n대상의 홈이 같은 절대 경로에 있지 않으면 홈 아래 경로를 변환하고\n프로젝트 slug를 다시 만듭니다.\n\n옵션:\n --to <destination> SSH 대상(user@host 또는 ssh_config 별칭) 또는 local\n --from <destination> 세션의 현재 위치(기본값: 마지막 이동으로 옮긴 곳, 없으면 local)\n --name <title> 작업 공간 제목(기본값: ID의 처음 8자)\n --no-code git 체크아웃을 옮기지 않음\n --port <n> SSH 포트\n --identity <path> SSH ID 파일\n --ssh-option <opt> 추가 SSH -o 옵션(반복 가능)\n --no-focus 작업 공간으로 전환하지 않고 열기\n\n예시:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "使用方法: cmux session move <session-id> --to <ssh-destination|local> [options]\n\n停止した Claude Code セッションを別のマシンへ移動し、そこで再開します。\n先にエージェントを終了してください。どちらかでセッションが実行中の間は移動を拒否します。\n\n移動する内容:\n - cwd の git チェックアウト: HEAD (安全なら同じブランチ) と、変更・削除されたファイル、\n 無視されていない未追跡ファイル。移動先にリポジトリはあるが\n パスがない場合は worktree を追加します\n - トランスクリプト、セッションディレクトリ、ファイル履歴、プロジェクトのメモリディレクトリ\n (双方向にマージ、新しいほうを優先、削除はしません)\n\nその後、移動先でワークスペース (ホストなら `cmux ssh`、`local` ならローカルのワークスペース) を\n開き、記録されたランチャーでセッションを再開します。\n移動先のホームが同じ絶対パスにない場合は、ホーム配下のパスを変換し、\nプロジェクトの slug を作り直します。\n\nオプション:\n --to <destination> SSH 接続先 (user@host または ssh_config のエイリアス)、または local\n --from <destination> セッションの現在の場所 (既定: 前回の移動先、なければ local)\n --name <title> ワークスペースのタイトル (既定: ID の先頭 8 文字)\n --no-code git チェックアウトを移動しない\n --port <n> SSH ポート\n --identity <path> SSH の ID ファイル\n --ssh-option <opt> 追加の SSH -o オプション (繰り返し可)\n --no-focus ワークスペースに切り替えずに開く\n\n例:\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to dev@my-host\n cmux session move 0b7a1e7c-3f0a-4c6e-9d59-8a0d8f7c2b11 --to local" + } + } + } + }, + "settings.notifications.sound.preview": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Preview Sound" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Ton anhören" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Écouter le son" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "معاينة الصوت" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Escuchar sonido" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "試聽音效" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "试听声音" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "소리 미리 듣기" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サウンドをプレビュー" + } + } + } + }, "cli.session.error.unknownSubcommand": { "extractionState": "manual", "localizations": { @@ -549829,3959 +554372,6 @@ } } }, - "settings.computerUse.enabled.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Lets supported agents see and control apps on this Mac. An agent's first Computer Use request starts setup automatically." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Unterstützte Agenten können Apps auf diesem Mac sehen und steuern. Die erste Computer-Use-Anfrage eines Agenten startet die Einrichtung automatisch." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Les agents pris en charge peuvent voir et contrôler les apps de ce Mac. La première requête Computer Use d’un agent lance automatiquement la configuration." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يمكن للوكلاء المدعومين رؤية التطبيقات على جهاز Mac هذا والتحكم فيها. ويبدأ الإعداد تلقائيًا عند أول طلب Computer Use من أحد الوكلاء." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Los agentes compatibles pueden ver y controlar apps en este Mac. La primera solicitud de Computer Use de un agente inicia la configuración automáticamente." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "允許支援的代理程式查看及操作此 Mac 上的 App。代理程式第一次發出 Computer Use 要求時會自動開始設定。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "允许受支持的代理查看并控制这台 Mac 上的 App。代理第一次发出 Computer Use 请求时会自动开始设置。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "지원되는 에이전트가 이 Mac의 앱을 보고 제어할 수 있게 합니다. 에이전트가 처음 Computer Use를 요청하면 설정이 자동으로 시작됩니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "対応するエージェントが、この Mac のアプリの画面を見て操作できるようにします。エージェントが最初に Computer Use を要求すると、セットアップが自動的に始まります。" - } - } - } - }, - "settings.app.closeWorkspaceOnLastSurfaceShortcut.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Closing the last surface in a workspace keeps the workspace open." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Der Arbeitsbereich bleibt geöffnet, auch wenn seine letzte Oberfläche geschlossen wird." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "L’espace de travail reste ouvert après la fermeture de sa dernière surface d’affichage." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "تبقى مساحة العمل مفتوحة عند إغلاق آخر سطح فيها." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "El espacio de trabajo permanece abierto aunque se cierre su última superficie." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "即使關閉最後一個介面,工作區仍會保持開啟。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "即使关闭最后一个表面,工作区也会保持打开。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "마지막 서피스를 닫아도 워크스페이스는 닫히지 않습니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "最後のサーフェスを閉じても、ワークスペースは閉じません。" - } - } - } - }, - "settings.app.commandPaletteSearchAllSurfaces.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Includes terminal, browser, and Markdown surfaces from every workspace in command palette results." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Die Befehlspalette findet auch Terminal-, Browser- und Markdown-Oberflächen in allen Arbeitsbereichen." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "La palette de commandes affiche aussi les surfaces d’affichage de terminal, de navigateur et Markdown de tous les espaces de travail." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "تعرض نتائج لوحة الأوامر أسطح الطرفية والمتصفح وMarkdown من جميع مساحات العمل." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "La paleta de comandos también muestra las superficies de terminal, navegador y Markdown de todos los espacios de trabajo." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "在命令面板中也能找到所有工作區的終端機、瀏覽器和 Markdown 介面。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在命令面板中也能找到所有工作区的终端、浏览器和 Markdown 表面。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "명령어 팔레트에서 모든 워크스페이스의 터미널, 브라우저, Markdown 서피스도 찾을 수 있습니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "コマンドパレットで、すべてのワークスペースのターミナル、ブラウザ、Markdown のサーフェスも検索できます。" - } - } - } - }, - "settings.app.fileDrop.defaultBehavior.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Choose what dropping files on a terminal or editor does. Holding Shift while dragging does the other." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Wählen Sie, was beim Ablegen von Dateien auf einem Terminal oder Editor passiert. Wenn Sie beim Ziehen die Umschalttaste gedrückt halten, gilt die andere Option." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Choisissez ce qui se passe lorsque vous déposez des fichiers sur un terminal ou un éditeur. Si vous maintenez la touche Maj enfoncée pendant le glissement, l’autre option s’applique." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "اختر ما يحدث عند إفلات الملفات على طرفية أو محرر. يؤدي الضغط مع الاستمرار على مفتاح Shift أثناء السحب إلى تطبيق الخيار الآخر." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Elige qué ocurre al soltar archivos en una terminal o un editor. Si mantienes pulsada la tecla Mayúsculas al arrastrar, se usa la otra opción." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "選擇將檔案拖放到終端機或編輯器時要執行的動作。拖移時按住 Shift 鍵可改用另一個動作。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "选择将文件拖放到终端或编辑器时执行的操作。拖动时按住 Shift 键可改用另一种操作。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "파일을 터미널이나 편집기에 드롭했을 때 수행할 동작을 선택합니다. 드래그 중에 Shift 키를 누르고 있으면 다른 쪽 동작을 수행합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ファイルをターミナルやエディタにドロップしたときの動作を選択します。ドラッグ中に Shift キーを押したままにすると、もう一方の動作になります。" - } - } - } - }, - "settings.app.focusHistoryIncludesPanesAndTabs.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Using Back and Forward also returns to previous panes and tabs." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Mit „Zurück“ und „Vor“ kehren Sie auch zu vorherigen Teilfenstern und Tabs zurück." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Précédent et Suivant ramènent aussi aux volets et aux onglets précédents." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "ينتقل «رجوع» و«تقدم» أيضًا إلى الأجزاء وعلامات التبويب السابقة." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Atrás y Adelante también vuelven a los paneles y pestañas anteriores." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "使用「返回」與「前進」時,也會回到先前的窗格與標籤頁。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "“后退”和“前进”也会回到之前的窗格和标签页。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "뒤로 가기와 앞으로 가기로 이전 분할 창과 탭에도 돌아갈 수 있습니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "「戻る」と「進む」で、以前のペインやタブにも戻れます。" - } - } - } - }, - "settings.app.globalFontMagnification.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Scales all text in cmux. Command-Plus and Command-Minus still zoom the focused pane." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Ändert die Größe des gesamten Texts in cmux. Mit Befehlstaste-Plus und Befehlstaste-Minus lässt sich das fokussierte Teilfenster weiterhin zoomen." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Modifie la taille de tout le texte de cmux. Commande + Plus et Commande + Moins continuent de régler le zoom du volet actif." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يغيّر حجم جميع النصوص في cmux. ولا يزال الاختصاران Command-علامة الجمع وCommand-علامة الطرح يكبّران الجزء المركّز عليه ويصغّرانه." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Cambia el tamaño de todo el texto de cmux. Comando + Más y Comando + Menos siguen ajustando el zoom del panel enfocado." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "調整 cmux 中所有文字的大小。Command + 加號和 Command + 減號仍可用來縮放目前聚焦的窗格。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "调整 cmux 中所有文本的大小。Command + 加号和 Command + 减号仍可用于缩放当前聚焦的窗格。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "cmux의 모든 텍스트 크기를 조절합니다. 포커스된 분할 창은 계속 Command-더하기와 Command-빼기로 확대/축소할 수 있습니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "cmux 内のすべてのテキストの大きさを変更します。フォーカス中のペインは、これまでどおり Command + プラスと Command + マイナスで拡大縮小できます。" - } - } - } - }, - "settings.app.hideAllSidebarDetails.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Shows only workspace titles in the sidebar." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Zeigt in der Seitenleiste nur die Titel der Arbeitsbereiche an." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Affiche uniquement les titres des espaces de travail dans la barre latérale." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يعرض عناوين مساحات العمل فقط في الشريط الجانبي." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Muestra solo los títulos de los espacios de trabajo en la barra lateral." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "側邊欄只顯示工作區標題。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "侧边栏只显示工作区标题。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "사이드바에 워크스페이스 제목만 표시합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "サイドバーにワークスペースのタイトルだけを表示します。" - } - } - } - }, - "settings.app.hideTabCloseButton.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Removes the close button from tabs." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Tabs werden ohne Schließen-Taste angezeigt." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Les onglets s’affichent sans bouton de fermeture." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "تظهر علامات التبويب دون زر إغلاق." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Las pestañas se muestran sin el botón de cerrar." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "標籤頁上不顯示關閉按鈕。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "标签页上不显示关闭按钮。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "탭에 닫기 버튼을 표시하지 않습니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "タブの閉じるボタンを非表示にします。" - } - } - } - }, - "settings.app.minimalMode.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Hides the workspace title bar and shows its controls in the sidebar." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Blendet die Titelleiste des Arbeitsbereichs aus und zeigt ihre Steuerelemente in der Seitenleiste an." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Masque la barre de titre de l’espace de travail et affiche ses commandes dans la barre latérale." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يخفي شريط عنوان مساحة العمل ويعرض عناصر التحكم الخاصة به في الشريط الجانبي." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Oculta la barra de título del espacio de trabajo y muestra sus controles en la barra lateral." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "隱藏工作區標題列,改在側邊欄中顯示其控制項。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "隐藏工作区标题栏,改为在侧边栏中显示其控件。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "워크스페이스 제목 막대를 숨기고, 제목 막대의 컨트롤을 사이드바에 표시합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ワークスペースのタイトルバーを非表示にし、そのコントロールをサイドバーに表示します。" - } - } - } - }, - "settings.app.newWorkspacePlacement.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Choose where new workspaces appear in the sidebar." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Wählen Sie, wo neue Arbeitsbereiche in der Seitenleiste erscheinen." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Choisissez où les nouveaux espaces de travail apparaissent dans la barre latérale." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "اختر مكان ظهور مساحات العمل الجديدة في الشريط الجانبي." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Elige dónde aparecen los nuevos espacios de trabajo en la barra lateral." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "選擇新工作區要顯示在側邊欄的哪個位置。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "选择新工作区在侧边栏中的显示位置。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "새 작업 공간을 사이드바의 어디에 표시할지 선택합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "新しいワークスペースをサイドバーのどこに表示するかを選択します。" - } - } - } - }, - "settings.app.openSidebarPRLinks.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Clicking a pull request link in the sidebar opens it in the cmux browser." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Pull-Request-Links in der Seitenleiste werden per Klick im cmux-Browser geöffnet." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Un clic sur un lien de pull request de la barre latérale l’ouvre dans le navigateur cmux." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "تُفتح روابط طلبات السحب في الشريط الجانبي في متصفح cmux عند النقر عليها." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Los enlaces de pull request de la barra lateral se abren en el navegador de cmux al hacer clic en ellos." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "側邊欄中的 PR 連結會在 cmux 瀏覽器中開啟。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "侧边栏中的 PR 链接会在 cmux 浏览器中打开。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "사이드바의 풀 리퀘스트 링크를 클릭하면 cmux 브라우저에서 열립니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "サイドバーのプルリクエストのリンクをクリックすると、cmuxブラウザで開きます。" - } - } - } - }, - "settings.app.openSidebarPortLinks.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Clicking a port link in the sidebar opens it in the cmux browser." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Port-Links in der Seitenleiste werden per Klick im cmux-Browser geöffnet." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Un clic sur un lien de port de la barre latérale l’ouvre dans le navigateur cmux." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "تُفتح روابط المنافذ في الشريط الجانبي في متصفح cmux عند النقر عليها." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Los enlaces de puerto de la barra lateral se abren en el navegador de cmux al hacer clic en ellos." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "側邊欄中的連接埠連結會在 cmux 瀏覽器中開啟。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "侧边栏中的端口链接会在 cmux 浏览器中打开。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "사이드바의 포트 링크를 클릭하면 cmux 브라우저에서 열립니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "サイドバーのポートリンクをクリックすると、cmuxブラウザで開きます。" - } - } - } - }, - "settings.app.paneFirstClickFocus.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Clicking a pane while cmux is in the background focuses that pane right away." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Ist cmux im Hintergrund, wird ein Teilfenster schon beim ersten Klick fokussiert." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Lorsque cmux est en arrière-plan, un clic sur un volet l’active immédiatement." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "عندما يكون cmux في الخلفية، يكفي النقر على جزء للتركيز عليه فورًا." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Si cmux está en segundo plano, un clic en un panel lo enfoca de inmediato." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "即使 cmux 在背景執行,按一下窗格也會立即將焦點移到該窗格。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "即使 cmux 在后台,点按窗格也会立即将焦点移到该窗格。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "cmux가 백그라운드에 있어도 분할 창을 클릭하면 바로 그 분할 창에 포커스가 이동합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "cmux がバックグラウンドにあっても、ペインをクリックするとすぐにそのペインにフォーカスが移ります。" - } - } - } - }, - "settings.app.pathLastSegmentOnly.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Shortens long paths from the start to keep their end visible." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Kürzt lange Pfade am Anfang, damit ihr Ende sichtbar bleibt." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Raccourcit les chemins longs par le début pour que leur fin reste visible." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يختصر المسارات الطويلة من بدايتها لتبقى نهايتها ظاهرة." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Acorta las rutas largas por el inicio para que su final siga visible." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "長路徑會省略開頭,保留結尾部分。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "长路径会省略开头,保留结尾部分。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "긴 경로는 앞부분을 생략하여 끝부분이 보이게 합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "長いパスは先頭を省略して、末尾が見えるようにします。" - } - } - } - }, - "settings.app.renameSelectsName.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Renaming from the command palette selects the current name." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Beim Umbenennen über die Befehlspalette ist der aktuelle Name bereits ausgewählt." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Lors d’un renommage depuis la palette de commandes, le nom actuel est sélectionné." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "عند إعادة التسمية من لوحة الأوامر، يكون الاسم الحالي محددًا." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Al cambiar el nombre desde la paleta de comandos, el nombre actual aparece seleccionado." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "從命令面板重新命名時,會選取目前的名稱。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "从命令面板重命名时,会选中当前名称。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "명령어 팔레트에서 이름을 변경할 때 현재 이름이 선택된 상태로 표시됩니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "コマンドパレットで名前を変更するとき、現在の名前が選択された状態になります。" - } - } - } - }, - "settings.app.sidebarBranchLayout.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Choose whether branches share one line or each get their own line." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Wählen Sie, ob sich Branches eine Zeile teilen oder jeweils eine eigene Zeile erhalten." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Choisissez si les branches partagent une même ligne ou ont chacune leur propre ligne." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "اختر ما إذا كانت الفروع تتشارك سطرًا واحدًا أو يحصل كل منها على سطر خاص به." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Elige si las ramas comparten una sola línea o cada una ocupa su propia línea." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "選擇分支要共用一行,還是各自獨立一行。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "选择分支是共用一行,还是各占一行。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "브랜치를 한 줄에 함께 표시할지, 각각 별도의 줄에 표시할지 선택합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ブランチを 1 行にまとめて表示するか、それぞれ別の行に表示するかを選択します。" - } - } - } - }, - "settings.app.stackBranchDirectory.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Shows the branch and directory on separate lines." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Zeigt Branch und Verzeichnis in getrennten Zeilen an." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Affiche la branche et le répertoire sur des lignes distinctes." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يعرض الفرع والدليل في سطرين منفصلين." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Muestra la rama y el directorio en líneas separadas." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "分支與目錄分行顯示。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "分支和目录分行显示。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "브랜치와 디렉터리를 각각 다른 줄에 표시합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ブランチとディレクトリを別々の行に表示します。" - } - } - } - }, - "settings.app.warnBeforeClosingTab.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Closing a tab asks for confirmation first." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Beim Schließen eines Tabs wird zuerst nach einer Bestätigung gefragt." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Une confirmation est demandée avant la fermeture d’un onglet." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يُطلب التأكيد قبل إغلاق علامة التبويب." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Se pide confirmación antes de cerrar una pestaña." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "關閉標籤頁前先要求確認。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "关闭标签页前先要求确认。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "탭을 닫기 전에 확인을 요청합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "タブを閉じる前に確認します。" - } - } - } - }, - "settings.app.warnBeforeClosingTabXButton.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Clicking a tab's close button asks for confirmation first." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Beim Klicken auf die Schließen-Taste eines Tabs wird zuerst nach einer Bestätigung gefragt." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Une confirmation est demandée avant la fermeture d’un onglet avec son bouton de fermeture." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يُطلب التأكيد قبل إغلاق علامة التبويب بالنقر على زر الإغلاق الخاص بها." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Se pide confirmación antes de cerrar una pestaña con su botón de cerrar." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "按一下標籤頁上的關閉按鈕後,會先要求確認再關閉。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "点按标签页上的关闭按钮后,会先要求确认再关闭。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "탭의 닫기 버튼을 클릭하면 탭을 닫기 전에 확인을 요청합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "タブの閉じるボタンをクリックしたとき、閉じる前に確認します。" - } - } - } - }, - "settings.app.warnBeforeQuit.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Choose when quitting with Command-Q asks for confirmation. Dirty Only asks only when a workspace would ask before closing." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Wählen Sie, wann beim Beenden mit Befehlstaste-Q nach einer Bestätigung gefragt wird. „Nur bei Bedarf“ fragt nur, wenn ein Arbeitsbereich vor dem Schließen nachfragen würde." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Choisissez quand une confirmation est demandée pour quitter avec Commande + Q. « Si nécessaire » ne la demande que si un espace de travail la demanderait avant sa fermeture." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "اختر متى يُطلب التأكيد عند الإنهاء باستخدام Command-Q. يطلب خيار «عند الحاجة فقط» التأكيد فقط عندما تطلبه مساحة عمل قبل إغلاقها." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Elige cuándo se pide confirmación al salir con Comando + Q. «Solo si hace falta» solo la pide cuando un espacio de trabajo la pediría antes de cerrarse." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "選擇按 Command + Q 結束時,在什麼情況下要求確認。「僅在需要時」只會在某個工作區關閉前需要確認時才詢問。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "选择按 Command + Q 退出时,在什么情况下要求确认。“仅在需要时”只会在某个工作区关闭前需要确认时才询问。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "Command-Q로 종료할 때 확인을 요청할 조건을 선택합니다. ‘필요 시에만’은 닫기 전에 확인이 필요한 작업 공간이 있을 때만 확인을 요청합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Command + Q で終了するときに、どのような場合に確認するかを選択します。「必要時のみ」では、閉じる前に確認が必要なワークスペースがある場合だけ確認します。" - } - } - } - }, - "settings.app.workspaceInheritWorkingDirectory.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Starts new workspaces in the working directory of the current workspace." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Startet neue Arbeitsbereiche im Arbeitsverzeichnis des aktuellen Arbeitsbereichs." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ouvre les nouveaux espaces de travail dans le répertoire de travail de l’espace de travail actuel." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يبدأ مساحات العمل الجديدة في دليل العمل الخاص بمساحة العمل الحالية." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Inicia los nuevos espacios de trabajo en el directorio de trabajo del espacio de trabajo actual." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "新工作區會沿用目前工作區的工作目錄。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "新工作区会沿用当前工作区的工作目录。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "새 작업 공간은 현재 작업 공간의 작업 디렉터리에서 시작됩니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "新しいワークスペースは、現在のワークスペースと同じ作業ディレクトリで開始します。" - } - } - } - }, - "settings.app.wrapWorkspaceTitles.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Shows long workspace titles in full on multiple lines." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Zeigt lange Arbeitsbereichstitel vollständig in mehreren Zeilen an." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Affiche en entier les titres longs des espaces de travail sur plusieurs lignes." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يعرض عناوين مساحات العمل الطويلة كاملةً على عدة أسطر." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Muestra completos los títulos largos de los espacios de trabajo en varias líneas." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "較長的工作區標題會換行完整顯示。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "较长的工作区标题会换行完整显示。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "긴 워크스페이스 제목을 줄바꿈하여 모두 표시합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "長いワークスペースタイトルを省略せずに複数行で表示します。" - } - } - } - }, - "settings.automation.amp.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Shows Amp status and notifications in the sidebar." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Zeigt den Status und die Benachrichtigungen von Amp in der Seitenleiste an." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Affiche l’état et les notifications d’Amp dans la barre latérale." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يعرض حالة Amp وإشعاراته في الشريط الجانبي." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Muestra el estado y las notificaciones de Amp en la barra lateral." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "在側邊欄中顯示 Amp 的狀態與通知。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在侧边栏中显示 Amp 的状态和通知。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "사이드바에 Amp 상태와 알림을 표시합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "サイドバーにAmpのステータスと通知を表示します。" - } - } - } - }, - "settings.automation.autoNamingAgent.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Automatic uses each session's own agent. Other supported agents need their command-line tool on the PATH." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "„Automatisch“ verwendet den Agenten der jeweiligen Sitzung. Andere unterstützte Agenten benötigen ihr Befehlszeilentool im PATH." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "« Automatique » utilise l’agent propre à chaque session. Les autres agents pris en charge nécessitent que leur outil en ligne de commande soit dans le PATH." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يستخدم خيار «تلقائي» الوكيل الخاص بكل جلسة. يحتاج الوكلاء المدعومون الآخرون إلى وجود أداة سطر الأوامر الخاصة بهم في PATH." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "«Automático» usa el agente de cada sesión. Los demás agentes compatibles necesitan su herramienta de línea de comandos en el PATH." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "「自動」會使用各工作階段本身的代理。若要使用其他支援的代理,其命令列工具必須位於 PATH 中。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "“自动”会使用各会话自身的代理。要使用其他受支持的代理,其命令行工具必须位于 PATH 中。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "‘자동’은 세션마다 해당 세션의 에이전트를 사용합니다. 지원되는 다른 에이전트를 사용하려면 해당 명령줄 도구가 PATH에 있어야 합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "「自動」では、各セッションのエージェントを使用します。ほかの対応エージェントを使うには、そのコマンドラインツールが PATH に含まれている必要があります。" - } - } - } - }, - "settings.automation.claudeCode.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Shows Claude Code status and notifications in the sidebar." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Zeigt den Status und die Benachrichtigungen von Claude Code in der Seitenleiste an." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Affiche l’état et les notifications de Claude Code dans la barre latérale." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يعرض حالة Claude Code وإشعاراته في الشريط الجانبي." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Muestra el estado y las notificaciones de Claude Code en la barra lateral." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "在側邊欄中顯示 Claude Code 的狀態與通知。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在侧边栏中显示 Claude Code 的状态和通知。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "사이드바에 Claude Code 상태와 알림을 표시합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "サイドバーにClaude Codeのステータスと通知を表示します。" - } - } - } - }, - "settings.automation.codex.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Shows Codex status and notifications in the sidebar." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Zeigt den Status und die Benachrichtigungen von Codex in der Seitenleiste an." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Affiche l’état et les notifications de Codex dans la barre latérale." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يعرض حالة Codex وإشعاراته في الشريط الجانبي." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Muestra el estado y las notificaciones de Codex en la barra lateral." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "在側邊欄中顯示 Codex 的狀態與通知。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在侧边栏中显示 Codex 的状态和通知。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "사이드바에 Codex 상태와 알림을 표시합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "サイドバーにCodexのステータスと通知を表示します。" - } - } - } - }, - "settings.automation.cursor.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Shows Cursor status and notifications in the sidebar." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Zeigt den Status und die Benachrichtigungen von Cursor in der Seitenleiste an." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Affiche l’état et les notifications de Cursor dans la barre latérale." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يعرض حالة Cursor وإشعاراته في الشريط الجانبي." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Muestra el estado y las notificaciones de Cursor en la barra lateral." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "在側邊欄中顯示 Cursor 的狀態與通知。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在侧边栏中显示 Cursor 的状态和通知。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "사이드바에 Cursor 상태와 알림을 표시합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "サイドバーにCursorのステータスと通知を表示します。" - } - } - } - }, - "settings.automation.gemini.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Shows Gemini CLI status and notifications in the sidebar." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Zeigt den Status und die Benachrichtigungen von Gemini CLI in der Seitenleiste an." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Affiche l’état et les notifications de Gemini CLI dans la barre latérale." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يعرض حالة Gemini CLI وإشعاراته في الشريط الجانبي." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Muestra el estado y las notificaciones de Gemini CLI en la barra lateral." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "在側邊欄中顯示 Gemini CLI 的狀態與通知。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在侧边栏中显示 Gemini CLI 的状态和通知。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "사이드바에 Gemini CLI 상태와 알림을 표시합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "サイドバーにGemini CLIのステータスと通知を表示します。" - } - } - } - }, - "settings.automation.kiro.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Shows Kiro CLI status and notifications in the sidebar, and its tool events in Feed." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Zeigt den Status und die Benachrichtigungen von Kiro CLI in der Seitenleiste und seine Tool-Ereignisse im Feed an." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Affiche l’état et les notifications de Kiro CLI dans la barre latérale, et ses événements d’outils dans le Flux." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يعرض حالة Kiro CLI وإشعاراته في الشريط الجانبي، وأحداث أدواته في Feed." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Muestra el estado y las notificaciones de Kiro CLI en la barra lateral, y sus eventos de herramientas en el Feed." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "在側邊欄中顯示 Kiro CLI 的狀態與通知,並在 Feed 中顯示其工具事件。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在侧边栏中显示 Kiro CLI 的状态和通知,并在 Feed 中显示其工具事件。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "사이드바에 Kiro CLI 상태와 알림을 표시하고, 피드에 해당 도구 이벤트를 표시합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "サイドバーにKiro CLIのステータスと通知を、フィードにそのツールイベントを表示します。" - } - } - } - }, - "settings.automation.socketMode.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Choose which local processes can control cmux." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Wählen Sie, welche lokalen Prozesse cmux steuern können." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Choisissez quels processus locaux peuvent contrôler cmux." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "اختر العمليات المحلية التي يمكنها التحكم في cmux." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Elige qué procesos locales pueden controlar cmux." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "選擇哪些本機程序可以控制 cmux。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "选择哪些本地进程可以控制 cmux。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "cmux를 제어할 수 있는 로컬 프로세스를 선택합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "cmux を制御できるローカルプロセスを選択します。" - } - } - } - }, - "settings.automation.suppressSubagentNotifications.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Lists subagent completions in Feed without sending notifications." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Abgeschlossene Subagenten erscheinen im Feed, ohne dass Benachrichtigungen gesendet werden." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Les sous-agents terminés apparaissent dans le Flux, sans notification." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يظهر اكتمال مهام الوكلاء الفرعيين في Feed دون إرسال إشعارات." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Los subagentes finalizados aparecen en el Feed sin enviar notificaciones." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "子代理完成時只會列在 Feed 中,不會傳送通知。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "子代理完成时只会列在 Feed 中,不会发送通知。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "하위 에이전트가 완료되면 알림을 보내지 않고 피드에만 표시합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "サブエージェントが完了しても通知は送らず、フィードにのみ表示します。" - } - } - } - }, - "settings.automation.workspaceAutoNaming.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Generates workspace and tab titles from agent conversations." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Benennt Arbeitsbereiche und Tabs anhand von Agentengesprächen." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Nomme les espaces de travail et les onglets d’après les conversations des agents." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يسمّي مساحات العمل وعلامات التبويب بناءً على محادثات الوكلاء." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Pone nombre a los espacios de trabajo y las pestañas según las conversaciones de los agentes." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "根據代理對話產生工作區和標籤頁標題。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "根据代理对话生成工作区和标签页标题。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "에이전트 대화를 바탕으로 작업 공간과 탭 제목을 생성합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "エージェントの会話からワークスペースとタブのタイトルを生成します。" - } - } - } - }, - "settings.betaFeatures.cloudMachines.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Adds Cloud Machines to the right sidebar, Settings, the command palette, and the new workspace menu. Cloud Machines also require a remote rollout; with this off, the Cloud tunnel and fleet polling stay off." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Fügt Cloud-Maschinen zur rechten Seitenleiste, zu den Einstellungen, zur Befehlspalette und zum Menü für neue Arbeitsbereiche hinzu. Cloud-Maschinen erfordern außerdem ein Remote-Rollout; solange diese Option deaktiviert ist, bleiben Cloud-Tunnel und Flottenabfrage aus." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ajoute les machines cloud à la barre latérale droite, aux Réglages, à la palette de commandes et au menu de nouvel espace de travail. Les machines cloud nécessitent aussi un déploiement à distance ; si cette option est désactivée, le tunnel Cloud et l’interrogation de la flotte restent arrêtés." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يضيف الأجهزة السحابية إلى الشريط الجانبي الأيمن والإعدادات ولوحة الأوامر وقائمة مساحة العمل الجديدة. وتتطلب الأجهزة السحابية أيضًا طرحًا عن بُعد؛ وعند إيقاف هذا الخيار يظل نفق Cloud واستطلاع الأسطول متوقفين." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Añade las máquinas en la nube a la barra lateral derecha, a Ajustes, a la paleta de comandos y al menú de nuevo espacio de trabajo. Las máquinas en la nube también requieren un despliegue remoto; con esta opción desactivada, el túnel de Cloud y el sondeo de la flota permanecen desactivados." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "在右側邊欄、設定、命令面板和新增工作區選單中加入雲端機器。雲端機器也需要遠端推出才能使用;關閉此選項時,Cloud 通道和機群輪詢都會保持停用。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在右侧边栏、设置、命令面板和新建工作区菜单中添加云主机。云主机还需要远程推出才能使用;关闭此选项时,Cloud 隧道和机群轮询都会保持停用。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "오른쪽 사이드바, 설정, 명령어 팔레트, 새 작업 공간 메뉴에 클라우드 머신을 추가합니다. 클라우드 머신은 원격 출시도 필요하며, 이 옵션이 꺼져 있으면 Cloud 터널과 플릿 폴링도 꺼진 상태로 유지됩니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "右サイドバー、設定、コマンドパレット、新規ワークスペースメニューにクラウドマシンを追加します。クラウドマシンにはリモートでの提供開始も必要です。この設定がオフの間は、Cloud トンネルとフリートのポーリングも停止したままです。" - } - } - } - }, - "settings.betaFeatures.customSidebars.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Adds sidebars from ~/.config/cmux/sidebars to the sidebar picker." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Fügt Seitenleisten aus ~/.config/cmux/sidebars zur Seitenleistenauswahl hinzu." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ajoute les barres latérales de ~/.config/cmux/sidebars au sélecteur de barre latérale." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يضيف الأشرطة الجانبية من ~/.config/cmux/sidebars إلى منتقي الشريط الجانبي." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Añade las barras laterales de ~/.config/cmux/sidebars al selector de barra lateral." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "在側邊欄選取器中加入 ~/.config/cmux/sidebars 裡的側邊欄。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在侧边栏选择器中添加 ~/.config/cmux/sidebars 里的侧边栏。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "~/.config/cmux/sidebars의 사이드바를 사이드바 선택기에 추가합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "~/.config/cmux/sidebars のサイドバーをサイドバーピッカーに追加します。" - } - } - } - }, - "settings.betaFeatures.dock.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Adds Dock to the right sidebar for custom terminal controls." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Fügt der rechten Seitenleiste das Dock für benutzerdefinierte Terminal-Steuerelemente hinzu." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ajoute le Dock à la barre latérale droite pour les commandes de terminal personnalisées." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يضيف Dock إلى الشريط الجانبي الأيمن لعناصر التحكم المخصصة في الطرفية." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Añade el Dock a la barra lateral derecha para controles de terminal personalizados." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "在右側邊欄加入 Dock,用來放置自訂的終端機控制項。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在右侧边栏添加 Dock,用来放置自定义的终端控件。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "오른쪽 사이드바에 Dock을 추가해 터미널 컨트롤을 직접 구성할 수 있습니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "右サイドバーに Dock を追加し、ターミナル用のカスタムコントロールを配置できるようにします。" - } - } - } - }, - "settings.betaFeatures.extensions.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Adds the extensions button and lets cmux install and run sidebar extensions." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Fügt die Taste „Erweiterungen“ hinzu und erlaubt cmux, Seitenleisten-Erweiterungen zu installieren und auszuführen." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ajoute le bouton des extensions et permet à cmux d’installer et d’exécuter des extensions de barre latérale." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يضيف زر الامتدادات ويتيح لـ cmux تثبيت امتدادات الشريط الجانبي وتشغيلها." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Añade el botón de extensiones y permite que cmux instale y ejecute extensiones de barra lateral." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "加入擴充功能按鈕,並讓 cmux 可安裝及執行側邊欄擴充功能。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "添加扩展按钮,并允许 cmux 安装和运行侧边栏扩展。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "확장 기능 버튼을 추가하고 cmux가 사이드바 확장 기능을 설치하고 실행할 수 있게 합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "エクステンションボタンを追加し、cmuxでサイドバーエクステンションをインストールして実行できるようにします。" - } - } - } - }, - "settings.betaFeatures.feed.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Adds Feed to the right sidebar for answering agent requests." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Fügt der rechten Seitenleiste den Feed zum Beantworten von Agentenanfragen hinzu." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ajoute le Flux à la barre latérale droite pour répondre aux demandes des agents." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يضيف Feed إلى الشريط الجانبي الأيمن للرد على طلبات الوكلاء." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Añade el Feed a la barra lateral derecha para responder a las solicitudes de los agentes." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "在右側邊欄加入 Feed,用於回覆代理程式的請求。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在右侧边栏添加 Feed,用于回复代理请求。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "오른쪽 사이드바에 피드를 추가해 에이전트 요청에 응답할 수 있습니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "右サイドバーにフィードを追加し、エージェントからのリクエストに応答できるようにします。" - } - } - } - }, - "settings.betaFeatures.remoteTmux.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Shows tmux sessions on remote hosts as workspaces in the sidebar. The sessions keep running after cmux quits." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Zeigt tmux-Sitzungen auf Remote-Hosts als Arbeitsbereiche in der Seitenleiste an. Die Sitzungen laufen auch nach dem Beenden von cmux weiter." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Affiche les sessions tmux des hôtes distants comme des espaces de travail dans la barre latérale. Les sessions continuent de s’exécuter après la fermeture de cmux." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يعرض جلسات tmux على المضيفين البعيدين كمساحات عمل في الشريط الجانبي. وتستمر الجلسات في العمل حتى بعد إنهاء cmux." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Muestra las sesiones de tmux de hosts remotos como espacios de trabajo en la barra lateral. Las sesiones siguen ejecutándose al salir de cmux." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "在側邊欄中將遠端主機上的 tmux 工作階段顯示為工作區。結束 cmux 後,這些工作階段仍會繼續執行。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在侧边栏中将远程主机上的 tmux 会话显示为工作区。退出 cmux 后,这些会话仍会继续运行。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "원격 호스트의 tmux 세션을 사이드바에 작업 공간으로 표시합니다. cmux를 종료해도 세션은 계속 실행됩니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "リモートホストの tmux セッションをサイドバーにワークスペースとして表示します。cmux を終了してもセッションは動作し続けます。" - } - } - } - }, - "settings.betaFeatures.workspaceTodoControls.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Shows Add Checklist Item and status controls on workspaces. A remote rollout can turn them on even while this is off." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Zeigt „Checklisten-Eintrag hinzufügen“ und Statussteuerelemente bei Arbeitsbereichen an. Ein Remote-Rollout kann sie auch dann aktivieren, wenn diese Option deaktiviert ist." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Affiche « Ajouter un élément à la liste de contrôle » et les commandes d’état sur les espaces de travail. Un déploiement à distance peut les activer même si cette option est désactivée." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يعرض «إضافة عنصر قائمة تحقق» وعناصر التحكم في الحالة على مساحات العمل. ويمكن لطرح عن بُعد تفعيلها حتى عند إيقاف هذا الخيار." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Muestra «Añadir elemento a la lista de verificación» y los controles de estado en los espacios de trabajo. Un despliegue remoto puede activarlos aunque esta opción esté desactivada." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "在工作區上顯示「新增檢查清單項目」和狀態控制項。即使關閉此選項,遠端推出仍可能啟用這些控制項。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在工作区上显示“添加清单项”和状态控件。即使关闭此选项,远程推出仍可能启用这些控件。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "작업 공간에 ‘체크리스트 항목 추가’ 및 상태 컨트롤을 표시합니다. 이 옵션이 꺼져 있어도 원격 출시로 이 컨트롤이 켜질 수 있습니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ワークスペースに「チェックリスト項目を追加」とステータスコントロールを表示します。この設定がオフでも、リモートでの提供開始によって有効になることがあります。" - } - } - } - }, - "settings.betaFeatures.workspaceTodosChecklistStyle.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Choose whether clicking a workspace checklist opens a popover or expands it under the row." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Wählen Sie, ob sich beim Klicken auf die Checkliste eines Arbeitsbereichs ein Popover öffnet oder die Checkliste unter der Zeile aufklappt." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Choisissez si un clic sur la liste de contrôle d’un espace de travail ouvre une fenêtre contextuelle ou la déplie sous la ligne." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "اختر ما إذا كان النقر على قائمة التحقق الخاصة بمساحة العمل يفتح نافذة منبثقة أو يوسّعها أسفل الصف." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Elige si al hacer clic en la lista de verificación de un espacio de trabajo se abre un popover o se despliega bajo la fila." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "選擇按一下工作區檢查清單時,要在彈出視窗中開啟,還是在該列下方展開。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "选择点按工作区清单时,是在浮层中打开,还是在该行下方展开。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "작업 공간 체크리스트를 클릭했을 때 팝오버로 열지, 행 아래에 펼칠지 선택합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ワークスペースのチェックリストをクリックしたときに、ポップオーバーで開くか、行の下に展開するかを選択します。" - } - } - } - }, - "settings.browser.enabled.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Opens browser tabs and links from terminals in the cmux browser." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Browser-Tabs und Links aus Terminals werden im cmux-Browser geöffnet." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ouvre les onglets de navigateur et les liens des terminaux dans le navigateur cmux." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يفتح علامات تبويب المتصفح والروابط من الطرفيات في متصفح cmux." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Abre pestañas del navegador y enlaces de las terminales en el navegador de cmux." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "瀏覽器標籤頁和從終端機開啟的連結都會在 cmux 瀏覽器中開啟。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "浏览器标签页和从终端打开的链接都会在 cmux 浏览器中打开。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "브라우저 탭과 터미널에서 연 링크를 cmux 브라우저에서 엽니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ブラウザタブとターミナルから開いたリンクをcmuxブラウザで開きます。" - } - } - } - }, - "settings.browser.hiddenWebViewDiscard.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Frees memory from browser tabs hidden longer than the delay. They reload when shown again." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Browser-Tabs, die länger als die festgelegte Zeit ausgeblendet sind, geben ihren Speicher frei und werden beim erneuten Anzeigen neu geladen." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Libère la mémoire des onglets de navigateur masqués depuis plus longtemps que le délai défini. Ils se rechargent lorsqu’ils réapparaissent." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يحرر ذاكرة علامات تبويب المتصفح المخفية لمدة تتجاوز المهلة المحددة، ويُعاد تحميلها عند عرضها مجددًا." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Libera la memoria de las pestañas del navegador que llevan ocultas más tiempo del indicado. Se vuelven a cargar al mostrarse de nuevo." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "瀏覽器標籤頁隱藏超過延遲時間後,會釋放其記憶體。再次顯示時會重新載入。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "浏览器标签页隐藏超过延迟时间后,会释放其内存。再次显示时会重新加载。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "브라우저 탭이 지연 시간보다 오래 숨겨져 있으면 메모리를 해제합니다. 탭을 다시 표시하면 새로 고칩니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "非表示の時間が遅延時間を超えたブラウザタブのメモリを解放します。タブは再び表示したときに再読み込みされます。" - } - } - } - }, - "settings.browser.theme.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Choose light or dark pages for sites that support both. System matches the app appearance." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Wählen Sie helle oder dunkle Seiten für Websites, die beides unterstützen. „System“ folgt dem Erscheinungsbild der App." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Choisissez des pages claires ou sombres pour les sites qui prennent en charge les deux. « Système » suit l’apparence de l’app." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "اختر الصفحات الفاتحة أو الداكنة للمواقع التي تدعم الوضعين. يتبع خيار «النظام» مظهر التطبيق." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Elige páginas claras u oscuras para los sitios compatibles con ambos modos. «Sistema» sigue el aspecto de la app." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "網站同時支援淺色與深色時,選擇要使用淺色或深色頁面。「系統」會跟隨 App 的外觀。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "网站同时支持浅色和深色时,选择使用浅色还是深色页面。“系统”会跟随应用外观。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "라이트와 다크를 모두 지원하는 사이트에서 표시할 페이지 모드를 선택합니다. ‘시스템’은 앱의 외관 설정을 따릅니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ライトとダークの両方に対応したサイトで、どちらのページを表示するかを選択します。「システム」はアプリの外観に合わせます。" - } - } - } - }, - "settings.customSidebars.enabled.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Adds sidebars from ~/.config/cmux/sidebars to the sidebar picker." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Fügt Seitenleisten aus ~/.config/cmux/sidebars zur Seitenleistenauswahl hinzu." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ajoute les barres latérales de ~/.config/cmux/sidebars au sélecteur de barre latérale." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يضيف الأشرطة الجانبية من ~/.config/cmux/sidebars إلى منتقي الشريط الجانبي." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Añade las barras laterales de ~/.config/cmux/sidebars al selector de barra lateral." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "在側邊欄選取器中加入 ~/.config/cmux/sidebars 裡的側邊欄。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在侧边栏选择器中添加 ~/.config/cmux/sidebars 里的侧边栏。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "~/.config/cmux/sidebars의 사이드바를 사이드바 선택기에 추가합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "~/.config/cmux/sidebars のサイドバーをサイドバーピッカーに追加します。" - } - } - } - }, - "settings.customSidebars.renderer.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Isolated process protects cmux from a faulty sidebar but accepts clicks only. In-app also accepts hover, focus, and typing." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "„Isolierter Prozess“ schützt cmux vor einer fehlerhaften Seitenleiste, unterstützt aber nur Klicks. „In-App“ unterstützt zusätzlich Hover, Fokus und Texteingabe." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "« Processus isolé » protège cmux d’une barre latérale défectueuse, mais n’accepte que les clics. « Dans l’application » accepte aussi le survol, le focus et la saisie." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يحمي خيار «عملية معزولة» cmux من الشريط الجانبي المعيب، لكنه لا يقبل سوى النقرات. أما خيار «داخل التطبيق» فيقبل أيضًا تمرير المؤشر والتركيز والكتابة." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "«Proceso aislado» protege cmux de una barra lateral defectuosa, pero solo admite clics. «En la aplicación» también admite pasar el puntero por encima, enfocar y escribir." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "「隔離程序」可保護 cmux 不受有問題的側邊欄影響,但只接受點擊。「應用程式內」還接受游標停留、聚焦和輸入。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "“隔离进程”可保护 cmux 不受有问题的侧边栏影响,但只接受点击。“应用内”还接受悬停、聚焦和输入。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "‘격리된 프로세스’는 결함이 있는 사이드바로부터 cmux를 보호하지만 클릭만 허용합니다. ‘앱 내부’는 포인터 올려놓기, 포커스, 키보드 입력도 허용합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "「分離プロセス」は問題のあるサイドバーから cmux を保護しますが、クリックしか受け付けません。「アプリ内」では、ホバー、フォーカス、入力も受け付けます。" - } - } - } - }, - "settings.globalHotkey.enable.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Pressing the shortcut in any app shows or hides all cmux windows." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Das Tastaturkürzel blendet in jeder App alle cmux-Fenster ein oder aus." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Le raccourci affiche ou masque toutes les fenêtres cmux, quelle que soit l’app active." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يُظهر الاختصار جميع نوافذ cmux أو يخفيها من أي تطبيق." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "El atajo muestra u oculta todas las ventanas de cmux desde cualquier app." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "在任何 App 中按下快速鍵,即可顯示或隱藏所有 cmux 視窗。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在任意 App 中按下快捷键,即可显示或隐藏所有 cmux 窗口。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "어느 앱에서든 단축키를 눌러 모든 cmux 윈도우를 표시하거나 숨길 수 있습니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "どのアプリを使用中でも、ショートカットを押すと cmux のすべてのウィンドウを表示または非表示にできます。" - } - } - } - }, - "settings.mobile.artifactFolderAccess.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Choose how much of a folder iPhone and iPad can browse. One Level shows only the items directly inside it." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Wählen Sie, wie tief iPhone und iPad einen Ordner durchsuchen können. „Eine Ebene“ zeigt nur die Objekte direkt darin." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Choisissez jusqu’où l’iPhone et l’iPad peuvent parcourir un dossier. « Un niveau » n’affiche que les éléments qui s’y trouvent directement." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "اختر مدى عمق تصفح iPhone وiPad داخل المجلد. يعرض خيار «مستوى واحد» العناصر الموجودة مباشرةً داخله فقط." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Elige hasta qué nivel pueden explorar una carpeta el iPhone y el iPad. «Un nivel» solo muestra los elementos que contiene directamente." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "選擇 iPhone 和 iPad 可瀏覽資料夾的範圍。「單一層級」只顯示該資料夾第一層的項目。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "选择 iPhone 和 iPad 可浏览文件夹的范围。“一层”只显示该文件夹第一层的项目。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "iPhone 및 iPad에서 폴더를 어디까지 탐색할 수 있는지 선택합니다. ‘한 단계’는 폴더 바로 아래에 있는 항목만 표시합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "iPhone と iPad からフォルダをどこまで参照できるかを選択します。「1 階層」では、そのフォルダの直下にある項目のみが表示されます。" - } - } - } - }, - "settings.mobile.iOSPairingHost.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Lets iPhone and iPad pair with and connect to this Mac." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "iPhone und iPad können sich mit diesem Mac koppeln und verbinden." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "L’iPhone et l’iPad peuvent se jumeler avec ce Mac et s’y connecter." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يمكن لـ iPhone وiPad الاقتران بجهاز Mac هذا والاتصال به." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "El iPhone y el iPad pueden emparejarse con este Mac y conectarse a él." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "允許 iPhone 和 iPad 與此 Mac 配對及連線。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "允许 iPhone 和 iPad 与这台 Mac 配对并连接。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "iPhone과 iPad가 이 Mac과 페어링하고 연결할 수 있게 합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "iPhone と iPad がこの Mac とペアリングして接続できるようにします。" - } - } - } - }, - "settings.mobile.phonePush.forwarding.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Sends agent notifications from this Mac to cmux on iPhone and iPad." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Leitet Agentenbenachrichtigungen von diesem Mac an cmux auf iPhone und iPad weiter." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Transfère les notifications des agents de ce Mac vers cmux sur iPhone et iPad." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يوجّه إشعارات الوكلاء من جهاز Mac هذا إلى cmux على iPhone وiPad." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Reenvía las notificaciones de agentes de este Mac a cmux en iPhone y iPad." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "將此 Mac 上的代理程式通知傳送至 iPhone 和 iPad 上的 cmux。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "将这台 Mac 上的代理通知发送到 iPhone 和 iPad 上的 cmux。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "이 Mac의 에이전트 알림을 iPhone 및 iPad의 cmux로 보냅니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "この Mac からエージェントの通知を iPhone と iPad の cmux に送信します。" - } - } - } - }, - "settings.shortcuts.showModifierHoldHints.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Holding Command or Control shows shortcut hints." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Hinweise zu Tastaturkürzeln erscheinen, solange die Befehlstaste oder die Control-Taste gedrückt ist." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Les indices de raccourcis apparaissent tant que la touche Commande ou Contrôle est maintenue enfoncée." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "تظهر تلميحات الاختصارات عند الضغط مع الاستمرار على Command أو Control." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Las sugerencias de atajos aparecen mientras se mantiene presionada la tecla Comando o Control." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "按住 Command 或 Control 鍵時,會顯示快速鍵提示。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "按住 Command 或 Control 键时,会显示快捷键提示。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "Command 또는 Control 키를 누르고 있으면 단축키 힌트가 표시됩니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Command キーまたは Control キーを押したままにすると、ショートカットヒントが表示されます。" - } - } - } - }, - "settings.sidebar.rightMaxWidth.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Lets the Dock in the right sidebar grow up to this width while leaving room for terminals." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Das Dock in der rechten Seitenleiste kann bis zu dieser Breite wachsen, lässt aber Platz für Terminals." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Le Dock de la barre latérale droite peut s’élargir jusqu’à cette largeur tout en laissant de la place aux terminaux." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يمكن أن يتسع Dock في الشريط الجانبي الأيمن حتى هذا العرض مع ترك مساحة للطرفيات." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "El Dock de la barra lateral derecha puede ampliarse hasta este ancho, pero deja espacio para las terminales." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "右側邊欄中的 Dock 最寬可擴展至此寬度,並為終端機保留空間。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "右侧边栏中的 Dock 最宽可扩展到此宽度,同时为终端保留空间。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "오른쪽 사이드바의 Dock은 터미널 공간을 남겨 둔 채 이 너비까지 넓어질 수 있습니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "右サイドバーの Dock は、ターミナルの領域を残したまま、この幅まで広げられます。" - } - } - } - }, - "settings.terminal.adaptiveDefaultTheme.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Matches terminal colors to the light or dark appearance when no Ghostty theme or colors are set." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Passt die Terminalfarben an das helle oder dunkle Erscheinungsbild an, wenn in Ghostty kein Theme und keine Farben festgelegt sind." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Si aucun thème ni aucune couleur Ghostty n’est défini, les couleurs du terminal suivent l’apparence claire ou sombre." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "عندما لا تكون هناك سمة أو ألوان محددة في Ghostty، تتوافق ألوان الطرفية مع المظهر الفاتح أو الداكن." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Si no hay ningún tema ni colores de Ghostty definidos, los colores de la terminal siguen la apariencia clara u oscura." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "未設定 Ghostty 主題或顏色時,終端機顏色會配合淺色或深色外觀。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "未设置 Ghostty 主题或颜色时,终端颜色会随浅色或深色外观变化。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "Ghostty 테마나 색상이 설정되어 있지 않으면 터미널 색상을 라이트 또는 다크 모드에 맞춥니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Ghostty のテーマや色が設定されていない場合、ターミナルの色をライトまたはダークの外観に合わせます。" - } - } - } - }, - "settings.terminal.agentAutoResume.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Reopening cmux resumes agent sessions automatically." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Beim erneuten Öffnen von cmux werden Agenten-Sitzungen automatisch fortgesetzt." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Les sessions d’agent reprennent automatiquement à la réouverture de cmux." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "تُستأنف جلسات الوكلاء تلقائيًا عند إعادة فتح cmux." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Las sesiones de agentes se reanudan automáticamente al volver a abrir cmux." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "重新開啟 cmux 時,自動繼續先前的代理工作階段。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "重新打开 cmux 时,自动恢复之前的代理会话。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "cmux를 다시 열면 에이전트 세션이 자동으로 재개됩니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "cmux を開き直すと、エージェントのセッションが自動的に再開されます。" - } - } - } - }, - "settings.terminal.agentHibernation.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Hibernates idle background agent terminals above the live terminal limit. Even when this is off, cmux may hibernate them under critical memory pressure." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Versetzt inaktive Agenten-Terminals im Hintergrund in den Ruhezustand, sobald das Limit für aktive Terminals überschritten ist. Auch wenn diese Option deaktiviert ist, kann cmux sie bei kritischer Speicherauslastung in den Ruhezustand versetzen." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Met en hibernation les terminaux d’agent inactifs en arrière-plan au-delà de la limite de terminaux actifs. Même si cette option est désactivée, cmux peut les mettre en hibernation en cas de pression mémoire critique." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يُدخل طرفيات الوكلاء الخاملة في الخلفية في السبات عند تجاوز حد الطرفيات النشطة. وحتى عند إيقاف هذا الخيار، قد يُدخلها cmux في السبات عند ضغط الذاكرة الحرج." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Pone en hibernación las terminales de agentes inactivas en segundo plano que superan el límite de terminales activas. Aunque esta opción esté desactivada, cmux puede ponerlas en hibernación si la presión de memoria es crítica." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "作用中終端機超過上限時,閒置的背景代理終端機會進入休眠。即使關閉此選項,cmux 仍可能在記憶體壓力達到嚴重程度時讓它們休眠。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "活动终端超过上限时,空闲的后台代理终端会进入休眠。即使关闭此选项,cmux 仍可能在内存压力达到严重程度时让它们休眠。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "활성 터미널 수가 한도를 넘으면 백그라운드에서 유휴 상태인 에이전트 터미널을 최대 절전 모드로 전환합니다. 이 옵션을 꺼도 메모리 압박이 심각하면 cmux가 해당 터미널을 최대 절전 모드로 전환할 수 있습니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "実行中のターミナル数が上限を超えると、アイドル状態のバックグラウンドエージェントのターミナルを休止させます。この設定がオフでも、メモリ逼迫が深刻な場合は cmux がこれらを休止させることがあります。" - } - } - } - }, - "settings.terminal.copyOnSelect.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Selecting text in a terminal copies it to the clipboard." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Im Terminal ausgewählter Text wird in die Zwischenablage kopiert." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Le texte sélectionné dans un terminal est copié dans le presse-papiers." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يُنسخ النص المحدد في الطرفية إلى الحافظة." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "El texto seleccionado en una terminal se copia al portapapeles." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "在終端機中選取文字時,會將文字複製到剪貼簿。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在终端中选择文本时,会将文本复制到剪贴板。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "터미널에서 텍스트를 선택하면 클립보드에 복사됩니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ターミナルでテキストを選択すると、クリップボードにコピーされます。" - } - } - } - }, - "settings.terminal.reflowHardWrapOnCopy.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Copying text also joins line breaks a program printed at the full terminal width and drops a short continuation indent. Soft-wrapped lines always copy as one line." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Beim Kopieren werden auch Zeilenumbrüche zusammengefügt, die ein Programm bei voller Terminalbreite ausgegeben hat, und ein kurzer Fortsetzungseinzug wird entfernt. Weich umbrochene Zeilen werden immer als eine Zeile kopiert." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Lors de la copie, les sauts de ligne qu’un programme a insérés à pleine largeur du terminal sont aussi supprimés, ainsi qu’un court retrait de continuation. Les lignes à retour automatique sont toujours copiées sur une seule ligne." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "عند النسخ، تُدمج أيضًا فواصل الأسطر التي طبعها برنامج عند العرض الكامل للطرفية، وتُحذف مسافة البادئة القصيرة للسطر التالي. أما الأسطر الملتفّة تلقائيًا فتُنسخ دائمًا كسطر واحد." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Al copiar, también se unen los saltos de línea que un programa imprimió al ancho completo de la terminal y se quita una sangría de continuación corta. Las líneas con ajuste automático siempre se copian como una sola línea." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "複製文字時,也會合併程式在終端機滿寬處輸出的換行,並移除較短的續行縮排。自動換行的行一律會複製為一行。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "复制文本时,也会合并程序在终端满宽处输出的换行,并移除较短的续行缩进。自动换行的行始终会复制为一行。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "텍스트를 복사할 때 프로그램이 터미널 전체 너비에서 넣은 줄바꿈도 합치고 짧은 연속 들여쓰기를 제거합니다. 자동 줄바꿈된 줄은 항상 한 줄로 복사됩니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "テキストをコピーするとき、プログラムがターミナルの幅いっぱいで出力した改行もつなげ、短い継続インデントを取り除きます。ソフトラップされた行は常に 1 行としてコピーされます。" - } - } - } - }, - "settings.terminal.memoryGuardrail.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Warns when the processes in one pane use too much memory." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Warnt, wenn die Prozesse in einem Teilfenster zu viel Speicher verbrauchen." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Avertit lorsque les processus d’un volet utilisent trop de mémoire." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "ينبّه عندما تستهلك العمليات في جزء واحد ذاكرة أكثر من اللازم." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Avisa cuando los procesos de un panel usan demasiada memoria." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "單一窗格中的處理程序使用過多記憶體時發出警告。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "某个窗格中的进程占用过多内存时发出警告。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "한 분할 창의 프로세스가 메모리를 너무 많이 사용하면 경고합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "1 つのペインのプロセスがメモリを使いすぎると警告します。" - } - } - } - }, - "settings.terminal.rendererRealization.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Frees graphics memory from terminals that are out of view. Their processes keep running." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Gibt den Grafikspeicher nicht sichtbarer Terminals frei. Ihre Prozesse laufen weiter." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Libère la mémoire graphique des terminaux hors de vue. Leurs processus continuent de s’exécuter." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يحرر ذاكرة الرسومات للطرفيات غير الظاهرة، مع استمرار تشغيل عملياتها." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Libera la memoria gráfica de las terminales fuera de la vista. Sus procesos siguen ejecutándose." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "釋放不在畫面上的終端機所使用的圖形記憶體,其處理程序仍會繼續執行。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "释放不在屏幕上的终端所占用的图形内存,其进程仍会继续运行。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "화면에 보이지 않는 터미널의 그래픽 메모리를 해제합니다. 해당 프로세스는 계속 실행됩니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "表示されていないターミナルのグラフィックスメモリを解放します。プロセスは動作し続けます。" - } - } - } - }, - "settings.terminal.scrollBar.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Shows a scroll bar in terminals, except in full-screen apps." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Zeigt in Terminals eine Bildlaufleiste an, außer in Vollbild-Apps." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Affiche une barre de défilement dans les terminaux, sauf dans les apps en plein écran." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يعرض شريط تمرير في الطرفيات، إلا في التطبيقات بملء الشاشة." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Muestra una barra de desplazamiento en las terminales, excepto en las apps a pantalla completa." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "在終端機中顯示捲軸(全螢幕 App 除外)。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在终端中显示滚动条(全屏 App 除外)。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "터미널에 스크롤 바를 표시합니다(전체 화면 앱 제외)." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ターミナルにスクロールバーを表示します(フルスクリーンアプリを除く)。" - } - } - } - }, - "settings.terminal.sessionContentWidth.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Wraps terminal and agent chat content at this width. Narrow panes still use their full width." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Terminal- und Agenten-Chat-Inhalte werden bei dieser Breite umbrochen. Schmalere Teilfenster nutzen weiterhin ihre volle Breite." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Le contenu du terminal et du chat de l’agent revient à la ligne à cette largeur. Les volets plus étroits conservent toute leur largeur." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يلتف محتوى الطرفية ودردشة الوكيل عند هذا العرض. أما الأجزاء الأضيق فتستخدم عرضها الكامل." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "El contenido de la terminal y del chat de agentes se ajusta a este ancho. Los paneles más estrechos siguen usando todo su ancho." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "終端機和代理對話的內容會在此寬度換行。較窄的窗格仍使用完整寬度。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "终端和代理对话的内容会在此宽度处换行。较窄的窗格仍使用全部宽度。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "터미널과 에이전트 채팅 내용을 이 너비에서 줄바꿈합니다. 이보다 좁은 분할 창에서는 전체 너비를 사용합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ターミナルとエージェントチャットの内容をこの幅で折り返します。これより狭いペインでは、ペインの幅いっぱいに表示します。" - } - } - } - }, - "settings.terminal.textEditingGestures.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Pressing Command or Option with an arrow key or Delete moves or deletes by line or word. Full-screen terminal apps receive the rewritten keys too, so turn this off for apps that need the original keys." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Mit der Befehls- oder Wahltaste und einer Pfeiltaste oder der Löschtaste bewegen Sie die Einfügemarke bzw. löschen Text zeilen- oder wortweise. Auch Terminal-Apps im Vollbildmodus erhalten die umgeschriebenen Tasten. Deaktivieren Sie diese Option für Apps, die die ursprünglichen Tasten benötigen." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Commande ou Option associée à une touche fléchée ou à Supprimer déplace le point d’insertion ou supprime du texte par ligne ou par mot. Les apps de terminal en plein écran reçoivent aussi les touches réécrites : désactivez cette option pour les apps qui ont besoin des touches d’origine." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يؤدي الضغط على Command أو Option مع مفتاح سهم أو مفتاح الحذف إلى التنقل أو الحذف سطرًا سطرًا أو كلمةً كلمة. وتتلقى تطبيقات الطرفية بملء الشاشة أيضًا المفاتيح المعاد كتابتها، لذا أوقف هذا الخيار للتطبيقات التي تحتاج إلى المفاتيح الأصلية." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Comando u Opción junto con una tecla de flecha o Eliminar mueve el punto de inserción o borra por líneas o palabras. Las apps de terminal a pantalla completa también reciben las teclas reescritas, así que desactiva esta opción para las apps que necesitan las teclas originales." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "按住 Command 或 Option 鍵再按方向鍵或 Delete 鍵,可按行或字詞移動或刪除。全螢幕終端機 App 也會收到改寫後的按鍵,因此若 App 需要原始按鍵,請關閉此選項。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "按住 Command 或 Option 键再按箭头键或 Delete 键,可按行或按词移动或删除。全屏终端 App 也会收到改写后的按键,因此如果 App 需要原始按键,请关闭此选项。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "Command 또는 Option 키를 누른 채 화살표 키나 Delete 키를 누르면 줄 또는 단어 단위로 이동하거나 삭제합니다. 전체 화면 터미널 앱에도 변환된 키가 전달되므로, 원래 키가 필요한 앱에서는 이 옵션을 끄세요." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Command キーまたは Option キーを押しながら矢印キーや Delete キーを押すと、行単位または単語単位で移動・削除します。フルスクリーンのターミナルアプリにも書き換えたキーが送られるため、元のキーが必要なアプリではこの設定をオフにしてください。" - } - } - } - }, - "settings.textBox.focusOnNewTerminals.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Opening a terminal tab, split, or workspace puts keyboard focus in the TextBox." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Wenn ein Terminal-Tab, eine Teilung oder ein Arbeitsbereich geöffnet wird, liegt der Tastaturfokus in der TextBox." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "À l’ouverture d’un onglet, d’une division ou d’un espace de travail de terminal, le focus du clavier est placé dans la TextBox." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "عند فتح علامة تبويب طرفية أو تقسيم أو مساحة عمل، ينتقل تركيز لوحة المفاتيح إلى TextBox." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Al abrir una pestaña de terminal, una división o un espacio de trabajo, el enfoque del teclado se sitúa en el TextBox." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "開啟終端機標籤頁、分割或工作區時,將鍵盤焦點移到 TextBox。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "打开终端标签页、分屏或工作区时,将键盘焦点移到 TextBox。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "터미널 탭, 분할 또는 작업 공간을 열면 키보드 포커스가 TextBox로 이동합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ターミナルタブ、分割、またはワークスペースを開くと、キーボードフォーカスが TextBox に移動します。" - } - } - } - }, - "settings.textBox.showOnNewTerminals.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Opening a terminal tab, split, or workspace shows the TextBox." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Beim Öffnen eines Terminal-Tabs, einer Teilung oder eines Arbeitsbereichs erscheint die TextBox." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "À l’ouverture d’un onglet, d’une division ou d’un espace de travail de terminal, la TextBox s’affiche." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "عند فتح علامة تبويب طرفية أو تقسيم أو مساحة عمل، يظهر TextBox." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Al abrir una pestaña de terminal, una división o un espacio de trabajo, se muestra el TextBox." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "開啟終端機標籤頁、分割或工作區時顯示 TextBox。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "打开终端标签页、分屏或工作区时显示 TextBox。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "터미널 탭, 분할 또는 작업 공간을 열면 TextBox가 표시됩니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ターミナルタブ、分割、またはワークスペースを開くと、TextBox が表示されます。" - } - } - } - }, - "settings.betaFeatures.predictedEcho.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Shows typed characters right away on slow remote connections. They stay underlined until the remote host confirms them. Password prompts and full-screen apps are excluded." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Getippte Zeichen erscheinen bei langsamen Remoteverbindungen sofort. Sie bleiben unterstrichen, bis der Remote-Host sie bestätigt. Ausgenommen sind Passwortabfragen und Vollbild-Apps." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Les caractères saisis s’affichent immédiatement sur les connexions distantes lentes. Ils restent soulignés jusqu’à ce que l’hôte distant les confirme. Cela ne s’applique ni aux invites de mot de passe ni aux apps en plein écran." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "تظهر الأحرف المكتوبة فورًا في الاتصالات البعيدة البطيئة، وتبقى مسطّرة حتى يؤكدها المضيف البعيد. ولا ينطبق ذلك على مطالبات كلمة المرور والتطبيقات بملء الشاشة." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Los caracteres escritos aparecen al instante en conexiones remotas lentas y permanecen subrayados hasta que el host remoto los confirma. No se aplica a solicitudes de contraseña ni a apps a pantalla completa." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "遠端連線較慢時,輸入的字元會立即顯示並加上底線,直到遠端主機確認為止。密碼提示和全螢幕 App 不適用此功能。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "远程连接较慢时,输入的字符会立即显示并带有下划线,直到远程主机确认为止。密码提示和全屏 App 不适用此功能。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "원격 연결이 느릴 때 입력한 문자를 바로 표시하고, 원격 호스트가 확인할 때까지 밑줄로 표시합니다. 비밀번호 프롬프트와 전체 화면 앱에는 적용되지 않습니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "リモート接続が遅いとき、入力した文字をすぐに表示し、リモートホストが確認するまで下線を付けます。パスワードの入力やフルスクリーンアプリには適用されません。" - } - } - } - }, - "settings.app.equalizeSplitsOnCreate.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Splitting a pane makes the panes in that direction equal in size." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Wird ein Teilfenster geteilt, werden alle Teilfenster in dieser Richtung gleich groß." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Lorsqu’un volet est divisé, les volets dans cette direction prennent la même taille." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "عند تقسيم جزء، تصبح الأجزاء في ذلك الاتجاه متساوية الحجم." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Al dividir un panel, los paneles en esa dirección se ajustan al mismo tamaño." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "分割窗格時,同一方向的窗格會調整為相同大小。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "拆分窗格时,同一方向的窗格会调整为相同大小。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "창을 분할하면 같은 방향의 분할 창이 모두 같은 크기가 됩니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ペインを分割すると、その方向のペインが同じサイズになります。" - } - } - } - }, - "settings.terminal.showPasswordInputIndicator.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Shows a lock badge in the terminal corner while a program such as sudo or ssh reads a password with echo off." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Zeigt in der Terminalecke ein Schloss-Symbol an, während ein Programm wie sudo oder ssh ein Passwort ohne Echo einliest." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Affiche un badge de cadenas dans le coin du terminal pendant qu'un programme comme sudo ou ssh lit un mot de passe sans écho." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يعرض شارة قفل في زاوية الطرفية أثناء قراءة برنامج مثل sudo أو ssh لكلمة مرور مع إيقاف الصدى." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Muestra una insignia de candado en la esquina del terminal mientras un programa como sudo o ssh lee una contraseña sin eco." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "當 sudo 或 ssh 等程式在關閉回顯的情況下讀取密碼時,在終端機角落顯示鎖頭標記。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "当 sudo 或 ssh 等程序在关闭回显的情况下读取密码时,在终端角落显示锁形标记。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "sudo나 ssh 같은 프로그램이 에코를 끈 상태로 비밀번호를 읽는 동안 터미널 모서리에 자물쇠 배지를 표시합니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "sudo や ssh などのプログラムがエコーをオフにしてパスワードを読み取っている間、ターミナルの隅に鍵のバッジを表示します。" - } - } - } - }, - "settings.terminal.showPasswordInputDots.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Shows one dot in the badge per typed character. cmux keeps only a count, never the characters." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Zeigt im Symbol einen Punkt pro eingegebenem Zeichen. cmux speichert nur die Anzahl, niemals die Zeichen." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Affiche dans le badge un point par caractère saisi. cmux ne conserve qu'un compteur, jamais les caractères." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يعرض في الشارة نقطة واحدة لكل حرف مكتوب. يحتفظ cmux بالعدد فقط، ولا يحتفظ بالأحرف أبدًا." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Muestra en la insignia un punto por cada carácter escrito. cmux solo guarda un recuento, nunca los caracteres." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "在標記中為每個已輸入的字元顯示一個圓點。cmux 只保留數量,絕不保留字元本身。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在标记中为每个已输入的字符显示一个圆点。cmux 只保留数量,绝不保留字符本身。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "배지에 입력한 문자마다 점을 하나씩 표시합니다. cmux는 개수만 유지하며 문자는 절대 보관하지 않습니다." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "入力した文字ごとにバッジにドットを 1 つ表示します。cmux が保持するのは数だけで、文字そのものは保持しません。" - } - } - } - }, "update.readyWaiting": { "extractionState": "manual", "localizations": { @@ -554258,596 +554848,6 @@ } } }, - "cli.help.send": { - "extractionState": "manual", - "localizations": { - "ar": { - "stringUnit": { - "state": "translated", - "value": "الاستخدام: cmux send [flags] [--] <text>\n\nيرسل نصًا إلى سطح طرفية كضغطات مفاتيح. تسلسلات الهروب: \\n و\\r ترسلان Enter، و\\t ترسل Tab.\n\nللنصوص الكبيرة (مثلًا أكثر من 4 كيلوبايت، أو عدة أسطر موجهة إلى وكيل)، استخدم --paste أو cmux paste. تضغط ضغطات المفاتيح Enter عند كل سطر جديد، مما قد يرسل كل سطر بمفرده.\n\nمع --paste، يمر النص دون تغيير عبر مسار اللصق نفسه الذي يستخدمه cmux paste وCmd+V: لا تُفسَّر تسلسلات الهروب مثل \\n، وتُستبدل أحرف التحكم مثل Esc وCtrl-C بمسافات. يجب أن يأتي --paste قبل النص.\n\nالخيارات:\n --workspace <id|ref|index> مساحة العمل المستهدفة (الافتراضي: $CMUX_WORKSPACE_ID)\n --surface <id|ref|index> السطح المستهدف (الافتراضي: $CMUX_SURFACE_ID)\n --window <id|ref|index> سياق النافذة لمراجع وفهارس مساحة العمل/السطح\n --paste يلصق النص بدلًا من كتابته\n\nمثال:\n cmux send \"echo hello\"\n cmux send --surface surface:2 \"ls -la\\n\"\n cmux send --paste --surface surface:2 \"$(cat notes.md)\"" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Verwendung: cmux send [flags] [--] <text>\n\nSendet Text als Tastatureingaben an eine Terminal-Oberfläche. Escape-Sequenzen: \\n und \\r senden Enter, \\t sendet Tab.\n\nFür große Texte (zum Beispiel über 4 KB oder mehrere Zeilen an einen Agenten) --paste oder cmux paste verwenden. Tastatureingaben drücken bei jedem Zeilenumbruch Enter, wodurch jede Zeile einzeln abgesendet werden kann.\n\nMit --paste geht der Text unverändert über denselben Einfügepfad wie cmux paste und Cmd+V: Escape-Sequenzen wie \\n werden nicht interpretiert, und Steuerzeichen wie Esc und Strg-C werden durch Leerzeichen ersetzt. --paste muss vor dem Text stehen.\n\nFlags:\n --workspace <id|ref|index> Ziel-Workspace (Standard: $CMUX_WORKSPACE_ID)\n --surface <id|ref|index> Ziel-Oberfläche (Standard: $CMUX_SURFACE_ID)\n --window <id|ref|index> Fensterkontext für Workspace-/Oberflächen-Refs und -Indizes\n --paste Fügt den Text ein, statt ihn zu tippen\n\nBeispiel:\n cmux send \"echo hello\"\n cmux send --surface surface:2 \"ls -la\\n\"\n cmux send --paste --surface surface:2 \"$(cat notes.md)\"" - } - }, - "en": { - "stringUnit": { - "state": "translated", - "value": "Usage: cmux send [flags] [--] <text>\n\nSend text to a terminal surface as keystrokes. Escape sequences: \\n and \\r send Enter, \\t sends Tab.\n\nFor large text (for example over 4 KB, or several lines going to an agent), use --paste or cmux paste. Keystrokes press Enter at every newline, which can submit each line on its own.\n\nWith --paste, the text goes through the same paste path as cmux paste and Cmd+V, unchanged: escape sequences such as \\n are not interpreted, and control characters such as Esc and Ctrl-C are replaced with spaces. --paste must come before the text.\n\nFlags:\n --workspace <id|ref|index> Target workspace (default: $CMUX_WORKSPACE_ID)\n --surface <id|ref|index> Target surface (default: $CMUX_SURFACE_ID)\n --window <id|ref|index> Window context for workspace/surface refs and indexes\n --paste Paste the text instead of typing it\n\nExample:\n cmux send \"echo hello\"\n cmux send --surface surface:2 \"ls -la\\n\"\n cmux send --paste --surface surface:2 \"$(cat notes.md)\"" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Uso: cmux send [flags] [--] <text>\n\nEnvía texto a una superficie de terminal como pulsaciones de teclas. Secuencias de escape: \\n y \\r envían Intro, \\t envía Tab.\n\nPara textos grandes (por ejemplo, más de 4 KB o varias líneas dirigidas a un agente), usa --paste o cmux paste. Las pulsaciones presionan Intro en cada salto de línea, lo que puede enviar cada línea por separado.\n\nCon --paste, el texto pasa sin cambios por la misma ruta de pegado que cmux paste y Cmd+V: las secuencias de escape como \\n no se interpretan y los caracteres de control como Esc y Ctrl-C se reemplazan por espacios. --paste debe ir antes del texto.\n\nOpciones:\n --workspace <id|ref|index> Espacio de trabajo de destino (predeterminado: $CMUX_WORKSPACE_ID)\n --surface <id|ref|index> Superficie de destino (predeterminada: $CMUX_SURFACE_ID)\n --window <id|ref|index> Contexto de ventana para referencias e índices de espacio de trabajo/superficie\n --paste Pega el texto en lugar de escribirlo\n\nEjemplo:\n cmux send \"echo hello\"\n cmux send --surface surface:2 \"ls -la\\n\"\n cmux send --paste --surface surface:2 \"$(cat notes.md)\"" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Utilisation : cmux send [flags] [--] <text>\n\nEnvoie du texte à une surface de terminal sous forme de frappes. Séquences d’échappement : \\n et \\r envoient Entrée, \\t envoie Tab.\n\nPour un texte volumineux (par exemple plus de 4 Ko, ou plusieurs lignes destinées à un agent), utilisez --paste ou cmux paste. Les frappes appuient sur Entrée à chaque saut de ligne, ce qui peut envoyer chaque ligne séparément.\n\nAvec --paste, le texte passe tel quel par le même chemin de collage que cmux paste et Cmd+V : les séquences d’échappement comme \\n ne sont pas interprétées, et les caractères de contrôle comme Échap et Ctrl-C sont remplacés par des espaces. --paste doit précéder le texte.\n\nOptions :\n --workspace <id|ref|index> Espace de travail cible (par défaut : $CMUX_WORKSPACE_ID)\n --surface <id|ref|index> Surface cible (par défaut : $CMUX_SURFACE_ID)\n --window <id|ref|index> Contexte de fenêtre pour les références et index d’espace de travail/surface\n --paste Colle le texte au lieu de le taper\n\nExemple :\n cmux send \"echo hello\"\n cmux send --surface surface:2 \"ls -la\\n\"\n cmux send --paste --surface surface:2 \"$(cat notes.md)\"" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "使用方法: cmux send [flags] [--] <text>\n\nテキストをキー入力としてターミナルサーフェスに送信します。エスケープシーケンス: \\n と \\r は Enter、\\t は Tab を送信します。\n\n大きなテキスト(たとえば 4 KB を超えるもの、またはエージェントに送る複数行)には --paste または cmux paste を使ってください。キー入力では改行ごとに Enter が押されるため、各行が個別に送信されることがあります。\n\n--paste を使うと、テキストはそのまま cmux paste や Cmd+V と同じ貼り付け経路で送られます。\\n などのエスケープシーケンスは解釈されず、Esc や Ctrl-C などの制御文字はスペースに置き換えられます。--paste はテキストより前に指定してください。\n\nフラグ:\n --workspace <id|ref|index> 対象ワークスペース(既定: $CMUX_WORKSPACE_ID)\n --surface <id|ref|index> 対象サーフェス(既定: $CMUX_SURFACE_ID)\n --window <id|ref|index> ワークスペース/サーフェスの参照とインデックスに使うウィンドウコンテキスト\n --paste テキストを入力する代わりに貼り付けます\n\n例:\n cmux send \"echo hello\"\n cmux send --surface surface:2 \"ls -la\\n\"\n cmux send --paste --surface surface:2 \"$(cat notes.md)\"" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "사용법: cmux send [flags] [--] <text>\n\n텍스트를 키 입력으로 터미널 서피스에 보냅니다. 이스케이프 시퀀스: \\n과 \\r은 Enter, \\t는 Tab을 보냅니다.\n\n큰 텍스트(예: 4 KB 초과 또는 에이전트로 보내는 여러 줄)에는 --paste 또는 cmux paste를 사용하세요. 키 입력은 줄바꿈마다 Enter를 누르므로 각 줄이 따로 제출될 수 있습니다.\n\n--paste를 쓰면 텍스트가 변경 없이 cmux paste 및 Cmd+V와 같은 붙여넣기 경로로 전달됩니다. \\n 같은 이스케이프 시퀀스는 해석되지 않으며, Esc와 Ctrl-C 같은 제어 문자는 공백으로 바뀝니다. --paste는 텍스트보다 앞에 와야 합니다.\n\n플래그:\n --workspace <id|ref|index> 대상 워크스페이스 (기본값: $CMUX_WORKSPACE_ID)\n --surface <id|ref|index> 대상 서피스 (기본값: $CMUX_SURFACE_ID)\n --window <id|ref|index> 워크스페이스/서피스 참조와 인덱스에 사용할 창 컨텍스트\n --paste 텍스트를 입력하는 대신 붙여넣습니다\n\n예:\n cmux send \"echo hello\"\n cmux send --surface surface:2 \"ls -la\\n\"\n cmux send --paste --surface surface:2 \"$(cat notes.md)\"" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "用法:cmux send [flags] [--] <text>\n\n以按键方式将文本发送到终端表面。转义序列:\\n 和 \\r 发送 Enter,\\t 发送 Tab。\n\n对于大量文本(例如超过 4 KB,或要发给代理的多行文本),请使用 --paste 或 cmux paste。按键方式会在每个换行处按下 Enter,可能让每一行单独提交。\n\n使用 --paste 时,文本会原样通过与 cmux paste 和 Cmd+V 相同的粘贴路径发送:不会解释 \\n 等转义序列,Esc 和 Ctrl-C 等控制字符会被替换为空格。--paste 必须放在文本之前。\n\n标志:\n --workspace <id|ref|index> 目标工作区(默认:$CMUX_WORKSPACE_ID)\n --surface <id|ref|index> 目标表面(默认:$CMUX_SURFACE_ID)\n --window <id|ref|index> 工作区/表面引用与索引的窗口上下文\n --paste 粘贴文本,而不是逐键输入\n\n示例:\n cmux send \"echo hello\"\n cmux send --surface surface:2 \"ls -la\\n\"\n cmux send --paste --surface surface:2 \"$(cat notes.md)\"" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "用法:cmux send [flags] [--] <text>\n\n以按鍵方式將文字傳送到終端機表面。跳脫序列:\\n 和 \\r 送出 Enter,\\t 送出 Tab。\n\n若是大量文字(例如超過 4 KB,或要送給代理的多行文字),請使用 --paste 或 cmux paste。按鍵方式會在每個換行處按下 Enter,可能讓每一行各自送出。\n\n使用 --paste 時,文字會原封不動地透過與 cmux paste 和 Cmd+V 相同的貼上路徑送出:不會解譯 \\n 等跳脫序列,Esc 和 Ctrl-C 等控制字元會被替換為空格。--paste 必須放在文字之前。\n\n旗標:\n --workspace <id|ref|index> 目標工作區(預設:$CMUX_WORKSPACE_ID)\n --surface <id|ref|index> 目標表面(預設:$CMUX_SURFACE_ID)\n --window <id|ref|index> 工作區/表面參照與索引所用的視窗上下文\n --paste 貼上文字,而不是逐鍵輸入\n\n範例:\n cmux send \"echo hello\"\n cmux send --surface surface:2 \"ls -la\\n\"\n cmux send --paste --surface surface:2 \"$(cat notes.md)\"" - } - } - } - }, - "cli.send.hint.usePaste": { - "extractionState": "manual", - "localizations": { - "ar": { - "stringUnit": { - "state": "translated", - "value": "تلميح: كُتب كل سطر جديد كضغطة Enter؛ للنصوص الكبيرة متعددة الأسطر استخدم cmux send --paste أو cmux paste لإرسالها كلصقة واحدة" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Hinweis: Jeder Zeilenumbruch wurde als Enter getippt; für große mehrzeilige Texte cmux send --paste oder cmux paste verwenden, um sie als ein einziges Einfügen zu senden" - } - }, - "en": { - "stringUnit": { - "state": "translated", - "value": "hint: each newline was typed as Enter; for large multi-line text use cmux send --paste or cmux paste to deliver it as one paste" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "sugerencia: cada salto de línea se escribió como Intro; para textos grandes de varias líneas usa cmux send --paste o cmux paste para enviarlos como un solo pegado" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "astuce : chaque saut de ligne a été tapé comme Entrée ; pour un texte volumineux sur plusieurs lignes, utilisez cmux send --paste ou cmux paste pour l’envoyer en un seul collage" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ヒント: 各改行は Enter として入力されました。大きな複数行テキストは cmux send --paste または cmux paste を使うと 1 回の貼り付けとして送れます" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "힌트: 각 줄바꿈이 Enter로 입력되었습니다. 큰 여러 줄 텍스트는 cmux send --paste 또는 cmux paste를 사용해 한 번의 붙여넣기로 보내세요" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "提示:每个换行都作为 Enter 输入;对于大量多行文本,请使用 cmux send --paste 或 cmux paste 以一次粘贴发送" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "提示:每個換行都以 Enter 輸入;若是大量多行文字,請使用 cmux send --paste 或 cmux paste 以單次貼上送出" - } - } - } - }, - "settings.terminal.importFromTerminal": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Import from Another Terminal" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "別のターミナルから読み込む" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "从其他终端导入" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Aus anderem Terminal importieren" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Importer depuis un autre terminal" - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "استيراد من طرفية أخرى" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Importar desde otra terminal" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "從其他終端機匯入" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "다른 터미널에서 가져오기" - } - } - } - }, - "settings.terminal.importFromTerminal.button": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Import…" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "読み込む…" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "导入…" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Importieren…" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Importer…" - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "استيراد…" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Importar…" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "匯入…" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "가져오기…" - } - } - } - }, - "settings.terminal.importFromTerminal.subtitle": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Bring over your font, colors, cursor and more from iTerm2, Terminal, Alacritty, Kitty, WezTerm or Warp." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "iTerm2、ターミナル、Alacritty、Kitty、WezTerm、Warp からフォント、カラー、カーソルなどを引き継ぎます。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "从 iTerm2、终端、Alacritty、Kitty、WezTerm 或 Warp 导入字体、颜色、光标等设置。" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Übernimmt Schrift, Farben, Cursor und mehr aus iTerm2, Terminal, Alacritty, Kitty, WezTerm oder Warp." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Reprend la police, les couleurs, le curseur et plus encore depuis iTerm2, Terminal, Alacritty, Kitty, WezTerm ou Warp." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "انقل الخط والألوان والمؤشر وغيرها من iTerm2 أو Terminal أو Alacritty أو Kitty أو WezTerm أو Warp." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Trae la fuente, los colores, el cursor y más desde iTerm2, Terminal, Alacritty, Kitty, WezTerm o Warp." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "從 iTerm2、終端機、Alacritty、Kitty、WezTerm 或 Warp 匯入字體、顏色、游標等設定。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "iTerm2, 터미널, Alacritty, Kitty, WezTerm, Warp에서 글꼴, 색상, 커서 등을 가져옵니다." - } - } - } - }, - "workspaceGroup.error.customColorIsNotAnInputKey": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "unknown key custom_color; set_color accepts hex or color" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "不明なキー custom_color です。set_color は hex または color を受け付けます" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "未知的键 custom_color;set_color 接受 hex 或 color" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Unbekannter Schlüssel custom_color; set_color akzeptiert hex oder color" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Clé inconnue custom_color ; set_color accepte hex ou color" - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "مفتاح غير معروف custom_color؛ يقبل set_color المفتاح hex أو color" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Clave desconocida custom_color; set_color acepta hex o color" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "未知的鍵 custom_color;set_color 接受 hex 或 color" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "알 수 없는 키 custom_color입니다. set_color는 hex 또는 color를 받습니다" - } - } - } - }, - "workspaceGroup.error.colorMustBeHex": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "color must be a 6-digit hex color like #FF3EA5 (leading # optional)" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "color は #FF3EA5 のような 6 桁の 16 進カラーである必要があります(先頭の # は省略可)" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "color 必须是 6 位十六进制颜色,例如 #FF3EA5(开头的 # 可省略)" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "color muss eine 6-stellige Hex-Farbe wie #FF3EA5 sein (führendes # optional)" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "color doit être une couleur hexadécimale à 6 chiffres comme #FF3EA5 (# initial facultatif)" - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يجب أن تكون قيمة color لونًا سداسيًا عشريًا من 6 خانات مثل #FF3EA5 (الرمز # في البداية اختياري)" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "color debe ser un color hexadecimal de 6 dígitos como #FF3EA5 (el # inicial es opcional)" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "color 必須是 6 位十六進位顏色,例如 #FF3EA5(開頭的 # 可省略)" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "color는 #FF3EA5 같은 6자리 16진수 색상이어야 합니다(앞의 #는 생략 가능)" - } - } - } - }, - "workspaceGroup.error.colorMustBeString": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "color must be a string holding a hex color" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "color は 16 進カラーを含む文字列である必要があります" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "color 必须是包含十六进制颜色的字符串" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "color muss ein String mit einer Hex-Farbe sein" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "color doit être une chaîne contenant une couleur hexadécimale" - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يجب أن تكون قيمة color سلسلة نصية تحتوي على لون سداسي عشري" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "color debe ser una cadena de texto con un color hexadecimal" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "color 必須是包含十六進位顏色的字串" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "color는 16진수 색상을 담은 문자열이어야 합니다" - } - } - } - }, - "workspaceGroup.error.iconSymbolIsNotAnInputKey": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "unknown key icon_symbol; set_icon accepts symbol or icon" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "不明なキー icon_symbol です。set_icon は symbol または icon を受け付けます" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "未知的键 icon_symbol;set_icon 接受 symbol 或 icon" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Unbekannter Schlüssel icon_symbol; set_icon akzeptiert symbol oder icon" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Clé inconnue icon_symbol ; set_icon accepte symbol ou icon" - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "مفتاح غير معروف icon_symbol؛ يقبل set_icon المفتاح symbol أو icon" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Clave desconocida icon_symbol; set_icon acepta symbol o icon" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "未知的鍵 icon_symbol;set_icon 接受 symbol 或 icon" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "알 수 없는 키 icon_symbol입니다. set_icon은 symbol 또는 icon을 받습니다" - } - } - } - }, - "workspaceGroup.error.symbolMustBeString": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "symbol must be a string" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "symbol は文字列である必要があります" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "symbol 必须是字符串" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "symbol muss ein String sein" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "symbol doit être une chaîne de caractères" - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يجب أن تكون قيمة symbol سلسلة نصية" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "symbol debe ser una cadena de texto" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "symbol 必須是字串" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "symbol은 문자열이어야 합니다" - } - } - } - }, "terminal.paste.notice.imageTooLarge": { "extractionState": "manual", "localizations": { From b429afe5545a6d0b32dc63ce0983b5b04083aa3b Mon Sep 17 00:00:00 2001 From: Leo Li <cheerleaderleo@outlook.com> Date: Sun, 27 Sep 2026 13:36:13 -0400 Subject: [PATCH 09/11] AppDelegate+AgentChat: import CmuxSettings for the socket path Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --- Sources/AppDelegate+AgentChat.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/AppDelegate+AgentChat.swift b/Sources/AppDelegate+AgentChat.swift index 7bea893d0794..97fdd563d696 100644 --- a/Sources/AppDelegate+AgentChat.swift +++ b/Sources/AppDelegate+AgentChat.swift @@ -1,5 +1,6 @@ import AppKit import CMUXAgentLaunch +import CmuxSettings import Foundation import os import Security From 33623f096a508ce04505c8354f6f1a2a55b2873f Mon Sep 17 00:00:00 2001 From: teamleaderleo <cheerleaderleo@outlook.com> Date: Sun, 27 Sep 2026 14:17:57 -0400 Subject: [PATCH 10/11] agent-chat: keep the sidecar up on an unreadable transcript; undo a failed terminal send - TranscriptTail's timer catches a rejected poll: open or read failing after stat (a deleted or root-owned transcript) was an unhandled rejection that ended the whole sidecar. - A failed send to the terminal carries its prompt on the error event. The client drops that prompt's optimistic echo and puts the status back to what the server last said, instead of showing "running" until the next transcript write. - An optimistic echo matches anywhere in the queue, so one that never lands or lands rewritten no longer doubles every later prompt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --- agent-chat/adapters/transcript.ts | 12 ++++++++---- agent-chat/src/session.ts | 22 +++++++++++++++++++--- agent-chat/test/session.test.ts | 6 ++++++ agent-chat/types.ts | 3 ++- 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/agent-chat/adapters/transcript.ts b/agent-chat/adapters/transcript.ts index dd93d8921a5d..25fde57cb302 100644 --- a/agent-chat/adapters/transcript.ts +++ b/agent-chat/adapters/transcript.ts @@ -376,8 +376,12 @@ export class TranscriptTail { start() { if (this.timer) return; - void this.poll(); - this.timer = setInterval(() => void this.poll(), this.opts.pollMs ?? TRANSCRIPT_POLL_MS); + // A transcript that stops being readable between stat and open (deleted, + // or a root-owned file) must not reject out of the timer: an unhandled + // rejection ends the whole sidecar. The next poll tries again. + const tick = () => void this.poll().catch(() => {}); + tick(); + this.timer = setInterval(tick, this.opts.pollMs ?? TRANSCRIPT_POLL_MS); } stop() { @@ -510,11 +514,11 @@ export const transcriptAdapter: Adapter = { async send(sess: SessionCtx, prompt: string) { const target = transcriptTarget(sess); if (!target) { - sess.emit({ kind: "error", message: "This view is not attached to a terminal session." }); + sess.emit({ kind: "error", message: "This view is not attached to a terminal session.", prompt }); return; } const res = await rpc("mobile.chat.send", { session_id: target.agentSessionId, text: prompt }); - if (!res.ok) sess.emit({ kind: "error", message: `Couldn't send to the terminal: ${res.error}` }); + if (!res.ok) sess.emit({ kind: "error", message: `Couldn't send to the terminal: ${res.error}`, prompt }); }, stop(sess: SessionCtx) { const target = transcriptTarget(sess); diff --git a/agent-chat/src/session.ts b/agent-chat/src/session.ts index 89598ea55615..6dc7aba178f0 100644 --- a/agent-chat/src/session.ts +++ b/agent-chat/src/session.ts @@ -33,7 +33,7 @@ export type AgentEvent = | { kind: "tool-end"; toolId: string; name?: string; detail?: string; ok?: boolean } | { kind: "done"; stats?: string } | { kind: "files-changed"; files: ChangedFile[] } - | { kind: "error"; message: string }; + | { kind: "error"; message: string; prompt?: string }; export type OptionKind = "select" | "toggle"; export type OptionValue = string | boolean; @@ -292,9 +292,12 @@ export function restoreComposerDraft(storage: Pick<Storage, "setItem">, prompt: storage.setItem(composerDraftKey, prompt); } +// An echo matches anywhere in the queue: one that never lands (a failed send) +// or lands rewritten (`!ls` recorded as a bash input) must not block the rest. export function consumeOptimisticUserEcho(queue: string[], text: string): boolean { - if (queue[0] !== text) return false; - queue.shift(); + const index = queue.indexOf(text); + if (index < 0) return false; + queue.splice(index, 1); return true; } @@ -355,6 +358,9 @@ export function useSession(): SessionState { } | null>(null); const pendingStartTimeoutRef = useRef<number | null>(null); const optimisticUsersRef = useRef<string[]>([]); + // The last status the server sent: reply() shows "running" before the server + // knows, and a send that fails puts this back. + const serverStatusRef = useRef<string | null>(null); const sessionModeRef = useRef<SessionSummary["mode"]>(routedToTranscript ? "transcript" : undefined); useEffect(() => { if (session) sessionModeRef.current = session.mode; @@ -461,6 +467,7 @@ export function useSession(): SessionState { sendRaw({ op: "send", sessionId: msg.session.id, requestId: queued.requestId, prompt: queued.prompt }); } } else { + serverStatusRef.current = msg.session.status; setSession(msg.session); setRouting(msg.routing?.kind === "routing" ? normalizeRouteStatus(msg.routing) : null); setBlocks([]); @@ -483,6 +490,7 @@ export function useSession(): SessionState { } sessionIdRef.current = msg.session.id; document.title = msg.session.title || "cmux agent"; + serverStatusRef.current = msg.session.status; setSession(msg.session); setRouting(latestRouteStatus(msg.events as AgentEvent[])); setBlocks((msg.events as AgentEvent[]).reduce(foldEvent, [] as Block[])); @@ -511,6 +519,7 @@ export function useSession(): SessionState { break; case "session-status": if (msg.sessionId === sessionIdRef.current) { + serverStatusRef.current = msg.status; setSession((s) => (s ? { ...s, status: msg.status } : s)); } break; @@ -537,6 +546,13 @@ export function useSession(): SessionState { if (evt.kind === "options") setActions(evt.actions ?? {}); if (evt.kind === "commands") setCommands((gs) => upsertCommands(gs, evt)); if (evt.kind === "error") setForkPending(false); + if (evt.kind === "error" && evt.prompt !== undefined) { + // The prompt never reached the terminal: its echo will not + // come, and the agent is as busy as the server last said. + consumeOptimisticUserEcho(optimisticUsersRef.current, evt.prompt); + const status = serverStatusRef.current ?? "idle"; + setSession((s) => (s ? { ...s, status } : s)); + } } break; case "session-forked": diff --git a/agent-chat/test/session.test.ts b/agent-chat/test/session.test.ts index dc8ba17a6c75..b9e60641c3a4 100644 --- a/agent-chat/test/session.test.ts +++ b/agent-chat/test/session.test.ts @@ -33,6 +33,12 @@ if (consumeOptimisticUserEcho(optimistic, "same")) { throw new Error("non-optimistic repeated user message should not be suppressed"); } +// An echo that never lands (a failed send) or lands rewritten must not block later ones. +const stuck: string[] = ["!ls", "next"]; +if (!consumeOptimisticUserEcho(stuck, "next") || stuck.length !== 1 || stuck[0] !== "!ls") { + throw new Error("a later echo should match past an entry whose echo never landed"); +} + if (!shouldAcceptHandoffResponse("session-1", "session-1", "session-1")) { throw new Error("current pending handoff response should be accepted"); } diff --git a/agent-chat/types.ts b/agent-chat/types.ts index 5d68f800a27f..fd3c106851fa 100644 --- a/agent-chat/types.ts +++ b/agent-chat/types.ts @@ -28,7 +28,8 @@ export type AgentEvent = | { kind: "tool-end"; toolId: string; name?: string; detail?: string; ok?: boolean } | { kind: "done"; stats?: string } | { kind: "files-changed"; files: ChangedFile[] } - | { kind: "error"; message: string }; + // `prompt`: the prompt a failed send carried, which never reached the agent. + | { kind: "error"; message: string; prompt?: string }; export type SessionStatus = "idle" | "running" | "exited" | "error"; export type OptionKind = "select" | "toggle"; From 77efef6c1d24ea989a4eb97d3471dce19a97a1b2 Mon Sep 17 00:00:00 2001 From: teamleaderleo <cheerleaderleo@outlook.com> Date: Sun, 27 Sep 2026 14:31:33 -0400 Subject: [PATCH 11/11] agent-chat: the delivery-failure test expects the failed prompt on the error Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --- agent-chat/test/transcript.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent-chat/test/transcript.test.ts b/agent-chat/test/transcript.test.ts index 136ffce6c72e..6a6ac1dcc20e 100644 --- a/agent-chat/test/transcript.test.ts +++ b/agent-chat/test/transcript.test.ts @@ -231,7 +231,7 @@ describe("terminal delivery", () => { setTranscriptRpcForTest(async () => ({ ok: false, error: "not_found" })); const sess = fakeSession({ agentSessionId: "claude-1234" }); await transcriptAdapter.send(sess, "hi"); - expect(sess.events).toEqual([{ kind: "error", message: "Couldn't send to the terminal: not_found" }]); + expect(sess.events).toEqual([{ kind: "error", message: "Couldn't send to the terminal: not_found", prompt: "hi" }]); expect((await focusTranscriptTerminal(sess)).ok).toBe(false); }); });