@intentic/sandbox-contract 1.331.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 (332) 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 +11 -0
  5. package/dist/contracts/accounts.contract.d.ts.map +1 -1
  6. package/dist/contracts/agent.contract.d.ts +37 -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 +103 -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 +72 -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 +17 -0
  41. package/dist/contracts/system.contract.d.ts.map +1 -1
  42. package/dist/contracts/translator.contract.d.ts +4 -0
  43. package/dist/contracts/translator.contract.d.ts.map +1 -1
  44. package/dist/events/agent-events.d.ts +46 -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 +2 -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 +313 -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 +25 -18
  83. package/dist/models/model-roles.d.ts.map +1 -1
  84. package/dist/models/model-roles.js +23 -27
  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 +3 -1
  91. package/dist/models/plan-pools.d.ts.map +1 -1
  92. package/dist/models/plan-pools.js +18 -6
  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 +32 -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/git/git-scope.d.ts +20 -0
  156. package/dist/schemas/git/git-scope.d.ts.map +1 -0
  157. package/dist/schemas/git/git-scope.js +37 -0
  158. package/dist/schemas/git/git-scope.js.map +1 -0
  159. package/dist/schemas/git/git.d.ts +1 -0
  160. package/dist/schemas/git/git.d.ts.map +1 -1
  161. package/dist/schemas/git/git.js +9 -1
  162. package/dist/schemas/git/git.js.map +1 -1
  163. package/dist/schemas/hosts.d.ts +42 -0
  164. package/dist/schemas/hosts.d.ts.map +1 -1
  165. package/dist/schemas/hosts.js +3 -0
  166. package/dist/schemas/hosts.js.map +1 -1
  167. package/dist/schemas/keep-warm.d.ts +1 -0
  168. package/dist/schemas/keep-warm.d.ts.map +1 -1
  169. package/dist/schemas/keep-warm.js +1 -1
  170. package/dist/schemas/keep-warm.js.map +1 -1
  171. package/dist/schemas/logs.d.ts +21 -0
  172. package/dist/schemas/logs.d.ts.map +1 -1
  173. package/dist/schemas/logs.js +19 -0
  174. package/dist/schemas/logs.js.map +1 -1
  175. package/dist/schemas/metrics.d.ts +12 -0
  176. package/dist/schemas/metrics.d.ts.map +1 -1
  177. package/dist/schemas/metrics.js +26 -8
  178. package/dist/schemas/metrics.js.map +1 -1
  179. package/dist/schemas/needs.d.ts +19 -0
  180. package/dist/schemas/needs.d.ts.map +1 -1
  181. package/dist/schemas/needs.js +17 -3
  182. package/dist/schemas/needs.js.map +1 -1
  183. package/dist/schemas/offload.d.ts +154 -0
  184. package/dist/schemas/offload.d.ts.map +1 -1
  185. package/dist/schemas/offload.js +34 -0
  186. package/dist/schemas/offload.js.map +1 -1
  187. package/dist/schemas/privacy.d.ts +19 -0
  188. package/dist/schemas/privacy.d.ts.map +1 -1
  189. package/dist/schemas/privacy.js +17 -2
  190. package/dist/schemas/privacy.js.map +1 -1
  191. package/dist/schemas/project-bring-back.d.ts +172 -0
  192. package/dist/schemas/project-bring-back.d.ts.map +1 -0
  193. package/dist/schemas/project-bring-back.js +39 -0
  194. package/dist/schemas/project-bring-back.js.map +1 -0
  195. package/dist/schemas/providers/handoff.d.ts +77 -0
  196. package/dist/schemas/providers/handoff.d.ts.map +1 -0
  197. package/dist/schemas/providers/handoff.js +85 -0
  198. package/dist/schemas/providers/handoff.js.map +1 -0
  199. package/dist/schemas/providers/plan-limits.d.ts +27 -0
  200. package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
  201. package/dist/schemas/providers/plan-limits.js +8 -1
  202. package/dist/schemas/providers/plan-limits.js.map +1 -1
  203. package/dist/schemas/providers/provider-oauth.d.ts +4 -0
  204. package/dist/schemas/providers/provider-oauth.d.ts.map +1 -1
  205. package/dist/schemas/providers/usage.d.ts +3 -1
  206. package/dist/schemas/providers/usage.d.ts.map +1 -1
  207. package/dist/schemas/providers/usage.js +7 -2
  208. package/dist/schemas/providers/usage.js.map +1 -1
  209. package/dist/schemas/run-targets.d.ts.map +1 -1
  210. package/dist/schemas/run-targets.js +5 -2
  211. package/dist/schemas/run-targets.js.map +1 -1
  212. package/dist/schemas/settings-history.d.ts +1 -1
  213. package/dist/schemas/settings-history.d.ts.map +1 -1
  214. package/dist/schemas/settings-history.js +21 -0
  215. package/dist/schemas/settings-history.js.map +1 -1
  216. package/dist/schemas/settings.d.ts +67 -100
  217. package/dist/schemas/settings.d.ts.map +1 -1
  218. package/dist/schemas/settings.js +41 -24
  219. package/dist/schemas/settings.js.map +1 -1
  220. package/dist/schemas/short-codes.d.ts +31 -0
  221. package/dist/schemas/short-codes.d.ts.map +1 -0
  222. package/dist/schemas/short-codes.js +28 -0
  223. package/dist/schemas/short-codes.js.map +1 -0
  224. package/dist/schemas/system.d.ts +130 -0
  225. package/dist/schemas/system.d.ts.map +1 -1
  226. package/dist/schemas/system.js +37 -1
  227. package/dist/schemas/system.js.map +1 -1
  228. package/dist/state/definition.d.ts +64 -40
  229. package/dist/state/definition.d.ts.map +1 -1
  230. package/dist/state/history-state.d.ts.map +1 -1
  231. package/dist/state/history-state.js +1 -0
  232. package/dist/state/history-state.js.map +1 -1
  233. package/dist/state/runtime-state.d.ts +3 -0
  234. package/dist/state/runtime-state.d.ts.map +1 -1
  235. package/dist/state/runtime-state.js +1 -0
  236. package/dist/state/runtime-state.js.map +1 -1
  237. package/dist/state/versions.d.ts +6 -0
  238. package/dist/state/versions.d.ts.map +1 -1
  239. package/dist/state/versions.js +47 -0
  240. package/dist/state/versions.js.map +1 -1
  241. package/dist/text/embed.d.ts +2 -1
  242. package/dist/text/embed.d.ts.map +1 -1
  243. package/dist/text/embed.js +10 -5
  244. package/dist/text/embed.js.map +1 -1
  245. package/dist/text/title.d.ts.map +1 -1
  246. package/dist/text/title.js +4 -7
  247. package/dist/text/title.js.map +1 -1
  248. package/dist/text/transcript-fold.d.ts +2 -1
  249. package/dist/text/transcript-fold.d.ts.map +1 -1
  250. package/dist/text/transcript-fold.js +21 -6
  251. package/dist/text/transcript-fold.js.map +1 -1
  252. package/package.json +28 -6
  253. package/src/chores/probes.ts +3 -1
  254. package/src/contracts/agent.contract.ts +24 -3
  255. package/src/contracts/offload.contract.ts +21 -1
  256. package/src/contracts/privacy.contract.ts +13 -0
  257. package/src/contracts/settings.contract.ts +23 -0
  258. package/src/events/agent-events.ts +13 -0
  259. package/src/events/agent-words.ts +2 -3
  260. package/src/events/resume.test.ts +14 -2
  261. package/src/events/resume.ts +32 -3
  262. package/src/events/sandbox-notice.ts +31 -1
  263. package/src/events/transcript.ts +27 -2
  264. package/src/index.ts +5 -0
  265. package/src/models/agent-catalog.test.ts +4 -3
  266. package/src/models/agent-catalog.ts +10 -7
  267. package/src/models/agent-runtimes.ts +30 -1
  268. package/src/models/capability-ledger.test.ts +4 -0
  269. package/src/models/model-order.ts +13 -0
  270. package/src/models/model-pins.ts +5 -4
  271. package/src/models/model-roles.test.ts +9 -31
  272. package/src/models/model-roles.ts +35 -50
  273. package/src/models/model-setups.test.ts +100 -0
  274. package/src/models/model-setups.ts +151 -0
  275. package/src/models/plan-pools.test.ts +58 -0
  276. package/src/models/plan-pools.ts +38 -7
  277. package/src/models/provider-specs.ts +20 -2
  278. package/src/policy/command-classes.ts +34 -3
  279. package/src/policy/credential-material.ts +58 -16
  280. package/src/policy/search-globs.ts +135 -0
  281. package/src/policy/secret-hosts.ts +7 -0
  282. package/src/policy/tests/command-classes.test.ts +35 -0
  283. package/src/policy/tests/credential-material.test.ts +20 -0
  284. package/src/policy/tests/search-globs.test.ts +46 -1
  285. package/src/policy/tests/secret-hosts.test.ts +17 -1
  286. package/src/policy/turned-away.ts +2 -0
  287. package/src/protocol/doors/runner-protocol.ts +14 -1
  288. package/src/protocol/pairing-redeem.test.ts +86 -0
  289. package/src/protocol/pairing-redeem.ts +79 -0
  290. package/src/protocol/peer-mcp-server.ts +1 -2
  291. package/src/protocol/raw/raw-json-routes.ts +3 -0
  292. package/src/protocol/raw/raw-routes.ts +5 -0
  293. package/src/protocol/sse.test.ts +50 -1
  294. package/src/protocol/sse.ts +172 -26
  295. package/src/schemas/agent.ts +23 -2
  296. package/src/schemas/automations.ts +6 -2
  297. package/src/schemas/daemon-health.test.ts +28 -0
  298. package/src/schemas/devices.ts +11 -2
  299. package/src/schemas/engines.ts +1 -1
  300. package/src/schemas/git/git-scope.test.ts +91 -0
  301. package/src/schemas/git/git-scope.ts +71 -0
  302. package/src/schemas/git/git.ts +15 -1
  303. package/src/schemas/hosts.ts +7 -0
  304. package/src/schemas/keep-warm.ts +2 -1
  305. package/src/schemas/logs.ts +25 -0
  306. package/src/schemas/metrics.test.ts +80 -0
  307. package/src/schemas/metrics.ts +31 -9
  308. package/src/schemas/needs.ts +29 -3
  309. package/src/schemas/offload.ts +76 -0
  310. package/src/schemas/privacy.ts +27 -3
  311. package/src/schemas/project-bring-back.test.ts +15 -0
  312. package/src/schemas/project-bring-back.ts +78 -0
  313. package/src/schemas/providers/handoff.test.ts +42 -0
  314. package/src/schemas/providers/handoff.ts +128 -0
  315. package/src/schemas/providers/plan-limits.ts +14 -1
  316. package/src/schemas/providers/usage.ts +13 -7
  317. package/src/schemas/run-targets.ts +5 -2
  318. package/src/schemas/settings-history.ts +28 -0
  319. package/src/schemas/settings.test.ts +74 -21
  320. package/src/schemas/settings.ts +74 -44
  321. package/src/schemas/short-codes.ts +48 -0
  322. package/src/schemas/system.ts +63 -1
  323. package/src/state/history-state.ts +4 -0
  324. package/src/state/runtime-state.test.ts +4 -0
  325. package/src/state/runtime-state.ts +4 -0
  326. package/src/state/versions.test.ts +30 -1
  327. package/src/state/versions.ts +68 -0
  328. package/src/text/embed.test.ts +14 -0
  329. package/src/text/embed.ts +19 -10
  330. package/src/text/title.ts +5 -9
  331. package/src/text/transcript-fold.test.ts +61 -0
  332. package/src/text/transcript-fold.ts +31 -6
