@intentic/sandbox-contract 1.329.0 → 1.331.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 (155) hide show
  1. package/dist/chores/probes.d.ts.map +1 -1
  2. package/dist/chores/probes.js +1 -2
  3. package/dist/chores/probes.js.map +1 -1
  4. package/dist/contracts/accounts.contract.d.ts +4 -0
  5. package/dist/contracts/accounts.contract.d.ts.map +1 -1
  6. package/dist/contracts/agent.contract.d.ts +1 -0
  7. package/dist/contracts/agent.contract.d.ts.map +1 -1
  8. package/dist/contracts/agents.contract.d.ts +69 -0
  9. package/dist/contracts/agents.contract.d.ts.map +1 -1
  10. package/dist/contracts/device.contract.d.ts +6 -0
  11. package/dist/contracts/device.contract.d.ts.map +1 -1
  12. package/dist/contracts/device.contract.js +2 -1
  13. package/dist/contracts/device.contract.js.map +1 -1
  14. package/dist/contracts/runner.contract.d.ts +3 -0
  15. package/dist/contracts/runner.contract.d.ts.map +1 -1
  16. package/dist/contracts/runs.contract.d.ts +35 -0
  17. package/dist/contracts/runs.contract.d.ts.map +1 -0
  18. package/dist/contracts/runs.contract.js +22 -0
  19. package/dist/contracts/runs.contract.js.map +1 -0
  20. package/dist/contracts/settings.contract.d.ts +10 -0
  21. package/dist/contracts/settings.contract.d.ts.map +1 -1
  22. package/dist/contracts/system.contract.d.ts +45 -30
  23. package/dist/contracts/system.contract.d.ts.map +1 -1
  24. package/dist/contracts/system.contract.js +10 -1
  25. package/dist/contracts/system.contract.js.map +1 -1
  26. package/dist/contracts/translator.contract.d.ts +4 -0
  27. package/dist/contracts/translator.contract.d.ts.map +1 -1
  28. package/dist/contracts/workspace.contract.d.ts +37 -0
  29. package/dist/contracts/workspace.contract.d.ts.map +1 -1
  30. package/dist/contracts/workspace.contract.js +11 -1
  31. package/dist/contracts/workspace.contract.js.map +1 -1
  32. package/dist/events/agent-events.d.ts +1 -0
  33. package/dist/events/agent-events.d.ts.map +1 -1
  34. package/dist/events/system-events.d.ts +10 -0
  35. package/dist/events/system-events.d.ts.map +1 -1
  36. package/dist/index.d.ts +179 -1
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +7 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/models/model-roles.d.ts +8 -0
  41. package/dist/models/model-roles.d.ts.map +1 -1
  42. package/dist/models/model-roles.js +8 -0
  43. package/dist/models/model-roles.js.map +1 -1
  44. package/dist/models/plan-pools.d.ts +2 -0
  45. package/dist/models/plan-pools.d.ts.map +1 -1
  46. package/dist/models/plan-pools.js +23 -7
  47. package/dist/models/plan-pools.js.map +1 -1
  48. package/dist/protocol/doors/listener-protocol.d.ts +0 -1
  49. package/dist/protocol/doors/listener-protocol.d.ts.map +1 -1
  50. package/dist/protocol/doors/listener-protocol.js +3 -2
  51. package/dist/protocol/doors/listener-protocol.js.map +1 -1
  52. package/dist/protocol/raw/raw-routes.d.ts +25 -0
  53. package/dist/protocol/raw/raw-routes.d.ts.map +1 -1
  54. package/dist/protocol/raw/raw-routes.js +7 -0
  55. package/dist/protocol/raw/raw-routes.js.map +1 -1
  56. package/dist/schemas/agent.d.ts +3 -0
  57. package/dist/schemas/agent.d.ts.map +1 -1
  58. package/dist/schemas/agents-list.d.ts +4 -0
  59. package/dist/schemas/agents-list.d.ts.map +1 -1
  60. package/dist/schemas/agents.d.ts +13 -0
  61. package/dist/schemas/agents.d.ts.map +1 -1
  62. package/dist/schemas/agents.js +4 -3
  63. package/dist/schemas/agents.js.map +1 -1
  64. package/dist/schemas/device-artifacts.d.ts +13 -0
  65. package/dist/schemas/device-artifacts.d.ts.map +1 -1
  66. package/dist/schemas/device-artifacts.js +13 -0
  67. package/dist/schemas/device-artifacts.js.map +1 -1
  68. package/dist/schemas/environment.d.ts +30 -0
  69. package/dist/schemas/environment.d.ts.map +1 -1
  70. package/dist/schemas/environment.js +13 -0
  71. package/dist/schemas/environment.js.map +1 -1
  72. package/dist/schemas/providers/plan-limits.d.ts +7 -0
  73. package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
  74. package/dist/schemas/providers/plan-limits.js +5 -1
  75. package/dist/schemas/providers/plan-limits.js.map +1 -1
  76. package/dist/schemas/providers/provider-oauth.d.ts +4 -0
  77. package/dist/schemas/providers/provider-oauth.d.ts.map +1 -1
  78. package/dist/schemas/run-targets.d.ts +82 -0
  79. package/dist/schemas/run-targets.d.ts.map +1 -0
  80. package/dist/schemas/run-targets.js +49 -0
  81. package/dist/schemas/run-targets.js.map +1 -0
  82. package/dist/schemas/settings-history.d.ts +6 -1
  83. package/dist/schemas/settings-history.d.ts.map +1 -1
  84. package/dist/schemas/settings-history.js +4 -1
  85. package/dist/schemas/settings-history.js.map +1 -1
  86. package/dist/schemas/settings.d.ts +10 -0
  87. package/dist/schemas/settings.d.ts.map +1 -1
  88. package/dist/schemas/settings.js +2 -1
  89. package/dist/schemas/settings.js.map +1 -1
  90. package/dist/schemas/speech.d.ts +75 -0
  91. package/dist/schemas/speech.d.ts.map +1 -0
  92. package/dist/schemas/speech.js +66 -0
  93. package/dist/schemas/speech.js.map +1 -0
  94. package/dist/schemas/terminal.d.ts +4 -0
  95. package/dist/schemas/terminal.d.ts.map +1 -1
  96. package/dist/schemas/terminal.js +4 -0
  97. package/dist/schemas/terminal.js.map +1 -1
  98. package/dist/schemas/turn-break.d.ts +7 -1
  99. package/dist/schemas/turn-break.d.ts.map +1 -1
  100. package/dist/schemas/turn-break.js +4 -2
  101. package/dist/schemas/turn-break.js.map +1 -1
  102. package/dist/schemas/workspace/workspace-graph.d.ts +133 -0
  103. package/dist/schemas/workspace/workspace-graph.d.ts.map +1 -0
  104. package/dist/schemas/workspace/workspace-graph.js +83 -0
  105. package/dist/schemas/workspace/workspace-graph.js.map +1 -0
  106. package/dist/schemas/workspace/workspace-repos.d.ts +0 -39
  107. package/dist/schemas/workspace/workspace-repos.d.ts.map +1 -1
  108. package/dist/schemas/workspace/workspace-repos.js +0 -15
  109. package/dist/schemas/workspace/workspace-repos.js.map +1 -1
  110. package/dist/state/definition.d.ts +8 -4
  111. package/dist/state/definition.d.ts.map +1 -1
  112. package/dist/state/workspace-state.d.ts +2 -1
  113. package/dist/state/workspace-state.d.ts.map +1 -1
  114. package/dist/state/workspace-state.js +5 -1
  115. package/dist/state/workspace-state.js.map +1 -1
  116. package/dist/text/transcript-page.d.ts +5 -0
  117. package/dist/text/transcript-page.d.ts.map +1 -0
  118. package/dist/text/transcript-page.js +17 -0
  119. package/dist/text/transcript-page.js.map +1 -0
  120. package/package.json +5 -5
  121. package/src/chores/probes.ts +1 -2
  122. package/src/contracts/device.contract.ts +6 -1
  123. package/src/contracts/runs.contract.ts +26 -0
  124. package/src/contracts/system.contract.ts +13 -0
  125. package/src/contracts/workspace.contract.ts +12 -1
  126. package/src/index.ts +7 -1
  127. package/src/models/agent-runtimes.ts +2 -2
  128. package/src/models/model-roles.ts +8 -0
  129. package/src/models/plan-pools.test.ts +27 -0
  130. package/src/models/plan-pools.ts +38 -10
  131. package/src/protocol/doors/listener-protocol.ts +5 -4
  132. package/src/protocol/raw/raw-routes.ts +12 -0
  133. package/src/schemas/agents.ts +6 -3
  134. package/src/schemas/device-artifacts.ts +21 -0
  135. package/src/schemas/environment.ts +25 -0
  136. package/src/schemas/providers/plan-limits.ts +9 -1
  137. package/src/schemas/run-targets.ts +93 -0
  138. package/src/schemas/settings-history.ts +15 -1
  139. package/src/schemas/settings.test.ts +25 -0
  140. package/src/schemas/settings.ts +6 -1
  141. package/src/schemas/speech.ts +96 -0
  142. package/src/schemas/terminal.ts +4 -0
  143. package/src/schemas/turn-break.ts +18 -9
  144. package/src/schemas/workspace/workspace-graph.ts +116 -0
  145. package/src/schemas/workspace/workspace-repos.ts +0 -22
  146. package/src/state/workspace-state.test.ts +42 -1
  147. package/src/state/workspace-state.ts +12 -1
  148. package/src/text/transcript-page.test.ts +39 -0
  149. package/src/text/transcript-page.ts +37 -0
  150. package/dist/text/whisper.d.ts +0 -3
  151. package/dist/text/whisper.d.ts.map +0 -1
  152. package/dist/text/whisper.js +0 -11
  153. package/dist/text/whisper.js.map +0 -1
  154. package/src/text/whisper.test.ts +0 -18
  155. package/src/text/whisper.ts +0 -17
