@inhuman.tools/turn 0.0.0-stage → 0.1.0

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 (346) hide show
  1. package/LICENSE.md +7 -0
  2. package/dist/agent-calls/acceptance.d.ts +12 -0
  3. package/dist/agent-calls/acceptance.d.ts.map +1 -0
  4. package/dist/agent-calls/acceptance.js +64 -0
  5. package/dist/agent-calls/acceptance.js.map +1 -0
  6. package/dist/agent-calls/callbacks.d.ts +14 -0
  7. package/dist/agent-calls/callbacks.d.ts.map +1 -0
  8. package/dist/agent-calls/callbacks.js +59 -0
  9. package/dist/agent-calls/callbacks.js.map +1 -0
  10. package/dist/agent-calls/delivery.d.ts +7 -0
  11. package/dist/agent-calls/delivery.d.ts.map +1 -0
  12. package/dist/agent-calls/delivery.js +16 -0
  13. package/dist/agent-calls/delivery.js.map +1 -0
  14. package/dist/agent-calls/index.d.ts +7 -0
  15. package/dist/agent-calls/index.d.ts.map +1 -0
  16. package/dist/agent-calls/index.js +6 -0
  17. package/dist/agent-calls/index.js.map +1 -0
  18. package/dist/agent-calls/operator.d.ts +21 -0
  19. package/dist/agent-calls/operator.d.ts.map +1 -0
  20. package/dist/agent-calls/operator.js +78 -0
  21. package/dist/agent-calls/operator.js.map +1 -0
  22. package/dist/agent-calls/runtime.d.ts +99 -0
  23. package/dist/agent-calls/runtime.d.ts.map +1 -0
  24. package/dist/agent-calls/runtime.js +245 -0
  25. package/dist/agent-calls/runtime.js.map +1 -0
  26. package/dist/agent-calls/types.d.ts +31 -0
  27. package/dist/agent-calls/types.d.ts.map +1 -0
  28. package/dist/agent-calls/types.js +2 -0
  29. package/dist/agent-calls/types.js.map +1 -0
  30. package/dist/context/compact-floors.d.ts +43 -0
  31. package/dist/context/compact-floors.d.ts.map +1 -0
  32. package/dist/context/compact-floors.js +52 -0
  33. package/dist/context/compact-floors.js.map +1 -0
  34. package/dist/context/compact.d.ts +42 -0
  35. package/dist/context/compact.d.ts.map +1 -0
  36. package/dist/context/compact.js +155 -0
  37. package/dist/context/compact.js.map +1 -0
  38. package/dist/context/delivery.d.ts +13 -0
  39. package/dist/context/delivery.d.ts.map +1 -0
  40. package/dist/context/delivery.js +6 -0
  41. package/dist/context/delivery.js.map +1 -0
  42. package/dist/context/history.d.ts +66 -0
  43. package/dist/context/history.d.ts.map +1 -0
  44. package/dist/context/history.js +573 -0
  45. package/dist/context/history.js.map +1 -0
  46. package/dist/context/index.d.ts +10 -0
  47. package/dist/context/index.d.ts.map +1 -0
  48. package/dist/context/index.js +10 -0
  49. package/dist/context/index.js.map +1 -0
  50. package/dist/context/observations.d.ts +7 -0
  51. package/dist/context/observations.d.ts.map +1 -0
  52. package/dist/context/observations.js +11 -0
  53. package/dist/context/observations.js.map +1 -0
  54. package/dist/context/retention/defaults.d.ts +10 -0
  55. package/dist/context/retention/defaults.d.ts.map +1 -0
  56. package/dist/context/retention/defaults.js +47 -0
  57. package/dist/context/retention/defaults.js.map +1 -0
  58. package/dist/context/retention/index.d.ts +14 -0
  59. package/dist/context/retention/index.d.ts.map +1 -0
  60. package/dist/context/retention/index.js +21 -0
  61. package/dist/context/retention/index.js.map +1 -0
  62. package/dist/context/retention/narrow.d.ts +22 -0
  63. package/dist/context/retention/narrow.d.ts.map +1 -0
  64. package/dist/context/retention/narrow.js +140 -0
  65. package/dist/context/retention/narrow.js.map +1 -0
  66. package/dist/context/retention/render.d.ts +37 -0
  67. package/dist/context/retention/render.d.ts.map +1 -0
  68. package/dist/context/retention/render.js +149 -0
  69. package/dist/context/retention/render.js.map +1 -0
  70. package/dist/context/retention/resolve.d.ts +51 -0
  71. package/dist/context/retention/resolve.d.ts.map +1 -0
  72. package/dist/context/retention/resolve.js +254 -0
  73. package/dist/context/retention/resolve.js.map +1 -0
  74. package/dist/context/retention/schema.d.ts +345 -0
  75. package/dist/context/retention/schema.d.ts.map +1 -0
  76. package/dist/context/retention/schema.js +149 -0
  77. package/dist/context/retention/schema.js.map +1 -0
  78. package/dist/context/retention/stamp.d.ts +25 -0
  79. package/dist/context/retention/stamp.d.ts.map +1 -0
  80. package/dist/context/retention/stamp.js +103 -0
  81. package/dist/context/retention/stamp.js.map +1 -0
  82. package/dist/context/retention/step.d.ts +33 -0
  83. package/dist/context/retention/step.d.ts.map +1 -0
  84. package/dist/context/retention/step.js +74 -0
  85. package/dist/context/retention/step.js.map +1 -0
  86. package/dist/context/retention/view.d.ts +57 -0
  87. package/dist/context/retention/view.d.ts.map +1 -0
  88. package/dist/context/retention/view.js +121 -0
  89. package/dist/context/retention/view.js.map +1 -0
  90. package/dist/context/squeeze.d.ts +45 -0
  91. package/dist/context/squeeze.d.ts.map +1 -0
  92. package/dist/context/squeeze.js +182 -0
  93. package/dist/context/squeeze.js.map +1 -0
  94. package/dist/context/tools.d.ts +16 -0
  95. package/dist/context/tools.d.ts.map +1 -0
  96. package/dist/context/tools.js +73 -0
  97. package/dist/context/tools.js.map +1 -0
  98. package/dist/context/transcript.d.ts +151 -0
  99. package/dist/context/transcript.d.ts.map +1 -0
  100. package/dist/context/transcript.js +659 -0
  101. package/dist/context/transcript.js.map +1 -0
  102. package/dist/policy-processor/bypass.d.ts +95 -0
  103. package/dist/policy-processor/bypass.d.ts.map +1 -0
  104. package/dist/policy-processor/bypass.js +188 -0
  105. package/dist/policy-processor/bypass.js.map +1 -0
  106. package/dist/policy-processor/classifier.d.ts +47 -0
  107. package/dist/policy-processor/classifier.d.ts.map +1 -0
  108. package/dist/policy-processor/classifier.js +671 -0
  109. package/dist/policy-processor/classifier.js.map +1 -0
  110. package/dist/policy-processor/cluster.d.ts +7 -0
  111. package/dist/policy-processor/cluster.d.ts.map +1 -0
  112. package/dist/policy-processor/cluster.js +32 -0
  113. package/dist/policy-processor/cluster.js.map +1 -0
  114. package/dist/policy-processor/evidence.d.ts +26 -0
  115. package/dist/policy-processor/evidence.d.ts.map +1 -0
  116. package/dist/policy-processor/evidence.js +204 -0
  117. package/dist/policy-processor/evidence.js.map +1 -0
  118. package/dist/policy-processor/explain.d.ts +59 -0
  119. package/dist/policy-processor/explain.d.ts.map +1 -0
  120. package/dist/policy-processor/explain.js +224 -0
  121. package/dist/policy-processor/explain.js.map +1 -0
  122. package/dist/policy-processor/index.d.ts +6 -0
  123. package/dist/policy-processor/index.d.ts.map +1 -0
  124. package/dist/policy-processor/index.js +6 -0
  125. package/dist/policy-processor/index.js.map +1 -0
  126. package/dist/policy-processor/license.d.ts +23 -0
  127. package/dist/policy-processor/license.d.ts.map +1 -0
  128. package/dist/policy-processor/license.js +47 -0
  129. package/dist/policy-processor/license.js.map +1 -0
  130. package/dist/policy-processor/peel-cwd.d.ts +2 -0
  131. package/dist/policy-processor/peel-cwd.d.ts.map +1 -0
  132. package/dist/policy-processor/peel-cwd.js +2 -0
  133. package/dist/policy-processor/peel-cwd.js.map +1 -0
  134. package/dist/policy-processor/real-path.d.ts +7 -0
  135. package/dist/policy-processor/real-path.d.ts.map +1 -0
  136. package/dist/policy-processor/real-path.js +42 -0
  137. package/dist/policy-processor/real-path.js.map +1 -0
  138. package/dist/policy-processor/scope.d.ts +111 -0
  139. package/dist/policy-processor/scope.d.ts.map +1 -0
  140. package/dist/policy-processor/scope.js +629 -0
  141. package/dist/policy-processor/scope.js.map +1 -0
  142. package/dist/policy-processor/self-protect.d.ts +10 -0
  143. package/dist/policy-processor/self-protect.d.ts.map +1 -0
  144. package/dist/policy-processor/self-protect.js +48 -0
  145. package/dist/policy-processor/self-protect.js.map +1 -0
  146. package/dist/policy-processor/soft-deny.d.ts +75 -0
  147. package/dist/policy-processor/soft-deny.d.ts.map +1 -0
  148. package/dist/policy-processor/soft-deny.js +185 -0
  149. package/dist/policy-processor/soft-deny.js.map +1 -0
  150. package/dist/policy-processor/unattended-guard.d.ts +28 -0
  151. package/dist/policy-processor/unattended-guard.d.ts.map +1 -0
  152. package/dist/policy-processor/unattended-guard.js +145 -0
  153. package/dist/policy-processor/unattended-guard.js.map +1 -0
  154. package/dist/session/cancel.d.ts +14 -0
  155. package/dist/session/cancel.d.ts.map +1 -0
  156. package/dist/session/cancel.js +32 -0
  157. package/dist/session/cancel.js.map +1 -0
  158. package/dist/session/commands.d.ts +41 -0
  159. package/dist/session/commands.d.ts.map +1 -0
  160. package/dist/session/commands.js +241 -0
  161. package/dist/session/commands.js.map +1 -0
  162. package/dist/session/compact-actor.d.ts +23 -0
  163. package/dist/session/compact-actor.d.ts.map +1 -0
  164. package/dist/session/compact-actor.js +91 -0
  165. package/dist/session/compact-actor.js.map +1 -0
  166. package/dist/session/compact-env.d.ts +54 -0
  167. package/dist/session/compact-env.d.ts.map +1 -0
  168. package/dist/session/compact-env.js +345 -0
  169. package/dist/session/compact-env.js.map +1 -0
  170. package/dist/session/compact.d.ts +42 -0
  171. package/dist/session/compact.d.ts.map +1 -0
  172. package/dist/session/compact.js +68 -0
  173. package/dist/session/compact.js.map +1 -0
  174. package/dist/session/index.d.ts +9 -0
  175. package/dist/session/index.d.ts.map +1 -0
  176. package/dist/session/index.js +8 -0
  177. package/dist/session/index.js.map +1 -0
  178. package/dist/session/park.d.ts +16 -0
  179. package/dist/session/park.d.ts.map +1 -0
  180. package/dist/session/park.js +73 -0
  181. package/dist/session/park.js.map +1 -0
  182. package/dist/session/side-channel-reminder.d.ts +41 -0
  183. package/dist/session/side-channel-reminder.d.ts.map +1 -0
  184. package/dist/session/side-channel-reminder.js +132 -0
  185. package/dist/session/side-channel-reminder.js.map +1 -0
  186. package/dist/session/types.d.ts +44 -0
  187. package/dist/session/types.d.ts.map +1 -0
  188. package/dist/session/types.js +2 -0
  189. package/dist/session/types.js.map +1 -0
  190. package/dist/tool-catalog/catalog.d.ts +77 -0
  191. package/dist/tool-catalog/catalog.d.ts.map +1 -0
  192. package/dist/tool-catalog/catalog.js +380 -0
  193. package/dist/tool-catalog/catalog.js.map +1 -0
  194. package/dist/tool-catalog/index.d.ts +4 -0
  195. package/dist/tool-catalog/index.d.ts.map +1 -0
  196. package/dist/tool-catalog/index.js +3 -0
  197. package/dist/tool-catalog/index.js.map +1 -0
  198. package/dist/tool-catalog/recipes.d.ts +36 -0
  199. package/dist/tool-catalog/recipes.d.ts.map +1 -0
  200. package/dist/tool-catalog/recipes.js +121 -0
  201. package/dist/tool-catalog/recipes.js.map +1 -0
  202. package/dist/tool-catalog/types.d.ts +103 -0
  203. package/dist/tool-catalog/types.d.ts.map +1 -0
  204. package/dist/tool-catalog/types.js +5 -0
  205. package/dist/tool-catalog/types.js.map +1 -0
  206. package/dist/tool-runtime/approval/approved.d.ts +13 -0
  207. package/dist/tool-runtime/approval/approved.d.ts.map +1 -0
  208. package/dist/tool-runtime/approval/approved.js +20 -0
  209. package/dist/tool-runtime/approval/approved.js.map +1 -0
  210. package/dist/tool-runtime/approval/dist.d.ts +6 -0
  211. package/dist/tool-runtime/approval/dist.d.ts.map +1 -0
  212. package/dist/tool-runtime/approval/dist.js +28 -0
  213. package/dist/tool-runtime/approval/dist.js.map +1 -0
  214. package/dist/tool-runtime/approval/inbound.d.ts +7 -0
  215. package/dist/tool-runtime/approval/inbound.d.ts.map +1 -0
  216. package/dist/tool-runtime/approval/inbound.js +48 -0
  217. package/dist/tool-runtime/approval/inbound.js.map +1 -0
  218. package/dist/tool-runtime/approval/unattended.d.ts +8 -0
  219. package/dist/tool-runtime/approval/unattended.d.ts.map +1 -0
  220. package/dist/tool-runtime/approval/unattended.js +23 -0
  221. package/dist/tool-runtime/approval/unattended.js.map +1 -0
  222. package/dist/tool-runtime/blob-port.d.ts +46 -0
  223. package/dist/tool-runtime/blob-port.d.ts.map +1 -0
  224. package/dist/tool-runtime/blob-port.js +47 -0
  225. package/dist/tool-runtime/blob-port.js.map +1 -0
  226. package/dist/tool-runtime/extra-root.d.ts +3 -0
  227. package/dist/tool-runtime/extra-root.d.ts.map +1 -0
  228. package/dist/tool-runtime/extra-root.js +14 -0
  229. package/dist/tool-runtime/extra-root.js.map +1 -0
  230. package/dist/tool-runtime/handler.d.ts +29 -0
  231. package/dist/tool-runtime/handler.d.ts.map +1 -0
  232. package/dist/tool-runtime/handler.js +460 -0
  233. package/dist/tool-runtime/handler.js.map +1 -0
  234. package/dist/tool-runtime/index.d.ts +14 -0
  235. package/dist/tool-runtime/index.d.ts.map +1 -0
  236. package/dist/tool-runtime/index.js +14 -0
  237. package/dist/tool-runtime/index.js.map +1 -0
  238. package/dist/tool-runtime/peel.d.ts +19 -0
  239. package/dist/tool-runtime/peel.d.ts.map +1 -0
  240. package/dist/tool-runtime/peel.js +74 -0
  241. package/dist/tool-runtime/peel.js.map +1 -0
  242. package/dist/tool-runtime/plugin-context.d.ts +13 -0
  243. package/dist/tool-runtime/plugin-context.d.ts.map +1 -0
  244. package/dist/tool-runtime/plugin-context.js +55 -0
  245. package/dist/tool-runtime/plugin-context.js.map +1 -0
  246. package/dist/tool-runtime/recall.d.ts +18 -0
  247. package/dist/tool-runtime/recall.d.ts.map +1 -0
  248. package/dist/tool-runtime/recall.js +100 -0
  249. package/dist/tool-runtime/recall.js.map +1 -0
  250. package/dist/tool-runtime/run-system-tool.d.ts +13 -0
  251. package/dist/tool-runtime/run-system-tool.d.ts.map +1 -0
  252. package/dist/tool-runtime/run-system-tool.js +230 -0
  253. package/dist/tool-runtime/run-system-tool.js.map +1 -0
  254. package/dist/tool-runtime/tool-error.d.ts +6 -0
  255. package/dist/tool-runtime/tool-error.d.ts.map +1 -0
  256. package/dist/tool-runtime/tool-error.js +6 -0
  257. package/dist/tool-runtime/tool-error.js.map +1 -0
  258. package/dist/tool-runtime/worker-tools.d.ts +8 -0
  259. package/dist/tool-runtime/worker-tools.d.ts.map +1 -0
  260. package/dist/tool-runtime/worker-tools.js +65 -0
  261. package/dist/tool-runtime/worker-tools.js.map +1 -0
  262. package/dist/tool-runtime/write-admit.d.ts +21 -0
  263. package/dist/tool-runtime/write-admit.d.ts.map +1 -0
  264. package/dist/tool-runtime/write-admit.js +89 -0
  265. package/dist/tool-runtime/write-admit.js.map +1 -0
  266. package/package.json +114 -3
  267. package/policy/auto-mode-ask.scm +201 -0
  268. package/policy/bypass-detect.prompt +55 -0
  269. package/policy/classifier-bash-incomplete.prompt +46 -0
  270. package/policy/classifier-bash.prompt +45 -0
  271. package/policy/classifier-mcp.prompt +45 -0
  272. package/policy/classifier-read.prompt +44 -0
  273. package/policy/classifier-write-outside.prompt +46 -0
  274. package/policy/classifier-write-risky.prompt +45 -0
  275. package/policy/classifier-write-workspace.prompt +46 -0
  276. package/policy/classifier-write.prompt +45 -0
  277. package/policy/soft-deny-round1.prompt +51 -0
  278. package/policy/soft-deny-round2.prompt +60 -0
  279. package/src/agent-calls/acceptance.ts +71 -0
  280. package/src/agent-calls/callbacks.ts +75 -0
  281. package/src/agent-calls/delivery.ts +15 -0
  282. package/src/agent-calls/index.ts +16 -0
  283. package/src/agent-calls/operator.ts +99 -0
  284. package/src/agent-calls/runtime.ts +310 -0
  285. package/src/agent-calls/types.ts +33 -0
  286. package/src/context/compact-floors.ts +76 -0
  287. package/src/context/compact.ts +176 -0
  288. package/src/context/delivery.ts +15 -0
  289. package/src/context/history.ts +725 -0
  290. package/src/context/index.ts +35 -0
  291. package/src/context/observations.ts +13 -0
  292. package/src/context/retention/defaults.ts +58 -0
  293. package/src/context/retention/index.ts +45 -0
  294. package/src/context/retention/narrow.ts +123 -0
  295. package/src/context/retention/render.ts +177 -0
  296. package/src/context/retention/resolve.ts +306 -0
  297. package/src/context/retention/schema.ts +173 -0
  298. package/src/context/retention/stamp.ts +121 -0
  299. package/src/context/retention/step.ts +85 -0
  300. package/src/context/retention/view.ts +161 -0
  301. package/src/context/squeeze.ts +218 -0
  302. package/src/context/tools.ts +92 -0
  303. package/src/context/transcript.ts +705 -0
  304. package/src/policy-processor/README.md +43 -0
  305. package/src/policy-processor/bypass.ts +246 -0
  306. package/src/policy-processor/classifier.ts +711 -0
  307. package/src/policy-processor/cluster.ts +31 -0
  308. package/src/policy-processor/evidence.ts +214 -0
  309. package/src/policy-processor/explain.ts +310 -0
  310. package/src/policy-processor/index.ts +6 -0
  311. package/src/policy-processor/license.ts +45 -0
  312. package/src/policy-processor/peel-cwd.ts +1 -0
  313. package/src/policy-processor/real-path.ts +41 -0
  314. package/src/policy-processor/scope.ts +655 -0
  315. package/src/policy-processor/self-protect.ts +46 -0
  316. package/src/policy-processor/soft-deny.ts +225 -0
  317. package/src/policy-processor/unattended-guard.ts +158 -0
  318. package/src/session/cancel.ts +34 -0
  319. package/src/session/commands.ts +292 -0
  320. package/src/session/compact-actor.ts +132 -0
  321. package/src/session/compact-env.ts +539 -0
  322. package/src/session/compact.ts +95 -0
  323. package/src/session/index.ts +18 -0
  324. package/src/session/park.ts +73 -0
  325. package/src/session/side-channel-reminder.ts +148 -0
  326. package/src/session/types.ts +50 -0
  327. package/src/tool-catalog/catalog.ts +282 -0
  328. package/src/tool-catalog/index.ts +26 -0
  329. package/src/tool-catalog/recipes.ts +154 -0
  330. package/src/tool-catalog/types.ts +110 -0
  331. package/src/tool-runtime/approval/approved.ts +20 -0
  332. package/src/tool-runtime/approval/dist.ts +29 -0
  333. package/src/tool-runtime/approval/inbound.ts +50 -0
  334. package/src/tool-runtime/approval/unattended.ts +24 -0
  335. package/src/tool-runtime/blob-port.ts +64 -0
  336. package/src/tool-runtime/extra-root.ts +16 -0
  337. package/src/tool-runtime/handler.ts +576 -0
  338. package/src/tool-runtime/index.ts +21 -0
  339. package/src/tool-runtime/peel.ts +91 -0
  340. package/src/tool-runtime/plugin-context.ts +68 -0
  341. package/src/tool-runtime/recall.ts +104 -0
  342. package/src/tool-runtime/run-system-tool.ts +273 -0
  343. package/src/tool-runtime/tool-error.ts +6 -0
  344. package/src/tool-runtime/worker-tools.ts +76 -0
  345. package/src/tool-runtime/write-admit.ts +90 -0
  346. package/README.md +0 -3
