@intentic/sandbox-contract 1.322.0 → 1.324.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 (213) hide show
  1. package/README.md +7 -0
  2. package/dist/contracts/agent.contract.d.ts +109 -6
  3. package/dist/contracts/agent.contract.d.ts.map +1 -1
  4. package/dist/contracts/agent.contract.js +13 -3
  5. package/dist/contracts/agent.contract.js.map +1 -1
  6. package/dist/contracts/agents.contract.d.ts +124 -0
  7. package/dist/contracts/agents.contract.d.ts.map +1 -1
  8. package/dist/contracts/approvals.contract.d.ts +31 -0
  9. package/dist/contracts/approvals.contract.d.ts.map +1 -1
  10. package/dist/contracts/approvals.contract.js +2 -2
  11. package/dist/contracts/approvals.contract.js.map +1 -1
  12. package/dist/contracts/capabilities.contract.d.ts +2 -0
  13. package/dist/contracts/capabilities.contract.d.ts.map +1 -1
  14. package/dist/contracts/loops.contract.d.ts +2 -0
  15. package/dist/contracts/loops.contract.d.ts.map +1 -1
  16. package/dist/contracts/needs.contract.d.ts +4 -0
  17. package/dist/contracts/needs.contract.d.ts.map +1 -1
  18. package/dist/contracts/privacy.contract.d.ts +187 -0
  19. package/dist/contracts/privacy.contract.d.ts.map +1 -0
  20. package/dist/contracts/privacy.contract.js +76 -0
  21. package/dist/contracts/privacy.contract.js.map +1 -0
  22. package/dist/contracts/runner.contract.d.ts +167 -122
  23. package/dist/contracts/runner.contract.d.ts.map +1 -1
  24. package/dist/contracts/sessions.contract.d.ts +11 -0
  25. package/dist/contracts/sessions.contract.d.ts.map +1 -1
  26. package/dist/contracts/settings.contract.d.ts +74 -0
  27. package/dist/contracts/settings.contract.d.ts.map +1 -1
  28. package/dist/contracts/system.contract.d.ts +90 -32
  29. package/dist/contracts/system.contract.d.ts.map +1 -1
  30. package/dist/contracts/system.contract.js +11 -1
  31. package/dist/contracts/system.contract.js.map +1 -1
  32. package/dist/contracts/usage.contract.d.ts +1 -0
  33. package/dist/contracts/usage.contract.d.ts.map +1 -1
  34. package/dist/contracts/workflows.contract.d.ts +3 -0
  35. package/dist/contracts/workflows.contract.d.ts.map +1 -1
  36. package/dist/events/agent-events.d.ts +53 -1
  37. package/dist/events/agent-events.d.ts.map +1 -1
  38. package/dist/events/agent-events.js +6 -0
  39. package/dist/events/agent-events.js.map +1 -1
  40. package/dist/events/agent-ui.d.ts +33 -0
  41. package/dist/events/agent-ui.d.ts.map +1 -0
  42. package/dist/events/agent-ui.js +24 -0
  43. package/dist/events/agent-ui.js.map +1 -0
  44. package/dist/events/requests.d.ts +3 -0
  45. package/dist/events/requests.d.ts.map +1 -1
  46. package/dist/events/requests.js +4 -0
  47. package/dist/events/requests.js.map +1 -1
  48. package/dist/events/resume.d.ts +1 -0
  49. package/dist/events/resume.d.ts.map +1 -1
  50. package/dist/events/resume.js +7 -1
  51. package/dist/events/resume.js.map +1 -1
  52. package/dist/events/sandbox-notice.d.ts +2 -0
  53. package/dist/events/sandbox-notice.d.ts.map +1 -1
  54. package/dist/events/sandbox-notice.js +1 -1
  55. package/dist/events/sandbox-notice.js.map +1 -1
  56. package/dist/events/system-events.d.ts +15 -0
  57. package/dist/events/system-events.d.ts.map +1 -1
  58. package/dist/events/system-events.js +1 -0
  59. package/dist/events/system-events.js.map +1 -1
  60. package/dist/events/transcript.d.ts +70 -0
  61. package/dist/events/transcript.d.ts.map +1 -1
  62. package/dist/events/transcript.js +44 -14
  63. package/dist/events/transcript.js.map +1 -1
  64. package/dist/front/generated/browser-wire.d.ts +4 -0
  65. package/dist/front/generated/browser-wire.d.ts.map +1 -1
  66. package/dist/index.d.ts +640 -38
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +6 -0
  69. package/dist/index.js.map +1 -1
  70. package/dist/models/agent-runtimes.d.ts +1 -0
  71. package/dist/models/agent-runtimes.d.ts.map +1 -1
  72. package/dist/models/agent-runtimes.js +6 -0
  73. package/dist/models/agent-runtimes.js.map +1 -1
  74. package/dist/policy/request-status.d.ts.map +1 -1
  75. package/dist/policy/request-status.js +16 -7
  76. package/dist/policy/request-status.js.map +1 -1
  77. package/dist/policy/turned-away.d.ts.map +1 -1
  78. package/dist/policy/turned-away.js +1 -0
  79. package/dist/policy/turned-away.js.map +1 -1
  80. package/dist/protocol/raw-routes.d.ts +12 -0
  81. package/dist/protocol/raw-routes.d.ts.map +1 -1
  82. package/dist/protocol/raw-routes.js +3 -0
  83. package/dist/protocol/raw-routes.js.map +1 -1
  84. package/dist/schemas/agent.d.ts +97 -0
  85. package/dist/schemas/agent.d.ts.map +1 -1
  86. package/dist/schemas/agent.js +20 -2
  87. package/dist/schemas/agent.js.map +1 -1
  88. package/dist/schemas/agents.d.ts +18 -0
  89. package/dist/schemas/agents.d.ts.map +1 -1
  90. package/dist/schemas/agents.js +9 -2
  91. package/dist/schemas/agents.js.map +1 -1
  92. package/dist/schemas/approvals.d.ts +102 -0
  93. package/dist/schemas/approvals.d.ts.map +1 -1
  94. package/dist/schemas/approvals.js +45 -4
  95. package/dist/schemas/approvals.js.map +1 -1
  96. package/dist/schemas/automations.d.ts +6 -0
  97. package/dist/schemas/automations.d.ts.map +1 -1
  98. package/dist/schemas/capabilities.d.ts +2 -0
  99. package/dist/schemas/capabilities.d.ts.map +1 -1
  100. package/dist/schemas/capabilities.js +10 -1
  101. package/dist/schemas/capabilities.js.map +1 -1
  102. package/dist/schemas/git/git.d.ts.map +1 -1
  103. package/dist/schemas/git/git.js.map +1 -1
  104. package/dist/schemas/logs.d.ts +1 -0
  105. package/dist/schemas/logs.d.ts.map +1 -1
  106. package/dist/schemas/logs.js +4 -0
  107. package/dist/schemas/logs.js.map +1 -1
  108. package/dist/schemas/loops.d.ts +3 -0
  109. package/dist/schemas/loops.d.ts.map +1 -1
  110. package/dist/schemas/loops.js +1 -1
  111. package/dist/schemas/loops.js.map +1 -1
  112. package/dist/schemas/needs.d.ts +6 -0
  113. package/dist/schemas/needs.d.ts.map +1 -1
  114. package/dist/schemas/needs.js +13 -3
  115. package/dist/schemas/needs.js.map +1 -1
  116. package/dist/schemas/platform-link.d.ts +50 -0
  117. package/dist/schemas/platform-link.d.ts.map +1 -0
  118. package/dist/schemas/platform-link.js +19 -0
  119. package/dist/schemas/platform-link.js.map +1 -0
  120. package/dist/schemas/privacy.d.ts +277 -0
  121. package/dist/schemas/privacy.d.ts.map +1 -0
  122. package/dist/schemas/privacy.js +127 -0
  123. package/dist/schemas/privacy.js.map +1 -0
  124. package/dist/schemas/providers/plan-limits.d.ts +8 -0
  125. package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
  126. package/dist/schemas/providers/plan-limits.js +25 -4
  127. package/dist/schemas/providers/plan-limits.js.map +1 -1
  128. package/dist/schemas/providers/usage.d.ts +4 -0
  129. package/dist/schemas/providers/usage.d.ts.map +1 -1
  130. package/dist/schemas/providers/usage.js +5 -2
  131. package/dist/schemas/providers/usage.js.map +1 -1
  132. package/dist/schemas/push.d.ts +1 -0
  133. package/dist/schemas/push.d.ts.map +1 -1
  134. package/dist/schemas/push.js +4 -0
  135. package/dist/schemas/push.js.map +1 -1
  136. package/dist/schemas/settings.d.ts +80 -0
  137. package/dist/schemas/settings.d.ts.map +1 -1
  138. package/dist/schemas/settings.js +14 -3
  139. package/dist/schemas/settings.js.map +1 -1
  140. package/dist/schemas/system.d.ts +17 -0
  141. package/dist/schemas/system.d.ts.map +1 -1
  142. package/dist/schemas/system.js +2 -1
  143. package/dist/schemas/system.js.map +1 -1
  144. package/dist/schemas/updates.d.ts +30 -0
  145. package/dist/schemas/updates.d.ts.map +1 -1
  146. package/dist/schemas/updates.js +37 -0
  147. package/dist/schemas/updates.js.map +1 -1
  148. package/dist/schemas/workflows.d.ts +5 -0
  149. package/dist/schemas/workflows.d.ts.map +1 -1
  150. package/dist/state/definition.d.ts +10 -0
  151. package/dist/state/definition.d.ts.map +1 -1
  152. package/dist/state/history-state.d.ts.map +1 -1
  153. package/dist/state/history-state.js +1 -0
  154. package/dist/state/history-state.js.map +1 -1
  155. package/dist/state/runtime-state.d.ts +3 -0
  156. package/dist/state/runtime-state.d.ts.map +1 -1
  157. package/dist/state/runtime-state.js +1 -0
  158. package/dist/state/runtime-state.js.map +1 -1
  159. package/dist/state/workspace-state.d.ts +17 -1
  160. package/dist/state/workspace-state.d.ts.map +1 -1
  161. package/dist/state/workspace-state.js +20 -1
  162. package/dist/state/workspace-state.js.map +1 -1
  163. package/dist/text/transcript-fold.d.ts.map +1 -1
  164. package/dist/text/transcript-fold.js +9 -0
  165. package/dist/text/transcript-fold.js.map +1 -1
  166. package/package.json +5 -5
  167. package/src/contracts/agent.contract.ts +16 -3
  168. package/src/contracts/approvals.contract.ts +2 -2
  169. package/src/contracts/privacy.contract.ts +95 -0
  170. package/src/contracts/system.contract.ts +14 -1
  171. package/src/events/agent-events.ts +11 -0
  172. package/src/events/agent-ui.ts +40 -0
  173. package/src/events/requests.ts +6 -0
  174. package/src/events/resume.test.ts +13 -0
  175. package/src/events/resume.ts +11 -2
  176. package/src/events/sandbox-notice.ts +1 -1
  177. package/src/events/system-events.ts +2 -0
  178. package/src/events/transcript.ts +84 -20
  179. package/src/front/generated/browser-wire.json +27 -0
  180. package/src/front/generated/browser-wire.ts +1 -1
  181. package/src/index.ts +6 -0
  182. package/src/models/agent-catalog.test.ts +1 -0
  183. package/src/models/agent-runtimes.ts +17 -0
  184. package/src/models/capability-ledger.test.ts +3 -0
  185. package/src/policy/request-status.ts +19 -9
  186. package/src/policy/turned-away.ts +2 -0
  187. package/src/protocol/raw-routes.ts +9 -0
  188. package/src/schemas/agent.ts +36 -4
  189. package/src/schemas/agents.ts +17 -4
  190. package/src/schemas/approvals.ts +59 -4
  191. package/src/schemas/automations.ts +4 -4
  192. package/src/schemas/capabilities.ts +15 -2
  193. package/src/schemas/ci.ts +1 -1
  194. package/src/schemas/git/git.ts +1 -0
  195. package/src/schemas/logs.ts +6 -0
  196. package/src/schemas/loops.ts +2 -1
  197. package/src/schemas/needs.ts +18 -3
  198. package/src/schemas/platform-link.ts +42 -0
  199. package/src/schemas/privacy.ts +204 -0
  200. package/src/schemas/providers/plan-limits.ts +38 -5
  201. package/src/schemas/providers/usage.ts +13 -2
  202. package/src/schemas/push.ts +7 -0
  203. package/src/schemas/settings-history.ts +3 -3
  204. package/src/schemas/settings.test.ts +5 -5
  205. package/src/schemas/settings.ts +47 -19
  206. package/src/schemas/system.ts +6 -1
  207. package/src/schemas/updates.ts +71 -0
  208. package/src/state/history-state.ts +3 -0
  209. package/src/state/runtime-state.ts +4 -0
  210. package/src/state/workspace-state.test.ts +8 -2
  211. package/src/state/workspace-state.ts +32 -2
  212. package/src/text/transcript-fold.test.ts +15 -0
  213. package/src/text/transcript-fold.ts +10 -0
