chainlesschain 0.166.107 → 0.166.109

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 (184) hide show
  1. package/README.md +18 -9
  2. package/package.json +1 -1
  3. package/src/assets/linux-subreaper/linux-arm64/manifest.json +1 -1
  4. package/src/assets/linux-subreaper/linux-x64/manifest.json +1 -1
  5. package/src/assets/web-panel/assets/{AIOps-DCx6L_HW.js → AIOps-DkrNbDzS.js} +1 -1
  6. package/src/assets/web-panel/assets/{ActionButton-DYm1CN3Z.js → ActionButton-DJjFXDhv.js} +1 -1
  7. package/src/assets/web-panel/assets/{Analytics-D5ItUS_y.js → Analytics-DyUbagRn.js} +3 -3
  8. package/src/assets/web-panel/assets/{AppLayout-Cb7XeRdH.js → AppLayout-p7kJWSvn.js} +5 -5
  9. package/src/assets/web-panel/assets/{Artifacts-DDDeqCDQ.js → Artifacts-C3rVh4Ak.js} +1 -1
  10. package/src/assets/web-panel/assets/{Audit-BRt3x13w.js → Audit-Bch_L_YO.js} +1 -1
  11. package/src/assets/web-panel/assets/{BackgroundAgents-C_oNALMd.js → BackgroundAgents-BNHy8zP6.js} +1 -1
  12. package/src/assets/web-panel/assets/{Backup-PSU2p6CR.js → Backup-BUaye_Ys.js} +1 -1
  13. package/src/assets/web-panel/assets/{BaseInput-G3oL4hcX.js → BaseInput-D49mzAVz.js} +1 -1
  14. package/src/assets/web-panel/assets/{Chat-C2cLI10b.js → Chat-2MDzM_us.js} +6 -6
  15. package/src/assets/web-panel/assets/ChatBubbleRenderer-DOX3Y5EJ.js +1 -0
  16. package/src/assets/web-panel/assets/{Checkbox-CHLnEgFD.js → Checkbox-5p1T_KEq.js} +1 -1
  17. package/src/assets/web-panel/assets/{Codegen-DZm9g_Bc.js → Codegen-TKtiLaoZ.js} +1 -1
  18. package/src/assets/web-panel/assets/{Col-D5Z4zSay.js → Col-BVCMG9mS.js} +1 -1
  19. package/src/assets/web-panel/assets/{Community-DHw69NSk.js → Community-D09-QczH.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compact-B-vjYNWz.js → Compact-QuRMCnj7.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compliance-DaIYVZQY.js → Compliance-CN0PV7fk.js} +1 -1
  22. package/src/assets/web-panel/assets/{Cowork-CoA9U-gF.js → Cowork-5N2zhAty.js} +2 -2
  23. package/src/assets/web-panel/assets/{Cron-DXqmxlRC.js → Cron-BxT8UZ0S.js} +2 -2
  24. package/src/assets/web-panel/assets/{Crosschain-Cdkc3ue1.js → Crosschain-Dp-noQl4.js} +1 -1
  25. package/src/assets/web-panel/assets/{DID-oKfYyNSR.js → DID-B7MZFkjG.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dashboard-n_ycvgWu.js → Dashboard-DulJ3rKV.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dropdown-D38rZ8WY.js → Dropdown-DV4ov4zF.js} +1 -1
  28. package/src/assets/web-panel/assets/{EmailListRenderer-BjreeBQ1.js → EmailListRenderer-DB2-ooz6.js} +1 -1
  29. package/src/assets/web-panel/assets/{EvolutionSettings-CTxA_SIq.js → EvolutionSettings-B1Rb6p7q.js} +1 -1
  30. package/src/assets/web-panel/assets/{FamilyGuardDashboard-DmECNwV5.js → FamilyGuardDashboard-U8aHayx-.js} +1 -1
  31. package/src/assets/web-panel/assets/{Federation-B2IaaDmQ.js → Federation-BkQULBHY.js} +1 -1
  32. package/src/assets/web-panel/assets/{FormItemContext-DgcgqUIk.js → FormItemContext-CtviG260.js} +1 -1
  33. package/src/assets/web-panel/assets/{GenericCardRenderer-B1-du6Qm.js → GenericCardRenderer-DnPirB24.js} +1 -1
  34. package/src/assets/web-panel/assets/{Git-XceM21Il.js → Git-CK57Oro6.js} +2 -2
  35. package/src/assets/web-panel/assets/{Governance-CV97iz3z.js → Governance-DbI-LB8-.js} +1 -1
  36. package/src/assets/web-panel/assets/{Inference-CI7fj305.js → Inference-sEG4T1Jg.js} +1 -1
  37. package/src/assets/web-panel/assets/{KnowledgeGraph-B5mekWRy.js → KnowledgeGraph-9SLJR3h9.js} +1 -1
  38. package/src/assets/web-panel/assets/{Logs-BlxFA0xY.js → Logs-BIZYcISQ.js} +2 -2
  39. package/src/assets/web-panel/assets/{MarkdownRenderer-rDE-XyMY.js → MarkdownRenderer-9nfWupWP.js} +1 -1
  40. package/src/assets/web-panel/assets/{Marketplace-DL1BVCiX.js → Marketplace-CRmLLwAE.js} +1 -1
  41. package/src/assets/web-panel/assets/{McpTools-BPtwrO3c.js → McpTools-DkOFkSO1.js} +3 -3
  42. package/src/assets/web-panel/assets/{Memory-DuSNw4E-.js → Memory-M29Kd9TU.js} +2 -2
  43. package/src/assets/web-panel/assets/{MobileBridge-DTgY9ywI.js → MobileBridge-Tl6JvPw_.js} +1 -1
  44. package/src/assets/web-panel/assets/MobileProjects-Bq-FPDzS.js +1 -0
  45. package/src/assets/web-panel/assets/{Mtc-h4fCLjXw.js → Mtc-DkUjX1F5.js} +2 -2
  46. package/src/assets/web-panel/assets/{MtcAudit-fqMDY3-w.js → MtcAudit-BCqr1I4q.js} +2 -2
  47. package/src/assets/web-panel/assets/{Multisig-Do3FehBL.js → Multisig-DmoqYGQ5.js} +3 -3
  48. package/src/assets/web-panel/assets/{NLProgramming-CQVY0kQF.js → NLProgramming-DNRQeRke.js} +1 -1
  49. package/src/assets/web-panel/assets/{Notes-4c3oNvWh.js → Notes-CQSBeiY6.js} +3 -3
  50. package/src/assets/web-panel/assets/{NotificationSettings-BKr2JD4k.js → NotificationSettings-gNY9bbH6.js} +1 -1
  51. package/src/assets/web-panel/assets/{OrderTableRenderer-Bq4jgzRt.js → OrderTableRenderer-DW0CNjh8.js} +1 -1
  52. package/src/assets/web-panel/assets/{Organization-Dy3XidyA.js → Organization-CthnbKag.js} +4 -4
  53. package/src/assets/web-panel/assets/{Overflow-DFF0qN5K.js → Overflow-_GQ3sbXu.js} +1 -1
  54. package/src/assets/web-panel/assets/{P2P-DAgYYykd.js → P2P-BTr7ea6W.js} +2 -2
  55. package/src/assets/web-panel/assets/PdhVaultBrowser-D9McHF9I.js +7 -0
  56. package/src/assets/web-panel/assets/{Permissions-Cev6uSBc.js → Permissions-DMny2Cjs.js} +3 -3
  57. package/src/assets/web-panel/assets/{PersonalDataHub-DJzbN06K.js → PersonalDataHub-CyYlMSoI.js} +4 -4
  58. package/src/assets/web-panel/assets/{Pipeline-CoJWvbQ5.js → Pipeline-FASu2Oc4.js} +1 -1
  59. package/src/assets/web-panel/assets/{Privacy-DZxV6etm.js → Privacy-9bC5FnsJ.js} +1 -1
  60. package/src/assets/web-panel/assets/{ProjectInit-BFW87JnM.js → ProjectInit-BOYZwB5G.js} +2 -2
  61. package/src/assets/web-panel/assets/{ProjectSettings-LBrRT27p.js → ProjectSettings-45DViNG-.js} +2 -2
  62. package/src/assets/web-panel/assets/Projects-C2fZzMZc.js +1 -0
  63. package/src/assets/web-panel/assets/{Providers-CnRbjOYk.js → Providers-BgjvZx9F.js} +1 -1
  64. package/src/assets/web-panel/assets/{QrScannerModal-BVFnIS3r.js → QrScannerModal-Brw-3csB.js} +1 -1
  65. package/src/assets/web-panel/assets/{QuickAsk-DrcHx5f1.js → QuickAsk-DWG-A5zB.js} +1 -1
  66. package/src/assets/web-panel/assets/{Recommend-CbRgrSJm.js → Recommend-DuQGxjIR.js} +1 -1
  67. package/src/assets/web-panel/assets/{RemoteSession-B2Uz_ElB.js → RemoteSession-T1cdSSRL.js} +2 -2
  68. package/src/assets/web-panel/assets/{Reputation-DS0ljTxL.js → Reputation-9FT2dI4G.js} +1 -1
  69. package/src/assets/web-panel/assets/{Row-ClLobFht.js → Row-yNIbNftX.js} +1 -1
  70. package/src/assets/web-panel/assets/{RssFeed-DxxcTD0x.js → RssFeed-BIchNoPd.js} +3 -3
  71. package/src/assets/web-panel/assets/{Search-BbHN0Fik.js → Search-msDJ8UTZ.js} +1 -1
  72. package/src/assets/web-panel/assets/{Security-BAVU_EcY.js → Security-DE46oBYT.js} +3 -3
  73. package/src/assets/web-panel/assets/{Services-CtqRSVq_.js → Services-Dori7vc3.js} +2 -2
  74. package/src/assets/web-panel/assets/{Skeleton-IJ8ZKdSZ.js → Skeleton-D3O3sUFz.js} +1 -1
  75. package/src/assets/web-panel/assets/{Skills-DXxcznnO.js → Skills-BV0ct8SO.js} +1 -1
  76. package/src/assets/web-panel/assets/{Sla-fRkhnA3i.js → Sla-DflIVOrh.js} +1 -1
  77. package/src/assets/web-panel/assets/{SpeechSettings-Bi8sYJ-I.js → SpeechSettings-Cpbv2Zz6.js} +1 -1
  78. package/src/assets/web-panel/assets/{SyncSettings-CjeplUtS.js → SyncSettings-Dgg6-wbe.js} +2 -2
  79. package/src/assets/web-panel/assets/{Tasks-D4KB2Hbx.js → Tasks-Cb6e4Klx.js} +1 -1
  80. package/src/assets/web-panel/assets/{Templates-BwVugi8n.js → Templates-GfdAATGz.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tenant-CsPuu5xK.js → Tenant-D0gecLXl.js} +1 -1
  82. package/src/assets/web-panel/assets/{Terminal-DuaeOPjY.js → Terminal-BFRPJt-0.js} +2 -2
  83. package/src/assets/web-panel/assets/{TimelineRenderer-D-PMrFxO.js → TimelineRenderer-BobtChxX.js} +1 -1
  84. package/src/assets/web-panel/assets/{Tokens-BXZuiQPZ.js → Tokens-CifeZtHV.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trigger-DgpTaHL3.js → Trigger-BHBvbBCf.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trust-Bn7lEGRY.js → Trust-6lvqjKYp.js} +1 -1
  87. package/src/assets/web-panel/assets/{UkeySign-zz-8JAAQ.js → UkeySign-CqzMc-G5.js} +1 -1
  88. package/src/assets/web-panel/assets/{VideoEditing-DCzfQGTc.js → VideoEditing-OIiRG582.js} +1 -1
  89. package/src/assets/web-panel/assets/{Wallet-BiRHM_zB.js → Wallet-4MeKq0q_.js} +4 -4
  90. package/src/assets/web-panel/assets/{WebAuthn-QmPo2JwU.js → WebAuthn-eHBRjGQG.js} +5 -5
  91. package/src/assets/web-panel/assets/{WorkflowEditor-gL7SsPnY.js → WorkflowEditor-DlzYdEqW.js} +1 -1
  92. package/src/assets/web-panel/assets/{chat-BysvK4hk.js → chat-CBcjOzkX.js} +1 -1
  93. package/src/assets/web-panel/assets/{colors-CQq20vqN.js → colors-zgwsYQSa.js} +1 -1
  94. package/src/assets/web-panel/assets/{compact-item-CAHlOf1X.js → compact-item-Dcd9H5Bn.js} +1 -1
  95. package/src/assets/web-panel/assets/{createContext-BeF3ESj_.js → createContext-DR1tO-8r.js} +1 -1
  96. package/src/assets/web-panel/assets/devWarning-DYhpRtve.js +1 -0
  97. package/src/assets/web-panel/assets/{hasIn-CiqnRf-U.js → hasIn-CtfbUH_p.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-Cp3IRTDK.js → index-5XaB_Tjg.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-DKYbru3i.js → index-B3QUK1ks.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-CCX0daFQ.js → index-BXfGHUz_.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CgkUkmEJ.js → index-BndPtS9D.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-zNkw0IY7.js → index-BoxowMYS.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-i9aJV9qk.js → index-C0kP88Pb.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BtWchYfW.js → index-C5XGvJb0.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-BHpHGGmB.js → index-CPLLgPxA.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-qjaCk1Mj.js → index-CSQCHpDo.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CImCEo8S.js → index-CT7zIkqP.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-Br5tGVbC.js → index-CaGNqkY0.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-xwtRrgVY.js → index-Cdhy5ckh.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CowbNBJs.js → index-CmVIM-m2.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-CTp89vmg.js → index-CmzQzTY5.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-D7q5VH2n.js → index-Cur8LNNn.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-CeIL3-nY.js → index-CwHgaYVH.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-jJhemX1R.js → index-D1GxtY-h.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-DTPUVm71.js → index-D7ZOiIKb.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-QALafVsT.js → index-D8WPohye.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-ZBuJ6O6g.js → index-DA2-Bhy8.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-B5941vyo.js → index-DHEiuvhA.js} +1 -1
  119. package/src/assets/web-panel/assets/index-DPJXFIY7.js +1 -0
  120. package/src/assets/web-panel/assets/{index-BsnLkZjz.js → index-DSvcz0RO.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-DHGtFwwR.js → index-DTx4FyLU.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-BOiyW31p.js → index-DVpp9YSQ.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-B2AOvjgR.js → index-DWG98orp.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-BfJabF_8.js → index-DWuCGhHa.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-DBmGQHEz.js → index-DYPMJ5Mb.js} +3 -3
  126. package/src/assets/web-panel/assets/{index-B2xbtl_c.js → index-DeAi4CNt.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-Bvkbj3x-.js → index-DfzOYmOj.js} +1 -1
  128. package/src/assets/web-panel/assets/index-DjBhR5M_.js +1 -0
  129. package/src/assets/web-panel/assets/{index-DXsXhR7p.js → index-DvPOpvvH.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-yFQWSRoW.js → index-J-ORW1i6.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-TdVEaJfk.js → index-JSfldpw4.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-CcccefJc.js → index-LN7Hkl1C.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-Dqkls2_6.js → index-dvF6C39V.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-BcVmt6u_.js → index-hTcre7U9.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-DXT7MKkD.js → index-oWOlF7CM.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-4a0Lmz_y.js → index-z8lrkcb-.js} +1 -1
  137. package/src/assets/web-panel/assets/{initDefaultProps-BQR2HnKb.js → initDefaultProps-CVMtDrWZ.js} +1 -1
  138. package/src/assets/web-panel/assets/{motion-gAz6l-nZ.js → motion-Dye3IU8k.js} +1 -1
  139. package/src/assets/web-panel/assets/{move-Cy5TN856.js → move-C251Zo34.js} +1 -1
  140. package/src/assets/web-panel/assets/{mtc-parser-BeT7VrGi.js → mtc-parser-BDiOORoQ.js} +1 -1
  141. package/src/assets/web-panel/assets/{omit-BLN3mlgK.js → omit-DTDA_RHv.js} +1 -1
  142. package/src/assets/web-panel/assets/{pickAttrs-DtwZ8u2d.js → pickAttrs-CqEUTPcT.js} +1 -1
  143. package/src/assets/web-panel/assets/{placementArrow-ViMwQSa-.js → placementArrow-BOB8QWH3.js} +1 -1
  144. package/src/assets/web-panel/assets/{responsiveObserve-dEL3u4E7.js → responsiveObserve-CZDXP1d9.js} +1 -1
  145. package/src/assets/web-panel/assets/{slide-l05us-Gz.js → slide-DPaRw3E9.js} +1 -1
  146. package/src/assets/web-panel/assets/{statusUtils-Bk-Qn2Og.js → statusUtils-Ba9d7XZ7.js} +1 -1
  147. package/src/assets/web-panel/assets/{styleChecker-Bn186qeY.js → styleChecker-mZKU3lvW.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFlexGapSupport-WP-ugR3H.js → useFlexGapSupport-VcauLq0M.js} +1 -1
  149. package/src/assets/web-panel/assets/{useFs-1SvoeSCL.js → useFs-Cal9KRcx.js} +1 -1
  150. package/src/assets/web-panel/assets/{usePersonalDataHub-CfNU8N3z.js → usePersonalDataHub-DISoRyQB.js} +1 -1
  151. package/src/assets/web-panel/assets/{vnode-Bd2Ya101.js → vnode-B58C6w6B.js} +1 -1
  152. package/src/assets/web-panel/assets/{zoom-Def_MeZv.js → zoom-BPlBAMKB.js} +1 -1
  153. package/src/assets/web-panel/index.html +1 -1
  154. package/src/data/changelog.json +29 -3
  155. package/src/gateways/ws/llm-chat-protocol.js +12 -2
  156. package/src/gateways/ws/llm-creds.js +50 -22
  157. package/src/gateways/ws/ws-server.js +47 -0
  158. package/src/harness/jsonl-session-store.js +128 -5
  159. package/src/harness/prompt-compressor.js +18 -10
  160. package/src/harness/structured-handoff.js +151 -13
  161. package/src/lib/agent-session-export.js +20 -0
  162. package/src/lib/background-agent-supervisor.js +54 -0
  163. package/src/lib/coding-task-guidance.js +2 -1
  164. package/src/lib/context-memory-kernel/live-compaction.js +75 -12
  165. package/src/lib/read-file-loop-guard.js +124 -8
  166. package/src/lib/session-host-snapshot.js +3 -0
  167. package/src/lib/session-message-provenance.js +24 -5
  168. package/src/lib/session-tool-round.js +39 -0
  169. package/src/lib/session-transcript-history.js +2 -0
  170. package/src/lib/session-transcript-page.js +4 -1
  171. package/src/lib/session-transcript-structure.js +4 -0
  172. package/src/runtime/__tests__/message-roles.test.js +29 -0
  173. package/src/runtime/agent-core.js +115 -43
  174. package/src/runtime/headless-runner.js +191 -34
  175. package/src/runtime/headless-stream.js +140 -42
  176. package/src/runtime/message-roles.js +43 -0
  177. package/src/workers/background-agent-worker.js +146 -47
  178. package/src/assets/web-panel/assets/ChatBubbleRenderer-KX1OITf8.js +0 -1
  179. package/src/assets/web-panel/assets/MobileProjects-CTpVHJMa.js +0 -1
  180. package/src/assets/web-panel/assets/PdhVaultBrowser-DgNHa8b1.js +0 -7
  181. package/src/assets/web-panel/assets/Projects-O3uz76R1.js +0 -1
  182. package/src/assets/web-panel/assets/devWarning-D-Iugqxv.js +0 -1
  183. package/src/assets/web-panel/assets/index-B0Oz2_rQ.js +0 -1
  184. package/src/assets/web-panel/assets/index-DwoycEQa.js +0 -1
