chainlesschain 0.162.30 → 0.162.32

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 (179) hide show
  1. package/package.json +2 -2
  2. package/src/assets/web-panel/assets/{AIOps-CsNttUU7.js → AIOps-Cg_uWAVl.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-lgohjckQ.js → ActionButton-DSFtQ1c2.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-ccV3LAca.js → Analytics-BMxpkw8y.js} +3 -3
  5. package/src/assets/web-panel/assets/AppLayout-tgVxlmsx.js +9 -0
  6. package/src/assets/web-panel/assets/{Audit-B1gFM5U9.js → Audit-DwzGllcp.js} +1 -1
  7. package/src/assets/web-panel/assets/{Backup-BeWE3ERo.js → Backup-BG28Y2MV.js} +1 -1
  8. package/src/assets/web-panel/assets/{BaseInput-CDkPsNG2.js → BaseInput-TXthbazl.js} +1 -1
  9. package/src/assets/web-panel/assets/{Chat-ztb9ia6e.js → Chat-D096SxaD.js} +4 -4
  10. package/src/assets/web-panel/assets/{ChatBubbleRenderer-Dlw_6n3M.js → ChatBubbleRenderer-PIx0Eu9I.js} +1 -1
  11. package/src/assets/web-panel/assets/{Checkbox-BcfRBlIY.js → Checkbox-Czttw1JS.js} +1 -1
  12. package/src/assets/web-panel/assets/{Codegen-DOs99xkr.js → Codegen-DZtMgv4q.js} +1 -1
  13. package/src/assets/web-panel/assets/{Col-D1X6tYlj.js → Col-D3DnfExY.js} +1 -1
  14. package/src/assets/web-panel/assets/{Community-DTksIWtz.js → Community-Bj5AdwqY.js} +1 -1
  15. package/src/assets/web-panel/assets/{Compact-DIJtAYBO.js → Compact-BQ8Zszub.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compliance-BBf7LF_k.js → Compliance-DXacb34n.js} +1 -1
  17. package/src/assets/web-panel/assets/{Cowork-UBPXQ40s.js → Cowork-BgMUBTkw.js} +2 -2
  18. package/src/assets/web-panel/assets/{Cron-CkRm1jPB.js → Cron-fqBWOqlN.js} +2 -2
  19. package/src/assets/web-panel/assets/{Crosschain-qALlTl7e.js → Crosschain-E4oa1MWy.js} +1 -1
  20. package/src/assets/web-panel/assets/{DID-CqyqVS6E.js → DID-pwgfYZaV.js} +2 -2
  21. package/src/assets/web-panel/assets/Dashboard-n8mdLFIR.js +3 -0
  22. package/src/assets/web-panel/assets/{Dropdown-Cb5UzbSZ.js → Dropdown--6DYqxk7.js} +1 -1
  23. package/src/assets/web-panel/assets/{EmailListRenderer-CarBq8Fk.js → EmailListRenderer-CkjQluz3.js} +1 -1
  24. package/src/assets/web-panel/assets/{FamilyGuardDashboard-CSiGXaZz.js → FamilyGuardDashboard-u-QTQ-OC.js} +1 -1
  25. package/src/assets/web-panel/assets/{Federation-DUxhVoBN.js → Federation-D219M5Qc.js} +1 -1
  26. package/src/assets/web-panel/assets/{FormItemContext-BoMQpkhx.js → FormItemContext-BBU_aopC.js} +1 -1
  27. package/src/assets/web-panel/assets/{GenericCardRenderer-DTVqC_CX.js → GenericCardRenderer-pTMCIHcM.js} +1 -1
  28. package/src/assets/web-panel/assets/{Git-C_XuPtK5.js → Git-ClcCARWt.js} +2 -2
  29. package/src/assets/web-panel/assets/{Governance-BZyqlqz-.js → Governance-CvUi3I93.js} +1 -1
  30. package/src/assets/web-panel/assets/{Inference-DdZVUimI.js → Inference-DT-a4pVg.js} +1 -1
  31. package/src/assets/web-panel/assets/{KnowledgeGraph-IzZ-jnCn.js → KnowledgeGraph-DHMs2LY8.js} +1 -1
  32. package/src/assets/web-panel/assets/{Logs-koTK6eNc.js → Logs-D2s4eV1N.js} +2 -2
  33. package/src/assets/web-panel/assets/{Marketplace-6zpJ1L8n.js → Marketplace-YC5-fx-6.js} +1 -1
  34. package/src/assets/web-panel/assets/{McpTools-Ywc4IVks.js → McpTools-7JHTEC4T.js} +3 -3
  35. package/src/assets/web-panel/assets/{Memory-C_zB9dUa.js → Memory-BudotVLD.js} +2 -2
  36. package/src/assets/web-panel/assets/{MobileBridge-Nc05r24L.js → MobileBridge-CAiRyLVU.js} +2 -2
  37. package/src/assets/web-panel/assets/{MobileProjects-BJGxL526.js → MobileProjects-CrJJOCFw.js} +1 -1
  38. package/src/assets/web-panel/assets/{Mtc-Im7SIcz1.js → Mtc-d0iY0CeK.js} +5 -5
  39. package/src/assets/web-panel/assets/{MtcAudit-BFFzvzMD.js → MtcAudit-aI2cG1UP.js} +4 -4
  40. package/src/assets/web-panel/assets/{Multisig-CcNEbycq.js → Multisig-4bF70khG.js} +3 -3
  41. package/src/assets/web-panel/assets/{NLProgramming-CDH6OTXN.js → NLProgramming-CwLib1S7.js} +1 -1
  42. package/src/assets/web-panel/assets/{Notes-Dqg3QXcU.js → Notes-Wt7AuFRU.js} +3 -3
  43. package/src/assets/web-panel/assets/{NotificationSettings-CDVmK1eU.js → NotificationSettings-D081vV_7.js} +1 -1
  44. package/src/assets/web-panel/assets/OrderTableRenderer-DCPei1L9.js +1 -0
  45. package/src/assets/web-panel/assets/{Organization-DJb9bRQS.js → Organization-BNEsUNdP.js} +4 -4
  46. package/src/assets/web-panel/assets/{Overflow-CK7Q5dje.js → Overflow-B_1iUXDD.js} +1 -1
  47. package/src/assets/web-panel/assets/{P2P-CJIyYfwc.js → P2P-Dbc-kNwJ.js} +2 -2
  48. package/src/assets/web-panel/assets/{PdhVaultBrowser-uqRULcuw.js → PdhVaultBrowser-D8Xh289k.js} +3 -3
  49. package/src/assets/web-panel/assets/{Permissions-Crvwt6bq.js → Permissions-C77mM6-n.js} +4 -4
  50. package/src/assets/web-panel/assets/{PersonalDataHub-DcN5OWzg.js → PersonalDataHub-Dj0J3r_K.js} +3 -3
  51. package/src/assets/web-panel/assets/{Pipeline-DfWJvvJW.js → Pipeline-B6F0WQ2C.js} +1 -1
  52. package/src/assets/web-panel/assets/{Privacy-DepD0S3v.js → Privacy-eDKOkyyq.js} +1 -1
  53. package/src/assets/web-panel/assets/{ProjectInit-B7OKhH27.js → ProjectInit-DAWwhr5_.js} +2 -2
  54. package/src/assets/web-panel/assets/{ProjectSettings-BJ4ueRFv.js → ProjectSettings-DwdK8k6I.js} +2 -2
  55. package/src/assets/web-panel/assets/Projects-Cb3p5QAP.js +1 -0
  56. package/src/assets/web-panel/assets/{Providers-Dl0FT1S3.js → Providers--DcYxQfN.js} +1 -1
  57. package/src/assets/web-panel/assets/{QuickAsk-V2hYLhfp.js → QuickAsk-DU268niT.js} +1 -1
  58. package/src/assets/web-panel/assets/{Recommend-8Kaiodgv.js → Recommend-ChnflhV1.js} +1 -1
  59. package/src/assets/web-panel/assets/{Reputation-CsxB3JGg.js → Reputation-DSsY3bQG.js} +1 -1
  60. package/src/assets/web-panel/assets/{Row-6-x7tEYq.js → Row-Zb-EjmgQ.js} +1 -1
  61. package/src/assets/web-panel/assets/{RssFeed-Buv6f5tw.js → RssFeed-CGLiixZB.js} +3 -3
  62. package/src/assets/web-panel/assets/{Search-ABrDz84n.js → Search-Dhr_po-U.js} +1 -1
  63. package/src/assets/web-panel/assets/{Security-DqOJmz18.js → Security-GMYNhGsR.js} +4 -4
  64. package/src/assets/web-panel/assets/{Services-Cq4Tda3q.js → Services-DiOpnVY0.js} +2 -2
  65. package/src/assets/web-panel/assets/{Skeleton-n74QlyYq.js → Skeleton-DG3ez6ME.js} +1 -1
  66. package/src/assets/web-panel/assets/{Skills-CC0iozL5.js → Skills-DZGptytP.js} +1 -1
  67. package/src/assets/web-panel/assets/{Sla-hwRgJ99Z.js → Sla-CtGpE3xA.js} +1 -1
  68. package/src/assets/web-panel/assets/{SpeechSettings-B6Bs6_-8.js → SpeechSettings-DQFw6Cf9.js} +1 -1
  69. package/src/assets/web-panel/assets/{SyncSettings-CTp2dZ0z.js → SyncSettings-C8X78RpX.js} +2 -2
  70. package/src/assets/web-panel/assets/{Tasks-D70Lis6S.js → Tasks-DtVkhWCV.js} +1 -1
  71. package/src/assets/web-panel/assets/{Templates-Cags0ssw.js → Templates-SF9_ZWsV.js} +1 -1
  72. package/src/assets/web-panel/assets/{Tenant-BxCMzzGt.js → Tenant-BbIQSVZz.js} +1 -1
  73. package/src/assets/web-panel/assets/{Terminal-v05SDqHd.js → Terminal-DKr5zDwu.js} +2 -2
  74. package/src/assets/web-panel/assets/{TimelineRenderer-BLUDHbBL.js → TimelineRenderer-BtLaNaWr.js} +1 -1
  75. package/src/assets/web-panel/assets/{Tokens-D-xKLJYv.js → Tokens-CfYbk2NG.js} +1 -1
  76. package/src/assets/web-panel/assets/{Trigger-B47tVIbH.js → Trigger-BLX_XDP0.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trust-DmRU9kfs.js → Trust-BWxUv9PR.js} +1 -1
  78. package/src/assets/web-panel/assets/{UkeySign-DzgSGs-c.js → UkeySign-DRwTyQD4.js} +1 -1
  79. package/src/assets/web-panel/assets/{VideoEditing-C6qu58up.js → VideoEditing-BsC4VOSo.js} +1 -1
  80. package/src/assets/web-panel/assets/{Wallet-Dh8ZWx8f.js → Wallet-CSsO1NJU.js} +4 -4
  81. package/src/assets/web-panel/assets/{WebAuthn-DFHOVuAY.js → WebAuthn-z1MxiFzS.js} +4 -4
  82. package/src/assets/web-panel/assets/{WorkflowEditor-B_fyQ3Y_.js → WorkflowEditor-B1vV7uuJ.js} +1 -1
  83. package/src/assets/web-panel/assets/{chat-BR-WxnCQ.js → chat-C0NJRaL2.js} +1 -1
  84. package/src/assets/web-panel/assets/{colors-C-6RysQe.js → colors-CHRiteWF.js} +1 -1
  85. package/src/assets/web-panel/assets/{compact-item-B_9_SCKN.js → compact-item-2XmBBKPD.js} +1 -1
  86. package/src/assets/web-panel/assets/{createContext-D6rklIbE.js → createContext-DkedHC38.js} +1 -1
  87. package/src/assets/web-panel/assets/devWarning-DmNpkOdC.js +1 -0
  88. package/src/assets/web-panel/assets/{hasIn-BrotgSvd.js → hasIn-Bpn9Xrlw.js} +1 -1
  89. package/src/assets/web-panel/assets/index-7nAysteg.js +1 -0
  90. package/src/assets/web-panel/assets/{index-MCmNzIC7.js → index-B5NGWgHp.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-GzuCTHVZ.js → index-BItcSqan.js} +3 -3
  92. package/src/assets/web-panel/assets/index-BKWSQilQ.js +1 -0
  93. package/src/assets/web-panel/assets/{index-DTCUOKu9.js → index-BN068mCR.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-Bv9BrnD2.js → index-BOsIgPge.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-DfqUsPl2.js → index-BYUd69vM.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-Cn21XmDt.js → index-BYmwEaIk.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-CWmJukRW.js → index-BZ1gOoiG.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-Bwkg_EJk.js → index-BfY9U3X5.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-MBOwmoOi.js → index-BveL_4n3.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-CJ70GAW2.js → index-CCg6ZY4t.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-B85rQNYG.js → index-CJOoo72F.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-Cn5ghmbB.js → index-CToQxpWz.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-rWiOF7Iu.js → index-CWgWrrWs.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-PzM_GlKb.js → index-CdR7RfRP.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-ZehgEQYa.js → index-Cljnfuxu.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BsDNNDBN.js → index-CxvA72CP.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-D6KqyxG1.js → index-CyJpmSHZ.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-E_5VXq8H.js → index-D7U411hK.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CJgp_QFo.js → index-D9mNfpxi.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-DTpElYJs.js → index-DAFLFMXQ.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DMnomft7.js → index-DAeHmElB.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-B2yXH6vy.js → index-DDy_RDjs.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-kkjq_hwC.js → index-DE5Qm9UI.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-DTh0fWI4.js → index-DM9JrnYi.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-DigjvHuo.js → index-DMbF-Euw.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-DkpDFJRn.js → index-DUBsq_1G.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BIiCIC2j.js → index-De49R7TX.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CsWVDOd2.js → index-De5vOO9V.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-CAfRNHna.js → index-Dk7P-q3n.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CdDmzoPE.js → index-DryKGM_t.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CTQkYbir.js → index-DtU4qZRF.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-CK8YwdNd.js → index-NuBsCRaR.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-BaLhL3Tj.js → index-Sk3-3tKa.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CTpxOc5s.js → index-alGjpoM1.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-CrGp-4E2.js → index-cfSUlOfY.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-BbRl_gIW.js → index-i4W_EAuh.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CCWzUY8K.js → index-uHGxyZtQ.js} +1 -1
  128. package/src/assets/web-panel/assets/{initDefaultProps-C2v_L5na.js → initDefaultProps-DlDE-QgI.js} +1 -1
  129. package/src/assets/web-panel/assets/{motion-DNDqGbfr.js → motion-CodUbIRF.js} +1 -1
  130. package/src/assets/web-panel/assets/{move-xvpQ_6hJ.js → move-DaLwsHeR.js} +1 -1
  131. package/src/assets/web-panel/assets/{omit-Cb0FsfrO.js → omit-DdVg-3rL.js} +1 -1
  132. package/src/assets/web-panel/assets/{pickAttrs-BxhYpnum.js → pickAttrs-KLR1EVCo.js} +1 -1
  133. package/src/assets/web-panel/assets/{placementArrow-B3soaW4h.js → placementArrow-ChV7HvNw.js} +1 -1
  134. package/src/assets/web-panel/assets/{responsiveObserve-B-eRSLvd.js → responsiveObserve-BB_A8dBt.js} +1 -1
  135. package/src/assets/web-panel/assets/{slide--cM2ZOx-.js → slide-Bc1tQnIK.js} +1 -1
  136. package/src/assets/web-panel/assets/{statusUtils-DjBhfi8Q.js → statusUtils-CgrveSb0.js} +1 -1
  137. package/src/assets/web-panel/assets/{styleChecker-C30mMh8o.js → styleChecker-vXAYhhjz.js} +1 -1
  138. package/src/assets/web-panel/assets/{useFlexGapSupport-f7y2Qlzs.js → useFlexGapSupport-BCIMPfq9.js} +1 -1
  139. package/src/assets/web-panel/assets/{useFs-iTCXoLoZ.js → useFs-DMZGdr6G.js} +1 -1
  140. package/src/assets/web-panel/assets/{usePersonalDataHub-BH0RXmVF.js → usePersonalDataHub-118tWI_Z.js} +1 -1
  141. package/src/assets/web-panel/assets/{vnode-DQtmeDXM.js → vnode-Z7O2Y7JP.js} +1 -1
  142. package/src/assets/web-panel/assets/{zoom-vw50zkLZ.js → zoom-BXym6zmD.js} +1 -1
  143. package/src/assets/web-panel/index.html +1 -1
  144. package/src/commands/agent.js +333 -1
  145. package/src/commands/ask.js +35 -1
  146. package/src/commands/checkpoint.js +439 -0
  147. package/src/commands/compact.js +150 -0
  148. package/src/commands/cost.js +114 -0
  149. package/src/commands/goal.js +417 -0
  150. package/src/commands/hub.js +7 -0
  151. package/src/commands/session.js +22 -2
  152. package/src/harness/prompt-compressor.js +71 -1
  153. package/src/index.js +8 -0
  154. package/src/lib/agent-core.js +1 -0
  155. package/src/lib/checkpoint-store.js +523 -0
  156. package/src/lib/file-checkpoint.js +300 -0
  157. package/src/lib/goal-context.js +87 -0
  158. package/src/lib/goal-store.js +308 -0
  159. package/src/lib/llm-pricing.js +227 -0
  160. package/src/lib/personal-data-hub-wiring.js +30 -0
  161. package/src/lib/recent-session.js +72 -0
  162. package/src/lib/session-picker.js +68 -0
  163. package/src/repl/agent-repl.js +101 -9
  164. package/src/repl/chat-repl.js +16 -1
  165. package/src/runtime/agent-core.js +313 -32
  166. package/src/runtime/fallback-model.js +109 -0
  167. package/src/runtime/file-ref-expander.js +258 -0
  168. package/src/runtime/headless-runner.js +601 -0
  169. package/src/runtime/headless-stream.js +315 -0
  170. package/src/runtime/policies/agent-policy.js +7 -0
  171. package/src/runtime/quiet-stdout.js +35 -0
  172. package/src/runtime/system-prompt.js +60 -0
  173. package/src/assets/web-panel/assets/AppLayout-B0hl5cPk.js +0 -9
  174. package/src/assets/web-panel/assets/Dashboard-XlMpT7K_.js +0 -3
  175. package/src/assets/web-panel/assets/OrderTableRenderer-Bg0bkfjR.js +0 -1
  176. package/src/assets/web-panel/assets/Projects-Dl_hPdhU.js +0 -1
  177. package/src/assets/web-panel/assets/devWarning-BiN5HELJ.js +0 -1
  178. package/src/assets/web-panel/assets/index-BhxiT2LJ.js +0 -1
  179. package/src/assets/web-panel/assets/index-DBNSZ2oz.js +0 -1
