@intentic/sandbox-contract 1.345.0 → 1.347.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 (222) hide show
  1. package/README.md +26 -14
  2. package/dist/contracts/accounts.contract.d.ts +0 -80
  3. package/dist/contracts/accounts.contract.d.ts.map +1 -1
  4. package/dist/contracts/agent.contract.d.ts +33 -2
  5. package/dist/contracts/agent.contract.d.ts.map +1 -1
  6. package/dist/contracts/agents.contract.d.ts +910 -29
  7. package/dist/contracts/agents.contract.d.ts.map +1 -1
  8. package/dist/contracts/agents.contract.js +43 -2
  9. package/dist/contracts/agents.contract.js.map +1 -1
  10. package/dist/contracts/device.contract.d.ts +2 -1
  11. package/dist/contracts/device.contract.d.ts.map +1 -1
  12. package/dist/contracts/endpoints.contract.d.ts +0 -1
  13. package/dist/contracts/endpoints.contract.d.ts.map +1 -1
  14. package/dist/contracts/providers.contract.d.ts +0 -1
  15. package/dist/contracts/providers.contract.d.ts.map +1 -1
  16. package/dist/contracts/runner.contract.d.ts +134 -123
  17. package/dist/contracts/runner.contract.d.ts.map +1 -1
  18. package/dist/contracts/settings.contract.d.ts +4 -0
  19. package/dist/contracts/settings.contract.d.ts.map +1 -1
  20. package/dist/contracts/system.contract.d.ts +102 -1
  21. package/dist/contracts/system.contract.d.ts.map +1 -1
  22. package/dist/contracts/translator.contract.d.ts +0 -80
  23. package/dist/contracts/translator.contract.d.ts.map +1 -1
  24. package/dist/events/agent-events.d.ts +43 -0
  25. package/dist/events/agent-events.d.ts.map +1 -1
  26. package/dist/events/agent-events.js +42 -37
  27. package/dist/events/agent-events.js.map +1 -1
  28. package/dist/events/failure-rows.d.ts +14 -0
  29. package/dist/events/failure-rows.d.ts.map +1 -0
  30. package/dist/events/failure-rows.js +109 -0
  31. package/dist/events/failure-rows.js.map +1 -0
  32. package/dist/events/sandbox-notice.d.ts +16 -0
  33. package/dist/events/sandbox-notice.d.ts.map +1 -1
  34. package/dist/events/sandbox-notice.js +1 -0
  35. package/dist/events/sandbox-notice.js.map +1 -1
  36. package/dist/events/system-events.d.ts +68 -0
  37. package/dist/events/system-events.d.ts.map +1 -1
  38. package/dist/events/system-events.js +7 -1
  39. package/dist/events/system-events.js.map +1 -1
  40. package/dist/index.d.ts +1158 -303
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/models/agent-lanes.d.ts.map +1 -1
  43. package/dist/models/agent-lanes.js +3 -5
  44. package/dist/models/agent-lanes.js.map +1 -1
  45. package/dist/models/agent-standing.d.ts +3 -20
  46. package/dist/models/agent-standing.d.ts.map +1 -1
  47. package/dist/models/agent-standing.js +12 -37
  48. package/dist/models/agent-standing.js.map +1 -1
  49. package/dist/models/model-pins.d.ts +3 -0
  50. package/dist/models/model-pins.d.ts.map +1 -1
  51. package/dist/models/model-pins.js +2 -0
  52. package/dist/models/model-pins.js.map +1 -1
  53. package/dist/models/plan-pools.d.ts +23 -5
  54. package/dist/models/plan-pools.d.ts.map +1 -1
  55. package/dist/models/plan-pools.js +116 -33
  56. package/dist/models/plan-pools.js.map +1 -1
  57. package/dist/protocol/doors/runner-protocol.d.ts +1 -0
  58. package/dist/protocol/doors/runner-protocol.d.ts.map +1 -1
  59. package/dist/protocol/doors/runner-protocol.js +1 -0
  60. package/dist/protocol/doors/runner-protocol.js.map +1 -1
  61. package/dist/protocol/raw/raw-json-routes.d.ts +3 -1
  62. package/dist/protocol/raw/raw-json-routes.d.ts.map +1 -1
  63. package/dist/protocol/raw/raw-routes.d.ts +4 -0
  64. package/dist/protocol/raw/raw-routes.d.ts.map +1 -1
  65. package/dist/protocol/raw/raw-routes.js +1 -0
  66. package/dist/protocol/raw/raw-routes.js.map +1 -1
  67. package/dist/schemas/agent-next.d.ts +21 -0
  68. package/dist/schemas/agent-next.d.ts.map +1 -0
  69. package/dist/schemas/agent-next.js +21 -0
  70. package/dist/schemas/agent-next.js.map +1 -0
  71. package/dist/schemas/agent-search.d.ts +45 -0
  72. package/dist/schemas/agent-search.d.ts.map +1 -0
  73. package/dist/schemas/agent-search.js +28 -0
  74. package/dist/schemas/agent-search.js.map +1 -0
  75. package/dist/schemas/agent.d.ts +9 -0
  76. package/dist/schemas/agent.d.ts.map +1 -1
  77. package/dist/schemas/agent.js +14 -10
  78. package/dist/schemas/agent.js.map +1 -1
  79. package/dist/schemas/agents-list.d.ts +16 -0
  80. package/dist/schemas/agents-list.d.ts.map +1 -1
  81. package/dist/schemas/agents.d.ts +48 -43
  82. package/dist/schemas/agents.d.ts.map +1 -1
  83. package/dist/schemas/agents.js +10 -33
  84. package/dist/schemas/agents.js.map +1 -1
  85. package/dist/schemas/compose-overlay.d.ts +9 -0
  86. package/dist/schemas/compose-overlay.d.ts.map +1 -0
  87. package/dist/schemas/compose-overlay.js +11 -0
  88. package/dist/schemas/compose-overlay.js.map +1 -0
  89. package/dist/schemas/composer-drafts.d.ts +169 -0
  90. package/dist/schemas/composer-drafts.d.ts.map +1 -0
  91. package/dist/schemas/composer-drafts.js +60 -0
  92. package/dist/schemas/composer-drafts.js.map +1 -0
  93. package/dist/schemas/device-commands.d.ts +63 -0
  94. package/dist/schemas/device-commands.d.ts.map +1 -0
  95. package/dist/schemas/device-commands.js +42 -0
  96. package/dist/schemas/device-commands.js.map +1 -0
  97. package/dist/schemas/device-folder.d.ts +11 -0
  98. package/dist/schemas/device-folder.d.ts.map +1 -1
  99. package/dist/schemas/device-folder.js +1 -0
  100. package/dist/schemas/device-folder.js.map +1 -1
  101. package/dist/schemas/devices.d.ts +14 -67
  102. package/dist/schemas/devices.d.ts.map +1 -1
  103. package/dist/schemas/devices.js +7 -45
  104. package/dist/schemas/devices.js.map +1 -1
  105. package/dist/schemas/environment.d.ts.map +1 -1
  106. package/dist/schemas/environment.js.map +1 -1
  107. package/dist/schemas/hosts.d.ts +27 -0
  108. package/dist/schemas/hosts.d.ts.map +1 -1
  109. package/dist/schemas/hosts.js +21 -0
  110. package/dist/schemas/hosts.js.map +1 -1
  111. package/dist/schemas/privacy-egress.d.ts +41 -0
  112. package/dist/schemas/privacy-egress.d.ts.map +1 -0
  113. package/dist/schemas/privacy-egress.js +30 -0
  114. package/dist/schemas/privacy-egress.js.map +1 -0
  115. package/dist/schemas/privacy.d.ts +1 -0
  116. package/dist/schemas/privacy.d.ts.map +1 -1
  117. package/dist/schemas/privacy.js +1 -0
  118. package/dist/schemas/privacy.js.map +1 -1
  119. package/dist/schemas/providers/plan-limits.d.ts +2 -130
  120. package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
  121. package/dist/schemas/providers/plan-limits.js +5 -23
  122. package/dist/schemas/providers/plan-limits.js.map +1 -1
  123. package/dist/schemas/providers/provider-oauth.d.ts +0 -82
  124. package/dist/schemas/providers/provider-oauth.d.ts.map +1 -1
  125. package/dist/schemas/providers/provider-oauth.js +1 -6
  126. package/dist/schemas/providers/provider-oauth.js.map +1 -1
  127. package/dist/schemas/providers/refusals.d.ts +64 -0
  128. package/dist/schemas/providers/refusals.d.ts.map +1 -0
  129. package/dist/schemas/providers/refusals.js +33 -0
  130. package/dist/schemas/providers/refusals.js.map +1 -0
  131. package/dist/schemas/sessions.js +1 -1
  132. package/dist/schemas/sessions.js.map +1 -1
  133. package/dist/schemas/settings.d.ts +4 -0
  134. package/dist/schemas/settings.d.ts.map +1 -1
  135. package/dist/schemas/settings.js +2 -0
  136. package/dist/schemas/settings.js.map +1 -1
  137. package/dist/schemas/system.d.ts +16 -0
  138. package/dist/schemas/system.d.ts.map +1 -1
  139. package/dist/schemas/system.js +11 -0
  140. package/dist/schemas/system.js.map +1 -1
  141. package/dist/state/definition.d.ts +8 -0
  142. package/dist/state/definition.d.ts.map +1 -1
  143. package/dist/state/runtime-state.d.ts +3 -0
  144. package/dist/state/runtime-state.d.ts.map +1 -1
  145. package/dist/state/runtime-state.js +1 -0
  146. package/dist/state/runtime-state.js.map +1 -1
  147. package/dist/state/sandbox-build.d.ts +20 -0
  148. package/dist/state/sandbox-build.d.ts.map +1 -0
  149. package/dist/state/sandbox-build.js +43 -0
  150. package/dist/state/sandbox-build.js.map +1 -0
  151. package/dist/state/ways-back.d.ts +4 -0
  152. package/dist/state/ways-back.d.ts.map +1 -0
  153. package/dist/state/ways-back.js +8 -0
  154. package/dist/state/ways-back.js.map +1 -0
  155. package/dist/text/land-rows.d.ts +14 -0
  156. package/dist/text/land-rows.d.ts.map +1 -0
  157. package/dist/text/land-rows.js +86 -0
  158. package/dist/text/land-rows.js.map +1 -0
  159. package/dist/text/transcript-fold.d.ts +4 -2
  160. package/dist/text/transcript-fold.d.ts.map +1 -1
  161. package/dist/text/transcript-fold.js +21 -187
  162. package/dist/text/transcript-fold.js.map +1 -1
  163. package/package.json +6 -6
  164. package/src/contracts/agents.contract.ts +49 -25
  165. package/src/events/agent-events.ts +95 -78
  166. package/src/events/failure-rows.ts +148 -0
  167. package/src/events/sandbox-notice.ts +11 -2
  168. package/src/events/system-events.ts +14 -4
  169. package/src/ids/project-dir.test.ts +2 -2
  170. package/src/ids/project-dir.ts +4 -4
  171. package/src/models/agent-lanes.ts +7 -21
  172. package/src/models/agent-runtimes.ts +2 -2
  173. package/src/models/agent-standing.ts +31 -96
  174. package/src/models/model-pins.test.ts +31 -1
  175. package/src/models/model-pins.ts +17 -0
  176. package/src/models/plan-pools.test.ts +159 -24
  177. package/src/models/plan-pools.ts +215 -83
  178. package/src/protocol/doors/runner-protocol.ts +4 -1
  179. package/src/protocol/raw/raw-routes.ts +1 -0
  180. package/src/schemas/agent-next.ts +34 -0
  181. package/src/schemas/agent-search.ts +51 -0
  182. package/src/schemas/agent.ts +21 -12
  183. package/src/schemas/agents.ts +24 -63
  184. package/src/schemas/compose-overlay.test.ts +35 -0
  185. package/src/schemas/compose-overlay.ts +19 -0
  186. package/src/schemas/composer-drafts.ts +86 -0
  187. package/src/schemas/device-commands.ts +89 -0
  188. package/src/schemas/device-folder.ts +9 -0
  189. package/src/schemas/devices.ts +24 -106
  190. package/src/schemas/environment.ts +8 -9
  191. package/src/schemas/hosts.ts +56 -0
  192. package/src/schemas/machine-presence.test.ts +33 -0
  193. package/src/schemas/privacy-egress.test.ts +83 -0
  194. package/src/schemas/privacy-egress.ts +86 -0
  195. package/src/schemas/privacy.ts +4 -0
  196. package/src/schemas/project-delivery.ts +1 -1
  197. package/src/schemas/providers/plan-limits.test.ts +16 -0
  198. package/src/schemas/providers/plan-limits.ts +8 -49
  199. package/src/schemas/providers/provider-oauth.ts +1 -13
  200. package/src/schemas/providers/refusals.ts +62 -0
  201. package/src/schemas/sessions.ts +1 -1
  202. package/src/schemas/settings.ts +2 -0
  203. package/src/schemas/system.ts +15 -0
  204. package/src/state/runtime-state.ts +5 -0
  205. package/src/state/sandbox-build.test.ts +95 -0
  206. package/src/state/sandbox-build.ts +96 -0
  207. package/src/state/ways-back.test.ts +36 -0
  208. package/src/state/ways-back.ts +19 -0
  209. package/src/state/workspace-state.ts +1 -1
  210. package/src/text/land-rows.ts +111 -0
  211. package/src/text/transcript-fold.test.ts +38 -9
  212. package/src/text/transcript-fold.ts +30 -232
  213. package/dist/events/undelivered.d.ts +0 -10
  214. package/dist/events/undelivered.d.ts.map +0 -1
  215. package/dist/events/undelivered.js +0 -22
  216. package/dist/events/undelivered.js.map +0 -1
  217. package/dist/state/history-state.d.ts +0 -3
  218. package/dist/state/history-state.d.ts.map +0 -1
  219. package/dist/state/history-state.js +0 -153
  220. package/dist/state/history-state.js.map +0 -1
  221. package/src/events/undelivered.ts +0 -33
  222. package/src/state/history-state.ts +0 -313
