pi-agent-flow 1.8.40 → 2.0.1

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 (331) hide show
  1. package/README.md +33 -37
  2. package/agents/audit.md +21 -22
  3. package/agents/build.md +23 -22
  4. package/agents/craft.md +23 -27
  5. package/agents/debug.md +24 -28
  6. package/agents/ideas.md +21 -101
  7. package/agents/scout.md +20 -19
  8. package/dist/batch/batch-bash.d.ts +2 -2
  9. package/dist/batch/batch-bash.d.ts.map +1 -1
  10. package/dist/batch/batch-bash.js +30 -7
  11. package/dist/batch/batch-bash.js.map +1 -1
  12. package/dist/batch/constants.d.ts +31 -5
  13. package/dist/batch/constants.d.ts.map +1 -1
  14. package/dist/batch/constants.js +50 -3
  15. package/dist/batch/constants.js.map +1 -1
  16. package/dist/batch/execute.d.ts +0 -1
  17. package/dist/batch/execute.d.ts.map +1 -1
  18. package/dist/batch/execute.js +210 -6
  19. package/dist/batch/execute.js.map +1 -1
  20. package/dist/batch/fuzzy-edit.d.ts +0 -6
  21. package/dist/batch/fuzzy-edit.d.ts.map +1 -1
  22. package/dist/batch/fuzzy-edit.js +1 -1
  23. package/dist/batch/fuzzy-edit.js.map +1 -1
  24. package/dist/batch/index.d.ts.map +1 -1
  25. package/dist/batch/index.js +87 -16
  26. package/dist/batch/index.js.map +1 -1
  27. package/dist/batch/render.d.ts +0 -1
  28. package/dist/batch/render.d.ts.map +1 -1
  29. package/dist/batch/render.js +7 -101
  30. package/dist/batch/render.js.map +1 -1
  31. package/dist/batch/shell-compress.d.ts +25 -0
  32. package/dist/batch/shell-compress.d.ts.map +1 -0
  33. package/dist/batch/shell-compress.js +602 -0
  34. package/dist/batch/shell-compress.js.map +1 -0
  35. package/dist/batch/summary.d.ts +5 -0
  36. package/dist/batch/summary.d.ts.map +1 -0
  37. package/dist/batch/summary.js +101 -0
  38. package/dist/batch/summary.js.map +1 -0
  39. package/dist/batch/symbols.d.ts.map +1 -1
  40. package/dist/batch/symbols.js +12 -7
  41. package/dist/batch/symbols.js.map +1 -1
  42. package/dist/{config.d.ts → config/config.d.ts} +39 -2
  43. package/dist/config/config.d.ts.map +1 -0
  44. package/dist/{config.js → config/config.js} +220 -9
  45. package/dist/config/config.js.map +1 -0
  46. package/dist/config/log.d.ts +27 -0
  47. package/dist/config/log.d.ts.map +1 -0
  48. package/dist/config/log.js +104 -0
  49. package/dist/config/log.js.map +1 -0
  50. package/dist/config/models.d.ts +2 -0
  51. package/dist/config/models.d.ts.map +1 -0
  52. package/dist/config/models.js +49 -0
  53. package/dist/config/models.js.map +1 -0
  54. package/dist/{settings-resolver.d.ts → config/settings-resolver.d.ts} +9 -2
  55. package/dist/config/settings-resolver.d.ts.map +1 -0
  56. package/dist/config/settings-resolver.js +275 -0
  57. package/dist/config/settings-resolver.js.map +1 -0
  58. package/dist/core/agents.d.ts.map +1 -0
  59. package/dist/{agents.js → core/agents.js} +13 -12
  60. package/dist/core/agents.js.map +1 -0
  61. package/dist/core/delegation.d.ts +24 -0
  62. package/dist/core/delegation.d.ts.map +1 -0
  63. package/dist/core/delegation.js +48 -0
  64. package/dist/core/delegation.js.map +1 -0
  65. package/dist/core/depth.d.ts.map +1 -0
  66. package/dist/{depth.js → core/depth.js} +9 -8
  67. package/dist/core/depth.js.map +1 -0
  68. package/dist/{executor.d.ts → core/executor.d.ts} +18 -3
  69. package/dist/core/executor.d.ts.map +1 -0
  70. package/dist/{executor.js → core/executor.js} +53 -14
  71. package/dist/core/executor.js.map +1 -0
  72. package/dist/{flow.d.ts → core/flow.d.ts} +13 -1
  73. package/dist/core/flow.d.ts.map +1 -0
  74. package/dist/{flow.js → core/flow.js} +125 -64
  75. package/dist/core/flow.js.map +1 -0
  76. package/dist/{session-mode.d.ts → core/session-mode.d.ts} +2 -1
  77. package/dist/core/session-mode.d.ts.map +1 -0
  78. package/dist/{session-mode.js → core/session-mode.js} +2 -1
  79. package/dist/core/session-mode.js.map +1 -0
  80. package/dist/core/session-registry.d.ts +16 -0
  81. package/dist/core/session-registry.d.ts.map +1 -0
  82. package/dist/core/session-registry.js +30 -0
  83. package/dist/core/session-registry.js.map +1 -0
  84. package/dist/core/transitions.d.ts.map +1 -0
  85. package/dist/{transitions.js → core/transitions.js} +1 -1
  86. package/dist/core/transitions.js.map +1 -0
  87. package/dist/flow/auto-warp.d.ts +12 -0
  88. package/dist/flow/auto-warp.d.ts.map +1 -0
  89. package/dist/flow/auto-warp.js +29 -0
  90. package/dist/flow/auto-warp.js.map +1 -0
  91. package/dist/flow/command.d.ts +8 -0
  92. package/dist/flow/command.d.ts.map +1 -0
  93. package/dist/flow/command.js +194 -0
  94. package/dist/flow/command.js.map +1 -0
  95. package/dist/flow/continuation.d.ts +16 -0
  96. package/dist/flow/continuation.d.ts.map +1 -0
  97. package/dist/flow/continuation.js +188 -0
  98. package/dist/flow/continuation.js.map +1 -0
  99. package/dist/flow/index.d.ts +18 -0
  100. package/dist/flow/index.d.ts.map +1 -0
  101. package/dist/flow/index.js +25 -0
  102. package/dist/flow/index.js.map +1 -0
  103. package/dist/flow/loop-command.d.ts +8 -0
  104. package/dist/flow/loop-command.d.ts.map +1 -0
  105. package/dist/flow/loop-command.js +99 -0
  106. package/dist/flow/loop-command.js.map +1 -0
  107. package/dist/flow/loop-templates.d.ts +7 -0
  108. package/dist/flow/loop-templates.d.ts.map +1 -0
  109. package/dist/flow/loop-templates.js +38 -0
  110. package/dist/flow/loop-templates.js.map +1 -0
  111. package/dist/flow/loop.d.ts +19 -0
  112. package/dist/flow/loop.d.ts.map +1 -0
  113. package/dist/flow/loop.js +95 -0
  114. package/dist/flow/loop.js.map +1 -0
  115. package/dist/flow/perform-warp.d.ts +28 -0
  116. package/dist/flow/perform-warp.d.ts.map +1 -0
  117. package/dist/flow/perform-warp.js +127 -0
  118. package/dist/flow/perform-warp.js.map +1 -0
  119. package/dist/flow/settings-command.d.ts +51 -0
  120. package/dist/flow/settings-command.d.ts.map +1 -0
  121. package/dist/flow/settings-command.js +937 -0
  122. package/dist/flow/settings-command.js.map +1 -0
  123. package/dist/flow/store.d.ts +26 -0
  124. package/dist/flow/store.d.ts.map +1 -0
  125. package/dist/flow/store.js +166 -0
  126. package/dist/flow/store.js.map +1 -0
  127. package/dist/flow/template-shared.d.ts +9 -0
  128. package/dist/flow/template-shared.d.ts.map +1 -0
  129. package/dist/flow/template-shared.js +13 -0
  130. package/dist/flow/template-shared.js.map +1 -0
  131. package/dist/flow/template-strings.d.ts +8 -0
  132. package/dist/flow/template-strings.d.ts.map +1 -0
  133. package/dist/flow/template-strings.js +36 -0
  134. package/dist/flow/template-strings.js.map +1 -0
  135. package/dist/flow/types.d.ts +61 -0
  136. package/dist/flow/types.d.ts.map +1 -0
  137. package/dist/flow/types.js +5 -0
  138. package/dist/flow/types.js.map +1 -0
  139. package/dist/flow/warp-command.d.ts +8 -0
  140. package/dist/flow/warp-command.d.ts.map +1 -0
  141. package/dist/flow/warp-command.js +144 -0
  142. package/dist/flow/warp-command.js.map +1 -0
  143. package/dist/flow/warp-utils.d.ts +11 -0
  144. package/dist/flow/warp-utils.d.ts.map +1 -0
  145. package/dist/flow/warp-utils.js +187 -0
  146. package/dist/flow/warp-utils.js.map +1 -0
  147. package/dist/index.d.ts +4 -1
  148. package/dist/index.d.ts.map +1 -1
  149. package/dist/index.js +115 -31
  150. package/dist/index.js.map +1 -1
  151. package/dist/{notify-state.d.ts → notify/notify-state.d.ts} +2 -1
  152. package/dist/notify/notify-state.d.ts.map +1 -0
  153. package/dist/notify/notify-state.js.map +1 -0
  154. package/dist/notify/notify.d.ts.map +1 -0
  155. package/dist/{notify.js → notify/notify.js} +3 -2
  156. package/dist/notify/notify.js.map +1 -0
  157. package/dist/{cli-args.d.ts → snapshot/cli-args.d.ts} +4 -2
  158. package/dist/snapshot/cli-args.d.ts.map +1 -0
  159. package/dist/{cli-args.js → snapshot/cli-args.js} +13 -1
  160. package/dist/snapshot/cli-args.js.map +1 -0
  161. package/dist/snapshot/index.d.ts +2 -0
  162. package/dist/snapshot/index.d.ts.map +1 -0
  163. package/dist/snapshot/index.js +2 -0
  164. package/dist/snapshot/index.js.map +1 -0
  165. package/dist/{reasoning-strip.d.ts → snapshot/reasoning-strip.d.ts} +0 -4
  166. package/dist/snapshot/reasoning-strip.d.ts.map +1 -0
  167. package/dist/{reasoning-strip.js → snapshot/reasoning-strip.js} +2 -2
  168. package/dist/snapshot/reasoning-strip.js.map +1 -0
  169. package/dist/snapshot/runner-events.d.ts.map +1 -0
  170. package/dist/{runner-events.js → snapshot/runner-events.js} +1 -1
  171. package/dist/snapshot/runner-events.js.map +1 -0
  172. package/dist/{snapshot.d.ts → snapshot/snapshot.d.ts} +24 -18
  173. package/dist/snapshot/snapshot.d.ts.map +1 -0
  174. package/dist/snapshot/snapshot.js +1791 -0
  175. package/dist/snapshot/snapshot.js.map +1 -0
  176. package/dist/{structured-output.d.ts → snapshot/structured-output.d.ts} +1 -1
  177. package/dist/snapshot/structured-output.d.ts.map +1 -0
  178. package/dist/snapshot/structured-output.js.map +1 -0
  179. package/dist/{flow-prompt.d.ts → steering/flow-prompt.d.ts} +2 -2
  180. package/dist/steering/flow-prompt.d.ts.map +1 -0
  181. package/dist/{flow-prompt.js → steering/flow-prompt.js} +1 -1
  182. package/dist/steering/flow-prompt.js.map +1 -0
  183. package/dist/{sliding-prompt.d.ts → steering/sliding-prompt.d.ts} +8 -7
  184. package/dist/steering/sliding-prompt.d.ts.map +1 -0
  185. package/dist/{sliding-prompt.js → steering/sliding-prompt.js} +18 -64
  186. package/dist/steering/sliding-prompt.js.map +1 -0
  187. package/dist/{tool-utils.d.ts → steering/tool-utils.d.ts} +1 -0
  188. package/dist/steering/tool-utils.d.ts.map +1 -0
  189. package/dist/{tool-utils.js → steering/tool-utils.js} +10 -3
  190. package/dist/steering/tool-utils.js.map +1 -0
  191. package/dist/{ask-user.d.ts → tools/ask-user.d.ts} +3 -15
  192. package/dist/tools/ask-user.d.ts.map +1 -0
  193. package/dist/tools/ask-user.js +778 -0
  194. package/dist/tools/ask-user.js.map +1 -0
  195. package/dist/{timed-bash.d.ts → tools/timed-bash.d.ts} +2 -7
  196. package/dist/tools/timed-bash.d.ts.map +1 -0
  197. package/dist/{timed-bash.js → tools/timed-bash.js} +11 -2
  198. package/dist/tools/timed-bash.js.map +1 -0
  199. package/dist/{web-tool.d.ts → tools/web-tool.d.ts} +1 -1
  200. package/dist/tools/web-tool.d.ts.map +1 -0
  201. package/dist/{web-tool.js → tools/web-tool.js} +8 -7
  202. package/dist/tools/web-tool.js.map +1 -0
  203. package/dist/tui/flow-colors.d.ts +55 -0
  204. package/dist/tui/flow-colors.d.ts.map +1 -0
  205. package/dist/tui/flow-colors.js +22 -0
  206. package/dist/tui/flow-colors.js.map +1 -0
  207. package/dist/{render-utils.d.ts → tui/render-utils.d.ts} +6 -2
  208. package/dist/tui/render-utils.d.ts.map +1 -0
  209. package/dist/{render-utils.js → tui/render-utils.js} +40 -12
  210. package/dist/tui/render-utils.js.map +1 -0
  211. package/dist/tui/render.d.ts +21 -0
  212. package/dist/tui/render.d.ts.map +1 -0
  213. package/dist/tui/render.js +786 -0
  214. package/dist/tui/render.js.map +1 -0
  215. package/dist/tui/scramble/algorithm.d.ts +7 -0
  216. package/dist/tui/scramble/algorithm.d.ts.map +1 -0
  217. package/dist/tui/scramble/algorithm.js +227 -0
  218. package/dist/tui/scramble/algorithm.js.map +1 -0
  219. package/dist/tui/scramble/constants.d.ts +99 -0
  220. package/dist/tui/scramble/constants.d.ts.map +1 -0
  221. package/dist/tui/scramble/constants.js +101 -0
  222. package/dist/tui/scramble/constants.js.map +1 -0
  223. package/dist/tui/scramble/index.d.ts +6 -0
  224. package/dist/tui/scramble/index.d.ts.map +1 -0
  225. package/dist/tui/scramble/index.js +6 -0
  226. package/dist/tui/scramble/index.js.map +1 -0
  227. package/dist/tui/scramble/manager.d.ts +44 -0
  228. package/dist/tui/scramble/manager.d.ts.map +1 -0
  229. package/dist/tui/scramble/manager.js +899 -0
  230. package/dist/tui/scramble/manager.js.map +1 -0
  231. package/dist/tui/scramble/utils.d.ts +18 -0
  232. package/dist/tui/scramble/utils.d.ts.map +1 -0
  233. package/dist/tui/scramble/utils.js +145 -0
  234. package/dist/tui/scramble/utils.js.map +1 -0
  235. package/dist/tui/single-select-layout.d.ts +17 -0
  236. package/dist/tui/single-select-layout.d.ts.map +1 -0
  237. package/dist/{single-select-layout.js → tui/single-select-layout.js} +8 -25
  238. package/dist/tui/single-select-layout.js.map +1 -0
  239. package/dist/types/flow.d.ts +112 -0
  240. package/dist/types/flow.d.ts.map +1 -0
  241. package/dist/{types.js → types/flow.js} +3 -54
  242. package/dist/types/flow.js.map +1 -0
  243. package/dist/types/index.d.ts +8 -0
  244. package/dist/types/index.d.ts.map +1 -0
  245. package/dist/types/index.js +7 -0
  246. package/dist/types/index.js.map +1 -0
  247. package/dist/types/output.d.ts +110 -0
  248. package/dist/types/output.d.ts.map +1 -0
  249. package/dist/types/output.js +5 -0
  250. package/dist/types/output.js.map +1 -0
  251. package/dist/types/ui.d.ts +24 -0
  252. package/dist/types/ui.d.ts.map +1 -0
  253. package/dist/types/ui.js +55 -0
  254. package/dist/types/ui.js.map +1 -0
  255. package/package.json +1 -1
  256. package/dist/agents.d.ts.map +0 -1
  257. package/dist/agents.js.map +0 -1
  258. package/dist/ask-user.d.ts.map +0 -1
  259. package/dist/ask-user.js +0 -1405
  260. package/dist/ask-user.js.map +0 -1
  261. package/dist/batch.d.ts +0 -12
  262. package/dist/batch.d.ts.map +0 -1
  263. package/dist/batch.js +0 -11
  264. package/dist/batch.js.map +0 -1
  265. package/dist/cli-args.d.ts.map +0 -1
  266. package/dist/cli-args.js.map +0 -1
  267. package/dist/config.d.ts.map +0 -1
  268. package/dist/config.js.map +0 -1
  269. package/dist/depth.d.ts.map +0 -1
  270. package/dist/depth.js.map +0 -1
  271. package/dist/executor.d.ts.map +0 -1
  272. package/dist/executor.js.map +0 -1
  273. package/dist/flow-prompt.d.ts.map +0 -1
  274. package/dist/flow-prompt.js.map +0 -1
  275. package/dist/flow.d.ts.map +0 -1
  276. package/dist/flow.js.map +0 -1
  277. package/dist/notify-state.d.ts.map +0 -1
  278. package/dist/notify-state.js.map +0 -1
  279. package/dist/notify.d.ts.map +0 -1
  280. package/dist/notify.js.map +0 -1
  281. package/dist/reasoning-strip.d.ts.map +0 -1
  282. package/dist/reasoning-strip.js.map +0 -1
  283. package/dist/render-utils.d.ts.map +0 -1
  284. package/dist/render-utils.js.map +0 -1
  285. package/dist/render.d.ts +0 -24
  286. package/dist/render.d.ts.map +0 -1
  287. package/dist/render.js +0 -592
  288. package/dist/render.js.map +0 -1
  289. package/dist/runner-events.d.ts.map +0 -1
  290. package/dist/runner-events.js.map +0 -1
  291. package/dist/scramble.d.ts +0 -183
  292. package/dist/scramble.d.ts.map +0 -1
  293. package/dist/scramble.js +0 -2478
  294. package/dist/scramble.js.map +0 -1
  295. package/dist/session-mode.d.ts.map +0 -1
  296. package/dist/session-mode.js.map +0 -1
  297. package/dist/settings-resolver.d.ts.map +0 -1
  298. package/dist/settings-resolver.js +0 -148
  299. package/dist/settings-resolver.js.map +0 -1
  300. package/dist/single-select-layout.d.ts +0 -20
  301. package/dist/single-select-layout.d.ts.map +0 -1
  302. package/dist/single-select-layout.js.map +0 -1
  303. package/dist/sliding-prompt.d.ts.map +0 -1
  304. package/dist/sliding-prompt.js.map +0 -1
  305. package/dist/snapshot.d.ts.map +0 -1
  306. package/dist/snapshot.js +0 -797
  307. package/dist/snapshot.js.map +0 -1
  308. package/dist/spec-mode.d.ts +0 -13
  309. package/dist/spec-mode.d.ts.map +0 -1
  310. package/dist/spec-mode.js +0 -90
  311. package/dist/spec-mode.js.map +0 -1
  312. package/dist/structured-output.d.ts.map +0 -1
  313. package/dist/structured-output.js.map +0 -1
  314. package/dist/timed-bash.d.ts.map +0 -1
  315. package/dist/timed-bash.js.map +0 -1
  316. package/dist/tool-utils.d.ts.map +0 -1
  317. package/dist/tool-utils.js.map +0 -1
  318. package/dist/transitions.d.ts.map +0 -1
  319. package/dist/transitions.js.map +0 -1
  320. package/dist/types.d.ts +0 -224
  321. package/dist/types.d.ts.map +0 -1
  322. package/dist/types.js.map +0 -1
  323. package/dist/web-tool.d.ts.map +0 -1
  324. package/dist/web-tool.js.map +0 -1
  325. /package/dist/{agents.d.ts → core/agents.d.ts} +0 -0
  326. /package/dist/{depth.d.ts → core/depth.d.ts} +0 -0
  327. /package/dist/{transitions.d.ts → core/transitions.d.ts} +0 -0
  328. /package/dist/{notify-state.js → notify/notify-state.js} +0 -0
  329. /package/dist/{notify.d.ts → notify/notify.d.ts} +0 -0
  330. /package/dist/{runner-events.d.ts → snapshot/runner-events.d.ts} +0 -0
  331. /package/dist/{structured-output.js → snapshot/structured-output.js} +0 -0
