@ccpocket/bridge 0.1.0 → 0.2.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 +1 -0
- package/dist/codex-process.d.ts +2 -2
- package/dist/codex-process.js +37 -30
- package/dist/codex-process.js.map +1 -1
- package/dist/parser.d.ts +6 -0
- package/dist/parser.js +10 -1
- package/dist/parser.js.map +1 -1
- package/dist/push-i18n.d.ts +7 -0
- package/dist/push-i18n.js +48 -0
- package/dist/push-i18n.js.map +1 -0
- package/dist/push-relay.d.ts +3 -1
- package/dist/push-relay.js +3 -2
- package/dist/push-relay.js.map +1 -1
- package/dist/sdk-process.d.ts +4 -4
- package/dist/sdk-process.js +31 -27
- package/dist/sdk-process.js.map +1 -1
- package/dist/startup-info.js +25 -19
- package/dist/startup-info.js.map +1 -1
- package/dist/websocket.d.ts +4 -0
- package/dist/websocket.js +109 -77
- package/dist/websocket.js.map +1 -1
- package/package.json +1 -1
package/dist/startup-info.js
CHANGED
|
@@ -42,36 +42,42 @@ export async function printStartupInfo(port, _host, apiKey) {
|
|
|
42
42
|
list.push(addr.ip);
|
|
43
43
|
grouped.set(addr.label, list);
|
|
44
44
|
}
|
|
45
|
+
const hideIp = !!process.env.BRIDGE_HIDE_IP;
|
|
45
46
|
for (const [label, ips] of grouped) {
|
|
46
47
|
for (const ip of ips) {
|
|
47
48
|
const padded = `${label}:`.padEnd(12);
|
|
48
|
-
|
|
49
|
+
const display = hideIp ? "xxx.xxx.xxx.xxx" : ip;
|
|
50
|
+
lines.push(`[bridge] ${padded} ws://${display}:${port}`);
|
|
49
51
|
}
|
|
50
52
|
}
|
|
51
53
|
// Use first LAN address, fallback to first address
|
|
52
54
|
const primaryAddr = addresses.find((a) => a.label === "LAN")?.ip ?? addresses[0].ip;
|
|
53
55
|
const deepLink = buildConnectionUrl(primaryAddr, port, apiKey);
|
|
54
56
|
lines.push("");
|
|
55
|
-
lines.push(`[bridge] Deep Link: ${deepLink}`);
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
lines.push(`[bridge] Deep Link: ${hideIp ? "(hidden)" : deepLink}`);
|
|
58
|
+
if (!hideIp) {
|
|
59
|
+
lines.push("");
|
|
60
|
+
lines.push("[bridge] Scan QR code with ccpocket app:");
|
|
61
|
+
}
|
|
58
62
|
// Print all non-QR lines
|
|
59
63
|
console.log(lines.join("\n"));
|
|
60
|
-
// Generate and print QR code
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
64
|
+
// Generate and print QR code (skip when hiding IP)
|
|
65
|
+
if (!hideIp) {
|
|
66
|
+
try {
|
|
67
|
+
const qrText = await QRCode.toString(deepLink, {
|
|
68
|
+
type: "terminal",
|
|
69
|
+
small: true,
|
|
70
|
+
});
|
|
71
|
+
// Indent QR code lines
|
|
72
|
+
const indented = qrText
|
|
73
|
+
.split("\n")
|
|
74
|
+
.map((line) => ` ${line}`)
|
|
75
|
+
.join("\n");
|
|
76
|
+
console.log(indented);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
console.log("[bridge] (QR code generation failed)");
|
|
80
|
+
}
|
|
75
81
|
}
|
|
76
82
|
console.log("[bridge] ───────────────────────────────────────────────");
|
|
77
83
|
}
|
package/dist/startup-info.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"startup-info.js","sourceRoot":"","sources":["../src/startup-info.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,MAAM,MAAM,QAAQ,CAAC;AAO5B,MAAM,UAAU,qBAAqB;IACnC,MAAM,UAAU,GAAG,EAAE,CAAC,iBAAiB,EAAE,CAAC;IAC1C,MAAM,SAAS,GAAqB,EAAE,CAAC;IAEvC,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QACxD,IAAI,CAAC,MAAM;YAAE,SAAS;QACtB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM,IAAI,KAAK,CAAC,QAAQ;gBAAE,SAAS;YAExD,IAAI,KAAK,GAAG,KAAK,CAAC;YAClB,IACE,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC;gBAChC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;gBACvB,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,EACxC,CAAC;gBACD,KAAK,GAAG,WAAW,CAAC;YACtB,CAAC;YAED,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,kBAAkB,CAChC,EAAU,EACV,IAAY,EACZ,MAAe;IAEf,MAAM,KAAK,GAAG,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC;IACnC,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;IACnD,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,sBAAsB,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;AACnD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,IAAY,EACZ,KAAa,EACb,MAAe;IAEf,MAAM,SAAS,GAAG,qBAAqB,EAAE,CAAC;IAC1C,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAEnC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,0DAA0D,CAAC,CAAC;IAEvE,iBAAiB;IACjB,MAAM,OAAO,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC5C,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QAC3C,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACnB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAChC,CAAC;IAED,KAAK,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,OAAO,EAAE,CAAC;QACnC,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;YACrB,MAAM,MAAM,GAAG,GAAG,KAAK,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACtC,KAAK,CAAC,IAAI,CAAC,cAAc,MAAM,SAAS,
|
|
1
|
+
{"version":3,"file":"startup-info.js","sourceRoot":"","sources":["../src/startup-info.ts"],"names":[],"mappings":"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"}
|
package/dist/websocket.d.ts
CHANGED
|
@@ -34,6 +34,8 @@ export declare class BridgeWebSocketServer {
|
|
|
34
34
|
private recentSessionsRequestId;
|
|
35
35
|
private debugEvents;
|
|
36
36
|
private notifiedPermissionToolUses;
|
|
37
|
+
/** FCM token → push notification locale */
|
|
38
|
+
private tokenLocales;
|
|
37
39
|
constructor(options: BridgeServerOptions);
|
|
38
40
|
close(): void;
|
|
39
41
|
/** Return session count for /health endpoint. */
|
|
@@ -50,6 +52,8 @@ export declare class BridgeWebSocketServer {
|
|
|
50
52
|
private broadcastSessionMessage;
|
|
51
53
|
/** Extract a short project label from the full projectPath (last directory name). */
|
|
52
54
|
private projectLabel;
|
|
55
|
+
/** Get unique locales from registered tokens. Falls back to ["en"] if none registered. */
|
|
56
|
+
private getRegisteredLocales;
|
|
53
57
|
private maybeSendPushNotification;
|
|
54
58
|
private broadcast;
|
|
55
59
|
private send;
|
package/dist/websocket.js
CHANGED
|
@@ -11,6 +11,7 @@ import { listWindows, takeScreenshot } from "./screenshot.js";
|
|
|
11
11
|
import { DebugTraceStore } from "./debug-trace-store.js";
|
|
12
12
|
import { RecordingStore } from "./recording-store.js";
|
|
13
13
|
import { PushRelayClient } from "./push-relay.js";
|
|
14
|
+
import { normalizePushLocale, t } from "./push-i18n.js";
|
|
14
15
|
import { fetchAllUsage } from "./usage.js";
|
|
15
16
|
export class BridgeWebSocketServer {
|
|
16
17
|
static MAX_DEBUG_EVENTS = 800;
|
|
@@ -29,6 +30,8 @@ export class BridgeWebSocketServer {
|
|
|
29
30
|
recentSessionsRequestId = 0;
|
|
30
31
|
debugEvents = new Map();
|
|
31
32
|
notifiedPermissionToolUses = new Map();
|
|
33
|
+
/** FCM token → push notification locale */
|
|
34
|
+
tokenLocales = new Map();
|
|
32
35
|
constructor(options) {
|
|
33
36
|
const { server, apiKey, imageStore, galleryStore, projectHistory, debugTraceStore, recordingStore, firebaseAuth, promptHistoryBackup } = options;
|
|
34
37
|
this.apiKey = apiKey ?? null;
|
|
@@ -204,35 +207,43 @@ export class BridgeWebSocketServer {
|
|
|
204
207
|
}
|
|
205
208
|
// Acknowledge receipt immediately so the client can mark the message as sent
|
|
206
209
|
this.send(ws, { type: "input_ack", sessionId: session.id });
|
|
210
|
+
// Normalize images: support new `images` array and legacy single-image fields
|
|
211
|
+
let images = [];
|
|
212
|
+
if (msg.images && msg.images.length > 0) {
|
|
213
|
+
images = msg.images;
|
|
214
|
+
}
|
|
215
|
+
else if (msg.imageBase64 && msg.mimeType) {
|
|
216
|
+
// Legacy single-image fallback
|
|
217
|
+
images = [{ base64: msg.imageBase64, mimeType: msg.mimeType }];
|
|
218
|
+
}
|
|
207
219
|
// Add user_input to in-memory history.
|
|
208
220
|
// The SDK stream does NOT emit user messages, so session.history would
|
|
209
221
|
// otherwise lack them. This ensures get_history responses include user
|
|
210
222
|
// messages and replaceEntries on the client side preserves them.
|
|
211
223
|
// We do NOT broadcast this back — Flutter already shows it via sendMessage().
|
|
212
|
-
const hasImage = !!(msg.imageBase64 || msg.imageId);
|
|
213
224
|
session.history.push({
|
|
214
225
|
type: "user_input",
|
|
215
226
|
text,
|
|
216
|
-
...(
|
|
227
|
+
...(images.length > 0 ? { imageCount: images.length } : {}),
|
|
217
228
|
});
|
|
218
|
-
//
|
|
229
|
+
// Persist images to Gallery Store asynchronously (fire-and-forget)
|
|
230
|
+
if (images.length > 0 && this.galleryStore && session.projectPath) {
|
|
231
|
+
for (const img of images) {
|
|
232
|
+
this.galleryStore.addImageFromBase64(img.base64, img.mimeType, session.projectPath, msg.sessionId).catch((err) => {
|
|
233
|
+
console.warn(`[ws] Failed to persist image to gallery: ${err}`);
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// Codex input path
|
|
219
238
|
if (session.provider === "codex") {
|
|
220
239
|
const codexProc = session.process;
|
|
221
|
-
if (
|
|
222
|
-
codexProc.
|
|
223
|
-
base64: msg.imageBase64,
|
|
224
|
-
mimeType: msg.mimeType,
|
|
225
|
-
});
|
|
226
|
-
if (this.galleryStore && session.projectPath) {
|
|
227
|
-
this.galleryStore.addImageFromBase64(msg.imageBase64, msg.mimeType, session.projectPath, msg.sessionId).catch((err) => {
|
|
228
|
-
console.warn(`[ws] Failed to persist image to gallery: ${err}`);
|
|
229
|
-
});
|
|
230
|
-
}
|
|
240
|
+
if (images.length > 0) {
|
|
241
|
+
codexProc.sendInputWithImages(text, images);
|
|
231
242
|
}
|
|
232
243
|
else if (msg.imageId && this.galleryStore) {
|
|
233
244
|
this.galleryStore.getImageAsBase64(msg.imageId).then((imageData) => {
|
|
234
245
|
if (imageData) {
|
|
235
|
-
codexProc.
|
|
246
|
+
codexProc.sendInputWithImages(text, [imageData]);
|
|
236
247
|
}
|
|
237
248
|
else {
|
|
238
249
|
console.warn(`[ws] Image not found: ${msg.imageId}`);
|
|
@@ -248,26 +259,17 @@ export class BridgeWebSocketServer {
|
|
|
248
259
|
}
|
|
249
260
|
break;
|
|
250
261
|
}
|
|
251
|
-
//
|
|
262
|
+
// Claude Code input path
|
|
252
263
|
const claudeProc = session.process;
|
|
253
|
-
if (
|
|
254
|
-
console.log(`[ws] Sending message with inline Base64 image
|
|
255
|
-
claudeProc.
|
|
256
|
-
base64: msg.imageBase64,
|
|
257
|
-
mimeType: msg.mimeType,
|
|
258
|
-
});
|
|
259
|
-
// Persist to Gallery Store asynchronously (fire-and-forget)
|
|
260
|
-
if (this.galleryStore && session.projectPath) {
|
|
261
|
-
this.galleryStore.addImageFromBase64(msg.imageBase64, msg.mimeType, session.projectPath, msg.sessionId).catch((err) => {
|
|
262
|
-
console.warn(`[ws] Failed to persist image to gallery: ${err}`);
|
|
263
|
-
});
|
|
264
|
-
}
|
|
264
|
+
if (images.length > 0) {
|
|
265
|
+
console.log(`[ws] Sending message with ${images.length} inline Base64 image(s)`);
|
|
266
|
+
claudeProc.sendInputWithImages(text, images);
|
|
265
267
|
}
|
|
266
|
-
//
|
|
268
|
+
// Legacy imageId mode (backward compatibility)
|
|
267
269
|
else if (msg.imageId && this.galleryStore) {
|
|
268
270
|
this.galleryStore.getImageAsBase64(msg.imageId).then((imageData) => {
|
|
269
271
|
if (imageData) {
|
|
270
|
-
claudeProc.
|
|
272
|
+
claudeProc.sendInputWithImages(text, [imageData]);
|
|
271
273
|
}
|
|
272
274
|
else {
|
|
273
275
|
console.warn(`[ws] Image not found: ${msg.imageId}`);
|
|
@@ -278,19 +280,21 @@ export class BridgeWebSocketServer {
|
|
|
278
280
|
session.process.sendInput(text);
|
|
279
281
|
});
|
|
280
282
|
}
|
|
281
|
-
//
|
|
283
|
+
// Text-only message
|
|
282
284
|
else {
|
|
283
285
|
session.process.sendInput(text);
|
|
284
286
|
}
|
|
285
287
|
break;
|
|
286
288
|
}
|
|
287
289
|
case "push_register": {
|
|
288
|
-
|
|
290
|
+
const locale = normalizePushLocale(msg.locale);
|
|
291
|
+
console.log(`[ws] push_register received (platform: ${msg.platform}, locale: ${locale}, configured: ${this.pushRelay.isConfigured})`);
|
|
289
292
|
if (!this.pushRelay.isConfigured) {
|
|
290
293
|
this.send(ws, { type: "error", message: "Push relay is not configured on bridge" });
|
|
291
294
|
return;
|
|
292
295
|
}
|
|
293
|
-
this.
|
|
296
|
+
this.tokenLocales.set(msg.token, locale);
|
|
297
|
+
this.pushRelay.registerToken(msg.token, msg.platform, locale).then(() => {
|
|
294
298
|
console.log("[ws] push_register: token registered successfully");
|
|
295
299
|
}).catch((err) => {
|
|
296
300
|
const detail = err instanceof Error ? err.message : String(err);
|
|
@@ -305,6 +309,7 @@ export class BridgeWebSocketServer {
|
|
|
305
309
|
this.send(ws, { type: "error", message: "Push relay is not configured on bridge" });
|
|
306
310
|
return;
|
|
307
311
|
}
|
|
312
|
+
this.tokenLocales.delete(msg.token);
|
|
308
313
|
this.pushRelay.unregisterToken(msg.token).then(() => {
|
|
309
314
|
console.log("[ws] push_unregister: token unregistered successfully");
|
|
310
315
|
}).catch((err) => {
|
|
@@ -426,6 +431,7 @@ export class BridgeWebSocketServer {
|
|
|
426
431
|
projectPath,
|
|
427
432
|
...(permissionMode ? { permissionMode } : {}),
|
|
428
433
|
clearContext: true,
|
|
434
|
+
sourceSessionId: sessionId,
|
|
429
435
|
});
|
|
430
436
|
this.broadcastSessionList();
|
|
431
437
|
}
|
|
@@ -1138,6 +1144,11 @@ export class BridgeWebSocketServer {
|
|
|
1138
1144
|
const parts = session.projectPath.replace(/\/+$/, "").split("/");
|
|
1139
1145
|
return parts[parts.length - 1] || "";
|
|
1140
1146
|
}
|
|
1147
|
+
/** Get unique locales from registered tokens. Falls back to ["en"] if none registered. */
|
|
1148
|
+
getRegisteredLocales() {
|
|
1149
|
+
const locales = new Set(this.tokenLocales.values());
|
|
1150
|
+
return locales.size > 0 ? [...locales] : ["en"];
|
|
1151
|
+
}
|
|
1141
1152
|
maybeSendPushNotification(sessionId, msg) {
|
|
1142
1153
|
if (!this.pushRelay.isConfigured)
|
|
1143
1154
|
return;
|
|
@@ -1149,38 +1160,54 @@ export class BridgeWebSocketServer {
|
|
|
1149
1160
|
seen.add(msg.toolUseId);
|
|
1150
1161
|
this.notifiedPermissionToolUses.set(sessionId, seen);
|
|
1151
1162
|
const isAskUserQuestion = msg.toolName === "AskUserQuestion";
|
|
1163
|
+
const isExitPlanMode = msg.toolName === "ExitPlanMode";
|
|
1152
1164
|
const eventType = isAskUserQuestion ? "ask_user_question" : "approval_required";
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
: (isAskUserQuestion ? "回答待ち" : "承認待ち");
|
|
1156
|
-
let body;
|
|
1165
|
+
// Extract question text for AskUserQuestion
|
|
1166
|
+
let questionText;
|
|
1157
1167
|
if (isAskUserQuestion) {
|
|
1158
|
-
// Extract question text from input.questions[0].question
|
|
1159
1168
|
const questions = msg.input?.questions;
|
|
1160
1169
|
const firstQuestion = Array.isArray(questions) && questions.length > 0
|
|
1161
1170
|
? questions[0]?.question
|
|
1162
1171
|
: undefined;
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1172
|
+
if (typeof firstQuestion === "string" && firstQuestion.length > 0) {
|
|
1173
|
+
questionText = firstQuestion.slice(0, 120);
|
|
1174
|
+
}
|
|
1166
1175
|
}
|
|
1167
|
-
|
|
1168
|
-
|
|
1176
|
+
const data = {
|
|
1177
|
+
sessionId,
|
|
1178
|
+
provider: this.sessionManager.get(sessionId)?.provider ?? "claude",
|
|
1179
|
+
toolUseId: msg.toolUseId,
|
|
1180
|
+
toolName: msg.toolName,
|
|
1181
|
+
};
|
|
1182
|
+
for (const locale of this.getRegisteredLocales()) {
|
|
1183
|
+
let title;
|
|
1184
|
+
let body;
|
|
1185
|
+
if (isExitPlanMode) {
|
|
1186
|
+
const titleKey = "plan_ready_title";
|
|
1187
|
+
title = project ? `${t(locale, titleKey)} - ${project}` : t(locale, titleKey);
|
|
1188
|
+
body = t(locale, "plan_ready_body");
|
|
1189
|
+
}
|
|
1190
|
+
else if (isAskUserQuestion) {
|
|
1191
|
+
const titleKey = "ask_title";
|
|
1192
|
+
title = project ? `${t(locale, titleKey)} - ${project}` : t(locale, titleKey);
|
|
1193
|
+
body = questionText ?? t(locale, "ask_default_body");
|
|
1194
|
+
}
|
|
1195
|
+
else {
|
|
1196
|
+
const titleKey = "approval_title";
|
|
1197
|
+
title = project ? `${t(locale, titleKey)} - ${project}` : t(locale, titleKey);
|
|
1198
|
+
body = t(locale, "approval_body", { toolName: msg.toolName });
|
|
1199
|
+
}
|
|
1200
|
+
void this.pushRelay.notify({
|
|
1201
|
+
eventType,
|
|
1202
|
+
title,
|
|
1203
|
+
body,
|
|
1204
|
+
locale,
|
|
1205
|
+
data,
|
|
1206
|
+
}).catch((err) => {
|
|
1207
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
1208
|
+
console.warn(`[ws] Failed to send push notification (${eventType}, ${locale}): ${detail}`);
|
|
1209
|
+
});
|
|
1169
1210
|
}
|
|
1170
|
-
void this.pushRelay.notify({
|
|
1171
|
-
eventType,
|
|
1172
|
-
title,
|
|
1173
|
-
body,
|
|
1174
|
-
data: {
|
|
1175
|
-
sessionId,
|
|
1176
|
-
provider: this.sessionManager.get(sessionId)?.provider ?? "claude",
|
|
1177
|
-
toolUseId: msg.toolUseId,
|
|
1178
|
-
toolName: msg.toolName,
|
|
1179
|
-
},
|
|
1180
|
-
}).catch((err) => {
|
|
1181
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
1182
|
-
console.warn(`[ws] Failed to send push notification (${eventType}): ${detail}`);
|
|
1183
|
-
});
|
|
1184
1211
|
return;
|
|
1185
1212
|
}
|
|
1186
1213
|
if (msg.type !== "result")
|
|
@@ -1191,24 +1218,14 @@ export class BridgeWebSocketServer {
|
|
|
1191
1218
|
return;
|
|
1192
1219
|
const isSuccess = msg.subtype === "success";
|
|
1193
1220
|
const eventType = isSuccess ? "session_completed" : "session_failed";
|
|
1194
|
-
const
|
|
1195
|
-
? (isSuccess ? `✅ ${project}` : `❌ ${project}`)
|
|
1196
|
-
: (isSuccess ? "タスク完了" : "エラー発生");
|
|
1197
|
-
let body;
|
|
1221
|
+
const pieces = [];
|
|
1198
1222
|
if (isSuccess) {
|
|
1199
|
-
const pieces = [];
|
|
1200
1223
|
if (msg.duration != null)
|
|
1201
1224
|
pieces.push(`${msg.duration.toFixed(1)}s`);
|
|
1202
1225
|
if (msg.cost != null)
|
|
1203
1226
|
pieces.push(`$${msg.cost.toFixed(4)}`);
|
|
1204
|
-
const stats = pieces.length > 0 ? ` (${pieces.join(", ")})` : "";
|
|
1205
|
-
body = msg.result
|
|
1206
|
-
? `${msg.result.slice(0, 120)}${stats}`
|
|
1207
|
-
: `セッション完了${stats}`;
|
|
1208
|
-
}
|
|
1209
|
-
else {
|
|
1210
|
-
body = msg.error ? msg.error.slice(0, 120) : "セッションが失敗しました";
|
|
1211
1227
|
}
|
|
1228
|
+
const stats = pieces.length > 0 ? ` (${pieces.join(", ")})` : "";
|
|
1212
1229
|
const data = {
|
|
1213
1230
|
sessionId,
|
|
1214
1231
|
provider: this.sessionManager.get(sessionId)?.provider ?? "claude",
|
|
@@ -1218,15 +1235,30 @@ export class BridgeWebSocketServer {
|
|
|
1218
1235
|
data.stopReason = msg.stopReason;
|
|
1219
1236
|
if (msg.sessionId)
|
|
1220
1237
|
data.providerSessionId = msg.sessionId;
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1238
|
+
for (const locale of this.getRegisteredLocales()) {
|
|
1239
|
+
const title = project
|
|
1240
|
+
? (isSuccess ? `✅ ${project}` : `❌ ${project}`)
|
|
1241
|
+
: (isSuccess ? t(locale, "task_completed") : t(locale, "error_occurred"));
|
|
1242
|
+
let body;
|
|
1243
|
+
if (isSuccess) {
|
|
1244
|
+
body = msg.result
|
|
1245
|
+
? `${msg.result.slice(0, 120)}${stats}`
|
|
1246
|
+
: `${t(locale, "session_completed")}${stats}`;
|
|
1247
|
+
}
|
|
1248
|
+
else {
|
|
1249
|
+
body = msg.error ? msg.error.slice(0, 120) : t(locale, "session_failed");
|
|
1250
|
+
}
|
|
1251
|
+
void this.pushRelay.notify({
|
|
1252
|
+
eventType,
|
|
1253
|
+
title,
|
|
1254
|
+
body,
|
|
1255
|
+
locale,
|
|
1256
|
+
data,
|
|
1257
|
+
}).catch((err) => {
|
|
1258
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
1259
|
+
console.warn(`[ws] Failed to send push notification (${eventType}, ${locale}): ${detail}`);
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1230
1262
|
}
|
|
1231
1263
|
broadcast(msg) {
|
|
1232
1264
|
const data = JSON.stringify(msg);
|