@intentic/sandbox-contract 1.244.0 → 1.246.1

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 (211) hide show
  1. package/README.md +17 -1
  2. package/dist/batch-runs.d.ts +32 -0
  3. package/dist/batch-runs.d.ts.map +1 -0
  4. package/dist/batch-runs.js +43 -0
  5. package/dist/batch-runs.js.map +1 -0
  6. package/dist/command-classes.d.ts +6 -3
  7. package/dist/command-classes.d.ts.map +1 -1
  8. package/dist/command-classes.js +43 -18
  9. package/dist/command-classes.js.map +1 -1
  10. package/dist/contracts/{cursor.contract.d.ts → accounts.contract.d.ts} +102 -3
  11. package/dist/contracts/accounts.contract.d.ts.map +1 -0
  12. package/dist/contracts/accounts.contract.js +61 -0
  13. package/dist/contracts/accounts.contract.js.map +1 -0
  14. package/dist/contracts/agents.contract.d.ts +121 -0
  15. package/dist/contracts/agents.contract.d.ts.map +1 -1
  16. package/dist/contracts/agents.contract.js +4 -4
  17. package/dist/contracts/agents.contract.js.map +1 -1
  18. package/dist/contracts/ci.contract.d.ts +2 -0
  19. package/dist/contracts/ci.contract.d.ts.map +1 -1
  20. package/dist/contracts/host.contract.d.ts +35 -0
  21. package/dist/contracts/host.contract.d.ts.map +1 -1
  22. package/dist/contracts/host.contract.js +3 -2
  23. package/dist/contracts/host.contract.js.map +1 -1
  24. package/dist/contracts/personas.contract.d.ts +4 -2
  25. package/dist/contracts/personas.contract.d.ts.map +1 -1
  26. package/dist/contracts/runner.contract.d.ts +2 -2
  27. package/dist/contracts/settings.contract.d.ts +2 -51
  28. package/dist/contracts/settings.contract.d.ts.map +1 -1
  29. package/dist/contracts/system.contract.d.ts +52 -2
  30. package/dist/contracts/system.contract.d.ts.map +1 -1
  31. package/dist/contracts/system.contract.js +26 -17
  32. package/dist/contracts/system.contract.js.map +1 -1
  33. package/dist/definition.d.ts +4 -8
  34. package/dist/definition.d.ts.map +1 -1
  35. package/dist/documents.d.ts +0 -1
  36. package/dist/documents.d.ts.map +1 -1
  37. package/dist/documents.js +1 -2
  38. package/dist/documents.js.map +1 -1
  39. package/dist/embed.d.ts +23 -0
  40. package/dist/embed.d.ts.map +1 -0
  41. package/dist/embed.js +84 -0
  42. package/dist/embed.js.map +1 -0
  43. package/dist/events.d.ts +21 -0
  44. package/dist/events.d.ts.map +1 -1
  45. package/dist/events.js +5 -2
  46. package/dist/events.js.map +1 -1
  47. package/dist/history-state.d.ts.map +1 -1
  48. package/dist/history-state.js +2 -0
  49. package/dist/history-state.js.map +1 -1
  50. package/dist/index.d.ts +354 -335
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +5 -13
  53. package/dist/index.js.map +1 -1
  54. package/dist/peer-dial.d.ts +33 -0
  55. package/dist/peer-dial.d.ts.map +1 -0
  56. package/dist/peer-dial.js +79 -0
  57. package/dist/peer-dial.js.map +1 -0
  58. package/dist/peer-mcp-server.d.ts +36 -0
  59. package/dist/peer-mcp-server.d.ts.map +1 -0
  60. package/dist/peer-mcp-server.js +71 -0
  61. package/dist/peer-mcp-server.js.map +1 -0
  62. package/dist/provider-specs.d.ts +38 -20
  63. package/dist/provider-specs.d.ts.map +1 -1
  64. package/dist/provider-specs.js +39 -13
  65. package/dist/provider-specs.js.map +1 -1
  66. package/dist/quick-model.d.ts.map +1 -1
  67. package/dist/quick-model.js +2 -1
  68. package/dist/quick-model.js.map +1 -1
  69. package/dist/runtime-state.d.ts +1 -1
  70. package/dist/runtime-state.js +1 -1
  71. package/dist/runtime-state.js.map +1 -1
  72. package/dist/safety-policy.d.ts +12 -3
  73. package/dist/safety-policy.d.ts.map +1 -1
  74. package/dist/safety-policy.js +30 -5
  75. package/dist/safety-policy.js.map +1 -1
  76. package/dist/schemas/agent.d.ts +6 -6
  77. package/dist/schemas/agent.d.ts.map +1 -1
  78. package/dist/schemas/agent.js +6 -2
  79. package/dist/schemas/agent.js.map +1 -1
  80. package/dist/schemas/agents.d.ts +42 -0
  81. package/dist/schemas/agents.d.ts.map +1 -1
  82. package/dist/schemas/agents.js +25 -4
  83. package/dist/schemas/agents.js.map +1 -1
  84. package/dist/schemas/automations.d.ts +11 -2
  85. package/dist/schemas/automations.d.ts.map +1 -1
  86. package/dist/schemas/automations.js +1 -1
  87. package/dist/schemas/automations.js.map +1 -1
  88. package/dist/schemas/ci.d.ts +6 -0
  89. package/dist/schemas/ci.d.ts.map +1 -1
  90. package/dist/schemas/ci.js +3 -2
  91. package/dist/schemas/ci.js.map +1 -1
  92. package/dist/schemas/context.d.ts +30 -0
  93. package/dist/schemas/context.d.ts.map +1 -0
  94. package/dist/schemas/context.js +34 -0
  95. package/dist/schemas/context.js.map +1 -0
  96. package/dist/schemas/{computers.d.ts → devices.d.ts} +155 -61
  97. package/dist/schemas/devices.d.ts.map +1 -0
  98. package/dist/schemas/devices.js +157 -0
  99. package/dist/schemas/devices.js.map +1 -0
  100. package/dist/schemas/hosts.d.ts +12 -0
  101. package/dist/schemas/hosts.d.ts.map +1 -1
  102. package/dist/schemas/hosts.js +1 -0
  103. package/dist/schemas/hosts.js.map +1 -1
  104. package/dist/schemas/issues.d.ts +0 -5
  105. package/dist/schemas/issues.d.ts.map +1 -1
  106. package/dist/schemas/issues.js +0 -1
  107. package/dist/schemas/issues.js.map +1 -1
  108. package/dist/schemas/personas.d.ts +5 -3
  109. package/dist/schemas/personas.d.ts.map +1 -1
  110. package/dist/schemas/personas.js +3 -2
  111. package/dist/schemas/personas.js.map +1 -1
  112. package/dist/schemas/provider-oauth.d.ts +48 -16
  113. package/dist/schemas/provider-oauth.d.ts.map +1 -1
  114. package/dist/schemas/provider-oauth.js +22 -20
  115. package/dist/schemas/provider-oauth.js.map +1 -1
  116. package/dist/schemas/settings.d.ts +1 -52
  117. package/dist/schemas/settings.d.ts.map +1 -1
  118. package/dist/schemas/settings.js +8 -11
  119. package/dist/schemas/settings.js.map +1 -1
  120. package/dist/schemas/terminal.js +9 -9
  121. package/dist/schemas/terminal.js.map +1 -1
  122. package/dist/schemas/usage.d.ts +0 -2
  123. package/dist/schemas/usage.d.ts.map +1 -1
  124. package/dist/schemas/usage.js +0 -2
  125. package/dist/schemas/usage.js.map +1 -1
  126. package/dist/shell-regions.d.ts +4 -0
  127. package/dist/shell-regions.d.ts.map +1 -0
  128. package/dist/shell-regions.js +156 -0
  129. package/dist/shell-regions.js.map +1 -0
  130. package/dist/workspace-state.d.ts +15 -0
  131. package/dist/workspace-state.d.ts.map +1 -1
  132. package/dist/workspace-state.js +21 -5
  133. package/dist/workspace-state.js.map +1 -1
  134. package/package.json +48 -4
  135. package/src/agent-catalog.ts +1 -1
  136. package/src/arrival.ts +3 -3
  137. package/src/batch-runs.test.ts +167 -0
  138. package/src/batch-runs.ts +181 -0
  139. package/src/command-classes.test.ts +195 -71
  140. package/src/command-classes.ts +148 -46
  141. package/src/contracts/accounts.contract.ts +94 -0
  142. package/src/contracts/agents.contract.ts +4 -3
  143. package/src/contracts/exit.contract.ts +2 -2
  144. package/src/contracts/host.contract.ts +17 -5
  145. package/src/contracts/settings.contract.ts +1 -1
  146. package/src/contracts/system.contract.ts +43 -24
  147. package/src/contracts/vpn.contract.ts +2 -2
  148. package/src/documents.test.ts +2 -1
  149. package/src/documents.ts +7 -11
  150. package/src/embed.test.ts +68 -0
  151. package/src/embed.ts +164 -0
  152. package/src/events.ts +30 -3
  153. package/src/history-state.ts +12 -3
  154. package/src/host-protocol.ts +2 -2
  155. package/src/index.ts +6 -14
  156. package/src/model-order.ts +1 -1
  157. package/src/peer-dial.test.ts +203 -0
  158. package/src/peer-dial.ts +163 -0
  159. package/src/peer-mcp-server.test.ts +104 -0
  160. package/src/peer-mcp-server.ts +144 -0
  161. package/src/provider-specs.test.ts +44 -17
  162. package/src/provider-specs.ts +147 -67
  163. package/src/quick-model.ts +10 -3
  164. package/src/routes.test.ts +6 -3
  165. package/src/runner-protocol.ts +1 -1
  166. package/src/runtime-state.ts +2 -2
  167. package/src/safety-policy.test.ts +88 -0
  168. package/src/safety-policy.ts +84 -14
  169. package/src/schemas/agent.ts +41 -13
  170. package/src/schemas/agents.ts +65 -4
  171. package/src/schemas/automations.ts +6 -4
  172. package/src/schemas/capabilities.ts +4 -4
  173. package/src/schemas/ci.ts +23 -6
  174. package/src/schemas/context.ts +87 -0
  175. package/src/schemas/{computers.ts → devices.ts} +201 -109
  176. package/src/schemas/hosts.ts +5 -1
  177. package/src/schemas/issues.ts +0 -4
  178. package/src/schemas/personas.ts +8 -3
  179. package/src/schemas/provider-oauth.ts +49 -52
  180. package/src/schemas/settings.ts +30 -54
  181. package/src/schemas/terminal.ts +12 -12
  182. package/src/schemas/usage.ts +3 -27
  183. package/src/schemas/version-seam.test.ts +0 -1
  184. package/src/shell-regions.ts +289 -0
  185. package/src/versions.ts +2 -2
  186. package/src/webext-links.ts +2 -2
  187. package/src/webext-protocol.ts +2 -2
  188. package/src/workspace-state.test.ts +55 -1
  189. package/src/workspace-state.ts +76 -11
  190. package/dist/contracts/claude.contract.d.ts +0 -91
  191. package/dist/contracts/claude.contract.d.ts.map +0 -1
  192. package/dist/contracts/claude.contract.js +0 -50
  193. package/dist/contracts/claude.contract.js.map +0 -1
  194. package/dist/contracts/cursor.contract.d.ts.map +0 -1
  195. package/dist/contracts/cursor.contract.js +0 -50
  196. package/dist/contracts/cursor.contract.js.map +0 -1
  197. package/dist/contracts/grok.contract.d.ts +0 -36
  198. package/dist/contracts/grok.contract.d.ts.map +0 -1
  199. package/dist/contracts/grok.contract.js +0 -31
  200. package/dist/contracts/grok.contract.js.map +0 -1
  201. package/dist/contracts/keys.contract.d.ts +0 -81
  202. package/dist/contracts/keys.contract.d.ts.map +0 -1
  203. package/dist/contracts/keys.contract.js +0 -51
  204. package/dist/contracts/keys.contract.js.map +0 -1
  205. package/dist/schemas/computers.d.ts.map +0 -1
  206. package/dist/schemas/computers.js +0 -134
  207. package/dist/schemas/computers.js.map +0 -1
  208. package/src/contracts/claude.contract.ts +0 -71
  209. package/src/contracts/cursor.contract.ts +0 -74
  210. package/src/contracts/grok.contract.ts +0 -41
  211. package/src/contracts/keys.contract.ts +0 -79
