toolroll 0.9.60 → 0.9.62

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 (128) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/agent-board.d.ts +208 -0
  3. package/dist/agent-board.js +454 -0
  4. package/dist/agent-fence.d.ts +19 -0
  5. package/dist/agent-fence.js +100 -8
  6. package/dist/agent-pulse.d.ts +43 -0
  7. package/dist/agent-pulse.js +214 -0
  8. package/dist/agent-turns.d.ts +52 -0
  9. package/dist/agent-turns.js +137 -0
  10. package/dist/agents-board.d.ts +101 -0
  11. package/dist/agents-board.js +225 -0
  12. package/dist/assignment-adapters.d.ts +1 -0
  13. package/dist/assignment-status.d.ts +1 -1
  14. package/dist/assignment.d.ts +4 -0
  15. package/dist/assignment.js +8 -6
  16. package/dist/browser/workspace.css +1 -1
  17. package/dist/browser/workspace.js +29 -25
  18. package/dist/browser-crew.js +2 -0
  19. package/dist/browser-workspace.d.ts +79 -1
  20. package/dist/browser-workspace.js +1 -0
  21. package/dist/builder.d.ts +8 -0
  22. package/dist/builder.js +113 -13
  23. package/dist/chat-channel.js +9 -0
  24. package/dist/chat-core.d.ts +6 -0
  25. package/dist/chat-core.js +8 -0
  26. package/dist/chat-delivery.js +22 -0
  27. package/dist/chat-messages.js +8 -1
  28. package/dist/chat-quiet.js +1 -0
  29. package/dist/chat-voice.d.ts +2 -0
  30. package/dist/chat-voice.js +2 -0
  31. package/dist/checkout-cleanup.d.ts +3 -0
  32. package/dist/checkout-cleanup.js +11 -2
  33. package/dist/cli.js +28 -11
  34. package/dist/contracts/console-api.d.ts +3 -1
  35. package/dist/contracts/console-api.js +5 -1
  36. package/dist/contracts/lead-tools.d.ts +16 -2
  37. package/dist/contracts/lead-tools.js +5 -1
  38. package/dist/desktop-update.d.ts +1 -1
  39. package/dist/evidence-pack.js +1 -1
  40. package/dist/evidence.d.ts +6 -0
  41. package/dist/evidence.js +14 -3
  42. package/dist/exec.d.ts +32 -0
  43. package/dist/exec.js +229 -10
  44. package/dist/invoke.js +110 -18
  45. package/dist/landed-cleanup.d.ts +170 -0
  46. package/dist/landed-cleanup.js +814 -0
  47. package/dist/lead-cli.js +22 -19
  48. package/dist/lead-contract.d.ts +1 -1
  49. package/dist/lead-contract.js +4 -4
  50. package/dist/lead-doors.js +45 -1
  51. package/dist/lead-tools.js +40 -7
  52. package/dist/live-input.d.ts +68 -0
  53. package/dist/live-input.js +254 -0
  54. package/dist/live.d.ts +1 -1
  55. package/dist/live.js +4 -2
  56. package/dist/maintenance.d.ts +2 -0
  57. package/dist/maintenance.js +5 -2
  58. package/dist/operate-remote-arguments.js +5 -1
  59. package/dist/operate-remote.js +1 -1
  60. package/dist/operate.d.ts +1 -1
  61. package/dist/operate.js +249 -26
  62. package/dist/orphan-sweep.d.ts +2 -0
  63. package/dist/orphan-sweep.js +5 -3
  64. package/dist/project-admission.d.ts +10 -6
  65. package/dist/project-admission.js +33 -11
  66. package/dist/project-delete-ui.d.ts +12 -0
  67. package/dist/project-delete-ui.js +32 -2
  68. package/dist/project-tools.d.ts +7 -11
  69. package/dist/project-tools.js +13 -37
  70. package/dist/provider.d.ts +65 -8
  71. package/dist/provider.js +80 -21
  72. package/dist/remote-main.d.ts +57 -0
  73. package/dist/remote-main.js +179 -0
  74. package/dist/result-completion.d.ts +7 -0
  75. package/dist/result-completion.js +9 -0
  76. package/dist/runner.d.ts +4 -2
  77. package/dist/runner.js +3 -3
  78. package/dist/serve.js +19 -1
  79. package/dist/server/live.d.ts +3 -0
  80. package/dist/server/live.js +35 -2
  81. package/dist/server/pages.js +59 -35
  82. package/dist/server/render-chat.js +12 -0
  83. package/dist/server/render-tasks.d.ts +15 -2
  84. package/dist/server/render-tasks.js +30 -7
  85. package/dist/server/route-table.js +4 -1
  86. package/dist/server/settings.js +5 -3
  87. package/dist/server/task-views.js +11 -0
  88. package/dist/server/tasks.js +63 -0
  89. package/dist/shared-deps.d.ts +9 -4
  90. package/dist/shared-deps.js +89 -13
  91. package/dist/staged-runtimes.d.ts +16 -4
  92. package/dist/staged-runtimes.js +160 -38
  93. package/dist/steer-live.d.ts +19 -0
  94. package/dist/steer-live.js +93 -0
  95. package/dist/steer-status.d.ts +8 -2
  96. package/dist/steer-status.js +19 -5
  97. package/dist/storage-sweep-child.d.ts +1 -0
  98. package/dist/storage-sweep-child.js +76 -0
  99. package/dist/storage-sweep-process.d.ts +48 -0
  100. package/dist/storage-sweep-process.js +204 -0
  101. package/dist/storage-sweep-schedule.d.ts +30 -0
  102. package/dist/storage-sweep-schedule.js +164 -0
  103. package/dist/storage-sweep.d.ts +31 -6
  104. package/dist/storage-sweep.js +165 -39
  105. package/dist/storage-ui.d.ts +4 -0
  106. package/dist/storage-ui.js +22 -7
  107. package/dist/storage.d.ts +6 -0
  108. package/dist/storage.js +35 -0
  109. package/dist/store.d.ts +200 -3
  110. package/dist/store.js +443 -17
  111. package/dist/surface.js +8 -4
  112. package/dist/sweep-quarantine.d.ts +28 -0
  113. package/dist/sweep-quarantine.js +153 -0
  114. package/dist/task-base.d.ts +35 -0
  115. package/dist/task-base.js +51 -0
  116. package/dist/task-status.d.ts +2 -0
  117. package/dist/task-status.js +5 -1
  118. package/dist/test-temp.d.ts +6 -3
  119. package/dist/test-temp.js +15 -9
  120. package/dist/toolroll-update.js +4 -2
  121. package/dist/work-index.d.ts +1 -0
  122. package/dist/work-index.js +32 -12
  123. package/dist/work-summary.d.ts +2 -2
  124. package/dist/workspace-revision.js +3 -1
  125. package/dist/worktree.d.ts +57 -3
  126. package/dist/worktree.js +201 -23
  127. package/package.json +1 -1
  128. package/scripts/provider-canary.mjs +235 -147
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.62 — 2026-10-12
4
+
5
+ - **Release checks in minutes.** The upgrade path runs on a sample, CI's
6
+ passing run on the exact commit is reused, and the first failure stops it.
7
+ - **Storage sweep waits for quiet.** It runs when nothing is building, and
8
+ Settings → Storage says when it last ran and why it waits.
9
+ - **Landing proof you can trust.** A task completes on its own only for a
10
+ merged pull request or the project's recorded remote main, never a local
11
+ branch a build could move. Projects added before this need it once: you
12
+ (the owner) or an instance operator's lead runs
13
+ `toolroll repos add <project> --remote <url>`. Until then, their tasks wait
14
+ for you.
15
+ - **Cleanup follow-ups.** Only branches Toolroll created are removed, counted
16
+ from when it made them; scout branches stay, and duplicate teardowns merge.
17
+ - **Fresh base.** A task with no commits starts from current main; the task
18
+ page shows its base.
19
+
20
+ ## 0.9.61 — 2026-10-12
21
+
22
+ - **Agents board.** One place to check on running agents: Needs you,
23
+ Working, Stuck, Done and Idle, live on desktop and phone. Agents can ask a
24
+ question mid-run, and you reply from the board.
25
+ - **Steer in seconds.** A note reaches a running Claude build within seconds.
26
+ Pause, Resume, and Pause and steer sit beside Stop.
27
+ - **Builds run on their own setup.** Your personal Claude hooks, plugins, MCP
28
+ servers and memory no longer load into builds.
29
+ - **The sweep can't stop builds.** Storage cleanup runs in its own process
30
+ with a time limit, and never removes what a build is using.
31
+ - **Finished work cleans up.** Tasks whose work has landed complete on their
32
+ own, and their checkouts and branches are removed.
33
+ - **Release checks test everything since the last release.**
34
+
3
35
  ## 0.9.60 — 2026-10-11
