chainlesschain 0.162.34 → 0.162.36

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 (185) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-BYfi9NYS.js → AIOps-vAVAFNJ4.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-BiS_tAN7.js → ActionButton-BnRHFCKM.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-jiWl_p-B.js → Analytics-BOjwqWqG.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-m4sIzDot.js → AppLayout-Dc0D1Txn.js} +5 -5
  6. package/src/assets/web-panel/assets/{Audit-CPla3Erm.js → Audit-dd_2efaZ.js} +1 -1
  7. package/src/assets/web-panel/assets/{Backup-BGeQzTaB.js → Backup-HF1jgm8G.js} +1 -1
  8. package/src/assets/web-panel/assets/{BaseInput-DTf7Z1iU.js → BaseInput-CCtzmoKe.js} +1 -1
  9. package/src/assets/web-panel/assets/{Chat-DPTlQlD-.js → Chat-BNfH1c3p.js} +6 -6
  10. package/src/assets/web-panel/assets/{ChatBubbleRenderer-BgRXce4e.js → ChatBubbleRenderer-DCWFqmI4.js} +1 -1
  11. package/src/assets/web-panel/assets/{Checkbox-DY-XuQMu.js → Checkbox-BOr-NscK.js} +1 -1
  12. package/src/assets/web-panel/assets/{Codegen-B6oxPiZI.js → Codegen-DE058N7-.js} +1 -1
  13. package/src/assets/web-panel/assets/{Col-Dqxb4wSE.js → Col-SOREo1XE.js} +1 -1
  14. package/src/assets/web-panel/assets/{Community-DCIX514p.js → Community-sOvNZo9f.js} +1 -1
  15. package/src/assets/web-panel/assets/{Compact-BGtCzDoJ.js → Compact-DnBe558D.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compliance-zcOYd55o.js → Compliance-o-r6CUbg.js} +1 -1
  17. package/src/assets/web-panel/assets/{Cowork-DVTtdIdM.js → Cowork-D6_k9mHP.js} +4 -4
  18. package/src/assets/web-panel/assets/{Cron-CPUaR69k.js → Cron-CEV3Xkrm.js} +2 -2
  19. package/src/assets/web-panel/assets/{Crosschain-DnjUS6QH.js → Crosschain-eJ1lQWKU.js} +1 -1
  20. package/src/assets/web-panel/assets/{DID-Dnz8VDmx.js → DID-B-WqM9Hp.js} +2 -2
  21. package/src/assets/web-panel/assets/{Dashboard-CtWf27j7.js → Dashboard-ZnKPcsHN.js} +2 -2
  22. package/src/assets/web-panel/assets/{Dropdown-B4GC1ZV4.js → Dropdown-B8uLWDIP.js} +1 -1
  23. package/src/assets/web-panel/assets/{EmailListRenderer-wjij3kzr.js → EmailListRenderer-Jmj2Y7aH.js} +1 -1
  24. package/src/assets/web-panel/assets/{FamilyGuardDashboard-rS-2W4u5.js → FamilyGuardDashboard-Cb2xetG-.js} +1 -1
  25. package/src/assets/web-panel/assets/{Federation-90p5Tnoz.js → Federation-C_07GXoq.js} +1 -1
  26. package/src/assets/web-panel/assets/{FormItemContext-Cnrw7gzq.js → FormItemContext-D3kbYrMU.js} +1 -1
  27. package/src/assets/web-panel/assets/{GenericCardRenderer-C85NsWa3.js → GenericCardRenderer-9xgqvGPg.js} +1 -1
  28. package/src/assets/web-panel/assets/{Git-BFAVM9F8.js → Git-BlwWlMMB.js} +2 -2
  29. package/src/assets/web-panel/assets/{Governance-DBoRonpq.js → Governance-DxN3wQZ_.js} +1 -1
  30. package/src/assets/web-panel/assets/{Inference-DHRyD66j.js → Inference-ls7pSw_D.js} +1 -1
  31. package/src/assets/web-panel/assets/{KnowledgeGraph-CTvUKecD.js → KnowledgeGraph-_n9hYuPI.js} +1 -1
  32. package/src/assets/web-panel/assets/{Logs-CB0dv_Ts.js → Logs-CvEVY5TK.js} +2 -2
  33. package/src/assets/web-panel/assets/{Marketplace-CN7Hm5Uw.js → Marketplace-C3qvQJT7.js} +1 -1
  34. package/src/assets/web-panel/assets/{McpTools-q5H25_8L.js → McpTools-DiwKpnKx.js} +5 -5
  35. package/src/assets/web-panel/assets/{Memory-BCV3pZ1d.js → Memory-CIBPi_da.js} +2 -2
  36. package/src/assets/web-panel/assets/{MobileBridge-C04Mngt4.js → MobileBridge-D-v0Se8y.js} +2 -2
  37. package/src/assets/web-panel/assets/MobileProjects-cP1apTQD.js +1 -0
  38. package/src/assets/web-panel/assets/{Mtc-ByAMz2DN.js → Mtc-BMFWrI65.js} +4 -4
  39. package/src/assets/web-panel/assets/{MtcAudit-B7V7byJq.js → MtcAudit-2s8LaHtR.js} +2 -2
  40. package/src/assets/web-panel/assets/{Multisig-DtKmcVQV.js → Multisig-dL_nvj7d.js} +3 -3
  41. package/src/assets/web-panel/assets/{NLProgramming-CaMbT5SC.js → NLProgramming-BbrJp06R.js} +1 -1
  42. package/src/assets/web-panel/assets/{Notes-DRjbSTCU.js → Notes-jR9irwy3.js} +4 -4
  43. package/src/assets/web-panel/assets/{NotificationSettings-B9YbJID5.js → NotificationSettings-Dk-STCIX.js} +1 -1
  44. package/src/assets/web-panel/assets/{OrderTableRenderer-BcI_-vGS.js → OrderTableRenderer-CqqfY6zq.js} +1 -1
  45. package/src/assets/web-panel/assets/{Organization-oTask4BE.js → Organization-BCK5jylo.js} +4 -4
  46. package/src/assets/web-panel/assets/{Overflow-Bab06ey7.js → Overflow-BRAY7Smt.js} +1 -1
  47. package/src/assets/web-panel/assets/{P2P--wlBeU0N.js → P2P-BltVRGjb.js} +2 -2
  48. package/src/assets/web-panel/assets/{PdhVaultBrowser-D4t77Pwc.js → PdhVaultBrowser-CV8UbXHe.js} +3 -3
  49. package/src/assets/web-panel/assets/{Permissions-B3sf6CJ3.js → Permissions-_tNl47Qh.js} +4 -4
  50. package/src/assets/web-panel/assets/{PersonalDataHub-BXOojk63.js → PersonalDataHub-Cgc4HjpX.js} +4 -4
  51. package/src/assets/web-panel/assets/{Pipeline-DReqtBFN.js → Pipeline-Bn_QU4mu.js} +1 -1
  52. package/src/assets/web-panel/assets/{Privacy-cT1GwKLx.js → Privacy-jzJowp5P.js} +1 -1
  53. package/src/assets/web-panel/assets/{ProjectInit-BhTAzVhH.js → ProjectInit-B_1pJ8qd.js} +2 -2
  54. package/src/assets/web-panel/assets/{ProjectSettings-CK-D8Fyj.js → ProjectSettings-CPVZpXzs.js} +2 -2
  55. package/src/assets/web-panel/assets/{Projects-CbHiwen6.js → Projects-CQsHOWnT.js} +1 -1
  56. package/src/assets/web-panel/assets/{Providers-B-ftiXa8.js → Providers-CzzMiLC0.js} +1 -1
  57. package/src/assets/web-panel/assets/{QuickAsk-CT5XPwTF.js → QuickAsk-MxBKIn9o.js} +1 -1
  58. package/src/assets/web-panel/assets/{Recommend-CohhlBZ_.js → Recommend-D8lN6Lis.js} +1 -1
  59. package/src/assets/web-panel/assets/{Reputation-CrgbixFz.js → Reputation-CfYK-IrV.js} +1 -1
  60. package/src/assets/web-panel/assets/{Row-ClExmBn3.js → Row-Bg7NZDP9.js} +1 -1
  61. package/src/assets/web-panel/assets/{RssFeed-VV0qizCJ.js → RssFeed-BOVNJhj0.js} +3 -3
  62. package/src/assets/web-panel/assets/{Search-CqJapSiL.js → Search-B38qzmhY.js} +1 -1
  63. package/src/assets/web-panel/assets/{Security-DY66Zie6.js → Security-CjqleZpe.js} +4 -4
  64. package/src/assets/web-panel/assets/{Services-RQwxat7-.js → Services-Bu9JSJap.js} +2 -2
  65. package/src/assets/web-panel/assets/{Skeleton-0v37UTU_.js → Skeleton-B2RvRkaX.js} +1 -1
  66. package/src/assets/web-panel/assets/{Skills-B4Vm4DxN.js → Skills-_h42mxMN.js} +1 -1
  67. package/src/assets/web-panel/assets/{Sla-CggphTlo.js → Sla-BssLs56D.js} +1 -1
  68. package/src/assets/web-panel/assets/{SpeechSettings-BAOU08C7.js → SpeechSettings-DCxFYHsd.js} +1 -1
  69. package/src/assets/web-panel/assets/{SyncSettings-DmtC4J1w.js → SyncSettings-D2xQuNLE.js} +2 -2
  70. package/src/assets/web-panel/assets/Tasks-DhpOGOlo.js +1 -0
  71. package/src/assets/web-panel/assets/{Templates-C1QK0YoU.js → Templates-CYG-R-aS.js} +1 -1
  72. package/src/assets/web-panel/assets/{Tenant-CieOfmqp.js → Tenant-BQRYLsvP.js} +1 -1
  73. package/src/assets/web-panel/assets/{Terminal-DWdhrxRq.js → Terminal-imKU7N5j.js} +2 -2
  74. package/src/assets/web-panel/assets/{TimelineRenderer-CjFVUUDU.js → TimelineRenderer-BIZzBftk.js} +1 -1
  75. package/src/assets/web-panel/assets/{Tokens-Bwbk3id9.js → Tokens-uMLH5p_a.js} +1 -1
  76. package/src/assets/web-panel/assets/{Trigger-uJle_yj4.js → Trigger-BzS6XPqx.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trust-BcOuxAA5.js → Trust-R4zhHufZ.js} +1 -1
  78. package/src/assets/web-panel/assets/{UkeySign-DUu7Ufg6.js → UkeySign-DATQCoGe.js} +1 -1
  79. package/src/assets/web-panel/assets/{VideoEditing-Ck8JtQ2n.js → VideoEditing-ClUmKOtS.js} +1 -1
  80. package/src/assets/web-panel/assets/{Wallet-B3jw43on.js → Wallet-DzJTbQzD.js} +4 -4
  81. package/src/assets/web-panel/assets/{WebAuthn-Baf9K0y7.js → WebAuthn-CrXrLmzQ.js} +5 -5
  82. package/src/assets/web-panel/assets/{WorkflowEditor-CTEDl_83.js → WorkflowEditor-CpvZ0Tma.js} +1 -1
  83. package/src/assets/web-panel/assets/{chat-CKV51quV.js → chat-a6wpYmVL.js} +1 -1
  84. package/src/assets/web-panel/assets/{colors-BO_RP_yz.js → colors-CXJADb1t.js} +1 -1
  85. package/src/assets/web-panel/assets/{compact-item-BZsxw_ZG.js → compact-item-CL2pohS_.js} +1 -1
  86. package/src/assets/web-panel/assets/{createContext-CAbvtzVL.js → createContext-xFi_1G5_.js} +1 -1
  87. package/src/assets/web-panel/assets/devWarning-BtmELbtB.js +1 -0
  88. package/src/assets/web-panel/assets/{hasIn-QmHT8zDz.js → hasIn-Bchh1rAi.js} +1 -1
  89. package/src/assets/web-panel/assets/{index-fnDgExTu.js → index-B3Tpv7-d.js} +1 -1
  90. package/src/assets/web-panel/assets/index-B4l4vLTB.js +1 -0
  91. package/src/assets/web-panel/assets/{index-BEJa1FiF.js → index-B4zNisy9.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-jd2r-T4p.js → index-B6NehWty.js} +1 -1
  93. package/src/assets/web-panel/assets/index-B7Ek5iiY.js +1 -0
  94. package/src/assets/web-panel/assets/{index-BPZHeug4.js → index-B7knYOpm.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-GPY0LjCu.js → index-B7wT5VRi.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-DKnngF_f.js → index-BF4xx1_b.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-BRNYA0BV.js → index-BH9t10pe.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-DKquNxL2.js → index-BPH5ESqs.js} +3 -3
  99. package/src/assets/web-panel/assets/{index-CEh2Ry_A.js → index-BmsIKzyu.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-Dob6B6qS.js → index-BoaRB-4a.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-Ha2_56mf.js → index-BrbJBnT-.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-Dln_vjSY.js → index-C2eMYASq.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-CqiKnXtL.js → index-C4yBRKT4.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-B3fwyCjJ.js → index-CGq4HQno.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-B2aiE8jk.js → index-CMybtJY6.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-B5zhcul9.js → index-CR3kFPuC.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-8BMLlHCv.js → index-CTRd7vkq.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-C6i3reUS.js → index-CdU8BwRW.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-BNvTNZ1V.js → index-Cua_P8St.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-DjrDGJP2.js → index-CuehgDOp.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BCsZiq4i.js → index-D-TT9Swq.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-qPafbZmr.js → index-DEYcLAl7.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DeC7lehI.js → index-DQ_hw_5P.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BnPBG3Tr.js → index-DTEu7TSF.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-D8CHQnPl.js → index-DVo1GJoj.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-9IqJODII.js → index-DjdOL159.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-DBCYOypV.js → index-DsbMVBj1.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-BL7gQAuB.js → index-DxahxRP7.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DC1CFfQU.js → index-EPERz4Pu.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CVoYeZ5Q.js → index-IkvkNxbc.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CsBx0u5G.js → index-KCib1PTw.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-5hlO2-JQ.js → index-M8SZI11a.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CSaI8R_7.js → index-TxbHusq2.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-C6AA-xB2.js → index-dsLc7t6W.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-DRK0oAV5.js → index-jMcv1u5o.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-B9Z83FTS.js → index-majCS3s2.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-C3K1eHDd.js → index-u8K1y_lh.js} +1 -1
  128. package/src/assets/web-panel/assets/{initDefaultProps-Bc2GWeWe.js → initDefaultProps-DYn3Gc09.js} +1 -1
  129. package/src/assets/web-panel/assets/{motion-BI-Rxw6o.js → motion-ZS3eolb9.js} +1 -1
  130. package/src/assets/web-panel/assets/{move-DRPdwDQB.js → move-CEw4uqr3.js} +1 -1
  131. package/src/assets/web-panel/assets/{omit-B4XTl3jW.js → omit-DlHFZnPp.js} +1 -1
  132. package/src/assets/web-panel/assets/{pickAttrs-Do5d86Wr.js → pickAttrs-eZQvV5fA.js} +1 -1
  133. package/src/assets/web-panel/assets/{placementArrow-B8VGZ0ZF.js → placementArrow-B31jQwa-.js} +1 -1
  134. package/src/assets/web-panel/assets/{responsiveObserve-Cf0kI_vN.js → responsiveObserve-DAsNmVto.js} +1 -1
  135. package/src/assets/web-panel/assets/{slide-Cb0psjSL.js → slide-gPQPrYZC.js} +1 -1
  136. package/src/assets/web-panel/assets/{statusUtils-Bjuo5Oal.js → statusUtils-DwWKX5co.js} +1 -1
  137. package/src/assets/web-panel/assets/{styleChecker-BLMhoHJ5.js → styleChecker-B3VOtXuH.js} +1 -1
  138. package/src/assets/web-panel/assets/{useFlexGapSupport-BdCwAfNU.js → useFlexGapSupport-6ADctM2r.js} +1 -1
  139. package/src/assets/web-panel/assets/{useFs-9Jhaz5gG.js → useFs-6Zx1SSKs.js} +1 -1
  140. package/src/assets/web-panel/assets/{usePersonalDataHub-xYFyXKwD.js → usePersonalDataHub-BzReowln.js} +1 -1
  141. package/src/assets/web-panel/assets/{vnode-CVhepE6Z.js → vnode-C8IpEQbD.js} +1 -1
  142. package/src/assets/web-panel/assets/{zoom-IbbtJ4Zr.js → zoom-ruc9vHr0.js} +1 -1
  143. package/src/assets/web-panel/index.html +1 -1
  144. package/src/commands/agent.js +161 -6
  145. package/src/commands/agents.js +199 -0
  146. package/src/commands/command.js +7 -2
  147. package/src/commands/hook.js +136 -28
  148. package/src/commands/ide.js +168 -0
  149. package/src/commands/mcp.js +92 -0
  150. package/src/commands/output-style.js +127 -0
  151. package/src/commands/permissions.js +211 -0
  152. package/src/commands/statusline.js +93 -0
  153. package/src/index.js +8 -0
  154. package/src/lib/agent-core.js +7 -0
  155. package/src/lib/agents.js +147 -0
  156. package/src/lib/hook-manager.js +1 -0
  157. package/src/lib/hook-runner.cjs +183 -0
  158. package/src/lib/ide-bridge.js +310 -0
  159. package/src/lib/image-input.js +156 -0
  160. package/src/lib/mcp-oauth.js +415 -0
  161. package/src/lib/output-styles.js +179 -0
  162. package/src/lib/permission-rules.cjs +325 -0
  163. package/src/lib/provider-options.js +11 -7
  164. package/src/lib/settings-hook-events.cjs +102 -0
  165. package/src/lib/settings-hooks.cjs +163 -0
  166. package/src/lib/settings-loader.cjs +244 -0
  167. package/src/lib/slash-commands.js +21 -13
  168. package/src/lib/status-line.cjs +204 -0
  169. package/src/lib/sub-agent-profiles.js +3 -0
  170. package/src/lib/web-search.js +487 -0
  171. package/src/repl/agent-repl.js +445 -35
  172. package/src/repl/slash-macro.js +45 -0
  173. package/src/runtime/agent-core.js +799 -21
  174. package/src/runtime/coding-agent-contract-shared.cjs +94 -4
  175. package/src/runtime/coding-agent-policy.cjs +24 -0
  176. package/src/runtime/headless-runner.js +162 -6
  177. package/src/runtime/headless-stream.js +133 -7
  178. package/src/runtime/mcp-config.js +161 -15
  179. package/src/runtime/policies/agent-policy.js +1 -0
  180. package/src/runtime/system-prompt.js +6 -1
  181. package/src/assets/web-panel/assets/MobileProjects-CUxONYre.js +0 -1
  182. package/src/assets/web-panel/assets/Tasks-CExqxzL6.js +0 -1
  183. package/src/assets/web-panel/assets/devWarning-DQYatsRR.js +0 -1
  184. package/src/assets/web-panel/assets/index-Bv_y1Ud7.js +0 -1
  185. package/src/assets/web-panel/assets/index-CZZnSJEX.js +0 -1
