toolroll 0.9.72 → 0.9.74

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 (137) hide show
  1. package/CHANGELOG.md +108 -0
  2. package/THIRD_PARTY_NOTICES.md +40 -0
  3. package/dist/agent-fence.d.ts +5 -0
  4. package/dist/agent-fence.js +42 -7
  5. package/dist/agent-turns.js +5 -0
  6. package/dist/agents-live.d.ts +31 -0
  7. package/dist/agents-live.js +43 -0
  8. package/dist/browser/THIRD_PARTY_NOTICES.txt +40 -0
  9. package/dist/browser/workspace.css +2 -2
  10. package/dist/browser/workspace.js +31 -30
  11. package/dist/browser-workspace.d.ts +6 -0
  12. package/dist/builder.js +1 -1
  13. package/dist/chat-actions.js +13 -8
  14. package/dist/chat-continuity.js +10 -0
  15. package/dist/chat-decide.d.ts +47 -1
  16. package/dist/chat-decide.js +170 -4
  17. package/dist/chat-quiet.js +3 -3
  18. package/dist/check-report.js +2 -1
  19. package/dist/coding-provider.js +4 -2
  20. package/dist/commit-extract.d.ts +17 -0
  21. package/dist/commit-extract.js +82 -4
  22. package/dist/contracts/console-api.d.ts +3 -3
  23. package/dist/contracts/console-api.js +4 -4
  24. package/dist/contracts/release-terms.d.ts +7 -1
  25. package/dist/contracts/release-terms.js +6 -2
  26. package/dist/deploy-gate.js +5 -2
  27. package/dist/desktop-update.d.ts +1 -1
  28. package/dist/dispose.js +35 -2
  29. package/dist/exec.d.ts +1 -1
  30. package/dist/exec.js +20 -19
  31. package/dist/first-run.d.ts +17 -1
  32. package/dist/first-run.js +38 -0
  33. package/dist/flow-gallery-ui.js +4 -2
  34. package/dist/flow-gallery.d.ts +3 -0
  35. package/dist/flow-gallery.js +6 -2
  36. package/dist/flow-triggers.d.ts +8 -0
  37. package/dist/flow-triggers.js +15 -0
  38. package/dist/flows-cli.js +13 -3
  39. package/dist/guides.js +10 -1
  40. package/dist/invoke.d.ts +13 -0
  41. package/dist/invoke.js +21 -12
  42. package/dist/kits-ui.js +4 -2
  43. package/dist/kits.d.ts +6 -4
  44. package/dist/kits.js +9 -11
  45. package/dist/knowledge-ui.d.ts +4 -1
  46. package/dist/knowledge-ui.js +10 -5
  47. package/dist/lead-progress.d.ts +4 -1
  48. package/dist/lead-progress.js +3 -1
  49. package/dist/lead.js +4 -0
  50. package/dist/live-bus.d.ts +2 -0
  51. package/dist/live-bus.js +1 -0
  52. package/dist/live-children-read.d.ts +52 -0
  53. package/dist/live-children-read.js +333 -0
  54. package/dist/live-children.d.ts +73 -0
  55. package/dist/live-children.js +318 -0
  56. package/dist/live-detail.d.ts +4 -3
  57. package/dist/live-detail.js +9 -5
  58. package/dist/live-input.js +7 -8
  59. package/dist/live-main-read.d.ts +19 -0
  60. package/dist/live-main-read.js +91 -0
  61. package/dist/live-open.d.ts +28 -0
  62. package/dist/live-open.js +125 -0
  63. package/dist/live-records.d.ts +10 -1
  64. package/dist/live-records.js +16 -5
  65. package/dist/live.d.ts +54 -22
  66. package/dist/live.js +218 -69
  67. package/dist/mcp-connect.d.ts +10 -0
  68. package/dist/mcp-connect.js +2 -0
  69. package/dist/operate.js +3 -2
  70. package/dist/peek-cli.d.ts +10 -3
  71. package/dist/peek-cli.js +17 -8
  72. package/dist/planner.js +1 -1
  73. package/dist/project-tools.js +3 -1
  74. package/dist/protected-storage.d.ts +67 -0
  75. package/dist/protected-storage.js +398 -0
  76. package/dist/provider-limits.d.ts +3 -1
  77. package/dist/provider-limits.js +5 -2
  78. package/dist/provider.js +6 -12
  79. package/dist/release-adapters.d.ts +8 -1
  80. package/dist/release-adapters.js +84 -14
  81. package/dist/release-authority.d.ts +6 -0
  82. package/dist/release-authority.js +9 -0
  83. package/dist/release-card.d.ts +76 -0
  84. package/dist/release-card.js +252 -0
  85. package/dist/release-carry.d.ts +3 -0
  86. package/dist/release-carry.js +23 -4
  87. package/dist/release-cli.js +36 -3
  88. package/dist/release-git.d.ts +43 -0
  89. package/dist/release-git.js +106 -0
  90. package/dist/release.d.ts +11 -2
  91. package/dist/release.js +82 -6
  92. package/dist/repository-context-ui.js +2 -1
  93. package/dist/run-efficiency.d.ts +3 -3
  94. package/dist/run-efficiency.js +20 -8
  95. package/dist/scope.d.ts +7 -0
  96. package/dist/scope.js +22 -5
  97. package/dist/scout.d.ts +3 -0
  98. package/dist/scout.js +5 -3
  99. package/dist/serve.js +34 -23
  100. package/dist/server/agents-live-lead.d.ts +47 -0
  101. package/dist/server/agents-live-lead.js +116 -0
  102. package/dist/server/agents-live-room.d.ts +23 -0
  103. package/dist/server/agents-live-room.js +88 -0
  104. package/dist/server/chat.js +84 -30
  105. package/dist/server/chrome.js +2 -2
  106. package/dist/server/console.css +68 -40
  107. package/dist/server/flows.js +40 -10
  108. package/dist/server/live.d.ts +4 -0
  109. package/dist/server/live.js +42 -2
  110. package/dist/server/pages.js +81 -1
  111. package/dist/server/render-chat.d.ts +15 -1
  112. package/dist/server/render-chat.js +14 -7
  113. package/dist/server/render-pages.d.ts +8 -4
  114. package/dist/server/render-pages.js +55 -50
  115. package/dist/server/render-settings.js +1 -1
  116. package/dist/server/render-tasks.js +10 -3
  117. package/dist/server/route-table.js +5 -1
  118. package/dist/server/runtime.d.ts +1 -1
  119. package/dist/server/settings.js +29 -8
  120. package/dist/server/task-views.js +1 -1
  121. package/dist/server/tasks.js +59 -41
  122. package/dist/setup-guide.d.ts +2 -2
  123. package/dist/store.d.ts +34 -1
  124. package/dist/store.js +81 -2
  125. package/dist/stream-scope.d.ts +18 -0
  126. package/dist/stream-scope.js +28 -0
  127. package/dist/subagent-memory.d.ts +1 -0
  128. package/dist/subagent-memory.js +1 -1
  129. package/dist/subagent-tools.d.ts +22 -6
  130. package/dist/subagent-tools.js +44 -8
  131. package/dist/subagent-work.d.ts +2 -0
  132. package/dist/subagent-work.js +5 -2
  133. package/dist/subagents-ui.js +6 -4
  134. package/dist/subscription-chat.js +5 -3
  135. package/dist/telegram.d.ts +6 -1
  136. package/dist/telegram.js +58 -6
  137. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,113 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.74 — 2026-10-27