@@ -58,7 +58,9 @@ const asString = (value: unknown): string | undefined => (typeof value === `stri
58
58
  const countOf = (value: unknown): number => (Array.isArray(value) ? value.length : 0);
59
59
 
60
60
  // Leading-integer parts of a version, compared positionally rather than via full semver; prerelease/build metadata
61
- // can't change which position differs first.
61
+ // can't change which position differs first. Not the contract's compareVersions: this asks which part an upgrade steps
62
+ // (major, minor or patch), not which version is newer, and it reads whatever pnpm prints, `1.2.3(peer@1)` included,
63
+ // where the semver reader would refuse the whole string.
62
64
  const versionParts = (version: string): number[] =>
63
65
  version
64
66
  .replace(/^[^\d]*/, ``)
@@ -2,7 +2,15 @@ import { procedure } from "../protocol/route-meta.js";
2
2
  import { streamOf } from "../protocol/routes.js";
3
3
  import { AgentCommandsQuerySchema, AgentCommandsSchema } from "../events/requests.js";
4
4
  import { AttachFrameSchema } from "../events/agent-events.js";
5
- import { AgentRunSchema, AttachTurnSchema, ConversationQueueSchema, MessageReceiptSchema, QueueResumedSchema, StartedTurnSchema } from "../schemas/agent.js";
5
+ import {
6
+ AgentRunSchema,
7
+ AttachTurnSchema,
8
+ ConversationQueueSchema,
9
+ MessageReceiptSchema,
10
+ QueueResumedSchema,
11
+ QueueTakenBackSchema,
12
+ StartedTurnSchema,
13
+ } from "../schemas/agent.js";
6
14
  import { ChatRouteAskSchema, ChatRouteSchema } from "../schemas/chat-route.js";
7
15
  import { RewindResultSchema, RewindTurnSchema } from "../schemas/history.js";
8
16
  import {
@@ -19,6 +27,7 @@ import {
19
27
  StopResultSchema,
20
28
  StopTurnSchema,
21
29
  } from "../schemas/providers/plan-limits.js";
30
+ import { ChooseHandoffSchema } from "../schemas/providers/handoff.js";
22
31
  import { OkSchema } from "../schemas/shared.js";
23
32
 
24
33
  // A turn executes as a detached daemon-side run. `run` starts it, or says the message into the running turn, or queues it
@@ -101,11 +110,11 @@ export const agentContract = {
101
110
  path: "/agent/queue/remove",
102
111
  summary: "Take back a waiting message",
103
112
  description:
104
- "Removes a message from the conversation's queue before the agent gets it. Name the revision you read it at: a message somebody reworded since is left alone, so you never take back words you have not seen.",
113
+ "Removes a message from the conversation's queue before the agent gets it. Name the revision you read it at: a message somebody reworded since is left alone, so you never take back words you have not seen. A message that opened its conversation by being scheduled, with nothing else in that conversation since, takes the conversation with it: the answer says so, and the words are the caller's to put back where they were written.",
105
114
  })
106
115
  .meta({ floor: "collaborator", guest: true })
107
116
  .input(QueuedMessageRefSchema)
108
- .output(ConversationQueueSchema),
117
+ .output(QueueTakenBackSchema),
109
118
  queueResume: procedure
110
119
  .route({
111
120
  method: "POST",
@@ -154,6 +163,18 @@ export const agentContract = {
154
163
  .meta({ floor: "collaborator", guest: true })
155
164
  .input(SwitchAccountSchema)
156
165
  .output(AccountSwitchedSchema),
166
+ // NOT_FOUND when no spent allowance holds a turn there, or the hand-off named is not one this turn can take.
167
+ chooseHandoff: procedure
168
+ .route({
169
+ method: "POST",
170
+ path: "/agent/handoff",
171
+ summary: "Choose how a held turn continues",
172
+ description:
173
+ "Records how the turn a spent allowance is holding continues the conversation once it is sent again: carry the session as it is, trim it, or summarise it. Starts nothing: a press, a booked move or the reset sends it, and each uses this pick.",
174
+ })
175
+ .meta({ floor: "collaborator", guest: true })
176
+ .input(ChooseHandoffSchema)
177
+ .output(OkSchema),
157
178
  // CONFLICT while a turn is running; NOT_FOUND when the message has no checkpoint; PRECONDITION_FAILED when the
158
179
  // position no longer holds the message named.
159
180
  rewind: procedure
@@ -1,7 +1,15 @@
1
1
  import { z } from "zod";
2
2
  import { procedure } from "../protocol/route-meta.js";
3
3
  import { streamOf } from "../protocol/routes.js";
4
- import { OffloadFrameSchema, OffloadKindSchema, OffloadRecordSchema, OffloadRunSchema, OffloadTargetSchema } from "../schemas/offload.js";
4
+ import {
5
+ FleetAdviceInputSchema,
6
+ FleetAdviceSchema,
7
+ OffloadFrameSchema,
8
+ OffloadKindSchema,
9
+ OffloadRecordSchema,
10
+ OffloadRunSchema,
11
+ OffloadTargetSchema,
12
+ } from "../schemas/offload.js";
5
13
  import { OkSchema } from "../schemas/shared.js";
6
14
 
7
15
  // The in-sandbox `offload-run` command drives the first three on the agent token: the Bash hook and the check after
@@ -57,4 +65,16 @@ export const offloadContract = {
57
65
  description: "The heavy commands this sandbox sent to runners lately, newest first, with where they ran and how they ended.",
58
66
  })
59
67
  .output(z.object({ runs: z.array(OffloadRecordSchema) })),
68
+ // Read by the composer while a new conversation is still a draft (settings `offload.sessions`); measuring a runner
69
+ // asks it over its link, so a closed composer costs the fleet nothing.
70
+ placement: procedure
71
+ .route({
72
+ method: "GET",
73
+ path: "/offload/placement",
74
+ summary: "Which machine a new session should start on",
75
+ description:
76
+ "How full this sandbox and each runner on your machines are right now, measured the same way on every one, and the runner a new session should start on instead of here when this machine is busy and another has room. Name the draft's provider and harness: a runner it could not sign in from is never suggested.",
77
+ })
78
+ .input(FleetAdviceInputSchema)
79
+ .output(FleetAdviceSchema),
60
80
  };
@@ -2,7 +2,9 @@ import { z } from "zod";
2
2
  import { procedure } from "../protocol/route-meta.js";
3
3
  import {
4
4
  PRIVACY_KNOWN_BATCH_MAX,
5
+ PRIVACY_IMAGE_ID,
5
6
  PrivacyDictionarySchema,
7
+ PrivacyImagePairSchema,
6
8
  PrivacyKnownSourceSchema,
7
9
  PrivacyKnownValueSchema,
8
10
  PrivacyLedgerEntrySchema,
@@ -57,6 +59,17 @@ export const privacyContract = {
57
59
  .meta({ floor: "owner", control: "never", panel: false })
58
60
  .input(PrivacyRevealSchema)
59
61
  .output(z.record(z.string(), z.string())),
62
+ image: procedure
63
+ .route({
64
+ method: "POST",
65
+ path: "/privacy/image",
66
+ summary: "An image before and after masking",
67
+ description:
68
+ "The picture as it was and as the shield painted it before it left, for a log entry's image replacement. The picture before holds the personal data, so only the owner may ask. Only the most recent images are kept; an older one answers not found.",
69
+ })
70
+ .meta({ floor: "owner", control: "never", panel: false })
71
+ .input(z.object({ id: z.string().regex(PRIVACY_IMAGE_ID) }))
72
+ .output(PrivacyImagePairSchema),
60
73
  dictionary: procedure
61
74
  .route({
62
75
  method: "GET",
@@ -8,6 +8,7 @@ import {
8
8
  MemberAudienceSchema,
9
9
  BuiltinPromptTextSchema,
10
10
  FieldNotesStatusSchema,
11
+ GettingStartedSchema,
11
12
  REPO_CHECKS_FILE,
12
13
  RepoChecksAdoptSchema,
13
14
  RepoChecksListSchema,
@@ -142,6 +143,28 @@ export const settingsContract = {
142
143
  .meta({ guest: true, floor: "viewer" })
143
144
  .input(AudienceAnswerSchema)
144
145
  .output(AudienceStateSchema),
146
+ // Like the audience, a person's own and not a sandbox setting: nobody's choice to put the checklist away or pass a
147
+ // step over changes another member's screen.
148
+ gettingStarted: procedure
149
+ .route({
150
+ method: "GET",
151
+ path: "/settings/getting-started",
152
+ summary: "What you said about the getting-started checklist here",
153
+ description:
154
+ "Whether you put the editor's getting-started checklist away on this sandbox, and which of its steps you passed over. Whether a step is done is not kept here: the editor reads that from the sandbox itself.",
155
+ })
156
+ .output(GettingStartedSchema),
157
+ setGettingStarted: procedure
158
+ .route({
159
+ method: "POST",
160
+ path: "/settings/getting-started",
161
+ summary: "Put away or bring back the getting-started checklist",
162
+ description:
163
+ "Replaces what you said about the editor's getting-started checklist on this sandbox: whether it is put away, and which steps you passed over. Other members keep their own.",
164
+ })
165
+ .meta({ floor: "viewer" })
166
+ .input(GettingStartedSchema)
167
+ .output(GettingStartedSchema),
145
168
  adoptRepoChecks: procedure
146
169
  .route({
147
170
  method: "POST",
@@ -10,6 +10,7 @@ import { PromptCacheOpeningSchema, PromptFingerprintSchema } from "../schemas/ke
10
10
  import { AgentReplySchema, UsageWindowSchema } from "../schemas/providers/plan-limits.js";
11
11
  import { SubagentKindSchema, SubagentStatusSchema, SubagentVerificationSchema } from "../schemas/terminal.js";
12
12
  import { RetryLadderSchema } from "../schemas/turn-break.js";
13
+ import { HandedOffSchema, HandoffOfferSchema } from "../schemas/providers/handoff.js";
13
14
  import { AgentCommandSchema, browserHelpRequest, capabilityOfferRequest, CapabilityOutcomeSchema, ContextUsageSchema, credentialOfferRequest, CredentialReceiptSchema, pageAskRequest, PageSchema, paymentOfferRequest, PaymentReceiptSchema, PermissionRequestSchema, PlanRequestSchema, QuestionRequestSchema, terminalHelpRequest, TodoItemSchema, ToolCallContentSchema, ToolCallLocationSchema, ToolCallStatusSchema, ToolKindSchema } from "./requests.js";
14
15
  import { TranscriptPatchSchema, TranscriptRowSchema, TurnNoteSchema } from "./transcript.js";
15
16
  import { agentNoticeEvent, agentStatusEvent } from "./agent-ui.js";
@@ -56,6 +57,8 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
56
57
  // The other half of `preamble`: what a window too small for a full turn left OUT. A note that never rode has no
57
58
  // message to be drawn beside, so this is the only place the reader can learn the turn ran thin.
58
59
  ContextTrimSchema.extend({ kind: z.literal("context_trim") }),
60
+ // How a turn a spent allowance held continued the conversation: the whole session, a trimmed copy, or a summary.
61
+ HandedOffSchema.extend({ kind: z.literal("handoff") }),
59
62
  // The SDK's init handshake; carries the model it actually resolved for the turn.
60
63
  // `prompt` names the parts the cache is keyed on, so a later turn can say which of them changed.
61
64
  z.object({ kind: z.literal("init"), model: z.string(), prompt: PromptFingerprintSchema.optional() }),
@@ -92,6 +95,8 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
92
95
  target: z.string().optional(),
93
96
  locations: z.array(ToolCallLocationSchema).optional(),
94
97
  content: z.array(ToolCallContentSchema).optional(),
98
+ // The call's input as bounded JSON text, for a call with no one-line `target` (TranscriptTool.input).
99
+ input: z.string().optional(),
95
100
  parentToolUseId: z.string().optional(),
96
101
  }),
97
102
  // A later state of a call, correlated by `id`. Status and/or fresh content/locations REPLACE the prior value
@@ -102,6 +107,8 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
102
107
  status: ToolCallStatusSchema.optional(),
103
108
  content: z.array(ToolCallContentSchema).optional(),
104
109
  locations: z.array(ToolCallLocationSchema).optional(),
110
+ // A command's exit code, from a runtime that reports it as a number (TranscriptTool.exitCode).
111
+ exitCode: z.number().int().optional(),
105
112
  }),
106
113
  // The agent started running Bash in its live `agent-<id>` tmux session; one per turn, reused across all of a turn's
107
114
  // commands including subagents'.
@@ -296,6 +303,9 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
296
303
  // The privacy shield is on, the provider is untrusted, and the runtime is one it cannot read by content at
297
304
  // all (an ACP agent, Pi), refused before sending; the words wait for a covered provider.
298
305
  "privacy-unshielded",
306
+ // The turn carries a tool list (an automation's `allowedTools`) and its runtime cannot keep one
307
+ // (AgentCapabilities.toolAllowlist), refused before sending; the words wait for a runtime that can.
308
+ "tools-unenforced",
299
309
  // The protected execution-domain policy is unsupported or unreadable; refused before any model/helper ran.
300
310
  "agent-domain-refused",
301
311
  // The privacy shield found personal data in instructions the runtime loads itself, past every channel it
@@ -361,6 +371,9 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
361
371
  // Set when the owner's policy is already moving this turn to the named account, instead of offering a
362
372
  // press.
363
373
  moving: z.string().optional(),
374
+ // The ways this turn can continue once its cache is cold, the one the sandbox suggests, and the one a
375
+ // person picked (schemas/providers/handoff.ts). Absent where there is nothing to carry.
376
+ handoff: HandoffOfferSchema.optional(),
364
377
  })
365
378
  .optional(),
366
379
  // provider-outage only, while the breaker has tries left: epoch seconds its backoff next lets one through.
@@ -1,3 +1,4 @@
1
+ import { oneLine } from "@intentic/base/one-line";
1
2
  import type { SubagentVerification } from "../schemas/terminal.js";
2
3
  import type { TranscriptAgentWords, TranscriptRow } from "./transcript.js";
3
4
  import { needWakeRow } from "./need-wake.js";
@@ -11,11 +12,9 @@ const CHILD_OPENING = "Report from a subagent you started: ";
11
12
  // What a report opened with before a spawned agent was called a subagent like every other; still in stored prompts.
12
13
  const LEGACY_CHILD_OPENING = "Report from a child agent you started: ";
13
14
 
14
- // The parser reads the first line only, so a title may not break it.
15
- const oneLine = (text: string): string => text.replaceAll(/\s+/gu, " ").trim();
16
-
17
15
  // `sub-x7` or `sub-x7 ("Port the parser")`.
18
16
  const senderOf = (from: string, title: string | undefined): string => {
17
+ // The parser reads the first line only, so a title may not break it.
19
18
  const label = title === undefined ? "" : oneLine(title);
20
19
  return label === "" ? `\`${from}\`` : `\`${from}\` ("${label}")`;
21
20
  };
