@intentic/sandbox-contract 1.330.0 → 1.332.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 (337) hide show
  1. package/README.md +15 -0
  2. package/dist/chores/probes.d.ts.map +1 -1
  3. package/dist/chores/probes.js.map +1 -1
  4. package/dist/contracts/accounts.contract.d.ts +15 -0
  5. package/dist/contracts/accounts.contract.d.ts.map +1 -1
  6. package/dist/contracts/agent.contract.d.ts +38 -0
  7. package/dist/contracts/agent.contract.d.ts.map +1 -1
  8. package/dist/contracts/agent.contract.js +14 -3
  9. package/dist/contracts/agent.contract.js.map +1 -1
  10. package/dist/contracts/agents.contract.d.ts +29 -0
  11. package/dist/contracts/agents.contract.d.ts.map +1 -1
  12. package/dist/contracts/automations.contract.d.ts +2 -0
  13. package/dist/contracts/automations.contract.d.ts.map +1 -1
  14. package/dist/contracts/device.contract.d.ts +7 -0
  15. package/dist/contracts/device.contract.d.ts.map +1 -1
  16. package/dist/contracts/git.contract.d.ts +2 -0
  17. package/dist/contracts/git.contract.d.ts.map +1 -1
  18. package/dist/contracts/logs.contract.d.ts +10 -0
  19. package/dist/contracts/logs.contract.d.ts.map +1 -1
  20. package/dist/contracts/needs.contract.d.ts +7 -0
  21. package/dist/contracts/needs.contract.d.ts.map +1 -1
  22. package/dist/contracts/offload.contract.d.ts +70 -0
  23. package/dist/contracts/offload.contract.d.ts.map +1 -1
  24. package/dist/contracts/offload.contract.js +10 -1
  25. package/dist/contracts/offload.contract.js.map +1 -1
  26. package/dist/contracts/privacy.contract.d.ts +14 -0
  27. package/dist/contracts/privacy.contract.d.ts.map +1 -1
  28. package/dist/contracts/privacy.contract.js +11 -1
  29. package/dist/contracts/privacy.contract.js.map +1 -1
  30. package/dist/contracts/providers.contract.d.ts +3 -0
  31. package/dist/contracts/providers.contract.d.ts.map +1 -1
  32. package/dist/contracts/runner.contract.d.ts +106 -2
  33. package/dist/contracts/runner.contract.d.ts.map +1 -1
  34. package/dist/contracts/sessions.contract.d.ts +1 -0
  35. package/dist/contracts/sessions.contract.d.ts.map +1 -1
  36. package/dist/contracts/settings.contract.d.ts +74 -94
  37. package/dist/contracts/settings.contract.d.ts.map +1 -1
  38. package/dist/contracts/settings.contract.js +19 -1
  39. package/dist/contracts/settings.contract.js.map +1 -1
  40. package/dist/contracts/system.contract.d.ts +20 -0
  41. package/dist/contracts/system.contract.d.ts.map +1 -1
  42. package/dist/contracts/translator.contract.d.ts +8 -0
  43. package/dist/contracts/translator.contract.d.ts.map +1 -1
  44. package/dist/events/agent-events.d.ts +47 -0
  45. package/dist/events/agent-events.d.ts.map +1 -1
  46. package/dist/events/agent-events.js +6 -0
  47. package/dist/events/agent-events.js.map +1 -1
  48. package/dist/events/agent-words.d.ts.map +1 -1
  49. package/dist/events/agent-words.js +1 -1
  50. package/dist/events/agent-words.js.map +1 -1
  51. package/dist/events/resume.d.ts +3 -0
  52. package/dist/events/resume.d.ts.map +1 -1
  53. package/dist/events/resume.js +17 -2
  54. package/dist/events/resume.js.map +1 -1
  55. package/dist/events/sandbox-notice.d.ts +18 -0
  56. package/dist/events/sandbox-notice.d.ts.map +1 -1
  57. package/dist/events/sandbox-notice.js +27 -1
  58. package/dist/events/sandbox-notice.js.map +1 -1
  59. package/dist/events/system-events.d.ts +4 -0
  60. package/dist/events/system-events.d.ts.map +1 -1
  61. package/dist/events/transcript.d.ts +64 -0
  62. package/dist/events/transcript.d.ts.map +1 -1
  63. package/dist/events/transcript.js +17 -1
  64. package/dist/events/transcript.js.map +1 -1
  65. package/dist/index.d.ts +327 -124
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +5 -0
  68. package/dist/index.js.map +1 -1
  69. package/dist/models/agent-catalog.d.ts.map +1 -1
  70. package/dist/models/agent-catalog.js +8 -7
  71. package/dist/models/agent-catalog.js.map +1 -1
  72. package/dist/models/agent-runtimes.d.ts +3 -1
  73. package/dist/models/agent-runtimes.d.ts.map +1 -1
  74. package/dist/models/agent-runtimes.js +10 -0
  75. package/dist/models/agent-runtimes.js.map +1 -1
  76. package/dist/models/model-order.d.ts +1 -0
  77. package/dist/models/model-order.d.ts.map +1 -1
  78. package/dist/models/model-order.js +9 -0
  79. package/dist/models/model-order.js.map +1 -1
  80. package/dist/models/model-pins.d.ts.map +1 -1
  81. package/dist/models/model-pins.js.map +1 -1
  82. package/dist/models/model-roles.d.ts +32 -17
  83. package/dist/models/model-roles.d.ts.map +1 -1
  84. package/dist/models/model-roles.js +30 -26
  85. package/dist/models/model-roles.js.map +1 -1
  86. package/dist/models/model-setups.d.ts +46 -0
  87. package/dist/models/model-setups.d.ts.map +1 -0
  88. package/dist/models/model-setups.js +86 -0
  89. package/dist/models/model-setups.js.map +1 -0
  90. package/dist/models/plan-pools.d.ts +5 -1
  91. package/dist/models/plan-pools.d.ts.map +1 -1
  92. package/dist/models/plan-pools.js +41 -13
  93. package/dist/models/plan-pools.js.map +1 -1
  94. package/dist/models/provider-specs.d.ts +20 -0
  95. package/dist/models/provider-specs.d.ts.map +1 -1
  96. package/dist/models/provider-specs.js +13 -1
  97. package/dist/models/provider-specs.js.map +1 -1
  98. package/dist/policy/command-classes.d.ts.map +1 -1
  99. package/dist/policy/command-classes.js +26 -3
  100. package/dist/policy/command-classes.js.map +1 -1
  101. package/dist/policy/credential-material.d.ts +6 -0
  102. package/dist/policy/credential-material.d.ts.map +1 -1
  103. package/dist/policy/credential-material.js +37 -15
  104. package/dist/policy/credential-material.js.map +1 -1
  105. package/dist/policy/search-globs.d.ts +3 -0
  106. package/dist/policy/search-globs.d.ts.map +1 -1
  107. package/dist/policy/search-globs.js +118 -0
  108. package/dist/policy/search-globs.js.map +1 -1
  109. package/dist/policy/secret-hosts.d.ts +1 -0
  110. package/dist/policy/secret-hosts.d.ts.map +1 -1
  111. package/dist/policy/secret-hosts.js +1 -0
  112. package/dist/policy/secret-hosts.js.map +1 -1
  113. package/dist/policy/turned-away.d.ts.map +1 -1
  114. package/dist/policy/turned-away.js +1 -0
  115. package/dist/policy/turned-away.js.map +1 -1
  116. package/dist/protocol/doors/runner-protocol.d.ts +9 -0
  117. package/dist/protocol/doors/runner-protocol.d.ts.map +1 -1
  118. package/dist/protocol/doors/runner-protocol.js +5 -0
  119. package/dist/protocol/doors/runner-protocol.js.map +1 -1
  120. package/dist/protocol/pairing-redeem.d.ts +23 -0
  121. package/dist/protocol/pairing-redeem.d.ts.map +1 -0
  122. package/dist/protocol/pairing-redeem.js +44 -0
  123. package/dist/protocol/pairing-redeem.js.map +1 -0
  124. package/dist/protocol/peer-mcp-server.d.ts.map +1 -1
  125. package/dist/protocol/peer-mcp-server.js +1 -1
  126. package/dist/protocol/peer-mcp-server.js.map +1 -1
  127. package/dist/protocol/raw/raw-json-routes.d.ts +16 -0
  128. package/dist/protocol/raw/raw-json-routes.d.ts.map +1 -1
  129. package/dist/protocol/raw/raw-json-routes.js +2 -0
  130. package/dist/protocol/raw/raw-json-routes.js.map +1 -1
  131. package/dist/protocol/raw/raw-routes.d.ts +8 -0
  132. package/dist/protocol/raw/raw-routes.d.ts.map +1 -1
  133. package/dist/protocol/raw/raw-routes.js +2 -0
  134. package/dist/protocol/raw/raw-routes.js.map +1 -1
  135. package/dist/protocol/sse.d.ts +20 -1
  136. package/dist/protocol/sse.d.ts.map +1 -1
  137. package/dist/protocol/sse.js +112 -17
  138. package/dist/protocol/sse.js.map +1 -1
  139. package/dist/schemas/agent.d.ts +35 -0
  140. package/dist/schemas/agent.d.ts.map +1 -1
  141. package/dist/schemas/agent.js +12 -1
  142. package/dist/schemas/agent.js.map +1 -1
  143. package/dist/schemas/automations.d.ts +6 -0
  144. package/dist/schemas/automations.d.ts.map +1 -1
  145. package/dist/schemas/automations.js +3 -1
  146. package/dist/schemas/automations.js.map +1 -1
  147. package/dist/schemas/devices.d.ts +17 -0
  148. package/dist/schemas/devices.d.ts.map +1 -1
  149. package/dist/schemas/devices.js +2 -1
  150. package/dist/schemas/devices.js.map +1 -1
  151. package/dist/schemas/engines.d.ts +8 -1
  152. package/dist/schemas/engines.d.ts.map +1 -1
  153. package/dist/schemas/engines.js +1 -1
  154. package/dist/schemas/engines.js.map +1 -1
  155. package/dist/schemas/environment.d.ts +30 -0
  156. package/dist/schemas/environment.d.ts.map +1 -1
  157. package/dist/schemas/environment.js +13 -0
  158. package/dist/schemas/environment.js.map +1 -1
  159. package/dist/schemas/git/git-scope.d.ts +20 -0
  160. package/dist/schemas/git/git-scope.d.ts.map +1 -0
  161. package/dist/schemas/git/git-scope.js +37 -0
  162. package/dist/schemas/git/git-scope.js.map +1 -0
  163. package/dist/schemas/git/git.d.ts +1 -0
  164. package/dist/schemas/git/git.d.ts.map +1 -1
  165. package/dist/schemas/git/git.js +9 -1
  166. package/dist/schemas/git/git.js.map +1 -1
  167. package/dist/schemas/hosts.d.ts +42 -0
  168. package/dist/schemas/hosts.d.ts.map +1 -1
  169. package/dist/schemas/hosts.js +3 -0
  170. package/dist/schemas/hosts.js.map +1 -1
  171. package/dist/schemas/keep-warm.d.ts +1 -0
  172. package/dist/schemas/keep-warm.d.ts.map +1 -1
  173. package/dist/schemas/keep-warm.js +1 -1
  174. package/dist/schemas/keep-warm.js.map +1 -1
  175. package/dist/schemas/logs.d.ts +21 -0
  176. package/dist/schemas/logs.d.ts.map +1 -1
  177. package/dist/schemas/logs.js +19 -0
  178. package/dist/schemas/logs.js.map +1 -1
  179. package/dist/schemas/metrics.d.ts +12 -0
  180. package/dist/schemas/metrics.d.ts.map +1 -1
  181. package/dist/schemas/metrics.js +26 -8
  182. package/dist/schemas/metrics.js.map +1 -1
  183. package/dist/schemas/needs.d.ts +19 -0
  184. package/dist/schemas/needs.d.ts.map +1 -1
  185. package/dist/schemas/needs.js +17 -3
  186. package/dist/schemas/needs.js.map +1 -1
  187. package/dist/schemas/offload.d.ts +154 -0
  188. package/dist/schemas/offload.d.ts.map +1 -1
  189. package/dist/schemas/offload.js +34 -0
  190. package/dist/schemas/offload.js.map +1 -1
  191. package/dist/schemas/privacy.d.ts +19 -0
  192. package/dist/schemas/privacy.d.ts.map +1 -1
  193. package/dist/schemas/privacy.js +17 -2
  194. package/dist/schemas/privacy.js.map +1 -1
  195. package/dist/schemas/project-bring-back.d.ts +172 -0
  196. package/dist/schemas/project-bring-back.d.ts.map +1 -0
  197. package/dist/schemas/project-bring-back.js +39 -0
  198. package/dist/schemas/project-bring-back.js.map +1 -0
  199. package/dist/schemas/providers/handoff.d.ts +77 -0
  200. package/dist/schemas/providers/handoff.d.ts.map +1 -0
  201. package/dist/schemas/providers/handoff.js +85 -0
  202. package/dist/schemas/providers/handoff.js.map +1 -0
  203. package/dist/schemas/providers/plan-limits.d.ts +34 -0
  204. package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
  205. package/dist/schemas/providers/plan-limits.js +13 -2
  206. package/dist/schemas/providers/plan-limits.js.map +1 -1
  207. package/dist/schemas/providers/provider-oauth.d.ts +8 -0
  208. package/dist/schemas/providers/provider-oauth.d.ts.map +1 -1
  209. package/dist/schemas/providers/usage.d.ts +3 -1
  210. package/dist/schemas/providers/usage.d.ts.map +1 -1
  211. package/dist/schemas/providers/usage.js +7 -2
  212. package/dist/schemas/providers/usage.js.map +1 -1
  213. package/dist/schemas/run-targets.d.ts.map +1 -1
  214. package/dist/schemas/run-targets.js +5 -2
  215. package/dist/schemas/run-targets.js.map +1 -1
  216. package/dist/schemas/settings-history.d.ts +6 -1
  217. package/dist/schemas/settings-history.d.ts.map +1 -1
  218. package/dist/schemas/settings-history.js +25 -1
  219. package/dist/schemas/settings-history.js.map +1 -1
  220. package/dist/schemas/settings.d.ts +69 -100
  221. package/dist/schemas/settings.d.ts.map +1 -1
  222. package/dist/schemas/settings.js +41 -24
  223. package/dist/schemas/settings.js.map +1 -1
  224. package/dist/schemas/short-codes.d.ts +31 -0
  225. package/dist/schemas/short-codes.d.ts.map +1 -0
  226. package/dist/schemas/short-codes.js +28 -0
  227. package/dist/schemas/short-codes.js.map +1 -0
  228. package/dist/schemas/system.d.ts +130 -0
  229. package/dist/schemas/system.d.ts.map +1 -1
  230. package/dist/schemas/system.js +37 -1
  231. package/dist/schemas/system.js.map +1 -1
  232. package/dist/state/definition.d.ts +64 -40
  233. package/dist/state/definition.d.ts.map +1 -1
  234. package/dist/state/history-state.d.ts.map +1 -1
  235. package/dist/state/history-state.js +1 -0
  236. package/dist/state/history-state.js.map +1 -1
  237. package/dist/state/runtime-state.d.ts +3 -0
  238. package/dist/state/runtime-state.d.ts.map +1 -1
  239. package/dist/state/runtime-state.js +1 -0
  240. package/dist/state/runtime-state.js.map +1 -1
  241. package/dist/state/versions.d.ts +6 -0
  242. package/dist/state/versions.d.ts.map +1 -1
  243. package/dist/state/versions.js +47 -0
  244. package/dist/state/versions.js.map +1 -1
  245. package/dist/text/embed.d.ts +2 -1
  246. package/dist/text/embed.d.ts.map +1 -1
  247. package/dist/text/embed.js +10 -5
  248. package/dist/text/embed.js.map +1 -1
  249. package/dist/text/title.d.ts.map +1 -1
  250. package/dist/text/title.js +4 -7
  251. package/dist/text/title.js.map +1 -1
  252. package/dist/text/transcript-fold.d.ts +2 -1
  253. package/dist/text/transcript-fold.d.ts.map +1 -1
  254. package/dist/text/transcript-fold.js +21 -6
  255. package/dist/text/transcript-fold.js.map +1 -1
  256. package/package.json +28 -6
  257. package/src/chores/probes.ts +3 -1
  258. package/src/contracts/agent.contract.ts +24 -3
  259. package/src/contracts/offload.contract.ts +21 -1
  260. package/src/contracts/privacy.contract.ts +13 -0
  261. package/src/contracts/settings.contract.ts +23 -0
  262. package/src/events/agent-events.ts +13 -0
  263. package/src/events/agent-words.ts +2 -3
  264. package/src/events/resume.test.ts +14 -2
  265. package/src/events/resume.ts +32 -3
  266. package/src/events/sandbox-notice.ts +31 -1
  267. package/src/events/transcript.ts +27 -2
  268. package/src/index.ts +5 -0
  269. package/src/models/agent-catalog.test.ts +4 -3
  270. package/src/models/agent-catalog.ts +10 -7
  271. package/src/models/agent-runtimes.ts +30 -1
  272. package/src/models/capability-ledger.test.ts +4 -0
  273. package/src/models/model-order.ts +13 -0
  274. package/src/models/model-pins.ts +5 -4
  275. package/src/models/model-roles.test.ts +9 -31
  276. package/src/models/model-roles.ts +42 -49
  277. package/src/models/model-setups.test.ts +100 -0
  278. package/src/models/model-setups.ts +151 -0
  279. package/src/models/plan-pools.test.ts +85 -0
  280. package/src/models/plan-pools.ts +76 -17
  281. package/src/models/provider-specs.ts +20 -2
  282. package/src/policy/command-classes.ts +34 -3
  283. package/src/policy/credential-material.ts +58 -16
  284. package/src/policy/search-globs.ts +135 -0
  285. package/src/policy/secret-hosts.ts +7 -0
  286. package/src/policy/tests/command-classes.test.ts +35 -0
  287. package/src/policy/tests/credential-material.test.ts +20 -0
  288. package/src/policy/tests/search-globs.test.ts +46 -1
  289. package/src/policy/tests/secret-hosts.test.ts +17 -1
  290. package/src/policy/turned-away.ts +2 -0
  291. package/src/protocol/doors/runner-protocol.ts +14 -1
  292. package/src/protocol/pairing-redeem.test.ts +86 -0
  293. package/src/protocol/pairing-redeem.ts +79 -0
  294. package/src/protocol/peer-mcp-server.ts +1 -2
  295. package/src/protocol/raw/raw-json-routes.ts +3 -0
  296. package/src/protocol/raw/raw-routes.ts +5 -0
  297. package/src/protocol/sse.test.ts +50 -1
  298. package/src/protocol/sse.ts +172 -26
  299. package/src/schemas/agent.ts +23 -2
  300. package/src/schemas/automations.ts +6 -2
  301. package/src/schemas/daemon-health.test.ts +28 -0
  302. package/src/schemas/devices.ts +11 -2
  303. package/src/schemas/engines.ts +1 -1
  304. package/src/schemas/environment.ts +25 -0
  305. package/src/schemas/git/git-scope.test.ts +91 -0
  306. package/src/schemas/git/git-scope.ts +71 -0
  307. package/src/schemas/git/git.ts +15 -1
  308. package/src/schemas/hosts.ts +7 -0
  309. package/src/schemas/keep-warm.ts +2 -1
  310. package/src/schemas/logs.ts +25 -0
  311. package/src/schemas/metrics.test.ts +80 -0
  312. package/src/schemas/metrics.ts +31 -9
  313. package/src/schemas/needs.ts +29 -3
  314. package/src/schemas/offload.ts +76 -0
  315. package/src/schemas/privacy.ts +27 -3
  316. package/src/schemas/project-bring-back.test.ts +15 -0
  317. package/src/schemas/project-bring-back.ts +78 -0
  318. package/src/schemas/providers/handoff.test.ts +42 -0
  319. package/src/schemas/providers/handoff.ts +128 -0
  320. package/src/schemas/providers/plan-limits.ts +23 -2
  321. package/src/schemas/providers/usage.ts +13 -7
  322. package/src/schemas/run-targets.ts +5 -2
  323. package/src/schemas/settings-history.ts +43 -1
  324. package/src/schemas/settings.test.ts +84 -6
  325. package/src/schemas/settings.ts +74 -44
  326. package/src/schemas/short-codes.ts +48 -0
  327. package/src/schemas/system.ts +63 -1
  328. package/src/state/history-state.ts +4 -0
  329. package/src/state/runtime-state.test.ts +4 -0
  330. package/src/state/runtime-state.ts +4 -0
  331. package/src/state/versions.test.ts +30 -1
  332. package/src/state/versions.ts +68 -0
  333. package/src/text/embed.test.ts +14 -0
  334. package/src/text/embed.ts +19 -10
  335. package/src/text/title.ts +5 -9
  336. package/src/text/transcript-fold.test.ts +61 -0
  337. package/src/text/transcript-fold.ts +31 -6
