chainlesschain 0.166.106 → 0.166.108
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 +187 -8
- package/package.json +2 -2
- package/src/assets/linux-subreaper/linux-arm64/manifest.json +1 -1
- package/src/assets/linux-subreaper/linux-x64/manifest.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-CKwEku2e.js → AIOps-B5HWUFaS.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BSUOuTpW.js → ActionButton-DCIKpxoQ.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CGxvL_FI.js → Analytics-dsgvjCEQ.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-bCPwz-5w.js → AppLayout-DWDH5Qbm.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-2_BOWo6e.js → Artifacts-BD8S8kzU.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CBOruzgW.js → Audit-CDXv4Aoj.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-lg0IU5HN.js → BackgroundAgents-BLAQ5Uy0.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-OspCi452.js → Backup-moPac6tF.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BMkliqlz.js → BaseInput-C_6nzsLU.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-BfoiUZwp.js → Chat-cYuslkFG.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-BjnfMwep.js → ChatBubbleRenderer-Ceh3MG6M.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-jTMlkNQZ.js → Checkbox-Qjb-XOX_.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CycyJcnU.js → Codegen-IYRWXlMA.js} +1 -1
- package/src/assets/web-panel/assets/{Col-BVWlReQj.js → Col-CsQ9jv8M.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CKRHl4xK.js → Community-BbNmfTwe.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BtpfMdhT.js → Compact-SShDx-nr.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DtHB1Ct1.js → Compliance-CLWhKIJp.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-D_8X7-RK.js → Cowork-D4tK9_s3.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-DMCOyvEa.js → Cron-D7GnfQ1z.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-CeHdsu7m.js → Crosschain-TG86SH3X.js} +1 -1
- package/src/assets/web-panel/assets/{DID-CL6mR6zX.js → DID-6pue0NCO.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-oz9n6DC7.js → Dashboard-BwkJ29du.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CbWm8mWu.js → Dropdown-DEvpYDof.js} +1 -1
- package/src/assets/web-panel/assets/EmailListRenderer-CQj7UlKJ.js +1 -0
- package/src/assets/web-panel/assets/{EvolutionSettings-DPwqgRYm.js → EvolutionSettings-tpvPMM9W.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DkV4YMFj.js → FamilyGuardDashboard-CWYFW75O.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-CpUKHIOH.js → Federation-gRIi96rM.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-C6KXRgjR.js → FormItemContext-B7Nr3YkV.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-C5Y4L5E3.js → GenericCardRenderer-b1BgGgLT.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CBwKdF8s.js → Git-Dis1jjO7.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-Dt-OR7Aa.js → Governance-C4Yg99Am.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-Czkl-Lhl.js → Inference-D4UQRbGu.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-C7NE4949.js → KnowledgeGraph-Co_kLMAq.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CoAuZiMG.js → Logs-D4BRp7bg.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-DTkjTwGI.js → MarkdownRenderer-Dgy8arQI.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-IzrV-7zH.js → Marketplace-ys0j1vr9.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DYEea7aS.js → McpTools-B3ZvRZe_.js} +2 -2
- package/src/assets/web-panel/assets/{Memory-Uqe7ZrV6.js → Memory-BOa0jbr-.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-D092EGyM.js → MobileBridge-CWJRmUbj.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-7ty8vTpj.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-gg4jzfB-.js → Mtc-CCcOk5m5.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-BibGfs71.js → MtcAudit-vlrrScbQ.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-BmB5yRXP.js → Multisig-C8K59S_8.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-BBSlPDyY.js → NLProgramming-oTpFYVme.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Bui3-KiC.js → Notes-BhqAJWiz.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-ovXtbHe5.js → NotificationSettings-DCnV1CJM.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-Bq-qD7eG.js → OrderTableRenderer-CZGnsrVW.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-DacIY9oJ.js → Organization-DX1LuQlJ.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-CX8osheZ.js → Overflow-CbfKrFdT.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-1lnikqVp.js → P2P-BEwYZuHL.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DCrU-V6A.js → PdhVaultBrowser-YNmWbwd7.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-Dfcb6kM2.js → Permissions-DyZ18LiA.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-dvgai4hf.js → PersonalDataHub-B2J3aw2g.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-nymAEtnK.js → Pipeline-CXl2ZlhQ.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-D0Vd91oM.js → Privacy-I5SDmgAp.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BpUBcu7o.js → ProjectInit-Crl9tXw3.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-DIzwTT-M.js → ProjectSettings-DCFtFlSK.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-Den4RUw6.js → Projects-Do5BQq1i.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-Cq8p-M0d.js → Providers-Cb7OWa3y.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-DSmqOfYk.js → QrScannerModal-CJmhAU-s.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BDHCeSnA.js → QuickAsk-Cu6lEWou.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-B_9M8oF4.js → Recommend-Crip3jLB.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-5ot4leHy.js → RemoteSession-KxYY-2CB.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-B3F013PY.js → Reputation-D0vN-htI.js} +1 -1
- package/src/assets/web-panel/assets/{Row-BMyBJnZw.js → Row-wqzd_xCD.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-WmV0l52g.js → RssFeed-8lsPfb0a.js} +3 -3
- package/src/assets/web-panel/assets/{Search-Wl80yXnR.js → Search-C-e8qAeU.js} +1 -1
- package/src/assets/web-panel/assets/{Security-gbDMKb5m.js → Security-CMM9WgFy.js} +4 -4
- package/src/assets/web-panel/assets/{Services-Bx2Bjhah.js → Services-CZdxjN2B.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-BvJNybjV.js → Skeleton-BI3XNk35.js} +1 -1
- package/src/assets/web-panel/assets/Skills-DMlsh9It.js +1 -0
- package/src/assets/web-panel/assets/{Sla-BM9gyZwc.js → Sla-HQruznKY.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-Bu9FL9Tz.js → SpeechSettings-Dl-MFq3w.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-D8ICJsMt.js → SyncSettings-BU224vDm.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-DESg3yCs.js +1 -0
- package/src/assets/web-panel/assets/{Templates-BJzpTD0W.js → Templates-Jb3TPz4Q.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BN8eX4R2.js → Tenant-CQ-GTrfa.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-CjtP9bpq.js → Terminal-BbMl3Rue.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-CpNRa4XW.js → TimelineRenderer-CS_vstKF.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-2lNNvNAH.js → Tokens-BaopVl1f.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DxtxXyPg.js → Trigger-DtnD4WUN.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-CakzGeF9.js → Trust-CsvG4Tmf.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CvJeRXwV.js → UkeySign-C1-A-cOV.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-D80pMzcO.js → VideoEditing-CNgJPdSL.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BF-VOBmZ.js → Wallet-CdF41501.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DXyoAzE_.js → WebAuthn-D-JmxD5g.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-X6CDxzrF.js → WorkflowEditor-C0XunR13.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BcUhqjeA.js → chat-BBAZpD_G.js} +1 -1
- package/src/assets/web-panel/assets/{colors-Dw4bKaCm.js → colors-BxSxO5BX.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BWhu5Eri.js → compact-item-BoxL8pg5.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BLJ9JKoN.js → createContext-W5JAyZmY.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-BI5TZ7EX.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DIkCiJbt.js → hasIn-HpiKDPVC.js} +1 -1
- package/src/assets/web-panel/assets/index-9-JLDK5n.js +1 -0
- package/src/assets/web-panel/assets/{index-BAwcMKaD.js → index-9caMEwQ-.js} +1 -1
- package/src/assets/web-panel/assets/{index-0mHbawfQ.js → index-B05_YRHZ.js} +1 -1
- package/src/assets/web-panel/assets/index-B7m8ioaQ.js +1 -0
- package/src/assets/web-panel/assets/{index-D3wp_jgy.js → index-B9VxE1C-.js} +1 -1
- package/src/assets/web-panel/assets/{index-BotMuGS2.js → index-BA3WWQAT.js} +1 -1
- package/src/assets/web-panel/assets/{index-LOsZYbic.js → index-BSJ0cAle.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5qms36i.js → index-BW4PP5GN.js} +1 -1
- package/src/assets/web-panel/assets/{index-CDfB6hWf.js → index-BZAbaY64.js} +1 -1
- package/src/assets/web-panel/assets/{index-BipG-tDs.js → index-Ba4usRFM.js} +1 -1
- package/src/assets/web-panel/assets/{index-CgsVg5Up.js → index-BgMViZIY.js} +1 -1
- package/src/assets/web-panel/assets/{index-CTPoBbIR.js → index-BtFAEG08.js} +1 -1
- package/src/assets/web-panel/assets/{index-DKzG3kBM.js → index-Bwn6k_qw.js} +1 -1
- package/src/assets/web-panel/assets/{index-PRYboDFm.js → index-C4uI7WCB.js} +1 -1
- package/src/assets/web-panel/assets/{index-DQbWGvE4.js → index-C5kqii-G.js} +1 -1
- package/src/assets/web-panel/assets/{index-B1fufz1A.js → index-C7GIUWWF.js} +1 -1
- package/src/assets/web-panel/assets/{index-DjEP26Te.js → index-CM_gdohF.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcTch6Fp.js → index-CRg3aOoj.js} +1 -1
- package/src/assets/web-panel/assets/{index-04SKBH8l.js → index-CcyzZloJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DHLW27Je.js → index-CehxfRUR.js} +1 -1
- package/src/assets/web-panel/assets/{index-5vzPcr1g.js → index-D6m6B4Wy.js} +1 -1
- package/src/assets/web-panel/assets/{index-DV4FSjmI.js → index-DSyfxxQS.js} +3 -3
- package/src/assets/web-panel/assets/{index-D5vFJmmX.js → index-DaJk9U9V.js} +1 -1
- package/src/assets/web-panel/assets/{index-BxLDNxEQ.js → index-DcmAiq_Y.js} +1 -1
- package/src/assets/web-panel/assets/{index-D-Erv8A9.js → index-DdEcQ1rp.js} +1 -1
- package/src/assets/web-panel/assets/{index-CqGqoID5.js → index-DinY7IQa.js} +1 -1
- package/src/assets/web-panel/assets/{index-BGMzh2oj.js → index-DkSnlgK_.js} +1 -1
- package/src/assets/web-panel/assets/{index-BxfimJOH.js → index-DlPvZgv8.js} +1 -1
- package/src/assets/web-panel/assets/{index-DOjQi7oU.js → index-DuM9I1If.js} +1 -1
- package/src/assets/web-panel/assets/{index-uMtx6e3D.js → index-DwidpDEE.js} +1 -1
- package/src/assets/web-panel/assets/{index-CXJ8t1hK.js → index-IO2I7nzu.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cmc7qH2C.js → index-JrMBRi62.js} +1 -1
- package/src/assets/web-panel/assets/{index-BaStonJm.js → index-QzlEbtRL.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cr05twNa.js → index-V6VIJe2N.js} +1 -1
- package/src/assets/web-panel/assets/{index-BGFqk1-E.js → index-_38IJqoP.js} +1 -1
- package/src/assets/web-panel/assets/{index-u0tqS0iq.js → index-_p4e3KfB.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1r2R4a-.js → index-gvfIRZxJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CFGOV-YT.js → index-ooP1pQCt.js} +1 -1
- package/src/assets/web-panel/assets/{index-BUC4TZWg.js → index-p6rNtYud.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-Bx6Hj_FG.js → initDefaultProps-Dfn6M05J.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BSWivB70.js → motion-BRRwFqGO.js} +1 -1
- package/src/assets/web-panel/assets/{move-Dv1Pg9R7.js → move-DpDPN6zR.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-Bqtl8fsk.js → mtc-parser-BHlSE87F.js} +1 -1
- package/src/assets/web-panel/assets/{omit-DpOBjOSQ.js → omit-CEOri_L5.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-D-M-dz48.js → pickAttrs-DqICh-ad.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DsTLAifk.js → placementArrow-DQizFFiV.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BVTetMpc.js → responsiveObserve-DmFHPdvC.js} +1 -1
- package/src/assets/web-panel/assets/{slide-abEcz-Cv.js → slide-s2s1XS3T.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-CBhrdU2G.js → statusUtils-IwEIbBan.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-CFq8ZCKa.js → styleChecker-CIFLlbXH.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-o4eE7fDV.js → useFlexGapSupport-CFkN2Cgh.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-Hs9W8ihi.js → useFs-BSY6O48s.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-Bnet5Jho.js → usePersonalDataHub-iJl3VnU0.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DJfN4kWs.js → vnode-vu8ReMs7.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-BzV-2e7X.js → zoom-CeSrYAHp.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +2 -2
- package/src/commands/agent.js +20 -20
- package/src/commands/goal.js +299 -1
- package/src/commands/plugin.js +129 -68
- package/src/constants.js +2 -2
- package/src/data/changelog.json +29 -3
- package/src/harness/jsonl-session-store.js +128 -5
- package/src/harness/prompt-compressor.js +18 -10
- package/src/harness/structured-handoff.js +151 -13
- package/src/lib/agent-session-export.js +20 -0
- package/src/lib/background-agent-supervisor.js +54 -0
- package/src/lib/cli-goal-catalog.js +129 -0
- package/src/lib/cli-goal-source.js +119 -0
- package/src/lib/coding-task-guidance.js +2 -1
- package/src/lib/config-schema.js +1 -1
- package/src/lib/context-memory-kernel/live-compaction.js +75 -12
- package/src/lib/eval/stream-terminal.js +27 -2
- package/src/lib/evolution/evolution-artifact-ports.js +22 -11
- package/src/lib/evolution/evolution-file-witness.js +86 -63
- package/src/lib/evolution/evolution-ledger-file-backend.js +22 -0
- package/src/lib/evolution/evolution-ledger-file-manifest-catalog.js +34 -35
- package/src/lib/evolution/evolution-ledger-file-manifest-head-store.js +32 -39
- package/src/lib/evolution/evolution-ledger.js +367 -169
- package/src/lib/evolution/evolution-manifest-state-reader.js +58 -0
- package/src/lib/evolution/evolution-physical-state-reader.js +308 -0
- package/src/lib/evolution/evolution-witness-segments.js +12 -13
- package/src/lib/evolution/governed-wiki-pruning-ledger-adapter.js +82 -56
- package/src/lib/evolution/rrsi-observed-text-index.js +17 -1
- package/src/lib/evolution/rrsi-tenant-index-anchor-history.js +5 -0
- package/src/lib/execution-location-failure-site.js +20 -3
- package/src/lib/goal-store.js +8 -1
- package/src/lib/image-input.js +43 -14
- package/src/lib/llm-providers.js +1 -0
- package/src/lib/plugin-runtime/capabilities.js +10 -0
- package/src/lib/plugin-runtime/cli-goal-draft-request.js +138 -0
- package/src/lib/plugin-runtime/cli-goal-drafts.js +413 -0
- package/src/lib/plugin-runtime/cli-goal-extension-host.js +234 -0
- package/src/lib/plugin-runtime/examples/goal-description-drafter/plugin.json +17 -0
- package/src/lib/plugin-runtime/examples/goal-evidence-viewer/plugin.json +25 -0
- package/src/lib/plugin-runtime/examples/goal-progress-viewer/plugin.json +21 -0
- package/src/lib/plugin-runtime/examples/project-delivery-assistant/plugin.json +38 -0
- package/src/lib/plugin-runtime/goal-extension-contract.js +190 -0
- package/src/lib/plugin-runtime/goal-extensions.js +277 -0
- package/src/lib/plugin-runtime/install.js +40 -40
- package/src/lib/plugin-runtime/manifest.js +12 -0
- package/src/lib/plugin-runtime/policy.js +1 -0
- package/src/lib/project-goal-connection.js +225 -0
- package/src/lib/project-goal-draft-connection.js +217 -0
- package/src/lib/read-file-loop-guard.js +124 -8
- package/src/lib/session-host-snapshot.js +3 -0
- package/src/lib/session-message-provenance.js +24 -5
- package/src/lib/session-tool-round.js +39 -0
- package/src/lib/session-transcript-history.js +2 -0
- package/src/lib/session-transcript-page.js +4 -1
- package/src/lib/session-transcript-structure.js +4 -0
- package/src/lib/task-model-selector.js +1 -1
- package/src/repl/agent-repl.js +14 -7
- package/src/repl/config-summary.js +1 -1
- package/src/runtime/__tests__/message-roles.test.js +29 -0
- package/src/runtime/agent-core.js +116 -44
- package/src/runtime/headless-runner.js +191 -34
- package/src/runtime/headless-stream.js +144 -48
- package/src/runtime/message-roles.js +43 -0
- package/src/runtime/policies/agent-policy.js +1 -0
- package/src/workers/background-agent-worker.js +146 -47
- package/src/assets/web-panel/assets/EmailListRenderer-DXlmbC45.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-CwA1VoVJ.js +0 -1
- package/src/assets/web-panel/assets/Skills-Tz4rQcDp.js +0 -1
- package/src/assets/web-panel/assets/Tasks-B6MowBDo.js +0 -1
- package/src/assets/web-panel/assets/devWarning-D_MJgDGJ.js +0 -1
- package/src/assets/web-panel/assets/index-BP8log78.js +0 -1
- package/src/assets/web-panel/assets/index-PexF9zYf.js +0 -1
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
import fs from "fs";
|
|
28
28
|
import path from "path";
|
|
29
29
|
import semver from "semver";
|
|
30
|
+
import { normalizeGoalExtension } from "./goal-extension-contract.js";
|
|
30
31
|
import {
|
|
31
32
|
normalizeCapabilities,
|
|
32
33
|
normalizeOptionsSchema,
|
|
@@ -89,6 +90,8 @@ export function parsePluginManifest(root) {
|
|
|
89
90
|
// actual audit) from a legacy one that declared nothing (unrestricted).
|
|
90
91
|
capabilities: normalizeCapabilities(null),
|
|
91
92
|
capabilitiesDeclared: false,
|
|
93
|
+
goalExtensionDeclared: false,
|
|
94
|
+
goalExtension: null,
|
|
92
95
|
optionsSchema: {},
|
|
93
96
|
errors,
|
|
94
97
|
warnings,
|
|
@@ -219,6 +222,15 @@ export function parsePluginManifest(root) {
|
|
|
219
222
|
result.capabilities = normalizeCapabilities(manifest.permissions);
|
|
220
223
|
result.capabilitiesDeclared = Boolean(hasPermissions);
|
|
221
224
|
result.optionsSchema = normalizeOptionsSchema(manifest.optionsSchema);
|
|
225
|
+
result.goalExtensionDeclared = Object.hasOwn(manifest, "goalExtension");
|
|
226
|
+
try {
|
|
227
|
+
result.goalExtension = normalizeGoalExtension(
|
|
228
|
+
manifest.goalExtension,
|
|
229
|
+
manifest.permissions,
|
|
230
|
+
);
|
|
231
|
+
} catch (err) {
|
|
232
|
+
errors.push(err.message);
|
|
233
|
+
}
|
|
222
234
|
// Declared-vs-actual audit runs ONLY when a permissions block is present — a
|
|
223
235
|
// legacy plugin that declares nothing is unrestricted by design, so it never
|
|
224
236
|
// gets these warnings (keeps existing manifests warning-free).
|
|
@@ -45,6 +45,7 @@ export function loadManagedPluginPolicy(opts = {}) {
|
|
|
45
45
|
const key = String(
|
|
46
46
|
opts.managedSettingsFile || env.CC_MANAGED_SETTINGS || "<default>",
|
|
47
47
|
);
|
|
48
|
+
if (opts.fresh === true) _cache.delete(key);
|
|
48
49
|
if (_cache.has(key)) {
|
|
49
50
|
const cached = _cache.get(key);
|
|
50
51
|
// A cached failure sentinel must re-throw on EVERY call — otherwise the
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/** Explicit short-lived desktop connection. No DB path or client-supplied actor. */
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import http from "node:http";
|
|
5
|
+
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
6
|
+
import { inspectPrivatePath } from "@chainlesschain/session-core/private-storage";
|
|
7
|
+
import { goalExtensionRef } from "@chainlesschain/session-core/goal-extension-proposal-contract";
|
|
8
|
+
import { validateGoalEvidenceProjection } from "@chainlesschain/session-core/goal-evidence-contract";
|
|
9
|
+
import { validateGoalDraftDescriptor } from "@chainlesschain/session-core/goal-draft-connection-contract";
|
|
10
|
+
import { createProjectGoalDraftClient } from "./project-goal-draft-connection.js";
|
|
11
|
+
function fail() {
|
|
12
|
+
throw new Error("CLI_GOAL_CONNECTION_UNAVAILABLE");
|
|
13
|
+
}
|
|
14
|
+
const sameFile = (a, b) =>
|
|
15
|
+
a.dev === b.dev &&
|
|
16
|
+
a.ino === b.ino &&
|
|
17
|
+
a.size === b.size &&
|
|
18
|
+
a.mtimeMs === b.mtimeMs &&
|
|
19
|
+
a.ctimeMs === b.ctimeMs;
|
|
20
|
+
export function createProjectGoalConnection(file) {
|
|
21
|
+
let fd;
|
|
22
|
+
try {
|
|
23
|
+
if (
|
|
24
|
+
typeof file !== "string" ||
|
|
25
|
+
!file ||
|
|
26
|
+
file.length > 4096 ||
|
|
27
|
+
/[\u0000-\u001f]/u.test(file)
|
|
28
|
+
)
|
|
29
|
+
fail();
|
|
30
|
+
const target = path.resolve(file);
|
|
31
|
+
const before = fs.lstatSync(target);
|
|
32
|
+
if (
|
|
33
|
+
!before.isFile() ||
|
|
34
|
+
before.isSymbolicLink() ||
|
|
35
|
+
before.nlink !== 1 ||
|
|
36
|
+
before.size > 4096 ||
|
|
37
|
+
!inspectPrivatePath(target).ok
|
|
38
|
+
)
|
|
39
|
+
fail();
|
|
40
|
+
fd = fs.openSync(
|
|
41
|
+
target,
|
|
42
|
+
fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW || 0),
|
|
43
|
+
);
|
|
44
|
+
const opened = fs.fstatSync(fd);
|
|
45
|
+
if (!sameFile(before, opened)) fail();
|
|
46
|
+
const raw = Buffer.alloc(4097);
|
|
47
|
+
const count = fs.readSync(fd, raw, 0, raw.length, 0);
|
|
48
|
+
if (count > 4096 || !sameFile(opened, fs.fstatSync(fd))) fail();
|
|
49
|
+
const descriptor = JSON.parse(raw.subarray(0, count).toString("utf8"));
|
|
50
|
+
if (descriptor.schema === "chainlesschain.project-goal-connection/v3") {
|
|
51
|
+
const verified = validateGoalDraftDescriptor(descriptor);
|
|
52
|
+
return createProjectGoalDraftClient(verified, () => {
|
|
53
|
+
if (
|
|
54
|
+
Date.now() >= verified.expiresAt ||
|
|
55
|
+
!sameFile(opened, fs.lstatSync(target))
|
|
56
|
+
)
|
|
57
|
+
fail();
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
const evidence =
|
|
61
|
+
descriptor.schema === "chainlesschain.project-goal-connection/v2";
|
|
62
|
+
const endpointPath = evidence ? "/goal-evidence" : "/goal-summary";
|
|
63
|
+
if (
|
|
64
|
+
Object.keys(descriptor).sort().join() !==
|
|
65
|
+
(evidence
|
|
66
|
+
? "capability,endpoint,expiresAt,goalRef,key,schema"
|
|
67
|
+
: "endpoint,expiresAt,goalRef,key,schema") ||
|
|
68
|
+
(evidence
|
|
69
|
+
? descriptor.capability !== "goal.evidence"
|
|
70
|
+
: descriptor.schema !== "chainlesschain.project-goal-connection/v1") ||
|
|
71
|
+
typeof descriptor.endpoint !== "string" ||
|
|
72
|
+
!/^http:\/\/127\.0\.0\.1:[1-9][0-9]{0,4}\/goal-(?:summary|evidence)$/.test(
|
|
73
|
+
descriptor.endpoint,
|
|
74
|
+
) ||
|
|
75
|
+
new URL(descriptor.endpoint).pathname !== endpointPath ||
|
|
76
|
+
Number(new URL(descriptor.endpoint).port) > 65535 ||
|
|
77
|
+
typeof descriptor.key !== "string" ||
|
|
78
|
+
!/^[a-f0-9]{64}$/.test(descriptor.key) ||
|
|
79
|
+
!Number.isSafeInteger(descriptor.expiresAt) ||
|
|
80
|
+
descriptor.expiresAt <= Date.now() ||
|
|
81
|
+
descriptor.expiresAt > Date.now() + 15 * 60 * 1000 + 5000
|
|
82
|
+
)
|
|
83
|
+
fail();
|
|
84
|
+
const ref = goalExtensionRef(descriptor.goalRef);
|
|
85
|
+
function validate() {
|
|
86
|
+
if (
|
|
87
|
+
Date.now() >= descriptor.expiresAt ||
|
|
88
|
+
!sameFile(opened, fs.lstatSync(target))
|
|
89
|
+
)
|
|
90
|
+
fail();
|
|
91
|
+
}
|
|
92
|
+
const mac = (value) =>
|
|
93
|
+
createHmac("sha256", Buffer.from(descriptor.key, "hex"))
|
|
94
|
+
.update(value)
|
|
95
|
+
.digest("hex");
|
|
96
|
+
async function read(id = ref.id) {
|
|
97
|
+
validate();
|
|
98
|
+
if (id !== ref.id) fail();
|
|
99
|
+
const nonce = randomBytes(32).toString("hex");
|
|
100
|
+
try {
|
|
101
|
+
const result = await new Promise((resolve, reject) => {
|
|
102
|
+
const request = http.get(
|
|
103
|
+
descriptor.endpoint,
|
|
104
|
+
{
|
|
105
|
+
agent: false,
|
|
106
|
+
maxHeaderSize: 4096,
|
|
107
|
+
headers: {
|
|
108
|
+
"X-CC-Nonce": nonce,
|
|
109
|
+
"X-CC-Signature": mac(
|
|
110
|
+
`request\nGET\n${endpointPath}\n${nonce}`,
|
|
111
|
+
),
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
(response) => {
|
|
115
|
+
const chunks = [];
|
|
116
|
+
let bytes = 0;
|
|
117
|
+
response.on("error", reject);
|
|
118
|
+
response.on("data", (chunk) => {
|
|
119
|
+
bytes += chunk.length;
|
|
120
|
+
if (bytes > 16 * 1024) response.destroy(new Error("limit"));
|
|
121
|
+
else chunks.push(chunk);
|
|
122
|
+
});
|
|
123
|
+
response.on("end", () => {
|
|
124
|
+
const body = Buffer.concat(chunks).toString("utf8"),
|
|
125
|
+
signature = response.headers["x-cc-signature"];
|
|
126
|
+
if (
|
|
127
|
+
response.statusCode !== 200 ||
|
|
128
|
+
typeof signature !== "string" ||
|
|
129
|
+
!/^[a-f0-9]{64}$/.test(signature) ||
|
|
130
|
+
!timingSafeEqual(
|
|
131
|
+
Buffer.from(signature),
|
|
132
|
+
Buffer.from(mac(`response\n${nonce}\n${body}`)),
|
|
133
|
+
)
|
|
134
|
+
) {
|
|
135
|
+
reject(new Error("signature"));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
resolve(JSON.parse(body));
|
|
140
|
+
} catch {
|
|
141
|
+
reject(new Error("json"));
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
},
|
|
145
|
+
);
|
|
146
|
+
request.setTimeout(5000, () => request.destroy(new Error("timeout")));
|
|
147
|
+
const deadline = setTimeout(
|
|
148
|
+
() => request.destroy(new Error("timeout")),
|
|
149
|
+
5000,
|
|
150
|
+
);
|
|
151
|
+
request.once("close", () => clearTimeout(deadline));
|
|
152
|
+
request.on("error", reject);
|
|
153
|
+
});
|
|
154
|
+
validate();
|
|
155
|
+
if (
|
|
156
|
+
Object.keys(result).sort().join() !==
|
|
157
|
+
(evidence
|
|
158
|
+
? "goalRef,objective,receipts,risk,schema,status"
|
|
159
|
+
: "goalRef,objective,schema,status") ||
|
|
160
|
+
result.schema !==
|
|
161
|
+
(evidence
|
|
162
|
+
? "chainlesschain.project-goal-evidence/v1"
|
|
163
|
+
: "chainlesschain.project-goal-summary/v1") ||
|
|
164
|
+
JSON.stringify(goalExtensionRef(result.goalRef)) !==
|
|
165
|
+
JSON.stringify(ref) ||
|
|
166
|
+
typeof result.objective !== "string" ||
|
|
167
|
+
result.objective.length > 4096 ||
|
|
168
|
+
result.status !== "active"
|
|
169
|
+
)
|
|
170
|
+
fail();
|
|
171
|
+
if (evidence)
|
|
172
|
+
validateGoalEvidenceProjection({
|
|
173
|
+
risk: result.risk,
|
|
174
|
+
receipts: result.receipts,
|
|
175
|
+
});
|
|
176
|
+
return { ...result, goalRef: ref };
|
|
177
|
+
} catch {
|
|
178
|
+
fail();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
validate();
|
|
182
|
+
return Object.freeze({ goalRef: ref, evidence, read });
|
|
183
|
+
} catch {
|
|
184
|
+
fail();
|
|
185
|
+
} finally {
|
|
186
|
+
if (fd !== undefined) fs.closeSync(fd);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export async function readProjectGoalCatalog(options = {}) {
|
|
191
|
+
if (
|
|
192
|
+
options.after !== undefined ||
|
|
193
|
+
!Number.isSafeInteger(options.limit ?? 20) ||
|
|
194
|
+
(options.limit ?? 20) < 1 ||
|
|
195
|
+
(options.limit ?? 20) > 20
|
|
196
|
+
)
|
|
197
|
+
fail();
|
|
198
|
+
const source = createProjectGoalConnection(options.connection);
|
|
199
|
+
const goal = await source.read();
|
|
200
|
+
return {
|
|
201
|
+
schema: "chainlesschain.goal-catalog.v1",
|
|
202
|
+
source: "desktop-project",
|
|
203
|
+
snapshot: true,
|
|
204
|
+
storeId: source.goalRef.storeId,
|
|
205
|
+
observedAt: new Date().toISOString(),
|
|
206
|
+
entries: [
|
|
207
|
+
{
|
|
208
|
+
id: source.goalRef.id,
|
|
209
|
+
title: goal.objective
|
|
210
|
+
.replace(
|
|
211
|
+
/[\u0000-\u001f\u007f-\u009f\u200e\u200f\u202a-\u202e\u2066-\u2069]/gu,
|
|
212
|
+
" ",
|
|
213
|
+
)
|
|
214
|
+
.slice(0, 160),
|
|
215
|
+
status: goal.status,
|
|
216
|
+
revision: source.goalRef.revision,
|
|
217
|
+
controlGeneration: source.goalRef.controlGeneration,
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
scannedCount: 1,
|
|
221
|
+
skippedCount: 0,
|
|
222
|
+
invalidNameCount: 0,
|
|
223
|
+
nextCursor: null,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
import {
|
|
3
|
+
createHash,
|
|
4
|
+
createHmac,
|
|
5
|
+
randomBytes,
|
|
6
|
+
timingSafeEqual,
|
|
7
|
+
} from "node:crypto";
|
|
8
|
+
import { digestBusinessObjectContent as digest } from "@chainlesschain/session-core/business-object-contract";
|
|
9
|
+
import {
|
|
10
|
+
validateGoalDraftSources,
|
|
11
|
+
validateGoalDraftReceipt,
|
|
12
|
+
validateGoalDraftProposal,
|
|
13
|
+
} from "@chainlesschain/session-core/goal-draft-connection-contract";
|
|
14
|
+
function unavailable() {
|
|
15
|
+
return new Error("CLI_GOAL_CONNECTION_UNAVAILABLE");
|
|
16
|
+
}
|
|
17
|
+
function exact(raw, keys) {
|
|
18
|
+
if (
|
|
19
|
+
!raw ||
|
|
20
|
+
typeof raw !== "object" ||
|
|
21
|
+
Array.isArray(raw) ||
|
|
22
|
+
Object.keys(raw).length !== keys.length ||
|
|
23
|
+
keys.some((key) => !Object.hasOwn(raw, key))
|
|
24
|
+
)
|
|
25
|
+
throw unavailable();
|
|
26
|
+
}
|
|
27
|
+
/** Constructed only after the shared private-file reader verifies the descriptor. */
|
|
28
|
+
export function createProjectGoalDraftClient(descriptor, validate) {
|
|
29
|
+
const ref = Object.freeze({ ...descriptor.goalRef }),
|
|
30
|
+
grant = Object.freeze({
|
|
31
|
+
id: descriptor.grantRef.id,
|
|
32
|
+
epoch: descriptor.grantRef.epoch,
|
|
33
|
+
plugin: Object.freeze({ ...descriptor.plugin }),
|
|
34
|
+
declarationDigest: descriptor.declarationDigest,
|
|
35
|
+
});
|
|
36
|
+
const mac = (text) =>
|
|
37
|
+
createHmac("sha256", Buffer.from(descriptor.key, "hex"))
|
|
38
|
+
.update(text)
|
|
39
|
+
.digest("hex");
|
|
40
|
+
async function exchange(method, payload, writing = false) {
|
|
41
|
+
validate();
|
|
42
|
+
const bytes =
|
|
43
|
+
payload === undefined
|
|
44
|
+
? Buffer.alloc(0)
|
|
45
|
+
: Buffer.from(JSON.stringify(payload));
|
|
46
|
+
if (bytes.length > 16384) throw unavailable();
|
|
47
|
+
const nonce = randomBytes(32).toString("hex"),
|
|
48
|
+
bodyHash = createHash("sha256").update(bytes).digest("hex");
|
|
49
|
+
let sent = false;
|
|
50
|
+
try {
|
|
51
|
+
const result = await new Promise((resolve, reject) => {
|
|
52
|
+
const request = http.request(
|
|
53
|
+
descriptor.endpoint,
|
|
54
|
+
{
|
|
55
|
+
method,
|
|
56
|
+
agent: false,
|
|
57
|
+
maxHeaderSize: 4096,
|
|
58
|
+
headers: {
|
|
59
|
+
"X-CC-Nonce": nonce,
|
|
60
|
+
"X-CC-Signature": mac(
|
|
61
|
+
`request-v3\n${method}\n/goal-drafts\n${nonce}\n${bodyHash}`,
|
|
62
|
+
),
|
|
63
|
+
...(method === "POST"
|
|
64
|
+
? {
|
|
65
|
+
"Content-Type": "application/json",
|
|
66
|
+
"Content-Length": bytes.length,
|
|
67
|
+
}
|
|
68
|
+
: {}),
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
(response) => {
|
|
72
|
+
const chunks = [];
|
|
73
|
+
let size = 0;
|
|
74
|
+
response.on("error", reject);
|
|
75
|
+
response.on("data", (chunk) => {
|
|
76
|
+
size += chunk.length;
|
|
77
|
+
if (size > 16384) response.destroy(unavailable());
|
|
78
|
+
else chunks.push(chunk);
|
|
79
|
+
});
|
|
80
|
+
response.on("end", () => {
|
|
81
|
+
try {
|
|
82
|
+
const text = new TextDecoder("utf-8", { fatal: true }).decode(
|
|
83
|
+
Buffer.concat(chunks),
|
|
84
|
+
),
|
|
85
|
+
signature = response.headers["x-cc-signature"];
|
|
86
|
+
if (
|
|
87
|
+
typeof signature !== "string" ||
|
|
88
|
+
!/^[a-f0-9]{64}$/u.test(signature) ||
|
|
89
|
+
!timingSafeEqual(
|
|
90
|
+
Buffer.from(signature),
|
|
91
|
+
Buffer.from(
|
|
92
|
+
mac(
|
|
93
|
+
`response-v3\n${nonce}\n${response.statusCode}\n${text}`,
|
|
94
|
+
),
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
throw unavailable();
|
|
99
|
+
if (response.statusCode !== 200) throw unavailable();
|
|
100
|
+
resolve(JSON.parse(text));
|
|
101
|
+
} catch (error) {
|
|
102
|
+
reject(error);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
},
|
|
106
|
+
);
|
|
107
|
+
const timer = setTimeout(() => request.destroy(unavailable()), 5000);
|
|
108
|
+
request.once("close", () => clearTimeout(timer));
|
|
109
|
+
request.on("error", reject);
|
|
110
|
+
request.setTimeout(5000, () => request.destroy(unavailable()));
|
|
111
|
+
sent = writing;
|
|
112
|
+
request.end(bytes);
|
|
113
|
+
});
|
|
114
|
+
validate();
|
|
115
|
+
if (digest(result.goalRef) !== digest(ref)) throw unavailable();
|
|
116
|
+
return result;
|
|
117
|
+
} catch {
|
|
118
|
+
const error = unavailable();
|
|
119
|
+
if (sent) {
|
|
120
|
+
error.code = "CLI_GOAL_DRAFT_OUTCOME_UNKNOWN";
|
|
121
|
+
error.outcomeUnknown = true;
|
|
122
|
+
}
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async function read(id = ref.id) {
|
|
127
|
+
if (id !== ref.id) throw unavailable();
|
|
128
|
+
const result = await exchange("GET");
|
|
129
|
+
exact(result, [
|
|
130
|
+
"schema",
|
|
131
|
+
"goalRef",
|
|
132
|
+
"objective",
|
|
133
|
+
"status",
|
|
134
|
+
"grant",
|
|
135
|
+
"sources",
|
|
136
|
+
]);
|
|
137
|
+
if (
|
|
138
|
+
result.schema !== "chainlesschain.project-goal-drafts/v1" ||
|
|
139
|
+
typeof result.objective !== "string" ||
|
|
140
|
+
result.objective.length > 4096 ||
|
|
141
|
+
result.status !== "active" ||
|
|
142
|
+
digest(result.grant) !== digest(grant)
|
|
143
|
+
)
|
|
144
|
+
throw unavailable();
|
|
145
|
+
validateGoalDraftSources(result.sources);
|
|
146
|
+
return { ...result, goalRef: ref };
|
|
147
|
+
}
|
|
148
|
+
async function sources(afterId) {
|
|
149
|
+
const result = await exchange("POST", {
|
|
150
|
+
operation: "sources",
|
|
151
|
+
...(afterId !== undefined ? { afterId } : {}),
|
|
152
|
+
});
|
|
153
|
+
exact(result, ["schema", "goalRef", "sources"]);
|
|
154
|
+
if (result.schema !== "chainlesschain.goal-draft-sources/v1")
|
|
155
|
+
throw unavailable();
|
|
156
|
+
return validateGoalDraftSources(result.sources);
|
|
157
|
+
}
|
|
158
|
+
async function receipt({ requestId, inputDigest }) {
|
|
159
|
+
const result = await exchange("POST", {
|
|
160
|
+
operation: "receipt",
|
|
161
|
+
requestId,
|
|
162
|
+
inputDigest,
|
|
163
|
+
});
|
|
164
|
+
exact(result, ["schema", "goalRef", "receipt"]);
|
|
165
|
+
if (result.schema !== "chainlesschain.goal-draft-receipt/v1")
|
|
166
|
+
throw unavailable();
|
|
167
|
+
const value = validateGoalDraftReceipt(result.receipt);
|
|
168
|
+
if (value.requestId !== requestId || value.inputDigest !== inputDigest)
|
|
169
|
+
throw unavailable();
|
|
170
|
+
return value;
|
|
171
|
+
}
|
|
172
|
+
async function enqueue(raw) {
|
|
173
|
+
const proposal = validateGoalDraftProposal(raw),
|
|
174
|
+
inputDigest = digest(proposal);
|
|
175
|
+
let received = false;
|
|
176
|
+
try {
|
|
177
|
+
const result = await exchange(
|
|
178
|
+
"POST",
|
|
179
|
+
{ operation: "enqueue", proposal },
|
|
180
|
+
true,
|
|
181
|
+
);
|
|
182
|
+
received = true;
|
|
183
|
+
exact(result, ["schema", "goalRef", "receipt"]);
|
|
184
|
+
if (result.schema !== "chainlesschain.goal-draft-submit/v1")
|
|
185
|
+
throw unavailable();
|
|
186
|
+
const value = validateGoalDraftReceipt(result.receipt);
|
|
187
|
+
if (
|
|
188
|
+
value.state !== "saved" ||
|
|
189
|
+
value.requestId !== proposal.requestId ||
|
|
190
|
+
value.inputDigest !== inputDigest
|
|
191
|
+
)
|
|
192
|
+
throw unavailable();
|
|
193
|
+
return value;
|
|
194
|
+
} catch (error) {
|
|
195
|
+
// Once HTTP may have dispatched, an invalid/absent response is not proof
|
|
196
|
+
// that no native draft was saved. Preserve this exact request identity.
|
|
197
|
+
if (received || error.outcomeUnknown) {
|
|
198
|
+
error.code = "CLI_GOAL_DRAFT_OUTCOME_UNKNOWN";
|
|
199
|
+
error.outcomeUnknown = true;
|
|
200
|
+
}
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
validate();
|
|
205
|
+
return Object.freeze({
|
|
206
|
+
goalRef: ref,
|
|
207
|
+
evidence: false,
|
|
208
|
+
drafts: true,
|
|
209
|
+
plugin: grant.plugin,
|
|
210
|
+
declarationDigest: grant.declarationDigest,
|
|
211
|
+
grantRef: Object.freeze({ ...descriptor.grantRef }),
|
|
212
|
+
read,
|
|
213
|
+
sources,
|
|
214
|
+
enqueue,
|
|
215
|
+
receipt,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
@@ -32,6 +32,79 @@ export class ReadFileLoopGuard {
|
|
|
32
32
|
this.batch = null;
|
|
33
33
|
this.largeOutputs = new Set();
|
|
34
34
|
this.editRecoveryReads = new Map();
|
|
35
|
+
this.visiblePages = null;
|
|
36
|
+
this.visibleRequest = 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Observe only the request actually selected for the provider, not history. */
|
|
40
|
+
observeVisibleMessages(messages) {
|
|
41
|
+
this.visibleRequest++;
|
|
42
|
+
this.visiblePages = [];
|
|
43
|
+
const retain = (value) => {
|
|
44
|
+
if (
|
|
45
|
+
typeof value?.path !== "string" ||
|
|
46
|
+
typeof value.fileVersion !== "string"
|
|
47
|
+
)
|
|
48
|
+
return;
|
|
49
|
+
if (typeof value.content === "string" && !value.contentOmitted) {
|
|
50
|
+
this.visiblePages.push(value);
|
|
51
|
+
}
|
|
52
|
+
for (const excerpt of Array.isArray(value.excerpts)
|
|
53
|
+
? value.excerpts
|
|
54
|
+
: []) {
|
|
55
|
+
if (typeof excerpt?.content === "string")
|
|
56
|
+
this.visiblePages.push({
|
|
57
|
+
...excerpt,
|
|
58
|
+
path: value.path,
|
|
59
|
+
fileVersion: value.fileVersion,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const retainContext = (context) => {
|
|
64
|
+
if (
|
|
65
|
+
typeof context !== "string" ||
|
|
66
|
+
!context.startsWith("[File excerpts retained across compaction")
|
|
67
|
+
)
|
|
68
|
+
return;
|
|
69
|
+
try {
|
|
70
|
+
const entries = JSON.parse(context.slice(context.indexOf("\n") + 1));
|
|
71
|
+
if (Array.isArray(entries)) entries.forEach(retain);
|
|
72
|
+
} catch {
|
|
73
|
+
/* A truncated hint provides no confirmed source evidence. */
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
for (const message of messages || []) {
|
|
77
|
+
if (message.role === "assistant") retainContext(message.content);
|
|
78
|
+
if (message.role !== "tool") continue;
|
|
79
|
+
let value;
|
|
80
|
+
try {
|
|
81
|
+
value =
|
|
82
|
+
typeof message.content === "string"
|
|
83
|
+
? JSON.parse(message.content.split("\n\n[Budget ")[0])
|
|
84
|
+
: message.content;
|
|
85
|
+
} catch {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
retain(value);
|
|
89
|
+
for (const context of Array.isArray(value?.retainedReadContext)
|
|
90
|
+
? value.retainedReadContext
|
|
91
|
+
: []) {
|
|
92
|
+
retainContext(context);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
isPageVisible(page) {
|
|
98
|
+
return (
|
|
99
|
+
this.visiblePages?.some(
|
|
100
|
+
(visible) =>
|
|
101
|
+
keyFor(visible.path, {}) === keyFor(page.path, {}) &&
|
|
102
|
+
visible.fileVersion === page.fileVersion &&
|
|
103
|
+
typeof page.content === "string" &&
|
|
104
|
+
page.content.length > 0 &&
|
|
105
|
+
visible.content.includes(page.content),
|
|
106
|
+
) === true
|
|
107
|
+
);
|
|
35
108
|
}
|
|
36
109
|
|
|
37
110
|
recordEditRecovery(result) {
|
|
@@ -94,6 +167,7 @@ export class ReadFileLoopGuard {
|
|
|
94
167
|
rereads: new Set(),
|
|
95
168
|
summary: null,
|
|
96
169
|
excerpts: [],
|
|
170
|
+
contextRestoreOffered: false,
|
|
97
171
|
};
|
|
98
172
|
}
|
|
99
173
|
let page = readPage(args);
|
|
@@ -108,11 +182,11 @@ export class ReadFileLoopGuard {
|
|
|
108
182
|
readProgress: { newContent: false },
|
|
109
183
|
};
|
|
110
184
|
}
|
|
111
|
-
|
|
185
|
+
let span = page.readSpan;
|
|
112
186
|
const covered = entry.spans.some(
|
|
113
187
|
([start, end]) => start <= span.start && end >= span.end,
|
|
114
188
|
);
|
|
115
|
-
|
|
189
|
+
let targeted =
|
|
116
190
|
Number(args.offset) > 0 &&
|
|
117
191
|
Number(args.limit) > 0 &&
|
|
118
192
|
Number(args.limit) <= 200 &&
|
|
@@ -122,20 +196,52 @@ export class ReadFileLoopGuard {
|
|
|
122
196
|
);
|
|
123
197
|
let newContent = !covered;
|
|
124
198
|
const hadOutline = !!entry.summary?.outline;
|
|
199
|
+
// A planner can evict a read without compacting the transcript. Give one
|
|
200
|
+
// bounded landing page for that version, then require explicit ranges.
|
|
201
|
+
// Dropping the same evidence repeatedly must not reopen full-file scans.
|
|
202
|
+
if (
|
|
203
|
+
covered &&
|
|
204
|
+
entry.summary?.reachedEnd === true &&
|
|
205
|
+
!targeted &&
|
|
206
|
+
this.visiblePages !== null &&
|
|
207
|
+
entry.lastReadRequest < this.visibleRequest &&
|
|
208
|
+
!this.isPageVisible(page) &&
|
|
209
|
+
!entry.contextRestoreOffered &&
|
|
210
|
+
entry.rereads.size < 3
|
|
211
|
+
) {
|
|
212
|
+
const offset =
|
|
213
|
+
Number.isSafeInteger(Number(args.offset)) && Number(args.offset) > 0
|
|
214
|
+
? Number(args.offset)
|
|
215
|
+
: 1;
|
|
216
|
+
const bounded = readPage({ ...args, offset, limit: 80 });
|
|
217
|
+
if (!bounded.error && bounded.readSpan && !this.isPageVisible(bounded)) {
|
|
218
|
+
page = bounded;
|
|
219
|
+
span = page.readSpan;
|
|
220
|
+
targeted = true;
|
|
221
|
+
entry.contextRestoreOffered = true;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
125
224
|
if (covered || (coveredPrefix && !targeted)) {
|
|
126
225
|
// Permit a bounded, explicit reread for editing or lost context. Broad
|
|
127
226
|
// rescans and subsequent repeats recover automatically, including when
|
|
128
227
|
// the model varies offset/limit inside an already covered region.
|
|
129
|
-
const
|
|
130
|
-
.
|
|
131
|
-
.
|
|
228
|
+
const restoring =
|
|
229
|
+
this.visiblePages !== null &&
|
|
230
|
+
entry.lastReadRequest < this.visibleRequest &&
|
|
231
|
+
!this.isPageVisible(page);
|
|
232
|
+
const fingerprint =
|
|
233
|
+
(restoring ? "restore:" : "review:") +
|
|
234
|
+
createHash("sha256").update(JSON.stringify(span)).digest("hex");
|
|
132
235
|
if (
|
|
133
236
|
targeted &&
|
|
134
237
|
!entry.rereads.has(fingerprint) &&
|
|
135
238
|
entry.rereads.size < 3
|
|
136
239
|
) {
|
|
137
240
|
entry.rereads.add(fingerprint);
|
|
138
|
-
page.readRecovery = {
|
|
241
|
+
page.readRecovery = {
|
|
242
|
+
action: restoring ? "context-restore" : "targeted-review",
|
|
243
|
+
};
|
|
244
|
+
if (restoring) page.readRecovery.fileVersion = fileVersion;
|
|
139
245
|
} else {
|
|
140
246
|
const unread = entry.spans[0]?.[0] === 0 ? entry.spans[0][1] : 0;
|
|
141
247
|
if (unread < span.total) {
|
|
@@ -162,12 +268,20 @@ export class ReadFileLoopGuard {
|
|
|
162
268
|
...(entry.summary?.outline
|
|
163
269
|
? { outline: entry.summary.outline }
|
|
164
270
|
: {}),
|
|
165
|
-
|
|
271
|
+
recoveryRead: {
|
|
272
|
+
path: filePath,
|
|
273
|
+
offset: 1,
|
|
274
|
+
limit: 80,
|
|
275
|
+
...(args.hashed === true ? { hashed: true } : {}),
|
|
276
|
+
...(args.raw === true ? { raw: true } : {}),
|
|
277
|
+
},
|
|
278
|
+
hint: "The unchanged file was read earlier, but historical coverage does not mean its source is visible now. Use any source excerpts actually present in this request. For missing details, use search_files or read_file with a specific offset and limit (at most 80 lines); recoveryRead is a bounded starting range, not permission to restart a full scan. Reading the file is not task completion.",
|
|
166
279
|
};
|
|
167
280
|
}
|
|
168
281
|
}
|
|
169
282
|
}
|
|
170
283
|
entry.spans = mergeSpan(entry.spans, page.readSpan);
|
|
284
|
+
entry.lastReadRequest = this.visibleRequest;
|
|
171
285
|
// Coverage alone cannot restore source code after compaction. Keep bounded
|
|
172
286
|
// observed pages for every file type, not only large Markdown outlines.
|
|
173
287
|
// Label truncation explicitly; a retained prefix is not the whole range.
|
|
@@ -232,6 +346,8 @@ export class ReadFileLoopGuard {
|
|
|
232
346
|
if (tool === "read_file") {
|
|
233
347
|
if (result?.readRecovery?.action === "edit-anchor-refresh")
|
|
234
348
|
this.batch.advanced = true;
|
|
349
|
+
if (result?.readRecovery?.action === "context-restore")
|
|
350
|
+
this.batch.advanced = true;
|
|
235
351
|
if (result?.readProgress?.newContent === true) this.batch.advanced = true;
|
|
236
352
|
if (result?.readProgress?.newContent === false) this.batch.duplicates++;
|
|
237
353
|
} else if (
|
|
@@ -308,7 +424,7 @@ export class ReadFileLoopGuard {
|
|
|
308
424
|
|
|
309
425
|
get recoveryHint() {
|
|
310
426
|
return this.repeatedBatches >= 1
|
|
311
|
-
? "Repeated file reads or identical large command outputs detected. Do not switch to run_code/run_shell to dump the same file again. Filter/count locally and print only relevant rows, totals and source line numbers, or use a focused search. The runtime continues unfinished read_file scans automatically and returns use-findings after EOF. Use
|
|
427
|
+
? "Repeated file reads or identical large command outputs detected. Do not switch to run_code/run_shell to dump the same file again. Filter/count locally and print only relevant rows, totals and source line numbers, or use a focused search. The runtime continues unfinished read_file scans automatically and returns use-findings after EOF. Use source evidence actually visible in this request; recover missing details with a specific offset and limit of at most 80 lines. Do not restart a whole-file scan after context compaction; answer once the requested evidence is sufficient."
|
|
312
428
|
: null;
|
|
313
429
|
}
|
|
314
430
|
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
sanitizePersistedMessages,
|
|
28
28
|
} from "./session-message-provenance.js";
|
|
29
29
|
import { createSessionTranscriptStructureProjection } from "./session-transcript-structure.js";
|
|
30
|
+
import { projectToolRoundMessages } from "./session-tool-round.js";
|
|
30
31
|
import { normalizeSessionBudgetRootConfig } from "./session-budget-production-root.js";
|
|
31
32
|
import {
|
|
32
33
|
createDeferredQuestionEventReducer,
|
|
@@ -86,6 +87,8 @@ function replayMessagesFromVerifiedEvents(events) {
|
|
|
86
87
|
isReplayableMessage(event.data)
|
|
87
88
|
) {
|
|
88
89
|
suffix.push(event.data);
|
|
90
|
+
} else if (event?.type === "tool_round") {
|
|
91
|
+
suffix.push(...projectToolRoundMessages(event).toReversed());
|
|
89
92
|
} else if (event?.type === "ws_turn") {
|
|
90
93
|
const turn = projectWsTurnMessages(event);
|
|
91
94
|
if (turn) suffix.push(turn.assistant, turn.user);
|