@@ -0,0 +1,601 @@
1
+ /**
2
+ * Headless agent runner — Claude-Code `claude -p` parity for `cc agent`.
3
+ *
4
+ * Runs ONE non-interactive agentic turn (the agent may still take many internal
5
+ * tool-loop iterations) and emits the result in a machine-consumable format.
6
+ * Unlike startAgentRepl, there is no readline loop — input arrives via the
7
+ * `prompt` option (flag / positional / piped stdin) and the process exits when
8
+ * the loop completes.
9
+ *
10
+ * Output formats (mirrors `claude -p --output-format`):
11
+ * - text : final assistant text only → stdout; tool trace → stderr
12
+ * - json : a single result envelope (one JSON object) → stdout
13
+ * - stream-json : one JSON event per line (NDJSON) → stdout, as they happen
14
+ *
15
+ * Permission model: headless cannot show an interactive approval prompt, so the
16
+ * default is fail-closed (deny MEDIUM/HIGH-risk shell). --permission-mode opts
17
+ * into a looser tier:
18
+ * - (default) / plan → STRICT + deny-confirmer (plan also restricts tools)
19
+ * - acceptEdits → TRUSTED + deny-confirmer (HIGH-risk shell still denied)
20
+ * - bypassPermissions → AUTOPILOT (everything allowed, no confirm)
21
+ */
22
+
23
+ import { bootstrap } from "./bootstrap.js";
24
+ import {
25
+ buildSystemPrompt,
26
+ agentLoop as coreAgentLoop,
27
+ formatToolArgs,
28
+ } from "./agent-core.js";
29
+ import { IterationBudget } from "../lib/iteration-budget.js";
30
+ import {
31
+ startSession as jsonlStartSession,
32
+ appendUserMessage as jsonlAppendUserMessage,
33
+ appendAssistantMessage as jsonlAppendAssistantMessage,
34
+ appendTokenUsage as jsonlAppendTokenUsage,
35
+ rebuildMessages as jsonlRebuildMessages,
36
+ sessionExists as jsonlSessionExists,
37
+ getLastSessionId as jsonlGetLastSessionId,
38
+ } from "../harness/jsonl-session-store.js";
39
+ import { expandFileRefs } from "./file-ref-expander.js";
40
+ import { composeSystemPrompt } from "./system-prompt.js";
41
+ import { withQuietStdout } from "./quiet-stdout.js";
42
+
43
+ /** Tools that cannot mutate the filesystem or run commands. */
44
+ export const READ_ONLY_TOOLS = Object.freeze([
45
+ "read_file",
46
+ "search_files",
47
+ "list_dir",
48
+ "list_skills",
49
+ "search_sessions",
50
+ ]);
51
+
52
+ const VALID_PERMISSION_MODES = Object.freeze([
53
+ "default",
54
+ "plan",
55
+ "acceptEdits",
56
+ "bypassPermissions",
57
+ ]);
58
+
59
+ const VALID_OUTPUT_FORMATS = Object.freeze(["text", "json", "stream-json"]);
60
+
61
+ /**
62
+ * Resolve a --permission-mode string into the session-policy tier + a
63
+ * non-interactive confirmer + whether to clamp tools to the read-only set.
64
+ *
65
+ * @param {string} mode
66
+ * @returns {{ sessionPolicy: string, confirmer: (ctx:any)=>Promise<boolean>, readOnly: boolean }}
67
+ */
68
+ export function resolvePermissionMode(mode = "default") {
69
+ const m = mode || "default";
70
+ if (!VALID_PERMISSION_MODES.includes(m)) {
71
+ throw new Error(
72
+ `Invalid --permission-mode "${m}". Expected one of: ${VALID_PERMISSION_MODES.join(", ")}`,
73
+ );
74
+ }
75
+ // Headless can't ask a human — deny when a confirm would be required.
76
+ const denyConfirmer = async () => false;
77
+ const allowConfirmer = async () => true;
78
+ switch (m) {
79
+ case "bypassPermissions":
80
+ return {
81
+ sessionPolicy: "autopilot",
82
+ confirmer: allowConfirmer,
83
+ readOnly: false,
84
+ };
85
+ case "acceptEdits":
86
+ return {
87
+ sessionPolicy: "trusted",
88
+ confirmer: denyConfirmer,
89
+ readOnly: false,
90
+ };
91
+ case "plan":
92
+ return {
93
+ sessionPolicy: "strict",
94
+ confirmer: denyConfirmer,
95
+ readOnly: true,
96
+ };
97
+ case "default":
98
+ default:
99
+ return {
100
+ sessionPolicy: "strict",
101
+ confirmer: denyConfirmer,
102
+ readOnly: false,
103
+ };
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Compute the effective tool allow-list. --allowed-tools wins; when plan mode
109
+ * forces read-only we intersect with READ_ONLY_TOOLS so a user can't widen it.
110
+ *
111
+ * @returns {string[]|null} null = all tools (subject to disabledTools)
112
+ */
113
+ export function resolveEnabledTools({ allowedTools, readOnly } = {}) {
114
+ let names =
115
+ Array.isArray(allowedTools) && allowedTools.length > 0
116
+ ? [...allowedTools]
117
+ : null;
118
+ if (readOnly) {
119
+ names = names
120
+ ? names.filter((n) => READ_ONLY_TOOLS.includes(n))
121
+ : [...READ_ONLY_TOOLS];
122
+ }
123
+ return names;
124
+ }
125
+
126
+ /** Normalize a comma/space separated CLI list into a string[] (or null). */
127
+ export function parseToolList(value) {
128
+ if (!value) return null;
129
+ if (Array.isArray(value)) return value.flatMap((v) => parseToolList(v) || []);
130
+ const out = String(value)
131
+ .split(/[,\s]+/)
132
+ .map((s) => s.trim())
133
+ .filter(Boolean);
134
+ return out.length > 0 ? out : null;
135
+ }
136
+
137
+ /**
138
+ * Resolve the working session id, the id to resume history from, and whether to
139
+ * persist this turn — mirroring `claude -p --resume <id>` / `--continue`.
140
+ *
141
+ * - continueSession (or --resume with no id) → resume the most-recent session
142
+ * - resume "<id>" → resume that specific session
143
+ * (the id need not exist yet — it then doubles as "create + persist here",
144
+ * so a later `--resume <id>` picks the conversation back up)
145
+ *
146
+ * Persistence is intentionally OFF unless a resume/continue/persist intent is
147
+ * present, so a plain one-shot `cc agent -p "..."` writes nothing to disk.
148
+ *
149
+ * @param {object} options { resume, continueSession, sessionId, persistSession }
150
+ * @param {object} store { getLastSessionId } (injection seam)
151
+ * @param {string} fallbackId used when nothing is being resumed
152
+ * @returns {{ sessionId:string, resumeId:string|null, persist:boolean, wantLatest:boolean }}
153
+ */
154
+ export function resolveHeadlessSession(options = {}, store = {}, fallbackId) {
155
+ const { resume, continueSession, sessionId, persistSession } = options;
156
+ const wantLatest = continueSession === true || resume === true;
157
+ let resumeId = null;
158
+ if (wantLatest) {
159
+ resumeId =
160
+ (typeof store.getLastSessionId === "function" &&
161
+ store.getLastSessionId()) ||
162
+ null;
163
+ } else if (typeof resume === "string" && resume.trim()) {
164
+ resumeId = resume.trim();
165
+ }
166
+ const persist = persistSession === true || resumeId != null || wantLatest;
167
+ const id = resumeId || sessionId || fallbackId;
168
+ return { sessionId: id, resumeId, persist, wantLatest };
169
+ }
170
+
171
+ /**
172
+ * Run a single headless agentic turn.
173
+ *
174
+ * @param {object} options
175
+ * @param {string} options.prompt The task/prompt (required).
176
+ * @param {string} [options.model]
177
+ * @param {string} [options.provider]
178
+ * @param {string} [options.baseUrl]
179
+ * @param {string} [options.apiKey]
180
+ * @param {string} [options.outputFormat="text"]
181
+ * @param {string} [options.permissionMode="default"]
182
+ * @param {string[]} [options.allowedTools]
183
+ * @param {string[]} [options.disallowedTools]
184
+ * @param {number} [options.maxTurns] Cap on agent loop iterations.
185
+ * @param {string} [options.cwd]
186
+ * @param {string[]} [options.additionalDirectories] Extra workspace roots
187
+ * (--add-dir): absolute dirs the
188
+ * agent may read/search/edit.
189
+ * @param {string|boolean} [options.resume] Resume a session: "<id>", or true
190
+ * (no id) → most-recent session.
191
+ * @param {boolean} [options.continueSession] Resume the most-recent session.
192
+ * @param {boolean} [options.persistSession] Force persistence without resume.
193
+ * @param {boolean} [options.autoCheckpoint] Snapshot the work tree before each
194
+ * mutating tool (git engine only).
195
+ * @param {boolean} [options.expandFileRefs=true] Expand `@path` file references
196
+ * in the prompt into context blocks.
197
+ * @param {object} [deps] Injection seam for tests.
198
+ * @returns {Promise<{ exitCode:number, result:string, isError:boolean }>}
199
+ */
200
+ export async function runAgentHeadless(options = {}, deps = {}) {
201
+ const prompt = (options.prompt || "").trim();
202
+ if (!prompt) {
203
+ throw new Error(
204
+ "runAgentHeadless requires a non-empty prompt (use -p, a positional arg, or pipe stdin).",
205
+ );
206
+ }
207
+
208
+ const outputFormat = options.outputFormat || "text";
209
+ if (!VALID_OUTPUT_FORMATS.includes(outputFormat)) {
210
+ throw new Error(
211
+ `Invalid --output-format "${outputFormat}". Expected one of: ${VALID_OUTPUT_FORMATS.join(", ")}`,
212
+ );
213
+ }
214
+
215
+ const model = options.model || "qwen2.5:7b";
216
+ const provider = options.provider || "ollama";
217
+ const baseUrl = options.baseUrl || "http://localhost:11434";
218
+ const apiKey = options.apiKey || null;
219
+ const cwd = options.cwd || process.cwd();
220
+ // Extra workspace roots (--add-dir). Resolved/validated by the caller; we
221
+ // just normalize to a clean string[] here.
222
+ const additionalDirectories = Array.isArray(options.additionalDirectories)
223
+ ? options.additionalDirectories.filter(Boolean)
224
+ : [];
225
+
226
+ const runLoop = deps.agentLoop || coreAgentLoop;
227
+ const doBootstrap = deps.bootstrap || bootstrap;
228
+ const getApprovalGate =
229
+ deps.getApprovalGate ||
230
+ (async () => {
231
+ const m = await import("../lib/session-core-singletons.js");
232
+ return m.getApprovalGate();
233
+ });
234
+ // stdout carries the machine-consumable payload; stderr carries the human
235
+ // trace so `cc agent -p ... > out.txt` keeps `out.txt` clean.
236
+ const writeOut = deps.writeOut || ((s) => process.stdout.write(s));
237
+ const writeErr = deps.writeErr || ((s) => process.stderr.write(s));
238
+ // Session persistence seam (file-based JSONL; DB-free, like the rest of
239
+ // headless). Defaults to the real store; tests inject fakes.
240
+ const store = {
241
+ sessionExists: deps.sessionExists || jsonlSessionExists,
242
+ rebuildMessages: deps.rebuildMessages || jsonlRebuildMessages,
243
+ startSession: deps.startSession || jsonlStartSession,
244
+ appendUserMessage: deps.appendUserMessage || jsonlAppendUserMessage,
245
+ appendAssistantMessage:
246
+ deps.appendAssistantMessage || jsonlAppendAssistantMessage,
247
+ appendTokenUsage: deps.appendTokenUsage || jsonlAppendTokenUsage,
248
+ getLastSessionId: deps.getLastSessionId || jsonlGetLastSessionId,
249
+ };
250
+ const isStream = outputFormat === "stream-json";
251
+ const isJson = outputFormat === "json";
252
+ const isText = outputFormat === "text";
253
+
254
+ // ── Expand @file references in the prompt (Claude-Code parity) ─────────
255
+ // `@path/to/file` tokens are augmented with the referenced file contents (or
256
+ // a dir listing) so `cc agent -p "review @src/x.js"` works without a manual
257
+ // cat-pipe. Opt out with `--no-file-refs` (options.expandFileRefs === false).
258
+ let userContent = prompt;
259
+ if (options.expandFileRefs !== false) {
260
+ const doExpand = deps.expandFileRefs || expandFileRefs;
261
+ const expanded = doExpand(prompt, { cwd });
262
+ userContent = expanded.prompt;
263
+ // Warnings (typo'd paths, unreadable files) go to stderr in every output
264
+ // format so stdout stays a clean machine payload.
265
+ for (const w of expanded.warnings) {
266
+ writeErr(` @ref: ${w}\n`);
267
+ }
268
+ }
269
+
270
+ // ── Permission + tool resolution ──────────────────────────────────────
271
+ const perm = resolvePermissionMode(options.permissionMode);
272
+ const enabledToolNames = resolveEnabledTools({
273
+ allowedTools: options.allowedTools,
274
+ readOnly: perm.readOnly,
275
+ });
276
+ const disabledTools = options.disallowedTools || [];
277
+
278
+ // ── Best-effort runtime bootstrap (DB optional, like startAgentRepl) ───
279
+ let db = null;
280
+ try {
281
+ // Bootstrap logs db/config diagnostics via console.info (→ stdout); divert
282
+ // to stderr so text/JSON/NDJSON stdout payloads stay clean.
283
+ const ctx = await withQuietStdout(() => doBootstrap({ verbose: false }));
284
+ db = ctx.db || null;
285
+ } catch {
286
+ // Continue without DB — static-prompt fallback.
287
+ }
288
+
289
+ // ── Resolve session continuity (--resume / --continue) ─────────────────
290
+ const { sessionId, resumeId, persist } = resolveHeadlessSession(
291
+ options,
292
+ store,
293
+ `headless-${Date.now()}-${process.pid}`,
294
+ );
295
+ if (options.continueSession === true && !resumeId && isText) {
296
+ writeErr("No previous session to continue; starting a new one.\n");
297
+ }
298
+
299
+ // Load prior conversation when resuming an existing session. The fresh
300
+ // system prompt always leads; we drop any persisted system turns so it is
301
+ // never duplicated.
302
+ let history = [];
303
+ if (resumeId && store.sessionExists(resumeId)) {
304
+ try {
305
+ history = (store.rebuildMessages(resumeId) || []).filter(
306
+ (m) => m && m.role !== "system",
307
+ );
308
+ } catch {
309
+ history = [];
310
+ }
311
+ }
312
+
313
+ // ── Wire the persistent ApprovalGate with our non-interactive confirmer
314
+ // and force the session-policy tier dictated by --permission-mode. ──────
315
+ let approvalGate = null;
316
+ try {
317
+ approvalGate = await getApprovalGate();
318
+ if (approvalGate) {
319
+ if (typeof approvalGate.setSessionPolicy === "function") {
320
+ approvalGate.setSessionPolicy(sessionId, perm.sessionPolicy);
321
+ }
322
+ if (typeof approvalGate.setConfirmer === "function") {
323
+ approvalGate.setConfirmer(perm.confirmer);
324
+ }
325
+ }
326
+ } catch {
327
+ approvalGate = null;
328
+ }
329
+
330
+ const budget = Number.isFinite(options.maxTurns)
331
+ ? new IterationBudget({ limit: Math.max(1, Math.floor(options.maxTurns)) })
332
+ : new IterationBudget();
333
+
334
+ // Effective system prompt: built-in base, optionally replaced by
335
+ // --system-prompt and/or extended by --append-system-prompt.
336
+ const systemContent = composeSystemPrompt(
337
+ buildSystemPrompt(cwd, { additionalDirectories }),
338
+ {
339
+ systemPrompt: options.systemPrompt,
340
+ appendSystemPrompt: options.appendSystemPrompt,
341
+ },
342
+ );
343
+
344
+ const messages = [
345
+ { role: "system", content: systemContent },
346
+ ...history,
347
+ { role: "user", content: userContent },
348
+ ];
349
+
350
+ // Persist the user turn up front (best-effort) so a session is recoverable
351
+ // even if the run crashes mid-loop. startSession is append-safe: only seed
352
+ // the header when the file does not yet exist.
353
+ if (persist) {
354
+ try {
355
+ if (!store.sessionExists(sessionId)) {
356
+ store.startSession(sessionId, {
357
+ title: prompt.slice(0, 60),
358
+ provider,
359
+ model,
360
+ });
361
+ }
362
+ // Persist the expanded content so a resumed session faithfully replays
363
+ // what the model actually saw (the file snapshot, not just the @token).
364
+ store.appendUserMessage(sessionId, userContent);
365
+ } catch {
366
+ // Persistence is best-effort — never fail the run over it.
367
+ }
368
+ }
369
+
370
+ const loopOptions = {
371
+ model,
372
+ provider,
373
+ baseUrl,
374
+ apiKey,
375
+ cwd,
376
+ additionalDirectories,
377
+ sessionId,
378
+ autoCheckpoint: options.autoCheckpoint || false,
379
+ checkpointSession: options.checkpointSession || sessionId,
380
+ hookDb: db,
381
+ approvalGate,
382
+ enabledToolNames,
383
+ disabledTools,
384
+ iterationBudget: budget,
385
+ // chatFn passthrough lets tests drive the loop deterministically.
386
+ chatFn: deps.chatFn || options.chatFn || undefined,
387
+ signal: options.signal || undefined,
388
+ };
389
+
390
+ const startedAt = deps.now ? deps.now() : Date.now();
391
+ const toolCalls = [];
392
+ const usage = { input_tokens: 0, output_tokens: 0 };
393
+ let finalText = "";
394
+ let endReason = "complete";
395
+
396
+ const emitStream = (obj) => {
397
+ if (isStream) writeOut(JSON.stringify(obj) + "\n");
398
+ };
399
+
400
+ // --include-partial-messages: forward live assistant-text deltas as
401
+ // `stream_event` NDJSON lines (Claude-Code parity). Only meaningful for
402
+ // stream-json output, where the agent loop's onToken hook feeds chunks as
403
+ // they arrive from a streaming provider.
404
+ if (isStream && options.includePartialMessages) {
405
+ loopOptions.onToken = (text) =>
406
+ emitStream({
407
+ type: "stream_event",
408
+ event: {
409
+ type: "content_block_delta",
410
+ delta: { type: "text_delta", text },
411
+ },
412
+ });
413
+ }
414
+
415
+ emitStream({
416
+ type: "system",
417
+ subtype: "init",
418
+ session_id: sessionId,
419
+ model,
420
+ provider,
421
+ permission_mode: options.permissionMode || "default",
422
+ tools: enabledToolNames,
423
+ max_turns: budget.limit,
424
+ resumed_from: resumeId,
425
+ history_messages: history.length,
426
+ additional_directories: additionalDirectories,
427
+ });
428
+
429
+ try {
430
+ for await (const event of runLoop(messages, loopOptions)) {
431
+ switch (event.type) {
432
+ case "checkpoint": {
433
+ if (isText)
434
+ writeErr(` ⎌ checkpoint ${event.id} (before ${event.tool})\n`);
435
+ emitStream({ type: "checkpoint", id: event.id, tool: event.tool });
436
+ break;
437
+ }
438
+ case "tool-executing": {
439
+ const line = ` [${event.tool}] ${formatToolArgs(event.tool, event.args)}`;
440
+ if (isText) writeErr(line + "\n");
441
+ emitStream({
442
+ type: "tool_use",
443
+ tool: event.tool,
444
+ args: event.args,
445
+ });
446
+ toolCalls.push({ tool: event.tool, args: event.args });
447
+ break;
448
+ }
449
+ case "tool-result": {
450
+ const err = event.error || event.result?.error || null;
451
+ if (isText && err) writeErr(` Error: ${err}\n`);
452
+ emitStream({
453
+ type: "tool_result",
454
+ tool: event.tool,
455
+ is_error: Boolean(err),
456
+ error: err,
457
+ result: event.result,
458
+ });
459
+ if (toolCalls.length > 0) {
460
+ toolCalls[toolCalls.length - 1].is_error = Boolean(err);
461
+ }
462
+ break;
463
+ }
464
+ case "token-usage": {
465
+ usage.input_tokens += event.usage?.input_tokens || 0;
466
+ usage.output_tokens += event.usage?.output_tokens || 0;
467
+ emitStream({ type: "token_usage", usage: event.usage });
468
+ break;
469
+ }
470
+ case "iteration-warning": {
471
+ if (isText) writeErr(` ${event.message}\n`);
472
+ emitStream({ type: "iteration_warning", message: event.message });
473
+ break;
474
+ }
475
+ case "iteration-budget-exhausted": {
476
+ endReason = "max_turns";
477
+ emitStream({
478
+ type: "iteration_budget_exhausted",
479
+ budget: event.budget,
480
+ });
481
+ break;
482
+ }
483
+ case "response-complete": {
484
+ finalText = event.content || "";
485
+ break;
486
+ }
487
+ case "run-ended": {
488
+ if (event.reason) endReason = event.reason;
489
+ break;
490
+ }
491
+ default:
492
+ // slot-filling, run-started, etc. — surfaced only in stream mode.
493
+ if (isStream && event.type) emitStream(event);
494
+ break;
495
+ }
496
+ }
497
+ } catch (err) {
498
+ const message = err?.message || String(err);
499
+ if (isStream) {
500
+ emitStream({
501
+ type: "result",
502
+ subtype: "error",
503
+ is_error: true,
504
+ error: message,
505
+ });
506
+ } else if (isJson) {
507
+ writeOut(
508
+ JSON.stringify(
509
+ buildResultEnvelope({
510
+ subtype: "error",
511
+ isError: true,
512
+ result: message,
513
+ sessionId,
514
+ toolCalls,
515
+ usage,
516
+ numTurns: budget.consumed,
517
+ durationMs: (deps.now ? deps.now() : Date.now()) - startedAt,
518
+ }),
519
+ ) + "\n",
520
+ );
521
+ } else {
522
+ writeErr(`Error: ${message}\n`);
523
+ }
524
+ return { exitCode: 1, result: message, isError: true };
525
+ }
526
+
527
+ // coreAgentLoop emits run-ended reason "budget-exhausted" when the iteration
528
+ // cap is hit; treat that as the max-turns error surface.
529
+ const exhausted =
530
+ endReason === "budget-exhausted" || endReason === "max_turns";
531
+ const isError = exhausted || endReason === "no-response";
532
+ const subtype = exhausted ? "error_max_turns" : isError ? "error" : "success";
533
+ const durationMs = (deps.now ? deps.now() : Date.now()) - startedAt;
534
+
535
+ // Persist the assistant turn so a later --resume / --continue replays it.
536
+ // The user turn was already recorded up front; only append on a clean run.
537
+ if (persist && !isError) {
538
+ try {
539
+ if (finalText) store.appendAssistantMessage(sessionId, finalText);
540
+ store.appendTokenUsage(sessionId, usage);
541
+ } catch {
542
+ // Persistence is best-effort — never fail the run over it.
543
+ }
544
+ }
545
+
546
+ if (isStream) {
547
+ emitStream({
548
+ type: "result",
549
+ subtype,
550
+ is_error: isError,
551
+ result: finalText,
552
+ session_id: sessionId,
553
+ num_turns: budget.consumed,
554
+ duration_ms: durationMs,
555
+ usage,
556
+ });
557
+ } else if (isJson) {
558
+ writeOut(
559
+ JSON.stringify(
560
+ buildResultEnvelope({
561
+ subtype,
562
+ isError,
563
+ result: finalText,
564
+ sessionId,
565
+ toolCalls,
566
+ usage,
567
+ numTurns: budget.consumed,
568
+ durationMs,
569
+ }),
570
+ ) + "\n",
571
+ );
572
+ } else {
573
+ // text: just the final answer on stdout.
574
+ writeOut(finalText + (finalText.endsWith("\n") ? "" : "\n"));
575
+ }
576
+
577
+ return { exitCode: isError ? 1 : 0, result: finalText, isError };
578
+ }
579
+
580
+ function buildResultEnvelope({
581
+ subtype,
582
+ isError,
583
+ result,
584
+ sessionId,
585
+ toolCalls,
586
+ usage,
587
+ numTurns,
588
+ durationMs,
589
+ }) {
590
+ return {
591
+ type: "result",
592
+ subtype,
593
+ is_error: isError,
594
+ result,
595
+ session_id: sessionId,
596
+ num_turns: numTurns,
597
+ duration_ms: durationMs,
598
+ tool_calls: toolCalls,
599
+ usage,
600
+ };
601
+ }