@remnic/core 1.1.1 → 1.1.3
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/abort-error.js +1 -0
- package/dist/abstraction-nodes.js +1 -0
- package/dist/access-audit.js +1 -0
- package/dist/access-cli.js +76 -51
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +50 -5
- package/dist/access-http.js +38 -16
- package/dist/access-idempotency.js +1 -0
- package/dist/access-mcp.d.ts +10 -5
- package/dist/access-mcp.js +37 -14
- package/dist/access-schema.d.ts +133 -13
- package/dist/access-schema.js +20 -1
- package/dist/access-service-_AEUMVyX.d.ts +1981 -0
- package/dist/access-service.d.ts +11 -6
- package/dist/access-service.js +39 -14
- package/dist/active-memory-bridge.js +1 -0
- package/dist/active-recall.js +1 -0
- package/dist/active-recall.js.map +1 -1
- package/dist/behavior-learner.js +1 -0
- package/dist/behavior-learner.js.map +1 -1
- package/dist/behavior-signals.js +1 -0
- package/dist/bootstrap.d.ts +6 -4
- package/dist/bootstrap.js +1 -0
- package/dist/boxes.js +1 -0
- package/dist/briefing.d.ts +9 -5
- package/dist/briefing.js +10 -6
- package/dist/buffer-surprise-report.js +1 -0
- package/dist/buffer-surprise.js +1 -0
- package/dist/buffer.d.ts +1 -1
- package/dist/buffer.js +1 -0
- package/dist/calibration.d.ts +8 -1
- package/dist/calibration.js +10 -2
- package/dist/calibration.js.map +1 -1
- package/dist/capsule-cli.d.ts +137 -0
- package/dist/capsule-cli.js +34 -0
- package/dist/capsule-crypto-5CYAGVC5.js +18 -0
- package/dist/capsule-export-NZQPOTQ4.js +17 -0
- package/dist/capsule-export-NZQPOTQ4.js.map +1 -0
- package/dist/capsule-import-SDCUXLEV.js +16 -0
- package/dist/capsule-import-SDCUXLEV.js.map +1 -0
- package/dist/capsule-merge-DI7PNQ2H.js +189 -0
- package/dist/capsule-merge-DI7PNQ2H.js.map +1 -0
- package/dist/causal-behavior.js +1 -0
- package/dist/causal-behavior.js.map +1 -1
- package/dist/causal-chain.js +1 -0
- package/dist/causal-consolidation.js +12 -9
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/causal-retrieval.js +2 -1
- package/dist/causal-retrieval.js.map +1 -1
- package/dist/causal-trajectory-graph.js +4 -1
- package/dist/causal-trajectory-graph.js.map +1 -1
- package/dist/causal-trajectory.js +2 -1
- package/dist/chunk-2LSZVONP.js +67 -0
- package/dist/chunk-2LSZVONP.js.map +1 -0
- package/dist/chunk-32KD5IHZ.js +245 -0
- package/dist/chunk-32KD5IHZ.js.map +1 -0
- package/dist/{chunk-VDX363PS.js → chunk-34F3PLWZ.js} +10 -3
- package/dist/chunk-34F3PLWZ.js.map +1 -0
- package/dist/chunk-3KIS4VGT.js +228 -0
- package/dist/chunk-3KIS4VGT.js.map +1 -0
- package/dist/chunk-3LCWFNVS.js +350 -0
- package/dist/chunk-3LCWFNVS.js.map +1 -0
- package/dist/chunk-43EKP2UK.js +26 -0
- package/dist/chunk-43EKP2UK.js.map +1 -0
- package/dist/chunk-457A4P3L.js +119 -0
- package/dist/chunk-457A4P3L.js.map +1 -0
- package/dist/{chunk-KUB6JU6H.js → chunk-47WOM4YW.js} +2 -2
- package/dist/{chunk-HK3FGIEW.js → chunk-4PLGJRBV.js} +656 -20
- package/dist/chunk-4PLGJRBV.js.map +1 -0
- package/dist/{chunk-BGJGXLZ7.js → chunk-55FXRRSJ.js} +11 -8
- package/dist/chunk-55FXRRSJ.js.map +1 -0
- package/dist/{chunk-ULYOGL6R.js → chunk-5HRY2WRF.js} +7 -3
- package/dist/chunk-5HRY2WRF.js.map +1 -0
- package/dist/chunk-6TBWYBJ3.js +236 -0
- package/dist/chunk-6TBWYBJ3.js.map +1 -0
- package/dist/chunk-74EMIVE4.js +329 -0
- package/dist/chunk-74EMIVE4.js.map +1 -0
- package/dist/chunk-74WWN7ZW.js +82 -0
- package/dist/chunk-74WWN7ZW.js.map +1 -0
- package/dist/{chunk-B5WXLVDY.js → chunk-7GCMLT7J.js} +245 -25
- package/dist/chunk-7GCMLT7J.js.map +1 -0
- package/dist/chunk-A6XUJE5D.js +126 -0
- package/dist/chunk-A6XUJE5D.js.map +1 -0
- package/dist/chunk-AJA46VX5.js +393 -0
- package/dist/chunk-AJA46VX5.js.map +1 -0
- package/dist/{chunk-DFTTJYSO.js → chunk-AKUCB2OG.js} +525 -24
- package/dist/chunk-AKUCB2OG.js.map +1 -0
- package/dist/chunk-ASIQZXYO.js +277 -0
- package/dist/chunk-ASIQZXYO.js.map +1 -0
- package/dist/{chunk-ZEM3OK2K.js → chunk-B2TL6GA2.js} +3 -3
- package/dist/chunk-BJMBJZ2Y.js +290 -0
- package/dist/chunk-BJMBJZ2Y.js.map +1 -0
- package/dist/chunk-BT7NVCML.js +79 -0
- package/dist/chunk-BT7NVCML.js.map +1 -0
- package/dist/chunk-CK5NTM2S.js +454 -0
- package/dist/chunk-CK5NTM2S.js.map +1 -0
- package/dist/{chunk-3GXCSUXR.js → chunk-CRU27Q4J.js} +2 -2
- package/dist/{chunk-F5VP6YCB.js → chunk-DCE6SQLA.js} +572 -155
- package/dist/chunk-DCE6SQLA.js.map +1 -0
- package/dist/{chunk-CUPFXL3J.js → chunk-DHRQHX36.js} +4 -4
- package/dist/chunk-DHRQHX36.js.map +1 -0
- package/dist/{chunk-GKFXUTJ2.js → chunk-DR7MCMPS.js} +981 -61
- package/dist/chunk-DR7MCMPS.js.map +1 -0
- package/dist/chunk-FP2373TW.js +149 -0
- package/dist/chunk-FP2373TW.js.map +1 -0
- package/dist/{chunk-RBBWYEFJ.js → chunk-G2WADRQ3.js} +1 -1
- package/dist/chunk-G7D6GZ5J.js +48 -0
- package/dist/chunk-G7D6GZ5J.js.map +1 -0
- package/dist/chunk-H7XKCNR6.js +60 -0
- package/dist/chunk-H7XKCNR6.js.map +1 -0
- package/dist/{chunk-VYM3VWOF.js → chunk-IM3JSE73.js} +966 -329
- package/dist/chunk-IM3JSE73.js.map +1 -0
- package/dist/chunk-IXEJRKCZ.js +18 -0
- package/dist/chunk-IXEJRKCZ.js.map +1 -0
- package/dist/chunk-IYY4MCPG.js +275 -0
- package/dist/chunk-IYY4MCPG.js.map +1 -0
- package/dist/{chunk-BK2EFTE2.js → chunk-JWSENLQI.js} +508 -28
- package/dist/chunk-JWSENLQI.js.map +1 -0
- package/dist/chunk-KNKUID7G.js +183 -0
- package/dist/chunk-KNKUID7G.js.map +1 -0
- package/dist/chunk-L2IO2QPY.js +2036 -0
- package/dist/chunk-L2IO2QPY.js.map +1 -0
- package/dist/{chunk-SPI27QT6.js → chunk-L5IIGA5V.js} +9 -4
- package/dist/chunk-L5IIGA5V.js.map +1 -0
- package/dist/{chunk-RGLL5SPU.js → chunk-LVYGDT5V.js} +56 -82
- package/dist/chunk-LVYGDT5V.js.map +1 -0
- package/dist/{chunk-ZAIM4TUE.js → chunk-LW2NMHDW.js} +46 -1
- package/dist/chunk-LW2NMHDW.js.map +1 -0
- package/dist/{chunk-3OGMS3PE.js → chunk-LZRYQK6L.js} +3 -2
- package/dist/chunk-LZRYQK6L.js.map +1 -0
- package/dist/chunk-MDYG7VI7.js +48 -0
- package/dist/chunk-MDYG7VI7.js.map +1 -0
- package/dist/chunk-MXC3AP5I.js +74 -0
- package/dist/chunk-MXC3AP5I.js.map +1 -0
- package/dist/{chunk-S3EEFKNY.js → chunk-N7X62G74.js} +26 -11
- package/dist/chunk-N7X62G74.js.map +1 -0
- package/dist/chunk-NN3TS5BM.js +147 -0
- package/dist/chunk-NN3TS5BM.js.map +1 -0
- package/dist/chunk-OA3L7BFR.js +183 -0
- package/dist/chunk-OA3L7BFR.js.map +1 -0
- package/dist/{chunk-LK6SGL53.js → chunk-OR64ZGRZ.js} +3 -2
- package/dist/chunk-OR64ZGRZ.js.map +1 -0
- package/dist/chunk-OZHRDTDX.js +240 -0
- package/dist/chunk-OZHRDTDX.js.map +1 -0
- package/dist/chunk-PCUKNJAZ.js +165 -0
- package/dist/chunk-PCUKNJAZ.js.map +1 -0
- package/dist/{chunk-6PFRXT4K.js → chunk-PFV5C235.js} +11 -6
- package/dist/chunk-PFV5C235.js.map +1 -0
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/{chunk-XZ2TIKGC.js → chunk-Q7FJ5ZHM.js} +30 -10
- package/dist/chunk-Q7FJ5ZHM.js.map +1 -0
- package/dist/{chunk-7I7FKFZH.js → chunk-R2L7SUX2.js} +6 -6
- package/dist/{chunk-JL2PU6AI.js → chunk-R2XRID2N.js} +2 -2
- package/dist/{chunk-WCLICCGB.js → chunk-RILIVK4O.js} +91 -4
- package/dist/chunk-RILIVK4O.js.map +1 -0
- package/dist/{chunk-C2EFFULQ.js → chunk-RK2Y4XOM.js} +163 -20
- package/dist/chunk-RK2Y4XOM.js.map +1 -0
- package/dist/{chunk-TP4FZJIZ.js → chunk-RULE4VG5.js} +5 -1
- package/dist/chunk-RULE4VG5.js.map +1 -0
- package/dist/{chunk-PVPWZSSI.js → chunk-SMA4IMHV.js} +19 -3
- package/dist/chunk-SMA4IMHV.js.map +1 -0
- package/dist/{chunk-WVVA7F5A.js → chunk-SS253RXF.js} +30 -16
- package/dist/chunk-SS253RXF.js.map +1 -0
- package/dist/chunk-TUFG6VXY.js +875 -0
- package/dist/chunk-TUFG6VXY.js.map +1 -0
- package/dist/chunk-TYEOAFH3.js +251 -0
- package/dist/chunk-TYEOAFH3.js.map +1 -0
- package/dist/chunk-UKJAGEXH.js +260 -0
- package/dist/chunk-UKJAGEXH.js.map +1 -0
- package/dist/{chunk-KVBLZUKV.js → chunk-USFPPRAF.js} +93 -3
- package/dist/chunk-USFPPRAF.js.map +1 -0
- package/dist/{chunk-EPQJM2GC.js → chunk-VTJVUHRK.js} +22 -36
- package/dist/chunk-VTJVUHRK.js.map +1 -0
- package/dist/{chunk-O5ETUNBT.js → chunk-VTU2B4VF.js} +7 -3
- package/dist/chunk-VTU2B4VF.js.map +1 -0
- package/dist/chunk-WIICJPET.js +45 -0
- package/dist/chunk-WIICJPET.js.map +1 -0
- package/dist/{chunk-VBVG2M5G.js → chunk-WPGJYVUH.js} +6 -2
- package/dist/chunk-WPGJYVUH.js.map +1 -0
- package/dist/{chunk-YNQKWQT4.js → chunk-WSZIHQBK.js} +31 -11
- package/dist/{chunk-YNQKWQT4.js.map → chunk-WSZIHQBK.js.map} +1 -1
- package/dist/{chunk-NZLQTHS5.js → chunk-WW3QQF4H.js} +4 -1
- package/dist/chunk-WW3QQF4H.js.map +1 -0
- package/dist/{chunk-FVA6TGI3.js → chunk-Y3WQ4ZWK.js} +42 -2
- package/dist/chunk-Y3WQ4ZWK.js.map +1 -0
- package/dist/chunk-YNJHCGDT.js +309 -0
- package/dist/chunk-YNJHCGDT.js.map +1 -0
- package/dist/{chunk-ALXMCZEU.js → chunk-Z2E7VW55.js} +6 -3
- package/dist/chunk-Z2E7VW55.js.map +1 -0
- package/dist/{chunk-INXV5JBT.js → chunk-ZGXSCMQN.js} +1992 -410
- package/dist/chunk-ZGXSCMQN.js.map +1 -0
- package/dist/{chunk-W6SL7OFG.js → chunk-ZTSE2ZJ6.js} +12 -2
- package/dist/{chunk-W6SL7OFG.js.map → chunk-ZTSE2ZJ6.js.map} +1 -1
- package/dist/chunking.js +1 -0
- package/dist/cipher-GVE2GQ5H.js +28 -0
- package/dist/cipher-GVE2GQ5H.js.map +1 -0
- package/dist/citations.js +1 -0
- package/dist/{cli-BkeRaYfk.d.ts → cli-x2APT9a6.d.ts} +26 -7
- package/dist/cli.d.ts +11 -6
- package/dist/cli.js +68 -34
- package/dist/codex-thread-key.js +1 -0
- package/dist/commitment-ledger.js +1 -0
- package/dist/compression-optimizer.js +1 -0
- package/dist/config.d.ts +2 -1
- package/dist/config.js +5 -2
- package/dist/connectors-cli-DFGtY2DB.d.ts +257 -0
- package/dist/connectors-cli.d.ts +2 -0
- package/dist/connectors-cli.js +22 -0
- package/dist/connectors-cli.js.map +1 -0
- package/dist/consolidation-operator.d.ts +65 -5
- package/dist/consolidation-operator.js +6 -1
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-provenance-check.js +3 -2
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/consolidation-undo.js +1 -0
- package/dist/consolidation-undo.js.map +1 -1
- package/dist/{contradiction-review-WIUBAR52.js → contradiction-review-5LTTVDQV.js} +2 -1
- package/dist/contradiction-review-5LTTVDQV.js.map +1 -0
- package/dist/{contradiction-scan-E3GJTI4F.js → contradiction-scan-3Z6YW7YA.js} +2 -1
- package/dist/{contradiction-scan-E3GJTI4F.js.map → contradiction-scan-3Z6YW7YA.js.map} +1 -1
- package/dist/cross-namespace-budget.js +1 -0
- package/dist/cue-anchors.js +1 -0
- package/dist/dashboard-runtime.js +1 -0
- package/dist/day-summary.js +1 -0
- package/dist/delinearize.js +1 -0
- package/dist/direct-answer-wiring.js +1 -0
- package/dist/direct-answer.js +1 -0
- package/dist/dreams-ledger-LR2NBAZE.js +286 -0
- package/dist/dreams-ledger-LR2NBAZE.js.map +1 -0
- package/dist/embedding-fallback.js +3 -1
- package/dist/{engine-F3GOXGE5.js → engine-ICC2DSQF.js} +10 -7
- package/dist/engine-ICC2DSQF.js.map +1 -0
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +9 -6
- package/dist/entity-schema.js +1 -0
- package/dist/evals.js +1 -0
- package/dist/evidence-pack.d.ts +16 -0
- package/dist/evidence-pack.js +8 -0
- package/dist/evidence-pack.js.map +1 -0
- package/dist/explicit-capture.d.ts +6 -4
- package/dist/explicit-capture.js +1 -0
- package/dist/extraction-judge-telemetry.js +1 -0
- package/dist/extraction-judge-training.js +1 -0
- package/dist/extraction-judge.js +1 -0
- package/dist/extraction.js +9 -8
- package/dist/fallback-llm.js +3 -2
- package/dist/first-start-migration-4MHQEOSD.js +263 -0
- package/dist/first-start-migration-4MHQEOSD.js.map +1 -0
- package/dist/forget-PLR6J5DN.js +69 -0
- package/dist/forget-PLR6J5DN.js.map +1 -0
- package/dist/framework-CyHYDcri.d.ts +153 -0
- package/dist/fs-utils-IRVUFB6G.js +30 -0
- package/dist/fs-utils-IRVUFB6G.js.map +1 -0
- package/dist/graph-dashboard-diff.js +1 -0
- package/dist/graph-dashboard-key.js +1 -0
- package/dist/graph-dashboard-parser.js +1 -0
- package/dist/graph-edge-decay-PWB63GRE.js +207 -0
- package/dist/graph-edge-decay-PWB63GRE.js.map +1 -0
- package/dist/graph-edge-reinforcement.d.ts +81 -0
- package/dist/graph-edge-reinforcement.js +24 -0
- package/dist/graph-edge-reinforcement.js.map +1 -0
- package/dist/graph-events.d.ts +87 -0
- package/dist/graph-events.js +14 -0
- package/dist/graph-events.js.map +1 -0
- package/dist/graph-recall.js +1 -0
- package/dist/graph-retrieval.js +1 -0
- package/dist/graph-snapshot.d.ts +112 -0
- package/dist/graph-snapshot.js +19 -0
- package/dist/graph-snapshot.js.map +1 -0
- package/dist/graph.d.ts +105 -7
- package/dist/graph.js +20 -3
- package/dist/harmonic-retrieval.js +1 -0
- package/dist/himem.js +1 -0
- package/dist/hygiene.js +1 -0
- package/dist/identity-continuity.js +1 -0
- package/dist/importance.js +1 -0
- package/dist/index.d.ts +562 -13
- package/dist/index.js +365 -96
- package/dist/index.js.map +1 -1
- package/dist/intent.js +1 -0
- package/dist/json-extract.js +1 -0
- package/dist/json-store.js +1 -0
- package/dist/kdf-7S6RWKLZ.js +26 -0
- package/dist/kdf-7S6RWKLZ.js.map +1 -0
- package/dist/legacy-hook-compat.js +1 -0
- package/dist/legacy-hook-compat.js.map +1 -1
- package/dist/lifecycle.js +1 -0
- package/dist/live-connectors-runner.d.ts +48 -0
- package/dist/live-connectors-runner.js +17 -0
- package/dist/live-connectors-runner.js.map +1 -0
- package/dist/local-llm.js +3 -2
- package/dist/logger.js +1 -0
- package/dist/memory-action-policy.js +1 -0
- package/dist/memory-cache.d.ts +2 -1
- package/dist/memory-cache.js +4 -1
- package/dist/memory-governance-KG52RITE.js +37 -0
- package/dist/memory-governance-KG52RITE.js.map +1 -0
- package/dist/memory-lifecycle-ledger-utils.d.ts +2 -1
- package/dist/memory-lifecycle-ledger-utils.js +4 -1
- package/dist/memory-projection-format.js +1 -0
- package/dist/{memory-projection-store-DeSXPh1j.d.ts → memory-projection-store-D3vBHS4J.d.ts} +1 -0
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-projection-store.js +1 -0
- package/dist/memory-worth-bench.js +1 -0
- package/dist/memory-worth-bench.js.map +1 -1
- package/dist/memory-worth-filter.js +1 -0
- package/dist/memory-worth-outcomes.d.ts +1 -1
- package/dist/memory-worth-outcomes.js +1 -0
- package/dist/memory-worth.js +1 -0
- package/dist/metadata-FC3XPDRQ.js +21 -0
- package/dist/metadata-FC3XPDRQ.js.map +1 -0
- package/dist/migrate-from-identity-anchor-TTEDEJGX.js +8 -0
- package/dist/migrate-from-identity-anchor-TTEDEJGX.js.map +1 -0
- package/dist/model-registry.js +1 -0
- package/dist/models-json.js +1 -0
- package/dist/native-knowledge.js +1 -0
- package/dist/negative.js +1 -0
- package/dist/objective-state-writers.js +1 -0
- package/dist/objective-state-writers.js.map +1 -1
- package/dist/objective-state.js +1 -0
- package/dist/openai-chat-compat.js +1 -0
- package/dist/operator-toolkit.d.ts +46 -2
- package/dist/operator-toolkit.js +29 -17
- package/dist/opik-exporter.js +1 -0
- package/dist/opik-exporter.js.map +1 -1
- package/dist/{orchestrator-CmJ-NTdJ.d.ts → orchestrator-ChkesB8U.d.ts} +177 -13
- package/dist/orchestrator.d.ts +6 -4
- package/dist/orchestrator.js +58 -42
- package/dist/page-versioning.js +1 -0
- package/dist/path-RMTY5Y5A.js +9 -0
- package/dist/path-RMTY5Y5A.js.map +1 -0
- package/dist/patterns-cli.d.ts +160 -0
- package/dist/patterns-cli.js +29 -0
- package/dist/patterns-cli.js.map +1 -0
- package/dist/peers-6OSQ3NK6.js +44 -0
- package/dist/peers-6OSQ3NK6.js.map +1 -0
- package/dist/plugin-id.js +1 -0
- package/dist/policy-runtime.js +1 -0
- package/dist/{port-BADbLZU5.d.ts → port-hqGnoStS.d.ts} +6 -0
- package/dist/profiling.js +1 -0
- package/dist/purge-6ATBGT77.js +205 -0
- package/dist/purge-6ATBGT77.js.map +1 -0
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd-recall-cache.js +1 -0
- package/dist/qmd.d.ts +2 -1
- package/dist/qmd.js +4 -3
- package/dist/reasoning-trace-recall.js +1 -0
- package/dist/reasoning-trace-types.js +1 -0
- package/dist/recall-audit-anomaly.js +1 -0
- package/dist/recall-audit.js +1 -0
- package/dist/recall-disclosure-escalation.d.ts +84 -0
- package/dist/recall-disclosure-escalation.js +14 -0
- package/dist/recall-disclosure-escalation.js.map +1 -0
- package/dist/recall-explain-renderer.js +4 -1
- package/dist/recall-mmr.js +1 -0
- package/dist/recall-qos.js +1 -0
- package/dist/recall-query-policy.js +1 -0
- package/dist/recall-state.d.ts +7 -0
- package/dist/recall-state.js +2 -1
- package/dist/recall-tag-filter.d.ts +56 -0
- package/dist/recall-tag-filter.js +14 -0
- package/dist/recall-tag-filter.js.map +1 -0
- package/dist/recall-tokenization.js +1 -0
- package/dist/recall-xray-cli.d.ts +9 -2
- package/dist/recall-xray-cli.js +9 -4
- package/dist/recall-xray-renderer.js +4 -1
- package/dist/recall-xray.d.ts +116 -2
- package/dist/recall-xray.js +9 -3
- package/dist/reconstruct.js +1 -0
- package/dist/release-changelog.js +2 -0
- package/dist/release-changelog.js.map +1 -1
- package/dist/relevance.js +1 -0
- package/dist/rerank.js +1 -0
- package/dist/{resolution-QBTDHTG7.js → resolution-YGIBORXI.js} +2 -1
- package/dist/{resolution-QBTDHTG7.js.map → resolution-YGIBORXI.js.map} +1 -1
- package/dist/resolve-auth-token.d.ts +51 -0
- package/dist/resolve-auth-token.js +12 -0
- package/dist/resolve-auth-token.js.map +1 -0
- package/dist/resolve-provider-secret.d.ts +13 -1
- package/dist/resolve-provider-secret.js +6 -1
- package/dist/resume-bundles.js +5 -4
- package/dist/retrieval-agents.d.ts +1 -1
- package/dist/retrieval-agents.js +1 -0
- package/dist/retrieval-tiers.js +1 -0
- package/dist/retrieval.js +1 -0
- package/dist/sanitize.js +1 -0
- package/dist/schemas.d.ts +15 -2
- package/dist/schemas.js +2 -1
- package/dist/sdk-compat.js +1 -0
- package/dist/sdk-compat.js.map +1 -1
- package/dist/secure-store-4R2GSO7S.js +156 -0
- package/dist/secure-store-4R2GSO7S.js.map +1 -0
- package/dist/semantic-chunking.js +1 -0
- package/dist/{semantic-consolidation-CxJU6MJk.d.ts → semantic-consolidation-ByBXb-sf.d.ts} +3 -3
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +12 -6
- package/dist/semantic-rule-promotion.d.ts +1 -1
- package/dist/semantic-rule-promotion.js +9 -6
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +9 -6
- package/dist/session-integrity.js +1 -0
- package/dist/session-observer-bands.js +1 -0
- package/dist/session-observer-state.js +1 -0
- package/dist/session-toggles.js +2 -0
- package/dist/session-toggles.js.map +1 -1
- package/dist/signal.js +1 -0
- package/dist/skills-registry.js +2 -0
- package/dist/skills-registry.js.map +1 -1
- package/dist/source-attribution.js +1 -0
- package/dist/state-NCHQ4TRG.js +8 -0
- package/dist/state-NCHQ4TRG.js.map +1 -0
- package/dist/state-store-3EH7HYIN.js +16 -0
- package/dist/state-store-3EH7HYIN.js.map +1 -0
- package/dist/storage.d.ts +76 -2
- package/dist/storage.js +8 -5
- package/dist/store-contract.js +1 -0
- package/dist/summarizer.js +6 -5
- package/dist/summary-snapshot.js +1 -0
- package/dist/temporal-index.js +1 -0
- package/dist/temporal-supersession.d.ts +1 -1
- package/dist/temporal-supersession.js +2 -1
- package/dist/temporal-validity.d.ts +52 -0
- package/dist/temporal-validity.js +14 -0
- package/dist/temporal-validity.js.map +1 -0
- package/dist/threading.js +1 -0
- package/dist/tier-migration.d.ts +2 -2
- package/dist/tier-migration.js +1 -0
- package/dist/tier-routing.js +1 -0
- package/dist/tier-stats-62ZVDFKS.js +152 -0
- package/dist/tier-stats-62ZVDFKS.js.map +1 -0
- package/dist/tmt.js +1 -0
- package/dist/tokens.js +3 -1
- package/dist/topics.js +1 -0
- package/dist/trace-C5ETWBEF.js +290 -0
- package/dist/trace-C5ETWBEF.js.map +1 -0
- package/dist/transcript.js +1 -0
- package/dist/trust-zones.js +1 -0
- package/dist/tui-RI7P6PBS.js +13 -0
- package/dist/tui-RI7P6PBS.js.map +1 -0
- package/dist/types-V3FJ26TF.js +30 -0
- package/dist/types-V3FJ26TF.js.map +1 -0
- package/dist/types.d.ts +634 -9
- package/dist/types.js +10 -3
- package/dist/utility-learner.js +1 -0
- package/dist/utility-runtime.js +1 -0
- package/dist/utility-telemetry.js +1 -0
- package/dist/verified-recall.js +9 -6
- package/dist/version-utils.js +1 -0
- package/dist/whitespace.js +1 -0
- package/dist/work-product-ledger.js +1 -0
- package/package.json +2 -1
- package/dist/access-service-Br8ZydTK.d.ts +0 -827
- package/dist/chunk-3OGMS3PE.js.map +0 -1
- package/dist/chunk-6PFRXT4K.js.map +0 -1
- package/dist/chunk-ALXMCZEU.js.map +0 -1
- package/dist/chunk-B5WXLVDY.js.map +0 -1
- package/dist/chunk-BGJGXLZ7.js.map +0 -1
- package/dist/chunk-BK2EFTE2.js.map +0 -1
- package/dist/chunk-C2EFFULQ.js.map +0 -1
- package/dist/chunk-CUPFXL3J.js.map +0 -1
- package/dist/chunk-DFTTJYSO.js.map +0 -1
- package/dist/chunk-EPQJM2GC.js.map +0 -1
- package/dist/chunk-F5VP6YCB.js.map +0 -1
- package/dist/chunk-FVA6TGI3.js.map +0 -1
- package/dist/chunk-GKFXUTJ2.js.map +0 -1
- package/dist/chunk-HK3FGIEW.js.map +0 -1
- package/dist/chunk-INXV5JBT.js.map +0 -1
- package/dist/chunk-KVBLZUKV.js.map +0 -1
- package/dist/chunk-LK6SGL53.js.map +0 -1
- package/dist/chunk-LTCGGW2D.js +0 -14
- package/dist/chunk-LTCGGW2D.js.map +0 -1
- package/dist/chunk-NZLQTHS5.js.map +0 -1
- package/dist/chunk-O5ETUNBT.js.map +0 -1
- package/dist/chunk-PVPWZSSI.js.map +0 -1
- package/dist/chunk-RGLL5SPU.js.map +0 -1
- package/dist/chunk-S3EEFKNY.js.map +0 -1
- package/dist/chunk-SPI27QT6.js.map +0 -1
- package/dist/chunk-TP4FZJIZ.js.map +0 -1
- package/dist/chunk-ULYOGL6R.js.map +0 -1
- package/dist/chunk-VBVG2M5G.js.map +0 -1
- package/dist/chunk-VDX363PS.js.map +0 -1
- package/dist/chunk-VYM3VWOF.js.map +0 -1
- package/dist/chunk-WCLICCGB.js.map +0 -1
- package/dist/chunk-WVVA7F5A.js.map +0 -1
- package/dist/chunk-X6GF3FX2.js +0 -26
- package/dist/chunk-X6GF3FX2.js.map +0 -1
- package/dist/chunk-XZ2TIKGC.js.map +0 -1
- package/dist/chunk-ZAIM4TUE.js.map +0 -1
- /package/dist/{contradiction-review-WIUBAR52.js.map → capsule-cli.js.map} +0 -0
- /package/dist/{engine-F3GOXGE5.js.map → capsule-crypto-5CYAGVC5.js.map} +0 -0
- /package/dist/{chunk-KUB6JU6H.js.map → chunk-47WOM4YW.js.map} +0 -0
- /package/dist/{chunk-ZEM3OK2K.js.map → chunk-B2TL6GA2.js.map} +0 -0
- /package/dist/{chunk-3GXCSUXR.js.map → chunk-CRU27Q4J.js.map} +0 -0
- /package/dist/{chunk-RBBWYEFJ.js.map → chunk-G2WADRQ3.js.map} +0 -0
- /package/dist/{chunk-7I7FKFZH.js.map → chunk-R2L7SUX2.js.map} +0 -0
- /package/dist/{chunk-JL2PU6AI.js.map → chunk-R2XRID2N.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/transfer/capsule-import.ts"],"sourcesContent":["import { lstat, mkdir, readFile, realpath, stat, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { createHash, randomUUID } from \"node:crypto\";\nimport { gunzipSync } from \"node:zlib\";\nimport {\n createVersion,\n type VersioningConfig,\n type VersioningLogger,\n} from \"../page-versioning.js\";\nimport {\n assertIsDirectoryNotSymlink,\n assertRealpathInsideRoot,\n fromPosixRelPath,\n isPathInsideRoot,\n sha256String,\n} from \"./fs-utils.js\";\nimport {\n parseExportBundle,\n type CapsuleBlock,\n type ExportManifestV2,\n type ExportMemoryRecordV1,\n} from \"./types.js\";\nimport { isEncryptedCapsuleFile, decryptCapsuleFileInMemory } from \"./capsule-crypto.js\";\n\n/**\n * Conflict-resolution mode for {@link importCapsule}. Inverse of the\n * `mode` selector in PR 4/6's CLI surface.\n *\n * - `\"skip\"` (default) — when a target file already exists at the same\n * relative path, leave it untouched. The corresponding record is reported\n * via {@link ImportCapsuleResult.skipped}.\n * - `\"overwrite\"` — when a target file exists, snapshot the prior content\n * via {@link createVersion} (gotcha #54: write-before-delete; gotcha #25:\n * don't destroy old state until new state is confirmed), then write the\n * incoming content. The snapshot's `note` includes the source capsule id.\n * - `\"fork\"` — never overwrite. Every record is rebased under\n * `forks/<capsule-id>/<original-path>` and any YAML-frontmatter `id:`\n * field is rewritten to a new fork-scoped value. The original tree is\n * not touched.\n *\n * The mode is selected once per call and applied uniformly to every record\n * in the bundle. Mixed-mode imports are not supported by design — that\n * would let a single corrupted manifest silently land partial state.\n */\nexport type ImportCapsuleMode = \"skip\" | \"overwrite\" | \"fork\";\n\n/**\n * Options accepted by {@link importCapsule}.\n *\n * `archivePath` — absolute or cwd-relative path to a `.capsule.json.gz`\n * archive produced by `exportCapsule`. The archive must contain a V2\n * bundle (`schemaVersion: 2`); V1 archives are rejected.\n *\n * `root` — absolute or cwd-relative path to the memory directory that\n * will receive the records. Must be an existing directory.\n *\n * `mode` — see {@link ImportCapsuleMode}. Defaults to `\"skip\"`.\n *\n * `versioning` — optional page-versioning config used by `mode: \"overwrite\"`\n * to snapshot prior content before replacing it. Snapshots are skipped when\n * not provided or when `enabled === false`. Tests pass an enabled config\n * to assert snapshot creation; production callers thread the\n * orchestrator's resolved versioning config.\n *\n * `log` — optional logger forwarded to {@link createVersion}.\n *\n * `now` — optional clock override (ms epoch) used to derive deterministic\n * fork ids in tests. Production callers omit this.\n */\nexport interface ImportCapsuleOptions {\n archivePath: string;\n root: string;\n mode?: ImportCapsuleMode;\n versioning?: VersioningConfig;\n log?: VersioningLogger;\n now?: number;\n /**\n * Memory directory whose secure-store keyring is used when the archive is\n * encrypted. Required when `archivePath` ends with `.enc` or when the file\n * starts with the REMNIC-ENC magic header. If `memoryDir` is omitted and the\n * archive turns out to be encrypted, `importCapsule` throws a clear error\n * rather than silently failing.\n *\n * When provided and the archive is NOT encrypted, the value is ignored.\n */\n memoryDir?: string;\n}\n\nexport interface ImportCapsuleSkippedRecord {\n /** Original record path (capsule-relative, posix). */\n path: string;\n /**\n * Why this record was not written.\n *\n * `\"exists\"` — the target path already existed and the mode was `\"skip\"`,\n * OR the computed fork path already existed in fork mode.\n *\n * `\"checksum_mismatch\"` is intentionally absent: a checksum failure aborts\n * the import entirely (fail-closed) rather than skipping the offending\n * record, so no `ImportCapsuleSkippedRecord` is ever produced for it.\n */\n reason: \"exists\";\n}\n\nexport interface ImportCapsuleImportedRecord {\n /** Capsule-relative posix path the record carried. */\n sourcePath: string;\n /** Memory-dir-relative posix path the file was written to. */\n targetPath: string;\n /** Whether a prior version snapshot was taken (overwrite-mode only). */\n snapshotted: boolean;\n /** Whether the frontmatter `id:` field was rewritten (fork-mode only). */\n rewroteId: boolean;\n}\n\nexport interface ImportCapsuleResult {\n /** Records that landed on disk. */\n imported: ImportCapsuleImportedRecord[];\n /** Records that were not written. */\n skipped: ImportCapsuleSkippedRecord[];\n /** The manifest decoded from the archive. */\n manifest: ExportManifestV2;\n}\n\n/**\n * Pure async function that imports a capsule archive into a memory\n * directory. Inverse of {@link import(\"./capsule-export.js\").exportCapsule}.\n *\n * Sequence:\n * 1. Read + gunzip + JSON.parse the archive.\n * 2. Validate the bundle through `parseExportBundle` (V1 rejected).\n * 3. Verify each record's content sha256 against the manifest entry.\n * Any mismatch aborts the import BEFORE any file is written —\n * partial-write recovery would require a full rollback we cannot\n * offer cheaply, so we fail closed (gotcha #25).\n * 4. Apply the selected {@link ImportCapsuleMode} to every record.\n *\n * Determinism guarantees:\n * - Imported records are returned sorted by `sourcePath`.\n * - Skipped records are returned sorted by `path`.\n * - Fork-mode rebases under a stable `forks/<capsule-id>/` prefix so\n * repeated fork imports of the same capsule shape land in predictable\n * locations (subsequent fork imports still skip-on-exist because the\n * target tree is computed from the source path).\n */\nexport async function importCapsule(\n opts: ImportCapsuleOptions,\n): Promise<ImportCapsuleResult> {\n const archiveAbs = path.resolve(opts.archivePath);\n const rootAbs = path.resolve(opts.root);\n await assertIsDirectoryNotSymlink(rootAbs, \"importCapsule\", \"root\");\n\n const mode: ImportCapsuleMode = opts.mode ?? \"skip\";\n // Reject unknown mode values up-front (rule 51). TypeScript callers get a\n // compile-time check via the union type; JS/CLI callers get a runtime error\n // here before any write begins — not a silent destructive fallback.\n if (mode !== \"skip\" && mode !== \"overwrite\" && mode !== \"fork\") {\n throw new Error(\n `importCapsule: unknown mode ${JSON.stringify(mode)}; expected \"skip\", \"overwrite\", or \"fork\"`,\n );\n }\n\n // Auto-detect encrypted archives. If the file ends with `.enc` or starts\n // with the REMNIC-ENC magic header, decrypt it first before gunzip+parse.\n // Rule 48: default is \"not encrypted\"; we only attempt decryption when the\n // header is definitively present, never on ambiguous content.\n const encrypted = await isEncryptedCapsuleFile(archiveAbs);\n let raw: Buffer;\n if (encrypted) {\n if (!opts.memoryDir) {\n throw new Error(\n `importCapsule: archive is encrypted but 'memoryDir' was not provided. ` +\n `Pass the memory directory so the secure-store key can be retrieved, ` +\n `or run \\`remnic secure-store unlock\\` before importing.`,\n );\n }\n raw = await decryptCapsuleFileInMemory(archiveAbs, opts.memoryDir);\n } else {\n raw = await readFile(archiveAbs);\n }\n const json = gunzipSync(raw).toString(\"utf-8\");\n let parsedJson: unknown;\n try {\n parsedJson = JSON.parse(json);\n } catch (cause) {\n throw new Error(\n `importCapsule: archive is not valid JSON after gunzip: ${archiveAbs}`,\n { cause: cause as Error },\n );\n }\n\n const parsed = parseExportBundle(parsedJson);\n if (parsed.capsuleVersion !== 2) {\n // PR 1/6 ships a V1 reader; capsule import is V2-only by design. A V1\n // bundle does not carry a capsule block, so fork-mode (which depends on\n // `capsule.id` for the rebase prefix) cannot work. Reject up-front per\n // gotcha #51 instead of silently routing to a different code path.\n throw new Error(\n \"importCapsule: archive is V1; only V2 capsule archives are supported\",\n );\n }\n const bundle = parsed.bundle as { manifest: ExportManifestV2; records: ExportMemoryRecordV1[] };\n const manifest = bundle.manifest;\n const capsule = manifest.capsule;\n\n // Build a path → manifest-entry index for O(1) checksum lookup. The\n // manifest is the source of truth; records-without-manifest-entries and\n // manifest-entries-without-records are both treated as corruption.\n const manifestIndex = new Map<string, ExportManifestV2[\"files\"][number]>();\n for (const f of manifest.files) {\n manifestIndex.set(f.path, f);\n }\n if (manifestIndex.size !== manifest.files.length) {\n throw new Error(\n \"importCapsule: manifest contains duplicate file paths\",\n );\n }\n const recordPaths = new Set<string>();\n for (const rec of bundle.records) {\n if (recordPaths.has(rec.path)) {\n throw new Error(\n `importCapsule: bundle contains duplicate record path: ${rec.path}`,\n );\n }\n recordPaths.add(rec.path);\n }\n\n // Phase 1: verify every record matches its manifest entry AND validate all\n // target paths before any filesystem mutation. We do both in a single pass\n // so a corrupted or malicious archive cannot leave the memory dir partially\n // written (fail-closed per gotcha #25).\n //\n // The real root is resolved once via realpath so the subsequent per-record\n // inside-root checks are symlink-aware: a record path like `facts/a.md`\n // cannot write outside the intended sandbox via a symlinked subdirectory\n // (Codex P1 feedback). If realpath fails (root does not exist) the earlier\n // assertIsDirectoryNotSymlink call already threw, so this should always succeed.\n const rootReal = await realpath(rootAbs).catch(() => rootAbs);\n\n // Tracks normalized, case-folded target paths seen so far in phase 1. Maps\n // targetAbs.toLowerCase() → first source path so the collision error can name\n // both offending entries. Two manifest entries whose computed target paths\n // normalize to the same absolute path (e.g. `subdir/file.md` and\n // `subdir/./file.md`, or differing case on case-insensitive filesystems such\n // as macOS and Windows) would both try to write the same inode — the second\n // would silently overwrite the first. We reject the import up-front before\n // any write (Codex P2 thread on PR #741, line 283).\n const seenTargetPaths = new Map<string, string>();\n\n for (const rec of bundle.records) {\n // Checksum validation.\n const entry = manifestIndex.get(rec.path);\n if (!entry) {\n throw new Error(\n `importCapsule: archive checksum mismatch (record without manifest entry: ${rec.path})`,\n );\n }\n const { sha256, bytes } = sha256String(rec.content);\n if (sha256 !== entry.sha256 || bytes !== entry.bytes) {\n throw new Error(\n `importCapsule: archive checksum mismatch for ${rec.path}: ` +\n `expected sha256=${entry.sha256} bytes=${entry.bytes}, ` +\n `got sha256=${sha256} bytes=${bytes}`,\n );\n }\n\n // Source path validation: reject any record whose posix source path\n // contains a `..` segment, an absolute prefix, or any component that would\n // let fork-mode bypass its isolation invariant. This check runs before\n // `computeTargetPath` so that fork mode's `forks/<id>/` rebase cannot be\n // bypassed by a path like `../../profile.md` (which `path.join` would\n // silently collapse, landing the file inside root but outside the fork\n // prefix — Cursor medium thread #741). Rejecting `..` up-front is simpler\n // and more robust than normalising after the fact.\n //\n // Extended guard (Codex P1 #741 round 5): also reject paths containing\n // backslash separators (`\\`), which are Windows path separators. On Windows\n // a path like `..\\\\escape.md` or `subdir\\\\..\\\\..\\\\escape.md` would resolve\n // to a parent traversal but would bypass a purely posix-split check.\n // Additionally, after posix-normalizing the path we re-check for a leading\n // `..` or `/` — this catches edge-cases such as `subdir/../..` where the\n // individual split segments are not `..` but the normalized result is.\n if (rec.path.includes(\"\\\\\")) {\n throw new Error(\n `importCapsule: record path contains backslash separators (Windows-style paths are not allowed): ${rec.path}`,\n );\n }\n const posixNormalized = path.posix.normalize(rec.path);\n if (\n rec.path.startsWith(\"/\") ||\n rec.path.split(\"/\").some((seg) => seg === \"..\") ||\n posixNormalized.startsWith(\"..\") ||\n posixNormalized.startsWith(\"/\")\n ) {\n throw new Error(\n `importCapsule: record path escapes target root: ${rec.path}`,\n );\n }\n\n // Path-traversal validation (moved here from phase 2 per Cursor feedback:\n // the traversal check must run before ANY write so a malicious archive\n // with an escaping path that sorts last cannot land partial writes).\n //\n // We build targetAbs relative to the real-resolved root (rootReal) so\n // that the inside-root check is symlink-aware: if `rootReal !== rootAbs`\n // (the import root is itself behind a symlink), we still compare against\n // the canonical path (Codex P1).\n const targetRel = computeTargetPath(rec.path, mode, capsule.id);\n const targetAbs = path.join(rootReal, fromPosixRelPath(targetRel));\n if (!isPathInsideRoot(rootReal, targetAbs)) {\n throw new Error(\n `importCapsule: record path escapes target root: ${rec.path}`,\n );\n }\n\n // Symlink-aware containment check (Cursor thread #741 line 247/264).\n // The lexical check above handles `..`-traversal but cannot detect\n // symlinked subdirectories that point outside the root. We resolve the\n // nearest existing ancestor of the target via realpath to catch symlinks\n // anywhere in the path components. If any resolved prefix escapes rootReal,\n // the import is rejected before any write. This applies to ALL modes,\n // including fork mode whose rebase prefix may itself traverse a symlink.\n await assertRealpathInsideRoot(rootReal, targetAbs, rec.path, \"importCapsule\");\n\n // Target-file symlink check (Codex P1 #741 round 4, line 260).\n // `assertRealpathInsideRoot` resolves the nearest existing *ancestor* to\n // catch symlinked parent directories, but if the TARGET FILE itself already\n // exists and is a symlink, that check passes (the parent is real) while the\n // write would silently follow the symlink to a path outside root. We\n // therefore lstat the target directly: if it exists and is a symlink we\n // reject the record up-front. For new files the parent-canonicalization\n // performed above is sufficient; no realpath of a non-existent file is\n // needed.\n const targetLstat = await lstat(targetAbs).catch(() => null);\n if (targetLstat !== null && targetLstat.isSymbolicLink()) {\n throw new Error(\n `importCapsule: record target is a symlink and cannot be written to safely: ${rec.path}`,\n );\n }\n\n // Duplicate normalized target path detection (Codex P2 #741, line 283).\n // `path.join` already normalises `.` segments (e.g. `subdir/./file.md` →\n // `subdir/file.md`). On case-insensitive filesystems (macOS default,\n // Windows), two paths that differ only in case would resolve to the same\n // inode. We fold the dedup key to lowercase so that `subdir/File.md` and\n // `subdir/file.md` are detected as duplicates before any write occurs.\n // This is intentionally unconditional: the cost of an extra `.toLowerCase()`\n // on case-sensitive filesystems is negligible, and a defensive lowercase\n // is far simpler than probing filesystem case-sensitivity at runtime.\n const dedupKey = targetAbs.toLowerCase();\n const firstSourcePath = seenTargetPaths.get(dedupKey);\n if (firstSourcePath !== undefined) {\n throw new Error(\n `importCapsule: manifest contains two entries that resolve to the same target path: ` +\n `\"${firstSourcePath}\" and \"${rec.path}\" both map to \"${targetRel}\"`,\n );\n }\n seenTargetPaths.set(dedupKey, rec.path);\n }\n // Detect manifest-only entries (missing record). Treat as corruption.\n for (const f of manifest.files) {\n if (!recordPaths.has(f.path)) {\n throw new Error(\n `importCapsule: archive checksum mismatch (manifest entry without record: ${f.path})`,\n );\n }\n }\n\n // Phase 2: apply the mode. All records were validated in phase 1; we now\n // write to disk. Per-record errors propagate; each individual write is\n // atomic (mkdir + writeFile pair).\n const imported: ImportCapsuleImportedRecord[] = [];\n const skipped: ImportCapsuleSkippedRecord[] = [];\n\n // Sort by source path so the imported/skipped lists are deterministic\n // regardless of bundle order. (`exportCapsule` already sorts; we re-sort\n // defensively in case a hand-edited archive ships records in another order.)\n const sortedRecords = [...bundle.records].sort((a, b) =>\n a.path.localeCompare(b.path),\n );\n\n for (const rec of sortedRecords) {\n const targetRel = computeTargetPath(rec.path, mode, capsule.id);\n // Use rootReal (realpath-resolved) to stay consistent with phase 1's\n // traversal validation and avoid writing through stale symlinks.\n const targetAbs = path.join(rootReal, fromPosixRelPath(targetRel));\n\n const exists = await fileExistsAt(targetAbs);\n\n // Skip logic applies to both `skip` mode (explicit) and `fork` mode\n // (Cursor medium: fork mode must also be skip-on-exist for the computed\n // fork path, because production imports generate non-deterministic IDs —\n // so re-importing the same capsule should not overwrite user edits in the\n // fork tree or silently change file identities by generating new IDs).\n if ((mode === \"skip\" || mode === \"fork\") && exists) {\n skipped.push({ path: rec.path, reason: \"exists\" });\n continue;\n }\n\n let snapshotted = false;\n if (mode === \"overwrite\" && exists) {\n // Gotcha #54: snapshot BEFORE overwriting. If snapshot creation fails\n // we abort this record's import rather than silently destroying\n // history. The page-versioning module is responsible for atomic\n // sidecar writes; we just call it.\n if (opts.versioning && opts.versioning.enabled) {\n const prior = await readFile(targetAbs, \"utf-8\").catch(() => \"\");\n await createVersion(\n targetAbs,\n prior,\n \"manual\",\n opts.versioning,\n opts.log,\n `capsule-import: ${capsule.id}`,\n rootReal,\n );\n snapshotted = true;\n }\n }\n\n let contentToWrite = rec.content;\n let rewroteId = false;\n if (mode === \"fork\") {\n const forked = rewriteFrontmatterIdForFork(rec.content, capsule.id, opts.now);\n contentToWrite = forked.content;\n rewroteId = forked.rewrote;\n }\n\n await mkdir(path.dirname(targetAbs), { recursive: true });\n await writeFile(targetAbs, contentToWrite, \"utf-8\");\n imported.push({\n sourcePath: rec.path,\n targetPath: targetRel,\n snapshotted,\n rewroteId,\n });\n }\n\n // Sort skipped for stable output (see comment above).\n skipped.sort((a, b) => a.path.localeCompare(b.path));\n\n return { imported, skipped, manifest };\n}\n\n// ---------------------------------------------------------------------------\n// Path helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Compute the destination relative path for a record under the chosen\n * mode.\n *\n * - `skip` / `overwrite` — identity: the record's posix path is used as-is.\n * - `fork` — rebased under `forks/<capsule-id>/<original-path>` so the\n * original tree is never modified.\n */\nfunction computeTargetPath(\n sourcePosix: string,\n mode: ImportCapsuleMode,\n capsuleId: string,\n): string {\n if (mode === \"fork\") return `forks/${capsuleId}/${sourcePosix}`;\n return sourcePosix;\n}\n\nasync function fileExistsAt(absPath: string): Promise<boolean> {\n const st = await stat(absPath).catch(() => null);\n return st !== null && st.isFile();\n}\n\n// ---------------------------------------------------------------------------\n// Fork-mode id rewriting\n// ---------------------------------------------------------------------------\n\n/**\n * Rewrite a YAML-frontmatter `id:` field to a fork-scoped value.\n *\n * Strategy: we deliberately do NOT parse the entire YAML — memory files\n * use a deterministic top-of-file frontmatter delimited by `---` lines,\n * and the orchestrator's `parseFrontmatter` reads `id` as a top-level\n * key with a single-line scalar value. We mirror that contract:\n *\n * - Detect a leading `---\\n` … `\\n---` block at the top of the file.\n * - Within that block, locate the first `id:` line that is not nested\n * under another key (cheap heuristic: line starts at column 0).\n * - Replace its value with a new fork-id derived from `capsuleId` and\n * a short random suffix (or {@link opts.now} when provided for tests).\n *\n * Files without a frontmatter block, or without an `id:` key inside one,\n * are returned unchanged with `rewrote: false`. This keeps non-memory\n * artifacts (READMEs, transcripts) byte-identical to the source.\n */\nfunction rewriteFrontmatterIdForFork(\n content: string,\n capsuleId: string,\n now: number | undefined,\n): { content: string; rewrote: boolean } {\n // Detect the dominant line ending in the file so we can preserve it when\n // rebuilding the frontmatter delimiters. Splitting/joining on `\\n` silently\n // drops `\\r` from CRLF files, changing the byte content (Cursor thread #741\n // line 467). We detect the ending of the very first line (the opening `---`)\n // and use that as the canonical separator for the reconstructed delimiters.\n // All three cases are covered: CRLF (\\r\\n), LF (\\n), and bare CR (\\r).\n const crlfMatch = /^---(\\r\\n|\\r|\\n)/.exec(content);\n // Fall back to LF if the file starts with `---` but has no newline (EOF).\n const eol = crlfMatch ? crlfMatch[1] : \"\\n\";\n\n // Frontmatter block detection: `---` on its own line at the very start,\n // followed by content, followed by `---` on its own line. The closing\n // delimiter must be at column 0. We capture the trailing terminator\n // (`\\r?\\n` or end-of-string) as a separate group so we can re-emit it\n // verbatim, preserving CRLF/LF/EOF shape from the original file.\n const fmMatch = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---(\\r?\\n|$)/.exec(content);\n if (!fmMatch) return { content, rewrote: false };\n const fmBody = fmMatch[1];\n const fmTrailer = fmMatch[2];\n const fmStart = fmMatch.index;\n const fmEnd = fmStart + fmMatch[0].length;\n\n // Find a top-level `id:` line. Top-level means the line begins at\n // column 0 inside the frontmatter body (no leading whitespace) and the\n // key is exactly `id`. We deliberately accept `id:` and `id : ` shapes\n // but reject `nested_id:` to avoid touching unrelated keys.\n const idLineRe = /^id:[ \\t]*([^\\r\\n]*)$/m;\n const idMatch = idLineRe.exec(fmBody);\n if (!idMatch) return { content, rewrote: false };\n\n const newId = mintForkId(capsuleId, idMatch[1]?.trim() ?? \"\", now);\n const replacedBody = fmBody.replace(idLineRe, `id: ${newId}`);\n // Reconstruct the file: original prefix (always empty here, fmStart === 0\n // by the `^` anchor) + frontmatter delimiters around the rewritten body.\n // Use the detected `eol` for the opening/closing `---` lines so that CRLF\n // files remain CRLF and LF files remain LF, preserving byte-content fidelity\n // (Cursor thread #741 line 467). The captured `fmTrailer` (the newline\n // immediately after the closing `---`, or end-of-string) is re-emitted\n // verbatim so the byte immediately after the closing fence is unchanged.\n const prefix = content.slice(0, fmStart);\n const tail = content.slice(fmEnd);\n const rebuilt = `${prefix}---${eol}${replacedBody}${eol}---${fmTrailer}${tail}`;\n return { content: rebuilt, rewrote: true };\n}\n\n/**\n * Derive a deterministic-when-`now`-is-set fork id from the capsule id and\n * the original record id. Production callers omit `now` and get a UUID\n * suffix; tests pass `now` for byte-stable assertions.\n */\nfunction mintForkId(capsuleId: string, originalId: string, now: number | undefined): string {\n const base = originalId.length > 0 ? originalId : \"fork\";\n if (now === undefined) {\n const suffix = randomUUID().slice(0, 8);\n return `${base}-fork-${capsuleId}-${suffix}`;\n }\n // Deterministic suffix: short hash of (capsuleId, originalId, now). Using\n // sha256 over a sorted-key serialization (gotcha #38) so the suffix does\n // not depend on Object.entries order.\n const payload = JSON.stringify({ c: capsuleId, i: originalId, n: now });\n const suffix = createHash(\"sha256\").update(payload, \"utf-8\").digest(\"hex\").slice(0, 8);\n return `${base}-fork-${capsuleId}-${suffix}`;\n}\n\n// Note: `CapsuleBlock` is re-exported here purely so callers that want to\n// inspect the manifest block don't have to deep-import from `./types.js`.\nexport type { CapsuleBlock };\n"],"mappings":"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","names":["suffix"]}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PEER_ID_PATTERN,
|
|
3
|
+
appendInteractionLog,
|
|
4
|
+
listPeers,
|
|
5
|
+
readPeerInteractionLog,
|
|
6
|
+
readPeerProfile,
|
|
7
|
+
writePeerProfile
|
|
8
|
+
} from "./chunk-TUFG6VXY.js";
|
|
9
|
+
|
|
10
|
+
// src/peers/profile-reasoner.ts
|
|
11
|
+
function buildPeerProfileReasonerPrompt(input) {
|
|
12
|
+
const existingFields = input.existingProfile ? Object.keys(input.existingProfile.fields) : [];
|
|
13
|
+
const logBlock = input.log.map((e) => {
|
|
14
|
+
const session = e.sessionId ? ` session=${e.sessionId}` : "";
|
|
15
|
+
return `- [${e.timestamp}] (${e.kind})${session} ${e.summary}`;
|
|
16
|
+
}).join("\n");
|
|
17
|
+
return [
|
|
18
|
+
`You are an async peer-profile reasoner. Your job is to read recent interaction-log entries for one peer and propose 0..${input.maxFields} profile-field updates.`,
|
|
19
|
+
"",
|
|
20
|
+
`Peer:`,
|
|
21
|
+
` id: ${input.peer.id}`,
|
|
22
|
+
` kind: ${input.peer.kind}`,
|
|
23
|
+
` displayName: ${input.peer.displayName}`,
|
|
24
|
+
"",
|
|
25
|
+
`Existing profile field keys (preserve names when proposing updates that refine an existing field): ${existingFields.length > 0 ? existingFields.join(", ") : "(none yet)"}`,
|
|
26
|
+
"",
|
|
27
|
+
`Recent interaction log (oldest first):`,
|
|
28
|
+
logBlock.length > 0 ? logBlock : "(no entries)",
|
|
29
|
+
"",
|
|
30
|
+
`Output a single JSON object: {"proposals": [{"field": "<stable_key>", "value": "<markdown>", "signal": "<short_label>", "note": "<optional>", "sourceSessionId": "<optional>"}]}.`,
|
|
31
|
+
"",
|
|
32
|
+
`Rules:`,
|
|
33
|
+
`1. Only propose fields supported by evidence in the log. Do not invent.`,
|
|
34
|
+
`2. Keys are short snake_case (e.g. "communication_style", "tool_patterns").`,
|
|
35
|
+
`3. value is markdown. signal is a short label like "explicit_preference" or "topic_recurrence".`,
|
|
36
|
+
`4. Omit fields you can't justify. Empty proposals array is valid.`,
|
|
37
|
+
`5. Output JSON ONLY \u2014 no prose before or after.`
|
|
38
|
+
].join("\n");
|
|
39
|
+
}
|
|
40
|
+
function parsePeerProfileReasonerResponse(raw) {
|
|
41
|
+
if (typeof raw !== "string" || raw.trim() === "") return [];
|
|
42
|
+
const trimmed = raw.trim();
|
|
43
|
+
const fenced = /^```(?:json)?\s*([\s\S]*?)```\s*$/u.exec(trimmed);
|
|
44
|
+
const payload = fenced ? fenced[1].trim() : trimmed;
|
|
45
|
+
let parsed;
|
|
46
|
+
try {
|
|
47
|
+
parsed = JSON.parse(payload);
|
|
48
|
+
} catch {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
const obj = parsed;
|
|
55
|
+
if (!Array.isArray(obj.proposals)) return [];
|
|
56
|
+
const out = [];
|
|
57
|
+
const RESERVED_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
58
|
+
for (const item of obj.proposals) {
|
|
59
|
+
if (typeof item !== "object" || item === null || Array.isArray(item)) continue;
|
|
60
|
+
const r = item;
|
|
61
|
+
if (typeof r.field !== "string" || r.field.trim() === "") continue;
|
|
62
|
+
if (RESERVED_KEYS.has(r.field)) continue;
|
|
63
|
+
if (typeof r.value !== "string" || r.value.trim() === "") continue;
|
|
64
|
+
if (typeof r.signal !== "string" || r.signal.trim() === "") continue;
|
|
65
|
+
const proposal = {
|
|
66
|
+
field: r.field,
|
|
67
|
+
value: r.value,
|
|
68
|
+
signal: r.signal,
|
|
69
|
+
...typeof r.note === "string" && r.note.length > 0 ? { note: r.note } : {},
|
|
70
|
+
...typeof r.sourceSessionId === "string" && r.sourceSessionId.length > 0 ? { sourceSessionId: r.sourceSessionId } : {}
|
|
71
|
+
};
|
|
72
|
+
out.push(proposal);
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
var SYSTEM_MESSAGE = 'You are a peer-profile reasoner. Output ONLY a JSON object of the form {"proposals":[{"field":"...","value":"...","signal":"...","note":"...","sourceSessionId":"..."}]}. No prose, no fenced code block, no commentary.';
|
|
77
|
+
var RUN_MARKER_KIND = "peer_profile_reasoner_run";
|
|
78
|
+
function lastRunTimestamp(log) {
|
|
79
|
+
let latest;
|
|
80
|
+
for (const entry of log) {
|
|
81
|
+
if (entry.kind !== RUN_MARKER_KIND) continue;
|
|
82
|
+
if (latest === void 0 || entry.timestamp > latest) {
|
|
83
|
+
latest = entry.timestamp;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return latest;
|
|
87
|
+
}
|
|
88
|
+
function noopLogger() {
|
|
89
|
+
return { debug: () => {
|
|
90
|
+
}, info: () => {
|
|
91
|
+
}, warn: () => {
|
|
92
|
+
} };
|
|
93
|
+
}
|
|
94
|
+
async function runPeerProfileReasoner(options) {
|
|
95
|
+
const log = {
|
|
96
|
+
debug: options.log?.debug ?? noopLogger().debug,
|
|
97
|
+
info: options.log?.info ?? noopLogger().info,
|
|
98
|
+
warn: options.log?.warn ?? noopLogger().warn
|
|
99
|
+
};
|
|
100
|
+
const result = {
|
|
101
|
+
peersConsidered: 0,
|
|
102
|
+
peersProcessed: 0,
|
|
103
|
+
fieldsApplied: 0,
|
|
104
|
+
perPeer: []
|
|
105
|
+
};
|
|
106
|
+
if (options.enabled !== true) {
|
|
107
|
+
log.debug("[peer-profile-reasoner] disabled \u2014 no-op");
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
if (!options.llm) {
|
|
111
|
+
log.warn("[peer-profile-reasoner] no LLM client supplied \u2014 skipping run");
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
const minInteractions = Number.isFinite(options.minInteractions) ? Math.max(0, Math.floor(options.minInteractions)) : 0;
|
|
115
|
+
const maxFields = Number.isFinite(options.maxFieldsPerRun) ? Math.max(0, Math.floor(options.maxFieldsPerRun)) : 0;
|
|
116
|
+
if (maxFields === 0) {
|
|
117
|
+
log.debug("[peer-profile-reasoner] maxFieldsPerRun=0 \u2014 no-op");
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
const maxLogPerPeer = Number.isFinite(options.maxLogEntriesPerPeer ?? NaN) ? Math.max(1, Math.floor(options.maxLogEntriesPerPeer)) : 50;
|
|
121
|
+
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
122
|
+
const nowIso = now.toISOString();
|
|
123
|
+
let peers;
|
|
124
|
+
try {
|
|
125
|
+
if (options.peerIds && options.peerIds.length > 0) {
|
|
126
|
+
const all = await listPeers(options.memoryDir);
|
|
127
|
+
const wanted = new Set(
|
|
128
|
+
options.peerIds.filter(
|
|
129
|
+
(id) => typeof id === "string" && PEER_ID_PATTERN.test(id)
|
|
130
|
+
)
|
|
131
|
+
);
|
|
132
|
+
peers = all.filter((p) => wanted.has(p.id));
|
|
133
|
+
} else {
|
|
134
|
+
peers = await listPeers(options.memoryDir);
|
|
135
|
+
}
|
|
136
|
+
} catch (err) {
|
|
137
|
+
log.warn(
|
|
138
|
+
`[peer-profile-reasoner] listPeers failed: ${err instanceof Error ? err.message : String(err)}`
|
|
139
|
+
);
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
result.peersConsidered = peers.length;
|
|
143
|
+
let fieldsAppliedTotal = 0;
|
|
144
|
+
for (const peer of peers) {
|
|
145
|
+
if (options.signal?.aborted) {
|
|
146
|
+
result.perPeer.push({
|
|
147
|
+
peerId: peer.id,
|
|
148
|
+
status: "skipped_aborted",
|
|
149
|
+
fieldsApplied: 0,
|
|
150
|
+
droppedDueToCap: 0,
|
|
151
|
+
fields: []
|
|
152
|
+
});
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
const fullLog = await readPeerInteractionLog(
|
|
157
|
+
options.memoryDir,
|
|
158
|
+
peer.id
|
|
159
|
+
);
|
|
160
|
+
if (fullLog.length === 0) {
|
|
161
|
+
result.perPeer.push({
|
|
162
|
+
peerId: peer.id,
|
|
163
|
+
status: "skipped_no_log",
|
|
164
|
+
fieldsApplied: 0,
|
|
165
|
+
droppedDueToCap: 0,
|
|
166
|
+
fields: []
|
|
167
|
+
});
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const lastRun = lastRunTimestamp(fullLog);
|
|
171
|
+
const sinceLastRunFull = lastRun ? fullLog.filter(
|
|
172
|
+
(e) => e.timestamp > lastRun && e.kind !== RUN_MARKER_KIND
|
|
173
|
+
) : fullLog.filter((e) => e.kind !== RUN_MARKER_KIND);
|
|
174
|
+
if (sinceLastRunFull.length < minInteractions) {
|
|
175
|
+
result.perPeer.push({
|
|
176
|
+
peerId: peer.id,
|
|
177
|
+
status: "skipped_below_min_interactions",
|
|
178
|
+
fieldsApplied: 0,
|
|
179
|
+
droppedDueToCap: 0,
|
|
180
|
+
fields: []
|
|
181
|
+
});
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const sinceLastRun = sinceLastRunFull.length > maxLogPerPeer ? sinceLastRunFull.slice(sinceLastRunFull.length - maxLogPerPeer) : sinceLastRunFull;
|
|
185
|
+
const existingProfile = await readPeerProfile(options.memoryDir, peer.id);
|
|
186
|
+
const remainingBudget = maxFields - fieldsAppliedTotal;
|
|
187
|
+
if (remainingBudget <= 0) {
|
|
188
|
+
result.perPeer.push({
|
|
189
|
+
peerId: peer.id,
|
|
190
|
+
status: "skipped_cap_reached",
|
|
191
|
+
fieldsApplied: 0,
|
|
192
|
+
droppedDueToCap: 0,
|
|
193
|
+
fields: []
|
|
194
|
+
});
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
const prompt = buildPeerProfileReasonerPrompt({
|
|
198
|
+
peer,
|
|
199
|
+
existingProfile,
|
|
200
|
+
log: sinceLastRun,
|
|
201
|
+
maxFields: remainingBudget
|
|
202
|
+
});
|
|
203
|
+
const messages = [
|
|
204
|
+
{ role: "system", content: SYSTEM_MESSAGE },
|
|
205
|
+
{ role: "user", content: prompt }
|
|
206
|
+
];
|
|
207
|
+
let response;
|
|
208
|
+
try {
|
|
209
|
+
response = await options.llm.chatCompletion(messages, {
|
|
210
|
+
temperature: 0.2,
|
|
211
|
+
maxTokens: 1500
|
|
212
|
+
});
|
|
213
|
+
} catch (err) {
|
|
214
|
+
log.warn(
|
|
215
|
+
`[peer-profile-reasoner] LLM call failed for "${peer.id}": ${err instanceof Error ? err.message : String(err)}`
|
|
216
|
+
);
|
|
217
|
+
result.perPeer.push({
|
|
218
|
+
peerId: peer.id,
|
|
219
|
+
status: "skipped_llm_unavailable",
|
|
220
|
+
fieldsApplied: 0,
|
|
221
|
+
droppedDueToCap: 0,
|
|
222
|
+
fields: []
|
|
223
|
+
});
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (!response || typeof response.content !== "string") {
|
|
227
|
+
result.perPeer.push({
|
|
228
|
+
peerId: peer.id,
|
|
229
|
+
status: "skipped_llm_unavailable",
|
|
230
|
+
fieldsApplied: 0,
|
|
231
|
+
droppedDueToCap: 0,
|
|
232
|
+
fields: []
|
|
233
|
+
});
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
const proposals = parsePeerProfileReasonerResponse(response.content);
|
|
237
|
+
if (proposals.length === 0) {
|
|
238
|
+
result.perPeer.push({
|
|
239
|
+
peerId: peer.id,
|
|
240
|
+
status: "processed",
|
|
241
|
+
fieldsApplied: 0,
|
|
242
|
+
droppedDueToCap: 0,
|
|
243
|
+
fields: []
|
|
244
|
+
});
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
const sessionIdsInWindow = new Set(
|
|
248
|
+
sinceLastRun.map((e) => e.sessionId).filter((s) => typeof s === "string" && s.length > 0)
|
|
249
|
+
);
|
|
250
|
+
const baseFields = existingProfile ? { ...existingProfile.fields } : {};
|
|
251
|
+
const baseProvenance = {};
|
|
252
|
+
if (existingProfile) {
|
|
253
|
+
for (const [k, list] of Object.entries(existingProfile.provenance)) {
|
|
254
|
+
baseProvenance[k] = [...list];
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
const appliedFieldsForPeer = [];
|
|
258
|
+
let droppedDueToCap = 0;
|
|
259
|
+
let invalidProposalSeen = false;
|
|
260
|
+
for (const proposal of proposals) {
|
|
261
|
+
const candidateBudget = maxFields - fieldsAppliedTotal - appliedFieldsForPeer.length;
|
|
262
|
+
if (candidateBudget <= 0) {
|
|
263
|
+
droppedDueToCap += 1;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
if (proposal.field === "__proto__" || proposal.field === "constructor" || proposal.field === "prototype") {
|
|
267
|
+
invalidProposalSeen = true;
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_]{0,63}$/.test(proposal.field)) {
|
|
271
|
+
invalidProposalSeen = true;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
baseFields[proposal.field] = proposal.value;
|
|
275
|
+
const sourceSessionId = proposal.sourceSessionId && sessionIdsInWindow.has(proposal.sourceSessionId) ? proposal.sourceSessionId : void 0;
|
|
276
|
+
const provEntry = {
|
|
277
|
+
observedAt: nowIso,
|
|
278
|
+
signal: proposal.signal,
|
|
279
|
+
...sourceSessionId ? { sourceSessionId } : {},
|
|
280
|
+
...proposal.note && proposal.note.length > 0 ? { note: proposal.note } : {}
|
|
281
|
+
};
|
|
282
|
+
const list = baseProvenance[proposal.field] ?? [];
|
|
283
|
+
list.push(provEntry);
|
|
284
|
+
baseProvenance[proposal.field] = list;
|
|
285
|
+
appliedFieldsForPeer.push(proposal.field);
|
|
286
|
+
}
|
|
287
|
+
if (appliedFieldsForPeer.length === 0) {
|
|
288
|
+
result.perPeer.push({
|
|
289
|
+
peerId: peer.id,
|
|
290
|
+
status: invalidProposalSeen ? "skipped_invalid_proposal" : droppedDueToCap > 0 ? "skipped_cap_reached" : "processed",
|
|
291
|
+
fieldsApplied: 0,
|
|
292
|
+
droppedDueToCap,
|
|
293
|
+
fields: []
|
|
294
|
+
});
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
const merged = {
|
|
298
|
+
peerId: peer.id,
|
|
299
|
+
updatedAt: nowIso,
|
|
300
|
+
fields: baseFields,
|
|
301
|
+
provenance: baseProvenance
|
|
302
|
+
};
|
|
303
|
+
await writePeerProfile(options.memoryDir, merged);
|
|
304
|
+
fieldsAppliedTotal += appliedFieldsForPeer.length;
|
|
305
|
+
const wantsMarker = options.appendRunMarkerToLog ?? true;
|
|
306
|
+
if (wantsMarker) {
|
|
307
|
+
try {
|
|
308
|
+
await appendInteractionLog(options.memoryDir, peer.id, {
|
|
309
|
+
timestamp: nowIso,
|
|
310
|
+
kind: RUN_MARKER_KIND,
|
|
311
|
+
summary: `applied ${appliedFieldsForPeer.length} field(s) via ${options.model ?? "unknown-model"}`
|
|
312
|
+
});
|
|
313
|
+
} catch (err) {
|
|
314
|
+
log.warn(
|
|
315
|
+
`[peer-profile-reasoner] run-marker append failed for "${peer.id}": ${err instanceof Error ? err.message : String(err)}`
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
result.perPeer.push({
|
|
320
|
+
peerId: peer.id,
|
|
321
|
+
status: "processed",
|
|
322
|
+
fieldsApplied: appliedFieldsForPeer.length,
|
|
323
|
+
droppedDueToCap,
|
|
324
|
+
fields: appliedFieldsForPeer
|
|
325
|
+
});
|
|
326
|
+
result.peersProcessed += 1;
|
|
327
|
+
result.fieldsApplied = fieldsAppliedTotal;
|
|
328
|
+
} catch (err) {
|
|
329
|
+
log.warn(
|
|
330
|
+
`[peer-profile-reasoner] error processing peer "${peer.id}": ${err instanceof Error ? err.message : String(err)}`
|
|
331
|
+
);
|
|
332
|
+
result.perPeer.push({
|
|
333
|
+
peerId: peer.id,
|
|
334
|
+
status: "error",
|
|
335
|
+
fieldsApplied: 0,
|
|
336
|
+
droppedDueToCap: 0,
|
|
337
|
+
fields: [],
|
|
338
|
+
error: err instanceof Error ? err.message : String(err)
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return result;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export {
|
|
346
|
+
buildPeerProfileReasonerPrompt,
|
|
347
|
+
parsePeerProfileReasonerResponse,
|
|
348
|
+
runPeerProfileReasoner
|
|
349
|
+
};
|
|
350
|
+
//# sourceMappingURL=chunk-3LCWFNVS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/peers/profile-reasoner.ts"],"sourcesContent":["/**\n * Peer profile reasoner — issue #679 PR 2/5.\n *\n * Pure async function that, for each peer:\n *\n * 1. Reads recent interaction-log entries via `readPeerInteractionLog`.\n * 2. Calls an injected LLM client (same chat shape as\n * `FallbackLlmClient.chatCompletion`) to derive 0..N profile-field\n * proposals with provenance `{observedAt, signal, sourceSessionId,\n * note}`.\n * 3. Merges the proposals into the peer's existing `PeerProfile`,\n * appending provenance entries (never replacing existing\n * provenance — the reasoner is additive by design so the operator\n * retains the full audit trail).\n * 4. Writes via `writePeerProfile`.\n *\n * Gating is handled in two layers:\n *\n * - The orchestrator wires the call behind the\n * `peerProfileReasonerEnabled` config flag (default `false` —\n * opt-in per Gotcha #30/#48). The reasoner ALSO short-circuits\n * when `options.enabled !== true`, so direct callers can't\n * accidentally bypass the flag.\n * - Per-peer, the `peerProfileReasonerMinInteractions` threshold\n * skips peers whose log has fewer entries since the last reasoner\n * run than required.\n *\n * The reasoner is intentionally storage-agnostic — it accepts an LLM\n * client by interface (`PeerProfileReasonerLlm`) so tests can mock the\n * call and the orchestrator can inject either the gateway client or\n * a fast local model. No direct OpenAI imports here.\n */\n\nimport {\n appendInteractionLog,\n listPeers,\n readPeerInteractionLog,\n readPeerProfile,\n writePeerProfile,\n} from \"./storage.js\";\nimport type {\n Peer,\n PeerInteractionLogEntry,\n PeerProfile,\n PeerProfileFieldProvenance,\n} from \"./types.js\";\nimport { PEER_ID_PATTERN } from \"./types.js\";\n\n// ──────────────────────────────────────────────────────────────────────\n// Types\n// ──────────────────────────────────────────────────────────────────────\n\n/**\n * Minimal chat-completion contract the reasoner depends on. Matches\n * `FallbackLlmClient.chatCompletion` so the orchestrator can pass it\n * through directly. Tests inject a mock that returns canned strings.\n *\n * Returning `null` means the LLM is unavailable / failed — the\n * reasoner treats that as \"no proposals for this peer\" rather than an\n * error so a flaky LLM never aborts the whole pass.\n */\nexport interface PeerProfileReasonerLlm {\n chatCompletion(\n messages: Array<{ role: \"system\" | \"user\" | \"assistant\"; content: string }>,\n options?: { temperature?: number; maxTokens?: number; timeoutMs?: number },\n ): Promise<{ content: string } | null>;\n}\n\n/**\n * One LLM-proposed profile-field update.\n *\n * `value` is the new markdown string to set under `field`. The\n * provenance entry the LLM emits travels alongside it; the reasoner\n * does NOT trust the LLM's `observedAt` — it always overwrites with\n * the run's `now` timestamp so provenance can never claim future or\n * past observation timestamps the operator didn't witness.\n */\nexport interface PeerProfileReasonerProposal {\n /** Stable section key, e.g. \"communication_style\". */\n readonly field: string;\n /** Markdown value to set under that key. */\n readonly value: string;\n /**\n * Short label for the signal that justified the inference,\n * e.g. \"explicit_preference\", \"tool_pattern\", \"topic_recurrence\".\n */\n readonly signal: string;\n /** Optional free-form note explaining the inference. */\n readonly note?: string;\n /**\n * Originating session id, when the LLM can attribute the inference\n * to a specific log line. Reasoner clamps this to a value that\n * actually appeared in the log window so the LLM can't hallucinate.\n */\n readonly sourceSessionId?: string;\n}\n\nexport interface PeerProfileReasonerOptions {\n /** Memory directory containing the peers/ subtree. */\n readonly memoryDir: string;\n /**\n * Master gate. When `false` (the default the orchestrator passes\n * when the config flag is off), the reasoner is a no-op and\n * returns an empty result. Direct callers must explicitly pass\n * `true` so the gate can never be defaulted ON by accident\n * (Gotcha #48 — least-privileged default).\n */\n readonly enabled: boolean;\n /** Injected LLM client. Required when `enabled === true`. */\n readonly llm?: PeerProfileReasonerLlm;\n /** Model name to log for telemetry; not used to dispatch. */\n readonly model?: string;\n /**\n * Minimum new interaction-log entries since last reasoner run\n * before this peer is processed. Peers below the threshold are\n * skipped with `reason: \"below_min_interactions\"`.\n */\n readonly minInteractions: number;\n /**\n * Hard cap on profile fields the reasoner will accept across all\n * peers in a single run. Tracked in insertion order: once the cap\n * is reached, subsequent proposals are dropped with\n * `dropped_due_to_cap` in the per-peer result. Use to bound LLM\n * cost and reviewer load per pass.\n */\n readonly maxFieldsPerRun: number;\n /**\n * Optional restriction to specific peer ids. When omitted, the\n * reasoner enumerates the entire peer registry via `listPeers`.\n */\n readonly peerIds?: ReadonlyArray<string>;\n /**\n * Maximum number of recent log entries to feed the LLM per peer.\n * Defaults to 50. Bounded so a runaway log can't blow the prompt.\n */\n readonly maxLogEntriesPerPeer?: number;\n /**\n * Reasoner run timestamp. Defaults to `new Date()` at call time.\n * Tests inject a deterministic clock; the orchestrator passes\n * `new Date()` so provenance entries reflect actual wall time.\n */\n readonly now?: Date;\n /**\n * Optional logger; defaults to a no-op so the reasoner stays\n * silent in unit tests. The orchestrator wires its `log` here so\n * runs surface in the gateway log under the\n * `[peer-profile-reasoner]` prefix.\n */\n readonly log?: {\n debug?: (msg: string) => void;\n info?: (msg: string) => void;\n warn?: (msg: string) => void;\n };\n /**\n * Whether to append a `peer_profile_reasoner_run` entry to the\n * peer's interaction log when the reasoner emits at least one\n * field for that peer. Defaults to `true`. Disable in tests that\n * want to assert the log was untouched.\n */\n readonly appendRunMarkerToLog?: boolean;\n /**\n * Optional abort signal. The reasoner checks between peers and\n * returns the partial result if cancelled mid-run.\n */\n readonly signal?: AbortSignal;\n}\n\nexport interface PeerProfileReasonerPeerResult {\n readonly peerId: string;\n readonly status:\n | \"processed\"\n | \"skipped_below_min_interactions\"\n | \"skipped_no_log\"\n | \"skipped_disabled\"\n | \"skipped_no_llm\"\n | \"skipped_llm_unavailable\"\n | \"skipped_invalid_proposal\"\n | \"skipped_cap_reached\"\n | \"skipped_aborted\"\n | \"error\";\n /** Number of fields actually applied to the peer's profile. */\n readonly fieldsApplied: number;\n /** Number of proposals dropped because the per-run cap was hit. */\n readonly droppedDueToCap: number;\n /** Set of field keys applied; useful for tests and telemetry. */\n readonly fields: ReadonlyArray<string>;\n /** Error message, when `status === \"error\"`. */\n readonly error?: string;\n}\n\nexport interface PeerProfileReasonerResult {\n readonly peersConsidered: number;\n readonly peersProcessed: number;\n readonly fieldsApplied: number;\n readonly perPeer: ReadonlyArray<PeerProfileReasonerPeerResult>;\n}\n\n// ──────────────────────────────────────────────────────────────────────\n// Prompt + parser (pure, exported for tests)\n// ──────────────────────────────────────────────────────────────────────\n\n/**\n * Build the user-facing reasoner prompt. The system message carries\n * the strict-JSON instruction; this function emits the user message\n * with the peer context and the recent log slice.\n *\n * The prompt is intentionally schema-prescriptive — sibling modules\n * (`semantic-consolidation.ts`, `extraction-judge.ts`) demonstrated\n * that letting the LLM improvise field names produces unstable\n * profiles across runs.\n */\nexport function buildPeerProfileReasonerPrompt(input: {\n peer: Peer;\n existingProfile: PeerProfile | null;\n log: ReadonlyArray<PeerInteractionLogEntry>;\n maxFields: number;\n}): string {\n const existingFields = input.existingProfile\n ? Object.keys(input.existingProfile.fields)\n : [];\n const logBlock = input.log\n .map((e) => {\n const session = e.sessionId ? ` session=${e.sessionId}` : \"\";\n return `- [${e.timestamp}] (${e.kind})${session} ${e.summary}`;\n })\n .join(\"\\n\");\n return [\n `You are an async peer-profile reasoner. Your job is to read recent interaction-log entries for one peer and propose 0..${input.maxFields} profile-field updates.`,\n \"\",\n `Peer:`,\n ` id: ${input.peer.id}`,\n ` kind: ${input.peer.kind}`,\n ` displayName: ${input.peer.displayName}`,\n \"\",\n `Existing profile field keys (preserve names when proposing updates that refine an existing field): ${existingFields.length > 0 ? existingFields.join(\", \") : \"(none yet)\"}`,\n \"\",\n `Recent interaction log (oldest first):`,\n logBlock.length > 0 ? logBlock : \"(no entries)\",\n \"\",\n `Output a single JSON object: {\"proposals\": [{\"field\": \"<stable_key>\", \"value\": \"<markdown>\", \"signal\": \"<short_label>\", \"note\": \"<optional>\", \"sourceSessionId\": \"<optional>\"}]}.`,\n \"\",\n `Rules:`,\n `1. Only propose fields supported by evidence in the log. Do not invent.`,\n `2. Keys are short snake_case (e.g. \"communication_style\", \"tool_patterns\").`,\n `3. value is markdown. signal is a short label like \"explicit_preference\" or \"topic_recurrence\".`,\n `4. Omit fields you can't justify. Empty proposals array is valid.`,\n `5. Output JSON ONLY — no prose before or after.`,\n ].join(\"\\n\");\n}\n\n/**\n * Parse the LLM response. Tolerates a fenced code block wrapper.\n * Returns an empty array on any malformed payload — the contract is\n * that flaky LLM output silently produces zero proposals rather than\n * surfacing an error to the caller.\n *\n * Exported so unit tests can verify parser behavior without spinning\n * up the full reasoner.\n */\nexport function parsePeerProfileReasonerResponse(\n raw: string,\n): PeerProfileReasonerProposal[] {\n if (typeof raw !== \"string\" || raw.trim() === \"\") return [];\n const trimmed = raw.trim();\n const fenced = /^```(?:json)?\\s*([\\s\\S]*?)```\\s*$/u.exec(trimmed);\n const payload = fenced ? fenced[1].trim() : trimmed;\n let parsed: unknown;\n try {\n parsed = JSON.parse(payload);\n } catch {\n return [];\n }\n // Gotcha #18: JSON.parse('null') succeeds. Reject non-objects.\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n return [];\n }\n const obj = parsed as { proposals?: unknown };\n if (!Array.isArray(obj.proposals)) return [];\n const out: PeerProfileReasonerProposal[] = [];\n // Gotcha — drop prototype-pollution keys at the field-name layer.\n const RESERVED_KEYS = new Set([\"__proto__\", \"constructor\", \"prototype\"]);\n for (const item of obj.proposals) {\n if (typeof item !== \"object\" || item === null || Array.isArray(item)) continue;\n const r = item as Record<string, unknown>;\n if (typeof r.field !== \"string\" || r.field.trim() === \"\") continue;\n if (RESERVED_KEYS.has(r.field)) continue;\n if (typeof r.value !== \"string\" || r.value.trim() === \"\") continue;\n if (typeof r.signal !== \"string\" || r.signal.trim() === \"\") continue;\n const proposal: PeerProfileReasonerProposal = {\n field: r.field,\n value: r.value,\n signal: r.signal,\n ...(typeof r.note === \"string\" && r.note.length > 0 ? { note: r.note } : {}),\n ...(typeof r.sourceSessionId === \"string\" && r.sourceSessionId.length > 0\n ? { sourceSessionId: r.sourceSessionId }\n : {}),\n };\n out.push(proposal);\n }\n return out;\n}\n\n// ──────────────────────────────────────────────────────────────────────\n// Reasoner core\n// ──────────────────────────────────────────────────────────────────────\n\nconst SYSTEM_MESSAGE =\n 'You are a peer-profile reasoner. Output ONLY a JSON object of the form {\"proposals\":[{\"field\":\"...\",\"value\":\"...\",\"signal\":\"...\",\"note\":\"...\",\"sourceSessionId\":\"...\"}]}. No prose, no fenced code block, no commentary.';\n\nconst RUN_MARKER_KIND = \"peer_profile_reasoner_run\";\n\n/**\n * Find the most recent reasoner-run marker timestamp in the log.\n * Used to count \"interactions since last run\" so the threshold\n * gate doesn't keep firing on the same dormant log forever.\n */\nfunction lastRunTimestamp(\n log: ReadonlyArray<PeerInteractionLogEntry>,\n): string | undefined {\n let latest: string | undefined;\n for (const entry of log) {\n if (entry.kind !== RUN_MARKER_KIND) continue;\n if (latest === undefined || entry.timestamp > latest) {\n latest = entry.timestamp;\n }\n }\n return latest;\n}\n\nfunction noopLogger(): NonNullable<PeerProfileReasonerOptions[\"log\"]> {\n return { debug: () => {}, info: () => {}, warn: () => {} };\n}\n\n/**\n * Run the reasoner across all (or the requested subset of) peers.\n *\n * Always returns a `PeerProfileReasonerResult` — never throws to the\n * caller — so the orchestrator can wire it as a best-effort\n * post-consolidation hook (Gotcha #13).\n */\nexport async function runPeerProfileReasoner(\n options: PeerProfileReasonerOptions,\n): Promise<PeerProfileReasonerResult> {\n const log = {\n debug: options.log?.debug ?? noopLogger().debug!,\n info: options.log?.info ?? noopLogger().info!,\n warn: options.log?.warn ?? noopLogger().warn!,\n };\n const result: {\n peersConsidered: number;\n peersProcessed: number;\n fieldsApplied: number;\n perPeer: PeerProfileReasonerPeerResult[];\n } = {\n peersConsidered: 0,\n peersProcessed: 0,\n fieldsApplied: 0,\n perPeer: [],\n };\n // Disabled flag is the master gate. Defaults to false in callers'\n // config; we additionally require strict `=== true` here so a\n // stray \"true\" string doesn't silently flip the flag (Gotcha #36).\n if (options.enabled !== true) {\n log.debug(\"[peer-profile-reasoner] disabled — no-op\");\n return result;\n }\n if (!options.llm) {\n log.warn(\"[peer-profile-reasoner] no LLM client supplied — skipping run\");\n return result;\n }\n const minInteractions = Number.isFinite(options.minInteractions)\n ? Math.max(0, Math.floor(options.minInteractions))\n : 0;\n const maxFields = Number.isFinite(options.maxFieldsPerRun)\n ? Math.max(0, Math.floor(options.maxFieldsPerRun))\n : 0;\n if (maxFields === 0) {\n log.debug(\"[peer-profile-reasoner] maxFieldsPerRun=0 — no-op\");\n return result;\n }\n const maxLogPerPeer = Number.isFinite(options.maxLogEntriesPerPeer ?? NaN)\n ? Math.max(1, Math.floor(options.maxLogEntriesPerPeer as number))\n : 50;\n const now = options.now ?? new Date();\n const nowIso = now.toISOString();\n\n let peers: Peer[];\n try {\n if (options.peerIds && options.peerIds.length > 0) {\n // Filter the explicit list against on-disk peers so we never\n // act on an id the operator typed but didn't register.\n const all = await listPeers(options.memoryDir);\n const wanted = new Set(\n options.peerIds.filter(\n (id) => typeof id === \"string\" && PEER_ID_PATTERN.test(id),\n ),\n );\n peers = all.filter((p) => wanted.has(p.id));\n } else {\n peers = await listPeers(options.memoryDir);\n }\n } catch (err) {\n log.warn(\n `[peer-profile-reasoner] listPeers failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n return result;\n }\n result.peersConsidered = peers.length;\n let fieldsAppliedTotal = 0;\n\n for (const peer of peers) {\n if (options.signal?.aborted) {\n result.perPeer.push({\n peerId: peer.id,\n status: \"skipped_aborted\",\n fieldsApplied: 0,\n droppedDueToCap: 0,\n fields: [],\n });\n continue;\n }\n try {\n // Codex P2 review on PR #736: the min-interactions threshold\n // must reflect the FULL log of new activity, not the\n // `maxLogPerPeer`-truncated slice. Otherwise a peer with a\n // genuinely active conversation history can be permanently\n // marked `skipped_below_min_interactions` whenever\n // `peerProfileReasonerMinInteractions > maxLogEntriesPerPeer`,\n // because the slice will never include enough new entries.\n // Read the full log first to compute the gate, then truncate\n // for prompt construction below.\n const fullLog = await readPeerInteractionLog(\n options.memoryDir,\n peer.id,\n );\n if (fullLog.length === 0) {\n result.perPeer.push({\n peerId: peer.id,\n status: \"skipped_no_log\",\n fieldsApplied: 0,\n droppedDueToCap: 0,\n fields: [],\n });\n continue;\n }\n // Count interactions since the last reasoner-run marker, so\n // dormant peers don't trigger another LLM call until enough\n // new signal accumulates. Run markers themselves don't count.\n const lastRun = lastRunTimestamp(fullLog);\n const sinceLastRunFull = lastRun\n ? fullLog.filter(\n (e) => e.timestamp > lastRun && e.kind !== RUN_MARKER_KIND,\n )\n : fullLog.filter((e) => e.kind !== RUN_MARKER_KIND);\n if (sinceLastRunFull.length < minInteractions) {\n result.perPeer.push({\n peerId: peer.id,\n status: \"skipped_below_min_interactions\",\n fieldsApplied: 0,\n droppedDueToCap: 0,\n fields: [],\n });\n continue;\n }\n // Truncate ONLY for prompt construction so the LLM context\n // stays bounded. Use the most recent `maxLogPerPeer` entries\n // from the full since-last-run set so the prompt prefers fresh\n // signal over older entries.\n const sinceLastRun =\n sinceLastRunFull.length > maxLogPerPeer\n ? sinceLastRunFull.slice(sinceLastRunFull.length - maxLogPerPeer)\n : sinceLastRunFull;\n\n const existingProfile = await readPeerProfile(options.memoryDir, peer.id);\n\n const remainingBudget = maxFields - fieldsAppliedTotal;\n if (remainingBudget <= 0) {\n result.perPeer.push({\n peerId: peer.id,\n status: \"skipped_cap_reached\",\n fieldsApplied: 0,\n droppedDueToCap: 0,\n fields: [],\n });\n continue;\n }\n\n const prompt = buildPeerProfileReasonerPrompt({\n peer,\n existingProfile,\n log: sinceLastRun,\n maxFields: remainingBudget,\n });\n const messages = [\n { role: \"system\" as const, content: SYSTEM_MESSAGE },\n { role: \"user\" as const, content: prompt },\n ];\n let response: { content: string } | null;\n try {\n response = await options.llm.chatCompletion(messages, {\n temperature: 0.2,\n maxTokens: 1500,\n });\n } catch (err) {\n log.warn(\n `[peer-profile-reasoner] LLM call failed for \"${peer.id}\": ${err instanceof Error ? err.message : String(err)}`,\n );\n result.perPeer.push({\n peerId: peer.id,\n status: \"skipped_llm_unavailable\",\n fieldsApplied: 0,\n droppedDueToCap: 0,\n fields: [],\n });\n continue;\n }\n if (!response || typeof response.content !== \"string\") {\n result.perPeer.push({\n peerId: peer.id,\n status: \"skipped_llm_unavailable\",\n fieldsApplied: 0,\n droppedDueToCap: 0,\n fields: [],\n });\n continue;\n }\n\n const proposals = parsePeerProfileReasonerResponse(response.content);\n if (proposals.length === 0) {\n result.perPeer.push({\n peerId: peer.id,\n status: \"processed\",\n fieldsApplied: 0,\n droppedDueToCap: 0,\n fields: [],\n });\n continue;\n }\n\n // Build the merged profile. We never replace existing\n // provenance entries — provenance is append-only so the\n // operator retains a full audit trail.\n const sessionIdsInWindow = new Set(\n sinceLastRun\n .map((e) => e.sessionId)\n .filter((s): s is string => typeof s === \"string\" && s.length > 0),\n );\n const baseFields: Record<string, string> = existingProfile\n ? { ...existingProfile.fields }\n : {};\n const baseProvenance: Record<string, PeerProfileFieldProvenance[]> = {};\n if (existingProfile) {\n for (const [k, list] of Object.entries(existingProfile.provenance)) {\n baseProvenance[k] = [...list];\n }\n }\n\n // Codex P1 review on PR #736: the global `fieldsAppliedTotal`\n // counter must NOT be incremented until the profile write\n // actually succeeds. Otherwise a transient I/O error here\n // poisons the per-run cap for every subsequent peer — they get\n // marked `skipped_cap_reached` for fields that were never\n // persisted. Track the candidate count locally and only\n // commit it to the run-wide budget after `writePeerProfile`\n // returns successfully (Gotcha #25 — don't destroy old state\n // before confirming new state succeeds).\n const appliedFieldsForPeer: string[] = [];\n let droppedDueToCap = 0;\n let invalidProposalSeen = false;\n for (const proposal of proposals) {\n // Use a candidate-budget projection so we never propose more\n // than the run-wide cap allows even before we know the write\n // will succeed.\n const candidateBudget =\n maxFields - fieldsAppliedTotal - appliedFieldsForPeer.length;\n if (candidateBudget <= 0) {\n droppedDueToCap += 1;\n continue;\n }\n // Final defensive guard against prototype keys (parser\n // already drops them, but be redundant for safety).\n if (\n proposal.field === \"__proto__\" ||\n proposal.field === \"constructor\" ||\n proposal.field === \"prototype\"\n ) {\n invalidProposalSeen = true;\n continue;\n }\n // Sanity-check field key matches a conservative pattern so a\n // hostile LLM can't sneak path-traversal-shaped keys through\n // for downstream consumers.\n if (!/^[a-zA-Z][a-zA-Z0-9_]{0,63}$/.test(proposal.field)) {\n invalidProposalSeen = true;\n continue;\n }\n baseFields[proposal.field] = proposal.value;\n const sourceSessionId =\n proposal.sourceSessionId &&\n sessionIdsInWindow.has(proposal.sourceSessionId)\n ? proposal.sourceSessionId\n : undefined;\n const provEntry: PeerProfileFieldProvenance = {\n observedAt: nowIso,\n signal: proposal.signal,\n ...(sourceSessionId ? { sourceSessionId } : {}),\n ...(proposal.note && proposal.note.length > 0\n ? { note: proposal.note }\n : {}),\n };\n const list = baseProvenance[proposal.field] ?? [];\n list.push(provEntry);\n baseProvenance[proposal.field] = list;\n appliedFieldsForPeer.push(proposal.field);\n // NOTE: fieldsAppliedTotal is NOT incremented here — see the\n // P1 comment above. We commit the budget after the write\n // succeeds.\n }\n\n if (appliedFieldsForPeer.length === 0) {\n result.perPeer.push({\n peerId: peer.id,\n status: invalidProposalSeen\n ? \"skipped_invalid_proposal\"\n : droppedDueToCap > 0\n ? \"skipped_cap_reached\"\n : \"processed\",\n fieldsApplied: 0,\n droppedDueToCap,\n fields: [],\n });\n continue;\n }\n\n const merged: PeerProfile = {\n peerId: peer.id,\n updatedAt: nowIso,\n fields: baseFields,\n provenance: baseProvenance,\n };\n await writePeerProfile(options.memoryDir, merged);\n // Write succeeded — NOW commit the budget. A throw above\n // bubbles to the outer catch, where the peer is recorded as\n // `error` and the global cap remains intact for subsequent\n // peers (Codex P1 fix on PR #736).\n fieldsAppliedTotal += appliedFieldsForPeer.length;\n\n // Append a run marker so the next reasoner pass can compute\n // \"interactions since last run\" without a dedicated state\n // file. The marker is best-effort — a write failure here\n // logs but does not roll back the profile (the operator\n // would prefer a slightly noisy threshold over a lost\n // profile update).\n const wantsMarker = options.appendRunMarkerToLog ?? true;\n if (wantsMarker) {\n try {\n await appendInteractionLog(options.memoryDir, peer.id, {\n timestamp: nowIso,\n kind: RUN_MARKER_KIND,\n summary: `applied ${appliedFieldsForPeer.length} field(s) via ${options.model ?? \"unknown-model\"}`,\n });\n } catch (err) {\n log.warn(\n `[peer-profile-reasoner] run-marker append failed for \"${peer.id}\": ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n result.perPeer.push({\n peerId: peer.id,\n status: \"processed\",\n fieldsApplied: appliedFieldsForPeer.length,\n droppedDueToCap,\n fields: appliedFieldsForPeer,\n });\n result.peersProcessed += 1;\n result.fieldsApplied = fieldsAppliedTotal;\n } catch (err) {\n log.warn(\n `[peer-profile-reasoner] error processing peer \"${peer.id}\": ${err instanceof Error ? err.message : String(err)}`,\n );\n result.perPeer.push({\n peerId: peer.id,\n status: \"error\",\n fieldsApplied: 0,\n droppedDueToCap: 0,\n fields: [],\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n return result;\n}\n"],"mappings":";;;;;;;;;;AAmNO,SAAS,+BAA+B,OAKpC;AACT,QAAM,iBAAiB,MAAM,kBACzB,OAAO,KAAK,MAAM,gBAAgB,MAAM,IACxC,CAAC;AACL,QAAM,WAAW,MAAM,IACpB,IAAI,CAAC,MAAM;AACV,UAAM,UAAU,EAAE,YAAY,YAAY,EAAE,SAAS,KAAK;AAC1D,WAAO,MAAM,EAAE,SAAS,MAAM,EAAE,IAAI,IAAI,OAAO,IAAI,EAAE,OAAO;AAAA,EAC9D,CAAC,EACA,KAAK,IAAI;AACZ,SAAO;AAAA,IACL,0HAA0H,MAAM,SAAS;AAAA,IACzI;AAAA,IACA;AAAA,IACA,SAAS,MAAM,KAAK,EAAE;AAAA,IACtB,WAAW,MAAM,KAAK,IAAI;AAAA,IAC1B,kBAAkB,MAAM,KAAK,WAAW;AAAA,IACxC;AAAA,IACA,sGAAsG,eAAe,SAAS,IAAI,eAAe,KAAK,IAAI,IAAI,YAAY;AAAA,IAC1K;AAAA,IACA;AAAA,IACA,SAAS,SAAS,IAAI,WAAW;AAAA,IACjC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI;AACb;AAWO,SAAS,iCACd,KAC+B;AAC/B,MAAI,OAAO,QAAQ,YAAY,IAAI,KAAK,MAAM,GAAI,QAAO,CAAC;AAC1D,QAAM,UAAU,IAAI,KAAK;AACzB,QAAM,SAAS,qCAAqC,KAAK,OAAO;AAChE,QAAM,UAAU,SAAS,OAAO,CAAC,EAAE,KAAK,IAAI;AAC5C,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,OAAO;AAAA,EAC7B,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AAEA,MAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,MAAM,GAAG;AAC1E,WAAO,CAAC;AAAA,EACV;AACA,QAAM,MAAM;AACZ,MAAI,CAAC,MAAM,QAAQ,IAAI,SAAS,EAAG,QAAO,CAAC;AAC3C,QAAM,MAAqC,CAAC;AAE5C,QAAM,gBAAgB,oBAAI,IAAI,CAAC,aAAa,eAAe,WAAW,CAAC;AACvE,aAAW,QAAQ,IAAI,WAAW;AAChC,QAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,MAAM,QAAQ,IAAI,EAAG;AACtE,UAAM,IAAI;AACV,QAAI,OAAO,EAAE,UAAU,YAAY,EAAE,MAAM,KAAK,MAAM,GAAI;AAC1D,QAAI,cAAc,IAAI,EAAE,KAAK,EAAG;AAChC,QAAI,OAAO,EAAE,UAAU,YAAY,EAAE,MAAM,KAAK,MAAM,GAAI;AAC1D,QAAI,OAAO,EAAE,WAAW,YAAY,EAAE,OAAO,KAAK,MAAM,GAAI;AAC5D,UAAM,WAAwC;AAAA,MAC5C,OAAO,EAAE;AAAA,MACT,OAAO,EAAE;AAAA,MACT,QAAQ,EAAE;AAAA,MACV,GAAI,OAAO,EAAE,SAAS,YAAY,EAAE,KAAK,SAAS,IAAI,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;AAAA,MAC1E,GAAI,OAAO,EAAE,oBAAoB,YAAY,EAAE,gBAAgB,SAAS,IACpE,EAAE,iBAAiB,EAAE,gBAAgB,IACrC,CAAC;AAAA,IACP;AACA,QAAI,KAAK,QAAQ;AAAA,EACnB;AACA,SAAO;AACT;AAMA,IAAM,iBACJ;AAEF,IAAM,kBAAkB;AAOxB,SAAS,iBACP,KACoB;AACpB,MAAI;AACJ,aAAW,SAAS,KAAK;AACvB,QAAI,MAAM,SAAS,gBAAiB;AACpC,QAAI,WAAW,UAAa,MAAM,YAAY,QAAQ;AACpD,eAAS,MAAM;AAAA,IACjB;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,aAA6D;AACpE,SAAO,EAAE,OAAO,MAAM;AAAA,EAAC,GAAG,MAAM,MAAM;AAAA,EAAC,GAAG,MAAM,MAAM;AAAA,EAAC,EAAE;AAC3D;AASA,eAAsB,uBACpB,SACoC;AACpC,QAAM,MAAM;AAAA,IACV,OAAO,QAAQ,KAAK,SAAS,WAAW,EAAE;AAAA,IAC1C,MAAM,QAAQ,KAAK,QAAQ,WAAW,EAAE;AAAA,IACxC,MAAM,QAAQ,KAAK,QAAQ,WAAW,EAAE;AAAA,EAC1C;AACA,QAAM,SAKF;AAAA,IACF,iBAAiB;AAAA,IACjB,gBAAgB;AAAA,IAChB,eAAe;AAAA,IACf,SAAS,CAAC;AAAA,EACZ;AAIA,MAAI,QAAQ,YAAY,MAAM;AAC5B,QAAI,MAAM,+CAA0C;AACpD,WAAO;AAAA,EACT;AACA,MAAI,CAAC,QAAQ,KAAK;AAChB,QAAI,KAAK,oEAA+D;AACxE,WAAO;AAAA,EACT;AACA,QAAM,kBAAkB,OAAO,SAAS,QAAQ,eAAe,IAC3D,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,eAAe,CAAC,IAC/C;AACJ,QAAM,YAAY,OAAO,SAAS,QAAQ,eAAe,IACrD,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,eAAe,CAAC,IAC/C;AACJ,MAAI,cAAc,GAAG;AACnB,QAAI,MAAM,wDAAmD;AAC7D,WAAO;AAAA,EACT;AACA,QAAM,gBAAgB,OAAO,SAAS,QAAQ,wBAAwB,GAAG,IACrE,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,oBAA8B,CAAC,IAC9D;AACJ,QAAM,MAAM,QAAQ,OAAO,oBAAI,KAAK;AACpC,QAAM,SAAS,IAAI,YAAY;AAE/B,MAAI;AACJ,MAAI;AACF,QAAI,QAAQ,WAAW,QAAQ,QAAQ,SAAS,GAAG;AAGjD,YAAM,MAAM,MAAM,UAAU,QAAQ,SAAS;AAC7C,YAAM,SAAS,IAAI;AAAA,QACjB,QAAQ,QAAQ;AAAA,UACd,CAAC,OAAO,OAAO,OAAO,YAAY,gBAAgB,KAAK,EAAE;AAAA,QAC3D;AAAA,MACF;AACA,cAAQ,IAAI,OAAO,CAAC,MAAM,OAAO,IAAI,EAAE,EAAE,CAAC;AAAA,IAC5C,OAAO;AACL,cAAQ,MAAM,UAAU,QAAQ,SAAS;AAAA,IAC3C;AAAA,EACF,SAAS,KAAK;AACZ,QAAI;AAAA,MACF,6CAA6C,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,IAC/F;AACA,WAAO;AAAA,EACT;AACA,SAAO,kBAAkB,MAAM;AAC/B,MAAI,qBAAqB;AAEzB,aAAW,QAAQ,OAAO;AACxB,QAAI,QAAQ,QAAQ,SAAS;AAC3B,aAAO,QAAQ,KAAK;AAAA,QAClB,QAAQ,KAAK;AAAA,QACb,QAAQ;AAAA,QACR,eAAe;AAAA,QACf,iBAAiB;AAAA,QACjB,QAAQ,CAAC;AAAA,MACX,CAAC;AACD;AAAA,IACF;AACA,QAAI;AAUF,YAAM,UAAU,MAAM;AAAA,QACpB,QAAQ;AAAA,QACR,KAAK;AAAA,MACP;AACA,UAAI,QAAQ,WAAW,GAAG;AACxB,eAAO,QAAQ,KAAK;AAAA,UAClB,QAAQ,KAAK;AAAA,UACb,QAAQ;AAAA,UACR,eAAe;AAAA,UACf,iBAAiB;AAAA,UACjB,QAAQ,CAAC;AAAA,QACX,CAAC;AACD;AAAA,MACF;AAIA,YAAM,UAAU,iBAAiB,OAAO;AACxC,YAAM,mBAAmB,UACrB,QAAQ;AAAA,QACN,CAAC,MAAM,EAAE,YAAY,WAAW,EAAE,SAAS;AAAA,MAC7C,IACA,QAAQ,OAAO,CAAC,MAAM,EAAE,SAAS,eAAe;AACpD,UAAI,iBAAiB,SAAS,iBAAiB;AAC7C,eAAO,QAAQ,KAAK;AAAA,UAClB,QAAQ,KAAK;AAAA,UACb,QAAQ;AAAA,UACR,eAAe;AAAA,UACf,iBAAiB;AAAA,UACjB,QAAQ,CAAC;AAAA,QACX,CAAC;AACD;AAAA,MACF;AAKA,YAAM,eACJ,iBAAiB,SAAS,gBACtB,iBAAiB,MAAM,iBAAiB,SAAS,aAAa,IAC9D;AAEN,YAAM,kBAAkB,MAAM,gBAAgB,QAAQ,WAAW,KAAK,EAAE;AAExE,YAAM,kBAAkB,YAAY;AACpC,UAAI,mBAAmB,GAAG;AACxB,eAAO,QAAQ,KAAK;AAAA,UAClB,QAAQ,KAAK;AAAA,UACb,QAAQ;AAAA,UACR,eAAe;AAAA,UACf,iBAAiB;AAAA,UACjB,QAAQ,CAAC;AAAA,QACX,CAAC;AACD;AAAA,MACF;AAEA,YAAM,SAAS,+BAA+B;AAAA,QAC5C;AAAA,QACA;AAAA,QACA,KAAK;AAAA,QACL,WAAW;AAAA,MACb,CAAC;AACD,YAAM,WAAW;AAAA,QACf,EAAE,MAAM,UAAmB,SAAS,eAAe;AAAA,QACnD,EAAE,MAAM,QAAiB,SAAS,OAAO;AAAA,MAC3C;AACA,UAAI;AACJ,UAAI;AACF,mBAAW,MAAM,QAAQ,IAAI,eAAe,UAAU;AAAA,UACpD,aAAa;AAAA,UACb,WAAW;AAAA,QACb,CAAC;AAAA,MACH,SAAS,KAAK;AACZ,YAAI;AAAA,UACF,gDAAgD,KAAK,EAAE,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,QAC/G;AACA,eAAO,QAAQ,KAAK;AAAA,UAClB,QAAQ,KAAK;AAAA,UACb,QAAQ;AAAA,UACR,eAAe;AAAA,UACf,iBAAiB;AAAA,UACjB,QAAQ,CAAC;AAAA,QACX,CAAC;AACD;AAAA,MACF;AACA,UAAI,CAAC,YAAY,OAAO,SAAS,YAAY,UAAU;AACrD,eAAO,QAAQ,KAAK;AAAA,UAClB,QAAQ,KAAK;AAAA,UACb,QAAQ;AAAA,UACR,eAAe;AAAA,UACf,iBAAiB;AAAA,UACjB,QAAQ,CAAC;AAAA,QACX,CAAC;AACD;AAAA,MACF;AAEA,YAAM,YAAY,iCAAiC,SAAS,OAAO;AACnE,UAAI,UAAU,WAAW,GAAG;AAC1B,eAAO,QAAQ,KAAK;AAAA,UAClB,QAAQ,KAAK;AAAA,UACb,QAAQ;AAAA,UACR,eAAe;AAAA,UACf,iBAAiB;AAAA,UACjB,QAAQ,CAAC;AAAA,QACX,CAAC;AACD;AAAA,MACF;AAKA,YAAM,qBAAqB,IAAI;AAAA,QAC7B,aACG,IAAI,CAAC,MAAM,EAAE,SAAS,EACtB,OAAO,CAAC,MAAmB,OAAO,MAAM,YAAY,EAAE,SAAS,CAAC;AAAA,MACrE;AACA,YAAM,aAAqC,kBACvC,EAAE,GAAG,gBAAgB,OAAO,IAC5B,CAAC;AACL,YAAM,iBAA+D,CAAC;AACtE,UAAI,iBAAiB;AACnB,mBAAW,CAAC,GAAG,IAAI,KAAK,OAAO,QAAQ,gBAAgB,UAAU,GAAG;AAClE,yBAAe,CAAC,IAAI,CAAC,GAAG,IAAI;AAAA,QAC9B;AAAA,MACF;AAWA,YAAM,uBAAiC,CAAC;AACxC,UAAI,kBAAkB;AACtB,UAAI,sBAAsB;AAC1B,iBAAW,YAAY,WAAW;AAIhC,cAAM,kBACJ,YAAY,qBAAqB,qBAAqB;AACxD,YAAI,mBAAmB,GAAG;AACxB,6BAAmB;AACnB;AAAA,QACF;AAGA,YACE,SAAS,UAAU,eACnB,SAAS,UAAU,iBACnB,SAAS,UAAU,aACnB;AACA,gCAAsB;AACtB;AAAA,QACF;AAIA,YAAI,CAAC,+BAA+B,KAAK,SAAS,KAAK,GAAG;AACxD,gCAAsB;AACtB;AAAA,QACF;AACA,mBAAW,SAAS,KAAK,IAAI,SAAS;AACtC,cAAM,kBACJ,SAAS,mBACT,mBAAmB,IAAI,SAAS,eAAe,IAC3C,SAAS,kBACT;AACN,cAAM,YAAwC;AAAA,UAC5C,YAAY;AAAA,UACZ,QAAQ,SAAS;AAAA,UACjB,GAAI,kBAAkB,EAAE,gBAAgB,IAAI,CAAC;AAAA,UAC7C,GAAI,SAAS,QAAQ,SAAS,KAAK,SAAS,IACxC,EAAE,MAAM,SAAS,KAAK,IACtB,CAAC;AAAA,QACP;AACA,cAAM,OAAO,eAAe,SAAS,KAAK,KAAK,CAAC;AAChD,aAAK,KAAK,SAAS;AACnB,uBAAe,SAAS,KAAK,IAAI;AACjC,6BAAqB,KAAK,SAAS,KAAK;AAAA,MAI1C;AAEA,UAAI,qBAAqB,WAAW,GAAG;AACrC,eAAO,QAAQ,KAAK;AAAA,UAClB,QAAQ,KAAK;AAAA,UACb,QAAQ,sBACJ,6BACA,kBAAkB,IAChB,wBACA;AAAA,UACN,eAAe;AAAA,UACf;AAAA,UACA,QAAQ,CAAC;AAAA,QACX,CAAC;AACD;AAAA,MACF;AAEA,YAAM,SAAsB;AAAA,QAC1B,QAAQ,KAAK;AAAA,QACb,WAAW;AAAA,QACX,QAAQ;AAAA,QACR,YAAY;AAAA,MACd;AACA,YAAM,iBAAiB,QAAQ,WAAW,MAAM;AAKhD,4BAAsB,qBAAqB;AAQ3C,YAAM,cAAc,QAAQ,wBAAwB;AACpD,UAAI,aAAa;AACf,YAAI;AACF,gBAAM,qBAAqB,QAAQ,WAAW,KAAK,IAAI;AAAA,YACrD,WAAW;AAAA,YACX,MAAM;AAAA,YACN,SAAS,WAAW,qBAAqB,MAAM,iBAAiB,QAAQ,SAAS,eAAe;AAAA,UAClG,CAAC;AAAA,QACH,SAAS,KAAK;AACZ,cAAI;AAAA,YACF,yDAAyD,KAAK,EAAE,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,UACxH;AAAA,QACF;AAAA,MACF;AAEA,aAAO,QAAQ,KAAK;AAAA,QAClB,QAAQ,KAAK;AAAA,QACb,QAAQ;AAAA,QACR,eAAe,qBAAqB;AAAA,QACpC;AAAA,QACA,QAAQ;AAAA,MACV,CAAC;AACD,aAAO,kBAAkB;AACzB,aAAO,gBAAgB;AAAA,IACzB,SAAS,KAAK;AACZ,UAAI;AAAA,QACF,kDAAkD,KAAK,EAAE,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,MACjH;AACA,aAAO,QAAQ,KAAK;AAAA,QAClB,QAAQ,KAAK;AAAA,QACb,QAAQ;AAAA,QACR,eAAe;AAAA,QACf,iBAAiB;AAAA,QACjB,QAAQ,CAAC;AAAA,QACT,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MACxD,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var RECALL_DISCLOSURE_LEVELS = [
|
|
3
|
+
"chunk",
|
|
4
|
+
"section",
|
|
5
|
+
"raw"
|
|
6
|
+
];
|
|
7
|
+
var DEFAULT_RECALL_DISCLOSURE = "chunk";
|
|
8
|
+
function isRecallDisclosure(value) {
|
|
9
|
+
return typeof value === "string" && RECALL_DISCLOSURE_LEVELS.includes(value);
|
|
10
|
+
}
|
|
11
|
+
function confidenceTier(score) {
|
|
12
|
+
if (score >= 0.95) return "explicit";
|
|
13
|
+
if (score >= 0.7) return "implied";
|
|
14
|
+
if (score >= 0.4) return "inferred";
|
|
15
|
+
return "speculative";
|
|
16
|
+
}
|
|
17
|
+
var SPECULATIVE_TTL_DAYS = 30;
|
|
18
|
+
|
|
19
|
+
export {
|
|
20
|
+
RECALL_DISCLOSURE_LEVELS,
|
|
21
|
+
DEFAULT_RECALL_DISCLOSURE,
|
|
22
|
+
isRecallDisclosure,
|
|
23
|
+
confidenceTier,
|
|
24
|
+
SPECULATIVE_TTL_DAYS
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=chunk-43EKP2UK.js.map
|