chainlesschain 0.162.163 → 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-BQ0kgfA1.js → AIOps-BqRkaHGb.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-Dr1MDANe.js → ActionButton-DWO7OZLC.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CZ5vsaXW.js → Analytics-C5oaVezM.js} +2 -2
- package/src/assets/web-panel/assets/{AppLayout-BIBga881.js → AppLayout-CsUESDoZ.js} +3 -3
- package/src/assets/web-panel/assets/{Artifacts-vpXHRU3u.js → Artifacts-Dragqu5k.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-B8exuoub.js → Audit-CQsaHctY.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-923evRRN.js → BackgroundAgents-DdG1jNSM.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-sr_sswlQ.js → Backup-DN6sjK4z.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CnW-4XKT.js → BaseInput-Cxm_ZTCl.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-FZCsTZIL.js → Chat-DVXGmA56.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-DDPd9qT0.js → ChatBubbleRenderer-BdaCL_iE.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-DFrxB59H.js → Checkbox-DEc4vD_A.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-_b6WEel8.js → Codegen-CKiTcv36.js} +1 -1
- package/src/assets/web-panel/assets/{Col-CkbnnZ-K.js → Col-Br9UUNN0.js} +1 -1
- package/src/assets/web-panel/assets/{Community-BxM_82zW.js → Community-NeUH_70T.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DYinP45R.js → Compact-BqR-z2Df.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DyXp3sSP.js → Compliance-DcWB_SgZ.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BEr2DhW7.js → Cowork-BfhgcudF.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-B-Vwo1hN.js → Cron-LaU29LgD.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DbLxKddd.js → Crosschain-B2ZB9uwW.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BsQ_LFw9.js → DID-BuHl38A2.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-lVlXQHu5.js → Dashboard-m627yqvZ.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-Beo_foO7.js → Dropdown-C7bxF8F5.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DszHIXED.js → EmailListRenderer-DPLPQzLp.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DcAwoh81.js → FamilyGuardDashboard-cOiH6ike.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-CuJLa2eb.js → Federation-Bvkzg88z.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Cxd1lxhc.js → FormItemContext-BTsiA7h_.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BBvq5Jsw.js → GenericCardRenderer-BGnVE5qu.js} +1 -1
- package/src/assets/web-panel/assets/{Git-WqPKetx6.js → Git-DS_j6r25.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CWYtY3ke.js → Governance-CKRG3Rsl.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BnX1iiNV.js → Inference-DW8INoNQ.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-BJuM8GN5.js → KnowledgeGraph-BhEYeFhC.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DqLMNntm.js → Logs-wqi0uxuR.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-BqYwEYou.js → MarkdownRenderer-DS8dE8ty.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-_vGfmhNl.js → Marketplace-CMlTuPLT.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BeJDOkDj.js → McpTools-CSx4m5JJ.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-Da0EEZDO.js → Memory-vnQCUy-_.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DSuzcCCy.js → MobileBridge-BV2NA742.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-Bw0k8e1B.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-BE9YgdA0.js → Mtc-CzK_FPB1.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-fC2BpO5S.js → MtcAudit-Cb7G9oL3.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-D24Xxqw3.js → Multisig-qJxlUyzr.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-Bs3oGvS8.js → NLProgramming-DqaBJ7Te.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BFjmAA-3.js → Notes-CwhTJkRi.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-BX_9Bhv0.js → NotificationSettings-YDs4Fal4.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-wjkZwJXI.js → OrderTableRenderer-CvEALpq7.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-DBcdsVEP.js → Organization-CEmQm9n8.js} +2 -2
- package/src/assets/web-panel/assets/{Overflow-DDyaF9vC.js → Overflow-zby_P_LA.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BB78L4he.js → P2P-DsmDNFNT.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DqTiZFum.js → PdhVaultBrowser-FWwFlZGF.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-INbnWv4j.js → Permissions-DuzQ6me5.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BST-VmTq.js → PersonalDataHub-DZbeN613.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-CE7_tS2k.js → Pipeline-CoNSVAUS.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-hK5L8dOB.js → Privacy-OYJEAaMC.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BvkkUoDn.js → ProjectInit-CWIapV--.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-u7_EUtkm.js → ProjectSettings-CX8xMxZn.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-5Fmt_8Nw.js → Projects-BLCN8YyY.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-B9eanarR.js → Providers-BV_sxp75.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-C0dX5N2f.js → QrScannerModal-BTnRrHRp.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CKTpG-PZ.js → QuickAsk-D1KZg018.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Px_w_XQM.js → Recommend-COm5cuNg.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-Di0fnYkM.js → RemoteSession-0saWEkYI.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-vesnWjXt.js → Reputation-Dr7iKLS2.js} +1 -1
- package/src/assets/web-panel/assets/{Row-exbY6LkT.js → Row-C1vpPBcr.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-B3O3x70t.js → RssFeed-CxlWrtNj.js} +2 -2
- package/src/assets/web-panel/assets/{Search-uRA0DhNz.js → Search-BT2V8dPN.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CViqdH6K.js → Security-bm9V3c5k.js} +4 -4
- package/src/assets/web-panel/assets/{Services-CkXS97WN.js → Services-CocH1yyX.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DxW5WYnE.js → Skeleton-DHItlMdd.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CLxmGFse.js → Skills-C59sLc4w.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-Cr5fziXq.js → Sla-Crj4BMc8.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-H0JSb3cp.js → SpeechSettings-DX4KFceY.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DMYWMae7.js → SyncSettings-BD_iwQLr.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-CbYyDdF4.js → Tasks-BmCTzYsH.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-Cpc8JjiK.js → Templates-BGUaxZYl.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CRh77Jda.js → Tenant-BS0tJx2O.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-4D8tNBO5.js → Terminal--tHSAsld.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-D41jzzbG.js → TimelineRenderer-D9-MFxS4.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-C_jWEy6b.js → Tokens-CiCB485f.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-Dk1ZUhsy.js → Trigger-SmCFuW_l.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-CLVy4l7m.js → Trust-DkK3Rqe0.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-C-FeqFDj.js → UkeySign-CDM4LGdB.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CqVZzyMm.js → VideoEditing-IDB7DJXt.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DX9BW9pu.js → Wallet-Bikj5cmt.js} +2 -2
- package/src/assets/web-panel/assets/{WebAuthn-7hQDpWw_.js → WebAuthn-C9abWlAw.js} +2 -2
- package/src/assets/web-panel/assets/{WorkflowEditor-6XqUAO0j.js → WorkflowEditor-DrbRKVky.js} +1 -1
- package/src/assets/web-panel/assets/{chat-5R_vkaJG.js → chat-CccCSDQf.js} +1 -1
- package/src/assets/web-panel/assets/{colors-huUPZdC-.js → colors-CXObXLhb.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-CfJlgisE.js → compact-item-B5kfYAxC.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-jO8heofr.js → createContext-DmiPBfip.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CvKrADNa.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DY8Vxqx7.js → hasIn-Q6lYteAc.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtEXSoPL.js → index-25Wlqps4.js} +1 -1
- package/src/assets/web-panel/assets/{index-BsBtRWbA.js → index-33U29UNV.js} +1 -1
- package/src/assets/web-panel/assets/{index-BQS-uPOK.js → index-4oXBDczR.js} +1 -1
- package/src/assets/web-panel/assets/{index-DXBcvUnf.js → index-BGYXvMc5.js} +1 -1
- package/src/assets/web-panel/assets/{index-C2iVSFpy.js → index-BJCCa0s_.js} +1 -1
- package/src/assets/web-panel/assets/{index-A48PtXw9.js → index-BJJ5DhKI.js} +1 -1
- package/src/assets/web-panel/assets/{index-Db1cLWir.js → index-BLOzViJ6.js} +1 -1
- package/src/assets/web-panel/assets/{index-vXs3B8GJ.js → index-BRrbUMoG.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHE8OIls.js → index-BUFpT17Q.js} +1 -1
- package/src/assets/web-panel/assets/{index-BmqGerpC.js → index-BkRDiqGc.js} +1 -1
- package/src/assets/web-panel/assets/{index-CzKmteNS.js → index-BsfvW6my.js} +1 -1
- package/src/assets/web-panel/assets/{index-BAON7TzU.js → index-By-kfPED.js} +1 -1
- package/src/assets/web-panel/assets/{index-DoI2sZm7.js → index-BzFTcQsc.js} +1 -1
- package/src/assets/web-panel/assets/{index-j3O2EEJe.js → index-C2FXdboq.js} +1 -1
- package/src/assets/web-panel/assets/{index-CRElRhyS.js → index-C5XOlTHN.js} +1 -1
- package/src/assets/web-panel/assets/{index-KRHroryI.js → index-CLhBDS2w.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bxax9vQ6.js → index-CMq7UeoT.js} +1 -1
- package/src/assets/web-panel/assets/{index-BfCx4pFT.js → index-CVMTWNKl.js} +1 -1
- package/src/assets/web-panel/assets/{index-CA6sO5VD.js → index-Caghi-yL.js} +1 -1
- package/src/assets/web-panel/assets/{index-CA90TZxG.js → index-CdB6FuSH.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cbe9G0V4.js → index-CoK8-kWQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-D0pet5Uy.js → index-CsO3GUAY.js} +1 -1
- package/src/assets/web-panel/assets/{index-ChvHubX3.js → index-CwZBa20Z.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRXOP09X.js → index-D-kASucw.js} +1 -1
- package/src/assets/web-panel/assets/{index-B74OXFxC.js → index-D8eBw7vW.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1VLuww0.js → index-D9wgifk4.js} +1 -1
- package/src/assets/web-panel/assets/{index-p85YaaKw.js → index-DAYCTc07.js} +1 -1
- package/src/assets/web-panel/assets/{index-BliaT02i.js → index-DF6Q8l2h.js} +1 -1
- package/src/assets/web-panel/assets/index-DTdFUWc_.js +1 -0
- package/src/assets/web-panel/assets/{index-Cjxbu68P.js → index-DgTLApS9.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cis_Ip7H.js → index-DgxVJ8pg.js} +1 -1
- package/src/assets/web-panel/assets/{index-v3pTaEQm.js → index-DlJvIqoR.js} +1 -1
- package/src/assets/web-panel/assets/{index-BYLBPpt8.js → index-Dw4eHfsG.js} +1 -1
- package/src/assets/web-panel/assets/{index-B300RPnT.js → index-Rh-ytwow.js} +3 -3
- package/src/assets/web-panel/assets/{index-CLhBh-qE.js → index-StAEt2pq.js} +1 -1
- package/src/assets/web-panel/assets/{index-d1xkbtNz.js → index-bzfw951k.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCqxyvqp.js → index-fSUMbMvv.js} +1 -1
- package/src/assets/web-panel/assets/{index-dKx57io7.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-MCB7-raZ.js → initDefaultProps-BNq7X72N.js} +1 -1
- package/src/assets/web-panel/assets/{motion-UWDF9swV.js → motion-DOf9viTc.js} +1 -1
- package/src/assets/web-panel/assets/{move-CsTmgXZU.js → move-CRHTcW4x.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-fQy6KGLg.js → mtc-parser-Cyoozgm2.js} +1 -1
- package/src/assets/web-panel/assets/{omit-woPnEjd1.js → omit-6-TjFNru.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-Bs2dZ1gf.js → pickAttrs-DlUmO1zi.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-C7EVgPkg.js → placementArrow-CFOL8mIR.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BH2kYCTN.js → responsiveObserve-BtaFK5zd.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BFBP2V_e.js → slide-BIbc2bur.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-xdDxDYse.js → statusUtils-BXX9d6-w.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-aAWgPPQf.js → styleChecker-BGv10Bpn.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DkGSAWoO.js → useFlexGapSupport-bKnKT8tw.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BrOih40N.js → useFs-FY4IL4jn.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-eIYrgMqO.js → usePersonalDataHub-Bwl9TEgu.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-B8lutNSE.js → vnode-BM3OHxAT.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CffNre7V.js → zoom-D1fbFiHB.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agenda.js +71 -14
- package/src/commands/doctor.js +85 -1
- package/src/commands/plugin.js +226 -9
- package/src/commands/review.js +17 -1
- package/src/commands/session.js +191 -0
- package/src/commands/team.js +19 -1
- package/src/data/changelog.json +5 -4
- package/src/gateways/ws/session-protocol.js +50 -0
- 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 +30 -1
- 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/diagnostic-bundle.js +230 -0
- package/src/lib/doctor-checkup.js +223 -4
- package/src/lib/execution-location.js +286 -0
- package/src/lib/headless-manifest.js +13 -40
- package/src/lib/hook-stats-store.cjs +197 -0
- package/src/lib/idempotency.js +109 -0
- 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 +28 -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/pr-automation-policy.js +224 -0
- package/src/lib/remote-approval-bridge.js +66 -13
- package/src/lib/repl-rewind.js +101 -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/secret-scan.js +409 -0
- package/src/lib/session-branch.js +0 -0
- package/src/lib/side-effect-ledger.js +105 -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 +36 -4
- package/src/lib/sub-agent-registry.js +19 -0
- package/src/lib/telemetry/span-recorder.js +17 -4
- package/src/lib/terminal-context.js +195 -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 +152 -38
- package/src/runtime/agent-core.js +191 -13
- package/src/runtime/diagnostics.js +60 -0
- package/src/runtime/headless-runner.js +126 -1
- package/src/assets/web-panel/assets/MobileProjects-D0RAk4e8.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DeIrCfH-.js +0 -1
- package/src/assets/web-panel/assets/index-BxUKMRpP.js +0 -1
- package/src/assets/web-panel/assets/index-DscU9iPI.js +0 -1
|
@@ -23,10 +23,18 @@ import {
|
|
|
23
23
|
statSync,
|
|
24
24
|
rmSync,
|
|
25
25
|
} from "node:fs";
|
|
26
|
-
import { execSync } from "node:child_process";
|
|
26
|
+
import { execSync, spawnSync } from "node:child_process";
|
|
27
27
|
import { join, basename } from "node:path";
|
|
28
28
|
import { getHomeDir, getConfigPath } from "./paths.js";
|
|
29
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
checkAgenda,
|
|
31
|
+
checkInstructionFiles,
|
|
32
|
+
checkHookConfig,
|
|
33
|
+
checkHooks,
|
|
34
|
+
checkSandbox,
|
|
35
|
+
checkDuplicateSkills,
|
|
36
|
+
DEFAULT_CHECKUP_THRESHOLDS,
|
|
37
|
+
} from "./runtime-checkup.js";
|
|
30
38
|
|
|
31
39
|
export const CHECK_LEVELS = Object.freeze({
|
|
32
40
|
OK: "ok",
|
|
@@ -45,6 +53,7 @@ export const _deps = {
|
|
|
45
53
|
statSync,
|
|
46
54
|
rmSync,
|
|
47
55
|
execSync,
|
|
56
|
+
spawnSync,
|
|
48
57
|
now: () => Date.now(),
|
|
49
58
|
};
|
|
50
59
|
|
|
@@ -618,7 +627,7 @@ async function runtimeSection(opts, deps) {
|
|
|
618
627
|
dueAt: e.kind === "wakeup" ? e.dueAt : e.nextAt,
|
|
619
628
|
recurring: e.kind !== "wakeup",
|
|
620
629
|
}));
|
|
621
|
-
for (const f of checkAgenda(entries, now)) {
|
|
630
|
+
for (const f of checkAgenda(entries, now, DEFAULT_CHECKUP_THRESHOLDS)) {
|
|
622
631
|
checks.push(
|
|
623
632
|
check(
|
|
624
633
|
f.id,
|
|
@@ -647,7 +656,7 @@ async function runtimeSection(opts, deps) {
|
|
|
647
656
|
}
|
|
648
657
|
}
|
|
649
658
|
}
|
|
650
|
-
for (const f of checkInstructionFiles(files)) {
|
|
659
|
+
for (const f of checkInstructionFiles(files, DEFAULT_CHECKUP_THRESHOLDS)) {
|
|
651
660
|
checks.push(
|
|
652
661
|
check(
|
|
653
662
|
f.id,
|
|
@@ -661,12 +670,221 @@ async function runtimeSection(opts, deps) {
|
|
|
661
670
|
checks.push(failedCheck("instructions", "instruction files", err));
|
|
662
671
|
}
|
|
663
672
|
|
|
673
|
+
// settings.json hook config — statically catch hooks that will silently never
|
|
674
|
+
// fire (unknown event / bad regex matcher / missing command) or are
|
|
675
|
+
// misconfigured (bad timeout). Runtime hook health (slow/circuit-broken) needs
|
|
676
|
+
// live per-process stats and isn't computed here.
|
|
677
|
+
try {
|
|
678
|
+
const settingsHooks = await import("./settings-hooks.cjs");
|
|
679
|
+
const mod = settingsHooks.default || settingsHooks;
|
|
680
|
+
const cwd = opts.cwd || process.cwd();
|
|
681
|
+
const files = mod.settingsFiles(cwd);
|
|
682
|
+
const seen = new Set();
|
|
683
|
+
for (const file of files) {
|
|
684
|
+
if (seen.has(file) || !deps.existsSync(file)) continue;
|
|
685
|
+
seen.add(file);
|
|
686
|
+
let hooksBlock = null;
|
|
687
|
+
try {
|
|
688
|
+
const parsed = JSON.parse(deps.readFileSync(file, "utf-8"));
|
|
689
|
+
hooksBlock = parsed && typeof parsed === "object" ? parsed.hooks : null;
|
|
690
|
+
} catch {
|
|
691
|
+
continue; // malformed settings.json is reported elsewhere (config section)
|
|
692
|
+
}
|
|
693
|
+
if (!hooksBlock) continue;
|
|
694
|
+
for (const f of checkHookConfig(hooksBlock, {
|
|
695
|
+
validEvents: mod.HOOK_EVENTS,
|
|
696
|
+
})) {
|
|
697
|
+
checks.push(
|
|
698
|
+
check(
|
|
699
|
+
f.id,
|
|
700
|
+
`${basename(file)} ${f.ref}`,
|
|
701
|
+
SEVERITY_TO_LEVEL[f.severity] || CHECK_LEVELS.INFO,
|
|
702
|
+
`${f.message} — ${f.remediation}`,
|
|
703
|
+
),
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
} catch (err) {
|
|
708
|
+
checks.push(failedCheck("hook-config", "settings hook config", err));
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// Async-hook RUNTIME health — the reliability aggregate persisted by
|
|
712
|
+
// AsyncHookSupervisor.stopAll(): hooks that have been repeatedly failing,
|
|
713
|
+
// circuit-broken (N failures in a row), or slow on average. Complements the
|
|
714
|
+
// static config check above (which catches hooks that never fire at all).
|
|
715
|
+
try {
|
|
716
|
+
const store = await import("./hook-stats-store.cjs");
|
|
717
|
+
const mod = store.default || store;
|
|
718
|
+
const statsFs = {
|
|
719
|
+
existsSync: deps.existsSync,
|
|
720
|
+
readFileSync: deps.readFileSync,
|
|
721
|
+
};
|
|
722
|
+
const stats = mod.loadHookStats(mod.defaultHookStatsPath(), statsFs);
|
|
723
|
+
const hooks = mod.toCheckHooksInput(stats, {
|
|
724
|
+
circuitThreshold: DEFAULT_CHECKUP_THRESHOLDS.hookFailureThreshold,
|
|
725
|
+
});
|
|
726
|
+
for (const f of checkHooks(hooks, DEFAULT_CHECKUP_THRESHOLDS)) {
|
|
727
|
+
checks.push(
|
|
728
|
+
check(
|
|
729
|
+
f.id,
|
|
730
|
+
`hook ${f.ref}`,
|
|
731
|
+
SEVERITY_TO_LEVEL[f.severity] || CHECK_LEVELS.INFO,
|
|
732
|
+
`${f.message} — ${f.remediation}`,
|
|
733
|
+
),
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
} catch (err) {
|
|
737
|
+
checks.push(failedCheck("hook-health", "async hook stats", err));
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// Sandbox real capability — catch the "silent degradation" case where a
|
|
741
|
+
// sandbox is configured in settings.json but its engine (docker/bwrap) isn't
|
|
742
|
+
// actually runnable, so tool subprocesses run UNSANDBOXED without warning.
|
|
743
|
+
try {
|
|
744
|
+
const settingsHooks = await import("./settings-hooks.cjs");
|
|
745
|
+
const shMod = settingsHooks.default || settingsHooks;
|
|
746
|
+
const sandboxMod = await import("./agent-sandbox.js");
|
|
747
|
+
const cwd = opts.cwd || process.cwd();
|
|
748
|
+
let sandboxSettings = null;
|
|
749
|
+
for (const file of shMod.settingsFiles(cwd)) {
|
|
750
|
+
if (!deps.existsSync(file)) continue;
|
|
751
|
+
try {
|
|
752
|
+
const parsed = JSON.parse(deps.readFileSync(file, "utf-8"));
|
|
753
|
+
if (parsed && parsed.sandbox && typeof parsed.sandbox === "object") {
|
|
754
|
+
sandboxSettings = { ...(sandboxSettings || {}), ...parsed.sandbox };
|
|
755
|
+
}
|
|
756
|
+
} catch {
|
|
757
|
+
// malformed settings.json is reported by the config section
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
if (sandboxSettings && sandboxSettings.enabled === true) {
|
|
761
|
+
const sandbox = sandboxMod.normalizeAgentSandbox(true, {
|
|
762
|
+
settings: sandboxSettings,
|
|
763
|
+
cwd,
|
|
764
|
+
});
|
|
765
|
+
if (sandbox) {
|
|
766
|
+
const probe = sandboxMod.probeSandboxAvailability(sandbox, {
|
|
767
|
+
spawnSync: deps.spawnSync,
|
|
768
|
+
});
|
|
769
|
+
for (const f of checkSandbox({
|
|
770
|
+
configured: true,
|
|
771
|
+
engine: sandbox.engine,
|
|
772
|
+
available: probe.available,
|
|
773
|
+
reason: probe.reason,
|
|
774
|
+
failIfUnavailable: sandbox.policy?.failIfUnavailable === true,
|
|
775
|
+
isolationLevel: sandboxMod.isolationLevel(sandbox),
|
|
776
|
+
})) {
|
|
777
|
+
checks.push(
|
|
778
|
+
check(
|
|
779
|
+
f.id,
|
|
780
|
+
`sandbox ${f.ref}`,
|
|
781
|
+
SEVERITY_TO_LEVEL[f.severity] || CHECK_LEVELS.INFO,
|
|
782
|
+
f.remediation ? `${f.message} — ${f.remediation}` : f.message,
|
|
783
|
+
),
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
} catch (err) {
|
|
789
|
+
checks.push(failedCheck("sandbox", "sandbox availability", err));
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// Duplicate skills — an id defined in more than one layer silently shadows
|
|
793
|
+
// (loadAll keeps only the winner), so a user's custom skill can override, or
|
|
794
|
+
// be overridden by, another without any signal. opts.skillLayerEntries lets a
|
|
795
|
+
// caller inject the (un-deduped) entry list; otherwise scan via the loader.
|
|
796
|
+
try {
|
|
797
|
+
let entries = opts.skillLayerEntries;
|
|
798
|
+
if (!entries) {
|
|
799
|
+
const { CLISkillLoader } = await import("./skill-loader.js");
|
|
800
|
+
entries = new CLISkillLoader().listSkillLayerEntries();
|
|
801
|
+
}
|
|
802
|
+
for (const f of checkDuplicateSkills(entries)) {
|
|
803
|
+
checks.push(
|
|
804
|
+
check(
|
|
805
|
+
f.id,
|
|
806
|
+
`skill ${f.ref}`,
|
|
807
|
+
SEVERITY_TO_LEVEL[f.severity] || CHECK_LEVELS.INFO,
|
|
808
|
+
`${f.message} — ${f.remediation}`,
|
|
809
|
+
),
|
|
810
|
+
);
|
|
811
|
+
}
|
|
812
|
+
} catch (err) {
|
|
813
|
+
checks.push(failedCheck("skill-duplicate", "skill layers", err));
|
|
814
|
+
}
|
|
815
|
+
|
|
664
816
|
if (checks.length === 0) {
|
|
665
817
|
checks.push(check("runtime-clean", "runtime hygiene", CHECK_LEVELS.OK, ""));
|
|
666
818
|
}
|
|
667
819
|
return { id: "runtime", title: "Runtime checkup", checks };
|
|
668
820
|
}
|
|
669
821
|
|
|
822
|
+
// ── execution context (P1-7) ───────────────────────────────────────────────
|
|
823
|
+
// "任务在哪执行是 Session 一等属性": surface the DETECTED execution location and
|
|
824
|
+
// the fail-closed permission posture it implies. Read-only advisory — it never
|
|
825
|
+
// blocks. A local trusted machine is OK; a remote/unknown environment is a WARN
|
|
826
|
+
// because a session there fails closed to read-only unless powers are granted
|
|
827
|
+
// explicitly (and any policy violation from the pure model is listed).
|
|
828
|
+
async function executionSection(opts, deps) {
|
|
829
|
+
const checks = [];
|
|
830
|
+
try {
|
|
831
|
+
const {
|
|
832
|
+
detectAmbientLocation,
|
|
833
|
+
validateExecutionContext,
|
|
834
|
+
EXECUTION_LOCATION,
|
|
835
|
+
} = await import("./execution-location.js");
|
|
836
|
+
// /.dockerenv is the classic in-container marker; keep it injectable via deps
|
|
837
|
+
// so the section is deterministic under test.
|
|
838
|
+
let dockerEnvFileExists = false;
|
|
839
|
+
try {
|
|
840
|
+
dockerEnvFileExists = deps.existsSync("/.dockerenv") === true;
|
|
841
|
+
} catch {
|
|
842
|
+
// non-POSIX host / unreadable — treat as not-a-container.
|
|
843
|
+
}
|
|
844
|
+
const location = detectAmbientLocation({
|
|
845
|
+
env: opts.env || process.env,
|
|
846
|
+
dockerEnvFileExists,
|
|
847
|
+
});
|
|
848
|
+
// Honest minimal input: just the detected location + the working dir. We do
|
|
849
|
+
// NOT fabricate a return path or granted powers — the validation then flags a
|
|
850
|
+
// real remote box with nowhere to return results, which is a meaningful hint.
|
|
851
|
+
const result = validateExecutionContext({
|
|
852
|
+
location,
|
|
853
|
+
source: { dir: opts.cwd || process.cwd() },
|
|
854
|
+
});
|
|
855
|
+
const isUnknown = location === EXECUTION_LOCATION.UNKNOWN;
|
|
856
|
+
const level =
|
|
857
|
+
isUnknown || result.descriptor.remote
|
|
858
|
+
? CHECK_LEVELS.WARN
|
|
859
|
+
: CHECK_LEVELS.OK;
|
|
860
|
+
checks.push(
|
|
861
|
+
check(
|
|
862
|
+
"execution-location",
|
|
863
|
+
`Execution location: ${location}`,
|
|
864
|
+
level,
|
|
865
|
+
result.descriptor.remote
|
|
866
|
+
? "remote/unknown — a session here fails closed to read-only unless powers are granted explicitly"
|
|
867
|
+
: isUnknown
|
|
868
|
+
? "could not identify the environment — treated as untrusted (read-only floor)"
|
|
869
|
+
: "local trusted machine",
|
|
870
|
+
),
|
|
871
|
+
);
|
|
872
|
+
if (result.violations.length > 0) {
|
|
873
|
+
checks.push(
|
|
874
|
+
check(
|
|
875
|
+
"execution-policy",
|
|
876
|
+
"Execution policy",
|
|
877
|
+
CHECK_LEVELS.WARN,
|
|
878
|
+
`fail-closed advisories: ${result.violations.join(", ")}`,
|
|
879
|
+
),
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
} catch (err) {
|
|
883
|
+
checks.push(failedCheck("execution-location", "execution context", err));
|
|
884
|
+
}
|
|
885
|
+
return { id: "execution", title: "Execution context", checks };
|
|
886
|
+
}
|
|
887
|
+
|
|
670
888
|
/**
|
|
671
889
|
* Collect all checkup sections. Never throws — a failing subsystem becomes an
|
|
672
890
|
* `err` check inside its section.
|
|
@@ -685,6 +903,7 @@ export async function collectCheckupSections(opts = {}) {
|
|
|
685
903
|
backgroundSection,
|
|
686
904
|
worktreeSection,
|
|
687
905
|
runtimeSection,
|
|
906
|
+
executionSection,
|
|
688
907
|
]) {
|
|
689
908
|
try {
|
|
690
909
|
sections.push(await build(opts, deps));
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execution-location model — the "任务在哪执行是 Session 一等属性" core of P1-7
|
|
3
|
+
* (Local / SSH / Cloud / Remote Control 统一模型) of
|
|
4
|
+
* CLAUDE_CODE_IDE_INCREMENTAL_GAP_ANALYSIS_2026-07-13.md.
|
|
5
|
+
*
|
|
6
|
+
* [[execution-backend.js]] is the runtime EXECUTOR (Local / Docker / SSH backends
|
|
7
|
+
* that actually run a command). It is NOT a session-level, displayable, policy
|
|
8
|
+
* layer: it has no notion of "show the user where this runs, with what
|
|
9
|
+
* permissions, whose credentials (source+scope only) and how results return",
|
|
10
|
+
* and no fail-closed default for an UNKNOWN location.
|
|
11
|
+
*
|
|
12
|
+
* This module is that pure descriptor + policy. The P1-7 table requires every
|
|
13
|
+
* session to surface: Execution Location, Source, Permissions, Credentials
|
|
14
|
+
* (source & scope only — never a value), Lifecycle, Cost and Return Path. This
|
|
15
|
+
* module normalizes those, and enforces the two invariants that give it teeth:
|
|
16
|
+
*
|
|
17
|
+
* 1. Credentials NEVER carry values — only {name, source, scope} survive, so a
|
|
18
|
+
* descriptor can be rendered/logged/shipped without leaking a secret.
|
|
19
|
+
* 2. An UNKNOWN or unrecognized location fails CLOSED to the most-restrictive
|
|
20
|
+
* permission profile (read-only, no shell/network/mcp/external), because we
|
|
21
|
+
* must not grant a remote/unknown environment ambient power by default.
|
|
22
|
+
*
|
|
23
|
+
* PURE: no fs / clock / RNG / process. A caller feeds observed session facts and
|
|
24
|
+
* reads back a safe descriptor plus a list of policy violations.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** Canonical execution locations. UNKNOWN is the fail-closed sink. */
|
|
28
|
+
export const EXECUTION_LOCATION = Object.freeze({
|
|
29
|
+
LOCAL: "local",
|
|
30
|
+
WSL: "wsl",
|
|
31
|
+
SSH: "ssh",
|
|
32
|
+
CONTAINER: "container",
|
|
33
|
+
CLOUD: "cloud",
|
|
34
|
+
UNKNOWN: "unknown",
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const LOCATION_ALIASES = new Map([
|
|
38
|
+
["local", EXECUTION_LOCATION.LOCAL],
|
|
39
|
+
["localhost", EXECUTION_LOCATION.LOCAL],
|
|
40
|
+
["wsl", EXECUTION_LOCATION.WSL],
|
|
41
|
+
["wsl2", EXECUTION_LOCATION.WSL],
|
|
42
|
+
["ssh", EXECUTION_LOCATION.SSH],
|
|
43
|
+
["remote-ssh", EXECUTION_LOCATION.SSH],
|
|
44
|
+
["remote_ssh", EXECUTION_LOCATION.SSH],
|
|
45
|
+
["container", EXECUTION_LOCATION.CONTAINER],
|
|
46
|
+
["docker", EXECUTION_LOCATION.CONTAINER],
|
|
47
|
+
["devcontainer", EXECUTION_LOCATION.CONTAINER],
|
|
48
|
+
["dev-container", EXECUTION_LOCATION.CONTAINER],
|
|
49
|
+
["cloud", EXECUTION_LOCATION.CLOUD],
|
|
50
|
+
["codespaces", EXECUTION_LOCATION.CLOUD],
|
|
51
|
+
["codespace", EXECUTION_LOCATION.CLOUD],
|
|
52
|
+
["sandbox", EXECUTION_LOCATION.CLOUD],
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
/** Locations that are NOT the user's own trusted machine. */
|
|
56
|
+
const REMOTE_LOCATIONS = new Set([
|
|
57
|
+
EXECUTION_LOCATION.SSH,
|
|
58
|
+
EXECUTION_LOCATION.CONTAINER,
|
|
59
|
+
EXECUTION_LOCATION.CLOUD,
|
|
60
|
+
]);
|
|
61
|
+
|
|
62
|
+
/** Normalize any location label to a canonical value; unrecognized → UNKNOWN. */
|
|
63
|
+
export function normalizeExecutionLocation(value) {
|
|
64
|
+
if (typeof value !== "string") return EXECUTION_LOCATION.UNKNOWN;
|
|
65
|
+
return (
|
|
66
|
+
LOCATION_ALIASES.get(value.trim().toLowerCase()) ||
|
|
67
|
+
EXECUTION_LOCATION.UNKNOWN
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function isRemoteLocation(location) {
|
|
72
|
+
return REMOTE_LOCATIONS.has(normalizeExecutionLocation(location));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Detect the ambient execution location from process signals. PURE: the caller
|
|
77
|
+
* supplies `env` (process.env) and `dockerEnvFileExists` (existsSync('/.dockerenv'))
|
|
78
|
+
* so this stays fs/process-free and unit-testable. Ordered most-specific-first,
|
|
79
|
+
* so a Codespace (which is also a container) reports CLOUD, and an SSH login
|
|
80
|
+
* outranks a bare WSL marker.
|
|
81
|
+
*
|
|
82
|
+
* @param {{env?:object, dockerEnvFileExists?:boolean}} [signals]
|
|
83
|
+
* @returns {string} a canonical EXECUTION_LOCATION value
|
|
84
|
+
*/
|
|
85
|
+
export function detectAmbientLocation({ env = {}, dockerEnvFileExists } = {}) {
|
|
86
|
+
const e = env && typeof env === "object" ? env : {};
|
|
87
|
+
const has = (k) => typeof e[k] === "string" && e[k].trim() !== "";
|
|
88
|
+
|
|
89
|
+
// Cloud dev environments (GitHub Codespaces) — most specific.
|
|
90
|
+
if (e.CODESPACES === "true" || has("GITHUB_CODESPACE_TOKEN")) {
|
|
91
|
+
return EXECUTION_LOCATION.CLOUD;
|
|
92
|
+
}
|
|
93
|
+
// An interactive SSH login carries these — a remote shell, not the box's own tty.
|
|
94
|
+
if (has("SSH_CONNECTION") || has("SSH_CLIENT") || has("SSH_TTY")) {
|
|
95
|
+
return EXECUTION_LOCATION.SSH;
|
|
96
|
+
}
|
|
97
|
+
// Containers: the classic /.dockerenv marker, an explicit `container` env, or k8s.
|
|
98
|
+
if (
|
|
99
|
+
dockerEnvFileExists === true ||
|
|
100
|
+
has("container") ||
|
|
101
|
+
has("KUBERNETES_SERVICE_HOST")
|
|
102
|
+
) {
|
|
103
|
+
return EXECUTION_LOCATION.CONTAINER;
|
|
104
|
+
}
|
|
105
|
+
// WSL exports these into every shell.
|
|
106
|
+
if (has("WSL_DISTRO_NAME") || has("WSL_INTEROP")) {
|
|
107
|
+
return EXECUTION_LOCATION.WSL;
|
|
108
|
+
}
|
|
109
|
+
return EXECUTION_LOCATION.LOCAL;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** The most-restrictive permission profile — the fail-closed floor. */
|
|
113
|
+
function lockedDownPermissions() {
|
|
114
|
+
return Object.freeze({
|
|
115
|
+
file: "read", // read-only
|
|
116
|
+
shell: false,
|
|
117
|
+
network: false,
|
|
118
|
+
mcp: false,
|
|
119
|
+
externalSystems: false,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function normalizeFileAccess(v) {
|
|
124
|
+
const s = String(v || "").toLowerCase();
|
|
125
|
+
if (s === "write" || s === "readwrite" || s === "read-write") return "write";
|
|
126
|
+
if (s === "none") return "none";
|
|
127
|
+
return "read";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Clamp a requested permission set to what a location may grant. Every ambient
|
|
132
|
+
* power (shell / network / mcp / external) requires an EXPLICIT `true` — an
|
|
133
|
+
* unspecified or truthy-but-not-true power stays off. File access defaults to
|
|
134
|
+
* read; only a write-class request ("write" / "readwrite") grants write. An
|
|
135
|
+
* UNKNOWN location ignores the request entirely and returns the locked-down
|
|
136
|
+
* floor — the fail-closed core.
|
|
137
|
+
*/
|
|
138
|
+
export function clampPermissionsForLocation(location, requested = {}) {
|
|
139
|
+
const loc = normalizeExecutionLocation(location);
|
|
140
|
+
if (loc === EXECUTION_LOCATION.UNKNOWN) return lockedDownPermissions();
|
|
141
|
+
|
|
142
|
+
const req = requested && typeof requested === "object" ? requested : {};
|
|
143
|
+
const grant = (key) => req[key] === true;
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
file: normalizeFileAccess(req.file),
|
|
147
|
+
shell: grant("shell"),
|
|
148
|
+
network: grant("network"),
|
|
149
|
+
mcp: grant("mcp"),
|
|
150
|
+
externalSystems: grant("externalSystems"),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Strip credential references down to what may be displayed: name, source and
|
|
156
|
+
* scope only. Any `value` / `token` / `secret` field is dropped — this is the
|
|
157
|
+
* enforcement point behind "Credentials 只显示来源和作用域,不显示值".
|
|
158
|
+
*/
|
|
159
|
+
export function redactCredentialRefs(creds) {
|
|
160
|
+
const list = Array.isArray(creds) ? creds : [];
|
|
161
|
+
return list
|
|
162
|
+
.filter((c) => c && typeof c === "object")
|
|
163
|
+
.map((c) => ({
|
|
164
|
+
name: c.name == null ? null : String(c.name),
|
|
165
|
+
source: c.source == null ? null : String(c.source),
|
|
166
|
+
scope: c.scope == null ? null : String(c.scope),
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const RETURN_PATHS = new Set([
|
|
171
|
+
"commit",
|
|
172
|
+
"pr",
|
|
173
|
+
"patch",
|
|
174
|
+
"artifact",
|
|
175
|
+
"report",
|
|
176
|
+
"none",
|
|
177
|
+
]);
|
|
178
|
+
|
|
179
|
+
function normalizeReturnPath(v) {
|
|
180
|
+
const s = String(v || "").toLowerCase();
|
|
181
|
+
return RETURN_PATHS.has(s) ? s : "none";
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Build the safe, displayable execution-context descriptor for a session.
|
|
186
|
+
*
|
|
187
|
+
* @param {object} input
|
|
188
|
+
* @param {string} input.location raw location label
|
|
189
|
+
* @param {object} [input.source] {dir, repo, commit, repos?}
|
|
190
|
+
* @param {object} [input.permissions] requested permissions (clamped)
|
|
191
|
+
* @param {Array} [input.credentials] credential refs (values stripped)
|
|
192
|
+
* @param {object} [input.lifecycle] {foreground?, onDisconnect?, onSleep?}
|
|
193
|
+
* @param {object} [input.cost] {model?, tokenBudget?, remote?}
|
|
194
|
+
* @param {string} [input.returnPath] commit|pr|patch|artifact|report|none
|
|
195
|
+
* @returns {object} normalized descriptor (safe to render/log/ship)
|
|
196
|
+
*/
|
|
197
|
+
export function describeExecutionContext(input = {}) {
|
|
198
|
+
const location = normalizeExecutionLocation(input.location);
|
|
199
|
+
const permissions = clampPermissionsForLocation(location, input.permissions);
|
|
200
|
+
const src =
|
|
201
|
+
input.source && typeof input.source === "object" ? input.source : {};
|
|
202
|
+
return {
|
|
203
|
+
location,
|
|
204
|
+
remote: REMOTE_LOCATIONS.has(location),
|
|
205
|
+
source: {
|
|
206
|
+
dir: src.dir == null ? null : String(src.dir),
|
|
207
|
+
repo: src.repo == null ? null : String(src.repo),
|
|
208
|
+
commit: src.commit == null ? null : String(src.commit),
|
|
209
|
+
repos: Array.isArray(src.repos) ? src.repos.map((r) => String(r)) : [],
|
|
210
|
+
},
|
|
211
|
+
permissions,
|
|
212
|
+
credentials: redactCredentialRefs(input.credentials),
|
|
213
|
+
lifecycle: {
|
|
214
|
+
foreground: input.lifecycle?.foreground !== false,
|
|
215
|
+
onDisconnect: input.lifecycle?.onDisconnect
|
|
216
|
+
? String(input.lifecycle.onDisconnect)
|
|
217
|
+
: "pause",
|
|
218
|
+
onSleep: input.lifecycle?.onSleep
|
|
219
|
+
? String(input.lifecycle.onSleep)
|
|
220
|
+
: "pause",
|
|
221
|
+
},
|
|
222
|
+
cost: {
|
|
223
|
+
model: input.cost?.model ? String(input.cost.model) : null,
|
|
224
|
+
tokenBudget: Number.isFinite(Number(input.cost?.tokenBudget))
|
|
225
|
+
? Number(input.cost.tokenBudget)
|
|
226
|
+
: null,
|
|
227
|
+
remote: input.cost?.remote === true,
|
|
228
|
+
},
|
|
229
|
+
returnPath: normalizeReturnPath(input.returnPath),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Keys that must never appear on a credential ref (a value leak). */
|
|
234
|
+
const CREDENTIAL_VALUE_KEYS = ["value", "token", "secret", "password", "key"];
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Validate a raw execution-context input for policy compliance. EXHAUSTIVE:
|
|
238
|
+
* collects every violation. Fail-closed reads:
|
|
239
|
+
* - a credential ref carrying a value/token/secret/password/key → leak
|
|
240
|
+
* - location UNKNOWN → the session runs somewhere we cannot vouch for
|
|
241
|
+
* - a remote location with no return path → results have nowhere safe to go
|
|
242
|
+
* - a remote session granted network / external egress → surfaced as
|
|
243
|
+
* `remote-egress-granted` so it must be explicitly acknowledged, not ambient
|
|
244
|
+
*
|
|
245
|
+
* @returns {{ok:boolean, violations:string[], descriptor:object}}
|
|
246
|
+
*/
|
|
247
|
+
export function validateExecutionContext(input = {}) {
|
|
248
|
+
const violations = [];
|
|
249
|
+
const rawCreds = Array.isArray(input.credentials) ? input.credentials : [];
|
|
250
|
+
for (const c of rawCreds) {
|
|
251
|
+
if (c && typeof c === "object") {
|
|
252
|
+
for (const k of CREDENTIAL_VALUE_KEYS) {
|
|
253
|
+
if (c[k] != null) {
|
|
254
|
+
violations.push("credential-value-present");
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const location = normalizeExecutionLocation(input.location);
|
|
262
|
+
if (location === EXECUTION_LOCATION.UNKNOWN) {
|
|
263
|
+
violations.push("unknown-location");
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const descriptor = describeExecutionContext(input);
|
|
267
|
+
|
|
268
|
+
if (descriptor.remote && descriptor.returnPath === "none") {
|
|
269
|
+
violations.push("remote-without-return-path");
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Network / external egress FROM a remote box is the highest-risk grant — it
|
|
273
|
+
// must be an explicit, acknowledged decision, never ambient.
|
|
274
|
+
if (
|
|
275
|
+
descriptor.remote &&
|
|
276
|
+
(descriptor.permissions.network || descriptor.permissions.externalSystems)
|
|
277
|
+
) {
|
|
278
|
+
violations.push("remote-egress-granted");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
ok: violations.length === 0,
|
|
283
|
+
violations,
|
|
284
|
+
descriptor,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
import crypto from "node:crypto";
|
|
18
18
|
import { createRequire } from "node:module";
|
|
19
19
|
import { PROTOCOL_MIN_VERSION } from "./capability-negotiation.js";
|
|
20
|
+
import {
|
|
21
|
+
CAPABILITY_MANIFEST,
|
|
22
|
+
toAgentFeatureFlags,
|
|
23
|
+
} from "./capability-manifest.js";
|
|
20
24
|
|
|
21
25
|
export const STREAM_PROTOCOL_VERSION = 1;
|
|
22
26
|
|
|
@@ -143,46 +147,15 @@ export function buildAgentCapabilities() {
|
|
|
143
147
|
// older line shape, so min === current.
|
|
144
148
|
min_protocol_version: PROTOCOL_MIN_VERSION,
|
|
145
149
|
agent_tools: contract.listCodingAgentToolNames(),
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
],
|
|
155
|
-
output_formats: ["text", "json", "stream-json"],
|
|
156
|
-
input_formats: ["text", "stream-json"],
|
|
150
|
+
// P1-9: permission modes, formats and the feature flags all project from the
|
|
151
|
+
// single canonical [[capability-manifest.js]] so `cc agent --capabilities`,
|
|
152
|
+
// the negotiator and the docs can no longer drift. Additive protocol-v1
|
|
153
|
+
// stream fields (event_seq / tool_use_id / trace_id) come in via the wire
|
|
154
|
+
// features; the mcp object and sandbox_engines come from the manifest too.
|
|
155
|
+
permission_modes: [...CAPABILITY_MANIFEST.permissionModes],
|
|
156
|
+
output_formats: [...CAPABILITY_MANIFEST.outputFormats],
|
|
157
|
+
input_formats: [...CAPABILITY_MANIFEST.inputFormats],
|
|
157
158
|
exit_codes: HEADLESS_EXIT_CODES,
|
|
158
|
-
features:
|
|
159
|
-
bare: true,
|
|
160
|
-
safe_mode: true,
|
|
161
|
-
ephemeral: true,
|
|
162
|
-
background: true,
|
|
163
|
-
worktree: true,
|
|
164
|
-
checkpoint: true,
|
|
165
|
-
json_schema_output: true,
|
|
166
|
-
partial_messages: true,
|
|
167
|
-
interactive_approvals: true,
|
|
168
|
-
permission_prompt_tool: true,
|
|
169
|
-
remote_control: true,
|
|
170
|
-
// Additive protocol-v1 stream fields (agent-sdk docs/PROTOCOL.md §1.2.1):
|
|
171
|
-
// every stream-json stdout line carries a monotonic `seq` and a
|
|
172
|
-
// run-scoped `trace_id`, and tool_use/tool_result lines carry a pairing
|
|
173
|
-
// `id` ("tu-<n>"). `trace_id` is caller-injectable (--trace-id /
|
|
174
|
-
// CC_TRACE_ID) for end-to-end IDE ↔ CLI correlation.
|
|
175
|
-
tool_use_id: true,
|
|
176
|
-
event_seq: true,
|
|
177
|
-
trace_id: true,
|
|
178
|
-
mcp: {
|
|
179
|
-
config_file: true,
|
|
180
|
-
registry: true,
|
|
181
|
-
tool_search: true,
|
|
182
|
-
oauth: true,
|
|
183
|
-
managed_allowlist: true,
|
|
184
|
-
},
|
|
185
|
-
sandbox_engines: ["docker", "bubblewrap"],
|
|
186
|
-
},
|
|
159
|
+
features: toAgentFeatureFlags(),
|
|
187
160
|
};
|
|
188
161
|
}
|