@@ -8,6 +8,7 @@ import { FastModeStateSchema } from "../schemas/providers/fast-mode.js";
8
8
  import { NeedSchema } from "../schemas/needs.js";
9
9
  import { PromptCacheOpeningSchema, PromptFingerprintSchema } from "../schemas/keep-warm.js";
10
10
  import { AgentReplySchema, UsageWindowSchema } from "../schemas/providers/plan-limits.js";
11
+ import { PrivacyBypassSchema } from "../schemas/privacy-egress.js";
11
12
  import { SubagentKindSchema, SubagentSdkRecordSchema, SubagentStatusSchema, SubagentVerificationSchema } from "../schemas/terminal.js";
12
13
  import { RetryLadderSchema } from "../schemas/turn-break.js";
13
14
  import { HandedOffSchema, HandoffOfferSchema } from "../schemas/providers/handoff.js";
@@ -19,6 +20,87 @@ import { agentNoticeEvent, agentStatusEvent } from "./agent-ui.js";
19
20
  // Every frame an agent turn streams, as one `kind`-discriminated union: turn facts (session, standing, cost) and the
20
21
  // attach stream a window joins through. One closed vocabulary, kept in one file.
21
22
 
23
+ // What a failure is, by name: the one vocabulary every runtime's failure is read into at its boundary (a runtime's typed
24
+ // error, or the shared reading of its words), and the one a reader draws a failure's line from. A failure none of these
25
+ // names goes out with no code: the unclassified kind, said as such, never a guess.
26
+ export const AgentErrorCodeSchema = z.enum([
27
+ "session-not-found",
28
+ "rate_limit",
29
+ // Codex ran the turn but warned about it (fallback model metadata); a notice, not a failure.
30
+ "codex-advisory",
31
+ "codex-reauth",
32
+ // An ACP capability requires its own loginCommand, not a native provider account reconnect.
33
+ "acp-auth-required",
34
+ // The Claude subscription credential is dead; only a reconnect fixes it, unlike no account connected.
35
+ "claude-reauth",
36
+ // The API refused this turn's token mid-flight, usually superseded by a rotation already re-minting.
37
+ "claude-token-refused",
38
+ // The account authenticates and has real usage pools, but the org disabled Claude Code for this seat.
39
+ "claude-not-entitled",
40
+ // Any other provider's account signs in but is refused service outright, for every model (Cursor's
41
+ // `[role_forbidden]` for a non-billable admin): filed against the account, so placement skips it.
42
+ "account-not-entitled",
43
+ // The provider failed transiently and in-turn retries did not outlast it; the daemon retries on
44
+ // backoff.
45
+ "provider-outage",
46
+ // The platform-owned free-trial pool failed after its bounded key walk; the failed call is refunded.
47
+ "trial-unavailable",
48
+ // The trial answered, but the selected upstream model/request cannot run through this sandbox.
49
+ "trial-model-unavailable",
50
+ // This account's platform-owned daily trial allowance is spent until its UTC reset.
51
+ "trial-exhausted",
52
+ // The harness read the message as a slash command it doesn't have; the model never saw it.
53
+ "unknown-command",
54
+ "grok-model-invalid",
55
+ "codex-model-invalid",
56
+ // The model is real and listed, but this plan's subscription does not have access to it.
57
+ "model-unavailable",
58
+ // The model cannot hold a turn this size, refused before sending; retrying the same request never
59
+ // helps.
60
+ "context-window-too-small",
61
+ // The model can only write one-shot jobs (Model.helperOnly: its server says it cannot call tools, or
62
+ // it is the quick-jobs local model), refused before sending; the words wait for another model.
63
+ "model-helper-only",
64
+ // The privacy shield is on, the provider is untrusted, and the runtime is one it cannot read by content at
65
+ // all (an ACP agent, Pi), refused before sending; the words wait for a covered provider.
66
+ "privacy-unshielded",
67
+ // The turn carries a tool list (an automation's `allowedTools`) and its runtime cannot keep one
68
+ // (AgentCapabilities.toolAllowlist), refused before sending; the words wait for a runtime that can.
69
+ "tools-unenforced",
70
+ // The protected execution-domain policy is unsupported or unreadable; refused before any model/helper ran.
71
+ "agent-domain-refused",
72
+ // The privacy shield found personal data in instructions the runtime loads itself, past every channel it
73
+ // could mask (Cursor's project rules, AGENTS.md), refused before sending; the words wait for the owner to
74
+ // let the provider read this conversation, trust it, or take the data out of those files.
75
+ "privacy-instructions",
76
+ // The session outgrew the model's context window mid-turn; resuming that session only overflows again,
77
+ // so the daemon re-runs the turn once in a fresh session carrying the hand-off.
78
+ "context-overflow",
79
+ "subscription-required",
80
+ "agent-busy",
81
+ // The sandbox is short of memory, held before anything spawned: a person once per spell, after which
82
+ // their sends run; background work on every short reading. Transient, no clock.
83
+ "sandbox-memory-low",
84
+ // The conversation's own view of the workspace (its mount namespace) did not come up in time, twice,
85
+ // before anything ran: the sandbox too loaded to set it up. Held like a memory hold, and sent again by
86
+ // itself once the sandbox has had room for a while (the memory wall's answer).
87
+ "workspace-setup",
88
+ // The history volume cannot fit a new conversation checkout while keeping its free-space floor.
89
+ "sandbox-disk-low",
90
+ // The runtime hit its own iteration ceiling and stopped; the work may be half done, so the user
91
+ // decides.
92
+ "turn-cap",
93
+ // The harness ended the turn without succeeding, with no modelled reason; the vendor's subtype rides
94
+ // verbatim.
95
+ "harness-incomplete",
96
+ // The installed engine is too old for the model; the provider names the version required, on `engine`.
97
+ "engine-version-floor",
98
+ // The provider's safety classifier stopped the turn partway, often a false positive on ordinary work.
99
+ // Held for a person: retry on the same model without the stopped response, or go on with another.
100
+ "safeguard-flagged",
101
+ ]);
102
+ export type AgentErrorCode = z.infer<typeof AgentErrorCodeSchema>;
103
+
22
104
  // One frame per emission, `kind`-discriminated; the daemon maps SDK message types onto this closed set, dropping any
