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.
Files changed (152) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +8 -6
  3. package/dist/agent-board.d.ts +208 -0
  4. package/dist/agent-board.js +454 -0
  5. package/dist/agent-fence.d.ts +19 -0
  6. package/dist/agent-fence.js +100 -8
  7. package/dist/agent-pulse.d.ts +43 -0
  8. package/dist/agent-pulse.js +214 -0
  9. package/dist/agent-turns.d.ts +52 -0
  10. package/dist/agent-turns.js +137 -0
  11. package/dist/agents-board.d.ts +101 -0
  12. package/dist/agents-board.js +225 -0
  13. package/dist/assignment-adapters.d.ts +2 -1
  14. package/dist/assignment-adapters.js +1 -1
  15. package/dist/assignment-delivery.d.ts +16 -0
  16. package/dist/assignment-delivery.js +54 -28
  17. package/dist/assignment-status.d.ts +14 -5
  18. package/dist/assignment-status.js +70 -39
  19. package/dist/assignment.d.ts +10 -2
  20. package/dist/assignment.js +21 -14
  21. package/dist/bin.js +11 -2
  22. package/dist/brief-fence.d.ts +9 -0
  23. package/dist/brief-fence.js +22 -0
  24. package/dist/browser/workspace.css +2 -2
  25. package/dist/browser/workspace.js +29 -25
  26. package/dist/browser-crew.js +2 -0
  27. package/dist/browser-workspace.d.ts +79 -1
  28. package/dist/browser-workspace.js +1 -0
  29. package/dist/builder.d.ts +8 -0
  30. package/dist/builder.js +178 -29
  31. package/dist/chat-channel.js +10 -1
  32. package/dist/chat-core.d.ts +6 -0
  33. package/dist/chat-core.js +8 -0
  34. package/dist/chat-delivery.js +22 -0
  35. package/dist/chat-messages.js +8 -1
  36. package/dist/chat-quiet.js +1 -0
  37. package/dist/chat-voice.d.ts +2 -0
  38. package/dist/chat-voice.js +2 -0
  39. package/dist/checkout-cleanup.d.ts +3 -0
  40. package/dist/checkout-cleanup.js +11 -2
  41. package/dist/cli-fast.d.ts +8 -0
  42. package/dist/cli-fast.js +18 -0
  43. package/dist/cli-help.d.ts +5 -0
  44. package/dist/cli-help.js +91 -0
  45. package/dist/cli.d.ts +6 -5
  46. package/dist/cli.js +17 -96
  47. package/dist/contracts/console-api.d.ts +3 -1
  48. package/dist/contracts/console-api.js +5 -1
  49. package/dist/contracts/lead-tools.d.ts +16 -2
  50. package/dist/contracts/lead-tools.js +5 -1
  51. package/dist/design/tokens.css +234 -0
  52. package/dist/desktop-update.d.ts +1 -1
  53. package/dist/discord.js +2 -1
  54. package/dist/evidence-pack.js +1 -1
  55. package/dist/evidence.d.ts +28 -0
  56. package/dist/evidence.js +87 -17
  57. package/dist/exec.d.ts +32 -0
  58. package/dist/exec.js +229 -10
  59. package/dist/flow-actions.js +2 -1
  60. package/dist/guides.js +14 -11
  61. package/dist/invoke.js +110 -18
  62. package/dist/landed-cleanup.d.ts +157 -0
  63. package/dist/landed-cleanup.js +707 -0
  64. package/dist/lead-cli.js +22 -19
  65. package/dist/lead-contract.js +1 -1
  66. package/dist/lead-doors.js +49 -2
  67. package/dist/lead-follow.d.ts +2 -0
  68. package/dist/lead-follow.js +138 -88
  69. package/dist/lead-tools.js +48 -8
  70. package/dist/live-input.d.ts +68 -0
  71. package/dist/live-input.js +254 -0
  72. package/dist/live.d.ts +1 -1
  73. package/dist/live.js +4 -2
  74. package/dist/mailbox.js +3 -2
  75. package/dist/maintenance.d.ts +2 -0
  76. package/dist/maintenance.js +5 -2
  77. package/dist/mcp-core.js +1 -1
  78. package/dist/operate-remote-arguments.js +5 -1
  79. package/dist/operate-remote.js +1 -1
  80. package/dist/operate.d.ts +1 -1
  81. package/dist/operate.js +201 -19
  82. package/dist/orphan-sweep.d.ts +2 -0
  83. package/dist/orphan-sweep.js +5 -3
  84. package/dist/project-delete-ui.d.ts +11 -0
  85. package/dist/project-delete-ui.js +29 -2
  86. package/dist/project-tools.d.ts +7 -11
  87. package/dist/project-tools.js +13 -37
  88. package/dist/provider.d.ts +73 -8
  89. package/dist/provider.js +87 -17
  90. package/dist/result-completion.d.ts +7 -0
  91. package/dist/result-completion.js +9 -0
  92. package/dist/runner.d.ts +4 -2
  93. package/dist/runner.js +3 -3
  94. package/dist/serve.js +22 -3
  95. package/dist/server/chrome.d.ts +6 -3
  96. package/dist/server/chrome.js +8 -4
  97. package/dist/server/console.css +9 -64
  98. package/dist/server/live.d.ts +3 -0
  99. package/dist/server/live.js +35 -2
  100. package/dist/server/pages.js +59 -35
  101. package/dist/server/render-chat.js +14 -2
  102. package/dist/server/render-tasks.d.ts +14 -2
  103. package/dist/server/render-tasks.js +38 -20
  104. package/dist/server/route-table.js +4 -1
  105. package/dist/server/settings.js +2 -1
  106. package/dist/server/task-views.js +10 -0
  107. package/dist/server/tasks.js +63 -0
  108. package/dist/shared-deps.d.ts +9 -4
  109. package/dist/shared-deps.js +89 -13
  110. package/dist/slack-socket.d.ts +12 -0
  111. package/dist/slack-socket.js +29 -0
  112. package/dist/slack.d.ts +0 -7
  113. package/dist/slack.js +2 -24
  114. package/dist/staged-runtimes.d.ts +16 -4
  115. package/dist/staged-runtimes.js +160 -38
  116. package/dist/steer-live.d.ts +19 -0
  117. package/dist/steer-live.js +93 -0
  118. package/dist/steer-mailbox.d.ts +39 -0
  119. package/dist/steer-mailbox.js +207 -0
  120. package/dist/steer-status.d.ts +25 -0
  121. package/dist/steer-status.js +44 -0
  122. package/dist/storage-sweep-child.d.ts +1 -0
  123. package/dist/storage-sweep-child.js +76 -0
  124. package/dist/storage-sweep-process.d.ts +41 -0
  125. package/dist/storage-sweep-process.js +187 -0
  126. package/dist/storage-sweep.d.ts +30 -6
  127. package/dist/storage-sweep.js +214 -28
  128. package/dist/storage-ui.d.ts +2 -0
  129. package/dist/storage-ui.js +16 -4
  130. package/dist/storage.d.ts +6 -0
  131. package/dist/storage.js +35 -0
  132. package/dist/store.d.ts +202 -2
  133. package/dist/store.js +440 -13
  134. package/dist/surface.js +5 -3
  135. package/dist/sweep-gate.d.ts +10 -0
  136. package/dist/sweep-gate.js +18 -0
  137. package/dist/sweep-quarantine.d.ts +28 -0
  138. package/dist/sweep-quarantine.js +153 -0
  139. package/dist/task-status.d.ts +2 -0
  140. package/dist/task-status.js +5 -1
  141. package/dist/team-updates.js +3 -3
  142. package/dist/test-temp.d.ts +6 -3
  143. package/dist/test-temp.js +15 -9
  144. package/dist/toolroll-update.js +15 -2
  145. package/dist/work-index.d.ts +1 -0
  146. package/dist/work-index.js +32 -12
  147. package/dist/work-summary.d.ts +2 -2
  148. package/dist/workspace-revision.js +3 -1
  149. package/dist/worktree.d.ts +3 -1
  150. package/dist/worktree.js +93 -10
  151. package/package.json +21 -21
  152. package/scripts/provider-canary.mjs +235 -147
