toolroll 0.9.59 → 0.9.61
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.
- package/CHANGELOG.md +26 -0
- package/README.md +8 -6
- package/dist/agent-board.d.ts +208 -0
- package/dist/agent-board.js +454 -0
- package/dist/agent-fence.d.ts +19 -0
- package/dist/agent-fence.js +100 -8
- package/dist/agent-pulse.d.ts +43 -0
- package/dist/agent-pulse.js +214 -0
- package/dist/agent-turns.d.ts +52 -0
- package/dist/agent-turns.js +137 -0
- package/dist/agents-board.d.ts +101 -0
- package/dist/agents-board.js +225 -0
- package/dist/assignment-adapters.d.ts +2 -1
- package/dist/assignment-adapters.js +1 -1
- package/dist/assignment-delivery.d.ts +16 -0
- package/dist/assignment-delivery.js +54 -28
- package/dist/assignment-status.d.ts +14 -5
- package/dist/assignment-status.js +70 -39
- package/dist/assignment.d.ts +10 -2
- package/dist/assignment.js +21 -14
- package/dist/bin.js +11 -2
- package/dist/brief-fence.d.ts +9 -0
- package/dist/brief-fence.js +22 -0
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +29 -25
- package/dist/browser-crew.js +2 -0
- package/dist/browser-workspace.d.ts +79 -1
- package/dist/browser-workspace.js +1 -0
- package/dist/builder.d.ts +8 -0
- package/dist/builder.js +178 -29
- package/dist/chat-channel.js +10 -1
- package/dist/chat-core.d.ts +6 -0
- package/dist/chat-core.js +8 -0
- package/dist/chat-delivery.js +22 -0
- package/dist/chat-messages.js +8 -1
- package/dist/chat-quiet.js +1 -0
- package/dist/chat-voice.d.ts +2 -0
- package/dist/chat-voice.js +2 -0
- package/dist/checkout-cleanup.d.ts +3 -0
- package/dist/checkout-cleanup.js +11 -2
- package/dist/cli-fast.d.ts +8 -0
- package/dist/cli-fast.js +18 -0
- package/dist/cli-help.d.ts +5 -0
- package/dist/cli-help.js +91 -0
- package/dist/cli.d.ts +6 -5
- package/dist/cli.js +17 -96
- package/dist/contracts/console-api.d.ts +3 -1
- package/dist/contracts/console-api.js +5 -1
- package/dist/contracts/lead-tools.d.ts +16 -2
- package/dist/contracts/lead-tools.js +5 -1
- package/dist/design/tokens.css +234 -0
- package/dist/desktop-update.d.ts +1 -1
- package/dist/discord.js +2 -1
- package/dist/evidence-pack.js +1 -1
- package/dist/evidence.d.ts +28 -0
- package/dist/evidence.js +87 -17
- package/dist/exec.d.ts +32 -0
- package/dist/exec.js +229 -10
- package/dist/flow-actions.js +2 -1
- package/dist/guides.js +14 -11
- package/dist/invoke.js +110 -18
- package/dist/landed-cleanup.d.ts +157 -0
- package/dist/landed-cleanup.js +707 -0
- package/dist/lead-cli.js +22 -19
- package/dist/lead-contract.js +1 -1
- package/dist/lead-doors.js +49 -2
- package/dist/lead-follow.d.ts +2 -0
- package/dist/lead-follow.js +138 -88
- package/dist/lead-tools.js +48 -8
- package/dist/live-input.d.ts +68 -0
- package/dist/live-input.js +254 -0
- package/dist/live.d.ts +1 -1
- package/dist/live.js +4 -2
- package/dist/mailbox.js +3 -2
- package/dist/maintenance.d.ts +2 -0
- package/dist/maintenance.js +5 -2
- package/dist/mcp-core.js +1 -1
- package/dist/operate-remote-arguments.js +5 -1
- package/dist/operate-remote.js +1 -1
- package/dist/operate.d.ts +1 -1
- package/dist/operate.js +201 -19
- package/dist/orphan-sweep.d.ts +2 -0
- package/dist/orphan-sweep.js +5 -3
- package/dist/project-delete-ui.d.ts +11 -0
- package/dist/project-delete-ui.js +29 -2
- package/dist/project-tools.d.ts +7 -11
- package/dist/project-tools.js +13 -37
- package/dist/provider.d.ts +73 -8
- package/dist/provider.js +87 -17
- package/dist/result-completion.d.ts +7 -0
- package/dist/result-completion.js +9 -0
- package/dist/runner.d.ts +4 -2
- package/dist/runner.js +3 -3
- package/dist/serve.js +22 -3
- package/dist/server/chrome.d.ts +6 -3
- package/dist/server/chrome.js +8 -4
- package/dist/server/console.css +9 -64
- package/dist/server/live.d.ts +3 -0
- package/dist/server/live.js +35 -2
- package/dist/server/pages.js +59 -35
- package/dist/server/render-chat.js +14 -2
- package/dist/server/render-tasks.d.ts +14 -2
- package/dist/server/render-tasks.js +38 -20
- package/dist/server/route-table.js +4 -1
- package/dist/server/settings.js +2 -1
- package/dist/server/task-views.js +10 -0
- package/dist/server/tasks.js +63 -0
- package/dist/shared-deps.d.ts +9 -4
- package/dist/shared-deps.js +89 -13
- package/dist/slack-socket.d.ts +12 -0
- package/dist/slack-socket.js +29 -0
- package/dist/slack.d.ts +0 -7
- package/dist/slack.js +2 -24
- package/dist/staged-runtimes.d.ts +16 -4
- package/dist/staged-runtimes.js +160 -38
- package/dist/steer-live.d.ts +19 -0
- package/dist/steer-live.js +93 -0
- package/dist/steer-mailbox.d.ts +39 -0
- package/dist/steer-mailbox.js +207 -0
- package/dist/steer-status.d.ts +25 -0
- package/dist/steer-status.js +44 -0
- package/dist/storage-sweep-child.d.ts +1 -0
- package/dist/storage-sweep-child.js +76 -0
- package/dist/storage-sweep-process.d.ts +41 -0
- package/dist/storage-sweep-process.js +187 -0
- package/dist/storage-sweep.d.ts +30 -6
- package/dist/storage-sweep.js +214 -28
- package/dist/storage-ui.d.ts +2 -0
- package/dist/storage-ui.js +16 -4
- package/dist/storage.d.ts +6 -0
- package/dist/storage.js +35 -0
- package/dist/store.d.ts +202 -2
- package/dist/store.js +440 -13
- package/dist/surface.js +5 -3
- package/dist/sweep-gate.d.ts +10 -0
- package/dist/sweep-gate.js +18 -0
- package/dist/sweep-quarantine.d.ts +28 -0
- package/dist/sweep-quarantine.js +153 -0
- package/dist/task-status.d.ts +2 -0
- package/dist/task-status.js +5 -1
- package/dist/team-updates.js +3 -3
- package/dist/test-temp.d.ts +6 -3
- package/dist/test-temp.js +15 -9
- package/dist/toolroll-update.js +15 -2
- package/dist/work-index.d.ts +1 -0
- package/dist/work-index.js +32 -12
- package/dist/work-summary.d.ts +2 -2
- package/dist/workspace-revision.js +3 -1
- package/dist/worktree.d.ts +3 -1
- package/dist/worktree.js +93 -10
- package/package.json +21 -21
- package/scripts/provider-canary.mjs +235 -147
package/dist/worktree.js
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* over `.git` internals corrupts both.
|
|
24
24
|
*/
|
|
25
25
|
import { createHash, randomBytes } from "node:crypto";
|
|
26
|
-
import { existsSync, readFileSync, writeFileSync, rmSync, realpathSync, mkdirSync, readdirSync } from "node:fs";
|
|
26
|
+
import { existsSync, readFileSync, writeFileSync, rmSync, realpathSync, mkdirSync, readdirSync, renameSync } from "node:fs";
|
|
27
27
|
import { hostname } from "node:os";
|
|
28
28
|
import { dirname, join } from "node:path";
|
|
29
29
|
import { run } from "./exec.js";
|
|
@@ -42,6 +42,61 @@ import { markNeverIndex } from "./never-index.js";
|
|
|
42
42
|
export const MARKER = ".standing-orders-lease";
|
|
43
43
|
/** Checkouts this process is leasing or removing right now: a lease and storage retention never overlap on one. */
|
|
44
44
|
const busy = new Map();
|
|
45
|
+
// Outside the checkout so removal cannot erase the reservation before git and the row update finish.
|
|
46
|
+
const reservationPath = (path) => `${path}.occupancy`;
|
|
47
|
+
function reservation(path) {
|
|
48
|
+
try {
|
|
49
|
+
const note = JSON.parse(readFileSync(reservationPath(path), "utf8"));
|
|
50
|
+
if (Number.isSafeInteger(note.pid) && note.pid > 0 && typeof note.host === "string" && typeof note.token === "string")
|
|
51
|
+
return note;
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (error.code === "ENOENT")
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
// An unreadable reservation is occupied, never a reason to delete someone's checkout.
|
|
58
|
+
return { pid: 0, host: "unknown", bootId: null, token: "unknown" };
|
|
59
|
+
}
|
|
60
|
+
function reservationAlive(note) {
|
|
61
|
+
if (note.pid === 0 || note.host !== hostname())
|
|
62
|
+
return true;
|
|
63
|
+
if (note.bootId !== null && provenDeadByBootChange(note))
|
|
64
|
+
return false;
|
|
65
|
+
try {
|
|
66
|
+
process.kill(note.pid, 0);
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
return error.code !== "ESRCH";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** A short store transaction arbitrates marker creation/recovery, never git, a scan or deletion.
|
|
74
|
+
* Atomic rename publishes a complete owner; a crash cannot leave a half-written reservation. */
|
|
75
|
+
function reserveCheckout(store, path) {
|
|
76
|
+
const note = { pid: process.pid, host: hostname(), bootId: currentBootId(), token: randomBytes(12).toString("hex") };
|
|
77
|
+
const file = reservationPath(path), temp = `${file}.${note.token}`;
|
|
78
|
+
const held = store.transact(() => {
|
|
79
|
+
const prior = reservation(path);
|
|
80
|
+
if (prior !== null && reservationAlive(prior))
|
|
81
|
+
return false;
|
|
82
|
+
try {
|
|
83
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
84
|
+
writeFileSync(temp, JSON.stringify(note), { flag: "wx", mode: 0o600 });
|
|
85
|
+
renameSync(temp, file);
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
rmSync(temp, { force: true });
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
if (!held)
|
|
96
|
+
return null;
|
|
97
|
+
return () => store.transact(() => { if (reservation(path)?.token === note.token)
|
|
98
|
+
rmSync(file, { force: true }); });
|
|
99
|
+
}
|
|
45
100
|
/** What a kept checkout drops once its run ends (only where .gitignore hides it): dependencies and build output. */
|
|
46
101
|
export const SLIM_NAMES = ["node_modules", "dist", "build", "out", ".next", ".nuxt", ".svelte-kit", ".turbo", ".parcel-cache", "coverage", "target"];
|
|
47
102
|
/** Creating a worktree copies a tree; it is local work but not instant. */
|
|
@@ -94,6 +149,9 @@ export function recordWorktreeProcess(store, path, runner, pid, leaseEpoch) {
|
|
|
94
149
|
}
|
|
95
150
|
/** Read-only occupancy witness shared with stop settlement. */
|
|
96
151
|
export function worktreeProcessOccupancy(path) {
|
|
152
|
+
const reserved = reservation(path);
|
|
153
|
+
if (reserved !== null && (reserved.pid !== process.pid || reserved.host !== hostname()) && reservationAlive(reserved))
|
|
154
|
+
return { held: true, by: reserved.pid };
|
|
97
155
|
const note = join(path, MARKER);
|
|
98
156
|
if (!existsSync(note))
|
|
99
157
|
return { held: false };
|
|
@@ -176,6 +234,8 @@ export class WorktreePool {
|
|
|
176
234
|
store;
|
|
177
235
|
options;
|
|
178
236
|
runner;
|
|
237
|
+
/** Carried to the independent storage sweep process. */
|
|
238
|
+
get root() { return this.options.root; }
|
|
179
239
|
constructor(store, options) {
|
|
180
240
|
this.store = store;
|
|
181
241
|
this.options = options;
|
|
@@ -237,17 +297,18 @@ export class WorktreePool {
|
|
|
237
297
|
}
|
|
238
298
|
async lease(request) {
|
|
239
299
|
const target = worktreePath(this.options.root, request.repo, request.branch);
|
|
240
|
-
if (busy.
|
|
241
|
-
return { ok: false, reason: "in-use", message: `${target} is
|
|
242
|
-
const
|
|
243
|
-
if (
|
|
244
|
-
|
|
300
|
+
if (busy.has(target))
|
|
301
|
+
return { ok: false, reason: "in-use", message: `${target} is busy; try again in a moment` };
|
|
302
|
+
const release = reserveCheckout(this.store, target);
|
|
303
|
+
if (release === null)
|
|
304
|
+
return { ok: false, reason: "in-use", message: `${target} is busy; try again in a moment` };
|
|
305
|
+
busy.set(target, "leasing");
|
|
245
306
|
try {
|
|
246
307
|
return await this.leaseNow(request);
|
|
247
308
|
}
|
|
248
309
|
finally {
|
|
249
|
-
|
|
250
|
-
|
|
310
|
+
release();
|
|
311
|
+
busy.delete(target);
|
|
251
312
|
}
|
|
252
313
|
}
|
|
253
314
|
async leaseNow(request) {
|
|
@@ -688,7 +749,10 @@ export class WorktreePool {
|
|
|
688
749
|
for (const row of rows) {
|
|
689
750
|
if (removed.length >= max)
|
|
690
751
|
break;
|
|
691
|
-
if (row.releasedAt === null || row.runner !== null || busy.has(row.path)
|
|
752
|
+
if (row.releasedAt === null || row.runner !== null || busy.has(row.path))
|
|
753
|
+
continue;
|
|
754
|
+
const selected = wanted(row, false);
|
|
755
|
+
if (!(typeof selected === "boolean" ? selected : await selected))
|
|
692
756
|
continue;
|
|
693
757
|
if (!existsSync(row.path) || !idle(row, row.releasedAt))
|
|
694
758
|
continue;
|
|
@@ -699,11 +763,18 @@ export class WorktreePool {
|
|
|
699
763
|
}
|
|
700
764
|
if (busy.has(row.path))
|
|
701
765
|
continue;
|
|
766
|
+
const release = reserveCheckout(this.store, row.path);
|
|
767
|
+
if (release === null)
|
|
768
|
+
continue;
|
|
702
769
|
busy.set(row.path, "removing");
|
|
703
770
|
try {
|
|
704
771
|
// A lease, or a task coming back, since the listing wins: look again right before removing.
|
|
705
772
|
const now = this.store.getWorktree(row.path);
|
|
706
|
-
if (!idle(now, row.releasedAt)
|
|
773
|
+
if (!idle(now, row.releasedAt))
|
|
774
|
+
continue;
|
|
775
|
+
// Landed cleanup rechecks Git here; existing synchronous guards keep their synchronous path.
|
|
776
|
+
const allowed = wanted(now, true);
|
|
777
|
+
if (!(typeof allowed === "boolean" ? allowed : await allowed))
|
|
707
778
|
continue;
|
|
708
779
|
const gone = await this.removeWorkingCopy(row);
|
|
709
780
|
if (!gone) {
|
|
@@ -713,6 +784,7 @@ export class WorktreePool {
|
|
|
713
784
|
removed.push(row);
|
|
714
785
|
}
|
|
715
786
|
finally {
|
|
787
|
+
release();
|
|
716
788
|
busy.delete(row.path);
|
|
717
789
|
}
|
|
718
790
|
}
|
|
@@ -735,6 +807,9 @@ export class WorktreePool {
|
|
|
735
807
|
.filter(one => { const name = one.split("/").at(-1); return SLIM_NAMES.includes(name) && (deps || name !== "node_modules"); });
|
|
736
808
|
if (drop.length === 0)
|
|
737
809
|
return { ok: true, dropped: [], bytes: 0 };
|
|
810
|
+
const release = reserveCheckout(this.store, row.path);
|
|
811
|
+
if (release === null)
|
|
812
|
+
return { ok: false, message: `${row.path} is in use` };
|
|
738
813
|
busy.set(row.path, "removing");
|
|
739
814
|
const note = join(row.path, MARKER);
|
|
740
815
|
let held = false;
|
|
@@ -759,6 +834,7 @@ export class WorktreePool {
|
|
|
759
834
|
finally {
|
|
760
835
|
if (held)
|
|
761
836
|
rmSync(note, { force: true });
|
|
837
|
+
release();
|
|
762
838
|
busy.delete(row.path);
|
|
763
839
|
}
|
|
764
840
|
}
|
|
@@ -775,8 +851,14 @@ export class WorktreePool {
|
|
|
775
851
|
const occupied = this.inUse(path);
|
|
776
852
|
if (occupied.held)
|
|
777
853
|
return { ok: false, message: `${path} is being used by process ${occupied.by} — it can't be discarded now` };
|
|
854
|
+
const release = reserveCheckout(this.store, path);
|
|
855
|
+
if (release === null)
|
|
856
|
+
return { ok: false, message: `${path} is in use` };
|
|
778
857
|
busy.set(path, "removing");
|
|
779
858
|
try {
|
|
859
|
+
const fresh = this.store.getWorktree(path);
|
|
860
|
+
if (fresh === null || fresh.runner !== null || fresh.releasedAt !== row.releasedAt || this.inUse(path).held)
|
|
861
|
+
return { ok: false, message: `${path} is in use` };
|
|
780
862
|
if (!existsSync(path)) {
|
|
781
863
|
this.store.transact(() => { const still = this.store.getWorktree(path); if (still !== null && still.runner === null && still.releasedAt === row.releasedAt)
|
|
782
864
|
this.store.forgetWorktree(path); });
|
|
@@ -785,6 +867,7 @@ export class WorktreePool {
|
|
|
785
867
|
return (await this.removeWorkingCopy(row)) ? { ok: true, row } : { ok: false, message: `git would not remove ${path}` };
|
|
786
868
|
}
|
|
787
869
|
finally {
|
|
870
|
+
release();
|
|
788
871
|
busy.delete(path);
|
|
789
872
|
}
|
|
790
873
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolroll",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.61",
|
|
4
4
|
"description": "A control plane for unattended coding agents — queue tasks, walk away, come back to pull requests. Agents build in leased worktrees, never touch your default branch, and interrupt you only for decisions that need a human.",
|
|
5
5
|
"directories": {
|
|
6
6
|
"doc": "docs"
|
|
@@ -62,16 +62,36 @@
|
|
|
62
62
|
"node": ">=22.13.0"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
|
+
"@radix-ui/react-collapsible": "1.1.20",
|
|
66
|
+
"@radix-ui/react-dialog": "1.1.23",
|
|
67
|
+
"@radix-ui/react-dropdown-menu": "2.1.24",
|
|
68
|
+
"@radix-ui/react-label": "2.1.15",
|
|
69
|
+
"@radix-ui/react-radio-group": "1.4.7",
|
|
70
|
+
"@radix-ui/react-select": "2.3.7",
|
|
71
|
+
"@radix-ui/react-separator": "1.1.15",
|
|
72
|
+
"@radix-ui/react-slot": "1.3.3",
|
|
73
|
+
"@radix-ui/react-switch": "1.3.7",
|
|
74
|
+
"@radix-ui/react-tabs": "1.1.21",
|
|
75
|
+
"@radix-ui/react-tooltip": "1.2.16",
|
|
65
76
|
"@tailwindcss/cli": "4.3.3",
|
|
66
77
|
"@types/mailparser": "3.9.0",
|
|
67
78
|
"@types/node": "26.6.4",
|
|
68
79
|
"@types/react": "19.3.0",
|
|
69
80
|
"@types/react-dom": "19.3.0",
|
|
81
|
+
"@xyflow/react": "12.12.0",
|
|
82
|
+
"class-variance-authority": "0.7.1",
|
|
83
|
+
"clsx": "2.1.1",
|
|
70
84
|
"esbuild": "0.28.2",
|
|
71
85
|
"happy-dom": "20.14.5",
|
|
86
|
+
"lucide-react": "1.52.0",
|
|
87
|
+
"react": "19.3.0",
|
|
88
|
+
"react-dom": "19.3.0",
|
|
89
|
+
"sonner": "2.0.8",
|
|
90
|
+
"tailwind-merge": "3.7.0",
|
|
72
91
|
"tailwindcss": "4.3.3",
|
|
73
92
|
"tsx": "4.23.15",
|
|
74
93
|
"typescript": "7.0.2",
|
|
94
|
+
"use-stick-to-bottom": "1.1.6",
|
|
75
95
|
"vitest": "4.1.11"
|
|
76
96
|
},
|
|
77
97
|
"files": [
|
|
@@ -96,32 +116,12 @@
|
|
|
96
116
|
}
|
|
97
117
|
},
|
|
98
118
|
"dependencies": {
|
|
99
|
-
"@radix-ui/react-collapsible": "1.1.20",
|
|
100
|
-
"@radix-ui/react-dialog": "1.1.23",
|
|
101
|
-
"@radix-ui/react-dropdown-menu": "2.1.24",
|
|
102
|
-
"@radix-ui/react-label": "2.1.15",
|
|
103
|
-
"@radix-ui/react-radio-group": "1.4.7",
|
|
104
|
-
"@radix-ui/react-select": "2.3.7",
|
|
105
|
-
"@radix-ui/react-separator": "1.1.15",
|
|
106
|
-
"@radix-ui/react-slot": "1.3.3",
|
|
107
|
-
"@radix-ui/react-switch": "1.3.7",
|
|
108
|
-
"@radix-ui/react-tabs": "1.1.21",
|
|
109
|
-
"@radix-ui/react-tooltip": "1.2.16",
|
|
110
119
|
"@slack/socket-mode": "3.1.0",
|
|
111
|
-
"@xyflow/react": "12.12.0",
|
|
112
|
-
"class-variance-authority": "0.7.1",
|
|
113
|
-
"clsx": "2.1.1",
|
|
114
120
|
"discord.js": "14.27.0",
|
|
115
121
|
"imapflow": "2.2.5",
|
|
116
|
-
"lucide-react": "1.52.0",
|
|
117
122
|
"mailparser": "3.9.36",
|
|
118
123
|
"nodemailer": "10.0.15",
|
|
119
|
-
"react": "19.3.0",
|
|
120
|
-
"react-dom": "19.3.0",
|
|
121
|
-
"sonner": "2.0.8",
|
|
122
|
-
"tailwind-merge": "3.7.0",
|
|
123
124
|
"typescript-parser": "npm:typescript@6.0.3",
|
|
124
|
-
"use-stick-to-bottom": "1.1.6",
|
|
125
125
|
"yaml": "2.9.1",
|
|
126
126
|
"zod": "^4.6.5"
|
|
127
127
|
}
|