polygram 0.21.0 → 0.23.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.
Files changed (48) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +5 -3
  3. package/config.example.json +23 -1
  4. package/lib/codex/binary.js +287 -0
  5. package/lib/codex/delivery-finalizer.js +97 -0
  6. package/lib/codex/diagnostics.js +483 -0
  7. package/lib/codex/event-normalizer.js +50 -0
  8. package/lib/codex/host-identity.js +103 -0
  9. package/lib/codex/model-catalog.js +359 -0
  10. package/lib/codex/retention-scheduler.js +59 -0
  11. package/lib/codex/runtime-controller.js +1292 -0
  12. package/lib/codex/runtime-profile.js +888 -0
  13. package/lib/codex/spawn-context.js +87 -0
  14. package/lib/db/auto-resume.js +77 -9
  15. package/lib/db/codex-reconciliation.js +490 -0
  16. package/lib/db/codex-retention.js +139 -0
  17. package/lib/db/sessions.js +102 -1
  18. package/lib/db.js +3418 -19
  19. package/lib/error/classify.js +140 -0
  20. package/lib/handlers/abort.js +143 -5
  21. package/lib/handlers/autosteer.js +256 -1
  22. package/lib/handlers/codex-reconciliation.js +230 -0
  23. package/lib/handlers/config-callback.js +324 -89
  24. package/lib/handlers/config-ui.js +401 -39
  25. package/lib/handlers/dispatcher.js +71 -8
  26. package/lib/handlers/edit-correction.js +11 -1
  27. package/lib/handlers/edit-redelivery.js +230 -4
  28. package/lib/handlers/gate-inbound.js +7 -0
  29. package/lib/handlers/replay-disposition.js +133 -11
  30. package/lib/handlers/slash-commands.js +443 -37
  31. package/lib/ipc/client.js +22 -12
  32. package/lib/ipc/runtime-directory.js +208 -0
  33. package/lib/ipc/server.js +18 -12
  34. package/lib/model-costs.js +4 -3
  35. package/lib/pm-interface.js +33 -0
  36. package/lib/prompt.js +15 -9
  37. package/lib/rewind/rewind.js +21 -1
  38. package/lib/runtime-config.js +605 -0
  39. package/lib/sdk/build-options.js +4 -1
  40. package/lib/sdk/callbacks.js +46 -3
  41. package/lib/util/claude-session-jsonl.js +3 -2
  42. package/migrations/015-agent-runtime-sessions.sql +50 -0
  43. package/migrations/016-agent-turn-attempts.sql +211 -0
  44. package/migrations/017-config-runtime-changes.sql +46 -0
  45. package/ops/README.md +57 -0
  46. package/package.json +2 -2
  47. package/polygram.js +697 -48
  48. package/scripts/doctor.js +37 -8
package/lib/db.js CHANGED
@@ -7,6 +7,16 @@
7
7
  const fs = require('fs');
8
8
  const path = require('path');
9
9
  const Database = require('better-sqlite3');
10
+ const {
11
+ pruneCodexOperationalData,
12
+ } = require('./db/codex-retention');
13
+ const {
14
+ codexError,
15
+ listUnresolvedCodexAttempts,
16
+ requiredString: requiredCodexString,
17
+ reconcileCodexAttempt,
18
+ reconstructCodexRecovery,
19
+ } = require('./db/codex-reconciliation');
10
20
 
11
21
  // 0.8.0 Phase 1: bumped from 9 → 10. Adds migration
12
22
  // 010-tool-use-id.sql (pending_approvals.tool_use_id column for the
@@ -26,7 +36,7 @@ const Database = require('better-sqlite3');
26
36
  //
27
37
  // 0.15: bumped 13 → 14. Adds migration 014-secret-redactions.sql
28
38
  // (secret_redactions audit table + messages.secret_scanned_at).
29
- const SCHEMA_VERSION = 14;
39
+ const SCHEMA_VERSION = 17;
30
40
 
31
41
  // Sentinel `error` value for outbound rows whose API call may or may not
32
42
  // have reached Telegram. markStalePending writes it; hasOutboundReplyTo
@@ -36,6 +46,78 @@ const SCHEMA_VERSION = 14;
36
46
  // reply on boot).
37
47
  const CRASHED_MID_SEND = 'crashed-mid-send';
38
48
 
