@intentic/sandbox-contract 1.317.0 → 1.318.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 (333) hide show
  1. package/README.md +12 -4
  2. package/dist/chores/chores.js +1 -1
  3. package/dist/chores/chores.js.map +1 -1
  4. package/dist/chores/probes.js +1 -1
  5. package/dist/chores/probes.js.map +1 -1
  6. package/dist/contracts/accounts.contract.d.ts +75 -0
  7. package/dist/contracts/accounts.contract.d.ts.map +1 -1
  8. package/dist/contracts/accounts.contract.js +10 -1
  9. package/dist/contracts/accounts.contract.js.map +1 -1
  10. package/dist/contracts/agent.contract.d.ts +55 -1
  11. package/dist/contracts/agent.contract.d.ts.map +1 -1
  12. package/dist/contracts/agents.contract.d.ts +796 -93
  13. package/dist/contracts/agents.contract.d.ts.map +1 -1
  14. package/dist/contracts/agents.contract.js +12 -12
  15. package/dist/contracts/agents.contract.js.map +1 -1
  16. package/dist/contracts/ci.contract.d.ts +22 -4
  17. package/dist/contracts/ci.contract.d.ts.map +1 -1
  18. package/dist/contracts/device.contract.d.ts +38 -0
  19. package/dist/contracts/device.contract.d.ts.map +1 -1
  20. package/dist/contracts/device.contract.js +2 -0
  21. package/dist/contracts/device.contract.js.map +1 -1
  22. package/dist/contracts/endpoints.contract.d.ts +23 -0
  23. package/dist/contracts/endpoints.contract.d.ts.map +1 -1
  24. package/dist/contracts/endpoints.contract.js +6 -1
  25. package/dist/contracts/endpoints.contract.js.map +1 -1
  26. package/dist/contracts/extensions.contract.d.ts +12 -0
  27. package/dist/contracts/extensions.contract.d.ts.map +1 -1
  28. package/dist/contracts/needs.contract.d.ts +2 -0
  29. package/dist/contracts/needs.contract.d.ts.map +1 -1
  30. package/dist/contracts/providers.contract.d.ts +4 -0
  31. package/dist/contracts/providers.contract.d.ts.map +1 -1
  32. package/dist/contracts/runner.contract.d.ts +143 -124
  33. package/dist/contracts/runner.contract.d.ts.map +1 -1
  34. package/dist/contracts/secrets.contract.d.ts +37 -0
  35. package/dist/contracts/secrets.contract.d.ts.map +1 -1
  36. package/dist/contracts/secrets.contract.js +20 -1
  37. package/dist/contracts/secrets.contract.js.map +1 -1
  38. package/dist/contracts/sessions.contract.d.ts +4 -0
  39. package/dist/contracts/sessions.contract.d.ts.map +1 -1
  40. package/dist/contracts/settings.contract.d.ts +31 -2
  41. package/dist/contracts/settings.contract.d.ts.map +1 -1
  42. package/dist/contracts/settings.contract.js +20 -1
  43. package/dist/contracts/settings.contract.js.map +1 -1
  44. package/dist/contracts/system.contract.d.ts +96 -435
  45. package/dist/contracts/system.contract.d.ts.map +1 -1
  46. package/dist/contracts/system.contract.js +2 -12
  47. package/dist/contracts/system.contract.js.map +1 -1
  48. package/dist/contracts/translator.contract.d.ts +7 -0
  49. package/dist/contracts/translator.contract.d.ts.map +1 -1
  50. package/dist/contracts/translator.contract.js.map +1 -1
  51. package/dist/contracts/webext.contract.d.ts +38 -0
  52. package/dist/contracts/webext.contract.d.ts.map +1 -1
  53. package/dist/contracts/webext.contract.js +3 -0
  54. package/dist/contracts/webext.contract.js.map +1 -1
  55. package/dist/contracts/workspace.contract.d.ts +1 -80
  56. package/dist/contracts/workspace.contract.d.ts.map +1 -1
  57. package/dist/contracts/workspace.contract.js +0 -27
  58. package/dist/contracts/workspace.contract.js.map +1 -1
  59. package/dist/events/agent-events.d.ts +21 -0
  60. package/dist/events/agent-events.d.ts.map +1 -1
  61. package/dist/events/agent-events.js +11 -0
  62. package/dist/events/agent-events.js.map +1 -1
  63. package/dist/events/agent-words.d.ts +1 -0
  64. package/dist/events/agent-words.d.ts.map +1 -1
  65. package/dist/events/agent-words.js +1 -1
  66. package/dist/events/agent-words.js.map +1 -1
  67. package/dist/events/land-breakage.d.ts.map +1 -1
  68. package/dist/events/land-breakage.js.map +1 -1
  69. package/dist/events/resume.d.ts +6 -1
  70. package/dist/events/resume.d.ts.map +1 -1
  71. package/dist/events/resume.js +17 -10
  72. package/dist/events/resume.js.map +1 -1
  73. package/dist/events/sandbox-notice.d.ts +183 -0
  74. package/dist/events/sandbox-notice.d.ts.map +1 -0
  75. package/dist/events/sandbox-notice.js +56 -0
  76. package/dist/events/sandbox-notice.js.map +1 -0
  77. package/dist/events/system-events.d.ts +46 -0
  78. package/dist/events/system-events.d.ts.map +1 -1
  79. package/dist/events/system-events.js +2 -0
  80. package/dist/events/system-events.js.map +1 -1
  81. package/dist/events/transcript.d.ts +24 -0
  82. package/dist/events/transcript.d.ts.map +1 -1
  83. package/dist/events/transcript.js +3 -1
  84. package/dist/events/transcript.js.map +1 -1
  85. package/dist/ids/conversation-ids.d.ts +1 -3
  86. package/dist/ids/conversation-ids.d.ts.map +1 -1
  87. package/dist/ids/conversation-ids.js +1 -3
  88. package/dist/ids/conversation-ids.js.map +1 -1
  89. package/dist/ids/project-dir.d.ts +6 -0
  90. package/dist/ids/project-dir.d.ts.map +1 -0
  91. package/dist/ids/project-dir.js +43 -0
  92. package/dist/ids/project-dir.js.map +1 -0
  93. package/dist/index.d.ts +11570 -11030
  94. package/dist/index.d.ts.map +1 -1
  95. package/dist/index.js +7 -4
  96. package/dist/index.js.map +1 -1
  97. package/dist/models/agent-runtimes.d.ts.map +1 -1
  98. package/dist/models/agent-runtimes.js +2 -2
  99. package/dist/models/agent-runtimes.js.map +1 -1
  100. package/dist/models/model-roles.d.ts +1 -9
  101. package/dist/models/model-roles.d.ts.map +1 -1
  102. package/dist/models/model-roles.js +1 -9
  103. package/dist/models/model-roles.js.map +1 -1
  104. package/dist/policy/command-classes.d.ts.map +1 -1
  105. package/dist/policy/command-classes.js +52 -3
  106. package/dist/policy/command-classes.js.map +1 -1
  107. package/dist/policy/safety-policy.d.ts +7 -1
  108. package/dist/policy/safety-policy.d.ts.map +1 -1
  109. package/dist/policy/safety-policy.js +2 -0
  110. package/dist/policy/safety-policy.js.map +1 -1
  111. package/dist/policy/secret-hosts.d.ts +13 -0
  112. package/dist/policy/secret-hosts.d.ts.map +1 -0
  113. package/dist/policy/secret-hosts.js +41 -0
  114. package/dist/policy/secret-hosts.js.map +1 -0
  115. package/dist/policy/turned-away.d.ts.map +1 -1
  116. package/dist/policy/turned-away.js +1 -0
  117. package/dist/policy/turned-away.js.map +1 -1
  118. package/dist/protocol/raw-routes.d.ts +7 -0
  119. package/dist/protocol/raw-routes.d.ts.map +1 -1
  120. package/dist/protocol/raw-routes.js +4 -0
  121. package/dist/protocol/raw-routes.js.map +1 -1
  122. package/dist/protocol/routes.d.ts +2 -6
  123. package/dist/protocol/routes.d.ts.map +1 -1
  124. package/dist/protocol/routes.js +2 -3
  125. package/dist/protocol/routes.js.map +1 -1
  126. package/dist/protocol/stream-of.d.ts +6 -0
  127. package/dist/protocol/stream-of.d.ts.map +1 -0
  128. package/dist/protocol/stream-of.js +4 -0
  129. package/dist/protocol/stream-of.js.map +1 -0
  130. package/dist/schemas/activity.js +1 -1
  131. package/dist/schemas/activity.js.map +1 -1
  132. package/dist/schemas/agent.d.ts +19 -2
  133. package/dist/schemas/agent.d.ts.map +1 -1
  134. package/dist/schemas/agent.js +2 -1
  135. package/dist/schemas/agent.js.map +1 -1
  136. package/dist/schemas/agents.d.ts +61 -0
  137. package/dist/schemas/agents.d.ts.map +1 -1
  138. package/dist/schemas/agents.js +36 -0
  139. package/dist/schemas/agents.js.map +1 -1
  140. package/dist/schemas/automations.d.ts +18 -0
  141. package/dist/schemas/automations.d.ts.map +1 -1
  142. package/dist/schemas/capabilities.d.ts +2 -0
  143. package/dist/schemas/capabilities.d.ts.map +1 -1
  144. package/dist/schemas/capabilities.js +34 -4
  145. package/dist/schemas/capabilities.js.map +1 -1
  146. package/dist/schemas/ci.d.ts +69 -9
  147. package/dist/schemas/ci.d.ts.map +1 -1
  148. package/dist/schemas/ci.js +29 -8
  149. package/dist/schemas/ci.js.map +1 -1
  150. package/dist/schemas/context-trim.d.ts +5 -1
  151. package/dist/schemas/context-trim.d.ts.map +1 -1
  152. package/dist/schemas/context-trim.js +8 -4
  153. package/dist/schemas/context-trim.js.map +1 -1
  154. package/dist/schemas/devices.d.ts +23 -0
  155. package/dist/schemas/devices.d.ts.map +1 -1
  156. package/dist/schemas/devices.js +16 -1
  157. package/dist/schemas/devices.js.map +1 -1
  158. package/dist/schemas/environment.d.ts +48 -0
  159. package/dist/schemas/environment.d.ts.map +1 -1
  160. package/dist/schemas/environment.js +15 -0
  161. package/dist/schemas/environment.js.map +1 -1
  162. package/dist/schemas/extension-updates.d.ts +24 -0
  163. package/dist/schemas/extension-updates.d.ts.map +1 -1
  164. package/dist/schemas/git/git.d.ts +13 -0
  165. package/dist/schemas/git/git.d.ts.map +1 -1
  166. package/dist/schemas/git/git.js +11 -1
  167. package/dist/schemas/git/git.js.map +1 -1
  168. package/dist/schemas/hosts.d.ts +2 -0
  169. package/dist/schemas/hosts.d.ts.map +1 -1
  170. package/dist/schemas/hosts.js +2 -1
  171. package/dist/schemas/hosts.js.map +1 -1
  172. package/dist/schemas/keep-warm.d.ts +4 -0
  173. package/dist/schemas/keep-warm.d.ts.map +1 -1
  174. package/dist/schemas/keep-warm.js +15 -5
  175. package/dist/schemas/keep-warm.js.map +1 -1
  176. package/dist/schemas/loopback-catch.d.ts +33 -0
  177. package/dist/schemas/loopback-catch.d.ts.map +1 -0
  178. package/dist/schemas/loopback-catch.js +20 -0
  179. package/dist/schemas/loopback-catch.js.map +1 -0
  180. package/dist/schemas/maintenance.js +1 -1
  181. package/dist/schemas/maintenance.js.map +1 -1
  182. package/dist/schemas/needs.d.ts +3 -0
  183. package/dist/schemas/needs.d.ts.map +1 -1
  184. package/dist/schemas/needs.js +10 -3
  185. package/dist/schemas/needs.js.map +1 -1
  186. package/dist/schemas/personas.d.ts.map +1 -1
  187. package/dist/schemas/personas.js +6 -3
  188. package/dist/schemas/personas.js.map +1 -1
  189. package/dist/schemas/providers/provider-oauth.d.ts +87 -0
  190. package/dist/schemas/providers/provider-oauth.d.ts.map +1 -1
  191. package/dist/schemas/providers/provider-oauth.js +20 -0
  192. package/dist/schemas/providers/provider-oauth.js.map +1 -1
  193. package/dist/schemas/secrets.d.ts +66 -0
  194. package/dist/schemas/secrets.d.ts.map +1 -1
  195. package/dist/schemas/secrets.js +50 -0
  196. package/dist/schemas/secrets.js.map +1 -1
  197. package/dist/schemas/settings-history.d.ts +1 -1
  198. package/dist/schemas/settings-history.d.ts.map +1 -1
  199. package/dist/schemas/settings-history.js +1 -0
  200. package/dist/schemas/settings-history.js.map +1 -1
  201. package/dist/schemas/settings.d.ts +42 -2
  202. package/dist/schemas/settings.d.ts.map +1 -1
  203. package/dist/schemas/settings.js +26 -4
  204. package/dist/schemas/settings.js.map +1 -1
  205. package/dist/schemas/terminal.d.ts +2 -3
  206. package/dist/schemas/terminal.d.ts.map +1 -1
  207. package/dist/schemas/terminal.js +12 -4
  208. package/dist/schemas/terminal.js.map +1 -1
  209. package/dist/schemas/webext.d.ts +3 -0
  210. package/dist/schemas/webext.d.ts.map +1 -1
  211. package/dist/schemas/webext.js +1 -0
  212. package/dist/schemas/webext.js.map +1 -1
  213. package/dist/schemas/workflows.js +3 -3
  214. package/dist/schemas/workflows.js.map +1 -1
  215. package/dist/schemas/workspace/workspace-tree.d.ts +2 -0
  216. package/dist/schemas/workspace/workspace-tree.d.ts.map +1 -1
  217. package/dist/schemas/workspace/workspace-tree.js +4 -0
  218. package/dist/schemas/workspace/workspace-tree.js.map +1 -1
  219. package/dist/state/definition.d.ts +12 -4
  220. package/dist/state/definition.d.ts.map +1 -1
  221. package/dist/state/history-state.d.ts.map +1 -1
  222. package/dist/state/history-state.js +7 -0
  223. package/dist/state/history-state.js.map +1 -1
  224. package/dist/state/runtime-state.d.ts +0 -3
  225. package/dist/state/runtime-state.d.ts.map +1 -1
  226. package/dist/state/runtime-state.js +0 -1
  227. package/dist/state/runtime-state.js.map +1 -1
  228. package/dist/state/workspace-state.d.ts +0 -5
  229. package/dist/state/workspace-state.d.ts.map +1 -1
  230. package/dist/state/workspace-state.js +0 -6
  231. package/dist/state/workspace-state.js.map +1 -1
  232. package/dist/text/documents.d.ts +3 -0
  233. package/dist/text/documents.d.ts.map +1 -1
  234. package/dist/text/documents.js +14 -0
  235. package/dist/text/documents.js.map +1 -1
  236. package/dist/text/transcript-fold.d.ts +4 -3
  237. package/dist/text/transcript-fold.d.ts.map +1 -1
  238. package/dist/text/transcript-fold.js +137 -31
  239. package/dist/text/transcript-fold.js.map +1 -1
  240. package/dist/webext/index.d.ts +1 -0
  241. package/dist/webext/index.d.ts.map +1 -1
  242. package/dist/webext/index.js +1 -0
  243. package/dist/webext/index.js.map +1 -1
  244. package/package.json +5 -5
  245. package/src/chores/chores.ts +2 -2
  246. package/src/chores/probes.ts +1 -1
  247. package/src/contracts/accounts.contract.ts +12 -0
  248. package/src/contracts/agents.contract.ts +13 -13
  249. package/src/contracts/device.contract.ts +5 -0
  250. package/src/contracts/endpoints.contract.ts +20 -4
  251. package/src/contracts/secrets.contract.ts +28 -0
  252. package/src/contracts/settings.contract.ts +29 -0
  253. package/src/contracts/system.contract.ts +5 -14
  254. package/src/contracts/translator.contract.ts +2 -1
  255. package/src/contracts/webext.contract.ts +7 -1
  256. package/src/contracts/workspace.contract.ts +0 -37
  257. package/src/events/agent-events.ts +18 -1
  258. package/src/events/agent-words.ts +4 -1
  259. package/src/events/land-breakage.ts +3 -4
  260. package/src/events/resume.test.ts +13 -2
  261. package/src/events/resume.ts +25 -12
  262. package/src/events/sandbox-notice.ts +92 -0
  263. package/src/events/system-events.ts +7 -0
  264. package/src/events/transcript.ts +7 -1
  265. package/src/events/verify-nudge.ts +3 -3
  266. package/src/ids/conversation-ids.test.ts +2 -26
  267. package/src/ids/conversation-ids.ts +6 -12
  268. package/src/ids/project-dir.fixture.json +65 -0
  269. package/src/ids/project-dir.test.ts +60 -0
  270. package/src/ids/project-dir.ts +70 -0
  271. package/src/index.ts +12 -4
  272. package/src/models/agent-catalog.test.ts +7 -1
  273. package/src/models/agent-runtimes.ts +7 -5
  274. package/src/models/model-roles.ts +1 -9
  275. package/src/policy/command-classes.test.ts +39 -0
  276. package/src/policy/command-classes.ts +70 -3
  277. package/src/policy/safety-policy.ts +10 -0
  278. package/src/policy/secret-hosts.test.ts +89 -0
  279. package/src/policy/secret-hosts.ts +73 -0
  280. package/src/policy/turned-away.ts +1 -0
  281. package/src/protocol/raw-routes.ts +5 -1
  282. package/src/protocol/routes.test.ts +6 -6
  283. package/src/protocol/routes.ts +2 -9
  284. package/src/protocol/stream-of.ts +13 -0
  285. package/src/schemas/activity.ts +1 -1
  286. package/src/schemas/agent.ts +4 -1
  287. package/src/schemas/agents.ts +58 -4
  288. package/src/schemas/automations.ts +2 -2
  289. package/src/schemas/capabilities.ts +44 -7
  290. package/src/schemas/ci.ts +56 -11
  291. package/src/schemas/context-trim.ts +11 -6
  292. package/src/schemas/devices.ts +38 -2
  293. package/src/schemas/environment.ts +39 -0
  294. package/src/schemas/git/git.ts +20 -2
  295. package/src/schemas/hosts.ts +4 -1
  296. package/src/schemas/keep-warm.ts +19 -6
  297. package/src/schemas/loopback-catch.ts +45 -0
  298. package/src/schemas/maintenance.ts +1 -1
  299. package/src/schemas/needs.ts +12 -3
  300. package/src/schemas/personas.ts +8 -3
  301. package/src/schemas/providers/provider-oauth.ts +35 -0
  302. package/src/schemas/secrets.ts +70 -0
  303. package/src/schemas/settings-history.ts +2 -0
  304. package/src/schemas/settings.test.ts +19 -1
  305. package/src/schemas/settings.ts +58 -9
  306. package/src/schemas/speaker.ts +5 -5
  307. package/src/schemas/terminal.ts +19 -6
  308. package/src/schemas/webext.ts +3 -0
  309. package/src/schemas/workflows.ts +5 -5
  310. package/src/schemas/workspace/workspace-tree.ts +7 -0
  311. package/src/state/fix-attempt-plan.test.ts +1 -1
  312. package/src/state/fix-stance.ts +1 -1
  313. package/src/state/history-state.ts +14 -0
  314. package/src/state/runtime-state.ts +0 -3
  315. package/src/state/workspace-state.ts +0 -6
  316. package/src/text/documents.test.ts +25 -1
  317. package/src/text/documents.ts +24 -0
  318. package/src/text/title.ts +1 -1
  319. package/src/text/transcript-fold.test.ts +218 -25
  320. package/src/text/transcript-fold.ts +166 -45
  321. package/src/webext/index.ts +1 -0
  322. package/dist/schemas/workspace/push-checks.d.ts +0 -222
  323. package/dist/schemas/workspace/push-checks.d.ts.map +0 -1
  324. package/dist/schemas/workspace/push-checks.js +0 -97
  325. package/dist/schemas/workspace/push-checks.js.map +0 -1
  326. package/dist/state/red-streak.d.ts +0 -7
  327. package/dist/state/red-streak.d.ts.map +0 -1
  328. package/dist/state/red-streak.js +0 -11
  329. package/dist/state/red-streak.js.map +0 -1
  330. package/src/schemas/workspace/push-checks.test.ts +0 -28
  331. package/src/schemas/workspace/push-checks.ts +0 -155
  332. package/src/state/red-streak.test.ts +0 -28
  333. package/src/state/red-streak.ts +0 -28