@@ -46,6 +46,7 @@ import {
46
46
  buildReadFileOutline,
47
47
  } from "../lib/read-file-page.js";
48
48
  import { ReadFileLoopGuard } from "../lib/read-file-loop-guard.js";
49
+ const READ_FILE_REQUEST_CONTEXT = Symbol("read-file-request-context");
49
50
  import { gitToolInputError } from "../lib/git-tool-input.js";
50
51
  import { shellOutputPage, shellOutputPreview } from "../lib/shell-output.js";
51
52
  import { DIAGNOSTIC_WORKFLOW_GUIDANCE } from "../lib/diagnostic-workflow.js";
@@ -232,7 +233,10 @@ import {
232
233
  formatStructuredHandoff,
233
234
  } from "../harness/structured-handoff.js";
234
235
  import { isMcpRpcError } from "../harness/mcp-client.js";
235
- import { projectCanonicalResumeMessages } from "../lib/session-message-provenance.js";
236
+ import {
237
+ projectCanonicalResumeMessages,
238
+ markRuntimeOnlySessionMessage,
239
+ } from "../lib/session-message-provenance.js";
236
240
  import { releaseOldLiveSessionResults } from "../lib/session-runtime-retention.js";
237
241
  import {
238
242
  pathMatchesOpenedFileIdentitySync,
@@ -4935,7 +4939,19 @@ export async function _withPostEditDiagnostics(
4935
4939
  // Parsing the bytes we just wrote is safe even when starting a language
4936
4940
  // server is forbidden. Feed definite local import mistakes back in the same
4937
4941
  // tool result; a successful write still means persistence, not test success.
4938
- let enriched = result;
4942
+ let enriched =
4943
+ result?.success === true && typeof writtenContent === "string"
4944
+ ? {
4945
+ ...result,
4946
+ validation: {
4947
+ persisted: true,
4948
+ testsExecuted: false,
4949
+ relativeImports: "not-checked",
4950
+ message:
4951
+ "File saved. This file operation did not run tests. Any import feedback checks explicit JavaScript relative references only and does not establish test or task acceptance.",
4952
+ },
4953
+ }
4954
+ : result;
4939
4955
  if (
4940
4956
  result?.success === true &&
4941
4957
  typeof writtenContent === "string" &&
@@ -4956,9 +4972,21 @@ export async function _withPostEditDiagnostics(
4956
4972
  content: writtenContent,
4957
4973
  workspaceRoot: root,
4958
4974
  });
4975
+ enriched = {
4976
+ ...enriched,
4977
+ validation: {
4978
+ ...enriched.validation,
4979
+ relativeImports: checked.status,
4980
+ checkedReferences: checked.checkedReferences,
4981
+ uncheckedReferences: checked.uncheckedReferences.length,
4982
+ truncated: checked.truncated,
4983
+ diagnosticsTruncated: checked.diagnostics.length > 8,
4984
+ ...(checked.reason ? { skipReason: checked.reason } : {}),
4985
+ },
4986
+ };
4959
4987
  if (checked.diagnostics.length) {
4960
4988
  enriched = {
4961
- ...result,
4989
+ ...enriched,
4962
4990
  newDiagnostics: [
4963
4991
  ...(result.newDiagnostics || []),
4964
4992
  ...checked.diagnostics.slice(0, 8),
@@ -12103,6 +12131,10 @@ export async function chatWithTools(rawMessages, options) {
12103
12131
  tools = projected.tools;
12104
12132
  }
12105
12133
 
12134
+ if (options[READ_FILE_REQUEST_CONTEXT]) {
12135
+ options[READ_FILE_REQUEST_CONTEXT].messages = messages;
12136
+ }
12137
+
12106
12138
  throwIfAborted(signal);
12107
12139
 
12108
12140
  // Host accounting starts only after request admission. A rejected model
@@ -14697,7 +14729,9 @@ export async function* agentLoop(messages, options) {
14697
14729
  // These tools are alternative ways to deliver content to the same target.
14698
14730
  // A successful explicit replacement must settle a deferred edit (and vice
14699
14731
  // versa); move/delete/notebook effects retain their distinct obligations.
14700
- const effect = ["write_file", "edit_file", "edit_file_hashed"].includes(tool)
14732
+ const effect = ["write_file", "edit_file", "edit_file_hashed"].includes(
14733
+ tool,
14734
+ )
14701
14735
  ? "file-content"
14702
14736
  : tool;
14703
14737
  const key = JSON.stringify([effect, targets]);
@@ -15850,10 +15884,12 @@ export async function* agentLoop(messages, options) {
15850
15884
  // SubagentStop hooks are best-effort
15851
15885
  }
15852
15886
  }
15853
- messages.push({
15854
- role: "user",
15855
- content: _backgroundSubAgentResultText(entry),
15856
- });
15887
+ messages.push(
15888
+ markRuntimeOnlySessionMessage({
15889
+ role: "user",
15890
+ content: _backgroundSubAgentResultText(entry),
15891
+ }),
15892
+ );
15857
15893
  taskProgressTracker.retainChildResult(
15858
15894
  entry.id,
15859
15895
  {
@@ -16002,6 +16038,11 @@ export async function* agentLoop(messages, options) {
16002
16038
  ];
16003
16039
  }
16004
16040
  }
16041
+ // Role normalization is request-only: durable history remains unchanged
16042
+ // for tool settlement and canonical compaction authority comparisons.
16043
+ if (typeof options.projectProviderMessages === "function") {
16044
+ callMessages = options.projectProviderMessages(callMessages);
16045
+ }
16005
16046
  const contextMemoryTrustedSystemIndexes = [];
16006
16047
  if (Number.isFinite(budget.limit)) {
16007
16048
  // consume() already charged this dispatch. The current request may still
@@ -16197,10 +16238,15 @@ export async function* agentLoop(messages, options) {
16197
16238
  const requestOptions = providerRequestId
16198
16239
  ? { ...plannedProviderOptions, providerRequestId }
16199
16240
  : plannedProviderOptions;
16200
- const providerCallOptions =
16201
- evolutionIngress !== null
16241
+ // Capture the custom adapter's input or the built-in adapter's final
16242
+ // projection. Commit visibility only after a successful model response.
16243
+ const readFileRequestContext = { messages: callMessages };
16244
+ const providerCallOptions = {
16245
+ [READ_FILE_REQUEST_CONTEXT]: readFileRequestContext,
16246
+ ...(evolutionIngress !== null
16202
16247
  ? { ...requestOptions, evolutionIngress }
16203
- : requestOptions;
16248
+ : requestOptions),
16249
+ };
16204
16250
  const modelUsageCall = {
16205
16251
  type: "model-usage-started",
16206
16252
  callId: _newModelUsageCallId("model"),
@@ -16302,6 +16348,7 @@ export async function* agentLoop(messages, options) {
16302
16348
  },
16303
16349
  "model_error",
16304
16350
  );
16351
+ readFileLoopGuard.observeVisibleMessages(readFileRequestContext.messages);
16305
16352
  providerReceipt = providerRequestId
16306
16353
  ? _snapshotProviderRequestReceipt(
16307
16354
  result?.providerReceipt,
@@ -16463,17 +16510,24 @@ export async function* agentLoop(messages, options) {
16463
16510
  );
16464
16511
  _throwBackgroundUsageFailureState(backgroundUsageFailureState);
16465
16512
  throwIfAborted(signal);
16466
- messages.push({ role: "assistant", content: msg.content || "" });
16513
+ messages.push(
16514
+ markRuntimeOnlySessionMessage({
16515
+ role: "assistant",
16516
+ content: msg.content || "",
16517
+ }),
16518
+ );
16467
16519
  for (const entry of _takeSettledBackgroundSubAgents(
16468
16520
  backgroundSubAgents,
16469
16521
  )) {
16470
16522
  _throwBackgroundSubAgentUsageFailure(entry);
16471
- messages.push({
16472
- role: "user",
16473
- content:
16474
- _backgroundSubAgentResultText(entry) +
16475
- "\n\nAll background sub-agents have finished. Incorporate their results and give your final answer.",
16476
- });
16523
+ messages.push(
16524
+ markRuntimeOnlySessionMessage({
16525
+ role: "user",
16526
+ content:
16527
+ _backgroundSubAgentResultText(entry) +
16528
+ "\n\nAll background sub-agents have finished. Incorporate their results and give your final answer.",
16529
+ }),
16530
+ );
16477
16531
  taskProgressTracker.retainChildResult(
16478
16532
  entry.id,
16479
16533
  {
@@ -16498,17 +16552,24 @@ export async function* agentLoop(messages, options) {
16498
16552
  const pending = [...pendingSubtreeMutations.values()];
16499
16553
  if (!subtreeMutationReprompted && budget.remaining() > 0) {
16500
16554
  subtreeMutationReprompted = true;
16501
- messages.push({ role: "assistant", content: msg.content || "" });
16502
- messages.push({
16503
- role: "user",
16504
- content:
16505
- "Runtime status: these mutations were deferred before any effect: " +
16506
- JSON.stringify(pending) +
16507
- ". Review the authoritative subtree instructions already returned. " +
16508
- "If still authorized and appropriate, explicitly issue the required tool call. " +
16509
- "Do not bypass rules or perform a prohibited write. Otherwise report the unfinished work and its blocker. " +
16510
- "A promise to retry does not execute the mutation.",
16511
- });
16555
+ messages.push(
16556
+ markRuntimeOnlySessionMessage({
16557
+ role: "assistant",
16558
+ content: msg.content || "",
16559
+ }),
16560
+ );
16561
+ messages.push(
16562
+ markRuntimeOnlySessionMessage({
16563
+ role: "user",
16564
+ content:
16565
+ "Runtime status: these mutations were deferred before any effect: " +
16566
+ JSON.stringify(pending) +
16567
+ ". Review the authoritative subtree instructions already returned. " +
16568
+ "If still authorized and appropriate, explicitly issue the required tool call. " +
16569
+ "Do not bypass rules or perform a prohibited write. Otherwise report the unfinished work and its blocker. " +
16570
+ "A promise to retry does not execute the mutation.",
16571
+ }),
16572
+ );
16512
16573
  yield {
16513
16574
  type: "iteration-warning",
16514
16575
  message:
@@ -16551,13 +16612,17 @@ export async function* agentLoop(messages, options) {
16551
16612
  const _contentEmpty = !String(msg.content || "").trim();
16552
16613
  if (_contentEmpty && _thinking && !emptyThinkingReprompted) {
16553
16614
  emptyThinkingReprompted = true;
16554
- messages.push({ role: "assistant", content: "" });
16555
- messages.push({
16556
- role: "user",
16557
- content:
16558
- "You ended your turn with only internal reasoning and no visible " +
16559
- "response. Please provide your actual answer now.",
16560
- });
16615
+ messages.push(
16616
+ markRuntimeOnlySessionMessage({ role: "assistant", content: "" }),
16617
+ );
16618
+ messages.push(
16619
+ markRuntimeOnlySessionMessage({
16620
+ role: "user",
16621
+ content:
16622
+ "You ended your turn with only internal reasoning and no visible " +
16623
+ "response. Please provide your actual answer now.",
16624
+ }),
16625
+ );
16561
16626
  yield { type: "empty-thinking-reprompt", runId };
16562
16627
  continue;
16563
16628
  }
@@ -16614,13 +16679,20 @@ export async function* agentLoop(messages, options) {
16614
16679
  }
16615
16680
  if (stopOutcome && stopOutcome.decision === "block") {
16616
16681
  stopHookActive = true;
16617
- messages.push({ role: "assistant", content: msg.content || "" });
16618
- messages.push({
16619
- role: "user",
16620
- content:
16621
- stopOutcome.reason ||
16622
- "A Stop hook requested that you keep working.",
16623
- });
16682
+ messages.push(
16683
+ markRuntimeOnlySessionMessage({
16684
+ role: "assistant",
16685
+ content: msg.content || "",
16686
+ }),
16687
+ );
16688
+ messages.push(
16689
+ markRuntimeOnlySessionMessage({
16690
+ role: "user",
16691
+ content:
16692
+ stopOutcome.reason ||
16693
+ "A Stop hook requested that you keep working.",
16694
+ }),
16695
+ );
16624
16696
  yield {
16625
16697
  type: "stop-hook-continue",
16626
16698
  runId,
@@ -23,6 +23,7 @@
23
23
  */
24
24
 
25
25
  import { randomUUID } from "node:crypto";
26
+ import { isProxy } from "node:util/types";
26
27
  import { transportErrorDetails } from "../lib/transport-error-details.js";
27
28
  import { captureAgentExecutionPolicy } from "../lib/agent-execution-policy.js";
28
29
  import { HERMETIC_CODING_SYSTEM_PROMPT } from "../lib/coding-task-guidance.js";
@@ -55,6 +56,8 @@ import {
55
56
  appendToolCallCompact as jsonlAppendToolCallCompact,
56
57
  appendLlmRetryCompact as jsonlAppendLlmRetryCompact,
57
58
  appendCompactEvent as jsonlAppendCompactEvent,
59
+ appendCompactEventIfMessagesMatch as jsonlAppendCompactEventIfMessagesMatch,
60
+ appendToolRoundsIfMessagesMatch as jsonlAppendToolRoundsIfMessagesMatch,
58
61
  appendEvent as jsonlAppendEvent,
59
62
  appendAuthorityEvent as jsonlAppendAuthorityEvent,
60
63
  readEvents as jsonlReadEvents,
@@ -68,6 +71,10 @@ import {
68
71
  getLastSessionId as jsonlGetLastSessionId,
69
72
  verifySession as jsonlVerifySession,
70
73
  } from "../harness/jsonl-session-store.js";
74
+ import {
75
+ markRuntimeOnlySessionMessage,
76
+ projectCanonicalResumeMessages,
77
+ } from "../lib/session-message-provenance.js";
71
78
  import { classifyStreamRetryReason } from "../lib/stream-retry.js";
72
79
  import {
73
80
  SideEffectLedger,
@@ -106,7 +113,7 @@ import { CLISkillLoader } from "../lib/skill-loader.js";
106
113
  import { expandFileRefsAsync } from "./file-ref-expander.js";
107
114
  import { composeSystemPrompt } from "./system-prompt.js";
108
115
  import { buildUserContent } from "../lib/image-input.js";
109
- import { mergeConsecutiveMessages } from "./message-roles.js";
116
+ import { createResumeRoleProjection } from "./message-roles.js";
110
117
  import { isHeadlessConfigCommand } from "../lib/headless-config-command.js";
111
118
  import {
112
119
  STREAM_PROTOCOL_VERSION,
@@ -1356,6 +1363,21 @@ async function runAgentHeadlessInWorkspace(
1356
1363
  ? () => null
1357
1364
  : jsonlFindLatestEvent);
1358
1365
  const store = {
1366
+ appendToolRoundsIfMessagesMatch:
1367
+ deps.appendToolRoundsIfMessagesMatch ||
1368
+ (hasInjectedSessionStore
1369
+ ? unavailableAuthorityCapability("appendToolRoundsIfMessagesMatch")
1370
+ : jsonlAppendToolRoundsIfMessagesMatch),
1371
+ appendCompactEventIfMessagesMatch:
1372
+ deps.appendCompactEventIfMessagesMatch ||
1373
+ (hasInjectedSessionStore
1374
+ ? unavailableAuthorityCapability("appendCompactEventIfMessagesMatch")
1375
+ : jsonlAppendCompactEventIfMessagesMatch),
1376
+ readVerifiedProjection:
1377
+ deps.readVerifiedProjection ||
1378
+ (hasInjectedSessionStore
1379
+ ? unavailableAuthorityCapability("readVerifiedProjection")
1380
+ : jsonlReadVerifiedProjection),
1359
1381
  sessionExists: deps.sessionExists || jsonlSessionExists,
1360
1382
  rebuildMessages: deps.rebuildMessages || jsonlRebuildMessages,
1361
1383
  startSession: deps.startSession || jsonlStartSession,
@@ -2150,12 +2172,14 @@ async function runAgentHeadlessInWorkspace(
2150
2172
  // content array (agent-core converts it per-provider for ollama/anthropic).
2151
2173
  const userMessageContent = buildUserContent(userContent, options.images);
2152
2174
 
2153
- // Merge consecutive same-role turns so a resumed session whose previous run
2175
+ // Project consecutive same-role turns only at the provider boundary. A
2176
+ // resumed session whose previous run
2154
2177
  // produced NO assistant response (history ends with a bare `user` turn) does
2155
2178
  // not yield two adjacent `user` messages — which Anthropic/Bedrock reject as
2156
2179
  // "roles must alternate", failing the resume (Claude Code 2.1.187 parity).
2157
2180
  // No-op on a healthy alternating transcript.
2158
- const messages = mergeConsecutiveMessages([
2181
+ // Keep the authority array identical to the durable transcript for CAS.
2182
+ const messages = [
2159
2183
  { role: "system", content: systemContent },
2160
2184
  ...(instructionsLoadedContext
2161
2185
  ? [{ role: "system", content: instructionsLoadedContext }]
@@ -2174,7 +2198,7 @@ async function runAgentHeadlessInWorkspace(
2174
2198
  : []),
2175
2199
  ...history,
2176
2200
  { role: "user", content: userMessageContent },
2177
- ]);
2201
+ ];
2178
2202
 
2179
2203
  // Persist the user turn before model/tool side effects. A full or read-only
2180
2204
  // session disk is a recoverability contract failure, so it fails closed
@@ -2199,7 +2223,7 @@ async function runAgentHeadlessInWorkspace(
2199
2223
  }
2200
2224
  // Persist the expanded content so a resumed session faithfully replays
2201
2225
  // what the model actually saw (the file snapshot, not just the @token).
2202
- store.appendUserMessage(sessionId, userContent);
2226
+ store.appendUserMessage(sessionId, userMessageContent);
2203
2227
  } catch (error) {
2204
2228
  const persistenceError = createSessionPersistenceFailure(error, {
2205
2229
  sessionId,
@@ -2393,19 +2417,20 @@ async function runAgentHeadlessInWorkspace(
2393
2417
 
2394
2418
  // IDE live context (Claude-Code parity): when an IDE bridge is connected,
2395
2419
  // share the editor's selection/active file/open tabs with this turn. Appended
2396
- // to the in-flight user message only — AFTER persistence — so a resumed
2420
+ // as private live context only — AFTER persistence — so a resumed
2397
2421
  // session replays the prompt, not a stale editor snapshot. Best-effort with
2398
2422
  // a short timeout; CC_IDE_CONTEXT=0 disables.
2399
2423
  if (!hermeticExecution) {
2400
2424
  try {
2401
- const { buildIdePromptContext, appendTextToContent, expandIdeMentions } =
2402
- await import("../lib/ide-context.js");
2403
- const last = messages[messages.length - 1];
2425
+ const { buildIdePromptContext, expandIdeMentions } = await import(
2426
+ "../lib/ide-context.js"
2427
+ );
2428
+ const ideBlocks = [];
2404
2429
  const ideCtx = await (
2405
2430
  deps.buildIdePromptContext || buildIdePromptContext
2406
2431
  )(hostMcp);
2407
2432
  if (ideCtx) {
2408
- last.content = appendTextToContent(last.content, ideCtx);
2433
+ ideBlocks.push(ideCtx);
2409
2434
  }
2410
2435
  // Explicit @selection / @diagnostics mentions in the user's prompt
2411
2436
  // (Claude-Code parity). Scan the ORIGINAL prompt so injected file-ref
@@ -2414,7 +2439,15 @@ async function runAgentHeadlessInWorkspace(
2414
2439
  const mentioned = await expandIdeMentions(prompt, hostMcp);
2415
2440
  for (const w of mentioned.warnings) writeErr(` @ide: ${w}\n`);
2416
2441
  if (mentioned.block) {
2417
- last.content = appendTextToContent(last.content, mentioned.block);
2442
+ ideBlocks.push(mentioned.block);
2443
+ }
2444
+ if (ideBlocks.length) {
2445
+ messages.push(
2446
+ markRuntimeOnlySessionMessage({
2447
+ role: "user",
2448
+ content: ideBlocks.join("\n\n"),
2449
+ }),
2450
+ );
2418
2451
  }
2419
2452
  } catch {
2420
2453
  // IDE context is optional polish — never fail the run over it.
@@ -2811,10 +2844,65 @@ async function runAgentHeadlessInWorkspace(
2811
2844
  }
2812
2845
 
2813
2846
  const childToolExecs = new Map();
2847
+ const synchronousAuthorityResult = (value, capability) => {
2848
+ if (isProxy(value)) {
2849
+ throw Object.assign(
2850
+ new Error(`${capability} must return non-Proxy authority`),
2851
+ {
2852
+ code: "SESSION_AUTHORITY_CAPABILITY_INVALID",
2853
+ },
2854
+ );
2855
+ }
2856
+ if (value && typeof value.then === "function") {
2857
+ void Promise.resolve(value).catch(() => {});
2858
+ throw Object.assign(
2859
+ new Error(`${capability} must settle synchronously`),
2860
+ {
2861
+ code: "SESSION_AUTHORITY_CAPABILITY_INVALID",
2862
+ },
2863
+ );
2864
+ }
2865
+ return value;
2866
+ };
2867
+ const settleToolRounds = () => {
2868
+ if (
2869
+ !persist ||
2870
+ !messages.some(
2871
+ (message) => message.role === "assistant" && message.tool_calls?.length,
2872
+ )
2873
+ )
2874
+ return;
2875
+ return persistRuntimeLedgerWrite(() => {
2876
+ sessionHostLease?.assert?.();
2877
+ const expected = synchronousAuthorityResult(
2878
+ store.readVerifiedProjection(sessionId, () => ({
2879
+ accept() {},
2880
+ finish(authority) {
2881
+ return authority.readMessages();
2882
+ },
2883
+ })),
2884
+ "readVerifiedProjection",
2885
+ );
2886
+ const appended = synchronousAuthorityResult(
2887
+ store.appendToolRoundsIfMessagesMatch(sessionId, messages, expected),
2888
+ "appendToolRoundsIfMessagesMatch",
2889
+ );
2890
+ if (typeof appended?.hash !== "string" || !appended.hash) {
2891
+ throw Object.assign(
2892
+ new Error("Tool round settlement did not return its durable head"),
2893
+ {
2894
+ code: "CC_SESSION_TOOL_ROUND_SETTLEMENT_HEAD_UNKNOWN",
2895
+ },
2896
+ );
2897
+ }
2898
+ return appended;
2899
+ });
2900
+ };
2814
2901
  const loopOptions = {
2815
2902
  ...(evolutionIngress === null ? {} : { evolutionIngress }),
2816
2903
  model,
2817
2904
  provider,
2905
+ projectProviderMessages: createResumeRoleProjection(messages),
2818
2906
  recorder: _otlpRecorder,
2819
2907
  // --otlp-content: opt in to stamping prompt CONTENT onto --otlp spans. Off by
2820
2908
  // default → content stays redacted (agent-core omits it entirely, so default
@@ -2916,6 +3004,28 @@ async function runAgentHeadlessInWorkspace(
2916
3004
  mcpDispatchAdmission: hermeticExecution
2917
3005
  ? null
2918
3006
  : sessionHostLease?.admitMcpDispatch || null,
3007
+ persistCompaction: persist,
3008
+ onBeforeCompaction: settleToolRounds,
3009
+ onCompaction: persist
3010
+ ? (stats, compacted, settlement = {}) =>
3011
+ persistRuntimeLedgerWrite(() => {
3012
+ sessionHostLease?.assert?.();
3013
+ return synchronousAuthorityResult(
3014
+ store.appendCompactEventIfMessagesMatch(
3015
+ sessionId,
3016
+ {
3017
+ ...stats,
3018
+ trigger: settlement.trigger || "auto",
3019
+ messages: projectCanonicalResumeMessages(compacted, {
3020
+ strict: true,
3021
+ }),
3022
+ },
3023
+ settlement.expectedMessages,
3024
+ ),
3025
+ "appendCompactEventIfMessagesMatch",
3026
+ );
3027
+ })
3028
+ : undefined,
2919
3029
  // chatFn passthrough lets tests drive the loop deterministically.
2920
3030
  chatFn: deps.chatFn || options.chatFn || undefined,
2921
3031
  signal: options.signal || undefined,
@@ -3870,12 +3980,14 @@ async function runAgentHeadlessInWorkspace(
3870
3980
  `${rw.error ? `: ${rw.error}` : ""}`,
3871
3981
  )
3872
3982
  .join("\n");
3873
- messages.push({
3874
- role: "user",
3875
- content:
3876
- `A background check flagged a problem after your last turn:\n${detail}\n` +
3877
- `Investigate and fix it, then confirm it passes.`,
3878
- });
3983
+ messages.push(
3984
+ markRuntimeOnlySessionMessage({
3985
+ role: "user",
3986
+ content:
3987
+ `A background check flagged a problem after your last turn:\n${detail}\n` +
3988
+ `Investigate and fix it, then confirm it passes.`,
3989
+ }),
3990
+ );
3879
3991
  if (isText)
3880
3992
  writeErr(
3881
3993
  ` ↻ async-hook rewake — re-engaging agent (${rewakeBudget} re-drive(s) left)\n`,
@@ -3983,13 +4095,15 @@ async function runAgentHeadlessInWorkspace(
3983
4095
  // and the next turn still resumes at the right point.
3984
4096
  persistGoalSnapshot();
3985
4097
  if (decision === _goalHelpers.GOAL_DECISION.CONTINUE) {
3986
- messages.push({
3987
- role: "user",
3988
- content:
3989
- `The completion condition is not yet met: ${evaluation.reason}.\n` +
3990
- `Keep working toward: "${cond.source}". When you believe it is ` +
3991
- `satisfied, make sure it actually passes.`,
3992
- });
4098
+ messages.push(
4099
+ markRuntimeOnlySessionMessage({
4100
+ role: "user",
4101
+ content:
4102
+ `The completion condition is not yet met: ${evaluation.reason}.\n` +
4103
+ `Keep working toward: "${cond.source}". When you believe it is ` +
4104
+ `satisfied, make sure it actually passes.`,
4105
+ }),
4106
+ );
3993
4107
  finalText = "";
3994
4108
  endReason = "complete";
3995
4109
  if (isText)
@@ -4005,6 +4119,22 @@ async function runAgentHeadlessInWorkspace(
4005
4119
  } catch (err) {
4006
4120
  const runtimeLedgerPersistenceFailed =
4007
4121
  err?.runtimeLedgerPersistence === true;
4122
+ const persistence = runtimeLedgerPersistenceFailed
4123
+ ? projectSessionPersistenceFailure(
4124
+ createSessionPersistenceFailure(err, {
4125
+ sessionId,
4126
+ operation: "append-event",
4127
+ }),
4128
+ { phase: "after-model" },
4129
+ )
4130
+ : null;
4131
+ if (persistence) {
4132
+ try {
4133
+ deps.onPersistenceFailure?.(persistence);
4134
+ } catch {
4135
+ // Observability cannot change persistence semantics.
4136
+ }
4137
+ }
4008
4138
  const sessionBudgetFailed =
4009
4139
  err?.code === "CC_SESSION_BUDGET_EXHAUSTED" ||
4010
4140
  err?.code === "CC_SESSION_BUDGET_USAGE_UNKNOWN" ||
@@ -4028,7 +4158,12 @@ async function runAgentHeadlessInWorkspace(
4028
4158
  errorSubtype === "error" ? transportErrorDetails(err) : undefined;
4029
4159
  const diagnosticFields = transportDetails
4030
4160
  ? { error_details: transportDetails }
4031
- : {};
4161
+ : {
4162
+ ...(runtimeLedgerPersistenceFailed && err.code
4163
+ ? { code: err.code }
4164
+ : {}),
4165
+ ...(persistence ? { persistence } : {}),
4166
+ };
4032
4167
  if (isStream) {
4033
4168
  emitStream({
4034
4169
  type: "result",
@@ -4067,6 +4202,8 @@ async function runAgentHeadlessInWorkspace(
4067
4202
  : classifyLoopError(err),
4068
4203
  result: message,
4069
4204
  isError: true,
4205
+ ...(persistence ? { persistence } : {}),
4206
+ ...(runtimeLedgerPersistenceFailed && err.code ? { code: err.code } : {}),
4070
4207
  };
4071
4208
  } finally {
4072
4209
  // Capture body-cache hits/misses caused by list_skills/run_skill during the
@@ -4250,16 +4387,21 @@ async function runAgentHeadlessInWorkspace(
4250
4387
  : "success";
4251
4388
  const durationMs = (deps.now ? deps.now() : Date.now()) - startedAt;
4252
4389
  let persistenceFailure = null;
4390
+ let sessionPersistenceCode = null;
4253
4391
 
4254
- // Persist the assistant turn so a later --resume / --continue replays it.
4255
- // The user turn was already recorded up front; only append on a clean run.
4256
- if (persist && !isError) {
4392
+ // Complete tool evidence survives a clean budget stop. Only a successful
4393
+ // run appends a final assistant answer; settlement failure is always terminal.
4394
+ if (persist) {
4395
+ let persistenceOperation = "append-event";
4257
4396
  try {
4258
- if (finalText) store.appendAssistantMessage(sessionId, finalText);
4397
+ settleToolRounds();
4398
+ persistenceOperation = "assistant-turn-append";
4399
+ if (!isError && finalText)
4400
+ store.appendAssistantMessage(sessionId, finalText);
4259
4401
  } catch (error) {
4260
4402
  const persistenceError = createSessionPersistenceFailure(error, {
4261
4403
  sessionId,
4262
- operation: "assistant-turn-append",
4404
+ operation: persistenceOperation,
4263
4405
  });
4264
4406
  persistenceFailure = projectSessionPersistenceFailure(persistenceError, {
4265
4407
  phase: "after-model",
@@ -4277,6 +4419,15 @@ async function runAgentHeadlessInWorkspace(
4277
4419
  `CC_SESSION_PERSISTENCE_FAILED: assistant turn durability is ${persistenceFailure.commit_state} (${persistenceFailure.fs_code})\n`,
4278
4420
  );
4279
4421
  }
4422
+ } else if (error?.runtimeLedgerPersistence === true) {
4423
+ isError = true;
4424
+ subtype = "error_session_persistence";
4425
+ sessionPersistenceCode =
4426
+ error.code || "CC_SESSION_TOOL_ROUND_PERSISTENCE_FAILED";
4427
+ if (isText)
4428
+ writeErr(
4429
+ `${sessionPersistenceCode}: completed tool rounds could not be settled; refresh the session before continuing\n`,
4430
+ );
4280
4431
  }
4281
4432
  // Preserve legacy best-effort handling for errors outside the explicit
4282
4433
  // ENOSPC/EROFS durability contract.
@@ -4416,11 +4567,12 @@ async function runAgentHeadlessInWorkspace(
4416
4567
  ? { compaction_degradations: compactionDegradations }
4417
4568
  : {}),
4418
4569
  ...(persistenceFailure ? { persistence: persistenceFailure } : {}),
4570
+ ...(sessionPersistenceCode ? { code: sessionPersistenceCode } : {}),
4419
4571
  });
4420
4572
  } else if (isJson) {
4421
4573
  writeOut(
4422
- JSON.stringify(
4423
- buildResultEnvelope({
4574
+ JSON.stringify({
4575
+ ...buildResultEnvelope({
4424
4576
  subtype,
4425
4577
  isError,
4426
4578
  result: finalText,
@@ -4434,7 +4586,8 @@ async function runAgentHeadlessInWorkspace(
4434
4586
  compactionDegradations,
4435
4587
  persistence: persistenceFailure,
4436
4588
  }),
4437
- ) + "\n",
4589
+ ...(sessionPersistenceCode ? { code: sessionPersistenceCode } : {}),
4590
+ }) + "\n",
4438
4591
  );
4439
4592
  } else {
4440
4593
  // text: just the final answer on stdout.
@@ -4475,10 +4628,14 @@ async function runAgentHeadlessInWorkspace(
4475
4628
  // Exit-code taxonomy: max-turns → 3, cost cap → 4 (both still non-zero, so
4476
4629
  // "any failure" checks keep working; scripts can now tell exhaustion apart).
4477
4630
  return {
4478
- exitCode: exitCodeForEndReason(endReason, isError),
4631
+ exitCode:
4632
+ persistenceFailure || sessionPersistenceCode
4633
+ ? 1
4634
+ : exitCodeForEndReason(endReason, isError),
4479
4635
  result: finalText,
4480
4636
  isError,
4481
4637
  ...(persistenceFailure ? { persistence: persistenceFailure } : {}),
4638
+ ...(sessionPersistenceCode ? { code: sessionPersistenceCode } : {}),
4482
4639
  };
4483
4640
  }
4484
4641