toolroll 0.9.31 → 0.9.33
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 +30 -0
- package/dist/assignment.d.ts +3 -0
- package/dist/assignment.js +8 -2
- package/dist/batch-checks.d.ts +15 -0
- package/dist/batch-checks.js +244 -0
- package/dist/batch-policy.d.ts +23 -0
- package/dist/batch-policy.js +25 -0
- package/dist/browser/workspace.js +13 -13
- package/dist/builder.js +14 -3
- package/dist/chat-actions.js +2 -2
- package/dist/check-levels-ui.d.ts +4 -0
- package/dist/check-levels-ui.js +21 -2
- package/dist/checkout-cleanup.d.ts +2 -1
- package/dist/checkout-cleanup.js +24 -8
- package/dist/coding-context.js +2 -2
- package/dist/coding-handoff.js +2 -2
- package/dist/desktop-update.d.ts +1 -1
- package/dist/exec.d.ts +2 -0
- package/dist/exec.js +11 -0
- package/dist/guides.js +3 -0
- package/dist/knowledge-cli.d.ts +31 -0
- package/dist/knowledge-cli.js +35 -2
- package/dist/knowledge-ui.d.ts +1 -1
- package/dist/knowledge-ui.js +7 -0
- package/dist/lead-capabilities.d.ts +0 -7
- package/dist/lead-capabilities.js +11 -1
- package/dist/mate-doors.js +2 -2
- package/dist/mate.d.ts +6 -2
- package/dist/mate.js +63 -38
- package/dist/never-index.d.ts +3 -0
- package/dist/never-index.js +21 -0
- package/dist/operate.js +99 -5
- package/dist/orphan-sweep.d.ts +63 -0
- package/dist/orphan-sweep.js +235 -0
- package/dist/project-delete.js +1 -1
- package/dist/project-knowledge.d.ts +28 -3
- package/dist/project-knowledge.js +64 -25
- package/dist/project-learning.d.ts +33 -0
- package/dist/project-learning.js +105 -15
- package/dist/project-skills.js +27 -21
- package/dist/result-follow-ups.d.ts +22 -2
- package/dist/result-follow-ups.js +44 -10
- package/dist/serve.js +33 -11
- package/dist/shared-deps.d.ts +18 -2
- package/dist/shared-deps.js +129 -15
- package/dist/staged-runtimes.d.ts +42 -0
- package/dist/staged-runtimes.js +191 -0
- package/dist/storage-sweep.d.ts +50 -0
- package/dist/storage-sweep.js +203 -0
- package/dist/storage-ui.d.ts +5 -0
- package/dist/storage-ui.js +30 -4
- package/dist/storage.d.ts +2 -0
- package/dist/storage.js +15 -3
- package/dist/store.d.ts +17 -2
- package/dist/store.js +37 -5
- package/dist/subscription-chat.d.ts +3 -1
- package/dist/subscription-chat.js +1 -0
- package/dist/surface.js +2 -1
- package/dist/task-status.d.ts +13 -0
- package/dist/task-status.js +37 -3
- package/dist/team-leads.js +6 -2
- package/dist/team-runtime.js +2 -1
- package/dist/test-temp.d.ts +20 -2
- package/dist/test-temp.js +53 -13
- package/dist/toolroll-update.js +2 -0
- package/dist/workspace-ui.js +3 -1
- package/dist/worktree.js +9 -0
- package/package.json +1 -1
package/dist/task-status.js
CHANGED
|
@@ -26,6 +26,21 @@ export const CHECKS_LABEL = "Project checks";
|
|
|
26
26
|
export const PR_CI_LABEL = "PR CI";
|
|
27
27
|
/** The one verb for opening a saved result, on every surface. */
|
|
28
28
|
export const OPEN_RESULT = "Open result";
|
|
29
|
+
/** "T2", "T2 and T3", "T2, T3 and 2 more": short enough for one line. The full list is on the result. */
|
|
30
|
+
export function peerWords(peers, shown = 2) {
|
|
31
|
+
if (peers.length <= shown)
|
|
32
|
+
return peers.length <= 1 ? peers.join("") : `${peers.slice(0, -1).join(", ")} and ${peers.at(-1)}`;
|
|
33
|
+
return `${peers.slice(0, shown).join(", ")} and ${peers.length - shown} more`;
|
|
34
|
+
}
|
|
35
|
+
/** "Checked together with T2 on 1a2b3c4": every surface says a shared check the same way, naming the commit it tested. */
|
|
36
|
+
export function checkedTogetherWords(batch) {
|
|
37
|
+
const sha = short(batch.tested);
|
|
38
|
+
return batch.state === "together" && batch.peers.length > 0 && sha !== null ? `Checked together with ${peerWords(batch.peers)} on ${sha}` : null;
|
|
39
|
+
}
|
|
40
|
+
const BATCH_WHY = {
|
|
41
|
+
split: "Its batch check failed, so it was checked on its own.",
|
|
42
|
+
conflict: "It conflicted with another result when merged for a batch check, so it was checked on its own.",
|
|
43
|
+
};
|
|
29
44
|
const short = (sha) => sha !== null && /^[a-f0-9]{7,40}$/.test(sha) ? sha.slice(0, 7) : null;
|
|
30
45
|
export function headlineOf(facts) {
|
|
31
46
|
switch (facts.stage) {
|
|
@@ -64,8 +79,15 @@ function sentenceOf(headline, facts) {
|
|
|
64
79
|
return "The report is ready to read. Read it, then mark it complete.";
|
|
65
80
|
if (facts.checks?.level === "off" && facts.checks.status !== "passed")
|
|
66
81
|
return "Checks are off for this project. Review the change, then mark it complete.";
|
|
67
|
-
if (facts.checks?.status === "passed")
|
|
82
|
+
if (facts.checks?.status === "passed") {
|
|
83
|
+
// The Checks row names the others and the batch commit; the sentence doesn't repeat them.
|
|
84
|
+
const peers = facts.checks.batch?.state === "together" ? facts.checks.batch.peers.length : 0;
|
|
85
|
+
if (peers > 0)
|
|
86
|
+
return `Checks passed together with ${peers === 1 ? "1 other result" : `${peers} other results`}. Review the change, then mark it complete.`;
|
|
68
87
|
return `${facts.checks.level === "quick" ? "Quick checks" : "Checks"} passed${sha === null ? "" : ` on ${sha}`}. Review the change, then mark it complete.`;
|
|
88
|
+
}
|
|
89
|
+
if (facts.checks?.batch?.state === "waiting")
|
|
90
|
+
return "Its full check runs with other results within 10 minutes. Review the change, then mark it complete.";
|
|
69
91
|
// Never claim a check that isn't known to have passed.
|
|
70
92
|
if (facts.checks == null)
|
|
71
93
|
return "Review the change, then mark it complete.";
|
|
@@ -93,8 +115,19 @@ function detailsOf(headline, facts) {
|
|
|
93
115
|
const checksHref = facts.links?.checks ?? facts.links?.result ?? null;
|
|
94
116
|
const runChecks = facts.links?.runChecks === undefined ? null : { label: "Run checks", href: facts.links.runChecks };
|
|
95
117
|
const quick = checks.level === "quick";
|
|
96
|
-
|
|
118
|
+
const batch = checks.batch ?? null;
|
|
119
|
+
const together = batch === null ? null : checkedTogetherWords(batch);
|
|
120
|
+
const batchWhy = batch === null ? null : BATCH_WHY[batch.state] ?? null;
|
|
121
|
+
if (batch?.state === "waiting")
|
|
122
|
+
row("checks", CHECKS_LABEL, "Waiting to check with other results", "running", { href: checksHref });
|
|
123
|
+
else if (checks.running != null)
|
|
97
124
|
row("checks", CHECKS_LABEL, `${checks.running === "quick" ? "Quick" : "Full"} checks running`, "running", { href: checksHref });
|
|
125
|
+
else if (checks.status === "passed" && together !== null)
|
|
126
|
+
row("checks", CHECKS_LABEL, together, "ok", { href: checksHref });
|
|
127
|
+
else if (checks.status === "passed" && batchWhy !== null)
|
|
128
|
+
row("checks", CHECKS_LABEL, `Passed on its own${sha === null ? "" : ` on ${sha}`}`, "ok", { href: checksHref, why: batchWhy });
|
|
129
|
+
else if (checks.status === "failed" && batchWhy !== null)
|
|
130
|
+
row("checks", CHECKS_LABEL, `Failed on its own${checks.exitCode === null ? "" : ` (exit ${checks.exitCode})`}`, problem(headline), headline === "Failed" ? { href: checksHref, why: batchWhy } : { action: { label: "See what failed", href: checksHref }, why: batchWhy });
|
|
98
131
|
else if (checks.status === "passed")
|
|
99
132
|
row("checks", CHECKS_LABEL, `${quick ? "Quick checks passed" : "Passed"}${sha === null ? "" : ` on ${sha}`}`, "ok", quick && runChecks !== null && headline !== "Complete" ? { href: checksHref, action: { label: "Run full checks", href: runChecks.href } } : { href: checksHref });
|
|
100
133
|
else if (checks.status === "failed")
|
|
@@ -320,7 +353,8 @@ export function assignmentStatusFacts(assignment, options = {}) {
|
|
|
320
353
|
const report = receipt?.completionKind === "research-report";
|
|
321
354
|
const checks = receipt === null || !withResult ? null
|
|
322
355
|
: { status: receipt.checks.status, exitCode: receipt.checks.exitCode, head: receipt.head,
|
|
323
|
-
...(receipt.checks.level == null ? {} : { level: receipt.checks.level }), ...(receipt.checks.running == null ? {} : { running: receipt.checks.running })
|
|
356
|
+
...(receipt.checks.level == null ? {} : { level: receipt.checks.level }), ...(receipt.checks.running == null ? {} : { running: receipt.checks.running }),
|
|
357
|
+
...(receipt.checks.batch == null ? {} : { batch: receipt.checks.batch }) };
|
|
324
358
|
const publication = assignment.publication;
|
|
325
359
|
const pullRequest = options.pullRequest !== undefined ? options.pullRequest
|
|
326
360
|
: publication === null ? (withResult && !report ? { state: "none", number: null, url: null, ci: null, error: null } : null)
|
package/dist/team-leads.js
CHANGED
|
@@ -32,6 +32,10 @@ function role(value) {
|
|
|
32
32
|
refuse('invalid', 'Choose Viewer, Contributor or Manager.');
|
|
33
33
|
return value;
|
|
34
34
|
}
|
|
35
|
+
/** A failed turn's words for its message: none when the turn said why in the thread itself (it stopped at its deadline). */
|
|
36
|
+
function savedFailure(reason) {
|
|
37
|
+
return reason === 'timeout' || reason === 'crashed' ? null : String(reason ?? 'The saved turn failed.');
|
|
38
|
+
}
|
|
35
39
|
function hash(value) { return createHash('sha256').update(JSON.stringify(value)).digest('hex'); }
|
|
36
40
|
function stable(value) {
|
|
37
41
|
if (Array.isArray(value))
|
|
@@ -461,7 +465,7 @@ export class TeamLeads {
|
|
|
461
465
|
const uncertain = this.deliveryUncertain(Number(row['turn_id']));
|
|
462
466
|
const cancelled = Number(row['stop_requested']) === 1 || !this.authorStillAllowed(row);
|
|
463
467
|
const status = uncertain ? 'uncertain' : cancelled ? 'cancelled' : row['turn_state'] === 'answered' ? 'answered' : 'failed';
|
|
464
|
-
const error = uncertain ? 'Delivery could not be confirmed. Inspect saved activity before continuing.' : cancelled ? 'Stopped or conversation access changed.' : status === 'failed' ?
|
|
468
|
+
const error = uncertain ? 'Delivery could not be confirmed. Inspect saved activity before continuing.' : cancelled ? 'Stopped or conversation access changed.' : status === 'failed' ? savedFailure(row['failure_reason']) : null;
|
|
465
469
|
const result = this.db.prepare("UPDATE team_message SET status=?,error=?,generation=generation+1,revision=revision+1 WHERE message=? AND status='running' AND generation=?").run(status, error, Number(row['message']), Number(row['generation']));
|
|
466
470
|
if (Number(result.changes) === 1) {
|
|
467
471
|
changed++;
|
|
@@ -480,7 +484,7 @@ export class TeamLeads {
|
|
|
480
484
|
for (const row of rows) {
|
|
481
485
|
const turn = row['turn_id'] === null ? null : this.store.getMateTurn(Number(row['turn_id']));
|
|
482
486
|
const status = turn?.state === 'answered' ? 'answered' : turn?.state === 'failed' && !this.deliveryUncertain(turn.id) ? 'failed' : 'uncertain';
|
|
483
|
-
const error = status === 'uncertain' ? 'Delivery unconfirmed after the previous worker stopped. Inspect activity before continuing.' : status === 'failed' ? (turn?.failureReason
|
|
487
|
+
const error = status === 'uncertain' ? 'Delivery unconfirmed after the previous worker stopped. Inspect activity before continuing.' : status === 'failed' ? savedFailure(turn?.failureReason) : null;
|
|
484
488
|
this.db.prepare('UPDATE team_message SET status=?,generation=generation+1,revision=revision+1,error=? WHERE message=?').run(status, error, Number(row['message']));
|
|
485
489
|
this.event(String(row['lead']), String(row['conversation']), status === 'uncertain' ? 'delivery-unconfirmed' : 'message-reconciled', String(row['author']), now);
|
|
486
490
|
}
|
package/dist/team-runtime.js
CHANGED
|
@@ -166,9 +166,10 @@ export function createTeamRuntime(options) {
|
|
|
166
166
|
const state = admission(claim.actor, claim.conversationId);
|
|
167
167
|
if (domain.defer(claim, result.message, clock()))
|
|
168
168
|
waiting.set(claim.messageId, state.stamp);
|
|
169
|
+
// A turn that saved its outcome in the thread (stopped at its deadline) is said there, not again on the message.
|
|
169
170
|
}
|
|
170
171
|
else
|
|
171
|
-
domain.finish(claim, { status: 'unknownSpend' in result && result.unknownSpend ? 'uncertain' : 'failed', error: result.message, ...('turn' in result ? { turnId: result.turn } : {}) }, clock());
|
|
172
|
+
domain.finish(claim, { status: 'unknownSpend' in result && result.unknownSpend ? 'uncertain' : 'failed', ...('saved' in result && result.saved ? {} : { error: result.message }), ...('turn' in result ? { turnId: result.turn } : {}) }, clock());
|
|
172
173
|
}
|
|
173
174
|
catch {
|
|
174
175
|
// An exception may have followed native admission. Preserve an explicit
|
package/dist/test-temp.d.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
/** In a suite's temp root: who made it (scripts/suite-lifecycle.mjs). */
|
|
2
|
+
export declare const OWNER_FILE = ".toolroll-temp-owner";
|
|
3
|
+
export type TempOwner = {
|
|
4
|
+
pid: number;
|
|
5
|
+
startedAt: number;
|
|
6
|
+
};
|
|
1
7
|
/** Name prefixes of what tests, journeys and scripts leave in the temp folder. */
|
|
2
8
|
export declare const TEST_TEMP_PREFIXES: readonly string[];
|
|
9
|
+
/** Who made a temp root, from its owner file; null when it has none (older suites, scripts, Playwright). */
|
|
10
|
+
export declare function tempOwner(path: string): TempOwner | null;
|
|
3
11
|
export declare function isTestTemp(name: string): boolean;
|
|
4
12
|
/** Where they collect: the temp folder (and /tmp, where some scripts write). */
|
|
5
13
|
export declare function tempRoots(): string[];
|
|
@@ -12,8 +20,18 @@ export declare function testTempFolders(roots: readonly string[], now: Date): {
|
|
|
12
20
|
all: TempLeftover[];
|
|
13
21
|
stale: TempLeftover[];
|
|
14
22
|
};
|
|
15
|
-
|
|
16
|
-
|
|
23
|
+
export type RemoveOptions = {
|
|
24
|
+
/** Whether a temp root's owner (by its owner file) is still running: its root stays, however old it looks. */
|
|
25
|
+
ownerAlive?: (owner: TempOwner) => boolean;
|
|
26
|
+
/** At most this many go in one pass (a sweep after weeks can find 100,000); `more` says some were left. */
|
|
27
|
+
max?: number;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Remove the test temp folders nothing has touched for a day; each is looked at again right before it goes (still a
|
|
31
|
+
* folder of its own, not a link, still directly in its temp folder, still a day untouched, its owner gone).
|
|
32
|
+
*/
|
|
33
|
+
export declare function removeStaleTestTemp(roots: readonly string[], now: Date, options?: RemoveOptions): {
|
|
17
34
|
removed: string[];
|
|
18
35
|
failed: string[];
|
|
36
|
+
more: boolean;
|
|
19
37
|
};
|
package/dist/test-temp.js
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Test temp folders (`toolroll storage`, `storage clean
|
|
3
|
-
* system temp folder. The
|
|
4
|
-
* test/temp-root.ts); a run
|
|
5
|
-
*
|
|
2
|
+
* Test temp folders (`toolroll storage`, `storage clean`, the plane's daily storage sweep): what the unit tests,
|
|
3
|
+
* journeys and scripts make in the system temp folder. The suites delete their own on pass, failure, timeout and
|
|
4
|
+
* interrupt (scripts/suite-lifecycle.mjs, test/temp-root.ts); a run killed outright, a proof that keeps its output and
|
|
5
|
+
* older versions leave folders behind. `toolroll storage` counts them; the daily sweep (storage-sweep.ts) and
|
|
6
|
+
* `storage clean` remove the ones nothing has touched for a day, never one whose owner is still running.
|
|
6
7
|
*
|
|
7
8
|
* Every prefix the tests and scripts give tmpdir() is covered here (test-temp.test.ts reads the sources to keep it
|
|
8
9
|
* so), and Playwright's own browser profiles.
|
|
9
10
|
*/
|
|
10
|
-
import {
|
|
11
|
+
import { spawnSync } from "node:child_process";
|
|
12
|
+
import { lstatSync, readFileSync, readdirSync, rmSync } from "node:fs";
|
|
11
13
|
import { tmpdir } from "node:os";
|
|
12
|
-
import { join } from "node:path";
|
|
14
|
+
import { dirname, join, resolve } from "node:path";
|
|
13
15
|
const DAY_MS = 86_400_000;
|
|
16
|
+
/** In a suite's temp root: who made it (scripts/suite-lifecycle.mjs). */
|
|
17
|
+
export const OWNER_FILE = ".toolroll-temp-owner";
|
|
14
18
|
/** Name prefixes of what tests, journeys and scripts leave in the temp folder. */
|
|
15
19
|
export const TEST_TEMP_PREFIXES = [
|
|
16
20
|
"so-", "standing-orders-", "toolroll-", "no-wt", "epoch", "cancel-floor",
|
|
@@ -18,10 +22,21 @@ export const TEST_TEMP_PREFIXES = [
|
|
|
18
22
|
"flow-goal-", "flow-scratch", "flows-chat-", "invoke-attest-", "knowledge-test-", "learning-", "peek-", "phone-origin-",
|
|
19
23
|
"proposal-repo-", "race-", "refresh-migration-", "refresh-refusal-", "release-check-", "shared-chat-", "skills-test-",
|
|
20
24
|
"task-status-surfaces-", "template-repo-", "template-test-", "v14-migrate-", "v17-migrate-", "watch-anchor-",
|
|
21
|
-
"needs-you-", "deps-lock-", "deps-state-", "upgrade-fresh-", "upgrade-path-", "deploy-restore-", "result-review-", "one-result-page-", "deploy-before-swap-", "one-vocabulary-", "accept-finishes-", "lead-promises-", "lead-people-", "sizing-repo-",
|
|
25
|
+
"needs-you-", "deps-lock-", "deps-state-", "upgrade-fresh-", "upgrade-path-", "deploy-restore-", "result-review-", "one-result-page-", "deploy-before-swap-", "one-vocabulary-", "accept-finishes-", "lead-promises-", "lead-people-", "sizing-repo-", "batch-checks-", "deps-identity-",
|
|
22
26
|
// Playwright's browser profiles and artifacts.
|
|
23
27
|
"playwright_chromiumdev_profile-", "playwright_firefoxdev_profile-", "playwright-artifacts-",
|
|
24
28
|
];
|
|
29
|
+
/** Who made a temp root, from its owner file; null when it has none (older suites, scripts, Playwright). */
|
|
30
|
+
export function tempOwner(path) {
|
|
31
|
+
try {
|
|
32
|
+
const parsed = JSON.parse(readFileSync(join(path, OWNER_FILE), "utf8"));
|
|
33
|
+
const startedAt = typeof parsed.startedAt === "string" ? Date.parse(parsed.startedAt) : NaN;
|
|
34
|
+
return Number.isSafeInteger(parsed.pid) && Number(parsed.pid) > 1 && Number.isFinite(startedAt) ? { pid: Number(parsed.pid), startedAt } : null;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
25
40
|
export function isTestTemp(name) {
|
|
26
41
|
return TEST_TEMP_PREFIXES.some(prefix => name.startsWith(prefix));
|
|
27
42
|
}
|
|
@@ -79,20 +94,45 @@ export function testTempFolders(roots, now) {
|
|
|
79
94
|
}
|
|
80
95
|
return { all, stale: all.filter(one => now.getTime() - one.touchedAt > DAY_MS) };
|
|
81
96
|
}
|
|
82
|
-
/**
|
|
83
|
-
|
|
97
|
+
/**
|
|
98
|
+
* Remove the test temp folders nothing has touched for a day; each is looked at again right before it goes (still a
|
|
99
|
+
* folder of its own, not a link, still directly in its temp folder, still a day untouched, its owner gone).
|
|
100
|
+
*/
|
|
101
|
+
export function removeStaleTestTemp(roots, now, options = {}) {
|
|
84
102
|
const removed = [];
|
|
85
103
|
const failed = [];
|
|
86
|
-
|
|
104
|
+
const stale = testTempFolders(roots, now).stale;
|
|
105
|
+
const max = options.max ?? Infinity;
|
|
106
|
+
const inside = new Set(roots.map(root => resolve(root)));
|
|
107
|
+
for (const one of stale) {
|
|
108
|
+
if (removed.length + failed.length >= max)
|
|
109
|
+
return { removed, failed, more: true };
|
|
87
110
|
try {
|
|
88
|
-
|
|
111
|
+
const stat = lstatSync(one.path);
|
|
112
|
+
if (stat.isSymbolicLink() || !inside.has(resolve(dirname(one.path))))
|
|
113
|
+
continue;
|
|
114
|
+
if (now.getTime() - (stat.isDirectory() ? touchedAt(one.path) : stat.mtimeMs) <= DAY_MS)
|
|
115
|
+
continue;
|
|
116
|
+
const owner = stat.isDirectory() ? tempOwner(one.path) : null;
|
|
117
|
+
if (owner !== null && (options.ownerAlive ?? (() => false))(owner))
|
|
89
118
|
continue;
|
|
90
|
-
|
|
119
|
+
removeTree(one.path);
|
|
91
120
|
removed.push(one.path);
|
|
92
121
|
}
|
|
93
122
|
catch {
|
|
94
123
|
failed.push(one.path);
|
|
95
124
|
}
|
|
96
125
|
}
|
|
97
|
-
return { removed, failed };
|
|
126
|
+
return { removed, failed, more: false };
|
|
127
|
+
}
|
|
128
|
+
/** rm -r; a tree a test made read-only (a shared dependency copy, a locked fixture) is made writable first. */
|
|
129
|
+
function removeTree(path) {
|
|
130
|
+
try {
|
|
131
|
+
rmSync(path, { recursive: true, force: true, maxRetries: 3 });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
catch { /* read-only inside */ }
|
|
135
|
+
if (process.platform !== "win32")
|
|
136
|
+
spawnSync("chmod", ["-R", "u+w", path], { timeout: 120_000 });
|
|
137
|
+
rmSync(path, { recursive: true, force: true, maxRetries: 3 });
|
|
98
138
|
}
|
package/dist/toolroll-update.js
CHANGED
|
@@ -36,6 +36,7 @@ import { assertCodingUpdateStopped, backupCodingCatalog, codingCatalogExists, re
|
|
|
36
36
|
import { installLaunchdService, launchdPlist, stopLaunchdService, writeFileDurably } from "./daemon.js";
|
|
37
37
|
import { NAME } from "./names.js";
|
|
38
38
|
import { isNewer, REGISTRY } from "./releases.js";
|
|
39
|
+
import { markNeverIndex } from "./never-index.js";
|
|
39
40
|
/** Loaded on first use (as backup.ts and store.ts do), so modules that only import this one (the console,
|
|
40
41
|
* and tests that load it in a browser-like environment) never need `node:sqlite` itself. */
|
|
41
42
|
function sqlite() {
|
|
@@ -699,6 +700,7 @@ function newJournal(kind, o, now) {
|
|
|
699
700
|
const id = randomUUID(), stageDir = join(resolve(o.stateDir), "staged-upgrades", `${kind === "update" ? "release" : "rollback"}-${o.to.version}-${id.slice(0, 8)}`);
|
|
700
701
|
mkdirSync(stageDir, { recursive: true, mode: 0o700 });
|
|
701
702
|
chmodSync(stageDir, 0o700);
|
|
703
|
+
markNeverIndex(dirname(stageDir));
|
|
702
704
|
return { version: 1, id, kind, stateDir: resolve(o.stateDir), databaseFile: o.databaseFile, stageDir, from: o.from, to: o.to, when: o.when, at: o.at, actor: o.actor, phase: "scheduled", detail: "", steps: [], startedAt: now.toISOString(), updatedAt: now.toISOString() };
|
|
703
705
|
}
|
|
704
706
|
function assertNoneActive(stateDir) {
|
package/dist/workspace-ui.js
CHANGED
|
@@ -371,7 +371,9 @@ function sentenceOf(text) {
|
|
|
371
371
|
* the saved log. Null when the log printed nothing. */
|
|
372
372
|
export function lastErrorLineOf(log) {
|
|
373
373
|
const lines = log.split("\n");
|
|
374
|
-
const printed = (text) => text.trim() !== "" && !/^\$ /.test(text) && !/^\(exit -?[0-9]+\)$/.test(text.trim()) && !/^--- (stdout|stderr) ---$/.test(text.trim())
|
|
374
|
+
const printed = (text) => text.trim() !== "" && !/^\$ /.test(text) && !/^\(exit -?[0-9]+\)$/.test(text.trim()) && !/^--- (stdout|stderr) ---$/.test(text.trim())
|
|
375
|
+
// A section's own header ("=== Project check · attempt 1 ===") is the log's, never the check's output.
|
|
376
|
+
&& !/^=== .* ===$/.test(text.trim());
|
|
375
377
|
const stderr = lines.findIndex(text => text.trim() === "--- stderr ---");
|
|
376
378
|
const pick = (from, to) => {
|
|
377
379
|
let last = null;
|
package/dist/worktree.js
CHANGED
|
@@ -31,6 +31,7 @@ import { currentBootId, provenDeadByBootChange } from "./boot-identity.js";
|
|
|
31
31
|
import { HANDOFF_PREFIX, MAILBOX_SUFFIX, looksLikeProtocolFile, readMailbox } from "./evidence.js";
|
|
32
32
|
import { parseHandoff } from "./decision.js";
|
|
33
33
|
import { diskBytes } from "./storage.js";
|
|
34
|
+
import { markNeverIndex } from "./never-index.js";
|
|
34
35
|
/**
|
|
35
36
|
* The note a lease leaves in the checkout, naming the process holding it.
|
|
36
37
|
*
|
|
@@ -337,6 +338,14 @@ export class WorktreePool {
|
|
|
337
338
|
const branchExists = request.base !== undefined && request.reuseBranch === true &&
|
|
338
339
|
(await this.git(request.repo, ["rev-parse", "--verify", "--quiet", `refs/heads/${request.branch}`], READ_ONLY)).code === 0;
|
|
339
340
|
const fromBase = request.base !== undefined && !branchExists;
|
|
341
|
+
// Spotlight leaves the checkouts alone: the marker sits in the pool and the project's folder, never inside a
|
|
342
|
+
// checkout, where a build's `git add -A` would commit it (never-index.ts).
|
|
343
|
+
try {
|
|
344
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
345
|
+
}
|
|
346
|
+
catch { /* git reports it */ }
|
|
347
|
+
markNeverIndex(this.options.root);
|
|
348
|
+
markNeverIndex(dirname(path));
|
|
340
349
|
const add = await this.git(request.repo, [
|
|
341
350
|
"worktree",
|
|
342
351
|
"add",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolroll",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.33",
|
|
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"
|