@@ -1,6 +1,7 @@
1
1
  import { STATE_DIR } from "@intentic/constants";
2
2
  import type { AgentEvent } from "../events/agent-events.js";
3
- import type { TranscriptPatch, TranscriptRow } from "../events/transcript.js";
3
+ import { type TranscriptPatch, type TranscriptRow, TranscriptRowSchema } from "../events/transcript.js";
4
+ import { sandboxNoticeOf } from "../events/sandbox-notice.js";
4
5
  import { watchWakePrompt } from "../events/watch-wake.js";
5
6
  import { LAND_CONFLICT_OPENING } from "../events/land-conflict.js";
6
7
  import { RESUME_NOTES } from "../events/resume.js";
@@ -11,6 +12,10 @@ import { applyTranscriptPatch, foldTurn, TranscriptFold, userRow } from "./trans
11
12
  const SENT_AT = 1_767_225_600_000;
12
13
  const openingOf = (prompt: string): TranscriptRow[] => [userRow(prompt, SENT_AT, [])];
13
14
  const foldOf = (prompt: string, events: readonly AgentEvent[]): TranscriptRow[] => foldTurn(openingOf(prompt), events);
15
+ // What an armed credential renewal's row promises.
16
+ const RENEWING_TEXT = "The credential is being renewed and this turn continues automatically.";
17
+ // The same row as a code, which an app words in its reader's language (sandbox-notice.ts).
18
+ const RENEWING_CODE = { code: "renewing", params: { message: "Token refused.", error: "claude-token-refused" } };
14
19
 