@@ -44,7 +44,7 @@ import { SkillDraftSchema, SkillNameSchema, SystemPromptModeSchema } from "./set
44
44
  * what it said after the SDK renames something.
45
45
  *
46
46
  * TWO STRENGTHS, AND THE DIFFERENCE IS VISIBLE FROM HERE. Everything capability-shaped (`connectors`,
47
- * `computers`, `mcp`, and the accounts in `capabilities`) is enforced by ABSENCE, the credential is never
47
+ * `devices`, `mcp`, and the accounts in `capabilities`) is enforced by ABSENCE, the credential is never
48
48
  * injected, the server never mounted, the browser never launched, which is the same mechanism the account
49
49
  * filter already uses and needs no cooperation from the model. The plain switches are enforced by taking the
50
50
  * tools out of the turn's context, which holds for every tool the harness owns and cannot reach a program the
@@ -99,7 +99,7 @@ export const PersonaPowersSchema = z.object({
99
99
  * whole reason these are optional rather than defaulted arrays, "all" and "none" are both real answers and
100
100
  * an empty default could only spell one of them. */
101
101
  connectors: z.array(entryId).max(100).optional(),
102
- computers: z.array(entryId).max(50).optional(),
102
+ devices: z.array(entryId).max(50).optional(),
103
103
  mcp: z.array(entryId).max(50).optional(),
104
104
  });
