chainlesschain 0.162.159 → 0.162.160

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.
Files changed (183) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/src/assets/web-panel/assets/{AIOps-ByIiF5bT.js → AIOps-CkRHjW8e.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-NlgSFZq5.js → ActionButton-BP3MeNi2.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-D17cOUcb.js → Analytics-CjbjQsOr.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-B4mEZcbl.js → AppLayout-i0adH1M8.js} +5 -5
  7. package/src/assets/web-panel/assets/{Artifacts-CASsmyQw.js → Artifacts-CCCG2GOU.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-DYAia2e2.js → Audit-DrsgvwHC.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-C1lvYUSu.js → BackgroundAgents-DkXNkbZM.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-B6agK6bA.js → Backup-DarTWCex.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-DM43R2vy.js → BaseInput-DaDzhcto.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-Dtdp3eXp.js → Chat-C4EzGw6V.js} +5 -5
  13. package/src/assets/web-panel/assets/{ChatBubbleRenderer-BL5c_OQ1.js → ChatBubbleRenderer-lkavIWMS.js} +1 -1
  14. package/src/assets/web-panel/assets/{Checkbox-D2xf7uZt.js → Checkbox-gddNgaQi.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-D3j45jJL.js → Codegen-BKldFWf9.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-voPJ9fUZ.js → Col-ChR-brDs.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-DEsyTESF.js → Community-DlHtZX86.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-OHV_QXLD.js → Compact-BAhfcBpC.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-eDTJWC-J.js → Compliance-gIrKkm6m.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-BBvy-6d1.js → Cowork-BYnzLBO3.js} +2 -2
  21. package/src/assets/web-panel/assets/{Cron-Dw2eoJxu.js → Cron-ByUDIljI.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-DQHms_Bh.js → Crosschain-DOdNbHFp.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-Cyu7Id1c.js → DID-BJHaNWD2.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-sIDzkG92.js → Dashboard-Drm_tClz.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-DUW3T2ey.js → Dropdown-CA1WLzGN.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-CECELXW8.js → EmailListRenderer-Di1SKjNO.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-BIZR37ov.js → FamilyGuardDashboard-44Ve7ZFe.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-BEZIQX84.js → Federation-CubPwv_N.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-BcdUMznL.js → FormItemContext-CsUFe5bU.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-DsN_fr5H.js → GenericCardRenderer-D7xRjPjZ.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-63LhjBx2.js → Git-D-Qx9dhO.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-CYtQtIcH.js → Governance-_YMYaU4n.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-BwR6UUox.js → Inference-J7Yzh-K_.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-D9lVNTUA.js → KnowledgeGraph-D37iI_Mo.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-C4q5h7Ut.js → Logs-oMTSYfGY.js} +2 -2
  36. package/src/assets/web-panel/assets/{MarkdownRenderer-DexLlXfN.js → MarkdownRenderer-BqaPmC0W.js} +1 -1
  37. package/src/assets/web-panel/assets/{Marketplace-Plksliuw.js → Marketplace-DKUqYDaF.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-DZUFfeuG.js → McpTools-BEv3Gx61.js} +3 -3
  39. package/src/assets/web-panel/assets/{Memory-NoDYt29V.js → Memory-CtA8XD2y.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-CEXGTkdh.js → MobileBridge-ug7c1mMj.js} +1 -1
  41. package/src/assets/web-panel/assets/MobileProjects-BpX5g9It.js +1 -0
  42. package/src/assets/web-panel/assets/{Mtc-DTAa0cwy.js → Mtc-WJaMs4i2.js} +5 -5
  43. package/src/assets/web-panel/assets/{MtcAudit-CVV7LnNS.js → MtcAudit-DXnK_-ru.js} +2 -2
  44. package/src/assets/web-panel/assets/{Multisig-BqqGrjt_.js → Multisig-C4Ev7726.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-C4WP8DRp.js → NLProgramming-BMFb9_zk.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-Zo4DJUVI.js → Notes-Bl93nvBX.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-DBcjRaz2.js → NotificationSettings-BN2W5cl6.js} +1 -1
  48. package/src/assets/web-panel/assets/OrderTableRenderer-Dxc9oN4o.js +1 -0
  49. package/src/assets/web-panel/assets/{Organization-BT9WPBOX.js → Organization-CAs48637.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-D6b4bZ3q.js → Overflow-Bhq27Zoc.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-Cw3OsYx-.js → P2P-D61-2542.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-C0nyiK2b.js → PdhVaultBrowser-C1j4iUxz.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-zVBZEB_C.js → Permissions-rJ2-fScM.js} +3 -3
  54. package/src/assets/web-panel/assets/{PersonalDataHub-BOspsFew.js → PersonalDataHub-DFIlTok6.js} +2 -2
  55. package/src/assets/web-panel/assets/{Pipeline-CQWfZ4XH.js → Pipeline-BjIgEWoZ.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-C2oGJ_WL.js → Privacy-BQoH5wcX.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-BZMR4qY1.js → ProjectInit-CYR_-_Ub.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-DAujoP63.js → ProjectSettings-nkatCq-9.js} +2 -2
  59. package/src/assets/web-panel/assets/Projects-SS9uZKCl.js +1 -0
  60. package/src/assets/web-panel/assets/{Providers-D_RGHM-v.js → Providers-BnKM9fsF.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-Mf6KfL2J.js → QrScannerModal-Cfe82dbz.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-5D8URoI_.js → QuickAsk-r1RbPufV.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-Df4kC8Gy.js → Recommend-CjCbM2WE.js} +1 -1
  64. package/src/assets/web-panel/assets/{RemoteSession-DGi3f3vO.js → RemoteSession-BuMGPH6V.js} +2 -2
  65. package/src/assets/web-panel/assets/{Reputation-jmkGFNWb.js → Reputation-Bmj3NY1F.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-DP6Pcu3m.js → Row-BIsNcE_1.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-D60hSuPO.js → RssFeed-BObmojD2.js} +3 -3
  68. package/src/assets/web-panel/assets/{Search-0mf1BQUK.js → Search-BRmbkQNS.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-CfZtLFYV.js → Security-_rF3DRpY.js} +4 -4
  70. package/src/assets/web-panel/assets/{Services-D9zpZLWz.js → Services-D0If9ZV4.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-D1_44eMD.js → Skeleton-o7zIksUg.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-cVFa8zkz.js → Skills-oUR7zi9u.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-Cinpzc3L.js → Sla-fqKyGi03.js} +1 -1
  74. package/src/assets/web-panel/assets/{SpeechSettings-bqdiJE_P.js → SpeechSettings-B1wZ_gxx.js} +1 -1
  75. package/src/assets/web-panel/assets/{SyncSettings-Dgl_SLgj.js → SyncSettings-B3qJWYXq.js} +2 -2
  76. package/src/assets/web-panel/assets/{Tasks-DawWAEhy.js → Tasks-BH5_dUfr.js} +1 -1
  77. package/src/assets/web-panel/assets/{Templates-Ca0nY651.js → Templates-CCIVpjRP.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-CgaGEhLm.js → Tenant-2GZtTGnB.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-D-9EnpRx.js → Terminal-BlMkfs_u.js} +2 -2
  80. package/src/assets/web-panel/assets/{TimelineRenderer-CSOxDHZ6.js → TimelineRenderer-U6hi5OMc.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-BpwLZpL3.js → Tokens-C-_BerGJ.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-DgowwBK5.js → Trigger-DaKzBgN3.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-BZutapjE.js → Trust-CLvcsco3.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-QZPt8_Sp.js → UkeySign-BFn_Y1zZ.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-DlQx5UDJ.js → VideoEditing-B0V18dLT.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-ZfdTgtWA.js → Wallet-BbmP7Imu.js} +2 -2
  87. package/src/assets/web-panel/assets/{WebAuthn-CAp0y_Cr.js → WebAuthn-Dewj_0kt.js} +5 -5
  88. package/src/assets/web-panel/assets/{WorkflowEditor-BVITZEmr.js → WorkflowEditor-DOxz3Lwa.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-CC2tPaNg.js → chat-B1OIpZnh.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-31brc9CA.js → colors-CSGNVdri.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-DLMkC9C-.js → compact-item-8S0vcg0u.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-BMtveI6F.js → createContext-vZyc27TN.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-DYEZcY55.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-DbD6WHW2.js → hasIn-DWw3-UhA.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-Dm0byjm1.js → index-B4pP2ycr.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-CEXa-4zd.js → index-BDHyovnK.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-C_eUKGYP.js → index-BKEmQQvR.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BOM-VFlv.js → index-BQssf0EQ.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-bdaJwTXJ.js → index-BZnauCqN.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-Cwbqh0KZ.js → index-BdyokEau.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DJvxFyIz.js → index-BeBv896m.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-DndNnYc0.js → index-BjC-TrOb.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-CWgfipcC.js → index-BkWd6YL7.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-D7cV0tu0.js → index-BoIPHP4R.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-CcVszn2b.js → index-BuB5P0TR.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-COHHQ9cU.js → index-BwKrTVV0.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-C1PfeI88.js → index-C6r27A-o.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-Cod97Urs.js → index-CB2MOS_8.js} +3 -3
  109. package/src/assets/web-panel/assets/{index-CEwjBA9q.js → index-CFQWZvak.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-D67kIagl.js → index-CI3slBKI.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-jG6h22Qg.js → index-CJgKP2cm.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-DZl-UXB6.js → index-CLPr2vC_.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-ntVQqSML.js → index-CbZqw7pg.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-C-cGlCDW.js → index-CfEvacz7.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BFRLmCR3.js → index-CwXBblm-.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-AaIKG60c.js → index-D1i_8oop.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BPpS-lGb.js → index-D8qXVfoV.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-WQUO2vvK.js → index-DKwl1AkQ.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-CUmLMFsy.js → index-DLAvUtOH.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-Bbc2JeQG.js → index-DLgkAhEE.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BKAB6-6V.js → index-DPgY5GLy.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-ALvoNTRF.js → index-Dc40pX3F.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-ep6JchNG.js → index-DcfLXAW7.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-C21pdBqs.js → index-DeCS12Mh.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-3oBsXZmF.js → index-Dg0Izipr.js} +1 -1
  126. package/src/assets/web-panel/assets/index-DmVFfcNR.js +1 -0
  127. package/src/assets/web-panel/assets/{index-BnFLzejT.js → index-DtHWHJnj.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-C7LHNw_N.js → index-Q6oyLnKC.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-BoO4WEJB.js → index-dxocDVD_.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-BiRQUoyE.js → index-t-q3rkVm.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-CFXKbOek.js → index-tr1x96W9.js} +1 -1
  132. package/src/assets/web-panel/assets/index-x7YyhMUa.js +1 -0
  133. package/src/assets/web-panel/assets/{index-CJhREOlG.js → index-xx5M5Rbj.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-D5UoBRhN.js → initDefaultProps-Di5YFId0.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-DltOw0CR.js → motion-BVSN0aeL.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-_oPkk5zj.js → move-DHNiaP2l.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-EES8ROCS.js → mtc-parser-xJPf3gQD.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-B-_39x_M.js → omit-C7qzUdkN.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-l6D0yJHz.js → pickAttrs-pSpjg3Le.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-DIeUK31L.js → placementArrow-cs_jL_UC.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-dG4EzUQo.js → responsiveObserve-DkS_Zs4r.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-DWFzYGdH.js → slide-B5O1Kpxo.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-CECdTA5H.js → statusUtils-BX02GnZp.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-D6kP-mMK.js → styleChecker-EmBYRftd.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-BWfna_rd.js → useFlexGapSupport-ZGzN1cU8.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-Bb-j6rzo.js → useFs-CfP7M1m7.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-D5OIsYM4.js → usePersonalDataHub-CbIPHgNh.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-DNpwzFTM.js → vnode-7xh0If_O.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-vNLpFQBW.js → zoom-DmitvTR2.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/commands/agent.js +65 -6
  152. package/src/commands/background-session.js +30 -0
  153. package/src/commands/mcp.js +46 -0
  154. package/src/commands/session.js +70 -0
  155. package/src/data/changelog.json +7 -2
  156. package/src/harness/jsonl-session-store.js +70 -1
  157. package/src/harness/mcp-client.js +50 -0
  158. package/src/lib/agent-sandbox.js +68 -0
  159. package/src/lib/agents.js +12 -4
  160. package/src/lib/api-key-helper.js +62 -0
  161. package/src/lib/background-agent-supervisor.js +32 -0
  162. package/src/lib/dependency-install-policy.js +130 -0
  163. package/src/lib/exit-codes.cjs +77 -0
  164. package/src/lib/headless-manifest.js +175 -0
  165. package/src/lib/hook-runner.cjs +70 -4
  166. package/src/lib/ide-context-redaction.js +130 -0
  167. package/src/lib/ide-context.js +89 -13
  168. package/src/lib/jsonl-session-store.js +2 -0
  169. package/src/lib/llm-config-defaults.js +24 -8
  170. package/src/lib/project-mcp-trust.js +87 -0
  171. package/src/lib/sensitive-file-guard.js +51 -0
  172. package/src/lib/sub-agent-context.js +7 -3
  173. package/src/runtime/agent-core.js +102 -6
  174. package/src/runtime/coding-agent-contract-shared.cjs +19 -2
  175. package/src/runtime/headless-runner.js +66 -7
  176. package/src/runtime/headless-stream.js +29 -1
  177. package/src/runtime/mcp-config.js +32 -1
  178. package/src/assets/web-panel/assets/MobileProjects-DnbJkbZo.js +0 -1
  179. package/src/assets/web-panel/assets/OrderTableRenderer-BCQel9bD.js +0 -1
  180. package/src/assets/web-panel/assets/Projects-DvH_P2Uq.js +0 -1
  181. package/src/assets/web-panel/assets/devWarning-CB8pPOtd.js +0 -1
  182. package/src/assets/web-panel/assets/index-BhXiaiev.js +0 -1
  183. package/src/assets/web-panel/assets/index-WpaYaE3D.js +0 -1
@@ -0,0 +1,62 @@
1
+ /**
2
+ * `llm.apiKeyHelper` (gap-analysis 2026-07-11 P0 "依赖安装与凭据"): fetch the
3
+ * LLM API key from an external command instead of storing it in plaintext
4
+ * config.json. Point it at your OS credential store, e.g.:
5
+ *
6
+ * Windows : "powershell -NoProfile -Command \"...CredentialManager...\""
7
+ * macOS : "security find-generic-password -s cc-llm -w"
8
+ * Linux : "secret-tool lookup service cc-llm"
9
+ *
10
+ * Resolution precedence stays: --api-key > CC_API_KEY > config llm.apiKey >
11
+ * llm.apiKeyHelper. The helper's stdout (trimmed) is the key; a failing or
12
+ * empty helper resolves null so the run fails with the provider's own
13
+ * missing-key error (never a silent fallback — see the no-silent-substitution
14
+ * rule). Cached per process (5 min) so multi-call runs don't re-spawn it.
15
+ */
16
+
17
+ import { execSync } from "node:child_process";
18
+
19
+ const TTL_MS = 5 * 60 * 1000;
20
+ const _cache = new Map(); // helper command → { key, at }
21
+ let _warnedFailure = false;
22
+
23
+ /**
24
+ * @param {string} helperCmd shell command printing the key on stdout
25
+ * @param {{exec?:Function, now?:()=>number, writeErr?:(s:string)=>void}} [opts]
26
+ * @returns {string|null}
27
+ */
28
+ export function resolveApiKeyFromHelper(helperCmd, opts = {}) {
29
+ const cmd = typeof helperCmd === "string" ? helperCmd.trim() : "";
30
+ if (!cmd) return null;
31
+ const now = opts.now || Date.now;
32
+ const hit = _cache.get(cmd);
33
+ if (hit && now() - hit.at < TTL_MS) return hit.key;
34
+ const exec = opts.exec || execSync;
35
+ try {
36
+ const out = exec(cmd, {
37
+ encoding: "utf-8",
38
+ timeout: 10000,
39
+ windowsHide: true,
40
+ stdio: ["ignore", "pipe", "pipe"],
41
+ });
42
+ const key = String(out || "").trim();
43
+ if (!key) return null;
44
+ _cache.set(cmd, { key, at: now() });
45
+ return key;
46
+ } catch (err) {
47
+ // Warn once per process, then stay quiet — the provider's missing-key
48
+ // error is the actionable surface.
49
+ if (!_warnedFailure) {
50
+ _warnedFailure = true;
51
+ const writeErr = opts.writeErr || ((s) => process.stderr.write(s));
52
+ writeErr(`[api-key-helper] llm.apiKeyHelper failed: ${err.message}\n`);
53
+ }
54
+ return null;
55
+ }
56
+ }
57
+
58
+ /** Test seam: drop the per-process cache + warn-once latch. */
59
+ export function clearApiKeyHelperCache() {
60
+ _cache.clear();
61
+ _warnedFailure = false;
62
+ }
@@ -223,6 +223,38 @@ export function renameBackgroundAgent(id, title, options = {}) {
223
223
  return next;
224
224
  }
225
225
 
226
+ /**
227
+ * Remove a background agent's RECORD (state file + log) — `cc daemon rm`
228
+ * (gap-analysis 2026-07-11 P0 "后台 Agent Supervisor" 补 rm 动词). Terminal
229
+ * sessions (completed/failed/stopped/lost) remove directly; a still-running
230
+ * one is refused unless `force`, which stops it first. The underlying JSONL
231
+ * conversation session is NOT touched — that's `cc session delete`.
232
+ */
233
+ export function removeBackgroundAgent(id, options = {}) {
234
+ const state = effectiveBackgroundAgentState(readBackgroundAgentState(id), {
235
+ now: options.now,
236
+ heartbeatStaleMs: options.heartbeatStaleMs,
237
+ });
238
+ if (!state) throw new Error(`Background agent not found: ${id}`);
239
+ if (state.status === "running") {
240
+ if (options.force !== true) {
241
+ throw new Error(
242
+ `${id} is still running — stop it first (cc daemon stop ${id}) or pass --force`,
243
+ );
244
+ }
245
+ try {
246
+ stopBackgroundAgent(id);
247
+ } catch {
248
+ /* best-effort — the record removal below is the point of rm --force */
249
+ }
250
+ }
251
+ rmSync(statePath(id), { force: true });
252
+ if (options.keepLog !== true) {
253
+ rmSync(logPath(id), { force: true });
254
+ }
255
+ return { id, removed: true, status: state.status };
256
+ }
257
+
226
258
  /**
227
259
  * Pin/unpin a session for the dashboard (`cc daemon view`) — pinned sessions
228
260
  * sort first inside their group. Same read-modify-write + verify-retry dance
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Dependency-install policy for `run_code` (gap-analysis 2026-07-11,
3
+ * docs/CLAUDE_CODE_CLI_GAP_ANALYSIS.md P0 "依赖安装与凭据").
4
+ *
5
+ * Auto `pip install` on a Python import error used to be unconditional. It is
6
+ * now OPT-IN:
7
+ * - env `CC_RUN_CODE_AUTO_INSTALL=1|0` wins outright (kill-switch friendly)
8
+ * - settings.json `runCode.autoInstall: true` opts in per project/user
9
+ * - default: DISABLED — the tool result explains how to enable it
10
+ *
11
+ * When enabled, an optional `runCode.installAllowlist` (array of package
12
+ * names, PEP 503-normalized) restricts WHICH packages may be installed, and
13
+ * every attempt — including blocked/disabled ones — is appended to an audit
14
+ * log at ~/.chainlesschain/audit/dependency-install.jsonl.
15
+ */
16
+
17
+ import fs from "node:fs";
18
+ import os from "node:os";
19
+ import path from "node:path";
20
+ import { createRequire } from "node:module";
21
+
22
+ const _require = createRequire(import.meta.url);
23
+
24
+ /** PEP 503 name normalization: lowercase, runs of -_. collapse to "-". */
25
+ export function normalizePackageName(name) {
26
+ return String(name || "")
27
+ .trim()
28
+ .toLowerCase()
29
+ .replace(/[-_.]+/g, "-");
30
+ }
31
+
32
+ /**
33
+ * Resolve the effective auto-install policy.
34
+ * @param {{cwd?:string, env?:object, settingsFile?:string|null}} opts
35
+ * @returns {{enabled:boolean, allowlist:string[]|null, source:string}}
36
+ */
37
+ export function resolveAutoInstallPolicy({
38
+ cwd = process.cwd(),
39
+ env = process.env,
40
+ settingsFile = null,
41
+ } = {}) {
42
+ const raw = env.CC_RUN_CODE_AUTO_INSTALL;
43
+ if (raw === "1" || raw === "0") {
44
+ return {
45
+ enabled: raw === "1",
46
+ allowlist: raw === "1" ? readAllowlist({ cwd, settingsFile }) : null,
47
+ source: "env",
48
+ };
49
+ }
50
+ let enabled = false;
51
+ try {
52
+ const { readBooleanSetting } = _require("./settings-loader.cjs");
53
+ enabled =
54
+ readBooleanSetting("runCode.autoInstall", { cwd, settingsFile }) === true;
55
+ } catch {
56
+ enabled = false; // fail-closed: no settings → stay disabled
57
+ }
58
+ return {
59
+ enabled,
60
+ allowlist: enabled ? readAllowlist({ cwd, settingsFile }) : null,
61
+ source: enabled ? "settings" : "default",
62
+ };
63
+ }
64
+
65
+ function readAllowlist({ cwd = process.cwd(), settingsFile }) {
66
+ try {
67
+ const { settingsPaths, readSettingsFile, loadManagedSettings } = _require(
68
+ "./settings-loader.cjs",
69
+ );
70
+ let value;
71
+ for (const file of settingsPaths(cwd, settingsFile)) {
72
+ const node = readSettingsFile(file)?.runCode?.installAllowlist;
73
+ if (Array.isArray(node)) value = node; // closest layer wins
74
+ }
75
+ const managed = loadManagedSettings({});
76
+ const mNode = managed?.settings?.runCode?.installAllowlist;
77
+ if (Array.isArray(mNode)) value = mNode; // managed policy wins outright
78
+ if (!Array.isArray(value)) return null;
79
+ const list = value.map(normalizePackageName).filter(Boolean);
80
+ return list.length ? list : null;
81
+ } catch {
82
+ return null;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Whether a package may be installed under the policy's allowlist.
88
+ * No allowlist configured → any (validity is checked separately).
89
+ */
90
+ export function isPackageAllowed(name, allowlist) {
91
+ if (!allowlist) return true;
92
+ return allowlist.includes(normalizePackageName(name));
93
+ }
94
+
95
+ /** Human hint for the disabled-by-default path (returned in the tool result). */
96
+ export function autoInstallDisabledHint(packageName) {
97
+ return (
98
+ `Missing Python package "${packageName}". Auto-install is disabled by ` +
99
+ `default; install it yourself (pip install ${packageName}), or opt in via ` +
100
+ `settings.json {"runCode":{"autoInstall":true}} or CC_RUN_CODE_AUTO_INSTALL=1.`
101
+ );
102
+ }
103
+
104
+ /**
105
+ * Append an install-attempt audit record (best-effort, never throws).
106
+ * @param {{package:string, interpreter?:string, cwd?:string,
107
+ * outcome:"installed"|"failed"|"blocked"|"disabled", detail?:string}} entry
108
+ * @param {{baseDir?:string, now?:()=>number}} [opts] injection seams
109
+ */
110
+ export function recordInstallAudit(entry, opts = {}) {
111
+ try {
112
+ const baseDir =
113
+ opts.baseDir ||
114
+ process.env.CC_AUDIT_DIR ||
115
+ path.join(os.homedir(), ".chainlesschain", "audit");
116
+ fs.mkdirSync(baseDir, { recursive: true });
117
+ const line = JSON.stringify({
118
+ ts: new Date(opts.now ? opts.now() : Date.now()).toISOString(),
119
+ kind: "dependency-install",
120
+ ...entry,
121
+ });
122
+ fs.appendFileSync(
123
+ path.join(baseDir, "dependency-install.jsonl"),
124
+ line + "\n",
125
+ "utf-8",
126
+ );
127
+ } catch {
128
+ /* audit is best-effort — never affect the run */
129
+ }
130
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Headless exit-code taxonomy (gap-analysis 2026-07-11, P0 "确定性 Headless"):
3
+ * scripted callers can tell apart WHY a run ended without parsing stderr.
4
+ *
5
+ * Backward compatibility: every failure is still non-zero, 0 stays success,
6
+ * 2 stays "settings hook blocked the prompt" (pre-existing), 130/143 stay the
7
+ * signal-interrupt codes. The new codes carve budget/turn exhaustion and
8
+ * config errors out of the generic 1.
9
+ */
10
+
11
+ const HEADLESS_EXIT_CODES = Object.freeze({
12
+ OK: 0,
13
+ /** generic failure (tool/loop/unclassified error) */
14
+ ERROR: 1,
15
+ /** a settings hook blocked the prompt (pre-existing semantic) */
16
+ HOOK_BLOCKED: 2,
17
+ /** the turn budget (--max-turns / loop iteration cap) ran out first */
18
+ MAX_TURNS: 3,
19
+ /** the --max-budget-usd cost cap stopped the run */
20
+ MAX_BUDGET: 4,
21
+ /** the LLM call itself failed (provider/model/network error) */
22
+ MODEL_ERROR: 5,
23
+ /** bad run configuration (--mcp-config / --permission-prompt-tool / settings) */
24
+ CONFIG_ERROR: 6,
25
+ /** user interrupt (SIGINT) — 128+2, shell convention */
26
+ INTERRUPT_SIGINT: 130,
27
+ /** termination (SIGTERM) — 128+15, shell convention */
28
+ INTERRUPT_SIGTERM: 143,
29
+ });
30
+
31
+ /**
32
+ * Best-effort classifier for errors thrown out of the agent loop: provider /
33
+ * transport failures → MODEL_ERROR, everything else → generic ERROR. Checks
34
+ * structural markers (HTTP status, network error codes) before falling back —
35
+ * never message-text guessing beyond the explicit LLM markers.
36
+ */
37
+ function classifyLoopError(err) {
38
+ if (!err) return HEADLESS_EXIT_CODES.ERROR;
39
+ const status = err.status ?? err.statusCode ?? err.response?.status;
40
+ if (Number.isFinite(Number(status))) return HEADLESS_EXIT_CODES.MODEL_ERROR;
41
+ const code = err.code || err.cause?.code;
42
+ if (
43
+ typeof code === "string" &&
44
+ /^(ECONNREFUSED|ECONNRESET|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|UND_ERR)/.test(
45
+ code,
46
+ )
47
+ ) {
48
+ return HEADLESS_EXIT_CODES.MODEL_ERROR;
49
+ }
50
+ if (typeof err.name === "string" && /LLM|Provider|Api/i.test(err.name)) {
51
+ return HEADLESS_EXIT_CODES.MODEL_ERROR;
52
+ }
53
+ return HEADLESS_EXIT_CODES.ERROR;
54
+ }
55
+
56
+ /**
57
+ * Map a finished run's endReason (+ error flag) to an exit code.
58
+ * @param {string} endReason "complete" | "max_turns" | "budget-exhausted" |
59
+ * "cost-budget-exhausted" | "no-response" | ...
60
+ * @param {boolean} isError
61
+ */
62
+ function exitCodeForEndReason(endReason, isError) {
63
+ if (!isError) return HEADLESS_EXIT_CODES.OK;
64
+ if (endReason === "max_turns" || endReason === "budget-exhausted") {
65
+ return HEADLESS_EXIT_CODES.MAX_TURNS;
66
+ }
67
+ if (endReason === "cost-budget-exhausted") {
68
+ return HEADLESS_EXIT_CODES.MAX_BUDGET;
69
+ }
70
+ return HEADLESS_EXIT_CODES.ERROR;
71
+ }
72
+
73
+ module.exports = {
74
+ HEADLESS_EXIT_CODES,
75
+ classifyLoopError,
76
+ exitCodeForEndReason,
77
+ };
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Deterministic-headless manifest helpers (gap-analysis 2026-07-11,
3
+ * docs/CLAUDE_CODE_CLI_GAP_ANALYSIS.md P0 "确定性 Headless").
4
+ *
5
+ * The stream-json init event carries a protocol version, whether this run
6
+ * persists its session, which ambient customization sources are live, and
7
+ * short digests of the resolved permission policy + tool surface — so a CI
8
+ * caller can assert "same run shape" across invocations without diffing the
9
+ * whole event stream.
10
+ *
11
+ * STREAM_PROTOCOL_VERSION mirrors packages/agent-sdk/src/protocol.ts
12
+ * PROTOCOL_VERSION — a BREAKING stream-json change must bump both in the same
13
+ * commit (see agent-sdk docs/PROTOCOL.md). Additive optional fields stay on
14
+ * the current version.
15
+ */
16
+
17
+ import crypto from "node:crypto";
18
+ import { createRequire } from "node:module";
19
+
20
+ export const STREAM_PROTOCOL_VERSION = 1;
21
+
22
+ const _require = createRequire(import.meta.url);
23
+
24
+ function shortHash(value) {
25
+ return crypto
26
+ .createHash("sha256")
27
+ .update(JSON.stringify(value))
28
+ .digest("hex")
29
+ .slice(0, 16);
30
+ }
31
+
32
+ const sortedList = (v) =>
33
+ [...new Set((Array.isArray(v) ? v : []).filter(Boolean))].sort();
34
+
35
+ // null = "no explicit list" (the default full tool set) — kept distinct from
36
+ // an explicit empty list so the digest can tell the two apart.
37
+ const sortedListOrNull = (v) => (Array.isArray(v) ? sortedList(v) : null);
38
+
39
+ /**
40
+ * Digest of the resolved permission policy: permission mode + allow/deny tool
41
+ * lists + settings permission rules. Stable across key order / duplicates so
42
+ * two runs with the same effective policy produce the same digest.
43
+ *
44
+ * @param {object} p
45
+ * @param {string} [p.permissionMode]
46
+ * @param {string[]} [p.allowedTools]
47
+ * @param {string[]} [p.disallowedTools]
48
+ * @param {{allow?:string[],ask?:string[],deny?:string[]}|null} [p.permissionRules]
49
+ * @returns {string} 16-hex-char sha256 prefix
50
+ */
51
+ export function computePolicyDigest({
52
+ permissionMode,
53
+ allowedTools,
54
+ disallowedTools,
55
+ permissionRules,
56
+ } = {}) {
57
+ const rules = permissionRules
58
+ ? {
59
+ allow: sortedList(permissionRules.allow),
60
+ ask: sortedList(permissionRules.ask),
61
+ deny: sortedList(permissionRules.deny),
62
+ }
63
+ : null;
64
+ return shortHash({
65
+ v: 1,
66
+ permissionMode: permissionMode || "default",
67
+ allowedTools: sortedListOrNull(allowedTools),
68
+ disallowedTools: sortedList(disallowedTools),
69
+ rules,
70
+ });
71
+ }
72
+
73
+ /**
74
+ * Digest of the tool surface exposed to the model (name set, order-independent).
75
+ * @param {string[]} toolNames
76
+ * @returns {string} 16-hex-char sha256 prefix
77
+ */
78
+ export function computeToolsHash(toolNames) {
79
+ return shortHash({ v: 1, tools: sortedListOrNull(toolNames) });
80
+ }
81
+
82
+ /**
83
+ * Which ambient customization sources are LIVE for this run. Reflects the
84
+ * kill-switch env vars (--safe-mode / --bare flip these) plus what the runner
85
+ * actually wired. Under `--bare` this collapses to at most
86
+ * ["settings-permissions"] (+ explicit MCP), which is the reproducibility
87
+ * signal CI wants.
88
+ *
89
+ * @param {object} p
90
+ * @param {object|null} [p.permissionRules] resolved settings permission rules
91
+ * @param {object|null} [p.settingsHooks] resolved settings hooks
92
+ * @param {boolean} [p.mcp] MCP surface enabled/connected
93
+ * @param {string[]} [p.enabledToolNames]
94
+ * @param {object} [p.env=process.env]
95
+ * @returns {string[]}
96
+ */
97
+ export function buildLoadedSources({
98
+ permissionRules = null,
99
+ settingsHooks = null,
100
+ mcp = false,
101
+ enabledToolNames = null,
102
+ env = process.env,
103
+ } = {}) {
104
+ const on = (k) => env[k] !== "0";
105
+ // null tool list = "default full set" → run_skill is available.
106
+ const skillToolAvailable =
107
+ !Array.isArray(enabledToolNames) || enabledToolNames.includes("run_skill");
108
+ const sources = [];
109
+ if (permissionRules) sources.push("settings-permissions");
110
+ if (settingsHooks) sources.push("settings-hooks");
111
+ if (on("CC_PROJECT_MEMORY")) sources.push("project-memory");
112
+ if (on("CC_MEMORY_INJECT")) sources.push("memory-recall");
113
+ if (on("CC_SKILLS") && skillToolAvailable) {
114
+ sources.push("skills");
115
+ }
116
+ if (on("CC_PLUGINS")) sources.push("plugins");
117
+ if (mcp) sources.push("mcp");
118
+ return sources;
119
+ }
120
+
121
+ /**
122
+ * Machine-readable capability manifest for `cc agent --capabilities`:
123
+ * everything a programmatic caller needs to know up front to drive this CLI
124
+ * (protocol version, tool names, permission modes, IO formats, exit codes,
125
+ * feature flags) without parsing --help text.
126
+ */
127
+ export function buildAgentCapabilities() {
128
+ let version = null;
129
+ try {
130
+ version = _require("../../package.json").version || null;
131
+ } catch {
132
+ /* manifest stays usable without a version */
133
+ }
134
+ const contract = _require("../runtime/coding-agent-contract-shared.cjs");
135
+ const { HEADLESS_EXIT_CODES } = _require("./exit-codes.cjs");
136
+ return {
137
+ name: "chainlesschain-cli",
138
+ version,
139
+ protocol_version: STREAM_PROTOCOL_VERSION,
140
+ agent_tools: contract.listCodingAgentToolNames(),
141
+ permission_modes: [
142
+ "default",
143
+ "manual",
144
+ "auto",
145
+ "dontAsk",
146
+ "plan",
147
+ "acceptEdits",
148
+ "bypassPermissions",
149
+ ],
150
+ output_formats: ["text", "json", "stream-json"],
151
+ input_formats: ["text", "stream-json"],
152
+ exit_codes: HEADLESS_EXIT_CODES,
153
+ features: {
154
+ bare: true,
155
+ safe_mode: true,
156
+ ephemeral: true,
157
+ background: true,
158
+ worktree: true,
159
+ checkpoint: true,
160
+ json_schema_output: true,
161
+ partial_messages: true,
162
+ interactive_approvals: true,
163
+ permission_prompt_tool: true,
164
+ remote_control: true,
165
+ mcp: {
166
+ config_file: true,
167
+ registry: true,
168
+ tool_search: true,
169
+ oauth: true,
170
+ managed_allowlist: true,
171
+ },
172
+ sandbox_engines: ["docker", "bubblewrap"],
173
+ },
174
+ };
175
+ }
@@ -117,6 +117,69 @@ function tryParseDecision(stdout) {
117
117
  * nonBlockingError?:boolean, error?:string }}
118
118
  */
119
119
  function runCommandHook(command, input = {}, opts = {}) {
120
+ if (!command) {
121
+ return { decision: HOOK_DECISIONS.CONTINUE, reason: null, exitCode: 0 };
122
+ }
123
+ // Circuit breaker (gap 2026-07-11 P2 "Hooks 硬化"): N consecutive
124
+ // non-blocking failures (spawn error / timeout / non-zero non-2 exit) of
125
+ // the SAME command trip an open state — further runs are skipped for a
126
+ // cooldown, then one half-open trial runs; success (or a real block/ask
127
+ // decision) closes it. CC_HOOK_BREAKER_THRESHOLD=0 disables.
128
+ const breaker = hookBreakerConfig();
129
+ if (breaker.threshold > 0) {
130
+ const st = _breaker.get(command);
131
+ if (st && st.fails >= breaker.threshold) {
132
+ const now = Date.now();
133
+ if (now - st.openedAt < breaker.cooldownMs) {
134
+ return {
135
+ decision: HOOK_DECISIONS.CONTINUE,
136
+ reason: `hook circuit breaker open (${st.fails} consecutive failures) — skipped for cooldown`,
137
+ exitCode: null,
138
+ skipped: true,
139
+ breakerOpen: true,
140
+ };
141
+ }
142
+ // half-open: let this one trial through; the outcome below decides.
143
+ }
144
+ }
145
+ const result = _runCommandHookInner(command, input, opts);
146
+ if (breaker.threshold > 0) {
147
+ if (result.nonBlockingError) {
148
+ const st = _breaker.get(command) || { fails: 0, openedAt: 0 };
149
+ st.fails += 1;
150
+ if (st.fails >= breaker.threshold) st.openedAt = Date.now();
151
+ _breaker.set(command, st);
152
+ } else {
153
+ _breaker.delete(command); // success / block / ask closes the breaker
154
+ }
155
+ }
156
+ return result;
157
+ }
158
+
159
+ // Payload schema version (gap 2026-07-11 P2): every hook's stdin JSON carries
160
+ // schema_version so hook scripts can branch on future payload changes.
161
+ // Additive-only changes keep the version; a breaking reshape must bump it.
162
+ const HOOK_PAYLOAD_SCHEMA_VERSION = 1;
163
+
164
+ // command → { fails, openedAt } (per process; hooks are per-run anyway)
165
+ const _breaker = new Map();
166
+
167
+ function hookBreakerConfig(env = process.env) {
168
+ const threshold = Number(env.CC_HOOK_BREAKER_THRESHOLD);
169
+ const cooldown = Number(env.CC_HOOK_BREAKER_COOLDOWN_MS);
170
+ return {
171
+ threshold:
172
+ Number.isFinite(threshold) && threshold >= 0 ? Math.floor(threshold) : 3,
173
+ cooldownMs: Number.isFinite(cooldown) && cooldown > 0 ? cooldown : 60000,
174
+ };
175
+ }
176
+
177
+ /** Test seam: clear breaker state between cases. */
178
+ function _resetHookBreaker() {
179
+ _breaker.clear();
180
+ }
181
+
182
+ function _runCommandHookInner(command, input = {}, opts = {}) {
120
183
  const { cwd, event } = opts;
121
184
  // spawnSync is SYNCHRONOUS — it blocks the whole agent until the hook returns
122
185
  // or its timeout fires. Node treats a 0/undefined timeout as UNLIMITED, so a
@@ -128,15 +191,15 @@ function runCommandHook(command, input = {}, opts = {}) {
128
191
  Number.isFinite(_rawTimeout) && _rawTimeout > 0
129
192
  ? Math.min(_rawTimeout, 600000)
130
193
  : 60000;
131
- if (!command) {
132
- return { decision: HOOK_DECISIONS.CONTINUE, reason: null, exitCode: 0 };
133
- }
134
194
  const shellKind = normalizeShellKind(opts.shell);
135
195
  const usePowershell = shellKind === "powershell" || shellKind === "pwsh";
136
196
  let res;
137
197
  try {
138
198
  const common = {
139
- input: JSON.stringify(input),
199
+ input: JSON.stringify({
200
+ ...input,
201
+ schema_version: input.schema_version ?? HOOK_PAYLOAD_SCHEMA_VERSION,
202
+ }),
140
203
  cwd: cwd || process.cwd(),
141
204
  encoding: "utf-8",
142
205
  timeout,
@@ -258,5 +321,8 @@ module.exports = {
258
321
  runHooks,
259
322
  tryParseDecision,
260
323
  HOOK_DECISIONS,
324
+ HOOK_PAYLOAD_SCHEMA_VERSION,
325
+ hookBreakerConfig,
326
+ _resetHookBreaker,
261
327
  _deps,
262
328
  };