15
20
  // The one fold every window and the stored record draw from; pins what a live, reopened and stored chat all show.
16
21
  describe("foldTurn", () => {
@@ -223,20 +228,6 @@ describe("foldTurn", () => {
223
228
  expect(foldOf("delegate", events).slice(1)).toEqual([{ role: "assistant", text: "delegating" }]);
224
229
  });
225
230
 
226
- it("reads one subagent's stream as a transcript of its own", () => {
227
- const questions = [{ question: "Which?", header: "Pick", multiSelect: false, options: [{ label: "A", description: "a" }] }];
228
- const events: AgentEvent[] = [
229
- { kind: "delta", text: "parent prose" },
230
- { kind: "tool_call", id: "t1", name: "Grep", category: "search", status: "completed" },
231
- { kind: "delta", text: "found it", parentToolUseId: "task-1" },
232
- { kind: "tool_call", id: "t2", name: "Read", category: "read", status: "completed", parentToolUseId: "task-1" },
233
- { kind: "question", requestId: "q1", questions },
234
- ];
235
- expect(foldTurn([], events, "settled", "task-1")).toEqual([
236
- { role: "assistant", text: "found it", tools: [{ id: "t2", name: "Read", category: "read", status: "completed" }] },
237
- ]);
238
- });
239
-
240
231
  it("records the thinking a turn showed", () => {
241
232
  const events: AgentEvent[] = [
242
233
  { kind: "thinking", text: "hm, " },
@@ -313,24 +304,62 @@ describe("foldTurn", () => {
313
304
  ];
314
305
  // The row states what happened and nothing more: what happens next, and the one control that changes it, are
315
306
  // the chat card's, asked once (ChatContinueStrip), not a second switch on a transcript line.
316
- expect(foldOf("hi", outage).at(-1)).toEqual({ role: "notice", text: "Anthropic is down. Retrying by itself: attempt 2 of 6." });
307
+ expect(foldOf("hi", outage).at(-1)).toEqual({
308
+ role: "notice",
309
+ text: "Anthropic is down. Retrying by itself: attempt 2 of 6.",
310
+ noticeCode: { code: "retrying", params: { message: "Anthropic is down.", error: "provider-outage", attempt: 2, of: 6 } },
311
+ });
317
312
  const renewal: AgentEvent[] = [{ kind: "error", code: "claude-token-refused", message: "Token refused.", autoResume: "scheduled" }];
318
313
  expect(foldOf("hi", renewal).at(-1)).toEqual({
319
314
  role: "notice",
320
315
  text: "Token refused. The credential is being renewed and this turn continues automatically.",
321
316
  noticeWait: "credentialRenewal",
317
+ noticeCode: RENEWING_CODE,
322
318
  });
323
319
  });
324
320
 
321
+ // The sandbox keeps one hold per run and each failure replaces it: a stop right after an armed renewal is what it acts
322
+ // on, so the renewal row must stop promising a continuation that is never coming (one sat in a chat for 41 minutes).
323
+ it("takes back a renewal's promise when a later failure in the same run stops the turn", () => {
324
+ const refused: AgentEvent = { kind: "error", code: "claude-token-refused", message: "Token refused.", autoResume: "scheduled" };
325
+ const stopped: AgentEvent = { kind: "error", message: "Claude Code returned an error result: 401." };
326
+ const fold = new TranscriptFold(openingOf("hi"));
327
+ fold.apply(refused);
328
+ const patches = fold.apply(stopped);
329
+ // The code moves with the words, so an app wording the row itself says the stop too, not the old promise.
330
+ const withdrawn: TranscriptRow = {
331
+ role: "notice",
332
+ text: "Token refused. The credential was being renewed, but the turn stopped before it could continue: it will not carry on by itself, so press Continue to pick it back up.",
333
+ noticeCode: { code: "renewalWithdrawn", params: { message: "Token refused.", error: "claude-token-refused" } },
334
+ };
335
+ // The live window hears the rewrite as a patch on the row it already drew, then the stop's own row.
336
+ expect(patches).toEqual([
337
+ { op: "replace", index: 1, row: withdrawn },
338
+ { op: "append", row: { role: "notice", text: "Claude Code returned an error result: 401." } },
339
+ ]);
340
+ expect(fold.rows.slice(1)).toEqual([withdrawn, { role: "notice", text: "Claude Code returned an error result: 401." }]);
341
+ // A renewal armed again keeps its promise.
342
+ const again = new TranscriptFold(openingOf("hi"));
343
+ again.apply(refused);
344
+ const renewing: TranscriptRow = { role: "notice", text: `Token refused. ${RENEWING_TEXT}`, noticeWait: "credentialRenewal", noticeCode: RENEWING_CODE };
345
+ expect(again.apply(refused)).toEqual([{ op: "append", row: renewing }]);
346
+ expect(again.rows[1]).toEqual(renewing);
347
+ });
348
+
325
349
  // The count lives in the record, so a reader scrolling back sees how many times a stuck turn was sent again.
326
350
  it("says on a stopped turn's row which automatic try comes next, or that the ladder stood down", () => {
327
351
  const timedOut = "Google turn timed out waiting for OpenCode.";
328
352
  const booked: AgentEvent[] = [{ kind: "error", message: timedOut, autoResume: "scheduled", nextAt: 1, retries: { made: 1, max: 3 } }];
329
- expect(foldOf("hi", booked).at(-1)).toEqual({ role: "notice", text: `${timedOut} Retrying by itself: attempt 2 of 3.` });
353
+ expect(foldOf("hi", booked).at(-1)).toEqual({
354
+ role: "notice",
355
+ text: `${timedOut} Retrying by itself: attempt 2 of 3.`,
356
+ noticeCode: { code: "retrying", params: { message: timedOut, attempt: 2, of: 3 } },
357
+ });
330
358
  const spent: AgentEvent[] = [{ kind: "error", message: timedOut, autoResume: "available", retries: { made: 3, max: 3 } }];
331
359
  expect(foldOf("hi", spent).at(-1)).toEqual({
332
360
  role: "notice",
333
361
  text: `${timedOut} Retried 3 of 3 times by itself; nothing more is sent automatically.`,
362
+ noticeCode: { code: "retried", params: { message: timedOut, made: 3, of: 3 } },
334
363
  });
335
364
  // An outage nobody armed says so, instead of promising the breaker's retry.
336
365
  const unarmed: AgentEvent[] = [
@@ -346,6 +375,7 @@ describe("foldTurn", () => {
346
375
  expect(foldOf("hi", unarmed).at(-1)).toEqual({
347
376
  role: "notice",
348
377
  text: "Anthropic is down. Nothing is retrying it, so the turn is waiting here.",
378
+ noticeCode: { code: "outageWaiting", params: { message: "Anthropic is down.", error: "provider-outage" } },
349
379
  });
350
380
  });
351
381
 
@@ -357,18 +387,78 @@ describe("foldTurn", () => {
357
387
  { kind: "landed", landed: true, deps: { missing: 1, started: ["deps-1"], deferred: false } },
358
388
  ];
359
389
  expect(foldOf("go", events).slice(1)).toEqual([
360
- { role: "notice", text: "Your workspace moved on while this agent waited, its branch was rebased onto your latest 2 commits." },
390
+ {
391
+ role: "notice",
392
+ text: "Your workspace moved on while this agent waited, its branch was rebased onto your latest 2 commits.",
393
+ noticeCode: { code: "synced", params: { commits: 2 } },
394
+ },
361
395
  { role: "assistant", text: "on it" },
362
- { role: "notice", text: "Context compacted to free up space." },
396
+ { role: "notice", text: "Context compacted to free up space.", noticeCode: { code: "compacted" } },
363
397
  {
364
398
  role: "notice",
365
399
  text: "Changes landed in your workspace: review them in the Changes panel. Installing 1 dependency it added or changed; the project's checks run when that finishes, and the outcome lands in Activity.",
366
400
  noticeAction: "depsInstall",
401
+ noticeCode: { code: "landed", params: { deps: 1 } },
367
402
  },
368
403
  ]);
369
- expect(foldOf("go", [{ kind: "landed", landed: true, held: true }]).at(-1)?.text).toBe(
370
- "Finished: the work is on this agent's branch, ready to land from its review.",
404
+ expect(foldOf("go", [{ kind: "landed", landed: true, held: true }]).at(-1)).toEqual({
405
+ role: "notice",
406
+ text: "Finished: the work is on this agent's branch, ready to land from its review.",
407
+ noticeCode: { code: "landHeld" },
408
+ });
409
+ });
410
+
411
+ // An agent's own install, let through the command gate, says where it writes before its output arrives.
412
+ it("says where an agent's own install writes, naming its projects", () => {
413
+ expect(foldOf("go", [{ kind: "install", reach: "own-copy", projects: ["video"] }]).at(-1)).toEqual({
414
+ role: "notice",
415
+ text: "Installing packages in video/ into this conversation's own copy. The manifest and lockfile land with the work, and the review lists what it adds.",
416
+ noticeCode: { code: "installing", params: { ownCopy: true, projects: "video/" } },
417
+ });
418
+ expect(foldOf("go", [{ kind: "install", reach: "main-tree", projects: [""] }]).at(-1)).toEqual({
419
+ role: "notice",
420
+ text: "Installing packages in the workspace root in the main tree, one install at a time.",
421
+ noticeCode: { code: "installing", params: { ownCopy: false, root: true } },
422
+ });
423
+ // The workspace root is named first, and counts toward the three named.
424
+ expect(foldOf("go", [{ kind: "install", reach: "main-tree", projects: ["a", "", "b", "c", "d"] }]).at(-1)).toEqual({
425
+ role: "notice",
426
+ text: "Installing packages in the workspace root, a/, b/ and 2 more in the main tree, one install at a time.",
427
+ noticeCode: { code: "installing", params: { ownCopy: false, root: true, projects: "a/, b/", more: 2 } },
428
+ });
429
+ expect(foldOf("go", [{ kind: "install", reach: "main-tree", projects: ["a", "b", "c", "d", "e"] }]).at(-1)?.text).toBe(
430
+ "Installing packages in a/, b/, c/ and 2 more in the main tree, one install at a time.",
371
431
  );
432
+ expect(foldOf("go", [{ kind: "install", reach: "own-copy", projects: [] }]).at(-1)?.text).toBe(
433
+ "Installing packages into this conversation's own copy. The manifest and lockfile land with the work, and the review lists what it adds.",
434
+ );
435
+ });
436
+
437
+ // The reconciler starts a land's install within seconds, in the install lane; it never waits for turns to end, so
438
+ // the notice must not say it does.
439
+ it("says a land's dependency install is on its way, not waiting for other agents", () => {
440
+ expect(foldOf("go", [{ kind: "landed", landed: true, deps: { missing: 3, started: [], deferred: true } }]).at(-1)).toEqual({
441
+ role: "notice",
442
+ text: "Changes landed in your workspace: review them in the Changes panel. 3 dependencies it added or changed are being installed in your tree: the install waits for any other install to finish, appears in Work terminals, and its outcome lands in Activity.",
443
+ noticeAction: "landHold",
444
+ noticeCode: { code: "landed", params: { deps: 3, queued: true } },
445
+ });
446
+ });
447
+
448
+ // A spawned child's work goes into its parent's checkout, as the parent's in-process subagents' edits do: the row
449
+ // says so, never that it reached the owner's workspace, and says who is to bring in a clash.
450
+ it("says a child's work went into its parent's checkout, or was held off it by a clash", () => {
451
+ expect(foldOf("go", [{ kind: "landed", landed: true, into: "p1" }]).at(-1)).toEqual({
452
+ role: "notice",
453
+ text: "Changes went into the parent agent's checkout, as its in-process subagents' edits do: they reach your workspace with its land.",
454
+ noticeCode: { code: "intoParent" },
455
+ });
456
+ const clash: AgentEvent = { kind: "landed", landed: false, into: "p1", conflicts: [{ repo: "root", paths: [{ path: "app.ts", reason: "diverged" }], clean: 0 }] };
457
+ expect(foldOf("go", [clash]).at(-1)).toEqual({
458
+ role: "notice",
459
+ text: "1 file(s) clash with the parent agent's own edits, so nothing was written into its checkout. The parent was told, and can bring the changes in to resolve.",
460
+ noticeCode: { code: "intoParentClash", params: { files: 1 } },
461
+ });
372
462
  });
373
463
 
374
464
  // A resolve turn exists because the branch couldn't rebase; its own opening rebase failing the same way is that
@@ -382,11 +472,17 @@ describe("foldTurn", () => {
382
472
  {
383
473
  role: "notice",
384
474
  text: "Couldn't rebase onto your workspace in intentic: the turn is running from the older base, so its land may need a resolve.",
475
+ noticeCode: { code: "synced", params: { commits: 0, blocked: "intentic" } },
385
476
  },
386
477
  ]);
387
478
  const mixed: AgentEvent = { kind: "worktree", branch: "agent/x", base: "abc1234", sync: { commits: 3, blocked: ["intentic"] } };
479
+ // The code leaves out what the words leave out: the resolve turn is not told its own errand in any language.
388
480
  expect(foldOf(resolve, [mixed]).slice(1)).toEqual([
389
- { role: "notice", text: "Your workspace moved on while this agent waited, its branch was rebased onto your latest 3 commits." },
481
+ {
482
+ role: "notice",
483
+ text: "Your workspace moved on while this agent waited, its branch was rebased onto your latest 3 commits.",
484
+ noticeCode: { code: "synced", params: { commits: 3 } },
485
+ },
390
486
  ]);
391
487
  });
392
488
 
@@ -513,7 +609,7 @@ describe("foldTurn", () => {
513
609
  const events: AgentEvent[] = [{ kind: "credential_offer", requestId: "c1", offer }];
514
610
  expect(foldTurn(openingOf("post it"), events, "stopped").slice(1)).toEqual([
515
611
  { role: "assistant", text: "", credentialOffer: { requestId: "c1", offer, status: "cancelled" } },
516
- { role: "notice", text: "Stopped." },
612
+ { role: "notice", text: "Stopped.", noticeCode: { code: "stopped" } },
517
613
  ]);
518
614
  });
519
615
 
@@ -524,7 +620,7 @@ describe("foldTurn", () => {
524
620
  ];
525
621
  expect(foldTurn(openingOf("go"), events, "stopped").slice(1)).toEqual([
526
622
  { role: "assistant", text: "half", question: { requestId: "q1", questions: [], status: "cancelled" } },
527
- { role: "notice", text: "Stopped." },
623
+ { role: "notice", text: "Stopped.", noticeCode: { code: "stopped" } },
528
624
  ]);
529
625
  });
530
626
  });
@@ -756,7 +852,13 @@ describe(`a refusal that ran nothing`, () => {
756
852
  const fold = new TranscriptFold(openingOf(`land it`));
757
853
  const patches = fold.apply(REFUSED);
758
854
 
759
- expect(fold.rows).toEqual([{ role: `notice`, text: expect.stringContaining(`held for you to send again`) }]);
855
+ expect(fold.rows).toEqual([
856
+ {
857
+ role: `notice`,
858
+ text: expect.stringContaining(`held for you to send again`),
859
+ noticeCode: { code: `undelivered`, params: { message: REFUSED.message, error: REFUSED.code } },
860
+ },
861
+ ]);
760
862
  expect(fold.ranNothing).toBe(true);
761
863
  // Dropped ahead of the notice that stands in for it: a window applies patches in order, and an append first
762
864
  // would have it renumber every row under an index that is about to move.
@@ -843,3 +945,94 @@ describe(`a refusal that ran nothing`, () => {
843
945
  });
844
946
  });
845
947
  });