105
105
  export type PersonaPowers = z.infer<typeof PersonaPowersSchema>;
@@ -155,6 +155,11 @@ export const PersonaSchema = z.object({
155
155
  "What a conversation wearing it may do. Absent means the full toolbox, so a card written before this existed behaves exactly as it did.",
156
156
  ),
157
157
  workspace: PersonaWorkspaceSchema.optional().describe("Where it works. Absent means the whole workspace."),
158
+ /* WHICH PART OF THE WORKSPACE A SESSION WEARING THIS CARD CARRIES, the id of a context shelf
159
+ * (schemas/context.ts). A different question from `workspace.folders`: that one fences what the file tools
160
+ * may TOUCH inside a tree that holds everything, this one decides what the tree HOLDS. Absent falls through to
161
+ * the sandbox's `contextShelf` setting, and from there to everything. */
162
+ context: entryId.optional().describe("Which context shelf a conversation wearing it opens on: the part of the workspace it carries. Absent follows the sandbox setting."),
158
163
  /* WHICH SYSTEM PROMPT A SESSION WEARING THIS CARD RUNS ON, the same three bases the sandbox chooses
159
164
  * between, asked per card. ABSENT is the fourth answer and the default: follow the sandbox, which is what
160
165
  * every card meant before this field existed and what almost every card will go on meaning.
@@ -218,7 +223,7 @@ export const personaBounds = (persona: Persona): string => {
218
223
  !resolved.delegate,
219
224
  !resolved.sandbox,
220
225
  resolved.connectors !== undefined,
221
- resolved.computers !== undefined,
226
+ resolved.devices !== undefined,
222
227
  resolved.mcp !== undefined,
223
228
  ].filter(Boolean).length;
224
229
  return limits === 0 ? "Full powers" : `${limits} limit${limits === 1 ? "" : "s"}`;
@@ -64,68 +64,65 @@ export const AccountListQuerySchema = z.object({
64
64
  });
65
65
  // Address one account of a provider (disconnect, and the turn's `account`).
66
66
  export const AccountIdSchema = z.object({ id: z.string().min(1).describe("Which account.") });
67
- // Rename one account of a provider whose credential the sandbox owns (Claude, Kimi). Blank ⇒ the daemon falls
68
- // back to the derived name, so clearing a label restores the sign-in identity rather than leaving a nameless
69
- // row. Grok is absent for the same reason it holds one account: OpenCode owns that credential, not this store.
67
+ // Rename one account of a provider whose credential the sandbox owns. Blank ⇒ the daemon falls back to the
68
+ // derived name, so clearing a label restores the sign-in identity rather than leaving a nameless row.
70
69
  export const AccountRenameSchema = z.object({
71
70
  id: z.string().min(1).describe("Which account."),
72
71
  label: z.string().max(80).describe("The new name. Blank restores the one derived from the sign-in, rather than leaving a nameless row."),
73
72
  });
74
- // The completing calls carry the user-chosen label (blank the daemon derives one from the sign-in identity
75
- // or a provider default).
76
- export const OauthExchangeSchema = z.object({
77
- code: z.string().min(1).describe("The code the sign-in handed back."),
78
- verifier: z.string().min(1).describe("The proof from the start of the handshake, which is what stops somebody else's code being redeemed here."),
79
- state: z.string().min(1).describe("The handshake this belongs to. A mismatch is refused."),
80
- label: z.string().optional().describe("What to call the account. Blank derives one from the sign-in."),
81
- });
82
- export const AuthorizeChallengeSchema = z.object({
83
- authorizeUrl: z.string().describe("Where to send somebody to sign in."),
84
- verifier: z.string().describe("Keep this and send it back when finishing. It is what proves the code that comes back belongs to this handshake."),
85
- state: z.string().describe("The handshake's own id, sent back with it."),
86
- });
87
- /* CURSOR'S SIGN-IN START. A third login shape, and the reason it is not one of the two above is where the
88
- * SECRET lives during the handshake.
73
+ /* ONE SIGN-IN SHAPE FOR EVERY ACCOUNT THE SANDBOX ITSELF HOLDS, whatever the vendor's mechanism underneath.
89
74
  *
90
- * Claude's is paste-back: the browser receives a code and the caller hands it plus its verifier to `exchange`,
91
- * so the handshake's proof has to travel on the wire and AuthorizeChallengeSchema carries it. Cursor's PKCE
92
- * verifier must never leave the process that generated it, anyone holding it can redeem the login and mint a
93
- * durable key, so the daemon starts the whole flow, keeps the verifier in memory, polls Cursor itself, and
94
- * writes the account when it lands. Nothing redeemable is on this shape at all.
95
- *
96
- * Which makes it behave like a DEVICE flow from the caller's side (open the page, then watch the account list),
97
- * except that there is no one-time code to display: the login page is addressed to this handshake already. So
98
- * DeviceStartSchema's `code` would be a permanently blank field on every card, and TranslatorStartSchema's
99
- * `state` a value nothing sends back. `handshake` is neither, it is a cancellation handle. */
100
- export const CursorLoginStartSchema = z.object({
101
- url: z.string().describe("The page to open and sign in on. It is already addressed to this attempt, so there is no code to type."),
75
+ * Four handshakes used to answer four shapes, and the shapes differed in where the SECRET lived during the
76
+ * handshake. Anthropic's is paste-back: the browser shows a code and the user brings it here. Cursor's PKCE
77
+ * verifier must never leave the process that generated it (anyone holding it can redeem the login and mint a
78
+ * durable key), so the daemon runs the whole flow and the caller only watches the account list. xAI's is a
79
+ * device code. Meta's and Z.ai's mint the vendor's own key, by a device poll or by a redirect that dead-ends in
80
+ * the user's address bar. Every one of them is now the daemon's to hold: nothing redeemable is on this shape,
81
+ * and the one thing the caller can hand back (a code off a page, or the address a redirect landed on) goes to
82
+ * `complete` with the attempt's handshake. `flow` says how the attempt ENDS, which is the only thing a card
83
+ * cannot infer from the fields: a device sign-in finishes upstream and the account appears on its own; a
84
+ * redirect needs the landing address brought back; a paste needs the code the page showed. */
85
+ export const LoginFlowSchema = z.enum(["device", "redirect", "paste"]);
86
+ export type LoginFlow = z.infer<typeof LoginFlowSchema>;
87
+ export const LoginStartSchema = z.object({
88
+ url: z.string().describe("The page to open and sign in on."),
89
+ code: z.string().describe("The one-time code the page will ask for, where the vendor issues one. Blank when the page is already addressed to this attempt."),
90
+ state: z
91
+ .string()
92
+ .describe("For a redirect sign-in, the marker in the address the browser lands on, so a pasted URL can be recognised as this attempt's. Blank otherwise."),
93
+ flow: LoginFlowSchema.describe(
94
+ "How this attempt ends. A device sign-in finishes by itself and you watch the account list; a redirect needs the address it landed on handed back; a paste needs the code the page showed.",
95
+ ),
96
+ variant: z.string().describe("Which of the provider's estates this attempt signs in to. Blank for a provider with one."),
102
97
  handshake: z
103
98
  .string()
104
- .describe(
105
- "This attempt's id, for abandoning it. Not a credential and not redeemable: the proof that finishes the sign-in never leaves the sandbox.",
106
- ),
99
+ .describe("This attempt's id, for finishing or abandoning it. Not a credential and not redeemable: the proof that completes the sign-in never leaves the sandbox."),
107
100
  expiresAt: z.number().describe("When this attempt stops being answerable, in milliseconds, so a card can stop waiting instead of spinning."),
108
101
  });
109
- export type CursorLoginStart = z.infer<typeof CursorLoginStartSchema>;
110
- // Abandon a sign-in nobody completed, so the daemon stops polling Cursor for it. Ordinary tidiness rather than
111
- // a security boundary: an unanswered attempt also times out on its own (see `expiresAt`).
112
- export const CursorLoginCancelSchema = z.object({ handshake: z.string().min(1).describe("Which attempt to stop waiting on.") });
113
- // xAI Grok (via OpenCode) uses subscription OAuth via the headless device-code method. `start` returns the
114
- // `url` the user opens (xAI's verification_uri_complete, which pre-fills the code) and `code`, the same
115
- // one-time code, surfaced so the card matches x.ai exactly. There is no paste-back: OpenCode polls to
116
- // completion and the UI polls `/grok/accounts`.
117
- // ponytail: OpenCode holds one xAI auth per data dir, so Grok stays single-account, the list is 0 or 1. Per
118
- // account would need an OpenCode server per data dir; add when there's demand.
119
- // A device-code login start: the verification URL + the one-time code the user enters there. The native Grok
120
- // flow (via OpenCode), see TranslatorStartSchema for the routed-provider connect, which adds `state`.
121
- export const DeviceStartSchema = z.object({
122
- url: z.string().describe("The page to open, which already has the code in it."),
123
- code: z
124
- .string()
125
- .describe(
126
- "The one-time code, shown as well so the page and the card say the same thing. Nothing is pasted back: the sandbox waits for the sign-in to complete on its own.",
127
- ),
102
+ export type LoginStart = z.infer<typeof LoginStartSchema>;
103
+ // The estate to sign in to, where a provider has more than one (Z.ai's international and mainland plans). Absent
104
+ // takes the provider's default, which is what a provider with a single estate always sends.
105
+ export const LoginRequestSchema = z.object({
106
+ variant: z.string().min(1).optional().describe("Which estate to sign in to. Absent takes the provider's default."),
107
+ });
108
+ /* The half a person brings back, for the two flows that have one: the code the page showed (a paste), or the
109
+ * whole address a redirect landed on. Not the handshake's state, unlike the translator's version below: that
110
+ * one addresses a session CLIProxyAPI holds, so the state has to travel, while this handshake is held right
111
+ * here and taking the caller's word for its own state would be checking a claim against itself. */
112
+ export const LoginCompleteSchema = z.object({
113
+ handshake: z.string().min(1).describe("Which attempt this belongs to."),
114
+ code: z.string().optional().describe("The code the sign-in page showed, for a paste sign-in."),
115
+ redirectUrl: z.string().optional().describe("The address the browser was sent to, whole, for a redirect sign-in. The grant is inside it."),
116
+ label: z.string().optional().describe("What to call the account. Blank derives one from the sign-in."),
117
+ });
118
+ // What finishing hands back: the account, where the exchange answers with one at once (a paste); absent where
119
+ // the daemon still has a mint to do behind the answer and the row lands in the account list minutes later.
120
+ export const LoginCompletedSchema = z.object({
121
+ account: OauthAccountSchema.optional().describe("The account it connected, where the sign-in ends here. Absent means keep watching the account list."),
128
122
  });
123
+ // Abandon a sign-in nobody completed, so the daemon stops polling the vendor for it. Ordinary tidiness rather
124
+ // than a security boundary: an unanswered attempt also times out on its own (see `expiresAt`).
125
+ export const LoginCancelSchema = z.object({ handshake: z.string().min(1).describe("Which attempt to stop waiting on.") });
129
126
  // A routed-provider subscription login start (codex/grok/kimi/gemini via CLIProxyAPI). Device flows poll to
130
127
  // completion after the user approves upstream; redirect flows need the browser's landing URL pasted back. The
131
128
  // explicit flow discriminator matters even when a provider's verification URL already embeds its optional code.
@@ -251,8 +251,6 @@ export const SkillRemoveSchema = z.object({
251
251
  // tool is one daemon-side registry entry, not a new settings field.
252
252
  // hashlineEdits , swaps the native Read/Edit/Write for hash-anchored edits on the Claude path (stale-file
253
253
  // guard + fewer output tokens); off ⇒ the native file tools.
254
- // terseOutput , appends a concise-response steer to the end of the system prompt (a stable suffix, so it
255
- // composes with stableSystemPrompt) to cut the model's OWN output tokens.
256
254
  // systemPromptMode , which base the agent's prompt is: "intentic" (default), "claude", or "custom".
257
255
  // systemPrompt , the owner's own prompt text, used only by "custom" mode, where it is the ENTIRE system
258
256
  // prompt and nothing the daemon would otherwise append rides with it, see its own note.
@@ -306,28 +304,24 @@ export const SandboxSettingsSchema = z.object({
306
304
  "Keep the instructions identical between turns so the provider can cache them, moving anything that varies into the message instead. Cheaper, at the cost of some flexibility.",
307
305
  ),
308
306
  skills: z.array(z.string()).default(["lsp", "fileq"]).describe("Which skills are switched on."),
307
+ /* WHICH CONTEXT SHELF A CONVERSATION OPENS ON when nothing closer to it says (schemas/context.ts). A persona
308
+ * card's own `context` wins where a turn wears one; this is the sandbox's answer for the turns that do not.
309
+ * Empty means no shelf, so a conversation carries every repository the workspace has, which is what every
310
+ * conversation did before shelves existed. Empty rather than optional because a settings object is parsed
311
+ * from `{}` until the owner first changes something, and every field here has to answer to that. */
312
+ contextShelf: z
313
+ .string()
314
+ .max(60)
315
+ .default("")
316
+ .describe(
317
+ "Which context shelf a conversation opens on when its persona names none: the part of the workspace it carries. Empty means every repository, as before shelves existed.",
318
+ ),
309
319
  hashlineEdits: z
310
320
  .boolean()
311
321
  .default(false)
312
322
  .describe(
313
323
  "Have the agent edit files by line number rather than by quoting the text it wants replaced. Cheaper on large files, and less forgiving of a stale read.",
314
324
  ),
315
- terseOutput: z.boolean().default(false).describe("Ask the agent to say less. It changes how much it narrates, not how much it does."),
316
- /* Measurement control for the terse steer, at TURN level, the same trick `outputHoldout` plays over
317
- * commands, one layer up. A fraction [0,1] of otherwise-eligible turns run WITHOUT the steer and record
318
- * which arm they ran on (UsageTurn.terse), so the savings report can compare two real populations.
319
- *
320
- * It has to be an experiment: unlike a cleaned command, which yields its own raw baseline in the same
321
- * event, a turn cannot be re-run to see what it would have said unsteered. 0 ⇒ no measurement (every
322
- * eligible turn is steered), which is the default because the control costs the very tokens it measures. */
323
- terseHoldout: z
324
- .number()
325
- .min(0)
326
- .max(1)
327
- .default(0)
328
- .describe(
329
- "What share of turns to run without that instruction, so the two can be compared honestly. It has to be measured this way, because a turn cannot be re-run to see what it would have said. Zero means no measurement, which is the default, since the comparison costs the very tokens it is measuring.",
330
- ),
331
325
  /* WHICH SYSTEM PROMPT THE AGENT RUNS ON, the base, before anything this turn composes.
332
326
  *
333
327
  * intentic. Intentic's own prompt, tuned for this harness (intentic-prompt.ts). The default.
@@ -337,15 +331,14 @@ export const SandboxSettingsSchema = z.object({
337
331
  *
338
332
  * The first two are peers: both get the harness's own guidance appended (the AskUserQuestion/plan blocks
339
333
  * the chat's cards need, the checklist guidance behind the todo panel, the browser-tool guidance), plus the
340
- * delegation note and the terse steer. `custom` is the one that does not, by the owner's explicit choice,
341
- * see the field below. */
334
+ * delegation note. `custom` is the one that does not, by the owner's explicit choice, see the field below. */
342
335
  systemPromptMode: SystemPromptModeSchema.default("intentic").describe(
343
336
  "Which instructions the agent starts from: intentic's own, the ones the installed Claude Code carries, or your own. The first two both get this product's own guidance added on top; your own gets nothing added, which is the point of it.",
344
337
  ),
345
338
  /* The owner's own prompt, used only when `systemPromptMode` is "custom". Then it is the ENTIRE system
346
- * prompt: both built-in bases are gone and so is everything the daemon would otherwise append, the widget
347
- * guidance the chat's cards are driven by, and the terse-output steer (whose toggle goes inert). That is
348
- * the price of total control, and the UI states it at the moment of the edit rather than letting the
339
+ * prompt: both built-in bases are gone and so is everything the daemon would otherwise append, including
340
+ * the widget guidance the chat's cards are driven by. That is the price of total control, and the UI states
341
+ * it at the moment of the edit rather than letting the
349
342
  * widgets go quietly dark. Only the cross-provider delegation note survives, because it has a home outside
350
343
  * the system prompt already (the user-message preamble stableSystemPrompt puts it in).
351
344
  *
@@ -777,7 +770,7 @@ export const SandboxSettingsSchema = z.object({
777
770
  * The refusal an agent sees names the env var (`ask them to raise CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS`),
778
771
  * which is why these three exist as settings at all: without them the only answer to that ask is editing
779
772
  * the container's environment and restarting the daemon. */
780
- subagentsAtOnce: z.number().min(1).max(200).default(20).describe("How many helper agents may work at the same time."),
773
+ subagentsAtOnce: z.number().min(1).max(200).default(20).describe("How many subagents may work at the same time."),
781
774
  subagentsPerTurn: z.number().min(1).max(2000).default(200).describe("How many a single turn may start in total."),
782
775
  // Depth 1 = an agent may delegate, but its children may not. The CLI's own default is 3, and it is the one
783
776
  // of the three whose runaway case is unbounded rather than merely wide, each level multiplies the last.
@@ -786,7 +779,7 @@ export const SandboxSettingsSchema = z.object({
786
779
  .min(1)
787
780
  .max(10)
788
781
  .default(3)
789
- .describe("How many levels deep the delegation may go, since a helper can start helpers of its own."),
782
+ .describe("How many levels deep the delegation may go, since a subagent can start subagents of its own."),
790
783
  });
791
784
  export type SandboxSettings = z.infer<typeof SandboxSettingsSchema>;
792
785
  // One of the two built-in bases, as text: Intentic's own prompt, or Claude Code's preset read out of the CLI
@@ -800,19 +793,9 @@ export const BuiltinPromptTextSchema = z.object({ text: z.string(), version: z.s
800
793
  export type BuiltinPromptText = z.infer<typeof BuiltinPromptTextSchema>;
801
794
  /* ---- savings report: what each token-reduction mechanism actually saved ----
802
795
  *
803
- * TWO FAMILIES, deliberately never one list of bars. They are measured differently, and a chart that ranks
804
- * them side by side claims a confidence and a denominator that only one of them has:
805
- *
806
- * input , shell output the cleaners trimmed before the model ever saw it. Both sides of the comparison come
807
- * off the SAME command (raw in, emitted out), so the counterfactual is observed rather than
808
- * estimated: exact, per command, no sample size to argue about.
809
- * output, the model's own tokens under the terse steer. There is no second run of the same turn to compare
810
- * against, so the only honest number is an experiment: a turn-level holdout, an n per arm, and a
811
- * margin. It is absent entirely until both arms are large enough for the delta to mean anything.
812
- *
813
- * The two are also in different units of value, a saved tool-output token is saved again on every later
814
- * request of that conversation, an output token is saved once but costs several times as much, which is the
815
- * other reason they are separate sections with separate totals rather than one number.
796
+ * Input-side savings: shell output the cleaners trimmed before the model ever saw it. Both sides of the
797
+ * comparison come off the SAME command (raw in, emitted out), so the counterfactual is observed rather than
798
+ * estimated: exact, per command, no sample size to argue about.
816
799
  */
817
800
 
818
801
  // One mechanism's realized saving, biggest first. `savedTokens` is what THIS stage removed from what reached
@@ -851,15 +834,12 @@ export const SavingsArmSchema = z.object({ turns: z.number(), mean: z.number() }
851
834
  * stand behind. An experiment can carry several, see TurnExperimentSchema.
852
835
  *
853
836
  * `metric` says what `mean` counts and what `deltaPct` is a delta in, and choosing it is most of the work.
854
- * proseChars , the terse steer: the thing it steers, and the only part of the model's output that
855
- * responds to being asked to be brief (UsageTurn.proseChars has why output tokens cannot).
856
837
  * searchCalls , the search teaching: the searches a turn ran, which the teaching directly changes.
857
838
  * openingSearches, the same, narrower: the searches before the turn first touched a file.
858
839
  * Search mechanisms must not be judged on COST. Cost is a whole turn's work, a search mechanism moves one part
859
- * of it, and the part sits inside the noise of the rest exactly as the steer's effect once sat inside its
860
- * tool-call arguments. */
840
+ * of it, and the part sits inside the noise of the rest. */
861
841
  export const TurnMetricReadingSchema = z.object({
862
- metric: z.enum(["proseChars", "searchCalls", "openingSearches"]),
842
+ metric: z.enum(["searchCalls", "openingSearches"]),
863
843
  on: SavingsArmSchema,
864
844
  off: SavingsArmSchema,
865
845
  /* HOW MUCH LONGER, when the margin spans zero and the honest answer is "keep collecting": the additional
@@ -886,23 +866,20 @@ export const TurnMetricReadingSchema = z.object({
886
866
  /* THE CLAIM, present only once there is one. Both together, and only when the margin does NOT span zero.
887
867
  *
888
868
  * A schema that can't express a half-measured experiment is how a 34%-that-becomes-8%-tomorrow never
889
- * reaches the screen, and clearing `minTurns` turned out not to be enough to buy that. The terse steer
890
- * crossed its thirtieth control turn and immediately reported +31.2% ± 35.1pp: a confidence interval
869
+ * reaches the screen, and clearing `minTurns` turned out not to be enough to buy that. An early search
870
+ * teaching run crossed its thirtieth control turn and immediately reported +31.2% ± 35.1pp: a confidence interval
891
871
  * running from −3.4% to +66.7%, which is to say no effect was measured at all, rendered as an alarming
892
872
  * number pointing the wrong way. Thirty turns is where the normal approximation starts to hold, not where
893
873
  * this much per-turn spread resolves an effect; requiring the interval to exclude zero is the same
894
874
  * withhold-until-it-means-something rule applied to the thing that actually decides whether it does.
895
875
  * deltaPct, change in the metric's mean per turn under the mechanism; negative is a saving.
896
876
  * saved , what the delta is worth over the turns that actually ran with it, in this window, in the
897
- * metric's own unit (characters, or searches). */
877
+ * metric's own unit (searches). */
898
878
  deltaPct: z.number().optional(),
899
879
  saved: z.number().optional(),
900
880
  });
901
881
  export type TurnMetricReading = z.infer<typeof TurnMetricReadingSchema>;
902
- /* A turn-level A/B, the one shape both of this sandbox's turn experiments report in, because they differ in
903
- * nothing but which flag flips and what the turns are judged on. Only turns the mechanism was ELIGIBLE for are
904
- * counted: a turn under a custom system prompt drops the terse steer along with everything else the daemon
905
- * appends, so it belongs to neither arm.
882
+ /* A turn-level A/B, the shape the iq search teaching experiment reports in.
906
883
  *
907
884
  * ONE COIN FLIP, SEVERAL READINGS. `metrics` is a list because the search teaching is judged on two, the
908
885
  * searches a turn ran, and the ones it ran before touching a file, and they are two readings of the SAME
@@ -927,9 +904,9 @@ export const TurnExperimentSchema = z.object({
927
904
  cohort: z.string().optional(),
928
905
  });
929
906
  export type TurnExperiment = z.infer<typeof TurnExperimentSchema>;
930
- // `output`/`search` are absent when that experiment isn't running at all (its flag off, or no holdout set), a
931
- // section that isn't there reads as "not measured", which is the truth, while zeros would read as "measured,
932
- // worth nothing".
907
+ // `search` is absent when that experiment isn't running at all (its flag off, or no holdout set), a section
908
+ // that isn't there reads as "not measured", which is the truth, while zeros would read as "measured, worth
909
+ // nothing".
933
910
  /* WHAT THE COMPLEXITY JUDGE HAS BEEN SAYING, read back off the spend ledger's tier fields (UsageTurn.tierScore
934
911
  * and friends) over the requested window. The three numbers docs/model-routing-design.md §4 says the feature
935
912
  * cannot be defended without, plus the veto count, and nothing else: no counterfactual "you would have saved
@@ -964,7 +941,6 @@ export const TierReportSchema = z.object({
964
941
  export type TierReport = z.infer<typeof TierReportSchema>;
965
942
  export const SavingsReportSchema = z.object({
966
943
  input: InputSavingsSchema,
967
- output: TurnExperimentSchema.optional(),
968
944
  search: TurnExperimentSchema.optional(),
969
945
  // Automatic tier selection's readout, see TierReportSchema. Absent ⇒ nothing was judged in the window.
970
946
  tier: TierReportSchema.optional(),
@@ -194,14 +194,14 @@ export type SubagentKind = z.infer<typeof SubagentKindSchema>;
194
194
  // an operator acts on differently from "the child is working".
195
195
  export const SubagentStatusSchema = z.enum(["pending", "running", "blocked", "completed", "failed", "killed", "paused"]);
196
196
  export type SubagentStatus = z.infer<typeof SubagentStatusSchema>;
197
- /* WHETHER ANYTHING CHECKED WHAT THE HELPER DID, carried beside its report rather than left for the reader to
198
- * assume. Computed from the helper's own tool calls, the files it edited against the checks that ran after
197
+ /* WHETHER ANYTHING CHECKED WHAT THE SUBAGENT DID, carried beside its report rather than left for the reader to
198
+ * assume. Computed from the subagent's own tool calls, the files it edited against the checks that ran after
199
199
  * them (the daemon's child-verification.ts), so it holds on every provider rather than only where the Claude
200
200
  * hooks reach.
201
201
  *
202
202
  * The four states are deliberately not two. `verified` and `failing` each name the command that spoke, so a
203
203
  * targeted test is never read as the suite; `unproven` is the one that matters most, work changed and nothing
204
- * ran; and `no-code` says the helper edited nothing, which is the honest answer for a research helper and
204
+ * ran; and `no-code` says the subagent edited nothing, which is the honest answer for a research subagent and
205
205
  * must not be rendered as approval. Absent ⇒ the daemon saw no tool calls from it at all. */
206
206
  export const SubagentVerificationSchema = z.object({
207
207
  state: z
@@ -222,10 +222,10 @@ export const SubagentSessionSchema = z.object({
222
222
  id: z
223
223
  .string()
224
224
  .describe(
225
- "The id of the tool call that started it (an SDK child) or the child's own conversation id (a spawned one); either way both sides already hold it, so a card links to its helper with the id it has and the helper points back the same way.",
225
+ "The id of the tool call that started it (an SDK child) or the child's own conversation id (a spawned one); either way both sides already hold it, so a card links to its subagent with the id it has and the subagent points back the same way.",
226
226
  ),
227
227
  kind: SubagentKindSchema.describe(
228
- "What sort of helper: one the runtime's own Task tool spawned in-process, or a full agent the daemon started for the turn. It changes only how you watch it.",
228
+ "What sort of subagent: one the runtime's own Task tool spawned in-process, or a full child agent the daemon started for the turn. It changes only how you watch it.",
229
229
  ),
230
230
  // The conversation whose turn spawned this, what the area groups its rows by, and the way back to the chat
231
231
  // the card lives in.
@@ -233,19 +233,19 @@ export const SubagentSessionSchema = z.object({
233
233
  // What it is and what it was asked to do: the subagent type (`Explore`, `general-purpose`) or a spawned
234
234
  // child's provider label, and the caller's one-line description. The area's row and the card's title read
235
235
  // as `Explore · Locate claimIndexer definition`.
236
- agentType: z.string().optional().describe("What kind of helper it is."),
236
+ agentType: z.string().optional().describe("What kind of subagent it is."),
237
237
  description: z.string().optional().describe("What it was asked to do, in one line."),
238
238
  model: z.string().optional().describe("Which model it runs on."),
239
239
  // Which provider serves a `spawned` child (its AgentProvider id), so the row can wear the right logo. An
240
240
  // SDK subagent implies its own: it runs on its parent's provider.
241
- provider: z.string().optional().describe("Which provider serves it, for a helper spawned across providers."),
241
+ provider: z.string().optional().describe("Which provider serves it, for a child agent spawned across providers."),
242
242
  // How deep in the spawn tree (1 = spawned by the turn itself). From the SDK's meta.json; a subagent may
243
243
  // itself delegate, and a flat list that cannot say so reads as though the turn started all of them.
244
244
  spawnDepth: z
245
245
  .number()
246
246
  .optional()
247
247
  .describe(
248
- "How deep in the chain it sits, where one means the turn itself started it. A helper can start helpers, and a flat list that could not say so would read as though the turn started all of them.",
248
+ "How deep in the chain it sits, where one means the turn itself started it. A subagent can start subagents, and a flat list that could not say so would read as though the turn started all of them.",
249
249
  ),
250
250
  // Backgrounded: the parent went on working instead of waiting for it. This is the whole reason the list
251
251
  // exists, a backgrounded child used to be invisible until its result landed, sometimes minutes later.
@@ -253,7 +253,7 @@ export const SubagentSessionSchema = z.object({
253
253
  .boolean()
254
254
  .optional()
255
255
  .describe(
256
- "The parent carried on working instead of waiting for it. This is the whole reason the list exists: such a helper used to be invisible until its result landed, sometimes minutes later.",
256
+ "The parent carried on working instead of waiting for it. This is the whole reason the list exists: such a subagent used to be invisible until its result landed, sometimes minutes later.",
257
257
  ),
258
258
  status: SubagentStatusSchema.describe(
259
259
  "How it is going. Blocked means it needs an answer, which a parent and an operator act on differently from it simply working.",
@@ -266,7 +266,7 @@ export const SubagentSessionSchema = z.object({
266
266
  tokens: z
267
267
  .number()
268
268
  .optional()
269
- .describe("What it has spent. Its own, so a parent's cost and the sum of its helpers' are two different true numbers."),
269
+ .describe("What it has spent. Its own, so a parent's cost and the sum of its subagents' are two different true numbers."),
270
270
  toolUses: z.number().optional().describe("How many tools it has used."),
271
271
  lastTool: z.string().optional().describe("The last one it reached for."),
272
272
  // Its report, the last assistant message (SubagentStop) or the task summary. The answer to "what did it
@@ -274,7 +274,7 @@ export const SubagentSessionSchema = z.object({
274
274
  summary: z
275
275
  .string()
276
276
  .optional()
277
- .describe("Its report: what it concluded, without opening its record. The question a finished helper gets read for."),
277
+ .describe("Its report: what it concluded, without opening its record. The question a finished subagent gets read for."),
278
278
  error: z.string().optional().describe("Why it failed, when it did."),
279
279
  // Whether anything checked the work behind that report (SubagentVerificationSchema). Filled once it ends:
280
280
  // a standing read while it is still working would be a verdict on a job half done.
@@ -282,7 +282,7 @@ export const SubagentSessionSchema = z.object({
282
282
  });
283
283
  export type SubagentSession = z.infer<typeof SubagentSessionSchema>;
284
284
  export const SubagentsListSchema = z.object({
285
- sessions: z.array(SubagentSessionSchema).describe("Every helper this sandbox's conversations have started."),
285
+ sessions: z.array(SubagentSessionSchema).describe("Every subagent and child agent this sandbox's conversations have started."),
286
286
  });
287
287
  export type SubagentsList = z.infer<typeof SubagentsListSchema>;
288
288
  export const SubagentIdParamSchema = z.object({ id: z.string() });
@@ -83,14 +83,6 @@ export const UsageTurnSchema = z.object({
83
83
  cacheCreationTokens: z.number().describe("Tokens written to cache, which cost more up front and less afterwards."),
84
84
  costUsd: z.number().describe("What it cost, in dollars."),
85
85
  durationMs: z.number().describe("How long it took, in milliseconds."),
86
- /* Which arm of the terse experiment this turn ran on (settings.terseHoldout), the only record of it, and
87
- * the reason the savings report can say what the steer is worth instead of guessing.
88
- *
89
- * ABSENT means "not part of the experiment", not "off": a turn under a custom system prompt drops the
90
- * steer along with everything else the daemon appends, and a turn run with the experiment switched off has
91
- * no control to be compared against. Pooling those into the off-arm would compare steered turns against a
92
- * population selected by something other than the coin flip, which is not a control at all. */
93
- terse: z.boolean().optional(),
94
86
  /* Which arm of the iq SEARCH-TEACHING experiment this conversation runs on
95
87
  * (settings.iqSearchHoldout). Stable for every turn in one conversation: the treatment is instruction
96
88
  * loaded into a provider session, so flipping it per turn would call a remembered treatment a control.
@@ -99,25 +91,9 @@ export const UsageTurnSchema = z.object({
99
91
  // Hash of the plugin nudge + skill body used for this arm. Control turns carry it too, so a report can keep
100
92
  // both sides of one treatment revision together and exclude older wording after an upgrade.
101
93
  iqSearchCohort: z.string().optional(),
102
- /* Characters of the model's own PROSE this turn, the `delta` frames only, so no tool-call arguments and no
103
- * thinking. What the terse steer is judged on, and the reason it can be judged at all.
104
- *
105
- * `outputTokens` cannot serve: measured over a day of real turns it is 91.6% tool-call arguments (an Edit's
106
- * old_string and new_string, a Write's whole file body) and 7.8% prose. The steer moves prose. So a fifth
107
- * off the model's narration moves the total by 1.6%, against a margin of ±35 points, which is to say the
108
- * experiment was structurally unable to see its own treatment, and the number it printed instead was
109
- * whichever arm happened to draw the bigger tasks.
110
- *
111
- * CHARACTERS, not tokens, because the provider bills a total and never breaks it down, a token figure here
112
- * would be chars÷4 wearing a unit it had not earned. For a comparison of two arms the constant cancels
113
- * anyway, and the honest unit is the one actually counted.
114
- *
115
- * Absent ⇒ the turn predates this being measured; `armOf` drops it from the population rather than reading
116
- * it as a silent turn. */
117
- proseChars: z.number().optional(),
118
94
  /* SEARCHES THIS TURN RAN, every tool call that went looking for code, the dedicated search tools and the
119
95
  * CLI searches alike (isSearchCall owns the rule; `iq q` is Bash and would otherwise not be counted at all).
120
- * What the search teaching is judged on, and the same correction `proseChars` is to the terse steer.
96
+ * What the search teaching is judged on.
121
97
  *
122
98
  * COST PER TURN CANNOT SERVE: cost is a whole turn's worth of work, a search mechanism touches one part of
123
99
  * it, and the part lives inside the noise of the rest, exactly the shape that made output tokens unable to
@@ -128,8 +104,8 @@ export const UsageTurnSchema = z.object({
128
104
  * rather than being filtered out, they dilute both arms equally, while selecting on "did it search" would
129
105
  * select on the treatment itself.
130
106
  *
131
- * Absent ⇒ the turn predates this being measured; `armOf` drops it rather than reading it as a turn that
132
- * searched nothing. */
107
+ * Absent ⇒ the turn predates this being measured; the arm's mean drops it (turn-experiments.ts, the filter
108
+ * inside `conversationExperimentOf`) rather than reading it as a turn that searched nothing. */
133
109
  searchCalls: z.number().optional(),
134
110
  /* …and how many of them came BEFORE the turn first opened or changed a file, the orientation burst. A turn
135
111
  * that already knows where to look starts working; one that doesn't goes hunting first.
@@ -15,7 +15,6 @@ test("a payload from a build that predates a toggle parses, with the new toggle
15
15
  stableSystemPrompt: false,
16
16
  skills: [],
17
17
  hashlineEdits: false,
18
- terseOutput: true,
19
18
  iqSearch: true,
20
19
  outputCleaners: "-cap",
21
20
  outputHoldout: 0.1,