@cortexkit/opencode-magic-context 0.48.0 → 0.49.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/dist/{announcement-fp14dw0n.js → announcement-hdbkt8v5.js} +1 -1
- package/dist/auto-search-worker.js +3 -3
- package/dist/config/{mural-model-deprecation.d.ts → deprecated-config.d.ts} +4 -1
- package/dist/config/deprecated-config.d.ts.map +1 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/project-security.d.ts +2 -2
- package/dist/config/schema/magic-context.d.ts +4 -4
- package/dist/config/schema/magic-context.d.ts.map +1 -1
- package/dist/{conflict-warning-hook-w8cykrnf.js → conflict-warning-hook-asnt6hfc.js} +7 -7
- package/dist/{context-m0cy262m.js → context-tfyhtdcs.js} +187 -68
- package/dist/{derive-budgets-sg0xsq7s.js → derive-budgets-j4ry8j8n.js} +2 -2
- package/dist/embedding-backfill-wyc3ykyf.js +21 -0
- package/dist/embedding-worker-process.js +30 -0
- package/dist/embedding-worker.js +6 -6
- package/dist/features/magic-context/dreamer/task-registry.d.ts +2 -3
- package/dist/features/magic-context/dreamer/task-registry.d.ts.map +1 -1
- package/dist/features/magic-context/git-commits/search-git-commits.d.ts +2 -0
- package/dist/features/magic-context/git-commits/search-git-commits.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-worker-client.d.ts +8 -1
- package/dist/features/magic-context/memory/embedding-worker-client.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-worker-process.d.ts +2 -0
- package/dist/features/magic-context/memory/embedding-worker-process.d.ts.map +1 -0
- package/dist/features/magic-context/memory/onnx-telemetry.d.ts +3 -0
- package/dist/features/magic-context/memory/onnx-telemetry.d.ts.map +1 -0
- package/dist/features/magic-context/message-index.d.ts.map +1 -1
- package/dist/features/magic-context/overflow-detection.d.ts +1 -2
- package/dist/features/magic-context/overflow-detection.d.ts.map +1 -1
- package/dist/features/magic-context/pi-content-decisions.d.ts +1 -1
- package/dist/features/magic-context/pi-content-decisions.d.ts.map +1 -1
- package/dist/features/magic-context/search-rerank.d.ts +35 -0
- package/dist/features/magic-context/search-rerank.d.ts.map +1 -0
- package/dist/features/magic-context/search.d.ts +11 -0
- package/dist/features/magic-context/search.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-persisted.d.ts +13 -2
- package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-session.d.ts +1 -1
- package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta.d.ts +1 -1
- package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
- package/dist/features/magic-context/storage-session-tables.d.ts +2 -0
- package/dist/features/magic-context/storage-session-tables.d.ts.map +1 -1
- package/dist/features/magic-context/storage-tag-identity-revision.d.ts +29 -9
- package/dist/features/magic-context/storage-tag-identity-revision.d.ts.map +1 -1
- package/dist/features/magic-context/storage-tags.d.ts +9 -0
- package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
- package/dist/features/magic-context/storage.d.ts +1 -1
- package/dist/features/magic-context/storage.d.ts.map +1 -1
- package/dist/features/magic-context/tagger.d.ts +2 -0
- package/dist/features/magic-context/tagger.d.ts.map +1 -1
- package/dist/features/magic-context/transform-decision-log.d.ts +4 -1
- package/dist/features/magic-context/transform-decision-log.d.ts.map +1 -1
- package/dist/features/magic-context/user-memory/storage-user-memory.d.ts +1 -0
- package/dist/features/magic-context/user-memory/storage-user-memory.d.ts.map +1 -1
- package/dist/hooks/magic-context/auto-search-deadline.d.ts.map +1 -1
- package/dist/hooks/magic-context/auto-search-replay.fixture.d.ts +24 -0
- package/dist/hooks/magic-context/auto-search-replay.fixture.d.ts.map +1 -0
- package/dist/hooks/magic-context/auto-search-runner.d.ts +1 -1
- package/dist/hooks/magic-context/auto-search-runner.d.ts.map +1 -1
- package/dist/hooks/magic-context/auto-search-worker-client.d.ts +2 -0
- package/dist/hooks/magic-context/auto-search-worker-client.d.ts.map +1 -1
- package/dist/hooks/magic-context/command-handler.d.ts +2 -0
- package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/emergency-fail-closed.d.ts +28 -0
- package/dist/hooks/magic-context/emergency-fail-closed.d.ts.map +1 -1
- package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/final-wire-token-estimate.d.ts +1 -0
- package/dist/hooks/magic-context/final-wire-token-estimate.d.ts.map +1 -1
- package/dist/hooks/magic-context/historian-host-runner.d.ts +8 -0
- package/dist/hooks/magic-context/historian-host-runner.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook.d.ts +3 -1
- package/dist/hooks/magic-context/hook.d.ts.map +1 -1
- package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
- package/dist/hooks/magic-context/latest-thinking-recovery.d.ts +7 -0
- package/dist/hooks/magic-context/latest-thinking-recovery.d.ts.map +1 -1
- package/dist/hooks/magic-context/lkg-measured-request.d.ts +3 -1
- package/dist/hooks/magic-context/lkg-measured-request.d.ts.map +1 -1
- package/dist/hooks/magic-context/lkg-persist.d.ts +8 -14
- package/dist/hooks/magic-context/lkg-persist.d.ts.map +1 -1
- package/dist/hooks/magic-context/lkg-recovery-fence.d.ts +62 -0
- package/dist/hooks/magic-context/lkg-recovery-fence.d.ts.map +1 -0
- package/dist/hooks/magic-context/lkg-recovery-prefix.d.ts +38 -0
- package/dist/hooks/magic-context/lkg-recovery-prefix.d.ts.map +1 -0
- package/dist/hooks/magic-context/lkg-replay.d.ts +3 -1
- package/dist/hooks/magic-context/lkg-replay.d.ts.map +1 -1
- package/dist/hooks/magic-context/lkg-slot.d.ts.map +1 -1
- package/dist/hooks/magic-context/opaque-thinking.d.ts +12 -0
- package/dist/hooks/magic-context/opaque-thinking.d.ts.map +1 -0
- package/dist/hooks/magic-context/pass-outcome.d.ts +3 -2
- package/dist/hooks/magic-context/pass-outcome.d.ts.map +1 -1
- package/dist/hooks/magic-context/pending-materialization.d.ts +8 -0
- package/dist/hooks/magic-context/pending-materialization.d.ts.map +1 -0
- package/dist/hooks/magic-context/read-session-chunk.d.ts +7 -1
- package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-db.d.ts +3 -0
- package/dist/hooks/magic-context/read-session-db.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-raw.d.ts +35 -0
- package/dist/hooks/magic-context/read-session-raw.d.ts.map +1 -1
- package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -1
- package/dist/hooks/magic-context/rust-mode-transform.d.ts +37 -0
- package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
- package/dist/hooks/magic-context/shared-compartment-boundaries.d.ts +8 -1
- package/dist/hooks/magic-context/shared-compartment-boundaries.d.ts.map +1 -1
- package/dist/hooks/magic-context/strict-thinking-prefix.d.ts +8 -0
- package/dist/hooks/magic-context/strict-thinking-prefix.d.ts.map +1 -0
- package/dist/hooks/magic-context/strip-content.d.ts +5 -2
- package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
- package/dist/hooks/magic-context/system-date-refresh.d.ts +29 -0
- package/dist/hooks/magic-context/system-date-refresh.d.ts.map +1 -0
- package/dist/hooks/magic-context/system-prompt-dates.d.ts +6 -0
- package/dist/hooks/magic-context/system-prompt-dates.d.ts.map +1 -0
- package/dist/hooks/magic-context/system-prompt-delivery.d.ts +47 -0
- package/dist/hooks/magic-context/system-prompt-delivery.d.ts.map +1 -0
- package/dist/hooks/magic-context/system-prompt-hash.d.ts +4 -0
- package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
- package/dist/hooks/magic-context/tag-messages.d.ts +8 -0
- package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +15 -0
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts +7 -0
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/{index-ty64eqcn.js → index-0pjvpfkf.js} +24 -4
- package/dist/{index-db7zwa3v.js → index-1bt88epp.js} +93 -9
- package/dist/{index-2y0s2hep.js → index-4mjhqadz.js} +1 -1
- package/dist/index-6m7rdv7y.js +733 -0
- package/dist/{index-k1xb52kd.js → index-6tfxmc4n.js} +11 -11
- package/dist/{index-fbv1dn35.js → index-7w18gg78.js} +26 -2
- package/dist/{index-7mx8v0pw.js → index-att4bfan.js} +4 -4
- package/dist/{index-mmyh6d8f.js → index-eypn4ba6.js} +11 -6
- package/dist/{index-q2tqeqde.js → index-fdtkt5r1.js} +1229 -78
- package/dist/{index-c43kr12f.js → index-fg00p1cg.js} +42 -40
- package/dist/{v2/server-1bc61ans.js → index-fvnzcnww.js} +44 -1
- package/dist/index-gbmqa9x9.js +1170 -0
- package/dist/{index-knh4y330.js → index-gzky5rva.js} +31 -49
- package/dist/{index-0qhce371.js → index-h2wc01qk.js} +2 -2
- package/dist/{index-dhwzyd54.js → index-h5zfkjyk.js} +160 -25
- package/dist/{index-bhvjj0ys.js → index-k6xvbj2e.js} +255 -1075
- package/dist/{index-0w56v4fm.js → index-mw7c950g.js} +1 -1
- package/dist/{index-8j7gqj40.js → index-nsjyysj0.js} +6 -2
- package/dist/{index-k9nnwad5.js → index-nydv6g8r.js} +44 -7
- package/dist/{index-f0h6xwqm.js → index-p0n5ryq8.js} +304 -960
- package/dist/{index-pg50n748.js → index-p1hnwfd8.js} +4 -4
- package/dist/{index-1qb0z7wa.js → index-qx53vezw.js} +1 -1
- package/dist/{index-5zygecde.js → index-r2zcs71c.js} +8 -6
- package/dist/{index-cb7emxgj.js → index-rafsaxac.js} +336 -6
- package/dist/{index-z8e0psj8.js → index-s7fc0fkn.js} +3 -3
- package/dist/{index-14cjxt20.js → index-sfhndpcb.js} +505 -11
- package/dist/index-sv0bb7vg.js +6972 -0
- package/dist/{index-jcbd3znx.js → index-tqf7yg27.js} +1 -1
- package/dist/{index-72nhv486.js → index-vkbqdnkj.js} +2 -2
- package/dist/{index-pdecwzrd.js → index-w23dh319.js} +18 -259
- package/dist/{index-yq8n9qcb.js → index-xgrwmj82.js} +4 -4
- package/dist/{index-0an7742d.js → index-xnanvj3w.js} +1364 -2278
- package/dist/{index-eeezr5zz.js → index-z1tjp257.js} +1 -1
- package/dist/{index-692jzy0p.js → index-zfzdpeq2.js} +2279 -4061
- package/dist/{index-q7h7csab.js → index-zg8yq4ya.js} +34 -749
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +241 -76
- package/dist/{logger-d3wex3dz.js → logger-ewak5q1s.js} +1 -1
- package/dist/{message-index-bounded-7ezbgddp.js → message-index-bounded-hqktkhdy.js} +15 -14
- package/dist/{message-time-backfill-brbd63hv.js → message-time-backfill-8x06r9kx.js} +2 -2
- package/dist/migration-worker.js +11 -10
- package/dist/plugin/hooks/create-session-hooks.d.ts +5 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
- package/dist/plugin/messages-transform.d.ts.map +1 -1
- package/dist/plugin/rpc-handlers.d.ts.map +1 -1
- package/dist/plugin/tool-registry.d.ts.map +1 -1
- package/dist/{project-embedding-registry-7qzptcw7.js → project-embedding-registry-0gv3h6y8.js} +10 -10
- package/dist/raw-ordinal-warmup-worker.js +4 -4
- package/dist/{read-session-chunk-6k83m4fj.js → read-session-chunk-5mh0bh8y.js} +12 -9
- package/dist/{read-session-raw-3g9qy1xa.js → read-session-raw-8sjbc9h0.js} +5 -3
- package/dist/{recomp-orchestrator-d3fp1cy0.js → recomp-orchestrator-kpkhkjhq.js} +28 -27
- package/dist/{safe-notification-target-5ffqq32m.js → safe-notification-target-q0cyrx3x.js} +2 -2
- package/dist/{search-epax1ecv.js → search-p9w39qv4.js} +22 -21
- package/dist/{send-session-notification-erecz4y4.js → send-session-notification-xffh3bbe.js} +5 -5
- package/dist/shared/auto-search-lifecycle.d.ts +14 -0
- package/dist/shared/auto-search-lifecycle.d.ts.map +1 -1
- package/dist/shared/logger.d.ts.map +1 -1
- package/dist/shared/models-dev-cache.d.ts.map +1 -1
- package/dist/shared/models-dev-temperature.d.ts +18 -0
- package/dist/shared/models-dev-temperature.d.ts.map +1 -0
- package/dist/shared/project-directory-key.d.ts +16 -0
- package/dist/shared/project-directory-key.d.ts.map +1 -1
- package/dist/shared/storage-file-mutex.d.ts +9 -0
- package/dist/shared/storage-file-mutex.d.ts.map +1 -0
- package/dist/shared/storage-permissions.d.ts +6 -0
- package/dist/shared/storage-permissions.d.ts.map +1 -1
- package/dist/shared/subagent-temperature.d.ts +46 -0
- package/dist/shared/subagent-temperature.d.ts.map +1 -0
- package/dist/shared/window-geometry.d.ts +9 -66
- package/dist/shared/window-geometry.d.ts.map +1 -1
- package/dist/standalone-plugin-marker.d.ts +8 -0
- package/dist/standalone-plugin-marker.d.ts.map +1 -0
- package/dist/{storage-tags-d3b0v2ss.js → storage-tags-k0pda32s.js} +6 -4
- package/dist/tools/ctx-reduce/constants.d.ts +1 -2
- package/dist/tools/ctx-reduce/constants.d.ts.map +1 -1
- package/dist/tools/ctx-reduce/tools.d.ts +1 -1
- package/dist/tools/ctx-reduce/tools.d.ts.map +1 -1
- package/dist/tools/ctx-search/tools.d.ts.map +1 -1
- package/dist/tools/ctx-search/types.d.ts +1 -0
- package/dist/tools/ctx-search/types.d.ts.map +1 -1
- package/dist/{transform-postprocess-phase-spcy2qre.js → transform-postprocess-phase-481j5a7z.js} +28 -27
- package/dist/v2/auto-search-worker.js +3 -3
- package/dist/v2/{context-sf98kstd.js → context-gsdtha5v.js} +1619 -3111
- package/dist/v2/{derive-budgets-3vc0y84t.js → derive-budgets-enckmg7y.js} +3 -3
- package/dist/v2/embedding-backfill-dbvjbcmm.js +20 -0
- package/dist/v2/embedding-worker-process.js +29 -0
- package/dist/v2/embedding-worker.js +6 -7
- package/dist/v2/hidden-child-native.d.ts.map +1 -1
- package/dist/v2/hidden-completion.d.ts +7 -0
- package/dist/v2/hidden-completion.d.ts.map +1 -1
- package/dist/v2/hooks/context.d.ts.map +1 -1
- package/dist/v2/hooks/copied-tag-notation.d.ts +19 -0
- package/dist/v2/hooks/copied-tag-notation.d.ts.map +1 -0
- package/dist/v2/hooks/dream-trigger.d.ts +4 -1
- package/dist/v2/hooks/dream-trigger.d.ts.map +1 -1
- package/dist/v2/hooks/generate.d.ts +9 -2
- package/dist/v2/hooks/generate.d.ts.map +1 -1
- package/dist/v2/hooks/payload.d.ts.map +1 -1
- package/dist/v2/hooks/system-prompt-response.d.ts +5 -0
- package/dist/v2/hooks/system-prompt-response.d.ts.map +1 -0
- package/dist/v2/{logger-mxd0ym09.js → logger-pj02s7sr.js} +1 -1
- package/dist/v2/{message-index-bounded-dy3s2k1e.js → message-index-bounded-p6v0n8qh.js} +15 -14
- package/dist/v2/{message-time-backfill-gtx1pwk1.js → message-time-backfill-cje68egj.js} +2 -2
- package/dist/v2/migration-worker.js +11 -10
- package/dist/v2/{project-embedding-registry-1tgba5dv.js → project-embedding-registry-nyq7z8y6.js} +11 -12
- package/dist/v2/raw-ordinal-warmup-worker.js +4 -4
- package/dist/v2/{read-session-chunk-rh4khdkv.js → read-session-chunk-fhyfe4qa.js} +12 -9
- package/dist/v2/{read-session-raw-bsbmf0vj.js → read-session-raw-j8dvp7f4.js} +5 -3
- package/dist/v2/recomp-orchestrator-5xy7w136.js +57 -0
- package/dist/v2/{safe-notification-target-73ax1p5v.js → safe-notification-target-y3p1hce9.js} +2 -2
- package/dist/v2/{search-nzkwrc6n.js → search-j7bakcwa.js} +23 -23
- package/dist/v2/{send-session-notification-6km439kn.js → send-session-notification-f6v698r5.js} +5 -5
- package/dist/v2/{server-7wwc2nga.js → server-1fszbabv.js} +32 -32
- package/dist/v2/{server-k228xhjg.js → server-2htmbqbv.js} +26 -2
- package/dist/v2/{server-aan4q8mx.js → server-3t0q2cqx.js} +1 -1
- package/dist/v2/{server-686jg6eb.js → server-4c7jt8gq.js} +68 -7
- package/dist/v2/{server-xxm59eww.js → server-4enr7s6x.js} +4 -4
- package/dist/v2/{server-zxhgvsf9.js → server-676xw7at.js} +1 -1
- package/dist/v2/{server-52awr2mw.js → server-6byqfbqf.js} +93 -9
- package/dist/v2/{server-e0vdaae2.js → server-6tcrjfw8.js} +4 -4
- package/dist/v2/{server-qye6ykf7.js → server-7ekna9zs.js} +2 -2
- package/dist/v2/{server-g5r0n3b6.js → server-7h861yms.js} +160 -25
- package/dist/v2/{server-k8x2rbxc.js → server-7spkq1y6.js} +3457 -5192
- package/dist/v2/{server-8pyt7hev.js → server-7vkj6zhb.js} +34 -749
- package/dist/v2/{server-yczc713m.js → server-8xmq9h3f.js} +31 -49
- package/dist/v2/{server-sggvvzhb.js → server-bgzy0phk.js} +505 -11
- package/dist/v2/server-c7n2ftng.js +6860 -0
- package/dist/v2/server-cvgn49tr.js +733 -0
- package/dist/v2/{server-zm570tr2.js → server-cw6521wn.js} +232 -7
- package/dist/v2/{server-xdfwt6kh.js → server-czb2rkpk.js} +23 -3
- package/dist/v2/{server-x2g40a71.js → server-d6g4whc2.js} +21 -15
- package/dist/{index-1bc61ans.js → v2/server-fvnzcnww.js} +44 -1
- package/dist/v2/{server-4k748ke6.js → server-hb6xhh9q.js} +2 -2
- package/dist/v2/{server-gjcr4w98.js → server-kh958yj8.js} +5 -5
- package/dist/v2/{server-1r6pmz4f.js → server-kzns2hbz.js} +1 -1
- package/dist/v2/{server-psqc7e9n.js → server-mqvtz7wz.js} +1227 -78
- package/dist/v2/{server-vqpf3167.js → server-ras2sxnc.js} +135 -15
- package/dist/v2/{server-ezhze64f.js → server-s2st631p.js} +4 -4
- package/dist/v2/{server-5m3dt5r1.js → server-t4k4qexw.js} +6 -2
- package/dist/v2/{server-z2037m6z.js → server-t528t8qm.js} +2 -2
- package/dist/v2/{server-v06jcv0w.js → server-vb7pbd9p.js} +18 -259
- package/dist/v2/{server-8tqanswc.js → server-zbxjj2mz.js} +331 -4
- package/dist/v2/server.d.ts.map +1 -1
- package/dist/v2/server.js +150 -6
- package/dist/v2/{storage-tags-w4tv77sn.js → storage-tags-5yxrvkaf.js} +6 -4
- package/dist/v2/store-reader.d.ts +8 -2
- package/dist/v2/store-reader.d.ts.map +1 -1
- package/dist/v2/{wrapup-orchestrator-mz7efs7w.js → wrapup-orchestrator-yem5422s.js} +36 -36
- package/dist/wrapup-orchestrator-xvrr27ws.js +45 -0
- package/package.json +4 -4
- package/src/config/{mural-model-deprecation.ts → deprecated-config.ts} +19 -0
- package/src/config/index.test.ts +15 -37
- package/src/config/index.ts +2 -3
- package/src/config/project-security.ts +3 -3
- package/src/config/schema/magic-context.ts +11 -7
- package/src/features/magic-context/dreamer/task-registry.ts +2 -3
- package/src/shared/auto-search-lifecycle.ts +43 -0
- package/src/shared/logger.test.ts +38 -0
- package/src/shared/logger.ts +10 -2
- package/src/shared/models-dev-cache.ts +1 -7
- package/src/shared/models-dev-temperature.ts +107 -0
- package/src/shared/project-directory-key.test.ts +59 -0
- package/src/shared/project-directory-key.ts +20 -0
- package/src/shared/sqlite-640-review.test.ts +5 -1
- package/src/shared/storage-file-mutex-review.test.ts +162 -0
- package/src/shared/storage-file-mutex.ts +130 -0
- package/src/shared/storage-permissions.ts +46 -0
- package/src/shared/subagent-temperature.test.ts +270 -0
- package/src/shared/subagent-temperature.ts +110 -0
- package/src/shared/window-geometry.test.ts +102 -236
- package/src/shared/window-geometry.ts +23 -413
- package/dist/config/mural-model-deprecation.d.ts.map +0 -1
- package/dist/embedding-backfill-9rcm1mfy.js +0 -21
- package/dist/index-bdckkfw6.js +0 -3261
- package/dist/index-mfkzp7qj.js +0 -229
- package/dist/v2/embedding-backfill-vn8g1nh1.js +0 -21
- package/dist/v2/recomp-orchestrator-az61rfcg.js +0 -57
- package/dist/v2/server-2fbhp79z.js +0 -3205
- package/dist/v2/server-mfkzp7qj.js +0 -229
- package/dist/wrapup-orchestrator-amzta1t4.js +0 -44
- package/src/shared/__fixtures__/window-overlay.v1.json +0 -337
- /package/dist/{index-2pp04pmg.js → index-69vas84q.js} +0 -0
- /package/dist/v2/{index-2pp04pmg.js → index-69vas84q.js} +0 -0
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
__setCanonicalDirectoryRealpathForTests,
|
|
14
14
|
canonicalProjectDirectory,
|
|
15
15
|
projectDirectoryKey,
|
|
16
|
+
sameProjectDirectory,
|
|
16
17
|
} from "./project-directory-key";
|
|
17
18
|
import { projectHash, rpcPortDirsForLookup } from "./rpc-utils";
|
|
18
19
|
import { createTestTempDirFromPath } from "./test-temp-dir";
|
|
@@ -109,3 +110,61 @@ describe("one discovery directory per project directory", () => {
|
|
|
109
110
|
expect(dirs[0]).toBe(join("/storage", "rpc", projectHash("C:\\Users\\A\\Proj\\")));
|
|
110
111
|
});
|
|
111
112
|
});
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Background-session code compares a session's recorded directory with the
|
|
116
|
+
* plugin's own. OpenCode 2 on Windows records `D:/coding/app` while the plugin
|
|
117
|
+
* knows `D:\coding\app`; both must count as one directory, and a different
|
|
118
|
+
* directory must never be taken for this one.
|
|
119
|
+
*/
|
|
120
|
+
describe("sameProjectDirectory", () => {
|
|
121
|
+
for (const [name, left, right] of WINDOWS_PAIRS) {
|
|
122
|
+
test(`Windows ${name} is the same directory`, () => {
|
|
123
|
+
expect(sameProjectDirectory(left, right)).toBe(true);
|
|
124
|
+
expect(sameProjectDirectory(right, left)).toBe(true);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
test("Windows forward slashes against backslashes, as OpenCode 2 stores them", () => {
|
|
129
|
+
expect(
|
|
130
|
+
sameProjectDirectory(
|
|
131
|
+
"D:/coding/siac/wt/migracion-asincrona",
|
|
132
|
+
"D:\\coding\\siac\\wt\\migracion-asincrona",
|
|
133
|
+
),
|
|
134
|
+
).toBe(true);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("Windows `.` and `..` segments and a trailing forward slash", () => {
|
|
138
|
+
expect(
|
|
139
|
+
sameProjectDirectory("C:\\Users\\dev\\.\\work\\tmp\\..\\app", "C:/Users/dev/work/app/"),
|
|
140
|
+
).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("POSIX trailing slash and `..` segments", () => {
|
|
144
|
+
expect(sameProjectDirectory("/work/app/", "/work/app")).toBe(true);
|
|
145
|
+
expect(sameProjectDirectory("/work/tmp/../app", "/work/app")).toBe(true);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("different directories are refused", () => {
|
|
149
|
+
expect(
|
|
150
|
+
sameProjectDirectory(
|
|
151
|
+
"D:/coding/siac/wt/notificaciones-pui",
|
|
152
|
+
"D:\\coding\\siac\\wt\\migracion-asincrona",
|
|
153
|
+
),
|
|
154
|
+
).toBe(false);
|
|
155
|
+
// A sibling whose name extends this one, a parent, and another drive.
|
|
156
|
+
expect(sameProjectDirectory("C:/Users/dev/app-worktree", "C:\\Users\\dev\\app")).toBe(
|
|
157
|
+
false,
|
|
158
|
+
);
|
|
159
|
+
expect(sameProjectDirectory("C:/Users/dev", "C:\\Users\\dev\\app")).toBe(false);
|
|
160
|
+
expect(sameProjectDirectory("D:/Users/dev/app", "C:\\Users\\dev\\app")).toBe(false);
|
|
161
|
+
// POSIX paths are case-sensitive.
|
|
162
|
+
expect(sameProjectDirectory("/work/App", "/work/app")).toBe(false);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("an empty directory matches nothing, not even the process's working directory", () => {
|
|
166
|
+
expect(sameProjectDirectory("", process.cwd())).toBe(false);
|
|
167
|
+
expect(sameProjectDirectory(process.cwd(), "")).toBe(false);
|
|
168
|
+
expect(sameProjectDirectory("", "")).toBe(false);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
@@ -20,6 +20,26 @@ export function projectDirectoryKey(directory: string): string {
|
|
|
20
20
|
return path.resolve(directory);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Whether two spellings name the same directory, judged by spelling alone.
|
|
25
|
+
*
|
|
26
|
+
* Hosts and the plugin do not agree on how to write a directory: OpenCode 2 on
|
|
27
|
+
* Windows stores a session's directory as `D:/work/app` while the plugin's own
|
|
28
|
+
* location is `D:\work\app`, and a drive letter can arrive in either case.
|
|
29
|
+
* Both sides go through `projectDirectoryKey`, so separators, Windows case,
|
|
30
|
+
* `\\?\` prefixes, trailing separators and `.`/`..` segments cannot make one
|
|
31
|
+
* directory look like two. POSIX paths keep their case. Symlinks are not
|
|
32
|
+
* resolved here; callers that must follow them use `canonicalProjectDirectory`.
|
|
33
|
+
*
|
|
34
|
+
* An empty spelling never matches anything: `path.resolve("")` is the process's
|
|
35
|
+
* working directory, and a session with no recorded directory must not be
|
|
36
|
+
* taken for one in the directory the process happens to run in.
|
|
37
|
+
*/
|
|
38
|
+
export function sameProjectDirectory(a: string, b: string): boolean {
|
|
39
|
+
if (a.length === 0 || b.length === 0) return false;
|
|
40
|
+
return projectDirectoryKey(a) === projectDirectoryKey(b);
|
|
41
|
+
}
|
|
42
|
+
|
|
23
43
|
let realpathForCanonicalDirectory: (directory: string) => string = (directory) =>
|
|
24
44
|
realpathSync.native(directory);
|
|
25
45
|
|
|
@@ -2,9 +2,10 @@ import { expect, spyOn, test } from "bun:test";
|
|
|
2
2
|
import { openDatabase } from "../features/magic-context/storage-db";
|
|
3
3
|
import { getOrCreateSessionMeta, updateSessionMeta } from "../features/magic-context/storage-meta";
|
|
4
4
|
import { recordToolDefinition } from "../features/magic-context/tool-definition-tokens";
|
|
5
|
+
import { saveLkgSlotToDb } from "../hooks/magic-context/lkg-persist";
|
|
5
6
|
import { captureLkgSlot } from "../hooks/magic-context/lkg-replay";
|
|
6
7
|
import { lkgReplayFits } from "../hooks/magic-context/lkg-replay-fit";
|
|
7
|
-
import { resetLkgSlotsForTest } from "../hooks/magic-context/lkg-slot";
|
|
8
|
+
import { getSlot, resetLkgSlotsForTest } from "../hooks/magic-context/lkg-slot";
|
|
8
9
|
import type { MessageLike } from "../hooks/magic-context/transform-operations";
|
|
9
10
|
import { createMessagesTransformHandler } from "../plugin/messages-transform";
|
|
10
11
|
import { refreshModelLimitsFromApi } from "./models-dev-cache";
|
|
@@ -141,6 +142,9 @@ test("review: OpenCode tries a valid saved request before backed-off writer retr
|
|
|
141
142
|
providerKey: "openai",
|
|
142
143
|
}),
|
|
143
144
|
).toBe(true);
|
|
145
|
+
expect(saveLkgSlotToDb(db, sessionId, getSlot(sessionId)!, undefined, { rebuild: true })).toBe(
|
|
146
|
+
true,
|
|
147
|
+
);
|
|
144
148
|
expect(
|
|
145
149
|
lkgReplayFits({
|
|
146
150
|
db,
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { __resetRpcIdentityTestHooks, __setRpcIdentityTestHooks } from "./rpc-utils";
|
|
6
|
+
import { withStorageFileMutex } from "./storage-file-mutex";
|
|
7
|
+
import { createTestTempDir } from "./test-temp-dir";
|
|
8
|
+
|
|
9
|
+
const mutex = join(import.meta.dir, "storage-file-mutex.ts");
|
|
10
|
+
|
|
11
|
+
async function holder(root: string) {
|
|
12
|
+
const script = `
|
|
13
|
+
import { existsSync } from 'node:fs';
|
|
14
|
+
import { withStorageFileMutex } from ${JSON.stringify(mutex)};
|
|
15
|
+
withStorageFileMutex(${JSON.stringify(join(root, "locks"))}, () => {
|
|
16
|
+
console.log('HELD');
|
|
17
|
+
const pause = new Int32Array(new SharedArrayBuffer(4));
|
|
18
|
+
while (!existsSync(${JSON.stringify(join(root, "release"))})) Atomics.wait(pause, 0, 0, 10);
|
|
19
|
+
});`;
|
|
20
|
+
const child = Bun.spawn([process.execPath, "--eval", script], {
|
|
21
|
+
env: {
|
|
22
|
+
...process.env,
|
|
23
|
+
XDG_DATA_HOME: root,
|
|
24
|
+
XDG_CONFIG_HOME: root,
|
|
25
|
+
MAGIC_CONTEXT_STORAGE_DIR: root,
|
|
26
|
+
MAGIC_CONTEXT_TEST_DATA_DIR: root,
|
|
27
|
+
},
|
|
28
|
+
stdout: "pipe",
|
|
29
|
+
stderr: "pipe",
|
|
30
|
+
windowsHide: true,
|
|
31
|
+
});
|
|
32
|
+
const reader = child.stdout.getReader();
|
|
33
|
+
const first = await reader.read();
|
|
34
|
+
expect(new TextDecoder().decode(first.value)).toContain("HELD");
|
|
35
|
+
reader.releaseLock();
|
|
36
|
+
if (process.platform === "darwin") {
|
|
37
|
+
const files = spawnSync("/usr/sbin/lsof", ["-p", String(child.pid), "-Fn"], {
|
|
38
|
+
encoding: "utf8",
|
|
39
|
+
windowsHide: true,
|
|
40
|
+
});
|
|
41
|
+
expect(files.status).toBe(0);
|
|
42
|
+
const names = files.stdout.split("\n").filter((line) => line.startsWith("n"));
|
|
43
|
+
expect(
|
|
44
|
+
names.some((line) =>
|
|
45
|
+
/\/(?:\.local\/share\/opencode|\.local\/share\/cortexkit\/magic-context|\.config\/(?:opencode|cortexkit))\//.test(
|
|
46
|
+
line,
|
|
47
|
+
),
|
|
48
|
+
),
|
|
49
|
+
).toBe(false);
|
|
50
|
+
console.log(`REVIEW_LSOF pid=${child.pid} paths=${names.length} forbidden=0`);
|
|
51
|
+
}
|
|
52
|
+
return child;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
test.skipIf(process.platform === "win32")(
|
|
56
|
+
"independent mutex: paused live holder is not taken over, dead holder is reclaimed",
|
|
57
|
+
async () => {
|
|
58
|
+
const { dir: root, cleanup } = createTestTempDir("mc-mutex-review-");
|
|
59
|
+
const child = await holder(root);
|
|
60
|
+
try {
|
|
61
|
+
process.kill(child.pid, "SIGSTOP");
|
|
62
|
+
const start = performance.now();
|
|
63
|
+
expect(() =>
|
|
64
|
+
withStorageFileMutex(
|
|
65
|
+
join(root, "locks"),
|
|
66
|
+
() => {
|
|
67
|
+
throw new Error("LIVE HOLDER WAS TAKEN OVER");
|
|
68
|
+
},
|
|
69
|
+
200,
|
|
70
|
+
),
|
|
71
|
+
).toThrow("Storage safety publication lock is unavailable");
|
|
72
|
+
expect(performance.now() - start).toBeLessThan(2500);
|
|
73
|
+
process.kill(child.pid, "SIGKILL");
|
|
74
|
+
await child.exited;
|
|
75
|
+
let reached = false;
|
|
76
|
+
withStorageFileMutex(
|
|
77
|
+
join(root, "locks"),
|
|
78
|
+
() => {
|
|
79
|
+
reached = true;
|
|
80
|
+
},
|
|
81
|
+
200,
|
|
82
|
+
);
|
|
83
|
+
expect(reached).toBe(true);
|
|
84
|
+
expect(readdirSync(join(root, "locks"))).toEqual([]);
|
|
85
|
+
} finally {
|
|
86
|
+
if (child.exitCode === null) {
|
|
87
|
+
child.kill("SIGKILL");
|
|
88
|
+
await child.exited;
|
|
89
|
+
}
|
|
90
|
+
cleanup();
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
15000,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
test.skipIf(process.platform === "win32")(
|
|
97
|
+
"independent mutex: corrupt live-owner identity refuses within a bound",
|
|
98
|
+
async () => {
|
|
99
|
+
const { dir: root, cleanup } = createTestTempDir("mc-mutex-review-");
|
|
100
|
+
const child = await holder(root);
|
|
101
|
+
try {
|
|
102
|
+
const locks = join(root, "locks");
|
|
103
|
+
const ticket = join(locks, readdirSync(locks)[0]!);
|
|
104
|
+
expect(JSON.parse(readFileSync(ticket, "utf8")).birth).toBeString();
|
|
105
|
+
writeFileSync(ticket, "inconclusive owner identity", { mode: 0o600 });
|
|
106
|
+
const start = performance.now();
|
|
107
|
+
let entered = false;
|
|
108
|
+
expect(() =>
|
|
109
|
+
withStorageFileMutex(
|
|
110
|
+
locks,
|
|
111
|
+
() => {
|
|
112
|
+
entered = true;
|
|
113
|
+
},
|
|
114
|
+
200,
|
|
115
|
+
),
|
|
116
|
+
).toThrow("Storage safety publication lock is unavailable");
|
|
117
|
+
expect(entered).toBe(false);
|
|
118
|
+
expect(performance.now() - start).toBeLessThan(2500);
|
|
119
|
+
expect(readdirSync(locks)).toHaveLength(1);
|
|
120
|
+
} finally {
|
|
121
|
+
child.kill("SIGKILL");
|
|
122
|
+
await child.exited;
|
|
123
|
+
cleanup();
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
15000,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
test.skipIf(process.platform === "win32")(
|
|
130
|
+
"independent mutex: denied PID probe is inconclusive and refuses within a bound",
|
|
131
|
+
async () => {
|
|
132
|
+
const { dir: root, cleanup } = createTestTempDir("mc-mutex-inconclusive-review-");
|
|
133
|
+
const child = await holder(root);
|
|
134
|
+
try {
|
|
135
|
+
__setRpcIdentityTestHooks({
|
|
136
|
+
processKill: () => {
|
|
137
|
+
throw Object.assign(new Error("Denied PID probe"), { code: "EPERM" });
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
const start = performance.now();
|
|
141
|
+
let entered = false;
|
|
142
|
+
expect(() =>
|
|
143
|
+
withStorageFileMutex(
|
|
144
|
+
join(root, "locks"),
|
|
145
|
+
() => {
|
|
146
|
+
entered = true;
|
|
147
|
+
},
|
|
148
|
+
200,
|
|
149
|
+
),
|
|
150
|
+
).toThrow("Storage safety publication lock is unavailable");
|
|
151
|
+
expect(entered).toBe(false);
|
|
152
|
+
expect(performance.now() - start).toBeLessThan(2500);
|
|
153
|
+
expect(readdirSync(join(root, "locks"))).toHaveLength(1);
|
|
154
|
+
} finally {
|
|
155
|
+
__resetRpcIdentityTestHooks();
|
|
156
|
+
child.kill("SIGKILL");
|
|
157
|
+
await child.exited;
|
|
158
|
+
cleanup();
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
15000,
|
|
162
|
+
);
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import { readdirSync, readFileSync, rmSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { isPidAlive, readProcessStartTime } from "./rpc-utils";
|
|
6
|
+
import {
|
|
7
|
+
ensurePrivateStorageDirectorySync,
|
|
8
|
+
writeStorageFileAtomicSync,
|
|
9
|
+
writeStorageFileSync,
|
|
10
|
+
} from "./storage-permissions";
|
|
11
|
+
|
|
12
|
+
interface Ticket {
|
|
13
|
+
choosing: boolean;
|
|
14
|
+
number: number;
|
|
15
|
+
birth: string | null;
|
|
16
|
+
checksum: string;
|
|
17
|
+
}
|
|
18
|
+
const ticketChecksum = (name: string, ticket: Ticket) =>
|
|
19
|
+
createHash("sha256")
|
|
20
|
+
.update(JSON.stringify([name, ticket.choosing, ticket.number, ticket.birth]))
|
|
21
|
+
.digest("hex");
|
|
22
|
+
function processBirth(pid: number): string | null {
|
|
23
|
+
if (process.platform === "linux") {
|
|
24
|
+
try {
|
|
25
|
+
const stat = readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
26
|
+
const ticks = stat
|
|
27
|
+
.slice(stat.lastIndexOf(")") + 2)
|
|
28
|
+
.trim()
|
|
29
|
+
.split(/\s+/)[19];
|
|
30
|
+
if (!ticks || !/^\d+$/.test(ticks)) return null;
|
|
31
|
+
return `${readFileSync("/proc/sys/kernel/random/boot_id", "utf8").trim()}:${ticks}`;
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (process.platform === "darwin") {
|
|
37
|
+
try {
|
|
38
|
+
return (
|
|
39
|
+
execFileSync("ps", ["-p", String(pid), "-o", "lstart="], {
|
|
40
|
+
encoding: "utf8",
|
|
41
|
+
timeout: 1000,
|
|
42
|
+
windowsHide: true,
|
|
43
|
+
env: { ...process.env, TZ: "UTC", LC_ALL: "C" },
|
|
44
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
45
|
+
}).trim() || null
|
|
46
|
+
);
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const start = readProcessStartTime(pid);
|
|
52
|
+
return start === null ? null : String(start);
|
|
53
|
+
}
|
|
54
|
+
const pause = new Int32Array(new SharedArrayBuffer(4));
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Lamport's bakery mutex using unique contender files on a local filesystem.
|
|
58
|
+
* Exclusive creation and atomic replacement work on POSIX and Windows. A dead
|
|
59
|
+
* owner is removed by PID/start-time evidence, never by age: a paused live
|
|
60
|
+
* publisher must not lose its lock. Uncertain liveness refuses after the budget.
|
|
61
|
+
* Unique names mean reclaiming a dead ticket cannot unlink a successor's lock.
|
|
62
|
+
*/
|
|
63
|
+
export function withStorageFileMutex<T>(directory: string, work: () => T, timeoutMs = 1000): T {
|
|
64
|
+
ensurePrivateStorageDirectorySync(directory);
|
|
65
|
+
const name = `${process.pid}-${randomUUID()}.lock`;
|
|
66
|
+
const ownPath = join(directory, name);
|
|
67
|
+
const birth = processBirth(process.pid);
|
|
68
|
+
const ticket: Ticket = { choosing: true, number: 0, birth, checksum: "" };
|
|
69
|
+
const encodedTicket = () =>
|
|
70
|
+
JSON.stringify({ ...ticket, checksum: ticketChecksum(name, ticket) });
|
|
71
|
+
writeStorageFileSync(ownPath, encodedTicket(), { flag: "wx", forcePrivate: true });
|
|
72
|
+
const deadline = performance.now() + timeoutMs;
|
|
73
|
+
const peers = (): [string, Ticket | null][] => {
|
|
74
|
+
const found: [string, Ticket | null][] = [];
|
|
75
|
+
for (const other of readdirSync(directory)) {
|
|
76
|
+
if (other === name || !/^\d+-[\da-f-]+\.lock$/.test(other)) continue;
|
|
77
|
+
const pid = Number(other.split("-")[0]);
|
|
78
|
+
const otherPath = join(directory, other);
|
|
79
|
+
let value: Ticket | null = null;
|
|
80
|
+
try {
|
|
81
|
+
const parsed = JSON.parse(readFileSync(otherPath, "utf8")) as Ticket;
|
|
82
|
+
if (
|
|
83
|
+
typeof parsed.choosing === "boolean" &&
|
|
84
|
+
Number.isSafeInteger(parsed.number) &&
|
|
85
|
+
parsed.number >= 0 &&
|
|
86
|
+
(parsed.birth === null || typeof parsed.birth === "string") &&
|
|
87
|
+
parsed.checksum === ticketChecksum(other, parsed)
|
|
88
|
+
)
|
|
89
|
+
value = parsed;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") continue;
|
|
92
|
+
}
|
|
93
|
+
const alive = isPidAlive(pid);
|
|
94
|
+
const actualBirth = alive === "alive" && value?.birth ? processBirth(pid) : null;
|
|
95
|
+
if (
|
|
96
|
+
alive === "dead" ||
|
|
97
|
+
(actualBirth !== null && value?.birth && actualBirth !== value.birth)
|
|
98
|
+
) {
|
|
99
|
+
rmSync(otherPath, { force: true });
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
found.push([other, value]);
|
|
103
|
+
}
|
|
104
|
+
return found;
|
|
105
|
+
};
|
|
106
|
+
try {
|
|
107
|
+
ticket.number = 1 + Math.max(0, ...peers().map(([, value]) => value?.number ?? 0));
|
|
108
|
+
ticket.choosing = false;
|
|
109
|
+
writeStorageFileAtomicSync(ownPath, encodedTicket(), true);
|
|
110
|
+
while (
|
|
111
|
+
peers().some(
|
|
112
|
+
([other, value]) =>
|
|
113
|
+
!value ||
|
|
114
|
+
value.choosing ||
|
|
115
|
+
value.number < ticket.number ||
|
|
116
|
+
(value.number === ticket.number && other < name),
|
|
117
|
+
)
|
|
118
|
+
) {
|
|
119
|
+
if (performance.now() >= deadline)
|
|
120
|
+
throw new Error("Storage safety publication lock is unavailable");
|
|
121
|
+
Atomics.wait(pause, 0, 0, 10);
|
|
122
|
+
}
|
|
123
|
+
const result = work();
|
|
124
|
+
if (result && typeof (result as { then?: unknown }).then === "function")
|
|
125
|
+
throw new TypeError("Storage publication mutex requires synchronous work");
|
|
126
|
+
return result;
|
|
127
|
+
} finally {
|
|
128
|
+
rmSync(ownPath, { force: true });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
chmodSync,
|
|
11
11
|
closeSync,
|
|
12
12
|
createWriteStream,
|
|
13
|
+
fsyncSync,
|
|
13
14
|
lstatSync,
|
|
14
15
|
mkdirSync,
|
|
15
16
|
openSync,
|
|
@@ -54,6 +55,15 @@ export function ensureStorageDirectorySync(
|
|
|
54
55
|
});
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
/** Replay-authorizing files require a private real directory, even after a permissive restore. */
|
|
59
|
+
export function ensurePrivateStorageDirectorySync(directory: string): void {
|
|
60
|
+
ensureStorageDirectorySync(directory, true);
|
|
61
|
+
const stat = lstatSync(directory);
|
|
62
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
63
|
+
throw new Error("Private storage directory is not a real directory");
|
|
64
|
+
if (process.platform !== "win32") chmodSync(directory, 0o700);
|
|
65
|
+
}
|
|
66
|
+
|
|
57
67
|
/** Create or replace a storage file using a private mode from the first byte. */
|
|
58
68
|
export function writeStorageFileSync(
|
|
59
69
|
filePath: string,
|
|
@@ -85,6 +95,42 @@ export function writeStorageFileAtomicSync(
|
|
|
85
95
|
}
|
|
86
96
|
}
|
|
87
97
|
|
|
98
|
+
/** Flush the namespace containing a safety-critical sidecar (POSIX only). */
|
|
99
|
+
export function syncStorageDirectorySync(directory: string): void {
|
|
100
|
+
const fd = openSync(directory, "r");
|
|
101
|
+
try {
|
|
102
|
+
fsyncSync(fd);
|
|
103
|
+
} finally {
|
|
104
|
+
closeSync(fd);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Publish a safety record only after both its bytes and directory entry are durable. */
|
|
109
|
+
export function writeStorageFileDurableAtomicSync(
|
|
110
|
+
filePath: string,
|
|
111
|
+
content: string,
|
|
112
|
+
afterTempSync?: () => void,
|
|
113
|
+
): void {
|
|
114
|
+
const directory = dirname(filePath);
|
|
115
|
+
ensurePrivateStorageDirectorySync(directory);
|
|
116
|
+
const tempPath = `${filePath}.${process.pid}.${randomBytes(12).toString("hex")}.tmp`;
|
|
117
|
+
let fd: number | undefined;
|
|
118
|
+
try {
|
|
119
|
+
fd = openStorageFileSync(tempPath, "wx", true);
|
|
120
|
+
writeFileSync(fd, content, "utf8");
|
|
121
|
+
fsyncSync(fd);
|
|
122
|
+
closeSync(fd);
|
|
123
|
+
fd = undefined;
|
|
124
|
+
afterTempSync?.();
|
|
125
|
+
renameSync(tempPath, filePath);
|
|
126
|
+
syncStorageDirectorySync(directory);
|
|
127
|
+
syncStorageDirectorySync(dirname(directory));
|
|
128
|
+
} finally {
|
|
129
|
+
if (fd !== undefined) closeSync(fd);
|
|
130
|
+
rmSync(tempPath, { force: true });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
88
134
|
export function copyStorageFileSync(
|
|
89
135
|
sourcePath: string,
|
|
90
136
|
destinationPath: string,
|