chainlesschain 0.162.143 → 0.162.148
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +3 -3
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-Bq1t-B4V.js → AIOps-DwpCCp64.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BHb3KMc3.js → ActionButton-C9pBYocA.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DtJrE99O.js → Analytics-BWAkeXxK.js} +2 -2
- package/src/assets/web-panel/assets/{AppLayout-HXt309zh.js → AppLayout-CivFGH6B.js} +3 -3
- package/src/assets/web-panel/assets/{Audit-B-0384hX.js → Audit-Cm8hRpZm.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BXNwUJ7y.js → Backup-DXdFMsE8.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CYdSBe24.js → BaseInput-uAwSTeql.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-C5hSt-bf.js → Chat-KU8eeJJE.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-DCNfbdlx.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-DZSpGJs8.js → Checkbox-C6AMDkjd.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BKfMmpq3.js → Codegen-t2moDxcO.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DgsxR9Gb.js → Col-DCcsRRhN.js} +1 -1
- package/src/assets/web-panel/assets/{Community-B3GhKOp_.js → Community-DtB-8SAC.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-Dtk1xHSY.js → Compact-CZm8THYA.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DmkkUDze.js → Compliance-LiQgC7g1.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CB_Tby_i.js → Cowork-CA8SAxht.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-BLQFn3UU.js → Cron-CtRaF1wD.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-LCVSqtiL.js → Crosschain-6-br6Hub.js} +1 -1
- package/src/assets/web-panel/assets/{DID-Cv3xor-m.js → DID-Do2EccrL.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-DHAEYeUk.js → Dashboard-wrXcbzGe.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-FAp99VHO.js → Dropdown-p3FsT245.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-Cl4enKIT.js → EmailListRenderer-D95A2L8q.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CgbuK_Rv.js → FamilyGuardDashboard-cLuJW2zo.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-C2I5-34o.js → Federation-Brgq_cbN.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-4RNlhf3D.js → FormItemContext-DWLCkNgh.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DmaKYzyo.js → GenericCardRenderer-BBqUfQd6.js} +1 -1
- package/src/assets/web-panel/assets/{Git-BMrCOEGV.js → Git-8F090w4I.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BLwFR0Vd.js → Governance-CCogEbxb.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-Cdlx3Y4t.js → Inference-CfLD7v7C.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-CiwGb2h3.js → KnowledgeGraph-DDuHJewd.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-C6A4r33C.js → Logs-CE8KSEVC.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-DdybH2YZ.js → Marketplace-DmwpZmhT.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-pgwwEzwg.js → McpTools-Bf7AazU8.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-BQ31MLPN.js → Memory-D0QU_00S.js} +2 -2
- package/src/assets/web-panel/assets/MobileBridge-D3Arxfeg.css +1 -0
- package/src/assets/web-panel/assets/MobileBridge-yXS9xdhx.js +1 -0
- package/src/assets/web-panel/assets/{MobileProjects-CmnCTggB.js → MobileProjects-BuDUoGUP.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-3K6RfaUB.js → Mtc-BYyHy28p.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-BN9hXvXq.js → MtcAudit-CK0aqpiZ.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-CRxIT2RV.js → Multisig-mNimKYeb.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CdPvh_dA.js → NLProgramming-AioAJ0YA.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DzQSZ0ps.js → Notes-CVVNCLHE.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-DkqRVvfb.js → NotificationSettings-C4ABj-TK.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-ClTA64ji.js → OrderTableRenderer-DIp8Xcbd.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-B-Pndhhn.js → Organization-uozl_gWK.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-Dt56UM9b.js → Overflow-BCZOM2hK.js} +1 -1
- package/src/assets/web-panel/assets/{OverrideContext-C2r4vyBb.js → OverrideContext-BlgL9440.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-JDGQmED0.js → P2P-CArcaiaj.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-Sgq2Srr8.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-Dnqe1sny.js → Permissions-BYrQrXnH.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-mTkYbIR-.js → PersonalDataHub-BzHStAbz.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-BG6J9UZO.js → Pipeline-k7KqxX1M.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BdEydH6w.js → Privacy-IumTUWqx.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-QPxCoUzs.js → ProjectInit-BRyImYTf.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-hU_phubM.js → ProjectSettings-BFIqTBFk.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BBzAEGk4.js → Projects-Dpid9CDg.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-DJ_bIDRG.js → Providers-CyyFnUPs.js} +1 -1
- package/src/assets/web-panel/assets/QrScannerModal-C82cRx-X.js +1 -0
- package/src/assets/web-panel/assets/{MobileBridge-zJpPuf4E.css → QrScannerModal-NwAHNfiJ.css} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BNmX7Jgh.js → QuickAsk-DGxDF521.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Bo1I5nAG.js → Recommend-Cns-Am1y.js} +1 -1
- package/src/assets/web-panel/assets/RemoteSession-R7OqAIlg.js +4 -0
- package/src/assets/web-panel/assets/{Reputation-Bc9mfsh7.js → Reputation-D7mgPIYV.js} +1 -1
- package/src/assets/web-panel/assets/{Row-D12ZZx5m.js → Row-B3lEURyd.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-CXSlmgHl.js → RssFeed-9_UVrpBt.js} +2 -2
- package/src/assets/web-panel/assets/{Search-bNXObahl.js → Search-ClZeO80q.js} +1 -1
- package/src/assets/web-panel/assets/{Security-Bt7yBkJc.js → Security-BNNJczEp.js} +3 -3
- package/src/assets/web-panel/assets/{Services-ECQDydWI.js → Services-C5SjrWUj.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-BcgzLbTM.js → Skeleton-Ci_obQ-g.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-py_xMG8b.js → Skills-DdebN15P.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-CzNvjwaW.js → Sla-OinZP2vi.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-S3zMMqbB.js → SpeechSettings-CxzbNF51.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-BpDfhFv6.js → SyncSettings-D06N_66b.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-CUn36tpM.js → Tasks-BdEdW0AZ.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-yzCxc3lI.js → Templates-F1ctKmPa.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BTMrYsHv.js → Tenant-CVz1Urot.js} +1 -1
- package/src/assets/web-panel/assets/Terminal-BvAK_Zel.js +3 -0
- package/src/assets/web-panel/assets/{TimelineRenderer-BwJ4nL9S.js → TimelineRenderer-Doitzjmf.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-B1OwTaT_.js → Tokens-BpyVRpVA.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BDxi9fib.js → Trigger-nWhWYTbU.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-B_5hXqzI.js → Trust-DK_G-wLx.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-BpIMO611.js → UkeySign-B5yBUojO.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-Ds-9lxOW.js → VideoEditing-C5D51qAe.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CNfFp8if.js → Wallet-CLbL9K7v.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DbCj1-qn.js → WebAuthn-DZUelI3V.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-DjEeb957.js → WorkflowEditor-8RPxt4KW.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BX-27gI4.js → chat-4N4tOA3d.js} +1 -1
- package/src/assets/web-panel/assets/{collapseMotion-mxzzcDsg.js → collapseMotion-DVfhbsdP.js} +1 -1
- package/src/assets/web-panel/assets/{colors-CfM5FmdA.js → colors-zbbGVrua.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-Cb0Fm64g.js → compact-item-D7iMkbnE.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-D99MZ4gz.js → createContext-CBzPJv-w.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-C2Z5LRgq.js +1 -0
- package/src/assets/web-panel/assets/{echarts-jxgIOFUZ.js → echarts-CL0F2SnX.js} +1 -1
- package/src/assets/web-panel/assets/{hasIn-CW7wPkfb.js → hasIn-bHdrQSqZ.js} +1 -1
- package/src/assets/web-panel/assets/{icons-DP3uiYxy.js → icons-BsDmGpcT.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcCSEGzu.js → index-308gCGh7.js} +1 -1
- package/src/assets/web-panel/assets/{index-CXrNkD5j.js → index-B1s0Bz9O.js} +1 -1
- package/src/assets/web-panel/assets/{index-CxG5S21O.js → index-B7b1hGry.js} +1 -1
- package/src/assets/web-panel/assets/{index-BoCp5SY7.js → index-BBNr77E1.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dt-h8idq.js → index-BCr0geV9.js} +1 -1
- package/src/assets/web-panel/assets/index-BGp6Yr-Y.js +1 -0
- package/src/assets/web-panel/assets/{index-Bf7G5mYl.js → index-BJ4ZGyW0.js} +1 -1
- package/src/assets/web-panel/assets/{index-rIKv98lY.js → index-BS4_Mwk6.js} +1 -1
- package/src/assets/web-panel/assets/{index-ClSExghA.js → index-BTIjjXry.js} +1 -1
- package/src/assets/web-panel/assets/index-BXiw9dQf.js +1 -0
- package/src/assets/web-panel/assets/{index-CzwoBX8m.js → index-BZb8F8YG.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUENEaPU.js → index-BnBOpqv3.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZjN-U7A.js → index-Bqmx24kh.js} +1 -1
- package/src/assets/web-panel/assets/{index-ab4T5cPr.js → index-BsKehmmS.js} +1 -1
- package/src/assets/web-panel/assets/{index-DDGBasj6.js → index-BtbbdLnf.js} +4 -4
- package/src/assets/web-panel/assets/{index-CvFAgMxx.js → index-CPxkoKgA.js} +1 -1
- package/src/assets/web-panel/assets/{index-BqOy4TS5.js → index-CQ6NcfWz.js} +1 -1
- package/src/assets/web-panel/assets/{index--Y8IzVi2.js → index-CQfu1U78.js} +1 -1
- package/src/assets/web-panel/assets/{index-DcRbrdqR.js → index-CahryTX0.js} +1 -1
- package/src/assets/web-panel/assets/{index-ChGWrUQv.js → index-CdmMoYN1.js} +1 -1
- package/src/assets/web-panel/assets/{index-BTlOIaQj.js → index-Ci009ijp.js} +1 -1
- package/src/assets/web-panel/assets/{index-CNhZanpM.js → index-CiG1IZao.js} +1 -1
- package/src/assets/web-panel/assets/{index-kRlIAKNw.js → index-CkxK86vf.js} +1 -1
- package/src/assets/web-panel/assets/{index-DAd31-6W.js → index-CqdPyWm5.js} +1 -1
- package/src/assets/web-panel/assets/{index-kt0S8nXp.js → index-CvRMA9uT.js} +1 -1
- package/src/assets/web-panel/assets/{index-CxFhtfoo.js → index-D1YDh7Wr.js} +1 -1
- package/src/assets/web-panel/assets/{index-7cahuPKG.js → index-D4XKpj6c.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cavk4P2D.js → index-DE9j5YgT.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUxnWbi6.js → index-DISWAYce.js} +1 -1
- package/src/assets/web-panel/assets/{index-CPmtR1Wr.js → index-DUyjUkY7.js} +1 -1
- package/src/assets/web-panel/assets/{index-DFRrP3LL.js → index-DbE5D0WL.js} +1 -1
- package/src/assets/web-panel/assets/{index-DAIk483F.js → index-DmqlJed-.js} +1 -1
- package/src/assets/web-panel/assets/{index-yBiqcb9Z.js → index-DyoNgbXl.js} +1 -1
- package/src/assets/web-panel/assets/{index-DwhDhfZ8.js → index-QFObYeo1.js} +1 -1
- package/src/assets/web-panel/assets/{index-B8tI4Hg_.js → index-QNAG4JtR.js} +1 -1
- package/src/assets/web-panel/assets/{index-BPXhpO02.js → index-S74FpAIn.js} +1 -1
- package/src/assets/web-panel/assets/{index-CaPluhcX.js → index-kmh1TxRa.js} +1 -1
- package/src/assets/web-panel/assets/{index-3TDpYBKK.js → index-oDKNgCsP.js} +1 -1
- package/src/assets/web-panel/assets/{index-D8U1HFcm.js → index-xYCm6W2h.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-mzQV01we.js → initDefaultProps-BD55yNBt.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DgWAtKps.js → motion-B5Bbe77U.js} +1 -1
- package/src/assets/web-panel/assets/{move-Dm9NMjIw.js → move-CTvIqHEH.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BpuQIoKH.js → mtc-parser-BkNyPQKB.js} +1 -1
- package/src/assets/web-panel/assets/{omit-C507FdFH.js → omit-BaXJXgHA.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-BL5eykDR.js → pickAttrs-Bw6-YTxH.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-uSE1U6yg.js → placementArrow-BLdbkLqJ.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BVhSz_U1.js → responsiveObserve-IVYkzntU.js} +1 -1
- package/src/assets/web-panel/assets/{slide-CWCp5XZc.js → slide-DFMFwwtY.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-B_rS9tlX.js → statusUtils-CURYRtZ1.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DRo4TNq4.js → styleChecker-Cfz63s1r.js} +1 -1
- package/src/assets/web-panel/assets/useFlexGapSupport-COJL0KE3.js +1 -0
- package/src/assets/web-panel/assets/{useFs-DhsLsCr_.js → useFs-YLLojQQf.js} +1 -1
- package/src/assets/web-panel/assets/{useMergedState-Cl2q1fes.js → useMergedState-Dc9sO2d7.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BXI-FLwS.js → usePersonalDataHub-BF_21qUC.js} +1 -1
- package/src/assets/web-panel/assets/{useRefs-DD9q4mOT.js → useRefs-CAeFRzPf.js} +1 -1
- package/src/assets/web-panel/assets/{useState-BK4hy8Ma.js → useState-LwExUYDO.js} +1 -1
- package/src/assets/web-panel/assets/{vendor-BvqAck49.js → vendor-BMxUs6UX.js} +16 -16
- package/src/assets/web-panel/assets/{vnode-CYE0CVkI.js → vnode-9ZpkA7bb.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-B2-MtXVF.js → zoom-DhtAfhl8.js} +1 -1
- package/src/assets/web-panel/index.html +3 -3
- package/src/auth/npm-auth.js +65 -0
- package/src/commands/agent.js +92 -0
- package/src/commands/agents.js +68 -0
- package/src/commands/permissions.js +66 -10
- package/src/commands/plugin.js +50 -3
- package/src/commands/serve.js +10 -0
- package/src/gateways/remote-session-relay.js +160 -0
- package/src/gateways/ws/message-dispatcher.js +32 -0
- package/src/gateways/ws/remote-session-protocol.js +429 -0
- package/src/gateways/ws/ws-server.js +299 -0
- package/src/harness/golden-transcript.js +65 -0
- package/src/harness/mcp-client.js +6 -2
- package/src/harness/remote-session-audit-sink.js +132 -0
- package/src/harness/remote-session-audit.js +110 -0
- package/src/harness/remote-session-crypto.js +217 -0
- package/src/harness/remote-session-push-fcm.js +254 -0
- package/src/harness/remote-session-push.js +104 -0
- package/src/harness/remote-session-registry.js +420 -0
- package/src/lib/agent-sandbox.js +72 -0
- package/src/lib/background-agent-supervisor.js +197 -0
- package/src/lib/code-review.js +72 -0
- package/src/lib/did-manager.js +6 -4
- package/src/lib/mcp-oauth.js +23 -6
- package/src/lib/plugin-security.js +128 -0
- package/src/lib/pr-create.js +108 -0
- package/src/lib/publish-workspace.js +107 -0
- package/src/lib/resume-session.js +111 -0
- package/src/lib/session-manager.js +7 -3
- package/src/lib/settings-hooks.cjs +68 -25
- package/src/lib/settings-loader.cjs +115 -2
- package/src/lib/task-list.js +53 -0
- package/src/repl/agent-repl.js +83 -2
- package/src/repl/agents-status.js +8 -2
- package/src/repl/btw-command.js +56 -0
- package/src/repl/chat-repl.js +52 -1
- package/src/repl/hooks-status.js +1 -2
- package/src/repl/slash-command-registry.js +197 -0
- package/src/runtime/agent-core.js +137 -1
- package/src/runtime/agent-runtime.js +8 -13
- package/src/runtime/coding-agent-contract-shared.cjs +37 -0
- package/src/runtime/coding-agent-policy.cjs +14 -0
- package/src/runtime/headless-runner.js +26 -6
- package/src/runtime/headless-stream.js +25 -6
- package/src/runtime/mcp-config.js +46 -2
- package/src/runtime/policies/agent-policy.js +3 -0
- package/src/workers/background-agent-worker.js +60 -0
- package/src/assets/web-panel/assets/ChatBubbleRenderer-BJQ6SG49.js +0 -1
- package/src/assets/web-panel/assets/MobileBridge-CnRXuSP5.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-C56Oi811.js +0 -7
- package/src/assets/web-panel/assets/Terminal-BjZry1Jj.js +0 -3
- package/src/assets/web-panel/assets/devWarning-CDh1y3xt.js +0 -1
- package/src/assets/web-panel/assets/index-ACeVMo7x.js +0 -1
- package/src/assets/web-panel/assets/index-gYKDzWA9.js +0 -1
- package/src/assets/web-panel/assets/useFlexGapSupport-QsPzwpvi.js +0 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code review utilities (ESM)
|
|
3
|
+
* Mirrors Claude Code /review behavior
|
|
4
|
+
* @module lib/code-review
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { execSync } from "child_process";
|
|
8
|
+
import { logger } from "./logger.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Get git diff for review
|
|
12
|
+
* @param {string} target - Diff target (default: staged changes)
|
|
13
|
+
* @returns {string} diff output
|
|
14
|
+
*/
|
|
15
|
+
export function getGitDiff(target = "--staged") {
|
|
16
|
+
try {
|
|
17
|
+
return execSync(`git diff ${target}`, {
|
|
18
|
+
encoding: "utf8",
|
|
19
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
20
|
+
});
|
|
21
|
+
} catch (err) {
|
|
22
|
+
logger.error(`Failed to get git diff: ${err.message}`);
|
|
23
|
+
return "";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get list of changed files
|
|
29
|
+
* @param {string} target - Diff target
|
|
30
|
+
* @returns {string[]} changed files
|
|
31
|
+
*/
|
|
32
|
+
export function getChangedFiles(target = "--staged") {
|
|
33
|
+
try {
|
|
34
|
+
const output = execSync(`git diff ${target} --name-only`, {
|
|
35
|
+
encoding: "utf8",
|
|
36
|
+
});
|
|
37
|
+
return output.trim().split("\n").filter(Boolean);
|
|
38
|
+
} catch (err) {
|
|
39
|
+
logger.error(`Failed to get changed files: ${err.message}`);
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Run code review on current changes
|
|
46
|
+
* @param {Object} options Review options
|
|
47
|
+
* @returns {Promise<Object>} review result
|
|
48
|
+
*/
|
|
49
|
+
export async function runCodeReview(options = {}) {
|
|
50
|
+
const target = options.target || "--staged";
|
|
51
|
+
const diff = getGitDiff(target);
|
|
52
|
+
const files = getChangedFiles(target);
|
|
53
|
+
|
|
54
|
+
if (files.length === 0) {
|
|
55
|
+
logger.info("No changed files to review");
|
|
56
|
+
return { reviewed: false, files: [], issues: [] };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
logger.info(`Reviewing ${files.length} changed files...`);
|
|
60
|
+
files.forEach((f) => logger.info(` - ${f}`));
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
reviewed: true,
|
|
64
|
+
files,
|
|
65
|
+
diffSize: diff.length,
|
|
66
|
+
// Actual AI review will be handled by agent core
|
|
67
|
+
message:
|
|
68
|
+
"Code review request prepared. AI will analyze changes for bugs, security issues, and style problems.",
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export default { getGitDiff, getChangedFiles, runCodeReview };
|
package/src/lib/did-manager.js
CHANGED
|
@@ -163,10 +163,12 @@ export function setDefaultIdentity(db, did) {
|
|
|
163
163
|
const identity = getIdentity(db, did);
|
|
164
164
|
if (!identity) return false;
|
|
165
165
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
166
|
+
// Clear the current default(s). Match on is_default rather than `did LIKE '%'`
|
|
167
|
+
// so there's no wildcard pattern here that a reader/scanner could mistake for
|
|
168
|
+
// LIKE-injection — the target below is set via exact `did = ?` regardless.
|
|
169
|
+
db.prepare(
|
|
170
|
+
"UPDATE did_identities SET is_default = 0 WHERE is_default = 1",
|
|
171
|
+
).run();
|
|
170
172
|
db.prepare("UPDATE did_identities SET is_default = ? WHERE did = ?").run(
|
|
171
173
|
1,
|
|
172
174
|
identity.did,
|
package/src/lib/mcp-oauth.js
CHANGED
|
@@ -35,6 +35,7 @@ export const _deps = {
|
|
|
35
35
|
sha256: (s) => crypto.createHash("sha256").update(s).digest(),
|
|
36
36
|
createServer: (h) => http.createServer(h),
|
|
37
37
|
openBrowser: defaultOpenBrowser,
|
|
38
|
+
spawn: (...a) => spawn(...a),
|
|
38
39
|
now: () => Date.now(),
|
|
39
40
|
// Backoff sleep seam (tests override with a no-op so the retry doesn't wait).
|
|
40
41
|
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
@@ -648,13 +649,29 @@ export async function ensureValidToken(
|
|
|
648
649
|
|
|
649
650
|
// ── interactive orchestrator ──────────────────────────────────────────────
|
|
650
651
|
|
|
651
|
-
function defaultOpenBrowser(url) {
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
652
|
+
export function defaultOpenBrowser(url, platform = process.platform) {
|
|
653
|
+
// The authorize URL derives from REMOTE OAuth server metadata, so treat it
|
|
654
|
+
// as untrusted: only http(s) may reach the OS opener (no file:/ms-settings:/
|
|
655
|
+
// custom-scheme handlers), and it must never pass through cmd.exe's line
|
|
656
|
+
// parser — `cmd /c start "" <url>` truncates at the first unquoted `&` and
|
|
657
|
+
// EXECUTES the remainder as commands (query strings always contain `&`).
|
|
658
|
+
let parsed;
|
|
656
659
|
try {
|
|
657
|
-
|
|
660
|
+
parsed = new URL(url);
|
|
661
|
+
} catch {
|
|
662
|
+
return false;
|
|
663
|
+
}
|
|
664
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return false;
|
|
665
|
+
// win32: rundll32 is a plain executable — the URL arrives as a verbatim
|
|
666
|
+
// argv entry with no shell metacharacter interpretation.
|
|
667
|
+
const [cmd, args] =
|
|
668
|
+
platform === "win32"
|
|
669
|
+
? ["rundll32", ["url.dll,FileProtocolHandler", parsed.href]]
|
|
670
|
+
: platform === "darwin"
|
|
671
|
+
? ["open", [parsed.href]]
|
|
672
|
+
: ["xdg-open", [parsed.href]];
|
|
673
|
+
try {
|
|
674
|
+
const child = _deps.spawn(cmd, args, { stdio: "ignore", detached: true });
|
|
658
675
|
child.unref?.();
|
|
659
676
|
return true;
|
|
660
677
|
} catch {
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { createHash, createPublicKey, verify } from "node:crypto";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { loadManagedSettings } from "./settings-loader.cjs";
|
|
4
|
+
|
|
5
|
+
function stringSet(value) {
|
|
6
|
+
return Array.isArray(value)
|
|
7
|
+
? new Set(
|
|
8
|
+
value
|
|
9
|
+
.map((v) =>
|
|
10
|
+
typeof v === "string" ? v : v?.name || v?.source || v?.url || null,
|
|
11
|
+
)
|
|
12
|
+
.map((v) => String(v || "").trim())
|
|
13
|
+
.filter(Boolean),
|
|
14
|
+
)
|
|
15
|
+
: null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function enforcePluginPolicy(
|
|
19
|
+
{ name, source = null, action = "install" },
|
|
20
|
+
managed,
|
|
21
|
+
) {
|
|
22
|
+
if (!managed) return { allowed: true };
|
|
23
|
+
const denied = stringSet(managed.deniedPlugins) || new Set();
|
|
24
|
+
const allowed = stringSet(managed.allowedPlugins);
|
|
25
|
+
if (denied.has(name)) {
|
|
26
|
+
throw new Error(`plugin "${name}" is denied by managed settings`);
|
|
27
|
+
}
|
|
28
|
+
if (allowed && !allowed.has(name)) {
|
|
29
|
+
throw new Error(`plugin "${name}" is not in the managed allowlist`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const blockedSources = new Set([
|
|
33
|
+
...(stringSet(managed.blockedPluginSources) || []),
|
|
34
|
+
...(stringSet(managed.blockedMarketplaces) || []),
|
|
35
|
+
]);
|
|
36
|
+
const allowedSources = stringSet(managed.allowedPluginSources);
|
|
37
|
+
if (source && blockedSources.has(source)) {
|
|
38
|
+
throw new Error(`plugin source "${source}" is blocked by managed settings`);
|
|
39
|
+
}
|
|
40
|
+
if (action === "install" && allowedSources) {
|
|
41
|
+
if (!source) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
"managed settings require --source for plugin installation",
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
if (!allowedSources.has(source)) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`plugin source "${source}" is not in the managed allowlist`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return { allowed: true };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function verifyPluginManifest({
|
|
56
|
+
manifestFile,
|
|
57
|
+
expectedSha256,
|
|
58
|
+
signatureFile,
|
|
59
|
+
publicKeyFile,
|
|
60
|
+
requireSignature = false,
|
|
61
|
+
trustedKeySha256 = null,
|
|
62
|
+
requireTrustedKey = false,
|
|
63
|
+
}) {
|
|
64
|
+
if (!manifestFile) {
|
|
65
|
+
if (requireSignature) {
|
|
66
|
+
throw new Error("managed settings require a signed plugin manifest");
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const bytes = readFileSync(manifestFile);
|
|
71
|
+
const sha256 = createHash("sha256").update(bytes).digest("hex");
|
|
72
|
+
if (
|
|
73
|
+
expectedSha256 &&
|
|
74
|
+
sha256.toLowerCase() !== String(expectedSha256).toLowerCase()
|
|
75
|
+
) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`plugin manifest SHA-256 mismatch (expected ${expectedSha256}, got ${sha256})`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const wantsSignature = requireSignature || signatureFile || publicKeyFile;
|
|
82
|
+
let signatureVerified = false;
|
|
83
|
+
if (wantsSignature) {
|
|
84
|
+
if (!signatureFile || !publicKeyFile) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
"plugin signature verification requires --signature and --public-key",
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
const signature = readFileSync(signatureFile);
|
|
90
|
+
const publicKey = readFileSync(publicKeyFile, "utf8");
|
|
91
|
+
const keyObject = createPublicKey(publicKey);
|
|
92
|
+
const publicKeySha256 = createHash("sha256")
|
|
93
|
+
.update(keyObject.export({ type: "spki", format: "der" }))
|
|
94
|
+
.digest("hex");
|
|
95
|
+
const trusted = stringSet(trustedKeySha256);
|
|
96
|
+
if (requireTrustedKey && (!trusted || trusted.size === 0)) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
"managed settings require trustedPluginKeySha256 fingerprints",
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
if (trusted && !trusted.has(publicKeySha256)) {
|
|
102
|
+
throw new Error(`plugin signing key is not trusted (${publicKeySha256})`);
|
|
103
|
+
}
|
|
104
|
+
signatureVerified = verify(null, bytes, keyObject, signature);
|
|
105
|
+
if (!signatureVerified) {
|
|
106
|
+
throw new Error("plugin manifest signature verification failed");
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
bytes,
|
|
111
|
+
sha256,
|
|
112
|
+
signatureVerified,
|
|
113
|
+
publicKeySha256: signatureVerified
|
|
114
|
+
? createHash("sha256")
|
|
115
|
+
.update(
|
|
116
|
+
createPublicKey(readFileSync(publicKeyFile, "utf8")).export({
|
|
117
|
+
type: "spki",
|
|
118
|
+
format: "der",
|
|
119
|
+
}),
|
|
120
|
+
)
|
|
121
|
+
.digest("hex")
|
|
122
|
+
: null,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function loadPluginManagedPolicy(options = {}) {
|
|
127
|
+
return loadManagedSettings(options).settings;
|
|
128
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pull request creation utilities (ESM)
|
|
3
|
+
* Mirrors Claude Code /pr behavior
|
|
4
|
+
* @module lib/pr-create
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { execSync } from "child_process";
|
|
8
|
+
import { logger } from "./logger.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Get current git branch
|
|
12
|
+
* @returns {string} branch name
|
|
13
|
+
*/
|
|
14
|
+
export function getCurrentBranch() {
|
|
15
|
+
try {
|
|
16
|
+
return execSync("git rev-parse --abbrev-ref HEAD", {
|
|
17
|
+
encoding: "utf8",
|
|
18
|
+
}).trim();
|
|
19
|
+
} catch {
|
|
20
|
+
return "main";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get remote URL
|
|
26
|
+
* @returns {string|null}
|
|
27
|
+
*/
|
|
28
|
+
export function getRemoteUrl() {
|
|
29
|
+
try {
|
|
30
|
+
return execSync("git remote get-url origin", { encoding: "utf8" }).trim();
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Extract GitHub owner/repo from remote URL
|
|
38
|
+
* @returns {{owner: string, repo: string}|null}
|
|
39
|
+
*/
|
|
40
|
+
export function parseGitHubRepo() {
|
|
41
|
+
const url = getRemoteUrl();
|
|
42
|
+
if (!url) return null;
|
|
43
|
+
|
|
44
|
+
// Handle SSH and HTTPS URLs
|
|
45
|
+
const match = url.match(/github\.com[:/]([^/]+)\/([^/.]+)(\.git)?/);
|
|
46
|
+
if (!match) return null;
|
|
47
|
+
|
|
48
|
+
return { owner: match[1], repo: match[2] };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Prepare pull request
|
|
53
|
+
* @param {Object} options PR options
|
|
54
|
+
* @returns {Promise<Object>} PR creation result
|
|
55
|
+
*/
|
|
56
|
+
export async function preparePullRequest(options = {}) {
|
|
57
|
+
const branch = getCurrentBranch();
|
|
58
|
+
const repo = parseGitHubRepo();
|
|
59
|
+
|
|
60
|
+
// Check for uncommitted changes
|
|
61
|
+
const status = execSync("git status --porcelain", { encoding: "utf8" });
|
|
62
|
+
const hasChanges = status.trim().length > 0;
|
|
63
|
+
|
|
64
|
+
if (hasChanges && !options.skipStash) {
|
|
65
|
+
logger.warn(
|
|
66
|
+
"You have uncommitted changes. Commit or stash before creating PR.",
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Get commit messages for PR body
|
|
71
|
+
const defaultBase = options.base || "main";
|
|
72
|
+
const logRange = `${defaultBase}...${branch}`;
|
|
73
|
+
let commitLog = "";
|
|
74
|
+
try {
|
|
75
|
+
commitLog = execSync(`git log ${logRange} --oneline`, { encoding: "utf8" });
|
|
76
|
+
} catch {
|
|
77
|
+
commitLog = "";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const prUrl = repo
|
|
81
|
+
? `https://github.com/${repo.owner}/${repo.repo}/compare/${defaultBase}...${branch}?expand=1`
|
|
82
|
+
: null;
|
|
83
|
+
|
|
84
|
+
logger.info(`Branch: ${branch}`);
|
|
85
|
+
logger.info(`Base: ${defaultBase}`);
|
|
86
|
+
if (prUrl) {
|
|
87
|
+
logger.info(`Open PR in browser: ${prUrl}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
prepared: true,
|
|
92
|
+
branch,
|
|
93
|
+
base: defaultBase,
|
|
94
|
+
repo,
|
|
95
|
+
commitLog,
|
|
96
|
+
prUrl,
|
|
97
|
+
hasUncommittedChanges: hasChanges,
|
|
98
|
+
message:
|
|
99
|
+
"PR draft prepared. AI will help generate title and body from commits.",
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export default {
|
|
104
|
+
getCurrentBranch,
|
|
105
|
+
getRemoteUrl,
|
|
106
|
+
parseGitHubRepo,
|
|
107
|
+
preparePullRequest,
|
|
108
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Publish workspace packages to npm (ESM)
|
|
3
|
+
* Mirrors Claude Code /publish behavior
|
|
4
|
+
* @module lib/publish-workspace
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { execSync } from "child_process";
|
|
8
|
+
import { logger } from "./logger.js";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import fs from "fs";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get all workspace packages
|
|
14
|
+
* @returns {Array<{name: string, path: string, version: string}>}
|
|
15
|
+
*/
|
|
16
|
+
export function getWorkspacePackages() {
|
|
17
|
+
try {
|
|
18
|
+
const rootPkg = JSON.parse(fs.readFileSync("package.json", "utf8"));
|
|
19
|
+
const packages = [];
|
|
20
|
+
|
|
21
|
+
// Handle workspaces config
|
|
22
|
+
const workspaces = rootPkg.workspaces?.packages || rootPkg.workspaces || [];
|
|
23
|
+
for (const pattern of workspaces) {
|
|
24
|
+
// Simple glob handling for basic patterns
|
|
25
|
+
const dir = pattern.replace(/\*+/g, "");
|
|
26
|
+
if (fs.existsSync(dir)) {
|
|
27
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
28
|
+
for (const entry of entries) {
|
|
29
|
+
if (entry.isDirectory()) {
|
|
30
|
+
const pkgPath = path.join(dir, entry.name);
|
|
31
|
+
const pkgFile = path.join(pkgPath, "package.json");
|
|
32
|
+
if (fs.existsSync(pkgFile)) {
|
|
33
|
+
const pkg = JSON.parse(fs.readFileSync(pkgFile, "utf8"));
|
|
34
|
+
if (pkg.name && !pkg.private) {
|
|
35
|
+
packages.push({
|
|
36
|
+
name: pkg.name,
|
|
37
|
+
path: pkgPath,
|
|
38
|
+
version: pkg.version,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return packages;
|
|
48
|
+
} catch (err) {
|
|
49
|
+
logger.error(`Failed to get workspace packages: ${err.message}`);
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Publish a single package
|
|
56
|
+
* @param {Object} pkg Package info
|
|
57
|
+
* @param {Object} options Publish options
|
|
58
|
+
* @returns {boolean} success
|
|
59
|
+
*/
|
|
60
|
+
export function publishPackage(pkg, options = {}) {
|
|
61
|
+
try {
|
|
62
|
+
logger.info(`Publishing ${pkg.name}@${pkg.version}...`);
|
|
63
|
+
let cmd = "npm publish";
|
|
64
|
+
if (options.tag) cmd += ` --tag ${options.tag}`;
|
|
65
|
+
if (options.access) cmd += ` --access ${options.access}`;
|
|
66
|
+
|
|
67
|
+
execSync(cmd, {
|
|
68
|
+
cwd: pkg.path,
|
|
69
|
+
stdio: "inherit",
|
|
70
|
+
encoding: "utf8",
|
|
71
|
+
});
|
|
72
|
+
logger.succeed(`Published ${pkg.name}@${pkg.version}`);
|
|
73
|
+
return true;
|
|
74
|
+
} catch (err) {
|
|
75
|
+
logger.error(`Failed to publish ${pkg.name}: ${err.message}`);
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Publish all changed workspace packages
|
|
82
|
+
* @param {Object} options Options
|
|
83
|
+
* @returns {Promise<boolean>} success
|
|
84
|
+
*/
|
|
85
|
+
export async function publishWorkspace(options = {}) {
|
|
86
|
+
const packages = getWorkspacePackages();
|
|
87
|
+
if (packages.length === 0) {
|
|
88
|
+
logger.info("No publishable packages found");
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
logger.info(`Found ${packages.length} publishable packages`);
|
|
93
|
+
let allSucceeded = true;
|
|
94
|
+
|
|
95
|
+
for (const pkg of packages) {
|
|
96
|
+
if (options.dryRun) {
|
|
97
|
+
logger.info(`[dry-run] Would publish ${pkg.name}@${pkg.version}`);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const ok = publishPackage(pkg, options);
|
|
101
|
+
if (!ok) allSucceeded = false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return allSucceeded;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export default { getWorkspacePackages, publishPackage, publishWorkspace };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session resume utilities (ESM)
|
|
3
|
+
* Mirrors Claude Code /resume behavior
|
|
4
|
+
* @module lib/resume-session
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import os from "os";
|
|
10
|
+
import { logger } from "./logger.js";
|
|
11
|
+
|
|
12
|
+
const SESSION_DIR = path.join(os.homedir(), ".chainlesschain", "sessions");
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* List recent chat sessions
|
|
16
|
+
* @param {number} limit Maximum sessions to return
|
|
17
|
+
* @returns {Array<Object>} list of sessions
|
|
18
|
+
*/
|
|
19
|
+
export function listSessions(limit = 10) {
|
|
20
|
+
try {
|
|
21
|
+
if (!fs.existsSync(SESSION_DIR)) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const files = fs
|
|
26
|
+
.readdirSync(SESSION_DIR)
|
|
27
|
+
.filter((f) => f.endsWith(".json"))
|
|
28
|
+
.map((f) => {
|
|
29
|
+
const filePath = path.join(SESSION_DIR, f);
|
|
30
|
+
const stat = fs.statSync(filePath);
|
|
31
|
+
let summary = "";
|
|
32
|
+
try {
|
|
33
|
+
const data = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
34
|
+
summary = data.summary || data.title || f.replace(".json", "");
|
|
35
|
+
} catch {
|
|
36
|
+
summary = f.replace(".json", "");
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
id: f.replace(".json", ""),
|
|
40
|
+
file: filePath,
|
|
41
|
+
mtime: stat.mtimeMs,
|
|
42
|
+
summary,
|
|
43
|
+
};
|
|
44
|
+
})
|
|
45
|
+
.sort((a, b) => b.mtime - a.mtime)
|
|
46
|
+
.slice(0, limit);
|
|
47
|
+
|
|
48
|
+
return files;
|
|
49
|
+
} catch (err) {
|
|
50
|
+
logger.debug(`Error listing sessions: ${err.message}`);
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Load a session by ID
|
|
57
|
+
* @param {string} sessionId
|
|
58
|
+
* @returns {Object|null} session data
|
|
59
|
+
*/
|
|
60
|
+
export function loadSession(sessionId) {
|
|
61
|
+
try {
|
|
62
|
+
const filePath = path.join(SESSION_DIR, `${sessionId}.json`);
|
|
63
|
+
if (!fs.existsSync(filePath)) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
67
|
+
} catch (err) {
|
|
68
|
+
logger.error(`Error loading session ${sessionId}: ${err.message}`);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get the most recent session
|
|
75
|
+
* @returns {Object|null}
|
|
76
|
+
*/
|
|
77
|
+
export function getLatestSession() {
|
|
78
|
+
const sessions = listSessions(1);
|
|
79
|
+
return sessions.length > 0 ? loadSession(sessions[0].id) : null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Resume a chat session
|
|
84
|
+
* @param {string} [sessionId] Optional session ID (resumes latest if not provided)
|
|
85
|
+
* @returns {Promise<Object>} resume result
|
|
86
|
+
*/
|
|
87
|
+
export async function resumeSession(sessionId) {
|
|
88
|
+
let session;
|
|
89
|
+
if (sessionId) {
|
|
90
|
+
session = loadSession(sessionId);
|
|
91
|
+
if (!session) {
|
|
92
|
+
return { success: false, message: `Session ${sessionId} not found` };
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
session = getLatestSession();
|
|
96
|
+
if (!session) {
|
|
97
|
+
return { success: false, message: "No previous sessions found" };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
success: true,
|
|
103
|
+
sessionId: session.id || sessionId,
|
|
104
|
+
messageCount: session.messages?.length || 0,
|
|
105
|
+
summary: session.summary,
|
|
106
|
+
session,
|
|
107
|
+
message: "Session loaded. Continue chatting to resume the conversation.",
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export default { listSessions, loadSession, getLatestSession, resumeSession };
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { createHash } from "crypto";
|
|
9
9
|
import { safeJsonParse } from "./safe-json.js";
|
|
10
|
+
import { likePrefix } from "./sql-like.js";
|
|
10
11
|
|
|
11
12
|
function ensureSessionsTable(db) {
|
|
12
13
|
db.exec(`
|
|
@@ -115,10 +116,13 @@ export function getSession(db, sessionId) {
|
|
|
115
116
|
.prepare("SELECT * FROM llm_sessions WHERE id = ?")
|
|
116
117
|
.get(sessionId);
|
|
117
118
|
|
|
118
|
-
if (!session) {
|
|
119
|
+
if (!session && sessionId) {
|
|
120
|
+
// Prefix match as a fallback. Escape LIKE metachars (% _ \) in the id and
|
|
121
|
+
// pair with ESCAPE '\' so a value containing them can't wildcard-match the
|
|
122
|
+
// WRONG session — getSession resolves the id that resume writes back to.
|
|
119
123
|
session = db
|
|
120
|
-
.prepare("SELECT * FROM llm_sessions WHERE id LIKE ? LIMIT 1")
|
|
121
|
-
.get(
|
|
124
|
+
.prepare("SELECT * FROM llm_sessions WHERE id LIKE ? ESCAPE '\\' LIMIT 1")
|
|
125
|
+
.get(likePrefix(sessionId));
|
|
122
126
|
}
|
|
123
127
|
|
|
124
128
|
if (!session) return null;
|