package/src/index.js CHANGED
@@ -62,6 +62,10 @@ import { registerCheckpointCommand } from "./commands/checkpoint.js";
62
62
  import { registerGoalCommand } from "./commands/goal.js";
63
63
  import { registerCommandCommand } from "./commands/command.js";
64
64
  import { registerCompactCommand } from "./commands/compact.js";
65
+ import { registerPermissionsCommand } from "./commands/permissions.js";
66
+ import { registerOutputStyleCommand } from "./commands/output-style.js";
67
+ import { registerStatuslineCommand } from "./commands/statusline.js";
68
+ import { registerIdeCommand } from "./commands/ide.js";
65
69
  import { registerConsolCommand } from "./commands/consol.js";
66
70
  import { registerImportCommand } from "./commands/import.js";
67
71
  import { registerExportCommand } from "./commands/export.js";
@@ -460,6 +464,10 @@ export function createProgram(opts = {}) {
460
464
  registerGoalCommand(program);
461
465
  registerCommandCommand(program);
462
466
  registerCompactCommand(program);
467
+ registerPermissionsCommand(program);
468
+ registerOutputStyleCommand(program);
469
+ registerStatuslineCommand(program);
470
+ registerIdeCommand(program);
463
471
  registerConsolCommand(program);
464
472
 
465
473
  // Phase 2: Knowledge & content management
@@ -24,5 +24,12 @@ export {
24
24
  agentLoop,
25
25
  formatToolArgs,
26
26
  getActiveMcpServers,
27
+ listBackgroundShellTasks,
28
+ killAllBackgroundShellTasks,
27
29
  _accumulateOllamaStream,
30
+ _accumulateOpenAIStream,
31
+ _accumulateAnthropicStream,
32
+ _toAnthropicMessages,
33
+ _anthropicThinkingParams,
34
+ _normalizeAnthropicResponse,
28
35
  } from "../runtime/agent-core.js";
@@ -0,0 +1,147 @@
1
+ /**
2
+ * agents — user-defined subagent definitions (Claude-Code parity).
3
+ *
4
+ * Markdown files under `.claude/agents/` (project) or `~/.claude/agents/`
5
+ * (personal) define named subagents. Each file's body IS the subagent's system
6
+ * prompt; frontmatter declares its metadata. Mirrors `.claude/commands/` (see
7
+ * slash-commands.js) but for *agents* rather than prompt macros — a file
8
+ * `review/security.md` is the agent `review:security`.
9
+ *
10
+ * Frontmatter (all optional):
11
+ * name override the filename-derived name
12
+ * description one-line summary (when to use this agent)
13
+ * tools allow-list — comma string or YAML array; omit = inherit all
14
+ * model model override for runs of this agent
15
+ *
16
+ * Project scope shadows personal on a name clash. Discovery + parse are pure
17
+ * (inject fs/path/home) so the whole thing is unit-testable.
18
+ */
19
+
20
+ import fsDefault from "node:fs";
21
+ import pathDefault from "node:path";
22
+ import { homedir } from "node:os";
23
+ import yaml from "js-yaml";
24
+
25
+ const _deps = { fs: fsDefault, path: pathDefault };
26
+
27
+ /** Split `--- ... ---` YAML frontmatter from the body, camelCasing keys. */
28
+ function parseFrontmatter(content) {
29
+ const text = String(content || "");
30
+ const m = text.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
31
+ if (!m) return { data: {}, body: text.trim() };
32
+ let raw = {};
33
+ try {
34
+ raw = yaml.load(m[1]) || {};
35
+ } catch {
36
+ raw = {};
37
+ }
38
+ const data = {};
39
+ for (const [k, v] of Object.entries(raw)) {
40
+ const camel = k.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
41
+ data[camel] = v;
42
+ }
43
+ return { data, body: (m[2] || "").trim() };
44
+ }
45
+
46
+ /** Normalize `tools` (comma string | array | null) into a string[] or null. */
47
+ export function normalizeTools(tools) {
48
+ if (tools == null) return null;
49
+ const list = Array.isArray(tools)
50
+ ? tools
51
+ : String(tools).split(/[,\s]+/);
52
+ const out = list.map((t) => String(t).trim()).filter(Boolean);
53
+ return out.length > 0 ? out : null;
54
+ }
55
+
56
+ /** Directories scanned for agent files — project first (shadows personal). */
57
+ export function agentDirs(cwd = process.cwd(), opts = {}) {
58
+ const path = opts.deps?.path || _deps.path;
59
+ const home = opts.home || homedir();
60
+ // Project-native first (highest precedence), then the Claude-Code-portable
61
+ // location (so existing `.claude/agents/*.md` work unchanged), then personal.
62
+ // discoverAgents reverses + last-write-wins, so `.chainlesschain/agents/`
63
+ // shadows `.claude/agents/` shadows `~/.claude/agents/` on a name clash.
64
+ return [
65
+ { dir: path.join(cwd, ".chainlesschain", "agents"), scope: "project" },
66
+ { dir: path.join(cwd, ".claude", "agents"), scope: "project" },
67
+ { dir: path.join(home, ".claude", "agents"), scope: "personal" },
68
+ ];
69
+ }
70
+
71
+ /** Recursively collect `*.md` files under `dir` as `{file, rel}` (rel uses /). */
72
+ function walkMd(dir, { fs, path }, base = dir, acc = []) {
73
+ let entries;
74
+ try {
75
+ entries = fs.readdirSync(dir, { withFileTypes: true });
76
+ } catch {
77
+ return acc;
78
+ }
79
+ for (const e of entries) {
80
+ const full = path.join(dir, e.name);
81
+ if (e.isDirectory()) {
82
+ walkMd(full, { fs, path }, base, acc);
83
+ } else if (e.isFile() && e.name.endsWith(".md")) {
84
+ const rel = path.relative(base, full).replace(/\\/g, "/");
85
+ acc.push({ file: full, rel });
86
+ }
87
+ }
88
+ return acc;
89
+ }
90
+
91
+ /** Agent name from a relative path: `review/security.md` → `review:security`. */
92
+ function nameFromRel(rel) {
93
+ return rel.replace(/\.md$/, "").replace(/\//g, ":");
94
+ }
95
+
96
+ /** Parse one agent file into its metadata + system prompt (the body). */
97
+ export function parseAgentFile(file, scope, opts = {}) {
98
+ const fs = opts.deps?.fs || _deps.fs;
99
+ let content;
100
+ try {
101
+ content = fs.readFileSync(file, "utf-8");
102
+ } catch {
103
+ return null;
104
+ }
105
+ const { data, body } = parseFrontmatter(content);
106
+ return {
107
+ file,
108
+ scope,
109
+ name: data.name || null, // resolved against the path in discoverAgents
110
+ description: data.description || "",
111
+ tools: normalizeTools(data.tools),
112
+ model: data.model || null,
113
+ systemPrompt: body || "",
114
+ };
115
+ }
116
+
117
+ /**
118
+ * Discover all agents across both scopes. Project shadows personal by name.
119
+ * @returns {Array<{name, scope, file, description, tools, model, systemPrompt}>}
120
+ */
121
+ export function discoverAgents(cwd = process.cwd(), opts = {}) {
122
+ const fs = opts.deps?.fs || _deps.fs;
123
+ const path = opts.deps?.path || _deps.path;
124
+ const byName = new Map();
125
+ // Personal first, then project — so project overwrites on clash.
126
+ const dirs = agentDirs(cwd, opts).reverse();
127
+ for (const { dir, scope } of dirs) {
128
+ for (const { file, rel } of walkMd(dir, { fs, path })) {
129
+ const meta = parseAgentFile(file, scope, opts);
130
+ if (!meta) continue;
131
+ // Explicit frontmatter `name` wins; else derive from the path.
132
+ const name = meta.name || nameFromRel(rel);
133
+ byName.set(name, { ...meta, name });
134
+ }
135
+ }
136
+ return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
137
+ }
138
+
139
+ /** Look up one agent by name (accepts `review:security` or `review/security`). */
140
+ export function getAgent(name, cwd = process.cwd(), opts = {}) {
141
+ const wanted = String(name || "")
142
+ .replace(/^\//, "")
143
+ .replace(/\//g, ":");
144
+ return discoverAgents(cwd, opts).find((a) => a.name === wanted) || null;
145
+ }
146
+
147
+ export { _deps };
@@ -40,6 +40,7 @@ export const HookEvents = {
40
40
  SessionEnd: "SessionEnd",
41
41
  PreCompact: "PreCompact",
42
42
  PostCompact: "PostCompact",
43
+ Notification: "Notification",
43
44
  UserPromptSubmit: "UserPromptSubmit",
44
45
  AssistantResponse: "AssistantResponse",
45
46
  AgentStart: "AgentStart",
@@ -0,0 +1,183 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * hook-runner — execute a Claude-Code `command` hook with the JSON protocol.
5
+ *
6
+ * Protocol (Claude-Code parity):
7
+ * - the hook event payload is written to the command's STDIN as JSON;
8
+ * - exit code 2 → BLOCK (reason = stderr) — the canonical "deny" path;
9
+ * - exit code 0 + JSON stdout → honored decision:
10
+ * { "decision": "block"|"approve"|"ask", "reason": "..." }
11
+ * { "hookSpecificOutput": { "permissionDecision": "deny"|"allow"|"ask",
12
+ * "permissionDecisionReason": "..." } }
13
+ * { "continue": false, "stopReason": "..." } → block
14
+ * { "additionalContext": "..." } → continue
15
+ * - any other non-zero → non-blocking error (surfaced, never blocks);
16
+ * - spawn failure / timeout → non-blocking (a broken hook must not wedge the
17
+ * agent — only an explicit block decision blocks).
18
+ *
19
+ * Returns a normalized `{ decision, reason, exitCode, stdout, stderr, ... }`.
20
+ * `_deps.spawnSync` is injected for unit tests (no real process needed).
21
+ */
22
+
23
+ const cpDefault = require("node:child_process");
24
+
25
+ const _deps = { spawnSync: cpDefault.spawnSync };
26
+
27
+ const HOOK_DECISIONS = Object.freeze({
28
+ BLOCK: "block",
29
+ ALLOW: "allow",
30
+ ASK: "ask",
31
+ CONTINUE: "continue",
32
+ });
33
+
34
+ /** Parse a hook's stdout JSON into a normalized decision, or null if not JSON. */
35
+ function tryParseDecision(stdout) {
36
+ const text = String(stdout || "").trim();
37
+ if (!text || text[0] !== "{") return null;
38
+ let obj;
39
+ try {
40
+ obj = JSON.parse(text);
41
+ } catch {
42
+ return null;
43
+ }
44
+ // PreToolUse-specific permission decision
45
+ const hso = obj.hookSpecificOutput;
46
+ if (hso && hso.permissionDecision) {
47
+ const pd = String(hso.permissionDecision).toLowerCase();
48
+ const decision =
49
+ pd === "deny"
50
+ ? HOOK_DECISIONS.BLOCK
51
+ : pd === "ask"
52
+ ? HOOK_DECISIONS.ASK
53
+ : pd === "allow"
54
+ ? HOOK_DECISIONS.ALLOW
55
+ : HOOK_DECISIONS.CONTINUE;
56
+ return { decision, reason: hso.permissionDecisionReason || null };
57
+ }
58
+ // Generic decision field
59
+ if (obj.decision) {
60
+ const d = String(obj.decision).toLowerCase();
61
+ const decision =
62
+ d === "block" || d === "deny"
63
+ ? HOOK_DECISIONS.BLOCK
64
+ : d === "approve" || d === "allow"
65
+ ? HOOK_DECISIONS.ALLOW
66
+ : d === "ask"
67
+ ? HOOK_DECISIONS.ASK
68
+ : HOOK_DECISIONS.CONTINUE;
69
+ return { decision, reason: obj.reason || null };
70
+ }
71
+ // continue:false → stop/block
72
+ if (obj.continue === false) {
73
+ return {
74
+ decision: HOOK_DECISIONS.BLOCK,
75
+ reason: obj.stopReason || obj.reason || "hook requested stop",
76
+ };
77
+ }
78
+ return {
79
+ decision: HOOK_DECISIONS.CONTINUE,
80
+ reason: null,
81
+ additionalContext: obj.additionalContext || null,
82
+ };
83
+ }
84
+
85
+ /**
86
+ * Run one command hook. `input` is JSON-serialized to the hook's stdin.
87
+ *
88
+ * @param {string} command
89
+ * @param {object} [input] the hook event payload (tool_name, tool_input, …)
90
+ * @param {object} [opts] { timeout=60000, cwd, event }
91
+ * @returns {{ decision:string, reason:string|null, exitCode:number|null,
92
+ * stdout?:string, stderr?:string, additionalContext?:string,
93
+ * nonBlockingError?:boolean, error?:string }}
94
+ */
95
+ function runCommandHook(command, input = {}, opts = {}) {
96
+ const { timeout = 60000, cwd, event } = opts;
97
+ if (!command) {
98
+ return { decision: HOOK_DECISIONS.CONTINUE, reason: null, exitCode: 0 };
99
+ }
100
+ let res;
101
+ try {
102
+ res = _deps.spawnSync(command, {
103
+ input: JSON.stringify(input),
104
+ cwd: cwd || process.cwd(),
105
+ encoding: "utf-8",
106
+ timeout,
107
+ shell: true,
108
+ maxBuffer: 8 * 1024 * 1024,
109
+ env: {
110
+ ...process.env,
111
+ CLAUDE_HOOK_EVENT: event || input.hook_event_name || "",
112
+ },
113
+ });
114
+ } catch (err) {
115
+ return {
116
+ decision: HOOK_DECISIONS.CONTINUE,
117
+ reason: `hook spawn failed: ${err.message}`,
118
+ exitCode: null,
119
+ error: err.message,
120
+ nonBlockingError: true,
121
+ };
122
+ }
123
+ // spawnSync surfaces timeout/ENOENT on res.error (status null) — non-blocking.
124
+ if (res.error) {
125
+ return {
126
+ decision: HOOK_DECISIONS.CONTINUE,
127
+ reason: `hook error: ${res.error.message}`,
128
+ exitCode: null,
129
+ stdout: String(res.stdout || ""),
130
+ stderr: String(res.stderr || ""),
131
+ error: res.error.message,
132
+ nonBlockingError: true,
133
+ };
134
+ }
135
+ const exitCode = res.status;
136
+ const stdout = String(res.stdout || "");
137
+ const stderr = String(res.stderr || "");
138
+
139
+ if (exitCode === 2) {
140
+ return {
141
+ decision: HOOK_DECISIONS.BLOCK,
142
+ reason: stderr.trim() || "hook exited 2 (blocked)",
143
+ exitCode,
144
+ stdout,
145
+ stderr,
146
+ };
147
+ }
148
+ if (exitCode === 0) {
149
+ const parsed = tryParseDecision(stdout);
150
+ if (parsed) return { ...parsed, exitCode, stdout, stderr };
151
+ return { decision: HOOK_DECISIONS.CONTINUE, reason: null, exitCode, stdout, stderr };
152
+ }
153
+ // Other non-zero → non-blocking error.
154
+ return {
155
+ decision: HOOK_DECISIONS.CONTINUE,
156
+ reason: stderr.trim() || `hook exited ${exitCode}`,
157
+ exitCode,
158
+ stdout,
159
+ stderr,
160
+ nonBlockingError: true,
161
+ };
162
+ }
163
+
164
+ /**
165
+ * Run a list of command hooks in order; first BLOCK (or ASK) short-circuits.
166
+ * @returns {{ decision, reason, hook?, results }}
167
+ */
168
+ function runHooks(commandHooks, input = {}, opts = {}) {
169
+ const results = [];
170
+ for (const h of commandHooks || []) {
171
+ const r = runCommandHook(h.command, input, {
172
+ ...opts,
173
+ timeout: h.timeout != null ? h.timeout * 1000 : opts.timeout,
174
+ });
175
+ results.push({ command: h.command, ...r });
176
+ if (r.decision === HOOK_DECISIONS.BLOCK || r.decision === HOOK_DECISIONS.ASK) {
177
+ return { decision: r.decision, reason: r.reason, hook: h.command, results };
178
+ }
179
+ }
180
+ return { decision: HOOK_DECISIONS.CONTINUE, reason: null, results };
181
+ }
182
+
183
+ module.exports = { runCommandHook, runHooks, tryParseDecision, HOOK_DECISIONS, _deps };
@@ -0,0 +1,310 @@
1
+ /**
2
+ * IDE bridge discovery (Phase 0) — find a first-party IDE MCP server that an
3
+ * editor extension advertises via a lockfile, and turn it into an MCP server
4
+ * config the agent loop can connect to.
5
+ *
6
+ * Protocol (design: docs/design/modules/98_IDE桥接对标方案.md): each running
7
+ * IDE instance writes
8
+ * ~/.chainlesschain/ide/<port>.json (file 0600, dir 0700)
9
+ * describing a localhost SSE/HTTP MCP server + a per-instance bearer token.
10
+ * The extension that owns the integrated terminal ALSO injects
11
+ * CHAINLESSCHAIN_IDE_PORT (+ optional CHAINLESSCHAIN_IDE_TOKEN)
12
+ * so the CLI can lock onto the exact instance without scanning/guessing
13
+ * (the deterministic "path A" below). Plain terminals fall back to a lockfile
14
+ * scan + workspace match ("path B").
15
+ *
16
+ * Phase 0 is pure CLI: no extension code, no VS Code dependency, fully
17
+ * unit-testable through `_deps`. The connect path itself reuses the existing
18
+ * MCP client (SSE/HTTP) via mcp-config.js `loadIdeMcp`.
19
+ */
20
+ import fs from "fs";
21
+ import path from "path";
22
+ import os from "os";
23
+
24
+ /** A lock whose pid is dead and whose file is older than this = stale. */
25
+ const STALE_TTL_MS = 30_000;
26
+
27
+ /** Transports the CLI's MCP client can actually talk (see mcp-client.js). */
28
+ const SUPPORTED_TRANSPORTS = new Set(["sse", "http", "https"]);
29
+
30
+ /** Liveness probe via signal 0 — cross-platform (works on Windows too). */
31
+ function defaultProcessAlive(pid) {
32
+ if (!pid || typeof pid !== "number") return false;
33
+ try {
34
+ process.kill(pid, 0);
35
+ return true;
36
+ } catch (err) {
37
+ // EPERM: the process exists but we may not signal it → still alive.
38
+ return !!(err && err.code === "EPERM");
39
+ }
40
+ }
41
+
42
+ export const _deps = {
43
+ readDir: (d) => fs.readdirSync(d),
44
+ readFile: (p) => fs.readFileSync(p, "utf-8"),
45
+ statMtimeMs: (p) => fs.statSync(p).mtimeMs,
46
+ homedir: () => os.homedir(),
47
+ now: () => Date.now(),
48
+ processAlive: defaultProcessAlive,
49
+ };
50
+
51
+ /** `~/.chainlesschain/ide/`. */
52
+ export function ideLockDir(deps = _deps) {
53
+ return path.join(deps.homedir(), ".chainlesschain", "ide");
54
+ }
55
+
56
+ function isLocalhostUrl(url) {
57
+ try {
58
+ const u = new URL(url);
59
+ return u.hostname === "127.0.0.1" || u.hostname === "::1";
60
+ } catch {
61
+ return false;
62
+ }
63
+ }
64
+
65
+ function inferTransportFromUrl(url) {
66
+ if (/\/sse(\b|\/|$)/i.test(url)) return "sse";
67
+ if (/^https:/i.test(url)) return "https";
68
+ if (/^http:/i.test(url)) return "http";
69
+ return null;
70
+ }
71
+
72
+ /** Parse + validate one lock JSON. Returns a normalized lock or null. */
73
+ function parseLock(raw, filePath) {
74
+ let lock;
75
+ try {
76
+ lock = JSON.parse(raw);
77
+ } catch {
78
+ return null;
79
+ }
80
+ if (!lock || typeof lock !== "object") return null;
81
+ if (!lock.url || !isLocalhostUrl(lock.url)) return null;
82
+
83
+ const transport = String(
84
+ lock.transport || inferTransportFromUrl(lock.url) || "",
85
+ ).toLowerCase();
86
+ if (!SUPPORTED_TRANSPORTS.has(transport)) return null; // e.g. ws — not yet
87
+
88
+ let folders = lock.workspaceFolders;
89
+ if (typeof folders === "string") folders = [folders];
90
+ if (!Array.isArray(folders)) folders = [];
91
+ folders = folders.filter((f) => typeof f === "string" && f.length > 0);
92
+
93
+ return {
94
+ ide: typeof lock.ide === "string" ? lock.ide : "unknown",
95
+ transport,
96
+ url: lock.url,
97
+ port: lock.port,
98
+ token: typeof lock.token === "string" ? lock.token : null,
99
+ workspaceFolders: folders,
100
+ pid: typeof lock.pid === "number" ? lock.pid : null,
101
+ startedAt: typeof lock.started_at === "number" ? lock.started_at : 0,
102
+ _file: filePath,
103
+ };
104
+ }
105
+
106
+ /** True when a lock is dead: pid gone AND file older than the TTL. */
107
+ function isStale(lock, deps) {
108
+ if (lock.pid && deps.processAlive(lock.pid)) return false;
109
+ let mtime;
110
+ try {
111
+ mtime = deps.statMtimeMs(lock._file);
112
+ } catch {
113
+ return true;
114
+ }
115
+ return deps.now() - mtime > STALE_TTL_MS;
116
+ }
117
+
118
+ /**
119
+ * Scan the lock dir → normalized, live, localhost, supported-transport locks.
120
+ * Missing dir / unreadable files are silently skipped (best-effort).
121
+ */
122
+ export function readIdeLocks(deps = _deps) {
123
+ const dir = ideLockDir(deps);
124
+ let names;
125
+ try {
126
+ names = deps.readDir(dir);
127
+ } catch {
128
+ return [];
129
+ }
130
+ const out = [];
131
+ for (const name of names || []) {
132
+ if (!String(name).endsWith(".json")) continue;
133
+ const fp = path.join(dir, name);
134
+ let raw;
135
+ try {
136
+ raw = deps.readFile(fp);
137
+ } catch {
138
+ continue;
139
+ }
140
+ const lock = parseLock(raw, fp);
141
+ if (!lock) continue;
142
+ if (isStale(lock, deps)) continue;
143
+ out.push(lock);
144
+ }
145
+ return out;
146
+ }
147
+
148
+ /** Are we running inside an editor's integrated terminal? */
149
+ export function isInIdeTerminal(env = process.env) {
150
+ if (!env) return false;
151
+ if (env.CHAINLESSCHAIN_IDE_PORT) return true;
152
+ if (env.TERM_PROGRAM === "vscode") return true;
153
+ if (env.CHAINLESSCHAIN_IDE) return true;
154
+ if (env.TERMINAL_EMULATOR && /jetbrains/i.test(env.TERMINAL_EMULATOR)) {
155
+ return true;
156
+ }
157
+ return false;
158
+ }
159
+
160
+ /** Normalize a path for prefix comparison (sep + trailing slash + win case). */
161
+ function normPath(p) {
162
+ const s = String(p)
163
+ .replace(/\\/g, "/")
164
+ .replace(/\/+$/, "");
165
+ return process.platform === "win32" ? s.toLowerCase() : s;
166
+ }
167
+
168
+ /**
169
+ * Longest matching workspace-folder length for `cwd`, or -1 if none of the
170
+ * lock's folders contains (or equals) cwd. Longer = more specific.
171
+ */
172
+ function workspaceMatchScore(lock, cwd) {
173
+ const c = normPath(cwd);
174
+ let best = -1;
175
+ for (const f of lock.workspaceFolders) {
176
+ const nf = normPath(f);
177
+ if (c === nf || c.startsWith(nf + "/")) {
178
+ if (nf.length > best) best = nf.length;
179
+ }
180
+ }
181
+ return best;
182
+ }
183
+
184
+ /**
185
+ * Pick the IDE server to connect to, or null.
186
+ *
187
+ * Path A (deterministic): if `CHAINLESSCHAIN_IDE_PORT` is set and a live lock
188
+ * has that port, use it (token may come from the lock or the env).
189
+ * Path B (scan): otherwise rank live locks by (longest workspace-prefix match,
190
+ * then newest started_at). With `force`, fall back to the newest lock even
191
+ * when no folder matches (used by `--ide`).
192
+ *
193
+ * @param {object} opts { cwd?, env?, force? }
194
+ * @param {object} [deps]
195
+ */
196
+ export function discoverIdeServer(
197
+ { cwd = process.cwd(), env = process.env, force = false } = {},
198
+ deps = _deps,
199
+ ) {
200
+ const locks = readIdeLocks(deps);
201
+ if (!locks.length) return null;
202
+
203
+ // Path A — env fast-path.
204
+ const envPort = env && env.CHAINLESSCHAIN_IDE_PORT;
205
+ if (envPort) {
206
+ const hit = locks.find((l) => String(l.port) === String(envPort));
207
+ if (hit) {
208
+ if (!hit.token && env.CHAINLESSCHAIN_IDE_TOKEN) {
209
+ hit.token = env.CHAINLESSCHAIN_IDE_TOKEN;
210
+ }
211
+ return hit;
212
+ }
213
+ // env named a port with no live lock — fall through to scan.
214
+ }
215
+
216
+ // Path B — workspace match.
217
+ let best = null;
218
+ let bestScore = -1;
219
+ let bestStarted = -1;
220
+ for (const l of locks) {
221
+ const score = workspaceMatchScore(l, cwd);
222
+ if (score < 0) continue;
223
+ if (
224
+ score > bestScore ||
225
+ (score === bestScore && l.startedAt > bestStarted)
226
+ ) {
227
+ best = l;
228
+ bestScore = score;
229
+ bestStarted = l.startedAt;
230
+ }
231
+ }
232
+ if (best) return best;
233
+
234
+ // --ide forced but no workspace match → newest live lock.
235
+ if (force) {
236
+ return locks
237
+ .slice()
238
+ .sort((a, b) => b.startedAt - a.startedAt)[0];
239
+ }
240
+ return null;
241
+ }
242
+
243
+ /**
244
+ * A discovered lock → an MCP server config row for `setupMcpFromConfig`.
245
+ * `longRunning` is forward-compat metadata: IDE tools like `ide_openDiff` may
246
+ * block on the user for minutes, so the agent loop should exempt them from a
247
+ * per-call timeout (consumed in Phase 1).
248
+ */
249
+ export function ideServerToMcpConfig(lock) {
250
+ if (!lock || !lock.url) return null;
251
+ const headers = {};
252
+ if (lock.token) headers.Authorization = `Bearer ${lock.token}`;
253
+ return {
254
+ url: lock.url,
255
+ transport: lock.transport,
256
+ headers,
257
+ longRunning: true,
258
+ };
259
+ }
260
+
261
+ /**
262
+ * Human-readable explanation of why discovery did/didn't find a server — backs
263
+ * `cc ide doctor`. Returns { inIdeTerminal, locks:[{...,reasons?}], chosen,
264
+ * reason }.
265
+ */
266
+ export function diagnoseIde(
267
+ { cwd = process.cwd(), env = process.env, force = false } = {},
268
+ deps = _deps,
269
+ ) {
270
+ const inIdeTerminal = isInIdeTerminal(env);
271
+ const locks = readIdeLocks(deps);
272
+ const chosen = discoverIdeServer({ cwd, env, force }, deps);
273
+
274
+ let reason;
275
+ if (chosen) {
276
+ reason = env && env.CHAINLESSCHAIN_IDE_PORT &&
277
+ String(chosen.port) === String(env.CHAINLESSCHAIN_IDE_PORT)
278
+ ? "matched CHAINLESSCHAIN_IDE_PORT (env fast-path)"
279
+ : force
280
+ ? "forced (--ide); newest live lock"
281
+ : "workspace match";
282
+ } else if (!locks.length) {
283
+ reason =
284
+ "no live IDE lockfiles in " +
285
+ ideLockDir(deps) +
286
+ " (is an IDE extension running? are locks stale?)";
287
+ } else {
288
+ reason =
289
+ "lockfiles present but none match cwd's workspace " +
290
+ "(run with --ide to force the newest, or cd into the IDE workspace)";
291
+ }
292
+ return {
293
+ inIdeTerminal,
294
+ lockDir: ideLockDir(deps),
295
+ locks: locks.map((l) => ({
296
+ ide: l.ide,
297
+ port: l.port,
298
+ transport: l.transport,
299
+ url: l.url,
300
+ hasToken: !!l.token,
301
+ workspaceFolders: l.workspaceFolders,
302
+ pid: l.pid,
303
+ matchScore: workspaceMatchScore(l, cwd),
304
+ })),
305
+ chosen: chosen
306
+ ? { ide: chosen.ide, port: chosen.port, url: chosen.url }
307
+ : null,
308
+ reason,
309
+ };
310
+ }