@@ -1,4 +1,4 @@
1
- import { RESUME_NOTES, resumeDisclosure, resumeNoticeRow, withResumeNote, withoutResumeNote } from "./resume.js";
1
+ import { RESUME_NOTES, resumeDisclosure, resumeNoteWith, resumeNoticeRow, withResumeNote, withoutResumeNote } from "./resume.js";
2
2
 
3
3
  // Wrapping then unwrapping a resume note must return the exact original prompt; a mismatch leaks machine narration into
4
4
  // the user's own words on screen.
@@ -69,11 +69,23 @@ test("a re-run's notice row names its reason beside its words", () => {
69
69
  const disclosure = resumeDisclosure(withResumeNote("ship the parser", RESUME_NOTES.restart));
70
70
  expect(disclosure?.kind === "notice" ? resumeNoticeRow(disclosure) : undefined).toEqual({
71
71
  role: "notice",
72
- text: "The sandbox came back, this turn picked up where it left off.",
72
+ text: "The sandbox came back; background commands and dev servers this turn started have ended; this turn picked up where it left off.",
73
73
  noticeCode: { code: "resumed", params: { reason: "restart" } },
74
74
  });
75
75
  });
76
76
 
77
+ // A restart's re-run also says what the restart cost it; the note stays the prefix, so a prompt from before the tail
78
+ // existed and one carrying it read as the same re-run, and the person's words come back out whole.
79
+ test("a note's tail goes with the note, and a prompt stored without one still reads the same", () => {
80
+ const note = resumeNoteWith(RESUME_NOTES.restart, "The whole container restarted.\n\n/tmp was emptied.");
81
+ expect(note).toBe(`${RESUME_NOTES.restart} The whole container restarted.\n/tmp was emptied.`);
82
+ expect(resumeNoteWith(RESUME_NOTES.restart, undefined)).toBe(RESUME_NOTES.restart);
83
+ const prompt = withResumeNote("ship the parser\n\nand the lexer", note);
84
+ expect(withoutResumeNote(prompt)).toBe("ship the parser\n\nand the lexer");
85
+ expect(resumeDisclosure(prompt)).toEqual(resumeDisclosure(withResumeNote("ship the parser", RESUME_NOTES.restart)));
86
+ expect(withoutResumeNote(withResumeNote("ship the parser", RESUME_NOTES.restart))).toBe("ship the parser");
87
+ });
88
+
77
89
  // A Continue on a turn the sandbox kept nothing of sends the note alone: no words of the person's to strip back out, and
