chainlesschain 0.162.142 → 0.162.147

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 (192) hide show
  1. package/bin/chainlesschain.js +5 -3
  2. package/package.json +2 -2
  3. package/src/assets/web-panel/assets/{AIOps-Dc6fWgoa.js → AIOps-DywJ7xTv.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-DzMx25vJ.js → ActionButton-BTlqcY-q.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-eDC3KNht.js → Analytics-FdG1Mvwy.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-uA1TOtNp.js → AppLayout-CVsnbvN1.js} +3 -3
  7. package/src/assets/web-panel/assets/{Audit-Bq3QX9Cj.js → Audit-ITdpJ7vR.js} +1 -1
  8. package/src/assets/web-panel/assets/{Backup-B1V3Jfyv.js → Backup-BfXqnXo1.js} +1 -1
  9. package/src/assets/web-panel/assets/{BaseInput-DMFwddIF.js → BaseInput-C09ip3_E.js} +1 -1
  10. package/src/assets/web-panel/assets/{Chat-CKWQDH5j.js → Chat-qdkhB42l.js} +6 -6
  11. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CStSBCnk.js → ChatBubbleRenderer-D2KfETZD.js} +1 -1
  12. package/src/assets/web-panel/assets/{Checkbox-DepIzI7G.js → Checkbox-j6WZCuff.js} +1 -1
  13. package/src/assets/web-panel/assets/{Codegen-ClanWll2.js → Codegen-CGZ-K3uK.js} +1 -1
  14. package/src/assets/web-panel/assets/{Col-CFBzsL4W.js → Col-CIlPOAu6.js} +1 -1
  15. package/src/assets/web-panel/assets/{Community-CaAyZ_Pt.js → Community-sY-i-hic.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compact-3dCHRSVe.js → Compact-DcoEfyL8.js} +1 -1
  17. package/src/assets/web-panel/assets/Compliance-T8EtTecW.js +1 -0
  18. package/src/assets/web-panel/assets/{Cowork-DkJmdQaU.js → Cowork-DLY_fkLv.js} +3 -3
  19. package/src/assets/web-panel/assets/{Cron-DKfNf6J2.js → Cron-D049pZBC.js} +2 -2
  20. package/src/assets/web-panel/assets/{Crosschain-COu1aQIO.js → Crosschain-DCXdE8M9.js} +1 -1
  21. package/src/assets/web-panel/assets/{DID-CkAgFLkI.js → DID-JCvPZtjW.js} +2 -2
  22. package/src/assets/web-panel/assets/{Dashboard-CfaKWZCU.js → Dashboard-Cg2xu3iE.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dropdown-Cjw97sdb.js → Dropdown-DjJLjbLh.js} +1 -1
  24. package/src/assets/web-panel/assets/{EmailListRenderer-DROLQgOn.js → EmailListRenderer-DugNcSLR.js} +1 -1
  25. package/src/assets/web-panel/assets/{FamilyGuardDashboard-o_cYzlLq.js → FamilyGuardDashboard-J6Nb-E30.js} +1 -1
  26. package/src/assets/web-panel/assets/{Federation-Boex3ljt.js → Federation-CQ3Ttpbl.js} +1 -1
  27. package/src/assets/web-panel/assets/{FormItemContext-BQQ05T_3.js → FormItemContext-Cz0NKag4.js} +1 -1
  28. package/src/assets/web-panel/assets/{GenericCardRenderer-CdfL8PBd.js → GenericCardRenderer-NYcMP6iz.js} +1 -1
  29. package/src/assets/web-panel/assets/{Git-CfZ07qJy.js → Git-B4hYN18N.js} +2 -2
  30. package/src/assets/web-panel/assets/{Governance-B21ISudq.js → Governance-DOQPCcC0.js} +1 -1
  31. package/src/assets/web-panel/assets/{Inference-CGIK4Im1.js → Inference-HtLF746W.js} +1 -1
  32. package/src/assets/web-panel/assets/{KnowledgeGraph-CI1kmfdT.js → KnowledgeGraph-BXrk8rMm.js} +1 -1
  33. package/src/assets/web-panel/assets/{Logs-Dpc1-__h.js → Logs-DS0JnD3-.js} +2 -2
  34. package/src/assets/web-panel/assets/{Marketplace-BpMtMWyK.js → Marketplace-Cqnjihrz.js} +1 -1
  35. package/src/assets/web-panel/assets/{McpTools-B94WvOOS.js → McpTools-Cx3Psk-U.js} +5 -5
  36. package/src/assets/web-panel/assets/{Memory-DODsAxZf.js → Memory-BjyysPtj.js} +2 -2
  37. package/src/assets/web-panel/assets/{MobileBridge-C3Bjshem.js → MobileBridge-CYaITZ7w.js} +1 -1
  38. package/src/assets/web-panel/assets/{MobileProjects-Bi8IHDv_.js → MobileProjects-Xwf-fdOQ.js} +1 -1
  39. package/src/assets/web-panel/assets/{Mtc-CH5BSuWG.js → Mtc-Djkql5m5.js} +2 -2
  40. package/src/assets/web-panel/assets/{MtcAudit-DN3MzNtL.js → MtcAudit-D2BAZlu0.js} +2 -2
  41. package/src/assets/web-panel/assets/{Multisig-CO6GWQOh.js → Multisig-ByqP1ZzH.js} +3 -3
  42. package/src/assets/web-panel/assets/{NLProgramming-DhIr3V_u.js → NLProgramming-C_kn0nE2.js} +1 -1
  43. package/src/assets/web-panel/assets/{Notes-CNgrvZCJ.js → Notes-7l7eXHPq.js} +3 -3
  44. package/src/assets/web-panel/assets/{NotificationSettings-Crr4LPjQ.js → NotificationSettings-BrCDoitw.js} +1 -1
  45. package/src/assets/web-panel/assets/{OrderTableRenderer-C8q-kK2z.js → OrderTableRenderer-CmtqIdwr.js} +1 -1
  46. package/src/assets/web-panel/assets/{Organization-hWhCmeUn.js → Organization-DdoNAxsM.js} +4 -4
  47. package/src/assets/web-panel/assets/{Overflow-t_EBxns-.js → Overflow-Bw7R6dE3.js} +1 -1
  48. package/src/assets/web-panel/assets/{P2P-B8fqfwis.js → P2P-DJNtBugA.js} +2 -2
  49. package/src/assets/web-panel/assets/{PdhVaultBrowser-d-wlJw3R.js → PdhVaultBrowser-Dwhd5_ue.js} +3 -3
  50. package/src/assets/web-panel/assets/{Permissions-Ch13YpBo.js → Permissions-BYoSfhPR.js} +4 -4
  51. package/src/assets/web-panel/assets/{PersonalDataHub-DW7LlLbS.js → PersonalDataHub-aYCYJbnH.js} +2 -2
  52. package/src/assets/web-panel/assets/{Pipeline-BHh3HqOQ.js → Pipeline-DJp6CprF.js} +1 -1
  53. package/src/assets/web-panel/assets/{Privacy-DAhlJDSC.js → Privacy-Cq9HZweR.js} +1 -1
  54. package/src/assets/web-panel/assets/{ProjectInit-BRtddAM_.js → ProjectInit-CmF0HsSt.js} +2 -2
  55. package/src/assets/web-panel/assets/{ProjectSettings-CzR1EC7b.js → ProjectSettings-P8V5Aea2.js} +2 -2
  56. package/src/assets/web-panel/assets/{Projects-DSavX602.js → Projects-DPd5-lNS.js} +1 -1
  57. package/src/assets/web-panel/assets/{Providers-D55_kGZs.js → Providers-D589v0q2.js} +1 -1
  58. package/src/assets/web-panel/assets/{QuickAsk-BR5qDIaT.js → QuickAsk-lvRV2osB.js} +1 -1
  59. package/src/assets/web-panel/assets/{Recommend-CTFAM97K.js → Recommend-sb84MTpY.js} +1 -1
  60. package/src/assets/web-panel/assets/{Reputation-Z7pvEha9.js → Reputation-d9Y3vy-W.js} +1 -1
  61. package/src/assets/web-panel/assets/{Row-MqKcIsjP.js → Row-DMdhLUTp.js} +1 -1
  62. package/src/assets/web-panel/assets/{RssFeed-DdLDjRpk.js → RssFeed-OL-SMDkz.js} +3 -3
  63. package/src/assets/web-panel/assets/{Search-CeOzh2hz.js → Search-BjStLKq1.js} +1 -1
  64. package/src/assets/web-panel/assets/{Security-gHrXaf4o.js → Security-MeI411qr.js} +3 -3
  65. package/src/assets/web-panel/assets/{Services-B8Y7pmY1.js → Services-UajosuhT.js} +2 -2
  66. package/src/assets/web-panel/assets/{Skeleton-CRS0WNut.js → Skeleton-CGpG-QJ1.js} +1 -1
  67. package/src/assets/web-panel/assets/{Skills--a5_Pdxx.js → Skills-C6P2RPY-.js} +1 -1
  68. package/src/assets/web-panel/assets/{Sla-EcTyL6EM.js → Sla-B_fPprgw.js} +1 -1
  69. package/src/assets/web-panel/assets/{SpeechSettings-QpfvOiJr.js → SpeechSettings-CqxLjSlQ.js} +1 -1
  70. package/src/assets/web-panel/assets/{SyncSettings-DaLBBzlE.js → SyncSettings-DixSfFOQ.js} +2 -2
  71. package/src/assets/web-panel/assets/{Tasks-Ca6xvO-I.js → Tasks-PavzPBxc.js} +1 -1
  72. package/src/assets/web-panel/assets/{Templates-CWguiCoE.js → Templates-BYdGRnfX.js} +1 -1
  73. package/src/assets/web-panel/assets/{Tenant-BGLLZulu.js → Tenant-BQLgnarD.js} +1 -1
  74. package/src/assets/web-panel/assets/Terminal-BzOHpq-B.js +3 -0
  75. package/src/assets/web-panel/assets/{TimelineRenderer-CLjvjbmr.js → TimelineRenderer-Bv7uZRM7.js} +1 -1
  76. package/src/assets/web-panel/assets/{Tokens-De5mmw_a.js → Tokens-DxhgszRJ.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trigger-Chbq6U0N.js → Trigger-nQYHayuc.js} +1 -1
  78. package/src/assets/web-panel/assets/{Trust-CYxUO2lv.js → Trust-CvqZDuZ4.js} +1 -1
  79. package/src/assets/web-panel/assets/{UkeySign-iyoZXXF6.js → UkeySign-DaR8GV2I.js} +1 -1
  80. package/src/assets/web-panel/assets/{VideoEditing-CHg3ifJs.js → VideoEditing-C6Mpsokw.js} +1 -1
  81. package/src/assets/web-panel/assets/{Wallet-DfjqObsO.js → Wallet-B57VRrWc.js} +4 -4
  82. package/src/assets/web-panel/assets/{WebAuthn-0FI6xfud.js → WebAuthn-BbtEEtpq.js} +4 -4
  83. package/src/assets/web-panel/assets/{WorkflowEditor-CJgGcctS.js → WorkflowEditor-C5LwvKmH.js} +1 -1
  84. package/src/assets/web-panel/assets/{chat-fH-E4vhY.js → chat-MzuPR5jh.js} +1 -1
  85. package/src/assets/web-panel/assets/{colors-CuxsOD6d.js → colors-HyW8mi_y.js} +1 -1
  86. package/src/assets/web-panel/assets/{compact-item-AfVcFrFU.js → compact-item-DvUSzWAp.js} +1 -1
  87. package/src/assets/web-panel/assets/{createContext-DTsD7sPO.js → createContext-4MppZl7X.js} +1 -1
  88. package/src/assets/web-panel/assets/devWarning-6YJJMjSH.js +1 -0
  89. package/src/assets/web-panel/assets/{hasIn-DJR763M6.js → hasIn-D9q3CJ-u.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-DhM9FZUH.js → index--Bm6OqmU.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-OZwLoscJ.js → index-B5E4DhVc.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-CF8OSTin.js → index-B994UQfE.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-9cryBBzh.js → index-BF2JsbiX.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-BMVULNUC.js → index-BFGfC5RS.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-Dj1_lz3M.js → index-BGQtFso0.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-DBPAH7rp.js → index-BcunsBIx.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-mLAfJXAf.js → index-Bl-E4W4q.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-Cfppli6E.js → index-BmCo2Xdp.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-_hP8-Y2F.js → index-Btz8pU68.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DvlzcX3Q.js → index-C0DiL97c.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CBBStCbO.js → index-C9I5MuJ0.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-SuJ2_OzM.js → index-CLAZs1Vd.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-B8DLT-F4.js → index-Caqtu6Et.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-Cm4cHE5_.js → index-CgIfXOD9.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DbQX0Zbf.js → index-Ci6adKh8.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BEGMojUg.js → index-CifKUNtV.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CdV7a6-K.js → index-Cs-RKRUM.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-Dswd93W4.js → index-CucRo4Vz.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-D2KdGSMa.js → index-CvAqCAo9.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-BqflTl_b.js → index-D2jrnaq3.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Cl7chKuM.js → index-D7TDMMfu.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CAbZ3dW9.js → index-DDZ6fP5Z.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-BD4-kWQY.js → index-DFqvWeGc.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-DejUsZOw.js → index-DNYqw0MO.js} +1 -1
  115. package/src/assets/web-panel/assets/index-D_oeGLr6.js +1 -0
  116. package/src/assets/web-panel/assets/{index-DH-dA4pG.js → index-DcMkjb92.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-CMcYdHhK.js → index-DmZ6-suL.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-85iIi3qF.js → index-Dv7ffI2g.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DJIFQ-Ay.js → index-Dvg5xYuP.js} +1 -1
  120. package/src/assets/web-panel/assets/index-Qa57YG19.js +1 -0
  121. package/src/assets/web-panel/assets/{index-DxD03TL7.js → index-SDblbKj6.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-CmUxSTNy.js → index-UUMCA2Sq.js} +3 -3
  123. package/src/assets/web-panel/assets/{index-BJl6Ob-o.js → index-Zvtru2oE.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-DPHYW3IK.js → index-eOQO6KnV.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-B-rcowKd.js → index-m0b6Fj9q.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-BTtL2Qkb.js → index-ryFLxB1p.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-fj3ZXgiy.js → index-xvUVuFnA.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-BbpuFc8C.js → index-zLwYpvX0.js} +1 -1
  129. package/src/assets/web-panel/assets/{initDefaultProps-BGoLJP6T.js → initDefaultProps-CC--PMsC.js} +1 -1
  130. package/src/assets/web-panel/assets/{motion-23xdoVi0.js → motion-DdrBjRF1.js} +1 -1
  131. package/src/assets/web-panel/assets/{move-C3Bnnft-.js → move-D712Gwl2.js} +1 -1
  132. package/src/assets/web-panel/assets/{mtc-parser-Dv0C3a90.js → mtc-parser-39y-keKO.js} +1 -1
  133. package/src/assets/web-panel/assets/{omit-C3r9qzDO.js → omit-6Y51gDB9.js} +1 -1
  134. package/src/assets/web-panel/assets/{pickAttrs-B4bJOcNp.js → pickAttrs-Cy6EHbq9.js} +1 -1
  135. package/src/assets/web-panel/assets/{placementArrow-Dxf48wcO.js → placementArrow-BYxdAm0p.js} +1 -1
  136. package/src/assets/web-panel/assets/{responsiveObserve-Rq9U2hbz.js → responsiveObserve-C9R6q_cr.js} +1 -1
  137. package/src/assets/web-panel/assets/{slide-BeWiqbdF.js → slide-CXUJlilB.js} +1 -1
  138. package/src/assets/web-panel/assets/{statusUtils-yX9N1cEc.js → statusUtils-DOtrOeql.js} +1 -1
  139. package/src/assets/web-panel/assets/{styleChecker-5Ko9NorO.js → styleChecker-D5r39o92.js} +1 -1
  140. package/src/assets/web-panel/assets/{useFlexGapSupport-WKpkTLok.js → useFlexGapSupport-DfAD5U4Z.js} +1 -1
  141. package/src/assets/web-panel/assets/{useFs-BV4X0JOG.js → useFs-BkrT-Zbc.js} +1 -1
  142. package/src/assets/web-panel/assets/{usePersonalDataHub-g41wq_2p.js → usePersonalDataHub-BwnnnZjU.js} +1 -1
  143. package/src/assets/web-panel/assets/{vnode-BAurbOH8.js → vnode-SU-N4pum.js} +1 -1
  144. package/src/assets/web-panel/assets/{zoom-Cl5XniRg.js → zoom-DHScfpTP.js} +1 -1
  145. package/src/assets/web-panel/index.html +1 -1
  146. package/src/auth/npm-auth.js +65 -0
  147. package/src/command-manifest.json +1126 -0
  148. package/src/commands/agent.js +92 -0
  149. package/src/commands/agents.js +68 -0
  150. package/src/commands/note.js +2 -1
  151. package/src/commands/permissions.js +66 -10
  152. package/src/commands/plugin.js +50 -3
  153. package/src/commands/search.js +2 -1
  154. package/src/harness/worktree-isolator.js +14 -7
  155. package/src/index.js +4 -12
  156. package/src/lazy-dispatch.js +107 -0
  157. package/src/lib/agent-sandbox.js +72 -0
  158. package/src/lib/background-agent-supervisor.js +197 -0
  159. package/src/lib/code-review.js +72 -0
  160. package/src/lib/ensure-utf8.js +25 -7
  161. package/src/lib/knowledge-exporter.js +3 -2
  162. package/src/lib/permission-engine.js +1 -1
  163. package/src/lib/plugin-security.js +128 -0
  164. package/src/lib/pr-create.js +108 -0
  165. package/src/lib/publish-workspace.js +107 -0
  166. package/src/lib/resume-session.js +111 -0
  167. package/src/lib/session-manager.js +2 -1
  168. package/src/lib/settings-hooks.cjs +68 -25
  169. package/src/lib/settings-loader.cjs +115 -2
  170. package/src/lib/task-list.js +53 -0
  171. package/src/program-base.js +24 -0
  172. package/src/repl/agent-repl.js +108 -3
  173. package/src/repl/agents-status.js +8 -2
  174. package/src/repl/btw-command.js +56 -0
  175. package/src/repl/chat-repl.js +52 -1
  176. package/src/repl/hooks-status.js +1 -2
  177. package/src/repl/slash-command-registry.js +197 -0
  178. package/src/repl/think-command.js +31 -0
  179. package/src/runtime/agent-core.js +137 -1
  180. package/src/runtime/coding-agent-contract-shared.cjs +37 -0
  181. package/src/runtime/coding-agent-policy.cjs +14 -0
  182. package/src/runtime/file-ref-expander.js +100 -14
  183. package/src/runtime/headless-runner.js +26 -6
  184. package/src/runtime/headless-stream.js +25 -6
  185. package/src/runtime/mcp-config.js +46 -2
  186. package/src/runtime/policies/agent-policy.js +1 -0
  187. package/src/workers/background-agent-worker.js +60 -0
  188. package/src/assets/web-panel/assets/Compliance-BKYHHTdq.js +0 -1
  189. package/src/assets/web-panel/assets/Terminal-Bryzb1Rm.js +0 -3
  190. package/src/assets/web-panel/assets/devWarning-DffbPlEe.js +0 -1
  191. package/src/assets/web-panel/assets/index-BAHgOh3h.js +0 -1
  192. package/src/assets/web-panel/assets/index-zfkDb9FC.js +0 -1
