@cortexkit/opencode-magic-context 0.49.15 → 0.49.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auto-search-worker.js +76 -63
- package/dist/chunk-embedding-write-worker.js +12 -10
- package/dist/{compaction-marker-candidates-v5c6997n.js → compaction-marker-candidates-n4krhat4.js} +2 -2
- package/dist/compaction-marker-candidates-worker.js +2 -2
- package/dist/config/schema/magic-context.d.ts +343 -343
- package/dist/config/schema/magic-context.d.ts.map +1 -1
- package/dist/{conflict-warning-hook-mgq17yr3.js → conflict-warning-hook-4pmz879v.js} +6 -6
- package/dist/{context-0m46day0.js → context-yhetp0kn.js} +297 -141
- package/dist/embedding-backfill-fmz50tsy.js +24 -0
- package/dist/embedding-worker-process.js +1 -1
- package/dist/embedding-worker.js +5 -5
- package/dist/features/magic-context/compartment-chunk-embedding.d.ts +1 -1
- package/dist/features/magic-context/compartment-chunk-embedding.d.ts.map +1 -1
- package/dist/features/magic-context/git-commits/indexer.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-openai.d.ts.map +1 -1
- package/dist/features/magic-context/memory/project-identity-cache.d.ts +2 -0
- package/dist/features/magic-context/memory/project-identity-cache.d.ts.map +1 -1
- package/dist/features/magic-context/message-fts-rowid-map.d.ts +2 -2
- package/dist/features/magic-context/message-fts-rowid-map.d.ts.map +1 -1
- package/dist/features/magic-context/message-fts-session-filter.d.ts +2 -0
- package/dist/features/magic-context/message-fts-session-filter.d.ts.map +1 -1
- package/dist/features/magic-context/message-index-async.d.ts +1 -1
- package/dist/features/magic-context/message-index-async.d.ts.map +1 -1
- package/dist/features/magic-context/message-index.d.ts +5 -0
- package/dist/features/magic-context/message-index.d.ts.map +1 -1
- package/dist/features/magic-context/message-time-backfill.d.ts +2 -2
- package/dist/features/magic-context/message-time-backfill.d.ts.map +1 -1
- package/dist/features/magic-context/search.d.ts +6 -1
- package/dist/features/magic-context/search.d.ts.map +1 -1
- package/dist/features/magic-context/storage-db.d.ts +2 -1
- package/dist/features/magic-context/storage-db.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-shared.d.ts +2 -2
- package/dist/features/magic-context/storage-meta-shared.d.ts.map +1 -1
- package/dist/features/magic-context/storage-session-tables.d.ts +9 -1
- package/dist/features/magic-context/storage-session-tables.d.ts.map +1 -1
- package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
- package/dist/hooks/magic-context/auto-search-deadline.d.ts +2 -1
- package/dist/hooks/magic-context/auto-search-deadline.d.ts.map +1 -1
- package/dist/hooks/magic-context/auto-search-hint.d.ts +3 -0
- package/dist/hooks/magic-context/auto-search-hint.d.ts.map +1 -1
- package/dist/hooks/magic-context/auto-search-runner.d.ts.map +1 -1
- package/dist/hooks/magic-context/auto-search-worker-client.d.ts +10 -2
- package/dist/hooks/magic-context/auto-search-worker-client.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-historian.d.ts +4 -2
- package/dist/hooks/magic-context/compartment-runner-historian.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-validation.d.ts.map +1 -1
- package/dist/hooks/magic-context/emergency-drop.d.ts.map +1 -1
- package/dist/hooks/magic-context/historian-host-runner.d.ts.map +1 -1
- package/dist/hooks/magic-context/historian-prompt-fit.d.ts +2 -1
- package/dist/hooks/magic-context/historian-prompt-fit.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook.d.ts.map +1 -1
- package/dist/hooks/magic-context/lkg-recovery-fence.d.ts.map +1 -1
- package/dist/hooks/magic-context/raw-ordinal-warmup.d.ts +26 -0
- package/dist/hooks/magic-context/raw-ordinal-warmup.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
- package/dist/hooks/magic-context/recomp-orchestrator.d.ts +3 -3
- package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -1
- package/dist/hooks/magic-context/system-prompt-diagnostics.d.ts +8 -0
- package/dist/hooks/magic-context/system-prompt-diagnostics.d.ts.map +1 -0
- package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
- package/dist/hooks/magic-context/tag-messages.d.ts +6 -1
- package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
- package/dist/hooks/magic-context/todo-view.d.ts +2 -2
- package/dist/hooks/magic-context/tool-drop-target.d.ts +3 -2
- package/dist/hooks/magic-context/tool-drop-target.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-stage-logger.d.ts +3 -0
- package/dist/hooks/magic-context/transform-stage-logger.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts +1 -0
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/{index-3kdm78zp.js → index-24fezq97.js} +4 -4
- package/dist/{index-a8bvhwqs.js → index-2vxtcb95.js} +32 -3
- package/dist/{index-f71thw8j.js → index-4s3t0nd0.js} +1 -1
- package/dist/{index-ts1hjk1h.js → index-4yt7dtf4.js} +453 -227
- package/dist/{index-mc1cqza6.js → index-5a1ex6x8.js} +5 -5
- package/dist/{index-fkx2qnvw.js → index-6x57ar45.js} +2 -2
- package/dist/{index-dyvbsvga.js → index-a6mc0v4d.js} +25 -11
- package/dist/{index-snp5wbge.js → index-a7c4r5re.js} +160 -749
- package/dist/{index-42vcjcwe.js → index-aabzay3h.js} +180 -103
- package/dist/{index-3kx0kg0f.js → index-b5vqckbw.js} +14 -8
- package/dist/{v2/server-wafzvyhg.js → index-beht37jy.js} +8 -0
- package/dist/{index-e20m7284.js → index-bjcnntmz.js} +3 -3
- package/dist/{index-hqkczed7.js → index-c5pytzne.js} +22 -2
- package/dist/{index-thvnpfsv.js → index-cwzfcbtq.js} +967 -88
- package/dist/{index-yxycg7fs.js → index-fb2qxs2y.js} +26 -18
- package/dist/{index-mht7bq0z.js → index-hqhm3wke.js} +32 -8
- package/dist/{index-e4dsjj1p.js → index-jn12fvvh.js} +1 -1
- package/dist/index-kaqs6h31.js +58 -0
- package/dist/{index-n6m03s3n.js → index-ke416mdc.js} +1 -1
- package/dist/index-kmdj7dsq.js +96 -0
- package/dist/{index-1d289pq2.js → index-kq82mtg0.js} +2 -2
- package/dist/{index-n78avt2p.js → index-n37sebck.js} +1 -1
- package/dist/{index-1vc7jrkg.js → index-nkpmm9q8.js} +3 -3
- package/dist/{index-944w16m0.js → index-p7m7j0cp.js} +25 -17
- package/dist/{index-wk0n1cgt.js → index-qn7xstt2.js} +2 -2
- package/dist/{index-saews2s3.js → index-rr2bb45z.js} +1 -1
- package/dist/{index-mmfa86mt.js → index-s734jcsr.js} +169 -149
- package/dist/{index-dgs16axy.js → index-s74zqahq.js} +1 -1
- package/dist/{index-bxdg2nbt.js → index-v8awdgvd.js} +4 -4
- package/dist/{index-xn6q9pg6.js → index-vrzjhftj.js} +36 -76
- package/dist/{index-1sarmj9z.js → index-wgypt3cs.js} +6 -6
- package/dist/{index-y1qysm3q.js → index-ze7mvk54.js} +11 -8
- package/dist/index.js +47 -45
- package/dist/message-fts-rowid-map-17s0dfn4.js +25 -0
- package/dist/{message-index-bounded-vtgpq6b4.js → message-index-bounded-1633vc7f.js} +17 -17
- package/dist/{message-index-a6gztaqx.js → message-index-dprebmtt.js} +15 -15
- package/dist/{message-time-backfill-qjge9044.js → message-time-backfill-73mmm76q.js} +5 -5
- package/dist/migration-worker.js +9 -7
- package/dist/plugin/dream-timer.d.ts +11 -0
- package/dist/plugin/dream-timer.d.ts.map +1 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
- package/dist/{project-embedding-registry-x45bv3qr.js → project-embedding-registry-z74mhqkf.js} +12 -10
- package/dist/raw-ordinal-warmup-worker.js +40 -10
- package/dist/{read-session-chunk-3ae3nfdn.js → read-session-chunk-4zp09ejb.js} +9 -9
- package/dist/{read-session-raw-m56hc7v2.js → read-session-raw-rsc8c7qy.js} +2 -2
- package/dist/{recomp-orchestrator-nwzcfhx4.js → recomp-orchestrator-z3ecszx6.js} +26 -26
- package/dist/{search-deajrd90.js → search-6mee3vty.js} +23 -22
- package/dist/search-archive-znyy44ve.js +25 -0
- package/dist/{send-session-notification-24r4esbh.js → send-session-notification-6ve0e8p4.js} +5 -5
- package/dist/shared/historian-cache.d.ts +8 -0
- package/dist/shared/historian-cache.d.ts.map +1 -0
- package/dist/shared/model-suggestion-retry.d.ts +4 -0
- package/dist/shared/model-suggestion-retry.d.ts.map +1 -1
- package/dist/shared/resolve-fallbacks.d.ts +3 -3
- package/dist/shared/rpc-discovery-cleanup.d.ts +3 -0
- package/dist/shared/rpc-discovery-cleanup.d.ts.map +1 -0
- package/dist/shared/rpc-server.d.ts +2 -0
- package/dist/shared/rpc-server.d.ts.map +1 -1
- package/dist/shared/search-text.d.ts +8 -0
- package/dist/shared/search-text.d.ts.map +1 -0
- package/dist/shared/sqlite.d.ts +4 -2
- package/dist/shared/sqlite.d.ts.map +1 -1
- package/dist/shared/storage-file-mutex.d.ts.map +1 -1
- package/dist/shared/subagent-runner.d.ts +2 -0
- package/dist/shared/subagent-runner.d.ts.map +1 -1
- package/dist/shared/system-directive.d.ts +2 -0
- package/dist/shared/system-directive.d.ts.map +1 -1
- package/dist/{sqlite-4pqyynrs.js → sqlite-zf72c52d.js} +1 -1
- package/dist/{storage-memory-j3v3hc83.js → storage-memory-y3qmy00r.js} +2 -2
- package/dist/{storage-tags-s5bg1ahx.js → storage-tags-8qns3012.js} +8 -4
- package/dist/tools/ctx-expand/search-archive.d.ts +5 -1
- package/dist/tools/ctx-expand/search-archive.d.ts.map +1 -1
- package/dist/{transform-postprocess-phase-ne3z1zmt.js → transform-postprocess-phase-3ydzxa62.js} +26 -26
- package/dist/v2/auto-search-worker.js +76 -63
- package/dist/v2/chunk-embedding-write-worker.js +12 -10
- package/dist/v2/{compaction-marker-candidates-1vqwr3s5.js → compaction-marker-candidates-ebx3388k.js} +2 -2
- package/dist/v2/compaction-marker-candidates-worker.js +2 -2
- package/dist/v2/{context-4qczzck7.js → context-jn3zpeqh.js} +909 -454
- package/dist/v2/embedding-backfill-7c7yr69y.js +23 -0
- package/dist/v2/embedding-worker-process.js +1 -1
- package/dist/v2/embedding-worker.js +5 -5
- package/dist/v2/fold/native-replay.d.ts +11 -2
- package/dist/v2/fold/native-replay.d.ts.map +1 -1
- package/dist/v2/fold/policy.d.ts.map +1 -1
- package/dist/v2/fold/restore.d.ts.map +1 -1
- package/dist/v2/hooks/context.d.ts.map +1 -1
- package/dist/v2/hooks/hidden-child.d.ts +8 -0
- package/dist/v2/hooks/hidden-child.d.ts.map +1 -1
- package/dist/v2/hooks/types.d.ts +17 -0
- package/dist/v2/hooks/types.d.ts.map +1 -1
- package/dist/v2/message-fts-rowid-map-wfx67gfd.js +24 -0
- package/dist/v2/{message-index-bounded-rxxznd34.js → message-index-bounded-hpejqyd8.js} +17 -17
- package/dist/v2/{message-index-f947qtbz.js → message-index-pxehjpzx.js} +15 -15
- package/dist/v2/{message-time-backfill-bsteyc4d.js → message-time-backfill-04t0km1z.js} +5 -5
- package/dist/v2/migration-worker.js +9 -7
- package/dist/v2/{project-embedding-registry-0msp56p8.js → project-embedding-registry-gnkj56xp.js} +12 -10
- package/dist/v2/raw-ordinal-warmup-worker.js +40 -10
- package/dist/v2/{read-session-chunk-rq9nx1nn.js → read-session-chunk-zdppn4bj.js} +9 -9
- package/dist/v2/{read-session-raw-b3tf6ra1.js → read-session-raw-yaegtjzh.js} +2 -2
- package/dist/v2/{recomp-orchestrator-5zhxtwha.js → recomp-orchestrator-cmqnfpp3.js} +26 -26
- package/dist/v2/{search-n8wqxb1j.js → search-66842dya.js} +23 -22
- package/dist/v2/search-archive-gfh11vs0.js +24 -0
- package/dist/v2/{send-session-notification-8ghn9tnv.js → send-session-notification-3pxn658x.js} +5 -5
- package/dist/v2/{server-yrvqaswb.js → server-1wy7b9de.js} +25 -11
- package/dist/v2/{server-th4y11y7.js → server-271qxsf5.js} +6 -6
- package/dist/v2/{server-e298hswg.js → server-3rhpvy84.js} +967 -88
- package/dist/v2/{server-qhfvvfxj.js → server-46neh5fa.js} +14 -8
- package/dist/v2/{server-rrqpvwqd.js → server-6tmj0s7j.js} +2 -2
- package/dist/v2/{server-hek6z3w6.js → server-8by8qfec.js} +11 -8
- package/dist/v2/{server-vbe4se57.js → server-a9xqj88f.js} +1 -1
- package/dist/{index-wafzvyhg.js → v2/server-beht37jy.js} +8 -0
- package/dist/v2/{server-05xjtq6c.js → server-d04vrx0j.js} +1 -1
- package/dist/v2/server-ec4ca92p.js +96 -0
- package/dist/v2/{server-dd0tz6cn.js → server-fw7wrk01.js} +160 -750
- package/dist/v2/{server-zs8d0s3b.js → server-g3qma5c0.js} +1 -1
- package/dist/v2/{server-k2bzedxk.js → server-gpvay0xz.js} +2 -2
- package/dist/v2/{server-kmbarfmn.js → server-gtfhttbr.js} +26 -18
- package/dist/v2/{server-g10mdym6.js → server-h1v0pyxb.js} +3 -3
- package/dist/v2/{server-4gn3z5an.js → server-hy3d3vsx.js} +1 -1
- package/dist/v2/server-kaqs6h31.js +58 -0
- package/dist/v2/{server-ehyava47.js → server-kh7dpa1a.js} +25 -17
- package/dist/v2/{server-6cdy4rc0.js → server-my7k4eds.js} +4 -4
- package/dist/v2/{server-d8tj10db.js → server-n1prf1g6.js} +1 -1
- package/dist/v2/{server-n78avt2p.js → server-n37sebck.js} +1 -1
- package/dist/v2/{server-zc3e54vy.js → server-nj659894.js} +6 -6
- package/dist/v2/{server-gvkm8xaj.js → server-rmygf5pn.js} +32 -3
- package/dist/v2/{server-0ppsweh6.js → server-tgnbmwah.js} +22 -2
- package/dist/v2/{server-cfbr1s2x.js → server-vsw8tmm6.js} +36 -76
- package/dist/v2/{server-prg55b7k.js → server-xj136kt8.js} +23 -3
- package/dist/v2/{server-atbc19ab.js → server-zyzawhdw.js} +32 -8
- package/dist/v2/{server-ef89gdsx.js → server-zzkj2fch.js} +4 -4
- package/dist/v2/server.js +5 -5
- package/dist/v2/{sqlite-4pqyynrs.js → sqlite-zf72c52d.js} +1 -1
- package/dist/v2/{storage-memory-j3v3hc83.js → storage-memory-y3qmy00r.js} +2 -2
- package/dist/v2/{storage-tags-hcc2adpf.js → storage-tags-1h8k6ra9.js} +8 -4
- package/dist/v2/store-reader.d.ts +2 -0
- package/dist/v2/store-reader.d.ts.map +1 -1
- package/dist/v2/{wrapup-orchestrator-65pwye0d.js → wrapup-orchestrator-xs8redz9.js} +26 -26
- package/dist/wrapup-orchestrator-j7d7ry5b.js +48 -0
- package/package.json +1 -1
- package/src/config/latch-permanence-guard.test.ts +4 -0
- package/src/config/schema/magic-context.ts +14 -6
- package/src/shared/historian-cache.test.ts +69 -0
- package/src/shared/historian-cache.ts +55 -0
- package/src/shared/model-resolution.test.ts +22 -0
- package/src/shared/model-suggestion-retry.ts +11 -3
- package/src/shared/resolve-fallbacks.test.ts +2 -3
- package/src/shared/resolve-fallbacks.ts +3 -3
- package/src/shared/rpc-discovery-cleanup.test.ts +57 -0
- package/src/shared/rpc-discovery-cleanup.ts +57 -0
- package/src/shared/rpc-server.ts +36 -13
- package/src/shared/search-text.test.ts +47 -0
- package/src/shared/search-text.ts +63 -0
- package/src/shared/sqlite.test.ts +56 -0
- package/src/shared/sqlite.ts +51 -2
- package/src/shared/storage-file-mutex-review.test.ts +22 -2
- package/src/shared/storage-file-mutex.ts +11 -1
- package/src/shared/subagent-runner.ts +2 -0
- package/src/shared/system-directive.ts +19 -1
- package/src/shared/tag-transcript.ts +12 -4
- package/dist/embedding-backfill-5e1t5wha.js +0 -22
- package/dist/v2/embedding-backfill-jmaf8zrr.js +0 -21
- package/dist/wrapup-orchestrator-28xzsj5q.js +0 -48
|
@@ -9,34 +9,46 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
getHarness2
|
|
11
11
|
} from "./index-pavzsdc6.js";
|
|
12
|
+
import {
|
|
13
|
+
getMagicContextStorageDir
|
|
14
|
+
} from "./index-g2q4htwp.js";
|
|
15
|
+
import {
|
|
16
|
+
isPidAlive,
|
|
17
|
+
readProcessStartTime
|
|
18
|
+
} from "./index-nj7yykkx.js";
|
|
12
19
|
import {
|
|
13
20
|
removeSystemReminders,
|
|
21
|
+
removeNestedBlocks,
|
|
14
22
|
hasMeaningfulUserText2,
|
|
15
23
|
extractTexts2
|
|
16
|
-
} from "./index-
|
|
17
|
-
import {
|
|
18
|
-
getMagicContextStorageDir
|
|
19
|
-
} from "./index-g2q4htwp.js";
|
|
24
|
+
} from "./index-c5pytzne.js";
|
|
20
25
|
import {
|
|
26
|
+
Database2,
|
|
27
|
+
getSqliteDatabasePath2,
|
|
28
|
+
prepareCachedStatement2,
|
|
21
29
|
withSqliteBackgroundWriter2,
|
|
22
30
|
withoutSqliteTransformPass2
|
|
23
|
-
} from "./index-
|
|
31
|
+
} from "./index-beht37jy.js";
|
|
24
32
|
import {
|
|
25
|
-
|
|
26
|
-
|
|
33
|
+
resolveOpenCodeDbPath,
|
|
34
|
+
assertOpenCodeStoreGeneration
|
|
35
|
+
} from "./index-58e3y90n.js";
|
|
27
36
|
import {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
messageFtsOrdinalRangeIsMapped,
|
|
31
|
-
deleteUnmappedMessageFtsRows
|
|
32
|
-
} from "./index-dyvbsvga.js";
|
|
37
|
+
cleanUserText2
|
|
38
|
+
} from "./index-p7m7j0cp.js";
|
|
33
39
|
import {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
recordMessageFtsRowid2,
|
|
41
|
+
recordIndexedMessageTime2,
|
|
42
|
+
messageFtsOrdinalRangeIsMapped2,
|
|
43
|
+
getMessageFtsRowidMapBackfillProgress2,
|
|
44
|
+
deleteUnmappedMessageFtsRows2
|
|
45
|
+
} from "./index-a6mc0v4d.js";
|
|
37
46
|
import {
|
|
38
47
|
deleteChunkEmbedBackoffForSession
|
|
39
|
-
} from "./index-
|
|
48
|
+
} from "./index-b5vqckbw.js";
|
|
49
|
+
import {
|
|
50
|
+
sameProjectDirectory
|
|
51
|
+
} from "./index-knvkbfvh.js";
|
|
40
52
|
import {
|
|
41
53
|
closeQuietly
|
|
42
54
|
} from "./index-8ajstryz.js";
|
|
@@ -198,11 +210,11 @@ function clearCompressionDepthRange(db, sessionId, startOrdinal, endOrdinal) {
|
|
|
198
210
|
}
|
|
199
211
|
|
|
200
212
|
// src/features/magic-context/message-index.ts
|
|
201
|
-
import { createHash as
|
|
213
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
202
214
|
|
|
203
215
|
// src/hooks/magic-context/lkg-recovery-fence.ts
|
|
204
216
|
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
205
|
-
import { lstatSync, readdirSync as readdirSync2, readFileSync as readFileSync2, rmSync as rmSync2 } from "node:fs";
|
|
217
|
+
import { lstatSync, readdirSync as readdirSync2, readFileSync as readFileSync2, rmdirSync, rmSync as rmSync2 } from "node:fs";
|
|
206
218
|
import { basename, dirname, join as join2 } from "node:path";
|
|
207
219
|
|
|
208
220
|
// src/shared/storage-file-mutex.ts
|
|
@@ -247,7 +259,16 @@ function withStorageFileMutex(directory, work, timeoutMs = 1000) {
|
|
|
247
259
|
const birth = processBirth(process.pid);
|
|
248
260
|
const ticket = { choosing: true, number: 0, birth, checksum: "" };
|
|
249
261
|
const encodedTicket = () => JSON.stringify({ ...ticket, checksum: ticketChecksum(name, ticket) });
|
|
250
|
-
|
|
262
|
+
for (let attempt = 0;; attempt++) {
|
|
263
|
+
try {
|
|
264
|
+
writeStorageFileSync(ownPath, encodedTicket(), { flag: "wx", forcePrivate: true });
|
|
265
|
+
break;
|
|
266
|
+
} catch (error) {
|
|
267
|
+
if (error.code !== "ENOENT" || attempt >= 2)
|
|
268
|
+
throw error;
|
|
269
|
+
ensurePrivateStorageDirectorySync(directory);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
251
272
|
const deadline = performance.now() + timeoutMs;
|
|
252
273
|
const peers = () => {
|
|
253
274
|
const found = [];
|
|
@@ -585,15 +606,767 @@ function commitLkgWithRecoveryFence(args) {
|
|
|
585
606
|
function clearLkgRecoveryFence(sessionId, remove, shouldClear) {
|
|
586
607
|
if (!durableFencesSupported())
|
|
587
608
|
return remove?.();
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
609
|
+
const path = lkgRecoveryFencePath(sessionId);
|
|
610
|
+
const missing = (file) => {
|
|
611
|
+
try {
|
|
612
|
+
lstatSync(file);
|
|
613
|
+
return false;
|
|
614
|
+
} catch (error) {
|
|
615
|
+
return error.code === "ENOENT";
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
if (!remove && !shouldClear && missing(path) && missing(`${path}.locks`)) {
|
|
594
619
|
clearLkgFenceProcessState(sessionId);
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
try {
|
|
623
|
+
return locked(sessionId, () => {
|
|
624
|
+
const result = remove?.();
|
|
625
|
+
if (shouldClear && !shouldClear(result))
|
|
626
|
+
return result;
|
|
627
|
+
rmSync2(lkgRecoveryFencePath(sessionId), { force: true });
|
|
628
|
+
syncStorageDirectorySync(dirname(lkgRecoveryFencePath(sessionId)));
|
|
629
|
+
clearLkgFenceProcessState(sessionId);
|
|
630
|
+
return result;
|
|
631
|
+
});
|
|
632
|
+
} finally {
|
|
633
|
+
try {
|
|
634
|
+
rmdirSync(`${path}.locks`);
|
|
635
|
+
} catch {}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// src/shared/search-text.ts
|
|
640
|
+
function removeTaskNotifications(text) {
|
|
641
|
+
return removeNestedBlocks(text, ["task", "task_cohort"]);
|
|
642
|
+
}
|
|
643
|
+
function cleanSearchText(text) {
|
|
644
|
+
return removeNestedBlocks(text, ["system-reminder", "task", "task_cohort"]).replace(/\s+/g, " ").trim();
|
|
645
|
+
}
|
|
646
|
+
var QUERY_STOP_WORDS = new Set("a an and are as at be been by can could did do does for from how i in is it me my of on or our please should that the their them there these they this to us was we were what when where which who why will with would you your".split(" "));
|
|
647
|
+
function terms(text) {
|
|
648
|
+
return [
|
|
649
|
+
...new Set((text.toLowerCase().match(/[\p{L}\p{N}_]+/gu) ?? []).filter((term) => !QUERY_STOP_WORDS.has(term)))
|
|
650
|
+
];
|
|
651
|
+
}
|
|
652
|
+
function keywordRelevance(query, content) {
|
|
653
|
+
const needles = terms(query);
|
|
654
|
+
if (needles.length === 0)
|
|
655
|
+
return 0;
|
|
656
|
+
const words = new Set(terms(cleanSearchText(content)));
|
|
657
|
+
return 0.8 * needles.filter((term) => words.has(term)).length / needles.length;
|
|
658
|
+
}
|
|
659
|
+
function matchedSearchSnippet(content, query, cap = 220) {
|
|
660
|
+
const text = cleanSearchText(content);
|
|
661
|
+
const needles = new Set(terms(query));
|
|
662
|
+
const matches = [...text.matchAll(/[\p{L}\p{N}_]+/gu)].filter((match) => needles.has(match[0].toLowerCase()));
|
|
663
|
+
if (matches.length === 0)
|
|
664
|
+
return "";
|
|
665
|
+
let bestStart = matches[0].index;
|
|
666
|
+
let bestCoverage = -1;
|
|
667
|
+
for (const match of matches) {
|
|
668
|
+
const start = Math.max(0, match.index - Math.floor(cap / 5));
|
|
669
|
+
const coverage = keywordRelevance(query, text.slice(start, start + cap));
|
|
670
|
+
if (coverage > bestCoverage) {
|
|
671
|
+
bestStart = start;
|
|
672
|
+
bestCoverage = coverage;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
if (bestStart > 0) {
|
|
676
|
+
const boundary = text.indexOf(" ", bestStart);
|
|
677
|
+
if (boundary >= 0 && boundary < matches[matches.length - 1].index)
|
|
678
|
+
bestStart = boundary + 1;
|
|
679
|
+
}
|
|
680
|
+
const passage = text.slice(bestStart, bestStart + cap).trim();
|
|
681
|
+
return `${bestStart > 0 ? "…" : ""}${passage}${bestStart + cap < text.length ? "…" : ""}`;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// src/v2/store-reader.ts
|
|
685
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
686
|
+
import { statSync } from "node:fs";
|
|
687
|
+
import { hostname } from "node:os";
|
|
688
|
+
import { resolve } from "node:path";
|
|
689
|
+
function gaDatabasePath(dataHome, channel = "latest", env = process.env) {
|
|
690
|
+
return resolveOpenCodeDbPath("v2", { dataHome, channel, env }).path;
|
|
691
|
+
}
|
|
692
|
+
var RAW_MESSAGE_TYPES = [
|
|
693
|
+
"user",
|
|
694
|
+
"synthetic",
|
|
695
|
+
"assistant",
|
|
696
|
+
"skill",
|
|
697
|
+
"shell",
|
|
698
|
+
"system"
|
|
699
|
+
];
|
|
700
|
+
var RAW_MESSAGE_TYPE_PARAMETERS = RAW_MESSAGE_TYPES.map(() => "?").join(", ");
|
|
701
|
+
var V2_MESSAGE_PAGE_SQL = `WITH bounds AS (
|
|
702
|
+
SELECT
|
|
703
|
+
CASE WHEN ? = 0 THEN -1 ELSE COALESCE((
|
|
704
|
+
SELECT seq FROM session_message
|
|
705
|
+
WHERE session_id = ? AND type IN (${RAW_MESSAGE_TYPE_PARAMETERS})
|
|
706
|
+
ORDER BY seq ASC LIMIT 1 OFFSET ?
|
|
707
|
+
), (SELECT MAX(seq) FROM session_message WHERE session_id = ?), -1) END AS after_seq,
|
|
708
|
+
COALESCE((
|
|
709
|
+
SELECT seq FROM session_message
|
|
710
|
+
WHERE session_id = ? AND type IN (${RAW_MESSAGE_TYPE_PARAMETERS})
|
|
711
|
+
ORDER BY seq ASC LIMIT 1 OFFSET ?
|
|
712
|
+
), ?) AS watermark_seq
|
|
713
|
+
)
|
|
714
|
+
SELECT id, session_id, type, seq, time_created, data FROM session_message, bounds
|
|
715
|
+
WHERE session_id = ?
|
|
716
|
+
AND type IN (${RAW_MESSAGE_TYPE_PARAMETERS})
|
|
717
|
+
AND seq > bounds.after_seq
|
|
718
|
+
AND seq <= bounds.watermark_seq
|
|
719
|
+
ORDER BY seq ASC LIMIT ?`;
|
|
720
|
+
var V2_STORE_READER_DEBUG_COUNTER_KEY = "magic-context.v2.store-reader-debug";
|
|
721
|
+
var debugSymbol = Symbol.for(V2_STORE_READER_DEBUG_COUNTER_KEY);
|
|
722
|
+
var debugGlobal = globalThis;
|
|
723
|
+
var QUERY_STATEMENTS = {
|
|
724
|
+
messagePage: V2_MESSAGE_PAGE_SQL,
|
|
725
|
+
messageCount: "SELECT COUNT(*) AS count FROM session_message WHERE session_id = ? AND type IN (?)",
|
|
726
|
+
storedMessageCount: "SELECT COUNT(*) AS count FROM session_message WHERE session_id = ?",
|
|
727
|
+
messageById: "SELECT id, session_id, type, seq, time_created, data FROM session_message WHERE session_id = ? AND id = ? AND type IN (?) LIMIT 1",
|
|
728
|
+
messageExistsById: "SELECT 1 FROM session_message WHERE id = ? AND session_id = ? AND type IN (?) LIMIT 1",
|
|
729
|
+
messageOrdinalById: `WITH target AS (
|
|
730
|
+
SELECT seq FROM session_message WHERE session_id = ? AND id = ? AND type IN (?) LIMIT 1
|
|
731
|
+
)
|
|
732
|
+
SELECT (SELECT COUNT(*) FROM session_message WHERE session_id = ? AND type IN (?) AND seq <= target.seq) AS ordinal FROM target`,
|
|
733
|
+
messageIdOrdinals: "SELECT id FROM session_message WHERE session_id = ? AND type IN (?) ORDER BY seq ASC LIMIT ? OFFSET ?",
|
|
734
|
+
messageOrdinalPage: "SELECT id, type, seq, json_valid(data) AS valid FROM session_message WHERE session_id = ? AND seq > ? ORDER BY seq ASC, id ASC LIMIT ?",
|
|
735
|
+
rawRowsThrough: "SELECT id, session_id, type, seq, time_created, data FROM session_message WHERE session_id = ? AND type IN (?) AND seq <= ? ORDER BY seq DESC LIMIT ?"
|
|
736
|
+
};
|
|
737
|
+
function debugCounters() {
|
|
738
|
+
const counters = debugGlobal[debugSymbol] ??= {
|
|
739
|
+
decodedRows: 0,
|
|
740
|
+
operations: {},
|
|
741
|
+
openReaders: 0,
|
|
742
|
+
maxOpenReaders: 0,
|
|
743
|
+
readersOpened: 0,
|
|
744
|
+
readersClosed: 0
|
|
745
|
+
};
|
|
746
|
+
counters.openReaders ??= 0;
|
|
747
|
+
counters.maxOpenReaders ??= 0;
|
|
748
|
+
counters.readersOpened ??= 0;
|
|
749
|
+
counters.readersClosed ??= 0;
|
|
750
|
+
return counters;
|
|
751
|
+
}
|
|
752
|
+
function returnedRowCount(value) {
|
|
753
|
+
if (Array.isArray(value))
|
|
754
|
+
return value.length;
|
|
755
|
+
if (value === null || value === undefined || value === false)
|
|
756
|
+
return 0;
|
|
757
|
+
return 1;
|
|
758
|
+
}
|
|
759
|
+
function trackDecodeOperation(name, operation) {
|
|
760
|
+
const counters = debugCounters();
|
|
761
|
+
const before = counters.decodedRows;
|
|
762
|
+
const started = performance.now();
|
|
763
|
+
let value;
|
|
764
|
+
try {
|
|
765
|
+
value = operation();
|
|
766
|
+
return value;
|
|
767
|
+
} finally {
|
|
768
|
+
const decodedRows = counters.decodedRows - before;
|
|
769
|
+
const current = counters.operations[name] ?? {
|
|
770
|
+
calls: 0,
|
|
771
|
+
decodedRows: 0,
|
|
772
|
+
maxDecodedRows: 0
|
|
773
|
+
};
|
|
774
|
+
current.calls += 1;
|
|
775
|
+
current.decodedRows += decodedRows;
|
|
776
|
+
current.maxDecodedRows = Math.max(current.maxDecodedRows, decodedRows);
|
|
777
|
+
counters.operations[name] = current;
|
|
778
|
+
const statement = QUERY_STATEMENTS[name];
|
|
779
|
+
if (counters.captureQueries && statement) {
|
|
780
|
+
(counters.queries ??= []).push({
|
|
781
|
+
operation: name,
|
|
782
|
+
statement,
|
|
783
|
+
rows: returnedRowCount(value),
|
|
784
|
+
elapsedMs: performance.now() - started
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
function decode(row) {
|
|
790
|
+
debugCounters().decodedRows += 1;
|
|
791
|
+
const data = JSON.parse(row.data);
|
|
792
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
793
|
+
throw new Error(`Invalid session_message data at seq ${row.seq}`);
|
|
794
|
+
}
|
|
795
|
+
if (row.type === "idle" && !["succeeded", "failed", "interrupted"].includes(String(data.outcome))) {
|
|
796
|
+
throw new Error(`Invalid idle outcome at seq ${row.seq}`);
|
|
797
|
+
}
|
|
798
|
+
return { ...row, data };
|
|
799
|
+
}
|
|
800
|
+
function connect(path, identity = "") {
|
|
801
|
+
const stat = statSync(path);
|
|
802
|
+
const acquiredIdentity = identity || `${stat.dev}:${stat.ino}:${stat.birthtimeMs}`;
|
|
803
|
+
const db = new Database2(path, { readonly: true, fileMustExist: true });
|
|
804
|
+
try {
|
|
805
|
+
assertOpenCodeStoreGeneration(db, "v2", path);
|
|
806
|
+
} catch (error) {
|
|
807
|
+
db.close();
|
|
808
|
+
throw error;
|
|
809
|
+
}
|
|
810
|
+
const counters = debugCounters();
|
|
811
|
+
counters.openReaders += 1;
|
|
812
|
+
counters.readersOpened += 1;
|
|
813
|
+
counters.maxOpenReaders = Math.max(counters.maxOpenReaders, counters.openReaders);
|
|
814
|
+
return { db, statements: new Map, identity: acquiredIdentity, leases: 0, retired: false };
|
|
815
|
+
}
|
|
816
|
+
function closeConnection(connection) {
|
|
817
|
+
connection.statements.clear();
|
|
818
|
+
try {
|
|
819
|
+
connection.db.close();
|
|
820
|
+
} finally {
|
|
821
|
+
const counters = debugCounters();
|
|
822
|
+
counters.openReaders = Math.max(0, counters.openReaders - 1);
|
|
823
|
+
counters.readersClosed += 1;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
class V2StoreReaderPool {
|
|
828
|
+
capacity;
|
|
829
|
+
connections = new Map;
|
|
830
|
+
closed = false;
|
|
831
|
+
closing = false;
|
|
832
|
+
holds = 0;
|
|
833
|
+
idleWaiters = new Set;
|
|
834
|
+
constructor(capacity = 4) {
|
|
835
|
+
this.capacity = capacity;
|
|
836
|
+
if (!Number.isSafeInteger(capacity) || capacity < 1)
|
|
837
|
+
throw new Error("Invalid reader capacity");
|
|
838
|
+
}
|
|
839
|
+
open(path) {
|
|
840
|
+
return new V2StoreReader(path, this);
|
|
841
|
+
}
|
|
842
|
+
acquire(path) {
|
|
843
|
+
if (this.closed)
|
|
844
|
+
throw new Error("Store reader pool is closed");
|
|
845
|
+
const key = resolve(path);
|
|
846
|
+
const stat = statSync(key);
|
|
847
|
+
const identity = `${stat.dev}:${stat.ino}:${stat.birthtimeMs}`;
|
|
848
|
+
let connection = this.connections.get(key);
|
|
849
|
+
if (connection && connection.identity !== identity) {
|
|
850
|
+
this.connections.delete(key);
|
|
851
|
+
this.retire(connection);
|
|
852
|
+
connection = undefined;
|
|
853
|
+
}
|
|
854
|
+
if (!connection)
|
|
855
|
+
connection = connect(key, identity);
|
|
856
|
+
connection.leases++;
|
|
857
|
+
this.connections.delete(key);
|
|
858
|
+
this.connections.set(key, connection);
|
|
859
|
+
while (this.connections.size > this.capacity) {
|
|
860
|
+
const oldest = this.connections.entries().next().value;
|
|
861
|
+
if (!oldest)
|
|
862
|
+
break;
|
|
863
|
+
this.connections.delete(oldest[0]);
|
|
864
|
+
this.retire(oldest[1]);
|
|
865
|
+
}
|
|
866
|
+
return connection;
|
|
867
|
+
}
|
|
868
|
+
release(connection) {
|
|
869
|
+
connection.leases--;
|
|
870
|
+
if (connection.retired && connection.leases === 0)
|
|
871
|
+
closeConnection(connection);
|
|
872
|
+
}
|
|
873
|
+
retire(connection) {
|
|
874
|
+
connection.retired = true;
|
|
875
|
+
if (connection.leases === 0)
|
|
876
|
+
closeConnection(connection);
|
|
877
|
+
}
|
|
878
|
+
hold() {
|
|
879
|
+
if (this.closed || this.closing)
|
|
880
|
+
throw new Error("Store reader pool is closed");
|
|
881
|
+
this.holds++;
|
|
882
|
+
let released = false;
|
|
883
|
+
return () => {
|
|
884
|
+
if (released)
|
|
885
|
+
return;
|
|
886
|
+
released = true;
|
|
887
|
+
this.holds--;
|
|
888
|
+
if (this.holds === 0 && this.closing)
|
|
889
|
+
this.finishClose();
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
isClosed() {
|
|
893
|
+
return this.closed;
|
|
894
|
+
}
|
|
895
|
+
holdCount() {
|
|
896
|
+
return this.holds;
|
|
897
|
+
}
|
|
898
|
+
close() {
|
|
899
|
+
if (this.closed || this.closing)
|
|
900
|
+
return;
|
|
901
|
+
this.closing = true;
|
|
902
|
+
if (this.holds === 0)
|
|
903
|
+
this.finishClose();
|
|
904
|
+
}
|
|
905
|
+
async closeWhenIdle(timeoutMs) {
|
|
906
|
+
this.close();
|
|
907
|
+
if (this.closed)
|
|
908
|
+
return true;
|
|
909
|
+
let timer;
|
|
910
|
+
let waiter;
|
|
911
|
+
try {
|
|
912
|
+
await new Promise((resolve) => {
|
|
913
|
+
waiter = resolve;
|
|
914
|
+
this.idleWaiters.add(resolve);
|
|
915
|
+
timer = setTimeout(resolve, Math.max(0, timeoutMs));
|
|
916
|
+
});
|
|
917
|
+
} finally {
|
|
918
|
+
if (timer)
|
|
919
|
+
clearTimeout(timer);
|
|
920
|
+
if (waiter)
|
|
921
|
+
this.idleWaiters.delete(waiter);
|
|
922
|
+
}
|
|
923
|
+
return this.closed;
|
|
924
|
+
}
|
|
925
|
+
finishClose() {
|
|
926
|
+
if (this.closed)
|
|
927
|
+
return;
|
|
928
|
+
this.closed = true;
|
|
929
|
+
for (const connection of this.connections.values())
|
|
930
|
+
this.retire(connection);
|
|
931
|
+
this.connections.clear();
|
|
932
|
+
for (const resolve of this.idleWaiters)
|
|
933
|
+
resolve();
|
|
934
|
+
this.idleWaiters.clear();
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
class V2StoreReader {
|
|
939
|
+
path;
|
|
940
|
+
pool;
|
|
941
|
+
db;
|
|
942
|
+
connection;
|
|
943
|
+
closed = false;
|
|
944
|
+
constructor(path, pool) {
|
|
945
|
+
this.path = path;
|
|
946
|
+
this.pool = pool;
|
|
947
|
+
this.connection = pool ? pool.acquire(path) : connect(path);
|
|
948
|
+
this.db = this.connection.db;
|
|
949
|
+
}
|
|
950
|
+
hostIdentity() {
|
|
951
|
+
return createHash3("sha256").update(JSON.stringify([hostname(), resolve(this.path), this.connection.identity])).digest("hex");
|
|
952
|
+
}
|
|
953
|
+
prepare(sql) {
|
|
954
|
+
if (this.closed)
|
|
955
|
+
throw new Error("Store reader is closed");
|
|
956
|
+
const statements = this.connection.statements;
|
|
957
|
+
let statement = statements.get(sql);
|
|
958
|
+
if (!statement) {
|
|
959
|
+
statement = this.db.prepare(sql);
|
|
960
|
+
if (statements.size >= 128)
|
|
961
|
+
statements.clear();
|
|
962
|
+
statements.set(sql, statement);
|
|
963
|
+
}
|
|
964
|
+
return statement;
|
|
965
|
+
}
|
|
966
|
+
close() {
|
|
967
|
+
if (this.closed)
|
|
968
|
+
return;
|
|
969
|
+
this.closed = true;
|
|
970
|
+
if (this.pool)
|
|
971
|
+
this.pool.release(this.connection);
|
|
972
|
+
else
|
|
973
|
+
closeConnection(this.connection);
|
|
974
|
+
}
|
|
975
|
+
page(sessionID, options = {}) {
|
|
976
|
+
return trackDecodeOperation("page", () => {
|
|
977
|
+
const limit = options.limit ?? 100;
|
|
978
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1e4)
|
|
979
|
+
throw new Error("Invalid page limit");
|
|
980
|
+
const after = options.after ?? -1;
|
|
981
|
+
if (!Number.isSafeInteger(after))
|
|
982
|
+
throw new Error("Invalid seq cursor");
|
|
983
|
+
if (options.through !== undefined && !Number.isSafeInteger(options.through))
|
|
984
|
+
throw new Error("Invalid seq upper bound");
|
|
985
|
+
const predicates = ["session_id = ?", "seq > ?"];
|
|
986
|
+
const parameters = [sessionID, after];
|
|
987
|
+
if (options.through !== undefined) {
|
|
988
|
+
predicates.push("seq <= ?");
|
|
989
|
+
parameters.push(options.through);
|
|
990
|
+
}
|
|
991
|
+
if (options.type) {
|
|
992
|
+
predicates.push("type = ?");
|
|
993
|
+
parameters.push(options.type);
|
|
994
|
+
}
|
|
995
|
+
const rows = this.prepare(`SELECT id, session_id, type, seq, time_created, data FROM session_message
|
|
996
|
+
WHERE ${predicates.join(" AND ")}
|
|
997
|
+
ORDER BY seq ASC LIMIT ?`).all(...parameters, limit).map(decode);
|
|
998
|
+
return { rows, cursor: rows.at(-1)?.seq };
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
messagePage(sessionID, afterOrdinal, limit, finalWatermark, after) {
|
|
1002
|
+
return trackDecodeOperation("messagePage", () => {
|
|
1003
|
+
if (!Number.isSafeInteger(afterOrdinal) || afterOrdinal < 0)
|
|
1004
|
+
throw new Error("Invalid raw-message ordinal cursor");
|
|
1005
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1e4)
|
|
1006
|
+
throw new Error("Invalid page limit");
|
|
1007
|
+
if (!Number.isSafeInteger(finalWatermark) || finalWatermark < 0)
|
|
1008
|
+
throw new Error("Invalid raw-message watermark");
|
|
1009
|
+
const pageSize = Math.min(limit, finalWatermark - afterOrdinal);
|
|
1010
|
+
if (pageSize <= 0)
|
|
1011
|
+
return [];
|
|
1012
|
+
if (after) {
|
|
1013
|
+
const anchor = this.prepare("SELECT seq FROM session_message WHERE session_id = ? AND id = ?").get(sessionID, after.id);
|
|
1014
|
+
if (anchor) {
|
|
1015
|
+
return this.prepare(`SELECT id, session_id, type, seq, time_created, data
|
|
1016
|
+
FROM session_message WHERE session_id = ? AND seq > ?
|
|
1017
|
+
AND type IN (${RAW_MESSAGE_TYPE_PARAMETERS}) ORDER BY seq ASC LIMIT ?`).all(sessionID, anchor.seq, ...RAW_MESSAGE_TYPES, pageSize).map(decode);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
const maximumSeq = Number.MAX_SAFE_INTEGER;
|
|
1021
|
+
const rows = this.prepare(V2_MESSAGE_PAGE_SQL).all(afterOrdinal, sessionID, ...RAW_MESSAGE_TYPES, Math.max(0, afterOrdinal - 1), sessionID, sessionID, ...RAW_MESSAGE_TYPES, finalWatermark - 1, maximumSeq, sessionID, ...RAW_MESSAGE_TYPES, pageSize);
|
|
1022
|
+
return rows.map(decode);
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
messageCount(sessionID) {
|
|
1026
|
+
return trackDecodeOperation("messageCount", () => {
|
|
1027
|
+
const rawTypes = RAW_MESSAGE_TYPES.map(() => "?").join(", ");
|
|
1028
|
+
const row = this.prepare(`SELECT COUNT(*) AS count FROM session_message
|
|
1029
|
+
WHERE session_id = ? AND type IN (${rawTypes})`).get(sessionID, ...RAW_MESSAGE_TYPES);
|
|
1030
|
+
return typeof row?.count === "number" ? row.count : 0;
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
sessionDirectoryPage(afterSessionID, limit) {
|
|
1034
|
+
const rows = this.prepare(`SELECT id, COALESCE(directory, '') AS directory FROM session_v2
|
|
1035
|
+
WHERE id > ? ORDER BY id ASC LIMIT ?`).all(afterSessionID ?? "", limit);
|
|
1036
|
+
return rows.map((row) => ({ sessionId: row.id, directory: row.directory }));
|
|
1037
|
+
}
|
|
1038
|
+
latestMessageTime(sessionID) {
|
|
1039
|
+
const row = this.prepare("SELECT MAX(time_created) AS time FROM session_message WHERE session_id = ?").get(sessionID);
|
|
1040
|
+
return row?.time ?? undefined;
|
|
1041
|
+
}
|
|
1042
|
+
isSubagent(sessionID) {
|
|
1043
|
+
const row = this.prepare("SELECT parent_id FROM session_v2 WHERE id = ?").get(sessionID);
|
|
1044
|
+
if (!row)
|
|
1045
|
+
return null;
|
|
1046
|
+
return typeof row.parent_id === "string" && row.parent_id.length > 0;
|
|
1047
|
+
}
|
|
1048
|
+
sessionIds() {
|
|
1049
|
+
return this.prepare("SELECT id FROM session_v2").all().map((row) => row.id);
|
|
1050
|
+
}
|
|
1051
|
+
latestRootSessionInDirectory(directory) {
|
|
1052
|
+
const columns = new Set(this.prepare("PRAGMA table_info(session_v2)").all().map((column) => column.name));
|
|
1053
|
+
const archived = columns.has("time_archived") ? " AND time_archived IS NULL" : "";
|
|
1054
|
+
const order = columns.has("time_updated") ? "time_updated DESC, id DESC" : "id DESC";
|
|
1055
|
+
const page = this.prepare(`SELECT id, COALESCE(directory, '') AS directory FROM session_v2
|
|
1056
|
+
WHERE parent_id IS NULL${archived}
|
|
1057
|
+
AND COALESCE(json_extract(metadata, '$.magic_context'), '') <> 'hidden-run'
|
|
1058
|
+
ORDER BY ${order} LIMIT ? OFFSET ?`);
|
|
1059
|
+
const pageSize = 500;
|
|
1060
|
+
for (let offset = 0;; offset += pageSize) {
|
|
1061
|
+
const rows = page.all(pageSize, offset);
|
|
1062
|
+
const match = rows.find((row) => sameProjectDirectory(row.directory, directory));
|
|
1063
|
+
if (match)
|
|
1064
|
+
return match.id;
|
|
1065
|
+
if (rows.length < pageSize)
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
rootSessionActivity(sessionIDs) {
|
|
1070
|
+
const roots = new Map;
|
|
1071
|
+
for (let offset = 0;offset < sessionIDs.length; offset += 500) {
|
|
1072
|
+
const chunk = sessionIDs.slice(offset, offset + 500);
|
|
1073
|
+
const rows = this.prepare(`SELECT id, (
|
|
1074
|
+
SELECT MAX(time_created) FROM session_message
|
|
1075
|
+
WHERE session_id = session_v2.id AND type = 'user'
|
|
1076
|
+
) AS time FROM session_v2
|
|
1077
|
+
WHERE parent_id IS NULL
|
|
1078
|
+
AND COALESCE(json_extract(metadata, '$.magic_context'), '') <> 'hidden-run'
|
|
1079
|
+
AND id IN (${chunk.map(() => "?").join(",")})`).all(...chunk);
|
|
1080
|
+
for (const row of rows)
|
|
1081
|
+
if (row.time !== null)
|
|
1082
|
+
roots.set(row.id, row.time);
|
|
1083
|
+
}
|
|
1084
|
+
return roots;
|
|
1085
|
+
}
|
|
1086
|
+
oldestUserMessageTimesSince(sessionIDs, sinceMs) {
|
|
1087
|
+
const result = new Map;
|
|
1088
|
+
for (let offset = 0;offset < sessionIDs.length; offset += 500) {
|
|
1089
|
+
const chunk = sessionIDs.slice(offset, offset + 500);
|
|
1090
|
+
const rows = this.prepare(`SELECT session_id, MIN(time_created) AS time FROM session_message
|
|
1091
|
+
WHERE type = 'user' AND time_created > ? AND session_id IN (${chunk.map(() => "?").join(",")})
|
|
1092
|
+
GROUP BY session_id`).all(sinceMs, ...chunk);
|
|
1093
|
+
for (const row of rows)
|
|
1094
|
+
result.set(row.session_id, row.time);
|
|
1095
|
+
}
|
|
595
1096
|
return result;
|
|
596
|
-
}
|
|
1097
|
+
}
|
|
1098
|
+
retrospectiveUserPage(sessionID, options) {
|
|
1099
|
+
const { boundaryMs, before, limit, maxChars, truncationMarker } = options;
|
|
1100
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1e4)
|
|
1101
|
+
throw new Error("Invalid page limit");
|
|
1102
|
+
if (!Number.isSafeInteger(maxChars) || maxChars <= truncationMarker.length)
|
|
1103
|
+
throw new Error("Invalid text limit");
|
|
1104
|
+
const direction = before ? "DESC" : "ASC";
|
|
1105
|
+
const head = Math.floor((maxChars - truncationMarker.length) / 2);
|
|
1106
|
+
const tail = maxChars - truncationMarker.length - head;
|
|
1107
|
+
return this.prepare(`WITH users AS (
|
|
1108
|
+
SELECT session_id, seq, time_created, data FROM session_message
|
|
1109
|
+
WHERE session_id = ? AND type = 'user' AND time_created ${before ? "<=" : ">"}
|
|
1110
|
+
?
|
|
1111
|
+
ORDER BY time_created ${direction}, seq ${direction} LIMIT ?
|
|
1112
|
+
), texts AS (
|
|
1113
|
+
SELECT session_id, seq, time_created, CASE
|
|
1114
|
+
WHEN json_extract(data, '$.synthetic') = 1 OR json_extract(data, '$.ignored') = 1 THEN ''
|
|
1115
|
+
WHEN json_type(data, '$.text') = 'text' THEN json_extract(data, '$.text')
|
|
1116
|
+
ELSE COALESCE((SELECT group_concat(json_extract(value, '$.text'), char(10))
|
|
1117
|
+
FROM json_each(users.data, '$.content')
|
|
1118
|
+
WHERE json_extract(value, '$.type') IN ('text', 'input_text')
|
|
1119
|
+
AND COALESCE(json_extract(value, '$.synthetic'), 0) <> 1
|
|
1120
|
+
AND COALESCE(json_extract(value, '$.ignored'), 0) <> 1), '') END AS text
|
|
1121
|
+
FROM users WHERE json_valid(data)
|
|
1122
|
+
) SELECT seq, time_created, CASE WHEN length(text) > ?
|
|
1123
|
+
THEN substr(text, 1, ?) || ? || substr(text, -?) ELSE text END AS text,
|
|
1124
|
+
EXISTS (
|
|
1125
|
+
SELECT 1 FROM session_message assistant_reply
|
|
1126
|
+
WHERE assistant_reply.session_id = texts.session_id
|
|
1127
|
+
AND assistant_reply.type = 'assistant'
|
|
1128
|
+
AND assistant_reply.seq < texts.seq
|
|
1129
|
+
AND EXISTS (
|
|
1130
|
+
SELECT 1 FROM session_message prior_user
|
|
1131
|
+
WHERE prior_user.session_id = texts.session_id
|
|
1132
|
+
AND prior_user.type = 'user'
|
|
1133
|
+
AND prior_user.seq < assistant_reply.seq
|
|
1134
|
+
)
|
|
1135
|
+
) AS follows_assistant_reply
|
|
1136
|
+
FROM texts ORDER BY time_created ${direction}, seq ${direction}`).all(sessionID, boundaryMs, limit, maxChars, head, truncationMarker, tail);
|
|
1137
|
+
}
|
|
1138
|
+
storedRowsAfter(sessionID, after) {
|
|
1139
|
+
const row = this.prepare("SELECT COUNT(*) AS count FROM session_message WHERE session_id = ? AND seq > ?").get(sessionID, after);
|
|
1140
|
+
return row.count;
|
|
1141
|
+
}
|
|
1142
|
+
storedMessageCount(sessionID) {
|
|
1143
|
+
return trackDecodeOperation("storedMessageCount", () => {
|
|
1144
|
+
const row = this.prepare("SELECT COUNT(*) AS count FROM session_message WHERE session_id = ?").get(sessionID);
|
|
1145
|
+
return typeof row?.count === "number" ? row.count : 0;
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
messageById(sessionID, id) {
|
|
1149
|
+
return trackDecodeOperation("messageById", () => {
|
|
1150
|
+
const rawTypes = RAW_MESSAGE_TYPES.map(() => "?").join(", ");
|
|
1151
|
+
const row = this.prepare(`SELECT id, session_id, type, seq, time_created, data FROM session_message
|
|
1152
|
+
WHERE session_id = ? AND id = ? AND type IN (${rawTypes}) LIMIT 1`).get(sessionID, id, ...RAW_MESSAGE_TYPES);
|
|
1153
|
+
return row ? decode(row) : null;
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
messageTimes(sessionID, ids) {
|
|
1157
|
+
const times = new Map;
|
|
1158
|
+
for (let offset = 0;offset < ids.length; offset += 500) {
|
|
1159
|
+
const batch = ids.slice(offset, offset + 500);
|
|
1160
|
+
const rows = this.prepare(`SELECT id, COALESCE(time_created, json_extract(data, '$.time.created')) AS created,
|
|
1161
|
+
json_extract(data, '$.time.completed') AS completed
|
|
1162
|
+
FROM session_message WHERE session_id = ? AND id IN (${batch.map(() => "?").join(",")})`).all(sessionID, ...batch);
|
|
1163
|
+
for (const row of rows)
|
|
1164
|
+
times.set(row.id, {
|
|
1165
|
+
created: typeof row.created === "number" ? row.created : undefined,
|
|
1166
|
+
completed: typeof row.completed === "number" ? row.completed : undefined
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
return times;
|
|
1170
|
+
}
|
|
1171
|
+
messageExistsById(sessionID, id) {
|
|
1172
|
+
return trackDecodeOperation("messageExistsById", () => {
|
|
1173
|
+
const rawTypes = RAW_MESSAGE_TYPES.map(() => "?").join(", ");
|
|
1174
|
+
const row = this.prepare(`SELECT 1 FROM session_message
|
|
1175
|
+
WHERE id = ? AND session_id = ? AND type IN (${rawTypes}) LIMIT 1`).get(id, sessionID, ...RAW_MESSAGE_TYPES);
|
|
1176
|
+
return row != null;
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
messageOrdinalById(sessionID, id) {
|
|
1180
|
+
return trackDecodeOperation("messageOrdinalById", () => {
|
|
1181
|
+
const rawTypes = RAW_MESSAGE_TYPES.map(() => "?").join(", ");
|
|
1182
|
+
const row = this.prepare(`WITH target AS (
|
|
1183
|
+
SELECT seq FROM session_message
|
|
1184
|
+
WHERE session_id = ? AND id = ? AND type IN (${rawTypes})
|
|
1185
|
+
LIMIT 1
|
|
1186
|
+
)
|
|
1187
|
+
SELECT (
|
|
1188
|
+
SELECT COUNT(*) FROM session_message
|
|
1189
|
+
WHERE session_id = ?
|
|
1190
|
+
AND type IN (${rawTypes})
|
|
1191
|
+
AND seq <= target.seq
|
|
1192
|
+
) AS ordinal
|
|
1193
|
+
FROM target`).get(sessionID, id, ...RAW_MESSAGE_TYPES, sessionID, ...RAW_MESSAGE_TYPES);
|
|
1194
|
+
return typeof row?.ordinal === "number" ? row.ordinal : null;
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
messageIdOrdinals(sessionID, fromOrdinal, toOrdinal) {
|
|
1198
|
+
return trackDecodeOperation("messageIdOrdinals", () => {
|
|
1199
|
+
if (!Number.isSafeInteger(fromOrdinal) || fromOrdinal < 1)
|
|
1200
|
+
throw new Error("Invalid raw-message range start");
|
|
1201
|
+
if (!Number.isSafeInteger(toOrdinal) || toOrdinal < fromOrdinal)
|
|
1202
|
+
throw new Error("Invalid raw-message range end");
|
|
1203
|
+
const rawTypes = RAW_MESSAGE_TYPES.map(() => "?").join(", ");
|
|
1204
|
+
const rows = this.prepare(`SELECT id FROM session_message
|
|
1205
|
+
WHERE session_id = ? AND type IN (${rawTypes})
|
|
1206
|
+
ORDER BY seq ASC LIMIT ? OFFSET ?`).all(sessionID, ...RAW_MESSAGE_TYPES, toOrdinal - fromOrdinal + 1, fromOrdinal - 1);
|
|
1207
|
+
return new Map(rows.map((row, index) => [row.id, fromOrdinal + index]));
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
messageOrdinalPage(sessionID, after, limit) {
|
|
1211
|
+
return trackDecodeOperation("messageOrdinalPage", () => {
|
|
1212
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1e4)
|
|
1213
|
+
throw new Error("Invalid page limit");
|
|
1214
|
+
const rows = after ? this.prepare(`SELECT id, type, seq, json_valid(data) AS valid
|
|
1215
|
+
FROM session_message
|
|
1216
|
+
WHERE session_id = ? AND (seq > ? OR (seq = ? AND id > ?))
|
|
1217
|
+
ORDER BY seq ASC, id ASC LIMIT ?`).all(sessionID, after.timeCreated, after.timeCreated, after.id, limit) : this.prepare(`SELECT id, type, seq, json_valid(data) AS valid
|
|
1218
|
+
FROM session_message
|
|
1219
|
+
WHERE session_id = ?
|
|
1220
|
+
ORDER BY seq ASC, id ASC LIMIT ?`).all(sessionID, limit);
|
|
1221
|
+
const rawTypes = new Set(RAW_MESSAGE_TYPES);
|
|
1222
|
+
return rows.map((row) => ({
|
|
1223
|
+
id: row.id,
|
|
1224
|
+
timeCreated: row.seq,
|
|
1225
|
+
contributesOrdinal: rawTypes.has(row.type),
|
|
1226
|
+
hasValidInfo: row.valid === 1
|
|
1227
|
+
}));
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
range(sessionID, after, through) {
|
|
1231
|
+
return trackDecodeOperation("range", () => through <= after ? [] : this.all(sessionID, after, undefined, through));
|
|
1232
|
+
}
|
|
1233
|
+
spanRowStamps(sessionID, after, through) {
|
|
1234
|
+
if (!Number.isSafeInteger(after) || !Number.isSafeInteger(through))
|
|
1235
|
+
throw new Error("Invalid seq span");
|
|
1236
|
+
const rows = this.prepare(`SELECT seq, id, time_updated AS updated, length(CAST(data AS BLOB)) AS bytes
|
|
1237
|
+
FROM session_message WHERE session_id = ? AND seq > ? AND seq <= ?
|
|
1238
|
+
ORDER BY seq ASC`).all(sessionID, after, through);
|
|
1239
|
+
const stamps = new Map;
|
|
1240
|
+
for (const row of rows)
|
|
1241
|
+
stamps.set(row.seq, `${row.id}\x00${row.updated ?? ""}\x00${row.bytes ?? ""}`);
|
|
1242
|
+
return stamps;
|
|
1243
|
+
}
|
|
1244
|
+
rawRowsThrough(sessionID, throughSeq, limit) {
|
|
1245
|
+
return trackDecodeOperation("rawRowsThrough", () => {
|
|
1246
|
+
if (!Number.isSafeInteger(throughSeq))
|
|
1247
|
+
throw new Error("Invalid seq upper bound");
|
|
1248
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1e4)
|
|
1249
|
+
throw new Error("Invalid page limit");
|
|
1250
|
+
const rawTypes = RAW_MESSAGE_TYPES.map(() => "?").join(", ");
|
|
1251
|
+
return this.prepare(`SELECT id, session_id, type, seq, time_created, data FROM session_message
|
|
1252
|
+
WHERE session_id = ? AND type IN (${rawTypes}) AND seq <= ?
|
|
1253
|
+
ORDER BY seq DESC LIMIT ?`).all(sessionID, ...RAW_MESSAGE_TYPES, throughSeq, limit).map(decode);
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
sequenceForId(sessionID, id) {
|
|
1257
|
+
if (!id)
|
|
1258
|
+
return;
|
|
1259
|
+
const row = this.prepare("SELECT seq FROM session_message WHERE session_id = ? AND id = ? LIMIT 1").get(sessionID, id);
|
|
1260
|
+
return typeof row?.seq === "number" ? row.seq : undefined;
|
|
1261
|
+
}
|
|
1262
|
+
syntheticMessageIDs(sessionID) {
|
|
1263
|
+
const rows = this.prepare("SELECT id FROM session_message WHERE session_id = ? AND type = 'synthetic'").all(sessionID);
|
|
1264
|
+
return new Set(rows.map((row) => row.id));
|
|
1265
|
+
}
|
|
1266
|
+
forkOrigin(sessionID) {
|
|
1267
|
+
const columns = new Set(this.prepare("PRAGMA table_info(session_v2)").all().map((column) => column.name));
|
|
1268
|
+
if (!columns.has("fork_session_id") || !columns.has("fork_boundary"))
|
|
1269
|
+
return null;
|
|
1270
|
+
const row = this.prepare("SELECT fork_session_id, fork_boundary FROM session_v2 WHERE id = ?").get(sessionID);
|
|
1271
|
+
if (!row?.fork_session_id || !row.fork_boundary)
|
|
1272
|
+
return null;
|
|
1273
|
+
try {
|
|
1274
|
+
const boundary = JSON.parse(row.fork_boundary);
|
|
1275
|
+
if (boundary.type !== "before" && boundary.type !== "through" || typeof boundary.messageID !== "string" || boundary.messageID.length === 0)
|
|
1276
|
+
return null;
|
|
1277
|
+
return {
|
|
1278
|
+
parentSessionID: row.fork_session_id,
|
|
1279
|
+
boundary: { type: boundary.type, messageID: boundary.messageID }
|
|
1280
|
+
};
|
|
1281
|
+
} catch {
|
|
1282
|
+
return null;
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
sessionExists(sessionID) {
|
|
1286
|
+
return this.prepare("SELECT 1 FROM session_v2 WHERE id = ?").get(sessionID) != null;
|
|
1287
|
+
}
|
|
1288
|
+
rowStampsThrough(sessionID, throughSeq) {
|
|
1289
|
+
return this.prepare(`SELECT id, type, seq, time_created FROM session_message
|
|
1290
|
+
WHERE session_id = ? AND seq <= ? ORDER BY seq ASC`).all(sessionID, throughSeq);
|
|
1291
|
+
}
|
|
1292
|
+
latestSequenceForIds(sessionID, ids) {
|
|
1293
|
+
let latest = -1;
|
|
1294
|
+
for (let offset = 0;offset < ids.length; offset += 500) {
|
|
1295
|
+
const chunk = ids.slice(offset, offset + 500);
|
|
1296
|
+
const row = this.prepare(`SELECT MAX(seq) AS seq FROM session_message
|
|
1297
|
+
WHERE session_id = ? AND id IN (${chunk.map(() => "?").join(", ")})`).get(sessionID, ...chunk);
|
|
1298
|
+
if (typeof row?.seq === "number")
|
|
1299
|
+
latest = Math.max(latest, row.seq);
|
|
1300
|
+
}
|
|
1301
|
+
return latest;
|
|
1302
|
+
}
|
|
1303
|
+
compactionByStatus(sessionID, status) {
|
|
1304
|
+
const row = this.prepare(`SELECT id, session_id, type, seq, time_created, data FROM session_message
|
|
1305
|
+
WHERE session_id = ? AND type = 'compaction'
|
|
1306
|
+
AND json_extract(data, '$.status') = ?
|
|
1307
|
+
ORDER BY seq DESC LIMIT 1`).get(sessionID, status);
|
|
1308
|
+
return row ? decode(row) : undefined;
|
|
1309
|
+
}
|
|
1310
|
+
latestCompaction(sessionID) {
|
|
1311
|
+
return trackDecodeOperation("latestCompaction", () => this.compactionByStatus(sessionID, "completed"));
|
|
1312
|
+
}
|
|
1313
|
+
latestRunningCompaction(sessionID) {
|
|
1314
|
+
return trackDecodeOperation("latestRunningCompaction", () => this.compactionByStatus(sessionID, "running"));
|
|
1315
|
+
}
|
|
1316
|
+
idleRows(sessionID, after = -1) {
|
|
1317
|
+
return trackDecodeOperation("idleRows", () => this.all(sessionID, after, "idle"));
|
|
1318
|
+
}
|
|
1319
|
+
earliestSequence(sessionID) {
|
|
1320
|
+
const row = this.prepare("SELECT MIN(seq) AS seq FROM session_message WHERE session_id = ?").get(sessionID);
|
|
1321
|
+
return typeof row?.seq === "number" ? row.seq : undefined;
|
|
1322
|
+
}
|
|
1323
|
+
latestSequence(sessionID) {
|
|
1324
|
+
const row = this.prepare("SELECT MAX(seq) AS seq FROM session_message WHERE session_id = ?").get(sessionID);
|
|
1325
|
+
return typeof row?.seq === "number" ? row.seq : -1;
|
|
1326
|
+
}
|
|
1327
|
+
assistantSince(sessionID, afterSeq) {
|
|
1328
|
+
return trackDecodeOperation("assistantSince", () => {
|
|
1329
|
+
const rows = this.prepare(`SELECT id, session_id, type, seq, time_created, data FROM session_message
|
|
1330
|
+
WHERE session_id = ? AND type = 'assistant' AND seq > ?
|
|
1331
|
+
ORDER BY seq ASC`).all(sessionID, afterSeq);
|
|
1332
|
+
return rows.map((row) => decode(row));
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
latestAssistant(sessionID) {
|
|
1336
|
+
return trackDecodeOperation("latestAssistant", () => {
|
|
1337
|
+
const row = this.prepare(`SELECT id, session_id, type, seq, time_created, data FROM session_message
|
|
1338
|
+
WHERE session_id = ? AND type = 'assistant'
|
|
1339
|
+
ORDER BY seq DESC LIMIT 1`).get(sessionID);
|
|
1340
|
+
return row ? decode(row) : undefined;
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
latestIdle(sessionID) {
|
|
1344
|
+
return trackDecodeOperation("latestIdle", () => {
|
|
1345
|
+
const row = this.prepare(`SELECT id, session_id, type, seq, time_created, data FROM session_message
|
|
1346
|
+
WHERE session_id = ? AND type = 'idle'
|
|
1347
|
+
ORDER BY seq DESC LIMIT 1`).get(sessionID);
|
|
1348
|
+
return row ? decode(row) : undefined;
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
window(sessionID) {
|
|
1352
|
+
return trackDecodeOperation("window", () => this.db.transaction(() => {
|
|
1353
|
+
const cut = this.latestCompaction(sessionID);
|
|
1354
|
+
return this.all(sessionID, cut ? cut.seq - 1 : -1);
|
|
1355
|
+
}).deferred());
|
|
1356
|
+
}
|
|
1357
|
+
history(sessionID) {
|
|
1358
|
+
return trackDecodeOperation("history", () => this.all(sessionID, -1));
|
|
1359
|
+
}
|
|
1360
|
+
all(sessionID, after, type, through) {
|
|
1361
|
+
const rows = [];
|
|
1362
|
+
for (;; ) {
|
|
1363
|
+
const page = this.page(sessionID, { after, through, type });
|
|
1364
|
+
rows.push(...page.rows);
|
|
1365
|
+
if (page.rows.length < 100 || page.cursor === undefined)
|
|
1366
|
+
return rows;
|
|
1367
|
+
after = page.cursor;
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
597
1370
|
}
|
|
598
1371
|
|
|
599
1372
|
// src/features/magic-context/storage-session-tables.ts
|
|
@@ -642,6 +1415,22 @@ var SESSION_SCOPED_TABLES = [
|
|
|
642
1415
|
{ table: "lkg_slots" },
|
|
643
1416
|
{ table: "lkg_slot_chunks" }
|
|
644
1417
|
];
|
|
1418
|
+
var cleanupStatements = new WeakMap;
|
|
1419
|
+
function cleanupStatement(db, sql) {
|
|
1420
|
+
let cache = cleanupStatements.get(db);
|
|
1421
|
+
if (!cache) {
|
|
1422
|
+
cache = new Map;
|
|
1423
|
+
cleanupStatements.set(db, cache);
|
|
1424
|
+
}
|
|
1425
|
+
let statement = cache.get(sql);
|
|
1426
|
+
if (!statement) {
|
|
1427
|
+
if (cache.size >= 256)
|
|
1428
|
+
cache.clear();
|
|
1429
|
+
statement = prepareCachedStatement2(db, sql);
|
|
1430
|
+
cache.set(sql, statement);
|
|
1431
|
+
}
|
|
1432
|
+
return statement;
|
|
1433
|
+
}
|
|
645
1434
|
function deleteSessionScopedRows(db, sessionIds, harness, options = {}) {
|
|
646
1435
|
if (sessionIds.length === 0)
|
|
647
1436
|
return 0;
|
|
@@ -649,7 +1438,7 @@ function deleteSessionScopedRows(db, sessionIds, harness, options = {}) {
|
|
|
649
1438
|
if (options.rustModuleCleanupAcknowledged !== true) {
|
|
650
1439
|
const placeholders = sessionIds.map(() => "?").join(", ");
|
|
651
1440
|
const harnessPredicate = harness === undefined ? "" : " AND harness = ?";
|
|
652
|
-
const protectedRows = db
|
|
1441
|
+
const protectedRows = cleanupStatement(db, `SELECT session_id
|
|
653
1442
|
FROM pending_session_cleanup
|
|
654
1443
|
WHERE session_id IN (${placeholders})
|
|
655
1444
|
AND harness LIKE '%:rust'${harnessPredicate}`).all(...sessionIds, ...harness === undefined ? [] : [`${harness}:rust`]);
|
|
@@ -661,11 +1450,11 @@ function deleteSessionScopedRows(db, sessionIds, harness, options = {}) {
|
|
|
661
1450
|
const placeholders = deletableSessionIds.map(() => "?").join(", ");
|
|
662
1451
|
const harnessTables = SESSION_SCOPED_TABLES.filter((definition) => definition.harnessScoped);
|
|
663
1452
|
if (options.preserveTagRevisionTombstones) {
|
|
664
|
-
const owners = db
|
|
665
|
-
const record = db
|
|
1453
|
+
const owners = cleanupStatement(db, harnessTables.map((definition) => `SELECT session_id, harness FROM ${definition.table} WHERE session_id IN (${placeholders})${definition.extraPredicate ? ` AND ${definition.extraPredicate}` : ""}`).join(" UNION ")).all(...harnessTables.flatMap(() => deletableSessionIds));
|
|
1454
|
+
const record = cleanupStatement(db, "INSERT OR IGNORE INTO schema_migrations_meta (key, value) VALUES (?, ?)");
|
|
666
1455
|
for (const owner of owners)
|
|
667
1456
|
record.run(tagTombstoneOwnerKey(owner.session_id, owner.harness), owner.harness);
|
|
668
|
-
const fallback = db
|
|
1457
|
+
const fallback = cleanupStatement(db, `INSERT INTO schema_migrations_meta (key, value)
|
|
669
1458
|
SELECT ?, ? WHERE NOT EXISTS (SELECT 1 FROM schema_migrations_meta WHERE key >= ? AND key < ?)`);
|
|
670
1459
|
for (const id of deletableSessionIds)
|
|
671
1460
|
fallback.run(tagTombstoneOwnerKey(id, getHarness2()), getHarness2(), ...tagTombstoneOwnerRange(id));
|
|
@@ -674,13 +1463,13 @@ function deleteSessionScopedRows(db, sessionIds, harness, options = {}) {
|
|
|
674
1463
|
deleteChunkEmbedBackoffForSession(db, sessionId);
|
|
675
1464
|
for (const definition of SESSION_SCOPED_TABLES) {
|
|
676
1465
|
if (definition.table === "message_history_fts") {
|
|
677
|
-
db
|
|
1466
|
+
cleanupStatement(db, `DELETE FROM message_history_fts
|
|
678
1467
|
WHERE rowid IN (
|
|
679
1468
|
SELECT fts_rowid
|
|
680
1469
|
FROM message_fts_rowid_map
|
|
681
1470
|
WHERE session_id IN (${placeholders})
|
|
682
1471
|
)`).run(...deletableSessionIds);
|
|
683
|
-
|
|
1472
|
+
deleteUnmappedMessageFtsRows2(db, deletableSessionIds);
|
|
684
1473
|
continue;
|
|
685
1474
|
}
|
|
686
1475
|
const predicates = [`session_id IN (${placeholders})`];
|
|
@@ -689,7 +1478,7 @@ function deleteSessionScopedRows(db, sessionIds, harness, options = {}) {
|
|
|
689
1478
|
const bindHarness = harness !== undefined && definition.harnessScoped === true;
|
|
690
1479
|
if (bindHarness)
|
|
691
1480
|
predicates.push("harness = ?");
|
|
692
|
-
const statement = db
|
|
1481
|
+
const statement = cleanupStatement(db, `DELETE FROM ${definition.table} WHERE ${predicates.join(" AND ")}`);
|
|
693
1482
|
if (bindHarness)
|
|
694
1483
|
statement.run(...deletableSessionIds, harness);
|
|
695
1484
|
else
|
|
@@ -697,9 +1486,9 @@ function deleteSessionScopedRows(db, sessionIds, harness, options = {}) {
|
|
|
697
1486
|
}
|
|
698
1487
|
deleteSessionActivity(db, deletableSessionIds);
|
|
699
1488
|
if (!options.preserveTagRevisionTombstones) {
|
|
700
|
-
const retireOwner = db
|
|
1489
|
+
const retireOwner = cleanupStatement(db, harness === undefined ? "DELETE FROM schema_migrations_meta WHERE key >= ? AND key < ?" : "DELETE FROM schema_migrations_meta WHERE key = ?");
|
|
701
1490
|
const survivingRows = harnessTables.map((definition) => `EXISTS (SELECT 1 FROM ${definition.table} WHERE session_id = ?${definition.extraPredicate ? ` AND ${definition.extraPredicate}` : ""})`).join(" OR ");
|
|
702
|
-
const retire = db
|
|
1491
|
+
const retire = cleanupStatement(db, `DELETE FROM schema_migrations_meta WHERE key IN (?, ?)
|
|
703
1492
|
AND NOT (${survivingRows})
|
|
704
1493
|
AND NOT EXISTS (SELECT 1 FROM schema_migrations_meta AS owners WHERE owners.key >= ? AND owners.key < ?)`);
|
|
705
1494
|
for (const id of deletableSessionIds) {
|
|
@@ -710,10 +1499,59 @@ function deleteSessionScopedRows(db, sessionIds, harness, options = {}) {
|
|
|
710
1499
|
retire.run(tagIdentityRevisionKey(id), tagRevisionKey(id), ...harnessTables.map(() => id), ...tagTombstoneOwnerRange(id));
|
|
711
1500
|
}
|
|
712
1501
|
}
|
|
713
|
-
|
|
714
|
-
|
|
1502
|
+
if (!options.deferRecoveryFenceCleanup)
|
|
1503
|
+
for (const sessionId of deletableSessionIds)
|
|
1504
|
+
clearLkgRecoveryFence(sessionId);
|
|
715
1505
|
return deletableSessionIds.length;
|
|
716
1506
|
}
|
|
1507
|
+
var SESSION_CLEANUP_SLICE_ROWS = 64;
|
|
1508
|
+
function deleteSessionScopedRowsSlice(db, sessionId, harness) {
|
|
1509
|
+
if (cleanupStatement(db, "SELECT 1 FROM pending_session_cleanup WHERE session_id = ? AND harness = ?").get(sessionId, `${harness}:rust`))
|
|
1510
|
+
return { complete: true, deleted: 0 };
|
|
1511
|
+
cleanupStatement(db, "INSERT OR IGNORE INTO schema_migrations_meta(key,value) VALUES (?,?)").run(tagTombstoneOwnerKey(sessionId, harness), harness);
|
|
1512
|
+
let remaining = SESSION_CLEANUP_SLICE_ROWS;
|
|
1513
|
+
const started = performance.now();
|
|
1514
|
+
remaining -= deleteChunkEmbedBackoffForSession(db, sessionId, remaining);
|
|
1515
|
+
if (remaining <= 0)
|
|
1516
|
+
return { complete: false, deleted: 0 };
|
|
1517
|
+
for (const definition of SESSION_SCOPED_TABLES) {
|
|
1518
|
+
if (definition.table === "message_history_fts") {
|
|
1519
|
+
const rows = cleanupStatement(db, "SELECT fts_rowid AS id FROM message_fts_rowid_map WHERE session_id = ? LIMIT ?").all(sessionId, remaining);
|
|
1520
|
+
const removeFts = cleanupStatement(db, "DELETE FROM message_history_fts WHERE rowid = ?");
|
|
1521
|
+
const removeMap = cleanupStatement(db, "DELETE FROM message_fts_rowid_map WHERE session_id = ? AND fts_rowid = ?");
|
|
1522
|
+
for (const row of rows) {
|
|
1523
|
+
removeFts.run(row.id);
|
|
1524
|
+
removeMap.run(sessionId, row.id);
|
|
1525
|
+
}
|
|
1526
|
+
remaining -= rows.length;
|
|
1527
|
+
if (remaining > 0 && !getMessageFtsRowidMapBackfillProgress2(db).completed)
|
|
1528
|
+
remaining -= cleanupStatement(db, `DELETE FROM message_history_fts WHERE rowid IN (
|
|
1529
|
+
SELECT rowid FROM message_history_fts WHERE session_id = ? LIMIT ?
|
|
1530
|
+
)`).run(sessionId, remaining).changes;
|
|
1531
|
+
} else {
|
|
1532
|
+
const predicates = ["session_id = ?"];
|
|
1533
|
+
if (definition.extraPredicate)
|
|
1534
|
+
predicates.push(definition.extraPredicate);
|
|
1535
|
+
if (definition.harnessScoped)
|
|
1536
|
+
predicates.push("harness = ?");
|
|
1537
|
+
const args = [sessionId, ...definition.harnessScoped ? [harness] : [], remaining];
|
|
1538
|
+
const withoutRowid = definition.table === "temporal_decisions" || definition.table === "session_replay_decisions";
|
|
1539
|
+
const key = withoutRowid ? "message_id" : "rowid";
|
|
1540
|
+
remaining -= cleanupStatement(db, `DELETE FROM ${definition.table}
|
|
1541
|
+
WHERE ${withoutRowid ? "session_id = ? AND " : ""}${key} IN (
|
|
1542
|
+
SELECT ${key} FROM ${definition.table} WHERE ${predicates.join(" AND ")} LIMIT ?
|
|
1543
|
+
)`).run(...withoutRowid ? [sessionId] : [], ...args).changes;
|
|
1544
|
+
}
|
|
1545
|
+
if (remaining <= 0 || performance.now() - started >= 10)
|
|
1546
|
+
return { complete: false, deleted: 0 };
|
|
1547
|
+
}
|
|
1548
|
+
return {
|
|
1549
|
+
complete: true,
|
|
1550
|
+
deleted: deleteSessionScopedRows(db, [sessionId], harness, {
|
|
1551
|
+
deferRecoveryFenceCleanup: true
|
|
1552
|
+
})
|
|
1553
|
+
};
|
|
1554
|
+
}
|
|
717
1555
|
|
|
718
1556
|
// src/features/magic-context/message-index.ts
|
|
719
1557
|
var MESSAGE_HISTORY_ORPHAN_SWEEP_BATCH_SIZE = 200;
|
|
@@ -927,7 +1765,7 @@ function getDeleteMessageFtsMapStatement(db) {
|
|
|
927
1765
|
}
|
|
928
1766
|
function insertMessageFtsRow(db, sessionId, messageOrdinal, messageId, role, content, messageTimeMs) {
|
|
929
1767
|
const result = getInsertMessageStatement(db).run(sessionId, messageOrdinal, messageId, role, content);
|
|
930
|
-
|
|
1768
|
+
recordMessageFtsRowid2(db, sessionId, messageOrdinal, result.lastInsertRowid, messageTimeMs ?? null);
|
|
931
1769
|
}
|
|
932
1770
|
function normalizeSourceVersion(version) {
|
|
933
1771
|
if (typeof version === "number")
|
|
@@ -944,7 +1782,7 @@ function toMessageIndexSource2(message) {
|
|
|
944
1782
|
role: message.role,
|
|
945
1783
|
createdAt: message.createdAt,
|
|
946
1784
|
sourceVersion: normalizeSourceVersion(message.version),
|
|
947
|
-
contentHash:
|
|
1785
|
+
contentHash: createHash4("sha256").update(content).digest("hex"),
|
|
948
1786
|
content
|
|
949
1787
|
};
|
|
950
1788
|
}
|
|
@@ -1027,7 +1865,7 @@ function deleteIndexedMessage2(db, sessionId, messageId) {
|
|
|
1027
1865
|
function clearIndexedMessagesInTransaction2(db, sessionId) {
|
|
1028
1866
|
deleteMessageIndexSkips(db, sessionId);
|
|
1029
1867
|
getDeleteFtsStatement(db).run(sessionId);
|
|
1030
|
-
|
|
1868
|
+
deleteUnmappedMessageFtsRows2(db, [sessionId]);
|
|
1031
1869
|
getDeleteFtsMapStatement(db).run(sessionId);
|
|
1032
1870
|
getDeleteMessageSourceStatement(db).run(sessionId);
|
|
1033
1871
|
getDeleteIndexStatement(db).run(sessionId);
|
|
@@ -1046,10 +1884,10 @@ function getIndexableContent2(role, parts) {
|
|
|
1046
1884
|
if (!hasMeaningfulUserText2(parts)) {
|
|
1047
1885
|
return "";
|
|
1048
1886
|
}
|
|
1049
|
-
return extractTexts2(parts).map(cleanUserText2).map(normalizeIndexText).filter((text) => text.length > 0).join(" / ");
|
|
1887
|
+
return extractTexts2(parts).map(removeTaskNotifications).map(cleanUserText2).map(normalizeIndexText).filter((text) => text.length > 0).join(" / ");
|
|
1050
1888
|
}
|
|
1051
1889
|
if (role === "assistant") {
|
|
1052
|
-
return extractTexts2(parts).map(removeSystemReminders).map(normalizeIndexText).filter((text) => text.length > 0).join(" / ");
|
|
1890
|
+
return extractTexts2(parts).map(removeTaskNotifications).map(removeSystemReminders).map(normalizeIndexText).filter((text) => text.length > 0).join(" / ");
|
|
1053
1891
|
}
|
|
1054
1892
|
return "";
|
|
1055
1893
|
}
|
|
@@ -1058,10 +1896,10 @@ function indexSingleMessageInTransaction(db, sessionId, message, now, dirtyFloor
|
|
|
1058
1896
|
const dirtyFloor = getDirtyIndexFloor(db, sessionId);
|
|
1059
1897
|
if (message.ordinal <= currentWatermark) {
|
|
1060
1898
|
if (isIndexSourceCurrent(db, sessionId, source)) {
|
|
1061
|
-
|
|
1899
|
+
recordIndexedMessageTime2(db, sessionId, message.ordinal, message.createdAt);
|
|
1062
1900
|
return false;
|
|
1063
1901
|
}
|
|
1064
|
-
if (!
|
|
1902
|
+
if (!messageFtsOrdinalRangeIsMapped2(db, sessionId, message.ordinal, message.ordinal)) {
|
|
1065
1903
|
return false;
|
|
1066
1904
|
}
|
|
1067
1905
|
getDeleteMessageFtsStatement(db).run(sessionId, message.id);
|
|
@@ -1091,7 +1929,7 @@ function indexSingleMessage2(db, sessionId, message, shouldCommit = () => true,
|
|
|
1091
1929
|
const source = preparedSource ?? getMessageSourceSnapshot(message);
|
|
1092
1930
|
const currentWatermark = getLastIndexedOrdinal2(db, sessionId);
|
|
1093
1931
|
if (message.ordinal <= currentWatermark && isIndexSourceCurrent(db, sessionId, source)) {
|
|
1094
|
-
|
|
1932
|
+
recordIndexedMessageTime2(db, sessionId, message.ordinal, message.createdAt);
|
|
1095
1933
|
return false;
|
|
1096
1934
|
}
|
|
1097
1935
|
const dirtyFloorBeforeAttempt = getDirtyIndexFloor(db, sessionId);
|
|
@@ -1132,7 +1970,7 @@ function indexItemsAfterOrdinal(db, sessionId, items, finalWatermark, toSource)
|
|
|
1132
1970
|
const currentWatermark = getLastIndexedOrdinal2(db, sessionId);
|
|
1133
1971
|
const dirtyFloor = getDirtyIndexFloor(db, sessionId);
|
|
1134
1972
|
const effectiveWatermark = dirtyFloor === null ? currentWatermark : Math.min(currentWatermark, Math.max(0, dirtyFloor - 1));
|
|
1135
|
-
if (dirtyFloor !== null && dirtyFloor <= currentWatermark && !
|
|
1973
|
+
if (dirtyFloor !== null && dirtyFloor <= currentWatermark && !messageFtsOrdinalRangeIsMapped2(db, sessionId, dirtyFloor, Math.min(currentWatermark, finalWatermark))) {
|
|
1136
1974
|
db.exec("COMMIT");
|
|
1137
1975
|
committed = true;
|
|
1138
1976
|
logSlowWriteTransaction("message_index_reconcile", transactionStartedAt);
|
|
@@ -1184,7 +2022,7 @@ function indexItemsAfterOrdinal(db, sessionId, items, finalWatermark, toSource)
|
|
|
1184
2022
|
return inserted;
|
|
1185
2023
|
}
|
|
1186
2024
|
function harnessSupportsOpenCodeOrphanSweep(harness) {
|
|
1187
|
-
return harness === "opencode";
|
|
2025
|
+
return harness === "opencode" || harness === "opencode2";
|
|
1188
2026
|
}
|
|
1189
2027
|
function getMessageHistoryOrphanSweepState(db, harness) {
|
|
1190
2028
|
return db.prepare("SELECT cursor_session_id, last_swept_at FROM message_history_orphan_sweep WHERE harness = ?").get(harness) ?? {};
|
|
@@ -1213,7 +2051,7 @@ function getOpenCodeSessionScopedCandidateSourceSql(harness) {
|
|
|
1213
2051
|
UNION
|
|
1214
2052
|
`);
|
|
1215
2053
|
}
|
|
1216
|
-
function sweepOrphanedOpenCodeMessageIndexesInBackground(db, openReadableOpenCodeDb, options = {}) {
|
|
2054
|
+
function* sweepOrphanedOpenCodeMessageIndexesInBackground(db, openReadableOpenCodeDb, options = {}) {
|
|
1217
2055
|
const now = options.now ?? Date.now();
|
|
1218
2056
|
const batchSize = Math.max(1, Math.floor(options.batchSize ?? MESSAGE_HISTORY_ORPHAN_SWEEP_BATCH_SIZE));
|
|
1219
2057
|
const safetyAgeMs = Math.max(0, options.safetyAgeMs ?? MESSAGE_HISTORY_ORPHAN_SAFETY_AGE_MS);
|
|
@@ -1227,15 +2065,6 @@ function sweepOrphanedOpenCodeMessageIndexesInBackground(db, openReadableOpenCod
|
|
|
1227
2065
|
}
|
|
1228
2066
|
if (!harnessSupportsOpenCodeOrphanSweep(harness)) {
|
|
1229
2067
|
persistMessageHistoryOrphanSweepState(db, cursor, now + unavailableReprobeMs - cooldownMs, harness);
|
|
1230
|
-
if (harness === "opencode2") {
|
|
1231
|
-
return {
|
|
1232
|
-
status: "unavailable",
|
|
1233
|
-
scanned: 0,
|
|
1234
|
-
deleted: 0,
|
|
1235
|
-
cursor,
|
|
1236
|
-
reason: "OpenCode 2 keeps its sessions in session_v2, which this sweep does not read"
|
|
1237
|
-
};
|
|
1238
|
-
}
|
|
1239
2068
|
return { status: "unavailable", scanned: 0, deleted: 0, cursor };
|
|
1240
2069
|
}
|
|
1241
2070
|
let openCodeDb = null;
|
|
@@ -1248,7 +2077,27 @@ function sweepOrphanedOpenCodeMessageIndexesInBackground(db, openReadableOpenCod
|
|
|
1248
2077
|
persistMessageHistoryOrphanSweepState(db, cursor, now + unavailableReprobeMs - cooldownMs, harness);
|
|
1249
2078
|
return { status: "source_unavailable", scanned: 0, deleted: 0, cursor };
|
|
1250
2079
|
}
|
|
2080
|
+
let v2Reader;
|
|
1251
2081
|
try {
|
|
2082
|
+
let sessionExists;
|
|
2083
|
+
if (harness === "opencode2") {
|
|
2084
|
+
try {
|
|
2085
|
+
const path = getSqliteDatabasePath2(openCodeDb);
|
|
2086
|
+
if (!path)
|
|
2087
|
+
throw new Error("OpenCode 2 source has no durable path");
|
|
2088
|
+
v2Reader = new V2StoreReader(path);
|
|
2089
|
+
const liveIds = new Set(v2Reader.sessionIds());
|
|
2090
|
+
if (liveIds.size === 0)
|
|
2091
|
+
throw new Error("OpenCode 2 source has no sessions");
|
|
2092
|
+
sessionExists = (id) => liveIds.has(id);
|
|
2093
|
+
} catch {
|
|
2094
|
+
persistMessageHistoryOrphanSweepState(db, cursor, now + unavailableReprobeMs - cooldownMs, harness);
|
|
2095
|
+
return { status: "source_unavailable", scanned: 0, deleted: 0, cursor };
|
|
2096
|
+
}
|
|
2097
|
+
} else {
|
|
2098
|
+
const query = openCodeDb.prepare("SELECT 1 FROM session WHERE id = ? LIMIT 1");
|
|
2099
|
+
sessionExists = (id) => !!query.get(id);
|
|
2100
|
+
}
|
|
1252
2101
|
const cutoff = now - safetyAgeMs;
|
|
1253
2102
|
const candidateSourceSql = getOpenCodeSessionScopedCandidateSourceSql(harness);
|
|
1254
2103
|
const candidates = db.prepare(`SELECT session_id
|
|
@@ -1263,39 +2112,56 @@ function sweepOrphanedOpenCodeMessageIndexesInBackground(db, openReadableOpenCod
|
|
|
1263
2112
|
)
|
|
1264
2113
|
ORDER BY session_id ASC
|
|
1265
2114
|
LIMIT ?`).all(cursor, cutoff, batchSize);
|
|
1266
|
-
const
|
|
1267
|
-
const missingSessionIds = candidates.filter((candidate) => !sessionExists.get(candidate.session_id)).map((candidate) => candidate.session_id);
|
|
2115
|
+
const missingSessionIds = candidates.filter((candidate) => !sessionExists(candidate.session_id)).map((candidate) => candidate.session_id);
|
|
1268
2116
|
const nextCursor = candidates.length < batchSize ? "" : candidates[candidates.length - 1]?.session_id ?? cursor;
|
|
1269
2117
|
const completedAt = candidates.length < batchSize ? now : null;
|
|
1270
|
-
db.exec("BEGIN IMMEDIATE");
|
|
1271
|
-
const transactionStartedAt = performance.now();
|
|
1272
|
-
let committed = false;
|
|
1273
2118
|
let deleted = 0;
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
const eligibleSessionIds = missingSessionIds.filter((sessionId) => !sessionExists.get(sessionId) && stillEligible.get(sessionId, cutoff));
|
|
1287
|
-
deleted = deleteSessionScopedRows(db, eligibleSessionIds, harness);
|
|
1288
|
-
persistMessageHistoryOrphanSweepState(db, nextCursor, completedAt, harness);
|
|
1289
|
-
db.exec("COMMIT");
|
|
1290
|
-
committed = true;
|
|
1291
|
-
logSlowWriteTransaction("message_index_orphan_sweep", transactionStartedAt);
|
|
1292
|
-
} finally {
|
|
1293
|
-
if (!committed) {
|
|
2119
|
+
let slices = 0;
|
|
2120
|
+
const stillEligible = db.prepare(`SELECT 1 WHERE NOT EXISTS (
|
|
2121
|
+
SELECT 1 FROM message_history_index
|
|
2122
|
+
WHERE session_id = ? AND harness = '${harness}' AND updated_at > ?
|
|
2123
|
+
)`);
|
|
2124
|
+
for (const sessionId of missingSessionIds) {
|
|
2125
|
+
let complete = false;
|
|
2126
|
+
do {
|
|
2127
|
+
let removed = 0;
|
|
2128
|
+
db.exec("BEGIN IMMEDIATE");
|
|
2129
|
+
const transactionStartedAt = performance.now();
|
|
2130
|
+
let committed = false;
|
|
1294
2131
|
try {
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
2132
|
+
const hostStillMissing = v2Reader ? !v2Reader.sessionExists(sessionId) : !sessionExists(sessionId);
|
|
2133
|
+
if (hostStillMissing && stillEligible.get(sessionId, cutoff)) {
|
|
2134
|
+
const slice = deleteSessionScopedRowsSlice(db, sessionId, harness);
|
|
2135
|
+
removed = slice.deleted;
|
|
2136
|
+
complete = slice.complete;
|
|
2137
|
+
} else
|
|
2138
|
+
complete = true;
|
|
2139
|
+
db.exec("COMMIT");
|
|
2140
|
+
committed = true;
|
|
2141
|
+
logSlowWriteTransaction("message_index_orphan_sweep", transactionStartedAt);
|
|
2142
|
+
} finally {
|
|
2143
|
+
if (!committed) {
|
|
2144
|
+
try {
|
|
2145
|
+
db.exec("ROLLBACK");
|
|
2146
|
+
} catch {}
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
deleted += removed;
|
|
2150
|
+
if (removed)
|
|
2151
|
+
clearLkgRecoveryFence(sessionId);
|
|
2152
|
+
yield;
|
|
2153
|
+
if (++slices >= Math.max(1, options.maxSlices ?? 512)) {
|
|
2154
|
+
return {
|
|
2155
|
+
status: "swept",
|
|
2156
|
+
scanned: candidates.length,
|
|
2157
|
+
deleted,
|
|
2158
|
+
cursor,
|
|
2159
|
+
partial: true
|
|
2160
|
+
};
|
|
2161
|
+
}
|
|
2162
|
+
} while (!complete);
|
|
1298
2163
|
}
|
|
2164
|
+
persistMessageHistoryOrphanSweepState(db, nextCursor, completedAt, harness);
|
|
1299
2165
|
return {
|
|
1300
2166
|
status: "swept",
|
|
1301
2167
|
scanned: candidates.length,
|
|
@@ -1303,11 +2169,24 @@ function sweepOrphanedOpenCodeMessageIndexesInBackground(db, openReadableOpenCod
|
|
|
1303
2169
|
cursor: nextCursor
|
|
1304
2170
|
};
|
|
1305
2171
|
} finally {
|
|
2172
|
+
v2Reader?.close();
|
|
1306
2173
|
closeQuietly(openCodeDb);
|
|
1307
2174
|
}
|
|
1308
2175
|
}
|
|
1309
|
-
function
|
|
1310
|
-
return withoutSqliteTransformPass2(() => withSqliteBackgroundWriter2(() =>
|
|
2176
|
+
async function sweepOrphanedOpenCodeMessageIndexesCooperatively2(db, openSource, options = {}) {
|
|
2177
|
+
return withoutSqliteTransformPass2(() => withSqliteBackgroundWriter2(async () => {
|
|
2178
|
+
const sweep = sweepOrphanedOpenCodeMessageIndexesInBackground(db, openSource, options);
|
|
2179
|
+
try {
|
|
2180
|
+
for (;; ) {
|
|
2181
|
+
const step = sweep.next();
|
|
2182
|
+
if (step.done)
|
|
2183
|
+
return step.value;
|
|
2184
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
2185
|
+
}
|
|
2186
|
+
} finally {
|
|
2187
|
+
sweep.return({ status: "source_unavailable", scanned: 0, deleted: 0, cursor: "" });
|
|
2188
|
+
}
|
|
2189
|
+
}));
|
|
1311
2190
|
}
|
|
1312
2191
|
|
|
1313
|
-
export { BoundedSessionMap, advanceSessionActivity, observeSessionActivity, backfillSessionActivity, clearCompressionDepth, clearCompressionDepthRange, LkgRecoveryFenceError, clearLkgFenceProcessState, lkgServedArrayDigest, hasCleanLkgRecoveryFence, lkgFenceRequiresRebuild, lkgRecoveryFenceUnclean, dirtyLkgRecoveryRevision, ownsLkgDirtyFence, invalidateCleanLkgRecoveryFence, prepareLkgCaptureFence, certifyCommittedLkgCapture, markLkgRecoveryDirty, commitLkgWithRecoveryFence, clearLkgRecoveryFence, deleteSessionScopedRows, MESSAGE_INDEX_MAX_FTS_BYTES2, messageIndexSkipKey2, toMessageIndexSource2, getMessageIndexSourceIdentity2, isMessageIndexSourceCurrent2, getLastIndexedOrdinal2, getIndexedMessageCorpusSize2, markMessageIndexDirty2, getMessageIndexReconciliationStartOrdinal2, isMessageIndexReconciledThrough2, deleteIndexedMessage2, clearIndexedMessagesInTransaction2, clearIndexedMessages2, getIndexableContent2, indexSingleMessage2, indexMessagesAfterOrdinal2, indexSourcesAfterOrdinal2,
|
|
2192
|
+
export { BoundedSessionMap, advanceSessionActivity, observeSessionActivity, backfillSessionActivity, clearCompressionDepth, clearCompressionDepthRange, LkgRecoveryFenceError, clearLkgFenceProcessState, lkgServedArrayDigest, hasCleanLkgRecoveryFence, lkgFenceRequiresRebuild, lkgRecoveryFenceUnclean, dirtyLkgRecoveryRevision, ownsLkgDirtyFence, invalidateCleanLkgRecoveryFence, prepareLkgCaptureFence, certifyCommittedLkgCapture, markLkgRecoveryDirty, commitLkgWithRecoveryFence, clearLkgRecoveryFence, cleanSearchText, keywordRelevance, matchedSearchSnippet, gaDatabasePath, RAW_MESSAGE_TYPES, V2StoreReaderPool, V2StoreReader, deleteSessionScopedRows, MESSAGE_INDEX_MAX_FTS_BYTES2, messageIndexSkipKey2, toMessageIndexSource2, getMessageIndexSourceIdentity2, isMessageIndexSourceCurrent2, getLastIndexedOrdinal2, getIndexedMessageCorpusSize2, markMessageIndexDirty2, getMessageIndexReconciliationStartOrdinal2, isMessageIndexReconciledThrough2, deleteIndexedMessage2, clearIndexedMessagesInTransaction2, clearIndexedMessages2, getIndexableContent2, indexSingleMessage2, indexMessagesAfterOrdinal2, indexSourcesAfterOrdinal2, sweepOrphanedOpenCodeMessageIndexesCooperatively2 };
|