@@ -1073,3 +1073,64 @@ describe(`the code beside a notice's words`, () => {
1073
1073
  expect(sandboxNoticeOf({ noticeCode: { code: `landConflict`, params: { files: `three` } } })).toBeUndefined();
1074
1074
  });
1075
1075
  });
1076
+
1077
+ // A runtime's frames carry no clock; the live fold stamps what it folds, so a stored record can say when each call ran
1078
+ // and how long it took. A fold without a clock (a replay, these tests above) stamps nothing.
1079
+ describe("TranscriptFold with a clock", () => {
1080
+ const ticking = (): (() => number) => {
1081
+ let now = SENT_AT;
1082
+ return () => (now += 1000);
1083
+ };
1084
+
1085
+ it("stamps each card's start and the arrival of its result, and each assistant and notice row's opening", () => {
1086
+ const fold = new TranscriptFold(openingOf("run"), ticking());
1087
+ for (const event of [
1088
+ { kind: "delta", text: "running" },
1089
+ { kind: "tool_call", id: "t1", name: "Bash", category: "execute", status: "in_progress", target: "pnpm test" },
1090
+ { kind: "tool_call_update", id: "t1", content: [{ type: "text", text: "partial" }] },
1091
+ { kind: "tool_call_update", id: "t1", status: "failed", content: [{ type: "text", text: "1 failed" }], exitCode: 1 },
1092
+ // Re-sending a final status does not move when the result arrived.
1093
+ { kind: "tool_call_update", id: "t1", status: "failed" },
1094
+ { kind: "compact", trigger: "auto" },
1095
+ ] satisfies AgentEvent[]) {
1096
+ fold.apply(event);
1097
+ }
1098
+ const [user, assistant, notice] = fold.rows;
1099
+ expect(user).toEqual(userRow("run", SENT_AT, []));
1100
+ expect(assistant?.openedAt).toBe(SENT_AT + 1000);
1101
+ expect(assistant?.tools).toEqual([
1102
+ {
1103
+ id: "t1",
1104
+ name: "Bash",
1105
+ category: "execute",
1106
+ status: "failed",
1107
+ target: "pnpm test",
1108
+ content: [{ type: "text", text: "1 failed" }],
1109
+ startedAt: SENT_AT + 2000,
1110
+ endedAt: SENT_AT + 3000,
1111
+ exitCode: 1,
1112
+ },
1113
+ ]);
1114
+ expect(notice).toMatchObject({ role: "notice", openedAt: SENT_AT + 4000 });
1115
+ // Every stamp survives the schema a stored record is read back through.
1116
+ expect(fold.rows.map((row) => TranscriptRowSchema.parse(row))).toEqual(fold.rows);
1117
+ });
1118
+
1119
+ it("dates a call that arrives already finished to the moment it was seen, and a nested call like any other", () => {
1120
+ const fold = new TranscriptFold(openingOf("look"), ticking());
1121
+ fold.apply({ kind: "tool_call", id: "a1", name: "Agent", category: "other", status: "in_progress", input: '{"prompt":"look"}' });
1122
+ const [patch] = fold.apply({ kind: "tool_call", id: "r1", name: "Read", category: "read", status: "completed", parentToolUseId: "a1" });
1123
+ expect(patch).toMatchObject({ op: "tool", tool: { id: "r1", startedAt: SENT_AT + 3000, endedAt: SENT_AT + 3000 }, parent: "a1" });
1124
+ expect(fold.rows[1]?.tools?.[0]).toMatchObject({ input: '{"prompt":"look"}', startedAt: SENT_AT + 2000 });
1125
+ });
1126
+
1127
+ it("keeps a daemon-written row's own moment, and never stamps a user's row", () => {
1128
+ const fold = new TranscriptFold(openingOf("go"), ticking());
1129
+ fold.note({ role: "notice", text: "kept", openedAt: 7 });
1130
+ fold.apply({ kind: "steer", text: "and this", sentAt: SENT_AT + 5 });
1131
+ expect(fold.rows.slice(1)).toEqual([
1132
+ { role: "notice", text: "kept", openedAt: 7 },
1133
+ { role: "user", text: "and this", sentAt: SENT_AT + 5 },
1134
+ ]);
1135
+ });
1136
+ });
@@ -13,6 +13,7 @@ import {
13
13
  } from "../events/transcript.js";
