@remnic/core 10.0.49 → 10.0.50
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/access-admin-ops-surface.js +41 -40
- package/dist/access-authorization-probe.js +42 -41
- package/dist/access-boundary.js +41 -40
- package/dist/access-cli.js +107 -106
- package/dist/access-cli.js.map +1 -1
- package/dist/access-extraction-force-flush.js +41 -40
- package/dist/access-http-briefing.d.ts +2 -0
- package/dist/access-http-briefing.d.ts.map +1 -1
- package/dist/access-http-briefing.js +42 -41
- package/dist/access-http-query.js +43 -42
- package/dist/access-http.d.ts.map +1 -1
- package/dist/access-http.js +59 -58
- package/dist/access-identity-continuity-surface.js +41 -40
- package/dist/access-lcm-surface.js +41 -40
- package/dist/access-mcp.js +51 -50
- package/dist/access-namespace-preflight.js +41 -40
- package/dist/access-observe-write-category.d.ts +1 -1
- package/dist/access-observe-write-category.d.ts.map +1 -1
- package/dist/access-observe-write-category.js +10 -10
- package/dist/access-observe-write-surface.d.ts.map +1 -1
- package/dist/access-observe-write-surface.js +41 -40
- package/dist/access-offline-manifest.js +4 -4
- package/dist/access-operations-batch.js +42 -41
- package/dist/access-operations.js +49 -48
- package/dist/access-recall-concurrency.js +41 -40
- package/dist/access-recall-response.js +41 -40
- package/dist/access-recall-surface.js +41 -40
- package/dist/access-schema.js +6 -6
- package/dist/access-service.d.ts +5 -0
- package/dist/access-service.d.ts.map +1 -1
- package/dist/access-service.js +41 -40
- package/dist/access-surface-catalog.js +43 -42
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/active-recall.js +14 -14
- package/dist/{auto-sync-BNCBMU6H.js → auto-sync-UQQ6YJSB.js} +3 -3
- package/dist/briefing-deadline.d.ts +7 -0
- package/dist/briefing-deadline.d.ts.map +1 -0
- package/dist/briefing-deadline.js +13 -0
- package/dist/briefing-window.d.ts +12 -4
- package/dist/briefing-window.d.ts.map +1 -1
- package/dist/briefing-window.js +3 -1
- package/dist/briefing.d.ts +2 -0
- package/dist/briefing.d.ts.map +1 -1
- package/dist/briefing.js +12 -11
- package/dist/buffer.js +3 -3
- package/dist/calibration.js +2 -2
- package/dist/{capsule-merge-RNQUP2VL.js → capsule-merge-D4BTIJGK.js} +3 -3
- package/dist/causal-consolidation.js +11 -11
- package/dist/{chunk-ESDOQEJM.js → chunk-2AKIVSHP.js} +2 -2
- package/dist/{chunk-MCS4LIXP.js → chunk-43TUWQNR.js} +2 -2
- package/dist/{chunk-2NLCDNGA.js → chunk-564YP3DW.js} +2 -2
- package/dist/{chunk-PBWJVRKE.js → chunk-5FUDSK6Z.js} +2 -2
- package/dist/{chunk-V4GPFCJ7.js → chunk-5G53JSRO.js} +2 -2
- package/dist/{chunk-WJCAAVZX.js → chunk-5X2H3X4J.js} +2 -2
- package/dist/{chunk-CPBYKOTC.js → chunk-6ZQX3R66.js} +2 -2
- package/dist/{chunk-AF4UIHON.js → chunk-74AY3S7U.js} +9 -9
- package/dist/{chunk-XSJL62LE.js → chunk-7J6A5MV2.js} +2 -2
- package/dist/{chunk-A3WXV7YU.js → chunk-7SAE6T36.js} +2 -2
- package/dist/{chunk-BWF3KNSQ.js → chunk-7YVER7ZO.js} +2 -2
- package/dist/{chunk-5IDQVE4U.js → chunk-A3UFV4K3.js} +6 -6
- package/dist/{chunk-LWRIGKFM.js → chunk-A5L5KGDI.js} +19 -1
- package/dist/chunk-A5L5KGDI.js.map +1 -0
- package/dist/{chunk-6TIVVOVU.js → chunk-BNJAVRNJ.js} +9 -9
- package/dist/{chunk-CMOYD5CA.js → chunk-CCL2OXFO.js} +2 -2
- package/dist/{chunk-7XFQSQYS.js → chunk-CRN3EPE2.js} +4 -3
- package/dist/chunk-CRN3EPE2.js.map +1 -0
- package/dist/{chunk-3NYIXVCT.js → chunk-DMKTOCC7.js} +2 -2
- package/dist/chunk-DMKTOCC7.js.map +1 -0
- package/dist/{chunk-FRXS6SC7.js → chunk-EKUI7I2X.js} +20 -4
- package/dist/chunk-EKUI7I2X.js.map +1 -0
- package/dist/{chunk-N6EM7ESC.js → chunk-EKYWZYZJ.js} +22 -14
- package/dist/chunk-EKYWZYZJ.js.map +1 -0
- package/dist/{chunk-FOKTW6GY.js → chunk-EWBXMGXR.js} +2 -2
- package/dist/{chunk-YNDLCWXS.js → chunk-EZ25VE3G.js} +4 -4
- package/dist/{chunk-6W3QH3QW.js → chunk-GQIYL7OD.js} +388 -292
- package/dist/chunk-GQIYL7OD.js.map +1 -0
- package/dist/{chunk-E47UQBYW.js → chunk-GXVXBYRA.js} +3 -3
- package/dist/{chunk-RZDDNH63.js → chunk-HKOMHP6D.js} +27 -15
- package/dist/chunk-HKOMHP6D.js.map +1 -0
- package/dist/{chunk-AGCEJDG2.js → chunk-HPNXS6W6.js} +2 -2
- package/dist/{chunk-O4CA53UY.js → chunk-IMKOJVD4.js} +69 -63
- package/dist/chunk-IMKOJVD4.js.map +1 -0
- package/dist/{chunk-N5PISU26.js → chunk-IN6U2BS5.js} +2 -2
- package/dist/{chunk-HI53EK3Y.js → chunk-IUYXCTA7.js} +2 -2
- package/dist/{chunk-VGRBKEBH.js → chunk-JBWD3J5U.js} +3 -3
- package/dist/{chunk-YB3WN6XA.js → chunk-K5QQXVG6.js} +12 -12
- package/dist/{chunk-7LCGFUMB.js → chunk-KQAFEZQX.js} +4 -4
- package/dist/{chunk-HDT3DMUF.js → chunk-KW5YZELC.js} +2 -2
- package/dist/{chunk-DM6SOROF.js → chunk-LIBVANOF.js} +2 -2
- package/dist/{chunk-VKUMKVEU.js → chunk-LMMBV7QC.js} +4 -4
- package/dist/{chunk-NMOEOQV4.js → chunk-M2DOJBH7.js} +2 -2
- package/dist/{chunk-3INLTKTW.js → chunk-M4HRYLOA.js} +2 -2
- package/dist/{chunk-YY6PWFUC.js → chunk-M6TP6UYD.js} +3 -3
- package/dist/{chunk-BJTFLP3J.js → chunk-MLNXATFG.js} +2 -2
- package/dist/{chunk-C7WDWD35.js → chunk-MWURUSRE.js} +5 -5
- package/dist/{chunk-WIBWONL4.js → chunk-N74WJ7B2.js} +2 -2
- package/dist/{chunk-LCHFINL4.js → chunk-N7FNCE7F.js} +2 -2
- package/dist/{chunk-QZK6HG4J.js → chunk-NPLM7AFU.js} +2 -2
- package/dist/{chunk-CRJX4OMN.js → chunk-NSP5PIQH.js} +2 -2
- package/dist/{chunk-X52SXKVC.js → chunk-P7OVFGMU.js} +2 -2
- package/dist/{chunk-2J6BIOVN.js → chunk-PJCVI44H.js} +2 -2
- package/dist/{chunk-Q6PYCPHO.js → chunk-Q3PNQYAC.js} +2 -2
- package/dist/{chunk-D76AHITY.js → chunk-QS765SBL.js} +2 -2
- package/dist/{chunk-KNCR7APE.js → chunk-QTDZ2NYC.js} +7 -7
- package/dist/{chunk-YFXRLRKP.js → chunk-S4OZKPBS.js} +7 -11
- package/dist/{chunk-YFXRLRKP.js.map → chunk-S4OZKPBS.js.map} +1 -1
- package/dist/{chunk-HCPVL6SF.js → chunk-SJTSWG2Z.js} +3 -3
- package/dist/{chunk-SA6P2KS3.js → chunk-TEH5MDX5.js} +33 -17
- package/dist/chunk-TEH5MDX5.js.map +1 -0
- package/dist/{chunk-XFND63OB.js → chunk-TFWJXWL6.js} +2 -2
- package/dist/{chunk-SVJ5JVZO.js → chunk-TXKQBQYW.js} +3 -3
- package/dist/{chunk-WQFHHJWK.js → chunk-UALXWWGN.js} +2 -2
- package/dist/{chunk-3PVKV3XL.js → chunk-UNOAVHV4.js} +2 -2
- package/dist/{chunk-FYZXZUGU.js → chunk-UQBOPCDK.js} +2 -2
- package/dist/{chunk-SUKPM6DV.js → chunk-UWMZT2TY.js} +47 -4
- package/dist/chunk-UWMZT2TY.js.map +1 -0
- package/dist/{chunk-OD7ZXCUF.js → chunk-UXGXJUZ5.js} +7 -7
- package/dist/{chunk-NH37SP6V.js → chunk-UZVQFGAY.js} +81 -78
- package/dist/chunk-UZVQFGAY.js.map +1 -0
- package/dist/{chunk-2BY56HE6.js → chunk-VCOMACR6.js} +35 -35
- package/dist/{chunk-5FNZ3G6X.js → chunk-VKQI47DJ.js} +4 -4
- package/dist/{chunk-MPJDQC4G.js → chunk-WJCXUE2J.js} +2 -2
- package/dist/{chunk-C3HRYVGD.js → chunk-WV4UYDP6.js} +2 -2
- package/dist/{chunk-E3DL3CAL.js → chunk-WXJC2L66.js} +2 -2
- package/dist/chunk-X2KO4FK5.js +40 -0
- package/dist/chunk-X2KO4FK5.js.map +1 -0
- package/dist/{chunk-O7GNSHFF.js → chunk-XC3H7KON.js} +2 -2
- package/dist/{chunk-OOOX5C4X.js → chunk-XL2EE5W3.js} +4 -4
- package/dist/{chunk-NUK5XU3P.js → chunk-XTXWR6KQ.js} +25 -10
- package/dist/chunk-XTXWR6KQ.js.map +1 -0
- package/dist/{chunk-CZVNOTLR.js → chunk-XXR4A33N.js} +2 -2
- package/dist/{chunk-2AV2U2SZ.js → chunk-YDIOMDBW.js} +2 -2
- package/dist/{chunk-QFTNWDJ6.js → chunk-YECEEJUU.js} +2 -2
- package/dist/{chunk-BMG3SAXA.js → chunk-Z5277JWF.js} +3 -3
- package/dist/{chunk-SW3WHVKN.js → chunk-ZFNXJEUG.js} +2 -2
- package/dist/{chunk-VY6BD5KB.js → chunk-ZMO2S5RT.js} +12 -10
- package/dist/chunk-ZMO2S5RT.js.map +1 -0
- package/dist/{chunk-KMTMMBEB.js → chunk-ZNRTHYYW.js} +6 -6
- package/dist/{chunk-VCBS5GRQ.js → chunk-ZSMA67FV.js} +2 -2
- package/dist/cli.js +80 -79
- package/dist/coding/export-okf-codegraph.js +9 -9
- package/dist/compounding/engine.js +11 -11
- package/dist/config.js +14 -14
- package/dist/connectors/codex-materialize-runner.js +10 -10
- package/dist/connectors/index.js +10 -10
- package/dist/consolidation-provenance-check.js +4 -4
- package/dist/consolidation-undo.js +2 -2
- package/dist/convergence-refresh.js +12 -12
- package/dist/corpus-watermark.js +12 -12
- package/dist/correction/passive-correction-notifications.d.ts +1 -1
- package/dist/correction/passive-correction-notifications.d.ts.map +1 -1
- package/dist/dashboard-runtime.js +2 -2
- package/dist/deep-recall-http-glue.js +7 -7
- package/dist/{entity-canonical-id-migration-2YOLMOFC.js → entity-canonical-id-migration-ZBNRGUNV.js} +3 -3
- package/dist/entity-canonical-id-migration-ZBNRGUNV.js.map +1 -0
- package/dist/entity-retrieval.js +11 -11
- package/dist/explicit-capture.js +2 -2
- package/dist/external-wiki-access.js +42 -41
- package/dist/extraction.js +16 -16
- package/dist/{first-start-migration-QVD22WWS.js → first-start-migration-A6PHIBUQ.js} +4 -4
- package/dist/harmonic-retrieval.js +10 -10
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +130 -122
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.js +5 -5
- package/dist/local-llm.js +2 -2
- package/dist/location/index.js +15 -15
- package/dist/location/tagging.d.ts +7 -5
- package/dist/location/tagging.d.ts.map +1 -1
- package/dist/maintenance/memory-governance.js +10 -10
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +10 -10
- package/dist/maintenance/rebuild-memory-projection-runner.js +12 -12
- package/dist/maintenance/rebuild-memory-projection.js +11 -11
- package/dist/memory-browse-operations.js +42 -41
- package/dist/namespaces/migrate.js +11 -11
- package/dist/namespaces/storage.js +10 -10
- package/dist/offline-sync-file-io.d.ts +15 -0
- package/dist/offline-sync-file-io.d.ts.map +1 -1
- package/dist/offline-sync-file-io.js +9 -1
- package/dist/offline-sync-snapshot-stream.js +4 -4
- package/dist/offline-sync-upload-staging.js +2 -2
- package/dist/offline-sync.d.ts.map +1 -1
- package/dist/offline-sync.js +3 -3
- package/dist/operator-doctor-corpus.js +13 -13
- package/dist/operator-toolkit.js +24 -24
- package/dist/orchestration/maintenance.js +23 -23
- package/dist/orchestrator.d.ts +1 -0
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +109 -108
- package/dist/page-versioning.js +2 -2
- package/dist/recall-navigation-operations.js +42 -41
- package/dist/recall-navigation.js +41 -40
- package/dist/resume-bundles.js +15 -15
- package/dist/semantic-consolidation.js +15 -15
- package/dist/semantic-rule-promotion.js +10 -10
- package/dist/semantic-rule-verifier.js +10 -10
- package/dist/source-agent-qualifier.js +13 -13
- package/dist/standup/build.js +10 -10
- package/dist/storage/entity-canonical-id-migration-adapter.d.ts +1 -0
- package/dist/storage/entity-canonical-id-migration-adapter.d.ts.map +1 -1
- package/dist/storage/entity-canonical-id-migration.d.ts +7 -0
- package/dist/storage/entity-canonical-id-migration.d.ts.map +1 -1
- package/dist/storage/entity-canonical-id-references.d.ts +2 -0
- package/dist/storage/entity-canonical-id-references.d.ts.map +1 -1
- package/dist/storage/entity-migration-completion-cache.d.ts +46 -0
- package/dist/storage/entity-migration-completion-cache.d.ts.map +1 -0
- package/dist/storage/entity-store.d.ts +1 -8
- package/dist/storage/entity-store.d.ts.map +1 -1
- package/dist/storage/memory-read-store.d.ts +3 -2
- package/dist/storage/memory-read-store.d.ts.map +1 -1
- package/dist/storage/windowed-memory-read.d.ts +6 -0
- package/dist/storage/windowed-memory-read.d.ts.map +1 -1
- package/dist/storage.d.ts +2 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +9 -9
- package/dist/summarizer.js +3 -3
- package/dist/support-passport/index.js +44 -43
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/capsule-import.js +4 -4
- package/dist/transfer/export-okf.js +10 -10
- package/dist/verified-recall.js +10 -10
- package/dist/who-knows-cli.js +11 -11
- package/dist/who-knows.js +10 -10
- package/package.json +2 -2
- package/src/access-http-briefing.ts +4 -0
- package/src/access-http.test.ts +40 -11
- package/src/access-http.ts +6 -0
- package/src/access-observe-write-category.ts +1 -1
- package/src/access-observe-write-surface.ts +24 -19
- package/src/access-operations-batch.ts +18 -2
- package/src/access-service.ts +5 -0
- package/src/briefing-abort.test.ts +173 -0
- package/src/briefing-deadline.test.ts +84 -0
- package/src/briefing-deadline.ts +33 -0
- package/src/briefing-window-abort.test.ts +47 -0
- package/src/briefing-window.ts +33 -4
- package/src/briefing.ts +23 -16
- package/src/correction/passive-correction-notifications-abort.test.ts +42 -0
- package/src/correction/passive-correction-notifications.ts +4 -0
- package/src/entity-lazy-compatibility.test.ts +229 -0
- package/src/entity-migration-completion-notification.test.ts +148 -0
- package/src/index.ts +1 -1
- package/src/location/tagging.ts +14 -12
- package/src/offline-sync-file-io.ts +61 -2
- package/src/offline-sync.test.ts +313 -20
- package/src/offline-sync.ts +43 -21
- package/src/orchestrator-init.test.ts +49 -1
- package/src/orchestrator.ts +9 -8
- package/src/storage/entity-canonical-id-migration-adapter.ts +16 -1
- package/src/storage/entity-canonical-id-migration.ts +17 -8
- package/src/storage/entity-canonical-id-references.ts +24 -0
- package/src/storage/entity-migration-completion-cache.nofollow.test.ts +223 -0
- package/src/storage/entity-migration-completion-cache.ts +153 -0
- package/src/storage/entity-store.ts +58 -86
- package/src/storage/memory-read-store.ts +9 -3
- package/src/storage/windowed-memory-read.ts +8 -0
- package/src/storage-startup-nonblocking-migration.test.ts +183 -0
- package/src/storage.ts +7 -2
- package/src/testing/subjects/entity-migration-completion-cache.test.ts +90 -0
- package/dist/chunk-3NYIXVCT.js.map +0 -1
- package/dist/chunk-6W3QH3QW.js.map +0 -1
- package/dist/chunk-7XFQSQYS.js.map +0 -1
- package/dist/chunk-FRXS6SC7.js.map +0 -1
- package/dist/chunk-LWRIGKFM.js.map +0 -1
- package/dist/chunk-N6EM7ESC.js.map +0 -1
- package/dist/chunk-NH37SP6V.js.map +0 -1
- package/dist/chunk-NUK5XU3P.js.map +0 -1
- package/dist/chunk-O4CA53UY.js.map +0 -1
- package/dist/chunk-RZDDNH63.js.map +0 -1
- package/dist/chunk-SA6P2KS3.js.map +0 -1
- package/dist/chunk-SUKPM6DV.js.map +0 -1
- package/dist/chunk-VY6BD5KB.js.map +0 -1
- /package/dist/{auto-sync-BNCBMU6H.js.map → auto-sync-UQQ6YJSB.js.map} +0 -0
- /package/dist/{entity-canonical-id-migration-2YOLMOFC.js.map → briefing-deadline.js.map} +0 -0
- /package/dist/{capsule-merge-RNQUP2VL.js.map → capsule-merge-D4BTIJGK.js.map} +0 -0
- /package/dist/{chunk-ESDOQEJM.js.map → chunk-2AKIVSHP.js.map} +0 -0
- /package/dist/{chunk-MCS4LIXP.js.map → chunk-43TUWQNR.js.map} +0 -0
- /package/dist/{chunk-2NLCDNGA.js.map → chunk-564YP3DW.js.map} +0 -0
- /package/dist/{chunk-PBWJVRKE.js.map → chunk-5FUDSK6Z.js.map} +0 -0
- /package/dist/{chunk-V4GPFCJ7.js.map → chunk-5G53JSRO.js.map} +0 -0
- /package/dist/{chunk-WJCAAVZX.js.map → chunk-5X2H3X4J.js.map} +0 -0
- /package/dist/{chunk-CPBYKOTC.js.map → chunk-6ZQX3R66.js.map} +0 -0
- /package/dist/{chunk-AF4UIHON.js.map → chunk-74AY3S7U.js.map} +0 -0
- /package/dist/{chunk-XSJL62LE.js.map → chunk-7J6A5MV2.js.map} +0 -0
- /package/dist/{chunk-A3WXV7YU.js.map → chunk-7SAE6T36.js.map} +0 -0
- /package/dist/{chunk-BWF3KNSQ.js.map → chunk-7YVER7ZO.js.map} +0 -0
- /package/dist/{chunk-5IDQVE4U.js.map → chunk-A3UFV4K3.js.map} +0 -0
- /package/dist/{chunk-6TIVVOVU.js.map → chunk-BNJAVRNJ.js.map} +0 -0
- /package/dist/{chunk-CMOYD5CA.js.map → chunk-CCL2OXFO.js.map} +0 -0
- /package/dist/{chunk-FOKTW6GY.js.map → chunk-EWBXMGXR.js.map} +0 -0
- /package/dist/{chunk-YNDLCWXS.js.map → chunk-EZ25VE3G.js.map} +0 -0
- /package/dist/{chunk-E47UQBYW.js.map → chunk-GXVXBYRA.js.map} +0 -0
- /package/dist/{chunk-AGCEJDG2.js.map → chunk-HPNXS6W6.js.map} +0 -0
- /package/dist/{chunk-N5PISU26.js.map → chunk-IN6U2BS5.js.map} +0 -0
- /package/dist/{chunk-HI53EK3Y.js.map → chunk-IUYXCTA7.js.map} +0 -0
- /package/dist/{chunk-VGRBKEBH.js.map → chunk-JBWD3J5U.js.map} +0 -0
- /package/dist/{chunk-YB3WN6XA.js.map → chunk-K5QQXVG6.js.map} +0 -0
- /package/dist/{chunk-7LCGFUMB.js.map → chunk-KQAFEZQX.js.map} +0 -0
- /package/dist/{chunk-HDT3DMUF.js.map → chunk-KW5YZELC.js.map} +0 -0
- /package/dist/{chunk-DM6SOROF.js.map → chunk-LIBVANOF.js.map} +0 -0
- /package/dist/{chunk-VKUMKVEU.js.map → chunk-LMMBV7QC.js.map} +0 -0
- /package/dist/{chunk-NMOEOQV4.js.map → chunk-M2DOJBH7.js.map} +0 -0
- /package/dist/{chunk-3INLTKTW.js.map → chunk-M4HRYLOA.js.map} +0 -0
- /package/dist/{chunk-YY6PWFUC.js.map → chunk-M6TP6UYD.js.map} +0 -0
- /package/dist/{chunk-BJTFLP3J.js.map → chunk-MLNXATFG.js.map} +0 -0
- /package/dist/{chunk-C7WDWD35.js.map → chunk-MWURUSRE.js.map} +0 -0
- /package/dist/{chunk-WIBWONL4.js.map → chunk-N74WJ7B2.js.map} +0 -0
- /package/dist/{chunk-LCHFINL4.js.map → chunk-N7FNCE7F.js.map} +0 -0
- /package/dist/{chunk-QZK6HG4J.js.map → chunk-NPLM7AFU.js.map} +0 -0
- /package/dist/{chunk-CRJX4OMN.js.map → chunk-NSP5PIQH.js.map} +0 -0
- /package/dist/{chunk-X52SXKVC.js.map → chunk-P7OVFGMU.js.map} +0 -0
- /package/dist/{chunk-2J6BIOVN.js.map → chunk-PJCVI44H.js.map} +0 -0
- /package/dist/{chunk-Q6PYCPHO.js.map → chunk-Q3PNQYAC.js.map} +0 -0
- /package/dist/{chunk-D76AHITY.js.map → chunk-QS765SBL.js.map} +0 -0
- /package/dist/{chunk-KNCR7APE.js.map → chunk-QTDZ2NYC.js.map} +0 -0
- /package/dist/{chunk-HCPVL6SF.js.map → chunk-SJTSWG2Z.js.map} +0 -0
- /package/dist/{chunk-XFND63OB.js.map → chunk-TFWJXWL6.js.map} +0 -0
- /package/dist/{chunk-SVJ5JVZO.js.map → chunk-TXKQBQYW.js.map} +0 -0
- /package/dist/{chunk-WQFHHJWK.js.map → chunk-UALXWWGN.js.map} +0 -0
- /package/dist/{chunk-3PVKV3XL.js.map → chunk-UNOAVHV4.js.map} +0 -0
- /package/dist/{chunk-FYZXZUGU.js.map → chunk-UQBOPCDK.js.map} +0 -0
- /package/dist/{chunk-OD7ZXCUF.js.map → chunk-UXGXJUZ5.js.map} +0 -0
- /package/dist/{chunk-2BY56HE6.js.map → chunk-VCOMACR6.js.map} +0 -0
- /package/dist/{chunk-5FNZ3G6X.js.map → chunk-VKQI47DJ.js.map} +0 -0
- /package/dist/{chunk-MPJDQC4G.js.map → chunk-WJCXUE2J.js.map} +0 -0
- /package/dist/{chunk-C3HRYVGD.js.map → chunk-WV4UYDP6.js.map} +0 -0
- /package/dist/{chunk-E3DL3CAL.js.map → chunk-WXJC2L66.js.map} +0 -0
- /package/dist/{chunk-O7GNSHFF.js.map → chunk-XC3H7KON.js.map} +0 -0
- /package/dist/{chunk-OOOX5C4X.js.map → chunk-XL2EE5W3.js.map} +0 -0
- /package/dist/{chunk-CZVNOTLR.js.map → chunk-XXR4A33N.js.map} +0 -0
- /package/dist/{chunk-2AV2U2SZ.js.map → chunk-YDIOMDBW.js.map} +0 -0
- /package/dist/{chunk-QFTNWDJ6.js.map → chunk-YECEEJUU.js.map} +0 -0
- /package/dist/{chunk-BMG3SAXA.js.map → chunk-Z5277JWF.js.map} +0 -0
- /package/dist/{chunk-SW3WHVKN.js.map → chunk-ZFNXJEUG.js.map} +0 -0
- /package/dist/{chunk-KMTMMBEB.js.map → chunk-ZNRTHYYW.js.map} +0 -0
- /package/dist/{chunk-VCBS5GRQ.js.map → chunk-ZSMA67FV.js.map} +0 -0
- /package/dist/{first-start-migration-QVD22WWS.js.map → first-start-migration-A6PHIBUQ.js.map} +0 -0
package/src/access-http.test.ts
CHANGED
|
@@ -15,11 +15,13 @@ import {
|
|
|
15
15
|
EngramAccessInputError,
|
|
16
16
|
type EngramAccessRecallRequest,
|
|
17
17
|
type EngramAccessService,
|
|
18
|
+
type EngramAccessBriefingRequest,
|
|
18
19
|
} from "./access-service.js";
|
|
19
20
|
import {
|
|
20
21
|
tokenCapabilityStore,
|
|
21
22
|
type TokenCapabilities,
|
|
22
23
|
} from "./access-token-capabilities.js";
|
|
24
|
+
import { BriefingTimeoutError } from "./briefing-deadline.js";
|
|
23
25
|
import { parseConfig } from "./config.js";
|
|
24
26
|
import { readPair, writePair } from "./contradiction/contradiction-review.js";
|
|
25
27
|
import { createChatSession } from "./chat/chat-session.js";
|
|
@@ -859,17 +861,23 @@ test("HTTP briefing endpoint dispatches through the briefing operation", async (
|
|
|
859
861
|
assert.equal(body.format, "markdown");
|
|
860
862
|
assert.equal(body.markdown, "# Briefing");
|
|
861
863
|
assert.deepEqual(body.json.sections.activeThreads, []);
|
|
862
|
-
assert.
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
864
|
+
assert.equal(calls.length, 1);
|
|
865
|
+
// SAFETY: the registered handler builds this request after validating the HTTP body; assertions below check its values.
|
|
866
|
+
const captured = calls[0] as EngramAccessBriefingRequest;
|
|
867
|
+
const { abortSignal, ...briefingRequest } = captured;
|
|
868
|
+
assert.ok(
|
|
869
|
+
abortSignal instanceof AbortSignal,
|
|
870
|
+
"the briefing request carries the HTTP request lifetime signal",
|
|
871
|
+
);
|
|
872
|
+
assert.deepEqual(briefingRequest, {
|
|
873
|
+
since: "24h",
|
|
874
|
+
focus: undefined,
|
|
875
|
+
includeLocation: undefined,
|
|
876
|
+
namespace: "team",
|
|
877
|
+
format: "markdown",
|
|
878
|
+
maxFollowups: 5,
|
|
879
|
+
principal: "panel-user",
|
|
880
|
+
});
|
|
873
881
|
|
|
874
882
|
const invalid = await fetch(
|
|
875
883
|
`http://127.0.0.1:${status.port}/engram/v1/briefing`,
|
|
@@ -890,6 +898,27 @@ test("HTTP briefing endpoint dispatches through the briefing operation", async (
|
|
|
890
898
|
}
|
|
891
899
|
});
|
|
892
900
|
|
|
901
|
+
test("HTTP briefing endpoint answers 504 briefing_timeout when the request budget is exhausted", async () => {
|
|
902
|
+
const service: EngramAccessService = Object.assign(Object.create(null), {
|
|
903
|
+
briefing: async () => {
|
|
904
|
+
throw new BriefingTimeoutError(30_000);
|
|
905
|
+
},
|
|
906
|
+
});
|
|
907
|
+
const server = new EngramAccessHttpServer({ service, port: 0, authToken: "test-token", adminConsoleEnabled: false });
|
|
908
|
+
const status = await server.start();
|
|
909
|
+
try {
|
|
910
|
+
const response = await fetch(`http://127.0.0.1:${status.port}/engram/v1/briefing`, {
|
|
911
|
+
method: "POST",
|
|
912
|
+
headers: { authorization: "Bearer test-token", "content-type": "application/json" },
|
|
913
|
+
body: JSON.stringify({}),
|
|
914
|
+
});
|
|
915
|
+
assert.equal(response.status, 504);
|
|
916
|
+
assert.deepEqual(await response.json(), { error: "briefing timed out after 30000ms", code: "briefing_timeout" });
|
|
917
|
+
} finally {
|
|
918
|
+
await server.stop();
|
|
919
|
+
}
|
|
920
|
+
});
|
|
921
|
+
|
|
893
922
|
test("HTTP contradiction scan uses writable namespace resolver", async () => {
|
|
894
923
|
const resolverCalls: Array<{
|
|
895
924
|
namespace: string | undefined;
|
package/src/access-http.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { log } from "./logger.js";
|
|
|
10
10
|
import { resolveNamespaceCapabilities } from "./capabilities.js";
|
|
11
11
|
import { WriteRateLimiter, type WriteRateLimitReservation } from "./write-rate-limiter.js";
|
|
12
12
|
import { abortError, isAbortError } from "./abort-error.js";
|
|
13
|
+
import { BriefingTimeoutError } from "./briefing-deadline.js";
|
|
13
14
|
import { EngramAccessForbiddenError } from "./access-errors.js";
|
|
14
15
|
import {
|
|
15
16
|
EngramAccessInputError,
|
|
@@ -500,6 +501,10 @@ export class EngramAccessHttpServer extends ReviewDeckAccessHttpBase {
|
|
|
500
501
|
res.once("close", abortDisconnectedRequest);
|
|
501
502
|
correlationIdStore.run(correlationId, () => {
|
|
502
503
|
void this.handle(req, res, correlationId, abortController.signal).catch((err) => {
|
|
504
|
+
if (err instanceof BriefingTimeoutError) {
|
|
505
|
+
this.respondJson(res, 504, { error: err.message, code: "briefing_timeout" });
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
503
508
|
if (isAbortError(err)) {
|
|
504
509
|
if (!res.destroyed && !res.writableEnded) {
|
|
505
510
|
res.end();
|
|
@@ -1979,6 +1984,7 @@ export class EngramAccessHttpServer extends ReviewDeckAccessHttpBase {
|
|
|
1979
1984
|
service: this.service,
|
|
1980
1985
|
principal: this.resolveRequestPrincipal(req),
|
|
1981
1986
|
respondJson: (status, payload) => this.respondJson(res, status, payload),
|
|
1987
|
+
abortSignal,
|
|
1982
1988
|
});
|
|
1983
1989
|
return;
|
|
1984
1990
|
}
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from "./access-schema.js";
|
|
16
16
|
|
|
17
17
|
export { reapplyCategoryCoercion };
|
|
18
|
-
export {
|
|
18
|
+
export { buildWithBriefingLocation } from "./location/tagging.js";
|
|
19
19
|
|
|
20
20
|
export function splitCanonicalWriteRequest<T extends { rawCategory?: string; category?: string }>(
|
|
21
21
|
request: T,
|
|
@@ -29,8 +29,9 @@ import {
|
|
|
29
29
|
type MemoryScopePlan,
|
|
30
30
|
NamespaceNotWritableError,
|
|
31
31
|
} from "./access-service.js";
|
|
32
|
-
import {
|
|
32
|
+
import { buildWithBriefingLocation, reapplyCategoryCoercion, splitCanonicalWriteRequest } from "./access-observe-write-category.js";
|
|
33
33
|
import { abortError } from "./abort-error.js";
|
|
34
|
+
import { withBriefingDeadline } from "./briefing-deadline.js";
|
|
34
35
|
import { extractionForceFlush } from "./access-extraction-force-flush.js";
|
|
35
36
|
import { ObserveTranscriptPersister, observeTranscriptSessionKey } from "./access-observe-transcript.js";
|
|
36
37
|
import { FileCalendarSource, buildBriefing, parseBriefingFocus, parseBriefingWindow } from "./briefing.js";
|
|
@@ -410,7 +411,6 @@ export class AccessObserveWriteSurface {
|
|
|
410
411
|
}
|
|
411
412
|
|
|
412
413
|
const namespace = this.deps.resolveReadableNamespace(request.namespace, request.principal);
|
|
413
|
-
const storage = await this.deps.orchestrator.getStorage(namespace);
|
|
414
414
|
|
|
415
415
|
const token =
|
|
416
416
|
typeof request.since === "string" && request.since.trim().length > 0
|
|
@@ -457,23 +457,28 @@ export class AccessObserveWriteSurface {
|
|
|
457
457
|
? new FileCalendarSource(config.briefing.calendarSource)
|
|
458
458
|
: undefined;
|
|
459
459
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
namespace
|
|
463
|
-
window,
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
460
|
+
// Storage acquisition and the briefing body share one request budget.
|
|
461
|
+
const result = await withBriefingDeadline(async (signal) => {
|
|
462
|
+
const storage = await this.deps.orchestrator.getStorage(namespace);
|
|
463
|
+
return buildWithBriefingLocation(request.includeLocation, storage.dir, window.from.toISOString(), config.location, () => buildBriefing({
|
|
464
|
+
storage,
|
|
465
|
+
namespace,
|
|
466
|
+
window,
|
|
467
|
+
focus,
|
|
468
|
+
calendarSource,
|
|
469
|
+
maxFollowups,
|
|
470
|
+
allowLlm: config.briefing.llmFollowups,
|
|
471
|
+
openaiApiKey: config.openaiApiKey,
|
|
472
|
+
openaiBaseUrl: config.openaiBaseUrl,
|
|
473
|
+
model: config.model,
|
|
474
|
+
// Without a direct OpenAI key, route follow-ups through the configured
|
|
475
|
+
// LLM chain (gateway model source or local LLM) — same fallback every
|
|
476
|
+
// other LLM feature uses. A configured key keeps its precedence so
|
|
477
|
+
// existing deployments are unchanged.
|
|
478
|
+
followupGenerator: config.openaiApiKey ? undefined : this.deps.orchestrator.briefingChainFollowupGenerator,
|
|
479
|
+
abortSignal: signal,
|
|
480
|
+
}));
|
|
481
|
+
}, request.abortSignal);
|
|
477
482
|
|
|
478
483
|
return {
|
|
479
484
|
format,
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
retainedCategoryAlias,
|
|
24
24
|
suggestionSubmitRequestSchema,
|
|
25
25
|
} from "./access-schema.js";
|
|
26
|
-
import { EngramAccessInputError, type EngramAccessService } from "./access-service.js";
|
|
26
|
+
import { EngramAccessInputError, type EngramAccessService, type EngramAccessBriefingRequest } from "./access-service.js";
|
|
27
27
|
import { enforceNamespaceAllowList, tokenCapabilityStore } from "./access-token-capabilities.js";
|
|
28
28
|
import { projectTagProjectId } from "./coding/coding-namespace.js";
|
|
29
29
|
import {
|
|
@@ -381,7 +381,23 @@ defineOperation({ name: "memory_promote", description: "Promote memory.", schema
|
|
|
381
381
|
defineOperation({ name: "memory_outcome", description: "Record outcome.", schema: strictSchema({ memoryId: S.str, outcome: S.str, namespace: S.str, sessionKey: S.str, timestamp: S.str }), handler: async (input, ctx) => { const o = optStr(input.outcome); if (o !== "success" && o !== "failure") throw new EngramAccessInputError("memory_outcome: outcome must be \"success\" or \"failure\"; got " + JSON.stringify(o)); return { result: await ctx.service.memoryOutcome({ memoryId: optStr(input.memoryId) ?? "", outcome: o, namespace: optStr(input.namespace), sessionKey: optStr(input.sessionKey), timestamp: optStr(input.timestamp), principal: ctx.authenticatedPrincipal }) }; } });
|
|
382
382
|
defineOperation({ name: "memory_action_apply", description: "Apply action.", schema: strictSchema({ action: S.str, outcome: S.str, reason: S.str, memoryId: S.str, namespace: S.str, sessionKey: S.str, content: S.str, category: S.str, linkTargetId: S.str, linkType: S.str, linkStrength: S.num, artifactType: S.str, execute: S.bool, sourcePrompt: S.str, dryRun: S.bool }), handler: async (input, ctx) => ({ result: await ctx.service.memoryActionApply({ action: optStr(input.action) ?? "", outcome: optStr(input.outcome), reason: optStr(input.reason), memoryId: optStr(input.memoryId) ?? "", namespace: optStr(input.namespace), sessionKey: optStr(input.sessionKey), content: optStr(input.content), category: optStr(input.category), linkTargetId: optStr(input.linkTargetId), linkType: optStr(input.linkType), linkStrength: optNum(input.linkStrength), artifactType: optStr(input.artifactType), execute: optBool(input.execute), sourcePrompt: optStr(input.sourcePrompt), dryRun: input.dryRun === true, principal: ctx.authenticatedPrincipal }) }) });
|
|
383
383
|
defineOperation({ name: "context_checkpoint", description: "Context checkpoint.", schema: strictSchema({ sessionKey: S.str, context: S.str, namespace: S.str }), handler: async (input, ctx) => ({ result: await ctx.service.contextCheckpoint({ sessionKey: optStr(input.sessionKey) ?? "", context: optStr(input.context) ?? "", namespace: optStr(input.namespace), principal: ctx.authenticatedPrincipal }) }) });
|
|
384
|
-
defineOperation({
|
|
384
|
+
defineOperation({
|
|
385
|
+
name: "briefing", description: "Daily briefing.",
|
|
386
|
+
schema: strictSchema({ since: S.str, focus: S.str, namespace: S.str, includeLocation: S.bool, format: S.str, maxFollowups: S.num }),
|
|
387
|
+
handler: async (input, ctx) => {
|
|
388
|
+
const rf = optStr(input.format);
|
|
389
|
+
const fe = validateBriefingFormat(rf);
|
|
390
|
+
if (fe) throw new EngramAccessInputError(fe);
|
|
391
|
+
// SAFETY: validateBriefingFormat rejects every format except json, markdown, or undefined.
|
|
392
|
+
const request: EngramAccessBriefingRequest = {
|
|
393
|
+
since: optStr(input.since), focus: optStr(input.focus), namespace: optStr(input.namespace),
|
|
394
|
+
includeLocation: optBool(input.includeLocation), format: rf as "json" | "markdown" | undefined,
|
|
395
|
+
maxFollowups: optNum(input.maxFollowups), principal: ctx.authenticatedPrincipal,
|
|
396
|
+
};
|
|
397
|
+
if (ctx.abortSignal) request.abortSignal = ctx.abortSignal;
|
|
398
|
+
return { result: await ctx.service.briefing(request) };
|
|
399
|
+
},
|
|
400
|
+
});
|
|
385
401
|
|
|
386
402
|
// === CONTRADICTION/REVIEW ===
|
|
387
403
|
defineOperation({ name: "review_list", description: "List review pairs.", schema: strictSchema({ filter: S.str, namespace: S.str, limit: S.num }),
|
package/src/access-service.ts
CHANGED
|
@@ -507,6 +507,11 @@ export interface EngramAccessBriefingRequest extends LocationContextRequestOptio
|
|
|
507
507
|
maxFollowups?: number;
|
|
508
508
|
/** Caller principal for namespace access checks. Transport-bound — never from untrusted payloads. */
|
|
509
509
|
principal?: string;
|
|
510
|
+
/**
|
|
511
|
+
* Transport request lifetime. When it aborts, the briefing stops and
|
|
512
|
+
* rejects with that AbortError instead of running on (issue #3139).
|
|
513
|
+
*/
|
|
514
|
+
abortSignal?: AbortSignal;
|
|
510
515
|
}
|
|
511
516
|
|
|
512
517
|
/** Response for `remnic_briefing`. */
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import test, { type TestContext } from "node:test";
|
|
6
|
+
import { abortError } from "./abort-error.js";
|
|
7
|
+
import fs from "node:fs/promises";
|
|
8
|
+
import { syncBuiltinESMExports } from "node:module";
|
|
9
|
+
import { AccessObserveWriteSurface } from "./access-observe-write-surface.js";
|
|
10
|
+
import { Orchestrator } from "./orchestrator.js";
|
|
11
|
+
import { buildBriefing, type ParsedBriefingWindow } from "./briefing.js";
|
|
12
|
+
import type { CorpusReadOptions } from "./corpus-read-cancellation.js";
|
|
13
|
+
import { StorageManager } from "./storage.js";
|
|
14
|
+
import type { WindowedMemoryReadOptions, WindowedMemoryReadResult } from "./storage/windowed-memory-read.js";
|
|
15
|
+
import type { EntityFile, MemoryFile } from "./types.js";
|
|
16
|
+
|
|
17
|
+
const WINDOW: ParsedBriefingWindow = {
|
|
18
|
+
from: new Date("2026-04-10T00:00:00.000Z"),
|
|
19
|
+
to: new Date("2026-04-11T00:00:00.000Z"),
|
|
20
|
+
label: "test-window",
|
|
21
|
+
};
|
|
22
|
+
const MEMORY: MemoryFile = {
|
|
23
|
+
path: "/synthetic/mem.md",
|
|
24
|
+
frontmatter: {
|
|
25
|
+
id: "synthetic-mem",
|
|
26
|
+
category: "fact",
|
|
27
|
+
created: "2026-04-10T01:00:00.000Z",
|
|
28
|
+
updated: "2026-04-10T02:00:00.000Z",
|
|
29
|
+
source: "test",
|
|
30
|
+
confidence: 0.9,
|
|
31
|
+
confidenceTier: "explicit",
|
|
32
|
+
tags: [],
|
|
33
|
+
},
|
|
34
|
+
content: "synthetic briefing memory",
|
|
35
|
+
};
|
|
36
|
+
const ENTITY: EntityFile = {
|
|
37
|
+
name: "SyntheticEntity",
|
|
38
|
+
type: "project",
|
|
39
|
+
updated: "2026-04-10T03:00:00.000Z",
|
|
40
|
+
facts: ["synthetic fact"],
|
|
41
|
+
timeline: [],
|
|
42
|
+
relationships: [],
|
|
43
|
+
activity: [],
|
|
44
|
+
aliases: [],
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function makeStorage(t: TestContext): StorageManager {
|
|
48
|
+
const dir = mkdtempSync(path.join(os.tmpdir(), "remnic-briefing-abort-"));
|
|
49
|
+
t.after(() => rmSync(dir, { recursive: true, force: true }));
|
|
50
|
+
const storage = new StorageManager(dir);
|
|
51
|
+
t.mock.method(storage, "readMemoriesWindow", async (_options: WindowedMemoryReadOptions) => ({
|
|
52
|
+
memories: [MEMORY],
|
|
53
|
+
filePaths: [MEMORY.path],
|
|
54
|
+
}));
|
|
55
|
+
t.mock.method(storage, "readAllEntityFiles", async (_options?: CorpusReadOptions) => [ENTITY]);
|
|
56
|
+
return storage;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function makeStallingStorage(t: TestContext) {
|
|
60
|
+
const storage = makeStorage(t);
|
|
61
|
+
const fallback = Promise.withResolvers<void>();
|
|
62
|
+
// The fallback releases broken signal plumbing too, so the regression cannot hang.
|
|
63
|
+
t.mock.method(
|
|
64
|
+
storage,
|
|
65
|
+
"readMemoriesWindow",
|
|
66
|
+
(options: WindowedMemoryReadOptions): Promise<WindowedMemoryReadResult> => {
|
|
67
|
+
const signal = options.abortSignal;
|
|
68
|
+
if (!signal) return fallback.promise.then(() => ({ memories: [], filePaths: [] }));
|
|
69
|
+
const read = Promise.withResolvers<WindowedMemoryReadResult>();
|
|
70
|
+
const onAbort = () => read.reject(abortError("corpus read aborted"));
|
|
71
|
+
if (signal.aborted) {
|
|
72
|
+
onAbort();
|
|
73
|
+
return read.promise;
|
|
74
|
+
}
|
|
75
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
76
|
+
void fallback.promise.then(() => {
|
|
77
|
+
signal.removeEventListener("abort", onAbort);
|
|
78
|
+
read.resolve({ memories: [], filePaths: [] });
|
|
79
|
+
});
|
|
80
|
+
return read.promise;
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
return { storage, releaseFallback: fallback.resolve };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
test("buildBriefing rejects with an AbortError when the caller's cancel signal fires mid-read", async (t) => {
|
|
87
|
+
const { storage, releaseFallback } = makeStallingStorage(t);
|
|
88
|
+
const controller = new AbortController();
|
|
89
|
+
const call = buildBriefing({ storage, window: WINDOW, allowLlm: false, abortSignal: controller.signal });
|
|
90
|
+
controller.abort();
|
|
91
|
+
releaseFallback();
|
|
92
|
+
await assert.rejects(call, { name: "AbortError", message: /abort|cancel/i });
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("buildBriefing rejects when the caller's cancel signal is already aborted", async (t) => {
|
|
96
|
+
const { storage, releaseFallback } = makeStallingStorage(t);
|
|
97
|
+
const controller = new AbortController();
|
|
98
|
+
controller.abort();
|
|
99
|
+
const call = buildBriefing({ storage, window: WINDOW, allowLlm: false, abortSignal: controller.signal });
|
|
100
|
+
releaseFallback();
|
|
101
|
+
await assert.rejects(call, { name: "AbortError" });
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("a cancelled briefing does not drain the correction-notification queue", async (t) => {
|
|
105
|
+
const storage = makeStorage(t);
|
|
106
|
+
const queueDir = path.join(storage.dir, "state", "corrections");
|
|
107
|
+
mkdirSync(queueDir, { recursive: true });
|
|
108
|
+
const queueFile = path.join(queueDir, "notifications.jsonl");
|
|
109
|
+
writeFileSync(
|
|
110
|
+
queueFile,
|
|
111
|
+
`${JSON.stringify({ planId: "synthetic-plan", summary: "synthetic correction", undoCommand: "remnic correct plan synthetic-plan", createdAt: "2026-04-10T04:00:00.000Z" })}\n`
|
|
112
|
+
);
|
|
113
|
+
const controller = new AbortController();
|
|
114
|
+
const call = buildBriefing({
|
|
115
|
+
storage,
|
|
116
|
+
window: WINDOW,
|
|
117
|
+
abortSignal: controller.signal,
|
|
118
|
+
// Cancel after reads, just before the queue-draining phase.
|
|
119
|
+
followupGenerator: async () => {
|
|
120
|
+
controller.abort();
|
|
121
|
+
return [];
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
await assert.rejects(call, { name: "AbortError" });
|
|
125
|
+
assert.ok(existsSync(queueFile), "a cancelled briefing must not consume correction notifications");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("buildBriefing returns a non-empty briefing when nothing is cancelled", async (t) => {
|
|
129
|
+
const result = await buildBriefing({ storage: makeStorage(t), window: WINDOW, allowLlm: false });
|
|
130
|
+
assert.equal(result.sections.activeThreads.length, 1);
|
|
131
|
+
assert.equal(result.sections.recentEntities.length, 1);
|
|
132
|
+
assert.ok(result.markdown.length > 0);
|
|
133
|
+
assert.deepEqual(result.window, { from: "2026-04-10T00:00:00.000Z", to: "2026-04-11T00:00:00.000Z" });
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("an HTTP/MCP briefing cancelled during the location read keeps the notification queue", async (t) => {
|
|
137
|
+
const storage = makeStorage(t);
|
|
138
|
+
const queueDir = path.join(storage.dir, "state", "corrections");
|
|
139
|
+
mkdirSync(queueDir, { recursive: true });
|
|
140
|
+
const queueFile = path.join(queueDir, "notifications.jsonl");
|
|
141
|
+
writeFileSync(
|
|
142
|
+
queueFile,
|
|
143
|
+
`${JSON.stringify({ planId: "synthetic-plan", summary: "synthetic correction", undoCommand: "remnic correct plan synthetic-plan", createdAt: "2026-04-10T04:00:00.000Z" })}\n`
|
|
144
|
+
);
|
|
145
|
+
const controller = new AbortController();
|
|
146
|
+
const read: (file: string, encoding: "utf-8") => Promise<string> = fs.readFile;
|
|
147
|
+
t.mock.method(fs, "readFile", async (file: string) => {
|
|
148
|
+
if (file.endsWith(path.join("state", "locations", "sync.json"))) controller.abort(abortError("deadline"));
|
|
149
|
+
return read(file, "utf-8");
|
|
150
|
+
});
|
|
151
|
+
syncBuiltinESMExports();
|
|
152
|
+
t.after(() => {
|
|
153
|
+
t.mock.restoreAll();
|
|
154
|
+
syncBuiltinESMExports();
|
|
155
|
+
});
|
|
156
|
+
// Object.create yields untyped values, so no assertion is needed: briefing() reads only these members.
|
|
157
|
+
const orchestrator: Orchestrator = Object.assign(Object.create(Orchestrator.prototype), {
|
|
158
|
+
config: {
|
|
159
|
+
briefing: { enabled: true, defaultWindow: "24h", defaultFormat: "markdown", maxFollowups: 0, llmFollowups: false },
|
|
160
|
+
location: { enabled: true, tagging: { enabled: true }, timezone: "UTC" },
|
|
161
|
+
},
|
|
162
|
+
getStorage: async () => storage,
|
|
163
|
+
});
|
|
164
|
+
const surface = new AccessObserveWriteSurface(
|
|
165
|
+
Object.assign(Object.create(null), { orchestrator, resolveReadableNamespace: () => "default" }),
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
await assert.rejects(
|
|
169
|
+
surface.briefing({ includeLocation: true, abortSignal: controller.signal }),
|
|
170
|
+
{ name: "AbortError" },
|
|
171
|
+
);
|
|
172
|
+
assert.ok(existsSync(queueFile), "a briefing cancelled during the location read must not consume notifications");
|
|
173
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue #3139 — the briefing's default deadline.
|
|
3
|
+
*
|
|
4
|
+
* The budget is a server-side limit, not a client cancellation: exhausting it
|
|
5
|
+
* must surface as an explicit TimeoutError, never as an empty result.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import assert from "node:assert/strict";
|
|
9
|
+
import test from "node:test";
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
BRIEFING_DEADLINE_MS,
|
|
13
|
+
BriefingTimeoutError,
|
|
14
|
+
withBriefingDeadline,
|
|
15
|
+
} from "./briefing-deadline.js";
|
|
16
|
+
|
|
17
|
+
test("withBriefingDeadline rejects with a TimeoutError when the default budget is exhausted", async (t) => {
|
|
18
|
+
t.mock.timers.enable({ apis: ["setTimeout", "Date"], now: 0 });
|
|
19
|
+
|
|
20
|
+
const pending = withBriefingDeadline(
|
|
21
|
+
() => Promise.withResolvers<never>().promise,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
t.mock.timers.tick(BRIEFING_DEADLINE_MS);
|
|
25
|
+
|
|
26
|
+
await assert.rejects(pending, (err) => {
|
|
27
|
+
assert.ok(
|
|
28
|
+
err instanceof BriefingTimeoutError,
|
|
29
|
+
"budget exhaustion must surface as a timeout",
|
|
30
|
+
);
|
|
31
|
+
assert.equal(err.name, "TimeoutError");
|
|
32
|
+
assert.equal(err.deadlineMs, BRIEFING_DEADLINE_MS);
|
|
33
|
+
return true;
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("withBriefingDeadline clears its timer once the run settles", async (t) => {
|
|
38
|
+
t.mock.timers.enable({ apis: ["setTimeout", "Date"], now: 0 });
|
|
39
|
+
let observed: AbortSignal | undefined;
|
|
40
|
+
|
|
41
|
+
const result = await withBriefingDeadline(async (signal) => {
|
|
42
|
+
observed = signal;
|
|
43
|
+
return "briefing";
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
assert.equal(result, "briefing");
|
|
47
|
+
t.mock.timers.tick(BRIEFING_DEADLINE_MS);
|
|
48
|
+
assert.equal(
|
|
49
|
+
observed?.aborted,
|
|
50
|
+
false,
|
|
51
|
+
"the budget timer must be cleared when the run settles, not left to fire afterwards",
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("withBriefingDeadline preserves work failures and clears their timer", async (t) => {
|
|
56
|
+
t.mock.timers.enable({ apis: ["setTimeout", "Date"], now: 0 });
|
|
57
|
+
const failure = new Error("synthetic work failure");
|
|
58
|
+
const signals: AbortSignal[] = [];
|
|
59
|
+
|
|
60
|
+
const pending = withBriefingDeadline(async (signal) => {
|
|
61
|
+
signals.push(signal);
|
|
62
|
+
throw failure;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
await assert.rejects(pending, (err) => err === failure);
|
|
66
|
+
assert.equal(signals.length, 1);
|
|
67
|
+
t.mock.timers.tick(BRIEFING_DEADLINE_MS);
|
|
68
|
+
assert.equal(signals[0].aborted, false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("withBriefingDeadline normalizes arbitrary caller cancellation to AbortError", async () => {
|
|
72
|
+
const controller = new AbortController();
|
|
73
|
+
const pending = withBriefingDeadline(
|
|
74
|
+
() => Promise.withResolvers<never>().promise,
|
|
75
|
+
controller.signal,
|
|
76
|
+
);
|
|
77
|
+
controller.abort(new Error("synthetic caller cancellation"));
|
|
78
|
+
|
|
79
|
+
await assert.rejects(pending, (err) => {
|
|
80
|
+
assert.ok(err instanceof Error);
|
|
81
|
+
assert.equal(err.name, "AbortError");
|
|
82
|
+
return true;
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { isAbortError, raceAbort } from "./abort-error.js";
|
|
2
|
+
|
|
3
|
+
export const BRIEFING_DEADLINE_MS = 30_000;
|
|
4
|
+
|
|
5
|
+
export class BriefingTimeoutError extends Error {
|
|
6
|
+
constructor(readonly deadlineMs: number) {
|
|
7
|
+
super(`briefing timed out after ${deadlineMs}ms`);
|
|
8
|
+
this.name = "TimeoutError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function withBriefingDeadline<T>(
|
|
13
|
+
run: (signal: AbortSignal) => Promise<T>,
|
|
14
|
+
abortSignal?: AbortSignal
|
|
15
|
+
): Promise<T> {
|
|
16
|
+
const controller = new AbortController();
|
|
17
|
+
const signal = abortSignal ? AbortSignal.any([abortSignal, controller.signal]) : controller.signal;
|
|
18
|
+
const timer = setTimeout(
|
|
19
|
+
() => controller.abort(new BriefingTimeoutError(BRIEFING_DEADLINE_MS)),
|
|
20
|
+
BRIEFING_DEADLINE_MS
|
|
21
|
+
);
|
|
22
|
+
try {
|
|
23
|
+
signal.throwIfAborted();
|
|
24
|
+
return await raceAbort(run(signal), signal, "briefing aborted");
|
|
25
|
+
} catch (err) {
|
|
26
|
+
// raceAbort normalizes cancellation; retain the distinct deadline reason.
|
|
27
|
+
const reason: unknown = signal.reason;
|
|
28
|
+
if (signal.aborted && (reason instanceof BriefingTimeoutError || isAbortError(reason))) throw reason;
|
|
29
|
+
throw err;
|
|
30
|
+
} finally {
|
|
31
|
+
clearTimeout(timer);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { safeReadMemories } from "./briefing-window.js";
|
|
4
|
+
import type { CorpusReadOptions } from "./corpus-read-cancellation.js";
|
|
5
|
+
import type { MemoryFile } from "./types.js";
|
|
6
|
+
|
|
7
|
+
test("caller cancellation reaches the signal-aware legacy briefing scan", async () => {
|
|
8
|
+
const controller = new AbortController();
|
|
9
|
+
const started = Promise.withResolvers<AbortSignal>();
|
|
10
|
+
const release = Promise.withResolvers<void>();
|
|
11
|
+
const reader = {
|
|
12
|
+
supportsAbortSignal: true,
|
|
13
|
+
async readAllMemories(options?: CorpusReadOptions): Promise<MemoryFile[]> {
|
|
14
|
+
assert.ok(options?.abortSignal);
|
|
15
|
+
started.resolve(options.abortSignal);
|
|
16
|
+
await release.promise;
|
|
17
|
+
options.abortSignal.throwIfAborted();
|
|
18
|
+
return [];
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
const result = safeReadMemories(
|
|
22
|
+
reader,
|
|
23
|
+
{
|
|
24
|
+
from: new Date("2026-04-10T00:00:00.000Z"),
|
|
25
|
+
to: new Date("2026-04-11T00:00:00.000Z"),
|
|
26
|
+
label: "yesterday",
|
|
27
|
+
},
|
|
28
|
+
{ signal: controller.signal }
|
|
29
|
+
).then(
|
|
30
|
+
() => ({ ok: true as const }),
|
|
31
|
+
(error) => ({ ok: false as const, error })
|
|
32
|
+
);
|
|
33
|
+
try {
|
|
34
|
+
const scanSignal = await started.promise;
|
|
35
|
+
assert.equal(scanSignal.aborted, false);
|
|
36
|
+
controller.abort();
|
|
37
|
+
release.resolve();
|
|
38
|
+
const outcome = await result;
|
|
39
|
+
assert.equal(outcome.ok, false, "a cancelled scan must reject, not return an empty corpus");
|
|
40
|
+
assert.ok(outcome.error instanceof Error);
|
|
41
|
+
assert.equal(outcome.error.name, "AbortError");
|
|
42
|
+
assert.equal(scanSignal.aborted, true, "the underlying scan must stop too");
|
|
43
|
+
} finally {
|
|
44
|
+
release.resolve();
|
|
45
|
+
await result;
|
|
46
|
+
}
|
|
47
|
+
});
|
package/src/briefing-window.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { log } from "./logger.js";
|
|
2
|
-
import type { MemoryFile } from "./types.js";
|
|
2
|
+
import type { EntityFile, MemoryFile } from "./types.js";
|
|
3
3
|
import { isAbortError } from "./abort-error.js";
|
|
4
4
|
import type { CorpusReadOptions } from "./corpus-read-cancellation.js";
|
|
5
5
|
import type { ParsedBriefingWindow } from "./briefing.js";
|
|
6
|
+
import type { WindowedMemoryReadOptions } from "./storage/windowed-memory-read.js";
|
|
6
7
|
|
|
7
8
|
type BriefingMemoryReader = {
|
|
8
|
-
readMemoriesWindow?: (options:
|
|
9
|
+
readMemoriesWindow?: (options: WindowedMemoryReadOptions) => Promise<{ memories: MemoryFile[] }>;
|
|
9
10
|
readAllMemories: ((options?: CorpusReadOptions) => Promise<MemoryFile[]>) & {
|
|
10
11
|
supportsAbortSignal?: boolean;
|
|
11
12
|
};
|
|
@@ -30,6 +31,12 @@ export const BRIEFING_FULL_READ_FALLBACK_MS = 30_000;
|
|
|
30
31
|
export interface SafeReadMemoriesOptions {
|
|
31
32
|
/** Test seam overriding {@link BRIEFING_FULL_READ_FALLBACK_MS}. */
|
|
32
33
|
fallbackDeadlineMs?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Caller cancellation. The signal reaches the storage read so it stops at
|
|
36
|
+
* its next boundary, and a cancelled read is rethrown rather than degraded
|
|
37
|
+
* into an empty corpus (issue #3139).
|
|
38
|
+
*/
|
|
39
|
+
signal?: AbortSignal;
|
|
33
40
|
}
|
|
34
41
|
|
|
35
42
|
type BriefingReadMode = "windowed" | "full-read-fallback";
|
|
@@ -138,6 +145,12 @@ export async function safeReadMemories(
|
|
|
138
145
|
resultCount = memories.length;
|
|
139
146
|
return memories;
|
|
140
147
|
} catch (err) {
|
|
148
|
+
if (isAbortError(err) && options.signal?.aborted) {
|
|
149
|
+
// Caller cancellation is not a read failure: rethrow it so the briefing
|
|
150
|
+
// rejects instead of degrading into an empty corpus (issue #3139). The
|
|
151
|
+
// own fallback deadline is reported as BriefingReadTimedOut below.
|
|
152
|
+
throw err;
|
|
153
|
+
}
|
|
141
154
|
if (err instanceof BriefingReadTimedOut) {
|
|
142
155
|
outcome = "timeout";
|
|
143
156
|
bestEffortLog(
|
|
@@ -174,7 +187,9 @@ async function readBriefingMemories(
|
|
|
174
187
|
// the full corpus on cache misses; keep the full-read fallback for custom
|
|
175
188
|
// StorageManager-compatible callers that predate readMemoriesWindow().
|
|
176
189
|
if (mode === "windowed") {
|
|
177
|
-
const
|
|
190
|
+
const readOptions: WindowedMemoryReadOptions = { updatedAfter: window.from };
|
|
191
|
+
if (options.signal) readOptions.abortSignal = options.signal;
|
|
192
|
+
const result = await storage.readMemoriesWindow!(readOptions);
|
|
178
193
|
return result.memories;
|
|
179
194
|
}
|
|
180
195
|
// Refuse legacy adapters that do not explicitly declare cancellation support (#2827).
|
|
@@ -195,12 +210,13 @@ async function readBriefingMemories(
|
|
|
195
210
|
deadlineFired = true;
|
|
196
211
|
controller.abort();
|
|
197
212
|
}, deadlineMs);
|
|
213
|
+
const signal = options.signal ? AbortSignal.any([options.signal, controller.signal]) : controller.signal;
|
|
198
214
|
try {
|
|
199
215
|
// Direct await, no Promise.race: the signal IS the deadline. A
|
|
200
216
|
// signal-aware read settles on abort (AbortError at its next scan
|
|
201
217
|
// boundary, #2307), so nothing is left scanning in the background once
|
|
202
218
|
// this function returns.
|
|
203
|
-
const memories = await storage.readAllMemories({ abortSignal:
|
|
219
|
+
const memories = await storage.readAllMemories({ abortSignal: signal });
|
|
204
220
|
if (deadlineFired) {
|
|
205
221
|
// A double that resolves partial rows instead of rejecting on abort
|
|
206
222
|
// must not have them served as a complete corpus.
|
|
@@ -214,3 +230,16 @@ async function readBriefingMemories(
|
|
|
214
230
|
clearTimeout(timer);
|
|
215
231
|
}
|
|
216
232
|
}
|
|
233
|
+
|
|
234
|
+
export async function safeReadEntities(
|
|
235
|
+
storage: { readAllEntityFiles(options?: CorpusReadOptions): Promise<EntityFile[]> },
|
|
236
|
+
signal: AbortSignal,
|
|
237
|
+
): Promise<EntityFile[]> {
|
|
238
|
+
try {
|
|
239
|
+
return await storage.readAllEntityFiles({ abortSignal: signal });
|
|
240
|
+
} catch (err) {
|
|
241
|
+
if (isAbortError(err) && signal.aborted) throw err;
|
|
242
|
+
log.warn(`briefing: readAllEntityFiles failed: ${err}`);
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
}
|