sessix-server 0.1.0 → 0.1.3

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.js CHANGED
@@ -26,6 +26,254 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  // src/index.ts
27
27
  var import_node_os5 = require("os");
28
28
 
29
+ // src/i18n/locales/zh.ts
30
+ var zh = {
31
+ startup: {
32
+ banner: " Sessix \u2014 AI \u7F16\u7A0B\u79FB\u52A8\u6307\u6325\u4E2D\u5FC3",
33
+ scanToPair: " \u626B\u7801\u914D\u5BF9\uFF1A",
34
+ waitingConnection: " \u7B49\u5F85\u624B\u673A\u8FDE\u63A5...",
35
+ wsPort: " WebSocket \u7AEF\u53E3: {{port}}",
36
+ httpPort: " HTTP \u5BA1\u6279\u7AEF\u53E3: {{port}}",
37
+ tokenDisabled: " \u8FDE\u63A5 Token: (\u5DF2\u7981\u7528\uFF0C\u5F00\u53D1\u6A21\u5F0F)",
38
+ token: " \u8FDE\u63A5 Token: {{token}}",
39
+ wsAddress: " WebSocket \u5730\u5740: ws://{{ip}}:{{port}}",
40
+ wsAddressWithToken: " WebSocket \u5730\u5740: ws://{{ip}}:{{port}}?token={{token}}",
41
+ healthCheck: " \u5065\u5EB7\u68C0\u67E5: http://localhost:{{port}}/health",
42
+ devMode: " [\u5F00\u53D1\u6A21\u5F0F] \u65E0\u9700 Token\uFF0C\u624B\u673A\u7AEF\u53EA\u9700\u8F93\u5165 IP:\u7AEF\u53E3 \u5373\u53EF\u8FDE\u63A5",
43
+ autoDiscoveryOn: " \u{1F4A1} \u81EA\u52A8\u53D1\u73B0\u5DF2\u542F\u7528\uFF0C\u540C\u7F51\u6BB5\u624B\u673A\u53EF\u81EA\u52A8\u8FDE\u63A5",
44
+ autoDiscoveryHint: " \u5982\u5728\u516C\u5171\u7F51\u7EDC\uFF0C\u5EFA\u8BAE\u5173\u95ED: SESSIX_AUTO_CONNECT=false npx sessix-server",
45
+ autoDiscoveryOff: " \u2139\uFE0F \u81EA\u52A8\u53D1\u73B0\u5DF2\u5173\u95ED\uFF0C\u624B\u673A\u9700\u624B\u52A8\u8F93\u5165\u5730\u5740\u8FDE\u63A5",
46
+ receivedSignal: "\u6536\u5230 {{signal}}\uFF0C\u6B63\u5728\u4F18\u96C5\u5173\u95ED...",
47
+ goodbye: "\u6240\u6709\u670D\u52A1\u5DF2\u5173\u95ED\uFF0C\u518D\u89C1\uFF01",
48
+ shutdownError: "\u5173\u95ED\u8FC7\u7A0B\u51FA\u9519:",
49
+ startFailed: "\u542F\u52A8\u5931\u8D25:"
50
+ },
51
+ server: {
52
+ listProjectsFailed: "\u83B7\u53D6\u9879\u76EE\u5217\u8868\u5931\u8D25: {{error}}",
53
+ listSessionsFailed: "\u83B7\u53D6\u9879\u76EE\u4F1A\u8BDD\u5931\u8D25: {{error}}",
54
+ readHistoryFailed: "\u8BFB\u53D6\u4F1A\u8BDD\u5386\u53F2\u5931\u8D25: {{error}}",
55
+ noHistory: "\uFF08\u6682\u65E0\u5BF9\u8BDD\u5386\u53F2\uFF09",
56
+ unknownEvent: "\u672A\u77E5\u7684\u4E8B\u4EF6\u7C7B\u578B: {{type}}",
57
+ clientEventError: "\u5904\u7406\u5BA2\u6237\u7AEF\u4E8B\u4EF6\u5F02\u5E38",
58
+ phoneDisconnected: "\u624B\u673A\u7AEF\u5DF2\u65AD\u5F00",
59
+ approvalRetry: "\u5BA1\u6279\u8BF7\u6C42 {{id}} 60\u79D2\u672A\u5904\u7406\uFF0C\u91CD\u8BD5\u63A8\u9001",
60
+ hookInstalled: "Sessix hook \u5DF2\u5B89\u88C5\u5230 Claude Code",
61
+ hookExists: "Sessix hook \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\u5B89\u88C5",
62
+ hookContinue: "\u7EE7\u7EED\u542F\u52A8\uFF08hook \u529F\u80FD\u53EF\u80FD\u4E0D\u53EF\u7528\uFF09",
63
+ hookInstallFailed: "Hook \u5B89\u88C5\u5931\u8D25:",
64
+ shuttingDown: "\u6B63\u5728\u4F18\u96C5\u5173\u95ED...",
65
+ shutdownComponentError: "\u5173\u95ED {{label}} \u51FA\u9519",
66
+ shutdownWithErrors: "\u5173\u95ED\u5B8C\u6210\uFF0C{{count}} \u4E2A\u9519\u8BEF",
67
+ shutdownComplete: "\u6240\u6709\u670D\u52A1\u5DF2\u5173\u95ED",
68
+ portInUse: "\u7AEF\u53E3 {{port}} \u88AB\u5360\u7528\uFF0C\u5C1D\u8BD5\u91CA\u653E\u65E7\u8FDB\u7A0B...",
69
+ restarting: "\u91CD\u65B0\u542F\u52A8 {{label}}...",
70
+ activityPushEnabled: "ActivityKit Push \u5DF2\u542F\u7528",
71
+ activityPushFailed: "ActivityKit Push \u521D\u59CB\u5316\u5931\u8D25:",
72
+ activityPushContinue: "\u7EE7\u7EED\u542F\u52A8\uFF08Live Activity \u540E\u53F0\u63A8\u9001\u4E0D\u53EF\u7528\uFF09"
73
+ },
74
+ ws: {
75
+ started: "WebSocket \u670D\u52A1\u5DF2\u542F\u52A8\uFF0C\u7AEF\u53E3 {{port}}",
76
+ serverError: "\u670D\u52A1\u8FD0\u884C\u9519\u8BEF"
77
+ },
78
+ mdns: {
79
+ alreadyRunning: "\u670D\u52A1\u5DF2\u5728\u8FD0\u884C\u4E2D",
80
+ started: "mDNS \u5E7F\u64AD\u5DF2\u542F\u52A8: _sessix._tcp \u7AEF\u53E3 {{port}}",
81
+ stopped: "\u670D\u52A1\u5E7F\u64AD\u5DF2\u505C\u6B62",
82
+ closed: "mDNS \u670D\u52A1\u5DF2\u5173\u95ED"
83
+ },
84
+ approval: {
85
+ httpStarted: "HTTP \u5BA1\u6279\u670D\u52A1\u5DF2\u542F\u52A8\uFF0C\u7AEF\u53E3 {{port}}",
86
+ serverError: "\u670D\u52A1\u8FD0\u884C\u9519\u8BEF",
87
+ yoloMode: "YOLO \u6A21\u5F0F{{status}}",
88
+ yoloEnabled: "\u5DF2\u542F\u7528",
89
+ yoloDisabled: "\u5DF2\u5173\u95ED",
90
+ requestNotFound: "\u5BA1\u6279\u8BF7\u6C42 {{id}} \u4E0D\u5B58\u5728\u6216\u5DF2\u8D85\u65F6",
91
+ requestProcessed: "\u5BA1\u6279\u8BF7\u6C42 {{id}} \u5DF2\u5904\u7406",
92
+ alwaysAllowWritten: "\u5DF2\u5C06 {{entry}} \u5199\u5165 {{label}}",
93
+ settingsWriteFailed: "\u5199\u5165 settings.json \u5931\u8D25",
94
+ autoAllowed: "\u5BA1\u6279\u8BF7\u6C42 {{id}} \u5DF2\u81EA\u52A8\u5141\u8BB8{{reason}}",
95
+ serverClosed: "\u670D\u52A1\u5668\u5DF2\u5173\u95ED",
96
+ httpClosed: "HTTP \u5BA1\u6279\u670D\u52A1\u5DF2\u5173\u95ED",
97
+ received: "\u6536\u5230\u5BA1\u6279\u8BF7\u6C42",
98
+ alwaysAllowPassThrough: "{{tool}} \u5DF2\u88AB\u59CB\u7EC8\u5141\u8BB8\uFF0C\u76F4\u63A5\u653E\u884C\uFF08\u4E0D\u901A\u77E5\uFF09",
99
+ yoloAutoAllow: "YOLO \u6A21\u5F0F\uFF0C\u81EA\u52A8\u653E\u884C",
100
+ timeout: "\u5BA1\u6279\u8BF7\u6C42 {{id}} \u5DF2\u8D85\u65F6\uFF0C\u9ED8\u8BA4\u5141\u8BB8",
101
+ processingFailed: "\u5904\u7406\u5BA1\u6279\u8BF7\u6C42\u5931\u8D25",
102
+ forbidden: "Forbidden: \u4EC5\u5141\u8BB8\u672C\u673A\u8BBF\u95EE",
103
+ bodyTooLarge: "\u8BF7\u6C42 body \u8FC7\u5927\uFF08\u8D85\u8FC7 1MB\uFF09",
104
+ invalidJson: "\u65E0\u6548\u7684 JSON body"
105
+ },
106
+ notification: {
107
+ tokenRegistered: "\u5DF2\u6CE8\u518C push token\uFF0C\u5F53\u524D\u8BBE\u5907\u6570: {{count}}",
108
+ tokenRemoved: "\u5DF2\u79FB\u9664 push token\uFF0C\u5F53\u524D\u8BBE\u5907\u6570: {{count}}",
109
+ soundPrefsUpdated: "\u5DF2\u66F4\u65B0\u97F3\u6548\u504F\u597D",
110
+ sendingPush: "\u53D1\u9001\u63A8\u9001\uFF0Ctokens:",
111
+ pushApiError: "Expo Push API \u8FD4\u56DE\u9519\u8BEF:",
112
+ pushApiFormatError: "Expo Push API \u54CD\u5E94\u683C\u5F0F\u5F02\u5E38\uFF0C\u7F3A\u5C11 data \u6570\u7EC4:",
113
+ pushFailed: "\u63A8\u9001\u5931\u8D25:",
114
+ sendFailed: "\u53D1\u9001\u63A8\u9001\u5931\u8D25:",
115
+ pendingApprovals: "{{title}} \u2014 {{count}} \u9879\u5F85\u5BA1\u6279",
116
+ taskComplete: "\u5DF2\u5B8C\u6210\uFF0C\u7B49\u5F85\u4E0B\u4E00\u6B65\u6307\u4EE4",
117
+ taskError: "\u6267\u884C\u51FA\u9519\uFF0C\u8BF7\u67E5\u770B\u8BE6\u60C5",
118
+ questionRetry: "\u63D0\u95EE {{id}} 60\u79D2\u672A\u56DE\u7B54\uFF0C\u91CD\u8BD5\u63A8\u9001"
119
+ },
120
+ tray: {
121
+ tooltip: "Sessix \u2014 AI \u7F16\u7A0B\u79FB\u52A8\u6307\u6325\u4E2D\u5FC3"
122
+ },
123
+ watcher: {
124
+ readError: "\u8BFB\u53D6\u5F02\u5E38 {{sessionId}}",
125
+ startWatching: "\u5F00\u59CB\u76D1\u542C",
126
+ stopWatching: "\u505C\u6B62\u76D1\u542C"
127
+ }
128
+ };
129
+
130
+ // src/i18n/locales/en.ts
131
+ var en = {
132
+ startup: {
133
+ banner: " Sessix \u2014 AI Coding Mobile Command Center",
134
+ scanToPair: " Scan to pair:",
135
+ waitingConnection: " Waiting for phone connection...",
136
+ wsPort: " WebSocket port: {{port}}",
137
+ httpPort: " HTTP approval port: {{port}}",
138
+ tokenDisabled: " Token: (disabled, dev mode)",
139
+ token: " Token: {{token}}",
140
+ wsAddress: " WebSocket URL: ws://{{ip}}:{{port}}",
141
+ wsAddressWithToken: " WebSocket URL: ws://{{ip}}:{{port}}?token={{token}}",
142
+ healthCheck: " Health check: http://localhost:{{port}}/health",
143
+ devMode: " [Dev mode] No token required, just enter IP:port on your phone",
144
+ autoDiscoveryOn: " Auto-discovery enabled, phones on the same network can connect automatically",
145
+ autoDiscoveryHint: " On public networks, disable with: SESSIX_AUTO_CONNECT=false npx sessix-server",
146
+ autoDiscoveryOff: " Auto-discovery disabled, phone must enter address manually",
147
+ receivedSignal: "Received {{signal}}, graceful shutdown...",
148
+ goodbye: "All services closed, goodbye!",
149
+ shutdownError: "Shutdown error:",
150
+ startFailed: "Startup failed:"
151
+ },
152
+ server: {
153
+ listProjectsFailed: "Failed to list projects: {{error}}",
154
+ listSessionsFailed: "Failed to list project sessions: {{error}}",
155
+ readHistoryFailed: "Failed to read session history: {{error}}",
156
+ noHistory: "(No conversation history)",
157
+ unknownEvent: "Unknown event type: {{type}}",
158
+ clientEventError: "Client event handling error",
159
+ phoneDisconnected: "Phone disconnected",
160
+ approvalRetry: "Approval request {{id}} not handled in 60s, retrying push",
161
+ hookInstalled: "Sessix hook installed to Claude Code",
162
+ hookExists: "Sessix hook already exists, skipping installation",
163
+ hookContinue: "Continuing startup (hook functionality may be unavailable)",
164
+ hookInstallFailed: "Hook installation failed:",
165
+ shuttingDown: "Graceful shutdown in progress...",
166
+ shutdownComponentError: "Error closing {{label}}",
167
+ shutdownWithErrors: "Shutdown complete, {{count}} error(s)",
168
+ shutdownComplete: "All services closed",
169
+ portInUse: "Port {{port}} in use, attempting to release old process...",
170
+ restarting: "Restarting {{label}}...",
171
+ activityPushEnabled: "ActivityKit Push enabled",
172
+ activityPushFailed: "ActivityKit Push init failed:",
173
+ activityPushContinue: "Continuing startup (Live Activity background push unavailable)"
174
+ },
175
+ ws: {
176
+ started: "WebSocket server started on port {{port}}",
177
+ serverError: "Server runtime error"
178
+ },
179
+ mdns: {
180
+ alreadyRunning: "Service is already running",
181
+ started: "mDNS broadcast started: _sessix._tcp port {{port}}",
182
+ stopped: "Service broadcast stopped",
183
+ closed: "mDNS service closed"
184
+ },
185
+ approval: {
186
+ httpStarted: "HTTP approval server started on port {{port}}",
187
+ serverError: "Server runtime error",
188
+ yoloMode: "YOLO mode {{status}}",
189
+ yoloEnabled: "enabled",
190
+ yoloDisabled: "disabled",
191
+ requestNotFound: "Approval request {{id}} not found or timed out",
192
+ requestProcessed: "Approval request {{id}} processed",
193
+ alwaysAllowWritten: "Written {{entry}} to {{label}}",
194
+ settingsWriteFailed: "Failed to write settings.json",
195
+ autoAllowed: "Approval request {{id}} auto-allowed{{reason}}",
196
+ serverClosed: "Server closed",
197
+ httpClosed: "HTTP approval server closed",
198
+ received: "Approval request received",
199
+ alwaysAllowPassThrough: "{{tool}} is always-allowed, passing through (no notification)",
200
+ yoloAutoAllow: "YOLO mode, auto-allowing",
201
+ timeout: "Approval request {{id}} timed out, default allowed",
202
+ processingFailed: "Approval request processing failed",
203
+ forbidden: "Forbidden: localhost access only",
204
+ bodyTooLarge: "Request body too large (>1MB)",
205
+ invalidJson: "Invalid JSON body"
206
+ },
207
+ notification: {
208
+ tokenRegistered: "Push token registered, devices: {{count}}",
209
+ tokenRemoved: "Push token removed, devices: {{count}}",
210
+ soundPrefsUpdated: "Sound preferences updated",
211
+ sendingPush: "Sending push, tokens:",
212
+ pushApiError: "Expo Push API returned error:",
213
+ pushApiFormatError: "Expo Push API response format error, missing data array:",
214
+ pushFailed: "Push failed:",
215
+ sendFailed: "Send push failed:",
216
+ pendingApprovals: "{{title}} \u2014 {{count}} pending approval(s)",
217
+ taskComplete: "Completed, awaiting next instruction",
218
+ taskError: "Execution error, check details",
219
+ questionRetry: "Question {{id}} not answered in 60s, retrying push"
220
+ },
221
+ tray: {
222
+ tooltip: "Sessix \u2014 AI Coding Mobile Command Center"
223
+ },
224
+ watcher: {
225
+ readError: "Read error {{sessionId}}",
226
+ startWatching: "Start watching",
227
+ stopWatching: "Stop watching"
228
+ }
229
+ };
230
+
231
+ // src/i18n/index.ts
232
+ var locales = { zh, en };
233
+ function detectLocale() {
234
+ const explicit = process.env.SESSIX_LANG;
235
+ if (explicit && explicit in locales) return explicit;
236
+ try {
237
+ const raw = process.env.LANG || process.env.LC_ALL || process.env.LC_MESSAGES || "";
238
+ if (raw.startsWith("zh")) return "zh";
239
+ } catch {
240
+ }
241
+ return "en";
242
+ }
243
+ var currentLocale = detectLocale();
244
+ var currentMessages = locales[currentLocale] ?? en;
245
+ function t(key, params) {
246
+ const parts = key.split(".");
247
+ let val = currentMessages;
248
+ for (const p of parts) {
249
+ if (val && typeof val === "object") {
250
+ val = val[p];
251
+ } else {
252
+ val = void 0;
253
+ break;
254
+ }
255
+ }
256
+ if (typeof val !== "string") {
257
+ let fallback = en;
258
+ for (const p of parts) {
259
+ if (fallback && typeof fallback === "object") {
260
+ fallback = fallback[p];
261
+ } else {
262
+ fallback = void 0;
263
+ break;
264
+ }
265
+ }
266
+ val = typeof fallback === "string" ? fallback : key;
267
+ }
268
+ let result = val;
269
+ if (params) {
270
+ for (const [k, v] of Object.entries(params)) {
271
+ result = result.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), String(v));
272
+ }
273
+ }
274
+ return result;
275
+ }
276
+
29
277
  // src/server.ts