package/dist/snapshot.js DELETED
@@ -1,797 +0,0 @@
1
- /**
2
- * Two JSONL protocols are used in this codebase:
3
- *
4
- * 1. Fork Snapshot Protocol (snapshot.ts):
5
- * Types: session, model_change, thinking_level_change, system, message,
6
- * compression-stats
7
- * Purpose: Serialized session state passed to child flows via --session.
8
- * Emitted by buildForkSessionSnapshotJsonl() and consumed by
9
- * sanitizeForkSnapshot() before forking.
10
- *
11
- * 2. Streaming Stdout Protocol (runner-events.ts):
12
- * Types: session, agent_start, turn_start, message_start, message_end,
13
- * message_update
14
- * Sub-events under message_update: thinking_start, thinking_delta, text_delta
15
- * Purpose: Real-time events emitted by the pi process stdout during flow
16
- * execution. Parsed by processFlowJsonLine().
17
- */
18
- import { stripReasoningFromAssistantMessage } from "./reasoning-strip.js";
19
- import { stripSteeringHintFromContent, contentContainsSteeringHintTag, isJsonEqual, } from "./sliding-prompt.js";
20
- import { stripStrategicHintsFromContent } from "./tool-utils.js";
21
- // ---------------------------------------------------------------------------
22
- // Session snapshot serialization
23
- // ---------------------------------------------------------------------------
24
- export function buildForkSessionSnapshotJsonl(sessionManager) {
25
- const header = sessionManager.getHeader();
26
- if (!header || typeof header !== "object")
27
- return null;
28
- const branchEntries = sessionManager.getBranch();
29
- const lines = [];
30
- // Emit session header once, unless getBranch() already includes it as the
31
- // first entry (some session managers include the header in the branch).
32
- const firstBranch = branchEntries[0];
33
- const headerId = header?.id;
34
- const firstId = firstBranch && typeof firstBranch === "object" ? firstBranch?.id : undefined;
35
- const firstType = firstBranch && typeof firstBranch === "object" ? firstBranch?.type : undefined;
36
- if (!firstBranch ||
37
- typeof firstBranch !== "object" ||
38
- (firstType !== "session" && firstType !== "header") ||
39
- firstId !== headerId) {
40
- lines.push(JSON.stringify(header));
41
- }
42
- // Emit system event so the JSONL is self-contained — parsers can reconstruct
43
- // full context without needing the markdown section.
44
- const systemPrompt = header.systemPrompt;
45
- if (typeof systemPrompt === "string" && systemPrompt) {
46
- lines.push(JSON.stringify({ type: "system", content: systemPrompt }));
47
- }
48
- for (const entry of branchEntries)
49
- lines.push(JSON.stringify(entry));
50
- return `${lines.join("\n")}\n`;
51
- }
52
- // ---------------------------------------------------------------------------
53
- // Flow result compression
54
- // ---------------------------------------------------------------------------
55
- /**
56
- * Render a compressed flow result as compact text for child context.
57
- */
58
- export function renderCompressedFlowResult(r) {
59
- const parts = [`[Flow: ${r.type} ${r.status}]`];
60
- if (r.intent)
61
- parts.push(`Intent: ${r.intent}`);
62
- if (r.aim)
63
- parts.push(`Aim: ${r.aim}`);
64
- if (r.summary)
65
- parts.push(`Summary: ${r.summary}`);
66
- if (r.files?.length) {
67
- const fileLines = r.files
68
- .map((f) => {
69
- if (!f.path)
70
- return undefined;
71
- const role = f.role ? ` (${f.role})` : "";
72
- const desc = f.description ? ` — ${f.description}` : "";
73
- return ` ${f.path}${role}${desc}`;
74
- })
75
- .filter((line) => line !== undefined);
76
- // Safety net: if >50% of file entries were invalid (no path), compression is
77
- // producing garbage. Return undefined so caller falls back to truncated raw.
78
- if (fileLines.length === 0 || fileLines.length < r.files.length / 2) {
79
- return undefined;
80
- }
81
- parts.push(`Files:\n${fileLines.join("\n")}`);
82
- }
83
- if (r.actions?.length) {
84
- const actionLines = r.actions.map((a) => {
85
- const result = a.result ? ` → ${a.result}` : "";
86
- const target = a.target ? ` (${a.target})` : "";
87
- return ` [${a.type}] ${a.description}${target}${result}`;
88
- });
89
- parts.push(`Actions:\n${actionLines.join("\n")}`);
90
- }
91
- if (r.commands?.length) {
92
- const cmdLines = r.commands.map((c) => ` ${c.tool ?? "cmd"}: ${c.command}`);
93
- parts.push(`Commands:\n${cmdLines.join("\n")}`);
94
- }
95
- if (r.notDone?.length) {
96
- const ndLines = r.notDone.map((n) => {
97
- const reason = n.reason ? ` — ${n.reason}` : "";
98
- return ` ${n.item}${reason}`;
99
- });
100
- parts.push(`Not done:\n${ndLines.join("\n")}`);
101
- }
102
- if (r.nextSteps?.length) {
103
- parts.push(`Next steps:\n${r.nextSteps.map((s) => ` ${s}`).join("\n")}`);
104
- }
105
- if (r.reasoning?.length) {
106
- parts.push(`Reasoning:\n${r.reasoning.map((s) => ` ${s}`).join("\n")}`);
107
- }
108
- if (r.notes?.length) {
109
- parts.push(`Notes:\n${r.notes.map((s) => ` ${s}`).join("\n")}`);
110
- }
111
- if (r.error)
112
- parts.push(`Error: ${r.error}`);
113
- const text = parts.join("\n");
114
- if (text.includes("undefined"))
115
- return undefined;
116
- return text;
117
- }
118
- // ---------------------------------------------------------------------------
119
- // batch_read result compression
120
- // ---------------------------------------------------------------------------
121
- /**
122
- * Extract file paths from a batch_read tool call's arguments.
123
- * Handles both { o: [...] } and bare array argument formats.
124
- */
125
- function extractBatchReadPaths(args) {
126
- if (!args || typeof args !== "object")
127
- return [];
128
- let ops;
129
- if (Array.isArray(args)) {
130
- ops = args;
131
- }
132
- else if (Array.isArray(args.o)) {
133
- ops = args.o;
134
- }
135
- else {
136
- return [];
137
- }
138
- const paths = [];
139
- for (const op of ops) {
140
- if (!op || typeof op !== "object")
141
- continue;
142
- const p = op.p;
143
- if (typeof p === "string" && p)
144
- paths.push(p);
145
- }
146
- return paths;
147
- }
148
- /**
149
- * Render a compressed batch_read result as compact metadata for child context.
150
- * Format: [batch_read] N ops → paths: file1.ts, file2.ts, …
151
- */
152
- function renderCompressedBatchReadResult(paths) {
153
- const MAX_PATHS_DISPLAY = 10;
154
- const display = paths.slice(0, MAX_PATHS_DISPLAY);
155
- const suffix = paths.length > MAX_PATHS_DISPLAY ? `, … +${paths.length - MAX_PATHS_DISPLAY} more` : "";
156
- return `[batch_read] ${paths.length} ops → paths: ${display.join(", ")}${suffix}`;
157
- }
158
- // ---------------------------------------------------------------------------
159
- // Additional tool result compressors
160
- // ---------------------------------------------------------------------------
161
- const DEBUG_CONTEXT = typeof process !== "undefined" && process.env.PI_FLOW_DEBUG_CONTEXT === "1";
162
- function logCompress(toolName, before, after) {
163
- if (!DEBUG_CONTEXT)
164
- return;
165
- const reduction = before > 0 ? ((1 - after / before) * 100).toFixed(0) : "0";
166
- console.error(`[context-compress] ${toolName}: ${before} → ${after} bytes (${reduction}% reduction)`);
167
- }
168
- const KNOWN_SECTION_HEADERS = [
169
- /^--- (.+) \((\d+) lines\) ---$/,
170
- /^--- (.+) (context map|file summary) ---$/,
171
- /^--- bash \[.+\] exit (\d+) ---$/,
172
- /^--- edit: .+ ---$/,
173
- /^--- write: .+ ---$/,
174
- /^--- delete: .+ ---$/,
175
- /^--- read: .+ ---$/,
176
- /^--- (?!bash \[|edit:|write:|delete:|read:)(.+) ---$/,
177
- ];
178
- function isKnownSectionHeader(line) {
179
- return KNOWN_SECTION_HEADERS.some((re) => re.test(line));
180
- }
181
- /** Compress batch tool result: keep bash sections verbatim, truncate read content. */
182
- function compressBatchResult(text) {
183
- const lines = text.replace(/\r\n/g, "\n").split("\n");
184
- const out = [];
185
- let i = 0;
186
- while (i < lines.length) {
187
- const line = lines[i];
188
- // File read section with content — truncate
189
- const readMatch = line.match(/^--- (.+) \((\d+) lines\) ---$/);
190
- if (readMatch) {
191
- out.push(`--- ${readMatch[1]} (${readMatch[2]} lines, content truncated) ---`);
192
- i++;
193
- while (i < lines.length && !isKnownSectionHeader(lines[i])) {
194
- i++;
195
- }
196
- continue;
197
- }
198
- // Context map / file summary section — truncate
199
- const ctxMapMatch = line.match(/^--- (.+) (context map|file summary) ---$/);
200
- if (ctxMapMatch) {
201
- out.push(`--- ${ctxMapMatch[1]} (${ctxMapMatch[2]}, truncated) ---`);
202
- i++;
203
- while (i < lines.length && !isKnownSectionHeader(lines[i])) {
204
- i++;
205
- }
206
- continue;
207
- }
208
- // File read without line count — truncate
209
- // Negative lookahead excludes bash/edit/write/delete/read-error sections that should be kept verbatim
210
- const fallbackReadMatch = line.match(/^--- (?!bash \[|edit:|write:|delete:|read:)(.+) ---$/);
211
- if (fallbackReadMatch) {
212
- out.push(`--- ${fallbackReadMatch[1]} (content truncated) ---`);
213
- i++;
214
- while (i < lines.length && !isKnownSectionHeader(lines[i])) {
215
- i++;
216
- }
217
- continue;
218
- }
219
- // Everything else (bash, edit, write, delete, error, summary) — keep as-is
220
- out.push(line);
221
- i++;
222
- }
223
- return out.join("\n");
224
- }
225
- /** Compress web tool result into compact metadata. */
226
- function compressWebResult(text, args) {
227
- // Try to extract query/url from args
228
- let query;
229
- let url;
230
- if (args && typeof args === "object") {
231
- const a = args;
232
- const ops = Array.isArray(a.o) ? a.o : Array.isArray(a.op) ? a.op : undefined;
233
- if (ops && ops.length > 0) {
234
- const firstOp = ops[0];
235
- query = typeof firstOp.q === "string" ? firstOp.q : undefined;
236
- url = typeof firstOp.u === "string" ? firstOp.u : undefined;
237
- }
238
- }
239
- // Search result format: numbered list
240
- if (text.match(/^\d+\. .+\n https?:\/\//m)) {
241
- const lines = text.split("\n\n");
242
- const count = lines.length;
243
- const firstTitle = lines[0]?.match(/^\d+\. (.+)\n/)?.[1] ?? "unknown";
244
- const q = query ? ` "${query}"` : "";
245
- return `[web:search]${q} · ${count} results · first: ${firstTitle}`;
246
- }
247
- // Fetch result format: File/Title/Content length/Preview
248
- const fileMatch = text.match(/^File: (.+)\n/m);
249
- const titleMatch = text.match(/^Title: (.+)\n/m);
250
- const lengthMatch = text.match(/^Content length: (\d+) chars\n/m);
251
- if (fileMatch || titleMatch || lengthMatch || url) {
252
- const file = url ?? fileMatch?.[1] ?? "";
253
- const title = titleMatch?.[1] ?? "";
254
- const length = lengthMatch?.[1] ?? "0";
255
- return `[web:fetch] ${file} · "${title}" · ${length} chars`;
256
- }
257
- return `[web] result truncated (${text.length} chars)`;
258
- }
259
- /** Compress ask_user tool result into compact metadata. */
260
- function compressAskUserResult(text, args) {
261
- let question = "";
262
- if (args && typeof args === "object") {
263
- const q = args.question;
264
- if (typeof q === "string") {
265
- question = q.length > 80 ? q.slice(0, 77) + "..." : q;
266
- }
267
- }
268
- const answeredMatch = text.match(/^User answered: (.+)$/ms);
269
- if (answeredMatch) {
270
- const q = question ? ` "${question}"` : "";
271
- return `[ask_user]${q} → "${answeredMatch[1]}"`;
272
- }
273
- if (text.match(/^User cancelled/m)) {
274
- const q = question ? ` "${question}"` : "";
275
- return `[ask_user]${q} → cancelled`;
276
- }
277
- return `[ask_user] · ${text.length} chars`;
278
- }
279
- // ---------------------------------------------------------------------------
280
- // Shared: toolCallId → toolName mapping
281
- // ---------------------------------------------------------------------------
282
- /**
283
- * Build a map from toolCallId → toolName by scanning assistant messages.
284
- */
285
- function buildToolCallIdToNameMap(lines) {
286
- const map = new Map();
287
- for (const line of lines) {
288
- let entry;
289
- try {
290
- entry = JSON.parse(line);
291
- }
292
- catch {
293
- continue;
294
- }
295
- if (entry?.type !== "message" || entry.message?.role !== "assistant")
296
- continue;
297
- const content = entry.message.content;
298
- if (!Array.isArray(content))
299
- continue;
300
- for (const part of content) {
301
- if (part.type === "toolCall" && part.name) {
302
- const tcId = part.id ?? part.toolCallId;
303
- if (typeof tcId === "string" && tcId.trim()) {
304
- map.set(tcId, part.name);
305
- }
306
- }
307
- }
308
- }
309
- return map;
310
- }
311
- // ---------------------------------------------------------------------------
312
- // Tool result compression (flow + batch_read)
313
- // ---------------------------------------------------------------------------
314
- /**
315
- * Compress tool results in a sanitized session snapshot.
316
- *
317
- * Handles two tool types:
318
- * - `flow` results: replaced with compact CompressedFlowResult output from cache.
319
- * - `batch_read` results: replaced with compact metadata (paths + op count)
320
- * since children have `batch` and can re-read files themselves.
321
- */
322
- export function compressToolResults(snapshot, cache) {
323
- const lines = snapshot.trimEnd().split("\n");
324
- // Quick check: if there are no flow cache entries and no compressible tool calls,
325
- // nothing to compress — return early.
326
- if (cache.size === 0) {
327
- const hasCompressible = lines.some((line) => {
328
- try {
329
- const entry = JSON.parse(line);
330
- return entry?.type === "message" && entry.message?.role === "assistant" &&
331
- Array.isArray(entry.message.content) &&
332
- entry.message.content.some((p) => p.type === "toolCall" &&
333
- ["batch_read", "batch", "web", "ask_user"].includes(p.name));
334
- }
335
- catch {
336
- return false;
337
- }
338
- });
339
- const hasToolResultMessages = lines.some((line) => {
340
- try {
341
- const entry = JSON.parse(line);
342
- return entry?.type === "message" &&
343
- (entry.message?.role === "tool" || entry.message?.role === "toolResult");
344
- }
345
- catch {
346
- return false;
347
- }
348
- });
349
- // Must run the pass whenever tool results exist: we drop empty/whitespace
350
- // toolCallIds and pass through bash/flow/etc. even when the cache is empty.
351
- if (!hasCompressible && !hasToolResultMessages)
352
- return snapshot;
353
- }
354
- // Build toolCallId → toolName mapping
355
- const toolCallIdToName = buildToolCallIdToNameMap(lines);
356
- // Build toolCallId → arguments mapping for all tools (needed for batch/web/ask_user metadata)
357
- const toolCallIdToArgs = new Map();
358
- for (const line of lines) {
359
- let entry;
360
- try {
361
- entry = JSON.parse(line);
362
- }
363
- catch {
364
- continue;
365
- }
366
- if (entry?.type !== "message" || entry.message?.role !== "assistant")
367
- continue;
368
- const content = entry.message.content;
369
- if (!Array.isArray(content))
370
- continue;
371
- for (const part of content) {
372
- if (part.type === "toolCall" && (part.id || part.toolCallId) && part.arguments) {
373
- toolCallIdToArgs.set(part.id ?? part.toolCallId, part.arguments);
374
- }
375
- }
376
- }
377
- const result = [];
378
- // Second pass: compress matching tool results
379
- for (const line of lines) {
380
- let entry;
381
- try {
382
- entry = JSON.parse(line);
383
- }
384
- catch {
385
- result.push(line);
386
- continue;
387
- }
388
- if (entry?.type !== "message" || (entry.message?.role !== "tool" && entry.message?.role !== "toolResult")) {
389
- result.push(line);
390
- continue;
391
- }
392
- // Extract toolCallId — message-level or content-level toolResult.
393
- // Drop only *explicit* empty/whitespace IDs (APIs reject those). Missing
394
- // toolCallId is treated as legacy shape and passes through unchanged.
395
- let toolCallId;
396
- let invalidEmptyId = false;
397
- if (typeof entry.message.toolCallId === "string") {
398
- const v = entry.message.toolCallId;
399
- if (!v.trim())
400
- invalidEmptyId = true;
401
- else
402
- toolCallId = v;
403
- }
404
- else if (Array.isArray(entry.message.content)) {
405
- for (const part of entry.message.content) {
406
- if (part.type === "toolResult" && typeof part.toolCallId === "string") {
407
- if (!part.toolCallId.trim()) {
408
- invalidEmptyId = true;
409
- break;
410
- }
411
- toolCallId = part.toolCallId;
412
- break;
413
- }
414
- }
415
- }
416
- if (invalidEmptyId)
417
- continue;
418
- if (!toolCallId) {
419
- result.push(line);
420
- continue;
421
- }
422
- const toolName = toolCallIdToName.get(toolCallId);
423
- let rendered;
424
- let originalText = "";
425
- // --- Compress flow tool results ---
426
- if (toolName === "flow") {
427
- const compressed = cache.get(toolCallId);
428
- if (!compressed || compressed.length === 0) {
429
- // Cache miss (never populated or evicted) — do NOT pass megabytes of raw
430
- // flow output verbatim into child context. Render a minimal placeholder.
431
- originalText = extractToolResultText(entry) ?? "";
432
- const rawContent = entry.message?.content;
433
- const contentSize = rawContent
434
- ? (typeof rawContent === "string" ? rawContent.length : JSON.stringify(rawContent).length)
435
- : 0;
436
- const size = originalText.length || contentSize || line.length;
437
- rendered = `[flow] prior result · ${size} chars (not cached or evicted)`;
438
- }
439
- else {
440
- const renderResults = compressed.map(renderCompressedFlowResult);
441
- const hasAnyUndefined = renderResults.some(r => r === undefined);
442
- if (hasAnyUndefined) {
443
- // Safety net: compression produced garbage, fall back to truncated raw.
444
- originalText = extractToolResultText(entry) ?? "";
445
- const size = originalText.length;
446
- rendered = size > 2000
447
- ? originalText.slice(0, 2000) + "\n[truncated]"
448
- : originalText;
449
- }
450
- else {
451
- rendered = renderResults.filter((r) => r !== undefined).join("\n\n");
452
- }
453
- }
454
- }
455
- // Note: batch_read tool results are now compressed in stripBatchReadToolCalls
456
- // before compressToolResults runs, so this branch is no longer needed.
457
- // Kept as a no-op safety net for any edge cases.
458
- else if (toolName === "batch_read") {
459
- rendered = undefined; // handled upstream
460
- }
461
- // --- Compress batch tool results (selective: keep bash, truncate reads) ---
462
- else if (toolName === "batch") {
463
- originalText = extractToolResultText(entry) ?? "";
464
- rendered = compressBatchResult(originalText);
465
- }
466
- // --- Compress web tool results ---
467
- else if (toolName === "web") {
468
- originalText = extractToolResultText(entry) ?? "";
469
- const args = toolCallIdToArgs.get(toolCallId);
470
- rendered = compressWebResult(originalText, args);
471
- }
472
- // --- Compress ask_user tool results ---
473
- else if (toolName === "ask_user") {
474
- originalText = extractToolResultText(entry) ?? "";
475
- const args = toolCallIdToArgs.get(toolCallId);
476
- rendered = compressAskUserResult(originalText, args);
477
- }
478
- if (rendered !== undefined) {
479
- logCompress(toolName ?? "unknown", originalText.length || line.length, rendered.length);
480
- // Strip the 'details' field which carries UI metadata that children don't need.
481
- // This eliminates ~98% of payload bloat from flow tool results.
482
- const { details, ...messageWithoutDetails } = entry.message;
483
- if (typeof entry.message.toolCallId === "string") {
484
- entry = {
485
- ...entry,
486
- message: {
487
- ...messageWithoutDetails,
488
- content: [{ type: "text", text: rendered }],
489
- },
490
- };
491
- }
492
- else {
493
- entry = {
494
- ...entry,
495
- message: {
496
- ...messageWithoutDetails,
497
- content: entry.message.content.map((part) => part.type === "toolResult" && part.toolCallId === toolCallId
498
- ? { ...part, content: rendered }
499
- : part),
500
- },
501
- };
502
- }
503
- result.push(JSON.stringify(entry));
504
- continue;
505
- }
506
- // Other tool results pass through unchanged
507
- result.push(line);
508
- }
509
- return `${result.join("\n")}\n`;
510
- }
511
- /** Extract text content from a tool result entry for compression analysis. */
512
- function extractToolResultText(entry) {
513
- if (typeof entry.message?.content === "string") {
514
- return entry.message.content;
515
- }
516
- if (Array.isArray(entry.message?.content)) {
517
- for (const part of entry.message.content) {
518
- if (part.type === "text" && typeof part.text === "string") {
519
- return part.text;
520
- }
521
- }
522
- }
523
- return undefined;
524
- }
525
- /**
526
- * Backward-compatible alias for compressToolResults.
527
- * @deprecated Use compressToolResults instead.
528
- */
529
- export function compressFlowToolResults(snapshot, cache) {
530
- return compressToolResults(snapshot, cache);
531
- }
532
- // ---------------------------------------------------------------------------
533
- // batch_read tool call stripping
534
- // ---------------------------------------------------------------------------
535
- /**
536
- * Strip batch_read tool calls from assistant messages in a session snapshot.
537
- *
538
- * Children don't have batch_read in their active tools, so seeing calls to it
539
- * could confuse the model. This removes toolCall parts where name === "batch_read"
540
- * from assistant messages AND drops the corresponding toolResult messages
541
- * whose toolCallId references a stripped batch_read call. Keeping orphaned tool
542
- * results causes strict API providers (e.g. kimi-coding, DeepSeek) to reject
543
- * the request with `tool_call_id is not found`.
544
- */
545
- export function stripBatchReadToolCalls(snapshot) {
546
- const lines = snapshot.trimEnd().split("\n");
547
- // Pass 1: Collect all batch_read toolCallIds from assistant messages.
548
- const batchReadToolCallIds = new Set();
549
- for (const line of lines) {
550
- let entry;
551
- try {
552
- entry = JSON.parse(line);
553
- }
554
- catch {
555
- continue;
556
- }
557
- if (entry?.type !== "message" || entry.message?.role !== "assistant")
558
- continue;
559
- const content = entry.message.content;
560
- if (!Array.isArray(content))
561
- continue;
562
- for (const part of content) {
563
- if (part.type === "toolCall" && part.name === "batch_read" && (part.id || part.toolCallId)) {
564
- batchReadToolCallIds.add(part.id ?? part.toolCallId);
565
- }
566
- }
567
- }
568
- // Pass 2: Strip batch_read toolCall parts from assistant messages,
569
- // and remove orphaned tool result messages.
570
- const result = [];
571
- for (const line of lines) {
572
- let entry;
573
- try {
574
- entry = JSON.parse(line);
575
- }
576
- catch {
577
- result.push(line);
578
- continue;
579
- }
580
- if (entry?.type !== "message") {
581
- result.push(line);
582
- continue;
583
- }
584
- // Tool result message — skip if it's a batch_read result
585
- if (entry.message.role === "tool" || entry.message.role === "toolResult") {
586
- const toolCallId = entry.message.toolCallId ??
587
- (Array.isArray(entry.message.content) ? entry.message.content.find((p) => p.type === "toolResult")?.toolCallId : undefined);
588
- if (toolCallId && batchReadToolCallIds.has(toolCallId))
589
- continue;
590
- result.push(line);
591
- continue;
592
- }
593
- if (entry.message.role !== "assistant") {
594
- result.push(line);
595
- continue;
596
- }
597
- const content = entry.message.content;
598
- if (!Array.isArray(content)) {
599
- result.push(line);
600
- continue;
601
- }
602
- const hasBatchReadCall = content.some((part) => part.type === "toolCall" && part.name === "batch_read");
603
- if (!hasBatchReadCall) {
604
- result.push(line);
605
- continue;
606
- }
607
- const filteredContent = content.filter((part) => !(part.type === "toolCall" && part.name === "batch_read"));
608
- if (filteredContent.length === 0) {
609
- filteredContent.push({ type: "text", text: "" });
610
- }
611
- result.push(JSON.stringify({
612
- ...entry,
613
- message: {
614
- ...entry.message,
615
- content: filteredContent,
616
- },
617
- }));
618
- }
619
- return `${result.join("\n")}\n`;
620
- }
621
- export function sanitizeForkSnapshot(snapshot, cache = new Map(), options) {
622
- if (!snapshot)
623
- return snapshot;
624
- const preBytes = snapshot.length;
625
- const lines = snapshot.trimEnd().split("\n");
626
- const sanitizedLines = [];
627
- for (let i = 0; i < lines.length; i++) {
628
- const line = lines[i];
629
- let entry;
630
- try {
631
- entry = JSON.parse(line);
632
- }
633
- catch {
634
- sanitizedLines.push(line);
635
- continue;
636
- }
637
- let changed = false;
638
- // Header (first line): merge fork metadata and replace parent system prompt.
639
- if (i === 0 && entry && typeof entry === "object") {
640
- // Inject fork metadata so children know their lineage.
641
- if (options && (options.forkedFrom || options.forkedAt || options.parentFlow || options.depth !== undefined)) {
642
- entry = {
643
- ...entry,
644
- ...(options.forkedFrom !== undefined ? { forkedFrom: options.forkedFrom } : {}),
645
- ...(options.forkedAt !== undefined ? { forkedAt: options.forkedAt } : {}),
646
- ...(options.parentFlow !== undefined ? { parentFlow: options.parentFlow } : {}),
647
- ...(options.depth !== undefined ? { depth: options.depth } : {}),
648
- };
649
- changed = true;
650
- }
651
- // Replace the parent orchestrator system prompt with a brief note.
652
- // Children receive their own directive in the <activation> block.
653
- if (entry.systemPrompt && typeof entry.systemPrompt === "string") {
654
- entry = { ...entry, systemPrompt: "[parent orchestrator system prompt stripped — child receives its own directive]" };
655
- changed = true;
656
- }
657
- }
658
- // Drop sliding system prompt messages entirely.
659
- if (entry?.type === "message" &&
660
- entry.message?.role === "system" &&
661
- contentContainsSteeringHintTag(entry.message?.content)) {
662
- continue;
663
- }
664
- if (entry?.type === "message" && entry.message) {
665
- let message = entry.message;
666
- // Normalize internal "toolResult" role to "tool" for API compatibility.
667
- if (message.role === "toolResult") {
668
- message = { ...message, role: "tool" };
669
- changed = true;
670
- }
671
- // Strip reasoning/thinking from assistant messages.
672
- // (Reasoning typically only appears in assistant messages, but we
673
- // also check system/tool roles as a safety net for provider-specific
674
- // formats. stripReasoningFromAssistantMessage is a no-op on non-assistant
675
- // shapes, so calling it universally is safe.)
676
- if (message.role === "assistant" || message.role === "system" || message.role === "tool") {
677
- const stripped = stripReasoningFromAssistantMessage(message);
678
- message = stripped.message;
679
- changed ||= stripped.changed;
680
- }
681
- // Strip inner `message.timestamp` — the outer event-level timestamp (ISO string)
682
- // is sufficient for ordering. The inner epoch-ms timestamp is redundant.
683
- if ("timestamp" in message) {
684
- const { timestamp, ...restMessage } = message;
685
- message = restMessage;
686
- changed = true;
687
- }
688
- // Strip API metadata fields that children don't need (~5-7 KB per assistant message).
689
- // IMPORTANT: keep `usage` (including `totalTokens`). The child `pi` process replays
690
- // this JSONL and core/session code reads `message.usage.totalTokens`; stripping
691
- // `usage` causes: Cannot read properties of undefined (reading 'totalTokens').
692
- // Strip `cost` from `usage` — it's always zeros in forked context and children never need it.
693
- if (message.role === "assistant") {
694
- const { api, provider, model, stopReason, responseId, responseModel, usage, ...rest } = message;
695
- let stripped = false;
696
- if (api !== undefined || provider !== undefined || model !== undefined ||
697
- stopReason !== undefined || responseId !== undefined || responseModel !== undefined) {
698
- stripped = true;
699
- }
700
- // Strip cost sub-object from usage while preserving totalTokens and other fields.
701
- let cleanedUsage = usage;
702
- if (usage && typeof usage === "object" && "cost" in usage) {
703
- const { cost, ...usageWithoutCost } = usage;
704
- cleanedUsage = usageWithoutCost;
705
- stripped = true;
706
- }
707
- if (stripped) {
708
- message = { ...rest, ...(cleanedUsage !== undefined ? { usage: cleanedUsage } : {}) };
709
- changed = true;
710
- }
711
- }
712
- // Strip `details` from tool/toolResult messages — carries FlowDetails UI metadata
713
- // (mode, flowStyle, projectAgentsDir, results) that children never need.
714
- if (message.role === "tool" || message.role === "toolResult") {
715
- if ("details" in message) {
716
- const { details, ...restMessage } = message;
717
- message = restMessage;
718
- changed = true;
719
- }
720
- }
721
- if ("content" in message) {
722
- let modifiedContent = message.content;
723
- // Strip sliding prompts
724
- const afterSliding = stripSteeringHintFromContent(modifiedContent);
725
- if (!isJsonEqual(afterSliding, modifiedContent)) {
726
- modifiedContent = afterSliding;
727
- changed = true;
728
- }
729
- // Strip strategic hints from tool results
730
- if (message.role === "tool" || message.role === "toolResult") {
731
- const afterHints = stripStrategicHintsFromContent(modifiedContent);
732
- if (!isJsonEqual(afterHints, modifiedContent)) {
733
- modifiedContent = afterHints;
734
- changed = true;
735
- }
736
- }
737
- if (changed) {
738
- message = { ...message, content: modifiedContent };
739
- }
740
- }
741
- if (changed) {
742
- entry = { ...entry, message };
743
- }
744
- }
745
- const outLine = changed ? JSON.stringify(entry) : line;
746
- sanitizedLines.push(outLine);
747
- }
748
- // Reparent orphaned parentIds after steering-hint messages were dropped.
749
- // Build a set of surviving message IDs, then fix any parentId that points
750
- // to a removed message.
751
- const survivingIds = new Set();
752
- for (const line of sanitizedLines) {
753
- try {
754
- const entry = JSON.parse(line);
755
- const id = entry?.message?.id ?? entry?.message?.messageId ?? entry?.id;
756
- if (typeof id === "string" && id)
757
- survivingIds.add(id);
758
- }
759
- catch { /* ignore */ }
760
- }
761
- for (let i = 0; i < sanitizedLines.length; i++) {
762
- try {
763
- const entry = JSON.parse(sanitizedLines[i]);
764
- if (!entry?.message)
765
- continue;
766
- const parentId = entry.message.parentId ?? entry.message.parentMessageId;
767
- if (typeof parentId === "string" && parentId && !survivingIds.has(parentId)) {
768
- const { parentId: _pid, parentMessageId: _pmid, ...restMessage } = entry.message;
769
- entry.message = restMessage;
770
- sanitizedLines[i] = JSON.stringify(entry);
771
- }
772
- }
773
- catch { /* ignore */ }
774
- }
775
- let sanitized = `${sanitizedLines.join("\n")}\n`;
776
- // Strip batch_read tool calls from assistant messages.
777
- // Children don't have batch_read in their active tools.
778
- sanitized = stripBatchReadToolCalls(sanitized);
779
- // Compress tool results (flow, batch, web, ask_user).
780
- sanitized = compressToolResults(sanitized, cache);
781
- // Telemetry: measure total delta across sanitization, stripping, and compression.
782
- const postBytes = sanitized.length;
783
- const reduction = preBytes > 0 ? ((1 - postBytes / preBytes) * 100).toFixed(0) : "0";
784
- if (DEBUG_CONTEXT) {
785
- console.error(`[context-snapshot] pre: ${preBytes} → post: ${postBytes} bytes (${reduction}% reduction)`);
786
- }
787
- // Always emit compression-stats as a trailing metadata entry so the dump contains
788
- // observability data regardless of DEBUG_CONTEXT setting.
789
- sanitized = sanitized.trimEnd() + "\n" + JSON.stringify({
790
- type: "compression-stats",
791
- preBytes,
792
- postBytes,
793
- reductionPercent: Number(reduction),
794
- }) + "\n";
795
- return sanitized;
796
- }
797
- //# sourceMappingURL=snapshot.js.map