@tunnelbox/claude-code 0.1.1 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -4,16 +4,6 @@
4
4
  import { resolve as resolve2 } from "node:path";
5
5
  import { fileURLToPath as fileURLToPath2 } from "node:url";
6
6
 
7
- // src/bridge.ts
8
- import { randomBytes as randomBytes2 } from "node:crypto";
9
- import { existsSync as existsSync2, statSync } from "node:fs";
10
- import { hostname } from "node:os";
11
- import { isAbsolute, join as join2, resolve } from "node:path";
12
- import { fileURLToPath } from "node:url";
13
- import {
14
- query
15
- } from "@anthropic-ai/claude-agent-sdk";
16
-
17
7
  // ../core/src/types.ts
18
8
  function envelope(type, payload, id) {
19
9
  return { id, type, payload, ts: Date.now() };
@@ -108,9 +98,1101 @@ import { randomBytes } from "node:crypto";
108
98
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
109
99
  import { homedir } from "node:os";
110
100
  import { dirname, join } from "node:path";
111
- var DEFAULT_RELAY_URL = process.env.TUNNELBOX_RELAY_URL || (process.env.NODE_ENV === "production" ? "wss://chat.wxngrok.com" : "ws://127.0.0.1:8080");
101
+
102
+ // ../core/locales/zh-CN.json
103
+ var zh_CN_default = {
104
+ "agent.err.createSession": "\u521B\u5EFA\u4F1A\u8BDD\u5931\u8D25",
105
+ "agent.err.httpClient": "\u65E0\u6CD5\u8BBF\u95EE opencode HTTP client",
106
+ "agent.err.unknownPerm": "\u672A\u77E5\u7684\u6743\u9650\u8BF7\u6C42: {id}",
107
+ "agent.err.unknownQuestion": "\u672A\u77E5\u7684\u63D0\u95EE\u8BF7\u6C42: {id}",
108
+ "agent.log.fallback404": "\u56DE\u5305\u9996\u9009\u7AEF\u70B9 404\uFF0C\u56DE\u9000\u53E6\u4E00\u7AEF\u70B9: {message}",
109
+ "agent.log.gotPermReply": "\u6536\u5230\u624B\u673A\u6743\u9650\u56DE\u590D (id={id} {reply} {version})",
110
+ "agent.log.gotQuestionReply": "\u6536\u5230\u624B\u673A\u63D0\u95EE\u56DE\u590D (id={id} {status} {version})",
111
+ "agent.log.handleFailed": "\u5904\u7406 {type} \u5931\u8D25: {message}",
112
+ "agent.log.httpOk": "opencode HTTP {status} {path}",
113
+ "agent.log.pendingResent": "session.pending: \u8865\u53D1 {session} \u79EF\u538B\u8BF7\u6C42 {count} \u6761",
114
+ "agent.log.unhandledType": "\u672A\u5904\u7406\u7684\u6D88\u606F\u7C7B\u578B: {type}",
115
+ "events.log.fwdPermission": "\u8F6C\u53D1\u6743\u9650\u8BF7\u6C42\u5230\u624B\u673A (id={id} tool={tool} {version})",
116
+ "events.log.permissionReplied": "opencode \u56DE\u6267 permission.replied (id={id} {response})",
117
+ "events.log.questionRejected": "opencode \u56DE\u6267 question.rejected (id={id})",
118
+ "events.log.questionReplied": "opencode \u56DE\u6267 question.replied (id={id})",
119
+ "index.error.connectRelay": "\u65E0\u6CD5\u8FDE\u63A5\u4E2D\u7EE7\uFF0C\u8BF7\u68C0\u67E5\u4E2D\u7EE7\u5730\u5740\u540E\u91CD\u8BD5",
120
+ "index.error.pairTimeout": "\u7B49\u5F85\u914D\u5BF9\u7801\u8D85\u65F6\uFF08\u4E2D\u7EE7\u672A\u54CD\u5E94\uFF09",
121
+ "index.log.claimed": "\u5DF2\u7ED1\u5B9A\u5230\u624B\u673A\u8D26\u53F7\uFF0C\u4E4B\u540E\u624B\u673A\u4ECE\u300C\u6211\u7684\u7535\u8111\u300D\u76F4\u63A5\u8FDE\u63A5\uFF0C\u65E0\u9700\u518D\u626B\u7801",
122
+ "index.log.connectedRelay": "\u5DF2\u8FDE\u63A5\u4E2D\u7EE7 {url}",
123
+ "index.log.disconnected": "\u4E0E\u4E2D\u7EE7\u65AD\u5F00\uFF0C\u6B63\u5728\u81EA\u52A8\u91CD\u8FDE\u2026",
124
+ "index.log.exit": "\u5DF2\u9000\u51FA",
125
+ "index.log.pairCode": "\u751F\u6210\u914D\u5BF9\u7801: {code}",
126
+ "index.log.permissionToPhone": "\u6743\u9650\u8BF7\u6C42 \u2192 \u624B\u673A\u5BA1\u6279 (id={id} tool={tool})",
127
+ "index.log.permissionTuiFallback": "app \u79BB\u7EBF\uFF0C\u6743\u9650\u8BF7\u6C42\u7531 opencode TUI \u515C\u5E95 (id={id} tool={tool})",
128
+ "index.log.relayError": "\u4E2D\u7EE7\u9519\u8BEF: {msg}",
129
+ "index.log.resetIdentity": "\u88AB\u8D26\u53F7\u89E3\u7ED1\uFF0C\u6B63\u5728\u91CD\u7F6E\u8EAB\u4EFD\u5E76\u91CD\u65B0\u751F\u6210\u914D\u5BF9\u7801\u2026",
130
+ "index.log.unknown": "\u672A\u77E5",
131
+ "install.codeLine": "\u914D\u5BF9\u7801: {code}",
132
+ "install.commandInstalled": "\u5DF2\u5B89\u88C5\u547D\u4EE4: {path}",
133
+ "install.done": "\u5B8C\u6210\u3002\u5DF2\u5C1D\u8BD5\u8FDE\u63A5\u4E2D\u7EE7\u8F93\u51FA\u672C\u673A\u914D\u5BF9\u4FE1\u606F\uFF1A",
134
+ "install.failConnect": "\uFF08\u672A\u80FD\u5373\u65F6\u8FDE\u63A5\u4E2D\u7EE7\u6253\u5370\u914D\u5BF9\u4FE1\u606F\u2014\u2014\u4E2D\u7EE7\u4E0D\u53EF\u8FBE\u6216\u5F53\u524D Node \u8FC7\u65E7\u3002",
135
+ "install.failConnectHint": "\u91CD\u542F opencode \u540E\u53EF\u7528 --print-logs \u67E5\u770B\u7EC8\u7AEF\u4E8C\u7EF4\u7801\uFF0C\u6216\u5728 opencode \u5185\u8F93\u5165\u201C\u663E\u793A\u624B\u673A\u914D\u5BF9\u7801\u201D\u6216 /pair\u3002\uFF09",
136
+ "install.hintFresh": "\u63D0\u793A\uFF1A\u914D\u5BF9\u7801\u4E3A\u4E00\u6B21\u6027\uFF0C\u7EA6 10 \u5206\u949F\u6709\u6548\uFF1B\u82E5\u7A0D\u540E\u624D\u542F\u52A8 opencode\uFF0C\u4EE5\u542F\u52A8\u65F6\u7EC8\u7AEF\u6253\u5370\u6216 /pair \u53D6\u5F97\u7684\u6700\u65B0\u914D\u5BF9\u4FE1\u606F\u4E3A\u51C6\u3002",
137
+ "install.linkLine": "\u914D\u5BF9\u94FE\u63A5: {link}",
138
+ "install.pluginInstalled": "\u5DF2\u5B89\u88C5\u63D2\u4EF6: {path}",
139
+ "install.qrUnavailable": "\uFF08\u4E8C\u7EF4\u7801\u6E32\u67D3\u4E0D\u53EF\u7528\uFF0C\u8BF7\u4F7F\u7528\u4E0A\u65B9\u914D\u5BF9\u94FE\u63A5\u6216\u914D\u5BF9\u7801\uFF09",
140
+ "install.qrcodeEnsure": "\u5B89\u88C5 qrcode \u5230 opencode \u914D\u7F6E\u76EE\u5F55\u2026",
141
+ "install.qrcodeFail": "qrcode \u5B89\u88C5\u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u4F7F\u7528\uFF0C\u4E8C\u7EF4\u7801\u4F1A\u964D\u7EA7\u4E3A\u94FE\u63A5\uFF09: {err}",
142
+ "install.relayError": "\u4E2D\u7EE7\u8FD4\u56DE\u9519\u8BEF: {msg}",
143
+ "install.scanHint": "\u624B\u673A\u6253\u5F00 tunnelbox App\u300C\u626B\u4E00\u626B\u914D\u5BF9\u300D\u6216\u76F8\u673A\u626B\u63CF\u4E0B\u65B9\u4E8C\u7EF4\u7801\u5373\u53EF\u8FDE\u63A5\uFF1A",
144
+ "pair.heading": "tunnelbox \u624B\u673A\u914D\u5BF9",
145
+ "pair.howTo": "\u5728\u624B\u673A\u6D4F\u89C8\u5668\u6253\u5F00\u94FE\u63A5\uFF0C\u6216\u6253\u5F00 tunnelbox \u5E94\u7528\u8F93\u5165\u914D\u5BF9\u7801\u3002",
146
+ "pair.oneTime": "\u914D\u5BF9\u7801\u4E3A\u4E00\u6B21\u6027\uFF0C\u4F7F\u7528\u540E\u5373\u5931\u6548\uFF1B\u5982\u9700\u65B0\u7801\uFF0C\u53EF\u8981\u6C42\u201C\u5237\u65B0\u914D\u5BF9\u7801\u201D\u3002",
147
+ "pair.promptBody": "\u8BF7\u5E2E\u6211\u67E5\u770B\u672C\u673A\u624B\u673A\u8FDC\u7A0B\u914D\u5BF9\u4FE1\u606F\uFF1A\u663E\u793A\u914D\u5BF9\u7801\u3001\u914D\u5BF9\u94FE\u63A5\uFF0C\u4EE5\u53CA\u53EF\u4F9B\u626B\u7801\u7684\u4E8C\u7EF4\u7801\u3002\u4E8C\u7EF4\u7801\u8BF7\u653E\u5728\u7B49\u5BBD\u4EE3\u7801\u5757\u91CC\u5E76\u9010\u5B57\u539F\u6837\u4FDD\u7559\uFF0C\u65B9\u4FBF\u6211\u7528\u624B\u673A\u626B\u7801\u3002",
148
+ "pair.promptDescription": "\u663E\u793A\u624B\u673A\u8FDC\u7A0B\u914D\u5BF9\u7684\u914D\u5BF9\u7801\u3001\u914D\u5BF9\u94FE\u63A5\u4E0E\u626B\u7801\u4E8C\u7EF4\u7801",
149
+ "pair.scanIntro": "**\u626B\u4E00\u626B\u914D\u5BF9**\uFF1A\u624B\u673A\u6253\u5F00 tunnelbox App\u300C\u626B\u4E00\u626B\u914D\u5BF9\u300D\u6216\u76F8\u673A\uFF0C\u626B\u63CF\u4E0B\u65B9\u4E8C\u7EF4\u7801\u5373\u53EF\u81EA\u52A8\u8FDE\u63A5\u3002",
150
+ "perm.promptAuthorize": "opencode \u8BF7\u6C42\u6388\u6743",
151
+ "perm.promptPatterns": "opencode \u8BF7\u6C42\u8BBF\u95EE\uFF1A{list}",
152
+ "perm.promptResource": "opencode \u8BF7\u6C42\u8BBF\u95EE\u8D44\u6E90\uFF1A{list}",
153
+ "perm.promptTool": "opencode \u8BF7\u6C42\u6267\u884C {tool}",
154
+ "qr.codeLabel": "\u914D\u5BF9\u7801",
155
+ "qr.fileWritten": "\u914D\u5BF9\u4FE1\u606F\u5DF2\u5199\u5165: {path}",
156
+ "qr.linkLabel": "\u94FE\u63A5",
157
+ "qr.openHint": "\u624B\u673A\u6D4F\u89C8\u5668\u6253\u5F00\u94FE\u63A5\uFF0C\u6216\u6253\u5F00 App \u540E\u624B\u52A8\u8F93\u5165\u914D\u5BF9\u7801\u3002",
158
+ "qr.titleNoType": "tunnelbox \u624B\u673A\u914D\u5BF9",
159
+ "qr.titleWithType": "tunnelbox {type} \u624B\u673A\u914D\u5BF9",
160
+ "qr.unavailable": "\uFF08\u672A\u5B89\u88C5 qrcode\uFF0C\u8BF7\u4F7F\u7528\u4E0A\u65B9\u94FE\u63A5\u6216\u914D\u5BF9\u7801\uFF09",
161
+ "claude.errBusy": "\u8BE5\u4F1A\u8BDD\u6B63\u5728\u751F\u6210\u4E2D\uFF0C\u8BF7\u7B49\u5F85\u5B8C\u6210\u6216\u5148\u505C\u6B62",
162
+ "claude.errTurn": "\u751F\u6210\u51FA\u9519",
163
+ "claude.pairingStillClaimed": "\u5DF2\u7ED1\u5B9A\u624B\u673A\u8D26\u53F7\uFF1A\u6B64\u914D\u5BF9\u7801\u4EC5\u7528\u4E8E\u540C\u8D26\u53F7\u65B0\u8BBE\u5907\u91CD\u65B0\u914D\u5BF9\uFF0C\u5F53\u524D\u8BBE\u5907\u53EF\u76F4\u63A5\u4ECE\u624B\u673A\u300C\u6211\u7684\u7535\u8111\u300D\u8FDE\u63A5",
164
+ "claude.pairForced": "--pair\uFF1A\u5DF2\u5F3A\u5236\u7533\u8BF7\u914D\u5BF9\u7801\uFF08\u4EC5\u672C\u6B21\uFF09",
165
+ "claude.pairUnclaimed": "\u672A\u68C0\u6D4B\u5230\u5DF2\u7ED1\u5B9A\u8D26\u53F7\uFF0C\u5F00\u59CB\u914D\u5BF9\u5F15\u5BFC\uFF1A",
166
+ "claude.claimedHint": "\u5DF2\u7ED1\u5B9A\u624B\u673A\u8D26\u53F7\uFF1A\u4ECE\u624B\u673A\u300C\u6211\u7684\u7535\u8111\u300D\u76F4\u63A5\u8FDE\u63A5\u5373\u53EF",
167
+ "claude.errStartFailed": "\u542F\u52A8\u5931\u8D25:",
168
+ "claude.msgStarted": "Claude Code \u9002\u914D\u5668\u5DF2\u542F\u52A8\uFF08Ctrl+C \u9000\u51FA\uFF09",
169
+ "claude.warnInvalidMode": "\u5FFD\u7565\u65E0\u6548 TUNNELBOX_CLAUDE_MODE: {raw}\uFF08\u5141\u8BB8 {list}\uFF09",
170
+ "claude.warnInvalidThinking": "\u5FFD\u7565\u65E0\u6548 TUNNELBOX_CLAUDE_THINKING: {raw}\uFF08off|adaptive|enabled[:budget]\uFF09",
171
+ "claude.cmdNew": "\u65B0\u5EFA\u4F1A\u8BDD",
172
+ "claude.cmdHelp": "\u663E\u793A\u5E2E\u52A9",
173
+ "claude.errNoRelay": "\u672A\u914D\u7F6E\u4E2D\u7EE7\u5730\u5740\uFF1A\u6784\u5EFA\u8BF7\u7528 --env \u6CE8\u5165\u9ED8\u8BA4\uFF0C\u8FD0\u884C\u53EF\u7528 TUNNELBOX_RELAY_URL \u8986\u76D6",
174
+ "claude.logConnected": "\u5DF2\u8FDE\u63A5\u4E2D\u7EE7 {url}",
175
+ "claude.logMsgUncaught": "\u6D88\u606F\u5904\u7406\u672A\u6355\u83B7\u5F02\u5E38: {message}",
176
+ "claude.reasonDisconnect": "\u4E2D\u7EE7\u65AD\u5F00\uFF08fail-closed \u81EA\u52A8\u62D2\u7EDD\uFF09",
177
+ "claude.reasonUnbind": "\u8D26\u53F7\u89E3\u7ED1\uFF08fail-closed \u81EA\u52A8\u62D2\u7EDD\uFF09",
178
+ "claude.logDisconnected": "\u4E0E\u4E2D\u7EE7\u65AD\u5F00\uFF0C\u6B63\u5728\u81EA\u52A8\u91CD\u8FDE\u2026",
179
+ "claude.logPairCode": "\u751F\u6210\u914D\u5BF9\u7801: {code}",
180
+ "claude.logUnhandledType": "\u672A\u5904\u7406\u7684\u6D88\u606F\u7C7B\u578B: {type}",
181
+ "claude.logHandleFailed": "\u5904\u7406 {type} \u5931\u8D25: {message}",
182
+ "claude.newSessionTitle": "\u65B0\u4F1A\u8BDD",
183
+ "claude.helpText": "\u652F\u6301\u7684\u547D\u4EE4\uFF1A\n- /new \u65B0\u5EFA\u4F1A\u8BDD\n- /help \u663E\u793A\u5E2E\u52A9",
184
+ "claude.logPromptFailed": "session.prompt \u5931\u8D25: {message}",
185
+ "claude.logAbortSent": "\u5DF2\u53D1\u9001\u4E2D\u6B62 (session={session})",
186
+ "claude.logAbortFailed": "\u4E2D\u6B62\u5931\u8D25 (session={session}): {message}",
187
+ "claude.errBadWorkspaceEmpty": "\u7F3A\u5C11\u5DE5\u4F5C\u533A\u8DEF\u5F84",
188
+ "claude.errBadWorkspaceNotDir": "\u8BE5\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u7528",
189
+ "claude.logWorkspaceSwitched": "\u5207\u6362\u5DE5\u4F5C\u533A: {path}",
190
+ "claude.logUnknownQuestionReply": "\u672A\u5339\u914D\u7684\u7ED3\u6784\u5316\u63D0\u95EE\u5E94\u7B54 (id={id})",
191
+ "claude.logFlushedQuestions": "\u5DF2\u53D6\u6D88 {count} \u4E2A\u5F85\u4F5C\u7B54\u63D0\u95EE: {reason}",
192
+ "claude.logFlushedApprovals": "\u5DF2\u62D2\u7EDD {count} \u4E2A\u5F85\u4F5C\u7B54\u8BF7\u6C42: {reason}",
193
+ "claude.logPendingResent": "session.pending \u8865\u53D1\u5F85\u529E {count} \u6761 (session={session})",
194
+ "claude.logSetModeFailed": "setPermissionMode \u5931\u8D25 (session={session}): {message}",
195
+ "claude.errWorkspaceMissing": "\u5DE5\u4F5C\u533A\u4E0D\u5B58\u5728: {dir}",
196
+ "claude.logSessionError": "\u4F1A\u8BDD\u51FA\u9519 (session={session}): {message}",
197
+ "claude.logOfflineDenyTool": "\u4E2D\u7EE7\u79BB\u7EBF\uFF0C\u81EA\u52A8\u62D2\u7EDD\u5DE5\u5177 {tool}",
198
+ "claude.errOfflineDeny": "\u4E2D\u7EE7\u79BB\u7EBF\uFF0C\u81EA\u52A8\u62D2\u7EDD\uFF08fail-closed\uFF09",
199
+ "claude.logAuqDisabled": "AskUserQuestion \u5DF2\u5173\u95ED\uFF08TUNNELBOX_CLAUDE_ASK_USER_QUESTION=0\uFF09\uFF0C\u81EA\u52A8\u62D2\u7EDD",
200
+ "claude.errAuqDisabled": "AskUserQuestion \u63D0\u95EE\u672A\u542F\u7528",
201
+ "claude.logAuqParseFailed": "AskUserQuestion input \u89E3\u6790\u5931\u8D25\uFF0C\u81EA\u52A8\u62D2\u7EDD\uFF08fail-closed\uFF09",
202
+ "claude.errAuqParse": "\u65E0\u6CD5\u89E3\u6790\u9898\u76EE",
203
+ "claude.logAuqSkipped": "AskUserQuestion \u672A\u4F5C\u7B54/\u53D6\u6D88 (session={session})",
204
+ "claude.errAuqCancelled": "\u7528\u6237\u672A\u9009\u62E9\uFF08cancelled\uFF09",
205
+ "claude.logAuqDone": "AskUserQuestion \u4F5C\u7B54\u5B8C\u6210 (session={session})",
206
+ "claude.promptRunTool": "Claude Code \u8BF7\u6C42\u6267\u884C {tool}",
207
+ "claude.errApprovalTimeout": "\u5BA1\u6279\u8D85\u65F6\uFF0C\u81EA\u52A8\u62D2\u7EDD\uFF08fail-closed\uFF09",
208
+ "claude.errUserDenied": "\u7528\u6237\u62D2\u7EDD",
209
+ "claude.errAborted": "\u8BF7\u6C42\u5DF2\u4E2D\u6B62",
210
+ "claude.logPermToPhone": "\u5BA1\u6279\u8BF7\u6C42(tool={tool}) \u2192 \u53D1\u9001\u5230\u624B\u673A\u6743\u9650\u5361 permissionID={id}",
211
+ "claude.errRelayUnreachable": "\u4E2D\u7EE7\u4E0D\u53EF\u8FBE\uFF0C\u81EA\u52A8\u62D2\u7EDD\uFF08fail-closed\uFF09",
212
+ "claude.logQuestionOffline": "\u4E2D\u7EE7\u79BB\u7EBF\uFF0C\u7ED3\u6784\u5316\u63D0\u95EE\u81EA\u52A8\u53D6\u6D88",
213
+ "claude.logQuestionToPhone": "\u7ED3\u6784\u5316\u63D0\u95EE({count} \u9898) \u2192 \u53D1\u9001\u5230\u624B\u673A questionID={id}",
214
+ "claude.logDialogOffline": "\u4E2D\u7EE7\u79BB\u7EBF\uFF0C\u5BF9\u8BDD\u6846\u81EA\u52A8\u53D6\u6D88\uFF08{tool}\uFF09",
215
+ "claude.logDialogToPhone": "\u5BF9\u8BDD\u6846\u8BF7\u6C42(kind={kind} tool={tool}) \u2192 \u53D1\u9001\u5230\u624B\u673A\u6743\u9650\u5361 permissionID={id}",
216
+ "claude.logAuqDialogParse": "AskUserQuestion payload \u89E3\u6790\u5931\u8D25\uFF0C\u4FDD\u6301\u9ED8\u8BA4\u884C\u4E3A\uFF08session={session}\uFF09",
217
+ "claude.logDialogUnmapped": "request_user_dialog(kind={kind}) \u672A\u63A5\u5165\u6620\u5C04\uFF0C\u4FDD\u6301\u9ED8\u8BA4\u884C\u4E3A\uFF08session={session}\uFF09",
218
+ "claude.promptMcpAuth": "MCP\u300C{server}\u300D\u8BF7\u6C42\u6388\u6743",
219
+ "claude.logElicitUnmapped": "MCP elicitation(server={server}, mode={mode}) \u672A\u63A5\u5165\uFF0C\u81EA\u52A8\u62D2\u7EDD\uFF08session={session}\uFF09",
220
+ "claude.logResetIdentity": "\u88AB\u8D26\u53F7\u89E3\u7ED1\uFF0C\u6B63\u5728\u91CD\u7F6E\u8EAB\u4EFD\u5E76\u91CD\u65B0\u751F\u6210\u914D\u5BF9\u7801\u2026",
221
+ "claude.dialogConfirm": "Claude Code \u8BF7\u6C42\u786E\u8BA4\uFF08{kind}\uFF09",
222
+ "claude.resultTruncated": "\u2026\uFF08\u7ED3\u679C\u8FC7\u957F\u5DF2\u622A\u65AD\uFF09",
223
+ "claude.streamError": "claude \u6D41\u5F0F\u51FA\u9519",
224
+ "claude.errListSessions": "\u8BFB\u53D6 Claude \u4F1A\u8BDD\u5217\u8868\u5931\u8D25",
225
+ "claude.errReadHistory": "\u8BFB\u53D6\u4F1A\u8BDD\u5386\u53F2\u5931\u8D25({id})",
226
+ "setup.langSet": "\u5DF2\u8BBE\u7F6E\u8FD0\u884C\u8BED\u8A00",
227
+ "setup.langHint": "\u91CD\u542F\u9002\u914D\u5668\u540E\u6309\u8BE5\u8BED\u8A00\u8F93\u51FA\uFF08\u8FD0\u884C\u65F6\u53EF\u88AB TUNNELBOX_LANG \u8986\u76D6\uFF09\u3002\u72B6\u6001\u6587\u4EF6\uFF1A",
228
+ "setup.langCurrent": "\u5F53\u524D\u8FD0\u884C\u8BED\u8A00"
229
+ };
230
+
231
+ // ../core/locales/zh-TW.json
232
+ var zh_TW_default = {
233
+ "agent.err.createSession": "\u5EFA\u7ACB\u6703\u8A71\u5931\u6557",
234
+ "agent.err.httpClient": "\u7121\u6CD5\u5B58\u53D6 opencode HTTP client",
235
+ "agent.err.unknownPerm": "\u672A\u77E5\u7684\u6B0A\u9650\u8ACB\u6C42: {id}",
236
+ "agent.err.unknownQuestion": "\u672A\u77E5\u7684\u63D0\u554F\u8ACB\u6C42: {id}",
237
+ "agent.log.fallback404": "\u56DE\u5305\u9996\u9078\u7AEF\u9EDE 404\uFF0C\u6539\u8D70\u53E6\u4E00\u7AEF\u9EDE: {message}",
238
+ "agent.log.gotPermReply": "\u6536\u5230\u624B\u6A5F\u6B0A\u9650\u56DE\u8986 (id={id} {reply} {version})",
239
+ "agent.log.gotQuestionReply": "\u6536\u5230\u624B\u6A5F\u63D0\u554F\u56DE\u8986 (id={id} {status} {version})",
240
+ "agent.log.handleFailed": "\u8655\u7406 {type} \u5931\u6557: {message}",
241
+ "agent.log.httpOk": "opencode HTTP {status} {path}",
242
+ "agent.log.pendingResent": "session.pending: \u88DC\u767C {session} \u7A4D\u58D3\u8ACB\u6C42 {count} \u689D",
243
+ "agent.log.unhandledType": "\u672A\u8655\u7406\u7684\u8A0A\u606F\u985E\u578B: {type}",
244
+ "events.log.fwdPermission": "\u8F49\u767C\u6B0A\u9650\u8ACB\u6C42\u5230\u624B\u6A5F (id={id} tool={tool} {version})",
245
+ "events.log.permissionReplied": "opencode \u56DE\u57F7 permission.replied (id={id} {response})",
246
+ "events.log.questionRejected": "opencode \u56DE\u57F7 question.rejected (id={id})",
247
+ "events.log.questionReplied": "opencode \u56DE\u57F7 question.replied (id={id})",
248
+ "index.error.connectRelay": "\u7121\u6CD5\u9023\u63A5\u4E2D\u7E7C\uFF0C\u8ACB\u6AA2\u67E5\u4E2D\u7E7C\u4F4D\u5740\u5F8C\u91CD\u8A66",
249
+ "index.error.pairTimeout": "\u7B49\u5F85\u914D\u5C0D\u78BC\u903E\u6642\uFF08\u4E2D\u7E7C\u672A\u56DE\u61C9\uFF09",
250
+ "index.log.claimed": "\u5DF2\u7D81\u5B9A\u5230\u624B\u6A5F\u5E33\u865F\uFF0C\u4E4B\u5F8C\u624B\u6A5F\u76F4\u63A5\u5F9E\u300C\u6211\u7684\u96FB\u8166\u300D\u9023\u63A5\uFF0C\u4E0D\u9700\u518D\u6383\u78BC",
251
+ "index.log.connectedRelay": "\u5DF2\u9023\u63A5\u4E2D\u7E7C {url}",
252
+ "index.log.disconnected": "\u8207\u4E2D\u7E7C\u65B7\u7DDA\uFF0C\u6B63\u5728\u81EA\u52D5\u91CD\u9023\u2026",
253
+ "index.log.exit": "\u5DF2\u9000\u51FA",
254
+ "index.log.pairCode": "\u7522\u751F\u914D\u5C0D\u78BC: {code}",
255
+ "index.log.permissionToPhone": "\u6B0A\u9650\u8ACB\u6C42 \u2192 \u624B\u6A5F\u5BE9\u6279 (id={id} tool={tool})",
256
+ "index.log.permissionTuiFallback": "app \u96E2\u7DDA\uFF0C\u6B0A\u9650\u8ACB\u6C42\u7531 opencode TUI \u4EE3\u7BA1 (id={id} tool={tool})",
257
+ "index.log.relayError": "\u4E2D\u7E7C\u932F\u8AA4: {msg}",
258
+ "index.log.resetIdentity": "\u5DF2\u89E3\u9664\u5E33\u865F\u7D81\u5B9A\uFF0C\u6B63\u5728\u91CD\u8A2D\u8EAB\u5206\u4E26\u91CD\u65B0\u7522\u751F\u914D\u5C0D\u78BC\u2026",
259
+ "index.log.unknown": "\u672A\u77E5",
260
+ "install.codeLine": "\u914D\u5C0D\u78BC: {code}",
261
+ "install.commandInstalled": "\u5DF2\u5B89\u88DD\u6307\u4EE4: {path}",
262
+ "install.done": "\u5B8C\u6210\u3002\u5DF2\u5617\u8A66\u9023\u63A5\u4E2D\u7E7C\u8F38\u51FA\u672C\u6A5F\u914D\u5C0D\u8CC7\u8A0A\uFF1A",
263
+ "install.failConnect": "\uFF08\u672A\u80FD\u5373\u6642\u9023\u63A5\u4E2D\u7E7C\u5217\u5370\u914D\u5C0D\u8CC7\u8A0A\u2014\u2014\u4E2D\u7E7C\u7121\u6CD5\u9023\u7DDA\u6216\u76EE\u524D Node \u904E\u820A\u3002",
264
+ "install.failConnectHint": "\u91CD\u555F opencode \u5F8C\u53EF\u7528 --print-logs \u67E5\u770B\u7D42\u7AEF QR Code\uFF0C\u6216\u662F\u5728 opencode \u5167\u8F38\u5165\u300C\u986F\u793A\u624B\u6A5F\u914D\u5C0D\u78BC\u300D\u6216 /pair\u3002\uFF09",
265
+ "install.hintFresh": "\u63D0\u793A\uFF1A\u914D\u5C0D\u78BC\u70BA\u4E00\u6B21\u6027\uFF0C\u7D04 10 \u5206\u9418\u6709\u6548\uFF1B\u82E5\u7A0D\u5F8C\u624D\u555F\u52D5 opencode\uFF0C\u4EE5\u555F\u52D5\u6642\u7D42\u7AEF\u5217\u5370\u6216 /pair \u53D6\u5F97\u7684\u6700\u65B0\u914D\u5C0D\u8CC7\u8A0A\u70BA\u6E96\u3002",
266
+ "install.linkLine": "\u914D\u5C0D\u9023\u7D50: {link}",
267
+ "install.pluginInstalled": "\u5DF2\u5B89\u88DD\u5916\u639B: {path}",
268
+ "install.qrUnavailable": "\uFF08QR Code \u7121\u6CD5\u986F\u793A\uFF0C\u8ACB\u4F7F\u7528\u4E0A\u65B9\u914D\u5C0D\u9023\u7D50\u6216\u914D\u5C0D\u78BC\uFF09",
269
+ "install.qrcodeEnsure": "\u6B63\u5728\u5C07 qrcode \u5B89\u88DD\u5230 opencode \u8A2D\u5B9A\u76EE\u9304\u2026",
270
+ "install.qrcodeFail": "qrcode \u5B89\u88DD\u5931\u6557\uFF08\u4E0D\u5F71\u97FF\u4F7F\u7528\uFF0CQR Code \u6703\u964D\u7D1A\u70BA\u9023\u7D50\uFF09: {err}",
271
+ "install.relayError": "\u4E2D\u7E7C\u50B3\u56DE\u932F\u8AA4: {msg}",
272
+ "install.scanHint": "\u624B\u6A5F\u958B\u555F tunnelbox App\u300C\u6383\u63CF\u914D\u5C0D\u300D\uFF0C\u6216\u4F7F\u7528\u76F8\u6A5F\u6383\u63CF\u4E0B\u65B9 QR Code \u5373\u53EF\u9023\u63A5\uFF1A",
273
+ "pair.heading": "tunnelbox \u624B\u6A5F\u914D\u5C0D",
274
+ "pair.howTo": "\u5728\u624B\u6A5F\u700F\u89BD\u5668\u958B\u555F\u9023\u7D50\uFF0C\u6216\u958B\u555F tunnelbox App \u8F38\u5165\u914D\u5C0D\u78BC\u3002",
275
+ "pair.oneTime": "\u914D\u5C0D\u78BC\u70BA\u4E00\u6B21\u6027\uFF0C\u4F7F\u7528\u5F8C\u5373\u5931\u6548\uFF1B\u5982\u9700\u65B0\u78BC\uFF0C\u53EF\u8981\u6C42\u300C\u91CD\u65B0\u6574\u7406\u914D\u5C0D\u78BC\u300D\u3002",
276
+ "pair.promptBody": "\u8ACB\u5E6B\u6211\u67E5\u770B\u672C\u6A5F\u624B\u6A5F\u9060\u7AEF\u914D\u5C0D\u8CC7\u8A0A\uFF1A\u986F\u793A\u914D\u5C0D\u78BC\u3001\u914D\u5C0D\u9023\u7D50\uFF0C\u4EE5\u53CA\u53EF\u4F9B\u6383\u78BC\u7684 QR Code\u3002QR Code \u8ACB\u653E\u5728\u7B49\u5BEC\u7A0B\u5F0F\u78BC\u5340\u584A\u5167\u4E26\u9010\u5B57\u539F\u6A23\u4FDD\u7559\uFF0C\u65B9\u4FBF\u6211\u7528\u624B\u6A5F\u6383\u63CF\u3002",
277
+ "pair.promptDescription": "\u986F\u793A\u624B\u6A5F\u9060\u7AEF\u914D\u5C0D\u7684\u914D\u5C0D\u78BC\u3001\u914D\u5C0D\u9023\u7D50\u8207\u6383\u78BC QR Code",
278
+ "pair.scanIntro": "**\u6383\u78BC\u914D\u5C0D**\uFF1A\u624B\u6A5F\u958B\u555F tunnelbox App\u300C\u6383\u63CF\u914D\u5C0D\u300D\uFF0C\u6216\u4F7F\u7528\u76F8\u6A5F\u6383\u63CF\u4E0B\u65B9 QR Code \u5373\u53EF\u81EA\u52D5\u9023\u63A5\u3002",
279
+ "perm.promptAuthorize": "opencode \u8ACB\u6C42\u6388\u6B0A",
280
+ "perm.promptPatterns": "opencode \u8ACB\u6C42\u5B58\u53D6\uFF1A{list}",
281
+ "perm.promptResource": "opencode \u8ACB\u6C42\u5B58\u53D6\u8CC7\u6E90\uFF1A{list}",
282
+ "perm.promptTool": "opencode \u8ACB\u6C42\u57F7\u884C {tool}",
283
+ "qr.codeLabel": "\u914D\u5C0D\u78BC",
284
+ "qr.fileWritten": "\u914D\u5C0D\u8CC7\u8A0A\u5DF2\u5BEB\u5165: {path}",
285
+ "qr.linkLabel": "\u9023\u7D50",
286
+ "qr.openHint": "\u5728\u624B\u6A5F\u700F\u89BD\u5668\u958B\u555F\u9023\u7D50\uFF0C\u6216\u5728 App \u4E2D\u624B\u52D5\u8F38\u5165\u914D\u5C0D\u78BC\u3002",
287
+ "qr.titleNoType": "tunnelbox \u624B\u6A5F\u914D\u5C0D",
288
+ "qr.titleWithType": "tunnelbox {type} \u624B\u6A5F\u914D\u5C0D",
289
+ "qr.unavailable": "\uFF08\u672A\u5B89\u88DD qrcode\uFF0C\u8ACB\u4F7F\u7528\u4E0A\u65B9\u9023\u7D50\u6216\u914D\u5C0D\u78BC\uFF09",
290
+ "claude.errBusy": "\u8A72\u6703\u8A71\u6B63\u5728\u7522\u751F\u4E2D\uFF0C\u8ACB\u7B49\u5F85\u5B8C\u6210\u6216\u5148\u505C\u6B62",
291
+ "claude.errTurn": "\u7522\u751F\u51FA\u932F",
292
+ "claude.pairingStillClaimed": "\u5DF2\u7D81\u5B9A\u624B\u6A5F\u5E33\u865F\uFF1A\u6B64\u914D\u5C0D\u78BC\u50C5\u4F9B\u540C\u5E33\u865F\u65B0\u88DD\u7F6E\u91CD\u65B0\u914D\u5C0D\uFF0C\u76EE\u524D\u88DD\u7F6E\u53EF\u76F4\u63A5\u5F9E\u624B\u6A5F\u300C\u6211\u7684\u96FB\u8166\u300D\u9023\u63A5",
293
+ "claude.pairForced": "--pair\uFF1A\u5DF2\u5F37\u5236\u7533\u8ACB\u914D\u5C0D\u78BC\uFF08\u50C5\u672C\u6B21\uFF09",
294
+ "claude.pairUnclaimed": "\u672A\u5075\u6E2C\u5230\u5DF2\u7D81\u5B9A\u5E33\u865F\uFF0C\u958B\u59CB\u914D\u5C0D\u5F15\u5C0E\uFF1A",
295
+ "claude.claimedHint": "\u5DF2\u7D81\u5B9A\u624B\u6A5F\u5E33\u865F\uFF1A\u5F9E\u624B\u6A5F\u300C\u6211\u7684\u96FB\u8166\u300D\u76F4\u63A5\u9023\u63A5\u5373\u53EF",
296
+ "claude.errStartFailed": "\u555F\u52D5\u5931\u6557:",
297
+ "claude.msgStarted": "Claude Code \u4ECB\u9762\u5361\u5DF2\u555F\u52D5\uFF08Ctrl+C \u7D50\u675F\uFF09",
298
+ "claude.warnInvalidMode": "\u5FFD\u7565\u7121\u6548 TUNNELBOX_CLAUDE_MODE: {raw}\uFF08\u5141\u8A31 {list}\uFF09",
299
+ "claude.warnInvalidThinking": "\u5FFD\u7565\u7121\u6548 TUNNELBOX_CLAUDE_THINKING: {raw}\uFF08off|adaptive|enabled[:budget]\uFF09",
300
+ "claude.cmdNew": "\u65B0\u958B\u6703\u8A71",
301
+ "claude.cmdHelp": "\u986F\u793A\u8AAA\u660E",
302
+ "claude.errNoRelay": "\u672A\u8A2D\u5B9A\u4E2D\u7E7C\u4F4D\u5740\uFF1A\u5EFA\u7F6E\u8ACB\u7528 --env \u6CE8\u5165\u9810\u8A2D\uFF0C\u57F7\u884C\u53EF\u7528 TUNNELBOX_RELAY_URL \u8986\u5BEB",
303
+ "claude.logConnected": "\u5DF2\u9023\u7DDA\u4E2D\u7E7C {url}",
304
+ "claude.logMsgUncaught": "\u8A0A\u606F\u8655\u7406\u672A\u6355\u7372\u4F8B\u5916: {message}",
305
+ "claude.reasonDisconnect": "\u4E2D\u7E7C\u65B7\u7DDA\uFF08fail-closed \u81EA\u52D5\u62D2\u7D55\uFF09",
306
+ "claude.reasonUnbind": "\u5E33\u865F\u89E3\u9664\u7D81\u5B9A\uFF08fail-closed \u81EA\u52D5\u62D2\u7D55\uFF09",
307
+ "claude.logDisconnected": "\u8207\u4E2D\u7E7C\u65B7\u7DDA\uFF0C\u6B63\u5728\u81EA\u52D5\u91CD\u9023\u2026",
308
+ "claude.logPairCode": "\u7522\u751F\u914D\u5C0D\u78BC: {code}",
309
+ "claude.logUnhandledType": "\u672A\u8655\u7406\u7684\u8A0A\u606F\u985E\u578B: {type}",
310
+ "claude.logHandleFailed": "\u8655\u7406 {type} \u5931\u6557: {message}",
311
+ "claude.newSessionTitle": "\u65B0\u6703\u8A71",
312
+ "claude.helpText": "\u652F\u63F4\u7684\u6307\u4EE4\uFF1A\n- /new \u65B0\u958B\u6703\u8A71\n- /help \u986F\u793A\u8AAA\u660E",
313
+ "claude.logPromptFailed": "session.prompt \u5931\u6557: {message}",
314
+ "claude.logAbortSent": "\u5DF2\u9001\u51FA\u4E2D\u6B62 (session={session})",
315
+ "claude.logAbortFailed": "\u4E2D\u6B62\u5931\u6557 (session={session}): {message}",
316
+ "claude.errBadWorkspaceEmpty": "\u7F3A\u5C11\u5DE5\u4F5C\u5340\u8DEF\u5F91",
317
+ "claude.errBadWorkspaceNotDir": "\u8A72\u76EE\u9304\u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u7528",
318
+ "claude.logWorkspaceSwitched": "\u5207\u63DB\u5DE5\u4F5C\u5340: {path}",
319
+ "claude.logUnknownQuestionReply": "\u672A\u76F8\u7B26\u7684\u7D50\u69CB\u5316\u63D0\u554F\u61C9\u7B54 (id={id})",
320
+ "claude.logFlushedQuestions": "\u5DF2\u53D6\u6D88 {count} \u500B\u5F85\u56DE\u7B54\u63D0\u554F: {reason}",
321
+ "claude.logFlushedApprovals": "\u5DF2\u62D2\u7D55 {count} \u500B\u5F85\u4F5C\u7B54\u8ACB\u6C42: {reason}",
322
+ "claude.logPendingResent": "session.pending \u88DC\u767C\u5F85\u8FA6 {count} \u7B46 (session={session})",
323
+ "claude.logSetModeFailed": "setPermissionMode \u5931\u6557 (session={session}): {message}",
324
+ "claude.errWorkspaceMissing": "\u5DE5\u4F5C\u5340\u4E0D\u5B58\u5728: {dir}",
325
+ "claude.logSessionError": "\u6703\u8A71\u51FA\u932F (session={session}): {message}",
326
+ "claude.logOfflineDenyTool": "\u4E2D\u7E7C\u96E2\u7DDA\uFF0C\u81EA\u52D5\u62D2\u7D55\u5DE5\u5177 {tool}",
327
+ "claude.errOfflineDeny": "\u4E2D\u7E7C\u96E2\u7DDA\uFF0C\u81EA\u52D5\u62D2\u7D55\uFF08fail-closed\uFF09",
328
+ "claude.logAuqDisabled": "AskUserQuestion \u5DF2\u95DC\u9589\uFF08TUNNELBOX_CLAUDE_ASK_USER_QUESTION=0\uFF09\uFF0C\u81EA\u52D5\u62D2\u7D55",
329
+ "claude.errAuqDisabled": "AskUserQuestion \u63D0\u554F\u672A\u555F\u7528",
330
+ "claude.logAuqParseFailed": "AskUserQuestion input \u89E3\u6790\u5931\u6557\uFF0C\u81EA\u52D5\u62D2\u7D55\uFF08fail-closed\uFF09",
331
+ "claude.errAuqParse": "\u7121\u6CD5\u89E3\u6790\u984C\u76EE",
332
+ "claude.logAuqSkipped": "AskUserQuestion \u672A\u4F5C\u7B54/\u53D6\u6D88 (session={session})",
333
+ "claude.errAuqCancelled": "\u4F7F\u7528\u8005\u672A\u9078\u64C7\uFF08cancelled\uFF09",
334
+ "claude.logAuqDone": "AskUserQuestion \u4F5C\u7B54\u5B8C\u6210 (session={session})",
335
+ "claude.promptRunTool": "Claude Code \u8ACB\u6C42\u57F7\u884C {tool}",
336
+ "claude.errApprovalTimeout": "\u5BE9\u6279\u903E\u6642\uFF0C\u81EA\u52D5\u62D2\u7D55\uFF08fail-closed\uFF09",
337
+ "claude.errUserDenied": "\u4F7F\u7528\u8005\u62D2\u7D55",
338
+ "claude.errAborted": "\u8ACB\u6C42\u5DF2\u4E2D\u6B62",
339
+ "claude.logPermToPhone": "\u5BE9\u6279\u8ACB\u6C42(tool={tool}) \u2192 \u50B3\u9001\u5230\u624B\u6A5F\u6B0A\u9650\u5361 permissionID={id}",
340
+ "claude.errRelayUnreachable": "\u4E2D\u7E7C\u4E0D\u53EF\u9054\uFF0C\u81EA\u52D5\u62D2\u7D55\uFF08fail-closed\uFF09",
341
+ "claude.logQuestionOffline": "\u4E2D\u7E7C\u96E2\u7DDA\uFF0C\u7D50\u69CB\u5316\u63D0\u554F\u81EA\u52D5\u53D6\u6D88",
342
+ "claude.logQuestionToPhone": "\u7D50\u69CB\u5316\u63D0\u554F({count} \u984C) \u2192 \u50B3\u9001\u5230\u624B\u6A5F questionID={id}",
343
+ "claude.logDialogOffline": "\u4E2D\u7E7C\u96E2\u7DDA\uFF0C\u5C0D\u8A71\u6846\u81EA\u52D5\u53D6\u6D88\uFF08{tool}\uFF09",
344
+ "claude.logDialogToPhone": "\u5C0D\u8A71\u6846\u8ACB\u6C42(kind={kind} tool={tool}) \u2192 \u50B3\u9001\u5230\u624B\u6A5F\u6B0A\u9650\u5361 permissionID={id}",
345
+ "claude.logAuqDialogParse": "AskUserQuestion payload \u89E3\u6790\u5931\u6557\uFF0C\u7DAD\u6301\u9810\u8A2D\u884C\u70BA\uFF08session={session}\uFF09",
346
+ "claude.logDialogUnmapped": "request_user_dialog(kind={kind}) \u672A\u63A5\u5165\u5C0D\u61C9\uFF0C\u7DAD\u6301\u9810\u8A2D\u884C\u70BA\uFF08session={session}\uFF09",
347
+ "claude.promptMcpAuth": "MCP\u300C{server}\u300D\u8ACB\u6C42\u6388\u6B0A",
348
+ "claude.logElicitUnmapped": "MCP elicitation(server={server}, mode={mode}) \u672A\u63A5\u5165\uFF0C\u81EA\u52D5\u62D2\u7D55\uFF08session={session}\uFF09",
349
+ "claude.logResetIdentity": "\u88AB\u5E33\u865F\u89E3\u9664\u7D81\u5B9A\uFF0C\u6B63\u5728\u91CD\u8A2D\u8EAB\u5206\u4E26\u91CD\u65B0\u7522\u751F\u914D\u5C0D\u78BC\u2026",
350
+ "claude.dialogConfirm": "Claude Code \u8ACB\u6C42\u78BA\u8A8D\uFF08{kind}\uFF09",
351
+ "claude.resultTruncated": "\u2026\uFF08\u7D50\u679C\u904E\u9577\u5DF2\u622A\u65B7\uFF09",
352
+ "claude.streamError": "claude \u4E32\u6D41\u51FA\u932F",
353
+ "claude.errListSessions": "\u8B80\u53D6 Claude \u6703\u8A71\u5217\u8868\u5931\u6557",
354
+ "claude.errReadHistory": "\u8B80\u53D6\u6703\u8A71\u6B77\u53F2\u5931\u6557({id})",
355
+ "setup.langSet": "\u5DF2\u8A2D\u5B9A\u57F7\u884C\u8A9E\u8A00",
356
+ "setup.langHint": "\u91CD\u65B0\u555F\u52D5\u4ECB\u9762\u5361\u5F8C\u5C07\u4EE5\u8A72\u8A9E\u8A00\u8F38\u51FA\uFF08\u57F7\u884C\u6642\u53EF\u88AB TUNNELBOX_LANG \u8986\u5BEB\uFF09\u3002\u72C0\u614B\u6A94\uFF1A",
357
+ "setup.langCurrent": "\u76EE\u524D\u57F7\u884C\u8A9E\u8A00"
358
+ };
359
+
360
+ // ../core/locales/en-US.json
361
+ var en_US_default = {
362
+ "agent.err.createSession": "Failed to create session",
363
+ "agent.err.httpClient": "Cannot access the opencode HTTP client",
364
+ "agent.err.unknownPerm": "Unknown permission request: {id}",
365
+ "agent.err.unknownQuestion": "Unknown question request: {id}",
366
+ "agent.log.fallback404": "Preferred endpoint returned 404, falling back to the other one: {message}",
367
+ "agent.log.gotPermReply": "Received permission reply from phone (id={id} {reply} {version})",
368
+ "agent.log.gotQuestionReply": "Received question reply from phone (id={id} {status} {version})",
369
+ "agent.log.handleFailed": "Failed to handle {type}: {message}",
370
+ "agent.log.httpOk": "opencode HTTP {status} {path}",
371
+ "agent.log.pendingResent": "session.pending: resent {count} pending request(s) for {session}",
372
+ "agent.log.unhandledType": "Unhandled message type: {type}",
373
+ "events.log.fwdPermission": "Forwarded permission request to phone (id={id} tool={tool} {version})",
374
+ "events.log.permissionReplied": "opencode acked permission.replied (id={id} {response})",
375
+ "events.log.questionRejected": "opencode acked question.rejected (id={id})",
376
+ "events.log.questionReplied": "opencode acked question.replied (id={id})",
377
+ "index.error.connectRelay": "Cannot connect to the relay; check the relay address and try again",
378
+ "index.error.pairTimeout": "Timed out waiting for a pairing code (the relay did not respond)",
379
+ "index.log.claimed": "Bound to your phone account. From now on the phone connects directly from My Computers; no need to scan again",
380
+ "index.log.connectedRelay": "Connected to relay {url}",
381
+ "index.log.disconnected": "Disconnected from relay, reconnecting automatically\u2026",
382
+ "index.log.exit": "Exited",
383
+ "index.log.pairCode": "Generated pairing code: {code}",
384
+ "index.log.permissionToPhone": "Permission request \u2192 phone approval (id={id} tool={tool})",
385
+ "index.log.permissionTuiFallback": "App offline; permission request handled by the opencode TUI (id={id} tool={tool})",
386
+ "index.log.relayError": "Relay error: {msg}",
387
+ "index.log.resetIdentity": "Unbound from the account; resetting identity and generating a new pairing code\u2026",
388
+ "index.log.unknown": "Unknown",
389
+ "install.codeLine": "Pairing code: {code}",
390
+ "install.commandInstalled": "Installed command: {path}",
391
+ "install.done": "Done. Attempted to connect to the relay and output pairing info:",
392
+ "install.failConnect": "(Could not connect to the relay to print pairing info \u2014 the relay may be unreachable or the Node version is too old.",
393
+ "install.failConnectHint": "After restarting opencode, check the terminal QR code with --print-logs, or type \u201Cshow phone pairing code\u201D / /pair inside opencode.)",
394
+ "install.hintFresh": "Note: the pairing code is single-use and valid for about 10 minutes. If you start opencode later, use the latest pairing info printed at startup or obtained via /pair.",
395
+ "install.linkLine": "Pairing link: {link}",
396
+ "install.pluginInstalled": "Installed plugin: {path}",
397
+ "install.qrUnavailable": "(QR rendering unavailable \u2014 use the pairing link or code above)",
398
+ "install.qrcodeEnsure": "Installing qrcode into the opencode config directory\u2026",
399
+ "install.qrcodeFail": "qrcode installation failed (not critical \u2014 the QR code degrades to a link): {err}",
400
+ "install.relayError": "Relay returned an error: {msg}",
401
+ "install.scanHint": "Open tunnelbox on your phone, tap \u201CScan to pair\u201D, or point your camera at the QR code below to connect:",
402
+ "pair.heading": "tunnelbox phone pairing",
403
+ "pair.howTo": "Open the link in your phone browser, or enter the pairing code in the tunnelbox app.",
404
+ "pair.oneTime": "The pairing code is single-use and invalidates after it is claimed; ask to \u201Crefresh the pairing code\u201D for a new one.",
405
+ "pair.promptBody": "Please show this computer's phone pairing info: the pairing code, the pairing link, and a QR code I can scan. Keep the QR code inside a monospaced code block and reproduce it character-for-character so my phone can scan it.",
406
+ "pair.promptDescription": "Show the phone pairing code, pairing link and scan-able QR code",
407
+ "pair.scanIntro": "**Pair by scanning**: open the tunnelbox app on your phone (\u201CScan to pair\u201D) or use your camera to scan the QR code below and connect automatically.",
408
+ "perm.promptAuthorize": "opencode requests authorization",
409
+ "perm.promptPatterns": "opencode requests access to: {list}",
410
+ "perm.promptResource": "opencode requests access to resources: {list}",
411
+ "perm.promptTool": "opencode requests to run {tool}",
412
+ "qr.codeLabel": "Pairing code",
413
+ "qr.fileWritten": "Pairing info written to: {path}",
414
+ "qr.linkLabel": "Link",
415
+ "qr.openHint": "Open the link in your phone browser, or enter the pairing code manually in the app.",
416
+ "qr.titleNoType": "tunnelbox phone pairing",
417
+ "qr.titleWithType": "tunnelbox {type} phone pairing",
418
+ "qr.unavailable": "(qrcode not installed \u2014 use the link or pairing code above)",
419
+ "claude.errBusy": "This session is still running; wait for it to finish or stop it first",
420
+ "claude.errTurn": "generation error",
421
+ "claude.pairingStillClaimed": "Already bound to a phone account: this code is only for pairing a new device on the same account; the current device connects directly from My Computers",
422
+ "claude.pairForced": "--pair: forcing a pairing code request (this run only)",
423
+ "claude.pairUnclaimed": "No bound account detected; starting pairing guide:",
424
+ "claude.claimedHint": "Bound to a phone account: connect directly from My Computers on the phone",
425
+ "claude.errStartFailed": "Failed to start:",
426
+ "claude.msgStarted": "Claude Code adapter started (Ctrl+C to quit)",
427
+ "claude.warnInvalidMode": "Ignoring invalid TUNNELBOX_CLAUDE_MODE: {raw} (allowed {list})",
428
+ "claude.warnInvalidThinking": "Ignoring invalid TUNNELBOX_CLAUDE_THINKING: {raw} (off|adaptive|enabled[:budget])",
429
+ "claude.cmdNew": "New session",
430
+ "claude.cmdHelp": "Show help",
431
+ "claude.errNoRelay": "No relay URL configured: build with --env to inject a default, or override at runtime with TUNNELBOX_RELAY_URL",
432
+ "claude.logConnected": "Connected to relay {url}",
433
+ "claude.logMsgUncaught": "Uncaught exception while handling message: {message}",
434
+ "claude.reasonDisconnect": "Relay disconnected (fail-closed auto-reject)",
435
+ "claude.reasonUnbind": "Account unbound (fail-closed auto-reject)",
436
+ "claude.logDisconnected": "Disconnected from relay, reconnecting automatically\u2026",
437
+ "claude.logPairCode": "Generated pairing code: {code}",
438
+ "claude.logUnhandledType": "Unhandled message type: {type}",
439
+ "claude.logHandleFailed": "Failed to handle {type}: {message}",
440
+ "claude.newSessionTitle": "New session",
441
+ "claude.helpText": "Supported commands:\n- /new new session\n- /help show help",
442
+ "claude.logPromptFailed": "session.prompt failed: {message}",
443
+ "claude.logAbortSent": "Abort sent (session={session})",
444
+ "claude.logAbortFailed": "Abort failed (session={session}): {message}",
445
+ "claude.errBadWorkspaceEmpty": "Missing workspace path",
446
+ "claude.errBadWorkspaceNotDir": "Directory does not exist or is unusable",
447
+ "claude.logWorkspaceSwitched": "Switched workspace: {path}",
448
+ "claude.logUnknownQuestionReply": "Unmatched structured question reply (id={id})",
449
+ "claude.logFlushedQuestions": "Cancelled {count} pending question(s): {reason}",
450
+ "claude.logFlushedApprovals": "Rejected {count} pending request(s): {reason}",
451
+ "claude.logPendingResent": "session.pending: resent {count} pending item(s) for {session}",
452
+ "claude.logSetModeFailed": "setPermissionMode failed (session={session}): {message}",
453
+ "claude.errWorkspaceMissing": "Workspace does not exist: {dir}",
454
+ "claude.logSessionError": "Session error (session={session}): {message}",
455
+ "claude.logOfflineDenyTool": "Relay offline, auto-rejecting tool {tool}",
456
+ "claude.errOfflineDeny": "Relay offline, auto-rejected (fail-closed)",
457
+ "claude.logAuqDisabled": "AskUserQuestion disabled (TUNNELBOX_CLAUDE_ASK_USER_QUESTION=0), auto-rejected",
458
+ "claude.errAuqDisabled": "AskUserQuestion is not enabled",
459
+ "claude.logAuqParseFailed": "Failed to parse AskUserQuestion input, auto-rejected (fail-closed)",
460
+ "claude.errAuqParse": "Could not parse the questions",
461
+ "claude.logAuqSkipped": "AskUserQuestion unanswered/cancelled (session={session})",
462
+ "claude.errAuqCancelled": "User did not select (cancelled)",
463
+ "claude.logAuqDone": "AskUserQuestion answered (session={session})",
464
+ "claude.promptRunTool": "Claude Code requests to run {tool}",
465
+ "claude.errApprovalTimeout": "Approval timed out, auto-rejected (fail-closed)",
466
+ "claude.errUserDenied": "Rejected by user",
467
+ "claude.errAborted": "Request aborted",
468
+ "claude.logPermToPhone": "Permission request (tool={tool}) \u2192 sent to phone as permissionID={id}",
469
+ "claude.errRelayUnreachable": "Relay unreachable, auto-rejected (fail-closed)",
470
+ "claude.logQuestionOffline": "Relay offline, structured question auto-cancelled",
471
+ "claude.logQuestionToPhone": "Structured question ({count} item(s)) \u2192 sent to phone questionID={id}",
472
+ "claude.logDialogOffline": "Relay offline, dialog auto-cancelled ({tool})",
473
+ "claude.logDialogToPhone": "Dialog request (kind={kind} tool={tool}) \u2192 sent to phone as permissionID={id}",
474
+ "claude.logAuqDialogParse": "Failed to parse AskUserQuestion payload, keeping default behavior (session={session})",
475
+ "claude.logDialogUnmapped": "request_user_dialog(kind={kind}) has no mapping, keeping default behavior (session={session})",
476
+ "claude.promptMcpAuth": "MCP \u201C{server}\u201D requests authorization",
477
+ "claude.logElicitUnmapped": "MCP elicitation (server={server}, mode={mode}) not handled, auto-rejected (session={session})",
478
+ "claude.logResetIdentity": "Unbound from account; resetting identity and generating a new pairing code\u2026",
479
+ "claude.dialogConfirm": "Claude Code requests confirmation ({kind})",
480
+ "claude.resultTruncated": "\u2026 (result truncated)",
481
+ "claude.streamError": "claude streaming error",
482
+ "claude.errListSessions": "Failed to read Claude session list",
483
+ "claude.errReadHistory": "Failed to read session history ({id})",
484
+ "setup.langSet": "Runtime language set to",
485
+ "setup.langHint": "The adapter will output in this language after restart (runtime TUNNELBOX_LANG can override). State file:",
486
+ "setup.langCurrent": "Current runtime language"
487
+ };
488
+
489
+ // ../core/locales/ja-JP.json
490
+ var ja_JP_default = {
491
+ "agent.err.createSession": "\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
492
+ "agent.err.httpClient": "opencode HTTP client \u306B\u30A2\u30AF\u30BB\u30B9\u3067\u304D\u307E\u305B\u3093",
493
+ "agent.err.unknownPerm": "\u4E0D\u660E\u306A\u6A29\u9650\u30EA\u30AF\u30A8\u30B9\u30C8: {id}",
494
+ "agent.err.unknownQuestion": "\u4E0D\u660E\u306A\u8CEA\u554F\u30EA\u30AF\u30A8\u30B9\u30C8: {id}",
495
+ "agent.log.fallback404": "\u512A\u5148\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u304C 404 \u3092\u8FD4\u3057\u305F\u305F\u3081\u3001\u5225\u306E\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u3078\u30D5\u30A9\u30FC\u30EB\u30D0\u30C3\u30AF: {message}",
496
+ "agent.log.gotPermReply": "\u643A\u5E2F\u304B\u3089\u6A29\u9650\u306E\u8FD4\u4FE1\u3092\u53D7\u4FE1 (id={id} {reply} {version})",
497
+ "agent.log.gotQuestionReply": "\u643A\u5E2F\u304B\u3089\u8CEA\u554F\u306E\u8FD4\u4FE1\u3092\u53D7\u4FE1 (id={id} {status} {version})",
498
+ "agent.log.handleFailed": "{type} \u306E\u51E6\u7406\u306B\u5931\u6557: {message}",
499
+ "agent.log.httpOk": "opencode HTTP {status} {path}",
500
+ "agent.log.pendingResent": "session.pending: {session} \u306E\u4FDD\u7559\u30EA\u30AF\u30A8\u30B9\u30C8 {count} \u4EF6\u3092\u518D\u9001",
501
+ "agent.log.unhandledType": "\u672A\u51E6\u7406\u306E\u30E1\u30C3\u30BB\u30FC\u30B8\u30BF\u30A4\u30D7: {type}",
502
+ "events.log.fwdPermission": "\u6A29\u9650\u30EA\u30AF\u30A8\u30B9\u30C8\u3092\u643A\u5E2F\u3078\u8EE2\u9001 (id={id} tool={tool} {version})",
503
+ "events.log.permissionReplied": "opencode \u5FDC\u7B54 permission.replied (id={id} {response})",
504
+ "events.log.questionRejected": "opencode \u5FDC\u7B54 question.rejected (id={id})",
505
+ "events.log.questionReplied": "opencode \u5FDC\u7B54 question.replied (id={id})",
506
+ "index.error.connectRelay": "\u30EA\u30EC\u30FC\u306B\u63A5\u7D9A\u3067\u304D\u307E\u305B\u3093\u3002\u30EA\u30EC\u30FC\u30A2\u30C9\u30EC\u30B9\u3092\u78BA\u8A8D\u3057\u3066\u518D\u8A66\u884C\u3057\u3066\u304F\u3060\u3055\u3044",
507
+ "index.error.pairTimeout": "\u30DA\u30A2\u30EA\u30F3\u30B0\u30B3\u30FC\u30C9\u306E\u53D6\u5F97\u304C\u30BF\u30A4\u30E0\u30A2\u30A6\u30C8\u3057\u307E\u3057\u305F\uFF08\u30EA\u30EC\u30FC\u304C\u5FDC\u7B54\u3057\u307E\u305B\u3093\uFF09",
508
+ "index.log.claimed": "\u30B9\u30DE\u30DB\u306E\u30A2\u30AB\u30A6\u30F3\u30C8\u306B\u7D10\u4ED8\u3051\u307E\u3057\u305F\u3002\u4EE5\u964D\u306F\u300C\u30DE\u30A4 PC\u300D\u304B\u3089\u76F4\u63A5\u63A5\u7D9A\u3055\u308C\u308B\u305F\u3081\u3001\u518D\u30B9\u30AD\u30E3\u30F3\u306F\u4E0D\u8981\u3067\u3059",
509
+ "index.log.connectedRelay": "\u30EA\u30EC\u30FC\u306B\u63A5\u7D9A\u3057\u307E\u3057\u305F {url}",
510
+ "index.log.disconnected": "\u30EA\u30EC\u30FC\u304B\u3089\u5207\u65AD\u3001\u81EA\u52D5\u7684\u306B\u518D\u63A5\u7D9A\u3057\u3066\u3044\u307E\u3059\u2026",
511
+ "index.log.exit": "\u7D42\u4E86\u3057\u307E\u3057\u305F",
512
+ "index.log.pairCode": "\u30DA\u30A2\u30EA\u30F3\u30B0\u30B3\u30FC\u30C9\u3092\u751F\u6210: {code}",
513
+ "index.log.permissionToPhone": "\u6A29\u9650\u30EA\u30AF\u30A8\u30B9\u30C8 \u2192 \u30B9\u30DE\u30DB\u3067\u627F\u8A8D (id={id} tool={tool})",
514
+ "index.log.permissionTuiFallback": "App \u304C\u30AA\u30D5\u30E9\u30A4\u30F3\u306E\u305F\u3081\u3001\u6A29\u9650\u30EA\u30AF\u30A8\u30B9\u30C8\u306F opencode TUI \u3067\u5BFE\u5FDC (id={id} tool={tool})",
515
+ "index.log.relayError": "\u30EA\u30EC\u30FC\u30A8\u30E9\u30FC: {msg}",
516
+ "index.log.resetIdentity": "\u30A2\u30AB\u30A6\u30F3\u30C8\u304B\u3089\u89E3\u9664\u3055\u308C\u307E\u3057\u305F\u3002ID \u3092\u30EA\u30BB\u30C3\u30C8\u3057\u3066\u65B0\u3057\u3044\u30DA\u30A2\u30EA\u30F3\u30B0\u30B3\u30FC\u30C9\u3092\u751F\u6210\u3057\u307E\u3059\u2026",
517
+ "index.log.unknown": "\u4E0D\u660E",
518
+ "install.codeLine": "\u30DA\u30A2\u30EA\u30F3\u30B0\u30B3\u30FC\u30C9: {code}",
519
+ "install.commandInstalled": "\u30B3\u30DE\u30F3\u30C9\u3092\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB: {path}",
520
+ "install.done": "\u5B8C\u4E86\u3002\u30EA\u30EC\u30FC\u306B\u63A5\u7D9A\u3057\u3066\u672C\u6A5F\u306E\u30DA\u30A2\u30EA\u30F3\u30B0\u60C5\u5831\u3092\u51FA\u529B\u3057\u307E\u3057\u305F\uFF1A",
521
+ "install.failConnect": "\uFF08\u30DA\u30A2\u30EA\u30F3\u30B0\u60C5\u5831\u3092\u51FA\u529B\u3059\u308B\u305F\u3081\u30EA\u30EC\u30FC\u306B\u63A5\u7D9A\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F \u2014 \u30EA\u30EC\u30FC\u306B\u5230\u9054\u3067\u304D\u306A\u3044\u304B Node \u304C\u53E4\u3059\u304E\u308B\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002",
522
+ "install.failConnectHint": "opencode \u3092\u518D\u8D77\u52D5\u5F8C\u3001--print-logs \u3067\u30BF\u30FC\u30DF\u30CA\u30EB\u306E QR \u30B3\u30FC\u30C9\u3092\u78BA\u8A8D\u3059\u308B\u304B\u3001opencode \u5185\u3067\u300C\u30DA\u30A2\u30EA\u30F3\u30B0\u30B3\u30FC\u30C9\u3092\u8868\u793A\u300D\u307E\u305F\u306F /pair \u3068\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002\uFF09",
523
+ "install.hintFresh": "\u6CE8\u610F\uFF1A\u30DA\u30A2\u30EA\u30F3\u30B0\u30B3\u30FC\u30C9\u306F\u4E00\u5EA6\u304D\u308A\u3067\u3001\u7D04 10 \u5206\u9593\u6709\u52B9\u3067\u3059\u3002\u5F8C\u304B\u3089 opencode \u3092\u8D77\u52D5\u3059\u308B\u5834\u5408\u306F\u3001\u8D77\u52D5\u6642\u306B\u8868\u793A\u3055\u308C\u308B\u304B /pair \u3067\u53D6\u5F97\u3057\u305F\u6700\u65B0\u60C5\u5831\u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
524
+ "install.linkLine": "\u30DA\u30A2\u30EA\u30F3\u30B0\u30EA\u30F3\u30AF: {link}",
525
+ "install.pluginInstalled": "\u30D7\u30E9\u30B0\u30A4\u30F3\u3092\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB: {path}",
526
+ "install.qrUnavailable": "\uFF08QR \u30B3\u30FC\u30C9\u3092\u8868\u793A\u3067\u304D\u307E\u305B\u3093 \u2014 \u4E0A\u8A18\u306E\u30EA\u30F3\u30AF\u307E\u305F\u306F\u30DA\u30A2\u30EA\u30F3\u30B0\u30B3\u30FC\u30C9\u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\uFF09",
527
+ "install.qrcodeEnsure": "qrcode \u3092 opencode \u8A2D\u5B9A\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306B\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u3057\u3066\u3044\u307E\u3059\u2026",
528
+ "install.qrcodeFail": "qrcode \u306E\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u306B\u5931\u6557\u3057\u307E\u3057\u305F\uFF08\u4F7F\u7528\u306B\u306F\u5F71\u97FF\u305B\u305A\u3001QR \u306F\u30EA\u30F3\u30AF\u306B\u30D5\u30A9\u30FC\u30EB\u30D0\u30C3\u30AF\u3057\u307E\u3059\uFF09: {err}",
529
+ "install.relayError": "\u30EA\u30EC\u30FC\u304C\u30A8\u30E9\u30FC\u3092\u8FD4\u3057\u307E\u3057\u305F: {msg}",
530
+ "install.scanHint": "\u30B9\u30DE\u30DB\u3067 tunnelbox App\u300C\u30B9\u30AD\u30E3\u30F3\u3057\u3066\u30DA\u30A2\u30EA\u30F3\u30B0\u300D\u3092\u958B\u304F\u304B\u3001\u30AB\u30E1\u30E9\u3067\u4E0B\u306E QR \u30B3\u30FC\u30C9\u3092\u30B9\u30AD\u30E3\u30F3\u3057\u3066\u63A5\u7D9A\uFF1A",
531
+ "pair.heading": "tunnelbox \u30B9\u30DE\u30DB\u30DA\u30A2\u30EA\u30F3\u30B0",
532
+ "pair.howTo": "\u30B9\u30DE\u30DB\u306E\u30D6\u30E9\u30A6\u30B6\u3067\u30EA\u30F3\u30AF\u3092\u958B\u304F\u304B\u3001tunnelbox App \u306B\u30DA\u30A2\u30EA\u30F3\u30B0\u30B3\u30FC\u30C9\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
533
+ "pair.oneTime": "\u30DA\u30A2\u30EA\u30F3\u30B0\u30B3\u30FC\u30C9\u306F\u4E00\u5EA6\u304D\u308A\u3067\u3001\u4F7F\u7528\u5F8C\u306F\u7121\u52B9\u306B\u306A\u308A\u307E\u3059\u3002\u65B0\u3057\u3044\u30B3\u30FC\u30C9\u304C\u5FC5\u8981\u306A\u5834\u5408\u306F\u300C\u30DA\u30A2\u30EA\u30F3\u30B0\u30B3\u30FC\u30C9\u3092\u66F4\u65B0\u300D\u3068\u4F9D\u983C\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
534
+ "pair.promptBody": "\u3053\u306E PC \u306E\u30B9\u30DE\u30DB\u9060\u9694\u30DA\u30A2\u30EA\u30F3\u30B0\u60C5\u5831\u3092\u8868\u793A\u3057\u3066\u304F\u3060\u3055\u3044\uFF1A\u30DA\u30A2\u30EA\u30F3\u30B0\u30B3\u30FC\u30C9\u3001\u30DA\u30A2\u30EA\u30F3\u30B0\u30EA\u30F3\u30AF\u3001\u304A\u3088\u3073\u30B9\u30AD\u30E3\u30F3\u3067\u304D\u308B QR \u30B3\u30FC\u30C9\u3002QR \u30B3\u30FC\u30C9\u306F\u7B49\u5E45\u30B3\u30FC\u30C9\u30D6\u30ED\u30C3\u30AF\u306B\u5165\u308C\u30011 \u6587\u5B57\u3082\u5909\u3048\u305A\u305D\u306E\u307E\u307E\u51FA\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u30B9\u30DE\u30DB\u3067\u30B9\u30AD\u30E3\u30F3\u3067\u304D\u308B\u3088\u3046\u306B\u3059\u308B\u305F\u3081\u3067\u3059\u3002",
535
+ "pair.promptDescription": "\u30B9\u30DE\u30DB\u9060\u9694\u30DA\u30A2\u30EA\u30F3\u30B0\u306E\u30DA\u30A2\u30EA\u30F3\u30B0\u30B3\u30FC\u30C9\u30FB\u30EA\u30F3\u30AF\u30FB\u30B9\u30AD\u30E3\u30F3\u7528 QR \u30B3\u30FC\u30C9\u3092\u8868\u793A",
536
+ "pair.scanIntro": "**\u30B9\u30AD\u30E3\u30F3\u3067\u30DA\u30A2\u30EA\u30F3\u30B0**\uFF1A\u30B9\u30DE\u30DB\u3067 tunnelbox App\u300C\u30B9\u30AD\u30E3\u30F3\u3057\u3066\u30DA\u30A2\u30EA\u30F3\u30B0\u300D\u3092\u958B\u304F\u304B\u3001\u30AB\u30E1\u30E9\u3067\u4E0B\u306E QR \u30B3\u30FC\u30C9\u3092\u30B9\u30AD\u30E3\u30F3\u3059\u308B\u3068\u81EA\u52D5\u7684\u306B\u63A5\u7D9A\u3055\u308C\u307E\u3059\u3002",
537
+ "perm.promptAuthorize": "opencode \u304C\u8A8D\u53EF\u3092\u8981\u6C42\u3057\u3066\u3044\u307E\u3059",
538
+ "perm.promptPatterns": "opencode \u304C\u30A2\u30AF\u30BB\u30B9\u3092\u8981\u6C42: {list}",
539
+ "perm.promptResource": "opencode \u304C\u30EA\u30BD\u30FC\u30B9\u3078\u306E\u30A2\u30AF\u30BB\u30B9\u3092\u8981\u6C42: {list}",
540
+ "perm.promptTool": "opencode \u304C {tool} \u306E\u5B9F\u884C\u3092\u8981\u6C42",
541
+ "qr.codeLabel": "\u30DA\u30A2\u30EA\u30F3\u30B0\u30B3\u30FC\u30C9",
542
+ "qr.fileWritten": "\u30DA\u30A2\u30EA\u30F3\u30B0\u60C5\u5831\u3092\u66F8\u304D\u8FBC\u307F\u307E\u3057\u305F: {path}",
543
+ "qr.linkLabel": "\u30EA\u30F3\u30AF",
544
+ "qr.openHint": "\u30B9\u30DE\u30DB\u306E\u30D6\u30E9\u30A6\u30B6\u3067\u30EA\u30F3\u30AF\u3092\u958B\u304F\u304B\u3001App \u3067\u30DA\u30A2\u30EA\u30F3\u30B0\u30B3\u30FC\u30C9\u3092\u624B\u52D5\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
545
+ "qr.titleNoType": "tunnelbox \u30B9\u30DE\u30DB\u30DA\u30A2\u30EA\u30F3\u30B0",
546
+ "qr.titleWithType": "tunnelbox {type} \u30B9\u30DE\u30DB\u30DA\u30A2\u30EA\u30F3\u30B0",
547
+ "qr.unavailable": "\uFF08qrcode \u304C\u672A\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB \u2014 \u4E0A\u8A18\u306E\u30EA\u30F3\u30AF\u307E\u305F\u306F\u30DA\u30A2\u30EA\u30F3\u30B0\u30B3\u30FC\u30C9\u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\uFF09",
548
+ "claude.errBusy": "\u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u5B9F\u884C\u4E2D\u3067\u3059\u3002\u5B8C\u4E86\u3059\u308B\u307E\u3067\u304A\u5F85\u3061\u3044\u305F\u3060\u304F\u304B\u3001\u5148\u306B\u505C\u6B62\u3057\u3066\u304F\u3060\u3055\u3044",
549
+ "claude.errTurn": "\u751F\u6210\u30A8\u30E9\u30FC",
550
+ "claude.pairingStillClaimed": "\u3059\u3067\u306B\u30A2\u30AB\u30A6\u30F3\u30C8\u306B\u7D10\u4ED8\u3051\u6E08\u307F\u3067\u3059\u3002\u3053\u306E\u30B3\u30FC\u30C9\u306F\u540C\u3058\u30A2\u30AB\u30A6\u30F3\u30C8\u306E\u65B0\u3057\u3044\u7AEF\u672B\u306E\u30DA\u30A2\u30EA\u30F3\u30B0\u5C02\u7528\u3067\u3059\u3002\u73FE\u5728\u306E\u7AEF\u672B\u306F\u300C\u30DE\u30A4\u30B3\u30F3\u30D4\u30E5\u30FC\u30BF\u300D\u304B\u3089\u76F4\u63A5\u63A5\u7D9A\u3067\u304D\u307E\u3059",
551
+ "claude.pairForced": "--pair\uFF1A\u30DA\u30A2\u30EA\u30F3\u30B0\u30B3\u30FC\u30C9\u3092\u5F37\u5236\u30EA\u30AF\u30A8\u30B9\u30C8\u3057\u307E\u3057\u305F\uFF08\u4ECA\u56DE\u306E\u307F\uFF09",
552
+ "claude.pairUnclaimed": "\u7D10\u4ED8\u3051\u6E08\u307F\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u691C\u51FA\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u30DA\u30A2\u30EA\u30F3\u30B0\u6848\u5185\u3092\u958B\u59CB\u3057\u307E\u3059\uFF1A",
553
+ "claude.claimedHint": "\u643A\u5E2F\u30A2\u30AB\u30A6\u30F3\u30C8\u306B\u7D10\u4ED8\u3051\u6E08\u307F\u3067\u3059\uFF1A\u643A\u5E2F\u306E\u300C\u30DE\u30A4\u30B3\u30F3\u30D4\u30E5\u30FC\u30BF\u300D\u304B\u3089\u76F4\u63A5\u63A5\u7D9A\u3057\u3066\u304F\u3060\u3055\u3044",
554
+ "claude.errStartFailed": "\u8D77\u52D5\u306B\u5931\u6557\u3057\u307E\u3057\u305F:",
555
+ "claude.msgStarted": "Claude Code \u30A2\u30C0\u30D7\u30BF\u30FC\u3092\u8D77\u52D5\u3057\u307E\u3057\u305F\uFF08Ctrl+C \u3067\u7D42\u4E86\uFF09",
556
+ "claude.warnInvalidMode": "\u7121\u52B9\u306A TUNNELBOX_CLAUDE_MODE \u3092\u7121\u8996: {raw}\uFF08\u8A31\u53EF {list}\uFF09",
557
+ "claude.warnInvalidThinking": "\u7121\u52B9\u306A TUNNELBOX_CLAUDE_THINKING \u3092\u7121\u8996: {raw}\uFF08off|adaptive|enabled[:budget]\uFF09",
558
+ "claude.cmdNew": "\u65B0\u3057\u3044\u30BB\u30C3\u30B7\u30E7\u30F3",
559
+ "claude.cmdHelp": "\u30D8\u30EB\u30D7\u3092\u8868\u793A",
560
+ "claude.errNoRelay": "\u4E2D\u7D99 URL \u304C\u8A2D\u5B9A\u3055\u308C\u3066\u3044\u307E\u305B\u3093: \u30D3\u30EB\u30C9\u6642\u306F --env \u3067\u30C7\u30D5\u30A9\u30EB\u30C8\u3092\u6CE8\u5165\u3057\u3001\u5B9F\u884C\u6642\u306F TUNNELBOX_RELAY_URL \u3067\u4E0A\u66F8\u304D\u3067\u304D\u307E\u3059",
561
+ "claude.logConnected": "\u4E2D\u7D99 {url} \u306B\u63A5\u7D9A\u3057\u307E\u3057\u305F",
562
+ "claude.logMsgUncaught": "\u30E1\u30C3\u30BB\u30FC\u30B8\u51E6\u7406\u4E2D\u306B\u672A\u6355\u6349\u306E\u4F8B\u5916: {message}",
563
+ "claude.reasonDisconnect": "\u4E2D\u7D99\u304C\u5207\u65AD\u3055\u308C\u307E\u3057\u305F\uFF08fail-closed \u81EA\u52D5\u62D2\u5426\uFF09",
564
+ "claude.reasonUnbind": "\u30A2\u30AB\u30A6\u30F3\u30C8\u9023\u643A\u89E3\u9664\uFF08fail-closed \u81EA\u52D5\u62D2\u5426\uFF09",
565
+ "claude.logDisconnected": "\u4E2D\u7D99\u304B\u3089\u5207\u65AD\u3001\u81EA\u52D5\u3067\u518D\u63A5\u7D9A\u4E2D\u2026",
566
+ "claude.logPairCode": "\u30DA\u30A2\u30EA\u30F3\u30B0\u30B3\u30FC\u30C9\u3092\u751F\u6210: {code}",
567
+ "claude.logUnhandledType": "\u672A\u51E6\u7406\u306E\u30E1\u30C3\u30BB\u30FC\u30B8\u30BF\u30A4\u30D7: {type}",
568
+ "claude.logHandleFailed": "{type} \u306E\u51E6\u7406\u306B\u5931\u6557: {message}",
569
+ "claude.newSessionTitle": "\u65B0\u3057\u3044\u30BB\u30C3\u30B7\u30E7\u30F3",
570
+ "claude.helpText": "\u4F7F\u7528\u53EF\u80FD\u306A\u30B3\u30DE\u30F3\u30C9\uFF1A\n- /new \u65B0\u3057\u3044\u30BB\u30C3\u30B7\u30E7\u30F3\n- /help \u30D8\u30EB\u30D7\u3092\u8868\u793A",
571
+ "claude.logPromptFailed": "session.prompt \u306B\u5931\u6557: {message}",
572
+ "claude.logAbortSent": "\u4E2D\u6B62\u3092\u9001\u4FE1\u3057\u307E\u3057\u305F (session={session})",
573
+ "claude.logAbortFailed": "\u4E2D\u6B62\u306B\u5931\u6557 (session={session}): {message}",
574
+ "claude.errBadWorkspaceEmpty": "\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u306E\u30D1\u30B9\u304C\u3042\u308A\u307E\u305B\u3093",
575
+ "claude.errBadWorkspaceNotDir": "\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u304C\u5B58\u5728\u3057\u306A\u3044\u304B\u5229\u7528\u3067\u304D\u307E\u305B\u3093",
576
+ "claude.logWorkspaceSwitched": "\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u3092\u5207\u66FF: {path}",
577
+ "claude.logUnknownQuestionReply": "\u4E00\u81F4\u3057\u306A\u3044\u69CB\u9020\u5316\u8CEA\u554F\u306E\u5FDC\u7B54 (id={id})",
578
+ "claude.logFlushedQuestions": "\u672A\u56DE\u7B54\u306E\u8CEA\u554F {count} \u4EF6\u3092\u30AD\u30E3\u30F3\u30BB\u30EB: {reason}",
579
+ "claude.logFlushedApprovals": "\u672A\u56DE\u7B54\u306E\u30EA\u30AF\u30A8\u30B9\u30C8 {count} \u4EF6\u3092\u62D2\u5426: {reason}",
580
+ "claude.logPendingResent": "session.pending: {session} \u306E\u4FDD\u7559\u9805\u76EE {count} \u4EF6\u3092\u518D\u9001\u4FE1",
581
+ "claude.logSetModeFailed": "setPermissionMode \u306B\u5931\u6557 (session={session}): {message}",
582
+ "claude.errWorkspaceMissing": "\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u304C\u5B58\u5728\u3057\u307E\u305B\u3093: {dir}",
583
+ "claude.logSessionError": "\u30BB\u30C3\u30B7\u30E7\u30F3\u30A8\u30E9\u30FC (session={session}): {message}",
584
+ "claude.logOfflineDenyTool": "\u4E2D\u7D99\u30AA\u30D5\u30E9\u30A4\u30F3\u3001\u30C4\u30FC\u30EB {tool} \u3092\u81EA\u52D5\u62D2\u5426",
585
+ "claude.errOfflineDeny": "\u4E2D\u7D99\u30AA\u30D5\u30E9\u30A4\u30F3\u3001\u81EA\u52D5\u62D2\u5426\uFF08fail-closed\uFF09",
586
+ "claude.logAuqDisabled": "AskUserQuestion \u306F\u7121\u52B9\u3067\u3059\uFF08TUNNELBOX_CLAUDE_ASK_USER_QUESTION=0\uFF09\u3001\u81EA\u52D5\u62D2\u5426",
587
+ "claude.errAuqDisabled": "AskUserQuestion \u306F\u6709\u52B9\u306B\u306A\u3063\u3066\u3044\u307E\u305B\u3093",
588
+ "claude.logAuqParseFailed": "AskUserQuestion \u5165\u529B\u306E\u89E3\u6790\u306B\u5931\u6557\u3001\u81EA\u52D5\u62D2\u5426\uFF08fail-closed\uFF09",
589
+ "claude.errAuqParse": "\u8CEA\u554F\u3092\u89E3\u6790\u3067\u304D\u307E\u305B\u3093",
590
+ "claude.logAuqSkipped": "AskUserQuestion \u672A\u56DE\u7B54/\u30AD\u30E3\u30F3\u30BB\u30EB (session={session})",
591
+ "claude.errAuqCancelled": "\u30E6\u30FC\u30B6\u30FC\u304C\u9078\u629E\u3057\u307E\u305B\u3093\u3067\u3057\u305F\uFF08\u30AD\u30E3\u30F3\u30BB\u30EB\uFF09",
592
+ "claude.logAuqDone": "AskUserQuestion \u306B\u56DE\u7B54\u3057\u307E\u3057\u305F (session={session})",
593
+ "claude.promptRunTool": "Claude Code \u304C {tool} \u306E\u5B9F\u884C\u3092\u8981\u6C42",
594
+ "claude.errApprovalTimeout": "\u627F\u8A8D\u304C\u30BF\u30A4\u30E0\u30A2\u30A6\u30C8\u3001\u81EA\u52D5\u62D2\u5426\uFF08fail-closed\uFF09",
595
+ "claude.errUserDenied": "\u30E6\u30FC\u30B6\u30FC\u304C\u62D2\u5426",
596
+ "claude.errAborted": "\u30EA\u30AF\u30A8\u30B9\u30C8\u306F\u4E2D\u6B62\u3055\u308C\u307E\u3057\u305F",
597
+ "claude.logPermToPhone": "\u6A29\u9650\u30EA\u30AF\u30A8\u30B9\u30C8(tool={tool}) \u2192 \u30B9\u30DE\u30DB\u3078\u9001\u4FE1 permissionID={id}",
598
+ "claude.errRelayUnreachable": "\u4E2D\u7D99\u306B\u5230\u9054\u3067\u304D\u305A\u3001\u81EA\u52D5\u62D2\u5426\uFF08fail-closed\uFF09",
599
+ "claude.logQuestionOffline": "\u4E2D\u7D99\u30AA\u30D5\u30E9\u30A4\u30F3\u3001\u69CB\u9020\u5316\u8CEA\u554F\u3092\u81EA\u52D5\u30AD\u30E3\u30F3\u30BB\u30EB",
600
+ "claude.logQuestionToPhone": "\u69CB\u9020\u5316\u8CEA\u554F\uFF08{count} \u554F\uFF09\u2192 \u30B9\u30DE\u30DB\u3078\u9001\u4FE1 questionID={id}",
601
+ "claude.logDialogOffline": "\u4E2D\u7D99\u30AA\u30D5\u30E9\u30A4\u30F3\u3001\u30C0\u30A4\u30A2\u30ED\u30B0\u3092\u81EA\u52D5\u30AD\u30E3\u30F3\u30BB\u30EB\uFF08{tool}\uFF09",
602
+ "claude.logDialogToPhone": "\u30C0\u30A4\u30A2\u30ED\u30B0\u8981\u6C42(kind={kind} tool={tool}) \u2192 \u30B9\u30DE\u30DB\u3078\u9001\u4FE1 permissionID={id}",
603
+ "claude.logAuqDialogParse": "AskUserQuestion \u30DA\u30A4\u30ED\u30FC\u30C9\u306E\u89E3\u6790\u306B\u5931\u6557\u3001\u65E2\u5B9A\u306E\u52D5\u4F5C\u3092\u7DAD\u6301\uFF08session={session}\uFF09",
604
+ "claude.logDialogUnmapped": "request_user_dialog(kind={kind}) \u306E\u30DE\u30C3\u30D4\u30F3\u30B0\u304C\u306A\u304F\u3001\u65E2\u5B9A\u306E\u52D5\u4F5C\u3092\u7DAD\u6301\uFF08session={session}\uFF09",
605
+ "claude.promptMcpAuth": "MCP\u300C{server}\u300D\u304C\u8A8D\u53EF\u3092\u8981\u6C42",
606
+ "claude.logElicitUnmapped": "MCP elicitation\uFF08server={server}, mode={mode}\uFF09\u306F\u672A\u5BFE\u5FDC\u3001\u81EA\u52D5\u62D2\u5426\uFF08session={session}\uFF09",
607
+ "claude.logResetIdentity": "\u30A2\u30AB\u30A6\u30F3\u30C8\u9023\u643A\u304C\u89E3\u9664\u3055\u308C\u307E\u3057\u305F\u3002ID \u3092\u30EA\u30BB\u30C3\u30C8\u3057\u3001\u65B0\u3057\u3044\u30DA\u30A2\u30EA\u30F3\u30B0\u30B3\u30FC\u30C9\u3092\u751F\u6210\u3057\u3066\u3044\u307E\u3059\u2026",
608
+ "claude.dialogConfirm": "Claude Code \u304C\u78BA\u8A8D\u3092\u8981\u6C42\uFF08{kind}\uFF09",
609
+ "claude.resultTruncated": "\u2026\uFF08\u7D50\u679C\u304C\u9577\u3059\u304E\u308B\u305F\u3081\u7701\u7565\uFF09",
610
+ "claude.streamError": "claude \u30B9\u30C8\u30EA\u30FC\u30DF\u30F3\u30B0\u30A8\u30E9\u30FC",
611
+ "claude.errListSessions": "Claude \u30BB\u30C3\u30B7\u30E7\u30F3\u4E00\u89A7\u306E\u8AAD\u307F\u53D6\u308A\u306B\u5931\u6557",
612
+ "claude.errReadHistory": "\u30BB\u30C3\u30B7\u30E7\u30F3\u5C65\u6B74\u306E\u8AAD\u307F\u53D6\u308A\u306B\u5931\u6557\uFF08{id}\uFF09",
613
+ "setup.langSet": "\u5B9F\u884C\u8A00\u8A9E\u3092\u8A2D\u5B9A\u3057\u307E\u3057\u305F",
614
+ "setup.langHint": "\u518D\u8D77\u52D5\u5F8C\u306B\u3053\u306E\u8A00\u8A9E\u3067\u51FA\u529B\u3057\u307E\u3059\uFF08\u5B9F\u884C\u6642\u306F TUNNELBOX_LANG \u3067\u4E0A\u66F8\u304D\u53EF\u80FD\uFF09\u3002\u72B6\u614B\u30D5\u30A1\u30A4\u30EB\uFF1A",
615
+ "setup.langCurrent": "\u73FE\u5728\u306E\u5B9F\u884C\u8A00\u8A9E"
616
+ };
617
+
618
+ // ../core/locales/ko-KR.json
619
+ var ko_KR_default = {
620
+ "agent.err.createSession": "\uC138\uC158 \uC0DD\uC131 \uC2E4\uD328",
621
+ "agent.err.httpClient": "opencode HTTP client\uC5D0 \uC811\uADFC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4",
622
+ "agent.err.unknownPerm": "\uC54C \uC218 \uC5C6\uB294 \uAD8C\uD55C \uC694\uCCAD: {id}",
623
+ "agent.err.unknownQuestion": "\uC54C \uC218 \uC5C6\uB294 \uC9C8\uBB38 \uC694\uCCAD: {id}",
624
+ "agent.log.fallback404": "\uC120\uD638 \uC5D4\uB4DC\uD3EC\uC778\uD2B8\uAC00 404\uB97C \uBC18\uD658\uD574 \uB2E4\uB978 \uC5D4\uB4DC\uD3EC\uC778\uD2B8\uB85C \uB300\uCCB4: {message}",
625
+ "agent.log.gotPermReply": "\uD734\uB300\uD3F0\uC5D0\uC11C \uAD8C\uD55C \uC751\uB2F5 \uC218\uC2E0 (id={id} {reply} {version})",
626
+ "agent.log.gotQuestionReply": "\uD734\uB300\uD3F0\uC5D0\uC11C \uC9C8\uBB38 \uC751\uB2F5 \uC218\uC2E0 (id={id} {status} {version})",
627
+ "agent.log.handleFailed": "{type} \uCC98\uB9AC \uC2E4\uD328: {message}",
628
+ "agent.log.httpOk": "opencode HTTP {status} {path}",
629
+ "agent.log.pendingResent": "session.pending: {session} \uBCF4\uB958 \uC694\uCCAD {count}\uAC74 \uC7AC\uC804\uC1A1",
630
+ "agent.log.unhandledType": "\uCC98\uB9AC\uB418\uC9C0 \uC54A\uC740 \uBA54\uC2DC\uC9C0 \uC720\uD615: {type}",
631
+ "events.log.fwdPermission": "\uAD8C\uD55C \uC694\uCCAD\uC744 \uD734\uB300\uD3F0\uC73C\uB85C \uC804\uB2EC (id={id} tool={tool} {version})",
632
+ "events.log.permissionReplied": "opencode \uC751\uB2F5 permission.replied (id={id} {response})",
633
+ "events.log.questionRejected": "opencode \uC751\uB2F5 question.rejected (id={id})",
634
+ "events.log.questionReplied": "opencode \uC751\uB2F5 question.replied (id={id})",
635
+ "index.error.connectRelay": "\uB9B4\uB808\uC774\uC5D0 \uC5F0\uACB0\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uB9B4\uB808\uC774 \uC8FC\uC18C\uB97C \uD655\uC778\uD55C \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694",
636
+ "index.error.pairTimeout": "\uD398\uC5B4\uB9C1 \uCF54\uB4DC \uB300\uAE30 \uC2DC\uAC04 \uCD08\uACFC(\uB9B4\uB808\uC774\uAC00 \uC751\uB2F5\uD558\uC9C0 \uC54A\uC74C)",
637
+ "index.log.claimed": "\uD734\uB300\uD3F0 \uACC4\uC815\uC5D0 \uC5F0\uACB0\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC774\uC81C \uD734\uB300\uD3F0\uC740 \u300C\uB0B4 PC\u300D\uC5D0\uC11C \uC9C1\uC811 \uC5F0\uACB0\uD558\uBA70 \uB2E4\uC2DC \uC2A4\uCE94\uD560 \uD544\uC694\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4",
638
+ "index.log.connectedRelay": "\uB9B4\uB808\uC774\uC5D0 \uC5F0\uACB0\uB428 {url}",
639
+ "index.log.disconnected": "\uB9B4\uB808\uC774 \uC5F0\uACB0\uC774 \uB04A\uACA8 \uC790\uB3D9\uC73C\uB85C \uC7AC\uC5F0\uACB0 \uC911\u2026",
640
+ "index.log.exit": "\uC885\uB8CC\uB428",
641
+ "index.log.pairCode": "\uD398\uC5B4\uB9C1 \uCF54\uB4DC \uC0DD\uC131: {code}",
642
+ "index.log.permissionToPhone": "\uAD8C\uD55C \uC694\uCCAD \u2192 \uD734\uB300\uD3F0 \uC2B9\uC778 (id={id} tool={tool})",
643
+ "index.log.permissionTuiFallback": "\uC571 \uC624\uD504\uB77C\uC778, \uAD8C\uD55C \uC694\uCCAD\uC740 opencode TUI\uAC00 \uCC98\uB9AC (id={id} tool={tool})",
644
+ "index.log.relayError": "\uB9B4\uB808\uC774 \uC624\uB958: {msg}",
645
+ "index.log.resetIdentity": "\uACC4\uC815\uC5D0\uC11C \uD574\uC81C\uB418\uC5B4 ID\uB97C \uC7AC\uC124\uC815\uD558\uACE0 \uC0C8 \uD398\uC5B4\uB9C1 \uCF54\uB4DC\uB97C \uC0DD\uC131\uD569\uB2C8\uB2E4\u2026",
646
+ "index.log.unknown": "\uC54C \uC218 \uC5C6\uC74C",
647
+ "install.codeLine": "\uD398\uC5B4\uB9C1 \uCF54\uB4DC: {code}",
648
+ "install.commandInstalled": "\uBA85\uB839 \uC124\uCE58\uB428: {path}",
649
+ "install.done": "\uC644\uB8CC. \uB9B4\uB808\uC774\uC5D0 \uC5F0\uACB0\uD574 \uC774 PC\uC758 \uD398\uC5B4\uB9C1 \uC815\uBCF4\uB97C \uCD9C\uB825\uD569\uB2C8\uB2E4:",
650
+ "install.failConnect": "(\uD398\uC5B4\uB9C1 \uC815\uBCF4\uB97C \uCD9C\uB825\uD558\uB824\uACE0 \uB9B4\uB808\uC774\uC5D0 \uC989\uC2DC \uC5F0\uACB0\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4 \u2014 \uB9B4\uB808\uC774\uC5D0 \uC5F0\uACB0\uD560 \uC218 \uC5C6\uAC70\uB098 Node \uBC84\uC804\uC774 \uB108\uBB34 \uC624\uB798\uB410\uC2B5\uB2C8\uB2E4.",
651
+ "install.failConnectHint": "opencode\uB97C \uB2E4\uC2DC \uC2DC\uC791\uD55C \uB4A4 --print-logs\uB85C \uD130\uBBF8\uB110 QR \uCF54\uB4DC\uB97C \uD655\uC778\uD558\uAC70\uB098, opencode \uC548\uC5D0\uC11C \u201C\uD734\uB300\uD3F0 \uD398\uC5B4\uB9C1 \uCF54\uB4DC \uD45C\uC2DC\u201D \uB610\uB294 /pair\uB97C \uC785\uB825\uD558\uC138\uC694.)",
652
+ "install.hintFresh": "\uCC38\uACE0: \uD398\uC5B4\uB9C1 \uCF54\uB4DC\uB294 \uC77C\uD68C\uC6A9\uC774\uBA70 \uC57D 10\uBD84\uAC04 \uC720\uD6A8\uD569\uB2C8\uB2E4. \uB098\uC911\uC5D0 opencode\uB97C \uC2DC\uC791\uD55C\uB2E4\uBA74 \uC2DC\uC791 \uC2DC \uCD9C\uB825\uB418\uAC70\uB098 /pair\uB85C \uBC1B\uC740 \uCD5C\uC2E0 \uC815\uBCF4\uB97C \uC0AC\uC6A9\uD558\uC138\uC694.",
653
+ "install.linkLine": "\uD398\uC5B4\uB9C1 \uB9C1\uD06C: {link}",
654
+ "install.pluginInstalled": "\uD50C\uB7EC\uADF8\uC778 \uC124\uCE58\uB428: {path}",
655
+ "install.qrUnavailable": "(QR \uCF54\uB4DC\uB97C \uD45C\uC2DC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uC704\uC758 \uB9C1\uD06C \uB610\uB294 \uD398\uC5B4\uB9C1 \uCF54\uB4DC\uB97C \uC0AC\uC6A9\uD558\uC138\uC694)",
656
+ "install.qrcodeEnsure": "qrcode\uB97C opencode \uC124\uC815 \uB514\uB809\uD130\uB9AC\uC5D0 \uC124\uCE58\uD558\uB294 \uC911\u2026",
657
+ "install.qrcodeFail": "qrcode \uC124\uCE58 \uC2E4\uD328(\uC0AC\uC6A9\uC5D0\uB294 \uC601\uD5A5\uC774 \uC5C6\uC73C\uBA70 QR\uC740 \uB9C1\uD06C\uB85C \uB300\uCCB4\uB429\uB2C8\uB2E4): {err}",
658
+ "install.relayError": "\uB9B4\uB808\uC774 \uC624\uB958 \uC751\uB2F5: {msg}",
659
+ "install.scanHint": "\uD734\uB300\uD3F0\uC5D0\uC11C tunnelbox \uC571 \u201C\uC2A4\uCE94\uD558\uC5EC \uC5F0\uACB0\u201D\uC744 \uC5F4\uAC70\uB098 \uCE74\uBA54\uB77C\uB85C \uC544\uB798 QR \uCF54\uB4DC\uB97C \uC2A4\uCE94\uD558\uC5EC \uC5F0\uACB0\uD558\uC138\uC694:",
660
+ "pair.heading": "tunnelbox \uD734\uB300\uD3F0 \uD398\uC5B4\uB9C1",
661
+ "pair.howTo": "\uD734\uB300\uD3F0 \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB9C1\uD06C\uB97C \uC5F4\uAC70\uB098 tunnelbox \uC571\uC5D0 \uD398\uC5B4\uB9C1 \uCF54\uB4DC\uB97C \uC785\uB825\uD558\uC138\uC694.",
662
+ "pair.oneTime": "\uD398\uC5B4\uB9C1 \uCF54\uB4DC\uB294 \uC77C\uD68C\uC6A9\uC774\uBA70 \uC0AC\uC6A9 \uD6C4\uC5D0\uB294 \uB9CC\uB8CC\uB429\uB2C8\uB2E4. \uC0C8 \uCF54\uB4DC\uAC00 \uD544\uC694\uD558\uBA74 \u201C\uD398\uC5B4\uB9C1 \uCF54\uB4DC \uC0C8\uB85C\uACE0\uCE68\u201D\uC744 \uC694\uCCAD\uD558\uC138\uC694.",
663
+ "pair.promptBody": "\uC774 PC\uC758 \uD734\uB300\uD3F0 \uC6D0\uACA9 \uD398\uC5B4\uB9C1 \uC815\uBCF4\uB97C \uBCF4\uC5EC\uC8FC\uC138\uC694: \uD398\uC5B4\uB9C1 \uCF54\uB4DC, \uD398\uC5B4\uB9C1 \uB9C1\uD06C, \uADF8\uB9AC\uACE0 \uC2A4\uCE94\uD560 \uC218 \uC788\uB294 QR \uCF54\uB4DC. QR \uCF54\uB4DC\uB294 \uACE0\uC815\uD3ED \uCF54\uB4DC \uBE14\uB85D\uC5D0 \uB123\uACE0 \uD55C \uAE00\uC790\uB3C4 \uBC14\uAFB8\uC9C0 \uB9D0\uACE0 \uADF8\uB300\uB85C \uD45C\uC2DC\uD574 \uC8FC\uC138\uC694. \uD734\uB300\uD3F0\uC73C\uB85C \uC2A4\uCE94\uD560 \uC218 \uC788\uB3C4\uB85D \uD558\uAE30 \uC704\uD568\uC785\uB2C8\uB2E4.",
664
+ "pair.promptDescription": "\uD734\uB300\uD3F0 \uC6D0\uACA9 \uD398\uC5B4\uB9C1\uC758 \uD398\uC5B4\uB9C1 \uCF54\uB4DC\xB7\uB9C1\uD06C\xB7\uC2A4\uCE94\uC6A9 QR \uCF54\uB4DC \uD45C\uC2DC",
665
+ "pair.scanIntro": "**\uC2A4\uCE94\uC73C\uB85C \uC5F0\uACB0**: \uD734\uB300\uD3F0\uC5D0\uC11C tunnelbox \uC571 \u201C\uC2A4\uCE94\uD558\uC5EC \uC5F0\uACB0\u201D\uC744 \uC5F4\uAC70\uB098 \uCE74\uBA54\uB77C\uB85C \uC544\uB798 QR \uCF54\uB4DC\uB97C \uC2A4\uCE94\uD558\uBA74 \uC790\uB3D9\uC73C\uB85C \uC5F0\uACB0\uB429\uB2C8\uB2E4.",
666
+ "perm.promptAuthorize": "opencode\uAC00 \uAD8C\uD55C\uC744 \uC694\uCCAD\uD569\uB2C8\uB2E4",
667
+ "perm.promptPatterns": "opencode\uAC00 \uC811\uADFC\uC744 \uC694\uCCAD: {list}",
668
+ "perm.promptResource": "opencode\uAC00 \uB9AC\uC18C\uC2A4 \uC811\uADFC\uC744 \uC694\uCCAD: {list}",
669
+ "perm.promptTool": "opencode\uAC00 {tool} \uC2E4\uD589\uC744 \uC694\uCCAD",
670
+ "qr.codeLabel": "\uD398\uC5B4\uB9C1 \uCF54\uB4DC",
671
+ "qr.fileWritten": "\uD398\uC5B4\uB9C1 \uC815\uBCF4\uAC00 \uAE30\uB85D\uB428: {path}",
672
+ "qr.linkLabel": "\uB9C1\uD06C",
673
+ "qr.openHint": "\uD734\uB300\uD3F0 \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB9C1\uD06C\uB97C \uC5F4\uAC70\uB098 \uC571\uC5D0\uC11C \uD398\uC5B4\uB9C1 \uCF54\uB4DC\uB97C \uC9C1\uC811 \uC785\uB825\uD558\uC138\uC694.",
674
+ "qr.titleNoType": "tunnelbox \uD734\uB300\uD3F0 \uD398\uC5B4\uB9C1",
675
+ "qr.titleWithType": "tunnelbox {type} \uD734\uB300\uD3F0 \uD398\uC5B4\uB9C1",
676
+ "qr.unavailable": "(qrcode\uAC00 \uC124\uCE58\uB418\uC9C0 \uC54A\uC74C \u2014 \uC704\uC758 \uB9C1\uD06C \uB610\uB294 \uD398\uC5B4\uB9C1 \uCF54\uB4DC\uB97C \uC0AC\uC6A9\uD558\uC138\uC694)",
677
+ "claude.errBusy": "\uC774 \uC138\uC158\uC774 \uC2E4\uD589 \uC911\uC785\uB2C8\uB2E4. \uC644\uB8CC\uB420 \uB54C\uAE4C\uC9C0 \uAE30\uB2E4\uB9AC\uAC70\uB098 \uBA3C\uC800 \uC911\uC9C0\uD558\uC138\uC694",
678
+ "claude.errTurn": "\uC0DD\uC131 \uC624\uB958",
679
+ "claude.pairingStillClaimed": "\uC774\uBBF8 \uACC4\uC815\uC5D0 \uC5F0\uACB0\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. \uC774 \uCF54\uB4DC\uB294 \uAC19\uC740 \uACC4\uC815\uC758 \uC0C8 \uAE30\uAE30 \uC5F0\uACB0 \uC804\uC6A9\uC774\uBA70, \uD604\uC7AC \uAE30\uAE30\uB294 '\uB0B4 \uCEF4\uD4E8\uD130'\uC5D0\uC11C \uC9C1\uC811 \uC5F0\uACB0\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4",
680
+ "claude.pairForced": "--pair: \uD398\uC5B4\uB9C1 \uCF54\uB4DC\uB97C \uAC15\uC81C \uC694\uCCAD\uD588\uC2B5\uB2C8\uB2E4(\uC774\uBC88 \uC2E4\uD589\uB9CC)",
681
+ "claude.pairUnclaimed": "\uC5F0\uACB0\uB41C \uACC4\uC815\uC744 \uAC10\uC9C0\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uD398\uC5B4\uB9C1 \uC548\uB0B4\uB97C \uC2DC\uC791\uD569\uB2C8\uB2E4:",
682
+ "claude.claimedHint": "\uD734\uB300\uD3F0 \uACC4\uC815\uC5D0 \uC5F0\uACB0\uB428: \uD734\uB300\uD3F0\uC758 '\uB0B4 \uCEF4\uD4E8\uD130'\uC5D0\uC11C \uC9C1\uC811 \uC5F0\uACB0\uD558\uC138\uC694",
683
+ "claude.errStartFailed": "\uC2DC\uC791 \uC2E4\uD328:",
684
+ "claude.msgStarted": "Claude Code \uC5B4\uB311\uD130\uAC00 \uC2DC\uC791\uB418\uC5C8\uC2B5\uB2C8\uB2E4(Ctrl+C\uB85C \uC885\uB8CC)",
685
+ "claude.warnInvalidMode": "\uC798\uBABB\uB41C TUNNELBOX_CLAUDE_MODE \uBB34\uC2DC: {raw}(\uD5C8\uC6A9 {list})",
686
+ "claude.warnInvalidThinking": "\uC798\uBABB\uB41C TUNNELBOX_CLAUDE_THINKING \uBB34\uC2DC: {raw}(off|adaptive|enabled[:budget])",
687
+ "claude.cmdNew": "\uC0C8 \uC138\uC158",
688
+ "claude.cmdHelp": "\uB3C4\uC6C0\uB9D0 \uBCF4\uAE30",
689
+ "claude.errNoRelay": "\uB9B4\uB808\uC774 URL\uC774 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4: --env\uB85C \uAE30\uBCF8\uAC12\uC744 \uC8FC\uC785\uD558\uAC70\uB098 \uC2E4\uD589 \uC2DC TUNNELBOX_RELAY_URL\uB85C \uB36E\uC5B4\uC4F0\uC138\uC694",
690
+ "claude.logConnected": "\uB9B4\uB808\uC774 {url}\uC5D0 \uC5F0\uACB0\uB428",
691
+ "claude.logMsgUncaught": "\uBA54\uC2DC\uC9C0 \uCC98\uB9AC \uC911 \uCC98\uB9AC\uB418\uC9C0 \uC54A\uC740 \uC608\uC678: {message}",
692
+ "claude.reasonDisconnect": "\uB9B4\uB808\uC774 \uC5F0\uACB0 \uB04A\uAE40(fail-closed \uC790\uB3D9 \uAC70\uBD80)",
693
+ "claude.reasonUnbind": "\uACC4\uC815 \uC5F0\uACB0 \uD574\uC81C(fail-closed \uC790\uB3D9 \uAC70\uBD80)",
694
+ "claude.logDisconnected": "\uB9B4\uB808\uC774 \uC5F0\uACB0\uC774 \uB04A\uC5B4\uC838 \uC790\uB3D9 \uC7AC\uC5F0\uACB0 \uC911\u2026",
695
+ "claude.logPairCode": "\uD398\uC5B4\uB9C1 \uCF54\uB4DC \uC0DD\uC131: {code}",
696
+ "claude.logUnhandledType": "\uCC98\uB9AC\uB418\uC9C0 \uC54A\uC740 \uBA54\uC2DC\uC9C0 \uC720\uD615: {type}",
697
+ "claude.logHandleFailed": "{type} \uCC98\uB9AC \uC2E4\uD328: {message}",
698
+ "claude.newSessionTitle": "\uC0C8 \uC138\uC158",
699
+ "claude.helpText": "\uC9C0\uC6D0 \uBA85\uB839:\n- /new \uC0C8 \uC138\uC158\n- /help \uB3C4\uC6C0\uB9D0 \uBCF4\uAE30",
700
+ "claude.logPromptFailed": "session.prompt \uC2E4\uD328: {message}",
701
+ "claude.logAbortSent": "\uC911\uC9C0 \uC804\uC1A1\uB428 (session={session})",
702
+ "claude.logAbortFailed": "\uC911\uC9C0 \uC2E4\uD328 (session={session}): {message}",
703
+ "claude.errBadWorkspaceEmpty": "\uC791\uC5C5 \uC601\uC5ED \uACBD\uB85C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4",
704
+ "claude.errBadWorkspaceNotDir": "\uB514\uB809\uD130\uB9AC\uAC00 \uC5C6\uAC70\uB098 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4",
705
+ "claude.logWorkspaceSwitched": "\uC791\uC5C5 \uC601\uC5ED \uC804\uD658: {path}",
706
+ "claude.logUnknownQuestionReply": "\uC77C\uCE58\uD558\uC9C0 \uC54A\uB294 \uAD6C\uC870\uD654 \uC9C8\uBB38 \uC751\uB2F5 (id={id})",
707
+ "claude.logFlushedQuestions": "\uB300\uAE30 \uC911 \uC9C8\uBB38 {count}\uAC1C \uCDE8\uC18C: {reason}",
708
+ "claude.logFlushedApprovals": "\uB300\uAE30 \uC911 \uC694\uCCAD {count}\uAC1C \uAC70\uBD80: {reason}",
709
+ "claude.logPendingResent": "session.pending: {session}\uC758 \uB300\uAE30 \uD56D\uBAA9 {count}\uAC1C \uC7AC\uC804\uC1A1",
710
+ "claude.logSetModeFailed": "setPermissionMode \uC2E4\uD328 (session={session}): {message}",
711
+ "claude.errWorkspaceMissing": "\uC791\uC5C5 \uC601\uC5ED\uC774 \uC5C6\uC2B5\uB2C8\uB2E4: {dir}",
712
+ "claude.logSessionError": "\uC138\uC158 \uC624\uB958 (session={session}): {message}",
713
+ "claude.logOfflineDenyTool": "\uB9B4\uB808\uC774 \uC624\uD504\uB77C\uC778, \uB3C4\uAD6C {tool} \uC790\uB3D9 \uAC70\uBD80",
714
+ "claude.errOfflineDeny": "\uB9B4\uB808\uC774 \uC624\uD504\uB77C\uC778, \uC790\uB3D9 \uAC70\uBD80(fail-closed)",
715
+ "claude.logAuqDisabled": "AskUserQuestion \uBE44\uD65C\uC131\uD654\uB428(TUNNELBOX_CLAUDE_ASK_USER_QUESTION=0), \uC790\uB3D9 \uAC70\uBD80",
716
+ "claude.errAuqDisabled": "AskUserQuestion\uC774 \uD65C\uC131\uD654\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4",
717
+ "claude.logAuqParseFailed": "AskUserQuestion \uC785\uB825 \uAD6C\uBB38 \uBD84\uC11D \uC2E4\uD328, \uC790\uB3D9 \uAC70\uBD80(fail-closed)",
718
+ "claude.errAuqParse": "\uC9C8\uBB38\uC744 \uAD6C\uBB38 \uBD84\uC11D\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4",
719
+ "claude.logAuqSkipped": "AskUserQuestion \uBB34\uC751\uB2F5/\uCDE8\uC18C\uB428 (session={session})",
720
+ "claude.errAuqCancelled": "\uC0AC\uC6A9\uC790\uAC00 \uC120\uD0DD\uD558\uC9C0 \uC54A\uC74C(\uCDE8\uC18C\uB428)",
721
+ "claude.logAuqDone": "AskUserQuestion \uB2F5\uBCC0 \uC644\uB8CC (session={session})",
722
+ "claude.promptRunTool": "Claude Code\uAC00 {tool} \uC2E4\uD589\uC744 \uC694\uCCAD",
723
+ "claude.errApprovalTimeout": "\uC2B9\uC778 \uC2DC\uAC04 \uCD08\uACFC, \uC790\uB3D9 \uAC70\uBD80(fail-closed)",
724
+ "claude.errUserDenied": "\uC0AC\uC6A9\uC790\uAC00 \uAC70\uBD80",
725
+ "claude.errAborted": "\uC694\uCCAD\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4",
726
+ "claude.logPermToPhone": "\uAD8C\uD55C \uC694\uCCAD(tool={tool}) \u2192 \uD734\uB300\uD3F0\uC73C\uB85C \uC804\uC1A1 permissionID={id}",
727
+ "claude.errRelayUnreachable": "\uB9B4\uB808\uC774\uC5D0 \uC5F0\uACB0\uD560 \uC218 \uC5C6\uC74C, \uC790\uB3D9 \uAC70\uBD80(fail-closed)",
728
+ "claude.logQuestionOffline": "\uB9B4\uB808\uC774 \uC624\uD504\uB77C\uC778, \uAD6C\uC870\uD654 \uC9C8\uBB38 \uC790\uB3D9 \uCDE8\uC18C",
729
+ "claude.logQuestionToPhone": "\uAD6C\uC870\uD654 \uC9C8\uBB38({count}\uAC1C) \u2192 \uD734\uB300\uD3F0\uC73C\uB85C \uC804\uC1A1 questionID={id}",
730
+ "claude.logDialogOffline": "\uB9B4\uB808\uC774 \uC624\uD504\uB77C\uC778, \uB300\uD654 \uC0C1\uC790 \uC790\uB3D9 \uCDE8\uC18C({tool})",
731
+ "claude.logDialogToPhone": "\uB300\uD654 \uC0C1\uC790 \uC694\uCCAD(kind={kind} tool={tool}) \u2192 \uD734\uB300\uD3F0\uC73C\uB85C \uC804\uC1A1 permissionID={id}",
732
+ "claude.logAuqDialogParse": "AskUserQuestion \uD398\uC774\uB85C\uB4DC \uAD6C\uBB38 \uBD84\uC11D \uC2E4\uD328, \uAE30\uBCF8 \uB3D9\uC791 \uC720\uC9C0(session={session})",
733
+ "claude.logDialogUnmapped": "request_user_dialog(kind={kind}) \uB9E4\uD551 \uC5C6\uC74C, \uAE30\uBCF8 \uB3D9\uC791 \uC720\uC9C0(session={session})",
734
+ "claude.promptMcpAuth": 'MCP "{server}" \uC778\uC99D \uC694\uCCAD',
735
+ "claude.logElicitUnmapped": "MCP elicitation(server={server}, mode={mode}) \uBBF8\uCC98\uB9AC, \uC790\uB3D9 \uAC70\uBD80(session={session})",
736
+ "claude.logResetIdentity": "\uACC4\uC815 \uC5F0\uACB0\uC774 \uD574\uC81C\uB418\uC5C8\uC2B5\uB2C8\uB2E4. ID\uB97C \uC7AC\uC124\uC815\uD558\uACE0 \uC0C8 \uD398\uC5B4\uB9C1 \uCF54\uB4DC\uB97C \uC0DD\uC131\uD558\uB294 \uC911\u2026",
737
+ "claude.dialogConfirm": "Claude Code\uAC00 \uD655\uC778\uC744 \uC694\uCCAD({kind})",
738
+ "claude.resultTruncated": "\u2026(\uACB0\uACFC\uAC00 \uB108\uBB34 \uAE38\uC5B4 \uC798\uB9BC)",
739
+ "claude.streamError": "claude \uC2A4\uD2B8\uB9AC\uBC0D \uC624\uB958",
740
+ "claude.errListSessions": "Claude \uC138\uC158 \uBAA9\uB85D\uC744 \uC77D\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4",
741
+ "claude.errReadHistory": "\uC138\uC158 \uAE30\uB85D\uC744 \uC77D\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4({id})",
742
+ "setup.langSet": "\uC2E4\uD589 \uC5B8\uC5B4\uB97C \uC124\uC815\uD588\uC2B5\uB2C8\uB2E4",
743
+ "setup.langHint": "\uB2E4\uC2DC \uC2DC\uC791\uD558\uBA74 \uC774 \uC5B8\uC5B4\uB85C \uCD9C\uB825\uB429\uB2C8\uB2E4(\uC2E4\uD589 \uC2DC TUNNELBOX_LANG\uC73C\uB85C \uC7AC\uC815\uC758 \uAC00\uB2A5). \uC0C1\uD0DC \uD30C\uC77C:",
744
+ "setup.langCurrent": "\uD604\uC7AC \uC2E4\uD589 \uC5B8\uC5B4"
745
+ };
746
+
747
+ // ../core/locales/fr-FR.json
748
+ var fr_FR_default = {
749
+ "agent.err.createSession": "\xC9chec de la cr\xE9ation de la session",
750
+ "agent.err.httpClient": "Impossible d'acc\xE9der au client HTTP d'opencode",
751
+ "agent.err.unknownPerm": "Demande d'autorisation inconnue : {id}",
752
+ "agent.err.unknownQuestion": "Demande de question inconnue : {id}",
753
+ "agent.log.fallback404": "Le point d'acc\xE8s pr\xE9f\xE9r\xE9 a renvoy\xE9 404, bascule sur l'autre : {message}",
754
+ "agent.log.gotPermReply": "R\xE9ponse d'autorisation re\xE7ue du t\xE9l\xE9phone (id={id} {reply} {version})",
755
+ "agent.log.gotQuestionReply": "R\xE9ponse de question re\xE7ue du t\xE9l\xE9phone (id={id} {status} {version})",
756
+ "agent.log.handleFailed": "\xC9chec du traitement de {type} : {message}",
757
+ "agent.log.httpOk": "opencode HTTP {status} {path}",
758
+ "agent.log.pendingResent": "session.pending : renvoi de {count} demande(s) en attente pour {session}",
759
+ "agent.log.unhandledType": "Type de message non g\xE9r\xE9 : {type}",
760
+ "events.log.fwdPermission": "Demande d'autorisation transmise au t\xE9l\xE9phone (id={id} tool={tool} {version})",
761
+ "events.log.permissionReplied": "opencode a accus\xE9 permission.replied (id={id} {response})",
762
+ "events.log.questionRejected": "opencode a accus\xE9 question.rejected (id={id})",
763
+ "events.log.questionReplied": "opencode a accus\xE9 question.replied (id={id})",
764
+ "index.error.connectRelay": "Impossible de se connecter au relais ; v\xE9rifiez son adresse et r\xE9essayez",
765
+ "index.error.pairTimeout": "D\xE9lai d'attente du code d'appairage d\xE9pass\xE9 (le relais ne r\xE9pond pas)",
766
+ "index.log.claimed": "Li\xE9 \xE0 votre compte t\xE9l\xE9phone. Le t\xE9l\xE9phone se connecte d\xE9sormais directement depuis \xAB Mes PC \xBB ; plus besoin de scanner",
767
+ "index.log.connectedRelay": "Connect\xE9 au relais {url}",
768
+ "index.log.disconnected": "D\xE9connect\xE9 du relais, reconnexion automatique\u2026",
769
+ "index.log.exit": "Arr\xEAt\xE9",
770
+ "index.log.pairCode": "Code d'appairage g\xE9n\xE9r\xE9 : {code}",
771
+ "index.log.permissionToPhone": "Demande d'autorisation \u2192 approbation sur t\xE9l\xE9phone (id={id} tool={tool})",
772
+ "index.log.permissionTuiFallback": "App hors ligne ; la demande est g\xE9r\xE9e par le TUI opencode (id={id} tool={tool})",
773
+ "index.log.relayError": "Erreur du relais : {msg}",
774
+ "index.log.resetIdentity": "D\xE9li\xE9 du compte ; r\xE9initialisation de l'identit\xE9 et g\xE9n\xE9ration d'un nouveau code d'appairage\u2026",
775
+ "index.log.unknown": "Inconnu",
776
+ "install.codeLine": "Code d'appairage : {code}",
777
+ "install.commandInstalled": "Commande install\xE9e : {path}",
778
+ "install.done": "Termin\xE9. Tentative de connexion au relais pour afficher les informations d'appairage :",
779
+ "install.failConnect": "(Impossible de se connecter au relais pour afficher l'appairage \u2014 relais injoignable ou version de Node trop ancienne.",
780
+ "install.failConnectHint": "Apr\xE8s le red\xE9marrage d'opencode, consultez le QR code du terminal avec --print-logs, ou tapez \u201Cafficher le code d'appairage\u201D / /pair dans opencode.)",
781
+ "install.hintFresh": "Remarque : le code d'appairage est \xE0 usage unique et valide environ 10 minutes. Si vous d\xE9marrez opencode plus tard, utilisez les informations imprim\xE9es au d\xE9marrage ou obtenues via /pair.",
782
+ "install.linkLine": "Lien d'appairage : {link}",
783
+ "install.pluginInstalled": "Plugin install\xE9 : {path}",
784
+ "install.qrUnavailable": "(Rendu du QR indisponible \u2014 utilisez le lien ou le code ci-dessus)",
785
+ "install.qrcodeEnsure": "Installation de qrcode dans le r\xE9pertoire de configuration d'opencode\u2026",
786
+ "install.qrcodeFail": "\xC9chec de l'installation de qrcode (sans cons\xE9quence \u2014 le QR est remplac\xE9 par un lien) : {err}",
787
+ "install.relayError": "Le relais a renvoy\xE9 une erreur : {msg}",
788
+ "install.scanHint": "Ouvrez tunnelbox sur votre t\xE9l\xE9phone, touchez \u201CScanner pour appairer\u201D, ou scannez le QR ci-dessous avec la cam\xE9ra :",
789
+ "pair.heading": "Appairage t\xE9l\xE9phone tunnelbox",
790
+ "pair.howTo": "Ouvrez le lien dans le navigateur du t\xE9l\xE9phone, ou saisissez le code d'appairage dans l'application tunnelbox.",
791
+ "pair.oneTime": "Le code d'appairage est \xE0 usage unique et expire apr\xE8s utilisation ; demandez \xE0 \u201Craffra\xEEchir le code d'appairage\u201D pour en obtenir un nouveau.",
792
+ "pair.promptBody": "Veuillez afficher les informations d'appairage t\xE9l\xE9phone de cet ordinateur : le code d'appairage, le lien d'appairage et un QR code scannable. Gardez le QR code dans un bloc de code \xE0 largeur fixe et reproduisez-le caract\xE8re par caract\xE8re pour que mon t\xE9l\xE9phone puisse le scanner.",
793
+ "pair.promptDescription": "Afficher le code d'appairage, le lien et le QR code scannable",
794
+ "pair.scanIntro": "**Appairer par scan** : ouvrez l'application tunnelbox sur votre t\xE9l\xE9phone (\u201CScanner pour appairer\u201D) ou utilisez la cam\xE9ra pour scanner le QR ci-dessous et vous connecter automatiquement.",
795
+ "perm.promptAuthorize": "opencode demande une autorisation",
796
+ "perm.promptPatterns": "opencode demande l'acc\xE8s \xE0 : {list}",
797
+ "perm.promptResource": "opencode demande l'acc\xE8s aux ressources : {list}",
798
+ "perm.promptTool": "opencode demande d'ex\xE9cuter {tool}",
799
+ "qr.codeLabel": "Code d'appairage",
800
+ "qr.fileWritten": "Informations d'appairage \xE9crites dans : {path}",
801
+ "qr.linkLabel": "Lien",
802
+ "qr.openHint": "Ouvrez le lien dans le navigateur du t\xE9l\xE9phone, ou saisissez le code d'appairage dans l'application.",
803
+ "qr.titleNoType": "appairage t\xE9l\xE9phone tunnelbox",
804
+ "qr.titleWithType": "appairage t\xE9l\xE9phone tunnelbox {type}",
805
+ "qr.unavailable": "(qrcode non install\xE9 \u2014 utilisez le lien ou le code d'appairage ci-dessus)",
806
+ "claude.errBusy": "Cette session est en cours d'ex\xE9cution ; attendez la fin ou arr\xEAtez-la d'abord",
807
+ "claude.errTurn": "erreur de g\xE9n\xE9ration",
808
+ "claude.pairingStillClaimed": "D\xE9j\xE0 li\xE9 \xE0 un compte t\xE9l\xE9phone : ce code sert uniquement \xE0 appairer un nouvel appareil sur le m\xEAme compte ; l'appareil actuel se connecte directement depuis Mes ordinateurs",
809
+ "claude.pairForced": "--pair : demande forc\xE9e d'un code d'appairage (cette ex\xE9cution uniquement)",
810
+ "claude.pairUnclaimed": "Aucun compte li\xE9 d\xE9tect\xE9 ; lancement du guide d'appairage :",
811
+ "claude.claimedHint": "Li\xE9 \xE0 un compte t\xE9l\xE9phone : connectez-vous directement depuis Mes ordinateurs sur le t\xE9l\xE9phone",
812
+ "claude.errStartFailed": "\xC9chec du d\xE9marrage :",
813
+ "claude.msgStarted": "Adaptateur Claude Code d\xE9marr\xE9 (Ctrl+C pour quitter)",
814
+ "claude.warnInvalidMode": "TUNNELBOX_CLAUDE_MODE invalide ignor\xE9 : {raw} (autoris\xE9 {list})",
815
+ "claude.warnInvalidThinking": "TUNNELBOX_CLAUDE_THINKING invalide ignor\xE9 : {raw} (off|adaptive|enabled[:budget])",
816
+ "claude.cmdNew": "Nouvelle session",
817
+ "claude.cmdHelp": "Afficher l'aide",
818
+ "claude.errNoRelay": "Aucune URL de relais configur\xE9e : injectez une valeur par d\xE9faut avec --env, ou remplacez-la via TUNNELBOX_RELAY_URL",
819
+ "claude.logConnected": "Connect\xE9 au relais {url}",
820
+ "claude.logMsgUncaught": "Exception non intercept\xE9e lors du traitement du message : {message}",
821
+ "claude.reasonDisconnect": "Relais d\xE9connect\xE9 (rejet automatique fail-closed)",
822
+ "claude.reasonUnbind": "Compte dissoci\xE9 (rejet automatique fail-closed)",
823
+ "claude.logDisconnected": "D\xE9connect\xE9 du relais, reconnexion automatique\u2026",
824
+ "claude.logPairCode": "Code d'appairage g\xE9n\xE9r\xE9 : {code}",
825
+ "claude.logUnhandledType": "Type de message non g\xE9r\xE9 : {type}",
826
+ "claude.logHandleFailed": "\xC9chec du traitement de {type} : {message}",
827
+ "claude.newSessionTitle": "Nouvelle session",
828
+ "claude.helpText": "Commandes prises en charge :\n- /new nouvelle session\n- /help afficher l'aide",
829
+ "claude.logPromptFailed": "\xC9chec de session.prompt : {message}",
830
+ "claude.logAbortSent": "Interruption envoy\xE9e (session={session})",
831
+ "claude.logAbortFailed": "\xC9chec de l'interruption (session={session}) : {message}",
832
+ "claude.errBadWorkspaceEmpty": "Chemin de l'espace de travail manquant",
833
+ "claude.errBadWorkspaceNotDir": "Le r\xE9pertoire n'existe pas ou est inutilisable",
834
+ "claude.logWorkspaceSwitched": "Espace de travail chang\xE9 : {path}",
835
+ "claude.logUnknownQuestionReply": "R\xE9ponse de question structur\xE9e sans correspondance (id={id})",
836
+ "claude.logFlushedQuestions": "{count} question(s) en attente annul\xE9e(s) : {reason}",
837
+ "claude.logFlushedApprovals": "{count} demande(s) en attente rejet\xE9e(s) : {reason}",
838
+ "claude.logPendingResent": "session.pending : {count} \xE9l\xE9ment(s) en attente renvoy\xE9(s) pour {session}",
839
+ "claude.logSetModeFailed": "\xC9chec de setPermissionMode (session={session}) : {message}",
840
+ "claude.errWorkspaceMissing": "L'espace de travail n'existe pas : {dir}",
841
+ "claude.logSessionError": "Erreur de session (session={session}) : {message}",
842
+ "claude.logOfflineDenyTool": "Relais hors ligne, rejet automatique de l'outil {tool}",
843
+ "claude.errOfflineDeny": "Relais hors ligne, rejet automatique (fail-closed)",
844
+ "claude.logAuqDisabled": "AskUserQuestion d\xE9sactiv\xE9 (TUNNELBOX_CLAUDE_ASK_USER_QUESTION=0), rejet automatique",
845
+ "claude.errAuqDisabled": "AskUserQuestion n'est pas activ\xE9",
846
+ "claude.logAuqParseFailed": "\xC9chec de l'analyse de l'entr\xE9e AskUserQuestion, rejet automatique (fail-closed)",
847
+ "claude.errAuqParse": "Impossible d'analyser les questions",
848
+ "claude.logAuqSkipped": "AskUserQuestion sans r\xE9ponse/annul\xE9e (session={session})",
849
+ "claude.errAuqCancelled": "L'utilisateur n'a pas fait de choix (annul\xE9)",
850
+ "claude.logAuqDone": "AskUserQuestion r\xE9pondue (session={session})",
851
+ "claude.promptRunTool": "Claude Code demande \xE0 ex\xE9cuter {tool}",
852
+ "claude.errApprovalTimeout": "Approbation expir\xE9e, rejet automatique (fail-closed)",
853
+ "claude.errUserDenied": "Rejet\xE9 par l'utilisateur",
854
+ "claude.errAborted": "Requ\xEAte interrompue",
855
+ "claude.logPermToPhone": "Demande d'autorisation (tool={tool}) \u2192 envoy\xE9e au t\xE9l\xE9phone permissionID={id}",
856
+ "claude.errRelayUnreachable": "Relais injoignable, rejet automatique (fail-closed)",
857
+ "claude.logQuestionOffline": "Relais hors ligne, question structur\xE9e annul\xE9e automatiquement",
858
+ "claude.logQuestionToPhone": "Question structur\xE9e ({count} \xE9l\xE9ment(s)) \u2192 envoy\xE9e au t\xE9l\xE9phone questionID={id}",
859
+ "claude.logDialogOffline": "Relais hors ligne, dialogue annul\xE9 automatiquement ({tool})",
860
+ "claude.logDialogToPhone": "Demande de dialogue (kind={kind} tool={tool}) \u2192 envoy\xE9e au t\xE9l\xE9phone permissionID={id}",
861
+ "claude.logAuqDialogParse": "\xC9chec de l'analyse du payload AskUserQuestion, comportement par d\xE9faut conserv\xE9 (session={session})",
862
+ "claude.logDialogUnmapped": "request_user_dialog(kind={kind}) sans correspondance, comportement par d\xE9faut conserv\xE9 (session={session})",
863
+ "claude.promptMcpAuth": "MCP \xAB {server} \xBB demande une autorisation",
864
+ "claude.logElicitUnmapped": "\xC9licitation MCP (server={server}, mode={mode}) non trait\xE9e, rejet automatique (session={session})",
865
+ "claude.logResetIdentity": "Compte dissoci\xE9 ; r\xE9initialisation de l'identit\xE9 et g\xE9n\xE9ration d'un nouveau code d'appairage\u2026",
866
+ "claude.dialogConfirm": "Claude Code demande une confirmation ({kind})",
867
+ "claude.resultTruncated": "\u2026 (r\xE9sultat tronqu\xE9)",
868
+ "claude.streamError": "erreur de streaming claude",
869
+ "claude.errListSessions": "\xC9chec de la lecture de la liste des sessions Claude",
870
+ "claude.errReadHistory": "\xC9chec de la lecture de l'historique de session ({id})",
871
+ "setup.langSet": "Langue d'ex\xE9cution d\xE9finie sur",
872
+ "setup.langHint": "L'adaptateur utilisera cette langue au prochain d\xE9marrage (TUNNELBOX_LANG peut la remplacer). Fichier d'\xE9tat :",
873
+ "setup.langCurrent": "Langue d'ex\xE9cution actuelle"
874
+ };
875
+
876
+ // ../core/locales/de-DE.json
877
+ var de_DE_default = {
878
+ "agent.err.createSession": "Sitzung konnte nicht erstellt werden",
879
+ "agent.err.httpClient": "Auf den opencode-HTTP-Client kann nicht zugegriffen werden",
880
+ "agent.err.unknownPerm": "Unbekannte Berechtigungsanfrage: {id}",
881
+ "agent.err.unknownQuestion": "Unbekannte Frageanfrage: {id}",
882
+ "agent.log.fallback404": "Bevorzugter Endpunkt lieferte 404, Fallback auf den anderen: {message}",
883
+ "agent.log.gotPermReply": "Berechtigungsantwort vom Telefon erhalten (id={id} {reply} {version})",
884
+ "agent.log.gotQuestionReply": "Frageantwort vom Telefon erhalten (id={id} {status} {version})",
885
+ "agent.log.handleFailed": "Fehler beim Verarbeiten von {type}: {message}",
886
+ "agent.log.httpOk": "opencode HTTP {status} {path}",
887
+ "agent.log.pendingResent": "session.pending: {count} ausstehende Anfrage(n) f\xFCr {session} erneut gesendet",
888
+ "agent.log.unhandledType": "Nicht behandelter Nachrichtentyp: {type}",
889
+ "events.log.fwdPermission": "Berechtigungsanfrage an Telefon weitergeleitet (id={id} tool={tool} {version})",
890
+ "events.log.permissionReplied": "opencode quittierte permission.replied (id={id} {response})",
891
+ "events.log.questionRejected": "opencode quittierte question.rejected (id={id})",
892
+ "events.log.questionReplied": "opencode quittierte question.replied (id={id})",
893
+ "index.error.connectRelay": "Verbindung zum Relay nicht m\xF6glich; pr\xFCfen Sie die Relay-Adresse und versuchen Sie es erneut",
894
+ "index.error.pairTimeout": "Zeit\xFCberschreitung beim Warten auf einen Pairing-Code (Relay antwortet nicht)",
895
+ "index.log.claimed": "An Ihr Telefonkonto gebunden. Das Telefon verbindet sich nun direkt \xFCber \u201CMeine PCs\u201D; kein erneutes Scannen n\xF6tig",
896
+ "index.log.connectedRelay": "Mit Relay verbunden {url}",
897
+ "index.log.disconnected": "Vom Relay getrennt, verbinde automatisch neu\u2026",
898
+ "index.log.exit": "Beendet",
899
+ "index.log.pairCode": "Pairing-Code erzeugt: {code}",
900
+ "index.log.permissionToPhone": "Berechtigungsanfrage \u2192 Genehmigung am Telefon (id={id} tool={tool})",
901
+ "index.log.permissionTuiFallback": "App offline; Berechtigungsanfrage wird vom opencode-TUI \xFCbernommen (id={id} tool={tool})",
902
+ "index.log.relayError": "Relay-Fehler: {msg}",
903
+ "index.log.resetIdentity": "Vom Konto gel\xF6st; Identit\xE4t wird zur\xFCckgesetzt und ein neuer Pairing-Code erzeugt\u2026",
904
+ "index.log.unknown": "Unbekannt",
905
+ "install.codeLine": "Pairing-Code: {code}",
906
+ "install.commandInstalled": "Befehl installiert: {path}",
907
+ "install.done": "Fertig. Versuche, eine Verbindung zum Relay herzustellen und Pairing-Informationen auszugeben:",
908
+ "install.failConnect": "(Keine sofortige Verbindung zum Relay zum Drucken der Pairing-Informationen m\xF6glich \u2014 Relay nicht erreichbar oder Node zu alt.",
909
+ "install.failConnectHint": "Nach dem Neustart von opencode den Terminal-QR-Code mit --print-logs anzeigen oder im opencode \u201CTelefon-Pairing-Code anzeigen\u201D bzw. /pair eingeben.)",
910
+ "install.hintFresh": "Hinweis: Der Pairing-Code ist einmalig und etwa 10 Minuten g\xFCltig. Wenn Sie opencode sp\xE4ter starten, nutzen Sie die beim Start gedruckten oder per /pair abgerufenen aktuellen Informationen.",
911
+ "install.linkLine": "Pairing-Link: {link}",
912
+ "install.pluginInstalled": "Plugin installiert: {path}",
913
+ "install.qrUnavailable": "(QR-Darstellung nicht verf\xFCgbar \u2014 nutzen Sie den Link oder Code oben)",
914
+ "install.qrcodeEnsure": "Installiere qrcode in das opencode-Konfigurationsverzeichnis\u2026",
915
+ "install.qrcodeFail": "qrcode-Installation fehlgeschlagen (nicht kritisch \u2014 QR wird durch einen Link ersetzt): {err}",
916
+ "install.relayError": "Relay hat einen Fehler zur\xFCckgegeben: {msg}",
917
+ "install.scanHint": "\xD6ffnen Sie tunnelbox auf dem Telefon, tippen Sie auf \u201CZum Pairing scannen\u201D, oder scannen Sie den QR-Code unten mit der Kamera:",
918
+ "pair.heading": "tunnelbox Telefon-Pairing",
919
+ "pair.howTo": "\xD6ffnen Sie den Link im Telefon-Browser oder geben Sie den Pairing-Code in der tunnelbox-App ein.",
920
+ "pair.oneTime": "Der Pairing-Code ist einmalig und verf\xE4llt nach der Nutzung; f\xFCr einen neuen Code \u201CPairing-Code aktualisieren\u201D anfordern.",
921
+ "pair.promptBody": "Zeigen Sie bitte die Telefon-Pairing-Informationen dieses Computers an: den Pairing-Code, den Pairing-Link und einen scannbaren QR-Code. Behalten Sie den QR-Code in einem Codeblock mit fester Breite und geben Sie ihn zeichengenau wieder, damit mein Telefon ihn scannen kann.",
922
+ "pair.promptDescription": "Pairing-Code, Pairing-Link und scannbaren QR-Code anzeigen",
923
+ "pair.scanIntro": "**Per Scan pairen**: \xD6ffnen Sie die tunnelbox-App auf dem Telefon (\u201CZum Pairing scannen\u201D) oder scannen Sie den QR-Code unten mit der Kamera, um sich automatisch zu verbinden.",
924
+ "perm.promptAuthorize": "opencode fordert eine Genehmigung an",
925
+ "perm.promptPatterns": "opencode fordert Zugriff auf: {list}",
926
+ "perm.promptResource": "opencode fordert Zugriff auf Ressourcen: {list}",
927
+ "perm.promptTool": "opencode fordert die Ausf\xFChrung von {tool} an",
928
+ "qr.codeLabel": "Pairing-Code",
929
+ "qr.fileWritten": "Pairing-Informationen geschrieben in: {path}",
930
+ "qr.linkLabel": "Link",
931
+ "qr.openHint": "\xD6ffnen Sie den Link im Telefon-Browser oder geben Sie den Pairing-Code manuell in der App ein.",
932
+ "qr.titleNoType": "tunnelbox Telefon-Pairing",
933
+ "qr.titleWithType": "tunnelbox {type} Telefon-Pairing",
934
+ "qr.unavailable": "(qrcode nicht installiert \u2014 nutzen Sie den Link oder Pairing-Code oben)",
935
+ "claude.errBusy": "Diese Sitzung l\xE4uft noch; warten Sie auf das Ende oder stoppen Sie sie zuerst",
936
+ "claude.errTurn": "Generierungsfehler",
937
+ "claude.pairingStillClaimed": "Bereits an ein Telefonkonto gebunden: Dieser Code dient nur zum Koppeln eines neuen Ger\xE4ts auf demselben Konto; das aktuelle Ger\xE4t verbindet sich direkt \xFCber \u201EMeine Computer\u201C",
938
+ "claude.pairForced": "--pair: Pairing-Code erzwungen (nur dieser Lauf)",
939
+ "claude.pairUnclaimed": "Kein gebundenes Konto erkannt; Start der Pairing-Anleitung:",
940
+ "claude.claimedHint": "An ein Telefonkonto gebunden: direkt \xFCber \u201EMeine Computer\u201C auf dem Telefon verbinden",
941
+ "claude.errStartFailed": "Start fehlgeschlagen:",
942
+ "claude.msgStarted": "Claude-Code-Adapter gestartet (Strg+C zum Beenden)",
943
+ "claude.warnInvalidMode": "Ung\xFCltiges TUNNELBOX_CLAUDE_MODE ignoriert: {raw} (erlaubt {list})",
944
+ "claude.warnInvalidThinking": "Ung\xFCltiges TUNNELBOX_CLAUDE_THINKING ignoriert: {raw} (off|adaptive|enabled[:budget])",
945
+ "claude.cmdNew": "Neue Sitzung",
946
+ "claude.cmdHelp": "Hilfe anzeigen",
947
+ "claude.errNoRelay": "Keine Relay-URL konfiguriert: Standard mit --env injizieren oder zur Laufzeit per TUNNELBOX_RELAY_URL \xFCberschreiben",
948
+ "claude.logConnected": "Mit Relay {url} verbunden",
949
+ "claude.logMsgUncaught": "Unbehandelte Ausnahme bei der Nachrichtenverarbeitung: {message}",
950
+ "claude.reasonDisconnect": "Relay getrennt (fail-closed automatische Ablehnung)",
951
+ "claude.reasonUnbind": "Konto entbunden (fail-closed automatische Ablehnung)",
952
+ "claude.logDisconnected": "Vom Relay getrennt, verbinde automatisch neu\u2026",
953
+ "claude.logPairCode": "Pairing-Code generiert: {code}",
954
+ "claude.logUnhandledType": "Unbehandelter Nachrichtentyp: {type}",
955
+ "claude.logHandleFailed": "{type} konnte nicht verarbeitet werden: {message}",
956
+ "claude.newSessionTitle": "Neue Sitzung",
957
+ "claude.helpText": "Unterst\xFCtzte Befehle:\n- /new neue Sitzung\n- /help Hilfe anzeigen",
958
+ "claude.logPromptFailed": "session.prompt fehlgeschlagen: {message}",
959
+ "claude.logAbortSent": "Abbruch gesendet (session={session})",
960
+ "claude.logAbortFailed": "Abbruch fehlgeschlagen (session={session}): {message}",
961
+ "claude.errBadWorkspaceEmpty": "Pfad zum Arbeitsbereich fehlt",
962
+ "claude.errBadWorkspaceNotDir": "Verzeichnis existiert nicht oder ist unbrauchbar",
963
+ "claude.logWorkspaceSwitched": "Arbeitsbereich gewechselt: {path}",
964
+ "claude.logUnknownQuestionReply": "Keine \xDCbereinstimmung f\xFCr strukturierte Frage-Antwort (id={id})",
965
+ "claude.logFlushedQuestions": "{count} ausstehende Frage(n) abgebrochen: {reason}",
966
+ "claude.logFlushedApprovals": "{count} ausstehende Anfrage(n) abgelehnt: {reason}",
967
+ "claude.logPendingResent": "session.pending: {count} ausstehende(r) Eintrag/Eintr\xE4ge f\xFCr {session} erneut gesendet",
968
+ "claude.logSetModeFailed": "setPermissionMode fehlgeschlagen (session={session}): {message}",
969
+ "claude.errWorkspaceMissing": "Arbeitsbereich existiert nicht: {dir}",
970
+ "claude.logSessionError": "Sitzungsfehler (session={session}): {message}",
971
+ "claude.logOfflineDenyTool": "Relay offline, Tool {tool} automatisch abgelehnt",
972
+ "claude.errOfflineDeny": "Relay offline, automatisch abgelehnt (fail-closed)",
973
+ "claude.logAuqDisabled": "AskUserQuestion deaktiviert (TUNNELBOX_CLAUDE_ASK_USER_QUESTION=0), automatisch abgelehnt",
974
+ "claude.errAuqDisabled": "AskUserQuestion ist nicht aktiviert",
975
+ "claude.logAuqParseFailed": "AskUserQuestion-Eingabe konnte nicht geparst werden, automatisch abgelehnt (fail-closed)",
976
+ "claude.errAuqParse": "Fragen konnten nicht geparst werden",
977
+ "claude.logAuqSkipped": "AskUserQuestion unbeantwortet/abgebrochen (session={session})",
978
+ "claude.errAuqCancelled": "Keine Auswahl durch Benutzer (abgebrochen)",
979
+ "claude.logAuqDone": "AskUserQuestion beantwortet (session={session})",
980
+ "claude.promptRunTool": "Claude Code fordert Ausf\xFChrung von {tool}",
981
+ "claude.errApprovalTimeout": "Genehmigung abgelaufen, automatisch abgelehnt (fail-closed)",
982
+ "claude.errUserDenied": "Vom Benutzer abgelehnt",
983
+ "claude.errAborted": "Anfrage abgebrochen",
984
+ "claude.logPermToPhone": "Berechtigungsanfrage (tool={tool}) \u2192 an Telefon gesendet permissionID={id}",
985
+ "claude.errRelayUnreachable": "Relay nicht erreichbar, automatisch abgelehnt (fail-closed)",
986
+ "claude.logQuestionOffline": "Relay offline, strukturierte Frage automatisch abgebrochen",
987
+ "claude.logQuestionToPhone": "Strukturierte Frage ({count}) \u2192 an Telefon gesendet questionID={id}",
988
+ "claude.logDialogOffline": "Relay offline, Dialog automatisch abgebrochen ({tool})",
989
+ "claude.logDialogToPhone": "Dialoganfrage (kind={kind} tool={tool}) \u2192 an Telefon gesendet permissionID={id}",
990
+ "claude.logAuqDialogParse": "AskUserQuestion-Payload konnte nicht geparst werden, Standardverhalten beibehalten (session={session})",
991
+ "claude.logDialogUnmapped": "request_user_dialog(kind={kind}) ohne Zuordnung, Standardverhalten beibehalten (session={session})",
992
+ "claude.promptMcpAuth": "MCP \u201E{server}\u201C fordert Autorisierung",
993
+ "claude.logElicitUnmapped": "MCP-Elicitation (server={server}, mode={mode}) nicht behandelt, automatisch abgelehnt (session={session})",
994
+ "claude.logResetIdentity": "Konto entbunden; Identit\xE4t wird zur\xFCckgesetzt und ein neuer Pairing-Code erzeugt\u2026",
995
+ "claude.dialogConfirm": "Claude Code fordert Best\xE4tigung an ({kind})",
996
+ "claude.resultTruncated": "\u2026 (Ergebnis gek\xFCrzt)",
997
+ "claude.streamError": "claude-Streaming-Fehler",
998
+ "claude.errListSessions": "Claude-Sitzungsliste konnte nicht gelesen werden",
999
+ "claude.errReadHistory": "Sitzungsverlauf konnte nicht gelesen werden ({id})",
1000
+ "setup.langSet": "Laufzeitsprache gesetzt auf",
1001
+ "setup.langHint": "Der Adapter gibt nach dem Neustart in dieser Sprache aus (TUNNELBOX_LANG kann zur Laufzeit \xFCberschreiben). Statusdatei:",
1002
+ "setup.langCurrent": "Aktuelle Laufzeitsprache"
1003
+ };
1004
+
1005
+ // ../core/locales/es-ES.json
1006
+ var es_ES_default = {
1007
+ "agent.err.createSession": "No se pudo crear la sesi\xF3n",
1008
+ "agent.err.httpClient": "No se puede acceder al cliente HTTP de opencode",
1009
+ "agent.err.unknownPerm": "Solicitud de permiso desconocida: {id}",
1010
+ "agent.err.unknownQuestion": "Solicitud de pregunta desconocida: {id}",
1011
+ "agent.log.fallback404": "El punto de acceso preferido devolvi\xF3 404; se recurre al otro: {message}",
1012
+ "agent.log.gotPermReply": "Respuesta de permiso recibida del tel\xE9fono (id={id} {reply} {version})",
1013
+ "agent.log.gotQuestionReply": "Respuesta de pregunta recibida del tel\xE9fono (id={id} {status} {version})",
1014
+ "agent.log.handleFailed": "Error al procesar {type}: {message}",
1015
+ "agent.log.httpOk": "opencode HTTP {status} {path}",
1016
+ "agent.log.pendingResent": "session.pending: reenviadas {count} solicitud(es) pendientes para {session}",
1017
+ "agent.log.unhandledType": "Tipo de mensaje no gestionado: {type}",
1018
+ "events.log.fwdPermission": "Solicitud de permiso reenviada al tel\xE9fono (id={id} tool={tool} {version})",
1019
+ "events.log.permissionReplied": "opencode confirm\xF3 permission.replied (id={id} {response})",
1020
+ "events.log.questionRejected": "opencode confirm\xF3 question.rejected (id={id})",
1021
+ "events.log.questionReplied": "opencode confirm\xF3 question.replied (id={id})",
1022
+ "index.error.connectRelay": "No se pudo conectar con el relay; compruebe su direcci\xF3n e int\xE9ntelo de nuevo",
1023
+ "index.error.pairTimeout": "Tiempo de espera agotado al obtener el c\xF3digo de emparejamiento (el relay no responde)",
1024
+ "index.log.claimed": "Vinculado a su cuenta de tel\xE9fono. El tel\xE9fono se conecta ahora directamente desde \xABMis PC\xBB; no hace falta volver a escanear",
1025
+ "index.log.connectedRelay": "Conectado al relay {url}",
1026
+ "index.log.disconnected": "Desconectado del relay, reconectando autom\xE1ticamente\u2026",
1027
+ "index.log.exit": "Salido",
1028
+ "index.log.pairCode": "C\xF3digo de emparejamiento generado: {code}",
1029
+ "index.log.permissionToPhone": "Solicitud de permiso \u2192 aprobaci\xF3n en el tel\xE9fono (id={id} tool={tool})",
1030
+ "index.log.permissionTuiFallback": "App sin conexi\xF3n; la solicitud la gestiona el TUI de opencode (id={id} tool={tool})",
1031
+ "index.log.relayError": "Error del relay: {msg}",
1032
+ "index.log.resetIdentity": "Desvinculado de la cuenta; se restablece la identidad y se genera un nuevo c\xF3digo de emparejamiento\u2026",
1033
+ "index.log.unknown": "Desconocido",
1034
+ "install.codeLine": "C\xF3digo de emparejamiento: {code}",
1035
+ "install.commandInstalled": "Comando instalado: {path}",
1036
+ "install.done": "Hecho. Se intent\xF3 conectar con el relay para mostrar la informaci\xF3n de emparejamiento:",
1037
+ "install.failConnect": "(No se pudo conectar de inmediato con el relay para imprimir el emparejamiento \u2014 relay inaccesible o versi\xF3n de Node demasiado antigua.",
1038
+ "install.failConnectHint": "Tras reiniciar opencode, consulte el c\xF3digo QR del terminal con --print-logs, o escriba \u201Cmostrar c\xF3digo de emparejamiento del tel\xE9fono\u201D o /pair en opencode.)",
1039
+ "install.hintFresh": "Nota: el c\xF3digo de emparejamiento es de un solo uso y v\xE1lido durante unos 10 minutos. Si inicia opencode m\xE1s tarde, use la informaci\xF3n m\xE1s reciente impresa al iniciar u obtenida con /pair.",
1040
+ "install.linkLine": "Enlace de emparejamiento: {link}",
1041
+ "install.pluginInstalled": "Complemento instalado: {path}",
1042
+ "install.qrUnavailable": "(Representaci\xF3n del QR no disponible: use el enlace o el c\xF3digo anteriores)",
1043
+ "install.qrcodeEnsure": "Instalando qrcode en el directorio de configuraci\xF3n de opencode\u2026",
1044
+ "install.qrcodeFail": "Error al instalar qrcode (no es cr\xEDtico; el QR pasa a ser un enlace): {err}",
1045
+ "install.relayError": "El relay devolvi\xF3 un error: {msg}",
1046
+ "install.scanHint": "Abra tunnelbox en el tel\xE9fono, pulse \u201CEscanear para emparejar\u201D, o escanee el c\xF3digo QR de abajo con la c\xE1mara:",
1047
+ "pair.heading": "Emparejamiento de tel\xE9fono tunnelbox",
1048
+ "pair.howTo": "Abra el enlace en el navegador del tel\xE9fono o introduzca el c\xF3digo de emparejamiento en la aplicaci\xF3n tunnelbox.",
1049
+ "pair.oneTime": "El c\xF3digo de emparejamiento es de un solo uso y caduca al utilizarse; pida \u201Cactualizar el c\xF3digo de emparejamiento\u201D para obtener uno nuevo.",
1050
+ "pair.promptBody": "Mu\xE9strame la informaci\xF3n de emparejamiento con el tel\xE9fono de este ordenador: el c\xF3digo de emparejamiento, el enlace de emparejamiento y un c\xF3digo QR escaneable. Mant\xE9n el c\xF3digo QR dentro de un bloque de c\xF3digo de ancho fijo y reprod\xFAcelo car\xE1cter por car\xE1cter para que mi tel\xE9fono pueda escanearlo.",
1051
+ "pair.promptDescription": "Mostrar el c\xF3digo de emparejamiento, el enlace y el QR escaneable",
1052
+ "pair.scanIntro": "**Emparejar escaneando**: abra la aplicaci\xF3n tunnelbox en el tel\xE9fono (\u201CEscanear para emparejar\u201D) o use la c\xE1mara para escanear el QR de abajo y conectar autom\xE1ticamente.",
1053
+ "perm.promptAuthorize": "opencode solicita autorizaci\xF3n",
1054
+ "perm.promptPatterns": "opencode solicita acceso a: {list}",
1055
+ "perm.promptResource": "opencode solicita acceso a los recursos: {list}",
1056
+ "perm.promptTool": "opencode solicita ejecutar {tool}",
1057
+ "qr.codeLabel": "C\xF3digo de emparejamiento",
1058
+ "qr.fileWritten": "Informaci\xF3n de emparejamiento escrita en: {path}",
1059
+ "qr.linkLabel": "Enlace",
1060
+ "qr.openHint": "Abra el enlace en el navegador del tel\xE9fono o introduzca el c\xF3digo de emparejamiento manualmente en la aplicaci\xF3n.",
1061
+ "qr.titleNoType": "emparejamiento de tel\xE9fono tunnelbox",
1062
+ "qr.titleWithType": "emparejamiento de tel\xE9fono tunnelbox {type}",
1063
+ "qr.unavailable": "(qrcode no instalado: use el enlace o el c\xF3digo de emparejamiento anteriores)",
1064
+ "claude.errBusy": "Esta sesi\xF3n est\xE1 en ejecuci\xF3n; espere a que termine o det\xE9ngala primero",
1065
+ "claude.errTurn": "error de generaci\xF3n",
1066
+ "claude.pairingStillClaimed": "Ya vinculado a una cuenta de tel\xE9fono: este c\xF3digo solo sirve para emparejar un nuevo dispositivo en la misma cuenta; el dispositivo actual se conecta directamente desde Mis computadoras",
1067
+ "claude.pairForced": "--pair: solicitud forzada de c\xF3digo de emparejamiento (solo esta ejecuci\xF3n)",
1068
+ "claude.pairUnclaimed": "No se detect\xF3 una cuenta vinculada; iniciando la gu\xEDa de emparejamiento:",
1069
+ "claude.claimedHint": "Vinculado a una cuenta de tel\xE9fono: con\xE9ctese directamente desde Mis computadoras en el tel\xE9fono",
1070
+ "claude.errStartFailed": "Error al iniciar:",
1071
+ "claude.msgStarted": "Adaptador de Claude Code iniciado (Ctrl+C para salir)",
1072
+ "claude.warnInvalidMode": "Se ignora TUNNELBOX_CLAUDE_MODE no v\xE1lido: {raw} (permitido {list})",
1073
+ "claude.warnInvalidThinking": "Se ignora TUNNELBOX_CLAUDE_THINKING no v\xE1lido: {raw} (off|adaptive|enabled[:budget])",
1074
+ "claude.cmdNew": "Nueva sesi\xF3n",
1075
+ "claude.cmdHelp": "Mostrar ayuda",
1076
+ "claude.errNoRelay": "No hay URL de relay configurada: inyecte un valor con --env o sobrescr\xEDbalo con TUNNELBOX_RELAY_URL",
1077
+ "claude.logConnected": "Conectado al relay {url}",
1078
+ "claude.logMsgUncaught": "Excepci\xF3n no capturada al procesar el mensaje: {message}",
1079
+ "claude.reasonDisconnect": "Relay desconectado (rechazo autom\xE1tico fail-closed)",
1080
+ "claude.reasonUnbind": "Cuenta desvinculada (rechazo autom\xE1tico fail-closed)",
1081
+ "claude.logDisconnected": "Desconectado del relay, reconectando autom\xE1ticamente\u2026",
1082
+ "claude.logPairCode": "C\xF3digo de emparejamiento generado: {code}",
1083
+ "claude.logUnhandledType": "Tipo de mensaje no manejado: {type}",
1084
+ "claude.logHandleFailed": "No se pudo procesar {type}: {message}",
1085
+ "claude.newSessionTitle": "Nueva sesi\xF3n",
1086
+ "claude.helpText": "Comandos compatibles:\n- /new nueva sesi\xF3n\n- /help mostrar ayuda",
1087
+ "claude.logPromptFailed": "session.prompt fall\xF3: {message}",
1088
+ "claude.logAbortSent": "Se envi\xF3 la interrupci\xF3n (session={session})",
1089
+ "claude.logAbortFailed": "Error al interrumpir (session={session}): {message}",
1090
+ "claude.errBadWorkspaceEmpty": "Falta la ruta del espacio de trabajo",
1091
+ "claude.errBadWorkspaceNotDir": "El directorio no existe o no se puede usar",
1092
+ "claude.logWorkspaceSwitched": "Espacio de trabajo cambiado: {path}",
1093
+ "claude.logUnknownQuestionReply": "Respuesta de pregunta estructurada sin coincidencia (id={id})",
1094
+ "claude.logFlushedQuestions": "{count} pregunta(s) pendiente(s) cancelada(s): {reason}",
1095
+ "claude.logFlushedApprovals": "{count} solicitud(es) pendiente(s) rechazada(s): {reason}",
1096
+ "claude.logPendingResent": "session.pending: se reenviaron {count} elemento(s) pendiente(s) de {session}",
1097
+ "claude.logSetModeFailed": "setPermissionMode fall\xF3 (session={session}): {message}",
1098
+ "claude.errWorkspaceMissing": "El espacio de trabajo no existe: {dir}",
1099
+ "claude.logSessionError": "Error de sesi\xF3n (session={session}): {message}",
1100
+ "claude.logOfflineDenyTool": "Relay sin conexi\xF3n, rechazo autom\xE1tico de la herramienta {tool}",
1101
+ "claude.errOfflineDeny": "Relay sin conexi\xF3n, rechazo autom\xE1tico (fail-closed)",
1102
+ "claude.logAuqDisabled": "AskUserQuestion deshabilitado (TUNNELBOX_CLAUDE_ASK_USER_QUESTION=0), rechazo autom\xE1tico",
1103
+ "claude.errAuqDisabled": "AskUserQuestion no est\xE1 habilitado",
1104
+ "claude.logAuqParseFailed": "No se pudo analizar la entrada de AskUserQuestion, rechazo autom\xE1tico (fail-closed)",
1105
+ "claude.errAuqParse": "No se pudieron analizar las preguntas",
1106
+ "claude.logAuqSkipped": "AskUserQuestion sin respuesta/cancelada (session={session})",
1107
+ "claude.errAuqCancelled": "El usuario no seleccion\xF3 (cancelado)",
1108
+ "claude.logAuqDone": "AskUserQuestion respondida (session={session})",
1109
+ "claude.promptRunTool": "Claude Code solicita ejecutar {tool}",
1110
+ "claude.errApprovalTimeout": "Tiempo de aprobaci\xF3n agotado, rechazo autom\xE1tico (fail-closed)",
1111
+ "claude.errUserDenied": "Rechazado por el usuario",
1112
+ "claude.errAborted": "Solicitud interrumpida",
1113
+ "claude.logPermToPhone": "Solicitud de permiso (tool={tool}) \u2192 enviada al tel\xE9fono permissionID={id}",
1114
+ "claude.errRelayUnreachable": "Relay inaccesible, rechazo autom\xE1tico (fail-closed)",
1115
+ "claude.logQuestionOffline": "Relay sin conexi\xF3n, pregunta estructurada cancelada autom\xE1ticamente",
1116
+ "claude.logQuestionToPhone": "Pregunta estructurada ({count}) \u2192 enviada al tel\xE9fono questionID={id}",
1117
+ "claude.logDialogOffline": "Relay sin conexi\xF3n, di\xE1logo cancelado autom\xE1ticamente ({tool})",
1118
+ "claude.logDialogToPhone": "Solicitud de di\xE1logo (kind={kind} tool={tool}) \u2192 enviada al tel\xE9fono permissionID={id}",
1119
+ "claude.logAuqDialogParse": "No se pudo analizar el payload de AskUserQuestion; se mantiene el comportamiento predeterminado (session={session})",
1120
+ "claude.logDialogUnmapped": "request_user_dialog(kind={kind}) sin correspondencia, se mantiene el comportamiento predeterminado (session={session})",
1121
+ "claude.promptMcpAuth": "MCP \xAB{server}\xBB solicita autorizaci\xF3n",
1122
+ "claude.logElicitUnmapped": "Elicitaci\xF3n MCP (server={server}, mode={mode}) no manejada, rechazo autom\xE1tico (session={session})",
1123
+ "claude.logResetIdentity": "Cuenta desvinculada; restableciendo identidad y generando un nuevo c\xF3digo de emparejamiento\u2026",
1124
+ "claude.dialogConfirm": "Claude Code solicita confirmaci\xF3n ({kind})",
1125
+ "claude.resultTruncated": "\u2026 (resultado truncado)",
1126
+ "claude.streamError": "error de streaming de claude",
1127
+ "claude.errListSessions": "No se pudo leer la lista de sesiones de Claude",
1128
+ "claude.errReadHistory": "No se pudo leer el historial de la sesi\xF3n ({id})",
1129
+ "setup.langSet": "Idioma de ejecuci\xF3n establecido en",
1130
+ "setup.langHint": "El adaptador emitir\xE1 en este idioma tras reiniciar (TUNNELBOX_LANG puede sobrescribirlo en tiempo de ejecuci\xF3n). Archivo de estado:",
1131
+ "setup.langCurrent": "Idioma de ejecuci\xF3n actual"
1132
+ };
1133
+
1134
+ // ../core/src/i18n.ts
1135
+ var DEFAULT_LANG = "en-US";
1136
+ var dicts = {
1137
+ "zh-CN": zh_CN_default,
1138
+ "zh-TW": zh_TW_default,
1139
+ "en-US": en_US_default,
1140
+ "ja-JP": ja_JP_default,
1141
+ "ko-KR": ko_KR_default,
1142
+ "fr-FR": fr_FR_default,
1143
+ "de-DE": de_DE_default,
1144
+ "es-ES": es_ES_default
1145
+ };
1146
+ function normalizeLang(raw) {
1147
+ if (!raw) return DEFAULT_LANG;
1148
+ const s = raw.trim().replace(/_/g, "-").toLowerCase();
1149
+ if (!s) return DEFAULT_LANG;
1150
+ if (s.startsWith("zh")) {
1151
+ if (s.startsWith("zh-tw") || s.includes("hant")) return "zh-TW";
1152
+ return "zh-CN";
1153
+ }
1154
+ const base = s.split("-")[0];
1155
+ const map = {
1156
+ en: "en-US",
1157
+ ja: "ja-JP",
1158
+ ko: "ko-KR",
1159
+ fr: "fr-FR",
1160
+ de: "de-DE",
1161
+ es: "es-ES"
1162
+ };
1163
+ return map[base] ?? DEFAULT_LANG;
1164
+ }
1165
+ function detectSystemLang() {
1166
+ for (const k of ["LC_ALL", "LC_MESSAGES", "LANG"]) {
1167
+ const v = process.env[k];
1168
+ if (v && v.trim()) return v.trim().split(".")[0];
1169
+ }
1170
+ return null;
1171
+ }
1172
+ function resolveLang(opts = {}) {
1173
+ const env = process.env.TUNNELBOX_LANG;
1174
+ if (opts.config) return normalizeLang(opts.config);
1175
+ if (env) return normalizeLang(env);
1176
+ if (opts.saved) return normalizeLang(opts.saved);
1177
+ return normalizeLang(detectSystemLang());
1178
+ }
1179
+ function t(lang, key, vars) {
1180
+ const dict = dicts[lang] ?? dicts[DEFAULT_LANG];
1181
+ let text = dict[key] ?? dicts[DEFAULT_LANG][key] ?? key;
1182
+ if (vars) {
1183
+ for (const [k, v] of Object.entries(vars)) {
1184
+ text = text.replaceAll(`{${k}}`, String(v));
1185
+ }
1186
+ }
1187
+ return text;
1188
+ }
1189
+ function makeT(lang) {
1190
+ return (key, vars) => t(lang, key, vars);
1191
+ }
1192
+
1193
+ // ../core/src/state.ts
112
1194
  function baseName() {
113
- return join(homedir(), ".config", "opencode");
1195
+ return join(homedir(), ".tunnelbox");
114
1196
  }
115
1197
  function suffix(type) {
116
1198
  return type ? `.${type}` : "";
@@ -129,8 +1211,7 @@ function saveState(st, type) {
129
1211
  }
130
1212
  function loadState(type) {
131
1213
  const fallback = {
132
- agentId: randomBytes(16).toString("hex"),
133
- relayUrl: DEFAULT_RELAY_URL
1214
+ agentId: randomBytes(16).toString("hex")
134
1215
  };
135
1216
  try {
136
1217
  if (!existsSync(statePath(type))) {
@@ -141,8 +1222,9 @@ function loadState(type) {
141
1222
  if (parsed.agentId && parsed.agentId.length >= 16) {
142
1223
  return {
143
1224
  agentId: parsed.agentId,
144
- relayUrl: parsed.relayUrl || DEFAULT_RELAY_URL,
145
- claimed: parsed.claimed === true
1225
+ relayUrl: parsed.relayUrl || "",
1226
+ claimed: parsed.claimed === true,
1227
+ lang: parsed.lang ? normalizeLang(parsed.lang) : void 0
146
1228
  };
147
1229
  }
148
1230
  saveState(fallback, type);
@@ -173,22 +1255,50 @@ function writePairingJson(info, type) {
173
1255
  return p;
174
1256
  }
175
1257
 
1258
+ // ../core/src/env.ts
1259
+ function injectedRelayUrl() {
1260
+ return "wss://relay.tunnelbox.top" ? "wss://relay.tunnelbox.top".replace(/\/+$/, "") : "";
1261
+ }
1262
+
176
1263
  // ../core/src/qr.ts
177
- async function printPairing(relayHttpBase, code, type, name) {
1264
+ function dispWidth(s) {
1265
+ let w = 0;
1266
+ for (const ch of s) w += ch.charCodeAt(0) > 255 ? 2 : 1;
1267
+ return w;
1268
+ }
1269
+ function padVisual(s, width, align = "left") {
1270
+ const cur = dispWidth(s);
1271
+ if (cur >= width) return s;
1272
+ const pad = width - cur;
1273
+ if (align === "center") {
1274
+ const left = Math.floor(pad / 2);
1275
+ return " ".repeat(left) + s + " ".repeat(pad - left);
1276
+ }
1277
+ return s + " ".repeat(pad);
1278
+ }
1279
+ async function printPairing(relayHttpBase, code, type, name, lang) {
1280
+ const resolved = lang ? normalizeLang(lang) : resolveLang();
178
1281
  const p = new URLSearchParams({ code });
179
1282
  if (type) p.set("type", type);
180
1283
  if (name) p.set("name", name);
181
1284
  const link = `${relayHttpBase}/app/#/pages/index/index?${p.toString()}`;
182
1285
  const file = writePairingFile(link, code, type);
183
- const tag = type ? ` ${type}` : "";
1286
+ const tt = (key, vars) => t(resolved, key, vars);
1287
+ const title = type ? tt("qr.titleWithType", { type }) : tt("qr.titleNoType");
1288
+ const codeLine = `${tt("qr.codeLabel")}: ${code}`;
1289
+ const linkLine = `${tt("qr.linkLabel")}: ${link}`;
1290
+ const hint = tt("qr.openHint");
1291
+ const inner = Math.max(40, dispWidth(codeLine), dispWidth(linkLine), dispWidth(title));
1292
+ const W = Math.min(76, inner + 4);
1293
+ const bar = "\u2500".repeat(W);
184
1294
  console.log("");
185
- console.log("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
186
- console.log(`\u2502 tunnelbox${tag} \u624B\u673A\u914D\u5BF9 \u2502`);
187
- console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
188
- console.log(`\u2502 \u914D\u5BF9\u7801: ${code.padEnd(42)} \u2502`);
189
- console.log(`\u2502 \u94FE\u63A5 : ${link.padEnd(42)} \u2502`);
190
- console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
191
- console.log(" \u624B\u673A\u6D4F\u89C8\u5668\u6253\u5F00\u94FE\u63A5\uFF0C\u6216\u6253\u5F00 App \u540E\u624B\u52A8\u8F93\u5165\u914D\u5BF9\u7801\u3002");
1295
+ console.log("\u250C" + bar + "\u2510");
1296
+ console.log("\u2502 " + padVisual(title, W - 2, "center") + " \u2502");
1297
+ console.log("\u251C" + bar + "\u2524");
1298
+ console.log("\u2502 " + padVisual(codeLine, W - 2) + " \u2502");
1299
+ console.log("\u2502 " + padVisual(linkLine, W - 2) + " \u2502");
1300
+ console.log("\u2514" + bar + "\u2518");
1301
+ console.log(" " + hint);
192
1302
  try {
193
1303
  const mod = await import("qrcode");
194
1304
  const render = mod.default ?? mod;
@@ -198,13 +1308,29 @@ async function printPairing(relayHttpBase, code, type, name) {
198
1308
  if (qr) console.log(qr);
199
1309
  }
200
1310
  } catch {
201
- console.log(" \uFF08\u672A\u5B89\u88C5 qrcode\uFF0C\u8BF7\u4F7F\u7528\u4E0A\u65B9\u94FE\u63A5\u6216\u914D\u5BF9\u7801\uFF09");
1311
+ console.log(" " + tt("qr.unavailable"));
202
1312
  }
203
- console.log(` \u914D\u5BF9\u4FE1\u606F\u5DF2\u5199\u5165: ${file}`);
1313
+ console.log(" " + tt("qr.fileWritten", { path: file }));
204
1314
  console.log("");
205
1315
  }
206
1316
 
1317
+ // src/bridge.ts
1318
+ import { randomBytes as randomBytes2 } from "node:crypto";
1319
+ import { existsSync as existsSync2, statSync } from "node:fs";
1320
+ import { hostname } from "node:os";
1321
+ import { isAbsolute, join as join2, resolve } from "node:path";
1322
+ import { fileURLToPath } from "node:url";
1323
+ import {
1324
+ query
1325
+ } from "@anthropic-ai/claude-agent-sdk";
1326
+
207
1327
  // src/map.ts
1328
+ var tr = (key, vars) => {
1329
+ return key;
1330
+ };
1331
+ function setMapTranslator(t2) {
1332
+ tr = t2;
1333
+ }
208
1334
  function rec(v) {
209
1335
  return v && typeof v === "object" ? v : {};
210
1336
  }
@@ -272,7 +1398,8 @@ function toolResultText(content) {
272
1398
  push(content);
273
1399
  const text = parts.join("\n").trim();
274
1400
  if (text.length <= RESULT_MAX_CHARS) return text;
275
- return text.slice(0, RESULT_MAX_CHARS) + "\n\u2026\uFF08\u7ED3\u679C\u8FC7\u957F\u5DF2\u622A\u65AD\uFF09";
1401
+ return text.slice(0, RESULT_MAX_CHARS) + `
1402
+ ${tr("claude.resultTruncated")}`;
276
1403
  }
277
1404
  function transcriptToMessages(entries) {
278
1405
  const out = [];
@@ -354,9 +1481,10 @@ var TurnStreamer = class {
354
1481
  }
355
1482
  onStreamEvent(f) {
356
1483
  const ev = rec(f.event);
357
- const t = typeof ev.type === "string" ? ev.type : "";
358
- switch (t) {
1484
+ const t2 = typeof ev.type === "string" ? ev.type : "";
1485
+ switch (t2) {
359
1486
  case "message_start": {
1487
+ this.end();
360
1488
  const m = rec(ev.message);
361
1489
  this.mid = typeof m.id === "string" && m.id ? String(m.id) : this.fallbackId();
362
1490
  break;
@@ -419,11 +1547,10 @@ var TurnStreamer = class {
419
1547
  break;
420
1548
  }
421
1549
  case "message_stop":
422
- this.end();
423
1550
  break;
424
1551
  case "error": {
425
1552
  const err = rec(ev.error);
426
- const message = typeof err.message === "string" && err.message ? err.message : "claude \u6D41\u5F0F\u51FA\u9519";
1553
+ const message = typeof err.message === "string" && err.message ? err.message : tr("claude.streamError");
427
1554
  this.send({ id: "err", type: "text", text: `[claude] ${message}`, complete: true });
428
1555
  this.end();
429
1556
  break;
@@ -494,10 +1621,34 @@ function asLabels(v) {
494
1621
  }
495
1622
  return out;
496
1623
  }
497
- function tryMapDialog(dialogKind, payload) {
1624
+ function asQuestion(v) {
1625
+ const q = asRec(v);
1626
+ const text = firstText(q);
1627
+ if (!text) return null;
1628
+ const options = asLabels(q.options);
1629
+ return {
1630
+ question: text,
1631
+ header: typeof q.header === "string" && q.header.trim() ? q.header.trim() : void 0,
1632
+ options,
1633
+ multiple: q.multiSelect === true || q.multiple === true,
1634
+ custom: q.allowCustom === true || q.custom === true || q.other === true
1635
+ };
1636
+ }
1637
+ function mapAskUserQuestion(payload) {
1638
+ const p = asRec(payload);
1639
+ const list = Array.isArray(p.questions) ? p.questions : Array.isArray(p.question) ? p.question : null;
1640
+ if (!list || !list.length) return null;
1641
+ const out = [];
1642
+ for (const raw of list) {
1643
+ const qi = asQuestion(raw);
1644
+ if (qi) out.push(qi);
1645
+ }
1646
+ return out.length ? out : null;
1647
+ }
1648
+ function tryMapDialog(dialogKind, payload, t2) {
498
1649
  const p = asRec(payload);
499
1650
  const choices = p.choices !== void 0 ? asLabels(p.choices) : asLabels(p.options);
500
- const prompt = firstText(payload) || `Claude Code \u8BF7\u6C42\u786E\u8BA4\uFF08${dialogKind}\uFF09`;
1651
+ const prompt = firstText(payload) || (t2 ? t2("claude.dialogConfirm", { kind: dialogKind }) : `Claude Code request confirmation (${dialogKind})`);
501
1652
  if (choices.length) {
502
1653
  return { kind: "choice", tool: dialogKind, prompt, options: choices };
503
1654
  }
@@ -508,68 +1659,6 @@ function tryMapDialog(dialogKind, payload) {
508
1659
  return null;
509
1660
  }
510
1661
 
511
- // src/warm.ts
512
- import { startup } from "@anthropic-ai/claude-agent-sdk";
513
- var WarmPool = class {
514
- ready = /* @__PURE__ */ new Map();
515
- starting = /* @__PURE__ */ new Map();
516
- closed = false;
517
- /** 取走某会话已就绪的预热句柄;没有(未就绪/无)返回 null。 */
518
- take(sessionID) {
519
- const w = this.ready.get(sessionID);
520
- if (!w) return null;
521
- this.ready.delete(sessionID);
522
- return w;
523
- }
524
- /** 为该会话异步预 spawn 一个就绪进程(已有就绪/进行中则不重复)。 */
525
- replenish(sessionID, options) {
526
- if (this.closed || this.ready.has(sessionID) || this.starting.has(sessionID)) {
527
- return Promise.resolve();
528
- }
529
- let done;
530
- const p = new Promise((r) => done = r);
531
- this.starting.set(sessionID, p);
532
- void (async () => {
533
- try {
534
- const w = await startup({ options });
535
- if (this.closed) {
536
- w.close();
537
- } else {
538
- this.ready.set(sessionID, w);
539
- }
540
- } catch {
541
- } finally {
542
- this.starting.delete(sessionID);
543
- done();
544
- }
545
- })();
546
- return p;
547
- }
548
- /** 会话删除/结束等场景清理该会话的预热资源。 */
549
- drop(sessionID) {
550
- const w = this.ready.get(sessionID);
551
- if (w) {
552
- this.ready.delete(sessionID);
553
- try {
554
- w.close();
555
- } catch {
556
- }
557
- }
558
- }
559
- /** 关闭全部预热进程。 */
560
- dispose() {
561
- this.closed = true;
562
- for (const w of this.ready.values()) {
563
- try {
564
- w.close();
565
- } catch {
566
- }
567
- }
568
- this.ready.clear();
569
- this.starting.clear();
570
- }
571
- };
572
-
573
1662
  // src/sessions.ts
574
1663
  import {
575
1664
  deleteSession,
@@ -577,11 +1666,19 @@ import {
577
1666
  getSessionMessages,
578
1667
  listSessions
579
1668
  } from "@anthropic-ai/claude-agent-sdk";
580
- var DEFAULT_TITLE = "Claude Code \u4F1A\u8BDD";
1669
+ var tr2 = (key, vars) => {
1670
+ return key;
1671
+ };
1672
+ function setSessionsTranslator(t2) {
1673
+ tr2 = t2;
1674
+ }
1675
+ function sessionTitle(s) {
1676
+ return s.customTitle?.trim() || s.summary?.trim() || s.firstPrompt?.trim() || tr2("claude.newSessionTitle");
1677
+ }
581
1678
  function toSessionInfo(s) {
582
1679
  return {
583
1680
  id: s.sessionId,
584
- title: s.customTitle?.trim() || s.summary?.trim() || s.firstPrompt?.trim() || DEFAULT_TITLE,
1681
+ title: sessionTitle(s),
585
1682
  created: s.createdAt ?? s.lastModified,
586
1683
  updated: s.lastModified,
587
1684
  status: "idle",
@@ -590,7 +1687,7 @@ function toSessionInfo(s) {
590
1687
  }
591
1688
  async function fetchSessionInfos(workspace) {
592
1689
  const list = await listSessions().catch((e) => {
593
- throw new Error(`\u8BFB\u53D6 Claude \u4F1A\u8BDD\u5217\u8868\u5931\u8D25: ${e.message}`);
1690
+ throw new Error(`${tr2("claude.errListSessions")}: ${e.message}`);
594
1691
  });
595
1692
  return list.map(toSessionInfo).filter((s) => !workspace || s.workspace === workspace).sort((a, b) => b.updated - a.updated);
596
1693
  }
@@ -604,7 +1701,7 @@ async function sessionExists(sessionId, dir) {
604
1701
  }
605
1702
  async function readHistory(sessionId, dir) {
606
1703
  const rows = await getSessionMessages(sessionId, dir ? { dir } : void 0).catch((e) => {
607
- throw new Error(`\u8BFB\u53D6\u4F1A\u8BDD\u5386\u53F2\u5931\u8D25(${sessionId}): ${e.message}`);
1704
+ throw new Error(`${tr2("claude.errReadHistory", { id: sessionId })}: ${e.message}`);
608
1705
  });
609
1706
  const entries = rows.map((r) => ({
610
1707
  type: r.type,
@@ -653,8 +1750,8 @@ var VERSION = "0.1.0";
653
1750
  var AGENT_TYPE = "claude-code";
654
1751
  var APPROVAL_TIMEOUT_MS = 12e4;
655
1752
  var ADAPTER_COMMANDS = [
656
- { name: "new", description: "\u65B0\u5EFA\u4F1A\u8BDD" },
657
- { name: "help", description: "\u663E\u793A\u5E2E\u52A9" }
1753
+ { name: "new", descriptionKey: "claude.cmdNew" },
1754
+ { name: "help", descriptionKey: "claude.cmdHelp" }
658
1755
  ];
659
1756
  function defaultOfficialPluginDir() {
660
1757
  try {
@@ -664,15 +1761,54 @@ function defaultOfficialPluginDir() {
664
1761
  return void 0;
665
1762
  }
666
1763
  }
1764
+ var PushChannel = class {
1765
+ buf = [];
1766
+ waiters = [];
1767
+ ended = false;
1768
+ push(v) {
1769
+ this.buf.push(v);
1770
+ const w = this.waiters.shift();
1771
+ if (w) w();
1772
+ }
1773
+ end() {
1774
+ this.ended = true;
1775
+ while (this.waiters.length) this.waiters.shift()();
1776
+ }
1777
+ [Symbol.asyncIterator]() {
1778
+ return {
1779
+ next: async () => {
1780
+ for (; ; ) {
1781
+ if (this.buf.length) return { value: this.buf.shift(), done: false };
1782
+ if (this.ended) return { value: void 0, done: true };
1783
+ await new Promise((r) => this.waiters.push(r));
1784
+ }
1785
+ }
1786
+ };
1787
+ }
1788
+ };
1789
+ function userMessage(text) {
1790
+ return {
1791
+ type: "user",
1792
+ message: { role: "user", content: text },
1793
+ parent_tool_use_id: null,
1794
+ origin: { kind: "human" }
1795
+ };
1796
+ }
667
1797
  var ClaudeBridge = class {
668
1798
  constructor(cfg) {
669
1799
  this.cfg = cfg;
670
1800
  this.currentWorkspace = resolve(cfg.cwd || process.cwd());
671
- if (cfg.warm) this.warmPool = new WarmPool();
1801
+ this.tr = makeT(this.lang);
1802
+ setSessionsTranslator(this.tr);
1803
+ setMapTranslator(this.tr);
672
1804
  }
673
1805
  ws = new RelayClient();
674
1806
  state = loadState(AGENT_TYPE);
675
1807
  claimed = !!this.state.claimed;
1808
+ /** 本次连接是否已收到 relay 的 agent.claimed(绑定状态权威来源)。 */
1809
+ claimedConfirmed = false;
1810
+ /** 智能引导定时器:announce 后等待 relay 补发 claimed 的窗口,到期未收到则视为未绑定发起引导。 */
1811
+ pairTimer = null;
676
1812
  everConnected = false;
677
1813
  relayBase = "";
678
1814
  /** 当前工作区(新建会话的默认 cwd)。 */
@@ -681,15 +1817,19 @@ var ClaudeBridge = class {
681
1817
  pendingWorkspaces = /* @__PURE__ */ new Map();
682
1818
  /** 已知会话 → cwd(从 SDK 列表/首次 prompt 维护)。 */
683
1819
  sessionCwd = /* @__PURE__ */ new Map();
684
- /** 进行中的回合(sessionID → AbortController)。 */
685
- activeTurns = /* @__PURE__ */ new Map();
1820
+ /** 进行中的会话(sessionID → 长活运行时;running 标记当前是否有一轮在跑)。 */
1821
+ sessions = /* @__PURE__ */ new Map();
686
1822
  /** 待手机作答的请求(工具审批 / onUserDialog 选择输入共用一张表)。 */
687
1823
  approvals = /* @__PURE__ */ new Map();
688
1824
  approvalSeq = 0;
1825
+ /** 待手机作答的结构化提问(AskUserQuestion 经 question.request 推给手机;question.reply 回来)。 */
1826
+ questions = /* @__PURE__ */ new Map();
1827
+ questionSeq = 0;
689
1828
  /** 会话级 mode 覆盖(session.prompt.agent 映射,如 plan)。 */
690
1829
  sessionModes = /* @__PURE__ */ new Map();
691
- /** P4 预热池(cfg.warm 开启时启用)。 */
692
- warmPool = null;
1830
+ /** 当前语言(终端配对框/日志本地化;TUNNELBOX_LANG > state.lang > 系统检测 > en-US)。 */
1831
+ tr;
1832
+ lang = resolveLang({ saved: this.state.lang });
693
1833
  get home() {
694
1834
  return this.currentWorkspace;
695
1835
  }
@@ -697,15 +1837,22 @@ var ClaudeBridge = class {
697
1837
  return this.relayBase.replace(/^ws/, "http").replace(/\/+$/, "");
698
1838
  }
699
1839
  async start() {
700
- const url = (this.cfg.relayUrl || this.state.relayUrl || "").replace(/\/+$/, "");
1840
+ const url = (this.cfg.relayUrl || injectedRelayUrl() || "").replace(/\/+$/, "");
701
1841
  this.relayBase = url;
1842
+ if (!url) {
1843
+ this.log(this.tr("claude.errNoRelay"));
1844
+ return;
1845
+ }
702
1846
  this.connect(url);
703
1847
  }
704
1848
  dispose() {
705
1849
  this.ws.close();
706
- for (const c of this.activeTurns.values()) c.abort();
707
- this.activeTurns.clear();
708
- this.warmPool?.dispose();
1850
+ if (this.pairTimer) {
1851
+ clearTimeout(this.pairTimer);
1852
+ this.pairTimer = null;
1853
+ }
1854
+ for (const rt of this.sessions.values()) this.closeRuntime(rt, false);
1855
+ this.sessions.clear();
709
1856
  }
710
1857
  log(msg) {
711
1858
  console.log(`[tunnelbox:claude] ${msg}`);
@@ -731,17 +1878,22 @@ var ClaudeBridge = class {
731
1878
  {
732
1879
  onOpen: () => {
733
1880
  this.everConnected = true;
734
- this.log("\u5DF2\u8FDE\u63A5\u4E2D\u7EE7 " + url);
1881
+ this.claimedConfirmed = false;
1882
+ this.log(this.tr("claude.logConnected", { url }));
735
1883
  this.announce();
1884
+ this.schedulePairDecision();
736
1885
  },
737
1886
  onMessage: (env) => {
738
- void this.onMessage(env).catch((e) => this.log(`\u6D88\u606F\u5904\u7406\u672A\u6355\u83B7\u5F02\u5E38: ${e.message}`));
1887
+ void this.onMessage(env).catch(
1888
+ (e) => this.log(this.tr("claude.logMsgUncaught", { message: e.message }))
1889
+ );
739
1890
  },
740
1891
  onClose: () => {
741
- this.flushApprovals("\u4E2D\u7EE7\u65AD\u5F00\uFF0C\u81EA\u52A8\u62D2\u7EDD\uFF08fail-closed\uFF09");
1892
+ this.flushApprovals(this.tr("claude.reasonDisconnect"));
1893
+ this.flushQuestions(this.tr("claude.reasonDisconnect"));
742
1894
  if (this.everConnected) {
743
1895
  this.everConnected = false;
744
- this.log("\u4E0E\u4E2D\u7EE7\u65AD\u5F00\uFF0C\u6B63\u5728\u81EA\u52A8\u91CD\u8FDE\u2026");
1896
+ this.log(this.tr("claude.logDisconnected"));
745
1897
  }
746
1898
  },
747
1899
  onError: () => {
@@ -774,7 +1926,30 @@ var ClaudeBridge = class {
774
1926
  timezone: localTimezone()
775
1927
  };
776
1928
  this.send(envelope("agent.info", info));
777
- if (!this.claimed) this.send(envelope("pair.create", {}));
1929
+ }
1930
+ /**
1931
+ * 智能配对引导:announce 后等 relay 补发 agent.claimed 的窗口。
1932
+ * 到期仍未被确认绑定 → 视为未绑定,自动申请配对码打印(引导);已收到 claimed → 取消引导。
1933
+ * --pair(forcePair)忽略窗口,立即申请并打印。
1934
+ */
1935
+ schedulePairDecision() {
1936
+ if (this.cfg.forcePair) {
1937
+ this.log(this.tr("claude.pairForced"));
1938
+ this.requestPairCode();
1939
+ return;
1940
+ }
1941
+ if (this.pairTimer) clearTimeout(this.pairTimer);
1942
+ this.pairTimer = setTimeout(() => {
1943
+ this.pairTimer = null;
1944
+ if (!this.claimedConfirmed) {
1945
+ this.log(this.tr("claude.pairUnclaimed"));
1946
+ this.requestPairCode();
1947
+ }
1948
+ }, 1500);
1949
+ }
1950
+ /** 向 relay 申请配对码;收到 pair.created 后打印(onMessage 处理)。 */
1951
+ requestPairCode() {
1952
+ this.send(envelope("pair.create", {}));
778
1953
  }
779
1954
  /** 计算某回合生效的 permissionMode:agent 参数 > 会话级 > 配置默认。 */
780
1955
  turnModeFor(sessionID, agent) {
@@ -790,15 +1965,23 @@ var ClaudeBridge = class {
790
1965
  const code = env.payload.code;
791
1966
  const link = this.pairingLink(code);
792
1967
  writePairingJson({ code, link, relayUrl: this.relayBase, at: Date.now() }, AGENT_TYPE);
793
- this.log("\u751F\u6210\u914D\u5BF9\u7801: " + code);
794
- void printPairing(this.relayHttpBase, code, AGENT_TYPE, hostname());
1968
+ this.log(this.tr("claude.logPairCode", { code }));
1969
+ if (this.claimed) {
1970
+ this.log(this.tr("claude.pairingStillClaimed"));
1971
+ }
1972
+ void printPairing(this.relayHttpBase, code, AGENT_TYPE, hostname(), this.lang);
795
1973
  return;
796
1974
  }
797
1975
  case "agent.claimed":
798
1976
  this.claimed = true;
1977
+ this.claimedConfirmed = true;
1978
+ if (this.pairTimer) {
1979
+ clearTimeout(this.pairTimer);
1980
+ this.pairTimer = null;
1981
+ }
799
1982
  this.state.claimed = true;
800
1983
  saveState(this.state, AGENT_TYPE);
801
- this.log("\u5DF2\u7ED1\u5B9A\u5230\u624B\u673A\u8D26\u53F7\uFF0C\u4E4B\u540E\u624B\u673A\u4ECE\u300C\u6211\u7684\u7535\u8111\u300D\u76F4\u63A5\u8FDE\u63A5");
1984
+ this.log(this.tr("claude.claimedHint"));
802
1985
  return;
803
1986
  case "agent.revoked":
804
1987
  this.resetIdentity();
@@ -823,13 +2006,17 @@ var ClaudeBridge = class {
823
2006
  return this.workspaceSet(env);
824
2007
  case "permission.reply":
825
2008
  return this.permissionReply(env);
2009
+ case "question.reply":
2010
+ return this.questionReply(env);
2011
+ case "session.pending":
2012
+ return this.sessionPending(env);
826
2013
  case "error":
827
2014
  return;
828
2015
  default:
829
- this.log("\u672A\u5904\u7406\u7684\u6D88\u606F\u7C7B\u578B: " + env.type);
2016
+ this.log(this.tr("claude.logUnhandledType", { type: env.type }));
830
2017
  }
831
2018
  } catch (e) {
832
- this.log(`\u5904\u7406 ${env.type} \u5931\u8D25: ${e.message}`);
2019
+ this.log(this.tr("claude.logHandleFailed", { type: env.type, message: e.message }));
833
2020
  this.reply(env, "error", { code: "HANDLER_ERROR", message: e.message, id: env.id });
834
2021
  }
835
2022
  }
@@ -838,7 +2025,7 @@ var ClaudeBridge = class {
838
2025
  const payload = env.payload;
839
2026
  const sessions = (await fetchSessionInfos(payload?.workspace)).map((s) => ({
840
2027
  ...s,
841
- status: this.activeTurns.has(s.id) ? "running" : s.status
2028
+ status: this.sessions.get(s.id)?.running ? "running" : s.status
842
2029
  }));
843
2030
  this.reply(env, "sessions", { sessions });
844
2031
  }
@@ -849,7 +2036,7 @@ var ClaudeBridge = class {
849
2036
  const now = Date.now();
850
2037
  this.pendingWorkspaces.set(id, workspace);
851
2038
  this.reply(env, "session.created", {
852
- session: { id, title: "\u65B0\u4F1A\u8BDD", created: now, updated: now, status: "idle", workspace }
2039
+ session: { id, title: this.tr("claude.newSessionTitle"), created: now, updated: now, status: "idle", workspace }
853
2040
  });
854
2041
  }
855
2042
  async sessionMessages(env) {
@@ -870,45 +2057,46 @@ var ClaudeBridge = class {
870
2057
  this.reply(env, "message.part", {
871
2058
  sessionID: payload.sessionID,
872
2059
  messageID: `m-${Date.now()}`,
873
- part: { id: "t", type: "text", text: "\u652F\u6301\u7684\u547D\u4EE4\uFF1A\n- /new \u65B0\u5EFA\u4F1A\u8BDD\n- /help \u663E\u793A\u5E2E\u52A9", complete: true }
2060
+ part: { id: "t", type: "text", text: this.tr("claude.helpText"), complete: true }
874
2061
  });
875
2062
  return;
876
2063
  }
877
2064
  const agent = (payload.agent || "").trim();
878
2065
  if (agent === "plan") this.sessionModes.set(payload.sessionID, "plan");
879
2066
  else if (agent === "build") this.sessionModes.delete(payload.sessionID);
880
- if (this.activeTurns.has(payload.sessionID)) {
881
- this.reply(env, "error", { code: "BUSY", message: "\u8BE5\u4F1A\u8BDD\u6B63\u5728\u751F\u6210\u4E2D\uFF0C\u8BF7\u7B49\u5F85\u5B8C\u6210\u6216\u5148\u505C\u6B62", id: env.id });
882
- return;
883
- }
884
2067
  try {
885
- await this.runTurn(payload.sessionID, text);
2068
+ await this.pushTurn(payload.sessionID, text);
886
2069
  } catch (e) {
887
2070
  const msg = e.message || String(e);
888
- this.log(`session.prompt \u5931\u8D25: ${msg}`);
2071
+ this.log(this.tr("claude.logPromptFailed", { message: msg }));
889
2072
  this.reply(env, "error", { code: "PROMPT_ERROR", message: msg, id: env.id });
890
2073
  }
891
2074
  }
892
2075
  async sessionAbort(env) {
893
2076
  const { sessionID } = env.payload;
894
- const c = this.activeTurns.get(sessionID);
895
- if (c) {
896
- c.abort();
897
- this.log(`\u5DF2\u53D1\u9001\u4E2D\u6B62 (session=${sessionID})`);
2077
+ const rt = this.sessions.get(sessionID);
2078
+ if (rt && rt.running) {
2079
+ try {
2080
+ await rt.query.interrupt();
2081
+ this.log(this.tr("claude.logAbortSent", { session: sessionID }));
2082
+ } catch (e) {
2083
+ this.log(this.tr("claude.logAbortFailed", { session: sessionID, message: e.message }));
2084
+ } finally {
2085
+ rt.running = false;
2086
+ }
898
2087
  }
899
2088
  this.send(envelope("session.status", { sessionID, status: "idle" }));
900
2089
  }
901
2090
  async sessionDelete(env) {
902
2091
  const { sessionID } = env.payload;
903
- const c = this.activeTurns.get(sessionID);
904
- if (c) c.abort();
905
- this.activeTurns.delete(sessionID);
2092
+ const rt = this.sessions.get(sessionID);
2093
+ if (rt) this.closeRuntime(rt, true);
2094
+ this.sessions.delete(sessionID);
906
2095
  const dir = await this.dirOf(sessionID);
907
2096
  await removeSession(sessionID, dir);
908
2097
  this.sessionCwd.delete(sessionID);
909
2098
  this.pendingWorkspaces.delete(sessionID);
910
2099
  this.sessionModes.delete(sessionID);
911
- this.warmPool?.drop(sessionID);
912
2100
  this.reply(env, "session.deleted", { sessionID });
913
2101
  }
914
2102
  /** 定位会话所在工作区:内存缓存 → SDK 列表 → undefined。 */
@@ -924,7 +2112,11 @@ var ClaudeBridge = class {
924
2112
  return void 0;
925
2113
  }
926
2114
  async commandList(env) {
927
- this.reply(env, "commands", { commands: ADAPTER_COMMANDS });
2115
+ const commands = ADAPTER_COMMANDS.map((c) => ({
2116
+ name: c.name,
2117
+ description: this.tr(c.descriptionKey)
2118
+ }));
2119
+ this.reply(env, "commands", { commands });
928
2120
  }
929
2121
  // ---- 工作区 ----
930
2122
  async workspaceList(env) {
@@ -934,16 +2126,16 @@ var ClaudeBridge = class {
934
2126
  async workspaceSet(env) {
935
2127
  const raw = (env.payload?.path || "").trim();
936
2128
  if (!raw) {
937
- this.reply(env, "error", { code: "BAD_WORKSPACE", message: "\u7F3A\u5C11\u5DE5\u4F5C\u533A\u8DEF\u5F84", id: env.id });
2129
+ this.reply(env, "error", { code: "BAD_WORKSPACE", message: this.tr("claude.errBadWorkspaceEmpty"), id: env.id });
938
2130
  return;
939
2131
  }
940
2132
  const path = isAbsolute(raw) ? raw : resolve(raw);
941
2133
  if (!existsSync2(path) || !statSync(path).isDirectory()) {
942
- this.reply(env, "error", { code: "BAD_WORKSPACE", message: "\u8BE5\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u7528", id: env.id });
2134
+ this.reply(env, "error", { code: "BAD_WORKSPACE", message: this.tr("claude.errBadWorkspaceNotDir"), id: env.id });
943
2135
  return;
944
2136
  }
945
2137
  this.currentWorkspace = path;
946
- this.log("\u5207\u6362\u5DE5\u4F5C\u533A: " + path);
2138
+ this.log(this.tr("claude.logWorkspaceSwitched", { path }));
947
2139
  this.announce();
948
2140
  const workspaces = await fetchWorkspaceList(this.currentWorkspace);
949
2141
  this.reply(env, "workspaces", { workspaces, current: this.currentWorkspace });
@@ -957,6 +2149,27 @@ var ClaudeBridge = class {
957
2149
  }
958
2150
  this.send(envelope("permission.replied", { permissionID: p.permissionID, status: p.status }));
959
2151
  }
2152
+ /** question.reply:手机作答/拒绝结构化提问 → resolve 对应 pending(AskUserQuestion 经此回填)。 */
2153
+ async questionReply(env) {
2154
+ const p = env.payload;
2155
+ const entry = this.questions.get(p.questionID);
2156
+ if (entry) {
2157
+ this.questions.delete(p.questionID);
2158
+ entry.resolve({ answers: p.answers, reject: !!p.reject });
2159
+ } else {
2160
+ this.log(this.tr("claude.logUnknownQuestionReply", { id: p.questionID }));
2161
+ }
2162
+ this.send(envelope("question.replied", { questionID: p.questionID, status: p.reject ? "rejected" : "answered" }));
2163
+ }
2164
+ /** 断线/重置时把所有待作答的结构化提问 resolve 为拒绝(fail-closed),避免挂满超时。 */
2165
+ flushQuestions(reason) {
2166
+ const entries = [...this.questions.values()];
2167
+ this.questions.clear();
2168
+ for (const entry of entries) {
2169
+ entry.resolve({ reject: true });
2170
+ }
2171
+ if (entries.length) this.log(this.tr("claude.logFlushedQuestions", { count: entries.length, reason }));
2172
+ }
960
2173
  /** 断线/重置时把所有待作答请求 resolve 为 deny(fail-closed),避免挂满超时。 */
961
2174
  flushApprovals(reason) {
962
2175
  const entries = [...this.approvals.values()];
@@ -964,85 +2177,161 @@ var ClaudeBridge = class {
964
2177
  for (const entry of entries) {
965
2178
  entry.resolve({ allow: false, reason });
966
2179
  }
967
- if (entries.length) this.log(`\u5DF2\u62D2\u7EDD ${entries.length} \u4E2A\u5F85\u4F5C\u7B54\u8BF7\u6C42: ${reason}`);
2180
+ if (entries.length) this.log(this.tr("claude.logFlushedApprovals", { count: entries.length, reason }));
968
2181
  }
969
- // ---- 回合驱动 ----
970
- /** 每个手机消息 = 一次 query(resume 既有会话 / sessionId 固定新会话),跨会话并行。 */
971
- async runTurn(sessionID, text) {
972
- const dir = this.pendingWorkspaces.get(sessionID) || await this.dirOf(sessionID) || this.currentWorkspace;
973
- if (!existsSync2(dir) || !statSync(dir).isDirectory()) {
974
- throw new Error(`\u5DE5\u4F5C\u533A\u4E0D\u5B58\u5728: ${dir}`);
2182
+ /** session.pending:补发该会话仍未决的审批/选择/输入请求 + 结构化提问(客户端按 id 幂等去重)。 */
2183
+ async sessionPending(env) {
2184
+ const { sessionID } = env.payload;
2185
+ if (!sessionID) return;
2186
+ let n = 0;
2187
+ for (const [, e] of this.approvals) {
2188
+ if (e.req.sessionID !== sessionID) continue;
2189
+ if (Date.now() - e.req.createdAt >= APPROVAL_TIMEOUT_MS) continue;
2190
+ if (this.send(envelope("permission.request", e.req))) n++;
2191
+ }
2192
+ for (const [, e] of this.questions) {
2193
+ if (e.req.sessionID !== sessionID) continue;
2194
+ if (Date.now() - e.req.createdAt >= APPROVAL_TIMEOUT_MS) continue;
2195
+ if (this.send(envelope("question.request", e.req))) n++;
2196
+ }
2197
+ if (n) this.log(this.tr("claude.logPendingResent", { count: n, session: sessionID }));
2198
+ }
2199
+ // ---- 回合驱动(常驻长活会话:每会话一个 query + streaming-input)----
2200
+ /**
2201
+ * 推送一条手机消息到会话:会话已存在(长活 query 在跑)则 push 触发新轮;
2202
+ * 否则先建常驻会话(首轮),再 push。跨轮复用同一 claude 子进程(无逐条冷启动)。
2203
+ */
2204
+ async pushTurn(sessionID, text) {
2205
+ let rt = this.sessions.get(sessionID);
2206
+ if (!rt) {
2207
+ rt = await this.ensureSession(sessionID);
2208
+ this.sessions.set(sessionID, rt);
2209
+ }
2210
+ if (rt.closed) {
2211
+ rt = await this.ensureSession(sessionID);
2212
+ this.sessions.set(sessionID, rt);
2213
+ }
2214
+ if (rt.running) {
2215
+ throw new Error(this.tr("claude.errBusy"));
975
2216
  }
976
- this.pendingWorkspaces.delete(sessionID);
977
- const known = await sessionExists(sessionID, dir);
978
- const controller = new AbortController();
979
- this.activeTurns.set(sessionID, controller);
980
- this.send(envelope("session.status", { sessionID, status: "running" }));
981
- const streamer = new TurnStreamer(
982
- sessionID,
983
- (s, messageID, part) => this.send(envelope("message.part", { sessionID: s, messageID, part })),
984
- (s, status) => this.send(envelope("session.status", { sessionID: s, status }))
985
- );
986
2217
  const mode = this.turnModeFor(sessionID, void 0);
987
- const withPermission = mode !== "bypassPermissions";
2218
+ if (mode !== rt.mode) {
2219
+ try {
2220
+ await rt.query.setPermissionMode(mode);
2221
+ rt.mode = mode;
2222
+ } catch (e) {
2223
+ this.log(this.tr("claude.logSetModeFailed", { session: sessionID, message: e.message }));
2224
+ }
2225
+ }
2226
+ rt.running = true;
2227
+ rt.channel.push(userMessage(text));
2228
+ this.send(envelope("session.status", { sessionID, status: "running" }));
2229
+ }
2230
+ /** 组装长活会话 Options(mode/回调/模型/env/官方插件 与旧 runTurn 一致;不含按轮 abort)。 */
2231
+ buildOptions(sessionID, dir, mode) {
988
2232
  const options = {
989
2233
  cwd: dir,
990
2234
  includePartialMessages: true,
991
2235
  permissionMode: mode,
992
- abortController: controller,
993
2236
  // 对话框白名单(默认空 = 不发任何 dialog,fail-closed);onElicitation 见下
994
2237
  supportedDialogKinds: this.cfg.dialogKinds ?? [],
995
2238
  onUserDialog: (req, info) => this.onUserDialog(sessionID, req, info),
996
2239
  onElicitation: (req, info) => this.onElicitation(sessionID, req, info)
997
2240
  };
998
- if (withPermission) {
2241
+ if (mode === "bypassPermissions") {
2242
+ options.allowDangerouslySkipPermissions = true;
2243
+ }
2244
+ if (mode !== "bypassPermissions") {
999
2245
  const canUseTool = (toolName, input, info) => this.askPermission(sessionID, toolName, input, info);
1000
2246
  options.canUseTool = canUseTool;
1001
2247
  }
1002
2248
  if (this.cfg.model) options.model = this.cfg.model;
1003
2249
  if (this.cfg.thinking) options.thinking = this.cfg.thinking;
1004
2250
  if (this.cfg.maxThinkingTokens != null) options.maxThinkingTokens = this.cfg.maxThinkingTokens;
2251
+ const env = { ...process.env };
2252
+ for (const [k, v] of Object.entries(this.cfg.env ?? {})) {
2253
+ if (v !== void 0 && v !== "") env[k] = v;
2254
+ }
1005
2255
  const pluginDir = this.cfg.pluginDir || defaultOfficialPluginDir();
1006
2256
  if (pluginDir) {
1007
2257
  options.plugins = [
1008
2258
  { type: "local", path: pluginDir }
1009
2259
  ];
1010
- options.env = { ...this.cfg.env || {}, TUNNELBOX_SDK_SESSION: "1" };
1011
- } else if (this.cfg.env && Object.keys(this.cfg.env).length) {
1012
- options.env = this.cfg.env;
2260
+ env.TUNNELBOX_SDK_SESSION = "1";
1013
2261
  }
2262
+ options.env = env;
2263
+ return options;
2264
+ }
2265
+ /**
2266
+ * 建立(或重建)某会话的常驻长活 query:以常开 PushChannel 为输入,
2267
+ * 后台 for-await 消费 Query 生成器,把流式消息实时转发手机;
2268
+ * 收到 result 表示该轮结束(running=false、广播 idle + session.updated),进程保持等待下一轮。
2269
+ */
2270
+ async ensureSession(sessionID) {
2271
+ const dir = this.pendingWorkspaces.get(sessionID) || await this.dirOf(sessionID) || this.currentWorkspace;
2272
+ if (!existsSync2(dir) || !statSync(dir).isDirectory()) {
2273
+ throw new Error(this.tr("claude.errWorkspaceMissing", { dir }));
2274
+ }
2275
+ this.pendingWorkspaces.delete(sessionID);
2276
+ const known = await sessionExists(sessionID, dir);
2277
+ const mode = this.turnModeFor(sessionID, void 0);
2278
+ const options = this.buildOptions(sessionID, dir, mode);
1014
2279
  if (known) options.resume = sessionID;
1015
2280
  else options.sessionId = sessionID;
1016
- let last = null;
2281
+ const channel = new PushChannel();
2282
+ const turn = new TurnStreamer(
2283
+ sessionID,
2284
+ (s, messageID, part) => this.send(envelope("message.part", { sessionID: s, messageID, part })),
2285
+ (s, status) => this.send(envelope("session.status", { sessionID: s, status }))
2286
+ );
2287
+ const q = query({ prompt: channel, options });
2288
+ const rt = {
2289
+ sessionID,
2290
+ dir,
2291
+ query: q,
2292
+ channel,
2293
+ turn,
2294
+ running: false,
2295
+ closed: false,
2296
+ mode,
2297
+ task: Promise.resolve()
2298
+ };
2299
+ rt.task = this.consumeLoop(rt, options);
2300
+ if (!this.sessionCwd.has(sessionID)) this.sessionCwd.set(sessionID, dir);
2301
+ return rt;
2302
+ }
2303
+ /** 后台消费常驻 query 生成器;仅在会话主动关闭或子进程异常退出时结束。 */
2304
+ async consumeLoop(rt, options) {
2305
+ const { sessionID } = rt;
1017
2306
  try {
1018
- let gen;
1019
- const warm = this.warmPool?.take(sessionID);
1020
- if (warm) {
1021
- gen = warm.query(text);
1022
- } else {
1023
- gen = query({ prompt: text, options });
1024
- }
1025
- for await (const msg of gen) {
1026
- last = msg;
1027
- streamer.feed(msg);
1028
- }
1029
- if (last && last.type === "result" && last.is_error) {
1030
- const subtype = last.subtype;
1031
- const text2 = last.result;
1032
- this.send(
1033
- envelope("message.part", {
1034
- sessionID,
1035
- messageID: `m-err-${Date.now()}`,
1036
- part: { id: "err", type: "text", text: `[claude] ${text2 || subtype || "\u751F\u6210\u51FA\u9519"}`, complete: true }
1037
- })
1038
- );
2307
+ for await (const msg of rt.query) {
2308
+ if (rt.closed) break;
2309
+ rt.turn.feed(msg);
2310
+ if (msg.type === "result") {
2311
+ const r = msg;
2312
+ if (r.is_error) {
2313
+ const subtype = r.subtype;
2314
+ const text2 = r.result;
2315
+ this.send(
2316
+ envelope("message.part", {
2317
+ sessionID,
2318
+ messageID: `m-err-${Date.now()}`,
2319
+ part: { id: "err", type: "text", text: `[claude] ${text2 || subtype || this.tr("claude.errTurn")}`, complete: true }
2320
+ })
2321
+ );
2322
+ }
2323
+ if (this.sessions.get(sessionID) === rt) {
2324
+ rt.running = false;
2325
+ this.send(envelope("session.status", { sessionID, status: "idle" }));
2326
+ void this.broadcastSessionUpdate(sessionID);
2327
+ }
2328
+ }
1039
2329
  }
1040
2330
  } catch (e) {
1041
- if (controller.signal.aborted) {
1042
- this.log(`\u4F1A\u8BDD\u5DF2\u4E2D\u6B62 (session=${sessionID})`);
1043
- } else {
1044
- const msg = e.message || String(e);
1045
- this.log(`\u56DE\u5408\u51FA\u9519 (session=${sessionID}): ${msg}`);
2331
+ const msg = e.message || String(e);
2332
+ this.log(this.tr("claude.logSessionError", { session: sessionID, message: msg }));
2333
+ if (this.sessions.get(sessionID) === rt) {
2334
+ rt.running = false;
1046
2335
  this.send(
1047
2336
  envelope("message.part", {
1048
2337
  sessionID,
@@ -1050,16 +2339,31 @@ var ClaudeBridge = class {
1050
2339
  part: { id: "err", type: "text", text: `[claude] ${msg}`, complete: true }
1051
2340
  })
1052
2341
  );
2342
+ this.send(envelope("session.status", { sessionID, status: "idle" }));
1053
2343
  }
1054
2344
  } finally {
1055
- this.activeTurns.delete(sessionID);
1056
- if (!this.sessionCwd.has(sessionID)) this.sessionCwd.set(sessionID, dir);
1057
- this.send(envelope("session.status", { sessionID, status: "idle" }));
1058
- if (this.warmPool) void this.warmPool.replenish(sessionID, options).catch(() => {
1059
- });
1060
- void this.broadcastSessionUpdate(sessionID);
2345
+ rt.closed = true;
2346
+ if (this.sessions.get(sessionID) === rt) {
2347
+ this.sessions.delete(sessionID);
2348
+ this.send(envelope("session.status", { sessionID, status: "idle" }));
2349
+ }
1061
2350
  }
1062
2351
  }
2352
+ /** 主动关闭某会话的常驻 query(end channel → 子进程退出)。 */
2353
+ closeRuntime(rt, notifyOffline) {
2354
+ if (rt.closed) return;
2355
+ rt.closed = true;
2356
+ rt.running = false;
2357
+ try {
2358
+ rt.channel.end();
2359
+ } catch {
2360
+ }
2361
+ try {
2362
+ rt.query.close();
2363
+ } catch {
2364
+ }
2365
+ if (notifyOffline) this.send(envelope("session.status", { sessionID: rt.sessionID, status: "idle" }));
2366
+ }
1063
2367
  /** 广播 session.updated(最新 listSessions 信息)。 */
1064
2368
  async broadcastSessionUpdate(sessionID) {
1065
2369
  try {
@@ -1076,25 +2380,57 @@ var ClaudeBridge = class {
1076
2380
  askPermission(sessionID, toolName, input, info) {
1077
2381
  return new Promise((resolve3) => {
1078
2382
  if (!this.ws.isConnected) {
1079
- this.log(`\u4E2D\u7EE7\u79BB\u7EBF\uFF0C\u81EA\u52A8\u62D2\u7EDD\u5DE5\u5177 ${toolName}`);
1080
- resolve3({ behavior: "deny", message: "\u4E2D\u7EE7\u79BB\u7EBF\uFF0C\u81EA\u52A8\u62D2\u7EDD\uFF08fail-closed\uFF09" });
2383
+ this.log(this.tr("claude.logOfflineDenyTool", { tool: toolName }));
2384
+ resolve3({ behavior: "deny", message: this.tr("claude.errOfflineDeny") });
2385
+ return;
2386
+ }
2387
+ if (toolName === "AskUserQuestion") {
2388
+ if (this.cfg.askUserQuestion === false) {
2389
+ this.log(this.tr("claude.logAuqDisabled"));
2390
+ resolve3({ behavior: "deny", message: this.tr("claude.errAuqDisabled") });
2391
+ return;
2392
+ }
2393
+ void (async () => {
2394
+ const questions = mapAskUserQuestion(input);
2395
+ if (!questions || !questions.length) {
2396
+ this.log(this.tr("claude.logAuqParseFailed"));
2397
+ resolve3({ behavior: "deny", message: this.tr("claude.errAuqParse") });
2398
+ return;
2399
+ }
2400
+ const ans = await this.askUserQuestions(sessionID, questions, info.signal);
2401
+ if (!ans || ans.reject) {
2402
+ this.log(this.tr("claude.logAuqSkipped", { session: sessionID }));
2403
+ resolve3({ behavior: "deny", message: this.tr("claude.errAuqCancelled") });
2404
+ return;
2405
+ }
2406
+ const answers = {};
2407
+ const raw = input.questions ?? [];
2408
+ raw.forEach((q, i) => {
2409
+ const labels = ans.answers[i];
2410
+ if (q.question && labels?.length) answers[q.question] = labels.join(",");
2411
+ });
2412
+ this.log(this.tr("claude.logAuqDone", { session: sessionID }));
2413
+ resolve3({ behavior: "allow", updatedInput: { ...input ?? {}, answers } });
2414
+ })();
1081
2415
  return;
1082
2416
  }
1083
2417
  const tool = toolName || "tool";
1084
2418
  const id = `p-${++this.approvalSeq}-${Date.now()}`;
1085
- const prompt = info.title || info.description || (info.decisionReason ? `Claude Code: ${info.decisionReason}` : `Claude Code \u8BF7\u6C42\u6267\u884C ${tool}`);
2419
+ const prompt = info.title || info.description || (info.decisionReason ? `Claude Code: ${info.decisionReason}` : this.tr("claude.promptRunTool", { tool }));
1086
2420
  const timer = setTimeout(() => {
1087
2421
  this.approvals.delete(id);
1088
- resolve3({ behavior: "deny", message: "\u5BA1\u6279\u8D85\u65F6\uFF0C\u81EA\u52A8\u62D2\u7EDD\uFF08fail-closed\uFF09" });
2422
+ resolve3({ behavior: "deny", message: this.tr("claude.errApprovalTimeout") });
1089
2423
  }, APPROVAL_TIMEOUT_MS);
2424
+ const payload = { id, sessionID, tool, args: input ?? {}, prompt, createdAt: Date.now() };
1090
2425
  this.approvals.set(id, {
1091
2426
  tool,
1092
2427
  kind: "approval",
2428
+ req: payload,
1093
2429
  resolve: (o) => {
1094
2430
  clearTimeout(timer);
1095
2431
  this.approvals.delete(id);
1096
2432
  if (!o.allow) {
1097
- resolve3({ behavior: "deny", message: o.reason || "\u7528\u6237\u62D2\u7EDD" });
2433
+ resolve3({ behavior: "deny", message: o.reason || this.tr("claude.errUserDenied") });
1098
2434
  } else if (o.always && info.suggestions?.length) {
1099
2435
  resolve3({ behavior: "allow", updatedPermissions: info.suggestions });
1100
2436
  } else {
@@ -1108,25 +2444,67 @@ var ClaudeBridge = class {
1108
2444
  () => {
1109
2445
  clearTimeout(timer);
1110
2446
  this.approvals.delete(id);
1111
- resolve3({ behavior: "deny", message: "\u8BF7\u6C42\u5DF2\u4E2D\u6B62" });
2447
+ resolve3({ behavior: "deny", message: this.tr("claude.errAborted") });
1112
2448
  },
1113
2449
  { once: true }
1114
2450
  );
1115
- this.log(`\u5BA1\u6279\u8BF7\u6C42(tool=${tool}) \u2192 \u53D1\u9001\u5230\u624B\u673A\u6743\u9650\u5361 permissionID=${id}`);
1116
- const ok = this.send(
1117
- envelope("permission.request", {
1118
- id,
1119
- sessionID,
1120
- tool,
1121
- args: input ?? {},
1122
- prompt,
1123
- createdAt: Date.now()
1124
- })
1125
- );
2451
+ this.log(this.tr("claude.logPermToPhone", { tool, id }));
2452
+ const ok = this.send(envelope("permission.request", payload));
1126
2453
  if (!ok) {
1127
2454
  clearTimeout(timer);
1128
2455
  this.approvals.delete(id);
1129
- resolve3({ behavior: "deny", message: "\u4E2D\u7EE7\u4E0D\u53EF\u8FBE\uFF0C\u81EA\u52A8\u62D2\u7EDD\uFF08fail-closed\uFF09" });
2456
+ resolve3({ behavior: "deny", message: this.tr("claude.errRelayUnreachable") });
2457
+ }
2458
+ });
2459
+ }
2460
+ /**
2461
+ * 向手机发起结构化提问(question.request,AskUserQuestion 用)。
2462
+ * 支持多题/多选/自定义(QuestionInfo.multiple/custom),手机 QuestionCard/AskInline 渲染。
2463
+ * 返回用户作答 answers(每题所选标签数组)或 null(拒绝/离线/超时/中止)。
2464
+ */
2465
+ askUserQuestions(sessionID, questions, signal) {
2466
+ return new Promise((resolve3) => {
2467
+ if (!this.ws.isConnected) {
2468
+ this.log(this.tr("claude.logQuestionOffline"));
2469
+ resolve3(null);
2470
+ return;
2471
+ }
2472
+ const id = `q-${++this.questionSeq}-${Date.now()}`;
2473
+ const timer = setTimeout(() => {
2474
+ this.questions.delete(id);
2475
+ resolve3(null);
2476
+ }, APPROVAL_TIMEOUT_MS);
2477
+ const req = {
2478
+ id,
2479
+ sessionID,
2480
+ prompt: questions.map((q) => q.question).join("\n"),
2481
+ questions,
2482
+ createdAt: Date.now()
2483
+ };
2484
+ this.questions.set(id, {
2485
+ sessionID,
2486
+ req,
2487
+ resolve: (o) => {
2488
+ clearTimeout(timer);
2489
+ this.questions.delete(id);
2490
+ resolve3({ answers: o.answers ?? [], reject: !!o.reject });
2491
+ }
2492
+ });
2493
+ signal?.addEventListener(
2494
+ "abort",
2495
+ () => {
2496
+ clearTimeout(timer);
2497
+ this.questions.delete(id);
2498
+ resolve3(null);
2499
+ },
2500
+ { once: true }
2501
+ );
2502
+ this.log(this.tr("claude.logQuestionToPhone", { count: questions.length, id }));
2503
+ const ok = this.send(envelope("question.request", req));
2504
+ if (!ok) {
2505
+ clearTimeout(timer);
2506
+ this.questions.delete(id);
2507
+ resolve3(null);
1130
2508
  }
1131
2509
  });
1132
2510
  }
@@ -1137,7 +2515,7 @@ var ClaudeBridge = class {
1137
2515
  askUserChoice(sessionID, tool, kind, prompt, options, signal) {
1138
2516
  return new Promise((resolve3) => {
1139
2517
  if (!this.ws.isConnected) {
1140
- this.log(`\u4E2D\u7EE7\u79BB\u7EBF\uFF0C\u5BF9\u8BDD\u6846\u81EA\u52A8\u53D6\u6D88\uFF08${tool}\uFF09`);
2518
+ this.log(this.tr("claude.logDialogOffline", { tool }));
1141
2519
  resolve3(null);
1142
2520
  return;
1143
2521
  }
@@ -1146,9 +2524,19 @@ var ClaudeBridge = class {
1146
2524
  this.approvals.delete(id);
1147
2525
  resolve3(null);
1148
2526
  }, APPROVAL_TIMEOUT_MS);
2527
+ const payload = {
2528
+ id,
2529
+ sessionID,
2530
+ tool,
2531
+ args: { dialogKind: tool },
2532
+ prompt,
2533
+ createdAt: Date.now(),
2534
+ ...kind === "approval" ? {} : kind === "choice" && options?.length ? { kind, options } : { kind }
2535
+ };
1149
2536
  this.approvals.set(id, {
1150
2537
  tool,
1151
2538
  kind,
2539
+ req: payload,
1152
2540
  resolve: (o) => {
1153
2541
  clearTimeout(timer);
1154
2542
  this.approvals.delete(id);
@@ -1168,18 +2556,8 @@ var ClaudeBridge = class {
1168
2556
  },
1169
2557
  { once: true }
1170
2558
  );
1171
- this.log(`\u5BF9\u8BDD\u6846\u8BF7\u6C42(kind=${kind} tool=${tool}) \u2192 \u53D1\u9001\u5230\u624B\u673A\u6743\u9650\u5361 permissionID=${id}`);
1172
- const ok = this.send(
1173
- envelope("permission.request", {
1174
- id,
1175
- sessionID,
1176
- tool,
1177
- args: { dialogKind: tool },
1178
- prompt,
1179
- createdAt: Date.now(),
1180
- ...kind === "approval" ? {} : kind === "choice" && options?.length ? { kind, options } : { kind }
1181
- })
1182
- );
2559
+ this.log(this.tr("claude.logDialogToPhone", { kind, tool, id }));
2560
+ const ok = this.send(envelope("permission.request", payload));
1183
2561
  if (!ok) {
1184
2562
  clearTimeout(timer);
1185
2563
  this.approvals.delete(id);
@@ -1188,15 +2566,32 @@ var ClaudeBridge = class {
1188
2566
  });
1189
2567
  }
1190
2568
  /**
1191
- * request_user_dialog:把白名单内的 kind 映射为手机 choice/input 卡。
1192
- * 仅当 kind cfg.dialogKinds(运行时白名单)内时才会被 SDK 发射并走到这里;
2569
+ * request_user_dialog:把白名单内的 kind 映射为手机交互。
2570
+ * - permission_ask_user_question:兜底路径(实测 AskUserQuestion canUseTool,见 askPermission;此处防止 CLI 改走 dialog 通道)
2571
+ * - 其余白名单 kind → choice/input 卡(tryMapDialog 启发式)。
1193
2572
  * 未映射/离线/超时 → cancelled(走 CLI 默认路径)。
1194
2573
  * 注意:payload/result 为 kind 相关结构,映射为实验性,需装 claude 后实测校正。
1195
2574
  */
1196
2575
  async onUserDialog(sessionID, req, info) {
1197
- const mapped = tryMapDialog(req.dialogKind, req.payload);
2576
+ if (req.dialogKind === "permission_ask_user_question") {
2577
+ const questions = mapAskUserQuestion(req.payload);
2578
+ if (!questions || !questions.length) {
2579
+ this.log(this.tr("claude.logAuqDialogParse", { session: sessionID }));
2580
+ return null;
2581
+ }
2582
+ const ans2 = await this.askUserQuestions(
2583
+ sessionID,
2584
+ questions,
2585
+ info.signal
2586
+ );
2587
+ if (ans2 && !ans2.reject) {
2588
+ return { behavior: "completed", result: { answers: ans2.answers } };
2589
+ }
2590
+ return { behavior: "cancelled" };
2591
+ }
2592
+ const mapped = tryMapDialog(req.dialogKind, req.payload, this.tr);
1198
2593
  if (!mapped) {
1199
- this.log(`request_user_dialog(kind=${req.dialogKind}) \u672A\u63A5\u5165\u6620\u5C04\uFF0C\u4FDD\u6301\u9ED8\u8BA4\u884C\u4E3A\uFF08session=${sessionID}\uFF09`);
2594
+ this.log(this.tr("claude.logDialogUnmapped", { kind: req.dialogKind, session: sessionID }));
1200
2595
  return null;
1201
2596
  }
1202
2597
  const ans = await this.askUserChoice(
@@ -1223,7 +2618,7 @@ var ClaudeBridge = class {
1223
2618
  sessionID,
1224
2619
  tool,
1225
2620
  "approval",
1226
- `MCP\u300C${req.serverName}\u300D\u8BF7\u6C42\u6388\u6743
2621
+ `${this.tr("claude.promptMcpAuth", { server: req.serverName })}
1227
2622
 
1228
2623
  ${req.message || ""}${req.url ? `
1229
2624
 
@@ -1234,12 +2629,13 @@ URL: ${req.url}` : ""}`,
1234
2629
  if (ans) return { action: "accept" };
1235
2630
  return { action: "decline" };
1236
2631
  }
1237
- this.log(`MCP elicitation(server=${req.serverName}, mode=${req.mode ?? "-"}) \u672A\u63A5\u5165\uFF0C\u81EA\u52A8\u62D2\u7EDD\uFF08session=${sessionID}\uFF09`);
2632
+ this.log(this.tr("claude.logElicitUnmapped", { server: req.serverName, mode: req.mode ?? "-", session: sessionID }));
1238
2633
  return { action: "decline" };
1239
2634
  }
1240
2635
  resetIdentity() {
1241
- this.log("\u88AB\u8D26\u53F7\u89E3\u7ED1\uFF0C\u6B63\u5728\u91CD\u7F6E\u8EAB\u4EFD\u5E76\u91CD\u65B0\u751F\u6210\u914D\u5BF9\u7801\u2026");
1242
- this.flushApprovals("\u88AB\u8D26\u53F7\u89E3\u7ED1\uFF0C\u81EA\u52A8\u62D2\u7EDD\uFF08fail-closed\uFF09");
2636
+ this.log(this.tr("claude.logResetIdentity"));
2637
+ this.flushApprovals(this.tr("claude.reasonUnbind"));
2638
+ this.flushQuestions(this.tr("claude.reasonUnbind"));
1243
2639
  this.claimed = false;
1244
2640
  this.state.agentId = randomBytes2(16).toString("hex");
1245
2641
  this.state.claimed = false;
@@ -1260,14 +2656,19 @@ function localTimezone() {
1260
2656
  // src/index.ts
1261
2657
  var ENV_PREFIX = "TUNNELBOX_CLAUDE_ENV_";
1262
2658
  var VALID_MODES = /* @__PURE__ */ new Set(["default", "plan", "acceptEdits", "bypassPermissions", "dontAsk", "auto"]);
2659
+ var AGENT_TYPE2 = "claude-code";
2660
+ var tr3 = makeT(resolveLang({ saved: loadState(AGENT_TYPE2).lang }));
1263
2661
  function envOn(name) {
1264
2662
  return /^(1|true|yes|on)$/i.test(process.env[name] || "");
1265
2663
  }
2664
+ function envOff(name) {
2665
+ return /^(0|false|no|off)$/i.test(process.env[name] || "");
2666
+ }
1266
2667
  function parseMode(raw) {
1267
2668
  const m = (raw || "").trim().toLowerCase();
1268
2669
  if (!m) return void 0;
1269
2670
  if (!VALID_MODES.has(m)) {
1270
- console.warn(`[tunnelbox:claude] \u5FFD\u7565\u65E0\u6548 TUNNELBOX_CLAUDE_MODE: ${raw}\uFF08\u5141\u8BB8 ${[...VALID_MODES].join("/")}\uFF09`);
2671
+ console.warn(`[tunnelbox:claude] ${tr3("claude.warnInvalidMode", { raw: raw ?? "", list: [...VALID_MODES].join("/") })}`);
1271
2672
  return void 0;
1272
2673
  }
1273
2674
  return m;
@@ -1281,7 +2682,7 @@ function parseThinking(raw) {
1281
2682
  if (s === "on" || s === "enabled") return as({ type: "enabled" });
1282
2683
  const m = s.match(/^enabled:(\d+)$/);
1283
2684
  if (m) return as({ type: "enabled", budgetTokens: Number(m[1]) });
1284
- console.warn(`[tunnelbox:claude] \u5FFD\u7565\u65E0\u6548 TUNNELBOX_CLAUDE_THINKING: ${raw}\uFF08off|adaptive|enabled[:budget]\uFF09`);
2685
+ console.warn(`[tunnelbox:claude] ${tr3("claude.warnInvalidThinking", { raw: raw ?? "" })}`);
1285
2686
  return void 0;
1286
2687
  }
1287
2688
  function parseEnvPassthrough() {
@@ -1308,11 +2709,13 @@ function start(opts = {}) {
1308
2709
  env: parseEnvPassthrough(),
1309
2710
  warm: envOn("TUNNELBOX_CLAUDE_WARM"),
1310
2711
  dialogKinds: parseDialogKinds(process.env.TUNNELBOX_CLAUDE_DIALOG_KINDS),
1311
- pluginDir: process.env.TUNNELBOX_CLAUDE_PLUGIN_DIR?.trim() || void 0
2712
+ askUserQuestion: !envOff("TUNNELBOX_CLAUDE_ASK_USER_QUESTION"),
2713
+ pluginDir: process.env.TUNNELBOX_CLAUDE_PLUGIN_DIR?.trim() || void 0,
2714
+ forcePair: opts.forcePair ?? process.argv.includes("--pair")
1312
2715
  };
1313
2716
  const bridge = new ClaudeBridge(cfg);
1314
2717
  void bridge.start().catch((e) => {
1315
- console.error("[tunnelbox:claude] \u542F\u52A8\u5931\u8D25:", e);
2718
+ console.error(`[tunnelbox:claude] ${tr3("claude.errStartFailed")}`, e);
1316
2719
  });
1317
2720
  let stopped = false;
1318
2721
  return {
@@ -1344,7 +2747,7 @@ if (isEntry) {
1344
2747
  };
1345
2748
  process.once("SIGINT", onSignal);
1346
2749
  process.once("SIGTERM", onSignal);
1347
- console.log("[tunnelbox:claude] Claude Code \u9002\u914D\u5668\u5DF2\u542F\u52A8\uFF08Ctrl+C \u9000\u51FA\uFF09");
2750
+ console.log(`[tunnelbox:claude] ${tr3("claude.msgStarted")}`);
1348
2751
  }
1349
2752
  export {
1350
2753
  start