@cortexkit/opencode-magic-context 0.45.0 → 0.46.1
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/README.md +26 -0
- package/dist/{announcement-f75t58v9.js → announcement-y35wzg2r.js} +1 -1
- 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/schema/magic-context.d.ts +13 -7
- package/dist/config/schema/magic-context.d.ts.map +1 -1
- package/dist/{conflict-warning-hook-5yrz2r24.js → conflict-warning-hook-7fj2b9jw.js} +6 -5
- package/dist/context-sh2m2vde.js +4525 -0
- package/dist/{derive-budgets-a63zsm5c.js → derive-budgets-7nj7r1rv.js} +2 -2
- package/dist/embedding-worker.js +4 -4
- package/dist/features/magic-context/checkout-claim-fake-subc.test-support.d.ts +45 -0
- package/dist/features/magic-context/checkout-claim-fake-subc.test-support.d.ts.map +1 -0
- package/dist/features/magic-context/checkout-claim.d.ts +155 -0
- package/dist/features/magic-context/checkout-claim.d.ts.map +1 -0
- package/dist/features/magic-context/compaction-marker.d.ts +20 -3
- package/dist/features/magic-context/compaction-marker.d.ts.map +1 -1
- package/dist/features/magic-context/compartment-storage.d.ts +23 -2
- package/dist/features/magic-context/compartment-storage.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/classify.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/evaluate-smart-notes.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/provider-output-failure.d.ts +6 -2
- package/dist/features/magic-context/dreamer/provider-output-failure.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/provider-output-failure.test-support.d.ts +30 -0
- package/dist/features/magic-context/dreamer/provider-output-failure.test-support.d.ts.map +1 -0
- package/dist/features/magic-context/dreamer/storage-dream-runs.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-executor.d.ts +9 -0
- 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-scheduler.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/verify-diff.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/verify-gate.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/verify.d.ts.map +1 -1
- package/dist/features/magic-context/git-commits/search-git-commits.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
- package/dist/features/magic-context/memory/project-identity-cache.d.ts.map +1 -1
- package/dist/features/magic-context/memory/storage-memory-verifications.d.ts.map +1 -1
- package/dist/features/magic-context/memory/storage-memory.d.ts +3 -0
- package/dist/features/magic-context/memory/storage-memory.d.ts.map +1 -1
- package/dist/features/magic-context/memory/verification-paths.d.ts +2 -2
- package/dist/features/magic-context/memory/verification-paths.d.ts.map +1 -1
- package/dist/features/magic-context/merged-reasoning-decisions.d.ts.map +1 -1
- package/dist/features/magic-context/message-index-async.d.ts +3 -3
- package/dist/features/magic-context/message-index-async.d.ts.map +1 -1
- package/dist/features/magic-context/message-index.d.ts +2 -0
- package/dist/features/magic-context/message-index.d.ts.map +1 -1
- package/dist/features/magic-context/migration-v95-perf-indexes.d.ts +12 -0
- package/dist/features/magic-context/migration-v95-perf-indexes.d.ts.map +1 -0
- package/dist/features/magic-context/migration-worker-client.d.ts +4 -4
- package/dist/features/magic-context/migration-worker-client.d.ts.map +1 -1
- package/dist/features/magic-context/migrations.d.ts +1 -0
- package/dist/features/magic-context/migrations.d.ts.map +1 -1
- package/dist/features/magic-context/mural/render-trigger.d.ts.map +1 -1
- package/dist/features/magic-context/mural/resolve-mural.d.ts +15 -4
- package/dist/features/magic-context/mural/resolve-mural.d.ts.map +1 -1
- package/dist/features/magic-context/mural/source-revision.d.ts +7 -0
- package/dist/features/magic-context/mural/source-revision.d.ts.map +1 -0
- package/dist/features/magic-context/mural/storage-mural-cues.d.ts.map +1 -1
- package/dist/features/magic-context/reclaim-protection.d.ts +15 -10
- package/dist/features/magic-context/reclaim-protection.d.ts.map +1 -1
- package/dist/features/magic-context/session-cache-ttl.d.ts +4 -2
- package/dist/features/magic-context/session-cache-ttl.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/capabilities.d.ts +3 -0
- package/dist/features/magic-context/smart-notes/capabilities.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/compiler.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/github-token.d.ts +5 -0
- package/dist/features/magic-context/smart-notes/github-token.d.ts.map +1 -0
- 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/smart-notes/sandbox-runner.d.ts +12 -0
- package/dist/features/magic-context/smart-notes/sandbox-runner.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/ssrf-guard.d.ts +6 -0
- package/dist/features/magic-context/smart-notes/ssrf-guard.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/storage.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/types.d.ts +2 -0
- package/dist/features/magic-context/smart-notes/types.d.ts.map +1 -1
- package/dist/features/magic-context/storage-clone.d.ts.map +1 -1
- package/dist/features/magic-context/storage-db.d.ts +6 -9
- package/dist/features/magic-context/storage-db.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-shared.d.ts +1 -1
- package/dist/features/magic-context/storage-meta-shared.d.ts.map +1 -1
- package/dist/features/magic-context/storage-ops.d.ts +8 -1
- package/dist/features/magic-context/storage-ops.d.ts.map +1 -1
- package/dist/features/magic-context/storage-replay-document.d.ts +7 -0
- package/dist/features/magic-context/storage-replay-document.d.ts.map +1 -1
- package/dist/features/magic-context/storage-schema-helpers.d.ts +2 -0
- package/dist/features/magic-context/storage-schema-helpers.d.ts.map +1 -1
- package/dist/features/magic-context/storage-session-tables.d.ts.map +1 -1
- package/dist/features/magic-context/storage-source.d.ts.map +1 -1
- package/dist/features/magic-context/storage-tags.d.ts +11 -3
- package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
- package/dist/features/magic-context/storage.d.ts +2 -2
- package/dist/features/magic-context/storage.d.ts.map +1 -1
- package/dist/features/magic-context/temporal-decisions.d.ts +10 -0
- package/dist/features/magic-context/temporal-decisions.d.ts.map +1 -0
- package/dist/features/magic-context/tool-definition-tokens.d.ts +2 -0
- package/dist/features/magic-context/tool-definition-tokens.d.ts.map +1 -1
- package/dist/features/magic-context/transform-decision-log.d.ts.map +1 -1
- package/dist/features/magic-context/types.d.ts +7 -6
- package/dist/features/magic-context/types.d.ts.map +1 -1
- package/dist/features/magic-context/window-report-ledger.d.ts.map +1 -1
- package/dist/hooks/auto-update-checker/index.d.ts.map +1 -1
- package/dist/hooks/magic-context/apply-operations.d.ts +8 -0
- package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
- package/dist/hooks/magic-context/cache-busting-signals.d.ts +6 -3
- package/dist/hooks/magic-context/cache-busting-signals.d.ts.map +1 -1
- package/dist/hooks/magic-context/channel2-cycle.d.ts +2 -0
- package/dist/hooks/magic-context/channel2-cycle.d.ts.map +1 -1
- package/dist/hooks/magic-context/compaction-marker-manager.d.ts +7 -0
- package/dist/hooks/magic-context/compaction-marker-manager.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-prompt.d.ts +2 -2
- package/dist/hooks/magic-context/compartment-prompt.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-render-epoch.d.ts +8 -0
- package/dist/hooks/magic-context/compartment-render-epoch.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-drop-queue.d.ts +18 -4
- package/dist/hooks/magic-context/compartment-runner-drop-queue.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-partial-recomp.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
- package/dist/hooks/magic-context/ctx-reduce-availability.d.ts +12 -0
- package/dist/hooks/magic-context/ctx-reduce-availability.d.ts.map +1 -1
- package/dist/hooks/magic-context/decision-calibration.d.ts +3 -0
- package/dist/hooks/magic-context/decision-calibration.d.ts.map +1 -1
- package/dist/hooks/magic-context/default-protection-pressure.test-support.d.ts +37 -0
- package/dist/hooks/magic-context/default-protection-pressure.test-support.d.ts.map +1 -0
- package/dist/hooks/magic-context/drop-stale-reduce-calls.d.ts +4 -0
- package/dist/hooks/magic-context/drop-stale-reduce-calls.d.ts.map +1 -1
- package/dist/hooks/magic-context/edit-marker.d.ts.map +1 -1
- package/dist/hooks/magic-context/emergency-drop.d.ts +2 -0
- package/dist/hooks/magic-context/emergency-drop.d.ts.map +1 -1
- package/dist/hooks/magic-context/emergency-fail-closed.d.ts +21 -1
- package/dist/hooks/magic-context/emergency-fail-closed.d.ts.map +1 -1
- package/dist/hooks/magic-context/event-handler.d.ts +5 -0
- package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/final-wire-token-estimate.d.ts +30 -1
- package/dist/hooks/magic-context/final-wire-token-estimate.d.ts.map +1 -1
- package/dist/hooks/magic-context/heuristic-cleanup.d.ts +2 -0
- package/dist/hooks/magic-context/heuristic-cleanup.d.ts.map +1 -1
- package/dist/hooks/magic-context/historian-prompt-fit.d.ts +4 -2
- package/dist/hooks/magic-context/historian-prompt-fit.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook.d.ts +2 -1
- package/dist/hooks/magic-context/hook.d.ts.map +1 -1
- package/dist/hooks/magic-context/inject-compartments.d.ts +9 -10
- package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
- package/dist/hooks/magic-context/lkg-measured-request.d.ts +69 -0
- package/dist/hooks/magic-context/lkg-measured-request.d.ts.map +1 -0
- package/dist/hooks/magic-context/lkg-persist.d.ts +2 -0
- package/dist/hooks/magic-context/lkg-persist.d.ts.map +1 -1
- package/dist/hooks/magic-context/lkg-replay-fit.d.ts +14 -0
- package/dist/hooks/magic-context/lkg-replay-fit.d.ts.map +1 -1
- package/dist/hooks/magic-context/lkg-replay.d.ts +10 -1
- package/dist/hooks/magic-context/lkg-replay.d.ts.map +1 -1
- package/dist/hooks/magic-context/lkg-slot.d.ts +4 -0
- package/dist/hooks/magic-context/lkg-slot.d.ts.map +1 -1
- package/dist/hooks/magic-context/module-state-sync.d.ts +2 -0
- package/dist/hooks/magic-context/module-state-sync.d.ts.map +1 -1
- package/dist/hooks/magic-context/module-transport.d.ts.map +1 -1
- package/dist/hooks/magic-context/module-wire.d.ts.map +1 -1
- package/dist/hooks/magic-context/note-nudger.d.ts +17 -5
- package/dist/hooks/magic-context/note-nudger.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-chunk.d.ts +1 -1
- package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-db.d.ts +1 -0
- package/dist/hooks/magic-context/read-session-db.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-formatting.d.ts +7 -0
- package/dist/hooks/magic-context/read-session-formatting.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-raw.d.ts +4 -0
- 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/recomp-orchestrator.d.ts +2 -0
- package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -1
- package/dist/hooks/magic-context/reference-retrieval.d.ts +27 -17
- package/dist/hooks/magic-context/reference-retrieval.d.ts.map +1 -1
- package/dist/hooks/magic-context/request-hook-order.d.ts +56 -0
- package/dist/hooks/magic-context/request-hook-order.d.ts.map +1 -0
- package/dist/hooks/magic-context/rust-mode-transform.d.ts +22 -3
- package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
- package/dist/hooks/magic-context/strip-content.d.ts +4 -2
- package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
- package/dist/hooks/magic-context/supersession-reclaim.d.ts +4 -0
- package/dist/hooks/magic-context/supersession-reclaim.d.ts.map +1 -1
- package/dist/hooks/magic-context/system-prompt-hash.d.ts +7 -0
- package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
- package/dist/hooks/magic-context/tail-hygiene-walk.d.ts +12 -3
- package/dist/hooks/magic-context/tail-hygiene-walk.d.ts.map +1 -1
- package/dist/hooks/magic-context/temporal-awareness.d.ts +6 -3
- package/dist/hooks/magic-context/temporal-awareness.d.ts.map +1 -1
- package/dist/hooks/magic-context/temporal-served-projection.d.ts +4 -0
- package/dist/hooks/magic-context/temporal-served-projection.d.ts.map +1 -0
- package/dist/hooks/magic-context/tool-drop-target.d.ts.map +1 -1
- package/dist/hooks/magic-context/tool-reclaim.d.ts +2 -0
- package/dist/hooks/magic-context/tool-reclaim.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-message-helpers.d.ts +5 -0
- package/dist/hooks/magic-context/transform-message-helpers.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +41 -17
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts +12 -0
- 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-vvtfyw1z.js → index-0d3g3461.js} +5 -13
- package/dist/index-1bc61ans.js +154 -0
- package/dist/{index-n96ag4rk.js → index-1zx87sdm.js} +5 -1
- package/dist/{index-4krbw8z9.js → index-3h0ybwah.js} +608 -102
- package/dist/{index-gd5qx1j9.js → index-5eqk6z3s.js} +62 -1
- package/dist/index-5gq3b2ya.js +494 -0
- package/dist/{index-8fgkmxmy.js → index-5p8jdswx.js} +4 -3
- package/dist/{index-75zjgwga.js → index-7qw742ev.js} +1 -1
- package/dist/{index-f85teqck.js → index-8da2zjhz.js} +3 -256
- package/dist/{index-q7yesyk9.js → index-8x2v7gg9.js} +25 -24
- package/dist/{index-w9jwmntv.js → index-b35pxds2.js} +9 -1
- package/dist/{v2/server-a4m2b1yv.js → index-fg34g6a5.js} +55 -1
- package/dist/index-hpecdzec.js +28506 -0
- package/dist/{index-2ntg5sd4.js → index-k5bdx1bx.js} +9 -5
- package/dist/{index-bmzf7zxv.js → index-majxda46.js} +23 -5
- package/dist/{index-2aq21j41.js → index-pkbcrdqs.js} +418 -127
- package/dist/{index-remp7e2g.js → index-q4yp4b52.js} +16 -1
- package/dist/{index-fr2z4qy8.js → index-r5hmbx3t.js} +132 -169
- package/dist/index-sfnj6h8h.js +259 -0
- package/dist/{index-xgt9k6c6.js → index-sgthv3f7.js} +2 -1
- package/dist/index-sj29b3z2.js +30 -0
- package/dist/{index-16ny736w.js → index-sjthx78w.js} +20526 -19817
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1983 -33118
- package/dist/{message-time-backfill-an311pmk.js → message-time-backfill-68gj6t3e.js} +2 -2
- package/dist/migration-worker.js +12 -7
- package/dist/plugin/dream-timer.d.ts +25 -1
- package/dist/plugin/dream-timer.d.ts.map +1 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts +2 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
- package/dist/plugin/messages-transform.d.ts +13 -0
- package/dist/plugin/messages-transform.d.ts.map +1 -1
- package/dist/plugin/rpc-handlers.d.ts +4 -0
- package/dist/plugin/rpc-handlers.d.ts.map +1 -1
- package/dist/plugin/subagent-tool-policy.d.ts +14 -0
- package/dist/plugin/subagent-tool-policy.d.ts.map +1 -0
- package/dist/plugin/tool-registry.d.ts +1 -0
- package/dist/plugin/tool-registry.d.ts.map +1 -1
- package/dist/{read-session-chunk-8rxs0tc9.js → read-session-chunk-egz7r68d.js} +8 -8
- package/dist/{recomp-orchestrator-nzamtw29.js → recomp-orchestrator-y15j0rb9.js} +15 -15
- package/dist/{send-session-notification-nqpv7cne.js → send-session-notification-dyw42dtr.js} +4 -4
- package/dist/shared/announcement.d.ts.map +1 -1
- package/dist/shared/cache-ttl-display.d.ts +6 -4
- package/dist/shared/cache-ttl-display.d.ts.map +1 -1
- package/dist/shared/model-cache-ttl.d.ts +1 -1
- package/dist/shared/model-cache-ttl.d.ts.map +1 -1
- package/dist/shared/model-suggestion-retry.d.ts +2 -0
- package/dist/shared/model-suggestion-retry.d.ts.map +1 -1
- package/dist/shared/models-dev-cache.d.ts.map +1 -1
- package/dist/shared/protected-tools-policy.d.ts +6 -0
- package/dist/shared/protected-tools-policy.d.ts.map +1 -0
- package/dist/shared/quota-diagnostic.d.ts +4 -0
- package/dist/shared/quota-diagnostic.d.ts.map +1 -0
- package/dist/shared/rpc-server.d.ts.map +1 -1
- package/dist/shared/sqlite-context-pragmas.d.ts +12 -0
- package/dist/shared/sqlite-context-pragmas.d.ts.map +1 -0
- package/dist/shared/sqlite-helpers.d.ts +9 -0
- package/dist/shared/sqlite-helpers.d.ts.map +1 -1
- package/dist/shared/sqlite.d.ts +10 -0
- package/dist/shared/sqlite.d.ts.map +1 -1
- package/dist/shared/status-summary.d.ts +1 -0
- package/dist/shared/status-summary.d.ts.map +1 -1
- package/dist/shared/status-view-check.d.ts.map +1 -1
- package/dist/shared/status-view.d.ts +4 -0
- package/dist/shared/status-view.d.ts.map +1 -1
- package/dist/shared/storage-permissions.d.ts +24 -0
- package/dist/shared/storage-permissions.d.ts.map +1 -1
- package/dist/shared/temporal-upgrade-fixture.d.ts +19 -0
- package/dist/shared/temporal-upgrade-fixture.d.ts.map +1 -0
- package/dist/shared/user-facing-codes.d.ts +5 -0
- package/dist/shared/user-facing-codes.d.ts.map +1 -1
- package/dist/shared/write-transaction-timing.d.ts +8 -1
- package/dist/shared/write-transaction-timing.d.ts.map +1 -1
- package/dist/tools/ctx-expand/render.d.ts.map +1 -1
- package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
- package/dist/tools/ctx-reduce/constants.d.ts +2 -0
- package/dist/tools/ctx-reduce/constants.d.ts.map +1 -1
- package/dist/tools/ctx-reduce/tools.d.ts +4 -4
- package/dist/tools/ctx-reduce/tools.d.ts.map +1 -1
- package/dist/{transform-postprocess-phase-414phndq.js → transform-postprocess-phase-bqhz6vdr.js} +15 -15
- package/dist/v2/context-zecmzk5s.js +52209 -0
- package/dist/v2/{derive-budgets-v0krf3t7.js → derive-budgets-kdgp4s3r.js} +4 -3
- package/dist/v2/embedding-worker.js +6 -5
- package/dist/v2/fold/boundary.d.ts +2 -0
- package/dist/v2/fold/boundary.d.ts.map +1 -1
- package/dist/v2/fold/owner.d.ts.map +1 -1
- package/dist/v2/hidden-child-native.d.ts +7 -0
- package/dist/v2/hidden-child-native.d.ts.map +1 -1
- package/dist/v2/hidden-child-record.d.ts +8 -0
- package/dist/v2/hidden-child-record.d.ts.map +1 -1
- package/dist/v2/hidden-completion.d.ts.map +1 -1
- package/dist/v2/hooks/channel2.d.ts +2 -0
- package/dist/v2/hooks/channel2.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-timer.d.ts +39 -0
- package/dist/v2/hooks/dream-timer.d.ts.map +1 -0
- package/dist/v2/hooks/dream-trigger.d.ts +15 -1
- package/dist/v2/hooks/dream-trigger.d.ts.map +1 -1
- package/dist/v2/hooks/hidden-child.d.ts +54 -3
- package/dist/v2/hooks/hidden-child.d.ts.map +1 -1
- package/dist/v2/hooks/payload.d.ts.map +1 -1
- package/dist/v2/hooks/store.d.ts.map +1 -1
- package/dist/v2/hooks/tools.d.ts +1 -1
- package/dist/v2/hooks/tools.d.ts.map +1 -1
- package/dist/v2/hooks/types.d.ts +12 -1
- package/dist/v2/hooks/types.d.ts.map +1 -1
- package/dist/v2/hooks/update-check.d.ts.map +1 -1
- package/dist/v2/{message-time-backfill-0n93vrd7.js → message-time-backfill-r6qkem1x.js} +3 -3
- package/dist/v2/migration-worker.js +13 -8
- package/dist/v2/{read-session-chunk-hjza38hr.js → read-session-chunk-8vjp6581.js} +9 -9
- package/dist/v2/recomp-orchestrator-8ejxvrzz.js +39 -0
- package/dist/v2/{safe-notification-target-wtj0bbc2.js → safe-notification-target-eb1n1vfb.js} +2 -2
- package/dist/v2/{send-session-notification-mq9frrtm.js → send-session-notification-qs0j78v6.js} +6 -6
- package/dist/v2/server-14vq4kpm.js +30 -0
- package/dist/v2/server-1bc61ans.js +154 -0
- package/dist/v2/{server-n96ag4rk.js → server-1zx87sdm.js} +5 -1
- package/dist/v2/server-3n4q9xme.js +79 -0
- package/dist/v2/{server-rhdgxp1t.js → server-51dv1pr5.js} +63 -2
- package/dist/v2/{server-5317ykaf.js → server-6r257mf5.js} +1 -1
- package/dist/v2/server-84w6rvvf.js +14 -0
- package/dist/v2/{server-qpg6wf72.js → server-ajzeteqw.js} +9 -1
- package/dist/v2/server-c5gz8wph.js +3892 -0
- package/dist/v2/{server-v5xgft5v.js → server-e29d7p7c.js} +188 -3533
- package/dist/{index-a4m2b1yv.js → v2/server-fg34g6a5.js} +55 -1
- package/dist/v2/{server-e36nf2p1.js → server-frbm99q5.js} +1595 -1599
- package/dist/v2/{server-remp7e2g.js → server-g0fjrkta.js} +16 -1
- package/dist/v2/{server-c4g2y1z8.js → server-jc7y02q0.js} +2816 -5392
- package/dist/v2/{server-z19x7eqz.js → server-mcfawjgc.js} +4 -4
- package/dist/v2/{server-hfjfbg59.js → server-qp5hx1ad.js} +608 -105
- package/dist/v2/server-vs5gpnd4.js +493 -0
- package/dist/v2/{server-w99cyt6a.js → server-wvjvfhck.js} +14 -6
- package/dist/v2/server-xa4xe3rv.js +3351 -0
- package/dist/v2/{server-0jqw05sa.js → server-y8maxeft.js} +6 -84
- package/dist/v2/server.d.ts.map +1 -1
- package/dist/v2/server.js +104 -49288
- package/dist/v2/store-reader.d.ts +39 -1
- package/dist/v2/store-reader.d.ts.map +1 -1
- package/dist/v2/tool-result.d.ts +8 -0
- package/dist/v2/tool-result.d.ts.map +1 -0
- package/dist/v2/tui/index.d.ts.map +1 -1
- package/dist/v2/{wrapup-orchestrator-tbyx78dz.js → wrapup-orchestrator-0snhvwcv.js} +20 -16
- package/dist/wrapup-orchestrator-39amhv9s.js +26 -0
- package/package.json +26 -10
- package/src/config/index.ts +21 -4
- package/src/config/latch-permanence-guard.test.ts +6 -1
- package/src/config/live-run-config.ts +1 -0
- package/src/config/migrate-config-location.test.ts +31 -1
- package/src/config/migrate-config-location.ts +8 -41
- package/src/config/protected-tools.test.ts +70 -0
- package/src/config/raw-loader.test.ts +15 -0
- package/src/config/raw-loader.ts +1 -1
- package/src/config/removed-agent-source-fence.test.ts +10 -1
- package/src/config/schema/magic-context.test.ts +20 -1
- package/src/config/schema/magic-context.ts +26 -7
- package/src/shared/announcement.ts +6 -5
- package/src/shared/cache-ttl-display.test.ts +1 -1
- package/src/shared/cache-ttl-display.ts +17 -16
- package/src/shared/fold-render-lock-fence.test.ts +84 -0
- package/src/shared/fold-render-lock-fixtures.test.ts +276 -0
- package/src/shared/model-cache-ttl.test.ts +2 -2
- package/src/shared/model-cache-ttl.ts +8 -4
- package/src/shared/model-suggestion-retry.test.ts +104 -1
- package/src/shared/model-suggestion-retry.ts +63 -6
- package/src/shared/models-dev-cache.ts +3 -11
- package/src/shared/protected-tools-policy.ts +23 -0
- package/src/shared/quota-diagnostic.ts +9 -0
- package/src/shared/rpc-server.ts +6 -54
- package/src/shared/sqlite-context-pragmas.test.ts +105 -0
- package/src/shared/sqlite-context-pragmas.ts +24 -0
- package/src/shared/sqlite-helpers.ts +17 -0
- package/src/shared/sqlite.ts +75 -0
- package/src/shared/status-detail-text.test.ts +18 -1
- package/src/shared/status-summary.ts +11 -0
- package/src/shared/status-view-check.ts +5 -0
- package/src/shared/status-view.test.ts +11 -2
- package/src/shared/status-view.ts +12 -1
- package/src/shared/storage-permissions.test.ts +204 -0
- package/src/shared/storage-permissions.ts +192 -0
- package/src/shared/temporal-upgrade-fixture.ts +50 -0
- package/src/shared/user-facing-codes.ts +6 -0
- package/src/shared/write-transaction-timing.test.ts +39 -0
- package/src/shared/write-transaction-timing.ts +25 -1
- package/src/tui/dialogs/status-dialog-render.test.ts +1 -1
- package/src/tui/index.tsx +3 -1
- package/src/tui/startup-order.test.ts +16 -0
- package/src/tui-compiled/index.tsx +3 -1
- package/src/v2/tui/index.ts +9 -2
- package/dist/index-anq4nn6f.js +0 -131
- package/dist/index-e44zzy84.js +0 -10
- package/dist/index-x23gah8c.js +0 -16
- package/dist/v2/recomp-orchestrator-1aq7h44c.js +0 -36
- package/dist/v2/server-e44zzy84.js +0 -10
- package/dist/v2/server-fgmxwrgy.js +0 -16
- package/dist/v2/server-r7cmye3k.js +0 -131
- package/dist/wrapup-orchestrator-h28bqsy6.js +0 -26
- package/dist/v2/{server-ckpc8pxy.js → server-1qbnzc80.js} +8 -8
|
@@ -6,14 +6,21 @@ import {
|
|
|
6
6
|
importPluginModule,
|
|
7
7
|
writeForwardedLogLine,
|
|
8
8
|
log
|
|
9
|
-
} from "./server-
|
|
9
|
+
} from "./server-1qbnzc80.js";
|
|
10
10
|
import {
|
|
11
|
+
ensureColumn,
|
|
12
|
+
healAllNullColumns,
|
|
13
|
+
installV95PerfSchema,
|
|
14
|
+
setDatabase,
|
|
15
|
+
loadToolDefinitionMeasurements
|
|
16
|
+
} from "./server-vs5gpnd4.js";
|
|
17
|
+
import {
|
|
18
|
+
closeQuietly,
|
|
11
19
|
resolveOpenCodeDbPath,
|
|
12
20
|
hasV1MessageTables,
|
|
13
21
|
openCodeDbPathExists,
|
|
14
|
-
getOpenCodeDbProbeDescriptions
|
|
15
|
-
|
|
16
|
-
} from "./server-qpg6wf72.js";
|
|
22
|
+
getOpenCodeDbProbeDescriptions
|
|
23
|
+
} from "./server-ajzeteqw.js";
|
|
17
24
|
import {
|
|
18
25
|
registerSlowWriteReporter,
|
|
19
26
|
registerSqliteDiagnosticSink,
|
|
@@ -23,1479 +30,1610 @@ import {
|
|
|
23
30
|
withoutSqliteTransformPass,
|
|
24
31
|
isTransientSqliteError,
|
|
25
32
|
withPrivilegedWriter
|
|
26
|
-
} from "./server-
|
|
27
|
-
import {
|
|
28
|
-
setDatabase,
|
|
29
|
-
loadToolDefinitionMeasurements
|
|
30
|
-
} from "./server-r7cmye3k.js";
|
|
33
|
+
} from "./server-fg34g6a5.js";
|
|
31
34
|
import {
|
|
32
35
|
getErrorMessage
|
|
33
36
|
} from "./server-y8v6yerm.js";
|
|
34
37
|
import {
|
|
35
|
-
shouldEnforcePrivateStoragePermissions
|
|
36
|
-
|
|
38
|
+
shouldEnforcePrivateStoragePermissions,
|
|
39
|
+
ensureStorageDirectorySync,
|
|
40
|
+
writeStorageFileSync,
|
|
41
|
+
writeStorageFileAtomicSync,
|
|
42
|
+
copyStorageFileSync,
|
|
43
|
+
copyStorageTreeSync,
|
|
44
|
+
tightenStorageTreeSync
|
|
45
|
+
} from "./server-1bc61ans.js";
|
|
37
46
|
import {
|
|
38
47
|
logSlowWriteTransaction
|
|
39
|
-
} from "./server-
|
|
40
|
-
|
|
41
|
-
// src/features/magic-context/migrations.ts
|
|
42
|
-
import { isMainThread } from "node:worker_threads";
|
|
43
|
-
|
|
44
|
-
// src/features/magic-context/memory/constants.ts
|
|
45
|
-
var V2_MEMORY_CATEGORIES = [
|
|
46
|
-
"PROJECT_RULES",
|
|
47
|
-
"ARCHITECTURE",
|
|
48
|
-
"CONSTRAINTS",
|
|
49
|
-
"CONFIG_VALUES",
|
|
50
|
-
"NAMING"
|
|
51
|
-
];
|
|
52
|
-
var PROMOTABLE_CATEGORIES = [
|
|
53
|
-
...V2_MEMORY_CATEGORIES,
|
|
54
|
-
"ARCHITECTURE_DECISIONS",
|
|
55
|
-
"CONFIG_DEFAULTS",
|
|
56
|
-
"USER_PREFERENCES",
|
|
57
|
-
"USER_DIRECTIVES",
|
|
58
|
-
"ENVIRONMENT",
|
|
59
|
-
"WORKFLOW_RULES",
|
|
60
|
-
"KNOWN_ISSUES"
|
|
61
|
-
];
|
|
62
|
-
var CATEGORY_PRIORITY = [
|
|
63
|
-
...V2_MEMORY_CATEGORIES,
|
|
64
|
-
"USER_DIRECTIVES",
|
|
65
|
-
"USER_PREFERENCES",
|
|
66
|
-
"CONFIG_DEFAULTS",
|
|
67
|
-
"ARCHITECTURE_DECISIONS",
|
|
68
|
-
"ENVIRONMENT",
|
|
69
|
-
"WORKFLOW_RULES",
|
|
70
|
-
"KNOWN_ISSUES"
|
|
71
|
-
];
|
|
72
|
-
var MEMORY_CATEGORY_ORDER_UNKNOWN = 99;
|
|
73
|
-
var MEMORY_CATEGORY_ORDER_PRIORITY = CATEGORY_PRIORITY.reduce((acc, category, index) => {
|
|
74
|
-
acc[category] = index;
|
|
75
|
-
return acc;
|
|
76
|
-
}, {});
|
|
77
|
-
var MEMORY_CATEGORY_ORDER_SQL = `CASE category ${CATEGORY_PRIORITY.map((category, index) => `WHEN '${category}' THEN ${index}`).join(" ")} ELSE ${MEMORY_CATEGORY_ORDER_UNKNOWN} END`;
|
|
78
|
-
function getMemoryCategoryOrder(category) {
|
|
79
|
-
return MEMORY_CATEGORY_ORDER_PRIORITY[category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
|
|
80
|
-
}
|
|
81
|
-
var CATEGORY_DEFAULT_TTL = {
|
|
82
|
-
WORKFLOW_RULES: 90 * 24 * 60 * 60 * 1000,
|
|
83
|
-
KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
|
|
84
|
-
};
|
|
48
|
+
} from "./server-14vq4kpm.js";
|
|
85
49
|
|
|
86
|
-
// src/
|
|
87
|
-
var
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
};
|
|
95
|
-
function unescapeXml(s) {
|
|
96
|
-
return s.replace(XML_ENTITY_REGEX, (entity, name) => XML_ENTITIES[name] ?? entity);
|
|
50
|
+
// src/features/magic-context/fail-closed-block.ts
|
|
51
|
+
var FAIL_CLOSED_DOCTOR_COMMAND = "npx @cortexkit/magic-context@latest doctor";
|
|
52
|
+
function attachFailClosedBlockingProcessEvidence(process2, evidence) {
|
|
53
|
+
Object.defineProperties(process2, {
|
|
54
|
+
startTime: { configurable: true, value: evidence.startTime },
|
|
55
|
+
commandLine: { configurable: true, value: evidence.commandLine }
|
|
56
|
+
});
|
|
57
|
+
return process2;
|
|
97
58
|
}
|
|
98
59
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
var ATTR_EPISODE_REGEX = /\bepisode_type="([^"]*)"/;
|
|
107
|
-
var ATTR_IMPORTANCE_REGEX = /\bimportance="(\d+)"/;
|
|
108
|
-
var TIER_OPEN_REGEX = /<p(\d)\s*(\/?)>/g;
|
|
109
|
-
var TIER_CLOSE_REGEX = /<\/p(\d)\s*>/g;
|
|
110
|
-
var TIER_CLOSE_FOLLOWER_REGEX = /^\s*(?:$|<\/?p\d\s*\/?>)/;
|
|
111
|
-
var TIER_CLOSE_ANY_REGEX = /<\/p\d/;
|
|
112
|
-
var TIER_OPEN_ANY_REGEX = /<p\d/;
|
|
113
|
-
var CATEGORY_BLOCK_REGEX = /<([A-Za-z_][A-Za-z0-9_-]*)>(.*?)<\/\1>/gs;
|
|
114
|
-
var HISTORIAN_CATEGORIES = new Set(V2_MEMORY_CATEGORIES);
|
|
115
|
-
var FACT_ITEM_REGEX = /^\s*\*\s*(.+)$/gm;
|
|
116
|
-
var UNPROCESSED_REGEX = /<unprocessed_from>(\d+)<\/unprocessed_from>/;
|
|
117
|
-
var USER_OBSERVATIONS_REGEX = /<user_observations>(.*?)<\/user_observations>/s;
|
|
118
|
-
var USER_OBS_ITEM_REGEX = /^\s*\*\s*(.+)$/gm;
|
|
119
|
-
var PRIMER_CANDIDATES_REGEX = /<primer_candidates>(.*?)<\/primer_candidates>/s;
|
|
120
|
-
var PRIMER_ELEMENT_REGEX = /<primer\s+at_compartment="(\d+)"\s*>(.*?)<\/primer>/gs;
|
|
121
|
-
var PRIMER_ITEM_REGEX = /^\s*(?:\*|-|\d+\.)\s*(.+)$/gm;
|
|
122
|
-
var FACTS_BLOCK_REGEX = /<facts>(.*?)<\/facts>/s;
|
|
123
|
-
var EVENTS_BLOCK_REGEX = /<events>(.*?)<\/events>/s;
|
|
124
|
-
var EVENT_ELEMENT_REGEX = /<([a-z_]+)\s+at_compartment="(\d+)"\s*>(.*?)<\/\1>/gs;
|
|
125
|
-
var EVENT_FIELD_REGEX = /<([a-z_]+)\s*>(.*?)<\/\1>/gs;
|
|
126
|
-
function searchFrom(s, regex, from) {
|
|
127
|
-
const at = s.slice(from).search(regex);
|
|
128
|
-
return at === -1 ? -1 : from + at;
|
|
129
|
-
}
|
|
130
|
-
function hasTierOpenerFrom(inner, digit, from) {
|
|
131
|
-
return new RegExp(`<p${digit}\\s*/?>`).test(inner.slice(from));
|
|
132
|
-
}
|
|
133
|
-
function findMatchingTierClose(inner, bodyStart, digit, found) {
|
|
134
|
-
const closeRegex = new RegExp(TIER_CLOSE_REGEX.source, "g");
|
|
135
|
-
closeRegex.lastIndex = bodyStart;
|
|
136
|
-
for (let close = closeRegex.exec(inner);close; close = closeRegex.exec(inner)) {
|
|
137
|
-
const end = close.index + close[0].length;
|
|
138
|
-
if (Number(close[1]) !== digit)
|
|
139
|
-
continue;
|
|
140
|
-
if (!TIER_CLOSE_FOLLOWER_REGEX.test(inner.slice(end)))
|
|
141
|
-
continue;
|
|
142
|
-
const body = inner.slice(bodyStart, close.index);
|
|
143
|
-
for (const open of body.matchAll(new RegExp(TIER_OPEN_REGEX.source, "g"))) {
|
|
144
|
-
const other = Number(open[1]);
|
|
145
|
-
const otherwiseMissing = other >= 1 && other <= 4 && other !== digit && !found.has(other) && !hasTierOpenerFrom(inner, other, end);
|
|
146
|
-
if (otherwiseMissing)
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
return { start: close.index, end };
|
|
60
|
+
class FailClosedBlockingError extends Error {
|
|
61
|
+
code = "FAIL_CLOSED_BLOCKING";
|
|
62
|
+
reason;
|
|
63
|
+
constructor(message, reason, options) {
|
|
64
|
+
super(message, options);
|
|
65
|
+
this.name = "FailClosedBlockingError";
|
|
66
|
+
this.reason = reason;
|
|
150
67
|
}
|
|
151
|
-
return;
|
|
152
68
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
for (;; ) {
|
|
158
|
-
openRegex.lastIndex = pos;
|
|
159
|
-
const open = openRegex.exec(inner);
|
|
160
|
-
if (!open)
|
|
161
|
-
break;
|
|
162
|
-
const digit = Number(open[1]);
|
|
163
|
-
const bodyStart = open.index + open[0].length;
|
|
164
|
-
let bodyEnd = bodyStart;
|
|
165
|
-
pos = bodyStart;
|
|
166
|
-
if (open[2] !== "/") {
|
|
167
|
-
const close = findMatchingTierClose(inner, bodyStart, digit, tiers);
|
|
168
|
-
if (close) {
|
|
169
|
-
bodyEnd = close.start;
|
|
170
|
-
pos = close.end;
|
|
171
|
-
} else {
|
|
172
|
-
const closeAt = searchFrom(inner, TIER_CLOSE_ANY_REGEX, bodyStart);
|
|
173
|
-
bodyEnd = closeAt === -1 ? inner.length : closeAt;
|
|
174
|
-
pos = closeAt === -1 ? inner.length : closeAt + 1;
|
|
175
|
-
const openInside = searchFrom(inner.slice(0, bodyEnd), TIER_OPEN_ANY_REGEX, bodyStart);
|
|
176
|
-
if (openInside !== -1) {
|
|
177
|
-
bodyEnd = openInside;
|
|
178
|
-
pos = openInside;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
if (!tiers.has(digit)) {
|
|
183
|
-
tiers.set(digit, unescapeXml(inner.slice(bodyStart, bodyEnd).trim()));
|
|
184
|
-
}
|
|
69
|
+
var OPENCODE_INTERNAL_AGENT_NAMES = new Set(["title", "summary", "compaction"]);
|
|
70
|
+
function isMagicContextHiddenAgentName(agent) {
|
|
71
|
+
if (agent === "smart-note-compiler" || agent.startsWith("smart-note-")) {
|
|
72
|
+
return true;
|
|
185
73
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
return
|
|
74
|
+
if (agent === "historian" || agent.startsWith("historian-"))
|
|
75
|
+
return true;
|
|
76
|
+
if (agent === "dreamer" || agent.startsWith("dreamer-"))
|
|
77
|
+
return true;
|
|
78
|
+
return false;
|
|
191
79
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const end = closeAt + COMPARTMENT_CLOSE_TAG.length;
|
|
215
|
-
elements.push({
|
|
216
|
-
start: open.index,
|
|
217
|
-
end,
|
|
218
|
-
attrs: open[1],
|
|
219
|
-
inner: text.slice(bodyStart, closeAt)
|
|
220
|
-
});
|
|
221
|
-
cursor = end;
|
|
80
|
+
var MAX_FORMATTED_BLOCKING_PROCESSES = 8;
|
|
81
|
+
var MAX_FORMATTED_COMMAND_LINE_LENGTH = 80;
|
|
82
|
+
function normalizeFailClosedProcessKind(process2) {
|
|
83
|
+
switch (process2.kind) {
|
|
84
|
+
case "OpenCode server":
|
|
85
|
+
case "OpenCode instance (TUI/CLI)":
|
|
86
|
+
case "Pi":
|
|
87
|
+
case "process":
|
|
88
|
+
return process2.kind;
|
|
89
|
+
}
|
|
90
|
+
switch (process2.harness?.trim().toLowerCase()) {
|
|
91
|
+
case "opencode server":
|
|
92
|
+
return "OpenCode server";
|
|
93
|
+
case "opencode instance (tui/cli)":
|
|
94
|
+
case "opencode instance":
|
|
95
|
+
return "OpenCode instance (TUI/CLI)";
|
|
96
|
+
case "pi":
|
|
97
|
+
case "pi harness":
|
|
98
|
+
case "omp":
|
|
99
|
+
return "Pi";
|
|
100
|
+
default:
|
|
101
|
+
return "process";
|
|
222
102
|
}
|
|
223
|
-
return elements;
|
|
224
103
|
}
|
|
225
|
-
function
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
104
|
+
function formatFailClosedProcessStartTime(startTime) {
|
|
105
|
+
if (!Number.isFinite(startTime) || startTime <= 0)
|
|
106
|
+
return "unverified";
|
|
107
|
+
try {
|
|
108
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
109
|
+
day: "numeric",
|
|
110
|
+
hour: "2-digit",
|
|
111
|
+
minute: "2-digit",
|
|
112
|
+
month: "short",
|
|
113
|
+
hour12: false
|
|
114
|
+
}).formatToParts(new Date(startTime));
|
|
115
|
+
const values = Object.fromEntries(parts.map(({ type, value }) => [type, value]));
|
|
116
|
+
if (!values.month || !values.day || !values.hour || !values.minute)
|
|
117
|
+
return "unverified";
|
|
118
|
+
return `${values.month} ${values.day} ${values.hour}:${values.minute}`;
|
|
119
|
+
} catch {
|
|
120
|
+
return "unverified";
|
|
231
121
|
}
|
|
232
|
-
return outside + text.slice(last);
|
|
233
122
|
}
|
|
234
|
-
function
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const endMessage = parseInt(endMatch[1], 10);
|
|
248
|
-
const title = unescapeXml(titleMatch[1]);
|
|
249
|
-
if (Number.isNaN(startMessage) || Number.isNaN(endMessage) || !title)
|
|
250
|
-
continue;
|
|
251
|
-
const episodeMatch = attrs.match(ATTR_EPISODE_REGEX);
|
|
252
|
-
const importanceMatch = attrs.match(ATTR_IMPORTANCE_REGEX);
|
|
253
|
-
const episodeType = episodeMatch ? unescapeXml(episodeMatch[1]) : undefined;
|
|
254
|
-
const importance = importanceMatch ? parseInt(importanceMatch[1], 10) : undefined;
|
|
255
|
-
const { p1, p2, p3, p4 } = extractTiersFromInner(inner);
|
|
256
|
-
if (typeof p1 === "string" && p1.length > 0) {
|
|
257
|
-
compartments.push({
|
|
258
|
-
startMessage,
|
|
259
|
-
endMessage,
|
|
260
|
-
title,
|
|
261
|
-
content: p1,
|
|
262
|
-
p1,
|
|
263
|
-
p2: typeof p2 === "string" ? p2 : p1,
|
|
264
|
-
p3: typeof p3 === "string" ? p3 : typeof p2 === "string" ? p2 : p1,
|
|
265
|
-
p4: typeof p4 === "string" ? p4 : "",
|
|
266
|
-
importance,
|
|
267
|
-
episodeType
|
|
268
|
-
});
|
|
269
|
-
continue;
|
|
270
|
-
}
|
|
271
|
-
const content = unescapeXml(inner.trim());
|
|
272
|
-
if (content) {
|
|
273
|
-
compartments.push({
|
|
274
|
-
startMessage,
|
|
275
|
-
endMessage,
|
|
276
|
-
title,
|
|
277
|
-
content,
|
|
278
|
-
importance,
|
|
279
|
-
episodeType
|
|
280
|
-
});
|
|
281
|
-
}
|
|
123
|
+
function formatFailClosedCommandLine(commandLine) {
|
|
124
|
+
if (typeof commandLine !== "string" || commandLine.trim().length === 0)
|
|
125
|
+
return "unverified";
|
|
126
|
+
try {
|
|
127
|
+
const normalized = commandLine.replaceAll("\x00", " ").replace(/\s+/g, " ").trim();
|
|
128
|
+
if (normalized.length === 0)
|
|
129
|
+
return "unverified";
|
|
130
|
+
const sanitized = sanitizeDiagnosticText(normalized).trim();
|
|
131
|
+
if (sanitized.length === 0)
|
|
132
|
+
return "unverified";
|
|
133
|
+
return sanitized.length > MAX_FORMATTED_COMMAND_LINE_LENGTH ? `${sanitized.slice(0, MAX_FORMATTED_COMMAND_LINE_LENGTH - 3)}...` : sanitized;
|
|
134
|
+
} catch {
|
|
135
|
+
return "unverified";
|
|
282
136
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
const
|
|
286
|
-
for (const
|
|
287
|
-
|
|
288
|
-
const blockContent = categoryMatch[2];
|
|
289
|
-
const items = [...blockContent.matchAll(FACT_ITEM_REGEX)].map((match) => unescapeXml(match[1].trim())).filter(Boolean);
|
|
290
|
-
if (!HISTORIAN_CATEGORIES.has(category)) {
|
|
291
|
-
if (items.length === 0)
|
|
292
|
-
continue;
|
|
293
|
-
droppedFactBlocks++;
|
|
294
|
-
droppedFacts += items.length;
|
|
295
|
-
log(`[historian] Dropped <facts> category ${category} (${items.length} facts)`);
|
|
137
|
+
}
|
|
138
|
+
function formatFailClosedBlockingProcesses(processes) {
|
|
139
|
+
const uniqueProcesses = new Map;
|
|
140
|
+
for (const process2 of processes) {
|
|
141
|
+
if (!Number.isInteger(process2.pid) || process2.pid <= 0)
|
|
296
142
|
continue;
|
|
143
|
+
const candidate = {
|
|
144
|
+
kind: normalizeFailClosedProcessKind(process2),
|
|
145
|
+
pid: process2.pid,
|
|
146
|
+
startTime: process2.startTime,
|
|
147
|
+
commandLine: process2.commandLine
|
|
148
|
+
};
|
|
149
|
+
const previous = uniqueProcesses.get(process2.pid);
|
|
150
|
+
if (!previous || previous.kind === "process" && candidate.kind !== "process") {
|
|
151
|
+
uniqueProcesses.set(process2.pid, candidate);
|
|
152
|
+
} else {
|
|
153
|
+
uniqueProcesses.set(process2.pid, {
|
|
154
|
+
...previous,
|
|
155
|
+
startTime: previous.startTime ?? candidate.startTime,
|
|
156
|
+
commandLine: previous.commandLine ?? candidate.commandLine
|
|
157
|
+
});
|
|
297
158
|
}
|
|
298
|
-
for (const content of items)
|
|
299
|
-
facts.push({ category, content });
|
|
300
159
|
}
|
|
301
|
-
const
|
|
302
|
-
const
|
|
303
|
-
const
|
|
304
|
-
const
|
|
305
|
-
if (
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
160
|
+
const entries = [...uniqueProcesses.values()];
|
|
161
|
+
const visible = entries.slice(0, MAX_FORMATTED_BLOCKING_PROCESSES);
|
|
162
|
+
const rendered = visible.map(({ kind, pid }) => `${kind} (PID ${pid})`);
|
|
163
|
+
const omitted = entries.length - visible.length;
|
|
164
|
+
if (omitted > 0)
|
|
165
|
+
rendered.push(`${omitted} more blocking process(es)`);
|
|
166
|
+
if (rendered.length === 0)
|
|
167
|
+
return "a live process";
|
|
168
|
+
const summary = rendered.length === 1 ? rendered[0] : `${rendered.slice(0, -1).join(", ")}, and ${rendered.at(-1)}`;
|
|
169
|
+
const evidence = entries.map(({ kind, pid, startTime, commandLine }) => `- PID ${pid}: ${kind}, started ${formatFailClosedProcessStartTime(startTime)}, cmd: ${formatFailClosedCommandLine(commandLine)}`).join(`
|
|
170
|
+
`);
|
|
171
|
+
return `${summary}
|
|
172
|
+
Blocking process evidence:
|
|
173
|
+
${evidence}`;
|
|
174
|
+
}
|
|
175
|
+
function formatFailClosedBlockingMessage(reason) {
|
|
176
|
+
if (reason.kind === "migration_guard") {
|
|
177
|
+
if (reason.unreadableFile) {
|
|
178
|
+
const arm = reason.unreadableArm ?? "io";
|
|
179
|
+
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.`;
|
|
180
|
+
return [
|
|
181
|
+
`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.`,
|
|
182
|
+
recovery,
|
|
183
|
+
`Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
|
|
184
|
+
].join(" ");
|
|
310
185
|
}
|
|
186
|
+
return [
|
|
187
|
+
`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.`,
|
|
188
|
+
`The database is at upstream migration v${reason.persistedVersion}; this build needs v${reason.supportedVersion}.`,
|
|
189
|
+
"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.",
|
|
190
|
+
"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.",
|
|
191
|
+
`Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
|
|
192
|
+
].join(" ");
|
|
311
193
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
for (const el of block.matchAll(PRIMER_ELEMENT_REGEX)) {
|
|
318
|
-
sawElement = true;
|
|
319
|
-
const question = unescapeXml(el[2].trim());
|
|
320
|
-
if (question) {
|
|
321
|
-
primerCandidates.push({
|
|
322
|
-
question,
|
|
323
|
-
originCompartmentIndex: Number.parseInt(el[1], 10)
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
if (!sawElement) {
|
|
328
|
-
for (const itemMatch of block.matchAll(PRIMER_ITEM_REGEX)) {
|
|
329
|
-
const question = unescapeXml(itemMatch[1].trim());
|
|
330
|
-
if (question)
|
|
331
|
-
primerCandidates.push({ question });
|
|
332
|
-
}
|
|
333
|
-
}
|
|
194
|
+
if (reason.kind === "schema_fence") {
|
|
195
|
+
return [
|
|
196
|
+
`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}).`,
|
|
197
|
+
`Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
|
|
198
|
+
].join(" ");
|
|
334
199
|
}
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
const remapAnchor = (anchor) => anchor >= 1 && anchor <= sortedPosition.length ? sortedPosition[anchor - 1] : anchor;
|
|
342
|
-
return {
|
|
343
|
-
compartments: sortedByStart.map(({ compartment }) => compartment),
|
|
344
|
-
facts,
|
|
345
|
-
droppedFactBlocks,
|
|
346
|
-
droppedFacts,
|
|
347
|
-
events: events.map((event) => event.atCompartment === null ? event : { ...event, atCompartment: remapAnchor(event.atCompartment) }),
|
|
348
|
-
unprocessedFrom,
|
|
349
|
-
userObservations,
|
|
350
|
-
primerCandidates: primerCandidates.map((candidate) => candidate.originCompartmentIndex === undefined ? candidate : {
|
|
351
|
-
...candidate,
|
|
352
|
-
originCompartmentIndex: remapAnchor(candidate.originCompartmentIndex)
|
|
353
|
-
})
|
|
354
|
-
};
|
|
200
|
+
const cause = reason.cause.trim().length > 0 ? reason.cause.trim() : "unknown storage error";
|
|
201
|
+
return [
|
|
202
|
+
`Magic Context cannot operate: persistent storage failed (${cause}).`,
|
|
203
|
+
"The plugin will not silently degrade to native compaction while enabled.",
|
|
204
|
+
`Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
|
|
205
|
+
].join(" ");
|
|
355
206
|
}
|
|
356
|
-
function
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
const block = blockMatch[1];
|
|
361
|
-
const events = [];
|
|
362
|
-
for (const elMatch of block.matchAll(EVENT_ELEMENT_REGEX)) {
|
|
363
|
-
const kind = elMatch[1];
|
|
364
|
-
const atRaw = parseInt(elMatch[2], 10);
|
|
365
|
-
const atCompartment = Number.isNaN(atRaw) ? null : atRaw;
|
|
366
|
-
const fields = {};
|
|
367
|
-
for (const fieldMatch of elMatch[3].matchAll(EVENT_FIELD_REGEX)) {
|
|
368
|
-
const name = fieldMatch[1];
|
|
369
|
-
const value = unescapeXml(fieldMatch[2].trim());
|
|
370
|
-
if (value)
|
|
371
|
-
fields[name] = value;
|
|
207
|
+
function formatFailClosedBlockingSummary(reason) {
|
|
208
|
+
if (reason.kind === "migration_guard") {
|
|
209
|
+
if (reason.unreadableFile) {
|
|
210
|
+
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.`;
|
|
372
211
|
}
|
|
373
|
-
|
|
212
|
+
const blockers = reason.blockingProcesses.filter((process2) => Number.isInteger(process2.pid) && process2.pid > 0).map((process2) => `${normalizeFailClosedProcessKind(process2)} (PID ${process2.pid})`);
|
|
213
|
+
const named = blockers.length > 0 ? blockers.join(", ") : "a live process";
|
|
214
|
+
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.`;
|
|
374
215
|
}
|
|
375
|
-
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// src/features/magic-context/lkg-prefix-chunks.ts
|
|
379
|
-
import { createHash } from "node:crypto";
|
|
380
|
-
var LKG_PREFIX_CHUNK_CHARS = 64 * 1024;
|
|
381
|
-
function splitLkgPrefix(jsonPrefix) {
|
|
382
|
-
const chunks = [];
|
|
383
|
-
let start = 0;
|
|
384
|
-
while (start < jsonPrefix.length) {
|
|
385
|
-
let end = Math.min(start + LKG_PREFIX_CHUNK_CHARS, jsonPrefix.length);
|
|
386
|
-
if (end < jsonPrefix.length) {
|
|
387
|
-
const last = jsonPrefix.charCodeAt(end - 1);
|
|
388
|
-
if (last >= 55296 && last <= 56319)
|
|
389
|
-
end -= 1;
|
|
390
|
-
}
|
|
391
|
-
chunks.push(jsonPrefix.slice(start, end));
|
|
392
|
-
start = end;
|
|
216
|
+
if (reason.kind === "schema_fence") {
|
|
217
|
+
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.`;
|
|
393
218
|
}
|
|
394
|
-
|
|
395
|
-
}
|
|
396
|
-
function hashLkgChunk(body) {
|
|
397
|
-
return createHash("sha256").update(body).digest("hex");
|
|
219
|
+
const cause = reason.cause.trim().length > 0 ? reason.cause.trim() : "unknown storage error";
|
|
220
|
+
return `Magic Context cannot operate: persistent storage failed (${cause}).`;
|
|
398
221
|
}
|
|
399
|
-
function
|
|
400
|
-
return
|
|
401
|
-
`)).digest("hex");
|
|
222
|
+
function createFailClosedBlockingError(reason, options) {
|
|
223
|
+
return new FailClosedBlockingError(formatFailClosedBlockingMessage(reason), reason, options);
|
|
402
224
|
}
|
|
403
|
-
function
|
|
404
|
-
|
|
405
|
-
const chunkHashes = chunks.map(hashLkgChunk);
|
|
406
|
-
return { chunks, chunkHashes, chars: jsonPrefix.length, hash: hashLkgChunkList(chunkHashes) };
|
|
225
|
+
function isFailClosedBlockingError(error) {
|
|
226
|
+
return error instanceof FailClosedBlockingError || typeof error === "object" && error !== null && error.name === "FailClosedBlockingError" && error.code === "FAIL_CLOSED_BLOCKING";
|
|
407
227
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
session_id TEXT NOT NULL,
|
|
422
|
-
message_id TEXT NOT NULL,
|
|
423
|
-
decision TEXT NOT NULL,
|
|
424
|
-
PRIMARY KEY (session_id, message_id)
|
|
425
|
-
) WITHOUT ROWID;
|
|
426
|
-
`;
|
|
427
|
-
function lkgSlotsDdl(table) {
|
|
428
|
-
return `
|
|
429
|
-
CREATE TABLE IF NOT EXISTS ${table} (
|
|
430
|
-
session_id TEXT PRIMARY KEY,
|
|
431
|
-
json_prefix_chars INTEGER NOT NULL,
|
|
432
|
-
json_prefix_chunks INTEGER NOT NULL,
|
|
433
|
-
json_prefix_hash TEXT NOT NULL,
|
|
434
|
-
input_id_seq TEXT NOT NULL,
|
|
435
|
-
input_content_digests TEXT NOT NULL,
|
|
436
|
-
input_content_signatures TEXT,
|
|
437
|
-
last_input_message_id TEXT NOT NULL,
|
|
438
|
-
model_key TEXT,
|
|
439
|
-
provider_key TEXT,
|
|
440
|
-
captured_at INTEGER NOT NULL,
|
|
441
|
-
row_version INTEGER,
|
|
442
|
-
capture_sequence INTEGER
|
|
443
|
-
);
|
|
444
|
-
`;
|
|
228
|
+
function shouldBypassFailClosedBlock(input) {
|
|
229
|
+
if (input.isPiSubagentEnv === true)
|
|
230
|
+
return true;
|
|
231
|
+
if (input.isInternalChildSession === true)
|
|
232
|
+
return true;
|
|
233
|
+
const agent = typeof input.agent === "string" ? input.agent.trim() : "";
|
|
234
|
+
if (agent.length === 0)
|
|
235
|
+
return false;
|
|
236
|
+
if (OPENCODE_INTERNAL_AGENT_NAMES.has(agent))
|
|
237
|
+
return true;
|
|
238
|
+
if (isMagicContextHiddenAgentName(agent))
|
|
239
|
+
return true;
|
|
240
|
+
return false;
|
|
445
241
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
242
|
+
function resolveAgentNameFromMessages(messages) {
|
|
243
|
+
for (let i = messages.length - 1;i >= 0; i -= 1) {
|
|
244
|
+
const info = messages[i]?.info;
|
|
245
|
+
if (!info || typeof info !== "object")
|
|
246
|
+
continue;
|
|
247
|
+
const agent = info.agent;
|
|
248
|
+
if (typeof agent === "string" && agent.length > 0)
|
|
249
|
+
return agent;
|
|
250
|
+
}
|
|
251
|
+
return;
|
|
450
252
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
253
|
+
|
|
254
|
+
// src/features/magic-context/memory/project-identity.ts
|
|
255
|
+
import { execFileSync } from "node:child_process";
|
|
256
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
257
|
+
import { existsSync, realpathSync as realpathSync2, statSync } from "node:fs";
|
|
258
|
+
import { homedir } from "node:os";
|
|
259
|
+
import path3 from "node:path";
|
|
260
|
+
|
|
261
|
+
// src/features/magic-context/memory/project-identity-cache.ts
|
|
262
|
+
import { createHash } from "node:crypto";
|
|
263
|
+
import { readFileSync } from "node:fs";
|
|
264
|
+
import path2 from "node:path";
|
|
265
|
+
|
|
266
|
+
// src/shared/project-directory-key.ts
|
|
267
|
+
import { realpathSync } from "node:fs";
|
|
268
|
+
import path from "node:path";
|
|
269
|
+
function projectDirectoryKey(directory) {
|
|
270
|
+
const slashed = directory.replaceAll("\\", "/").replace(/^\/\/\?\/UNC\//i, "//").replace(/^\/\/\?\//, "");
|
|
271
|
+
if (/^[a-z]:\//i.test(slashed) || slashed.startsWith("//")) {
|
|
272
|
+
return path.win32.normalize(slashed).replaceAll("\\", "/").replace(/\/$/, "").toLowerCase();
|
|
273
|
+
}
|
|
274
|
+
return path.resolve(directory);
|
|
454
275
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
276
|
+
var realpathForCanonicalDirectory = (directory) => realpathSync.native(directory);
|
|
277
|
+
function canonicalProjectDirectory(directory) {
|
|
278
|
+
let resolved = directory;
|
|
279
|
+
try {
|
|
280
|
+
resolved = realpathForCanonicalDirectory(directory);
|
|
281
|
+
} catch {}
|
|
282
|
+
return projectDirectoryKey(resolved);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// src/features/magic-context/memory/project-identity-cache.ts
|
|
286
|
+
function cachePath(directory, storageDir = getMagicContextStorageDir()) {
|
|
287
|
+
const hash = createHash("sha256").update(projectDirectoryKey(directory)).digest("hex");
|
|
288
|
+
return path2.join(storageDir, "project-identities", `${hash}.json`);
|
|
289
|
+
}
|
|
290
|
+
function rememberGitIdentity(directory, identity) {
|
|
291
|
+
try {
|
|
292
|
+
const destination = cachePath(directory);
|
|
293
|
+
writeStorageFileAtomicSync(destination, JSON.stringify({ directory: projectDirectoryKey(directory), identity }));
|
|
294
|
+
} catch {}
|
|
295
|
+
}
|
|
296
|
+
function readRememberedGitIdentity(directory, storageDir) {
|
|
297
|
+
try {
|
|
298
|
+
const record = JSON.parse(readFileSync(cachePath(directory, storageDir), "utf8"));
|
|
299
|
+
if (record.directory === projectDirectoryKey(directory) && /^git:[0-9a-f]{7,64}$/.test(record.identity))
|
|
300
|
+
return record.identity;
|
|
301
|
+
} catch {}
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// src/features/magic-context/memory/project-identity.ts
|
|
306
|
+
var GIT_TIMEOUT_MS = 5000;
|
|
307
|
+
var TRANSIENT_FAILURE_COOLDOWN_MS = 5 * 60 * 1000;
|
|
308
|
+
var identityCache = new Map;
|
|
309
|
+
var linkedGitWorktreeCache = new Map;
|
|
310
|
+
var lastKnownGitIdentityCache = new Map;
|
|
311
|
+
var directoryFallbackCache = new Map;
|
|
312
|
+
var transientFailureCooldown = new Map;
|
|
313
|
+
var dubiousOwnershipFallbackDirectories = new Set;
|
|
314
|
+
var dubiousOwnershipLoggedDirectories = new Set;
|
|
315
|
+
var dubiousOwnershipWarnedDirectories = new Set;
|
|
316
|
+
var transientGitIdentityReuseLoggedDirectories = new Set;
|
|
317
|
+
var sessionIdentityCache = new Map;
|
|
318
|
+
var homeProjectPermission = false;
|
|
319
|
+
var homeProjectSkipLogged = false;
|
|
320
|
+
var pausedIdentityReasons = new Map;
|
|
321
|
+
var pausedIdentityLogged = new Set;
|
|
322
|
+
function setHomeProjectPermission(allowed) {
|
|
323
|
+
homeProjectPermission = allowed;
|
|
324
|
+
}
|
|
325
|
+
function shouldSkipHomeProjectMemory(directory) {
|
|
326
|
+
try {
|
|
327
|
+
assertProjectAllowed(directory, homeProjectPermission);
|
|
328
|
+
return false;
|
|
329
|
+
} catch (error) {
|
|
330
|
+
if (!(error instanceof ProjectIdentityError) || error.errorClass !== "home_project_disabled")
|
|
331
|
+
throw error;
|
|
332
|
+
if (!homeProjectSkipLogged) {
|
|
333
|
+
homeProjectSkipLogged = true;
|
|
334
|
+
log("[magic-context] home project memory disabled; skipping memory features");
|
|
335
|
+
}
|
|
336
|
+
return true;
|
|
460
337
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
captured_at, row_version, capture_sequence
|
|
478
|
-
FROM lkg_slots WHERE session_id IS ?`);
|
|
479
|
-
const insertChunk = db.prepare("INSERT INTO lkg_slot_chunks (session_id, chunk, hash, body) VALUES (?, ?, ?, ?)");
|
|
480
|
-
db.prepare("DELETE FROM lkg_slot_chunks").run();
|
|
481
|
-
for (const sessionId of sessionIds) {
|
|
482
|
-
if (typeof sessionId !== "string")
|
|
483
|
-
continue;
|
|
484
|
-
const row = readPrefix.get(sessionId);
|
|
485
|
-
if (typeof row?.json_prefix !== "string")
|
|
486
|
-
continue;
|
|
487
|
-
const layout = layoutLkgPrefix(row.json_prefix);
|
|
488
|
-
const inserted = insertSlot.run(layout.chars, layout.chunks.length, layout.hash, sessionId);
|
|
489
|
-
if (Number(inserted.changes) !== 1)
|
|
490
|
-
continue;
|
|
491
|
-
for (let index = 0;index < layout.chunks.length; index += 1) {
|
|
492
|
-
insertChunk.run(sessionId, index, layout.chunkHashes[index], layout.chunks[index]);
|
|
338
|
+
}
|
|
339
|
+
var execFileSyncForIdentity = execFileSync;
|
|
340
|
+
var userHomeDirectoryForIdentity = () => homedir();
|
|
341
|
+
var nowMs = () => Date.now();
|
|
342
|
+
var filesystemProbeObserverForTests;
|
|
343
|
+
|
|
344
|
+
class ProjectIdentityError extends Error {
|
|
345
|
+
errorClass;
|
|
346
|
+
rawDirectory;
|
|
347
|
+
constructor(errorClass, rawDirectory, message, cause) {
|
|
348
|
+
super(message);
|
|
349
|
+
this.name = "ProjectIdentityError";
|
|
350
|
+
this.errorClass = errorClass;
|
|
351
|
+
this.rawDirectory = rawDirectory;
|
|
352
|
+
if (cause) {
|
|
353
|
+
this.cause = cause;
|
|
493
354
|
}
|
|
494
355
|
}
|
|
495
|
-
db.exec("DROP TABLE lkg_slots");
|
|
496
|
-
db.exec("ALTER TABLE lkg_slots_v94 RENAME TO lkg_slots");
|
|
497
356
|
}
|
|
498
|
-
function
|
|
499
|
-
return
|
|
500
|
-
${value} IN ('keep', 'strip') OR (
|
|
501
|
-
${value} GLOB 'keep:[1-9]*'
|
|
502
|
-
AND substr(${value}, 6) NOT GLOB '*[^0-9]*'
|
|
503
|
-
AND length(${value}) <= 10
|
|
504
|
-
AND CAST(substr(${value}, 6) AS INTEGER) BETWEEN 2 AND 10000
|
|
505
|
-
)
|
|
506
|
-
))`;
|
|
357
|
+
function asError(error) {
|
|
358
|
+
return error instanceof Error ? error : undefined;
|
|
507
359
|
}
|
|
508
|
-
function
|
|
509
|
-
|
|
510
|
-
if (!tableExists(db, "session_meta"))
|
|
360
|
+
function getErrorCode(error) {
|
|
361
|
+
if (error === null || typeof error !== "object" || !("code" in error)) {
|
|
511
362
|
return;
|
|
512
|
-
|
|
363
|
+
}
|
|
364
|
+
const code = error.code;
|
|
365
|
+
return typeof code === "string" ? code : undefined;
|
|
366
|
+
}
|
|
367
|
+
function getErrorSignal(error) {
|
|
368
|
+
if (error === null || typeof error !== "object" || !("signal" in error)) {
|
|
513
369
|
return;
|
|
514
|
-
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
const isV2 = `(${hasVersionKey} AND NOT ${versionIsDecision})`;
|
|
518
|
-
const v2Valid = `(
|
|
519
|
-
json_type(${doc}, '$.version') IN ('integer', 'real')
|
|
520
|
-
AND json_extract(${doc}, '$.version') = 2
|
|
521
|
-
AND json_type(${doc}, '$.trailingBlank') = 'object'
|
|
522
|
-
AND NOT EXISTS (
|
|
523
|
-
SELECT 1 FROM json_each(${doc}, '$.trailingBlank') AS entry
|
|
524
|
-
WHERE entry.key = '' OR NOT ${validDecisionSql("entry.type", "entry.value")}
|
|
525
|
-
)
|
|
526
|
-
)`;
|
|
527
|
-
const v1Valid = `NOT EXISTS (
|
|
528
|
-
SELECT 1 FROM json_each(${doc}) AS entry
|
|
529
|
-
WHERE entry.key = '' OR NOT ${validDecisionSql("entry.type", "entry.value")}
|
|
530
|
-
)`;
|
|
531
|
-
db.exec("DROP TABLE IF EXISTS temp.v94_replay_documents");
|
|
532
|
-
db.exec(`
|
|
533
|
-
CREATE TEMP TABLE v94_replay_documents AS
|
|
534
|
-
SELECT session_id, version FROM (
|
|
535
|
-
SELECT sm.session_id AS session_id, CASE WHEN ${isV2} THEN 2 ELSE 1 END AS version,
|
|
536
|
-
CASE WHEN ${isV2} THEN ${v2Valid} ELSE ${v1Valid} END AS strictly_valid
|
|
537
|
-
FROM session_meta AS sm
|
|
538
|
-
WHERE typeof(${doc}) = 'text' AND ${doc} <> ''
|
|
539
|
-
AND json_valid(${doc}) AND json_type(${doc}) = 'object'
|
|
540
|
-
) WHERE strictly_valid;
|
|
541
|
-
`);
|
|
542
|
-
db.exec(`
|
|
543
|
-
INSERT INTO session_replay_decisions (session_id, message_id, decision)
|
|
544
|
-
SELECT sm.session_id, entry.key, entry.value
|
|
545
|
-
FROM v94_replay_documents AS moved
|
|
546
|
-
JOIN session_meta AS sm ON sm.session_id = moved.session_id
|
|
547
|
-
JOIN json_each(
|
|
548
|
-
${doc}, CASE moved.version WHEN 2 THEN '$.trailingBlank' ELSE '$' END
|
|
549
|
-
) AS entry
|
|
550
|
-
WHERE true
|
|
551
|
-
ON CONFLICT(session_id, message_id) DO UPDATE SET decision = excluded.decision;
|
|
552
|
-
`);
|
|
553
|
-
db.exec(`
|
|
554
|
-
UPDATE session_meta SET trailing_blank_decisions = (
|
|
555
|
-
SELECT CASE moved.version
|
|
556
|
-
WHEN 2 THEN json_set(session_meta.trailing_blank_decisions, '$.trailingBlank', json('{}'))
|
|
557
|
-
ELSE ''
|
|
558
|
-
END
|
|
559
|
-
FROM v94_replay_documents AS moved WHERE moved.session_id = session_meta.session_id
|
|
560
|
-
)
|
|
561
|
-
WHERE session_id IN (SELECT session_id FROM v94_replay_documents);
|
|
562
|
-
`);
|
|
563
|
-
db.exec("DROP TABLE temp.v94_replay_documents");
|
|
370
|
+
}
|
|
371
|
+
const signal = error.signal;
|
|
372
|
+
return typeof signal === "string" ? signal : undefined;
|
|
564
373
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
var V87_HARNESS_TWIN_RULES = [
|
|
569
|
-
{
|
|
570
|
-
table: "session_projects",
|
|
571
|
-
keyColumns: ["session_id"],
|
|
572
|
-
incomingIsNewer: "incoming.updated_at > existing.updated_at"
|
|
573
|
-
},
|
|
574
|
-
{
|
|
575
|
-
table: "primer_candidates",
|
|
576
|
-
keyColumns: [
|
|
577
|
-
"project_path",
|
|
578
|
-
"session_id",
|
|
579
|
-
"source_start_message_id",
|
|
580
|
-
"source_end_message_id"
|
|
581
|
-
],
|
|
582
|
-
incomingIsNewer: "incoming.created_at > existing.created_at"
|
|
583
|
-
},
|
|
584
|
-
{
|
|
585
|
-
table: "transform_decisions",
|
|
586
|
-
keyColumns: ["session_id", "message_id"],
|
|
587
|
-
incomingIsNewer: "incoming.ts_ms > existing.ts_ms"
|
|
374
|
+
function getErrorKilled(error) {
|
|
375
|
+
if (error === null || typeof error !== "object" || !("killed" in error)) {
|
|
376
|
+
return false;
|
|
588
377
|
}
|
|
589
|
-
|
|
590
|
-
function tableExists2(db, name) {
|
|
591
|
-
return Boolean(db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = ?").get(name));
|
|
378
|
+
return error.killed === true;
|
|
592
379
|
}
|
|
593
|
-
function
|
|
594
|
-
|
|
380
|
+
function getErrorStderr(error) {
|
|
381
|
+
if (error === null || typeof error !== "object" || !("stderr" in error)) {
|
|
382
|
+
return "";
|
|
383
|
+
}
|
|
384
|
+
const stderr = error.stderr;
|
|
385
|
+
if (typeof stderr === "string") {
|
|
386
|
+
return stderr;
|
|
387
|
+
}
|
|
388
|
+
if (Buffer.isBuffer(stderr)) {
|
|
389
|
+
return stderr.toString("utf8");
|
|
390
|
+
}
|
|
391
|
+
return "";
|
|
595
392
|
}
|
|
596
|
-
function
|
|
597
|
-
const
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
393
|
+
function directoryFallback(directory) {
|
|
394
|
+
const canonical = path3.resolve(directory);
|
|
395
|
+
const hash = createHash2("md5").update(canonical, "utf8").digest("hex").slice(0, 12);
|
|
396
|
+
return `dir:${hash}`;
|
|
397
|
+
}
|
|
398
|
+
function rememberedIdentityOrDirectoryFallback(canonical) {
|
|
399
|
+
return readRememberedGitIdentity(canonical) ?? directoryFallback(canonical);
|
|
400
|
+
}
|
|
401
|
+
function assertDirectoryUsable(canonicalDirectory, rawDirectory) {
|
|
402
|
+
try {
|
|
403
|
+
const stat = statSync(canonicalDirectory);
|
|
404
|
+
if (!stat.isDirectory()) {
|
|
405
|
+
throw new ProjectIdentityError("unknown", rawDirectory, `Project path is not a directory: ${canonicalDirectory}`);
|
|
406
|
+
}
|
|
407
|
+
} catch (error) {
|
|
408
|
+
if (error instanceof ProjectIdentityError) {
|
|
409
|
+
throw error;
|
|
410
|
+
}
|
|
411
|
+
const code = getErrorCode(error);
|
|
412
|
+
if (code === "EACCES" || code === "EPERM") {
|
|
413
|
+
throw new ProjectIdentityError("permission_denied", rawDirectory, `Permission denied while accessing project directory: ${canonicalDirectory}`, asError(error));
|
|
414
|
+
}
|
|
415
|
+
throw new ProjectIdentityError("unknown", rawDirectory, `Unable to access project directory: ${canonicalDirectory}`, asError(error));
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
function isGitTimeoutError(error) {
|
|
419
|
+
const code = getErrorCode(error);
|
|
420
|
+
const signal = getErrorSignal(error);
|
|
421
|
+
return code === "ETIMEDOUT" || signal === "SIGTERM" || signal === "SIGKILL" || getErrorKilled(error);
|
|
422
|
+
}
|
|
423
|
+
function hasUnbornHead(directory) {
|
|
424
|
+
const options = {
|
|
425
|
+
cwd: directory,
|
|
426
|
+
encoding: "utf8",
|
|
427
|
+
env: { ...process.env, LC_ALL: "C", LANG: "C" },
|
|
428
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
429
|
+
timeout: GIT_TIMEOUT_MS,
|
|
430
|
+
windowsHide: true
|
|
431
|
+
};
|
|
432
|
+
try {
|
|
433
|
+
execFileSyncForIdentity("git", ["rev-parse", "--git-dir"], options);
|
|
434
|
+
try {
|
|
435
|
+
execFileSyncForIdentity("git", ["rev-parse", "--verify", "--quiet", "HEAD"], options);
|
|
436
|
+
return false;
|
|
437
|
+
} catch (error) {
|
|
438
|
+
return !isGitTimeoutError(error) && typeof error === "object" && error !== null && "status" in error && error.status === 1 && getErrorStderr(error) === "";
|
|
439
|
+
}
|
|
440
|
+
} catch {
|
|
441
|
+
return false;
|
|
605
442
|
}
|
|
606
|
-
return shapes;
|
|
607
443
|
}
|
|
608
|
-
function
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
if (!shape.hasSessionId)
|
|
612
|
-
continue;
|
|
613
|
-
const rows = db.prepare(`SELECT DISTINCT session_id AS id FROM ${shape.table}
|
|
614
|
-
WHERE session_id IS NOT NULL AND harness IN ('opencode', 'opencode2')`).all();
|
|
615
|
-
for (const row of rows)
|
|
616
|
-
sessions.add(row.id);
|
|
444
|
+
function classifyGitError(error, rawDirectory) {
|
|
445
|
+
if (isGitTimeoutError(error)) {
|
|
446
|
+
return new ProjectIdentityError("git_timeout", rawDirectory, `git rev-list timed out after ${GIT_TIMEOUT_MS}ms`, asError(error));
|
|
617
447
|
}
|
|
618
|
-
|
|
448
|
+
const code = getErrorCode(error);
|
|
449
|
+
if (code === "ENOENT") {
|
|
450
|
+
return new ProjectIdentityError("git_missing", rawDirectory, "git binary is not available in PATH", asError(error));
|
|
451
|
+
}
|
|
452
|
+
if (code === "EACCES" || code === "EPERM") {
|
|
453
|
+
return new ProjectIdentityError("permission_denied", rawDirectory, "Permission denied while spawning git", asError(error));
|
|
454
|
+
}
|
|
455
|
+
const stderr = getErrorStderr(error).toLowerCase();
|
|
456
|
+
if (stderr.includes("detected dubious ownership")) {
|
|
457
|
+
return new ProjectIdentityError("dubious_ownership", rawDirectory, "git refused to read the repository because it detected dubious ownership", asError(error));
|
|
458
|
+
}
|
|
459
|
+
if (stderr.includes("not a git repository") || stderr.includes("does not have any commits yet") || stderr.includes("ambiguous argument 'head'") || stderr.includes("unknown revision or path")) {
|
|
460
|
+
return new ProjectIdentityError("not_git_repo", rawDirectory, "Directory has no git root commit; caller may use directory fallback", asError(error));
|
|
461
|
+
}
|
|
462
|
+
return new ProjectIdentityError("unknown", rawDirectory, "git rev-list failed while resolving project identity", asError(error));
|
|
619
463
|
}
|
|
620
|
-
function
|
|
621
|
-
|
|
464
|
+
function resolveProjectIdentityStrict(directory, allowHomeProject = homeProjectPermission) {
|
|
465
|
+
assertProjectAllowed(directory, allowHomeProject);
|
|
466
|
+
const canonical = path3.resolve(directory);
|
|
467
|
+
const cached = identityCache.get(canonical);
|
|
468
|
+
if (cached !== undefined) {
|
|
469
|
+
return cached;
|
|
470
|
+
}
|
|
471
|
+
assertDirectoryUsable(canonical, directory);
|
|
472
|
+
if (!hasGitDir(canonical)) {
|
|
473
|
+
throw new ProjectIdentityError("not_git_repo", directory, "Directory has no git metadata; caller may use directory fallback");
|
|
474
|
+
}
|
|
475
|
+
let output;
|
|
622
476
|
try {
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
}
|
|
631
|
-
const v1Message = hasV1Messages ? store.prepare("SELECT MAX(time_updated) AS newest FROM message WHERE session_id = ?") : null;
|
|
632
|
-
const v1Session = hasV1Session ? store.prepare("SELECT time_updated AS newest FROM session WHERE id = ?") : null;
|
|
633
|
-
const v2Message = hasV2Messages ? store.prepare("SELECT MAX(time_updated) AS newest FROM session_message WHERE session_id = ?") : null;
|
|
634
|
-
const v2Session = hasV2 ? store.prepare("SELECT time_updated AS newest FROM session_v2 WHERE id = ?") : null;
|
|
635
|
-
const newest = (statements, sessionId) => {
|
|
636
|
-
let best = null;
|
|
637
|
-
for (const statement of statements) {
|
|
638
|
-
if (!statement)
|
|
639
|
-
continue;
|
|
640
|
-
const row = statement.get(sessionId);
|
|
641
|
-
const value = row?.newest;
|
|
642
|
-
if (typeof value !== "number")
|
|
643
|
-
continue;
|
|
644
|
-
if (best === null || value > best)
|
|
645
|
-
best = value;
|
|
646
|
-
}
|
|
647
|
-
return best;
|
|
648
|
-
};
|
|
649
|
-
return {
|
|
650
|
-
newestV1Activity: (sessionId) => newest([v1Message, v1Session], sessionId),
|
|
651
|
-
newestV2Activity: (sessionId) => newest([v2Message, v2Session], sessionId),
|
|
652
|
-
close: () => store.close()
|
|
653
|
-
};
|
|
477
|
+
output = execFileSyncForIdentity("git", ["rev-list", "--max-parents=0", "HEAD"], {
|
|
478
|
+
cwd: canonical,
|
|
479
|
+
encoding: "utf8",
|
|
480
|
+
env: { ...process.env, LC_ALL: "C", LANG: "C" },
|
|
481
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
482
|
+
timeout: GIT_TIMEOUT_MS,
|
|
483
|
+
windowsHide: true
|
|
484
|
+
});
|
|
654
485
|
} catch (error) {
|
|
655
|
-
|
|
656
|
-
|
|
486
|
+
const classified = classifyGitError(error, directory);
|
|
487
|
+
if (classified.errorClass === "not_git_repo" && hasUnbornHead(canonical)) {
|
|
488
|
+
throw new ProjectIdentityError("no_commits", directory, "Git repository has no commits yet");
|
|
489
|
+
}
|
|
490
|
+
throw classified;
|
|
491
|
+
}
|
|
492
|
+
const rootCommit = output.split(`
|
|
493
|
+
`).map((line) => line.trim().slice(0, 64)).filter((line) => /^[0-9a-f]{7,64}$/.test(line)).sort()[0];
|
|
494
|
+
if (!rootCommit) {
|
|
495
|
+
throw new ProjectIdentityError("unknown", directory, "git rev-list returned no valid root commit hash");
|
|
657
496
|
}
|
|
497
|
+
const identity = `git:${rootCommit}`;
|
|
498
|
+
identityCache.set(canonical, identity);
|
|
499
|
+
lastKnownGitIdentityCache.set(canonical, identity);
|
|
500
|
+
rememberGitIdentity(canonical, identity);
|
|
501
|
+
const root = gitRootDirectory(canonical);
|
|
502
|
+
if (root && root !== canonical)
|
|
503
|
+
rememberGitIdentity(root, identity);
|
|
504
|
+
transientFailureCooldown.delete(canonical);
|
|
505
|
+
dubiousOwnershipFallbackDirectories.delete(canonical);
|
|
506
|
+
transientGitIdentityReuseLoggedDirectories.delete(canonical);
|
|
507
|
+
return identity;
|
|
658
508
|
}
|
|
659
|
-
function
|
|
660
|
-
|
|
661
|
-
const v2 = evidence.newestV2Activity(sessionId);
|
|
662
|
-
if (v1 === null && v2 === null)
|
|
663
|
-
return null;
|
|
664
|
-
if (v2 !== null && (v1 === null || v2 > v1))
|
|
665
|
-
return "opencode2";
|
|
666
|
-
if (v1 !== null && (v2 === null || v1 > v2))
|
|
667
|
-
return "opencode";
|
|
668
|
-
return null;
|
|
509
|
+
function shouldUseDirectoryFallback(error) {
|
|
510
|
+
return error.errorClass !== "home_project_disabled";
|
|
669
511
|
}
|
|
670
|
-
function
|
|
671
|
-
const
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
512
|
+
function getActiveCooldown(canonical) {
|
|
513
|
+
const until = transientFailureCooldown.get(canonical);
|
|
514
|
+
if (until === undefined)
|
|
515
|
+
return;
|
|
516
|
+
if (nowMs() < until)
|
|
517
|
+
return until;
|
|
518
|
+
transientFailureCooldown.delete(canonical);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
function lastKnownGitIdentity(canonical) {
|
|
522
|
+
return lastKnownGitIdentityCache.get(canonical) ?? identityCache.get(canonical) ?? readRememberedGitIdentity(canonical);
|
|
523
|
+
}
|
|
524
|
+
function nearestLastKnownGitIdentity(canonical) {
|
|
525
|
+
const visited = new Set;
|
|
526
|
+
const walk = (start) => {
|
|
527
|
+
let current = start;
|
|
528
|
+
while (!visited.has(current)) {
|
|
529
|
+
visited.add(current);
|
|
530
|
+
const cached = lastKnownGitIdentity(current);
|
|
531
|
+
if (cached !== undefined)
|
|
532
|
+
return { identity: cached, source: current };
|
|
533
|
+
if (existsSync(path3.join(current, ".git")))
|
|
534
|
+
break;
|
|
535
|
+
const parent = path3.dirname(current);
|
|
536
|
+
if (parent === current)
|
|
537
|
+
break;
|
|
538
|
+
current = parent;
|
|
688
539
|
}
|
|
540
|
+
return;
|
|
689
541
|
};
|
|
542
|
+
const exactOrAncestor = walk(canonical);
|
|
543
|
+
if (exactOrAncestor)
|
|
544
|
+
return exactOrAncestor;
|
|
545
|
+
try {
|
|
546
|
+
const realCanonical = realpathSync2.native(canonical);
|
|
547
|
+
if (realCanonical !== canonical)
|
|
548
|
+
return walk(realCanonical);
|
|
549
|
+
} catch {}
|
|
550
|
+
return;
|
|
690
551
|
}
|
|
691
|
-
function
|
|
692
|
-
|
|
552
|
+
function reuseLastKnownGitIdentity(canonical) {
|
|
553
|
+
const cached = nearestLastKnownGitIdentity(canonical);
|
|
554
|
+
if (cached === undefined)
|
|
693
555
|
return;
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
db.prepare(`INSERT INTO schema_migrations_meta (key, value) VALUES (?, ?)
|
|
701
|
-
ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(OPENCODE2_RELABEL_STATE_KEY, JSON.stringify(value));
|
|
556
|
+
if (!transientGitIdentityReuseLoggedDirectories.has(canonical)) {
|
|
557
|
+
transientGitIdentityReuseLoggedDirectories.add(canonical);
|
|
558
|
+
const sourceNote = cached.source === canonical ? "" : ` from ancestor ${cached.source}`;
|
|
559
|
+
log(`[magic-context] git identity resolution is temporarily unavailable for ${canonical}; reusing the last successful project identity${sourceNote} to avoid splitting project-scoped memory`);
|
|
560
|
+
}
|
|
561
|
+
return cached.identity;
|
|
702
562
|
}
|
|
703
|
-
function
|
|
704
|
-
|
|
563
|
+
function formatDubiousOwnershipWarning(canonical) {
|
|
564
|
+
return `Magic Context: git refused to read ${canonical} (dubious ownership — the repo is owned by a different user). Memory features are paused unless a previous git identity is available. Fix: git config --global --add safe.directory ${canonical}`;
|
|
565
|
+
}
|
|
566
|
+
function recordDubiousOwnershipFallback(canonical) {
|
|
567
|
+
dubiousOwnershipFallbackDirectories.add(canonical);
|
|
568
|
+
if (dubiousOwnershipLoggedDirectories.has(canonical))
|
|
705
569
|
return;
|
|
706
|
-
|
|
570
|
+
dubiousOwnershipLoggedDirectories.add(canonical);
|
|
571
|
+
log(`[magic-context] ${formatDubiousOwnershipWarning(canonical)}`);
|
|
707
572
|
}
|
|
708
|
-
function
|
|
709
|
-
const
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
return
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
573
|
+
function takeDubiousOwnershipProjectIdentityWarning(directory) {
|
|
574
|
+
const canonical = path3.resolve(directory);
|
|
575
|
+
if (!dubiousOwnershipFallbackDirectories.has(canonical))
|
|
576
|
+
return null;
|
|
577
|
+
if (dubiousOwnershipWarnedDirectories.has(canonical))
|
|
578
|
+
return null;
|
|
579
|
+
dubiousOwnershipWarnedDirectories.add(canonical);
|
|
580
|
+
return formatDubiousOwnershipWarning(canonical);
|
|
581
|
+
}
|
|
582
|
+
function canonicalUserHomeDirectory() {
|
|
583
|
+
const homeDirectory = userHomeDirectoryForIdentity();
|
|
584
|
+
try {
|
|
585
|
+
return realpathSync2.native(homeDirectory);
|
|
586
|
+
} catch {
|
|
587
|
+
return homeDirectory;
|
|
719
588
|
}
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
589
|
+
}
|
|
590
|
+
function isUserHomeDirectory(directory) {
|
|
591
|
+
if (projectDirectoryKey(directory) === projectDirectoryKey(userHomeDirectoryForIdentity()))
|
|
592
|
+
return true;
|
|
593
|
+
try {
|
|
594
|
+
return projectDirectoryKey(realpathSync2.native(path3.resolve(directory))) === projectDirectoryKey(canonicalUserHomeDirectory());
|
|
595
|
+
} catch {
|
|
596
|
+
return false;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
function assertProjectAllowed(directory, allowHomeProject) {
|
|
600
|
+
const canonical = path3.resolve(directory);
|
|
601
|
+
if (!allowHomeProject && (isUserHomeDirectory(directory) || isUserHomeDirectory(gitRootDirectory(canonical) ?? canonical))) {
|
|
602
|
+
throw new ProjectIdentityError("home_project_disabled", directory, "Home project memory is disabled; set allow_home_project to opt in");
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
function resolveProjectIdentity(directory, allowHomeProject = homeProjectPermission) {
|
|
606
|
+
assertProjectAllowed(directory, allowHomeProject);
|
|
607
|
+
const canonical = path3.resolve(directory);
|
|
608
|
+
const cachedFallback = directoryFallbackCache.get(canonical);
|
|
609
|
+
if (cachedFallback !== undefined) {
|
|
610
|
+
if (!hasGitDir(canonical)) {
|
|
611
|
+
return cachedFallback;
|
|
733
612
|
}
|
|
613
|
+
directoryFallbackCache.delete(canonical);
|
|
734
614
|
}
|
|
735
|
-
if (
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
relabelledSessions: []
|
|
745
|
-
};
|
|
615
|
+
if (getActiveCooldown(canonical) !== undefined) {
|
|
616
|
+
if (hasGitDir(canonical)) {
|
|
617
|
+
const cachedGitIdentity = reuseLastKnownGitIdentity(canonical);
|
|
618
|
+
if (cachedGitIdentity !== undefined) {
|
|
619
|
+
return cachedGitIdentity;
|
|
620
|
+
}
|
|
621
|
+
throw new ProjectIdentityError("git_identity_unavailable", directory, "Git identity unavailable; memory features paused until the retry cooldown expires");
|
|
622
|
+
}
|
|
623
|
+
return rememberedIdentityOrDirectoryFallback(canonical);
|
|
746
624
|
}
|
|
747
|
-
const relabelledSessions = [];
|
|
748
625
|
try {
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
if (
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
moved += Number(update.run(target, sessionId, source).changes ?? 0);
|
|
626
|
+
return resolveProjectIdentityStrict(directory, allowHomeProject);
|
|
627
|
+
} catch (error) {
|
|
628
|
+
if (error instanceof ProjectIdentityError && shouldUseDirectoryFallback(error)) {
|
|
629
|
+
if (error.errorClass === "no_commits")
|
|
630
|
+
return directoryFallback(canonical);
|
|
631
|
+
if (!hasGitDir(canonical)) {
|
|
632
|
+
if (error.errorClass === "permission_denied")
|
|
633
|
+
throw error;
|
|
634
|
+
const resolved = rememberedIdentityOrDirectoryFallback(canonical);
|
|
635
|
+
directoryFallbackCache.set(canonical, resolved);
|
|
636
|
+
transientFailureCooldown.delete(canonical);
|
|
637
|
+
return resolved;
|
|
762
638
|
}
|
|
763
|
-
|
|
764
|
-
|
|
639
|
+
transientFailureCooldown.set(canonical, nowMs() + TRANSIENT_FAILURE_COOLDOWN_MS);
|
|
640
|
+
const cachedGitIdentity = reuseLastKnownGitIdentity(canonical);
|
|
641
|
+
if (error.errorClass === "dubious_ownership")
|
|
642
|
+
recordDubiousOwnershipFallback(canonical);
|
|
643
|
+
if (cachedGitIdentity !== undefined) {
|
|
644
|
+
return cachedGitIdentity;
|
|
645
|
+
}
|
|
646
|
+
throw error;
|
|
765
647
|
}
|
|
766
|
-
|
|
767
|
-
evidence.close();
|
|
648
|
+
throw error;
|
|
768
649
|
}
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
650
|
+
}
|
|
651
|
+
function resolveProjectIdentityOrFallback(directory, allowHomeProject = homeProjectPermission) {
|
|
652
|
+
try {
|
|
653
|
+
return resolveProjectIdentity(directory, allowHomeProject);
|
|
654
|
+
} catch (error) {
|
|
655
|
+
const canonical = path3.resolve(directory);
|
|
656
|
+
if (hasGitDir(canonical) || error instanceof ProjectIdentityError && error.errorClass === "home_project_disabled")
|
|
657
|
+
throw error;
|
|
658
|
+
const fallback = rememberedIdentityOrDirectoryFallback(canonical);
|
|
659
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
660
|
+
log(`[magic-context] project identity resolution failed for ${canonical}; using directory fallback ${fallback}: ${message}`);
|
|
661
|
+
return fallback;
|
|
773
662
|
}
|
|
774
|
-
return {
|
|
775
|
-
status: "resolved",
|
|
776
|
-
storePath: resolution.path,
|
|
777
|
-
candidateSessionIds,
|
|
778
|
-
relabelledSessions
|
|
779
|
-
};
|
|
780
663
|
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
sql: `CREATE TRIGGER compartment_history_ai AFTER INSERT ON compartments BEGIN
|
|
802
|
-
INSERT INTO compartment_history_versions(session_id, version) VALUES (NEW.session_id, 1)
|
|
803
|
-
ON CONFLICT(session_id) DO UPDATE SET version = version + 1;
|
|
804
|
-
END`
|
|
805
|
-
},
|
|
806
|
-
{
|
|
807
|
-
type: "trigger",
|
|
808
|
-
name: "compartment_history_ad",
|
|
809
|
-
sql: `CREATE TRIGGER compartment_history_ad AFTER DELETE ON compartments BEGIN
|
|
810
|
-
INSERT INTO compartment_history_versions(session_id, version) VALUES (OLD.session_id, 1)
|
|
811
|
-
ON CONFLICT(session_id) DO UPDATE SET version = version + 1;
|
|
812
|
-
END`
|
|
813
|
-
},
|
|
814
|
-
{
|
|
815
|
-
type: "trigger",
|
|
816
|
-
name: "compartment_history_au",
|
|
817
|
-
sql: `CREATE TRIGGER compartment_history_au AFTER UPDATE ON compartments BEGIN
|
|
818
|
-
INSERT INTO compartment_history_versions(session_id, version, rewrite_version) VALUES (OLD.session_id, 1, ${externalUpdate})
|
|
819
|
-
ON CONFLICT(session_id) DO UPDATE SET version = version + 1, rewrite_version = rewrite_version + ${externalUpdate};
|
|
820
|
-
INSERT INTO compartment_history_versions(session_id, version, rewrite_version)
|
|
821
|
-
SELECT NEW.session_id, 1, ${externalUpdate} WHERE NEW.session_id != OLD.session_id
|
|
822
|
-
ON CONFLICT(session_id) DO UPDATE SET version = version + 1, rewrite_version = rewrite_version + ${externalUpdate};
|
|
823
|
-
END`
|
|
664
|
+
function hasGitDir(canonical) {
|
|
665
|
+
if (hasGitDirInAncestorChain(canonical)) {
|
|
666
|
+
return true;
|
|
667
|
+
}
|
|
668
|
+
try {
|
|
669
|
+
const realCanonical = realpathSync2.native(canonical);
|
|
670
|
+
return realCanonical !== canonical && hasGitDirInAncestorChain(realCanonical);
|
|
671
|
+
} catch {
|
|
672
|
+
return false;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
function gitRootInAncestorChain(startDirectory) {
|
|
676
|
+
let current = startDirectory;
|
|
677
|
+
while (true) {
|
|
678
|
+
if (existsSync(path3.join(current, ".git"))) {
|
|
679
|
+
try {
|
|
680
|
+
return realpathSync2.native(current);
|
|
681
|
+
} catch {
|
|
682
|
+
return path3.resolve(current);
|
|
683
|
+
}
|
|
824
684
|
}
|
|
825
|
-
|
|
685
|
+
const parent = path3.dirname(current);
|
|
686
|
+
if (parent === current) {
|
|
687
|
+
return null;
|
|
688
|
+
}
|
|
689
|
+
current = parent;
|
|
690
|
+
}
|
|
826
691
|
}
|
|
827
|
-
function
|
|
828
|
-
|
|
829
|
-
WHERE name IN ('compartment_history_versions', 'compartment_history_ai', 'compartment_history_ad', 'compartment_history_au')`).all();
|
|
830
|
-
return new Map(rows.map((row) => [`${row.type}:${row.name}`, row.sql]));
|
|
692
|
+
function hasGitDirInAncestorChain(startDirectory) {
|
|
693
|
+
return gitRootInAncestorChain(startDirectory) !== null;
|
|
831
694
|
}
|
|
832
|
-
function
|
|
833
|
-
|
|
695
|
+
function gitRootDirectory(canonical) {
|
|
696
|
+
const direct = gitRootInAncestorChain(canonical);
|
|
697
|
+
if (direct)
|
|
698
|
+
return direct;
|
|
699
|
+
try {
|
|
700
|
+
const realCanonical = realpathSync2.native(canonical);
|
|
701
|
+
return realCanonical === canonical ? null : gitRootInAncestorChain(realCanonical);
|
|
702
|
+
} catch {
|
|
703
|
+
return null;
|
|
704
|
+
}
|
|
834
705
|
}
|
|
835
|
-
function
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
const existingTable = installed.get(`table:${table.name}`);
|
|
847
|
-
if (existingTable !== undefined && existingTable !== table.sql) {
|
|
848
|
-
throw new Error("compartment_history_versions schema differs from v93; refusing to replace revision data");
|
|
706
|
+
function directoryHasGitMetadata(directory) {
|
|
707
|
+
return gitRootDirectory(path3.resolve(directory)) !== null;
|
|
708
|
+
}
|
|
709
|
+
function describeUnresolvedProjectIdentity(directory) {
|
|
710
|
+
const resolvedDirectory = path3.resolve(directory);
|
|
711
|
+
const canonicalHome = canonicalUserHomeDirectory();
|
|
712
|
+
const canonicalDirectory = (() => {
|
|
713
|
+
try {
|
|
714
|
+
return realpathSync2.native(resolvedDirectory);
|
|
715
|
+
} catch {
|
|
716
|
+
return resolvedDirectory;
|
|
849
717
|
}
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
718
|
+
})();
|
|
719
|
+
if (isUserHomeDirectory(directory) || gitRootDirectory(canonicalDirectory) === canonicalHome) {
|
|
720
|
+
return `this session runs in your home directory (${canonicalHome}), which Magic Context does not treat as a project. ` + "Start OpenCode inside a project folder, or set `allow_home_project: true` in the user-level " + "magic-context.jsonc (~/.config/cortexkit/) to give home sessions their own memory.";
|
|
721
|
+
}
|
|
722
|
+
if (hasGitDir(resolvedDirectory))
|
|
723
|
+
return `git identity resolution for ${resolvedDirectory} is temporarily unavailable. Memory features are paused; retry after git access recovers.`;
|
|
724
|
+
return `the session directory ${resolvedDirectory} could not be read as a project.`;
|
|
725
|
+
}
|
|
726
|
+
function isUsableProjectIdentity(identity) {
|
|
727
|
+
return typeof identity === "string" && identity.trim().length > 0;
|
|
728
|
+
}
|
|
729
|
+
function resolveProjectIdentityForSession(directory, allowHomeProject = homeProjectPermission) {
|
|
730
|
+
const resolvedDirectory = path3.resolve(directory);
|
|
731
|
+
const cacheKey = `${allowHomeProject ? "1" : "0"}\x00${resolvedDirectory}`;
|
|
732
|
+
const cached = sessionIdentityCache.get(cacheKey);
|
|
733
|
+
if (cached && (cached.revalidateAt === null || nowMs() < cached.revalidateAt) && !(cached.identity?.startsWith("dir:") && hasGitDir(resolvedDirectory))) {
|
|
734
|
+
return cached.identity;
|
|
735
|
+
}
|
|
736
|
+
sessionIdentityCache.delete(cacheKey);
|
|
737
|
+
filesystemProbeObserverForTests?.();
|
|
738
|
+
const canonicalDirectory = (() => {
|
|
739
|
+
try {
|
|
740
|
+
filesystemProbeObserverForTests?.();
|
|
741
|
+
return realpathSync2.native(resolvedDirectory);
|
|
742
|
+
} catch {
|
|
743
|
+
return resolvedDirectory;
|
|
744
|
+
}
|
|
745
|
+
})();
|
|
746
|
+
filesystemProbeObserverForTests?.();
|
|
747
|
+
const homeRoot = gitRootDirectory(canonicalDirectory);
|
|
748
|
+
const inheritsHomeRepository = homeRoot !== null && isUserHomeDirectory(homeRoot);
|
|
749
|
+
let identity;
|
|
750
|
+
if (!allowHomeProject && (isUserHomeDirectory(directory) || inheritsHomeRepository)) {
|
|
751
|
+
identity = undefined;
|
|
752
|
+
} else {
|
|
753
|
+
try {
|
|
754
|
+
identity = resolveProjectIdentityOrFallback(isUserHomeDirectory(directory) ? canonicalUserHomeDirectory() : directory, allowHomeProject);
|
|
755
|
+
} catch (error) {
|
|
756
|
+
if (!(error instanceof ProjectIdentityError))
|
|
757
|
+
throw error;
|
|
758
|
+
const reason = error.errorClass === "git_identity_unavailable" ? pausedIdentityReasons.get(resolvedDirectory) ?? error.errorClass : error.errorClass;
|
|
759
|
+
pausedIdentityReasons.set(resolvedDirectory, reason);
|
|
760
|
+
const logKey = `${resolvedDirectory}\x00${reason}`;
|
|
761
|
+
if (!pausedIdentityLogged.has(logKey)) {
|
|
762
|
+
pausedIdentityLogged.add(logKey);
|
|
763
|
+
log(`[magic-context] memory features paused for ${resolvedDirectory}: ${reason}`);
|
|
764
|
+
}
|
|
765
|
+
identity = undefined;
|
|
859
766
|
}
|
|
860
|
-
}).immediate();
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
// src/features/magic-context/storage-schema-helpers.ts
|
|
864
|
-
function ensureColumn(db, table, column, definition) {
|
|
865
|
-
if (!/^[a-z][a-z0-9_]*$/.test(table) || !/^[a-z][a-z0-9_]*$/.test(column) || !/^[A-Z0-9_"'(),[\]\s]+$/i.test(definition)) {
|
|
866
|
-
throw new Error(`Unsafe schema identifier: ${table}.${column} ${definition}`);
|
|
867
767
|
}
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
768
|
+
if (identity !== undefined)
|
|
769
|
+
pausedIdentityReasons.delete(resolvedDirectory);
|
|
770
|
+
sessionIdentityCache.set(cacheKey, {
|
|
771
|
+
identity,
|
|
772
|
+
revalidateAt: identity?.startsWith("git:") === true ? null : nowMs() + TRANSIENT_FAILURE_COOLDOWN_MS
|
|
773
|
+
});
|
|
774
|
+
return identity;
|
|
775
|
+
}
|
|
776
|
+
function normalizeStoredProjectPath(rawOrStored) {
|
|
777
|
+
if (rawOrStored.startsWith("git:") || rawOrStored.startsWith("dir:")) {
|
|
778
|
+
return rawOrStored;
|
|
871
779
|
}
|
|
872
780
|
try {
|
|
873
|
-
|
|
874
|
-
} catch
|
|
875
|
-
|
|
876
|
-
if (recheck.some((row) => row.name === column)) {
|
|
877
|
-
return;
|
|
878
|
-
}
|
|
879
|
-
throw err;
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
function healAllNullColumns(db) {
|
|
883
|
-
const existingColumns = getSessionMetaColumns(db);
|
|
884
|
-
const fallbacks = [
|
|
885
|
-
["cache_ttl", ""],
|
|
886
|
-
["last_nudge_band", ""],
|
|
887
|
-
["last_nudge_level", ""],
|
|
888
|
-
["channel2_nudge_claim_token", ""],
|
|
889
|
-
["last_transform_error", ""],
|
|
890
|
-
["nudge_anchor_message_id", ""],
|
|
891
|
-
["nudge_anchor_text", ""],
|
|
892
|
-
["sticky_turn_reminder_text", ""],
|
|
893
|
-
["sticky_turn_reminder_message_id", ""],
|
|
894
|
-
["note_nudge_trigger_message_id", ""],
|
|
895
|
-
["note_nudge_sticky_text", ""],
|
|
896
|
-
["note_nudge_sticky_message_id", ""],
|
|
897
|
-
["last_todo_state", ""],
|
|
898
|
-
["todo_synthetic_call_id", ""],
|
|
899
|
-
["todo_synthetic_anchor_message_id", ""],
|
|
900
|
-
["todo_synthetic_state_json", ""],
|
|
901
|
-
["system_prompt_hash", ""],
|
|
902
|
-
["stripped_placeholder_ids", ""],
|
|
903
|
-
["stale_reduce_stripped_ids", ""],
|
|
904
|
-
["processed_image_stripped_ids", ""],
|
|
905
|
-
["merged_reasoning_stripped_ids", ""],
|
|
906
|
-
["thinking_binding_recovery_target", ""],
|
|
907
|
-
["trailing_blank_decisions", ""],
|
|
908
|
-
["memory_block_cache", ""],
|
|
909
|
-
["memory_block_ids", ""],
|
|
910
|
-
["compaction_marker_state", ""],
|
|
911
|
-
["key_files", ""],
|
|
912
|
-
["times_execute_threshold_reached", 0],
|
|
913
|
-
["compartment_in_progress", 0],
|
|
914
|
-
["historian_failure_count", 0],
|
|
915
|
-
["cleared_reasoning_through_tag", 0],
|
|
916
|
-
["memory_block_count", 0],
|
|
917
|
-
["system_prompt_tokens", 0],
|
|
918
|
-
["conversation_tokens", 0],
|
|
919
|
-
["tool_call_tokens", 0],
|
|
920
|
-
["note_nudge_trigger_pending", 0],
|
|
921
|
-
["observed_safe_input_tokens", 0],
|
|
922
|
-
["cache_alert_sent", 0],
|
|
923
|
-
["new_work_tokens", 0],
|
|
924
|
-
["total_input_tokens", 0],
|
|
925
|
-
["last_emergency_input_sample", 0],
|
|
926
|
-
["channel2_nudge_claimed_at", 0],
|
|
927
|
-
["last_usage_context_limit", 0],
|
|
928
|
-
["prior_boundary_ordinal", 1],
|
|
929
|
-
["protected_tail_policy_version", 0],
|
|
930
|
-
["protected_tail_drain_window_started_at", 0],
|
|
931
|
-
["protected_tail_drain_tokens", 0],
|
|
932
|
-
["recovery_no_eligible_head_count", 0],
|
|
933
|
-
["force_emergency_bypass_window_start", 0],
|
|
934
|
-
["force_emergency_bypass_used", 0],
|
|
935
|
-
["emergency_drain_active", 0],
|
|
936
|
-
["historian_drain_failure_at", 0]
|
|
937
|
-
];
|
|
938
|
-
const presentFallbacks = fallbacks.filter(([column]) => existingColumns.has(column));
|
|
939
|
-
if (presentFallbacks.length > 0) {
|
|
940
|
-
const assignments = presentFallbacks.map(([column]) => `${column} = COALESCE(${column}, ?)`).join(", ");
|
|
941
|
-
const nullPredicate = presentFallbacks.map(([column]) => `${column} IS NULL`).join(" OR ");
|
|
942
|
-
db.prepare(`UPDATE session_meta SET ${assignments} WHERE ${nullPredicate}`).run(...presentFallbacks.map(([, fallback]) => fallback));
|
|
781
|
+
return resolveProjectIdentity(rawOrStored);
|
|
782
|
+
} catch {
|
|
783
|
+
return rawOrStored;
|
|
943
784
|
}
|
|
944
|
-
healMissingMemoryBlockIds(db, existingColumns);
|
|
945
785
|
}
|
|
946
|
-
function
|
|
947
|
-
|
|
948
|
-
return new Set(rows.flatMap((row) => typeof row.name === "string" ? [row.name] : []));
|
|
949
|
-
}
|
|
950
|
-
function healMissingMemoryBlockIds(db, columns) {
|
|
951
|
-
if (!columns.has("memory_block_cache") || !columns.has("memory_block_ids") || !columns.has("memory_block_count")) {
|
|
952
|
-
return;
|
|
953
|
-
}
|
|
954
|
-
db.prepare("UPDATE session_meta SET memory_block_cache = '' WHERE memory_block_cache != '' AND (memory_block_ids IS NULL OR memory_block_ids = '') AND memory_block_count > 0").run();
|
|
786
|
+
function storedPathBelongsToIdentity(storedProjectPath, projectIdentity) {
|
|
787
|
+
return storedProjectPath === projectIdentity || normalizeStoredProjectPath(storedProjectPath) === projectIdentity;
|
|
955
788
|
}
|
|
956
789
|
|
|
957
|
-
// src/features/magic-context/
|
|
958
|
-
import {
|
|
959
|
-
|
|
960
|
-
// src/features/magic-context/memory/project-identity.ts
|
|
961
|
-
import { execFileSync } from "node:child_process";
|
|
962
|
-
import { createHash as createHash3 } from "node:crypto";
|
|
963
|
-
import { existsSync, realpathSync as realpathSync2, statSync } from "node:fs";
|
|
964
|
-
import { homedir } from "node:os";
|
|
965
|
-
import path3 from "node:path";
|
|
966
|
-
|
|
967
|
-
// src/features/magic-context/memory/project-identity-cache.ts
|
|
968
|
-
import { createHash as createHash2, randomUUID } from "node:crypto";
|
|
969
|
-
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
970
|
-
import path2 from "node:path";
|
|
790
|
+
// src/features/magic-context/migrations.ts
|
|
791
|
+
import { isMainThread } from "node:worker_threads";
|
|
971
792
|
|
|
972
|
-
// src/
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
793
|
+
// src/features/magic-context/memory/constants.ts
|
|
794
|
+
var V2_MEMORY_CATEGORIES = [
|
|
795
|
+
"PROJECT_RULES",
|
|
796
|
+
"ARCHITECTURE",
|
|
797
|
+
"CONSTRAINTS",
|
|
798
|
+
"CONFIG_VALUES",
|
|
799
|
+
"NAMING"
|
|
800
|
+
];
|
|
801
|
+
var PROMOTABLE_CATEGORIES = [
|
|
802
|
+
...V2_MEMORY_CATEGORIES,
|
|
803
|
+
"ARCHITECTURE_DECISIONS",
|
|
804
|
+
"CONFIG_DEFAULTS",
|
|
805
|
+
"USER_PREFERENCES",
|
|
806
|
+
"USER_DIRECTIVES",
|
|
807
|
+
"ENVIRONMENT",
|
|
808
|
+
"WORKFLOW_RULES",
|
|
809
|
+
"KNOWN_ISSUES"
|
|
810
|
+
];
|
|
811
|
+
var CATEGORY_PRIORITY = [
|
|
812
|
+
...V2_MEMORY_CATEGORIES,
|
|
813
|
+
"USER_DIRECTIVES",
|
|
814
|
+
"USER_PREFERENCES",
|
|
815
|
+
"CONFIG_DEFAULTS",
|
|
816
|
+
"ARCHITECTURE_DECISIONS",
|
|
817
|
+
"ENVIRONMENT",
|
|
818
|
+
"WORKFLOW_RULES",
|
|
819
|
+
"KNOWN_ISSUES"
|
|
820
|
+
];
|
|
821
|
+
var MEMORY_CATEGORY_ORDER_UNKNOWN = 99;
|
|
822
|
+
var MEMORY_CATEGORY_ORDER_PRIORITY = CATEGORY_PRIORITY.reduce((acc, category, index) => {
|
|
823
|
+
acc[category] = index;
|
|
824
|
+
return acc;
|
|
825
|
+
}, {});
|
|
826
|
+
var MEMORY_CATEGORY_ORDER_SQL = `CASE category ${CATEGORY_PRIORITY.map((category, index) => `WHEN '${category}' THEN ${index}`).join(" ")} ELSE ${MEMORY_CATEGORY_ORDER_UNKNOWN} END`;
|
|
827
|
+
function getMemoryCategoryOrder(category) {
|
|
828
|
+
return MEMORY_CATEGORY_ORDER_PRIORITY[category] ?? MEMORY_CATEGORY_ORDER_UNKNOWN;
|
|
989
829
|
}
|
|
830
|
+
var CATEGORY_DEFAULT_TTL = {
|
|
831
|
+
WORKFLOW_RULES: 90 * 24 * 60 * 60 * 1000,
|
|
832
|
+
KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
|
|
833
|
+
};
|
|
990
834
|
|
|
991
|
-
// src/
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
renameSync(temporary, destination);
|
|
1003
|
-
} catch {}
|
|
1004
|
-
}
|
|
1005
|
-
function readRememberedGitIdentity(directory, storageDir) {
|
|
1006
|
-
try {
|
|
1007
|
-
const record = JSON.parse(readFileSync(cachePath(directory, storageDir), "utf8"));
|
|
1008
|
-
if (record.directory === projectDirectoryKey(directory) && /^git:[0-9a-f]{7,64}$/.test(record.identity))
|
|
1009
|
-
return record.identity;
|
|
1010
|
-
} catch {}
|
|
1011
|
-
return;
|
|
835
|
+
// src/shared/xml-unescape.ts
|
|
836
|
+
var XML_ENTITY_REGEX = /&(amp|apos|quot|lt|gt);/g;
|
|
837
|
+
var XML_ENTITIES = {
|
|
838
|
+
amp: "&",
|
|
839
|
+
apos: "'",
|
|
840
|
+
quot: '"',
|
|
841
|
+
lt: "<",
|
|
842
|
+
gt: ">"
|
|
843
|
+
};
|
|
844
|
+
function unescapeXml(s) {
|
|
845
|
+
return s.replace(XML_ENTITY_REGEX, (entity, name) => XML_ENTITIES[name] ?? entity);
|
|
1012
846
|
}
|
|
1013
847
|
|
|
1014
|
-
// src/
|
|
1015
|
-
var
|
|
1016
|
-
var
|
|
1017
|
-
var
|
|
1018
|
-
var
|
|
1019
|
-
var
|
|
1020
|
-
var
|
|
1021
|
-
var
|
|
1022
|
-
var
|
|
1023
|
-
var
|
|
1024
|
-
var
|
|
1025
|
-
var
|
|
1026
|
-
var
|
|
1027
|
-
var
|
|
1028
|
-
var
|
|
1029
|
-
var
|
|
1030
|
-
var
|
|
1031
|
-
|
|
1032
|
-
|
|
848
|
+
// src/hooks/magic-context/compartment-parser.ts
|
|
849
|
+
var COMPARTMENT_OPEN_REGEX = /<compartment\s+((?:[^>"]|"[^"\n]*")*?)\s*>/g;
|
|
850
|
+
var COMPARTMENT_CLOSE_TAG = "</compartment>";
|
|
851
|
+
var COMPARTMENT_CLOSE_FOLLOWER_REGEX = /^\s*(?:$|<(?:compartment\s|\/compartments\s*>|\/output\s*>|(?:facts|events|meta|user_observations|primer_candidates|unprocessed_from)\s*>))/;
|
|
852
|
+
var ATTR_START_REGEX = /\bstart="(\d+)"/;
|
|
853
|
+
var ATTR_END_REGEX = /\bend="(\d+)"/;
|
|
854
|
+
var ATTR_TITLE_REGEX = /\btitle="([^"]*)"/;
|
|
855
|
+
var ATTR_EPISODE_REGEX = /\bepisode_type="([^"]*)"/;
|
|
856
|
+
var ATTR_IMPORTANCE_REGEX = /\bimportance="(\d+)"/;
|
|
857
|
+
var TIER_OPEN_REGEX = /<p(\d)\s*(\/?)>/g;
|
|
858
|
+
var TIER_CLOSE_REGEX = /<\/p(\d)\s*>/g;
|
|
859
|
+
var TIER_CLOSE_FOLLOWER_REGEX = /^\s*(?:$|<\/?p\d\s*\/?>)/;
|
|
860
|
+
var TIER_CLOSE_ANY_REGEX = /<\/p\d/;
|
|
861
|
+
var TIER_OPEN_ANY_REGEX = /<p\d/;
|
|
862
|
+
var CATEGORY_BLOCK_REGEX = /<([A-Za-z_][A-Za-z0-9_-]*)>(.*?)<\/\1>/gs;
|
|
863
|
+
var HISTORIAN_CATEGORIES = new Set(V2_MEMORY_CATEGORIES);
|
|
864
|
+
var FACT_ITEM_REGEX = /^\s*\*\s*(.+)$/gm;
|
|
865
|
+
var UNPROCESSED_REGEX = /<unprocessed_from>(\d+)<\/unprocessed_from>/;
|
|
866
|
+
var USER_OBSERVATIONS_REGEX = /<user_observations>(.*?)<\/user_observations>/s;
|
|
867
|
+
var USER_OBS_ITEM_REGEX = /^\s*\*\s*(.+)$/gm;
|
|
868
|
+
var PRIMER_CANDIDATES_REGEX = /<primer_candidates>(.*?)<\/primer_candidates>/s;
|
|
869
|
+
var PRIMER_ELEMENT_REGEX = /<primer\s+at_compartment="(\d+)"\s*>(.*?)<\/primer>/gs;
|
|
870
|
+
var PRIMER_ITEM_REGEX = /^\s*(?:\*|-|\d+\.)\s*(.+)$/gm;
|
|
871
|
+
var FACTS_BLOCK_REGEX = /<facts>(.*?)<\/facts>/s;
|
|
872
|
+
var EVENTS_BLOCK_REGEX = /<events>(.*?)<\/events>/s;
|
|
873
|
+
var EVENT_ELEMENT_REGEX = /<([a-z_]+)\s+at_compartment="(\d+)"\s*>(.*?)<\/\1>/gs;
|
|
874
|
+
var EVENT_FIELD_REGEX = /<([a-z_]+)\s*>(.*?)<\/\1>/gs;
|
|
875
|
+
function searchFrom(s, regex, from) {
|
|
876
|
+
const at = s.slice(from).search(regex);
|
|
877
|
+
return at === -1 ? -1 : from + at;
|
|
1033
878
|
}
|
|
1034
|
-
function
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
879
|
+
function hasTierOpenerFrom(inner, digit, from) {
|
|
880
|
+
return new RegExp(`<p${digit}\\s*/?>`).test(inner.slice(from));
|
|
881
|
+
}
|
|
882
|
+
function findMatchingTierClose(inner, bodyStart, digit, found) {
|
|
883
|
+
const closeRegex = new RegExp(TIER_CLOSE_REGEX.source, "g");
|
|
884
|
+
closeRegex.lastIndex = bodyStart;
|
|
885
|
+
for (let close = closeRegex.exec(inner);close; close = closeRegex.exec(inner)) {
|
|
886
|
+
const end = close.index + close[0].length;
|
|
887
|
+
if (Number(close[1]) !== digit)
|
|
888
|
+
continue;
|
|
889
|
+
if (!TIER_CLOSE_FOLLOWER_REGEX.test(inner.slice(end)))
|
|
890
|
+
continue;
|
|
891
|
+
const body = inner.slice(bodyStart, close.index);
|
|
892
|
+
for (const open of body.matchAll(new RegExp(TIER_OPEN_REGEX.source, "g"))) {
|
|
893
|
+
const other = Number(open[1]);
|
|
894
|
+
const otherwiseMissing = other >= 1 && other <= 4 && other !== digit && !found.has(other) && !hasTierOpenerFrom(inner, other, end);
|
|
895
|
+
if (otherwiseMissing)
|
|
896
|
+
return;
|
|
1044
897
|
}
|
|
1045
|
-
return
|
|
898
|
+
return { start: close.index, end };
|
|
1046
899
|
}
|
|
900
|
+
return;
|
|
1047
901
|
}
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
if (
|
|
1062
|
-
|
|
902
|
+
function extractTiers(inner) {
|
|
903
|
+
const tiers = new Map;
|
|
904
|
+
const openRegex = new RegExp(TIER_OPEN_REGEX.source, "g");
|
|
905
|
+
let pos = 0;
|
|
906
|
+
for (;; ) {
|
|
907
|
+
openRegex.lastIndex = pos;
|
|
908
|
+
const open = openRegex.exec(inner);
|
|
909
|
+
if (!open)
|
|
910
|
+
break;
|
|
911
|
+
const digit = Number(open[1]);
|
|
912
|
+
const bodyStart = open.index + open[0].length;
|
|
913
|
+
let bodyEnd = bodyStart;
|
|
914
|
+
pos = bodyStart;
|
|
915
|
+
if (open[2] !== "/") {
|
|
916
|
+
const close = findMatchingTierClose(inner, bodyStart, digit, tiers);
|
|
917
|
+
if (close) {
|
|
918
|
+
bodyEnd = close.start;
|
|
919
|
+
pos = close.end;
|
|
920
|
+
} else {
|
|
921
|
+
const closeAt = searchFrom(inner, TIER_CLOSE_ANY_REGEX, bodyStart);
|
|
922
|
+
bodyEnd = closeAt === -1 ? inner.length : closeAt;
|
|
923
|
+
pos = closeAt === -1 ? inner.length : closeAt + 1;
|
|
924
|
+
const openInside = searchFrom(inner.slice(0, bodyEnd), TIER_OPEN_ANY_REGEX, bodyStart);
|
|
925
|
+
if (openInside !== -1) {
|
|
926
|
+
bodyEnd = openInside;
|
|
927
|
+
pos = openInside;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
if (!tiers.has(digit)) {
|
|
932
|
+
tiers.set(digit, unescapeXml(inner.slice(bodyStart, bodyEnd).trim()));
|
|
1063
933
|
}
|
|
1064
934
|
}
|
|
935
|
+
return tiers;
|
|
1065
936
|
}
|
|
1066
|
-
function
|
|
1067
|
-
|
|
1068
|
-
}
|
|
1069
|
-
function getErrorCode(error) {
|
|
1070
|
-
if (error === null || typeof error !== "object" || !("code" in error)) {
|
|
1071
|
-
return;
|
|
1072
|
-
}
|
|
1073
|
-
const code = error.code;
|
|
1074
|
-
return typeof code === "string" ? code : undefined;
|
|
937
|
+
function extractTiersFromInner(inner) {
|
|
938
|
+
const tiers = extractTiers(inner);
|
|
939
|
+
return { p1: tiers.get(1), p2: tiers.get(2), p3: tiers.get(3), p4: tiers.get(4) };
|
|
1075
940
|
}
|
|
1076
|
-
function
|
|
1077
|
-
|
|
1078
|
-
|
|
941
|
+
function findCompartmentElements(text) {
|
|
942
|
+
const elements = [];
|
|
943
|
+
const openRegex = new RegExp(COMPARTMENT_OPEN_REGEX.source, "g");
|
|
944
|
+
let cursor = 0;
|
|
945
|
+
for (;; ) {
|
|
946
|
+
openRegex.lastIndex = cursor;
|
|
947
|
+
const open = openRegex.exec(text);
|
|
948
|
+
if (!open)
|
|
949
|
+
break;
|
|
950
|
+
const bodyStart = open.index + open[0].length;
|
|
951
|
+
const nextOpen = openRegex.exec(text);
|
|
952
|
+
const windowEnd = nextOpen ? nextOpen.index : text.length;
|
|
953
|
+
const firstClose = text.indexOf(COMPARTMENT_CLOSE_TAG, bodyStart);
|
|
954
|
+
if (firstClose === -1)
|
|
955
|
+
break;
|
|
956
|
+
let closeAt = firstClose;
|
|
957
|
+
for (let at = firstClose;at !== -1 && at < windowEnd; at = text.indexOf(COMPARTMENT_CLOSE_TAG, at + 1)) {
|
|
958
|
+
if (COMPARTMENT_CLOSE_FOLLOWER_REGEX.test(text.slice(at + COMPARTMENT_CLOSE_TAG.length))) {
|
|
959
|
+
closeAt = at;
|
|
960
|
+
break;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
const end = closeAt + COMPARTMENT_CLOSE_TAG.length;
|
|
964
|
+
elements.push({
|
|
965
|
+
start: open.index,
|
|
966
|
+
end,
|
|
967
|
+
attrs: open[1],
|
|
968
|
+
inner: text.slice(bodyStart, closeAt)
|
|
969
|
+
});
|
|
970
|
+
cursor = end;
|
|
1079
971
|
}
|
|
1080
|
-
|
|
1081
|
-
return typeof signal === "string" ? signal : undefined;
|
|
972
|
+
return elements;
|
|
1082
973
|
}
|
|
1083
|
-
function
|
|
1084
|
-
|
|
1085
|
-
|
|
974
|
+
function textOutsideCompartments(text, elements) {
|
|
975
|
+
let outside = "";
|
|
976
|
+
let last = 0;
|
|
977
|
+
for (const element of elements) {
|
|
978
|
+
outside += text.slice(last, element.start);
|
|
979
|
+
last = element.end;
|
|
1086
980
|
}
|
|
1087
|
-
return
|
|
981
|
+
return outside + text.slice(last);
|
|
1088
982
|
}
|
|
1089
|
-
function
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
983
|
+
function parseCompartmentOutput(text) {
|
|
984
|
+
const compartments = [];
|
|
985
|
+
const facts = [];
|
|
986
|
+
let droppedFactBlocks = 0;
|
|
987
|
+
let droppedFacts = 0;
|
|
988
|
+
const elements = findCompartmentElements(text);
|
|
989
|
+
for (const { attrs, inner } of elements) {
|
|
990
|
+
const startMatch = attrs.match(ATTR_START_REGEX);
|
|
991
|
+
const endMatch = attrs.match(ATTR_END_REGEX);
|
|
992
|
+
const titleMatch = attrs.match(ATTR_TITLE_REGEX);
|
|
993
|
+
if (!startMatch || !endMatch || !titleMatch)
|
|
994
|
+
continue;
|
|
995
|
+
const startMessage = parseInt(startMatch[1], 10);
|
|
996
|
+
const endMessage = parseInt(endMatch[1], 10);
|
|
997
|
+
const title = unescapeXml(titleMatch[1]);
|
|
998
|
+
if (Number.isNaN(startMessage) || Number.isNaN(endMessage) || !title)
|
|
999
|
+
continue;
|
|
1000
|
+
const episodeMatch = attrs.match(ATTR_EPISODE_REGEX);
|
|
1001
|
+
const importanceMatch = attrs.match(ATTR_IMPORTANCE_REGEX);
|
|
1002
|
+
const episodeType = episodeMatch ? unescapeXml(episodeMatch[1]) : undefined;
|
|
1003
|
+
const importance = importanceMatch ? parseInt(importanceMatch[1], 10) : undefined;
|
|
1004
|
+
const { p1, p2, p3, p4 } = extractTiersFromInner(inner);
|
|
1005
|
+
if (typeof p1 === "string" && p1.length > 0) {
|
|
1006
|
+
compartments.push({
|
|
1007
|
+
startMessage,
|
|
1008
|
+
endMessage,
|
|
1009
|
+
title,
|
|
1010
|
+
content: p1,
|
|
1011
|
+
p1,
|
|
1012
|
+
p2: typeof p2 === "string" ? p2 : p1,
|
|
1013
|
+
p3: typeof p3 === "string" ? p3 : typeof p2 === "string" ? p2 : p1,
|
|
1014
|
+
p4: typeof p4 === "string" ? p4 : "",
|
|
1015
|
+
importance,
|
|
1016
|
+
episodeType
|
|
1017
|
+
});
|
|
1018
|
+
continue;
|
|
1019
|
+
}
|
|
1020
|
+
const content = unescapeXml(inner.trim());
|
|
1021
|
+
if (content) {
|
|
1022
|
+
compartments.push({
|
|
1023
|
+
startMessage,
|
|
1024
|
+
endMessage,
|
|
1025
|
+
title,
|
|
1026
|
+
content,
|
|
1027
|
+
importance,
|
|
1028
|
+
episodeType
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1096
1031
|
}
|
|
1097
|
-
|
|
1098
|
-
|
|
1032
|
+
const outside = textOutsideCompartments(text, elements);
|
|
1033
|
+
const factsBlockMatch = outside.match(FACTS_BLOCK_REGEX);
|
|
1034
|
+
const factsScope = factsBlockMatch ? factsBlockMatch[1] : outside.replace(EVENTS_BLOCK_REGEX, "").replace(/<(meta|user_observations|primer_candidates)>.*?<\/\1>/gs, "").replace(/<\/?(?:output|compartments)>/g, "");
|
|
1035
|
+
for (const categoryMatch of factsScope.matchAll(CATEGORY_BLOCK_REGEX)) {
|
|
1036
|
+
const category = categoryMatch[1];
|
|
1037
|
+
const blockContent = categoryMatch[2];
|
|
1038
|
+
const items = [...blockContent.matchAll(FACT_ITEM_REGEX)].map((match) => unescapeXml(match[1].trim())).filter(Boolean);
|
|
1039
|
+
if (!HISTORIAN_CATEGORIES.has(category)) {
|
|
1040
|
+
if (items.length === 0)
|
|
1041
|
+
continue;
|
|
1042
|
+
droppedFactBlocks++;
|
|
1043
|
+
droppedFacts += items.length;
|
|
1044
|
+
log(`[historian] Dropped <facts> category ${category} (${items.length} facts)`);
|
|
1045
|
+
continue;
|
|
1046
|
+
}
|
|
1047
|
+
for (const content of items)
|
|
1048
|
+
facts.push({ category, content });
|
|
1099
1049
|
}
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
const
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
}
|
|
1110
|
-
function assertDirectoryUsable(canonicalDirectory, rawDirectory) {
|
|
1111
|
-
try {
|
|
1112
|
-
const stat = statSync(canonicalDirectory);
|
|
1113
|
-
if (!stat.isDirectory()) {
|
|
1114
|
-
throw new ProjectIdentityError("unknown", rawDirectory, `Project path is not a directory: ${canonicalDirectory}`);
|
|
1050
|
+
const unprocessedMatch = outside.match(UNPROCESSED_REGEX);
|
|
1051
|
+
const unprocessedFrom = unprocessedMatch ? parseInt(unprocessedMatch[1], 10) : null;
|
|
1052
|
+
const userObservations = [];
|
|
1053
|
+
const userObsMatch = outside.match(USER_OBSERVATIONS_REGEX);
|
|
1054
|
+
if (userObsMatch) {
|
|
1055
|
+
for (const itemMatch of userObsMatch[1].matchAll(USER_OBS_ITEM_REGEX)) {
|
|
1056
|
+
const obs = unescapeXml(itemMatch[1].trim());
|
|
1057
|
+
if (obs)
|
|
1058
|
+
userObservations.push(obs);
|
|
1115
1059
|
}
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
|
|
1060
|
+
}
|
|
1061
|
+
const primerCandidates = [];
|
|
1062
|
+
const primerMatch = outside.match(PRIMER_CANDIDATES_REGEX);
|
|
1063
|
+
if (primerMatch) {
|
|
1064
|
+
const block = primerMatch[1];
|
|
1065
|
+
let sawElement = false;
|
|
1066
|
+
for (const el of block.matchAll(PRIMER_ELEMENT_REGEX)) {
|
|
1067
|
+
sawElement = true;
|
|
1068
|
+
const question = unescapeXml(el[2].trim());
|
|
1069
|
+
if (question) {
|
|
1070
|
+
primerCandidates.push({
|
|
1071
|
+
question,
|
|
1072
|
+
originCompartmentIndex: Number.parseInt(el[1], 10)
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1119
1075
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1076
|
+
if (!sawElement) {
|
|
1077
|
+
for (const itemMatch of block.matchAll(PRIMER_ITEM_REGEX)) {
|
|
1078
|
+
const question = unescapeXml(itemMatch[1].trim());
|
|
1079
|
+
if (question)
|
|
1080
|
+
primerCandidates.push({ question });
|
|
1081
|
+
}
|
|
1123
1082
|
}
|
|
1124
|
-
throw new ProjectIdentityError("unknown", rawDirectory, `Unable to access project directory: ${canonicalDirectory}`, asError(error));
|
|
1125
1083
|
}
|
|
1084
|
+
const events = parseEvents(outside);
|
|
1085
|
+
const sortedByStart = compartments.map((compartment, emitted) => ({ compartment, emitted })).sort((a, b) => a.compartment.startMessage - b.compartment.startMessage);
|
|
1086
|
+
const sortedPosition = [];
|
|
1087
|
+
sortedByStart.forEach(({ emitted }, sorted) => {
|
|
1088
|
+
sortedPosition[emitted] = sorted + 1;
|
|
1089
|
+
});
|
|
1090
|
+
const remapAnchor = (anchor) => anchor >= 1 && anchor <= sortedPosition.length ? sortedPosition[anchor - 1] : anchor;
|
|
1091
|
+
return {
|
|
1092
|
+
compartments: sortedByStart.map(({ compartment }) => compartment),
|
|
1093
|
+
facts,
|
|
1094
|
+
droppedFactBlocks,
|
|
1095
|
+
droppedFacts,
|
|
1096
|
+
events: events.map((event) => event.atCompartment === null ? event : { ...event, atCompartment: remapAnchor(event.atCompartment) }),
|
|
1097
|
+
unprocessedFrom,
|
|
1098
|
+
userObservations,
|
|
1099
|
+
primerCandidates: primerCandidates.map((candidate) => candidate.originCompartmentIndex === undefined ? candidate : {
|
|
1100
|
+
...candidate,
|
|
1101
|
+
originCompartmentIndex: remapAnchor(candidate.originCompartmentIndex)
|
|
1102
|
+
})
|
|
1103
|
+
};
|
|
1126
1104
|
}
|
|
1127
|
-
function
|
|
1128
|
-
const
|
|
1129
|
-
|
|
1130
|
-
|
|
1105
|
+
function parseEvents(text) {
|
|
1106
|
+
const blockMatch = text.match(EVENTS_BLOCK_REGEX);
|
|
1107
|
+
if (!blockMatch)
|
|
1108
|
+
return [];
|
|
1109
|
+
const block = blockMatch[1];
|
|
1110
|
+
const events = [];
|
|
1111
|
+
for (const elMatch of block.matchAll(EVENT_ELEMENT_REGEX)) {
|
|
1112
|
+
const kind = elMatch[1];
|
|
1113
|
+
const atRaw = parseInt(elMatch[2], 10);
|
|
1114
|
+
const atCompartment = Number.isNaN(atRaw) ? null : atRaw;
|
|
1115
|
+
const fields = {};
|
|
1116
|
+
for (const fieldMatch of elMatch[3].matchAll(EVENT_FIELD_REGEX)) {
|
|
1117
|
+
const name = fieldMatch[1];
|
|
1118
|
+
const value = unescapeXml(fieldMatch[2].trim());
|
|
1119
|
+
if (value)
|
|
1120
|
+
fields[name] = value;
|
|
1121
|
+
}
|
|
1122
|
+
events.push({ kind, atCompartment, fields });
|
|
1123
|
+
}
|
|
1124
|
+
return events;
|
|
1131
1125
|
}
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
return false;
|
|
1146
|
-
} catch (error) {
|
|
1147
|
-
return !isGitTimeoutError(error) && typeof error === "object" && error !== null && "status" in error && error.status === 1 && getErrorStderr(error) === "";
|
|
1126
|
+
|
|
1127
|
+
// src/features/magic-context/lkg-prefix-chunks.ts
|
|
1128
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
1129
|
+
var LKG_PREFIX_CHUNK_CHARS = 64 * 1024;
|
|
1130
|
+
function splitLkgPrefix(jsonPrefix) {
|
|
1131
|
+
const chunks = [];
|
|
1132
|
+
let start = 0;
|
|
1133
|
+
while (start < jsonPrefix.length) {
|
|
1134
|
+
let end = Math.min(start + LKG_PREFIX_CHUNK_CHARS, jsonPrefix.length);
|
|
1135
|
+
if (end < jsonPrefix.length) {
|
|
1136
|
+
const last = jsonPrefix.charCodeAt(end - 1);
|
|
1137
|
+
if (last >= 55296 && last <= 56319)
|
|
1138
|
+
end -= 1;
|
|
1148
1139
|
}
|
|
1149
|
-
|
|
1150
|
-
|
|
1140
|
+
chunks.push(jsonPrefix.slice(start, end));
|
|
1141
|
+
start = end;
|
|
1151
1142
|
}
|
|
1143
|
+
return chunks;
|
|
1152
1144
|
}
|
|
1153
|
-
function
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1145
|
+
function hashLkgChunk(body) {
|
|
1146
|
+
return createHash3("sha256").update(body).digest("hex");
|
|
1147
|
+
}
|
|
1148
|
+
function hashLkgChunkList(chunkHashes) {
|
|
1149
|
+
return createHash3("sha256").update(chunkHashes.join(`
|
|
1150
|
+
`)).digest("hex");
|
|
1151
|
+
}
|
|
1152
|
+
function layoutLkgPrefix(jsonPrefix) {
|
|
1153
|
+
const chunks = splitLkgPrefix(jsonPrefix);
|
|
1154
|
+
const chunkHashes = chunks.map(hashLkgChunk);
|
|
1155
|
+
return { chunks, chunkHashes, chars: jsonPrefix.length, hash: hashLkgChunkList(chunkHashes) };
|
|
1156
|
+
}
|
|
1157
|
+
function assembleLkgPrefix(expected, rows) {
|
|
1158
|
+
const { chars, chunks, hash } = expected;
|
|
1159
|
+
if (typeof chars !== "number" || !Number.isSafeInteger(chars) || typeof chunks !== "number" || !Number.isSafeInteger(chunks) || typeof hash !== "string" || rows.length !== chunks) {
|
|
1160
|
+
return;
|
|
1167
1161
|
}
|
|
1168
|
-
|
|
1169
|
-
|
|
1162
|
+
const bodies = [];
|
|
1163
|
+
let length = 0;
|
|
1164
|
+
for (let index = 0;index < rows.length; index += 1) {
|
|
1165
|
+
const row = rows[index];
|
|
1166
|
+
if (row?.chunk !== index || typeof row.body !== "string")
|
|
1167
|
+
return;
|
|
1168
|
+
bodies.push(row.body);
|
|
1169
|
+
length += row.body.length;
|
|
1170
1170
|
}
|
|
1171
|
-
|
|
1171
|
+
if (length !== chars)
|
|
1172
|
+
return;
|
|
1173
|
+
const chunkHashes = bodies.map(hashLkgChunk);
|
|
1174
|
+
if (chunkHashes.some((chunkHash, index) => rows[index]?.hash !== chunkHash))
|
|
1175
|
+
return;
|
|
1176
|
+
if (hashLkgChunkList(chunkHashes) !== hash)
|
|
1177
|
+
return;
|
|
1178
|
+
return { jsonPrefix: bodies.join(""), chunkHashes, hash };
|
|
1172
1179
|
}
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1180
|
+
|
|
1181
|
+
// src/features/magic-context/migration-v94-write-split.ts
|
|
1182
|
+
var LKG_SLOT_CHUNKS_DDL = `
|
|
1183
|
+
CREATE TABLE IF NOT EXISTS lkg_slot_chunks (
|
|
1184
|
+
session_id TEXT NOT NULL,
|
|
1185
|
+
chunk INTEGER NOT NULL,
|
|
1186
|
+
hash TEXT NOT NULL,
|
|
1187
|
+
body TEXT NOT NULL,
|
|
1188
|
+
PRIMARY KEY (session_id, chunk)
|
|
1189
|
+
);
|
|
1190
|
+
`;
|
|
1191
|
+
var SESSION_REPLAY_DECISIONS_DDL = `
|
|
1192
|
+
CREATE TABLE IF NOT EXISTS session_replay_decisions (
|
|
1193
|
+
session_id TEXT NOT NULL,
|
|
1194
|
+
message_id TEXT NOT NULL,
|
|
1195
|
+
decision TEXT NOT NULL,
|
|
1196
|
+
PRIMARY KEY (session_id, message_id)
|
|
1197
|
+
) WITHOUT ROWID;
|
|
1198
|
+
`;
|
|
1199
|
+
function lkgSlotsDdl(table) {
|
|
1200
|
+
return `
|
|
1201
|
+
CREATE TABLE IF NOT EXISTS ${table} (
|
|
1202
|
+
session_id TEXT PRIMARY KEY,
|
|
1203
|
+
json_prefix_chars INTEGER NOT NULL,
|
|
1204
|
+
json_prefix_chunks INTEGER NOT NULL,
|
|
1205
|
+
json_prefix_hash TEXT NOT NULL,
|
|
1206
|
+
input_id_seq TEXT NOT NULL,
|
|
1207
|
+
input_content_digests TEXT NOT NULL,
|
|
1208
|
+
input_content_signatures TEXT,
|
|
1209
|
+
last_input_message_id TEXT NOT NULL,
|
|
1210
|
+
model_key TEXT,
|
|
1211
|
+
provider_key TEXT,
|
|
1212
|
+
captured_at INTEGER NOT NULL,
|
|
1213
|
+
row_version INTEGER,
|
|
1214
|
+
capture_sequence INTEGER
|
|
1215
|
+
);
|
|
1216
|
+
`;
|
|
1217
|
+
}
|
|
1218
|
+
var LKG_SLOTS_DDL = lkgSlotsDdl("lkg_slots");
|
|
1219
|
+
var LKG_MOVE_WINDOW_MS = 24 * 60 * 60 * 1000;
|
|
1220
|
+
function tableExists(db, name) {
|
|
1221
|
+
return Boolean(db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = ?").get(name));
|
|
1222
|
+
}
|
|
1223
|
+
function columnExists(db, table, column) {
|
|
1224
|
+
const rows = db.prepare(`PRAGMA table_info(${table})`).all();
|
|
1225
|
+
return rows.some((row) => row.name === column);
|
|
1226
|
+
}
|
|
1227
|
+
function splitLkgSlotPrefixes(db, now = Date.now()) {
|
|
1228
|
+
db.exec(LKG_SLOT_CHUNKS_DDL);
|
|
1229
|
+
if (!tableExists(db, "lkg_slots")) {
|
|
1230
|
+
db.exec(LKG_SLOTS_DDL);
|
|
1231
|
+
return;
|
|
1183
1232
|
}
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1233
|
+
if (!columnExists(db, "lkg_slots", "json_prefix"))
|
|
1234
|
+
return;
|
|
1235
|
+
db.prepare("DELETE FROM lkg_slots WHERE NOT (captured_at >= ?)").run(now - LKG_MOVE_WINDOW_MS);
|
|
1236
|
+
db.exec("DROP TABLE IF EXISTS lkg_slots_v94");
|
|
1237
|
+
db.exec(lkgSlotsDdl("lkg_slots_v94"));
|
|
1238
|
+
const sessionIds = db.prepare("SELECT session_id FROM lkg_slots").all().map((row) => row.session_id);
|
|
1239
|
+
const readPrefix = db.prepare("SELECT json_prefix FROM lkg_slots WHERE session_id IS ?");
|
|
1240
|
+
const insertSlot = db.prepare(`INSERT OR IGNORE INTO lkg_slots_v94 (
|
|
1241
|
+
session_id, json_prefix_chars, json_prefix_chunks, json_prefix_hash,
|
|
1242
|
+
input_id_seq, input_content_digests, input_content_signatures,
|
|
1243
|
+
last_input_message_id, model_key, provider_key,
|
|
1244
|
+
captured_at, row_version, capture_sequence
|
|
1245
|
+
)
|
|
1246
|
+
SELECT session_id, ?, ?, ?,
|
|
1247
|
+
input_id_seq, input_content_digests, input_content_signatures,
|
|
1248
|
+
last_input_message_id, model_key, provider_key,
|
|
1249
|
+
captured_at, row_version, capture_sequence
|
|
1250
|
+
FROM lkg_slots WHERE session_id IS ?`);
|
|
1251
|
+
const insertChunk = db.prepare("INSERT INTO lkg_slot_chunks (session_id, chunk, hash, body) VALUES (?, ?, ?, ?)");
|
|
1252
|
+
db.prepare("DELETE FROM lkg_slot_chunks").run();
|
|
1253
|
+
for (const sessionId of sessionIds) {
|
|
1254
|
+
if (typeof sessionId !== "string")
|
|
1255
|
+
continue;
|
|
1256
|
+
const row = readPrefix.get(sessionId);
|
|
1257
|
+
if (typeof row?.json_prefix !== "string")
|
|
1258
|
+
continue;
|
|
1259
|
+
const layout = layoutLkgPrefix(row.json_prefix);
|
|
1260
|
+
const inserted = insertSlot.run(layout.chars, layout.chunks.length, layout.hash, sessionId);
|
|
1261
|
+
if (Number(inserted.changes) !== 1)
|
|
1262
|
+
continue;
|
|
1263
|
+
for (let index = 0;index < layout.chunks.length; index += 1) {
|
|
1264
|
+
insertChunk.run(sessionId, index, layout.chunkHashes[index], layout.chunks[index]);
|
|
1198
1265
|
}
|
|
1199
|
-
throw classified;
|
|
1200
|
-
}
|
|
1201
|
-
const rootCommit = output.split(`
|
|
1202
|
-
`).map((line) => line.trim().slice(0, 64)).filter((line) => /^[0-9a-f]{7,64}$/.test(line)).sort()[0];
|
|
1203
|
-
if (!rootCommit) {
|
|
1204
|
-
throw new ProjectIdentityError("unknown", directory, "git rev-list returned no valid root commit hash");
|
|
1205
1266
|
}
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
lastKnownGitIdentityCache.set(canonical, identity);
|
|
1209
|
-
rememberGitIdentity(canonical, identity);
|
|
1210
|
-
const root = gitRootDirectory(canonical);
|
|
1211
|
-
if (root && root !== canonical)
|
|
1212
|
-
rememberGitIdentity(root, identity);
|
|
1213
|
-
transientFailureCooldown.delete(canonical);
|
|
1214
|
-
dubiousOwnershipFallbackDirectories.delete(canonical);
|
|
1215
|
-
transientGitIdentityReuseLoggedDirectories.delete(canonical);
|
|
1216
|
-
return identity;
|
|
1267
|
+
db.exec("DROP TABLE lkg_slots");
|
|
1268
|
+
db.exec("ALTER TABLE lkg_slots_v94 RENAME TO lkg_slots");
|
|
1217
1269
|
}
|
|
1218
|
-
function
|
|
1219
|
-
return
|
|
1270
|
+
function validDecisionSql(type, value) {
|
|
1271
|
+
return `(${type} = 'text' AND (
|
|
1272
|
+
${value} IN ('keep', 'strip') OR (
|
|
1273
|
+
${value} GLOB 'keep:[1-9]*'
|
|
1274
|
+
AND substr(${value}, 6) NOT GLOB '*[^0-9]*'
|
|
1275
|
+
AND length(${value}) <= 10
|
|
1276
|
+
AND CAST(substr(${value}, 6) AS INTEGER) BETWEEN 2 AND 10000
|
|
1277
|
+
)
|
|
1278
|
+
))`;
|
|
1220
1279
|
}
|
|
1221
|
-
function
|
|
1222
|
-
|
|
1223
|
-
if (
|
|
1280
|
+
function splitReplayDecisions(db) {
|
|
1281
|
+
db.exec(SESSION_REPLAY_DECISIONS_DDL);
|
|
1282
|
+
if (!tableExists(db, "session_meta"))
|
|
1224
1283
|
return;
|
|
1225
|
-
if (
|
|
1226
|
-
return until;
|
|
1227
|
-
transientFailureCooldown.delete(canonical);
|
|
1228
|
-
return;
|
|
1229
|
-
}
|
|
1230
|
-
function lastKnownGitIdentity(canonical) {
|
|
1231
|
-
return lastKnownGitIdentityCache.get(canonical) ?? identityCache.get(canonical) ?? readRememberedGitIdentity(canonical);
|
|
1232
|
-
}
|
|
1233
|
-
function nearestLastKnownGitIdentity(canonical) {
|
|
1234
|
-
const visited = new Set;
|
|
1235
|
-
const walk = (start) => {
|
|
1236
|
-
let current = start;
|
|
1237
|
-
while (!visited.has(current)) {
|
|
1238
|
-
visited.add(current);
|
|
1239
|
-
const cached = lastKnownGitIdentity(current);
|
|
1240
|
-
if (cached !== undefined)
|
|
1241
|
-
return { identity: cached, source: current };
|
|
1242
|
-
if (existsSync(path3.join(current, ".git")))
|
|
1243
|
-
break;
|
|
1244
|
-
const parent = path3.dirname(current);
|
|
1245
|
-
if (parent === current)
|
|
1246
|
-
break;
|
|
1247
|
-
current = parent;
|
|
1248
|
-
}
|
|
1284
|
+
if (!columnExists(db, "session_meta", "trailing_blank_decisions"))
|
|
1249
1285
|
return;
|
|
1250
|
-
|
|
1251
|
-
const
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1286
|
+
const doc = "sm.trailing_blank_decisions";
|
|
1287
|
+
const hasVersionKey = `json_type(${doc}, '$.version') IS NOT NULL`;
|
|
1288
|
+
const versionIsDecision = validDecisionSql(`json_type(${doc}, '$.version')`, `json_extract(${doc}, '$.version')`);
|
|
1289
|
+
const isV2 = `(${hasVersionKey} AND NOT ${versionIsDecision})`;
|
|
1290
|
+
const v2Valid = `(
|
|
1291
|
+
json_type(${doc}, '$.version') IN ('integer', 'real')
|
|
1292
|
+
AND json_extract(${doc}, '$.version') = 2
|
|
1293
|
+
AND json_type(${doc}, '$.trailingBlank') = 'object'
|
|
1294
|
+
AND NOT EXISTS (
|
|
1295
|
+
SELECT 1 FROM json_each(${doc}, '$.trailingBlank') AS entry
|
|
1296
|
+
WHERE entry.key = '' OR NOT ${validDecisionSql("entry.type", "entry.value")}
|
|
1297
|
+
)
|
|
1298
|
+
)`;
|
|
1299
|
+
const v1Valid = `NOT EXISTS (
|
|
1300
|
+
SELECT 1 FROM json_each(${doc}) AS entry
|
|
1301
|
+
WHERE entry.key = '' OR NOT ${validDecisionSql("entry.type", "entry.value")}
|
|
1302
|
+
)`;
|
|
1303
|
+
db.exec("DROP TABLE IF EXISTS temp.v94_replay_documents");
|
|
1304
|
+
db.exec(`
|
|
1305
|
+
CREATE TEMP TABLE v94_replay_documents AS
|
|
1306
|
+
SELECT session_id, version FROM (
|
|
1307
|
+
SELECT sm.session_id AS session_id, CASE WHEN ${isV2} THEN 2 ELSE 1 END AS version,
|
|
1308
|
+
CASE WHEN ${isV2} THEN ${v2Valid} ELSE ${v1Valid} END AS strictly_valid
|
|
1309
|
+
FROM session_meta AS sm
|
|
1310
|
+
WHERE typeof(${doc}) = 'text' AND ${doc} <> ''
|
|
1311
|
+
AND json_valid(${doc}) AND json_type(${doc}) = 'object'
|
|
1312
|
+
) WHERE strictly_valid;
|
|
1313
|
+
`);
|
|
1314
|
+
db.exec(`
|
|
1315
|
+
INSERT INTO session_replay_decisions (session_id, message_id, decision)
|
|
1316
|
+
SELECT sm.session_id, entry.key, entry.value
|
|
1317
|
+
FROM v94_replay_documents AS moved
|
|
1318
|
+
JOIN session_meta AS sm ON sm.session_id = moved.session_id
|
|
1319
|
+
JOIN json_each(
|
|
1320
|
+
${doc}, CASE moved.version WHEN 2 THEN '$.trailingBlank' ELSE '$' END
|
|
1321
|
+
) AS entry
|
|
1322
|
+
WHERE true
|
|
1323
|
+
ON CONFLICT(session_id, message_id) DO UPDATE SET decision = excluded.decision;
|
|
1324
|
+
`);
|
|
1325
|
+
db.exec(`
|
|
1326
|
+
UPDATE session_meta SET trailing_blank_decisions = (
|
|
1327
|
+
SELECT CASE moved.version
|
|
1328
|
+
WHEN 2 THEN json_set(session_meta.trailing_blank_decisions, '$.trailingBlank', json('{}'))
|
|
1329
|
+
ELSE ''
|
|
1330
|
+
END
|
|
1331
|
+
FROM v94_replay_documents AS moved WHERE moved.session_id = session_meta.session_id
|
|
1332
|
+
)
|
|
1333
|
+
WHERE session_id IN (SELECT session_id FROM v94_replay_documents);
|
|
1334
|
+
`);
|
|
1335
|
+
db.exec("DROP TABLE temp.v94_replay_documents");
|
|
1260
1336
|
}
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1337
|
+
|
|
1338
|
+
// src/features/magic-context/opencode2-relabel.ts
|
|
1339
|
+
var OPENCODE2_RELABEL_STATE_KEY = "v87_opencode2_relabel";
|
|
1340
|
+
var V87_HARNESS_TWIN_RULES = [
|
|
1341
|
+
{
|
|
1342
|
+
table: "session_projects",
|
|
1343
|
+
keyColumns: ["session_id"],
|
|
1344
|
+
incomingIsNewer: "incoming.updated_at > existing.updated_at"
|
|
1345
|
+
},
|
|
1346
|
+
{
|
|
1347
|
+
table: "primer_candidates",
|
|
1348
|
+
keyColumns: [
|
|
1349
|
+
"project_path",
|
|
1350
|
+
"session_id",
|
|
1351
|
+
"source_start_message_id",
|
|
1352
|
+
"source_end_message_id"
|
|
1353
|
+
],
|
|
1354
|
+
incomingIsNewer: "incoming.created_at > existing.created_at"
|
|
1355
|
+
},
|
|
1356
|
+
{
|
|
1357
|
+
table: "transform_decisions",
|
|
1358
|
+
keyColumns: ["session_id", "message_id"],
|
|
1359
|
+
incomingIsNewer: "incoming.ts_ms > existing.ts_ms"
|
|
1269
1360
|
}
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
return `Magic Context: git refused to read ${canonical} (dubious ownership — the repo is owned by a different user). Memory features are paused unless a previous git identity is available. Fix: git config --global --add safe.directory ${canonical}`;
|
|
1274
|
-
}
|
|
1275
|
-
function recordDubiousOwnershipFallback(canonical) {
|
|
1276
|
-
dubiousOwnershipFallbackDirectories.add(canonical);
|
|
1277
|
-
if (dubiousOwnershipLoggedDirectories.has(canonical))
|
|
1278
|
-
return;
|
|
1279
|
-
dubiousOwnershipLoggedDirectories.add(canonical);
|
|
1280
|
-
log(`[magic-context] ${formatDubiousOwnershipWarning(canonical)}`);
|
|
1281
|
-
}
|
|
1282
|
-
function takeDubiousOwnershipProjectIdentityWarning(directory) {
|
|
1283
|
-
const canonical = path3.resolve(directory);
|
|
1284
|
-
if (!dubiousOwnershipFallbackDirectories.has(canonical))
|
|
1285
|
-
return null;
|
|
1286
|
-
if (dubiousOwnershipWarnedDirectories.has(canonical))
|
|
1287
|
-
return null;
|
|
1288
|
-
dubiousOwnershipWarnedDirectories.add(canonical);
|
|
1289
|
-
return formatDubiousOwnershipWarning(canonical);
|
|
1361
|
+
];
|
|
1362
|
+
function tableExists2(db, name) {
|
|
1363
|
+
return Boolean(db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = ?").get(name));
|
|
1290
1364
|
}
|
|
1291
|
-
function
|
|
1292
|
-
|
|
1293
|
-
try {
|
|
1294
|
-
return realpathSync2.native(homeDirectory);
|
|
1295
|
-
} catch {
|
|
1296
|
-
return homeDirectory;
|
|
1297
|
-
}
|
|
1365
|
+
function columnNames(db, table) {
|
|
1366
|
+
return new Set(db.prepare(`PRAGMA table_info(${table})`).all().map((column) => column.name));
|
|
1298
1367
|
}
|
|
1299
|
-
function
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1368
|
+
function listSessionScopedHarnessTables(db, tables) {
|
|
1369
|
+
const shapes = [];
|
|
1370
|
+
for (const table of tables) {
|
|
1371
|
+
if (!tableExists2(db, table))
|
|
1372
|
+
continue;
|
|
1373
|
+
const columns = columnNames(db, table);
|
|
1374
|
+
if (!columns.has("harness"))
|
|
1375
|
+
continue;
|
|
1376
|
+
shapes.push({ table, hasSessionId: columns.has("session_id") });
|
|
1306
1377
|
}
|
|
1378
|
+
return shapes;
|
|
1307
1379
|
}
|
|
1308
|
-
function
|
|
1309
|
-
const
|
|
1310
|
-
|
|
1311
|
-
|
|
1380
|
+
function collectCandidateSessions(db, shapes) {
|
|
1381
|
+
const sessions = new Set;
|
|
1382
|
+
for (const shape of shapes) {
|
|
1383
|
+
if (!shape.hasSessionId)
|
|
1384
|
+
continue;
|
|
1385
|
+
const rows = db.prepare(`SELECT DISTINCT session_id AS id FROM ${shape.table}
|
|
1386
|
+
WHERE session_id IS NOT NULL AND harness IN ('opencode', 'opencode2')`).all();
|
|
1387
|
+
for (const row of rows)
|
|
1388
|
+
sessions.add(row.id);
|
|
1312
1389
|
}
|
|
1390
|
+
return [...sessions].sort();
|
|
1313
1391
|
}
|
|
1314
|
-
function
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1392
|
+
function openEvidenceStore(path) {
|
|
1393
|
+
const store = new Database(path, { readonly: true, fileMustExist: true });
|
|
1394
|
+
try {
|
|
1395
|
+
const hasV1Messages = tableExists2(store, "message");
|
|
1396
|
+
const hasV1Session = tableExists2(store, "session");
|
|
1397
|
+
const hasV2 = tableExists2(store, "session_v2");
|
|
1398
|
+
const hasV2Messages = hasV2 && tableExists2(store, "session_message");
|
|
1399
|
+
if (!hasV1Messages && !hasV1Session && !hasV2) {
|
|
1400
|
+
store.close();
|
|
1401
|
+
return null;
|
|
1321
1402
|
}
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1403
|
+
const v1Message = hasV1Messages ? store.prepare("SELECT MAX(time_updated) AS newest FROM message WHERE session_id = ?") : null;
|
|
1404
|
+
const v1Session = hasV1Session ? store.prepare("SELECT time_updated AS newest FROM session WHERE id = ?") : null;
|
|
1405
|
+
const v2Message = hasV2Messages ? store.prepare("SELECT MAX(time_updated) AS newest FROM session_message WHERE session_id = ?") : null;
|
|
1406
|
+
const v2Session = hasV2 ? store.prepare("SELECT time_updated AS newest FROM session_v2 WHERE id = ?") : null;
|
|
1407
|
+
const newest = (statements, sessionId) => {
|
|
1408
|
+
let best = null;
|
|
1409
|
+
for (const statement of statements) {
|
|
1410
|
+
if (!statement)
|
|
1411
|
+
continue;
|
|
1412
|
+
const row = statement.get(sessionId);
|
|
1413
|
+
const value = row?.newest;
|
|
1414
|
+
if (typeof value !== "number")
|
|
1415
|
+
continue;
|
|
1416
|
+
if (best === null || value > best)
|
|
1417
|
+
best = value;
|
|
1329
1418
|
}
|
|
1330
|
-
|
|
1331
|
-
}
|
|
1332
|
-
return
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1419
|
+
return best;
|
|
1420
|
+
};
|
|
1421
|
+
return {
|
|
1422
|
+
newestV1Activity: (sessionId) => newest([v1Message, v1Session], sessionId),
|
|
1423
|
+
newestV2Activity: (sessionId) => newest([v2Message, v2Session], sessionId),
|
|
1424
|
+
close: () => store.close()
|
|
1425
|
+
};
|
|
1336
1426
|
} catch (error) {
|
|
1337
|
-
|
|
1338
|
-
if (error.errorClass === "no_commits")
|
|
1339
|
-
return directoryFallback(canonical);
|
|
1340
|
-
if (!hasGitDir(canonical)) {
|
|
1341
|
-
if (error.errorClass === "permission_denied")
|
|
1342
|
-
throw error;
|
|
1343
|
-
const resolved = rememberedIdentityOrDirectoryFallback(canonical);
|
|
1344
|
-
directoryFallbackCache.set(canonical, resolved);
|
|
1345
|
-
transientFailureCooldown.delete(canonical);
|
|
1346
|
-
return resolved;
|
|
1347
|
-
}
|
|
1348
|
-
transientFailureCooldown.set(canonical, nowMs() + TRANSIENT_FAILURE_COOLDOWN_MS);
|
|
1349
|
-
const cachedGitIdentity = reuseLastKnownGitIdentity(canonical);
|
|
1350
|
-
if (error.errorClass === "dubious_ownership")
|
|
1351
|
-
recordDubiousOwnershipFallback(canonical);
|
|
1352
|
-
if (cachedGitIdentity !== undefined) {
|
|
1353
|
-
return cachedGitIdentity;
|
|
1354
|
-
}
|
|
1355
|
-
throw error;
|
|
1356
|
-
}
|
|
1427
|
+
store.close();
|
|
1357
1428
|
throw error;
|
|
1358
1429
|
}
|
|
1359
1430
|
}
|
|
1360
|
-
function
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
const canonical = path3.resolve(directory);
|
|
1365
|
-
if (hasGitDir(canonical) || error instanceof ProjectIdentityError && error.errorClass === "home_project_disabled")
|
|
1366
|
-
throw error;
|
|
1367
|
-
const fallback = rememberedIdentityOrDirectoryFallback(canonical);
|
|
1368
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1369
|
-
log(`[magic-context] project identity resolution failed for ${canonical}; using directory fallback ${fallback}: ${message}`);
|
|
1370
|
-
return fallback;
|
|
1371
|
-
}
|
|
1372
|
-
}
|
|
1373
|
-
function hasGitDir(canonical) {
|
|
1374
|
-
if (hasGitDirInAncestorChain(canonical)) {
|
|
1375
|
-
return true;
|
|
1376
|
-
}
|
|
1377
|
-
try {
|
|
1378
|
-
const realCanonical = realpathSync2.native(canonical);
|
|
1379
|
-
return realCanonical !== canonical && hasGitDirInAncestorChain(realCanonical);
|
|
1380
|
-
} catch {
|
|
1381
|
-
return false;
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1384
|
-
function gitRootInAncestorChain(startDirectory) {
|
|
1385
|
-
let current = startDirectory;
|
|
1386
|
-
while (true) {
|
|
1387
|
-
if (existsSync(path3.join(current, ".git"))) {
|
|
1388
|
-
try {
|
|
1389
|
-
return realpathSync2.native(current);
|
|
1390
|
-
} catch {
|
|
1391
|
-
return path3.resolve(current);
|
|
1392
|
-
}
|
|
1393
|
-
}
|
|
1394
|
-
const parent = path3.dirname(current);
|
|
1395
|
-
if (parent === current) {
|
|
1396
|
-
return null;
|
|
1397
|
-
}
|
|
1398
|
-
current = parent;
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
|
-
function hasGitDirInAncestorChain(startDirectory) {
|
|
1402
|
-
return gitRootInAncestorChain(startDirectory) !== null;
|
|
1403
|
-
}
|
|
1404
|
-
function gitRootDirectory(canonical) {
|
|
1405
|
-
const direct = gitRootInAncestorChain(canonical);
|
|
1406
|
-
if (direct)
|
|
1407
|
-
return direct;
|
|
1408
|
-
try {
|
|
1409
|
-
const realCanonical = realpathSync2.native(canonical);
|
|
1410
|
-
return realCanonical === canonical ? null : gitRootInAncestorChain(realCanonical);
|
|
1411
|
-
} catch {
|
|
1431
|
+
function resolveHarnessFromEvidence(evidence, sessionId) {
|
|
1432
|
+
const v1 = evidence.newestV1Activity(sessionId);
|
|
1433
|
+
const v2 = evidence.newestV2Activity(sessionId);
|
|
1434
|
+
if (v1 === null && v2 === null)
|
|
1412
1435
|
return null;
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1436
|
+
if (v2 !== null && (v1 === null || v2 > v1))
|
|
1437
|
+
return "opencode2";
|
|
1438
|
+
if (v1 !== null && (v2 === null || v1 > v2))
|
|
1439
|
+
return "opencode";
|
|
1440
|
+
return null;
|
|
1417
1441
|
}
|
|
1418
|
-
function
|
|
1419
|
-
const
|
|
1420
|
-
const
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1442
|
+
function prepareTwinResolver(db, table, keyColumns, incomingIsNewer) {
|
|
1443
|
+
const joinOn = keyColumns.map((column) => `incoming.${column} = existing.${column}`).join(" AND ");
|
|
1444
|
+
const dropIncoming = db.prepare(`DELETE FROM ${table} WHERE rowid IN (
|
|
1445
|
+
SELECT incoming.rowid FROM ${table} AS incoming
|
|
1446
|
+
JOIN ${table} AS existing ON ${joinOn} AND existing.harness = ?
|
|
1447
|
+
WHERE incoming.session_id = ? AND incoming.harness = ?
|
|
1448
|
+
AND NOT (${incomingIsNewer})
|
|
1449
|
+
)`);
|
|
1450
|
+
const dropExisting = db.prepare(`DELETE FROM ${table} WHERE rowid IN (
|
|
1451
|
+
SELECT existing.rowid FROM ${table} AS existing
|
|
1452
|
+
JOIN ${table} AS incoming ON ${joinOn} AND incoming.harness = ?
|
|
1453
|
+
WHERE existing.session_id = ? AND existing.harness = ?
|
|
1454
|
+
AND (${incomingIsNewer})
|
|
1455
|
+
)`);
|
|
1456
|
+
return {
|
|
1457
|
+
run(sessionId, target, source) {
|
|
1458
|
+
dropIncoming.run(target, sessionId, source);
|
|
1459
|
+
dropExisting.run(source, sessionId, target);
|
|
1426
1460
|
}
|
|
1427
|
-
}
|
|
1428
|
-
if (isUserHomeDirectory(directory) || gitRootDirectory(canonicalDirectory) === canonicalHome) {
|
|
1429
|
-
return `this session runs in your home directory (${canonicalHome}), which Magic Context does not treat as a project. ` + "Start OpenCode inside a project folder, or set `allow_home_project: true` in the user-level " + "magic-context.jsonc (~/.config/cortexkit/) to give home sessions their own memory.";
|
|
1430
|
-
}
|
|
1431
|
-
if (hasGitDir(resolvedDirectory))
|
|
1432
|
-
return `git identity resolution for ${resolvedDirectory} is temporarily unavailable. Memory features are paused; retry after git access recovers.`;
|
|
1433
|
-
return `the session directory ${resolvedDirectory} could not be read as a project.`;
|
|
1461
|
+
};
|
|
1434
1462
|
}
|
|
1435
|
-
function
|
|
1436
|
-
|
|
1463
|
+
function writeUnresolvedState(db, reason, resolution, sessionIds) {
|
|
1464
|
+
if (!tableExists2(db, "schema_migrations_meta"))
|
|
1465
|
+
return;
|
|
1466
|
+
const value = {
|
|
1467
|
+
reason,
|
|
1468
|
+
lookedFor: getOpenCodeDbProbeDescriptions(resolution).join(", "),
|
|
1469
|
+
sessionIds: [...sessionIds],
|
|
1470
|
+
recordedAt: Date.now()
|
|
1471
|
+
};
|
|
1472
|
+
db.prepare(`INSERT INTO schema_migrations_meta (key, value) VALUES (?, ?)
|
|
1473
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(OPENCODE2_RELABEL_STATE_KEY, JSON.stringify(value));
|
|
1437
1474
|
}
|
|
1438
|
-
function
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1475
|
+
function clearUnresolvedState(db) {
|
|
1476
|
+
if (!tableExists2(db, "schema_migrations_meta"))
|
|
1477
|
+
return;
|
|
1478
|
+
db.prepare("DELETE FROM schema_migrations_meta WHERE key = ?").run(OPENCODE2_RELABEL_STATE_KEY);
|
|
1479
|
+
}
|
|
1480
|
+
function repairOpenCode2HarnessLabels(db, options) {
|
|
1481
|
+
const shapes = listSessionScopedHarnessTables(db, options.tables);
|
|
1482
|
+
const candidateSessionIds = collectCandidateSessions(db, shapes);
|
|
1483
|
+
if (candidateSessionIds.length === 0) {
|
|
1484
|
+
clearUnresolvedState(db);
|
|
1485
|
+
return {
|
|
1486
|
+
status: "no_candidates",
|
|
1487
|
+
storePath: null,
|
|
1488
|
+
candidateSessionIds,
|
|
1489
|
+
relabelledSessions: []
|
|
1490
|
+
};
|
|
1444
1491
|
}
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
return realpathSync2.native(resolvedDirectory);
|
|
1451
|
-
} catch {
|
|
1452
|
-
return resolvedDirectory;
|
|
1453
|
-
}
|
|
1454
|
-
})();
|
|
1455
|
-
filesystemProbeObserverForTests?.();
|
|
1456
|
-
const homeRoot = gitRootDirectory(canonicalDirectory);
|
|
1457
|
-
const inheritsHomeRepository = homeRoot !== null && isUserHomeDirectory(homeRoot);
|
|
1458
|
-
let identity;
|
|
1459
|
-
if (!allowHomeProject && (isUserHomeDirectory(directory) || inheritsHomeRepository)) {
|
|
1460
|
-
identity = undefined;
|
|
1492
|
+
const resolution = resolveOpenCodeDbPath("v1", { env: options.env });
|
|
1493
|
+
let evidence = null;
|
|
1494
|
+
let reason = null;
|
|
1495
|
+
if (!openCodeDbPathExists(resolution)) {
|
|
1496
|
+
reason = "store_not_found";
|
|
1461
1497
|
} else {
|
|
1462
1498
|
try {
|
|
1463
|
-
|
|
1499
|
+
evidence = openEvidenceStore(resolution.path);
|
|
1500
|
+
if (!evidence)
|
|
1501
|
+
reason = "store_schema_unknown";
|
|
1464
1502
|
} catch (error) {
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
const reason = error.errorClass === "git_identity_unavailable" ? pausedIdentityReasons.get(resolvedDirectory) ?? error.errorClass : error.errorClass;
|
|
1468
|
-
pausedIdentityReasons.set(resolvedDirectory, reason);
|
|
1469
|
-
const logKey = `${resolvedDirectory}\x00${reason}`;
|
|
1470
|
-
if (!pausedIdentityLogged.has(logKey)) {
|
|
1471
|
-
pausedIdentityLogged.add(logKey);
|
|
1472
|
-
log(`[magic-context] memory features paused for ${resolvedDirectory}: ${reason}`);
|
|
1473
|
-
}
|
|
1474
|
-
identity = undefined;
|
|
1503
|
+
reason = "store_unreadable";
|
|
1504
|
+
log(`[migration] OpenCode session database at ${resolution.path} could not be read: ${error instanceof Error ? error.message : String(error)}`);
|
|
1475
1505
|
}
|
|
1476
1506
|
}
|
|
1477
|
-
if (
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1507
|
+
if (!evidence || reason !== null) {
|
|
1508
|
+
evidence?.close();
|
|
1509
|
+
writeUnresolvedState(db, reason ?? "store_not_found", resolution, candidateSessionIds);
|
|
1510
|
+
log(`[migration] OpenCode harness labels left unverified for ${candidateSessionIds.length} session(s): ` + `no readable OpenCode session database (${reason ?? "store_not_found"}) at ${resolution.path}. ` + "Nothing was relabelled; `magic-context doctor` reports the affected sessions.");
|
|
1511
|
+
return {
|
|
1512
|
+
status: "unresolved",
|
|
1513
|
+
reason: reason ?? "store_not_found",
|
|
1514
|
+
storePath: null,
|
|
1515
|
+
candidateSessionIds,
|
|
1516
|
+
relabelledSessions: []
|
|
1517
|
+
};
|
|
1488
1518
|
}
|
|
1519
|
+
const relabelledSessions = [];
|
|
1489
1520
|
try {
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1521
|
+
const updates = shapes.filter((shape) => shape.hasSessionId).map((shape) => db.prepare(`UPDATE ${shape.table} SET harness = ? WHERE session_id = ? AND harness = ?`));
|
|
1522
|
+
const presentTables = new Set(shapes.map((shape) => shape.table));
|
|
1523
|
+
const twinResolvers = V87_HARNESS_TWIN_RULES.filter((rule) => presentTables.has(rule.table)).map((rule) => prepareTwinResolver(db, rule.table, rule.keyColumns, rule.incomingIsNewer));
|
|
1524
|
+
for (const sessionId of candidateSessionIds) {
|
|
1525
|
+
const target = resolveHarnessFromEvidence(evidence, sessionId);
|
|
1526
|
+
if (target === null)
|
|
1527
|
+
continue;
|
|
1528
|
+
const source = target === "opencode" ? "opencode2" : "opencode";
|
|
1529
|
+
for (const resolver of twinResolvers)
|
|
1530
|
+
resolver.run(sessionId, target, source);
|
|
1531
|
+
let moved = 0;
|
|
1532
|
+
for (const update of updates) {
|
|
1533
|
+
moved += Number(update.run(target, sessionId, source).changes ?? 0);
|
|
1534
|
+
}
|
|
1535
|
+
if (moved > 0)
|
|
1536
|
+
relabelledSessions.push({ sessionId, harness: target });
|
|
1537
|
+
}
|
|
1538
|
+
} finally {
|
|
1539
|
+
evidence.close();
|
|
1540
|
+
}
|
|
1541
|
+
clearUnresolvedState(db);
|
|
1542
|
+
if (relabelledSessions.length > 0) {
|
|
1543
|
+
const toOpenCode2 = relabelledSessions.filter((entry) => entry.harness === "opencode2").length;
|
|
1544
|
+
log(`[migration] OpenCode harness labels repaired from ${resolution.path}: ` + `${toOpenCode2} session(s) restored to opencode2, ` + `${relabelledSessions.length - toOpenCode2} session(s) set to opencode.`);
|
|
1493
1545
|
}
|
|
1546
|
+
return {
|
|
1547
|
+
status: "resolved",
|
|
1548
|
+
storePath: resolution.path,
|
|
1549
|
+
candidateSessionIds,
|
|
1550
|
+
relabelledSessions
|
|
1551
|
+
};
|
|
1494
1552
|
}
|
|
1495
|
-
|
|
1496
|
-
|
|
1553
|
+
|
|
1554
|
+
// src/features/magic-context/storage-compartment-history-version.ts
|
|
1555
|
+
function definitions(db) {
|
|
1556
|
+
const hasPrivilegeTable = Boolean(db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='context_privilege_state'").get());
|
|
1557
|
+
const externalUpdate = hasPrivilegeTable ? "CASE WHEN COALESCE((SELECT enabled FROM context_privilege_state WHERE id=1),0)=0 THEN 1 ELSE 0 END" : "1";
|
|
1558
|
+
return [
|
|
1559
|
+
{
|
|
1560
|
+
type: "table",
|
|
1561
|
+
name: "compartment_history_versions",
|
|
1562
|
+
sql: `CREATE TABLE compartment_history_versions (
|
|
1563
|
+
session_id TEXT PRIMARY KEY NOT NULL,
|
|
1564
|
+
generation TEXT NOT NULL DEFAULT (lower(hex(randomblob(16)))),
|
|
1565
|
+
version INTEGER NOT NULL DEFAULT 0 CHECK (version >= 0),
|
|
1566
|
+
rewrite_version INTEGER NOT NULL DEFAULT 0 CHECK (rewrite_version >= 0),
|
|
1567
|
+
seeded INTEGER NOT NULL DEFAULT 0 CHECK (seeded IN (0, 1))
|
|
1568
|
+
)`
|
|
1569
|
+
},
|
|
1570
|
+
{
|
|
1571
|
+
type: "trigger",
|
|
1572
|
+
name: "compartment_history_ai",
|
|
1573
|
+
sql: `CREATE TRIGGER compartment_history_ai AFTER INSERT ON compartments BEGIN
|
|
1574
|
+
INSERT INTO compartment_history_versions(session_id, version) VALUES (NEW.session_id, 1)
|
|
1575
|
+
ON CONFLICT(session_id) DO UPDATE SET version = version + 1;
|
|
1576
|
+
END`
|
|
1577
|
+
},
|
|
1578
|
+
{
|
|
1579
|
+
type: "trigger",
|
|
1580
|
+
name: "compartment_history_ad",
|
|
1581
|
+
sql: `CREATE TRIGGER compartment_history_ad AFTER DELETE ON compartments BEGIN
|
|
1582
|
+
INSERT INTO compartment_history_versions(session_id, version) VALUES (OLD.session_id, 1)
|
|
1583
|
+
ON CONFLICT(session_id) DO UPDATE SET version = version + 1;
|
|
1584
|
+
END`
|
|
1585
|
+
},
|
|
1586
|
+
{
|
|
1587
|
+
type: "trigger",
|
|
1588
|
+
name: "compartment_history_au",
|
|
1589
|
+
sql: `CREATE TRIGGER compartment_history_au AFTER UPDATE ON compartments BEGIN
|
|
1590
|
+
INSERT INTO compartment_history_versions(session_id, version, rewrite_version) VALUES (OLD.session_id, 1, ${externalUpdate})
|
|
1591
|
+
ON CONFLICT(session_id) DO UPDATE SET version = version + 1, rewrite_version = rewrite_version + ${externalUpdate};
|
|
1592
|
+
INSERT INTO compartment_history_versions(session_id, version, rewrite_version)
|
|
1593
|
+
SELECT NEW.session_id, 1, ${externalUpdate} WHERE NEW.session_id != OLD.session_id
|
|
1594
|
+
ON CONFLICT(session_id) DO UPDATE SET version = version + 1, rewrite_version = rewrite_version + ${externalUpdate};
|
|
1595
|
+
END`
|
|
1596
|
+
}
|
|
1597
|
+
];
|
|
1598
|
+
}
|
|
1599
|
+
function installedDefinitions(db) {
|
|
1600
|
+
const rows = db.prepare(`SELECT type, name, sql FROM sqlite_master
|
|
1601
|
+
WHERE name IN ('compartment_history_versions', 'compartment_history_ai', 'compartment_history_ad', 'compartment_history_au')`).all();
|
|
1602
|
+
return new Map(rows.map((row) => [`${row.type}:${row.name}`, row.sql]));
|
|
1603
|
+
}
|
|
1604
|
+
function matches(expected, installed) {
|
|
1605
|
+
return expected.every((definition) => installed.get(`${definition.type}:${definition.name}`) === definition.sql);
|
|
1606
|
+
}
|
|
1607
|
+
function installCompartmentHistoryVersions(db) {
|
|
1608
|
+
if (!db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='compartments'").get())
|
|
1609
|
+
return;
|
|
1610
|
+
if (matches(definitions(db), installedDefinitions(db)))
|
|
1611
|
+
return;
|
|
1612
|
+
db.transaction(() => {
|
|
1613
|
+
const expected = definitions(db);
|
|
1614
|
+
const installed = installedDefinitions(db);
|
|
1615
|
+
if (matches(expected, installed))
|
|
1616
|
+
return;
|
|
1617
|
+
const table = expected[0];
|
|
1618
|
+
const existingTable = installed.get(`table:${table.name}`);
|
|
1619
|
+
if (existingTable !== undefined && existingTable !== table.sql) {
|
|
1620
|
+
throw new Error("compartment_history_versions schema differs from v93; refusing to replace revision data");
|
|
1621
|
+
}
|
|
1622
|
+
if (existingTable === undefined)
|
|
1623
|
+
db.exec(table.sql);
|
|
1624
|
+
db.exec(`INSERT OR IGNORE INTO compartment_history_versions(session_id, seeded)
|
|
1625
|
+
SELECT DISTINCT session_id, 1 FROM compartments`);
|
|
1626
|
+
for (const trigger of expected.slice(1)) {
|
|
1627
|
+
if (installed.get(`trigger:${trigger.name}`) === trigger.sql)
|
|
1628
|
+
continue;
|
|
1629
|
+
db.exec(`DROP TRIGGER IF EXISTS ${trigger.name}`);
|
|
1630
|
+
db.exec(trigger.sql);
|
|
1631
|
+
}
|
|
1632
|
+
}).immediate();
|
|
1497
1633
|
}
|
|
1634
|
+
|
|
1498
1635
|
// src/features/magic-context/workspaces.ts
|
|
1636
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
1499
1637
|
var VALID_SHARE_CATEGORIES = new Set(V2_MEMORY_CATEGORIES);
|
|
1500
1638
|
var DEFAULT_WORKSPACE_SHARE_CATEGORIES = ["CONSTRAINTS"];
|
|
1501
1639
|
function tableExists3(db, tableName) {
|
|
@@ -4105,6 +4243,13 @@ var MIGRATIONS = [
|
|
|
4105
4243
|
splitLkgSlotPrefixes(db);
|
|
4106
4244
|
splitReplayDecisions(db);
|
|
4107
4245
|
}
|
|
4246
|
+
},
|
|
4247
|
+
{
|
|
4248
|
+
version: 95,
|
|
4249
|
+
description: "cover cleanup and retention reads, index git FTS rowids and store temporal replay decisions",
|
|
4250
|
+
up(db) {
|
|
4251
|
+
installV95PerfSchema(db, true);
|
|
4252
|
+
}
|
|
4108
4253
|
}
|
|
4109
4254
|
];
|
|
4110
4255
|
var LATEST_MIGRATION_VERSION = MIGRATIONS.reduce((max, m) => Math.max(max, m.version), 0);
|
|
@@ -4132,6 +4277,9 @@ function hasPendingMigrations(db) {
|
|
|
4132
4277
|
return MIGRATIONS.some((candidate) => candidate.version > currentVersion && !isMigrationApplied(db, candidate.version));
|
|
4133
4278
|
}
|
|
4134
4279
|
var mainThreadMigrationBodies = 0;
|
|
4280
|
+
function getMainThreadMigrationBodyCount() {
|
|
4281
|
+
return mainThreadMigrationBodies;
|
|
4282
|
+
}
|
|
4135
4283
|
function isSiblingMigrationConflict(db, error, version) {
|
|
4136
4284
|
if (!(error instanceof Error))
|
|
4137
4285
|
return false;
|
|
@@ -4244,23 +4392,13 @@ async function runMigrationsWithRetry(db, options = {}) {
|
|
|
4244
4392
|
}
|
|
4245
4393
|
}
|
|
4246
4394
|
|
|
4247
|
-
// src/features/magic-context/storage-db.ts
|
|
4248
|
-
import {
|
|
4249
|
-
chmodSync,
|
|
4250
|
-
copyFileSync,
|
|
4251
|
-
cpSync,
|
|
4252
|
-
existsSync as existsSync3,
|
|
4253
|
-
mkdirSync as mkdirSync2,
|
|
4254
|
-
readdirSync,
|
|
4255
|
-
readFileSync as readFileSync3,
|
|
4256
|
-
rmdirSync,
|
|
4257
|
-
statSync as statSync2,
|
|
4258
|
-
unlinkSync
|
|
4259
|
-
} from "node:fs";
|
|
4260
|
-
import { basename, dirname, join as join2, resolve } from "node:path";
|
|
4261
|
-
|
|
4262
4395
|
// src/plugin/boot-quiet.ts
|
|
4396
|
+
var BOOT_QUIET_MS = 120000;
|
|
4263
4397
|
var bootQuietUntilMs = 0;
|
|
4398
|
+
function beginBootQuietPeriod(now = Date.now()) {
|
|
4399
|
+
if (bootQuietUntilMs === 0)
|
|
4400
|
+
bootQuietUntilMs = now + BOOT_QUIET_MS;
|
|
4401
|
+
}
|
|
4264
4402
|
function bootQuietRemainingMs(now = Date.now()) {
|
|
4265
4403
|
return Math.max(0, bootQuietUntilMs - now);
|
|
4266
4404
|
}
|
|
@@ -4270,6 +4408,17 @@ function scheduleAfterBootQuiet(task, additionalDelayMs = 0) {
|
|
|
4270
4408
|
return timer;
|
|
4271
4409
|
}
|
|
4272
4410
|
|
|
4411
|
+
// src/features/magic-context/storage-db.ts
|
|
4412
|
+
import {
|
|
4413
|
+
existsSync as existsSync3,
|
|
4414
|
+
readdirSync,
|
|
4415
|
+
readFileSync as readFileSync3,
|
|
4416
|
+
rmdirSync,
|
|
4417
|
+
statSync as statSync2,
|
|
4418
|
+
unlinkSync
|
|
4419
|
+
} from "node:fs";
|
|
4420
|
+
import { basename, dirname, join as join2, resolve } from "node:path";
|
|
4421
|
+
|
|
4273
4422
|
// src/shared/rpc-utils.ts
|
|
4274
4423
|
import { execFile, execFileSync as execFileSync2 } from "node:child_process";
|
|
4275
4424
|
import { createHash as createHash5 } from "node:crypto";
|
|
@@ -4995,8 +5144,20 @@ function inspectWindowsProcessesSync() {
|
|
|
4995
5144
|
};
|
|
4996
5145
|
}
|
|
4997
5146
|
|
|
5147
|
+
// src/shared/sqlite-context-pragmas.ts
|
|
5148
|
+
function configureContextDatabasePragmas(db, readonly = false) {
|
|
5149
|
+
const [row] = db.prepare(readonly ? "PRAGMA journal_mode" : "PRAGMA journal_mode=WAL").all();
|
|
5150
|
+
const mode = row?.journal_mode?.toLowerCase();
|
|
5151
|
+
if (mode === "memory" || readonly && mode !== "wal")
|
|
5152
|
+
return;
|
|
5153
|
+
if (mode !== "wal") {
|
|
5154
|
+
throw new Error(`context.db requires WAL before synchronous=NORMAL (got ${mode ?? "unknown"})`);
|
|
5155
|
+
}
|
|
5156
|
+
db.exec("PRAGMA synchronous=NORMAL");
|
|
5157
|
+
}
|
|
5158
|
+
|
|
4998
5159
|
// src/features/magic-context/context-store-uuid.ts
|
|
4999
|
-
import { randomUUID
|
|
5160
|
+
import { randomUUID } from "node:crypto";
|
|
5000
5161
|
function getContextStoreUuid(db) {
|
|
5001
5162
|
const row = db.prepare("SELECT value FROM context_store_meta WHERE key = 'store_uuid'").get();
|
|
5002
5163
|
return typeof row?.value === "string" && row.value.length > 0 ? row.value : null;
|
|
@@ -5005,7 +5166,7 @@ function ensureContextStoreUuid(db) {
|
|
|
5005
5166
|
const existing = getContextStoreUuid(db);
|
|
5006
5167
|
if (existing)
|
|
5007
5168
|
return existing;
|
|
5008
|
-
const minted =
|
|
5169
|
+
const minted = randomUUID();
|
|
5009
5170
|
withPrivilegedWriter(db, () => {
|
|
5010
5171
|
db.transaction(() => {
|
|
5011
5172
|
db.prepare("INSERT INTO context_store_meta(key, value) VALUES ('store_uuid', ?) ON CONFLICT(key) DO NOTHING").run(minted);
|
|
@@ -5014,210 +5175,6 @@ function ensureContextStoreUuid(db) {
|
|
|
5014
5175
|
return getContextStoreUuid(db) ?? minted;
|
|
5015
5176
|
}
|
|
5016
5177
|
|
|
5017
|
-
// src/features/magic-context/fail-closed-block.ts
|
|
5018
|
-
var FAIL_CLOSED_DOCTOR_COMMAND = "npx @cortexkit/magic-context@latest doctor";
|
|
5019
|
-
function attachFailClosedBlockingProcessEvidence(process2, evidence) {
|
|
5020
|
-
Object.defineProperties(process2, {
|
|
5021
|
-
startTime: { configurable: true, value: evidence.startTime },
|
|
5022
|
-
commandLine: { configurable: true, value: evidence.commandLine }
|
|
5023
|
-
});
|
|
5024
|
-
return process2;
|
|
5025
|
-
}
|
|
5026
|
-
|
|
5027
|
-
class FailClosedBlockingError extends Error {
|
|
5028
|
-
code = "FAIL_CLOSED_BLOCKING";
|
|
5029
|
-
reason;
|
|
5030
|
-
constructor(message, reason, options) {
|
|
5031
|
-
super(message, options);
|
|
5032
|
-
this.name = "FailClosedBlockingError";
|
|
5033
|
-
this.reason = reason;
|
|
5034
|
-
}
|
|
5035
|
-
}
|
|
5036
|
-
var OPENCODE_INTERNAL_AGENT_NAMES = new Set(["title", "summary", "compaction"]);
|
|
5037
|
-
function isMagicContextHiddenAgentName(agent) {
|
|
5038
|
-
if (agent === "smart-note-compiler" || agent.startsWith("smart-note-")) {
|
|
5039
|
-
return true;
|
|
5040
|
-
}
|
|
5041
|
-
if (agent === "historian" || agent.startsWith("historian-"))
|
|
5042
|
-
return true;
|
|
5043
|
-
if (agent === "dreamer" || agent.startsWith("dreamer-"))
|
|
5044
|
-
return true;
|
|
5045
|
-
return false;
|
|
5046
|
-
}
|
|
5047
|
-
var MAX_FORMATTED_BLOCKING_PROCESSES = 8;
|
|
5048
|
-
var MAX_FORMATTED_COMMAND_LINE_LENGTH = 80;
|
|
5049
|
-
function normalizeFailClosedProcessKind(process2) {
|
|
5050
|
-
switch (process2.kind) {
|
|
5051
|
-
case "OpenCode server":
|
|
5052
|
-
case "OpenCode instance (TUI/CLI)":
|
|
5053
|
-
case "Pi":
|
|
5054
|
-
case "process":
|
|
5055
|
-
return process2.kind;
|
|
5056
|
-
}
|
|
5057
|
-
switch (process2.harness?.trim().toLowerCase()) {
|
|
5058
|
-
case "opencode server":
|
|
5059
|
-
return "OpenCode server";
|
|
5060
|
-
case "opencode instance (tui/cli)":
|
|
5061
|
-
case "opencode instance":
|
|
5062
|
-
return "OpenCode instance (TUI/CLI)";
|
|
5063
|
-
case "pi":
|
|
5064
|
-
case "pi harness":
|
|
5065
|
-
case "omp":
|
|
5066
|
-
return "Pi";
|
|
5067
|
-
default:
|
|
5068
|
-
return "process";
|
|
5069
|
-
}
|
|
5070
|
-
}
|
|
5071
|
-
function formatFailClosedProcessStartTime(startTime) {
|
|
5072
|
-
if (!Number.isFinite(startTime) || startTime <= 0)
|
|
5073
|
-
return "unverified";
|
|
5074
|
-
try {
|
|
5075
|
-
const parts = new Intl.DateTimeFormat("en-US", {
|
|
5076
|
-
day: "numeric",
|
|
5077
|
-
hour: "2-digit",
|
|
5078
|
-
minute: "2-digit",
|
|
5079
|
-
month: "short",
|
|
5080
|
-
hour12: false
|
|
5081
|
-
}).formatToParts(new Date(startTime));
|
|
5082
|
-
const values = Object.fromEntries(parts.map(({ type, value }) => [type, value]));
|
|
5083
|
-
if (!values.month || !values.day || !values.hour || !values.minute)
|
|
5084
|
-
return "unverified";
|
|
5085
|
-
return `${values.month} ${values.day} ${values.hour}:${values.minute}`;
|
|
5086
|
-
} catch {
|
|
5087
|
-
return "unverified";
|
|
5088
|
-
}
|
|
5089
|
-
}
|
|
5090
|
-
function formatFailClosedCommandLine(commandLine) {
|
|
5091
|
-
if (typeof commandLine !== "string" || commandLine.trim().length === 0)
|
|
5092
|
-
return "unverified";
|
|
5093
|
-
try {
|
|
5094
|
-
const normalized = commandLine.replaceAll("\x00", " ").replace(/\s+/g, " ").trim();
|
|
5095
|
-
if (normalized.length === 0)
|
|
5096
|
-
return "unverified";
|
|
5097
|
-
const sanitized = sanitizeDiagnosticText(normalized).trim();
|
|
5098
|
-
if (sanitized.length === 0)
|
|
5099
|
-
return "unverified";
|
|
5100
|
-
return sanitized.length > MAX_FORMATTED_COMMAND_LINE_LENGTH ? `${sanitized.slice(0, MAX_FORMATTED_COMMAND_LINE_LENGTH - 3)}...` : sanitized;
|
|
5101
|
-
} catch {
|
|
5102
|
-
return "unverified";
|
|
5103
|
-
}
|
|
5104
|
-
}
|
|
5105
|
-
function formatFailClosedBlockingProcesses(processes) {
|
|
5106
|
-
const uniqueProcesses = new Map;
|
|
5107
|
-
for (const process2 of processes) {
|
|
5108
|
-
if (!Number.isInteger(process2.pid) || process2.pid <= 0)
|
|
5109
|
-
continue;
|
|
5110
|
-
const candidate = {
|
|
5111
|
-
kind: normalizeFailClosedProcessKind(process2),
|
|
5112
|
-
pid: process2.pid,
|
|
5113
|
-
startTime: process2.startTime,
|
|
5114
|
-
commandLine: process2.commandLine
|
|
5115
|
-
};
|
|
5116
|
-
const previous = uniqueProcesses.get(process2.pid);
|
|
5117
|
-
if (!previous || previous.kind === "process" && candidate.kind !== "process") {
|
|
5118
|
-
uniqueProcesses.set(process2.pid, candidate);
|
|
5119
|
-
} else {
|
|
5120
|
-
uniqueProcesses.set(process2.pid, {
|
|
5121
|
-
...previous,
|
|
5122
|
-
startTime: previous.startTime ?? candidate.startTime,
|
|
5123
|
-
commandLine: previous.commandLine ?? candidate.commandLine
|
|
5124
|
-
});
|
|
5125
|
-
}
|
|
5126
|
-
}
|
|
5127
|
-
const entries = [...uniqueProcesses.values()];
|
|
5128
|
-
const visible = entries.slice(0, MAX_FORMATTED_BLOCKING_PROCESSES);
|
|
5129
|
-
const rendered = visible.map(({ kind, pid }) => `${kind} (PID ${pid})`);
|
|
5130
|
-
const omitted = entries.length - visible.length;
|
|
5131
|
-
if (omitted > 0)
|
|
5132
|
-
rendered.push(`${omitted} more blocking process(es)`);
|
|
5133
|
-
if (rendered.length === 0)
|
|
5134
|
-
return "a live process";
|
|
5135
|
-
const summary = rendered.length === 1 ? rendered[0] : `${rendered.slice(0, -1).join(", ")}, and ${rendered.at(-1)}`;
|
|
5136
|
-
const evidence = entries.map(({ kind, pid, startTime, commandLine }) => `- PID ${pid}: ${kind}, started ${formatFailClosedProcessStartTime(startTime)}, cmd: ${formatFailClosedCommandLine(commandLine)}`).join(`
|
|
5137
|
-
`);
|
|
5138
|
-
return `${summary}
|
|
5139
|
-
Blocking process evidence:
|
|
5140
|
-
${evidence}`;
|
|
5141
|
-
}
|
|
5142
|
-
function formatFailClosedBlockingMessage(reason) {
|
|
5143
|
-
if (reason.kind === "migration_guard") {
|
|
5144
|
-
if (reason.unreadableFile) {
|
|
5145
|
-
const arm = reason.unreadableArm ?? "io";
|
|
5146
|
-
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.`;
|
|
5147
|
-
return [
|
|
5148
|
-
`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.`,
|
|
5149
|
-
recovery,
|
|
5150
|
-
`Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
|
|
5151
|
-
].join(" ");
|
|
5152
|
-
}
|
|
5153
|
-
return [
|
|
5154
|
-
`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.`,
|
|
5155
|
-
`The database is at upstream migration v${reason.persistedVersion}; this build needs v${reason.supportedVersion}.`,
|
|
5156
|
-
"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.",
|
|
5157
|
-
"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.",
|
|
5158
|
-
`Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
|
|
5159
|
-
].join(" ");
|
|
5160
|
-
}
|
|
5161
|
-
if (reason.kind === "schema_fence") {
|
|
5162
|
-
return [
|
|
5163
|
-
`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}).`,
|
|
5164
|
-
`Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
|
|
5165
|
-
].join(" ");
|
|
5166
|
-
}
|
|
5167
|
-
const cause = reason.cause.trim().length > 0 ? reason.cause.trim() : "unknown storage error";
|
|
5168
|
-
return [
|
|
5169
|
-
`Magic Context cannot operate: persistent storage failed (${cause}).`,
|
|
5170
|
-
"The plugin will not silently degrade to native compaction while enabled.",
|
|
5171
|
-
`Recovery: ${FAIL_CLOSED_DOCTOR_COMMAND}`
|
|
5172
|
-
].join(" ");
|
|
5173
|
-
}
|
|
5174
|
-
function formatFailClosedBlockingSummary(reason) {
|
|
5175
|
-
if (reason.kind === "migration_guard") {
|
|
5176
|
-
if (reason.unreadableFile) {
|
|
5177
|
-
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.`;
|
|
5178
|
-
}
|
|
5179
|
-
const blockers = reason.blockingProcesses.filter((process2) => Number.isInteger(process2.pid) && process2.pid > 0).map((process2) => `${normalizeFailClosedProcessKind(process2)} (PID ${process2.pid})`);
|
|
5180
|
-
const named = blockers.length > 0 ? blockers.join(", ") : "a live process";
|
|
5181
|
-
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.`;
|
|
5182
|
-
}
|
|
5183
|
-
if (reason.kind === "schema_fence") {
|
|
5184
|
-
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.`;
|
|
5185
|
-
}
|
|
5186
|
-
const cause = reason.cause.trim().length > 0 ? reason.cause.trim() : "unknown storage error";
|
|
5187
|
-
return `Magic Context cannot operate: persistent storage failed (${cause}).`;
|
|
5188
|
-
}
|
|
5189
|
-
function createFailClosedBlockingError(reason, options) {
|
|
5190
|
-
return new FailClosedBlockingError(formatFailClosedBlockingMessage(reason), reason, options);
|
|
5191
|
-
}
|
|
5192
|
-
function isFailClosedBlockingError(error) {
|
|
5193
|
-
return error instanceof FailClosedBlockingError || typeof error === "object" && error !== null && error.name === "FailClosedBlockingError" && error.code === "FAIL_CLOSED_BLOCKING";
|
|
5194
|
-
}
|
|
5195
|
-
function shouldBypassFailClosedBlock(input) {
|
|
5196
|
-
if (input.isPiSubagentEnv === true)
|
|
5197
|
-
return true;
|
|
5198
|
-
if (input.isInternalChildSession === true)
|
|
5199
|
-
return true;
|
|
5200
|
-
const agent = typeof input.agent === "string" ? input.agent.trim() : "";
|
|
5201
|
-
if (agent.length === 0)
|
|
5202
|
-
return false;
|
|
5203
|
-
if (OPENCODE_INTERNAL_AGENT_NAMES.has(agent))
|
|
5204
|
-
return true;
|
|
5205
|
-
if (isMagicContextHiddenAgentName(agent))
|
|
5206
|
-
return true;
|
|
5207
|
-
return false;
|
|
5208
|
-
}
|
|
5209
|
-
function resolveAgentNameFromMessages(messages) {
|
|
5210
|
-
for (let i = messages.length - 1;i >= 0; i -= 1) {
|
|
5211
|
-
const info = messages[i]?.info;
|
|
5212
|
-
if (!info || typeof info !== "object")
|
|
5213
|
-
continue;
|
|
5214
|
-
const agent = info.agent;
|
|
5215
|
-
if (typeof agent === "string" && agent.length > 0)
|
|
5216
|
-
return agent;
|
|
5217
|
-
}
|
|
5218
|
-
return;
|
|
5219
|
-
}
|
|
5220
|
-
|
|
5221
5178
|
// src/features/magic-context/message-fts-rowid-map.ts
|
|
5222
5179
|
import { createHash as createHash6 } from "node:crypto";
|
|
5223
5180
|
var MESSAGE_FTS_ROWID_MAP_BACKFILL_BATCH_SIZE = 100;
|
|
@@ -5452,6 +5409,9 @@ function startBootDeadline(timeoutMs, onDeferred) {
|
|
|
5452
5409
|
}
|
|
5453
5410
|
|
|
5454
5411
|
// src/features/magic-context/migration-worker-client.ts
|
|
5412
|
+
function workerStartError(detail) {
|
|
5413
|
+
return new Error(`the migration worker could not start (${detail}); reinstall or rebuild the plugin`);
|
|
5414
|
+
}
|
|
5455
5415
|
function defaultWorkerEntry() {
|
|
5456
5416
|
return new URL(new URL(import.meta.url).pathname.endsWith(".ts") ? "./migration-worker.ts" : "./migration-worker.js", import.meta.url);
|
|
5457
5417
|
}
|
|
@@ -5462,8 +5422,7 @@ function runMigrationsOffThread(data) {
|
|
|
5462
5422
|
try {
|
|
5463
5423
|
worker = new Worker(workerEntryOverride ?? defaultWorkerEntry(), { workerData: data });
|
|
5464
5424
|
} catch (error) {
|
|
5465
|
-
|
|
5466
|
-
resolve("worker_unavailable");
|
|
5425
|
+
reject(workerStartError(error instanceof Error ? error.message : String(error)));
|
|
5467
5426
|
return;
|
|
5468
5427
|
}
|
|
5469
5428
|
log(`[migrations] applying pending migrations on a worker thread: ${data.dbPath}`);
|
|
@@ -5487,14 +5446,14 @@ function runMigrationsOffThread(data) {
|
|
|
5487
5446
|
};
|
|
5488
5447
|
const unavailable = (detail) => {
|
|
5489
5448
|
settle(() => {
|
|
5490
|
-
|
|
5491
|
-
resolve("worker_unavailable");
|
|
5449
|
+
reject(workerStartError(detail));
|
|
5492
5450
|
});
|
|
5493
5451
|
};
|
|
5494
5452
|
worker.on("message", (message) => {
|
|
5495
5453
|
switch (message.type) {
|
|
5496
5454
|
case "ready":
|
|
5497
5455
|
ready = true;
|
|
5456
|
+
log(`[migrations] migration worker ready: ${data.dbPath}`);
|
|
5498
5457
|
return;
|
|
5499
5458
|
case "log":
|
|
5500
5459
|
writeForwardedLogLine(message.line);
|
|
@@ -5504,9 +5463,17 @@ function runMigrationsOffThread(data) {
|
|
|
5504
5463
|
setBusy(!message.waiting);
|
|
5505
5464
|
return;
|
|
5506
5465
|
case "done":
|
|
5466
|
+
if (!ready) {
|
|
5467
|
+
unavailable("reported completion before ready");
|
|
5468
|
+
return;
|
|
5469
|
+
}
|
|
5507
5470
|
settle(() => resolve("migrated"));
|
|
5508
5471
|
return;
|
|
5509
5472
|
case "failed":
|
|
5473
|
+
if (!ready) {
|
|
5474
|
+
unavailable(message.message);
|
|
5475
|
+
return;
|
|
5476
|
+
}
|
|
5510
5477
|
settle(() => reject(new Error(message.message)));
|
|
5511
5478
|
return;
|
|
5512
5479
|
}
|
|
@@ -5808,38 +5775,14 @@ function getSchemaFenceRejection() {
|
|
|
5808
5775
|
function getMigrationOnOpenRefusal() {
|
|
5809
5776
|
return lastMigrationOnOpenRefusal;
|
|
5810
5777
|
}
|
|
5811
|
-
var LATEST_SUPPORTED_VERSION =
|
|
5778
|
+
var LATEST_SUPPORTED_VERSION = 95;
|
|
5812
5779
|
var BOOT_SQLITE_BUSY_TIMEOUT_MS = 5000;
|
|
5813
|
-
var PERMISSIONS_ENFORCEABLE = process.platform !== "win32";
|
|
5814
|
-
var defaultStoragePermissionFs = { chmodSync, mkdirSync: mkdirSync2 };
|
|
5815
|
-
var storagePermissionFs = defaultStoragePermissionFs;
|
|
5816
5780
|
function ensureSecureStorageDir(dir) {
|
|
5817
|
-
|
|
5818
|
-
storagePermissionFs.mkdirSync(dir, { recursive: true });
|
|
5819
|
-
return;
|
|
5820
|
-
}
|
|
5821
|
-
storagePermissionFs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
5822
|
-
if (!PERMISSIONS_ENFORCEABLE)
|
|
5823
|
-
return;
|
|
5824
|
-
try {
|
|
5825
|
-
storagePermissionFs.chmodSync(dir, 448);
|
|
5826
|
-
} catch (error) {
|
|
5827
|
-
log(`[magic-context] could not restrict storage dir permissions on ${dir}: ${getErrorMessage(error)}`);
|
|
5828
|
-
}
|
|
5781
|
+
ensureStorageDirectorySync(dir);
|
|
5829
5782
|
}
|
|
5830
|
-
function
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
for (const suffix of ["", "-wal", "-shm"]) {
|
|
5834
|
-
const file = `${dbPath}${suffix}`;
|
|
5835
|
-
if (!existsSync3(file))
|
|
5836
|
-
continue;
|
|
5837
|
-
try {
|
|
5838
|
-
storagePermissionFs.chmodSync(file, 384);
|
|
5839
|
-
} catch (error) {
|
|
5840
|
-
log(`[magic-context] could not restrict DB file permissions on ${file}: ${getErrorMessage(error)}`);
|
|
5841
|
-
}
|
|
5842
|
-
}
|
|
5783
|
+
function tightenStorageTree(dir) {
|
|
5784
|
+
const { failures } = tightenStorageTreeSync(dir);
|
|
5785
|
+
log(`[magic-context] storage permission tightening failures=${failures}`);
|
|
5843
5786
|
}
|
|
5844
5787
|
function resolveBootBusyTimeoutMs(value) {
|
|
5845
5788
|
if (value === undefined)
|
|
@@ -5875,6 +5818,7 @@ function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
|
|
|
5875
5818
|
try {
|
|
5876
5819
|
const legacyDb = new Database(legacyDbPath);
|
|
5877
5820
|
try {
|
|
5821
|
+
configureContextDatabasePragmas(legacyDb, true);
|
|
5878
5822
|
legacyDb.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
5879
5823
|
} finally {
|
|
5880
5824
|
closeQuietly(legacyDb);
|
|
@@ -5887,7 +5831,7 @@ function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
|
|
|
5887
5831
|
const dst = join2(targetDbDir, `context.db${suffix}`);
|
|
5888
5832
|
if (existsSync3(src)) {
|
|
5889
5833
|
try {
|
|
5890
|
-
|
|
5834
|
+
copyStorageFileSync(src, dst);
|
|
5891
5835
|
} catch (error) {
|
|
5892
5836
|
log(`[magic-context] failed to copy ${src}:`, getErrorMessage(error));
|
|
5893
5837
|
}
|
|
@@ -5897,7 +5841,7 @@ function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
|
|
|
5897
5841
|
const targetModelsDir = join2(targetDbDir, "models");
|
|
5898
5842
|
if (existsSync3(legacyModelsDir) && !existsSync3(targetModelsDir)) {
|
|
5899
5843
|
try {
|
|
5900
|
-
|
|
5844
|
+
copyStorageTreeSync(legacyModelsDir, targetModelsDir);
|
|
5901
5845
|
} catch (error) {
|
|
5902
5846
|
log("[magic-context] failed to copy embedding model cache:", getErrorMessage(error));
|
|
5903
5847
|
}
|
|
@@ -6236,6 +6180,12 @@ function applySqliteTuningPragmas(db) {
|
|
|
6236
6180
|
db.exec(`PRAGMA mmap_size=${Math.round(sqlitePragmaConfig.mmapSizeMb * 1024 * 1024)}`);
|
|
6237
6181
|
db.exec("PRAGMA analysis_limit=400");
|
|
6238
6182
|
}
|
|
6183
|
+
function runSqliteOptimize(db) {
|
|
6184
|
+
try {
|
|
6185
|
+
db.exec("PRAGMA analysis_limit=400");
|
|
6186
|
+
db.exec("PRAGMA optimize");
|
|
6187
|
+
} catch {}
|
|
6188
|
+
}
|
|
6239
6189
|
function finishDatabaseOpen(db, dbPath, explicitDbPath, latestSupportedVersion) {
|
|
6240
6190
|
if (!enforceSchemaFence(db, dbPath, latestSupportedVersion)) {
|
|
6241
6191
|
closeQuietly(db);
|
|
@@ -6256,8 +6206,8 @@ function finishDatabaseOpen(db, dbPath, explicitDbPath, latestSupportedVersion)
|
|
|
6256
6206
|
startMessageFtsRowidMapBackfill(db).then(async () => {
|
|
6257
6207
|
if (!readsOpenCodeStore)
|
|
6258
6208
|
return;
|
|
6259
|
-
const { readRawSessionMessagePage, readRawSessionMessages } = await importPluginModule(() => import("./read-session-chunk-
|
|
6260
|
-
const { startMessageTimeBackfill } = await importPluginModule(() => import("./message-time-backfill-
|
|
6209
|
+
const { readRawSessionMessagePage, readRawSessionMessages } = await importPluginModule(() => import("./read-session-chunk-8vjp6581.js"));
|
|
6210
|
+
const { startMessageTimeBackfill } = await importPluginModule(() => import("./message-time-backfill-r6qkem1x.js"));
|
|
6261
6211
|
await startMessageTimeBackfill(db, Object.assign(readRawSessionMessages, {
|
|
6262
6212
|
readPage: readRawSessionMessagePage
|
|
6263
6213
|
}));
|
|
@@ -6280,7 +6230,7 @@ function finishDatabaseOpen(db, dbPath, explicitDbPath, latestSupportedVersion)
|
|
|
6280
6230
|
}
|
|
6281
6231
|
setDatabase(db);
|
|
6282
6232
|
loadToolDefinitionMeasurements(db);
|
|
6283
|
-
|
|
6233
|
+
tightenStorageTree(dirname(dbPath));
|
|
6284
6234
|
databases.set(dbPath, db);
|
|
6285
6235
|
pathByDatabase.set(db, dbPath);
|
|
6286
6236
|
persistenceByDatabase.set(db, true);
|
|
@@ -6293,7 +6243,7 @@ function finishDatabaseOpen(db, dbPath, explicitDbPath, latestSupportedVersion)
|
|
|
6293
6243
|
function initializeDatabase(db, busyTimeoutMs = BOOT_SQLITE_BUSY_TIMEOUT_MS) {
|
|
6294
6244
|
db.exec(`PRAGMA busy_timeout=${resolveBootBusyTimeoutMs(busyTimeoutMs)}`);
|
|
6295
6245
|
db.exec("PRAGMA foreign_keys=ON");
|
|
6296
|
-
db
|
|
6246
|
+
configureContextDatabasePragmas(db);
|
|
6297
6247
|
applySqliteTuningPragmas(db);
|
|
6298
6248
|
db.exec(`
|
|
6299
6249
|
CREATE TABLE IF NOT EXISTS tags (
|
|
@@ -6355,7 +6305,6 @@ function initializeDatabase(db, busyTimeoutMs = BOOT_SQLITE_BUSY_TIMEOUT_MS) {
|
|
|
6355
6305
|
harness TEXT NOT NULL DEFAULT 'opencode',
|
|
6356
6306
|
UNIQUE(session_id, sequence)
|
|
6357
6307
|
);
|
|
6358
|
-
CREATE INDEX IF NOT EXISTS idx_compartments_session ON compartments(session_id);
|
|
6359
6308
|
|
|
6360
6309
|
CREATE TABLE IF NOT EXISTS compartment_chunk_embeddings (
|
|
6361
6310
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -6416,7 +6365,6 @@ function initializeDatabase(db, busyTimeoutMs = BOOT_SQLITE_BUSY_TIMEOUT_MS) {
|
|
|
6416
6365
|
harness TEXT NOT NULL DEFAULT 'opencode',
|
|
6417
6366
|
PRIMARY KEY(session_id, message_ordinal)
|
|
6418
6367
|
);
|
|
6419
|
-
CREATE INDEX IF NOT EXISTS idx_compression_depth_session ON compression_depth(session_id);
|
|
6420
6368
|
|
|
6421
6369
|
CREATE TABLE IF NOT EXISTS session_facts (
|
|
6422
6370
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -6722,7 +6670,6 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
|
|
|
6722
6670
|
stale_reason TEXT,
|
|
6723
6671
|
PRIMARY KEY (project_path, path)
|
|
6724
6672
|
);
|
|
6725
|
-
CREATE INDEX IF NOT EXISTS idx_project_key_files_project ON project_key_files(project_path);
|
|
6726
6673
|
CREATE INDEX IF NOT EXISTS idx_project_key_files_generated_at ON project_key_files(project_path, generated_at);
|
|
6727
6674
|
|
|
6728
6675
|
CREATE TABLE IF NOT EXISTS project_key_files_version (
|
|
@@ -7107,10 +7054,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
|
|
|
7107
7054
|
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
7108
7055
|
PRIMARY KEY (session_id, harness, message_id)
|
|
7109
7056
|
);
|
|
7110
|
-
CREATE INDEX IF NOT EXISTS idx_transform_decisions_session_harness
|
|
7111
|
-
ON transform_decisions(session_id, harness);
|
|
7112
7057
|
|
|
7113
|
-
CREATE INDEX IF NOT EXISTS idx_tags_session_tag_number ON tags(session_id, tag_number);
|
|
7114
7058
|
CREATE INDEX IF NOT EXISTS idx_tags_session_message_id ON tags(session_id, message_id);
|
|
7115
7059
|
|
|
7116
7060
|
-- Clone/import paths can write tags before session bootstrap. Keep trigger-created
|
|
@@ -7156,9 +7100,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
|
|
|
7156
7100
|
WHERE NEW.session_id != OLD.session_id
|
|
7157
7101
|
ON CONFLICT(session_id) DO UPDATE SET tags_version = tags_version + 1;
|
|
7158
7102
|
END;
|
|
7159
|
-
CREATE INDEX IF NOT EXISTS idx_pending_ops_session ON pending_ops(session_id);
|
|
7160
7103
|
CREATE INDEX IF NOT EXISTS idx_pending_ops_session_tag_id ON pending_ops(session_id, tag_id);
|
|
7161
|
-
CREATE INDEX IF NOT EXISTS idx_source_contents_session ON source_contents(session_id);
|
|
7162
7104
|
|
|
7163
7105
|
CREATE TABLE IF NOT EXISTS recomp_compartments (
|
|
7164
7106
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -7511,8 +7453,6 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
|
|
|
7511
7453
|
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
7512
7454
|
PRIMARY KEY (session_id, harness, message_id)
|
|
7513
7455
|
);
|
|
7514
|
-
CREATE INDEX IF NOT EXISTS idx_transform_decisions_session_harness
|
|
7515
|
-
ON transform_decisions(session_id, harness);
|
|
7516
7456
|
`);
|
|
7517
7457
|
ensureColumn(db, "transform_decisions", "system_hash_prev", "TEXT");
|
|
7518
7458
|
ensureColumn(db, "transform_decisions", "system_hash_new", "TEXT");
|
|
@@ -7534,12 +7474,26 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
|
|
|
7534
7474
|
ON message_history_index(harness, session_id, updated_at);
|
|
7535
7475
|
`);
|
|
7536
7476
|
ensureColumn(db, "workspaces", "share_categories", `TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'`);
|
|
7477
|
+
const version = getPersistedSchemaVersion(db);
|
|
7478
|
+
if (version === 0 || version >= 95)
|
|
7479
|
+
installV95PerfSchema(db, false, version === 0);
|
|
7537
7480
|
}
|
|
7538
7481
|
var CHANNEL2_CLAIM_TTL_MS = 10 * 60000;
|
|
7482
|
+
var channel2HealStatements = new WeakMap;
|
|
7539
7483
|
function healWedgedChannel2Claims(db) {
|
|
7540
7484
|
try {
|
|
7541
7485
|
const staleBefore = Date.now() - CHANNEL2_CLAIM_TTL_MS;
|
|
7542
|
-
|
|
7486
|
+
let statements = channel2HealStatements.get(db);
|
|
7487
|
+
if (!statements) {
|
|
7488
|
+
const predicate = "channel2_nudge_state = 'claimed' AND (channel2_nudge_claimed_at IS NULL OR channel2_nudge_claimed_at = 0 OR channel2_nudge_claimed_at <= ?)";
|
|
7489
|
+
statements = {
|
|
7490
|
+
stale: db.prepare(`SELECT 1 FROM session_meta WHERE ${predicate} LIMIT 1`),
|
|
7491
|
+
heal: db.prepare(`UPDATE session_meta SET channel2_nudge_state = '', channel2_nudge_claimed_at = 0, channel2_nudge_claim_token = '' WHERE ${predicate}`)
|
|
7492
|
+
};
|
|
7493
|
+
channel2HealStatements.set(db, statements);
|
|
7494
|
+
}
|
|
7495
|
+
if (statements.stale.get(staleBefore))
|
|
7496
|
+
statements.heal.run(staleBefore);
|
|
7543
7497
|
} catch {}
|
|
7544
7498
|
}
|
|
7545
7499
|
function openDatabase(dbPathOrOptions) {
|
|
@@ -7571,18 +7525,38 @@ function openDatabase(dbPathOrOptions) {
|
|
|
7571
7525
|
migrateLegacyStorageIfNeeded(dbPath, dbDir);
|
|
7572
7526
|
}
|
|
7573
7527
|
ensureSecureStorageDir(dbDir);
|
|
7528
|
+
tightenStorageTree(dbDir);
|
|
7529
|
+
if (!existsSync3(dbPath) && shouldEnforcePrivateStoragePermissions()) {
|
|
7530
|
+
try {
|
|
7531
|
+
writeStorageFileSync(dbPath, new Uint8Array, { flag: "wx" });
|
|
7532
|
+
} catch (error) {
|
|
7533
|
+
if (error.code !== "EEXIST")
|
|
7534
|
+
throw error;
|
|
7535
|
+
}
|
|
7536
|
+
}
|
|
7574
7537
|
db = new Database(dbPath);
|
|
7575
7538
|
installBootBusyTimeout(db, dbPath, busyTimeoutMs, options?.onBootBusyTimeout);
|
|
7576
7539
|
if (!enforceSchemaFence(db, dbPath, latestSupportedVersion)) {
|
|
7577
7540
|
closeQuietly(db);
|
|
7578
7541
|
return null;
|
|
7579
7542
|
}
|
|
7543
|
+
if (options?.allowMigrations === false && hasPendingMigrations(db)) {
|
|
7544
|
+
log(`[magic-context] storage not ready: pending migrations at ${dbPath}; host callers must await the async opener`);
|
|
7545
|
+
closeQuietly(db);
|
|
7546
|
+
return null;
|
|
7547
|
+
}
|
|
7580
7548
|
if (!enforceMigrationOnOpenGuard(db, dbPath, dbDir, latestSupportedVersion)) {
|
|
7581
7549
|
closeQuietly(db);
|
|
7582
7550
|
return null;
|
|
7583
7551
|
}
|
|
7584
7552
|
initializeDatabase(db, busyTimeoutMs);
|
|
7585
|
-
|
|
7553
|
+
if (options?.allowMigrations === false) {
|
|
7554
|
+
if (hasPendingMigrations(db)) {
|
|
7555
|
+
closeQuietly(db);
|
|
7556
|
+
return null;
|
|
7557
|
+
}
|
|
7558
|
+
} else
|
|
7559
|
+
runMigrations(db);
|
|
7586
7560
|
ensureContextStoreUuid(db);
|
|
7587
7561
|
return finishDatabaseOpen(db, dbPath, explicitDbPath, latestSupportedVersion);
|
|
7588
7562
|
} catch (error) {
|
|
@@ -7593,6 +7567,9 @@ function openDatabase(dbPathOrOptions) {
|
|
|
7593
7567
|
throw new Error(`[magic-context] storage unavailable: ${detail}. Magic Context is disabled for this run; check log for details.`);
|
|
7594
7568
|
}
|
|
7595
7569
|
}
|
|
7570
|
+
function openCurrentDatabase(options = {}) {
|
|
7571
|
+
return openDatabase({ ...options, allowMigrations: false });
|
|
7572
|
+
}
|
|
7596
7573
|
async function openDatabaseAsync(dbPathOrOptions) {
|
|
7597
7574
|
const options = typeof dbPathOrOptions === "string" ? { dbPath: dbPathOrOptions } : dbPathOrOptions;
|
|
7598
7575
|
const explicitDbPath = options?.dbPath !== undefined;
|
|
@@ -7620,8 +7597,12 @@ async function openDatabaseAsync(dbPathOrOptions) {
|
|
|
7620
7597
|
const pending = pendingAsyncOpens.get(dbPath);
|
|
7621
7598
|
if (pending)
|
|
7622
7599
|
return pending;
|
|
7600
|
+
if (!isFileBackedPath(dbPath)) {
|
|
7601
|
+
throw new Error("async migration requires a file-backed database; use the explicit synchronous opener for in-memory or URI test databases");
|
|
7602
|
+
}
|
|
7623
7603
|
const opening = (async () => {
|
|
7624
7604
|
let db;
|
|
7605
|
+
const mainThreadBodiesBefore = getMainThreadMigrationBodyCount();
|
|
7625
7606
|
const openStartedAt = performance.now();
|
|
7626
7607
|
let openMs = 0;
|
|
7627
7608
|
let guardMs = 0;
|
|
@@ -7632,6 +7613,15 @@ async function openDatabaseAsync(dbPathOrOptions) {
|
|
|
7632
7613
|
if (!explicitDbPath)
|
|
7633
7614
|
migrateLegacyStorageIfNeeded(dbPath, dbDir);
|
|
7634
7615
|
ensureSecureStorageDir(dbDir);
|
|
7616
|
+
tightenStorageTree(dbDir);
|
|
7617
|
+
if (!existsSync3(dbPath) && shouldEnforcePrivateStoragePermissions()) {
|
|
7618
|
+
try {
|
|
7619
|
+
writeStorageFileSync(dbPath, new Uint8Array, { flag: "wx" });
|
|
7620
|
+
} catch (error) {
|
|
7621
|
+
if (error.code !== "EEXIST")
|
|
7622
|
+
throw error;
|
|
7623
|
+
}
|
|
7624
|
+
}
|
|
7635
7625
|
db = new Database(dbPath);
|
|
7636
7626
|
installBootBusyTimeout(db, dbPath, busyTimeoutMs, options?.onBootBusyTimeout);
|
|
7637
7627
|
openMs = performance.now() - openStartedAt;
|
|
@@ -7649,15 +7639,20 @@ async function openDatabaseAsync(dbPathOrOptions) {
|
|
|
7649
7639
|
}
|
|
7650
7640
|
guardMs = performance.now() - guardStartedAt;
|
|
7651
7641
|
migrateStartedAt = performance.now();
|
|
7652
|
-
if (
|
|
7642
|
+
if (hasPendingMigrations(db)) {
|
|
7653
7643
|
await runMigrationsOffThread({
|
|
7654
7644
|
dbPath,
|
|
7655
7645
|
busyTimeoutMs,
|
|
7656
7646
|
sqlitePragmaConfig: { ...sqlitePragmaConfig }
|
|
7657
7647
|
});
|
|
7658
7648
|
}
|
|
7649
|
+
if (hasPendingMigrations(db)) {
|
|
7650
|
+
throw new Error("the migration worker did not complete the pending migration; reinstall or rebuild the plugin");
|
|
7651
|
+
}
|
|
7659
7652
|
initializeDatabase(db, busyTimeoutMs);
|
|
7660
|
-
|
|
7653
|
+
if (hasPendingMigrations(db)) {
|
|
7654
|
+
throw new Error("storage initialization left a pending migration; reinstall or rebuild the plugin");
|
|
7655
|
+
}
|
|
7661
7656
|
ensureContextStoreUuid(db);
|
|
7662
7657
|
const opened = finishDatabaseOpen(db, dbPath, explicitDbPath, latestSupportedVersion);
|
|
7663
7658
|
migrateMs = performance.now() - migrateStartedAt;
|
|
@@ -7669,6 +7664,7 @@ async function openDatabaseAsync(dbPathOrOptions) {
|
|
|
7669
7664
|
log(`[magic-context] storage fatal: failed to open ${dbPath}: ${detail}`);
|
|
7670
7665
|
throw new Error(`[magic-context] storage unavailable: ${detail}. Magic Context is disabled for this run; check log for details.`);
|
|
7671
7666
|
} finally {
|
|
7667
|
+
log(`[migrations] async open main-thread migration-body count: ${getMainThreadMigrationBodyCount() - mainThreadBodiesBefore} (total=${getMainThreadMigrationBodyCount()}) path=${dbPath}`);
|
|
7672
7668
|
if (openMs === 0)
|
|
7673
7669
|
openMs = performance.now() - openStartedAt;
|
|
7674
7670
|
if (guardStartedAt !== null && guardMs === 0) {
|
|
@@ -7699,4 +7695,4 @@ function getDatabasePersistenceError(db) {
|
|
|
7699
7695
|
return persistenceErrorByDatabase.get(db) ?? null;
|
|
7700
7696
|
}
|
|
7701
7697
|
|
|
7702
|
-
export { FAIL_CLOSED_DOCTOR_COMMAND, formatFailClosedBlockingMessage, formatFailClosedBlockingSummary, createFailClosedBlockingError, isFailClosedBlockingError, shouldBypassFailClosedBlock, resolveAgentNameFromMessages, canonicalProjectDirectory, setHomeProjectPermission, shouldSkipHomeProjectMemory, ProjectIdentityError, takeDubiousOwnershipProjectIdentityWarning, isUserHomeDirectory, resolveProjectIdentity, directoryHasGitMetadata, describeUnresolvedProjectIdentity, isUsableProjectIdentity, resolveProjectIdentityForSession, normalizeStoredProjectPath, storedPathBelongsToIdentity, recordMessageFtsRowid, messageFtsOrdinalRangeIsMapped, deleteUnmappedMessageFtsRows, registerOwnRpcServerInstance, rpcPortDir, rpcPortFilePath, isPidAlive, parseRpcPortFile, inspectProcessesAsync, V2_MEMORY_CATEGORIES, PROMOTABLE_CATEGORIES, CATEGORY_PRIORITY, getMemoryCategoryOrder, CATEGORY_DEFAULT_TTL, unescapeXml, parseCompartmentOutput, layoutLkgPrefix, resolveWorkspaceShareCategories, resolveWorkspaceIdentitySet, expandWorkspaceIdentitySetWithAliases, resolveStoredPathWorkspaceIdentity, storedPathBelongsToWorkspace, sourceNameForMemory, computeWorkspaceEpochFingerprint, managedAuthorityNoteRow, runMigrationsWithRetry, scheduleAfterBootQuiet, startBootDeadline, getUnconfirmedMigrationHolders, getSchemaFenceRejection, getMigrationOnOpenRefusal, LATEST_SUPPORTED_VERSION, BOOT_SQLITE_BUSY_TIMEOUT_MS, getPersistedSchemaVersion, setSqlitePragmaConfig, initializeDatabase, openDatabase, openDatabaseAsync, isDatabasePersisted, getDatabasePersistenceError };
|
|
7698
|
+
export { FAIL_CLOSED_DOCTOR_COMMAND, formatFailClosedBlockingMessage, formatFailClosedBlockingSummary, createFailClosedBlockingError, isFailClosedBlockingError, shouldBypassFailClosedBlock, resolveAgentNameFromMessages, canonicalProjectDirectory, setHomeProjectPermission, shouldSkipHomeProjectMemory, ProjectIdentityError, takeDubiousOwnershipProjectIdentityWarning, isUserHomeDirectory, resolveProjectIdentity, directoryHasGitMetadata, describeUnresolvedProjectIdentity, isUsableProjectIdentity, resolveProjectIdentityForSession, normalizeStoredProjectPath, storedPathBelongsToIdentity, recordMessageFtsRowid, messageFtsOrdinalRangeIsMapped, deleteUnmappedMessageFtsRows, registerOwnRpcServerInstance, rpcPortDir, rpcPortFilePath, isPidAlive, parseRpcPortFile, inspectProcessesAsync, V2_MEMORY_CATEGORIES, PROMOTABLE_CATEGORIES, CATEGORY_PRIORITY, getMemoryCategoryOrder, CATEGORY_DEFAULT_TTL, unescapeXml, parseCompartmentOutput, layoutLkgPrefix, assembleLkgPrefix, resolveWorkspaceShareCategories, resolveWorkspaceIdentitySet, expandWorkspaceIdentitySetWithAliases, resolveStoredPathWorkspaceIdentity, storedPathBelongsToWorkspace, sourceNameForMemory, computeWorkspaceEpochFingerprint, managedAuthorityNoteRow, runMigrationsWithRetry, beginBootQuietPeriod, scheduleAfterBootQuiet, startBootDeadline, getUnconfirmedMigrationHolders, getSchemaFenceRejection, getMigrationOnOpenRefusal, LATEST_SUPPORTED_VERSION, BOOT_SQLITE_BUSY_TIMEOUT_MS, getPersistedSchemaVersion, setSqlitePragmaConfig, runSqliteOptimize, initializeDatabase, openDatabase, openCurrentDatabase, openDatabaseAsync, isDatabasePersisted, getDatabasePersistenceError };
|