49
+ const CLAUDE_INLINE_NAMESPACE = 'claude:inline';
50
+ const CLAUDE_CHANNELS_NAMESPACE = 'claude:channels';
51
+ const CODEX_APP_SERVER_NAMESPACE = 'codex:app-server';
52
+ const PROVIDER_NAMESPACES = new Set([
53
+ CLAUDE_INLINE_NAMESPACE,
54
+ CLAUDE_CHANNELS_NAMESPACE,
55
+ CODEX_APP_SERVER_NAMESPACE,
56
+ ]);
57
+
58
+ function claudeNamespaceForStoredBackend(backend) {
59
+ return backend === 'cli' || backend === 'channels'
60
+ ? CLAUDE_CHANNELS_NAMESPACE
61
+ : CLAUDE_INLINE_NAMESPACE;
62
+ }
63
+
64
+ function requiredTimestamp(value, label) {
65
+ if (!Number.isSafeInteger(value) || value < 0) {
66
+ throw codexError(
67
+ `Codex ${label} must be a non-negative safe integer`,
68
+ 'CODEX_PERSISTENCE_INPUT_INVALID',
69
+ );
70
+ }
71
+ return value;
72
+ }
73
+
74
+ function optionalCodexString(value, label) {
75
+ if (value == null) return null;
76
+ return requiredCodexString(String(value), label);
77
+ }
78
+
79
+ function optionalCodexRequestId(value) {
80
+ if (value == null) return null;
81
+ if (typeof value === 'string') {
82
+ return `string:${requiredCodexString(value, 'request ID')}`;
83
+ }
84
+ if (Number.isSafeInteger(value)) {
85
+ return `number:${value}`;
86
+ }
87
+ throw codexError(
88
+ 'Codex request ID must be a bounded string or safe integer',
89
+ 'CODEX_PERSISTENCE_INPUT_INVALID',
90
+ );
91
+ }
92
+
93
+ function assertExactKeys(row, allowed, code, label) {
94
+ for (const key of Object.keys(row ?? {})) {
95
+ if (!allowed.has(key)) {
96
+ throw codexError(
97
+ `Codex ${label} rejected field ${key}`,
98
+ code,
99
+ );
100
+ }
101
+ }
102
+ }
103
+
104
+ function configChangeValue(value, { nullable, label }) {
105
+ if (value == null) {
106
+ if (nullable) return null;
107
+ const error = new TypeError(`config change ${label} is required`);
108
+ error.code = 'CONFIG_CHANGE_VALUE_INVALID';
109
+ throw error;
110
+ }
111
+ if (typeof value === 'string') return value;
112
+ if (typeof value === 'boolean') return String(value);
113
+ if (typeof value === 'number' && Number.isFinite(value)) return String(value);
114
+ const error = new TypeError(
115
+ `config change ${label} must be a string, boolean, or finite number`,
116
+ );
117
+ error.code = 'CONFIG_CHANGE_VALUE_INVALID';
118
+ throw error;
119
+ }
120
+
39
121
  function open(dbPath) {
40
122
  const db = new Database(dbPath);
41
123
  db.pragma('journal_mode = WAL');
@@ -147,6 +229,195 @@ function wrap(db) {
147
229
  const clearSessionIdStmt = db.prepare(`DELETE FROM sessions WHERE session_key = ?`);
148
230
  const setSessionBackendStmt = db.prepare(`UPDATE sessions SET pm_backend = ? WHERE session_key = ?`);
149
231
 
232
+ const upsertProviderSessionStmt = db.prepare(`
233
+ INSERT INTO agent_runtime_sessions (
234
+ session_key, namespace, provider, provider_session_id,
235
+ app_server_session_id, agent, cwd, model, effort, pm_backend,
236
+ created_ts, last_active_ts
237
+ ) VALUES (
238
+ @session_key, @namespace, @provider, @provider_session_id,
239
+ @app_server_session_id, @agent, @cwd, @model, @effort, @pm_backend,
240
+ @ts, @ts
241
+ )
242
+ ON CONFLICT(session_key, namespace) DO UPDATE SET
243
+ provider = excluded.provider,
244
+ provider_session_id = excluded.provider_session_id,
245
+ app_server_session_id = excluded.app_server_session_id,
246
+ agent = excluded.agent,
247
+ cwd = excluded.cwd,
248
+ model = excluded.model,
249
+ effort = excluded.effort,
250
+ pm_backend = excluded.pm_backend,
251
+ last_active_ts = excluded.last_active_ts
252
+ `);
253
+ const getProviderSessionStmt = db.prepare(`
254
+ SELECT *
255
+ FROM agent_runtime_sessions
256
+ WHERE session_key = ? AND namespace = ?
257
+ `);
258
+ const clearProviderSessionStmt = db.prepare(`
259
+ DELETE FROM agent_runtime_sessions
260
+ WHERE session_key = ? AND namespace = ?
261
+ `);
262
+ const touchProviderSessionStmt = db.prepare(`
263
+ UPDATE agent_runtime_sessions
264
+ SET last_active_ts = ?
265
+ WHERE session_key = ? AND namespace = ?
266
+ `);
267
+ const setProviderBackendStmt = db.prepare(`
268
+ UPDATE agent_runtime_sessions
269
+ SET pm_backend = ?
270
+ WHERE session_key = ? AND namespace = ?
271
+ `);
272
+
273
+ function providerSessionParams(row) {
274
+ if (!row || !PROVIDER_NAMESPACES.has(row.namespace)) {
275
+ throw new TypeError('provider session namespace is invalid');
276
+ }
277
+ const provider = row.provider;
278
+ if (
279
+ (provider === 'claude' && !row.namespace.startsWith('claude:'))
280
+ || (provider === 'codex' && row.namespace !== CODEX_APP_SERVER_NAMESPACE)
281
+ || !['claude', 'codex'].includes(provider)
282
+ ) {
283
+ throw new TypeError('provider session namespace/provider mismatch');
284
+ }
285
+ if (
286
+ typeof row.provider_session_id !== 'string'
287
+ || row.provider_session_id.length === 0
288
+ ) {
289
+ throw new TypeError('provider session ID must be a non-empty string');
290
+ }
291
+ return {
292
+ session_key: String(row.session_key),
293
+ namespace: row.namespace,
294
+ provider,
295
+ provider_session_id: row.provider_session_id,
296
+ app_server_session_id: row.app_server_session_id || null,
297
+ agent: row.agent || null,
298
+ cwd: row.cwd || null,
299
+ model: row.model || null,
300
+ effort: row.effort || null,
301
+ pm_backend: row.pm_backend || null,
302
+ ts: row.ts || Date.now(),
303
+ };
304
+ }
305
+
306
+ function legacySessionParams(row) {
307
+ return {
308
+ session_key: row.session_key,
309
+ chat_id: String(row.chat_id),
310
+ thread_id: row.thread_id ? String(row.thread_id) : null,
311
+ claude_session_id: row.claude_session_id,
312
+ agent: row.agent || null,
313
+ cwd: row.cwd || null,
314
+ model: row.model || null,
315
+ effort: row.effort || null,
316
+ pm_backend: row.pm_backend || 'sdk',
317
+ ts: row.ts || Date.now(),
318
+ };
319
+ }
320
+
321
+ const upsertLegacyAndProviderSession = db.transaction((row) => {
322
+ const legacy = legacySessionParams(row);
323
+ const result = upsertSessionStmt.run(legacy);
324
+ upsertProviderSessionStmt.run(providerSessionParams({
325
+ ...row,
326
+ session_key: legacy.session_key,
327
+ namespace: claudeNamespaceForStoredBackend(legacy.pm_backend),
328
+ provider: 'claude',
329
+ provider_session_id: legacy.claude_session_id,
330
+ pm_backend: legacy.pm_backend,
331
+ ts: legacy.ts,
332
+ }));
333
+ return result;
334
+ });
335
+
336
+ const upsertProviderAndLegacySession = db.transaction((row) => {
337
+ const provider = providerSessionParams(row);
338
+ const result = upsertProviderSessionStmt.run(provider);
339
+ if (provider.provider === 'claude') {
340
+ upsertSessionStmt.run(legacySessionParams({
341
+ ...row,
342
+ session_key: provider.session_key,
343
+ chat_id: row.chat_id ?? provider.session_key.split(':')[0],
344
+ claude_session_id: provider.provider_session_id,
345
+ pm_backend: provider.pm_backend
346
+ ?? (
347
+ provider.namespace === CLAUDE_CHANNELS_NAMESPACE
348
+ ? 'cli'
349
+ : 'sdk'
350
+ ),
351
+ ts: provider.ts,
352
+ }));
353
+ }
354
+ return result;
355
+ });
356
+
357
+ const clearLegacySession = db.transaction((sessionKey) => {
358
+ const row = getSessionStmt.get(sessionKey);
359
+ const result = clearSessionIdStmt.run(sessionKey);
360
+ if (row) {
361
+ clearProviderSessionStmt.run(
362
+ sessionKey,
363
+ claudeNamespaceForStoredBackend(row.pm_backend),
364
+ );
365
+ }
366
+ return result;
367
+ });
368
+
369
+ const clearOneProviderSession = db.transaction((sessionKey, namespace) => {
370
+ const row = getProviderSessionStmt.get(sessionKey, namespace);
371
+ const result = clearProviderSessionStmt.run(sessionKey, namespace);
372
+ if (row?.provider === 'claude') {
373
+ const legacy = getSessionStmt.get(sessionKey);
374
+ if (
375
+ legacy
376
+ && claudeNamespaceForStoredBackend(legacy.pm_backend) === namespace
377
+ ) {
378
+ clearSessionIdStmt.run(sessionKey);
379
+ }
380
+ }
381
+ return result;
382
+ });
383
+
384
+ const readProviderSession = db.transaction((sessionKey, namespace) => {
385
+ if (
386
+ namespace === CLAUDE_INLINE_NAMESPACE
387
+ || namespace === CLAUDE_CHANNELS_NAMESPACE
388
+ ) {
389
+ const legacy = getSessionStmt.get(sessionKey);
390
+ if (
391
+ legacy
392
+ && claudeNamespaceForStoredBackend(legacy.pm_backend) === namespace
393
+ ) {
394
+ const namespaced = getProviderSessionStmt.get(sessionKey, namespace);
395
+ if (
396
+ !namespaced
397
+ || legacy.last_active_ts > namespaced.last_active_ts
398
+ || (
399
+ legacy.last_active_ts === namespaced.last_active_ts
400
+ && legacy.claude_session_id !== namespaced.provider_session_id
401
+ )
402
+ ) {
403
+ upsertProviderSessionStmt.run(providerSessionParams({
404
+ session_key: legacy.session_key,
405
+ namespace,
406
+ provider: 'claude',
407
+ provider_session_id: legacy.claude_session_id,
408
+ agent: legacy.agent,
409
+ cwd: legacy.cwd,
410
+ model: legacy.model,
411
+ effort: legacy.effort,
412
+ pm_backend: legacy.pm_backend,
413
+ ts: legacy.last_active_ts,
414
+ }));
415
+ }
416
+ }
417
+ }
418
+ return getProviderSessionStmt.get(sessionKey, namespace);
419
+ });
420
+
150
421
  const getMessageStmt = db.prepare(`
151
422
  SELECT * FROM messages WHERE chat_id = ? AND msg_id = ?
152
423
  ORDER BY id DESC LIMIT 1
@@ -272,6 +543,3026 @@ function wrap(db) {
272
543
  return { replayMarked: txn() };
273
544
  }
274
545
 
546
+ function assertCodexGenerationIdentity(generationId, stableHostId, bootSessionId) {
547
+ const generation = db.prepare(`
548
+ SELECT * FROM codex_generations WHERE generation_id = ?
549
+ `).get(generationId);
550
+ if (!generation) {
551
+ throw codexError(
552
+ 'Codex generation does not exist',
553
+ 'CODEX_GENERATION_NOT_FOUND',
554
+ );
555
+ }
556
+ if (
557
+ generation.stable_host_id !== stableHostId
558
+ || generation.boot_session_id !== bootSessionId
559
+ ) {
560
+ throw codexError(
561
+ 'Codex generation identity does not match the durable owner',
562
+ 'CODEX_GENERATION_IDENTITY_MISMATCH',
563
+ );
564
+ }
565
+ return generation;
566
+ }
567
+
568
+ function createCodexGeneration(input) {
569
+ const generationId = requiredCodexString(
570
+ input?.generation_id,
571
+ 'generation ID',
572
+ );
573
+ const sessionKey = requiredCodexString(input?.session_key, 'session key');
574
+ const stableHostId = requiredCodexString(
575
+ input?.stable_host_id,
576
+ 'stable host identity',
577
+ );
578
+ const bootSessionId = requiredCodexString(
579
+ input?.boot_session_id,
580
+ 'boot-session identity',
581
+ );
582
+ const ts = requiredTimestamp(input?.ts ?? Date.now(), 'generation timestamp');
583
+ const threadId = optionalCodexString(input?.thread_id, 'thread ID');
584
+ const appServerSessionId = optionalCodexString(
585
+ input?.app_server_session_id,
586
+ 'app-server session ID',
587
+ );
588
+
589
+ return db.transaction(() => {
590
+ const identity = db.prepare(`
591
+ SELECT stable_host_id, last_boot_session_id
592
+ FROM codex_runtime_identity
593
+ WHERE singleton = 1
594
+ `).get();
595
+ if (!identity) {
596
+ const hasState = db.prepare(`
597
+ SELECT (
598
+ (SELECT COUNT(*) FROM codex_generations)
599
+ + (SELECT COUNT(*) FROM codex_daemon_lease)
600
+ ) AS count
601
+ `).get().count;
602
+ if (hasState) {
603
+ throw codexError(
604
+ 'Codex persisted identity is missing',
605
+ 'CODEX_PERSISTED_IDENTITY_MISSING',
606
+ );
607
+ }
608
+ db.prepare(`
609
+ INSERT INTO codex_runtime_identity (
610
+ singleton, stable_host_id, last_boot_session_id,
611
+ established_ts, updated_ts
612
+ ) VALUES (1, ?, ?, ?, ?)
613
+ `).run(stableHostId, bootSessionId, ts, ts);
614
+ } else if (identity.stable_host_id !== stableHostId) {
615
+ throw codexError(
616
+ 'Codex database belongs to another stable host',
617
+ 'CODEX_STABLE_HOST_MISMATCH',
618
+ );
619
+ } else if (identity.last_boot_session_id !== bootSessionId) {
620
+ throw codexError(
621
+ 'Codex boot identity must be reconstructed before generation creation',
622
+ 'CODEX_BOOT_RECONSTRUCTION_REQUIRED',
623
+ );
624
+ }
625
+ return db.prepare(`
626
+ INSERT INTO codex_generations (
627
+ generation_id, session_key, thread_id, app_server_session_id,
628
+ stable_host_id, boot_session_id, state,
629
+ created_ts, updated_ts
630
+ ) VALUES (?, ?, ?, ?, ?, ?, 'created', ?, ?)
631
+ `).run(
632
+ generationId,
633
+ sessionKey,
634
+ threadId,
635
+ appServerSessionId,
636
+ stableHostId,
637
+ bootSessionId,
638
+ ts,
639
+ ts,
640
+ );
641
+ })();
642
+ }
643
+
644
+ const checkpointDetailKeys = [
645
+ 'method',
646
+ 'outcome',
647
+ 'reason',
648
+ 'errorCode',
649
+ 'source',
650
+ 'clientUserMessageId',
651
+ 'terminalStatus',
652
+ 'itemType',
653
+ 'deltaBytes',
654
+ 'resumed',
655
+ 'model',
656
+ 'effort',
657
+ 'willRetry',
658
+ 'deliveryState',
659
+ 'cancellationCode',
660
+ 'statusType',
661
+ ];
662
+ const codexCheckpointKinds = new Set([
663
+ 'request-prepared',
664
+ 'request-write-attempted',
665
+ 'request-response-observed',
666
+ 'thread-initialized',
667
+ 'thread-status-changed',
668
+ 'turn-accepted',
669
+ 'turn-steer-accepted',
670
+ 'turn-error-observed',
671
+ 'turn-started',
672
+ 'item-delta-observed',
673
+ 'item-started',
674
+ 'item-completed',
675
+ 'turn-terminal',
676
+ 'active-start-cancelled',
677
+ 'queued-send-cancelled',
678
+ 'stop-background-cleanup-reused',
679
+ 'stop-terminal-reconciled',
680
+ 'stop-clean-accepted',
681
+ 'stop-empty-registry-observed',
682
+ 'background-terminal-reconciled',
683
+ 'background-clean-accepted',
684
+ 'background-empty-registry-observed',
685
+ 'failed-ambiguous-entered',
686
+ 'containment-entered',
687
+ 'telegram-delivery-settled',
688
+ 'telegram-delivery-failed',
689
+ ]);
690
+
691
+ function checkpointDetail(input) {
692
+ const detail = {};
693
+ for (const key of checkpointDetailKeys) {
694
+ const value = input[key];
695
+ if (
696
+ value == null
697
+ || typeof value === 'string'
698
+ || typeof value === 'boolean'
699
+ || Number.isSafeInteger(value)
700
+ ) {
701
+ if (value != null) detail[key] = value;
702
+ }
703
+ }
704
+ return Object.keys(detail).length > 0 ? JSON.stringify(detail) : null;
705
+ }
706
+
707
+ function requireCodexAttempt(attemptId, generationId) {
708
+ const attempt = db.prepare(`
709
+ SELECT * FROM codex_turn_attempts WHERE attempt_id = ?
710
+ `).get(attemptId);
711
+ if (!attempt || attempt.generation_id !== generationId) {
712
+ throw codexError(
713
+ 'Codex checkpoint attempt does not belong to the generation',
714
+ 'CODEX_ATTEMPT_NOT_FOUND',
715
+ );
716
+ }
717
+ return attempt;
718
+ }
719
+
720
+ function markGenerationAttemptsAmbiguous(generationId, ts) {
721
+ const result = db.prepare(`
722
+ UPDATE codex_turn_attempts
723
+ SET recovery_state = 'ambiguous',
724
+ ambiguous_ts = COALESCE(ambiguous_ts, ?),
725
+ updated_ts = ?
726
+ WHERE generation_id = ?
727
+ AND delivery_state != 'prepared'
728
+ AND recovery_state NOT IN ('settled', 'cancelled')
729
+ `).run(ts, ts, generationId);
730
+ db.prepare(`
731
+ UPDATE codex_linked_inputs
732
+ SET state = 'ambiguous', settled_ts = ?
733
+ WHERE generation_id = ?
734
+ AND state = 'linked'
735
+ `).run(ts, generationId);
736
+ db.prepare(`
737
+ UPDATE codex_dispatch_reservations
738
+ SET state = 'ambiguous',
739
+ updated_ts = ?,
740
+ settled_ts = NULL
741
+ WHERE generation_id = ?
742
+ AND state IN ('reserved', 'steer-accepted', 'queue-authorized')
743
+ `).run(ts, generationId);
744
+ db.prepare(`
745
+ UPDATE messages
746
+ SET handler_status = 'codex-ambiguous'
747
+ WHERE direction = 'in'
748
+ AND handler_status IN (
749
+ 'received', 'dispatched', 'processing', 'replay-pending'
750
+ )
751
+ AND EXISTS (
752
+ SELECT 1
753
+ FROM codex_dispatch_reservations reservation
754
+ WHERE reservation.generation_id = ?
755
+ AND reservation.state = 'ambiguous'
756
+ AND reservation.bot_name = messages.bot_name
757
+ AND reservation.telegram_chat_id = messages.chat_id
758
+ AND reservation.telegram_message_id = CAST(messages.msg_id AS TEXT)
759
+ )
760
+ `).run(generationId);
761
+ return result;
762
+ }
763
+
764
+ function assertCodexCheckpointOwner(generation, kind) {
765
+ if (['request-prepared', 'queued-send-cancelled'].includes(kind)) return;
766
+ const lease = db.prepare(`
767
+ SELECT generation_id, stable_host_id, boot_session_id, status
768
+ FROM codex_daemon_lease
769
+ WHERE singleton = 1
770
+ `).get();
771
+ if (
772
+ generation.state !== 'active'
773
+ || lease?.status !== 'active'
774
+ || lease.generation_id !== generation.generation_id
775
+ || lease.stable_host_id !== generation.stable_host_id
776
+ || lease.boot_session_id !== generation.boot_session_id
777
+ ) {
778
+ throw codexError(
779
+ 'Codex checkpoint generation does not own the active daemon lease',
780
+ 'CODEX_CHECKPOINT_STALE_GENERATION',
781
+ );
782
+ }
783
+ }
784
+
785
+ function assertCodexAttemptIdentity(attempt, fields) {
786
+ for (const [column, value] of Object.entries(fields)) {
787
+ if (value != null && attempt[column] != null && attempt[column] !== value) {
788
+ throw codexError(
789
+ `Codex attempt ${column} is immutable`,
790
+ 'CODEX_ATTEMPT_IDENTITY_MISMATCH',
791
+ );
792
+ }
793
+ }
794
+ }
795
+
796
+ const idempotentCheckpointKinds = new Set([
797
+ 'request-prepared',
798
+ 'request-write-attempted',
799
+ 'request-response-observed',
800
+ 'thread-initialized',
801
+ 'turn-accepted',
802
+ 'turn-steer-accepted',
803
+ 'turn-started',
804
+ 'item-started',
805
+ 'item-completed',
806
+ 'turn-terminal',
807
+ 'active-start-cancelled',
808
+ 'queued-send-cancelled',
809
+ 'telegram-delivery-settled',
810
+ 'telegram-delivery-failed',
811
+ ]);
812
+
813
+ function hasExactCodexCheckpoint({
814
+ generationId,
815
+ attemptId,
816
+ kind,
817
+ threadId,
818
+ turnId,
819
+ requestId,
820
+ itemId,
821
+ detailJson,
822
+ }) {
823
+ if (!idempotentCheckpointKinds.has(kind)) return false;
824
+ return db.prepare(`
825
+ SELECT 1
826
+ FROM codex_attempt_checkpoints
827
+ WHERE generation_id = ?
828
+ AND attempt_id IS ?
829
+ AND kind = ?
830
+ AND thread_id IS ?
831
+ AND turn_id IS ?
832
+ AND request_id IS ?
833
+ AND item_id IS ?
834
+ AND detail_json IS ?
835
+ LIMIT 1
836
+ `).get(
837
+ generationId,
838
+ attemptId,
839
+ kind,
840
+ threadId,
841
+ turnId,
842
+ requestId,
843
+ itemId,
844
+ detailJson,
845
+ ) != null;
846
+ }
847
+
848
+ function settleCodexInboundReservation(reservation, handlerStatus) {
849
+ const inbound = db.prepare(`
850
+ SELECT handler_status
851
+ FROM messages
852
+ WHERE direction = 'in'
853
+ AND bot_name = ?
854
+ AND chat_id = ?
855
+ AND CAST(msg_id AS TEXT) = ?
856
+ `).get(
857
+ reservation.bot_name,
858
+ reservation.telegram_chat_id,
859
+ reservation.telegram_message_id,
860
+ );
861
+ if (!inbound) {
862
+ throw codexError(
863
+ 'Codex disposition has no exact inbound message',
864
+ 'CODEX_LINKED_INPUT_MESSAGE_NOT_FOUND',
865
+ );
866
+ }
867
+ if (inbound.handler_status === handlerStatus) return;
868
+ if (![
869
+ 'received',
870
+ 'dispatched',
871
+ 'processing',
872
+ 'replay-pending',
873
+ ].includes(inbound.handler_status)) {
874
+ throw codexError(
875
+ 'Codex disposition conflicts with the inbound message state',
876
+ 'CODEX_LINKED_INPUT_MESSAGE_NOT_FOUND',
877
+ );
878
+ }
879
+ db.prepare(`
880
+ UPDATE messages
881
+ SET handler_status = ?
882
+ WHERE direction = 'in'
883
+ AND bot_name = ?
884
+ AND chat_id = ?
885
+ AND CAST(msg_id AS TEXT) = ?
886
+ `).run(
887
+ handlerStatus,
888
+ reservation.bot_name,
889
+ reservation.telegram_chat_id,
890
+ reservation.telegram_message_id,
891
+ );
892
+ }
893
+
894
+ function settleCodexPrimaryInbound(attempt, handlerStatus) {
895
+ if (attempt.telegram_source_message_id == null) return;
896
+ const selections = db.prepare(`
897
+ SELECT *
898
+ FROM inbound_runtime_selections
899
+ WHERE session_key = ?
900
+ AND telegram_message_id = ?
901
+ AND provider = 'codex'
902
+ ORDER BY bot_name, telegram_chat_id
903
+ `).all(
904
+ attempt.session_key,
905
+ attempt.telegram_source_message_id,
906
+ );
907
+ if (selections.length > 1) {
908
+ throw codexError(
909
+ 'Codex attempt matches conflicting inbound runtime selections',
910
+ 'INBOUND_RUNTIME_SELECTION_CONFLICT',
911
+ );
912
+ }
913
+ if (selections.length === 1) {
914
+ settleCodexInboundReservation(selections[0], handlerStatus);
915
+ }
916
+ }
917
+
918
+ function settleCodexAttemptConsumerOutcome({
919
+ attempt,
920
+ generationId,
921
+ linkedState,
922
+ attemptRecoveryState,
923
+ settleQueuedReservation,
924
+ ts,
925
+ }) {
926
+ db.prepare(`
927
+ UPDATE codex_turn_attempts
928
+ SET recovery_state = ?,
929
+ settled_ts = ?,
930
+ updated_ts = ?
931
+ WHERE attempt_id = ?
932
+ `).run(attemptRecoveryState, ts, ts, attempt.attempt_id);
933
+
934
+ const handlerStatus = linkedState === 'settled' ? 'replied' : 'failed';
935
+ settleCodexPrimaryInbound(attempt, handlerStatus);
936
+
937
+ const acceptedSteers = attempt.turn_id == null
938
+ ? []
939
+ : db.prepare(`
940
+ SELECT *
941
+ FROM codex_turn_attempts
942
+ WHERE generation_id = ?
943
+ AND method = 'turn/steer'
944
+ AND turn_id = ?
945
+ AND delivery_state = 'response-observed'
946
+ AND response_outcome = 'result'
947
+ AND recovery_state = 'active'
948
+ ORDER BY created_ts, attempt_id
949
+ `).all(generationId, attempt.turn_id);
950
+ for (const steer of acceptedSteers) {
951
+ if (steer.telegram_source_message_id == null) continue;
952
+ const reservations = db.prepare(`
953
+ SELECT *
954
+ FROM codex_dispatch_reservations
955
+ WHERE generation_id = ?
956
+ AND session_key = ?
957
+ AND telegram_message_id = ?
958
+ AND state = 'reserved'
959
+ ORDER BY reservation_id
960
+ `).all(
961
+ generationId,
962
+ steer.session_key,
963
+ steer.telegram_source_message_id,
964
+ );
965
+ if (reservations.length > 1) {
966
+ throw codexError(
967
+ 'Codex accepted steer matches conflicting reservations',
968
+ 'CODEX_DISPATCH_RESERVATION_CONFLICT',
969
+ );
970
+ }
971
+ if (reservations.length === 0) continue;
972
+ const reservation = reservations[0];
973
+ settleCodexInboundReservation(reservation, handlerStatus);
974
+ db.prepare(`
975
+ INSERT INTO codex_linked_inputs (
976
+ linked_input_id, generation_id, attempt_id, target_attempt_id,
977
+ telegram_chat_id, telegram_message_id, state,
978
+ created_ts, settled_ts
979
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
980
+ `).run(
981
+ reservation.reservation_id,
982
+ generationId,
983
+ steer.attempt_id,
984
+ attempt.attempt_id,
985
+ reservation.telegram_chat_id,
986
+ reservation.telegram_message_id,
987
+ linkedState,
988
+ reservation.created_ts,
989
+ ts,
990
+ );
991
+ db.prepare(`
992
+ UPDATE codex_dispatch_reservations
993
+ SET state = ?,
994
+ steer_attempt_id = ?,
995
+ target_attempt_id = ?,
996
+ updated_ts = ?,
997
+ settled_ts = ?
998
+ WHERE reservation_id = ?
999
+ AND state = 'reserved'
1000
+ `).run(
1001
+ linkedState,
1002
+ steer.attempt_id,
1003
+ attempt.attempt_id,
1004
+ ts,
1005
+ ts,
1006
+ reservation.reservation_id,
1007
+ );
1008
+ }
1009
+
1010
+ const linkedReservations = db.prepare(`
1011
+ SELECT reservation.*
1012
+ FROM codex_dispatch_reservations reservation
1013
+ JOIN codex_linked_inputs linked
1014
+ ON linked.linked_input_id = reservation.reservation_id
1015
+ WHERE linked.target_attempt_id = ?
1016
+ AND reservation.state = 'steer-accepted'
1017
+ `).all(attempt.attempt_id);
1018
+ for (const reservation of linkedReservations) {
1019
+ settleCodexInboundReservation(reservation, handlerStatus);
1020
+ }
1021
+ const linkedAttempts = db.prepare(`
1022
+ SELECT attempt_id
1023
+ FROM codex_linked_inputs
1024
+ WHERE target_attempt_id = ? AND state = 'linked'
1025
+ `).all(attempt.attempt_id);
1026
+ db.prepare(`
1027
+ UPDATE codex_linked_inputs
1028
+ SET state = ?, settled_ts = ?
1029
+ WHERE target_attempt_id = ? AND state = 'linked'
1030
+ `).run(linkedState, ts, attempt.attempt_id);
1031
+ db.prepare(`
1032
+ UPDATE codex_dispatch_reservations
1033
+ SET state = ?, updated_ts = ?, settled_ts = ?
1034
+ WHERE reservation_id IN (
1035
+ SELECT linked_input_id
1036
+ FROM codex_linked_inputs
1037
+ WHERE target_attempt_id = ?
1038
+ )
1039
+ AND state = 'steer-accepted'
1040
+ `).run(linkedState, ts, ts, attempt.attempt_id);
1041
+ for (const linked of linkedAttempts) {
1042
+ db.prepare(`
1043
+ UPDATE codex_turn_attempts
1044
+ SET recovery_state = 'settled',
1045
+ settled_ts = ?,
1046
+ updated_ts = ?
1047
+ WHERE attempt_id = ?
1048
+ AND recovery_state NOT IN ('settled', 'cancelled')
1049
+ `).run(ts, ts, linked.attempt_id);
1050
+ }
1051
+ if (attempt.turn_id != null) {
1052
+ db.prepare(`
1053
+ UPDATE codex_turn_attempts
1054
+ SET recovery_state = 'settled',
1055
+ settled_ts = ?,
1056
+ updated_ts = ?
1057
+ WHERE generation_id = ?
1058
+ AND method = 'turn/steer'
1059
+ AND turn_id = ?
1060
+ AND delivery_state = 'response-observed'
1061
+ AND response_outcome = 'result'
1062
+ AND recovery_state = 'active'
1063
+ `).run(ts, ts, generationId, attempt.turn_id);
1064
+ }
1065
+
1066
+ if (
1067
+ settleQueuedReservation
1068
+ && attempt.telegram_source_message_id != null
1069
+ ) {
1070
+ const queued = db.prepare(`
1071
+ SELECT *
1072
+ FROM codex_dispatch_reservations
1073
+ WHERE generation_id = ?
1074
+ AND session_key = ?
1075
+ AND telegram_message_id = ?
1076
+ AND state = 'queue-authorized'
1077
+ ORDER BY reservation_id
1078
+ `).all(
1079
+ generationId,
1080
+ attempt.session_key,
1081
+ attempt.telegram_source_message_id,
1082
+ );
1083
+ if (queued.length > 1) {
1084
+ throw codexError(
1085
+ 'Codex terminal attempt matches conflicting queued reservations',
1086
+ 'CODEX_QUEUE_SETTLEMENT_CONFLICT',
1087
+ );
1088
+ }
1089
+ if (queued.length === 1) {
1090
+ settleCodexInboundReservation(queued[0], handlerStatus);
1091
+ db.prepare(`
1092
+ UPDATE codex_dispatch_reservations
1093
+ SET state = ?,
1094
+ target_attempt_id = ?,
1095
+ updated_ts = ?,
1096
+ settled_ts = ?
1097
+ WHERE reservation_id = ?
1098
+ AND state = 'queue-authorized'
1099
+ `).run(
1100
+ linkedState,
1101
+ attempt.attempt_id,
1102
+ ts,
1103
+ ts,
1104
+ queued[0].reservation_id,
1105
+ );
1106
+ }
1107
+ }
1108
+
1109
+ db.prepare(`
1110
+ UPDATE codex_generations
1111
+ SET state = 'healthy-stopped',
1112
+ settled_ts = ?,
1113
+ updated_ts = ?
1114
+ WHERE generation_id = ?
1115
+ AND EXISTS (
1116
+ SELECT 1
1117
+ FROM codex_attempt_checkpoints
1118
+ WHERE generation_id = ?
1119
+ AND kind = 'stop-empty-registry-observed'
1120
+ )
1121
+ AND NOT EXISTS (
1122
+ SELECT 1
1123
+ FROM codex_turn_attempts
1124
+ WHERE generation_id = ?
1125
+ AND recovery_state NOT IN ('settled', 'cancelled')
1126
+ )
1127
+ `).run(ts, ts, generationId, generationId, generationId);
1128
+ }
1129
+
1130
+ function recordCodexCheckpoint(input) {
1131
+ const kind = requiredCodexString(input?.kind, 'checkpoint kind');
1132
+ if (!codexCheckpointKinds.has(kind)) {
1133
+ throw codexError(
1134
+ 'Codex checkpoint kind is not allowlisted',
1135
+ 'CODEX_CHECKPOINT_KIND_REJECTED',
1136
+ );
1137
+ }
1138
+ const generationId = requiredCodexString(
1139
+ input?.generationId ?? input?.generation_id,
1140
+ 'generation ID',
1141
+ );
1142
+ const stableHostId = requiredCodexString(
1143
+ input?.stable_host_id ?? input?.hostIdentity,
1144
+ 'stable host identity',
1145
+ );
1146
+ const bootSessionId = requiredCodexString(
1147
+ input?.boot_session_id ?? input?.bootSessionIdentity,
1148
+ 'boot-session identity',
1149
+ );
1150
+ const attemptId = optionalCodexString(
1151
+ input?.attemptId ?? input?.attempt_id,
1152
+ 'attempt ID',
1153
+ );
1154
+ const threadId = optionalCodexString(
1155
+ input?.threadId ?? input?.thread_id,
1156
+ 'thread ID',
1157
+ );
1158
+ const turnId = optionalCodexString(
1159
+ input?.turnId ?? input?.turn_id,
1160
+ 'turn ID',
1161
+ );
1162
+ const requestId = optionalCodexRequestId(
1163
+ input?.requestId ?? input?.request_id,
1164
+ );
1165
+ const itemId = optionalCodexString(
1166
+ input?.itemId ?? input?.item_id,
1167
+ 'item ID',
1168
+ );
1169
+ const method = optionalCodexString(input?.method, 'request method');
1170
+ const sourceId = optionalCodexString(input?.source, 'source message ID');
1171
+ const clientUserMessageId = optionalCodexString(
1172
+ input?.clientUserMessageId ?? input?.client_user_message_id,
1173
+ 'client user message ID',
1174
+ );
1175
+ const ts = requiredTimestamp(input?.ts ?? Date.now(), 'checkpoint timestamp');
1176
+ const detailJson = checkpointDetail(input);
1177
+
1178
+ return db.transaction(() => {
1179
+ const generation = assertCodexGenerationIdentity(
1180
+ generationId,
1181
+ stableHostId,
1182
+ bootSessionId,
1183
+ );
1184
+ let attempt = attemptId
1185
+ ? db.prepare(`
1186
+ SELECT * FROM codex_turn_attempts WHERE attempt_id = ?
1187
+ `).get(attemptId)
1188
+ : null;
1189
+ if (attempt) {
1190
+ assertCodexAttemptIdentity(attempt, {
1191
+ method,
1192
+ thread_id: threadId,
1193
+ turn_id: turnId,
1194
+ request_id: requestId,
1195
+ telegram_source_message_id: sourceId,
1196
+ client_user_message_id: clientUserMessageId,
1197
+ });
1198
+ if (hasExactCodexCheckpoint({
1199
+ generationId,
1200
+ attemptId,
1201
+ kind,
1202
+ threadId,
1203
+ turnId,
1204
+ requestId,
1205
+ itemId,
1206
+ detailJson,
1207
+ })) {
1208
+ return { changes: 0, attemptId, kind };
1209
+ }
1210
+ }
1211
+ assertCodexCheckpointOwner(generation, kind);
1212
+
1213
+ if (kind === 'request-prepared') {
1214
+ requiredCodexString(method, 'request method');
1215
+ if (!attemptId) {
1216
+ throw codexError(
1217
+ 'Codex prepared checkpoint requires an attempt ID',
1218
+ 'CODEX_CHECKPOINT_INPUT_INVALID',
1219
+ );
1220
+ }
1221
+ if (attempt) {
1222
+ if (
1223
+ attempt.generation_id === generationId
1224
+ && attempt.method === method
1225
+ && attempt.delivery_state === 'prepared'
1226
+ ) {
1227
+ return { changes: 0, attemptId, kind };
1228
+ }
1229
+ throw codexError(
1230
+ 'Codex attempt ID was reused with different metadata',
1231
+ 'CODEX_ATTEMPT_ID_REUSED',
1232
+ );
1233
+ }
1234
+ db.prepare(`
1235
+ INSERT INTO codex_turn_attempts (
1236
+ attempt_id, generation_id, session_key, method,
1237
+ thread_id, turn_id, telegram_source_message_id,
1238
+ client_user_message_id, delivery_state, recovery_state,
1239
+ created_ts, updated_ts
1240
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'prepared', 'prepared', ?, ?)
1241
+ `).run(
1242
+ attemptId,
1243
+ generationId,
1244
+ generation.session_key,
1245
+ method,
1246
+ threadId,
1247
+ turnId,
1248
+ sourceId,
1249
+ clientUserMessageId,
1250
+ ts,
1251
+ ts,
1252
+ );
1253
+ attempt = requireCodexAttempt(attemptId, generationId);
1254
+ } else if (kind === 'queued-send-cancelled' && attemptId && !attempt) {
1255
+ db.prepare(`
1256
+ INSERT INTO codex_turn_attempts (
1257
+ attempt_id, generation_id, session_key, method,
1258
+ thread_id, turn_id, telegram_source_message_id,
1259
+ client_user_message_id, delivery_state, recovery_state,
1260
+ created_ts, updated_ts, settled_ts
1261
+ ) VALUES (?, ?, ?, 'queued/send', ?, ?, ?, ?,
1262
+ 'prepared', 'cancelled', ?, ?, ?)
1263
+ `).run(
1264
+ attemptId,
1265
+ generationId,
1266
+ generation.session_key,
1267
+ threadId,
1268
+ turnId,
1269
+ sourceId,
1270
+ clientUserMessageId,
1271
+ ts,
1272
+ ts,
1273
+ ts,
1274
+ );
1275
+ attempt = requireCodexAttempt(attemptId, generationId);
1276
+ } else if (attemptId) {
1277
+ attempt = requireCodexAttempt(attemptId, generationId);
1278
+ }
1279
+
1280
+ if (kind === 'request-write-attempted') {
1281
+ if (attempt.delivery_state !== 'prepared') {
1282
+ throw codexError(
1283
+ 'Codex write-attempted checkpoint is out of order',
1284
+ 'CODEX_CHECKPOINT_SEQUENCE_INVALID',
1285
+ );
1286
+ }
1287
+ db.prepare(`
1288
+ UPDATE codex_turn_attempts
1289
+ SET request_id = ?,
1290
+ delivery_state = 'write-attempted',
1291
+ recovery_state = 'ambiguous',
1292
+ ambiguous_ts = COALESCE(ambiguous_ts, ?),
1293
+ updated_ts = ?
1294
+ WHERE attempt_id = ?
1295
+ `).run(requestId, ts, ts, attemptId);
1296
+ } else if (kind === 'request-response-observed') {
1297
+ if (attempt.delivery_state !== 'write-attempted') {
1298
+ throw codexError(
1299
+ 'Codex response-observed checkpoint is out of order',
1300
+ 'CODEX_CHECKPOINT_SEQUENCE_INVALID',
1301
+ );
1302
+ }
1303
+ if (!['result', 'error'].includes(input?.outcome)) {
1304
+ throw codexError(
1305
+ 'Codex response checkpoint outcome is invalid',
1306
+ 'CODEX_CHECKPOINT_INPUT_INVALID',
1307
+ );
1308
+ }
1309
+ db.prepare(`
1310
+ UPDATE codex_turn_attempts
1311
+ SET request_id = COALESCE(request_id, ?),
1312
+ delivery_state = 'response-observed',
1313
+ response_outcome = ?,
1314
+ updated_ts = ?
1315
+ WHERE attempt_id = ?
1316
+ `).run(requestId, input.outcome, ts, attemptId);
1317
+ } else if (
1318
+ ['turn-accepted', 'turn-steer-accepted'].includes(kind)
1319
+ ) {
1320
+ if (!attempt) {
1321
+ throw codexError(
1322
+ 'Codex turn lifecycle checkpoint requires an attempt',
1323
+ 'CODEX_CHECKPOINT_INPUT_INVALID',
1324
+ );
1325
+ }
1326
+ if (
1327
+ attempt.delivery_state !== 'response-observed'
1328
+ || attempt.response_outcome !== 'result'
1329
+ ) {
1330
+ throw codexError(
1331
+ 'Codex accepted checkpoint has no successful response predecessor',
1332
+ 'CODEX_CHECKPOINT_SEQUENCE_INVALID',
1333
+ );
1334
+ }
1335
+ db.prepare(`
1336
+ UPDATE codex_turn_attempts
1337
+ SET turn_id = COALESCE(?, turn_id),
1338
+ recovery_state = CASE
1339
+ WHEN terminal_status IS NULL THEN 'active'
1340
+ ELSE 'terminal-pending'
1341
+ END,
1342
+ updated_ts = ?
1343
+ WHERE attempt_id = ?
1344
+ `).run(turnId, ts, attemptId);
1345
+ db.prepare(`
1346
+ UPDATE codex_generations
1347
+ SET thread_id = COALESCE(?, thread_id),
1348
+ state = 'active',
1349
+ updated_ts = ?
1350
+ WHERE generation_id = ?
1351
+ `).run(threadId, ts, generationId);
1352
+ } else if (kind === 'turn-started') {
1353
+ if (
1354
+ !attempt
1355
+ || attempt.terminal_status != null
1356
+ || !(
1357
+ attempt.recovery_state === 'active'
1358
+ || (
1359
+ attempt.recovery_state === 'ambiguous'
1360
+ && ['write-attempted', 'response-observed'].includes(
1361
+ attempt.delivery_state,
1362
+ )
1363
+ )
1364
+ )
1365
+ ) {
1366
+ throw codexError(
1367
+ 'Codex turn-started checkpoint has no dispatched predecessor',
1368
+ 'CODEX_CHECKPOINT_SEQUENCE_INVALID',
1369
+ );
1370
+ }
1371
+ db.prepare(`
1372
+ UPDATE codex_turn_attempts
1373
+ SET turn_id = COALESCE(?, turn_id), updated_ts = ?
1374
+ WHERE attempt_id = ?
1375
+ `).run(turnId, ts, attemptId);
1376
+ } else if (
1377
+ ['item-started', 'item-completed', 'item-delta-observed'].includes(kind)
1378
+ ) {
1379
+ const notificationBeforeResponse = (
1380
+ attempt?.recovery_state === 'ambiguous'
1381
+ && ['write-attempted', 'response-observed'].includes(
1382
+ attempt?.delivery_state,
1383
+ )
1384
+ );
1385
+ if (
1386
+ !attempt
1387
+ || attempt.turn_id == null
1388
+ || attempt.terminal_status != null
1389
+ || !(attempt.recovery_state === 'active' || notificationBeforeResponse)
1390
+ ) {
1391
+ throw codexError(
1392
+ 'Codex item checkpoint requires a live dispatched turn',
1393
+ 'CODEX_CHECKPOINT_SEQUENCE_INVALID',
1394
+ );
1395
+ }
1396
+ db.prepare(`
1397
+ UPDATE codex_turn_attempts
1398
+ SET updated_ts = ?
1399
+ WHERE attempt_id = ?
1400
+ `).run(ts, attemptId);
1401
+ if (kind !== 'item-delta-observed' && itemId) {
1402
+ recordCodexItemEffect({
1403
+ generation_id: generationId,
1404
+ attempt_id: attemptId,
1405
+ item_id: itemId,
1406
+ item_type: input?.itemType ?? 'unknown',
1407
+ state: kind === 'item-started' ? 'started' : 'completed',
1408
+ ts,
1409
+ });
1410
+ }
1411
+ } else if (kind === 'turn-terminal') {
1412
+ if (
1413
+ !attempt
1414
+ || attempt.turn_id == null
1415
+ || attempt.terminal_status != null
1416
+ || !(
1417
+ attempt.recovery_state === 'active'
1418
+ || (
1419
+ attempt.recovery_state === 'ambiguous'
1420
+ && ['write-attempted', 'response-observed'].includes(
1421
+ attempt.delivery_state,
1422
+ )
1423
+ )
1424
+ )
1425
+ || !['completed', 'interrupted', 'failed'].includes(
1426
+ input?.terminalStatus,
1427
+ )
1428
+ ) {
1429
+ throw codexError(
1430
+ 'Codex turn terminal checkpoint is invalid',
1431
+ 'CODEX_CHECKPOINT_INPUT_INVALID',
1432
+ );
1433
+ }
1434
+ db.prepare(`
1435
+ UPDATE codex_turn_attempts
1436
+ SET turn_id = COALESCE(?, turn_id),
1437
+ terminal_status = ?,
1438
+ recovery_state = CASE
1439
+ WHEN recovery_state = 'active' THEN 'terminal-pending'
1440
+ ELSE recovery_state
1441
+ END,
1442
+ updated_ts = ?
1443
+ WHERE attempt_id = ?
1444
+ `).run(turnId, input.terminalStatus, ts, attemptId);
1445
+ } else if (
1446
+ ['stop-terminal-reconciled', 'background-terminal-reconciled']
1447
+ .includes(kind)
1448
+ ) {
1449
+ db.prepare(`
1450
+ UPDATE codex_turn_attempts
1451
+ SET recovery_state = 'clean-pending', updated_ts = ?
1452
+ WHERE generation_id = ?
1453
+ AND method IN ('turn/start', 'turn/interrupt')
1454
+ AND recovery_state NOT IN ('settled', 'cancelled')
1455
+ AND (? IS NULL OR turn_id = ?)
1456
+ `).run(ts, generationId, turnId, turnId);
1457
+ } else if (
1458
+ ['stop-clean-accepted', 'background-clean-accepted'].includes(kind)
1459
+ ) {
1460
+ db.prepare(`
1461
+ UPDATE codex_turn_attempts
1462
+ SET recovery_state = 'empty-registry-pending', updated_ts = ?
1463
+ WHERE generation_id = ?
1464
+ AND method = 'thread/backgroundTerminals/clean'
1465
+ AND recovery_state NOT IN ('settled', 'cancelled')
1466
+ `).run(ts, generationId);
1467
+ } else if (
1468
+ ['stop-empty-registry-observed', 'background-empty-registry-observed']
1469
+ .includes(kind)
1470
+ ) {
1471
+ db.prepare(`
1472
+ UPDATE codex_turn_attempts
1473
+ SET recovery_state = 'settled',
1474
+ settled_ts = ?,
1475
+ updated_ts = ?
1476
+ WHERE generation_id = ?
1477
+ AND method IN (
1478
+ 'turn/interrupt',
1479
+ 'thread/backgroundTerminals/clean'
1480
+ )
1481
+ AND recovery_state NOT IN ('settled', 'cancelled')
1482
+ `).run(ts, ts, generationId);
1483
+ if (kind === 'stop-empty-registry-observed') {
1484
+ db.prepare(`
1485
+ UPDATE codex_generations
1486
+ SET state = 'healthy-stopped',
1487
+ settled_ts = ?,
1488
+ updated_ts = ?
1489
+ WHERE generation_id = ?
1490
+ AND NOT EXISTS (
1491
+ SELECT 1
1492
+ FROM codex_turn_attempts
1493
+ WHERE generation_id = ?
1494
+ AND recovery_state NOT IN ('settled', 'cancelled')
1495
+ )
1496
+ `).run(ts, ts, generationId, generationId);
1497
+ }
1498
+ } else if (
1499
+ ['telegram-delivery-settled', 'telegram-delivery-failed']
1500
+ .includes(kind)
1501
+ ) {
1502
+ if (!attempt || ![
1503
+ 'terminal-pending',
1504
+ 'clean-pending',
1505
+ 'empty-registry-pending',
1506
+ ].includes(
1507
+ attempt.recovery_state,
1508
+ )) {
1509
+ throw codexError(
1510
+ 'Codex delivery settlement requires a terminal attempt',
1511
+ 'CODEX_CHECKPOINT_SEQUENCE_INVALID',
1512
+ );
1513
+ }
1514
+ const deliveryFailed = kind === 'telegram-delivery-failed';
1515
+ const linkedState = deliveryFailed
1516
+ ? 'failed'
1517
+ : attempt.terminal_status === 'interrupted'
1518
+ ? 'interrupted'
1519
+ : attempt.terminal_status === 'failed'
1520
+ ? 'failed'
1521
+ : 'settled';
1522
+ settleCodexAttemptConsumerOutcome({
1523
+ attempt,
1524
+ generationId,
1525
+ linkedState,
1526
+ attemptRecoveryState: 'settled',
1527
+ settleQueuedReservation: true,
1528
+ ts,
1529
+ });
1530
+ } else if (
1531
+ ['active-start-cancelled', 'queued-send-cancelled'].includes(kind)
1532
+ ) {
1533
+ if (attempt) {
1534
+ db.prepare(`
1535
+ UPDATE codex_turn_attempts
1536
+ SET recovery_state = 'cancelled',
1537
+ settled_ts = ?,
1538
+ updated_ts = ?
1539
+ WHERE attempt_id = ?
1540
+ `).run(ts, ts, attemptId);
1541
+ }
1542
+ } else if (kind === 'failed-ambiguous-entered') {
1543
+ if (attempt) {
1544
+ db.prepare(`
1545
+ UPDATE codex_turn_attempts
1546
+ SET recovery_state = 'ambiguous',
1547
+ ambiguous_ts = COALESCE(ambiguous_ts, ?),
1548
+ updated_ts = ?
1549
+ WHERE attempt_id = ?
1550
+ `).run(ts, ts, attemptId);
1551
+ db.prepare(`
1552
+ UPDATE codex_linked_inputs
1553
+ SET state = 'ambiguous', settled_ts = ?
1554
+ WHERE (
1555
+ attempt_id = ?
1556
+ OR target_attempt_id = ?
1557
+ )
1558
+ AND state = 'linked'
1559
+ `).run(ts, attemptId, attemptId);
1560
+ db.prepare(`
1561
+ UPDATE codex_dispatch_reservations
1562
+ SET state = 'ambiguous',
1563
+ updated_ts = ?,
1564
+ settled_ts = NULL
1565
+ WHERE (
1566
+ steer_attempt_id = ?
1567
+ OR target_attempt_id = ?
1568
+ )
1569
+ AND state IN (
1570
+ 'reserved', 'steer-accepted', 'queue-authorized'
1571
+ )
1572
+ `).run(ts, attemptId, attemptId);
1573
+ db.prepare(`
1574
+ UPDATE messages
1575
+ SET handler_status = 'codex-ambiguous'
1576
+ WHERE direction = 'in'
1577
+ AND handler_status IN (
1578
+ 'received', 'dispatched', 'processing', 'replay-pending'
1579
+ )
1580
+ AND EXISTS (
1581
+ SELECT 1
1582
+ FROM codex_dispatch_reservations reservation
1583
+ WHERE (
1584
+ reservation.steer_attempt_id = ?
1585
+ OR reservation.target_attempt_id = ?
1586
+ )
1587
+ AND reservation.state = 'ambiguous'
1588
+ AND reservation.bot_name = messages.bot_name
1589
+ AND reservation.telegram_chat_id = messages.chat_id
1590
+ AND reservation.telegram_message_id
1591
+ = CAST(messages.msg_id AS TEXT)
1592
+ )
1593
+ `).run(attemptId, attemptId);
1594
+ } else {
1595
+ markGenerationAttemptsAmbiguous(generationId, ts);
1596
+ }
1597
+ } else if (kind === 'containment-entered') {
1598
+ const reason = optionalCodexString(
1599
+ input?.reason,
1600
+ 'containment reason',
1601
+ );
1602
+ db.prepare(`
1603
+ UPDATE codex_generations
1604
+ SET state = 'containment-failed',
1605
+ containment_reason = ?,
1606
+ updated_ts = ?
1607
+ WHERE generation_id = ?
1608
+ `).run(reason, ts, generationId);
1609
+ markGenerationAttemptsAmbiguous(generationId, ts);
1610
+ const lease = db.prepare(`
1611
+ SELECT generation_id
1612
+ FROM codex_daemon_lease
1613
+ WHERE singleton = 1
1614
+ `).get();
1615
+ if (!lease || lease.generation_id !== generationId) {
1616
+ throw codexError(
1617
+ 'Codex containment checkpoint requires the owned daemon lease',
1618
+ 'CODEX_LEASE_MISMATCH',
1619
+ );
1620
+ }
1621
+ db.prepare(`
1622
+ UPDATE codex_daemon_lease
1623
+ SET status = 'quarantined',
1624
+ quarantine_reason = ?,
1625
+ updated_ts = ?
1626
+ WHERE singleton = 1
1627
+ `).run(reason, ts);
1628
+ } else if (kind === 'thread-initialized') {
1629
+ const initializing = db.prepare(`
1630
+ SELECT attempt_id, delivery_state, response_outcome
1631
+ FROM codex_turn_attempts
1632
+ WHERE generation_id = ?
1633
+ AND method IN ('thread/start', 'thread/resume')
1634
+ AND recovery_state NOT IN ('settled', 'cancelled')
1635
+ ORDER BY created_ts DESC
1636
+ LIMIT 1
1637
+ `).get(generationId);
1638
+ if (
1639
+ !initializing
1640
+ || initializing.delivery_state !== 'response-observed'
1641
+ || initializing.response_outcome !== 'result'
1642
+ ) {
1643
+ throw codexError(
1644
+ 'Codex thread initialization has no successful response predecessor',
1645
+ 'CODEX_CHECKPOINT_SEQUENCE_INVALID',
1646
+ );
1647
+ }
1648
+ db.prepare(`
1649
+ UPDATE codex_generations
1650
+ SET thread_id = COALESCE(?, thread_id), updated_ts = ?
1651
+ WHERE generation_id = ?
1652
+ `).run(threadId, ts, generationId);
1653
+ db.prepare(`
1654
+ UPDATE codex_turn_attempts
1655
+ SET thread_id = COALESCE(?, thread_id),
1656
+ recovery_state = 'settled',
1657
+ settled_ts = ?,
1658
+ updated_ts = ?
1659
+ WHERE attempt_id = (
1660
+ SELECT attempt_id
1661
+ FROM codex_turn_attempts
1662
+ WHERE generation_id = ?
1663
+ AND method IN ('thread/start', 'thread/resume')
1664
+ AND recovery_state NOT IN ('settled', 'cancelled')
1665
+ ORDER BY created_ts DESC
1666
+ LIMIT 1
1667
+ )
1668
+ `).run(threadId, ts, ts, generationId);
1669
+ } else if (kind === 'thread-status-changed') {
1670
+ requiredCodexString(input?.statusType, 'thread status type');
1671
+ }
1672
+
1673
+ db.prepare(`
1674
+ INSERT INTO codex_attempt_checkpoints (
1675
+ generation_id, attempt_id, kind, thread_id, turn_id,
1676
+ request_id, item_id, detail_json, ts
1677
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
1678
+ `).run(
1679
+ generationId,
1680
+ attemptId,
1681
+ kind,
1682
+ threadId,
1683
+ turnId,
1684
+ requestId,
1685
+ itemId,
1686
+ detailJson,
1687
+ ts,
1688
+ );
1689
+ return { changes: 1, attemptId, kind };
1690
+ })();
1691
+ }
1692
+
1693
+ function recordCodexDeliveryCheckpoint(input) {
1694
+ const allowed = new Set(['checkpoint', 'retireGeneration']);
1695
+ assertExactKeys(
1696
+ input,
1697
+ allowed,
1698
+ 'CODEX_DELIVERY_PAYLOAD_REJECTED',
1699
+ 'delivery checkpoint',
1700
+ );
1701
+ const checkpoint = input?.checkpoint;
1702
+ if (
1703
+ !checkpoint
1704
+ || ![
1705
+ 'telegram-delivery-settled',
1706
+ 'telegram-delivery-failed',
1707
+ ].includes(checkpoint.kind)
1708
+ || typeof input.retireGeneration !== 'boolean'
1709
+ ) {
1710
+ throw codexError(
1711
+ 'Codex delivery checkpoint payload is invalid',
1712
+ 'CODEX_DELIVERY_PAYLOAD_REJECTED',
1713
+ );
1714
+ }
1715
+ return db.transaction(() => {
1716
+ const result = recordCodexCheckpoint(checkpoint);
1717
+ if (!input.retireGeneration) {
1718
+ return { ...result, retired: false };
1719
+ }
1720
+ const generationId = requiredCodexString(
1721
+ checkpoint.generationId ?? checkpoint.generation_id,
1722
+ 'generation ID',
1723
+ );
1724
+ const ts = requiredTimestamp(
1725
+ checkpoint.ts ?? Date.now(),
1726
+ 'delivery checkpoint timestamp',
1727
+ );
1728
+ const retirement = retireCodexGeneration(generationId, ts, {
1729
+ onlyIfHealthyStopped: true,
1730
+ });
1731
+ if (!retirement.retired) {
1732
+ throw codexError(
1733
+ 'Codex delivery could not prove a healthy stopped generation',
1734
+ 'CODEX_RETIREMENT_UNVERIFIED',
1735
+ );
1736
+ }
1737
+ return { ...result, retired: true };
1738
+ })();
1739
+ }
1740
+
1741
+ function readCodexDispatchReservation(reservationId) {
1742
+ return db.prepare(`
1743
+ SELECT *
1744
+ FROM codex_dispatch_reservations
1745
+ WHERE reservation_id = ?
1746
+ `).get(reservationId);
1747
+ }
1748
+
1749
+ function recordInboundRuntimeSelection(input) {
1750
+ const allowed = new Set([
1751
+ 'session_key',
1752
+ 'bot_name',
1753
+ 'telegram_chat_id',
1754
+ 'telegram_message_id',
1755
+ 'provider',
1756
+ 'ts',
1757
+ ]);
1758
+ assertExactKeys(
1759
+ input,
1760
+ allowed,
1761
+ 'INBOUND_RUNTIME_SELECTION_PAYLOAD_REJECTED',
1762
+ 'inbound runtime selection',
1763
+ );
1764
+ const sessionKey = requiredCodexString(input?.session_key, 'session key');
1765
+ const botName = requiredCodexString(input?.bot_name, 'bot name');
1766
+ const telegramChatId = requiredCodexString(
1767
+ input?.telegram_chat_id,
1768
+ 'Telegram chat ID',
1769
+ );
1770
+ const telegramMessageId = requiredCodexString(
1771
+ input?.telegram_message_id,
1772
+ 'Telegram message ID',
1773
+ );
1774
+ const provider = input?.provider;
1775
+ if (!['claude', 'codex'].includes(provider)) {
1776
+ throw codexError(
1777
+ 'Inbound runtime provider is invalid',
1778
+ 'INBOUND_RUNTIME_SELECTION_INVALID',
1779
+ );
1780
+ }
1781
+ const ts = requiredTimestamp(
1782
+ input?.ts ?? Date.now(),
1783
+ 'inbound runtime selection timestamp',
1784
+ );
1785
+
1786
+ return db.transaction(() => {
1787
+ const existing = db.prepare(`
1788
+ SELECT *
1789
+ FROM inbound_runtime_selections
1790
+ WHERE bot_name = ?
1791
+ AND telegram_chat_id = ?
1792
+ AND telegram_message_id = ?
1793
+ `).get(botName, telegramChatId, telegramMessageId);
1794
+ if (existing) {
1795
+ if (
1796
+ existing.session_key === sessionKey
1797
+ && existing.provider === provider
1798
+ ) {
1799
+ return { changes: 0, selection: existing };
1800
+ }
1801
+ throw codexError(
1802
+ 'Inbound runtime selection conflicts with durable identity',
1803
+ 'INBOUND_RUNTIME_SELECTION_CONFLICT',
1804
+ );
1805
+ }
1806
+ const inbound = db.prepare(`
1807
+ SELECT direction, bot_name
1808
+ FROM messages
1809
+ WHERE chat_id = ?
1810
+ AND CAST(msg_id AS TEXT) = ?
1811
+ `).get(telegramChatId, telegramMessageId);
1812
+ if (inbound?.direction !== 'in' || inbound.bot_name !== botName) {
1813
+ throw codexError(
1814
+ 'Inbound runtime selection has no exact inbound message',
1815
+ 'INBOUND_RUNTIME_SELECTION_INBOUND_MISMATCH',
1816
+ );
1817
+ }
1818
+ db.prepare(`
1819
+ INSERT INTO inbound_runtime_selections (
1820
+ bot_name, telegram_chat_id, telegram_message_id,
1821
+ session_key, provider, selected_ts
1822
+ ) VALUES (?, ?, ?, ?, ?, ?)
1823
+ `).run(
1824
+ botName,
1825
+ telegramChatId,
1826
+ telegramMessageId,
1827
+ sessionKey,
1828
+ provider,
1829
+ ts,
1830
+ );
1831
+ return {
1832
+ changes: 1,
1833
+ selection: db.prepare(`
1834
+ SELECT *
1835
+ FROM inbound_runtime_selections
1836
+ WHERE bot_name = ?
1837
+ AND telegram_chat_id = ?
1838
+ AND telegram_message_id = ?
1839
+ `).get(botName, telegramChatId, telegramMessageId),
1840
+ };
1841
+ })();
1842
+ }
1843
+
1844
+ function publicCodexAttempt(attempt) {
1845
+ if (!attempt) return null;
1846
+ return {
1847
+ attemptId: attempt.attempt_id,
1848
+ generationId: attempt.generation_id,
1849
+ method: attempt.method,
1850
+ deliveryState: attempt.delivery_state,
1851
+ recoveryState: attempt.recovery_state,
1852
+ turnId: attempt.turn_id,
1853
+ terminalStatus: attempt.terminal_status,
1854
+ };
1855
+ }
1856
+
1857
+ function publicRuntimeSelection(selection) {
1858
+ return {
1859
+ provider: selection.provider,
1860
+ sessionKey: selection.session_key,
1861
+ selectedTs: selection.selected_ts,
1862
+ };
1863
+ }
1864
+
1865
+ function getReplayProviderRecovery(input) {
1866
+ const allowed = new Set([
1867
+ 'sessionKey',
1868
+ 'botName',
1869
+ 'telegramChatId',
1870
+ 'telegramMessageId',
1871
+ ]);
1872
+ assertExactKeys(
1873
+ input,
1874
+ allowed,
1875
+ 'REPLAY_PROVIDER_RECOVERY_PAYLOAD_REJECTED',
1876
+ 'replay provider recovery',
1877
+ );
1878
+ const sessionKey = requiredCodexString(input?.sessionKey, 'session key');
1879
+ const botName = requiredCodexString(input?.botName, 'bot name');
1880
+ const telegramChatId = requiredCodexString(
1881
+ input?.telegramChatId,
1882
+ 'Telegram chat ID',
1883
+ );
1884
+ const telegramMessageId = requiredCodexString(
1885
+ input?.telegramMessageId,
1886
+ 'Telegram message ID',
1887
+ );
1888
+ const unknown = (reason) => ({ provider: 'unknown', reason });
1889
+ const selection = db.prepare(`
1890
+ SELECT *
1891
+ FROM inbound_runtime_selections
1892
+ WHERE bot_name = ?
1893
+ AND telegram_chat_id = ?
1894
+ AND telegram_message_id = ?
1895
+ `).get(botName, telegramChatId, telegramMessageId);
1896
+ if (!selection) return unknown('selection-missing');
1897
+ if (selection.session_key !== sessionKey) {
1898
+ return unknown('selection-conflict');
1899
+ }
1900
+
1901
+ const reservations = db.prepare(`
1902
+ SELECT *
1903
+ FROM codex_dispatch_reservations
1904
+ WHERE bot_name = ?
1905
+ AND telegram_chat_id = ?
1906
+ AND telegram_message_id = ?
1907
+ `).all(botName, telegramChatId, telegramMessageId);
1908
+ const attempts = db.prepare(`
1909
+ SELECT attempt.*
1910
+ FROM codex_turn_attempts attempt
1911
+ JOIN codex_generations generation
1912
+ ON generation.generation_id = attempt.generation_id
1913
+ WHERE generation.session_key = ?
1914
+ AND attempt.telegram_source_message_id = ?
1915
+ ORDER BY attempt.created_ts, attempt.attempt_id
1916
+ `).all(sessionKey, telegramMessageId);
1917
+ const linkedInputs = db.prepare(`
1918
+ SELECT linked.*
1919
+ FROM codex_linked_inputs linked
1920
+ JOIN codex_generations generation
1921
+ ON generation.generation_id = linked.generation_id
1922
+ WHERE generation.session_key = ?
1923
+ AND linked.telegram_chat_id = ?
1924
+ AND linked.telegram_message_id = ?
1925
+ ORDER BY linked.created_ts, linked.linked_input_id
1926
+ `).all(sessionKey, telegramChatId, telegramMessageId);
1927
+ const hasCodexEvidence = (
1928
+ reservations.length > 0
1929
+ || attempts.length > 0
1930
+ || linkedInputs.length > 0
1931
+ );
1932
+ if (selection.provider === 'claude') {
1933
+ return hasCodexEvidence
1934
+ ? unknown('provider-evidence-conflict')
1935
+ : {
1936
+ provider: 'claude',
1937
+ kind: 'selected',
1938
+ selection: publicRuntimeSelection(selection),
1939
+ };
1940
+ }
1941
+ if (
1942
+ reservations.length > 1
1943
+ || attempts.length > 1
1944
+ || linkedInputs.length > 1
1945
+ ) {
1946
+ return unknown('codex-evidence-conflict');
1947
+ }
1948
+
1949
+ const reservation = reservations[0] ?? null;
1950
+ const attempt = attempts[0] ?? null;
1951
+ const linkedInput = linkedInputs[0] ?? null;
1952
+ if (
1953
+ reservation
1954
+ && (
1955
+ reservation.session_key !== sessionKey
1956
+ || (
1957
+ attempt
1958
+ && reservation.generation_id !== attempt.generation_id
1959
+ )
1960
+ )
1961
+ ) {
1962
+ return unknown('codex-evidence-conflict');
1963
+ }
1964
+ if (
1965
+ attempt
1966
+ && !['turn/start', 'turn/steer'].includes(attempt.method)
1967
+ ) {
1968
+ return unknown('codex-evidence-conflict');
1969
+ }
1970
+
1971
+ let kind = 'selection-only';
1972
+ let targetAttempt = null;
1973
+ if (linkedInput) {
1974
+ if (
1975
+ !attempt
1976
+ || linkedInput.attempt_id !== attempt.attempt_id
1977
+ || linkedInput.generation_id !== attempt.generation_id
1978
+ || (
1979
+ reservation
1980
+ && (
1981
+ reservation.reservation_id !== linkedInput.linked_input_id
1982
+ || reservation.steer_attempt_id !== attempt.attempt_id
1983
+ || reservation.target_attempt_id !== linkedInput.target_attempt_id
1984
+ )
1985
+ )
1986
+ ) {
1987
+ return unknown('codex-evidence-conflict');
1988
+ }
1989
+ targetAttempt = db.prepare(`
1990
+ SELECT attempt.*
1991
+ FROM codex_turn_attempts attempt
1992
+ JOIN codex_generations generation
1993
+ ON generation.generation_id = attempt.generation_id
1994
+ WHERE attempt.attempt_id = ?
1995
+ AND attempt.generation_id = ?
1996
+ AND generation.session_key = ?
1997
+ `).get(
1998
+ linkedInput.target_attempt_id,
1999
+ linkedInput.generation_id,
2000
+ sessionKey,
2001
+ );
2002
+ if (
2003
+ !targetAttempt
2004
+ || targetAttempt.method !== 'turn/start'
2005
+ || targetAttempt.turn_id !== attempt.turn_id
2006
+ ) {
2007
+ return unknown('codex-evidence-conflict');
2008
+ }
2009
+ kind = 'linked-input';
2010
+ } else if (reservation) {
2011
+ if (reservation.steer_attempt_id != null) {
2012
+ return unknown('codex-evidence-conflict');
2013
+ }
2014
+ if (
2015
+ reservation.target_attempt_id != null
2016
+ && (
2017
+ !attempt
2018
+ || reservation.target_attempt_id !== attempt.attempt_id
2019
+ || attempt.method !== 'turn/start'
2020
+ )
2021
+ ) {
2022
+ return unknown('codex-evidence-conflict');
2023
+ }
2024
+ if (reservation.target_attempt_id != null) {
2025
+ targetAttempt = attempt;
2026
+ }
2027
+ if (attempt && attempt.method !== 'turn/start') {
2028
+ return unknown('codex-evidence-conflict');
2029
+ }
2030
+ kind = 'dispatch-reservation';
2031
+ } else if (attempt) {
2032
+ if (attempt.method !== 'turn/start') {
2033
+ return unknown('codex-evidence-conflict');
2034
+ }
2035
+ kind = 'primary-turn';
2036
+ }
2037
+
2038
+ return {
2039
+ provider: 'codex',
2040
+ kind,
2041
+ selection: publicRuntimeSelection(selection),
2042
+ reservation: reservation
2043
+ ? {
2044
+ reservationId: reservation.reservation_id,
2045
+ generationId: reservation.generation_id,
2046
+ state: reservation.state,
2047
+ targetAttemptId: reservation.target_attempt_id,
2048
+ }
2049
+ : null,
2050
+ attempt: publicCodexAttempt(attempt),
2051
+ linkedInput: linkedInput
2052
+ ? {
2053
+ linkedInputId: linkedInput.linked_input_id,
2054
+ state: linkedInput.state,
2055
+ attemptId: linkedInput.attempt_id,
2056
+ targetAttemptId: linkedInput.target_attempt_id,
2057
+ }
2058
+ : null,
2059
+ targetAttempt: publicCodexAttempt(targetAttempt),
2060
+ };
2061
+ }
2062
+
2063
+ function assertCodexDispatchReservationIdentity(
2064
+ reservation,
2065
+ generationId,
2066
+ ) {
2067
+ if (!reservation) {
2068
+ throw codexError(
2069
+ 'Codex dispatch reservation does not exist',
2070
+ 'CODEX_DISPATCH_RESERVATION_NOT_FOUND',
2071
+ );
2072
+ }
2073
+ if (reservation.generation_id !== generationId) {
2074
+ throw codexError(
2075
+ 'Codex dispatch reservation belongs to another generation',
2076
+ 'CODEX_DISPATCH_RESERVATION_CONFLICT',
2077
+ );
2078
+ }
2079
+ return reservation;
2080
+ }
2081
+
2082
+ function claimCodexDispatchReservation(input) {
2083
+ const allowed = new Set([
2084
+ 'reservation_id',
2085
+ 'generation_id',
2086
+ 'session_key',
2087
+ 'bot_name',
2088
+ 'telegram_chat_id',
2089
+ 'telegram_message_id',
2090
+ 'stable_host_id',
2091
+ 'boot_session_id',
2092
+ 'ts',
2093
+ ]);
2094
+ assertExactKeys(
2095
+ input,
2096
+ allowed,
2097
+ 'CODEX_DISPATCH_RESERVATION_PAYLOAD_REJECTED',
2098
+ 'dispatch reservation',
2099
+ );
2100
+ const reservationId = requiredCodexString(
2101
+ input?.reservation_id,
2102
+ 'dispatch reservation ID',
2103
+ );
2104
+ const generationId = requiredCodexString(
2105
+ input?.generation_id,
2106
+ 'generation ID',
2107
+ );
2108
+ const sessionKey = requiredCodexString(
2109
+ input?.session_key,
2110
+ 'session key',
2111
+ );
2112
+ const botName = requiredCodexString(input?.bot_name, 'bot name');
2113
+ const telegramChatId = requiredCodexString(
2114
+ input?.telegram_chat_id,
2115
+ 'Telegram chat ID',
2116
+ );
2117
+ const telegramMessageId = requiredCodexString(
2118
+ input?.telegram_message_id,
2119
+ 'Telegram message ID',
2120
+ );
2121
+ const stableHostId = requiredCodexString(
2122
+ input?.stable_host_id,
2123
+ 'stable host identity',
2124
+ );
2125
+ const bootSessionId = requiredCodexString(
2126
+ input?.boot_session_id,
2127
+ 'boot-session identity',
2128
+ );
2129
+ const ts = requiredTimestamp(
2130
+ input?.ts ?? Date.now(),
2131
+ 'dispatch reservation timestamp',
2132
+ );
2133
+
2134
+ return db.transaction(() => {
2135
+ const generation = assertCodexGenerationIdentity(
2136
+ generationId,
2137
+ stableHostId,
2138
+ bootSessionId,
2139
+ );
2140
+ if (generation.session_key !== sessionKey) {
2141
+ throw codexError(
2142
+ 'Codex dispatch session does not own the generation',
2143
+ 'CODEX_DISPATCH_SESSION_MISMATCH',
2144
+ );
2145
+ }
2146
+ const existingRows = db.prepare(`
2147
+ SELECT *
2148
+ FROM codex_dispatch_reservations
2149
+ WHERE reservation_id = ?
2150
+ OR (
2151
+ bot_name = ?
2152
+ AND
2153
+ telegram_chat_id = ?
2154
+ AND telegram_message_id = ?
2155
+ )
2156
+ ORDER BY reservation_id
2157
+ `).all(reservationId, botName, telegramChatId, telegramMessageId);
2158
+ if (existingRows.length > 0) {
2159
+ const existing = existingRows[0];
2160
+ if (
2161
+ existingRows.length === 1
2162
+ && existing.reservation_id === reservationId
2163
+ && existing.generation_id === generationId
2164
+ && existing.session_key === sessionKey
2165
+ && existing.bot_name === botName
2166
+ && existing.telegram_chat_id === telegramChatId
2167
+ && existing.telegram_message_id === telegramMessageId
2168
+ ) {
2169
+ const handlerStatus = existing.state === 'settled'
2170
+ ? 'replied'
2171
+ : ['failed', 'interrupted', 'cancelled'].includes(existing.state)
2172
+ ? 'failed'
2173
+ : existing.state === 'ambiguous'
2174
+ ? 'codex-ambiguous'
2175
+ : 'dispatched';
2176
+ const inbound = db.prepare(`
2177
+ UPDATE messages
2178
+ SET handler_status = ?
2179
+ WHERE direction = 'in'
2180
+ AND bot_name = ?
2181
+ AND chat_id = ?
2182
+ AND CAST(msg_id AS TEXT) = ?
2183
+ `).run(
2184
+ handlerStatus,
2185
+ existing.bot_name,
2186
+ existing.telegram_chat_id,
2187
+ existing.telegram_message_id,
2188
+ );
2189
+ if (inbound.changes !== 1) {
2190
+ throw codexError(
2191
+ 'Codex duplicate reservation has no exact inbound message',
2192
+ 'CODEX_DISPATCH_INBOUND_MISMATCH',
2193
+ );
2194
+ }
2195
+ return { claimed: false, reservation: existing };
2196
+ }
2197
+ throw codexError(
2198
+ 'Codex dispatch reservation identity was reused',
2199
+ 'CODEX_DISPATCH_RESERVATION_CONFLICT',
2200
+ );
2201
+ }
2202
+
2203
+ assertCodexCheckpointOwner(generation, 'request-write-attempted');
2204
+ const inbound = db.prepare(`
2205
+ SELECT direction, bot_name
2206
+ FROM messages
2207
+ WHERE chat_id = ?
2208
+ AND CAST(msg_id AS TEXT) = ?
2209
+ `).get(telegramChatId, telegramMessageId);
2210
+ if (
2211
+ inbound?.direction !== 'in'
2212
+ || inbound.bot_name !== botName
2213
+ ) {
2214
+ throw codexError(
2215
+ 'Codex dispatch reservation has no exact inbound message',
2216
+ 'CODEX_DISPATCH_INBOUND_MISMATCH',
2217
+ );
2218
+ }
2219
+ db.prepare(`
2220
+ INSERT INTO codex_dispatch_reservations (
2221
+ reservation_id, generation_id, session_key,
2222
+ bot_name, telegram_chat_id, telegram_message_id, state,
2223
+ created_ts, updated_ts
2224
+ ) VALUES (?, ?, ?, ?, ?, ?, 'reserved', ?, ?)
2225
+ `).run(
2226
+ reservationId,
2227
+ generationId,
2228
+ sessionKey,
2229
+ botName,
2230
+ telegramChatId,
2231
+ telegramMessageId,
2232
+ ts,
2233
+ ts,
2234
+ );
2235
+ return {
2236
+ claimed: true,
2237
+ reservation: readCodexDispatchReservation(reservationId),
2238
+ };
2239
+ })();
2240
+ }
2241
+
2242
+ function hasCodexTurnCheckpoint(generationId, attemptId, kind, turnId) {
2243
+ return db.prepare(`
2244
+ SELECT 1
2245
+ FROM codex_attempt_checkpoints
2246
+ WHERE generation_id = ?
2247
+ AND attempt_id = ?
2248
+ AND kind = ?
2249
+ AND turn_id = ?
2250
+ LIMIT 1
2251
+ `).get(generationId, attemptId, kind, turnId) != null;
2252
+ }
2253
+
2254
+ function finalizeCodexAcceptedSteer(input) {
2255
+ const allowed = new Set([
2256
+ 'reservation_id',
2257
+ 'generation_id',
2258
+ 'steer_attempt_id',
2259
+ 'target_attempt_id',
2260
+ 'stable_host_id',
2261
+ 'boot_session_id',
2262
+ 'ts',
2263
+ ]);
2264
+ assertExactKeys(
2265
+ input,
2266
+ allowed,
2267
+ 'CODEX_DISPATCH_RESERVATION_PAYLOAD_REJECTED',
2268
+ 'accepted steer',
2269
+ );
2270
+ const reservationId = requiredCodexString(
2271
+ input?.reservation_id,
2272
+ 'dispatch reservation ID',
2273
+ );
2274
+ const generationId = requiredCodexString(
2275
+ input?.generation_id,
2276
+ 'generation ID',
2277
+ );
2278
+ const steerAttemptId = requiredCodexString(
2279
+ input?.steer_attempt_id,
2280
+ 'steer attempt ID',
2281
+ );
2282
+ const targetAttemptId = requiredCodexString(
2283
+ input?.target_attempt_id,
2284
+ 'target attempt ID',
2285
+ );
2286
+ const stableHostId = requiredCodexString(
2287
+ input?.stable_host_id,
2288
+ 'stable host identity',
2289
+ );
2290
+ const bootSessionId = requiredCodexString(
2291
+ input?.boot_session_id,
2292
+ 'boot-session identity',
2293
+ );
2294
+ const ts = requiredTimestamp(
2295
+ input?.ts ?? Date.now(),
2296
+ 'accepted steer timestamp',
2297
+ );
2298
+
2299
+ return db.transaction(() => {
2300
+ const generation = assertCodexGenerationIdentity(
2301
+ generationId,
2302
+ stableHostId,
2303
+ bootSessionId,
2304
+ );
2305
+ let reservation = assertCodexDispatchReservationIdentity(
2306
+ readCodexDispatchReservation(reservationId),
2307
+ generationId,
2308
+ );
2309
+ if ([
2310
+ 'steer-accepted',
2311
+ 'settled',
2312
+ 'failed',
2313
+ 'interrupted',
2314
+ ].includes(reservation.state)) {
2315
+ const linked = db.prepare(`
2316
+ SELECT generation_id, attempt_id, target_attempt_id, state,
2317
+ telegram_chat_id, telegram_message_id
2318
+ FROM codex_linked_inputs
2319
+ WHERE linked_input_id = ?
2320
+ `).get(reservationId);
2321
+ if (
2322
+ reservation.steer_attempt_id === steerAttemptId
2323
+ && reservation.target_attempt_id === targetAttemptId
2324
+ && linked?.generation_id === generationId
2325
+ && linked.attempt_id === steerAttemptId
2326
+ && linked.target_attempt_id === targetAttemptId
2327
+ && reservation.bot_name != null
2328
+ && linked.telegram_chat_id === reservation.telegram_chat_id
2329
+ && linked.telegram_message_id === reservation.telegram_message_id
2330
+ && (
2331
+ (
2332
+ reservation.state === 'steer-accepted'
2333
+ && linked.state === 'linked'
2334
+ )
2335
+ || linked.state === reservation.state
2336
+ )
2337
+ ) {
2338
+ return { changes: 0, reservation };
2339
+ }
2340
+ throw codexError(
2341
+ 'Codex accepted steer reservation metadata conflicts',
2342
+ 'CODEX_DISPATCH_RESERVATION_CONFLICT',
2343
+ );
2344
+ }
2345
+ if (reservation.state !== 'reserved') {
2346
+ throw codexError(
2347
+ 'Codex dispatch reservation cannot accept a steer in this state',
2348
+ 'CODEX_DISPATCH_TRANSITION_INVALID',
2349
+ );
2350
+ }
2351
+ assertCodexCheckpointOwner(generation, 'turn-steer-accepted');
2352
+
2353
+ const steer = requireCodexAttempt(steerAttemptId, generationId);
2354
+ const target = requireCodexAttempt(targetAttemptId, generationId);
2355
+ const exactTurn = (
2356
+ steer.turn_id != null
2357
+ && target.turn_id === steer.turn_id
2358
+ && steer.thread_id != null
2359
+ && target.thread_id === steer.thread_id
2360
+ );
2361
+ const steerDurable = (
2362
+ steer.method === 'turn/steer'
2363
+ && steer.delivery_state === 'response-observed'
2364
+ && steer.response_outcome === 'result'
2365
+ && steer.recovery_state === 'active'
2366
+ && hasCodexTurnCheckpoint(
2367
+ generationId,
2368
+ steerAttemptId,
2369
+ 'turn-steer-accepted',
2370
+ steer.turn_id,
2371
+ )
2372
+ );
2373
+ const targetDurable = (
2374
+ target.method === 'turn/start'
2375
+ && target.delivery_state === 'response-observed'
2376
+ && target.response_outcome === 'result'
2377
+ && [
2378
+ 'active',
2379
+ 'terminal-pending',
2380
+ 'clean-pending',
2381
+ 'empty-registry-pending',
2382
+ 'settled',
2383
+ ].includes(target.recovery_state)
2384
+ && hasCodexTurnCheckpoint(
2385
+ generationId,
2386
+ targetAttemptId,
2387
+ 'turn-accepted',
2388
+ target.turn_id,
2389
+ )
2390
+ );
2391
+ if (!exactTurn || !steerDurable || !targetDurable) {
2392
+ throw codexError(
2393
+ 'Codex accepted steer is not durably bound to its target turn',
2394
+ 'CODEX_DISPATCH_STEER_NOT_DURABLE',
2395
+ );
2396
+ }
2397
+
2398
+ const targetAlreadyDelivered = (
2399
+ target.recovery_state === 'settled'
2400
+ && ['completed', 'interrupted', 'failed'].includes(
2401
+ target.terminal_status,
2402
+ )
2403
+ && hasCodexTurnCheckpoint(
2404
+ generationId,
2405
+ targetAttemptId,
2406
+ 'telegram-delivery-settled',
2407
+ target.turn_id,
2408
+ )
2409
+ );
2410
+ if (target.recovery_state === 'settled' && !targetAlreadyDelivered) {
2411
+ throw codexError(
2412
+ 'Codex settled target lacks exact Telegram delivery proof',
2413
+ 'CODEX_DISPATCH_STEER_NOT_DURABLE',
2414
+ );
2415
+ }
2416
+ const linkedState = targetAlreadyDelivered
2417
+ ? target.terminal_status === 'completed'
2418
+ ? 'settled'
2419
+ : target.terminal_status
2420
+ : 'linked';
2421
+ const linkedSettledTs = targetAlreadyDelivered
2422
+ ? target.settled_ts ?? ts
2423
+ : null;
2424
+ db.prepare(`
2425
+ INSERT INTO codex_linked_inputs (
2426
+ linked_input_id, generation_id, attempt_id, target_attempt_id,
2427
+ telegram_chat_id, telegram_message_id, state, created_ts, settled_ts
2428
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
2429
+ `).run(
2430
+ reservationId,
2431
+ generationId,
2432
+ steerAttemptId,
2433
+ targetAttemptId,
2434
+ reservation.telegram_chat_id,
2435
+ reservation.telegram_message_id,
2436
+ linkedState,
2437
+ ts,
2438
+ linkedSettledTs,
2439
+ );
2440
+ if (targetAlreadyDelivered) {
2441
+ const handlerStatus = linkedState === 'settled' ? 'replied' : 'failed';
2442
+ const inbound = db.prepare(`
2443
+ UPDATE messages
2444
+ SET handler_status = ?
2445
+ WHERE direction = 'in'
2446
+ AND bot_name = ?
2447
+ AND chat_id = ?
2448
+ AND CAST(msg_id AS TEXT) = ?
2449
+ AND handler_status IN (
2450
+ 'received', 'dispatched', 'processing', 'replay-pending'
2451
+ )
2452
+ `).run(
2453
+ handlerStatus,
2454
+ reservation.bot_name,
2455
+ reservation.telegram_chat_id,
2456
+ reservation.telegram_message_id,
2457
+ );
2458
+ if (inbound.changes !== 1) {
2459
+ throw codexError(
2460
+ 'Codex late accepted steer has no live exact inbound message',
2461
+ 'CODEX_LINKED_INPUT_MESSAGE_NOT_FOUND',
2462
+ );
2463
+ }
2464
+ db.prepare(`
2465
+ UPDATE codex_turn_attempts
2466
+ SET recovery_state = 'settled',
2467
+ settled_ts = ?,
2468
+ updated_ts = ?
2469
+ WHERE attempt_id = ?
2470
+ AND recovery_state = 'active'
2471
+ `).run(linkedSettledTs, ts, steerAttemptId);
2472
+ }
2473
+ db.prepare(`
2474
+ UPDATE codex_dispatch_reservations
2475
+ SET state = ?,
2476
+ steer_attempt_id = ?,
2477
+ target_attempt_id = ?,
2478
+ updated_ts = ?,
2479
+ settled_ts = ?
2480
+ WHERE reservation_id = ?
2481
+ AND state = 'reserved'
2482
+ `).run(
2483
+ targetAlreadyDelivered ? linkedState : 'steer-accepted',
2484
+ steerAttemptId,
2485
+ targetAttemptId,
2486
+ ts,
2487
+ linkedSettledTs,
2488
+ reservationId,
2489
+ );
2490
+ reservation = readCodexDispatchReservation(reservationId);
2491
+ return { changes: 1, reservation };
2492
+ })();
2493
+ }
2494
+
2495
+ function markCodexDispatchDisposition(input) {
2496
+ const allowed = new Set([
2497
+ 'reservation_id',
2498
+ 'generation_id',
2499
+ 'disposition',
2500
+ 'stable_host_id',
2501
+ 'boot_session_id',
2502
+ 'ts',
2503
+ ]);
2504
+ assertExactKeys(
2505
+ input,
2506
+ allowed,
2507
+ 'CODEX_DISPATCH_RESERVATION_PAYLOAD_REJECTED',
2508
+ 'dispatch disposition',
2509
+ );
2510
+ const reservationId = requiredCodexString(
2511
+ input?.reservation_id,
2512
+ 'dispatch reservation ID',
2513
+ );
2514
+ const generationId = requiredCodexString(
2515
+ input?.generation_id,
2516
+ 'generation ID',
2517
+ );
2518
+ const disposition = input?.disposition;
2519
+ if (!['queue-authorized', 'ambiguous', 'cancelled'].includes(disposition)) {
2520
+ throw codexError(
2521
+ 'Codex dispatch disposition is invalid',
2522
+ 'CODEX_DISPATCH_DISPOSITION_INVALID',
2523
+ );
2524
+ }
2525
+ const stableHostId = requiredCodexString(
2526
+ input?.stable_host_id,
2527
+ 'stable host identity',
2528
+ );
2529
+ const bootSessionId = requiredCodexString(
2530
+ input?.boot_session_id,
2531
+ 'boot-session identity',
2532
+ );
2533
+ const ts = requiredTimestamp(
2534
+ input?.ts ?? Date.now(),
2535
+ 'dispatch disposition timestamp',
2536
+ );
2537
+
2538
+ return db.transaction(() => {
2539
+ const generation = assertCodexGenerationIdentity(
2540
+ generationId,
2541
+ stableHostId,
2542
+ bootSessionId,
2543
+ );
2544
+ let reservation = assertCodexDispatchReservationIdentity(
2545
+ readCodexDispatchReservation(reservationId),
2546
+ generationId,
2547
+ );
2548
+ if (reservation.state === disposition) {
2549
+ return { changes: 0, reservation };
2550
+ }
2551
+ const legal = (
2552
+ reservation.state === 'reserved'
2553
+ || (
2554
+ reservation.state === 'queue-authorized'
2555
+ && ['ambiguous', 'cancelled'].includes(disposition)
2556
+ )
2557
+ );
2558
+ if (!legal) {
2559
+ throw codexError(
2560
+ 'Codex dispatch disposition transition is invalid',
2561
+ 'CODEX_DISPATCH_TRANSITION_INVALID',
2562
+ );
2563
+ }
2564
+ assertCodexCheckpointOwner(generation, 'request-write-attempted');
2565
+ db.prepare(`
2566
+ UPDATE codex_dispatch_reservations
2567
+ SET state = ?,
2568
+ updated_ts = ?,
2569
+ settled_ts = CASE WHEN ? = 'cancelled' THEN ? ELSE NULL END
2570
+ WHERE reservation_id = ?
2571
+ `).run(disposition, ts, disposition, ts, reservationId);
2572
+ reservation = readCodexDispatchReservation(reservationId);
2573
+ if (['ambiguous', 'cancelled'].includes(disposition)) {
2574
+ const handlerStatus = disposition === 'ambiguous'
2575
+ ? 'codex-ambiguous'
2576
+ : 'failed';
2577
+ const inbound = db.prepare(`
2578
+ UPDATE messages
2579
+ SET handler_status = ?
2580
+ WHERE direction = 'in'
2581
+ AND bot_name = ?
2582
+ AND chat_id = ?
2583
+ AND CAST(msg_id AS TEXT) = ?
2584
+ AND handler_status IN (
2585
+ 'received', 'dispatched', 'processing', 'replay-pending'
2586
+ )
2587
+ `).run(
2588
+ handlerStatus,
2589
+ reservation.bot_name,
2590
+ reservation.telegram_chat_id,
2591
+ reservation.telegram_message_id,
2592
+ );
2593
+ if (inbound.changes !== 1) {
2594
+ throw codexError(
2595
+ 'Codex dispatch disposition has no live exact inbound message',
2596
+ 'CODEX_DISPATCH_INBOUND_MISMATCH',
2597
+ );
2598
+ }
2599
+ }
2600
+ return { changes: 1, reservation };
2601
+ })();
2602
+ }
2603
+
2604
+ function settleCodexQueuedDispatch(input) {
2605
+ const allowed = new Set([
2606
+ 'attempt_id',
2607
+ 'generation_id',
2608
+ 'session_key',
2609
+ 'bot_name',
2610
+ 'telegram_chat_id',
2611
+ 'telegram_message_id',
2612
+ 'stable_host_id',
2613
+ 'boot_session_id',
2614
+ 'ts',
2615
+ ]);
2616
+ assertExactKeys(
2617
+ input,
2618
+ allowed,
2619
+ 'CODEX_QUEUE_SETTLEMENT_PAYLOAD_REJECTED',
2620
+ 'queued dispatch settlement',
2621
+ );
2622
+ const attemptId = requiredCodexString(
2623
+ input?.attempt_id,
2624
+ 'queued turn attempt ID',
2625
+ );
2626
+ const generationId = requiredCodexString(
2627
+ input?.generation_id,
2628
+ 'generation ID',
2629
+ );
2630
+ const sessionKey = requiredCodexString(input?.session_key, 'session key');
2631
+ const botName = requiredCodexString(input?.bot_name, 'bot name');
2632
+ const telegramChatId = requiredCodexString(
2633
+ input?.telegram_chat_id,
2634
+ 'Telegram chat ID',
2635
+ );
2636
+ const telegramMessageId = requiredCodexString(
2637
+ input?.telegram_message_id,
2638
+ 'Telegram message ID',
2639
+ );
2640
+ const stableHostId = requiredCodexString(
2641
+ input?.stable_host_id,
2642
+ 'stable host identity',
2643
+ );
2644
+ const bootSessionId = requiredCodexString(
2645
+ input?.boot_session_id,
2646
+ 'boot-session identity',
2647
+ );
2648
+ const ts = requiredTimestamp(
2649
+ input?.ts ?? Date.now(),
2650
+ 'queued dispatch settlement timestamp',
2651
+ );
2652
+
2653
+ return db.transaction(() => {
2654
+ const generation = assertCodexGenerationIdentity(
2655
+ generationId,
2656
+ stableHostId,
2657
+ bootSessionId,
2658
+ );
2659
+ if (generation.session_key !== sessionKey) {
2660
+ throw codexError(
2661
+ 'Codex queued turn session does not own its generation',
2662
+ 'CODEX_QUEUE_SETTLEMENT_CONFLICT',
2663
+ );
2664
+ }
2665
+ const attempt = requireCodexAttempt(attemptId, generationId);
2666
+ const durableCompleted = (
2667
+ attempt.session_key === sessionKey
2668
+ && attempt.method === 'turn/start'
2669
+ && attempt.telegram_source_message_id === telegramMessageId
2670
+ && attempt.delivery_state === 'response-observed'
2671
+ && attempt.response_outcome === 'result'
2672
+ && attempt.recovery_state === 'settled'
2673
+ && attempt.terminal_status === 'completed'
2674
+ && attempt.turn_id != null
2675
+ && hasCodexTurnCheckpoint(
2676
+ generationId,
2677
+ attemptId,
2678
+ 'turn-accepted',
2679
+ attempt.turn_id,
2680
+ )
2681
+ && hasCodexTurnCheckpoint(
2682
+ generationId,
2683
+ attemptId,
2684
+ 'turn-terminal',
2685
+ attempt.turn_id,
2686
+ )
2687
+ && hasCodexTurnCheckpoint(
2688
+ generationId,
2689
+ attemptId,
2690
+ 'telegram-delivery-settled',
2691
+ attempt.turn_id,
2692
+ )
2693
+ );
2694
+ if (!durableCompleted) {
2695
+ throw codexError(
2696
+ 'Codex queued turn lacks exact completed delivery proof',
2697
+ 'CODEX_QUEUE_SETTLEMENT_NOT_DURABLE',
2698
+ );
2699
+ }
2700
+
2701
+ const candidates = db.prepare(`
2702
+ SELECT *
2703
+ FROM codex_dispatch_reservations
2704
+ WHERE (
2705
+ bot_name = ?
2706
+ AND telegram_chat_id = ?
2707
+ AND telegram_message_id = ?
2708
+ )
2709
+ OR (
2710
+ generation_id = ?
2711
+ AND session_key = ?
2712
+ AND telegram_message_id = ?
2713
+ )
2714
+ ORDER BY reservation_id
2715
+ `).all(
2716
+ botName,
2717
+ telegramChatId,
2718
+ telegramMessageId,
2719
+ generationId,
2720
+ sessionKey,
2721
+ telegramMessageId,
2722
+ );
2723
+ if (candidates.length === 0) {
2724
+ return {
2725
+ changes: 0,
2726
+ outcome: 'no-reservation',
2727
+ reservation: null,
2728
+ };
2729
+ }
2730
+ if (candidates.length !== 1) {
2731
+ throw codexError(
2732
+ 'Codex queued turn matches conflicting dispatch reservations',
2733
+ 'CODEX_QUEUE_SETTLEMENT_CONFLICT',
2734
+ );
2735
+ }
2736
+ let reservation = candidates[0];
2737
+ const exactReservation = (
2738
+ reservation.generation_id === generationId
2739
+ && reservation.session_key === sessionKey
2740
+ && reservation.bot_name === botName
2741
+ && reservation.telegram_chat_id === telegramChatId
2742
+ && reservation.telegram_message_id === telegramMessageId
2743
+ );
2744
+ if (!exactReservation) {
2745
+ throw codexError(
2746
+ 'Codex queued turn reservation identity conflicts',
2747
+ 'CODEX_QUEUE_SETTLEMENT_CONFLICT',
2748
+ );
2749
+ }
2750
+ if (
2751
+ reservation.state === 'settled'
2752
+ && reservation.target_attempt_id === attemptId
2753
+ && reservation.steer_attempt_id == null
2754
+ ) {
2755
+ return { changes: 0, outcome: 'settled', reservation };
2756
+ }
2757
+ if (
2758
+ reservation.state !== 'queue-authorized'
2759
+ || reservation.steer_attempt_id != null
2760
+ || reservation.target_attempt_id != null
2761
+ ) {
2762
+ throw codexError(
2763
+ 'Codex dispatch reservation cannot settle this queued turn',
2764
+ 'CODEX_QUEUE_SETTLEMENT_CONFLICT',
2765
+ );
2766
+ }
2767
+ db.prepare(`
2768
+ UPDATE codex_dispatch_reservations
2769
+ SET state = 'settled',
2770
+ target_attempt_id = ?,
2771
+ updated_ts = ?,
2772
+ settled_ts = ?
2773
+ WHERE reservation_id = ?
2774
+ AND state = 'queue-authorized'
2775
+ `).run(
2776
+ attemptId,
2777
+ ts,
2778
+ attempt.settled_ts ?? ts,
2779
+ reservation.reservation_id,
2780
+ );
2781
+ reservation = readCodexDispatchReservation(reservation.reservation_id);
2782
+ return { changes: 1, outcome: 'settled', reservation };
2783
+ })();
2784
+ }
2785
+
2786
+ function settleCodexStoppedGeneration(input) {
2787
+ const allowed = new Set([
2788
+ 'generation_id',
2789
+ 'stable_host_id',
2790
+ 'boot_session_id',
2791
+ 'ts',
2792
+ ]);
2793
+ assertExactKeys(
2794
+ input,
2795
+ allowed,
2796
+ 'CODEX_RETIREMENT_PAYLOAD_REJECTED',
2797
+ 'stopped generation settlement',
2798
+ );
2799
+ const generationId = requiredCodexString(
2800
+ input?.generation_id,
2801
+ 'generation ID',
2802
+ );
2803
+ const stableHostId = requiredCodexString(
2804
+ input?.stable_host_id,
2805
+ 'stable host identity',
2806
+ );
2807
+ const bootSessionId = requiredCodexString(
2808
+ input?.boot_session_id,
2809
+ 'boot-session identity',
2810
+ );
2811
+ const ts = requiredTimestamp(
2812
+ input?.ts ?? Date.now(),
2813
+ 'stopped generation settlement timestamp',
2814
+ );
2815
+
2816
+ return db.transaction(() => {
2817
+ const generation = assertCodexGenerationIdentity(
2818
+ generationId,
2819
+ stableHostId,
2820
+ bootSessionId,
2821
+ );
2822
+ const lease = db.prepare(`
2823
+ SELECT generation_id, stable_host_id, boot_session_id, status
2824
+ FROM codex_daemon_lease
2825
+ WHERE singleton = 1
2826
+ `).get();
2827
+ if (
2828
+ !['active', 'healthy-stopped'].includes(generation.state)
2829
+ || lease?.status !== 'active'
2830
+ || lease.generation_id !== generationId
2831
+ || lease.stable_host_id !== stableHostId
2832
+ || lease.boot_session_id !== bootSessionId
2833
+ ) {
2834
+ throw codexError(
2835
+ 'Codex stopped generation does not own the active daemon lease',
2836
+ 'CODEX_CHECKPOINT_STALE_GENERATION',
2837
+ );
2838
+ }
2839
+ const stop = db.prepare(`
2840
+ SELECT turn_id
2841
+ FROM codex_attempt_checkpoints
2842
+ WHERE generation_id = ?
2843
+ AND kind = 'stop-empty-registry-observed'
2844
+ ORDER BY id DESC
2845
+ LIMIT 1
2846
+ `).get(generationId);
2847
+ if (!stop) {
2848
+ throw codexError(
2849
+ 'Codex stopped generation lacks exact empty-registry proof',
2850
+ 'CODEX_RETIREMENT_UNVERIFIED',
2851
+ );
2852
+ }
2853
+ const unresolved = db.prepare(`
2854
+ SELECT *
2855
+ FROM codex_turn_attempts
2856
+ WHERE generation_id = ?
2857
+ AND delivery_state != 'prepared'
2858
+ AND recovery_state NOT IN ('settled', 'cancelled')
2859
+ ORDER BY created_ts, attempt_id
2860
+ `).all(generationId);
2861
+ if (unresolved.length === 0) {
2862
+ retireCodexGeneration(generationId, ts);
2863
+ return {
2864
+ changes: 0,
2865
+ disposition: 'already-disposed',
2866
+ attemptId: null,
2867
+ retired: true,
2868
+ };
2869
+ }
2870
+ const interrupted = unresolved.filter((attempt) => (
2871
+ attempt.method === 'turn/start'
2872
+ && attempt.turn_id === stop.turn_id
2873
+ && attempt.terminal_status === 'interrupted'
2874
+ && hasCodexTurnCheckpoint(
2875
+ generationId,
2876
+ attempt.attempt_id,
2877
+ 'turn-terminal',
2878
+ attempt.turn_id,
2879
+ )
2880
+ ));
2881
+ if (interrupted.length === 0) {
2882
+ return {
2883
+ changes: 0,
2884
+ disposition: 'pending-delivery',
2885
+ attemptId: null,
2886
+ };
2887
+ }
2888
+ if (interrupted.length !== 1) {
2889
+ throw codexError(
2890
+ 'Codex stopped generation has conflicting interrupted targets',
2891
+ 'CODEX_RETIREMENT_UNVERIFIED',
2892
+ );
2893
+ }
2894
+ const attempt = interrupted[0];
2895
+ settleCodexAttemptConsumerOutcome({
2896
+ attempt,
2897
+ generationId,
2898
+ linkedState: 'interrupted',
2899
+ attemptRecoveryState: 'cancelled',
2900
+ settleQueuedReservation: true,
2901
+ ts,
2902
+ });
2903
+ retireCodexGeneration(generationId, ts);
2904
+ return {
2905
+ changes: 1,
2906
+ disposition: 'stop-cancelled',
2907
+ attemptId: attempt.attempt_id,
2908
+ retired: true,
2909
+ };
2910
+ })();
2911
+ }
2912
+
2913
+ function linkCodexSteeringInput(input) {
2914
+ const allowed = new Set([
2915
+ 'linked_input_id',
2916
+ 'generation_id',
2917
+ 'attempt_id',
2918
+ 'target_attempt_id',
2919
+ 'telegram_chat_id',
2920
+ 'telegram_message_id',
2921
+ 'ts',
2922
+ ]);
2923
+ assertExactKeys(
2924
+ input,
2925
+ allowed,
2926
+ 'CODEX_LINKED_INPUT_PAYLOAD_REJECTED',
2927
+ 'linked input',
2928
+ );
2929
+ const ts = requiredTimestamp(input?.ts ?? Date.now(), 'linked input timestamp');
2930
+ const linkedInputId = requiredCodexString(
2931
+ input?.linked_input_id,
2932
+ 'linked input ID',
2933
+ );
2934
+ const generationId = requiredCodexString(
2935
+ input?.generation_id,
2936
+ 'generation ID',
2937
+ );
2938
+ const attemptId = requiredCodexString(
2939
+ input?.attempt_id,
2940
+ 'steer attempt ID',
2941
+ );
2942
+ const targetAttemptId = requiredCodexString(
2943
+ input?.target_attempt_id,
2944
+ 'target attempt ID',
2945
+ );
2946
+ const telegramChatId = requiredCodexString(
2947
+ input?.telegram_chat_id,
2948
+ 'Telegram chat ID',
2949
+ );
2950
+ const telegramMessageId = requiredCodexString(
2951
+ input?.telegram_message_id,
2952
+ 'Telegram message ID',
2953
+ );
2954
+ return db.transaction(() => {
2955
+ const attempt = requireCodexAttempt(
2956
+ attemptId,
2957
+ generationId,
2958
+ );
2959
+ const target = requireCodexAttempt(
2960
+ targetAttemptId,
2961
+ generationId,
2962
+ );
2963
+ if (attempt.method !== 'turn/steer' || target.method !== 'turn/start') {
2964
+ throw codexError(
2965
+ 'Codex linked input must connect steer work to a turn',
2966
+ 'CODEX_LINKED_INPUT_INVALID',
2967
+ );
2968
+ }
2969
+ const existing = db.prepare(`
2970
+ SELECT generation_id, attempt_id, target_attempt_id,
2971
+ telegram_chat_id, telegram_message_id
2972
+ FROM codex_linked_inputs
2973
+ WHERE linked_input_id = ?
2974
+ `).get(linkedInputId);
2975
+ if (existing) {
2976
+ if (
2977
+ existing.generation_id === generationId
2978
+ && existing.attempt_id === attemptId
2979
+ && existing.target_attempt_id === targetAttemptId
2980
+ && existing.telegram_chat_id === telegramChatId
2981
+ && existing.telegram_message_id === telegramMessageId
2982
+ ) {
2983
+ return { changes: 0 };
2984
+ }
2985
+ throw codexError(
2986
+ 'Codex linked input ID was reused with different metadata',
2987
+ 'CODEX_LINKED_INPUT_ID_REUSED',
2988
+ );
2989
+ }
2990
+ return db.prepare(`
2991
+ INSERT INTO codex_linked_inputs (
2992
+ linked_input_id, generation_id, attempt_id, target_attempt_id,
2993
+ telegram_chat_id, telegram_message_id, state, created_ts
2994
+ ) VALUES (?, ?, ?, ?, ?, ?, 'linked', ?)
2995
+ `).run(
2996
+ linkedInputId,
2997
+ generationId,
2998
+ attemptId,
2999
+ targetAttemptId,
3000
+ telegramChatId,
3001
+ telegramMessageId,
3002
+ ts,
3003
+ );
3004
+ })();
3005
+ }
3006
+
3007
+ function claimCodexRetryReservation(input) {
3008
+ const originalAttemptId = requiredCodexString(
3009
+ input?.original_attempt_id,
3010
+ 'original attempt ID',
3011
+ );
3012
+ const retryAttemptId = requiredCodexString(
3013
+ input?.retry_attempt_id,
3014
+ 'retry attempt ID',
3015
+ );
3016
+ const generationId = requiredCodexString(
3017
+ input?.generation_id,
3018
+ 'generation ID',
3019
+ );
3020
+ const stableHostId = requiredCodexString(
3021
+ input?.stable_host_id,
3022
+ 'stable host identity',
3023
+ );
3024
+ const bootSessionId = requiredCodexString(
3025
+ input?.boot_session_id,
3026
+ 'boot-session identity',
3027
+ );
3028
+ const method = requiredCodexString(input?.method, 'retry method');
3029
+ const threadId = optionalCodexString(input?.thread_id, 'thread ID');
3030
+ const sourceMessageId = optionalCodexString(
3031
+ input?.telegram_source_message_id,
3032
+ 'source message ID',
3033
+ );
3034
+ const clientUserMessageId = optionalCodexString(
3035
+ input?.client_user_message_id,
3036
+ 'client user message ID',
3037
+ );
3038
+ const ts = requiredTimestamp(input?.ts ?? Date.now(), 'retry claim timestamp');
3039
+ return db.transaction(() => {
3040
+ const generation = assertCodexGenerationIdentity(
3041
+ generationId,
3042
+ stableHostId,
3043
+ bootSessionId,
3044
+ );
3045
+ assertCodexCheckpointOwner(generation, 'request-write-attempted');
3046
+ const reservation = db.prepare(`
3047
+ SELECT reservation.*,
3048
+ original.generation_id AS original_generation_id,
3049
+ original.session_key AS original_session_key,
3050
+ original_generation.stable_host_id AS original_stable_host_id,
3051
+ original_generation.boot_session_id AS original_boot_session_id,
3052
+ original_generation.state AS original_generation_state
3053
+ FROM codex_retry_reservations reservation
3054
+ JOIN codex_turn_attempts original
3055
+ ON original.attempt_id = reservation.original_attempt_id
3056
+ JOIN codex_generations original_generation
3057
+ ON original_generation.generation_id = original.generation_id
3058
+ WHERE reservation.original_attempt_id = ?
3059
+ `).get(originalAttemptId);
3060
+ if (
3061
+ !reservation
3062
+ || reservation.retry_attempt_id !== retryAttemptId
3063
+ ) {
3064
+ throw codexError(
3065
+ 'Codex retry reservation does not match the claimed attempt',
3066
+ 'CODEX_RETRY_RESERVATION_MISMATCH',
3067
+ );
3068
+ }
3069
+ if (reservation.original_generation_id === generationId) {
3070
+ throw codexError(
3071
+ 'Codex retry requires a new generation after reboot release',
3072
+ 'CODEX_RETRY_NEW_GENERATION_REQUIRED',
3073
+ );
3074
+ }
3075
+ if (
3076
+ reservation.original_session_key !== generation.session_key
3077
+ || reservation.original_stable_host_id !== generation.stable_host_id
3078
+ ) {
3079
+ throw codexError(
3080
+ 'Codex retry generation does not match the original session',
3081
+ 'CODEX_RETRY_RESERVATION_MISMATCH',
3082
+ );
3083
+ }
3084
+ const released = db.prepare(`
3085
+ SELECT 1
3086
+ FROM codex_reboot_releases
3087
+ WHERE stable_host_id = ?
3088
+ AND incident_boot_session_id = ?
3089
+ AND released_boot_session_id = ?
3090
+ `).get(
3091
+ reservation.original_stable_host_id,
3092
+ reservation.original_boot_session_id,
3093
+ generation.boot_session_id,
3094
+ );
3095
+ if (!released) {
3096
+ throw codexError(
3097
+ 'Codex retry requires a durable reboot release',
3098
+ 'CODEX_RETRY_REBOOT_RELEASE_REQUIRED',
3099
+ );
3100
+ }
3101
+ const existing = db.prepare(`
3102
+ SELECT * FROM codex_turn_attempts WHERE attempt_id = ?
3103
+ `).get(retryAttemptId);
3104
+ if (
3105
+ ['claimed', 'dispatched', 'retired'].includes(reservation.state)
3106
+ && existing
3107
+ ) {
3108
+ assertCodexAttemptIdentity(existing, {
3109
+ generation_id: generationId,
3110
+ session_key: generation.session_key,
3111
+ method,
3112
+ thread_id: threadId,
3113
+ telegram_source_message_id: sourceMessageId,
3114
+ client_user_message_id: clientUserMessageId,
3115
+ });
3116
+ return { changes: 0, retryAttemptId };
3117
+ }
3118
+ if (reservation.state !== 'reserved' || existing) {
3119
+ throw codexError(
3120
+ 'Codex retry reservation has already been consumed',
3121
+ 'CODEX_RETRY_ALREADY_CLAIMED',
3122
+ );
3123
+ }
3124
+ db.prepare(`
3125
+ INSERT INTO codex_turn_attempts (
3126
+ attempt_id, generation_id, session_key, method,
3127
+ thread_id, telegram_source_message_id, client_user_message_id,
3128
+ delivery_state, recovery_state, created_ts, updated_ts
3129
+ )
3130
+ VALUES (?, ?, ?, ?, ?, ?, ?, 'prepared', 'prepared', ?, ?)
3131
+ `).run(
3132
+ retryAttemptId,
3133
+ generationId,
3134
+ generation.session_key,
3135
+ method,
3136
+ threadId,
3137
+ sourceMessageId,
3138
+ clientUserMessageId,
3139
+ ts,
3140
+ ts,
3141
+ );
3142
+ db.prepare(`
3143
+ INSERT INTO codex_attempt_checkpoints (
3144
+ generation_id, attempt_id, kind, thread_id,
3145
+ detail_json, ts
3146
+ ) VALUES (?, ?, 'request-prepared', ?, ?, ?)
3147
+ `).run(
3148
+ generationId,
3149
+ retryAttemptId,
3150
+ threadId,
3151
+ checkpointDetail({
3152
+ method,
3153
+ source: sourceMessageId,
3154
+ clientUserMessageId,
3155
+ }),
3156
+ ts,
3157
+ );
3158
+ db.prepare(`
3159
+ UPDATE codex_retry_reservations
3160
+ SET state = 'claimed', claimed_ts = ?
3161
+ WHERE original_attempt_id = ?
3162
+ `).run(ts, originalAttemptId);
3163
+ return { changes: 1, retryAttemptId };
3164
+ })();
3165
+ }
3166
+
3167
+ function markCodexRetryDispatched(input) {
3168
+ const originalAttemptId = requiredCodexString(
3169
+ input?.original_attempt_id,
3170
+ 'original attempt ID',
3171
+ );
3172
+ const retryAttemptId = requiredCodexString(
3173
+ input?.retry_attempt_id,
3174
+ 'retry attempt ID',
3175
+ );
3176
+ const ts = requiredTimestamp(
3177
+ input?.ts ?? Date.now(),
3178
+ 'retry dispatch timestamp',
3179
+ );
3180
+ return db.transaction(() => {
3181
+ const reservation = db.prepare(`
3182
+ SELECT * FROM codex_retry_reservations
3183
+ WHERE original_attempt_id = ?
3184
+ `).get(originalAttemptId);
3185
+ const retry = db.prepare(`
3186
+ SELECT * FROM codex_turn_attempts WHERE attempt_id = ?
3187
+ `).get(retryAttemptId);
3188
+ if (
3189
+ !reservation
3190
+ || reservation.retry_attempt_id !== retryAttemptId
3191
+ || !retry
3192
+ ) {
3193
+ throw codexError(
3194
+ 'Codex retry dispatch has no materialized reservation',
3195
+ 'CODEX_RETRY_RESERVATION_MISMATCH',
3196
+ );
3197
+ }
3198
+ if (reservation.state === 'dispatched') {
3199
+ return { changes: 0, retryAttemptId };
3200
+ }
3201
+ if (
3202
+ reservation.state !== 'claimed'
3203
+ || retry.delivery_state === 'prepared'
3204
+ ) {
3205
+ throw codexError(
3206
+ 'Codex retry was not durably write-attempted',
3207
+ 'CODEX_RETRY_NOT_WRITE_ATTEMPTED',
3208
+ );
3209
+ }
3210
+ const result = db.prepare(`
3211
+ UPDATE codex_retry_reservations
3212
+ SET state = 'dispatched', dispatched_ts = ?
3213
+ WHERE original_attempt_id = ?
3214
+ `).run(ts, originalAttemptId);
3215
+ return { changes: result.changes, retryAttemptId };
3216
+ })();
3217
+ }
3218
+
3219
+ function retireCodexRetryReservation(input) {
3220
+ const originalAttemptId = requiredCodexString(
3221
+ input?.original_attempt_id,
3222
+ 'original attempt ID',
3223
+ );
3224
+ const retryAttemptId = requiredCodexString(
3225
+ input?.retry_attempt_id,
3226
+ 'retry attempt ID',
3227
+ );
3228
+ const ts = requiredTimestamp(
3229
+ input?.ts ?? Date.now(),
3230
+ 'retry retirement timestamp',
3231
+ );
3232
+ return db.transaction(() => {
3233
+ const reservation = db.prepare(`
3234
+ SELECT * FROM codex_retry_reservations
3235
+ WHERE original_attempt_id = ?
3236
+ `).get(originalAttemptId);
3237
+ const retry = db.prepare(`
3238
+ SELECT recovery_state
3239
+ FROM codex_turn_attempts
3240
+ WHERE attempt_id = ?
3241
+ `).get(retryAttemptId);
3242
+ if (
3243
+ !reservation
3244
+ || reservation.retry_attempt_id !== retryAttemptId
3245
+ || !retry
3246
+ ) {
3247
+ throw codexError(
3248
+ 'Codex retry retirement has no materialized reservation',
3249
+ 'CODEX_RETRY_RESERVATION_MISMATCH',
3250
+ );
3251
+ }
3252
+ if (reservation.state === 'retired') {
3253
+ return { changes: 0, retryAttemptId };
3254
+ }
3255
+ if (!['settled', 'cancelled'].includes(retry.recovery_state)) {
3256
+ throw codexError(
3257
+ 'Codex retry attempt is not durably settled',
3258
+ 'CODEX_RETRY_NOT_SETTLED',
3259
+ );
3260
+ }
3261
+ const result = db.prepare(`
3262
+ UPDATE codex_retry_reservations
3263
+ SET state = 'retired', retired_ts = ?
3264
+ WHERE original_attempt_id = ?
3265
+ `).run(ts, originalAttemptId);
3266
+ return { changes: result.changes, retryAttemptId };
3267
+ })();
3268
+ }
3269
+
3270
+ function recordCodexItemEffect(input) {
3271
+ const allowed = new Set([
3272
+ 'generation_id',
3273
+ 'attempt_id',
3274
+ 'item_id',
3275
+ 'item_type',
3276
+ 'state',
3277
+ 'ts',
3278
+ ]);
3279
+ assertExactKeys(
3280
+ input,
3281
+ allowed,
3282
+ 'CODEX_EFFECT_PAYLOAD_REJECTED',
3283
+ 'item effect',
3284
+ );
3285
+ const generationId = requiredCodexString(
3286
+ input?.generation_id,
3287
+ 'generation ID',
3288
+ );
3289
+ const attemptId = requiredCodexString(input?.attempt_id, 'attempt ID');
3290
+ const itemId = requiredCodexString(input?.item_id, 'item ID');
3291
+ const itemType = requiredCodexString(input?.item_type, 'item type');
3292
+ const state = input?.state;
3293
+ if (!['started', 'completed', 'failed'].includes(state)) {
3294
+ throw codexError(
3295
+ 'Codex item effect state is invalid',
3296
+ 'CODEX_EFFECT_PAYLOAD_REJECTED',
3297
+ );
3298
+ }
3299
+ const ts = requiredTimestamp(input?.ts ?? Date.now(), 'item effect timestamp');
3300
+ return db.transaction(() => {
3301
+ const attempt = requireCodexAttempt(attemptId, generationId);
3302
+ const generation = db.prepare(`
3303
+ SELECT * FROM codex_generations WHERE generation_id = ?
3304
+ `).get(generationId);
3305
+ assertCodexCheckpointOwner(generation, 'item-started');
3306
+ const existing = db.prepare(`
3307
+ SELECT generation_id, item_type, state
3308
+ FROM codex_item_effects
3309
+ WHERE attempt_id = ? AND item_id = ?
3310
+ `).get(attemptId, itemId);
3311
+ if (existing) {
3312
+ if (
3313
+ existing.generation_id !== generationId
3314
+ || existing.item_type !== itemType
3315
+ ) {
3316
+ throw codexError(
3317
+ 'Codex item effect identity is immutable',
3318
+ 'CODEX_EFFECT_IDENTITY_MISMATCH',
3319
+ );
3320
+ }
3321
+ if (existing.state === state) return { changes: 0 };
3322
+ }
3323
+ const earlyNotification = (
3324
+ attempt.recovery_state === 'ambiguous'
3325
+ && ['write-attempted', 'response-observed'].includes(
3326
+ attempt.delivery_state,
3327
+ )
3328
+ );
3329
+ if (
3330
+ attempt.turn_id == null
3331
+ || attempt.terminal_status != null
3332
+ || !(attempt.recovery_state === 'active' || earlyNotification)
3333
+ ) {
3334
+ throw codexError(
3335
+ 'Codex item effect requires a live dispatched turn',
3336
+ 'CODEX_EFFECT_SEQUENCE_INVALID',
3337
+ );
3338
+ }
3339
+ if (existing) {
3340
+ if (existing.state !== 'started') {
3341
+ throw codexError(
3342
+ 'Codex terminal item effect cannot regress or change',
3343
+ 'CODEX_EFFECT_STATE_CONFLICT',
3344
+ );
3345
+ }
3346
+ return db.prepare(`
3347
+ UPDATE codex_item_effects
3348
+ SET state = ?, updated_ts = ?
3349
+ WHERE attempt_id = ? AND item_id = ?
3350
+ `).run(state, ts, attemptId, itemId);
3351
+ }
3352
+ return db.prepare(`
3353
+ INSERT INTO codex_item_effects (
3354
+ generation_id, attempt_id, item_id, item_type,
3355
+ state, created_ts, updated_ts
3356
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
3357
+ `).run(
3358
+ generationId,
3359
+ attemptId,
3360
+ itemId,
3361
+ itemType,
3362
+ state,
3363
+ ts,
3364
+ ts,
3365
+ );
3366
+ })();
3367
+ }
3368
+
3369
+ function acquireCodexLease(input) {
3370
+ const generationId = requiredCodexString(
3371
+ input?.generation_id,
3372
+ 'generation ID',
3373
+ );
3374
+ const stableHostId = requiredCodexString(
3375
+ input?.stable_host_id,
3376
+ 'stable host identity',
3377
+ );
3378
+ const bootSessionId = requiredCodexString(
3379
+ input?.boot_session_id,
3380
+ 'boot-session identity',
3381
+ );
3382
+ const ts = requiredTimestamp(input?.ts ?? Date.now(), 'lease timestamp');
3383
+ return db.transaction(() => {
3384
+ assertCodexGenerationIdentity(generationId, stableHostId, bootSessionId);
3385
+ const lease = db.prepare(`
3386
+ SELECT * FROM codex_daemon_lease WHERE singleton = 1
3387
+ `).get();
3388
+ if (
3389
+ lease?.status === 'quarantined'
3390
+ || (
3391
+ lease?.status === 'active'
3392
+ && lease.generation_id !== generationId
3393
+ )
3394
+ ) {
3395
+ throw codexError(
3396
+ 'Codex daemon generation lease is unavailable',
3397
+ lease.status === 'quarantined'
3398
+ ? 'CODEX_CONTAINMENT_QUARANTINED'
3399
+ : 'CODEX_DAEMON_GENERATION_BUSY',
3400
+ );
3401
+ }
3402
+ db.prepare(`
3403
+ INSERT INTO codex_daemon_lease (
3404
+ singleton, generation_id, stable_host_id, boot_session_id,
3405
+ status, quarantine_reason, acquired_ts, updated_ts, released_ts
3406
+ ) VALUES (1, ?, ?, ?, 'active', NULL, ?, ?, NULL)
3407
+ ON CONFLICT(singleton) DO UPDATE SET
3408
+ generation_id = excluded.generation_id,
3409
+ stable_host_id = excluded.stable_host_id,
3410
+ boot_session_id = excluded.boot_session_id,
3411
+ status = 'active',
3412
+ quarantine_reason = NULL,
3413
+ acquired_ts = excluded.acquired_ts,
3414
+ updated_ts = excluded.updated_ts,
3415
+ released_ts = NULL
3416
+ `).run(generationId, stableHostId, bootSessionId, ts, ts);
3417
+ db.prepare(`
3418
+ UPDATE codex_generations
3419
+ SET state = 'active', updated_ts = ?
3420
+ WHERE generation_id = ?
3421
+ `).run(ts, generationId);
3422
+ return db.prepare(`
3423
+ SELECT * FROM codex_daemon_lease WHERE singleton = 1
3424
+ `).get();
3425
+ })();
3426
+ }
3427
+
3428
+ function markCodexContainment(input) {
3429
+ const generationId = requiredCodexString(
3430
+ input?.generation_id,
3431
+ 'generation ID',
3432
+ );
3433
+ const stableHostId = requiredCodexString(
3434
+ input?.stable_host_id,
3435
+ 'stable host identity',
3436
+ );
3437
+ const bootSessionId = requiredCodexString(
3438
+ input?.boot_session_id,
3439
+ 'boot-session identity',
3440
+ );
3441
+ const reason = requiredCodexString(input?.reason, 'containment reason');
3442
+ const ts = requiredTimestamp(
3443
+ input?.ts ?? Date.now(),
3444
+ 'containment timestamp',
3445
+ );
3446
+ return db.transaction(() => {
3447
+ assertCodexGenerationIdentity(generationId, stableHostId, bootSessionId);
3448
+ const lease = db.prepare(`
3449
+ SELECT * FROM codex_daemon_lease WHERE singleton = 1
3450
+ `).get();
3451
+ if (!lease || lease.generation_id !== generationId) {
3452
+ throw codexError(
3453
+ 'Codex containment requires the owned daemon lease',
3454
+ 'CODEX_LEASE_MISMATCH',
3455
+ );
3456
+ }
3457
+ db.prepare(`
3458
+ UPDATE codex_generations
3459
+ SET state = 'containment-failed',
3460
+ containment_reason = ?,
3461
+ updated_ts = ?
3462
+ WHERE generation_id = ?
3463
+ `).run(reason, ts, generationId);
3464
+ markGenerationAttemptsAmbiguous(generationId, ts);
3465
+ return db.prepare(`
3466
+ UPDATE codex_daemon_lease
3467
+ SET status = 'quarantined',
3468
+ quarantine_reason = ?,
3469
+ updated_ts = ?
3470
+ WHERE singleton = 1
3471
+ `).run(reason, ts);
3472
+ })();
3473
+ }
3474
+
3475
+ function retireCodexGeneration(
3476
+ generationId,
3477
+ ts,
3478
+ { onlyIfHealthyStopped = false } = {},
3479
+ ) {
3480
+ const generation = db.prepare(`
3481
+ SELECT * FROM codex_generations WHERE generation_id = ?
3482
+ `).get(generationId);
3483
+ if (!generation) {
3484
+ throw codexError(
3485
+ 'Codex generation does not exist',
3486
+ 'CODEX_GENERATION_NOT_FOUND',
3487
+ );
3488
+ }
3489
+ if (generation.state === 'retired') {
3490
+ return { changes: 0, retired: true };
3491
+ }
3492
+ if (onlyIfHealthyStopped && generation.state !== 'healthy-stopped') {
3493
+ return { changes: 0, retired: false };
3494
+ }
3495
+ const unresolved = db.prepare(`
3496
+ SELECT 1
3497
+ FROM codex_turn_attempts
3498
+ WHERE generation_id = ?
3499
+ AND delivery_state != 'prepared'
3500
+ AND recovery_state NOT IN ('settled', 'cancelled')
3501
+ LIMIT 1
3502
+ `).get(generationId);
3503
+ if (unresolved) {
3504
+ throw codexError(
3505
+ 'Codex generation still owns unresolved delivery work',
3506
+ 'CODEX_RETIREMENT_UNVERIFIED',
3507
+ );
3508
+ }
3509
+ const lease = db.prepare(`
3510
+ SELECT * FROM codex_daemon_lease WHERE singleton = 1
3511
+ `).get();
3512
+ if (
3513
+ lease?.status !== 'active'
3514
+ || lease.generation_id !== generationId
3515
+ || lease.stable_host_id !== generation.stable_host_id
3516
+ || lease.boot_session_id !== generation.boot_session_id
3517
+ ) {
3518
+ throw codexError(
3519
+ 'Codex retirement requires the exact active daemon lease',
3520
+ 'CODEX_CHECKPOINT_STALE_GENERATION',
3521
+ );
3522
+ }
3523
+ const result = db.prepare(`
3524
+ UPDATE codex_generations
3525
+ SET state = 'retired', settled_ts = ?, updated_ts = ?
3526
+ WHERE generation_id = ?
3527
+ AND state != 'retired'
3528
+ `).run(ts, ts, generationId);
3529
+ const released = db.prepare(`
3530
+ UPDATE codex_daemon_lease
3531
+ SET generation_id = NULL,
3532
+ status = 'clear',
3533
+ quarantine_reason = NULL,
3534
+ updated_ts = ?,
3535
+ released_ts = ?
3536
+ WHERE singleton = 1
3537
+ AND generation_id = ?
3538
+ AND stable_host_id = ?
3539
+ AND boot_session_id = ?
3540
+ AND status = 'active'
3541
+ `).run(
3542
+ ts,
3543
+ ts,
3544
+ generationId,
3545
+ generation.stable_host_id,
3546
+ generation.boot_session_id,
3547
+ );
3548
+ if (result.changes !== 1 || released.changes !== 1) {
3549
+ throw codexError(
3550
+ 'Codex retirement lost the exact generation lease',
3551
+ 'CODEX_CHECKPOINT_STALE_GENERATION',
3552
+ );
3553
+ }
3554
+ return { changes: 1, retired: true };
3555
+ }
3556
+
3557
+ function markCodexGenerationRetired(input) {
3558
+ const generationId = requiredCodexString(
3559
+ input?.generation_id,
3560
+ 'generation ID',
3561
+ );
3562
+ const ts = requiredTimestamp(input?.ts ?? Date.now(), 'retirement timestamp');
3563
+ return db.transaction(() => retireCodexGeneration(generationId, ts))();
3564
+ }
3565
+
275
3566
  return {
276
3567
  raw: db,
277
3568
 
@@ -323,38 +3614,117 @@ function wrap(db) {
323
3614
  },
324
3615
 
325
3616
  upsertSession(row) {
326
- return upsertSessionStmt.run({
327
- session_key: row.session_key,
328
- chat_id: String(row.chat_id),
329
- thread_id: row.thread_id ? String(row.thread_id) : null,
330
- claude_session_id: row.claude_session_id,
331
- agent: row.agent || null,
332
- cwd: row.cwd || null,
333
- model: row.model || null,
334
- effort: row.effort || null,
335
- // 0.10.0: pm_backend defaults to 'sdk' if caller doesn't set it.
336
- pm_backend: row.pm_backend || 'sdk',
337
- ts: row.ts || Date.now(),
338
- });
3617
+ return upsertLegacyAndProviderSession(row);
339
3618
  },
340
3619
 
341
3620
  getSession(sessionKey) {
342
3621
  return getSessionStmt.get(sessionKey);
343
3622
  },
344
3623
 
3624
+ upsertProviderSession(row) {
3625
+ return upsertProviderAndLegacySession(row);
3626
+ },
3627
+
3628
+ getProviderSession(sessionKey, namespace) {
3629
+ if (!PROVIDER_NAMESPACES.has(namespace)) {
3630
+ throw new TypeError('provider session namespace is invalid');
3631
+ }
3632
+ return readProviderSession(String(sessionKey), namespace);
3633
+ },
3634
+
3635
+ clearProviderSession(sessionKey, namespace) {
3636
+ if (!PROVIDER_NAMESPACES.has(namespace)) {
3637
+ throw new TypeError('provider session namespace is invalid');
3638
+ }
3639
+ return clearOneProviderSession(String(sessionKey), namespace);
3640
+ },
3641
+
345
3642
  touchSession(sessionKey, ts = Date.now()) {
346
- return touchSessionStmt.run(ts, sessionKey);
3643
+ const row = getSessionStmt.get(sessionKey);
3644
+ const result = touchSessionStmt.run(ts, sessionKey);
3645
+ if (row) {
3646
+ touchProviderSessionStmt.run(
3647
+ ts,
3648
+ sessionKey,
3649
+ claudeNamespaceForStoredBackend(row.pm_backend),
3650
+ );
3651
+ }
3652
+ return result;
347
3653
  },
348
3654
 
349
3655
  clearSessionId(sessionKey) {
350
- return clearSessionIdStmt.run(sessionKey);
3656
+ return clearLegacySession(sessionKey);
351
3657
  },
352
3658
 
353
3659
  // 0.10.0: backend reassignment without resetting other session fields.
354
3660
  // Used when ProcessManager spawns a Process with a different backend
355
3661
  // than the persisted row says (drift event fires too).
356
3662
  setSessionBackend(sessionKey, backend) {
357
- return setSessionBackendStmt.run(backend, sessionKey);
3663
+ const row = getSessionStmt.get(sessionKey);
3664
+ const result = setSessionBackendStmt.run(backend, sessionKey);
3665
+ if (row) {
3666
+ const beforeNamespace = claudeNamespaceForStoredBackend(row.pm_backend);
3667
+ const afterNamespace = claudeNamespaceForStoredBackend(backend);
3668
+ if (beforeNamespace === afterNamespace) {
3669
+ setProviderBackendStmt.run(backend, sessionKey, beforeNamespace);
3670
+ }
3671
+ }
3672
+ return result;
3673
+ },
3674
+
3675
+ createCodexGeneration,
3676
+ recordCodexCheckpoint,
3677
+ recordCodexDeliveryCheckpoint,
3678
+ getCodexAttempt(attemptId) {
3679
+ return db.prepare(`
3680
+ SELECT * FROM codex_turn_attempts WHERE attempt_id = ?
3681
+ `).get(attemptId);
3682
+ },
3683
+ listCodexAttemptCheckpoints(attemptId) {
3684
+ return db.prepare(`
3685
+ SELECT *
3686
+ FROM codex_attempt_checkpoints
3687
+ WHERE attempt_id = ?
3688
+ ORDER BY id
3689
+ `).all(attemptId);
3690
+ },
3691
+ claimCodexDispatchReservation,
3692
+ recordInboundRuntimeSelection,
3693
+ getReplayProviderRecovery,
3694
+ getCodexDispatchReservation(reservationId) {
3695
+ return readCodexDispatchReservation(requiredCodexString(
3696
+ reservationId,
3697
+ 'dispatch reservation ID',
3698
+ ));
3699
+ },
3700
+ finalizeCodexAcceptedSteer,
3701
+ markCodexDispatchDisposition,
3702
+ settleCodexQueuedDispatch,
3703
+ settleCodexStoppedGeneration,
3704
+ linkCodexSteeringInput,
3705
+ claimCodexRetryReservation,
3706
+ markCodexRetryDispatched,
3707
+ retireCodexRetryReservation,
3708
+ recordCodexItemEffect,
3709
+ acquireCodexLease,
3710
+ getCodexLease() {
3711
+ return db.prepare(`
3712
+ SELECT * FROM codex_daemon_lease WHERE singleton = 1
3713
+ `).get();
3714
+ },
3715
+ markCodexContainment,
3716
+ markCodexGenerationRetired,
3717
+ reconstructCodexRecovery(input) {
3718
+ return reconstructCodexRecovery(db, input);
3719
+ },
3720
+ listUnresolvedCodexAttempts(input) {
3721
+ return listUnresolvedCodexAttempts(db, input);
3722
+ },
3723
+ reconcileCodexAttempt(input) {
3724
+ return reconcileCodexAttempt(db, input);
3725
+ },
3726
+ pruneCodexOperationalData(options) {
3727
+ return pruneCodexOperationalData(db, options);
358
3728
  },
359
3729
 
360
3730
  getMessage(chatId, msgId) {
@@ -526,13 +3896,42 @@ function wrap(db) {
526
3896
  return deleteChatToolDecisionStmt.run(String(bot_name), String(chat_id), id);
527
3897
  },
528
3898
 
3899
+ logConfigChanges(rows) {
3900
+ if (!Array.isArray(rows) || rows.length === 0) {
3901
+ throw new TypeError('config change rows are required');
3902
+ }
3903
+ return db.transaction(() => rows.map((row) => logConfigChangeStmt.run({
3904
+ chat_id: String(row.chat_id),
3905
+ thread_id: row.thread_id ? String(row.thread_id) : null,
3906
+ field: row.field,
3907
+ old_value: configChangeValue(row.old_value, {
3908
+ nullable: true,
3909
+ label: 'old_value',
3910
+ }),
3911
+ new_value: configChangeValue(row.new_value, {
3912
+ nullable: false,
3913
+ label: 'new_value',
3914
+ }),
3915
+ user_id: row.user_id || null,
3916
+ user: row.user || null,
3917
+ source: row.source || 'command',
3918
+ ts: row.ts || Date.now(),
3919
+ })))();
3920
+ },
3921
+
529
3922
  logConfigChange(row) {
530
3923
  return logConfigChangeStmt.run({
531
3924
  chat_id: String(row.chat_id),
532
3925
  thread_id: row.thread_id ? String(row.thread_id) : null,
533
3926
  field: row.field,
534
- old_value: row.old_value ?? null,
535
- new_value: row.new_value,
3927
+ old_value: configChangeValue(row.old_value, {
3928
+ nullable: true,
3929
+ label: 'old_value',
3930
+ }),
3931
+ new_value: configChangeValue(row.new_value, {
3932
+ nullable: false,
3933
+ label: 'new_value',
3934
+ }),
536
3935
  user_id: row.user_id || null,
537
3936
  user: row.user || null,
538
3937
  source: row.source || 'command',