@@ -0,0 +1,5 @@
1
+ import type { TranscriptRow, TranscriptTool } from "../events/transcript.js";
2
+ export declare const PAGE_TEXT_CAP = 8000;
3
+ export declare const fitPageRow: <Row extends TranscriptRow>(row: Row) => Row;
4
+ export declare const fitNestedTool: (tool: TranscriptTool) => TranscriptTool;
5
+ //# sourceMappingURL=transcript-page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcript-page.d.ts","sourceRoot":"","sources":["../../src/text/transcript-page.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAS7E,eAAO,MAAM,aAAa,OAAQ,CAAC;AAkBnC,eAAO,MAAM,UAAU,GAAI,GAAG,SAAS,aAAa,OAAO,GAAG,KAAG,GACgB,CAAC;AAGlF,eAAO,MAAM,aAAa,SAAU,cAAc,KAAG,cAInD,CAAC"}
@@ -0,0 +1,17 @@
1
+ export const PAGE_TEXT_CAP = 8_000;
2
+ const fitContent = (entry) => entry.type === "text" && entry.text.length > PAGE_TEXT_CAP ? { ...entry, text: entry.text.slice(0, PAGE_TEXT_CAP) } : entry;
3
+ const fitPageTool = (tool) => {
4
+ const { children, content, ...carried } = tool;
5
+ return {
6
+ ...carried,
7
+ ...(content !== undefined ? { content: content.map(fitContent) } : {}),
8
+ ...(children !== undefined && children.length > 0 ? { nested: children.length } : {}),
9
+ };
10
+ };
11
+ export const fitPageRow = (row) => row.tools === undefined ? row : { ...row, tools: row.tools.map(fitPageTool) };
12
+ export const fitNestedTool = (tool) => ({
13
+ ...tool,
14
+ ...(tool.content !== undefined ? { content: tool.content.map(fitContent) } : {}),
15
+ ...(tool.children !== undefined ? { children: tool.children.map(fitNestedTool) } : {}),
16
+ });
17
+ //# sourceMappingURL=transcript-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcript-page.js","sourceRoot":"","sources":["../../src/text/transcript-page.ts"],"names":[],"mappings":"AAUA,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,CAAC;AAEnC,MAAM,UAAU,GAAG,CAAC,KAAsB,EAAmB,EAAE,CAC3D,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;AAKhI,MAAM,WAAW,GAAG,CAAC,IAAoB,EAAkB,EAAE;IACzD,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,GAAG,IAAI,CAAC;IAC/C,OAAO;QACH,GAAG,OAAO;QACV,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtE,GAAG,CAAC,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACxF,CAAC;AACN,CAAC,CAAC;AAGF,MAAM,CAAC,MAAM,UAAU,GAAG,CAA4B,GAAQ,EAAO,EAAE,CACnE,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;AAGlF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,IAAoB,EAAkB,EAAE,CAAC,CAAC;IACpE,GAAG,IAAI;IACP,GAAG,CAAC,IAAI,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAChF,GAAG,CAAC,IAAI,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;CACzF,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentic/sandbox-contract",
3
- "version": "1.329.0",
3
+ "version": "1.331.0",
4
4
  "description": "oRPC wire contract for the intentic sandbox daemon, shared by the daemon and its browser client",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -306,10 +306,10 @@