4
+
5
+ - **Agents → Live: every agent and subagent at a glance.** A read-only grid
6
+ of everything running, on desktop and phone: what each agent is doing
7
+ right now, how long it has run and its latest lines. Subagents get their
8
+ own tiles nested under their parent; they appear when they start and show
9
+ Done for 30 seconds before leaving. A quiet agent reads **Needs you** with
10
+ a thin amber outline. Click a tile's title to open it full screen (Escape
11
+ brings you back). Your own lead chats show as lead tiles. Nothing on the
12
+ page acts, and the grid holds still while you look at it. Terminals you
13
+ drive yourself stay off the grid; one line links to Live terminals. Only a
14
+ signed-in browser can read it: tokens are refused.
15
+ - **Visiting a page starts nothing.** Opening Chat, a task, a result or a
16
+ project shows the page with a ready composer; your first message starts
17
+ the conversation. A retry or double submit lands in the conversation it
18
+ started. Link previews and prefetches can't start one.
19
+ - **A cleaner console.** One status chip everywhere, framed panels on the
20
+ Board, Settings, Knowledge and Projects, and a road on every task (Plan →
21
+ You approve → Build → Checks → Ready) built only from what happened.
22
+ Attempts show as a timeline and warnings as plain notices. Amber now means
23
+ only "waits on you". Built from ReUI's free, MIT-licensed components
24
+ (credited in THIRD_PARTY_NOTICES.md).
25
+ - **The old task list is retired.** `/tasks` now opens Work, which gained a
26
+ state filter (`/work?state=`), templates (`/tasks/new?template=`) and keeps
27
+ your draft when a filing is refused.
28
+ - **Rerunning a failed release check keeps your approval.**
29
+ `toolroll task regate` queues the same approved commit again instead of
30
+ re-filing it, and `toolroll release` waits for the rerun and uses it. A
31
+ changed commit, scope or approval still refuses.
32
+ - Database: schema v136 (each run's "Show commands" answer is kept for the
33
+ live view).
34
+
35
+ ## 0.9.73 — 2026-10-25
36
+
37
+ - **Carry release approvals when checks aren't sandboxed.** A project whose
38
+ checks run outside the sandbox (like Toolroll's own release project) can
39
+ let later test-only commits carry a release approval:
40
+ `toolroll release unsandboxed-carry on --repo <path> --as <you> --token
41
+ <password>`. It is off by default; only a person turns it on, and it counts
42
+ only for releases approved while it is on. The approval sheet says plainly
43
+ that those later commits will run outside the sandbox without being shown
44
+ to you first. Every other carry rule stays: tests and docs only, the
45
+ 400-line, 20-file and 800-line-churn limits, an identical production build,
46
+ and main's open nightly failures. Turning it off, or checks that reach
47
+ further than at the approval, stops carrying.
48
+ - **`toolroll release` stops at the gate when the pull request's checks
49
+ fail.** Before it files the release task or asks for your approval, it
50
+ reads the required checks on the exact commit. One that failed or was
51
+ cancelled stops the release with the failing job names, so you aren't
52
+ asked to approve a commit that can't merge. Checks not started or still
53
+ running are named and waited for; if they don't finish within the gate's
54
+ limit (60 minutes, `--limit gate=<min>`), it stops there too. The ci step
55
+ still checks every one again before merging.
56
+ - **Chat apps, browsers and password managers are off limits to every
57
+ launch.** Builds, checks, flows, tool servers and coding sessions can't
58
+ read or write where Slack, Telegram, Discord, Teams, WhatsApp, 1Password,
59
+ Bitwarden, or the Chrome, Chromium, Brave, Edge, Arc, Firefox, Zen,
60
+ Vivaldi, Opera, Orion and Safari profiles keep their data, on macOS and
61
+ Linux, under both sandbox versions and in Codex's sandbox. This covers the
62
+ folders by name and by real path, links inside them, and a moved profile
63
+ or config folder named in the environment. No project setting reopens
64
+ them, and those apps' variables (SLACK_, TELEGRAM_, DISCORD_, TEAMS_, OP_,
65
+ BW_, BITWARDENCLI_) are removed from every launch; a tool server keeps the
66
+ secrets it names.
67
+ - **`toolroll release` runs Git safely on the release checkout.** The release
68
+ used to run plain Git there, so a setting planted in the checkout's Git
69
+ config (an include, a hook folder, an ssh command, a proxy, a URL rewrite
70
+ and the like) could run a command or send the gh token elsewhere. Now it
71
+ uses the repository and GitHub URL from Toolroll's own records, runs Git,
72
+ gh and ssh by absolute path with a fixed environment, pins HTTPS settings
73
+ on the command line, and allows only the plain settings Git writes itself.
74
+ Any other setting stops the release and names it. The checkout must be a
75
+ project Toolroll records, with a recorded remote.
76
+ - **Widening actions take your password.** Setting the permission default to
77
+ Full access, giving a subagent a tool, loosening a tool rule, accepting a
78
+ subagent's suggested looser rule, and adding a trigger that brings in work
79
+ from anyone (GitHub from anyone, mail from any sender, from the Triggers
80
+ panel, a gallery template or a starter kit) now ask for your password (a
81
+ signed-in session does under Passwordless). Narrowing needs none. A chat
82
+ card, import or template can no longer do any of these; it points to the
83
+ page that can.
84
+ - **Approving a release from chat shows the whole release first.** A tap
85
+ approves a deploy only through the release card: every signed term and
86
+ release term, one line each (it says so when later test-only commits
87
+ will run outside the sandbox), and the commits since the last release
88
+ Toolroll recorded as merged. Continue sends the terms (as a file where the
89
+ chat app allows), and Approve is bound to exactly what was shown;
90
+ anything changed in between refuses with the new card. It works only in
91
+ your own private chat (Telegram or a Slack direct message); a group,
92
+ someone else's chat, Discord and Teams are refused. Each such approval is
93
+ announced to you, naming the chat app.
94
+ - **Network, writes, check access and sandbox settings stay on the typed
95
+ password under Passwordless,** and a test now fails if one moves to the
96
+ ordinary signed-in step-up.
97
+ - **Launches don't wait on macOS's "access data from other apps" prompt.**
98
+ Toolroll looks inside other apps' containers only after a 3-second look in
99
+ a separate process shows macOS lets it, so a service run by launchd no
100
+ longer hangs every fenced launch. While macOS is still asking, the
101
+ containers stay denied by name and real path, and the links found by the
102
+ last full look stay denied; a look that fails for any other reason refuses
103
+ the launch.
104
+ - **The release check ends itself before its time limit,** stopping the
105
+ parts still running and keeping every log and its report, and a failed
106
+ part's log reaches the run's output as soon as it fails.
107
+ - **`toolroll release` rides out short GitHub network timeouts** while a
108
+ step waits, asking again until that step's own limit.
109
+ - No database change: the schema stays at v135.
110
+
3
111
  ## 0.9.72 — 2026-10-25
