@ccpocket/bridge 1.48.1 → 1.49.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.
@@ -0,0 +1,322 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join } from "node:path";
5
+ const DEFAULT_STORE_FILE = join(homedir(), ".ccpocket", "prompt-history-v2.json");
6
+ const DEFAULT_BRIDGE_PORT = 8765;
7
+ export function promptHistoryStoreFileForPort(port, explicitFile) {
8
+ if (explicitFile?.trim())
9
+ return explicitFile.trim();
10
+ const parsedPort = typeof port === "number" ? port : Number.parseInt(port ?? "", 10);
11
+ if (!Number.isInteger(parsedPort) || parsedPort === DEFAULT_BRIDGE_PORT) {
12
+ return DEFAULT_STORE_FILE;
13
+ }
14
+ return join(homedir(), ".ccpocket", `prompt-history-v2-${parsedPort}.json`);
15
+ }
16
+ function isoNow() {
17
+ return new Date().toISOString();
18
+ }
19
+ function maxIso(left, right) {
20
+ if (!left)
21
+ return right ?? isoNow();
22
+ if (!right)
23
+ return left;
24
+ return left >= right ? left : right;
25
+ }
26
+ function minIso(left, right) {
27
+ if (!left)
28
+ return right ?? isoNow();
29
+ if (!right)
30
+ return left;
31
+ return left <= right ? left : right;
32
+ }
33
+ function normalizeText(text) {
34
+ return text.trim().replace(/\r\n/g, "\n");
35
+ }
36
+ function normalizeProjectPath(projectPath) {
37
+ return (projectPath ?? "").trim();
38
+ }
39
+ export function promptHistoryId(text, projectPath = "") {
40
+ const stableKey = `${normalizeProjectPath(projectPath)}\u0000${normalizeText(text)}`;
41
+ const digest = createHash("sha256").update(stableKey).digest("hex");
42
+ return `ph_${digest.slice(0, 24)}`;
43
+ }
44
+ export function detectPromptCommandKind(text) {
45
+ const trimmed = text.trimStart();
46
+ if (trimmed.startsWith("$"))
47
+ return "skill";
48
+ if (trimmed.startsWith("/"))
49
+ return "slash";
50
+ const commandMatch = /<command-name>\s*(.*?)\s*<\/command-name>/s.exec(text);
51
+ const commandName = commandMatch?.[1]?.trim();
52
+ if (commandName?.startsWith("$"))
53
+ return "skill";
54
+ if (commandName?.startsWith("/"))
55
+ return "slash";
56
+ return "none";
57
+ }
58
+ function cloneEntry(entry) {
59
+ return {
60
+ ...entry,
61
+ clientStats: { ...entry.clientStats },
62
+ sessionStats: { ...entry.sessionStats },
63
+ };
64
+ }
65
+ export class PromptHistoryStore {
66
+ data = {
67
+ version: 2,
68
+ bridgeInstanceId: randomUUID(),
69
+ revision: 0,
70
+ entries: [],
71
+ };
72
+ filePath;
73
+ constructor(filePath) {
74
+ this.filePath = filePath ?? DEFAULT_STORE_FILE;
75
+ }
76
+ async init() {
77
+ await mkdir(dirname(this.filePath), { recursive: true });
78
+ try {
79
+ const raw = await readFile(this.filePath, "utf-8");
80
+ const parsed = JSON.parse(raw);
81
+ if (parsed.version === 2 && Array.isArray(parsed.entries)) {
82
+ this.data = {
83
+ version: 2,
84
+ bridgeInstanceId: typeof parsed.bridgeInstanceId === "string" &&
85
+ parsed.bridgeInstanceId.length > 0
86
+ ? parsed.bridgeInstanceId
87
+ : randomUUID(),
88
+ revision: Number.isInteger(parsed.revision) ? parsed.revision : 0,
89
+ entries: parsed.entries.map((entry) => ({
90
+ ...entry,
91
+ clientStats: entry.clientStats ?? {},
92
+ sessionStats: entry.sessionStats ?? {},
93
+ commandKind: entry.commandKind ?? detectPromptCommandKind(entry.text),
94
+ })),
95
+ };
96
+ if (!parsed.bridgeInstanceId)
97
+ await this.save();
98
+ }
99
+ }
100
+ catch {
101
+ this.data = {
102
+ version: 2,
103
+ bridgeInstanceId: randomUUID(),
104
+ revision: 0,
105
+ entries: [],
106
+ };
107
+ await this.save();
108
+ }
109
+ }
110
+ get revision() {
111
+ return this.data.revision;
112
+ }
113
+ get bridgeInstanceId() {
114
+ return this.data.bridgeInstanceId;
115
+ }
116
+ list(includeDeleted = false) {
117
+ return this.data.entries
118
+ .filter((entry) => includeDeleted || !entry.deletedAt)
119
+ .map(cloneEntry);
120
+ }
121
+ async record(input) {
122
+ const text = normalizeText(input.text);
123
+ if (!text)
124
+ throw new Error("Prompt text is required");
125
+ const projectPath = normalizeProjectPath(input.projectPath);
126
+ const id = promptHistoryId(text, projectPath);
127
+ const usedAt = input.usedAt ?? isoNow();
128
+ const existing = this.findMutable(id);
129
+ if (existing) {
130
+ existing.totalUseCount += 1;
131
+ existing.lastUsedAt = maxIso(existing.lastUsedAt, usedAt);
132
+ existing.updatedAt = maxIso(existing.updatedAt, usedAt);
133
+ existing.deletedAt = undefined;
134
+ this.incrementClientStat(existing, input.clientId, input.clientName, usedAt, 1);
135
+ if (input.sessionId)
136
+ this.incrementSessionStat(existing, input.sessionId, usedAt, 1);
137
+ await this.saveBumped();
138
+ return cloneEntry(existing);
139
+ }
140
+ const entry = {
141
+ id,
142
+ text,
143
+ projectPath,
144
+ totalUseCount: 1,
145
+ isFavorite: false,
146
+ createdAt: usedAt,
147
+ lastUsedAt: usedAt,
148
+ updatedAt: usedAt,
149
+ commandKind: detectPromptCommandKind(text),
150
+ clientStats: {},
151
+ sessionStats: {},
152
+ };
153
+ this.incrementClientStat(entry, input.clientId, input.clientName, usedAt, 1);
154
+ if (input.sessionId)
155
+ this.incrementSessionStat(entry, input.sessionId, usedAt, 1);
156
+ this.data.entries.push(entry);
157
+ await this.saveBumped();
158
+ return cloneEntry(entry);
159
+ }
160
+ async mutate(input) {
161
+ const id = input.id ?? (input.text ? promptHistoryId(input.text, input.projectPath ?? "") : undefined);
162
+ if (!id)
163
+ return null;
164
+ const entry = this.findMutable(id);
165
+ if (!entry)
166
+ return null;
167
+ const updatedAt = input.updatedAt ?? isoNow();
168
+ switch (input.action) {
169
+ case "favorite":
170
+ entry.isFavorite = input.isFavorite ?? !entry.isFavorite;
171
+ entry.favoriteUpdatedAt = updatedAt;
172
+ entry.updatedAt = maxIso(entry.updatedAt, updatedAt);
173
+ break;
174
+ case "delete":
175
+ entry.deletedAt = updatedAt;
176
+ entry.updatedAt = maxIso(entry.updatedAt, updatedAt);
177
+ break;
178
+ case "restore":
179
+ entry.deletedAt = undefined;
180
+ entry.updatedAt = maxIso(entry.updatedAt, updatedAt);
181
+ break;
182
+ }
183
+ await this.saveBumped();
184
+ return cloneEntry(entry);
185
+ }
186
+ async importEntries(entries, clientId, clientName) {
187
+ this.data.entries = [];
188
+ let imported = 0;
189
+ for (const raw of entries) {
190
+ const text = normalizeText(raw.text);
191
+ if (!text)
192
+ continue;
193
+ const projectPath = normalizeProjectPath(raw.projectPath);
194
+ const id = raw.id ?? promptHistoryId(text, projectPath);
195
+ const now = isoNow();
196
+ const useCount = Math.max(1, raw.totalUseCount ?? raw.useCount ?? 1);
197
+ const createdAt = raw.createdAt ?? now;
198
+ const lastUsedAt = raw.lastUsedAt ?? raw.updatedAt ?? now;
199
+ const updatedAt = raw.updatedAt ?? lastUsedAt;
200
+ const incoming = {
201
+ id,
202
+ text,
203
+ projectPath,
204
+ totalUseCount: useCount,
205
+ isFavorite: raw.isFavorite ?? false,
206
+ createdAt,
207
+ lastUsedAt,
208
+ updatedAt,
209
+ favoriteUpdatedAt: raw.favoriteUpdatedAt ?? (raw.isFavorite ? updatedAt : undefined),
210
+ deletedAt: raw.deletedAt,
211
+ commandKind: raw.commandKind ?? detectPromptCommandKind(text),
212
+ clientStats: raw.clientStats ?? {},
213
+ sessionStats: raw.sessionStats ?? {},
214
+ };
215
+ this.incrementClientStat(incoming, clientId, clientName, lastUsedAt, useCount);
216
+ this.mergeEntry(incoming);
217
+ imported += 1;
218
+ }
219
+ await this.saveBumped();
220
+ return { imported, entries: this.list() };
221
+ }
222
+ async mergeClientEntries(entries) {
223
+ if (entries.length === 0)
224
+ return;
225
+ for (const raw of entries) {
226
+ const text = normalizeText(raw.text);
227
+ if (!text)
228
+ continue;
229
+ const projectPath = normalizeProjectPath(raw.projectPath);
230
+ this.mergeEntry({
231
+ id: raw.id ?? promptHistoryId(text, projectPath),
232
+ text,
233
+ projectPath,
234
+ totalUseCount: Math.max(1, raw.totalUseCount ?? raw.useCount ?? 1),
235
+ isFavorite: raw.isFavorite ?? false,
236
+ createdAt: raw.createdAt ?? isoNow(),
237
+ lastUsedAt: raw.lastUsedAt ?? raw.updatedAt ?? isoNow(),
238
+ updatedAt: raw.updatedAt ?? raw.lastUsedAt ?? isoNow(),
239
+ favoriteUpdatedAt: raw.favoriteUpdatedAt,
240
+ deletedAt: raw.deletedAt,
241
+ commandKind: raw.commandKind ?? detectPromptCommandKind(text),
242
+ clientStats: raw.clientStats ?? {},
243
+ sessionStats: raw.sessionStats ?? {},
244
+ });
245
+ }
246
+ await this.saveBumped();
247
+ }
248
+ findMutable(id) {
249
+ return this.data.entries.find((entry) => entry.id === id);
250
+ }
251
+ incrementClientStat(entry, clientId, clientName, lastUsedAt, increment) {
252
+ const current = entry.clientStats[clientId] ?? { useCount: 0, lastUsedAt };
253
+ entry.clientStats[clientId] = {
254
+ useCount: current.useCount + increment,
255
+ lastUsedAt: maxIso(current.lastUsedAt, lastUsedAt),
256
+ clientName: clientName ?? current.clientName,
257
+ };
258
+ }
259
+ incrementSessionStat(entry, sessionId, lastUsedAt, increment) {
260
+ const current = entry.sessionStats[sessionId] ?? { useCount: 0, lastUsedAt };
261
+ entry.sessionStats[sessionId] = {
262
+ useCount: current.useCount + increment,
263
+ lastUsedAt: maxIso(current.lastUsedAt, lastUsedAt),
264
+ };
265
+ }
266
+ mergeEntry(incoming) {
267
+ const existing = this.findMutable(incoming.id);
268
+ if (!existing) {
269
+ this.data.entries.push(cloneEntry(incoming));
270
+ return;
271
+ }
272
+ existing.totalUseCount += incoming.totalUseCount;
273
+ existing.createdAt = minIso(existing.createdAt, incoming.createdAt);
274
+ existing.lastUsedAt = maxIso(existing.lastUsedAt, incoming.lastUsedAt);
275
+ existing.updatedAt = maxIso(existing.updatedAt, incoming.updatedAt);
276
+ existing.commandKind =
277
+ existing.commandKind === "none" ? incoming.commandKind : existing.commandKind;
278
+ if (incoming.favoriteUpdatedAt &&
279
+ (!existing.favoriteUpdatedAt ||
280
+ incoming.favoriteUpdatedAt >= existing.favoriteUpdatedAt)) {
281
+ existing.isFavorite = incoming.isFavorite;
282
+ existing.favoriteUpdatedAt = incoming.favoriteUpdatedAt;
283
+ }
284
+ else if (incoming.isFavorite && !existing.favoriteUpdatedAt) {
285
+ existing.isFavorite = true;
286
+ existing.favoriteUpdatedAt = incoming.updatedAt;
287
+ }
288
+ if (incoming.deletedAt && (!existing.deletedAt || incoming.deletedAt >= existing.deletedAt)) {
289
+ existing.deletedAt = incoming.deletedAt;
290
+ }
291
+ for (const [clientId, stat] of Object.entries(incoming.clientStats)) {
292
+ const current = existing.clientStats[clientId];
293
+ existing.clientStats[clientId] = current
294
+ ? {
295
+ useCount: current.useCount + stat.useCount,
296
+ lastUsedAt: maxIso(current.lastUsedAt, stat.lastUsedAt),
297
+ clientName: stat.clientName ?? current.clientName,
298
+ }
299
+ : { ...stat };
300
+ }
301
+ for (const [sessionId, stat] of Object.entries(incoming.sessionStats)) {
302
+ const current = existing.sessionStats[sessionId];
303
+ existing.sessionStats[sessionId] = current
304
+ ? {
305
+ useCount: current.useCount + stat.useCount,
306
+ lastUsedAt: maxIso(current.lastUsedAt, stat.lastUsedAt),
307
+ }
308
+ : { ...stat };
309
+ }
310
+ }
311
+ async saveBumped() {
312
+ this.data.revision += 1;
313
+ await this.save();
314
+ }
315
+ async save() {
316
+ await mkdir(dirname(this.filePath), { recursive: true });
317
+ const tmp = `${this.filePath}.${randomUUID()}.tmp`;
318
+ await writeFile(tmp, JSON.stringify(this.data, null, 2), "utf-8");
319
+ await rename(tmp, this.filePath);
320
+ }
321
+ }
322
+ //# sourceMappingURL=prompt-history-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-history-store.js","sourceRoot":"","sources":["../src/prompt-history-store.ts"],"names":[],"mappings":"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"}
@@ -6,6 +6,7 @@ import { DebugTraceStore } from "./debug-trace-store.js";
6
6
  import { RecordingStore } from "./recording-store.js";