306
306
  }
307
307
  },
308
308
  "dependencies": {
309
- "@intentic/base": "1.329.0",
310
- "@intentic/constants": "1.329.0",
311
- "@intentic/extension-manifest": "1.329.0",
312
- "@intentic/registry": "1.329.0",
309
+ "@intentic/base": "1.331.0",
310
+ "@intentic/constants": "1.331.0",
311
+ "@intentic/extension-manifest": "1.331.0",
312
+ "@intentic/registry": "1.331.0",
313
313
  "@orpc/contract": "1.15.4",
314
314
  "tslib": "2.8.1",
315
315
  "zod": "4.6.5"
@@ -86,7 +86,6 @@ const STANDALONE_DIRS =
86
86
  `sed -n -e 's|/\\*\\*[[:space:]]*$||' -e 's|^[[:space:]]*-[[:space:]]*!\\([^[:space:]#]*\\).*$|\\1|p'`;
87
87
 
88
88
  // The line that opens one standalone package's report, as JSON so a guard can still read the stream with `jq -s`.
89
- const STANDALONE_KEY = `standalone`;
90
89
  const STANDALONE_LINE = /^\{"standalone":"([^"]*)"\}$/;
91
90
 
92
91
  // Runs `measure` against each standalone package in a scratch copy, never in place: `--lockfile-only` would write
@@ -97,7 +96,7 @@ const STANDALONE_LINE = /^\{"standalone":"([^"]*)"\}$/;
97
96
  const standaloneCommand = (measure: string): string =>
98
97
  `${STANDALONE_DIRS} | while IFS= read -r dir; do ` +
99
98
  `[ -f "$dir/package.json" ] || continue; ` +
100
- `printf '{"${STANDALONE_KEY}":"%s"}\\n' "$dir"; ` +
99
+ `printf '{"standalone":"%s"}\\n' "$dir"; ` +
101
100
  `scratch=$(mktemp -d) || continue; ` +
102
101
  `for file in package.json pnpm-lock.yaml package-lock.json .npmrc; do [ -f "$dir/$file" ] && cp "$dir/$file" "$scratch/"; done; ` +
103
102
  `( cd "$scratch" && { [ -f pnpm-lock.yaml ] || { [ -f package-lock.json ] && pnpm import >/dev/null 2>&1; } || ` +
@@ -7,7 +7,7 @@ import { DeviceFactsSchema } from "../schemas/hosts.js";
7
7
  import { LoopbackCatchEventSchema, LoopbackCatchSchema } from "../schemas/loopback-catch.js";
8
8
  import { OkSchema } from "../schemas/shared.js";
9
9
  import { ProjectDeliveryResultSchema, ProjectDeliverySchema } from "../schemas/project-delivery.js";
10
- import { StageArtifactResultSchema, StageArtifactSchema } from "../schemas/device-artifacts.js";
10
+ import { DialLoopbackSchema, StageArtifactResultSchema, StageArtifactSchema } from "../schemas/device-artifacts.js";
11
11
 
12
12
  // What a connected device can be asked, over the socket it opened; the machine is the oRPC server, the daemon the
13
13
  // client. No `.route()`: the procedure path is the address, not HTTP. Every input is strict (devices.ts says why). `mcp` stays opaque (`z.unknown()`) so a machine
@@ -41,4 +41,9 @@ export const deviceContract = {
41
41
  // Only the daemon calls it, for the agent's `devices push`, and only on an agent advertising `programs`; the machine
42
42
  // refuses every op unless "Run programs this sandbox sends" is on.
43
43
  stageArtifact: oc.input(StageArtifactSchema).output(StageArtifactResultSchema),
44
+ // A connection to the sandbox side of a `devices reach` tunnel: the machine dials its own 127.0.0.1:<port> and, once
45
+ // that answered, opens a WebSocket back to /system/hosts/tunnel with the ticket. Answers once the local port took
46
+ // the connection, or throws (nothing listening there, the switch is off). Advertised with `programs`; the machine
47
+ // allows it when "Run commands" or "Run programs this sandbox sends" is on, either of which already reaches it.
48
+ dialLoopback: oc.input(DialLoopbackSchema).output(z.object({ ok: z.literal(true) })),
44
49
  };