@@ -0,0 +1,72 @@
1
+ /** OS-isolated shell execution for the coding agent. */
2
+ import { spawnSync } from "node:child_process";
3
+ import path from "node:path";
4
+
5
+ export const DEFAULT_SANDBOX_IMAGE = "node:22-bookworm-slim";
6
+ export const _deps = { spawnSync };
7
+
8
+ export function normalizeAgentSandbox(value, options = {}) {
9
+ if (!value) return null;
10
+ const image =
11
+ typeof value === "string" && value.trim() && value !== "true"
12
+ ? value.trim()
13
+ : DEFAULT_SANDBOX_IMAGE;
14
+ return {
15
+ engine: "docker",
16
+ image,
17
+ cwd: path.resolve(options.cwd || process.cwd()),
18
+ network: options.network === true,
19
+ };
20
+ }
21
+
22
+ export function executeSandboxedShell(command, sandbox, options = {}) {
23
+ if (!sandbox || sandbox.engine !== "docker") {
24
+ throw new Error("A supported agent sandbox configuration is required");
25
+ }
26
+ const hostCwd = path.resolve(options.cwd || sandbox.cwd);
27
+ const args = ["run", "--rm", "--init"];
28
+ if (!sandbox.network) args.push("--network", "none");
29
+ args.push("--mount", `type=bind,source=${hostCwd},target=/workspace`);
30
+ args.push("--workdir", "/workspace");
31
+ if (process.platform !== "win32" && process.getuid && process.getgid) {
32
+ args.push("--user", `${process.getuid()}:${process.getgid()}`);
33
+ }
34
+ const env = options.env || {};
35
+ for (const key of ["CLAUDECODE", "CC_SESSION_ID", "CLAUDE_CODE_SESSION_ID"]) {
36
+ if (env[key] != null) args.push("--env", `${key}=${env[key]}`);
37
+ }
38
+ args.push(sandbox.image, "sh", "-lc", String(command || ""));
39
+ const result = _deps.spawnSync("docker", args, {
40
+ encoding: "utf8",
41
+ timeout: options.timeout,
42
+ maxBuffer: options.maxBuffer,
43
+ windowsHide: true,
44
+ });
45
+ if (result.error) {
46
+ return {
47
+ stdout: result.stdout || "",
48
+ stderr:
49
+ result.error.code === "ENOENT"
50
+ ? "Docker is not installed"
51
+ : result.error.message,
52
+ exitCode: typeof result.status === "number" ? result.status : 1,
53
+ failedToStart: true,
54
+ };
55
+ }
56
+ return {
57
+ stdout: result.stdout || "",
58
+ stderr: result.stderr || "",
59
+ exitCode: typeof result.status === "number" ? result.status : 1,
60
+ signal: result.signal || null,
61
+ };
62
+ }
63
+
64
+ export function sandboxSummary(sandbox) {
65
+ if (!sandbox) return null;
66
+ return {
67
+ engine: sandbox.engine,
68
+ image: sandbox.image,
69
+ network: sandbox.network ? "enabled" : "disabled",
70
+ workspace: "read-write",
71
+ };
72
+ }
@@ -0,0 +1,197 @@
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ import {
3
+ closeSync,
4
+ existsSync,
5
+ mkdirSync,
6
+ openSync,
7
+ readFileSync,
8
+ readdirSync,
9
+ renameSync,
10
+ rmSync,
11
+ writeFileSync,
12
+ } from "node:fs";
13
+ import { randomBytes } from "node:crypto";
14
+ import { join } from "node:path";
15
+ import { fileURLToPath } from "node:url";
16
+ import { getHomeDir } from "./paths.js";
17
+
18
+ export const _deps = { spawn, spawnSync };
19
+
20
+ export function backgroundAgentsDir() {
21
+ const dir =
22
+ process.env.CC_BACKGROUND_AGENTS_DIR ||
23
+ join(getHomeDir(), "background-agents");
24
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
25
+ return dir;
26
+ }
27
+
28
+ export function createBackgroundAgentId() {
29
+ return `bg-${Date.now()}-${randomBytes(3).toString("hex")}`;
30
+ }
31
+
32
+ function safeId(id) {
33
+ if (!/^bg-[a-zA-Z0-9-]+$/.test(String(id || ""))) {
34
+ throw new Error(`Invalid background agent id: ${id}`);
35
+ }
36
+ return String(id);
37
+ }
38
+
39
+ export function statePath(id) {
40
+ return join(backgroundAgentsDir(), `${safeId(id)}.json`);
41
+ }
42
+
43
+ export function logPath(id) {
44
+ return join(backgroundAgentsDir(), `${safeId(id)}.log`);
45
+ }
46
+
47
+ export function writeBackgroundAgentState(state) {
48
+ const target = statePath(state.id);
49
+ const tmp = `${target}.${process.pid}.tmp`;
50
+ writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 0o600 });
51
+ renameSync(tmp, target);
52
+ return state;
53
+ }
54
+
55
+ export function readBackgroundAgentState(id) {
56
+ const file = statePath(id);
57
+ if (!existsSync(file)) return null;
58
+ try {
59
+ return JSON.parse(readFileSync(file, "utf8"));
60
+ } catch {
61
+ return null;
62
+ }
63
+ }
64
+
65
+ export function isProcessAlive(pid) {
66
+ if (!Number.isInteger(Number(pid)) || Number(pid) <= 0) return false;
67
+ try {
68
+ process.kill(Number(pid), 0);
69
+ return true;
70
+ } catch (error) {
71
+ if (error?.code === "EPERM") return true;
72
+ return false;
73
+ }
74
+ }
75
+
76
+ export function effectiveBackgroundAgentState(state) {
77
+ if (!state) return null;
78
+ if (state.status === "running" && !isProcessAlive(state.pid)) {
79
+ return { ...state, status: "lost", endedAt: state.endedAt || Date.now() };
80
+ }
81
+ return state;
82
+ }
83
+
84
+ export function listBackgroundAgents(options = {}) {
85
+ return readdirSync(backgroundAgentsDir())
86
+ .filter((name) => name.endsWith(".json") && !name.includes(".job."))
87
+ .map((name) => readBackgroundAgentState(name.slice(0, -5)))
88
+ .filter(Boolean)
89
+ .map(effectiveBackgroundAgentState)
90
+ .filter((state) => options.all || state.status === "running")
91
+ .sort((a, b) => (b.startedAt || 0) - (a.startedAt || 0));
92
+ }
93
+
94
+ export function launchBackgroundAgent({
95
+ argv,
96
+ cwd,
97
+ sessionId,
98
+ title,
99
+ cliEntry,
100
+ }) {
101
+ const id = createBackgroundAgentId();
102
+ const dir = backgroundAgentsDir();
103
+ const jobFile = join(dir, `${id}.job.${process.pid}.json`);
104
+ const worker = fileURLToPath(
105
+ new URL("../workers/background-agent-worker.js", import.meta.url),
106
+ );
107
+ const job = {
108
+ id,
109
+ argv,
110
+ cwd,
111
+ sessionId,
112
+ title: title || "Background agent",
113
+ cliEntry: cliEntry || process.argv[1],
114
+ logFile: logPath(id),
115
+ };
116
+ writeFileSync(jobFile, JSON.stringify(job), { mode: 0o600 });
117
+ let child;
118
+ try {
119
+ child = _deps.spawn(process.execPath, [worker, jobFile], {
120
+ cwd,
121
+ detached: true,
122
+ stdio: "ignore",
123
+ windowsHide: true,
124
+ });
125
+ } catch (error) {
126
+ rmSync(jobFile, { force: true });
127
+ throw error;
128
+ }
129
+ child.unref();
130
+ const state = {
131
+ id,
132
+ sessionId,
133
+ title: job.title,
134
+ cwd,
135
+ pid: child.pid,
136
+ status: "running",
137
+ startedAt: Date.now(),
138
+ endedAt: null,
139
+ exitCode: null,
140
+ logFile: job.logFile,
141
+ };
142
+ writeBackgroundAgentState(state);
143
+ return state;
144
+ }
145
+
146
+ export function readBackgroundAgentLog(id, options = {}) {
147
+ const file = logPath(id);
148
+ if (!existsSync(file)) return "";
149
+ const text = readFileSync(file, "utf8");
150
+ const lines = text.split(/\r?\n/);
151
+ const limit = Math.max(1, Number(options.lines) || 100);
152
+ return lines.slice(-limit).join("\n");
153
+ }
154
+
155
+ export function stopBackgroundAgent(id) {
156
+ const state = effectiveBackgroundAgentState(readBackgroundAgentState(id));
157
+ if (!state) throw new Error(`Background agent not found: ${id}`);
158
+ if (state.status !== "running") return { ...state, stopped: false };
159
+ if (process.platform === "win32") {
160
+ const killed = _deps.spawnSync(
161
+ "taskkill",
162
+ ["/PID", String(state.pid), "/T", "/F"],
163
+ {
164
+ windowsHide: true,
165
+ encoding: "utf8",
166
+ },
167
+ );
168
+ if (killed.error || (killed.status !== 0 && isProcessAlive(state.pid))) {
169
+ throw new Error(
170
+ `Failed to stop background agent ${id}: ${killed.error?.message || killed.stderr || `taskkill exited ${killed.status}`}`,
171
+ );
172
+ }
173
+ } else {
174
+ try {
175
+ process.kill(-Number(state.pid), "SIGTERM");
176
+ } catch {
177
+ process.kill(Number(state.pid), "SIGTERM");
178
+ }
179
+ }
180
+ const next = {
181
+ ...state,
182
+ status: "stopped",
183
+ endedAt: Date.now(),
184
+ stoppedByUser: true,
185
+ };
186
+ writeBackgroundAgentState(next);
187
+ return { ...next, stopped: true };
188
+ }
189
+
190
+ export function openBackgroundLogFile(id) {
191
+ const fd = openSync(logPath(id), "a", 0o600);
192
+ return { fd, close: () => closeSync(fd) };
193
+ }
194
+
195
+ export function removeJobFile(file) {
196
+ rmSync(file, { force: true });
197
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Code review utilities (ESM)
3
+ * Mirrors Claude Code /review behavior
4
+ * @module lib/code-review
5
+ */
6
+
7
+ import { execSync } from "child_process";
8
+ import { logger } from "./logger.js";
9
+
10
+ /**
11
+ * Get git diff for review
12
+ * @param {string} target - Diff target (default: staged changes)
13
+ * @returns {string} diff output
14
+ */
15
+ export function getGitDiff(target = "--staged") {
16
+ try {
17
+ return execSync(`git diff ${target}`, {
18
+ encoding: "utf8",
19
+ maxBuffer: 10 * 1024 * 1024,
20
+ });
21
+ } catch (err) {
22
+ logger.error(`Failed to get git diff: ${err.message}`);
23
+ return "";
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Get list of changed files
29
+ * @param {string} target - Diff target
30
+ * @returns {string[]} changed files
31
+ */
32
+ export function getChangedFiles(target = "--staged") {
33
+ try {
34
+ const output = execSync(`git diff ${target} --name-only`, {
35
+ encoding: "utf8",
36
+ });
37
+ return output.trim().split("\n").filter(Boolean);
38
+ } catch (err) {
39
+ logger.error(`Failed to get changed files: ${err.message}`);
40
+ return [];
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Run code review on current changes
46
+ * @param {Object} options Review options
47
+ * @returns {Promise<Object>} review result
48
+ */
49
+ export async function runCodeReview(options = {}) {
50
+ const target = options.target || "--staged";
51
+ const diff = getGitDiff(target);
52
+ const files = getChangedFiles(target);
53
+
54
+ if (files.length === 0) {
55
+ logger.info("No changed files to review");
56
+ return { reviewed: false, files: [], issues: [] };
57
+ }
58
+
59
+ logger.info(`Reviewing ${files.length} changed files...`);
60
+ files.forEach((f) => logger.info(` - ${f}`));
61
+
62
+ return {
63
+ reviewed: true,
64
+ files,
65
+ diffSize: diff.length,
66
+ // Actual AI review will be handled by agent core
67
+ message:
68
+ "Code review request prepared. AI will analyze changes for bugs, security issues, and style problems.",
69
+ };
70
+ }
71
+
72
+ export default { getGitDiff, getChangedFiles, runCodeReview };
@@ -9,20 +9,38 @@
9
9
 
10
10
  import { execSync } from "child_process";
11
11
 
12
+ // Injectable so tests can assert whether the (expensive) chcp spawn happened
13
+ // without actually spawning cmd.exe. See cli-dev.md `_deps` pattern.
14
+ export const _deps = { execSync };
15
+
12
16
  /**
13
17
  * Call this as early as possible in the process entry point.
14
18
  */
15
19
  export function ensureUtf8() {
16
20
  if (process.platform !== "win32") return;
17
21
 
18
- // Set Windows console codepage to UTF-8
19
- try {
20
- execSync("chcp 65001", { stdio: "ignore" });
21
- } catch (_err) {
22
- // Ignore - may fail in non-interactive environments
22
+ // Setting the Windows console codepage to UTF-8 requires spawning cmd.exe
23
+ // (`chcp 65001`), which costs ~280ms per invocation — a Windows process
24
+ // creation, paid on EVERY `cc` run. But the codepage only affects how an
25
+ // *interactive console* renders bytes; when stdout/stderr are piped or
26
+ // redirected (JSON output, headless runs, `cc` spawned as a subprocess by the
27
+ // desktop app / Android / spawnSync test harnesses), the bytes flow through
28
+ // the pipe as raw UTF-8 regardless of codepage, so the spawn is pure startup
29
+ // tax. Only pay it when a console is actually attached — the 乱码 fix is
30
+ // preserved exactly where it matters. Set CC_FORCE_CHCP=1 to restore the
31
+ // unconditional old behavior if some console edge case needs it.
32
+ const consoleAttached =
33
+ Boolean(process.stdout && process.stdout.isTTY) ||
34
+ Boolean(process.stderr && process.stderr.isTTY);
35
+ if (consoleAttached || process.env.CC_FORCE_CHCP === "1") {
36
+ try {
37
+ _deps.execSync("chcp 65001", { stdio: "ignore" });
38
+ } catch (_err) {
39
+ // Ignore - may fail in non-interactive environments
40
+ }
23
41
  }
24
42
 
25
- // Ensure stdout/stderr use UTF-8 encoding
43
+ // Ensure stdout/stderr use UTF-8 encoding (cheap; always)
26
44
  if (process.stdout.setDefaultEncoding) {
27
45
  process.stdout.setDefaultEncoding("utf8");
28
46
  }
@@ -30,7 +48,7 @@ export function ensureUtf8() {
30
48
  process.stderr.setDefaultEncoding("utf8");
31
49
  }
32
50
 
33
- // Set environment variable so child processes inherit UTF-8
51
+ // Set environment variable so child processes inherit UTF-8 (always)
34
52
  process.env.PYTHONIOENCODING = "utf-8";
35
53
  process.env.LANG = "en_US.UTF-8";
36
54
  }
@@ -4,6 +4,7 @@
4
4
 
5
5
  import { writeFileSync, mkdirSync, existsSync } from "fs";
6
6
  import { join } from "path";
7
+ import { safeJsonParse } from "./safe-json.js";
7
8
 
8
9
  /**
9
10
  * Ensure the notes table exists
@@ -50,7 +51,7 @@ export function fetchNotes(db, { category, tag, limit } = {}) {
50
51
  if (tag) {
51
52
  notes = notes.filter((n) => {
52
53
  try {
53
- const tags = JSON.parse(n.tags || "[]");
54
+ const tags = safeJsonParse(n.tags, []);
54
55
  return tags.includes(tag);
55
56
  } catch {
56
57
  return false;
@@ -173,7 +174,7 @@ export function generateIndexHtml(
173
174
  ) {
174
175
  const noteLinks = notes
175
176
  .map((n) => {
176
- const tags = JSON.parse(n.tags || "[]");
177
+ const tags = safeJsonParse(n.tags, []);
177
178
  const tagsHtml = tags
178
179
  .map((t) => `<span class="tag">${escapeHtml(t)}</span>`)
179
180
  .join(" ");
@@ -308,7 +308,7 @@ export function getUserPermissions(db, userDid) {
308
308
  .prepare("SELECT permissions FROM rbac_roles WHERE name = ?")
309
309
  .get(grant.role_name);
310
310
  if (role) {
311
- const perms = JSON.parse(role.permissions || "[]");
311
+ const perms = safeJsonParse(role.permissions, []);
312
312
  for (const p of perms) allPerms.add(p);
313
313
  }
314
314
  }
@@ -0,0 +1,128 @@
1
+ import { createHash, createPublicKey, verify } from "node:crypto";
2
+ import { readFileSync } from "node:fs";
3
+ import { loadManagedSettings } from "./settings-loader.cjs";
4
+
5
+ function stringSet(value) {
6
+ return Array.isArray(value)
7
+ ? new Set(
8
+ value
9
+ .map((v) =>
10
+ typeof v === "string" ? v : v?.name || v?.source || v?.url || null,
11
+ )
12
+ .map((v) => String(v || "").trim())
13
+ .filter(Boolean),
14
+ )
15
+ : null;
16
+ }
17
+
18
+ export function enforcePluginPolicy(
19
+ { name, source = null, action = "install" },
20
+ managed,
21
+ ) {
22
+ if (!managed) return { allowed: true };
23
+ const denied = stringSet(managed.deniedPlugins) || new Set();
24
+ const allowed = stringSet(managed.allowedPlugins);
25
+ if (denied.has(name)) {
26
+ throw new Error(`plugin "${name}" is denied by managed settings`);
27
+ }
28
+ if (allowed && !allowed.has(name)) {
29
+ throw new Error(`plugin "${name}" is not in the managed allowlist`);
30
+ }
31
+
32
+ const blockedSources = new Set([
33
+ ...(stringSet(managed.blockedPluginSources) || []),
34
+ ...(stringSet(managed.blockedMarketplaces) || []),
35
+ ]);
36
+ const allowedSources = stringSet(managed.allowedPluginSources);
37
+ if (source && blockedSources.has(source)) {
38
+ throw new Error(`plugin source "${source}" is blocked by managed settings`);
39
+ }
40
+ if (action === "install" && allowedSources) {
41
+ if (!source) {
42
+ throw new Error(
43
+ "managed settings require --source for plugin installation",
44
+ );
45
+ }
46
+ if (!allowedSources.has(source)) {
47
+ throw new Error(
48
+ `plugin source "${source}" is not in the managed allowlist`,
49
+ );
50
+ }
51
+ }
52
+ return { allowed: true };
53
+ }
54
+
55
+ export function verifyPluginManifest({
56
+ manifestFile,
57
+ expectedSha256,
58
+ signatureFile,
59
+ publicKeyFile,
60
+ requireSignature = false,
61
+ trustedKeySha256 = null,
62
+ requireTrustedKey = false,
63
+ }) {
64
+ if (!manifestFile) {
65
+ if (requireSignature) {
66
+ throw new Error("managed settings require a signed plugin manifest");
67
+ }
68
+ return null;
69
+ }
70
+ const bytes = readFileSync(manifestFile);
71
+ const sha256 = createHash("sha256").update(bytes).digest("hex");
72
+ if (
73
+ expectedSha256 &&
74
+ sha256.toLowerCase() !== String(expectedSha256).toLowerCase()
75
+ ) {
76
+ throw new Error(
77
+ `plugin manifest SHA-256 mismatch (expected ${expectedSha256}, got ${sha256})`,
78
+ );
79
+ }
80
+
81
+ const wantsSignature = requireSignature || signatureFile || publicKeyFile;
82
+ let signatureVerified = false;
83
+ if (wantsSignature) {
84
+ if (!signatureFile || !publicKeyFile) {
85
+ throw new Error(
86
+ "plugin signature verification requires --signature and --public-key",
87
+ );
88
+ }
89
+ const signature = readFileSync(signatureFile);
90
+ const publicKey = readFileSync(publicKeyFile, "utf8");
91
+ const keyObject = createPublicKey(publicKey);
92
+ const publicKeySha256 = createHash("sha256")
93
+ .update(keyObject.export({ type: "spki", format: "der" }))
94
+ .digest("hex");
95
+ const trusted = stringSet(trustedKeySha256);
96
+ if (requireTrustedKey && (!trusted || trusted.size === 0)) {
97
+ throw new Error(
98
+ "managed settings require trustedPluginKeySha256 fingerprints",
99
+ );
100
+ }
101
+ if (trusted && !trusted.has(publicKeySha256)) {
102
+ throw new Error(`plugin signing key is not trusted (${publicKeySha256})`);
103
+ }
104
+ signatureVerified = verify(null, bytes, keyObject, signature);
105
+ if (!signatureVerified) {
106
+ throw new Error("plugin manifest signature verification failed");
107
+ }
108
+ }
109
+ return {
110
+ bytes,
111
+ sha256,
112
+ signatureVerified,
113
+ publicKeySha256: signatureVerified
114
+ ? createHash("sha256")
115
+ .update(
116
+ createPublicKey(readFileSync(publicKeyFile, "utf8")).export({
117
+ type: "spki",
118
+ format: "der",
119
+ }),
120
+ )
121
+ .digest("hex")
122
+ : null,
123
+ };
124
+ }
125
+
126
+ export function loadPluginManagedPolicy(options = {}) {
127
+ return loadManagedSettings(options).settings;
128
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Pull request creation utilities (ESM)
3
+ * Mirrors Claude Code /pr behavior
4
+ * @module lib/pr-create
5
+ */
6
+
7
+ import { execSync } from "child_process";
8
+ import { logger } from "./logger.js";
9
+
10
+ /**
11
+ * Get current git branch
12
+ * @returns {string} branch name
13
+ */
14
+ export function getCurrentBranch() {
15
+ try {
16
+ return execSync("git rev-parse --abbrev-ref HEAD", {
17
+ encoding: "utf8",
18
+ }).trim();
19
+ } catch {
20
+ return "main";
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Get remote URL
26
+ * @returns {string|null}
27
+ */
28
+ export function getRemoteUrl() {
29
+ try {
30
+ return execSync("git remote get-url origin", { encoding: "utf8" }).trim();
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Extract GitHub owner/repo from remote URL
38
+ * @returns {{owner: string, repo: string}|null}
39
+ */
40
+ export function parseGitHubRepo() {
41
+ const url = getRemoteUrl();
42
+ if (!url) return null;
43
+
44
+ // Handle SSH and HTTPS URLs
45
+ const match = url.match(/github\.com[:/]([^/]+)\/([^/.]+)(\.git)?/);
46
+ if (!match) return null;
47
+
48
+ return { owner: match[1], repo: match[2] };
49
+ }
50
+
51
+ /**
52
+ * Prepare pull request
53
+ * @param {Object} options PR options
54
+ * @returns {Promise<Object>} PR creation result
55
+ */
56
+ export async function preparePullRequest(options = {}) {
57
+ const branch = getCurrentBranch();
58
+ const repo = parseGitHubRepo();
59
+
60
+ // Check for uncommitted changes
61
+ const status = execSync("git status --porcelain", { encoding: "utf8" });
62
+ const hasChanges = status.trim().length > 0;
63
+
64
+ if (hasChanges && !options.skipStash) {
65
+ logger.warn(
66
+ "You have uncommitted changes. Commit or stash before creating PR.",
67
+ );
68
+ }
69
+
70
+ // Get commit messages for PR body
71
+ const defaultBase = options.base || "main";
72
+ const logRange = `${defaultBase}...${branch}`;
73
+ let commitLog = "";
74
+ try {
75
+ commitLog = execSync(`git log ${logRange} --oneline`, { encoding: "utf8" });
76
+ } catch {
77
+ commitLog = "";
78
+ }
79
+
80
+ const prUrl = repo
81
+ ? `https://github.com/${repo.owner}/${repo.repo}/compare/${defaultBase}...${branch}?expand=1`
82
+ : null;
83
+
84
+ logger.info(`Branch: ${branch}`);
85
+ logger.info(`Base: ${defaultBase}`);
86
+ if (prUrl) {
87
+ logger.info(`Open PR in browser: ${prUrl}`);
88
+ }
89
+
90
+ return {
91
+ prepared: true,
92
+ branch,
93
+ base: defaultBase,
94
+ repo,
95
+ commitLog,
96
+ prUrl,
97
+ hasUncommittedChanges: hasChanges,
98
+ message:
99
+ "PR draft prepared. AI will help generate title and body from commits.",
100
+ };
101
+ }
102
+
103
+ export default {
104
+ getCurrentBranch,
105
+ getRemoteUrl,
106
+ parseGitHubRepo,
107
+ preparePullRequest,
108
+ };