@@ -6,8 +6,27 @@ import { SubagentKindSchema, SubagentStatusSchema, SubagentVerificationSchema }
6
6
  import { NeedSchema } from "../schemas/needs.js";
7
7
  import { HeldEndingSchema, RetryLadderSchema } from "../schemas/turn-break.js";
8
8
  import { NoticeCodeSchema } from "./sandbox-notice.js";
9
- import type { ToolCallContent, ToolCallLocation, ToolCallStatus, ToolKind} from "./requests.js";
10
- import { browserHelpRequest, capabilityOfferRequest, CapabilityOutcomeSchema, credentialOfferRequest, CredentialReceiptSchema, paymentOfferRequest, PaymentReceiptSchema, PermissionAskSchema, permissionRequest, planRequest, questionRequest, terminalHelpRequest, TodoItemSchema, ToolCallContentSchema, ToolCallLocationSchema, ToolCallStatusSchema, ToolKindSchema } from "./requests.js";
9
+ import { TranscriptAgentNoticeSchema } from "./agent-ui.js";
10
+ import type { ToolCallContent, ToolCallLocation, ToolCallStatus, ToolKind } from "./requests.js";
11
+ import {
12
+ browserHelpRequest,
13
+ capabilityOfferRequest,
14
+ CapabilityOutcomeSchema,
15
+ credentialOfferRequest,
16
+ CredentialReceiptSchema,
17
+ paymentOfferRequest,
18
+ PaymentReceiptSchema,
19
+ PermissionAskSchema,
20
+ permissionRequest,
21
+ planRequest,
22
+ questionRequest,
23
+ terminalHelpRequest,
24
+ TodoItemSchema,
25
+ ToolCallContentSchema,
26
+ ToolCallLocationSchema,
27
+ ToolCallStatusSchema,
28
+ ToolKindSchema,
29
+ } from "./requests.js";
11
30
 