@@ -0,0 +1,26 @@
1
+ import { procedure } from "../protocol/route-meta.js";
2
+ import { RUN_TARGETS_FILE, RunStartedSchema, RunStartSchema, RunTargetsListSchema } from "../schemas/run-targets.js";
3
+
4
+ // A repository's programs run on the owner's own computers (schemas/run-targets.ts): what each repository declares, and
5
+ // the button that runs one. The run itself is `devices run` in a terminal, the same command an agent runs, so what the
6
+ // owner watches is what an agent gets.
7
+ export const runsContract = {
8
+ targets: procedure
9
+ .route({
10
+ method: "GET",
11
+ path: "/runs/targets",
12
+ summary: "What each repository can run on your computers",
13
+ description: `Every repository that declares run targets at \`${RUN_TARGETS_FILE}\`, and which of your computers could run one now.`,
14
+ })
15
+ .output(RunTargetsListSchema),
16
+ start: procedure
17
+ .route({
18
+ method: "POST",
19
+ path: "/runs/start",
20
+ summary: "Run a target on one of your computers",
21
+ description:
22
+ "Builds it in the sandbox, carries the build to the computer and starts it there, in a terminal you can watch. The computer still decides: its \"Run programs this sandbox sends\" switch has to be on.",
23
+ })
24
+ .input(RunStartSchema)
25
+ .output(RunStartedSchema),
26
+ };
@@ -17,6 +17,7 @@ import { DaemonSessionSchema, InfoSchema, ManifestProblemsSchema, ManifestRepair
17
17
  import { AutoUpdateInputSchema, AutoUpdateSchema, SkipUpdateInputSchema } from "../schemas/updates.js";
18
18
  import {
19
19
  BrowserNameParamSchema,
20
+ BrowserPageParamSchema,
20
21
  BrowsersListSchema,
21
22
  OpenBrowserInputSchema,
22
23
  OpenBrowserResultSchema,
@@ -253,6 +254,18 @@ export const systemContract = {
253
254
  })
254
255
  .input(BrowserNameParamSchema)
255
256
  .output(OkSchema),
257
+ // One tab, closed without watching its window: the Browsers strip shows every window's tabs, and only the window in
258
+ // front has a picture socket to say a close on.
259
+ closeBrowserPage: systemRoute
260
+ .route({
261
+ method: "DELETE",
262
+ path: "/system/browsers/{name}/pages/{pageId}",
263
+ summary: "Close one tab of a browser",
264
+ description:
265
+ "Closes one tab of a browser window, as its own close button does. A tab that is already gone answers the same, since gone is what was asked for.",
266
+ })
267
+ .input(BrowserPageParamSchema)
268
+ .output(OkSchema),
256
269
  // The picture and the hands come over /system/desktop-view; this is whether there is anything on it, and which windows
257
270
  // a tab could show alone. Pushed through the `desktop` runtime domain as windows open, close and take the keyboard,
258
271
  // and as their titles change while a view is open; never polled by the client.
@@ -13,9 +13,9 @@ import {
13
13
  ReposListSchema,
14
14
  RunTestsSchema,
15
15
  TemplatesListSchema,
16
- WorkspaceGraphSchema,
17
16
  WorkspaceSyncSchema,
18
17
  } from "../schemas/workspace/workspace-repos.js";
18
+ import { PackageModulesParamSchema, PackageModulesSchema, WorkspaceGraphSchema } from "../schemas/workspace/workspace-graph.js";
19
19
  import { WorkspaceSearchQuerySchema, WorkspaceSearchResultSchema } from "../schemas/workspace/workspace-search.js";
20
20
  import { WorkspaceInstallResultSchema, WorkspaceInstallSchema, WorkspaceSetupSchema } from "../schemas/workspace/workspace-setup.js";
21
21
  import {
@@ -341,6 +341,17 @@ export const workspaceContract = {
341
341
  })
342
342
  .input(RepoAppsParamSchema)
343
343
  .output(WorkspaceGraphSchema),
344
+ // One package's modules grouped into units, and the value imports between them, placed in its declared layers.
345
+ packageModules: procedure
346
+ .route({
347
+ method: "GET",
348
+ path: "/workspace/repos/{repo}/layers",
349
+ summary: "How one package's modules import each other",
350
+ description:
351
+ "One workspace package's source directories as units, the value imports between them, and the layers its layers.json declares, which is what a view of its insides draws.",
352
+ })
353
+ .input(PackageModulesParamSchema)
354
+ .output(PackageModulesSchema),
344
355
  // Every repo's package dirs in one call, since a review spans repos and per-repo would fan out per open.
345
356
  modules: procedure
346
357
  .route({
package/src/index.ts CHANGED
@@ -28,6 +28,7 @@ import { loopsContract } from "./contracts/loops.contract.js";
28
28
  import { pagesContract } from "./contracts/pages.contract.js";
29
29
  import { panelsContract } from "./contracts/panels.contract.js";
30
30
  import { portsContract } from "./contracts/ports.contract.js";
31
+ import { runsContract } from "./contracts/runs.contract.js";
31
32
  import { publicContract } from "./contracts/public.contract.js";
32
33
  import { netdiskContract } from "./contracts/netdisk.contract.js";
33
34
  import { providersContract } from "./contracts/providers.contract.js";
@@ -96,6 +97,7 @@ export * from "./protocol/vitals.js";
96
97
  export { loopsContract } from "./contracts/loops.contract.js";
97
98
  export { panelsContract } from "./contracts/panels.contract.js";
98
99
  export { portsContract } from "./contracts/ports.contract.js";
100
+ export { runsContract } from "./contracts/runs.contract.js";
99
101
  export { pagesContract, PageAppCallResultSchema, PageAppCallSchema, PageErrorsResultSchema, PageErrorsSchema } from "./contracts/pages.contract.js";
100
102
  export { publicContract } from "./contracts/public.contract.js";
101
103
  export { providersContract, type RunnableProviders, RunnableProvidersSchema } from "./contracts/providers.contract.js";
@@ -135,6 +137,7 @@ export * from "./events/child-run.js";
135
137
  export * from "./policy/turned-away.js";
136
138
  export * from "./text/mentions.js";
137
139
  export * from "./text/pages.js";
140
+ export * from "./text/transcript-page.js";
138
141
  export * from "./protocol/sse.js";
139
142
  export * from "./protocol/routes.js";
140
143
  export { type ControlReach, defaultFloor, type RouteAccess, type RouteMeta, routeAccess } from "./protocol/route-meta.js";
@@ -200,6 +203,7 @@ export * from "./policy/fence-paths.js";
200
203
  export * from "./policy/persona-home.js";
201
204
  export * from "./schemas/agents.js";
202
205
  export * from "./schemas/speaker.js";
206
+ export * from "./schemas/speech.js";
203
207
  export * from "./schemas/approvals.js";
204
208
  export * from "./schemas/automations.js";
205
209
  export * from "./schemas/agents-list.js";
@@ -212,6 +216,7 @@ export * from "./schemas/context-trim.js";
212
216
  export * from "./schemas/devices.js";
213
217
  export * from "./schemas/project-delivery.js";
214
218
  export * from "./schemas/device-artifacts.js";
219
+ export * from "./schemas/run-targets.js";
215
220
  export * from "./schemas/engines.js";
216
221
  export * from "./schemas/environment.js";
217
222
  export * from "./schemas/exit.js";
@@ -265,6 +270,7 @@ export * from "./schemas/offload.js";
265
270
  export * from "./schemas/webext.js";
266
271
  export * from "./schemas/phone.js";
267
272
  export * from "./schemas/workflows.js";
273
+ export * from "./schemas/workspace/workspace-graph.js";
268
274
  export * from "./schemas/workspace/workspace-repos.js";
269
275
  export * from "./schemas/workspace/workspace-search.js";
270
276
  export * from "./schemas/workspace/workspace-setup.js";
@@ -277,7 +283,6 @@ export * from "./text/title.js";
277
283
  export * from "./state/versions.js";
278
284
  export * from "./text/model-answer.js";
279
285
  export * from "./text/shell-regions.js";
280
- export * from "./text/whisper.js";
281
286
  export * from "./text/workflow-faults.js";
282
287
  export * from "./text/emoji.js";
283
288
  export * from "./time/zone.js";
@@ -316,6 +321,7 @@ export const sandboxContract = {
316
321
  pages: pagesContract,
317
322
  panels: panelsContract,
318
323
  ports: portsContract,
324
+ runs: runsContract,
319
325
  public: publicContract,
320
326
  providers: providersContract,
321
327
  push: pushContract,
@@ -121,7 +121,7 @@ export const OPENCODE: AgentCapabilities = {
121
121
  steering: false,
122
122
  permissions: "plan",
123
123
  questions: false,
124
- // Added at runtime under the conversation's own names, each prompt showing its session only its own (opencode-mcp.ts).
124
+ // Added at runtime under the conversation's own names, each session's own rules showing it only its own (opencode-mcp.ts).
125
125
  mcp: "http",
126
126
  execution: ["shell"],
127
127
  effort: false,
@@ -131,7 +131,7 @@ export const OPENCODE: AgentCapabilities = {
131
131
  terminals: false,
132
132
  recovery: false,
133
133
  warm: false,
134
- // `system` on the prompt body, per message; adds to OpenCode's own prompt, with no seam to replace it.
134
+ // The session's own instruction entry, set per message; adds to OpenCode's own prompt, with no seam to replace it.
135
135
  instructions: "append",
136
136
  skillDiscovery: "prompt",
137
137
  // OpenCode raises its permission asks for the commands its config names; the daemon answers from the rulebook, a
@@ -92,6 +92,14 @@ export const MODEL_ROLES = [
92
92
  trigger: "pressed",
93
93
  icon: "wrench",
94
94
  },
95
+ {
96
+ id: "refactor-run",
97
+ label: "Refactors",
98
+ blurb: "The agent started from a hotspot or key module on a repository's Health tab.",
99
+ kind: "run",
100
+ trigger: "pressed",
101
+ icon: "hammer",
102
+ },
95
103
  {
96
104
  id: "documentation-run",
97
105
  label: "Documentation runs",
@@ -132,6 +132,22 @@ test("spent is the contract's one line: 99.5% still has room, 100% is spent unti
132
132
  expect(headroomState(CLAUDE, { id: "claude-opus-4-6" })).toEqual({ kind: "spent" });
133
133
  });
134
134
 
135
+ // Anthropic reports whole percents: an account read at 100 kept serving turns for hours, so that reading is the line,
136
+ // not proof. Only the provider's refusal makes such a pool spent.
137
+ test("a pool read at 100 in whole percents is at the line with no room, until a refusal proves it empty", () => {
138
+ const week = window({ kind: "seven_day", utilization: 100, resetsAt: 900, rounded: true });
139
+ const atLine = usage(window({ kind: "five_hour", utilization: 75, rounded: true }), week);
140
+ expect(headroomState(atLine)).toEqual({ kind: "ready", room: 0 });
141
+ expect(headroomState(atLine, { id: "claude-opus-5-5" })).toEqual({ kind: "ready", room: 0 });
142
+ // A precise 100 is still spent.
143
+ expect(headroomState(usage(window({ kind: "seven_day", utilization: 100, resetsAt: 900 })))).toEqual({ kind: "spent", reopensAt: 900 });
144
+ // The provider refused it: spent until the pool reopens, and a reading still at the line does not answer that.
145
+ const facts = { account: "a", usage: { ...atLine, measuredAt: 2_000 } };
146
+ const refused = refusal({ kind: "limit", account: "a" });
147
+ expect(refusalVerdict(refused, [facts])).toBe("standing");
148
+ expect(serviceStates([facts], refused).get("a")).toEqual({ kind: "spent", reopensAt: 900 });
149
+ });
150
+
135
151
  test("with no model named, an account is spent only when no model can run on it", () => {
136
152
  // A full Opus slice leaves every other model the room of the fullest pool still open.
137
153
  expect(headroomState(CLAUDE)).toEqual({ kind: "ready", room: 70 });
@@ -243,3 +259,14 @@ test("an unnamed turn takes the most room, then an unmeasured account, then a sp
243
259
  expect(roomiestAccount([entry("seatless", blocked), entry("unread", { kind: "unknown" })])).toBeUndefined();
244
260
  expect(roomiestAccount([entry("low", { kind: "ready", room: 5 }), entry("high", { kind: "ready", room: 60 })])?.id).toBe("high");
245
261
  });
262
+
263
+ test("an account at the line is tried after an unmeasured one and before a spent one, and never a move's destination", () => {
264
+ const entry = (id: string, state: AccountState) => ({ id, state });
265
+ const atLine: AccountState = { kind: "ready", room: 0 };
266
+ expect(preferredAccount([entry("line", atLine), entry("low", { kind: "ready", room: 2 })])?.id).toBe("low");
267
+ expect(preferredAccount([entry("line", atLine), entry("unread", { kind: "unknown" })])?.id).toBe("unread");
268
+ expect(preferredAccount([entry("spent", { kind: "spent" }), entry("line", atLine)])?.id).toBe("line");
269
+ // A move lands only on proven room: two accounts at the line would otherwise hand a refused turn back and forth.
270
+ expect(roomiestAccount([entry("line", atLine), entry("spent", { kind: "spent" })])).toBeUndefined();
271
+ expect(roomiestAccount([entry("line", atLine), entry("low", { kind: "ready", room: 2 })])?.id).toBe("low");
272
+ });
@@ -8,6 +8,18 @@ import type { AccountState, AccountUsage, ProviderRefusal, UsageWindow, WindowGa
8
8
  // anything short of it still has room.
9
9
  export const SPENT_UTILIZATION = 100;
10
10
 
11
+ // Whether a reading proves its pool empty. A pool read at the line by a provider that rounds to whole percents
12
+ // (`rounded`) does not: Anthropic reports 99.5% as 100 and keeps serving turns on it, so treating that as spent strands
13
+ // the last of an allowance (and turns a plain send into one scheduled for the reset). Such a pool is at the line, with
14
+ // no room to rank by; only the provider's refusal (pinnedFull) proves it empty.
15
+ export const provenFull = (window: UsageWindow): boolean => window.utilization >= SPENT_UTILIZATION && window.rounded !== true;
16
+
17
+ // A pool a standing limit refusal shows full: at the line, and proven so whatever the provider's rounding.
18
+ export const pinnedFull = (window: UsageWindow): UsageWindow => {
19
+ const { rounded: _rounded, ...pool } = window;
20
+ return { ...pool, utilization: Math.max(window.utilization, SPENT_UTILIZATION) };
21
+ };
22
+
11
23
  export interface ModelRef {
12
24
  // Wire model id.
13
25
  readonly id: string;
@@ -210,15 +222,15 @@ export const headroomState = (usage: AccountUsage | undefined, model?: ModelRef)
210
222
  if (windows.length === 0) {
211
223
  return { kind: "unknown" };
212
224
  }
213
- const full = windows.filter((window) => window.utilization >= SPENT_UTILIZATION);
214
- const open = windows.filter((window) => window.utilization < SPENT_UTILIZATION);
225
+ const full = windows.filter(provenFull);
226
+ const open = windows.filter((window) => !provenFull(window));
215
227
  if (model !== undefined || full.some((window) => window.gates === "all") || open.length === 0) {
216
228
  const holding = model !== undefined ? full : full.some((window) => window.gates === "all") ? full.filter((window) => window.gates === "all") : full;
217
229
  if (holding.length > 0) {
218
230
  return spent(reopening(holding, model === undefined && !holding.some((window) => window.gates === "all")));
219
231
  }
220
232
  }
221
- return { kind: "ready", room: SPENT_UTILIZATION - Math.max(...open.map((window) => window.utilization)) };
233
+ return { kind: "ready", room: Math.max(0, SPENT_UTILIZATION - Math.max(...open.map((window) => window.utilization))) };
222
234
  };
223
235
 
224
236
  // A standing limit refusal reads its pool as full, since a polled reading freezes the moment a pool actually empties: the
@@ -231,7 +243,7 @@ const refusedPool = (usage: AccountUsage | undefined, refusal: ProviderRefusal,
231
243
  const sameModel = refusal.model === undefined || model === undefined || refusal.model === model.id;
232
244
  return sameModel ? spent(refusal.resetsAt) : headroomState(usage, model);
233
245
  }
234
- const pinned = usage.windows.map((window) => (window === binding ? { ...window, utilization: Math.max(window.utilization, SPENT_UTILIZATION) } : window));
246
+ const pinned = usage.windows.map((window) => (window === binding ? pinnedFull(window) : window));
235
247
  const state = headroomState({ ...usage, windows: pinned }, model);
236
248
  // The provider's own "try again at" is when it reopens, over whatever the polled pool last said.
237
249
  return state.kind === "spent" && refusal.resetsAt !== undefined ? spent(refusal.resetsAt) : state;
@@ -287,19 +299,35 @@ export const serviceStates = (
287
299
  );
288
300
  };
289
301
 
290
- // Worst last: proven room, no reading either way, known spent, and nothing a turn can run on.
291
- const PREFERENCE: Record<AccountState["kind"], number> = { ready: 0, unknown: 1, spent: 2, blocked: 3 };
302
+ // Worst last: proven room, no reading either way, a pool at the line (it may hold a sliver, or nothing), known spent,
303
+ // and nothing a turn can run on.
304
+ const preference = (state: AccountState): number => {
305
+ switch (state.kind) {
306
+ case "ready":
307
+ return state.room > 0 ? 0 : 2;
308
+ case "unknown":
309
+ return 1;
310
+ case "spent":
311
+ return 3;
312
+ case "blocked":
313
+ return 4;
314
+ }
315
+ };
292
316
 
293
- /** The account an unnamed turn runs on: ready by most room, then unmeasured, then spent, blocked only when that is all there is. Ties keep the caller's order. */
317
+ /** The account an unnamed turn runs on: ready by most room, then unmeasured, then at the line, then spent, blocked only when that is all there is. Ties keep the caller's order. */
294
318
  export const preferredAccount = <T extends { readonly state: AccountState }>(entries: readonly T[]): T | undefined =>
295
319
  entries.reduce<T | undefined>((best, next) => {
296
320
  if (best === undefined) {
297
321
  return next;
298
322
  }
299
- const tier = PREFERENCE[next.state.kind] - PREFERENCE[best.state.kind];
323
+ const tier = preference(next.state) - preference(best.state);
300
324
  return tier < 0 || (tier === 0 && next.state.kind === "ready" && best.state.kind === "ready" && next.state.room > best.state.room) ? next : best;
301
325
  }, undefined);
302
326
 
303
- /** The roomiest account with proven room, or none: what a move off a refused account may land on. Ties keep the caller's order. */
327
+ /**
328
+ * The roomiest account with proven room, or none: what a move off a refused account may land on. Ties keep the caller's
329
+ * order. An account at the line is left out: a move there could be refused in turn, and with one refusal remembered per
330
+ * provider, two such accounts would hand the turn back and forth.
331
+ */
304
332
  export const roomiestAccount = <T extends { readonly state: AccountState }>(entries: readonly T[]): T | undefined =>
305
- preferredAccount(entries.filter((entry) => entry.state.kind === "ready"));
333
+ preferredAccount(entries.filter((entry) => entry.state.kind === "ready" && entry.state.room > 0));
@@ -9,7 +9,9 @@ import { rawRouteUrl } from "../raw/raw-routes.js";
9
9
  // The path of one of the four listener routes for a provider, built from RAW_ROUTES so the gateway and the daemon
10
10
  // cannot spell it two ways.
11
11
  export const listenerRouteUrl = (route: "state" | "dispatch" | "failure" | "status", provider: string): string =>
12
- route === "state" ? rawRouteUrl("GET /listeners/{provider}/state", { provider }) : rawRouteUrl(`POST /listeners/{provider}/${route}`, { provider });
12
+ route === "state"
13
+ ? rawRouteUrl("GET /listeners/{provider}/state", { provider })
14
+ : rawRouteUrl(`POST /listeners/{provider}/${route}`, { provider });
13
15
 
14
16
  // The reconcile feed GET /listeners/<provider>/state answers: the enabled automations listening to this provider and
15
17
  // the connectors the asking extension contributes, each with its whole config, secrets included, since the gateway
@@ -86,10 +88,9 @@ export const ListenerPairingSchema = z.object({
86
88
  export type ListenerPairing = z.infer<typeof ListenerPairingSchema>;
87
89
 
88
90
  // Push-based status: a gateway POSTs its connection/voice snapshot to /listeners/<provider>/status, since the daemon
89
- // holds no provider connection to probe itself. Extends ActivityStatus with whisper's voice-pending flag and each
90
- // unpaired capability's pairing ceremony by id.
91
+ // holds no provider connection to probe itself. Extends ActivityStatus with each unpaired capability's pairing ceremony
92
+ // by id.
91
93
  export const ListenerStatusSchema = ActivityStatusSchema.extend({
92
- whisperReady: z.boolean().optional(),
93
94
  pairing: z.record(z.string(), ListenerPairingSchema).optional(),
94
95
  });
95
96
  export type ListenerStatus = z.infer<typeof ListenerStatusSchema>;
@@ -16,7 +16,11 @@ export const RAW_ROUTES = {
16
16
  "GET /diff/raw": { lane: "bulk" },
17
17
  // Arming the dictation model is a read any tier makes; dictating is writing a message, the collaborator's grant.
18
18
  "GET /speech/status": { guest: true },
19
+ "POST /speech/prepare": { guest: true },
19
20
  "POST /speech/transcribe": { floor: "collaborator", guest: true },
21
+ // The live phrase stream (schemas/speech.ts): a WebSocket, so the query ticket, floored at the collaborator in the
22
+ // handler like the transcribe it streams.
23
+ "GET /speech/stream": { auth: "door" },
20
24
  // The bytes behind the workspace reads; each applies the caller's fence itself.
21
25
  "GET /workspace/raw": { guest: true, lane: "bulk" },
22
26
  // No door, unlike media: the page fetches a thumbnail itself, so the request carries the header.
@@ -127,6 +131,14 @@ export const RAW_ROUTES = {
127
131
  // The `devices push` CLI: a program the agent built, carried to one of the owner's computers in chunks; held to the
128
132
  // devices the calling conversation's turn mounts, and refused by the machine itself unless "Run programs" is on.
129
133
  "POST /devices/{name}/artifacts": { agent: true, control: "never" },
134
+ // The rest of the `devices` command, held to the same rule (hosts/device-door.routes.ts): the device's app_start for a
135
+ // shell, and its loopback ports reached from the sandbox (hosts/device-tunnels.ts).
136
+ "POST /devices/{name}/apps": { agent: true, control: "never" },
137
+ "POST /devices/{name}/tunnels": { agent: true, control: "never" },
138
+ "DELETE /devices/{name}/tunnels/{port}": { agent: true, control: "never" },
139
+ "GET /devices/tunnels": { agent: true },
140
+ // Where a device dials back with a tunnel's one-time ticket; the ticket is the whole credential, checked by the route.
141
+ "GET /system/hosts/tunnel": { auth: "door", control: "never" },
130
142
  // An extension gateway's realtime-listener control: /state hands back its connectors' stored credentials, so only the
131
143
  // extension token of the extension declaring that `listener.provider` reaches them, checked in the handler too.
132
144
  "GET /listeners/{provider}/state": { floor: "maintainer", panel: false, control: "never" },
@@ -3,7 +3,7 @@ import { LandingDeliverySchema } from "./project-delivery.js";
3
3
  import { z } from "zod";
4
4
  import { AgentHarnessSchema, AgentOriginSchema, AgentProviderSchema, ConversationQueueSchema, ForkedFromSchema } from "./agent.js";
5
5
  import { LoopStateSchema } from "./loops.js";
6
- import { LimitPolicySchema, RetryPolicySchema, TurnBreakPolicySchema, TurnBreakSchema } from "./turn-break.js";
6
+ import { LimitPolicySchema, RetryPolicySchema, RoomPolicySchema, TurnBreakPolicySchema, TurnBreakSchema } from "./turn-break.js";
7
7
  import { KeepWarmSchema } from "./keep-warm.js";
8
8
  import { AgentNeedSchema } from "./needs.js";
9
9
  import { EMOJI_MAX_LENGTH, isSingleEmoji } from "../text/emoji.js";
@@ -362,6 +362,7 @@ export const AgentSummarySchema = z.object({
362
362
  limitPolicy: LimitPolicySchema.optional(),
363
363
  outagePolicy: RetryPolicySchema.optional(),
364
364
  stopPolicy: RetryPolicySchema.optional(),
365
+ memoryPolicy: RoomPolicySchema.optional(),
365
366
  // A collaborator's ask to land (collaborators can't merge themselves); cleared by whichever merge or discard
366
367
  // answers it.
367
368
  landRequested: z
@@ -907,9 +908,11 @@ export const AgentUnsentSchema = z.object({
907
908
  // verbs is how the surfaces drifted apart in the first place.
908
909
  export const AgentBreakPolicySchema = z.object({
909
910
  id: z.string().min(1).describe("Which conversation."),
910
- ending: TurnBreakSchema.describe("Which wall this answers for: a spent usage limit, a provider outage, or a turn that stopped short."),
911
+ ending: TurnBreakSchema.describe(
912
+ "Which wall this answers for: a spent usage limit, a provider outage, a turn that stopped short, or a message the sandbox held because it was short of memory.",
913
+ ),
911
914
  policy: TurnBreakPolicySchema.nullable().describe(
912
- "What happens next for that ending. `wait` holds the turn for a press; `retry` re-runs it on a bounded ladder (outage, stop); `resend` sends it again at the published reset and `move` also tries another account with room (limit only). An answer the ending does not allow is refused. Null clears the override and goes back to following the sandbox-wide policy, so a conversation does not sit holding a frozen copy of a default it has quietly stopped following.",
915
+ "What happens next for that ending. `wait` holds the turn for a press; `retry` re-runs it on a bounded ladder (outage, stop); `resend` sends it again at the published reset (limit) or once memory frees up (memory), and `move` also tries another account with room (limit only). An answer the ending does not allow is refused. Null clears the override and goes back to following the sandbox-wide policy, so a conversation does not sit holding a frozen copy of a default it has quietly stopped following.",
913
916
  ),
914
917
  });
915
918
  export const AgentFileDiffQuerySchema = z.object({
@@ -76,3 +76,24 @@ export const PushedArtifactSchema = z.object({
76
76
  reused: z.boolean(),
77
77
  });
78
78
  export type PushedArtifact = z.infer<typeof PushedArtifactSchema>;
79
+
80
+ // A DEVICE'S LOOPBACK PORT, REACHED FROM THE SANDBOX (`devices reach`, hosts/device-tunnels.ts). The sandbox listens on
81
+ // its own 127.0.0.1:<localPort>; each connection asks the device to dial its own 127.0.0.1:<devicePort> and open a
82
+ // WebSocket back to /system/hosts/tunnel carrying the ticket below, and the two are pumped together.
83
+ export const DialLoopbackSchema = z
84
+ .object({
85
+ ticket: z.string().regex(/^[0-9a-f]{48}$/),
86
+ port: z.int().min(1).max(65535),
87
+ })
88
+ .strict();
89
+ export type DialLoopback = z.infer<typeof DialLoopbackSchema>;
90
+
91
+ export const DeviceTunnelSchema = z.object({
92
+ device: z.string(),
93
+ devicePort: z.int().min(1).max(65535),
94
+ localPort: z.int().min(1).max(65535),
95
+ // What a program in the sandbox opens to reach the device's port.
96
+ url: z.string(),
97
+ openedAt: z.string(),
98
+ });
99
+ export type DeviceTunnel = z.infer<typeof DeviceTunnelSchema>;
@@ -45,12 +45,37 @@ export type RuntimeInstall = z.infer<typeof RuntimeInstallSchema>;
45
45
  // (`environment propose`) clears its tool's entries.
46
46
  export const SettledDraftSchema = z.object({ tool: z.string(), hash: z.string(), at: z.number() });
47
47
  export type SettledDraft = z.infer<typeof SettledDraftSchema>;
48
+ // One overlay the daemon composed, by the hash a rebuild is stamped with, and the custom-section blocks it carried (each
49
+ // by name and the hash of its trimmed steps). Kept so "is this tool in the image" is answered against the overlay the
50
+ // running container was actually built from, which is rarely the newest one composed: every approval, capability change
51
+ // and daemon update composes again, and a rebuild builds whatever was newest when it started.
52
+ export const EnvironmentCompositionSchema = z.object({
53
+ hash: z.string(),
54
+ at: z.number(),
55
+ blocks: z.array(z.object({ name: z.string(), hash: z.string() })),
56
+ });
57
+ export type EnvironmentComposition = z.infer<typeof EnvironmentCompositionSchema>;
58
+ // A draft the daemon wrote on its own (a recurring runtime install, a cache-rule revision of an approved block), by the
59
+ // name its file carries and the hash of its steps. A draft that later vanishes with no answer on record was thrown away
60
+ // by hand (the Changes panel's discard, a git clean): that is read as a no, rather than writing it back on the next
61
+ // sweep. `install` is the ledger entry an auto-draft came from, which a no declines.
62
+ export const EnvironmentOfferSchema = z.object({
63
+ tool: z.string(),
64
+ hash: z.string(),
65
+ at: z.number(),
66
+ install: z.string().optional(),
67
+ });
68
+ export type EnvironmentOffer = z.infer<typeof EnvironmentOfferSchema>;
48
69
  export const RuntimeInstallsFileSchema = z.object({
49
70
  installs: z.array(RuntimeInstallSchema),
50
71
  // The last drift snapshot, persisted so a daemon restart does not blank the card until the next sweep.
51
72
  drift: EnvironmentDriftSchema.optional(),
52
73
  // Drafts already answered, newest last and capped; absent on a ledger written before removal existed.
53
74
  settled: z.array(SettledDraftSchema).optional(),
75
+ // Overlays composed, oldest first and capped; absent on a ledger written before they were kept.
76
+ compositions: z.array(EnvironmentCompositionSchema).optional(),
77
+ // Drafts the daemon wrote that nobody has answered yet; absent on a ledger written before they were kept.
78
+ offered: z.array(EnvironmentOfferSchema).optional(),
54
79
  });
55
80
  export type RuntimeInstallsFile = z.infer<typeof RuntimeInstallsFileSchema>;
56
81
  // Ledger entry as the Environment card shows it: recurrence joined with whether it's present in the live container
@@ -23,6 +23,12 @@ export const UsageWindowSchema = z.object({
23
23
  utilization: z.number(), // 0-100
24
24
  resetsAt: z.number().optional(),
25
25
  gates: WindowGatesSchema,
26
+ rounded: z
27
+ .boolean()
28
+ .optional()
29
+ .describe(
30
+ "The provider rounds this figure to a whole percent (Anthropic does), so a reading of 100 may be 99.5 and the pool can still serve. Such a pool reads as at the line, not spent: only the provider's refusal proves it empty.",
31
+ ),
26
32
  });
27
33
  export type UsageWindow = z.infer<typeof UsageWindowSchema>;
28
34
  // A failed re-read, whatever the cause: a 429 park, a 403 asking for verification, a 5xx, a timeout. Recorded as the fact
@@ -59,7 +65,9 @@ export const AccountStateSchema = z.discriminatedUnion("kind", [
59
65
  kind: z.literal("ready"),
60
66
  room: z
61
67
  .number()
62
- .describe("How much of the fullest pool that gates the turn is left, in percent (above 0, up to 100). Pickers take the most room."),
68
+ .describe(
69
+ "How much of the fullest pool that gates the turn is left, in percent (0 up to 100). Pickers take the most room. 0 is a rounded pool read at 100: at the line, tried after an unmeasured account, never a limit move's destination.",
70
+ ),
63
71
  }),
64
72
  z.object({
65
73
  kind: z.literal("spent"),