@cortexkit/opencode-magic-context 0.46.1 → 0.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/magic-context-prompt.d.ts +1 -1
- package/dist/agents/magic-context-prompt.d.ts.map +1 -1
- package/dist/{announcement-y35wzg2r.js → announcement-fp14dw0n.js} +3 -2
- package/dist/auto-search-worker.js +112 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/live-run-config.d.ts.map +1 -1
- package/dist/config/migrate-config-location.d.ts.map +1 -1
- package/dist/config/mural-model-deprecation.d.ts +15 -0
- package/dist/config/mural-model-deprecation.d.ts.map +1 -0
- package/dist/config/project-security.d.ts +0 -3
- package/dist/config/project-security.d.ts.map +1 -1
- package/dist/config/schema/magic-context.d.ts +21 -9
- package/dist/config/schema/magic-context.d.ts.map +1 -1
- package/dist/{conflict-warning-hook-7fj2b9jw.js → conflict-warning-hook-w8cykrnf.js} +12 -9
- package/dist/{context-sh2m2vde.js → context-m0cy262m.js} +1785 -913
- package/dist/{derive-budgets-7nj7r1rv.js → derive-budgets-sg0xsq7s.js} +4 -3
- package/dist/embedding-activity-g1dhes6g.js +10 -0
- package/dist/embedding-backfill-9rcm1mfy.js +21 -0
- package/dist/embedding-cache-mgyr73ef.js +16 -0
- package/dist/embedding-worker.js +11 -9
- package/dist/features/magic-context/compaction-marker.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/evaluate-smart-notes.d.ts +1 -0
- package/dist/features/magic-context/dreamer/evaluate-smart-notes.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-config.d.ts +3 -3
- package/dist/features/magic-context/dreamer/task-config.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-executor.d.ts +1 -1
- package/dist/features/magic-context/dreamer/task-executor.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-gates.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-registry.d.ts +1 -0
- package/dist/features/magic-context/dreamer/task-registry.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/verify.d.ts.map +1 -1
- package/dist/features/magic-context/message-index-async.d.ts +16 -3
- package/dist/features/magic-context/message-index-async.d.ts.map +1 -1
- package/dist/features/magic-context/message-index-bounded.d.ts +17 -0
- package/dist/features/magic-context/message-index-bounded.d.ts.map +1 -0
- package/dist/features/magic-context/message-index.d.ts +7 -1
- package/dist/features/magic-context/message-index.d.ts.map +1 -1
- package/dist/features/magic-context/mural/render-trigger.d.ts +7 -4
- package/dist/features/magic-context/mural/render-trigger.d.ts.map +1 -1
- package/dist/features/magic-context/overflow-detection.d.ts +2 -0
- package/dist/features/magic-context/overflow-detection.d.ts.map +1 -1
- package/dist/features/magic-context/pi-content-decisions.d.ts +7 -1
- package/dist/features/magic-context/pi-content-decisions.d.ts.map +1 -1
- package/dist/features/magic-context/project-embedding-registry.d.ts +7 -0
- package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
- package/dist/features/magic-context/search.d.ts +31 -0
- package/dist/features/magic-context/search.d.ts.map +1 -1
- package/dist/features/magic-context/session-activity.d.ts.map +1 -1
- package/dist/features/magic-context/session-project-storage.d.ts +8 -2
- package/dist/features/magic-context/session-project-storage.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/runner.d.ts +1 -0
- package/dist/features/magic-context/smart-notes/runner.d.ts.map +1 -1
- package/dist/features/magic-context/storage-db.d.ts.map +1 -1
- package/dist/features/magic-context/storage-historian-runs.d.ts +2 -5
- package/dist/features/magic-context/storage-historian-runs.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-persisted.d.ts +12 -0
- package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-session.d.ts +6 -0
- package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
- package/dist/features/magic-context/storage-tag-identity-revision.d.ts +30 -0
- package/dist/features/magic-context/storage-tag-identity-revision.d.ts.map +1 -0
- package/dist/features/magic-context/storage-tags.d.ts +148 -3
- package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
- package/dist/features/magic-context/storage.d.ts +1 -0
- package/dist/features/magic-context/storage.d.ts.map +1 -1
- package/dist/features/magic-context/subagent-token-capture.d.ts.map +1 -1
- package/dist/features/magic-context/transform-decision-log.d.ts +4 -2
- package/dist/features/magic-context/transform-decision-log.d.ts.map +1 -1
- package/dist/harness-e4avh192.js +10 -0
- package/dist/hooks/magic-context/active-anthropic-turn.d.ts +10 -0
- package/dist/hooks/magic-context/active-anthropic-turn.d.ts.map +1 -0
- package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
- package/dist/hooks/magic-context/auto-search-blocking.fixture.d.ts +2 -0
- package/dist/hooks/magic-context/auto-search-blocking.fixture.d.ts.map +1 -0
- package/dist/hooks/magic-context/auto-search-deadline.d.ts +7 -0
- package/dist/hooks/magic-context/auto-search-deadline.d.ts.map +1 -1
- package/dist/hooks/magic-context/auto-search-host-review.fixture.d.ts +3 -0
- package/dist/hooks/magic-context/auto-search-host-review.fixture.d.ts.map +1 -0
- package/dist/hooks/magic-context/auto-search-lock-review.fixture.d.ts +2 -0
- package/dist/hooks/magic-context/auto-search-lock-review.fixture.d.ts.map +1 -0
- package/dist/hooks/magic-context/auto-search-long-lock-review.fixture.d.ts +2 -0
- package/dist/hooks/magic-context/auto-search-long-lock-review.fixture.d.ts.map +1 -0
- package/dist/hooks/magic-context/auto-search-omp-host-review.fixture.d.ts +4 -0
- package/dist/hooks/magic-context/auto-search-omp-host-review.fixture.d.ts.map +1 -0
- package/dist/hooks/magic-context/auto-search-omp-loader.fixture.d.ts +3 -0
- package/dist/hooks/magic-context/auto-search-omp-loader.fixture.d.ts.map +1 -0
- package/dist/hooks/magic-context/auto-search-runner.d.ts +1 -1
- package/dist/hooks/magic-context/auto-search-runner.d.ts.map +1 -1
- package/dist/hooks/magic-context/auto-search-snapshot.fixture.d.ts +4 -0
- package/dist/hooks/magic-context/auto-search-snapshot.fixture.d.ts.map +1 -0
- package/dist/hooks/magic-context/auto-search-worker-client.d.ts +98 -0
- package/dist/hooks/magic-context/auto-search-worker-client.d.ts.map +1 -0
- package/dist/hooks/magic-context/auto-search-worker.d.ts +2 -0
- package/dist/hooks/magic-context/auto-search-worker.d.ts.map +1 -0
- package/dist/hooks/magic-context/caveman-cleanup.d.ts.map +1 -1
- package/dist/hooks/magic-context/command-handler.d.ts +1 -0
- package/dist/hooks/magic-context/command-handler.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-recomp.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-types.d.ts +3 -5
- package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner.d.ts +2 -1
- package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-trigger.d.ts +3 -1
- package/dist/hooks/magic-context/compartment-trigger.d.ts.map +1 -1
- package/dist/hooks/magic-context/event-handler.d.ts +1 -1
- package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/event-resolvers.d.ts.map +1 -1
- package/dist/hooks/magic-context/execute-status.d.ts.map +1 -1
- package/dist/hooks/magic-context/final-wire-token-estimate.d.ts +2 -0
- package/dist/hooks/magic-context/final-wire-token-estimate.d.ts.map +1 -1
- package/dist/hooks/magic-context/heuristic-cleanup.d.ts.map +1 -1
- package/dist/hooks/magic-context/historian-drain-gate.d.ts +11 -0
- package/dist/hooks/magic-context/historian-drain-gate.d.ts.map +1 -0
- package/dist/hooks/magic-context/hook.d.ts +3 -2
- package/dist/hooks/magic-context/hook.d.ts.map +1 -1
- package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
- package/dist/hooks/magic-context/latest-assistant-turn.d.ts +25 -0
- package/dist/hooks/magic-context/latest-assistant-turn.d.ts.map +1 -0
- package/dist/hooks/magic-context/latest-thinking-recovery.d.ts +61 -0
- package/dist/hooks/magic-context/latest-thinking-recovery.d.ts.map +1 -0
- package/dist/hooks/magic-context/lkg-persist.d.ts +4 -3
- package/dist/hooks/magic-context/lkg-persist.d.ts.map +1 -1
- package/dist/hooks/magic-context/lkg-replay.d.ts +13 -8
- package/dist/hooks/magic-context/lkg-replay.d.ts.map +1 -1
- package/dist/hooks/magic-context/lkg-slot.d.ts +139 -2
- package/dist/hooks/magic-context/lkg-slot.d.ts.map +1 -1
- package/dist/hooks/magic-context/message-search-accepting-silent-worker.fixture.d.ts +2 -0
- package/dist/hooks/magic-context/message-search-accepting-silent-worker.fixture.d.ts.map +1 -0
- package/dist/hooks/magic-context/module-wire.d.ts.map +1 -1
- package/dist/hooks/magic-context/oc-stall-r2-warmup-worker.fixture.d.ts +2 -0
- package/dist/hooks/magic-context/oc-stall-r2-warmup-worker.fixture.d.ts.map +1 -0
- package/dist/hooks/magic-context/oc-stall-search-review.fixture.d.ts +2 -0
- package/dist/hooks/magic-context/oc-stall-search-review.fixture.d.ts.map +1 -0
- package/dist/hooks/magic-context/opencode-proven-floor.d.ts +6 -0
- package/dist/hooks/magic-context/opencode-proven-floor.d.ts.map +1 -0
- package/dist/hooks/magic-context/opencode1-to-model-messages.fixture.d.ts +63 -0
- package/dist/hooks/magic-context/opencode1-to-model-messages.fixture.d.ts.map +1 -0
- package/dist/hooks/magic-context/pass-outcome.d.ts.map +1 -1
- package/dist/hooks/magic-context/protected-tail-boundary.d.ts.map +1 -1
- package/dist/hooks/magic-context/provider-visible-parts.d.ts +45 -0
- package/dist/hooks/magic-context/provider-visible-parts.d.ts.map +1 -0
- package/dist/hooks/magic-context/raw-ordinal-warmup-silent-worker.fixture.d.ts +2 -0
- package/dist/hooks/magic-context/raw-ordinal-warmup-silent-worker.fixture.d.ts.map +1 -0
- package/dist/hooks/magic-context/raw-ordinal-warmup-worker.d.ts +2 -0
- package/dist/hooks/magic-context/raw-ordinal-warmup-worker.d.ts.map +1 -0
- package/dist/hooks/magic-context/raw-ordinal-warmup.d.ts +87 -0
- package/dist/hooks/magic-context/raw-ordinal-warmup.d.ts.map +1 -0
- package/dist/hooks/magic-context/read-session-chunk.d.ts +12 -0
- package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-db.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-formatting.d.ts +5 -0
- package/dist/hooks/magic-context/read-session-formatting.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-raw.d.ts +83 -1
- package/dist/hooks/magic-context/read-session-raw.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-true-raw-tokens.d.ts.map +1 -1
- package/dist/hooks/magic-context/reasoning-budget-status.d.ts +15 -0
- package/dist/hooks/magic-context/reasoning-budget-status.d.ts.map +1 -0
- package/dist/hooks/magic-context/reasoning-budget.d.ts +40 -0
- package/dist/hooks/magic-context/reasoning-budget.d.ts.map +1 -0
- package/dist/hooks/magic-context/reasoning-removal.d.ts +6 -6
- package/dist/hooks/magic-context/reasoning-removal.d.ts.map +1 -1
- package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
- package/dist/hooks/magic-context/sentinel.d.ts +45 -1
- package/dist/hooks/magic-context/sentinel.d.ts.map +1 -1
- package/dist/hooks/magic-context/strip-content.d.ts +21 -5
- package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
- package/dist/hooks/magic-context/system-prompt-hash.d.ts +1 -0
- package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
- package/dist/hooks/magic-context/tag-messages.d.ts +21 -0
- package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
- package/dist/hooks/magic-context/tag-owner-warmup.d.ts +6 -0
- package/dist/hooks/magic-context/tag-owner-warmup.d.ts.map +1 -0
- package/dist/hooks/magic-context/tail-hygiene-walk.d.ts.map +1 -1
- package/dist/hooks/magic-context/token-count-exact.d.ts +35 -0
- package/dist/hooks/magic-context/token-count-exact.d.ts.map +1 -0
- package/dist/hooks/magic-context/tool-drop-target.d.ts +5 -0
- package/dist/hooks/magic-context/tool-drop-target.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-compartment-phase.d.ts +3 -28
- package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +28 -6
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts +11 -3
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/hooks/magic-context/wrapup-orchestrator.d.ts.map +1 -1
- package/dist/{index-hpecdzec.js → index-0an7742d.js} +6820 -6587
- package/dist/index-0qhce371.js +120 -0
- package/dist/{index-q6hef7fh.js → index-0sahzkwr.js} +6 -19
- package/dist/{index-k5bdx1bx.js → index-0w56v4fm.js} +3 -5
- package/dist/index-14cjxt20.js +819 -0
- package/dist/{v2/server-51dv1pr5.js → index-1qb0z7wa.js} +301 -27
- package/dist/index-2bvekkfy.js +11 -0
- package/dist/{index-sj29b3z2.js → index-2y0s2hep.js} +3 -3
- package/dist/index-4dhv7mqz.js +131 -0
- package/dist/{index-b35pxds2.js → index-58e3y90n.js} +1 -18
- package/dist/index-5zygecde.js +152 -0
- package/dist/{index-sjthx78w.js → index-692jzy0p.js} +16879 -22907
- package/dist/index-72nhv486.js +2307 -0
- package/dist/index-7jzrrxap.js +931 -0
- package/dist/index-7mx8v0pw.js +3591 -0
- package/dist/index-8ajstryz.js +18 -0
- package/dist/{index-sfpzqvdz.js → index-8cd2j99z.js} +2 -3
- package/dist/{index-hybnrwq0.js → index-8j7gqj40.js} +131 -128
- package/dist/index-bdckkfw6.js +3261 -0
- package/dist/{index-8x2v7gg9.js → index-bhvjj0ys.js} +158 -2455
- package/dist/index-c43kr12f.js +3030 -0
- package/dist/index-cb7emxgj.js +984 -0
- package/dist/index-d9vfe7xq.js +16 -0
- package/dist/{index-5gq3b2ya.js → index-db7zwa3v.js} +147 -209
- package/dist/index-dhwzyd54.js +1142 -0
- package/dist/{index-8da2zjhz.js → index-eeezr5zz.js} +9 -7
- package/dist/{index-pkbcrdqs.js → index-f0h6xwqm.js} +3412 -4552
- package/dist/index-fb3zxn06.js +13 -0
- package/dist/index-fbv1dn35.js +171 -0
- package/dist/index-fr0wbhdn.js +43 -0
- package/dist/{index-mc22ayvf.js → index-ghc9f0n7.js} +11 -9
- package/dist/{index-fzbvakh0.js → index-jcbd3znx.js} +3 -3
- package/dist/{index-5p8jdswx.js → index-k1xb52kd.js} +20 -13
- package/dist/index-k9nnwad5.js +3532 -0
- package/dist/{v2/server-fg34g6a5.js → index-kb9nn0a0.js} +309 -52
- package/dist/index-knh4y330.js +566 -0
- package/dist/{index-q4yp4b52.js → index-mfkzp7qj.js} +28 -28
- package/dist/{index-sgthv3f7.js → index-mmyh6d8f.js} +4 -4
- package/dist/index-pavzsdc6.js +17 -0
- package/dist/{index-0d3g3461.js → index-pdecwzrd.js} +19 -16
- package/dist/index-pg50n748.js +1060 -0
- package/dist/index-q2tqeqde.js +2106 -0
- package/dist/index-q7h7csab.js +3469 -0
- package/dist/index-qt1qqasc.js +22 -0
- package/dist/{index-majxda46.js → index-ty64eqcn.js} +28 -18
- package/dist/{index-7qw742ev.js → index-yq8n9qcb.js} +25 -44
- package/dist/{index-sfnj6h8h.js → index-z8e0psj8.js} +20 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1061 -260
- package/dist/logger-d3wex3dz.js +20 -0
- package/dist/message-index-bounded-7ezbgddp.js +124 -0
- package/dist/{v2/message-time-backfill-r6qkem1x.js → message-time-backfill-brbd63hv.js} +8 -6
- package/dist/migration-worker.js +25 -16
- package/dist/{model-resolution-xhfmyyvy.js → model-resolution-nx97z3x7.js} +1 -1
- package/dist/plugin/dream-timer.d.ts +1 -1
- package/dist/plugin/dream-timer.d.ts.map +1 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts +3 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
- package/dist/plugin/host-stall-profiler-report.d.ts +2 -0
- package/dist/plugin/host-stall-profiler-report.d.ts.map +1 -0
- package/dist/plugin/host-stall-profiler.d.ts +3 -0
- package/dist/plugin/host-stall-profiler.d.ts.map +1 -0
- package/dist/plugin/messages-transform.d.ts +21 -19
- package/dist/plugin/messages-transform.d.ts.map +1 -1
- package/dist/plugin/rpc-handlers.d.ts.map +1 -1
- package/dist/plugin/tool-registry.d.ts.map +1 -1
- package/dist/project-embedding-registry-7qzptcw7.js +88 -0
- package/dist/raw-ordinal-warmup-worker.js +60 -0
- package/dist/{read-session-chunk-egz7r68d.js → read-session-chunk-6k83m4fj.js} +15 -9
- package/dist/read-session-raw-3g9qy1xa.js +58 -0
- package/dist/recomp-orchestrator-d3fp1cy0.js +57 -0
- package/dist/{v2/safe-notification-target-eb1n1vfb.js → safe-notification-target-5ffqq32m.js} +4 -2
- package/dist/search-epax1ecv.js +45 -0
- package/dist/{v2/send-session-notification-qs0j78v6.js → send-session-notification-erecz4y4.js} +10 -6
- package/dist/shared/auto-search-lifecycle.d.ts +7 -0
- package/dist/shared/auto-search-lifecycle.d.ts.map +1 -0
- package/dist/shared/conflict-detector.d.ts.map +1 -1
- package/dist/shared/historian-tool-expansions.d.ts +1 -1
- package/dist/shared/historian-tool-expansions.d.ts.map +1 -1
- package/dist/shared/historian-tool-template.d.ts +2 -2
- package/dist/shared/historian-tool-template.d.ts.map +1 -1
- package/dist/shared/host-stall-profiler-bun.d.ts +46 -0
- package/dist/shared/host-stall-profiler-bun.d.ts.map +1 -0
- package/dist/shared/host-stall-profiler-common.d.ts +58 -0
- package/dist/shared/host-stall-profiler-common.d.ts.map +1 -0
- package/dist/shared/host-stall-profiler-node.d.ts +36 -0
- package/dist/shared/host-stall-profiler-node.d.ts.map +1 -0
- package/dist/shared/host-stall-profiler-report.d.ts +68 -0
- package/dist/shared/host-stall-profiler-report.d.ts.map +1 -0
- package/dist/shared/host-stall-profiler.d.ts +5 -0
- package/dist/shared/host-stall-profiler.d.ts.map +1 -0
- package/dist/shared/model-resolution.d.ts +2 -3
- package/dist/shared/model-resolution.d.ts.map +1 -1
- package/dist/shared/sqlite.d.ts +46 -1
- package/dist/shared/sqlite.d.ts.map +1 -1
- package/dist/shared/stable-json.d.ts.map +1 -1
- package/dist/shared/status-view.d.ts.map +1 -1
- package/dist/shared/user-facing-codes.d.ts +0 -5
- package/dist/shared/user-facing-codes.d.ts.map +1 -1
- package/dist/shared/window-geometry.d.ts +4 -0
- package/dist/shared/window-geometry.d.ts.map +1 -1
- package/dist/sqlite-context-pragmas-mv9jqwjh.js +6 -0
- package/dist/sqlite-mhjkerzq.js +46 -0
- package/dist/storage-memory-07fx57k6.js +71 -0
- package/dist/storage-tags-d3b0v2ss.js +131 -0
- package/dist/{task-config-v81k8jvk.js → task-config-cgpg0nvh.js} +2 -2
- package/dist/tools/ctx-expand/render.d.ts.map +1 -1
- package/dist/tools/ctx-expand/tools.d.ts.map +1 -1
- package/dist/tools/ctx-note/constants.d.ts +4 -0
- package/dist/tools/ctx-note/constants.d.ts.map +1 -1
- package/dist/tools/ctx-note/tools.d.ts +1 -0
- package/dist/tools/ctx-note/tools.d.ts.map +1 -1
- package/dist/tools/ctx-search/tools.d.ts.map +1 -1
- package/dist/tools/parameter-descriptions.d.ts +2 -2
- package/dist/tools/parameter-descriptions.d.ts.map +1 -1
- package/dist/{transform-postprocess-phase-bqhz6vdr.js → transform-postprocess-phase-spcy2qre.js} +35 -17
- package/dist/transformers-node-wasm.js +18415 -18415
- package/dist/v2/auto-search-worker.js +112 -0
- package/dist/v2/{context-zecmzk5s.js → context-sf98kstd.js} +8378 -8530
- package/dist/v2/{derive-budgets-kdgp4s3r.js → derive-budgets-3vc0y84t.js} +4 -3
- package/dist/v2/embedding-activity-g1dhes6g.js +10 -0
- package/dist/v2/embedding-backfill-vn8g1nh1.js +21 -0
- package/dist/v2/embedding-cache-mgyr73ef.js +16 -0
- package/dist/v2/embedding-worker.js +12 -9
- package/dist/v2/fold/host-media.d.ts +4 -0
- package/dist/v2/fold/host-media.d.ts.map +1 -1
- package/dist/v2/fold/memory-cache.d.ts +70 -0
- package/dist/v2/fold/memory-cache.d.ts.map +1 -0
- package/dist/v2/fold/native-codec.d.ts +10 -0
- package/dist/v2/fold/native-codec.d.ts.map +1 -0
- package/dist/v2/fold/native-replay.d.ts +58 -0
- package/dist/v2/fold/native-replay.d.ts.map +1 -0
- package/dist/v2/fold/policy.d.ts +24 -0
- package/dist/v2/fold/policy.d.ts.map +1 -0
- package/dist/v2/harness-e4avh192.js +10 -0
- package/dist/v2/hidden-completion.d.ts.map +1 -1
- package/dist/v2/hooks/context.d.ts +2 -2
- package/dist/v2/hooks/context.d.ts.map +1 -1
- package/dist/v2/hooks/dream-manual.d.ts +1 -1
- package/dist/v2/hooks/dream-manual.d.ts.map +1 -1
- package/dist/v2/hooks/dream-timer.d.ts.map +1 -1
- package/dist/v2/hooks/dream-trigger.d.ts +1 -2
- package/dist/v2/hooks/dream-trigger.d.ts.map +1 -1
- package/dist/v2/hooks/payload.d.ts.map +1 -1
- package/dist/v2/hooks/tools.d.ts.map +1 -1
- package/dist/v2/hooks/types.d.ts +6 -0
- package/dist/v2/hooks/types.d.ts.map +1 -1
- package/dist/v2/logger-mxd0ym09.js +19 -0
- package/dist/v2/message-index-bounded-dy3s2k1e.js +123 -0
- package/dist/{message-time-backfill-68gj6t3e.js → v2/message-time-backfill-gtx1pwk1.js} +7 -7
- package/dist/v2/migration-worker.js +24 -15
- package/dist/v2/{model-resolution-xhfmyyvy.js → model-resolution-nx97z3x7.js} +1 -1
- package/dist/v2/project-embedding-registry-1tgba5dv.js +86 -0
- package/dist/v2/raw-ordinal-warmup-worker.js +60 -0
- package/dist/v2/{read-session-chunk-8vjp6581.js → read-session-chunk-rh4khdkv.js} +14 -8
- package/dist/v2/read-session-raw-bsbmf0vj.js +57 -0
- package/dist/v2/recomp-orchestrator-az61rfcg.js +57 -0
- package/dist/{safe-notification-target-43r8jsxw.js → v2/safe-notification-target-73ax1p5v.js} +3 -3
- package/dist/v2/search-nzkwrc6n.js +45 -0
- package/dist/{send-session-notification-dyw42dtr.js → v2/send-session-notification-6km439kn.js} +9 -7
- package/dist/{index-5eqk6z3s.js → v2/server-1r6pmz4f.js} +301 -27
- package/dist/v2/server-2bvekkfy.js +11 -0
- package/dist/v2/server-2fbhp79z.js +3205 -0
- package/dist/v2/{server-ajzeteqw.js → server-2ra6pwf3.js} +1 -18
- package/dist/v2/server-4dhv7mqz.js +131 -0
- package/dist/v2/{server-xa4xe3rv.js → server-4k748ke6.js} +11 -1053
- package/dist/v2/{server-vs5gpnd4.js → server-52awr2mw.js} +81 -145
- package/dist/v2/{server-1qbnzc80.js → server-5m3dt5r1.js} +136 -148
- package/dist/v2/server-686jg6eb.js +3508 -0
- package/dist/v2/server-7jzrrxap.js +931 -0
- package/dist/v2/server-7wwc2nga.js +3023 -0
- package/dist/v2/server-8ajstryz.js +18 -0
- package/dist/v2/{server-sfpzqvdz.js → server-8cd2j99z.js} +2 -3
- package/dist/v2/server-8pyt7hev.js +3408 -0
- package/dist/v2/server-8tqanswc.js +984 -0
- package/dist/v2/{server-14vq4kpm.js → server-aan4q8mx.js} +3 -3
- package/dist/v2/server-d9vfe7xq.js +16 -0
- package/dist/v2/server-e0vdaae2.js +3591 -0
- package/dist/v2/server-ezhze64f.js +1048 -0
- package/dist/v2/server-fb3zxn06.js +13 -0
- package/dist/v2/server-fr0wbhdn.js +43 -0
- package/dist/v2/server-g5r0n3b6.js +1137 -0
- package/dist/v2/server-gjcr4w98.js +154 -0
- package/dist/v2/server-k228xhjg.js +171 -0
- package/dist/v2/{server-jc7y02q0.js → server-k8x2rbxc.js} +5119 -10948
- package/dist/{index-fg34g6a5.js → v2/server-kb9nn0a0.js} +309 -52
- package/dist/v2/{server-g0fjrkta.js → server-mfkzp7qj.js} +28 -28
- package/dist/v2/server-pavzsdc6.js +17 -0
- package/dist/v2/server-psqc7e9n.js +2106 -0
- package/dist/v2/server-qt1qqasc.js +22 -0
- package/dist/v2/server-qye6ykf7.js +120 -0
- package/dist/v2/{server-qdjnb2z1.js → server-r5p3b1wy.js} +11 -9
- package/dist/v2/server-sggvvzhb.js +759 -0
- package/dist/v2/{server-y8maxeft.js → server-v06jcv0w.js} +16 -13
- package/dist/v2/server-vqpf3167.js +1048 -0
- package/dist/v2/{server-c5gz8wph.js → server-x2g40a71.js} +2048 -2059
- package/dist/v2/{server-wvjvfhck.js → server-xdfwt6kh.js} +20 -16
- package/dist/v2/{server-mcfawjgc.js → server-xxm59eww.js} +25 -44
- package/dist/v2/server-yczc713m.js +564 -0
- package/dist/v2/{server-3n4q9xme.js → server-z2037m6z.js} +4 -4
- package/dist/v2/{server-e29d7p7c.js → server-zm570tr2.js} +166 -160
- package/dist/v2/{server-6r257mf5.js → server-zxhgvsf9.js} +3 -3
- package/dist/v2/server.js +16 -14
- package/dist/v2/sqlite-context-pragmas-mv9jqwjh.js +6 -0
- package/dist/v2/sqlite-mhjkerzq.js +46 -0
- package/dist/v2/storage-memory-07fx57k6.js +71 -0
- package/dist/v2/storage-tags-w4tv77sn.js +130 -0
- package/dist/v2/store-reader.d.ts +16 -0
- package/dist/v2/store-reader.d.ts.map +1 -1
- package/dist/v2/{task-config-tbndqyny.js → task-config-x0hq3qfd.js} +2 -2
- package/dist/v2/transformers-node-wasm.js +18415 -18415
- package/dist/v2/tui/index.d.ts.map +1 -1
- package/dist/v2/tui/types.d.ts +1 -10
- package/dist/v2/tui/types.d.ts.map +1 -1
- package/dist/v2/{wrapup-orchestrator-0snhvwcv.js → wrapup-orchestrator-mz7efs7w.js} +51 -27
- package/dist/wrapup-orchestrator-amzta1t4.js +44 -0
- package/package.json +4 -3
- package/src/agents/magic-context-prompt.ts +27 -2
- package/src/agents/smart-notes-enabled.test.ts +294 -0
- package/src/agents/smart-notes-on.golden.json +70 -0
- package/src/config/index.test.ts +67 -1
- package/src/config/index.ts +8 -0
- package/src/config/latch-permanence-guard.test.ts +4 -0
- package/src/config/live-run-config.test.ts +37 -1
- package/src/config/live-run-config.ts +28 -0
- package/src/config/migrate-config-location.ts +10 -3
- package/src/config/mural-model-deprecation.ts +24 -0
- package/src/config/project-security.hostile-matrix.test.ts +4 -44
- package/src/config/project-security.test.ts +9 -14
- package/src/config/project-security.ts +13 -55
- package/src/config/schema/magic-context.test.ts +6 -5
- package/src/config/schema/magic-context.ts +34 -16
- package/src/features/magic-context/dreamer/task-registry.ts +6 -0
- package/src/shared/auto-search-lifecycle.ts +27 -0
- package/src/shared/conflict-detector.test.ts +56 -0
- package/src/shared/conflict-detector.ts +16 -5
- package/src/shared/historian-tool-defaults.json +6 -6
- package/src/shared/historian-tool-expansions.ts +65 -8
- package/src/shared/historian-tool-preview-defaults.json +13 -0
- package/src/shared/historian-tool-template.ts +48 -14
- package/src/shared/host-stall-profiler-bun.ts +358 -0
- package/src/shared/host-stall-profiler-common.ts +159 -0
- package/src/shared/host-stall-profiler-node.test.ts +234 -0
- package/src/shared/host-stall-profiler-node.ts +274 -0
- package/src/shared/host-stall-profiler-report.ts +248 -0
- package/src/shared/host-stall-profiler.ts +57 -0
- package/src/shared/model-resolution.test.ts +54 -17
- package/src/shared/model-resolution.ts +6 -17
- package/src/shared/sqlite-640-review-r2.test.ts +136 -0
- package/src/shared/sqlite-640-review.test.ts +183 -0
- package/src/shared/sqlite-acquisition-routing.test.ts +18 -0
- package/src/shared/sqlite.test.ts +52 -0
- package/src/shared/sqlite.ts +423 -37
- package/src/shared/stable-json-linear.test.ts +109 -0
- package/src/shared/stable-json.ts +37 -7
- package/src/shared/status-view.test.ts +6 -1
- package/src/shared/status-view.ts +15 -1
- package/src/shared/tag-transcript.ts +6 -0
- package/src/shared/user-facing-codes.ts +2 -6
- package/src/shared/window-geometry.ts +10 -0
- package/src/shared/write-transaction-attribution.test.ts +2 -2
- package/src/v2/tui/host-contract.test.ts +41 -67
- package/src/v2/tui/index.ts +6 -214
- package/src/v2/tui/types.ts +5 -17
- package/dist/features/magic-context/checkout-claim-fake-subc.test-support.d.ts +0 -45
- package/dist/features/magic-context/checkout-claim-fake-subc.test-support.d.ts.map +0 -1
- package/dist/features/magic-context/checkout-claim.d.ts +0 -155
- package/dist/features/magic-context/checkout-claim.d.ts.map +0 -1
- package/dist/index-3h0ybwah.js +0 -6540
- package/dist/index-r5hmbx3t.js +0 -7773
- package/dist/recomp-orchestrator-y15j0rb9.js +0 -39
- package/dist/v2/recomp-orchestrator-8ejxvrzz.js +0 -39
- package/dist/v2/server-frbm99q5.js +0 -7698
- package/dist/v2/server-qp5hx1ad.js +0 -6366
- package/dist/wrapup-orchestrator-39amhv9s.js +0 -26
- /package/dist/{index-en1k8h8m.js → index-2pp04pmg.js} +0 -0
- /package/dist/v2/{index-en1k8h8m.js → index-2pp04pmg.js} +0 -0
|
@@ -0,0 +1,3408 @@
|
|
|
1
|
+
import {
|
|
2
|
+
harnessOwnsOpenCodeStore2
|
|
3
|
+
} from "./server-pavzsdc6.js";
|
|
4
|
+
import {
|
|
5
|
+
getMagicContextStorageDir,
|
|
6
|
+
getLegacyOpenCodeMagicContextStorageDir,
|
|
7
|
+
sanitizeDiagnosticText,
|
|
8
|
+
importPluginModule,
|
|
9
|
+
writeForwardedLogLine2,
|
|
10
|
+
log2
|
|
11
|
+
} from "./server-5m3dt5r1.js";
|
|
12
|
+
import {
|
|
13
|
+
registerSlowWriteReporter2,
|
|
14
|
+
registerSqliteDiagnosticSink2,
|
|
15
|
+
detectSqliteRuntime2,
|
|
16
|
+
Database2,
|
|
17
|
+
withSqliteBackgroundWriter2,
|
|
18
|
+
withoutSqliteTransformPass2,
|
|
19
|
+
isTransientSqliteError2,
|
|
20
|
+
withPrivilegedWriter2
|
|
21
|
+
} from "./server-kb9nn0a0.js";
|
|
22
|
+
import {
|
|
23
|
+
configureContextDatabasePragmas2
|
|
24
|
+
} from "./server-fb3zxn06.js";
|
|
25
|
+
import {
|
|
26
|
+
startMessageFtsRowidMapBackfill
|
|
27
|
+
} from "./server-k228xhjg.js";
|
|
28
|
+
import {
|
|
29
|
+
LKG_SLOT_CHUNKS_DDL,
|
|
30
|
+
SESSION_REPLAY_DECISIONS_DDL,
|
|
31
|
+
LKG_SLOTS_DDL,
|
|
32
|
+
installCompartmentHistoryVersions,
|
|
33
|
+
FORK_MIGRATION_VERSION_FLOOR,
|
|
34
|
+
hasPendingMigrations,
|
|
35
|
+
getMainThreadMigrationBodyCount,
|
|
36
|
+
runMigrations
|
|
37
|
+
} from "./server-e0vdaae2.js";
|
|
38
|
+
import {
|
|
39
|
+
ensureColumn,
|
|
40
|
+
installV95PerfSchema,
|
|
41
|
+
installTagIdentityRevisionTrigger
|
|
42
|
+
} from "./server-52awr2mw.js";
|
|
43
|
+
import {
|
|
44
|
+
resolveOpenCodeDbPath,
|
|
45
|
+
hasV1MessageTables
|
|
46
|
+
} from "./server-2ra6pwf3.js";
|
|
47
|
+
import {
|
|
48
|
+
canonicalProjectDirectory,
|
|
49
|
+
bootQuietRemainingMs,
|
|
50
|
+
scheduleAfterBootQuiet
|
|
51
|
+
} from "./server-yczc713m.js";
|
|
52
|
+
import {
|
|
53
|
+
setDatabase,
|
|
54
|
+
loadToolDefinitionMeasurements
|
|
55
|
+
} from "./server-qye6ykf7.js";
|
|
56
|
+
import {
|
|
57
|
+
getErrorMessage
|
|
58
|
+
} from "./server-y8v6yerm.js";
|
|
59
|
+
import {
|
|
60
|
+
shouldEnforcePrivateStoragePermissions,
|
|
61
|
+
ensureStorageDirectorySync,
|
|
62
|
+
writeStorageFileSync,
|
|
63
|
+
copyStorageFileSync,
|
|
64
|
+
copyStorageTreeSync,
|
|
65
|
+
tightenStorageTreeSync
|
|
66
|
+
} from "./server-1bc61ans.js";
|
|
67
|
+
import {
|
|
68
|
+
closeQuietly
|
|
69
|
+
} from "./server-8ajstryz.js";
|
|
70
|
+
import {
|
|
71
|
+
logSlowWriteTransaction
|
|
72
|
+
} from "./server-aan4q8mx.js";
|
|
73
|
+
|
|
74
|
+
// src/features/magic-context/storage-db.ts
|
|
75
|
+
import {
|
|
76
|
+
existsSync as existsSync2,
|
|
77
|
+
readdirSync,
|
|
78
|
+
readFileSync as readFileSync2,
|
|
79
|
+
rmdirSync,
|
|
80
|
+
statSync,
|
|
81
|
+
unlinkSync
|
|
82
|
+
} from "node:fs";
|
|
83
|
+
import { basename, dirname, join as join2, resolve } from "node:path";
|
|
84
|
+
|
|
85
|
+
// src/shared/rpc-utils.ts
|
|
86
|
+
import { execFile, execFileSync } from "node:child_process";
|
|
87
|
+
import { createHash } from "node:crypto";
|
|
88
|
+
import { readFileSync } from "node:fs";
|
|
89
|
+
import { join } from "node:path";
|
|
90
|
+
|
|
91
|
+
// src/shared/pi-executable.ts
|
|
92
|
+
var PI_IMAGE_NAMES = new Set(["pi", "pi.cmd", "omp", "oh-my-pi"]);
|
|
93
|
+
function piHarnessKindFromExecutable(value) {
|
|
94
|
+
const executable = (value ?? "").trim().replace(/^['"]|['"]$/g, "").replaceAll("\\", "/").split("/").at(-1)?.toLowerCase().replace(/\.(?:exe|cmd)$/, "");
|
|
95
|
+
if (!executable || !PI_IMAGE_NAMES.has(executable))
|
|
96
|
+
return;
|
|
97
|
+
return executable === "pi" ? "pi" : "omp";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/shared/rpc-utils.ts
|
|
101
|
+
var ownRpcServerInstanceIds = new Set;
|
|
102
|
+
function registerOwnRpcServerInstance(instanceId) {
|
|
103
|
+
ownRpcServerInstanceIds.add(instanceId);
|
|
104
|
+
return () => {
|
|
105
|
+
ownRpcServerInstanceIds.delete(instanceId);
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function isOwnRpcServerRecord(record) {
|
|
109
|
+
return record.pid === process.pid && record.instance_id !== undefined && ownRpcServerInstanceIds.has(record.instance_id);
|
|
110
|
+
}
|
|
111
|
+
function projectHash(directory) {
|
|
112
|
+
return createHash("sha256").update(canonicalProjectDirectory(directory)).digest("hex").slice(0, 16);
|
|
113
|
+
}
|
|
114
|
+
function rpcPortDir(storageDir, directory) {
|
|
115
|
+
return join(storageDir, "rpc", projectHash(directory));
|
|
116
|
+
}
|
|
117
|
+
function rpcPortFilePath(storageDir, directory, pid = process.pid, instanceId) {
|
|
118
|
+
const suffix = instanceId ? `-${instanceId}` : "";
|
|
119
|
+
return join(rpcPortDir(storageDir, directory), `port-${pid}${suffix}.json`);
|
|
120
|
+
}
|
|
121
|
+
function isPidAlive(pid) {
|
|
122
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
123
|
+
return "dead";
|
|
124
|
+
if (rpcIdentityPlatform === "win32")
|
|
125
|
+
return readWindowsProcess(pid).state;
|
|
126
|
+
try {
|
|
127
|
+
rpcIdentityProcessKill(pid, 0);
|
|
128
|
+
return "alive";
|
|
129
|
+
} catch (error) {
|
|
130
|
+
return error.code === "ESRCH" ? "dead" : "inconclusive";
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
var RPC_IDENTITY_SKEW_TOLERANCE_MS = 120000;
|
|
134
|
+
var LINUX_CLOCK_TICKS_PER_SECOND = 100;
|
|
135
|
+
var PS_PROBE_TIMEOUT_MS = 1000;
|
|
136
|
+
var WINDOWS_CIM_PROBE_TIMEOUT_MS = 5000;
|
|
137
|
+
var WINDOWS_PROCESS_SNAPSHOT_TTL_MS = 2000;
|
|
138
|
+
var MAX_ANCESTOR_WALK_DEPTH = 16;
|
|
139
|
+
var OPEN_CODE_COMMAND_MARKERS = ["opencode", "node", "bun", "electron"];
|
|
140
|
+
var WINDOWS_CIM_COMMAND = "Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,Name,CommandLine,@{Name='CreationDate';Expression={if ($_.CreationDate) { $_.CreationDate.ToUniversalTime().ToString('o') } else { $null }}} | ConvertTo-Json -Compress";
|
|
141
|
+
var PROCESS_LIST_MAX_BUFFER_BYTES = 8 * 1024 * 1024;
|
|
142
|
+
var PI_HARNESS_ARC_MARKERS = [
|
|
143
|
+
"pi-coding-agent",
|
|
144
|
+
"oh-my-pi",
|
|
145
|
+
"@oh-my-pi",
|
|
146
|
+
"cljs/dist",
|
|
147
|
+
"dist/bundle/cli"
|
|
148
|
+
];
|
|
149
|
+
var rpcIdentityReadFileSync = readFileSync;
|
|
150
|
+
var rpcIdentityExecFileSync = execFileSync;
|
|
151
|
+
var rpcIdentityProcessKill = process.kill;
|
|
152
|
+
var rpcProcessListExecFileSync = execFileSync;
|
|
153
|
+
var rpcIdentityPlatform = process.platform;
|
|
154
|
+
var rpcIdentityNowMs = () => Date.now();
|
|
155
|
+
function parseLinuxProcessStartTime(statContent, uptimeContent) {
|
|
156
|
+
const closingCommandName = statContent.lastIndexOf(")");
|
|
157
|
+
if (closingCommandName < 0)
|
|
158
|
+
return null;
|
|
159
|
+
const statFields = statContent.slice(closingCommandName + 1).trim().split(/\s+/);
|
|
160
|
+
const startTimeTicks = Number(statFields[19]);
|
|
161
|
+
const uptimeSeconds = Number(uptimeContent.trim().split(/\s+/)[0]);
|
|
162
|
+
if (!Number.isFinite(startTimeTicks) || startTimeTicks < 0 || !Number.isFinite(uptimeSeconds) || uptimeSeconds < 0) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
const processStartTime = rpcIdentityNowMs() - uptimeSeconds * 1000 + startTimeTicks / LINUX_CLOCK_TICKS_PER_SECOND * 1000;
|
|
166
|
+
return Number.isFinite(processStartTime) ? processStartTime : null;
|
|
167
|
+
}
|
|
168
|
+
function readLinuxProcessStartTime(pid) {
|
|
169
|
+
try {
|
|
170
|
+
const statContent = String(rpcIdentityReadFileSync(`/proc/${pid}/stat`, "utf8"));
|
|
171
|
+
const uptimeContent = String(rpcIdentityReadFileSync("/proc/uptime", "utf8"));
|
|
172
|
+
return parseLinuxProcessStartTime(statContent, uptimeContent);
|
|
173
|
+
} catch {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function readPsProcessStartTime(pid) {
|
|
178
|
+
try {
|
|
179
|
+
const output = rpcIdentityExecFileSync("ps", ["-p", String(pid), "-o", "lstart="], {
|
|
180
|
+
encoding: "utf8",
|
|
181
|
+
timeout: PS_PROBE_TIMEOUT_MS,
|
|
182
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
183
|
+
windowsHide: true
|
|
184
|
+
});
|
|
185
|
+
const processStartTime = Date.parse(String(output).trim());
|
|
186
|
+
return Number.isFinite(processStartTime) ? processStartTime : null;
|
|
187
|
+
} catch {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function readProcessStartTime(pid) {
|
|
192
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
193
|
+
return null;
|
|
194
|
+
return rpcIdentityPlatform === "linux" ? readLinuxProcessStartTime(pid) : rpcIdentityPlatform === "win32" ? readWindowsProcessStartTime(pid) : readPsProcessStartTime(pid);
|
|
195
|
+
}
|
|
196
|
+
function readProcessProbeEvidence(pid) {
|
|
197
|
+
return {
|
|
198
|
+
startTime: readProcessStartTime(pid),
|
|
199
|
+
commandLine: readProcessCommand(pid)
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function readCachedWindowsImageName(pid) {
|
|
203
|
+
return rpcIdentityPlatform === "win32" ? windowsProcessFactsCache?.get(pid)?.imageName ?? null : null;
|
|
204
|
+
}
|
|
205
|
+
var windowsProcessFactsCache = null;
|
|
206
|
+
var windowsProcessSnapshotCache = null;
|
|
207
|
+
function rememberWindowsProcessFacts(facts) {
|
|
208
|
+
windowsProcessFactsCache = new Map(facts.map((fact) => [fact.pid, fact]));
|
|
209
|
+
}
|
|
210
|
+
function parseCsvLine(line) {
|
|
211
|
+
const fields = [];
|
|
212
|
+
let field = "";
|
|
213
|
+
let quoted = false;
|
|
214
|
+
for (let index = 0;index < line.length; index += 1) {
|
|
215
|
+
const character = line[index];
|
|
216
|
+
if (character === '"') {
|
|
217
|
+
if (quoted && line[index + 1] === '"') {
|
|
218
|
+
field += '"';
|
|
219
|
+
index += 1;
|
|
220
|
+
} else {
|
|
221
|
+
quoted = !quoted;
|
|
222
|
+
}
|
|
223
|
+
} else if (character === "," && !quoted) {
|
|
224
|
+
fields.push(field);
|
|
225
|
+
field = "";
|
|
226
|
+
} else {
|
|
227
|
+
field += character;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (quoted)
|
|
231
|
+
return null;
|
|
232
|
+
fields.push(field);
|
|
233
|
+
return fields;
|
|
234
|
+
}
|
|
235
|
+
function parseTasklistOutput(output) {
|
|
236
|
+
const entries = [];
|
|
237
|
+
for (const rawLine of output.split(/\r?\n/)) {
|
|
238
|
+
const line = rawLine.trim();
|
|
239
|
+
if (!line)
|
|
240
|
+
continue;
|
|
241
|
+
const fields = parseCsvLine(line);
|
|
242
|
+
if (!fields)
|
|
243
|
+
continue;
|
|
244
|
+
if (fields[1]?.trim().toLowerCase() === "pid") {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
const pid = Number(fields[1]);
|
|
248
|
+
if (!Number.isInteger(pid) || pid <= 0 || !fields[0])
|
|
249
|
+
continue;
|
|
250
|
+
entries.push({ pid, command: fields[0] });
|
|
251
|
+
}
|
|
252
|
+
return entries;
|
|
253
|
+
}
|
|
254
|
+
function readWindowsProcess(pid) {
|
|
255
|
+
try {
|
|
256
|
+
const output = rpcIdentityExecFileSync("tasklist", ["/FO", "CSV", "/NH", "/FI", `PID eq ${pid}`], {
|
|
257
|
+
encoding: "utf8",
|
|
258
|
+
timeout: PS_PROBE_TIMEOUT_MS,
|
|
259
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
260
|
+
windowsHide: true
|
|
261
|
+
});
|
|
262
|
+
const entries = parseTasklistOutput(String(output));
|
|
263
|
+
if (entries === null)
|
|
264
|
+
return { state: "inconclusive" };
|
|
265
|
+
const process2 = entries.find((entry) => entry.pid === pid);
|
|
266
|
+
return process2 ? { state: "alive", command: process2.command } : { state: "dead" };
|
|
267
|
+
} catch {
|
|
268
|
+
return { state: "inconclusive" };
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function readWindowsProcessStartTime(pid) {
|
|
272
|
+
const cached = windowsProcessFactsCache?.get(pid);
|
|
273
|
+
if (cached)
|
|
274
|
+
return cached.startTime;
|
|
275
|
+
const snapshot = tryReadWindowsCimSnapshot(rpcIdentityExecFileSync);
|
|
276
|
+
if (!snapshot)
|
|
277
|
+
return null;
|
|
278
|
+
rememberWindowsProcessFacts(snapshot.facts);
|
|
279
|
+
return windowsProcessFactsCache?.get(pid)?.startTime ?? null;
|
|
280
|
+
}
|
|
281
|
+
function readLinuxProcessCommand(pid) {
|
|
282
|
+
try {
|
|
283
|
+
return String(rpcIdentityReadFileSync(`/proc/${pid}/cmdline`, "utf8"));
|
|
284
|
+
} catch {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
function readPsProcessCommand(pid) {
|
|
289
|
+
try {
|
|
290
|
+
const output = rpcIdentityExecFileSync("ps", ["-p", String(pid), "-o", "command="], {
|
|
291
|
+
encoding: "utf8",
|
|
292
|
+
timeout: PS_PROBE_TIMEOUT_MS,
|
|
293
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
294
|
+
windowsHide: true
|
|
295
|
+
});
|
|
296
|
+
return String(output);
|
|
297
|
+
} catch {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function readProcessCommand(pid) {
|
|
302
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
303
|
+
return null;
|
|
304
|
+
if (rpcIdentityPlatform === "linux")
|
|
305
|
+
return readLinuxProcessCommand(pid);
|
|
306
|
+
if (rpcIdentityPlatform === "win32") {
|
|
307
|
+
const cached = windowsProcessFactsCache?.get(pid);
|
|
308
|
+
if (cached?.commandLine)
|
|
309
|
+
return cached.commandLine;
|
|
310
|
+
return readWindowsProcess(pid).command ?? null;
|
|
311
|
+
}
|
|
312
|
+
return readPsProcessCommand(pid);
|
|
313
|
+
}
|
|
314
|
+
function executableName(token) {
|
|
315
|
+
return (token ?? "").replace(/^['"]|['"]$/g, "").split("/").at(-1) ?? "";
|
|
316
|
+
}
|
|
317
|
+
function commandTokens(command) {
|
|
318
|
+
return command.toLowerCase().replaceAll("\\", "/").replaceAll("\x00", " ").split(/\s+/).map((token) => token.replace(/^['"]|['"]$/g, "")).filter(Boolean);
|
|
319
|
+
}
|
|
320
|
+
function commandHasOpenCodeExecutable(tokens) {
|
|
321
|
+
return tokens.findIndex((token) => {
|
|
322
|
+
const executable = executableName(token).replace(/\.(?:exe|cmd)$/, "");
|
|
323
|
+
return executable === "opencode" || executable.endsWith("/opencode");
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
function commandHasPiExecutable(tokens) {
|
|
327
|
+
for (let index = 0;index < tokens.length; index += 1) {
|
|
328
|
+
const executable = executableName(tokens[index]).replace(/\.(?:exe|cmd)$/, "");
|
|
329
|
+
if (piHarnessKindFromExecutable(executable) !== undefined)
|
|
330
|
+
return true;
|
|
331
|
+
if (["node", "bun", "deno"].includes(executable)) {
|
|
332
|
+
const script = executableName(tokens[index + 1]).replace(/\.(?:exe|cmd)$/, "");
|
|
333
|
+
if (["pi", "pi.js", "pi.mjs", "pi.cjs"].includes(script) || tokens[index + 1]?.includes("pi-coding-agent")) {
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
function classifyProcessKind(command) {
|
|
341
|
+
if (!command)
|
|
342
|
+
return "process";
|
|
343
|
+
const tokens = commandTokens(command);
|
|
344
|
+
const openCodeIndex = commandHasOpenCodeExecutable(tokens);
|
|
345
|
+
if (openCodeIndex >= 0) {
|
|
346
|
+
const args = tokens.slice(openCodeIndex + 1);
|
|
347
|
+
if (args.some((token) => token === "serve" || token === "--serve" || token.startsWith("--serve="))) {
|
|
348
|
+
return "OpenCode server";
|
|
349
|
+
}
|
|
350
|
+
return "OpenCode instance (TUI/CLI)";
|
|
351
|
+
}
|
|
352
|
+
return commandHasPiExecutable(tokens) ? "Pi" : "process";
|
|
353
|
+
}
|
|
354
|
+
function commandLooksLikeOpenCode(command) {
|
|
355
|
+
const normalized = command.toLowerCase();
|
|
356
|
+
return OPEN_CODE_COMMAND_MARKERS.some((marker) => normalized.includes(marker));
|
|
357
|
+
}
|
|
358
|
+
var RPC_HOST_PROCESS_MARKERS = [
|
|
359
|
+
"opencode",
|
|
360
|
+
"openchamber",
|
|
361
|
+
"ck-mc",
|
|
362
|
+
"magic-context",
|
|
363
|
+
"cortexkit",
|
|
364
|
+
"electron",
|
|
365
|
+
"node",
|
|
366
|
+
"bun",
|
|
367
|
+
"deno",
|
|
368
|
+
"pi-coding-agent",
|
|
369
|
+
"oh-my-pi"
|
|
370
|
+
];
|
|
371
|
+
function processCannotBeRpcHost(evidence) {
|
|
372
|
+
const texts = [evidence.imageName, evidence.commandLine].filter((text) => typeof text === "string" && text.trim().length > 0);
|
|
373
|
+
if (texts.length === 0)
|
|
374
|
+
return false;
|
|
375
|
+
for (const text of texts) {
|
|
376
|
+
const normalized = text.toLowerCase();
|
|
377
|
+
if (RPC_HOST_PROCESS_MARKERS.some((marker) => normalized.includes(marker)))
|
|
378
|
+
return false;
|
|
379
|
+
const tokens = commandTokens(text);
|
|
380
|
+
if (commandHasPiExecutable(tokens))
|
|
381
|
+
return false;
|
|
382
|
+
if (tokens.some((token) => piHarnessKindFromExecutable(token) !== undefined))
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
function isPidIdentityPlausible(record, evidence) {
|
|
388
|
+
if (!Number.isInteger(record.pid) || record.pid <= 0)
|
|
389
|
+
return "implausible";
|
|
390
|
+
if (Number.isFinite(record.started_at) && record.started_at > 0) {
|
|
391
|
+
const processStartTime = evidence ? evidence.startTime : readProcessStartTime(record.pid);
|
|
392
|
+
if (processStartTime !== null) {
|
|
393
|
+
return processStartTime <= record.started_at + RPC_IDENTITY_SKEW_TOLERANCE_MS ? "plausible" : "implausible";
|
|
394
|
+
}
|
|
395
|
+
const identity = evidence ?? {
|
|
396
|
+
commandLine: readProcessCommand(record.pid),
|
|
397
|
+
imageName: readCachedWindowsImageName(record.pid)
|
|
398
|
+
};
|
|
399
|
+
return processCannotBeRpcHost(identity) ? "implausible" : "inconclusive";
|
|
400
|
+
}
|
|
401
|
+
const command = evidence ? evidence.commandLine : rpcIdentityPlatform === "linux" ? readLinuxProcessCommand(record.pid) : rpcIdentityPlatform === "win32" ? readWindowsProcess(record.pid).command ?? null : readPsProcessCommand(record.pid);
|
|
402
|
+
if (command === null)
|
|
403
|
+
return "inconclusive";
|
|
404
|
+
if (commandLooksLikeOpenCode(command))
|
|
405
|
+
return "plausible";
|
|
406
|
+
return processCannotBeRpcHost({ commandLine: command, imageName: evidence?.imageName }) ? "implausible" : "inconclusive";
|
|
407
|
+
}
|
|
408
|
+
function commandLooksLikePiImage(command) {
|
|
409
|
+
const tokens = commandTokens(command);
|
|
410
|
+
const first = executableName(tokens[0]).replace(/\.(?:exe|cmd)$/, "");
|
|
411
|
+
return PI_IMAGE_NAMES.has(first);
|
|
412
|
+
}
|
|
413
|
+
function commandHasPiHarnessArc(command) {
|
|
414
|
+
const normalized = command.trim().toLowerCase().replaceAll("\\", "/").replaceAll("\x00", " ");
|
|
415
|
+
if (!normalized)
|
|
416
|
+
return false;
|
|
417
|
+
const tokens = commandTokens(command);
|
|
418
|
+
if (tokens.length === 0)
|
|
419
|
+
return false;
|
|
420
|
+
const hasArc = PI_HARNESS_ARC_MARKERS.some((marker) => normalized.includes(marker));
|
|
421
|
+
const first = executableName(tokens[0]).replace(/\.(?:exe|cmd)$/, "");
|
|
422
|
+
if (hasArc && ["pi", "omp", "oh-my-pi", "node", "bun", "deno", "cmd"].includes(first)) {
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
425
|
+
if (hasArc && PI_HARNESS_ARC_MARKERS.some((marker) => tokens[0].includes(marker))) {
|
|
426
|
+
return true;
|
|
427
|
+
}
|
|
428
|
+
if (["node", "bun", "deno"].includes(first)) {
|
|
429
|
+
const script = executableName(tokens[1]).replace(/\.(?:exe|cmd)$/, "");
|
|
430
|
+
if (["pi", "pi.js", "pi.mjs", "pi.cjs"].includes(script))
|
|
431
|
+
return true;
|
|
432
|
+
if (hasArc)
|
|
433
|
+
return true;
|
|
434
|
+
}
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
function execProcessList(exec, file, args, timeout = PS_PROBE_TIMEOUT_MS) {
|
|
438
|
+
return String(exec(file, [...args], {
|
|
439
|
+
encoding: "utf8",
|
|
440
|
+
timeout,
|
|
441
|
+
maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES,
|
|
442
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
443
|
+
windowsHide: true
|
|
444
|
+
}));
|
|
445
|
+
}
|
|
446
|
+
function parseWindowsCreationDate(value) {
|
|
447
|
+
if (value !== null && typeof value === "object" && "value" in value) {
|
|
448
|
+
return parseWindowsCreationDate(value.value);
|
|
449
|
+
}
|
|
450
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
451
|
+
return value > 1000000000000 ? value : value * 1000;
|
|
452
|
+
}
|
|
453
|
+
if (typeof value !== "string")
|
|
454
|
+
return null;
|
|
455
|
+
const trimmed = value.trim();
|
|
456
|
+
if (!trimmed)
|
|
457
|
+
return null;
|
|
458
|
+
const wmi = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\.(\d{6})([+-])(\d{3})$/.exec(trimmed);
|
|
459
|
+
if (wmi) {
|
|
460
|
+
const utcMs = Date.UTC(Number(wmi[1]), Number(wmi[2]) - 1, Number(wmi[3]), Number(wmi[4]), Number(wmi[5]), Number(wmi[6]), Number(wmi[7]) / 1000);
|
|
461
|
+
if (!Number.isFinite(utcMs))
|
|
462
|
+
return null;
|
|
463
|
+
const offsetMinutes = Number(wmi[9]);
|
|
464
|
+
const sign = wmi[8] === "+" ? 1 : -1;
|
|
465
|
+
return utcMs - sign * offsetMinutes * 60000;
|
|
466
|
+
}
|
|
467
|
+
const dotNet = /^\/Date\((-?\d+)\)\/$/.exec(trimmed);
|
|
468
|
+
if (dotNet) {
|
|
469
|
+
const milliseconds = Number(dotNet[1]);
|
|
470
|
+
return Number.isFinite(milliseconds) ? milliseconds : null;
|
|
471
|
+
}
|
|
472
|
+
const parsed = Date.parse(trimmed);
|
|
473
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
474
|
+
}
|
|
475
|
+
function parseWindowsCimOutput(output) {
|
|
476
|
+
const trimmed = output.trim();
|
|
477
|
+
if (!trimmed)
|
|
478
|
+
return null;
|
|
479
|
+
const bracket = trimmed.indexOf("[");
|
|
480
|
+
const brace = trimmed.indexOf("{");
|
|
481
|
+
const start = Math.min(bracket === -1 ? Number.POSITIVE_INFINITY : bracket, brace === -1 ? Number.POSITIVE_INFINITY : brace);
|
|
482
|
+
if (!Number.isFinite(start))
|
|
483
|
+
return null;
|
|
484
|
+
let parsed;
|
|
485
|
+
try {
|
|
486
|
+
parsed = JSON.parse(trimmed.slice(start));
|
|
487
|
+
} catch {
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
if (parsed == null)
|
|
491
|
+
return null;
|
|
492
|
+
const rows = Array.isArray(parsed) ? parsed : [parsed];
|
|
493
|
+
const facts = [];
|
|
494
|
+
for (const row of rows) {
|
|
495
|
+
if (!row || typeof row !== "object")
|
|
496
|
+
continue;
|
|
497
|
+
const record = row;
|
|
498
|
+
const pid = Number(record.ProcessId);
|
|
499
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
500
|
+
continue;
|
|
501
|
+
const parentRaw = record.ParentProcessId;
|
|
502
|
+
const parentPid = parentRaw == null || parentRaw === "" ? Number.NaN : Number(parentRaw);
|
|
503
|
+
const commandLine = typeof record.CommandLine === "string" ? record.CommandLine : null;
|
|
504
|
+
facts.push({
|
|
505
|
+
pid,
|
|
506
|
+
parentPid: Number.isInteger(parentPid) && parentPid > 0 ? parentPid : null,
|
|
507
|
+
commandLine,
|
|
508
|
+
imageName: typeof record.Name === "string" ? record.Name : commandLine ? executableName(commandTokens(commandLine)[0]) : null,
|
|
509
|
+
startTime: parseWindowsCreationDate(record.CreationDate)
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
return facts.length > 0 ? facts : null;
|
|
513
|
+
}
|
|
514
|
+
function snapshotFromFacts(facts, source) {
|
|
515
|
+
const parentByPid = new Map;
|
|
516
|
+
for (const fact of facts) {
|
|
517
|
+
if (fact.parentPid != null)
|
|
518
|
+
parentByPid.set(fact.pid, fact.parentPid);
|
|
519
|
+
}
|
|
520
|
+
return { facts, parentByPid, source };
|
|
521
|
+
}
|
|
522
|
+
function tryReadWindowsCimSnapshot(exec) {
|
|
523
|
+
try {
|
|
524
|
+
const output = execProcessList(exec, "powershell", ["-NoProfile", "-Command", WINDOWS_CIM_COMMAND], WINDOWS_CIM_PROBE_TIMEOUT_MS);
|
|
525
|
+
const facts = parseWindowsCimOutput(output);
|
|
526
|
+
return facts ? snapshotFromFacts(facts, "cim") : null;
|
|
527
|
+
} catch {
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
function tryReadWindowsTasklistSnapshot() {
|
|
532
|
+
try {
|
|
533
|
+
const output = execProcessList(rpcProcessListExecFileSync, "tasklist", [
|
|
534
|
+
"/FO",
|
|
535
|
+
"CSV",
|
|
536
|
+
"/NH"
|
|
537
|
+
]);
|
|
538
|
+
const entries = parseTasklistOutput(output);
|
|
539
|
+
if (entries === null)
|
|
540
|
+
return null;
|
|
541
|
+
const facts = entries.map((entry) => ({
|
|
542
|
+
pid: entry.pid,
|
|
543
|
+
parentPid: null,
|
|
544
|
+
commandLine: null,
|
|
545
|
+
imageName: entry.command,
|
|
546
|
+
startTime: null
|
|
547
|
+
}));
|
|
548
|
+
return snapshotFromFacts(facts, "tasklist");
|
|
549
|
+
} catch {
|
|
550
|
+
return null;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
function readPosixProcessSnapshot() {
|
|
554
|
+
const output = execProcessList(rpcProcessListExecFileSync, "ps", ["-axo", "pid=,command="]);
|
|
555
|
+
const facts = [];
|
|
556
|
+
for (const line of output.split(/\r?\n/)) {
|
|
557
|
+
const match = /^\s*(\d+)\s+(.+)$/.exec(line);
|
|
558
|
+
if (!match)
|
|
559
|
+
continue;
|
|
560
|
+
const pid = Number(match[1]);
|
|
561
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
562
|
+
continue;
|
|
563
|
+
facts.push({
|
|
564
|
+
pid,
|
|
565
|
+
parentPid: null,
|
|
566
|
+
commandLine: match[2],
|
|
567
|
+
imageName: executableName(commandTokens(match[2])[0]),
|
|
568
|
+
startTime: null
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
return snapshotFromFacts(facts, "ps");
|
|
572
|
+
}
|
|
573
|
+
function readPosixParentPid(pid) {
|
|
574
|
+
try {
|
|
575
|
+
const output = execProcessList(rpcProcessListExecFileSync, "ps", [
|
|
576
|
+
"-o",
|
|
577
|
+
"ppid=",
|
|
578
|
+
"-p",
|
|
579
|
+
String(pid)
|
|
580
|
+
]);
|
|
581
|
+
const match = /^\s*(\d+)\s*$/.exec(output);
|
|
582
|
+
if (!match)
|
|
583
|
+
return null;
|
|
584
|
+
const ppid = Number(match[1]);
|
|
585
|
+
return Number.isInteger(ppid) && ppid > 0 ? ppid : null;
|
|
586
|
+
} catch {
|
|
587
|
+
return null;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
function collectAncestorPids(selfPid, parentByPid, probeParents = true) {
|
|
591
|
+
const ancestors = new Set;
|
|
592
|
+
let current = selfPid;
|
|
593
|
+
for (let depth = 0;depth < MAX_ANCESTOR_WALK_DEPTH; depth += 1) {
|
|
594
|
+
let ppid = null;
|
|
595
|
+
if (parentByPid.has(current)) {
|
|
596
|
+
ppid = parentByPid.get(current) ?? null;
|
|
597
|
+
} else if (rpcIdentityPlatform !== "win32" && probeParents) {
|
|
598
|
+
ppid = readPosixParentPid(current);
|
|
599
|
+
if (ppid == null && current === process.pid && process.ppid > 0) {
|
|
600
|
+
ppid = process.ppid;
|
|
601
|
+
}
|
|
602
|
+
} else if (current === process.pid && process.ppid > 0) {
|
|
603
|
+
ppid = process.ppid;
|
|
604
|
+
} else {
|
|
605
|
+
break;
|
|
606
|
+
}
|
|
607
|
+
if (ppid == null || ppid <= 0 || ppid === current || ancestors.has(ppid))
|
|
608
|
+
break;
|
|
609
|
+
ancestors.add(ppid);
|
|
610
|
+
current = ppid;
|
|
611
|
+
}
|
|
612
|
+
return ancestors;
|
|
613
|
+
}
|
|
614
|
+
function classifyLivePiSnapshot(snapshot, probeParents = true) {
|
|
615
|
+
const ancestors = collectAncestorPids(process.pid, snapshot.parentByPid, probeParents);
|
|
616
|
+
const processIds = new Set;
|
|
617
|
+
const inconclusivePids = new Set;
|
|
618
|
+
const skippedAncestorPids = [];
|
|
619
|
+
for (const fact of snapshot.facts) {
|
|
620
|
+
if (fact.pid === process.pid)
|
|
621
|
+
continue;
|
|
622
|
+
const command = fact.commandLine ?? fact.imageName ?? "";
|
|
623
|
+
const looksLikeHarness = commandHasPiHarnessArc(command) || commandLooksLikePiImage(command);
|
|
624
|
+
if (!looksLikeHarness)
|
|
625
|
+
continue;
|
|
626
|
+
if (ancestors.has(fact.pid)) {
|
|
627
|
+
skippedAncestorPids.push(fact.pid);
|
|
628
|
+
log2(`[magic-context] Pi process scan: skipping ancestor PID ${fact.pid} (session launcher shim)`);
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
if (commandHasPiHarnessArc(command)) {
|
|
632
|
+
processIds.add(fact.pid);
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
inconclusivePids.add(fact.pid);
|
|
636
|
+
log2(`[magic-context] Pi process scan: PID ${fact.pid} command line is ambiguous (image-name or missing Pi/OMP arc); treating as inconclusive`);
|
|
637
|
+
}
|
|
638
|
+
skippedAncestorPids.sort((left, right) => left - right);
|
|
639
|
+
const verified = [...processIds].sort((left, right) => left - right);
|
|
640
|
+
const inconclusive = [...inconclusivePids].sort((left, right) => left - right);
|
|
641
|
+
if (verified.length === 0 && inconclusive.length > 0) {
|
|
642
|
+
return {
|
|
643
|
+
state: "inconclusive",
|
|
644
|
+
processIds: [],
|
|
645
|
+
inconclusivePids: inconclusive,
|
|
646
|
+
...skippedAncestorPids.length > 0 ? { skippedAncestorPids } : {}
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
return {
|
|
650
|
+
state: "known",
|
|
651
|
+
processIds: verified,
|
|
652
|
+
...inconclusive.length > 0 ? { inconclusivePids: inconclusive } : {},
|
|
653
|
+
...skippedAncestorPids.length > 0 ? { skippedAncestorPids } : {}
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
function inspectLivePiProcesses() {
|
|
657
|
+
if (false) {}
|
|
658
|
+
try {
|
|
659
|
+
if (rpcIdentityPlatform === "win32") {
|
|
660
|
+
const now = rpcIdentityNowMs();
|
|
661
|
+
const cached = windowsProcessSnapshotCache;
|
|
662
|
+
const snapshot = cached && now >= cached.at && now - cached.at < WINDOWS_PROCESS_SNAPSHOT_TTL_MS ? cached.snapshot : tryReadWindowsCimSnapshot(rpcProcessListExecFileSync) ?? tryReadWindowsTasklistSnapshot();
|
|
663
|
+
if (!snapshot) {
|
|
664
|
+
return {
|
|
665
|
+
state: "unreadable",
|
|
666
|
+
processIds: [],
|
|
667
|
+
error: "process list unavailable"
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
if (snapshot !== cached?.snapshot)
|
|
671
|
+
windowsProcessSnapshotCache = { snapshot, at: now };
|
|
672
|
+
rememberWindowsProcessFacts(snapshot.facts);
|
|
673
|
+
return classifyLivePiSnapshot(snapshot);
|
|
674
|
+
}
|
|
675
|
+
return classifyLivePiSnapshot(readPosixProcessSnapshot());
|
|
676
|
+
} catch (error) {
|
|
677
|
+
return {
|
|
678
|
+
state: "unreadable",
|
|
679
|
+
processIds: [],
|
|
680
|
+
error: error instanceof Error ? error.message : String(error)
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
function parseRpcPortFile(content, fallbackPid = 0) {
|
|
685
|
+
const trimmed = content.trim();
|
|
686
|
+
if (!trimmed)
|
|
687
|
+
return null;
|
|
688
|
+
if (trimmed.startsWith("{")) {
|
|
689
|
+
try {
|
|
690
|
+
const parsed = JSON.parse(trimmed);
|
|
691
|
+
const port = Number(parsed.port);
|
|
692
|
+
const pid = Number(parsed.pid);
|
|
693
|
+
const startedAt = Number(parsed.started_at);
|
|
694
|
+
if (!isValidPort(port) || !Number.isInteger(pid) || pid <= 0)
|
|
695
|
+
return null;
|
|
696
|
+
return {
|
|
697
|
+
port,
|
|
698
|
+
pid,
|
|
699
|
+
started_at: Number.isFinite(startedAt) ? startedAt : 0,
|
|
700
|
+
kind: typeof parsed.kind === "string" ? parsed.kind : undefined,
|
|
701
|
+
harness: typeof parsed.harness === "string" ? parsed.harness : undefined,
|
|
702
|
+
token: typeof parsed.token === "string" ? parsed.token : undefined,
|
|
703
|
+
instance_id: typeof parsed.instance_id === "string" ? parsed.instance_id : undefined
|
|
704
|
+
};
|
|
705
|
+
} catch {
|
|
706
|
+
return null;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
const port = Number.parseInt(trimmed, 10);
|
|
710
|
+
if (!isValidPort(port))
|
|
711
|
+
return null;
|
|
712
|
+
return { port, pid: fallbackPid, started_at: 0 };
|
|
713
|
+
}
|
|
714
|
+
function isValidPort(port) {
|
|
715
|
+
return Number.isInteger(port) && port > 0 && port <= 65535;
|
|
716
|
+
}
|
|
717
|
+
function snapshotEvidence(fact) {
|
|
718
|
+
return {
|
|
719
|
+
startTime: fact?.startTime ?? null,
|
|
720
|
+
commandLine: fact?.commandLine ?? fact?.imageName ?? null,
|
|
721
|
+
...fact?.imageName ? { imageName: fact.imageName } : {}
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
function execProcessListAsync(file, args, timeout) {
|
|
725
|
+
return new Promise((resolve, reject) => {
|
|
726
|
+
execFile(file, args, { encoding: "utf8", timeout, windowsHide: true, maxBuffer: 8 * 1024 * 1024 }, (error, stdout) => {
|
|
727
|
+
if (error)
|
|
728
|
+
reject(error);
|
|
729
|
+
else
|
|
730
|
+
resolve(stdout);
|
|
731
|
+
});
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
var asyncProcessExec = execProcessListAsync;
|
|
735
|
+
var asyncInspection;
|
|
736
|
+
var asyncInspectionExpires = 0;
|
|
737
|
+
var lastAsyncSnapshot = null;
|
|
738
|
+
function inspectProcessesAsync(forceFresh = false) {
|
|
739
|
+
if (!forceFresh && asyncInspection && rpcIdentityNowMs() < asyncInspectionExpires)
|
|
740
|
+
return asyncInspection;
|
|
741
|
+
asyncInspectionExpires = Number.POSITIVE_INFINITY;
|
|
742
|
+
asyncInspection = (async () => {
|
|
743
|
+
let snapshot = null;
|
|
744
|
+
try {
|
|
745
|
+
if (rpcIdentityPlatform === "win32") {
|
|
746
|
+
try {
|
|
747
|
+
const output = await asyncProcessExec("powershell", ["-NoProfile", "-Command", WINDOWS_CIM_COMMAND], WINDOWS_CIM_PROBE_TIMEOUT_MS);
|
|
748
|
+
const facts = parseWindowsCimOutput(output);
|
|
749
|
+
if (facts)
|
|
750
|
+
snapshot = snapshotFromFacts(facts, "cim");
|
|
751
|
+
} catch {}
|
|
752
|
+
if (!snapshot) {
|
|
753
|
+
const entries = parseTasklistOutput(await asyncProcessExec("tasklist", ["/FO", "CSV", "/NH"], PS_PROBE_TIMEOUT_MS));
|
|
754
|
+
if (entries)
|
|
755
|
+
snapshot = snapshotFromFacts(entries.map(({ pid, command }) => ({
|
|
756
|
+
pid,
|
|
757
|
+
parentPid: null,
|
|
758
|
+
commandLine: null,
|
|
759
|
+
imageName: command,
|
|
760
|
+
startTime: null
|
|
761
|
+
})), "tasklist");
|
|
762
|
+
}
|
|
763
|
+
} else {
|
|
764
|
+
const output = await asyncProcessExec("ps", ["-axo", "pid=,ppid=,lstart=,command="], PS_PROBE_TIMEOUT_MS);
|
|
765
|
+
const facts = [];
|
|
766
|
+
for (const line of output.split(/\r?\n/)) {
|
|
767
|
+
const match = /^\s*(\d+)\s+(\d+)\s+(\S+\s+\S+\s+\d+\s+\d+:\d+:\d+\s+\d+)\s+(.+)$/.exec(line);
|
|
768
|
+
if (!match)
|
|
769
|
+
continue;
|
|
770
|
+
const startTime = Date.parse(match[3]);
|
|
771
|
+
facts.push({
|
|
772
|
+
pid: Number(match[1]),
|
|
773
|
+
parentPid: Number(match[2]),
|
|
774
|
+
commandLine: match[4],
|
|
775
|
+
imageName: null,
|
|
776
|
+
startTime: Number.isFinite(startTime) ? startTime : null
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
snapshot = facts.length > 0 ? snapshotFromFacts(facts, "ps") : null;
|
|
780
|
+
}
|
|
781
|
+
} catch {}
|
|
782
|
+
const fresh = snapshot !== null;
|
|
783
|
+
if (snapshot)
|
|
784
|
+
lastAsyncSnapshot = snapshot;
|
|
785
|
+
else
|
|
786
|
+
snapshot = lastAsyncSnapshot;
|
|
787
|
+
const byPid = new Map(snapshot?.facts.map((fact) => [fact.pid, fact]));
|
|
788
|
+
return {
|
|
789
|
+
pi: snapshot ? classifyLivePiSnapshot(snapshot, false) : { state: "unreadable", processIds: [] },
|
|
790
|
+
...fresh && snapshot ? { processSnapshot: { source: snapshot.source, facts: snapshot.facts } } : {},
|
|
791
|
+
evidence: (pid) => snapshotEvidence(byPid.get(pid)),
|
|
792
|
+
liveness: (pid) => byPid.has(pid) ? "alive" : fresh ? "dead" : "inconclusive"
|
|
793
|
+
};
|
|
794
|
+
})().finally(() => {
|
|
795
|
+
asyncInspectionExpires = rpcIdentityNowMs() + WINDOWS_PROCESS_SNAPSHOT_TTL_MS;
|
|
796
|
+
});
|
|
797
|
+
return asyncInspection;
|
|
798
|
+
}
|
|
799
|
+
function inspectWindowsProcessesSync() {
|
|
800
|
+
const snapshot = tryReadWindowsCimSnapshot(rpcProcessListExecFileSync) ?? tryReadWindowsTasklistSnapshot();
|
|
801
|
+
const byPid = new Map(snapshot?.facts.map((fact) => [fact.pid, fact]));
|
|
802
|
+
return {
|
|
803
|
+
pi: snapshot ? classifyLivePiSnapshot(snapshot) : { state: "unreadable", processIds: [] },
|
|
804
|
+
...snapshot ? { processSnapshot: { source: snapshot.source, facts: snapshot.facts } } : {},
|
|
805
|
+
evidence: (pid) => snapshotEvidence(byPid.get(pid)),
|
|
806
|
+
liveness: (pid) => byPid.has(pid) ? "alive" : snapshot ? "dead" : "inconclusive"
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// src/features/magic-context/context-store-uuid.ts
|
|
811
|
+
import { randomUUID } from "node:crypto";
|
|
812
|
+
function getContextStoreUuid(db) {
|
|
813
|
+
const row = db.prepare("SELECT value FROM context_store_meta WHERE key = 'store_uuid'").get();
|
|
814
|
+
return typeof row?.value === "string" && row.value.length > 0 ? row.value : null;
|
|
815
|
+
}
|
|
816
|
+
function ensureContextStoreUuid(db) {
|
|
817
|
+
const existing = getContextStoreUuid(db);
|
|
818
|
+
if (existing)
|
|
819
|
+
return existing;
|
|
820
|
+
const minted = randomUUID();
|
|
821
|
+
withPrivilegedWriter2(db, () => {
|
|
822
|
+
db.transaction(() => {
|
|
823
|
+
db.prepare("INSERT INTO context_store_meta(key, value) VALUES ('store_uuid', ?) ON CONFLICT(key) DO NOTHING").run(minted);
|
|
824
|
+
}).immediate();
|
|
825
|
+
});
|
|
826
|
+
return getContextStoreUuid(db) ?? minted;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// src/features/magic-context/fail-closed-block.ts
|
|
830
|
+
var FAIL_CLOSED_DOCTOR_COMMAND = "npx @cortexkit/magic-context@latest doctor";
|
|
831
|
+
function attachFailClosedBlockingProcessEvidence(process2, evidence) {
|
|
832
|
+
Object.defineProperties(process2, {
|
|
833
|
+
startTime: { configurable: true, value: evidence.startTime },
|
|
834
|
+
commandLine: { configurable: true, value: evidence.commandLine }
|
|
835
|
+
});
|
|
836
|
+
return process2;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
class FailClosedBlockingError extends Error {
|
|
840
|
+
code = "FAIL_CLOSED_BLOCKING";
|
|
841
|
+
reason;
|
|
842
|
+
constructor(message, reason, options) {
|
|
843
|
+
super(message, options);
|
|
844
|
+
this.name = "FailClosedBlockingError";
|
|
845
|
+
this.reason = reason;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
var OPENCODE_INTERNAL_AGENT_NAMES = new Set(["title", "summary", "compaction"]);
|
|
849
|
+
function isMagicContextHiddenAgentName(agent) {
|
|
850
|
+
if (agent === "smart-note-compiler" || agent.startsWith("smart-note-")) {
|
|
851
|
+
return true;
|
|
852
|
+
}
|
|
853
|
+
if (agent === "historian" || agent.startsWith("historian-"))
|
|
854
|
+
return true;
|
|
855
|
+
if (agent === "dreamer" || agent.startsWith("dreamer-"))
|
|
856
|
+
return true;
|
|
857
|
+
return false;
|
|
858
|
+
}
|
|
859
|
+
var MAX_FORMATTED_BLOCKING_PROCESSES = 8;
|
|
860
|
+
var MAX_FORMATTED_COMMAND_LINE_LENGTH = 80;
|
|
861
|
+
function normalizeFailClosedProcessKind(process2) {
|
|
862
|
+
switch (process2.kind) {
|
|
863
|
+
case "OpenCode server":
|
|
864
|
+
case "OpenCode instance (TUI/CLI)":
|
|
865
|
+
case "Pi":
|
|
866
|
+
case "process":
|
|
867
|
+
return process2.kind;
|
|
868
|
+
}
|
|
869
|
+
switch (process2.harness?.trim().toLowerCase()) {
|
|
870
|
+
case "opencode server":
|
|
871
|
+
return "OpenCode server";
|
|
872
|
+
case "opencode instance (tui/cli)":
|
|
873
|
+
case "opencode instance":
|
|
874
|
+
return "OpenCode instance (TUI/CLI)";
|
|
875
|
+
case "pi":
|
|
876
|
+
case "pi harness":
|
|
877
|
+
case "omp":
|
|
878
|
+
return "Pi";
|
|
879
|
+
default:
|
|
880
|
+
return "process";
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
function formatFailClosedProcessStartTime(startTime) {
|
|
884
|
+
if (!Number.isFinite(startTime) || startTime <= 0)
|
|
885
|
+
return "unverified";
|
|
886
|
+
try {
|
|
887
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
888
|
+
day: "numeric",
|
|
889
|
+
hour: "2-digit",
|
|
890
|
+
minute: "2-digit",
|
|
891
|
+
month: "short",
|
|
892
|
+
hour12: false
|
|
893
|
+
}).formatToParts(new Date(startTime));
|
|
894
|
+
const values = Object.fromEntries(parts.map(({ type, value }) => [type, value]));
|
|
895
|
+
if (!values.month || !values.day || !values.hour || !values.minute)
|
|
896
|
+
return "unverified";
|
|
897
|
+
return `${values.month} ${values.day} ${values.hour}:${values.minute}`;
|
|
898
|
+
} catch {
|
|
899
|
+
return "unverified";
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
function formatFailClosedCommandLine(commandLine) {
|
|
903
|
+
if (typeof commandLine !== "string" || commandLine.trim().length === 0)
|
|
904
|
+
return "unverified";
|
|
905
|
+
try {
|
|
906
|
+
const normalized = commandLine.replaceAll("\x00", " ").replace(/\s+/g, " ").trim();
|
|
907
|
+
if (normalized.length === 0)
|
|
908
|
+
return "unverified";
|
|
909
|
+
const sanitized = sanitizeDiagnosticText(normalized).trim();
|
|
910
|
+
if (sanitized.length === 0)
|
|
911
|
+
return "unverified";
|
|
912
|
+
return sanitized.length > MAX_FORMATTED_COMMAND_LINE_LENGTH ? `${sanitized.slice(0, MAX_FORMATTED_COMMAND_LINE_LENGTH - 3)}...` : sanitized;
|
|
913
|
+
} catch {
|
|
914
|
+
return "unverified";
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
function formatFailClosedBlockingProcesses(processes) {
|
|
918
|
+
const uniqueProcesses = new Map;
|
|
919
|
+
for (const process2 of processes) {
|
|
920
|
+
if (!Number.isInteger(process2.pid) || process2.pid <= 0)
|
|
921
|
+
continue;
|
|
922
|
+
const candidate = {
|
|
923
|
+
kind: normalizeFailClosedProcessKind(process2),
|
|
924
|
+
pid: process2.pid,
|
|
925
|
+
startTime: process2.startTime,
|
|
926
|
+
commandLine: process2.commandLine
|
|
927
|
+
};
|
|
928
|
+
const previous = uniqueProcesses.get(process2.pid);
|
|
929
|
+
if (!previous || previous.kind === "process" && candidate.kind !== "process") {
|
|
930
|
+
uniqueProcesses.set(process2.pid, candidate);
|
|
931
|
+
} else {
|
|
932
|
+
uniqueProcesses.set(process2.pid, {
|
|
933
|
+
...previous,
|
|
934
|
+
startTime: previous.startTime ?? candidate.startTime,
|
|
935
|
+
commandLine: previous.commandLine ?? candidate.commandLine
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
const entries = [...uniqueProcesses.values()];
|
|
940
|
+
const visible = entries.slice(0, MAX_FORMATTED_BLOCKING_PROCESSES);
|
|
941
|
+
const rendered = visible.map(({ kind, pid }) => `${kind} (PID ${pid})`);
|
|
942
|
+
const omitted = entries.length - visible.length;
|
|
943
|
+
if (omitted > 0)
|
|
944
|
+
rendered.push(`${omitted} more blocking process(es)`);
|
|
945
|
+
if (rendered.length === 0)
|
|
946
|
+
return "a live process";
|
|
947
|
+
const summary = rendered.length === 1 ? rendered[0] : `${rendered.slice(0, -1).join(", ")}, and ${rendered.at(-1)}`;
|
|
948
|
+
const evidence = entries.map(({ kind, pid, startTime, commandLine }) => `- PID ${pid}: ${kind}, started ${formatFailClosedProcessStartTime(startTime)}, cmd: ${formatFailClosedCommandLine(commandLine)}`).join(`
|
|
949
|
+
`);
|
|
950
|
+
return `${summary}
|
|
951
|
+
Blocking process evidence:
|
|
952
|
+
${evidence}`;
|
|
953
|
+
}
|
|
954
|
+
function formatFailClosedBlockingMessage(reason) {
|
|
955
|
+
if (reason.kind === "migration_guard") {
|
|
956
|
+
if (reason.unreadableFile) {
|
|
957
|
+
const arm = reason.unreadableArm ?? "io";
|
|
958
|
+
const recovery = arm === "io" ? `If none of these processes are running, it is safe to delete ${reason.unreadableFile} and retry.` : `The file may be a recent incomplete write; retry after the file is older than the ten-minute grace window, or stop the relevant process before deleting it.`;
|
|
959
|
+
return [
|
|
960
|
+
`Magic Context cannot migrate the shared database because RPC discovery file ${reason.unreadableFile} is uncertain (${arm} arm), so the absence of a live process cannot be proven.`,
|
|
961
|
+
recovery,
|
|
962
|
+
`Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
|
|
963
|
+
].join(" ");
|
|
964
|
+
}
|
|
965
|
+
return [
|
|
966
|
+
`Magic Context cannot migrate the shared database (one database serves every project on this machine) because ${formatFailClosedBlockingProcesses(reason.blockingProcesses)} may be running an older Magic Context build that would fail against the migrated database.`,
|
|
967
|
+
`The database is at upstream migration v${reason.persistedVersion}; this build needs v${reason.supportedVersion}.`,
|
|
968
|
+
"Restart the blocking process — even one from a different project — and it will pick up the new build and migrate on start; or shut it down and retry.",
|
|
969
|
+
"If that host loads a pinned or cached older Magic Context build, a restart alone is not enough: stop it or update its Magic Context build, then start this host once so it migrates the database with nothing older attached.",
|
|
970
|
+
`Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
|
|
971
|
+
].join(" ");
|
|
972
|
+
}
|
|
973
|
+
if (reason.kind === "schema_fence") {
|
|
974
|
+
return [
|
|
975
|
+
`Magic Context cannot operate: this Magic Context build is older than the database; upgrade/restart this harness (upstream migration lane v${reason.persistedVersion}, build supports through v${reason.supportedVersion}).`,
|
|
976
|
+
`Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
|
|
977
|
+
].join(" ");
|
|
978
|
+
}
|
|
979
|
+
const cause = reason.cause.trim().length > 0 ? reason.cause.trim() : "unknown storage error";
|
|
980
|
+
return [
|
|
981
|
+
`Magic Context cannot operate: persistent storage failed (${cause}).`,
|
|
982
|
+
"The plugin will not silently degrade to native compaction while enabled.",
|
|
983
|
+
`Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
|
|
984
|
+
].join(" ");
|
|
985
|
+
}
|
|
986
|
+
function formatFailClosedBlockingSummary(reason) {
|
|
987
|
+
if (reason.kind === "migration_guard") {
|
|
988
|
+
if (reason.unreadableFile) {
|
|
989
|
+
return `Magic Context cannot migrate the shared database from v${reason.persistedVersion} to v${reason.supportedVersion}: RPC discovery file ${reason.unreadableFile} could not be read, so an older host may still be attached.`;
|
|
990
|
+
}
|
|
991
|
+
const blockers = reason.blockingProcesses.filter((process2) => Number.isInteger(process2.pid) && process2.pid > 0).map((process2) => `${normalizeFailClosedProcessKind(process2)} (PID ${process2.pid})`);
|
|
992
|
+
const named = blockers.length > 0 ? blockers.join(", ") : "a live process";
|
|
993
|
+
return `Magic Context cannot migrate the shared database from v${reason.persistedVersion} to v${reason.supportedVersion} while ${named} may still run an older Magic Context build. Stop or update that host, then start this one once.`;
|
|
994
|
+
}
|
|
995
|
+
if (reason.kind === "schema_fence") {
|
|
996
|
+
return `Magic Context cannot operate: the shared database is at v${reason.persistedVersion}, newer than this build supports (v${reason.supportedVersion}). Update and restart this host.`;
|
|
997
|
+
}
|
|
998
|
+
const cause = reason.cause.trim().length > 0 ? reason.cause.trim() : "unknown storage error";
|
|
999
|
+
return `Magic Context cannot operate: persistent storage failed (${cause}).`;
|
|
1000
|
+
}
|
|
1001
|
+
function createFailClosedBlockingError(reason, options) {
|
|
1002
|
+
return new FailClosedBlockingError(formatFailClosedBlockingMessage(reason), reason, options);
|
|
1003
|
+
}
|
|
1004
|
+
function isFailClosedBlockingError(error) {
|
|
1005
|
+
return error instanceof FailClosedBlockingError || typeof error === "object" && error !== null && error.name === "FailClosedBlockingError" && error.code === "FAIL_CLOSED_BLOCKING";
|
|
1006
|
+
}
|
|
1007
|
+
function shouldBypassFailClosedBlock(input) {
|
|
1008
|
+
if (input.isPiSubagentEnv === true)
|
|
1009
|
+
return true;
|
|
1010
|
+
if (input.isInternalChildSession === true)
|
|
1011
|
+
return true;
|
|
1012
|
+
const agent = typeof input.agent === "string" ? input.agent.trim() : "";
|
|
1013
|
+
if (agent.length === 0)
|
|
1014
|
+
return false;
|
|
1015
|
+
if (OPENCODE_INTERNAL_AGENT_NAMES.has(agent))
|
|
1016
|
+
return true;
|
|
1017
|
+
if (isMagicContextHiddenAgentName(agent))
|
|
1018
|
+
return true;
|
|
1019
|
+
return false;
|
|
1020
|
+
}
|
|
1021
|
+
function resolveAgentNameFromMessages(messages) {
|
|
1022
|
+
for (let i = messages.length - 1;i >= 0; i -= 1) {
|
|
1023
|
+
const info = messages[i]?.info;
|
|
1024
|
+
if (!info || typeof info !== "object")
|
|
1025
|
+
continue;
|
|
1026
|
+
const agent = info.agent;
|
|
1027
|
+
if (typeof agent === "string" && agent.length > 0)
|
|
1028
|
+
return agent;
|
|
1029
|
+
}
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// src/features/magic-context/migration-worker-client.ts
|
|
1034
|
+
import { Worker } from "node:worker_threads";
|
|
1035
|
+
|
|
1036
|
+
// src/shared/off-thread-migration-clock.ts
|
|
1037
|
+
var activeMigrations = 0;
|
|
1038
|
+
var activeSince = 0;
|
|
1039
|
+
var accumulatedMs = 0;
|
|
1040
|
+
var idleWaiters = [];
|
|
1041
|
+
function offThreadMigrationMs(now = performance.now()) {
|
|
1042
|
+
return accumulatedMs + (activeMigrations > 0 ? Math.max(0, now - activeSince) : 0);
|
|
1043
|
+
}
|
|
1044
|
+
function isOffThreadMigrationRunning() {
|
|
1045
|
+
return activeMigrations > 0;
|
|
1046
|
+
}
|
|
1047
|
+
function beginOffThreadMigration() {
|
|
1048
|
+
if (activeMigrations === 0)
|
|
1049
|
+
activeSince = performance.now();
|
|
1050
|
+
activeMigrations += 1;
|
|
1051
|
+
let ended = false;
|
|
1052
|
+
return () => {
|
|
1053
|
+
if (ended)
|
|
1054
|
+
return;
|
|
1055
|
+
ended = true;
|
|
1056
|
+
activeMigrations -= 1;
|
|
1057
|
+
if (activeMigrations > 0)
|
|
1058
|
+
return;
|
|
1059
|
+
accumulatedMs += Math.max(0, performance.now() - activeSince);
|
|
1060
|
+
const waiters = idleWaiters;
|
|
1061
|
+
idleWaiters = [];
|
|
1062
|
+
for (const wake of waiters)
|
|
1063
|
+
wake();
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
function whenOffThreadMigrationIdle() {
|
|
1067
|
+
if (activeMigrations === 0)
|
|
1068
|
+
return Promise.resolve();
|
|
1069
|
+
return new Promise((resolve) => idleWaiters.push(resolve));
|
|
1070
|
+
}
|
|
1071
|
+
function startBootDeadline(timeoutMs, onDeferred) {
|
|
1072
|
+
const startedAt = performance.now();
|
|
1073
|
+
const migratedAtStart = offThreadMigrationMs(startedAt);
|
|
1074
|
+
const budgetMs = Math.max(0, timeoutMs);
|
|
1075
|
+
let timer;
|
|
1076
|
+
let cancelled = false;
|
|
1077
|
+
let deferredReported = false;
|
|
1078
|
+
let resolveExpired;
|
|
1079
|
+
const expired = new Promise((resolve) => {
|
|
1080
|
+
resolveExpired = resolve;
|
|
1081
|
+
});
|
|
1082
|
+
const remainingMs = () => {
|
|
1083
|
+
const now = performance.now();
|
|
1084
|
+
const countedMs = now - startedAt - (offThreadMigrationMs(now) - migratedAtStart);
|
|
1085
|
+
return budgetMs - countedMs;
|
|
1086
|
+
};
|
|
1087
|
+
const check = () => {
|
|
1088
|
+
timer = undefined;
|
|
1089
|
+
if (cancelled)
|
|
1090
|
+
return;
|
|
1091
|
+
if (isOffThreadMigrationRunning()) {
|
|
1092
|
+
if (!deferredReported) {
|
|
1093
|
+
deferredReported = true;
|
|
1094
|
+
onDeferred?.();
|
|
1095
|
+
}
|
|
1096
|
+
whenOffThreadMigrationIdle().then(check);
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
const left = remainingMs();
|
|
1100
|
+
if (left > 0) {
|
|
1101
|
+
timer = setTimeout(check, left);
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
resolveExpired();
|
|
1105
|
+
};
|
|
1106
|
+
timer = setTimeout(check, budgetMs);
|
|
1107
|
+
return {
|
|
1108
|
+
expired,
|
|
1109
|
+
cancel: () => {
|
|
1110
|
+
cancelled = true;
|
|
1111
|
+
if (timer)
|
|
1112
|
+
clearTimeout(timer);
|
|
1113
|
+
timer = undefined;
|
|
1114
|
+
}
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
// src/features/magic-context/migration-worker-client.ts
|
|
1119
|
+
function workerStartError(detail) {
|
|
1120
|
+
return new Error(`the migration worker could not start (${detail}); reinstall or rebuild the plugin`);
|
|
1121
|
+
}
|
|
1122
|
+
function defaultWorkerEntry() {
|
|
1123
|
+
return new URL(new URL(import.meta.url).pathname.endsWith(".ts") ? "./migration-worker.ts" : "./migration-worker.js", import.meta.url);
|
|
1124
|
+
}
|
|
1125
|
+
var workerEntryOverride = null;
|
|
1126
|
+
function runMigrationsOffThread(data) {
|
|
1127
|
+
return new Promise((resolve, reject) => {
|
|
1128
|
+
let worker;
|
|
1129
|
+
try {
|
|
1130
|
+
worker = new Worker(workerEntryOverride ?? defaultWorkerEntry(), { workerData: data });
|
|
1131
|
+
} catch (error) {
|
|
1132
|
+
reject(workerStartError(error instanceof Error ? error.message : String(error)));
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
log2(`[migrations] applying pending migrations on a worker thread: ${data.dbPath}`);
|
|
1136
|
+
let ready = false;
|
|
1137
|
+
let settled = false;
|
|
1138
|
+
let endBusy = beginOffThreadMigration();
|
|
1139
|
+
const setBusy = (busy) => {
|
|
1140
|
+
if (busy && !endBusy)
|
|
1141
|
+
endBusy = beginOffThreadMigration();
|
|
1142
|
+
if (!busy && endBusy) {
|
|
1143
|
+
endBusy();
|
|
1144
|
+
endBusy = null;
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
const settle = (finish) => {
|
|
1148
|
+
if (settled)
|
|
1149
|
+
return;
|
|
1150
|
+
settled = true;
|
|
1151
|
+
setBusy(false);
|
|
1152
|
+
finish();
|
|
1153
|
+
};
|
|
1154
|
+
const unavailable = (detail) => {
|
|
1155
|
+
settle(() => {
|
|
1156
|
+
reject(workerStartError(detail));
|
|
1157
|
+
});
|
|
1158
|
+
};
|
|
1159
|
+
worker.on("message", (message) => {
|
|
1160
|
+
switch (message.type) {
|
|
1161
|
+
case "ready":
|
|
1162
|
+
ready = true;
|
|
1163
|
+
log2(`[migrations] migration worker ready: ${data.dbPath}`);
|
|
1164
|
+
return;
|
|
1165
|
+
case "log":
|
|
1166
|
+
writeForwardedLogLine2(message.line);
|
|
1167
|
+
return;
|
|
1168
|
+
case "lock-wait":
|
|
1169
|
+
if (!settled)
|
|
1170
|
+
setBusy(!message.waiting);
|
|
1171
|
+
return;
|
|
1172
|
+
case "done":
|
|
1173
|
+
if (!ready) {
|
|
1174
|
+
unavailable("reported completion before ready");
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
settle(() => resolve("migrated"));
|
|
1178
|
+
return;
|
|
1179
|
+
case "failed":
|
|
1180
|
+
if (!ready) {
|
|
1181
|
+
unavailable(message.message);
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
settle(() => reject(new Error(message.message)));
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
});
|
|
1188
|
+
worker.on("error", (error) => {
|
|
1189
|
+
if (!ready) {
|
|
1190
|
+
unavailable(error.message);
|
|
1191
|
+
worker.terminate();
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
settle(() => reject(new Error(`migration worker failed: ${error.message}`)));
|
|
1195
|
+
});
|
|
1196
|
+
worker.on("exit", (code) => {
|
|
1197
|
+
if (!ready) {
|
|
1198
|
+
unavailable(`exited with code ${code} before loading`);
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
settle(() => reject(new Error(`migration worker exited (code ${code}) before reporting a result`)));
|
|
1202
|
+
});
|
|
1203
|
+
});
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// src/features/magic-context/tool-owner-backfill.ts
|
|
1207
|
+
import { existsSync } from "node:fs";
|
|
1208
|
+
var LEASE_DURATION_MS = 5 * 60 * 1000;
|
|
1209
|
+
var LEASE_RENEWAL_MS = 60 * 1000;
|
|
1210
|
+
function resolveOpencodeDbPath() {
|
|
1211
|
+
return resolveOpenCodeDbPath().path;
|
|
1212
|
+
}
|
|
1213
|
+
function ensureBackfillStateTable(db) {
|
|
1214
|
+
db.exec(`
|
|
1215
|
+
CREATE TABLE IF NOT EXISTS tool_owner_backfill_state (
|
|
1216
|
+
session_id TEXT PRIMARY KEY,
|
|
1217
|
+
status TEXT NOT NULL CHECK (status IN ('pending', 'running', 'completed', 'skipped')),
|
|
1218
|
+
started_at INTEGER,
|
|
1219
|
+
lease_expires_at INTEGER,
|
|
1220
|
+
completed_at INTEGER,
|
|
1221
|
+
last_error TEXT
|
|
1222
|
+
);
|
|
1223
|
+
CREATE INDEX IF NOT EXISTS idx_tool_owner_backfill_state_status
|
|
1224
|
+
ON tool_owner_backfill_state(status);
|
|
1225
|
+
`);
|
|
1226
|
+
}
|
|
1227
|
+
function runToolOwnerBackfill(db) {
|
|
1228
|
+
const startedAt = performance.now();
|
|
1229
|
+
ensureBackfillStateTable(db);
|
|
1230
|
+
const result = {
|
|
1231
|
+
sessionsProcessed: 0,
|
|
1232
|
+
sessionsSkippedNoOcDb: 0,
|
|
1233
|
+
sessionsSkippedNoMatches: 0,
|
|
1234
|
+
sessionsCompleted: 0,
|
|
1235
|
+
sessionsBlockedByLease: 0,
|
|
1236
|
+
sessionsErrored: 0,
|
|
1237
|
+
rowsUpdated: 0,
|
|
1238
|
+
rowsLeftNull: 0,
|
|
1239
|
+
durationMs: 0
|
|
1240
|
+
};
|
|
1241
|
+
if (!isToolOwnerBackfillNeeded(db)) {
|
|
1242
|
+
result.durationMs = performance.now() - startedAt;
|
|
1243
|
+
return result;
|
|
1244
|
+
}
|
|
1245
|
+
const opencodeDbPath = resolveOpencodeDbPath();
|
|
1246
|
+
if (!existsSync(opencodeDbPath)) {
|
|
1247
|
+
log2(`[backfill] OpenCode DB not found at ${opencodeDbPath} — marking all unbackfilled sessions as skipped. Lazy adoption (defense-in-depth) handles legacy rows at runtime.`);
|
|
1248
|
+
markAllUnbackfilledSessionsSkipped(db);
|
|
1249
|
+
result.sessionsSkippedNoOcDb = countSessionsByStatus(db, "skipped");
|
|
1250
|
+
result.durationMs = performance.now() - startedAt;
|
|
1251
|
+
return result;
|
|
1252
|
+
}
|
|
1253
|
+
const escapedDbPath = opencodeDbPath.replaceAll("'", "''");
|
|
1254
|
+
db.exec(`ATTACH '${escapedDbPath}' AS oc_backfill`);
|
|
1255
|
+
try {
|
|
1256
|
+
if (!hasV1MessageTables(db, "oc_backfill")) {
|
|
1257
|
+
throw new Error(`OpenCode store at ${opencodeDbPath} has no v1 message tables; nothing to backfill`);
|
|
1258
|
+
}
|
|
1259
|
+
backfillToolOwnersInChunks(db, result);
|
|
1260
|
+
} finally {
|
|
1261
|
+
try {
|
|
1262
|
+
db.exec("DETACH DATABASE oc_backfill");
|
|
1263
|
+
} catch (error) {
|
|
1264
|
+
log2(`[backfill] failed to detach oc_backfill database: ${error instanceof Error ? error.message : String(error)}`);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
result.durationMs = performance.now() - startedAt;
|
|
1268
|
+
log2(`[backfill] sessions=${result.sessionsProcessed} completed=${result.sessionsCompleted} skipped_no_oc=${result.sessionsSkippedNoOcDb} skipped_no_matches=${result.sessionsSkippedNoMatches} blocked_by_lease=${result.sessionsBlockedByLease} errored=${result.sessionsErrored} rows_updated=${result.rowsUpdated} rows_left_null=${result.rowsLeftNull} duration_ms=${Math.round(result.durationMs)}`);
|
|
1269
|
+
return result;
|
|
1270
|
+
}
|
|
1271
|
+
function isToolOwnerBackfillNeeded(db) {
|
|
1272
|
+
ensureBackfillStateTable(db);
|
|
1273
|
+
const row = db.prepare(`SELECT 1 AS hit
|
|
1274
|
+
FROM tags
|
|
1275
|
+
WHERE type = 'tool' AND tool_owner_message_id IS NULL
|
|
1276
|
+
AND NOT EXISTS (
|
|
1277
|
+
SELECT 1 FROM tool_owner_backfill_state s
|
|
1278
|
+
WHERE s.session_id = tags.session_id
|
|
1279
|
+
AND s.status IN ('completed', 'skipped')
|
|
1280
|
+
)
|
|
1281
|
+
LIMIT 1`).get();
|
|
1282
|
+
return row !== null && row !== undefined;
|
|
1283
|
+
}
|
|
1284
|
+
function markAllUnbackfilledSessionsSkipped(db) {
|
|
1285
|
+
const now = Date.now();
|
|
1286
|
+
db.prepare(`INSERT INTO tool_owner_backfill_state(session_id, status, started_at, completed_at, last_error)
|
|
1287
|
+
SELECT DISTINCT session_id, 'skipped', NULL, ?, NULL
|
|
1288
|
+
FROM tags
|
|
1289
|
+
WHERE type = 'tool' AND tool_owner_message_id IS NULL
|
|
1290
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
1291
|
+
status = 'skipped',
|
|
1292
|
+
completed_at = excluded.completed_at,
|
|
1293
|
+
last_error = NULL
|
|
1294
|
+
WHERE tool_owner_backfill_state.status NOT IN ('completed', 'running')`).run(now);
|
|
1295
|
+
}
|
|
1296
|
+
function countSessionsByStatus(db, status) {
|
|
1297
|
+
const row = db.prepare("SELECT COUNT(*) AS c FROM tool_owner_backfill_state WHERE status = ?").get(status);
|
|
1298
|
+
return row.c;
|
|
1299
|
+
}
|
|
1300
|
+
function acquireSessionLease(db, sessionId, now) {
|
|
1301
|
+
const expiresAt = now + LEASE_DURATION_MS;
|
|
1302
|
+
const result = db.prepare(`INSERT INTO tool_owner_backfill_state(session_id, status, started_at, lease_expires_at)
|
|
1303
|
+
SELECT ?, 'running', ?, ?
|
|
1304
|
+
WHERE EXISTS (SELECT 1 FROM tags WHERE session_id = ?)
|
|
1305
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
1306
|
+
status = 'running',
|
|
1307
|
+
started_at = excluded.started_at,
|
|
1308
|
+
lease_expires_at = excluded.lease_expires_at,
|
|
1309
|
+
last_error = NULL
|
|
1310
|
+
WHERE tool_owner_backfill_state.status IN ('pending', 'skipped')
|
|
1311
|
+
OR (tool_owner_backfill_state.status = 'running'
|
|
1312
|
+
AND tool_owner_backfill_state.lease_expires_at < ?)`).run(sessionId, now, expiresAt, sessionId, now);
|
|
1313
|
+
return (result.changes ?? 0) === 1;
|
|
1314
|
+
}
|
|
1315
|
+
function renewSessionLease(db, sessionId, now) {
|
|
1316
|
+
const expiresAt = now + LEASE_DURATION_MS;
|
|
1317
|
+
db.prepare(`UPDATE tool_owner_backfill_state
|
|
1318
|
+
SET lease_expires_at = ?
|
|
1319
|
+
WHERE session_id = ? AND status = 'running'`).run(expiresAt, sessionId);
|
|
1320
|
+
}
|
|
1321
|
+
function markSessionCompleted(db, sessionId, now) {
|
|
1322
|
+
db.prepare(`UPDATE tool_owner_backfill_state
|
|
1323
|
+
SET status = 'completed', completed_at = ?, lease_expires_at = NULL, last_error = NULL
|
|
1324
|
+
WHERE session_id = ?`).run(now, sessionId);
|
|
1325
|
+
}
|
|
1326
|
+
function markSessionPendingRetry(db, sessionId) {
|
|
1327
|
+
db.prepare(`UPDATE tool_owner_backfill_state
|
|
1328
|
+
SET status = 'pending', completed_at = NULL, lease_expires_at = NULL, last_error = NULL
|
|
1329
|
+
WHERE session_id = ?`).run(sessionId);
|
|
1330
|
+
}
|
|
1331
|
+
function markSessionSkipped(db, sessionId, now, reason) {
|
|
1332
|
+
db.prepare(`UPDATE tool_owner_backfill_state
|
|
1333
|
+
SET status = 'skipped', completed_at = ?, last_error = ?, lease_expires_at = NULL
|
|
1334
|
+
WHERE session_id = ? AND status = 'running'`).run(now, reason, sessionId);
|
|
1335
|
+
}
|
|
1336
|
+
function markSessionErrored(db, sessionId, error) {
|
|
1337
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1338
|
+
db.prepare(`UPDATE tool_owner_backfill_state
|
|
1339
|
+
SET last_error = ?, lease_expires_at = NULL
|
|
1340
|
+
WHERE session_id = ?`).run(message, sessionId);
|
|
1341
|
+
}
|
|
1342
|
+
function getSessionsNeedingBackfill(db) {
|
|
1343
|
+
const rows = db.prepare(`SELECT DISTINCT t.session_id
|
|
1344
|
+
FROM tags t
|
|
1345
|
+
LEFT JOIN tool_owner_backfill_state s ON s.session_id = t.session_id
|
|
1346
|
+
WHERE t.type = 'tool' AND t.tool_owner_message_id IS NULL
|
|
1347
|
+
AND (s.status IS NULL OR s.status NOT IN ('completed', 'skipped'))
|
|
1348
|
+
ORDER BY t.session_id ASC`).all();
|
|
1349
|
+
return rows.map((r) => r.session_id);
|
|
1350
|
+
}
|
|
1351
|
+
function buildSessionOwnerMap(db, sessionId) {
|
|
1352
|
+
const rows = db.prepare(`SELECT
|
|
1353
|
+
COALESCE(
|
|
1354
|
+
CASE WHEN json_extract(p.data, '$.type') = 'tool_use'
|
|
1355
|
+
THEN json_extract(p.data, '$.id')
|
|
1356
|
+
END,
|
|
1357
|
+
json_extract(p.data, '$.callID')
|
|
1358
|
+
) AS callid,
|
|
1359
|
+
m.id AS owner_id,
|
|
1360
|
+
m.time_created AS owner_t_created,
|
|
1361
|
+
p.id AS part_id,
|
|
1362
|
+
p.time_created AS part_t_created
|
|
1363
|
+
FROM oc_backfill.message m
|
|
1364
|
+
INNER JOIN oc_backfill.part p ON p.message_id = m.id
|
|
1365
|
+
WHERE m.session_id = ?
|
|
1366
|
+
AND json_extract(m.data, '$.role') = 'assistant'
|
|
1367
|
+
AND (
|
|
1368
|
+
(json_extract(p.data, '$.type') IN ('tool', 'tool-invocation')
|
|
1369
|
+
AND json_extract(p.data, '$.callID') IS NOT NULL)
|
|
1370
|
+
OR (json_extract(p.data, '$.type') = 'tool_use'
|
|
1371
|
+
AND json_extract(p.data, '$.id') IS NOT NULL)
|
|
1372
|
+
)
|
|
1373
|
+
ORDER BY
|
|
1374
|
+
m.time_created ASC,
|
|
1375
|
+
m.id ASC,
|
|
1376
|
+
p.time_created ASC,
|
|
1377
|
+
p.id ASC`).all(sessionId);
|
|
1378
|
+
const oldestByCallId = new Map;
|
|
1379
|
+
for (const r of rows) {
|
|
1380
|
+
if (typeof r.callid !== "string" || r.callid.length === 0)
|
|
1381
|
+
continue;
|
|
1382
|
+
if (!oldestByCallId.has(r.callid)) {
|
|
1383
|
+
oldestByCallId.set(r.callid, r.owner_id);
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
return oldestByCallId;
|
|
1387
|
+
}
|
|
1388
|
+
function applyOwnersForSession(db, sessionId, ownersByCallId) {
|
|
1389
|
+
if (ownersByCallId.size === 0) {
|
|
1390
|
+
const leftNull = db.prepare(`SELECT COUNT(*) AS c FROM tags
|
|
1391
|
+
WHERE session_id = ? AND type = 'tool'
|
|
1392
|
+
AND tool_owner_message_id IS NULL`).get(sessionId).c;
|
|
1393
|
+
return { rowsUpdated: 0, rowsLeftNull: leftNull };
|
|
1394
|
+
}
|
|
1395
|
+
const findOrphanStmt = db.prepare(`SELECT id FROM tags
|
|
1396
|
+
WHERE session_id = ? AND message_id = ? AND type = 'tool'
|
|
1397
|
+
AND tool_owner_message_id IS NULL
|
|
1398
|
+
ORDER BY tag_number ASC
|
|
1399
|
+
LIMIT 1`);
|
|
1400
|
+
const updateRowStmt = db.prepare(`UPDATE tags
|
|
1401
|
+
SET tool_owner_message_id = ?
|
|
1402
|
+
WHERE id = ? AND tool_owner_message_id IS NULL`);
|
|
1403
|
+
const existingOwnerStmt = db.prepare(`SELECT 1 AS hit FROM tags
|
|
1404
|
+
WHERE session_id = ? AND message_id = ? AND type = 'tool'
|
|
1405
|
+
AND tool_owner_message_id = ?
|
|
1406
|
+
LIMIT 1`);
|
|
1407
|
+
const ownerForRowStmt = db.prepare("SELECT tool_owner_message_id FROM tags WHERE id = ?");
|
|
1408
|
+
let rowsUpdated = 0;
|
|
1409
|
+
db.transaction(() => {
|
|
1410
|
+
for (const [callId, ownerId] of ownersByCallId) {
|
|
1411
|
+
const orphan = findOrphanStmt.get(sessionId, callId);
|
|
1412
|
+
if (!orphan)
|
|
1413
|
+
continue;
|
|
1414
|
+
if (existingOwnerStmt.get(sessionId, callId, ownerId))
|
|
1415
|
+
continue;
|
|
1416
|
+
updateRowStmt.run(ownerId, orphan.id);
|
|
1417
|
+
const updated = ownerForRowStmt.get(orphan.id);
|
|
1418
|
+
if (updated?.tool_owner_message_id === ownerId)
|
|
1419
|
+
rowsUpdated += 1;
|
|
1420
|
+
}
|
|
1421
|
+
}).immediate();
|
|
1422
|
+
const rowsLeftNull = db.prepare(`SELECT COUNT(*) AS c FROM tags
|
|
1423
|
+
WHERE session_id = ? AND type = 'tool'
|
|
1424
|
+
AND tool_owner_message_id IS NULL`).get(sessionId).c;
|
|
1425
|
+
return { rowsUpdated, rowsLeftNull };
|
|
1426
|
+
}
|
|
1427
|
+
function backfillToolOwnersInChunks(db, result) {
|
|
1428
|
+
const sessionIds = getSessionsNeedingBackfill(db);
|
|
1429
|
+
let lastRenewedAt = Date.now();
|
|
1430
|
+
for (const sessionId of sessionIds) {
|
|
1431
|
+
const now = Date.now();
|
|
1432
|
+
result.sessionsProcessed += 1;
|
|
1433
|
+
const acquired = acquireSessionLease(db, sessionId, now);
|
|
1434
|
+
if (!acquired) {
|
|
1435
|
+
result.sessionsBlockedByLease += 1;
|
|
1436
|
+
continue;
|
|
1437
|
+
}
|
|
1438
|
+
try {
|
|
1439
|
+
const owners = buildSessionOwnerMap(db, sessionId);
|
|
1440
|
+
const { rowsUpdated, rowsLeftNull } = applyOwnersForSession(db, sessionId, owners);
|
|
1441
|
+
result.rowsUpdated += rowsUpdated;
|
|
1442
|
+
result.rowsLeftNull += rowsLeftNull;
|
|
1443
|
+
if (owners.size === 0) {
|
|
1444
|
+
markSessionSkipped(db, sessionId, Date.now(), "no_oc_matches");
|
|
1445
|
+
result.sessionsSkippedNoMatches += 1;
|
|
1446
|
+
} else if (rowsLeftNull > 0) {
|
|
1447
|
+
markSessionPendingRetry(db, sessionId);
|
|
1448
|
+
} else {
|
|
1449
|
+
markSessionCompleted(db, sessionId, Date.now());
|
|
1450
|
+
result.sessionsCompleted += 1;
|
|
1451
|
+
}
|
|
1452
|
+
} catch (error) {
|
|
1453
|
+
log2(`[backfill] session=${sessionId} errored: ${error instanceof Error ? error.message : String(error)}`);
|
|
1454
|
+
markSessionErrored(db, sessionId, error);
|
|
1455
|
+
result.sessionsErrored += 1;
|
|
1456
|
+
}
|
|
1457
|
+
const sinceRenew = Date.now() - lastRenewedAt;
|
|
1458
|
+
if (sinceRenew > LEASE_RENEWAL_MS) {
|
|
1459
|
+
renewSessionLease(db, sessionId, Date.now());
|
|
1460
|
+
lastRenewedAt = Date.now();
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
// src/features/magic-context/storage-db.ts
|
|
1466
|
+
registerSlowWriteReporter2(logSlowWriteTransaction);
|
|
1467
|
+
registerSqliteDiagnosticSink2((message) => log2(message));
|
|
1468
|
+
var databases = new Map;
|
|
1469
|
+
var pendingAsyncOpens = new Map;
|
|
1470
|
+
var persistenceByDatabase = new WeakMap;
|
|
1471
|
+
var persistenceErrorByDatabase = new WeakMap;
|
|
1472
|
+
var pathByDatabase = new WeakMap;
|
|
1473
|
+
var lastSchemaFenceRejection = null;
|
|
1474
|
+
var lastMigrationOnOpenRefusal = null;
|
|
1475
|
+
var lastUnconfirmedMigrationHolders = null;
|
|
1476
|
+
function getUnconfirmedMigrationHolders() {
|
|
1477
|
+
return lastUnconfirmedMigrationHolders;
|
|
1478
|
+
}
|
|
1479
|
+
function getSchemaFenceRejection() {
|
|
1480
|
+
return lastSchemaFenceRejection;
|
|
1481
|
+
}
|
|
1482
|
+
function getMigrationOnOpenRefusal() {
|
|
1483
|
+
return lastMigrationOnOpenRefusal;
|
|
1484
|
+
}
|
|
1485
|
+
var LATEST_SUPPORTED_VERSION = 95;
|
|
1486
|
+
var BOOT_SQLITE_BUSY_TIMEOUT_MS = 5000;
|
|
1487
|
+
function ensureSecureStorageDir(dir) {
|
|
1488
|
+
ensureStorageDirectorySync(dir);
|
|
1489
|
+
}
|
|
1490
|
+
function tightenStorageTree(dir) {
|
|
1491
|
+
const { failures } = tightenStorageTreeSync(dir);
|
|
1492
|
+
log2(`[magic-context] storage permission tightening failures=${failures}`);
|
|
1493
|
+
}
|
|
1494
|
+
function resolveBootBusyTimeoutMs(value) {
|
|
1495
|
+
if (value === undefined)
|
|
1496
|
+
return BOOT_SQLITE_BUSY_TIMEOUT_MS;
|
|
1497
|
+
if (!Number.isFinite(value))
|
|
1498
|
+
return BOOT_SQLITE_BUSY_TIMEOUT_MS;
|
|
1499
|
+
return Math.max(0, Math.min(BOOT_SQLITE_BUSY_TIMEOUT_MS, Math.floor(value)));
|
|
1500
|
+
}
|
|
1501
|
+
function installBootBusyTimeout(db, dbPath, timeoutMs, report = log2) {
|
|
1502
|
+
db.exec(`PRAGMA busy_timeout=${timeoutMs}`);
|
|
1503
|
+
report(`[magic-context] SQLite boot busy timeout: backend=${detectSqliteRuntime2()} timeout=${timeoutMs}ms path=${dbPath}`);
|
|
1504
|
+
}
|
|
1505
|
+
function resolveDatabasePath(dbPathOverride) {
|
|
1506
|
+
if (dbPathOverride) {
|
|
1507
|
+
return { dbDir: dirname(dbPathOverride), dbPath: dbPathOverride };
|
|
1508
|
+
}
|
|
1509
|
+
const dbDir = getMagicContextStorageDir();
|
|
1510
|
+
return { dbDir, dbPath: join2(dbDir, "context.db") };
|
|
1511
|
+
}
|
|
1512
|
+
function isFileBackedPath(dbPath) {
|
|
1513
|
+
const trimmed = dbPath.trim();
|
|
1514
|
+
return trimmed !== "" && trimmed !== ":memory:" && !trimmed.startsWith("file:");
|
|
1515
|
+
}
|
|
1516
|
+
function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
|
|
1517
|
+
if (existsSync2(targetDbPath))
|
|
1518
|
+
return;
|
|
1519
|
+
const legacyDir = getLegacyOpenCodeMagicContextStorageDir();
|
|
1520
|
+
const legacyDbPath = join2(legacyDir, "context.db");
|
|
1521
|
+
if (!existsSync2(legacyDbPath))
|
|
1522
|
+
return;
|
|
1523
|
+
log2(`[magic-context] migrating legacy plugin storage: ${legacyDir} -> ${targetDbDir} (legacy left in place as backup)`);
|
|
1524
|
+
ensureSecureStorageDir(targetDbDir);
|
|
1525
|
+
try {
|
|
1526
|
+
const legacyDb = new Database2(legacyDbPath);
|
|
1527
|
+
try {
|
|
1528
|
+
configureContextDatabasePragmas2(legacyDb, true);
|
|
1529
|
+
legacyDb.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
1530
|
+
} finally {
|
|
1531
|
+
closeQuietly(legacyDb);
|
|
1532
|
+
}
|
|
1533
|
+
} catch (error) {
|
|
1534
|
+
log2(`[magic-context] legacy WAL checkpoint before copy failed (continuing with sidecar copy): ${getErrorMessage(error)}`);
|
|
1535
|
+
}
|
|
1536
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
1537
|
+
const src = `${legacyDbPath}${suffix}`;
|
|
1538
|
+
const dst = join2(targetDbDir, `context.db${suffix}`);
|
|
1539
|
+
if (existsSync2(src)) {
|
|
1540
|
+
try {
|
|
1541
|
+
copyStorageFileSync(src, dst);
|
|
1542
|
+
} catch (error) {
|
|
1543
|
+
log2(`[magic-context] failed to copy ${src}:`, getErrorMessage(error));
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
const legacyModelsDir = join2(legacyDir, "models");
|
|
1548
|
+
const targetModelsDir = join2(targetDbDir, "models");
|
|
1549
|
+
if (existsSync2(legacyModelsDir) && !existsSync2(targetModelsDir)) {
|
|
1550
|
+
try {
|
|
1551
|
+
copyStorageTreeSync(legacyModelsDir, targetModelsDir);
|
|
1552
|
+
} catch (error) {
|
|
1553
|
+
log2("[magic-context] failed to copy embedding model cache:", getErrorMessage(error));
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
function getPersistedSchemaVersion(db) {
|
|
1558
|
+
const hasMigrationsTable = db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'schema_migrations'").get();
|
|
1559
|
+
if (!hasMigrationsTable) {
|
|
1560
|
+
return 0;
|
|
1561
|
+
}
|
|
1562
|
+
const row = db.prepare("SELECT COALESCE(MAX(version), 0) AS version FROM schema_migrations WHERE version < ?").get(FORK_MIGRATION_VERSION_FLOOR);
|
|
1563
|
+
return row?.version ?? 0;
|
|
1564
|
+
}
|
|
1565
|
+
function formatSchemaFenceBootLog(persistedVersion, supportedVersion) {
|
|
1566
|
+
return `[magic-context] upstream migration lane at boot: database=v${persistedVersion}, supported_fence=v${supportedVersion}`;
|
|
1567
|
+
}
|
|
1568
|
+
function getRuntimeLatestSupportedVersion(options) {
|
|
1569
|
+
if (options?.latestSupportedVersion !== undefined) {
|
|
1570
|
+
return options.latestSupportedVersion;
|
|
1571
|
+
}
|
|
1572
|
+
const override = process.env.MAGIC_CONTEXT_LATEST_SUPPORTED_VERSION;
|
|
1573
|
+
if (override) {
|
|
1574
|
+
const parsed = Number.parseInt(override, 10);
|
|
1575
|
+
if (Number.isFinite(parsed)) {
|
|
1576
|
+
return parsed;
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
return LATEST_SUPPORTED_VERSION;
|
|
1580
|
+
}
|
|
1581
|
+
function enforceSchemaFence(db, dbPath, latestSupportedVersion) {
|
|
1582
|
+
const persistedVersion = getPersistedSchemaVersion(db);
|
|
1583
|
+
if (persistedVersion <= latestSupportedVersion) {
|
|
1584
|
+
lastSchemaFenceRejection = null;
|
|
1585
|
+
return true;
|
|
1586
|
+
}
|
|
1587
|
+
lastSchemaFenceRejection = { persistedVersion, supportedVersion: latestSupportedVersion };
|
|
1588
|
+
log2(`[magic-context] storage fatal: refusing to open ${dbPath}; upstream migration lane v${persistedVersion} is newer than this binary supports (max v${latestSupportedVersion}). A pinned or stale plugin is likely sharing this database with a newer instance; update or unpin Magic Context with 'npx @cortexkit/magic-context@latest doctor --force', then restart.`);
|
|
1589
|
+
return false;
|
|
1590
|
+
}
|
|
1591
|
+
function unreadableDiscovery(path, arm) {
|
|
1592
|
+
return {
|
|
1593
|
+
state: "unreadable",
|
|
1594
|
+
serverPids: [],
|
|
1595
|
+
staleFiles: [],
|
|
1596
|
+
unreadableFile: path,
|
|
1597
|
+
unreadableArm: arm
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
var RPC_DISCOVERY_PARSE_GRACE_MS = 10 * 60 * 1000;
|
|
1601
|
+
var defaultRpcDiscoveryFs = {
|
|
1602
|
+
readdirSync: (path, options) => options?.withFileTypes ? readdirSync(path, { withFileTypes: true }) : readdirSync(path),
|
|
1603
|
+
readFileSync: (path, encoding) => String(readFileSync2(path, encoding)),
|
|
1604
|
+
statSync: (path) => ({ mtimeMs: statSync(path).mtimeMs }),
|
|
1605
|
+
unlinkSync: (path) => unlinkSync(path),
|
|
1606
|
+
rmdirSync: (path) => rmdirSync(path)
|
|
1607
|
+
};
|
|
1608
|
+
var rpcDiscoveryFs = defaultRpcDiscoveryFs;
|
|
1609
|
+
function invalidDiscoveryReason(raw) {
|
|
1610
|
+
const trimmed = raw.trim();
|
|
1611
|
+
if (trimmed.startsWith("{")) {
|
|
1612
|
+
try {
|
|
1613
|
+
const parsed = JSON.parse(trimmed);
|
|
1614
|
+
if ("pid" in parsed) {
|
|
1615
|
+
const pid = Number(parsed.pid);
|
|
1616
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
1617
|
+
return "invalid-pid";
|
|
1618
|
+
}
|
|
1619
|
+
} catch {}
|
|
1620
|
+
}
|
|
1621
|
+
return "parse-invalid";
|
|
1622
|
+
}
|
|
1623
|
+
function classifyDiscoveryRecordKind(record) {
|
|
1624
|
+
for (const value of [record.kind, record.harness]) {
|
|
1625
|
+
const normalized = value?.trim().toLowerCase();
|
|
1626
|
+
if (!normalized)
|
|
1627
|
+
continue;
|
|
1628
|
+
if (normalized === "process")
|
|
1629
|
+
return "process";
|
|
1630
|
+
if (normalized === "opencode server" || normalized === "server") {
|
|
1631
|
+
return "OpenCode server";
|
|
1632
|
+
}
|
|
1633
|
+
if (normalized === "opencode instance" || normalized === "opencode instance (tui/cli)" || normalized === "opencode" || normalized === "tui" || normalized === "cli") {
|
|
1634
|
+
return "OpenCode instance (TUI/CLI)";
|
|
1635
|
+
}
|
|
1636
|
+
if (normalized === "pi" || normalized === "pi harness" || normalized === "omp" || normalized === "oh-my-pi") {
|
|
1637
|
+
return "Pi";
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
return null;
|
|
1641
|
+
}
|
|
1642
|
+
function classifyRpcProcess(record, commandLine) {
|
|
1643
|
+
return classifyDiscoveryRecordKind(record) ?? classifyProcessKind(commandLine === undefined ? readProcessProbeEvidence(record.pid).commandLine : commandLine);
|
|
1644
|
+
}
|
|
1645
|
+
function classifyJunkDiscovery(portFile, raw, staleFiles) {
|
|
1646
|
+
let mtimeMs;
|
|
1647
|
+
try {
|
|
1648
|
+
mtimeMs = rpcDiscoveryFs.statSync(portFile).mtimeMs;
|
|
1649
|
+
} catch (error) {
|
|
1650
|
+
if (error.code === "ENOENT")
|
|
1651
|
+
return null;
|
|
1652
|
+
return unreadableDiscovery(portFile, "io");
|
|
1653
|
+
}
|
|
1654
|
+
const ageMs = Date.now() - mtimeMs;
|
|
1655
|
+
if (!Number.isFinite(ageMs) || ageMs < RPC_DISCOVERY_PARSE_GRACE_MS) {
|
|
1656
|
+
return unreadableDiscovery(portFile, "parse");
|
|
1657
|
+
}
|
|
1658
|
+
staleFiles.push(portFile);
|
|
1659
|
+
const reason = invalidDiscoveryReason(raw);
|
|
1660
|
+
log2(`[magic-context] removing stale RPC discovery file ${portFile}: ${reason} record older than 10 minutes`);
|
|
1661
|
+
return null;
|
|
1662
|
+
}
|
|
1663
|
+
function inspectRpcServerDiscovery(storageDir, processes, options) {
|
|
1664
|
+
const deadline = options?.deadlineMs === undefined ? Number.POSITIVE_INFINITY : Date.now() + options.deadlineMs;
|
|
1665
|
+
let progressAt = Date.now() + 3000;
|
|
1666
|
+
const rpcRoot = join2(storageDir, "rpc");
|
|
1667
|
+
let projectEntries;
|
|
1668
|
+
try {
|
|
1669
|
+
projectEntries = rpcDiscoveryFs.readdirSync(rpcRoot, { withFileTypes: true });
|
|
1670
|
+
} catch (error) {
|
|
1671
|
+
if (error.code === "ENOENT") {
|
|
1672
|
+
return { state: "absent", serverPids: [], staleFiles: [] };
|
|
1673
|
+
}
|
|
1674
|
+
return unreadableDiscovery(rpcRoot, "io");
|
|
1675
|
+
}
|
|
1676
|
+
const portFiles = [];
|
|
1677
|
+
for (const projectEntry of projectEntries) {
|
|
1678
|
+
if (!projectEntry.isDirectory())
|
|
1679
|
+
continue;
|
|
1680
|
+
const projectDir = join2(rpcRoot, projectEntry.name);
|
|
1681
|
+
let entries;
|
|
1682
|
+
try {
|
|
1683
|
+
entries = rpcDiscoveryFs.readdirSync(projectDir);
|
|
1684
|
+
} catch (error) {
|
|
1685
|
+
if (error.code === "ENOENT")
|
|
1686
|
+
continue;
|
|
1687
|
+
return unreadableDiscovery(projectDir, "io");
|
|
1688
|
+
}
|
|
1689
|
+
for (const entry of entries) {
|
|
1690
|
+
if (entry === "port" || entry.startsWith("port-") && entry.endsWith(".json")) {
|
|
1691
|
+
portFiles.push(join2(projectDir, entry));
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
if (portFiles.length === 0) {
|
|
1696
|
+
return { state: "absent", serverPids: [], staleFiles: [] };
|
|
1697
|
+
}
|
|
1698
|
+
if (!processes && process.platform === "win32")
|
|
1699
|
+
processes = inspectWindowsProcessesSync();
|
|
1700
|
+
const pids = new Set;
|
|
1701
|
+
const processByPid = new Map;
|
|
1702
|
+
const staleFiles = [];
|
|
1703
|
+
const inconclusivePids = new Set;
|
|
1704
|
+
const inconclusiveRecords = [];
|
|
1705
|
+
for (const [index, portFile] of portFiles.entries()) {
|
|
1706
|
+
if (Date.now() >= deadline)
|
|
1707
|
+
throw new Error(`RPC holder inspection timed out after ${Math.round((options?.deadlineMs ?? 0) / 1000)} seconds (${index}/${portFiles.length} records checked). Close OpenCode and Pi, then retry.`);
|
|
1708
|
+
if (options?.onProgress && Date.now() >= progressAt) {
|
|
1709
|
+
options.onProgress(index, portFiles.length);
|
|
1710
|
+
progressAt = Date.now() + 3000;
|
|
1711
|
+
}
|
|
1712
|
+
let raw;
|
|
1713
|
+
try {
|
|
1714
|
+
raw = rpcDiscoveryFs.readFileSync(portFile, "utf8");
|
|
1715
|
+
} catch (error) {
|
|
1716
|
+
if (error.code === "ENOENT")
|
|
1717
|
+
continue;
|
|
1718
|
+
return unreadableDiscovery(portFile, "io");
|
|
1719
|
+
}
|
|
1720
|
+
const filename = basename(portFile);
|
|
1721
|
+
const pidFromName = /^port-(\d+)/.exec(filename)?.[1];
|
|
1722
|
+
const fallbackPid = pidFromName ? Number(pidFromName) : 0;
|
|
1723
|
+
const record = parseRpcPortFile(raw, fallbackPid);
|
|
1724
|
+
if (!record || !Number.isInteger(record.pid) || record.pid <= 0) {
|
|
1725
|
+
const junk = classifyJunkDiscovery(portFile, raw, staleFiles);
|
|
1726
|
+
if (junk)
|
|
1727
|
+
return junk;
|
|
1728
|
+
continue;
|
|
1729
|
+
}
|
|
1730
|
+
if (isOwnRpcServerRecord(record))
|
|
1731
|
+
continue;
|
|
1732
|
+
const liveness = processes ? processes.liveness(record.pid) : isPidAlive(record.pid);
|
|
1733
|
+
if (liveness === "dead") {
|
|
1734
|
+
staleFiles.push(portFile);
|
|
1735
|
+
continue;
|
|
1736
|
+
}
|
|
1737
|
+
const evidence = processes ? processes.evidence(record.pid) : readProcessProbeEvidence(record.pid);
|
|
1738
|
+
const identity = isPidIdentityPlausible(record, evidence);
|
|
1739
|
+
if (identity === "plausible") {
|
|
1740
|
+
pids.add(record.pid);
|
|
1741
|
+
const detected = attachFailClosedBlockingProcessEvidence({
|
|
1742
|
+
kind: classifyRpcProcess(record, evidence.commandLine),
|
|
1743
|
+
pid: record.pid
|
|
1744
|
+
}, evidence);
|
|
1745
|
+
const previous = processByPid.get(record.pid);
|
|
1746
|
+
if (!previous || previous.kind === "process" && detected.kind !== "process") {
|
|
1747
|
+
processByPid.set(record.pid, detected);
|
|
1748
|
+
}
|
|
1749
|
+
} else if (identity === "implausible") {
|
|
1750
|
+
staleFiles.push(portFile);
|
|
1751
|
+
} else {
|
|
1752
|
+
inconclusivePids.add(record.pid);
|
|
1753
|
+
inconclusiveRecords.push({
|
|
1754
|
+
file: portFile,
|
|
1755
|
+
pid: record.pid,
|
|
1756
|
+
recordedStartedAt: record.started_at > 0 ? record.started_at : null,
|
|
1757
|
+
processStartTime: evidence.startTime,
|
|
1758
|
+
imageName: evidence.imageName ?? null,
|
|
1759
|
+
commandLine: evidence.commandLine,
|
|
1760
|
+
liveness
|
|
1761
|
+
});
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
for (const staleFile of staleFiles) {
|
|
1765
|
+
try {
|
|
1766
|
+
rpcDiscoveryFs.unlinkSync(staleFile);
|
|
1767
|
+
} catch {
|
|
1768
|
+
return unreadableDiscovery(staleFile, "io");
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
const serverPids = [...pids].sort((a, b) => a - b);
|
|
1772
|
+
if (serverPids.length > 0) {
|
|
1773
|
+
return {
|
|
1774
|
+
state: "live",
|
|
1775
|
+
serverPids,
|
|
1776
|
+
serverProcesses: serverPids.map((pid) => processByPid.get(pid) ?? { kind: "process", pid }),
|
|
1777
|
+
staleFiles,
|
|
1778
|
+
...inconclusiveRecords.length > 0 ? { inconclusiveRecords } : {}
|
|
1779
|
+
};
|
|
1780
|
+
}
|
|
1781
|
+
const uncertainPids = [...inconclusivePids].sort((a, b) => a - b);
|
|
1782
|
+
if (uncertainPids.length > 0) {
|
|
1783
|
+
return {
|
|
1784
|
+
state: "inconclusive",
|
|
1785
|
+
serverPids: [],
|
|
1786
|
+
staleFiles,
|
|
1787
|
+
inconclusivePids: uncertainPids,
|
|
1788
|
+
inconclusiveRecords
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
return { state: "stale", serverPids: [], staleFiles };
|
|
1792
|
+
}
|
|
1793
|
+
function createPiBlockingProcess(pid) {
|
|
1794
|
+
return attachFailClosedBlockingProcessEvidence({ kind: "Pi", pid }, readProcessProbeEvidence(pid));
|
|
1795
|
+
}
|
|
1796
|
+
function formatInconclusiveOpenCodeMigrationWarning(dbPath, pids) {
|
|
1797
|
+
return `[magic-context] storage warning: continuing migration for ${dbPath}; OpenCode server PID ${pids.join(", ")} was not confirmed because its liveness or identity check could not run. This commonly means an OS sandbox denied kill(0) or ps. No live OpenCode server was confirmed.`;
|
|
1798
|
+
}
|
|
1799
|
+
function formatInconclusivePiMigrationWarning(dbPath, pids) {
|
|
1800
|
+
return `[magic-context] storage warning: continuing migration for ${dbPath}; Pi/OMP PID ${pids.join(", ")} was not confirmed as a live harness because the process image or command line was ambiguous. No live Pi harness was confirmed.`;
|
|
1801
|
+
}
|
|
1802
|
+
function logInconclusiveMigrationProbes(dbPath, discovery, piDiscovery) {
|
|
1803
|
+
const uncertainPids = discovery.inconclusivePids ?? [];
|
|
1804
|
+
if (uncertainPids.length > 0) {
|
|
1805
|
+
log2(formatInconclusiveOpenCodeMigrationWarning(dbPath, uncertainPids));
|
|
1806
|
+
}
|
|
1807
|
+
if (piDiscovery.state === "unreadable") {
|
|
1808
|
+
log2(`[magic-context] storage warning: continuing migration for ${dbPath}; the Pi/OMP process-list probe could not run, which commonly means an OS sandbox denied ps. No live Pi harness was confirmed.`);
|
|
1809
|
+
} else if ((piDiscovery.inconclusivePids?.length ?? 0) > 0) {
|
|
1810
|
+
log2(formatInconclusivePiMigrationWarning(dbPath, piDiscovery.inconclusivePids ?? []));
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
function isDefaultSharedDatabasePath(dbPath) {
|
|
1814
|
+
if (!process.env.XDG_DATA_HOME && (process.env.MAGIC_CONTEXT_TEST_DATA_DIR || false)) {
|
|
1815
|
+
return false;
|
|
1816
|
+
}
|
|
1817
|
+
return resolve(dbPath) === resolve(join2(getMagicContextStorageDir(), "context.db"));
|
|
1818
|
+
}
|
|
1819
|
+
function migrationBlockingPiPids(dbPath, discovery, discoveredPiPids) {
|
|
1820
|
+
if (isDefaultSharedDatabasePath(dbPath))
|
|
1821
|
+
return [...discoveredPiPids];
|
|
1822
|
+
const sameDataDirPids = new Set(discovery.serverPids);
|
|
1823
|
+
return discoveredPiPids.filter((pid) => sameDataDirPids.has(pid));
|
|
1824
|
+
}
|
|
1825
|
+
function formatLiveProcessMigrationRefusal(dbPath, persistedVersion, latestSupportedVersion, serverPids, piPids) {
|
|
1826
|
+
const blockers = [
|
|
1827
|
+
...serverPids.map((pid) => `confirmed OpenCode server PID ${pid}`),
|
|
1828
|
+
...piPids.map((pid) => `confirmed Pi harness PID ${pid}`)
|
|
1829
|
+
];
|
|
1830
|
+
return `[magic-context] storage fatal: refusing to migrate ${dbPath} from upstream migration v${persistedVersion} to v${latestSupportedVersion} while ${blockers.join(", ")} still use the old plugin build. Restart the blocking harness, then retry this process.`;
|
|
1831
|
+
}
|
|
1832
|
+
function enforceMigrationOnOpenGuard(db, dbPath, dbDir, latestSupportedVersion, processes) {
|
|
1833
|
+
const persistedVersion = getPersistedSchemaVersion(db);
|
|
1834
|
+
if (persistedVersion >= latestSupportedVersion) {
|
|
1835
|
+
lastMigrationOnOpenRefusal = null;
|
|
1836
|
+
return true;
|
|
1837
|
+
}
|
|
1838
|
+
const discovery = inspectRpcServerDiscovery(dbDir, processes);
|
|
1839
|
+
const piDiscovery = processes?.pi ?? inspectLivePiProcesses();
|
|
1840
|
+
const piPids = migrationBlockingPiPids(dbPath, discovery, piDiscovery.processIds);
|
|
1841
|
+
const serverProcesses = discovery.serverProcesses ?? (discovery.state === "live" ? discovery.serverPids.map((pid) => ({ kind: "process", pid })) : []);
|
|
1842
|
+
const blockingProcesses = [
|
|
1843
|
+
...serverProcesses,
|
|
1844
|
+
...piPids.map((pid) => processes ? attachFailClosedBlockingProcessEvidence({ kind: "Pi", pid }, processes.evidence(pid)) : createPiBlockingProcess(pid))
|
|
1845
|
+
];
|
|
1846
|
+
if ((discovery.state === "absent" || discovery.state === "stale" || discovery.state === "inconclusive") && piPids.length === 0) {
|
|
1847
|
+
lastMigrationOnOpenRefusal = null;
|
|
1848
|
+
logInconclusiveMigrationProbes(dbPath, discovery, piDiscovery);
|
|
1849
|
+
const uncertainPids = discovery.inconclusivePids ?? [];
|
|
1850
|
+
if (uncertainPids.length > 0) {
|
|
1851
|
+
lastUnconfirmedMigrationHolders = {
|
|
1852
|
+
pids: [...uncertainPids],
|
|
1853
|
+
fromVersion: persistedVersion,
|
|
1854
|
+
toVersion: latestSupportedVersion
|
|
1855
|
+
};
|
|
1856
|
+
}
|
|
1857
|
+
return true;
|
|
1858
|
+
}
|
|
1859
|
+
const blockingPids = [...new Set([...discovery.serverPids, ...piPids])].sort((left, right) => left - right);
|
|
1860
|
+
lastMigrationOnOpenRefusal = {
|
|
1861
|
+
persistedVersion,
|
|
1862
|
+
supportedVersion: latestSupportedVersion,
|
|
1863
|
+
serverPids: blockingPids,
|
|
1864
|
+
blockingProcesses,
|
|
1865
|
+
...discovery.unreadableFile ? { unreadableFile: discovery.unreadableFile } : {},
|
|
1866
|
+
...discovery.unreadableArm ? { unreadableArm: discovery.unreadableArm } : {}
|
|
1867
|
+
};
|
|
1868
|
+
if (discovery.state === "unreadable") {
|
|
1869
|
+
const unreadableFile = discovery.unreadableFile ?? "<unknown>";
|
|
1870
|
+
const arm = discovery.unreadableArm ?? "io";
|
|
1871
|
+
const recovery = arm === "io" ? `If no OpenCode server is running, it is safe to delete ${unreadableFile} and retry.` : `Retry after the file is older than the ten-minute grace window, or stop OpenCode before deleting it.`;
|
|
1872
|
+
log2(`[magic-context] storage fatal: refusing to migrate ${dbPath} from upstream migration v${persistedVersion} to v${latestSupportedVersion} because RPC discovery file ${unreadableFile} is uncertain (${arm} arm), so the absence of a live OpenCode server cannot be proven. ${recovery}`);
|
|
1873
|
+
} else {
|
|
1874
|
+
log2(formatLiveProcessMigrationRefusal(dbPath, persistedVersion, latestSupportedVersion, discovery.serverPids, piPids));
|
|
1875
|
+
}
|
|
1876
|
+
return false;
|
|
1877
|
+
}
|
|
1878
|
+
var sqlitePragmaConfig = {
|
|
1879
|
+
cacheSizeMb: 64,
|
|
1880
|
+
mmapSizeMb: 0
|
|
1881
|
+
};
|
|
1882
|
+
function setSqlitePragmaConfig(config) {
|
|
1883
|
+
sqlitePragmaConfig = config;
|
|
1884
|
+
}
|
|
1885
|
+
function applySqliteTuningPragmas(db) {
|
|
1886
|
+
db.exec(`PRAGMA cache_size=-${Math.round(sqlitePragmaConfig.cacheSizeMb * 1024)}`);
|
|
1887
|
+
db.exec(`PRAGMA mmap_size=${Math.round(sqlitePragmaConfig.mmapSizeMb * 1024 * 1024)}`);
|
|
1888
|
+
db.exec("PRAGMA analysis_limit=400");
|
|
1889
|
+
}
|
|
1890
|
+
function runSqliteOptimize(db) {
|
|
1891
|
+
try {
|
|
1892
|
+
db.exec("PRAGMA analysis_limit=400");
|
|
1893
|
+
db.exec("PRAGMA optimize");
|
|
1894
|
+
} catch {}
|
|
1895
|
+
}
|
|
1896
|
+
function finishDatabaseOpen(db, dbPath, explicitDbPath, latestSupportedVersion) {
|
|
1897
|
+
if (!enforceSchemaFence(db, dbPath, latestSupportedVersion)) {
|
|
1898
|
+
closeQuietly(db);
|
|
1899
|
+
return null;
|
|
1900
|
+
}
|
|
1901
|
+
healWedgedChannel2Claims(db);
|
|
1902
|
+
if (!explicitDbPath) {
|
|
1903
|
+
const readsOpenCodeStore = harnessOwnsOpenCodeStore2();
|
|
1904
|
+
let busyRetryMs = 100;
|
|
1905
|
+
const runBackfills = () => withoutSqliteTransformPass2(() => withSqliteBackgroundWriter2(() => {
|
|
1906
|
+
if (readsOpenCodeStore) {
|
|
1907
|
+
try {
|
|
1908
|
+
runToolOwnerBackfill(db);
|
|
1909
|
+
} catch (error) {
|
|
1910
|
+
log2(`[magic-context] tool-owner backfill failed (continuing with lazy adoption fallback): ${getErrorMessage(error)}`);
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
startMessageFtsRowidMapBackfill(db).then(async () => {
|
|
1914
|
+
if (!readsOpenCodeStore)
|
|
1915
|
+
return;
|
|
1916
|
+
const { readRawSessionMessagePage, readRawSessionMessages } = await importPluginModule(() => import("./read-session-chunk-rh4khdkv.js"));
|
|
1917
|
+
const { startMessageTimeBackfill } = await importPluginModule(() => import("./message-time-backfill-gtx1pwk1.js"));
|
|
1918
|
+
await startMessageTimeBackfill(db, Object.assign(readRawSessionMessages, {
|
|
1919
|
+
readPage: readRawSessionMessagePage
|
|
1920
|
+
}));
|
|
1921
|
+
}).catch((error) => {
|
|
1922
|
+
if (isTransientSqliteError2(error)) {
|
|
1923
|
+
withoutSqliteTransformPass2(() => {
|
|
1924
|
+
const timer = setTimeout(runBackfills, busyRetryMs);
|
|
1925
|
+
timer.unref();
|
|
1926
|
+
});
|
|
1927
|
+
busyRetryMs = Math.min(30000, busyRetryMs * 2);
|
|
1928
|
+
return;
|
|
1929
|
+
}
|
|
1930
|
+
log2(`[magic-context] message-index backfill failed (will resume next startup): ${getErrorMessage(error)}`);
|
|
1931
|
+
});
|
|
1932
|
+
}));
|
|
1933
|
+
if (bootQuietRemainingMs() > 0)
|
|
1934
|
+
scheduleAfterBootQuiet(runBackfills);
|
|
1935
|
+
else
|
|
1936
|
+
runBackfills();
|
|
1937
|
+
}
|
|
1938
|
+
setDatabase(db);
|
|
1939
|
+
loadToolDefinitionMeasurements(db);
|
|
1940
|
+
tightenStorageTree(dirname(dbPath));
|
|
1941
|
+
databases.set(dbPath, db);
|
|
1942
|
+
pathByDatabase.set(db, dbPath);
|
|
1943
|
+
persistenceByDatabase.set(db, true);
|
|
1944
|
+
persistenceErrorByDatabase.delete(db);
|
|
1945
|
+
if (!explicitDbPath) {
|
|
1946
|
+
log2(formatSchemaFenceBootLog(getPersistedSchemaVersion(db), latestSupportedVersion));
|
|
1947
|
+
}
|
|
1948
|
+
return db;
|
|
1949
|
+
}
|
|
1950
|
+
function initializeDatabase(db, busyTimeoutMs = BOOT_SQLITE_BUSY_TIMEOUT_MS) {
|
|
1951
|
+
db.exec(`PRAGMA busy_timeout=${resolveBootBusyTimeoutMs(busyTimeoutMs)}`);
|
|
1952
|
+
db.exec("PRAGMA foreign_keys=ON");
|
|
1953
|
+
configureContextDatabasePragmas2(db);
|
|
1954
|
+
applySqliteTuningPragmas(db);
|
|
1955
|
+
db.exec(`
|
|
1956
|
+
CREATE TABLE IF NOT EXISTS tags (
|
|
1957
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1958
|
+
session_id TEXT,
|
|
1959
|
+
message_id TEXT,
|
|
1960
|
+
type TEXT,
|
|
1961
|
+
status TEXT DEFAULT 'active',
|
|
1962
|
+
byte_size INTEGER,
|
|
1963
|
+
tag_number INTEGER,
|
|
1964
|
+
harness TEXT NOT NULL DEFAULT 'opencode',
|
|
1965
|
+
entry_fingerprint TEXT,
|
|
1966
|
+
token_count INTEGER,
|
|
1967
|
+
input_token_count INTEGER,
|
|
1968
|
+
reasoning_token_count INTEGER,
|
|
1969
|
+
UNIQUE(session_id, tag_number)
|
|
1970
|
+
);
|
|
1971
|
+
|
|
1972
|
+
CREATE TABLE IF NOT EXISTS pending_ops (
|
|
1973
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1974
|
+
session_id TEXT,
|
|
1975
|
+
tag_id INTEGER,
|
|
1976
|
+
operation TEXT,
|
|
1977
|
+
queued_at INTEGER,
|
|
1978
|
+
harness TEXT NOT NULL DEFAULT 'opencode'
|
|
1979
|
+
);
|
|
1980
|
+
|
|
1981
|
+
CREATE TABLE IF NOT EXISTS source_contents (
|
|
1982
|
+
tag_id INTEGER,
|
|
1983
|
+
session_id TEXT,
|
|
1984
|
+
content TEXT,
|
|
1985
|
+
created_at INTEGER, -- epoch ms; Date.now() on source writes, preserved on session clones
|
|
1986
|
+
harness TEXT NOT NULL DEFAULT 'opencode',
|
|
1987
|
+
PRIMARY KEY(session_id, tag_id)
|
|
1988
|
+
);
|
|
1989
|
+
|
|
1990
|
+
CREATE TABLE IF NOT EXISTS compartments (
|
|
1991
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1992
|
+
session_id TEXT NOT NULL,
|
|
1993
|
+
sequence INTEGER NOT NULL,
|
|
1994
|
+
start_message INTEGER NOT NULL,
|
|
1995
|
+
end_message INTEGER NOT NULL,
|
|
1996
|
+
start_message_id TEXT DEFAULT '',
|
|
1997
|
+
end_message_id TEXT DEFAULT '',
|
|
1998
|
+
start_block_index INTEGER,
|
|
1999
|
+
end_block_index INTEGER,
|
|
2000
|
+
title TEXT NOT NULL,
|
|
2001
|
+
content TEXT NOT NULL,
|
|
2002
|
+
p1 TEXT,
|
|
2003
|
+
p2 TEXT,
|
|
2004
|
+
p3 TEXT,
|
|
2005
|
+
p4 TEXT,
|
|
2006
|
+
importance INTEGER NOT NULL DEFAULT 50,
|
|
2007
|
+
episode_type TEXT,
|
|
2008
|
+
p1_embedding BLOB,
|
|
2009
|
+
p1_embedding_model_id TEXT,
|
|
2010
|
+
legacy INTEGER NOT NULL DEFAULT 0,
|
|
2011
|
+
created_at INTEGER NOT NULL, -- epoch ms (Date.now())
|
|
2012
|
+
harness TEXT NOT NULL DEFAULT 'opencode',
|
|
2013
|
+
UNIQUE(session_id, sequence)
|
|
2014
|
+
);
|
|
2015
|
+
|
|
2016
|
+
CREATE TABLE IF NOT EXISTS compartment_chunk_embeddings (
|
|
2017
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2018
|
+
compartment_id INTEGER NOT NULL REFERENCES compartments(id) ON DELETE CASCADE,
|
|
2019
|
+
session_id TEXT NOT NULL,
|
|
2020
|
+
project_path TEXT NOT NULL,
|
|
2021
|
+
harness TEXT NOT NULL DEFAULT 'opencode',
|
|
2022
|
+
window_index INTEGER NOT NULL DEFAULT 0,
|
|
2023
|
+
start_ordinal INTEGER NOT NULL,
|
|
2024
|
+
end_ordinal INTEGER NOT NULL,
|
|
2025
|
+
chunk_hash TEXT NOT NULL,
|
|
2026
|
+
model_id TEXT NOT NULL,
|
|
2027
|
+
dims INTEGER NOT NULL,
|
|
2028
|
+
vector BLOB NOT NULL,
|
|
2029
|
+
created_at INTEGER NOT NULL, -- epoch ms (Date.now())
|
|
2030
|
+
UNIQUE(compartment_id, model_id, window_index)
|
|
2031
|
+
);
|
|
2032
|
+
CREATE INDEX IF NOT EXISTS idx_cce_session ON compartment_chunk_embeddings(session_id);
|
|
2033
|
+
CREATE INDEX IF NOT EXISTS idx_cce_project_model ON compartment_chunk_embeddings(project_path, model_id);
|
|
2034
|
+
|
|
2035
|
+
CREATE TABLE IF NOT EXISTS session_projects (
|
|
2036
|
+
session_id TEXT NOT NULL,
|
|
2037
|
+
harness TEXT NOT NULL DEFAULT 'opencode',
|
|
2038
|
+
project_path TEXT NOT NULL,
|
|
2039
|
+
updated_at INTEGER NOT NULL,
|
|
2040
|
+
PRIMARY KEY(session_id, harness)
|
|
2041
|
+
);
|
|
2042
|
+
CREATE INDEX IF NOT EXISTS idx_session_projects_project
|
|
2043
|
+
ON session_projects(project_path);
|
|
2044
|
+
|
|
2045
|
+
CREATE TABLE IF NOT EXISTS compartment_events (
|
|
2046
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2047
|
+
session_id TEXT NOT NULL,
|
|
2048
|
+
compartment_id INTEGER,
|
|
2049
|
+
kind TEXT NOT NULL,
|
|
2050
|
+
at_compartment INTEGER,
|
|
2051
|
+
fields_json TEXT NOT NULL DEFAULT '{}',
|
|
2052
|
+
created_at INTEGER NOT NULL, -- epoch ms (Date.now())
|
|
2053
|
+
harness TEXT NOT NULL DEFAULT 'opencode'
|
|
2054
|
+
);
|
|
2055
|
+
CREATE INDEX IF NOT EXISTS idx_compartment_events_session
|
|
2056
|
+
ON compartment_events(session_id);
|
|
2057
|
+
|
|
2058
|
+
CREATE TABLE IF NOT EXISTS compartment_state_lease (
|
|
2059
|
+
session_id TEXT PRIMARY KEY NOT NULL,
|
|
2060
|
+
holder_id TEXT NOT NULL,
|
|
2061
|
+
owner_pid INTEGER,
|
|
2062
|
+
acquired_at INTEGER NOT NULL,
|
|
2063
|
+
expires_at INTEGER NOT NULL
|
|
2064
|
+
);
|
|
2065
|
+
CREATE INDEX IF NOT EXISTS idx_compartment_state_lease_expires
|
|
2066
|
+
ON compartment_state_lease(expires_at);
|
|
2067
|
+
|
|
2068
|
+
CREATE TABLE IF NOT EXISTS compression_depth (
|
|
2069
|
+
session_id TEXT NOT NULL,
|
|
2070
|
+
message_ordinal INTEGER NOT NULL,
|
|
2071
|
+
depth INTEGER NOT NULL DEFAULT 0,
|
|
2072
|
+
harness TEXT NOT NULL DEFAULT 'opencode',
|
|
2073
|
+
PRIMARY KEY(session_id, message_ordinal)
|
|
2074
|
+
);
|
|
2075
|
+
|
|
2076
|
+
CREATE TABLE IF NOT EXISTS session_facts (
|
|
2077
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2078
|
+
session_id TEXT NOT NULL,
|
|
2079
|
+
category TEXT NOT NULL,
|
|
2080
|
+
content TEXT NOT NULL,
|
|
2081
|
+
created_at INTEGER NOT NULL, -- epoch ms (Date.now())
|
|
2082
|
+
updated_at INTEGER NOT NULL,
|
|
2083
|
+
harness TEXT NOT NULL DEFAULT 'opencode'
|
|
2084
|
+
);
|
|
2085
|
+
|
|
2086
|
+
CREATE TABLE IF NOT EXISTS primer_candidates (
|
|
2087
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2088
|
+
project_path TEXT NOT NULL,
|
|
2089
|
+
harness TEXT NOT NULL DEFAULT 'opencode',
|
|
2090
|
+
session_id TEXT NOT NULL,
|
|
2091
|
+
question TEXT NOT NULL,
|
|
2092
|
+
normalized_question TEXT NOT NULL,
|
|
2093
|
+
source_compartment_start INTEGER,
|
|
2094
|
+
source_compartment_end INTEGER,
|
|
2095
|
+
source_start_message_id TEXT NOT NULL DEFAULT '',
|
|
2096
|
+
source_end_message_id TEXT NOT NULL DEFAULT '',
|
|
2097
|
+
source_message_time INTEGER NOT NULL,
|
|
2098
|
+
question_embedding BLOB,
|
|
2099
|
+
question_embedding_model_id TEXT,
|
|
2100
|
+
created_at INTEGER NOT NULL, -- epoch ms (Date.now())
|
|
2101
|
+
UNIQUE(project_path, harness, session_id, source_start_message_id, source_end_message_id)
|
|
2102
|
+
);
|
|
2103
|
+
CREATE INDEX IF NOT EXISTS idx_primer_candidates_project_time
|
|
2104
|
+
ON primer_candidates(project_path, source_message_time);
|
|
2105
|
+
CREATE INDEX IF NOT EXISTS idx_primer_candidates_session
|
|
2106
|
+
ON primer_candidates(session_id, harness);
|
|
2107
|
+
CREATE INDEX IF NOT EXISTS idx_primer_candidates_embedding_model
|
|
2108
|
+
ON primer_candidates(project_path, question_embedding_model_id);
|
|
2109
|
+
|
|
2110
|
+
CREATE TABLE IF NOT EXISTS primers (
|
|
2111
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2112
|
+
project_path TEXT NOT NULL,
|
|
2113
|
+
question TEXT NOT NULL,
|
|
2114
|
+
question_embedding BLOB,
|
|
2115
|
+
question_embedding_model_id TEXT,
|
|
2116
|
+
answer TEXT NOT NULL DEFAULT '',
|
|
2117
|
+
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'archived')),
|
|
2118
|
+
total_support INTEGER NOT NULL DEFAULT 0,
|
|
2119
|
+
last_observed_at INTEGER,
|
|
2120
|
+
answer_refreshed_at INTEGER,
|
|
2121
|
+
source_candidate_ids TEXT NOT NULL DEFAULT '[]',
|
|
2122
|
+
source_candidate_provenance TEXT,
|
|
2123
|
+
created_at INTEGER NOT NULL, -- epoch ms (Date.now())
|
|
2124
|
+
updated_at INTEGER NOT NULL
|
|
2125
|
+
);
|
|
2126
|
+
CREATE INDEX IF NOT EXISTS idx_primers_project_status_observed
|
|
2127
|
+
ON primers(project_path, status, last_observed_at DESC);
|
|
2128
|
+
CREATE INDEX IF NOT EXISTS idx_primers_embedding_model
|
|
2129
|
+
ON primers(project_path, question_embedding_model_id);
|
|
2130
|
+
|
|
2131
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS primers_fts USING fts5(
|
|
2132
|
+
question,
|
|
2133
|
+
answer,
|
|
2134
|
+
project_path UNINDEXED,
|
|
2135
|
+
content='primers',
|
|
2136
|
+
content_rowid='id',
|
|
2137
|
+
tokenize='porter unicode61'
|
|
2138
|
+
);
|
|
2139
|
+
|
|
2140
|
+
CREATE TRIGGER IF NOT EXISTS primers_ai AFTER INSERT ON primers BEGIN
|
|
2141
|
+
INSERT INTO primers_fts(rowid, question, answer, project_path)
|
|
2142
|
+
VALUES (new.id, new.question, new.answer, new.project_path);
|
|
2143
|
+
END;
|
|
2144
|
+
|
|
2145
|
+
CREATE TRIGGER IF NOT EXISTS primers_ad AFTER DELETE ON primers BEGIN
|
|
2146
|
+
INSERT INTO primers_fts(primers_fts, rowid, question, answer, project_path)
|
|
2147
|
+
VALUES ('delete', old.id, old.question, old.answer, old.project_path);
|
|
2148
|
+
END;
|
|
2149
|
+
|
|
2150
|
+
CREATE TRIGGER IF NOT EXISTS primers_au AFTER UPDATE ON primers BEGIN
|
|
2151
|
+
INSERT INTO primers_fts(primers_fts, rowid, question, answer, project_path)
|
|
2152
|
+
VALUES ('delete', old.id, old.question, old.answer, old.project_path);
|
|
2153
|
+
INSERT INTO primers_fts(rowid, question, answer, project_path)
|
|
2154
|
+
VALUES (new.id, new.question, new.answer, new.project_path);
|
|
2155
|
+
END;
|
|
2156
|
+
|
|
2157
|
+
-- session_notes and smart_notes were merged into the unified notes table
|
|
2158
|
+
-- by migration v1 (see features/magic-context/migrations.ts). The old tables
|
|
2159
|
+
-- are never recreated; fresh DBs create only notes, upgraded DBs have
|
|
2160
|
+
-- their old tables migrated and dropped by the migration runner.
|
|
2161
|
+
|
|
2162
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
2163
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2164
|
+
project_path TEXT NOT NULL,
|
|
2165
|
+
category TEXT NOT NULL,
|
|
2166
|
+
content TEXT NOT NULL,
|
|
2167
|
+
normalized_hash TEXT NOT NULL,
|
|
2168
|
+
importance INTEGER,
|
|
2169
|
+
scope TEXT NOT NULL DEFAULT 'project',
|
|
2170
|
+
shareable INTEGER NOT NULL DEFAULT 0,
|
|
2171
|
+
source_session_id TEXT,
|
|
2172
|
+
source_type TEXT DEFAULT 'historian',
|
|
2173
|
+
seen_count INTEGER DEFAULT 1,
|
|
2174
|
+
retrieval_count INTEGER DEFAULT 0,
|
|
2175
|
+
first_seen_at INTEGER NOT NULL,
|
|
2176
|
+
created_at INTEGER NOT NULL,
|
|
2177
|
+
updated_at INTEGER NOT NULL,
|
|
2178
|
+
last_seen_at INTEGER NOT NULL,
|
|
2179
|
+
last_retrieved_at INTEGER,
|
|
2180
|
+
status TEXT DEFAULT 'active',
|
|
2181
|
+
expires_at INTEGER,
|
|
2182
|
+
verification_status TEXT DEFAULT 'unverified',
|
|
2183
|
+
verified_at INTEGER,
|
|
2184
|
+
classified_at INTEGER,
|
|
2185
|
+
superseded_by_memory_id INTEGER,
|
|
2186
|
+
merged_from TEXT,
|
|
2187
|
+
metadata_json TEXT,
|
|
2188
|
+
mural_cue TEXT,
|
|
2189
|
+
mural_cue_hash TEXT,
|
|
2190
|
+
mural_cue_at INTEGER,
|
|
2191
|
+
mural_cue_rejection_count INTEGER NOT NULL DEFAULT 0,
|
|
2192
|
+
UNIQUE(project_path, category, normalized_hash)
|
|
2193
|
+
);
|
|
2194
|
+
|
|
2195
|
+
CREATE TABLE IF NOT EXISTS memory_embeddings (
|
|
2196
|
+
-- FK-cascade audit (v12): memory_embeddings.memory_id -> memories.id
|
|
2197
|
+
-- uses ON DELETE CASCADE, so SQLite PRAGMA foreign_keys must be ON on
|
|
2198
|
+
-- every connection and v12 cleans historical orphan rows.
|
|
2199
|
+
memory_id INTEGER NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
|
|
2200
|
+
embedding BLOB NOT NULL,
|
|
2201
|
+
model_id TEXT NOT NULL,
|
|
2202
|
+
PRIMARY KEY(memory_id, model_id)
|
|
2203
|
+
);
|
|
2204
|
+
|
|
2205
|
+
CREATE TABLE IF NOT EXISTS embedding_identity_active (
|
|
2206
|
+
project_path TEXT NOT NULL,
|
|
2207
|
+
scope TEXT NOT NULL CHECK(scope IN ('memory', 'commit', 'chunk')),
|
|
2208
|
+
model_id TEXT NOT NULL,
|
|
2209
|
+
last_active_at INTEGER NOT NULL,
|
|
2210
|
+
PRIMARY KEY(project_path, scope, model_id)
|
|
2211
|
+
);
|
|
2212
|
+
|
|
2213
|
+
CREATE TABLE IF NOT EXISTS embedding_registrations (
|
|
2214
|
+
project_path TEXT PRIMARY KEY,
|
|
2215
|
+
provider_identity TEXT NOT NULL DEFAULT '',
|
|
2216
|
+
model_id TEXT NOT NULL DEFAULT '',
|
|
2217
|
+
chunk_model_id TEXT NOT NULL DEFAULT '',
|
|
2218
|
+
fingerprint TEXT NOT NULL DEFAULT '',
|
|
2219
|
+
table_epoch INTEGER NOT NULL DEFAULT 0,
|
|
2220
|
+
dims INTEGER NOT NULL DEFAULT 0,
|
|
2221
|
+
provenance_json TEXT NOT NULL DEFAULT '{}',
|
|
2222
|
+
generation INTEGER NOT NULL DEFAULT 0,
|
|
2223
|
+
updated_at INTEGER NOT NULL DEFAULT 0
|
|
2224
|
+
);
|
|
2225
|
+
|
|
2226
|
+
CREATE TABLE IF NOT EXISTS synapse_batch_ledger (
|
|
2227
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2228
|
+
session_id TEXT NOT NULL,
|
|
2229
|
+
project_path TEXT NOT NULL DEFAULT '',
|
|
2230
|
+
scope TEXT NOT NULL DEFAULT '',
|
|
2231
|
+
manifest_json TEXT NOT NULL DEFAULT '{}',
|
|
2232
|
+
request_key TEXT NOT NULL DEFAULT '',
|
|
2233
|
+
job_id TEXT,
|
|
2234
|
+
cursor TEXT,
|
|
2235
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
2236
|
+
created_at INTEGER NOT NULL DEFAULT 0, -- epoch ms (Date.now())
|
|
2237
|
+
updated_at INTEGER NOT NULL DEFAULT 0,
|
|
2238
|
+
UNIQUE(session_id, request_key)
|
|
2239
|
+
);
|
|
2240
|
+
CREATE INDEX IF NOT EXISTS idx_synapse_batch_ledger_session
|
|
2241
|
+
ON synapse_batch_ledger(session_id, updated_at);
|
|
2242
|
+
|
|
2243
|
+
CREATE TABLE IF NOT EXISTS shadow_embedding_registrations (
|
|
2244
|
+
project_path TEXT NOT NULL,
|
|
2245
|
+
scope TEXT NOT NULL CHECK(scope IN ('memory', 'commit', 'chunk')),
|
|
2246
|
+
model_id TEXT NOT NULL,
|
|
2247
|
+
generation INTEGER NOT NULL DEFAULT 0,
|
|
2248
|
+
fingerprint TEXT NOT NULL DEFAULT '',
|
|
2249
|
+
table_epoch INTEGER NOT NULL DEFAULT 0,
|
|
2250
|
+
dims INTEGER NOT NULL DEFAULT 0,
|
|
2251
|
+
provenance_json TEXT NOT NULL DEFAULT '{}',
|
|
2252
|
+
updated_at INTEGER NOT NULL DEFAULT 0,
|
|
2253
|
+
PRIMARY KEY(project_path, scope, model_id)
|
|
2254
|
+
);
|
|
2255
|
+
|
|
2256
|
+
CREATE TABLE IF NOT EXISTS embedding_measurement_corpus (
|
|
2257
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2258
|
+
session_id TEXT NOT NULL,
|
|
2259
|
+
project_path TEXT NOT NULL DEFAULT '',
|
|
2260
|
+
dedup_key TEXT NOT NULL DEFAULT '',
|
|
2261
|
+
cohort_key TEXT NOT NULL DEFAULT '',
|
|
2262
|
+
query_text_hash TEXT NOT NULL DEFAULT '',
|
|
2263
|
+
primary_result_ids_json TEXT NOT NULL DEFAULT '[]',
|
|
2264
|
+
shadow_result_ids_json TEXT NOT NULL DEFAULT '[]',
|
|
2265
|
+
primary_latency_ms INTEGER,
|
|
2266
|
+
shadow_latency_ms INTEGER,
|
|
2267
|
+
primary_failed INTEGER NOT NULL DEFAULT 0,
|
|
2268
|
+
shadow_failed INTEGER NOT NULL DEFAULT 0,
|
|
2269
|
+
primary_model_id TEXT NOT NULL DEFAULT '',
|
|
2270
|
+
shadow_model_id TEXT NOT NULL DEFAULT '',
|
|
2271
|
+
primary_fingerprint TEXT NOT NULL DEFAULT '',
|
|
2272
|
+
shadow_fingerprint TEXT NOT NULL DEFAULT '',
|
|
2273
|
+
primary_epoch INTEGER NOT NULL DEFAULT 0,
|
|
2274
|
+
shadow_epoch INTEGER NOT NULL DEFAULT 0,
|
|
2275
|
+
corpus_hash TEXT NOT NULL DEFAULT '',
|
|
2276
|
+
coverage_json TEXT NOT NULL DEFAULT '{}',
|
|
2277
|
+
created_at INTEGER NOT NULL DEFAULT 0, -- epoch ms (Date.now())
|
|
2278
|
+
UNIQUE(dedup_key, cohort_key)
|
|
2279
|
+
);
|
|
2280
|
+
CREATE INDEX IF NOT EXISTS idx_embedding_measurement_session
|
|
2281
|
+
ON embedding_measurement_corpus(session_id, created_at);
|
|
2282
|
+
|
|
2283
|
+
CREATE TABLE IF NOT EXISTS memory_verifications (
|
|
2284
|
+
memory_id INTEGER NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
|
|
2285
|
+
file_path TEXT NOT NULL,
|
|
2286
|
+
-- verified_at=0 means "mapped (files known) but not yet content-verified".
|
|
2287
|
+
-- map-memories sets mapped_at + verified_at=0; verify sets verified_at=now.
|
|
2288
|
+
verified_at INTEGER NOT NULL,
|
|
2289
|
+
mapped_at INTEGER NOT NULL DEFAULT 0,
|
|
2290
|
+
-- Distinguishes mapper-authored independence from a host rejection fallback.
|
|
2291
|
+
mapping_origin TEXT NOT NULL DEFAULT 'mapper',
|
|
2292
|
+
PRIMARY KEY (memory_id, file_path)
|
|
2293
|
+
);
|
|
2294
|
+
CREATE INDEX IF NOT EXISTS idx_memory_verifications_memory ON memory_verifications(memory_id);
|
|
2295
|
+
|
|
2296
|
+
CREATE TABLE IF NOT EXISTS memory_mutation_log (
|
|
2297
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2298
|
+
project_path TEXT NOT NULL,
|
|
2299
|
+
mutation_type TEXT NOT NULL CHECK (mutation_type IN ('archive', 'delete', 'update', 'superseded')),
|
|
2300
|
+
target_memory_id INTEGER NOT NULL,
|
|
2301
|
+
superseded_by_id INTEGER,
|
|
2302
|
+
category TEXT,
|
|
2303
|
+
new_content TEXT,
|
|
2304
|
+
queued_at INTEGER NOT NULL
|
|
2305
|
+
);
|
|
2306
|
+
CREATE INDEX IF NOT EXISTS idx_memory_mutation_log_project
|
|
2307
|
+
ON memory_mutation_log(project_path, id);
|
|
2308
|
+
CREATE INDEX IF NOT EXISTS idx_memory_mutation_log_visibility
|
|
2309
|
+
ON memory_mutation_log(project_path, category, id, target_memory_id);
|
|
2310
|
+
CREATE INDEX IF NOT EXISTS idx_memory_mutation_log_target
|
|
2311
|
+
ON memory_mutation_log(project_path, target_memory_id, id);
|
|
2312
|
+
|
|
2313
|
+
CREATE TABLE IF NOT EXISTS dream_state (
|
|
2314
|
+
key TEXT PRIMARY KEY,
|
|
2315
|
+
value TEXT NOT NULL
|
|
2316
|
+
);
|
|
2317
|
+
|
|
2318
|
+
CREATE TABLE IF NOT EXISTS dream_queue (
|
|
2319
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2320
|
+
project_path TEXT NOT NULL,
|
|
2321
|
+
reason TEXT NOT NULL,
|
|
2322
|
+
enqueued_at INTEGER NOT NULL,
|
|
2323
|
+
started_at INTEGER,
|
|
2324
|
+
retry_count INTEGER DEFAULT 0
|
|
2325
|
+
);
|
|
2326
|
+
CREATE INDEX IF NOT EXISTS idx_dream_queue_project ON dream_queue(project_path);
|
|
2327
|
+
CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, enqueued_at);
|
|
2328
|
+
|
|
2329
|
+
CREATE TABLE IF NOT EXISTS dream_runs (
|
|
2330
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2331
|
+
project_path TEXT NOT NULL,
|
|
2332
|
+
started_at INTEGER NOT NULL,
|
|
2333
|
+
finished_at INTEGER NOT NULL,
|
|
2334
|
+
holder_id TEXT NOT NULL,
|
|
2335
|
+
tasks_json TEXT NOT NULL,
|
|
2336
|
+
tasks_succeeded INTEGER NOT NULL DEFAULT 0,
|
|
2337
|
+
tasks_failed INTEGER NOT NULL DEFAULT 0,
|
|
2338
|
+
smart_notes_surfaced INTEGER NOT NULL DEFAULT 0,
|
|
2339
|
+
smart_notes_pending INTEGER NOT NULL DEFAULT 0,
|
|
2340
|
+
memory_changes_json TEXT,
|
|
2341
|
+
parent_session_id TEXT
|
|
2342
|
+
);
|
|
2343
|
+
CREATE INDEX IF NOT EXISTS idx_dream_runs_project ON dream_runs(project_path, finished_at DESC);
|
|
2344
|
+
|
|
2345
|
+
CREATE TABLE IF NOT EXISTS task_schedule_state (
|
|
2346
|
+
project_path TEXT NOT NULL,
|
|
2347
|
+
task TEXT NOT NULL,
|
|
2348
|
+
last_run_at INTEGER,
|
|
2349
|
+
next_due_at INTEGER,
|
|
2350
|
+
schedule TEXT,
|
|
2351
|
+
last_status TEXT,
|
|
2352
|
+
last_error TEXT,
|
|
2353
|
+
last_checked_commit TEXT,
|
|
2354
|
+
last_broad_run_at INTEGER,
|
|
2355
|
+
retrospective_watermark_ms INTEGER,
|
|
2356
|
+
retry_count INTEGER NOT NULL DEFAULT 0,
|
|
2357
|
+
PRIMARY KEY (project_path, task)
|
|
2358
|
+
);
|
|
2359
|
+
CREATE INDEX IF NOT EXISTS idx_task_schedule_due ON task_schedule_state(next_due_at);
|
|
2360
|
+
|
|
2361
|
+
CREATE TABLE IF NOT EXISTS retrospective_processed_windows (
|
|
2362
|
+
project_path TEXT NOT NULL,
|
|
2363
|
+
window_key TEXT NOT NULL,
|
|
2364
|
+
processed_at INTEGER NOT NULL,
|
|
2365
|
+
PRIMARY KEY (project_path, window_key)
|
|
2366
|
+
);
|
|
2367
|
+
|
|
2368
|
+
CREATE TABLE IF NOT EXISTS project_key_files (
|
|
2369
|
+
project_path TEXT NOT NULL,
|
|
2370
|
+
path TEXT NOT NULL,
|
|
2371
|
+
content TEXT NOT NULL,
|
|
2372
|
+
content_hash TEXT NOT NULL,
|
|
2373
|
+
local_token_estimate INTEGER NOT NULL,
|
|
2374
|
+
generated_at INTEGER NOT NULL,
|
|
2375
|
+
generated_by_model TEXT,
|
|
2376
|
+
generation_config_hash TEXT NOT NULL,
|
|
2377
|
+
stale_reason TEXT,
|
|
2378
|
+
PRIMARY KEY (project_path, path)
|
|
2379
|
+
);
|
|
2380
|
+
CREATE INDEX IF NOT EXISTS idx_project_key_files_generated_at ON project_key_files(project_path, generated_at);
|
|
2381
|
+
|
|
2382
|
+
CREATE TABLE IF NOT EXISTS project_key_files_version (
|
|
2383
|
+
project_path TEXT PRIMARY KEY,
|
|
2384
|
+
version INTEGER NOT NULL DEFAULT 0
|
|
2385
|
+
);
|
|
2386
|
+
|
|
2387
|
+
CREATE TABLE IF NOT EXISTS schema_migrations_meta (
|
|
2388
|
+
key TEXT PRIMARY KEY,
|
|
2389
|
+
value TEXT NOT NULL
|
|
2390
|
+
);
|
|
2391
|
+
|
|
2392
|
+
CREATE TABLE IF NOT EXISTS project_state (
|
|
2393
|
+
project_path TEXT PRIMARY KEY,
|
|
2394
|
+
project_memory_epoch INTEGER NOT NULL DEFAULT 0,
|
|
2395
|
+
project_user_profile_version INTEGER NOT NULL DEFAULT 0,
|
|
2396
|
+
updated_at INTEGER NOT NULL DEFAULT 0
|
|
2397
|
+
);
|
|
2398
|
+
|
|
2399
|
+
CREATE TABLE IF NOT EXISTS git_sweep_coordinator (
|
|
2400
|
+
project_path TEXT PRIMARY KEY,
|
|
2401
|
+
lease_holder TEXT,
|
|
2402
|
+
lease_expires_at INTEGER,
|
|
2403
|
+
last_swept_at INTEGER
|
|
2404
|
+
);
|
|
2405
|
+
CREATE INDEX IF NOT EXISTS idx_git_sweep_coordinator_lease_expires
|
|
2406
|
+
ON git_sweep_coordinator(lease_expires_at);
|
|
2407
|
+
CREATE INDEX IF NOT EXISTS idx_git_sweep_coordinator_last_swept
|
|
2408
|
+
ON git_sweep_coordinator(last_swept_at);
|
|
2409
|
+
|
|
2410
|
+
CREATE TABLE IF NOT EXISTS m0_mutation_log (
|
|
2411
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2412
|
+
session_id TEXT NOT NULL,
|
|
2413
|
+
mutation_type TEXT NOT NULL CHECK (mutation_type IN (
|
|
2414
|
+
'compartment_delete', 'compartment_merge', 'recomp_boundary_change', 'compartment_upgrade'
|
|
2415
|
+
)),
|
|
2416
|
+
target_id INTEGER,
|
|
2417
|
+
queued_at INTEGER NOT NULL
|
|
2418
|
+
);
|
|
2419
|
+
CREATE INDEX IF NOT EXISTS idx_m0_mutation_log_session ON m0_mutation_log(session_id);
|
|
2420
|
+
|
|
2421
|
+
CREATE TABLE IF NOT EXISTS v22_identity_rekey_map (
|
|
2422
|
+
old_project_path TEXT PRIMARY KEY,
|
|
2423
|
+
new_project_path TEXT NOT NULL,
|
|
2424
|
+
rekeyed_at INTEGER NOT NULL
|
|
2425
|
+
);
|
|
2426
|
+
|
|
2427
|
+
CREATE TABLE IF NOT EXISTS workspaces (
|
|
2428
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2429
|
+
name TEXT NOT NULL UNIQUE,
|
|
2430
|
+
created_at INTEGER NOT NULL,
|
|
2431
|
+
updated_at INTEGER NOT NULL,
|
|
2432
|
+
share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'
|
|
2433
|
+
);
|
|
2434
|
+
|
|
2435
|
+
CREATE TABLE IF NOT EXISTS workspace_members (
|
|
2436
|
+
workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
2437
|
+
project_path TEXT NOT NULL,
|
|
2438
|
+
display_name TEXT NOT NULL,
|
|
2439
|
+
display_path TEXT NOT NULL,
|
|
2440
|
+
added_at INTEGER NOT NULL,
|
|
2441
|
+
PRIMARY KEY (workspace_id, project_path)
|
|
2442
|
+
);
|
|
2443
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_unique ON workspace_members(project_path);
|
|
2444
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_name ON workspace_members(workspace_id, display_name);
|
|
2445
|
+
|
|
2446
|
+
CREATE TABLE IF NOT EXISTS v22_backfill_failures (
|
|
2447
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2448
|
+
table_name TEXT NOT NULL,
|
|
2449
|
+
row_id INTEGER NOT NULL,
|
|
2450
|
+
raw_project_path TEXT NOT NULL,
|
|
2451
|
+
error_class TEXT NOT NULL CHECK (error_class IN ('not_git_repo', 'git_missing', 'git_timeout', 'permission_denied', 'unknown')),
|
|
2452
|
+
error_message TEXT,
|
|
2453
|
+
failed_at INTEGER NOT NULL,
|
|
2454
|
+
UNIQUE(table_name, row_id)
|
|
2455
|
+
);
|
|
2456
|
+
|
|
2457
|
+
-- (smart_notes: see note above; merged into unified notes table by migration v1)
|
|
2458
|
+
|
|
2459
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
2460
|
+
content,
|
|
2461
|
+
category,
|
|
2462
|
+
content='memories',
|
|
2463
|
+
content_rowid='id',
|
|
2464
|
+
tokenize='porter unicode61'
|
|
2465
|
+
);
|
|
2466
|
+
|
|
2467
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS message_history_fts USING fts5(
|
|
2468
|
+
session_id UNINDEXED,
|
|
2469
|
+
message_ordinal UNINDEXED,
|
|
2470
|
+
message_id UNINDEXED,
|
|
2471
|
+
role,
|
|
2472
|
+
content,
|
|
2473
|
+
tokenize='porter unicode61'
|
|
2474
|
+
);
|
|
2475
|
+
|
|
2476
|
+
CREATE TABLE IF NOT EXISTS message_fts_rowid_map (
|
|
2477
|
+
session_id TEXT NOT NULL,
|
|
2478
|
+
message_ordinal INTEGER NOT NULL,
|
|
2479
|
+
fts_rowid INTEGER NOT NULL,
|
|
2480
|
+
message_time_ms INTEGER,
|
|
2481
|
+
PRIMARY KEY(session_id, message_ordinal)
|
|
2482
|
+
);
|
|
2483
|
+
|
|
2484
|
+
CREATE TABLE IF NOT EXISTS message_fts_rowid_map_backfill_state (
|
|
2485
|
+
id INTEGER PRIMARY KEY CHECK(id = 1),
|
|
2486
|
+
watermark_rowid INTEGER NOT NULL DEFAULT 0,
|
|
2487
|
+
completed INTEGER NOT NULL DEFAULT 0 CHECK(completed IN (0, 1)),
|
|
2488
|
+
updated_at INTEGER NOT NULL DEFAULT 0
|
|
2489
|
+
);
|
|
2490
|
+
INSERT OR IGNORE INTO message_fts_rowid_map_backfill_state
|
|
2491
|
+
(id, watermark_rowid, completed, updated_at)
|
|
2492
|
+
VALUES (1, 0, 0, 0);
|
|
2493
|
+
|
|
2494
|
+
CREATE TABLE IF NOT EXISTS message_time_backfill_state (
|
|
2495
|
+
id INTEGER PRIMARY KEY CHECK(id = 1),
|
|
2496
|
+
cursor_session_id TEXT NOT NULL DEFAULT '',
|
|
2497
|
+
cursor_ordinal INTEGER NOT NULL DEFAULT 0,
|
|
2498
|
+
completed INTEGER NOT NULL DEFAULT 0 CHECK(completed IN (0, 1)),
|
|
2499
|
+
updated_at INTEGER NOT NULL DEFAULT 0
|
|
2500
|
+
);
|
|
2501
|
+
INSERT OR IGNORE INTO message_time_backfill_state
|
|
2502
|
+
(id, cursor_session_id, cursor_ordinal, completed, updated_at)
|
|
2503
|
+
VALUES (1, '', 0, 0, 0);
|
|
2504
|
+
|
|
2505
|
+
CREATE TABLE IF NOT EXISTS single_store_state (
|
|
2506
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
2507
|
+
state TEXT NOT NULL CHECK (state IN ('required', 'migrated')),
|
|
2508
|
+
migrated_at INTEGER,
|
|
2509
|
+
migrated_by TEXT,
|
|
2510
|
+
backup_dir TEXT,
|
|
2511
|
+
report_json TEXT
|
|
2512
|
+
);
|
|
2513
|
+
INSERT OR IGNORE INTO single_store_state(id, state) VALUES (1, 'required');
|
|
2514
|
+
|
|
2515
|
+
-- Highest memory id another writer (the Rust module in single-store mode) put
|
|
2516
|
+
-- into memories for a project, and how far this host has embedded. Migration v91.
|
|
2517
|
+
CREATE TABLE IF NOT EXISTS memory_embedding_watermarks (
|
|
2518
|
+
project_path TEXT PRIMARY KEY,
|
|
2519
|
+
written_memory_id INTEGER NOT NULL DEFAULT 0,
|
|
2520
|
+
embedded_memory_id INTEGER NOT NULL DEFAULT 0,
|
|
2521
|
+
updated_at INTEGER NOT NULL DEFAULT 0
|
|
2522
|
+
);
|
|
2523
|
+
|
|
2524
|
+
-- Last-known-good replay slots, their prefix slices, and trailing-blank replay
|
|
2525
|
+
-- decisions, in the layout of migration v94.
|
|
2526
|
+
${LKG_SLOTS_DDL}
|
|
2527
|
+
${LKG_SLOT_CHUNKS_DDL}
|
|
2528
|
+
${SESSION_REPLAY_DECISIONS_DDL}
|
|
2529
|
+
|
|
2530
|
+
CREATE TABLE IF NOT EXISTS message_history_index (
|
|
2531
|
+
session_id TEXT PRIMARY KEY,
|
|
2532
|
+
last_indexed_ordinal INTEGER NOT NULL DEFAULT 0,
|
|
2533
|
+
dirty_floor_ordinal INTEGER NOT NULL DEFAULT 0,
|
|
2534
|
+
updated_at INTEGER NOT NULL,
|
|
2535
|
+
harness TEXT NOT NULL DEFAULT 'opencode'
|
|
2536
|
+
);
|
|
2537
|
+
|
|
2538
|
+
CREATE TABLE IF NOT EXISTS message_history_source (
|
|
2539
|
+
session_id TEXT NOT NULL,
|
|
2540
|
+
message_id TEXT NOT NULL,
|
|
2541
|
+
message_ordinal INTEGER NOT NULL,
|
|
2542
|
+
source_version TEXT NOT NULL,
|
|
2543
|
+
normalized_content_hash TEXT NOT NULL,
|
|
2544
|
+
role TEXT NOT NULL,
|
|
2545
|
+
harness TEXT NOT NULL DEFAULT 'opencode',
|
|
2546
|
+
updated_at INTEGER NOT NULL,
|
|
2547
|
+
PRIMARY KEY(session_id, message_id)
|
|
2548
|
+
);
|
|
2549
|
+
CREATE INDEX IF NOT EXISTS idx_message_history_source_session_ordinal
|
|
2550
|
+
ON message_history_source(session_id, message_ordinal);
|
|
2551
|
+
|
|
2552
|
+
CREATE TABLE IF NOT EXISTS pending_session_cleanup (
|
|
2553
|
+
session_id TEXT PRIMARY KEY,
|
|
2554
|
+
harness TEXT NOT NULL DEFAULT 'opencode',
|
|
2555
|
+
requested_at INTEGER NOT NULL,
|
|
2556
|
+
last_attempt_at INTEGER
|
|
2557
|
+
);
|
|
2558
|
+
|
|
2559
|
+
CREATE TABLE IF NOT EXISTS message_history_orphan_sweep (
|
|
2560
|
+
harness TEXT PRIMARY KEY,
|
|
2561
|
+
cursor_session_id TEXT NOT NULL DEFAULT '',
|
|
2562
|
+
last_swept_at INTEGER
|
|
2563
|
+
);
|
|
2564
|
+
|
|
2565
|
+
CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
|
|
2566
|
+
INSERT INTO memories_fts(rowid, content, category) VALUES (new.id, new.content, new.category);
|
|
2567
|
+
END;
|
|
2568
|
+
|
|
2569
|
+
CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
|
|
2570
|
+
INSERT INTO memories_fts(memories_fts, rowid, content, category) VALUES ('delete', old.id, old.content, old.category);
|
|
2571
|
+
END;
|
|
2572
|
+
|
|
2573
|
+
CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
|
|
2574
|
+
INSERT INTO memories_fts(memories_fts, rowid, content, category) VALUES ('delete', old.id, old.content, old.category);
|
|
2575
|
+
INSERT INTO memories_fts(rowid, content, category) VALUES (new.id, new.content, new.category);
|
|
2576
|
+
END;
|
|
2577
|
+
|
|
2578
|
+
CREATE TABLE IF NOT EXISTS session_meta (
|
|
2579
|
+
session_id TEXT PRIMARY KEY,
|
|
2580
|
+
harness TEXT NOT NULL DEFAULT 'opencode',
|
|
2581
|
+
last_response_time INTEGER,
|
|
2582
|
+
cache_ttl TEXT,
|
|
2583
|
+
counter INTEGER DEFAULT 0,
|
|
2584
|
+
tags_version INTEGER NOT NULL DEFAULT 0,
|
|
2585
|
+
last_nudge_tokens INTEGER DEFAULT 0,
|
|
2586
|
+
last_nudge_band TEXT DEFAULT '',
|
|
2587
|
+
last_nudge_undropped INTEGER DEFAULT 0,
|
|
2588
|
+
last_nudge_level TEXT DEFAULT '',
|
|
2589
|
+
channel2_nudge_state TEXT DEFAULT '',
|
|
2590
|
+
channel2_nudge_claimed_at INTEGER DEFAULT 0,
|
|
2591
|
+
channel2_nudge_claim_token TEXT DEFAULT '',
|
|
2592
|
+
last_emergency_input_sample INTEGER DEFAULT 0,
|
|
2593
|
+
last_transform_error TEXT DEFAULT '',
|
|
2594
|
+
nudge_anchor_message_id TEXT DEFAULT '',
|
|
2595
|
+
nudge_anchor_text TEXT DEFAULT '',
|
|
2596
|
+
sticky_turn_reminder_text TEXT DEFAULT '',
|
|
2597
|
+
sticky_turn_reminder_message_id TEXT DEFAULT '',
|
|
2598
|
+
note_nudge_trigger_pending INTEGER DEFAULT 0,
|
|
2599
|
+
note_nudge_trigger_message_id TEXT DEFAULT '',
|
|
2600
|
+
note_nudge_sticky_text TEXT DEFAULT '',
|
|
2601
|
+
note_nudge_sticky_message_id TEXT DEFAULT '',
|
|
2602
|
+
note_nudge_anchors TEXT NOT NULL DEFAULT '[]',
|
|
2603
|
+
auto_search_hint_decisions TEXT NOT NULL DEFAULT '[]',
|
|
2604
|
+
last_todo_state TEXT DEFAULT '',
|
|
2605
|
+
todo_permission_denied INTEGER NOT NULL DEFAULT 2,
|
|
2606
|
+
todo_synthetic_call_id TEXT DEFAULT '',
|
|
2607
|
+
todo_synthetic_anchor_message_id TEXT DEFAULT '',
|
|
2608
|
+
todo_synthetic_state_json TEXT DEFAULT '',
|
|
2609
|
+
is_subagent INTEGER DEFAULT 0,
|
|
2610
|
+
last_context_percentage REAL DEFAULT 0,
|
|
2611
|
+
last_input_tokens INTEGER DEFAULT 0,
|
|
2612
|
+
detected_context_limit_provenance TEXT NOT NULL DEFAULT 'unknown',
|
|
2613
|
+
observed_safe_input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
2614
|
+
cache_alert_sent INTEGER NOT NULL DEFAULT 0,
|
|
2615
|
+
times_execute_threshold_reached INTEGER DEFAULT 0,
|
|
2616
|
+
compartment_in_progress INTEGER DEFAULT 0,
|
|
2617
|
+
historian_failure_count INTEGER DEFAULT 0,
|
|
2618
|
+
historian_last_error TEXT DEFAULT NULL,
|
|
2619
|
+
historian_last_failure_at INTEGER DEFAULT NULL,
|
|
2620
|
+
system_prompt_hash TEXT DEFAULT '',
|
|
2621
|
+
memory_block_cache TEXT DEFAULT '',
|
|
2622
|
+
memory_block_count INTEGER DEFAULT 0,
|
|
2623
|
+
memory_block_ids TEXT DEFAULT '',
|
|
2624
|
+
-- pending_compaction_marker_state: intentionally NULLABLE without a
|
|
2625
|
+
-- default. Absence of a deferred marker is SQL NULL; presence is a
|
|
2626
|
+
-- valid JSON blob written via setPendingCompactionMarkerState.
|
|
2627
|
+
-- Excluded from the healAllNullColumns fallback list. Readers filter
|
|
2628
|
+
-- IS NOT NULL AND != empty-string defensively. Plan v6 section 3.
|
|
2629
|
+
pending_compaction_marker_state TEXT,
|
|
2630
|
+
-- Target OpenCode message id used to inject the current compaction marker.
|
|
2631
|
+
-- Nullable for legacy persisted markers; repaired on the next marker move.
|
|
2632
|
+
compaction_marker_target_end_message_id TEXT,
|
|
2633
|
+
-- pending_pi_compaction_marker_state: intentionally NULLABLE without a
|
|
2634
|
+
-- default. Absence of a deferred Pi-native marker is SQL NULL; presence
|
|
2635
|
+
-- is a valid JSON blob written via setPendingPiCompactionMarkerState.
|
|
2636
|
+
-- Excluded from the healAllNullColumns fallback list.
|
|
2637
|
+
pending_pi_compaction_marker_state TEXT,
|
|
2638
|
+
new_work_tokens INTEGER NOT NULL DEFAULT 0,
|
|
2639
|
+
total_input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
2640
|
+
-- Retired columns remain in place so existing databases keep the same schema:
|
|
2641
|
+
-- deferred_execute_state was used by the removed turn-boundary execute hold.
|
|
2642
|
+
deferred_execute_state TEXT,
|
|
2643
|
+
cached_m0_bytes BLOB,
|
|
2644
|
+
cached_m0_project_memory_epoch INTEGER,
|
|
2645
|
+
cached_m0_workspace_fingerprint TEXT,
|
|
2646
|
+
cached_m0_project_user_profile_version INTEGER,
|
|
2647
|
+
cached_m0_max_compartment_seq INTEGER,
|
|
2648
|
+
cached_m0_max_memory_id INTEGER,
|
|
2649
|
+
cached_m0_max_mutation_id INTEGER,
|
|
2650
|
+
cached_m0_max_memory_mutation_id INTEGER,
|
|
2651
|
+
cached_m0_project_docs_hash TEXT,
|
|
2652
|
+
cached_m1_bytes BLOB,
|
|
2653
|
+
last_observed_model_key TEXT,
|
|
2654
|
+
last_usage_context_limit INTEGER NOT NULL DEFAULT 0,
|
|
2655
|
+
prior_boundary_ordinal INTEGER NOT NULL DEFAULT 1,
|
|
2656
|
+
protected_tokens_effective INTEGER,
|
|
2657
|
+
protected_tokens_pre_snapshot TEXT,
|
|
2658
|
+
protected_tail_policy_version INTEGER NOT NULL DEFAULT 0,
|
|
2659
|
+
protected_tail_drain_window_started_at INTEGER NOT NULL DEFAULT 0,
|
|
2660
|
+
protected_tail_drain_tokens INTEGER NOT NULL DEFAULT 0,
|
|
2661
|
+
recovery_no_eligible_head_count INTEGER NOT NULL DEFAULT 0,
|
|
2662
|
+
force_emergency_bypass_window_start INTEGER NOT NULL DEFAULT 0,
|
|
2663
|
+
force_emergency_bypass_used INTEGER NOT NULL DEFAULT 0,
|
|
2664
|
+
emergency_drain_active INTEGER NOT NULL DEFAULT 0,
|
|
2665
|
+
historian_drain_failure_at INTEGER NOT NULL DEFAULT 0,
|
|
2666
|
+
wrapup_in_progress_state TEXT,
|
|
2667
|
+
compaction_mode_record TEXT,
|
|
2668
|
+
cached_m0_materialized_at INTEGER,
|
|
2669
|
+
cached_m0_session_facts_version INTEGER,
|
|
2670
|
+
cached_m0_upgrade_state TEXT,
|
|
2671
|
+
cached_m0_system_hash TEXT,
|
|
2672
|
+
cached_m0_tool_set_hash TEXT,
|
|
2673
|
+
cached_m0_model_key TEXT,
|
|
2674
|
+
cached_m0_project_identity TEXT,
|
|
2675
|
+
cached_m0_last_baseline_end_message_id TEXT,
|
|
2676
|
+
thinking_binding_recovery_target TEXT NOT NULL DEFAULT '',
|
|
2677
|
+
upgrade_reminded_at INTEGER,
|
|
2678
|
+
pi_stable_id_scheme INTEGER
|
|
2679
|
+
);
|
|
2680
|
+
|
|
2681
|
+
CREATE TABLE IF NOT EXISTS tool_owner_backfill_state (
|
|
2682
|
+
session_id TEXT PRIMARY KEY,
|
|
2683
|
+
status TEXT NOT NULL CHECK (status IN ('pending', 'running', 'completed', 'skipped')),
|
|
2684
|
+
started_at INTEGER,
|
|
2685
|
+
lease_expires_at INTEGER,
|
|
2686
|
+
completed_at INTEGER,
|
|
2687
|
+
last_error TEXT
|
|
2688
|
+
);
|
|
2689
|
+
CREATE INDEX IF NOT EXISTS idx_tool_owner_backfill_state_status
|
|
2690
|
+
ON tool_owner_backfill_state(status);
|
|
2691
|
+
|
|
2692
|
+
CREATE TABLE IF NOT EXISTS subagent_invocations (
|
|
2693
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2694
|
+
session_id TEXT NOT NULL,
|
|
2695
|
+
harness TEXT NOT NULL,
|
|
2696
|
+
subagent TEXT NOT NULL,
|
|
2697
|
+
task TEXT,
|
|
2698
|
+
provider_id TEXT,
|
|
2699
|
+
model_id TEXT,
|
|
2700
|
+
started_at INTEGER NOT NULL,
|
|
2701
|
+
ended_at INTEGER,
|
|
2702
|
+
status TEXT NOT NULL,
|
|
2703
|
+
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
2704
|
+
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
2705
|
+
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
|
|
2706
|
+
cache_write_tokens INTEGER NOT NULL DEFAULT 0,
|
|
2707
|
+
error TEXT,
|
|
2708
|
+
parent_invocation_id INTEGER
|
|
2709
|
+
);
|
|
2710
|
+
CREATE INDEX IF NOT EXISTS idx_sai_session_started
|
|
2711
|
+
ON subagent_invocations(session_id, started_at DESC);
|
|
2712
|
+
CREATE INDEX IF NOT EXISTS idx_sai_subagent
|
|
2713
|
+
ON subagent_invocations(subagent, started_at DESC);
|
|
2714
|
+
|
|
2715
|
+
CREATE TABLE IF NOT EXISTS historian_runs (
|
|
2716
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2717
|
+
session_id TEXT NOT NULL,
|
|
2718
|
+
harness TEXT NOT NULL DEFAULT 'opencode',
|
|
2719
|
+
subagent_invocation_id INTEGER,
|
|
2720
|
+
run_kind TEXT NOT NULL,
|
|
2721
|
+
status TEXT NOT NULL,
|
|
2722
|
+
failure_reason TEXT,
|
|
2723
|
+
chunk_start_ordinal INTEGER,
|
|
2724
|
+
chunk_end_ordinal INTEGER,
|
|
2725
|
+
unprocessed_from INTEGER,
|
|
2726
|
+
compartments_produced INTEGER NOT NULL DEFAULT 0,
|
|
2727
|
+
compartment_id_min INTEGER,
|
|
2728
|
+
compartment_id_max INTEGER,
|
|
2729
|
+
facts_emitted INTEGER NOT NULL DEFAULT 0,
|
|
2730
|
+
facts_by_category_json TEXT,
|
|
2731
|
+
events_emitted INTEGER NOT NULL DEFAULT 0,
|
|
2732
|
+
importance_min INTEGER,
|
|
2733
|
+
importance_max INTEGER,
|
|
2734
|
+
importance_avg REAL,
|
|
2735
|
+
discarded_last INTEGER NOT NULL DEFAULT 0,
|
|
2736
|
+
legacy INTEGER NOT NULL DEFAULT 0,
|
|
2737
|
+
created_at INTEGER NOT NULL -- epoch ms (Date.now())
|
|
2738
|
+
);
|
|
2739
|
+
CREATE INDEX IF NOT EXISTS idx_historian_runs_session
|
|
2740
|
+
ON historian_runs(session_id, created_at DESC);
|
|
2741
|
+
CREATE INDEX IF NOT EXISTS idx_historian_runs_status
|
|
2742
|
+
ON historian_runs(status, created_at DESC);
|
|
2743
|
+
|
|
2744
|
+
CREATE TABLE IF NOT EXISTS transform_decisions (
|
|
2745
|
+
session_id TEXT NOT NULL,
|
|
2746
|
+
harness TEXT NOT NULL DEFAULT 'opencode',
|
|
2747
|
+
message_id TEXT NOT NULL,
|
|
2748
|
+
ts_ms INTEGER NOT NULL,
|
|
2749
|
+
decision TEXT NOT NULL,
|
|
2750
|
+
materialized INTEGER NOT NULL DEFAULT 0,
|
|
2751
|
+
materialize_reason TEXT,
|
|
2752
|
+
system_hash_prev TEXT,
|
|
2753
|
+
system_hash_new TEXT,
|
|
2754
|
+
m0_tool_set_hash_prev TEXT,
|
|
2755
|
+
m0_tool_set_hash_new TEXT,
|
|
2756
|
+
m0_model_key_prev TEXT,
|
|
2757
|
+
m0_model_key_new TEXT,
|
|
2758
|
+
emergency INTEGER NOT NULL DEFAULT 0,
|
|
2759
|
+
dropped_tokens INTEGER NOT NULL DEFAULT 0,
|
|
2760
|
+
dropped_count INTEGER NOT NULL DEFAULT 0,
|
|
2761
|
+
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
2762
|
+
PRIMARY KEY (session_id, harness, message_id)
|
|
2763
|
+
);
|
|
2764
|
+
|
|
2765
|
+
CREATE INDEX IF NOT EXISTS idx_tags_session_message_id ON tags(session_id, message_id);
|
|
2766
|
+
|
|
2767
|
+
-- Clone/import paths can write tags before session bootstrap. Keep trigger-created
|
|
2768
|
+
-- metadata rows aligned with the explicit defaults in ensureSessionMetaRow.
|
|
2769
|
+
CREATE TRIGGER IF NOT EXISTS tags_version_ai AFTER INSERT ON tags BEGIN
|
|
2770
|
+
INSERT INTO session_meta(
|
|
2771
|
+
session_id, harness, last_response_time, cache_ttl, counter, tags_version,
|
|
2772
|
+
last_nudge_tokens, last_nudge_band, last_transform_error, is_subagent,
|
|
2773
|
+
last_context_percentage, last_input_tokens, observed_safe_input_tokens,
|
|
2774
|
+
cache_alert_sent, times_execute_threshold_reached, compartment_in_progress,
|
|
2775
|
+
system_prompt_hash, cleared_reasoning_through_tag
|
|
2776
|
+
) VALUES(NEW.session_id, NEW.harness, 0, '5m', 0, 1, 0, '', '', 0, 0, 0, 0, 0, 0, 0, '', 0)
|
|
2777
|
+
ON CONFLICT(session_id) DO UPDATE SET tags_version = tags_version + 1;
|
|
2778
|
+
END;
|
|
2779
|
+
CREATE TRIGGER IF NOT EXISTS tags_version_ad AFTER DELETE ON tags BEGIN
|
|
2780
|
+
INSERT INTO session_meta(
|
|
2781
|
+
session_id, harness, last_response_time, cache_ttl, counter, tags_version,
|
|
2782
|
+
last_nudge_tokens, last_nudge_band, last_transform_error, is_subagent,
|
|
2783
|
+
last_context_percentage, last_input_tokens, observed_safe_input_tokens,
|
|
2784
|
+
cache_alert_sent, times_execute_threshold_reached, compartment_in_progress,
|
|
2785
|
+
system_prompt_hash, cleared_reasoning_through_tag
|
|
2786
|
+
) VALUES(OLD.session_id, OLD.harness, 0, '5m', 0, 1, 0, '', '', 0, 0, 0, 0, 0, 0, 0, '', 0)
|
|
2787
|
+
ON CONFLICT(session_id) DO UPDATE SET tags_version = tags_version + 1;
|
|
2788
|
+
END;
|
|
2789
|
+
CREATE TRIGGER IF NOT EXISTS tags_version_au
|
|
2790
|
+
AFTER UPDATE OF session_id, message_id, tag_number, type, tool_owner_message_id, status ON tags BEGIN
|
|
2791
|
+
INSERT INTO session_meta(
|
|
2792
|
+
session_id, harness, last_response_time, cache_ttl, counter, tags_version,
|
|
2793
|
+
last_nudge_tokens, last_nudge_band, last_transform_error, is_subagent,
|
|
2794
|
+
last_context_percentage, last_input_tokens, observed_safe_input_tokens,
|
|
2795
|
+
cache_alert_sent, times_execute_threshold_reached, compartment_in_progress,
|
|
2796
|
+
system_prompt_hash, cleared_reasoning_through_tag
|
|
2797
|
+
) VALUES(OLD.session_id, OLD.harness, 0, '5m', 0, 1, 0, '', '', 0, 0, 0, 0, 0, 0, 0, '', 0)
|
|
2798
|
+
ON CONFLICT(session_id) DO UPDATE SET tags_version = tags_version + 1;
|
|
2799
|
+
INSERT INTO session_meta(
|
|
2800
|
+
session_id, harness, last_response_time, cache_ttl, counter, tags_version,
|
|
2801
|
+
last_nudge_tokens, last_nudge_band, last_transform_error, is_subagent,
|
|
2802
|
+
last_context_percentage, last_input_tokens, observed_safe_input_tokens,
|
|
2803
|
+
cache_alert_sent, times_execute_threshold_reached, compartment_in_progress,
|
|
2804
|
+
system_prompt_hash, cleared_reasoning_through_tag
|
|
2805
|
+
)
|
|
2806
|
+
SELECT NEW.session_id, NEW.harness, 0, '5m', 0, 1, 0, '', '', 0, 0, 0, 0, 0, 0, 0, '', 0
|
|
2807
|
+
WHERE NEW.session_id != OLD.session_id
|
|
2808
|
+
ON CONFLICT(session_id) DO UPDATE SET tags_version = tags_version + 1;
|
|
2809
|
+
END;
|
|
2810
|
+
CREATE INDEX IF NOT EXISTS idx_pending_ops_session_tag_id ON pending_ops(session_id, tag_id);
|
|
2811
|
+
|
|
2812
|
+
CREATE TABLE IF NOT EXISTS recomp_compartments (
|
|
2813
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2814
|
+
session_id TEXT NOT NULL,
|
|
2815
|
+
sequence INTEGER NOT NULL,
|
|
2816
|
+
start_message INTEGER NOT NULL,
|
|
2817
|
+
end_message INTEGER NOT NULL,
|
|
2818
|
+
start_message_id TEXT DEFAULT '',
|
|
2819
|
+
end_message_id TEXT DEFAULT '',
|
|
2820
|
+
start_block_index INTEGER,
|
|
2821
|
+
end_block_index INTEGER,
|
|
2822
|
+
title TEXT NOT NULL,
|
|
2823
|
+
content TEXT NOT NULL,
|
|
2824
|
+
p1 TEXT,
|
|
2825
|
+
p2 TEXT,
|
|
2826
|
+
p3 TEXT,
|
|
2827
|
+
p4 TEXT,
|
|
2828
|
+
importance INTEGER NOT NULL DEFAULT 50,
|
|
2829
|
+
episode_type TEXT,
|
|
2830
|
+
pass_number INTEGER NOT NULL,
|
|
2831
|
+
created_at INTEGER NOT NULL, -- epoch ms (Date.now())
|
|
2832
|
+
harness TEXT NOT NULL DEFAULT 'opencode',
|
|
2833
|
+
UNIQUE(session_id, sequence)
|
|
2834
|
+
);
|
|
2835
|
+
|
|
2836
|
+
CREATE TABLE IF NOT EXISTS recomp_facts (
|
|
2837
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2838
|
+
session_id TEXT NOT NULL,
|
|
2839
|
+
category TEXT NOT NULL,
|
|
2840
|
+
content TEXT NOT NULL,
|
|
2841
|
+
pass_number INTEGER NOT NULL,
|
|
2842
|
+
created_at INTEGER NOT NULL, -- epoch ms (Date.now())
|
|
2843
|
+
harness TEXT NOT NULL DEFAULT 'opencode'
|
|
2844
|
+
);
|
|
2845
|
+
|
|
2846
|
+
CREATE INDEX IF NOT EXISTS idx_session_facts_session ON session_facts(session_id);
|
|
2847
|
+
CREATE INDEX IF NOT EXISTS idx_recomp_compartments_session ON recomp_compartments(session_id);
|
|
2848
|
+
CREATE INDEX IF NOT EXISTS idx_recomp_facts_session ON recomp_facts(session_id);
|
|
2849
|
+
CREATE INDEX IF NOT EXISTS idx_memories_project_status_category ON memories(project_path, status, category);
|
|
2850
|
+
CREATE INDEX IF NOT EXISTS idx_memories_project_status_expires ON memories(project_path, status, expires_at);
|
|
2851
|
+
CREATE INDEX IF NOT EXISTS idx_memories_project_category_hash ON memories(project_path, category, normalized_hash);
|
|
2852
|
+
CREATE INDEX IF NOT EXISTS idx_message_history_index_updated_at ON message_history_index(updated_at);
|
|
2853
|
+
`);
|
|
2854
|
+
installCompartmentHistoryVersions(db);
|
|
2855
|
+
ensureColumn(db, "message_fts_rowid_map", "message_time_ms", "INTEGER");
|
|
2856
|
+
db.exec(`
|
|
2857
|
+
CREATE INDEX IF NOT EXISTS idx_message_fts_rowid_map_session_time
|
|
2858
|
+
ON message_fts_rowid_map(session_id, message_time_ms);
|
|
2859
|
+
`);
|
|
2860
|
+
ensureColumn(db, "primer_candidates", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
|
|
2861
|
+
ensureColumn(db, "primer_candidates", "source_start_message_id", "TEXT NOT NULL DEFAULT ''");
|
|
2862
|
+
ensureColumn(db, "primer_candidates", "source_end_message_id", "TEXT NOT NULL DEFAULT ''");
|
|
2863
|
+
ensureColumn(db, "primer_candidates", "question_embedding", "BLOB");
|
|
2864
|
+
ensureColumn(db, "primer_candidates", "question_embedding_model_id", "TEXT");
|
|
2865
|
+
ensureColumn(db, "primers", "question_embedding_model_id", "TEXT");
|
|
2866
|
+
ensureColumn(db, "primers", "source_candidate_provenance", "TEXT");
|
|
2867
|
+
const hasUserMemoriesTable = db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'user_memories'").get();
|
|
2868
|
+
if (hasUserMemoriesTable) {
|
|
2869
|
+
ensureColumn(db, "user_memories", "source_candidate_provenance", "TEXT");
|
|
2870
|
+
}
|
|
2871
|
+
db.exec(`
|
|
2872
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_primer_candidates_occurrence
|
|
2873
|
+
ON primer_candidates(project_path, harness, session_id, source_start_message_id, source_end_message_id);
|
|
2874
|
+
CREATE INDEX IF NOT EXISTS idx_primer_candidates_project_time
|
|
2875
|
+
ON primer_candidates(project_path, source_message_time);
|
|
2876
|
+
CREATE INDEX IF NOT EXISTS idx_primer_candidates_session
|
|
2877
|
+
ON primer_candidates(session_id, harness);
|
|
2878
|
+
CREATE INDEX IF NOT EXISTS idx_primer_candidates_embedding_model
|
|
2879
|
+
ON primer_candidates(project_path, question_embedding_model_id);
|
|
2880
|
+
CREATE INDEX IF NOT EXISTS idx_primers_project_status_observed
|
|
2881
|
+
ON primers(project_path, status, last_observed_at DESC);
|
|
2882
|
+
CREATE INDEX IF NOT EXISTS idx_primers_embedding_model
|
|
2883
|
+
ON primers(project_path, question_embedding_model_id);
|
|
2884
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS primers_fts USING fts5(
|
|
2885
|
+
question,
|
|
2886
|
+
answer,
|
|
2887
|
+
project_path UNINDEXED,
|
|
2888
|
+
content='primers',
|
|
2889
|
+
content_rowid='id',
|
|
2890
|
+
tokenize='porter unicode61'
|
|
2891
|
+
);
|
|
2892
|
+
CREATE TRIGGER IF NOT EXISTS primers_ai AFTER INSERT ON primers BEGIN
|
|
2893
|
+
INSERT INTO primers_fts(rowid, question, answer, project_path)
|
|
2894
|
+
VALUES (new.id, new.question, new.answer, new.project_path);
|
|
2895
|
+
END;
|
|
2896
|
+
CREATE TRIGGER IF NOT EXISTS primers_ad AFTER DELETE ON primers BEGIN
|
|
2897
|
+
INSERT INTO primers_fts(primers_fts, rowid, question, answer, project_path)
|
|
2898
|
+
VALUES ('delete', old.id, old.question, old.answer, old.project_path);
|
|
2899
|
+
END;
|
|
2900
|
+
CREATE TRIGGER IF NOT EXISTS primers_au AFTER UPDATE ON primers BEGIN
|
|
2901
|
+
INSERT INTO primers_fts(primers_fts, rowid, question, answer, project_path)
|
|
2902
|
+
VALUES ('delete', old.id, old.question, old.answer, old.project_path);
|
|
2903
|
+
INSERT INTO primers_fts(rowid, question, answer, project_path)
|
|
2904
|
+
VALUES (new.id, new.question, new.answer, new.project_path);
|
|
2905
|
+
END;
|
|
2906
|
+
`);
|
|
2907
|
+
ensureColumn(db, "session_meta", "last_nudge_band", "TEXT DEFAULT ''");
|
|
2908
|
+
ensureColumn(db, "session_meta", "last_nudge_undropped", "INTEGER DEFAULT 0");
|
|
2909
|
+
ensureColumn(db, "session_meta", "last_nudge_level", "TEXT DEFAULT ''");
|
|
2910
|
+
ensureColumn(db, "session_meta", "channel2_nudge_state", "TEXT DEFAULT ''");
|
|
2911
|
+
ensureColumn(db, "session_meta", "channel2_nudge_claimed_at", "INTEGER DEFAULT 0");
|
|
2912
|
+
ensureColumn(db, "session_meta", "channel2_nudge_claim_token", "TEXT DEFAULT ''");
|
|
2913
|
+
ensureColumn(db, "session_meta", "last_emergency_input_sample", "INTEGER DEFAULT 0");
|
|
2914
|
+
ensureColumn(db, "session_meta", "last_transform_error", "TEXT DEFAULT ''");
|
|
2915
|
+
ensureColumn(db, "session_meta", "nudge_anchor_message_id", "TEXT DEFAULT ''");
|
|
2916
|
+
ensureColumn(db, "session_meta", "nudge_anchor_text", "TEXT DEFAULT ''");
|
|
2917
|
+
ensureColumn(db, "session_meta", "sticky_turn_reminder_text", "TEXT DEFAULT ''");
|
|
2918
|
+
ensureColumn(db, "session_meta", "sticky_turn_reminder_message_id", "TEXT DEFAULT ''");
|
|
2919
|
+
ensureColumn(db, "session_meta", "note_nudge_trigger_pending", "INTEGER DEFAULT 0");
|
|
2920
|
+
ensureColumn(db, "session_meta", "note_nudge_trigger_message_id", "TEXT DEFAULT ''");
|
|
2921
|
+
ensureColumn(db, "session_meta", "note_nudge_sticky_text", "TEXT DEFAULT ''");
|
|
2922
|
+
ensureColumn(db, "session_meta", "note_nudge_sticky_message_id", "TEXT DEFAULT ''");
|
|
2923
|
+
ensureColumn(db, "session_meta", "note_nudge_anchors", "TEXT NOT NULL DEFAULT '[]'");
|
|
2924
|
+
ensureColumn(db, "session_meta", "auto_search_hint_decisions", "TEXT NOT NULL DEFAULT '[]'");
|
|
2925
|
+
ensureColumn(db, "session_meta", "last_todo_state", "TEXT DEFAULT ''");
|
|
2926
|
+
ensureColumn(db, "session_meta", "todo_permission_denied", "INTEGER NOT NULL DEFAULT 2");
|
|
2927
|
+
ensureColumn(db, "session_meta", "todo_synthetic_call_id", "TEXT DEFAULT ''");
|
|
2928
|
+
ensureColumn(db, "session_meta", "todo_synthetic_anchor_message_id", "TEXT DEFAULT ''");
|
|
2929
|
+
ensureColumn(db, "session_meta", "todo_synthetic_state_json", "TEXT DEFAULT ''");
|
|
2930
|
+
ensureColumn(db, "session_meta", "note_last_read_at", "INTEGER DEFAULT 0");
|
|
2931
|
+
ensureColumn(db, "session_meta", "times_execute_threshold_reached", "INTEGER DEFAULT 0");
|
|
2932
|
+
ensureColumn(db, "session_meta", "observed_safe_input_tokens", "INTEGER NOT NULL DEFAULT 0");
|
|
2933
|
+
ensureColumn(db, "session_meta", "cache_alert_sent", "INTEGER NOT NULL DEFAULT 0");
|
|
2934
|
+
ensureColumn(db, "session_meta", "compartment_in_progress", "INTEGER DEFAULT 0");
|
|
2935
|
+
ensureColumn(db, "session_meta", "historian_failure_count", "INTEGER DEFAULT 0");
|
|
2936
|
+
ensureColumn(db, "session_meta", "historian_last_error", "TEXT DEFAULT NULL");
|
|
2937
|
+
ensureColumn(db, "session_meta", "historian_last_failure_at", "INTEGER DEFAULT NULL");
|
|
2938
|
+
ensureColumn(db, "session_meta", "system_prompt_hash", "TEXT DEFAULT ''");
|
|
2939
|
+
ensureColumn(db, "session_meta", "cleared_reasoning_through_tag", "INTEGER DEFAULT 0");
|
|
2940
|
+
ensureColumn(db, "session_meta", "tags_version", "INTEGER NOT NULL DEFAULT 0");
|
|
2941
|
+
ensureColumn(db, "session_meta", "tool_reclaim_watermark", "INTEGER DEFAULT 0");
|
|
2942
|
+
ensureColumn(db, "session_meta", "stripped_placeholder_ids", "TEXT DEFAULT ''");
|
|
2943
|
+
ensureColumn(db, "session_meta", "stale_reduce_stripped_ids", "TEXT DEFAULT ''");
|
|
2944
|
+
ensureColumn(db, "session_meta", "processed_image_stripped_ids", "TEXT DEFAULT ''");
|
|
2945
|
+
ensureColumn(db, "session_meta", "merged_reasoning_stripped_ids", "TEXT DEFAULT ''");
|
|
2946
|
+
ensureColumn(db, "session_meta", "thinking_binding_recovery_target", "TEXT DEFAULT ''");
|
|
2947
|
+
ensureColumn(db, "session_meta", "trailing_blank_decisions", "TEXT DEFAULT ''");
|
|
2948
|
+
ensureColumn(db, "compartment_state_lease", "owner_pid", "INTEGER");
|
|
2949
|
+
ensureColumn(db, "compartments", "start_message_id", "TEXT DEFAULT ''");
|
|
2950
|
+
ensureColumn(db, "compartments", "end_message_id", "TEXT DEFAULT ''");
|
|
2951
|
+
ensureColumn(db, "memory_embeddings", "model_id", "TEXT");
|
|
2952
|
+
ensureColumn(db, "session_meta", "memory_block_cache", "TEXT DEFAULT ''");
|
|
2953
|
+
ensureColumn(db, "session_meta", "memory_block_count", "INTEGER DEFAULT 0");
|
|
2954
|
+
ensureColumn(db, "session_meta", "pi_stable_id_scheme", "INTEGER");
|
|
2955
|
+
ensureColumn(db, "session_meta", "memory_block_ids", "TEXT DEFAULT ''");
|
|
2956
|
+
ensureColumn(db, "dream_queue", "retry_count", "INTEGER DEFAULT 0");
|
|
2957
|
+
ensureColumn(db, "tags", "reasoning_byte_size", "INTEGER DEFAULT 0");
|
|
2958
|
+
ensureColumn(db, "tags", "drop_mode", "TEXT DEFAULT 'full'");
|
|
2959
|
+
ensureColumn(db, "tags", "tool_name", "TEXT");
|
|
2960
|
+
ensureColumn(db, "tags", "input_byte_size", "INTEGER DEFAULT 0");
|
|
2961
|
+
ensureColumn(db, "tags", "caveman_depth", "INTEGER DEFAULT 0");
|
|
2962
|
+
ensureColumn(db, "tags", "tool_owner_message_id", "TEXT DEFAULT NULL");
|
|
2963
|
+
ensureColumn(db, "tags", "entry_fingerprint", "TEXT");
|
|
2964
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_tags_pi_adopt
|
|
2965
|
+
ON tags(session_id, entry_fingerprint)
|
|
2966
|
+
WHERE type='message' AND entry_fingerprint IS NOT NULL`);
|
|
2967
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_tags_pi_fallback_tool_owner
|
|
2968
|
+
ON tags(session_id, tool_owner_message_id)
|
|
2969
|
+
WHERE type='tool'`);
|
|
2970
|
+
ensureColumn(db, "tags", "token_count", "INTEGER");
|
|
2971
|
+
ensureColumn(db, "tags", "input_token_count", "INTEGER");
|
|
2972
|
+
ensureColumn(db, "tags", "reasoning_token_count", "INTEGER");
|
|
2973
|
+
ensureColumn(db, "task_schedule_state", "schedule", "TEXT");
|
|
2974
|
+
ensureColumn(db, "task_schedule_state", "last_checked_commit", "TEXT");
|
|
2975
|
+
ensureColumn(db, "task_schedule_state", "last_broad_run_at", "INTEGER");
|
|
2976
|
+
ensureColumn(db, "task_schedule_state", "retrospective_watermark_ms", "INTEGER");
|
|
2977
|
+
ensureColumn(db, "dream_runs", "parent_session_id", "TEXT");
|
|
2978
|
+
ensureColumn(db, "session_meta", "system_prompt_tokens", "INTEGER DEFAULT 0");
|
|
2979
|
+
ensureColumn(db, "session_meta", "compaction_marker_state", "TEXT DEFAULT ''");
|
|
2980
|
+
ensureColumn(db, "session_meta", "compaction_marker_target_end_message_id", "TEXT");
|
|
2981
|
+
ensureColumn(db, "session_meta", "key_files", "TEXT DEFAULT ''");
|
|
2982
|
+
ensureColumn(db, "session_meta", "conversation_tokens", "INTEGER DEFAULT 0");
|
|
2983
|
+
ensureColumn(db, "session_meta", "tool_call_tokens", "INTEGER DEFAULT 0");
|
|
2984
|
+
ensureColumn(db, "session_meta", "recomp_partial_range_start", "INTEGER DEFAULT 0");
|
|
2985
|
+
ensureColumn(db, "session_meta", "recomp_partial_range_end", "INTEGER DEFAULT 0");
|
|
2986
|
+
ensureColumn(db, "session_meta", "detected_context_limit", "INTEGER DEFAULT 0");
|
|
2987
|
+
ensureColumn(db, "session_meta", "detected_context_limit_model_key", "TEXT");
|
|
2988
|
+
ensureColumn(db, "session_meta", "detected_context_limit_provenance", "TEXT NOT NULL DEFAULT 'unknown'");
|
|
2989
|
+
ensureColumn(db, "session_meta", "needs_emergency_recovery", "INTEGER DEFAULT 0");
|
|
2990
|
+
ensureColumn(db, "session_meta", "emergency_recovery_origin", "TEXT DEFAULT ''");
|
|
2991
|
+
ensureColumn(db, "session_meta", "pending_compaction_marker_state", "TEXT");
|
|
2992
|
+
ensureColumn(db, "session_meta", "pending_pi_compaction_marker_state", "TEXT");
|
|
2993
|
+
ensureColumn(db, "session_meta", "new_work_tokens", "INTEGER NOT NULL DEFAULT 0");
|
|
2994
|
+
ensureColumn(db, "session_meta", "total_input_tokens", "INTEGER NOT NULL DEFAULT 0");
|
|
2995
|
+
ensureColumn(db, "session_meta", "deferred_execute_state", "TEXT");
|
|
2996
|
+
ensureColumn(db, "compartments", "p1", "TEXT");
|
|
2997
|
+
ensureColumn(db, "compartments", "p2", "TEXT");
|
|
2998
|
+
ensureColumn(db, "compartments", "p3", "TEXT");
|
|
2999
|
+
ensureColumn(db, "compartments", "p4", "TEXT");
|
|
3000
|
+
ensureColumn(db, "compartments", "importance", "INTEGER NOT NULL DEFAULT 50");
|
|
3001
|
+
ensureColumn(db, "compartments", "episode_type", "TEXT");
|
|
3002
|
+
ensureColumn(db, "compartments", "p1_embedding", "BLOB");
|
|
3003
|
+
ensureColumn(db, "compartments", "p1_embedding_model_id", "TEXT");
|
|
3004
|
+
ensureColumn(db, "compartments", "legacy", "INTEGER NOT NULL DEFAULT 0");
|
|
3005
|
+
ensureColumn(db, "recomp_compartments", "p1", "TEXT");
|
|
3006
|
+
ensureColumn(db, "recomp_compartments", "p2", "TEXT");
|
|
3007
|
+
ensureColumn(db, "recomp_compartments", "p3", "TEXT");
|
|
3008
|
+
ensureColumn(db, "recomp_compartments", "p4", "TEXT");
|
|
3009
|
+
ensureColumn(db, "recomp_compartments", "importance", "INTEGER NOT NULL DEFAULT 50");
|
|
3010
|
+
ensureColumn(db, "recomp_compartments", "episode_type", "TEXT");
|
|
3011
|
+
ensureColumn(db, "memories", "importance", "INTEGER");
|
|
3012
|
+
ensureColumn(db, "memories", "classified_at", "INTEGER");
|
|
3013
|
+
ensureColumn(db, "memories", "mural_cue", "TEXT");
|
|
3014
|
+
ensureColumn(db, "memories", "mural_cue_hash", "TEXT");
|
|
3015
|
+
ensureColumn(db, "memories", "mural_cue_at", "INTEGER");
|
|
3016
|
+
ensureColumn(db, "memory_verifications", "mapped_at", "INTEGER NOT NULL DEFAULT 0");
|
|
3017
|
+
ensureColumn(db, "session_meta", "cached_m0_bytes", "BLOB");
|
|
3018
|
+
ensureColumn(db, "session_meta", "cached_m0_project_memory_epoch", "INTEGER");
|
|
3019
|
+
ensureColumn(db, "session_meta", "cached_m0_workspace_fingerprint", "TEXT");
|
|
3020
|
+
ensureColumn(db, "session_meta", "cached_m0_project_user_profile_version", "INTEGER");
|
|
3021
|
+
ensureColumn(db, "session_meta", "cached_m0_max_compartment_seq", "INTEGER");
|
|
3022
|
+
ensureColumn(db, "session_meta", "cached_m0_max_memory_id", "INTEGER");
|
|
3023
|
+
ensureColumn(db, "session_meta", "cached_m0_max_mutation_id", "INTEGER");
|
|
3024
|
+
ensureColumn(db, "session_meta", "cached_m0_max_memory_mutation_id", "INTEGER");
|
|
3025
|
+
ensureColumn(db, "session_meta", "cached_m0_project_docs_hash", "TEXT");
|
|
3026
|
+
ensureColumn(db, "session_meta", "cached_m1_bytes", "BLOB");
|
|
3027
|
+
ensureColumn(db, "session_meta", "last_observed_model_key", "TEXT");
|
|
3028
|
+
ensureColumn(db, "session_meta", "last_usage_context_limit", "INTEGER NOT NULL DEFAULT 0");
|
|
3029
|
+
ensureColumn(db, "session_meta", "prior_boundary_ordinal", "INTEGER NOT NULL DEFAULT 1");
|
|
3030
|
+
ensureColumn(db, "session_meta", "protected_tokens_effective", "INTEGER");
|
|
3031
|
+
ensureColumn(db, "session_meta", "protected_tokens_pre_snapshot", "TEXT");
|
|
3032
|
+
ensureColumn(db, "session_meta", "protected_tail_policy_version", "INTEGER NOT NULL DEFAULT 0");
|
|
3033
|
+
ensureColumn(db, "session_meta", "protected_tail_drain_window_started_at", "INTEGER NOT NULL DEFAULT 0");
|
|
3034
|
+
ensureColumn(db, "session_meta", "protected_tail_drain_tokens", "INTEGER NOT NULL DEFAULT 0");
|
|
3035
|
+
ensureColumn(db, "session_meta", "recovery_no_eligible_head_count", "INTEGER NOT NULL DEFAULT 0");
|
|
3036
|
+
ensureColumn(db, "session_meta", "force_emergency_bypass_window_start", "INTEGER NOT NULL DEFAULT 0");
|
|
3037
|
+
ensureColumn(db, "session_meta", "force_emergency_bypass_used", "INTEGER NOT NULL DEFAULT 0");
|
|
3038
|
+
ensureColumn(db, "session_meta", "emergency_drain_active", "INTEGER NOT NULL DEFAULT 0");
|
|
3039
|
+
ensureColumn(db, "session_meta", "historian_drain_failure_at", "INTEGER NOT NULL DEFAULT 0");
|
|
3040
|
+
ensureColumn(db, "session_meta", "wrapup_in_progress_state", "TEXT");
|
|
3041
|
+
ensureColumn(db, "session_meta", "compaction_mode_record", "TEXT");
|
|
3042
|
+
ensureColumn(db, "session_meta", "cached_m0_materialized_at", "INTEGER");
|
|
3043
|
+
ensureColumn(db, "session_meta", "cached_m0_session_facts_version", "INTEGER");
|
|
3044
|
+
ensureColumn(db, "session_meta", "cached_m0_upgrade_state", "TEXT");
|
|
3045
|
+
ensureColumn(db, "session_meta", "cached_m0_system_hash", "TEXT");
|
|
3046
|
+
ensureColumn(db, "session_meta", "cached_m0_tool_set_hash", "TEXT");
|
|
3047
|
+
ensureColumn(db, "session_meta", "cached_m0_model_key", "TEXT");
|
|
3048
|
+
ensureColumn(db, "session_meta", "cached_m0_project_identity", "TEXT");
|
|
3049
|
+
ensureColumn(db, "session_meta", "cached_m0_last_baseline_end_message_id", "TEXT");
|
|
3050
|
+
ensureColumn(db, "session_meta", "upgrade_reminded_at", "INTEGER");
|
|
3051
|
+
ensureColumn(db, "session_meta", "upgrade_reminder_last_sent_at", "INTEGER");
|
|
3052
|
+
ensureColumn(db, "session_meta", "upgrade_reminder_count", "INTEGER NOT NULL DEFAULT 0");
|
|
3053
|
+
ensureColumn(db, "session_meta", "cached_m0_mural_data_url", "TEXT");
|
|
3054
|
+
ensureColumn(db, "session_meta", "cached_m0_mural_hash", "TEXT");
|
|
3055
|
+
ensureColumn(db, "session_meta", "coordinate_generation", "TEXT");
|
|
3056
|
+
ensureColumn(db, "session_meta", "coordinate_rebase_notice", "TEXT");
|
|
3057
|
+
ensureColumn(db, "compartments", "rebase_status", "TEXT NOT NULL DEFAULT 'ok'");
|
|
3058
|
+
ensureColumn(db, "recomp_compartments", "rebase_status", "TEXT NOT NULL DEFAULT 'ok'");
|
|
3059
|
+
db.exec(`
|
|
3060
|
+
CREATE TABLE IF NOT EXISTS project_state (
|
|
3061
|
+
project_path TEXT PRIMARY KEY,
|
|
3062
|
+
project_memory_epoch INTEGER NOT NULL DEFAULT 0,
|
|
3063
|
+
project_user_profile_version INTEGER NOT NULL DEFAULT 0,
|
|
3064
|
+
updated_at INTEGER NOT NULL DEFAULT 0
|
|
3065
|
+
);
|
|
3066
|
+
CREATE TABLE IF NOT EXISTS session_projects (
|
|
3067
|
+
session_id TEXT NOT NULL,
|
|
3068
|
+
harness TEXT NOT NULL DEFAULT 'opencode',
|
|
3069
|
+
project_path TEXT NOT NULL,
|
|
3070
|
+
updated_at INTEGER NOT NULL,
|
|
3071
|
+
PRIMARY KEY(session_id, harness)
|
|
3072
|
+
);
|
|
3073
|
+
CREATE INDEX IF NOT EXISTS idx_session_projects_project
|
|
3074
|
+
ON session_projects(project_path);
|
|
3075
|
+
CREATE TABLE IF NOT EXISTS m0_mutation_log (
|
|
3076
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
3077
|
+
session_id TEXT NOT NULL,
|
|
3078
|
+
mutation_type TEXT NOT NULL CHECK (mutation_type IN (
|
|
3079
|
+
'compartment_delete', 'compartment_merge', 'recomp_boundary_change', 'compartment_upgrade'
|
|
3080
|
+
)),
|
|
3081
|
+
target_id INTEGER,
|
|
3082
|
+
queued_at INTEGER NOT NULL
|
|
3083
|
+
);
|
|
3084
|
+
CREATE INDEX IF NOT EXISTS idx_m0_mutation_log_session ON m0_mutation_log(session_id);
|
|
3085
|
+
CREATE TABLE IF NOT EXISTS memory_mutation_log (
|
|
3086
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
3087
|
+
project_path TEXT NOT NULL,
|
|
3088
|
+
mutation_type TEXT NOT NULL CHECK (mutation_type IN ('archive', 'delete', 'update', 'superseded')),
|
|
3089
|
+
target_memory_id INTEGER NOT NULL,
|
|
3090
|
+
superseded_by_id INTEGER,
|
|
3091
|
+
category TEXT,
|
|
3092
|
+
new_content TEXT,
|
|
3093
|
+
queued_at INTEGER NOT NULL
|
|
3094
|
+
);
|
|
3095
|
+
CREATE INDEX IF NOT EXISTS idx_memory_mutation_log_project
|
|
3096
|
+
ON memory_mutation_log(project_path, id);
|
|
3097
|
+
CREATE TABLE IF NOT EXISTS v22_identity_rekey_map (
|
|
3098
|
+
old_project_path TEXT PRIMARY KEY,
|
|
3099
|
+
new_project_path TEXT NOT NULL,
|
|
3100
|
+
rekeyed_at INTEGER NOT NULL
|
|
3101
|
+
);
|
|
3102
|
+
CREATE TABLE IF NOT EXISTS identity_merge_log (
|
|
3103
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
3104
|
+
from_identity TEXT NOT NULL,
|
|
3105
|
+
to_identity TEXT NOT NULL,
|
|
3106
|
+
table_name TEXT NOT NULL,
|
|
3107
|
+
row_id TEXT NOT NULL,
|
|
3108
|
+
action TEXT NOT NULL,
|
|
3109
|
+
target_row_id TEXT,
|
|
3110
|
+
merged_at INTEGER NOT NULL
|
|
3111
|
+
);
|
|
3112
|
+
CREATE INDEX IF NOT EXISTS idx_identity_merge_log_identities
|
|
3113
|
+
ON identity_merge_log(from_identity, to_identity, merged_at);
|
|
3114
|
+
CREATE INDEX IF NOT EXISTS idx_identity_merge_log_table_row
|
|
3115
|
+
ON identity_merge_log(table_name, row_id);
|
|
3116
|
+
CREATE TABLE IF NOT EXISTS workspaces (
|
|
3117
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
3118
|
+
name TEXT NOT NULL UNIQUE,
|
|
3119
|
+
created_at INTEGER NOT NULL,
|
|
3120
|
+
updated_at INTEGER NOT NULL,
|
|
3121
|
+
share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'
|
|
3122
|
+
);
|
|
3123
|
+
CREATE TABLE IF NOT EXISTS workspace_members (
|
|
3124
|
+
workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
3125
|
+
project_path TEXT NOT NULL,
|
|
3126
|
+
display_name TEXT NOT NULL,
|
|
3127
|
+
display_path TEXT NOT NULL,
|
|
3128
|
+
added_at INTEGER NOT NULL,
|
|
3129
|
+
PRIMARY KEY (workspace_id, project_path)
|
|
3130
|
+
);
|
|
3131
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_unique ON workspace_members(project_path);
|
|
3132
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_name ON workspace_members(workspace_id, display_name);
|
|
3133
|
+
CREATE TABLE IF NOT EXISTS v22_backfill_failures (
|
|
3134
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
3135
|
+
table_name TEXT NOT NULL,
|
|
3136
|
+
row_id INTEGER NOT NULL,
|
|
3137
|
+
raw_project_path TEXT NOT NULL,
|
|
3138
|
+
error_class TEXT NOT NULL CHECK (error_class IN ('not_git_repo', 'git_missing', 'git_timeout', 'permission_denied', 'unknown')),
|
|
3139
|
+
error_message TEXT,
|
|
3140
|
+
failed_at INTEGER NOT NULL,
|
|
3141
|
+
UNIQUE(table_name, row_id)
|
|
3142
|
+
);
|
|
3143
|
+
CREATE TABLE IF NOT EXISTS transform_decisions (
|
|
3144
|
+
session_id TEXT NOT NULL,
|
|
3145
|
+
harness TEXT NOT NULL DEFAULT 'opencode',
|
|
3146
|
+
message_id TEXT NOT NULL,
|
|
3147
|
+
ts_ms INTEGER NOT NULL,
|
|
3148
|
+
decision TEXT NOT NULL,
|
|
3149
|
+
materialized INTEGER NOT NULL DEFAULT 0,
|
|
3150
|
+
materialize_reason TEXT,
|
|
3151
|
+
system_hash_prev TEXT,
|
|
3152
|
+
system_hash_new TEXT,
|
|
3153
|
+
m0_tool_set_hash_prev TEXT,
|
|
3154
|
+
m0_tool_set_hash_new TEXT,
|
|
3155
|
+
m0_model_key_prev TEXT,
|
|
3156
|
+
m0_model_key_new TEXT,
|
|
3157
|
+
emergency INTEGER NOT NULL DEFAULT 0,
|
|
3158
|
+
dropped_tokens INTEGER NOT NULL DEFAULT 0,
|
|
3159
|
+
dropped_count INTEGER NOT NULL DEFAULT 0,
|
|
3160
|
+
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
3161
|
+
PRIMARY KEY (session_id, harness, message_id)
|
|
3162
|
+
);
|
|
3163
|
+
`);
|
|
3164
|
+
ensureColumn(db, "transform_decisions", "system_hash_prev", "TEXT");
|
|
3165
|
+
ensureColumn(db, "transform_decisions", "system_hash_new", "TEXT");
|
|
3166
|
+
ensureColumn(db, "transform_decisions", "m0_model_key_prev", "TEXT");
|
|
3167
|
+
ensureColumn(db, "transform_decisions", "m0_model_key_new", "TEXT");
|
|
3168
|
+
ensureColumn(db, "tags", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
|
|
3169
|
+
ensureColumn(db, "message_history_index", "dirty_floor_ordinal", "INTEGER NOT NULL DEFAULT 0");
|
|
3170
|
+
ensureColumn(db, "pending_ops", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
|
|
3171
|
+
ensureColumn(db, "source_contents", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
|
|
3172
|
+
ensureColumn(db, "compartments", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
|
|
3173
|
+
ensureColumn(db, "compression_depth", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
|
|
3174
|
+
ensureColumn(db, "session_facts", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
|
|
3175
|
+
ensureColumn(db, "session_meta", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
|
|
3176
|
+
ensureColumn(db, "recomp_compartments", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
|
|
3177
|
+
ensureColumn(db, "recomp_facts", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
|
|
3178
|
+
ensureColumn(db, "message_history_index", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
|
|
3179
|
+
db.exec(`
|
|
3180
|
+
CREATE INDEX IF NOT EXISTS idx_message_history_index_orphan_sweep
|
|
3181
|
+
ON message_history_index(harness, session_id, updated_at);
|
|
3182
|
+
`);
|
|
3183
|
+
ensureColumn(db, "workspaces", "share_categories", `TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'`);
|
|
3184
|
+
const version = getPersistedSchemaVersion(db);
|
|
3185
|
+
if (version === 0 || version >= 95)
|
|
3186
|
+
installV95PerfSchema(db, false, version === 0);
|
|
3187
|
+
installTagIdentityRevisionTrigger(db);
|
|
3188
|
+
}
|
|
3189
|
+
var CHANNEL2_CLAIM_TTL_MS = 10 * 60000;
|
|
3190
|
+
var channel2HealStatements = new WeakMap;
|
|
3191
|
+
function healWedgedChannel2Claims(db) {
|
|
3192
|
+
try {
|
|
3193
|
+
const staleBefore = Date.now() - CHANNEL2_CLAIM_TTL_MS;
|
|
3194
|
+
let statements = channel2HealStatements.get(db);
|
|
3195
|
+
if (!statements) {
|
|
3196
|
+
const predicate = "channel2_nudge_state = 'claimed' AND (channel2_nudge_claimed_at IS NULL OR channel2_nudge_claimed_at = 0 OR channel2_nudge_claimed_at <= ?)";
|
|
3197
|
+
statements = {
|
|
3198
|
+
stale: db.prepare(`SELECT 1 FROM session_meta WHERE ${predicate} LIMIT 1`),
|
|
3199
|
+
heal: db.prepare(`UPDATE session_meta SET channel2_nudge_state = '', channel2_nudge_claimed_at = 0, channel2_nudge_claim_token = '' WHERE ${predicate}`)
|
|
3200
|
+
};
|
|
3201
|
+
channel2HealStatements.set(db, statements);
|
|
3202
|
+
}
|
|
3203
|
+
if (statements.stale.get(staleBefore))
|
|
3204
|
+
statements.heal.run(staleBefore);
|
|
3205
|
+
} catch {}
|
|
3206
|
+
}
|
|
3207
|
+
function openDatabase(dbPathOrOptions) {
|
|
3208
|
+
const options = typeof dbPathOrOptions === "string" ? { dbPath: dbPathOrOptions } : dbPathOrOptions;
|
|
3209
|
+
const explicitDbPath = options?.dbPath !== undefined;
|
|
3210
|
+
const { dbDir, dbPath } = resolveDatabasePath(options?.dbPath);
|
|
3211
|
+
const latestSupportedVersion = getRuntimeLatestSupportedVersion(options);
|
|
3212
|
+
const busyTimeoutMs = resolveBootBusyTimeoutMs(options?.busyTimeoutMs);
|
|
3213
|
+
lastSchemaFenceRejection = null;
|
|
3214
|
+
lastMigrationOnOpenRefusal = null;
|
|
3215
|
+
const existing = databases.get(dbPath);
|
|
3216
|
+
if (!existing && pendingAsyncOpens.has(dbPath)) {
|
|
3217
|
+
log2(`[magic-context] storage not ready: ${dbPath} is still being opened or migrated; refusing a second synchronous open`);
|
|
3218
|
+
return null;
|
|
3219
|
+
}
|
|
3220
|
+
if (existing) {
|
|
3221
|
+
if (!enforceSchemaFence(existing, dbPath, latestSupportedVersion)) {
|
|
3222
|
+
return null;
|
|
3223
|
+
}
|
|
3224
|
+
if (!persistenceByDatabase.has(existing)) {
|
|
3225
|
+
persistenceByDatabase.set(existing, true);
|
|
3226
|
+
}
|
|
3227
|
+
healWedgedChannel2Claims(existing);
|
|
3228
|
+
return existing;
|
|
3229
|
+
}
|
|
3230
|
+
let db;
|
|
3231
|
+
try {
|
|
3232
|
+
if (!explicitDbPath) {
|
|
3233
|
+
migrateLegacyStorageIfNeeded(dbPath, dbDir);
|
|
3234
|
+
}
|
|
3235
|
+
ensureSecureStorageDir(dbDir);
|
|
3236
|
+
tightenStorageTree(dbDir);
|
|
3237
|
+
if (!existsSync2(dbPath) && shouldEnforcePrivateStoragePermissions()) {
|
|
3238
|
+
try {
|
|
3239
|
+
writeStorageFileSync(dbPath, new Uint8Array, { flag: "wx" });
|
|
3240
|
+
} catch (error) {
|
|
3241
|
+
if (error.code !== "EEXIST")
|
|
3242
|
+
throw error;
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
db = new Database2(dbPath);
|
|
3246
|
+
installBootBusyTimeout(db, dbPath, busyTimeoutMs, options?.onBootBusyTimeout);
|
|
3247
|
+
if (!enforceSchemaFence(db, dbPath, latestSupportedVersion)) {
|
|
3248
|
+
closeQuietly(db);
|
|
3249
|
+
return null;
|
|
3250
|
+
}
|
|
3251
|
+
if (options?.allowMigrations === false && hasPendingMigrations(db)) {
|
|
3252
|
+
log2(`[magic-context] storage not ready: pending migrations at ${dbPath}; host callers must await the async opener`);
|
|
3253
|
+
closeQuietly(db);
|
|
3254
|
+
return null;
|
|
3255
|
+
}
|
|
3256
|
+
if (!enforceMigrationOnOpenGuard(db, dbPath, dbDir, latestSupportedVersion)) {
|
|
3257
|
+
closeQuietly(db);
|
|
3258
|
+
return null;
|
|
3259
|
+
}
|
|
3260
|
+
initializeDatabase(db, busyTimeoutMs);
|
|
3261
|
+
if (options?.allowMigrations === false) {
|
|
3262
|
+
if (hasPendingMigrations(db)) {
|
|
3263
|
+
closeQuietly(db);
|
|
3264
|
+
return null;
|
|
3265
|
+
}
|
|
3266
|
+
} else
|
|
3267
|
+
runMigrations(db);
|
|
3268
|
+
ensureContextStoreUuid(db);
|
|
3269
|
+
installTagIdentityRevisionTrigger(db);
|
|
3270
|
+
return finishDatabaseOpen(db, dbPath, explicitDbPath, latestSupportedVersion);
|
|
3271
|
+
} catch (error) {
|
|
3272
|
+
if (db)
|
|
3273
|
+
closeQuietly(db);
|
|
3274
|
+
const detail = getErrorMessage(error);
|
|
3275
|
+
log2(`[magic-context] storage fatal: failed to open ${dbPath}: ${detail}`);
|
|
3276
|
+
throw new Error(`[magic-context] storage unavailable: ${detail}. Magic Context is disabled for this run; check log for details.`);
|
|
3277
|
+
}
|
|
3278
|
+
}
|
|
3279
|
+
function openCurrentDatabase(options = {}) {
|
|
3280
|
+
return openDatabase({ ...options, allowMigrations: false });
|
|
3281
|
+
}
|
|
3282
|
+
async function openDatabaseAsync(dbPathOrOptions) {
|
|
3283
|
+
const options = typeof dbPathOrOptions === "string" ? { dbPath: dbPathOrOptions } : dbPathOrOptions;
|
|
3284
|
+
const explicitDbPath = options?.dbPath !== undefined;
|
|
3285
|
+
const { dbDir, dbPath } = resolveDatabasePath(options?.dbPath);
|
|
3286
|
+
const latestSupportedVersion = getRuntimeLatestSupportedVersion(options);
|
|
3287
|
+
const busyTimeoutMs = resolveBootBusyTimeoutMs(options?.busyTimeoutMs);
|
|
3288
|
+
lastSchemaFenceRejection = null;
|
|
3289
|
+
lastMigrationOnOpenRefusal = null;
|
|
3290
|
+
const existing = databases.get(dbPath);
|
|
3291
|
+
if (existing) {
|
|
3292
|
+
const startedAt = performance.now();
|
|
3293
|
+
const accepted = enforceSchemaFence(existing, dbPath, latestSupportedVersion);
|
|
3294
|
+
if (accepted) {
|
|
3295
|
+
if (!persistenceByDatabase.has(existing))
|
|
3296
|
+
persistenceByDatabase.set(existing, true);
|
|
3297
|
+
healWedgedChannel2Claims(existing);
|
|
3298
|
+
}
|
|
3299
|
+
options?.onBootTimings?.({
|
|
3300
|
+
openMs: performance.now() - startedAt,
|
|
3301
|
+
guardMs: 0,
|
|
3302
|
+
migrateMs: 0
|
|
3303
|
+
});
|
|
3304
|
+
return accepted ? existing : null;
|
|
3305
|
+
}
|
|
3306
|
+
const pending = pendingAsyncOpens.get(dbPath);
|
|
3307
|
+
if (pending)
|
|
3308
|
+
return pending;
|
|
3309
|
+
if (!isFileBackedPath(dbPath)) {
|
|
3310
|
+
throw new Error("async migration requires a file-backed database; use the explicit synchronous opener for in-memory or URI test databases");
|
|
3311
|
+
}
|
|
3312
|
+
const opening = (async () => {
|
|
3313
|
+
let db;
|
|
3314
|
+
const mainThreadBodiesBefore = getMainThreadMigrationBodyCount();
|
|
3315
|
+
const openStartedAt = performance.now();
|
|
3316
|
+
let openMs = 0;
|
|
3317
|
+
let guardMs = 0;
|
|
3318
|
+
let migrateMs = 0;
|
|
3319
|
+
let guardStartedAt = null;
|
|
3320
|
+
let migrateStartedAt = null;
|
|
3321
|
+
try {
|
|
3322
|
+
if (!explicitDbPath)
|
|
3323
|
+
migrateLegacyStorageIfNeeded(dbPath, dbDir);
|
|
3324
|
+
ensureSecureStorageDir(dbDir);
|
|
3325
|
+
tightenStorageTree(dbDir);
|
|
3326
|
+
if (!existsSync2(dbPath) && shouldEnforcePrivateStoragePermissions()) {
|
|
3327
|
+
try {
|
|
3328
|
+
writeStorageFileSync(dbPath, new Uint8Array, { flag: "wx" });
|
|
3329
|
+
} catch (error) {
|
|
3330
|
+
if (error.code !== "EEXIST")
|
|
3331
|
+
throw error;
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
db = new Database2(dbPath);
|
|
3335
|
+
installBootBusyTimeout(db, dbPath, busyTimeoutMs, options?.onBootBusyTimeout);
|
|
3336
|
+
openMs = performance.now() - openStartedAt;
|
|
3337
|
+
guardStartedAt = performance.now();
|
|
3338
|
+
if (!enforceSchemaFence(db, dbPath, latestSupportedVersion)) {
|
|
3339
|
+
guardMs = performance.now() - guardStartedAt;
|
|
3340
|
+
closeQuietly(db);
|
|
3341
|
+
return null;
|
|
3342
|
+
}
|
|
3343
|
+
const processes = getPersistedSchemaVersion(db) < latestSupportedVersion ? await inspectProcessesAsync() : undefined;
|
|
3344
|
+
if (!enforceMigrationOnOpenGuard(db, dbPath, dbDir, latestSupportedVersion, processes)) {
|
|
3345
|
+
guardMs = performance.now() - guardStartedAt;
|
|
3346
|
+
closeQuietly(db);
|
|
3347
|
+
return null;
|
|
3348
|
+
}
|
|
3349
|
+
guardMs = performance.now() - guardStartedAt;
|
|
3350
|
+
migrateStartedAt = performance.now();
|
|
3351
|
+
if (hasPendingMigrations(db)) {
|
|
3352
|
+
await runMigrationsOffThread({
|
|
3353
|
+
dbPath,
|
|
3354
|
+
busyTimeoutMs,
|
|
3355
|
+
sqlitePragmaConfig: { ...sqlitePragmaConfig }
|
|
3356
|
+
});
|
|
3357
|
+
}
|
|
3358
|
+
if (hasPendingMigrations(db)) {
|
|
3359
|
+
throw new Error("the migration worker did not complete the pending migration; reinstall or rebuild the plugin");
|
|
3360
|
+
}
|
|
3361
|
+
initializeDatabase(db, busyTimeoutMs);
|
|
3362
|
+
if (hasPendingMigrations(db)) {
|
|
3363
|
+
throw new Error("storage initialization left a pending migration; reinstall or rebuild the plugin");
|
|
3364
|
+
}
|
|
3365
|
+
ensureContextStoreUuid(db);
|
|
3366
|
+
installTagIdentityRevisionTrigger(db);
|
|
3367
|
+
const opened = finishDatabaseOpen(db, dbPath, explicitDbPath, latestSupportedVersion);
|
|
3368
|
+
migrateMs = performance.now() - migrateStartedAt;
|
|
3369
|
+
return opened;
|
|
3370
|
+
} catch (error) {
|
|
3371
|
+
if (db)
|
|
3372
|
+
closeQuietly(db);
|
|
3373
|
+
const detail = getErrorMessage(error);
|
|
3374
|
+
log2(`[magic-context] storage fatal: failed to open ${dbPath}: ${detail}`);
|
|
3375
|
+
throw new Error(`[magic-context] storage unavailable: ${detail}. Magic Context is disabled for this run; check log for details.`);
|
|
3376
|
+
} finally {
|
|
3377
|
+
log2(`[migrations] async open main-thread migration-body count: ${getMainThreadMigrationBodyCount() - mainThreadBodiesBefore} (total=${getMainThreadMigrationBodyCount()}) path=${dbPath}`);
|
|
3378
|
+
if (openMs === 0)
|
|
3379
|
+
openMs = performance.now() - openStartedAt;
|
|
3380
|
+
if (guardStartedAt !== null && guardMs === 0) {
|
|
3381
|
+
guardMs = performance.now() - guardStartedAt;
|
|
3382
|
+
}
|
|
3383
|
+
if (migrateStartedAt !== null && migrateMs === 0) {
|
|
3384
|
+
migrateMs = performance.now() - migrateStartedAt;
|
|
3385
|
+
}
|
|
3386
|
+
options?.onBootTimings?.({ openMs, guardMs, migrateMs });
|
|
3387
|
+
}
|
|
3388
|
+
})();
|
|
3389
|
+
pendingAsyncOpens.set(dbPath, opening);
|
|
3390
|
+
try {
|
|
3391
|
+
return await opening;
|
|
3392
|
+
} finally {
|
|
3393
|
+
if (pendingAsyncOpens.get(dbPath) === opening)
|
|
3394
|
+
pendingAsyncOpens.delete(dbPath);
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
function isDatabasePersisted(db) {
|
|
3398
|
+
if (!db)
|
|
3399
|
+
return false;
|
|
3400
|
+
return persistenceByDatabase.get(db) ?? false;
|
|
3401
|
+
}
|
|
3402
|
+
function getDatabasePersistenceError(db) {
|
|
3403
|
+
if (!db)
|
|
3404
|
+
return null;
|
|
3405
|
+
return persistenceErrorByDatabase.get(db) ?? null;
|
|
3406
|
+
}
|
|
3407
|
+
|
|
3408
|
+
export { FAIL_CLOSED_DOCTOR_COMMAND, formatFailClosedBlockingMessage, formatFailClosedBlockingSummary, createFailClosedBlockingError, isFailClosedBlockingError, shouldBypassFailClosedBlock, resolveAgentNameFromMessages, registerOwnRpcServerInstance, rpcPortDir, rpcPortFilePath, isPidAlive, parseRpcPortFile, inspectProcessesAsync, startBootDeadline, getUnconfirmedMigrationHolders, getSchemaFenceRejection, getMigrationOnOpenRefusal, LATEST_SUPPORTED_VERSION, BOOT_SQLITE_BUSY_TIMEOUT_MS, getPersistedSchemaVersion, setSqlitePragmaConfig, runSqliteOptimize, initializeDatabase, openDatabase, openCurrentDatabase, openDatabaseAsync, isDatabasePersisted, getDatabasePersistenceError };
|