chainlesschain 0.162.162 → 0.162.164
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/package.json +3 -1
- package/src/assets/web-panel/assets/{AIOps-ClI2ALgv.js → AIOps-BqRkaHGb.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BDjRcJaK.js → ActionButton-DWO7OZLC.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DikSRsaT.js → Analytics-C5oaVezM.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-9mLhMFmz.js → AppLayout-CsUESDoZ.js} +3 -3
- package/src/assets/web-panel/assets/{Artifacts-BSr88BrH.js → Artifacts-Dragqu5k.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CdvyimOF.js → Audit-CQsaHctY.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-D3_hCedh.js → BackgroundAgents-DdG1jNSM.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-Cyvv9RHf.js → Backup-DN6sjK4z.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BZM14kt8.js → BaseInput-Cxm_ZTCl.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DrQSokVz.js → Chat-DVXGmA56.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-HwJR-C6B.js → ChatBubbleRenderer-BdaCL_iE.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-bFLYeWMV.js → Checkbox-DEc4vD_A.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-7mN7xJXT.js → Codegen-CKiTcv36.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DAB5ZWm7.js → Col-Br9UUNN0.js} +1 -1
- package/src/assets/web-panel/assets/{Community-B_1TymaK.js → Community-NeUH_70T.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-Bq1uSWLJ.js → Compact-BqR-z2Df.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CyiE1jEe.js → Compliance-DcWB_SgZ.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Cpk0D7vm.js → Cowork-BfhgcudF.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-d_Lv3rdB.js → Cron-LaU29LgD.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DMqhHjMR.js → Crosschain-B2ZB9uwW.js} +1 -1
- package/src/assets/web-panel/assets/{DID-Br-oPuBw.js → DID-BuHl38A2.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CfLW_RaP.js → Dashboard-m627yqvZ.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DZnTOExV.js → Dropdown-C7bxF8F5.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DHIUHDv1.js → EmailListRenderer-DPLPQzLp.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-Bw3t3myu.js → FamilyGuardDashboard-cOiH6ike.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-D9cpQPAY.js → Federation-Bvkzg88z.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Jdczr9Mr.js → FormItemContext-BTsiA7h_.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BT8ZMpC5.js → GenericCardRenderer-BGnVE5qu.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DjARip2b.js → Git-DS_j6r25.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BgWbSRcj.js → Governance-CKRG3Rsl.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CfouJciM.js → Inference-DW8INoNQ.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-50l7mrAr.js → KnowledgeGraph-BhEYeFhC.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-rq50H84B.js → Logs-wqi0uxuR.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-DUPIEdQH.js → MarkdownRenderer-DS8dE8ty.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-BYgPDmHU.js → Marketplace-CMlTuPLT.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-5DVgcFdx.js → McpTools-CSx4m5JJ.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-B70LPk7f.js → Memory-vnQCUy-_.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-De8ssA5Y.js → MobileBridge-BV2NA742.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-Bw0k8e1B.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-RJsOTsIA.js → Mtc-CzK_FPB1.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-Crh-6BEK.js → MtcAudit-Cb7G9oL3.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-wuoR5l3x.js → Multisig-qJxlUyzr.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-U2Mji4fZ.js → NLProgramming-DqaBJ7Te.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CdTZAyDH.js → Notes-CwhTJkRi.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-C-FucWE7.js → NotificationSettings-YDs4Fal4.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DCYFTeaI.js → OrderTableRenderer-CvEALpq7.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-an3OF85L.js → Organization-CEmQm9n8.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-C-1vmYFe.js → Overflow-zby_P_LA.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Cwh47xKg.js → P2P-DsmDNFNT.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-Dm38GT0k.js → PdhVaultBrowser-FWwFlZGF.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-DS2Y8awD.js → Permissions-DuzQ6me5.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BQNziA0I.js → PersonalDataHub-DZbeN613.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-DF85L1ue.js → Pipeline-CoNSVAUS.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BuLornF-.js → Privacy-OYJEAaMC.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-C3Y31IwX.js → ProjectInit-CWIapV--.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BCPiI8aD.js → ProjectSettings-CX8xMxZn.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BZ6s4k6g.js → Projects-BLCN8YyY.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-D3Wp022X.js → Providers-BV_sxp75.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-CUJfQkH4.js → QrScannerModal-BTnRrHRp.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-UAc0EO8l.js → QuickAsk-D1KZg018.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BaxKupcs.js → Recommend-COm5cuNg.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-C75NNnXZ.js → RemoteSession-0saWEkYI.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-BVXynZAk.js → Reputation-Dr7iKLS2.js} +1 -1
- package/src/assets/web-panel/assets/{Row-C5MRZ0bi.js → Row-C1vpPBcr.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-DAKkEYka.js → RssFeed-CxlWrtNj.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DvWv9YOA.js → Search-BT2V8dPN.js} +1 -1
- package/src/assets/web-panel/assets/{Security-B5peafW1.js → Security-bm9V3c5k.js} +4 -4
- package/src/assets/web-panel/assets/{Services-UQmvPEaG.js → Services-CocH1yyX.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-C3bhWpuF.js → Skeleton-DHItlMdd.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DfkKf9_H.js → Skills-C59sLc4w.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DqMSjlBM.js → Sla-Crj4BMc8.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-C-WbxWUX.js → SpeechSettings-DX4KFceY.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CYuHZ4N2.js → SyncSettings-BD_iwQLr.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-CyeAyB9d.js → Tasks-BmCTzYsH.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-BTCeQnpq.js → Templates-BGUaxZYl.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BifQuA9Y.js → Tenant-BS0tJx2O.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-Bx_9Bocf.js → Terminal--tHSAsld.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-BX0RFCOm.js → TimelineRenderer-D9-MFxS4.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CGHrCmUS.js → Tokens-CiCB485f.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DlqaY4SO.js → Trigger-SmCFuW_l.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DfOmj-ay.js → Trust-DkK3Rqe0.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-B72jH5fE.js → UkeySign-CDM4LGdB.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BT-63fqv.js → VideoEditing-IDB7DJXt.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-Dsul3sHr.js → Wallet-Bikj5cmt.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Izkwwdgy.js → WebAuthn-C9abWlAw.js} +2 -2
- package/src/assets/web-panel/assets/{WorkflowEditor-D3FHSiH1.js → WorkflowEditor-DrbRKVky.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CmVz5z3U.js → chat-CccCSDQf.js} +1 -1
- package/src/assets/web-panel/assets/{colors-CuLgyJBp.js → colors-CXObXLhb.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-Ml1YkXpw.js → compact-item-B5kfYAxC.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BvjJ48bs.js → createContext-DmiPBfip.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CvKrADNa.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DVC0157C.js → hasIn-Q6lYteAc.js} +1 -1
- package/src/assets/web-panel/assets/{index-C229ABfy.js → index-25Wlqps4.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ck_52dz_.js → index-33U29UNV.js} +1 -1
- package/src/assets/web-panel/assets/{index-QXzlT9KD.js → index-4oXBDczR.js} +1 -1
- package/src/assets/web-panel/assets/{index-4SBta3d7.js → index-BGYXvMc5.js} +1 -1
- package/src/assets/web-panel/assets/{index-B0S8NzO6.js → index-BJCCa0s_.js} +1 -1
- package/src/assets/web-panel/assets/{index-DJyN28Et.js → index-BJJ5DhKI.js} +1 -1
- package/src/assets/web-panel/assets/{index-DC_OI9H2.js → index-BLOzViJ6.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_XuRgGn.js → index-BRrbUMoG.js} +1 -1
- package/src/assets/web-panel/assets/{index-wvb_FtaV.js → index-BUFpT17Q.js} +1 -1
- package/src/assets/web-panel/assets/{index-KMqhcF7l.js → index-BkRDiqGc.js} +1 -1
- package/src/assets/web-panel/assets/{index-vG4XSJ1d.js → index-BsfvW6my.js} +1 -1
- package/src/assets/web-panel/assets/{index-DYXHFr4r.js → index-By-kfPED.js} +1 -1
- package/src/assets/web-panel/assets/{index-DuGszaWY.js → index-BzFTcQsc.js} +1 -1
- package/src/assets/web-panel/assets/{index-CXkJH-lK.js → index-C2FXdboq.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dt5KMjwJ.js → index-C5XOlTHN.js} +1 -1
- package/src/assets/web-panel/assets/{index-4wcgvPoF.js → index-CLhBDS2w.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_H_S-DY.js → index-CMq7UeoT.js} +1 -1
- package/src/assets/web-panel/assets/{index-BydjdZnl.js → index-CVMTWNKl.js} +1 -1
- package/src/assets/web-panel/assets/{index-BjHBpRYZ.js → index-Caghi-yL.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bzr0rB5T.js → index-CdB6FuSH.js} +1 -1
- package/src/assets/web-panel/assets/{index-BbPtzB8M.js → index-CoK8-kWQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BUt8kTIB.js → index-CsO3GUAY.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_vRgTl9.js → index-CwZBa20Z.js} +1 -1
- package/src/assets/web-panel/assets/{index-DukDVXCv.js → index-D-kASucw.js} +1 -1
- package/src/assets/web-panel/assets/{index-BbepULSc.js → index-D8eBw7vW.js} +1 -1
- package/src/assets/web-panel/assets/{index-BfuKP4qe.js → index-D9wgifk4.js} +1 -1
- package/src/assets/web-panel/assets/{index-BiC6dQZG.js → index-DAYCTc07.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmFNvNLZ.js → index-DF6Q8l2h.js} +1 -1
- package/src/assets/web-panel/assets/index-DTdFUWc_.js +1 -0
- package/src/assets/web-panel/assets/{index-KjgKdZe-.js → index-DgTLApS9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BpFJHiTv.js → index-DgxVJ8pg.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cw0Rjy87.js → index-DlJvIqoR.js} +1 -1
- package/src/assets/web-panel/assets/{index-CFYVQMvu.js → index-Dw4eHfsG.js} +1 -1
- package/src/assets/web-panel/assets/{index-CudmwNBV.js → index-Rh-ytwow.js} +3 -3
- package/src/assets/web-panel/assets/{index-DDGaOWBV.js → index-StAEt2pq.js} +1 -1
- package/src/assets/web-panel/assets/{index-D3lD5qap.js → index-bzfw951k.js} +1 -1
- package/src/assets/web-panel/assets/{index-BzG-x9p4.js → index-fSUMbMvv.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_EvG_GW.js → index-tFHy5Wol.js} +1 -1
- package/src/assets/web-panel/assets/index-zQ0M_kz-.js +1 -0
- package/src/assets/web-panel/assets/{initDefaultProps-BDgd2YZ8.js → initDefaultProps-BNq7X72N.js} +1 -1
- package/src/assets/web-panel/assets/{motion-ErtrXdYe.js → motion-DOf9viTc.js} +1 -1
- package/src/assets/web-panel/assets/{move-_GPPNCgs.js → move-CRHTcW4x.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DRLL-8V2.js → mtc-parser-Cyoozgm2.js} +1 -1
- package/src/assets/web-panel/assets/{omit-p86sl023.js → omit-6-TjFNru.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-BTu7cMLH.js → pickAttrs-DlUmO1zi.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BEKz6Isr.js → placementArrow-CFOL8mIR.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-Ck42c9MP.js → responsiveObserve-BtaFK5zd.js} +1 -1
- package/src/assets/web-panel/assets/{slide-1CEvuPsB.js → slide-BIbc2bur.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BDfdx1GJ.js → statusUtils-BXX9d6-w.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-B4K46m5w.js → styleChecker-BGv10Bpn.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DoIVpJNq.js → useFlexGapSupport-bKnKT8tw.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DdAWi-ln.js → useFs-FY4IL4jn.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-iUu6k8dx.js → usePersonalDataHub-Bwl9TEgu.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DZz4R6Tw.js → vnode-BM3OHxAT.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DXlf9sA3.js → zoom-D1fbFiHB.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agenda.js +320 -32
- package/src/commands/agent.js +105 -11
- package/src/commands/context.js +125 -14
- package/src/commands/doctor.js +85 -1
- package/src/commands/hook.js +181 -7
- package/src/commands/plugin.js +456 -1
- package/src/commands/review.js +363 -2
- package/src/commands/session.js +191 -0
- package/src/commands/team.js +19 -1
- package/src/data/changelog.json +10 -4
- package/src/gateways/ws/remote-session-protocol.js +39 -0
- package/src/gateways/ws/session-protocol.js +55 -2
- package/src/gateways/ws/ws-agent-handler.js +50 -0
- package/src/harness/jsonl-session-store.js +60 -0
- package/src/harness/worktree-isolator.js +140 -5
- package/src/lib/agent-schedule-store.js +318 -10
- package/src/lib/agent-team/team-worktree.js +37 -2
- package/src/lib/agent-tool-admission.js +125 -0
- package/src/lib/app-preview.js +360 -0
- package/src/lib/async-hook-supervisor.cjs +54 -0
- package/src/lib/capability-manifest.js +319 -0
- package/src/lib/capability-negotiation.js +23 -12
- package/src/lib/channels/channel-manager.js +54 -2
- package/src/lib/cli-context-engineering.js +9 -2
- package/src/lib/context-breakdown.js +116 -0
- package/src/lib/diagnostic-bundle.js +230 -0
- package/src/lib/doctor-checkup.js +275 -4
- package/src/lib/execution-location.js +286 -0
- package/src/lib/headless-manifest.js +13 -40
- package/src/lib/hook-event-log.cjs +193 -0
- package/src/lib/hook-stats-store.cjs +197 -0
- package/src/lib/idempotency.js +109 -0
- package/src/lib/interaction-adapter.js +34 -4
- package/src/lib/json-schema-output.js +46 -12
- package/src/lib/json-schema-validate.js +143 -3
- package/src/lib/jsonl-session-store.js +1 -0
- package/src/lib/lsp/__tests__/lsp-manager.test.js +58 -0
- package/src/lib/lsp/lsp-manager.js +56 -0
- package/src/lib/monitor-event.js +290 -0
- package/src/lib/operation-fingerprint.js +217 -0
- package/src/lib/plugin-runtime/bin.js +37 -0
- package/src/lib/plugin-runtime/capabilities.js +19 -0
- package/src/lib/plugin-runtime/capability-consent.js +203 -0
- package/src/lib/plugin-runtime/hooks.js +37 -0
- package/src/lib/plugin-runtime/lsp.js +38 -0
- package/src/lib/plugin-runtime/mcp.js +40 -0
- package/src/lib/plugin-runtime/monitors.js +37 -0
- package/src/lib/plugin-runtime/plugin-options.js +151 -0
- package/src/lib/plugin-runtime/policy.js +62 -0
- package/src/lib/plugin-runtime/scopes.js +20 -4
- package/src/lib/pr-automation-policy.js +224 -0
- package/src/lib/project-instructions.js +128 -0
- package/src/lib/remote-approval-bridge.js +66 -13
- package/src/lib/repl-goal.js +132 -0
- package/src/lib/repl-rewind.js +153 -0
- package/src/lib/review-comment-anchor.js +241 -0
- package/src/lib/review-pipeline.js +46 -7
- package/src/lib/runtime-checkup.js +221 -0
- package/src/lib/schedule-planner.js +17 -1
- package/src/lib/secret-scan.js +409 -0
- package/src/lib/session-branch.js +0 -0
- package/src/lib/settings-hook-events.cjs +13 -0
- package/src/lib/settings-loader.cjs +42 -0
- package/src/lib/side-effect-ledger-store.js +88 -0
- package/src/lib/side-effect-ledger.js +401 -0
- package/src/lib/skill-loader.js +32 -0
- package/src/lib/skill-path-scope.js +63 -0
- package/src/lib/sub-agent-context.js +110 -9
- package/src/lib/sub-agent-registry.js +19 -0
- package/src/lib/subagent-inheritance.js +112 -0
- package/src/lib/telemetry/span-recorder.js +17 -4
- package/src/lib/terminal-context.js +195 -0
- package/src/lib/turn-binding-store.js +119 -0
- package/src/lib/unattended-action-policy.js +182 -0
- package/src/lib/worktree-cleanup-safety.js +138 -0
- package/src/lib/worktree-isolator.js +8 -0
- package/src/lib/worktree-sparse.js +131 -0
- package/src/repl/agent-repl.js +280 -30
- package/src/repl/permission-tier.js +28 -0
- package/src/runtime/agent-core.js +565 -21
- package/src/runtime/coding-agent-contract-shared.cjs +60 -1
- package/src/runtime/diagnostics.js +60 -0
- package/src/runtime/headless-runner.js +393 -1
- package/src/runtime/headless-stream.js +60 -1
- package/src/assets/web-panel/assets/MobileProjects-D10VISps.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DhiGrbsh.js +0 -1
- package/src/assets/web-panel/assets/index-B7a1TrU6.js +0 -1
- package/src/assets/web-panel/assets/index-C2B1KRja.js +0 -1
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Side-effect ledger — crash-safe accounting of irreversible actions so a
|
|
3
|
+
* recovered worker never repeats a side-effect it already issued (P0
|
|
4
|
+
* state-machine slice: CLAUDE_CODE_CLI_INCREMENTAL_GAP_ANALYSIS_2026-07-12
|
|
5
|
+
* §"后台 Agent 状态机" — "副作用台账(prepared→started→committed|failed|unknown)
|
|
6
|
+
* 与崩溃恢复重放").
|
|
7
|
+
*
|
|
8
|
+
* The gap's acceptance is concrete: kill the worker/supervisor mid `file-write`,
|
|
9
|
+
* `git push`, or `package-install`, and after recovery the effect must not be
|
|
10
|
+
* applied twice. The classic bug is a blind retry — the process died AFTER
|
|
11
|
+
* `git push` reached the remote but BEFORE it recorded success, so a naive
|
|
12
|
+
* "resume the turn" pushes again.
|
|
13
|
+
*
|
|
14
|
+
* The fix is a two-phase record around every side-effect:
|
|
15
|
+
*
|
|
16
|
+
* prepare(opId) → start(opId) → commit(opId) | fail(opId) | unknown(opId)
|
|
17
|
+
*
|
|
18
|
+
* and an HONEST recovery planner. The invariant: a `started`-but-not-settled op
|
|
19
|
+
* is INDETERMINATE — we cannot know if the effect landed — so unless the op is
|
|
20
|
+
* explicitly idempotent it must be INSPECTED by a human/verifier, never blindly
|
|
21
|
+
* redone. `prepared`-but-never-`started` ops are safe to redo (no effect issued
|
|
22
|
+
* yet); `committed` ops are skipped; cleanly `failed` ops redo only when the
|
|
23
|
+
* caller vouched they roll back atomically.
|
|
24
|
+
*
|
|
25
|
+
* Everything here is PURE (no timers, no I/O): the ledger is in-memory +
|
|
26
|
+
* serializable so a caller persists it alongside the session record. I/O lives
|
|
27
|
+
* in [[side-effect-ledger-store.js]].
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/** Lifecycle states of one side-effecting operation. */
|
|
31
|
+
export const SIDE_EFFECT_STATE = Object.freeze({
|
|
32
|
+
PREPARED: "prepared", // intent recorded; nothing issued yet
|
|
33
|
+
STARTED: "started", // effect issued; outcome not yet known
|
|
34
|
+
COMMITTED: "committed", // effect confirmed applied
|
|
35
|
+
FAILED: "failed", // effect confirmed NOT applied (clean failure)
|
|
36
|
+
UNKNOWN: "unknown", // outcome indeterminate (e.g. crash mid-flight)
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
/** Recovery actions the reconciler emits for each unsettled op. */
|
|
40
|
+
export const RECOVERY_ACTION = Object.freeze({
|
|
41
|
+
SKIP: "skip", // already committed — do nothing
|
|
42
|
+
REDO: "redo", // safe to run again (no effect issued, or idempotent)
|
|
43
|
+
INSPECT: "inspect", // indeterminate — verify before any repeat
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Default idempotency for well-known side-effect kinds. Only actions that are
|
|
48
|
+
* naturally safe to re-run without duplicating an external effect are `true`.
|
|
49
|
+
* `file-write` is idempotent ONLY when a pre-write checkpoint exists (the write
|
|
50
|
+
* can be re-applied from the same source over a restored tree); a raw append
|
|
51
|
+
* has no such guarantee, so the default is conservative `false`.
|
|
52
|
+
*/
|
|
53
|
+
const KIND_IDEMPOTENT = new Map([
|
|
54
|
+
["git-push", false],
|
|
55
|
+
["package-install", false],
|
|
56
|
+
["network-mutation", false],
|
|
57
|
+
["payment", false],
|
|
58
|
+
["file-write", false],
|
|
59
|
+
["file-write-checkpointed", true],
|
|
60
|
+
["read", true],
|
|
61
|
+
["compute", true],
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
/** Whether a kind is idempotent by default (unknown kinds → conservative false). */
|
|
65
|
+
export function kindIsIdempotent(kind) {
|
|
66
|
+
return KIND_IDEMPOTENT.get(String(kind || "")) === true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Trim a free-text descriptor to a short, log-safe key. */
|
|
70
|
+
function shortKey(v) {
|
|
71
|
+
if (v == null) return null;
|
|
72
|
+
const s = String(v);
|
|
73
|
+
return s.length > 80 ? s.slice(0, 80) + "…" : s;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Classify one agent tool call into a side-effect kind for the ledger, or null
|
|
78
|
+
* when the tool issues no irreversible external effect (reads, searches, plain
|
|
79
|
+
* git status, etc.). PURE. Conservative by design — an opaque shell command is
|
|
80
|
+
* NON-idempotent so a mid-flight crash forces INSPECT rather than a blind
|
|
81
|
+
* replay (the P0-2 "started_unknown 风险操作默认 needs_input" invariant).
|
|
82
|
+
*
|
|
83
|
+
* Only the tools that can leave a durable external mark are recorded; read-only
|
|
84
|
+
* and purely-local-recoverable tools return null so a resumed session is not
|
|
85
|
+
* spammed with false "verify before replay" warnings.
|
|
86
|
+
*
|
|
87
|
+
* @param {string} toolName
|
|
88
|
+
* @param {object} [args]
|
|
89
|
+
* @returns {{kind:string, key:string|null}|null}
|
|
90
|
+
*/
|
|
91
|
+
export function classifyToolSideEffect(toolName, args = {}) {
|
|
92
|
+
const name = String(toolName || "");
|
|
93
|
+
const a = args && typeof args === "object" ? args : {};
|
|
94
|
+
switch (name) {
|
|
95
|
+
case "write_file":
|
|
96
|
+
case "notebook_edit":
|
|
97
|
+
case "edit_file":
|
|
98
|
+
return {
|
|
99
|
+
kind: "file-write",
|
|
100
|
+
key: shortKey(a.path || a.file || a.notebook_path),
|
|
101
|
+
};
|
|
102
|
+
case "edit_file_hashed":
|
|
103
|
+
// Hash-guarded: re-applying over an already-changed tree fails safely, so
|
|
104
|
+
// a replay cannot silently double-apply — idempotent-by-construction.
|
|
105
|
+
return {
|
|
106
|
+
kind: "file-write-checkpointed",
|
|
107
|
+
key: shortKey(a.path || a.file),
|
|
108
|
+
};
|
|
109
|
+
case "run_shell":
|
|
110
|
+
case "run_code":
|
|
111
|
+
// Opaque command: could be `npm publish`, `rm -rf`, or just `npm test`.
|
|
112
|
+
// We cannot tell, so fail-closed to a non-idempotent effect.
|
|
113
|
+
return { kind: "shell", key: shortKey(a.command || a.code) };
|
|
114
|
+
case "git": {
|
|
115
|
+
const cmd = String(a.command || "").trim();
|
|
116
|
+
const verb = cmd.split(/\s+/)[0] || "";
|
|
117
|
+
if (verb === "push") return { kind: "git-push", key: shortKey(cmd) };
|
|
118
|
+
// Every other git op (status/diff/commit/branch/fetch/pull/…) is either
|
|
119
|
+
// read-only or locally recoverable via git — not an irreversible effect.
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
case "publish_artifact":
|
|
123
|
+
return {
|
|
124
|
+
kind: "network-mutation",
|
|
125
|
+
key: shortKey(a.title || a.path || a.name),
|
|
126
|
+
};
|
|
127
|
+
case "schedule":
|
|
128
|
+
return {
|
|
129
|
+
kind: "network-mutation",
|
|
130
|
+
key: shortKey(a.name || a.cron || a.when),
|
|
131
|
+
};
|
|
132
|
+
case "notify":
|
|
133
|
+
return { kind: "network-mutation", key: shortKey(a.title || a.message) };
|
|
134
|
+
case "browser_act":
|
|
135
|
+
// A CDP action (click/type/submit) can mutate remote state.
|
|
136
|
+
return {
|
|
137
|
+
kind: "network-mutation",
|
|
138
|
+
key: shortKey(a.action || a.selector),
|
|
139
|
+
};
|
|
140
|
+
default:
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function nowFrom(clock) {
|
|
146
|
+
return typeof clock === "function" ? Number(clock()) || 0 : 0;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* The in-memory, serializable ledger. A caller records the two-phase lifecycle
|
|
151
|
+
* around each side-effect and, after a crash, hands the rebuilt ledger to
|
|
152
|
+
* `reconcileSideEffects` for a recovery plan.
|
|
153
|
+
*
|
|
154
|
+
* Timestamps are optional and only stamped when a `clock` is injected — keeping
|
|
155
|
+
* the ledger deterministic in tests (no `Date.now()`).
|
|
156
|
+
*/
|
|
157
|
+
export class SideEffectLedger {
|
|
158
|
+
constructor({ clock } = {}) {
|
|
159
|
+
this._ops = new Map();
|
|
160
|
+
this._order = [];
|
|
161
|
+
this._clock = clock || null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
_rec(opId) {
|
|
165
|
+
return this._ops.get(String(opId)) || null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Record intent to perform a side-effect. Idempotent on opId (re-preparing an
|
|
170
|
+
* existing op is a no-op, so a retry loop can't reset a committed op).
|
|
171
|
+
*
|
|
172
|
+
* @param {string} opId stable id (survives restart — e.g. `${turnId}:${seq}`)
|
|
173
|
+
* @param {{kind?:string, key?:string, idempotent?:boolean, meta?:object}} [info]
|
|
174
|
+
*/
|
|
175
|
+
prepare(opId, info = {}) {
|
|
176
|
+
const id = String(opId);
|
|
177
|
+
if (this._ops.has(id)) return this;
|
|
178
|
+
const kind = info.kind == null ? null : String(info.kind);
|
|
179
|
+
this._ops.set(id, {
|
|
180
|
+
opId: id,
|
|
181
|
+
kind,
|
|
182
|
+
key: info.key == null ? null : String(info.key),
|
|
183
|
+
idempotent:
|
|
184
|
+
typeof info.idempotent === "boolean"
|
|
185
|
+
? info.idempotent
|
|
186
|
+
: kindIsIdempotent(kind),
|
|
187
|
+
state: SIDE_EFFECT_STATE.PREPARED,
|
|
188
|
+
reason: null,
|
|
189
|
+
meta: info.meta && typeof info.meta === "object" ? { ...info.meta } : {},
|
|
190
|
+
preparedAt: nowFrom(this._clock),
|
|
191
|
+
settledAt: null,
|
|
192
|
+
});
|
|
193
|
+
this._order.push(id);
|
|
194
|
+
return this;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Mark the effect as ISSUED (outcome not yet known). Only from `prepared`. */
|
|
198
|
+
start(opId) {
|
|
199
|
+
return this._advance(opId, SIDE_EFFECT_STATE.STARTED, [
|
|
200
|
+
SIDE_EFFECT_STATE.PREPARED,
|
|
201
|
+
]);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Confirm the effect applied. Only from `started`. */
|
|
205
|
+
commit(opId) {
|
|
206
|
+
return this._advance(
|
|
207
|
+
opId,
|
|
208
|
+
SIDE_EFFECT_STATE.COMMITTED,
|
|
209
|
+
[SIDE_EFFECT_STATE.STARTED],
|
|
210
|
+
{ settle: true },
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Confirm the effect did NOT apply (clean failure). Only from `started`. */
|
|
215
|
+
fail(opId, reason) {
|
|
216
|
+
return this._advance(
|
|
217
|
+
opId,
|
|
218
|
+
SIDE_EFFECT_STATE.FAILED,
|
|
219
|
+
[SIDE_EFFECT_STATE.STARTED],
|
|
220
|
+
{ settle: true, reason },
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Explicitly mark an op's outcome indeterminate. Accepts `prepared`/`started`
|
|
226
|
+
* (a recovery pass sweeping unsettled ops), never overrides a settled op.
|
|
227
|
+
*/
|
|
228
|
+
unknown(opId, reason) {
|
|
229
|
+
return this._advance(
|
|
230
|
+
opId,
|
|
231
|
+
SIDE_EFFECT_STATE.UNKNOWN,
|
|
232
|
+
[SIDE_EFFECT_STATE.PREPARED, SIDE_EFFECT_STATE.STARTED],
|
|
233
|
+
{ settle: true, reason },
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
_advance(opId, next, allowedFrom, { settle = false, reason } = {}) {
|
|
238
|
+
const rec = this._rec(opId);
|
|
239
|
+
if (!rec) return this;
|
|
240
|
+
if (!allowedFrom.includes(rec.state)) return this; // illegal transition → ignore
|
|
241
|
+
rec.state = next;
|
|
242
|
+
if (reason != null) rec.reason = String(reason);
|
|
243
|
+
if (settle) rec.settledAt = nowFrom(this._clock);
|
|
244
|
+
return this;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
get(opId) {
|
|
248
|
+
const rec = this._rec(opId);
|
|
249
|
+
return rec ? { ...rec, meta: { ...rec.meta } } : null;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
list() {
|
|
253
|
+
return this._order.map((id) => this.get(id));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Ops that never reached a terminal state — the recovery surface. */
|
|
257
|
+
unsettled() {
|
|
258
|
+
return this.list().filter(
|
|
259
|
+
(o) =>
|
|
260
|
+
o.state === SIDE_EFFECT_STATE.PREPARED ||
|
|
261
|
+
o.state === SIDE_EFFECT_STATE.STARTED,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
toJSON() {
|
|
266
|
+
return { ops: this._order.map((id) => this.get(id)) };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
static fromJSON(json, { clock } = {}) {
|
|
270
|
+
const ledger = new SideEffectLedger({ clock });
|
|
271
|
+
for (const o of json?.ops || []) {
|
|
272
|
+
if (!o || o.opId == null) continue;
|
|
273
|
+
const id = String(o.opId);
|
|
274
|
+
if (ledger._ops.has(id)) continue;
|
|
275
|
+
ledger._ops.set(id, {
|
|
276
|
+
opId: id,
|
|
277
|
+
kind: o.kind == null ? null : String(o.kind),
|
|
278
|
+
key: o.key == null ? null : String(o.key),
|
|
279
|
+
idempotent: o.idempotent === true,
|
|
280
|
+
state: Object.values(SIDE_EFFECT_STATE).includes(o.state)
|
|
281
|
+
? o.state
|
|
282
|
+
: SIDE_EFFECT_STATE.UNKNOWN,
|
|
283
|
+
reason: o.reason == null ? null : String(o.reason),
|
|
284
|
+
meta: o.meta && typeof o.meta === "object" ? { ...o.meta } : {},
|
|
285
|
+
preparedAt: Number(o.preparedAt) || 0,
|
|
286
|
+
settledAt: o.settledAt == null ? null : Number(o.settledAt) || 0,
|
|
287
|
+
});
|
|
288
|
+
ledger._order.push(id);
|
|
289
|
+
}
|
|
290
|
+
return ledger;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Plan recovery for one op record (from SideEffectLedger.get()/list()). HONEST:
|
|
296
|
+
* an indeterminate `started` op is INSPECT unless it is explicitly idempotent.
|
|
297
|
+
*
|
|
298
|
+
* @param {object} op
|
|
299
|
+
* @returns {{opId:string, action:string, reason:string}}
|
|
300
|
+
*/
|
|
301
|
+
export function planOpRecovery(op) {
|
|
302
|
+
const opId = op?.opId ?? null;
|
|
303
|
+
switch (op?.state) {
|
|
304
|
+
case SIDE_EFFECT_STATE.COMMITTED:
|
|
305
|
+
return {
|
|
306
|
+
opId,
|
|
307
|
+
action: RECOVERY_ACTION.SKIP,
|
|
308
|
+
reason: "already committed",
|
|
309
|
+
};
|
|
310
|
+
case SIDE_EFFECT_STATE.PREPARED:
|
|
311
|
+
// Intent only — no external effect was issued, so a clean re-run is safe.
|
|
312
|
+
return {
|
|
313
|
+
opId,
|
|
314
|
+
action: RECOVERY_ACTION.REDO,
|
|
315
|
+
reason: "prepared but never started — no effect issued",
|
|
316
|
+
};
|
|
317
|
+
case SIDE_EFFECT_STATE.FAILED:
|
|
318
|
+
// A clean failure means the effect did not land; idempotent ops re-run.
|
|
319
|
+
return op.idempotent
|
|
320
|
+
? {
|
|
321
|
+
opId,
|
|
322
|
+
action: RECOVERY_ACTION.REDO,
|
|
323
|
+
reason: "failed cleanly and idempotent — safe to retry",
|
|
324
|
+
}
|
|
325
|
+
: {
|
|
326
|
+
opId,
|
|
327
|
+
action: RECOVERY_ACTION.INSPECT,
|
|
328
|
+
reason: "failed — verify no partial effect before retrying",
|
|
329
|
+
};
|
|
330
|
+
case SIDE_EFFECT_STATE.STARTED:
|
|
331
|
+
case SIDE_EFFECT_STATE.UNKNOWN:
|
|
332
|
+
default:
|
|
333
|
+
// Crashed mid-flight: the effect MAY have landed. Only idempotent ops are
|
|
334
|
+
// safe to redo; everything else must be inspected, never blindly repeated.
|
|
335
|
+
return op?.idempotent
|
|
336
|
+
? {
|
|
337
|
+
opId,
|
|
338
|
+
action: RECOVERY_ACTION.REDO,
|
|
339
|
+
reason: "indeterminate but idempotent — safe to re-run",
|
|
340
|
+
}
|
|
341
|
+
: {
|
|
342
|
+
opId,
|
|
343
|
+
action: RECOVERY_ACTION.INSPECT,
|
|
344
|
+
reason:
|
|
345
|
+
"started but not confirmed — effect may have applied; do not repeat blindly",
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Count COMMITTED side-effects that share an idempotency key beyond the first —
|
|
352
|
+
* the "resume 后重复副作用 = 0" acceptance metric (P0-2 §11.2). A committed op's
|
|
353
|
+
* dedup key is `meta.idempotencyKey`; when absent it falls back to the (unique)
|
|
354
|
+
* `opId`, so un-keyed ops never register as duplicates. A correct resume never
|
|
355
|
+
* re-commits an already-committed effect, so this must stay `0`.
|
|
356
|
+
*
|
|
357
|
+
* @param {SideEffectLedger|{ops:Array}} ledger
|
|
358
|
+
* @returns {number}
|
|
359
|
+
*/
|
|
360
|
+
export function countDuplicateCommittedEffects(ledger) {
|
|
361
|
+
const ops =
|
|
362
|
+
ledger instanceof SideEffectLedger
|
|
363
|
+
? ledger.list()
|
|
364
|
+
: Array.isArray(ledger?.ops)
|
|
365
|
+
? ledger.ops
|
|
366
|
+
: [];
|
|
367
|
+
const seen = new Map();
|
|
368
|
+
let duplicates = 0;
|
|
369
|
+
for (const o of ops) {
|
|
370
|
+
if (o?.state !== SIDE_EFFECT_STATE.COMMITTED) continue;
|
|
371
|
+
const key = (o.meta && o.meta.idempotencyKey) || o.opId;
|
|
372
|
+
const n = (seen.get(key) || 0) + 1;
|
|
373
|
+
seen.set(key, n);
|
|
374
|
+
if (n > 1) duplicates++;
|
|
375
|
+
}
|
|
376
|
+
return duplicates;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Build a recovery plan for every op in a (crash-rebuilt) ledger.
|
|
381
|
+
*
|
|
382
|
+
* @param {SideEffectLedger|{ops:Array}} ledger
|
|
383
|
+
* @returns {{plans:Array, redo:string[], inspect:string[], skip:string[]}}
|
|
384
|
+
*/
|
|
385
|
+
export function reconcileSideEffects(ledger) {
|
|
386
|
+
const ops =
|
|
387
|
+
ledger instanceof SideEffectLedger
|
|
388
|
+
? ledger.list()
|
|
389
|
+
: Array.isArray(ledger?.ops)
|
|
390
|
+
? ledger.ops
|
|
391
|
+
: [];
|
|
392
|
+
const plans = ops.map((o) => planOpRecovery(o));
|
|
393
|
+
const bucket = (action) =>
|
|
394
|
+
plans.filter((p) => p.action === action).map((p) => p.opId);
|
|
395
|
+
return {
|
|
396
|
+
plans,
|
|
397
|
+
redo: bucket(RECOVERY_ACTION.REDO),
|
|
398
|
+
inspect: bucket(RECOVERY_ACTION.INSPECT),
|
|
399
|
+
skip: bucket(RECOVERY_ACTION.SKIP),
|
|
400
|
+
};
|
|
401
|
+
}
|
package/src/lib/skill-loader.js
CHANGED
|
@@ -28,6 +28,7 @@ import { getElectronUserDataDir } from "./paths.js";
|
|
|
28
28
|
import { findProjectRoot } from "./project-detector.js";
|
|
29
29
|
import { findProjectRoot as findGitRoot } from "./project-instructions.js";
|
|
30
30
|
import { parseSkillMcpServers } from "./skill-mcp.js";
|
|
31
|
+
import { normalizeSkillPaths } from "./skill-path-scope.js";
|
|
31
32
|
import { discoverPluginSkillLayers } from "./plugin-runtime/skills.js";
|
|
32
33
|
import settingsLoader from "./settings-loader.cjs";
|
|
33
34
|
|
|
@@ -467,6 +468,10 @@ export class CLISkillLoader {
|
|
|
467
468
|
executionMode: data.executionMode || null,
|
|
468
469
|
cliDomain: data.cliDomain || null,
|
|
469
470
|
cliVersionHash: data.cliVersionHash || null,
|
|
471
|
+
// Optional per-directory path-scope: when set, the skill is only
|
|
472
|
+
// surfaced/runnable under a matching subtree (large-monorepo lazy
|
|
473
|
+
// context). null (the default) = applies everywhere.
|
|
474
|
+
paths: normalizeSkillPaths(data),
|
|
470
475
|
dirName: entry.name,
|
|
471
476
|
hasHandler: fs.existsSync(path.join(skillDir, "handler.js")),
|
|
472
477
|
body,
|
|
@@ -515,6 +520,33 @@ export class CLISkillLoader {
|
|
|
515
520
|
return this._cache;
|
|
516
521
|
}
|
|
517
522
|
|
|
523
|
+
/**
|
|
524
|
+
* Every skill found in every layer, WITHOUT dedup. loadAll() keeps only the
|
|
525
|
+
* winning copy per id (Map.set overwrite), so a skill id defined in more than
|
|
526
|
+
* one layer silently shadows — a caller (e.g. `cc doctor`) needs the full,
|
|
527
|
+
* un-deduped list to detect and report that. Same scan logic as loadAll, no
|
|
528
|
+
* override collapse.
|
|
529
|
+
* @returns {Array<{id:string, layer:string, skillDir:string}>}
|
|
530
|
+
*/
|
|
531
|
+
listSkillLayerEntries(options = {}) {
|
|
532
|
+
if (allSkillsDisabled(options)) return [];
|
|
533
|
+
const dropBundled = bundledSkillsDisabled(options);
|
|
534
|
+
const out = [];
|
|
535
|
+
for (const { layer, path: layerPath, exists } of this.getLayerPaths()) {
|
|
536
|
+
if (!exists) continue;
|
|
537
|
+
if (dropBundled && (layer === "bundled" || layer === "cli-bundled"))
|
|
538
|
+
continue;
|
|
539
|
+
for (const skill of this._loadFromDir(layerPath, layer)) {
|
|
540
|
+
out.push({
|
|
541
|
+
id: skill.id,
|
|
542
|
+
layer: skill.source || layer,
|
|
543
|
+
skillDir: skill.skillDir,
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return out;
|
|
548
|
+
}
|
|
549
|
+
|
|
518
550
|
/**
|
|
519
551
|
* Get resolved skills (uses cache if available)
|
|
520
552
|
* @returns {object[]}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-directory skill path-scoping (P1 large-monorepo lazy context).
|
|
3
|
+
*
|
|
4
|
+
* A skill may declare a `paths:` (or `globs:` / `pathScope:`) frontmatter list
|
|
5
|
+
* so it is only surfaced when the agent is working under a matching subtree —
|
|
6
|
+
* e.g. a skill scoped to `packages/cli/**` should not clutter discovery or run
|
|
7
|
+
* when the agent is operating in `android-app/`. This keeps the on-demand skill
|
|
8
|
+
* surface small in a big monorepo.
|
|
9
|
+
*
|
|
10
|
+
* Pure (path math only; the matching semantics are reused from
|
|
11
|
+
* project-instructions.js's `ruleApplies`, the same prefix-overlap rule used for
|
|
12
|
+
* path-scoped `.claude/rules`). A skill with no `paths` applies everywhere, so a
|
|
13
|
+
* skill set with no path-scoped skills is filtered byte-for-byte unchanged.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
import { ruleApplies } from "./project-instructions.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Normalize a skill's raw path-scope frontmatter (`paths` / `globs` /
|
|
21
|
+
* `pathScope`, string or array) into a non-empty string[] of globs, or null
|
|
22
|
+
* when the skill declares no scope (→ applies everywhere).
|
|
23
|
+
*/
|
|
24
|
+
export function normalizeSkillPaths(data) {
|
|
25
|
+
const raw = data?.paths ?? data?.globs ?? data?.pathScope ?? null;
|
|
26
|
+
if (raw == null) return null;
|
|
27
|
+
const list = (Array.isArray(raw) ? raw : [raw])
|
|
28
|
+
.map((g) => String(g == null ? "" : g).trim())
|
|
29
|
+
.filter(Boolean);
|
|
30
|
+
return list.length > 0 ? list : null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The agent's cwd expressed relative to the project root, forward-slashed — the
|
|
35
|
+
* form `ruleApplies` expects (skill `paths` globs are project-root-relative,
|
|
36
|
+
* like rule globs). A cwd at (or outside) the root resolves to "" → every skill
|
|
37
|
+
* applies (never hide skills just because we can't locate the root).
|
|
38
|
+
*/
|
|
39
|
+
export function relCwdForCwd(cwd, root) {
|
|
40
|
+
if (!cwd || !root) return "";
|
|
41
|
+
let rel;
|
|
42
|
+
try {
|
|
43
|
+
rel = path.relative(root, cwd);
|
|
44
|
+
} catch {
|
|
45
|
+
return "";
|
|
46
|
+
}
|
|
47
|
+
if (!rel || rel.startsWith("..") || path.isAbsolute(rel)) return "";
|
|
48
|
+
return rel.replace(/\\/g, "/");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Does a skill's path-scope apply when the agent runs at `relCwd`? A skill with
|
|
53
|
+
* no `paths` (null/empty) applies everywhere.
|
|
54
|
+
*/
|
|
55
|
+
export function skillAppliesToCwd(skill, relCwd) {
|
|
56
|
+
return ruleApplies(skill?.paths || [], relCwd);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Filter a skill list to those whose path-scope applies at `relCwd`. */
|
|
60
|
+
export function filterSkillsByRelCwd(skills, relCwd) {
|
|
61
|
+
if (!Array.isArray(skills)) return skills;
|
|
62
|
+
return skills.filter((s) => skillAppliesToCwd(s, relCwd));
|
|
63
|
+
}
|