diff --git a/apps/macos/Tests/OpenClawIPCTests/MacNodeHostWorkerTests.swift b/apps/macos/Tests/OpenClawIPCTests/MacNodeHostWorkerTests.swift index 72ab8c7f1665..1bb0e246a3f4 100644 --- a/apps/macos/Tests/OpenClawIPCTests/MacNodeHostWorkerTests.swift +++ b/apps/macos/Tests/OpenClawIPCTests/MacNodeHostWorkerTests.swift @@ -79,7 +79,7 @@ struct MacNodeHostWorkerTests { test "$OPENCLAW_NODE_EXEC_HOST" = app || exit 42 test "$OPENCLAW_NODE_EXEC_FALLBACK" = 0 || exit 43 printf '%s\\n' '{"type":"ready","version":"test","manifest":{"caps":["system"],"commands":["system.run"],"pathEnv":"/usr/bin:/bin"},"inventory":{"skills":null,"pluginTools":[]}}' - printf '%s\\n' '{"type":"gateway-request","id":"gateway-1","method":"skills.bins","params":{},"timeoutMs":1000}' + printf '%s\\n' '{"type":"gateway-request","id":"gateway-1","method":"node.invoke.progress","params":{"invokeId":"terminal-1","nodeId":"node-1","seq":0,"chunk":"hello"},"timeoutMs":1000}' IFS= read -r unavailable printf '%s' "$unavailable" | grep -q '"type":"gateway-response"' || exit 44 printf '%s' "$unavailable" | grep -q '"ok":false' || exit 45 diff --git a/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayNodeSession.swift b/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayNodeSession.swift index 8d77fe23bb5b..e0c5696bf5df 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayNodeSession.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayNodeSession.swift @@ -1536,13 +1536,7 @@ extension GatewayNodeSession { NSLocalizedDescriptionKey: "paramsJSON not UTF-8", ]) } - let raw = try JSONSerialization.jsonObject(with: data) - guard let dict = raw as? [String: Any] else { - return nil - } - return dict.reduce(into: [:]) { acc, entry in - acc[entry.key] = AnyCodable(entry.value) - } + return try JSONDecoder().decode([String: AnyCodable].self, from: data) } private func broadcastServerEvent(_ evt: EventFrame) { diff --git a/apps/shared/OpenClawKit/Tests/OpenClawKitTests/GatewayNodeSessionTests.swift b/apps/shared/OpenClawKit/Tests/OpenClawKitTests/GatewayNodeSessionTests.swift index a8ad343b87a3..76b7bbefa16a 100644 --- a/apps/shared/OpenClawKit/Tests/OpenClawKitTests/GatewayNodeSessionTests.swift +++ b/apps/shared/OpenClawKit/Tests/OpenClawKitTests/GatewayNodeSessionTests.swift @@ -604,6 +604,51 @@ private func nodeInvokePush(id: String, command: String) -> GatewayPush { @Suite(.serialized) struct GatewayNodeSessionTests { + @Test func `node requests preserve numeric JSON params`() async throws { + let session = FakeGatewayWebSocketSession() + let gateway = GatewayNodeSession() + let options = GatewayConnectOptions( + role: "node", + scopes: [], + caps: ["terminal"], + commands: ["codex.terminal.resume.v1"], + permissions: [:], + clientId: "openclaw-macos", + clientMode: "node", + clientDisplayName: "macOS Test", + includeDeviceIdentity: false) + + try await gateway.connect( + url: #require(URL(string: "ws://gateway.example.invalid")), + connectOptions: options, + sessionBox: WebSocketSessionBox(session: session), + onConnected: {}, + onDisconnected: { _ in }, + onInvoke: { request in BridgeInvokeResponse(id: request.id, ok: true) }) + + let pending = Task { + try await gateway.request( + method: "node.invoke.progress", + paramsJSON: #"{"invokeId":"terminal-1","nodeId":"node-1","seq":0,"ratio":1.5}"#, + timeoutSeconds: 30) + } + try await waitUntil("progress request sent") { + session.latestTask()?.sentRequestCount(method: "node.invoke.progress") == 1 + } + let task = try #require(session.latestTask()) + let request = try #require(task.sentRequests(method: "node.invoke.progress").first) + let params = try #require(request["params"] as? [String: Any]) + let seq = try #require(params["seq"] as? NSNumber) + let ratio = try #require(params["ratio"] as? NSNumber) + + #expect(CFGetTypeID(seq) != CFBooleanGetTypeID()) + #expect(seq.intValue == 0) + #expect(ratio.doubleValue == 1.5) + + await gateway.disconnect() + _ = try? await pending.value + } + @Test func `node invoke input and cancellation reach route callbacks`() async throws { let session = FakeGatewayWebSocketSession() let gateway = GatewayNodeSession() diff --git a/docs/.generated/plugin-sdk-api-baseline.sha256 b/docs/.generated/plugin-sdk-api-baseline.sha256 index c4c2446379e0..b440d33ba884 100644 --- a/docs/.generated/plugin-sdk-api-baseline.sha256 +++ b/docs/.generated/plugin-sdk-api-baseline.sha256 @@ -1,2 +1,2 @@ -202ccd0c71f23d8448f37dfc7b10ee2e8b2e12a90b9498740da5b757fb90513f plugin-sdk-api-baseline.json -265925043de4f1e2beed2660c5fcdc9c7b79b570e94afbe1dd3074bc304cea98 plugin-sdk-api-baseline.jsonl +8e04207c5befa97e4b9cc3e2b885f2f00eef115cad139a5a0907eb985a817904 plugin-sdk-api-baseline.json +495f677730bc73c9e021ebe8ce9dc984d99436f7700c69d2a0a95c88850c383a plugin-sdk-api-baseline.jsonl diff --git a/extensions/acpx/src/pi-session-catalog-plugin.ts b/extensions/acpx/src/pi-session-catalog-plugin.ts index 5ac4b5cd55c3..b2e91e6b77f9 100644 --- a/extensions/acpx/src/pi-session-catalog-plugin.ts +++ b/extensions/acpx/src/pi-session-catalog-plugin.ts @@ -326,7 +326,9 @@ async function listPiHosts( }).then((page) => setTerminalCapability( page, - resolveExecutableFromPathEnv("pi", process.env.PATH ?? "") !== undefined, + resolveExecutableFromPathEnv("pi", process.env.PATH ?? "", process.env, { + fallbackToLoginShell: true, + }) !== undefined, ), )), }); @@ -396,14 +398,18 @@ async function openPiTerminal(params: { const title = `pi --session ${params.threadId.slice(0, 12)}…`; if (params.hostId === LOCAL_HOST_ID) { const record = await requireLocalPiSession(params.threadId); - const executable = resolveExecutableFromPathEnv("pi", process.env.PATH ?? ""); - if (!executable) { + const resolution = resolveExecutableFromPathEnv("pi", process.env.PATH ?? "", process.env, { + fallbackToLoginShell: true, + withPathEnv: true, + }); + if (!resolution) { throw new Error("Pi CLI is unavailable"); } return { kind: "local", - argv: [executable, "--session", params.threadId], + argv: [resolution.executable, "--session", params.threadId], ...(record.cwd ? { cwd: record.cwd } : {}), + ...(resolution.pathEnv ? { pathEnv: resolution.pathEnv } : {}), title, }; } diff --git a/extensions/acpx/src/pi-session-catalog.test.ts b/extensions/acpx/src/pi-session-catalog.test.ts index f395b827ad5f..73c4cee2d345 100644 --- a/extensions/acpx/src/pi-session-catalog.test.ts +++ b/extensions/acpx/src/pi-session-catalog.test.ts @@ -10,7 +10,19 @@ const nodeHostMocks = vi.hoisted(() => ({ vi.mock("openclaw/plugin-sdk/node-host", async (importOriginal) => { const actual = await importOriginal(); - return { ...actual, runNodePtyCommand: nodeHostMocks.runNodePtyCommand }; + return { + ...actual, + runNodePtyCommand: nodeHostMocks.runNodePtyCommand, + resolveExecutableFromPathEnv: ( + command: string, + pathEnv: string, + env?: NodeJS.ProcessEnv, + options?: { withPathEnv?: boolean }, + ) => + options?.withPathEnv + ? actual.resolveExecutableFromPathEnv(command, pathEnv, env, { withPathEnv: true }) + : actual.resolveExecutableFromPathEnv(command, pathEnv, env), + }; }); import { registerPiSessionCatalog } from "./pi-session-catalog-plugin.js"; diff --git a/extensions/anthropic/session-catalog-terminal.ts b/extensions/anthropic/session-catalog-terminal.ts index c5502b1b212f..d234ed444948 100644 --- a/extensions/anthropic/session-catalog-terminal.ts +++ b/extensions/anthropic/session-catalog-terminal.ts @@ -25,7 +25,11 @@ type ClaudeTerminalDependencies = { }; export function isClaudeCliAvailable(pathEnv = process.env.PATH ?? ""): boolean { - return resolveExecutableFromPathEnv("claude", pathEnv) !== undefined; + const env = { ...process.env, PATH: pathEnv }; + return ( + resolveExecutableFromPathEnv("claude", pathEnv, env, { fallbackToLoginShell: true }) !== + undefined + ); } export function claudeNodeTerminalCapability(node: { @@ -90,14 +94,18 @@ export async function openClaudeCatalogTerminal( if (!source?.isFile()) { throw new ClaudeCatalogParamsError("Claude session transcript is unavailable"); } - const executable = resolveExecutableFromPathEnv("claude", process.env.PATH ?? ""); - if (!executable) { + const resolution = resolveExecutableFromPathEnv("claude", process.env.PATH ?? "", process.env, { + fallbackToLoginShell: true, + withPathEnv: true, + }); + if (!resolution) { throw new ClaudeCatalogParamsError("Claude CLI is unavailable"); } return { kind: "local", - argv: [executable, "--resume", params.threadId], + argv: [resolution.executable, "--resume", params.threadId], ...(record.cwd ? { cwd: record.cwd } : {}), + ...(resolution.pathEnv ? { pathEnv: resolution.pathEnv } : {}), title, }; } diff --git a/extensions/anthropic/session-catalog.test.ts b/extensions/anthropic/session-catalog.test.ts index 4d4cd767e72e..920af05c51aa 100644 --- a/extensions/anthropic/session-catalog.test.ts +++ b/extensions/anthropic/session-catalog.test.ts @@ -28,12 +28,39 @@ const originalHome = process.env.HOME; const originalPath = process.env.PATH; const nodeHostMocks = vi.hoisted(() => ({ runNodePtyCommand: vi.fn(async () => ({ exitCode: 0 })), + userShellPaths: new Map(), })); -vi.mock("openclaw/plugin-sdk/node-host", async (importOriginal) => ({ - ...(await importOriginal()), - runNodePtyCommand: nodeHostMocks.runNodePtyCommand, -})); +vi.mock("openclaw/plugin-sdk/node-host", async (importOriginal) => { + const actual = await importOriginal(); + const resolveWithTestShellPath = ( + command: string, + pathEnv: string, + env: NodeJS.ProcessEnv | undefined, + options: { fallbackToLoginShell?: boolean; withPathEnv?: boolean } | undefined, + ) => { + const fallbackPath = options?.fallbackToLoginShell + ? nodeHostMocks.userShellPaths.get(command) + : undefined; + const resolution = actual.resolveExecutableFromPathEnv(command, fallbackPath ?? pathEnv, env, { + withPathEnv: true, + }); + return resolution && fallbackPath ? { ...resolution, pathEnv: fallbackPath } : resolution; + }; + return { + ...actual, + runNodePtyCommand: nodeHostMocks.runNodePtyCommand, + resolveExecutableFromPathEnv: ( + command: string, + pathEnv: string, + env: NodeJS.ProcessEnv | undefined, + options: { fallbackToLoginShell?: boolean; withPathEnv?: boolean } | undefined, + ) => { + const resolution = resolveWithTestShellPath(command, pathEnv, env, options); + return options?.withPathEnv ? resolution : resolution?.executable; + }, + }; +}); async function createHome(): Promise { const home = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-claude-catalog-")); @@ -103,6 +130,7 @@ function message( afterEach(async () => { nodeHostMocks.runNodePtyCommand.mockClear(); + nodeHostMocks.userShellPaths.clear(); process.env.HOME = originalHome; process.env.PATH = originalPath; await Promise.all(homes.splice(0).map((home) => fs.rm(home, { recursive: true, force: true }))); @@ -1007,7 +1035,7 @@ describe("Claude session catalog", () => { ).rejects.toThrow("Claude session cannot be resumed in a terminal"); }); - it("requires the Claude binary for local terminal capability and plans", async () => { + it("uses the login-shell Claude binary for local terminal capability and plans", async () => { const home = await createHome(); process.env.HOME = home; const sessionId = "claude-session-1"; @@ -1023,9 +1051,11 @@ describe("Claude session catalog", () => { ], transcripts: { [sessionId]: [message(sessionId, "user", "hello", 1)] }, }); - const binDir = path.join(home, "bin"); - await fs.mkdir(binDir); - process.env.PATH = binDir; + const daemonBinDir = path.join(home, "daemon-bin"); + const shellBinDir = path.join(home, "shell-bin"); + await fs.mkdir(daemonBinDir); + await fs.mkdir(shellBinDir); + process.env.PATH = daemonBinDir; let provider: SessionCatalogProvider | undefined; registerClaudeSessionCatalog({ id: "anthropic", @@ -1047,8 +1077,9 @@ describe("Claude session catalog", () => { provider?.openTerminal?.({ hostId: "gateway:local", threadId: sessionId }), ).rejects.toThrow("Claude CLI is unavailable"); - await fs.writeFile(path.join(binDir, "claude"), "#!/bin/sh\n"); - await fs.chmod(path.join(binDir, "claude"), 0o755); + await fs.writeFile(path.join(shellBinDir, "claude"), "#!/bin/sh\n"); + await fs.chmod(path.join(shellBinDir, "claude"), 0o755); + nodeHostMocks.userShellPaths.set("claude", shellBinDir); await expect(provider?.list({})).resolves.toMatchObject([ { sessions: [{ threadId: sessionId, canOpenTerminal: true }] }, ]); @@ -1056,8 +1087,9 @@ describe("Claude session catalog", () => { provider?.openTerminal?.({ hostId: "gateway:local", threadId: sessionId }), ).resolves.toMatchObject({ kind: "local", - argv: [path.join(binDir, "claude"), "--resume", sessionId], + argv: [path.join(shellBinDir, "claude"), "--resume", sessionId], cwd: home, + pathEnv: shellBinDir, }); await expect( provider?.openTerminal?.({ hostId: "gateway:local", threadId: "missing" }), diff --git a/extensions/codex/src/session-catalog-terminal.ts b/extensions/codex/src/session-catalog-terminal.ts index 26590479de7f..c510ab3a5a9e 100644 --- a/extensions/codex/src/session-catalog-terminal.ts +++ b/extensions/codex/src/session-catalog-terminal.ts @@ -30,9 +30,18 @@ import type { export const CODEX_TERMINAL_RESUME_COMMAND = "codex.terminal.resume.v1"; export function resolveLocalCodexTerminalExecutable( - pathEnv = process.env.PATH ?? "", + env: NodeJS.ProcessEnv = process.env, ): string | undefined { - return resolveExecutableFromPathEnv("codex", pathEnv); + return resolveExecutableFromPathEnv("codex", env.PATH ?? env.Path ?? "", env, { + fallbackToLoginShell: true, + }); +} + +function resolveLocalCodexTerminalExecutableWithPathEnv(env: NodeJS.ProcessEnv = process.env) { + return resolveExecutableFromPathEnv("codex", env.PATH ?? env.Path ?? "", env, { + fallbackToLoginShell: true, + withPathEnv: true, + }); } export function codexNodeTerminalCapability(node: { @@ -173,16 +182,17 @@ export async function openCodexCatalogTerminal(params: { const title = `codex resume ${params.threadId.slice(0, 8)}…`; if (params.hostId === CODEX_LOCAL_SESSION_HOST_ID) { const record = await requireCatalogEligibleThread(params.control, params.threadId); - const executable = resolveLocalCodexTerminalExecutable(); + const resolution = resolveLocalCodexTerminalExecutableWithPathEnv(); // A managed app-server may exist without a local CLI. Fail closed so // terminal resume never targets a different machine or missing binary. - if (!executable) { + if (!resolution) { throw new CatalogParamsError("Codex CLI is unavailable"); } return { kind: "local", - argv: [executable, "resume", params.threadId], + argv: [resolution.executable, "resume", params.threadId], ...(record.cwd ? { cwd: record.cwd } : {}), + ...(resolution.pathEnv ? { pathEnv: resolution.pathEnv } : {}), title, }; } diff --git a/extensions/codex/src/session-catalog.test.ts b/extensions/codex/src/session-catalog.test.ts index 7934e821604a..f9852c746194 100644 --- a/extensions/codex/src/session-catalog.test.ts +++ b/extensions/codex/src/session-catalog.test.ts @@ -78,10 +78,22 @@ vi.mock("./app-server/shared-client.js", () => ({ vi.mock("./app-server/transcript-mirror.js", () => ({ importCodexThreadHistoryToTranscript: transcriptMirrorMocks.importCodexThreadHistoryToTranscript, })); -vi.mock("openclaw/plugin-sdk/node-host", async (importOriginal) => ({ - ...(await importOriginal()), - runNodePtyCommand: nodeHostMocks.runNodePtyCommand, -})); +vi.mock("openclaw/plugin-sdk/node-host", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + runNodePtyCommand: nodeHostMocks.runNodePtyCommand, + resolveExecutableFromPathEnv: ( + command: string, + pathEnv: string, + env?: NodeJS.ProcessEnv, + options?: { withPathEnv?: boolean }, + ) => + options?.withPathEnv + ? actual.resolveExecutableFromPathEnv(command, pathEnv, env, { withPathEnv: true }) + : actual.resolveExecutableFromPathEnv(command, pathEnv, env), + }; +}); type CreateSessionEntryParams = Parameters< PluginRuntime["agent"]["session"]["createSessionEntry"] diff --git a/extensions/opencode/session-catalog-plugin.ts b/extensions/opencode/session-catalog-plugin.ts index 8bfb3063ad14..0901a3c37b33 100644 --- a/extensions/opencode/session-catalog-plugin.ts +++ b/extensions/opencode/session-catalog-plugin.ts @@ -1,6 +1,7 @@ import { accessSync, constants, statSync } from "node:fs"; import path from "node:path"; import process from "node:process"; +import { resolveExecutableFromPathEnv } from "openclaw/plugin-sdk/node-host"; import type { OpenClawPluginApi, OpenClawPluginNodeHostCommand, @@ -317,7 +318,12 @@ async function listOpenCodeHosts( const requested = query.hostIds ? new Set(query.hostIds) : undefined; const searchTerm = query.search?.trim().slice(0, MAX_SEARCH_LENGTH) || undefined; const hosts: SessionCatalogHost[] = []; - if ((!requested || requested.has(LOCAL_HOST_ID)) && executableOnPath("opencode", process.env)) { + if ( + (!requested || requested.has(LOCAL_HOST_ID)) && + resolveExecutableFromPathEnv("opencode", process.env.PATH ?? "", process.env, { + fallbackToLoginShell: true, + }) + ) { try { hosts.push({ hostId: LOCAL_HOST_ID, diff --git a/extensions/opencode/session-catalog-terminal.ts b/extensions/opencode/session-catalog-terminal.ts index f8e401eac822..03f7e2ecf24b 100644 --- a/extensions/opencode/session-catalog-terminal.ts +++ b/extensions/opencode/session-catalog-terminal.ts @@ -112,14 +112,20 @@ export async function openOpenCodeCatalogTerminal( const title = `opencode --session ${params.threadId.slice(0, 12)}…`; if (params.hostId === OPENCODE_LOCAL_SESSION_HOST_ID) { const record = await requireLocalOpenCodeSession(params.threadId); - const executable = resolveExecutableFromPathEnv("opencode", process.env.PATH ?? ""); - if (!executable) { + const resolution = resolveExecutableFromPathEnv( + "opencode", + process.env.PATH ?? "", + process.env, + { fallbackToLoginShell: true, withPathEnv: true }, + ); + if (!resolution) { throw new Error("OpenCode CLI is unavailable"); } return { kind: "local", - argv: [executable, "--session", params.threadId], + argv: [resolution.executable, "--session", params.threadId], ...(record.cwd ? { cwd: record.cwd } : {}), + ...(resolution.pathEnv ? { pathEnv: resolution.pathEnv } : {}), title, }; } diff --git a/extensions/opencode/session-catalog.test.ts b/extensions/opencode/session-catalog.test.ts index 0509958847d7..880baf1d0a01 100644 --- a/extensions/opencode/session-catalog.test.ts +++ b/extensions/opencode/session-catalog.test.ts @@ -10,7 +10,19 @@ const nodeHostMocks = vi.hoisted(() => ({ vi.mock("openclaw/plugin-sdk/node-host", async (importOriginal) => { const actual = await importOriginal(); - return { ...actual, runNodePtyCommand: nodeHostMocks.runNodePtyCommand }; + return { + ...actual, + runNodePtyCommand: nodeHostMocks.runNodePtyCommand, + resolveExecutableFromPathEnv: ( + command: string, + pathEnv: string, + env?: NodeJS.ProcessEnv, + options?: { withPathEnv?: boolean }, + ) => + options?.withPathEnv + ? actual.resolveExecutableFromPathEnv(command, pathEnv, env, { withPathEnv: true }) + : actual.resolveExecutableFromPathEnv(command, pathEnv, env), + }; }); import { diff --git a/src/gateway/server-methods/terminal-open-plan.ts b/src/gateway/server-methods/terminal-open-plan.ts index 726cdb05c885..7d9ffb45810d 100644 --- a/src/gateway/server-methods/terminal-open-plan.ts +++ b/src/gateway/server-methods/terminal-open-plan.ts @@ -55,6 +55,8 @@ export function resolveTerminalOpenSpawnPlan( return resolveTerminalSpawnPlan(launchPlan); } if (catalogPlan.kind === "local") { + // Keep catalog commands behind the policy-selected login shell. Its profile restores + // CLI dependencies omitted from a sparse Gateway daemon PATH before the shebang runs. return resolveTerminalSpawnPlan({ ...launchPlan, initialCommand: catalogPlan.argv, diff --git a/src/gateway/server-methods/terminal.test.ts b/src/gateway/server-methods/terminal.test.ts index 9e4d020ee87c..7a8633087eb9 100644 --- a/src/gateway/server-methods/terminal.test.ts +++ b/src/gateway/server-methods/terminal.test.ts @@ -7,7 +7,7 @@ import { createEmptyPluginRegistry } from "../../plugins/registry-empty.js"; import { resetPluginRuntimeStateForTest, setActivePluginRegistry } from "../../plugins/runtime.js"; import type { SessionCatalogProvider } from "../../plugins/session-catalog.js"; import { createTerminalLaunchPolicy } from "../terminal/launch.js"; -import { terminalHandlers } from "./terminal.js"; +import { terminalHandlers, TERMINAL_OPEN_DEADLINE_MS } from "./terminal.js"; const policyMocks = vi.hoisted(() => ({ resolveNodeCommandAllowlist: vi.fn(() => new Set()), @@ -160,6 +160,7 @@ describe("terminal gateway policy", () => { const openTerminal = vi.fn(async () => ({ kind: "local" as const, argv: ["codex", "resume", "thread"], + pathEnv: "/login-shell/bin:/usr/bin", title: "codex resume thread", })); installCatalog({ @@ -188,6 +189,7 @@ describe("terminal gateway policy", () => { expect.objectContaining({ shell: expect.any(String), args: ["-il", "-c", "'codex' 'resume' 'thread'"], + env: expect.objectContaining({ PATH: "/login-shell/bin:/usr/bin" }), }), ); expect(respond).toHaveBeenCalledWith( @@ -196,6 +198,77 @@ describe("terminal gateway policy", () => { ); }); + it("rejects a catalog plan that finishes after the absolute open deadline", async () => { + vi.useFakeTimers(); + vi.setSystemTime(0); + try { + installCatalog({ + id: "codex", + label: "Codex", + list: async () => [], + read: async (request) => ({ ...request, items: [] }), + openTerminal: async () => { + vi.setSystemTime(TERMINAL_OPEN_DEADLINE_MS); + return { kind: "local", argv: ["codex", "resume", "thread"] }; + }, + }); + const { opts, sessions, respond } = makeOpts( + { + cols: 80, + rows: 24, + catalog: { catalogId: "codex", hostId: "gateway:local", threadId: "thread" }, + }, + { enabled: true }, + ); + + await expectDefined(terminalHandlers["terminal.open"], "terminal.open")(opts); + + expect(sessions.open).not.toHaveBeenCalled(); + expect(respond).toHaveBeenCalledWith( + false, + undefined, + expect.objectContaining({ message: "terminal open timed out" }), + ); + } finally { + vi.useRealTimers(); + } + }); + + it("maps a catalog rejection after the absolute deadline to a timeout", async () => { + vi.useFakeTimers(); + vi.setSystemTime(0); + try { + installCatalog({ + id: "codex", + label: "Codex", + list: async () => [], + read: async (request) => ({ ...request, items: [] }), + openTerminal: async () => { + vi.setSystemTime(TERMINAL_OPEN_DEADLINE_MS); + throw new Error("late catalog failure"); + }, + }); + const { opts, respond } = makeOpts( + { + cols: 80, + rows: 24, + catalog: { catalogId: "codex", hostId: "gateway:local", threadId: "thread" }, + }, + { enabled: true }, + ); + + await expectDefined(terminalHandlers["terminal.open"], "terminal.open")(opts); + + expect(respond).toHaveBeenCalledWith( + false, + undefined, + expect.objectContaining({ message: "terminal open timed out" }), + ); + } finally { + vi.useRealTimers(); + } + }); + it("does not create a terminal after the owning connection closes during catalog lookup", async () => { const plan = deferred<{ kind: "local"; argv: string[] }>(); const openTerminal = vi.fn(() => plan.promise); @@ -229,6 +302,83 @@ describe("terminal gateway policy", () => { ); }); + it("closes a terminal whose owning connection disappears during PTY creation", async () => { + const created = deferred<{ + ok: true; + sessionId: string; + agentId: string; + shell: string; + cwd: string; + }>(); + const { opts, sessions, respond, isConnectionActive } = makeOpts( + { cols: 80, rows: 24 }, + { enabled: true }, + ); + sessions.open.mockImplementationOnce(async () => await created.promise); + + const opening = expectDefined(terminalHandlers["terminal.open"], "terminal.open")(opts); + await vi.waitFor(() => expect(sessions.open).toHaveBeenCalledOnce()); + isConnectionActive.mockReturnValue(false); + created.resolve({ + ok: true, + sessionId: "terminal-raced", + agentId: "main", + shell: "/bin/zsh", + cwd: "/work", + }); + await opening; + + expect(sessions.close).toHaveBeenCalledWith("conn-1", "terminal-raced"); + expect(respond).toHaveBeenCalledWith( + false, + undefined, + expect.objectContaining({ message: "terminal connection closed" }), + ); + }); + + it("times out one terminal open with request-scoped cancellation", async () => { + vi.useFakeTimers(); + try { + const created = deferred<{ + ok: true; + sessionId: string; + agentId: string; + shell: string; + cwd: string; + }>(); + let openSignal: AbortSignal | undefined; + const { opts, sessions, respond } = makeOpts({ cols: 80, rows: 24 }, { enabled: true }); + sessions.open.mockImplementationOnce(async (request: unknown) => { + openSignal = (request as { signal?: AbortSignal }).signal; + return await created.promise; + }); + + const opening = expectDefined(terminalHandlers["terminal.open"], "terminal.open")(opts); + await vi.waitFor(() => expect(openSignal).toBeDefined()); + await vi.advanceTimersByTimeAsync(TERMINAL_OPEN_DEADLINE_MS); + await opening; + + expect(openSignal?.aborted).toBe(true); + expect(respond).toHaveBeenCalledWith( + false, + undefined, + expect.objectContaining({ message: "terminal open timed out" }), + ); + created.resolve({ + ok: true, + sessionId: "terminal-late", + agentId: "main", + shell: "/bin/zsh", + cwd: "/work", + }); + await vi.waitFor(() => + expect(sessions.close).toHaveBeenCalledWith("conn-1", "terminal-late"), + ); + } finally { + vi.useRealTimers(); + } + }); + it("does not create a terminal when disabled during catalog lookup", async () => { const plan = deferred<{ kind: "local"; argv: string[] }>(); const openTerminal = vi.fn(() => plan.promise); diff --git a/src/gateway/server-methods/terminal.ts b/src/gateway/server-methods/terminal.ts index cd92c15a47ae..072fcf3cc8f7 100644 --- a/src/gateway/server-methods/terminal.ts +++ b/src/gateway/server-methods/terminal.ts @@ -20,6 +20,7 @@ import { validateTerminalTextParams, validateTerminalUploadResult, } from "../../../packages/gateway-protocol/src/index.js"; +import { toErrorObject } from "../../infra/errors.js"; import { NODE_TERMINAL_UPLOAD_COMMAND } from "../../infra/node-commands.js"; import type { TerminalUploadFile } from "../../infra/terminal-file-upload.js"; import type { SessionCatalogTerminalPlan } from "../../plugins/session-catalog.js"; @@ -53,6 +54,91 @@ function terminalEnabled(context: GatewayRequestHandlerOptions["context"]): bool return context.isTerminalEnabled(); } +export const TERMINAL_OPEN_DEADLINE_MS = 30_000; + +class TerminalOpenDeadlineError extends Error { + constructor() { + super("terminal open timed out"); + this.name = "TerminalOpenDeadlineError"; + } +} + +type TerminalOpenDeadline = { + expiresAtMs: number; + controller: AbortController; +}; + +function createTerminalOpenDeadline(): TerminalOpenDeadline { + return { + expiresAtMs: Date.now() + TERMINAL_OPEN_DEADLINE_MS, + controller: new AbortController(), + }; +} + +function expireTerminalOpenDeadline(deadline: TerminalOpenDeadline): Error { + if (!deadline.controller.signal.aborted) { + deadline.controller.abort(new TerminalOpenDeadlineError()); + } + return toErrorObject(deadline.controller.signal.reason, "Terminal open timed out"); +} + +async function waitForTerminalOpenDeadline( + run: () => Promise, + deadline: TerminalOpenDeadline, +): Promise { + if (deadline.controller.signal.aborted || Date.now() >= deadline.expiresAtMs) { + throw expireTerminalOpenDeadline(deadline); + } + return await new Promise((resolve, reject) => { + const onAbort = () => { + clearTimeout(timer); + reject(expireTerminalOpenDeadline(deadline)); + }; + const timer = setTimeout( + () => expireTerminalOpenDeadline(deadline), + Math.max(0, deadline.expiresAtMs - Date.now()), + ); + deadline.controller.signal.addEventListener("abort", onAbort, { once: true }); + let promise: Promise; + try { + promise = run(); + } catch (error) { + if (deadline.controller.signal.aborted || Date.now() >= deadline.expiresAtMs) { + expireTerminalOpenDeadline(deadline); + return; + } + clearTimeout(timer); + deadline.controller.signal.removeEventListener("abort", onAbort); + reject(toErrorObject(error, "Terminal open failed")); + return; + } + void promise.then( + (value) => { + if (deadline.controller.signal.aborted || Date.now() >= deadline.expiresAtMs) { + expireTerminalOpenDeadline(deadline); + return; + } + clearTimeout(timer); + deadline.controller.signal.removeEventListener("abort", onAbort); + resolve(value); + }, + (error: unknown) => { + if (deadline.controller.signal.aborted || Date.now() >= deadline.expiresAtMs) { + expireTerminalOpenDeadline(deadline); + return; + } + clearTimeout(timer); + deadline.controller.signal.removeEventListener("abort", onAbort); + reject(toErrorObject(error, "Terminal open failed")); + }, + ); + }); +} + +function respondTerminalOpenTimeout(respond: GatewayRequestHandlerOptions["respond"]): void { + respond(false, undefined, errorShape(ErrorCodes.UNAVAILABLE, "terminal open timed out")); +} + function parseNodePayload(payload: unknown, payloadJSON?: string | null): unknown { if (!payloadJSON) { return payload; @@ -144,6 +230,7 @@ export const terminalHandlers: GatewayRequestHandlers = { respondLaunchBlocked(respond, launch.block); return; } + const deadline = createTerminalOpenDeadline(); let catalogPlan: SessionCatalogTerminalPlan | undefined; let title: string | undefined; @@ -173,12 +260,22 @@ export const terminalHandlers: GatewayRequestHandlers = { ); return; } + const openTerminal = provider.openTerminal; + const catalog = p.catalog; try { - catalogPlan = await provider.openTerminal({ - hostId: p.catalog.hostId, - threadId: p.catalog.threadId, - }); + catalogPlan = await waitForTerminalOpenDeadline( + () => + openTerminal.call(provider, { + hostId: catalog.hostId, + threadId: catalog.threadId, + }), + deadline, + ); } catch (error) { + if (error instanceof TerminalOpenDeadlineError) { + respondTerminalOpenTimeout(respond); + return; + } respond( false, undefined, @@ -213,13 +310,26 @@ export const terminalHandlers: GatewayRequestHandlers = { invalid(respond, "catalog terminal plan has invalid params"); return; } - const policyResult = await applyPluginNodeInvokePolicy({ - context, - client: opts.client, - nodeSession: access.node, - command: nodeCatalogPlan.command, - params: nodeParams, - }); + let policyResult: Awaited>; + try { + policyResult = await waitForTerminalOpenDeadline( + () => + applyPluginNodeInvokePolicy({ + context, + client: opts.client, + nodeSession: access.node, + command: nodeCatalogPlan.command, + params: nodeParams, + }), + deadline, + ); + } catch (error) { + if (error instanceof TerminalOpenDeadlineError) { + respondTerminalOpenTimeout(respond); + return; + } + throw error; + } if (policyResult && !policyResult.ok) { respond(false, undefined, errorShape(ErrorCodes.UNAVAILABLE, policyResult.message)); return; @@ -260,23 +370,62 @@ export const terminalHandlers: GatewayRequestHandlers = { }); } const spawnPlan = resolveTerminalOpenSpawnPlan(refreshedLaunch.plan, catalogPlan); - const outcome = await manager.open({ - connId, - agentId: spawnPlan.agentId, - cwd: spawnPlan.cwd, - shell: spawnPlan.shell, - args: spawnPlan.args, - cols: p.cols, - rows: p.rows, - env: buildTerminalEnv(process.env), - ...(createBackend ? { createBackend } : {}), - ...(stageUpload ? { stageUpload } : {}), - }); + const terminalEnv = buildTerminalEnv(process.env); + if (catalogPlan?.kind === "local" && catalogPlan.pathEnv) { + // Preserve the PATH that found a login-shell CLI so env-based shebangs + // can resolve their interpreter inside the spawned terminal process. + terminalEnv.PATH = catalogPlan.pathEnv; + } + let openingTerminal: ReturnType | undefined; + let outcome: Awaited>; + try { + outcome = await waitForTerminalOpenDeadline(() => { + openingTerminal = manager.open({ + connId, + agentId: spawnPlan.agentId, + cwd: spawnPlan.cwd, + shell: spawnPlan.shell, + args: spawnPlan.args, + cols: p.cols, + rows: p.rows, + env: terminalEnv, + signal: deadline.controller.signal, + ...(createBackend ? { createBackend } : {}), + ...(stageUpload ? { stageUpload } : {}), + }); + return openingTerminal; + }, deadline); + } catch (error) { + if (error instanceof TerminalOpenDeadlineError) { + // The backend can register immediately before deadline arbitration. + // Close a late success by id so timeout never leaves an unreachable PTY. + if (openingTerminal) { + void openingTerminal.then( + (lateOutcome) => { + if (lateOutcome.ok) { + manager.close(connId, lateOutcome.sessionId); + } + }, + () => undefined, + ); + } + respondTerminalOpenTimeout(respond); + return; + } + throw error; + } if (!outcome.ok) { const code = outcome.code === "limit" ? ErrorCodes.INVALID_REQUEST : ErrorCodes.UNAVAILABLE; respond(false, undefined, errorShape(code, outcome.message)); return; } + if (context.isConnectionActive?.(connId) === false) { + // A browser deadline can close the socket while PTY creation is still + // finishing. Release the raced session instead of leaving an orphan. + manager.close(connId, outcome.sessionId); + respond(false, undefined, errorShape(ErrorCodes.UNAVAILABLE, "terminal connection closed")); + return; + } context.logGateway.info( `terminal opened session=${outcome.sessionId} agent=${outcome.agentId} conn=${connId} shell=${outcome.shell}`, ); diff --git a/src/gateway/terminal/session-manager.test.ts b/src/gateway/terminal/session-manager.test.ts index d5613ccbed3c..d44fed6419ee 100644 --- a/src/gateway/terminal/session-manager.test.ts +++ b/src/gateway/terminal/session-manager.test.ts @@ -9,6 +9,14 @@ type TerminalPtyHandle = Awaited>; const TERMINAL_EVENT_DATA = "terminal.data"; const TERMINAL_EVENT_EXIT = "terminal.exit"; +function deferred() { + let resolve!: (value: T) => void; + const promise = new Promise((next) => { + resolve = next; + }); + return { promise, resolve }; +} + /** A controllable fake PTY that records writes and lets tests drive data/exit. */ function makeFakePty() { let dataListener: ((chunk: string) => void) | undefined; @@ -77,6 +85,73 @@ function baseRequest(overrides?: Partial): TerminalOpenRequ } describe("TerminalSessionManager", () => { + it("kills a backend that finishes after its open request is cancelled", async () => { + const spawned = deferred(); + const controller = new AbortController(); + const first = makeFakePty(); + const second = makeFakePty(); + let spawnCount = 0; + const manager = new TerminalSessionManager({ + emit: vi.fn(), + maxSessions: 1, + spawn: () => (spawnCount++ === 0 ? spawned.promise : Promise.resolve(second)), + }); + const opening = manager.open(baseRequest({ signal: controller.signal })); + + controller.abort(new Error("terminal open timed out")); + const next = await manager.open(baseRequest({ connId: "conn-2" })); + expect(next.ok).toBe(true); + spawned.resolve(first); + + await expect(opening).resolves.toEqual({ + ok: false, + code: "closed", + message: "terminal open timed out", + }); + expect(first.killed).toBe(true); + expect(manager.size).toBe(1); + if (next.ok) { + expect(manager.close("conn-2", next.sessionId)).toBe(true); + } + expect(second.killed).toBe(true); + expect(manager.size).toBe(0); + }); + + it("bounds cancelled backend operations until they settle", async () => { + const firstSpawn = deferred(); + const secondSpawn = deferred(); + const firstController = new AbortController(); + const secondController = new AbortController(); + let spawnCount = 0; + const manager = new TerminalSessionManager({ + emit: vi.fn(), + maxSessions: 1, + spawn: () => (spawnCount++ === 0 ? firstSpawn.promise : secondSpawn.promise), + }); + + const firstOpening = manager.open(baseRequest({ signal: firstController.signal })); + firstController.abort(new Error("first cancelled")); + const secondOpening = manager.open( + baseRequest({ connId: "conn-2", signal: secondController.signal }), + ); + secondController.abort(new Error("second cancelled")); + + await expect(manager.open(baseRequest({ connId: "conn-3" }))).resolves.toEqual({ + ok: false, + code: "limit", + message: "terminal spawn limit reached (2)", + }); + + const first = makeFakePty(); + const second = makeFakePty(); + firstSpawn.resolve(first); + secondSpawn.resolve(second); + await expect(firstOpening).resolves.toMatchObject({ ok: false, code: "closed" }); + await expect(secondOpening).resolves.toMatchObject({ ok: false, code: "closed" }); + expect(first.killed).toBe(true); + expect(second.killed).toBe(true); + }); + it("runs relay backends through the same stream, input, resize, and close lifecycle", async () => { let onData: ((data: string) => void) | undefined; let onExit: diff --git a/src/gateway/terminal/session-manager.ts b/src/gateway/terminal/session-manager.ts index b9a832bbfc9c..b6e145690d45 100644 --- a/src/gateway/terminal/session-manager.ts +++ b/src/gateway/terminal/session-manager.ts @@ -52,6 +52,10 @@ export class TerminalSessionManager { // Slots reserved by opens that are still awaiting spawn. Counted against the // cap so concurrent opens cannot all pass the check and exceed maxSessions. private opening = 0; + // Cancellation frees a session slot, but cannot stop every backend factory. + // Bound those physical operations until they settle so disconnect churn + // cannot create an unbounded number of native/node spawn attempts. + private spawning = 0; constructor(options: TerminalSessionManagerOptions) { void ensureTerminalUploadCleanup(); @@ -71,6 +75,16 @@ export class TerminalSessionManager { /** Spawns a shell and wires its output/exit to the owning connection. */ async open(request: TerminalOpenRequest): Promise { + if (request.signal?.aborted) { + return { ok: false, code: "closed", message: this.openAbortMessage(request.signal) }; + } + if (this.spawning >= this.maxSessions * 2) { + return { + ok: false, + code: "limit", + message: `terminal spawn limit reached (${this.maxSessions * 2})`, + }; + } if (this.sessions.size + this.opening >= this.maxSessions) { return { ok: false, @@ -80,7 +94,29 @@ export class TerminalSessionManager { } // Reserve the slot before the async spawn so it is visible to concurrent opens. this.opening += 1; - const pending: TerminalPendingOpen = { agentId: request.agentId }; + this.spawning += 1; + let reservationActive = true; + const releaseReservation = () => { + if (!reservationActive) { + return; + } + reservationActive = false; + this.opening -= 1; + this.untrackPendingOpen(request.connId, pending); + }; + const pending: TerminalPendingOpen = { + agentId: request.agentId, + abort: (message) => { + pending.abortMessage ??= message; + // A hung spawn must not consume capacity after its owner is gone. + // Its eventual backend is still killed by the abortMessage check below. + releaseReservation(); + }, + }; + const abortPending = () => { + pending.abort(this.openAbortMessage(request.signal)); + }; + request.signal?.addEventListener("abort", abortPending, { once: true }); this.trackPendingOpen(request.connId, pending); let backend: TerminalBackend; try { @@ -98,15 +134,17 @@ export class TerminalSessionManager { this.spawn, ); } catch (err) { - this.opening -= 1; - this.untrackPendingOpen(request.connId, pending); + this.spawning -= 1; + releaseReservation(); + request.signal?.removeEventListener("abort", abortPending); const message = err instanceof Error ? err.message : String(err); return { ok: false, code: "spawn_failed", message }; } // Hand the reservation over to the live session (synchronous from here — no // await — so the counts never both drop). - this.opening -= 1; - this.untrackPendingOpen(request.connId, pending); + this.spawning -= 1; + releaseReservation(); + request.signal?.removeEventListener("abort", abortPending); if (pending.abortMessage) { // The owning connection disconnected while the shell was spawning; kill it // now rather than register an orphan no one can reach or close. @@ -310,6 +348,10 @@ export class TerminalSessionManager { set.add(pending); } + private openAbortMessage(signal: AbortSignal | undefined): string { + return signal?.reason instanceof Error ? signal.reason.message : "terminal open cancelled"; + } + private untrackPendingOpen(connId: string, pending: TerminalPendingOpen): void { const set = this.pendingOpens.get(connId); if (set) { @@ -332,7 +374,7 @@ export class TerminalSessionManager { const opens = this.pendingOpens.get(connId); if (opens) { for (const pending of opens) { - pending.abortMessage = "connection closed during open"; + pending.abort("connection closed during open"); } } const ids = this.byConn.get(connId); @@ -361,7 +403,7 @@ export class TerminalSessionManager { for (const opens of this.pendingOpens.values()) { for (const pending of opens) { if (!isAllowed(pending.agentId)) { - pending.abortMessage = "terminal closed because the agent policy changed"; + pending.abort("terminal closed because the agent policy changed"); } } } @@ -413,7 +455,7 @@ export class TerminalSessionManager { // Abort any opens still spawning so they don't register after shutdown. for (const opens of this.pendingOpens.values()) { for (const pending of opens) { - pending.abortMessage = "gateway closed during terminal open"; + pending.abort("gateway closed during terminal open"); } } // Snapshot first: finalize() deletes from this.sessions during iteration. diff --git a/src/gateway/terminal/session-manager.types.ts b/src/gateway/terminal/session-manager.types.ts index e21beb1d882c..d0f5a31d3889 100644 --- a/src/gateway/terminal/session-manager.types.ts +++ b/src/gateway/terminal/session-manager.types.ts @@ -46,6 +46,8 @@ export type TerminalOpenRequest = { cols: number; rows: number; env: Record; + /** Request-scoped cancellation; a late backend is killed before registration. */ + signal?: AbortSignal; createBackend?: () => Promise; stageUpload?: (file: TerminalUploadFile) => Promise; }; @@ -55,4 +57,8 @@ export type TerminalOpenOutcome = | { ok: false; code: "limit" | "spawn_failed" | "closed"; message: string }; /** Abort state shared between a pending open and lifecycle/policy teardown. */ -export type TerminalPendingOpen = { agentId: string; abortMessage?: string }; +export type TerminalPendingOpen = { + agentId: string; + abortMessage?: string; + abort(message: string): void; +}; diff --git a/src/infra/shell-env.test.ts b/src/infra/shell-env.test.ts index c48d376e49eb..6c046934da9d 100644 --- a/src/infra/shell-env.test.ts +++ b/src/infra/shell-env.test.ts @@ -10,6 +10,7 @@ let clearShellEnvAppliedKeys: ShellEnvModule["clearShellEnvAppliedKeys"]; let getShellEnvAppliedKeys: ShellEnvModule["getShellEnvAppliedKeys"]; let getShellPathFromLoginShell: ShellEnvModule["getShellPathFromLoginShell"]; let loadShellEnvFallback: ShellEnvModule["loadShellEnvFallback"]; +let resolveExecutableFromUserShellPathWithPathEnv: ShellEnvModule["resolveExecutableFromUserShellPathWithPathEnv"]; let resolveShellEnvFallbackTimeoutMs: ShellEnvModule["resolveShellEnvFallbackTimeoutMs"]; let shouldDeferShellEnvFallback: ShellEnvModule["shouldDeferShellEnvFallback"]; let shouldEnableShellEnvFallback: ShellEnvModule["shouldEnableShellEnvFallback"]; @@ -21,6 +22,7 @@ beforeEach(async () => { getShellEnvAppliedKeys, getShellPathFromLoginShell, loadShellEnvFallback, + resolveExecutableFromUserShellPathWithPathEnv, resolveShellEnvFallbackTimeoutMs, shouldDeferShellEnvFallback, shouldEnableShellEnvFallback, @@ -563,6 +565,47 @@ describe("shell env fallback", () => { expectSanitizedStartupEnv(receivedEnv); }); + it("resolves from the daemon PATH without probing the login shell", () => { + const exec = vi.fn(() => Buffer.from("PATH=/bin\0")); + + const result = resolveExecutableFromUserShellPathWithPathEnv("sh", { + env: { PATH: "/bin" }, + exec: exec as unknown as Parameters< + typeof resolveExecutableFromUserShellPathWithPathEnv + >[1]["exec"], + }); + + expect(result).toEqual({ executable: "/bin/sh" }); + expect(exec).not.toHaveBeenCalled(); + }); + + it("resolves from the login-shell PATH when the daemon PATH misses the executable", () => { + const exec = vi.fn(() => Buffer.from("PATH=/bin\0")); + + const result = resolveExecutableFromUserShellPathWithPathEnv("sh", { + env: { PATH: "/missing", SHELL: "/bin/sh" }, + exec: exec as unknown as Parameters< + typeof resolveExecutableFromUserShellPathWithPathEnv + >[1]["exec"], + }); + + expect(result).toEqual({ executable: "/bin/sh", pathEnv: "/bin" }); + expect(exec).toHaveBeenCalledOnce(); + }); + + it("returns the login-shell PATH needed by env-based executable launchers", () => { + const exec = vi.fn(() => Buffer.from("PATH=/bin\0")); + + const result = resolveExecutableFromUserShellPathWithPathEnv("sh", { + env: { PATH: "/missing", SHELL: "/bin/sh" }, + exec: exec as unknown as Parameters< + typeof resolveExecutableFromUserShellPathWithPathEnv + >[1]["exec"], + }); + + expect(result).toEqual({ executable: "/bin/sh", pathEnv: "/bin" }); + }); + it("returns null without invoking shell on win32", () => { const exec = vi.fn(() => Buffer.from("PATH=/usr/local/bin:/usr/bin\0HOME=/tmp\0")); diff --git a/src/infra/shell-env.ts b/src/infra/shell-env.ts index 3ee9e2989f73..ad37065d634f 100644 --- a/src/infra/shell-env.ts +++ b/src/infra/shell-env.ts @@ -6,6 +6,7 @@ import path from "node:path"; import { resolveTimerTimeoutMs } from "@openclaw/normalization-core/number-coercion"; import { isTruthyEnvValue } from "./env.js"; import { formatErrorMessage } from "./errors.js"; +import { resolveExecutableFromPathEnv } from "./executable-path.js"; import { sanitizeHostExecEnv } from "./host-env-security.js"; import { parseStrictNonNegativeInteger } from "./parse-finite-number.js"; @@ -317,6 +318,48 @@ export function getShellPathFromLoginShell(opts: { cachedShellPath = shellPath && shellPath.length > 0 ? shellPath : null; return cachedShellPath; } + +type UserShellExecutableResolution = { + executable: string; + /** Present only when resolution required the login-shell PATH fallback. */ + pathEnv?: string; +}; + +export function resolveExecutableFromUserShellPathWithPathEnv( + executable: string, + opts: { + env: NodeJS.ProcessEnv; + pathEnv?: string; + includeExtensionless?: boolean; + timeoutMs?: number; + exec?: typeof execFileSync; + platform?: NodeJS.Platform; + }, +): UserShellExecutableResolution | undefined { + const direct = resolveExecutableFromPathEnv( + executable, + opts.pathEnv ?? opts.env.PATH ?? opts.env.Path ?? "", + opts.env, + { includeExtensionless: opts.includeExtensionless }, + ); + if (direct) { + return { executable: direct }; + } + const shellPath = getShellPathFromLoginShell({ + env: opts.env, + timeoutMs: opts.timeoutMs, + exec: opts.exec, + platform: opts.platform, + }); + if (!shellPath) { + return undefined; + } + const resolved = resolveExecutableFromPathEnv(executable, shellPath, opts.env, { + includeExtensionless: opts.includeExtensionless, + }); + return resolved ? { executable: resolved, pathEnv: shellPath } : undefined; +} + export function getShellEnvAppliedKeys(): string[] { return [...lastAppliedKeys]; } diff --git a/src/node-host/worker-support.ts b/src/node-host/worker-support.ts index 4e35f402c253..dee1e367979a 100644 --- a/src/node-host/worker-support.ts +++ b/src/node-host/worker-support.ts @@ -88,12 +88,6 @@ export class NodeHostWorkerBridgeClient implements NodeHostClient { this.writeMessage({ type: "invoke-result", result: params ?? {} }); return {} as T; } - if (method === "node.invoke.progress") { - // Unreachable while the embedded app worker never enables agent runs - // (the only progress emitter); the Mac host bridge has no case for it. - this.writeMessage({ type: "invoke-progress", progress: params ?? {} }); - return {} as T; - } if (method === "node.event") { this.writeMessage({ type: "node-event", event: params ?? {} }); return {} as T; diff --git a/src/node-host/worker.test.ts b/src/node-host/worker.test.ts index 38088be607af..320d427b1eb2 100644 --- a/src/node-host/worker.test.ts +++ b/src/node-host/worker.test.ts @@ -35,19 +35,55 @@ describe("NodeHostWorkerBridgeClient", () => { const client = new NodeHostWorkerBridgeClient((message) => messages.push(message)); await client.request("node.invoke.result", { id: "invoke-1", ok: true }); - await client.request("node.invoke.progress", { invokeId: "invoke-1", seq: 0, chunk: "a" }); await client.request("node.event", { event: "exec.started", payloadJSON: "{}" }); expect(messages).toEqual([ { type: "invoke-result", result: { id: "invoke-1", ok: true } }, - { - type: "invoke-progress", - progress: { invokeId: "invoke-1", seq: 0, chunk: "a" }, - }, { type: "node-event", event: { event: "exec.started", payloadJSON: "{}" } }, ]); }); + it("tunnels invoke progress and waits for gateway acceptance", async () => { + const messages: Array> = []; + const client = new NodeHostWorkerBridgeClient((message) => { + messages.push(message as Record); + }); + + let settled = false; + const response = client + .request("node.invoke.progress", { + invokeId: "invoke-1", + nodeId: "node-1", + seq: 0, + chunk: "a", + }) + .then(() => { + settled = true; + }); + await Promise.resolve(); + expect(settled).toBe(false); + expect(messages).toEqual([ + { + type: "gateway-request", + id: "gateway-1", + method: "node.invoke.progress", + params: { invokeId: "invoke-1", nodeId: "node-1", seq: 0, chunk: "a" }, + timeoutMs: 15_000, + }, + ]); + + expect( + client.handleResponse({ + type: "gateway-response", + id: "gateway-1", + ok: true, + result: { ok: true }, + }), + ).toBe(true); + await response; + expect(settled).toBe(true); + }); + it("tunnels runtime gateway requests and resolves their matching response", async () => { const messages: Array> = []; const client = new NodeHostWorkerBridgeClient((message) => { diff --git a/src/plugin-sdk/node-host.ts b/src/plugin-sdk/node-host.ts index 7de8fbda7fc3..bc5e90fc66d8 100644 --- a/src/plugin-sdk/node-host.ts +++ b/src/plugin-sdk/node-host.ts @@ -1,3 +1,6 @@ +import { resolveExecutableFromPathEnv as resolveExecutableFromPathEnvDirect } from "../infra/executable-path.js"; +import { resolveExecutableFromUserShellPathWithPathEnv } from "../infra/shell-env.js"; + export { decodeNodePtyResumeParams, runNodePtyCommand, @@ -5,5 +8,51 @@ export { type NodePtyResumeParams, } from "../node-host/pty-command.js"; export { validateClaudeSessionId } from "../node-host/invoke-agent-cli-claude-params.js"; -export { resolveExecutableFromPathEnv } from "../infra/executable-path.js"; export type { OpenClawPluginNodeHostCommandIo } from "../plugins/types.js"; + +export function resolveExecutableFromPathEnv( + executable: string, + pathEnv: string, + env: NodeJS.ProcessEnv | undefined, + options: { + includeExtensionless?: boolean; + fallbackToLoginShell?: boolean; + withPathEnv: true; + }, +): { executable: string; pathEnv?: string } | undefined; +export function resolveExecutableFromPathEnv( + executable: string, + pathEnv: string, + env?: NodeJS.ProcessEnv, + options?: { + includeExtensionless?: boolean; + fallbackToLoginShell?: boolean; + withPathEnv?: false; + }, +): string | undefined; +export function resolveExecutableFromPathEnv( + executable: string, + pathEnv: string, + env?: NodeJS.ProcessEnv, + options?: { + includeExtensionless?: boolean; + fallbackToLoginShell?: boolean; + withPathEnv?: boolean; + }, +): string | { executable: string; pathEnv?: string } | undefined { + let resolution: { executable: string; pathEnv?: string } | undefined; + if (!options?.fallbackToLoginShell) { + const resolved = resolveExecutableFromPathEnvDirect(executable, pathEnv, env, options); + resolution = resolved ? { executable: resolved } : undefined; + } else { + // Local catalog terminals launch with this same login-shell PATH. Carry it + // forward because npm-style launchers may use `#!/usr/bin/env node`. + const shellEnv = env ?? process.env; + resolution = resolveExecutableFromUserShellPathWithPathEnv(executable, { + env: shellEnv, + pathEnv, + includeExtensionless: options.includeExtensionless, + }); + } + return options?.withPathEnv ? resolution : resolution?.executable; +} diff --git a/src/plugins/session-catalog.ts b/src/plugins/session-catalog.ts index ce841dd07b65..97d8a5d50865 100644 --- a/src/plugins/session-catalog.ts +++ b/src/plugins/session-catalog.ts @@ -23,7 +23,14 @@ export type SessionCatalogContinueProviderParams = Omit< export type SessionCatalogArchiveProviderParams = Omit; export type SessionCatalogTerminalPlan = - | { kind: "local"; argv: string[]; cwd?: string; title?: string } + | { + kind: "local"; + argv: string[]; + cwd?: string; + title?: string; + /** PATH that resolved argv[0], needed by env-based script interpreters. */ + pathEnv?: string; + } | { kind: "node"; nodeId: string; diff --git a/ui/src/components/terminal/terminal-connection.test.ts b/ui/src/components/terminal/terminal-connection.test.ts index edc768186307..1c0c706d5050 100644 --- a/ui/src/components/terminal/terminal-connection.test.ts +++ b/ui/src/components/terminal/terminal-connection.test.ts @@ -1,8 +1,13 @@ import { describe, expect, it, vi } from "vitest"; -import { TerminalConnection, type TerminalGatewayClient } from "./terminal-connection.ts"; +import { + TerminalConnection, + type TerminalGatewayClient, + TerminalOpenTimeoutError, +} from "./terminal-connection.ts"; const TERMINAL_LIVENESS_IDLE_MS = 20_000; const TERMINAL_LIVENESS_PROBE_TIMEOUT_MS = 5_000; +const TERMINAL_OPEN_WATCHDOG_MS = 35_000; function deferred() { let resolve!: (value: T) => void; @@ -75,7 +80,11 @@ describe("TerminalConnection", () => { ); expect(result.sessionId).toBe("s1"); - expect(client.requests[0]).toEqual({ method: "terminal.open", params: { cols: 80, rows: 24 } }); + expect(client.requests[0]).toEqual({ + method: "terminal.open", + params: { cols: 80, rows: 24 }, + options: { timeoutMs: TERMINAL_OPEN_WATCHDOG_MS }, + }); client.emit("terminal.data", { sessionId: "s1", seq: 5, data: "hello" }); client.emit("terminal.data", { sessionId: "s1", seq: 6, data: "!" }); @@ -476,9 +485,49 @@ describe("TerminalConnection", () => { expect(client.requests[0]).toEqual({ method: "terminal.open", params: { agentId: "ops", cols: 100, rows: 30 }, + options: { timeoutMs: TERMINAL_OPEN_WATCHDOG_MS }, }); }); + it("maps the Gateway's request-scoped terminal open deadline", async () => { + const client = makeFakeClient(); + client.request = ( + method: string, + params?: unknown, + options?: { timeoutMs?: number | null }, + ) => { + client.requests.push({ method, params, ...(options ? { options } : {}) }); + return Promise.reject(new Error("terminal open timed out")) as Promise; + }; + const conn = new TerminalConnection(client); + + await expect( + conn.open({ cols: 80, rows: 24 }, { onData: () => {}, onExit: () => {} }), + ).rejects.toBeInstanceOf(TerminalOpenTimeoutError); + expect(client.requests[0]?.options).toEqual({ timeoutMs: TERMINAL_OPEN_WATCHDOG_MS }); + expect(client.forceReconnects).toEqual([]); + }); + + it("reconnects when the browser watchdog cannot receive the Gateway deadline", async () => { + const client = makeFakeClient(); + client.request = ( + method: string, + params?: unknown, + options?: { timeoutMs?: number | null }, + ) => { + client.requests.push({ method, params, ...(options ? { options } : {}) }); + return Promise.reject( + new Error(`gateway request timed out after ${options?.timeoutMs}ms: ${method}`), + ) as Promise; + }; + const conn = new TerminalConnection(client); + + await expect( + conn.open({ cols: 80, rows: 24 }, { onData: () => {}, onExit: () => {} }), + ).rejects.toBeInstanceOf(TerminalOpenTimeoutError); + expect(client.forceReconnects).toEqual(["terminal open watchdog timeout"]); + }); + it("forwards a typed catalog reference and preserves the returned title", async () => { const client = makeFakeClient(); client.nextResponse = { @@ -499,6 +548,7 @@ describe("TerminalConnection", () => { expect(client.requests[0]).toEqual({ method: "terminal.open", params: { cols: 100, rows: 30, catalog }, + options: { timeoutMs: TERMINAL_OPEN_WATCHDOG_MS }, }); expect(result.title).toBe("codex resume 0d5c…"); }); diff --git a/ui/src/components/terminal/terminal-connection.ts b/ui/src/components/terminal/terminal-connection.ts index 1e73fd0b7468..90ee003b54ea 100644 --- a/ui/src/components/terminal/terminal-connection.ts +++ b/ui/src/components/terminal/terminal-connection.ts @@ -13,7 +13,8 @@ export interface TerminalGatewayClient { ): Promise; addEventListener(listener: (evt: { event: string; payload: unknown }) => void): () => void; inboundActivitySeq?: number; - forceReconnect?(reason: string): void; + /** Recovers unreplayable output gaps and half-open terminal streams. */ + forceReconnect(reason: string): void; } type TerminalOpenResult = { @@ -75,6 +76,29 @@ type PendingEvent = const TERMINAL_LIVENESS_IDLE_MS = 20_000; const TERMINAL_LIVENESS_PROBE_TIMEOUT_MS = 5_000; +// The Gateway owns the 30s open deadline. This longer browser watchdog only +// recovers a half-open socket when the Gateway's response cannot arrive. +const TERMINAL_OPEN_WATCHDOG_MS = 35_000; +export class TerminalOpenTimeoutError extends Error { + constructor(cause: unknown) { + super("terminal open timed out", { cause }); + this.name = "TerminalOpenTimeoutError"; + } +} + +function isTerminalOpenRequestTimeout(error: unknown): boolean { + return ( + error instanceof Error && + /^gateway request timed out after \d+ms: terminal\.open$/u.test(error.message) + ); +} + +function isTerminalOpenTimeout(error: unknown): boolean { + return ( + error instanceof Error && + (error.message === "terminal open timed out" || isTerminalOpenRequestTimeout(error)) + ); +} /** Routes the shared terminal event stream to the session that owns each id. */ export class TerminalConnection { @@ -161,9 +185,24 @@ export class TerminalConnection { params: { agentId?: string; cols: number; rows: number; catalog?: TerminalCatalogReference }, sink: SessionSink, ): Promise { - const result = await this.requestWhileHoldingStream(() => - this.client.request("terminal.open", params), - ); + let result: TerminalOpenResult; + try { + result = await this.requestWhileHoldingStream(() => + this.client.request("terminal.open", params, { + timeoutMs: TERMINAL_OPEN_WATCHDOG_MS, + }), + ); + } catch (error) { + if (!isTerminalOpenTimeout(error)) { + throw error; + } + if (isTerminalOpenRequestTimeout(error)) { + // The server should answer first. A later browser timeout means this + // socket cannot carry the response, so disconnect to cancel ownership. + this.client.forceReconnect("terminal open watchdog timeout"); + } + throw new TerminalOpenTimeoutError(error); + } this.adoptSession(result.sessionId, sink, { seqMode: "unknown", expectedSeq: 0 }); return result; } @@ -301,7 +340,7 @@ export class TerminalConnection { // would duplicate bytes already rendered before the detected gap. stream.recovering = false; this.pending.delete(sessionId); - this.client.forceReconnect?.("terminal replay reset unavailable"); + this.client.forceReconnect("terminal replay reset unavailable"); return; } stream.sink.onReplay(result.buffer); @@ -331,7 +370,7 @@ export class TerminalConnection { } stream.recovering = false; this.pending.delete(sessionId); - this.client.forceReconnect?.("terminal replay failed"); + this.client.forceReconnect("terminal replay failed"); }); } @@ -437,7 +476,7 @@ export class TerminalConnection { const activityNow = this.client.inboundActivitySeq ?? this.inboundActivityVersion; if (activityNow === activityBefore) { this.lastTerminalActivityAtMs = Date.now(); - this.client.forceReconnect?.("terminal liveness timeout"); + this.client.forceReconnect("terminal liveness timeout"); } else { // Any valid inbound frame proves the socket is not half-open. this.lastTerminalActivityAtMs = Date.now(); diff --git a/ui/src/components/terminal/terminal-panel-readiness.test.ts b/ui/src/components/terminal/terminal-panel-readiness.test.ts new file mode 100644 index 000000000000..64bb456bd123 --- /dev/null +++ b/ui/src/components/terminal/terminal-panel-readiness.test.ts @@ -0,0 +1,249 @@ +/* @vitest-environment jsdom */ + +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { i18n } from "../../i18n/index.ts"; +import { createStorageMock } from "../../test-helpers/storage.ts"; +import type { TerminalGatewayClient } from "./terminal-connection.ts"; +import { OpenClawTerminalPanel } from "./terminal-panel.ts"; +import type { createIsolatedGhosttyTerminal } from "./terminal-runtime.ts"; + +function createTerminalController() { + return { + readOnly: false, + terminal: { + cols: 100, + rows: 30, + viewportY: 0, + write: vi.fn(), + focus: vi.fn(), + reset: vi.fn(), + paste: vi.fn(), + }, + write: vi.fn(), + fit: vi.fn(), + resize: vi.fn(), + setReadOnly: vi.fn(), + attach: vi.fn(), + dispose: vi.fn(), + }; +} + +const createTerminal = vi.fn(async () => createTerminalController()); + +class ReadinessTestTerminalPanel extends OpenClawTerminalPanel { + protected override createTerminal = + createTerminal as unknown as typeof createIsolatedGhosttyTerminal; +} + +const TERMINAL_PANEL_ELEMENT_NAME = `test-terminal-panel-readiness-${crypto.randomUUID()}`; +customElements.define(TERMINAL_PANEL_ELEMENT_NAME, ReadinessTestTerminalPanel); + +function deferred() { + let resolve!: (value: T) => void; + const promise = new Promise((next) => { + resolve = next; + }); + return { promise, resolve }; +} + +function terminalOpenResult(sessionId: string) { + return { + sessionId, + agentId: "main", + shell: "/bin/zsh", + cwd: "/work", + confined: false, + }; +} + +describe("terminal panel readiness", () => { + beforeEach(async () => { + vi.stubGlobal("localStorage", createStorageMock()); + vi.stubGlobal("sessionStorage", createStorageMock()); + await i18n.setLocale("en"); + }); + + afterEach(async () => { + document.body.replaceChildren(); + createTerminal.mockClear(); + vi.unstubAllGlobals(); + await i18n.setLocale("en"); + }); + + it("shows a connecting animation while a terminal open is in flight", async () => { + const open = deferred<{ + sessionId: string; + agentId: string; + shell: string; + cwd: string; + confined: boolean; + }>(); + const client: TerminalGatewayClient = { + forceReconnect: () => {}, + request: (method: string) => + (method === "terminal.open" ? open.promise : Promise.resolve({})) as Promise, + addEventListener: () => () => {}, + }; + const panel = document.createElement(TERMINAL_PANEL_ELEMENT_NAME) as OpenClawTerminalPanel; + panel.client = client; + panel.available = true; + document.body.append(panel); + panel.toggle(); + + await vi.waitFor(() => { + expect(panel.renderRoot.querySelector(".tp-connecting")?.textContent).toContain( + "Connecting to session", + ); + }); + expect(panel.renderRoot.querySelector(".tp-tab")?.classList.contains("is-connecting")).toBe( + true, + ); + + open.resolve(terminalOpenResult("session-1")); + await vi.waitFor(() => expect(panel.renderRoot.querySelector(".tp-connecting")).toBeNull()); + expect(panel.renderRoot.querySelector(".tp-tab")?.classList.contains("is-live")).toBe(true); + }); + + it("persists a catalog tab after its first output arrives", async () => { + const controller = createTerminalController(); + createTerminal.mockResolvedValue(controller); + const requests: Array<{ method: string; params: unknown }> = []; + let listener: ((event: { event: string; payload: unknown }) => void) | undefined; + const client: TerminalGatewayClient = { + forceReconnect: () => {}, + request: async (method: string, params?: unknown) => { + requests.push({ method, params }); + return { + ...terminalOpenResult("catalog-terminal-1"), + title: "codex resume 0d5c…", + } as T; + }, + addEventListener: (nextListener) => { + listener = nextListener; + return () => { + listener = undefined; + }; + }, + }; + const panel = document.createElement(TERMINAL_PANEL_ELEMENT_NAME) as OpenClawTerminalPanel; + panel.client = client; + panel.available = true; + document.body.append(panel); + const catalog = { catalogId: "codex", hostId: "node:mac", threadId: "thread" }; + + panel.handleToggleRequest(new CustomEvent("openclaw:terminal-toggle", { detail: { catalog } })); + + await vi.waitFor(() => { + expect(requests).toContainEqual({ + method: "terminal.open", + params: { agentId: undefined, cols: 100, rows: 30, catalog }, + }); + }); + expect(panel.renderRoot.querySelector(".tp-tab")?.textContent).toContain("codex resume 0d5c…"); + expect(panel.renderRoot.querySelector(".tp-connecting")?.textContent).toContain( + "Connecting to session", + ); + + listener?.({ + event: "terminal.data", + payload: { sessionId: "catalog-terminal-1", seq: 5, data: "ready" }, + }); + await vi.waitFor(() => expect(panel.renderRoot.querySelector(".tp-connecting")).toBeNull()); + expect(new TextDecoder().decode(controller.write.mock.calls[0]?.[0])).toBe("ready"); + expect(sessionStorage.getItem("openclaw.terminal.sessions.v1")).toBe( + JSON.stringify(["catalog-terminal-1"]), + ); + }); + + it("marks a catalog terminal ready when its first visible output is a replay", async () => { + const controller = createTerminalController(); + createTerminal.mockResolvedValue(controller); + const requests: Array<{ method: string; params: unknown }> = []; + let listener: ((event: { event: string; payload: unknown }) => void) | undefined; + const client: TerminalGatewayClient = { + forceReconnect: () => {}, + request: async (method: string, params?: unknown) => { + requests.push({ method, params }); + if (method === "terminal.attach") { + return { + ...terminalOpenResult("catalog-terminal-1"), + buffer: "recovered output", + seq: 12, + } as T; + } + return { + ...terminalOpenResult("catalog-terminal-1"), + title: "claude --resume 1234…", + } as T; + }, + addEventListener: (nextListener) => { + listener = nextListener; + return () => { + listener = undefined; + }; + }, + }; + const panel = document.createElement(TERMINAL_PANEL_ELEMENT_NAME) as OpenClawTerminalPanel; + panel.client = client; + panel.available = true; + document.body.append(panel); + + panel.handleToggleRequest( + new CustomEvent("openclaw:terminal-toggle", { + detail: { catalog: { catalogId: "anthropic", hostId: "node:mac", threadId: "thread" } }, + }), + ); + await vi.waitFor(() => expect(panel.renderRoot.querySelector(".tp-connecting")).not.toBeNull()); + + listener?.({ + event: "terminal.data", + payload: { sessionId: "catalog-terminal-1", seq: 12, data: "gap" }, + }); + + await vi.waitFor(() => expect(panel.renderRoot.querySelector(".tp-connecting")).toBeNull()); + expect(requests).toContainEqual({ + method: "terminal.attach", + params: { sessionId: "catalog-terminal-1" }, + }); + expect(controller.terminal.reset).toHaveBeenCalledOnce(); + expect(new TextDecoder().decode(controller.write.mock.calls[0]?.[0])).toBe("recovered output"); + }); + + it("closes a catalog terminal and shows an error when no output arrives", async () => { + const requests: Array<{ method: string; params: unknown }> = []; + const client: TerminalGatewayClient = { + forceReconnect: () => {}, + request: async (method: string, params?: unknown) => { + requests.push({ method, params }); + return ( + method === "terminal.open" + ? { ...terminalOpenResult("catalog-terminal-1"), title: "claude --resume 1234…" } + : {} + ) as T; + }, + addEventListener: () => () => {}, + }; + const panel = document.createElement(TERMINAL_PANEL_ELEMENT_NAME) as OpenClawTerminalPanel; + panel.client = client; + panel.available = true; + (panel as unknown as { catalogReadyTimeoutMs: number }).catalogReadyTimeoutMs = 5; + document.body.append(panel); + + panel.handleToggleRequest( + new CustomEvent("openclaw:terminal-toggle", { + detail: { catalog: { catalogId: "anthropic", hostId: "node:mac", threadId: "thread" } }, + }), + ); + + await vi.waitFor(() => { + expect(panel.renderRoot.querySelector(".tp-error")?.textContent).toContain( + "Session did not connect within 30 seconds", + ); + }); + expect(requests).toContainEqual({ + method: "terminal.close", + params: { sessionId: "catalog-terminal-1" }, + }); + expect(panel.renderRoot.querySelector(".tp-tab")).toBeNull(); + }); +}); diff --git a/ui/src/components/terminal/terminal-panel-styles.ts b/ui/src/components/terminal/terminal-panel-styles.ts index fbf9d907f191..140dd26b0340 100644 --- a/ui/src/components/terminal/terminal-panel-styles.ts +++ b/ui/src/components/terminal/terminal-panel-styles.ts @@ -106,6 +106,9 @@ export const terminalPanelStyles = css` .tp-tab.is-exited::part(base) { opacity: 0.55; } + .tp-tab.is-connecting .tp-tab__icon { + animation: tp-pulse 1.2s ease-in-out infinite; + } .tp-tab__icon { display: inline-flex; color: var(--accent, #4ec9a8); @@ -267,9 +270,31 @@ export const terminalPanelStyles = css` .tp-host { position: absolute; inset: 0; + z-index: 0; padding: 6px 8px; caret-color: transparent; } + .tp-connecting { + position: absolute; + inset: 0; + z-index: 2; + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + color: var(--muted, #8a919e); + background: color-mix(in srgb, var(--bg, #0e1015) 88%, transparent); + font-size: 12px; + pointer-events: none; + } + .tp-connecting__spinner { + width: 16px; + height: 16px; + border: 2px solid color-mix(in srgb, var(--accent, #ff5c5c) 24%, transparent); + border-top-color: var(--accent, #ff5c5c); + border-radius: 50%; + animation: tp-spin 0.8s linear infinite; + } .tp-empty, .tp-error { padding: 10px 12px; @@ -279,4 +304,20 @@ export const terminalPanelStyles = css` .tp-error { color: var(--danger, #ff6b6b); } + @keyframes tp-spin { + to { + transform: rotate(360deg); + } + } + @keyframes tp-pulse { + 50% { + opacity: 0.35; + } + } + @media (prefers-reduced-motion: reduce) { + .tp-connecting__spinner, + .tp-tab.is-connecting .tp-tab__icon { + animation: none; + } + } `; diff --git a/ui/src/components/terminal/terminal-panel-tabs.ts b/ui/src/components/terminal/terminal-panel-tabs.ts index a48ea519684b..00c1f7ba031b 100644 --- a/ui/src/components/terminal/terminal-panel-tabs.ts +++ b/ui/src/components/terminal/terminal-panel-tabs.ts @@ -8,7 +8,7 @@ export type TerminalPanelTab = { shellName: string | null; agentId: string | null; cwd: string | null; - status: "live" | "exited"; + status: "connecting" | "live" | "exited"; exitReason?: string; exitCode?: number | null; }; @@ -28,6 +28,9 @@ function terminalTabHint(tab: TerminalPanelTab): string | null { } function terminalTabStatusLabel(tab: TerminalPanelTab): string | null { + if (tab.status === "connecting") { + return t("terminal.connecting"); + } if (tab.status !== "exited") { return null; } @@ -60,7 +63,7 @@ export function renderTerminalPanelTabs(params: { return html` () { let resolve!: (value: T) => void; let reject!: (reason?: unknown) => void; @@ -80,12 +90,208 @@ describe("OpenClawTerminalPanel upload lifecycle", () => { await i18n.setLocale("en"); }); + it("uploads dropped files and pastes shell-safe paths without executing", async () => { + const controller = createTerminalController(); + createGhosttyTerminalMock.mockResolvedValue(controller); + const requests: Array<{ method: string; params: unknown }> = []; + const client: TerminalGatewayClient = { + forceReconnect: () => {}, + request: async (method: string, params?: unknown) => { + requests.push({ method, params }); + if (method === "terminal.open") { + return terminalOpenResult("session-1") as T; + } + if (method === "terminal.upload") { + return { path: "/tmp/openclaw upload/scan final.pdf", size: 3 } as T; + } + return {} as T; + }, + addEventListener: () => () => {}, + }; + const panel = document.createElement(TERMINAL_PANEL_ELEMENT_NAME) as OpenClawTerminalPanel; + panel.client = client; + panel.available = true; + document.body.append(panel); + panel.toggle(); + await vi.waitFor(() => { + expect(panel.renderRoot.querySelector(".tp-upload")?.disabled).toBe(false); + }); + expect(panel.renderRoot.querySelector(".tp-file-input")?.multiple).toBe(true); + + const file = new File(["pdf"], "scan final.pdf", { type: "application/pdf" }); + Object.defineProperty(file, "arrayBuffer", { + value: async () => new TextEncoder().encode("pdf").buffer, + }); + const drop = new Event("drop", { bubbles: true, cancelable: true }); + Object.defineProperty(drop, "dataTransfer", { + value: { types: ["Files"], files: [file], dropEffect: "none" }, + }); + panel.renderRoot.querySelector(".tp-viewport")?.dispatchEvent(drop); + + await vi.waitFor(() => { + expect(requests).toContainEqual({ + method: "terminal.upload", + params: { + sessionId: "session-1", + name: "scan final.pdf", + contentBase64: "cGRm", + }, + }); + }); + expect(controller.terminal.paste).toHaveBeenCalledWith("'/tmp/openclaw upload/scan final.pdf'"); + expect(controller.terminal.paste).not.toHaveBeenCalledWith(expect.stringContaining("\n")); + }); + + it("shows file progress and retries only the failed remainder", async () => { + const controller = createTerminalController(); + createGhosttyTerminalMock.mockResolvedValue(controller); + const requests: Array<{ method: string; params: unknown; signal?: AbortSignal }> = []; + const failedUpload = deferred<{ path: string; size: number }>(); + let notesAttempts = 0; + const client: TerminalGatewayClient = { + forceReconnect: () => {}, + request: async (method: string, params?: unknown, options?: { signal?: AbortSignal }) => { + requests.push({ method, params, signal: options?.signal }); + if (method === "terminal.open") { + return terminalOpenResult("session-1") as T; + } + if (method === "terminal.upload") { + const name = (params as { name: string }).name; + if (name === "scan final.pdf") { + return { path: "/tmp/openclaw upload/scan final.pdf", size: 3 } as T; + } + notesAttempts += 1; + if (notesAttempts === 1) { + return (await failedUpload.promise) as T; + } + return { path: "/tmp/openclaw upload/notes.txt", size: 4 } as T; + } + return {} as T; + }, + addEventListener: () => () => {}, + }; + const panel = document.createElement(TERMINAL_PANEL_ELEMENT_NAME) as OpenClawTerminalPanel; + panel.client = client; + panel.available = true; + document.body.append(panel); + panel.toggle(); + await vi.waitFor(() => { + expect(panel.renderRoot.querySelector(".tp-upload")?.disabled).toBe(false); + }); + + const drop = new Event("drop", { bubbles: true, cancelable: true }); + Object.defineProperty(drop, "dataTransfer", { + value: { + types: ["Files"], + files: [ + terminalUploadFile("scan final.pdf", "pdf"), + terminalUploadFile("notes.txt", "note"), + ], + dropEffect: "none", + }, + }); + panel.renderRoot.querySelector(".tp-viewport")?.dispatchEvent(drop); + + await vi.waitFor(() => { + const progress = panel.renderRoot.querySelector(".tp-upload-progress"); + expect(progress?.getAttribute("aria-valuenow")).toBe("1"); + expect(progress?.getAttribute("aria-valuemax")).toBe("2"); + expect(panel.renderRoot.querySelector(".tp-upload-card")?.textContent).toContain( + "Uploading 2 of 2", + ); + expect(panel.renderRoot.querySelector(".tp-upload-card")?.textContent).toContain("notes.txt"); + }); + expect(controller.terminal.paste).not.toHaveBeenCalled(); + + failedUpload.reject( + Object.assign(new Error("paired node went offline"), { + gatewayCode: "UNAVAILABLE", + retryable: false, + }), + ); + await vi.waitFor(() => { + const failed = panel.renderRoot.querySelector(".tp-upload-card--failed"); + expect(failed?.textContent).toContain("Upload failed"); + expect(failed?.textContent).toContain("paired node went offline"); + expect(panel.renderRoot.querySelector(".tp-upload-retry")).not.toBeNull(); + }); + panel.renderRoot.querySelector(".tp-upload-retry")?.click(); + + await vi.waitFor(() => { + expect(controller.terminal.paste).toHaveBeenCalledWith( + "'/tmp/openclaw upload/scan final.pdf' '/tmp/openclaw upload/notes.txt'", + ); + expect(panel.renderRoot.querySelector(".tp-upload-card")).toBeNull(); + }); + expect( + requests + .filter(({ method }) => method === "terminal.upload") + .map(({ params }) => (params as { name: string }).name), + ).toEqual(["scan final.pdf", "notes.txt", "notes.txt"]); + }); + + it("cancels an active batch without pasting staged paths", async () => { + const controller = createTerminalController(); + createGhosttyTerminalMock.mockResolvedValue(controller); + const pendingUpload = deferred<{ path: string; size: number }>(); + let uploadSignal: AbortSignal | undefined; + const client: TerminalGatewayClient = { + forceReconnect: () => {}, + request: async (method: string, _params?: unknown, options?: { signal?: AbortSignal }) => { + if (method === "terminal.open") { + return terminalOpenResult("session-1") as T; + } + if (method === "terminal.upload") { + uploadSignal = options?.signal; + return (await pendingUpload.promise) as T; + } + return {} as T; + }, + addEventListener: () => () => {}, + }; + const panel = document.createElement(TERMINAL_PANEL_ELEMENT_NAME) as OpenClawTerminalPanel; + panel.client = client; + panel.available = true; + document.body.append(panel); + panel.toggle(); + await vi.waitFor(() => { + expect(panel.renderRoot.querySelector(".tp-upload")?.disabled).toBe(false); + }); + + const drop = new Event("drop", { bubbles: true, cancelable: true }); + Object.defineProperty(drop, "dataTransfer", { + value: { + types: ["Files"], + files: [terminalUploadFile("archive.zip", "zip")], + dropEffect: "none", + }, + }); + panel.renderRoot.querySelector(".tp-viewport")?.dispatchEvent(drop); + await vi.waitFor(() => { + expect(panel.renderRoot.querySelector(".tp-upload-card")?.textContent).toContain( + "Uploading 1 of 1", + ); + }); + + panel.renderRoot.querySelector(".tp-upload-cancel")?.click(); + await panel.updateComplete; + expect(uploadSignal?.aborted).toBe(true); + expect(panel.renderRoot.querySelector(".tp-upload-card")).toBeNull(); + expect(panel.renderRoot.querySelector(".tp-upload")?.disabled).toBe(false); + + pendingUpload.resolve({ path: "/tmp/openclaw upload/archive.zip", size: 3 }); + await Promise.resolve(); + await Promise.resolve(); + expect(controller.terminal.paste).not.toHaveBeenCalled(); + }); + it("cancels a pending upload when its terminal tab closes", async () => { const controller = createTerminalController(); createGhosttyTerminalMock.mockResolvedValue(controller); const pendingUpload = deferred<{ path: string; size: number }>(); let uploadSignal: AbortSignal | undefined; const client: TerminalGatewayClient = { + forceReconnect: () => {}, request: async (method: string, _params?: unknown, options?: { signal?: AbortSignal }) => { if (method === "terminal.open") { return { diff --git a/ui/src/components/terminal/terminal-panel.test.ts b/ui/src/components/terminal/terminal-panel.test.ts index 542b104f3907..7fd5163cca1c 100644 --- a/ui/src/components/terminal/terminal-panel.test.ts +++ b/ui/src/components/terminal/terminal-panel.test.ts @@ -50,14 +50,6 @@ function terminalOpenResult(sessionId: string) { }; } -function terminalUploadFile(name: string, content: string): File { - const file = new File([content], name); - Object.defineProperty(file, "arrayBuffer", { - value: async () => new TextEncoder().encode(content).buffer, - }); - return file; -} - function deferred() { let resolve!: (value: T) => void; let reject!: (reason?: unknown) => void; @@ -89,6 +81,7 @@ async function startPanelWithPendingOpen() { const open = deferred>(); const requests: Array<{ method: string; params: unknown }> = []; const client: TerminalGatewayClient = { + forceReconnect: () => {}, request: (method: string, params?: unknown) => { requests.push({ method, params }); return (method === "terminal.open" ? open.promise : Promise.resolve({})) as Promise; @@ -147,6 +140,7 @@ describe("OpenClawTerminalPanel", () => { }); const requests: Array<{ method: string; params: unknown }> = []; const client: TerminalGatewayClient = { + forceReconnect: () => {}, request: async (method: string, params?: unknown) => { requests.push({ method, params }); return { @@ -253,37 +247,6 @@ describe("OpenClawTerminalPanel", () => { expect(requests.some(({ method }) => method === "terminal.input")).toBe(false); }); - it("opens a new titled tab for a catalog toggle request", async () => { - createGhosttyTerminalMock.mockImplementation(async () => createTerminalController()); - const requests: Array<{ method: string; params: unknown }> = []; - const client: TerminalGatewayClient = { - request: async (method: string, params?: unknown) => { - requests.push({ method, params }); - return { - ...terminalOpenResult("catalog-terminal-1"), - title: "codex resume 0d5c…", - } as T; - }, - addEventListener: () => () => {}, - }; - const panel = document.createElement(TERMINAL_PANEL_ELEMENT_NAME) as OpenClawTerminalPanel; - panel.client = client; - panel.available = true; - document.body.append(panel); - const catalog = { catalogId: "codex", hostId: "node:mac", threadId: "thread" }; - - panel.handleToggleRequest(new CustomEvent("openclaw:terminal-toggle", { detail: { catalog } })); - - await vi.waitFor(() => { - expect(requests).toContainEqual({ - method: "terminal.open", - params: { agentId: undefined, cols: 100, rows: 30, catalog }, - }); - }); - await panel.updateComplete; - expect(panel.renderRoot.querySelector(".tp-tab")?.textContent).toContain("codex resume 0d5c…"); - }); - it("reattaches persisted sessions before opening a catalog tab", async () => { sessionStorage.setItem("openclaw.terminal.sessions.v1", JSON.stringify(["persisted-1"])); createGhosttyTerminalMock @@ -291,6 +254,7 @@ describe("OpenClawTerminalPanel", () => { .mockResolvedValueOnce(createTerminalController()); const requests: Array<{ method: string; params: unknown }> = []; const client: TerminalGatewayClient = { + forceReconnect: () => {}, request: async (method: string, params?: unknown) => { requests.push({ method, params }); if (method === "terminal.list") { @@ -337,7 +301,7 @@ describe("OpenClawTerminalPanel", () => { requests.findIndex((entry) => entry.method === "terminal.open"), ); expect(sessionStorage.getItem("openclaw.terminal.sessions.v1")).toBe( - JSON.stringify(["persisted-1", "catalog-terminal-1"]), + JSON.stringify(["persisted-1"]), ); }); @@ -353,6 +317,7 @@ describe("OpenClawTerminalPanel", () => { .mockResolvedValueOnce(controllers[2]); const requests: Array<{ method: string; params: unknown }> = []; const client: TerminalGatewayClient = { + forceReconnect: () => {}, request: async (method: string, params?: unknown) => { requests.push({ method, params }); if (method === "terminal.open") { @@ -459,6 +424,7 @@ describe("OpenClawTerminalPanel", () => { const secondList = deferred<{ sessions: ListedSession[] }>(); let listCount = 0; const client: TerminalGatewayClient = { + forceReconnect: () => {}, request: (method: string) => { if (method === "terminal.open") { return Promise.resolve(terminalOpenResult("current-1")) as Promise; @@ -523,6 +489,7 @@ describe("OpenClawTerminalPanel", () => { .mockResolvedValueOnce(createTerminalController()) .mockResolvedValueOnce(createTerminalController()); const client: TerminalGatewayClient = { + forceReconnect: () => {}, request: async (method: string) => { if (method === "terminal.open") { return terminalOpenResult("current-1") as T; @@ -565,6 +532,7 @@ describe("OpenClawTerminalPanel", () => { const requests: Array<{ method: string; params: unknown }> = []; let openCount = 0; const client: TerminalGatewayClient = { + forceReconnect: () => {}, request: async (method: string, params?: unknown) => { requests.push({ method, params }); if (method === "terminal.open") { @@ -599,6 +567,7 @@ describe("OpenClawTerminalPanel", () => { createGhosttyTerminalMock.mockImplementation(async () => createTerminalController()); const requests: Array<{ method: string; params: unknown }> = []; const client: TerminalGatewayClient = { + forceReconnect: () => {}, request: async (method: string, params?: unknown) => { requests.push({ method, params }); return { @@ -647,6 +616,7 @@ describe("OpenClawTerminalPanel", () => { let listener: ((event: { event: string; payload: unknown }) => void) | undefined; let openCount = 0; const client: TerminalGatewayClient = { + forceReconnect: () => {}, request: async (method: string, params?: unknown) => { requests.push({ method, params }); if (method === "terminal.open") { @@ -716,6 +686,7 @@ describe("OpenClawTerminalPanel", () => { const oldRequests: string[] = []; const oldUnsubscribe = vi.fn(); const oldClient: TerminalGatewayClient = { + forceReconnect: () => {}, request: async (method: string) => { oldRequests.push(method); return (method === "terminal.open" ? terminalOpenResult("old-session") : {}) as T; @@ -724,6 +695,7 @@ describe("OpenClawTerminalPanel", () => { }; const newRequests: string[] = []; const newClient: TerminalGatewayClient = { + forceReconnect: () => {}, request: async (method: string) => { newRequests.push(method); if (method === "terminal.list") { @@ -763,6 +735,7 @@ describe("OpenClawTerminalPanel", () => { .mockResolvedValueOnce(currentController); const requests: string[] = []; const client: TerminalGatewayClient = { + forceReconnect: () => {}, request: async (method: string) => { requests.push(method); return (method === "terminal.open" ? terminalOpenResult("current-session") : {}) as T; @@ -804,6 +777,7 @@ describe("OpenClawTerminalPanel", () => { it("removes resize listeners when disconnected mid-drag", async () => { createGhosttyTerminalMock.mockResolvedValue(createTerminalController()); const client: TerminalGatewayClient = { + forceReconnect: () => {}, request: async (method: string) => (method === "terminal.open" ? terminalOpenResult("session-1") : {}) as T, addEventListener: () => () => {}, @@ -847,202 +821,11 @@ describe("OpenClawTerminalPanel", () => { expect(host.isConnected).toBe(false); }); - it("uploads dropped files and pastes shell-safe paths without executing", async () => { - const controller = createTerminalController(); - createGhosttyTerminalMock.mockResolvedValue(controller); - const requests: Array<{ method: string; params: unknown }> = []; - const client: TerminalGatewayClient = { - request: async (method: string, params?: unknown) => { - requests.push({ method, params }); - if (method === "terminal.open") { - return terminalOpenResult("session-1") as T; - } - if (method === "terminal.upload") { - return { path: "/tmp/openclaw upload/scan final.pdf", size: 3 } as T; - } - return {} as T; - }, - addEventListener: () => () => {}, - }; - const panel = document.createElement(TERMINAL_PANEL_ELEMENT_NAME) as OpenClawTerminalPanel; - panel.client = client; - panel.available = true; - document.body.append(panel); - panel.toggle(); - await vi.waitFor(() => { - expect(panel.renderRoot.querySelector(".tp-upload")?.disabled).toBe(false); - }); - expect(panel.renderRoot.querySelector(".tp-file-input")?.multiple).toBe(true); - - const file = new File(["pdf"], "scan final.pdf", { type: "application/pdf" }); - Object.defineProperty(file, "arrayBuffer", { - value: async () => new TextEncoder().encode("pdf").buffer, - }); - const drop = new Event("drop", { bubbles: true, cancelable: true }); - Object.defineProperty(drop, "dataTransfer", { - value: { types: ["Files"], files: [file], dropEffect: "none" }, - }); - panel.renderRoot.querySelector(".tp-viewport")?.dispatchEvent(drop); - - await vi.waitFor(() => { - expect(requests).toContainEqual({ - method: "terminal.upload", - params: { - sessionId: "session-1", - name: "scan final.pdf", - contentBase64: "cGRm", - }, - }); - }); - expect(controller.terminal.paste).toHaveBeenCalledWith("'/tmp/openclaw upload/scan final.pdf'"); - expect(controller.terminal.paste).not.toHaveBeenCalledWith(expect.stringContaining("\n")); - }); - - it("shows file progress and retries only the failed remainder", async () => { - const controller = createTerminalController(); - createGhosttyTerminalMock.mockResolvedValue(controller); - const requests: Array<{ method: string; params: unknown; signal?: AbortSignal }> = []; - const failedUpload = deferred<{ path: string; size: number }>(); - let notesAttempts = 0; - const client: TerminalGatewayClient = { - request: async (method: string, params?: unknown, options?: { signal?: AbortSignal }) => { - requests.push({ method, params, signal: options?.signal }); - if (method === "terminal.open") { - return terminalOpenResult("session-1") as T; - } - if (method === "terminal.upload") { - const name = (params as { name: string }).name; - if (name === "scan final.pdf") { - return { path: "/tmp/openclaw upload/scan final.pdf", size: 3 } as T; - } - notesAttempts += 1; - if (notesAttempts === 1) { - return (await failedUpload.promise) as T; - } - return { path: "/tmp/openclaw upload/notes.txt", size: 4 } as T; - } - return {} as T; - }, - addEventListener: () => () => {}, - }; - const panel = document.createElement(TERMINAL_PANEL_ELEMENT_NAME) as OpenClawTerminalPanel; - panel.client = client; - panel.available = true; - document.body.append(panel); - panel.toggle(); - await vi.waitFor(() => { - expect(panel.renderRoot.querySelector(".tp-upload")?.disabled).toBe(false); - }); - - const drop = new Event("drop", { bubbles: true, cancelable: true }); - Object.defineProperty(drop, "dataTransfer", { - value: { - types: ["Files"], - files: [ - terminalUploadFile("scan final.pdf", "pdf"), - terminalUploadFile("notes.txt", "note"), - ], - dropEffect: "none", - }, - }); - panel.renderRoot.querySelector(".tp-viewport")?.dispatchEvent(drop); - - await vi.waitFor(() => { - const progress = panel.renderRoot.querySelector(".tp-upload-progress"); - expect(progress?.getAttribute("aria-valuenow")).toBe("1"); - expect(progress?.getAttribute("aria-valuemax")).toBe("2"); - expect(panel.renderRoot.querySelector(".tp-upload-card")?.textContent).toContain( - "Uploading 2 of 2", - ); - expect(panel.renderRoot.querySelector(".tp-upload-card")?.textContent).toContain("notes.txt"); - }); - expect(controller.terminal.paste).not.toHaveBeenCalled(); - - failedUpload.reject( - Object.assign(new Error("paired node went offline"), { - gatewayCode: "UNAVAILABLE", - retryable: false, - }), - ); - await vi.waitFor(() => { - const failed = panel.renderRoot.querySelector(".tp-upload-card--failed"); - expect(failed?.textContent).toContain("Upload failed"); - expect(failed?.textContent).toContain("paired node went offline"); - expect(panel.renderRoot.querySelector(".tp-upload-retry")).not.toBeNull(); - }); - panel.renderRoot.querySelector(".tp-upload-retry")?.click(); - - await vi.waitFor(() => { - expect(controller.terminal.paste).toHaveBeenCalledWith( - "'/tmp/openclaw upload/scan final.pdf' '/tmp/openclaw upload/notes.txt'", - ); - expect(panel.renderRoot.querySelector(".tp-upload-card")).toBeNull(); - }); - expect( - requests - .filter(({ method }) => method === "terminal.upload") - .map(({ params }) => (params as { name: string }).name), - ).toEqual(["scan final.pdf", "notes.txt", "notes.txt"]); - }); - - it("cancels an active batch without pasting staged paths", async () => { - const controller = createTerminalController(); - createGhosttyTerminalMock.mockResolvedValue(controller); - const pendingUpload = deferred<{ path: string; size: number }>(); - let uploadSignal: AbortSignal | undefined; - const client: TerminalGatewayClient = { - request: async (method: string, _params?: unknown, options?: { signal?: AbortSignal }) => { - if (method === "terminal.open") { - return terminalOpenResult("session-1") as T; - } - if (method === "terminal.upload") { - uploadSignal = options?.signal; - return (await pendingUpload.promise) as T; - } - return {} as T; - }, - addEventListener: () => () => {}, - }; - const panel = document.createElement(TERMINAL_PANEL_ELEMENT_NAME) as OpenClawTerminalPanel; - panel.client = client; - panel.available = true; - document.body.append(panel); - panel.toggle(); - await vi.waitFor(() => { - expect(panel.renderRoot.querySelector(".tp-upload")?.disabled).toBe(false); - }); - - const drop = new Event("drop", { bubbles: true, cancelable: true }); - Object.defineProperty(drop, "dataTransfer", { - value: { - types: ["Files"], - files: [terminalUploadFile("archive.zip", "zip")], - dropEffect: "none", - }, - }); - panel.renderRoot.querySelector(".tp-viewport")?.dispatchEvent(drop); - await vi.waitFor(() => { - expect(panel.renderRoot.querySelector(".tp-upload-card")?.textContent).toContain( - "Uploading 1 of 1", - ); - }); - - panel.renderRoot.querySelector(".tp-upload-cancel")?.click(); - await panel.updateComplete; - expect(uploadSignal?.aborted).toBe(true); - expect(panel.renderRoot.querySelector(".tp-upload-card")).toBeNull(); - expect(panel.renderRoot.querySelector(".tp-upload")?.disabled).toBe(false); - - pendingUpload.resolve({ path: "/tmp/openclaw upload/archive.zip", size: 3 }); - await Promise.resolve(); - await Promise.resolve(); - expect(controller.terminal.paste).not.toHaveBeenCalled(); - }); - it("retranslates cached exit state when the locale changes", async () => { createGhosttyTerminalMock.mockResolvedValue(createTerminalController()); let listener: ((event: { event: string; payload: unknown }) => void) | undefined; const client: TerminalGatewayClient = { + forceReconnect: () => {}, request: async (method: string) => (method === "terminal.open" ? terminalOpenResult("session-1") : {}) as T, addEventListener: (nextListener) => { diff --git a/ui/src/components/terminal/terminal-panel.ts b/ui/src/components/terminal/terminal-panel.ts index 1a71fbe63efd..32dadb6ed516 100644 --- a/ui/src/components/terminal/terminal-panel.ts +++ b/ui/src/components/terminal/terminal-panel.ts @@ -18,6 +18,7 @@ import { import { TerminalConnection, type TerminalGatewayClient, + TerminalOpenTimeoutError, type TerminalSessionInfo, } from "./terminal-connection.ts"; import { terminalPanelStyles } from "./terminal-panel-styles.ts"; @@ -35,19 +36,24 @@ import { persistTerminalSessionIds, } from "./terminal-session-storage.ts"; import { createTerminalStartupInput, type StartupInputBuffer } from "./terminal-startup-input.ts"; +import { + TerminalTabReadinessController, + type TerminalTabReadinessState, +} from "./terminal-tab-readiness.ts"; import { TerminalTaskQueue } from "./terminal-task-queue.ts"; import { terminalTheme } from "./terminal-theme.ts"; type TerminalDock = DockPanelSide; -type TerminalTabState = TerminalPanelTab & { - gatewaySessionId: string; - pendingInput: StartupInputBuffer; - controller: GhosttyTerminalController; - shell: string; - host: HTMLDivElement; - /** Why an in-flight open/attach must not adopt this disposed terminal. */ - cancelled?: "close" | "lifecycle"; -}; +type TerminalTabState = TerminalPanelTab & + TerminalTabReadinessState & { + gatewaySessionId: string; + pendingInput: StartupInputBuffer; + controller: GhosttyTerminalController; + shell: string; + host: HTMLDivElement; + /** Why an in-flight open/attach must not adopt this disposed terminal. */ + cancelled?: "close" | "lifecycle"; + }; type TerminalOperation = { generation: number; @@ -72,6 +78,7 @@ const panelLayout = createDockPanelLayout({ const TERMINAL_FONT_FAMILY = 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Symbols Nerd Font Mono", "MesloLGLDZ Nerd Font Mono", "JetBrainsMono Nerd Font Mono", "Liberation Mono", monospace'; const TERMINAL_OUTPUT_ENCODER = new TextEncoder(); +const CATALOG_TERMINAL_READY_TIMEOUT_MS = 30_000; /** `` — the dockable Control UI shell surface. */ export class OpenClawTerminalPanel extends OpenClawLitElement { @@ -123,6 +130,21 @@ export class OpenClawTerminalPanel extends OpenClawLitElement { }); private readonly bootQueue = new TerminalTaskQueue(); protected createTerminal = createIsolatedGhosttyTerminal; + protected catalogReadyTimeoutMs = CATALOG_TERMINAL_READY_TIMEOUT_MS; + private readonly readiness = new TerminalTabReadinessController({ + timeoutMs: () => this.catalogReadyTimeoutMs, + isCurrent: (tab) => this.tabs.includes(tab), + onReady: () => { + this.tabs = [...this.tabs]; + this.persistLiveSessions(); + }, + onTimeout: (tab) => { + this.errorText = t("terminal.connectionTimedOut"); + void this.connection?.close(tab.gatewaySessionId); + this.dropFailedTab(tab); + this.persistLiveSessions(); + }, + }); private readonly onGlobalKeyDown = (event: KeyboardEvent) => this.handleGlobalKey(event); private readonly onToggleRequest = (event: Event) => this.handleToggleRequest(event); // Re-clamp a dock sized on a larger window so the header/resizer never end @@ -461,7 +483,10 @@ export class OpenClawTerminalPanel extends OpenClawLitElement { } /** Boots a tab with a libterminal controller, ready for an open or attach RPC. */ - private async bootTab(operation: TerminalOperation): Promise<{ + private async bootTab( + operation: TerminalOperation, + options: { awaitFirstOutput?: boolean } = {}, + ): Promise<{ tab: TerminalTabState; connection: TerminalConnection; cols: number; @@ -528,7 +553,9 @@ export class OpenClawTerminalPanel extends OpenClawLitElement { cwd: null, controller, host, - status: "live", + status: "connecting", + awaitFirstOutput: options.awaitFirstOutput === true, + readyTimer: null, }; tabRef.current = tab; this.tabs = [...this.tabs, tab]; @@ -545,6 +572,9 @@ export class OpenClawTerminalPanel extends OpenClawLitElement { onData: (data: string) => { if (!tab.cancelled) { tab.controller.write(TERMINAL_OUTPUT_ENCODER.encode(data)); + if (data.length > 0) { + this.readiness.markReady(tab); + } } }, // A replay is authoritative. Reset parser, screen, and scrollback so a @@ -554,10 +584,12 @@ export class OpenClawTerminalPanel extends OpenClawLitElement { tab.controller.terminal.reset(); if (data) { tab.controller.write(TERMINAL_OUTPUT_ENCODER.encode(data)); + this.readiness.markReady(tab); } } }, - onExit: (info: { reason?: string; exitCode: number | null }) => this.handleExit(tab.id, info), + onExit: (info: { reason?: string; exitCode: number | null; error?: string }) => + this.handleExit(tab.id, info), }; } @@ -579,6 +611,14 @@ export class OpenClawTerminalPanel extends OpenClawLitElement { void this.connection?.input(result.sessionId, data); } + if (tab.status === "connecting") { + if (tab.awaitFirstOutput) { + this.readiness.arm(tab); + } else { + this.readiness.markReady(tab); + } + } + this.tabs = [...this.tabs]; this.persistLiveSessions(); } @@ -608,7 +648,7 @@ export class OpenClawTerminalPanel extends OpenClawLitElement { // Tracked outside the try so the catch can dispose a tab whose open failed. let createdTab: TerminalTabState | undefined; try { - const boot = await this.bootTab(operation); + const boot = await this.bootTab(operation, { awaitFirstOutput: Boolean(catalog) }); createdTab = boot.tab; const result = await boot.connection.open( { agentId, cols: boot.cols, rows: boot.rows, ...(catalog ? { catalog } : {}) }, @@ -637,7 +677,12 @@ export class OpenClawTerminalPanel extends OpenClawLitElement { if (!this.isTerminalOperationCurrent(operation)) { return; } - this.errorText = err instanceof Error ? err.message : String(err); + this.errorText = + err instanceof TerminalOpenTimeoutError + ? t("terminal.connectionTimedOut") + : err instanceof Error + ? err.message + : String(err); } finally { if (this.isTerminalOperationCurrent(operation)) { this.booting = false; @@ -677,14 +722,21 @@ export class OpenClawTerminalPanel extends OpenClawLitElement { } } - private handleExit(tabId: string, info: { reason?: string; exitCode: number | null }): void { + private handleExit( + tabId: string, + info: { reason?: string; exitCode: number | null; error?: string }, + ): void { const tab = this.tabs.find((entry) => entry.id === tabId); if (!tab) { return; } + this.readiness.stop(tab); tab.status = "exited"; tab.exitReason = info.reason; tab.exitCode = info.exitCode; + if (info.error?.trim()) { + this.errorText = info.error.trim(); + } // The connection drops its own sink on exit delivery, so no release() here — // the session id may not be recorded yet when an early exit is replayed. this.tabs = [...this.tabs]; @@ -697,9 +749,9 @@ export class OpenClawTerminalPanel extends OpenClawLitElement { return; } this.upload.cancelForTab(tab); - if (tab.gatewaySessionId && tab.status === "live") { + if (tab.gatewaySessionId && tab.status !== "exited") { void this.connection?.close(tab.gatewaySessionId); - } else if (!tab.gatewaySessionId && tab.status === "live") { + } else if (!tab.gatewaySessionId && tab.status !== "exited") { // Open still in flight: no session id to close yet. Flag it so the open // continuation closes the server session as soon as the RPC resolves. tab.cancelled = "close"; @@ -760,6 +812,7 @@ export class OpenClawTerminalPanel extends OpenClawLitElement { } private disposeTab(tab: TerminalTabState): void { + this.readiness.stop(tab); try { tab.controller.dispose(); } catch { @@ -893,6 +946,9 @@ export class OpenClawTerminalPanel extends OpenClawLitElement { : this.dock === "bottom" ? `height:${this.height}px;--tp-panel-height:${this.height}px` : `width:${this.width}px`; + const activeTab = this.tabs.find((tab) => tab.id === this.activeId); + const connecting = + (this.booting && this.tabs.length === 0) || activeTab?.status === "connecting"; return html`
${this.fullscreen @@ -950,8 +1006,11 @@ export class OpenClawTerminalPanel extends OpenClawLitElement { @dragleave=${this.upload.handleDragLeave} @drop=${this.upload.handleDrop} > - ${this.booting && this.tabs.length === 0 - ? html`
${t("terminal.starting")}
` + ${connecting + ? html`
+ + ${t("terminal.connecting")} +
` : nothing} ${renderTerminalUploadLayer(this.upload)} diff --git a/ui/src/components/terminal/terminal-tab-readiness.ts b/ui/src/components/terminal/terminal-tab-readiness.ts new file mode 100644 index 000000000000..26763f5c902f --- /dev/null +++ b/ui/src/components/terminal/terminal-tab-readiness.ts @@ -0,0 +1,52 @@ +import type { TerminalPanelTab } from "./terminal-panel-tabs.ts"; + +export type TerminalTabReadinessState = { + awaitFirstOutput: boolean; + readyTimer: ReturnType | null; +}; + +type ReadyTab = TerminalPanelTab & TerminalTabReadinessState; + +export class TerminalTabReadinessController { + constructor( + private readonly options: { + timeoutMs: () => number; + isCurrent: (tab: T) => boolean; + onReady: (tab: T) => void; + onTimeout: (tab: T) => void; + }, + ) {} + + markReady(tab: T): void { + this.stop(tab); + if (tab.status !== "connecting") { + return; + } + tab.status = "live"; + this.options.onReady(tab); + } + + arm(tab: T): void { + if (tab.readyTimer || tab.status !== "connecting" || !tab.awaitFirstOutput) { + return; + } + // PTY creation only proves transport setup. Native CLIs are ready once they + // emit their first byte; otherwise a broken relay looks like a blank shell. + tab.readyTimer = setTimeout(() => { + tab.readyTimer = null; + if (!this.options.isCurrent(tab) || tab.status !== "connecting" || !tab.awaitFirstOutput) { + return; + } + tab.awaitFirstOutput = false; + this.options.onTimeout(tab); + }, this.options.timeoutMs()); + } + + stop(tab: T): void { + if (tab.readyTimer) { + clearTimeout(tab.readyTimer); + tab.readyTimer = null; + } + tab.awaitFirstOutput = false; + } +} diff --git a/ui/src/e2e/claude-sessions.e2e.test.ts b/ui/src/e2e/claude-sessions.e2e.test.ts index 3a62aaa78f24..bc70875f43cc 100644 --- a/ui/src/e2e/claude-sessions.e2e.test.ts +++ b/ui/src/e2e/claude-sessions.e2e.test.ts @@ -1,6 +1,6 @@ import fs from "node:fs/promises"; import path from "node:path"; -import { chromium, type Browser, type Locator } from "playwright"; +import { chromium, type Browser, type Locator, type Page } from "playwright"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { canRunPlaywrightChromium, @@ -131,6 +131,47 @@ function expectStableVirtualRowPrepend( ).toBe(true); } +function resumableClaudeCatalog() { + return { + catalogs: [ + { + id: "claude", + label: "Claude Code", + capabilities: { continueSession: true, archive: false }, + hosts: [ + { + hostId: "gateway:local", + label: "Local Mac", + kind: "local", + connected: true, + sessions: [ + { + threadId: "claude-terminal-session", + name: "Native Claude terminal", + status: "stored", + source: "claude-cli", + archived: false, + canContinue: true, + canArchive: false, + canOpenTerminal: true, + }, + ], + }, + ], + }, + ], + }; +} + +async function openClaudeCatalogTerminal(page: Page) { + await page.goto(`${server.baseUrl}chat`); + const row = page.locator('[data-session-key^="catalog:"]').filter({ + hasText: "Native Claude terminal", + }); + await row.click({ button: "right" }); + await page.locator('wa-dropdown-item[value="terminal"]').click(); +} + suite("Claude native session catalog", () => { beforeAll(async () => { if (!available) { @@ -145,6 +186,118 @@ suite("Claude native session catalog", () => { await server?.close(); }); + it("shows catalog connection progress until the first terminal output", async () => { + const context = await browser.newContext({ viewport: { width: 1440, height: 900 } }); + const page = await context.newPage(); + const gateway = await installMockGateway(page, { + deferredMethods: ["terminal.open"], + featureMethods: [ + "chat.metadata", + "chat.startup", + "sessions.catalog.list", + "sessions.catalog.read", + "terminal.open", + ], + methodResponses: { + "sessions.catalog.list": resumableClaudeCatalog(), + "sessions.catalog.read": { + hostId: "gateway:local", + threadId: "claude-terminal-session", + items: [{ type: "userMessage", text: "Continue the native session" }], + }, + "terminal.list": { sessions: [] }, + }, + terminalEnabled: true, + }); + + try { + await openClaudeCatalogTerminal(page); + const open = await gateway.waitForRequest("terminal.open"); + expect(open.params).toMatchObject({ + catalog: { + catalogId: "claude", + hostId: "gateway:local", + threadId: "claude-terminal-session", + }, + }); + const connecting = page.getByRole("status").filter({ hasText: "Connecting to session" }); + await connecting.waitFor(); + expect(await page.locator(".tp-tab.is-connecting").count()).toBe(1); + + const artifactDir = process.env.OPENCLAW_UI_E2E_ARTIFACT_DIR?.trim(); + if (artifactDir) { + await fs.mkdir(artifactDir, { recursive: true }); + await page.screenshot({ path: path.join(artifactDir, "claude-terminal-connecting.png") }); + } + + await gateway.resolveDeferred("terminal.open", { + agentId: "main", + confined: false, + cwd: "/workspace", + sessionId: "claude-terminal-e2e", + shell: "/bin/zsh", + title: "claude --resume claude-termi…", + }); + await expect.poll(() => connecting.count()).toBe(1); + await gateway.emitGatewayEvent("terminal.data", { + sessionId: "claude-terminal-e2e", + seq: 17, + data: "Claude Code ready\r\n", + }); + await expect.poll(() => connecting.count()).toBe(0); + expect(await page.locator(".tp-tab.is-live").count()).toBe(1); + } finally { + await context.close(); + } + }); + + it("closes a catalog terminal that produces no output before the deadline", async () => { + const context = await browser.newContext(); + const page = await context.newPage(); + const gateway = await installMockGateway(page, { + featureMethods: [ + "chat.metadata", + "chat.startup", + "sessions.catalog.list", + "sessions.catalog.read", + "terminal.open", + ], + methodResponses: { + "sessions.catalog.list": resumableClaudeCatalog(), + "sessions.catalog.read": { + hostId: "gateway:local", + threadId: "claude-terminal-session", + items: [], + }, + "terminal.list": { sessions: [] }, + "terminal.open": { + agentId: "main", + confined: false, + cwd: "/workspace", + sessionId: "claude-terminal-timeout", + shell: "/bin/zsh", + title: "claude --resume claude-termi…", + }, + }, + terminalEnabled: true, + }); + + try { + await page.clock.install(); + await openClaudeCatalogTerminal(page); + await gateway.waitForRequest("terminal.open"); + await page.getByRole("status").filter({ hasText: "Connecting to session" }).waitFor(); + await page.clock.runFor(30_001); + + await page.getByText("Session did not connect within 30 seconds.", { exact: true }).waitFor(); + const close = await gateway.waitForRequest("terminal.close"); + expect(close.params).toEqual({ sessionId: "claude-terminal-timeout" }); + expect(await page.locator(".tp-tab").count()).toBe(0); + } finally { + await context.close(); + } + }); + it("auto-loads older chat without moving the viewport and disables paired-node continuation", async () => { const page = await browser.newPage(); await page.clock.install(); diff --git a/ui/src/i18n/.i18n/ar.meta.json b/ui/src/i18n/.i18n/ar.meta.json index ba6cd20f9b15..91ad804b989f 100644 --- a/ui/src/i18n/.i18n/ar.meta.json +++ b/ui/src/i18n/.i18n/ar.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-07-14T23:35:51.115Z", + "generatedAt": "2026-07-15T00:45:23.615Z", "locale": "ar", - "model": "claude-opus-4-8", - "provider": "anthropic", - "sourceHash": "85e004373478d946c5002519a35b2c50e1aa220fd2a81f7c1c80ded99bc7dc4e", - "totalKeys": 3353, - "translatedKeys": 3353, + "model": "gpt-5.6-sol", + "provider": "openai", + "sourceHash": "dc6d0f7bdb69960a5b9d54c5743445b89b0689913b3f962829f8490dede56d2c", + "totalKeys": 3355, + "translatedKeys": 3355, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/ar.tm.jsonl b/ui/src/i18n/.i18n/ar.tm.jsonl index 99dc995d88dd..5f58ec209328 100644 --- a/ui/src/i18n/.i18n/ar.tm.jsonl +++ b/ui/src/i18n/.i18n/ar.tm.jsonl @@ -1549,7 +1549,9 @@ {"cache_key":"dac4aa199d3b6e8b364499cd91adc775def2c7b3f9572aa1bef60e352abd0898","model":"gpt-5.5","provider":"openai","segment_id":"chat.composer.loadingMicrophones","source_path":"ui/src/i18n/locales/ar.ts","src_lang":"en","text":"Loading microphones…","text_hash":"042a481c407f31b97b0cc8ff4e6c8b8f3f6e85d798cba8549c348d6d33a9945c","tgt_lang":"ar","translated":"جارٍ تحميل الميكروفونات…","updated_at":"2026-07-06T17:33:50.709Z"} {"cache_key":"db35b841b8920c0afee334b5565e2cba2f8a016fe1e3d56a0acf650971bdc2e5","model":"gpt-5.6-sol","provider":"openai","segment_id":"dreaming.wiki.id","source_path":"ui/src/i18n/locales/ar.ts","src_lang":"en","text":"Id:","text_hash":"68d036759794566ce5d3e8d118a575d6b481ceeb79b324e938a3e06614164d32","tgt_lang":"ar","translated":"المعرّف:","updated_at":"2026-07-12T07:00:40.742Z"} {"cache_key":"db54f3edea7af3d6db8e66ee8a77ac0b99aecaa81310f10c3b98d594bd8f4ee5","model":"gpt-5.6-sol","provider":"openai","segment_id":"agentTools.overrideOn","source_path":"ui/src/i18n/locales/ar.ts","src_lang":"en","text":"Override On","text_hash":"3740d84158209fb42847c00fb88816dc86fa62e9083bc56953fe2fcf0ee2942c","tgt_lang":"ar","translated":"التجاوز مفعّل","updated_at":"2026-07-12T06:59:12.313Z"} +{"cache_key":"db72f08123aba0fc51c9f06d6e2bf4eea7bac689b61029070977f68e0e6aefac","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connectionTimedOut","source_path":"ui/src/i18n/locales/ar.ts","src_lang":"en","text":"Session did not connect within 30 seconds.","text_hash":"38c85973d7e3a6e0d95a423366c67d737a8857eeeb09954ed1e31bf47a0f4b9e","tgt_lang":"ar","translated":"تعذّر الاتصال بالجلسة خلال 30 ثانية.","updated_at":"2026-07-15T00:45:23.615Z"} {"cache_key":"db848099464ca02f0d6941a55267884c2a626bff5fadd98e3688e7847ed6de8b","model":"claude-opus-4-8","provider":"anthropic","segment_id":"chat.workspaceFiles.preview","source_path":"ui/src/i18n/locales/ar.ts","src_lang":"en","text":"Preview","text_hash":"324b134f57c70c729ae3dc4d298bb451656717d70523e942c1ce667b8024ea07","tgt_lang":"ar","translated":"معاينة","updated_at":"2026-06-16T14:15:44.085Z"} +{"cache_key":"dba7161818a3dc2d88ad414eaf92786d70fc9b59ebd8f0672930cf6172325d65","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connecting","source_path":"ui/src/i18n/locales/ar.ts","src_lang":"en","text":"Connecting to session…","text_hash":"f1f49121a17a09b93f1601768c015f6fa9ef2056365c5a745837a2be98f71874","tgt_lang":"ar","translated":"جارٍ الاتصال بالجلسة…","updated_at":"2026-07-15T00:45:23.615Z"} {"cache_key":"dbce10b79cd0a126c40ee72de1b4951d75ea0eca3d0d8e0227a99a5211621b10","model":"gpt-5.6-sol","provider":"openai","segment_id":"dreaming.wiki.openConfig","source_path":"ui/src/i18n/locales/ar.ts","src_lang":"en","text":"Open Config","text_hash":"63697308e9be76a975649e826a0640b5badf2e94f21a82af5e2c66c4a75889c4","tgt_lang":"ar","translated":"فتح الإعدادات","updated_at":"2026-07-12T07:00:40.742Z"} {"cache_key":"dbf577649c253dae4464d601a9313a88d8c0e1e22ba8fe93064588429eb9b75c","model":"gpt-5.6-sol","provider":"openai","segment_id":"configForm.customEntries","source_path":"ui/src/i18n/locales/ar.ts","src_lang":"en","text":"Custom entries","text_hash":"1d5eb91b086b17275aa0d539482ac5c845807edb5d2b96ceaf7cf5cc4a70d5da","tgt_lang":"ar","translated":"إدخالات مخصّصة","updated_at":"2026-07-12T06:58:08.154Z"} {"cache_key":"dc1e1e1c65a0df066efbeb2104e02b5332d73e24727ea67165fc0ea5d2fa5a44","model":"gpt-5.6-sol","provider":"openai","segment_id":"skillStatus.disabled","source_path":"ui/src/i18n/locales/ar.ts","src_lang":"en","text":"disabled","text_hash":"17eb3c0168d0d7b21ede5481150f17233427d89833ec121b4dbc4fb96cfab71e","tgt_lang":"ar","translated":"معطّل","updated_at":"2026-07-12T06:59:37.065Z"} diff --git a/ui/src/i18n/.i18n/catalog-fallbacks.json b/ui/src/i18n/.i18n/catalog-fallbacks.json index 13f2067e1097..47db9ea26a3d 100644 --- a/ui/src/i18n/.i18n/catalog-fallbacks.json +++ b/ui/src/i18n/.i18n/catalog-fallbacks.json @@ -1,5 +1,5 @@ { "fallbacks": {}, - "sourceHash": "85e004373478d946c5002519a35b2c50e1aa220fd2a81f7c1c80ded99bc7dc4e", + "sourceHash": "dc6d0f7bdb69960a5b9d54c5743445b89b0689913b3f962829f8490dede56d2c", "version": 1 } diff --git a/ui/src/i18n/.i18n/de.meta.json b/ui/src/i18n/.i18n/de.meta.json index 1074f46963db..c05d1d54085b 100644 --- a/ui/src/i18n/.i18n/de.meta.json +++ b/ui/src/i18n/.i18n/de.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-07-14T23:35:50.466Z", + "generatedAt": "2026-07-15T00:45:07.547Z", "locale": "de", - "model": "claude-opus-4-8", - "provider": "anthropic", - "sourceHash": "85e004373478d946c5002519a35b2c50e1aa220fd2a81f7c1c80ded99bc7dc4e", - "totalKeys": 3353, - "translatedKeys": 3353, + "model": "gpt-5.6-sol", + "provider": "openai", + "sourceHash": "dc6d0f7bdb69960a5b9d54c5743445b89b0689913b3f962829f8490dede56d2c", + "totalKeys": 3355, + "translatedKeys": 3355, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/de.tm.jsonl b/ui/src/i18n/.i18n/de.tm.jsonl index 2b52ccdb0f19..a2924175cd57 100644 --- a/ui/src/i18n/.i18n/de.tm.jsonl +++ b/ui/src/i18n/.i18n/de.tm.jsonl @@ -240,6 +240,7 @@ {"cache_key":"21c3baf040c532b1dbfc3412b7b07aa2ff4d5733a9964ee5f3887ec894439cc0","model":"gpt-5","provider":"openai","segment_id":"claudeSessions.searchPlaceholder","source_path":"ui/src/i18n/locales/de.ts","src_lang":"en","text":"Search session titles","text_hash":"9a6bdede84234fa08d6294e1636a34d26cbdd8eca425248f8d3c6cf8844a0cf9","tgt_lang":"de","translated":"Sitzungstitel durchsuchen","updated_at":"2026-07-09T10:01:43.726Z"} {"cache_key":"222981f3dabff8f006dd110acf52ee80162d3e8d00d35cc3937c86baa8fab05f","model":"claude-opus-4-8","provider":"anthropic","segment_id":"workboard.agentFilterUnassigned","source_path":"ui/src/i18n/locales/de.ts","src_lang":"en","text":"Unassigned (uses {agent})","text_hash":"2700af0c4ab5e86726f72a723ecdf50370b87690db35f00b83723d6457879c8e","tgt_lang":"de","translated":"Nicht zugewiesen (verwendet {agent})","updated_at":"2026-06-17T14:13:07.390Z"} {"cache_key":"2282d02251bb51133ef9d333372392209eca239eb0d6456ff5bf33664269c0bf","model":"claude-opus-4-8","provider":"anthropic","segment_id":"agentTools.title","source_path":"ui/src/i18n/locales/de.ts","src_lang":"en","text":"Tool Access","text_hash":"cf8084fcb3ba5158b83bed00ade2acfcba14311d9da2e7f7891f321addcd1c6c","tgt_lang":"de","translated":"Tool-Zugriff","updated_at":"2026-07-12T06:27:35.086Z"} +{"cache_key":"228a07205cd33a6b462b44416dbc27cf74b17930d063a656ad5c0cf8244e9878","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connecting","source_path":"ui/src/i18n/locales/de.ts","src_lang":"en","text":"Connecting to session…","text_hash":"f1f49121a17a09b93f1601768c015f6fa9ef2056365c5a745837a2be98f71874","tgt_lang":"de","translated":"Verbindung zur Sitzung wird hergestellt…","updated_at":"2026-07-15T00:45:07.547Z"} {"cache_key":"229e511cd57fbe3e3bd052510b240e9455817fe54a871b3c68ec58b36cab3f91","model":"gpt-5","provider":"openai","segment_id":"modelProviders.probe.status.ok","source_path":"ui/src/i18n/locales/de.ts","src_lang":"en","text":"Connected","text_hash":"22965568d22a14ee17af055d2870b50afcfe9fd94a83eec3196e266932297bb2","tgt_lang":"de","translated":"Verbunden","updated_at":"2026-07-09T10:01:43.726Z"} {"cache_key":"22c09ac355f646e893761f9ae2c071946cf7e4f0f060eb37d76b33604c344013","model":"claude-opus-4-8","provider":"anthropic","segment_id":"nodes.inventory.roleUpgrade","source_path":"ui/src/i18n/locales/de.ts","src_lang":"en","text":"role upgrade requires approval","text_hash":"c2c2a021e6839c8bb89fcf5d387a4a7c577bf4a042a110220ee1ffb78f238966","tgt_lang":"de","translated":"Rollen-Erweiterung erfordert Genehmigung","updated_at":"2026-07-12T06:25:24.484Z"} {"cache_key":"22dca2f6ae545073b14cb1f44d2352ddd0e046a4b9983c82403c3fd5fc7a66ee","model":"gpt-5.6-sol","provider":"openai","segment_id":"cron.form.summaryCron","source_path":"ui/src/i18n/locales/de.ts","src_lang":"en","text":"Cron schedule {expr}","text_hash":"953e6a80deb2a7c7fd3a8a29e4c363baa92bc4df48ae58ac0c7dafee554fd510","tgt_lang":"de","translated":"Cron-Zeitplan {expr}","updated_at":"2026-07-12T09:21:49.502Z"} @@ -1359,6 +1360,7 @@ {"cache_key":"bc745191d77ded6eb1fc717e44b1f0dd0d9a5e86e6d6e484612a7f2edc3f5a24","model":"claude-opus-4-8","provider":"anthropic","segment_id":"configForm.customEntries","source_path":"ui/src/i18n/locales/de.ts","src_lang":"en","text":"Custom entries","text_hash":"1d5eb91b086b17275aa0d539482ac5c845807edb5d2b96ceaf7cf5cc4a70d5da","tgt_lang":"de","translated":"Benutzerdefinierte Einträge","updated_at":"2026-07-12T06:26:04.418Z"} {"cache_key":"bc8d533ff5bdecd6eb0d3cc8f3959e0883abfc31c4015a40d760048329d6b6a4","model":"claude-opus-4-8","provider":"anthropic","segment_id":"configForm.noItems","source_path":"ui/src/i18n/locales/de.ts","src_lang":"en","text":"No items yet. Click \"Add\" to create one.","text_hash":"7911888656dc431af458b7521f85f45aeeb702b8060d63a49d52520aa96cd9e8","tgt_lang":"de","translated":"Noch keine Elemente. Klicken Sie auf „Hinzufügen“, um eines zu erstellen.","updated_at":"2026-07-12T06:26:04.418Z"} {"cache_key":"bc8f51f2daa9f811e422f883cce65998a0072fd03163531457e5e8a011583136","model":"gpt-5.5","provider":"openai","segment_id":"chat.pullRequests.ciMonitoring","source_path":"ui/src/i18n/locales/de.ts","src_lang":"en","text":"CI monitoring","text_hash":"b729ae0c12be4bfdccc13ca31d0ee718cfc6a324b564daa239a5bf2e147b3398","tgt_lang":"de","translated":"CI-Überwachung","updated_at":"2026-07-10T23:12:26.926Z"} +{"cache_key":"bca9f570f1aba136ac16df762ca4a74bbcf033e85a2700cd9e85d7c8ce35263a","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connectionTimedOut","source_path":"ui/src/i18n/locales/de.ts","src_lang":"en","text":"Session did not connect within 30 seconds.","text_hash":"38c85973d7e3a6e0d95a423366c67d737a8857eeeb09954ed1e31bf47a0f4b9e","tgt_lang":"de","translated":"Die Verbindung zur Sitzung wurde nicht innerhalb von 30 Sekunden hergestellt.","updated_at":"2026-07-15T00:45:07.547Z"} {"cache_key":"bcc72747c637cb37cbd61a2031e294497bde9b3268d2382c72a1dd3eb840d386","model":"claude-opus-4-8","provider":"anthropic","segment_id":"agentTools.enableNamed","source_path":"ui/src/i18n/locales/de.ts","src_lang":"en","text":"Enable {name}","text_hash":"dd01fc045da3bbf286494b66561c74d4e5e7ea1295a99bbd69dafb34d647928b","tgt_lang":"de","translated":"{name} aktivieren","updated_at":"2026-07-12T06:27:47.285Z"} {"cache_key":"bd69dd219b34a86ad13ed2d47d897c45139f72401d20a9d02a521c8284368128","model":"gpt-5.5","provider":"openai","segment_id":"newSession.title","source_path":"ui/src/i18n/locales/de.ts","src_lang":"en","text":"New session","text_hash":"cffdba22adf299e8fc6f937ef63ca4352ca9db3022d4634004c0a9bd10aa0dfe","tgt_lang":"de","translated":"Neue Sitzung","updated_at":"2026-07-10T15:20:45.759Z"} {"cache_key":"bdbd69d4d05e013329248fdf282ba894f41e5a92268171705cb01e7c1f73c53c","model":"claude-opus-4-8","provider":"anthropic","segment_id":"cron.form.failureAlertInherit","source_path":"ui/src/i18n/locales/de.ts","src_lang":"en","text":"Inherit global setting","text_hash":"d4a23b45ca8a97a420a2716e0c2b9db41f0a89adc259a43f8c842cea1fcd1491","tgt_lang":"de","translated":"Globale Einstellung übernehmen","updated_at":"2026-07-12T06:30:02.971Z"} diff --git a/ui/src/i18n/.i18n/es.meta.json b/ui/src/i18n/.i18n/es.meta.json index 4ec7ca9b958c..90ee498996ee 100644 --- a/ui/src/i18n/.i18n/es.meta.json +++ b/ui/src/i18n/.i18n/es.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-07-14T23:35:50.574Z", + "generatedAt": "2026-07-15T00:45:09.673Z", "locale": "es", - "model": "claude-opus-4-8", - "provider": "anthropic", - "sourceHash": "85e004373478d946c5002519a35b2c50e1aa220fd2a81f7c1c80ded99bc7dc4e", - "totalKeys": 3353, - "translatedKeys": 3353, + "model": "gpt-5.6-sol", + "provider": "openai", + "sourceHash": "dc6d0f7bdb69960a5b9d54c5743445b89b0689913b3f962829f8490dede56d2c", + "totalKeys": 3355, + "translatedKeys": 3355, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/es.tm.jsonl b/ui/src/i18n/.i18n/es.tm.jsonl index 1e8efbc4d682..860bc9290d40 100644 --- a/ui/src/i18n/.i18n/es.tm.jsonl +++ b/ui/src/i18n/.i18n/es.tm.jsonl @@ -627,6 +627,7 @@ {"cache_key":"5374b6991e3457597aa34c1ac94740da287f12cb2241f61c42acbfee762a8997","model":"claude-opus-4-8","provider":"anthropic","segment_id":"chat.workspaceFiles.artifacts","source_path":"ui/src/i18n/locales/es.ts","src_lang":"en","text":"Artifacts","text_hash":"314ae71b8c8dc9c952f0ffc58e35e6d9a41b5cf4756471c7cab0c9476cd5d20b","tgt_lang":"es","translated":"Artefactos","updated_at":"2026-06-16T14:14:23.912Z"} {"cache_key":"537569c7d6b0e66bda3ea379226e9a01302c5a0b725f20e0bc93344ff2d5e5f8","model":"claude-opus-4-8","provider":"anthropic","segment_id":"githubPreview.ariaLabel","source_path":"ui/src/i18n/locales/es.ts","src_lang":"en","text":"{state} {kind} {repo} #{number}: {title}, by {author}","text_hash":"e18c624550814e4318a45e1e0f8782913409763d046aa703519e91c887be205b","tgt_lang":"es","translated":"{state} {kind} {repo} #{number}: {title}, por {author}","updated_at":"2026-07-12T06:31:12.973Z"} {"cache_key":"53a39119b041c8b526ef9cf2fae89094f1299c8f5e3772df4dab61042c3f4518","model":"claude-opus-4-8","provider":"anthropic","segment_id":"chat.commentaryLabel","source_path":"ui/src/i18n/locales/es.ts","src_lang":"en","text":"Commentary","text_hash":"4a7a75ab79cde05b0b1baa8f7a704c991586ea44d7e7793e57178ef56f778ff8","tgt_lang":"es","translated":"Comentarios","updated_at":"2026-07-01T01:06:39.912Z"} +{"cache_key":"53be9b8d5cb43e2f3ff71cdeca7fc995fc8cf6184d28ca691d24067dd34eaa87","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connecting","source_path":"ui/src/i18n/locales/es.ts","src_lang":"en","text":"Connecting to session…","text_hash":"f1f49121a17a09b93f1601768c015f6fa9ef2056365c5a745837a2be98f71874","tgt_lang":"es","translated":"Conectando con la sesión…","updated_at":"2026-07-15T00:45:09.673Z"} {"cache_key":"53e5bda60a120f4bcabf15af0b94dd1dfef943f58e03809378ab07ca66632bb5","model":"claude-opus-4-8","provider":"anthropic","segment_id":"cron.detail.historyTitle","source_path":"ui/src/i18n/locales/es.ts","src_lang":"en","text":"Run history","text_hash":"addf321bfa5b8346b1699c837e7658a4c646025227efada351113b4cbd649181","tgt_lang":"es","translated":"Historial de ejecuciones","updated_at":"2026-07-12T06:35:32.739Z"} {"cache_key":"54265c742b13510e3bac93963ad6ca4b1dd9cb78ca6b88cc58b1a39d0cb32024","model":"claude-opus-4-8","provider":"anthropic","segment_id":"nodes.inventory.rolesAndScopes","source_path":"ui/src/i18n/locales/es.ts","src_lang":"en","text":"roles: {roles} · scopes: {scopes}","text_hash":"035200095981fd169e4ff5855efc8233dafe1de451cccd054074b791f1f10e17","tgt_lang":"es","translated":"roles: {roles} · ámbitos: {scopes}","updated_at":"2026-07-12T06:31:31.117Z"} {"cache_key":"542f049cede42d6cded59b38679c20156241f6fbad27c3ec19f3e07e7a799f01","model":"gpt-5.6-sol","provider":"openai","segment_id":"newSession.nodeOffline","source_path":"ui/src/i18n/locales/es.ts","src_lang":"en","text":"Device is offline","text_hash":"3571104eb98b4de871b743f3f2993d1aea3f6bf9636730c858201c902464fa44","tgt_lang":"es","translated":"El dispositivo está sin conexión","updated_at":"2026-07-12T18:40:02.321Z"} @@ -693,6 +694,7 @@ {"cache_key":"5c970759f652f97f481d8c6dbb9e01d2752a2095dbab3d749f85e19e2a1ade89","model":"claude-opus-4-8","provider":"anthropic","segment_id":"configForm.sections.skills.description","source_path":"ui/src/i18n/locales/es.ts","src_lang":"en","text":"Skill packs and capabilities","text_hash":"16929f911a9b43cb09809e2088aa93241e7da0ffea4630ca251dc6825b7587b4","tgt_lang":"es","translated":"Paquetes de Skills y capacidades","updated_at":"2026-07-12T06:32:21.509Z"} {"cache_key":"5c9c0c4d6b02004b5062fee3e6f30ef48fb7a52f2c04c39a0338662a62e8acbb","model":"gpt-5.5","provider":"openai","segment_id":"nativeLinkMenu.copy","source_path":"ui/src/i18n/locales/es.ts","src_lang":"en","text":"Copy Link","text_hash":"724e78a3254c899d16ef6324a7abc9a8f5240ffce8bff74976df397c68ce9d78","tgt_lang":"es","translated":"Copiar enlace","updated_at":"2026-07-09T11:02:44.131Z"} {"cache_key":"5ca3eb3ab1cd3c888a4ca21ec8add873e170b8f2aac4082db3983b5b1f62af4d","model":"claude-opus-4-8","provider":"anthropic","segment_id":"skillWorkshop.actions.revise","source_path":"ui/src/i18n/locales/es.ts","src_lang":"en","text":"Revise","text_hash":"f1323f09f63c867d520b3b267f6e314e4eaf92fb314f916b1c50b67076dfcab7","tgt_lang":"es","translated":"Revisar","updated_at":"2026-07-12T06:34:21.942Z"} +{"cache_key":"5cb7042c36c5cb48e3bedfabd7727b789a7bf1f57e0f76b81d781b0e15f468c0","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connectionTimedOut","source_path":"ui/src/i18n/locales/es.ts","src_lang":"en","text":"Session did not connect within 30 seconds.","text_hash":"38c85973d7e3a6e0d95a423366c67d737a8857eeeb09954ed1e31bf47a0f4b9e","tgt_lang":"es","translated":"La sesión no se conectó en un plazo de 30 segundos.","updated_at":"2026-07-15T00:45:09.673Z"} {"cache_key":"5cb84d880d8ccc775012caa552980ad9a96e40a81cfdce70852082b7492a2dfc","model":"claude-opus-4-8","provider":"anthropic","segment_id":"agents.toolCatalog.descriptions.webSearch","source_path":"ui/src/i18n/locales/es.ts","src_lang":"en","text":"Search the web","text_hash":"0d3d9dd6d2ebd697f7068a644d1b72767a7b04309e333fa432fbadc536c46491","tgt_lang":"es","translated":"Buscar en la web","updated_at":"2026-07-12T06:32:02.628Z"} {"cache_key":"5ccaa2d7e9e92dc8710fa7c7c259d5f01c796218348ec0e90e48f30d53f40826","model":"claude-opus-4-8","provider":"anthropic","segment_id":"skillWorkshop.empty.appliedTitle","source_path":"ui/src/i18n/locales/es.ts","src_lang":"en","text":"Nothing applied yet","text_hash":"32697e30c8206968d4e025e8055ac5555a79c518aef4da12fabaa25280c2459a","tgt_lang":"es","translated":"Aún no se ha aplicado nada","updated_at":"2026-07-12T06:34:29.611Z"} {"cache_key":"5ce401cf073b34992749bdefbcecf375c9e9687124c9e90b296713ad9f96954a","model":"gpt-5.6-sol","provider":"openai","segment_id":"skillsPage.title","source_path":"ui/src/i18n/locales/es.ts","src_lang":"en","text":"Skills","text_hash":"66d0f523a379b2de6f8d5fba3a817ebc395f7bcaa54cc132ca9dfa665d1e9378","tgt_lang":"es","translated":"Skills","updated_at":"2026-07-12T00:08:26.918Z"} diff --git a/ui/src/i18n/.i18n/fa.meta.json b/ui/src/i18n/.i18n/fa.meta.json index 75568b3efdc5..85a33475c9c3 100644 --- a/ui/src/i18n/.i18n/fa.meta.json +++ b/ui/src/i18n/.i18n/fa.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-07-14T23:35:52.082Z", + "generatedAt": "2026-07-15T00:45:47.920Z", "locale": "fa", - "model": "claude-opus-4-8", - "provider": "anthropic", - "sourceHash": "85e004373478d946c5002519a35b2c50e1aa220fd2a81f7c1c80ded99bc7dc4e", - "totalKeys": 3353, - "translatedKeys": 3353, + "model": "gpt-5.6-sol", + "provider": "openai", + "sourceHash": "dc6d0f7bdb69960a5b9d54c5743445b89b0689913b3f962829f8490dede56d2c", + "totalKeys": 3355, + "translatedKeys": 3355, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/fa.tm.jsonl b/ui/src/i18n/.i18n/fa.tm.jsonl index 1adb20c2cc70..f4556872159c 100644 --- a/ui/src/i18n/.i18n/fa.tm.jsonl +++ b/ui/src/i18n/.i18n/fa.tm.jsonl @@ -149,6 +149,7 @@ {"cache_key":"149119bd2cf79c38b902f80b62de12cb57eaa09688329a070438fbdc44eb961c","model":"claude-opus-4-8","provider":"anthropic","segment_id":"agents.overview.title","source_path":"ui/src/i18n/locales/fa.ts","src_lang":"en","text":"Overview","text_hash":"d4b1ea5708dd532930a85188b45aff6f0a3ed458500c7577e0127a538eb0d100","tgt_lang":"fa","translated":"نمای کلی","updated_at":"2026-07-12T06:53:17.312Z"} {"cache_key":"14a622b02a51860b3c883e0b565061b7ca5dc023cd354925937564743f6a3967","model":"claude-opus-4-8","provider":"anthropic","segment_id":"channels.nostr.title","source_path":"ui/src/i18n/locales/fa.ts","src_lang":"en","text":"Nostr","text_hash":"cd27eb85bbbcde66be45dd832281190c0002b58be5c867e4f095d59b110c0c04","tgt_lang":"fa","translated":"Nostr","updated_at":"2026-07-12T06:52:24.346Z"} {"cache_key":"14d08fb31b7428e1328002e00999ae27383f642933f78110b988accf33809924","model":"claude-opus-4-8","provider":"anthropic","segment_id":"configView.notifications.unavailable","source_path":"ui/src/i18n/locales/fa.ts","src_lang":"en","text":"Unavailable","text_hash":"ca184496974204a0bd3fd4925e0540bc4b0df6fcc232994d29754272b7183812","tgt_lang":"fa","translated":"در دسترس نیست","updated_at":"2026-07-12T06:55:07.729Z"} +{"cache_key":"1515c06d19273982ffa12c73c3598bbf4444d0eab106d355e633c0655b578a3a","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connectionTimedOut","source_path":"ui/src/i18n/locales/fa.ts","src_lang":"en","text":"Session did not connect within 30 seconds.","text_hash":"38c85973d7e3a6e0d95a423366c67d737a8857eeeb09954ed1e31bf47a0f4b9e","tgt_lang":"fa","translated":"اتصال به نشست ظرف ۳۰ ثانیه برقرار نشد.","updated_at":"2026-07-15T00:45:47.920Z"} {"cache_key":"153016aa6f387314e4cc4662a949c92c2952a816171461282bb406ddf04dd0ca","model":"claude-opus-4-8","provider":"anthropic","segment_id":"pluginsPage.connectorDescriptions.travel","source_path":"ui/src/i18n/locales/fa.ts","src_lang":"en","text":"Flight and hotel search with fare watching and trip memory.","text_hash":"674641dedb84777fbda9257f030592dd29e4a7403b9a342eb28a9fe581f37286","tgt_lang":"fa","translated":"جستجوی پرواز و هتل با رصد کرایه و حافظه سفر.","updated_at":"2026-07-12T06:57:30.982Z"} {"cache_key":"1559d2502c8a68602b514e23d6e4e320cf9f7f31721643a13b5ee33c3c5cb84b","model":"gpt-5.6-sol","provider":"openai","segment_id":"agentChip.noAgentMatches","source_path":"ui/src/i18n/locales/fa.ts","src_lang":"en","text":"No matching agents","text_hash":"38b006752ec7a0a6e18631050994899431708df2707bbc0792315deaf8e3a933","tgt_lang":"fa","translated":"هیچ عامل منطبقی یافت نشد","updated_at":"2026-07-13T05:31:20.131Z"} {"cache_key":"1572ba82025e96e3bf0387d0ac68167d85bb9b109d6434340c79d736e5f4629f","model":"claude-sonnet-4-6","provider":"anthropic","segment_id":"cron.suggestions.ideas.nightWatch.prompt","source_path":"ui/src/i18n/locales/fa.ts","src_lang":"en","text":"Check that my services and gateway are healthy: scan recent logs for new errors, restarts, or unusual load. Stay quiet if everything is fine; if something looks broken, message me what failed and where to start looking.","text_hash":"ff691fc0bcd98cc27aea75758d323fc926a74252f5007eb43b49073d1a5716b7","tgt_lang":"fa","translated":"بررسی کن که سرویس‌ها و Gateway من سالم باشند: لاگ‌های اخیر را برای خطاهای جدید، راه‌اندازی مجدد یا بار غیرعادی اسکن کن. اگر همه چیز خوب است، ساکت بمان؛ اگر چیزی خراب به نظر می‌رسد، به من پیام بده چه چیزی خراب شده و از کجا شروع به بررسی کنم.","updated_at":"2026-07-11T22:49:36.745Z"} @@ -1765,6 +1766,7 @@ {"cache_key":"faac3e3875308802f7caedd0e2725842459b138d922b3deea34b67b881656bd4","model":"claude-opus-4-8","provider":"anthropic","segment_id":"nodes.execApprovals.override","source_path":"ui/src/i18n/locales/fa.ts","src_lang":"en","text":"Override ({value}).","text_hash":"b561858a9c97ebe63ee4ea9608da6af30ed0710bd5f18e12f1d35025ebe08f79","tgt_lang":"fa","translated":"بازنویسی ({value}).","updated_at":"2026-07-12T06:53:09.066Z"} {"cache_key":"fad84c869f4c72c6781d4c7b345c5e63a8782ad3fe5ffd6a43ccb67e98d122da","model":"claude-opus-4-8","provider":"anthropic","segment_id":"quickSettings.security.deviceAuth","source_path":"ui/src/i18n/locales/fa.ts","src_lang":"en","text":"Device auth","text_hash":"6fc25a19873431c090a39a9ea327265e37e2201237e8f1d06045426d4341b92a","tgt_lang":"fa","translated":"احراز هویت دستگاه","updated_at":"2026-07-12T06:54:30.738Z"} {"cache_key":"fae540f92ca9e65f917d524e2bf3640acbb9845c0013211e81664628a9da36d3","model":"gpt-5.5","provider":"openai","segment_id":"pluginsPage.mcp","source_path":"ui/src/i18n/locales/fa.ts","src_lang":"en","text":"MCP","text_hash":"53f13ae99ed53bd346eb8e1c8cefb7ef8260683b50401caf101360967ea052aa","tgt_lang":"fa","translated":"MCP","updated_at":"2026-05-31T05:36:59.400Z"} +{"cache_key":"fb368b042dd2dd4cc6c9e5683591a3be141cedc9f5971e8efa763cb7e2c8dc6b","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connecting","source_path":"ui/src/i18n/locales/fa.ts","src_lang":"en","text":"Connecting to session…","text_hash":"f1f49121a17a09b93f1601768c015f6fa9ef2056365c5a745837a2be98f71874","tgt_lang":"fa","translated":"در حال اتصال به نشست…","updated_at":"2026-07-15T00:45:47.920Z"} {"cache_key":"fb67c83b1bb46517f66b8c0f3dd7884ec600b88c3ebdce640031324320d16c02","model":"claude-opus-4-8","provider":"anthropic","segment_id":"skillsPage.noClawHubResults","source_path":"ui/src/i18n/locales/fa.ts","src_lang":"en","text":"No skills found on ClawHub.","text_hash":"a3b2387163dc439615c00ab811a92f533225c5c02a529b68074ef56fac75821f","tgt_lang":"fa","translated":"هیچ Skill در ClawHub یافت نشد.","updated_at":"2026-07-12T06:56:35.267Z"} {"cache_key":"fb736bf01b588cfee6d256f787421041bfeb552fcf2f93863b0ce27cd9e40a17","model":"claude-opus-4-8","provider":"anthropic","segment_id":"agents.toolCatalog.descriptions.write","source_path":"ui/src/i18n/locales/fa.ts","src_lang":"en","text":"Create or overwrite files","text_hash":"b29a2552b08fc6afff15e7d9e6276beeaa33e59f3fa298a999c6e8115b487d7a","tgt_lang":"fa","translated":"ایجاد یا بازنویسی فایل‌ها","updated_at":"2026-07-12T06:53:26.782Z"} {"cache_key":"fb7d1fa78a10d7f85914aee2870bb81ca931e2a87ca266184c8b380391dadfc2","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.unpinSession","source_path":"ui/src/i18n/locales/fa.ts","src_lang":"en","text":"Unpin session","text_hash":"f4c582ee4d7a87bf069d05a49bb0211759d3db8366daab4ba7024425961af5dc","tgt_lang":"fa","translated":"برداشتن سنجاق نشست","updated_at":"2026-07-02T14:31:11.668Z"} diff --git a/ui/src/i18n/.i18n/fr.meta.json b/ui/src/i18n/.i18n/fr.meta.json index 8ae967c7a9d7..c7037ac897ce 100644 --- a/ui/src/i18n/.i18n/fr.meta.json +++ b/ui/src/i18n/.i18n/fr.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-07-14T23:35:50.895Z", + "generatedAt": "2026-07-15T00:45:16.828Z", "locale": "fr", - "model": "claude-opus-4-8", - "provider": "anthropic", - "sourceHash": "85e004373478d946c5002519a35b2c50e1aa220fd2a81f7c1c80ded99bc7dc4e", - "totalKeys": 3353, - "translatedKeys": 3353, + "model": "gpt-5.6-sol", + "provider": "openai", + "sourceHash": "dc6d0f7bdb69960a5b9d54c5743445b89b0689913b3f962829f8490dede56d2c", + "totalKeys": 3355, + "translatedKeys": 3355, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/fr.tm.jsonl b/ui/src/i18n/.i18n/fr.tm.jsonl index 6d5e47c5239b..b3c877787c9d 100644 --- a/ui/src/i18n/.i18n/fr.tm.jsonl +++ b/ui/src/i18n/.i18n/fr.tm.jsonl @@ -967,6 +967,7 @@ {"cache_key":"83a3f830b91efce5c1261f2de88ea64eaa055e3d61452ba8fcd923226823c3ae","model":"claude-opus-4-8","provider":"anthropic","segment_id":"skillWorkshop.queue.search","source_path":"ui/src/i18n/locales/fr.ts","src_lang":"en","text":"Search proposals…","text_hash":"920a1bd7a15443b762e0d8f9f5aac701346342c3cd5ce3583b42a0e63e43ec0c","tgt_lang":"fr","translated":"Rechercher des propositions…","updated_at":"2026-07-12T06:35:13.975Z"} {"cache_key":"83a3fb48f64340235cc516ca8d88ca305f08dc0cdccea062af22e5badd71090b","model":"gpt-5.5","provider":"openai","segment_id":"browser.start","source_path":"ui/src/i18n/locales/fr.ts","src_lang":"en","text":"Start browser","text_hash":"4af2fc76dfa1bfc8b4b30cefd65fb5ca171d792bedc4cab90f082df7c9b961ff","tgt_lang":"fr","translated":"Démarrer le navigateur","updated_at":"2026-07-11T02:18:20.344Z"} {"cache_key":"83b8bbdf993208bb03a0f25732561aaa378bdecf9b585b73a474cb93cf9f57b5","model":"claude-opus-4-8","provider":"anthropic","segment_id":"skillGroups.extra","source_path":"ui/src/i18n/locales/fr.ts","src_lang":"en","text":"Extra Skills","text_hash":"1999b1716503a67377522c9416f5d2b2a424668ec761c5007655f0c8d284d686","tgt_lang":"fr","translated":"Skills supplémentaires","updated_at":"2026-07-12T06:34:31.353Z"} +{"cache_key":"83e1fcffcfdf9a74a9d72caf82b1c7a3e05f3c78bea3172c2e9cb8c000720a1f","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connecting","source_path":"ui/src/i18n/locales/fr.ts","src_lang":"en","text":"Connecting to session…","text_hash":"f1f49121a17a09b93f1601768c015f6fa9ef2056365c5a745837a2be98f71874","tgt_lang":"fr","translated":"Connexion à la session…","updated_at":"2026-07-15T00:45:16.828Z"} {"cache_key":"83fa83338c609d9ce0d5a0fd044202ca93c5debd047857b6e76b4c543a80d072","model":"claude-opus-4-8","provider":"anthropic","segment_id":"chat.queue.removeQueuedMessage","source_path":"ui/src/i18n/locales/fr.ts","src_lang":"en","text":"Remove queued message","text_hash":"1c99e5283577df5340915a16859019f651a3d20dd9928c78d0e5ec14464d9b74","tgt_lang":"fr","translated":"Supprimer le message en file d'attente","updated_at":"2026-07-12T06:36:02.496Z"} {"cache_key":"8426336867446f1b7bb80a177852d24ab0e269be467bfa71e633bb7b29145292","model":"claude-opus-4-8","provider":"anthropic","segment_id":"skillWorkshop.empty.rejectedTitle","source_path":"ui/src/i18n/locales/fr.ts","src_lang":"en","text":"No rejected proposals","text_hash":"c94fa71bf6645cbf1d245fd8849cb25281262df6c7e7d186cefabd7c846c9380","tgt_lang":"fr","translated":"Aucune proposition rejetée","updated_at":"2026-07-12T06:35:23.166Z"} {"cache_key":"8437bbde31f98a8212d5402ac1a1677eb444423a6a622da8c17624d0af769b0c","model":"claude-opus-4-8","provider":"anthropic","segment_id":"chat.actions.exitFocusMode","source_path":"ui/src/i18n/locales/fr.ts","src_lang":"en","text":"Exit focus mode","text_hash":"cf1f3e5858222364ee1dbaad49d0a246996a578e39d0a46aff1283dd3fd1d9b7","tgt_lang":"fr","translated":"Quitter le mode focus","updated_at":"2026-07-12T06:35:57.306Z"} @@ -1006,6 +1007,7 @@ {"cache_key":"8a07c4150358cf2d4e04f54e9783795cba937ac092e114ae6ad87f30369dba62","model":"claude-opus-4-8","provider":"anthropic","segment_id":"skillWorkshop.relative.secondsAgo","source_path":"ui/src/i18n/locales/fr.ts","src_lang":"en","text":"{count}s ago","text_hash":"244073ecb2be8fe875a37bcf7023ff32fb21f7c64e5d29e0ae62931a84c98a6a","tgt_lang":"fr","translated":"il y a {count} s","updated_at":"2026-07-12T06:35:36.282Z"} {"cache_key":"8a3bf6a7710c3d99cc75528d25a79ae7405c8a17cba773c5fbb52b22193820fd","model":"claude-opus-4-8","provider":"anthropic","segment_id":"chat.composer.talkVoice","source_path":"ui/src/i18n/locales/fr.ts","src_lang":"en","text":"Voice","text_hash":"87bf2bc08589f0bd4a078db145c34ad5e14b8fda53c3ae65b78601294913df95","tgt_lang":"fr","translated":"Voix","updated_at":"2026-07-06T20:20:02.809Z"} {"cache_key":"8a407cc06ec5c609e1eee29c0d973582c258dcb68fe007a4f57ed5e71351be57","model":"gpt-5.5","provider":"openai","segment_id":"chat.composer.contextUsage.contextWindow","source_path":"ui/src/i18n/locales/fr.ts","src_lang":"en","text":"Context window","text_hash":"7696d0855331622dc12438057f5509348f9d6f0ec2eb3580e18a99d31eba86db","tgt_lang":"fr","translated":"Fenêtre de contexte","updated_at":"2026-07-05T10:16:09.189Z"} +{"cache_key":"8a5482f2664f7787b17ac8d556fc2c49cf3f95249139470b26dd5f40e2bfbfec","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connectionTimedOut","source_path":"ui/src/i18n/locales/fr.ts","src_lang":"en","text":"Session did not connect within 30 seconds.","text_hash":"38c85973d7e3a6e0d95a423366c67d737a8857eeeb09954ed1e31bf47a0f4b9e","tgt_lang":"fr","translated":"La connexion à la session n’a pas été établie dans les 30 secondes.","updated_at":"2026-07-15T00:45:16.828Z"} {"cache_key":"8a9af3a254d52b7eee14f5f63650af64abe3323807045edf76145af7c9965ee3","model":"gpt-5.6-sol","provider":"openai","segment_id":"modelProviders.defaults.saved","source_path":"ui/src/i18n/locales/fr.ts","src_lang":"en","text":"Default models saved.","text_hash":"bcfc1802a87c6f284158e3d9b881d5b50ef50c24a7cb3b1a8878766784caf907","tgt_lang":"fr","translated":"Modèles par défaut enregistrés.","updated_at":"2026-07-13T16:31:59.089Z"} {"cache_key":"8ad358d7f59dc9144ea68e02b5ad6c733b04e1bf67e6a3ce4a1fe46fd36f877b","model":"claude-opus-4-8","provider":"anthropic","segment_id":"channels.nostr.placeholders.website","source_path":"ui/src/i18n/locales/fr.ts","src_lang":"en","text":"https://example.com","text_hash":"100680ad546ce6a577f42f52df33b4cfdca756859e664b8d7de329b150d09ce9","tgt_lang":"fr","translated":"https://example.com","updated_at":"2026-07-12T06:32:00.816Z"} {"cache_key":"8ae836ba79430ff9534233893652805eee3f30b2dba903571d2bde4ba4996c4d","model":"claude-opus-4-8","provider":"anthropic","segment_id":"skillWorkshop.empty.staleBody","source_path":"ui/src/i18n/locales/fr.ts","src_lang":"en","text":"Proposals that can no longer apply cleanly will appear here.","text_hash":"cc140b15a8c376fc139829453cab08c9280f277f0796e9673ea723621772d877","tgt_lang":"fr","translated":"Les propositions qui ne peuvent plus s'appliquer proprement apparaîtront ici.","updated_at":"2026-07-12T06:35:23.166Z"} diff --git a/ui/src/i18n/.i18n/hi.meta.json b/ui/src/i18n/.i18n/hi.meta.json index 54610e151739..ff2c3f8544d2 100644 --- a/ui/src/i18n/.i18n/hi.meta.json +++ b/ui/src/i18n/.i18n/hi.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-07-14T23:35:51.001Z", + "generatedAt": "2026-07-15T00:45:21.158Z", "locale": "hi", - "model": "claude-opus-4-8", - "provider": "anthropic", - "sourceHash": "85e004373478d946c5002519a35b2c50e1aa220fd2a81f7c1c80ded99bc7dc4e", - "totalKeys": 3353, - "translatedKeys": 3353, + "model": "gpt-5.6-sol", + "provider": "openai", + "sourceHash": "dc6d0f7bdb69960a5b9d54c5743445b89b0689913b3f962829f8490dede56d2c", + "totalKeys": 3355, + "translatedKeys": 3355, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/hi.tm.jsonl b/ui/src/i18n/.i18n/hi.tm.jsonl index 145d9ec9cb59..f4ebbbb50e23 100644 --- a/ui/src/i18n/.i18n/hi.tm.jsonl +++ b/ui/src/i18n/.i18n/hi.tm.jsonl @@ -202,6 +202,7 @@ {"cache_key":"0fba2d5dead215b9bf8cf4dbde67a0a18686ec09a3e598efe086bd06b627f0e4","model":"gpt-5.5","provider":"openai","segment_id":"profilePage.insightModel","source_path":"ui/src/i18n/locales/hi.ts","src_lang":"en","text":"Most used model","text_hash":"a13c86cd4f835d2c2b5af940171ede2a27e0f805cef070d87628f1bd333bb706","tgt_lang":"hi","translated":"सबसे अधिक उपयोग किया गया model","updated_at":"2026-07-09T11:27:28.426Z"} {"cache_key":"0fc99e5b34dab73253294ca22f274b1d4dd60a2300c24c261c0b5168a290f306","model":"claude-opus-4-8","provider":"anthropic","segment_id":"configForm.sections.mcp.description","source_path":"ui/src/i18n/locales/hi.ts","src_lang":"en","text":"Model Context Protocol server definitions","text_hash":"bcafe6b826750f5565395b53cb77493f44b5cb0c9809d6b97e0df95407b91b9e","tgt_lang":"hi","translated":"Model Context Protocol सर्वर परिभाषाएँ","updated_at":"2026-07-12T06:39:07.768Z"} {"cache_key":"0fcb2e1d7604ccf474d8d53b5675d3de499d2420ed8928d79868762a4b5c3cc7","model":"gpt-5.6-sol","provider":"openai","segment_id":"chat.workRun.workedFor","source_path":"ui/src/i18n/locales/hi.ts","src_lang":"en","text":"Worked for {duration}","text_hash":"c8e2dac0ee966bbad30c620b4049b9cb92879e3e4f1967dc8161e3937a73cc2c","tgt_lang":"hi","translated":"{duration} तक काम किया","updated_at":"2026-07-12T17:49:35.699Z"} +{"cache_key":"0fe289379373c585c2cee6c26b166c607a8054782d15c47b6d2bcfd56e8b305a","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connecting","source_path":"ui/src/i18n/locales/hi.ts","src_lang":"en","text":"Connecting to session…","text_hash":"f1f49121a17a09b93f1601768c015f6fa9ef2056365c5a745837a2be98f71874","tgt_lang":"hi","translated":"सत्र से कनेक्ट किया जा रहा है…","updated_at":"2026-07-15T00:45:21.158Z"} {"cache_key":"0fe85a1bf4b86ecdc3a2d90e92bc495f67df949b23bf5e5c36aea457a49c7863","model":"gpt-5.5","provider":"openai","segment_id":"chat.refreshTitle","source_path":"ui/src/i18n/locales/hi.ts","src_lang":"en","text":"Refresh chat data","text_hash":"40b8edfd9a1326939cf9db6cca2b31d4af4e815606fe6d86f7982f4f9534e268","tgt_lang":"hi","translated":"चैट डेटा रीफ़्रेश करें","updated_at":"2026-06-26T21:36:16.800Z"} {"cache_key":"0ff9313f900028f3097a954f640efdb603a7a0b256f33332270ee6f08b6b6aed","model":"gpt-5.5","provider":"openai","segment_id":"execApproval.labels.cwd","source_path":"ui/src/i18n/locales/hi.ts","src_lang":"en","text":"CWD","text_hash":"0217f1cb7725737f15a6710df3bcfa3bc10a239f0f7801ec3d7168e675f5ebd6","tgt_lang":"hi","translated":"CWD","updated_at":"2026-06-26T21:31:18.653Z"} {"cache_key":"1001c2d65a3bd7607d746129578d6be34c919b352b3b7cea41fddeb0bb6c1b91","model":"gpt-5.5","provider":"openai","segment_id":"quickSettings.model.fastModes.auto","source_path":"ui/src/i18n/locales/hi.ts","src_lang":"en","text":"Auto","text_hash":"0286249762f7c94349cdc0ba3bb2255baf9a80036e2193ead1d77696f888582f","tgt_lang":"hi","translated":"स्वचालित","updated_at":"2026-07-06T20:20:02.809Z"} @@ -1274,6 +1275,7 @@ {"cache_key":"630bc2b30a8210742eb7e82f792b6c3aa28bccc85e9094ec3ee3e4b65d3d58f6","model":"gpt-5.5","provider":"openai","segment_id":"nodes.execApprovals.security","source_path":"ui/src/i18n/locales/hi.ts","src_lang":"en","text":"Security","text_hash":"8f6fb4eb7f42c0e245e29e63f5b82cc3ba19852681d1ed9aed291f59cf75ec0e","tgt_lang":"hi","translated":"सुरक्षा","updated_at":"2026-06-26T21:31:18.653Z"} {"cache_key":"631d2edc2138392209d7036ad0528e07df06030ae288b6eb66e5b4a358abb98e","model":"claude-opus-4-8","provider":"anthropic","segment_id":"quickSettings.automation.configure","source_path":"ui/src/i18n/locales/hi.ts","src_lang":"en","text":"Configure →","text_hash":"3853cdcbe571c3cc86f127a799dfbcc2540b79f092761008c94f70976f8d9e7c","tgt_lang":"hi","translated":"कॉन्फ़िगर करें →","updated_at":"2026-07-12T06:39:13.839Z"} {"cache_key":"631ff04d5745f46ef01d373b06a0edab2d601a25230f34e42f843a29f703c866","model":"gpt-5.5","provider":"openai","segment_id":"browser.annotate","source_path":"ui/src/i18n/locales/hi.ts","src_lang":"en","text":"Annotate page","text_hash":"a195c16075cc573ed53b608e832e1656b59bd30481eea941f6f264dc10294c65","tgt_lang":"hi","translated":"पेज पर टिप्पणी करें","updated_at":"2026-07-11T02:18:28.211Z"} +{"cache_key":"6337b7b6601061147ba01da53c548a982b46234216d95bd2f1bb557c0731a9e4","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connectionTimedOut","source_path":"ui/src/i18n/locales/hi.ts","src_lang":"en","text":"Session did not connect within 30 seconds.","text_hash":"38c85973d7e3a6e0d95a423366c67d737a8857eeeb09954ed1e31bf47a0f4b9e","tgt_lang":"hi","translated":"सत्र 30 सेकंड के भीतर कनेक्ट नहीं हुआ।","updated_at":"2026-07-15T00:45:21.158Z"} {"cache_key":"635e3b572a7f32c609358147cf17a9902f3e01071d29e733f8e4e4f90bcd66d2","model":"claude-opus-4-8","provider":"anthropic","segment_id":"pluginsPage.connectorDescriptions.todoist","source_path":"ui/src/i18n/locales/hi.ts","src_lang":"en","text":"Read, add, and complete tasks and projects in Todoist.","text_hash":"de752d3f3270b42889ff4f1fa5ded19d459cb653d13deafa4359e2c960b9976a","tgt_lang":"hi","translated":"Todoist में कार्य और प्रोजेक्ट पढ़ें, जोड़ें, और पूर्ण करें।","updated_at":"2026-07-12T06:41:11.385Z"} {"cache_key":"636076b1547d2584ec19524ec4686640df542a157916a5a07fa76f9435a70c6a","model":"claude-sonnet-4-6","provider":"anthropic","segment_id":"cron.suggestions.ideas.hackerNewsScout.prompt","source_path":"ui/src/i18n/locales/hi.ts","src_lang":"en","text":"Scan today's Hacker News front page for posts about AI agents, developer tooling, and TypeScript. Send me the three most interesting links, each with a one-line hot take.","text_hash":"11c42596963d19c50c7108c9f5ad1001f72a55028e816eeca0208f55d5713de8","tgt_lang":"hi","translated":"AI agents, developer tooling और TypeScript पर आज के Hacker News front page को स्कैन करें। मुझे तीन सबसे दिलचस्प लिंक भेजें, प्रत्येक के साथ एक पंक्ति का hot take।","updated_at":"2026-07-11T22:46:11.446Z"} {"cache_key":"6362d51a03302c48fa00184207b14f6e7d3edb987a3b79f64c13139e4d3d9718","model":"claude-opus-4-8","provider":"anthropic","segment_id":"chat.toolCards.openDetails","source_path":"ui/src/i18n/locales/hi.ts","src_lang":"en","text":"Open tool details in side panel","text_hash":"c6056b4228d222d66008f2ddf0d39980d4d451f1590b08a15999b629a32ff5db","tgt_lang":"hi","translated":"साइड पैनल में टूल विवरण खोलें","updated_at":"2026-07-12T06:43:10.661Z"} diff --git a/ui/src/i18n/.i18n/id.meta.json b/ui/src/i18n/.i18n/id.meta.json index 78fa1707426b..db619b83eae2 100644 --- a/ui/src/i18n/.i18n/id.meta.json +++ b/ui/src/i18n/.i18n/id.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-07-14T23:35:51.543Z", + "generatedAt": "2026-07-15T00:45:34.312Z", "locale": "id", - "model": "claude-opus-4-8", - "provider": "anthropic", - "sourceHash": "85e004373478d946c5002519a35b2c50e1aa220fd2a81f7c1c80ded99bc7dc4e", - "totalKeys": 3353, - "translatedKeys": 3353, + "model": "gpt-5.6-sol", + "provider": "openai", + "sourceHash": "dc6d0f7bdb69960a5b9d54c5743445b89b0689913b3f962829f8490dede56d2c", + "totalKeys": 3355, + "translatedKeys": 3355, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/id.tm.jsonl b/ui/src/i18n/.i18n/id.tm.jsonl index 9464f75c6d84..11bbe36d5961 100644 --- a/ui/src/i18n/.i18n/id.tm.jsonl +++ b/ui/src/i18n/.i18n/id.tm.jsonl @@ -831,6 +831,7 @@ {"cache_key":"77fd51cb53dd11dbbe1962e4a402a56557a00f442b0cbbd5f97995cb18d5ede1","model":"claude-opus-4-8","provider":"anthropic","segment_id":"channels.nostr.subtitle","source_path":"ui/src/i18n/locales/id.ts","src_lang":"en","text":"Decentralized DMs via Nostr relays (NIP-04).","text_hash":"f910815433f5df92e08b45c501380a6607a3bb86a66dd51bfc5ea32720f918d3","tgt_lang":"id","translated":"DM terdesentralisasi melalui relay Nostr (NIP-04).","updated_at":"2026-07-12T06:44:48.803Z"} {"cache_key":"780fd2544a24cd758296900de5e055bed5b5e0a2e9a2c72ecce6b6af00c63f6b","model":"gpt-5.5","provider":"openai","segment_id":"chat.pullRequests.checks","source_path":"ui/src/i18n/locales/id.ts","src_lang":"en","text":"CI","text_hash":"fe8ee15bb86d27a77f2a62bd71bc65936156c99fe5b58537b347cbad1761fd95","tgt_lang":"id","translated":"CI","updated_at":"2026-07-10T17:04:17.152Z"} {"cache_key":"781981308f67efff0c5ade16e91efa5f13004fcb8d532008e842d617b29f30f2","model":"gpt-5.5","provider":"openai","segment_id":"nodes.pairing.copySetupCode","source_path":"ui/src/i18n/locales/id.ts","src_lang":"en","text":"Copy setup code","text_hash":"277aeb7a54e49353875cc3dc3bb548f26aef0af0c328d6709455c0f813b3efa8","tgt_lang":"id","translated":"Salin kode penyiapan","updated_at":"2026-07-04T16:48:34.014Z"} +{"cache_key":"782f14ae3fbfeb942896e6469a76713458b5bba62f01a34845544a998f7e1292","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connectionTimedOut","source_path":"ui/src/i18n/locales/id.ts","src_lang":"en","text":"Session did not connect within 30 seconds.","text_hash":"38c85973d7e3a6e0d95a423366c67d737a8857eeeb09954ed1e31bf47a0f4b9e","tgt_lang":"id","translated":"Sesi tidak terhubung dalam waktu 30 detik.","updated_at":"2026-07-15T00:45:34.312Z"} {"cache_key":"7836ee7b362527aac3bce43f3c550eb423f2e2df68e019daad8a236950de582b","model":"claude-opus-4-8","provider":"anthropic","segment_id":"cron.detail.detailsSection","source_path":"ui/src/i18n/locales/id.ts","src_lang":"en","text":"Details","text_hash":"45989de49fb7f66dfe17a8fc26f3a02c7abcd74f7e8e6cf9f39fa0e3775780df","tgt_lang":"id","translated":"Detail","updated_at":"2026-07-12T06:49:32.707Z"} {"cache_key":"785c116ca15b349bb785e46909a5c8ffb51193836fdc53779404d11f1ff83256","model":"claude-opus-4-8","provider":"anthropic","segment_id":"nodes.execApprovals.hostNativeHint","source_path":"ui/src/i18n/locales/id.ts","src_lang":"en","text":"Read-only here. Edit from the companion app or CLI.","text_hash":"f40b6f5e3ead3a1a83b8c2cb0ffe80b8d105b35497ec464651dc8be98b7fe8cb","tgt_lang":"id","translated":"Hanya-baca di sini. Edit dari aplikasi pendamping atau CLI.","updated_at":"2026-07-12T06:45:09.776Z"} {"cache_key":"786790754bba3400c2aa1d4adb4d7c339878258807060a8c7ab9a4a7bc9e8b1a","model":"claude-opus-4-8","provider":"anthropic","segment_id":"configForm.sections.models.label","source_path":"ui/src/i18n/locales/id.ts","src_lang":"en","text":"Models","text_hash":"d17d2d78d76e6a6cd13048de225e107ac73de4aa5f8914e58a0cfaa9698c373e","tgt_lang":"id","translated":"Model","updated_at":"2026-07-12T06:46:00.053Z"} @@ -1715,6 +1716,7 @@ {"cache_key":"f47516abdcee50dd1d144f2981b9b9542d0e934a971c553dd5a9c5cd18c6498c","model":"claude-opus-4-8","provider":"anthropic","segment_id":"chat.detailPanel.overwrite","source_path":"ui/src/i18n/locales/id.ts","src_lang":"en","text":"Overwrite","text_hash":"b24963ea2cbc74b97321a347754137aa207b126695a95c860406098528966b55","tgt_lang":"id","translated":"Timpa","updated_at":"2026-07-12T06:49:15.040Z"} {"cache_key":"f4bab9579700a67ef35249bc12292bb928fb05af2d8b7cd7761560a721d57474","model":"claude-opus-4-8","provider":"anthropic","segment_id":"pluginsPage.connectorDescriptions.spotify","source_path":"ui/src/i18n/locales/id.ts","src_lang":"en","text":"Search, queue, and soundtrack your day with mood-based playlists.","text_hash":"ba29daebd2737cc8a802f208e9eb5c270570ec1e2a3fd936ed6bd0f73553bfd8","tgt_lang":"id","translated":"Cari, antrekan, dan iringi hari Anda dengan playlist berbasis suasana hati.","updated_at":"2026-07-12T06:48:01.260Z"} {"cache_key":"f4c91a2aa66246787035abe4cb42ecbe1d9b37f7a3b9ab21a6c9cc029918d2ad","model":"gpt-5.5","provider":"openai","segment_id":"browser.empty","source_path":"ui/src/i18n/locales/id.ts","src_lang":"en","text":"No open tabs. Enter a URL above to browse.","text_hash":"a9e25255aa326aac375865aac61e932f80d6030997ad0d8d2fe04b6d30772edd","tgt_lang":"id","translated":"Tidak ada tab terbuka. Masukkan URL di atas untuk menjelajah.","updated_at":"2026-07-11T02:19:28.776Z"} +{"cache_key":"f5012d5bcba7cb4337e9e1cc669178517280f17db02a0c252efc21f664574667","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connecting","source_path":"ui/src/i18n/locales/id.ts","src_lang":"en","text":"Connecting to session…","text_hash":"f1f49121a17a09b93f1601768c015f6fa9ef2056365c5a745837a2be98f71874","tgt_lang":"id","translated":"Menghubungkan ke sesi…","updated_at":"2026-07-15T00:45:34.312Z"} {"cache_key":"f50c8765057719fb1a3b5bedb5ad86b5a899cbe3d28902c8acadba821029bebc","model":"claude-opus-4-8","provider":"anthropic","segment_id":"quickSettings.appearance.themes.claw","source_path":"ui/src/i18n/locales/id.ts","src_lang":"en","text":"Claw","text_hash":"587cfbbbcd42a71ba7f33f8051d5c354707fa1ff227cab11452c3ba2147a8682","tgt_lang":"id","translated":"Claw","updated_at":"2026-07-12T06:46:17.106Z"} {"cache_key":"f50fa2715128b7c94a18e2fa5fc6a4c84e261e1fa3d0790dc89d56a4fce7bb23","model":"gpt-5.6-sol","provider":"openai","segment_id":"chat.progressLabels.clawing","source_path":"ui/src/i18n/locales/id.ts","src_lang":"en","text":"Clawing","text_hash":"f4ce53e24f86c82ea592e29402eb72daf6f56f1e10185b9c73229a1c10d853a0","tgt_lang":"id","translated":"Mencapit","updated_at":"2026-07-14T04:54:35.208Z"} {"cache_key":"f526899381a459c119e1b2c00dec965341de010401082d6c00a2a9c1faae7ad0","model":"claude-opus-4-8","provider":"anthropic","segment_id":"nodes.execApprovals.scope","source_path":"ui/src/i18n/locales/id.ts","src_lang":"en","text":"Scope","text_hash":"b073f6c68ef8721107fd9815b19b2c35ec111d526b75c2123d1111ba64424000","tgt_lang":"id","translated":"Cakupan","updated_at":"2026-07-12T06:45:17.163Z"} diff --git a/ui/src/i18n/.i18n/it.meta.json b/ui/src/i18n/.i18n/it.meta.json index d8601114c343..82e304f1d4ee 100644 --- a/ui/src/i18n/.i18n/it.meta.json +++ b/ui/src/i18n/.i18n/it.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-07-14T23:35:51.218Z", + "generatedAt": "2026-07-15T00:45:25.876Z", "locale": "it", - "model": "claude-opus-4-8", - "provider": "anthropic", - "sourceHash": "85e004373478d946c5002519a35b2c50e1aa220fd2a81f7c1c80ded99bc7dc4e", - "totalKeys": 3353, - "translatedKeys": 3353, + "model": "gpt-5.6-sol", + "provider": "openai", + "sourceHash": "dc6d0f7bdb69960a5b9d54c5743445b89b0689913b3f962829f8490dede56d2c", + "totalKeys": 3355, + "translatedKeys": 3355, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/it.tm.jsonl b/ui/src/i18n/.i18n/it.tm.jsonl index 30eb0d177737..8990d7bcfcc8 100644 --- a/ui/src/i18n/.i18n/it.tm.jsonl +++ b/ui/src/i18n/.i18n/it.tm.jsonl @@ -293,6 +293,7 @@ {"cache_key":"2921e861dc586f4f070f2fab2089c4681abae68d3480bdcd4574c42d17f04ea8","model":"gpt-5.5","provider":"openai","segment_id":"chat.workspaceFiles.showFiles","source_path":"ui/src/i18n/locales/it.ts","src_lang":"en","text":"Show session files","text_hash":"88e60963b00018033f164b496f29e784fddfdb400cd19baf3311e04645bab27a","tgt_lang":"it","translated":"Mostra i file della sessione","updated_at":"2026-07-10T06:08:23.266Z"} {"cache_key":"2926df7d5f4c4b3fd6029c5b7bac0df9a590aa60622d7ddb1e567943014c0471","model":"gpt-5.5","provider":"openai","segment_id":"codexSessions.transcript.loadMore","source_path":"ui/src/i18n/locales/it.ts","src_lang":"en","text":"Load older transcript items","text_hash":"2dd9c742f15de60d977597a17faf62db2cd79cd58caf87f349dac75899b3788e","tgt_lang":"it","translated":"Carica elementi precedenti della trascrizione","updated_at":"2026-07-11T10:25:06.585Z"} {"cache_key":"293cd5413295620b18d22090f49b4ef40d91519dd921086dcbe0004a6336cf19","model":"gpt-5.5","provider":"openai","segment_id":"newSession.hint","source_path":"ui/src/i18n/locales/it.ts","src_lang":"en","text":"Pick where this session works, then say what to do.","text_hash":"fcab2bf24a4a66dfbdf0589e5e1474d3b5cf952720f9f9f619d11f564283e698","tgt_lang":"it","translated":"Scegli dove lavora questa sessione, poi indica cosa fare.","updated_at":"2026-07-10T17:59:26.804Z"} +{"cache_key":"295c5f03400a6afb65294925bf1f81188941d3da9f29484dfdd002ae67549d09","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connectionTimedOut","source_path":"ui/src/i18n/locales/it.ts","src_lang":"en","text":"Session did not connect within 30 seconds.","text_hash":"38c85973d7e3a6e0d95a423366c67d737a8857eeeb09954ed1e31bf47a0f4b9e","tgt_lang":"it","translated":"La sessione non si è connessa entro 30 secondi.","updated_at":"2026-07-15T00:45:25.876Z"} {"cache_key":"2967e9aea81e98f4c54fc315111bac0e865f34b9beda20f34e8050540310e6e8","model":"claude-opus-4-8","provider":"anthropic","segment_id":"configForm.sections.update.description","source_path":"ui/src/i18n/locales/it.ts","src_lang":"en","text":"Auto-update settings and release channel","text_hash":"e61b824d77d8e34d4e3ede3d018533f02848cc6f2071b15a7682beed13a315e7","tgt_lang":"it","translated":"Impostazioni di aggiornamento automatico e canale di rilascio","updated_at":"2026-07-12T06:38:56.155Z"} {"cache_key":"299d1a2add22162789625cc7196d9dce21ca2363a0b64229047f1c6850a1127a","model":"claude-opus-4-8","provider":"anthropic","segment_id":"configView.viewPendingChangesRaw","source_path":"ui/src/i18n/locales/it.ts","src_lang":"en","text":"View pending changes","text_hash":"dfde31545cd2686bfc6834a69fdc8e56c5dbc9206557d0016cb861f02025b51c","tgt_lang":"it","translated":"Visualizza modifiche in sospeso","updated_at":"2026-07-12T06:40:05.679Z"} {"cache_key":"29c83567ecda1cd615ad78ef42b4f0bc863f798d2bf64e9e9e2a164eacc32749","model":"claude-opus-4-8","provider":"anthropic","segment_id":"channels.signal.title","source_path":"ui/src/i18n/locales/it.ts","src_lang":"en","text":"Signal","text_hash":"1e9806e4227ba3b9a986732f1b09a21fd6b96043d12e5a4334a326ec5ad39842","tgt_lang":"it","translated":"Signal","updated_at":"2026-07-12T06:37:45.624Z"} @@ -1783,6 +1784,7 @@ {"cache_key":"fd624f84075dc14e7c13a3ef798716202cd0422b553f427fab1d8cc7ad19f148","model":"gpt-5.5","provider":"openai","segment_id":"claudeSessions.sidebar.title","source_path":"ui/src/i18n/locales/it.ts","src_lang":"en","text":"Claude sessions","text_hash":"45d8dde95eda96ef87a6371eefdef22b667880c6e55ef402313b25f589bd983b","tgt_lang":"it","translated":"Sessioni Claude","updated_at":"2026-07-11T13:50:58.465Z"} {"cache_key":"fd90ff517fe38f8f425f638c15307c2e4e1a23a0915baa0f8e3396e74ef2fc41","model":"claude-opus-4-8","provider":"anthropic","segment_id":"workboard.viewRecentlyDone","source_path":"ui/src/i18n/locales/it.ts","src_lang":"en","text":"Recently done","text_hash":"46b889592a2f5e79197f62b5f96c76993545626bf207740ea58632ceed9623be","tgt_lang":"it","translated":"Completate di recente","updated_at":"2026-06-17T14:19:01.168Z"} {"cache_key":"fdac4608aa308a8bd3d55f66564ff26ffa29d123acab3516a2f9c91a52ce1555","model":"claude-opus-4-8","provider":"anthropic","segment_id":"cron.form.promptPlaceholder","source_path":"ui/src/i18n/locales/it.ts","src_lang":"en","text":"Describe what OpenClaw should do...","text_hash":"81afbe360be2d62d23ea39306a351c9b95b2d9a5a653c7469343454dc2ac706b","tgt_lang":"it","translated":"Descrivi cosa deve fare OpenClaw...","updated_at":"2026-07-12T06:42:36.545Z"} +{"cache_key":"fdb362ac04715d85992c17140b5c34de0b154d6331ae046e0207b5ec999d2d85","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connecting","source_path":"ui/src/i18n/locales/it.ts","src_lang":"en","text":"Connecting to session…","text_hash":"f1f49121a17a09b93f1601768c015f6fa9ef2056365c5a745837a2be98f71874","tgt_lang":"it","translated":"Connessione alla sessione…","updated_at":"2026-07-15T00:45:25.876Z"} {"cache_key":"fdedb51f0df618cd58829d6441986fe05e18a11317d336484f92021fbd7a4400","model":"claude-opus-4-8","provider":"anthropic","segment_id":"workboard.detailAutomationWorkspace","source_path":"ui/src/i18n/locales/it.ts","src_lang":"en","text":"Workspace: {workspace}","text_hash":"17f5e696e557a646a9003fc8448f6f6761f5fe6bdf7478f750f471496e87c17b","tgt_lang":"it","translated":"Workspace: {workspace}","updated_at":"2026-06-16T14:15:39.914Z"} {"cache_key":"fdfdd72696cb4003f558a5d5d7b81991cf5e62377c1ddb18c4305eadd437bce1","model":"claude-opus-4-8","provider":"anthropic","segment_id":"configView.notifications.unavailable","source_path":"ui/src/i18n/locales/it.ts","src_lang":"en","text":"Unavailable","text_hash":"ca184496974204a0bd3fd4925e0540bc4b0df6fcc232994d29754272b7183812","tgt_lang":"it","translated":"Non disponibile","updated_at":"2026-07-12T06:39:47.853Z"} {"cache_key":"fe2dd36bd9ffc987d984a2d7bfa18da5e5fff1c20c34d1d6446f9314b42acf36","model":"claude-opus-4-8","provider":"anthropic","segment_id":"workboard.detailAddNote","source_path":"ui/src/i18n/locales/it.ts","src_lang":"en","text":"Add note","text_hash":"63565c0485fec4f743719849734553a5d7947f5962ec9e831e3bce131b3c47fb","tgt_lang":"it","translated":"Aggiungi nota","updated_at":"2026-06-16T14:15:46.111Z"} diff --git a/ui/src/i18n/.i18n/ja-JP.meta.json b/ui/src/i18n/.i18n/ja-JP.meta.json index 0ca90e5d1f33..b5f63b190485 100644 --- a/ui/src/i18n/.i18n/ja-JP.meta.json +++ b/ui/src/i18n/.i18n/ja-JP.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-07-14T23:35:50.693Z", + "generatedAt": "2026-07-15T00:45:11.997Z", "locale": "ja-JP", - "model": "claude-opus-4-8", - "provider": "anthropic", - "sourceHash": "85e004373478d946c5002519a35b2c50e1aa220fd2a81f7c1c80ded99bc7dc4e", - "totalKeys": 3353, - "translatedKeys": 3353, + "model": "gpt-5.6-sol", + "provider": "openai", + "sourceHash": "dc6d0f7bdb69960a5b9d54c5743445b89b0689913b3f962829f8490dede56d2c", + "totalKeys": 3355, + "translatedKeys": 3355, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/ja-JP.tm.jsonl b/ui/src/i18n/.i18n/ja-JP.tm.jsonl index 7ccd93a83881..0bedaf2a00d0 100644 --- a/ui/src/i18n/.i18n/ja-JP.tm.jsonl +++ b/ui/src/i18n/.i18n/ja-JP.tm.jsonl @@ -1177,6 +1177,7 @@ {"cache_key":"a8d755d21f56af8a78796a70a5b4702c34255df2ff75699bbacc81d376dbe0c0","model":"claude-opus-4-8","provider":"anthropic","segment_id":"skillWorkshop.actions.reject","source_path":"ui/src/i18n/locales/ja-JP.ts","src_lang":"en","text":"Reject","text_hash":"ab604a360777735fe2993aec7d4ffae415f1515b0a4c989269603fd3d5c07b61","tgt_lang":"ja-JP","translated":"拒否","updated_at":"2026-07-12T06:34:56.740Z"} {"cache_key":"a8e2ae138d39cdce280859f816898618e76290b2bc9b57f926dde5c4c150f893","model":"gpt-5.5","provider":"openai","segment_id":"claudeSessions.transcript.toolResult","source_path":"ui/src/i18n/locales/ja-JP.ts","src_lang":"en","text":"Tool result","text_hash":"9bb620efa692f707a302a5f42464015a54c20843e2f76f18a1542626b886bb91","tgt_lang":"ja-JP","translated":"ツールの結果","updated_at":"2026-07-11T13:50:31.844Z"} {"cache_key":"a910809f88a20b6501c54dd454679bdcb761b3da05281e0cf733bfa258a7d0b0","model":"claude-opus-4-8","provider":"anthropic","segment_id":"nodes.execApprovals.on","source_path":"ui/src/i18n/locales/ja-JP.ts","src_lang":"en","text":"on","text_hash":"b8d31e852725afb1e26d53bab6095b2bff1749c9275be13ed1c05a56ed31ec09","tgt_lang":"ja-JP","translated":"オン","updated_at":"2026-07-12T06:31:51.627Z"} +{"cache_key":"a9890403d41a02984f8df18075a3c2fe4dce04b70d980279e838429f25622f81","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connecting","source_path":"ui/src/i18n/locales/ja-JP.ts","src_lang":"en","text":"Connecting to session…","text_hash":"f1f49121a17a09b93f1601768c015f6fa9ef2056365c5a745837a2be98f71874","tgt_lang":"ja-JP","translated":"セッションに接続しています…","updated_at":"2026-07-15T00:45:11.997Z"} {"cache_key":"a98d042f52a274515ae8b61ab23a7c03467d924f4e9d9e5399c7033b75ce6f38","model":"gpt-5.5","provider":"openai","segment_id":"browser.reload","source_path":"ui/src/i18n/locales/ja-JP.ts","src_lang":"en","text":"Reload","text_hash":"bdc090ec61e3fcfc65f469951dfe00f3f2ecfc6003c44deac8e05b7237092de6","tgt_lang":"ja-JP","translated":"再読み込み","updated_at":"2026-07-11T02:17:57.654Z"} {"cache_key":"a9a3fd54b08bbe0382dab2c8e5f07c346dbf83de9303341cae3c109bb24162b5","model":"claude-opus-4-8","provider":"anthropic","segment_id":"channels.nostr.subtitle","source_path":"ui/src/i18n/locales/ja-JP.ts","src_lang":"en","text":"Decentralized DMs via Nostr relays (NIP-04).","text_hash":"f910815433f5df92e08b45c501380a6607a3bb86a66dd51bfc5ea32720f918d3","tgt_lang":"ja-JP","translated":"Nostr リレー経由の分散型 DM(NIP-04)。","updated_at":"2026-07-12T06:31:25.246Z"} {"cache_key":"a9ab379f6f3edb6a98e5d1eb2046e2b733cb7be57d9d0ff54b8f8854e530efc0","model":"gpt-5.6-sol","provider":"openai","segment_id":"agents.identity.name","source_path":"ui/src/i18n/locales/ja-JP.ts","src_lang":"en","text":"Display name","text_hash":"2b7f6a84de917e387539dbe441ca22056e793d738e6c50db30c3f569e4448df3","tgt_lang":"ja-JP","translated":"表示名","updated_at":"2026-07-13T05:29:43.148Z"} @@ -1192,6 +1193,7 @@ {"cache_key":"ab549fa4fb01c4471d13b3fefd1d408d3039a20a2f666c49973bd4512f20f0f8","model":"claude-opus-4-8","provider":"anthropic","segment_id":"githubPreview.ariaLabel","source_path":"ui/src/i18n/locales/ja-JP.ts","src_lang":"en","text":"{state} {kind} {repo} #{number}: {title}, by {author}","text_hash":"e18c624550814e4318a45e1e0f8782913409763d046aa703519e91c887be205b","tgt_lang":"ja-JP","translated":"{state} {kind} {repo} #{number}: {title}、作成者 {author}","updated_at":"2026-07-12T06:31:19.391Z"} {"cache_key":"ab59ca701851244c4776ae00dea97ebb790841296d301b61c5bde750690eb171","model":"claude-opus-4-8","provider":"anthropic","segment_id":"configForm.sections.talk.label","source_path":"ui/src/i18n/locales/ja-JP.ts","src_lang":"en","text":"Talk","text_hash":"449f5a775762cdc561ee231300af1b02be67c56b81e503058a1c777ea1dc8b55","tgt_lang":"ja-JP","translated":"Talk","updated_at":"2026-07-12T06:32:47.571Z"} {"cache_key":"ab9ee088c32c48932a47f1df2584997d5042b2eec59c70b562de0f079b71fbf5","model":"claude-opus-4-8","provider":"anthropic","segment_id":"skillsPage.refreshing","source_path":"ui/src/i18n/locales/ja-JP.ts","src_lang":"en","text":"Refreshing…","text_hash":"1c0def7be0607b966b89e4974da38090472d8ada625f5b4c89f25b09d39683bd","tgt_lang":"ja-JP","translated":"更新中…","updated_at":"2026-07-12T06:34:27.939Z"} +{"cache_key":"abc210f438b105429b48544f027a713be4aa4b294521935c6b46626ce8420a94","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connectionTimedOut","source_path":"ui/src/i18n/locales/ja-JP.ts","src_lang":"en","text":"Session did not connect within 30 seconds.","text_hash":"38c85973d7e3a6e0d95a423366c67d737a8857eeeb09954ed1e31bf47a0f4b9e","tgt_lang":"ja-JP","translated":"30秒以内にセッションに接続できませんでした。","updated_at":"2026-07-15T00:45:11.997Z"} {"cache_key":"ac161ebbba0207c1eee4d54140004b06269f533b765e00816f872711ea6a4a78","model":"claude-opus-4-6","provider":"anthropic","segment_id":"configView.autoSaveSaved","source_path":"ui/src/i18n/locales/ja-JP.ts","src_lang":"en","text":"Saved","text_hash":"b5c120b316c237a0deab3140267aebedba61947d3ae268d708a53d5be119e9fb","tgt_lang":"ja-JP","translated":"保存済み","updated_at":"2026-07-14T12:52:40.223Z"} {"cache_key":"ac368e893d2db8f865eac84435d4ba801a31552756358a6a64b533261b0a1c44","model":"claude-opus-4-8","provider":"anthropic","segment_id":"skillWorkshop.today.upNext","source_path":"ui/src/i18n/locales/ja-JP.ts","src_lang":"en","text":"Up next · {count} more waiting","text_hash":"9f2a67f22d9ca908c4251c43b81269329e118fd9c8dfc42dea3bb006924b8ad2","tgt_lang":"ja-JP","translated":"次へ · あと {count} 件が待機中","updated_at":"2026-07-12T06:35:20.102Z"} {"cache_key":"ac3b2e583623039a3990a982b7bf0d5a7afc0c7e44521518d466e6e530cde28f","model":"claude-opus-4-8","provider":"anthropic","segment_id":"workboard.healthBlocked","source_path":"ui/src/i18n/locales/ja-JP.ts","src_lang":"en","text":"blocked","text_hash":"6973dddd3ef9cb6a2932702f31777faad9c9bf3124d147a84f31aadb6d139546","tgt_lang":"ja-JP","translated":"ブロック中","updated_at":"2026-06-17T14:14:09.255Z"} diff --git a/ui/src/i18n/.i18n/ko.meta.json b/ui/src/i18n/.i18n/ko.meta.json index 7ba795bab162..606ba12686a3 100644 --- a/ui/src/i18n/.i18n/ko.meta.json +++ b/ui/src/i18n/.i18n/ko.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-07-14T23:35:50.793Z", + "generatedAt": "2026-07-15T00:45:14.871Z", "locale": "ko", - "model": "claude-opus-4-8", - "provider": "anthropic", - "sourceHash": "85e004373478d946c5002519a35b2c50e1aa220fd2a81f7c1c80ded99bc7dc4e", - "totalKeys": 3353, - "translatedKeys": 3353, + "model": "gpt-5.6-sol", + "provider": "openai", + "sourceHash": "dc6d0f7bdb69960a5b9d54c5743445b89b0689913b3f962829f8490dede56d2c", + "totalKeys": 3355, + "translatedKeys": 3355, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/ko.tm.jsonl b/ui/src/i18n/.i18n/ko.tm.jsonl index 0c822ed599ff..666640d8ceef 100644 --- a/ui/src/i18n/.i18n/ko.tm.jsonl +++ b/ui/src/i18n/.i18n/ko.tm.jsonl @@ -1150,6 +1150,7 @@ {"cache_key":"a5f1ea8004c1818dd7c0ce0b970f49a275f0579437bc0470a1c52a5591a7f602","model":"claude-sonnet-4-6","provider":"anthropic","segment_id":"cron.suggestions.ideas.nightWatch.prompt","source_path":"ui/src/i18n/locales/ko.ts","src_lang":"en","text":"Check that my services and gateway are healthy: scan recent logs for new errors, restarts, or unusual load. Stay quiet if everything is fine; if something looks broken, message me what failed and where to start looking.","text_hash":"ff691fc0bcd98cc27aea75758d323fc926a74252f5007eb43b49073d1a5716b7","tgt_lang":"ko","translated":"내 서비스와 Gateway가 정상인지 확인해 주세요: 최근 로그에서 새로운 오류, 재시작, 또는 비정상적인 부하를 스캔하세요. 모든 것이 정상이면 조용히 있어도 됩니다; 문제가 보이면 무엇이 실패했고 어디서부터 확인해야 할지 알려주세요.","updated_at":"2026-07-11T22:45:43.800Z"} {"cache_key":"a60533aa79fa5e103f52b3712766e13309241f3e3eec56c0e52086783e112a4d","model":"claude-opus-4-8","provider":"anthropic","segment_id":"skillsPage.clawHub","source_path":"ui/src/i18n/locales/ko.ts","src_lang":"en","text":"ClawHub","text_hash":"a2019fe71279ebb59b7876298299699524a6eb7885a04484409a1b556c8548f3","tgt_lang":"ko","translated":"ClawHub","updated_at":"2026-07-12T06:34:39.346Z"} {"cache_key":"a63f8e6abe07b9dc720665586dcd28dab5183f43c90dbc074bd44c6bc5533519","model":"gpt-5","provider":"openai","segment_id":"approvalPage.nodeLabel","source_path":"ui/src/i18n/locales/ko.ts","src_lang":"en","text":"Node","text_hash":"e93372533f323b2f12783aa3a586135cf421486439c2cdcde47411b78f9839ec","tgt_lang":"ko","translated":"노드","updated_at":"2026-07-09T10:01:43.732Z"} +{"cache_key":"a65aaf5bcb5862a72eac0d3f7d04b5cde40c016c9c6227f13c9ff6acf1288892","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connectionTimedOut","source_path":"ui/src/i18n/locales/ko.ts","src_lang":"en","text":"Session did not connect within 30 seconds.","text_hash":"38c85973d7e3a6e0d95a423366c67d737a8857eeeb09954ed1e31bf47a0f4b9e","tgt_lang":"ko","translated":"30초 이내에 세션에 연결하지 못했습니다.","updated_at":"2026-07-15T00:45:14.871Z"} {"cache_key":"a67b1892e0c6b7bf13dcece3156d2b99cd74845998c94e22562d9a50e3521644","model":"claude-opus-4-8","provider":"anthropic","segment_id":"nodes.inventory.gateway","source_path":"ui/src/i18n/locales/ko.ts","src_lang":"en","text":"gateway","text_hash":"4ea5ee68fea05586106890ded5733820bb77d919cda27bc4b8139b7cd33b8889","tgt_lang":"ko","translated":"gateway","updated_at":"2026-07-12T06:31:59.316Z"} {"cache_key":"a67c411e720506873247a370e8df0d3807cabf66074e6ce3064d68282387f75a","model":"claude-opus-4-8","provider":"anthropic","segment_id":"common.previous","source_path":"ui/src/i18n/locales/ko.ts","src_lang":"en","text":"Previous","text_hash":"a57b08a480b822a0a572b993391c292ede593bf8000b406675b180bbb16260fa","tgt_lang":"ko","translated":"이전","updated_at":"2026-07-12T06:31:28.728Z"} {"cache_key":"a69af17552a6ee7f48b7b0e9b0e6ef8a989ee7760fc5e62e14c2bd6bcbacc64b","model":"claude-opus-4-8","provider":"anthropic","segment_id":"configView.notifications.hint","source_path":"ui/src/i18n/locales/ko.ts","src_lang":"en","text":"Receive browser push notifications from your gateway.","text_hash":"1a90345f698ef3383b5aaef3cce80cd844e49a2d6301876816c49439dc17661e","tgt_lang":"ko","translated":"Gateway에서 브라우저 푸시 알림을 받습니다.","updated_at":"2026-07-12T06:33:43.023Z"} @@ -1756,6 +1757,7 @@ {"cache_key":"f9da79be76e39c522c57e3669b99da2d00c9c51ad557689db58ea87d6152b238","model":"claude-opus-4-8","provider":"anthropic","segment_id":"cron.form.lightContextHelp","source_path":"ui/src/i18n/locales/ko.ts","src_lang":"en","text":"Use lightweight bootstrap context for this agent job.","text_hash":"6aebad7bcc7f52b2df758588930165ccdfce28f58fe77c2db43a2fb5e950da00","tgt_lang":"ko","translated":"이 에이전트 작업에 경량 부트스트랩 컨텍스트를 사용합니다.","updated_at":"2026-07-12T06:36:45.775Z"} {"cache_key":"f9db17659980eab33947cd7850f01bb822cb3a3c04b26d5307ee9e76ef22fdf4","model":"gpt-5.5","provider":"openai","segment_id":"quickSettings.appearance.lobsterVisitsOff","source_path":"ui/src/i18n/locales/ko.ts","src_lang":"en","text":"Never visits","text_hash":"4892012cb692f089af106c0533e80e3ce3331a7ff839bd2a1416a286cc85c6e8","tgt_lang":"ko","translated":"절대 방문하지 않습니다","updated_at":"2026-07-09T20:51:31.206Z"} {"cache_key":"fa66a6052376197305c4f49ae4cbd10229bbe2376fe80c30a16e0335a1823722","model":"claude-opus-4-8","provider":"anthropic","segment_id":"skillWorkshop.header.useCurrentChat","source_path":"ui/src/i18n/locales/ko.ts","src_lang":"en","text":"Use current chat","text_hash":"fbc1ffd63daa506e927c7a85f6e43acd11e0b8c9f52a3951fc782b236ce9a787","tgt_lang":"ko","translated":"현재 채팅 사용","updated_at":"2026-06-16T14:14:21.259Z"} +{"cache_key":"fa79f0431eb964c1740c6f63d83fe0c0636d71e13ac2ca61dce5740d6b8b5dc6","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connecting","source_path":"ui/src/i18n/locales/ko.ts","src_lang":"en","text":"Connecting to session…","text_hash":"f1f49121a17a09b93f1601768c015f6fa9ef2056365c5a745837a2be98f71874","tgt_lang":"ko","translated":"세션에 연결하는 중…","updated_at":"2026-07-15T00:45:14.871Z"} {"cache_key":"fa8badc022ea990163ca945913fd3c521455cbf703b2d1cc9f0412531f719364","model":"claude-opus-4-8","provider":"anthropic","segment_id":"skillStatus.eligible","source_path":"ui/src/i18n/locales/ko.ts","src_lang":"en","text":"eligible","text_hash":"fc99c152d84713fe276da3f905807ab166666451a8736224efc2fee710658d5f","tgt_lang":"ko","translated":"사용 가능","updated_at":"2026-07-12T06:34:44.618Z"} {"cache_key":"fabc3f3809de6e206f6065fa8e23817c8f2c6cb0203c9f9c857627b866d7c873","model":"claude-opus-4-8","provider":"anthropic","segment_id":"chat.messages.unknownDate","source_path":"ui/src/i18n/locales/ko.ts","src_lang":"en","text":"Unknown date","text_hash":"ad224bb89c8a3fb6dc0b567dd13c3c4f104deb2acf64aa3679d85301f007a6ee","tgt_lang":"ko","translated":"알 수 없는 날짜","updated_at":"2026-07-12T06:36:13.954Z"} {"cache_key":"fabfa09f8370e02212d114ad149c6b6a57221b3752e6b220799886492d0142f4","model":"claude-opus-4-8","provider":"anthropic","segment_id":"chat.workspaceFiles.session","source_path":"ui/src/i18n/locales/ko.ts","src_lang":"en","text":"Session","text_hash":"6959b4159575d8dd76d9f3bbe2c6437904f861e7860c35abd18deffb1c3425a0","tgt_lang":"ko","translated":"세션","updated_at":"2026-06-16T14:14:34.760Z"} diff --git a/ui/src/i18n/.i18n/nl.meta.json b/ui/src/i18n/.i18n/nl.meta.json index c7097c42fc33..2afd99799ba0 100644 --- a/ui/src/i18n/.i18n/nl.meta.json +++ b/ui/src/i18n/.i18n/nl.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-07-14T23:35:51.963Z", + "generatedAt": "2026-07-15T00:45:44.990Z", "locale": "nl", - "model": "claude-opus-4-8", - "provider": "anthropic", - "sourceHash": "85e004373478d946c5002519a35b2c50e1aa220fd2a81f7c1c80ded99bc7dc4e", - "totalKeys": 3353, - "translatedKeys": 3353, + "model": "gpt-5.6-sol", + "provider": "openai", + "sourceHash": "dc6d0f7bdb69960a5b9d54c5743445b89b0689913b3f962829f8490dede56d2c", + "totalKeys": 3355, + "translatedKeys": 3355, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/nl.tm.jsonl b/ui/src/i18n/.i18n/nl.tm.jsonl index 524ca22ef952..11b5c7a7cab2 100644 --- a/ui/src/i18n/.i18n/nl.tm.jsonl +++ b/ui/src/i18n/.i18n/nl.tm.jsonl @@ -60,6 +60,7 @@ {"cache_key":"072f393e1442e87be86ef8b4a54ebe37b6a104f54208c7826b862a7c233d5355","model":"claude-opus-4-8","provider":"anthropic","segment_id":"nodes.execApprovals.rules","source_path":"ui/src/i18n/locales/nl.ts","src_lang":"en","text":"{count} rules","text_hash":"e838d0ef12825f802a10f914fbebc46645de9dacffffe4522eb035cd806039de","tgt_lang":"nl","translated":"{count} regels","updated_at":"2026-07-12T06:52:20.624Z"} {"cache_key":"074147c1fe6ae8a5c4ee3f0c15f9aac7a5c10a1b7212d529233fd2d7fd169e22","model":"claude-opus-4-8","provider":"anthropic","segment_id":"workboard.healthRunning","source_path":"ui/src/i18n/locales/nl.ts","src_lang":"en","text":"running","text_hash":"c071cf5f5ed6f884cc70155b6f05f755fd46a302d05e4261b7e92ce878bbfed8","tgt_lang":"nl","translated":"actief","updated_at":"2026-06-17T14:17:36.035Z"} {"cache_key":"078f461da84360a2d859b640ae0c8881bf9fbbfaceb42c726644b27a52536199","model":"gpt-5.5","provider":"openai","segment_id":"browser.refresh","source_path":"ui/src/i18n/locales/nl.ts","src_lang":"en","text":"Refresh view","text_hash":"112b4ddb82dac7080c1c85de7475cd9754b2ac7e0a89fbb38126923180d76ec2","tgt_lang":"nl","translated":"Weergave vernieuwen","updated_at":"2026-07-11T02:20:09.408Z"} +{"cache_key":"079af6a85cba877e5745498fed553207f6a56958972c563f0449f2eae76ed21f","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connectionTimedOut","source_path":"ui/src/i18n/locales/nl.ts","src_lang":"en","text":"Session did not connect within 30 seconds.","text_hash":"38c85973d7e3a6e0d95a423366c67d737a8857eeeb09954ed1e31bf47a0f4b9e","tgt_lang":"nl","translated":"Er kon niet binnen 30 seconden verbinding worden gemaakt met de sessie.","updated_at":"2026-07-15T00:45:44.990Z"} {"cache_key":"07ab8d07f27ba9f66160aa7a335f9dbe70a346950643509842c8ea56ee4788c8","model":"claude-opus-4-8","provider":"anthropic","segment_id":"configView.notifications.unavailableHint","source_path":"ui/src/i18n/locales/nl.ts","src_lang":"en","text":"Not available in this browser.","text_hash":"4dad76698cde08136f50bd8b789e635c4cff19e25b6a4ec62290684958b23add","tgt_lang":"nl","translated":"Niet beschikbaar in deze browser.","updated_at":"2026-07-12T06:54:02.978Z"} {"cache_key":"07cc3fad504faf0eb379a2458ec915d8aa5fe61dce0d006be7fe4ce9a1f4706c","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.dragSessionHint","source_path":"ui/src/i18n/locales/nl.ts","src_lang":"en","text":"Drag to move between groups","text_hash":"3e8692b1cd1c1f9a606a779f1037aa390c885fde224ddb841df9259e8aab03ae","tgt_lang":"nl","translated":"Sleep om tussen groepen te verplaatsen","updated_at":"2026-07-05T14:40:20.847Z"} {"cache_key":"07d7e135848387ad8996ffab20b817e8ee33a12368f9ead23770559b6935ee12","model":"claude-opus-4-8","provider":"anthropic","segment_id":"chat.workspaceFiles.read","source_path":"ui/src/i18n/locales/nl.ts","src_lang":"en","text":"Read","text_hash":"9b9a8d05a7ec353bda84f9c1bb3178c299de3001b5e970508ddc889c487f92ca","tgt_lang":"nl","translated":"Gelezen","updated_at":"2026-06-16T14:18:24.009Z"} @@ -1293,6 +1294,7 @@ {"cache_key":"aab054102b0c4897f2379ca669b36ce88883888a2d0aee8e5dad82ad977ae2e3","model":"claude-sonnet-4-6","provider":"anthropic","segment_id":"cron.suggestions.ideas.polyglotMinute.prompt","source_path":"ui/src/i18n/locales/nl.ts","src_lang":"en","text":"Teach me one useful phrase in Japanese: the phrase, how to pronounce it, its literal meaning, and when to use it. Keep it under five lines.","text_hash":"acaeae7dfcaab66a6b06b682970496a2cec2efcde9d564204288f71819578186","tgt_lang":"nl","translated":"Leer me één nuttige zin in het Japans: de zin, hoe je hem uitspreekt, de letterlijke betekenis en wanneer je hem gebruikt. Houd het onder de vijf regels.","updated_at":"2026-07-11T22:49:06.392Z"} {"cache_key":"aacc8671a3d63c2642d47534bf926bf388e3d32d8b0e46142da8a1142ca5a692","model":"claude-opus-4-8","provider":"anthropic","segment_id":"chat.compaction.label","source_path":"ui/src/i18n/locales/nl.ts","src_lang":"en","text":"Compacted history","text_hash":"1c066091aa0c37ad253bfe195469b0cf82b276a06dea4ccc55e246e175b2e68d","tgt_lang":"nl","translated":"Gecomprimeerde geschiedenis","updated_at":"2026-07-12T06:56:19.372Z"} {"cache_key":"aafacbc4613c7e1c128d420d1f56df1e2fe159561076eee60c0435811b94a129","model":"gpt-5.5","provider":"openai","segment_id":"pluginsPage.mcpServersGroup","source_path":"ui/src/i18n/locales/nl.ts","src_lang":"en","text":"MCP servers","text_hash":"22a7559f09bf8f82c510280f934bf50db4c45cc611fd4dd47d7cbf7c7d4f5b82","tgt_lang":"nl","translated":"MCP-servers","updated_at":"2026-07-10T02:28:58.148Z"} +{"cache_key":"ab170b454009debc335b1925fb8b7a9b572de21048fadb55d09deb3caee6b480","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connecting","source_path":"ui/src/i18n/locales/nl.ts","src_lang":"en","text":"Connecting to session…","text_hash":"f1f49121a17a09b93f1601768c015f6fa9ef2056365c5a745837a2be98f71874","tgt_lang":"nl","translated":"Verbinding maken met sessie…","updated_at":"2026-07-15T00:45:44.990Z"} {"cache_key":"ab2dcada6652ca028ff3f5ed792cfb9b96562fd33d8ca85b5aa867d174c17609","model":"gpt-5.5","provider":"openai","segment_id":"worktrees.actions","source_path":"ui/src/i18n/locales/nl.ts","src_lang":"en","text":"Actions","text_hash":"ff8059dc6752afdd30d275932b1d5031a2ec854b387a8c57ecc6689915293a43","tgt_lang":"nl","translated":"Acties","updated_at":"2026-07-05T21:01:37.071Z"} {"cache_key":"ab570a6ab4088b159718e2cd9d415cf31c1c843db8f35eee542ec8798fea6ad3","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.openChat","source_path":"ui/src/i18n/locales/nl.ts","src_lang":"en","text":"Open chat","text_hash":"0600175af8429c3cd44ab859dcdd3cfa5c763d9b352c7b03520196d4ceb59a88","tgt_lang":"nl","translated":"Chat openen","updated_at":"2026-07-09T08:27:29.789Z"} {"cache_key":"abd00aa5e64818b81c3c5fecbde76865bc0a7e3472d7671f262b176fed5f7da4","model":"gpt-5.6-sol","provider":"openai","segment_id":"channels.setup.finish","source_path":"ui/src/i18n/locales/nl.ts","src_lang":"en","text":"Finish","text_hash":"a6c7a84baa6750fce33f7512acd6793e53def1d228b5f2efb8074b42648424fc","tgt_lang":"nl","translated":"Voltooien","updated_at":"2026-07-13T16:53:23.812Z"} diff --git a/ui/src/i18n/.i18n/pl.meta.json b/ui/src/i18n/.i18n/pl.meta.json index 90c07ff5fb68..bfb2ada81a76 100644 --- a/ui/src/i18n/.i18n/pl.meta.json +++ b/ui/src/i18n/.i18n/pl.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-07-14T23:35:51.639Z", + "generatedAt": "2026-07-15T00:45:37.101Z", "locale": "pl", - "model": "claude-opus-4-8", - "provider": "anthropic", - "sourceHash": "85e004373478d946c5002519a35b2c50e1aa220fd2a81f7c1c80ded99bc7dc4e", - "totalKeys": 3353, - "translatedKeys": 3353, + "model": "gpt-5.6-sol", + "provider": "openai", + "sourceHash": "dc6d0f7bdb69960a5b9d54c5743445b89b0689913b3f962829f8490dede56d2c", + "totalKeys": 3355, + "translatedKeys": 3355, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/pl.tm.jsonl b/ui/src/i18n/.i18n/pl.tm.jsonl index 00b3d3f9bbfe..518c83a697f5 100644 --- a/ui/src/i18n/.i18n/pl.tm.jsonl +++ b/ui/src/i18n/.i18n/pl.tm.jsonl @@ -902,6 +902,8 @@ {"cache_key":"7db74e90c18af539e2f75c9664fa6afc220d658a95da94c374182ac19c1b19d9","model":"claude-opus-4-8","provider":"anthropic","segment_id":"cron.detail.overviewTitle","source_path":"ui/src/i18n/locales/pl.ts","src_lang":"en","text":"Recent activity","text_hash":"6cb44b56336af70b050861cb4617a0d729c872fe8206e1104c26edad0b79cb7a","tgt_lang":"pl","translated":"Ostatnia aktywność","updated_at":"2026-07-12T06:50:33.143Z"} {"cache_key":"7ddd791a0eef6e2b81bf314c3f50863a3bb695dff7cc1c092a702be8b7f29e83","model":"claude-opus-4-8","provider":"anthropic","segment_id":"configView.sections.web","source_path":"ui/src/i18n/locales/pl.ts","src_lang":"en","text":"Web","text_hash":"2975104784a401e3880e2215550e9490eda7e67db5fc2b35e1a244acb092ced3","tgt_lang":"pl","translated":"Sieć","updated_at":"2026-07-12T06:47:28.389Z"} {"cache_key":"7dec11d6123c7df613c6b38e6e7ab35d55ef6c168bac323b4b4b5eac939cb51f","model":"gpt-5.5","provider":"openai","segment_id":"claudeSessions.empty.nonArchived","source_path":"ui/src/i18n/locales/pl.ts","src_lang":"en","text":"No non-archived sessions on this host.","text_hash":"49ecb9e9967d53ce0ffb0d1d5c1f75a595973708d03a577ba9a6e3f327a428fb","tgt_lang":"pl","translated":"Brak niezarchiwizowanych sesji na tym hoście.","updated_at":"2026-07-11T13:51:15.565Z"} +{"cache_key":"7dee7dd62cacb2101661643f0bbaea9b654b19ce195b260730a29b50a95f6bbb","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connectionTimedOut","source_path":"ui/src/i18n/locales/pl.ts","src_lang":"en","text":"Session did not connect within 30 seconds.","text_hash":"38c85973d7e3a6e0d95a423366c67d737a8857eeeb09954ed1e31bf47a0f4b9e","tgt_lang":"pl","translated":"Nie udało się połączyć z sesją w ciągu 30 sekund.","updated_at":"2026-07-15T00:45:37.101Z"} +{"cache_key":"7e074f9ab07b5fb3f64893db6ea684536a2616f572dc4a66123c46b7d39ceb2f","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connecting","source_path":"ui/src/i18n/locales/pl.ts","src_lang":"en","text":"Connecting to session…","text_hash":"f1f49121a17a09b93f1601768c015f6fa9ef2056365c5a745837a2be98f71874","tgt_lang":"pl","translated":"Łączenie z sesją…","updated_at":"2026-07-15T00:45:37.101Z"} {"cache_key":"7e4f44c6bafbc6743ed1fa5f6ff990595a3a2a505068873b4a465b3cda63baec","model":"claude-opus-4-8","provider":"anthropic","segment_id":"configView.textSizes.xl","source_path":"ui/src/i18n/locales/pl.ts","src_lang":"en","text":"XL","text_hash":"f365705bb0612eed918b30a166bd7b5d48908d58cbfb7da0deff2a7b953199ce","tgt_lang":"pl","translated":"XL","updated_at":"2026-07-12T06:47:33.953Z"} {"cache_key":"7e6ba9b4c0cb06b50d82883f6cd7016194c9f93101cf520c224322751709fd94","model":"gpt-5.5","provider":"openai","segment_id":"chat.backgroundTasks.loading","source_path":"ui/src/i18n/locales/pl.ts","src_lang":"en","text":"Loading background tasks…","text_hash":"b8f8eaea7ccdee15740c7daa3d290a534fd3db2b4f0b2835243a6627d9ff7ff6","tgt_lang":"pl","translated":"Ładowanie zadań w tle…","updated_at":"2026-07-11T00:45:31.838Z"} {"cache_key":"7e814784495586c151e4c556cb072b933df4b4f74760e5ba077fff751c577ea8","model":"gpt-5.6-sol","provider":"openai","segment_id":"connection.snapshot.lastChannelsRefresh","source_path":"ui/src/i18n/locales/pl.ts","src_lang":"en","text":"Last Channels Refresh","text_hash":"97a20d4f5b29914b8a08748cfc55d704a4d52ed948180cc90b7c1e06267c692f","tgt_lang":"pl","translated":"Ostatnie odświeżenie kanałów","updated_at":"2026-07-12T00:10:05.075Z"} diff --git a/ui/src/i18n/.i18n/pt-BR.meta.json b/ui/src/i18n/.i18n/pt-BR.meta.json index 9f4661d5c9eb..7c27e5219c07 100644 --- a/ui/src/i18n/.i18n/pt-BR.meta.json +++ b/ui/src/i18n/.i18n/pt-BR.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-07-14T23:35:50.367Z", + "generatedAt": "2026-07-15T00:45:05.102Z", "locale": "pt-BR", - "model": "claude-opus-4-8", - "provider": "anthropic", - "sourceHash": "85e004373478d946c5002519a35b2c50e1aa220fd2a81f7c1c80ded99bc7dc4e", - "totalKeys": 3353, - "translatedKeys": 3353, + "model": "gpt-5.6-sol", + "provider": "openai", + "sourceHash": "dc6d0f7bdb69960a5b9d54c5743445b89b0689913b3f962829f8490dede56d2c", + "totalKeys": 3355, + "translatedKeys": 3355, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/pt-BR.tm.jsonl b/ui/src/i18n/.i18n/pt-BR.tm.jsonl index 778c89b0a9a6..c3fc640bf36e 100644 --- a/ui/src/i18n/.i18n/pt-BR.tm.jsonl +++ b/ui/src/i18n/.i18n/pt-BR.tm.jsonl @@ -630,6 +630,7 @@ {"cache_key":"5494d598782d821e79e7a9eab1e6e942bd34909e44a5618d1ce487b2189c4804","model":"claude-opus-4-8","provider":"anthropic","segment_id":"skillWorkshop.actions.previous","source_path":"ui/src/i18n/locales/pt-BR.ts","src_lang":"en","text":"Previous","text_hash":"a57b08a480b822a0a572b993391c292ede593bf8000b406675b180bbb16260fa","tgt_lang":"pt-BR","translated":"Anterior","updated_at":"2026-07-12T06:28:16.888Z"} {"cache_key":"549a2440defaf37562a29331c04387f66a0345d9b69feab4ca72d0bd060683b5","model":"claude-opus-4-8","provider":"anthropic","segment_id":"chat.messages.openInCanvas","source_path":"ui/src/i18n/locales/pt-BR.ts","src_lang":"en","text":"Open in canvas","text_hash":"02597bb20cb5d93ebbb47e62c3591fcf9dec76fc42697e1dc130e1f167232e61","tgt_lang":"pt-BR","translated":"Abrir no canvas","updated_at":"2026-07-12T06:29:10.164Z"} {"cache_key":"549e4fe33824e518937b667adb9ece09a9f6f731e794697856236aa728257494","model":"claude-opus-4-8","provider":"anthropic","segment_id":"skillsPage.verdict.blocked","source_path":"ui/src/i18n/locales/pt-BR.ts","src_lang":"en","text":"Blocked","text_hash":"18f2a0947f9d6523991b29b450307f22773f57d65f7efb98d48a167df04d6b1d","tgt_lang":"pt-BR","translated":"Bloqueado","updated_at":"2026-06-17T14:13:15.046Z"} +{"cache_key":"549f9eb5bfb47109c634961ade76e170c4c4ce6b3d29f76bad18851a389818d2","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connectionTimedOut","source_path":"ui/src/i18n/locales/pt-BR.ts","src_lang":"en","text":"Session did not connect within 30 seconds.","text_hash":"38c85973d7e3a6e0d95a423366c67d737a8857eeeb09954ed1e31bf47a0f4b9e","tgt_lang":"pt-BR","translated":"A sessão não foi conectada em até 30 segundos.","updated_at":"2026-07-15T00:45:05.102Z"} {"cache_key":"54e413d127eabc5d41ba8916286c51228984010b95c36efea00704ed25e04c56","model":"claude-opus-4-8","provider":"anthropic","segment_id":"configView.themes.claw.label","source_path":"ui/src/i18n/locales/pt-BR.ts","src_lang":"en","text":"Claw","text_hash":"587cfbbbcd42a71ba7f33f8051d5c354707fa1ff227cab11452c3ba2147a8682","tgt_lang":"pt-BR","translated":"Claw","updated_at":"2026-07-12T06:26:59.355Z"} {"cache_key":"54f626f2673974a4dc9ac0d723d762fb12942a16d33fabec18dc9f13ac410200","model":"claude-opus-4-8","provider":"anthropic","segment_id":"agentTools.toolsOne","source_path":"ui/src/i18n/locales/pt-BR.ts","src_lang":"en","text":"{count} Tool","text_hash":"1b25a793182ec17a490365d4b12703940e9e4592bc40c4a48d66a7d9ed06adcd","tgt_lang":"pt-BR","translated":"{count} ferramenta","updated_at":"2026-07-12T06:27:35.199Z"} {"cache_key":"55058cc343c6f9340676eff66678c5afd5ef256ccd02c97f0dafc4a72c1787f9","model":"claude-opus-4-8","provider":"anthropic","segment_id":"configForm.sections.channels.description","source_path":"ui/src/i18n/locales/pt-BR.ts","src_lang":"en","text":"Messaging channels (Telegram, Discord, Slack, etc.)","text_hash":"6e372c3083a4d6153c076740549e9cc20995bcb1bd0ef43950671b7845b6e07d","tgt_lang":"pt-BR","translated":"Canais de mensagens (Telegram, Discord, Slack, etc.)","updated_at":"2026-07-12T06:26:05.905Z"} @@ -1365,6 +1366,7 @@ {"cache_key":"bedac72b4656686d4b258e89f738336b8cd4cbda02583c14642f64da8dccd5a4","model":"claude-opus-4-8","provider":"anthropic","segment_id":"quickSettings.system.used","source_path":"ui/src/i18n/locales/pt-BR.ts","src_lang":"en","text":"used","text_hash":"f839161355091fcd9e33f571e0b11c60217a5009f9973e769c13d7858db162cc","tgt_lang":"pt-BR","translated":"usado","updated_at":"2026-07-12T06:26:38.130Z"} {"cache_key":"bf56aab3b35d65a3dedc9a41f3b56833baa554735d2f6e8e590155eccdf868fb","model":"claude-opus-4-8","provider":"anthropic","segment_id":"chat.composer.working","source_path":"ui/src/i18n/locales/pt-BR.ts","src_lang":"en","text":"{name} is working...","text_hash":"dbce69e1f37797e32879e9960125e409ed6f1e36e238cfd0898eb60ea839deca","tgt_lang":"pt-BR","translated":"{name} está trabalhando...","updated_at":"2026-07-12T06:29:21.041Z"} {"cache_key":"bf5d0d78c050f9e4e5347bf603a69da4e5799d7204beb7a36c67fb884b7aa61c","model":"claude-opus-4-8","provider":"anthropic","segment_id":"chat.pairingQrExpired.title","source_path":"ui/src/i18n/locales/pt-BR.ts","src_lang":"en","text":"Pairing QR expired","text_hash":"74e09eafc1d35cad5b62b7a9c321a4d090bb8fefdfa8b33913d6194186eadda6","tgt_lang":"pt-BR","translated":"QR de pareamento expirado","updated_at":"2026-07-01T10:31:00.782Z"} +{"cache_key":"bf6c8276cc89c2e07cfb243ee92d7b13129242791f6367f948d0eb54fd6e2300","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connecting","source_path":"ui/src/i18n/locales/pt-BR.ts","src_lang":"en","text":"Connecting to session…","text_hash":"f1f49121a17a09b93f1601768c015f6fa9ef2056365c5a745837a2be98f71874","tgt_lang":"pt-BR","translated":"Conectando à sessão…","updated_at":"2026-07-15T00:45:05.102Z"} {"cache_key":"bfd3b08dbaf6bb1b7b282c2f6bede7b2cd75d7412036270de7692ca19d0aa0bb","model":"claude-opus-4-8","provider":"anthropic","segment_id":"configForm.resetToDefault","source_path":"ui/src/i18n/locales/pt-BR.ts","src_lang":"en","text":"Reset to default","text_hash":"bc5b45ae7b60692ade0bb26ebe5b6830f150d97134153e3c3091882eb72e7c25","tgt_lang":"pt-BR","translated":"Redefinir para o padrão","updated_at":"2026-07-12T06:26:00.301Z"} {"cache_key":"c00ff84c177f8c24cf5c3ba1cdb8a383441b8b7c412206a22253281022ca4f3d","model":"claude-opus-4-8","provider":"anthropic","segment_id":"skillsPage.reason","source_path":"ui/src/i18n/locales/pt-BR.ts","src_lang":"en","text":"Reason: {reasons}","text_hash":"f98758e12634814d8357b70d457d3847a9c8d641993694b536c63367075187b3","tgt_lang":"pt-BR","translated":"Motivo: {reasons}","updated_at":"2026-07-12T06:27:51.283Z"} {"cache_key":"c037fa2fa74af71c6693ddb13369398a2936c62e789d5e4004dbf84dbcedac19","model":"gpt-5.6-sol","provider":"openai","segment_id":"chat.progressLabels.pinching","source_path":"ui/src/i18n/locales/pt-BR.ts","src_lang":"en","text":"Pinching","text_hash":"01cfb797d96a35b63f28cf276b5c777df0f256885b7c0f5e0d2d35b418daf7d5","tgt_lang":"pt-BR","translated":"Beliscando","updated_at":"2026-07-14T04:53:03.154Z"} diff --git a/ui/src/i18n/.i18n/ru.meta.json b/ui/src/i18n/.i18n/ru.meta.json index c5cd56f87e98..eef4d61bbe2a 100644 --- a/ui/src/i18n/.i18n/ru.meta.json +++ b/ui/src/i18n/.i18n/ru.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-07-14T23:35:52.213Z", + "generatedAt": "2026-07-15T00:45:50.229Z", "locale": "ru", - "model": "claude-opus-4-8", - "provider": "anthropic", - "sourceHash": "85e004373478d946c5002519a35b2c50e1aa220fd2a81f7c1c80ded99bc7dc4e", - "totalKeys": 3353, - "translatedKeys": 3353, + "model": "gpt-5.6-sol", + "provider": "openai", + "sourceHash": "dc6d0f7bdb69960a5b9d54c5743445b89b0689913b3f962829f8490dede56d2c", + "totalKeys": 3355, + "translatedKeys": 3355, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/ru.tm.jsonl b/ui/src/i18n/.i18n/ru.tm.jsonl index 1179b4423cc1..fa4ea12fd1e5 100644 --- a/ui/src/i18n/.i18n/ru.tm.jsonl +++ b/ui/src/i18n/.i18n/ru.tm.jsonl @@ -2618,6 +2618,7 @@ {"cache_key":"d27d192faa982d1f1a0c3b444c5c6e5fa3d8759c79b2be176257c9ade031a3e2","model":"claude-opus-4-8","provider":"anthropic","segment_id":"configView.appearance.replace","source_path":"ui/src/i18n/locales/ru.ts","src_lang":"en","text":"Replace {name}","text_hash":"93a0cf0f05a0f232f354d458882d45d10c60c5bc12c89a5df7f4cb6553233fcd","tgt_lang":"ru","translated":"Заменить {name}","updated_at":"2026-07-12T07:00:21.256Z"} {"cache_key":"d28bf8b903552be6f3384de908b08d85882961ef6f3b6fd09ba7a4219030f656","model":"gpt-5.5","provider":"openai","segment_id":"overview.palette.footer.select","source_path":"ui/src/i18n/locales/ru.ts","src_lang":"en","text":"select","text_hash":"b1a36d25d9633ed2ac04939fcb614ccb2b513243c148f18694592ae037f9d35f","tgt_lang":"ru","translated":"выбрать","updated_at":"2026-06-26T21:40:37.573Z"} {"cache_key":"d29b4bbb191e0c891d1b249c5b774fcf108482d7df85df83117caed8f45c31d2","model":"gpt-5.6-sol","provider":"openai","segment_id":"agents.identity.subtitle","source_path":"ui/src/i18n/locales/ru.ts","src_lang":"en","text":"Name, emoji, and avatar shown in chats and the sidebar.","text_hash":"a7fa9a1082ea324736d132dd25b5940c0b2b2edc5117ffed30a35ff47b175d0e","tgt_lang":"ru","translated":"Имя, эмодзи и аватар, отображаемые в чатах и на боковой панели.","updated_at":"2026-07-13T05:31:27.367Z"} +{"cache_key":"d2a3081c1e540a86a0aefcef6ca29bdfbe46051c98ba4794eed2ef89db6f0874","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connecting","source_path":"ui/src/i18n/locales/ru.ts","src_lang":"en","text":"Connecting to session…","text_hash":"f1f49121a17a09b93f1601768c015f6fa9ef2056365c5a745837a2be98f71874","tgt_lang":"ru","translated":"Подключение к сеансу…","updated_at":"2026-07-15T00:45:50.229Z"} {"cache_key":"d2b87ae61cb8d7763072763db801029f2cc4017b4ee970e5d10c885937fb692d","model":"gpt-5.5","provider":"openai","segment_id":"cron.jobList.edit","source_path":"ui/src/i18n/locales/ru.ts","src_lang":"en","text":"Edit","text_hash":"464c4ffd019e1e9691dcf0537c797353ef2b1c1d4833d3d463e5b74ae4547344","tgt_lang":"ru","translated":"Изменить","updated_at":"2026-06-26T21:42:47.554Z"} {"cache_key":"d2cc6dc04e56f4e28ea0bc76cff98d2d741ff954b9a91d268169c3af4053277c","model":"gpt-5.5","provider":"openai","segment_id":"dreaming.trace.emptyShortTerm","source_path":"ui/src/i18n/locales/ru.ts","src_lang":"en","text":"No active short-term items.","text_hash":"e3a71c5ac02b76384ed603efc99062bf70b21092fd094fb3a7c0b3e2647ee757","tgt_lang":"ru","translated":"Нет активных краткосрочных элементов.","updated_at":"2026-06-26T21:40:48.120Z"} {"cache_key":"d2d592685376469426cf4d5cf32e5c2884b19942c6087bdc7e02cee567fb9d4b","model":"gpt-5.5","provider":"openai","segment_id":"worktrees.owner","source_path":"ui/src/i18n/locales/ru.ts","src_lang":"en","text":"Owner","text_hash":"4b1b8aa3608a26da451ae0630d75b60ab1bc2dd229c41a80838fc7993e835c46","tgt_lang":"ru","translated":"Владелец","updated_at":"2026-07-10T18:00:13.179Z"} @@ -3085,6 +3086,7 @@ {"cache_key":"f86284c2698db911ebac5d20ef9eab007ead6a773546341ea6d76e14f3c51a16","model":"gpt-5.5","provider":"openai","segment_id":"configView.sections.models","source_path":"ui/src/i18n/locales/ru.ts","src_lang":"en","text":"Models","text_hash":"d17d2d78d76e6a6cd13048de225e107ac73de4aa5f8914e58a0cfaa9698c373e","tgt_lang":"ru","translated":"Модели","updated_at":"2026-06-26T21:39:17.817Z"} {"cache_key":"f86ce0fbee87217702d44a021e0d51c0a564fd15a0c4f1e43f2d669cc5077e35","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.defaultOption","source_path":"ui/src/i18n/locales/ru.ts","src_lang":"en","text":"Default ({value})","text_hash":"a9d6571117890ef77ecc72f77ba43e9d8b05ed82c1c64ff27a352c02dff3c2bd","tgt_lang":"ru","translated":"По умолчанию ({value})","updated_at":"2026-06-26T21:38:54.164Z"} {"cache_key":"f86e511303df40c74db4a1e9ad9803c68716427fbdba55e442a64433d69eb5ad","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.filters","source_path":"ui/src/i18n/locales/ru.ts","src_lang":"en","text":"Filters","text_hash":"546ebb8eb993ea561029d9febd84c363bdb09010bb2cb915a8287762b76b9a64","tgt_lang":"ru","translated":"Фильтры","updated_at":"2026-06-26T21:38:48.084Z"} +{"cache_key":"f89f34ef96a6eb801473ff9d358d21c5cf563930947af394eaaa7d34cf6d266e","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connectionTimedOut","source_path":"ui/src/i18n/locales/ru.ts","src_lang":"en","text":"Session did not connect within 30 seconds.","text_hash":"38c85973d7e3a6e0d95a423366c67d737a8857eeeb09954ed1e31bf47a0f4b9e","tgt_lang":"ru","translated":"Не удалось подключиться к сеансу в течение 30 секунд.","updated_at":"2026-07-15T00:45:50.229Z"} {"cache_key":"f8d1664c037238e7b7966b8af30a892ceffd853ee093dcda14325c2e08271995","model":"gpt-5.5","provider":"openai","segment_id":"common.reloadConfig","source_path":"ui/src/i18n/locales/ru.ts","src_lang":"en","text":"Reload Config","text_hash":"48e6315352561c36be84097326fbb3558b4c2fa3fc4f833402d32040ccb640f7","tgt_lang":"ru","translated":"Перезагрузить конфигурацию","updated_at":"2026-06-26T21:38:30.975Z"} {"cache_key":"f8d36cbffbfdec2566a29cc23f954616dee2fc091172edc9c98a74ea674d6328","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.dateToday","source_path":"ui/src/i18n/locales/ru.ts","src_lang":"en","text":"Today","text_hash":"2b065c7c9ce466e5ebcad757987d5d660ee4c9ea708bc62c43444b53334738ba","tgt_lang":"ru","translated":"Сегодня","updated_at":"2026-06-26T21:40:56.975Z"} {"cache_key":"f8dcfb5a4fe291ae049cdc2770276434112e2c96a769f23ad9f2df36647fc2aa","model":"claude-opus-4-8","provider":"anthropic","segment_id":"agentTools.loadConfig","source_path":"ui/src/i18n/locales/ru.ts","src_lang":"en","text":"Load the gateway config to adjust tool profiles.","text_hash":"0e9291ef2bab7a6a96376087b37d281a973319e0c5b893360b67cf5681d8c863","tgt_lang":"ru","translated":"Загрузите конфигурацию gateway, чтобы настроить профили инструментов.","updated_at":"2026-07-12T07:00:42.425Z"} diff --git a/ui/src/i18n/.i18n/th.meta.json b/ui/src/i18n/.i18n/th.meta.json index 069145473b5a..252f81fa07c4 100644 --- a/ui/src/i18n/.i18n/th.meta.json +++ b/ui/src/i18n/.i18n/th.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-07-14T23:35:51.742Z", + "generatedAt": "2026-07-15T00:45:39.822Z", "locale": "th", - "model": "claude-opus-4-8", - "provider": "anthropic", - "sourceHash": "85e004373478d946c5002519a35b2c50e1aa220fd2a81f7c1c80ded99bc7dc4e", - "totalKeys": 3353, - "translatedKeys": 3353, + "model": "gpt-5.6-sol", + "provider": "openai", + "sourceHash": "dc6d0f7bdb69960a5b9d54c5743445b89b0689913b3f962829f8490dede56d2c", + "totalKeys": 3355, + "translatedKeys": 3355, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/th.tm.jsonl b/ui/src/i18n/.i18n/th.tm.jsonl index 081ea2191610..ee76809d257d 100644 --- a/ui/src/i18n/.i18n/th.tm.jsonl +++ b/ui/src/i18n/.i18n/th.tm.jsonl @@ -84,6 +84,7 @@ {"cache_key":"0b46aea66abe0e5da609d04faa21bcd1568c58bb3ade9b485b3f88cede82f0bd","model":"gpt-5.5","provider":"openai","segment_id":"chat.pullRequests.checksFailed","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"Failed","text_hash":"031a8f0f659df890dfd53c92e45295b0f14c997185bae46e168831e403b273f7","tgt_lang":"th","translated":"ล้มเหลว","updated_at":"2026-07-10T23:12:49.455Z"} {"cache_key":"0b51c6aff1f66c79ba67dd53f88619f76829d952af88fbb1ffafffd080404e7a","model":"gpt-5.5","provider":"openai","segment_id":"usage.providerUsage.lastDays","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"{count} days","text_hash":"e9f0a85930cc6fa61b7ac01763893020adc4c712d1b8e8897bdd13971637d529","tgt_lang":"th","translated":"{count} วัน","updated_at":"2026-07-06T06:40:15.357Z"} {"cache_key":"0b576e45e59c88e6195e7ba95d23bd9db989d475a96b1eeebe56e6a9f74127e5","model":"claude-opus-4-8","provider":"anthropic","segment_id":"agentTools.enabledByOverride","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"Enabled by agent override.","text_hash":"298b111d71465ff9092838988e31a5d6fbdb8557d301e99fae621ba1924e1fb6","tgt_lang":"th","translated":"เปิดใช้งานโดยการแทนที่ของเอเจนต์","updated_at":"2026-07-12T06:54:00.247Z"} +{"cache_key":"0bacd36c88ada0c6ca89e58796a7c7837bd0d817aa8f55759eefe341e9489dc3","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connecting","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"Connecting to session…","text_hash":"f1f49121a17a09b93f1601768c015f6fa9ef2056365c5a745837a2be98f71874","tgt_lang":"th","translated":"กำลังเชื่อมต่อกับเซสชัน…","updated_at":"2026-07-15T00:45:39.822Z"} {"cache_key":"0bc2280a1a058c478c10765ef0fd9ed59142e530a465dbf8147094d7453b9244","model":"gpt-5.6-sol","provider":"openai","segment_id":"skillWorkshop.selfLearning.pitchBody","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"OpenClaw reviews corrections and substantial completed runs, then drafts skill proposals for this board. It spends extra background tokens and every proposal waits for your review.","text_hash":"b06e460a44a232840a13f7cefdb87910f61da21ddafe0c9cc40eecb9be9e6757","tgt_lang":"th","translated":"OpenClaw จะตรวจสอบการแก้ไขและการดำเนินงานสำคัญที่เสร็จสมบูรณ์ จากนั้นร่างข้อเสนอ Skills สำหรับบอร์ดนี้ โดยจะใช้โทเค็นเบื้องหลังเพิ่มเติม และทุกข้อเสนอจะรอให้คุณตรวจสอบ","updated_at":"2026-07-13T06:16:22.653Z"} {"cache_key":"0bca8ee9c50779080f2276c69d865ed5a4369614745fd8c52b5cf88da23c0b16","model":"claude-opus-4-8","provider":"anthropic","segment_id":"workboard.viewRecentlyDone","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"Recently done","text_hash":"46b889592a2f5e79197f62b5f96c76993545626bf207740ea58632ceed9623be","tgt_lang":"th","translated":"เพิ่งเสร็จ","updated_at":"2026-06-17T14:16:47.316Z"} {"cache_key":"0bd7f0ec49e6bb6fccc86a5dc42fc6a06aed138f112e66e6edda18e9721797a3","model":"gpt-5.6-sol","provider":"openai","segment_id":"connection.access.showPassword","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"Show password","text_hash":"6aeaa6a53d09dcad071fdda6280b1e7c42aa164cd0514304ff162e7da440ffaa","tgt_lang":"th","translated":"แสดงรหัสผ่าน","updated_at":"2026-07-12T00:10:17.485Z"} @@ -1181,6 +1182,7 @@ {"cache_key":"a0cecd735cd38870804887fc91e904ee91769eb29f1efd33539e05ef9fcf28d2","model":"claude-opus-4-8","provider":"anthropic","segment_id":"filePreview.readOnly","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"read-only","text_hash":"4fed3970dcc0d31dccddbf69ef55b00f32d2ca787f1757894914bc0365ea7aa5","tgt_lang":"th","translated":"อ่านอย่างเดียว","updated_at":"2026-07-12T06:49:19.074Z"} {"cache_key":"a0d4438f197d54754a0ecf05a40ae1be4952b538453fc661b0d8f759735ac598","model":"claude-opus-4-8","provider":"anthropic","segment_id":"channels.nostr.placeholders.lightningAddress","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"you@getalby.com","text_hash":"7c433ad5c3a532bd4ab1a634c4ac4d75cbd857e41b1de75b50ac8c8b16ccf319","tgt_lang":"th","translated":"you@getalby.com","updated_at":"2026-07-12T06:49:37.673Z"} {"cache_key":"a0db05454e35ec33757ac8fa9e37a2bf5b813584cc87ae0ed708317e3658500e","model":"claude-opus-4-8","provider":"anthropic","segment_id":"chat.sideChat.title","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"Side chat","text_hash":"77db106985161e4794ca08b6f32f3cec864d91f7b8107b21b30c52be19ed0236","tgt_lang":"th","translated":"แชทด้านข้าง","updated_at":"2026-07-12T06:57:50.389Z"} +{"cache_key":"a0ec6e2a50d73f982c30b7ad4f0f03c2ec6017129ae8dbbe4d7df437cc871d6f","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connectionTimedOut","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"Session did not connect within 30 seconds.","text_hash":"38c85973d7e3a6e0d95a423366c67d737a8857eeeb09954ed1e31bf47a0f4b9e","tgt_lang":"th","translated":"ไม่สามารถเชื่อมต่อเซสชันได้ภายใน 30 วินาที","updated_at":"2026-07-15T00:45:39.822Z"} {"cache_key":"a10d87a8e1f91b20dabe0977ab313c1e9e3ed00a447c1d5f0876e2d071d0f009","model":"gpt-5","provider":"openai","segment_id":"codexSessions.host.connected","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"Connected","text_hash":"22965568d22a14ee17af055d2870b50afcfe9fd94a83eec3196e266932297bb2","tgt_lang":"th","translated":"เชื่อมต่อแล้ว","updated_at":"2026-07-09T10:01:43.768Z"} {"cache_key":"a1371bbf9cc3ef23b2f5f39aea49299c961f1a81bdac841c6b2e091b409968c8","model":"gpt-5.6-sol","provider":"openai","segment_id":"aboutPage.linkGitHub","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"GitHub","text_hash":"f911e414cf6bdfc595532ab166b5ba0f63d73c021452fcdacbda363dda6ad8fb","tgt_lang":"th","translated":"GitHub","updated_at":"2026-07-13T17:00:15.912Z"} {"cache_key":"a19793da03829e99bb156deb2638814f8ad817c4b3266bad12c066b119200694","model":"gpt-5.6-sol","provider":"openai","segment_id":"configPage.content","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"Settings content","text_hash":"e4dcd3d9bff6ba5422b5216d88009d9b4be8eb0e6c65d25781cb3441c80e0597","tgt_lang":"th","translated":"เนื้อหาการตั้งค่า","updated_at":"2026-07-13T13:04:23.414Z"} diff --git a/ui/src/i18n/.i18n/tr.meta.json b/ui/src/i18n/.i18n/tr.meta.json index db74b5acf120..6320eea72b50 100644 --- a/ui/src/i18n/.i18n/tr.meta.json +++ b/ui/src/i18n/.i18n/tr.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-07-14T23:35:51.326Z", + "generatedAt": "2026-07-15T00:45:28.240Z", "locale": "tr", - "model": "claude-opus-4-8", - "provider": "anthropic", - "sourceHash": "85e004373478d946c5002519a35b2c50e1aa220fd2a81f7c1c80ded99bc7dc4e", - "totalKeys": 3353, - "translatedKeys": 3353, + "model": "gpt-5.6-sol", + "provider": "openai", + "sourceHash": "dc6d0f7bdb69960a5b9d54c5743445b89b0689913b3f962829f8490dede56d2c", + "totalKeys": 3355, + "translatedKeys": 3355, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/tr.tm.jsonl b/ui/src/i18n/.i18n/tr.tm.jsonl index 4eafe677a0b6..7e3b88a055ca 100644 --- a/ui/src/i18n/.i18n/tr.tm.jsonl +++ b/ui/src/i18n/.i18n/tr.tm.jsonl @@ -663,6 +663,7 @@ {"cache_key":"607fa5183ba39bbc7c00b2fbf9422837367a0c297be46a3b6688895cee8436a2","model":"claude-opus-4-8","provider":"anthropic","segment_id":"quickSettings.automation.configure","source_path":"ui/src/i18n/locales/tr.ts","src_lang":"en","text":"Configure →","text_hash":"3853cdcbe571c3cc86f127a799dfbcc2540b79f092761008c94f70976f8d9e7c","tgt_lang":"tr","translated":"Yapılandır →","updated_at":"2026-07-12T06:39:49.200Z"} {"cache_key":"60a81b02bbbe7ccb8d41c22387b9e72efe27aeb50f80e0734f9c3c7937195fb2","model":"claude-opus-4-8","provider":"anthropic","segment_id":"configView.appearance.themeLink","source_path":"ui/src/i18n/locales/tr.ts","src_lang":"en","text":"Theme link or ID","text_hash":"5c6e9a2d22ee3070ff697719d1236c9381856b1737b511563084ffca7f74d797","tgt_lang":"tr","translated":"Tema bağlantısı veya kimliği","updated_at":"2026-07-12T06:40:40.562Z"} {"cache_key":"60a9849004064cd9509ec3e357f872ac97ae548355170af6967076900e120dbd","model":"claude-opus-4-8","provider":"anthropic","segment_id":"pluginsPage.connectorDescriptions.email","source_path":"ui/src/i18n/locales/tr.ts","src_lang":"en","text":"Mailbox triage, summaries, and drafts with send-on-approval.","text_hash":"ba48296df0613eff276edad474c32efeb8ffec2590287d7f6de51a4de26f4d0f","tgt_lang":"tr","translated":"Onay üzerine gönderme özelliğiyle posta kutusu önceliklendirme, özetler ve taslaklar.","updated_at":"2026-07-12T06:41:55.091Z"} +{"cache_key":"60c642f6cdcd25502be33d33ba8d90121baf4efb40b57f595420f0933c9f1432","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connecting","source_path":"ui/src/i18n/locales/tr.ts","src_lang":"en","text":"Connecting to session…","text_hash":"f1f49121a17a09b93f1601768c015f6fa9ef2056365c5a745837a2be98f71874","tgt_lang":"tr","translated":"Oturuma bağlanılıyor…","updated_at":"2026-07-15T00:45:28.240Z"} {"cache_key":"60db5fc671394b1fcca3c8f55e2f5ace272cc439e19f4233bee049394ab1c314","model":"claude-opus-4-8","provider":"anthropic","segment_id":"nodes.inventory.inputAgo","source_path":"ui/src/i18n/locales/tr.ts","src_lang":"en","text":"input {time} ago","text_hash":"cf922e918893ebac328c042f6cb71f6381c808dbdf06faa23a962bf21ac225f1","tgt_lang":"tr","translated":"girdi {time} önce","updated_at":"2026-07-12T06:38:23.899Z"} {"cache_key":"610e81960ab9e3620b58537c737e3060f0910ab2e2e73b599ac5bf8a55efc653","model":"gpt-5.5","provider":"openai","segment_id":"profilePage.loading","source_path":"ui/src/i18n/locales/tr.ts","src_lang":"en","text":"Diving for stats…","text_hash":"81a616d9c5f179db9a23f119bd752b6c0445a3e37bcc28e76eab97fd29da7c74","tgt_lang":"tr","translated":"İstatistikler için dalınıyor…","updated_at":"2026-07-09T11:28:03.876Z"} {"cache_key":"61135b4bc8e62f5a047e4ec5c2b3fbb7a88e83c3e1643c1e3bb7c0ea3e058386","model":"claude-opus-4-8","provider":"anthropic","segment_id":"configView.sections.theme","source_path":"ui/src/i18n/locales/tr.ts","src_lang":"en","text":"Theme","text_hash":"efb52e7172b77731d996ff4f51cd7b3dcfd55fc6f07392994619418d58d170dd","tgt_lang":"tr","translated":"Tema","updated_at":"2026-07-12T06:40:24.490Z"} @@ -1425,6 +1426,7 @@ {"cache_key":"ce0fee74eb15e48d1a90ac3db4907e6cb39b62575daa408cb2c9d4fe2051533e","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.groupByChannel","source_path":"ui/src/i18n/locales/tr.ts","src_lang":"en","text":"Channel","text_hash":"ce4683e7013a18cdf3d224bfcb4e9594ea8f559e946a837c633defe7d3c32172","tgt_lang":"tr","translated":"Kanal","updated_at":"2026-07-05T14:40:02.505Z"} {"cache_key":"ce20ae4a3e847496f1d8e55da89ff57ecd6742b674667e0334fe271216b6919f","model":"claude-opus-4-8","provider":"anthropic","segment_id":"chat.commands.clearDescription","source_path":"ui/src/i18n/locales/tr.ts","src_lang":"en","text":"Clear chat history","text_hash":"797a31a346b1a6256296ebe1b6d04dfdf22a2d9d67ad2d2c48370720c0c4bdae","tgt_lang":"tr","translated":"Sohbet geçmişini temizle","updated_at":"2026-07-12T06:43:13.126Z"} {"cache_key":"ce7fb70fbee4ffd9f71ce61b44152ea4fad646f86933576580c8faf393882259","model":"claude-opus-4-8","provider":"anthropic","segment_id":"channels.nostr.placeholders.username","source_path":"ui/src/i18n/locales/tr.ts","src_lang":"en","text":"satoshi","text_hash":"da2876b3eb31edb4436fa4650673fc6f01f90de2f1793c4ec332b2387b09726f","tgt_lang":"tr","translated":"satoshi","updated_at":"2026-07-12T06:38:14.794Z"} +{"cache_key":"ce8ae11155554d37b79de6ad693d99181f7929fb0b06ecd56305e2f87ddc1f62","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connectionTimedOut","source_path":"ui/src/i18n/locales/tr.ts","src_lang":"en","text":"Session did not connect within 30 seconds.","text_hash":"38c85973d7e3a6e0d95a423366c67d737a8857eeeb09954ed1e31bf47a0f4b9e","tgt_lang":"tr","translated":"Oturum bağlantısı 30 saniye içinde kurulamadı.","updated_at":"2026-07-15T00:45:28.240Z"} {"cache_key":"cec1be0c36dacef8e37f132517a334985510bcd01541a58f5233a13ccbc99c2f","model":"claude-opus-4-8","provider":"anthropic","segment_id":"chat.detailPanel.copyPath","source_path":"ui/src/i18n/locales/tr.ts","src_lang":"en","text":"Copy path","text_hash":"720ff4160412b943370afdb8fc1c082ff057d54713d5fb4b4b7a9634bfabf5fe","tgt_lang":"tr","translated":"Yolu kopyala","updated_at":"2026-06-16T14:15:58.007Z"} {"cache_key":"cec993593eeea34ecd5b372d3cf523d5914ec98322acfd7fc339d1b2f4a0152d","model":"claude-opus-4-8","provider":"anthropic","segment_id":"agents.toolCatalog.descriptions.write","source_path":"ui/src/i18n/locales/tr.ts","src_lang":"en","text":"Create or overwrite files","text_hash":"b29a2552b08fc6afff15e7d9e6276beeaa33e59f3fa298a999c6e8115b487d7a","tgt_lang":"tr","translated":"Dosya oluştur veya üzerine yaz","updated_at":"2026-07-12T06:39:05.862Z"} {"cache_key":"ced5928326b77d658f564ce934ea46eca8b0e8151294b842fd435a92ab3596d7","model":"claude-opus-4-8","provider":"anthropic","segment_id":"configView.sections.nodeHost","source_path":"ui/src/i18n/locales/tr.ts","src_lang":"en","text":"Node Host","text_hash":"5dead206faf634af13473cb72f84b9c9fcd99d2cc6191a0ab06f2ff026853d9f","tgt_lang":"tr","translated":"Node Host","updated_at":"2026-07-12T06:40:19.200Z"} diff --git a/ui/src/i18n/.i18n/uk.meta.json b/ui/src/i18n/.i18n/uk.meta.json index dcc03ed7c9e5..7e3993870c0d 100644 --- a/ui/src/i18n/.i18n/uk.meta.json +++ b/ui/src/i18n/.i18n/uk.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-07-14T23:35:51.436Z", + "generatedAt": "2026-07-15T00:45:31.680Z", "locale": "uk", - "model": "claude-opus-4-8", - "provider": "anthropic", - "sourceHash": "85e004373478d946c5002519a35b2c50e1aa220fd2a81f7c1c80ded99bc7dc4e", - "totalKeys": 3353, - "translatedKeys": 3353, + "model": "gpt-5.6-sol", + "provider": "openai", + "sourceHash": "dc6d0f7bdb69960a5b9d54c5743445b89b0689913b3f962829f8490dede56d2c", + "totalKeys": 3355, + "translatedKeys": 3355, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/uk.tm.jsonl b/ui/src/i18n/.i18n/uk.tm.jsonl index ce3f419fb27e..7c02b1aff337 100644 --- a/ui/src/i18n/.i18n/uk.tm.jsonl +++ b/ui/src/i18n/.i18n/uk.tm.jsonl @@ -611,6 +611,7 @@ {"cache_key":"4c9fc4d357241738a36aa22670508d2dc9ebc19ae7905ab7d914f55f86091975","model":"claude-opus-4-8","provider":"anthropic","segment_id":"skillWorkshop.header.useCurrentChat","source_path":"ui/src/i18n/locales/uk.ts","src_lang":"en","text":"Use current chat","text_hash":"fbc1ffd63daa506e927c7a85f6e43acd11e0b8c9f52a3951fc782b236ce9a787","tgt_lang":"uk","translated":"Використовувати поточний чат","updated_at":"2026-06-16T14:15:40.204Z"} {"cache_key":"4d31c39f0ad51921b6e5a644fc60333aa826d9c64f6d792d052c97c70411964c","model":"claude-opus-4-8","provider":"anthropic","segment_id":"configView.notifications.sendTest","source_path":"ui/src/i18n/locales/uk.ts","src_lang":"en","text":"Send test","text_hash":"22d7b11d40c98b51df5d10fdb6d5dfdf7eb83481ba75b5269aad4fab51010615","tgt_lang":"uk","translated":"Надіслати тест","updated_at":"2026-07-12T06:46:56.909Z"} {"cache_key":"4d4431540fcb12e4807302aab91fe2a484dc7cac8646a5dae5476718ea08fdce","model":"gpt-5.5","provider":"openai","segment_id":"browser.refresh","source_path":"ui/src/i18n/locales/uk.ts","src_lang":"en","text":"Refresh view","text_hash":"112b4ddb82dac7080c1c85de7475cd9754b2ac7e0a89fbb38126923180d76ec2","tgt_lang":"uk","translated":"Оновити подання","updated_at":"2026-07-11T02:19:21.035Z"} +{"cache_key":"4d5240441cf4c3f832e9d378b44da2433a6f1b981a00001f2dcecaa50d9f571b","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connectionTimedOut","source_path":"ui/src/i18n/locales/uk.ts","src_lang":"en","text":"Session did not connect within 30 seconds.","text_hash":"38c85973d7e3a6e0d95a423366c67d737a8857eeeb09954ed1e31bf47a0f4b9e","tgt_lang":"uk","translated":"Не вдалося підключитися до сеансу протягом 30 секунд.","updated_at":"2026-07-15T00:45:31.680Z"} {"cache_key":"4d873333457c611d47bf6cc0a7bf0377b4e16f16d89afd46ae75f09685af8c54","model":"claude-opus-4-8","provider":"anthropic","segment_id":"skillWorkshop.header.useCurrentChatAria","source_path":"ui/src/i18n/locales/uk.ts","src_lang":"en","text":"Use current chat for revision requests","text_hash":"9c551a423ae74aedaaa90e4df9899dbdc02f846d6ee058bf2576a812e2c52119","tgt_lang":"uk","translated":"Використовувати поточний чат для запитів на перегляд","updated_at":"2026-06-16T14:15:40.204Z"} {"cache_key":"4dbbcfd455caf06766a2ef4a8514af8a927369a4eb7dc168205feb3f9261f9dd","model":"claude-opus-4-8","provider":"anthropic","segment_id":"chat.workspaceFiles.session","source_path":"ui/src/i18n/locales/uk.ts","src_lang":"en","text":"Session","text_hash":"6959b4159575d8dd76d9f3bbe2c6437904f861e7860c35abd18deffb1c3425a0","tgt_lang":"uk","translated":"Сесія","updated_at":"2026-06-16T14:16:00.845Z"} {"cache_key":"4dcd52b6ed8d83f04ead04c0d5689fdeebe1bb7969fb11eafb9cb74c412c8049","model":"claude-opus-4-8","provider":"anthropic","segment_id":"agentTools.availableError","source_path":"ui/src/i18n/locales/uk.ts","src_lang":"en","text":"Could not load available tools for this session.","text_hash":"9b6a953e54f271fb698a93b197ec42eec5c2861af23e0f41a2ae37bdb4b0862d","tgt_lang":"uk","translated":"Не вдалося завантажити доступні інструменти для цього сеансу.","updated_at":"2026-07-12T06:47:38.942Z"} @@ -1063,6 +1064,7 @@ {"cache_key":"945e0b243cec2884dfb31c513583914446ac8191a2c473ab2c8beab04e4bc4df","model":"claude-opus-4-8","provider":"anthropic","segment_id":"agents.toolCatalog.profiles.minimal","source_path":"ui/src/i18n/locales/uk.ts","src_lang":"en","text":"Minimal","text_hash":"057b5de48d7b90f123ec28d7e15f65d99b508d6b7cc2958c39472070f0f0f6bb","tgt_lang":"uk","translated":"Мінімальний","updated_at":"2026-07-12T06:45:33.014Z"} {"cache_key":"94a7d93d5cb99e0093782ea249e2bcc3c73572654265b4d9f1e82dd4ce9b6135","model":"claude-opus-4-8","provider":"anthropic","segment_id":"pluginsPage.connectorDescriptions.airtable","source_path":"ui/src/i18n/locales/uk.ts","src_lang":"en","text":"Query and update records, tables, and bases in Airtable.","text_hash":"d50d210d4078f40825578718ef4891ae7c0a793081413c977803486e45bf3e05","tgt_lang":"uk","translated":"Запити та оновлення записів, таблиць і баз у Airtable.","updated_at":"2026-07-12T06:48:23.928Z"} {"cache_key":"94adc5add559fe7aef79b930f6baa31857ba5eb0f2704f0b9bfe2d747102ec05","model":"gpt-5","provider":"openai","segment_id":"claudeSessions.loadMore","source_path":"ui/src/i18n/locales/uk.ts","src_lang":"en","text":"Load more","text_hash":"ac8991ef01019cf55a2426194a05959e0cb886333f1a332ff4f442320d165400","tgt_lang":"uk","translated":"Завантажити ще","updated_at":"2026-07-09T10:01:43.750Z"} +{"cache_key":"94b75d5553e6a9d2cc8bafb1c3c2f7fdec63232198b298d270a8a360747de0ca","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connecting","source_path":"ui/src/i18n/locales/uk.ts","src_lang":"en","text":"Connecting to session…","text_hash":"f1f49121a17a09b93f1601768c015f6fa9ef2056365c5a745837a2be98f71874","tgt_lang":"uk","translated":"Підключення до сеансу…","updated_at":"2026-07-15T00:45:31.680Z"} {"cache_key":"94c154ef5fb3405c144a2a648e6c5cab9740fec55fe29ddfb21b192f48b8643c","model":"claude-opus-4-8","provider":"anthropic","segment_id":"skillStatus.blockedAgentFilter","source_path":"ui/src/i18n/locales/uk.ts","src_lang":"en","text":"blocked by agent filter","text_hash":"b16ca6740fd805900151d4e254d16f5375bb1b52d75117f2cb663ff0f194874d","tgt_lang":"uk","translated":"заблоковано фільтром агента","updated_at":"2026-07-12T06:48:07.313Z"} {"cache_key":"94c63a8726854da419d46b8724eec008bb02c89d5ff53dce92db40c7c4097cbf","model":"claude-opus-4-8","provider":"anthropic","segment_id":"configForm.sections.browser.description","source_path":"ui/src/i18n/locales/uk.ts","src_lang":"en","text":"Browser automation settings","text_hash":"afa2191d7389067d0f6f97e9e4b518059338732b2b74b7457485f04d59abb432","tgt_lang":"uk","translated":"Налаштування автоматизації браузера","updated_at":"2026-07-12T06:45:55.278Z"} {"cache_key":"94c6dba19016d3ebe9f319c4b224c98b661f36f8fe5f5f7a4a87d42f18ef8779","model":"claude-opus-4-8","provider":"anthropic","segment_id":"configView.secretCountPlural","source_path":"ui/src/i18n/locales/uk.ts","src_lang":"en","text":"{count} secrets","text_hash":"c2415948dbe8d1915fd8ebf02fc8fd83144375381f5fe47ad832d16ef71f91d9","tgt_lang":"uk","translated":"{count} секретів","updated_at":"2026-07-12T06:47:22.144Z"} diff --git a/ui/src/i18n/.i18n/vi.meta.json b/ui/src/i18n/.i18n/vi.meta.json index bd38a08dadfc..4955578ef395 100644 --- a/ui/src/i18n/.i18n/vi.meta.json +++ b/ui/src/i18n/.i18n/vi.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-07-14T23:35:51.846Z", + "generatedAt": "2026-07-15T00:45:42.329Z", "locale": "vi", - "model": "claude-opus-4-8", - "provider": "anthropic", - "sourceHash": "85e004373478d946c5002519a35b2c50e1aa220fd2a81f7c1c80ded99bc7dc4e", - "totalKeys": 3353, - "translatedKeys": 3353, + "model": "gpt-5.6-sol", + "provider": "openai", + "sourceHash": "dc6d0f7bdb69960a5b9d54c5743445b89b0689913b3f962829f8490dede56d2c", + "totalKeys": 3355, + "translatedKeys": 3355, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/vi.tm.jsonl b/ui/src/i18n/.i18n/vi.tm.jsonl index 23b40783caff..db51bfde35d0 100644 --- a/ui/src/i18n/.i18n/vi.tm.jsonl +++ b/ui/src/i18n/.i18n/vi.tm.jsonl @@ -179,6 +179,7 @@ {"cache_key":"19f5ca4070cd41065c8df2595365b90afb111f55f46147588f94c235e2d72fd3","model":"claude-opus-4-8","provider":"anthropic","segment_id":"agentTools.availableNowSubtitle","source_path":"ui/src/i18n/locales/vi.ts","src_lang":"en","text":"What this agent can use in the current chat session.","text_hash":"3972b644e6c3212107fc9c119c2897c2c786ca08ee620880f321f92496a5e7d2","tgt_lang":"vi","translated":"Những gì agent này có thể dùng trong phiên chat hiện tại.","updated_at":"2026-07-12T06:54:29.578Z"} {"cache_key":"1a41f539c53d96bd8cce5ba7ed80ba516be0ca849d4be03dc6aa20b55d2aef08","model":"claude-opus-4-8","provider":"anthropic","segment_id":"chat.sideChat.close","source_path":"ui/src/i18n/locales/vi.ts","src_lang":"en","text":"Close side chat","text_hash":"2a1484f044f8f6a1108e2eaaf52849239fa7317678c770fc392907a9a45e6570","tgt_lang":"vi","translated":"Đóng cuộc trò chuyện phụ","updated_at":"2026-07-12T06:56:47.205Z"} {"cache_key":"1a51d38dfdd4de7b887800b71759d7e1e36cd30aa5085fab63e7f5d19d0457be","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.contextUsageApprox","source_path":"ui/src/i18n/locales/vi.ts","src_lang":"en","text":"~{percent}% of context used ({used} / {context} tokens, approximate)","text_hash":"54007077673048ad26ff220971d2598fde53a34c33da9e1eaca0927ce80b2708","tgt_lang":"vi","translated":"~{percent}% ngữ cảnh đã sử dụng ({used} / {context} token, xấp xỉ)","updated_at":"2026-07-09T07:40:49.631Z"} +{"cache_key":"1a854aa352934a5ad162a57740938be7ce3096ec636b749448fc7f926a33f58d","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connectionTimedOut","source_path":"ui/src/i18n/locales/vi.ts","src_lang":"en","text":"Session did not connect within 30 seconds.","text_hash":"38c85973d7e3a6e0d95a423366c67d737a8857eeeb09954ed1e31bf47a0f4b9e","tgt_lang":"vi","translated":"Không thể kết nối với phiên trong vòng 30 giây.","updated_at":"2026-07-15T00:45:42.329Z"} {"cache_key":"1aa299910e591536f311340fbff62b6283139690d7ee4ab20b06ffbdf22f56ef","model":"claude-opus-4-6","provider":"anthropic","segment_id":"configView.autoSaveSaving","source_path":"ui/src/i18n/locales/vi.ts","src_lang":"en","text":"Saving…","text_hash":"23e39291d6135814ed7c936e278974544b0df5fbf0eb0427b6700979b7472a93","tgt_lang":"vi","translated":"Đang lưu…","updated_at":"2026-07-14T12:53:44.445Z"} {"cache_key":"1ad063c1f6fe4c790c5abcdfaf4b7fd0cb9a05c302fd55a13c482c7c7e8bd99a","model":"claude-opus-4-8","provider":"anthropic","segment_id":"nodes.execApprovals.addPattern","source_path":"ui/src/i18n/locales/vi.ts","src_lang":"en","text":"Add pattern","text_hash":"d57e0aac9bfb822d6e9d05908d0f813fa353ba71e49f22d7d497f8f660679a6d","tgt_lang":"vi","translated":"Thêm mẫu","updated_at":"2026-07-12T06:52:08.251Z"} {"cache_key":"1b10d910bc88440f0ad20de866f07465ff2ece293d6bc09cfa80d6c6c342b5f0","model":"claude-sonnet-4-6","provider":"anthropic","segment_id":"cron.suggestions.ideas.watchdog.name","source_path":"ui/src/i18n/locales/vi.ts","src_lang":"en","text":"Night watch","text_hash":"438d99f2923b4b1a22e4538646acb1dae4ca587870ca0ec3cdf19f132d9fb876","tgt_lang":"vi","translated":"Canh đêm","updated_at":"2026-07-11T22:48:46.070Z"} @@ -1684,6 +1685,7 @@ {"cache_key":"edd05826519470eeb55b7450fe76833349a31c44fc1bb2a784868eef282b1021","model":"gpt-5","provider":"openai","segment_id":"usage.daily.compressedScaleHint","source_path":"ui/src/i18n/locales/vi.ts","src_lang":"en","text":"Square-root scale keeps low-usage days visible.","text_hash":"9515e7c6db149c32b64dba95a43e31a61d53dce8f11fe98683b234fb1cfd1920","tgt_lang":"vi","translated":"Thang căn bậc hai giúp các ngày có mức sử dụng thấp vẫn hiển thị rõ.","updated_at":"2026-07-05T20:24:32.108Z"} {"cache_key":"eddbf928c3f0ab0153354d78a785d0f40411ccd1aff9c0169d2f98324cf838e6","model":"claude-opus-4-8","provider":"anthropic","segment_id":"configView.notifications.title","source_path":"ui/src/i18n/locales/vi.ts","src_lang":"en","text":"Push notifications","text_hash":"a1fa4443fe4abe63d6a29c433e4e8f23604c6bda89d6cec04bc261b5021f74e4","tgt_lang":"vi","translated":"Thông báo đẩy","updated_at":"2026-07-12T06:53:48.470Z"} {"cache_key":"ee1d18cc9612222c980abbf669e41904092a1f0f90b59de0577fc53d4da2f346","model":"claude-opus-4-8","provider":"anthropic","segment_id":"configView.appearance.importTheme","source_path":"ui/src/i18n/locales/vi.ts","src_lang":"en","text":"Import theme","text_hash":"f9cc6392852003dbe15afb017131be1e2fd9aa71fe8823956c6dfd221cf077ff","tgt_lang":"vi","translated":"Nhập chủ đề","updated_at":"2026-07-12T06:54:04.680Z"} +{"cache_key":"ee49eab704ecb4f5fe6be9eb499ed40ab9f98f5065c0ec7d65034a4bff2b1d78","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connecting","source_path":"ui/src/i18n/locales/vi.ts","src_lang":"en","text":"Connecting to session…","text_hash":"f1f49121a17a09b93f1601768c015f6fa9ef2056365c5a745837a2be98f71874","tgt_lang":"vi","translated":"Đang kết nối với phiên…","updated_at":"2026-07-15T00:45:42.329Z"} {"cache_key":"ee7841c4730faccf0adba786191c372996ce6bc5e52570e598b39d51d5dda0b7","model":"gpt-5","provider":"openai","segment_id":"terminal.refreshSessions","source_path":"ui/src/i18n/locales/vi.ts","src_lang":"en","text":"Refresh","text_hash":"0e91610117029a62a478b7fa7df0b8598bebe3ab1e192d4b1882e310719c9671","tgt_lang":"vi","translated":"Làm mới","updated_at":"2026-07-09T10:01:43.734Z"} {"cache_key":"ee91b4a15e6f450a5e52f89ec7485bca7b9b4df649e4504c41d94b2de0bd713d","model":"gpt-5.6-sol","provider":"openai","segment_id":"agentScope.label","source_path":"ui/src/i18n/locales/vi.ts","src_lang":"en","text":"Agent scope","text_hash":"0833bc070d2c153692bc25fcc0b30f774cb415efdbf95fe8cd132feab8053698","tgt_lang":"vi","translated":"Phạm vi agent","updated_at":"2026-07-13T11:01:31.660Z"} {"cache_key":"eec2b89fc96c11aea0362c45a5981fab5d5b24a69ee996450047641dc58209bf","model":"claude-opus-4-8","provider":"anthropic","segment_id":"configView.search","source_path":"ui/src/i18n/locales/vi.ts","src_lang":"en","text":"Search settings","text_hash":"255b076dd82855431a4ce9a722d77d41f47755ab11be14fc686fd71415cdeef0","tgt_lang":"vi","translated":"Tìm kiếm cài đặt","updated_at":"2026-07-12T06:54:12.096Z"} diff --git a/ui/src/i18n/.i18n/zh-CN.meta.json b/ui/src/i18n/.i18n/zh-CN.meta.json index c5f84a420a70..517409279107 100644 --- a/ui/src/i18n/.i18n/zh-CN.meta.json +++ b/ui/src/i18n/.i18n/zh-CN.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-07-14T23:35:50.151Z", + "generatedAt": "2026-07-15T00:44:56.393Z", "locale": "zh-CN", - "model": "claude-opus-4-8", - "provider": "anthropic", - "sourceHash": "85e004373478d946c5002519a35b2c50e1aa220fd2a81f7c1c80ded99bc7dc4e", - "totalKeys": 3353, - "translatedKeys": 3353, + "model": "gpt-5.6-sol", + "provider": "openai", + "sourceHash": "dc6d0f7bdb69960a5b9d54c5743445b89b0689913b3f962829f8490dede56d2c", + "totalKeys": 3355, + "translatedKeys": 3355, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/zh-CN.tm.jsonl b/ui/src/i18n/.i18n/zh-CN.tm.jsonl index 3828605c0699..b0105dab236f 100644 --- a/ui/src/i18n/.i18n/zh-CN.tm.jsonl +++ b/ui/src/i18n/.i18n/zh-CN.tm.jsonl @@ -539,6 +539,7 @@ {"cache_key":"4d79c047e70ddfe14896c9fdc902a16a7ce97193f636a6df266b9b73d6f45ff7","model":"gpt-5.6-sol","provider":"openai","segment_id":"skillsPage.enabledNamed","source_path":"ui/src/i18n/locales/zh-CN.ts","src_lang":"en","text":"{name} enabled","text_hash":"52997ef00b23bebf057110d3caba48add4cb6b458b252474b02d22e27074e03b","tgt_lang":"zh-CN","translated":"{name} 已启用","updated_at":"2026-07-13T13:03:49.381Z"} {"cache_key":"4d7abbb2ed890a4fb51fc24f29385216258f198af90add19677a1984f9f8db98","model":"gpt-5.6-sol","provider":"openai","segment_id":"lazyView.staleSubtitle","source_path":"ui/src/i18n/locales/zh-CN.ts","src_lang":"en","text":"OpenClaw was updated in the background. Reload to get the latest panel.","text_hash":"059f63e57629dbb4ea5aa42c5598d95f2889169d76efefa813d0bb148e56648e","tgt_lang":"zh-CN","translated":"OpenClaw 已在后台更新。请重新加载以获取最新面板。","updated_at":"2026-07-13T05:01:09.010Z"} {"cache_key":"4d9cf225c85a5b41525cd887a911893c8140b6f9e46d854c995fa5b7ce9458dc","model":"gpt-5.5","provider":"openai","segment_id":"tasksPage.recentSub","source_path":"ui/src/i18n/locales/zh-CN.ts","src_lang":"en","text":"Latest completed, failed, and cancelled tasks.","text_hash":"44280ebc1ef9ff6ae709f96c5d262b1818e8c580d877fb7f9885344e102eba59","tgt_lang":"zh-CN","translated":"最近已完成、失败和已取消的任务。","updated_at":"2026-07-09T21:53:01.582Z"} +{"cache_key":"4db3df0f1aeb870e48c814d0af8a69b99211475789ac38178a538560fe3c6a5b","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connecting","source_path":"ui/src/i18n/locales/zh-CN.ts","src_lang":"en","text":"Connecting to session…","text_hash":"f1f49121a17a09b93f1601768c015f6fa9ef2056365c5a745837a2be98f71874","tgt_lang":"zh-CN","translated":"正在连接到会话…","updated_at":"2026-07-15T00:44:56.393Z"} {"cache_key":"4db81d22b1656ea356a6a78cbf47e987809783db1c8176c66bcfc49ef2d8b98c","model":"claude-opus-4-8","provider":"anthropic","segment_id":"workboard.detailTask","source_path":"ui/src/i18n/locales/zh-CN.ts","src_lang":"en","text":"Gateway task","text_hash":"6696e7c592238747dd39d7ba000db600a92f843add73ee90b028c72a2dfd37dd","tgt_lang":"zh-CN","translated":"Gateway 任务","updated_at":"2026-06-16T14:12:56.348Z"} {"cache_key":"4dfc1918c5ffd5151839f83a5c20dfae03b360d92592d71feea4f8d8218524a5","model":"claude-opus-4-8","provider":"anthropic","segment_id":"dreaming.wiki.id","source_path":"ui/src/i18n/locales/zh-CN.ts","src_lang":"en","text":"Id:","text_hash":"68d036759794566ce5d3e8d118a575d6b481ceeb79b324e938a3e06614164d32","tgt_lang":"zh-CN","translated":"Id:","updated_at":"2026-07-12T06:29:01.870Z"} {"cache_key":"4e119d261994fb901bc39e184d5995599e011a80b0daf21151309234acff1cd0","model":"claude-opus-4-8","provider":"anthropic","segment_id":"chat.messages.deleteMessage","source_path":"ui/src/i18n/locales/zh-CN.ts","src_lang":"en","text":"Delete message","text_hash":"87e7176a7ce700af0c4afe34a922d1401eec9611ffc5a99bdbf032e8654c2e9b","tgt_lang":"zh-CN","translated":"删除消息","updated_at":"2026-07-12T06:29:11.505Z"} @@ -1751,6 +1752,7 @@ {"cache_key":"fe8e79f0b42cbf50f099424b829d10e99869c3f28205191e9c19f355ffbd3694","model":"claude-opus-4-8","provider":"anthropic","segment_id":"channels.telegram.title","source_path":"ui/src/i18n/locales/zh-CN.ts","src_lang":"en","text":"Telegram","text_hash":"acdd1e734125f341604c0efbabdcc4c4b0597e8f6235d66c2445edd1812838c1","tgt_lang":"zh-CN","translated":"Telegram","updated_at":"2026-07-12T06:25:05.607Z"} {"cache_key":"fe9033afedace899e183c9ba33ee53312da244aaab70ce62a5d929592512c68c","model":"claude-opus-4-8","provider":"anthropic","segment_id":"skillWorkshop.revision.description","source_path":"ui/src/i18n/locales/zh-CN.ts","src_lang":"en","text":"Tell the agent what should change. The proposal stays pending and the workshop will create a revised version.","text_hash":"c9eb5236c5b73f0eec0f11927862295a2259dff5b052b04f0b01d846b0c97b22","tgt_lang":"zh-CN","translated":"告诉智能体需要更改什么。提案保持待处理状态,工作坊将创建一个修订版本。","updated_at":"2026-07-12T06:28:22.526Z"} {"cache_key":"fe94db7417cccf382133f998704fb44c831bd8eca497cc04ab4b033e7a95ec6f","model":"claude-opus-4-8","provider":"anthropic","segment_id":"nodes.binding.useDefault","source_path":"ui/src/i18n/locales/zh-CN.ts","src_lang":"en","text":"Use default","text_hash":"a769cedc799260e81ce23b3fc1533ae48004f6ab25f0dffcc783d95fea378e19","tgt_lang":"zh-CN","translated":"使用默认值","updated_at":"2026-07-12T06:25:12.243Z"} +{"cache_key":"febc50ec52f311860fa1de9531c4f3d9fa703ec4d7ff26d816bb9e7a1d836b2d","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connectionTimedOut","source_path":"ui/src/i18n/locales/zh-CN.ts","src_lang":"en","text":"Session did not connect within 30 seconds.","text_hash":"38c85973d7e3a6e0d95a423366c67d737a8857eeeb09954ed1e31bf47a0f4b9e","tgt_lang":"zh-CN","translated":"未能在 30 秒内连接到会话。","updated_at":"2026-07-15T00:44:56.393Z"} {"cache_key":"fec08a3e695a91329016c91ad131e996fe049d42823aca666ed87e1cee0dd616","model":"claude-opus-4-8","provider":"anthropic","segment_id":"chat.modelControls.default","source_path":"ui/src/i18n/locales/zh-CN.ts","src_lang":"en","text":"Default","text_hash":"21b111cbfe6e8fca2d181c43f53ad548b22e38aca955b9824706a504b0a07a2d","tgt_lang":"zh-CN","translated":"默认","updated_at":"2026-07-06T20:20:02.809Z"} {"cache_key":"ff22c687393d76fce445196c6bcd52e2ee1017b41441d1aae57cadca995c9ed9","model":"claude-opus-4-8","provider":"anthropic","segment_id":"skillStatus.blockedAllowlist","source_path":"ui/src/i18n/locales/zh-CN.ts","src_lang":"en","text":"blocked by allowlist","text_hash":"5ef37759cb09792554c00666d262cfe812ae35a592a69c2c7b705be8687bca4b","tgt_lang":"zh-CN","translated":"被允许列表阻止","updated_at":"2026-07-12T06:27:47.508Z"} {"cache_key":"ff2abac63994ff6a2fd30e34bb286c86c2fef934c23c3a6d39c3e5ce8f3f02d9","model":"claude-opus-4-8","provider":"anthropic","segment_id":"chat.messages.activity","source_path":"ui/src/i18n/locales/zh-CN.ts","src_lang":"en","text":"Activity","text_hash":"38da1505ca8373288489495101b14f24ac95078f520ad64df18272aa6054f750","tgt_lang":"zh-CN","translated":"活动","updated_at":"2026-07-12T06:29:11.505Z"} diff --git a/ui/src/i18n/.i18n/zh-TW.meta.json b/ui/src/i18n/.i18n/zh-TW.meta.json index 71ed6c893ebf..1ed49061bfe3 100644 --- a/ui/src/i18n/.i18n/zh-TW.meta.json +++ b/ui/src/i18n/.i18n/zh-TW.meta.json @@ -1,11 +1,11 @@ { "fallbackKeys": [], - "generatedAt": "2026-07-14T23:35:50.272Z", + "generatedAt": "2026-07-15T00:45:03.008Z", "locale": "zh-TW", - "model": "claude-opus-4-8", - "provider": "anthropic", - "sourceHash": "85e004373478d946c5002519a35b2c50e1aa220fd2a81f7c1c80ded99bc7dc4e", - "totalKeys": 3353, - "translatedKeys": 3353, + "model": "gpt-5.6-sol", + "provider": "openai", + "sourceHash": "dc6d0f7bdb69960a5b9d54c5743445b89b0689913b3f962829f8490dede56d2c", + "totalKeys": 3355, + "translatedKeys": 3355, "workflow": 1 } diff --git a/ui/src/i18n/.i18n/zh-TW.tm.jsonl b/ui/src/i18n/.i18n/zh-TW.tm.jsonl index ebe76e27340f..7462ce3baba3 100644 --- a/ui/src/i18n/.i18n/zh-TW.tm.jsonl +++ b/ui/src/i18n/.i18n/zh-TW.tm.jsonl @@ -1010,6 +1010,7 @@ {"cache_key":"93635a60e63e0db12fee2ef6ec969903a6d72978e64ff50b5255464a257af7cd","model":"gpt-5.6-sol","provider":"openai","segment_id":"chat.progressLabels.molting","source_path":"ui/src/i18n/locales/zh-TW.ts","src_lang":"en","text":"Molting","text_hash":"fbd2ae2ba1642ca5ffd2a92167bfc7aca3669ae73d6a592cede7ddae67bb55c3","tgt_lang":"zh-TW","translated":"蛻殼中","updated_at":"2026-07-14T04:52:54.602Z"} {"cache_key":"93bd1734efd4f15a634a9c2c98bb1556a01f5ef5f3c63cb7e2d20efa3b5c665d","model":"claude-opus-4-6","provider":"anthropic","segment_id":"configView.applyBannerAction","source_path":"ui/src/i18n/locales/zh-TW.ts","src_lang":"en","text":"Restart & apply","text_hash":"31982092d02b6e51f60cd993f5377f10ce2f245aabe107c2380150342e0374fe","tgt_lang":"zh-TW","translated":"重新啟動並套用","updated_at":"2026-07-14T12:52:18.608Z"} {"cache_key":"93c80b85b65138c8af8ae94caf33c3ab88652cc6823dc0b2d2b66e368ad346f9","model":"claude-opus-4-8","provider":"anthropic","segment_id":"skillWorkshop.detail.edited","source_path":"ui/src/i18n/locales/zh-TW.ts","src_lang":"en","text":"Edited {time}","text_hash":"51c9630775198996ab6bbb54de3215bb774c09198d49201b023b1119ddefa1ed","tgt_lang":"zh-TW","translated":"已編輯 {time}","updated_at":"2026-07-12T06:29:14.670Z"} +{"cache_key":"93d6d39ad5b7cf6685f176435ba75590365873a97486576680dfe8cabd5fac7d","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connectionTimedOut","source_path":"ui/src/i18n/locales/zh-TW.ts","src_lang":"en","text":"Session did not connect within 30 seconds.","text_hash":"38c85973d7e3a6e0d95a423366c67d737a8857eeeb09954ed1e31bf47a0f4b9e","tgt_lang":"zh-TW","translated":"工作階段未能在 30 秒內連線。","updated_at":"2026-07-15T00:45:03.008Z"} {"cache_key":"94199e4653bb1f7a74390f940cb818546854b12fd00b1d56a63bd45a9ab6d2ec","model":"claude-opus-4-8","provider":"anthropic","segment_id":"configView.dismissWarning","source_path":"ui/src/i18n/locales/zh-TW.ts","src_lang":"en","text":"Don't remind again","text_hash":"f7f8a139c18f0c904c95a5aad9adc39b99d3e115b671dfc09c44f2d6970d05dc","tgt_lang":"zh-TW","translated":"不再提醒","updated_at":"2026-07-12T06:27:55.841Z"} {"cache_key":"9453b56e509d26313c25cf74cec0e9bcbbbc9c0163f9e933e9eb0d382d81530e","model":"claude-opus-4-8","provider":"anthropic","segment_id":"workboard.dependencies","source_path":"ui/src/i18n/locales/zh-TW.ts","src_lang":"en","text":"Dependencies","text_hash":"2e41b118eb209c139f2bcbf690486f6e1509ab978aa96feb053877a70a1a5a09","tgt_lang":"zh-TW","translated":"相依項目","updated_at":"2026-06-16T14:13:10.175Z"} {"cache_key":"946f3c34437ac039723452352b26a4d2f001fbcd5e758c29275ea53c9dba74ef","model":"claude-opus-4-8","provider":"anthropic","segment_id":"cron.form.failureAlertAfterHelp","source_path":"ui/src/i18n/locales/zh-TW.ts","src_lang":"en","text":"Consecutive errors before alerting.","text_hash":"cfab25f9233c6418534dc02f551be9afb62045e6c956ddb44340ce131ede1614","tgt_lang":"zh-TW","translated":"警示前的連續錯誤次數。","updated_at":"2026-07-12T06:30:38.985Z"} @@ -1637,6 +1638,7 @@ {"cache_key":"e6fb37f69ea765d94a29762a32191317701dfbbe37f1967743c995c89a3d5f76","model":"gpt-5.6-sol","provider":"openai","segment_id":"configForm.sections.plugins.label","source_path":"ui/src/i18n/locales/zh-TW.ts","src_lang":"en","text":"Plugins","text_hash":"9514b7ff4860ead73491768e45cce0ce20e6e3473a7b272e496c43c875d80ac5","tgt_lang":"zh-TW","translated":"外掛程式","updated_at":"2026-07-12T00:07:58.605Z"} {"cache_key":"e6fb55b186b44041e533a3b7dcd2351664fbd7275e0181262e8f8fa7d439289d","model":"claude-opus-4-8","provider":"anthropic","segment_id":"agents.toolCatalog.descriptions.browser","source_path":"ui/src/i18n/locales/zh-TW.ts","src_lang":"en","text":"Control web browser","text_hash":"4c99358b099daf6172352f111c30fc6e71b8ba519bbeab6d45107a902179512f","tgt_lang":"zh-TW","translated":"控制網頁瀏覽器","updated_at":"2026-07-12T06:26:21.254Z"} {"cache_key":"e7586e1763a46aa0c60aba4d2041d7085a725af507ebefd3bc4cbf507a669c24","model":"claude-opus-4-6","provider":"anthropic","segment_id":"chat.composer.realtimeTalkRequiresMicrophone","source_path":"ui/src/i18n/locales/zh-TW.ts","src_lang":"en","text":"Realtime voice input requires browser microphone access.","text_hash":"a70d86265802e30aac70647cde33c62c5386741941d2ea9f54636a0242109dcd","tgt_lang":"zh-TW","translated":"即時語音輸入需要瀏覽器麥克風存取權限。","updated_at":"2026-07-06T22:41:51.388Z"} +{"cache_key":"e764e316ef136b4f93cfa22761ec379f44d716b9308edfd37a0391ccf17ed221","model":"gpt-5.6-sol","provider":"openai","segment_id":"terminal.connecting","source_path":"ui/src/i18n/locales/zh-TW.ts","src_lang":"en","text":"Connecting to session…","text_hash":"f1f49121a17a09b93f1601768c015f6fa9ef2056365c5a745837a2be98f71874","tgt_lang":"zh-TW","translated":"正在連線至工作階段…","updated_at":"2026-07-15T00:45:03.008Z"} {"cache_key":"e771be91505216368710f3f2007039e854a0c6f0d9d753cc4b2a8b8adbd78992","model":"claude-opus-4-8","provider":"anthropic","segment_id":"quickSettings.personal.clearAvatar","source_path":"ui/src/i18n/locales/zh-TW.ts","src_lang":"en","text":"Clear avatar","text_hash":"13bf7b9f89587bcbc16db609df7343b1fbb40330635edb276881c1ce235fbcb8","tgt_lang":"zh-TW","translated":"清除頭像","updated_at":"2026-07-12T06:27:14.052Z"} {"cache_key":"e7e2cebfcb556a07858fd9ff39d0c64b4ff0c3828f3a320ac488b0530c6c8ba2","model":"claude-opus-4-8","provider":"anthropic","segment_id":"configView.sections.canvasHost","source_path":"ui/src/i18n/locales/zh-TW.ts","src_lang":"en","text":"Canvas Host","text_hash":"53e6ca49326ab836d65a66fb09ce46caa85dfe68616be5776cb8d3096bda2e92","tgt_lang":"zh-TW","translated":"Canvas Host","updated_at":"2026-07-12T06:27:29.739Z"} {"cache_key":"e7eac3f0b14c54c128dc7e57f622844250263b814fb5eddcd7f030f34a3177d2","model":"claude-opus-4-8","provider":"anthropic","segment_id":"configForm.schemaUnavailable","source_path":"ui/src/i18n/locales/zh-TW.ts","src_lang":"en","text":"Schema unavailable.","text_hash":"5ff36b82d87f7973ac44f95373bbcbf058d3d24977fbbe31b3c594ce8c231ea8","tgt_lang":"zh-TW","translated":"無法使用結構描述。","updated_at":"2026-07-12T06:26:35.708Z"} diff --git a/ui/src/i18n/locales/ar.ts b/ui/src/i18n/locales/ar.ts index bc0ba448fd25..69c933ebb6e2 100644 --- a/ui/src/i18n/locales/ar.ts +++ b/ui/src/i18n/locales/ar.ts @@ -1602,6 +1602,8 @@ export const ar: TranslationMap = { attachSession: "الاتصال بالجلسة", attachFailed: "تعذّر الاتصال بجلسة الطرفية", starting: "Starting terminal…", + connecting: "جارٍ الاتصال بالجلسة…", + connectionTimedOut: "تعذّر الاتصال بالجلسة خلال 30 ثانية.", tabLabel: "Shell {n}", tabHint: "{agent} · {cwd}", exited: "exited", diff --git a/ui/src/i18n/locales/de.ts b/ui/src/i18n/locales/de.ts index ebe4e75ec841..30e6b276ff33 100644 --- a/ui/src/i18n/locales/de.ts +++ b/ui/src/i18n/locales/de.ts @@ -1624,6 +1624,9 @@ export const de: TranslationMap = { attachSession: "Sitzung verbinden", attachFailed: "Terminalsitzung konnte nicht verbunden werden", starting: "Starting terminal…", + connecting: "Verbindung zur Sitzung wird hergestellt…", + connectionTimedOut: + "Die Verbindung zur Sitzung wurde nicht innerhalb von 30 Sekunden hergestellt.", tabLabel: "Shell {n}", tabHint: "{agent} · {cwd}", exited: "exited", diff --git a/ui/src/i18n/locales/en.ts b/ui/src/i18n/locales/en.ts index 669c5165b88d..da8eab55f6c8 100644 --- a/ui/src/i18n/locales/en.ts +++ b/ui/src/i18n/locales/en.ts @@ -1575,6 +1575,8 @@ export const en: TranslationMap = { attachSession: "Attach session", attachFailed: "Could not attach terminal session", starting: "Starting terminal…", + connecting: "Connecting to session…", + connectionTimedOut: "Session did not connect within 30 seconds.", tabLabel: "shell {n}", tabHint: "{agent} · {cwd}", exited: "exited", diff --git a/ui/src/i18n/locales/es.ts b/ui/src/i18n/locales/es.ts index 0b114e55246e..7cf8a78d1666 100644 --- a/ui/src/i18n/locales/es.ts +++ b/ui/src/i18n/locales/es.ts @@ -1621,6 +1621,8 @@ export const es: TranslationMap = { attachSession: "Conectar sesión", attachFailed: "No se pudo conectar la sesión de terminal", starting: "Starting terminal…", + connecting: "Conectando con la sesión…", + connectionTimedOut: "La sesión no se conectó en un plazo de 30 segundos.", tabLabel: "Shell {n}", tabHint: "{agent} · {cwd}", exited: "exited", diff --git a/ui/src/i18n/locales/fa.ts b/ui/src/i18n/locales/fa.ts index 6e88ef06890a..b3c2a1266809 100644 --- a/ui/src/i18n/locales/fa.ts +++ b/ui/src/i18n/locales/fa.ts @@ -1611,6 +1611,8 @@ export const fa: TranslationMap = { attachSession: "اتصال به نشست", attachFailed: "اتصال به نشست ترمینال ممکن نشد", starting: "Starting terminal…", + connecting: "در حال اتصال به نشست…", + connectionTimedOut: "اتصال به نشست ظرف ۳۰ ثانیه برقرار نشد.", tabLabel: "Shell {n}", tabHint: "{agent} · {cwd}", exited: "exited", diff --git a/ui/src/i18n/locales/fr.ts b/ui/src/i18n/locales/fr.ts index 3cf9337cfe43..5c5dbed32187 100644 --- a/ui/src/i18n/locales/fr.ts +++ b/ui/src/i18n/locales/fr.ts @@ -1631,6 +1631,8 @@ export const fr: TranslationMap = { attachSession: "Se connecter à la session", attachFailed: "Impossible de se connecter à la session de terminal", starting: "Starting terminal…", + connecting: "Connexion à la session…", + connectionTimedOut: "La connexion à la session n’a pas été établie dans les 30 secondes.", tabLabel: "Shell {n}", tabHint: "{agent} · {cwd}", exited: "exited", diff --git a/ui/src/i18n/locales/hi.ts b/ui/src/i18n/locales/hi.ts index ba8f2e4b23e0..2df9b5c261a4 100644 --- a/ui/src/i18n/locales/hi.ts +++ b/ui/src/i18n/locales/hi.ts @@ -1602,6 +1602,8 @@ export const hi: TranslationMap = { attachSession: "सत्र संलग्न करें", attachFailed: "टर्मिनल सत्र संलग्न नहीं किया जा सका", starting: "Starting terminal…", + connecting: "सत्र से कनेक्ट किया जा रहा है…", + connectionTimedOut: "सत्र 30 सेकंड के भीतर कनेक्ट नहीं हुआ।", tabLabel: "Shell {n}", tabHint: "{agent} · {cwd}", exited: "exited", diff --git a/ui/src/i18n/locales/id.ts b/ui/src/i18n/locales/id.ts index 29d1f89831c2..2db4d18a9759 100644 --- a/ui/src/i18n/locales/id.ts +++ b/ui/src/i18n/locales/id.ts @@ -1612,6 +1612,8 @@ export const id: TranslationMap = { attachSession: "Hubungkan sesi", attachFailed: "Tidak dapat menghubungkan sesi terminal", starting: "Starting terminal…", + connecting: "Menghubungkan ke sesi…", + connectionTimedOut: "Sesi tidak terhubung dalam waktu 30 detik.", tabLabel: "Shell {n}", tabHint: "{agent} · {cwd}", exited: "exited", diff --git a/ui/src/i18n/locales/it.ts b/ui/src/i18n/locales/it.ts index d1f5d4f885fb..335a37d145b2 100644 --- a/ui/src/i18n/locales/it.ts +++ b/ui/src/i18n/locales/it.ts @@ -1627,6 +1627,8 @@ export const it: TranslationMap = { attachSession: "Collega sessione", attachFailed: "Impossibile collegare la sessione del terminale", starting: "Starting terminal…", + connecting: "Connessione alla sessione…", + connectionTimedOut: "La sessione non si è connessa entro 30 secondi.", tabLabel: "Shell {n}", tabHint: "{agent} · {cwd}", exited: "exited", diff --git a/ui/src/i18n/locales/ja-JP.ts b/ui/src/i18n/locales/ja-JP.ts index 9ff18fba650e..3b94b7a6e1ec 100644 --- a/ui/src/i18n/locales/ja-JP.ts +++ b/ui/src/i18n/locales/ja-JP.ts @@ -1620,6 +1620,8 @@ export const ja_JP: TranslationMap = { attachSession: "セッションに接続", attachFailed: "ターミナルセッションに接続できませんでした", starting: "Starting terminal…", + connecting: "セッションに接続しています…", + connectionTimedOut: "30秒以内にセッションに接続できませんでした。", tabLabel: "Shell {n}", tabHint: "{agent} · {cwd}", exited: "exited", diff --git a/ui/src/i18n/locales/ko.ts b/ui/src/i18n/locales/ko.ts index 564682434d55..f9a1934017c5 100644 --- a/ui/src/i18n/locales/ko.ts +++ b/ui/src/i18n/locales/ko.ts @@ -1606,6 +1606,8 @@ export const ko: TranslationMap = { attachSession: "세션 연결", attachFailed: "터미널 세션을 연결할 수 없습니다", starting: "Starting terminal…", + connecting: "세션에 연결하는 중…", + connectionTimedOut: "30초 이내에 세션에 연결하지 못했습니다.", tabLabel: "Shell {n}", tabHint: "{agent} · {cwd}", exited: "exited", diff --git a/ui/src/i18n/locales/nl.ts b/ui/src/i18n/locales/nl.ts index bf82de43c648..6c9327a74cd7 100644 --- a/ui/src/i18n/locales/nl.ts +++ b/ui/src/i18n/locales/nl.ts @@ -1620,6 +1620,8 @@ export const nl: TranslationMap = { attachSession: "Sessie koppelen", attachFailed: "Kan terminalsessie niet koppelen", starting: "Starting terminal…", + connecting: "Verbinding maken met sessie…", + connectionTimedOut: "Er kon niet binnen 30 seconden verbinding worden gemaakt met de sessie.", tabLabel: "Shell {n}", tabHint: "{agent} · {cwd}", exited: "exited", diff --git a/ui/src/i18n/locales/pl.ts b/ui/src/i18n/locales/pl.ts index de914584c715..ffcfac835ebf 100644 --- a/ui/src/i18n/locales/pl.ts +++ b/ui/src/i18n/locales/pl.ts @@ -1620,6 +1620,8 @@ export const pl: TranslationMap = { attachSession: "Podłącz sesję", attachFailed: "Nie udało się podłączyć sesji terminala", starting: "Starting terminal…", + connecting: "Łączenie z sesją…", + connectionTimedOut: "Nie udało się połączyć z sesją w ciągu 30 sekund.", tabLabel: "Shell {n}", tabHint: "{agent} · {cwd}", exited: "exited", diff --git a/ui/src/i18n/locales/pt-BR.ts b/ui/src/i18n/locales/pt-BR.ts index f3cb78e73ec8..2fe896153633 100644 --- a/ui/src/i18n/locales/pt-BR.ts +++ b/ui/src/i18n/locales/pt-BR.ts @@ -1615,6 +1615,8 @@ export const pt_BR: TranslationMap = { attachSession: "Conectar à sessão", attachFailed: "Não foi possível conectar à sessão de terminal", starting: "Starting terminal…", + connecting: "Conectando à sessão…", + connectionTimedOut: "A sessão não foi conectada em até 30 segundos.", tabLabel: "Shell {n}", tabHint: "{agent} · {cwd}", exited: "exited", diff --git a/ui/src/i18n/locales/ru.ts b/ui/src/i18n/locales/ru.ts index e0062ff28f19..7406732c7f62 100644 --- a/ui/src/i18n/locales/ru.ts +++ b/ui/src/i18n/locales/ru.ts @@ -1623,6 +1623,8 @@ export const ru: TranslationMap = { attachSession: "Подключиться к сеансу", attachFailed: "Не удалось подключиться к сеансу терминала", starting: "Starting terminal…", + connecting: "Подключение к сеансу…", + connectionTimedOut: "Не удалось подключиться к сеансу в течение 30 секунд.", tabLabel: "Shell {n}", tabHint: "{agent} · {cwd}", exited: "exited", diff --git a/ui/src/i18n/locales/th.ts b/ui/src/i18n/locales/th.ts index d241639be95d..686efb388be2 100644 --- a/ui/src/i18n/locales/th.ts +++ b/ui/src/i18n/locales/th.ts @@ -1593,6 +1593,8 @@ export const th: TranslationMap = { attachSession: "เชื่อมต่อเซสชัน", attachFailed: "ไม่สามารถเชื่อมต่อเซสชันเทอร์มินัลได้", starting: "Starting terminal…", + connecting: "กำลังเชื่อมต่อกับเซสชัน…", + connectionTimedOut: "ไม่สามารถเชื่อมต่อเซสชันได้ภายใน 30 วินาที", tabLabel: "Shell {n}", tabHint: "{agent} · {cwd}", exited: "exited", diff --git a/ui/src/i18n/locales/tr.ts b/ui/src/i18n/locales/tr.ts index 341207214d81..00635cffa5f0 100644 --- a/ui/src/i18n/locales/tr.ts +++ b/ui/src/i18n/locales/tr.ts @@ -1619,6 +1619,8 @@ export const tr: TranslationMap = { attachSession: "Oturuma bağlan", attachFailed: "Terminal oturumuna bağlanılamadı", starting: "Starting terminal…", + connecting: "Oturuma bağlanılıyor…", + connectionTimedOut: "Oturum bağlantısı 30 saniye içinde kurulamadı.", tabLabel: "Shell {n}", tabHint: "{agent} · {cwd}", exited: "exited", diff --git a/ui/src/i18n/locales/uk.ts b/ui/src/i18n/locales/uk.ts index 276742e27ece..1476c468b666 100644 --- a/ui/src/i18n/locales/uk.ts +++ b/ui/src/i18n/locales/uk.ts @@ -1619,6 +1619,8 @@ export const uk: TranslationMap = { attachSession: "Підключитися до сеансу", attachFailed: "Не вдалося підключитися до сеансу термінала", starting: "Starting terminal…", + connecting: "Підключення до сеансу…", + connectionTimedOut: "Не вдалося підключитися до сеансу протягом 30 секунд.", tabLabel: "Shell {n}", tabHint: "{agent} · {cwd}", exited: "exited", diff --git a/ui/src/i18n/locales/vi.ts b/ui/src/i18n/locales/vi.ts index 9072922b4874..8f41748a258c 100644 --- a/ui/src/i18n/locales/vi.ts +++ b/ui/src/i18n/locales/vi.ts @@ -1613,6 +1613,8 @@ export const vi: TranslationMap = { attachSession: "Kết nối phiên", attachFailed: "Không thể kết nối phiên terminal", starting: "Starting terminal…", + connecting: "Đang kết nối với phiên…", + connectionTimedOut: "Không thể kết nối với phiên trong vòng 30 giây.", tabLabel: "Shell {n}", tabHint: "{agent} · {cwd}", exited: "exited", diff --git a/ui/src/i18n/locales/zh-CN.ts b/ui/src/i18n/locales/zh-CN.ts index 32f1a22a43c5..6cb2eb4b68fe 100644 --- a/ui/src/i18n/locales/zh-CN.ts +++ b/ui/src/i18n/locales/zh-CN.ts @@ -1583,6 +1583,8 @@ export const zh_CN: TranslationMap = { attachSession: "连接会话", attachFailed: "无法连接终端会话", starting: "Starting terminal…", + connecting: "正在连接到会话…", + connectionTimedOut: "未能在 30 秒内连接到会话。", tabLabel: "Shell {n}", tabHint: "{agent} · {cwd}", exited: "exited", diff --git a/ui/src/i18n/locales/zh-TW.ts b/ui/src/i18n/locales/zh-TW.ts index cd0d37f5872f..b88e38983b7f 100644 --- a/ui/src/i18n/locales/zh-TW.ts +++ b/ui/src/i18n/locales/zh-TW.ts @@ -1584,6 +1584,8 @@ export const zh_TW: TranslationMap = { attachSession: "連接工作階段", attachFailed: "無法連接終端機工作階段", starting: "Starting terminal…", + connecting: "正在連線至工作階段…", + connectionTimedOut: "工作階段未能在 30 秒內連線。", tabLabel: "Shell {n}", tabHint: "{agent} · {cwd}", exited: "exited",