23
105
  // without a UI mapping. `plan`/`question`/`permission` pause until `resolved` names the same requestId.
24
106
  export const AgentEventSchema = z.discriminatedUnion("kind", [
@@ -271,84 +353,13 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
271
353
  z.object({
272
354
  kind: z.literal("error"),
273
355
  message: z.string(),
274
- code: z
275
- .enum([
276
- "session-not-found",
277
- "rate_limit",
278
- // Codex ran the turn but warned about it (fallback model metadata); a notice, not a failure.
279
- "codex-advisory",
280
- "codex-reauth",
281
- // An ACP capability requires its own loginCommand, not a native provider account reconnect.
282
- "acp-auth-required",
283
- // The Claude subscription credential is dead; only a reconnect fixes it, unlike no account connected.
284
- "claude-reauth",
285
- // The API refused this turn's token mid-flight, usually superseded by a rotation already re-minting.
286
- "claude-token-refused",
287
- // The account authenticates and has real usage pools, but the org disabled Claude Code for this seat.
288
- "claude-not-entitled",
289
- // Any other provider's account signs in but is refused service outright, for every model (Cursor's
290
- // `[role_forbidden]` for a non-billable admin): filed against the account, so placement skips it.
291
- "account-not-entitled",
292
- // The provider failed transiently and in-turn retries did not outlast it; the daemon retries on
293
- // backoff.
294
- "provider-outage",
295
- // The platform-owned free-trial pool failed after its bounded key walk; the failed call is refunded.
296
- "trial-unavailable",
297
- // The trial answered, but the selected upstream model/request cannot run through this sandbox.
298
- "trial-model-unavailable",
299
- // This account's platform-owned daily trial allowance is spent until its UTC reset.
300
- "trial-exhausted",
301
- // The harness read the message as a slash command it doesn't have; the model never saw it.
302
- "unknown-command",
303
- "grok-model-invalid",
304
- "codex-model-invalid",
305
- // The model is real and listed, but this plan's subscription does not have access to it.
306
- "model-unavailable",
307
- // The model cannot hold a turn this size, refused before sending; retrying the same request never
308
- // helps.
309
- "context-window-too-small",
310
- // The model can only write one-shot jobs (Model.helperOnly: its server says it cannot call tools, or
311
- // it is the quick-jobs local model), refused before sending; the words wait for another model.
312
- "model-helper-only",
313
- // The privacy shield is on, the provider is untrusted, and the runtime is one it cannot read by content at
314
- // all (an ACP agent, Pi), refused before sending; the words wait for a covered provider.
315
- "privacy-unshielded",
316
- // The turn carries a tool list (an automation's `allowedTools`) and its runtime cannot keep one
317
- // (AgentCapabilities.toolAllowlist), refused before sending; the words wait for a runtime that can.
318
- "tools-unenforced",
319
- // The protected execution-domain policy is unsupported or unreadable; refused before any model/helper ran.
320
- "agent-domain-refused",
321
- // The privacy shield found personal data in instructions the runtime loads itself, past every channel it
322
- // could mask (Cursor's project rules, AGENTS.md), refused before sending; the words wait for the owner to
323
- // let the provider read this conversation, trust it, or take the data out of those files.
324
- "privacy-instructions",
325
- // The session outgrew the model's context window mid-turn; resuming that session only overflows again,
326
- // so the daemon re-runs the turn once in a fresh session carrying the hand-off.
327
- "context-overflow",
328
- "subscription-required",
329
- "agent-busy",
330
- // The sandbox is short of memory, held before anything spawned: a person once per spell, after which
331
- // their sends run; background work on every short reading. Transient, no clock.
332
- "sandbox-memory-low",
333
- // The conversation's own view of the workspace (its mount namespace) did not come up in time, twice,
334
- // before anything ran: the sandbox too loaded to set it up. Held like a memory hold, and sent again by
335
- // itself once the sandbox has had room for a while (the memory wall's answer).
336
- "workspace-setup",
337
- // The history volume cannot fit a new conversation checkout while keeping its free-space floor.
338
- "sandbox-disk-low",
339
- // The runtime hit its own iteration ceiling and stopped; the work may be half done, so the user
340
- // decides.
341
- "turn-cap",
342
- // The harness ended the turn without succeeding, with no modelled reason; the vendor's subtype rides
343
- // verbatim.
344
- "harness-incomplete",
345
- // The installed engine is too old for the model; the provider names the version required, on `engine`.
346
- "engine-version-floor",
347
- // The provider's safety classifier stopped the turn partway, often a false positive on ordinary work.
348
- // Held for a person: retry on the same model without the stopped response, or go on with another.
349
- "safeguard-flagged",
350
- ])
351
- .optional(),
356
+ // Who worded `message`. Absent, it is the provider's or the harness's own words, which a reader keeps behind a
357
+ // press and leads with a line of its own for the code: nothing the sandbox forgot to mark is ever a raw headline.
358
+ own: z
359
+ .boolean()
360
+ .optional()
361
+ .describe("The message is the sandbox's own sentence, fit to be the notice's line. Absent, it is what the provider or the harness said."),
362
+ code: AgentErrorCodeSchema.optional(),
352
363
  // safeguard-flagged only: what the classifier named, and where a retry resumes the session from.
353
364
  refusal: z
354
365
  .object({
@@ -359,6 +370,12 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
359
370
  .describe("The last session entry before the stopped response: a retry resumes the session there, so the model never sees what was stopped."),
360
371
  })
361
372
  .optional(),
373
+ // A privacy refusal only: the route the shield's verdict refused (shieldVerdict's `refuse.route`), typed, so a
374
+ // reader acts on where the bytes would have gone rather than on the sentence. Absent for a policy that could
375
+ // not be read, which refuses whatever the route.
376
+ privacyRoute: PrivacyBypassSchema.optional().describe(
377
+ "Where the bytes would have gone past the privacy shield: a runtime it cannot read, a runner calling its provider from its own machine, the container's own key, or a shared server started before the shield was on.",
378
+ ),
362
379
  // Which engine, the version refused, and the floor the provider requires; lets the client offer an install.
363
380
  engine: z
364
381
  .object({
@@ -0,0 +1,148 @@
1
+ import type { AgentEvent } from "./agent-events.js";
2
+ import { type FailureSaid, type NoticeCode, noticeCode } from "./sandbox-notice.js";
3
+ import type { TranscriptRow } from "./transcript.js";
4
+ import { anotherModelClears, turnedAwayCode } from "../policy/turned-away.js";
5
+
6
+ /* EVERY ROW A FAILURE LEAVES, in one place: the fold's one way from an `error` frame to a notice. Each row is coded,
7
+ whatever the frame carried, so no reader ever has to guess from a row's words whether it is a failure, or what kind:
8
+ a failure nobody named is `failed` with no `error`, the unclassified kind, never bare text (2026-10-20: a week of
9
+ transcripts held 105 failure rows written as bare provider words, a spent allowance's among them, against 34 coded). */
10
+
11
+ type ErrorFrame = Extract<AgentEvent, { kind: "error" }>;
12
+ type FailureRow = TranscriptRow & { readonly noticeCode: NoticeCode };
13
+
14
+ // A failure's own sentence, its code and who worded it, which every failure row carries ahead of the clause it adds.
15
+ const failureOf = (event: ErrorFrame): FailureSaid => ({ message: event.message, error: event.code, own: event.own === true ? true : undefined });
16
+
17
+ const failedRow = (event: ErrorFrame): FailureRow => ({
18
+ role: "notice",
19
+ text: event.message,
20
+ noticeCode: noticeCode({ code: "failed", params: failureOf(event) }),
21
+ });
22
+
23
+ // The row a refusal that ran nothing leaves when the conversation's queue holds the words (heldRow). A turn somebody typed
24
+ // is held in its conversation's queue for another press; a turn that started itself (an automation, a loop, a watch wake)
25
+ // has no typed message and nobody watching, so promising a resend there names a composer the reader is not looking at.
26
+ // Neither version promises a retry: whether one comes is the scheduler's decision, not this row's to state.
27
+ const undeliveredRow = (said: FailureSaid, unattended: boolean): FailureRow => ({
28
+ role: "notice",
29
+ text: unattended
30
+ ? `${said.message} Nothing ran, and nothing is held: this run started on its own, so there is no message waiting to be sent again.`
31
+ : `${said.message} Your message was not delivered: it is held for you to send again.`,
32
+ noticeCode: noticeCode({ code: "undelivered", params: { ...said, unattended: unattended || undefined } }),
33
+ });
34
+
35
+ // The row the same refusal leaves once the turn had already run: a trial spent, or a model refused, on a later step. The
36
+ // message was read and acted on, so it is neither undelivered nor held (turn-runs keeps only a turn turned away at the
37
+ // door), and saying so invited a resend of something that had already changed files (onboarding review 2026-10-18,
38
+ // F07). What it did stays in the conversation's copy; the press, when another model gets past the refusal, carries on there.
39
+ const stoppedPartwayRow = (said: FailureSaid, switchModel: boolean): FailureRow => {
40
+ const row: FailureRow = {
41
+ role: "notice",
42
+ text: switchModel
43
+ ? `${said.message} The run stopped partway. The changes it made so far are kept: continue with another model to pick up from here.`
44
+ : `${said.message} The run stopped partway. The changes it made so far are kept: send a message to pick up from here.`,
45
+ noticeCode: noticeCode({ code: "stoppedPartway", params: { ...said, switchModel: switchModel || undefined } }),
46
+ };
47
+ if (switchModel) {
48
+ row.noticeAction = "switchModel";
49
+ }
50
+ return row;
51
+ };
52
+
53
+ // The press a memory hold offers: the raise rides along only when the hold carried a ceiling to size it by, since a
54
+ // stall names none and raising one would not clear it.
55
+ const memoryPress = (event: ErrorFrame): "sendAnyway" | "sandboxMemory" => (event.memory === undefined ? "sendAnyway" : "sandboxMemory");
56
+
57
+ // A memory hold is asked once and never again that spell (resource-budget.ts), so the next send always runs. A turn the
58
+ // sandbox started itself (a fix press, a peer's message, a re-run) was never in a composer: its message stays above, and
59
+ // the press asks the sandbox to run the turn it kept rather than sending a queue that holds nothing.
60
+ const heldRow = (event: ErrorFrame): FailureRow => {
61
+ const unattended = event.unattended === true;
62
+ const memory = event.code === "sandbox-memory-low";
63
+ const said = failureOf(event);
64
+ if (event.held !== undefined && !unattended) {
65
+ return {
66
+ role: "notice",
67
+ text: memory
68
+ ? `${event.message} Nothing has run yet: the message above is kept here, and sending it anyway starts it.`
69
+ : `${event.message} Nothing has run yet: the message above is kept here to send again once that is sorted.`,
70
+ noticeAction: memory ? memoryPress(event) : "sendAgain",
71
+ sandboxHeld: true,
72
+ noticeCode: noticeCode({ code: "kept", params: { ...said, memory: memory || undefined } }),
73
+ };
74
+ }
75
+ if (!memory || unattended) {
76
+ return undeliveredRow(said, unattended);
77
+ }
78
+ return {
79
+ role: "notice",
80
+ text: `${event.message} Your message is held: send it again to start anyway.`,
81
+ noticeAction: memoryPress(event),
82
+ noticeCode: noticeCode({ code: "memoryHeld", params: said }),
83
+ };
84
+ };
85
+
86
+ // What an armed credential renewal promises, and what its row says instead once a later failure in the same run took
87
+ // that promise back (TranscriptFold.withdrawRenewal).
88
+ export const RENEWING = "The credential is being renewed and this turn continues automatically.";
89
+ export const RENEWAL_WITHDRAWN =
90
+ "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.";
91
+
92
+ // What a laddered wall's automatic re-runs are doing, said on the failure's own row so the count stays in the record.
93
+ const retryClause = (event: ErrorFrame): { text: string; code: NoticeCode } | undefined => {
94
+ const { retries } = event;
95
+ if (retries === undefined) {
96
+ return undefined;
97
+ }
98
+ const said = failureOf(event);
99
+ if (event.autoResume === "scheduled") {
100
+ return {
101
+ text: `Retrying by itself: attempt ${retries.made + 1} of ${retries.max}.`,
102
+ code: noticeCode({ code: "retrying", params: { ...said, attempt: retries.made + 1, of: retries.max } }),
103
+ };
104
+ }
105
+ return retries.made >= retries.max
106
+ ? {
107
+ text: `Retried ${retries.made} of ${retries.max} times by itself; nothing more is sent automatically.`,
108
+ code: noticeCode({ code: "retried", params: { ...said, made: retries.made, of: retries.max } }),
109
+ }
110
+ : undefined;
111
+ };
112
+
113
+ /** The row a turn-ending failure leaves: its own sentence plus one clause on what happens next, coded whatever it was.
114
+ * The live wait itself is drawn by the chat, not stored here. `ran`: an assistant row stands, so a door refusal stopped
115
+ * the turn partway. */
116
+ export const errorRow = (event: ErrorFrame, ran: boolean): FailureRow => {
117
+ const { message, code } = event;
118
+ const said = failureOf(event);
119
+ if (code === "provider-outage") {
120
+ const retry = retryClause(event);
121
+ return retry === undefined
122
+ ? {
123
+ role: "notice",
124
+ text: `${message} Nothing is retrying it, so the turn is waiting here.`,
125
+ noticeCode: noticeCode({ code: "outageWaiting", params: said }),
126
+ }
127
+ : { role: "notice", text: `${message} ${retry.text}`, noticeCode: retry.code };
128
+ }
129
+ if (code === "claude-token-refused") {
130
+ return event.autoResume === "scheduled"
131
+ ? {
132
+ role: "notice",
133
+ text: `${message} ${RENEWING}`,
134
+ noticeWait: "credentialRenewal",
135
+ noticeCode: noticeCode({ code: "renewing", params: said }),
136
+ }
137
+ : {
138
+ role: "notice",
139
+ text: `${message} Reconnect the account to pick this conversation back up.`,
140
+ noticeCode: noticeCode({ code: "reconnect", params: said }),
141
+ };
142
+ }
143
+ if (!turnedAwayCode(code)) {
144
+ const retry = retryClause(event);
145
+ return retry === undefined ? failedRow(event) : { role: "notice", text: `${message} ${retry.text}`, noticeCode: retry.code };
146
+ }
147
+ return ran && event.held === undefined ? stoppedPartwayRow(said, anotherModelClears(code)) : heldRow(event);
148
+ };
@@ -16,13 +16,22 @@ export const NoticeCodeSchema = z.object({
16
16
  });
17
17
  export type NoticeCode = z.infer<typeof NoticeCodeSchema>;
18
18
 
19
+ /** A failure's facts as every failure notice carries them (the `failure` params below), before the clause it adds. */
20
+ export interface FailureSaid {
21
+ readonly message: string;
22
+ readonly error?: string | undefined;
23
+ readonly own?: boolean | undefined;
24
+ }
25
+
19
26
  const count = z.number().int().nonnegative();
20
27
 
21
- // A failure's own sentence, which the notice's clause follows: the provider's words, or the sandbox's under `error`, its
22
- // failure code, for a reader that words a failure it knows itself.
28
+ // A failure's own sentence, which the notice's clause follows, its failure code (`error`; absent for a failure nobody
29
+ // named), and whether the sandbox worded the sentence (`own`). Absent `own`, the sentence is the provider's or the
30
+ // harness's: a reader leads with its own line for the code and keeps those words behind a press.
23
31
  const failure = {
24
32
  message: z.string(),
25
33
  error: z.string().optional(),
34
+ own: z.boolean().optional(),
26
35
  };
27
36
 
28
37
  // 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.
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { AgentSummarySchema } from "../schemas/agents.js";
3
- import { AccountUsageSchema, ProviderRefusalSchema } from "../schemas/providers/plan-limits.js";
3
+ import { AccountUsageSchema, type ProviderRefusal, ProviderRefusalSchema } from "../schemas/providers/plan-limits.js";
4
4
  import { MemberRoleSchema } from "../schemas/shared.js";
5
5
  import { WorkspaceTreeDeltaSchema } from "../schemas/workspace/workspace-tree.js";
6
6
 
@@ -147,11 +147,21 @@ export const AccountUsageChangedSchema = z.object({
147
147
  });
148
148
  export type AccountUsageChanged = z.infer<typeof AccountUsageChangedSchema>;
149
149
 
150
- // A provider's last refusal was recorded or settled, the observed half of "can I run on this". `refusal` absent means
151
- // settled, nothing stands.
152
- export const ProviderRefusalChangedSchema = z.object({ kind: z.literal("providerRefusal"), provider: z.string(), refusal: ProviderRefusalSchema.optional() });
150
+ // A provider's refusals on file changed (one recorded, settled or forgotten), the observed half of "can I run on this":
151
+ // every one it still holds, so a window judges by exactly what the daemon does. Empty means nothing stands. A daemon at
152
+ // the floor sends only its last `refusal` (absent: settled), so `refusals` stays optional; read both with refusalsOf.
153
+ export const ProviderRefusalChangedSchema = z.object({
154
+ kind: z.literal("providerRefusal"),
155
+ provider: z.string(),
156
+ refusals: z.array(ProviderRefusalSchema).optional(),
157
+ refusal: ProviderRefusalSchema.optional(),
158
+ });
153
159
  export type ProviderRefusalChanged = z.infer<typeof ProviderRefusalChangedSchema>;
154
160
 
161
+ // Every refusal a frame says stands, from a daemon that sends the whole list or one at the floor that sends its last.
162
+ export const refusalsOf = (event: ProviderRefusalChanged): readonly ProviderRefusal[] =>
163
+ event.refusals ?? (event.refusal === undefined ? [] : [event.refusal]);
164
+
155
165
  // What moved in the shared tree, once the daemon's resident copy re-listed a batch: a tab holding the generation it
156
166
  // counts from patches its tree, and one holding any other fetches it afresh.
157
167
  export const TreeChangedSchema = WorkspaceTreeDeltaSchema.extend({ kind: z.literal("treeChanged") });
@@ -59,8 +59,8 @@ test("a project dir is exactly one valid name under the workspace root", () => {
59
59
  expect(projectDirNameOf("/srv/work/my-app", "/srv/work")).toBe("my-app");
60
60
  });
61
61
 
62
- // A second folder of the same name takes a name that says which one it is: its parent's in front (machine_sandbox.rs
63
- // `unique_name` takes the first one free). Where a folder is (a drive, a network share) never lends a name.
62
+ // A second folder of the same name takes a name that says which one it is: its parent's in front. Where a folder is (a
63
+ // drive, a network share) never lends a name.
64
64
  const NAMED: readonly { readonly path: string; readonly names: string[] }[] = [
65
65
  { path: "C:\\Users\\ada\\code\\app", names: ["app-project", "code-app", "ada-code-app"] },
66
66
  { path: "C:\\Users\\ada\\work\\My App", names: ["My-App", "work-My-App", "ada-work-My-App"] },
@@ -56,10 +56,10 @@ export const projectDirNameFor = (folderBasename: string): string => {
56
56
  return RESERVED_PROJECT_DIR_NAMES.includes(name) ? `${name}-project` : name;
57
57
  };
58
58
 
59
- // The names a folder may take in this computer's sandbox, best first: its own name, then its parent's name in front of
60
- // it, then its grandparent's too (`app`, `code-app`, `radar-code-app`), each one `isProjectDirName` accepts and none
61
- // twice. The desktop app takes the first one free (machine_sandbox.rs `unique_name`) and numbers only past all of them,
62
- // so a second folder called `app` reads as `code-app`, which says which one it is, rather than `app-2` (2026-10-12). A
59
+ // The names a folder may take in a sandbox's /work, best first: its own name, then its parent's name in front of it,
60
+ // then its grandparent's too (`app`, `code-app`, `radar-code-app`), each one `isProjectDirName` accepts and none
61
+ // twice. Written (2026-10-12) for the desktop app's one sandbox per computer, where a second folder called `app` read as
62
+ // `code-app` rather than `app-2`; since 2026-10-21 each folder has a sandbox of its own and takes its own name. A
63
63
  // drive (`C:`) and a network share's server and share (`\\server\share`, `\\wsl.localhost\<distro>`) are where a folder
64
64
  // is, not what it is called, so they never lend a name.
65
65
  export const projectDirCandidates = (hostPath: string): string[] => {
@@ -1,17 +1,8 @@
1
1
  import type { AgentAttention } from "../schemas/agents.js";
2
2
  import type { AutomationApproval } from "../schemas/automations.js";
3
+ import { goesBySelf } from "../schemas/agent-next.js";
3
4
  import { parentOf } from "./agent-starter.js";
4
- import {
5
- type AgentStanding,
6
- blocked,
7
- heardByParent,
8
- limitScheduled,
9
- onlyOwnerCanAnswer,
10
- scheduledSend,
11
- turnInFlight,
12
- turnWorking,
13
- watching,
14
- } from "./agent-standing.js";
5
+ import { type AgentStanding, blocked, heardByParent, onlyOwnerCanAnswer, turnInFlight, turnWorking, watching } from "./agent-standing.js";
15
6
 
16
7
  // The lane every surface files a conversation in, and what of a fleet calls the person. Moved here from the editor
17
8
  // (_editor/web/src/models/agent-status/agentStatus-lanes.ts, which re-exports it) on 2026-10-16, so the daemon's
@@ -50,7 +41,7 @@ export const NO_ATTENTION: AgentAttention = {
50
41
  // "finished" needs no explicit timer since auto-land already flips a cleanly-completed turn to landed/idle within
51
42
  // ms, and any follow-up message moves the card back to active. Unread stays a badge, not a lane.
52
43
  export const laneOf = (agent: AgentStanding): FleetLane => {
53
- // A spent allowance nothing is booked for reaches Attention through `blocked` alone, no branch of its own, and ahead
44
+ // A spent allowance nothing carries on reaches Attention through `blocked` alone, no branch of its own, and ahead
54
45
  // of the `watching` branch below: a watch is the agent's own plan for waiting, a spent allowance an obstacle in
55
46
  // the way of it, and a watch firing into a shut window runs nothing (turn-admission.ts holds its words back).
56
47
  if (blocked(agent) || agent.status === "awaiting" || agent.status === "conflict") {
@@ -74,15 +65,10 @@ export const laneOf = (agent: AgentStanding): FleetLane => {
74
65
  if (watching(agent)) {
75
66
  return "active";
76
67
  }
77
- // A stranded turn booked to go again is Active: at the reset, or on the resume pass's next beat for a move to another
78
- // account. Reached only because `blocked` let it through; stated explicitly or the card would fall through to
79
- // Finished, which would announce as over work that resumes by itself.
80
- if (limitScheduled(agent)) {
81
- return "active";
82
- }
83
- // Messages booked for later are the same: the conversation starts again by itself, at a time or once the agent it
84
- // waits for has landed, so filing it Finished would announce as done work that has not started yet.
85
- if (scheduledSend(agent)) {
68
+ // Whatever carries it on by itself (`next`: a held turn re-run or moved, a send once memory frees, messages booked
69
+ // for a time or another agent's land) is Active: filing it Finished would announce as over work that resumes or has
70
+ // not started yet. An ending it answers reached here because `blocked` let it through on the same reading.
71
+ if (goesBySelf(agent)) {
86
72
  return "active";
87
73
  }
88
74
  // `landed`/`idle`/`ready` all finish here: `ready` is work held on the branch because auto-land is off, still
@@ -252,7 +252,7 @@ export const ACP: AgentCapabilities = {
252
252
  isolation: "cwd",
253
253
  commands: true,
254
254
  terminals: true,
255
- // A failure the agent words as a spent allowance or an outgrown window is coded (acp-agent.ts ACP_FAILURES), but the
255
+ // A failure the agent words as a spent allowance or an outgrown window is coded (error-frames.ts codedFailure), but the
256
256
  // words are each agent's own and the protocol marks no outage, so nothing promises the codes auto-resume keys off.
257
257
  recovery: false,
258
258
  warm: false,
@@ -332,7 +332,7 @@ export const PI: AgentCapabilities = {
332
332
  // session, as the Claude loop's Bash hook does (pi-hooks.ts).
333
333
  terminals: true,
334
334
  // Pi reports a provider failure as its own event (auto_retry_end, an errored message_end) carrying the provider's
335
- // sentence, status code included, which the shared rules code (pi-events.ts PI_FAILURES).
335
+ // sentence, status code included, which the shared rules code (error-frames.ts codedFailure).
336
336
  recovery: true,
337
337
  warm: false,
338
338
  // `--system-prompt` replaces Pi's base and `--append-system-prompt` adds to it, both per process; `--no-context-files`