codex@macbookpro
·
2026-04-01
index.test.js
1import assert from "node:assert/strict";
2import {
3 existsSync,
4 mkdirSync,
5 mkdtempSync,
6 readFileSync,
7 rmSync
8} from "node:fs";
9import { tmpdir } from "node:os";
10import { join } from "node:path";
11import { DatabaseSync } from "node:sqlite";
12import test from "node:test";
13
14import {
15 ARTIFACT_DB_FILENAME,
16 ARTIFACTS_DIRNAME,
17 ArtifactStore
18} from "../dist/index.js";
19
20const STORE_SOURCE = readFileSync(new URL("./store.ts", import.meta.url), "utf8");
21
22function extractStoreSql(name) {
23 const marker = `const ${name} = \``;
24 const start = STORE_SOURCE.indexOf(marker);
25 assert.notEqual(start, -1, `Could not find ${name} in store.ts`);
26
27 const sqlStart = start + marker.length;
28 const sqlEnd = STORE_SOURCE.indexOf("`;", sqlStart);
29 assert.notEqual(sqlEnd, -1, `Could not parse ${name} from store.ts`);
30
31 return STORE_SOURCE.slice(sqlStart, sqlEnd);
32}
33
34function getStoreDb(store) {
35 const db = Reflect.get(store, "db");
36 assert.ok(db instanceof DatabaseSync);
37 return db;
38}
39
40test("ArtifactStore writes message, execution, session, and index artifacts synchronously", async () => {
41 const rootDir = mkdtempSync(join(tmpdir(), "artifact-db-test-"));
42 const stateDir = join(rootDir, "state");
43 const databasePath = join(stateDir, ARTIFACT_DB_FILENAME);
44 const artifactDir = join(stateDir, ARTIFACTS_DIRNAME);
45 const store = new ArtifactStore({
46 artifactDir,
47 databasePath,
48 publicBaseUrl: "https://conductor.makefile.so"
49 });
50
51 try {
52 const message = await store.insertMessage({
53 conversationId: "conv_123",
54 id: "msg_123",
55 observedAt: Date.UTC(2026, 2, 28, 8, 9, 0),
56 organizationId: "org_123",
57 pageTitle: "Claude",
58 pageUrl: "https://claude.ai/chat/conv_123",
59 platform: "claude",
60 rawText: "完整消息内容\n第二行",
61 role: "assistant"
62 });
63 const execution = await store.insertExecution({
64 executedAt: Date.UTC(2026, 2, 28, 8, 10, 0),
65 instructionId: "inst_123",
66 messageId: message.id,
67 params: {
68 command: "pnpm test"
69 },
70 paramsKind: "body",
71 resultData: {
72 exit_code: 0,
73 stdout: "all good"
74 },
75 resultOk: true,
76 target: "conductor",
77 tool: "exec"
78 });
79 const [session] = await store.getLatestSessions(1);
80
81 assert.ok(session);
82 assert.equal(session.conversationId, "conv_123");
83 assert.equal(session.executionCount, 1);
84 assert.equal(session.messageCount, 1);
85 assert.equal(session.platform, "claude");
86
87 assert.equal(existsSync(databasePath), true);
88 assert.equal(existsSync(join(artifactDir, "msg", "msg_123.txt")), true);
89 assert.equal(existsSync(join(artifactDir, "msg", "msg_123.json")), true);
90 assert.equal(existsSync(join(artifactDir, "exec", "inst_123.txt")), true);
91 assert.equal(existsSync(join(artifactDir, "exec", "inst_123.json")), true);
92 assert.equal(existsSync(join(artifactDir, session.staticPath)), true);
93 assert.equal(existsSync(join(artifactDir, session.staticPath.replace(/\.txt$/u, ".json"))), true);
94 assert.equal(existsSync(join(artifactDir, "session", "latest.txt")), true);
95 assert.equal(existsSync(join(artifactDir, "session", "latest.json")), true);
96
97 assert.match(readFileSync(join(artifactDir, "msg", "msg_123.txt"), "utf8"), /kind: message/u);
98 assert.match(readFileSync(join(artifactDir, "msg", "msg_123.txt"), "utf8"), /完整消息内容/u);
99 assert.match(
100 readFileSync(join(artifactDir, "msg", "msg_123.json"), "utf8"),
101 /https:\/\/conductor\.makefile\.so\/artifact\/msg\/msg_123\.txt/u
102 );
103 assert.match(readFileSync(join(artifactDir, "exec", "inst_123.txt"), "utf8"), /params:/u);
104 assert.match(readFileSync(join(artifactDir, "exec", "inst_123.txt"), "utf8"), /pnpm test/u);
105 assert.match(readFileSync(join(artifactDir, session.staticPath), "utf8"), /### message msg_123/u);
106 assert.match(readFileSync(join(artifactDir, session.staticPath), "utf8"), /### execution inst_123/u);
107 assert.match(readFileSync(join(artifactDir, "session", "latest.txt"), "utf8"), new RegExp(session.id, "u"));
108 assert.match(
109 readFileSync(join(artifactDir, "session", "latest.json"), "utf8"),
110 new RegExp(`https://conductor\\.makefile\\.so/artifact/session/${session.id}\\.txt`, "u")
111 );
112
113 assert.deepEqual(await store.getMessage(message.id), message);
114 assert.deepEqual(await store.getExecution(execution.instructionId), execution);
115 assert.deepEqual(await store.listMessages({ conversationId: "conv_123" }), [message]);
116 assert.deepEqual(await store.listExecutions({ messageId: message.id }), [execution]);
117 assert.deepEqual(await store.listSessions({ platform: "claude" }), [session]);
118 } finally {
119 store.close();
120 rmSync(rootDir, {
121 force: true,
122 recursive: true
123 });
124 }
125});
126
127test("ArtifactStore scans messages by cursor and settle cutoff without rescanning older rows", async () => {
128 const rootDir = mkdtempSync(join(tmpdir(), "artifact-db-message-scan-test-"));
129 const stateDir = join(rootDir, "state");
130 const databasePath = join(stateDir, ARTIFACT_DB_FILENAME);
131 const artifactDir = join(stateDir, ARTIFACTS_DIRNAME);
132 const store = new ArtifactStore({
133 artifactDir,
134 databasePath
135 });
136
137 try {
138 const first = await store.insertMessage({
139 id: "msg_scan_001",
140 observedAt: Date.UTC(2026, 2, 28, 9, 0, 0),
141 platform: "claude",
142 rawText: "first assistant message",
143 role: "assistant"
144 });
145 await store.insertMessage({
146 id: "msg_scan_user",
147 observedAt: Date.UTC(2026, 2, 28, 9, 0, 30),
148 platform: "claude",
149 rawText: "interleaved user message",
150 role: "user"
151 });
152 const second = await store.insertMessage({
153 id: "msg_scan_002",
154 observedAt: Date.UTC(2026, 2, 28, 9, 1, 0),
155 platform: "claude",
156 rawText: "second assistant message",
157 role: "assistant"
158 });
159 const third = await store.insertMessage({
160 id: "msg_scan_003",
161 observedAt: Date.UTC(2026, 2, 28, 9, 2, 0),
162 platform: "claude",
163 rawText: "third assistant message",
164 role: "assistant"
165 });
166
167 assert.deepEqual(
168 await store.scanMessages({
169 limit: 10,
170 observedAtLte: second.observedAt,
171 role: "assistant"
172 }),
173 [first, second]
174 );
175 assert.deepEqual(
176 await store.scanMessages({
177 after: {
178 id: first.id,
179 observedAt: first.observedAt
180 },
181 limit: 10,
182 observedAtLte: third.observedAt,
183 role: "assistant"
184 }),
185 [second, third]
186 );
187 assert.deepEqual(
188 await store.scanMessages({
189 after: {
190 id: second.id,
191 observedAt: second.observedAt
192 },
193 limit: 10,
194 observedAtLte: second.observedAt,
195 role: "assistant"
196 }),
197 []
198 );
199 } finally {
200 store.close();
201 rmSync(rootDir, {
202 force: true,
203 recursive: true
204 });
205 }
206});
207
208test("ArtifactStore persists renewal storage records and enqueues sync payloads", async () => {
209 const rootDir = mkdtempSync(join(tmpdir(), "artifact-db-renewal-test-"));
210 const stateDir = join(rootDir, "state");
211 const databasePath = join(stateDir, ARTIFACT_DB_FILENAME);
212 const artifactDir = join(stateDir, ARTIFACTS_DIRNAME);
213 const store = new ArtifactStore({
214 artifactDir,
215 databasePath
216 });
217 const syncRecords = [];
218 store.setSyncQueue({
219 enqueueSyncRecord(input) {
220 syncRecords.push(input);
221 }
222 });
223
224 try {
225 const message = await store.insertMessage({
226 conversationId: "conv_renew_123",
227 id: "msg_renew_123",
228 observedAt: Date.UTC(2026, 2, 28, 9, 0, 0),
229 platform: "claude",
230 rawText: "续命候选消息",
231 role: "assistant"
232 });
233
234 const localConversation = await store.upsertLocalConversation({
235 automationStatus: "auto",
236 cooldownUntil: Date.UTC(2026, 2, 28, 9, 5, 0),
237 lastMessageAt: message.observedAt,
238 lastMessageId: message.id,
239 localConversationId: "lc_123",
240 platform: "claude",
241 summary: "Initial summary",
242 title: "Renewal thread"
243 });
244 const pausedConversation = await store.upsertLocalConversation({
245 automationStatus: "paused",
246 localConversationId: localConversation.localConversationId,
247 pausedAt: Date.UTC(2026, 2, 28, 9, 6, 0),
248 platform: "claude"
249 });
250
251 const conversationLink = await store.upsertConversationLink({
252 clientId: "firefox-claude",
253 linkId: "link_123",
254 localConversationId: localConversation.localConversationId,
255 observedAt: Date.UTC(2026, 2, 28, 9, 1, 0),
256 pageTitle: "Claude",
257 pageUrl: "https://claude.ai/chat/conv_renew_123",
258 platform: "claude",
259 remoteConversationId: "conv_renew_123",
260 routeParams: { conversationId: "conv_renew_123" },
261 routePath: "/chat/conv_renew_123",
262 routePattern: "/chat/:conversationId",
263 targetId: "firefox-claude",
264 targetKind: "browser.proxy_delivery",
265 targetPayload: { clientId: "firefox-claude" }
266 });
267 const updatedConversationLink = await store.upsertConversationLink({
268 linkId: "link_ignore_new_id",
269 localConversationId: localConversation.localConversationId,
270 observedAt: Date.UTC(2026, 2, 28, 9, 2, 0),
271 pageTitle: "Claude Updated",
272 platform: "claude",
273 remoteConversationId: "conv_renew_123"
274 });
275
276 const dueJob = await store.insertRenewalJob({
277 jobId: "job_123",
278 localConversationId: localConversation.localConversationId,
279 logPath: "logs/renewal/2026-03-28.jsonl",
280 maxAttempts: 5,
281 messageId: message.id,
282 nextAttemptAt: Date.UTC(2026, 2, 28, 9, 3, 0),
283 payload: "[renew] keepalive",
284 payloadKind: "text",
285 targetSnapshot: {
286 clientId: "firefox-claude",
287 pageUrl: "https://claude.ai/chat/conv_renew_123"
288 }
289 });
290 const runningJob = await store.updateRenewalJob({
291 attemptCount: 1,
292 jobId: dueJob.jobId,
293 lastAttemptAt: Date.UTC(2026, 2, 28, 9, 4, 0),
294 nextAttemptAt: null,
295 startedAt: Date.UTC(2026, 2, 28, 9, 4, 0),
296 status: "running"
297 });
298
299 assert.deepEqual(await store.getLocalConversation(localConversation.localConversationId), pausedConversation);
300 assert.deepEqual(await store.getConversationLink(conversationLink.linkId), updatedConversationLink);
301 assert.deepEqual(
302 await store.findConversationLinkByRemoteConversation("claude", "conv_renew_123"),
303 updatedConversationLink
304 );
305 assert.deepEqual(await store.getRenewalJob(dueJob.jobId), runningJob);
306
307 assert.equal(localConversation.automationStatus, "auto");
308 assert.equal(pausedConversation.automationStatus, "paused");
309 assert.equal(pausedConversation.summary, "Initial summary");
310 assert.equal(updatedConversationLink.linkId, "link_123");
311 assert.equal(updatedConversationLink.clientId, "firefox-claude");
312 assert.equal(updatedConversationLink.pageTitle, "Claude Updated");
313 assert.equal(runningJob.status, "running");
314 assert.equal(runningJob.attemptCount, 1);
315 assert.equal(runningJob.nextAttemptAt, null);
316 assert.match(runningJob.targetSnapshot, /firefox-claude/u);
317
318 assert.deepEqual(await store.listLocalConversations({ automationStatus: "paused" }), [pausedConversation]);
319 assert.deepEqual(
320 await store.listConversationLinks({ localConversationId: localConversation.localConversationId }),
321 [updatedConversationLink]
322 );
323 assert.deepEqual(await store.listRenewalJobs({ status: "running" }), [runningJob]);
324 assert.deepEqual(
325 syncRecords.map((record) => [record.tableName, record.operation, record.recordId]),
326 [
327 ["messages", "insert", "msg_renew_123"],
328 ["local_conversations", "insert", "lc_123"],
329 ["local_conversations", "insert", "lc_123"],
330 ["conversation_links", "insert", "link_123"],
331 ["conversation_links", "insert", "link_123"],
332 ["renewal_jobs", "insert", "job_123"],
333 ["renewal_jobs", "update", "job_123"]
334 ]
335 );
336 } finally {
337 store.close();
338 rmSync(rootDir, {
339 force: true,
340 recursive: true
341 });
342 }
343});
344
345test("ArtifactStore persists browser request policy state and enqueues sync payloads", async () => {
346 const rootDir = mkdtempSync(join(tmpdir(), "artifact-db-browser-policy-state-test-"));
347 const stateDir = join(rootDir, "state");
348 const databasePath = join(stateDir, ARTIFACT_DB_FILENAME);
349 const artifactDir = join(stateDir, ARTIFACTS_DIRNAME);
350 const store = new ArtifactStore({
351 artifactDir,
352 databasePath
353 });
354 const syncRecords = [];
355 store.setSyncQueue({
356 enqueueSyncRecord(input) {
357 syncRecords.push(input);
358 }
359 });
360
361 try {
362 const persisted = await store.upsertBrowserRequestPolicyState({
363 stateKey: "global",
364 updatedAt: Date.UTC(2026, 2, 28, 9, 30, 0),
365 valueJson: JSON.stringify({
366 platforms: [
367 {
368 dispatches: [Date.UTC(2026, 2, 28, 9, 29, 0)],
369 lastDispatchedAt: Date.UTC(2026, 2, 28, 9, 29, 0),
370 platform: "claude"
371 }
372 ],
373 targets: [],
374 version: 1
375 })
376 });
377
378 assert.deepEqual(await store.getBrowserRequestPolicyState("global"), persisted);
379 assert.deepEqual(
380 syncRecords.map((record) => [record.tableName, record.operation, record.recordId]),
381 [["browser_request_policy_state", "update", "global"]]
382 );
383 } finally {
384 store.close();
385 rmSync(rootDir, {
386 force: true,
387 recursive: true
388 });
389 }
390});
391
392test("ArtifactStore runtime recovery clears stale execution locks and requeues running renewal jobs", async () => {
393 const rootDir = mkdtempSync(join(tmpdir(), "artifact-db-runtime-recovery-test-"));
394 const stateDir = join(rootDir, "state");
395 const databasePath = join(stateDir, ARTIFACT_DB_FILENAME);
396 const artifactDir = join(stateDir, ARTIFACTS_DIRNAME);
397 const store = new ArtifactStore({
398 artifactDir,
399 databasePath
400 });
401 const syncRecords = [];
402 store.setSyncQueue({
403 enqueueSyncRecord(input) {
404 syncRecords.push(input);
405 }
406 });
407
408 try {
409 const message = await store.insertMessage({
410 conversationId: "conv_recovery",
411 id: "msg_recovery",
412 observedAt: Date.UTC(2026, 2, 28, 10, 0, 0),
413 platform: "claude",
414 rawText: "recovery message",
415 role: "assistant"
416 });
417 await store.upsertLocalConversation({
418 automationStatus: "auto",
419 executionState: "renewal_running",
420 localConversationId: "lc_recovery_busy",
421 platform: "claude",
422 updatedAt: Date.UTC(2026, 2, 28, 10, 1, 0)
423 });
424 await store.upsertLocalConversation({
425 automationStatus: "auto",
426 executionState: "instruction_running",
427 localConversationId: "lc_recovery_instruction",
428 platform: "claude",
429 updatedAt: Date.UTC(2026, 2, 28, 10, 1, 30)
430 });
431 await store.insertRenewalJob({
432 attemptCount: 1,
433 createdAt: Date.UTC(2026, 2, 28, 10, 2, 0),
434 jobId: "job_recovery_running",
435 localConversationId: "lc_recovery_busy",
436 messageId: message.id,
437 nextAttemptAt: null,
438 payload: "[renew]",
439 startedAt: Date.UTC(2026, 2, 28, 10, 2, 0),
440 status: "running",
441 updatedAt: Date.UTC(2026, 2, 28, 10, 2, 0)
442 });
443
444 const recovered = await store.recoverAutomationRuntimeState({
445 now: Date.UTC(2026, 2, 28, 10, 3, 0),
446 renewalRecoveryDelayMs: 45_000
447 });
448
449 const recoveredBusyConversation = await store.getLocalConversation("lc_recovery_busy");
450 const recoveredInstructionConversation = await store.getLocalConversation("lc_recovery_instruction");
451 const recoveredJob = await store.getRenewalJob("job_recovery_running");
452
453 assert.deepEqual(recovered, {
454 recoveredExecutionStateCount: 2,
455 recoveryNextAttemptAt: Date.UTC(2026, 2, 28, 10, 3, 45),
456 requeuedRenewalJobCount: 1
457 });
458 assert.equal(recoveredBusyConversation?.executionState, "idle");
459 assert.equal(recoveredInstructionConversation?.executionState, "idle");
460 assert.equal(recoveredJob?.status, "pending");
461 assert.equal(recoveredJob?.startedAt, null);
462 assert.equal(recoveredJob?.finishedAt, null);
463 assert.equal(recoveredJob?.nextAttemptAt, Date.UTC(2026, 2, 28, 10, 3, 45));
464 assert.deepEqual(
465 syncRecords
466 .filter((record) => record.operation === "update")
467 .map((record) => [record.tableName, record.recordId])
468 .sort(),
469 [
470 ["local_conversations", "lc_recovery_busy"],
471 ["local_conversations", "lc_recovery_instruction"],
472 ["renewal_jobs", "job_recovery_running"]
473 ]
474 );
475 } finally {
476 store.close();
477 rmSync(rootDir, {
478 force: true,
479 recursive: true
480 });
481 }
482});
483
484test("ArtifactStore UPSERT SQL preserves created_at for renewal storage rows on conflict", async () => {
485 const rootDir = mkdtempSync(join(tmpdir(), "artifact-db-created-at-upsert-test-"));
486 const stateDir = join(rootDir, "state");
487 const databasePath = join(stateDir, ARTIFACT_DB_FILENAME);
488 const artifactDir = join(stateDir, ARTIFACTS_DIRNAME);
489 const store = new ArtifactStore({
490 artifactDir,
491 databasePath
492 });
493
494 try {
495 const db = getStoreDb(store);
496 const upsertLocalConversationSql = extractStoreSql("UPSERT_LOCAL_CONVERSATION_SQL");
497 const upsertConversationLinkSql = extractStoreSql("UPSERT_CONVERSATION_LINK_SQL");
498 const upsertRenewalJobSql = extractStoreSql("UPSERT_RENEWAL_JOB_SQL");
499
500 await store.upsertLocalConversation({
501 automationStatus: "manual",
502 createdAt: 100,
503 localConversationId: "lc_created_at",
504 platform: "claude",
505 updatedAt: 110
506 });
507 db.prepare(upsertLocalConversationSql).run(
508 "lc_created_at",
509 "claude",
510 "auto",
511 "auto",
512 null,
513 null,
514 "idle",
515 0,
516 0,
517 0,
518 null,
519 null,
520 "Updated title",
521 "Updated summary",
522 null,
523 null,
524 null,
525 null,
526 999,
527 220
528 );
529
530 const storedConversation = await store.getLocalConversation("lc_created_at");
531 assert.ok(storedConversation);
532 assert.equal(storedConversation.createdAt, 100);
533 assert.equal(storedConversation.updatedAt, 220);
534 assert.equal(storedConversation.automationStatus, "auto");
535 assert.equal(storedConversation.title, "Updated title");
536
537 await store.upsertConversationLink({
538 createdAt: 200,
539 linkId: "link_created_at",
540 localConversationId: "lc_created_at",
541 observedAt: 300,
542 pageTitle: "Before",
543 platform: "claude",
544 remoteConversationId: "conv_created_at",
545 updatedAt: 310
546 });
547 db.prepare(upsertConversationLinkSql).run(
548 "link_created_at",
549 "lc_created_at",
550 "claude",
551 "conv_created_at",
552 "firefox-claude",
553 "https://claude.ai/chat/conv_created_at",
554 "After",
555 "/chat/conv_created_at",
556 "/chat/:conversationId",
557 "{\"conversationId\":\"conv_created_at\"}",
558 "browser.proxy_delivery",
559 "tab:1",
560 "{\"tabId\":1}",
561 0,
562 400,
563 999,
564 410
565 );
566
567 const storedLink = await store.getConversationLink("link_created_at");
568 assert.ok(storedLink);
569 assert.equal(storedLink.createdAt, 200);
570 assert.equal(storedLink.updatedAt, 410);
571 assert.equal(storedLink.pageTitle, "After");
572 assert.equal(storedLink.isActive, false);
573
574 const message = await store.insertMessage({
575 conversationId: "conv_created_at",
576 createdAt: 500,
577 id: "msg_created_at",
578 observedAt: 500,
579 platform: "claude",
580 rawText: "续命消息",
581 role: "assistant"
582 });
583 await store.insertRenewalJob({
584 createdAt: 600,
585 jobId: "job_created_at",
586 localConversationId: "lc_created_at",
587 messageId: message.id,
588 payload: "[renew] ping",
589 targetSnapshot: {
590 clientId: "firefox-claude"
591 },
592 updatedAt: 610
593 });
594 db.prepare(upsertRenewalJobSql).run(
595 "job_created_at",
596 "lc_created_at",
597 message.id,
598 "running",
599 "[renew] pong",
600 "text",
601 "{\"clientId\":\"firefox-claude\",\"targetId\":\"tab:1\"}",
602 1,
603 3,
604 null,
605 700,
606 "temporary failure",
607 "logs/renewal/created-at.jsonl",
608 700,
609 null,
610 999,
611 710
612 );
613
614 const storedJob = await store.getRenewalJob("job_created_at");
615 assert.ok(storedJob);
616 assert.equal(storedJob.createdAt, 600);
617 assert.equal(storedJob.updatedAt, 710);
618 assert.equal(storedJob.status, "running");
619 assert.equal(storedJob.payload, "[renew] pong");
620 assert.match(storedJob.targetSnapshot, /tab:1/u);
621 } finally {
622 store.close();
623 rmSync(rootDir, {
624 force: true,
625 recursive: true
626 });
627 }
628});
629
630test("ArtifactStore findConversationLinkByRemoteConversation only returns active links", async () => {
631 const rootDir = mkdtempSync(join(tmpdir(), "artifact-db-renewal-active-link-test-"));
632 const stateDir = join(rootDir, "state");
633 const databasePath = join(stateDir, ARTIFACT_DB_FILENAME);
634 const artifactDir = join(stateDir, ARTIFACTS_DIRNAME);
635 const store = new ArtifactStore({
636 artifactDir,
637 databasePath
638 });
639
640 try {
641 await store.upsertLocalConversation({
642 localConversationId: "lc_active_link_123",
643 platform: "chatgpt"
644 });
645 const activeLink = await store.upsertConversationLink({
646 linkId: "link_active_link_123",
647 localConversationId: "lc_active_link_123",
648 observedAt: Date.UTC(2026, 2, 30, 8, 0, 0),
649 platform: "chatgpt",
650 remoteConversationId: "conv_active_link_123"
651 });
652
653 assert.deepEqual(
654 await store.findConversationLinkByRemoteConversation("chatgpt", "conv_active_link_123"),
655 activeLink
656 );
657
658 const inactiveLink = await store.upsertConversationLink({
659 isActive: false,
660 linkId: activeLink.linkId,
661 localConversationId: activeLink.localConversationId,
662 observedAt: activeLink.observedAt,
663 platform: activeLink.platform,
664 updatedAt: Date.UTC(2026, 2, 30, 8, 1, 0)
665 });
666
667 assert.deepEqual(
668 await store.listConversationLinks({
669 isActive: false,
670 platform: "chatgpt",
671 remoteConversationId: "conv_active_link_123"
672 }),
673 [inactiveLink]
674 );
675 assert.equal(
676 await store.findConversationLinkByRemoteConversation("chatgpt", "conv_active_link_123"),
677 null
678 );
679 } finally {
680 store.close();
681 rmSync(rootDir, {
682 force: true,
683 recursive: true
684 });
685 }
686});
687
688test("ArtifactStore listConversationLinks supports exact renewal identity filters", async () => {
689 const rootDir = mkdtempSync(join(tmpdir(), "artifact-db-link-filter-test-"));
690 const stateDir = join(rootDir, "state");
691 const databasePath = join(stateDir, ARTIFACT_DB_FILENAME);
692 const artifactDir = join(stateDir, ARTIFACTS_DIRNAME);
693 const store = new ArtifactStore({
694 artifactDir,
695 databasePath
696 });
697 const observedAt = Date.UTC(2026, 2, 30, 8, 5, 0);
698
699 try {
700 await store.upsertLocalConversation({
701 localConversationId: "lc_filter_1",
702 platform: "chatgpt"
703 });
704 await store.upsertLocalConversation({
705 localConversationId: "lc_filter_2",
706 platform: "chatgpt"
707 });
708
709 const matchingLink = await store.upsertConversationLink({
710 clientId: "firefox-chatgpt",
711 linkId: "link_filter_match",
712 localConversationId: "lc_filter_1",
713 observedAt,
714 pageTitle: "Target Thread",
715 pageUrl: "https://chatgpt.com/c/conv-filter-match",
716 platform: "chatgpt",
717 remoteConversationId: "conv-filter-match",
718 routePath: "/c/conv-filter-match",
719 routePattern: "/c/:conversationId",
720 targetId: "tab:7",
721 updatedAt: observedAt
722 });
723 await store.upsertConversationLink({
724 clientId: "firefox-chatgpt",
725 linkId: "link_filter_other",
726 localConversationId: "lc_filter_2",
727 observedAt: observedAt + 1_000,
728 pageTitle: "Other Thread",
729 pageUrl: "https://chatgpt.com/c/conv-filter-other",
730 platform: "chatgpt",
731 remoteConversationId: "conv-filter-other",
732 routePath: "/c/conv-filter-other",
733 routePattern: "/c/:conversationId",
734 targetId: "tab:8",
735 updatedAt: observedAt + 1_000
736 });
737
738 assert.deepEqual(
739 await store.listConversationLinks({ pageTitle: "Target Thread", platform: "chatgpt" }),
740 [matchingLink]
741 );
742 assert.deepEqual(
743 await store.listConversationLinks({ pageUrl: "https://chatgpt.com/c/conv-filter-match", platform: "chatgpt" }),
744 [matchingLink]
745 );
746 assert.deepEqual(
747 await store.listConversationLinks({ platform: "chatgpt", routePath: "/c/conv-filter-match" }),
748 [matchingLink]
749 );
750 assert.deepEqual(
751 await store.listConversationLinks({ platform: "chatgpt", targetId: "tab:7" }),
752 [matchingLink]
753 );
754 } finally {
755 store.close();
756 rmSync(rootDir, {
757 force: true,
758 recursive: true
759 });
760 }
761});
762
763test("ArtifactStore reuses the same null-remote conversation link for repeated route upserts", async () => {
764 const rootDir = mkdtempSync(join(tmpdir(), "artifact-db-null-remote-upsert-test-"));
765 const stateDir = join(rootDir, "state");
766 const databasePath = join(stateDir, ARTIFACT_DB_FILENAME);
767 const artifactDir = join(stateDir, ARTIFACTS_DIRNAME);
768 const store = new ArtifactStore({
769 artifactDir,
770 databasePath
771 });
772 const firstObservedAt = Date.UTC(2026, 2, 30, 18, 0, 0);
773
774 try {
775 await store.upsertLocalConversation({
776 localConversationId: "lc_null_remote_1",
777 platform: "gemini"
778 });
779 await store.upsertLocalConversation({
780 localConversationId: "lc_null_remote_2",
781 platform: "gemini"
782 });
783
784 const firstLink = await store.upsertConversationLink({
785 clientId: "firefox-gemini",
786 linkId: "link_null_remote_1",
787 localConversationId: "lc_null_remote_1",
788 observedAt: firstObservedAt,
789 pageTitle: "Gemini",
790 platform: "gemini",
791 routePath: "/app",
792 routePattern: "/app",
793 targetKind: "browser.proxy_delivery"
794 });
795 const secondLink = await store.upsertConversationLink({
796 clientId: "firefox-gemini",
797 linkId: "link_null_remote_2",
798 localConversationId: "lc_null_remote_2",
799 observedAt: firstObservedAt + 60_000,
800 pageTitle: "Gemini Updated",
801 platform: "gemini",
802 routePath: "/app",
803 routePattern: "/app",
804 targetKind: "browser.proxy_delivery"
805 });
806
807 assert.equal(firstLink.linkId, "link_null_remote_1");
808 assert.equal(secondLink.linkId, "link_null_remote_1");
809 assert.equal(secondLink.localConversationId, "lc_null_remote_2");
810 assert.equal(secondLink.pageTitle, "Gemini Updated");
811 assert.equal(
812 (await store.listConversationLinks({
813 localConversationId: "lc_null_remote_1"
814 })).length,
815 0
816 );
817 assert.deepEqual(await store.listConversationLinks({ platform: "gemini" }), [secondLink]);
818 } finally {
819 store.close();
820 rmSync(rootDir, {
821 force: true,
822 recursive: true
823 });
824 }
825});
826
827test("ArtifactStore migrates duplicate null-remote conversation links into one canonical route record", async () => {
828 const rootDir = mkdtempSync(join(tmpdir(), "artifact-db-null-remote-migration-test-"));
829 const stateDir = join(rootDir, "state");
830 mkdirSync(stateDir, {
831 recursive: true
832 });
833 const databasePath = join(stateDir, ARTIFACT_DB_FILENAME);
834 const artifactDir = join(stateDir, ARTIFACTS_DIRNAME);
835 const bootstrapDb = new DatabaseSync(databasePath);
836 let bootstrapClosed = false;
837
838 try {
839 bootstrapDb.exec(`
840 CREATE TABLE IF NOT EXISTS local_conversations (
841 local_conversation_id TEXT PRIMARY KEY,
842 platform TEXT NOT NULL,
843 automation_status TEXT NOT NULL DEFAULT 'manual',
844 title TEXT,
845 summary TEXT,
846 last_message_id TEXT,
847 last_message_at INTEGER,
848 cooldown_until INTEGER,
849 paused_at INTEGER,
850 created_at INTEGER NOT NULL,
851 updated_at INTEGER NOT NULL
852 );
853
854 CREATE TABLE IF NOT EXISTS conversation_links (
855 link_id TEXT PRIMARY KEY,
856 local_conversation_id TEXT NOT NULL,
857 platform TEXT NOT NULL,
858 remote_conversation_id TEXT,
859 client_id TEXT,
860 page_url TEXT,
861 page_title TEXT,
862 route_path TEXT,
863 route_pattern TEXT,
864 route_params TEXT,
865 target_kind TEXT,
866 target_id TEXT,
867 target_payload TEXT,
868 is_active INTEGER NOT NULL DEFAULT 1,
869 observed_at INTEGER NOT NULL,
870 created_at INTEGER NOT NULL,
871 updated_at INTEGER NOT NULL
872 );
873
874 CREATE UNIQUE INDEX IF NOT EXISTS idx_conversation_links_remote
875 ON conversation_links(platform, remote_conversation_id);
876 `);
877 bootstrapDb.exec(`
878 INSERT INTO local_conversations (
879 local_conversation_id,
880 platform,
881 automation_status,
882 created_at,
883 updated_at
884 ) VALUES
885 ('lc_migration_null_remote_1', 'gemini', 'manual', 1000, 1000),
886 ('lc_migration_null_remote_2', 'gemini', 'manual', 2000, 2000);
887
888 INSERT INTO conversation_links (
889 link_id,
890 local_conversation_id,
891 platform,
892 remote_conversation_id,
893 client_id,
894 page_url,
895 page_title,
896 route_path,
897 route_pattern,
898 route_params,
899 target_kind,
900 target_id,
901 target_payload,
902 is_active,
903 observed_at,
904 created_at,
905 updated_at
906 ) VALUES
907 (
908 'link_migration_null_remote_older',
909 'lc_migration_null_remote_1',
910 'gemini',
911 NULL,
912 'firefox-gemini',
913 'https://gemini.google.com/app?first=1',
914 'Gemini Older',
915 '/app',
916 '/app',
917 NULL,
918 'browser.proxy_delivery',
919 'tab:1',
920 NULL,
921 1,
922 1000,
923 1000,
924 1000
925 ),
926 (
927 'link_migration_null_remote_newer',
928 'lc_migration_null_remote_2',
929 'gemini',
930 NULL,
931 'firefox-gemini',
932 'https://gemini.google.com/app?second=1',
933 'Gemini Newer',
934 '/app',
935 '/app',
936 NULL,
937 'browser.proxy_delivery',
938 'tab:2',
939 NULL,
940 1,
941 2000,
942 2000,
943 2000
944 );
945 `);
946 bootstrapDb.close();
947 bootstrapClosed = true;
948
949 const store = new ArtifactStore({
950 artifactDir,
951 databasePath
952 });
953
954 try {
955 const links = await store.listConversationLinks({ platform: "gemini" });
956
957 assert.equal(links.length, 1);
958 assert.equal(links[0].linkId, "link_migration_null_remote_newer");
959 assert.equal(links[0].localConversationId, "lc_migration_null_remote_2");
960 assert.equal(links[0].createdAt, 1000);
961 assert.equal(links[0].routePath, "/app");
962 assert.equal(links[0].pageTitle, "Gemini Newer");
963
964 const reused = await store.upsertConversationLink({
965 clientId: "firefox-gemini",
966 linkId: "link_migration_null_remote_reused",
967 localConversationId: "lc_migration_null_remote_2",
968 observedAt: 3000,
969 platform: "gemini",
970 routePath: "/app",
971 routePattern: "/app"
972 });
973
974 assert.equal(reused.linkId, "link_migration_null_remote_newer");
975 assert.equal((await store.listConversationLinks({ platform: "gemini" })).length, 1);
976 } finally {
977 store.close();
978 }
979 } finally {
980 if (bootstrapClosed === false) {
981 bootstrapDb.close();
982 }
983 rmSync(rootDir, {
984 force: true,
985 recursive: true
986 });
987 }
988});