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
|
@@ -23,9 +23,160 @@ import fs from "fs";
|
|
|
23
23
|
import os from "os";
|
|
24
24
|
import path from "path";
|
|
25
25
|
import { randomUUID } from "crypto";
|
|
26
|
+
import { isEntryExpired, effectiveFireAt } from "./schedule-planner.js";
|
|
27
|
+
import { SUBAGENT_PERMISSION_MODES } from "./subagent-contract.js";
|
|
28
|
+
import { parseGoalCondition } from "./goal-condition-engine.js";
|
|
29
|
+
import { normalizeActionClass } from "./unattended-action-policy.js";
|
|
26
30
|
|
|
27
31
|
export const SCHEDULE_KINDS = Object.freeze(["wakeup", "cron", "monitor"]);
|
|
28
32
|
|
|
33
|
+
/** A valid absolute-epoch-ms expiry, or null (never expires). */
|
|
34
|
+
function normalizeExpiresAt(v) {
|
|
35
|
+
const n = Number(v);
|
|
36
|
+
return Number.isFinite(n) && n > 0 ? n : null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A non-negative integer jitter window (ms), or 0. Stored PER entry so each
|
|
41
|
+
* task carries its own deterministic spread — the schedule-planner applies a
|
|
42
|
+
* stable per-id offset within `[0, jitterMs)` so tasks sharing a cron minute
|
|
43
|
+
* fan out instead of firing as a thundering herd. 0 (the default) = no jitter.
|
|
44
|
+
*/
|
|
45
|
+
function normalizeJitterMs(v) {
|
|
46
|
+
const n = Math.floor(Number(v));
|
|
47
|
+
return Number.isFinite(n) && n > 0 ? n : 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* A per-task execution policy for wakeup/cron entries — how the spawned
|
|
52
|
+
* `cc agent` run should behave when this entry fires. Each field maps to a real
|
|
53
|
+
* `cc agent` flag consumed by `cc agenda run`, so a scheduled task can carry its
|
|
54
|
+
* OWN permission mode, worktree isolation and turn budget instead of inheriting
|
|
55
|
+
* whatever ambient environment `cc agenda run` happens to run in (the gap's
|
|
56
|
+
* "每个定时任务独立预算·权限模式·Worktree"). Returns `null` when nothing is set —
|
|
57
|
+
* the entry then omits `runPolicy` entirely and is byte-identical to before,
|
|
58
|
+
* so the default (unpolicied) task keeps spawning a plain `cc agent -p`.
|
|
59
|
+
*
|
|
60
|
+
* - permissionMode → `--permission-mode <mode>` (validated against the single
|
|
61
|
+
* source SUBAGENT_PERMISSION_MODES; invalid → dropped)
|
|
62
|
+
* - worktree → `--worktree` (run the task in a fresh git worktree)
|
|
63
|
+
* - maxTurns → `--max-turns <n>` (cap the agent loop — a real budget)
|
|
64
|
+
* - goalCondition → `--goal-condition <spec>` (re-drive the run to a
|
|
65
|
+
* completion condition; validated via parseGoalCondition,
|
|
66
|
+
* invalid spec → the whole goal set is dropped) with its
|
|
67
|
+
* own token/cost/time/outer-turn budget:
|
|
68
|
+
* goalMaxTokens → `--goal-max-tokens <n>`
|
|
69
|
+
* goalMaxCost → `--goal-max-cost <usd>`
|
|
70
|
+
* goalMaxTime → `--goal-max-time <ms>`
|
|
71
|
+
* maxOuterTurns → `--max-outer-turns <n>`
|
|
72
|
+
* (the goal budgets are meaningless without a valid condition, so they are
|
|
73
|
+
* only kept when the condition parses.)
|
|
74
|
+
*
|
|
75
|
+
* Monitors run a shell command / watch a file rather than spawning an agent, so
|
|
76
|
+
* a run policy does not apply to them.
|
|
77
|
+
*/
|
|
78
|
+
export function normalizeRunPolicy({
|
|
79
|
+
permissionMode = null,
|
|
80
|
+
worktree = false,
|
|
81
|
+
maxTurns = null,
|
|
82
|
+
goalCondition = null,
|
|
83
|
+
goalMaxTokens = null,
|
|
84
|
+
goalMaxCost = null,
|
|
85
|
+
goalMaxTime = null,
|
|
86
|
+
maxOuterTurns = null,
|
|
87
|
+
unattendedAllowlist = null,
|
|
88
|
+
} = {}) {
|
|
89
|
+
const policy = {};
|
|
90
|
+
if (
|
|
91
|
+
typeof permissionMode === "string" &&
|
|
92
|
+
SUBAGENT_PERMISSION_MODES.includes(permissionMode)
|
|
93
|
+
) {
|
|
94
|
+
policy.permissionMode = permissionMode;
|
|
95
|
+
}
|
|
96
|
+
if (worktree === true) policy.worktree = true;
|
|
97
|
+
const turns = Math.floor(Number(maxTurns));
|
|
98
|
+
if (Number.isFinite(turns) && turns > 0) policy.maxTurns = turns;
|
|
99
|
+
|
|
100
|
+
// goal-condition + its budgets: run to a completion condition with a per-task
|
|
101
|
+
// token/cost/time/outer-turn budget. Validate the spec up front so a doomed
|
|
102
|
+
// condition never gets scheduled; an invalid spec drops the whole goal set.
|
|
103
|
+
if (typeof goalCondition === "string" && goalCondition.trim()) {
|
|
104
|
+
let validSpec = false;
|
|
105
|
+
try {
|
|
106
|
+
parseGoalCondition(goalCondition);
|
|
107
|
+
validSpec = true;
|
|
108
|
+
} catch {
|
|
109
|
+
// invalid spec → drop goalCondition and its budgets entirely
|
|
110
|
+
}
|
|
111
|
+
if (validSpec) {
|
|
112
|
+
policy.goalCondition = goalCondition.trim();
|
|
113
|
+
const gTokens = Math.floor(Number(goalMaxTokens));
|
|
114
|
+
if (Number.isFinite(gTokens) && gTokens > 0)
|
|
115
|
+
policy.goalMaxTokens = gTokens;
|
|
116
|
+
const gCost = Number(goalMaxCost);
|
|
117
|
+
if (Number.isFinite(gCost) && gCost > 0) policy.goalMaxCost = gCost;
|
|
118
|
+
const gTime = Math.floor(Number(goalMaxTime));
|
|
119
|
+
if (Number.isFinite(gTime) && gTime > 0) policy.goalMaxTime = gTime;
|
|
120
|
+
const outer = Math.floor(Number(maxOuterTurns));
|
|
121
|
+
if (Number.isFinite(outer) && outer > 0) policy.maxOuterTurns = outer;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// P1-8: action classes this UNATTENDED task may still perform (e.g. "publish",
|
|
126
|
+
// "external_message"). Validated against the known classes; unknowns dropped.
|
|
127
|
+
// Absent → the scheduled run gets the default-deny high-risk tool set.
|
|
128
|
+
if (Array.isArray(unattendedAllowlist)) {
|
|
129
|
+
const cleaned = [
|
|
130
|
+
...new Set(unattendedAllowlist.map(normalizeActionClass).filter(Boolean)),
|
|
131
|
+
];
|
|
132
|
+
if (cleaned.length > 0) policy.unattendedAllowlist = cleaned;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return Object.keys(policy).length > 0 ? policy : null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Resolve an entry's expiry: an explicit absolute `expiresAt` (epoch-ms) wins;
|
|
140
|
+
* otherwise a positive relative `expiresInMs` is anchored to the store's own
|
|
141
|
+
* `now` (symmetric with `delayMs` on a wakeup, and deterministic under an
|
|
142
|
+
* injected clock). No expiry → null.
|
|
143
|
+
*/
|
|
144
|
+
function resolveExpiresAt(expiresAt, expiresInMs, now) {
|
|
145
|
+
if (expiresAt != null) return normalizeExpiresAt(expiresAt);
|
|
146
|
+
const rel = Math.floor(Number(expiresInMs));
|
|
147
|
+
if (Number.isFinite(rel) && rel > 0) return normalizeExpiresAt(now + rel);
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Is an entry currently in a schedulable (non-terminal) state? */
|
|
152
|
+
function isSchedulableStatus(entry) {
|
|
153
|
+
return entry.kind === "wakeup"
|
|
154
|
+
? entry.status === "pending"
|
|
155
|
+
: entry.status === "active";
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Statuses an entry can never leave — safe to prune. */
|
|
159
|
+
export const TERMINAL_STATUSES = Object.freeze([
|
|
160
|
+
"fired",
|
|
161
|
+
"matched",
|
|
162
|
+
"exhausted",
|
|
163
|
+
"expired",
|
|
164
|
+
]);
|
|
165
|
+
const TERMINAL_SET = new Set(TERMINAL_STATUSES);
|
|
166
|
+
|
|
167
|
+
/** Best available "this entry reached a terminal state at" timestamp. */
|
|
168
|
+
function terminalAt(entry) {
|
|
169
|
+
return (
|
|
170
|
+
entry.firedAt ??
|
|
171
|
+
entry.matchedAt ??
|
|
172
|
+
entry.expiredAt ??
|
|
173
|
+
entry.lastRunAt ??
|
|
174
|
+
entry.lastCheckAt ??
|
|
175
|
+
entry.createdAt ??
|
|
176
|
+
0
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
29
180
|
export function agentScheduleDir(homedir = os.homedir()) {
|
|
30
181
|
return path.join(homedir, ".chainlesschain", "agent-schedule");
|
|
31
182
|
}
|
|
@@ -154,11 +305,23 @@ export class AgentScheduleStore {
|
|
|
154
305
|
|
|
155
306
|
// ── create ──────────────────────────────────────────────────────────────
|
|
156
307
|
|
|
157
|
-
scheduleWakeup(
|
|
308
|
+
scheduleWakeup(options = {}) {
|
|
309
|
+
const {
|
|
310
|
+
prompt,
|
|
311
|
+
delayMs = 0,
|
|
312
|
+
dueAt = null,
|
|
313
|
+
label = null,
|
|
314
|
+
expiresAt = null,
|
|
315
|
+
expiresInMs = null,
|
|
316
|
+
jitterMs = 0,
|
|
317
|
+
} = options;
|
|
158
318
|
if (!prompt || typeof prompt !== "string") {
|
|
159
319
|
throw new Error("wakeup requires a prompt");
|
|
160
320
|
}
|
|
161
321
|
const now = this._now();
|
|
322
|
+
// The full options object is passed to normalizeRunPolicy, which reads only
|
|
323
|
+
// the policy fields it knows — new policy knobs need no signature change here.
|
|
324
|
+
const runPolicy = normalizeRunPolicy(options);
|
|
162
325
|
const entry = {
|
|
163
326
|
id: randomUUID(),
|
|
164
327
|
kind: "wakeup",
|
|
@@ -166,6 +329,9 @@ export class AgentScheduleStore {
|
|
|
166
329
|
label: label || null,
|
|
167
330
|
dueAt:
|
|
168
331
|
dueAt != null ? Number(dueAt) : now + Math.max(0, Number(delayMs) || 0),
|
|
332
|
+
expiresAt: resolveExpiresAt(expiresAt, expiresInMs, now),
|
|
333
|
+
jitterMs: normalizeJitterMs(jitterMs),
|
|
334
|
+
...(runPolicy ? { runPolicy } : {}),
|
|
169
335
|
createdAt: now,
|
|
170
336
|
status: "pending",
|
|
171
337
|
};
|
|
@@ -175,7 +341,15 @@ export class AgentScheduleStore {
|
|
|
175
341
|
return entry;
|
|
176
342
|
}
|
|
177
343
|
|
|
178
|
-
createCron(
|
|
344
|
+
createCron(options = {}) {
|
|
345
|
+
const {
|
|
346
|
+
prompt,
|
|
347
|
+
cron,
|
|
348
|
+
label = null,
|
|
349
|
+
expiresAt = null,
|
|
350
|
+
expiresInMs = null,
|
|
351
|
+
jitterMs = 0,
|
|
352
|
+
} = options;
|
|
179
353
|
if (!prompt || typeof prompt !== "string") {
|
|
180
354
|
throw new Error("cron requires a prompt");
|
|
181
355
|
}
|
|
@@ -184,6 +358,8 @@ export class AgentScheduleStore {
|
|
|
184
358
|
if (nextAt == null) {
|
|
185
359
|
throw new Error(`cron "${cron}" has no upcoming match`);
|
|
186
360
|
}
|
|
361
|
+
// Full options → normalizeRunPolicy (reads only known policy fields).
|
|
362
|
+
const runPolicy = normalizeRunPolicy(options);
|
|
187
363
|
const entry = {
|
|
188
364
|
id: randomUUID(),
|
|
189
365
|
kind: "cron",
|
|
@@ -191,6 +367,9 @@ export class AgentScheduleStore {
|
|
|
191
367
|
cron,
|
|
192
368
|
label: label || null,
|
|
193
369
|
nextAt,
|
|
370
|
+
expiresAt: resolveExpiresAt(expiresAt, expiresInMs, now),
|
|
371
|
+
jitterMs: normalizeJitterMs(jitterMs),
|
|
372
|
+
...(runPolicy ? { runPolicy } : {}),
|
|
194
373
|
createdAt: now,
|
|
195
374
|
lastRunAt: null,
|
|
196
375
|
runs: 0,
|
|
@@ -204,14 +383,56 @@ export class AgentScheduleStore {
|
|
|
204
383
|
|
|
205
384
|
createMonitor({
|
|
206
385
|
command,
|
|
386
|
+
watchFile = null,
|
|
387
|
+
watchUrl = null,
|
|
388
|
+
watchChange = false,
|
|
207
389
|
intervalMs,
|
|
208
390
|
stopWhen = null,
|
|
209
391
|
notify = null,
|
|
210
392
|
maxChecks = null,
|
|
211
393
|
label = null,
|
|
394
|
+
expiresAt = null,
|
|
395
|
+
expiresInMs = null,
|
|
396
|
+
jitterMs = 0,
|
|
212
397
|
} = {}) {
|
|
213
|
-
|
|
214
|
-
|
|
398
|
+
// A monitor watches exactly one source:
|
|
399
|
+
// - a shell `command` → match its stdout/stderr
|
|
400
|
+
// - a `watchFile` → match its content, or fire when the file appears
|
|
401
|
+
// - a `watchUrl` → GET it and match the response body, or fire on 2xx
|
|
402
|
+
const hasCommand = typeof command === "string" && command.length > 0;
|
|
403
|
+
const hasFile = typeof watchFile === "string" && watchFile.length > 0;
|
|
404
|
+
const hasUrl = typeof watchUrl === "string" && watchUrl.length > 0;
|
|
405
|
+
const sourceCount = Number(hasCommand) + Number(hasFile) + Number(hasUrl);
|
|
406
|
+
if (sourceCount !== 1) {
|
|
407
|
+
throw new Error(
|
|
408
|
+
"monitor requires exactly one of `command`, `watchFile`, or `watchUrl`",
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
if (hasUrl) {
|
|
412
|
+
// Only http(s) endpoints — reject file:// and other schemes up-front so a
|
|
413
|
+
// bad URL errors at creation, not silently at the first poll.
|
|
414
|
+
let parsed;
|
|
415
|
+
try {
|
|
416
|
+
parsed = new URL(watchUrl);
|
|
417
|
+
} catch {
|
|
418
|
+
throw new Error(`monitor watchUrl is not a valid URL: ${watchUrl}`);
|
|
419
|
+
}
|
|
420
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
421
|
+
throw new Error(
|
|
422
|
+
`monitor watchUrl must be http(s), got: ${parsed.protocol}`,
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (watchChange) {
|
|
427
|
+
// `watchChange` is a file-only mode — fire when the file's mtime advances.
|
|
428
|
+
// It carries its own signal, so it cannot be combined with a stopWhen
|
|
429
|
+
// content pattern (that would be an ambiguous double condition).
|
|
430
|
+
if (!hasFile) {
|
|
431
|
+
throw new Error("monitor watchChange requires a watchFile");
|
|
432
|
+
}
|
|
433
|
+
if (stopWhen != null) {
|
|
434
|
+
throw new Error("monitor watchChange cannot be combined with stopWhen");
|
|
435
|
+
}
|
|
215
436
|
}
|
|
216
437
|
const interval = Math.max(1000, Number(intervalMs) || 60000);
|
|
217
438
|
if (stopWhen != null) {
|
|
@@ -221,16 +442,24 @@ export class AgentScheduleStore {
|
|
|
221
442
|
new RegExp(stopWhen);
|
|
222
443
|
}
|
|
223
444
|
const now = this._now();
|
|
445
|
+
const source = hasFile ? "file" : hasUrl ? "http" : "command";
|
|
224
446
|
const entry = {
|
|
225
447
|
id: randomUUID(),
|
|
226
448
|
kind: "monitor",
|
|
227
|
-
|
|
449
|
+
source,
|
|
450
|
+
command: hasCommand ? command : null,
|
|
451
|
+
watchFile: hasFile ? watchFile : null,
|
|
452
|
+
watchUrl: hasUrl ? watchUrl : null,
|
|
453
|
+
watchChange: hasFile ? Boolean(watchChange) : false,
|
|
454
|
+
lastMtimeMs: null,
|
|
228
455
|
intervalMs: interval,
|
|
229
456
|
stopWhen: stopWhen || null,
|
|
230
457
|
notify: notify || null,
|
|
231
458
|
maxChecks: maxChecks != null ? Number(maxChecks) : null,
|
|
232
459
|
label: label || null,
|
|
233
460
|
nextAt: now + interval,
|
|
461
|
+
expiresAt: resolveExpiresAt(expiresAt, expiresInMs, now),
|
|
462
|
+
jitterMs: normalizeJitterMs(jitterMs),
|
|
234
463
|
createdAt: now,
|
|
235
464
|
checks: 0,
|
|
236
465
|
status: "active",
|
|
@@ -254,13 +483,45 @@ export class AgentScheduleStore {
|
|
|
254
483
|
due(kind = null, atMs = null) {
|
|
255
484
|
const now = atMs != null ? atMs : this._now();
|
|
256
485
|
return this.list(kind).filter((entry) => {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
486
|
+
// An entry past its expiry never fires — even if it is not yet retired
|
|
487
|
+
// (defense-in-depth: retireExpired need not have run first).
|
|
488
|
+
if (isEntryExpired(entry, now)) return false;
|
|
489
|
+
if (!isSchedulableStatus(entry)) return false;
|
|
490
|
+
// The effective fire time applies the entry's OWN deterministic jitter
|
|
491
|
+
// (`entry.jitterMs`, default 0 → base fire time, byte-identical to the
|
|
492
|
+
// prior `dueAt`/`nextAt` comparison). A jittered entry fires slightly
|
|
493
|
+
// later, spreading tasks that share a cron minute.
|
|
494
|
+
const fireAt = effectiveFireAt(entry);
|
|
495
|
+
return fireAt != null && fireAt <= now;
|
|
261
496
|
});
|
|
262
497
|
}
|
|
263
498
|
|
|
499
|
+
/**
|
|
500
|
+
* Retire every schedulable entry whose `expiresAt` has passed (status →
|
|
501
|
+
* "expired", stamping `expiredAt`), across all kinds, so it never fires again.
|
|
502
|
+
* A daemon / `cc agenda run` calls this before firing due entries — mirroring
|
|
503
|
+
* the schedule-planner's "retire expired BEFORE due" semantics. Returns the
|
|
504
|
+
* list of retired entries.
|
|
505
|
+
*/
|
|
506
|
+
retireExpired(atMs = null) {
|
|
507
|
+
const now = atMs != null ? atMs : this._now();
|
|
508
|
+
const retired = [];
|
|
509
|
+
for (const kind of SCHEDULE_KINDS) {
|
|
510
|
+
const entries = this._readAll(kind);
|
|
511
|
+
let changed = false;
|
|
512
|
+
for (const entry of entries) {
|
|
513
|
+
if (isSchedulableStatus(entry) && isEntryExpired(entry, now)) {
|
|
514
|
+
entry.status = "expired";
|
|
515
|
+
entry.expiredAt = now;
|
|
516
|
+
retired.push(entry);
|
|
517
|
+
changed = true;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
if (changed) this._writeAll(kind, entries);
|
|
521
|
+
}
|
|
522
|
+
return retired;
|
|
523
|
+
}
|
|
524
|
+
|
|
264
525
|
cancel(id) {
|
|
265
526
|
for (const kind of SCHEDULE_KINDS) {
|
|
266
527
|
const entries = this._readAll(kind);
|
|
@@ -274,6 +535,28 @@ export class AgentScheduleStore {
|
|
|
274
535
|
return null;
|
|
275
536
|
}
|
|
276
537
|
|
|
538
|
+
/**
|
|
539
|
+
* Remove terminal entries (fired / matched / exhausted / expired) whose
|
|
540
|
+
* terminal timestamp is at or before `before` (default: all terminal
|
|
541
|
+
* entries), across all kinds. Keeps the append-only JSONL store from growing
|
|
542
|
+
* without bound once entries finish. Returns the removed entries; only a kind
|
|
543
|
+
* that actually changed is rewritten.
|
|
544
|
+
*/
|
|
545
|
+
pruneTerminal({ before = Infinity } = {}) {
|
|
546
|
+
const removed = [];
|
|
547
|
+
for (const kind of SCHEDULE_KINDS) {
|
|
548
|
+
const entries = this._readAll(kind);
|
|
549
|
+
const keep = entries.filter((entry) => {
|
|
550
|
+
const prunable =
|
|
551
|
+
TERMINAL_SET.has(entry.status) && terminalAt(entry) <= before;
|
|
552
|
+
if (prunable) removed.push(entry);
|
|
553
|
+
return !prunable;
|
|
554
|
+
});
|
|
555
|
+
if (keep.length !== entries.length) this._writeAll(kind, keep);
|
|
556
|
+
}
|
|
557
|
+
return removed;
|
|
558
|
+
}
|
|
559
|
+
|
|
277
560
|
// ── lifecycle mutations (used by `cc agenda run`) ──────────────────────────
|
|
278
561
|
|
|
279
562
|
/** Mark a one-shot wakeup fired (keeps history; due() skips it). */
|
|
@@ -303,14 +586,39 @@ export class AgentScheduleStore {
|
|
|
303
586
|
* Record a monitor check. `matched` ends the monitor (done); otherwise it is
|
|
304
587
|
* re-armed for the next interval, or exhausted once maxChecks is hit.
|
|
305
588
|
*/
|
|
306
|
-
recordMonitorCheck(
|
|
589
|
+
recordMonitorCheck(
|
|
590
|
+
id,
|
|
591
|
+
{
|
|
592
|
+
matched = false,
|
|
593
|
+
atMs = null,
|
|
594
|
+
mtimeMs = null,
|
|
595
|
+
eventId = null,
|
|
596
|
+
authority = null,
|
|
597
|
+
} = {},
|
|
598
|
+
) {
|
|
307
599
|
const now = atMs != null ? atMs : this._now();
|
|
308
600
|
return this._mutate("monitor", id, (entry) => {
|
|
309
601
|
entry.checks = (entry.checks || 0) + 1;
|
|
310
602
|
entry.lastCheckAt = now;
|
|
603
|
+
// Record the mtime baseline the first time we see the file (for a
|
|
604
|
+
// watchChange monitor); leave it fixed thereafter so "changed" always
|
|
605
|
+
// means "changed since we started watching".
|
|
606
|
+
if (mtimeMs != null && entry.lastMtimeMs == null) {
|
|
607
|
+
entry.lastMtimeMs = mtimeMs;
|
|
608
|
+
}
|
|
311
609
|
if (matched) {
|
|
312
610
|
entry.status = "matched";
|
|
313
611
|
entry.matchedAt = now;
|
|
612
|
+
// Audit record of the firing: the deterministic monitor-event id + the
|
|
613
|
+
// (always SYSTEM/steer) authority the event was delivered under. Durable
|
|
614
|
+
// on the entry so `cc agenda list --json` and a future resident daemon
|
|
615
|
+
// can recognise / dedup the exact observation. Only stamped when the
|
|
616
|
+
// caller supplies them, so legacy callers stay byte-identical.
|
|
617
|
+
if (eventId != null) {
|
|
618
|
+
entry.lastEventId = String(eventId);
|
|
619
|
+
entry.lastEventAt = now;
|
|
620
|
+
}
|
|
621
|
+
if (authority != null) entry.lastAuthority = String(authority);
|
|
314
622
|
} else if (entry.maxChecks != null && entry.checks >= entry.maxChecks) {
|
|
315
623
|
entry.status = "exhausted";
|
|
316
624
|
} else {
|
|
@@ -22,8 +22,31 @@ import {
|
|
|
22
22
|
mergeWorktree as _mergeWorktree,
|
|
23
23
|
} from "../../harness/worktree-isolator.js";
|
|
24
24
|
import { isGitRepo as _isGitRepo } from "../../lib/git-integration.js";
|
|
25
|
+
import { normalizeSparsePaths } from "../../lib/worktree-sparse.js";
|
|
25
26
|
import { execFileSync, spawn } from "node:child_process";
|
|
26
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the sparse-checkout / dependency-symlink options for a task, letting a
|
|
30
|
+
* per-task value (from the plan) override the coordinator-wide default. Returns
|
|
31
|
+
* `undefined` when nothing is configured so `createWorktree` takes its unchanged
|
|
32
|
+
* full-checkout path (byte-identical to before this feature).
|
|
33
|
+
*/
|
|
34
|
+
function resolveWorktreeOptions(task, defaults) {
|
|
35
|
+
const t = task || {};
|
|
36
|
+
const sparse = normalizeSparsePaths(
|
|
37
|
+
t.sparsePaths ?? t.metadata?.sparsePaths ?? defaults.sparsePaths ?? null,
|
|
38
|
+
);
|
|
39
|
+
const symlink =
|
|
40
|
+
t.symlinkDirectories ??
|
|
41
|
+
t.metadata?.symlinkDirectories ??
|
|
42
|
+
defaults.symlinkDirectories ??
|
|
43
|
+
null;
|
|
44
|
+
const opts = {};
|
|
45
|
+
if (sparse) opts.sparsePaths = sparse;
|
|
46
|
+
if (symlink != null) opts.symlinkDirectories = symlink;
|
|
47
|
+
return Object.keys(opts).length ? opts : undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
27
50
|
function defaultRunShell(command, cwd) {
|
|
28
51
|
return new Promise((resolve, reject) => {
|
|
29
52
|
const child = spawn(command, { cwd, shell: true, env: process.env });
|
|
@@ -72,9 +95,15 @@ export const _deps = {
|
|
|
72
95
|
};
|
|
73
96
|
|
|
74
97
|
export class TeamWorktreeCoordinator {
|
|
75
|
-
constructor(repoDir) {
|
|
98
|
+
constructor(repoDir, options = {}) {
|
|
76
99
|
this.repoDir = repoDir;
|
|
77
100
|
this._created = new Map(); // key → { branch, path, committed }
|
|
101
|
+
// Coordinator-wide sparse-checkout / dependency-symlink defaults; a per-task
|
|
102
|
+
// value (task.sparsePaths / task.metadata.sparsePaths) overrides these.
|
|
103
|
+
this._worktreeDefaults = {
|
|
104
|
+
sparsePaths: options.sparsePaths ?? null,
|
|
105
|
+
symlinkDirectories: options.symlinkDirectories ?? null,
|
|
106
|
+
};
|
|
78
107
|
}
|
|
79
108
|
|
|
80
109
|
isGitRepo() {
|
|
@@ -114,7 +143,13 @@ export class TeamWorktreeCoordinator {
|
|
|
114
143
|
}
|
|
115
144
|
this._created.delete(key);
|
|
116
145
|
}
|
|
117
|
-
const
|
|
146
|
+
const wtOptions = resolveWorktreeOptions(task, this._worktreeDefaults);
|
|
147
|
+
const { path: worktreePath } = _deps.createWorktree(
|
|
148
|
+
this.repoDir,
|
|
149
|
+
branch,
|
|
150
|
+
null,
|
|
151
|
+
wtOptions,
|
|
152
|
+
);
|
|
118
153
|
this._created.set(key, { branch, path: worktreePath, committed: false });
|
|
119
154
|
if (typeof runInWorktree === "function") {
|
|
120
155
|
await runInWorktree({ key, task, holder, cwd: worktreePath });
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent tool admission + attribution — the session-level gate behind P1-6
|
|
3
|
+
* (Agent 内 Skills / MCP / Plugin 治理) of
|
|
4
|
+
* CLAUDE_CODE_IDE_INCREMENTAL_GAP_ANALYSIS_2026-07-13.md:
|
|
5
|
+
*
|
|
6
|
+
* "Extension Tier 工具需经过 Capability、Policy、权限、预算和 UI 支持检查后再
|
|
7
|
+
* 开放。每次 Tool/Skill 调用显示 Attribution。"
|
|
8
|
+
*
|
|
9
|
+
* [[plugin-runtime/capabilities.js]] decides whether a PLUGIN declared the
|
|
10
|
+
* capabilities its components need (an install-time contract). It does NOT decide
|
|
11
|
+
* whether an agent TOOL may be opened into a running session. MVP-tier tools
|
|
12
|
+
* (read/write/shell/…) are the baseline set; extension-tier tools (run_skill,
|
|
13
|
+
* spawn_sub_agent, browser, schedule, publish_artifact, MCP tools) reach further
|
|
14
|
+
* and must clear five gates first — and every admitted call must be attributable.
|
|
15
|
+
*
|
|
16
|
+
* This module is that pure decision + the attribution record. Fail-closed and
|
|
17
|
+
* EXHAUSTIVE (every unmet gate is collected, like [[pr-automation-policy.js]]):
|
|
18
|
+
* an extension tool opens only when Capability, Policy, permission, budget and UI
|
|
19
|
+
* support are ALL satisfied. An unknown tier is treated as extension — the
|
|
20
|
+
* most-restrictive reading.
|
|
21
|
+
*
|
|
22
|
+
* PURE: no fs / clock / RNG / process. The caller passes the signals it already
|
|
23
|
+
* gathered (capability grant, policy allow, permission grant, budget, UI
|
|
24
|
+
* support) and reads back allow/deny + a log-safe attribution record.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** Tool tiers. MVP = baseline built-ins; EXTENSION = the gated, further-reaching set. */
|
|
28
|
+
export const TOOL_TIER = Object.freeze({
|
|
29
|
+
MVP: "mvp",
|
|
30
|
+
EXTENSION: "extension",
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
/** Normalize a tier label; anything unrecognized → EXTENSION (fail-closed). */
|
|
34
|
+
export function normalizeToolTier(value) {
|
|
35
|
+
const s = String(value || "")
|
|
36
|
+
.trim()
|
|
37
|
+
.toLowerCase();
|
|
38
|
+
if (s === "mvp" || s === "baseline" || s === "core") return TOOL_TIER.MVP;
|
|
39
|
+
return TOOL_TIER.EXTENSION;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Decide whether a tool may be admitted into the session. EXHAUSTIVE fail-closed.
|
|
44
|
+
*
|
|
45
|
+
* Extension-tier gates (all required): capability, policy, permission, budget,
|
|
46
|
+
* ui-support. MVP-tier gates: policy + budget only (capability / UI support /
|
|
47
|
+
* per-tool permission do not gate a built-in's ADMISSION — a built-in still
|
|
48
|
+
* asks permission per call at execution time).
|
|
49
|
+
*
|
|
50
|
+
* @param {object} params
|
|
51
|
+
* @param {string} params.tool tool name (for the record)
|
|
52
|
+
* @param {string} params.tier "mvp" | "extension"
|
|
53
|
+
* @param {boolean} params.capabilityGranted plugin declared the capability
|
|
54
|
+
* @param {boolean} params.policyAllowed enterprise/allowlist policy permits it
|
|
55
|
+
* @param {boolean} params.permissionGranted session permission mode permits it
|
|
56
|
+
* @param {boolean} params.budgetOk the run's budget is not exhausted
|
|
57
|
+
* @param {boolean} params.uiSupported the current surface can render it
|
|
58
|
+
* @returns {{admitted:boolean, unmet:string[], reason:string, tier:string}}
|
|
59
|
+
*/
|
|
60
|
+
export function admitTool(params = {}) {
|
|
61
|
+
const tier = normalizeToolTier(params.tier);
|
|
62
|
+
const unmet = [];
|
|
63
|
+
|
|
64
|
+
// Policy + budget gate every tier.
|
|
65
|
+
if (params.policyAllowed !== true) unmet.push("policy-blocked");
|
|
66
|
+
if (params.budgetOk !== true) unmet.push("budget-exhausted");
|
|
67
|
+
|
|
68
|
+
// Extension tier additionally requires capability, permission and UI support.
|
|
69
|
+
if (tier === TOOL_TIER.EXTENSION) {
|
|
70
|
+
if (params.capabilityGranted !== true) unmet.push("capability-not-granted");
|
|
71
|
+
if (params.permissionGranted !== true) unmet.push("permission-not-granted");
|
|
72
|
+
if (params.uiSupported !== true) unmet.push("ui-unsupported");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
admitted: unmet.length === 0,
|
|
77
|
+
unmet,
|
|
78
|
+
reason: unmet.length === 0 ? "ok" : unmet[0],
|
|
79
|
+
tier,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Build the log-safe attribution record shown per Tool/Skill call. Carries WHERE
|
|
85
|
+
* a tool came from and WHETHER it was admitted — never a secret, arg value or
|
|
86
|
+
* credential. `decision` is an admitTool() result (or omitted for a bare record).
|
|
87
|
+
*
|
|
88
|
+
* @param {object} params {tool, source, version, scope, tier, callId, decision}
|
|
89
|
+
* @returns {object}
|
|
90
|
+
*/
|
|
91
|
+
export function buildToolAttribution(params = {}) {
|
|
92
|
+
const decision = params.decision || null;
|
|
93
|
+
const tier = normalizeToolTier(params.tier ?? decision?.tier);
|
|
94
|
+
return {
|
|
95
|
+
tool: params.tool == null ? null : String(params.tool),
|
|
96
|
+
source: params.source == null ? null : String(params.source),
|
|
97
|
+
version: params.version == null ? null : String(params.version),
|
|
98
|
+
scope: params.scope == null ? null : String(params.scope),
|
|
99
|
+
tier,
|
|
100
|
+
callId: params.callId == null ? null : String(params.callId),
|
|
101
|
+
admitted: decision ? decision.admitted === true : null,
|
|
102
|
+
reason: decision ? decision.reason : null,
|
|
103
|
+
unmet: decision && Array.isArray(decision.unmet) ? [...decision.unmet] : [],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* A one-line, token-free attribution string for the transcript
|
|
109
|
+
* (e.g. "run_skill ← acme-skills@1.2.0 (project) · extension · admitted").
|
|
110
|
+
*/
|
|
111
|
+
export function describeToolAttribution(attr = {}) {
|
|
112
|
+
const parts = [String(attr.tool || "tool")];
|
|
113
|
+
if (attr.source) {
|
|
114
|
+
parts.push(
|
|
115
|
+
`← ${attr.source}${attr.version ? `@${attr.version}` : ""}${
|
|
116
|
+
attr.scope ? ` (${attr.scope})` : ""
|
|
117
|
+
}`,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
parts.push(`· ${normalizeToolTier(attr.tier)}`);
|
|
121
|
+
if (attr.admitted === true) parts.push("· admitted");
|
|
122
|
+
else if (attr.admitted === false)
|
|
123
|
+
parts.push(`· denied(${attr.reason || "policy"})`);
|
|
124
|
+
return parts.join(" ");
|
|
125
|
+
}
|