948
+
949
+ // The sandbox's own notices ride as a code beside their English words, so an app can say them in the reader's language
950
+ // and words (sandbox-notice.ts). The words stay exactly as they were: older apps, stored records and agents read them.
951
+ describe(`the code beside a notice's words`, () => {
952
+ const GIB = 1024 ** 3;
953
+ const lowMemory = {
954
+ kind: `error`,
955
+ code: `sandbox-memory-low`,
956
+ message: `Sandbox memory is low: 15.9 GiB of 16.0 GiB used.`,
957
+ memory: { limitBytes: 16 * GIB, residentBytes: 15.9 * GIB, swapBytes: 0 },
958
+ } as const satisfies AgentEvent;
959
+ const lastOf = (events: readonly AgentEvent[]): TranscriptRow | undefined => foldOf(`land it`, events).at(-1);
960
+
961
+ it(`names each land outcome, with the counts and repos its sentence was worded from`, () => {
962
+ expect(lastOf([{ kind: `landed`, landed: true }])).toEqual({
963
+ role: `notice`,
964
+ text: `Changes landed in your workspace: review them in the Changes panel.`,
965
+ noticeAction: `landHold`,
966
+ noticeCode: { code: `landed` },
967
+ });
968
+ expect(lastOf([{ kind: `landed`, landed: true, deps: { missing: 2, started: [], deferred: true } }])?.noticeCode).toEqual({
969
+ code: `landed`,
970
+ params: { deps: 2, queued: true },
971
+ });
972
+ const conflicts = [
973
+ { repo: `root`, paths: [{ path: `a.ts`, reason: `diverged` as const }], clean: 0 },
974
+ { repo: `web`, paths: [{ path: `b.ts`, reason: `diverged` as const }, { path: `c.ts`, reason: `diverged` as const }], clean: 0 },
975
+ ];
976
+ expect(lastOf([{ kind: `landed`, landed: false, conflicts }])).toEqual({
977
+ role: `notice`,
978
+ text: `3 file(s) couldn't land automatically in root, web. Open the agent's review to see what blocked them and land from there.`,
979
+ noticeCode: { code: `landConflict`, params: { files: 3, repos: `root, web` } },
980
+ });
981
+ });
982
+
983
+ it(`names each memory hold by where the message waits`, () => {
984
+ expect(lastOf([lowMemory])?.noticeCode).toEqual({ code: `memoryHeld`, params: { message: lowMemory.message, error: `sandbox-memory-low` } });
985
+ expect(lastOf([{ ...lowMemory, held: { ran: false } }])?.noticeCode).toEqual({
986
+ code: `kept`,
987
+ params: { message: lowMemory.message, error: `sandbox-memory-low`, memory: true },
988
+ });
989
+ expect(lastOf([{ ...lowMemory, unattended: true }])?.noticeCode).toEqual({
990
+ code: `undelivered`,
991
+ params: { message: lowMemory.message, error: `sandbox-memory-low`, unattended: true },
992
+ });
993
+ });
994
+
995
+ it(`codes a failure the sandbox names, and leaves a provider's own words to themselves`, () => {
996
+ const busy = `This agent is already running a turn, wait for it to finish.`;
997
+ expect(lastOf([{ kind: `error`, code: `agent-busy`, message: busy }])).toEqual({
998
+ role: `notice`,
999
+ text: busy,
1000
+ noticeCode: { code: `failed`, params: { message: busy, error: `agent-busy` } },
1001
+ });
1002
+ expect(lastOf([{ kind: `error`, message: `Overloaded.` }])).toEqual({ role: `notice`, text: `Overloaded.` });
1003
+ expect(lastOf([{ kind: `error`, code: `claude-token-refused`, message: `Token refused.` }])).toEqual({
1004
+ role: `notice`,
1005
+ text: `Token refused. Reconnect the account to pick this conversation back up.`,
1006
+ noticeCode: { code: `reconnect`, params: { message: `Token refused.`, error: `claude-token-refused` } },
1007
+ });
1008
+ });
1009
+
1010
+ // A reader decodes a code with this build's own schema: every code the fold writes has to be one it knows.
1011
+ it(`writes only codes this build can read back`, () => {
1012
+ const events: AgentEvent[][] = [
1013
+ [{ kind: `worktree`, branch: `agent/x`, base: `abc1234`, sync: { commits: 1, blocked: [`web`] } }],
1014
+ [{ kind: `compact`, trigger: `auto` }],
1015
+ [{ kind: `landed`, landed: true, into: `p1` }],
1016
+ [{ kind: `landed`, landed: false, into: `p1`, conflicts: [] }],
1017
+ [{ kind: `landed`, landed: true, held: true }],
1018
+ [{ kind: `error`, code: `provider-outage`, message: `Down.`, autoResume: `available` }],
1019
+ [lowMemory],
1020
+ [{ ...lowMemory, held: { ran: false } }],
1021
+ [{ kind: `error`, code: `claude-token-refused`, message: `Token refused.`, autoResume: `scheduled` }, { kind: `error`, message: `401.` }],
1022
+ ];
1023
+ const rows = events.flatMap((turn) => foldTurn(openingOf(`go`), turn, `stopped`)).filter((row) => row.noticeCode !== undefined);
1024
+ expect(rows.filter((row) => sandboxNoticeOf(row) === undefined)).toEqual([]);
1025
+ expect(new Set(rows.map((row) => row.noticeCode?.code))).toEqual(
1026
+ new Set([`synced`, `compacted`, `intoParent`, `intoParentClash`, `landHeld`, `outageWaiting`, `memoryHeld`, `kept`, `renewalWithdrawn`, `stopped`]),
1027
+ );
1028
+ });
1029
+
1030
+ // A newer sandbox's code must not cost an older app the whole page: the row parses, and the reader draws its words.
1031
+ it(`reads a row whose code this build does not know, and one written before rows carried codes`, () => {
1032
+ const newer: TranscriptRow = { role: `notice`, text: `Something new happened.`, noticeCode: { code: `somethingNew`, params: { count: 2 } } };
1033
+ expect(TranscriptRowSchema.parse(newer)).toEqual(newer);
1034
+ expect(sandboxNoticeOf(newer)).toBeUndefined();
1035
+ expect(TranscriptRowSchema.parse({ role: `notice`, text: `Stopped.` })).toEqual({ role: `notice`, text: `Stopped.` });
1036
+ expect(sandboxNoticeOf({ noticeCode: { code: `landConflict`, params: { files: `three` } } })).toBeUndefined();
1037
+ });
1038
+ });