@cjhyy/code-shell-web 0.8.0
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/README.md +42 -0
- package/dist/hooks/remoteAppSync.d.ts +21 -0
- package/dist/hooks/remoteAppSync.js +102 -0
- package/dist/hooks/useRemoteApp.d.ts +89 -0
- package/dist/hooks/useRemoteApp.js +1015 -0
- package/dist/hooks/useRemoteSocket.d.ts +25 -0
- package/dist/hooks/useRemoteSocket.js +267 -0
- package/dist/i18n/mobile.d.ts +323 -0
- package/dist/i18n/mobile.js +323 -0
- package/dist/i18n/translate.d.ts +351 -0
- package/dist/i18n/translate.js +35 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +28 -0
- package/dist/lib/deviceCredential.d.ts +14 -0
- package/dist/lib/deviceCredential.js +19 -0
- package/dist/lib/format.d.ts +26 -0
- package/dist/lib/format.js +96 -0
- package/dist/lib/messageMappers.d.ts +37 -0
- package/dist/lib/messageMappers.js +145 -0
- package/dist/lib/mobileAttachments.d.ts +23 -0
- package/dist/lib/mobileAttachments.js +121 -0
- package/dist/lib/pairing.d.ts +6 -0
- package/dist/lib/pairing.js +8 -0
- package/dist/lib/riskClassify.d.ts +5 -0
- package/dist/lib/riskClassify.js +56 -0
- package/dist/lib/storage.d.ts +10 -0
- package/dist/lib/storage.js +53 -0
- package/dist/lib/streamReducer.d.ts +90 -0
- package/dist/lib/streamReducer.js +564 -0
- package/dist/lib/uiLanguage.d.ts +15 -0
- package/dist/lib/uiLanguage.js +34 -0
- package/dist-app/assets/index-CbC5kEGZ.js +11 -0
- package/dist-app/assets/index-xwzva9ZI.css +1 -0
- package/dist-app/index.html +13 -0
- package/package.json +55 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { MobileClientEvent, MobileServerEvent } from "@cjhyy/code-shell-core";
|
|
2
|
+
export type ConnStatus = "connecting" | "authenticating" | "unpaired" | "online" | "offline";
|
|
3
|
+
export type ResyncReason = "online" | "visible" | "focus" | "pageshow";
|
|
4
|
+
/** A parsed server event (typed) OR a raw worker→renderer JSON-RPC line that the
|
|
5
|
+
* caller folds through streamReducer. We surface both: typed events drive UI
|
|
6
|
+
* state; raw lines drive the chat reducer. */
|
|
7
|
+
export interface RemoteSocket {
|
|
8
|
+
status: ConnStatus;
|
|
9
|
+
deviceName: string;
|
|
10
|
+
/** Monotonic id of the currently authenticated socket. */
|
|
11
|
+
connectionGeneration: number;
|
|
12
|
+
/** Send only on an authenticated socket, optionally requiring the captured generation. */
|
|
13
|
+
send: (event: MobileClientEvent, expectedGeneration?: number) => boolean;
|
|
14
|
+
/** Manually drop credentials and return to the pairing screen. */
|
|
15
|
+
logout: () => void;
|
|
16
|
+
}
|
|
17
|
+
export interface RemoteSocketHandlers {
|
|
18
|
+
/** A typed server event (auth/session/approval/room/…). */
|
|
19
|
+
onServerEvent?: (event: MobileServerEvent) => void;
|
|
20
|
+
/** Any raw line (used to fold agent/streamEvent through streamReducer). */
|
|
21
|
+
onRawLine?: (raw: unknown) => void;
|
|
22
|
+
/** The transport is usable again, or the page returned from background. */
|
|
23
|
+
onResyncNeeded?: (reason: ResyncReason) => void;
|
|
24
|
+
}
|
|
25
|
+
export declare function useRemoteSocket(handlers: RemoteSocketHandlers): RemoteSocket;
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import { deviceStore } from "../lib/storage.js";
|
|
3
|
+
import { generateSecret } from "../lib/deviceCredential.js";
|
|
4
|
+
import { parsePairingToken } from "../lib/pairing.js";
|
|
5
|
+
const RECONNECT_BASE_MS = 800;
|
|
6
|
+
const RECONNECT_MAX_MS = 8000;
|
|
7
|
+
export function useRemoteSocket(handlers) {
|
|
8
|
+
const [status, setStatus] = useState("connecting");
|
|
9
|
+
const [deviceName, setDeviceName] = useState("");
|
|
10
|
+
const [connectionGeneration, setConnectionGeneration] = useState(0);
|
|
11
|
+
const wsRef = useRef(null);
|
|
12
|
+
const connectionGenerationRef = useRef(0);
|
|
13
|
+
const retryRef = useRef(0);
|
|
14
|
+
const closedByUs = useRef(false);
|
|
15
|
+
const statusRef = useRef(status);
|
|
16
|
+
statusRef.current = status;
|
|
17
|
+
// Keep latest handlers without retriggering the connect effect.
|
|
18
|
+
const handlersRef = useRef(handlers);
|
|
19
|
+
handlersRef.current = handlers;
|
|
20
|
+
const send = useCallback((event, expectedGeneration) => {
|
|
21
|
+
const ws = wsRef.current;
|
|
22
|
+
if (!ws ||
|
|
23
|
+
ws.readyState !== WebSocket.OPEN ||
|
|
24
|
+
statusRef.current !== "online" ||
|
|
25
|
+
(expectedGeneration !== undefined && expectedGeneration !== connectionGenerationRef.current)) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
ws.send(JSON.stringify(event));
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}, []);
|
|
36
|
+
const logout = useCallback(() => {
|
|
37
|
+
deviceStore.clearId();
|
|
38
|
+
closedByUs.current = true;
|
|
39
|
+
wsRef.current?.close();
|
|
40
|
+
// Drop the pairing token from the URL and reload into the unpaired state.
|
|
41
|
+
window.history.replaceState(null, "", window.location.pathname);
|
|
42
|
+
statusRef.current = "unpaired";
|
|
43
|
+
setStatus("unpaired");
|
|
44
|
+
}, []);
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
closedByUs.current = false;
|
|
47
|
+
let retryTimer;
|
|
48
|
+
let openWatchdog;
|
|
49
|
+
/** Schedule a reconnect with jittered exponential backoff. Cancels any
|
|
50
|
+
* pending timer so a manual/event-driven reconnect can pre-empt it. */
|
|
51
|
+
const scheduleReconnect = () => {
|
|
52
|
+
if (closedByUs.current)
|
|
53
|
+
return;
|
|
54
|
+
if (retryTimer)
|
|
55
|
+
clearTimeout(retryTimer);
|
|
56
|
+
const base = Math.min(RECONNECT_BASE_MS * 2 ** retryRef.current, RECONNECT_MAX_MS);
|
|
57
|
+
// Full jitter: random in [base/2, base] avoids thundering-herd and makes
|
|
58
|
+
// a flaky network settle instead of hammering on a fixed cadence.
|
|
59
|
+
const delay = base / 2 + Math.random() * (base / 2);
|
|
60
|
+
retryRef.current += 1;
|
|
61
|
+
retryTimer = setTimeout(connect, delay);
|
|
62
|
+
};
|
|
63
|
+
/** Force an immediate reconnect (network back / tab visible again). */
|
|
64
|
+
const reconnectNow = () => {
|
|
65
|
+
if (closedByUs.current)
|
|
66
|
+
return;
|
|
67
|
+
const ws = wsRef.current;
|
|
68
|
+
// If we already have a live, open socket, leave it alone.
|
|
69
|
+
if (ws && ws.readyState === WebSocket.OPEN)
|
|
70
|
+
return;
|
|
71
|
+
if (retryTimer)
|
|
72
|
+
clearTimeout(retryTimer);
|
|
73
|
+
retryRef.current = 0;
|
|
74
|
+
// Drop a half-open/connecting socket before redialing.
|
|
75
|
+
if (ws) {
|
|
76
|
+
ws.onclose = null;
|
|
77
|
+
ws.onerror = null;
|
|
78
|
+
try {
|
|
79
|
+
ws.close();
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
/* ignore */
|
|
83
|
+
}
|
|
84
|
+
wsRef.current = null;
|
|
85
|
+
}
|
|
86
|
+
connect();
|
|
87
|
+
};
|
|
88
|
+
const connect = () => {
|
|
89
|
+
const wsUrl = window.location.origin.replace(/^http/, "ws") + "/ws";
|
|
90
|
+
const ws = new WebSocket(wsUrl);
|
|
91
|
+
wsRef.current = ws;
|
|
92
|
+
statusRef.current = "connecting";
|
|
93
|
+
setStatus("connecting");
|
|
94
|
+
// Watchdog: a socket stuck in CONNECTING (dead network, captive portal)
|
|
95
|
+
// never fires onclose. Force-close after 10s so backoff kicks in.
|
|
96
|
+
if (openWatchdog)
|
|
97
|
+
clearTimeout(openWatchdog);
|
|
98
|
+
openWatchdog = setTimeout(() => {
|
|
99
|
+
if (ws.readyState === WebSocket.CONNECTING) {
|
|
100
|
+
try {
|
|
101
|
+
ws.close();
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
/* ignore */
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}, 10_000);
|
|
108
|
+
ws.onopen = () => {
|
|
109
|
+
if (openWatchdog)
|
|
110
|
+
clearTimeout(openWatchdog);
|
|
111
|
+
retryRef.current = 0;
|
|
112
|
+
const pairingToken = parsePairingToken(window.location.search);
|
|
113
|
+
const secret = deviceStore.getOrCreateSecret(() => generateSecret());
|
|
114
|
+
const name = deviceStore.getOrCreateName();
|
|
115
|
+
setDeviceName(name);
|
|
116
|
+
if (pairingToken) {
|
|
117
|
+
statusRef.current = "authenticating";
|
|
118
|
+
setStatus("authenticating");
|
|
119
|
+
ws.send(JSON.stringify({
|
|
120
|
+
type: "pair.complete",
|
|
121
|
+
token: pairingToken,
|
|
122
|
+
name,
|
|
123
|
+
secretHash: secret,
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
else if (deviceStore.getId()) {
|
|
127
|
+
statusRef.current = "authenticating";
|
|
128
|
+
setStatus("authenticating");
|
|
129
|
+
ws.send(JSON.stringify({
|
|
130
|
+
type: "auth.device",
|
|
131
|
+
deviceId: deviceStore.getId(),
|
|
132
|
+
secretHash: secret,
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
statusRef.current = "unpaired";
|
|
137
|
+
setStatus("unpaired");
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
ws.onmessage = (e) => {
|
|
141
|
+
if (wsRef.current !== ws)
|
|
142
|
+
return;
|
|
143
|
+
let msg;
|
|
144
|
+
try {
|
|
145
|
+
msg = JSON.parse(String(e.data));
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const obj = msg;
|
|
151
|
+
// Auth/pair lifecycle is handled here so the hook owns the state machine.
|
|
152
|
+
if (obj.type === "pair.ok") {
|
|
153
|
+
const device = obj.device;
|
|
154
|
+
deviceStore.setId(device.id);
|
|
155
|
+
setDeviceName(device.name);
|
|
156
|
+
// Re-auth with the freshly-minted device id, then clean the URL.
|
|
157
|
+
const secret = deviceStore.getOrCreateSecret(() => generateSecret());
|
|
158
|
+
ws.send(JSON.stringify({ type: "auth.device", deviceId: device.id, secretHash: secret }));
|
|
159
|
+
window.history.replaceState(null, "", window.location.pathname);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (obj.type === "auth.ok") {
|
|
163
|
+
const device = obj.device;
|
|
164
|
+
if (device?.name)
|
|
165
|
+
setDeviceName(device.name);
|
|
166
|
+
statusRef.current = "online";
|
|
167
|
+
setStatus("online");
|
|
168
|
+
connectionGenerationRef.current += 1;
|
|
169
|
+
setConnectionGeneration(connectionGenerationRef.current);
|
|
170
|
+
handlersRef.current.onServerEvent?.(msg);
|
|
171
|
+
handlersRef.current.onResyncNeeded?.("online");
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (obj.type === "auth.failed" || obj.type === "pair.failed") {
|
|
175
|
+
if (obj.type === "auth.failed")
|
|
176
|
+
deviceStore.clearId();
|
|
177
|
+
statusRef.current = "unpaired";
|
|
178
|
+
setStatus("unpaired");
|
|
179
|
+
handlersRef.current.onServerEvent?.(msg);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
// Typed server events carry a string `type`; raw worker lines carry a
|
|
183
|
+
// `method` (agent/streamEvent, agent/approvalRequest, …). Route both.
|
|
184
|
+
if (typeof obj.type === "string") {
|
|
185
|
+
handlersRef.current.onServerEvent?.(msg);
|
|
186
|
+
}
|
|
187
|
+
if (typeof obj.method === "string") {
|
|
188
|
+
handlersRef.current.onRawLine?.(msg);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
ws.onclose = () => {
|
|
192
|
+
if (openWatchdog)
|
|
193
|
+
clearTimeout(openWatchdog);
|
|
194
|
+
if (wsRef.current !== ws)
|
|
195
|
+
return;
|
|
196
|
+
wsRef.current = null;
|
|
197
|
+
if (closedByUs.current)
|
|
198
|
+
return;
|
|
199
|
+
statusRef.current = "offline";
|
|
200
|
+
setStatus("offline");
|
|
201
|
+
scheduleReconnect();
|
|
202
|
+
};
|
|
203
|
+
ws.onerror = () => {
|
|
204
|
+
// onclose handles retry; closing here makes the failure deterministic.
|
|
205
|
+
try {
|
|
206
|
+
ws.close();
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
/* ignore */
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
};
|
|
213
|
+
// Reconnect proactively when the OS reports the network is back or the tab
|
|
214
|
+
// becomes visible again (phones suspend the socket in the background — the
|
|
215
|
+
// close event often doesn't fire until the user returns).
|
|
216
|
+
const resyncOrReconnect = (reason) => {
|
|
217
|
+
const ws = wsRef.current;
|
|
218
|
+
if (ws && ws.readyState === WebSocket.OPEN && statusRef.current === "online") {
|
|
219
|
+
handlersRef.current.onResyncNeeded?.(reason);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
reconnectNow();
|
|
223
|
+
};
|
|
224
|
+
const onOnline = () => resyncOrReconnect("online");
|
|
225
|
+
const onVisible = () => {
|
|
226
|
+
if (document.visibilityState === "visible")
|
|
227
|
+
resyncOrReconnect("visible");
|
|
228
|
+
};
|
|
229
|
+
const onFocus = () => resyncOrReconnect("focus");
|
|
230
|
+
const onPageShow = () => resyncOrReconnect("pageshow");
|
|
231
|
+
window.addEventListener("online", onOnline);
|
|
232
|
+
window.addEventListener("focus", onFocus);
|
|
233
|
+
window.addEventListener("pageshow", onPageShow);
|
|
234
|
+
document.addEventListener("visibilitychange", onVisible);
|
|
235
|
+
connect();
|
|
236
|
+
return () => {
|
|
237
|
+
closedByUs.current = true;
|
|
238
|
+
if (retryTimer)
|
|
239
|
+
clearTimeout(retryTimer);
|
|
240
|
+
if (openWatchdog)
|
|
241
|
+
clearTimeout(openWatchdog);
|
|
242
|
+
window.removeEventListener("online", onOnline);
|
|
243
|
+
window.removeEventListener("focus", onFocus);
|
|
244
|
+
window.removeEventListener("pageshow", onPageShow);
|
|
245
|
+
document.removeEventListener("visibilitychange", onVisible);
|
|
246
|
+
// Null ALL handlers before close: onmessage/onopen call setState without the
|
|
247
|
+
// closedByUs guard the close/error paths have, so an event landing in the
|
|
248
|
+
// close window would setState on the unmounted component. (Mirrors the
|
|
249
|
+
// reconnectNow teardown above, which already nulls handlers.)
|
|
250
|
+
const ws = wsRef.current;
|
|
251
|
+
if (ws) {
|
|
252
|
+
ws.onmessage = null;
|
|
253
|
+
ws.onopen = null;
|
|
254
|
+
ws.onclose = null;
|
|
255
|
+
ws.onerror = null;
|
|
256
|
+
try {
|
|
257
|
+
ws.close();
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
/* ignore */
|
|
261
|
+
}
|
|
262
|
+
wsRef.current = null;
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
}, []);
|
|
266
|
+
return { status, deviceName, connectionGeneration, send, logout };
|
|
267
|
+
}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/** Mobile remote web app strings. Owns top-level key `mobile`. */
|
|
2
|
+
export declare const mobile: {
|
|
3
|
+
readonly zh: {
|
|
4
|
+
readonly mobile: {
|
|
5
|
+
readonly app: {
|
|
6
|
+
readonly extendGoal: "延长目标";
|
|
7
|
+
readonly clearGoal: "清除目标";
|
|
8
|
+
readonly loadingCcSession: "正在加载 CC 会话…";
|
|
9
|
+
readonly loadingSession: "正在加载会话…";
|
|
10
|
+
readonly conversationFallback: "对话";
|
|
11
|
+
readonly desktopSession: "桌面会话";
|
|
12
|
+
readonly newSession: "新会话";
|
|
13
|
+
readonly openSessionsAria: "打开会话";
|
|
14
|
+
readonly leaveSessionAria: "退出会话";
|
|
15
|
+
readonly deviceFallback: "设备";
|
|
16
|
+
readonly logoutAria: "退出登录";
|
|
17
|
+
readonly permission: "权限";
|
|
18
|
+
readonly ccRoomObserving: "此会话当前为只读查看;请在桌面端明确接管后再发送消息。";
|
|
19
|
+
};
|
|
20
|
+
readonly approval: {
|
|
21
|
+
readonly risk: {
|
|
22
|
+
readonly low: "低风险";
|
|
23
|
+
readonly medium: "中风险";
|
|
24
|
+
readonly high: "高风险";
|
|
25
|
+
};
|
|
26
|
+
readonly readSourceSummary: "读取数据源 {source} / {scope} / {resource}";
|
|
27
|
+
readonly question: "问题";
|
|
28
|
+
readonly customPlaceholder: "或输入自定义回答…";
|
|
29
|
+
readonly sendCustom: "发送自定义回答";
|
|
30
|
+
readonly rememberScope: "记住范围";
|
|
31
|
+
readonly scope: {
|
|
32
|
+
readonly once: "仅本次";
|
|
33
|
+
readonly session: "本会话";
|
|
34
|
+
readonly project: "本项目";
|
|
35
|
+
};
|
|
36
|
+
readonly pathScopeLabel: "路径范围";
|
|
37
|
+
readonly pathScope: {
|
|
38
|
+
readonly file: "此文件";
|
|
39
|
+
readonly dir: "此目录";
|
|
40
|
+
readonly tool: "此工具";
|
|
41
|
+
};
|
|
42
|
+
readonly allow: "允许";
|
|
43
|
+
readonly reject: "拒绝";
|
|
44
|
+
};
|
|
45
|
+
readonly cc: {
|
|
46
|
+
readonly title: "CC 会话";
|
|
47
|
+
readonly subtitle: "外部 {label} 会话";
|
|
48
|
+
readonly selectProjectFirst: "先选择一个项目。";
|
|
49
|
+
readonly probingCli: "正在检测 {label} CLI…";
|
|
50
|
+
readonly cliMissing: "未检测到 {label} CLI(需在桌面端机器的 PATH 中)。";
|
|
51
|
+
readonly loadingSessions: "正在加载 cc 会话…";
|
|
52
|
+
readonly empty: "该项目下没有 {label} 会话。";
|
|
53
|
+
readonly messageCount: "{count} 条";
|
|
54
|
+
};
|
|
55
|
+
readonly sidePane: {
|
|
56
|
+
readonly localTab: "会话";
|
|
57
|
+
readonly ccTab: "CC 会话";
|
|
58
|
+
};
|
|
59
|
+
readonly composer: {
|
|
60
|
+
readonly disconnected: "未连接";
|
|
61
|
+
readonly placeholder: "发消息…";
|
|
62
|
+
readonly stop: "停止";
|
|
63
|
+
readonly send: "发送";
|
|
64
|
+
readonly chooseImages: "从相册选择图片";
|
|
65
|
+
readonly takePhoto: "拍照";
|
|
66
|
+
readonly removeImage: "移除图片";
|
|
67
|
+
readonly uploading: "上传中…";
|
|
68
|
+
readonly tooManyImages: "每条消息最多发送 4 张图片";
|
|
69
|
+
readonly unsupportedImage: "不支持此图片格式";
|
|
70
|
+
readonly sendFailed: "图片发送失败,请重试";
|
|
71
|
+
};
|
|
72
|
+
readonly connection: {
|
|
73
|
+
readonly connectingTitle: "正在连接…";
|
|
74
|
+
readonly connectingBody: "与 CodeShell 建立通道";
|
|
75
|
+
readonly authenticatingTitle: "认证中…";
|
|
76
|
+
readonly authenticatingBody: "正在校验设备身份";
|
|
77
|
+
readonly unpairedTitle: "未配对";
|
|
78
|
+
readonly unpairedBody: "请在 CodeShell 桌面端打开「设置 → 远程」,扫码或打开配对链接。";
|
|
79
|
+
readonly offlineTitle: "连接断开";
|
|
80
|
+
readonly offlineBody: "正在自动重连…";
|
|
81
|
+
};
|
|
82
|
+
readonly format: {
|
|
83
|
+
readonly noProject: "无项目";
|
|
84
|
+
readonly justNow: "刚刚";
|
|
85
|
+
readonly minutesAgo: "{n} 分钟前";
|
|
86
|
+
readonly hoursAgo: "{n} 小时前";
|
|
87
|
+
readonly daysAgo: "{n} 天前";
|
|
88
|
+
readonly monthsAgo: "{n} 个月前";
|
|
89
|
+
readonly yearsAgo: "{n} 年前";
|
|
90
|
+
};
|
|
91
|
+
readonly notice: {
|
|
92
|
+
readonly roomMissing: "会话不存在或未就绪";
|
|
93
|
+
readonly roomError: "会话错误";
|
|
94
|
+
readonly goalExtendFailed: "延长目标失败";
|
|
95
|
+
readonly ccRoomOpenFailed: "cc 会话无法打开";
|
|
96
|
+
readonly ccRoomObserving: "此 CC 会话当前为只读查看,消息未发送";
|
|
97
|
+
readonly attachmentSendFailed: "图片发送失败:{error}";
|
|
98
|
+
};
|
|
99
|
+
readonly permissionMode: {
|
|
100
|
+
readonly default: "默认";
|
|
101
|
+
readonly acceptEdits: "自动改";
|
|
102
|
+
readonly bypassPermissions: "全放行";
|
|
103
|
+
readonly defaultDesc: "每个工具调用都要你确认";
|
|
104
|
+
readonly acceptEditsDesc: "自动放行文件编辑,其余仍确认";
|
|
105
|
+
readonly bypassDesc: "跳过所有权限确认(危险)";
|
|
106
|
+
readonly title: "选择权限模式";
|
|
107
|
+
readonly confirmBypass: "全放行模式会跳过所有权限确认。确定吗?";
|
|
108
|
+
};
|
|
109
|
+
readonly sessionList: {
|
|
110
|
+
readonly title: "会话";
|
|
111
|
+
readonly takeoverCount: "{count} 个可接管";
|
|
112
|
+
readonly refreshAria: "刷新会话";
|
|
113
|
+
readonly new: "新建";
|
|
114
|
+
readonly createHint: "新会话默认使用当前目录";
|
|
115
|
+
readonly currentProject: "当前项目 · {name}";
|
|
116
|
+
readonly noProjectConversation: "无项目对话";
|
|
117
|
+
readonly desktopCwd: "当前桌面目录";
|
|
118
|
+
readonly unboundRepo: "不绑定 repo";
|
|
119
|
+
readonly followsDesktopCwd: "跟随桌面当前 cwd";
|
|
120
|
+
readonly otherProjects: "其他项目";
|
|
121
|
+
readonly loading: "正在加载会话…";
|
|
122
|
+
readonly empty: "还没有会话。点「新建」开一个,或在桌面端开始。";
|
|
123
|
+
readonly automation: "自动";
|
|
124
|
+
readonly noProjectPath: "无项目路径";
|
|
125
|
+
readonly unreadAria: "有新内容";
|
|
126
|
+
};
|
|
127
|
+
readonly status: {
|
|
128
|
+
readonly conn: {
|
|
129
|
+
readonly connecting: "连接中…";
|
|
130
|
+
readonly authenticating: "认证中…";
|
|
131
|
+
readonly unpaired: "未配对";
|
|
132
|
+
readonly online: "在线";
|
|
133
|
+
readonly offline: "已断开";
|
|
134
|
+
};
|
|
135
|
+
readonly run: {
|
|
136
|
+
readonly idle: "空闲";
|
|
137
|
+
readonly running: "运行中";
|
|
138
|
+
readonly waiting: "待审批";
|
|
139
|
+
readonly completed: "已完成";
|
|
140
|
+
readonly error: "出错";
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
readonly storage: {
|
|
144
|
+
readonly browserDevice: "{platform} 浏览器";
|
|
145
|
+
};
|
|
146
|
+
readonly stream: {
|
|
147
|
+
readonly showReasoning: "显示思考";
|
|
148
|
+
readonly hideReasoning: "隐藏思考";
|
|
149
|
+
readonly subagent: "子代理";
|
|
150
|
+
readonly loading: "正在加载…";
|
|
151
|
+
readonly generating: "正在生成回复";
|
|
152
|
+
readonly emptyHint: "发个任务试试,或在侧栏打开一个 Claude Code 会话。";
|
|
153
|
+
};
|
|
154
|
+
readonly tool: {
|
|
155
|
+
readonly running: "运行中";
|
|
156
|
+
readonly failed: "失败";
|
|
157
|
+
readonly completed: "完成";
|
|
158
|
+
readonly fallbackName: "操作";
|
|
159
|
+
readonly truncated: "… (已截断)";
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
readonly en: {
|
|
164
|
+
readonly mobile: {
|
|
165
|
+
readonly app: {
|
|
166
|
+
readonly extendGoal: "Extend goal";
|
|
167
|
+
readonly clearGoal: "Clear goal";
|
|
168
|
+
readonly loadingCcSession: "Loading CC session…";
|
|
169
|
+
readonly loadingSession: "Loading conversation…";
|
|
170
|
+
readonly conversationFallback: "Conversation";
|
|
171
|
+
readonly desktopSession: "Desktop session";
|
|
172
|
+
readonly newSession: "New session";
|
|
173
|
+
readonly openSessionsAria: "Open sessions";
|
|
174
|
+
readonly leaveSessionAria: "Leave session";
|
|
175
|
+
readonly deviceFallback: "Device";
|
|
176
|
+
readonly logoutAria: "Log out";
|
|
177
|
+
readonly permission: "Permission";
|
|
178
|
+
readonly ccRoomObserving: "This session is read-only. Take it over explicitly on desktop before sending messages.";
|
|
179
|
+
};
|
|
180
|
+
readonly approval: {
|
|
181
|
+
readonly risk: {
|
|
182
|
+
readonly low: "Low risk";
|
|
183
|
+
readonly medium: "Medium risk";
|
|
184
|
+
readonly high: "High risk";
|
|
185
|
+
};
|
|
186
|
+
readonly readSourceSummary: "Read data source {source} / {scope} / {resource}";
|
|
187
|
+
readonly question: "Question";
|
|
188
|
+
readonly customPlaceholder: "Or enter a custom answer…";
|
|
189
|
+
readonly sendCustom: "Send custom answer";
|
|
190
|
+
readonly rememberScope: "Remember scope";
|
|
191
|
+
readonly scope: {
|
|
192
|
+
readonly once: "Once";
|
|
193
|
+
readonly session: "This session";
|
|
194
|
+
readonly project: "This project";
|
|
195
|
+
};
|
|
196
|
+
readonly pathScopeLabel: "Path scope";
|
|
197
|
+
readonly pathScope: {
|
|
198
|
+
readonly file: "This file";
|
|
199
|
+
readonly dir: "This directory";
|
|
200
|
+
readonly tool: "This tool";
|
|
201
|
+
};
|
|
202
|
+
readonly allow: "Allow";
|
|
203
|
+
readonly reject: "Deny";
|
|
204
|
+
};
|
|
205
|
+
readonly cc: {
|
|
206
|
+
readonly title: "CC sessions";
|
|
207
|
+
readonly subtitle: "External {label} sessions";
|
|
208
|
+
readonly selectProjectFirst: "Select a project first.";
|
|
209
|
+
readonly probingCli: "Checking {label} CLI…";
|
|
210
|
+
readonly cliMissing: "{label} CLI was not found (it must be in PATH on the desktop machine).";
|
|
211
|
+
readonly loadingSessions: "Loading cc sessions…";
|
|
212
|
+
readonly empty: "No {label} sessions in this project.";
|
|
213
|
+
readonly messageCount: "{count} messages";
|
|
214
|
+
};
|
|
215
|
+
readonly sidePane: {
|
|
216
|
+
readonly localTab: "Sessions";
|
|
217
|
+
readonly ccTab: "CC";
|
|
218
|
+
};
|
|
219
|
+
readonly composer: {
|
|
220
|
+
readonly disconnected: "Disconnected";
|
|
221
|
+
readonly placeholder: "Message…";
|
|
222
|
+
readonly stop: "Stop";
|
|
223
|
+
readonly send: "Send";
|
|
224
|
+
readonly chooseImages: "Choose images";
|
|
225
|
+
readonly takePhoto: "Take photo";
|
|
226
|
+
readonly removeImage: "Remove image";
|
|
227
|
+
readonly uploading: "Uploading…";
|
|
228
|
+
readonly tooManyImages: "Up to 4 images can be sent per message";
|
|
229
|
+
readonly unsupportedImage: "This image format is not supported";
|
|
230
|
+
readonly sendFailed: "Could not send images; try again";
|
|
231
|
+
};
|
|
232
|
+
readonly connection: {
|
|
233
|
+
readonly connectingTitle: "Connecting…";
|
|
234
|
+
readonly connectingBody: "Opening a channel to CodeShell";
|
|
235
|
+
readonly authenticatingTitle: "Authenticating…";
|
|
236
|
+
readonly authenticatingBody: "Verifying this device";
|
|
237
|
+
readonly unpairedTitle: "Not paired";
|
|
238
|
+
readonly unpairedBody: "Open Settings → Remote in CodeShell desktop, then scan the QR code or open the pairing link.";
|
|
239
|
+
readonly offlineTitle: "Disconnected";
|
|
240
|
+
readonly offlineBody: "Reconnecting automatically…";
|
|
241
|
+
};
|
|
242
|
+
readonly format: {
|
|
243
|
+
readonly noProject: "No project";
|
|
244
|
+
readonly justNow: "Just now";
|
|
245
|
+
readonly minutesAgo: "{n} min ago";
|
|
246
|
+
readonly hoursAgo: "{n} hr ago";
|
|
247
|
+
readonly daysAgo: "{n} days ago";
|
|
248
|
+
readonly monthsAgo: "{n} mo ago";
|
|
249
|
+
readonly yearsAgo: "{n} yr ago";
|
|
250
|
+
};
|
|
251
|
+
readonly notice: {
|
|
252
|
+
readonly roomMissing: "Session does not exist or is not ready";
|
|
253
|
+
readonly roomError: "Session error";
|
|
254
|
+
readonly goalExtendFailed: "Failed to extend goal";
|
|
255
|
+
readonly ccRoomOpenFailed: "Could not open cc session";
|
|
256
|
+
readonly ccRoomObserving: "This CC session is read-only; the message was not sent";
|
|
257
|
+
readonly attachmentSendFailed: "Could not send images: {error}";
|
|
258
|
+
};
|
|
259
|
+
readonly permissionMode: {
|
|
260
|
+
readonly default: "Default";
|
|
261
|
+
readonly acceptEdits: "Auto-edit";
|
|
262
|
+
readonly bypassPermissions: "Allow all";
|
|
263
|
+
readonly defaultDesc: "Confirm every tool call";
|
|
264
|
+
readonly acceptEditsDesc: "Allow file edits automatically; confirm everything else";
|
|
265
|
+
readonly bypassDesc: "Skip all permission confirmations (dangerous)";
|
|
266
|
+
readonly title: "Choose permission mode";
|
|
267
|
+
readonly confirmBypass: "Allow all mode skips every permission confirmation. Continue?";
|
|
268
|
+
};
|
|
269
|
+
readonly sessionList: {
|
|
270
|
+
readonly title: "Sessions";
|
|
271
|
+
readonly takeoverCount: "{count} available";
|
|
272
|
+
readonly refreshAria: "Refresh sessions";
|
|
273
|
+
readonly new: "New";
|
|
274
|
+
readonly createHint: "New sessions default to the current directory";
|
|
275
|
+
readonly currentProject: "Current project · {name}";
|
|
276
|
+
readonly noProjectConversation: "No-project conversation";
|
|
277
|
+
readonly desktopCwd: "Current desktop directory";
|
|
278
|
+
readonly unboundRepo: "Do not bind a repo";
|
|
279
|
+
readonly followsDesktopCwd: "Follow desktop current cwd";
|
|
280
|
+
readonly otherProjects: "Other projects";
|
|
281
|
+
readonly loading: "Loading sessions…";
|
|
282
|
+
readonly empty: "No sessions yet. Tap “New” to start one, or start from desktop.";
|
|
283
|
+
readonly automation: "Automation";
|
|
284
|
+
readonly noProjectPath: "No project path";
|
|
285
|
+
readonly unreadAria: "New content";
|
|
286
|
+
};
|
|
287
|
+
readonly status: {
|
|
288
|
+
readonly conn: {
|
|
289
|
+
readonly connecting: "Connecting…";
|
|
290
|
+
readonly authenticating: "Authenticating…";
|
|
291
|
+
readonly unpaired: "Not paired";
|
|
292
|
+
readonly online: "Online";
|
|
293
|
+
readonly offline: "Disconnected";
|
|
294
|
+
};
|
|
295
|
+
readonly run: {
|
|
296
|
+
readonly idle: "Idle";
|
|
297
|
+
readonly running: "Running";
|
|
298
|
+
readonly waiting: "Awaiting approval";
|
|
299
|
+
readonly completed: "Completed";
|
|
300
|
+
readonly error: "Error";
|
|
301
|
+
};
|
|
302
|
+
};
|
|
303
|
+
readonly storage: {
|
|
304
|
+
readonly browserDevice: "{platform} browser";
|
|
305
|
+
};
|
|
306
|
+
readonly stream: {
|
|
307
|
+
readonly showReasoning: "Show reasoning";
|
|
308
|
+
readonly hideReasoning: "Hide reasoning";
|
|
309
|
+
readonly subagent: "Subagent";
|
|
310
|
+
readonly loading: "Loading…";
|
|
311
|
+
readonly generating: "Generating response";
|
|
312
|
+
readonly emptyHint: "Send a task, or open a Claude Code session from the sidebar.";
|
|
313
|
+
};
|
|
314
|
+
readonly tool: {
|
|
315
|
+
readonly running: "Running";
|
|
316
|
+
readonly failed: "Failed";
|
|
317
|
+
readonly completed: "Completed";
|
|
318
|
+
readonly fallbackName: "Operation";
|
|
319
|
+
readonly truncated: "… (truncated)";
|
|
320
|
+
};
|
|
321
|
+
};
|
|
322
|
+
};
|
|
323
|
+
};
|