7
7
  import type { FirebaseAuthClient } from "./firebase-auth.js";
8
8
  import type { PromptHistoryBackupStore } from "./prompt-history-backup.js";
9
+ import type { PromptHistoryStore } from "./prompt-history-store.js";
9
10
  export interface BridgeServerOptions {
10
11
  server: HttpServer;
11
12
  apiKey?: string;
@@ -17,6 +18,7 @@ export interface BridgeServerOptions {
17
18
  recordingStore?: RecordingStore;
18
19
  firebaseAuth?: FirebaseAuthClient;
19
20
  promptHistoryBackup?: PromptHistoryBackupStore;
21
+ promptHistoryStore?: PromptHistoryStore;
20
22
  platform?: NodeJS.Platform;
21
23
  }
22
24
  export declare class BridgeWebSocketServer {
@@ -34,6 +36,7 @@ export declare class BridgeWebSocketServer {
34
36
  private worktreeStore;
35
37
  private pushRelay;
36
38
  private promptHistoryBackup;
39
+ private promptHistoryStore;
37
40
  private recentSessionsRequestId;
38
41
  private debugEvents;
39
42
  private notifiedPermissionToolUses;
@@ -83,8 +86,10 @@ export declare class BridgeWebSocketServer {
83
86
  private resolveSession;
84
87
  private getFirstSession;
85
88
  private sendSessionList;
89
+ private sendPromptHistoryStatus;
86
90
  /** Broadcast session list to all connected clients. */
87
91
  private broadcastSessionList;
92
+ private broadcastPromptHistoryStatus;
88
93
  private broadcastSessionMessage;
89
94
  private listRecentSessions;
90
95
  private refreshCodexProfiles;
package/dist/websocket.js CHANGED
@@ -38,6 +38,10 @@ const CODEX_MODELS = [
38
38
  "gpt-5.3-codex",
39
39
  "gpt-5.3-codex-spark",
40
40
  ];
41
+ const OPT_IN_SERVER_MESSAGES = new Set([
42
+ "conversation_queue",
43
+ "prompt_history_status",
44
+ ]);
41
45
  // ---- Codex mode mapping helpers ----
42
46
  /** Map unified PermissionMode to Codex approval_policy.
43
47
  * Only "bypassPermissions" maps to "never"; all others use "on-request". */
@@ -166,6 +170,7 @@ export class BridgeWebSocketServer {
166
170
  worktreeStore;
167
171
  pushRelay;
168
172
  promptHistoryBackup;
173
+ promptHistoryStore;
169
174
  recentSessionsRequestId = 0;
170
175
  debugEvents = new Map();
171
176
  notifiedPermissionToolUses = new Map();
@@ -181,7 +186,7 @@ export class BridgeWebSocketServer {
181
186
  platform;
182
187
  clientSupportedServerMessages = new WeakMap();
183
188
  constructor(options) {
184
- const { server, apiKey, allowedDirs, imageStore, galleryStore, projectHistory, debugTraceStore, recordingStore, firebaseAuth, promptHistoryBackup, platform, } = options;
189
+ const { server, apiKey, allowedDirs, imageStore, galleryStore, projectHistory, debugTraceStore, recordingStore, firebaseAuth, promptHistoryBackup, promptHistoryStore, platform, } = options;
185
190
  this.apiKey = apiKey ?? null;
186
191
  this.allowedDirs = allowedDirs ?? [];
187
192
  this.imageStore = imageStore ?? null;
@@ -192,6 +197,7 @@ export class BridgeWebSocketServer {
192
197
  this.worktreeStore = new WorktreeStore();
193
198
  this.pushRelay = new PushRelayClient({ firebaseAuth });
194
199
  this.promptHistoryBackup = promptHistoryBackup ?? null;
200
+ this.promptHistoryStore = promptHistoryStore ?? null;
195
201
  this.platform = platform ?? process.platform;
196
202
  this.archiveStore = new ArchiveStore();
197
203
  void this.debugTraceStore.init().catch((err) => {
@@ -544,6 +550,7 @@ export class BridgeWebSocketServer {
544
550
  async handleClientMessage(msg, ws) {
545
551
  if (msg.type === "client_capabilities") {
546
552
  this.clientSupportedServerMessages.set(ws, new Set(msg.supportedServerMessages ?? []));
553
+ this.sendPromptHistoryStatus(ws);
547
554
  return;
548
555
  }
549
556
  const incomingSessionId = this.extractSessionIdFromClientMessage(msg);
@@ -3205,6 +3212,144 @@ export class BridgeWebSocketServer {
3205
3212
  });
3206
3213
  break;
3207
3214
  }
3215
+ case "record_prompt_history": {
3216
+ if (!this.promptHistoryStore) {
3217
+ this.send(ws, {
3218
+ type: "prompt_history_mutation_result",
3219
+ success: false,
3220
+ error: "Prompt history store not available",
3221
+ });
3222
+ break;
3223
+ }
3224
+ try {
3225
+ const entry = await this.promptHistoryStore.record({
3226
+ text: msg.text,
3227
+ projectPath: msg.projectPath,
3228
+ clientId: msg.clientId,
3229
+ clientName: msg.clientName,
3230
+ sessionId: msg.sessionId,
3231
+ usedAt: msg.usedAt,
3232
+ });
3233
+ this.send(ws, {
3234
+ type: "prompt_history_mutation_result",
3235
+ success: true,
3236
+ bridgeInstanceId: this.promptHistoryStore.bridgeInstanceId,
3237
+ revision: this.promptHistoryStore.revision,
3238
+ entry,
3239
+ });
3240
+ this.broadcastPromptHistoryStatus();
3241
+ }
3242
+ catch (err) {
3243
+ this.send(ws, {
3244
+ type: "prompt_history_mutation_result",
3245
+ success: false,
3246
+ error: err instanceof Error ? err.message : String(err),
3247
+ });
3248
+ }
3249
+ break;
3250
+ }
3251
+ case "sync_prompt_history": {
3252
+ if (!this.promptHistoryStore) {
3253
+ this.send(ws, {
3254
+ type: "prompt_history_sync_result",
3255
+ success: false,
3256
+ error: "Prompt history store not available",
3257
+ });
3258
+ break;
3259
+ }
3260
+ try {
3261
+ if (msg.entries?.length) {
3262
+ await this.promptHistoryStore.mergeClientEntries(msg.entries);
3263
+ }
3264
+ this.send(ws, {
3265
+ type: "prompt_history_sync_result",
3266
+ success: true,
3267
+ bridgeInstanceId: this.promptHistoryStore.bridgeInstanceId,
3268
+ revision: this.promptHistoryStore.revision,
3269
+ syncedAt: new Date().toISOString(),
3270
+ fullSnapshot: true,
3271
+ entries: this.promptHistoryStore.list(msg.includeDeleted ?? true),
3272
+ });
3273
+ this.broadcastPromptHistoryStatus();
3274
+ }
3275
+ catch (err) {
3276
+ this.send(ws, {
3277
+ type: "prompt_history_sync_result",
3278
+ success: false,
3279
+ error: err instanceof Error ? err.message : String(err),
3280
+ });
3281
+ }
3282
+ break;
3283
+ }
3284
+ case "mutate_prompt_history": {
3285
+ if (!this.promptHistoryStore) {
3286
+ this.send(ws, {
3287
+ type: "prompt_history_mutation_result",
3288
+ success: false,
3289
+ error: "Prompt history store not available",
3290
+ });
3291
+ break;
3292
+ }
3293
+ try {
3294
+ const entry = await this.promptHistoryStore.mutate({
3295
+ id: msg.id,
3296
+ text: msg.text,
3297
+ projectPath: msg.projectPath,
3298
+ action: msg.action,
3299
+ isFavorite: msg.isFavorite,
3300
+ updatedAt: msg.updatedAt,
3301
+ });
3302
+ this.send(ws, {
3303
+ type: "prompt_history_mutation_result",
3304
+ success: entry != null,
3305
+ bridgeInstanceId: this.promptHistoryStore.bridgeInstanceId,
3306
+ revision: this.promptHistoryStore.revision,
3307
+ entry: entry ?? undefined,
3308
+ error: entry == null ? "Prompt not found" : undefined,
3309
+ });
3310
+ if (entry)
3311
+ this.broadcastPromptHistoryStatus();
3312
+ }
3313
+ catch (err) {
3314
+ this.send(ws, {
3315
+ type: "prompt_history_mutation_result",
3316
+ success: false,
3317
+ error: err instanceof Error ? err.message : String(err),
3318
+ });
3319
+ }
3320
+ break;
3321
+ }
3322
+ case "import_prompt_history_v1": {
3323
+ if (!this.promptHistoryStore) {
3324
+ this.send(ws, {
3325
+ type: "prompt_history_sync_result",
3326
+ success: false,
3327
+ error: "Prompt history store not available",
3328
+ });
3329
+ break;
3330
+ }
3331
+ try {
3332
+ const result = await this.promptHistoryStore.importEntries(msg.entries, msg.clientId, msg.clientName);
3333
+ this.send(ws, {
3334
+ type: "prompt_history_sync_result",
3335
+ success: true,
3336
+ bridgeInstanceId: this.promptHistoryStore.bridgeInstanceId,
3337
+ revision: this.promptHistoryStore.revision,
3338
+ syncedAt: new Date().toISOString(),
3339
+ fullSnapshot: true,
3340
+ entries: result.entries,
3341
+ });
3342
+ this.broadcastPromptHistoryStatus();
3343
+ }
3344
+ catch (err) {
3345
+ this.send(ws, {
3346
+ type: "prompt_history_sync_result",
3347
+ success: false,
3348
+ error: err instanceof Error ? err.message : String(err),
3349
+ });
3350
+ }
3351
+ break;
3352
+ }
3208
3353
  case "rename_session": {
3209
3354
  const name = msg.name || null;
3210
3355
  await this.handleRenameSession(ws, msg.sessionId, name, msg);
@@ -3308,6 +3453,19 @@ export class BridgeWebSocketServer {
3308
3453
  bridgeVersion: getPackageVersion(),
3309
3454
  });
3310
3455
  }
3456
+ sendPromptHistoryStatus(ws) {
3457
+ if (!this.promptHistoryStore)
3458
+ return;
3459
+ const entries = this.promptHistoryStore.list(true);
3460
+ const updatedAt = entries.reduce((latest, entry) => !latest || entry.updatedAt > latest ? entry.updatedAt : latest, undefined);
3461
+ this.send(ws, {
3462
+ type: "prompt_history_status",
3463
+ bridgeInstanceId: this.promptHistoryStore.bridgeInstanceId,
3464
+ revision: this.promptHistoryStore.revision,
3465
+ entryCount: entries.filter((entry) => !entry.deletedAt).length,
3466
+ updatedAt,
3467
+ });
3468
+ }
3311
3469
  /** Broadcast session list to all connected clients. */
3312
3470
  broadcastSessionList() {
3313
3471
  this.pruneDebugEvents();
@@ -3323,6 +3481,19 @@ export class BridgeWebSocketServer {
3323
3481
  bridgeVersion: getPackageVersion(),
3324
3482
  });
3325
3483
  }
3484
+ broadcastPromptHistoryStatus() {
3485
+ if (!this.promptHistoryStore)
3486
+ return;
3487
+ const entries = this.promptHistoryStore.list(true);
3488
+ const updatedAt = entries.reduce((latest, entry) => !latest || entry.updatedAt > latest ? entry.updatedAt : latest, undefined);
3489
+ this.broadcast({
3490
+ type: "prompt_history_status",
3491
+ bridgeInstanceId: this.promptHistoryStore.bridgeInstanceId,
3492
+ revision: this.promptHistoryStore.revision,
3493
+ entryCount: entries.filter((entry) => !entry.deletedAt).length,
3494
+ updatedAt,
3495
+ });
3496
+ }
3326
3497
  broadcastSessionMessage(sessionId, msg) {
3327
3498
  this.maybeSendPushNotification(sessionId, msg);
3328
3499
  this.recordDebugEvent(sessionId, {
@@ -3681,11 +3852,10 @@ export class BridgeWebSocketServer {
3681
3852
  }
3682
3853
  }
3683
3854
  shouldSendToClient(ws, msg) {
3684
- if (msg.type !== "conversation_queue")
3855
+ const type = typeof msg.type === "string" ? msg.type : "";
3856
+ if (!OPT_IN_SERVER_MESSAGES.has(type))
3685
3857
  return true;
3686
- return (this.clientSupportedServerMessages
3687
- .get(ws)
3688
- ?.has("conversation_queue") ?? false);
3858
+ return (this.clientSupportedServerMessages.get(ws)?.has(type) ?? false);
3689
3859
  }
3690
3860
  hasInputConflictSince(sessionId, baseSeq) {
3691
3861
  const delta = this.sessionManager.getHistorySince(sessionId, baseSeq);