@@ -0,0 +1,539 @@
1
+ /**
2
+ * Compact env is an EnvCapability, not a bag of names.
3
+ *
4
+ * Five keys (`writing-capabilities.md`, `environments.md` §CAPABILITY):
5
+ * configuration, resources, prelude, symbols, deps. Symbols are the membrane
6
+ * crossing (honest codecs). Prelude is Scheme bootstrap that PERSISTS into the
7
+ * strategy eval — derived names are `(define …)` over primitives, not a second
8
+ * JS verb. User code may call prelude defines; it may not name a `preludeOnly`
9
+ * symbol (those exist only while the prelude runs).
10
+ *
11
+ * Compact is not a classify walk. `applyCompact` loads the strategy with
12
+ * `execState` and calls `compact` / `forced-compact` as host functions,
13
+ * passing `Transcript` instances as arguments. Occupancy primitives are
14
+ * `tokens/used` and `tokens/total`; remaining and relative live in the prelude.
15
+ * `user-rounds/count` is a primitive (prompt nonempty). `message/ack` is a
16
+ * primitive so the cell is marked synthetic.
17
+ *
18
+ * Membrane: `Transcript` is `@arrival.private` — opaque `z.instance(Transcript)`.
19
+ * Eat cells are a union of host classes, so they cross as `z.box` (non-opaque)
20
+ * — `z.instance` unions throw on the first arm instead of falling through.
21
+ *
22
+ * Provenance: config-readers (`chunks`, `reason`, `kv/get`, …) are `source` —
23
+ * external data crossing in. Meters and slicers over a transcript ARGUMENT
24
+ * are `pipe` — pure transforms that mint nothing. `transcript/warm?` is the
25
+ * one argument-reader that stays `source`: `isWarm` reads the live clock.
26
+ */
27
+ import { dirname } from "node:path";
28
+
29
+ import {
30
+ EnvCapability,
31
+ execState,
32
+ jsToScheme,
33
+ makeCallCtx,
34
+ toJS,
35
+ type RunContext,
36
+ type SchemeValue,
37
+ } from "@here.build/arrival";
38
+ import { applyCallback, is_callable_value } from "@here.build/arrival/host-internals";
39
+ import { arrivalLoaderCapability } from "@here.build/arrival-modules";
40
+ import { promptIsolatedCapability, type PromptComplete } from "@inhuman.tools/env-capability-module-prompt";
41
+ import {
42
+ CompactJob,
43
+ CompletionExpectation,
44
+ CompletionJob,
45
+ ProgrammaticJob,
46
+ tapeCompletion,
47
+ ToolCallJob,
48
+ TurnApprovalNoteChunk,
49
+ TurnNotificationChunk,
50
+ TurnSteerChunk,
51
+ } from "@inhuman.tools/harness-model";
52
+ import { Job } from "@inhuman.tools/plexus-expectations";
53
+ import { programSourceLoader } from "@inhuman.tools/scheme-program/fs";
54
+ import invariant from "tiny-invariant";
55
+ import { z } from "zod";
56
+
57
+ import {
58
+ contentTokensOf,
59
+ MessageChunk,
60
+ payloadSuffix,
61
+ textOfCell,
62
+ toolCallRepresentation,
63
+ Transcript,
64
+ type EatCell,
65
+ } from "../context/index.js";
66
+ import { charsOf } from "../context/transcript.js";
67
+
68
+ /** Same door as scheme_repl `load`: last-write-wins over `require`. */
69
+ const schemeLoadAliasCapability = EnvCapability.define("harness/scheme-repl-load", {
70
+ deps: [arrivalLoaderCapability],
71
+ symbols: (symbol) => ({
72
+ load: symbol.defineSyntax`load: alias of require; spills .scm defines into this run`(
73
+ "(lambda (specifier) `(require ,specifier))",
74
+ ),
75
+ }),
76
+ });
77
+
78
+ const hostChunk = z.union([
79
+ z.instanceof(CompactJob),
80
+ z.instanceof(CompletionExpectation),
81
+ z.instanceof(CompletionJob),
82
+ z.instanceof(ToolCallJob),
83
+ z.instanceof(ProgrammaticJob),
84
+ z.instanceof(TurnNotificationChunk),
85
+ z.instanceof(TurnApprovalNoteChunk),
86
+ z.instanceof(TurnSteerChunk),
87
+ z.instanceof(MessageChunk),
88
+ z.string(),
89
+ ]);
90
+
91
+ export type CompactStrategyOpts = {
92
+ readonly reason?: string;
93
+ readonly focus?: string;
94
+ readonly kv?: Readonly<Record<string, string>>;
95
+ readonly plan?: string | null;
96
+ readonly forced?: boolean;
97
+ /**
98
+ * Absolute path the program source was read from. Enables `(require "…")` —
99
+ * `.prompt` pretreats ship next to the strategy — jailed under its dirname.
100
+ * Null for the inline fallback: no `require` surface.
101
+ */
102
+ readonly sourcePath?: string | null;
103
+ /**
104
+ * Live completion wiring for the summarizer call a `.prompt` performs. The
105
+ * prompt capability admits an agent/ask on this job; absent → the strategy
106
+ * renders but cannot run a model call (bench/stub path).
107
+ */
108
+ readonly job?: Job;
109
+ readonly complete?: PromptComplete;
110
+ readonly connectionId?: string;
111
+ readonly completionModel?: string;
112
+ /** Abort rejects at once and hands the abort to each summarizer round through `complete`. */
113
+ readonly signal?: AbortSignal;
114
+ };
115
+
116
+ export type CompactFailure = {
117
+ readonly ok: false;
118
+ readonly reason: "empty-check-in" | "did-not-shrink";
119
+ };
120
+
121
+ export type CompactSuccess = {
122
+ readonly ok: true;
123
+ readonly checkIn: EatCell[];
124
+ };
125
+
126
+ export type CompactApplyResult = CompactSuccess | CompactFailure;
127
+
128
+ type CompactConfig = {
129
+ chunks: Array<z.infer<typeof hostChunk>>;
130
+ reason: string;
131
+ focus: string;
132
+ kv: Record<string, string>;
133
+ plan: string | null;
134
+ };
135
+
136
+ export const compactReducerCapability = EnvCapability.define("harness/compact", {
137
+ configuration: {
138
+ chunks: z.array(hostChunk).default([]),
139
+ reason: z.string().default("overflow"),
140
+ focus: z.string().default(""),
141
+ kv: z.record(z.string(), z.string()).default({}),
142
+ plan: z.string().nullable().optional(),
143
+ },
144
+ symbols: (symbol, sz) => {
145
+ const chunk = sz.union([sz.box, sz.string]);
146
+ const transcript = sz.instance(Transcript);
147
+ const cells = sz.list(chunk);
148
+ // "value or #f" — the R7RS search-sentinel union (r7rs/lists.ts), never z.dynamic.
149
+ const stringOrFalse = sz.union([sz.string, sz.booleanFalse]);
150
+ return {
151
+ chunks: symbol.rosetta`chunks: current eat; completions and tool calls`(
152
+ { input: [], output: [cells], provenance: "source" },
153
+ function () {
154
+ return this.configuration.chunks;
155
+ },
156
+ ),
157
+
158
+ "tokens/used": symbol.rosetta`tokens/used: occupancy of a transcript`(
159
+ // Gauge diff at the slice boundary — never a per-cell sum of the
160
+ // window gauge (transcript.ts preamble).
161
+ { input: [transcript], output: [sz.number], provenance: "pipe" },
162
+ (t: Transcript) => t.tokensUsed(),
163
+ ),
164
+ "tokens/total": symbol.rosetta`tokens/total: cap attached to a transcript`(
165
+ { input: [transcript], output: [sz.number], provenance: "pipe" },
166
+ (t: Transcript) => t.cap,
167
+ ),
168
+ "tokens/count": symbol.rosetta`tokens/count: raw content meter of a cell or list`(
169
+ { input: [transcript], output: [sz.number], provenance: "pipe" },
170
+ (t: Transcript) => contentTokensOf(t.cells),
171
+ ),
172
+ "chars/count": symbol.rosetta`chars/count: JSON-char meter (Gemini)`(
173
+ { input: [transcript], output: [sz.number], provenance: "pipe" },
174
+ (t: Transcript) => charsOf(t.cells),
175
+ ),
176
+ "rounds/count": symbol.rosetta`rounds/count: ReACT rounds in a transcript`(
177
+ { input: [transcript], output: [sz.number], provenance: "pipe" },
178
+ (t: Transcript) => t.rounds().length,
179
+ ),
180
+ "user-rounds/count": symbol.rosetta`user-rounds/count: rounds whose prompt is nonempty`(
181
+ { input: [transcript], output: [sz.number], provenance: "pipe" },
182
+ (t: Transcript) => t.rounds().filter((round) => round.prompt !== "").length,
183
+ ),
184
+
185
+ "transcript/warm?": symbol.rosetta`transcript/warm?: any completion still in the prompt cache`(
186
+ { input: [transcript], output: [sz.boolean], provenance: "source" },
187
+ (t: Transcript) =>
188
+ t.cells.some((cell) => {
189
+ const pew = tapeCompletion(cell) ?? (cell instanceof CompletionExpectation ? cell : null);
190
+ return pew != null && pew.isWarm;
191
+ }),
192
+ ),
193
+ // Frontier direction: preserve runs frontier → newest, compact runs
194
+ // oldest → frontier. The frontier is preserve's OLDEST round and
195
+ // compact's NEWEST — newer drops the oldest, older drops the newest.
196
+ "transcript/round": symbol.rosetta`transcript/round: frontier as a one-round transcript`(
197
+ { input: [transcript], output: [transcript], provenance: "pipe" },
198
+ (t: Transcript) => t.frontierRound(),
199
+ ),
200
+ "transcript/newer": symbol.rosetta`transcript/newer: preserve without the frontier`(
201
+ { input: [transcript], output: [transcript], provenance: "pipe" },
202
+ (t: Transcript) => t.withoutFrontier(),
203
+ ),
204
+ "transcript/older": symbol.rosetta`transcript/older: compact without the frontier`(
205
+ { input: [transcript], output: [transcript], provenance: "pipe" },
206
+ (t: Transcript) => t.withoutNewest(),
207
+ ),
208
+ "transcript/tools": symbol.rosetta`transcript/tools: flattened tool cells`(
209
+ { input: [transcript], output: [cells], provenance: "pipe" },
210
+ (t: Transcript) => t.cells.filter((cell) => cell instanceof ToolCallJob || cell instanceof ProgrammaticJob),
211
+ ),
212
+ "transcript/cells": symbol.rosetta`transcript/cells: flattened eat cells`(
213
+ { input: [transcript], output: [cells], provenance: "pipe" },
214
+ (t: Transcript) => [...t.cells],
215
+ ),
216
+ "transcript/compaction-results":
217
+ symbol.rosetta`transcript/compaction-results: prior compact check-ins, oldest to newest`(
218
+ { input: [transcript], output: [cells], provenance: "pipe" },
219
+ (t: Transcript) => t.checkInCells(),
220
+ ),
221
+ "transcript/payload": symbol.rosetta`transcript/payload: t without its prior compact cards`(
222
+ { input: [transcript], output: [transcript], provenance: "pipe" },
223
+ (t: Transcript) => t.withoutCheckIns(),
224
+ ),
225
+ "transcript/complete-rounds": symbol.rosetta`transcript/complete-rounds: rounds in t that ended`(
226
+ { input: [transcript], output: [transcript], provenance: "pipe" },
227
+ (t: Transcript) => t.completeRounds(),
228
+ ),
229
+ "transcript/partial-round": symbol.rosetta`transcript/partial-round: unfinished last round, or #f`(
230
+ {
231
+ input: [transcript],
232
+ output: [sz.union([transcript, sz.nil])],
233
+ provenance: "pipe",
234
+ },
235
+ (t: Transcript) => t.partialRound(),
236
+ ),
237
+ "transcript/concat": symbol.rosetta`transcript/concat: join two transcripts`(
238
+ {
239
+ input: [transcript, transcript],
240
+ output: [transcript],
241
+ provenance: "pipe",
242
+ },
243
+ (a: Transcript, b: Transcript) => a.concat(b),
244
+ ),
245
+ "transcript/empty": symbol.rosetta`transcript/empty: empty transcript`(
246
+ { input: [], output: [transcript], provenance: "pipe" },
247
+ () => Transcript.empty(),
248
+ ),
249
+
250
+ "payload/suffix": symbol.rosetta`payload/suffix: pair-safe last n cells that are not compact check-ins`(
251
+ {
252
+ input: [transcript, sz.number],
253
+ output: [cells],
254
+ provenance: "pipe",
255
+ },
256
+ (t: Transcript, n: number) => payloadSuffix(t, n),
257
+ ),
258
+
259
+ "message/user": symbol.rosetta`message/user: user cell or user-shaped check-in`(
260
+ { input: [sz.string], output: [chunk], provenance: "pipe" },
261
+ (text: string) => new MessageChunk("user", text),
262
+ ),
263
+ "message/assistant": symbol.rosetta`message/assistant: assistant cell or assistant-shaped check-in`(
264
+ { input: [sz.string], output: [chunk], provenance: "pipe" },
265
+ (text: string) => new MessageChunk("assistant", text),
266
+ ),
267
+ "message/ack": symbol.rosetta`message/ack: synthetic turn-taking cell`(
268
+ {
269
+ input: [sz.foldName.pipe(sz.enum(["user", "assistant"])), sz.string],
270
+ output: [chunk],
271
+ provenance: "pipe",
272
+ },
273
+ (role, text: string) => new MessageChunk(role, text, { ack: true }),
274
+ ),
275
+ "message/user?": symbol.rosetta`message/user?: eat cell is user-shaped`(
276
+ { input: [chunk], output: [sz.boolean], provenance: "pipe" },
277
+ (cell: unknown) => typeof cell === "string" || (cell instanceof MessageChunk && cell.role === "user"),
278
+ ),
279
+ "message/assistant?": symbol.rosetta`message/assistant?: eat cell is assistant-shaped`(
280
+ { input: [chunk], output: [sz.boolean], provenance: "pipe" },
281
+ (cell: unknown) => {
282
+ const pew = tapeCompletion(cell) ?? (cell instanceof CompletionExpectation ? cell : null);
283
+ return pew != null || (cell instanceof MessageChunk && cell.role === "assistant");
284
+ },
285
+ ),
286
+ "message/tool?": symbol.rosetta`message/tool?: eat cell is a tool call`(
287
+ { input: [chunk], output: [sz.boolean], provenance: "pipe" },
288
+ (cell: unknown) => cell instanceof ToolCallJob || cell instanceof ProgrammaticJob,
289
+ ),
290
+ "pair-id": symbol.rosetta`pair-id: toolCallId, or #f`(
291
+ { input: [chunk], output: [stringOrFalse], provenance: "pipe" },
292
+ (cell: unknown) => {
293
+ if (cell instanceof ToolCallJob && cell.toolCallId !== "") return cell.toolCallId;
294
+ if (cell instanceof ProgrammaticJob && cell.toolCallId !== "") return cell.toolCallId;
295
+ return false;
296
+ },
297
+ ),
298
+ "tool-name": symbol.rosetta`tool-name: Read / Bash / Shell / …`(
299
+ { input: [chunk], output: [sz.string], provenance: "pipe" },
300
+ (cell: unknown) =>
301
+ cell instanceof ToolCallJob ? cell.name : cell instanceof ProgrammaticJob ? cell.toolName : "",
302
+ ),
303
+ "chunk-text": symbol.rosetta`chunk-text: cell text, or #f`(
304
+ { input: [chunk], output: [stringOrFalse], provenance: "pipe" },
305
+ (cell: unknown) => textOfCell(cell) || false,
306
+ ),
307
+ "message/transcript-as-single-prompt":
308
+ symbol.rosetta`message/transcript-as-single-prompt: paint a transcript as one summarizer blob`(
309
+ { input: [transcript], output: [sz.string], provenance: "pipe" },
310
+ (t: Transcript) =>
311
+ t.cells
312
+ .map(function (cell: EatCell): string {
313
+ if (typeof cell === "string") return cell;
314
+ if (cell instanceof CompactJob) return `user: ${textOfCell(cell)}`;
315
+ if (cell instanceof MessageChunk) return `${cell.role}: ${cell.text}`;
316
+ if (cell instanceof TurnNotificationChunk) return textOfCell(cell);
317
+ if (cell instanceof TurnApprovalNoteChunk) return `user: ${cell.approvalNoteText()}`;
318
+ if (cell instanceof TurnSteerChunk) return `user: ${cell.completionMessage}`;
319
+ if (cell instanceof ToolCallJob) {
320
+ const scheme = cell.children.find(
321
+ (child): child is ProgrammaticJob => child instanceof ProgrammaticJob,
322
+ );
323
+ if (scheme !== undefined) {
324
+ return `[scheme ${scheme.toolCallId}]\n${scheme.expr}\n${scheme.resultText()}`.trim();
325
+ }
326
+ const painted = toolCallRepresentation(cell);
327
+ return `[${painted.name} ${painted.id}]\n${painted.args}\n${painted.result}`.trim();
328
+ }
329
+ if (cell instanceof ProgrammaticJob) {
330
+ return `[scheme ${cell.toolCallId}]\n${cell.expr}\n${cell.resultText()}`.trim();
331
+ }
332
+ const pew = tapeCompletion(cell) ?? (cell instanceof CompletionExpectation ? cell : null);
333
+ if (pew == null) return "";
334
+ const parts: string[] = [];
335
+ if (pew.userPrompt !== "") parts.push(`user: ${pew.userPrompt}`);
336
+ if (pew.responseText !== "") parts.push(`assistant: ${pew.responseText}`);
337
+ return parts.join("\n");
338
+ })
339
+ .filter((line) => line !== "")
340
+ .join("\n\n"),
341
+ ),
342
+
343
+ reason: symbol.rosetta`reason: why classify was invoked; overflow, reactive, context-exceeded`(
344
+ { input: [], output: [sz.foldName], provenance: "source" },
345
+ function () {
346
+ return this.configuration.reason;
347
+ },
348
+ ),
349
+ focus: symbol.rosetta`focus: /compact instructions; empty on auto`(
350
+ { input: [], output: [sz.string], provenance: "source" },
351
+ function () {
352
+ return this.configuration.focus;
353
+ },
354
+ ),
355
+ "kv/get": symbol.rosetta`kv/get: session kv value, or #f`(
356
+ {
357
+ input: [sz.foldName],
358
+ output: [sz.union([sz.string, sz.number, sz.booleanFalse])],
359
+ provenance: "source",
360
+ },
361
+ function (key: string) {
362
+ return kvGet(this.configuration.kv, key);
363
+ },
364
+ ),
365
+ "plan/get": symbol.rosetta`plan/get: working plan text, or #f`(
366
+ { input: [], output: [stringOrFalse], provenance: "source" },
367
+ function () {
368
+ return this.configuration.plan ?? false;
369
+ },
370
+ ),
371
+ };
372
+ },
373
+ prelude: `
374
+ (define (tokens/remaining t)
375
+ (- (tokens/total t) (tokens/used t)))
376
+
377
+ (define (tokens/relative/used t)
378
+ (let ((total (tokens/total t)))
379
+ (if (zero? total) 0 (/ (tokens/used t) total))))
380
+
381
+ (define (tokens/relative/total t)
382
+ 1)
383
+
384
+ (define (tokens/relative/remaining t)
385
+ (- (tokens/relative/total t) (tokens/relative/used t)))
386
+
387
+ (define (cells/count t)
388
+ (length (transcript/cells t)))
389
+
390
+ (define (starts-with s prefix)
391
+ (string-prefix? prefix s))
392
+ `,
393
+ });
394
+
395
+ /**
396
+ * Load the strategy with `execState`, then call `compact` / `forced-compact`
397
+ * as host functions with the prefix and full transcripts as arguments. Empty or
398
+ * non-shrinking cards are typed failures — the kernel's "failed to compact"
399
+ * path is the caller's.
400
+ */
401
+ export async function applyCompact(
402
+ program: string,
403
+ compactTranscript: Transcript,
404
+ fullTranscript: Transcript,
405
+ opts: CompactStrategyOpts = {},
406
+ ): Promise<CompactApplyResult> {
407
+ invariant(program.trim() !== "", "compact strategy program is empty");
408
+ const names = opts.forced === true ? (["forced-compact", "compact"] as const) : (["compact"] as const);
409
+ // `(require "compact.prompt")` — the threaded-snapshot strategy is a
410
+ // `.scm` + `.prompt` pair. `require` is a loader symbol, and the loader is
411
+ // jailed under the strategy file's directory (same rule as scheme_repl:
412
+ // `programSourceLoader`). Without these the strategy throws
413
+ // `Unbound variable require` and the compact silently degrades to the fallback.
414
+ const moduleRoot = opts.sourcePath != null && opts.sourcePath !== "" ? dirname(opts.sourcePath) : undefined;
415
+ // `require` only needs the loader. The isolated host is assembled when a
416
+ // parent job and a connection exist; without them a stub strategy still
417
+ // loads, and `dotprompt/run` is not a symbol.
418
+ const connectionId = opts.connectionId ?? "";
419
+ const isolated = moduleRoot != null && opts.job != null && connectionId !== "";
420
+ const state = await execState(program, {
421
+ capabilities:
422
+ moduleRoot == null
423
+ ? [compactReducerCapability]
424
+ : isolated
425
+ ? [compactReducerCapability, promptIsolatedCapability, schemeLoadAliasCapability]
426
+ : [compactReducerCapability, schemeLoadAliasCapability],
427
+ config: {
428
+ ...compactConfig(opts, fullTranscript.cells),
429
+ ...(moduleRoot == null
430
+ ? {}
431
+ : {
432
+ loader: programSourceLoader(moduleRoot),
433
+ dirname: "",
434
+ ...(isolated
435
+ ? {
436
+ job: opts.job,
437
+ connectionId,
438
+ completionModel: opts.completionModel ?? "",
439
+ }
440
+ : {}),
441
+ }),
442
+ },
443
+ });
444
+ const fn = names.map((name) => state.scope.lookup(name)).find(is_callable_value);
445
+ invariant(fn !== undefined, `compact strategy: program does not define ${names.join(" or ")}`);
446
+ const raw = await abortable(() => callLoaded(state.runCtx, fn, compactTranscript, fullTranscript), opts);
447
+ if (!Array.isArray(raw) || raw.length === 0) {
448
+ return { ok: false, reason: "empty-check-in" };
449
+ }
450
+ const checkIn = raw
451
+ .filter((cell): cell is EatCell => isEatCellValue(cell))
452
+ .map((cell) => (cell instanceof MessageChunk ? cell.asCheckIn() : cell));
453
+ if (checkIn.length === 0) return { ok: false, reason: "empty-check-in" };
454
+ // Prefix occupancy is the gauge; the check-in is fresh content with no gauge.
455
+ const prefixTokens = compactTranscript.tokensUsed();
456
+ const checkInTokens = contentTokensOf(checkIn);
457
+ if (checkInTokens >= prefixTokens) return { ok: false, reason: "did-not-shrink" };
458
+ return { ok: true, checkIn };
459
+ }
460
+
461
+ /**
462
+ * The strategy cannot be interrupted mid-eval, so abort stops waiting for it.
463
+ * A summarizer round it opened joins the abort; the driver owns that round.
464
+ */
465
+ async function abortable(run: () => Promise<unknown>, opts: CompactStrategyOpts): Promise<unknown> {
466
+ const signal = opts.signal;
467
+ if (signal == null) return run();
468
+ const before = new Set(opts.job?.children);
469
+ const pending = run();
470
+ let onAbort = (): void => {};
471
+ const aborted = new Promise<never>((_resolve, reject) => {
472
+ onAbort = () => {
473
+ for (const child of opts.job?.children ?? []) {
474
+ if (before.has(child) || !(child instanceof CompletionJob)) continue;
475
+ const round = child.expectation;
476
+ if (round == null || round.isDone) continue;
477
+ void opts.complete?.({ connectionId: round.connectionId, model: round.model, round, signal }).catch(() => {});
478
+ }
479
+ reject(signal.reason);
480
+ };
481
+ if (signal.aborted) onAbort();
482
+ else signal.addEventListener("abort", onAbort, { once: true });
483
+ });
484
+ try {
485
+ return await Promise.race([pending, aborted]);
486
+ } finally {
487
+ signal.removeEventListener("abort", onAbort);
488
+ }
489
+ }
490
+
491
+ function isEatCellValue(value: unknown): value is EatCell {
492
+ return (
493
+ typeof value === "string" ||
494
+ value instanceof CompactJob ||
495
+ value instanceof CompletionExpectation ||
496
+ value instanceof CompletionJob ||
497
+ value instanceof ToolCallJob ||
498
+ value instanceof ProgrammaticJob ||
499
+ value instanceof TurnNotificationChunk ||
500
+ value instanceof TurnApprovalNoteChunk ||
501
+ value instanceof TurnSteerChunk ||
502
+ value instanceof MessageChunk
503
+ );
504
+ }
505
+
506
+ function kvGet(kv: Record<string, string>, key: string): string | number | false {
507
+ const folded = key.startsWith(":") ? key.slice(1) : key;
508
+ const raw = kv[folded] ?? kv[key] ?? kv[`:${folded}`];
509
+ if (raw === undefined) return false;
510
+ if (folded === "compact.keep-tokens" || folded === "compact.keep-turns") {
511
+ const n = Number(raw);
512
+ return Number.isFinite(n) ? n : raw;
513
+ }
514
+ return raw;
515
+ }
516
+
517
+ function compactConfig(opts: CompactStrategyOpts, cells: readonly EatCell[]): CompactConfig {
518
+ return {
519
+ chunks: [...cells],
520
+ reason: opts.reason ?? "overflow",
521
+ focus: opts.focus ?? "",
522
+ kv: { ...(opts.kv ?? {}) },
523
+ plan: opts.plan ?? null,
524
+ };
525
+ }
526
+
527
+ async function callLoaded(
528
+ runCtx: RunContext,
529
+ fn: Parameters<typeof applyCallback>[0],
530
+ compactTranscript: Transcript,
531
+ fullTranscript: Transcript,
532
+ ): Promise<unknown> {
533
+ const boxed = await applyCallback(
534
+ fn,
535
+ [jsToScheme(runCtx, compactTranscript) as SchemeValue, jsToScheme(runCtx, fullTranscript) as SchemeValue],
536
+ makeCallCtx(runCtx),
537
+ );
538
+ return toJS(boxed);
539
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Compact is the unique well-known session action. Obligatory.
3
+ * Calculations (floors, occupancy, reject, zones) live in harness-context.
4
+ * `applyCompact` is the executing actor in this package.
5
+ *
6
+ * `loadCompactProgram` reads a strategy path through an optional resolve
7
+ * host. Composition supplies package defaults, and the shipped default is a
8
+ * real strategy (a model-written state snapshot, `compact.scm` +
9
+ * `compact.prompt` in the harness package). The inline keep-first program is
10
+ * a FAILSAFE, nothing more: it runs only when the configured program and the
11
+ * shipped default are both missing or unreadable, so that a compact never
12
+ * has nothing to run. It is not a strategy. A program that is returned with
13
+ * `failsafe` set tells the caller to log a diagnostic.
14
+ */
15
+
16
+ const INLINE_KEEP_FIRST =
17
+ "(define (compact compact-transcript full-transcript) (let ((xs (transcript/cells compact-transcript))) (if (null? xs) xs (list (car xs)))))";
18
+
19
+ export type CompactProgram = {
20
+ readonly source: string;
21
+ /**
22
+ * Absolute path the source was read from. `require` (`.prompt` pretreats) is
23
+ * jailed under its dirname; absent for the inline fallback and a dist stub
24
+ * that has no `compact.prompt` next to it.
25
+ */
26
+ readonly sourcePath: string | null;
27
+ /**
28
+ * Set only on the inline keep-first failsafe: why no real program could be
29
+ * loaded. The caller must surface it; a silent failsafe would hide a broken
30
+ * install behind a compaction that drops almost everything.
31
+ */
32
+ readonly failsafe?: string;
33
+ };
34
+
35
+ export type CompactStrategyResolve = {
36
+ resolvePath(spec: string, kernelFsRoot: string): string;
37
+ defaultSource(): string;
38
+ packageStrategyPath(): string | null;
39
+ readFile(path: string): string;
40
+ };
41
+
42
+ /**
43
+ * The loaded strategy. `source` is what `applyCompact` executes; `sourcePath`
44
+ * is the module root for `(require …)` — a strategy ships its `compact.prompt`
45
+ * next to its `.scm`, and `require` is jailed to that directory.
46
+ */
47
+ export function loadCompactProgram(
48
+ spec?: string,
49
+ kernelFsRoot?: string,
50
+ resolve?: CompactStrategyResolve,
51
+ ): CompactProgram {
52
+ const failsafe = (reason: string): CompactProgram => ({
53
+ source: INLINE_KEEP_FIRST,
54
+ sourcePath: null,
55
+ failsafe: reason,
56
+ });
57
+ if (resolve == null) return failsafe("no strategy resolver");
58
+ let configured: string | null = null;
59
+ try {
60
+ if (spec != null && spec.trim() !== "" && kernelFsRoot != null && kernelFsRoot !== "") {
61
+ const path = resolve.resolvePath(spec, kernelFsRoot);
62
+ const loaded = identityOrSource(resolve.readFile(path));
63
+ if (loaded !== "") return { source: loaded, sourcePath: path };
64
+ configured = `configured strategy ${path} is empty`;
65
+ }
66
+ } catch (error) {
67
+ configured = `configured strategy ${spec ?? ""} could not be read: ${errorText(error)}`;
68
+ }
69
+ try {
70
+ const source = identityOrSource(resolve.defaultSource());
71
+ if (source !== "") return { source, sourcePath: resolve.packageStrategyPath() };
72
+ return failsafe(`${configured ?? "no strategy configured"}; the shipped default program is empty`);
73
+ } catch (error) {
74
+ return failsafe(
75
+ `${configured ?? "no strategy configured"}; the shipped default program could not be read: ${errorText(error)}`,
76
+ );
77
+ }
78
+ }
79
+
80
+ function errorText(error: unknown): string {
81
+ return error instanceof Error ? error.message : String(error);
82
+ }
83
+
84
+ function identityOrSource(source: string): string {
85
+ const code = source
86
+ .split("\n")
87
+ .filter((line) => {
88
+ const trimmed = line.trim();
89
+ return trimmed !== "" && !trimmed.startsWith(";;");
90
+ })
91
+ .join("\n")
92
+ .trim();
93
+ if (code === "" || code === "()") return "";
94
+ return source;
95
+ }
@@ -0,0 +1,18 @@
1
+ export { type SessionCommandHost } from "./types.js";
2
+ export { failLeftoverApprovables, turnLoopStopped } from "./cancel.js";
3
+ export { abortParkableWork, dispatchedMcpSettled, liveEvalWork, mcpJobOnWire } from "./park.js";
4
+ export { admitCompact, admitTurn, cancelTurnCommand, drainPromptQueue, retryTurn, rewindTurn } from "./commands.js";
5
+ export { type CompactProgram, type CompactStrategyResolve, loadCompactProgram } from "./compact.js";
6
+ export { applyCompact, compactReducerCapability } from "./compact-env.js";
7
+ export { type CompactHost, runCompactJob } from "./compact-actor.js";
8
+ export {
9
+ CONTEXT_NEED_REMINDER,
10
+ joinSystemNotes,
11
+ quietRoundsSince,
12
+ sealedActorRounds,
13
+ SIDE_CHANNEL_REMINDER_EVERY,
14
+ sideChannelReminderDue,
15
+ sideChannelReminderNote,
16
+ TITLE_UPDATE_REMINDER,
17
+ type SideChannelRound,
18
+ } from "./side-channel-reminder.js";