12
31
  // A conversation as recorded and replayed: the rows, the cards they carry, and the patches that change them while a
13
32
  // turn runs. One shape for the live row and the recorded one, since they're the same row.
@@ -19,7 +38,8 @@ export const PlanStatusSchema = z.enum(["pending", "approved", "rejected", "canc
19
38
  export type PlanStatus = z.infer<typeof PlanStatusSchema>;
20
39
  export const QuestionStatusSchema = z.enum(["pending", "answered", "cancelled"]);
21
40
  export type QuestionStatus = z.infer<typeof QuestionStatusSchema>;
22
- export const PermissionStatusSchema = z.enum(["pending", "allowed", "always", "denied", "cancelled"]);
41
+ // `everything`: allowed by a conversation-wide yes, the one given on this card or one already standing.
42
+ export const PermissionStatusSchema = z.enum(["pending", "allowed", "always", "everything", "denied", "cancelled"]);
23
43
  export type PermissionStatus = z.infer<typeof PermissionStatusSchema>;
24
44
  export const HelpStatusSchema = z.enum(["pending", "helped", "declined", "cancelled"]);
25
45
  export type HelpStatus = z.infer<typeof HelpStatusSchema>;
@@ -38,10 +58,17 @@ export const TranscriptQuestionSchema = z.object({
38
58
  .record(z.string(), z.array(z.string()))
39
59
  .optional()
40
60
  .describe("What was chosen, keyed by the question, with the chosen labels or the user's own words."),
61
+ attachments: z
62
+ .record(z.string(), z.array(z.string()))
63
+ .optional()
64
+ .describe("Files the user attached to their own-words answer, keyed by the question, as workspace-relative paths."),
41
65
  });
42
66
  export type TranscriptQuestion = z.infer<typeof TranscriptQuestionSchema>;
43
67
  // `explain`, the judge's sentence, arrives via PermissionAskSchema at raise time; nothing patches it in after.
44
- export const TranscriptPermissionSchema = PermissionAskSchema.extend({ ...permissionRequest, status: PermissionStatusSchema.describe("Where the decision stands.") });
68
+ export const TranscriptPermissionSchema = PermissionAskSchema.extend({
69
+ ...permissionRequest,
70
+ status: PermissionStatusSchema.describe("Where the decision stands."),
71
+ });
45
72
  export type TranscriptPermission = z.infer<typeof TranscriptPermissionSchema>;
46
73
  export const TranscriptBrowserHelpSchema = z.object({ ...browserHelpRequest, status: HelpStatusSchema.describe("How the hand-over ended.") });
47
74
  export type TranscriptBrowserHelp = z.infer<typeof TranscriptBrowserHelpSchema>;
@@ -297,15 +324,16 @@ export const TranscriptRowSchema = z.object({
297
324
  .optional()
298
325
  .describe("The wait this notice describes, by name, so a reader can say whether it is still on."),
299
326
  // Which one, for a wait whose kind can have several in flight at once; without it two armed watches settle together.
300
- noticeWaitId: z
301
- .string()
302
- .optional()
303
- .describe("Which instance of the wait this notice names, for a kind that can have several running at once."),
327
+ noticeWaitId: z.string().optional().describe("Which instance of the wait this notice names, for a kind that can have several running at once."),
304
328
  // The sandbox's own notice by name, beside its English `text` (sandbox-notice.ts); absent on every other row, and
305
329
  // on the notices written before rows carried it, which a reader draws from `text` as it always did.
306
330
  noticeCode: NoticeCodeSchema.optional().describe(
307
331
  "Which of the sandbox's own notices this row is, and the facts it was worded from, so a reader can say it in the reader's own language. The text stays the English sentence.",
308
332
  ),
333
+ // A notice a runtime's own extension or loop said (events/agent-ui.ts), the words in `text`; absent on every other row.
334
+ agentNotice: TranscriptAgentNoticeSchema.optional().describe(
335
+ "This notice was said by the agent's runtime or one of its extensions rather than by the sandbox: how loud, and who said it.",
336
+ ),
309
337
  // At most one card per row; a card closes its bubble. One field per kind, so a reader reaches it by name.
310
338
  plan: TranscriptPlanSchema.optional().describe("The plan this row asked approval for, and the answer."),
311
339
  question: TranscriptQuestionSchema.optional().describe("The questions this row asked, and the picks that answered them."),
@@ -321,7 +349,9 @@ export const TranscriptRowSchema = z.object({
321
349
  "Something the agent asked a person for, as it was when raised. Its live state (answered, met) is read by its id, since it outlives the turn.",
322
350
  ),
323
351
  needWake: TranscriptNeedWakeSchema.optional().describe("The answered need that reached this conversation, and the prompt it came as."),
324
- agentWords: TranscriptAgentWordsSchema.optional().describe("Another agent's words that reached this conversation, whose they are, and the prompt they came as."),
352
+ agentWords: TranscriptAgentWordsSchema.optional().describe(
353
+ "Another agent's words that reached this conversation, whose they are, and the prompt they came as.",
354
+ ),
325
355
  backgroundJob: TranscriptBackgroundJobSchema.optional().describe("The background job this row marks the start of."),
326
356
  credentialOffer: TranscriptCredentialOfferSchema.optional().describe(
327
357
  "The gated credential this row asked to use, who may release it, and who did.",
@@ -352,10 +382,25 @@ export const isAwaitingDecision = (row: TranscriptRequests): boolean => REQUEST_
352
382
  // everything else replaces its row; `drop` removes a row that opened and never wrote.
353
383
  export const TranscriptPatchSchema = z.discriminatedUnion("op", [
354
384
  z.object({ op: z.literal("append").describe("A new row at the end."), row: TranscriptRowSchema }),
355
- z.object({ op: z.literal("replace").describe("This row, whole, in place of the one at that index."), index: z.number().int().nonnegative(), row: TranscriptRowSchema }),
356
- z.object({ op: z.literal("drop").describe("The row at that index is gone: it was opened and never written into."), index: z.number().int().nonnegative() }),
357
- z.object({ op: z.literal("text").describe("More of the agent's prose, onto that row's text."), index: z.number().int().nonnegative(), text: z.string() }),
358
- z.object({ op: z.literal("thinking").describe("More of the agent's reasoning, onto that row's thinking."), index: z.number().int().nonnegative(), text: z.string() }),
385
+ z.object({
386
+ op: z.literal("replace").describe("This row, whole, in place of the one at that index."),
387
+ index: z.number().int().nonnegative(),
388
+ row: TranscriptRowSchema,
389
+ }),
390
+ z.object({
391
+ op: z.literal("drop").describe("The row at that index is gone: it was opened and never written into."),
392
+ index: z.number().int().nonnegative(),
393
+ }),
394
+ z.object({
395
+ op: z.literal("text").describe("More of the agent's prose, onto that row's text."),
396
+ index: z.number().int().nonnegative(),
397
+ text: z.string(),
398
+ }),
399
+ z.object({
400
+ op: z.literal("thinking").describe("More of the agent's reasoning, onto that row's thinking."),
401
+ index: z.number().int().nonnegative(),
402
+ text: z.string(),
403
+ }),
359
404
  z.object({
360
405
  op: z.literal("toolThinking").describe("More of a delegated subagent's reasoning, onto the thinking of the card that started it."),
361
406
  index: z.number().int().nonnegative(),
@@ -391,26 +436,41 @@ export const TurnEndingSchema = z.object({
391
436
  resetsAt: z
392
437
  .number()
393
438
  .optional()
394
- .describe("When the spent allowance reopens, in epoch seconds. Absent for every ending that names no instant, and for a provider that publishes none."),
439
+ .describe(
440
+ "When the spent allowance reopens, in epoch seconds. Absent for every ending that names no instant, and for a provider that publishes none.",
441
+ ),
395
442
  held: z
396
443
  .object({
397
- ran: z.boolean().describe("Whether the held turn got anywhere before it was refused, which is a different sentence from one refused at the door."),
444
+ ran: z
445
+ .boolean()
446
+ .describe("Whether the held turn got anywhere before it was refused, which is a different sentence from one refused at the door."),
398
447
  contextTokens: z
399
448
  .number()
400
449
  .optional()
401
- .describe("How much context a press that keeps the session re-reads once, on this account at the reset or carried to another. Absent when no usage frame measured it."),
450
+ .describe(
451
+ "How much context a press that keeps the session re-reads once, on this account at the reset or carried to another. Absent when no usage frame measured it.",
452
+ ),
402
453
  handoffTokens: z
403
454
  .number()
404
455
  .optional()
405
- .describe("What a press that opens a fresh session pays instead: the capped record plus the sandbox's measured brief, counted at the failure."),
406
- moving: z.string().optional().describe("The account the owner's policy is already moving this turn to, when it is; the surface then reports the move rather than offering a press."),
456
+ .describe(
457
+ "What a press that opens a fresh session pays instead: the capped record plus the sandbox's measured brief, counted at the failure.",
458
+ ),
459
+ moving: z
460
+ .string()
461
+ .optional()
462
+ .describe(
463
+ "The account the owner's policy is already moving this turn to, when it is; the surface then reports the move rather than offering a press.",
464
+ ),
407
465
  })
408
466
  .optional()
409
467
  .describe("Present when the daemon still holds the refused turn whole, so a press re-runs it rather than appending a message after it."),
410
468
  scheduled: z
411
469
  .boolean()
412
470
  .optional()
413
- .describe("Whether something other than the user is already booked to send this turn again, so the surface reports the wait instead of offering a press."),
471
+ .describe(
472
+ "Whether something other than the user is already booked to send this turn again, so the surface reports the wait instead of offering a press.",
473
+ ),
414
474
  // `resetsAt` is the provider's fact about the allowance; this is the daemon's own appointment, and the two differ
415
475
  // whenever a ladder rung falls short of the reset, or an ending with no allowance at all is armed.
416
476
  nextAt: z
@@ -440,7 +500,11 @@ export const AgentTranscriptSchema = SessionTranscriptSchema.extend({
440
500
  "How the last turn ended, when it left work behind that one press finishes. Absent for a conversation whose last turn ended on its own, and for the failures that name something to repair first.",
441
501
  ),
442
502
  // `from` offsets every `rewindIndex` here, and is the `before` for the page above; `more` flags older messages.
443
- from: z.number().int().nonnegative().describe("Where the first message sits in the whole record, and the `before` that asks for the page above this one."),
503
+ from: z
504
+ .number()
505
+ .int()
506
+ .nonnegative()
507
+ .describe("Where the first message sits in the whole record, and the `before` that asks for the page above this one."),
444
508
  more: z.boolean().describe("Whether older messages precede this page."),
445
509
  });
446
510
 
@@ -90,6 +90,33 @@
90
90
  "server": {
91
91
  "description": "What a terminal socket sends as JSON text; the pane's own bytes travel as binary messages.",
92
92
  "oneOf": [
93
+ {
94
+ "description": "The shared pane's grid. Viewport resize requests remain the viewer's own available size.",
95
+ "properties": {
96
+ "cols": {
97
+ "format": "uint16",
98
+ "maximum": 65535,
99
+ "minimum": 0,
100
+ "type": "integer"
101
+ },
102
+ "rows": {
103
+ "format": "uint16",
104
+ "maximum": 65535,
105
+ "minimum": 0,
106
+ "type": "integer"
107
+ },
108
+ "type": {
109
+ "const": "grid",
110
+ "type": "string"
111
+ }
112
+ },
113
+ "required": [
114
+ "type",
115
+ "cols",
116
+ "rows"
117
+ ],
118
+ "type": "object"
119
+ },
93
120
  {
94
121
  "description": "The session is over, and the browser must not reconnect; `reason` is tmux's own words when it had any.",
95
122
  "properties": {
@@ -49,4 +49,4 @@ export type TerminalClientMessage = { "type": "input", data: string, } | { "type
49
49
  /**
50
50
  * What a terminal socket sends as JSON text; the pane's own bytes travel as binary messages.
51
51
  */
52
- export type TerminalServerMessage = { "type": "exit", code: number, reason?: string, } | { "type": "pong" };
52
+ export type TerminalServerMessage = { "type": "grid", cols: number, rows: number, } | { "type": "exit", code: number, reason?: string, } | { "type": "pong" };
package/src/index.ts CHANGED
@@ -31,6 +31,7 @@ import { publicContract } from "./contracts/public.contract.js";
31
31
  import { netdiskContract } from "./contracts/netdisk.contract.js";
32
32
  import { providersContract } from "./contracts/providers.contract.js";
33
33
  import { pushContract } from "./contracts/push.contract.js";
34
+ import { privacyContract } from "./contracts/privacy.contract.js";
34
35
  import { safetyContract } from "./contracts/safety.contract.js";
35
36
  import { needsContract } from "./contracts/needs.contract.js";
36
37
  import { secretsContract } from "./contracts/secrets.contract.js";
@@ -95,6 +96,7 @@ export { portsContract } from "./contracts/ports.contract.js";
95
96
  export { publicContract } from "./contracts/public.contract.js";
96
97
  export { providersContract, type RunnableProviders, RunnableProvidersSchema } from "./contracts/providers.contract.js";
97
98
  export { pushContract } from "./contracts/push.contract.js";
99
+ export { privacyContract } from "./contracts/privacy.contract.js";
98
100
  export { safetyContract } from "./contracts/safety.contract.js";
99
101
  export { needsContract } from "./contracts/needs.contract.js";
100
102
  export { secretsContract } from "./contracts/secrets.contract.js";
@@ -111,6 +113,7 @@ export { vpnContract } from "./contracts/vpn.contract.js";
111
113
  export { workflowsContract } from "./contracts/workflows.contract.js";
112
114
  export { workspaceContract } from "./contracts/workspace.contract.js";
113
115
  export * from "./events/agent-events.js";
116
+ export * from "./events/agent-ui.js";
114
117
  export * from "./events/agent-words.js";
115
118
  export * from "./events/requests.js";
116
119
  export * from "./events/resume.js";
@@ -216,7 +219,9 @@ export * from "./schemas/marketplace.js";
216
219
  export * from "./schemas/metrics.js";
217
220
  export * from "./schemas/panels.js";
218
221
  export * from "./schemas/personas.js";
222
+ export * from "./schemas/privacy.js";
219
223
  export * from "./schemas/providers/plan-limits.js";
224
+ export * from "./schemas/platform-link.js";
220
225
  export * from "./schemas/ports.js";
221
226
  export * from "./schemas/providers/provider-oauth.js";
222
227
  export * from "./schemas/providers/provider-subscriptions.js";
@@ -277,6 +282,7 @@ export const sandboxContract = {
277
282
  endpoints: endpointsContract,
278
283
  extensions: extensionsContract,
279
284
  personas: personasContract,
285
+ privacy: privacyContract,
280
286
  safety: safetyContract,
281
287
  sessions: sessionsContract,
282
288
  settings: settingsContract,
@@ -162,6 +162,7 @@ test("every axis a record can lack has words for it", () => {
162
162
  skillDiscovery: "prompt",
163
163
  rulebook: "none",
164
164
  secrets: "none",
165
+ privacy: "none",
165
166
  };
166
167
 
167
168
  // Thirteen disclosable axes, thirteen sentences; `fastMode` is disclosed via fastAllowed, and `warm` (keep-warm, off by
@@ -51,6 +51,11 @@ export interface AgentCapabilities {
51
51
  readonly rulebook: "hooks" | "approval" | "refuse-only" | "none";
52
52
  // Whether a credential is masked; "none" is structural, only the Claude Code loop can rewrite a model's read.
53
53
  readonly secrets: "masked" | "none";
54
+ // Whether the privacy shield can stand between this loop and its provider: `gateway` when the daemon names the base
55
+ // URL the loop sends its model requests to (so the gateway reads and masks every byte of them), `none` when the loop
56
+ // talks to its provider on a wire or a configuration the daemon does not own. A `none` loop on an untrusted provider
57
+ // is refused while the shield is on.
58
+ readonly privacy: "gateway" | "none";
54
59
  }
55
60
 
56
61
  // The Claude Code Agent SDK loop, the ceiling every other runtime is measured against: the only one that owns the whole
@@ -75,6 +80,8 @@ export const CLAUDE_CODE: AgentCapabilities = {
75
80
  // The only runtime with its own pre-execution hook, so the only one where a hold can park instead of refusing.
76
81
  rulebook: "hooks",
77
82
  secrets: "masked",
83
+ // Every request goes to ANTHROPIC_BASE_URL, which the daemon sets per turn.
84
+ privacy: "gateway",
78
85
  };
79
86
 
80
87
  // Codex app-server: item-level events, the turn's remote MCP servers over http, and four seams (steer, a question
@@ -100,6 +107,8 @@ export const CODEX: AgentCapabilities = {
100
107
  // Asked only when the owner has written command rules; an unconfigured workspace pays nothing for it.
101
108
  rulebook: "approval",
102
109
  secrets: "none",
110
+ // Its model provider's base_url is the daemon's, written into the per-thread config.
111
+ privacy: "gateway",
103
112
  };
104
113
 
105
114
  // OpenCode (the Grok runtime): its own agentic loop and tools, with the turn's remote MCP servers mounted on the shared
@@ -126,6 +135,8 @@ export const OPENCODE: AgentCapabilities = {
126
135
  // hold parked on a card while the turn's inactivity watchdog is held.
127
136
  rulebook: "approval",
128
137
  secrets: "none",
138
+ // The shared server's provider baseURLs are written by the daemon at spawn.
139
+ privacy: "gateway",
129
140
  };
130
141
 
131
142
  // The same OpenCode loop, serving Gemini instead of xAI, identical abilities. The Claude Code loop announces itself in
@@ -157,6 +168,8 @@ export const ACP: AgentCapabilities = {
157
168
  // In the protocol floor, so every agent has it; which calls it asks about is entirely its own choice.
158
169
  rulebook: "approval",
159
170
  secrets: "none",
171
+ // The agent picks its own provider and endpoint from its own login and environment.
172
+ privacy: "none",
160
173
  };
161
174
 
162
175
  // Pi driven over its RPC mode: above the ACP floor and below the Claude Code ceiling. Real mid-turn steering, an effort
@@ -181,6 +194,8 @@ export const PI: AgentCapabilities = {
181
194
  // The one runtime with no seam at all: bash runs in-process, and no rule the owner writes can apply.
182
195
  rulebook: "none",
183
196
  secrets: "none",
197
+ // Pi calls its provider from its own login; the daemon names no endpoint.
198
+ privacy: "none",
184
199
  };
185
200
 
186
201
  // Cursor's own runtime, driven through `@cursor/sdk` from this daemon's process; an embedding surface, so most seams
@@ -219,4 +234,6 @@ export const CURSOR: AgentCapabilities = {
219
234
  rulebook: "hooks",
220
235
  // none, structurally: its hooks only allow/deny or are discarded, nothing here to substitute a reference into.
221
236
  secrets: "none",
237
+ // Cursor's own RPC to its own servers, which then reach the model: redirectable, not readable.
238
+ privacy: "none",
222
239
  };
@@ -34,6 +34,9 @@ const LEDGER: Record<keyof AgentCapabilities, Backing> = {
34
34
  rulebook: "enforced",
35
35
  // agent/run/turn/cache-keepwarm.ts files nothing to keep warm for a turn whose runtime lacks it.
36
36
  warm: "enforced",
37
+ // agent/run/decide/turn-decision.ts refuses a "none" runtime on an untrusted provider while the privacy shield is
38
+ // on; "gateway" runtimes get the daemon's gateway as their base URL (privacy/gateway).
39
+ privacy: "enforced",
37
40
 
38
41
  // Descriptive: true of the runtime but nothing consults these; wiring one up moves it to enforced.
39
42
  questions: "descriptive",
@@ -1,4 +1,4 @@
1
- import { REQUEST_FIELDS, type RequestField, type TranscriptRequests, type TranscriptRow } from "../events/transcript.js";
1
+ import { type PermissionStatus, REQUEST_FIELDS, type RequestField, type TranscriptRequests, type TranscriptRow } from "../events/transcript.js";
2
2
  import type { AgentReply } from "../schemas/providers/plan-limits.js";
3
3
  import { startedChildCard } from "../events/child-run.js";
4
4
 
@@ -7,6 +7,21 @@ import { startedChildCard } from "../events/child-run.js";
7
7
  // of the wrong kind cannot settle a card of another, since requestId is what matches them.
8
8
  type Cards = { -readonly [K in RequestField]?: TranscriptRow[K] };
9
9
 
10
+ // A permission card's ending, from the reply that released it: each kind of yes keeps its own word.
11
+ const permissionStatusOf = (reply: AgentReply | undefined): PermissionStatus => {
12
+ if (reply?.kind !== "permission") {
13
+ return "cancelled";
14
+ }
15
+ switch (reply.decision) {
16
+ case "deny":
17
+ return "denied";
18
+ case "once":
19
+ return "allowed";
20
+ default:
21
+ return reply.decision;
22
+ }
23
+ };
24
+
10
25
  export const settledRequests = (cards: TranscriptRequests, reply: AgentReply | undefined): TranscriptRequests => {
11
26
  const out: Cards = {};
12
27
  const { plan, question, permission, browserHelp, terminalHelp, capabilityOffer, paymentOffer, credentialOffer } = cards;
@@ -15,23 +30,18 @@ export const settledRequests = (cards: TranscriptRequests, reply: AgentReply | u
15
30
  }
16
31
  if (question !== undefined) {
17
32
  const answers = reply?.kind === "question" ? reply.answers : undefined;
33
+ const attachments = reply?.kind === "question" ? reply.attachments : undefined;
18
34
  out.question = {
19
35
  ...question,
20
36
  status: reply?.kind === "question" && reply.cancelled !== true ? "answered" : "cancelled",
21
37
  ...(answers === undefined ? {} : { answers }),
38
+ ...(attachments === undefined ? {} : { attachments }),
22
39
  };
23
40
  }
24
41
  if (permission !== undefined) {
25
42
  out.permission = {
26
43
  ...startedChildCard(permission, reply),
27
- status:
28
- reply?.kind !== "permission"
29
- ? "cancelled"
30
- : reply.decision === "deny"
31
- ? "denied"
32
- : reply.decision === "always"
33
- ? "always"
34
- : "allowed",
44
+ status: permissionStatusOf(reply),
35
45
  };
36
46
  }
37
47
  if (browserHelp !== undefined) {
@@ -11,6 +11,8 @@ const TURNED_AWAY: ReadonlySet<string> = new Set([
11
11
  "unknown-command",
12
12
  "context-window-too-small",
13
13
  "model-helper-only",
14
+ // The privacy shield refused an untrusted provider on a runtime it cannot stand in front of.
15
+ "privacy-unshielded",
14
16
  "sandbox-memory-low",
15
17
  "trial-unavailable",
16
18
  "trial-model-unavailable",
@@ -8,6 +8,9 @@ import { runnerTranslatorPath } from "./runner-protocol.js";
8
8
  export const RAW_ROUTES = {
9
9
  // The "is a daemon there" probe every flow uses; it names the sandbox and says whether it is still converging.
10
10
  "GET /health": { auth: "door", beforeBoot: true },
11
+ // A person opening this sandbox's own address in a browser: sent to the app's /open on it, the address being what
12
+ // they may still hold when the platform has lost the rest. Anything else gets the same 401 as before it existed.
13
+ "GET /": { auth: "door", beforeBoot: true },
11
14
  "GET /diff/raw": { lane: "bulk" },
12
15
  // Arming the dictation model is a read any tier makes; dictating is writing a message, the collaborator's grant.
13
16
  "GET /speech/status": { guest: true },
@@ -59,6 +62,8 @@ export const RAW_ROUTES = {
59
62
  "POST /members": { control: "never" },
60
63
  "DELETE /members": { control: "never" },
61
64
  "DELETE /members/self": { floor: "viewer", guest: true, control: "never" },
65
+ // The owner's Reconnect: register with the platform now, having it adopt the sandbox first when it holds no record.
66
+ "POST /platform/relink": { control: "never" },
62
67
  "GET /environment": {},
63
68
  "GET /environment/contents": {},
64
69
  "POST /environment/approve": {},
@@ -153,6 +158,10 @@ export const RAW_ROUTES = {
153
158
  "POST /system/runners/credentials": { auth: "door", control: "never" },
154
159
  "POST /system/runners/credentials/refresh": { auth: "door", control: "never" },
155
160
  [`ALL ${runnerTranslatorPath}/*` as const]: { auth: "door", control: "never" },
161
+ // The privacy shield's gateway: every shielded runtime's model requests, on the signed session its base URL carries
162
+ // (privacy/gateway/session-token.ts), which names the provider and the only upstream it may forward to. Held open
163
+ // while a model streams its answer.
164
+ "ALL /privacy/gateway/{session}/*": { auth: "door", control: "never", stream: true },
156
165
  "POST /system/control/tokens": { control: "never" },
157
166
  "GET /system/control/tokens": { control: "never" },
158
167
  "DELETE /system/control/tokens/{id}": { control: "never" },
@@ -304,15 +304,28 @@ const AgentTurnFieldsSchema = z.object({
304
304
  editorContext: EditorContextSchema.optional().describe(
305
305
  'What the user has open in their editor, folded into the prompt so that pointing words like "this" resolve.',
306
306
  ),
307
- // The composer's scheduled send: a person who knows the account is spent books the message for its reopen instead of
308
- // sending it into a refusal. Held in the conversation's queue, never started early; the queue's `until` is this.
307
+ // The composer's scheduled send: a time the person chose, or a spent account's reopen they would rather wait for than
308
+ // send into a refusal. Held in the conversation's queue, never started early; the queue's `until` is this.
309
309
  sendAt: z
310
310
  .number()
311
311
  .int()
312
312
  .positive()
313
313
  .optional()
314
314
  .describe(
315
- "Hold this message until then (epoch milliseconds) instead of starting a turn now, for an allowance you know is spent. It waits in the conversation's queue, where it can be sent early, reworded or removed, and goes by itself at that instant. Ignored when already past, or when a turn is running.",
315
+ "Hold this message until then (epoch milliseconds) instead of starting a turn now: a time you chose, or the reopen of an allowance you know is spent. It waits in the conversation's queue, where it can be sent early, reworded, rescheduled or removed, and goes by itself at that instant, even for a conversation this message opens or one whose turn is running now. Ignored when already past; at most a month ahead.",
316
+ ),
317
+ // The other scheduled send: work that builds on another agent's, held until that agent's work is in the workspace.
318
+ // Held in the queue like `sendAt`; the queue's `after` is this. Never both: a hold waits on exactly one thing.
319
+ sendAfter: ConversationIdSchema.optional().describe(
320
+ "Hold this message until the conversation named here has finished and all of its work has landed in the workspace, instead of starting a turn now: for work that builds on another agent's. It waits in this conversation's queue, where it can be sent early, reworded, rescheduled or removed. Sent at once when that conversation has nothing running and nothing left to land.",
321
+ ),
322
+ // Decided like `isolated`: the opening turn's answer, latched on the entry it opens. A maintainer's alone, since the
323
+ // standing answer it sets is what `agents.autoLand` sets, and that route is a maintainer's.
324
+ conversationAutoLand: z
325
+ .boolean()
326
+ .optional()
327
+ .describe(
328
+ "Whether this conversation's finished work merges into the workspace by itself from now on: its own answer to the sandbox-wide setting, the one `agents.autoLand` changes later. Read only from the message that opens the conversation, and only from a maintainer. Unlike `autoLand`, it holds for every later turn.",
316
329
  ),
317
330
  });
318
331
  export const AgentTurnSchema = AgentTurnFieldsSchema
@@ -335,8 +348,24 @@ export const AgentTurnSchema = AgentTurnFieldsSchema
335
348
  // "then" needs a checkout of its own; rolling back the shared tree under everyone else is what it must never mean.
336
349
  .refine((turn) => turn.forkOf?.files !== "then" || turn.isolated === true, {
337
350
  message: 'forkOf.files "then" requires isolated',
351
+ })
352
+ .refine((turn) => turn.sendAt === undefined || turn.sendAfter === undefined, {
353
+ message: "sendAt and sendAfter are two different holds: name one",
354
+ })
355
+ .refine((turn) => turn.sendAfter === undefined || turn.sendAfter !== turn.conversationId, {
356
+ message: "a conversation cannot wait for its own work to land",
338
357
  });
339
358
  export type AgentTurn = z.infer<typeof AgentTurnSchema>;
359
+ // What `agent.run` takes: a turn, plus the one field only a request carries and no stored turn keeps. A daemon from before
360
+ // `continues` reads past it and takes `prompt` as the words, which is why a client still sends some.
361
+ export const AgentRunSchema = AgentTurnSchema.safeExtend({
362
+ continues: z
363
+ .literal(true)
364
+ .optional()
365
+ .describe(
366
+ "Carry the conversation on from where its last turn stopped instead of saying anything: nothing of yours is added to the conversation, and `prompt` is ignored. A turn the sandbox still holds runs again as it was; otherwise the agent is told to continue in its own session. Refused while a turn is running.",
367
+ ),
368
+ });
340
369
  // What a turn naming no provider or loop runs on, stated once. Applied where a request comes in (the daemon's turn port),
341
370
  // so nothing downstream defaults them again: the engine takes a turn with both named (withRuntimeDefaults).
342
371
  /** A turn with its provider and loop named, as the engine takes it past the door. */
@@ -491,12 +520,15 @@ export const ConversationQueueSchema = z.object({
491
520
  items: z.array(QueuedMessageSchema).describe("What waits, in the order it goes out."),
492
521
  revision: z.number().int().nonnegative().describe("Moves with every change to the queue, so of two copies the higher is the newer."),
493
522
  paused: QueuePauseSchema.optional().describe(
494
- "Why nothing goes out by itself: somebody stopped the turn, the turn these messages started was refused before it ran, or they were scheduled for later. Resuming lets them go, and so does sending another message unless they are scheduled.",
523
+ "Why nothing goes out by itself: somebody stopped the turn, the turn these messages started was refused before it ran, or they were scheduled for a time or for after another conversation's work lands. Resuming lets them go, and so does sending another message.",
495
524
  ),
496
525
  until: z
497
526
  .number()
498
527
  .optional()
499
528
  .describe("When scheduled messages go out by themselves, in milliseconds. Only on a queue paused as `scheduled`."),
529
+ after: ConversationIdSchema.optional().describe(
530
+ "The conversation whose finished work must land before scheduled messages go out by themselves. Only on a queue paused as `scheduled`, in place of `until`.",
531
+ ),
500
532
  });
501
533
  export type ConversationQueue = z.infer<typeof ConversationQueueSchema>;
502
534
  // What resuming a queue did: started a turn with what waited, when nothing else ran.
@@ -89,12 +89,14 @@ export const UnfinishedWorkSchema = z.object({
89
89
  })
90
90
  .optional()
91
91
  .describe("The agent's own checklist where that turn left it. Absent for a conversation that kept no list."),
92
- // Name of the `turn.ending` check still failing when the turn ended. Retired with that check: nothing writes it now, and
93
- // it stays so entries written before still read.
92
+ // Name of the `turn.ending` check still failing when the turn ended, from when a failing one left the work unfinished.
93
+ // Nothing writes it now, and it stays so entries written before still read.
94
94
  check: z
95
95
  .string()
96
96
  .optional()
97
- .describe("The end-of-turn check that was still failing when the turn ended, by name. No longer written: nothing checks inside a turn."),
97
+ .describe(
98
+ "The end-of-turn check that was still failing when the turn ended, by name. No longer written: what a check finds as a turn ends is said back to the model and never leaves its work unfinished.",
99
+ ),
98
100
  });
99
101
  export type UnfinishedWork = z.infer<typeof UnfinishedWorkSchema>;
100
102
  // What the last turn showed of its own work, read off its tool calls and never asked of the model: the record a card
@@ -357,7 +359,7 @@ export const AgentSummarySchema = z.object({
357
359
  // Root repo's short base sha; per-repo bases stay daemon-internal.
358
360
  base: z.string().optional().describe("The commit its private copy started from, shortened."),
359
361
  costUsd: z.number().optional().describe("What it has cost so far, in dollars. A subagent's spend is its own and is not folded in here."),
360
- inputTokens: z.number().optional().describe("Tokens sent."),
362
+ inputTokens: z.number().optional().describe("Uncached input tokens, excluding cache reads and cache writes."),
361
363
  outputTokens: z.number().optional().describe("Tokens received."),
362
364
  contextTokens: z.number().optional().describe("How much of the window the conversation currently fills."),
363
365
  contextWindow: z.number().optional().describe("How large that window is."),
@@ -616,6 +618,17 @@ export const AgentSummarySchema = z.object({
616
618
  .describe(
617
619
  "Who stopped it: the sandbox, when the turn that used it ended without handing it over; a person; or the agent itself. Present from the moment the stop is asked. Absent for a job that exited by itself.",
618
620
  ),
621
+ // agent/tools/jobs/input-wait.ts: told from /proc, never from what it printed, since a prompt can go
622
+ // straight to the terminal or sit in a pipe.
623
+ inputWait: z
624
+ .object({
625
+ since: z.number().describe("When it went still, in milliseconds."),
626
+ program: z.string().describe("The process blocked reading the terminal, as its command line."),
627
+ })
628
+ .optional()
629
+ .describe(
630
+ "Present while it sits blocked reading its terminal with nothing moving: a prompt nobody in the sandbox will answer, which only stopping it or a person typing into its terminal ends. Absent while it runs on, and once it ends.",
631
+ ),
619
632
  }),
620
633
  )
621
634
  .optional()