78
90
  // the row it records is the sandbox's line, never a message from them.
79
91
  test("a carried-on turn's note stands alone, leaves no words, and records as a notice", () => {
@@ -17,6 +17,10 @@ export const RESUME_NOTES = {
17
17
  limit: `The model provider's usage allowance ran out while this turn was running, which stopped it, and it has been sent again. ${REPEATED}`,
18
18
  switched:
19
19
  "The model provider's usage allowance ran out while this turn was running, which stopped it, and it has been sent again on a different account, which starts a fresh session. The conversation so far has been carried across above, including the part of the request that was already completed, and the sandbox has measured where the work actually stands (the files changed on this branch, what was verified, what the checklist still holds) in the note headed 'Where the work stands': trust that note over anything recalled, then continue from that point instead of starting over.",
20
+ // The two cold-cache hand-offs a person can pick (schemas/providers/handoff.ts) besides carrying the session whole.
21
+ trimmed: `The model provider's usage allowance ran out while this conversation was running, and this turn has been sent again in a copy of the same session, to save re-reading it: every message and tool call is still here, but the output of older tool calls has been cleared (each says so where it was). Run a command or read a file again if you need what it returned. ${REPEATED}`,
22
+ summarized:
23
+ "The model provider's usage allowance ran out while this conversation was running, and this turn has been sent again in a fresh session, to save re-reading the old one. A summary of the conversation so far is in the note headed 'Summary of the conversation so far', the most recent part of it is carried across above, and the sandbox has measured where the work actually stands in the note headed 'Where the work stands': trust that note over the summary where they differ, then continue from that point instead of starting over.",
20
24
  // `carried` switches account but keeps the session, so REPEATED stays true here, unlike `switched`.
21
25
  carried: `The model provider's usage allowance ran out while this turn was running, which stopped it, and it has been sent again on a different account of the same provider, in this same session: everything you knew is still here. ${REPEATED}`,
22
26
  refused:
@@ -44,15 +48,34 @@ export const RESUME_NOTES = {
44
48
  export const withResumeNote = (prompt: string, note: string): string =>
45
49
  Object.values(RESUME_NOTES).some((known) => prompt.startsWith(known)) ? prompt : `${note}\n\n${prompt}`;
46
50
 
51
+ /**
52
+ * A note with what this one re-run alone can say added in its own paragraph's tail: what the restart cost the turn (its
53
+ * background commands, /tmp, why it happened). The note itself stays the prefix every reader recognises, so a prompt
54
+ * stored before tails existed, and one with a tail, both read as the same re-run. A tail holds no blank line: the first
55
+ * one ends the note.
56
+ */
57
+ export const resumeNoteWith = (note: string, tail: string | undefined): string => {
58
+ const said = tail?.replaceAll(/\n\s*\n/g, "\n").trim();
59
+ return said === undefined || said === "" ? note : `${note} ${said}`;
60
+ };
61
+
47
62
  // The user's own words inside a resumed prompt, note and explanation stripped. Returns the prompt unchanged when it
48
- // isn't a resume, so any attach head can pass through it. A note standing alone (`continued`) leaves no words.
63
+ // isn't a resume, so any attach head can pass through it. A note standing alone (`continued`) leaves no words, and a
64
+ // note's tail (resumeNoteWith) goes with it, up to the blank line that ends it.
49
65
  export const withoutResumeNote = (prompt: string): string => {
50
66
  const note = Object.values(RESUME_NOTES).find((known) => prompt.startsWith(known));
51
67
  if (note === undefined) {
52
68
  return prompt;
53
69
  }
54
70
  const rest = prompt.slice(note.length);
55
- return rest.startsWith("\n\n") ? rest.slice(2) : rest;
71
+ if (rest.startsWith("\n\n")) {
72
+ return rest.slice(2);
73
+ }
74
+ if (!rest.startsWith(" ")) {
75
+ return rest;
76
+ }
77
+ const end = rest.indexOf("\n\n");
78
+ return end === -1 ? "" : rest.slice(end + 2);
56
79
  };
57
80
 
58
81
  export type ResumeReason = keyof typeof RESUME_NOTES;
@@ -69,12 +92,18 @@ const notice = (reason: ResumeNoticeReason, text: string): ResumeDisclosure => (
69
92
  const RESUME_DISCLOSURES: Record<ResumeReason, ResumeDisclosure> = {
70
93
  auth: notice("auth", "Claude sign-in renewed, this turn picked up where it left off."),
71
94
  outage: notice("outage", "The model provider came back, this turn picked up where it left off."),
72
- restart: notice("restart", "The sandbox came back, this turn picked up where it left off."),
95
+ // Says what a restart costs a turn, since the turn re-runs as if nothing changed and finds its servers gone.
96
+ restart: notice(
97
+ "restart",
98
+ "The sandbox came back; background commands and dev servers this turn started have ended; this turn picked up where it left off.",
99
+ ),
73
100
  stopped: notice("stopped", "Sent again after the turn stopped short, picking up where it left off."),
74
101
  // None of these four auto-resumed, a person pressed Continue; `switched`/`carried` name the differing account.
75
102
  limit: notice("limit", "Sent again after the allowance ran out mid-turn, picking up where it left off."),
76
103
  switched: notice("switched", "Sent again on the switched account after the allowance ran out mid-turn, in a fresh session."),
77
104
  carried: notice("carried", "Sent again on the switched account after the allowance ran out mid-turn, carrying the session with it."),
105
+ trimmed: notice("trimmed", "Sent again after the allowance ran out, in a copy of the session with older tool output cleared."),
106
+ summarized: notice("summarized", "Sent again after the allowance ran out, in a fresh session opened with a summary of the conversation."),
78
107
  refused: notice("refused", "Sent again after the allowance refused it: nothing had run."),
79
108
  door: notice("door", "Sent again: the first attempt was turned away before anything ran."),
80
109
  overflow: notice("overflow", "Sent again in a fresh session after the last one outgrew the model's context window."),
@@ -26,7 +26,22 @@ const failure = {
26
26
  };
27
27
 
28
28
  // The re-runs a resumed turn's row can name (events/resume.ts); a reason a reader does not know draws the row's text.
29
- const ResumeNoticeReasonSchema = z.enum(["auth", "outage", "restart", "stopped", "limit", "switched", "carried", "refused", "door", "overflow", "flagged", "continued"]);
29
+ const ResumeNoticeReasonSchema = z.enum([
30
+ "auth",
31
+ "outage",
32
+ "restart",
33
+ "stopped",
34
+ "limit",
35
+ "switched",
36
+ "carried",
37
+ "trimmed",
38
+ "summarized",
39
+ "refused",
40
+ "door",
41
+ "overflow",
42
+ "flagged",
43
+ "continued",
44
+ ]);
30
45
  export type ResumeNoticeReason = z.infer<typeof ResumeNoticeReasonSchema>;
31
46
 
32
47
  // Every code this build writes, one per sentence shape, each with the facts that shape needs.
@@ -66,6 +81,21 @@ export const SandboxNoticeSchema = z.discriminatedUnion("code", [
66
81
  // A turn sent thin for a small window ("16k"): what was left out, and whether the base instructions were swapped too.
67
82
  z.object({ code: z.literal("contextTrim"), params: z.object({ window: z.string(), omitted: z.string().optional(), base: z.boolean() }) }),
68
83
  z.object({ code: z.literal("resumed"), params: z.object({ reason: ResumeNoticeReasonSchema }) }),
84
+ // How a turn a spent allowance held continued the conversation (schemas/providers/handoff.ts): `tokens` the context
85
+ // it started from and `from` the one carrying the session whole would have re-read (both worded, "118k"); `cleared`
86
+ // older tool outputs a trimmed copy stubbed; `model` the one that wrote a summary. `fellBack` names the hand-off that
87
+ // was asked for and could not be built, which then went on with the record's short hand-off instead.
88
+ z.object({
89
+ code: z.literal("handedOff"),
90
+ params: z.object({
91
+ mode: z.enum(["carry", "trim", "summary"]),
92
+ tokens: z.string().optional(),
93
+ from: z.string().optional(),
94
+ cleared: count.optional(),
95
+ model: z.string().optional(),
96
+ fellBack: z.boolean().optional(),
97
+ }),
98
+ }),
69
99
  // An agent's own dependency install starting: into its conversation's own copy (`ownCopy`) or the main tree; the
70
100
  // workspace root (`root`) and up to three project folders named (`projects`, each with its slash, already joined),
71
101
  // and how many `more` were not named.
@@ -5,6 +5,7 @@ import { TurnErrandSchema, TurnSpeakerSchema } from "../schemas/speaker.js";
5
5
  import { SubagentKindSchema, SubagentStatusSchema, SubagentVerificationSchema } from "../schemas/terminal.js";
6
6
  import { NeedSchema } from "../schemas/needs.js";
7
7
  import { HeldEndingSchema, RetryLadderSchema } from "../schemas/turn-break.js";
8
+ import { HandoffOfferSchema } from "../schemas/providers/handoff.js";
8
9
  import { NoticeCodeSchema } from "./sandbox-notice.js";
9
10
  import { TranscriptAgentNoticeSchema } from "./agent-ui.js";
10
11
  import type { ToolCallContent, ToolCallLocation, ToolCallStatus, ToolKind } from "./requests.js";
@@ -134,6 +135,18 @@ export const TranscriptToolSchema: z.ZodType<TranscriptTool> = z.lazy(() =>
134
135
  subagent: TranscriptSubagentSchema.optional().describe(
135
136
  "The helper this call started, as the daemon's registry sees it: what it is, how it is going, what it has spent. What a card can say about a backgrounded child whose result is minutes away.",
136
137
  ),
138
+ input: z
139
+ .string()
140
+ .optional()
141
+ .describe(
142
+ "What the call was given, as JSON text cut to a few kilobytes, on a call whose input names no one-line target: an MCP tool's arguments, a delegation's prompt. Read afterwards to learn what was asked when the card alone cannot say.",
143
+ ),
144
+ startedAt: z.number().optional().describe("When the call was seen, in milliseconds. Absent on calls recorded before cards were stamped."),
145
+ endedAt: z
146
+ .number()
147
+ .optional()
148
+ .describe("When its result arrived, in milliseconds; absent while it runs, and on a call whose result never came."),
149
+ exitCode: z.number().int().optional().describe("A command's exit code, where the runtime reports one as a number rather than in its output."),
137
150
  }),
138
151
  );
139
152
  // The subagent a call started, on that call's card whichever mechanism started it (transcript-fold.ts places it).
@@ -176,6 +189,10 @@ export interface TranscriptTool {
176
189
  nested?: number | undefined;
177
190
  thinking?: string | undefined;
178
191
  subagent?: TranscriptSubagent | undefined;
192
+ input?: string | undefined;
193
+ startedAt?: number | undefined;
194
+ endedAt?: number | undefined;
195
+ exitCode?: number | undefined;
179
196
  }
180
197
 
181
198
  // Every ending that wakes a conversation off a condition watch; composing and parsing the wake is watch-wake.ts.
@@ -260,12 +277,17 @@ export const TranscriptRowSchema = z.object({
260
277
  .describe(
261
278
  "The run that produced this row. Present on everything a turn produced, absent on rows written outside one. A client draws a run's rows over whatever it already holds for that run, which is what this identifies; content cannot, because the last row of a live run keeps growing.",
262
279
  ),
263
- // The turn's send time (user rows only), not settlement; the only row-moment the daemon actually knows.
280
+ // The turn's send time (user rows only), not settlement; the agent's rows carry `openedAt` instead.
264
281
  sentAt: z
282
+ .number()
283
+ .optional()
284
+ .describe("When it was sent, in milliseconds. On the user's rows only; the agent's own rows and notices carry `openedAt`."),
285
+ // Stamped by the daemon as it folds the frame that opens the row, since the frames themselves carry no clock.
286
+ openedAt: z
265
287
  .number()
266
288
  .optional()
267
289
  .describe(
268
- "When it was sent, in milliseconds. On the user's rows only, because that is the only moment actually known: a turn's own frames arrive with no clock, so stamping the agent's rows could only ever mean the whole turn's start or end.",
290
+ "When an assistant bubble or a notice opened, in milliseconds, as the daemon folded the frame that started it. Absent on user rows, which carry `sentAt`, and on rows recorded before rows were stamped.",
269
291
  ),
270
292
  // A position moves under a rewind and a message's words can repeat, so this is the one name a message keeps.
271
293
  messageId: z
@@ -476,6 +498,9 @@ export const TurnEndingSchema = z.object({
476
498
  .describe(
477
499
  "The account the owner's policy is already moving this turn to, when it is; the surface then reports the move rather than offering a press.",
478
500
  ),
501
+ handoff: HandoffOfferSchema.optional().describe(
502
+ "How the held turn can continue once its cache is cold: carry the session, trim it, or summarise it, which one the sandbox suggests, and which one a person picked. Absent where there is nothing to carry.",
503
+ ),
479
504
  })
480
505
  .optional()
481
506
  .describe("Present when the daemon still holds the refused turn whole, so a press re-runs it rather than appending a message after it."),
package/src/index.ts CHANGED
@@ -188,6 +188,7 @@ export * from "./models/model-order.js";
188
188
  export * from "./models/model-offer.js";
189
189
  export * from "./models/model-pins.js";
190
190
  export * from "./models/model-roles.js";
191
+ export * from "./models/model-setups.js";
191
192
  export * from "./models/plan-pools.js";
192
193
  export * from "./text/path-refs.js";
193
194
  export * from "./text/archives.js";
@@ -224,6 +225,7 @@ export * from "./schemas/extension-updates.js";
224
225
  export * from "./schemas/providers/fast-mode.js";
225
226
  export * from "./schemas/diff.js";
226
227
  export * from "./schemas/git/git.js";
228
+ export * from "./schemas/git/git-scope.js";
227
229
  export * from "./schemas/git/git-history.js";
228
230
  export * from "./schemas/history.js";
229
231
  export * from "./schemas/hosts.js";
@@ -242,7 +244,9 @@ export * from "./schemas/panels.js";
242
244
  export * from "./schemas/personas.js";
243
245
  export * from "./schemas/privacy.js";
244
246
  export * from "./schemas/providers/plan-limits.js";
247
+ export * from "./schemas/providers/handoff.js";
245
248
  export * from "./schemas/platform-link.js";
249
+ export * from "./schemas/project-bring-back.js";
246
250
  export * from "./schemas/ports.js";
247
251
  export * from "./schemas/providers/provider-oauth.js";
248
252
  export * from "./schemas/providers/provider-subscriptions.js";
@@ -257,6 +261,7 @@ export * from "./schemas/settings.js";
257
261
  export * from "./schemas/settings-history.js";
258
262
  export * from "./schemas/share.js";
259
263
  export * from "./schemas/shared.js";
264
+ export * from "./schemas/short-codes.js";
260
265
  export * from "./schemas/system-prompt.js";
261
266
  export * from "./schemas/system.js";
262
267
  export * from "./schemas/state-plan.js";
@@ -29,7 +29,7 @@ describe("every provider/harness pair declares what it can do", () => {
29
29
  it.each(pairs)("$provider on the $harness harness", ({ provider, harness }) => {
30
30
  const capabilities = capabilitiesOf(provider, harness);
31
31
 
32
- expect(["claude-code", "codex", "cursor", "opencode", "opencode-gemini", "acp", "pi"]).toContain(capabilities.runtime);
32
+ expect(["claude-code", "codex", "cursor", "opencode", "opencode-gemini", "acp", "devin", "pi"]).toContain(capabilities.runtime);
33
33
  expect(["modes", "plan"]).toContain(capabilities.permissions);
34
34
  expect(["full", "tools", "http", "none"]).toContain(capabilities.mcp);
35
35
  expect(capabilities.execution).toContain("shell");
@@ -163,11 +163,12 @@ test("every axis a record can lack has words for it", () => {
163
163
  rulebook: "none",
164
164
  secrets: "none",
165
165
  privacy: "none",
166
+ toolAllowlist: false,
166
167
  };
167
168
 
168
- // Thirteen disclosable axes, thirteen sentences; `fastMode` is disclosed via fastAllowed, and `warm` (keep-warm, off by
169
+ // Fourteen disclosable axes, fourteen sentences; `fastMode` is disclosed via fastAllowed, and `warm` (keep-warm, off by
169
170
  // default) is no limit on the turn itself.
170
- expect(limitationsOf(nothing)).toHaveLength(13);
171
+ expect(limitationsOf(nothing)).toHaveLength(14);
171
172
  expect(limitationsOf(nothing).join(" ")).not.toContain("fast");
172
173
  });
173
174
 
@@ -106,19 +106,21 @@ export const modesFor = (capabilities: AgentCapabilities): readonly PermissionMo
106
106
  export const clampMode = (mode: PermissionMode, capabilities: AgentCapabilities): PermissionMode =>
107
107
  modesFor(capabilities).includes(mode) ? mode : "bypassPermissions";
108
108
 
109
+ // What each reach of `mcp` leaves out, said once per answer.
110
+ const MCP_LIMITS = {
111
+ none: ["no MCP tools or plugins"],
112
+ http: ["MCP servers only, none of the daemon's own tools, no plugins"],
113
+ tools: ["no plugins"],
114
+ full: [],
115
+ } as const satisfies Record<AgentCapabilities["mcp"], readonly string[]>;
116
+
109
117
  // What this pair cannot do, phrased for the sender; empty means the full ceiling. `fastMode` is deliberately not
110
118
  // disclosed here, since it also depends on the route and model; fastAllowed answers that instead.
111
119
  export const limitationsOf = (capabilities: AgentCapabilities): string[] => [
112
120
  ...(capabilities.permissions === "plan" ? ["no per-tool approvals"] : []),
113
121
  ...(capabilities.questions ? [] : ["no clarifying questions"]),
114
122
  ...(capabilities.steering ? [] : ["no mid-turn steering"]),
115
- ...(capabilities.mcp === "none"
116
- ? ["no MCP tools or plugins"]
117
- : capabilities.mcp === "http"
118
- ? ["MCP servers only, none of the daemon's own tools, no plugins"]
119
- : capabilities.mcp === "tools"
120
- ? ["no plugins"]
121
- : []),
123
+ ...MCP_LIMITS[capabilities.mcp],
122
124
  ...(capabilities.execution.includes("js") ? [] : ["no code runs, shell only"]),
123
125
  ...(capabilities.effort ? [] : ["no effort control"]),
124
126
  ...(capabilities.commands ? [] : ["no slash commands"]),
@@ -131,6 +133,7 @@ export const limitationsOf = (capabilities: AgentCapabilities): string[] => [
131
133
  ...(capabilities.rulebook === "refuse-only" ? ["command rules can refuse but not hold"] : []),
132
134
  ...(capabilities.rulebook === "none" ? ["command rules not applied"] : []),
133
135
  ...(capabilities.secrets === "none" ? ["secrets reach the model unmasked"] : []),
136
+ ...(capabilities.toolAllowlist ? [] : ["no tool lists: an automation limited to some tools won't run here"]),
134
137
  ];
135
138
 
136
139
  // Claude's compile-time model floor, shared by daemon and web pre-load; versioned ids only, never a tier alias.