4
112
 
5
113
  - **One approval per release.** A person approves a release once. Later
@@ -321,6 +321,46 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
321
321
  SOFTWARE.
322
322
 
323
323
 
324
+ ## ReUI — MIT
325
+
326
+ Components in `src/browser/components/ui/` adapt free, MIT-licensed ReUI
327
+ primitives and examples (the Radix base) from:
328
+ https://github.com/keenthemes/reui/tree/067aa0d/registry-reui/bases/radix/reui
329
+
330
+ Source inspected 27 October 2026, revision `067aa0d`. Files copied by hand (the
331
+ shadcn CLI and ReUI's registry CSS variables are not used) and cut to Toolroll's
332
+ tokens:
333
+
334
+ - `badge.tsx` — the light badge and its status-dot example (`c-badge-22`): one
335
+ 20px chip size and a decorative dot on the existing tones, plus a "Needs you"
336
+ tone. The style variants, solid fills and focus/invert tones are omitted.
337
+ - `frame.tsx` — the well of panels: one geometry (a raised well, hairline,
338
+ concentric panel corners) with the style variants, spacing scale, stacking
339
+ modes and panel shadows omitted; `FramePanel` can render as its child.
340
+ - `alert.tsx` — cut to a neutral and a destructive tone with an icon column;
341
+ the info, warning, success and invert tones, the title/action slots and the
342
+ style variants are omitted.
343
+ - `timeline.tsx` — cut to a read-only vertical list (item, indicator on a
344
+ hairline rail, content, date); the active-step context, horizontal mode and
345
+ styled indicators are omitted.
346
+ - `stepper.tsx` — its read-only horizontal layout (indicator, separator,
347
+ title, `aria-current="step"`) is followed by the task page's road
348
+ (`src/browser/first-run.tsx` `Journey`); no stepper source is copied, and
349
+ the controlled state, keyboard navigation and triggers are omitted.
350
+
351
+ No ReUI Pro blocks, icons or templates are included.
352
+
353
+ MIT License
354
+
355
+ Copyright (c) 2025 Keenthemes Inc
356
+
357
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
358
+
359
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
360
+
361
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
362
+
363
+
324
364
  ## Integration logos: Simple Icons (CC0 1.0) and svgl (MIT)
325
365
 
326
366
  The integration logos in `src/brand-icons.ts` are generated by
@@ -29,6 +29,8 @@ export declare function agentFence(options: {
29
29
  boundary?: TaskBoundary | null;
30
30
  binding?: WorktreeBinding | null;
31
31
  runFolders?: RunFolders;
32
+ /** Protected storage as this launch resolved it (protected-storage.ts); resolved here when not given. */
33
+ protectedStorage?: readonly string[];
32
34
  }): string[];
33
35
  /** The task's boundary: `shell` joins the OS fence; `fileTools` only Claude's file-tool deny rules; `readOnly` and
34
36
  * `lookup` are other tasks' git metadata and checkout roots (see FenceGrants). */
@@ -103,6 +105,9 @@ export type FenceGrants = {
103
105
  signIns?: SignInGrants;
104
106
  /** Never written, wherever the launch writes (homeWriteDenies): what runs for the operator next or signs them in. */
105
107
  writeDenies?: string[];
108
+ /** Protected storage (protected-storage.ts): denied to read and write by every backend, and no grant equal to or
109
+ * inside one survives (a sandbox that lets the more specific path win can't reopen it). */
110
+ protectedStorage?: string[];
106
111
  };
107
112
  export { isWritesMode, WRITES_DEFAULT, type WritesMode } from "./writes-mode.js";
108
113
  /**
@@ -34,6 +34,7 @@ import { readMonitoring } from "./monitoring-settings.js";
34
34
  import { existingFolders } from "./names.js";
35
35
  import { DEPS_FOLDER, linkedKey, readyCopy, sharedCopies } from "./shared-deps.js";
36
36
  import { SEATBELT_PROBE, sandboxRuntimeProblem, srtSpawn } from "./sandbox-runtime.js";
37
+ import { protectedStorageDenies, protectedStorageNames } from "./protected-storage.js";
37
38
  import { getDefaultWritePaths } from "@anthropic-ai/sandbox-runtime";
38
39
  /** Names that are secrets wherever the database lives (used when its folder is shared with other things). */
39
40
  const SENSITIVE_NAME = /(^up-login\.txt$|token|secret|login|password|credential|vapid|keys?\b|\.pem$|\.key$|^backups$|^evidence$|^remote$|^sign-in\.json$|^monitoring\.json$)/i;
@@ -188,11 +189,18 @@ export function agentFence(options) {
188
189
  for (const path of options.boundary?.protectedShell ?? [])
189
190
  fence.add(real(path));
190
191
  const protectedPaths = new Set([...fence].flatMap(path => path === runFolder ? [path, real(path)] : [real(path)]));
192
+ // Protected storage (chat apps, browser profiles, password managers): every launch, every policy, never exemptible,
193
+ // kept by the name it was resolved under as well as where it leads.
194
+ const storage = options.protectedStorage ?? protectedStorageDenies(home);
195
+ for (const path of storage) {
196
+ protectedPaths.add(path);
197
+ protectedPaths.add(real(path));
198
+ }
191
199
  for (const path of options.boundary?.shell ?? [])
192
200
  fence.add(path);
193
201
  const binding = options.binding;
194
202
  const needed = binding === undefined || binding === null ? [] : [binding.gitdir, real(join(binding.main, ".git"))];
195
- return [...new Set([...fence].flatMap(path => path === runFolder ? [path, real(path)] : [real(path)]))].filter(path => {
203
+ return [...new Set([...[...fence].flatMap(path => path === runFolder ? [path, real(path)] : [real(path)]), ...storage.flatMap(path => [path, real(path)])])].filter(path => {
196
204
  if (protectedPaths.has(path) || !needed.some(target => target === path || inside(target, path)))
197
205
  return true;
198
206
  console.warn(`Dropped build fence entry covering this checkout's own Git data: ${path}`);
@@ -237,10 +245,12 @@ export function taskBoundary(options) {
237
245
  const present = (names) => names.map(name => join(home, name)).filter(path => existsSync(path)).map(real);
238
246
  // Policy v2 (docs/plans/sandbox-parity.md §1.2): the home folder, the repositories beside this one and the project's
239
247
  // primary checkout are read as Anthropic's sandbox runtime reads them; only writes to the primary checkout stay denied.
248
+ // Protected storage by name, under both policies (protected-storage.ts): never part of the v1-only home configuration.
249
+ const storage = protectedStorageNames(home);
240
250
  if ((options.policy ?? 1) === 2)
241
- return v2Boundary(worktree, main, gitdir);
251
+ return v2Boundary(worktree, main, gitdir, storage);
242
252
  const shell = new Set(present(HOME_CONFIGURATION.filter(name => !own.has(name))));
243
- const protectedShell = [...shell];
253
+ const protectedShell = [...shell, ...storage];
244
254
  const repository = (path) => existsSync(join(path, ".git"));
245
255
  for (const parent of new Set([dirname(worktree), ...(main === null ? [] : [dirname(main)])])) {
246
256
  // Home contains toolchain clones such as .nvm and .oh-my-zsh. The
@@ -290,7 +300,7 @@ export function taskBoundary(options) {
290
300
  /** A v2 boundary: nothing of the home folder, the repositories beside this one or the primary checkout is denied to
291
301
  * read; the primary checkout (all but its `.git`, which this worktree's Git needs) is never written; other tasks' Git
292
302
  * metadata is read, never written, as under v1. */
293
- function v2Boundary(worktree, main, gitdir) {
303
+ function v2Boundary(worktree, main, gitdir, storage) {
294
304
  const noWrite = new Set();
295
305
  if (main !== null && main !== worktree) {
296
306
  let entries = [];
@@ -309,7 +319,7 @@ function v2Boundary(worktree, main, gitdir) {
309
319
  const { readOnly, lookup } = siblingGitData(main, gitdir);
310
320
  for (const path of noWrite)
311
321
  readOnly.add(path);
312
- return { protectedShell: [], shell: [], fileTools: [], readOnly: [...readOnly].sort(), lookup: [...lookup].sort(), homeNames: [] };
322
+ return { protectedShell: [...storage], shell: [], fileTools: [], readOnly: [...readOnly].sort(), lookup: [...lookup].sort(), homeNames: [] };
313
323
  }
314
324
  /**
315
325
  * Git needs this worktree's metadata and the shared objects/refs, but must not prune or rewrite the metadata of another
@@ -765,6 +775,18 @@ function codexFilesystem(fence, fullAccess, grants) {
765
775
  if (basename(path) === INSTANCE_DIR)
766
776
  access.set(path, "deny");
767
777
  }
778
+ // Protected storage: denied by name and real path, and nothing granted inside it (Codex lets the more specific path
779
+ // win, so a grant there would reopen it).
780
+ const storage = grants.protectedStorage ?? [];
781
+ if (storage.length > 0) {
782
+ for (const [path, mode] of [...access])
783
+ if (mode !== "deny" && storage.some(one => path === one || inside(path, one)))
784
+ access.delete(path);
785
+ for (const path of storage) {
786
+ access.set(path, "deny");
787
+ access.set(real(path), "deny");
788
+ }
789
+ }
768
790
  return access;
769
791
  }
770
792
  /**
@@ -971,6 +993,16 @@ export function srtTermsOf(fence, grants = NO_GRANTS) {
971
993
  denyWrite.add(sealed);
972
994
  }
973
995
  }
996
+ // Protected storage last: denied to read and write by name and real path, and no read grant inside it survives.
997
+ const storage = grants.protectedStorage ?? [];
998
+ const isProtected = (path) => storage.some(one => path === one || inside(path, one) || real(path) === one || inside(real(path), one));
999
+ for (const path of storage) {
1000
+ denyRead.add(path);
1001
+ denyWrite.add(path);
1002
+ }
1003
+ for (const path of [...allowRead])
1004
+ if (isProtected(path))
1005
+ allowRead.delete(path);
974
1006
  const readConfig = { denyOnly: [...denyRead].sort(), allowWithinDeny: [...allowRead].sort() };
975
1007
  const writes = grants.writes;
976
1008
  // A sign-ins check also writes Codex's session and log folders and its own Claude transcripts (never inside a
@@ -983,7 +1015,7 @@ export function srtTermsOf(fence, grants = NO_GRANTS) {
983
1015
  const network = grants.network;
984
1016
  return {
985
1017
  readConfig,
986
- writeConfig: { allowOnly: [...new Set(allowOnly)].sort(), denyWithinAllow: [...denyWrite].sort() },
1018
+ writeConfig: { allowOnly: [...new Set(allowOnly)].filter(path => path === "/" || !isProtected(path.replace(/\*$/, ""))).sort(), denyWithinAllow: [...denyWrite].sort() },
987
1019
  allowMachLookup: [...grants.machLookup ?? [], ...grants.signIns?.mach ?? []],
988
1020
  ...(grants.browser === true ? { browser: true } : {}),
989
1021
  ...(grants.keychain === true ? { keychain: true } : {}),
@@ -1078,7 +1110,9 @@ export function launchSandboxOf(profile) {
1078
1110
  export function fenceFor(kind, ctx) {
1079
1111
  const home = ctx.home === undefined ? {} : { home: ctx.home };
1080
1112
  // The project's read denies join the fence by name, there or not (the runtime denies a path that isn't there yet).
1081
- const fence = agentFence({ databaseFile: ctx.databaseFile, worktree: ctx.worktree, ...home, boundary: ctx.boundary, binding: ctx.binding, runFolders: ctx.runFolders, extra: [...ctx.extra ?? [], ...ctx.readDeny ?? []] });
1113
+ // Protected storage, resolved once for this launch, from the plane's own environment (protected-storage.ts).
1114
+ const storage = protectedStorageDenies(ctx.home ?? homedir());
1115
+ const fence = agentFence({ databaseFile: ctx.databaseFile, worktree: ctx.worktree, ...home, boundary: ctx.boundary, binding: ctx.binding, runFolders: ctx.runFolders, extra: [...ctx.extra ?? [], ...ctx.readDeny ?? []], protectedStorage: storage });
1082
1116
  const found = fenceGrants({ databaseFile: ctx.databaseFile, worktree: ctx.worktree, boundary: ctx.boundary, runFolders: ctx.runFolders, linked: ctx.linked ?? null, binding: ctx.binding, ...home, poolRoots: ctx.poolRoots });
1083
1117
  // Toolroll's own install, the sandbox runtime's package and the Node running them, never written by anything it
1084
1118
  // launches: rewriting the code that fences a build would switch the fence off.
@@ -1095,6 +1129,7 @@ export function fenceFor(kind, ctx) {
1095
1129
  ...(kind === "build" ? { keychain: true } : {}),
1096
1130
  // Under a write allowlist these are out of reach already; where the launch writes anywhere, they are denied by name.
1097
1131
  writeDenies: homeWriteDenies(ctx.home ?? homedir(), ctx.binding, ctx.provider ?? null),
1132
+ protectedStorage: storage,
1098
1133
  ...(made.pool === undefined ? {} : { pool: { ...made.pool, ...(ctx.policy === 2 ? { readable: true } : {}),
1099
1134
  // Flows' scratch folders in the pool (and any the caller names) stay denied whole, at every policy version.
1100
1135
  sealed: sealedScratch(ctx.poolRoots, ctx.scratchRoots ?? []) } }),
@@ -1,6 +1,11 @@
1
+ import { topLevelEvent } from "./stream-scope.js";
1
2
  /** What one stream event says about the turn; null when it says nothing. */
2
3
  export function turnSignalOf(event) {
3
4
  const type = typeof event["type"] === "string" ? event["type"] : "";
5
+ // A subagent's events are not the parent's turn: its result never marks the parent idle or clears its open tools,
6
+ // and its init or words never mark it busy (stream-scope.ts).
7
+ if (!topLevelEvent(event))
8
+ return null;
4
9
  switch (type) {
5
10
  // Claude stream-json: the session starts, the assistant speaks or calls a tool, a tool answers; then one result.
6
11
  case "system": return event["subtype"] === "init" ? "busy" : null;
@@ -0,0 +1,31 @@
1
+ import { type AgentsLiveRun, type Store } from "./store.js";
2
+ /** An ended agent stays on the grid this long, then leaves (it remains on the List). */
3
+ export declare const LIVE_ENDED_KEEP_MS: number;
4
+ export type AgentLiveState = "working" | "needs-you" | "done" | "failed" | "stopped" | "parked";
5
+ export type AgentLiveTile = {
6
+ runId: number;
7
+ role: AgentsLiveRun["role"];
8
+ taskId: string;
9
+ title: string;
10
+ project: string | null;
11
+ agent: string;
12
+ model: string | null;
13
+ startedAt: string;
14
+ endedAt: string | null;
15
+ state: AgentLiveState;
16
+ /** Why it needs you: it has been quiet (the board's Stuck rule) or it asked a question. */
17
+ why: "quiet" | "question" | null;
18
+ /** Since when it has been quiet, when that is why. */
19
+ quietSince: string | null;
20
+ /** Its output is on this computer (another runner's can't be read here). */
21
+ local: boolean;
22
+ /** A repair's build: the tile it nests under. */
23
+ parentRun: number | null;
24
+ /** The task's own page, for a task this console keeps (an outside tracker's task has none here). */
25
+ taskHref: string | null;
26
+ };
27
+ /** The grid's tiles for one viewer: `canSee` is the caller's proof (ceiling and account access) for each run. */
28
+ export declare function agentsLiveOf(store: Store, now: Date, viewer: {
29
+ canSee: (run: AgentsLiveRun) => boolean;
30
+ localRunner: string | undefined;
31
+ }): AgentLiveTile[];
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The Agents view's Live grid (docs/plans/agents-view.md §1, §2.5): which agents are running now (every role) and
3
+ * which ended in the last ten minutes, with the facts a tile shows. Store facts only — it verifies nothing, grants
4
+ * nothing, and reads no live file; visibility is proved by the caller's `canSee` for each run.
5
+ */
6
+ import { attemptKeyOf, agentNowForRun, openAgentQuestions } from "./agent-board.js";
7
+ import { agentsLiveRuns } from "./store.js";
8
+ /** An ended agent stays on the grid this long, then leaves (it remains on the List). */
9
+ export const LIVE_ENDED_KEEP_MS = 10 * 60_000;
10
+ const stateOf = (outcome) => outcome === null ? "working" : outcome === "built" || outcome === "no-change" ? "done" : outcome === "parked" ? "parked" : outcome === "interrupted" ? "stopped" : "failed";
11
+ /** The grid's tiles for one viewer: `canSee` is the caller's proof (ceiling and account access) for each run. */
12
+ export function agentsLiveOf(store, now, viewer) {
13
+ const tiles = [];
14
+ for (const run of agentsLiveRuns(store, new Date(now.getTime() - LIVE_ENDED_KEEP_MS))) {
15
+ if (!viewer.canSee(run))
16
+ continue;
17
+ let state = stateOf(run.outcome);
18
+ let why = null, quietSince = null;
19
+ if (state === "working") {
20
+ try {
21
+ const key = attemptKeyOf(store, run.id);
22
+ const facts = key === null ? null : agentNowForRun(store, run.id, key);
23
+ if (run.role === "builder" && openAgentQuestions(store, run.taskRef).length > 0)
24
+ why = "question";
25
+ else if (facts !== null && facts.stuckAt !== null) {
26
+ why = "quiet";
27
+ quietSince = facts.stuckAt;
28
+ }
29
+ }
30
+ catch { /* display state: no word yet */ }
31
+ if (why !== null)
32
+ state = "needs-you";
33
+ }
34
+ tiles.push({
35
+ runId: run.id, role: run.role, taskId: run.taskId, title: run.title, project: run.repo, agent: run.provider, model: run.model,
36
+ startedAt: run.startedAt, endedAt: run.finishedAt, state, why, quietSince,
37
+ local: viewer.localRunner !== undefined && run.runner === viewer.localRunner,
38
+ parentRun: run.role === "repair" ? run.parentRun : null,
39
+ taskHref: run.builtIn ? `/t/${encodeURIComponent(run.taskId)}` : null,
40
+ });
41
+ }
42
+ return tiles;
43
+ }
@@ -321,6 +321,46 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
321
321
  SOFTWARE.
322
322
 
323
323
 
324
+ ## ReUI — MIT
325
+
326
+ Components in `src/browser/components/ui/` adapt free, MIT-licensed ReUI
327
+ primitives and examples (the Radix base) from:
328
+ https://github.com/keenthemes/reui/tree/067aa0d/registry-reui/bases/radix/reui
329
+
330
+ Source inspected 27 October 2026, revision `067aa0d`. Files copied by hand (the
331
+ shadcn CLI and ReUI's registry CSS variables are not used) and cut to Toolroll's
332
+ tokens:
333
+
334
+ - `badge.tsx` — the light badge and its status-dot example (`c-badge-22`): one
335
+ 20px chip size and a decorative dot on the existing tones, plus a "Needs you"
336
+ tone. The style variants, solid fills and focus/invert tones are omitted.
337
+ - `frame.tsx` — the well of panels: one geometry (a raised well, hairline,
338
+ concentric panel corners) with the style variants, spacing scale, stacking
339
+ modes and panel shadows omitted; `FramePanel` can render as its child.
340
+ - `alert.tsx` — cut to a neutral and a destructive tone with an icon column;
341
+ the info, warning, success and invert tones, the title/action slots and the
342
+ style variants are omitted.
343
+ - `timeline.tsx` — cut to a read-only vertical list (item, indicator on a
344
+ hairline rail, content, date); the active-step context, horizontal mode and
345
+ styled indicators are omitted.
346
+ - `stepper.tsx` — its read-only horizontal layout (indicator, separator,
347
+ title, `aria-current="step"`) is followed by the task page's road
348
+ (`src/browser/first-run.tsx` `Journey`); no stepper source is copied, and
349
+ the controlled state, keyboard navigation and triggers are omitted.
350
+
351
+ No ReUI Pro blocks, icons or templates are included.
352
+
353
+ MIT License
354
+
355
+ Copyright (c) 2025 Keenthemes Inc
356
+
357
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
358
+
359
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
360
+
361
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
362
+
363
+
324
364
  ## Integration logos: Simple Icons (CC0 1.0) and svgl (MIT)
325
365
 
326
366
  The integration logos in `src/brand-icons.ts` are generated by