@intentic/sandbox-contract 1.222.0 → 1.224.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 (86) hide show
  1. package/dist/chores/chores.js +1 -1
  2. package/dist/chores/chores.js.map +1 -1
  3. package/dist/chores/prompt.d.ts.map +1 -1
  4. package/dist/chores/prompt.js +3 -3
  5. package/dist/chores/prompt.js.map +1 -1
  6. package/dist/contracts/ports.contract.d.ts +11 -0
  7. package/dist/contracts/ports.contract.d.ts.map +1 -1
  8. package/dist/index.d.ts +11 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/schemas.d.ts +22 -0
  11. package/dist/schemas.d.ts.map +1 -1
  12. package/dist/schemas.js +3 -0
  13. package/dist/schemas.js.map +1 -1
  14. package/package.json +4 -4
  15. package/src/agent-catalog.ts +78 -78
  16. package/src/agent-run-model.ts +8 -8
  17. package/src/capability-env.ts +3 -3
  18. package/src/capability-secrets.ts +5 -5
  19. package/src/chores/chores.ts +67 -67
  20. package/src/chores/digest.ts +3 -3
  21. package/src/chores/extension-update.ts +3 -3
  22. package/src/chores/fix-deps.ts +3 -3
  23. package/src/chores/probes.ts +16 -16
  24. package/src/chores/prompt.ts +9 -9
  25. package/src/chores/stack.ts +17 -17
  26. package/src/chores/verdict.ts +23 -23
  27. package/src/contract-lock.ts +7 -7
  28. package/src/contracts/activity.contract.ts +1 -1
  29. package/src/contracts/agent.contract.ts +2 -2
  30. package/src/contracts/agents.contract.ts +11 -11
  31. package/src/contracts/automations.contract.ts +5 -5
  32. package/src/contracts/capabilities.contract.ts +7 -7
  33. package/src/contracts/chores.contract.ts +3 -3
  34. package/src/contracts/ci.contract.ts +2 -2
  35. package/src/contracts/claude.contract.ts +3 -3
  36. package/src/contracts/drafts.contract.ts +1 -1
  37. package/src/contracts/endpoints.contract.ts +2 -2
  38. package/src/contracts/extensions.contract.ts +8 -8
  39. package/src/contracts/git.contract.ts +6 -6
  40. package/src/contracts/grok.contract.ts +4 -4
  41. package/src/contracts/host.contract.ts +5 -5
  42. package/src/contracts/intentic.contract.ts +2 -2
  43. package/src/contracts/logs.contract.ts +1 -1
  44. package/src/contracts/loops.contract.ts +8 -8
  45. package/src/contracts/personas.contract.ts +8 -8
  46. package/src/contracts/ports.contract.ts +1 -1
  47. package/src/contracts/prepush.contract.ts +2 -2
  48. package/src/contracts/providers.contract.ts +4 -4
  49. package/src/contracts/public.contract.ts +1 -1
  50. package/src/contracts/push.contract.ts +2 -2
  51. package/src/contracts/secrets.contract.ts +2 -2
  52. package/src/contracts/sessions.contract.ts +2 -2
  53. package/src/contracts/settings.contract.ts +3 -3
  54. package/src/contracts/share.contract.ts +1 -1
  55. package/src/contracts/skills.contract.ts +2 -2
  56. package/src/contracts/system.contract.ts +9 -9
  57. package/src/contracts/usage.contract.ts +1 -1
  58. package/src/contracts/vpn.contract.ts +5 -5
  59. package/src/contracts/workflows.contract.ts +12 -12
  60. package/src/contracts/workspace.contract.ts +13 -13
  61. package/src/conversation-ids.ts +7 -7
  62. package/src/events.ts +136 -136
  63. package/src/history-state.ts +12 -12
  64. package/src/host-protocol.ts +7 -7
  65. package/src/hostnames.ts +15 -15
  66. package/src/index.ts +8 -8
  67. package/src/listener-protocol.ts +12 -12
  68. package/src/model-order.ts +30 -30
  69. package/src/output-fields.ts +9 -9
  70. package/src/path-refs.ts +6 -6
  71. package/src/publish-drafts.ts +6 -6
  72. package/src/quick-model.ts +15 -15
  73. package/src/routes.ts +19 -19
  74. package/src/runtime-state.ts +22 -22
  75. package/src/schemas.ts +930 -918
  76. package/src/search-globs.ts +6 -6
  77. package/src/session-names.ts +5 -5
  78. package/src/share-paths.ts +7 -7
  79. package/src/sse.ts +1 -1
  80. package/src/state-portability.ts +8 -8
  81. package/src/terminal-protocol.ts +3 -3
  82. package/src/title.ts +20 -20
  83. package/src/tunnel-ids.ts +7 -7
  84. package/src/versions.ts +5 -5
  85. package/src/workflow-faults.ts +11 -11
  86. package/src/workspace-state.ts +119 -119
package/src/events.ts CHANGED
@@ -37,16 +37,16 @@ export type AskQuestion = z.infer<typeof AskQuestionSchema>;
37
37
 
38
38
  // One per-tool permission prompt (the SDK's canUseTool callback, surfaced as a card). The daemon passes the
39
39
  // bridge's own rendered strings through rather than re-deriving them, so the prompt reads exactly as Claude
40
- // Code words it. `alwaysLabel` is present only when the SDK offered rules to persist without it the card
40
+ // Code words it. `alwaysLabel` is present only when the SDK offered rules to persist, without it the card
41
41
  // shows allow-once / deny alone, because there is nothing an "always" answer could remember.