14
14
  import { contextTrimLine, contextTrimNotice } from "../schemas/context-trim.js";
15
15
  import { keptWarmLine, keptWarmNotice } from "../schemas/keep-warm.js";
16
+ import { handedOffLine, handedOffNotice } from "../schemas/providers/handoff.js";
16
17
  import { isLandConflict } from "../events/land-conflict.js";
17
18
  import { turnedAwayCode } from "../policy/turned-away.js";
18
19
  import { mentionedPathTokens } from "./mentions.js";
@@ -56,7 +57,12 @@ const resultWords = (tool: TranscriptTool): string =>
56
57
  .map((entry) => entry.text)
57
58
  .join("\n");
58
59
 
59
- const cardOf = (event: Extract<AgentEvent, { kind: "tool_call" }>): TranscriptTool => ({
60
+ // Whether a call's status is final: the moment it turns so is when its result arrived.
61
+ const ended = (status: TranscriptTool["status"]): boolean => status === "completed" || status === "failed";
62
+
63
+ // `at` is the fold's clock reading as the call is seen, absent for a fold without one; a call that arrives already
64
+ // finished started and ended in that same moment, as far as anyone here can tell.
65
+ const cardOf = (event: Extract<AgentEvent, { kind: "tool_call" }>, at: number | undefined): TranscriptTool => ({
60
66
  id: event.id,
61
67
  name: event.name,
62
68
  category: event.category,
@@ -64,6 +70,8 @@ const cardOf = (event: Extract<AgentEvent, { kind: "tool_call" }>): TranscriptTo
64
70
  ...(event.target !== undefined ? { target: event.target } : {}),
65
71
  ...(event.locations !== undefined ? { locations: event.locations } : {}),
66
72
  ...(event.content !== undefined ? { content: event.content } : {}),
73
+ ...(event.input !== undefined ? { input: event.input } : {}),
74
+ ...(at !== undefined ? { startedAt: at, ...(ended(event.status) ? { endedAt: at } : {}) } : {}),
67
75
  });
68
76
 
69
77
  // Whether a just-retired prose bubble says exactly what the plan about to be drawn says, so the request can take its
@@ -339,7 +347,13 @@ export class TranscriptFold {
339
347
  // Set by `retract`: this turn was refused before it ran, and its message went back to the conversation's queue.
340
348
  private unrun = false;
341
349
 
342
- constructor(opening: readonly TranscriptRow[]) {
350
+ // `now`, when given, stamps each card's start and end and each assistant or notice row's opening as the frame that
351
+ // brings it is folded: a runtime's frames carry no clock, so the live fold is the one place these moments are known.
352
+ // A fold replaying frames after the fact is given none, since its own clock would date them all to the replay.
353
+ constructor(
354
+ opening: readonly TranscriptRow[],
355
+ private readonly now?: () => number,
356
+ ) {
343
357
  for (const [index, row] of opening.entries()) {
344
358
  this.rows.push(row);
345
359
  // A run carried on after a restart opens on the cards it had raised, which it raises again: each is found
@@ -387,7 +401,7 @@ export class TranscriptFold {
387
401
  return [];
388
402
  case "tool_call": {
389
403
  const [index, opened] = this.open();
390
- const tool = cardOf(event);
404
+ const tool = cardOf(event, this.now?.());
391
405
  const row = this.rows[index]!;
392
406
  row.tools = [...(row.tools ?? []), tool];
393
407
  this.cards.set(tool.id, { tool, row: index });
@@ -399,6 +413,13 @@ export class TranscriptFold {
399
413
  return this.patchCard(event.id, (tool) => {
400
414
  if (event.status !== undefined) {
401
415
  tool.status = event.status;
416
+ // The first final status is when the result arrived; a later frame re-sending it is not.
417
+ if (ended(event.status) && tool.endedAt === undefined && this.now !== undefined) {
418
+ tool.endedAt = this.now();
419
+ }
420
+ }
421
+ if (event.exitCode !== undefined) {
422
+ tool.exitCode = event.exitCode;
402
423
  }
403
424
  if (event.content !== undefined) {
404
425
  tool.content = event.content;
@@ -460,6 +481,8 @@ export class TranscriptFold {
460
481
  // A row of its own rather than a stamp on the message: what was left out is not part of what was sent,
461
482
  // and the fold beside the message only ever lists notes that actually rode.
462
483
  return this.pushRow({ role: "notice", text: contextTrimLine(event), noticeCode: noticeCode(contextTrimNotice(event)) });
484
+ case "handoff":
485
+ return this.pushRow({ role: "notice", text: handedOffLine(event), noticeCode: noticeCode(handedOffNotice(event)) });
463
486
  case "worktree":
464
487
  return event.sync === undefined ? [] : this.synced(event.sync);
465
488
  case "landed":
@@ -735,7 +758,7 @@ export class TranscriptFold {
735
758
  return [{ op: "toolThinking", index: place.row, id: parent, text: event.text }];
736
759
  }
737
760
  if (event.kind === "tool_call") {
738
- const child = cardOf(event);
761
+ const child = cardOf(event, this.now?.());
739
762
  place.tool.children = [...(place.tool.children ?? []), child];
740
763
  this.cards.set(child.id, { tool: child, row: place.row, parent });
741
764
  this.claimById(child);
@@ -820,7 +843,7 @@ export class TranscriptFold {
820
843
  if (this.bubble !== undefined) {
821
844
  return [this.bubble, []];
822
845
  }
823
- const row: TranscriptRow = { role: "assistant", text: "" };
846
+ const row: TranscriptRow = { role: "assistant", text: "", ...(this.now === undefined ? {} : { openedAt: this.now() }) };
824
847
  this.rows.push(row);
825
848
  this.bubble = this.rows.length - 1;
826
849
  return [this.bubble, [{ op: "append", row: structuredClone(row) }]];
@@ -842,8 +865,10 @@ export class TranscriptFold {
842
865
  }
843
866
 
844
867
  // Pushes a row that is not the open bubble; closes the bubble first so order stays: what came before, then this.
845
- private pushRow(row: TranscriptRow): TranscriptPatch[] {
868
+ // A row other than a user's is stamped with when it landed, unless it came with its own moment.
869
+ private pushRow(given: TranscriptRow): TranscriptPatch[] {
846
870
  const closed = this.closeBubble();
871
+ const row = given.role === "user" || given.openedAt !== undefined || this.now === undefined ? given : { ...given, openedAt: this.now() };
847
872
  this.rows.push(row);
848
873
  return [...closed, { op: "append", row: structuredClone(row) }];
849
874
  }