chainlesschain 0.162.193 → 0.162.198

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 (215) hide show
  1. package/README.md +29 -6
  2. package/completions/_cc +2 -2
  3. package/completions/cc.bash +2 -2
  4. package/completions/cc.fish +2 -2
  5. package/completions/cc.ps1 +2 -2
  6. package/package.json +1 -1
  7. package/src/assets/web-panel/assets/{AIOps-DBLkaTje.js → AIOps-CSN-D2xb.js} +1 -1
  8. package/src/assets/web-panel/assets/{ActionButton-BAy-Qebo.js → ActionButton-CBSMlGzv.js} +1 -1
  9. package/src/assets/web-panel/assets/{Analytics-Dfb1Mlau.js → Analytics-3yr3Nt2t.js} +3 -3
  10. package/src/assets/web-panel/assets/{AppLayout-BbFkN8m0.js → AppLayout-1b-o3lMH.js} +4 -4
  11. package/src/assets/web-panel/assets/{Artifacts-kM4-cEEH.js → Artifacts-Dr8rJi17.js} +1 -1
  12. package/src/assets/web-panel/assets/{Audit-BSQfYWf3.js → Audit-2V689HC2.js} +1 -1
  13. package/src/assets/web-panel/assets/{BackgroundAgents-CWDbI_fG.js → BackgroundAgents-DIxkNrD_.js} +1 -1
  14. package/src/assets/web-panel/assets/{Backup-BxLPbq_Q.js → Backup-Pg6BcwrE.js} +1 -1
  15. package/src/assets/web-panel/assets/{BaseInput-DuIbdrp6.js → BaseInput-CLUpqyfH.js} +1 -1
  16. package/src/assets/web-panel/assets/{Chat-NEmjGahU.js → Chat-qVS8rD4G.js} +5 -5
  17. package/src/assets/web-panel/assets/{ChatBubbleRenderer-DfJEo-5_.js → ChatBubbleRenderer-ti49X12Q.js} +1 -1
  18. package/src/assets/web-panel/assets/{Checkbox-CaoeG3Ns.js → Checkbox-Bs9AaEay.js} +1 -1
  19. package/src/assets/web-panel/assets/{Codegen-BFf_g3xY.js → Codegen-Dvc1mq8q.js} +1 -1
  20. package/src/assets/web-panel/assets/{Col-CNOd8mdQ.js → Col-C4_biO7A.js} +1 -1
  21. package/src/assets/web-panel/assets/{Community-BhpDqz-i.js → Community-al25XQUV.js} +1 -1
  22. package/src/assets/web-panel/assets/{Compact-CQ42T7Z6.js → Compact-D-ZDB8M1.js} +1 -1
  23. package/src/assets/web-panel/assets/{Compliance-DI5PcLMN.js → Compliance-6lIfDz1W.js} +1 -1
  24. package/src/assets/web-panel/assets/{Cowork-wYYZe0wR.js → Cowork-DdI48XMJ.js} +3 -3
  25. package/src/assets/web-panel/assets/{Cron-Dlq03DD_.js → Cron-DF5HZd2g.js} +2 -2
  26. package/src/assets/web-panel/assets/{Crosschain-BzqS8PP3.js → Crosschain-DuxeSvt3.js} +1 -1
  27. package/src/assets/web-panel/assets/{DID-It55Yem7.js → DID-B9yHkUTd.js} +2 -2
  28. package/src/assets/web-panel/assets/{Dashboard-9HkH6oxn.js → Dashboard-Z5EuI3IM.js} +2 -2
  29. package/src/assets/web-panel/assets/{Dropdown-00e2YWRB.js → Dropdown-vuRfO3dY.js} +1 -1
  30. package/src/assets/web-panel/assets/{EmailListRenderer-By-ugRDL.js → EmailListRenderer-QFLCc1uL.js} +1 -1
  31. package/src/assets/web-panel/assets/{FamilyGuardDashboard-C396yu5m.js → FamilyGuardDashboard-BZ7EXkFJ.js} +1 -1
  32. package/src/assets/web-panel/assets/{Federation-BME5sbZY.js → Federation-DycIEg2s.js} +1 -1
  33. package/src/assets/web-panel/assets/{FormItemContext-B4TEZCr-.js → FormItemContext-Ba0H_7yG.js} +1 -1
  34. package/src/assets/web-panel/assets/{GenericCardRenderer-_9CX9ieY.js → GenericCardRenderer--JgSTK-C.js} +1 -1
  35. package/src/assets/web-panel/assets/{Git-BrS6WVSr.js → Git-DEvTbldE.js} +2 -2
  36. package/src/assets/web-panel/assets/{Governance-BMdCWnKF.js → Governance-BJIljy18.js} +1 -1
  37. package/src/assets/web-panel/assets/{Inference-Bcp3vzcA.js → Inference-CVyW3G3q.js} +1 -1
  38. package/src/assets/web-panel/assets/{KnowledgeGraph-DlLuqGY5.js → KnowledgeGraph-v8-borge.js} +1 -1
  39. package/src/assets/web-panel/assets/{Logs-D-W_6x09.js → Logs-BCdS7gGo.js} +2 -2
  40. package/src/assets/web-panel/assets/{MarkdownRenderer-DElGPxCZ.js → MarkdownRenderer-CU-JB2Ns.js} +1 -1
  41. package/src/assets/web-panel/assets/{Marketplace-Dzn2guvI.js → Marketplace-AN6tGA2B.js} +1 -1
  42. package/src/assets/web-panel/assets/{McpTools-uPict-KL.js → McpTools-DGViXASN.js} +6 -6
  43. package/src/assets/web-panel/assets/{Memory-BCw43kOb.js → Memory-Bpvmx9X4.js} +2 -2
  44. package/src/assets/web-panel/assets/{MobileBridge-DmV8Wv7F.js → MobileBridge-CTP-DOTC.js} +1 -1
  45. package/src/assets/web-panel/assets/{MobileProjects-C-HaCmSM.js → MobileProjects-C8qfktxn.js} +1 -1
  46. package/src/assets/web-panel/assets/{Mtc-C6jxJ34Y.js → Mtc-ndYr0M1s.js} +5 -5
  47. package/src/assets/web-panel/assets/{MtcAudit-Za8ExwhQ.js → MtcAudit-Ci0-YK7W.js} +5 -5
  48. package/src/assets/web-panel/assets/{Multisig-KKDbuEgb.js → Multisig-BehSTFW7.js} +3 -3
  49. package/src/assets/web-panel/assets/{NLProgramming-Dj97zbCk.js → NLProgramming-B91_YBLv.js} +1 -1
  50. package/src/assets/web-panel/assets/{Notes-DBXvMraU.js → Notes-BQlJ0-gq.js} +4 -4
  51. package/src/assets/web-panel/assets/{NotificationSettings-DOxITLYl.js → NotificationSettings-CDgYMjYK.js} +1 -1
  52. package/src/assets/web-panel/assets/{OrderTableRenderer-7DVjZwMM.js → OrderTableRenderer-BJkSlKeH.js} +1 -1
  53. package/src/assets/web-panel/assets/{Organization-D1i73mjb.js → Organization-B-b3cWCp.js} +4 -4
  54. package/src/assets/web-panel/assets/{Overflow-kW6Q3rvg.js → Overflow-CILUL4nL.js} +1 -1
  55. package/src/assets/web-panel/assets/{P2P-C-v_8p2v.js → P2P-BTCFBkF0.js} +2 -2
  56. package/src/assets/web-panel/assets/{PdhVaultBrowser-tUbbY64W.js → PdhVaultBrowser-BAR6AUx5.js} +5 -5
  57. package/src/assets/web-panel/assets/{Permissions-DkHxEZ58.js → Permissions-RRf6HbWJ.js} +4 -4
  58. package/src/assets/web-panel/assets/{PersonalDataHub-DWuMPGrK.js → PersonalDataHub-BGTMiICa.js} +4 -4
  59. package/src/assets/web-panel/assets/{Pipeline-30IYXWWm.js → Pipeline-945qY9YF.js} +1 -1
  60. package/src/assets/web-panel/assets/{Privacy-DZNQ4RrH.js → Privacy-DPZwI6lJ.js} +1 -1
  61. package/src/assets/web-panel/assets/{ProjectInit-De2MCElf.js → ProjectInit-BPH2NRKc.js} +2 -2
  62. package/src/assets/web-panel/assets/{ProjectSettings-5uHk9wsC.js → ProjectSettings-BZCRNCvb.js} +2 -2
  63. package/src/assets/web-panel/assets/{Projects-XjDfrFLb.js → Projects-BTI__tBt.js} +1 -1
  64. package/src/assets/web-panel/assets/{Providers-Bgwj6gcE.js → Providers-DDCf_nIw.js} +1 -1
  65. package/src/assets/web-panel/assets/{QrScannerModal-gKql9umv.js → QrScannerModal-N6rbqt1r.js} +1 -1
  66. package/src/assets/web-panel/assets/{QuickAsk-BWvmwkh7.js → QuickAsk-DrwjPS7M.js} +1 -1
  67. package/src/assets/web-panel/assets/{Recommend-Uok4hZPd.js → Recommend-Cog9Wd99.js} +1 -1
  68. package/src/assets/web-panel/assets/{RemoteSession-DLJzTk9a.js → RemoteSession-DC9pynjM.js} +3 -3
  69. package/src/assets/web-panel/assets/{Reputation-ZVwIPMSO.js → Reputation-D4th4gKS.js} +1 -1
  70. package/src/assets/web-panel/assets/{Row-CN04HxWR.js → Row-ogiWR6v9.js} +1 -1
  71. package/src/assets/web-panel/assets/{RssFeed-BdSrEG4g.js → RssFeed-uYlk-2Oj.js} +3 -3
  72. package/src/assets/web-panel/assets/{Search-5PwVd0IR.js → Search-DYzzwvtM.js} +1 -1
  73. package/src/assets/web-panel/assets/{Security-DdBo4zjN.js → Security-Da7YOyX6.js} +4 -4
  74. package/src/assets/web-panel/assets/{Services-D3letU7M.js → Services-CLI1VM1X.js} +2 -2
  75. package/src/assets/web-panel/assets/{Skeleton--Jb83f1v.js → Skeleton-DK77dzDC.js} +1 -1
  76. package/src/assets/web-panel/assets/{Skills-Bg-CulHX.js → Skills-C_z7Lmhl.js} +1 -1
  77. package/src/assets/web-panel/assets/{Sla-7QOa7S-X.js → Sla-D6413Q8L.js} +1 -1
  78. package/src/assets/web-panel/assets/{SpeechSettings-DQwppjRw.js → SpeechSettings-C07ydbj2.js} +1 -1
  79. package/src/assets/web-panel/assets/{SyncSettings-dNPQGL-s.js → SyncSettings-DGOm2W22.js} +2 -2
  80. package/src/assets/web-panel/assets/{Tasks-Ggs-Z48q.js → Tasks-Cr_PVyLM.js} +1 -1
  81. package/src/assets/web-panel/assets/{Templates-78TYHgRO.js → Templates-BhL2mZ0t.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tenant-xHwwv9vO.js → Tenant-CPk5oyUr.js} +1 -1
  83. package/src/assets/web-panel/assets/{Terminal-BJXSJfm4.js → Terminal-ClhqUsqV.js} +2 -2
  84. package/src/assets/web-panel/assets/{TimelineRenderer-fLbjRhJw.js → TimelineRenderer-BCC7MV5a.js} +1 -1
  85. package/src/assets/web-panel/assets/{Tokens-oMSBSBmB.js → Tokens-DD1Jt4oO.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trigger-CfnY_wX_.js → Trigger-BCydFvN2.js} +1 -1
  87. package/src/assets/web-panel/assets/{Trust-AdyTgYKO.js → Trust-DDHT1noa.js} +1 -1
  88. package/src/assets/web-panel/assets/{UkeySign-C5K_81dS.js → UkeySign-CWPZC7oI.js} +1 -1
  89. package/src/assets/web-panel/assets/{VideoEditing-vcGVDqki.js → VideoEditing-9Ea0nJR0.js} +1 -1
  90. package/src/assets/web-panel/assets/{Wallet-hhOc3hlY.js → Wallet-BjxhLxTf.js} +4 -4
  91. package/src/assets/web-panel/assets/{WebAuthn-CVloC11s.js → WebAuthn-C6uCye67.js} +4 -4
  92. package/src/assets/web-panel/assets/{WorkflowEditor-CYeE32R5.js → WorkflowEditor-d2g4reYo.js} +1 -1
  93. package/src/assets/web-panel/assets/{chat-CNROR5bN.js → chat-frTc6ScX.js} +1 -1
  94. package/src/assets/web-panel/assets/{colors-Cjx-cnwU.js → colors-B5K6lG3v.js} +1 -1
  95. package/src/assets/web-panel/assets/{compact-item-DOIfelvD.js → compact-item-Cdn46r8D.js} +1 -1
  96. package/src/assets/web-panel/assets/{createContext-DfA7UZ8u.js → createContext-DU1jqVQi.js} +1 -1
  97. package/src/assets/web-panel/assets/devWarning-CP-jEUyw.js +1 -0
  98. package/src/assets/web-panel/assets/{hasIn-BMSCzcfM.js → hasIn-DdNH-aW_.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-CAnvhwg6.js → index-32ajXmTb.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BEMwBBHk.js → index-7ehytj1K.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CVL27kXC.js → index-BC4eGkar.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-FBc_CJBd.js → index-BVLL-mq_.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-dCd8cSUq.js → index-BXKhhx9V.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BemJ2u_E.js → index-BcGwGIaR.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-CVm1_i3t.js → index-Be4OtL-N.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-Cs6l7uRu.js → index-BoFak7le.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DPAWBrMf.js → index-BwlIxAmX.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DLmn0BoK.js → index-CGiwfwB9.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-B7gf77CJ.js → index-CHmkkdfT.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-m2vG9gG_.js → index-CQorP1Ng.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-ClNMrqEY.js → index-CUoNAmE8.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-DHtpHZAQ.js → index-Cj6P9Nst.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-C2K9SNVX.js → index-CtRPi4YC.js} +1 -1
  114. package/src/assets/web-panel/assets/index-CzrCHrFy.js +1 -0
  115. package/src/assets/web-panel/assets/{index-BolaOQxx.js → index-D1qIC4GR.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-BM1y_9hM.js → index-D23xGYjM.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-Cyweh0Hy.js → index-D4l82Me1.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-By61ojUx.js → index-D4miNkWv.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-BbQ2eQ8H.js → index-DIVyTmZ3.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-Icdoqmzt.js → index-DM5FmPZU.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-DjEsxO5h.js → index-DRzBBdQ0.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-B4iYzpkL.js → index-Dca_i7H8.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-C3L5gdiD.js → index-DftZuyA7.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CdYBye8B.js → index-DtBCCkC4.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-AWg-0M1S.js → index-HjCvXHf5.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CEKMcOyU.js → index-LaivuXyF.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BxOk9vkG.js → index-MM4Zz2OE.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-CNC5onm4.js → index-NmEXVjzt.js} +1 -1
  129. package/src/assets/web-panel/assets/index-OJsGskZw.js +1 -0
  130. package/src/assets/web-panel/assets/{index-BUonmKSE.js → index-PjAS0sIZ.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-B5Udeb_6.js → index-VDM2KKo9.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-5-HXgOX5.js → index-cYaWV4Mq.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-DBpWXFed.js → index-gg3QPC3i.js} +3 -3
  134. package/src/assets/web-panel/assets/{index-C7xn0Nub.js → index-kVO_MDm-.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-ti_lnTWl.js → index-mgU3Q-lN.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-CshQbilz.js → index-t_OBzIP4.js} +1 -1
  137. package/src/assets/web-panel/assets/{index-mTpHoEzr.js → index-yZRuZpwK.js} +1 -1
  138. package/src/assets/web-panel/assets/{initDefaultProps-Bu8k7H1Y.js → initDefaultProps-DwLTRNdo.js} +1 -1
  139. package/src/assets/web-panel/assets/{motion-s3iVK41k.js → motion-BchZDXnh.js} +1 -1
  140. package/src/assets/web-panel/assets/{move-B6B-E5Jz.js → move-e8M-zZFl.js} +1 -1
  141. package/src/assets/web-panel/assets/{mtc-parser-BiYgTidA.js → mtc-parser-Ci40fQb3.js} +1 -1
  142. package/src/assets/web-panel/assets/{omit-pU1RdUDA.js → omit-C7110_f2.js} +1 -1
  143. package/src/assets/web-panel/assets/{pickAttrs-Cdoq_SeM.js → pickAttrs-skteMgnM.js} +1 -1
  144. package/src/assets/web-panel/assets/{placementArrow-DIuvAn2K.js → placementArrow-MGZ-lh2B.js} +1 -1
  145. package/src/assets/web-panel/assets/{responsiveObserve-8w16YR2D.js → responsiveObserve-N4Sd7uy-.js} +1 -1
  146. package/src/assets/web-panel/assets/{slide-VZ01cZe3.js → slide-C7HDGRaa.js} +1 -1
  147. package/src/assets/web-panel/assets/{statusUtils-lh8YMN8Z.js → statusUtils-BhRexlgy.js} +1 -1
  148. package/src/assets/web-panel/assets/{styleChecker-CmDyreec.js → styleChecker-ChEoQkN2.js} +1 -1
  149. package/src/assets/web-panel/assets/{useFlexGapSupport-hiJWMU5y.js → useFlexGapSupport-CDbG1s2Y.js} +1 -1
  150. package/src/assets/web-panel/assets/{useFs-Thnu-Iaj.js → useFs-CUIx0tcJ.js} +1 -1
  151. package/src/assets/web-panel/assets/{usePersonalDataHub-LBdOxjwY.js → usePersonalDataHub-bkO7L7ec.js} +1 -1
  152. package/src/assets/web-panel/assets/{vnode-Cg6lFY_X.js → vnode-B_ZvABIG.js} +1 -1
  153. package/src/assets/web-panel/assets/{zoom-DVfsR1YO.js → zoom-D5shjpS8.js} +1 -1
  154. package/src/assets/web-panel/index.html +1 -1
  155. package/src/command-help-index.json +4 -4
  156. package/src/command-manifest.json +214 -50
  157. package/src/command-surface-policy.js +115 -0
  158. package/src/commands/agent.js +3 -3
  159. package/src/commands/background-session.js +145 -0
  160. package/src/commands/mcp.js +125 -7
  161. package/src/commands/serve.js +1 -1
  162. package/src/commands/session-show.js +131 -0
  163. package/src/commands/session.js +50 -106
  164. package/src/commands/skill.js +87 -50
  165. package/src/data/changelog.json +18 -5
  166. package/src/gateways/ws/ws-agent-handler.js +8 -1
  167. package/src/harness/jsonl-session-store.js +428 -25
  168. package/src/harness/mcp-client.js +2652 -404
  169. package/src/harness/session-list-index.js +65 -7
  170. package/src/lazy-dispatch.js +76 -8
  171. package/src/lib/abort-utils.js +68 -0
  172. package/src/lib/agent-core.js +1 -59
  173. package/src/lib/chat-core.js +24 -6
  174. package/src/lib/cowork-mcp-tools.js +47 -2
  175. package/src/lib/cowork-task-runner.js +11 -3
  176. package/src/lib/doctor-checkup.js +9 -7
  177. package/src/lib/learning/skill-improver.js +21 -2
  178. package/src/lib/mcp-call-ledger-store.js +116 -3
  179. package/src/lib/mcp-call-ledger.js +59 -14
  180. package/src/lib/mcp-client.js +1 -0
  181. package/src/lib/mcp-stdio-executable-identity.js +598 -0
  182. package/src/lib/mcp-stdio-execution-authority.js +363 -0
  183. package/src/lib/mcp-tool-metadata.js +436 -0
  184. package/src/lib/mcp-tool-result.js +367 -0
  185. package/src/lib/observability/index.js +71 -0
  186. package/src/lib/pr-link-ledger.js +8 -27
  187. package/src/lib/pr-link-store.js +26 -0
  188. package/src/lib/process-execution-broker/index.js +14 -0
  189. package/src/lib/process-tree-termination.js +253 -0
  190. package/src/lib/public-ide-capability-manifest.js +6 -0
  191. package/src/lib/session-host-snapshot.js +9 -5
  192. package/src/lib/session-projection.js +5 -1
  193. package/src/lib/session-tail.js +31 -1
  194. package/src/lib/skill-budget.js +222 -0
  195. package/src/lib/skill-execution-authority.js +381 -0
  196. package/src/lib/skill-execution-identity.js +39 -2
  197. package/src/lib/skill-loader.js +322 -86
  198. package/src/lib/skill-mcp.js +43 -1
  199. package/src/repl/agent-repl.js +349 -46
  200. package/src/repl/bg-dashboard.js +81 -28
  201. package/src/repl/chat-repl.js +77 -12
  202. package/src/repl/prompt-interactions.js +67 -7
  203. package/src/repl/slash-command-registry.js +3 -4
  204. package/src/runtime/agent-core.js +340 -108
  205. package/src/runtime/agent-runtime.js +26 -3
  206. package/src/runtime/headless-runner.js +150 -23
  207. package/src/runtime/headless-stream.js +309 -59
  208. package/src/runtime/mcp-config.js +157 -10
  209. package/src/runtime/mcp-tool-search.js +0 -0
  210. package/src/runtime/output-backpressure.js +366 -0
  211. package/src/runtime/pipe-safety.js +56 -21
  212. package/src/assets/web-panel/assets/devWarning-ChcCBGs7.js +0 -1
  213. package/src/assets/web-panel/assets/index-Q2IajL54.js +0 -1
  214. package/src/assets/web-panel/assets/index-bRkHgOMc.js +0 -1
  215. package/src/lib/skill-process-broker.js +0 -85
