@tunnelbox/core 0.1.18 → 0.1.20

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.
@@ -1,166 +1,277 @@
1
- {
2
- "agent.err.createSession": "建立會話失敗",
3
- "agent.err.httpClient": "無法存取 opencode HTTP client",
4
- "agent.err.unknownPerm": "未知的權限請求: {id}",
5
- "agent.err.unknownQuestion": "未知的提問請求: {id}",
6
- "agent.log.fallback404": "回包首選端點 404,改走另一端點: {message}",
7
- "agent.log.gotPermReply": "收到手機權限回覆 (id={id} {reply} {version})",
8
- "agent.log.gotQuestionReply": "收到手機提問回覆 (id={id} {status} {version})",
9
- "agent.log.handleFailed": "處理 {type} 失敗: {message}",
10
- "agent.log.msgOk": "取得會話訊息成功 session={session} data={count} 方式={limited} win={win} ms={ms}",
11
- "agent.log.msgFailed": "取得會話訊息失敗 session={session} err={err} ms={ms}",
12
- "agent.log.msgLimitFallback": "目前的 opencode 不支援 limit,改為全量取得(session={session})",
13
- "agent.log.httpOk": "opencode HTTP {status} {path}",
14
- "agent.log.pendingResent": "session.pending: 補發 {session} 積壓請求 {count} 條",
15
- "agent.log.unhandledType": "未處理的訊息類型: {type}",
16
- "events.log.fwdPermission": "轉發權限請求到手機 (id={id} tool={tool} {version})",
17
- "events.log.sessionDeleted": "轉發 session.deleted 到手機 (session={session})",
18
- "events.log.permissionReplied": "opencode 回執 permission.replied (id={id} {response})",
19
- "events.log.questionRejected": "opencode 回執 question.rejected (id={id})",
20
- "events.log.questionReplied": "opencode 回執 question.replied (id={id})",
21
- "index.error.connectRelay": "無法連接中繼,請檢查中繼位址後重試",
22
- "index.error.pairTimeout": "等待配對碼逾時(中繼未回應)",
23
- "index.log.claimed": "已綁定到手機帳號,之後手機直接從「我的電腦」連接,不需再掃碼",
24
- "index.log.connectedRelay": "已連接中繼 {url}",
25
- "index.log.loaded": "tunnelbox opencode 外掛已啟動(v{version})",
26
- "index.log.disconnected": "與中繼斷線,正在自動重連…",
27
- "index.log.exit": "已退出",
28
- "index.log.pairCode": "產生配對碼: {code}",
29
- "index.log.pairingStillClaimed": "此配對碼僅供同帳號的新手機重新配對;本機已綁定,仍可從手機「我的電腦」直接連接。",
30
- "index.log.pairingPreparing": "正在連線中繼並產生配對碼…",
31
- "index.log.permissionToPhone": "權限請求 → 手機審批 (id={id} tool={tool})",
32
- "index.log.permissionTuiFallback": "app 離線,權限請求由 opencode TUI 代管 (id={id} tool={tool})",
33
- "index.log.relayError": "中繼錯誤: {msg}",
34
- "index.log.resetIdentity": "已解除帳號綁定,正在重設身分並重新產生配對碼…",
35
- "index.log.unknown": "未知",
36
- "install.codeLine": "配對碼: {code}",
37
- "install.commandInstalled": "已安裝指令: {path}",
38
- "install.done": "完成。已嘗試連接中繼輸出本機配對資訊:",
39
- "install.failConnect": "(未能即時連接中繼列印配對資訊——中繼無法連線或目前 Node 過舊。",
40
- "install.failConnectHint": "重啟 opencode 後可用 --print-logs 查看終端 QR Code,或是在 opencode 內輸入「顯示手機配對碼」或 /pair。)",
41
- "install.hintFresh": "提示:配對碼為一次性,約 10 分鐘有效;若稍後才啟動 opencode,以啟動時終端列印或 /pair 取得的最新配對資訊為準。",
42
- "install.pluginInstalled": "已安裝外掛: {path}",
43
- "install.qrUnavailable": "(QR Code 無法顯示,請使用上方配對碼)",
44
- "install.qrcodeEnsure": "正在將 qrcode 安裝到 opencode 設定目錄…",
45
- "install.qrcodeFail": "qrcode 安裝失敗(不影響使用,僅顯示配對碼): {err}",
46
- "install.relayError": "中繼傳回錯誤: {msg}",
47
- "install.scanHint": "手機開啟 tunnelbox App「掃描配對」,掃描上方 QR Code 即可連接:",
48
- "pair.heading": "tunnelbox 手機配對",
49
- "pair.howTo": "在手機上開啟 tunnelbox App,用「掃描配對」掃描上方 QR Code,或手動輸入配對碼。",
50
- "pair.oneTime": "配對碼為一次性,使用後即失效;如需新碼,可要求「重新整理配對碼」。",
51
- "pair.promptBody": "請幫我查看本機手機遠端配對資訊:顯示配對碼與可供掃碼的 QR Code。QR Code 請放在等寬程式碼區塊內並逐字原樣保留,方便我用手機掃描。",
52
- "pair.promptDescription": "顯示手機遠端配對的配對碼與掃碼 QR Code",
53
- "pair.scanIntro": "**掃碼配對**:手機開啟 tunnelbox App「掃描配對」,掃描下方 QR Code 即可自動連接。",
54
- "perm.promptAuthorize": "opencode 請求授權",
55
- "perm.promptPatterns": "opencode 請求存取:{list}",
56
- "perm.promptResource": "opencode 請求存取資源:{list}",
57
- "perm.promptTool": "opencode 請求執行 {tool}",
58
- "qr.codeLabel": "配對碼",
59
- "qr.fileWritten": "配對資訊已寫入: {path}",
60
- "qr.openHint": "開啟 tunnelbox App,用「掃描配對」掃描上方 QR Code,或手動輸入配對碼。",
61
- "qr.titleNoType": "tunnelbox 手機配對",
62
- "qr.titleWithType": "tunnelbox {type} 手機配對",
63
- "qr.unavailable": "(未安裝 qrcode,請使用上方配對碼)",
64
- "claude.errBusy": "該會話正在產生中,請等待完成或先停止",
65
- "claude.errTurn": "產生出錯",
66
- "claude.pairingStillClaimed": "已綁定手機帳號:此配對碼僅供同帳號新裝置重新配對,目前裝置可直接從手機「我的電腦」連接",
67
- "claude.pairForced": "--pair:已強制申請配對碼(僅本次)",
68
- "claude.pairUnclaimed": "未偵測到已綁定帳號,開始配對引導:",
69
- "claude.claimedHint": "已綁定手機帳號:從手機「我的電腦」直接連接即可",
70
- "claude.errStartFailed": "啟動失敗:",
71
- "claude.msgStarted": "Claude Code 介面卡已啟動(Ctrl+C 結束)",
72
- "claude.warnInvalidMode": "忽略無效 TUNNELBOX_CLAUDE_MODE: {raw}(允許 {list})",
73
- "claude.warnInvalidThinking": "忽略無效 TUNNELBOX_CLAUDE_THINKING: {raw}(off|adaptive|enabled[:budget])",
74
- "claude.cmdNew": "新開會話",
75
- "claude.cmdHelp": "顯示說明",
76
- "claude.errNoRelay": "未設定中繼位址:建置請用 --env 注入預設,執行可用 TUNNELBOX_RELAY_URL 覆寫",
77
- "claude.logConnected": "已連線中繼 {url}",
78
- "claude.logMsgUncaught": "訊息處理未捕獲例外: {message}",
79
- "claude.reasonDisconnect": "中繼斷線(fail-closed 自動拒絕)",
80
- "claude.reasonUnbind": "帳號解除綁定(fail-closed 自動拒絕)",
81
- "claude.logDisconnected": "與中繼斷線,正在自動重連…",
82
- "claude.logPairCode": "產生配對碼: {code}",
83
- "claude.logUnhandledType": "未處理的訊息類型: {type}",
84
- "claude.logHandleFailed": "處理 {type} 失敗: {message}",
85
- "claude.newSessionTitle": "新會話",
86
- "claude.helpText": "支援的指令:\n- /new 新開會話\n- /help 顯示說明",
87
- "claude.logPromptFailed": "session.prompt 失敗: {message}",
88
- "claude.logAbortSent": "已送出中止 (session={session})",
89
- "claude.logAbortFailed": "中止失敗 (session={session}): {message}",
90
- "claude.errBadWorkspaceEmpty": "缺少工作區路徑",
91
- "claude.errBadWorkspaceNotDir": "該目錄不存在或不可用",
92
- "claude.logWorkspaceSwitched": "切換工作區: {path}",
93
- "claude.logUnknownQuestionReply": "未相符的結構化提問應答 (id={id})",
94
- "claude.logFlushedQuestions": "已取消 {count} 個待回答提問: {reason}",
95
- "claude.logFlushedApprovals": "已拒絕 {count} 個待作答請求: {reason}",
96
- "claude.logPendingResent": "session.pending 補發待辦 {count} 筆 (session={session})",
97
- "claude.logSetModeFailed": "setPermissionMode 失敗 (session={session}): {message}",
98
- "claude.errWorkspaceMissing": "工作區不存在: {dir}",
99
- "claude.logSessionError": "會話出錯 (session={session}): {message}",
100
- "claude.logOfflineDenyTool": "中繼離線,自動拒絕工具 {tool}",
101
- "claude.errOfflineDeny": "中繼離線,自動拒絕(fail-closed)",
102
- "claude.logAuqDisabled": "AskUserQuestion 已關閉(TUNNELBOX_CLAUDE_ASK_USER_QUESTION=0),自動拒絕",
103
- "claude.errAuqDisabled": "AskUserQuestion 提問未啟用",
104
- "claude.logAuqParseFailed": "AskUserQuestion input 解析失敗,自動拒絕(fail-closed)",
105
- "claude.errAuqParse": "無法解析題目",
106
- "claude.logAuqSkipped": "AskUserQuestion 未作答/取消 (session={session})",
107
- "claude.errAuqCancelled": "使用者未選擇(cancelled)",
108
- "claude.logAuqDone": "AskUserQuestion 作答完成 (session={session})",
109
- "claude.promptRunTool": "Claude Code 請求執行 {tool}",
110
- "claude.errApprovalTimeout": "審批逾時,自動拒絕(fail-closed)",
111
- "claude.errUserDenied": "使用者拒絕",
112
- "claude.errAborted": "請求已中止",
113
- "claude.logPermToPhone": "審批請求(tool={tool}) → 傳送到手機權限卡 permissionID={id}",
114
- "claude.errRelayUnreachable": "中繼不可達,自動拒絕(fail-closed)",
115
- "claude.logQuestionOffline": "中繼離線,結構化提問自動取消",
116
- "claude.logQuestionToPhone": "結構化提問({count} 題) → 傳送到手機 questionID={id}",
117
- "claude.logDialogOffline": "中繼離線,對話框自動取消({tool})",
118
- "claude.logDialogToPhone": "對話框請求(kind={kind} tool={tool}) → 傳送到手機權限卡 permissionID={id}",
119
- "claude.logAuqDialogParse": "AskUserQuestion payload 解析失敗,維持預設行為(session={session})",
120
- "claude.logDialogUnmapped": "request_user_dialog(kind={kind}) 未接入對應,維持預設行為(session={session})",
121
- "claude.promptMcpAuth": "MCP「{server}」請求授權",
122
- "claude.logElicitUnmapped": "MCP elicitation(server={server}, mode={mode}) 未接入,自動拒絕(session={session})",
123
- "claude.logResetIdentity": "被帳號解除綁定,正在重設身分並重新產生配對碼…",
124
- "claude.dialogConfirm": "Claude Code 請求確認({kind})",
125
- "claude.resultTruncated": "…(結果過長已截斷)",
126
- "claude.streamError": "claude 串流出錯",
127
- "claude.errListSessions": "讀取 Claude 會話列表失敗",
128
- "claude.errReadHistory": "讀取會話歷史失敗({id})",
129
- "setup.langSet": "已設定執行語言",
130
- "setup.langHint": "重新啟動介面卡後將以該語言輸出(執行時可被 TUNNELBOX_LANG 覆寫)。狀態檔:",
131
- "setup.langCurrent": "目前執行語言",
132
- "hermes.errNoRelay": "未設定中繼位址:建置請用 --env 注入預設,執行可用 TUNNELBOX_RELAY_URL 覆寫",
133
- "hermes.logConnected": "已連線中繼 {url}",
134
- "hermes.logMsgUncaught": "訊息處理未捕獲例外: {message}",
135
- "hermes.logDisconnected": "與中繼斷線,正在自動重新連線…",
136
- "hermes.logPairCode": "產生配對碼: {code}",
137
- "hermes.logUnhandledType": "未處理的訊息類型: {type}",
138
- "hermes.logHandleFailed": "處理 {type} 失敗: {message}",
139
- "hermes.logClaimed": "已綁定到手機帳號,之後手機從「我的電腦」直接連線",
140
- "hermes.logResetIdentity": "被帳號解綁,正在重設身分並重新產生配對碼…",
141
- "hermes.cmdNew": "新增會話",
142
- "hermes.cmdHelp": "顯示說明",
143
- "hermes.newSessionTitle": "新會話",
144
- "hermes.helpText": "支援的命令:\n- /new 新增會話\n- /help 顯示說明",
145
- "hermes.errBusy": "此會話正在產生中,請等待完成或先停止",
146
- "hermes.errBadWorkspace": "此目錄不存在或無法使用",
147
- "hermes.logWorkspaceSwitched": "切換工作區: {path}",
148
- "hermes.errWorkspaceMissing": "工作區不存在: {dir}",
149
- "hermes.errStartFailed": "啟動失敗: {message}",
150
- "hermes.msgStarted": "Hermes 介接器已啟動(Ctrl+C 退出)",
151
- "hermes.logAbortSent": "已傳送中止 (session={session})",
152
- "hermes.logPromptFailed": "session.prompt 失敗: {message}",
153
- "hermes.logTurnError": "回合出錯 (session={session}): {message}",
154
- "hermes.logPairForced": "--pair:已強制申請配對碼(僅本次)",
155
- "hermes.pairingStillClaimed": "已綁定手機帳號:此配對碼僅供同帳號新裝置重新配對,目前裝置可直接從手機「我的電腦」連線",
156
- "hermes.logPairNoRelay": "未設定中繼位址,無法申請配對碼",
157
- "hermes.errSpawnHermes": "無法啟動 Hermes({bin}):請確認已安裝({err})",
158
- "hermes.errHermesExit": "hermes 退出({code}): {err}",
159
- "hermes.logSessionUpdated": "會話已更新 (session={session})",
160
- "hermes.errNoInject": "手機訊息注入不可用(目前工作階段非互動式或處理程序未就緒)",
161
- "hermes.pairNoRelay": "無法連線中繼(relay 不可達或 websockets 缺失)",
162
- "hermes.pairTimeout": "申請配對碼逾時(中繼未回應)",
163
- "hermes.errToolPair": "tunnelbox_pair 失敗: {err}",
164
- "hermes.errToolStatus": "tunnelbox_status 失敗: {err}",
165
- "hermes.cliUsage": "用法: /tunnelbox <pair|status>\n pair 向中繼申請手機配對碼並列印\n status 顯示中繼/綁定/連線狀態"
166
- }
1
+ {
2
+ "agent.err.createSession": "建立會話失敗",
3
+ "agent.err.httpClient": "無法存取 opencode HTTP client",
4
+ "agent.err.unknownPerm": "未知的權限請求: {id}",
5
+ "agent.err.unknownQuestion": "未知的提問請求: {id}",
6
+ "agent.log.fallback404": "回包首選端點 404,改走另一端點: {message}",
7
+ "agent.log.gotPermReply": "收到手機權限回覆 (id={id} {reply} {version})",
8
+ "agent.log.gotQuestionReply": "收到手機提問回覆 (id={id} {status} {version})",
9
+ "agent.log.handleFailed": "處理 {type} 失敗: {message}",
10
+ "agent.log.msgOk": "取得會話訊息成功 session={session} data={count} 方式={limited} win={win} ms={ms}",
11
+ "agent.log.msgFailed": "取得會話訊息失敗 session={session} err={err} ms={ms}",
12
+ "agent.log.msgLimitFallback": "目前的 opencode 不支援 limit,改為全量取得(session={session})",
13
+ "agent.log.httpOk": "opencode HTTP {status} {path}",
14
+ "agent.log.pendingResent": "session.pending: 補發 {session} 積壓請求 {count} 條",
15
+ "agent.log.unhandledType": "未處理的訊息類型: {type}",
16
+ "events.log.fwdPermission": "轉發權限請求到手機 (id={id} tool={tool} {version})",
17
+ "events.log.sessionDeleted": "轉發 session.deleted 到手機 (session={session})",
18
+ "events.log.permissionReplied": "opencode 回執 permission.replied (id={id} {response})",
19
+ "events.log.questionRejected": "opencode 回執 question.rejected (id={id})",
20
+ "events.log.questionReplied": "opencode 回執 question.replied (id={id})",
21
+ "index.error.connectRelay": "無法連接中繼,請檢查中繼位址後重試",
22
+ "index.error.pairTimeout": "等待配對碼逾時(中繼未回應)",
23
+ "index.log.claimed": "已綁定到手機帳號,之後手機直接從「我的電腦」連接,不需再掃碼",
24
+ "index.log.connectedRelay": "已連接中繼 {url}",
25
+ "index.log.loaded": "tunnelbox opencode 外掛已啟動(v{version})",
26
+ "index.log.disconnected": "與中繼斷線,正在自動重連…",
27
+ "index.log.exit": "已退出",
28
+ "index.log.pairCode": "產生配對碼: {code}",
29
+ "index.log.pairingStillClaimed": "此配對碼僅供同帳號的新手機重新配對;本機已綁定,仍可從手機「我的電腦」直接連接。",
30
+ "index.log.pairingPreparing": "正在連線中繼並產生配對碼…",
31
+ "index.log.permissionToPhone": "權限請求 → 手機審批 (id={id} tool={tool})",
32
+ "index.log.permissionTuiFallback": "app 離線,權限請求由 opencode TUI 代管 (id={id} tool={tool})",
33
+ "index.log.relayError": "中繼錯誤: {msg}",
34
+ "index.log.resetIdentity": "已解除帳號綁定,正在重設身分並重新產生配對碼…",
35
+ "index.log.unknown": "未知",
36
+ "install.codeLine": "配對碼: {code}",
37
+ "install.commandInstalled": "已安裝指令: {path}",
38
+ "install.done": "完成。已嘗試連接中繼輸出本機配對資訊:",
39
+ "install.failConnect": "(未能即時連接中繼列印配對資訊——中繼無法連線或目前 Node 過舊。",
40
+ "install.failConnectHint": "重啟 opencode 後可用 --print-logs 查看終端 QR Code,或是在 opencode 內輸入「顯示手機配對碼」或 /pair。)",
41
+ "install.hintFresh": "提示:配對碼為一次性,約 10 分鐘有效;若稍後才啟動 opencode,以啟動時終端列印或 /pair 取得的最新配對資訊為準。",
42
+ "install.pluginInstalled": "已安裝外掛: {path}",
43
+ "install.qrUnavailable": "(QR Code 無法顯示,請使用上方配對碼)",
44
+ "install.qrcodeEnsure": "正在將 qrcode 安裝到 opencode 設定目錄…",
45
+ "install.qrcodeFail": "qrcode 安裝失敗(不影響使用,僅顯示配對碼): {err}",
46
+ "install.relayError": "中繼傳回錯誤: {msg}",
47
+ "install.scanHint": "手機開啟 tunnelbox App「掃描配對」,掃描上方 QR Code 即可連接:",
48
+ "pair.heading": "tunnelbox 手機配對",
49
+ "pair.howTo": "在手機上開啟 tunnelbox App,用「掃描配對」掃描上方 QR Code,或手動輸入配對碼。",
50
+ "pair.oneTime": "配對碼為一次性,約 10 分鐘內有效;使用或過期後即失效,如需新碼可要求「重新整理配對碼」。",
51
+ "pair.promptBody": "請幫我查看本機手機遠端配對資訊:顯示配對碼與可供掃碼的 QR Code。QR Code 請放在等寬程式碼區塊內並逐字原樣保留,方便我用手機掃描。",
52
+ "pair.promptDescription": "顯示手機遠端配對的配對碼與掃碼 QR Code",
53
+ "pair.scanIntro": "**掃碼配對**:手機開啟 tunnelbox App「掃描配對」,掃描下方 QR Code 即可自動連接。",
54
+ "perm.promptAuthorize": "opencode 請求授權",
55
+ "perm.promptPatterns": "opencode 請求存取:{list}",
56
+ "perm.promptResource": "opencode 請求存取資源:{list}",
57
+ "perm.promptTool": "opencode 請求執行 {tool}",
58
+ "qr.codeLabel": "配對碼",
59
+ "qr.fileWritten": "配對資訊已寫入: {path}",
60
+ "qr.openHint": "開啟 tunnelbox App,用「掃描配對」掃描上方 QR Code,或手動輸入配對碼。",
61
+ "qr.titleNoType": "tunnelbox 手機配對",
62
+ "qr.titleWithType": "tunnelbox {type} 手機配對",
63
+ "qr.unavailable": "(未安裝 qrcode,請使用上方配對碼)",
64
+ "claude.errBusy": "該會話正在產生中,請等待完成或先停止",
65
+ "claude.errTurn": "產生出錯",
66
+ "claude.pairingStillClaimed": "已綁定手機帳號:此配對碼僅供同帳號新裝置重新配對,目前裝置可直接從手機「我的電腦」連接",
67
+ "claude.pairForced": "--pair:已強制申請配對碼(僅本次)",
68
+ "claude.pairUnclaimed": "未偵測到已綁定帳號,開始配對引導:",
69
+ "claude.claimedHint": "已綁定手機帳號:從手機「我的電腦」直接連接即可",
70
+ "claude.errStartFailed": "啟動失敗:",
71
+ "claude.msgStarted": "Claude Code 介面卡已啟動(Ctrl+C 結束)",
72
+ "claude.warnInvalidMode": "忽略無效 TUNNELBOX_CLAUDE_MODE: {raw}(允許 {list})",
73
+ "claude.warnInvalidThinking": "忽略無效 TUNNELBOX_CLAUDE_THINKING: {raw}(off|adaptive|enabled[:budget])",
74
+ "claude.cmdNew": "新開會話",
75
+ "claude.cmdHelp": "顯示說明",
76
+ "claude.errNoRelay": "未設定中繼位址:建置請用 --env 注入預設,執行可用 TUNNELBOX_RELAY_URL 覆寫",
77
+ "claude.logConnected": "已連線中繼 {url}",
78
+ "claude.logMsgUncaught": "訊息處理未捕獲例外: {message}",
79
+ "claude.reasonDisconnect": "中繼斷線(fail-closed 自動拒絕)",
80
+ "claude.reasonUnbind": "帳號解除綁定(fail-closed 自動拒絕)",
81
+ "claude.logDisconnected": "與中繼斷線,正在自動重連…",
82
+ "claude.logPairCode": "產生配對碼: {code}",
83
+ "claude.logUnhandledType": "未處理的訊息類型: {type}",
84
+ "claude.logHandleFailed": "處理 {type} 失敗: {message}",
85
+ "claude.newSessionTitle": "新會話",
86
+ "claude.helpText": "支援的指令:\n- /new 新開會話\n- /help 顯示說明",
87
+ "claude.logPromptFailed": "session.prompt 失敗: {message}",
88
+ "claude.logAbortSent": "已送出中止 (session={session})",
89
+ "claude.logAbortFailed": "中止失敗 (session={session}): {message}",
90
+ "claude.errBadWorkspaceEmpty": "缺少工作區路徑",
91
+ "claude.errBadWorkspaceNotDir": "該目錄不存在或不可用",
92
+ "claude.logWorkspaceSwitched": "切換工作區: {path}",
93
+ "claude.logUnknownQuestionReply": "未相符的結構化提問應答 (id={id})",
94
+ "claude.logFlushedQuestions": "已取消 {count} 個待回答提問: {reason}",
95
+ "claude.logFlushedApprovals": "已拒絕 {count} 個待作答請求: {reason}",
96
+ "claude.logPendingResent": "session.pending 補發待辦 {count} 筆 (session={session})",
97
+ "claude.logSetModeFailed": "setPermissionMode 失敗 (session={session}): {message}",
98
+ "claude.errWorkspaceMissing": "工作區不存在: {dir}",
99
+ "claude.logSessionError": "會話出錯 (session={session}): {message}",
100
+ "claude.logOfflineDenyTool": "中繼離線,自動拒絕工具 {tool}",
101
+ "claude.errOfflineDeny": "中繼離線,自動拒絕(fail-closed)",
102
+ "claude.logAuqDisabled": "AskUserQuestion 已關閉(TUNNELBOX_CLAUDE_ASK_USER_QUESTION=0),自動拒絕",
103
+ "claude.errAuqDisabled": "AskUserQuestion 提問未啟用",
104
+ "claude.logAuqParseFailed": "AskUserQuestion input 解析失敗,自動拒絕(fail-closed)",
105
+ "claude.errAuqParse": "無法解析題目",
106
+ "claude.logAuqSkipped": "AskUserQuestion 未作答/取消 (session={session})",
107
+ "claude.errAuqCancelled": "使用者未選擇(cancelled)",
108
+ "claude.logAuqDone": "AskUserQuestion 作答完成 (session={session})",
109
+ "claude.promptRunTool": "Claude Code 請求執行 {tool}",
110
+ "claude.errApprovalTimeout": "審批逾時,自動拒絕(fail-closed)",
111
+ "claude.errUserDenied": "使用者拒絕",
112
+ "claude.errAborted": "請求已中止",
113
+ "claude.logPermToPhone": "審批請求(tool={tool}) → 傳送到手機權限卡 permissionID={id}",
114
+ "claude.errRelayUnreachable": "中繼不可達,自動拒絕(fail-closed)",
115
+ "claude.logQuestionOffline": "中繼離線,結構化提問自動取消",
116
+ "claude.logQuestionToPhone": "結構化提問({count} 題) → 傳送到手機 questionID={id}",
117
+ "claude.logDialogOffline": "中繼離線,對話框自動取消({tool})",
118
+ "claude.logDialogToPhone": "對話框請求(kind={kind} tool={tool}) → 傳送到手機權限卡 permissionID={id}",
119
+ "claude.logAuqDialogParse": "AskUserQuestion payload 解析失敗,維持預設行為(session={session})",
120
+ "claude.logDialogUnmapped": "request_user_dialog(kind={kind}) 未接入對應,維持預設行為(session={session})",
121
+ "claude.promptMcpAuth": "MCP「{server}」請求授權",
122
+ "claude.logElicitUnmapped": "MCP elicitation(server={server}, mode={mode}) 未接入,自動拒絕(session={session})",
123
+ "claude.logResetIdentity": "被帳號解除綁定,正在重設身分並重新產生配對碼…",
124
+ "claude.dialogConfirm": "Claude Code 請求確認({kind})",
125
+ "claude.resultTruncated": "…(結果過長已截斷)",
126
+ "claude.streamError": "claude 串流出錯",
127
+ "claude.errListSessions": "讀取 Claude 會話列表失敗",
128
+ "claude.errReadHistory": "讀取會話歷史失敗({id})",
129
+ "setup.langSet": "已設定執行語言",
130
+ "setup.langHint": "重新啟動介面卡後將以該語言輸出(執行時可被 TUNNELBOX_LANG 覆寫)。狀態檔:",
131
+ "setup.langCurrent": "目前執行語言",
132
+ "hermes.errNoRelay": "未設定中繼位址:建置請用 --env 注入預設,執行可用 TUNNELBOX_RELAY_URL 覆寫",
133
+ "hermes.logConnected": "已連線中繼 {url}",
134
+ "hermes.logMsgUncaught": "訊息處理未捕獲例外: {message}",
135
+ "hermes.logDisconnected": "與中繼斷線,正在自動重新連線…",
136
+ "hermes.logPairCode": "產生配對碼: {code}",
137
+ "hermes.logUnhandledType": "未處理的訊息類型: {type}",
138
+ "hermes.logHandleFailed": "處理 {type} 失敗: {message}",
139
+ "hermes.logClaimed": "已綁定到手機帳號,之後手機從「我的電腦」直接連線",
140
+ "hermes.logResetIdentity": "被帳號解綁,正在重設身分並重新產生配對碼…",
141
+ "hermes.cmdNew": "新增會話",
142
+ "hermes.cmdHelp": "顯示說明",
143
+ "hermes.newSessionTitle": "新會話",
144
+ "hermes.helpText": "支援的命令:\n- /new 新增會話\n- /help 顯示說明",
145
+ "hermes.errBusy": "此會話正在產生中,請等待完成或先停止",
146
+ "hermes.errBadWorkspace": "此目錄不存在或無法使用",
147
+ "hermes.logWorkspaceSwitched": "切換工作區: {path}",
148
+ "hermes.errWorkspaceMissing": "工作區不存在: {dir}",
149
+ "hermes.errStartFailed": "啟動失敗: {message}",
150
+ "hermes.msgStarted": "Hermes 介接器已啟動(Ctrl+C 退出)",
151
+ "hermes.logAbortSent": "已傳送中止 (session={session})",
152
+ "hermes.logPromptFailed": "session.prompt 失敗: {message}",
153
+ "hermes.logTurnError": "回合出錯 (session={session}): {message}",
154
+ "hermes.logPairForced": "--pair:已強制申請配對碼(僅本次)",
155
+ "hermes.pairingStillClaimed": "已綁定手機帳號:此配對碼僅供同帳號新裝置重新配對,目前裝置可直接從手機「我的電腦」連線",
156
+ "hermes.logPairNoRelay": "未設定中繼位址,無法申請配對碼",
157
+ "hermes.errSpawnHermes": "無法啟動 Hermes({bin}):請確認已安裝({err})",
158
+ "hermes.errHermesExit": "hermes 退出({code}): {err}",
159
+ "hermes.logSessionUpdated": "會話已更新 (session={session})",
160
+ "hermes.errNoInject": "手機訊息注入不可用(目前工作階段非互動式或處理程序未就緒)",
161
+ "hermes.pairNoRelay": "無法連線中繼(relay 不可達或 websockets 缺失)",
162
+ "hermes.pairTimeout": "申請配對碼逾時(中繼未回應)",
163
+ "hermes.errToolPair": "tunnelbox_pair 失敗: {err}",
164
+ "hermes.errToolStatus": "tunnelbox_status 失敗: {err}",
165
+ "hermes.cliUsage": "用法: /tunnelbox <pair|status>\n pair 向中繼申請手機配對碼並列印\n status 顯示中繼/綁定/連線狀態",
166
+ "qr.validFor": "配對碼約 10 分鐘內有效,過期請在電腦端重新取得。",
167
+ "pair.toolDesc": "顯示本機的手機配對資訊(配對碼與可掃描 QR code)。當使用者詢問配對、掃描 QR code 或用手機控制本機時呼叫。",
168
+ "pair.toolRefreshDesc": "是否向中繼申請新的配對碼",
169
+ "state.errSaveState": "儲存狀態失敗",
170
+ "adapter.logConnected": "已連接中繼 {url}",
171
+ "adapter.logDisconnected": "與中繼斷開,正在自動重連…",
172
+ "adapter.logPairCode": "產生配對碼: {code}",
173
+ "adapter.logClaimed": "已綁定到手機帳號,之後手機從「我的電腦」直接連接",
174
+ "adapter.logResetIdentity": "被帳號解綁,正在重設身分並重新產生配對碼…",
175
+ "adapter.logUnhandledType": "未處理的訊息類型: {type}",
176
+ "adapter.errNoRelay": "未設定中繼位址:建置時用 --env 注入預設值,或執行時用 TUNNELBOX_RELAY_URL 覆寫",
177
+ "adapter.errBusy": "該工作階段正在生成中,請等待完成或先停止",
178
+ "adapter.errBadWorkspaceEmpty": "缺少工作區路徑",
179
+ "adapter.errBadWorkspaceNotDir": "該目錄不存在或不可用",
180
+ "adapter.errWorkspaceMissing": "工作區不存在: {dir}",
181
+ "adapter.cmdNew": "新建工作階段",
182
+ "adapter.cmdHelp": "顯示說明",
183
+ "adapter.helpText": "支援的命令:\n- /new 新建工作階段\n- /help 顯示說明",
184
+ "adapter.newSessionTitle": "新工作階段",
185
+ "install.langHelpTitle": "install-local 支援語言(--lang <tag>):",
186
+ "install.langHelpExample": "範例: npm run install-local -- --lang de-DE",
187
+ "install.unknownLang": "未知語言 \"{raw}\",已回退 en-US。支援:{list}",
188
+ "hermes.errAskRequired": "question 必填",
189
+ "hermes.msgPhoneUnavailable": "手機不可用或未回應:請改用內建 clarify 在本機提問,或自行判斷繼續。",
190
+ "hermes.errClarifyUnavailable": "clarify 支援不可用",
191
+ "hermes.errAskFailed": "tunnelbox_ask 失敗: {err}",
192
+ "hermes.errClarifyFailed": "clarify 失敗: {err}",
193
+ "bin.denied": "手機已拒絕",
194
+ "bin.timeout": "審批逾時(fail-closed)",
195
+ "bin.reqRun": "Claude Code 請求執行 {tool}",
196
+ "bin.pairTitle": "tunnelbox 手機配對",
197
+ "bin.pairCodeLine": " 配對碼: {code}",
198
+ "bin.pairHint": "打開 tunnelbox App,用「掃一掃配對」掃描 QR code,或手動輸入配對碼(一次性)。",
199
+ "bin.pairWritten": " 配對資訊已寫入: {path}",
200
+ "bin.pairRequestFailed": "向中繼申請配對碼失敗。",
201
+ "bin.alreadyClaimed": "本機已綁定手機帳號,可直接在手機「我的電腦」裡連接;如需重新配對,請先在手機端解綁。",
202
+ "bin.noRelay": "未設定中繼位址:請設定環境變數 TUNNELBOX_RELAY_URL 後重試。",
203
+ "bin.connectFailed": "無法連接中繼:{relay}\n請設定 TUNNELBOX_RELAY_URL 或在官方-plugin 狀態檔案中設定。",
204
+ "bin.statusRelay": "中繼: {value}",
205
+ "bin.statusAgent": "agent 識別碼: {value}",
206
+ "bin.statusClaimed": "已綁定手機: {value}",
207
+ "bin.statusStateFile": "狀態檔案: {value}",
208
+ "bin.statusLatest": "最新配對資訊:",
209
+ "bin.usage": "用法: tunnelbox <pair|status|ask>",
210
+ "bin.usagePair": " pair 取手機配對碼並列印",
211
+ "bin.usageStatus": " status 顯示中繼/綁定狀態",
212
+ "bin.usageAsk": " ask 把工具審批轉發到手機",
213
+ "adapter.logMsgUncaught": "訊息處理未捕捉例外: {message}",
214
+ "adapter.logHandleFailed": "處理 {type} 失敗: {message}",
215
+ "adapter.logPromptFailed": "session.prompt 失敗: {message}",
216
+ "adapter.logAbortSent": "已發送中止 (session={session})",
217
+ "adapter.errUserDenied": "使用者拒絕",
218
+ "adapter.logPendingResent": "session.pending 補發待審批 {count} 條",
219
+ "adapter.logWorkspaceSwitched": "切換工作區: {path}",
220
+ "adapter.logNewSession": "新工作階段落盤 codexId={codexId} (alias={session})",
221
+ "adapter.logSessionError": "回合出錯 (session={session}): {message}",
222
+ "adapter.logDshSent": "已發送到 dsh (session={session})",
223
+ "adapter.logReadHistoryFailed": "讀取工作階段歷史失敗({session}): {message}",
224
+ "adapter.logAnswererInitFailed": "應答器初始化失敗: {message}",
225
+ "adapter.logRemoteMode": "雙端同顯模式:選擇/審批走 typert 多端廣播,手機與 dsh web 同顯、誰答誰得",
226
+ "adapter.logRemoteProbeFailed": "typert 遠端事件源探測失敗,回退本地應答模式: {message}",
227
+ "adapter.logLocalMode": "本地應答模式:選擇/審批由手機作答(headless,無 dsh web 廣播)",
228
+ "adapter.logRemoteStreamInterrupted": "remote events 串流中斷: {message}",
229
+ "adapter.logResultRejected": "$events/result 未接受({eventId}): {message}",
230
+ "adapter.logResultBackfillFailed": "$events/result 回填失敗({eventId}): {message}",
231
+ "adapter.logRemoteQuestion": "remote 提問廣播 → 手機提問卡 questionID={id} (session={session})",
232
+ "adapter.errRemoteReadyFrame": "remote events 首幀異常",
233
+ "adapter.logRemoteApproval": "remote 審批廣播 → 手機權限卡 permissionID={id} (session={session})",
234
+ "adapter.logUserQuestionRoute": "user-questions 選擇 → 路由 session={session}",
235
+ "adapter.errDshQuestionEmpty": "dsh 提問內容為空",
236
+ "adapter.errQuestionTimeout": "提問逾時({seconds}s),已取消",
237
+ "adapter.errQuestionCancelledAbort": "提問已取消(agent 中止)",
238
+ "adapter.errQuestionCancelledRelay": "中繼不可達,提問已取消",
239
+ "adapter.logQuestionToPhone": "user-questions 選擇 → 發送到手機提問卡 questionID={id} (session={session})",
240
+ "adapter.errQuestionUserCancelled": "使用者在手機端取消/拒絕了提問",
241
+ "adapter.logApprovalRequest": "審批請求(tool={tool}) → 發送到手機權限卡 permissionID={id}",
242
+ "adapter.logAgentPresetFailed": "agentPreset 解析失敗({preset}),降級為無 preset: {message}",
243
+ "adapter.logSessionLiveAdopted": "工作階段 {session} 已由本行程其它 agent 持有(live),採納現有 agent 繼續驅動: {message}",
244
+ "adapter.logDisposeSkipped": "已採納的外部 live agent 不執行 dispose(工作階段 {session} 由原持有者管理)",
245
+ "adapter.logAgentCreateFailed": "建立/恢復 agent 失敗({session}): {message}",
246
+ "adapter.errSessionBusyLive": "工作階段 {session} 正被佔用(live),請稍後重試",
247
+ "adapter.errAgentsUnavailable": "agents 服務不可用",
248
+ "adapter.logToolReason": "dsh 請求執行工具 {tool}",
249
+ "adapter.logGatewayConnected": "已連接本機 OpenClaw Gateway({version})",
250
+ "adapter.logGatewayDisconnected": "與 OpenClaw Gateway 斷開,正在由官方客戶端重連…",
251
+ "adapter.logGatewayConnectFailed": "無法連接本機 OpenClaw Gateway(首次需在本機放行裝置:`openclaw devices list` + `openclaw devices approve <requestId>`)",
252
+ "adapter.errGatewayNoSessionKey": "Gateway 未返回工作階段 key",
253
+ "adapter.logDeleteSessionFailed": "刪除工作階段失敗({session}): {message}(仍按已刪除回執)",
254
+ "adapter.logApprovalReplyFailed": "審批應答失敗({id}): {message}",
255
+ "adapter.logPendingApproval": "session.pending 補發待審批 {count} 條 (session={session})",
256
+ "adapter.workspaceDefault": "預設",
257
+ "adapter.cursorSessionTitle": "Cursor 工作階段",
258
+ "adapter.reasonDisconnect": "中繼斷開,自動拒絕(fail-closed)",
259
+ "adapter.warnInvalidEnv": "忽略無效 {name}: {raw}({list})",
260
+ "adapter.errStartFailed": "啟動失敗:",
261
+ "adapter.msgStarted": "{name} 適配器已啟動(Ctrl+C 退出)",
262
+ "adapter.warnCliMissing": "未找到執行檔「{bin}」;請先安裝(如 npm i -g @openai/codex),或用 TUNNELBOX_CODEX_BIN 指定執行檔路徑",
263
+ "adapter.errCliNotFound": "未找到 {name} 執行檔「{bin}」:請安裝 Codex CLI(npm i -g @openai/codex)或設定 TUNNELBOX_CODEX_BIN",
264
+ "adapter.errSessionNotFound": "會話不存在或已被刪除",
265
+ "adapter.checkHeader": "tunnelbox-codex 環境自檢",
266
+ "adapter.checkCliOk": "{name} 執行檔: {path}",
267
+ "adapter.checkCliMissing": "未找到 {name} 執行檔({bin})",
268
+ "bin.yes": "是",
269
+ "bin.no": "否",
270
+ "adapter.errAppServerSpawn": "無法啟動 codex app-server:{message}(請確認 Codex CLI 支援 `codex app-server`)",
271
+ "adapter.errAppServerExit": "codex app-server 已結束,進行中的審批/選擇已自動拒絕",
272
+ "adapter.logUnsupportedServerRequest": "暫不支援的 app-server 請求: {method}",
273
+ "adapter.logQuestionNative": "選擇請求 → 手機選擇卡 questionID={id} (session={session})",
274
+ "adapter.logPermissionNative": "審批請求 → 手機權限卡 permissionID={id} (session={session})",
275
+ "adapter.checkAppServerOk": "codex app-server 可用",
276
+ "adapter.checkAppServerMissing": "目前 Codex CLI 不支援 `codex app-server`(請升級 Codex CLI)"
277
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tunnelbox/core",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "tunnelbox 共享核心:协议类型、中继客户端、状态持久化、二维码配对",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -22,6 +22,7 @@
22
22
  "scripts": {
23
23
  "type-check": "tsc --noEmit",
24
24
  "check:i18n": "node scripts/check-locales.mjs",
25
+ "test:cli": "node scripts/test-cli.mjs",
25
26
  "build": "node scripts/build.mjs",
26
27
  "build:dev": "node scripts/build.mjs",
27
28
  "build:test": "node scripts/build.mjs",
package/src/cli.ts CHANGED
@@ -3,8 +3,8 @@
3
3
  * 供各适配器驱动本机 CLI 时复用(Linux/macOS 直接 spawn 可执行文件)。
4
4
  */
5
5
  import { spawnSync, type ChildProcess } from "node:child_process";
6
- import { existsSync } from "node:fs";
7
- import { join } from "node:path";
6
+ import { existsSync, statSync } from "node:fs";
7
+ import { isAbsolute, join, resolve } from "node:path";
8
8
 
9
9
  /** 参数是否需要 cmd 引号(含空格或 cmd 元字符)。 */
10
10
  export function needsCmdQuote(s: string): boolean {
@@ -30,6 +30,62 @@ export function findWinShim(name: string): string | null {
30
30
  return null;
31
31
  }
32
32
 
33
+ export interface ResolvedCli {
34
+ /** 可执行文件路径(裸名在非 win32 下保持裸名,交由 spawn 走 PATH) */
35
+ path: string;
36
+ /** 是否需经 shell 执行(Windows `.cmd/.bat` 直接 spawn 会被拒绝,需 `cmd /c`) */
37
+ viaShell: boolean;
38
+ }
39
+
40
+ const WIN_CLI_EXTS = [".exe", ".cmd", ".bat"] as const;
41
+
42
+ function hasPathSep(name: string): boolean {
43
+ return name.includes("/") || name.includes("\\");
44
+ }
45
+
46
+ function fileExists(p: string): boolean {
47
+ try {
48
+ return statSync(p).isFile();
49
+ } catch {
50
+ return false;
51
+ }
52
+ }
53
+
54
+ function whichInPath(name: string): ResolvedCli | null {
55
+ const dirs = (process.env.PATH || "").split(";").filter(Boolean);
56
+ for (const dir of dirs) {
57
+ for (const ext of WIN_CLI_EXTS) {
58
+ const p = join(dir, name + ext);
59
+ if (fileExists(p)) return { path: p, viaShell: ext !== ".exe" };
60
+ }
61
+ }
62
+ return null;
63
+ }
64
+
65
+ /**
66
+ * 解析本机 CLI 可执行文件(适配器驱动外部 CLI 用)。
67
+ * - 含路径分隔符(绝对/相对):归一为绝对路径并探测;win32 无扩展名时依次补 `.exe/.cmd/.bat`;
68
+ * - 裸名:win32 扫 PATH 找 `.exe/.cmd/.bat`(`.exe` 优先);其余平台返回裸名交 spawn 解析。
69
+ * 找不到返回 null(调用方据此给出安装指引)。
70
+ */
71
+ export function resolveCliBinary(name: string): ResolvedCli | null {
72
+ const raw = (name || "").trim();
73
+ if (!raw) return null;
74
+ if (hasPathSep(raw)) {
75
+ const base = isAbsolute(raw) ? raw : resolve(process.cwd(), raw);
76
+ const candidates = [base];
77
+ if (process.platform === "win32" && !/\.[a-z0-9]+$/i.test(base)) {
78
+ for (const ext of WIN_CLI_EXTS) candidates.push(base + ext);
79
+ }
80
+ for (const p of candidates) {
81
+ if (fileExists(p)) return { path: p, viaShell: /\.(cmd|bat)$/i.test(p) };
82
+ }
83
+ return null;
84
+ }
85
+ if (process.platform === "win32") return whichInPath(raw);
86
+ return { path: raw, viaShell: false };
87
+ }
88
+
33
89
  /**
34
90
  * 终止子进程:
35
91
  * - Windows:`taskkill /PID <pid> /T /F` 杀整棵进程树(经 shell 启动时只杀 cmd.exe 不够);
package/src/qr.ts CHANGED
@@ -72,7 +72,7 @@ export async function printPairing(
72
72
  lang?: Lang,
73
73
  ): Promise<void> {
74
74
  const resolved = lang ? normalizeLang(lang) : resolveLang();
75
- const file = writePairingFile(code, type);
75
+ const file = writePairingFile(code, type, resolved);
76
76
  const tt = (key: Parameters<typeof t>[1], vars?: Parameters<typeof t>[2]) => t(resolved, key, vars);
77
77
 
78
78
  const title = type ? tt("qr.titleWithType", { type }) : tt("qr.titleNoType");
@@ -90,6 +90,7 @@ export async function printPairing(
90
90
  console.log("│ " + padVisual(codeLine, W - 2) + " │");
91
91
  console.log("└" + bar + "┘");
92
92
  console.log(" " + hint);
93
+ console.log(" " + tt("qr.validFor"));
93
94
 
94
95
  try {
95
96
  const mod = await import("qrcode");
package/src/state.ts CHANGED
@@ -9,7 +9,7 @@ import { randomBytes } from "node:crypto";
9
9
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
10
10
  import { homedir } from "node:os";
11
11
  import { dirname, join } from "node:path";
12
- import { normalizeLang, type Lang } from "./i18n";
12
+ import { normalizeLang, resolveLang, t, type Lang } from "./i18n";
13
13
 
14
14
  export interface State {
15
15
  agentId: string;
@@ -39,7 +39,7 @@ export function saveState(st: State, type?: string): void {
39
39
  mkdirSync(dirname(p), { recursive: true });
40
40
  writeFileSync(p, JSON.stringify(st, null, 2), "utf8");
41
41
  } catch (e) {
42
- console.error("[tunnelbox] 保存状态失败", e);
42
+ console.error(`[tunnelbox] ${t(st.lang ?? resolveLang(), "state.errSaveState")}`, e);
43
43
  }
44
44
  }
45
45
 
@@ -70,12 +70,13 @@ export function loadState(type?: string): State {
70
70
  }
71
71
  }
72
72
 
73
- /** 把配对码写入本地文件,作为终端日志被吞时的兜底。 */
74
- export function writePairingFile(code: string, type?: string): string {
73
+ /** 把配对码写入本地文件,作为终端日志被吞时的兜底。文案走 i18n(lang 缺省按系统解析)。 */
74
+ export function writePairingFile(code: string, type?: string, lang?: Lang): string {
75
75
  const p = join(baseName(), `remote-pairing${suffix(type)}.txt`);
76
76
  try {
77
77
  mkdirSync(dirname(p), { recursive: true });
78
- writeFileSync(p, `配对码: ${code}\n`, "utf8");
78
+ const label = t(lang ?? resolveLang(), "qr.codeLabel");
79
+ writeFileSync(p, `${label}: ${code}\n`, "utf8");
79
80
  } catch {
80
81
  /* ignore */
81
82
  }
package/src/types.ts CHANGED
@@ -34,6 +34,8 @@ export interface AgentInfo {
34
34
  permission?: boolean;
35
35
  commands?: boolean;
36
36
  abort?: boolean;
37
+ /** 支持结构化提问(question.request / question.reply) */
38
+ questions?: boolean;
37
39
  };
38
40
  }
39
41