chainlesschain 0.162.31 → 0.162.33

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 (170) hide show
  1. package/package.json +2 -2
  2. package/src/assets/web-panel/assets/{AIOps-BqWP6FKu.js → AIOps-3TazCYWE.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-CXwMgOvX.js → ActionButton-DUPN0PST.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-DAebZ4IY.js → Analytics-CemvhkzD.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-CYsqYoME.js → AppLayout-BL_tAU3M.js} +5 -5
  6. package/src/assets/web-panel/assets/{Audit-BbTtX1Nf.js → Audit-Dl9l-cxF.js} +1 -1
  7. package/src/assets/web-panel/assets/{Backup-DgqY2Eb-.js → Backup-BKDDX75m.js} +1 -1
  8. package/src/assets/web-panel/assets/{BaseInput-Cq2ZuSoO.js → BaseInput-CDYePvMI.js} +1 -1
  9. package/src/assets/web-panel/assets/{Chat-D2kqpUyO.js → Chat-CGtR0sg3.js} +5 -5
  10. package/src/assets/web-panel/assets/ChatBubbleRenderer-DZjc9uKn.js +1 -0
  11. package/src/assets/web-panel/assets/{Checkbox-_9swHpyc.js → Checkbox-CwYIHOOo.js} +1 -1
  12. package/src/assets/web-panel/assets/{Codegen-Cr9YbCPl.js → Codegen-CIF5tbtd.js} +1 -1
  13. package/src/assets/web-panel/assets/{Col--wdpCMxx.js → Col-z7d4kxeP.js} +1 -1
  14. package/src/assets/web-panel/assets/{Community-DuFcVnLu.js → Community-DUlDrqF7.js} +1 -1
  15. package/src/assets/web-panel/assets/{Compact-1yzYeT04.js → Compact-CJ1o8QQR.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compliance-Dq3aU9Df.js → Compliance-D3i9d_uO.js} +1 -1
  17. package/src/assets/web-panel/assets/{Cowork-CrWcnIg8.js → Cowork-Wm7JTkfB.js} +2 -2
  18. package/src/assets/web-panel/assets/{Cron-Bh6fKZ0h.js → Cron-B0QnHhZx.js} +2 -2
  19. package/src/assets/web-panel/assets/{Crosschain-8ofPaWVW.js → Crosschain-3yPrnNgd.js} +1 -1
  20. package/src/assets/web-panel/assets/{DID-D3EiYm3w.js → DID-cfdkiDWF.js} +2 -2
  21. package/src/assets/web-panel/assets/{Dashboard-BFjEdFne.js → Dashboard-DFkgM4gT.js} +2 -2
  22. package/src/assets/web-panel/assets/{Dropdown-pYVPcP6O.js → Dropdown-YYWE81DL.js} +1 -1
  23. package/src/assets/web-panel/assets/{EmailListRenderer-zBPodwJ1.js → EmailListRenderer-BXfHK1Bn.js} +1 -1
  24. package/src/assets/web-panel/assets/{FamilyGuardDashboard-CyQTW6PW.js → FamilyGuardDashboard-DInUxJ2G.js} +1 -1
  25. package/src/assets/web-panel/assets/{Federation-Ctaq3zYq.js → Federation-DNUYeFsv.js} +1 -1
  26. package/src/assets/web-panel/assets/{FormItemContext-CWYJCLq1.js → FormItemContext-Cr7eVEBB.js} +1 -1
  27. package/src/assets/web-panel/assets/{GenericCardRenderer-B1g6t9R9.js → GenericCardRenderer-_gF4cmDa.js} +1 -1
  28. package/src/assets/web-panel/assets/{Git-DH-v8iwd.js → Git-BqldmUbO.js} +2 -2
  29. package/src/assets/web-panel/assets/{Governance-jZxXvOs5.js → Governance-BF59ZiQ8.js} +1 -1
  30. package/src/assets/web-panel/assets/{Inference-D07LRghn.js → Inference-Cy7y1eb9.js} +1 -1
  31. package/src/assets/web-panel/assets/{KnowledgeGraph-DnGtRZhx.js → KnowledgeGraph-B3fVocTO.js} +1 -1
  32. package/src/assets/web-panel/assets/{Logs-D2pM9C4W.js → Logs-BDirsUVk.js} +2 -2
  33. package/src/assets/web-panel/assets/{Marketplace-UyIO7C7r.js → Marketplace-GhXpZgp2.js} +1 -1
  34. package/src/assets/web-panel/assets/{McpTools-Bf1gvZPf.js → McpTools-0VvfIhKx.js} +3 -3
  35. package/src/assets/web-panel/assets/{Memory-C1bWj4RN.js → Memory-CJLBgAUT.js} +2 -2
  36. package/src/assets/web-panel/assets/{MobileBridge-C_Ot1H_a.js → MobileBridge-BMedY9Yg.js} +2 -2
  37. package/src/assets/web-panel/assets/MobileProjects-mdohgRlL.js +1 -0
  38. package/src/assets/web-panel/assets/{Mtc-CnzFUz5J.js → Mtc-CgEuUg0g.js} +5 -5
  39. package/src/assets/web-panel/assets/{MtcAudit-CAAh99wz.js → MtcAudit-1pWNe_xi.js} +2 -2
  40. package/src/assets/web-panel/assets/{Multisig-D6IAg6HE.js → Multisig-DPIQ7oZL.js} +3 -3
  41. package/src/assets/web-panel/assets/{NLProgramming-BFMarxb0.js → NLProgramming-W__P_P4Z.js} +1 -1
  42. package/src/assets/web-panel/assets/{Notes-BRp9ro3t.js → Notes-C_MCDhFk.js} +3 -3
  43. package/src/assets/web-panel/assets/{NotificationSettings-C0Au3Cxb.js → NotificationSettings-CDFotapL.js} +1 -1
  44. package/src/assets/web-panel/assets/OrderTableRenderer-Dtht0cEs.js +1 -0
  45. package/src/assets/web-panel/assets/{Organization-DYoxLBRX.js → Organization-D6lMumhD.js} +2 -2
  46. package/src/assets/web-panel/assets/{Overflow-rO8JJWGJ.js → Overflow-BMOvUMW6.js} +1 -1
  47. package/src/assets/web-panel/assets/{P2P-DJleeXIK.js → P2P-DsQTEw1t.js} +2 -2
  48. package/src/assets/web-panel/assets/{PdhVaultBrowser-DM5qghFp.js → PdhVaultBrowser-CncRtN1Z.js} +3 -3
  49. package/src/assets/web-panel/assets/{Permissions-D5v4Beya.js → Permissions-DDC-DkUl.js} +4 -4
  50. package/src/assets/web-panel/assets/{PersonalDataHub-c2ZTX0Pv.js → PersonalDataHub-DVKY_NnT.js} +4 -4
  51. package/src/assets/web-panel/assets/{Pipeline-Crrkyhpz.js → Pipeline-C7oDVTl-.js} +1 -1
  52. package/src/assets/web-panel/assets/{Privacy-DZVyrJKa.js → Privacy-DReGvTEJ.js} +1 -1
  53. package/src/assets/web-panel/assets/{ProjectInit-DKg7J0gz.js → ProjectInit-C-j2dzxJ.js} +2 -2
  54. package/src/assets/web-panel/assets/{ProjectSettings-3ndmTvVH.js → ProjectSettings-DcUsvFnc.js} +2 -2
  55. package/src/assets/web-panel/assets/Projects-jSjWnmr6.js +1 -0
  56. package/src/assets/web-panel/assets/{Providers-BeqBVMhB.js → Providers-DIpohWG5.js} +1 -1
  57. package/src/assets/web-panel/assets/{QuickAsk-DKAAxzuA.js → QuickAsk-DdvLtpEU.js} +1 -1
  58. package/src/assets/web-panel/assets/{Recommend-Byu7IGei.js → Recommend-DPAi2zo3.js} +1 -1
  59. package/src/assets/web-panel/assets/{Reputation-BKhWAmCu.js → Reputation-DJD7qXSI.js} +1 -1
  60. package/src/assets/web-panel/assets/{Row-BFtn11O6.js → Row-XERdPDHk.js} +1 -1
  61. package/src/assets/web-panel/assets/{RssFeed-D5a0PT0k.js → RssFeed-Cl_VlCLg.js} +3 -3
  62. package/src/assets/web-panel/assets/{Search-DAkuaZNe.js → Search-C-poG9P5.js} +1 -1
  63. package/src/assets/web-panel/assets/{Security-C79Ml2Ms.js → Security-DjjCrw8v.js} +2 -2
  64. package/src/assets/web-panel/assets/{Services-BBk_jH6-.js → Services-BuWeB4YJ.js} +2 -2
  65. package/src/assets/web-panel/assets/{Skeleton-Cy0VvL0M.js → Skeleton-VZXOKwC_.js} +1 -1
  66. package/src/assets/web-panel/assets/Skills-B76ONTfP.js +1 -0
  67. package/src/assets/web-panel/assets/{Sla-CbX1f8xN.js → Sla-DIj1KREq.js} +1 -1
  68. package/src/assets/web-panel/assets/{SpeechSettings-BIkoUjws.js → SpeechSettings-BrAp3Yk3.js} +1 -1
  69. package/src/assets/web-panel/assets/{SyncSettings-DG6Swk7G.js → SyncSettings--mJcpccF.js} +2 -2
  70. package/src/assets/web-panel/assets/Tasks-DM8cMr83.js +1 -0
  71. package/src/assets/web-panel/assets/{Templates-AaJPeCIz.js → Templates-kOBK6m1Z.js} +1 -1
  72. package/src/assets/web-panel/assets/{Tenant-jVFRofww.js → Tenant-BjSzYPzn.js} +1 -1
  73. package/src/assets/web-panel/assets/{Terminal-DHBMzfK6.js → Terminal-DwpY-Ay7.js} +2 -2
  74. package/src/assets/web-panel/assets/{TimelineRenderer-9RFfOHSI.js → TimelineRenderer-aoI0DazM.js} +1 -1
  75. package/src/assets/web-panel/assets/{Tokens-ZTfwuABF.js → Tokens-YwE0LqSZ.js} +1 -1
  76. package/src/assets/web-panel/assets/{Trigger-Xo7uZNQs.js → Trigger-CwSKzvlX.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trust-C0cTPYvn.js → Trust-B__Jqdzn.js} +1 -1
  78. package/src/assets/web-panel/assets/{UkeySign-DmMKio71.js → UkeySign-mty0jwmx.js} +1 -1
  79. package/src/assets/web-panel/assets/{VideoEditing-DP7B-oGT.js → VideoEditing-Ddsx_OQ6.js} +1 -1
  80. package/src/assets/web-panel/assets/{Wallet-B1kZDARo.js → Wallet-D4Q8yXZm.js} +4 -4
  81. package/src/assets/web-panel/assets/{WebAuthn-Bo5kBx27.js → WebAuthn-CLUaKUr5.js} +5 -5
  82. package/src/assets/web-panel/assets/{WorkflowEditor-DGI9SNHH.js → WorkflowEditor-Di5pOaeC.js} +1 -1
  83. package/src/assets/web-panel/assets/{chat-y97W1CIG.js → chat-CELatHkT.js} +1 -1
  84. package/src/assets/web-panel/assets/{colors-DtTNo0sH.js → colors-CawDLjXV.js} +1 -1
  85. package/src/assets/web-panel/assets/{compact-item-D0q0exuS.js → compact-item-DeMp-K0j.js} +1 -1
  86. package/src/assets/web-panel/assets/{createContext-D7pLFs2I.js → createContext-zY9kXivd.js} +1 -1
  87. package/src/assets/web-panel/assets/devWarning-zLjV7g6r.js +1 -0
  88. package/src/assets/web-panel/assets/{hasIn-CXjG5B2j.js → hasIn-VEBMW8E4.js} +1 -1
  89. package/src/assets/web-panel/assets/{index-TrBGgrwG.js → index-8kqE_cVD.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-D_4WcI1V.js → index-B8AZpx7d.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-YWOEx3rP.js → index-BFc0vBN9.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-BqVjUN8b.js → index-BVkrfyuk.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-BnLrbXDA.js → index-BfGGKoo8.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-CKrbutAQ.js → index-BjctklSd.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-CFsPe2N7.js → index-BqJ2r12F.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-CSdhC7Qo.js → index-C0GhuYLk.js} +1 -1
  97. package/src/assets/web-panel/assets/index-CDtUWCtX.js +1 -0
  98. package/src/assets/web-panel/assets/{index-BO644Q4S.js → index-CHqvj9uz.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-gFLQe31v.js → index-CHxHLv2b.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BgyrM0UN.js → index-CWbbB1MI.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-1dwtkcJv.js → index-CbJZzK9B.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-B3y_4OdG.js → index-CfZV3FXN.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-Dr45Nm9V.js → index-Cr7lnIeI.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CkGFqlYX.js → index-CtLZammH.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-6np5ESBM.js → index-CtoauqWt.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BU944DeT.js → index-CyeYs7SG.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-POaFzYGS.js → index-DALuVdhu.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DjCawXk1.js → index-DClGYjBM.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-8jxbZupG.js → index-DPHe9NYG.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-_3wPBMKt.js → index-DSiL_W2n.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Cbqu804A.js → index-DXNe_zIP.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-EaIfumgW.js → index-DhsfyHcg.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-BzCPx1cq.js → index-Dna2psGz.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-kvV0f4tV.js → index-GRNVdvoA.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BdhEYW2a.js → index-JseP3-5X.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-aarO4HT9.js → index-KcOEkUCM.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BgmvrPJH.js → index-S9JZDSaa.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-DY6KLlgG.js → index-SrQIPYq8.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-Ct6xtKkc.js → index-TfXODan7.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-B_hjkMtX.js → index-V3K9gvKR.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-4mWZhCzz.js → index-VJnHvkv2.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-BJUf19Wd.js → index-XFyv3Sg_.js} +3 -3
  123. package/src/assets/web-panel/assets/{index-B4dPdrvC.js → index-b3ZuAreb.js} +1 -1
  124. package/src/assets/web-panel/assets/index-d_RPqH7u.js +1 -0
  125. package/src/assets/web-panel/assets/{index-BPXhU-jp.js → index-fBNVDEf2.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-bVJvqDAz.js → index-u_1aiNTA.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-qoB3whR9.js → index-vaD1iHg5.js} +1 -1
  128. package/src/assets/web-panel/assets/{initDefaultProps-BnXISaAa.js → initDefaultProps-Sd7Eayz4.js} +1 -1
  129. package/src/assets/web-panel/assets/{motion-ChY7C0zJ.js → motion-DlToY72q.js} +1 -1
  130. package/src/assets/web-panel/assets/{move-ByFZMFM5.js → move-DvS7EmAP.js} +1 -1
  131. package/src/assets/web-panel/assets/{omit-BYeliY1H.js → omit-CzLq4QKW.js} +1 -1
  132. package/src/assets/web-panel/assets/{pickAttrs-B9dcAKnu.js → pickAttrs-BcM75Jx_.js} +1 -1
  133. package/src/assets/web-panel/assets/{placementArrow-D3F_txz7.js → placementArrow-B7xXXiwd.js} +1 -1
  134. package/src/assets/web-panel/assets/{responsiveObserve-ClkwY7wS.js → responsiveObserve-CrYPRB-g.js} +1 -1
  135. package/src/assets/web-panel/assets/{slide-BNgy2Eea.js → slide-CSYTtsRt.js} +1 -1
  136. package/src/assets/web-panel/assets/{statusUtils-Bv3heMCD.js → statusUtils-CeSuOVT_.js} +1 -1
  137. package/src/assets/web-panel/assets/{styleChecker-DVdlHbQm.js → styleChecker-KiQethca.js} +1 -1
  138. package/src/assets/web-panel/assets/{useFlexGapSupport-alrRY5BK.js → useFlexGapSupport-CSQnQdiv.js} +1 -1
  139. package/src/assets/web-panel/assets/{useFs-CcVh0-Vu.js → useFs-Br8Kr1pr.js} +1 -1
  140. package/src/assets/web-panel/assets/{usePersonalDataHub-CkkHPhyq.js → usePersonalDataHub-DGJtDcMm.js} +1 -1
  141. package/src/assets/web-panel/assets/{vnode-DWi0X9WN.js → vnode-C-jVtGka.js} +1 -1
  142. package/src/assets/web-panel/assets/{zoom-DCbqxxLH.js → zoom-CeWySTPF.js} +1 -1
  143. package/src/assets/web-panel/index.html +1 -1
  144. package/src/commands/agent.js +47 -0
  145. package/src/commands/checkpoint.js +253 -53
  146. package/src/commands/compact.js +150 -0
  147. package/src/commands/goal.js +417 -0
  148. package/src/commands/hub.js +7 -0
  149. package/src/harness/prompt-compressor.js +71 -1
  150. package/src/index.js +4 -0
  151. package/src/lib/agent-core.js +1 -0
  152. package/src/lib/checkpoint-store.js +523 -0
  153. package/src/lib/goal-assess.js +228 -0
  154. package/src/lib/goal-context.js +87 -0
  155. package/src/lib/goal-store.js +341 -0
  156. package/src/repl/agent-repl.js +43 -7
  157. package/src/runtime/agent-core.js +267 -1
  158. package/src/runtime/headless-runner.js +147 -0
  159. package/src/runtime/headless-stream.js +34 -0
  160. package/src/runtime/mcp-config.js +139 -0
  161. package/src/runtime/policies/agent-policy.js +1 -0
  162. package/src/assets/web-panel/assets/ChatBubbleRenderer-C-svYkrC.js +0 -1
  163. package/src/assets/web-panel/assets/MobileProjects-zr-PpsT_.js +0 -1
  164. package/src/assets/web-panel/assets/OrderTableRenderer-ISp6btRY.js +0 -1
  165. package/src/assets/web-panel/assets/Projects-ll5wnj2L.js +0 -1
  166. package/src/assets/web-panel/assets/Skills-OQNky3uI.js +0 -1
  167. package/src/assets/web-panel/assets/Tasks-C9R8sgyi.js +0 -1
  168. package/src/assets/web-panel/assets/devWarning-BDK34w0I.js +0 -1
  169. package/src/assets/web-panel/assets/index-B6SaRuCI.js +0 -1
  170. package/src/assets/web-panel/assets/index-B9ekWb3I.js +0 -1