@@ -25,6 +25,7 @@ import path from "path";
25
25
  import { randomUUID } from "node:crypto";
26
26
  import { isPromise, isProxy } from "node:util/types";
27
27
  import { logger } from "../lib/logger.js";
28
+ import { issueMcpStdioExecutionAuthority } from "../lib/mcp-stdio-execution-authority.js";
28
29
  import { getPlanModeManager, PlanState } from "../lib/plan-mode.js";
29
30
  import { createVimState, feedNormalKey } from "../lib/repl-vim.js";
30
31
  import {
@@ -123,9 +124,10 @@ import {
123
124
  } from "../runtime/agent-core.js";
124
125
  import { formatBackgroundTasks } from "./tasks-status.js";
125
126
  import { expandFileRefsAsync } from "../runtime/file-ref-expander.js";
126
- import { prepareVisionTurn } from "../lib/image-input.js";
127
+ import { prepareVisionTurn, resolveVisionLlm } from "../lib/image-input.js";
127
128
  import { composeSystemPrompt } from "../runtime/system-prompt.js";
128
129
  import { installPipeSafety } from "../runtime/pipe-safety.js";
130
+ import { installOutputBackpressure } from "../runtime/output-backpressure.js";
129
131
  import {
130
132
  makeFallbackChatFn,
131
133
  normalizeFallbackModels,
@@ -145,6 +147,10 @@ import {
145
147
  } from "./btw-command.js";
146
148
  import { shouldStreamLive } from "./stream-decision.js";
147
149
  import { emptyTurnNotice } from "./empty-turn-notice.js";
150
+ import {
151
+ createPromptInteractionSurface,
152
+ mergeClipboardImageChips,
153
+ } from "./prompt-interactions.js";
148
154
  import {
149
155
  buildPermissionPrompt,
150
156
  resolveAskIdleTimeoutMs,
@@ -319,6 +325,9 @@ async function executeTool(name, args) {
319
325
  * unit-testable via the `options._coreLoop` injection seam.
320
326
  */
321
327
  export async function agentLoop(messages, options) {
328
+ const writeOut =
329
+ options.writeOut || ((text) => process.stdout.write(String(text)));
330
+ const waitForOutput = options.waitForOutput;
322
331
  // Resume-degenerate role merge (Claude Code 2.1.187 parity), gated by the
323
332
  // one-shot `mergeRoles` flag so it fires only on the first model call after
324
333
  // resuming a session whose prior run produced no assistant response. Collapse
@@ -339,7 +348,7 @@ export async function agentLoop(messages, options) {
339
348
  const onProviderFallback =
340
349
  options.onProviderFallback ||
341
350
  ((info) =>
342
- process.stdout.write(
351
+ writeOut(
343
352
  chalk.yellow(
344
353
  `\n ⚠️ ${info.message || `已从 "${info.from}" 切换到 "${info.to}"`}\n`,
345
354
  ),
@@ -376,7 +385,7 @@ export async function agentLoop(messages, options) {
376
385
  event.type === "managed-checkpoint-error" || event.coverage === "none"
377
386
  ? chalk.yellow
378
387
  : chalk.gray;
379
- process.stdout.write(render(`${managedCheckpointLine}\n`));
388
+ writeOut(render(`${managedCheckpointLine}\n`));
380
389
  } else if (event.type === "checkpoint") {
381
390
  // Remember which file snapshot lines up with the live conversation so
382
391
  // `/rewind <n>` can restore code + conversation together (Claude-Code
@@ -389,7 +398,7 @@ export async function agentLoop(messages, options) {
389
398
  tool: event.tool,
390
399
  });
391
400
  }
392
- process.stdout.write(
401
+ writeOut(
393
402
  chalk.gray(` ⎌ checkpoint ${event.id} (before ${event.tool})\n`),
394
403
  );
395
404
  } else if (event.type === "tool-executing") {
@@ -398,7 +407,7 @@ export async function agentLoop(messages, options) {
398
407
  args: event.args,
399
408
  startedAt: options.now ? options.now() : Date.now(),
400
409
  };
401
- process.stdout.write(
410
+ writeOut(
402
411
  chalk.gray(
403
412
  ` [${event.tool}] ${formatToolArgs(event.tool, event.args)}\n`,
404
413
  ),
@@ -434,9 +443,7 @@ export async function agentLoop(messages, options) {
434
443
  }
435
444
  }
436
445
  if (event.error || event.result?.error) {
437
- process.stdout.write(
438
- chalk.red(` Error: ${event.error || event.result?.error}\n`),
439
- );
446
+ writeOut(chalk.red(` Error: ${event.error || event.result?.error}\n`));
440
447
  // Record policy denials (not plain tool failures) into the caller's
441
448
  // denial log for review via `/permissions denials` (Claude-Code 2.1.193
442
449
  // recent denials). Caller passes the session log (mirrors checkpointMarks)
@@ -473,13 +480,13 @@ export async function agentLoop(messages, options) {
473
480
  const sid = options?.sessionId;
474
481
  const policy = approval.policy || "strict";
475
482
  if (sid && policy === "strict") {
476
- process.stdout.write(
483
+ writeOut(
477
484
  chalk.yellow(
478
485
  ` Hint: relax policy with cc session policy ${sid} --set trusted\n`,
479
486
  ),
480
487
  );
481
488
  } else if (sid) {
482
- process.stdout.write(
489
+ writeOut(
483
490
  chalk.yellow(
484
491
  ` Hint: per-session policy is "${policy}" — see cc session policy ${sid}\n`,
485
492
  ),
@@ -487,26 +494,26 @@ export async function agentLoop(messages, options) {
487
494
  }
488
495
  }
489
496
  } else if (event.result?.success) {
490
- process.stdout.write(chalk.green(` Done\n`));
497
+ writeOut(chalk.green(` Done\n`));
491
498
  }
492
499
  } else if (event.type === "thinking") {
493
500
  // Intermediate-step reasoning (before a tool call) — dimmed, inline.
494
501
  if (process.env.CC_REPL_THINKING !== "0" && event.text) {
495
- process.stdout.write(
502
+ writeOut(
496
503
  "\n" + chalk.dim("💭 " + event.text.replace(/\n/g, "\n ")) + "\n",
497
504
  );
498
505
  }
499
506
  } else if (event.type === "token-usage") {
500
507
  usageEvents.push(event);
501
508
  } else if (event.type === "iteration-warning") {
502
- process.stdout.write(chalk.yellow(`\n ${event.message}\n`));
509
+ writeOut(chalk.yellow(`\n ${event.message}\n`));
503
510
  } else if (event.type === "iteration-budget-exhausted") {
504
- process.stdout.write(
505
- chalk.red(`\n [Budget Exhausted] ${event.budget}\n`),
506
- );
511
+ writeOut(chalk.red(`\n [Budget Exhausted] ${event.budget}\n`));
507
512
  } else if (event.type === "response-complete") {
513
+ await waitForOutput?.();
508
514
  return { content: event.content, usageEvents, thinking: event.thinking };
509
515
  }
516
+ await waitForOutput?.();
510
517
  }
511
518
  return { content: "", usageEvents };
512
519
  }
@@ -1930,7 +1937,12 @@ export function createReplMcpHostRuntimeManager(dependencies = {}) {
1930
1937
  }
1931
1938
 
1932
1939
  const sink = persistLedger
1933
- ? createLedgerSink(sessionId, { appendEvent: appendLedgerEvent })
1940
+ ? createLedgerSink(
1941
+ sessionId,
1942
+ appendLedgerEvent === appendAuthorityEvent
1943
+ ? { recovery }
1944
+ : { appendEvent: appendLedgerEvent },
1945
+ )
1934
1946
  : null;
1935
1947
  const sharedController =
1936
1948
  !verifiedRecovery && current?.sessionId === sessionId
@@ -2064,27 +2076,93 @@ export async function startAgentRepl(options = {}) {
2064
2076
  }
2065
2077
 
2066
2078
  async function startAgentReplInWorkspace(options = {}, startupAdmission) {
2079
+ let failureHandler = null;
2080
+ let disposePipeSafety = null;
2081
+ const flow = installOutputBackpressure({
2082
+ stdout: process.stdout,
2083
+ stderr: process.stderr,
2084
+ maxQueuedBytes: options.outputBackpressureMaxBytes,
2085
+ onFailure: (error) => failureHandler?.(error),
2086
+ });
2087
+ let restored = false;
2088
+ const outputScope = {
2089
+ flow,
2090
+ setFailureHandler(handler) {
2091
+ failureHandler = handler;
2092
+ },
2093
+ setPipeDisposer(disposer) {
2094
+ disposePipeSafety = disposer;
2095
+ },
2096
+ restore() {
2097
+ if (restored) return;
2098
+ restored = true;
2099
+ disposePipeSafety?.();
2100
+ flow.restore();
2101
+ },
2102
+ };
2103
+ try {
2104
+ return await startAgentReplInWorkspaceOwned(
2105
+ options,
2106
+ startupAdmission,
2107
+ outputScope,
2108
+ );
2109
+ } catch (error) {
2110
+ outputScope.restore();
2111
+ throw error;
2112
+ }
2113
+ }
2114
+
2115
+ async function startAgentReplInWorkspaceOwned(
2116
+ options = {},
2117
+ startupAdmission,
2118
+ outputScope,
2119
+ ) {
2067
2120
  // EPIPE guard: if the REPL's stdout is piped and the consumer closes (e.g.
2068
2121
  // `cc agent | head`), the async stream `error` would otherwise crash the
2069
2122
  // process. Route a broken pipe into the REPL's own graceful shutdown (the
2070
2123
  // rl "close" handler — MCP disconnect, kill background tasks) when the
2071
- // interface exists, else exit cleanly. `_replClosing` makes it fire once so a
2072
- // cleanup write that also EPIPEs can't loop. `_replRl` is set when rl is built.
2124
+ // interface and its close handler exist. `_replClosing` makes it fire once so
2125
+ // a cleanup write that also EPIPEs can't loop. An early EPIPE remains pending
2126
+ // until the close handler is attached; it never skips directly to exit.
2073
2127
  let _replRl = null;
2074
2128
  let _replClosing = false;
2075
- installPipeSafety(undefined, () => {
2129
+ let _replCloseReady = false;
2130
+ let _replCleanupStarted = false;
2131
+ let _turnAbort = null;
2132
+ let _replOutputFailure = null;
2133
+ const _requestReplOutputClose = (error) => {
2134
+ if (error && !_replOutputFailure) _replOutputFailure = error;
2135
+ if (error) {
2136
+ process.exitCode = error.code === "EPIPE" ? 0 : 1;
2137
+ _turnAbort?.abort(error);
2138
+ }
2076
2139
  if (_replClosing) return;
2077
2140
  _replClosing = true;
2078
- if (_replRl) {
2141
+ if (_replRl && _replCloseReady) {
2079
2142
  try {
2080
- _replRl.close(); // triggers the graceful "close" cleanup → process.exit
2081
- return;
2143
+ _replRl.close(); // triggers the graceful "close" cleanup
2082
2144
  } catch {
2083
- /* fall through to a bare exit */
2145
+ // The close handler owns shutdown. Leaving exitCode set allows the
2146
+ // event loop to drain without bypassing the teardown lifecycle.
2084
2147
  }
2085
2148
  }
2086
- process.exit(0);
2087
- });
2149
+ };
2150
+ const _replOutputFlow = outputScope.flow;
2151
+ outputScope.setFailureHandler(_requestReplOutputClose);
2152
+ const _disposeReplPipeSafety = installPipeSafety(undefined, () =>
2153
+ _requestReplOutputClose(
2154
+ Object.assign(new Error("REPL output pipe closed"), { code: "EPIPE" }),
2155
+ ),
2156
+ );
2157
+ outputScope.setPipeDisposer(_disposeReplPipeSafety);
2158
+ const _waitForReplOutput = async () => {
2159
+ try {
2160
+ await _replOutputFlow.wait();
2161
+ return true;
2162
+ } catch {
2163
+ return false;
2164
+ }
2165
+ };
2088
2166
  const { useJsonl, candidate: _startupJsonlResume } = startupAdmission;
2089
2167
  let model = options.model || "qwen2.5:7b";
2090
2168
  let provider = options.provider || "ollama";
@@ -2134,6 +2212,8 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
2134
2212
  // an auto-detected image path so the REPL switches to a vision-capable model
2135
2213
  // for that turn only (resolveVisionLlm falls back to the default when unset).
2136
2214
  let _visionModel;
2215
+ let _promptInteractionConfig = {};
2216
+ let _persistPromptSuggestionsEnabled = null;
2137
2217
  // Hard stream inactivity timeout override (config.llm.streamStallTimeoutMs,
2138
2218
  // ms): a stream silent that long is aborted + retried instead of hanging.
2139
2219
  // Unset → agent-core's 180s default (matches cc chat/ask). Set to 0 to
@@ -2148,8 +2228,11 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
2148
2228
  // CC_PERMISSION_ASK_TIMEOUT_MS env > config permissions.askTimeoutMs > off.
2149
2229
  let _askIdleTimeoutMs = resolveAskIdleTimeoutMs();
2150
2230
  try {
2151
- const { loadConfig } = await import("../lib/config-manager.js");
2152
- const _cfg = loadConfig();
2231
+ const configManager = await import("../lib/config-manager.js");
2232
+ const _cfg = configManager.loadConfig();
2233
+ _promptInteractionConfig = _cfg || {};
2234
+ _persistPromptSuggestionsEnabled = (enabled) =>
2235
+ configManager.setConfigValue("cli.promptSuggestions", enabled === true);
2153
2236
  _visionModel = _cfg?.llm?.visionModel || undefined;
2154
2237
  _autoPinCfgValue = _cfg?.context?.autoPin;
2155
2238
  _askIdleTimeoutMs = resolveAskIdleTimeoutMs({
@@ -2979,7 +3062,15 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
2979
3062
  let connected = 0;
2980
3063
  for (const [name, cfg] of serverEntries) {
2981
3064
  try {
2982
- await _bundleMcpClient.connect(name, cfg);
3065
+ const authorizedConfig = { ...cfg };
3066
+ authorizedConfig.mcpStdioExecutionAuthority =
3067
+ issueMcpStdioExecutionAuthority({
3068
+ serverName: name,
3069
+ config: authorizedConfig,
3070
+ approvalKind: "explicit-config",
3071
+ approvalSource: `agent-bundle:${path.resolve(options.bundlePath)}`,
3072
+ });
3073
+ await _bundleMcpClient.connect(name, authorizedConfig);
2983
3074
  connected += 1;
2984
3075
  } catch (mcpErr) {
2985
3076
  logger.log(
@@ -3588,11 +3679,13 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
3588
3679
  "/note-next",
3589
3680
  "/output-style",
3590
3681
  "/permissions",
3682
+ "/paste-image",
3591
3683
  "/plan",
3592
3684
  "/pr-comments",
3593
3685
  "/profile",
3594
3686
  "/provider",
3595
3687
  "/quit",
3688
+ "/recap",
3596
3689
  "/reindex",
3597
3690
  "/release-notes",
3598
3691
  "/reload-plugins",
@@ -3606,7 +3699,9 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
3606
3699
  "/stats",
3607
3700
  "/status",
3608
3701
  "/statusline",
3702
+ "/stash",
3609
3703
  "/sub-agents",
3704
+ "/suggestions",
3610
3705
  "/task",
3611
3706
  "/tasks",
3612
3707
  "/terminal-setup",
@@ -3617,6 +3712,7 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
3617
3712
  "/ultrathink",
3618
3713
  "/vim",
3619
3714
  "/voice",
3715
+ "/editor",
3620
3716
  ],
3621
3717
  // User/project custom commands (.claude/commands/*.md) join TAB completion
3622
3718
  // alongside the built-ins above. Sync + best-effort; the completer
@@ -3660,6 +3756,28 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
3660
3756
  // "close" cleanup instead of a bare exit.
3661
3757
  _replRl = rl;
3662
3758
 
3759
+ // Prompt-side interaction modules share the production readline surface.
3760
+ // A per-REPL registry avoids singleton handlers retaining an old session.
3761
+ const _promptInteractionSurface = createPromptInteractionSurface({
3762
+ readline: rl,
3763
+ config: _promptInteractionConfig,
3764
+ keybindings: _promptInteractionConfig,
3765
+ getSessionId: () => sessionId,
3766
+ getSuggestionContext: () => ({ messages: messages.slice() }),
3767
+ persistSuggestionEnabled: _persistPromptSuggestionsEnabled,
3768
+ clipboardBinding: options.clipboardBinding || null,
3769
+ generateSuggestions: options.generatePromptSuggestions,
3770
+ suggestionDebounceMs: options.promptSuggestionDebounceMs,
3771
+ screenReader: _screenReaderMode,
3772
+ write: (text) => process.stdout.write(String(text)),
3773
+ writeError: (text) => process.stderr.write(String(text)),
3774
+ getColumns: () => process.stdout.columns || 80,
3775
+ });
3776
+ const _promptInteractions = _promptInteractionSurface.controller;
3777
+ for (const diagnostic of _promptInteractions.diagnostics().keybindingErrors) {
3778
+ logger.warn(`prompt keybinding ignored: ${diagnostic}`);
3779
+ }
3780
+
3663
3781
  // MCP elicitation in the interactive REPL uses the same readline surface as
3664
3782
  // approvals, while MCPClient retains the protocol-level response shape.
3665
3783
  const mcpElicitationClients = [_adhocMcp?.mcpClient, _bundleMcpClient].filter(
@@ -3757,9 +3875,34 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
3757
3875
  // Vim-mode plumbing: capture readline's OWN keypress listeners now so we can
3758
3876
  // suspend them while in NORMAL mode (the engine drives editing then) and
3759
3877
  // reattach them for INSERT mode (readline's rich editing/history/completion).
3760
- const _rlKeypressListeners = process.stdin.isTTY
3878
+ const _nativeReadlineKeypressListeners = process.stdin.isTTY
3761
3879
  ? process.stdin.listeners("keypress").slice()
3762
3880
  : [];
3881
+ const _consumedReadlineKeypresses = new WeakSet();
3882
+ const _consumeReadlineKeypress = (key) => {
3883
+ if (key && typeof key === "object") _consumedReadlineKeypresses.add(key);
3884
+ };
3885
+ const _rlKeypressListeners = _nativeReadlineKeypressListeners.map(
3886
+ (listener) =>
3887
+ function guardedReadlineKeypress(input, key) {
3888
+ if (
3889
+ key &&
3890
+ typeof key === "object" &&
3891
+ _consumedReadlineKeypresses.has(key)
3892
+ ) {
3893
+ return;
3894
+ }
3895
+ return listener.call(this, input, key);
3896
+ },
3897
+ );
3898
+ if (process.stdin.isTTY) {
3899
+ for (const listener of _nativeReadlineKeypressListeners) {
3900
+ process.stdin.removeListener("keypress", listener);
3901
+ }
3902
+ for (const listener of _rlKeypressListeners) {
3903
+ process.stdin.on("keypress", listener);
3904
+ }
3905
+ }
3763
3906
  const _suspendReadlineKeys = () => {
3764
3907
  for (const l of _rlKeypressListeners)
3765
3908
  process.stdin.removeListener("keypress", l);
@@ -3807,13 +3950,15 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
3807
3950
  // seam (throwIfAborted at each iteration); partial conversation is kept.
3808
3951
  // Idle Esc presses (no active turn) are ignored, and escape-prefixed key
3809
3952
  // sequences (arrows etc.) never reach here as bare "escape".
3810
- let _turnAbort = null;
3953
+ _turnAbort = null;
3811
3954
  let _lastIdleEscAt = 0;
3955
+ let _replKeypressHandler = null;
3812
3956
  if (process.stdin.isTTY) {
3813
- process.stdin.on("keypress", (_str, key) => {
3957
+ _replKeypressHandler = (_str, key) => {
3814
3958
  const k = key || {};
3815
3959
  // 1) Turn abort always wins, regardless of vim mode.
3816
3960
  if (k.name === "escape" && !k.meta && _turnAbort) {
3961
+ _consumeReadlineKeypress(key);
3817
3962
  process.stdout.write(chalk.yellow("\n⎋ interrupting…\n"));
3818
3963
  try {
3819
3964
  _turnAbort.abort();
@@ -3849,6 +3994,7 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
3849
3994
  const isShiftTab =
3850
3995
  (k.name === "tab" && k.shift) || k.sequence === "\u001b[Z";
3851
3996
  if (isShiftTab) {
3997
+ _consumeReadlineKeypress(key);
3852
3998
  if (
3853
3999
  _approvalGate &&
3854
4000
  sessionId &&
@@ -3877,14 +4023,30 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
3877
4023
  return;
3878
4024
  }
3879
4025
 
4026
+ // 1.75) Prompt interaction keybindings share the controller used by
4027
+ // slash commands. They are idle-only and stay out of Vim NORMAL mode,
4028
+ // where the modal editor owns every key.
4029
+ if (
4030
+ !_turnAbort &&
4031
+ (!_vimEnabled || !_vim) &&
4032
+ _promptInteractions.handleKeypress(_str, k)
4033
+ ) {
4034
+ _consumeReadlineKeypress(key);
4035
+ return;
4036
+ }
4037
+
3880
4038
  // 2) Vim mode: modal editing on the current input line.
3881
4039
  if (_vimEnabled && !_turnAbort) {
3882
4040
  if (!_vim) {
3883
4041
  // INSERT mode — readline owns the keys; Esc switches to NORMAL.
3884
- if (k.name === "escape" && !k.meta) _vimEnterNormal();
4042
+ if (k.name === "escape" && !k.meta) {
4043
+ _consumeReadlineKeypress(key);
4044
+ _vimEnterNormal();
4045
+ }
3885
4046
  return;
3886
4047
  }
3887
4048
  // NORMAL mode — readline suspended; the engine interprets every key.
4049
+ _consumeReadlineKeypress(key);
3888
4050
  const res = feedNormalKey(_vim, _str || "", k);
3889
4051
  if (res.submit) {
3890
4052
  // Hand the line to readline as a normal Enter (fires 'line', clears).
@@ -3906,6 +4068,7 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
3906
4068
 
3907
4069
  // 3) Non-vim: double-Esc while idle → rewind picker shortcut.
3908
4070
  if (k.name !== "escape" || k.meta) return;
4071
+ _consumeReadlineKeypress(key);
3909
4072
  const nowTs = Date.now();
3910
4073
  if (nowTs - _lastIdleEscAt < 600) {
3911
4074
  _lastIdleEscAt = 0;
@@ -3930,7 +4093,8 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
3930
4093
  } else {
3931
4094
  _lastIdleEscAt = nowTs;
3932
4095
  }
3933
- });
4096
+ };
4097
+ process.stdin.prependListener("keypress", _replKeypressHandler);
3934
4098
  }
3935
4099
 
3936
4100
  logger.log(chalk.bold("\nChainlessChain Agent"));
@@ -4007,7 +4171,9 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
4007
4171
  });
4008
4172
  // Custom command wins; otherwise the built-in context-usage render.
4009
4173
  if (_slCfg) {
4010
- return _sl.renderStatusLine(_slCfg, context, { cwd: process.cwd() });
4174
+ return _sl.renderStatusLine(_slCfg, context, {
4175
+ cwd: process.cwd(),
4176
+ });
4011
4177
  }
4012
4178
  const line = _sl.renderDefaultStatusLine(context);
4013
4179
  return line && line.trim() ? line : null;
@@ -4206,6 +4372,19 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
4206
4372
  return;
4207
4373
  }
4208
4374
 
4375
+ // The interaction commands are registered through the shared registry,
4376
+ // while the mature REPL handlers below remain on their existing direct
4377
+ // paths. --disable-slash-commands replaces `trimmed` with a sentinel, so
4378
+ // it cannot accidentally reach this dispatcher.
4379
+ if (trimmed.startsWith("/")) {
4380
+ const commandName = trimmed.split(/\s+/, 1)[0];
4381
+ if (_promptInteractionSurface.commandNames.has(commandName)) {
4382
+ await _promptInteractionSurface.dispatchSlash(trimmed);
4383
+ prompt();
4384
+ return;
4385
+ }
4386
+ }
4387
+
4209
4388
  if (trimmed === "/help") {
4210
4389
  logger.log(chalk.bold("\nCommands:"));
4211
4390
  logger.log(
@@ -4264,6 +4443,21 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
4264
4443
  logger.log(
4265
4444
  ` ${chalk.cyan("/output-style")} Response persona (/output-style <name|list>; explanatory/learning built-in)`,
4266
4445
  );
4446
+ logger.log(
4447
+ ` ${chalk.cyan("/editor")} Edit a draft prompt in an external editor`,
4448
+ );
4449
+ logger.log(
4450
+ ` ${chalk.cyan("/stash")} Stash/list/pop/clear draft prompts`,
4451
+ );
4452
+ logger.log(
4453
+ ` ${chalk.cyan("/recap")} Show a lightweight session recap`,
4454
+ );
4455
+ logger.log(
4456
+ ` ${chalk.cyan("/suggestions")} Prompt suggestions on|off|status|refresh`,
4457
+ );
4458
+ logger.log(
4459
+ ` ${chalk.cyan("/paste-image")} Attach a clipboard image from a supporting host`,
4460
+ );
4267
4461
  logger.log(
4268
4462
  ` ${chalk.cyan("/config")} Show config; ${chalk.cyan("/config <key>")} read, ${chalk.cyan("/config <key>=<val>")} set, ${chalk.cyan("/config --help")} keys`,
4269
4463
  );
@@ -4313,7 +4507,7 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
4313
4507
  ` ${chalk.cyan("/add-dir")} Add an extra working root (/add-dir <dir>; no arg lists roots)`,
4314
4508
  );
4315
4509
  logger.log(
4316
- ` ${chalk.cyan("/reload-skills")} Re-scan skill layers without restarting`,
4510
+ ` ${chalk.cyan("/reload-skills")} Revoke process Skill grants and re-scan layers`,
4317
4511
  );
4318
4512
  logger.log(
4319
4513
  ` ${chalk.cyan("/reload-plugins")} Re-scan installed plugins after add/trust/upgrade`,
@@ -5173,7 +5367,11 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
5173
5367
  dispatchAsyncHooks(
5174
5368
  _settingsHooks,
5175
5369
  "CwdChanged",
5176
- { old_cwd: oldCwd, cwd: newCwd, session_id: sessionId || null },
5370
+ {
5371
+ old_cwd: oldCwd,
5372
+ cwd: newCwd,
5373
+ session_id: sessionId || null,
5374
+ },
5177
5375
  { cwd: newCwd, supervisor: _asyncHookSupervisor },
5178
5376
  );
5179
5377
  } catch {
@@ -5737,6 +5935,11 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
5737
5935
  // layers (incl. .claude/skills) without restarting the session.
5738
5936
  if (trimmed === "/reload-skills") {
5739
5937
  try {
5938
+ _replSkillLoader.revokeExecutionAuthorizations?.({
5939
+ message:
5940
+ "Skill execution authorization was revoked by /reload-skills",
5941
+ reasonCode: "reload-skills",
5942
+ });
5740
5943
  _replSkillLoader.clearCache();
5741
5944
  const n = _replSkillLoader.loadAll().length;
5742
5945
  _replBaseSystem = _buildReplBaseSystem();
@@ -5746,7 +5949,7 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
5746
5949
  _persistReplContextSources();
5747
5950
  logger.log(
5748
5951
  chalk.green(
5749
- `✔ skills reloaded — ${n} available (${_replSkillLoader.getLayerPaths().length} layers re-scanned)`,
5952
+ `✔ Skill grants revoked and skills reloaded — ${n} available (${_replSkillLoader.getLayerPaths().length} layers re-scanned)`,
5750
5953
  ),
5751
5954
  );
5752
5955
  } catch (err) {
@@ -5959,7 +6162,9 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
5959
6162
  logger.log(chalk.gray(l));
5960
6163
  } else {
5961
6164
  // set — createReplGoal throws on a malformed spec (caught below).
5962
- _sessionGoal = rg.createReplGoal(cmd.spec, { now: () => Date.now() });
6165
+ _sessionGoal = rg.createReplGoal(cmd.spec, {
6166
+ now: () => Date.now(),
6167
+ });
5963
6168
  for (const l of rg.renderGoalStart(_sessionGoal))
5964
6169
  logger.log(chalk.cyan(l));
5965
6170
  }
@@ -7364,6 +7569,35 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
7364
7569
  return;
7365
7570
  }
7366
7571
 
7572
+ // Explicit clipboard attachment chips are consumed only when a real model
7573
+ // turn is about to start. They compose with auto-detected path images and
7574
+ // enter the same provider-neutral multimodal message/vision override.
7575
+ const clipboardImageChips = _promptInteractions.takeClipboardImageChips();
7576
+ const clipboardMerge = mergeClipboardImageChips(
7577
+ _userMessageContent,
7578
+ clipboardImageChips,
7579
+ );
7580
+ if (clipboardMerge.attached > 0) {
7581
+ _userMessageContent = clipboardMerge.content;
7582
+ if (!_visionLlm) {
7583
+ _visionLlm = resolveVisionLlm({
7584
+ hasImage: true,
7585
+ flags: {},
7586
+ llm: {
7587
+ provider,
7588
+ baseUrl,
7589
+ apiKey,
7590
+ visionModel: _visionModel,
7591
+ },
7592
+ });
7593
+ }
7594
+ logger.info(
7595
+ chalk.gray(
7596
+ `[image] ${clipboardMerge.attached} clipboard image(s) attached -> vision model ${_visionLlm.model}`,
7597
+ ),
7598
+ );
7599
+ }
7600
+
7367
7601
  // Add user message (keep the object ref so /note-next can be injected for
7368
7602
  // this turn's model call and then stripped before persistence).
7369
7603
  const _userMsg = { role: "user", content: _userMessageContent };
@@ -7426,7 +7660,17 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
7426
7660
  .map(([k, v]) => `${k}: ${v}`);
7427
7661
  // Append context to the last user message
7428
7662
  const lastMsg = messages[messages.length - 1];
7429
- lastMsg.content += `\n\n[Context — user provided: ${parts.join(", ")}]`;
7663
+ const slotContext = `[Context - user provided: ${parts.join(", ")}]`;
7664
+ if (Array.isArray(lastMsg.content)) {
7665
+ const textPart = lastMsg.content.find(
7666
+ (part) => part?.type === "text",
7667
+ );
7668
+ if (textPart)
7669
+ textPart.text = `${textPart.text}\n\n${slotContext}`;
7670
+ else lastMsg.content.unshift({ type: "text", text: slotContext });
7671
+ } else {
7672
+ lastMsg.content = `${lastMsg.content}\n\n${slotContext}`;
7673
+ }
7430
7674
  logger.info(chalk.gray(`[slot-fill] ${parts.join(", ")}`));
7431
7675
  }
7432
7676
  }
@@ -7530,22 +7774,29 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
7530
7774
  // reasoning dimmed before it. Skipped (left undefined) in replay mode.
7531
7775
  let _liveStreamed = false;
7532
7776
  let _liveThinkStarted = false;
7777
+ const _settleLiveWrite = (blocked) =>
7778
+ blocked ? _replOutputFlow.wait() : undefined;
7533
7779
  const liveOpts = _streamLive
7534
7780
  ? {
7535
7781
  onToken: (t) => {
7536
7782
  // Separate the answer from the dimmed reasoning above it (once).
7537
- if (!_liveStreamed && _liveThinkStarted)
7538
- process.stdout.write("\n");
7783
+ let blocked = false;
7784
+ if (!_liveStreamed && _liveThinkStarted) {
7785
+ blocked = process.stdout.write("\n") === false;
7786
+ }
7539
7787
  _liveStreamed = true;
7540
- process.stdout.write(t);
7788
+ blocked = process.stdout.write(t) === false || blocked;
7789
+ return _settleLiveWrite(blocked);
7541
7790
  },
7542
7791
  onThinking: (t) => {
7543
7792
  if (process.env.CC_REPL_THINKING === "0") return;
7793
+ let blocked = false;
7544
7794
  if (!_liveThinkStarted) {
7545
7795
  process.stdout.write(chalk.dim("💭 "));
7546
7796
  _liveThinkStarted = true;
7547
7797
  }
7548
- process.stdout.write(chalk.dim(t));
7798
+ blocked = process.stdout.write(chalk.dim(t)) === false || blocked;
7799
+ return _settleLiveWrite(blocked);
7549
7800
  },
7550
7801
  }
7551
7802
  : {};
@@ -7577,7 +7828,9 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
7577
7828
  if (_settingsHooks && !_asyncHookSupervisor) {
7578
7829
  const { AsyncHookSupervisor } =
7579
7830
  await import("../lib/async-hook-supervisor.js");
7580
- _asyncHookSupervisor = new AsyncHookSupervisor({ persistStats: true });
7831
+ _asyncHookSupervisor = new AsyncHookSupervisor({
7832
+ persistStats: true,
7833
+ });
7581
7834
  }
7582
7835
  const activeMcpRuntime = _activateMcpHostRuntime();
7583
7836
  const activeRawMcpClient = activeMcpRuntime.rawClient || undefined;
@@ -7587,6 +7840,7 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
7587
7840
  thinking: reasoning,
7588
7841
  } = await agentLoop(messages, {
7589
7842
  ...liveOpts,
7843
+ waitForOutput: _replOutputFlow.wait,
7590
7844
  mergeRoles: _mergeRolesThisTurn,
7591
7845
  // Visible auto-retry feedback (Claude-Code 2.1.181): when the model's
7592
7846
  // streaming call hits a transient connection drop and retries, tell the
@@ -7868,6 +8122,15 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
7868
8122
  );
7869
8123
  }
7870
8124
 
8125
+ // Generate follow-up suggestions from the settled assistant turn. The
8126
+ // controller snapshots the actual conversation context and debounces in
8127
+ // the background, so readline and persistence remain non-blocking.
8128
+ const suggestionRun = _promptInteractions.scheduleSuggestions({
8129
+ messages: messages.slice(),
8130
+ lastAssistantText: effectiveResponse || "",
8131
+ });
8132
+ void suggestionRun.promise;
8133
+
7871
8134
  // Auto-save session
7872
8135
  if (sessionId) {
7873
8136
  try {
@@ -8012,6 +8275,7 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
8012
8275
  };
8013
8276
 
8014
8277
  rl.on("line", async (input) => {
8278
+ if (!(await _waitForReplOutput())) return;
8015
8279
  if (_processingLine) {
8016
8280
  const concurrentBtw = _slashCommandsDisabled
8017
8281
  ? null
@@ -8035,10 +8299,12 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
8035
8299
  _processingLine = true;
8036
8300
  try {
8037
8301
  await handleLine(input);
8302
+ if (!(await _waitForReplOutput())) return;
8038
8303
  while (_pendingLines.length) {
8039
8304
  const next = _pendingLines.shift();
8040
8305
  logger.log(chalk.cyan(`▶ running queued input: ${next}`));
8041
8306
  await handleLine(next);
8307
+ if (!(await _waitForReplOutput())) return;
8042
8308
  }
8043
8309
  } finally {
8044
8310
  _processingLine = false;
@@ -8046,6 +8312,18 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
8046
8312
  });
8047
8313
 
8048
8314
  rl.on("close", async () => {
8315
+ if (_replCleanupStarted) return;
8316
+ _replCleanupStarted = true;
8317
+ _replClosing = true;
8318
+ _promptInteractions.dispose();
8319
+ if (process.stdin.isTTY) {
8320
+ if (_replKeypressHandler) {
8321
+ process.stdin.removeListener("keypress", _replKeypressHandler);
8322
+ }
8323
+ for (const listener of _rlKeypressListeners) {
8324
+ process.stdin.removeListener("keypress", listener);
8325
+ }
8326
+ }
8049
8327
  // Leave the alternate screen buffer first so the terminal is restored to
8050
8328
  // the user's scrollback no matter how the REPL exits.
8051
8329
  if (_tuiMode === "fullscreen" && process.stdout.isTTY) {
@@ -8224,6 +8502,31 @@ async function startAgentReplInWorkspace(options = {}, startupAdmission) {
8224
8502
  } catch (_e) {
8225
8503
  // Non-critical
8226
8504
  }
8227
- process.exit(0);
8505
+ if (!_replOutputFailure) {
8506
+ try {
8507
+ await _replOutputFlow.wait();
8508
+ } catch (error) {
8509
+ _replOutputFailure ||= error;
8510
+ }
8511
+ }
8512
+ const outputExitCode = _replOutputFailure
8513
+ ? _replOutputFailure.code === "EPIPE"
8514
+ ? 0
8515
+ : 1
8516
+ : 0;
8517
+ outputScope.restore();
8518
+ process.exit(outputExitCode);
8228
8519
  });
8520
+ _replCloseReady = true;
8521
+ if (_replClosing) {
8522
+ // stdout may have broken before readline and its cleanup handler were fully
8523
+ // initialized. Close only now so the complete async teardown runs.
8524
+ queueMicrotask(() => {
8525
+ try {
8526
+ rl.close();
8527
+ } catch {
8528
+ process.exitCode = 0;
8529
+ }
8530
+ });
8531
+ }
8229
8532
  }