toolroll 0.9.93 → 0.9.94
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 +68 -0
- package/dist/activity-line.d.ts +7 -4
- package/dist/activity-line.js +9 -7
- package/dist/agent-fence.d.ts +4 -0
- package/dist/agent-fence.js +4 -1
- package/dist/approval-authority.d.ts +7 -1
- package/dist/approval-authority.js +11 -2
- package/dist/approval-password.d.ts +31 -0
- package/dist/approval-password.js +26 -0
- package/dist/assignment-ui.js +1 -1
- package/dist/assignment.js +5 -0
- package/dist/boot-identity.d.ts +2 -1
- package/dist/boot-identity.js +6 -4
- package/dist/bounded-spawn.d.ts +57 -0
- package/dist/bounded-spawn.js +220 -0
- package/dist/browser/terminal.js +42 -42
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +32 -32
- package/dist/browser-shell.d.ts +1 -1
- package/dist/browser-shell.js +2 -2
- package/dist/browser-workspace.d.ts +45 -2
- package/dist/browser-workspace.js +3 -3
- package/dist/builder-restart.d.ts +21 -0
- package/dist/builder-restart.js +50 -0
- package/dist/builder.d.ts +4 -0
- package/dist/builder.js +53 -24
- package/dist/chat-acceptance.js +1 -1
- package/dist/chat-actions.d.ts +12 -2
- package/dist/chat-actions.js +25 -2
- package/dist/chat-channel.js +11 -0
- package/dist/chat-decide.d.ts +7 -2
- package/dist/chat-decide.js +37 -9
- package/dist/chat-quiet.js +3 -3
- package/dist/chat-voice.d.ts +1 -1
- package/dist/chat-voice.js +2 -2
- package/dist/check-facts.d.ts +3 -1
- package/dist/check-facts.js +57 -11
- package/dist/check-failure.d.ts +22 -3
- package/dist/check-failure.js +57 -13
- package/dist/check-levels-ui.d.ts +4 -0
- package/dist/check-levels-ui.js +10 -1
- package/dist/check-load.d.ts +85 -0
- package/dist/check-load.js +147 -0
- package/dist/check-only-standing.d.ts +36 -0
- package/dist/check-only-standing.js +39 -0
- package/dist/check-only.d.ts +15 -0
- package/dist/check-only.js +99 -0
- package/dist/check-report.d.ts +2 -0
- package/dist/check-report.js +1 -1
- package/dist/check-stamp.d.ts +46 -0
- package/dist/check-stamp.js +93 -0
- package/dist/checkout-cleanup.d.ts +36 -5
- package/dist/checkout-cleanup.js +166 -14
- package/dist/cli-help.d.ts +4 -1
- package/dist/cli-help.js +47 -30
- package/dist/cli.js +11 -1
- package/dist/code-extract.d.ts +44 -0
- package/dist/code-extract.js +88 -0
- package/dist/code-map-acts.d.ts +24 -0
- package/dist/code-map-acts.js +53 -0
- package/dist/code-map-admission.d.ts +52 -0
- package/dist/code-map-admission.js +295 -0
- package/dist/code-map-build.d.ts +63 -0
- package/dist/code-map-build.js +415 -0
- package/dist/code-map-cli.d.ts +153 -0
- package/dist/code-map-cli.js +103 -0
- package/dist/code-map-main.d.ts +1 -0
- package/dist/code-map-main.js +48 -0
- package/dist/code-map-read.d.ts +106 -0
- package/dist/code-map-read.js +171 -0
- package/dist/code-map-report.d.ts +37 -0
- package/dist/code-map-report.js +95 -0
- package/dist/code-map-shape.d.ts +172 -0
- package/dist/code-map-shape.js +327 -0
- package/dist/code-map-store.d.ts +136 -0
- package/dist/code-map-store.js +233 -0
- package/dist/coding-provider.d.ts +2 -0
- package/dist/coding-provider.js +3 -2
- package/dist/coding-update.js +3 -2
- package/dist/coding-workspace.js +4 -4
- package/dist/console-chrome.js +2 -2
- package/dist/containment.d.ts +5 -0
- package/dist/containment.js +14 -1
- package/dist/contracts/chat-actions.d.ts +9 -0
- package/dist/contracts/chat-actions.js +2 -0
- package/dist/contracts/cli.d.ts +52 -0
- package/dist/contracts/cli.js +4 -0
- package/dist/contracts/console-api.d.ts +10 -3
- package/dist/contracts/console-api.js +10 -3
- package/dist/contracts/gateway-tools.d.ts +17 -0
- package/dist/contracts/gateway-tools.js +3 -1
- package/dist/contracts/lead-tools.d.ts +61 -0
- package/dist/contracts/lead-tools.js +10 -1
- package/dist/contracts/project-knowledge.d.ts +38 -5
- package/dist/contracts/project-knowledge.js +11 -5
- package/dist/contracts/project-memory.d.ts +3 -1
- package/dist/contracts/project-memory.js +3 -2
- package/dist/contracts/storage.d.ts +14 -0
- package/dist/contracts/storage.js +3 -1
- package/dist/contracts/store-json.d.ts +2 -0
- package/dist/contracts/youiyoux-request.d.ts +1 -1
- package/dist/contracts/youiyoux-request.js +1 -1
- package/dist/desktop-access.js +6 -2
- package/dist/dispatch.js +18 -15
- package/dist/dispose.js +8 -2
- package/dist/easy-start.js +2 -3
- package/dist/evals.js +2 -1
- package/dist/fast-start.d.ts +136 -0
- package/dist/fast-start.js +431 -0
- package/dist/fast-terms.d.ts +9 -0
- package/dist/fast-terms.js +23 -0
- package/dist/fenced-check-coverage.d.ts +62 -0
- package/dist/fenced-check-coverage.js +54 -0
- package/dist/first-run.js +1 -1
- package/dist/flaky-ci.d.ts +31 -0
- package/dist/flaky-ci.js +149 -0
- package/dist/flaky-cli.d.ts +60 -0
- package/dist/flaky-cli.js +60 -0
- package/dist/flaky.d.ts +75 -9
- package/dist/flaky.js +255 -0
- package/dist/flow-card-task.js +3 -0
- package/dist/flow-triggers.d.ts +1 -1
- package/dist/flow-triggers.js +1 -1
- package/dist/github-import.d.ts +62 -0
- package/dist/github-import.js +146 -0
- package/dist/guides.js +15 -10
- package/dist/invoke.d.ts +5 -1
- package/dist/invoke.js +49 -14
- package/dist/job-object-helper.ps1 +10 -3
- package/dist/keymap.d.ts +41 -0
- package/dist/keymap.js +51 -0
- package/dist/knowledge-cli.js +1 -1
- package/dist/knowledge-ui.d.ts +21 -4
- package/dist/knowledge-ui.js +75 -7
- package/dist/laptop-background.d.ts +16 -0
- package/dist/laptop-background.js +108 -2
- package/dist/lead-context.js +1 -1
- package/dist/lead-contract.d.ts +1 -1
- package/dist/lead-contract.js +2 -2
- package/dist/lead-progress.js +3 -0
- package/dist/lead-status.d.ts +2 -0
- package/dist/lead-status.js +6 -2
- package/dist/lead-tools.js +51 -2
- package/dist/lead.js +1 -1
- package/dist/lesson-notes.d.ts +81 -0
- package/dist/lesson-notes.js +336 -0
- package/dist/limits-ui.js +1 -1
- package/dist/local-owner.d.ts +1 -1
- package/dist/local-owner.js +11 -0
- package/dist/machine-check.d.ts +16 -0
- package/dist/machine-check.js +26 -0
- package/dist/machine-identity.d.ts +75 -0
- package/dist/machine-identity.js +170 -0
- package/dist/mcp-core.js +11 -1
- package/dist/memory-cli.d.ts +300 -1
- package/dist/memory-cli.js +62 -9
- package/dist/memory-pass.d.ts +16 -0
- package/dist/memory-pass.js +67 -3
- package/dist/memory-tidy.d.ts +181 -0
- package/dist/memory-tidy.js +523 -0
- package/dist/memory-use.d.ts +35 -0
- package/dist/memory-use.js +109 -0
- package/dist/merge-checklist.d.ts +55 -0
- package/dist/merge-checklist.js +182 -0
- package/dist/needs-you.d.ts +27 -8
- package/dist/needs-you.js +96 -39
- package/dist/nightly-github.d.ts +2 -0
- package/dist/nightly-github.js +4 -4
- package/dist/operate-remote-arguments.js +6 -1
- package/dist/operate-remote.js +3 -1
- package/dist/operate.d.ts +6 -1
- package/dist/operate.js +402 -52
- package/dist/phase-routing.js +2 -2
- package/dist/plain-schema.d.ts +23 -0
- package/dist/plain-schema.js +94 -0
- package/dist/plain-words.d.ts +20 -0
- package/dist/plain-words.js +66 -0
- package/dist/planner.js +2 -1
- package/dist/process-liveness.d.ts +2 -0
- package/dist/process-liveness.js +61 -17
- package/dist/project-delete-ui.d.ts +7 -2
- package/dist/project-delete-ui.js +12 -1
- package/dist/project-delete.js +8 -1
- package/dist/project-knowledge.d.ts +18 -11
- package/dist/project-knowledge.js +39 -9
- package/dist/project-learning.d.ts +2 -1
- package/dist/project-learning.js +15 -45
- package/dist/project-memory.d.ts +5 -0
- package/dist/project-memory.js +22 -2
- package/dist/proof.js +30 -5
- package/dist/provider-auth.js +1 -1
- package/dist/publish.js +9 -1
- package/dist/pull-request-flow.js +5 -0
- package/dist/push.d.ts +18 -2
- package/dist/push.js +40 -16
- package/dist/release-adapters.d.ts +9 -0
- package/dist/release-adapters.js +76 -0
- package/dist/release-auto.js +13 -13
- package/dist/release-card.d.ts +1 -2
- package/dist/release-card.js +11 -0
- package/dist/release-carry.d.ts +7 -2
- package/dist/release-carry.js +39 -9
- package/dist/release-cli.d.ts +4 -0
- package/dist/release-cli.js +29 -22
- package/dist/release-coverage.js +1 -1
- package/dist/release-next.d.ts +48 -0
- package/dist/release-next.js +161 -0
- package/dist/release.d.ts +20 -1
- package/dist/release.js +76 -8
- package/dist/remote-destination.d.ts +36 -0
- package/dist/remote-destination.js +217 -0
- package/dist/remote-main.d.ts +48 -0
- package/dist/remote-main.js +84 -5
- package/dist/repository-context.d.ts +3 -2
- package/dist/repository-context.js +112 -70
- package/dist/restart-certification.d.ts +1 -0
- package/dist/restart-certification.js +8 -6
- package/dist/result-acts.d.ts +27 -19
- package/dist/result-acts.js +36 -25
- package/dist/result-follow-ups.d.ts +2 -2
- package/dist/result-follow-ups.js +18 -4
- package/dist/result-review.d.ts +1 -1
- package/dist/route-change-check.d.ts +4 -0
- package/dist/route-change-check.js +13 -0
- package/dist/route-change.d.ts +22 -0
- package/dist/route-change.js +89 -0
- package/dist/route-hooks.js +3 -2
- package/dist/route-results.d.ts +6 -1
- package/dist/route-results.js +14 -5
- package/dist/run-closeout.d.ts +1 -1
- package/dist/run-closeout.js +7 -5
- package/dist/runner-process.js +3 -2
- package/dist/runner.js +2 -1
- package/dist/scope.d.ts +6 -1
- package/dist/scope.js +22 -9
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +24 -18
- package/dist/server/chat.js +14 -12
- package/dist/server/chrome.d.ts +1 -1
- package/dist/server/chrome.js +26 -35
- package/dist/server/console.css +23 -0
- package/dist/server/guards.js +3 -0
- package/dist/server/home.d.ts +6 -0
- package/dist/server/home.js +21 -13
- package/dist/server/http.js +1 -1
- package/dist/server/kit.js +1 -1
- package/dist/server/merge-ready-view.d.ts +10 -0
- package/dist/server/merge-ready-view.js +30 -0
- package/dist/server/pages-assets.d.ts +1 -0
- package/dist/server/pages-assets.js +8 -5
- package/dist/server/pages.js +103 -82
- package/dist/server/render-chat.js +2 -2
- package/dist/server/render-code-map.d.ts +30 -0
- package/dist/server/render-code-map.js +137 -0
- package/dist/server/render-flaky.d.ts +20 -0
- package/dist/server/render-flaky.js +59 -0
- package/dist/server/render-history.js +1 -1
- package/dist/server/render-new-task.d.ts +26 -1
- package/dist/server/render-new-task.js +30 -5
- package/dist/server/render-pages.d.ts +1 -175
- package/dist/server/render-pages.js +38 -313
- package/dist/server/render-projects.d.ts +2 -1
- package/dist/server/render-projects.js +9 -7
- package/dist/server/render-release.d.ts +2 -0
- package/dist/server/render-release.js +3 -3
- package/dist/server/render-results.d.ts +2 -0
- package/dist/server/render-results.js +67 -67
- package/dist/server/render-tasks.d.ts +13 -2
- package/dist/server/render-tasks.js +153 -136
- package/dist/server/retired-routes.d.ts +17 -0
- package/dist/server/retired-routes.js +53 -0
- package/dist/server/route-table.d.ts +3 -0
- package/dist/server/route-table.js +45 -12
- package/dist/server/settings.js +129 -22
- package/dist/server/task-views.js +61 -26
- package/dist/server/tasks.d.ts +2 -6
- package/dist/server/tasks.js +174 -321
- package/dist/server/welcome.js +1 -1
- package/dist/settings-groups-ui.js +1 -1
- package/dist/setup-guide.d.ts +2 -2
- package/dist/stop-proof.js +6 -4
- package/dist/storage-sweep-process.js +1 -1
- package/dist/storage-sweep.d.ts +5 -3
- package/dist/storage-sweep.js +30 -9
- package/dist/storage-ui.js +28 -17
- package/dist/storage.d.ts +27 -2
- package/dist/storage.js +39 -2
- package/dist/store.d.ts +80 -5
- package/dist/store.js +296 -87
- package/dist/surface.js +45 -4
- package/dist/sweep-quarantine.d.ts +1 -0
- package/dist/sweep-quarantine.js +6 -4
- package/dist/tamper-scan.d.ts +4 -0
- package/dist/tamper-scan.js +90 -0
- package/dist/task-aliases.d.ts +5 -0
- package/dist/task-aliases.js +9 -0
- package/dist/task-control.js +4 -1
- package/dist/task-outcome-cli.d.ts +2 -0
- package/dist/task-outcome-cli.js +17 -17
- package/dist/task-sizing.d.ts +5 -0
- package/dist/task-sizing.js +2 -0
- package/dist/task-source.d.ts +7 -0
- package/dist/task-source.js +14 -0
- package/dist/task-status.d.ts +9 -0
- package/dist/task-status.js +40 -49
- package/dist/teardown-git.d.ts +16 -0
- package/dist/teardown-git.js +63 -5
- package/dist/telegram-progress.js +5 -3
- package/dist/telegram-status.js +6 -4
- package/dist/test-infra.d.ts +8 -0
- package/dist/test-infra.js +18 -0
- package/dist/tidy.js +1 -1
- package/dist/toolroll-update.js +17 -5
- package/dist/tui-contract.d.ts +24 -0
- package/dist/tui-source.js +67 -8
- package/dist/tui.js +51 -51
- package/dist/vanished-retry.d.ts +30 -0
- package/dist/vanished-retry.js +174 -0
- package/dist/work-index.d.ts +2 -0
- package/dist/work-index.js +76 -62
- package/dist/work-summary.js +2 -0
- package/dist/workspace-ui.d.ts +4 -2
- package/dist/workspace-ui.js +28 -22
- package/dist/worktree.d.ts +12 -3
- package/dist/worktree.js +68 -13
- package/package.json +1 -1
- package/dist/server/render-next.d.ts +0 -63
- package/dist/server/render-next.js +0 -109
package/dist/server/welcome.js
CHANGED
|
@@ -87,7 +87,7 @@ ${listed.length === 0 ? "" : html `<div class="welcome-rows">${listed}</div>`}${
|
|
|
87
87
|
const taskLeads = lead === "task";
|
|
88
88
|
if (taskLeads)
|
|
89
89
|
primary = btn("Write a task", { variant: "primary", href: "/tasks/new" });
|
|
90
|
-
const taskBody = model.task.done ? html `<p class="k-note">Filed. Its plan waits for your approval in <a class="k-link" href="/
|
|
90
|
+
const taskBody = model.task.done ? html `<p class="k-note">Filed. Its plan waits for your approval in <a class="k-link" href="/tasks">Tasks</a>.</p>`
|
|
91
91
|
: !model.task.open ? html `<p class="k-note">Add a project first.</p>`
|
|
92
92
|
: html `<p class="k-note">Describe a change in a sentence or two. Nothing starts until you approve its plan.</p>\
|
|
93
93
|
${model.task.starters.length === 0 ? "" : html `<div class="k-ideas" aria-label="Ideas to start from" data-starters>${model.task.starters.map(one => html `<a class="k-btn k-btn-outline k-idea" href="${one.href}">${one.label}</a>`)}</div>`}\
|
|
@@ -465,7 +465,7 @@ export function systemGroup(facts, flags) {
|
|
|
465
465
|
const links = facts.operator ? settingsLinks("In this group", [
|
|
466
466
|
settingsLink({ href: "/settings/updates", title: "Updates", status: facts.updates === null ? "Toolroll’s own version" : `Toolroll ${facts.updates.current}` }),
|
|
467
467
|
settingsLink({ href: "/settings/backups", title: "Backups", status: facts.backup?.words ?? "When and where the database is copied", needs: facts.backup?.failed ? 1 : 0 }),
|
|
468
|
-
settingsLink({ href: "/settings/storage", title: "Storage", status: "
|
|
468
|
+
settingsLink({ href: "/settings/storage", title: "Storage", status: "Task folders on this computer, and cleaning them up" }),
|
|
469
469
|
settingsLink({ href: "/settings/retention", title: "Retention", status: "How long run logs and chats are kept" }),
|
|
470
470
|
settingsLink({ href: "/settings/monitoring", title: "Monitoring", status: "Send the audit stream and traces to your own systems" }),
|
|
471
471
|
settingsLink({ href: "/settings/data", title: "Data export", status: "Everything except secrets, as one download" }),
|
package/dist/setup-guide.d.ts
CHANGED
|
@@ -18,9 +18,9 @@ export declare function previewProjectInstructions(repo: string): {
|
|
|
18
18
|
ok: false;
|
|
19
19
|
message: string;
|
|
20
20
|
plan?: never;
|
|
21
|
-
content?: never;
|
|
22
21
|
fingerprint?: never;
|
|
23
22
|
installed?: never;
|
|
23
|
+
content?: never;
|
|
24
24
|
} | {
|
|
25
25
|
message?: never;
|
|
26
26
|
ok: true;
|
|
@@ -33,7 +33,7 @@ export declare function addProjectInstructions(repo: string, fingerprint: string
|
|
|
33
33
|
ok: false;
|
|
34
34
|
message: string;
|
|
35
35
|
plan?: never;
|
|
36
|
-
content?: never;
|
|
37
36
|
fingerprint?: never;
|
|
38
37
|
installed?: never;
|
|
38
|
+
content?: never;
|
|
39
39
|
} | import("./skills.js").InstallResult;
|
package/dist/stop-proof.js
CHANGED
|
@@ -26,7 +26,7 @@ import { hostname } from "node:os";
|
|
|
26
26
|
import { sep } from "node:path";
|
|
27
27
|
import { ownedProcessCount, runOwnerTag } from "./exec.js";
|
|
28
28
|
import { closeoutTargets, processCensus, protectedProcesses, stopProcesses, stoppedWords } from "./run-closeout.js";
|
|
29
|
-
import { recordStopProofBlocked, settleRunStopByObservation } from "./store.js";
|
|
29
|
+
import { localWitnessSql, recordStopProofBlocked, settleRunStopByObservation } from "./store.js";
|
|
30
30
|
import { worktreeProcessOccupancy } from "./worktree.js";
|
|
31
31
|
/** How long after a run finished its processes have to go on their own before Toolroll looks. */
|
|
32
32
|
export const STOP_PROOF_GRACE_MS = 60_000;
|
|
@@ -223,7 +223,8 @@ export async function proveRunStopped(store, runId, clock, options = {}) {
|
|
|
223
223
|
return { run: subject, outcome: "waiting", words: "a process of it is still tracked here" };
|
|
224
224
|
if (process.platform === "win32")
|
|
225
225
|
return block(store, subject, "can't be checked on this computer: Windows processes can't be listed by folder", "no folder listing on Windows", now);
|
|
226
|
-
const
|
|
226
|
+
const local = localWitnessSql(store.handle);
|
|
227
|
+
const foreign = store.handle.prepare(`SELECT host FROM run_process WHERE run = ? AND exited_at IS NULL AND NOT ${local.sql} LIMIT 1`).get(subject, ...local.params);
|
|
227
228
|
if (foreign !== undefined)
|
|
228
229
|
return block(store, subject, `ran on another computer (${String(foreign["host"])}); only that computer can check it stopped`, `an open witness belongs to ${String(foreign["host"])}`, now);
|
|
229
230
|
if (run.worktree !== null) {
|
|
@@ -277,10 +278,11 @@ function block(store, runId, words, detail, now) {
|
|
|
277
278
|
export async function proveFinishedRunsStopped(store, clock, options = {}) {
|
|
278
279
|
const grace = options.afterFinishMs ?? STOP_PROOF_GRACE_MS;
|
|
279
280
|
const before = new Date(clock().getTime() - grace).toISOString();
|
|
281
|
+
const local = localWitnessSql(store.handle, "p");
|
|
280
282
|
const runs = store.handle.prepare(`SELECT id FROM run r WHERE r.outcome IS NOT NULL AND r.finished_at IS NOT NULL AND r.finished_at <= ? AND (
|
|
281
|
-
EXISTS (SELECT 1 FROM run_process p WHERE p.run = r.id AND p.exited_at IS NULL AND p.pid IS NULL AND p.containment IS NULL AND p.container IS NULL AND
|
|
283
|
+
EXISTS (SELECT 1 FROM run_process p WHERE p.run = r.id AND p.exited_at IS NULL AND p.pid IS NULL AND p.containment IS NULL AND p.container IS NULL AND ${local.sql})
|
|
282
284
|
OR (r.provider_started_at IS NOT NULL AND NOT EXISTS (SELECT 1 FROM run_process p WHERE p.run = r.id) AND NOT EXISTS (SELECT 1 FROM run_process_summary s WHERE s.run = r.id)))
|
|
283
|
-
ORDER BY r.id LIMIT ?`).all(before,
|
|
285
|
+
ORDER BY r.id LIMIT ?`).all(before, ...local.params, options.batch ?? 50).map(row => Number(row["id"]));
|
|
284
286
|
const out = [];
|
|
285
287
|
const shared = sharedLook(options);
|
|
286
288
|
for (const runId of runs) {
|
|
@@ -15,7 +15,7 @@ export function sweepResponse(value, request) {
|
|
|
15
15
|
if (!object(value) || value.version !== 1 || !object(value.record))
|
|
16
16
|
return false;
|
|
17
17
|
const record = value.record;
|
|
18
|
-
const kinds = ["test temp", "orphans", ...(request.poolRoot === null ? [] : ["checkouts"]), "runtimes", "dependencies"];
|
|
18
|
+
const kinds = ["test temp", "orphans", ...(request.poolRoot === null ? [] : ["checkouts"]), "runtimes", "dependencies", "check history"];
|
|
19
19
|
if (!date(record.at) || record.source !== "automatic" || record.actor !== request.actor || !Array.isArray(record.parts) || record.parts.length !== kinds.length)
|
|
20
20
|
return false;
|
|
21
21
|
return record.parts.every((part, index) => object(part) && part.kind === kinds[index] &&
|
package/dist/storage-sweep.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { type Located } from "./orphan-sweep.js";
|
|
2
2
|
import { type ProcessRow } from "./run-closeout.js";
|
|
3
|
-
import type
|
|
3
|
+
import { type Store } from "./store.js";
|
|
4
4
|
import { type WorktreePool } from "./worktree.js";
|
|
5
5
|
import { type SweepCompletion, type SweepExecutor } from "./storage-sweep-process.js";
|
|
6
6
|
export declare const SWEEP_CURSOR = "storage-sweep";
|
|
7
7
|
export declare const SWEEP_EVERY_MS = 86400000;
|
|
8
8
|
/** A sweep that failed somewhere, or left work for later, runs again this soon. */
|
|
9
9
|
export declare const RETRY_MS: number;
|
|
10
|
-
export type SweepKind = "test temp" | "orphans" | "checkouts" | "runtimes" | "dependencies";
|
|
10
|
+
export type SweepKind = "test temp" | "orphans" | "checkouts" | "runtimes" | "dependencies" | "check history";
|
|
11
11
|
export type SweepPart = {
|
|
12
12
|
kind: SweepKind;
|
|
13
13
|
count: number;
|
|
@@ -35,6 +35,8 @@ export type SweepDeps = {
|
|
|
35
35
|
inUse?: () => string[];
|
|
36
36
|
scripts?: (pids: readonly number[]) => string[];
|
|
37
37
|
graceMs?: number;
|
|
38
|
+
/** Which processes run from inside each path. */
|
|
39
|
+
cwds?: (paths: readonly string[]) => Map<string, number[]>;
|
|
38
40
|
};
|
|
39
41
|
export declare function storageSweepOff(env?: NodeJS.ProcessEnv): boolean;
|
|
40
42
|
export declare function sweepFailure(at: string, actor: string, withPool: boolean, reason: Exclude<SweepCompletion, {
|
|
@@ -69,7 +71,7 @@ export declare function lastSweep(store: Store, source?: SweepRecord["source"]):
|
|
|
69
71
|
/** When the next automatic sweep is due, or null when it is off. */
|
|
70
72
|
export declare function nextSweepAt(store: Store): string | null;
|
|
71
73
|
export declare function partWords(one: Pick<SweepPart, "kind" | "count">): string;
|
|
72
|
-
/** "Removed 120 test temp folders and 2
|
|
74
|
+
/** "Removed 120 test temp folders and 2 task folders, about 1.2 GB. Stopped 1 leftover process. 3 couldn't be removed." */
|
|
73
75
|
export declare function sweepWords(parts: readonly SweepPart[]): string;
|
|
74
76
|
/** The details a disclosure shows: each part's paths, problems and what's left for later, one line each. */
|
|
75
77
|
export declare function sweepDetails(parts: readonly SweepPart[]): string[];
|
package/dist/storage-sweep.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The storage sweep: the plane cleans up after itself without anyone running a command. About once a day, when nothing
|
|
3
3
|
* is running or waiting (storage-sweep-schedule.ts: one service takes it, the cursor moves in the same transaction),
|
|
4
4
|
* it removes test temp folders nothing touched for a day (test-temp.ts), stops orphans Toolroll left running (orphan-sweep.ts), removes finished checkouts the cleanup
|
|
5
|
-
* setting lets go (checkout-cleanup.ts), keeps the current, previous and newest staged runtimes (staged-runtimes.ts)
|
|
5
|
+
* setting lets go and idle ones past the idle limit (checkout-cleanup.ts), keeps the current, previous and newest staged runtimes (staged-runtimes.ts)
|
|
6
6
|
* and removes shared dependency installs no checkout has used for a week (shared-deps.ts).
|
|
7
7
|
*
|
|
8
8
|
* Nothing live work uses goes (`liveWork`): while a run is open, a lease current or a checkout in use, each removal
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
* The everyday work stays where it happens: suites remove their own temp roots and processes, a run's processes stop
|
|
16
16
|
* when it ends and a finished checkout goes on the worker's next pass. This is the net for what slipped through.
|
|
17
17
|
*
|
|
18
|
+
* The last part is rows, not disk: test results and flaky-test observations past their 180 days (check history).
|
|
19
|
+
*
|
|
18
20
|
* What each sweep did is saved (the latest few) and in the ledger: `toolroll storage` and Settings → Storage show it.
|
|
19
21
|
* A clean-up by hand (`storage clean --yes`) is saved the same way. A part that failed, or had more than one pass
|
|
20
22
|
* takes, brings the next sweep forward to an hour later; it still waits for nothing to be running.
|
|
@@ -22,14 +24,18 @@
|
|
|
22
24
|
* it runs on).
|
|
23
25
|
*/
|
|
24
26
|
import { realpathSync } from "node:fs";
|
|
25
|
-
import { hostname } from "node:os";
|
|
26
27
|
import { dirname, join, resolve, sep } from "node:path";
|
|
28
|
+
import { pruneCheckResults } from "./check-facts.js";
|
|
27
29
|
import { cleanCheckouts } from "./checkout-cleanup.js";
|
|
30
|
+
import { pruneCiObservations } from "./flaky-ci.js";
|
|
31
|
+
import { pruneObservations } from "./flaky.js";
|
|
28
32
|
import { durationWords, locateProcesses, sweepOrphans, ownerRunning } from "./orphan-sweep.js";
|
|
29
33
|
import { processCensus } from "./run-closeout.js";
|
|
30
34
|
import { linkedKey, pruneUnusedShared, sharedDepsRoot, sharedUse, SHARED_UNUSED_MS } from "./shared-deps.js";
|
|
31
35
|
import { pruneStagedRuntimes, runtimesInUse, runtimeScriptPaths, stagePlan } from "./staged-runtimes.js";
|
|
32
36
|
import { bytesWords, diskBytes } from "./storage.js";
|
|
37
|
+
import { localWitnessSql } from "./store.js";
|
|
38
|
+
import { processesInStrict } from "./stop-proof.js";
|
|
33
39
|
import { removeStaleTestTemp } from "./test-temp.js";
|
|
34
40
|
import { worktreeProcessOccupancy } from "./worktree.js";
|
|
35
41
|
import { runSweepChild } from "./storage-sweep-process.js";
|
|
@@ -80,8 +86,9 @@ export function liveWork(store, depsRoot, now, where) {
|
|
|
80
86
|
}
|
|
81
87
|
if (tasks.size === 0 && paths.length === 0)
|
|
82
88
|
return { live: false, paths: [], blind: false };
|
|
89
|
+
const local = localWitnessSql(db, "p");
|
|
83
90
|
const witnessed = db.prepare(`SELECT p.pid AS pid FROM run_process p JOIN run r ON r.id = p.run
|
|
84
|
-
WHERE r.finished_at IS NULL AND p.exited_at IS NULL AND p.pid IS NOT NULL AND
|
|
91
|
+
WHERE r.finished_at IS NULL AND p.exited_at IS NULL AND p.pid IS NOT NULL AND ${local.sql}`).all(...local.params);
|
|
85
92
|
for (const row of witnessed)
|
|
86
93
|
pids.add(Number(row["pid"]));
|
|
87
94
|
const from = pids.size === 0 ? [] : where([...pids].sort((a, b) => a - b));
|
|
@@ -181,7 +188,7 @@ export async function sweepStorage(store, deps, now, execute = runSweepChild) {
|
|
|
181
188
|
return saveSweep(store, record);
|
|
182
189
|
}
|
|
183
190
|
export function sweepKinds(withPool) {
|
|
184
|
-
return ["test temp", "orphans", ...(withPool ? ["checkouts"] : []), "runtimes", "dependencies"];
|
|
191
|
+
return ["test temp", "orphans", ...(withPool ? ["checkouts"] : []), "runtimes", "dependencies", "check history"];
|
|
185
192
|
}
|
|
186
193
|
/** Child side (also a focused unit-test seam): every scan and removal, without saving the parent's result. */
|
|
187
194
|
export async function executeSweep(store, deps, now) {
|
|
@@ -221,8 +228,15 @@ export async function executeSweep(store, deps, now) {
|
|
|
221
228
|
if (deps.pool !== null) {
|
|
222
229
|
const pool = deps.pool;
|
|
223
230
|
await part("checkouts", async () => {
|
|
224
|
-
|
|
225
|
-
|
|
231
|
+
// Idle folders go here only: nothing records a person's shell or editor in one, so the OS is asked. Unreadable keeps it.
|
|
232
|
+
const occupied = (path) => { try {
|
|
233
|
+
return (deps.cwds ?? processesInStrict)([path]).size > 0;
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
return true;
|
|
237
|
+
} };
|
|
238
|
+
const done = await cleanCheckouts(store, pool, now, { manual: false, actor, spare, idle: { occupied } });
|
|
239
|
+
return { count: done.removed.length, bytes: done.freed, failed: done.kept.filter(one => one.why === "git refused").length, ...(done.more ? { more: true } : {}), items: capped(done.removed.map(one => one.path)) };
|
|
226
240
|
});
|
|
227
241
|
}
|
|
228
242
|
const stateDir = dirname(deps.databaseFile);
|
|
@@ -244,6 +258,12 @@ export async function executeSweep(store, deps, now) {
|
|
|
244
258
|
const done = await pruneUnusedShared(root, checkouts, now(), SHARED_UNUSED_MS, { spare });
|
|
245
259
|
return { count: done.removed.length, bytes: done.removed.reduce((sum, one) => sum + (sizes.get(one.dir) ?? 0), 0), failed: done.failed.length, items: capped(done.removed.map(one => one.dir)) };
|
|
246
260
|
});
|
|
261
|
+
// Learning rows past their retention (docs/plans/learning-loops.md): the only DELETE on them. A lesson note's tallies
|
|
262
|
+
// are written with each check and never pruned, so what they say doesn't change here.
|
|
263
|
+
await part("check history", async () => {
|
|
264
|
+
const count = store.transact(() => pruneCheckResults(store, now()) + pruneObservations(store, now()) + pruneCiObservations(store, now()));
|
|
265
|
+
return { count, bytes: 0, failed: 0, items: [] };
|
|
266
|
+
});
|
|
247
267
|
return { at: now().toISOString(), source: "automatic", actor, parts };
|
|
248
268
|
}
|
|
249
269
|
/** Save what a sweep or a clean-up by hand did (the latest few are kept), with one ledger entry saying it. */
|
|
@@ -285,19 +305,20 @@ export function nextSweepAt(store) {
|
|
|
285
305
|
const NOUNS = {
|
|
286
306
|
"test temp": ["test temp folder", "test temp folders"],
|
|
287
307
|
orphans: ["leftover process", "leftover processes"],
|
|
288
|
-
checkouts: ["
|
|
308
|
+
checkouts: ["task folder", "task folders"],
|
|
289
309
|
runtimes: ["staged runtime", "staged runtimes"],
|
|
290
310
|
dependencies: ["unused dependency install", "unused dependency installs"],
|
|
311
|
+
"check history": ["old test result", "old test results"],
|
|
291
312
|
};
|
|
292
313
|
/** What a part looks at, for "Couldn't check …". */
|
|
293
|
-
const CHECKED = { "test temp": "test temp folders", orphans: "processes", checkouts: "
|
|
314
|
+
const CHECKED = { "test temp": "test temp folders", orphans: "processes", checkouts: "task folders", runtimes: "staged runtimes", dependencies: "dependency installs", "check history": "old test results" };
|
|
294
315
|
export function partWords(one) {
|
|
295
316
|
return `${one.count.toLocaleString("en-US")} ${NOUNS[one.kind][one.count === 1 ? 0 : 1]}`;
|
|
296
317
|
}
|
|
297
318
|
function list(items) {
|
|
298
319
|
return items.length <= 1 ? items.join("") : `${items.slice(0, -1).join(", ")} and ${items.at(-1)}`;
|
|
299
320
|
}
|
|
300
|
-
/** "Removed 120 test temp folders and 2
|
|
321
|
+
/** "Removed 120 test temp folders and 2 task folders, about 1.2 GB. Stopped 1 leftover process. 3 couldn't be removed." */
|
|
301
322
|
export function sweepWords(parts) {
|
|
302
323
|
const removed = parts.filter(one => one.kind !== "orphans" && one.count > 0);
|
|
303
324
|
const stopped = parts.find(one => one.kind === "orphans" && one.count > 0);
|
package/dist/storage-ui.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Settings → Storage: how much space task checkouts use, what a clean-up would free now, the
|
|
2
|
+
* Settings → Storage: how much space task folders (checkouts) use, what a clean-up would free now, what the next sweep's
|
|
3
|
+
* idle cleanup takes and when, the folders with uncommitted changes a person decides about, the clean-up's preview (what
|
|
3
4
|
* goes, what stays and why, with paths) and its Remove behind the password, what the last storage sweep removed and
|
|
4
5
|
* when (its paths in a disclosure) and why the next one waits, what landed work's teardown did and still waits on (in a
|
|
5
|
-
* disclosure, with the one-time command for older work), and when a
|
|
6
|
-
*
|
|
6
|
+
* disclosure, with the one-time command for older work), and when a task's clean folder goes by itself. An instance
|
|
7
|
+
* operator's page, built from the revamp kit; the Settings frame adds the title.
|
|
7
8
|
*/
|
|
8
9
|
import { homedir } from "node:os";
|
|
9
10
|
import { basename } from "node:path";
|
|
10
11
|
import { html, joinHtml } from "./html.js";
|
|
11
12
|
import { previewDigest, whyWords } from "./checkout-cleanup.js";
|
|
12
|
-
import { CLEANUP_CHOICES, bytesWords } from "./storage.js";
|
|
13
|
+
import { CLEANUP_CHOICES, IDLE_CHOICES, bytesWords, idleCleanupWords } from "./storage.js";
|
|
13
14
|
import { sweepDetails, sweepWords } from "./storage-sweep.js";
|
|
14
15
|
import { sweepScheduleWords } from "./storage-sweep-schedule.js";
|
|
15
16
|
import { codeLine, disclose, formRow, icon, infoLine, note, notice, select, settingsRecord, settingsSection } from "./server/kit.js";
|
|
@@ -48,17 +49,18 @@ function sweepHtml(sweep) {
|
|
|
48
49
|
/** Landed work, on demand: what its teardown did, what it waits on and why, and the one-time command for older work. */
|
|
49
50
|
function landedHtml(landed) {
|
|
50
51
|
const done = landed.completed + landed.checkouts + landed.branches === 0 ? "No landed work cleaned up yet."
|
|
51
|
-
: `${plural(landed.completed, "task")} marked complete; ${plural(landed.checkouts, "
|
|
52
|
+
: `${plural(landed.completed, "task")} marked complete; ${plural(landed.checkouts, "task folder")} and ${plural(landed.branches, "local branch", "local branches")} removed.`;
|
|
52
53
|
const waiting = landed.waiting.length === 0 ? "" : html `<div data-landed-waiting>${note(`Waiting (${landed.waiting.length}):`)}${landed.waiting.map(one => settingsRecord({ title: html `<span class="k-mono">${tilde(one.target)}</span>`, sub: one.why ?? "" }))}</div>`;
|
|
53
54
|
return html `<div data-landed>${disclose("Landed work", joinHtml([note(done), waiting,
|
|
54
55
|
note(html `For work that landed before this: <code class="k-mono">toolroll storage clean --landed</code> shows what would go; add <code class="k-mono">--yes</code> to do it.`)]))}</div>`;
|
|
55
56
|
}
|
|
56
|
-
/** One
|
|
57
|
+
/** One task folder: the task it was for, its size, where it is (the home folder as ~), why it stays, and Discard when it has changes. */
|
|
57
58
|
function item(one) {
|
|
58
|
-
|
|
59
|
+
// Under "Uncommitted changes" the heading already says why it stays.
|
|
60
|
+
const why = one.why === null || one.why === "has changes" ? "" : `${whyWords(one)[0].toUpperCase()}${whyWords(one).slice(1)}`;
|
|
59
61
|
const discard = one.why === "has changes"
|
|
60
62
|
? disclose("Discard", kitSectionForm({ proof: "storage-discard", action: "/settings/storage/discard", id: `discard-${previewId(one.path)}`, hidden: { path: one.path },
|
|
61
|
-
save: { label: "Discard changes", variant: "danger" }, body: note("Throws away this
|
|
63
|
+
save: { label: "Discard changes", variant: "danger" }, body: note("Throws away this folder and its uncommitted changes. Its branch stays.") }))
|
|
62
64
|
: "";
|
|
63
65
|
return settingsRecord({ title: one.taskId ?? basename(one.path), attrs: { "data-checkout": one.path }, when: bytesWords(one.bytes),
|
|
64
66
|
sub: html `<span class="k-mono">${tilde(one.path)}</span>${why === "" ? "" : ` · ${why}`}`, more: discard });
|
|
@@ -67,29 +69,38 @@ function item(one) {
|
|
|
67
69
|
const previewId = (path) => path.replace(/[^a-z0-9]+/gi, "-").replace(/^-|-$/g, "").slice(-48) || "checkout";
|
|
68
70
|
export function storageHtml(view, said) {
|
|
69
71
|
const { plan } = view;
|
|
70
|
-
const facts = [plural(plan.count, "
|
|
72
|
+
const facts = [plural(plan.count, "task folder"), `${plan.waitingReview} waiting for review`, `${plan.withChanges} with uncommitted changes`].join(" · ");
|
|
71
73
|
const frees = plan.go.length === 0 ? "Nothing to clean up now." : `Cleaning up now frees about ${bytesWords(plan.freeBytes)}.`;
|
|
72
|
-
const checkouts = settingsSection({ id: "checkouts", title: `
|
|
74
|
+
const checkouts = settingsSection({ id: "checkouts", title: `Task folders use ${bytesWords(plan.totalBytes)}`,
|
|
73
75
|
body: joinHtml([
|
|
74
76
|
said.problem ? notice(said.problem) : said.said ? infoLine(said.said) : "",
|
|
75
77
|
html `<p class="k-note" data-checkout-bytes="${plan.totalBytes}">${facts}</p>`,
|
|
76
78
|
view.measured ?? "",
|
|
77
79
|
html `<p class="k-note" data-clean-bytes="${plan.freeBytes}">${frees}</p>`,
|
|
80
|
+
html `<p class="k-note" data-idle-bytes="${plan.idleBytes}">${idleCleanupWords(plan)}</p>`,
|
|
78
81
|
view.sweep === undefined ? "" : sweepHtml(view.sweep),
|
|
79
82
|
view.landed === undefined ? "" : landedHtml(view.landed),
|
|
80
83
|
]) });
|
|
81
84
|
const remove = plan.go.length === 0 ? "" : kitSectionForm({ proof: "storage-clean", action: "/settings/storage/clean", id: "clean-form", hidden: { preview: previewDigest(plan) },
|
|
82
|
-
save: { label: `Remove ${plural(plan.go.length, "
|
|
85
|
+
save: { label: `Remove ${plural(plan.go.length, "task folder")}`, variant: "danger" }, body: html `` });
|
|
83
86
|
const goes = plan.go.length === 0 ? note("Nothing to remove now.")
|
|
84
87
|
: html `<div data-goes>${plan.go.map(one => item(one))}</div>`;
|
|
85
|
-
const
|
|
88
|
+
const changed = plan.stay.filter(one => one.why === "has changes");
|
|
89
|
+
const others = plan.stay.filter(one => one.why !== "has changes");
|
|
90
|
+
const stays = others.length === 0 ? "" : disclose(`Stays (${others.length})`, html `<div data-stays>${others.map(one => item(one))}</div>`);
|
|
91
|
+
// Never removed by itself: a person looks at each and keeps it or discards it.
|
|
92
|
+
const changes = changed.length === 0 ? "" : settingsSection({ id: "uncommitted", title: `Uncommitted changes (${changed.length})`,
|
|
93
|
+
lead: `${changed.length === 1 ? "This folder has" : "These folders have"} work that isn't on any branch, about ${bytesWords(changed.reduce((sum, one) => sum + one.bytes, 0))}. Toolroll never removes ${changed.length === 1 ? "it" : "them"} by itself.`,
|
|
94
|
+
body: html `<div data-uncommitted>${changed.map(one => item(one))}</div>` });
|
|
86
95
|
const cleanUp = settingsSection({ id: "clean-up", title: "Clean up now",
|
|
87
|
-
lead: plan.go.length === 0 ? "Nothing to remove now." : `Removes ${plural(plan.go.length, "
|
|
96
|
+
lead: plan.go.length === 0 ? "Nothing to remove now." : `Removes ${plural(plan.go.length, "task folder")}, about ${bytesWords(plan.freeBytes)}. Their branches stay, so nothing committed is lost.`,
|
|
88
97
|
body: html `<div data-clean-up>${joinHtml([plan.go.length === 0 ? "" : goes, remove, stays])}</div>` });
|
|
89
98
|
const automatic = settingsSection({ id: "automatic-cleanup", title: "Automatic cleanup",
|
|
90
|
-
lead: "
|
|
99
|
+
lead: "Branches stay, so a later run, revision or retry makes a new folder. Folders with uncommitted changes, commits on no branch, or in use are kept. When a task's work is already in main, Toolroll marks it complete and removes its folder and local branches.",
|
|
91
100
|
body: kitSectionForm({ proof: "storage-cleanup", action: "/settings/storage", id: "cleanup-form", gate: true,
|
|
92
|
-
body: formRow("Remove a finished task's clean
|
|
93
|
-
|
|
94
|
-
|
|
101
|
+
body: joinHtml([formRow("Remove a finished task's clean folder", null, select({ id: "storage-cleanup", name: "cleanup", label: "Remove a finished task's clean folder", current: plan.cleanup,
|
|
102
|
+
items: CLEANUP_CHOICES.map(one => [one.value, one.label]) }), "inline"),
|
|
103
|
+
formRow("Remove an idle task's clean folder", "Even with its result waiting for review or the task unfinished. Idle means no run, no one acting on it and no open lease.", select({ id: "storage-idle", name: "idle", label: "Remove an idle task's clean folder", current: plan.idle,
|
|
104
|
+
items: IDLE_CHOICES.map(one => [one.value, one.label]) }), "inline")]) }) });
|
|
105
|
+
return joinHtml([checkouts, changes, cleanUp, automatic]);
|
|
95
106
|
}
|
package/dist/storage.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Store } from "./store.js";
|
|
2
|
-
import { type CheckoutCleanup } from "./contracts/storage.js";
|
|
2
|
+
import { type CheckoutCleanup, type CheckoutIdle } from "./contracts/storage.js";
|
|
3
3
|
/** Settings → Storage: when a finished task's clean checkout is removed. One row, or none (the default). */
|
|
4
4
|
export declare const CHECKOUT_CLEANUP_SCHEMA = "\nCREATE TABLE IF NOT EXISTS checkout_cleanup_setting (\n id INTEGER PRIMARY KEY CHECK (id = 1),\n cleanup TEXT NOT NULL CHECK (cleanup IN ('finished', '2d', '7d', 'never')),\n updated_by TEXT NOT NULL,\n updated_at TEXT NOT NULL\n);\n";
|
|
5
5
|
/**
|
|
@@ -10,7 +10,7 @@ export declare const CHECKOUT_CLEANUP_SCHEMA = "\nCREATE TABLE IF NOT EXISTS che
|
|
|
10
10
|
export declare const LANDED_CLEANUP_SCHEMA = "\nCREATE TABLE IF NOT EXISTS landed_cleanup (\n id INTEGER PRIMARY KEY,\n repo TEXT NOT NULL,\n task_id TEXT,\n run_id INTEGER,\n receipt TEXT,\n head TEXT NOT NULL,\n how TEXT NOT NULL CHECK (how IN ('merged', 'in-main', 'content-in-main')),\n reason TEXT NOT NULL,\n completed INTEGER NOT NULL DEFAULT 0 CHECK (completed IN (0, 1)),\n actor TEXT NOT NULL,\n created_at TEXT NOT NULL,\n finished_at TEXT\n);\nCREATE UNIQUE INDEX IF NOT EXISTS landed_cleanup_result ON landed_cleanup(run_id) WHERE run_id IS NOT NULL;\nCREATE TABLE IF NOT EXISTS landed_cleanup_item (\n id INTEGER PRIMARY KEY,\n cleanup INTEGER NOT NULL REFERENCES landed_cleanup(id) ON DELETE CASCADE,\n kind TEXT NOT NULL CHECK (kind IN ('checkout', 'branch')),\n repo TEXT NOT NULL,\n target TEXT NOT NULL,\n expect TEXT NOT NULL,\n state TEXT NOT NULL DEFAULT 'pending' CHECK (state IN ('pending', 'done', 'kept')),\n why TEXT,\n done_at TEXT\n);\nCREATE UNIQUE INDEX IF NOT EXISTS landed_cleanup_pending ON landed_cleanup_item(kind, repo, target) WHERE state = 'pending';\nCREATE INDEX IF NOT EXISTS landed_cleanup_item_of ON landed_cleanup_item(cleanup, state);\n";
|
|
11
11
|
/** What each storage sweep or clean-up by hand did (storage-sweep.ts): the latest few. */
|
|
12
12
|
export declare const STORAGE_SWEEP_SCHEMA = "\nCREATE TABLE IF NOT EXISTS storage_sweep (\n id INTEGER PRIMARY KEY,\n at TEXT NOT NULL,\n source TEXT NOT NULL CHECK (source IN ('automatic', 'manual')),\n actor TEXT NOT NULL,\n result TEXT NOT NULL\n);\n";
|
|
13
|
-
export type { CheckoutCleanup } from "./contracts/storage.js";
|
|
13
|
+
export type { CheckoutCleanup, CheckoutIdle } from "./contracts/storage.js";
|
|
14
14
|
export declare const DEFAULT_CLEANUP: CheckoutCleanup;
|
|
15
15
|
export declare const CLEANUP_CHOICES: readonly {
|
|
16
16
|
value: CheckoutCleanup;
|
|
@@ -20,6 +20,31 @@ export declare const CLEANUP_CHOICES: readonly {
|
|
|
20
20
|
/** `finished` (or complete, cancelled), `2d`, `7d` (or week, 1w), `never` (or off); undefined when it's none of those. */
|
|
21
21
|
export declare function parseCleanup(text: string): CheckoutCleanup | undefined;
|
|
22
22
|
export declare function cleanupWords(cleanup: CheckoutCleanup): string;
|
|
23
|
+
/**
|
|
24
|
+
* v151: how long a task stays idle (no run, no person's act, no open lease) before its clean folder goes, even with its
|
|
25
|
+
* result waiting for review or the task unfinished (checkout-cleanup.ts). Saved rows read NULL: the default.
|
|
26
|
+
*/
|
|
27
|
+
export declare const V151_ADDED_COLUMNS: Readonly<Record<string, Readonly<Record<string, {
|
|
28
|
+
definition: string;
|
|
29
|
+
saved: string;
|
|
30
|
+
}>>>>;
|
|
31
|
+
export declare const DEFAULT_IDLE: CheckoutIdle;
|
|
32
|
+
export declare const IDLE_CHOICES: readonly {
|
|
33
|
+
value: CheckoutIdle;
|
|
34
|
+
label: string;
|
|
35
|
+
days: number | null;
|
|
36
|
+
}[];
|
|
37
|
+
/** `7`, `14d`, `30 days`, `never` (or off); undefined when it's none of those. */
|
|
38
|
+
export declare function parseIdle(text: string): CheckoutIdle | undefined;
|
|
39
|
+
/** After "A task's clean folder is removed ". */
|
|
40
|
+
export declare function idleWords(idle: CheckoutIdle): string;
|
|
41
|
+
/** What the next sweep's idle cleanup takes, for `toolroll storage` and Settings → Storage. The sweep's own line says when. */
|
|
42
|
+
export declare function idleCleanupWords(plan: {
|
|
43
|
+
cleanup: CheckoutCleanup;
|
|
44
|
+
idle: CheckoutIdle;
|
|
45
|
+
idleCount: number;
|
|
46
|
+
idleBytes: number;
|
|
47
|
+
}): string;
|
|
23
48
|
/** Bytes each path holds on disk: one `du` for them all (fast on a 300 MB checkout), a walk where it can't answer. */
|
|
24
49
|
export declare function diskBytes(paths: readonly string[]): Map<string, number>;
|
|
25
50
|
export type StorageLine = {
|
package/dist/storage.js
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
* Build checkouts and staged releases are the big ones; the worker removes a
|
|
5
5
|
* finished task's clean checkout as the checkout cleanup setting says
|
|
6
6
|
* (checkout-cleanup.ts), and the daily storage sweep (storage-sweep.ts) takes
|
|
7
|
-
* what slips through: stale test temp folders, leftover processes, extra
|
|
7
|
+
* idle tasks' clean checkouts and what slips through: stale test temp folders, leftover processes, extra
|
|
8
8
|
* staged runtimes and dependency installs unused for a week.
|
|
9
9
|
*/
|
|
10
10
|
import { execFile, spawnSync } from "node:child_process";
|
|
11
11
|
import { lstatSync, readdirSync } from "node:fs";
|
|
12
12
|
import { basename, dirname, join } from "node:path";
|
|
13
13
|
import { DEPS_FOLDER, sharedCopies } from "./shared-deps.js";
|
|
14
|
-
import { checkoutCleanupSchema } from "./contracts/storage.js";
|
|
14
|
+
import { checkoutCleanupSchema, checkoutIdleSchema } from "./contracts/storage.js";
|
|
15
15
|
/** Settings → Storage: when a finished task's clean checkout is removed. One row, or none (the default). */
|
|
16
16
|
export const CHECKOUT_CLEANUP_SCHEMA = `
|
|
17
17
|
CREATE TABLE IF NOT EXISTS checkout_cleanup_setting (
|
|
@@ -93,6 +93,43 @@ export function cleanupWords(cleanup) {
|
|
|
93
93
|
: cleanup === "never" ? "never (only when you clean up)"
|
|
94
94
|
: `${cleanup === "2d" ? "2 days" : "a week"} after its task is complete or cancelled`;
|
|
95
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* v151: how long a task stays idle (no run, no person's act, no open lease) before its clean folder goes, even with its
|
|
98
|
+
* result waiting for review or the task unfinished (checkout-cleanup.ts). Saved rows read NULL: the default.
|
|
99
|
+
*/
|
|
100
|
+
export const V151_ADDED_COLUMNS = Object.freeze({
|
|
101
|
+
checkout_cleanup_setting: { idle: { definition: "TEXT CHECK (idle IN ('7d', '14d', '30d', 'never'))", saved: "NULL" } },
|
|
102
|
+
});
|
|
103
|
+
export const DEFAULT_IDLE = "14d";
|
|
104
|
+
export const IDLE_CHOICES = [
|
|
105
|
+
{ value: "7d", label: "After 7 days idle", days: 7 },
|
|
106
|
+
{ value: "14d", label: "After 14 days idle", days: 14 },
|
|
107
|
+
{ value: "30d", label: "After 30 days idle", days: 30 },
|
|
108
|
+
{ value: "never", label: "Never", days: null },
|
|
109
|
+
];
|
|
110
|
+
/** `7`, `14d`, `30 days`, `never` (or off); undefined when it's none of those. */
|
|
111
|
+
export function parseIdle(text) {
|
|
112
|
+
const value = text.trim().toLowerCase().replace(/\s+/g, " ");
|
|
113
|
+
if (value === "off")
|
|
114
|
+
return "never";
|
|
115
|
+
const days = /^(\d+)( ?d| days?)?$/.exec(value)?.[1];
|
|
116
|
+
const parsed = checkoutIdleSchema.safeParse(days === undefined ? value : `${days}d`);
|
|
117
|
+
return parsed.success ? parsed.data : undefined;
|
|
118
|
+
}
|
|
119
|
+
/** After "A task's clean folder is removed ". */
|
|
120
|
+
export function idleWords(idle) {
|
|
121
|
+
const days = IDLE_CHOICES.find(one => one.value === idle).days;
|
|
122
|
+
return days === null ? "never for being idle" : `after ${days} days idle`;
|
|
123
|
+
}
|
|
124
|
+
/** What the next sweep's idle cleanup takes, for `toolroll storage` and Settings → Storage. The sweep's own line says when. */
|
|
125
|
+
export function idleCleanupWords(plan) {
|
|
126
|
+
if (plan.cleanup === "never" || plan.idle === "never")
|
|
127
|
+
return "Idle cleanup is off.";
|
|
128
|
+
const limit = `${idleWords(plan.idle)[0].toUpperCase()}${idleWords(plan.idle).slice(1)}`;
|
|
129
|
+
if (plan.idleCount === 0)
|
|
130
|
+
return `${limit}, a task's clean folder goes. None are due.`;
|
|
131
|
+
return `${limit}, a task's clean folder goes: the next sweep frees about ${bytesWords(plan.idleBytes)} (${plan.idleCount} folder${plan.idleCount === 1 ? "" : "s"}).`;
|
|
132
|
+
}
|
|
96
133
|
/** Bytes each path holds on disk: one `du` for them all (fast on a 300 MB checkout), a walk where it can't answer. */
|
|
97
134
|
export function diskBytes(paths) {
|
|
98
135
|
const sizes = new Map();
|
package/dist/store.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { type ProjectSandbox } from "./project-sandbox.js";
|
|
|
5
5
|
import type { FencePolicy } from "./agent-fence.js";
|
|
6
6
|
import { type NetworkProfile, type ProjectNetwork, type RunNetworkRecord } from "./egress.js";
|
|
7
7
|
import { type AcpLaunch } from "./acp-attest.js";
|
|
8
|
+
import { type CheckStamp, type StampReading } from "./check-stamp.js";
|
|
8
9
|
export { refArchived, taskArchived } from "./task-archive.js";
|
|
9
10
|
import { type ChatBinding } from "./chat-core.js";
|
|
10
11
|
import { ChatMessages, TELEGRAM_HOLD_REASONS, TELEGRAM_SKIPPED_ELSEWHERE, TELEGRAM_SKIPPED_OTHER_CHAT, TELEGRAM_SKIPPED_QUIET } from "./chat-messages.js";
|
|
@@ -16,6 +17,7 @@ import type { FlowCardChange, FlowCardFields } from "./contracts/flow-card.js";
|
|
|
16
17
|
import { type Actor } from "./actor.js";
|
|
17
18
|
import { type LiveActivityKind } from "./live.js";
|
|
18
19
|
import { type CredentialKind, type TokenPurpose } from "./api-tokens.js";
|
|
20
|
+
import { type ThisMachine } from "./machine-identity.js";
|
|
19
21
|
import { type CriterionMatrixRow, type CriterionJudgementWord } from "./proof.js";
|
|
20
22
|
import { type Authority, type LocalMode } from "./local-owner.js";
|
|
21
23
|
import { type ScopeAuthor, type ExecutionProfile, type UnattendedPermissionMode, type AcceptanceCriterion } from "./scope.js";
|
|
@@ -29,6 +31,7 @@ import type { Scope } from "./scope.js";
|
|
|
29
31
|
import type { QualityMode } from "./quality.js";
|
|
30
32
|
import type { ProgressSnapshot } from "./plan.js";
|
|
31
33
|
import { type CheckProgressSnapshot } from "./check-progress.js";
|
|
34
|
+
export { V153_ADDED_COLUMNS } from "./plain-schema.js";
|
|
32
35
|
import { type ReleaseCheckPurpose, type ReleaseTerms } from "./contracts/release-terms.js";
|
|
33
36
|
import { type ProjectAccess } from "./project-access.js";
|
|
34
37
|
import { type LedgerEntry } from "./action-ledger.js";
|
|
@@ -37,7 +40,7 @@ import { underCeiling, type OrgPolicy, type SavedPolicy } from "./policy.js";
|
|
|
37
40
|
import { type ReviewSwitch } from "./review-switch.js";
|
|
38
41
|
import { type Billing, type Budget, type BudgetAgent, type BudgetHold, type BudgetScope, type BudgetState, type SpendItem } from "./spend.js";
|
|
39
42
|
import { type RetentionKind, type RetentionPeriods } from "./retention.js";
|
|
40
|
-
import { type CheckoutCleanup } from "./storage.js";
|
|
43
|
+
import { type CheckoutCleanup, type CheckoutIdle } from "./storage.js";
|
|
41
44
|
import { type LedgerChainReport } from "./ledger-chain.js";
|
|
42
45
|
import { type ApprovalGate, type ApprovalRules, type ApproverKind, type Filer } from "./approval-policy.js";
|
|
43
46
|
import { type ReportedAmount } from "./reported-usage.js";
|
|
@@ -243,7 +246,7 @@ export type SealedLedgerEntry = LedgerEntry & {
|
|
|
243
246
|
hash: string;
|
|
244
247
|
} | null;
|
|
245
248
|
};
|
|
246
|
-
export declare const SCHEMA_VERSION =
|
|
249
|
+
export declare const SCHEMA_VERSION = 154;
|
|
247
250
|
/** v118: the tables merged into `credential`, in the order they move (a person's API tokens keep their ids first). */
|
|
248
251
|
export declare const V118_MERGED_TABLES: Readonly<Record<string, CredentialKind>>;
|
|
249
252
|
/**
|
|
@@ -435,7 +438,7 @@ export declare const V147_ADDED_COLUMNS: Readonly<Record<string, Readonly<Record
|
|
|
435
438
|
}>>>>;
|
|
436
439
|
/** The v147 migration's steps after the epoch, in order: a test seam (OpenOptions.migrationStep) may stop after each. */
|
|
437
440
|
export declare const V147_STEPS: readonly ["epoch", "local-mode", "password-set", "web-session", "console-instance"];
|
|
438
|
-
export type MigrationStep = (typeof V147_STEPS)[number] | (typeof V148_STEPS)[number] | (typeof V149_STEPS)[number];
|
|
441
|
+
export type MigrationStep = (typeof V147_STEPS)[number] | (typeof V148_STEPS)[number] | (typeof V149_STEPS)[number] | (typeof V150_STEPS)[number] | (typeof V151_STEPS)[number] | (typeof V152_STEPS)[number] | (typeof V153_STEPS)[number] | (typeof V154_STEPS)[number];
|
|
439
442
|
/**
|
|
440
443
|
* v148 (docs/plans/results-routing.md): every table the results-routing arc uses, landed at once because a current
|
|
441
444
|
* store runs no DDL. Slice 1 writes run_cohort, route_record and models_generation; route_rule, route_exposure* and
|
|
@@ -494,6 +497,33 @@ export declare const V149_TABLES: readonly ["check_result", "check_fact", "local
|
|
|
494
497
|
export declare const V149_REPO_TABLES: readonly ["check_fact", "local_observation", "ci_flaky_observation", "learning_watch", "lesson_note", "memory_use", "memory_tidy_proposal"];
|
|
495
498
|
/** The v149 migration's steps after v148's, in order (OpenOptions.migrationStep may stop after each). */
|
|
496
499
|
export declare const V149_STEPS: readonly ["learning-tables"];
|
|
500
|
+
/**
|
|
501
|
+
* v150 (machine-identity.ts): which computer a process witness belongs to, by the machine's stable id rather than its
|
|
502
|
+
* network hostname (which follows the network on a Mac with no HostName set). machine_host_alias records each
|
|
503
|
+
* hostname this machine read while it knew its own id, written only by a process on that machine, beside the id it
|
|
504
|
+
* read; a witness with no id (every saved one) is this machine's when its host is one of them. Never read for any
|
|
505
|
+
* other machine's id.
|
|
506
|
+
*/
|
|
507
|
+
export declare const V150_SCHEMA = "\nCREATE TABLE IF NOT EXISTS machine_host_alias (\n machine_id TEXT NOT NULL CHECK (length(machine_id) = 32),\n host TEXT NOT NULL CHECK (host <> ''),\n first_seen_at TEXT NOT NULL,\n PRIMARY KEY (machine_id, host)\n);\n";
|
|
508
|
+
/** The tables V150_SCHEMA adds. */
|
|
509
|
+
export declare const V150_TABLES: readonly ["machine_host_alias"];
|
|
510
|
+
/** v150: a process witness's machine id (saved witnesses read NULL: matched by host name, as before). */
|
|
511
|
+
export declare const V150_ADDED_COLUMNS: Readonly<Record<string, Readonly<Record<string, {
|
|
512
|
+
definition: string;
|
|
513
|
+
saved: string;
|
|
514
|
+
}>>>>;
|
|
515
|
+
/** The v150 migration's steps after v149's, in order (OpenOptions.migrationStep may stop after each). */
|
|
516
|
+
export declare const V150_STEPS: readonly ["machine-aliases", "witness-machine"];
|
|
517
|
+
/** The v151 migration's steps after v150's, in order (OpenOptions.migrationStep may stop after each). */
|
|
518
|
+
export declare const V151_STEPS: readonly ["checkout-idle"];
|
|
519
|
+
/** The v152 migration's steps after v151's, in order (OpenOptions.migrationStep may stop after each). */
|
|
520
|
+
export declare const V152_STEPS: readonly ["code-map-tables", "code-map-switch", "code-map-watermark"];
|
|
521
|
+
/** v153 (plain-toolroll.md Phase 2): the tables each slice owns. */
|
|
522
|
+
export declare const V153_SCHEMA: string;
|
|
523
|
+
/** The v153 migration's steps after v152's, in order (OpenOptions.migrationStep may stop after each). */
|
|
524
|
+
export declare const V153_STEPS: readonly ["plain-tables", "plain-columns"];
|
|
525
|
+
/** The v154 migration's steps after v153's, in order (OpenOptions.migrationStep may stop after each). */
|
|
526
|
+
export declare const V154_STEPS: readonly ["check-stamps"];
|
|
497
527
|
/**
|
|
498
528
|
* v135: one approval per release (docs/plans/release-once.md). `release_authority` is the single source of truth for a
|
|
499
529
|
* release a person approved: one row per generation, changed only by compare-and-swap on `rev`, with a lease whose
|
|
@@ -7425,7 +7455,8 @@ export declare class Store {
|
|
|
7425
7455
|
stampRunCheckAccess(runId: number, level: CheckAccess, network?: NetworkProfile | null): void;
|
|
7426
7456
|
/** The network a run's sign-ins checks ran under (null: none sealed; such a run's later checks run sandboxed). */
|
|
7427
7457
|
runCheckNetwork(runId: number): NetworkProfile | null;
|
|
7428
|
-
/** The level a run
|
|
7458
|
+
/** The level a later check of this run may run at, at most: missing or unreadable reads as sandboxed, the narrowest.
|
|
7459
|
+
* Never an account of how its checks DID run: that is runCheckStamp (check-stamp.ts). */
|
|
7429
7460
|
runCheckAccess(runId: number): CheckAccess;
|
|
7430
7461
|
/** A project's check access changed: saved, and in the ledger as before → after. `may` is what the caller proved:
|
|
7431
7462
|
* "widen" only after an instance operator's password typed now; anything else may only narrow. The level is read again
|
|
@@ -8291,6 +8322,12 @@ export type ReleaseProgress = {
|
|
|
8291
8322
|
message: string;
|
|
8292
8323
|
at: string;
|
|
8293
8324
|
} | null;
|
|
8325
|
+
/** The release's task moved on since its last recorded failure: this build started after it and is running now, so
|
|
8326
|
+
* the release isn't stopped there, even though `toolroll release` hasn't recorded a step since. */
|
|
8327
|
+
building: {
|
|
8328
|
+
run: number;
|
|
8329
|
+
startedAt: string;
|
|
8330
|
+
} | null;
|
|
8294
8331
|
};
|
|
8295
8332
|
/** What a release coordinator says about one step. */
|
|
8296
8333
|
export type ReleaseStepEntry = {
|
|
@@ -8310,7 +8347,8 @@ export type ReleaseStepEntry = {
|
|
|
8310
8347
|
*/
|
|
8311
8348
|
export declare function recordReleaseStep(store: Store, entry: ReleaseStepEntry): void;
|
|
8312
8349
|
/** A release's steps, for the release task: the newest tag recorded for it, its steps in the order they ran, and the
|
|
8313
|
-
* step it stopped at when the last thing recorded was a failure
|
|
8350
|
+
* step it stopped at when the last thing recorded was a failure and no build of the task has started since. Null: no
|
|
8351
|
+
* step recorded. */
|
|
8314
8352
|
export declare function releaseProgressFor(store: Store, taskId: string): ReleaseProgress | null;
|
|
8315
8353
|
/** The release task whose steps were recorded most recently (v146), or null when none was. */
|
|
8316
8354
|
export declare function latestReleaseStepTask(store: Store): string | null;
|
|
@@ -8439,3 +8477,40 @@ export declare function settleRunStopByObservation(store: Store, args: {
|
|
|
8439
8477
|
cause: StopProofCause;
|
|
8440
8478
|
evidence: string;
|
|
8441
8479
|
}, now: Date): boolean;
|
|
8480
|
+
/**
|
|
8481
|
+
* This machine as the store knows it (v150, machine-identity.ts): its stable id and every name it is known by, the
|
|
8482
|
+
* hostnames recorded beside its own id included. Read fresh each time, so a name recorded by another process on this
|
|
8483
|
+
* machine counts at once.
|
|
8484
|
+
*/
|
|
8485
|
+
export declare function storeMachine(db: Database): ThisMachine;
|
|
8486
|
+
/** Load this machine's recorded names for the checks that hold no store; a store that can't read them loads none. */
|
|
8487
|
+
export declare function rememberStoredHostAliases(db: Database): void;
|
|
8488
|
+
/** Record the names this machine is known by right now beside its own id, read here at this moment (never a name a
|
|
8489
|
+
* witness, a note or another computer supplied). Nothing when this machine's id is unknown. */
|
|
8490
|
+
export declare function recordMachineHostAliases(db: Database, now: Date): void;
|
|
8491
|
+
/** Whether a run_process row is this machine's (machine-identity.ts isThisMachine). */
|
|
8492
|
+
export declare function witnessIsLocal(row: Record<string, unknown>, here: ThisMachine): boolean;
|
|
8493
|
+
/** The same rule as witnessIsLocal, as SQL over run_process (optionally aliased): its id is ours, or it has none and
|
|
8494
|
+
* its host is one of our names. With our id unknown, `machine_id = NULL` matches nothing: an id-bearing witness is
|
|
8495
|
+
* never taken for ours. */
|
|
8496
|
+
export declare function localWitnessSql(db: Database, alias?: string): {
|
|
8497
|
+
sql: string;
|
|
8498
|
+
params: (string | null)[];
|
|
8499
|
+
};
|
|
8500
|
+
/** Whether this database has v150's run_process.machine_id. An update settles leftover records on the database it is
|
|
8501
|
+
* about to back up, still at the older schema (toolroll-update.ts drain), so no query it reaches may assume the column:
|
|
8502
|
+
* there every witness has no id, and the host-name rule above is the whole rule. */
|
|
8503
|
+
export declare function witnessesCarryMachine(db: Database): boolean;
|
|
8504
|
+
/** Settings → Storage: how long a task stays idle before its clean folder goes (the default until someone chooses). */
|
|
8505
|
+
export declare function checkoutIdle(store: Store): CheckoutIdle;
|
|
8506
|
+
/** Choose how long a task stays idle before its clean folder goes; the ledger keeps before → after. */
|
|
8507
|
+
export declare function setCheckoutIdle(store: Store, idle: CheckoutIdle, by: string, now: Date): CheckoutIdle;
|
|
8508
|
+
/** A check launch's execution stamp (check-stamp.ts), written by the code that launched it. One row per distinct launch. */
|
|
8509
|
+
export declare function recordCheckStamp(store: Store, runId: number, stamp: CheckStamp, now: Date): void;
|
|
8510
|
+
/** How a run's checks actually ran, by every launch it stamped: `unknown` when none is there or one can't be read. */
|
|
8511
|
+
export declare function runCheckStamp(store: Store, runId: number): StampReading;
|
|
8512
|
+
/** The newest run of a task whose checks ran (it saved a check log), and how they ran; null when none has. */
|
|
8513
|
+
export declare function taskCheckStamp(store: Store, repo: string, taskId: string): {
|
|
8514
|
+
runId: number;
|
|
8515
|
+
reading: StampReading;
|
|
8516
|
+
} | null;
|