@@ -92,8 +92,18 @@ async function executeTool(name, args) {
92
92
  */
93
93
  async function agentLoop(messages, options) {
94
94
  const usageEvents = [];
95
- for await (const event of coreAgentLoop(messages, options)) {
96
- if (event.type === "tool-executing") {
95
+ // The REPL runs its own auto-compaction (after each turn, with metrics +
96
+ // persisted compact events), so opt out of the agent loop's in-loop
97
+ // compaction to avoid compacting the same history twice.
98
+ for await (const event of coreAgentLoop(messages, {
99
+ autoCompact: false,
100
+ ...options,
101
+ })) {
102
+ if (event.type === "checkpoint") {
103
+ process.stdout.write(
104
+ chalk.gray(` ⎌ checkpoint ${event.id} (before ${event.tool})\n`),
105
+ );
106
+ } else if (event.type === "tool-executing") {
97
107
  process.stdout.write(
98
108
  chalk.gray(
99
109
  ` [${event.tool}] ${formatToolArgs(event.tool, event.args)}\n`,
@@ -158,6 +168,9 @@ export async function startAgentRepl(options = {}) {
158
168
  const additionalDirectories = Array.isArray(options.additionalDirectories)
159
169
  ? options.additionalDirectories
160
170
  : [];
171
+ // Snapshot the work tree before each mutating tool (git engine) so the user
172
+ // can `cc checkpoint restore` to just before any tool call.
173
+ const autoCheckpoint = options.autoCheckpoint === true;
161
174
 
162
175
  // --fallback-model: retry a turn's LLM call once on a backup model when the
163
176
  // primary errors out (overload / network). Built once; passed into every
@@ -718,8 +731,10 @@ export async function startAgentRepl(options = {}) {
718
731
 
719
732
  if (trimmed === "/compact") {
720
733
  if (_compressor && messages.length > 3) {
721
- const { messages: compacted, stats } =
722
- await _compressor.compress(messages);
734
+ const { messages: compacted, stats } = await _compressor.compress(
735
+ messages,
736
+ { preserveToolPairs: true },
737
+ );
723
738
  messages.length = 0;
724
739
  messages.push(...compacted);
725
740
  recordCompressionMetric(stats, {
@@ -1521,6 +1536,23 @@ export async function startAgentRepl(options = {}) {
1521
1536
  try {
1522
1537
  process.stdout.write("\n");
1523
1538
  const iterationBudget = new IterationBudget({ owner: sessionId });
1539
+ // Bind a cross-session goal (cc goal) into this run, if one resolves.
1540
+ // Composes WITH defaultPrepareCall — never replaces it. Best-effort.
1541
+ let prepareCall = defaultPrepareCall;
1542
+ try {
1543
+ const { resolveActiveGoal } = await import("../lib/goal-store.js");
1544
+ const boundGoal = resolveActiveGoal({ sessionId });
1545
+ if (boundGoal) {
1546
+ const { goalPrepareCall, composePrepareCall } =
1547
+ await import("../lib/goal-context.js");
1548
+ prepareCall = composePrepareCall([
1549
+ defaultPrepareCall,
1550
+ goalPrepareCall(boundGoal),
1551
+ ]);
1552
+ }
1553
+ } catch (_e) {
1554
+ /* goal binding is best-effort — fall back to defaultPrepareCall */
1555
+ }
1524
1556
  const { content: response, usageEvents } = await agentLoop(messages, {
1525
1557
  provider,
1526
1558
  model: activeModel,
@@ -1531,7 +1563,9 @@ export async function startAgentRepl(options = {}) {
1531
1563
  sessionId,
1532
1564
  cwd: process.cwd(),
1533
1565
  additionalDirectories,
1534
- prepareCall: defaultPrepareCall,
1566
+ autoCheckpoint,
1567
+ checkpointSession: sessionId,
1568
+ prepareCall,
1535
1569
  approvalGate: _approvalGate,
1536
1570
  mcpClient: _bundleMcpClient || undefined,
1537
1571
  chatFn: _fallbackChatFn,
@@ -1613,8 +1647,10 @@ export async function startAgentRepl(options = {}) {
1613
1647
  _compressor.shouldAutoCompact(messages)
1614
1648
  ) {
1615
1649
  try {
1616
- const { messages: compacted, stats } =
1617
- await _compressor.compress(messages);
1650
+ const { messages: compacted, stats } = await _compressor.compress(
1651
+ messages,
1652
+ { preserveToolPairs: true },
1653
+ );
1618
1654
  messages.length = 0;
1619
1655
  messages.push(...compacted);
1620
1656
  recordCompressionMetric(stats, {
@@ -1663,7 +1663,21 @@ export async function chatWithTools(rawMessages, options) {
1663
1663
  throwIfAborted(signal);
1664
1664
 
1665
1665
  if (provider === "ollama") {
1666
- const response = await fetch(`${baseUrl}/api/chat`, {
1666
+ const apiUrl = `${baseUrl}/api/chat`;
1667
+ // Real-time token deltas (Claude-Code `--include-partial-messages`): when
1668
+ // the caller supplies an onToken hook, stream the response and forward each
1669
+ // content chunk as it arrives. Tool calls + usage are accumulated and the
1670
+ // same {message, usage} shape is returned, so the agent loop is unchanged.
1671
+ // Without onToken we keep the cheaper single-shot non-streaming request.
1672
+ if (typeof options.onToken === "function") {
1673
+ return await _chatOllamaStreaming(
1674
+ apiUrl,
1675
+ { model, messages, tools },
1676
+ options.onToken,
1677
+ signal,
1678
+ );
1679
+ }
1680
+ const response = await fetch(apiUrl, {
1667
1681
  method: "POST",
1668
1682
  headers: { "Content-Type": "application/json" },
1669
1683
  signal,
@@ -1817,6 +1831,109 @@ export async function chatWithTools(rawMessages, options) {
1817
1831
  return out;
1818
1832
  }
1819
1833
 
1834
+ // ─── Ollama streaming (token deltas for --include-partial-messages) ─────────
1835
+ //
1836
+ // Ollama `/api/chat` with `stream:true` returns NDJSON: one JSON object per
1837
+ // line, each carrying an incremental `message.content` chunk, optional
1838
+ // `message.tool_calls` (emitted whole, not byte-streamed), and a final line
1839
+ // with `done:true` + `prompt_eval_count`/`eval_count` token totals. We reduce
1840
+ // the stream line-by-line so onToken fires live, then finalize into the same
1841
+ // {message, usage} shape the non-streaming branch returns.
1842
+
1843
+ function _ollamaInitState() {
1844
+ return {
1845
+ role: "assistant",
1846
+ content: "",
1847
+ toolCalls: null,
1848
+ promptEval: 0,
1849
+ evalCount: 0,
1850
+ };
1851
+ }
1852
+
1853
+ function _ollamaReduceLine(state, line, onToken) {
1854
+ const s = (line || "").trim();
1855
+ if (!s) return state;
1856
+ let obj;
1857
+ try {
1858
+ obj = JSON.parse(s);
1859
+ } catch {
1860
+ return state; // tolerate partial/garbage lines mid-stream
1861
+ }
1862
+ const msg = obj.message;
1863
+ if (msg) {
1864
+ if (msg.role) state.role = msg.role;
1865
+ if (typeof msg.content === "string" && msg.content) {
1866
+ state.content += msg.content;
1867
+ if (typeof onToken === "function") {
1868
+ try {
1869
+ onToken(msg.content);
1870
+ } catch {
1871
+ // A failing UI hook must never break the agent run.
1872
+ }
1873
+ }
1874
+ }
1875
+ if (Array.isArray(msg.tool_calls) && msg.tool_calls.length) {
1876
+ state.toolCalls = (state.toolCalls || []).concat(msg.tool_calls);
1877
+ }
1878
+ }
1879
+ if (obj.prompt_eval_count) state.promptEval = obj.prompt_eval_count;
1880
+ if (obj.eval_count) state.evalCount = obj.eval_count;
1881
+ return state;
1882
+ }
1883
+
1884
+ function _ollamaFinalize(state) {
1885
+ const message = { role: state.role, content: state.content };
1886
+ if (state.toolCalls && state.toolCalls.length) {
1887
+ message.tool_calls = state.toolCalls;
1888
+ }
1889
+ const data = { message };
1890
+ if (state.promptEval || state.evalCount) {
1891
+ data.usage = {
1892
+ input_tokens: state.promptEval,
1893
+ output_tokens: state.evalCount,
1894
+ };
1895
+ }
1896
+ return data;
1897
+ }
1898
+
1899
+ /**
1900
+ * Pure reducer over an iterable of Ollama NDJSON lines. Exported for tests so
1901
+ * the parse/accumulate logic can be exercised without a live HTTP stream.
1902
+ */
1903
+ export function _accumulateOllamaStream(lines, onToken) {
1904
+ const state = _ollamaInitState();
1905
+ for (const line of lines) _ollamaReduceLine(state, line, onToken);
1906
+ return _ollamaFinalize(state);
1907
+ }
1908
+
1909
+ async function _chatOllamaStreaming(apiUrl, body, onToken, signal) {
1910
+ const response = await fetch(apiUrl, {
1911
+ method: "POST",
1912
+ headers: { "Content-Type": "application/json" },
1913
+ signal,
1914
+ body: JSON.stringify({ ...body, stream: true }),
1915
+ });
1916
+ if (!response.ok) {
1917
+ throw new Error(`Ollama error: ${response.status}`);
1918
+ }
1919
+ const state = _ollamaInitState();
1920
+ const reader = response.body.getReader();
1921
+ const decoder = new TextDecoder();
1922
+ let buf = "";
1923
+ for (;;) {
1924
+ const { done, value } = await reader.read();
1925
+ if (done) break;
1926
+ buf += decoder.decode(value, { stream: true });
1927
+ let idx;
1928
+ while ((idx = buf.indexOf("\n")) >= 0) {
1929
+ _ollamaReduceLine(state, buf.slice(0, idx), onToken);
1930
+ buf = buf.slice(idx + 1);
1931
+ }
1932
+ }
1933
+ if (buf.trim()) _ollamaReduceLine(state, buf, onToken);
1934
+ return _ollamaFinalize(state);
1935
+ }
1936
+
1820
1937
  function _normalizeAnthropicResponse(data) {
1821
1938
  const content = data.content || [];
1822
1939
  const textBlocks = content.filter((b) => b.type === "text");
@@ -1843,11 +1960,58 @@ function _normalizeAnthropicResponse(data) {
1843
1960
 
1844
1961
  // ─── Agent loop (async generator) ─────────────────────────────────────────
1845
1962
 
1963
+ // Tools that never mutate the workspace — auto-checkpoint skips these.
1964
+ const _CHECKPOINT_READ_ONLY = new Set([
1965
+ "read_file",
1966
+ "search_files",
1967
+ "list_dir",
1968
+ "list_skills",
1969
+ "search_sessions",
1970
+ ]);
1971
+
1972
+ let _checkpointStoreP = null;
1973
+ function _loadCheckpointStore() {
1974
+ if (!_checkpointStoreP) {
1975
+ _checkpointStoreP = import("../lib/checkpoint-store.js");
1976
+ }
1977
+ return _checkpointStoreP;
1978
+ }
1979
+
1980
+ /**
1981
+ * Best-effort auto-checkpoint of the working tree BEFORE a mutating tool runs,
1982
+ * so a later `cc checkpoint restore` can roll back to just before that tool.
1983
+ * Enabled via toolContext.autoCheckpoint; uses the git engine only (no-op
1984
+ * outside a git work tree). Never throws — checkpointing must not block a tool.
1985
+ *
1986
+ * @returns {Promise<string|null>} the checkpoint id, or null when skipped
1987
+ */
1988
+ async function _autoCheckpointBeforeTool(toolContext, toolName, toolArgs) {
1989
+ if (!toolContext?.autoCheckpoint) return null;
1990
+ if (_CHECKPOINT_READ_ONLY.has(toolName)) return null;
1991
+ const cwd = toolContext.cwd || process.cwd();
1992
+ try {
1993
+ const store = await _loadCheckpointStore();
1994
+ if (!store.isCheckpointAvailable(cwd)) return null;
1995
+ const res = store.createCheckpoint(cwd, {
1996
+ session: toolContext.checkpointSession || "agent",
1997
+ label: `before ${toolName}: ${formatToolArgs(toolName, toolArgs)}`.slice(
1998
+ 0,
1999
+ 120,
2000
+ ),
2001
+ skipIfUnchanged: true,
2002
+ });
2003
+ return res?.id || null;
2004
+ } catch {
2005
+ return null; // checkpoint failure must never block the tool
2006
+ }
2007
+ }
2008
+
1846
2009
  /**
1847
2010
  * Async generator that drives the agentic tool-use loop.
1848
2011
  *
1849
2012
  * Yields events:
1850
2013
  * { type: "slot-filling", slot, question } — when asking user for missing info
2014
+ * { type: "checkpoint", id, tool } — auto-checkpoint before a mutating tool
1851
2015
  * { type: "tool-executing", tool, args }
1852
2016
  * { type: "tool-result", tool, result, error }
1853
2017
  * { type: "response-complete", content }
@@ -1855,6 +2019,38 @@ function _normalizeAnthropicResponse(data) {
1855
2019
  * @param {Array} messages - mutable messages array (will be appended to)
1856
2020
  * @param {object} options - provider, model, baseUrl, apiKey, contextEngine, hookDb, skillLoader, cwd, slotFiller, interaction
1857
2021
  */
2022
+ /**
2023
+ * Lazily build (and cache on `options`) the PromptCompressor used for in-loop
2024
+ * auto-compaction. Returns null when the feature is off or the module can't be
2025
+ * loaded — callers treat that as "don't compact". Cached (including null) so we
2026
+ * import once per run, not once per iteration.
2027
+ */
2028
+ async function _getAutoCompactor(options) {
2029
+ if (Object.prototype.hasOwnProperty.call(options, "_autoCompactor")) {
2030
+ return options._autoCompactor;
2031
+ }
2032
+ let compressor = null;
2033
+ try {
2034
+ const { feature } = await import("../lib/feature-flags.js");
2035
+ if (feature("PROMPT_COMPRESSOR")) {
2036
+ const { PromptCompressor } =
2037
+ await import("../harness/prompt-compressor.js");
2038
+ compressor = new PromptCompressor({
2039
+ model: options.model,
2040
+ provider: options.provider,
2041
+ });
2042
+ }
2043
+ } catch {
2044
+ compressor = null;
2045
+ }
2046
+ try {
2047
+ options._autoCompactor = compressor;
2048
+ } catch {
2049
+ // options may be frozen — fine, we just re-import next iteration
2050
+ }
2051
+ return compressor;
2052
+ }
2053
+
1858
2054
  export async function* agentLoop(messages, options) {
1859
2055
  // Shared iteration budget — replaces hardcoded MAX_ITERATIONS.
1860
2056
  // When options.iterationBudget is provided (e.g. from parent agent),
@@ -1879,6 +2075,9 @@ export async function* agentLoop(messages, options) {
1879
2075
  approvalGate: options.approvalGate || null,
1880
2076
  shellConfirm: options.shellConfirm || null,
1881
2077
  additionalDirectories: options.additionalDirectories || null,
2078
+ autoCheckpoint: options.autoCheckpoint || false,
2079
+ checkpointSession:
2080
+ options.checkpointSession || options.sessionId || "agent",
1882
2081
  };
1883
2082
 
1884
2083
  throwIfAborted(signal);
@@ -1979,6 +2178,64 @@ export async function* agentLoop(messages, options) {
1979
2178
  };
1980
2179
  }
1981
2180
 
2181
+ // Headless auto-compaction (Claude-Code `--print` parity). Keeps long
2182
+ // `-p` / `--resume` runs under the model's context window instead of
2183
+ // growing until the provider rejects the request. Opt-out with
2184
+ // `autoCompact: false` (the interactive REPL does this — it compacts on its
2185
+ // own schedule). Default-on, gated by the PROMPT_COMPRESSOR flag + a size
2186
+ // threshold inside the compressor, so it only fires for genuinely large
2187
+ // contexts. Safe to compact here: the previous iteration always finishes
2188
+ // its full tool_call→tool_result cycle before we loop, so `messages` has no
2189
+ // dangling call; `preserveToolPairs` then guarantees compaction never
2190
+ // orphans a tool result. Best-effort — a failure never aborts the run.
2191
+ if (options.autoCompact !== false && messages.length > 4) {
2192
+ try {
2193
+ const compactor = await _getAutoCompactor(options);
2194
+ if (compactor && compactor.shouldAutoCompact(messages)) {
2195
+ const { messages: compacted, stats } = await compactor.compress(
2196
+ messages,
2197
+ { preserveToolPairs: true },
2198
+ );
2199
+ if (stats.saved > 0 && compacted.length < messages.length) {
2200
+ messages.splice(0, messages.length, ...compacted);
2201
+ // Persist the compaction so a later --resume rebuilds from the
2202
+ // shortened history. An explicit `onCompaction` hook (if a caller
2203
+ // provides one) takes precedence; otherwise self-persist a `compact`
2204
+ // event — but only when the session is already being persisted to
2205
+ // disk (the JSONL file exists), so a one-shot `cc agent -p` (which
2206
+ // never creates a session file) writes nothing. Opt out with
2207
+ // `persistCompaction: false`. Best-effort throughout.
2208
+ if (typeof options.onCompaction === "function") {
2209
+ try {
2210
+ options.onCompaction(stats, compacted);
2211
+ } catch {
2212
+ // persistence is best-effort
2213
+ }
2214
+ } else if (
2215
+ options.sessionId &&
2216
+ options.persistCompaction !== false
2217
+ ) {
2218
+ try {
2219
+ const store = await import("../harness/jsonl-session-store.js");
2220
+ if (store.sessionExists(options.sessionId)) {
2221
+ store.appendCompactEvent(options.sessionId, {
2222
+ ...stats,
2223
+ messages: compacted,
2224
+ });
2225
+ }
2226
+ } catch {
2227
+ // self-persist is best-effort
2228
+ }
2229
+ }
2230
+ yield { type: "compaction", stats, runId };
2231
+ }
2232
+ }
2233
+ } catch (_e) {
2234
+ if (isAbortError(_e) || signal?.aborted) throw _e;
2235
+ // Compaction is best-effort — proceed with the uncompacted messages.
2236
+ }
2237
+ }
2238
+
1982
2239
  // Turn-scoped context injection (open-agents prepareCall parity).
1983
2240
  // prepareCall runs fresh each iteration and returns an ephemeral
1984
2241
  // system-message supplement that is NOT persisted to messages history.
@@ -2050,6 +2307,15 @@ export async function* agentLoop(messages, options) {
2050
2307
  toolArgs = {};
2051
2308
  }
2052
2309
 
2310
+ // Auto-checkpoint the work tree before a mutating tool (opt-in), so the
2311
+ // user can `cc checkpoint restore` back to just before this call.
2312
+ const cpId = await _autoCheckpointBeforeTool(
2313
+ toolContext,
2314
+ toolName,
2315
+ toolArgs,
2316
+ );
2317
+ if (cpId) yield { type: "checkpoint", id: cpId, tool: toolName };
2318
+
2053
2319
  yield { type: "tool-executing", tool: toolName, args: toolArgs };
2054
2320
 
2055
2321
  let toolResult;
@@ -26,12 +26,14 @@ import {
26
26
  agentLoop as coreAgentLoop,
27
27
  formatToolArgs,
28
28
  } from "./agent-core.js";
29
+ import { loadMcpConfig } from "./mcp-config.js";
29
30
  import { IterationBudget } from "../lib/iteration-budget.js";
30
31
  import {
31
32
  startSession as jsonlStartSession,
32
33
  appendUserMessage as jsonlAppendUserMessage,
33
34
  appendAssistantMessage as jsonlAppendAssistantMessage,
34
35
  appendTokenUsage as jsonlAppendTokenUsage,
36
+ appendCompactEvent as jsonlAppendCompactEvent,
35
37
  rebuildMessages as jsonlRebuildMessages,
36
38
  sessionExists as jsonlSessionExists,
37
39
  getLastSessionId as jsonlGetLastSessionId,
@@ -190,6 +192,8 @@ export function resolveHeadlessSession(options = {}, store = {}, fallbackId) {
190
192
  * (no id) → most-recent session.
191
193
  * @param {boolean} [options.continueSession] Resume the most-recent session.
192
194
  * @param {boolean} [options.persistSession] Force persistence without resume.
195
+ * @param {boolean} [options.autoCheckpoint] Snapshot the work tree before each
196
+ * mutating tool (git engine only).
193
197
  * @param {boolean} [options.expandFileRefs=true] Expand `@path` file references
194
198
  * in the prompt into context blocks.
195
199
  * @param {object} [deps] Injection seam for tests.
@@ -243,6 +247,7 @@ export async function runAgentHeadless(options = {}, deps = {}) {
243
247
  appendAssistantMessage:
244
248
  deps.appendAssistantMessage || jsonlAppendAssistantMessage,
245
249
  appendTokenUsage: deps.appendTokenUsage || jsonlAppendTokenUsage,
250
+ appendCompactEvent: deps.appendCompactEvent || jsonlAppendCompactEvent,
246
251
  getLastSessionId: deps.getLastSessionId || jsonlGetLastSessionId,
247
252
  };
248
253
  const isStream = outputFormat === "stream-json";
@@ -365,6 +370,26 @@ export async function runAgentHeadless(options = {}, deps = {}) {
365
370
  }
366
371
  }
367
372
 
373
+ // --mcp-config: connect ad-hoc MCP servers for this run and expose their
374
+ // tools to the LLM (Claude-Code parity). Connection is best-effort — a server
375
+ // that fails to connect is logged to stderr and contributes no tools; a
376
+ // missing/empty config file fails fast (the user explicitly asked for MCP).
377
+ let mcp = null;
378
+ if (options.mcpConfig) {
379
+ const doLoadMcp = deps.loadMcpConfig || loadMcpConfig;
380
+ try {
381
+ mcp = await doLoadMcp(options.mcpConfig, { writeErr });
382
+ if (isText) {
383
+ for (const c of mcp.connected) {
384
+ writeErr(` mcp: ${c.server} (${c.tools} tools)\n`);
385
+ }
386
+ }
387
+ } catch (err) {
388
+ writeErr(`Error: ${err.message}\n`);
389
+ return { exitCode: 1, result: err.message, isError: true };
390
+ }
391
+ }
392
+
368
393
  const loopOptions = {
369
394
  model,
370
395
  provider,
@@ -373,16 +398,55 @@ export async function runAgentHeadless(options = {}, deps = {}) {
373
398
  cwd,
374
399
  additionalDirectories,
375
400
  sessionId,
401
+ autoCheckpoint: options.autoCheckpoint || false,
402
+ checkpointSession: options.checkpointSession || sessionId,
376
403
  hookDb: db,
377
404
  approvalGate,
378
405
  enabledToolNames,
379
406
  disabledTools,
380
407
  iterationBudget: budget,
408
+ // --mcp-config wiring: tool defs for the LLM + dispatch map + live client.
409
+ mcpClient: mcp?.mcpClient || null,
410
+ extraToolDefinitions: mcp?.extraToolDefinitions || undefined,
411
+ externalToolExecutors: mcp?.externalToolExecutors || undefined,
412
+ externalToolDescriptors: mcp?.externalToolDescriptors || undefined,
381
413
  // chatFn passthrough lets tests drive the loop deterministically.
382
414
  chatFn: deps.chatFn || options.chatFn || undefined,
383
415
  signal: options.signal || undefined,
384
416
  };
385
417
 
418
+ // Goal binding (cc goal, Phase 1). `--goal <id>` binds explicitly; `--goal`
419
+ // with no value (options.goal === true) auto-resolves from active/session.
420
+ // When omitted, headless stays goal-free (no behavior change). Best-effort:
421
+ // a failure here must never fail the run.
422
+ let boundGoalId = null;
423
+ if (options.goal !== undefined && options.goal !== false) {
424
+ try {
425
+ const explicitId = typeof options.goal === "string" ? options.goal : null;
426
+ const { resolveActiveGoal, linkSession } =
427
+ await import("../lib/goal-store.js");
428
+ const goal = (deps.resolveActiveGoal || resolveActiveGoal)({
429
+ explicitId,
430
+ sessionId,
431
+ });
432
+ if (goal) {
433
+ const { goalPrepareCall } = await import("../lib/goal-context.js");
434
+ loopOptions.prepareCall = goalPrepareCall(goal);
435
+ boundGoalId = goal.id;
436
+ // Link the session so a later `--continue`/`--resume` keeps this goal.
437
+ if (explicitId && persist !== false) {
438
+ try {
439
+ linkSession(goal.id, sessionId);
440
+ } catch {
441
+ /* linking is optional polish — never fatal */
442
+ }
443
+ }
444
+ }
445
+ } catch {
446
+ /* goal binding is best-effort — proceed without it */
447
+ }
448
+ }
449
+
386
450
  const startedAt = deps.now ? deps.now() : Date.now();
387
451
  const toolCalls = [];
388
452
  const usage = { input_tokens: 0, output_tokens: 0 };
@@ -393,6 +457,21 @@ export async function runAgentHeadless(options = {}, deps = {}) {
393
457
  if (isStream) writeOut(JSON.stringify(obj) + "\n");
394
458
  };
395
459
 
460
+ // --include-partial-messages: forward live assistant-text deltas as
461
+ // `stream_event` NDJSON lines (Claude-Code parity). Only meaningful for
462
+ // stream-json output, where the agent loop's onToken hook feeds chunks as
463
+ // they arrive from a streaming provider.
464
+ if (isStream && options.includePartialMessages) {
465
+ loopOptions.onToken = (text) =>
466
+ emitStream({
467
+ type: "stream_event",
468
+ event: {
469
+ type: "content_block_delta",
470
+ delta: { type: "text_delta", text },
471
+ },
472
+ });
473
+ }
474
+
396
475
  emitStream({
397
476
  type: "system",
398
477
  subtype: "init",
@@ -405,11 +484,18 @@ export async function runAgentHeadless(options = {}, deps = {}) {
405
484
  resumed_from: resumeId,
406
485
  history_messages: history.length,
407
486
  additional_directories: additionalDirectories,
487
+ goal_id: boundGoalId,
408
488
  });
409
489
 
410
490
  try {
411
491
  for await (const event of runLoop(messages, loopOptions)) {
412
492
  switch (event.type) {
493
+ case "checkpoint": {
494
+ if (isText)
495
+ writeErr(` ⎌ checkpoint ${event.id} (before ${event.tool})\n`);
496
+ emitStream({ type: "checkpoint", id: event.id, tool: event.tool });
497
+ break;
498
+ }
413
499
  case "tool-executing": {
414
500
  const line = ` [${event.tool}] ${formatToolArgs(event.tool, event.args)}`;
415
501
  if (isText) writeErr(line + "\n");
@@ -497,6 +583,17 @@ export async function runAgentHeadless(options = {}, deps = {}) {
497
583
  writeErr(`Error: ${message}\n`);
498
584
  }
499
585
  return { exitCode: 1, result: message, isError: true };
586
+ } finally {
587
+ // Tear down ad-hoc MCP servers (--mcp-config) before returning, whether the
588
+ // loop completed or threw. Best-effort: a failed disconnect never masks the
589
+ // run's own outcome.
590
+ if (mcp?.mcpClient) {
591
+ try {
592
+ await mcp.mcpClient.disconnectAll();
593
+ } catch {
594
+ // ignore — disconnect is best-effort
595
+ }
596
+ }
500
597
  }
501
598
 
502
599
  // coreAgentLoop emits run-ended reason "budget-exhausted" when the iteration
@@ -518,6 +615,56 @@ export async function runAgentHeadless(options = {}, deps = {}) {
518
615
  }
519
616
  }
520
617
 
618
+ // Run-end goal self-assessment (cc goal Phase 2, opt-in via --goal-assess).
619
+ // Spends one extra completion to judge whether the run advanced the bound
620
+ // goal, then persists progress / key-result / drift updates. Best-effort: it
621
+ // must never change the run's own outcome.
622
+ if (options.goalAssess && boundGoalId && !isError) {
623
+ try {
624
+ const { getGoal } = await import("../lib/goal-store.js");
625
+ const goal = (deps.getGoal || getGoal)(boundGoalId);
626
+ if (goal) {
627
+ const { assessGoalProgress } = await import("../lib/goal-assess.js");
628
+ const doAssess = deps.assessGoalProgress || assessGoalProgress;
629
+ const assessChat =
630
+ deps.assessChat ||
631
+ (async (assessPrompt) => {
632
+ const { chatWithTools } = await import("./agent-core.js");
633
+ const r = await chatWithTools(
634
+ [{ role: "user", content: assessPrompt }],
635
+ { model, provider, baseUrl, apiKey, enabledToolNames: [] },
636
+ );
637
+ return r?.message?.content || "";
638
+ });
639
+ const { assessment } = await doAssess({
640
+ goal,
641
+ transcript: { prompt: options.prompt, finalText, toolCalls },
642
+ chat: assessChat,
643
+ });
644
+ if (assessment) {
645
+ if (isText) {
646
+ writeErr(
647
+ ` ◎ goal ${boundGoalId}: ${assessment.advanced ? "advanced" : "no progress"}` +
648
+ (assessment.progress != null
649
+ ? ` (${assessment.progress}%)`
650
+ : "") +
651
+ "\n",
652
+ );
653
+ }
654
+ emitStream({
655
+ type: "goal_assessment",
656
+ goal_id: boundGoalId,
657
+ advanced: assessment.advanced,
658
+ progress: assessment.progress,
659
+ note: assessment.note,
660
+ });
661
+ }
662
+ }
663
+ } catch {
664
+ /* assessment is best-effort — never affect the run outcome */
665
+ }
666
+ }
667
+
521
668
  if (isStream) {
522
669
  emitStream({
523
670
  type: "result",
@@ -207,6 +207,26 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
207
207
  additional_directories: additionalDirectories,
208
208
  });
209
209
 
210
+ // Goal binding (cc goal, Phase 1) — resolved once and injected on every turn.
211
+ // `--goal <id>` binds explicitly; `--goal` with no value auto-resolves.
212
+ let goalPrepareCallFn;
213
+ if (options.goal !== undefined && options.goal !== false) {
214
+ try {
215
+ const explicitId = typeof options.goal === "string" ? options.goal : null;
216
+ const { resolveActiveGoal } = await import("../lib/goal-store.js");
217
+ const goal = (deps.resolveActiveGoal || resolveActiveGoal)({
218
+ explicitId,
219
+ sessionId,
220
+ });
221
+ if (goal) {
222
+ const { goalPrepareCall } = await import("../lib/goal-context.js");
223
+ goalPrepareCallFn = goalPrepareCall(goal);
224
+ }
225
+ } catch {
226
+ /* goal binding is best-effort — proceed without it */
227
+ }
228
+ }
229
+
210
230
  const loopOptionsBase = {
211
231
  model,
212
232
  provider,
@@ -219,8 +239,22 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
219
239
  approvalGate,
220
240
  enabledToolNames,
221
241
  disabledTools,
242
+ prepareCall: goalPrepareCallFn,
222
243
  chatFn: deps.chatFn || options.chatFn || undefined,
223
244
  signal: options.signal || undefined,
245
+ // --include-partial-messages: stream live assistant-text deltas as
246
+ // `stream_event` lines. Output here is always NDJSON, so gating on the
247
+ // flag alone suffices.
248
+ onToken: options.includePartialMessages
249
+ ? (text) =>
250
+ emit({
251
+ type: "stream_event",
252
+ event: {
253
+ type: "content_block_delta",
254
+ delta: { type: "text_delta", text },
255
+ },
256
+ })
257
+ : undefined,
224
258
  };
225
259
 
226
260
  let turns = 0;