4
36
 
5
37
  - **Steer a running build.** A note now reaches the agent while it works, and
@@ -0,0 +1,208 @@
1
+ import type { Store } from "./store.js";
2
+ export type AgentTurn = "busy" | "idle" | "crashed";
3
+ export declare const AGENT_TURNS: readonly AgentTurn[];
4
+ export type AgentSaid = "working" | "blocked" | "done";
5
+ /** A busy run whose transcript and activity are both this old is Stuck. */
6
+ export declare const STUCK_AFTER_MS: number;
7
+ /** With a tool call still open (a long test run is silent), it is Stuck only after this long. */
8
+ export declare const TOOL_STUCK_AFTER_MS: number;
9
+ /** A page's watch on one run lapses this long after its last keep-alive (the stream's keep-alive is every 15 s). */
10
+ export declare const VIEW_LEASE_MS = 20000;
11
+ export declare const QUESTION_KEY: RegExp;
12
+ export declare const QUESTION_MAX = 300;
13
+ export declare const NOTE_MAX = 200;
14
+ export declare const ANSWER_MAX = 1500;
15
+ /** An agent is untrusted: at most this many of its questions are open at once per run… */
16
+ export declare const QUESTIONS_OPEN_MAX = 3;
17
+ /** …and at most this many are ever opened per attempt. A needs-decision past either opens nothing and pages no one. */
18
+ export declare const QUESTIONS_PER_ATTEMPT = 10;
19
+ /** At most one question page per run in this long; questions asked meanwhile wait for the next page. */
20
+ export declare const QUESTION_PAGE_EVERY_MS: number;
21
+ /** The board's one note on a run whose agent asked past the caps (set once). */
22
+ export declare const QUESTIONS_OVERFLOW_NOTE = "The agent asked more questions than the board takes; see its transcript.";
23
+ /** Notification kinds held while the person watches the run, and re-checked when the watch lapses. */
24
+ export declare const WATCHED_KINDS: ReadonlySet<string>;
25
+ export declare const AGENT_BOARD_SCHEMA = "\nCREATE TABLE IF NOT EXISTS agent_turn (\n id INTEGER PRIMARY KEY,\n run INTEGER NOT NULL REFERENCES run(id) ON DELETE CASCADE,\n attempt_key TEXT NOT NULL,\n state TEXT NOT NULL CHECK (state IN ('busy','idle','crashed')),\n at TEXT NOT NULL\n);\nCREATE INDEX IF NOT EXISTS agent_turn_run ON agent_turn (run, id);\nCREATE TABLE IF NOT EXISTS agent_now (\n task_ref INTEGER PRIMARY KEY REFERENCES task_ref(id) ON DELETE CASCADE,\n run INTEGER NOT NULL REFERENCES run(id) ON DELETE CASCADE,\n attempt_key TEXT NOT NULL,\n state TEXT NOT NULL CHECK (state IN ('busy','idle','crashed')),\n state_at TEXT NOT NULL,\n transcript_bytes INTEGER NOT NULL DEFAULT 0,\n transcript_at TEXT NOT NULL,\n stuck_at TEXT,\n said TEXT CHECK (said IN ('working','blocked','done')),\n said_note TEXT,\n said_at TEXT,\n status_offset INTEGER NOT NULL DEFAULT 0,\n diff_files INTEGER,\n diff_at TEXT\n);\nCREATE TABLE IF NOT EXISTS agent_question (\n id INTEGER PRIMARY KEY,\n run INTEGER NOT NULL REFERENCES run(id) ON DELETE CASCADE,\n task_ref INTEGER NOT NULL REFERENCES task_ref(id) ON DELETE CASCADE,\n attempt_key TEXT NOT NULL,\n key TEXT NOT NULL,\n question TEXT NOT NULL,\n asked_at TEXT NOT NULL,\n closed_at TEXT,\n closed_how TEXT CHECK (closed_how IN ('answered','ended')),\n answer_note INTEGER REFERENCES task_steer(id),\n answered_by TEXT,\n UNIQUE (run, key)\n);\nCREATE INDEX IF NOT EXISTS agent_question_open ON agent_question (task_ref) WHERE closed_at IS NULL;\nCREATE TABLE IF NOT EXISTS agent_seen (\n account TEXT NOT NULL,\n run INTEGER NOT NULL REFERENCES run(id) ON DELETE CASCADE,\n seen_at TEXT NOT NULL,\n PRIMARY KEY (account, run)\n);\nCREATE TABLE IF NOT EXISTS agent_view (\n view TEXT PRIMARY KEY,\n account TEXT NOT NULL,\n run INTEGER NOT NULL,\n expires_at TEXT NOT NULL\n);\nCREATE INDEX IF NOT EXISTS agent_view_run ON agent_view (run, account);\nCREATE INDEX IF NOT EXISTS agent_board_open ON run (task_ref, id) WHERE role = 'builder' AND outcome IS NULL;\nCREATE INDEX IF NOT EXISTS agent_board_recent ON run (finished_at, task_ref, id) WHERE role = 'builder' AND outcome IS NOT NULL;\nCREATE INDEX IF NOT EXISTS agent_board_repair ON run (parent_run, id) WHERE role = 'repair';\n";
26
+ /** Added to agent_now after the table first shipped (store.ts addColumn): the oldest open tool call's kind and start. */
27
+ export declare const AGENT_NOW_COLUMNS: readonly [string, string][];
28
+ /** Unpredictable per attempt: the run row is the only place it is written down. */
29
+ export declare function mintAttemptKey(): string;
30
+ /** A run's attempt key; null for a run admitted before the board existed (or no such run). */
31
+ export declare function attemptKeyOf(store: Store, run: number): string | null;
32
+ /** The task's newest build attempt: a repair mends its parent and never stands in for it. */
33
+ export declare const NEWEST_BUILDER: (task: string) => string;
34
+ /** The build attempt a run belongs to on the board: a repair's parent, otherwise itself. */
35
+ export declare function boardRunOf(store: Store, run: number): number;
36
+ export type TurnFact = {
37
+ run: number;
38
+ attemptKey: string;
39
+ state: AgentTurn;
40
+ at: Date;
41
+ };
42
+ /**
43
+ * Record one turn change. Always appended to the run's own history (under its
44
+ * key); the task's current snapshot moves only when this run is the task's
45
+ * newest and is not already newer-owned. Returns whether the snapshot moved.
46
+ */
47
+ export declare function recordAgentTurn(store: Store, fact: TurnFact): boolean;
48
+ /** One run's turn history, oldest first. */
49
+ export declare function agentTurnHistory(store: Store, run: number): {
50
+ attemptKey: string;
51
+ state: AgentTurn;
52
+ at: string;
53
+ }[];
54
+ export type AgentNow = {
55
+ taskRef: number;
56
+ run: number;
57
+ attemptKey: string;
58
+ state: AgentTurn;
59
+ stateAt: string;
60
+ transcriptBytes: number;
61
+ transcriptAt: string;
62
+ stuckAt: string | null;
63
+ said: AgentSaid | null;
64
+ saidNote: string | null;
65
+ saidAt: string | null;
66
+ statusOffset: number;
67
+ diffFiles: number | null;
68
+ diffAt: string | null;
69
+ /** The oldest tool call still open (a command, or any other tool), and when it started; null when none is. */
70
+ toolKind: ToolKind | null;
71
+ toolAt: string | null;
72
+ /** QUESTIONS_OVERFLOW_NOTE once the agent asked past the caps; null otherwise. */
73
+ questionNote: string | null;
74
+ /** The last question page: when it went, and the newest question it covered. */
75
+ pagedAt: string | null;
76
+ pagedUpto: number;
77
+ };
78
+ export type ToolKind = "command" | "tool";
79
+ /** The task's current snapshot, whichever run owns it. */
80
+ export declare function agentNowForTask(store: Store, taskRef: number): AgentNow | null;
81
+ /** The snapshot this attempt owns, or null when another (newer) attempt does or none was recorded. */
82
+ export declare function agentNowForRun(store: Store, run: number, attemptKey: string): AgentNow | null;
83
+ /** The pulse saw the live transcript's size: new bytes are news, and news is not Stuck. */
84
+ export declare function noteTranscript(store: Store, run: number, attemptKey: string, bytes: number, at: Date): boolean;
85
+ /** The changed-file count the pulse observed (null: it could not look). */
86
+ export declare function noteDiff(store: Store, run: number, attemptKey: string, files: number | null, at: Date): boolean;
87
+ /** The stream opened or closed a tool call: the oldest still open, or null. The stream moved, so it clears Stuck. */
88
+ export declare function noteOpenTool(store: Store, run: number, attemptKey: string, open: {
89
+ kind: ToolKind;
90
+ at: Date;
91
+ } | null, at: Date): boolean;
92
+ /** When the agent last showed a sign of life: its turn, transcript, recorded activity or the tool it opened. */
93
+ export declare function quietSince(now: Pick<AgentNow, "stateAt" | "transcriptAt"> & {
94
+ toolAt?: string | null;
95
+ }, activityAt: string | null): number;
96
+ /**
97
+ * Stuck, from saved facts and the clock alone (no model, no tokens): busy, and
98
+ * neither the transcript, the recorded activity nor the turn itself has moved
99
+ * for STUCK_AFTER_MS — or for TOOL_STUCK_AFTER_MS while a tool call is open.
100
+ */
101
+ export declare function stuckSince(now: Pick<AgentNow, "state" | "stateAt" | "transcriptAt"> & {
102
+ toolAt?: string | null;
103
+ }, activityAt: string | null, clock: Date): string | null;
104
+ /** The pulse persists the Stuck crossing (and its clearing), so open boards hear it through /live. */
105
+ export declare function markStuck(store: Store, run: number, attemptKey: string, clock: Date): boolean;
106
+ export type StatusLine = {
107
+ status: "working" | "blocked" | "done";
108
+ note: string | null;
109
+ } | {
110
+ status: "needs-decision";
111
+ key: string;
112
+ question: string;
113
+ };
114
+ export type StatusIngest = {
115
+ lines: StatusLine[];
116
+ nextOffset: number;
117
+ };
118
+ /**
119
+ * The agent's status lines since the saved offset, for the attempt that owns
120
+ * the snapshot. The offset only moves forward; a needs-decision opens its key
121
+ * once (a key already asked this run is never reopened), within the caps:
122
+ * QUESTIONS_OPEN_MAX open at once and QUESTIONS_PER_ATTEMPT in all. One past
123
+ * either opens nothing and pages no one; the run gets QUESTIONS_OVERFLOW_NOTE,
124
+ * once. New questions page the people the task reaches (pageQuestions).
125
+ */
126
+ export declare function ingestStatus(store: Store, run: number, attemptKey: string, read: StatusIngest, at: Date): {
127
+ asked: string[];
128
+ moved: boolean;
129
+ };
130
+ /**
131
+ * Page the run's open questions no page has covered yet, at most once per
132
+ * QUESTION_PAGE_EVERY_MS. A question asked outside that window, alone, pages
133
+ * at once with its own words; questions asked inside it wait, and the next
134
+ * page after the window says how many are waiting. The pulse calls this every
135
+ * beat, so the waiting ones go out when the window ends.
136
+ */
137
+ export declare function pageQuestions(store: Store, run: number, attemptKey: string, at: Date): boolean;
138
+ export declare const questionsWaiting: (count: number) => string;
139
+ export type AgentQuestion = {
140
+ id: number;
141
+ run: number;
142
+ taskRef: number;
143
+ key: string;
144
+ question: string;
145
+ askedAt: string;
146
+ };
147
+ /** A task's open questions, oldest first: only its newest run's, and only while that run is open. */
148
+ export declare function openAgentQuestions(store: Store, taskRef: number): AgentQuestion[];
149
+ export declare function agentQuestion(store: Store, id: number): (AgentQuestion & {
150
+ closedAt: string | null;
151
+ closedHow: string | null;
152
+ answerNote: number | null;
153
+ }) | null;
154
+ /** A run ended: every question it still had open is closed unanswered (its answer would reach no one). */
155
+ export declare function closeRunQuestions(store: Store, run: number, at: Date): number;
156
+ export type ReplyRefusal = "no-question" | "closed" | "stale" | "invalid-answer" | "not-saved";
157
+ export type ReplyResult = {
158
+ ok: true;
159
+ note: number;
160
+ key: string;
161
+ } | {
162
+ ok: false;
163
+ reason: ReplyRefusal;
164
+ problem?: string;
165
+ };
166
+ /** The steering note a reply becomes: the agent reads it in its steering folder, under its own key. */
167
+ export declare function replyNoteText(key: string, answer: string): string;
168
+ /**
169
+ * Reply to one open question, atomically: the question is still open, its run
170
+ * is still the task's open newest attempt under the same key, the answer saves
171
+ * as an ordinary verified steering note (the mailbox pulse delivers it), and
172
+ * exactly that key closes. Anything stale changes nothing.
173
+ */
174
+ export declare function answerAgentQuestion(store: Store, reply: {
175
+ question: number;
176
+ answer: string;
177
+ author: string;
178
+ at: Date;
179
+ file: (taskId: string, note: string) => {
180
+ ok: true;
181
+ id: number;
182
+ } | {
183
+ ok: false;
184
+ reason: string;
185
+ problem?: string;
186
+ };
187
+ }): ReplyResult;
188
+ /** This person opened a finished run (on the board or its result page): it leaves their unread Done. */
189
+ export declare function markAgentSeen(store: Store, account: string, run: number, at: Date): boolean;
190
+ /** A page is watching a run: hold (or renew) its short lease. */
191
+ export declare function holdAgentView(store: Store, view: string, account: string, run: number, at: Date): void;
192
+ export declare function releaseAgentView(store: Store, view: string, at: Date): void;
193
+ /** Until when this person has this exact run open (its repairs count as the run); null when they do not. */
194
+ export declare function watchingAgentUntil(store: Store, account: string, run: number, at: Date): string | null;
195
+ /** Someone has this exact run open right now. */
196
+ export declare function watchingAgent(store: Store, account: string, run: number, at: Date): boolean;
197
+ /**
198
+ * A watched ping, decided when its delivery is claimed (every chat, phone and browser asks here, after the
199
+ * permanent rules in store.pingAllowed): held until the watch lapses while this person has the run open; then
200
+ * skipped when they have already seen the finished run (an answered question is resolved, so never claimed);
201
+ * otherwise sent. Anything that is not a watched kind about a run is sent.
202
+ */
203
+ export declare function watchedPing(store: Store, row: {
204
+ kind: string;
205
+ run: number | null;
206
+ }, account: string, now: Date): {
207
+ hold: string;
208
+ } | "skip" | "send";