chainlesschain 0.162.161 → 0.162.163
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 +1 -1
- package/src/assets/web-panel/assets/{AIOps-DMn528q-.js → AIOps-BQ0kgfA1.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-D9ZcDCJL.js → ActionButton-Dr1MDANe.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-A_K-8PX0.js → Analytics-CZ5vsaXW.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-D4pFW3X6.js → AppLayout-BIBga881.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-Dz46COAd.js → Artifacts-vpXHRU3u.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-cbMjbyg0.js → Audit-B8exuoub.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-Dtf88GAj.js → BackgroundAgents-923evRRN.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-C5KRxn52.js → Backup-sr_sswlQ.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-Dd2UK3BN.js → BaseInput-CnW-4XKT.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-lptKTFdG.js → Chat-FZCsTZIL.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-DDPd9qT0.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-_yspG6rG.js → Checkbox-DFrxB59H.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BGq_GAha.js → Codegen-_b6WEel8.js} +1 -1
- package/src/assets/web-panel/assets/{Col-CePor71g.js → Col-CkbnnZ-K.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CLOWhIT7.js → Community-BxM_82zW.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CivJt2vT.js → Compact-DYinP45R.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Btmo1GGE.js → Compliance-DyXp3sSP.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Ct35OlG4.js → Cowork-BEr2DhW7.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-CTc2GmhB.js → Cron-B-Vwo1hN.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-ZtiBETk1.js → Crosschain-DbLxKddd.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BNqMQQXw.js → DID-BsQ_LFw9.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-BmzZd-9V.js → Dashboard-lVlXQHu5.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CMVgvP2M.js → Dropdown-Beo_foO7.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CcqQFAfT.js → EmailListRenderer-DszHIXED.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CTqB1_4V.js → FamilyGuardDashboard-DcAwoh81.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-CiKOOzlb.js → Federation-CuJLa2eb.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Wk-OT0gr.js → FormItemContext-Cxd1lxhc.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DbEQT6ue.js → GenericCardRenderer-BBvq5Jsw.js} +1 -1
- package/src/assets/web-panel/assets/{Git-0ZvCPSD9.js → Git-WqPKetx6.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CmVuKkPb.js → Governance-CWYtY3ke.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CTWE3Jwg.js → Inference-BnX1iiNV.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-CBEIF00L.js → KnowledgeGraph-BJuM8GN5.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DLhxDwVG.js → Logs-DqLMNntm.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-BqYwEYou.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-BgsCBrnc.js → Marketplace-_vGfmhNl.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-ClBUGDS2.js → McpTools-BeJDOkDj.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-CnG_ZnYw.js → Memory-Da0EEZDO.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BUOAbR1Z.js → MobileBridge-DSuzcCCy.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-Dnt_9-WB.js → MobileProjects-D0RAk4e8.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-CLjt4kCD.js → Mtc-BE9YgdA0.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-f24U9Aes.js → MtcAudit-fC2BpO5S.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-GrmGQbSq.js → Multisig-D24Xxqw3.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DADaWn-n.js → NLProgramming-Bs3oGvS8.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-xtf5RX6C.js → Notes-BFjmAA-3.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-Cd27Rgtx.js → NotificationSettings-BX_9Bhv0.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-fXRxlDjl.js → OrderTableRenderer-wjkZwJXI.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-joCFlNB0.js → Organization-DBcdsVEP.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-B1HoURq0.js → Overflow-DDyaF9vC.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DiwsKbT7.js → P2P-BB78L4he.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-Xe-BsW6S.js → PdhVaultBrowser-DqTiZFum.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-C23qi8kl.js → Permissions-INbnWv4j.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-BS1syX2f.js → PersonalDataHub-BST-VmTq.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-DRYwwDyh.js → Pipeline-CE7_tS2k.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BQKaDHPH.js → Privacy-hK5L8dOB.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BC6F9xiG.js → ProjectInit-BvkkUoDn.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-ClFPc1Nd.js → ProjectSettings-u7_EUtkm.js} +2 -2
- package/src/assets/web-panel/assets/Projects-5Fmt_8Nw.js +1 -0
- package/src/assets/web-panel/assets/{Providers-CLWuOUNt.js → Providers-B9eanarR.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-DiqULTxb.js → QrScannerModal-C0dX5N2f.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-9A03P-E_.js → QuickAsk-CKTpG-PZ.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CJZAnbq_.js → Recommend-Px_w_XQM.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-C9A1R77h.js → RemoteSession-Di0fnYkM.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-BIZc6Rky.js → Reputation-vesnWjXt.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DfIPNtuG.js → Row-exbY6LkT.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-C5znFcQ1.js → RssFeed-B3O3x70t.js} +2 -2
- package/src/assets/web-panel/assets/{Search-DSs2z3OX.js → Search-uRA0DhNz.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CV6LcrLY.js → Security-CViqdH6K.js} +4 -4
- package/src/assets/web-panel/assets/{Services-cmhoX0Xj.js → Services-CkXS97WN.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-tCsg2po7.js → Skeleton-DxW5WYnE.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BK1KUDiy.js → Skills-CLxmGFse.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-yRgyeA82.js → Sla-Cr5fziXq.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-c1mo1mlp.js → SpeechSettings-H0JSb3cp.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-koIAfuT3.js → SyncSettings-DMYWMae7.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-DkkreAaB.js → Tasks-CbYyDdF4.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-fy5Ctf9M.js → Templates-Cpc8JjiK.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-Bw70Jf6S.js → Tenant-CRh77Jda.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-BKh6Rx-4.js → Terminal-4D8tNBO5.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-Dg54WP0c.js → TimelineRenderer-D41jzzbG.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BvvD5SxJ.js → Tokens-C_jWEy6b.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-B7Ug9x4e.js → Trigger-Dk1ZUhsy.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BjvuMORY.js → Trust-CLVy4l7m.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CZqhkx8p.js → UkeySign-C-FeqFDj.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-ClR50lhX.js → VideoEditing-CqVZzyMm.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DlfppYjx.js → Wallet-DX9BW9pu.js} +2 -2
- package/src/assets/web-panel/assets/{WebAuthn-BirgVr8B.js → WebAuthn-7hQDpWw_.js} +2 -2
- package/src/assets/web-panel/assets/{WorkflowEditor-B8DP_bax.js → WorkflowEditor-6XqUAO0j.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DA8AUZzL.js → chat-5R_vkaJG.js} +1 -1
- package/src/assets/web-panel/assets/{colors-C38pLGp1.js → colors-huUPZdC-.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-81rtr98o.js → compact-item-CfJlgisE.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-D34ixq8t.js → createContext-jO8heofr.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DeIrCfH-.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-D4bzqTrZ.js → hasIn-DY8Vxqx7.js} +1 -1
- package/src/assets/web-panel/assets/{index-E1OJRXs4.js → index-A48PtXw9.js} +1 -1
- package/src/assets/web-panel/assets/{index-D3zCCZRS.js → index-B300RPnT.js} +3 -3
- package/src/assets/web-panel/assets/{index-Cd3KuNK5.js → index-B74OXFxC.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dmo3dFHb.js → index-BAON7TzU.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ddk0e_Pi.js → index-BCqxyvqp.js} +1 -1
- package/src/assets/web-panel/assets/{index-9qoVpPt6.js → index-BHE8OIls.js} +1 -1
- package/src/assets/web-panel/assets/{index-DBFSo1NF.js → index-BQS-uPOK.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUQLXLQM.js → index-BRXOP09X.js} +1 -1
- package/src/assets/web-panel/assets/{index-D6GfvEQG.js → index-BYLBPpt8.js} +1 -1
- package/src/assets/web-panel/assets/{index-DFyWKOBD.js → index-BfCx4pFT.js} +1 -1
- package/src/assets/web-panel/assets/{index-BP-zkXK8.js → index-BliaT02i.js} +1 -1
- package/src/assets/web-panel/assets/{index-DMgnRwrK.js → index-BmqGerpC.js} +1 -1
- package/src/assets/web-panel/assets/{index-BIP62a6r.js → index-BsBtRWbA.js} +1 -1
- package/src/assets/web-panel/assets/{index-DkHqMnn3.js → index-BtEXSoPL.js} +1 -1
- package/src/assets/web-panel/assets/index-BxUKMRpP.js +1 -0
- package/src/assets/web-panel/assets/{index-B8mkLM4x.js → index-Bxax9vQ6.js} +1 -1
- package/src/assets/web-panel/assets/{index-ZRJRItm0.js → index-C2iVSFpy.js} +1 -1
- package/src/assets/web-panel/assets/{index-CP5alL4t.js → index-CA6sO5VD.js} +1 -1
- package/src/assets/web-panel/assets/{index-BS3M5H9V.js → index-CA90TZxG.js} +1 -1
- package/src/assets/web-panel/assets/{index-DvfCEJG-.js → index-CLhBh-qE.js} +1 -1
- package/src/assets/web-panel/assets/{index-tKiC8T-4.js → index-CRElRhyS.js} +1 -1
- package/src/assets/web-panel/assets/{index-B0QArPQt.js → index-Cbe9G0V4.js} +1 -1
- package/src/assets/web-panel/assets/{index-COe_cOLJ.js → index-ChvHubX3.js} +1 -1
- package/src/assets/web-panel/assets/{index-BvL_3J_n.js → index-Cis_Ip7H.js} +1 -1
- package/src/assets/web-panel/assets/{index-F76w1NiL.js → index-Cjxbu68P.js} +1 -1
- package/src/assets/web-panel/assets/{index-BlxEphl7.js → index-CzKmteNS.js} +1 -1
- package/src/assets/web-panel/assets/{index-CuS-UPoj.js → index-D0pet5Uy.js} +1 -1
- package/src/assets/web-panel/assets/{index-CbhE3j5j.js → index-D1VLuww0.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_2VYPr1.js → index-DXBcvUnf.js} +1 -1
- package/src/assets/web-panel/assets/{index-DakfFZ-k.js → index-Db1cLWir.js} +1 -1
- package/src/assets/web-panel/assets/{index-fI5sXMLg.js → index-DoI2sZm7.js} +1 -1
- package/src/assets/web-panel/assets/index-DscU9iPI.js +1 -0
- package/src/assets/web-panel/assets/{index-CoVgZw_b.js → index-KRHroryI.js} +1 -1
- package/src/assets/web-panel/assets/{index-BvD7b2IK.js → index-d1xkbtNz.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtGxuQrO.js → index-dKx57io7.js} +1 -1
- package/src/assets/web-panel/assets/{index-BqIhLAbA.js → index-j3O2EEJe.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXd2_LmH.js → index-p85YaaKw.js} +1 -1
- package/src/assets/web-panel/assets/{index-DFHa1HIg.js → index-v3pTaEQm.js} +1 -1
- package/src/assets/web-panel/assets/{index-D0Ih7Hwn.js → index-vXs3B8GJ.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-ByyvflIR.js → initDefaultProps-MCB7-raZ.js} +1 -1
- package/src/assets/web-panel/assets/{motion-B3FQm2By.js → motion-UWDF9swV.js} +1 -1
- package/src/assets/web-panel/assets/{move-BSfns6Ef.js → move-CsTmgXZU.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-CTG1Ieri.js → mtc-parser-fQy6KGLg.js} +1 -1
- package/src/assets/web-panel/assets/{omit-DObM_2j6.js → omit-woPnEjd1.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DOfelQ1t.js → pickAttrs-Bs2dZ1gf.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-CHZILds_.js → placementArrow-C7EVgPkg.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-REbucKSx.js → responsiveObserve-BH2kYCTN.js} +1 -1
- package/src/assets/web-panel/assets/{slide-C6EZzD7o.js → slide-BFBP2V_e.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BxkWS-g3.js → statusUtils-xdDxDYse.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-Chklyoq6.js → styleChecker-aAWgPPQf.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-CRVwKrrI.js → useFlexGapSupport-DkGSAWoO.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DV24ik_J.js → useFs-BrOih40N.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-C7vIo4Pg.js → usePersonalDataHub-eIYrgMqO.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-By-v5GlF.js → vnode-B8lutNSE.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-B1w9Am3r.js → zoom-CffNre7V.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agenda.js +254 -23
- package/src/commands/agent.js +105 -11
- package/src/commands/context.js +125 -14
- package/src/commands/hook.js +181 -7
- package/src/commands/plugin.js +239 -1
- package/src/commands/review.js +352 -2
- package/src/data/changelog.json +14 -4
- package/src/gateways/ws/remote-session-protocol.js +39 -0
- package/src/gateways/ws/session-protocol.js +5 -2
- package/src/lib/agent-schedule-store.js +289 -10
- package/src/lib/agents.js +19 -0
- package/src/lib/background-agent-supervisor.js +34 -7
- 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/docs-drift.js +166 -0
- package/src/lib/doctor-checkup.js +128 -0
- package/src/lib/hook-event-bus.cjs +0 -0
- package/src/lib/hook-event-log.cjs +193 -0
- package/src/lib/interaction-adapter.js +34 -4
- package/src/lib/json-schema-output.js +75 -12
- package/src/lib/json-schema-validate.js +831 -0
- package/src/lib/lsp/__tests__/diagnostics-scheduler.test.js +93 -0
- package/src/lib/lsp/diagnostics-scheduler.js +127 -0
- package/src/lib/operation-fingerprint.js +95 -0
- package/src/lib/plugin-runtime/capabilities.js +371 -0
- package/src/lib/plugin-runtime/capability-consent.js +175 -0
- package/src/lib/plugin-runtime/manifest.js +30 -0
- package/src/lib/plugin-runtime/policy.js +62 -0
- package/src/lib/plugin-runtime/scopes.js +20 -4
- package/src/lib/project-instructions.js +128 -0
- package/src/lib/remote-approval-bridge.js +16 -0
- package/src/lib/repl-goal.js +132 -0
- package/src/lib/repl-rewind.js +52 -0
- package/src/lib/review-pipeline.js +227 -0
- package/src/lib/runtime-checkup.js +318 -0
- package/src/lib/schedule-planner.js +17 -1
- package/src/lib/session-lifecycle.js +305 -0
- package/src/lib/settings-hook-events.cjs +52 -13
- 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 +296 -0
- package/src/lib/sub-agent-context.js +74 -5
- package/src/lib/subagent-contract.js +383 -0
- package/src/lib/subagent-inheritance.js +112 -0
- package/src/lib/telemetry-ids.js +190 -0
- package/src/lib/turn-binding-store.js +119 -0
- package/src/lib/turn-binding.js +397 -0
- package/src/repl/agent-repl.js +136 -0
- package/src/repl/permission-tier.js +28 -0
- package/src/runtime/agent-core.js +387 -14
- package/src/runtime/coding-agent-contract-shared.cjs +60 -1
- package/src/runtime/headless-runner.js +267 -0
- package/src/runtime/headless-stream.js +60 -1
- package/src/assets/web-panel/assets/ChatBubbleRenderer-C2KLtwHz.js +0 -1
- package/src/assets/web-panel/assets/MarkdownRenderer-DxywvhTU.js +0 -1
- package/src/assets/web-panel/assets/Projects-Dn-f5aYe.js +0 -1
- package/src/assets/web-panel/assets/devWarning-CHDIEOeN.js +0 -1
- package/src/assets/web-panel/assets/index-CY1NURWZ.js +0 -1
- package/src/assets/web-panel/assets/index-DokUYqpq.js +0 -1
|
@@ -23,9 +23,147 @@ 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";
|
|
26
29
|
|
|
27
30
|
export const SCHEDULE_KINDS = Object.freeze(["wakeup", "cron", "monitor"]);
|
|
28
31
|
|
|
32
|
+
/** A valid absolute-epoch-ms expiry, or null (never expires). */
|
|
33
|
+
function normalizeExpiresAt(v) {
|
|
34
|
+
const n = Number(v);
|
|
35
|
+
return Number.isFinite(n) && n > 0 ? n : null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A non-negative integer jitter window (ms), or 0. Stored PER entry so each
|
|
40
|
+
* task carries its own deterministic spread — the schedule-planner applies a
|
|
41
|
+
* stable per-id offset within `[0, jitterMs)` so tasks sharing a cron minute
|
|
42
|
+
* fan out instead of firing as a thundering herd. 0 (the default) = no jitter.
|
|
43
|
+
*/
|
|
44
|
+
function normalizeJitterMs(v) {
|
|
45
|
+
const n = Math.floor(Number(v));
|
|
46
|
+
return Number.isFinite(n) && n > 0 ? n : 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A per-task execution policy for wakeup/cron entries — how the spawned
|
|
51
|
+
* `cc agent` run should behave when this entry fires. Each field maps to a real
|
|
52
|
+
* `cc agent` flag consumed by `cc agenda run`, so a scheduled task can carry its
|
|
53
|
+
* OWN permission mode, worktree isolation and turn budget instead of inheriting
|
|
54
|
+
* whatever ambient environment `cc agenda run` happens to run in (the gap's
|
|
55
|
+
* "每个定时任务独立预算·权限模式·Worktree"). Returns `null` when nothing is set —
|
|
56
|
+
* the entry then omits `runPolicy` entirely and is byte-identical to before,
|
|
57
|
+
* so the default (unpolicied) task keeps spawning a plain `cc agent -p`.
|
|
58
|
+
*
|
|
59
|
+
* - permissionMode → `--permission-mode <mode>` (validated against the single
|
|
60
|
+
* source SUBAGENT_PERMISSION_MODES; invalid → dropped)
|
|
61
|
+
* - worktree → `--worktree` (run the task in a fresh git worktree)
|
|
62
|
+
* - maxTurns → `--max-turns <n>` (cap the agent loop — a real budget)
|
|
63
|
+
* - goalCondition → `--goal-condition <spec>` (re-drive the run to a
|
|
64
|
+
* completion condition; validated via parseGoalCondition,
|
|
65
|
+
* invalid spec → the whole goal set is dropped) with its
|
|
66
|
+
* own token/cost/time/outer-turn budget:
|
|
67
|
+
* goalMaxTokens → `--goal-max-tokens <n>`
|
|
68
|
+
* goalMaxCost → `--goal-max-cost <usd>`
|
|
69
|
+
* goalMaxTime → `--goal-max-time <ms>`
|
|
70
|
+
* maxOuterTurns → `--max-outer-turns <n>`
|
|
71
|
+
* (the goal budgets are meaningless without a valid condition, so they are
|
|
72
|
+
* only kept when the condition parses.)
|
|
73
|
+
*
|
|
74
|
+
* Monitors run a shell command / watch a file rather than spawning an agent, so
|
|
75
|
+
* a run policy does not apply to them.
|
|
76
|
+
*/
|
|
77
|
+
export function normalizeRunPolicy({
|
|
78
|
+
permissionMode = null,
|
|
79
|
+
worktree = false,
|
|
80
|
+
maxTurns = null,
|
|
81
|
+
goalCondition = null,
|
|
82
|
+
goalMaxTokens = null,
|
|
83
|
+
goalMaxCost = null,
|
|
84
|
+
goalMaxTime = null,
|
|
85
|
+
maxOuterTurns = null,
|
|
86
|
+
} = {}) {
|
|
87
|
+
const policy = {};
|
|
88
|
+
if (
|
|
89
|
+
typeof permissionMode === "string" &&
|
|
90
|
+
SUBAGENT_PERMISSION_MODES.includes(permissionMode)
|
|
91
|
+
) {
|
|
92
|
+
policy.permissionMode = permissionMode;
|
|
93
|
+
}
|
|
94
|
+
if (worktree === true) policy.worktree = true;
|
|
95
|
+
const turns = Math.floor(Number(maxTurns));
|
|
96
|
+
if (Number.isFinite(turns) && turns > 0) policy.maxTurns = turns;
|
|
97
|
+
|
|
98
|
+
// goal-condition + its budgets: run to a completion condition with a per-task
|
|
99
|
+
// token/cost/time/outer-turn budget. Validate the spec up front so a doomed
|
|
100
|
+
// condition never gets scheduled; an invalid spec drops the whole goal set.
|
|
101
|
+
if (typeof goalCondition === "string" && goalCondition.trim()) {
|
|
102
|
+
let validSpec = false;
|
|
103
|
+
try {
|
|
104
|
+
parseGoalCondition(goalCondition);
|
|
105
|
+
validSpec = true;
|
|
106
|
+
} catch {
|
|
107
|
+
// invalid spec → drop goalCondition and its budgets entirely
|
|
108
|
+
}
|
|
109
|
+
if (validSpec) {
|
|
110
|
+
policy.goalCondition = goalCondition.trim();
|
|
111
|
+
const gTokens = Math.floor(Number(goalMaxTokens));
|
|
112
|
+
if (Number.isFinite(gTokens) && gTokens > 0)
|
|
113
|
+
policy.goalMaxTokens = gTokens;
|
|
114
|
+
const gCost = Number(goalMaxCost);
|
|
115
|
+
if (Number.isFinite(gCost) && gCost > 0) policy.goalMaxCost = gCost;
|
|
116
|
+
const gTime = Math.floor(Number(goalMaxTime));
|
|
117
|
+
if (Number.isFinite(gTime) && gTime > 0) policy.goalMaxTime = gTime;
|
|
118
|
+
const outer = Math.floor(Number(maxOuterTurns));
|
|
119
|
+
if (Number.isFinite(outer) && outer > 0) policy.maxOuterTurns = outer;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return Object.keys(policy).length > 0 ? policy : null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Resolve an entry's expiry: an explicit absolute `expiresAt` (epoch-ms) wins;
|
|
127
|
+
* otherwise a positive relative `expiresInMs` is anchored to the store's own
|
|
128
|
+
* `now` (symmetric with `delayMs` on a wakeup, and deterministic under an
|
|
129
|
+
* injected clock). No expiry → null.
|
|
130
|
+
*/
|
|
131
|
+
function resolveExpiresAt(expiresAt, expiresInMs, now) {
|
|
132
|
+
if (expiresAt != null) return normalizeExpiresAt(expiresAt);
|
|
133
|
+
const rel = Math.floor(Number(expiresInMs));
|
|
134
|
+
if (Number.isFinite(rel) && rel > 0) return normalizeExpiresAt(now + rel);
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Is an entry currently in a schedulable (non-terminal) state? */
|
|
139
|
+
function isSchedulableStatus(entry) {
|
|
140
|
+
return entry.kind === "wakeup"
|
|
141
|
+
? entry.status === "pending"
|
|
142
|
+
: entry.status === "active";
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Statuses an entry can never leave — safe to prune. */
|
|
146
|
+
export const TERMINAL_STATUSES = Object.freeze([
|
|
147
|
+
"fired",
|
|
148
|
+
"matched",
|
|
149
|
+
"exhausted",
|
|
150
|
+
"expired",
|
|
151
|
+
]);
|
|
152
|
+
const TERMINAL_SET = new Set(TERMINAL_STATUSES);
|
|
153
|
+
|
|
154
|
+
/** Best available "this entry reached a terminal state at" timestamp. */
|
|
155
|
+
function terminalAt(entry) {
|
|
156
|
+
return (
|
|
157
|
+
entry.firedAt ??
|
|
158
|
+
entry.matchedAt ??
|
|
159
|
+
entry.expiredAt ??
|
|
160
|
+
entry.lastRunAt ??
|
|
161
|
+
entry.lastCheckAt ??
|
|
162
|
+
entry.createdAt ??
|
|
163
|
+
0
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
29
167
|
export function agentScheduleDir(homedir = os.homedir()) {
|
|
30
168
|
return path.join(homedir, ".chainlesschain", "agent-schedule");
|
|
31
169
|
}
|
|
@@ -154,11 +292,23 @@ export class AgentScheduleStore {
|
|
|
154
292
|
|
|
155
293
|
// ── create ──────────────────────────────────────────────────────────────
|
|
156
294
|
|
|
157
|
-
scheduleWakeup(
|
|
295
|
+
scheduleWakeup(options = {}) {
|
|
296
|
+
const {
|
|
297
|
+
prompt,
|
|
298
|
+
delayMs = 0,
|
|
299
|
+
dueAt = null,
|
|
300
|
+
label = null,
|
|
301
|
+
expiresAt = null,
|
|
302
|
+
expiresInMs = null,
|
|
303
|
+
jitterMs = 0,
|
|
304
|
+
} = options;
|
|
158
305
|
if (!prompt || typeof prompt !== "string") {
|
|
159
306
|
throw new Error("wakeup requires a prompt");
|
|
160
307
|
}
|
|
161
308
|
const now = this._now();
|
|
309
|
+
// The full options object is passed to normalizeRunPolicy, which reads only
|
|
310
|
+
// the policy fields it knows — new policy knobs need no signature change here.
|
|
311
|
+
const runPolicy = normalizeRunPolicy(options);
|
|
162
312
|
const entry = {
|
|
163
313
|
id: randomUUID(),
|
|
164
314
|
kind: "wakeup",
|
|
@@ -166,6 +316,9 @@ export class AgentScheduleStore {
|
|
|
166
316
|
label: label || null,
|
|
167
317
|
dueAt:
|
|
168
318
|
dueAt != null ? Number(dueAt) : now + Math.max(0, Number(delayMs) || 0),
|
|
319
|
+
expiresAt: resolveExpiresAt(expiresAt, expiresInMs, now),
|
|
320
|
+
jitterMs: normalizeJitterMs(jitterMs),
|
|
321
|
+
...(runPolicy ? { runPolicy } : {}),
|
|
169
322
|
createdAt: now,
|
|
170
323
|
status: "pending",
|
|
171
324
|
};
|
|
@@ -175,7 +328,15 @@ export class AgentScheduleStore {
|
|
|
175
328
|
return entry;
|
|
176
329
|
}
|
|
177
330
|
|
|
178
|
-
createCron(
|
|
331
|
+
createCron(options = {}) {
|
|
332
|
+
const {
|
|
333
|
+
prompt,
|
|
334
|
+
cron,
|
|
335
|
+
label = null,
|
|
336
|
+
expiresAt = null,
|
|
337
|
+
expiresInMs = null,
|
|
338
|
+
jitterMs = 0,
|
|
339
|
+
} = options;
|
|
179
340
|
if (!prompt || typeof prompt !== "string") {
|
|
180
341
|
throw new Error("cron requires a prompt");
|
|
181
342
|
}
|
|
@@ -184,6 +345,8 @@ export class AgentScheduleStore {
|
|
|
184
345
|
if (nextAt == null) {
|
|
185
346
|
throw new Error(`cron "${cron}" has no upcoming match`);
|
|
186
347
|
}
|
|
348
|
+
// Full options → normalizeRunPolicy (reads only known policy fields).
|
|
349
|
+
const runPolicy = normalizeRunPolicy(options);
|
|
187
350
|
const entry = {
|
|
188
351
|
id: randomUUID(),
|
|
189
352
|
kind: "cron",
|
|
@@ -191,6 +354,9 @@ export class AgentScheduleStore {
|
|
|
191
354
|
cron,
|
|
192
355
|
label: label || null,
|
|
193
356
|
nextAt,
|
|
357
|
+
expiresAt: resolveExpiresAt(expiresAt, expiresInMs, now),
|
|
358
|
+
jitterMs: normalizeJitterMs(jitterMs),
|
|
359
|
+
...(runPolicy ? { runPolicy } : {}),
|
|
194
360
|
createdAt: now,
|
|
195
361
|
lastRunAt: null,
|
|
196
362
|
runs: 0,
|
|
@@ -204,14 +370,56 @@ export class AgentScheduleStore {
|
|
|
204
370
|
|
|
205
371
|
createMonitor({
|
|
206
372
|
command,
|
|
373
|
+
watchFile = null,
|
|
374
|
+
watchUrl = null,
|
|
375
|
+
watchChange = false,
|
|
207
376
|
intervalMs,
|
|
208
377
|
stopWhen = null,
|
|
209
378
|
notify = null,
|
|
210
379
|
maxChecks = null,
|
|
211
380
|
label = null,
|
|
381
|
+
expiresAt = null,
|
|
382
|
+
expiresInMs = null,
|
|
383
|
+
jitterMs = 0,
|
|
212
384
|
} = {}) {
|
|
213
|
-
|
|
214
|
-
|
|
385
|
+
// A monitor watches exactly one source:
|
|
386
|
+
// - a shell `command` → match its stdout/stderr
|
|
387
|
+
// - a `watchFile` → match its content, or fire when the file appears
|
|
388
|
+
// - a `watchUrl` → GET it and match the response body, or fire on 2xx
|
|
389
|
+
const hasCommand = typeof command === "string" && command.length > 0;
|
|
390
|
+
const hasFile = typeof watchFile === "string" && watchFile.length > 0;
|
|
391
|
+
const hasUrl = typeof watchUrl === "string" && watchUrl.length > 0;
|
|
392
|
+
const sourceCount = Number(hasCommand) + Number(hasFile) + Number(hasUrl);
|
|
393
|
+
if (sourceCount !== 1) {
|
|
394
|
+
throw new Error(
|
|
395
|
+
"monitor requires exactly one of `command`, `watchFile`, or `watchUrl`",
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
if (hasUrl) {
|
|
399
|
+
// Only http(s) endpoints — reject file:// and other schemes up-front so a
|
|
400
|
+
// bad URL errors at creation, not silently at the first poll.
|
|
401
|
+
let parsed;
|
|
402
|
+
try {
|
|
403
|
+
parsed = new URL(watchUrl);
|
|
404
|
+
} catch {
|
|
405
|
+
throw new Error(`monitor watchUrl is not a valid URL: ${watchUrl}`);
|
|
406
|
+
}
|
|
407
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
408
|
+
throw new Error(
|
|
409
|
+
`monitor watchUrl must be http(s), got: ${parsed.protocol}`,
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
if (watchChange) {
|
|
414
|
+
// `watchChange` is a file-only mode — fire when the file's mtime advances.
|
|
415
|
+
// It carries its own signal, so it cannot be combined with a stopWhen
|
|
416
|
+
// content pattern (that would be an ambiguous double condition).
|
|
417
|
+
if (!hasFile) {
|
|
418
|
+
throw new Error("monitor watchChange requires a watchFile");
|
|
419
|
+
}
|
|
420
|
+
if (stopWhen != null) {
|
|
421
|
+
throw new Error("monitor watchChange cannot be combined with stopWhen");
|
|
422
|
+
}
|
|
215
423
|
}
|
|
216
424
|
const interval = Math.max(1000, Number(intervalMs) || 60000);
|
|
217
425
|
if (stopWhen != null) {
|
|
@@ -221,16 +429,24 @@ export class AgentScheduleStore {
|
|
|
221
429
|
new RegExp(stopWhen);
|
|
222
430
|
}
|
|
223
431
|
const now = this._now();
|
|
432
|
+
const source = hasFile ? "file" : hasUrl ? "http" : "command";
|
|
224
433
|
const entry = {
|
|
225
434
|
id: randomUUID(),
|
|
226
435
|
kind: "monitor",
|
|
227
|
-
|
|
436
|
+
source,
|
|
437
|
+
command: hasCommand ? command : null,
|
|
438
|
+
watchFile: hasFile ? watchFile : null,
|
|
439
|
+
watchUrl: hasUrl ? watchUrl : null,
|
|
440
|
+
watchChange: hasFile ? Boolean(watchChange) : false,
|
|
441
|
+
lastMtimeMs: null,
|
|
228
442
|
intervalMs: interval,
|
|
229
443
|
stopWhen: stopWhen || null,
|
|
230
444
|
notify: notify || null,
|
|
231
445
|
maxChecks: maxChecks != null ? Number(maxChecks) : null,
|
|
232
446
|
label: label || null,
|
|
233
447
|
nextAt: now + interval,
|
|
448
|
+
expiresAt: resolveExpiresAt(expiresAt, expiresInMs, now),
|
|
449
|
+
jitterMs: normalizeJitterMs(jitterMs),
|
|
234
450
|
createdAt: now,
|
|
235
451
|
checks: 0,
|
|
236
452
|
status: "active",
|
|
@@ -254,13 +470,45 @@ export class AgentScheduleStore {
|
|
|
254
470
|
due(kind = null, atMs = null) {
|
|
255
471
|
const now = atMs != null ? atMs : this._now();
|
|
256
472
|
return this.list(kind).filter((entry) => {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
473
|
+
// An entry past its expiry never fires — even if it is not yet retired
|
|
474
|
+
// (defense-in-depth: retireExpired need not have run first).
|
|
475
|
+
if (isEntryExpired(entry, now)) return false;
|
|
476
|
+
if (!isSchedulableStatus(entry)) return false;
|
|
477
|
+
// The effective fire time applies the entry's OWN deterministic jitter
|
|
478
|
+
// (`entry.jitterMs`, default 0 → base fire time, byte-identical to the
|
|
479
|
+
// prior `dueAt`/`nextAt` comparison). A jittered entry fires slightly
|
|
480
|
+
// later, spreading tasks that share a cron minute.
|
|
481
|
+
const fireAt = effectiveFireAt(entry);
|
|
482
|
+
return fireAt != null && fireAt <= now;
|
|
261
483
|
});
|
|
262
484
|
}
|
|
263
485
|
|
|
486
|
+
/**
|
|
487
|
+
* Retire every schedulable entry whose `expiresAt` has passed (status →
|
|
488
|
+
* "expired", stamping `expiredAt`), across all kinds, so it never fires again.
|
|
489
|
+
* A daemon / `cc agenda run` calls this before firing due entries — mirroring
|
|
490
|
+
* the schedule-planner's "retire expired BEFORE due" semantics. Returns the
|
|
491
|
+
* list of retired entries.
|
|
492
|
+
*/
|
|
493
|
+
retireExpired(atMs = null) {
|
|
494
|
+
const now = atMs != null ? atMs : this._now();
|
|
495
|
+
const retired = [];
|
|
496
|
+
for (const kind of SCHEDULE_KINDS) {
|
|
497
|
+
const entries = this._readAll(kind);
|
|
498
|
+
let changed = false;
|
|
499
|
+
for (const entry of entries) {
|
|
500
|
+
if (isSchedulableStatus(entry) && isEntryExpired(entry, now)) {
|
|
501
|
+
entry.status = "expired";
|
|
502
|
+
entry.expiredAt = now;
|
|
503
|
+
retired.push(entry);
|
|
504
|
+
changed = true;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
if (changed) this._writeAll(kind, entries);
|
|
508
|
+
}
|
|
509
|
+
return retired;
|
|
510
|
+
}
|
|
511
|
+
|
|
264
512
|
cancel(id) {
|
|
265
513
|
for (const kind of SCHEDULE_KINDS) {
|
|
266
514
|
const entries = this._readAll(kind);
|
|
@@ -274,6 +522,28 @@ export class AgentScheduleStore {
|
|
|
274
522
|
return null;
|
|
275
523
|
}
|
|
276
524
|
|
|
525
|
+
/**
|
|
526
|
+
* Remove terminal entries (fired / matched / exhausted / expired) whose
|
|
527
|
+
* terminal timestamp is at or before `before` (default: all terminal
|
|
528
|
+
* entries), across all kinds. Keeps the append-only JSONL store from growing
|
|
529
|
+
* without bound once entries finish. Returns the removed entries; only a kind
|
|
530
|
+
* that actually changed is rewritten.
|
|
531
|
+
*/
|
|
532
|
+
pruneTerminal({ before = Infinity } = {}) {
|
|
533
|
+
const removed = [];
|
|
534
|
+
for (const kind of SCHEDULE_KINDS) {
|
|
535
|
+
const entries = this._readAll(kind);
|
|
536
|
+
const keep = entries.filter((entry) => {
|
|
537
|
+
const prunable =
|
|
538
|
+
TERMINAL_SET.has(entry.status) && terminalAt(entry) <= before;
|
|
539
|
+
if (prunable) removed.push(entry);
|
|
540
|
+
return !prunable;
|
|
541
|
+
});
|
|
542
|
+
if (keep.length !== entries.length) this._writeAll(kind, keep);
|
|
543
|
+
}
|
|
544
|
+
return removed;
|
|
545
|
+
}
|
|
546
|
+
|
|
277
547
|
// ── lifecycle mutations (used by `cc agenda run`) ──────────────────────────
|
|
278
548
|
|
|
279
549
|
/** Mark a one-shot wakeup fired (keeps history; due() skips it). */
|
|
@@ -303,11 +573,20 @@ export class AgentScheduleStore {
|
|
|
303
573
|
* Record a monitor check. `matched` ends the monitor (done); otherwise it is
|
|
304
574
|
* re-armed for the next interval, or exhausted once maxChecks is hit.
|
|
305
575
|
*/
|
|
306
|
-
recordMonitorCheck(
|
|
576
|
+
recordMonitorCheck(
|
|
577
|
+
id,
|
|
578
|
+
{ matched = false, atMs = null, mtimeMs = null } = {},
|
|
579
|
+
) {
|
|
307
580
|
const now = atMs != null ? atMs : this._now();
|
|
308
581
|
return this._mutate("monitor", id, (entry) => {
|
|
309
582
|
entry.checks = (entry.checks || 0) + 1;
|
|
310
583
|
entry.lastCheckAt = now;
|
|
584
|
+
// Record the mtime baseline the first time we see the file (for a
|
|
585
|
+
// watchChange monitor); leave it fixed thereafter so "changed" always
|
|
586
|
+
// means "changed since we started watching".
|
|
587
|
+
if (mtimeMs != null && entry.lastMtimeMs == null) {
|
|
588
|
+
entry.lastMtimeMs = mtimeMs;
|
|
589
|
+
}
|
|
311
590
|
if (matched) {
|
|
312
591
|
entry.status = "matched";
|
|
313
592
|
entry.matchedAt = now;
|
package/src/lib/agents.js
CHANGED
|
@@ -15,6 +15,21 @@
|
|
|
15
15
|
* model model override for runs of this agent
|
|
16
16
|
* maxTurns per-run iteration cap for this agent (positive integer)
|
|
17
17
|
* isolation "worktree" → run in a fresh git worktree (gap 2026-07-11)
|
|
18
|
+
* permission-mode child permission mode (tighten-only vs parent)
|
|
19
|
+
* skills capability allow-list — skills the child may use
|
|
20
|
+
* mcp-servers capability allow-list — MCP servers the child may use
|
|
21
|
+
* hooks capability allow-list — hooks the child may run
|
|
22
|
+
* memory whether the child may read permanent memory
|
|
23
|
+
* effort reasoning effort (low|medium|high|xhigh)
|
|
24
|
+
* background run detached
|
|
25
|
+
* context "fresh" (default, isolated) | "fork" (inherit parent)
|
|
26
|
+
* max-depth/max-children ceilings this agent imposes on its own descendants
|
|
27
|
+
* budget { tokens, cost, time } caps
|
|
28
|
+
*
|
|
29
|
+
* The extended P1 contract (permission/skills/mcp/hooks/memory/effort/
|
|
30
|
+
* background/context/depth/children/budget) is normalized into a `contract`
|
|
31
|
+
* field via subagent-contract.js; the top-level tools/maxTurns/isolation are
|
|
32
|
+
* kept as-is for the existing spawn path.
|
|
18
33
|
*
|
|
19
34
|
* Project scope shadows personal on a name clash. Discovery + parse are pure
|
|
20
35
|
* (inject fs/path/home) so the whole thing is unit-testable.
|
|
@@ -26,6 +41,7 @@ import { homedir } from "node:os";
|
|
|
26
41
|
import yaml from "js-yaml";
|
|
27
42
|
import { projectRootBase } from "./project-root.cjs";
|
|
28
43
|
import { discoverPluginAgentLayers } from "./plugin-runtime/agents.js";
|
|
44
|
+
import { normalizeSubagentContract } from "./subagent-contract.js";
|
|
29
45
|
|
|
30
46
|
const _deps = { fs: fsDefault, path: pathDefault };
|
|
31
47
|
|
|
@@ -147,6 +163,9 @@ export function parseAgentFile(file, scope, opts = {}) {
|
|
|
147
163
|
maxTurns:
|
|
148
164
|
Number.isFinite(maxTurns) && maxTurns > 0 ? Math.floor(maxTurns) : null,
|
|
149
165
|
isolation: data.isolation === "worktree" ? "worktree" : null,
|
|
166
|
+
// Extended P1 subagent contract (permission/skills/mcp/hooks/memory/effort/
|
|
167
|
+
// background/context/depth/children/budget), normalized + validated.
|
|
168
|
+
contract: normalizeSubagentContract(data),
|
|
150
169
|
systemPrompt: body || "",
|
|
151
170
|
};
|
|
152
171
|
}
|
|
@@ -15,6 +15,7 @@ import { randomBytes } from "node:crypto";
|
|
|
15
15
|
import { join } from "node:path";
|
|
16
16
|
import { fileURLToPath } from "node:url";
|
|
17
17
|
import { getHomeDir } from "./paths.js";
|
|
18
|
+
import { deriveSessionState } from "./session-lifecycle.js";
|
|
18
19
|
|
|
19
20
|
export const DEFAULT_HEARTBEAT_INTERVAL_MS = 5000;
|
|
20
21
|
export const DEFAULT_HEARTBEAT_STALE_MS = 120000;
|
|
@@ -347,6 +348,26 @@ export function normalizeBackgroundAgentTitle(title) {
|
|
|
347
348
|
return value.slice(0, 160);
|
|
348
349
|
}
|
|
349
350
|
|
|
351
|
+
/**
|
|
352
|
+
* The canonical unified lifecycle state (session-lifecycle.js) for a session —
|
|
353
|
+
* folds the supervisor `status` + worker `phase` + `pendingApprovals` into one
|
|
354
|
+
* of the 10 canonical states so every consumer (dashboard, `cc daemon list
|
|
355
|
+
* --json`, an IDE bridge) reasons about a single vocabulary. Pure; call it at a
|
|
356
|
+
* DISPLAY boundary only. It is deliberately NOT baked into
|
|
357
|
+
* effectiveBackgroundAgentState because that function's output is spread back
|
|
358
|
+
* into writeBackgroundAgentState by the rename/pin/stop read-modify-write
|
|
359
|
+
* paths — a derived field there would leak into the on-disk schema.
|
|
360
|
+
*/
|
|
361
|
+
export function sessionLifecycleState(state) {
|
|
362
|
+
return deriveSessionState(state);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** A display copy of `state` with the derived `lifecycleState` attached. */
|
|
366
|
+
function withLifecycleState(state) {
|
|
367
|
+
if (!state) return state;
|
|
368
|
+
return { ...state, lifecycleState: deriveSessionState(state) };
|
|
369
|
+
}
|
|
370
|
+
|
|
350
371
|
export function effectiveBackgroundAgentState(state, options = {}) {
|
|
351
372
|
if (!state) return null;
|
|
352
373
|
if (state.status !== "running") return state;
|
|
@@ -403,13 +424,19 @@ export function effectiveBackgroundAgentState(state, options = {}) {
|
|
|
403
424
|
}
|
|
404
425
|
|
|
405
426
|
export function listBackgroundAgents(options = {}) {
|
|
406
|
-
return
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
427
|
+
return (
|
|
428
|
+
readdirSync(backgroundAgentsDir())
|
|
429
|
+
.filter((name) => name.endsWith(".json") && !name.includes(".job."))
|
|
430
|
+
.map((name) => readBackgroundAgentState(name.slice(0, -5)))
|
|
431
|
+
.filter(Boolean)
|
|
432
|
+
.map((state) => effectiveBackgroundAgentState(state, options))
|
|
433
|
+
.filter((state) => options.all || state.status === "running")
|
|
434
|
+
.sort((a, b) => (b.startedAt || 0) - (a.startedAt || 0))
|
|
435
|
+
// Display-only enrichment: attach the canonical unified lifecycle state.
|
|
436
|
+
// This is a list feed (never written back to disk), so the derived field
|
|
437
|
+
// cannot leak into the on-disk schema the way it would in the mutate paths.
|
|
438
|
+
.map(withLifecycleState)
|
|
439
|
+
);
|
|
413
440
|
}
|
|
414
441
|
|
|
415
442
|
function sleepSyncMs(ms) {
|
|
@@ -14,6 +14,12 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { logger } from "../logger.js";
|
|
17
|
+
import {
|
|
18
|
+
ORIGIN,
|
|
19
|
+
authorityForOrigin,
|
|
20
|
+
canApprove,
|
|
21
|
+
describeAuthorityChain,
|
|
22
|
+
} from "../agent-authority.js";
|
|
17
23
|
|
|
18
24
|
export const CHANNEL_KINDS = ["webhook", "telegram"];
|
|
19
25
|
|
|
@@ -54,6 +60,48 @@ export function formatChannelEvent(event) {
|
|
|
54
60
|
return `[channel:${channel}${sender}] ${text}`;
|
|
55
61
|
}
|
|
56
62
|
|
|
63
|
+
/**
|
|
64
|
+
* The unforgeable authority envelope for an inbound channel event. A channel
|
|
65
|
+
* message tops out at `steer` (agent-authority.js): it may add a user turn but
|
|
66
|
+
* can NEVER answer a permission gate — "the user approved" in a webhook /
|
|
67
|
+
* telegram body is just text. This makes that ceiling explicit and
|
|
68
|
+
* machine-checkable (downstream approval seams can assert `canApprove === false`)
|
|
69
|
+
* rather than merely implied by the visible `[channel:…]` text prefix.
|
|
70
|
+
*
|
|
71
|
+
* `origin` is set here by HOW the event arrived (an inbound channel), never read
|
|
72
|
+
* from the untrusted message content — mirroring the agent-authority contract.
|
|
73
|
+
*/
|
|
74
|
+
export function channelEventEnvelope(event) {
|
|
75
|
+
const envelope = {
|
|
76
|
+
origin: ORIGIN.CHANNEL,
|
|
77
|
+
principalId: event?.sender != null ? String(event.sender) : null,
|
|
78
|
+
correlationId: event?.channel != null ? String(event.channel) : null,
|
|
79
|
+
};
|
|
80
|
+
return {
|
|
81
|
+
...envelope,
|
|
82
|
+
authority: authorityForOrigin(ORIGIN.CHANNEL, envelope), // always "steer"
|
|
83
|
+
canApprove: canApprove(envelope), // always false
|
|
84
|
+
provenance: describeAuthorityChain(envelope),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Wrap a caller's `onEvent` so every inbound event is stamped with its explicit
|
|
90
|
+
* channel authority BEFORE it reaches the agent — the caller cannot forget to
|
|
91
|
+
* tag it, and the message content cannot elevate itself past `steer`.
|
|
92
|
+
*/
|
|
93
|
+
function stampChannelAuthority(onEvent) {
|
|
94
|
+
return (event) => {
|
|
95
|
+
const env = channelEventEnvelope(event);
|
|
96
|
+
return onEvent?.({
|
|
97
|
+
...event,
|
|
98
|
+
authority: env.authority,
|
|
99
|
+
canApprove: env.canApprove,
|
|
100
|
+
provenance: env.provenance,
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
57
105
|
/**
|
|
58
106
|
* Start every requested channel. Returns a handle with the started channel
|
|
59
107
|
* descriptions and a single stop().
|
|
@@ -72,6 +120,10 @@ export async function startChannels(specsInput, opts = {}) {
|
|
|
72
120
|
const config = opts.config || {};
|
|
73
121
|
const log = opts.log || ((msg) => logger.info(msg));
|
|
74
122
|
const deps = opts.deps || {};
|
|
123
|
+
// Every inbound event is stamped with its channel authority (`steer`, never
|
|
124
|
+
// `approve`) before the caller's handler sees it — so a channel message can
|
|
125
|
+
// never be mistaken for a user approval no matter what it contains.
|
|
126
|
+
const onEvent = stampChannelAuthority(opts.onEvent);
|
|
75
127
|
|
|
76
128
|
const started = [];
|
|
77
129
|
const stopAll = () => {
|
|
@@ -95,7 +147,7 @@ export async function startChannels(specsInput, opts = {}) {
|
|
|
95
147
|
)({
|
|
96
148
|
...cfg,
|
|
97
149
|
port: spec.arg ? Number(spec.arg) : cfg.port,
|
|
98
|
-
onEvent
|
|
150
|
+
onEvent,
|
|
99
151
|
log,
|
|
100
152
|
});
|
|
101
153
|
started.push({ kind: "webhook", describe: handle.describe, ...handle });
|
|
@@ -107,7 +159,7 @@ export async function startChannels(specsInput, opts = {}) {
|
|
|
107
159
|
deps.telegram?.startTelegramChannel || startTelegramChannel
|
|
108
160
|
)({
|
|
109
161
|
...cfg,
|
|
110
|
-
onEvent
|
|
162
|
+
onEvent,
|
|
111
163
|
log,
|
|
112
164
|
});
|
|
113
165
|
started.push({
|
|
@@ -46,11 +46,17 @@ export class CLIContextEngineering {
|
|
|
46
46
|
* @param {string} [options.scope.taskId] - Task/sub-agent ID
|
|
47
47
|
* @param {string} [options.scope.role] - Sub-agent role
|
|
48
48
|
* @param {string} [options.scope.parentObjective] - Parent task objective
|
|
49
|
+
* @param {boolean} [options.memoryEnabled=true] - When false, hierarchical
|
|
50
|
+
* memory recall is suppressed even if a `db` is present. The subagent
|
|
51
|
+
* contract's `memory` boolean drives this for spawned children so a child
|
|
52
|
+
* denied memory physically cannot recall it (defense-in-depth beyond the
|
|
53
|
+
* spawn simply withholding the db).
|
|
49
54
|
*/
|
|
50
|
-
constructor({ db, permanentMemory, scope } = {}) {
|
|
55
|
+
constructor({ db, permanentMemory, scope, memoryEnabled = true } = {}) {
|
|
51
56
|
this.db = db || null;
|
|
52
57
|
this.permanentMemory = permanentMemory || null;
|
|
53
58
|
this.scope = scope || null;
|
|
59
|
+
this.memoryEnabled = memoryEnabled !== false;
|
|
54
60
|
this.errorHistory = [];
|
|
55
61
|
this.taskContext = null;
|
|
56
62
|
this._bm25 = null;
|
|
@@ -121,7 +127,8 @@ export class CLIContextEngineering {
|
|
|
121
127
|
}
|
|
122
128
|
|
|
123
129
|
// 3. Memory injection (scoped: higher threshold, namespace-aware)
|
|
124
|
-
|
|
130
|
+
// Suppressed when memoryEnabled is false (subagent contract memory:false).
|
|
131
|
+
if (this.memoryEnabled && this.db && userQuery) {
|
|
125
132
|
try {
|
|
126
133
|
const memoryQuery = this.scope
|
|
127
134
|
? `[${this.scope.role}] ${userQuery}`
|