30
278
  var import_uuid4 = require("uuid");
31
279
  var import_promises4 = require("fs/promises");
@@ -95,12 +343,12 @@ var ProcessProvider = class {
95
343
  session.pid = proc.pid;
96
344
  this.activeSessions.set(sessionId, { session, process: proc, model, permissionMode, effort });
97
345
  proc.on("error", (err) => {
98
- console.error(`[ProcessProvider] \u4F1A\u8BDD ${sessionId} \u8FDB\u7A0B\u9519\u8BEF:`, err.message);
346
+ console.error(`[ProcessProvider] Session ${sessionId} process error:`, err.message);
99
347
  this.activeSessions.delete(sessionId);
100
348
  const syntheticResult = {
101
349
  type: "result",
102
350
  subtype: "error",
103
- result: `\u8FDB\u7A0B\u542F\u52A8\u5931\u8D25: ${err.message}`,
351
+ result: `Process spawn failed: ${err.message}`,
104
352
  session_id: sessionId,
105
353
  duration_ms: 0,
106
354
  is_error: true,
@@ -153,15 +401,17 @@ var ProcessProvider = class {
153
401
  async sendMessage(sessionId, message, permissionMode, images) {
154
402
  const entry = this.activeSessions.get(sessionId);
155
403
  if (!entry) {
156
- throw new Error(`\u4F1A\u8BDD ${sessionId} \u4E0D\u5B58\u5728\u6216\u5DF2\u7ED3\u675F`);
404
+ throw new Error(`Session ${sessionId} not found or already ended`);
157
405
  }
158
406
  const modeChanged = permissionMode != null && permissionMode !== (entry.permissionMode ?? "default");
159
407
  if (!modeChanged && entry.process.exitCode === null && entry.process.signalCode === null && !entry.process.stdin?.destroyed) {
408
+ entry.session.status = "running";
409
+ entry.session.lastActiveAt = Date.now();
160
410
  this.writeUserMessage(entry.process, message, sessionId, images);
161
411
  return;
162
412
  }
163
413
  if (modeChanged) {
164
- console.log(`[ProcessProvider] \u4F1A\u8BDD ${sessionId}: \u6743\u9650\u6A21\u5F0F\u5207\u6362 ${entry.permissionMode ?? "default"} \u2192 ${permissionMode}\uFF0Crespawn`);
414
+ console.log(`[ProcessProvider] Session ${sessionId}: permission mode change ${entry.permissionMode ?? "default"} \u2192 ${permissionMode}, respawn`);
165
415
  if (entry.process.exitCode === null && entry.process.signalCode === null) {
166
416
  try {
167
417
  entry.process.stdin?.end();
@@ -170,7 +420,7 @@ var ProcessProvider = class {
170
420
  entry.process.kill("SIGTERM");
171
421
  }
172
422
  } else {
173
- console.log(`[ProcessProvider] \u4F1A\u8BDD ${sessionId}: \u8FDB\u7A0B\u5DF2\u9000\u51FA\uFF0Crespawn \u91CD\u65B0\u542F\u52A8`);
423
+ console.log(`[ProcessProvider] Session ${sessionId}: process exited, respawning`);
174
424
  }
175
425
  const savedPendingQuestion = entry.pendingQuestion;
176
426
  const newMode = permissionMode ?? entry.permissionMode;
@@ -183,12 +433,12 @@ var ProcessProvider = class {
183
433
  entry.permissionMode = newMode;
184
434
  entry.pendingQuestion = savedPendingQuestion;
185
435
  proc.on("error", (err) => {
186
- console.error(`[ProcessProvider] \u4F1A\u8BDD ${sessionId} sendMessage \u8FDB\u7A0B\u9519\u8BEF:`, err.message);
436
+ console.error(`[ProcessProvider] Session ${sessionId} sendMessage process error:`, err.message);
187
437
  this.activeSessions.delete(sessionId);
188
438
  const syntheticResult = {
189
439
  type: "result",
190
440
  subtype: "error",
191
- result: `\u6D88\u606F\u53D1\u9001\u5931\u8D25: ${err.message}`,
441
+ result: `Failed to send message: ${err.message}`,
192
442
  session_id: sessionId,
193
443
  duration_ms: 0,
194
444
  is_error: true,
@@ -279,16 +529,16 @@ var ProcessProvider = class {
279
529
  parent_tool_use_id: null
280
530
  });
281
531
  if (!proc.stdin || proc.stdin.destroyed) {
282
- console.error(`[ProcessProvider] stdin \u4E0D\u53EF\u7528\uFF0C\u6D88\u606F\u4E22\u5931`);
532
+ console.error(`[ProcessProvider] stdin unavailable, message lost`);
283
533
  if (sessionId) {
284
- this.emitWriteError(sessionId, "\u8FDB\u7A0B stdin \u5DF2\u5173\u95ED\uFF0C\u6D88\u606F\u672A\u9001\u8FBE");
534
+ this.emitWriteError(sessionId, "Process stdin closed, message not delivered");
285
535
  }
286
536
  return;
287
537
  }
288
538
  proc.stdin.write(payload + "\n", (err) => {
289
539
  if (err && sessionId) {
290
- console.error(`[ProcessProvider] \u4F1A\u8BDD ${sessionId} stdin \u5199\u5165\u5931\u8D25:`, err.message);
291
- this.emitWriteError(sessionId, `\u6D88\u606F\u53D1\u9001\u5931\u8D25: ${err.message}`);
540
+ console.error(`[ProcessProvider] Session ${sessionId} stdin write failed:`, err.message);
541
+ this.emitWriteError(sessionId, `Failed to send message: ${err.message}`);
292
542
  }
293
543
  });
294
544
  }
@@ -312,7 +562,7 @@ var ProcessProvider = class {
312
562
  */
313
563
  attachStdoutListener(sessionId, proc) {
314
564
  if (!proc.stdout) {
315
- console.warn(`[ProcessProvider] \u4F1A\u8BDD ${sessionId}: stdout \u4E0D\u53EF\u7528`);
565
+ console.warn(`[ProcessProvider] Session ${sessionId}: stdout unavailable`);
316
566
  return;
317
567
  }
318
568
  const rl = (0, import_readline.createInterface)({
@@ -350,7 +600,7 @@ var ProcessProvider = class {
350
600
  this.emitter.emit(this.getEventName(sessionId), event);
351
601
  } else {
352
602
  console.warn(
353
- `[ProcessProvider] \u4F1A\u8BDD ${sessionId}: \u65E0\u6CD5\u89E3\u6790\u884C: ${trimmed.substring(0, 100)}`
603
+ `[ProcessProvider] Session ${sessionId}: failed to parse line: ${trimmed.substring(0, 100)}`
354
604
  );
355
605
  }
356
606
  });
@@ -363,7 +613,7 @@ var ProcessProvider = class {
363
613
  proc.stderr.on("data", (data) => {
364
614
  const text = data.toString().trim();
365
615
  if (text) {
366
- console.error(`[ProcessProvider] \u4F1A\u8BDD ${sessionId} stderr: ${text}`);
616
+ console.error(`[ProcessProvider] Session ${sessionId} stderr: ${text}`);
367
617
  }
368
618
  });
369
619
  }
@@ -394,7 +644,7 @@ var ProcessProvider = class {
394
644
  entry.session.status = isNormal ? "idle" : "error";
395
645
  if (!isNormal) {
396
646
  console.error(
397
- `[ProcessProvider] \u4F1A\u8BDD ${sessionId}: \u8FDB\u7A0B\u5F02\u5E38\u9000\u51FA code=${code} signal=${signal}`
647
+ `[ProcessProvider] Session ${sessionId}: process exited abnormally code=${code} signal=${signal}`
398
648
  );
399
649
  }
400
650
  const syntheticResult = {
@@ -402,7 +652,7 @@ var ProcessProvider = class {
402
652
  subtype: isNormal ? "success" : "error",
403
653
  session_id: sessionId,
404
654
  is_error: !isNormal,
405
- result: isNormal ? "" : `\u8FDB\u7A0B\u9000\u51FA code=${code} signal=${signal}`,
655
+ result: isNormal ? "" : `Process exited code=${code} signal=${signal}`,
406
656
  duration_ms: 0,
407
657
  num_turns: 0
408
658
  };
@@ -450,7 +700,7 @@ var ProcessProvider = class {
450
700
  * 使用 --output-format text 做一次性调用,返回纯文本结果。
451
701
  */
452
702
  async generateSuggestion(context) {
453
- const prompt = `\u4F60\u662F\u4E00\u4E2A AI \u7F16\u7A0B\u52A9\u624B\u3002\u6839\u636E\u4EE5\u4E0B Claude Code \u5BF9\u8BDD\u4E0A\u4E0B\u6587\uFF0C\u5EFA\u8BAE\u7528\u6237\u4E0B\u4E00\u6B65\u6700\u6709\u4EF7\u503C\u7684\u4E00\u6761\u6307\u4EE4\uFF08\u76F4\u63A5\u7ED9\u51FA\u6307\u4EE4\u5185\u5BB9\uFF0C\u4E0D\u8981\u89E3\u91CA\uFF0C\u4E0D\u8981\u52A0\u5F15\u53F7\uFF09\uFF1A
703
+ const prompt = `You are an AI coding assistant. Based on the following Claude Code conversation context, suggest the most valuable next instruction for the user (give the instruction directly, no explanation, no quotes):
454
704
 
455
705
  ${context}`;
456
706
  return new Promise((resolve, reject) => {
@@ -470,7 +720,7 @@ ${context}`;
470
720
  if (code === 0) {
471
721
  resolve(output.trim());
472
722
  } else {
473
- reject(new Error(`generateSuggestion \u8FDB\u7A0B\u9000\u51FA\u7801: ${code}`));
723
+ reject(new Error(`generateSuggestion process exit code: ${code}`));
474
724
  }
475
725
  });
476
726
  proc.once("error", reject);
@@ -485,10 +735,10 @@ ${context}`;
485
735
  async answerQuestion(sessionId, toolUseId, answer) {
486
736
  const entry = this.activeSessions.get(sessionId);
487
737
  if (!entry) {
488
- throw new Error(`\u4F1A\u8BDD ${sessionId} \u4E0D\u5B58\u5728`);
738
+ throw new Error(`Session ${sessionId} not found`);
489
739
  }
490
740
  if (!entry.process.stdin || entry.process.stdin.destroyed) {
491
- throw new Error(`\u4F1A\u8BDD ${sessionId} stdin \u4E0D\u53EF\u7528`);
741
+ throw new Error(`Session ${sessionId} stdin unavailable`);
492
742
  }
493
743
  const toolResult = JSON.stringify({
494
744
  type: "tool_result",
@@ -501,7 +751,7 @@ ${context}`;
501
751
  else resolve();
502
752
  });
503
753
  });
504
- console.log(`[ProcessProvider] \u4F1A\u8BDD ${sessionId}: AskUserQuestion \u5DF2\u56DE\u7B54 (toolUseId=${toolUseId})`);
754
+ console.log(`[ProcessProvider] Session ${sessionId}: AskUserQuestion answered (toolUseId=${toolUseId})`);
505
755
  }
506
756
  /**
507
757
  * 订阅指定会话的 AskUserQuestion 事件
@@ -540,7 +790,7 @@ var SessionManager = class {
540
790
  unsubscribeMap = /* @__PURE__ */ new Map();
541
791
  /** 每个会话的事件缓冲区(用于新订阅者重放)*/
542
792
  sessionEventBuffers = /* @__PURE__ */ new Map();
543
- /** AskUserQuestion 问题映射:requestId → resolve 回调 */
793
+ /** AskUserQuestion 问题映射:requestId → resolve 回调 + 原始问题内容 */
544
794
  pendingQuestions = /* @__PURE__ */ new Map();
545
795
  /**
546
796
  * 会话状态缓存(用于追踪 status 变化,检测 oldStatus !== newStatus 时广播)
@@ -555,6 +805,10 @@ var SessionManager = class {
555
805
  runningStartedAt = /* @__PURE__ */ new Map();
556
806
  /** assistant 事件合并缓冲区(30ms 窗口内的 assistant 事件合并为一次发送) */
557
807
  pendingAssistantEvents = /* @__PURE__ */ new Map();
808
+ /** 标记哪些会话的缓冲区曾被截断(溢出过 BUFFER_MAX) */
809
+ bufferTruncated = /* @__PURE__ */ new Set();
810
+ /** sessionId → projectPath 映射,用于截断时从 JSONL 补全历史 */
811
+ sessionProjectPaths = /* @__PURE__ */ new Map();
558
812
  constructor(provider) {
559
813
  this.provider = provider;
560
814
  }
@@ -579,9 +833,10 @@ var SessionManager = class {
579
833
  images
580
834
  });
581
835
  this.lastBroadcastStatus.set(session.id, session.status);
836
+ this.sessionProjectPaths.set(session.id, projectPath);
582
837
  this.unsubscribeSession(session.id);
583
838
  this.subscribeToSession(session.id);
584
- console.log(`[SessionManager] \u4F1A\u8BDD\u5DF2\u521B\u5EFA: ${session.id} (\u9879\u76EE: ${projectPath})`);
839
+ console.log(`[SessionManager] Session created: ${session.id} (project: ${projectPath})`);
585
840
  return session;
586
841
  }
587
842
  /**
@@ -590,7 +845,7 @@ var SessionManager = class {
590
845
  async sendMessage(sessionId, message, permissionMode, images) {
591
846
  await this.provider.sendMessage(sessionId, message, permissionMode, images);
592
847
  this.updateSessionStatus(sessionId, "running");
593
- console.log(`[SessionManager] \u6D88\u606F\u5DF2\u53D1\u9001\u5230\u4F1A\u8BDD: ${sessionId}`);
848
+ console.log(`[SessionManager] Message sent to session: ${sessionId}`);
594
849
  }
595
850
  /**
596
851
  * 终止会话
@@ -600,6 +855,8 @@ var SessionManager = class {
600
855
  this.clearPendingQuestions(sessionId);
601
856
  this.lastBroadcastStatus.delete(sessionId);
602
857
  this.sessionEventBuffers.delete(sessionId);
858
+ this.bufferTruncated.delete(sessionId);
859
+ this.sessionProjectPaths.delete(sessionId);
603
860
  this.sessionStats.delete(sessionId);
604
861
  const pending = this.pendingAssistantEvents.get(sessionId);
605
862
  if (pending) {
@@ -607,7 +864,7 @@ var SessionManager = class {
607
864
  this.pendingAssistantEvents.delete(sessionId);
608
865
  }
609
866
  await this.provider.killSession(sessionId);
610
- console.log(`[SessionManager] \u4F1A\u8BDD\u5DF2\u7EC8\u6B62: ${sessionId}`);
867
+ console.log(`[SessionManager] Session killed: ${sessionId}`);
611
868
  }
612
869
  /**
613
870
  * 获取会话的缓冲事件(用于新订阅者重放)
@@ -615,19 +872,71 @@ var SessionManager = class {
615
872
  getSessionEvents(sessionId) {
616
873
  return this.sessionEventBuffers.get(sessionId) ?? [];
617
874
  }
875
+ /**
876
+ * 检查会话的缓冲区是否曾被截断(溢出过 BUFFER_MAX)
877
+ */
878
+ isBufferTruncated(sessionId) {
879
+ return this.bufferTruncated.has(sessionId);
880
+ }
881
+ /**
882
+ * 获取会话的项目路径(用于截断时从 JSONL 补全历史)
883
+ */
884
+ getSessionProjectPath(sessionId) {
885
+ return this.sessionProjectPaths.get(sessionId);
886
+ }
618
887
  /**
619
888
  * 处理 AskUserQuestion 回答(从手机端传来)
620
889
  */
621
890
  handleQuestionResponse(requestId, answer) {
622
891
  const pending = this.pendingQuestions.get(requestId);
623
892
  if (!pending) {
624
- console.warn(`[SessionManager] \u672A\u627E\u5230\u95EE\u9898\u8BF7\u6C42: ${requestId}`);
893
+ console.warn(`[SessionManager] Question request not found: ${requestId}`);
625
894
  return;
626
895
  }
627
896
  this.pendingQuestions.delete(requestId);
628
897
  this.updateSessionStatus(pending.sessionId, "running");
629
898
  pending.resolve(answer);
630
- console.log(`[SessionManager] \u95EE\u9898\u5DF2\u56DE\u7B54: ${requestId}`);
899
+ console.log(`[SessionManager] Question answered: ${requestId}`);
900
+ }
901
+ /**
902
+ * 获取指定会话的所有待回答问题(用于重连时恢复)
903
+ */
904
+ getPendingQuestionsForSession(sessionId) {
905
+ const result = [];
906
+ for (const [requestId, pending] of this.pendingQuestions) {
907
+ if (pending.sessionId === sessionId) {
908
+ result.push({
909
+ id: requestId,
910
+ sessionId,
911
+ toolUseId: pending.toolUseId,
912
+ question: pending.question,
913
+ options: pending.options,
914
+ createdAt: pending.createdAt
915
+ });
916
+ }
917
+ }
918
+ return result;
919
+ }
920
+ /** 检查某个问题是否仍在等待回答 */
921
+ isQuestionPending(requestId) {
922
+ return this.pendingQuestions.has(requestId);
923
+ }
924
+ /**
925
+ * 获取所有待回答问题(用于客户端重连时恢复状态)
926
+ */
927
+ getAllPendingQuestions() {
928
+ const result = [];
929
+ for (const [requestId, pending] of this.pendingQuestions) {
930
+ result.push({
931
+ id: requestId,
932
+ sessionId: pending.sessionId,
933
+ toolUseId: pending.toolUseId,
934
+ question: pending.question,
935
+ options: pending.options,
936
+ createdAt: pending.createdAt
937
+ });
938
+ }
939
+ return result;
631
940
  }
632
941
  /**
633
942
  * 获取所有活跃会话(含服务器端统计)
@@ -661,6 +970,8 @@ var SessionManager = class {
661
970
  }
662
971
  this.unsubscribeMap.clear();
663
972
  this.sessionEventBuffers.clear();
973
+ this.bufferTruncated.clear();
974
+ this.sessionProjectPaths.clear();
664
975
  this.sessionStats.clear();
665
976
  for (const [, pending] of this.pendingAssistantEvents) {
666
977
  clearTimeout(pending.timer);
@@ -669,7 +980,7 @@ var SessionManager = class {
669
980
  this.pendingQuestions.clear();
670
981
  this.lastBroadcastStatus.clear();
671
982
  this.eventCallbacks.length = 0;
672
- console.log("[SessionManager] \u5DF2\u9500\u6BC1");
983
+ console.log("[SessionManager] Destroyed");
673
984
  }
674
985
  // ============================================
675
986
  // 内部方法
@@ -714,6 +1025,7 @@ var SessionManager = class {
714
1025
  buffer.push(event);
715
1026
  if (buffer.length > BUFFER_MAX) {
716
1027
  buffer.splice(0, buffer.length - BUFFER_MAX);
1028
+ this.bufferTruncated.add(sessionId);
717
1029
  }
718
1030
  this.sessionEventBuffers.set(sessionId, buffer);
719
1031
  if (event.type === "assistant" && Array.isArray(event.message?.content)) {
@@ -822,7 +1134,7 @@ var SessionManager = class {
822
1134
  status: newStatus,
823
1135
  stats
824
1136
  });
825
- console.log(`[SessionManager] \u4F1A\u8BDD ${sessionId} \u72B6\u6001\u53D8\u5316: ${lastStatus ?? "(\u65E0)"} \u2192 ${newStatus}`);
1137
+ console.log(`[SessionManager] Session ${sessionId} status change: ${lastStatus ?? "(none)"} \u2192 ${newStatus}`);
826
1138
  }
827
1139
  }
828
1140
  /** 获取会话统计(含 runningStartedAt) */
@@ -851,7 +1163,7 @@ var SessionManager = class {
851
1163
  createdAt: Date.now()
852
1164
  };
853
1165
  this.emit({ type: "question_request", request: updatedRequest });
854
- console.log(`[SessionManager] \u4F1A\u8BDD ${sessionId}: AskUserQuestion \u5DF2\u66F4\u65B0 (requestId=${existingRequestId})`);
1166
+ console.log(`[SessionManager] Session ${sessionId}: AskUserQuestion updated (requestId=${existingRequestId})`);
855
1167
  return;
856
1168
  }
857
1169
  const requestId = (0, import_uuid2.v4)();
@@ -866,16 +1178,16 @@ var SessionManager = class {
866
1178
  this.updateSessionStatus(sessionId, "waiting_question");
867
1179
  this.emit({ type: "question_request", request });
868
1180
  const answerPromise = new Promise((resolve) => {
869
- this.pendingQuestions.set(requestId, { sessionId, toolUseId, resolve });
1181
+ this.pendingQuestions.set(requestId, { sessionId, toolUseId, question, options, createdAt: request.createdAt, resolve });
870
1182
  });
871
1183
  answerPromise.then(async (answer) => {
872
1184
  try {
873
1185
  await this.provider.answerQuestion(sessionId, toolUseId, answer);
874
1186
  } catch (err) {
875
- console.error(`[SessionManager] answerQuestion \u5931\u8D25 (${sessionId}):`, err);
1187
+ console.error(`[SessionManager] answerQuestion failed (${sessionId}):`, err);
876
1188
  }
877
1189
  }).catch((err) => console.error("[SessionManager] answerPromise rejected:", err));
878
- console.log(`[SessionManager] \u4F1A\u8BDD ${sessionId}: AskUserQuestion \u5DF2\u63A8\u9001 (requestId=${requestId})`);
1190
+ console.log(`[SessionManager] Session ${sessionId}: AskUserQuestion pushed (requestId=${requestId})`);
879
1191
  }
880
1192
  /**
881
1193
  * 清除指定会话的所有待回答问题
@@ -899,7 +1211,7 @@ var SessionManager = class {
899
1211
  try {
900
1212
  callback(event);
901
1213
  } catch (err) {
902
- console.error("[SessionManager] \u4E8B\u4EF6\u56DE\u8C03\u6267\u884C\u5F02\u5E38:", err);
1214
+ console.error("[SessionManager] Event callback error:", err);
903
1215
  }
904
1216
  }
905
1217
  }
@@ -945,13 +1257,13 @@ var SessionFileWatcher = class {
945
1257
  };
946
1258
  watcher.on("change", () => {
947
1259
  this.readNewLines(sessionId).catch((err) => {
948
- console.error(`[SessionFileWatcher] \u8BFB\u53D6\u5F02\u5E38 ${sessionId}:`, err);
1260
+ console.error(`[SessionFileWatcher] ${t("watcher.readError", { sessionId })}:`, err);
949
1261
  });
950
1262
  this.resetIdleTimer(sessionId);
951
1263
  });
952
1264
  this.watchers.set(sessionId, entry);
953
1265
  this.resetIdleTimer(sessionId);
954
- console.log(`[SessionFileWatcher] \u5F00\u59CB\u76D1\u542C: ${sessionId} (offset=${byteOffset})`);
1266
+ console.log(`[SessionFileWatcher] ${t("watcher.startWatching")}: ${sessionId} (offset=${byteOffset})`);
955
1267
  }
956
1268
  /** 停止监听指定会话 */
957
1269
  unwatch(sessionId) {
@@ -960,7 +1272,7 @@ var SessionFileWatcher = class {
960
1272
  if (entry.idleTimer) clearTimeout(entry.idleTimer);
961
1273
  void entry.watcher.close();
962
1274
  this.watchers.delete(sessionId);
963
- console.log(`[SessionFileWatcher] \u505C\u6B62\u76D1\u542C: ${sessionId}`);
1275
+ console.log(`[SessionFileWatcher] ${t("watcher.stopWatching")}: ${sessionId}`);
964
1276
  }
965
1277
  /** 停止所有监听(服务关闭时调用) */
966
1278
  destroy() {
@@ -976,7 +1288,7 @@ var SessionFileWatcher = class {
976
1288
  if (!entry) return;
977
1289
  if (entry.idleTimer) clearTimeout(entry.idleTimer);
978
1290
  entry.idleTimer = setTimeout(() => {
979
- console.log(`[SessionFileWatcher] \u7A7A\u95F2\u8D85\u65F6\uFF0C\u505C\u6B62\u76D1\u542C: ${sessionId}`);
1291
+ console.log(`[SessionFileWatcher] Idle timeout, stop watching: ${sessionId}`);
980
1292
  this.unwatch(sessionId);
981
1293
  }, this.IDLE_TIMEOUT_MS);
982
1294
  }
@@ -1083,6 +1395,8 @@ var WsBridge = class _WsBridge {
1083
1395
  lastPongMap = /* @__PURE__ */ new Map();
1084
1396
  /** 每个连接当前正在查看的会话 ID */
1085
1397
  viewingSessions = /* @__PURE__ */ new Map();
1398
+ /** 每个连接的消息处理队列(串行化 async handler,防止 create_session/subscribe 竞态) */
1399
+ messageQueues = /* @__PURE__ */ new Map();
1086
1400
  constructor(options) {
1087
1401
  this.token = options.token;
1088
1402
  this.wss = new import_ws.WebSocketServer({
@@ -1098,7 +1412,7 @@ var WsBridge = class _WsBridge {
1098
1412
  });
1099
1413
  this.wss.on("connection", (ws) => this.handleConnection(ws));
1100
1414
  this.startHeartbeat();
1101
- console.log(`[WsBridge] WebSocket \u670D\u52A1\u5DF2\u542F\u52A8\uFF0C\u7AEF\u53E3 ${options.port}`);
1415
+ console.log(`[WsBridge] ${t("ws.started", { port: options.port })}`);
1102
1416
  }
1103
1417
  /**
1104
1418
  * 异步工厂方法:等待端口监听成功后 resolve,端口占用等错误时 reject。
@@ -1108,7 +1422,7 @@ var WsBridge = class _WsBridge {
1108
1422
  return new Promise((resolve, reject) => {
1109
1423
  const bridge = new _WsBridge(options);
1110
1424
  bridge.wss.once("listening", () => {
1111
- bridge.wss.on("error", (err) => console.error("[WsBridge] \u670D\u52A1\u8FD0\u884C\u9519\u8BEF:", err));
1425
+ bridge.wss.on("error", (err) => console.error(`[WsBridge] ${t("ws.serverError")}:`, err));
1112
1426
  resolve(bridge);
1113
1427
  });
1114
1428
  bridge.wss.once("error", reject);
@@ -1177,7 +1491,7 @@ var WsBridge = class _WsBridge {
1177
1491
  if (err) {
1178
1492
  reject(err);
1179
1493
  } else {
1180
- console.log("[WsBridge] WebSocket \u670D\u52A1\u5DF2\u5173\u95ED");
1494
+ console.log("[WsBridge] WebSocket server closed");
1181
1495
  resolve();
1182
1496
  }
1183
1497
  });
@@ -1200,12 +1514,12 @@ var WsBridge = class _WsBridge {
1200
1514
  /** 处理新的 WebSocket 连接 */
1201
1515
  handleConnection(ws) {
1202
1516
  this.lastPongMap.set(ws, Date.now());
1203
- console.log(`[WsBridge] \u65B0\u5BA2\u6237\u7AEF\u8FDE\u63A5\uFF0C\u5F53\u524D\u8FDE\u63A5\u6570: ${this.getConnectionCount()}`);
1517
+ console.log(`[WsBridge] New client connected, connections: ${this.getConnectionCount()}`);
1204
1518
  for (const callback of this.connectionCallbacks) {
1205
1519
  try {
1206
1520
  callback(ws);
1207
1521
  } catch (err) {
1208
- console.error("[WsBridge] \u8FDE\u63A5\u56DE\u8C03\u6267\u884C\u5F02\u5E38:", err);
1522
+ console.error("[WsBridge] Connection callback error:", err);
1209
1523
  }
1210
1524
  }
1211
1525
  ws.on("pong", () => {
@@ -1216,10 +1530,10 @@ var WsBridge = class _WsBridge {
1216
1530
  const event = JSON.parse(raw.toString());
1217
1531
  this.dispatchClientEvent(event, ws);
1218
1532
  } catch (err) {
1219
- console.error("[WsBridge] \u6D88\u606F\u89E3\u6790\u5931\u8D25:", err);
1533
+ console.error("[WsBridge] Message parse error:", err);
1220
1534
  this.send(ws, {
1221
1535
  type: "error",
1222
- message: "\u6D88\u606F\u683C\u5F0F\u65E0\u6548",
1536
+ message: "Invalid message format",
1223
1537
  code: "INVALID_MESSAGE"
1224
1538
  });
1225
1539
  }
@@ -1227,30 +1541,40 @@ var WsBridge = class _WsBridge {
1227
1541
  ws.on("close", () => {
1228
1542
  this.lastPongMap.delete(ws);
1229
1543
  this.viewingSessions.delete(ws);
1544
+ this.messageQueues.delete(ws);
1230
1545
  setTimeout(() => {
1231
- console.log(`[WsBridge] \u5BA2\u6237\u7AEF\u65AD\u5F00\uFF0C\u5F53\u524D\u8FDE\u63A5\u6570: ${this.getConnectionCount()}`);
1546
+ console.log(`[WsBridge] Client disconnected, connections: ${this.getConnectionCount()}`);
1232
1547
  for (const cb of this.disconnectCallbacks) {
1233
1548
  try {
1234
1549
  cb();
1235
1550
  } catch (err) {
1236
- console.error("[WsBridge] \u65AD\u5F00\u56DE\u8C03\u6267\u884C\u5F02\u5E38:", err);
1551
+ console.error("[WsBridge] Disconnect callback error:", err);
1237
1552
  }
1238
1553
  }
1239
1554
  }, 0);
1240
1555
  });
1241
1556
  ws.on("error", (err) => {
1242
- console.error("[WsBridge] \u8FDE\u63A5\u9519\u8BEF:", err.message);
1557
+ console.error("[WsBridge] Connection error:", err.message);
1243
1558
  });
1244
1559
  }
1245
- /** 分发客户端事件到所有注册的回调 */
1560
+ /**
1561
+ * 分发客户端事件到所有注册的回调
1562
+ *
1563
+ * 使用 per-connection 队列串行化处理,确保 async 回调(如 create_session)
1564
+ * 完成后才处理下一条消息(如 subscribe),避免竞态条件。
1565
+ */
1246
1566
  dispatchClientEvent(event, ws) {
1247
- for (const callback of this.clientEventCallbacks) {
1248
- try {
1249
- callback(event, ws);
1250
- } catch (err) {
1251
- console.error("[WsBridge] \u4E8B\u4EF6\u56DE\u8C03\u6267\u884C\u5F02\u5E38:", err);
1567
+ const prev = this.messageQueues.get(ws) ?? Promise.resolve();
1568
+ const next = prev.then(async () => {
1569
+ for (const callback of this.clientEventCallbacks) {
1570
+ try {
1571
+ await callback(event, ws);
1572
+ } catch (err) {
1573
+ console.error("[WsBridge] Event callback error:", err);
1574
+ }
1252
1575
  }
1253
- }
1576
+ });
1577
+ this.messageQueues.set(ws, next);
1254
1578
  }
1255
1579
  /** 启动心跳机制 */
1256
1580
  startHeartbeat() {
@@ -1260,7 +1584,7 @@ var WsBridge = class _WsBridge {
1260
1584
  for (const ws of this.wss.clients) {
1261
1585
  const lastPong = this.lastPongMap.get(ws) ?? 0;
1262
1586
  if (now - lastPong > 45e3) {
1263
- console.log("[WsBridge] \u68C0\u6D4B\u5230\u6B7B\u8FDE\u63A5\uFF0C\u4E3B\u52A8\u65AD\u5F00");
1587
+ console.log("[WsBridge] Dead connection detected, terminating");
1264
1588
  ws.terminate();
1265
1589
  continue;
1266
1590
  }
@@ -1300,7 +1624,7 @@ var ApprovalProxy = class _ApprovalProxy {
1300
1624
  this.handleRequest(req, res);
1301
1625
  });
1302
1626
  this.server.listen(options.port, () => {
1303
- console.log(`[ApprovalProxy] HTTP \u5BA1\u6279\u670D\u52A1\u5DF2\u542F\u52A8\uFF0C\u7AEF\u53E3 ${options.port}`);
1627
+ console.log(`[ApprovalProxy] ${t("approval.httpStarted", { port: options.port })}`);
1304
1628
  });
1305
1629
  }
1306
1630
  /**
@@ -1310,7 +1634,7 @@ var ApprovalProxy = class _ApprovalProxy {
1310
1634
  return new Promise((resolve, reject) => {
1311
1635
  const proxy = new _ApprovalProxy(options);
1312
1636
  proxy.server.once("listening", () => {
1313
- proxy.server.on("error", (err) => console.error("[ApprovalProxy] \u670D\u52A1\u8FD0\u884C\u9519\u8BEF:", err));
1637
+ proxy.server.on("error", (err) => console.error(`[ApprovalProxy] ${t("approval.serverError")}:`, err));
1314
1638
  resolve(proxy);
1315
1639
  });
1316
1640
  proxy.server.once("error", reject);
@@ -1330,7 +1654,7 @@ var ApprovalProxy = class _ApprovalProxy {
1330
1654
  /** 设置会话的 YOLO 模式(服务端拦截,即使手机断连也生效) */
1331
1655
  setYoloMode(sessionId, enabled) {
1332
1656
  this.yoloSessions.set(sessionId, enabled);
1333
- console.log(`[ApprovalProxy] YOLO \u6A21\u5F0F ${enabled ? "\u5DF2\u542F\u7528" : "\u5DF2\u5173\u95ED"}: ${sessionId}`);
1657
+ console.log(`[ApprovalProxy] ${t("approval.yoloMode", { status: enabled ? t("approval.yoloEnabled") : t("approval.yoloDisabled") })}: ${sessionId}`);
1334
1658
  }
1335
1659
  /** 检查会话是否处于 YOLO 模式 */
1336
1660
  isYoloMode(sessionId) {
@@ -1345,13 +1669,13 @@ var ApprovalProxy = class _ApprovalProxy {
1345
1669
  resolveApproval(requestId, decision) {
1346
1670
  const pending = this.pendingApprovals.get(requestId);
1347
1671
  if (!pending) {
1348
- console.warn(`[ApprovalProxy] \u5BA1\u6279\u8BF7\u6C42 ${requestId} \u4E0D\u5B58\u5728\u6216\u5DF2\u8D85\u65F6`);
1672
+ console.warn(`[ApprovalProxy] ${t("approval.requestNotFound", { id: requestId })}`);
1349
1673
  return false;
1350
1674
  }
1351
1675
  clearTimeout(pending.timer);
1352
1676
  pending.resolve(decision);
1353
1677
  this.pendingApprovals.delete(requestId);
1354
- console.log(`[ApprovalProxy] \u5BA1\u6279\u8BF7\u6C42 ${requestId} \u5DF2\u5904\u7406: ${decision.decision}`);
1678
+ console.log(`[ApprovalProxy] ${t("approval.requestProcessed", { id: requestId })}: ${decision.decision}`);
1355
1679
  return true;
1356
1680
  }
1357
1681
  /** 获取当前待处理的审批数量 */
@@ -1418,11 +1742,11 @@ var ApprovalProxy = class _ApprovalProxy {
1418
1742
  allow.push(entry);
1419
1743
  import_node_fs.default.writeFileSync(targetPath, JSON.stringify(settings, null, 2), "utf-8");
1420
1744
  const label = projectPath ? `${projectPath}/.claude/settings.json` : "~/.claude/settings.json";
1421
- console.log(`[ApprovalProxy] \u5DF2\u5C06 ${entry} \u5199\u5165 ${label}`);
1745
+ console.log(`[ApprovalProxy] ${t("approval.alwaysAllowWritten", { entry, label })}`);
1422
1746
  }
1423
1747
  this.alwaysAllowedTools.add(toolName);
1424
1748
  } catch (err) {
1425
- console.error("[ApprovalProxy] \u5199\u5165 settings.json \u5931\u8D25:", err);
1749
+ console.error(`[ApprovalProxy] ${t("approval.settingsWriteFailed")}:`, err);
1426
1750
  }
1427
1751
  }
1428
1752
  /** 获取指定会话的所有 pending approval requests(用于 subscribe 重发) */
@@ -1435,6 +1759,10 @@ var ApprovalProxy = class _ApprovalProxy {
1435
1759
  }
1436
1760
  return result;
1437
1761
  }
1762
+ /** 获取所有 pending approval requests(用于客户端重连时恢复状态) */
1763
+ getAllPendingRequests() {
1764
+ return Array.from(this.pendingApprovals.values()).map(({ request }) => request);
1765
+ }
1438
1766
  /**
1439
1767
  * 批量允许所有待处理的审批请求(手机端断线时调用)
1440
1768
  */
@@ -1444,7 +1772,7 @@ var ApprovalProxy = class _ApprovalProxy {
1444
1772
  clearTimeout(pending.timer);
1445
1773
  pending.resolve({ decision: "allow" });
1446
1774
  this.pendingApprovals.delete(requestId);
1447
- console.log(`[ApprovalProxy] \u5BA1\u6279\u8BF7\u6C42 ${requestId} \u5DF2\u81EA\u52A8\u5141\u8BB8${reason ? `\uFF08${reason}\uFF09` : ""}`);
1775
+ console.log(`[ApprovalProxy] ${t("approval.autoAllowed", { id: requestId, reason: reason ? `\uFF08${reason}\uFF09` : "" })}`);
1448
1776
  }
1449
1777
  }
1450
1778
  /** 优雅关闭 HTTP 服务 */
@@ -1453,14 +1781,14 @@ var ApprovalProxy = class _ApprovalProxy {
1453
1781
  const pendingEntries = Array.from(this.pendingApprovals.entries());
1454
1782
  for (const [, pending] of pendingEntries) {
1455
1783
  clearTimeout(pending.timer);
1456
- pending.resolve({ decision: "deny", reason: "\u670D\u52A1\u5668\u5DF2\u5173\u95ED" });
1784
+ pending.resolve({ decision: "deny", reason: t("approval.serverClosed") });
1457
1785
  }
1458
1786
  this.pendingApprovals.clear();
1459
1787
  this.server.close((err) => {
1460
1788
  if (err) {
1461
1789
  reject(err);
1462
1790
  } else {
1463
- console.log("[ApprovalProxy] HTTP \u5BA1\u6279\u670D\u52A1\u5DF2\u5173\u95ED");
1791
+ console.log(`[ApprovalProxy] ${t("approval.httpClosed")}`);
1464
1792
  resolve();
1465
1793
  }
1466
1794
  });
@@ -1515,24 +1843,24 @@ var ApprovalProxy = class _ApprovalProxy {
1515
1843
  projectPath,
1516
1844
  toolName,
1517
1845
  toolInput,
1518
- description: String(payload.description ?? body.description ?? `${toolName} \u5DE5\u5177\u8C03\u7528\u8BF7\u6C42`),
1846
+ description: String(payload.description ?? body.description ?? `${toolName} tool call request`),
1519
1847
  createdAt: Date.now()
1520
1848
  };
1521
- console.log(`[ApprovalProxy] \u6536\u5230\u5BA1\u6279\u8BF7\u6C42: ${requestId} (${approvalRequest.toolName})`);
1849
+ console.log(`[ApprovalProxy] ${t("approval.received")}: ${requestId} (${approvalRequest.toolName})`);
1522
1850
  if (this.isToolAlwaysAllowed(approvalRequest.toolName, projectPath !== "unknown" ? projectPath : void 0)) {
1523
- console.log(`[ApprovalProxy] ${approvalRequest.toolName} \u5DF2\u88AB\u59CB\u7EC8\u5141\u8BB8\uFF0C\u76F4\u63A5\u653E\u884C\uFF08\u4E0D\u901A\u77E5\uFF09`);
1851
+ console.log(`[ApprovalProxy] ${t("approval.alwaysAllowPassThrough", { tool: approvalRequest.toolName })}`);
1524
1852
  this.sendJson(res, 200, { decision: "allow" });
1525
1853
  return;
1526
1854
  }
1527
1855
  if (this.yoloSessions.get(approvalRequest.sessionId)) {
1528
- console.log(`[ApprovalProxy] YOLO \u6A21\u5F0F\uFF0C\u81EA\u52A8\u653E\u884C: ${approvalRequest.toolName}`);
1856
+ console.log(`[ApprovalProxy] ${t("approval.yoloAutoAllow")}: ${approvalRequest.toolName}`);
1529
1857
  this.sendJson(res, 200, { decision: "allow" });
1530
1858
  return;
1531
1859
  }
1532
1860
  this.notifyApprovalRequest(approvalRequest);
1533
1861
  const decision = await new Promise((resolve) => {
1534
1862
  const timer = setTimeout(() => {
1535
- console.log(`[ApprovalProxy] \u5BA1\u6279\u8BF7\u6C42 ${requestId} \u5DF2\u8D85\u65F6\uFF0C\u9ED8\u8BA4\u5141\u8BB8`);
1863
+ console.log(`[ApprovalProxy] ${t("approval.timeout", { id: requestId })}`);
1536
1864
  this.pendingApprovals.delete(requestId);
1537
1865
  resolve({ decision: "allow" });
1538
1866
  }, 325e3);
@@ -1540,8 +1868,8 @@ var ApprovalProxy = class _ApprovalProxy {
1540
1868
  });
1541
1869
  this.sendJson(res, 200, decision);
1542
1870
  } catch (err) {
1543
- console.error("[ApprovalProxy] \u5904\u7406\u5BA1\u6279\u8BF7\u6C42\u5931\u8D25:", err);
1544
- this.sendJson(res, 200, { decision: "deny", reason: "\u670D\u52A1\u5668\u5904\u7406\u8BF7\u6C42\u5931\u8D25" });
1871
+ console.error(`[ApprovalProxy] ${t("approval.processingFailed")}:`, err);
1872
+ this.sendJson(res, 200, { decision: "deny", reason: "Server failed to process request" });
1545
1873
  }
1546
1874
  }
1547
1875
  /** 健康检查端点 */
@@ -1558,7 +1886,7 @@ var ApprovalProxy = class _ApprovalProxy {
1558
1886
  const remoteAddress = req.socket.remoteAddress;
1559
1887
  const isLocal = remoteAddress === "127.0.0.1" || remoteAddress === "::1" || remoteAddress === "::ffff:127.0.0.1";
1560
1888
  if (!isLocal) {
1561
- this.sendJson(res, 403, { error: "Forbidden: \u4EC5\u5141\u8BB8\u672C\u673A\u8BBF\u95EE" });
1889
+ this.sendJson(res, 403, { error: t("approval.forbidden") });
1562
1890
  return;
1563
1891
  }
1564
1892
  this.sendJson(res, 200, { token: this.token });
@@ -1569,7 +1897,7 @@ var ApprovalProxy = class _ApprovalProxy {
1569
1897
  try {
1570
1898
  callback(request);
1571
1899
  } catch (err) {
1572
- console.error("[ApprovalProxy] \u5BA1\u6279\u8BF7\u6C42\u56DE\u8C03\u6267\u884C\u5F02\u5E38:", err);
1900
+ console.error("[ApprovalProxy] Approval request callback error:", err);
1573
1901
  }
1574
1902
  }
1575
1903
  }
@@ -1586,7 +1914,7 @@ var ApprovalProxy = class _ApprovalProxy {
1586
1914
  if (totalSize > MAX_BODY_SIZE) {
1587
1915
  destroyed = true;
1588
1916
  req.destroy();
1589
- return reject(new Error("\u8BF7\u6C42 body \u8FC7\u5927\uFF08\u8D85\u8FC7 1MB\uFF09"));
1917
+ return reject(new Error(t("approval.bodyTooLarge")));
1590
1918
  }
1591
1919
  chunks.push(chunk);
1592
1920
  });
@@ -1596,7 +1924,7 @@ var ApprovalProxy = class _ApprovalProxy {
1596
1924
  const parsed = JSON.parse(raw);
1597
1925
  resolve(parsed);
1598
1926
  } catch {
1599
- reject(new Error("\u65E0\u6548\u7684 JSON body"));
1927
+ reject(new Error(t("approval.invalidJson")));
1600
1928
  }
1601
1929
  });
1602
1930
  req.on("error", (err) => {
@@ -1623,17 +1951,19 @@ var MdnsService = class {
1623
1951
  wsPort;
1624
1952
  httpPort;
1625
1953
  version;
1954
+ token;
1626
1955
  constructor(options) {
1627
1956
  this.wsPort = options.wsPort;
1628
1957
  this.httpPort = options.httpPort;
1629
1958
  this.version = options.version ?? "0.1.0";
1959
+ this.token = options.token ?? "";
1630
1960
  }
1631
1961
  /**
1632
1962
  * 启动 mDNS 广播
1633
1963
  */
1634
1964
  start() {
1635
1965
  if (this.bonjour) {
1636
- console.warn("[MdnsService] \u670D\u52A1\u5DF2\u5728\u8FD0\u884C\u4E2D");
1966
+ console.warn(`[MdnsService] ${t("mdns.alreadyRunning")}`);
1637
1967
  return;
1638
1968
  }
1639
1969
  this.bonjour = new import_bonjour_service.default();
@@ -1643,10 +1973,11 @@ var MdnsService = class {
1643
1973
  port: this.wsPort,
1644
1974
  txt: {
1645
1975
  version: this.version,
1646
- httpPort: String(this.httpPort)
1976
+ httpPort: String(this.httpPort),
1977
+ token: this.token
1647
1978
  }
1648
1979
  });
1649
- console.log(`[MdnsService] mDNS \u5E7F\u64AD\u5DF2\u542F\u52A8: _sessix._tcp \u7AEF\u53E3 ${this.wsPort}`);
1980
+ console.log(`[MdnsService] ${t("mdns.started", { port: this.wsPort })}`);
1650
1981
  }
1651
1982
  /**
1652
1983
  * 停止 mDNS 广播
@@ -1654,7 +1985,7 @@ var MdnsService = class {
1654
1985
  stop() {
1655
1986
  if (this.service) {
1656
1987
  this.service.stop?.(() => {
1657
- console.log("[MdnsService] \u670D\u52A1\u5E7F\u64AD\u5DF2\u505C\u6B62");
1988
+ console.log(`[MdnsService] ${t("mdns.stopped")}`);
1658
1989
  });
1659
1990
  this.service = null;
1660
1991
  }
@@ -1662,7 +1993,7 @@ var MdnsService = class {
1662
1993
  this.bonjour.destroy();
1663
1994
  this.bonjour = null;
1664
1995
  }
1665
- console.log("[MdnsService] mDNS \u670D\u52A1\u5DF2\u5173\u95ED");
1996
+ console.log(`[MdnsService] ${t("mdns.closed")}`);
1666
1997
  }
1667
1998
  };
1668
1999
 
@@ -1744,7 +2075,7 @@ var HookInstaller = class {
1744
2075
  await (0, import_promises2.chmod)(HOOK_SCRIPT_PATH, 493);
1745
2076
  await (0, import_promises2.chmod)(PERMISSION_ACCEPT_PATH, 493);
1746
2077
  await this.addHookToSettings();
1747
- console.log("[HookInstaller] Hook \u5B89\u88C5\u5B8C\u6210");
2078
+ console.log("[HookInstaller] Hook installation complete");
1748
2079
  }
1749
2080
  /**
1750
2081
  * 卸载 hook
@@ -1754,7 +2085,7 @@ var HookInstaller = class {
1754
2085
  */
1755
2086
  async uninstall() {
1756
2087
  await this.removeHookFromSettings();
1757
- console.log("[HookInstaller] Hook \u5DF2\u5378\u8F7D");
2088
+ console.log("[HookInstaller] Hook uninstalled");
1758
2089
  }
1759
2090
  /**
1760
2091
  * 检查 hook 是否已安装
@@ -1812,7 +2143,7 @@ var HookInstaller = class {
1812
2143
  if (changed) {
1813
2144
  await this.writeClaudeSettings(settings);
1814
2145
  } else {
1815
- console.log("[HookInstaller] Hook \u914D\u7F6E\u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7");
2146
+ console.log("[HookInstaller] Hook config already exists, skipping");
1816
2147
  }
1817
2148
  }
1818
2149
  /**
@@ -1897,6 +2228,8 @@ var NotificationService = class {
1897
2228
  yoloModeState = /* @__PURE__ */ new Map();
1898
2229
  /** 每个会话的最新 assistant 文本消息(用于通知正文预览) */
1899
2230
  latestAssistantText = /* @__PURE__ */ new Map();
2231
+ /** 获取全局待审批总数的回调(跨所有会话) */
2232
+ globalPendingCountProvider = null;
1900
2233
  /** 添加通知渠道(id 唯一,可用于后续动态开关) */
1901
2234
  addChannel(id, channel, enabled = true) {
1902
2235
  this.channelMap.set(id, { channel, enabled });
@@ -1930,6 +2263,14 @@ var NotificationService = class {
1930
2263
  removeActivityPushToken(sessionId) {
1931
2264
  this.activityPushChannel?.removeToken(sessionId);
1932
2265
  }
2266
+ /** 设置全局待审批总数提供者 */
2267
+ setGlobalPendingCountProvider(provider) {
2268
+ this.globalPendingCountProvider = provider;
2269
+ }
2270
+ /** 获取全局待审批总数 */
2271
+ getGlobalPendingCount() {
2272
+ return this.globalPendingCountProvider?.() ?? 0;
2273
+ }
1933
2274
  /** 更新会话的 YOLO 模式状态 */
1934
2275
  setYoloMode(sessionId, enabled) {
1935
2276
  this.yoloModeState.set(sessionId, enabled);
@@ -1938,7 +2279,7 @@ var NotificationService = class {
1938
2279
  notifyApproval(request, pendingCount) {
1939
2280
  if (this.yoloModeState.get(request.sessionId)) return;
1940
2281
  const sessionTitle = this.getSessionTitle(request.sessionId);
1941
- const title = pendingCount > 1 ? `${sessionTitle} \u2014 ${pendingCount} \u9879\u5F85\u5BA1\u6279` : sessionTitle;
2282
+ const title = pendingCount > 1 ? t("notification.pendingApprovals", { title: sessionTitle, count: pendingCount }) : sessionTitle;
1942
2283
  const body = pendingCount > 1 ? `\u{1F527} \u6700\u65B0: ${request.toolName}: ${request.description}` : `\u{1F527} ${request.toolName}: ${request.description}`;
1943
2284
  if (this.activityPushChannel?.hasToken(request.sessionId)) {
1944
2285
  const dangerLevel = this.getDangerLevel(request.toolName);
@@ -1948,7 +2289,7 @@ var NotificationService = class {
1948
2289
  {
1949
2290
  status: "waitingApproval",
1950
2291
  sessionTitle,
1951
- latestMessage: `${request.toolName}: ${request.description}`,
2292
+ latestMessage: "",
1952
2293
  approvalInfo: {
1953
2294
  requestId: request.id,
1954
2295
  toolName: request.toolName,
@@ -1966,8 +2307,8 @@ var NotificationService = class {
1966
2307
  this.notify({
1967
2308
  title,
1968
2309
  body,
1969
- sound: "Funk",
1970
- badge: pendingCount,
2310
+ sound: "default",
2311
+ badge: this.getGlobalPendingCount(),
1971
2312
  data: {
1972
2313
  type: "approval_request",
1973
2314
  sessionId: request.sessionId,
@@ -1975,6 +2316,37 @@ var NotificationService = class {
1975
2316
  }
1976
2317
  });
1977
2318
  }
2319
+ /** 直接触发提问通知(由 server.ts 在 question_request 事件时调用) */
2320
+ notifyQuestion(request) {
2321
+ const sessionTitle = this.getSessionTitle(request.sessionId);
2322
+ const body = `\u2753 ${request.question.slice(0, 80)}`;
2323
+ if (this.activityPushChannel?.hasToken(request.sessionId)) {
2324
+ const isYoloMode = this.getYoloMode(request.sessionId);
2325
+ this.activityPushChannel.updateActivityWithAlert(
2326
+ request.sessionId,
2327
+ {
2328
+ status: "waitingApproval",
2329
+ sessionTitle,
2330
+ latestMessage: request.question.slice(0, 80),
2331
+ isYoloMode,
2332
+ updatedAt: Date.now()
2333
+ },
2334
+ { title: sessionTitle, body }
2335
+ );
2336
+ return;
2337
+ }
2338
+ this.notify({
2339
+ title: sessionTitle,
2340
+ body,
2341
+ sound: "default",
2342
+ badge: this.getGlobalPendingCount(),
2343
+ data: {
2344
+ type: "question_request",
2345
+ sessionId: request.sessionId,
2346
+ requestId: request.id
2347
+ }
2348
+ });
2349
+ }
1978
2350
  /** 简单的工具危险等级判断 */
1979
2351
  getDangerLevel(toolName) {
1980
2352
  if (toolName === "Bash") return "danger";
@@ -2007,7 +2379,7 @@ var NotificationService = class {
2007
2379
  if (event.status === "idle") {
2008
2380
  const sessionTitle = this.getSessionTitle(event.sessionId);
2009
2381
  const latestMsg = this.latestAssistantText.get(event.sessionId);
2010
- const body = latestMsg ? `\u2705 ${latestMsg.slice(0, 80)}` : "\u5DF2\u5B8C\u6210\uFF0C\u7B49\u5F85\u4E0B\u4E00\u6B65\u6307\u4EE4";
2382
+ const body = latestMsg ? `\u2705 ${latestMsg.slice(0, 80)}` : t("notification.taskComplete");
2011
2383
  const isYoloMode = this.getYoloMode(event.sessionId);
2012
2384
  if (this.activityPushChannel?.hasToken(event.sessionId)) {
2013
2385
  this.activityPushChannel.endActivity(event.sessionId, {
@@ -2021,14 +2393,15 @@ var NotificationService = class {
2021
2393
  this.notify({
2022
2394
  title: sessionTitle,
2023
2395
  body,
2024
- sound: "Glass",
2396
+ sound: "default",
2397
+ badge: this.getGlobalPendingCount(),
2025
2398
  data: { type: "task_complete", sessionId: event.sessionId }
2026
2399
  });
2027
2400
  }
2028
2401
  } else if (event.status === "error") {
2029
2402
  const sessionTitle = this.getSessionTitle(event.sessionId);
2030
2403
  const latestMsg = this.latestAssistantText.get(event.sessionId);
2031
- const body = latestMsg ? `\u274C ${latestMsg.slice(0, 80)}` : "\u6267\u884C\u51FA\u9519\uFF0C\u8BF7\u67E5\u770B\u8BE6\u60C5";
2404
+ const body = latestMsg ? `\u274C ${latestMsg.slice(0, 80)}` : t("notification.taskError");
2032
2405
  const isYoloMode = this.getYoloMode(event.sessionId);
2033
2406
  if (this.activityPushChannel?.hasToken(event.sessionId)) {
2034
2407
  this.activityPushChannel.endActivity(event.sessionId, {
@@ -2042,7 +2415,8 @@ var NotificationService = class {
2042
2415
  this.notify({
2043
2416
  title: sessionTitle,
2044
2417
  body,
2045
- sound: "Basso",
2418
+ sound: "default",
2419
+ badge: this.getGlobalPendingCount(),
2046
2420
  data: { type: "task_error", sessionId: event.sessionId }
2047
2421
  });
2048
2422
  }
@@ -2055,7 +2429,7 @@ var NotificationService = class {
2055
2429
  for (const { channel, enabled } of this.channelMap.values()) {
2056
2430
  if (!enabled) continue;
2057
2431
  channel.send(payload).catch((err) => {
2058
- console.error("[NotificationService] \u901A\u77E5\u53D1\u9001\u5931\u8D25:", err);
2432
+ console.error("[NotificationService] Notification send failed:", err);
2059
2433
  });
2060
2434
  }
2061
2435
  }
@@ -2095,7 +2469,7 @@ var MacNotificationChannel = class {
2095
2469
  return new Promise((resolve) => {
2096
2470
  (0, import_node_child_process.execFile)("osascript", ["-e", script], (err) => {
2097
2471
  if (err) {
2098
- console.warn("[MacNotificationChannel] \u53D1\u9001\u901A\u77E5\u5931\u8D25:", err.message);
2472
+ console.warn("[MacNotificationChannel] Send notification failed:", err.message);
2099
2473
  }
2100
2474
  resolve();
2101
2475
  });
@@ -2114,19 +2488,19 @@ var ExpoNotificationChannel = class {
2114
2488
  }
2115
2489
  addToken(token) {
2116
2490
  this.tokens.add(token);
2117
- console.log(`[ExpoNotificationChannel] \u5DF2\u6CE8\u518C push token\uFF0C\u5F53\u524D\u8BBE\u5907\u6570: ${this.tokens.size}`);
2491
+ console.log(`[ExpoNotificationChannel] ${t("notification.tokenRegistered", { count: this.tokens.size })}`);
2118
2492
  }
2119
2493
  removeToken(token) {
2120
2494
  this.tokens.delete(token);
2121
2495
  this.soundPreferences.delete(token);
2122
- console.log(`[ExpoNotificationChannel] \u5DF2\u79FB\u9664 push token\uFF0C\u5F53\u524D\u8BBE\u5907\u6570: ${this.tokens.size}`);
2496
+ console.log(`[ExpoNotificationChannel] ${t("notification.tokenRemoved", { count: this.tokens.size })}`);
2123
2497
  }
2124
2498
  /** 更新某个 token 的音效偏好 */
2125
2499
  setSoundPreferences(prefs) {
2126
2500
  for (const token of this.tokens) {
2127
2501
  this.soundPreferences.set(token, prefs);
2128
2502
  }
2129
- console.log("[ExpoNotificationChannel] \u5DF2\u66F4\u65B0\u97F3\u6548\u504F\u597D");
2503
+ console.log(`[ExpoNotificationChannel] ${t("notification.soundPrefsUpdated")}`);
2130
2504
  }
2131
2505
  async send(payload) {
2132
2506
  if (this.tokens.size === 0) return;
@@ -2139,17 +2513,18 @@ var ExpoNotificationChannel = class {
2139
2513
  else if (notifType === "task_complete" && prefs.taskComplete) sound = prefs.taskComplete;
2140
2514
  else if (notifType === "task_error" && prefs.taskError) sound = prefs.taskError;
2141
2515
  }
2516
+ const pushSound = sound === "none" ? null : sound;
2142
2517
  return {
2143
2518
  to,
2144
2519
  title: payload.title,
2145
2520
  body: payload.body,
2146
2521
  badge: payload.badge,
2147
- sound: sound === "none" ? null : sound,
2522
+ sound: pushSound,
2148
2523
  data: payload.data ?? {}
2149
2524
  };
2150
2525
  });
2151
2526
  try {
2152
- console.log("[ExpoNotificationChannel] \u53D1\u9001\u63A8\u9001\uFF0Ctokens:", Array.from(this.tokens));
2527
+ console.log(`[ExpoNotificationChannel] ${t("notification.sendingPush")}`, Array.from(this.tokens));
2153
2528
  const res = await fetch(EXPO_PUSH_API, {
2154
2529
  method: "POST",
2155
2530
  headers: { "Content-Type": "application/json", Accept: "application/json" },
@@ -2157,20 +2532,20 @@ var ExpoNotificationChannel = class {
2157
2532
  });
2158
2533
  const body = await res.json();
2159
2534
  if (!res.ok) {
2160
- console.warn("[ExpoNotificationChannel] Expo Push API \u8FD4\u56DE\u9519\u8BEF:", res.status, JSON.stringify(body));
2535
+ console.warn(`[ExpoNotificationChannel] ${t("notification.pushApiError")}`, res.status, JSON.stringify(body));
2161
2536
  } else {
2162
2537
  if (!Array.isArray(body?.data)) {
2163
- console.warn("[ExpoNotificationChannel] Expo Push API \u54CD\u5E94\u683C\u5F0F\u5F02\u5E38\uFF0C\u7F3A\u5C11 data \u6570\u7EC4:", JSON.stringify(body));
2538
+ console.warn(`[ExpoNotificationChannel] ${t("notification.pushApiFormatError")}`, JSON.stringify(body));
2164
2539
  return;
2165
2540
  }
2166
2541
  for (const ticket of body.data) {
2167
2542
  if (ticket.status === "error") {
2168
- console.error(`[ExpoNotificationChannel] \u63A8\u9001\u5931\u8D25: ${ticket.message} (${ticket.details?.error ?? "unknown"})`);
2543
+ console.error(`[ExpoNotificationChannel] ${t("notification.pushFailed")} ${ticket.message} (${ticket.details?.error ?? "unknown"})`);
2169
2544
  }
2170
2545
  }
2171
2546
  }
2172
2547
  } catch (err) {
2173
- console.warn("[ExpoNotificationChannel] \u53D1\u9001\u63A8\u9001\u5931\u8D25:", err);
2548
+ console.warn(`[ExpoNotificationChannel] ${t("notification.sendFailed")}`, err);
2174
2549
  }
2175
2550
  }
2176
2551
  };
@@ -2195,7 +2570,7 @@ var ActivityPushChannel = class {
2195
2570
  this.keyId = config.keyId;
2196
2571
  this.authKey = fs2.readFileSync(config.authKeyPath, "utf-8");
2197
2572
  this.apnsHost = config.sandbox ? "api.sandbox.push.apple.com" : "api.push.apple.com";
2198
- console.log(`[ActivityPushChannel] \u5DF2\u521D\u59CB\u5316 (${config.sandbox ? "\u6C99\u7BB1" : "\u751F\u4EA7"}\u6A21\u5F0F)`);
2573
+ console.log(`[ActivityPushChannel] Initialized (${config.sandbox ? "sandbox" : "production"} mode)`);
2199
2574
  }
2200
2575
  /** 获取或新建 HTTP/2 长连接 */
2201
2576
  getHttp2Client() {
@@ -2204,7 +2579,7 @@ var ActivityPushChannel = class {
2204
2579
  }
2205
2580
  this.http2Client = http2.connect(`https://${this.apnsHost}`);
2206
2581
  this.http2Client.on("error", (err) => {
2207
- console.warn("[ActivityPushChannel] HTTP/2 \u8FDE\u63A5\u9519\u8BEF\uFF0C\u5C06\u5728\u4E0B\u6B21\u8BF7\u6C42\u65F6\u91CD\u5EFA:", err.message);
2582
+ console.warn("[ActivityPushChannel] HTTP/2 connection error, will reconnect on next request:", err.message);
2208
2583
  this.http2Client?.destroy();
2209
2584
  this.http2Client = null;
2210
2585
  });
@@ -2216,7 +2591,7 @@ var ActivityPushChannel = class {
2216
2591
  /** 注册 Activity push token */
2217
2592
  addToken(sessionId, token) {
2218
2593
  this.tokens.set(sessionId, token);
2219
- console.log(`[ActivityPushChannel] \u5DF2\u6CE8\u518C token: session=${sessionId}`);
2594
+ console.log(`[ActivityPushChannel] Token registered: session=${sessionId}`);
2220
2595
  }
2221
2596
  /** 移除 Activity push token */
2222
2597
  removeToken(sessionId) {
@@ -2236,7 +2611,7 @@ var ActivityPushChannel = class {
2236
2611
  try {
2237
2612
  await this.sendToAPNs(token, payload);
2238
2613
  } catch (err) {
2239
- console.warn(`[ActivityPushChannel] \u66F4\u65B0\u5931\u8D25 session=${sessionId}:`, err);
2614
+ console.warn(`[ActivityPushChannel] Update failed session=${sessionId}:`, err);
2240
2615
  }
2241
2616
  }
2242
2617
  /** 发送带通知的 content-state 更新(审批请求时使用) */
@@ -2255,7 +2630,7 @@ var ActivityPushChannel = class {
2255
2630
  try {
2256
2631
  await this.sendToAPNs(token, payload);
2257
2632
  } catch (err) {
2258
- console.warn(`[ActivityPushChannel] \u5E26\u63D0\u9192\u66F4\u65B0\u5931\u8D25 session=${sessionId}:`, err);
2633
+ console.warn(`[ActivityPushChannel] Alert update failed session=${sessionId}:`, err);
2259
2634
  }
2260
2635
  }
2261
2636
  /** 结束指定会话的 Live Activity */
@@ -2272,7 +2647,7 @@ var ActivityPushChannel = class {
2272
2647
  try {
2273
2648
  await this.sendToAPNs(token, payload);
2274
2649
  } catch (err) {
2275
- console.warn(`[ActivityPushChannel] \u7ED3\u675F\u5931\u8D25 session=${sessionId}:`, err);
2650
+ console.warn(`[ActivityPushChannel] End failed session=${sessionId}:`, err);
2276
2651
  }
2277
2652
  this.tokens.delete(sessionId);
2278
2653
  }
@@ -2319,7 +2694,7 @@ var ActivityPushChannel = class {
2319
2694
  this.http2Client?.destroy();
2320
2695
  this.http2Client = null;
2321
2696
  }
2322
- reject(new Error(`APNs \u8FD4\u56DE ${statusCode}: ${responseData}`));
2697
+ reject(new Error(`APNs returned ${statusCode}: ${responseData}`));
2323
2698
  }
2324
2699
  });
2325
2700
  req.on("error", (err) => {
@@ -2673,9 +3048,9 @@ async function createWithRetry(label, port, factory) {
2673
3048
  return await factory();
2674
3049
  } catch (err) {
2675
3050
  if (err?.code === "EADDRINUSE") {
2676
- console.warn(`[Server] \u7AEF\u53E3 ${port} \u88AB\u5360\u7528\uFF0C\u5C1D\u8BD5\u91CA\u653E\u65E7\u8FDB\u7A0B...`);
3051
+ console.warn(`[Server] ${t("server.portInUse", { port })}`);
2677
3052
  await killPortProcess(port);
2678
- console.log(`[Server] \u91CD\u65B0\u542F\u52A8 ${label}...`);
3053
+ console.log(`[Server] ${t("server.restarting", { label })}`);
2679
3054
  return await factory();
2680
3055
  }
2681
3056
  throw err;
@@ -2711,10 +3086,10 @@ async function start(opts = {}) {
2711
3086
  try {
2712
3087
  const activityChannel = new ActivityPushChannel(opts.activityPush);
2713
3088
  notificationService.setActivityPushChannel(activityChannel);
2714
- console.log("[Server] ActivityKit Push \u5DF2\u542F\u7528");
3089
+ console.log(`[Server] ${t("server.activityPushEnabled")}`);
2715
3090
  } catch (err) {
2716
- console.warn("[Server] ActivityKit Push \u521D\u59CB\u5316\u5931\u8D25:", err);
2717
- console.log("[Server] \u7EE7\u7EED\u542F\u52A8\uFF08Live Activity \u540E\u53F0\u63A8\u9001\u4E0D\u53EF\u7528\uFF09");
3091
+ console.warn(`[Server] ${t("server.activityPushFailed")}`, err);
3092
+ console.log(`[Server] ${t("server.activityPushContinue")}`);
2718
3093
  }
2719
3094
  }
2720
3095
  const wsBridge = await createWithRetry(
@@ -2730,6 +3105,13 @@ async function start(opts = {}) {
2730
3105
  HTTP_PORT,
2731
3106
  () => ApprovalProxy.create({ port: HTTP_PORT, token })
2732
3107
  );
3108
+ const unreadSessionIds = /* @__PURE__ */ new Set();
3109
+ notificationService.setGlobalPendingCountProvider(
3110
+ () => approvalProxy.getPendingCount() + unreadSessionIds.size
3111
+ );
3112
+ const broadcastUnreadSessions = () => {
3113
+ wsBridge.broadcast({ type: "unread_sessions", sessionIds: Array.from(unreadSessionIds) });
3114
+ };
2733
3115
  wsBridge.onConnection(async (ws) => {
2734
3116
  const result = await getProjects();
2735
3117
  if (result.ok) {
@@ -2739,6 +3121,15 @@ async function start(opts = {}) {
2739
3121
  type: "session_list",
2740
3122
  sessions: sessionManager.getActiveSessions()
2741
3123
  });
3124
+ for (const req of approvalProxy.getAllPendingRequests()) {
3125
+ wsBridge.send(ws, { type: "approval_request", request: req });
3126
+ }
3127
+ for (const req of sessionManager.getAllPendingQuestions()) {
3128
+ wsBridge.send(ws, { type: "question_request", request: req });
3129
+ }
3130
+ if (unreadSessionIds.size > 0) {
3131
+ wsBridge.send(ws, { type: "unread_sessions", sessionIds: Array.from(unreadSessionIds) });
3132
+ }
2742
3133
  });
2743
3134
  wsBridge.onClientEvent(async (event, ws) => {
2744
3135
  try {
@@ -2797,7 +3188,32 @@ async function start(opts = {}) {
2797
3188
  sessions: sessionManager.getActiveSessions()
2798
3189
  });
2799
3190
  const bufferedEvents = sessionManager.getSessionEvents(event.sessionId);
2800
- if (bufferedEvents.length > 0) {
3191
+ if (sessionManager.isBufferTruncated(event.sessionId)) {
3192
+ const projectPath = sessionManager.getSessionProjectPath(event.sessionId);
3193
+ if (projectPath) {
3194
+ const historyResult = await getSessionHistory(projectPath, event.sessionId);
3195
+ if (historyResult.ok && historyResult.value.length > 0) {
3196
+ const merged = [...historyResult.value, ...bufferedEvents];
3197
+ wsBridge.send(ws, {
3198
+ type: "session_history",
3199
+ sessionId: event.sessionId,
3200
+ events: merged
3201
+ });
3202
+ } else if (bufferedEvents.length > 0) {
3203
+ wsBridge.send(ws, {
3204
+ type: "session_history",
3205
+ sessionId: event.sessionId,
3206
+ events: bufferedEvents
3207
+ });
3208
+ }
3209
+ } else if (bufferedEvents.length > 0) {
3210
+ wsBridge.send(ws, {
3211
+ type: "session_history",
3212
+ sessionId: event.sessionId,
3213
+ events: bufferedEvents
3214
+ });
3215
+ }
3216
+ } else if (bufferedEvents.length > 0) {
2801
3217
  wsBridge.send(ws, {
2802
3218
  type: "session_history",
2803
3219
  sessionId: event.sessionId,
@@ -2807,6 +3223,9 @@ async function start(opts = {}) {
2807
3223
  for (const req of approvalProxy.getPendingRequestsForSession(event.sessionId)) {
2808
3224
  wsBridge.send(ws, { type: "approval_request", request: req });
2809
3225
  }
3226
+ for (const req of sessionManager.getPendingQuestionsForSession(event.sessionId)) {
3227
+ wsBridge.send(ws, { type: "question_request", request: req });
3228
+ }
2810
3229
  break;
2811
3230
  }
2812
3231
  case "list_projects": {
@@ -2816,7 +3235,7 @@ async function start(opts = {}) {
2816
3235
  } else {
2817
3236
  wsBridge.send(ws, {
2818
3237
  type: "error",
2819
- message: `\u83B7\u53D6\u9879\u76EE\u5217\u8868\u5931\u8D25: ${result.error.message}`,
3238
+ message: t("server.listProjectsFailed", { error: result.error.message }),
2820
3239
  code: "PROJECT_LIST_ERROR"
2821
3240
  });
2822
3241
  }
@@ -2842,7 +3261,7 @@ async function start(opts = {}) {
2842
3261
  } else {
2843
3262
  wsBridge.send(ws, {
2844
3263
  type: "error",
2845
- message: `\u83B7\u53D6\u9879\u76EE\u4F1A\u8BDD\u5931\u8D25: ${histResult.error.message}`,
3264
+ message: t("server.listSessionsFailed", { error: histResult.error.message }),
2846
3265
  code: "PROJECT_SESSIONS_ERROR"
2847
3266
  });
2848
3267
  }
@@ -2853,7 +3272,7 @@ async function start(opts = {}) {
2853
3272
  if (!historyResult.ok) {
2854
3273
  wsBridge.send(ws, {
2855
3274
  type: "error",
2856
- message: `\u8BFB\u53D6\u4F1A\u8BDD\u5386\u53F2\u5931\u8D25: ${historyResult.error.message}`,
3275
+ message: t("server.readHistoryFailed", { error: historyResult.error.message }),
2857
3276
  code: "SESSION_HISTORY_ERROR",
2858
3277
  sessionId: event.sessionId
2859
3278
  });
@@ -2878,7 +3297,7 @@ async function start(opts = {}) {
2878
3297
  }
2879
3298
  case "suggest_next_prompt": {
2880
3299
  const historyResult = await getSessionHistory(event.projectPath, event.sessionId);
2881
- let context = "\uFF08\u6682\u65E0\u5BF9\u8BDD\u5386\u53F2\uFF09";
3300
+ let context = t("server.noHistory");
2882
3301
  if (historyResult.ok && historyResult.value.length > 0) {
2883
3302
  const recent = historyResult.value.slice(-10);
2884
3303
  context = recent.map((e) => {
@@ -2887,7 +3306,8 @@ async function start(opts = {}) {
2887
3306
  return `Assistant: ${text.substring(0, 300)}`;
2888
3307
  }
2889
3308
  if (e.type === "user") {
2890
- const text = e.message.content.filter((b) => b.type === "text" && !!b.text).map((b) => b.text).join("");
3309
+ const content = e.message.content;
3310
+ const text = typeof content === "string" ? content : content.filter((b) => b.type === "text" && !!b.text).map((b) => b.text).join("");
2891
3311
  return text ? `User: ${text.substring(0, 300)}` : null;
2892
3312
  }
2893
3313
  return null;
@@ -2928,6 +3348,9 @@ async function start(opts = {}) {
2928
3348
  }
2929
3349
  case "viewing_session": {
2930
3350
  wsBridge.setViewingSession(ws, event.sessionId);
3351
+ if (unreadSessionIds.delete(event.sessionId)) {
3352
+ broadcastUnreadSessions();
3353
+ }
2931
3354
  break;
2932
3355
  }
2933
3356
  case "left_session": {
@@ -2941,14 +3364,14 @@ async function start(opts = {}) {
2941
3364
  default: {
2942
3365
  wsBridge.send(ws, {
2943
3366
  type: "error",
2944
- message: `\u672A\u77E5\u7684\u4E8B\u4EF6\u7C7B\u578B: ${event.type}`,
3367
+ message: t("server.unknownEvent", { type: event.type }),
2945
3368
  code: "UNKNOWN_EVENT"
2946
3369
  });
2947
3370
  }
2948
3371
  }
2949
3372
  } catch (err) {
2950
3373
  const message = err instanceof Error ? err.message : String(err);
2951
- console.error("[Server] \u5904\u7406\u5BA2\u6237\u7AEF\u4E8B\u4EF6\u5F02\u5E38:", message);
3374
+ console.error(`[Server] ${t("server.clientEventError")}:`, message);
2952
3375
  const errorCodeMap = {
2953
3376
  create_session: "SESSION_CREATE_ERROR",
2954
3377
  send_message: "SEND_MESSAGE_ERROR",
@@ -2964,10 +3387,16 @@ async function start(opts = {}) {
2964
3387
  });
2965
3388
  sessionManager.onEvent((event) => {
2966
3389
  wsBridge.broadcast(event);
3390
+ if (event.type === "status_change" && (event.status === "idle" || event.status === "error")) {
3391
+ if (!wsBridge.isViewingSession(event.sessionId)) {
3392
+ unreadSessionIds.add(event.sessionId);
3393
+ broadcastUnreadSessions();
3394
+ }
3395
+ }
2967
3396
  });
2968
3397
  wsBridge.onDisconnect(() => {
2969
3398
  if (wsBridge.getConnectionCount() === 0 && approvalProxy.getPendingCount() > 0) {
2970
- approvalProxy.approveAll("\u624B\u673A\u7AEF\u5DF2\u65AD\u5F00");
3399
+ approvalProxy.approveAll(t("server.phoneDisconnected"));
2971
3400
  }
2972
3401
  });
2973
3402
  approvalProxy.onApprovalRequest((request) => {
@@ -2983,52 +3412,81 @@ async function start(opts = {}) {
2983
3412
  if (!approvalProxy.isPending(request.id)) return;
2984
3413
  if (wsBridge.isViewingSession(request.sessionId)) return;
2985
3414
  if (wsBridge.getConnectionCount() > 0) return;
2986
- console.log(`[Server] \u5BA1\u6279\u8BF7\u6C42 ${request.id} 60\u79D2\u672A\u5904\u7406\uFF0C\u91CD\u8BD5\u63A8\u9001`);
3415
+ console.log(`[Server] ${t("server.approvalRetry", { id: request.id })}`);
2987
3416
  const pendingCount = approvalProxy.getPendingRequestsForSession(request.sessionId).length;
2988
3417
  notificationService.notifyApproval(request, pendingCount);
2989
3418
  }, 6e4);
2990
3419
  });
3420
+ sessionManager.onEvent((event) => {
3421
+ if (event.type !== "question_request") return;
3422
+ const { request } = event;
3423
+ setTimeout(() => {
3424
+ if (!sessionManager.isQuestionPending(request.id)) return;
3425
+ if (wsBridge.isViewingSession(request.sessionId)) return;
3426
+ if (wsBridge.getConnectionCount() > 0) return;
3427
+ notificationService.notifyQuestion(request);
3428
+ }, 5e3);
3429
+ setTimeout(() => {
3430
+ if (!sessionManager.isQuestionPending(request.id)) return;
3431
+ if (wsBridge.isViewingSession(request.sessionId)) return;
3432
+ if (wsBridge.getConnectionCount() > 0) return;
3433
+ console.log(`[Server] Question ${request.id} not answered in 60s, retrying push`);
3434
+ notificationService.notifyQuestion(request);
3435
+ }, 6e4);
3436
+ });
2991
3437
  approvalProxy.setStatusInfoProvider(() => ({
2992
3438
  connections: wsBridge.getConnectionCount(),
2993
3439
  activeSessions: sessionManager.getActiveSessions().length
2994
3440
  }));
2995
- const mdnsService = new MdnsService({ wsPort: WS_PORT, httpPort: HTTP_PORT });
2996
- mdnsService.start();
3441
+ let mdnsService = null;
3442
+ const startMdns = () => {
3443
+ if (mdnsService) return;
3444
+ mdnsService = new MdnsService({ wsPort: WS_PORT, httpPort: HTTP_PORT, token });
3445
+ mdnsService.start();
3446
+ };
3447
+ const stopMdns = () => {
3448
+ if (!mdnsService) return;
3449
+ mdnsService.stop();
3450
+ mdnsService = null;
3451
+ };
3452
+ if (opts.enableAutoConnect !== false) {
3453
+ startMdns();
3454
+ }
2997
3455
  const hookInstaller = new HookInstaller();
2998
3456
  try {
2999
3457
  const installed = await hookInstaller.isInstalled();
3000
3458
  if (!installed) {
3001
3459
  await hookInstaller.install();
3002
- console.log("[Server] Sessix hook \u5DF2\u5B89\u88C5\u5230 Claude Code");
3460
+ console.log(`[Server] ${t("server.hookInstalled")}`);
3003
3461
  } else {
3004
- console.log("[Server] Sessix hook \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\u5B89\u88C5");
3462
+ console.log(`[Server] ${t("server.hookExists")}`);
3005
3463
  }
3006
3464
  } catch (err) {
3007
- console.error("[Server] Hook \u5B89\u88C5\u5931\u8D25:", err);
3008
- console.log("[Server] \u7EE7\u7EED\u542F\u52A8\uFF08hook \u529F\u80FD\u53EF\u80FD\u4E0D\u53EF\u7528\uFF09");
3465
+ console.error(`[Server] ${t("server.hookInstallFailed")}`, err);
3466
+ console.log(`[Server] ${t("server.hookContinue")}`);
3009
3467
  }
3010
3468
  const stop = async () => {
3011
- console.log("[Server] \u6B63\u5728\u4F18\u96C5\u5173\u95ED...");
3469
+ console.log(`[Server] ${t("server.shuttingDown")}`);
3012
3470
  const errors = [];
3013
3471
  const attempt = async (fn, label) => {
3014
3472
  try {
3015
3473
  await fn();
3016
3474
  } catch (err) {
3017
- console.error(`[Server] \u5173\u95ED ${label} \u51FA\u9519:`, err);
3475
+ console.error(`[Server] ${t("server.shutdownComponentError", { label })}:`, err);
3018
3476
  errors.push(err);
3019
3477
  }
3020
3478
  };
3021
- await attempt(() => mdnsService.stop(), "mDNS");
3479
+ await attempt(() => stopMdns(), "mDNS");
3022
3480
  await attempt(() => wsBridge.close(), "WebSocket");
3023
3481
  await attempt(() => approvalProxy.close(), "ApprovalProxy");
3024
3482
  await attempt(() => sessionManager.destroy(), "SessionManager");
3025
3483
  await attempt(() => notificationService.destroy(), "NotificationService");
3026
3484
  await attempt(() => sessionFileWatcher.destroy(), "SessionFileWatcher");
3027
3485
  if (errors.length > 0) {
3028
- console.error(`[Server] \u5173\u95ED\u5B8C\u6210\uFF0C${errors.length} \u4E2A\u9519\u8BEF`);
3486
+ console.error(`[Server] ${t("server.shutdownWithErrors", { count: errors.length })}`);
3029
3487
  throw errors[0];
3030
3488
  }
3031
- console.log("[Server] \u6240\u6709\u670D\u52A1\u5DF2\u5173\u95ED");
3489
+ console.log(`[Server] ${t("server.shutdownComplete")}`);
3032
3490
  };
3033
3491
  return {
3034
3492
  token,
@@ -3039,7 +3497,14 @@ async function start(opts = {}) {
3039
3497
  stop,
3040
3498
  setMacNotification: (enabled) => notificationService.setChannelEnabled("mac", enabled),
3041
3499
  setExpoPush: (enabled) => notificationService.setChannelEnabled("expo", enabled),
3042
- onServerEvent: (cb) => sessionManager.onEvent(cb)
3500
+ onServerEvent: (cb) => sessionManager.onEvent(cb),
3501
+ setAutoConnect: (enabled) => {
3502
+ if (enabled) {
3503
+ startMdns();
3504
+ } else {
3505
+ stopMdns();
3506
+ }
3507
+ }
3043
3508
  };
3044
3509
  }
3045
3510
 
@@ -3047,47 +3512,55 @@ async function start(opts = {}) {
3047
3512
  var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
3048
3513
  async function main() {
3049
3514
  console.log("=".repeat(50));
3050
- console.log(" Sessix \u2014 AI \u7F16\u7A0B\u79FB\u52A8\u6307\u6325\u4E2D\u5FC3");
3515
+ console.log(t("startup.banner"));
3051
3516
  console.log("=".repeat(50));
3052
3517
  console.log();
3053
- const server = await start();
3518
+ const enableAutoConnect = process.env.SESSIX_AUTO_CONNECT !== "false";
3519
+ const server = await start({ enableAutoConnect });
3054
3520
  const localIp = getLocalIp();
3055
3521
  console.log("-".repeat(50));
3056
- console.log(` WebSocket \u7AEF\u53E3: ${server.wsPort}`);
3057
- console.log(` HTTP \u5BA1\u6279\u7AEF\u53E3: ${server.httpPort}`);
3522
+ console.log(t("startup.wsPort", { port: server.wsPort }));
3523
+ console.log(t("startup.httpPort", { port: server.httpPort }));
3058
3524
  if (server.token === "") {
3059
- console.log(` \u8FDE\u63A5 Token: (\u5DF2\u7981\u7528\uFF0C\u5F00\u53D1\u6A21\u5F0F)`);
3525
+ console.log(t("startup.tokenDisabled"));
3060
3526
  console.log();
3061
- console.log(` WebSocket \u5730\u5740: ws://${localIp}:${server.wsPort}`);
3527
+ console.log(t("startup.wsAddress", { ip: localIp, port: server.wsPort }));
3062
3528
  } else {
3063
- console.log(` \u8FDE\u63A5 Token: ${server.token}`);
3529
+ console.log(t("startup.token", { token: server.token }));
3064
3530
  console.log();
3065
- console.log(` WebSocket \u5730\u5740: ws://${localIp}:${server.wsPort}?token=${server.token}`);
3531
+ console.log(t("startup.wsAddressWithToken", { ip: localIp, port: server.wsPort, token: server.token }));
3066
3532
  }
3067
- console.log(` \u5065\u5EB7\u68C0\u67E5: http://localhost:${server.httpPort}/health`);
3533
+ console.log(t("startup.healthCheck", { port: server.httpPort }));
3068
3534
  console.log("-".repeat(50));
3069
3535
  if (server.token === "") {
3070
3536
  console.log();
3071
- console.log(" [\u5F00\u53D1\u6A21\u5F0F] \u65E0\u9700 Token\uFF0C\u624B\u673A\u7AEF\u53EA\u9700\u8F93\u5165 IP:\u7AEF\u53E3 \u5373\u53EF\u8FDE\u63A5");
3537
+ console.log(t("startup.devMode"));
3072
3538
  }
3073
3539
  console.log();
3074
3540
  const qrUrl = buildQrUrl(localIp, server.wsPort, server.token);
3075
- console.log(" \u626B\u7801\u914D\u5BF9\uFF1A");
3541
+ console.log(t("startup.scanToPair"));
3076
3542
  import_qrcode_terminal.default.generate(qrUrl, { small: true }, (qr) => {
3077
3543
  qr.split("\n").forEach((line) => console.log(` ${line}`));
3078
3544
  });
3079
3545
  console.log();
3080
- console.log(" \u7B49\u5F85\u624B\u673A\u8FDE\u63A5...");
3546
+ if (enableAutoConnect) {
3547
+ console.log(t("startup.autoDiscoveryOn"));
3548
+ console.log(t("startup.autoDiscoveryHint"));
3549
+ } else {
3550
+ console.log(t("startup.autoDiscoveryOff"));
3551
+ }
3552
+ console.log();
3553
+ console.log(t("startup.waitingConnection"));
3081
3554
  console.log();
3082
3555
  const shutdown = async (signal) => {
3083
3556
  console.log(`
3084
- [Main] \u6536\u5230 ${signal}\uFF0C\u6B63\u5728\u4F18\u96C5\u5173\u95ED...`);
3557
+ [Main] ${t("startup.receivedSignal", { signal })}`);
3085
3558
  try {
3086
3559
  await server.stop();
3087
- console.log("[Main] \u6240\u6709\u670D\u52A1\u5DF2\u5173\u95ED\uFF0C\u518D\u89C1\uFF01");
3560
+ console.log(`[Main] ${t("startup.goodbye")}`);
3088
3561
  process.exit(0);
3089
3562
  } catch (err) {
3090
- console.error("[Main] \u5173\u95ED\u8FC7\u7A0B\u51FA\u9519:", err);
3563
+ console.error(`[Main] ${t("startup.shutdownError")}`, err);
3091
3564
  process.exit(1);
3092
3565
  }
3093
3566
  };
@@ -3110,6 +3583,6 @@ function buildQrUrl(ip, wsPort, token) {
3110
3583
  return token ? `${base}?token=${token}` : base;
3111
3584
  }
3112
3585
  main().catch((err) => {
3113
- console.error("[Main] \u542F\u52A8\u5931\u8D25:", err);
3586
+ console.error(`[Main] ${t("startup.startFailed")}`, err);
3114
3587
  process.exit(1);
3115
3588
  });