@cortexkit/opencode-magic-context 0.49.6 → 0.49.8
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 +10 -7
- package/dist/compaction-marker-candidates-worker.js +2 -2
- package/dist/{compaction-marker-candidates-cz72pm1t.js → compaction-marker-candidates-xxz35vwp.js} +3 -3
- package/dist/{conflict-warning-hook-1jq1rxx7.js → conflict-warning-hook-w3crzrar.js} +8 -8
- package/dist/{context-ae3gxdye.js → context-x0ccfv0f.js} +88 -53
- package/dist/{derive-budgets-j4ry8j8n.js → derive-budgets-3nbxsyss.js} +2 -2
- package/dist/embedding-backfill-3h2m5n0q.js +21 -0
- package/dist/embedding-worker-process.js +6 -2
- package/dist/embedding-worker.js +6 -5
- package/dist/features/magic-context/compaction-marker-candidates.d.ts.map +1 -1
- package/dist/features/magic-context/compaction-marker.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/cron.d.ts +2 -0
- package/dist/features/magic-context/dreamer/cron.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/hidden-single-shot.d.ts +6 -3
- package/dist/features/magic-context/dreamer/hidden-single-shot.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/map-memories-prompt.d.ts +7 -1
- package/dist/features/magic-context/dreamer/map-memories-prompt.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/map-memories.d.ts +2 -2
- package/dist/features/magic-context/dreamer/map-memories.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/primer-seed.d.ts +2 -0
- package/dist/features/magic-context/dreamer/primer-seed.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/provider-output-failure.d.ts +1 -0
- package/dist/features/magic-context/dreamer/provider-output-failure.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/refresh-primers.d.ts +2 -2
- package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-worker-client.d.ts.map +1 -1
- package/dist/features/magic-context/message-index-async.d.ts.map +1 -1
- package/dist/features/magic-context/migration-worker-client.d.ts.map +1 -1
- package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/wake-plane.d.ts.map +1 -1
- package/dist/features/magic-context/storage-clone.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-persisted.d.ts +10 -3
- package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
- package/dist/features/magic-context/storage-replay-document.d.ts +4 -0
- package/dist/features/magic-context/storage-replay-document.d.ts.map +1 -1
- package/dist/features/magic-context/transform-decision-log.d.ts.map +1 -1
- package/dist/history-owner-ty29fz0f.js +244 -0
- package/dist/hooks/auto-update-checker/checker.d.ts.map +1 -1
- package/dist/hooks/magic-context/apply-operations.d.ts +7 -0
- package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
- package/dist/hooks/magic-context/auto-search-worker-client.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
- package/dist/hooks/magic-context/edit-marker.d.ts +2 -0
- package/dist/hooks/magic-context/edit-marker.d.ts.map +1 -1
- package/dist/hooks/magic-context/heuristic-cleanup.d.ts.map +1 -1
- package/dist/hooks/magic-context/history-boundary-repair.d.ts.map +1 -1
- package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
- package/dist/hooks/magic-context/lkg-persist.d.ts.map +1 -1
- package/dist/hooks/magic-context/module-transport.d.ts +1 -0
- package/dist/hooks/magic-context/module-transport.d.ts.map +1 -1
- package/dist/hooks/magic-context/note-nudger.d.ts +5 -3
- package/dist/hooks/magic-context/note-nudger.d.ts.map +1 -1
- package/dist/hooks/magic-context/postprocess-read-cache.d.ts.map +1 -1
- package/dist/hooks/magic-context/raw-ordinal-warmup.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-chunk.d.ts +15 -4
- package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
- package/dist/hooks/magic-context/replay-committed-drops.d.ts +9 -0
- package/dist/hooks/magic-context/replay-committed-drops.d.ts.map +1 -0
- package/dist/hooks/magic-context/rust-lkg-freeze-registry.d.ts +2 -2
- package/dist/hooks/magic-context/rust-lkg-freeze-registry.d.ts.map +1 -1
- package/dist/hooks/magic-context/rust-mode-transform.d.ts +4 -0
- package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
- package/dist/hooks/magic-context/rust-refusal-recovery.d.ts.map +1 -1
- package/dist/hooks/magic-context/rust-served-raw.d.ts +29 -0
- package/dist/hooks/magic-context/rust-served-raw.d.ts.map +1 -0
- package/dist/hooks/magic-context/strip-content.d.ts +23 -24
- package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
- package/dist/hooks/magic-context/tag-messages.d.ts +2 -0
- package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
- package/dist/hooks/magic-context/tool-drop-target.d.ts +2 -0
- 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.d.ts.map +1 -1
- package/dist/hooks/magic-context/unresolved-history-boundary.d.ts +5 -1
- package/dist/hooks/magic-context/unresolved-history-boundary.d.ts.map +1 -1
- package/dist/hooks/magic-context/wrapup-orchestrator.d.ts.map +1 -1
- package/dist/{index-g9exahnx.js → index-0jz47061.js} +6 -3
- package/dist/{index-4mjhqadz.js → index-150zz6fh.js} +1 -1
- package/dist/{index-ch08qwgd.js → index-2rw244rh.js} +88 -81
- package/dist/{index-6q5h70h2.js → index-4hb8nt93.js} +3 -3
- package/dist/{index-j55vn694.js → index-7bjgyaj1.js} +4 -4
- package/dist/{index-h2wc01qk.js → index-8kk7yh6m.js} +2 -2
- package/dist/{index-vztbgcdk.js → index-9g1c4bt9.js} +304 -92
- package/dist/{index-7q1m1qjk.js → index-a43xq1pg.js} +5 -5
- package/dist/{index-tqf7yg27.js → index-a4b0gbyg.js} +1 -1
- package/dist/{index-vnjhmkwk.js → index-angx1s0k.js} +4 -3
- package/dist/{index-1xaw2pq8.js → index-b4gcb3rk.js} +8 -8
- package/dist/{index-z1tjp257.js → index-b64n4d0h.js} +1 -1
- package/dist/{index-1bt88epp.js → index-bd9cjab3.js} +1 -1
- package/dist/{index-gbmqa9x9.js → index-bmwfzz28.js} +9 -6
- package/dist/{index-qx53vezw.js → index-gjvjh960.js} +1 -1
- package/dist/{index-zgzdm6dg.js → index-gzg9ekc1.js} +155 -103
- package/dist/{index-vh3e2mvr.js → index-hxrvx6cy.js} +121 -51
- package/dist/{index-w23dh319.js → index-jw4gbfkv.js} +1 -1
- package/dist/index-knvkbfvh.js +25 -0
- package/dist/{index-vkbqdnkj.js → index-kvw6xew3.js} +6 -3
- package/dist/{index-bg5qqene.js → index-mp2kghkv.js} +3 -27
- package/dist/{index-nsjyysj0.js → index-mvvephtj.js} +5 -1
- package/dist/{index-cm4hmxyk.js → index-n78avt2p.js} +1 -1
- package/dist/{index-hr64sth2.js → index-pgy3h49n.js} +4 -4
- package/dist/{index-a796ct3s.js → index-pjgzdezw.js} +13 -9
- package/dist/{index-9m7bn21d.js → index-q28hsbpg.js} +15 -3
- package/dist/{index-ab3bgm2m.js → index-q76br5zn.js} +3 -3
- package/dist/{index-xqyj5c85.js → index-qgp5vnt4.js} +5 -5
- package/dist/{index-9s58tq95.js → index-rg1cezmj.js} +22 -22
- package/dist/{index-f3dnvnw6.js → index-s178h9z8.js} +4 -3
- package/dist/{index-mj1fgfrk.js → index-w1ejb9qr.js} +939 -160
- package/dist/{index-k6xvbj2e.js → index-w28y7d0z.js} +2 -0
- package/dist/{index-0nnd4bry.js → index-wafzvyhg.js} +63 -19
- package/dist/{index-ck6y3td2.js → index-xf6k8szp.js} +4 -4
- package/dist/{index-1j8jcjpa.js → index-xpn05jxq.js} +4 -4
- package/dist/{index-s0gsmthp.js → index-ydjgz36f.js} +7 -7
- package/dist/{index-besxhcsh.js → index-zf9nydbd.js} +50 -39
- package/dist/index.js +53 -52
- package/dist/{logger-ewak5q1s.js → logger-wn328tfg.js} +1 -1
- package/dist/{message-index-bounded-630j53ps.js → message-index-bounded-hxjp56qk.js} +16 -16
- package/dist/{message-index-c84wm2g0.js → message-index-zs882h8e.js} +16 -16
- package/dist/{message-time-backfill-8kj57qe3.js → message-time-backfill-nb5hhnrt.js} +3 -3
- package/dist/migration-worker.js +11 -11
- package/dist/plugin/boot-quiet.d.ts.map +1 -1
- package/dist/plugin/dream-timer.d.ts.map +1 -1
- package/dist/plugin/messages-transform.d.ts.map +1 -1
- package/dist/plugin/rpc-handlers.d.ts +2 -0
- package/dist/plugin/rpc-handlers.d.ts.map +1 -1
- package/dist/{project-embedding-registry-vv59t7ah.js → project-embedding-registry-vy7sv9jt.js} +11 -11
- package/dist/raw-ordinal-warmup-worker.js +17 -15
- package/dist/{read-session-chunk-t3vjd0h0.js → read-session-chunk-t9r7r0sg.js} +13 -11
- package/dist/{read-session-raw-gf7eytnm.js → read-session-raw-gm6xj30v.js} +3 -3
- package/dist/recomp-orchestrator-gr36zvd2.js +60 -0
- package/dist/{safe-notification-target-q0cyrx3x.js → safe-notification-target-jdynm677.js} +2 -2
- package/dist/{search-kth2p6cc.js → search-qjen1pmz.js} +24 -24
- package/dist/{send-session-notification-3j6krd4w.js → send-session-notification-q8ngbap3.js} +6 -6
- package/dist/shared/history-owner.d.ts +12 -0
- package/dist/shared/history-owner.d.ts.map +1 -0
- package/dist/shared/off-thread-migration-clock.d.ts +8 -1
- package/dist/shared/off-thread-migration-clock.d.ts.map +1 -1
- package/dist/shared/processed-image-eligibility.d.ts +22 -0
- package/dist/shared/processed-image-eligibility.d.ts.map +1 -0
- package/dist/shared/recovery-reasoning-prefix.d.ts +9 -0
- package/dist/shared/recovery-reasoning-prefix.d.ts.map +1 -0
- package/dist/shared/redaction.d.ts.map +1 -1
- package/dist/shared/rpc-server.d.ts.map +1 -1
- package/dist/shared/sqlite.d.ts +3 -1
- package/dist/shared/sqlite.d.ts.map +1 -1
- package/dist/shared/tag-transcript.d.ts +9 -0
- package/dist/shared/tag-transcript.d.ts.map +1 -1
- package/dist/shared/user-facing-codes.d.ts +2 -2
- package/dist/{sqlite-d7edbk1s.js → sqlite-4pqyynrs.js} +3 -1
- package/dist/{storage-memory-af5zdz98.js → storage-memory-j3v3hc83.js} +2 -2
- package/dist/{storage-tags-dyj7mmbq.js → storage-tags-a513rkj3.js} +5 -5
- package/dist/tools/ctx-expand/search-archive.d.ts +4 -0
- package/dist/tools/ctx-expand/search-archive.d.ts.map +1 -0
- package/dist/tools/ctx-expand/tools.d.ts.map +1 -1
- package/dist/{transform-postprocess-phase-bv3kb5s1.js → transform-postprocess-phase-yv463b7y.js} +30 -30
- package/dist/v2/auto-search-worker.js +10 -7
- package/dist/v2/{compaction-marker-candidates-6gqp5wrc.js → compaction-marker-candidates-a15w3mqh.js} +3 -3
- package/dist/v2/compaction-marker-candidates-worker.js +2 -2
- package/dist/v2/{context-p8f2shp1.js → context-wd82v9z6.js} +1133 -278
- package/dist/v2/{derive-budgets-enckmg7y.js → derive-budgets-0rmpmxjw.js} +3 -3
- package/dist/v2/embedding-backfill-vr7wdtrf.js +20 -0
- package/dist/v2/embedding-worker-process.js +6 -2
- package/dist/v2/embedding-worker.js +6 -5
- package/dist/v2/hidden-completion.d.ts.map +1 -1
- package/dist/v2/history-owner-2cr9py37.js +243 -0
- package/dist/v2/hooks/context.d.ts.map +1 -1
- package/dist/v2/hooks/payload.d.ts.map +1 -1
- package/dist/v2/hooks/store.d.ts +1 -0
- package/dist/v2/hooks/store.d.ts.map +1 -1
- package/dist/v2/{logger-pj02s7sr.js → logger-te6e1j72.js} +1 -1
- package/dist/v2/{message-index-bounded-yzxmf6vp.js → message-index-bounded-madg2kfr.js} +16 -16
- package/dist/v2/{message-index-3ws17x1g.js → message-index-yd94vt9n.js} +16 -16
- package/dist/v2/{message-time-backfill-5nf10c65.js → message-time-backfill-rz1pnpaj.js} +3 -3
- package/dist/v2/migration-worker.js +11 -11
- package/dist/v2/{project-embedding-registry-rsnf2263.js → project-embedding-registry-ja23aygk.js} +11 -11
- package/dist/v2/raw-ordinal-warmup-worker.js +17 -15
- package/dist/v2/{read-session-chunk-vtb0w2kd.js → read-session-chunk-7qs791f3.js} +13 -11
- package/dist/v2/{read-session-raw-gpcddemt.js → read-session-raw-h4azh85b.js} +3 -3
- package/dist/v2/recomp-orchestrator-ee91r9s6.js +59 -0
- package/dist/v2/{safe-notification-target-y3p1hce9.js → safe-notification-target-936374xx.js} +2 -2
- package/dist/v2/search-pn15rr52.js +46 -0
- package/dist/v2/{send-session-notification-5aj2j7vf.js → send-session-notification-6n7chhyp.js} +6 -6
- package/dist/v2/{server-09g9pkvx.js → server-08k4zkfk.js} +50 -39
- package/dist/v2/{server-ras2sxnc.js → server-44ryn9rb.js} +9 -6
- package/dist/v2/{server-158ehmk2.js → server-4easnf78.js} +19 -19
- package/dist/v2/{server-ww9nr8pe.js → server-4gz0xkrc.js} +5 -5
- package/dist/v2/{server-d6g4whc2.js → server-4qr18rfg.js} +3 -3
- package/dist/v2/{server-f9py7s7a.js → server-4rs8sm06.js} +5 -5
- package/dist/v2/{server-yezz0cp2.js → server-63ha7f0f.js} +3 -27
- package/dist/v2/{server-3t0q2cqx.js → server-7dv9wt9r.js} +1 -1
- package/dist/v2/{server-cw6521wn.js → server-7e087rvk.js} +2 -0
- package/dist/v2/{server-hb6xhh9q.js → server-988cqjme.js} +6 -3
- package/dist/v2/{server-keswwkvv.js → server-9a6bwc12.js} +4 -4
- package/dist/v2/{server-t4k4qexw.js → server-9e5r8y60.js} +5 -1
- package/dist/v2/{server-7ekna9zs.js → server-adfte03x.js} +2 -2
- package/dist/v2/{server-9zqewm6g.js → server-ak7w74j9.js} +4 -4
- package/dist/v2/{server-387vkhjq.js → server-bx59ezg6.js} +4 -3
- package/dist/v2/{server-676xw7at.js → server-ckrb5rm8.js} +1 -1
- package/dist/v2/{server-wrem2nmz.js → server-cxsr0ng6.js} +15 -3
- package/dist/v2/{server-t528t8qm.js → server-hn9fb3v0.js} +2 -2
- package/dist/v2/{server-qn23p4h7.js → server-j3dm74ey.js} +9 -9
- package/dist/v2/{server-yd01snxq.js → server-jjsf29aq.js} +4 -4
- package/dist/v2/server-knvkbfvh.js +25 -0
- package/dist/v2/{server-cm4hmxyk.js → server-n78avt2p.js} +1 -1
- package/dist/v2/{server-btgnsv7w.js → server-njnx39q2.js} +6 -3
- package/dist/v2/{server-6a9mxnb2.js → server-npm52h2q.js} +4 -3
- package/dist/v2/{server-vb7pbd9p.js → server-nveratm5.js} +1 -1
- package/dist/v2/{server-7skbfb81.js → server-q360kdb8.js} +121 -51
- package/dist/v2/{server-kzns2hbz.js → server-s6jqh8bj.js} +1 -1
- package/dist/v2/{server-6byqfbqf.js → server-shme41gj.js} +1 -1
- package/dist/v2/{server-0nnd4bry.js → server-wafzvyhg.js} +63 -19
- package/dist/v2/{server-fby0cxbq.js → server-xj4mewze.js} +304 -92
- package/dist/v2/{server-aa9w15cs.js → server-y5g32dhq.js} +88 -81
- package/dist/v2/{server-x2r63c0x.js → server-yak3sxya.js} +7 -7
- package/dist/v2/{server-4tn95znb.js → server-zrwaj29a.js} +2 -2
- package/dist/v2/server.js +9 -8
- package/dist/v2/{sqlite-d7edbk1s.js → sqlite-4pqyynrs.js} +3 -1
- package/dist/v2/{storage-memory-af5zdz98.js → storage-memory-j3v3hc83.js} +2 -2
- package/dist/v2/{storage-tags-d47bpaqa.js → storage-tags-spa92sgj.js} +5 -5
- package/dist/v2/store-reader.d.ts.map +1 -1
- package/dist/v2/{wrapup-orchestrator-cahk37n7.js → wrapup-orchestrator-5wxy1ggv.js} +38 -35
- package/dist/wrapup-orchestrator-gv08c6f9.js +47 -0
- package/package.json +3 -3
- package/src/config/compaction-accessor-guard.test.ts +3 -0
- package/src/features/magic-context/dreamer/cron.ts +8 -0
- package/src/shared/history-owner.test.ts +60 -0
- package/src/shared/history-owner.ts +43 -0
- package/src/shared/host-stall-profiler-node.ts +3 -1
- package/src/shared/host-stall-profiler.ts +3 -1
- package/src/shared/off-thread-migration-clock.test.ts +80 -10
- package/src/shared/off-thread-migration-clock.ts +37 -13
- package/src/shared/processed-image-eligibility.test.ts +68 -0
- package/src/shared/processed-image-eligibility.ts +41 -0
- package/src/shared/processed-image-position-review.json +20 -0
- package/src/shared/processed-image-position-review.test.ts +19 -0
- package/src/shared/recovery-reasoning-prefix.test.ts +34 -0
- package/src/shared/recovery-reasoning-prefix.ts +35 -0
- package/src/shared/redaction.test.ts +16 -0
- package/src/shared/redaction.ts +8 -1
- package/src/shared/root-async-context.probe.mjs +36 -0
- package/src/shared/root-async-context.test.ts +49 -0
- package/src/shared/rpc-server.ts +27 -23
- package/src/shared/sqlite-busy-budget-review.test.ts +291 -0
- package/src/shared/sqlite-foreground-wait.test.ts +253 -1
- package/src/shared/sqlite-writer-diagnostics.test.ts +31 -0
- package/src/shared/sqlite.ts +112 -30
- package/src/shared/tag-transcript.ts +118 -0
- package/src/shared/user-facing-codes.ts +2 -2
- package/dist/embedding-backfill-dtjmyktb.js +0 -21
- package/dist/recomp-orchestrator-1d08g57a.js +0 -60
- package/dist/v2/embedding-backfill-cg4rv90f.js +0 -20
- package/dist/v2/recomp-orchestrator-v64r4s4r.js +0 -59
- package/dist/v2/search-bpa4fxq1.js +0 -46
- package/dist/wrapup-orchestrator-mqp0v195.js +0 -47
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {
|
|
2
|
+
runManagedWrapup2
|
|
3
|
+
} from "./index-pjgzdezw.js";
|
|
4
|
+
import"./index-xf6k8szp.js";
|
|
5
|
+
import"./index-hxrvx6cy.js";
|
|
6
|
+
import"./index-s178h9z8.js";
|
|
7
|
+
import"./index-ydjgz36f.js";
|
|
8
|
+
import"./index-b4gcb3rk.js";
|
|
9
|
+
import"./index-sd3egckz.js";
|
|
10
|
+
import"./index-a43xq1pg.js";
|
|
11
|
+
import"./index-n78avt2p.js";
|
|
12
|
+
import"./index-rg1cezmj.js";
|
|
13
|
+
import"./index-7bjgyaj1.js";
|
|
14
|
+
import"./index-4dhv7mqz.js";
|
|
15
|
+
import"./index-jw4gbfkv.js";
|
|
16
|
+
import"./index-pgy3h49n.js";
|
|
17
|
+
import"./index-qt1qqasc.js";
|
|
18
|
+
import"./index-x8a7jmkh.js";
|
|
19
|
+
import"./index-a4b0gbyg.js";
|
|
20
|
+
import"./index-bmwfzz28.js";
|
|
21
|
+
import"./index-kvw6xew3.js";
|
|
22
|
+
import"./index-zf9nydbd.js";
|
|
23
|
+
import"./index-qgp5vnt4.js";
|
|
24
|
+
import"./index-fb3zxn06.js";
|
|
25
|
+
import"./index-fr0wbhdn.js";
|
|
26
|
+
import"./index-0jz47061.js";
|
|
27
|
+
import"./index-mp2kghkv.js";
|
|
28
|
+
import"./index-q28hsbpg.js";
|
|
29
|
+
import"./index-9g1c4bt9.js";
|
|
30
|
+
import"./index-xpn05jxq.js";
|
|
31
|
+
import"./index-1zx87sdm.js";
|
|
32
|
+
import"./index-q76br5zn.js";
|
|
33
|
+
import"./index-angx1s0k.js";
|
|
34
|
+
import"./index-v0p37hm2.js";
|
|
35
|
+
import"./index-w28y7d0z.js";
|
|
36
|
+
import"./index-8kk7yh6m.js";
|
|
37
|
+
import"./index-bd9cjab3.js";
|
|
38
|
+
import"./index-58e3y90n.js";
|
|
39
|
+
import"./index-150zz6fh.js";
|
|
40
|
+
import"./index-gjvjh960.js";
|
|
41
|
+
import"./index-wafzvyhg.js";
|
|
42
|
+
import"./index-mvvephtj.js";
|
|
43
|
+
import"./index-0sahzkwr.js";
|
|
44
|
+
import"./index-pavzsdc6.js";
|
|
45
|
+
export {
|
|
46
|
+
runManagedWrapup2 as runManagedWrapup
|
|
47
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cortexkit/opencode-magic-context",
|
|
3
|
-
"version": "0.49.
|
|
3
|
+
"version": "0.49.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenCode plugin for Magic Context — cross-session memory and context management",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"check:tui-compiled": "git diff --exit-code -- src/tui-compiled && test -z \"$(git status --porcelain -- src/tui-compiled)\"",
|
|
44
44
|
"check:transformers-node-wasm": "bun scripts/verify-transformers-node-wasm.ts",
|
|
45
45
|
"typecheck": "tsc -p ../retina-local-fs/tsconfig.build.json && tsc --noEmit && tsc -p tsconfig.scripts.json",
|
|
46
|
-
"test": "bun ../../scripts/check-bun.mjs && bun install --frozen-lockfile && BUN_JSC_useOMGJIT=0 bun test --parallel=4 --timeout 30000",
|
|
47
|
-
"test:serial": "bun install --frozen-lockfile && BUN_JSC_useOMGJIT=0 bun test --timeout 30000",
|
|
46
|
+
"test": "bun ../../scripts/check-bun.mjs && bun install --frozen-lockfile && MC_REVIEW_ASSERT_LIVENESS=1 BUN_JSC_useOMGJIT=0 bun test --parallel=4 --timeout 30000",
|
|
47
|
+
"test:serial": "bun install --frozen-lockfile && MC_REVIEW_ASSERT_LIVENESS=1 BUN_JSC_useOMGJIT=0 bun test --timeout 30000",
|
|
48
48
|
"test:parallel:probe": "bun scripts/verify-parallel-db-isolation.ts",
|
|
49
49
|
"lint": "bun ../../scripts/check-bun.mjs && biome check .",
|
|
50
50
|
"lint:fix": "biome check --write .",
|
|
@@ -32,6 +32,9 @@ const ALLOWED_READERS = new Set<string>([
|
|
|
32
32
|
"packages/cli/src/lib/omp-helpers.ts",
|
|
33
33
|
"packages/cli/src/commands/setup-omp.ts",
|
|
34
34
|
"packages/cli/src/commands/doctor-omp.ts",
|
|
35
|
+
// The privacy projection recognizes the setting's name in diagnostic text;
|
|
36
|
+
// it never reads the parsed configuration value.
|
|
37
|
+
"packages/cli/src/lib/issue-privacy.ts",
|
|
35
38
|
]);
|
|
36
39
|
|
|
37
40
|
function sourceFiles(directory: string): string[] {
|
|
@@ -61,6 +61,13 @@ const MINUTE_MS = 60_000;
|
|
|
61
61
|
*/
|
|
62
62
|
const MAX_SEARCH_MS = 4 * 366 * 24 * 60 * MINUTE_MS;
|
|
63
63
|
|
|
64
|
+
let candidateVisitsForTests: (() => void) | undefined;
|
|
65
|
+
|
|
66
|
+
/** Count searched minutes in tests without affecting normal cron callers. */
|
|
67
|
+
export function __setCronCandidateVisitCounterForTests(counter?: () => void): void {
|
|
68
|
+
candidateVisitsForTests = counter;
|
|
69
|
+
}
|
|
70
|
+
|
|
64
71
|
/** Parse one field into the set of matching values, or null on error. */
|
|
65
72
|
function parseField(token: string, spec: FieldSpec): Set<number> | null {
|
|
66
73
|
const values = new Set<number>();
|
|
@@ -226,6 +233,7 @@ export function nextOccurrence(
|
|
|
226
233
|
const capMs = afterMs + Math.max(0, Math.min(MAX_SEARCH_MS, maxSearchMs));
|
|
227
234
|
|
|
228
235
|
while (cursorMs <= capMs) {
|
|
236
|
+
candidateVisitsForTests?.();
|
|
229
237
|
const candidate = new Date(cursorMs);
|
|
230
238
|
if (matchesCron(cron, candidate)) {
|
|
231
239
|
if (!excludeCivilMinute || civilMinuteKey(candidate) !== excludeCivilMinute) {
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { handoffHistoryWork } from "./history-owner";
|
|
3
|
+
import type { MagicContextRpcClient } from "./rpc-client";
|
|
4
|
+
|
|
5
|
+
test("moved-session recomp is handed to the current directory before local work starts", async () => {
|
|
6
|
+
const calls: unknown[] = [];
|
|
7
|
+
const result = await handoffHistoryWork({
|
|
8
|
+
storageDir: "/isolated/mc",
|
|
9
|
+
directory: "/old",
|
|
10
|
+
sessionId: "session",
|
|
11
|
+
method: "recomp",
|
|
12
|
+
params: { sessionId: "session" },
|
|
13
|
+
readDirectory: async () => "/new",
|
|
14
|
+
findOwner: async () =>
|
|
15
|
+
({
|
|
16
|
+
call: async (...args: unknown[]) => {
|
|
17
|
+
calls.push(args);
|
|
18
|
+
return { ok: true };
|
|
19
|
+
},
|
|
20
|
+
}) as unknown as MagicContextRpcClient,
|
|
21
|
+
});
|
|
22
|
+
expect(result).toEqual({ ok: true });
|
|
23
|
+
expect(calls).toEqual([
|
|
24
|
+
["recomp", { sessionId: "session", directory: "/new", historyHandoff: true }],
|
|
25
|
+
]);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("moved-session recovery explains how to load a missing owner and stops handoff loops", async () => {
|
|
29
|
+
for (const historyHandoff of [false, true]) {
|
|
30
|
+
const result = await handoffHistoryWork({
|
|
31
|
+
storageDir: "/isolated/mc",
|
|
32
|
+
directory: "/old",
|
|
33
|
+
sessionId: "session",
|
|
34
|
+
method: "wrapup",
|
|
35
|
+
params: { sessionId: "session", historyHandoff },
|
|
36
|
+
readDirectory: async () => "/new",
|
|
37
|
+
findOwner: async () => null,
|
|
38
|
+
});
|
|
39
|
+
expect(result?.ok).toBe(false);
|
|
40
|
+
expect(result?.error).toContain(
|
|
41
|
+
"Reopen this session in its current project directory (/new)",
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("history recovery remains local when the host still reports this directory", async () => {
|
|
47
|
+
expect(
|
|
48
|
+
await handoffHistoryWork({
|
|
49
|
+
storageDir: "/isolated/mc",
|
|
50
|
+
directory: "/project",
|
|
51
|
+
sessionId: "session",
|
|
52
|
+
method: "recomp",
|
|
53
|
+
params: {},
|
|
54
|
+
readDirectory: async () => "/project",
|
|
55
|
+
findOwner: async () => {
|
|
56
|
+
throw new Error("must not discover");
|
|
57
|
+
},
|
|
58
|
+
}),
|
|
59
|
+
).toBeUndefined();
|
|
60
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { sameProjectDirectory } from "./project-directory-key";
|
|
2
|
+
import { MagicContextRpcClient } from "./rpc-client";
|
|
3
|
+
|
|
4
|
+
/** History-compression children need the current project plugin's prompt and recognition of their run IDs. */
|
|
5
|
+
export async function handoffHistoryWork(args: {
|
|
6
|
+
storageDir: string;
|
|
7
|
+
directory: string;
|
|
8
|
+
sessionId: string;
|
|
9
|
+
method: string;
|
|
10
|
+
params: Record<string, unknown>;
|
|
11
|
+
readDirectory: () => Promise<string | null>;
|
|
12
|
+
findOwner?: typeof MagicContextRpcClient.findSessionOwner;
|
|
13
|
+
}): Promise<Record<string, unknown> | undefined> {
|
|
14
|
+
const current = await args.readDirectory();
|
|
15
|
+
if (current && sameProjectDirectory(current, args.directory)) return undefined;
|
|
16
|
+
const action = `Reopen this session in its current project directory${current ? ` (${current})` : ""}, then run /ctx-recomp.`;
|
|
17
|
+
if (!current || args.params.historyHandoff === true)
|
|
18
|
+
return {
|
|
19
|
+
ok: false,
|
|
20
|
+
error: `History recovery could not locate a stable owning instance. ${action}`,
|
|
21
|
+
};
|
|
22
|
+
const owner = await (args.findOwner ?? MagicContextRpcClient.findSessionOwner)(
|
|
23
|
+
args.storageDir,
|
|
24
|
+
args.sessionId,
|
|
25
|
+
);
|
|
26
|
+
if (!owner)
|
|
27
|
+
return {
|
|
28
|
+
ok: false,
|
|
29
|
+
error: `History recovery needs the instance for ${current}. ${action}`,
|
|
30
|
+
};
|
|
31
|
+
try {
|
|
32
|
+
return await owner.call(args.method, {
|
|
33
|
+
...args.params,
|
|
34
|
+
directory: current,
|
|
35
|
+
historyHandoff: true,
|
|
36
|
+
});
|
|
37
|
+
} catch {
|
|
38
|
+
return {
|
|
39
|
+
ok: false,
|
|
40
|
+
error: `The owning instance could not accept history recovery. ${action}`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -224,7 +224,7 @@ export function createNodeHostStallProfiler(deps: NodeProfilerDeps): HostStallPr
|
|
|
224
224
|
const api = await deps.createSession();
|
|
225
225
|
if (stopped || !existsSync(enablePath)) return;
|
|
226
226
|
session = api;
|
|
227
|
-
api.connect();
|
|
227
|
+
inRootAsyncContext(() => api.connect());
|
|
228
228
|
await post(api, "Profiler.enable");
|
|
229
229
|
await post(api, "Profiler.setSamplingInterval", {
|
|
230
230
|
interval: Math.round(options.sampleIntervalMs * 1000),
|
|
@@ -272,3 +272,5 @@ export function createNodeHostStallProfiler(deps: NodeProfilerDeps): HostStallPr
|
|
|
272
272
|
isUnavailable: () => unavailable,
|
|
273
273
|
};
|
|
274
274
|
}
|
|
275
|
+
|
|
276
|
+
import { inRootAsyncContext } from "./sqlite";
|
|
@@ -29,7 +29,7 @@ export function startHostStallProfilerSwitch(): void {
|
|
|
29
29
|
now: () => performance.now(),
|
|
30
30
|
wallNow: () => Date.now(),
|
|
31
31
|
setInterval: (callback, ms) => {
|
|
32
|
-
const handle = setInterval(callback, ms);
|
|
32
|
+
const handle = inRootAsyncContext(() => setInterval(callback, ms));
|
|
33
33
|
(handle as { unref?: () => void }).unref?.();
|
|
34
34
|
return handle;
|
|
35
35
|
},
|
|
@@ -55,3 +55,5 @@ export function startHostStallProfilerSwitch(): void {
|
|
|
55
55
|
log("[magic-context] host stall profiler: could not start the enable-file switch", error);
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
+
|
|
59
|
+
import { inRootAsyncContext } from "./sqlite";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
2
|
import {
|
|
3
3
|
__resetOffThreadMigrationClockForTests,
|
|
4
|
+
__setOffThreadMigrationClockForTests,
|
|
4
5
|
beginOffThreadMigration,
|
|
5
6
|
startBootDeadline,
|
|
6
7
|
} from "./off-thread-migration-clock";
|
|
@@ -11,6 +12,49 @@ async function expiredWithin(promise: Promise<void>, ms: number): Promise<boolea
|
|
|
11
12
|
return Promise.race([promise.then(() => true), sleep(ms).then(() => false)]);
|
|
12
13
|
}
|
|
13
14
|
|
|
15
|
+
function createFakeClock() {
|
|
16
|
+
let now = 0;
|
|
17
|
+
let nextTimerId = 0;
|
|
18
|
+
const timers = new Map<number, { at: number; callback: () => void }>();
|
|
19
|
+
let timerCountWaiter: { count: number; resolve: () => void } | undefined;
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
now: () => now,
|
|
23
|
+
setTimeout: (callback: () => void, delayMs: number) => {
|
|
24
|
+
const id = ++nextTimerId;
|
|
25
|
+
timers.set(id, { at: now + Math.max(0, delayMs), callback });
|
|
26
|
+
if (timerCountWaiter?.count === timers.size) {
|
|
27
|
+
timerCountWaiter.resolve();
|
|
28
|
+
timerCountWaiter = undefined;
|
|
29
|
+
}
|
|
30
|
+
return () => {
|
|
31
|
+
timers.delete(id);
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
advanceBy: (ms: number) => {
|
|
35
|
+
now += ms;
|
|
36
|
+
while (true) {
|
|
37
|
+
const next = [...timers.entries()].sort(
|
|
38
|
+
(left, right) => left[1].at - right[1].at,
|
|
39
|
+
)[0];
|
|
40
|
+
if (!next || next[1].at > now) return;
|
|
41
|
+
timers.delete(next[0]);
|
|
42
|
+
next[1].callback();
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
waitForTimerCount: (count: number) => {
|
|
46
|
+
if (timers.size === count) return Promise.resolve();
|
|
47
|
+
return new Promise<void>((resolve) => {
|
|
48
|
+
timerCountWaiter = { count, resolve };
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function isSettled(promise: Promise<void>): Promise<boolean> {
|
|
55
|
+
return Promise.race([promise.then(() => true), Promise.resolve(false)]);
|
|
56
|
+
}
|
|
57
|
+
|
|
14
58
|
describe("boot deadline that excludes off-thread migration time", () => {
|
|
15
59
|
afterEach(() => __resetOffThreadMigrationClockForTests());
|
|
16
60
|
|
|
@@ -19,32 +63,58 @@ describe("boot deadline that excludes off-thread migration time", () => {
|
|
|
19
63
|
expect(await expiredWithin(deadline.expired, 500)).toBe(true);
|
|
20
64
|
});
|
|
21
65
|
|
|
22
|
-
test("does not
|
|
66
|
+
test("does not count migration time and resumes with only the pre-migration time spent", async () => {
|
|
67
|
+
const clock = createFakeClock();
|
|
68
|
+
__setOffThreadMigrationClockForTests(clock);
|
|
23
69
|
let deferred = 0;
|
|
70
|
+
let signalDeferred!: () => void;
|
|
71
|
+
const wasDeferred = new Promise<void>((resolve) => {
|
|
72
|
+
signalDeferred = resolve;
|
|
73
|
+
});
|
|
24
74
|
const deadline = startBootDeadline(40, () => {
|
|
25
75
|
deferred += 1;
|
|
76
|
+
signalDeferred();
|
|
26
77
|
});
|
|
78
|
+
clock.advanceBy(15);
|
|
27
79
|
const end = beginOffThreadMigration();
|
|
28
|
-
|
|
80
|
+
clock.advanceBy(25);
|
|
81
|
+
await wasDeferred;
|
|
29
82
|
expect(deferred).toBe(1);
|
|
30
83
|
|
|
31
|
-
|
|
84
|
+
clock.advanceBy(1_000_000);
|
|
85
|
+
expect(await isSettled(deadline.expired)).toBe(false);
|
|
86
|
+
|
|
87
|
+
const nextTimer = clock.waitForTimerCount(1).then(() => "scheduled" as const);
|
|
32
88
|
end();
|
|
89
|
+
expect(
|
|
90
|
+
await Promise.race([nextTimer, deadline.expired.then(() => "expired" as const)]),
|
|
91
|
+
).toBe("scheduled");
|
|
92
|
+
|
|
93
|
+
clock.advanceBy(24);
|
|
94
|
+
expect(await isSettled(deadline.expired)).toBe(false);
|
|
95
|
+
clock.advanceBy(1);
|
|
33
96
|
await deadline.expired;
|
|
34
|
-
// Only the few milliseconds before the migration began were counted, so
|
|
35
|
-
// nearly the whole 40ms budget is still left once it ends.
|
|
36
|
-
expect(performance.now() - endedAt).toBeGreaterThanOrEqual(25);
|
|
37
97
|
});
|
|
38
98
|
|
|
39
99
|
test("time between migrations still counts", async () => {
|
|
100
|
+
const clock = createFakeClock();
|
|
101
|
+
__setOffThreadMigrationClockForTests(clock);
|
|
40
102
|
const deadline = startBootDeadline(60);
|
|
41
|
-
|
|
103
|
+
clock.advanceBy(40);
|
|
42
104
|
const end = beginOffThreadMigration();
|
|
43
|
-
|
|
105
|
+
clock.advanceBy(80);
|
|
106
|
+
expect(await isSettled(deadline.expired)).toBe(false);
|
|
107
|
+
|
|
108
|
+
const nextTimer = clock.waitForTimerCount(1).then(() => "scheduled" as const);
|
|
44
109
|
end();
|
|
45
|
-
|
|
110
|
+
expect(
|
|
111
|
+
await Promise.race([nextTimer, deadline.expired.then(() => "expired" as const)]),
|
|
112
|
+
).toBe("scheduled");
|
|
113
|
+
|
|
114
|
+
clock.advanceBy(19);
|
|
115
|
+
expect(await isSettled(deadline.expired)).toBe(false);
|
|
116
|
+
clock.advanceBy(1);
|
|
46
117
|
await deadline.expired;
|
|
47
|
-
expect(performance.now() - endedAt).toBeLessThan(45);
|
|
48
118
|
});
|
|
49
119
|
|
|
50
120
|
test("a cancelled deadline never expires", async () => {
|
|
@@ -19,9 +19,26 @@ let activeSince = 0;
|
|
|
19
19
|
let accumulatedMs = 0;
|
|
20
20
|
let idleWaiters: Array<() => void> = [];
|
|
21
21
|
|
|
22
|
+
interface TestClock {
|
|
23
|
+
now(): number;
|
|
24
|
+
setTimeout(callback: () => void, delayMs: number): () => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let testClock: TestClock | undefined;
|
|
28
|
+
|
|
29
|
+
function now(): number {
|
|
30
|
+
return testClock?.now() ?? performance.now();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function scheduleTimeout(callback: () => void, delayMs: number): () => void {
|
|
34
|
+
if (testClock) return testClock.setTimeout(callback, delayMs);
|
|
35
|
+
const timer = setTimeout(callback, delayMs);
|
|
36
|
+
return () => clearTimeout(timer);
|
|
37
|
+
}
|
|
38
|
+
|
|
22
39
|
/** Total milliseconds any off-thread migration has been running, up to `now`. */
|
|
23
|
-
export function offThreadMigrationMs(
|
|
24
|
-
return accumulatedMs + (activeMigrations > 0 ? Math.max(0,
|
|
40
|
+
export function offThreadMigrationMs(at = now()): number {
|
|
41
|
+
return accumulatedMs + (activeMigrations > 0 ? Math.max(0, at - activeSince) : 0);
|
|
25
42
|
}
|
|
26
43
|
|
|
27
44
|
export function isOffThreadMigrationRunning(): boolean {
|
|
@@ -33,7 +50,7 @@ export function isOffThreadMigrationRunning(): boolean {
|
|
|
33
50
|
* end; calling it more than once has no further effect.
|
|
34
51
|
*/
|
|
35
52
|
export function beginOffThreadMigration(): () => void {
|
|
36
|
-
if (activeMigrations === 0) activeSince =
|
|
53
|
+
if (activeMigrations === 0) activeSince = now();
|
|
37
54
|
activeMigrations += 1;
|
|
38
55
|
let ended = false;
|
|
39
56
|
return () => {
|
|
@@ -41,7 +58,7 @@ export function beginOffThreadMigration(): () => void {
|
|
|
41
58
|
ended = true;
|
|
42
59
|
activeMigrations -= 1;
|
|
43
60
|
if (activeMigrations > 0) return;
|
|
44
|
-
accumulatedMs += Math.max(0,
|
|
61
|
+
accumulatedMs += Math.max(0, now() - activeSince);
|
|
45
62
|
const waiters = idleWaiters;
|
|
46
63
|
idleWaiters = [];
|
|
47
64
|
for (const wake of waiters) wake();
|
|
@@ -66,10 +83,10 @@ export interface BootDeadlineTimer {
|
|
|
66
83
|
* still running.
|
|
67
84
|
*/
|
|
68
85
|
export function startBootDeadline(timeoutMs: number, onDeferred?: () => void): BootDeadlineTimer {
|
|
69
|
-
const startedAt =
|
|
86
|
+
const startedAt = now();
|
|
70
87
|
const migratedAtStart = offThreadMigrationMs(startedAt);
|
|
71
88
|
const budgetMs = Math.max(0, timeoutMs);
|
|
72
|
-
let
|
|
89
|
+
let cancelTimer: (() => void) | undefined;
|
|
73
90
|
let cancelled = false;
|
|
74
91
|
let deferredReported = false;
|
|
75
92
|
let resolveExpired!: () => void;
|
|
@@ -78,13 +95,14 @@ export function startBootDeadline(timeoutMs: number, onDeferred?: () => void): B
|
|
|
78
95
|
});
|
|
79
96
|
|
|
80
97
|
const remainingMs = (): number => {
|
|
81
|
-
const
|
|
82
|
-
const countedMs =
|
|
98
|
+
const currentTime = now();
|
|
99
|
+
const countedMs =
|
|
100
|
+
currentTime - startedAt - (offThreadMigrationMs(currentTime) - migratedAtStart);
|
|
83
101
|
return budgetMs - countedMs;
|
|
84
102
|
};
|
|
85
103
|
|
|
86
104
|
const check = (): void => {
|
|
87
|
-
|
|
105
|
+
cancelTimer = undefined;
|
|
88
106
|
if (cancelled) return;
|
|
89
107
|
if (isOffThreadMigrationRunning()) {
|
|
90
108
|
if (!deferredReported) {
|
|
@@ -96,28 +114,34 @@ export function startBootDeadline(timeoutMs: number, onDeferred?: () => void): B
|
|
|
96
114
|
}
|
|
97
115
|
const left = remainingMs();
|
|
98
116
|
if (left > 0) {
|
|
99
|
-
|
|
117
|
+
cancelTimer = scheduleTimeout(check, left);
|
|
100
118
|
return;
|
|
101
119
|
}
|
|
102
120
|
resolveExpired();
|
|
103
121
|
};
|
|
104
122
|
|
|
105
|
-
|
|
123
|
+
cancelTimer = scheduleTimeout(check, budgetMs);
|
|
106
124
|
return {
|
|
107
125
|
expired,
|
|
108
126
|
cancel: () => {
|
|
109
127
|
cancelled = true;
|
|
110
|
-
|
|
111
|
-
|
|
128
|
+
cancelTimer?.();
|
|
129
|
+
cancelTimer = undefined;
|
|
112
130
|
},
|
|
113
131
|
};
|
|
114
132
|
}
|
|
115
133
|
|
|
134
|
+
/** Inject deterministic time and timers in tests; production uses host APIs. */
|
|
135
|
+
export function __setOffThreadMigrationClockForTests(clock?: TestClock): void {
|
|
136
|
+
testClock = clock;
|
|
137
|
+
}
|
|
138
|
+
|
|
116
139
|
/** Test seam: forget accumulated migration time between isolated tests. */
|
|
117
140
|
export function __resetOffThreadMigrationClockForTests(): void {
|
|
118
141
|
activeMigrations = 0;
|
|
119
142
|
activeSince = 0;
|
|
120
143
|
accumulatedMs = 0;
|
|
144
|
+
testClock = undefined;
|
|
121
145
|
const waiters = idleWaiters;
|
|
122
146
|
idleWaiters = [];
|
|
123
147
|
for (const wake of waiters) wake();
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { createProcessedImageEligibility } from "./processed-image-eligibility";
|
|
3
|
+
|
|
4
|
+
const messages = ["old", "image", "answer", "tail"];
|
|
5
|
+
|
|
6
|
+
test("image position age inherits its next positive tag, never zero or an earlier tag", () => {
|
|
7
|
+
const policy = createProcessedImageEligibility(
|
|
8
|
+
messages,
|
|
9
|
+
new Map([
|
|
10
|
+
["old", 1],
|
|
11
|
+
["answer", 6],
|
|
12
|
+
]),
|
|
13
|
+
{ watermark: 1 },
|
|
14
|
+
);
|
|
15
|
+
expect(policy.ageTags.get("image")).toBe(6);
|
|
16
|
+
expect(policy.mayStrip("image")).toBe(false);
|
|
17
|
+
expect(policy.ageTags.has("tail")).toBe(false);
|
|
18
|
+
expect(policy.mayStrip("tail")).toBe(false);
|
|
19
|
+
const own = createProcessedImageEligibility(
|
|
20
|
+
messages,
|
|
21
|
+
new Map([
|
|
22
|
+
["image", 3],
|
|
23
|
+
["answer", 6],
|
|
24
|
+
]),
|
|
25
|
+
{ watermark: 4 },
|
|
26
|
+
);
|
|
27
|
+
expect(own.ageTags.get("image")).toBe(3);
|
|
28
|
+
expect(own.mayStrip("image")).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("answered image-only positions outside the protected window are eligible", () => {
|
|
32
|
+
const policy = createProcessedImageEligibility(
|
|
33
|
+
messages,
|
|
34
|
+
new Map([
|
|
35
|
+
["answer", 3],
|
|
36
|
+
["tail", 5],
|
|
37
|
+
]),
|
|
38
|
+
{ watermark: 4, protectedCutoff: 5, contextPercentage: 94.99 },
|
|
39
|
+
);
|
|
40
|
+
expect(policy.ageTags.get("image")).toBe(3);
|
|
41
|
+
expect(policy.mayStrip("image")).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("image positions inside the protected window stay below the 95 percent emergency", () => {
|
|
45
|
+
const policy = createProcessedImageEligibility(messages, new Map([["answer", 6]]), {
|
|
46
|
+
watermark: 10,
|
|
47
|
+
protectedCutoff: 5,
|
|
48
|
+
contextPercentage: 94.99,
|
|
49
|
+
});
|
|
50
|
+
expect(policy.mayStrip("image")).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("image positions follow emergency drops when the newest window yields at 95 percent", () => {
|
|
54
|
+
for (const contextPercentage of [95, 99]) {
|
|
55
|
+
const policy = createProcessedImageEligibility(messages, new Map([["answer", 6]]), {
|
|
56
|
+
watermark: 10,
|
|
57
|
+
protectedCutoff: 5,
|
|
58
|
+
contextPercentage,
|
|
59
|
+
});
|
|
60
|
+
expect(policy.mayStrip("image")).toBe(true);
|
|
61
|
+
const notDroppedYet = createProcessedImageEligibility(messages, new Map([["answer", 11]]), {
|
|
62
|
+
watermark: 10,
|
|
63
|
+
protectedCutoff: 5,
|
|
64
|
+
contextPercentage,
|
|
65
|
+
});
|
|
66
|
+
expect(notDroppedYet.mayStrip("image")).toBe(false);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared processed-image age and recency policy for both OpenCode adapters.
|
|
3
|
+
* Use the owning message's positive maximum tag; an untagged image-only message
|
|
4
|
+
* inherits the next positive tagged message after it, never age zero or a tag
|
|
5
|
+
* from an earlier message. Without either tag, retain the image at the live tail.
|
|
6
|
+
* A positive dropped-tag watermark must cover that position. Below 95% usage,
|
|
7
|
+
* retain every position at or beyond the protected newest-window cutoff. At
|
|
8
|
+
* 95% and above only that window yields, exactly as for emergency tool drops;
|
|
9
|
+
* independent per-tool protections still apply. Content before retained provider-signed
|
|
10
|
+
* thinking must stay unchanged unless an existing whole-turn rewrite removes that
|
|
11
|
+
* thinking first. Callers enforce these protections, later-assistant/stable-id checks, durable
|
|
12
|
+
* persistence and rewrite-only discovery. Frozen replay bypasses eligibility.
|
|
13
|
+
*/
|
|
14
|
+
export function createProcessedImageEligibility<T>(
|
|
15
|
+
messages: readonly T[],
|
|
16
|
+
messageTagNumbers: ReadonlyMap<T, number>,
|
|
17
|
+
options: { watermark: number; protectedCutoff?: number | null; contextPercentage?: number },
|
|
18
|
+
): { ageTags: ReadonlyMap<T, number>; mayStrip: (message: T) => boolean } {
|
|
19
|
+
const ageTags = new Map<T, number>();
|
|
20
|
+
let nextTag: number | undefined;
|
|
21
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
22
|
+
const message = messages[index];
|
|
23
|
+
const ownTag = messageTagNumbers.get(message);
|
|
24
|
+
if (ownTag !== undefined && Number.isFinite(ownTag) && ownTag > 0) nextTag = ownTag;
|
|
25
|
+
if (nextTag !== undefined) ageTags.set(message, nextTag);
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
ageTags,
|
|
29
|
+
mayStrip: (message) => {
|
|
30
|
+
const age = ageTags.get(message);
|
|
31
|
+
return (
|
|
32
|
+
options.watermark > 0 &&
|
|
33
|
+
age !== undefined &&
|
|
34
|
+
age <= options.watermark &&
|
|
35
|
+
((options.contextPercentage ?? 0) >= 95 ||
|
|
36
|
+
options.protectedCutoff == null ||
|
|
37
|
+
age < options.protectedCutoff)
|
|
38
|
+
);
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[
|
|
2
|
+
{ "name": "own maximum wins over a later smaller tag", "tags": [[2, 10, 4], [3]], "target": 0, "watermark": 8, "cutoff": null, "percentage": 60, "age": 10, "eligible": false },
|
|
3
|
+
{ "name": "own maximum at watermark", "tags": [[2, 10, 4], [30]], "target": 0, "watermark": 10, "cutoff": null, "percentage": 60, "age": 10, "eligible": true },
|
|
4
|
+
{ "name": "first later tag not largest later tag", "tags": [[], [3], [20]], "target": 0, "watermark": 3, "cutoff": null, "percentage": 60, "age": 3, "eligible": true },
|
|
5
|
+
{ "name": "first later message contributes its own maximum", "tags": [[], [2, 9], [3]], "target": 0, "watermark": 5, "cutoff": null, "percentage": 60, "age": 9, "eligible": false },
|
|
6
|
+
{ "name": "skip empty and zero tags when looking forward", "tags": [[1], [], [0], [4]], "target": 1, "watermark": 4, "cutoff": null, "percentage": 60, "age": 4, "eligible": true },
|
|
7
|
+
{ "name": "zero own tag inherits later positive", "tags": [[0], [7]], "target": 0, "watermark": 6, "cutoff": null, "percentage": 60, "age": 7, "eligible": false },
|
|
8
|
+
{ "name": "preceding tag cannot age live tail", "tags": [[1], [], []], "target": 1, "watermark": 100, "cutoff": null, "percentage": 60, "age": null, "eligible": false },
|
|
9
|
+
{ "name": "all untagged is ineligible", "tags": [[], []], "target": 0, "watermark": 100, "cutoff": null, "percentage": 60, "age": null, "eligible": false },
|
|
10
|
+
{ "name": "watermark zero even with emergency", "tags": [[], [1]], "target": 0, "watermark": 0, "cutoff": 1, "percentage": 95, "age": 1, "eligible": false },
|
|
11
|
+
{ "name": "age above watermark even with emergency", "tags": [[], [7]], "target": 0, "watermark": 6, "cutoff": 1, "percentage": 100, "age": 7, "eligible": false },
|
|
12
|
+
{ "name": "cutoff inclusive at 94.99 percent", "tags": [[5], [8]], "target": 0, "watermark": 9, "cutoff": 5, "percentage": 94.99, "age": 5, "eligible": false },
|
|
13
|
+
{ "name": "inside cutoff is a range not set membership", "tags": [[], [6]], "target": 0, "watermark": 9, "cutoff": 5, "percentage": 60, "age": 6, "eligible": false },
|
|
14
|
+
{ "name": "below cutoff is eligible", "tags": [[4], [8]], "target": 0, "watermark": 9, "cutoff": 5, "percentage": 94.99, "age": 4, "eligible": true },
|
|
15
|
+
{ "name": "exactly 95 waives cutoff", "tags": [[], [6]], "target": 0, "watermark": 9, "cutoff": 5, "percentage": 95, "age": 6, "eligible": true },
|
|
16
|
+
{ "name": "above 95 waives cutoff", "tags": [[5], [8]], "target": 0, "watermark": 9, "cutoff": 5, "percentage": 95.01, "age": 5, "eligible": true },
|
|
17
|
+
{ "name": "emergency cannot invent tail age", "tags": [[1], []], "target": 1, "watermark": 100, "cutoff": 1, "percentage": 100, "age": null, "eligible": false },
|
|
18
|
+
{ "name": "own positive tag overrides later greater tag", "tags": [[2], [20]], "target": 0, "watermark": 2, "cutoff": 3, "percentage": 60, "age": 2, "eligible": true },
|
|
19
|
+
{ "name": "last message with own tag is aged", "tags": [[1], [4]], "target": 1, "watermark": 4, "cutoff": null, "percentage": 60, "age": 4, "eligible": true }
|
|
20
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { createProcessedImageEligibility } from "./processed-image-eligibility";
|
|
3
|
+
import cases from "./processed-image-position-review.json";
|
|
4
|
+
|
|
5
|
+
// OpenCode, Rust and Pi can reuse these cases: each row supplies per-message
|
|
6
|
+
// tag numbers rather than provider-specific message objects or image encodings.
|
|
7
|
+
for (const row of cases) {
|
|
8
|
+
test(`independent image position table: ${row.name}`, () => {
|
|
9
|
+
const messages = row.tags.map((_, i) => `message-${i}`);
|
|
10
|
+
const tags = new Map(messages.map((message, i) => [message, Math.max(0, ...row.tags[i])]));
|
|
11
|
+
const policy = createProcessedImageEligibility(messages, tags, {
|
|
12
|
+
watermark: row.watermark,
|
|
13
|
+
protectedCutoff: row.cutoff,
|
|
14
|
+
contextPercentage: row.percentage,
|
|
15
|
+
});
|
|
16
|
+
expect(policy.ageTags.get(messages[row.target]) ?? null).toBe(row.age);
|
|
17
|
+
expect(policy.mayStrip(messages[row.target])).toBe(row.eligible);
|
|
18
|
+
});
|
|
19
|
+
}
|