@@ -0,0 +1,44 @@
1
+ /** `liveBuild`: the task has a build whose agent is running and reads its steering folder (Store.steeringLiveBuild). */
2
+ export function steerStandingOf(note, liveBuild) {
3
+ if (note.authorshipState !== "verified")
4
+ return "unverified";
5
+ if (note.supersededAt !== null)
6
+ return "ended";
7
+ if (note.deliveredAt !== null)
8
+ return "read";
9
+ if (!liveBuild)
10
+ return "next-attempt";
11
+ // Sent to the running build every time it was owed, and never moved to handled/ (steer-live.ts).
12
+ return note.liveMissedAt !== null ? "not-picked-up" : "waiting";
13
+ }
14
+ /** The words, and the moment the agent read it when it has: each surface renders that time its own way. */
15
+ export function steerStandingParts(note, liveBuild) {
16
+ switch (steerStandingOf(note, liveBuild)) {
17
+ case "read": return { words: "Read by the agent at", readAt: note.ackedAt ?? note.deliveredAt };
18
+ case "waiting": return { words: "Waiting for the agent to read it", readAt: null };
19
+ case "not-picked-up": return { words: "Not picked up", readAt: null };
20
+ case "next-attempt": return { words: "Will reach the next attempt", readAt: null };
21
+ case "ended": return { words: "The task ended before the agent read it", readAt: null };
22
+ case "unverified": return { words: "Saved before notes required a sign-in; never sent", readAt: null };
23
+ }
24
+ }
25
+ export function steerStandingWords(note, liveBuild, time) {
26
+ const { words, readAt } = steerStandingParts(note, liveBuild);
27
+ return readAt === null ? words : `${words} ${time(readAt)}`;
28
+ }
29
+ /** Where a note filed now goes, said before or right after filing it. */
30
+ export function steerDestinationWords(liveBuild, liveInput = false) {
31
+ // A Claude build with live input takes it in seconds (steer-live.ts); any other running build reads its folder.
32
+ return liveBuild && liveInput
33
+ ? "The running agent gets it now."
34
+ : liveBuild
35
+ ? "The running agent reads it when it next checks for notes."
36
+ : "The next attempt reads it before starting.";
37
+ }
38
+ /** A paused live build's one sentence: until when it can wait before it ends, and how long that is (live-input.ts
39
+ * idleLimit), or only that it is paused when it has no idle limit. `time` renders the moment its surface's way. */
40
+ export function pausedWords(pause, time) {
41
+ if (pause.until === null || pause.limitMs === null)
42
+ return "Paused.";
43
+ return `Paused. It can wait until ${time(pause.until)} (${Math.max(1, Math.round(pause.limitMs / 60_000))} min) before it ends.`;
44
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,76 @@
1
+ /** Launched only by storage-sweep-process. All potentially expensive sweep work stays in this process. */
2
+ import { Worker } from "node:worker_threads";
3
+ import { openStoreNoMigrate } from "./store.js";
4
+ import { WorktreePool } from "./worktree.js";
5
+ import { executeSweep } from "./storage-sweep.js";
6
+ import { sweepRequest } from "./storage-sweep-process.js";
7
+ const parent = process.ppid;
8
+ let store;
9
+ let finished = false;
10
+ const closeStore = () => { const opened = store; store = undefined; opened?.close(); };
11
+ const abandon = () => {
12
+ if (finished)
13
+ return;
14
+ finished = true;
15
+ try {
16
+ closeStore();
17
+ }
18
+ finally {
19
+ // This detached child owns the group, including any blocked rm/du descendants.
20
+ try {
21
+ if (process.platform !== "win32")
22
+ process.kill(-process.pid, "SIGKILL");
23
+ }
24
+ catch { /* no group */ }
25
+ process.exit(1);
26
+ }
27
+ };
28
+ process.once("disconnect", abandon);
29
+ process.once("SIGTERM", abandon);
30
+ // A separate thread also detects parent loss while synchronous filesystem work blocks the child's event loop.
31
+ const watchdog = new Worker(`
32
+ const { workerData } = require('node:worker_threads');
33
+ setInterval(() => {
34
+ let lost = process.ppid !== workerData.parent || workerData.parent <= 1;
35
+ try { process.kill(workerData.parent, 0); } catch (error) { if (error.code === 'ESRCH') lost = true; }
36
+ if (!lost) return;
37
+ try { if (process.platform !== 'win32') process.kill(-process.pid, 'SIGKILL'); } catch {}
38
+ process.kill(process.pid, 'SIGKILL');
39
+ }, 1000);
40
+ `, { eval: true, workerData: { parent } });
41
+ watchdog.unref();
42
+ watchdog.once("error", abandon);
43
+ const finish = (code) => {
44
+ if (finished)
45
+ return;
46
+ finished = true;
47
+ closeStore();
48
+ void watchdog.terminate();
49
+ process.exitCode = code;
50
+ if (process.connected)
51
+ process.disconnect?.();
52
+ };
53
+ if (parent <= 1 || !process.connected)
54
+ abandon();
55
+ process.once("message", async (request) => {
56
+ if (!sweepRequest(request)) {
57
+ finish(1);
58
+ return;
59
+ }
60
+ try {
61
+ const opened = openStoreNoMigrate(request.databaseFile);
62
+ if (!opened.ok)
63
+ throw new Error("Sweep database unavailable");
64
+ store = opened.store;
65
+ const started = Date.now();
66
+ const now = () => new Date(Date.parse(request.at) + Date.now() - started);
67
+ const pool = request.poolRoot === null ? null : new WorktreePool(store, { root: request.poolRoot });
68
+ const record = await executeSweep(store, { databaseFile: request.databaseFile, pool, tempRoots: request.tempRoots, actor: request.actor }, now);
69
+ closeStore();
70
+ if (!finished)
71
+ process.send?.({ version: 1, record }, error => finish(error === null ? 0 : 1));
72
+ }
73
+ catch {
74
+ finish(1);
75
+ }
76
+ });
@@ -0,0 +1,41 @@
1
+ /** The storage child's bounded IPC protocol. No paths or process arguments are written to logs. */
2
+ import { type ChildProcess } from "node:child_process";
3
+ import type { SweepRecord } from "./storage-sweep.js";
4
+ export type SweepRequest = {
5
+ version: 1;
6
+ databaseFile: string;
7
+ poolRoot: string | null;
8
+ tempRoots: string[];
9
+ actor: string;
10
+ at: string;
11
+ };
12
+ export type SweepExecutor = (request: SweepRequest) => Promise<SweepRecord>;
13
+ export declare function sweepRequest(value: unknown): value is SweepRequest;
14
+ export declare function sweepResponse(value: unknown, request: SweepRequest): value is {
15
+ version: 1;
16
+ record: SweepRecord;
17
+ };
18
+ export type SweepCompletion = {
19
+ ok: true;
20
+ record: SweepRecord;
21
+ } | {
22
+ ok: false;
23
+ reason: "failed" | "timeout" | "stopped";
24
+ };
25
+ export type SweepSupervisor = {
26
+ readonly running: boolean;
27
+ start: (request: SweepRequest, complete: (result: SweepCompletion) => void, owner?: object) => boolean;
28
+ stop: (owner?: object) => void;
29
+ };
30
+ export declare const SWEEP_TIMEOUT_MS: number;
31
+ export declare const SWEEP_KILL_GRACE_MS = 5000;
32
+ /** Owns one child without lending its lifetime to a reconcile pass or its database connection. */
33
+ export declare function createSweepSupervisor(options?: {
34
+ timeoutMs?: number;
35
+ killGraceMs?: number;
36
+ launch?: () => ChildProcess;
37
+ signal?: (child: ChildProcess, signal: NodeJS.Signals) => void;
38
+ }): SweepSupervisor;
39
+ export declare function storageSweepSupervisor(databaseFile: string): SweepSupervisor;
40
+ /** Awaitable only for explicit callers/tests; automatic maintenance always uses start(). */
41
+ export declare const runSweepChild: SweepExecutor;
@@ -0,0 +1,187 @@
1
+ /** The storage child's bounded IPC protocol. No paths or process arguments are written to logs. */
2
+ import { fork } from "node:child_process";
3
+ import { resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ const object = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
6
+ const date = (value) => typeof value === "string" && Number.isFinite(Date.parse(value));
7
+ const strings = (value) => Array.isArray(value) && value.every(one => typeof one === "string");
8
+ export function sweepRequest(value) {
9
+ return object(value) && value.version === 1 && typeof value.databaseFile === "string" && value.databaseFile !== ":memory:" &&
10
+ (value.poolRoot === null || typeof value.poolRoot === "string") && strings(value.tempRoots) && typeof value.actor === "string" && date(value.at);
11
+ }
12
+ export function sweepResponse(value, request) {
13
+ if (!object(value) || value.version !== 1 || !object(value.record))
14
+ return false;
15
+ const record = value.record;
16
+ const kinds = ["test temp", "orphans", ...(request.poolRoot === null ? [] : ["checkouts"]), "runtimes", "dependencies"];
17
+ if (!date(record.at) || record.source !== "automatic" || record.actor !== request.actor || !Array.isArray(record.parts) || record.parts.length !== kinds.length)
18
+ return false;
19
+ return record.parts.every((part, index) => object(part) && part.kind === kinds[index] &&
20
+ [part.count, part.bytes, part.failed].every(n => typeof n === "number" && Number.isSafeInteger(n) && n >= 0) &&
21
+ strings(part.items) && part.items.length <= 20 && part.items.every(item => item.length <= 16_384) &&
22
+ (part.more === undefined || typeof part.more === "boolean") && (part.problem === undefined || (typeof part.problem === "string" && part.problem.length <= 65_536)));
23
+ }
24
+ export const SWEEP_TIMEOUT_MS = 20 * 60_000;
25
+ export const SWEEP_KILL_GRACE_MS = 5_000;
26
+ /** Only source checkouts need tsx; installed builds use the sibling child and the same Node. */
27
+ function forkSweep() {
28
+ const source = import.meta.url.endsWith(".ts");
29
+ const child = fork(fileURLToPath(new URL(source ? "./storage-sweep-child.ts" : "./storage-sweep-child.js", import.meta.url)), [], {
30
+ execArgv: source ? ["--import", "tsx"] : [], stdio: ["ignore", "ignore", "ignore", "ipc"], detached: true,
31
+ });
32
+ // A one-shot CLI may exit after reconcile; the child's parent-loss guard then ends it.
33
+ child.unref();
34
+ child.channel?.unref();
35
+ return child;
36
+ }
37
+ /** The fork is a group leader: rm/du and any other descendants must receive the same signal. */
38
+ function signalGroup(child, signal) {
39
+ if (child.pid === undefined)
40
+ return;
41
+ if (process.platform !== "win32") {
42
+ try {
43
+ process.kill(-child.pid, signal);
44
+ return;
45
+ }
46
+ catch (error) {
47
+ if (error.code === "ESRCH")
48
+ return;
49
+ }
50
+ }
51
+ try {
52
+ child.kill(signal);
53
+ }
54
+ catch { /* already gone */ }
55
+ }
56
+ /** Owns one child without lending its lifetime to a reconcile pass or its database connection. */
57
+ export function createSweepSupervisor(options = {}) {
58
+ const timeoutMs = options.timeoutMs ?? SWEEP_TIMEOUT_MS;
59
+ const killGraceMs = options.killGraceMs ?? SWEEP_KILL_GRACE_MS;
60
+ for (const value of [timeoutMs, killGraceMs]) {
61
+ if (!Number.isSafeInteger(value) || value < 1 || value > 2_147_483_647)
62
+ throw new Error("Invalid sweep process time limit");
63
+ }
64
+ const signal = options.signal ?? signalGroup;
65
+ let active = null;
66
+ const stoppedOwners = new WeakSet();
67
+ return {
68
+ get running() { return active !== null; },
69
+ stop(owner) {
70
+ if (owner !== undefined)
71
+ stoppedOwners.add(owner);
72
+ if (owner === undefined || active?.owner === owner)
73
+ active?.stop();
74
+ },
75
+ start(request, complete, owner) {
76
+ if (active !== null || (owner !== undefined && stoppedOwners.has(owner)))
77
+ return false;
78
+ let child;
79
+ let settled = false, closed = false, killing = false, killed = false;
80
+ let record = null;
81
+ let deadline;
82
+ let grace;
83
+ const settle = (result) => {
84
+ if (settled)
85
+ return;
86
+ settled = true;
87
+ clearTimeout(deadline);
88
+ complete(result);
89
+ };
90
+ const release = () => {
91
+ if (!closed || grace !== undefined)
92
+ return;
93
+ clearTimeout(deadline);
94
+ child.removeListener("message", message);
95
+ child.removeListener("error", error);
96
+ child.removeListener("close", close);
97
+ // A late IPC write error must not become an unhandled EventEmitter error.
98
+ child.on("error", () => { });
99
+ active = null;
100
+ };
101
+ const kill = () => {
102
+ if (killed)
103
+ return;
104
+ killed = true;
105
+ killing = true;
106
+ clearTimeout(grace);
107
+ grace = undefined;
108
+ signal(child, "SIGKILL");
109
+ release();
110
+ };
111
+ const terminate = (reason) => {
112
+ if (settled)
113
+ return;
114
+ killing = true;
115
+ signal(child, "SIGTERM");
116
+ // Even if the leader exits on TERM, descendants may ignore it. Keep the group owned through KILL.
117
+ grace = setTimeout(kill, killGraceMs);
118
+ grace.unref?.();
119
+ settle({ ok: false, reason });
120
+ };
121
+ const message = (value) => {
122
+ if (settled)
123
+ return;
124
+ if (record !== null || !sweepResponse(value, request)) {
125
+ terminate("failed");
126
+ return;
127
+ }
128
+ record = value.record;
129
+ };
130
+ const error = () => terminate("failed");
131
+ const close = (code) => {
132
+ closed = true;
133
+ if (!settled) {
134
+ if (code === 0 && record !== null)
135
+ settle({ ok: true, record });
136
+ else {
137
+ signal(child, "SIGKILL");
138
+ settle({ ok: false, reason: "failed" });
139
+ }
140
+ }
141
+ if (!killing || grace === undefined)
142
+ release();
143
+ };
144
+ try {
145
+ child = (options.launch ?? forkSweep)();
146
+ }
147
+ catch {
148
+ complete({ ok: false, reason: "failed" });
149
+ return true;
150
+ }
151
+ active = { owner, stop: () => { kill(); settle({ ok: false, reason: "stopped" }); } };
152
+ child.on("message", message);
153
+ child.on("error", error);
154
+ child.once("close", close);
155
+ deadline = setTimeout(() => terminate("timeout"), timeoutMs);
156
+ deadline.unref?.();
157
+ try {
158
+ child.send(request, error => { if (error !== null)
159
+ terminate("failed"); });
160
+ }
161
+ catch {
162
+ terminate("failed");
163
+ }
164
+ return true;
165
+ },
166
+ };
167
+ }
168
+ // All watch loops in this process share a flight. Other workers arbitrate the daily claim in SQLite.
169
+ const supervisors = new Map();
170
+ export function storageSweepSupervisor(databaseFile) {
171
+ const key = resolve(databaseFile);
172
+ let supervisor = supervisors.get(key);
173
+ if (supervisor === undefined) {
174
+ supervisor = createSweepSupervisor();
175
+ supervisors.set(key, supervisor);
176
+ }
177
+ return supervisor;
178
+ }
179
+ /** Awaitable only for explicit callers/tests; automatic maintenance always uses start(). */
180
+ export const runSweepChild = request => new Promise((resolve, reject) => {
181
+ createSweepSupervisor().start(request, result => {
182
+ if (result.ok)
183
+ resolve(result.record);
184
+ else
185
+ reject(new Error("Sweep child failed"));
186
+ });
187
+ });
@@ -1,7 +1,8 @@
1
1
  import { type Located } from "./orphan-sweep.js";
2
2
  import { type ProcessRow } from "./run-closeout.js";
3
- import type { Store } from "./store.js";
4
- import type { WorktreePool } from "./worktree.js";
3
+ import { type Store } from "./store.js";
4
+ import { type WorktreePool } from "./worktree.js";
5
+ import { type SweepSupervisor, type SweepExecutor } from "./storage-sweep-process.js";
5
6
  export declare const SWEEP_EVERY_MS = 86400000;
6
7
  /** A sweep that failed somewhere, or left work for later, runs again this soon. */
7
8
  export declare const RETRY_MS: number;
@@ -26,17 +27,40 @@ export type SweepDeps = {
26
27
  pool: WorktreePool | null;
27
28
  tempRoots: readonly string[];
28
29
  actor?: string;
30
+ /** Parent lifecycle only; never sent to the child. */
31
+ owner?: object;
29
32
  census?: () => ProcessRow[];
30
33
  locate?: (pids: readonly number[]) => Map<number, Located>;
31
34
  inUse?: () => string[];
35
+ scripts?: (pids: readonly number[]) => string[];
32
36
  graceMs?: number;
33
37
  };
34
38
  export declare function storageSweepOff(env?: NodeJS.ProcessEnv): boolean;
35
39
  export declare function storageSweepDue(store: Store, now: Date): boolean;
36
- /** The daily sweep, when it's due and on: one worker takes it. Null when it wasn't this pass's to run. */
37
- export declare function dailyStorageSweep(store: Store, deps: SweepDeps, now: () => Date): Promise<SweepRecord | null>;
38
- /** One sweep, now, every part; saved and in the ledger. Each part's failure is its own: the others still run. */
39
- export declare function sweepStorage(store: Store, deps: SweepDeps, now: () => Date): Promise<SweepRecord>;
40
+ /** Claim and launch only. A hung child never holds reconciliation or the caller's store open. */
41
+ export declare function dailyStorageSweep(store: Store, deps: SweepDeps, now: () => Date, supervisor?: SweepSupervisor): SweepRecord | null;
42
+ /**
43
+ * What live work uses now. Live work: an open run, a current (unexpired, unreleased) lease, or a checkout leased, held
44
+ * by a process or whose task has either. It uses its checkouts, the shared install each links, and every folder its
45
+ * open runs' processes (with their groups and what they started) run from. `where` says where the given processes run
46
+ * from, or null when that can't be read: then `blind`, and everything stays. With nothing live, nothing is read from the OS.
47
+ */
48
+ export type LiveWork = {
49
+ live: boolean;
50
+ paths: string[];
51
+ blind: boolean;
52
+ };
53
+ export declare function liveWork(store: Store, depsRoot: string, now: Date, where: (pids: readonly number[]) => string[] | null): LiveWork;
54
+ /**
55
+ * Whether live work uses `path`, asked again at every call, including process children. A cached process look
56
+ * cannot protect a child launched between the pre-rename and post-rename guards. All this work is in the child.
57
+ */
58
+ export declare function liveUse(store: Store, deps: SweepDeps, now: () => Date): (path: string) => boolean;
59
+ /** Parent side: no filesystem/process scans here. The executor seam is for deterministic unit tests only. */
60
+ export declare function sweepStorage(store: Store, deps: SweepDeps, now: () => Date, execute?: SweepExecutor): Promise<SweepRecord>;
61
+ export declare function sweepKinds(withPool: boolean): SweepKind[];
62
+ /** Child side (also a focused unit-test seam): every scan and removal, without saving the parent's result. */
63
+ export declare function executeSweep(store: Store, deps: SweepDeps, now: () => Date): Promise<SweepRecord>;
40
64
  /** Save what a sweep or a clean-up by hand did (the latest few are kept), with one ledger entry saying it. */
41
65
  export declare function saveSweep(store: Store, record: SweepRecord): SweepRecord;
42
66
  /** The latest sweep (of `source`, or either), or null when there has been none. */