42
42
  export const PermissionAskSchema = z.object({
43
43
  toolName: z.string(),
44
- // "Claude wants to read foo.txt" the full prompt sentence, when the bridge rendered one.
44
+ // "Claude wants to read foo.txt", the full prompt sentence, when the bridge rendered one.
45
45
  title: z.string().optional(),
46
46
  // Short noun phrase for the allow button ("Read file").
47
47
  displayName: z.string().optional(),
48
48
  description: z.string().optional(),
49
- // Why the prompt fired ('rule' | 'mode' | 'classifier' | …) shown as the card's muted subline.
49
+ // Why the prompt fired ('rule' | 'mode' | 'classifier' | …), shown as the card's muted subline.
50
50
  reason: z.string().optional(),
51
51
  // The file the request is about, when it is about one (workspace-root-relative).
52
52
  path: z.string().optional(),
@@ -54,10 +54,10 @@ export const PermissionAskSchema = z.object({
54
54
  });
55
55
  export type PermissionAsk = z.infer<typeof PermissionAskSchema>;
56
56
 
57
- /* ONE PRICED SERVICE RUN, OFFERED the card the daemon raises when the agent asks to run a premium service
57
+ /* ONE PRICED SERVICE RUN, OFFERED, the card the daemon raises when the agent asks to run a premium service
58
58
  * (platform/service-offer.ts). Everything with a number on it is the PLATFORM's answer, relayed verbatim from
59
59
  * the catalog it serves the daemon: the model that asked contributes `request` (the JSON it wants sent) and
60
- * `why` (its one line of rationale), and nothing else which is what makes the price on the card impossible
60
+ * `why` (its one line of rationale), and nothing else, which is what makes the price on the card impossible
61
61
  * to misquote, and the click on it the only way the run can happen. */
62
62
  export const ServiceOfferSchema = z.object({
63
63
  // The service, as the platform lists it: `<slug>` is what the run names, the rest is the catalog row.
@@ -66,26 +66,26 @@ export const ServiceOfferSchema = z.object({
66
66
  publisher: z.string(),
67
67
  description: z.string(),
68
68
  creditsPerRun: z.number(),
69
- /* Whether the platform still has this listing on probation a new provider that passed admission's
69
+ /* Whether the platform still has this listing on probation, a new provider that passed admission's
70
70
  * mechanical gates but has not yet served enough runs cleanly to graduate. It rides the card because
71
71
  * probation is the honest form of "listed automatically, not vouched for": the member approving the
72
72
  * spend is the person who should know that, and the platform is the only party that can say it. */
73
73
  probation: z.boolean().optional(),
74
- // The owner's meter as the platform stated it with the catalog what "N left today" renders from. Absent
74
+ // The owner's meter as the platform stated it with the catalog, what "N left today" renders from. Absent
75
75
  // when the platform sent none (it answers a meter only to a member, and membership was already checked
76
76
  // before this card went up, so in practice it is present; the field stays honest about the wire).
77
77
  credits: z.object({ allowance: z.number(), remaining: z.number(), resetsAt: z.string() }).optional(),
78
- // The request body the agent wants forwarded, verbatim shown so the owner can see what leaves.
78
+ // The request body the agent wants forwarded, verbatim, shown so the owner can see what leaves.
79
79
  request: z.string(),
80
- // The agent's one-line case for spending the only prose on the card that is the model's.
80
+ // The agent's one-line case for spending, the only prose on the card that is the model's.
81
81
  why: z.string().optional(),
82
82
  });
83
83
  export type ServiceOffer = z.infer<typeof ServiceOfferSchema>;
84
84
 
85
- /* WHAT A SERVICE STREAMS the provider's event vocabulary, stated once here and imported by everyone who
85
+ /* WHAT A SERVICE STREAMS, the provider's event vocabulary, stated once here and imported by everyone who
86
86
  * touches it: the platform validates each line of a provider's NDJSON against this before relaying it, the
87
87
  * daemon turns `status` events into transcript frames, and the editor renders them under the offer card.
88
- * A run is `status` lines (each replaces the last a spinner label, not a log) ending in exactly one
88
+ * A run is `status` lines (each replaces the last, a spinner label, not a log) ending in exactly one
89
89
  * `result`, whose `data` is the answer the agent acts on. The union is where future event kinds land when
90
90
  * services start streaming richer transcript elements; today's two are the smallest honest set. */
91
91
  export const ServiceStreamEventSchema = z.discriminatedUnion(`event`, [
@@ -94,21 +94,21 @@ export const ServiceStreamEventSchema = z.discriminatedUnion(`event`, [
94
94
  ]);
95
95
  export type ServiceStreamEvent = z.infer<typeof ServiceStreamEventSchema>;
96
96
 
97
- /* ONE MISSING CAPABILITY, ASKED FOR the card the daemon raises when the agent hits something this sandbox
97
+ /* ONE MISSING CAPABILITY, ASKED FOR, the card the daemon raises when the agent hits something this sandbox
98
98
  * is not connected to (capabilities/capability-offer.ts). `card` names the catalog card and `name` is that
99
- * card's own title, both resolved by the daemon from the catalog it validates the ask against the model
99
+ * card's own title, both resolved by the daemon from the catalog it validates the ask against, the model
100
100
  * that asked contributes `why` (its one line of rationale) and nothing else, which is what makes the card
101
101
  * impossible to misrepresent, and the click on it the only way anything gets connected. */
102
102
  export const CapabilityOfferSchema = z.object({
103
103
  // The catalog card being asked for, and how the catalog itself titles it ("Notion", "GitHub", "Docker").
104
104
  card: z.string(),
105
105
  name: z.string(),
106
- // The agent's one-line case for connecting it the only prose on the card that is the model's.
106
+ // The agent's one-line case for connecting it, the only prose on the card that is the model's.
107
107
  why: z.string().optional(),
108
108
  });
109
109
  export type CapabilityOffer = z.infer<typeof CapabilityOfferSchema>;
110
110
 
111
- /* The trailer the PLATFORM appends to every relayed run stream never provider-authored: it is the ledger
111
+ /* The trailer the PLATFORM appends to every relayed run stream, never provider-authored: it is the ledger
112
112
  * speaking after the stream settled. `ok` means the run served and was charged (`remaining` is the meter
113
113
  * after); `refunded` means the provider's stream died before its `result` and the charge was reversed. */
114
114
  export const ServiceRunReceiptSchema = z.object({
@@ -119,9 +119,9 @@ export const ServiceRunReceiptSchema = z.object({
119
119
  });
120
120
  export type ServiceRunReceipt = z.infer<typeof ServiceRunReceiptSchema>;
121
121
 
122
- /* ONE OUTBOUND USDC PAYMENT, OFFERED the card the daemon raises when the agent asks to pay an x402
122
+ /* ONE OUTBOUND USDC PAYMENT, OFFERED, the card the daemon raises when the agent asks to pay an x402
123
123
  * endpoint out of the sandbox wallet (wallet/payment-offer.ts). Every number on it is the daemon's own
124
- * arithmetic over the ENDPOINT's parsed challenge and the wallet's own ledger the model that asked
124
+ * arithmetic over the ENDPOINT's parsed challenge and the wallet's own ledger, the model that asked
125
125
  * contributes `why` (its one line of rationale) and nothing else, which is what makes the price on the card
126
126
  * impossible to misquote, and the click on it the only way the money can move. */
127
127
  export const PaymentOfferSchema = z.object({
@@ -132,20 +132,20 @@ export const PaymentOfferSchema = z.object({
132
132
  payTo: z.string(),
133
133
  network: z.string(),
134
134
  asset: z.string(),
135
- // The token's display name ("USDC") dollar-pegged, which is what lets every amount below read as USD.
135
+ // The token's display name ("USDC"), dollar-pegged, which is what lets every amount below read as USD.
136
136
  assetName: z.string(),
137
- // The exact price in display units ("0.10") the x402 exact scheme has no ranges, so this is the whole
137
+ // The exact price in display units ("0.10"), the x402 exact scheme has no ranges, so this is the whole
138
138
  // spend, not a ceiling.
139
139
  amountUsd: z.string(),
140
- // The wallet's meter as the daemon's ledger states it what "spent today / cap" renders from.
140
+ // The wallet's meter as the daemon's ledger states it, what "spent today / cap" renders from.
141
141
  spentTodayUsd: z.string(),
142
142
  dailyCapUsd: z.string(),
143
- // The agent's one-line case for paying the only prose on the card that is the model's.
143
+ // The agent's one-line case for paying, the only prose on the card that is the model's.
144
144
  why: z.string().optional(),
145
145
  });
146
146
  export type PaymentOffer = z.infer<typeof PaymentOfferSchema>;
147
147
 
148
- // One provider-advertised slash command an ACP agent's available_commands entry, or a Claude Code session's
148
+ // One provider-advertised slash command, an ACP agent's available_commands entry, or a Claude Code session's
149
149
  // supportedCommands() (its built-ins plus the workspace's own .claude/commands and any plugin/skill commands).
150
150
  // `hint` is the argument placeholder the popover shows after the name.
151
151
  export const AgentCommandSchema = z.object({
@@ -155,7 +155,7 @@ export const AgentCommandSchema = z.object({
155
155
  });
156
156
  export type AgentCommand = z.infer<typeof AgentCommandSchema>;
157
157
 
158
- // GET /agent/commands which provider's last-published list to read; absent = claude, matching AgentTurn.
158
+ // GET /agent/commands, which provider's last-published list to read; absent = claude, matching AgentTurn.
159
159
  export const AgentCommandsQuerySchema = z.object({ agent: AgentProviderSchema.optional() });
160
160
  export const AgentCommandsSchema = z.object({ commands: z.array(AgentCommandSchema) });
161
161
 
@@ -183,7 +183,7 @@ export type ToolKind = z.infer<typeof ToolKindSchema>;
183
183
  export const ToolCallStatusSchema = z.enum(["pending", "in_progress", "completed", "failed"]);
184
184
  export type ToolCallStatus = z.infer<typeof ToolCallStatusSchema>;
185
185
 
186
- // A file a tool call touches. Workspace-root-relative, forward-slash (the tree/file route space) adapters
186
+ // A file a tool call touches. Workspace-root-relative, forward-slash (the tree/file route space), adapters
187
187
  // normalize from the turn's cwd. `line` is 1-based.
188
188
  export const ToolCallLocationSchema = z.object({
189
189
  path: z.string(),
@@ -217,13 +217,13 @@ export type ToolCallContent = z.infer<typeof ToolCallContentSchema>;
217
217
  // ---- restored transcripts ----
218
218
  // What /sessions/{id} replays into a reopened tab, and what the daemon's own conversation record stores. It has
219
219
  // to REDRAW the transcript the user was looking at rather than merely paraphrase it, so it keeps the assistant's
220
- // thinking and the tool cards its turn ran which is also what lets a runtime handoff carry more than bare
220
+ // thinking and the tool cards its turn ran, which is also what lets a runtime handoff carry more than bare
221
221
  // prose across to the replacement session (see runtime-history.ts). Reconstructed from the stored
222
222
  // tool_use/tool_result blocks, so a restored card carries everything the live `tool_call` frame did except
223
223
  // the streaming-only correlation fields.
224
224
  //
225
225
  // One restored tool card. A subagent's own calls and its thinking nest under the Agent card that spawned them,
226
- // the same two fields (and the same recursion) the live ChatTool carries so a reopened chat redraws the
226
+ // the same two fields (and the same recursion) the live ChatTool carries, so a reopened chat redraws the
227
227
  // delegation it was showing instead of a leaf card with the whole child collapsed into its result text.
228
228
  // z.lazy because the shape refers to itself: a subagent that delegates nests one level deeper.
229
229
  export const RestoredToolCallSchema: z.ZodType<RestoredToolCall> = z.lazy(() =>
@@ -260,49 +260,49 @@ export const TurnNoteSchema = z.object({ title: z.string(), text: z.string() });
260
260
  export type TurnNote = z.infer<typeof TurnNoteSchema>;
261
261
 
262
262
  // One restored bubble. Each stored assistant message becomes its own, which is what reproduces the live
263
- // interleaving prose, the tool cards that prose introduced, then the next block of prose rather than
263
+ // interleaving, prose, the tool cards that prose introduced, then the next block of prose, rather than
264
264
  // collapsing a turn's whole narration into a single bubble with its tools hanging off the end.
265
265
  export const RestoredMessageSchema = z.object({
266
266
  /* `notice` is neither side of the conversation: it is something that HAPPENED to the turn, recorded so a
267
- * reopened session can say it. The one that matters is a refused turn a provider that answers "your
267
+ * reopened session can say it. The one that matters is a refused turn, a provider that answers "your
268
268
  * organization has disabled Claude subscription access" produced no assistant text, so a transcript of the
269
269
  * two speakers alone ends on the user's message and the session reads as broken. It is the same muted line
270
270
  * the live client draws for the codes it does not turn red (ChatRole's `notice`). */
271
271
  role: z.enum(["user", "assistant", "notice"]),
272
272
  text: z.string(),
273
- /* WHEN THIS TURN WAS SENT, in epoch milliseconds (user rows only) what the chat shows on the bubble it
273
+ /* WHEN THIS TURN WAS SENT, in epoch milliseconds (user rows only), what the chat shows on the bubble it
274
274
  * belongs to. The turn's START, not the moment the record was written: a turn that ran for twenty minutes
275
275
  * was still sent when the user pressed send, and a stamp taken at settlement would say the conversation
276
276
  * happened at the times its answers finished.
277
277
  *
278
278
  * Only the user's row carries one, because it is the only row whose moment the daemon actually knows. A
279
279
  * turn's frames arrive with no clock of their own, so an assistant bubble could only ever be stamped with
280
- * the whole turn's start or end a number that says nothing about when that particular block was written.
280
+ * the whole turn's start or end, a number that says nothing about when that particular block was written.
281
281
  * Rows recorded before this existed simply have none, and the chat draws nothing for them. */
282
282
  sentAt: z.number().optional(),
283
283
  // Files the user attached to this turn (user bubbles only) as workspace-relative paths, recovered from
284
- // the stored prompt's attachment note so a reopened tab redraws chips, not the injected protocol text.
284
+ // the stored prompt's attachment note, so a reopened tab redraws chips, not the injected protocol text.
285
285
  attachments: z.array(z.string()).optional(),
286
286
  /* The checkpoint this message can be rewound to (user bubbles only), filled in when the transcript is read
287
- * back. Not stored in the record itself it is looked up per read from the daemon's rewind points, which
288
- * a rewind rewrites so a reopened tab offers exactly the turns that are still there to go back to. */
287
+ * back. Not stored in the record itself, it is looked up per read from the daemon's rewind points, which
288
+ * a rewind rewrites, so a reopened tab offers exactly the turns that are still there to go back to. */
289
289
  checkpointId: z.string().optional(),
290
290
  thinking: z.string().optional(),
291
291
  tools: z.array(RestoredToolCallSchema).optional(),
292
- /* What the daemon added to this turn's message (user rows only) the same notes the live `preamble` frame
292
+ /* What the daemon added to this turn's message (user rows only), the same notes the live `preamble` frame
293
293
  * carries, recovered from the stored prompt when the transcript is read back. The reader that strips them out
294
294
  * of the user's words is the one that hands them over here instead of dropping them on the floor.
295
295
  *
296
- * On the message rather than as a row of its own, and that is load-bearing twice: they ARE part of what was
296
+ * On the message rather than as a row of its own, and that matters twice: they ARE part of what was
297
297
  * sent, and a record row per turn preamble would break the one-row-per-bubble correspondence a branch counts
298
- * with (see the client's recordedRows notices are drawn locally and never recorded). */
298
+ * with (see the client's recordedRows, notices are drawn locally and never recorded). */
299
299
  notes: z.array(TurnNoteSchema).optional(),
300
- /* THE USER WROTE THIS ROW WEARING THE AGENT'S VOICE (assistant rows only) the composer's "as agent" mode
300
+ /* THE USER WROTE THIS ROW WEARING THE AGENT'S VOICE (assistant rows only), the composer's "as agent" mode
301
301
  * appending straight into the record, with no turn behind it (agents.place).
302
302
  *
303
303
  * The flag exists for exactly one audience: the HUMAN re-reading the transcript, whose bubble carries a
304
304
  * quiet mark so that months later their own words don't pass as the agent's. The one reader that must
305
- * never see it is the agent itself a placed line reaches the model only through the handoff that seeds a
305
+ * never see it is the agent itself, a placed line reaches the model only through the handoff that seeds a
306
306
  * fresh runtime session (agent/runtime-history.ts), which renders role and text alone, so there the line is
307
307
  * indistinguishable from anything the agent genuinely said. Keep it that way: rendering this flag into any
308
308
  * agent-facing text would break the feature's whole contract. */
@@ -313,11 +313,11 @@ export type RestoredMessage = z.infer<typeof RestoredMessageSchema>;
313
313
  export const SessionTranscriptSchema = z.object({ messages: z.array(RestoredMessageSchema) });
314
314
  export const AgentTranscriptSchema = SessionTranscriptSchema.extend({ sessionId: z.string().optional() });
315
315
 
316
- /* WHAT A PUBLISHED CONVERSATION'S PAGE IS HANDED the whole of it, baked into the page as one JSON block.
316
+ /* WHAT A PUBLISHED CONVERSATION'S PAGE IS HANDED, the whole of it, baked into the page as one JSON block.
317
317
  *
318
318
  * A share has to keep working with nothing behind it: no daemon, no session, no sandbox that has to still be
319
319
  * running when the recipient finally opens the link. So the page carries its conversation rather than fetching
320
- * it, which also settles the security question by construction a page with nothing to ask has no way to ask
320
+ * it, which also settles the security question by construction, a page with nothing to ask has no way to ask
321
321
  * for something it was not given.
322
322
  *
323
323
  * The messages are the SAME RestoredMessage rows the app replays a reopened tab from, already filtered to the
@@ -326,7 +326,7 @@ export const AgentTranscriptSchema = SessionTranscriptSchema.extend({ sessionId:
326
326
  * is what the owner saw. */
327
327
  export const SharePayloadSchema = z.object({
328
328
  title: z.string(),
329
- // When the snapshot was taken, not when the conversation happened see SharedConversation.sharedAt.
329
+ // When the snapshot was taken, not when the conversation happened, see SharedConversation.sharedAt.
330
330
  sharedAt: z.number(),
331
331
  detail: ShareDetailSchema,
332
332
  messages: z.array(RestoredMessageSchema),
@@ -335,7 +335,7 @@ export type SharePayload = z.infer<typeof SharePayloadSchema>;
335
335
 
336
336
  /* THE THREE RESTORABLE CARDS, named so the turn journal can hold them verbatim: a parked turn's raised cards
337
337
  * are written down beside its prompt (sandbox turn-journal.ts), and a daemon death under the park restores the
338
- * very same frames instead of ending the turn `interrupted` the card the user was about to answer survives
338
+ * very same frames instead of ending the turn `interrupted`, the card the user was about to answer survives
339
339
  * the restart that killed the process holding it. The two handover cards are deliberately not among them:
340
340
  * `browser_help`'s Chromium and `terminal_help`'s waiting command both die with the container, so those parks
341
341
  * cannot be restored, only reported. */
@@ -355,7 +355,7 @@ export type ParkedCard = z.infer<typeof ParkedCardSchema>;
355
355
  // subagent itself, keyed by the same tool_use id those tagged frames carry.
356
356
  export const AgentEventSchema = z.discriminatedUnion("kind", [
357
357
  z.object({ kind: z.literal("session"), sessionId: z.string() }),
358
- /* WHERE AN ISOLATED TURN IS STANDING: the conversation's worktree identity its branch (agent/<id>) and
358
+ /* WHERE AN ISOLATED TURN IS STANDING: the conversation's worktree identity, its branch (agent/<id>) and
359
359
  * the ROOT repo's short base sha. First frame of the turn, before any provider frames, and again each time
360
360
  * the branch MOVES underneath it, which is why `base` names where the branch sits now rather than the
361
361
  * moment it was checked out.
@@ -363,15 +363,15 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
363
363
  * `unenforced` marks the degraded container: no CAP_SYS_ADMIN, so the turn's worktree could not be
364
364
  * bind-mounted over the workspace root and the harness is rewriting tool paths into it instead. That
365
365
  * fallback covers what arrives as tool input and not what a subprocess computes for itself, so the
366
- * operator needs to know this state used to be one line in the daemon log at boot, and the way it got
366
+ * operator needs to know, this state used to be one line in the daemon log at boot, and the way it got
367
367
  * noticed was files appearing in the main tree from agents that were supposed to be on branches. Repeated
368
368
  * on every emission, because it describes the turn and a client rebuilds its standing from the last frame.
369
369
  *
370
370
  * `sync` reports a rebase (agents/sync.ts) and rides here because this frame is already the turn's "where
371
- * you are standing" announcement. Present only when the branch was BEHIND the main line `commits` is how
371
+ * you are standing" announcement. Present only when the branch was BEHIND the main line, `commits` is how
372
372
  * many main-line commits it gained, `blocked` names the repos whose rebase would not apply and was rolled
373
373
  * back. Both can be non-empty at once in a multi-repo composition. Two moments produce it: before the turn
374
- * starts, and after a card the turn parked on is answered a question or a plan approval waits minutes
374
+ * starts, and after a card the turn parked on is answered, a question or a plan approval waits minutes
375
375
  * for a person, and the main line does not stop moving meanwhile. It is a notice and never a question: the
376
376
  * user is answering their agent, and the alternative to rebasing is not "stay safe" but "conflict at land
377
377
  * time", which interrupts them harder. */
@@ -386,9 +386,9 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
386
386
  // UNCOMMITTED changes in the main tree (the Changes panel is the review); conflicts ⇒ it stayed safely in
387
387
  // the worktree, and each named path carries WHY it would not apply (see LandConflictSchema) so the report
388
388
  // can say whether the user's own copy is at risk or the main line simply moved on underneath the agent.
389
- // held ⇒ auto-land is off for this agent: nothing was applied and nothing failed the delta is waiting
389
+ // held ⇒ auto-land is off for this agent: nothing was applied and nothing failed, the delta is waiting
390
390
  // on the branch for a deliberate Land (landed is false, conflicts absent).
391
- // `deps` rides along when the landed delta left the main tree declaring dependencies it does not have
391
+ // `deps` rides along when the landed delta left the main tree declaring dependencies it does not have,
392
392
  // the residue of an agent adding one without installing it, which every LATER turn would inherit through
393
393
  // the overlay it mounts over the main checkout. The daemon reconciles it rather than asking anyone to
394
394
  // (workspace/reconcile-deps.ts); this is the receipt, and `deferred` is the honest answer while other turns
@@ -400,23 +400,23 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
400
400
  held: z.boolean().optional(),
401
401
  deps: z.object({ missing: z.number(), started: z.array(z.string()), deferred: z.boolean() }).optional(),
402
402
  }),
403
- /* WHAT THE DAEMON ADDED TO THE USER'S MESSAGE before the model read it the exact words, not a summary of
403
+ /* WHAT THE DAEMON ADDED TO THE USER'S MESSAGE before the model read it, the exact words, not a summary of
404
404
  * them.
405
405
  *
406
406
  * A turn's prompt is not only what was typed: the daemon prepends notes the model needs and the user did not
407
- * write (agent/turn-preamble.ts owns the list a rebase that moved the branch, dependencies that are behind,
407
+ * write (agent/turn-preamble.ts owns the list, a rebase that moved the branch, dependencies that are behind,
408
408
  * workspace context retrieved for this very message, where an unenforced runtime's files really live). Those
409
409
  * notes change what the agent does, and for a long time the chat's only trace of any of them was one muted
410
- * line paraphrasing the rebase so a user watching an agent act on instructions they could not see had no
410
+ * line paraphrasing the rebase, so a user watching an agent act on instructions they could not see had no
411
411
  * way to find out what those instructions said. This frame is the fix: the note text verbatim, one entry per
412
412
  * note, rendered collapsed so it costs a click rather than a scroll.
413
413
  *
414
- * `title` is the note's own opening header, which is what the stripper already anchors on so the two
414
+ * `title` is the note's own opening header, which is what the stripper already anchors on, so the two
415
415
  * cannot drift, and a note nobody thought to title cannot reach the wire unlabelled.
416
416
  *
417
417
  * ONE MOMENT, always: the notes went in front of the user's own message before the turn started, so they hang
418
418
  * off that message and are stored on it, which is how a reopened tab still has them. Nothing is injected into
419
- * a RUNNING turn the rebase taken while a card sat waiting was the only thing that ever was, and it no
419
+ * a RUNNING turn, the rebase taken while a card sat waiting was the only thing that ever was, and it no
420
420
  * longer says anything to the model at all (agent/turn-preamble.ts). */
421
421
  z.object({ kind: z.literal("preamble"), notes: z.array(TurnNoteSchema) }),
422
422
  // The SDK's init handshake; carries the model it actually resolved for the turn.
@@ -424,44 +424,44 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
424
424
  // The pre-turn workspace snapshot's id (the attribution-fence "user" capture), emitted once before the
425
425
  // provider stream so the client can offer "restore to before this message" on the turn's user bubble.
426
426
  // Absent on isolated turns (they snapshot nothing) and when the tree was already clean at turn start.
427
- /* The workspace checkpoint capturing the state as this turn FOUND it what "go back to before this
427
+ /* The workspace checkpoint capturing the state as this turn FOUND it, what "go back to before this
428
428
  * message" restores. `index` is the message's position in the conversation's transcript, which the rewind
429
429
  * route addresses it by; absent on a turn with no conversation behind it (the bench, a one-shot), where
430
430
  * the id still powers a plain restore but there is no message to rewind to. */
431
431
  z.object({ kind: z.literal("checkpoint"), id: z.string(), index: z.number().int().nonnegative().optional() }),
432
- /* A MESSAGE THE USER SENT INTO THE TURN WHILE IT RAN the mid-turn steer, at the point in the stream where
432
+ /* A MESSAGE THE USER SENT INTO THE TURN WHILE IT RAN, the mid-turn steer, at the point in the stream where
433
433
  * the daemon accepted it (agent/agent-steering.ts).
434
434
  *
435
435
  * A frame rather than a client-local write, because all three things that were wrong about the steer are the
436
436
  * same missing fact: nothing in the run's log said WHEN it arrived.
437
437
  * - POSITION. The harness injects a steer between tool calls and the model simply keeps writing, with no
438
- * `result` in between so there is no `usage` boundary to retire the open bubble. The sending window
438
+ * `result` in between, so there is no `usage` boundary to retire the open bubble. The sending window
439
439
  * appended the user's words at the END of its transcript while the turn kept typing into the bubble
440
440
  * ABOVE them, and the answer to a question landed over the question.
441
441
  * - EVERY OTHER WINDOW. A run is rendered by any number of attached clients; only the one that posted the
442
442
  * steer knew about it, so the same conversation read differently in two places.
443
443
  * - THE RECORD. The settled turn is written down from this log (sessions/turn-transcript.ts), and one that
444
- * never held the steer wrote a transcript the message was missing from entirely which also put the
444
+ * never held the steer wrote a transcript the message was missing from entirely, which also put the
445
445
  * client's row count one ahead of the daemon's for the rest of the conversation, and those counts are
446
446
  * what a fork copies a prefix of and a rewind addresses.
447
447
  *
448
448
  * `text` is what the user typed, never the composed prompt: the editor-context and attachment notes the
449
449
  * route wraps around it are protocol, and redrawing them as the user's words is the same lie the stored
450
450
  * prompt is unwrapped to avoid. `attachments` are workspace-relative, like the turn's own. `sentAt` is the
451
- * instant the turn took the message, carried so the bubble wears the same clock live and after a reopen
451
+ * instant the turn took the message, carried so the bubble wears the same clock live and after a reopen,
452
452
  * a turn's own user row is stamped from the daemon's clock too, and a live bubble stamped from the
453
453
  * browser's would visibly jump when the record replaced it. */
454
454
  z.object({ kind: z.literal("steer"), text: z.string(), sentAt: z.number(), attachments: z.array(z.string()).optional() }),
455
455
  z.object({ kind: z.literal("delta"), text: z.string(), parentToolUseId: z.string().optional() }),
456
456
  // The prose block the `delta` frames were writing is finished. A turn emits several: the model says what
457
- // it is about to do, runs tools, reports what it found, runs more, then summarizes each a separate text
457
+ // it is about to do, runs tools, reports what it found, runs more, then summarizes, each a separate text
458
458
  // block in the SDK stream. Without this boundary the client has no way to tell them apart and glues the
459
459
  // whole turn's narration into one paragraph run, so the client retires its current bubble here and lets
460
460
  // what follows (the tool calls this block introduced, or the next block of prose) open a fresh one.
461
461
  z.object({ kind: z.literal("text_end"), parentToolUseId: z.string().optional() }),
462
462
  z.object({ kind: z.literal("thinking"), text: z.string(), parentToolUseId: z.string().optional() }),
463
463
  // A tool call starting (or, for backends that only report completions, arriving whole). `content` carries
464
- // structured output known at call time an Edit's diff is derived from its input, no result needed.
464
+ // structured output known at call time, an Edit's diff is derived from its input, no result needed.
465
465
  z.object({
466
466
  kind: z.literal("tool_call"),
467
467
  id: z.string(),
@@ -474,7 +474,7 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
474
474
  parentToolUseId: z.string().optional(),
475
475
  }),
476
476
  // A later state of a tool call, correlated by `id`. N updates per call: status transitions and/or fresh
477
- // content/locations both REPLACE the prior value (snapshot semantics, not append); absent ⇒ unchanged.
477
+ // content/locations, both REPLACE the prior value (snapshot semantics, not append); absent ⇒ unchanged.
478
478
  z.object({
479
479
  kind: z.literal("tool_call_update"),
480
480
  id: z.string(),
@@ -482,20 +482,20 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
482
482
  content: z.array(ToolCallContentSchema).optional(),
483
483
  locations: z.array(ToolCallLocationSchema).optional(),
484
484
  }),
485
- // The agent just started running Bash in its live `agent-<id>` tmux session the client surfaces that
485
+ // The agent just started running Bash in its live `agent-<id>` tmux session, the client surfaces that
486
486
  // terminal in the global panel. One per turn (the session is reused across a turn's commands, incl. subagents').
487
487
  z.object({ kind: z.literal("terminal"), session: z.string() }),
488
- // The agent just used a browser tool its Chromium is coming up (or already is) behind a watchable
488
+ // The agent just used a browser tool, its Chromium is coming up (or already is) behind a watchable
489
489
  // `browser-<id>` session, and the client surfaces it in the same panel as the terminals. One per turn, for
490
490
  // the same reason: one browser serves every browser call the turn makes.
491
491
  z.object({ kind: z.literal("browser"), session: z.string() }),
492
- /* THE AGENT STARTED ANOTHER AGENT an Agent/Task subagent, or a Codex/Grok CLI it drove from its own Bash
492
+ /* THE AGENT STARTED ANOTHER AGENT, an Agent/Task subagent, or a Codex/Grok CLI it drove from its own Bash
493
493
  * (see SubagentSessionSchema). One `subagent` frame per child, then `subagent_update` as it works: the same
494
- * call/update pair `tool_call`/`tool_call_update` uses, and for the same reason the fields that move
494
+ * call/update pair `tool_call`/`tool_call_update` uses, and for the same reason, the fields that move
495
495
  * (status, spend, what it is doing) arrive many times and must REPLACE, while the fields that identify it are
496
496
  * said once.
497
497
  *
498
- * `id` is the SPAWNING TOOL CALL's id the same id the client already nests the child's inner frames under
498
+ * `id` is the SPAWNING TOOL CALL's id, the same id the client already nests the child's inner frames under
499
499
  * (`parentToolUseId`), so both frames land on the card that spawned the child by the lookup that is already
500
500
  * there (mapToolAnywhere). No second correlation, and nothing to get wrong.
501
501
  *
@@ -510,7 +510,7 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
510
510
  description: z.string().optional(),
511
511
  model: z.string().optional(),
512
512
  background: z.boolean().optional(),
513
- // A delegation's tmux session the one live view a subagent doesn't have (SubagentSessionSchema).
513
+ // A delegation's tmux session, the one live view a subagent doesn't have (SubagentSessionSchema).
514
514
  terminal: z.string().optional(),
515
515
  }),
516
516
  z.object({
@@ -524,18 +524,18 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
524
524
  error: z.string().optional(),
525
525
  }),
526
526
  z.object({ kind: z.literal("todos"), items: z.array(TodoItemSchema) }),
527
- // The provider's own slash commands (ACP available_commands_update), replaced whole each time the
527
+ // The provider's own slash commands (ACP available_commands_update), replaced whole each time, the
528
528
  // composer's `/` popover lists them; invoking one is plain `/name …` prompt text (the ACP convention).
529
529
  z.object({ kind: z.literal("commands"), items: z.array(AgentCommandSchema) }),
530
530
  z.object({
531
531
  kind: z.literal("usage"),
532
- // The account that served this turn the client attributes the totals to it (tagged by streamAgent).
532
+ // The account that served this turn, the client attributes the totals to it (tagged by streamAgent).
533
533
  account: z.string().optional(),
534
534
  costUsd: z.number().optional(),
535
535
  inputTokens: z.number().optional(),
536
536
  outputTokens: z.number().optional(),
537
537
  // Provider prompt-cache buckets for the turn: tokens served from cache (read) and written to cache
538
- // (creation). Optional per provider Codex reports only cached input (read); runtimes/turns that
538
+ // (creation). Optional per provider. Codex reports only cached input (read); runtimes/turns that
539
539
  // don't report a bucket omit it. Lets the client show cache hit rate = read / (read + input).
540
540
  cacheReadTokens: z.number().optional(),
541
541
  cacheCreationTokens: z.number().optional(),
@@ -543,14 +543,14 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
543
543
  numTurns: z.number().optional(),
544
544
  }),
545
545
  // The live gate: the provider's answer to "may this turn run", pushed mid-turn. Drives the rate-limited
546
- // notice, not the headroom readouts see RateLimitInfoSchema.
546
+ // notice, not the headroom readouts, see RateLimitInfoSchema.
547
547
  RateLimitInfoSchema.extend({ kind: z.literal("rate_limit_info"), account: z.string().optional() }),
548
548
  /* WHAT SPEED THIS TURN ACTUALLY RAN AT, and when it isn't the one asked for, why. Emitted only when the
549
549
  * answer CHANGES within a turn, so the ordinary case is one frame at init and nothing after it; a turn that
550
550
  * enters cooldown mid-flight (fast mode has its own rate-limit pool, separate from the model's) emits a
551
551
  * second.
552
552
  *
553
- * This frame exists because fast mode fails SILENTLY and for a lot of different reasons the plan is free,
553
+ * This frame exists because fast mode fails SILENTLY and for a lot of different reasons, the plan is free,
554
554
  * extra usage is off, the model doesn't offer it, the turn is routed through the translator and so isn't
555
555
  * first-party, an env var disables it, the pool is in cooldown. Asking for it and getting standard speed is
556
556
  * indistinguishable, from the outside, from asking for it and getting it: same frames, same text, a bill
@@ -564,19 +564,19 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
564
564
  z.object({
565
565
  kind: z.literal("fast_mode"),
566
566
  state: FastModeStateSchema,
567
- // Absent when nothing is blocking fast mode including on `state: "on"`, and on an `off` that simply
567
+ // Absent when nothing is blocking fast mode, including on `state: "on"`, and on an `off` that simply
568
568
  // wasn't asked for.
569
569
  reason: z.string().optional(),
570
570
  }),
571
571
  /* The turn is alive but WAITING on the provider: a request failed transiently (5xx, 529, a dropped socket)
572
- * and the harness is retrying it inside this same turn. A status, not a failure nothing has been lost and
572
+ * and the harness is retrying it inside this same turn. A status, not a failure, nothing has been lost and
573
573
  * the turn may still finish normally, so the client renders it where "thinking" goes rather than in the
574
574
  * transcript.
575
575
  *
576
576
  * It exists because the retry budget is deliberately long (see CLAUDE_CODE_RETRY_WATCHDOG in
577
577
  * harness-credentials.ts): a turn can now sit silent for minutes riding out an outage, and silence reads as
578
578
  * a hang. The one action a user takes against an apparent hang is Stop, which is the only action that
579
- * actually loses the work so the wait has to be visible, with its own next-attempt clock.
579
+ * actually loses the work, so the wait has to be visible, with its own next-attempt clock.
580
580
  *
581
581
  * `attempt`/`maxAttempts` are the harness's own counters; `nextAttemptAt` (epoch ms) is when it will try
582
582
  * again, so the readout counts down instead of freezing on a number nobody can interpret. BOTH are optional
@@ -590,7 +590,7 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
590
590
  maxAttempts: z.number().optional(),
591
591
  nextAttemptAt: z.number().optional(),
592
592
  // The HTTP status behind it when there was one (529 reads as capacity, 429 as a rate limit, 500 as a
593
- // fault the client says which). Absent for a transport failure that never got a response, and for a
593
+ // fault, the client says which). Absent for a transport failure that never got a response, and for a
594
594
  // runtime that reports the refusal as prose rather than a code (grok-agent.ts reads it back off that).
595
595
  status: z.number().optional(),
596
596
  }),
@@ -606,7 +606,7 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
606
606
  QuestionCardSchema,
607
607
  PermissionCardSchema,
608
608
  // The agent's browser needs a person: it parked mid-sign-in on something it cannot clear itself (a captcha,
609
- // a password it does not hold, a phone check). `session` names the browser session on /browsers the card's
609
+ // a password it does not hold, a phone check). `session` names the browser session on /browsers, the card's
610
610
  // one action is going THERE, where the live stage and Take control already are; the Browsers banner and this
611
611
  // card resolve the same requestId. `account` is the capability the sign-in is for, so the card can say whose
612
612
  // login is stuck even after the browser has navigated somewhere unrecognizable.
@@ -619,7 +619,7 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
619
619
  }),
620
620
  // The agent's TERMINAL needs a person: a command it started is sitting at a prompt it cannot answer (a
621
621
  // one-time password, a security-key touch, a confirm). `session` names the tmux session on the terminal
622
- // panel the card's one action is going THERE, where the live pane and its prompt already are, which is
622
+ // panel, the card's one action is going THERE, where the live pane and its prompt already are, which is
623
623
  // the same division of labour the browser card has with /browsers. Not journalled for restore, and for the
624
624
  // browser card's reason one door along: the pane holding the prompt belongs to a process the restart kills.
625
625
  z.object({
@@ -628,21 +628,21 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
628
628
  session: z.string(),
629
629
  message: z.string(),
630
630
  }),
631
- /* A premium service run awaiting the owner's click. Raised OUTSIDE the turn generator the daemon's
631
+ /* A premium service run awaiting the owner's click. Raised OUTSIDE the turn generator, the daemon's
632
632
  * services route parks the agent's own `services run` call and pushes this frame into the live run
633
- * (platform/service-offer.ts) so unlike the four cards above it is not journalled for restore: its
633
+ * (platform/service-offer.ts), so unlike the four cards above it is not journalled for restore: its
634
634
  * waiter is the CLI's held connection, which dies with the daemon, and a restored card would offer
635
635
  * buttons nothing is waiting behind. Settles through the same `POST /agent/reply` as every other card. */
636
636
  z.object({ kind: z.literal("service_offer"), requestId: z.string(), offer: ServiceOfferSchema }),
637
637
  /* One event off an approved run's stream, pushed as the provider emits it so the settled card shows the
638
638
  * run living rather than a spinner of unknowable length. Today that is `status` lines; `result` stays off
639
- * the transcript on purpose (it is the agent's answer to act on, not the card's to duplicate) the frame
639
+ * the transcript on purpose (it is the agent's answer to act on, not the card's to duplicate), the frame
640
640
  * carries the whole union so richer event kinds land here without a contract break. */
641
641
  z.object({ kind: z.literal("service_event"), requestId: z.string(), event: ServiceStreamEventSchema }),
642
642
  /* How an approved run ended, pushed after the platform answered so the card can settle as a receipt
643
643
  * rather than a promise: `ok` served and charged, `refunded` failed to answer and charged nothing,
644
644
  * `refused` the platform said no after the click (a raced-out allowance). `remaining` is the meter after,
645
- * when the platform stated one. Skip needs no receipt nothing happened, and `resolved` already says so. */
645
+ * when the platform stated one. Skip needs no receipt, nothing happened, and `resolved` already says so. */
646
646
  z.object({
647
647
  kind: z.literal("service_receipt"),
648
648
  requestId: z.string(),
@@ -650,17 +650,17 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
650
650
  credits: z.number(),
651
651
  remaining: z.number().optional(),
652
652
  }),
653
- /* A missing capability asking for the owner's setup the agent hit something this sandbox is not
653
+ /* A missing capability asking for the owner's setup, the agent hit something this sandbox is not
654
654
  * connected to and raised the card instead of describing manual steps. Raised OUTSIDE the turn generator
655
655
  * exactly like the service offer above (the daemon's ask route parks the agent's `capabilities request`
656
656
  * call and pushes this frame into the live run; capabilities/capability-offer.ts), so it is not
657
657
  * journalled for restore either: its waiter is the CLI's held connection, which dies with the daemon.
658
658
  * Settles through the same `POST /agent/reply` as every other card. */
659
659
  z.object({ kind: z.literal("capability_offer"), requestId: z.string(), offer: CapabilityOfferSchema }),
660
- /* How an accepted ask ended, pushed once the daemon stops watching for the connection: `connected` the
660
+ /* How an accepted ask ended, pushed once the daemon stops watching for the connection: `connected`, the
661
661
  * capability came live while the agent waited (`id` is the connected instance, the agent's handle for it)
662
- * or `unfinished`, the setup did not complete while anyone was waiting (the deadline passed, or the
663
- * asking command died). A skip needs no outcome frame nothing was set up, and `resolved` already says
662
+ *, or `unfinished`, the setup did not complete while anyone was waiting (the deadline passed, or the
663
+ * asking command died). A skip needs no outcome frame, nothing was set up, and `resolved` already says
664
664
  * so. It is what settles the card's "waiting for you to finish setup" state on every surface. */
665
665
  z.object({
666
666
  kind: z.literal("capability_outcome"),
@@ -674,9 +674,9 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
674
674
  * CLI's held connection, which dies with the daemon. Settles through the same `POST /agent/reply`. */
675
675
  z.object({ kind: z.literal("payment_offer"), requestId: z.string(), offer: PaymentOfferSchema }),
676
676
  /* How an approved (or auto-approved) payment ended, pushed after the endpoint answered so the card can
677
- * settle as a receipt rather than a promise: `paid` the endpoint confirmed settlement (`transaction` is
678
- * the onchain hash when it stated one); `failed` the payment was refused or settlement failed, in which
679
- * case the signed authorization expires unused and NOTHING left the wallet. A skip needs no receipt
677
+ * settle as a receipt rather than a promise: `paid`, the endpoint confirmed settlement (`transaction` is
678
+ * the onchain hash when it stated one); `failed`, the payment was refused or settlement failed, in which
679
+ * case the signed authorization expires unused and NOTHING left the wallet. A skip needs no receipt,
680
680
  * nothing moved, and `resolved` already says so. */
681
681
  z.object({
682
682
  kind: z.literal("payment_receipt"),
@@ -686,22 +686,22 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
686
686
  transaction: z.string().optional(),
687
687
  network: z.string().optional(),
688
688
  }),
689
- // The card above named by `requestId` is released the user answered (or dismissed it, or the turn was
689
+ // The card above named by `requestId` is released, the user answered (or dismissed it, or the turn was
690
690
  // stopped out from under it), so the turn is executing again. Emitted by whoever parked, the moment its
691
691
  // waiter settles, because the park's END is otherwise invisible on this stream: nothing else here says
692
692
  // "that card is done", and it cannot be inferred from the next frame that happens along. Frames DO arrive
693
- // while a turn is parked the pausing tool's own `tool_call` regularly trails its card (the SDK queues
693
+ // while a turn is parked, the pausing tool's own `tool_call` regularly trails its card (the SDK queues
694
694
  // stream messages while dispatching an in-process MCP tool straight off the transport), and a card raised
695
695
  // beside a parallel tool call sits through that tool's whole life. See agents-registry.ts, which reads
696
696
  // this pair as the fleet's "needs you" state.
697
697
  //
698
698
  // `reply` says HOW it settled, and is what a transcript rebuilt from this log freezes the card with: a
699
699
  // reload replays the run from seq 0 and a second window renders it live, so both would otherwise restore
700
- // the card pending offering buttons on a requestId nothing holds any more, under a transcript that has
700
+ // the card pending, offering buttons on a requestId nothing holds any more, under a transcript that has
701
701
  // already moved on. It rides verbatim, exactly as the client POSTed it; absent, nobody answered (the turn
702
702
  // was stopped, or died under the card), which is not a decision and must not replay as one.
703
703
  z.object({ kind: z.literal("resolved"), requestId: z.string(), reply: AgentReplySchema.optional() }),
704
- // The turn's permission mode, whenever it changes the user's pick at turn start, then every move the
704
+ // The turn's permission mode, whenever it changes, the user's pick at turn start, then every move the
705
705
  // AGENT makes on its own (EnterPlanMode on a request that needs thinking through, ExitPlanMode once the
706
706
  // user approves). The composer's mode selector follows this, so the UI never lies about the live posture.
707
707
  z.object({ kind: z.literal("mode"), mode: PermissionModeSchema }),
@@ -714,31 +714,31 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
714
714
  .enum([
715
715
  "session-not-found",
716
716
  "rate_limit",
717
- // Codex ran the turn but warned about it (fallback model metadata) a notice, not a failure.
717
+ // Codex ran the turn but warned about it (fallback model metadata), a notice, not a failure.
718
718
  "codex-advisory",
719
719
  "codex-reauth",
720
720
  // The Claude subscription credential is dead (revoked, or its refresh token rejected) and only a
721
721
  // reconnect fixes it. Distinct from "no account connected": the account IS there, so the UI can
722
722
  // offer reconnect where the user already is and replay the message that bounced.
723
723
  "claude-reauth",
724
- // The API refused this turn's token MID-FLIGHT nearly always one superseded by a rotation,
724
+ // The API refused this turn's token MID-FLIGHT, nearly always one superseded by a rotation,
725
725
  // which Anthropic retires the moment its successor is minted. Distinct from claude-reauth: the
726
726
  // account is fine and the daemon re-mints on the spot, so this is usually a notice about a turn
727
727
  // that is coming back rather than a request for the user to do anything. `autoResume` says
728
728
  // which of the two: "scheduled" means the re-mint-and-re-run is armed, and its absence means
729
729
  // nothing is coming (the turn was already a resume, or it ran on a credential with nothing to
730
- // re-mint from) that is the case where reconnecting really is the fix.
730
+ // re-mint from), that is the case where reconnecting really is the fix.
731
731
  "claude-token-refused",
732
- /* THE ACCOUNT IS FINE AND STILL NOT ALLOWED TO RUN an Anthropic organization that has turned
732
+ /* THE ACCOUNT IS FINE AND STILL NOT ALLOWED TO RUN, an Anthropic organization that has turned
733
733
  * Claude Code off for this seat. The token authenticates, the plan's own usage endpoint answers
734
734
  * with real pools, and every turn is refused anyway, which is why it is its own code rather than
735
735
  * a member of either neighbour: a spent allowance comes back on a clock and a refused credential
736
736
  * comes back on a re-mint, and NEITHER of those is true here. Only an admin re-enabling access
737
- * is, so nothing is re-run and nothing asks the user to reconnect the one recovery that looks
737
+ * is, so nothing is re-run and nothing asks the user to reconnect, the one recovery that looks
738
738
  * plausible and is guaranteed to waste their time. */
739
739
  "claude-not-entitled",
740
740
  // The model provider itself failed transiently — 500/502/503, a 529 at capacity, a dropped
741
- // socket and the harness's own in-turn retries did not outlast it. Nothing about the workspace
741
+ // socket, and the harness's own in-turn retries did not outlast it. Nothing about the workspace
742
742
  // or the request is wrong, so the daemon remembers the turn and re-runs it on an escalating
743
743
  // backoff (provider-health.ts): the frame is a notice about a turn that is coming back, and
744
744
  // reaches the client as a plain failure only once the attempts are spent.
@@ -751,7 +751,7 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
751
751
  // This account's platform-owned daily trial allowance is spent until its UTC reset.
752
752
  "trial-exhausted",
753
753
  // The harness read the message as a slash command it doesn't have, and discarded everything
754
- // after the name the model never saw the message. Nothing was processed, so the client holds
754
+ // after the name, the model never saw the message. Nothing was processed, so the client holds
755
755
  // the text back instead of leaving the user to retype it (same treatment as claude-reauth).
756
756
  "unknown-command",
757
757
  "grok-model-invalid",
@@ -768,14 +768,14 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
768
768
  // claude-token-refused). "scheduled" = the resume is armed and this turn comes back by itself;
769
769
  // "available" = the daemon remembered the failed turn and arming THIS conversation
770
770
  // (AgentSummarySchema.resumeAfterOutage) picks up that same resume, which is what the chat's offer
771
- // banner hangs off outage only, since a renewal is never gated on a posture at all. The two words
771
+ // banner hangs off, outage only, since a renewal is never gated on a posture at all. The two words
772
772
  // are read against the effective posture (the conversation's override, else the sandbox default), so a
773
773
  // chat armed on its own says "scheduled" while the unarmed board around it says "available".
774
774
  // Absent means there is nothing automatic to resume: a spent usage limit never has one,
775
775
  // and a refused credential has none once re-minting it has already been tried and failed.
776
776
  autoResume: z.enum(["scheduled", "available"]).optional(),
777
777
  /* provider-outage only: the shape of the wait. `retryAt` (epoch seconds) is when the next attempt is
778
- * due not a fixed cadence, because an outage has no reset instant to aim at and hammering a provider
778
+ * due, not a fixed cadence, because an outage has no reset instant to aim at and hammering a provider
779
779
  * that is down only spends tokens on refusals, so each attempt waits longer than the last
780
780
  * (provider-health.ts owns the schedule).
781
781
  *
@@ -789,11 +789,11 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
789
789
  export type AgentEvent = z.infer<typeof AgentEventSchema>;
790
790
 
791
791
  // The /agent/attach stream: a head frame identifying the run, then its AgentEvents stamped with their 1-based
792
- // seq (the client's resume cursor), then `end` when the run is over every frame delivered, nothing more
792
+ // seq (the client's resume cursor), then `end` when the run is over, every frame delivered, nothing more
793
793
  // coming. A stream that closes WITHOUT `end` was dropped mid-run; the client re-attaches with `after` = the
794
794
  // last seq it holds. The head's `prompt`/`startedAt` let a window that didn't initiate the turn (a reload, a
795
795
  // second window, another device) synthesize the user bubble and the elapsed readout; its `seq` is the log
796
- // length at attach time the replay/live boundary.
796
+ // length at attach time, the replay/live boundary.
797
797
  export const AttachFrameSchema = z.discriminatedUnion("kind", [
798
798
  z.object({ kind: z.literal("attached"), run: z.string(), prompt: z.string(), startedAt: z.number(), seq: z.number() }),
799
799
  z.object({ kind: z.literal("frame"), seq: z.number(), event: AgentEventSchema }),
@@ -806,10 +806,10 @@ export type AttachFrame = z.infer<typeof AttachFrameSchema>;
806
806
  *
807
807
  * They live on the wire rather than in the daemon because the CLIENT has to recognise them too: an attach head
808
808
  * carries the run's prompt verbatim, and a window joining a resumed run would otherwise render the note as a
809
- * message the USER wrote the same words the user already said one run up, with a machine's preamble on them.
809
+ * message the USER wrote, the same words the user already said one run up, with a machine's preamble on them.
810
810
  * Recognising the prefix is what lets that window reuse the bubble that is already there instead. */
811
811
  // The instruction the three whole-turn re-runs share: what follows the note is the original request, repeated.
812
- // `answered` deliberately does not carry it what follows THAT note is not a repetition but the user's answer,
812
+ // `answered` deliberately does not carry it, what follows THAT note is not a repetition but the user's answer,
813
813
  // and telling the model to "continue from that point instead of starting over" about words it has never seen
814
814
  // is how a resume reads as the user contradicting themselves.
815
815
  const REPEATED =
@@ -818,7 +818,7 @@ export const RESUME_NOTES = {
818
818
  auth: `The Claude credential that interrupted this conversation has been renewed, and this turn resumed automatically. ${REPEATED}`,
819
819
  outage: `The model provider was briefly unavailable and interrupted this conversation; this turn resumed automatically. ${REPEATED}`,
820
820
  restart: `The sandbox restarted while this turn was running, which stopped it, and this turn resumed automatically once it came back. ${REPEATED}`,
821
- // A turn that was PARKED on the user when the daemon died: nothing re-runs at boot the card is restored
821
+ // A turn that was PARKED on the user when the daemon died: nothing re-runs at boot, the card is restored
822
822
  // instead, and this is the turn their answer starts (turn-resume.ts). What rides below the note is the
823
823
  // answer itself, so the model picks the session back up at exactly the decision it had handed over.
824
824
  answered:
@@ -830,7 +830,7 @@ export const RESUME_NOTES = {
830
830
  export const withResumeNote = (prompt: string, note: string): string =>
831
831
  Object.values(RESUME_NOTES).some((known) => prompt.startsWith(known)) ? prompt : `${note}\n\n${prompt}`;
832
832
 
833
- // The user's own words inside a resumed prompt the note and its explanation stripped back off. Returns the
833
+ // The user's own words inside a resumed prompt, the note and its explanation stripped back off. Returns the
834
834
  // prompt unchanged when it is not a resume, so a caller can hand every attach head through it.
835
835
  export const withoutResumeNote = (prompt: string): string => {
836
836
  const note = Object.values(RESUME_NOTES).find((known) => prompt.startsWith(known));
@@ -839,22 +839,22 @@ export const withoutResumeNote = (prompt: string): string => {
839
839
 
840
840
  export type ResumeReason = keyof typeof RESUME_NOTES;
841
841
 
842
- /* HOW A RESUMED TURN READS TO THE PERSON the same interruption the note above tells the model, said in the
842
+ /* HOW A RESUMED TURN READS TO THE PERSON, the same interruption the note above tells the model, said in the
843
843
  * transcript's own voice instead.
844
844
  *
845
845
  * Stripping the note out of the user's words is only half the job, and for years it was the only half anyone
846
846
  * did: what a reopened conversation showed was a paragraph of machine prose stapled to the front of a message
847
- * the user had already sent once, directly under their own copy of it. Both halves of that are wrong it was
847
+ * the user had already sent once, directly under their own copy of it. Both halves of that are wrong, it was
848
848
  * never their sentence, and the words under it are a REPEAT rather than something new they said.
849
849
  *
850
850
  * So the two shapes below, which is the whole of what a reader has to be told:
851
851
  *
852
- * `notice` the three whole-turn re-runs. The words under the note are already in the transcript one turn up,
852
+ * `notice`, the three whole-turn re-runs. The words under the note are already in the transcript one turn up,
853
853
  * so the repeat is dropped entirely and the interruption takes its place as a muted line, sitting with the
854
854
  * failure line it resolves ("Failed to authenticate…") and reading like every other thing that HAPPENED to a
855
855
  * turn rather than like something anybody typed.
856
856
  *
857
- * `note` the answered case, where what rides under the note is the user's actual answer to a card and belongs
857
+ * `note`, the answered case, where what rides under the note is the user's actual answer to a card and belongs
858
858
  * in the transcript as their words. Nothing is dropped; the explanation rides that message as a collapsed row,
859
859
  * the same disclosure every other daemon-written note gets (TurnNote). */
860
860
  export type ResumeDisclosure = { readonly kind: "notice"; readonly text: string } | { readonly kind: "note"; readonly note: TurnNote };
@@ -874,7 +874,7 @@ export const resumeDisclosure = (prompt: string): ResumeDisclosure | undefined =
874
874
  };
875
875
 
876
876
  // One parsed line from `intentic … --output ndjson` (engine events, provider `log`, the terminal `result`).
877
- // Open-ended by design the sandbox consumes the wire shape, not @intentic/engine's types so a string
877
+ // Open-ended by design, the sandbox consumes the wire shape, not @intentic/engine's types, so a string
878
878
  // `kind` plus arbitrary extra fields pass through. The apply-events tail (intentic.contract `applyEvents`) rides
879
879
  // this same loose shape with three daemon/CLI-minted sentinel kinds alongside the engine ones: {kind:"start"}
880
880
  // (first line, written when the run's file is reset), {kind:"exit",code} (last line, on the CLI process exit),
@@ -888,7 +888,7 @@ export const HeartbeatSchema = z.object({ kind: z.literal("heartbeat") });
888
888
  export type Heartbeat = z.infer<typeof HeartbeatSchema>;
889
889
 
890
890
  // One step of the daemon's boot chain. `key` is the stable id the daemon declares it under, `label` the words
891
- // the browser shows. A step that FAILED is still a step that finished the boot chain is log-and-continue by
891
+ // the browser shows. A step that FAILED is still a step that finished, the boot chain is log-and-continue by
892
892
  // design (see main.ts), so a failure degrades one subsystem rather than holding the gate closed forever.
893
893
  export const BootStepSchema = z.object({
894
894
  key: z.string(),
@@ -900,13 +900,13 @@ export const BootStepSchema = z.object({
900
900
  export type BootStep = z.infer<typeof BootStepSchema>;
901
901
 
902
902
  /* WHERE THE DAEMON IS IN ITS BOOT. The listeners come up before the state they serve has converged (main.ts:
903
- * "listen first, converge behind the gate"), which is what stops a restart from reading as an outage but it
903
+ * "listen first, converge behind the gate"), which is what stops a restart from reading as an outage, but it
904
904
  * also means the daemon spends the first seconds of every boot both reachable and unable to answer, and until
905
905
  * this frame existed the browser had no way to tell that apart from a healthy sandbox. It painted an operable
906
906
  * workspace off its persisted cache and then parked every request the user made against the readiness gate.
907
907
  *
908
908
  * The step list is declared UP FRONT and sent whole, pending entries included, so the browser can say "4 of 11,
909
- * loading the conversation registry" rather than "something is happening" a boot that takes minutes has one
909
+ * loading the conversation registry" rather than "something is happening", a boot that takes minutes has one
910
910
  * slow step, and naming it is the whole point. Snapshot-not-diff, like every other roster on this stream. */
911
911
  export const BootProgressSchema = z.object({
912
912
  // False only while the chain is still converging. The browser holds every daemon read until this is true.
@@ -923,26 +923,26 @@ export const BootSchema = z.object({ kind: z.literal("boot"), ...BootProgressSch
923
923
  export type Boot = z.infer<typeof BootSchema>;
924
924
 
925
925
  // The stream's first frame: the workspace's stable identity, minted at the first boot of an empty /work. The
926
- // browser remembers it per sandbox id and drops that sandbox's persisted query cache when it changes a wiped
926
+ // browser remembers it per sandbox id and drops that sandbox's persisted query cache when it changes, a wiped
927
927
  // and recreated workspace (cleanup.sh + reconnect keeps the same sandbox id) must not be painted from the
928
928
  // previous workspace's cache. `build` is the same guard against a different axis: the daemon's own compiled
929
929
  // tree, so an image update (or a `pnpm build:sandbox` swap in dev) drops what the browser cached from the
930
930
  // PREVIOUS build instead of hydrating payloads the new one no longer shapes that way.
931
931
  //
932
- // It also advertises `routes` the contract route names (`vpn.list`, `kimi.models`) this daemon actually
932
+ // It also advertises `routes`, the contract route names (`vpn.list`, `kimi.models`) this daemon actually
933
933
  // implements, from ITS build of the contract. A browser is routinely newer than the daemon it talks to (a
934
934
  // released app plane serves whatever image each user last pulled; in local dev the web app is always ahead of
935
- // the last `pnpm build:sandbox`), and that stays fully supported the browser just compares the two sets so a
935
+ // the last `pnpm build:sandbox`), and that stays fully supported, the browser just compares the two sets so a
936
936
  // route the daemon predates surfaces as a named, explained gap instead of a bare 404 nobody can attribute.
937
937
  //
938
938
  // `shapes` answers the half `routes` structurally cannot: a route BOTH builds have, whose payload changed
939
- // between them. Names match, so nothing 404s the call goes out and a field the browser expects is simply
939
+ // between them. Names match, so nothing 404s, the call goes out and a field the browser expects is simply
940
940
  // missing from the answer. It is a map of route name → a fingerprint of that route's input and output schema
941
941
  // (see routes.ts), so a difference is a named route rather than "something, somewhere, moved". Beside `routes`
942
942
  // rather than folded into it: existence covers every route, shape covers only the ones that can be expressed.
943
943
  //
944
944
  // Every added field is optional: a daemon built before one simply says nothing, and the browser's fallback is
945
- // the pre-existing behaviour routes all assumed present, shapes all assumed to agree, the daemon assumed
945
+ // the pre-existing behaviour, routes all assumed present, shapes all assumed to agree, the daemon assumed
946
946
  // ready, the cache left alone. That is also why `routes` keeps its bare-string-array shape: an image already in
947
947
  // the wild sends exactly that, and a breaking change here would fail the hello frame's own parse and take the
948
948
  // whole event stream down for precisely the skew this frame exists to describe.
@@ -956,25 +956,25 @@ export const HelloSchema = z.object({
956
956
  });
957
957
  export type Hello = z.infer<typeof HelloSchema>;
958
958
 
959
- // The FULL discovered repo set (sorted root-relative ids), pushed whenever it changes a clone, a scaffold,
959
+ // The FULL discovered repo set (sorted root-relative ids), pushed whenever it changes, a clone, a scaffold,
960
960
  // or a deleted repo re-frames it. The watcher descent-ignores .git, so no workspaceChanged path pattern can
961
961
  // detect a repo appearing; the daemon diffs its own discovery instead. Snapshot-not-diff, last frame wins.
962
962
  export const ReposChangedSchema = z.object({ kind: z.literal("reposChanged"), repos: z.array(z.string()) });
963
963
  export type ReposChanged = z.infer<typeof ReposChangedSchema>;
964
964
 
965
965
  // A batch of workspace paths that just changed on disk (created/edited/deleted), pushed on the same /events
966
- // stream as the heartbeat so the browser refreshes the tree + any open file live the agent edits files
966
+ // stream as the heartbeat so the browser refreshes the tree + any open file live, the agent edits files
967
967
  // out-of-band (its own Write/Edit/Bash tools), so there's no HTTP mutation to hang an invalidate on. Paths are
968
968
  // root-relative, forward-slash (the tree/file route space). An empty array means "something changed, refetch the
969
- // tree" a burst too large to enumerate, or a reconnect recovery where we don't know what was missed.
969
+ // tree", a burst too large to enumerate, or a reconnect recovery where we don't know what was missed.
970
970
  export const WorkspaceChangedSchema = z.object({ kind: z.literal("workspaceChanged"), paths: z.array(z.string()) });
971
971
  export type WorkspaceChanged = z.infer<typeof WorkspaceChangedSchema>;
972
972
 
973
- /* THE REPOS WHOSE REFS JUST MOVED a commit, a checkout, a branch or tag, a rebase started or aborted.
973
+ /* THE REPOS WHOSE REFS JUST MOVED, a commit, a checkout, a branch or tag, a rebase started or aborted.
974
974
  *
975
975
  * A third push for the same reason as the two above, and the reason is structural: a repo's git dir does not
976
976
  * live under /work at all (it is relocated onto /history so an isolated turn's worktree can stand in for the
977
- * workspace root see git/repo-git-dirs.ts), and the file watcher descent-ignores `.git` besides. So no
977
+ * workspace root, see git/repo-git-dirs.ts), and the file watcher descent-ignores `.git` besides. So no
978
978
  * `workspaceChanged` path can ever say "a ref moved", and a surface built on the commit graph would otherwise
979
979
  * be exactly as fresh as the last thing the user clicked.
980
980
  *
@@ -985,32 +985,32 @@ export type WorkspaceChanged = z.infer<typeof WorkspaceChangedSchema>;
985
985
  export const RefsChangedSchema = z.object({ kind: z.literal("refsChanged"), repos: z.array(z.string()) });
986
986
  export type RefsChanged = z.infer<typeof RefsChangedSchema>;
987
987
 
988
- /* WHICH RUNNING THINGS JUST MOVED a session opened or exited, a dev server bound its port, a browser closed,
988
+ /* WHICH RUNNING THINGS JUST MOVED, a session opened or exited, a dev server bound its port, a browser closed,
989
989
  * a subagent reported in.
990
990
  *
991
991
  * The fourth push, and the one that covers what the other three structurally cannot: none of this state is on
992
992
  * disk, so no `workspaceChanged` path can name it, and none of it is a ref or a repo. Before it, every view of a
993
- * running thing polled on its own timer which is to say each browser asked, forever, a question only the
993
+ * running thing polled on its own timer, which is to say each browser asked, forever, a question only the
994
994
  * daemon could answer and almost always answered "no change".
995
995
  *
996
996
  * Diff-not-snapshot, and deliberately thin: the frame carries the DOMAIN that moved, never the roster itself.
997
997
  * Invalidation only reaches a query something is observing, so a tab showing none of these pays a frame and no
998
- * request whereas a roster on the wire would bill every connected browser the full list whether or not
998
+ * request, whereas a roster on the wire would bill every connected browser the full list whether or not
999
999
  * anything on screen renders it. Which query keys a domain stands for is runtime-state.ts's table. */
1000
1000
  export const RuntimeChangedSchema = z.object({ kind: z.literal("runtimeChanged"), domains: z.array(z.string()) });
1001
1001
  export type RuntimeChanged = z.infer<typeof RuntimeChangedSchema>;
1002
1002
 
1003
1003
  // One connected browser tab of a sandbox member. Identity fields come from the caller's verified Google ID
1004
- // token; activity fields from the tab's own /system/presence reports. No timestamps on the wire an entry's
1004
+ // token; activity fields from the tab's own /system/presence reports. No timestamps on the wire, an entry's
1005
1005
  // lifetime IS its /events connection's lifetime, so there is nothing to age out or compare clocks over.
1006
1006
  export const PresenceUserSchema = z.object({
1007
- // Per-CONNECTION id, minted by the browser for each /events attempt never reused across reconnects.
1007
+ // Per-CONNECTION id, minted by the browser for each /events attempt, never reused across reconnects.
1008
1008
  clientId: z.string(),
1009
1009
  email: z.string(),
1010
1010
  name: z.string().optional(),
1011
1011
  picture: z.string().optional(),
1012
1012
  // The caller's trust tier, resolved by the authorizer at connection time. On the roster so every member
1013
- // can see who may do what and so a tab knows its OWN role without an owner-only lookup.
1013
+ // can see who may do what, and so a tab knows its OWN role without an owner-only lookup.
1014
1014
  role: MemberRoleSchema,
1015
1015
  idle: z.boolean(),
1016
1016
  // Route/view name the tab is on ("workspace", "automations", "ext:<id>/<key>", …).
@@ -1022,16 +1022,16 @@ export const PresenceUserSchema = z.object({
1022
1022
  });
1023
1023
  export type PresenceUser = z.infer<typeof PresenceUserSchema>;
1024
1024
 
1025
- // The FULL roster of connected members, broadcast on every change snapshots, not diffs, so a reconnecting
1025
+ // The FULL roster of connected members, broadcast on every change, snapshots, not diffs, so a reconnecting
1026
1026
  // browser is consistent from its first frame and ordering never matters (last frame wins).
1027
1027
  export const PresenceSchema = z.object({ kind: z.literal("presence"), users: z.array(PresenceUserSchema) });
1028
1028
  export type Presence = z.infer<typeof PresenceSchema>;
1029
1029
 
1030
- // The FULL fleet roster, broadcast on every registry change same snapshot-not-diff contract as presence:
1030
+ // The FULL fleet roster, broadcast on every registry change, same snapshot-not-diff contract as presence:
1031
1031
  // a reconnecting browser is consistent from its first frame. NOT simply "last frame wins", though: `rev` is the
1032
1032
  // registry revision the snapshot was taken at, and the browser applies a frame only if it is newer than the one
1033
- // it already holds. Snapshots race two other sources of the same fact an explicit GET /agents and the
1034
- // browser's own optimistic writes and an unordered full replace lets the slowest of them win, which is how an
1033
+ // it already holds. Snapshots race two other sources of the same fact, an explicit GET /agents and the
1034
+ // browser's own optimistic writes, and an unordered full replace lets the slowest of them win, which is how an
1035
1035
  // archived card came back. See AgentsListSchema and useAgents.ts.
1036
1036
  export const AgentsSchema = z.object({ kind: z.literal("agents"), agents: z.array(AgentSummarySchema), rev: z.number() });
1037
1037
  export type Agents = z.infer<typeof AgentsSchema>;