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/builder.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { OBSERVATION_MAILBOX, observationBrief, parseObservationCases, collectObservations } from "./observations.js";
|
|
2
2
|
import { checkCommandFor, effectiveCheckLevel, recordRunCheckLevel } from "./check-levels.js";
|
|
3
|
+
import { queueBatchCheck } from "./batch-checks.js";
|
|
4
|
+
import { batchesFullCheck } from "./batch-policy.js";
|
|
3
5
|
import { skillsContext } from "./project-skills.js";
|
|
4
6
|
import { failedVerificationEvidence, sealVerificationReceipt, verificationEvidence, reuseObservationVerification } from "./verification-evidence.js";
|
|
5
7
|
import { learningContext } from "./project-learning.js";
|
|
@@ -684,7 +686,7 @@ export async function build(store, request) {
|
|
|
684
686
|
// copy links it instead of installing its own; a setup that is about to
|
|
685
687
|
// run never reaches through an old link into a shared copy.
|
|
686
688
|
const depsRoot = sharedDepsRootOf(store);
|
|
687
|
-
const sharing = depsRoot === null ? null : sharingFor({ repo: leased.repo, worktree, setup: setupWanted });
|
|
689
|
+
const sharing = depsRoot === null ? null : sharingFor({ repo: leased.repo, worktree, setup: setupWanted, root: depsRoot });
|
|
688
690
|
if (depsRoot !== null)
|
|
689
691
|
detachShared(worktree, depsRoot);
|
|
690
692
|
if (depsRoot !== null && sharing !== null) {
|
|
@@ -2175,6 +2177,11 @@ async function settleProof(captured, statArtifactId, sealedHead) {
|
|
|
2175
2177
|
const configured = chosenCheck?.command ?? null;
|
|
2176
2178
|
if (repo !== null && chosenCheck !== null)
|
|
2177
2179
|
recordRunCheckLevel(store, { id: runId, taskId: request.taskId, repo }, chosenCheck.level, checkChoice.from, now());
|
|
2180
|
+
// Batch checks (batch-checks.ts): with the project's batching on, a Full check waits to run once with
|
|
2181
|
+
// other results on a temporary batch commit. The build seals no check of its own; the batch's sealed
|
|
2182
|
+
// receipt is this result's check, and Merge waits for it.
|
|
2183
|
+
const batched = repo !== null && chosenCheck !== null && batchesFullCheck(store, repo, chosenCheck.level, configured);
|
|
2184
|
+
const gateCommand = batched ? null : configured;
|
|
2178
2185
|
let verifyCommand;
|
|
2179
2186
|
const checkLog = [];
|
|
2180
2187
|
const checkOutcomes = [];
|
|
@@ -2210,6 +2217,10 @@ async function settleProof(captured, statArtifactId, sealedHead) {
|
|
|
2210
2217
|
else if (configured === null) {
|
|
2211
2218
|
verifyCommand = { configured: false };
|
|
2212
2219
|
}
|
|
2220
|
+
else if (batched && repo !== null) {
|
|
2221
|
+
verifyCommand = { configured: false };
|
|
2222
|
+
queueBatchCheck(store, { runId, taskId: request.taskId, repo, head: sealedHead, base: captured.baseRevision, command: configured }, now());
|
|
2223
|
+
}
|
|
2213
2224
|
else if (!store.proveRunnerCustodyForSpawn(runId, now())) {
|
|
2214
2225
|
verifyCommand = { configured: true, ran: false, attemptFailed: true, failure: "custody-lost" };
|
|
2215
2226
|
recordCheckNote("Verification did not start: this worker no longer owned the build.");
|
|
@@ -2258,7 +2269,7 @@ async function settleProof(captured, statArtifactId, sealedHead) {
|
|
|
2258
2269
|
const live = linked === null ? null : store.liveWorktreeSetup(repo);
|
|
2259
2270
|
if (linked === null || live === null)
|
|
2260
2271
|
return null;
|
|
2261
|
-
if (sharingFor({ repo, worktree, setup: live })?.key === linked && readyCopy(depsRoot, linked) !== null)
|
|
2272
|
+
if (sharingFor({ repo, worktree, setup: live, root: depsRoot })?.key === linked && readyCopy(depsRoot, linked) !== null)
|
|
2262
2273
|
return null;
|
|
2263
2274
|
if (!store.proveRunnerCustodyForSpawn(runId, now()))
|
|
2264
2275
|
return "custody-lost";
|
|
@@ -2497,7 +2508,7 @@ async function settleProof(captured, statArtifactId, sealedHead) {
|
|
|
2497
2508
|
}
|
|
2498
2509
|
const { verdict, reasons, matrix, machineVerdict } = adjudicate({
|
|
2499
2510
|
directAssessment: true,
|
|
2500
|
-
...(
|
|
2511
|
+
...(gateCommand === null ? {} : { verificationCommand: gateCommand.command }),
|
|
2501
2512
|
proofArtifactPresent,
|
|
2502
2513
|
proofParse,
|
|
2503
2514
|
handoffPresent: handoffArtifact !== null,
|
package/dist/chat-actions.js
CHANGED
|
@@ -5,7 +5,7 @@ import { TEXT_LIMITS } from "./text-limits.js";
|
|
|
5
5
|
import { publicChatText } from "./chat-display.js";
|
|
6
6
|
import { gateWords } from "./approval-policy.js";
|
|
7
7
|
import { createHash, randomBytes } from "node:crypto";
|
|
8
|
-
import { verifiedAuthor } from "./store.js";
|
|
8
|
+
import { mateTurnKeepsProposals, verifiedAuthor } from "./store.js";
|
|
9
9
|
import { isVerifiedApprover, reproveApprover, } from "./principal.js";
|
|
10
10
|
import { changeSkills, importSkill, skillsView, skillsVersion, testSkill, validateSkill, } from "./project-skills.js";
|
|
11
11
|
import { changeKnowledge, knowledgeVersion, knowledgeView, } from "./project-knowledge.js";
|
|
@@ -1025,7 +1025,7 @@ function savedActionContext(store, who, id, state) {
|
|
|
1025
1025
|
session.ceilingDigest !== who.ceilingDigest ||
|
|
1026
1026
|
proposal.ceilingDigest !== who.ceilingDigest ||
|
|
1027
1027
|
(!shared && turn?.session !== session.id) ||
|
|
1028
|
-
turn
|
|
1028
|
+
!mateTurnKeepsProposals(turn))
|
|
1029
1029
|
throw Error("This conversation ended or its project access changed. Ask for a fresh proposal.");
|
|
1030
1030
|
requireActor(store, who, payload.repo, payload.operation);
|
|
1031
1031
|
return payload;
|
package/dist/check-levels-ui.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* one. Every change takes the password and lands in the ledger; approving a
|
|
4
4
|
* command restates exactly what will run, unattended, before the yes. */
|
|
5
5
|
import { CHECK_LEVELS, CHECK_LEVEL_HINTS, CHECK_LEVEL_WORDS } from "./check-levels.js";
|
|
6
|
+
import { BATCH_HINT } from "./batch-policy.js";
|
|
6
7
|
const escape = (text) => text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
7
8
|
export function checkSettingsHtml(view) {
|
|
8
9
|
const hidden = (act) => `<input type="hidden" name="csrf" value="${escape(view.csrf)}"><input type="hidden" name="repo" value="${escape(view.repo)}"><input type="hidden" name="act" value="${act}">`;
|
|
@@ -19,7 +20,7 @@ export function checkSettingsHtml(view) {
|
|
|
19
20
|
(fallsBack ? `<p class="meta">No quick command yet, so builds run the full check.</p>` : "") +
|
|
20
21
|
(view.level !== "full" && view.full !== null ? `<p class="meta">The full check still runs when a pull request opens, and Merge waits for it.</p>` : "");
|
|
21
22
|
if (!view.canChange) {
|
|
22
|
-
return head + `<section class="card check-settings">${current}</section>` + reviewHtml(view, "") +
|
|
23
|
+
return head + `<section class="card check-settings">${current}</section>` + batchHtml(view, "") + reviewHtml(view, "") +
|
|
23
24
|
`<section class="card check-settings"><h2>Quick check</h2>${view.quick === null ? `<p class="meta">None yet.</p>` : command(view.quick)}</section>` +
|
|
24
25
|
`<section class="card check-settings"><h2>Full check</h2>${view.full === null ? `<p class="meta">None yet.</p>` : command(view.full)}</section>`;
|
|
25
26
|
}
|
|
@@ -45,7 +46,25 @@ export function checkSettingsHtml(view) {
|
|
|
45
46
|
`<p class="meta">Quick builds run the full check instead.</p>${password}<button type="submit" class="danger">Remove</button></form></details></section>`;
|
|
46
47
|
const full = `<details class="settings-more"><summary>Full check</summary><section class="card check-settings">` +
|
|
47
48
|
(view.full === null ? `<p class="meta">None yet. Approve one with <span class="mono">toolroll verify set --repo … --command "…"</span>.</p>` : command(view.full)) + `</section></details>`;
|
|
48
|
-
return head + levelForm + reviewHtml(view, password) + quick + full;
|
|
49
|
+
return head + levelForm + batchHtml(view, password) + reviewHtml(view, password) + quick + full;
|
|
50
|
+
}
|
|
51
|
+
/** Batch checks: one state and one sentence; turning it on restates exactly what happens, behind a disclosure with the password. */
|
|
52
|
+
function batchHtml(view, password) {
|
|
53
|
+
const batch = view.batch;
|
|
54
|
+
if (batch === undefined)
|
|
55
|
+
return "";
|
|
56
|
+
const act = batch.on ? "Turn off" : "Turn on";
|
|
57
|
+
const terms = batch.on ? `<p class="meta">Each result runs its own full check again. Results already waiting are checked now.</p>`
|
|
58
|
+
: `<ul class="check-terms"><li>When results finish within 10 minutes of each other, they're merged in a temporary checkout and the full check runs once.</li>` +
|
|
59
|
+
`<li>If it fails, the batch is split until the result that breaks it is found. Results that conflict are checked on their own.</li>` +
|
|
60
|
+
`<li>Nothing is merged into a real branch. Each result still lands on its own.</li></ul>`;
|
|
61
|
+
const change = password === "" ? ""
|
|
62
|
+
: `<details class="settings-more"><summary>${act}</summary><form method="post" action="/settings/checks" class="check-settings">` +
|
|
63
|
+
`<input type="hidden" name="csrf" value="${escape(view.csrf)}"><input type="hidden" name="repo" value="${escape(view.repo)}"><input type="hidden" name="act" value="batch">` +
|
|
64
|
+
`<input type="hidden" name="on" value="${batch.on ? "0" : "1"}">${terms}${password}<button type="submit">${act}</button></form></details>`;
|
|
65
|
+
return `<section class="card check-settings" data-batch-checks="${batch.on ? "on" : "off"}"><h2>Batch checks</h2>` +
|
|
66
|
+
`<p><strong>${batch.on ? "On" : "Off"}</strong> · ${escape(BATCH_HINT)}</p>` +
|
|
67
|
+
(batch.on && view.level !== "full" ? `<p class="meta">Only Full checks are batched.</p>` : "") + `${change}</section>`;
|
|
49
68
|
}
|
|
50
69
|
/** Automatic review: one state, one sentence, and its one change behind a disclosure (the password). */
|
|
51
70
|
function reviewHtml(view, password) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type CheckoutCleanup } from "./storage.js";
|
|
2
2
|
import type { Store, WorktreeRow } from "./store.js";
|
|
3
3
|
import { type WorktreePool } from "./worktree.js";
|
|
4
|
-
/** A release candidate's checkout holds the build a deploy installs:
|
|
4
|
+
/** A release candidate's checkout holds the build a deploy installs: the newest complete one waits at most a week for it. */
|
|
5
5
|
export declare const CANDIDATE_KEEP_MS: number;
|
|
6
6
|
/** The worker's pass removes at most this many; a clean-up by hand has no cap. */
|
|
7
7
|
export declare const AUTO_MAX = 20;
|
|
@@ -43,6 +43,7 @@ type Status = {
|
|
|
43
43
|
export type Deployed = {
|
|
44
44
|
head: string | null;
|
|
45
45
|
run: number | null;
|
|
46
|
+
at?: string | null;
|
|
46
47
|
};
|
|
47
48
|
export declare function deployedRelease(stateDir: string | null): Deployed | null;
|
|
48
49
|
/**
|
package/dist/checkout-cleanup.js
CHANGED
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Always kept: a checkout in use, one whose task isn't finished (on hold, failed, a revision under way), a result
|
|
9
9
|
* still waiting for review, a checkout with changes (files Toolroll wrote itself aside), and one with commits on no
|
|
10
|
-
* branch. A release
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* branch. A release check's checkout (a deploy installs from it) goes once its task is complete, with its dependency
|
|
11
|
+
* install and journey output, except the one deployed now and the project's newest complete one still waiting for its
|
|
12
|
+
* deploy (no deploy since it was complete; at most a week). A superseded or cancelled gate goes at once, whatever its
|
|
13
|
+
* task's state (staged releases keep the rollback copy). A checkout no task names (adopted after a crash) goes only
|
|
13
14
|
* in a clean-up by hand. Every removal is in the ledger.
|
|
14
15
|
*
|
|
15
16
|
* A kept checkout waiting for review or with changes drops its dependencies and build output once its run ends
|
|
@@ -23,7 +24,7 @@ import { COMPLETION_ACTION } from "./result-completion.js";
|
|
|
23
24
|
import { CLEANUP_CHOICES, bytesWords, diskBytes } from "./storage.js";
|
|
24
25
|
import { worktreePath } from "./worktree.js";
|
|
25
26
|
const DAY_MS = 86_400_000;
|
|
26
|
-
/** A release candidate's checkout holds the build a deploy installs:
|
|
27
|
+
/** A release candidate's checkout holds the build a deploy installs: the newest complete one waits at most a week for it. */
|
|
27
28
|
export const CANDIDATE_KEEP_MS = 7 * DAY_MS;
|
|
28
29
|
/** The worker's pass removes at most this many; a clean-up by hand has no cap. */
|
|
29
30
|
export const AUTO_MAX = 20;
|
|
@@ -56,7 +57,7 @@ export function deployedRelease(stateDir) {
|
|
|
56
57
|
continue;
|
|
57
58
|
newest = { at, head: typeof journal["candidate"] === "string" ? journal["candidate"] : null, run: Number.isSafeInteger(journal["builder"]) ? Number(journal["builder"]) : null };
|
|
58
59
|
}
|
|
59
|
-
return newest === null ? null : { head: newest.head, run: newest.run };
|
|
60
|
+
return newest === null ? null : { head: newest.head, run: newest.run, at: newest.at === "" ? null : newest.at };
|
|
60
61
|
}
|
|
61
62
|
/** The folder beside the database (where staged releases live), or null for a database in memory. */
|
|
62
63
|
function stateDirOf(store) {
|
|
@@ -117,6 +118,19 @@ export function taskStatuses(store, now, deployed) {
|
|
|
117
118
|
if (ref !== undefined && (completedRef.get(ref) ?? "") < at)
|
|
118
119
|
completedRef.set(ref, at);
|
|
119
120
|
}
|
|
121
|
+
const completedAtOf = (row) => [completed.get(String(row["id"])), completedRef.get(Number(row["ref"]))].filter((one) => one !== undefined).sort().at(-1) ?? null;
|
|
122
|
+
// Per project, the newest complete gate (by when it was complete): the one a deploy may still be about to install.
|
|
123
|
+
// Once the project deployed after it was complete, it waits for nothing.
|
|
124
|
+
const deployedRepo = deployedRef === undefined ? (live?.head == null ? undefined : gates.find(row => String(row["candidate"]) === live.head)?.["repo"]) : refs.find(row => Number(row["ref"]) === deployedRef)?.["repo"];
|
|
125
|
+
const newestDone = new Map();
|
|
126
|
+
for (const row of gates) {
|
|
127
|
+
if (row["state"] !== "done")
|
|
128
|
+
continue;
|
|
129
|
+
const at = completedAtOf(row) ?? (row["updated_at"] === null ? "" : String(row["updated_at"]));
|
|
130
|
+
const seen = newestDone.get(String(row["repo"]));
|
|
131
|
+
if (seen === undefined || at > seen.at || (at === seen.at && Number(row["ref"]) > seen.ref))
|
|
132
|
+
newestDone.set(String(row["repo"]), { ref: Number(row["ref"]), at });
|
|
133
|
+
}
|
|
120
134
|
const byRef = new Map();
|
|
121
135
|
const byId = new Map();
|
|
122
136
|
const parentOf = new Map();
|
|
@@ -139,11 +153,13 @@ export function taskStatuses(store, now, deployed) {
|
|
|
139
153
|
else if (state === "cancelled")
|
|
140
154
|
status = { keep: null, taskId: id, finishedAt: updated };
|
|
141
155
|
else {
|
|
142
|
-
const completedAt =
|
|
156
|
+
const completedAt = completedAtOf(row);
|
|
143
157
|
if (gate) {
|
|
144
158
|
const since = completedAt ?? updated ?? now.toISOString();
|
|
145
159
|
const until = new Date(Date.parse(since) + CANDIDATE_KEEP_MS).toISOString();
|
|
146
|
-
|
|
160
|
+
const deployedSince = live?.at != null && deployedRepo !== undefined && String(row["repo"]) === String(deployedRepo) && live.at >= since;
|
|
161
|
+
const waiting = newestDone.get(String(row["repo"]))?.ref === ref && !deployedSince && until > now.toISOString();
|
|
162
|
+
status = waiting ? { keep: "release candidate", taskId: id, finishedAt: since, dueAt: until } : { keep: null, taskId: id, finishedAt: since, atOnce: true };
|
|
147
163
|
}
|
|
148
164
|
else
|
|
149
165
|
status = completedAt === null ? { keep: "waiting for review", taskId: id, finishedAt: null } : { keep: null, taskId: id, finishedAt: completedAt };
|
|
@@ -353,7 +369,7 @@ export function whyWords(item) {
|
|
|
353
369
|
case "in use": return "in use";
|
|
354
370
|
case "task not finished": return "its task isn't finished";
|
|
355
371
|
case "waiting for review": return "its result is waiting for review";
|
|
356
|
-
case "release candidate": return item.dueAt === undefined ? "the deployed release candidate" : `newest release candidate, kept until ${item.dueAt.slice(0, 10)}`;
|
|
372
|
+
case "release candidate": return item.dueAt === undefined ? "the deployed release candidate" : `newest release candidate, kept for its deploy until ${item.dueAt.slice(0, 10)}`;
|
|
357
373
|
case "has changes": return "has changes";
|
|
358
374
|
case "has commits": return "has commits on no branch";
|
|
359
375
|
case "unreadable": return "git couldn't read it";
|
package/dist/coding-context.js
CHANGED
|
@@ -4,7 +4,7 @@ import { createHash } from 'node:crypto';
|
|
|
4
4
|
import { basename, dirname, join, resolve, relative, isAbsolute, sep } from 'node:path';
|
|
5
5
|
import { KNOWLEDGE_GUIDANCE, selectProjectKnowledge } from './project-knowledge.js';
|
|
6
6
|
import { materializeProjectSkills, PROJECT_SKILLS_GUIDANCE, selectProjectSkills } from './project-skills.js';
|
|
7
|
-
import { learningIdentity, learningSha } from './project-learning.js';
|
|
7
|
+
import { identityMatches, learningIdentity, learningSha } from './project-learning.js';
|
|
8
8
|
function freeze(value) {
|
|
9
9
|
if (value !== null && typeof value === 'object' && !Object.isFrozen(value)) {
|
|
10
10
|
for (const child of Object.values(value))
|
|
@@ -74,7 +74,7 @@ export function prepareCodingContext(store, args) {
|
|
|
74
74
|
}
|
|
75
75
|
/** Integrity check for a persisted capture. The caller must separately recheck current account access. */
|
|
76
76
|
export function verifyCodingContext(context, repo, baseRevision) {
|
|
77
|
-
if (context.metadata.version !== 1 || context.metadata.repo !== repo || context.metadata.baseRevision !== baseRevision || context.metadata.identity
|
|
77
|
+
if (context.metadata.version !== 1 || context.metadata.repo !== repo || context.metadata.baseRevision !== baseRevision || !identityMatches(repo, context.metadata.identity) || context.sha256 !== digest(context.text, context.metadata))
|
|
78
78
|
throw Error('The coding session context could not be verified.');
|
|
79
79
|
if (context.metadata.files.length && context.metadata.directory === null)
|
|
80
80
|
throw Error('The coding session skill files are unavailable.');
|
package/dist/coding-handoff.js
CHANGED
|
@@ -4,7 +4,7 @@ import { lstatSync, realpathSync } from 'node:fs';
|
|
|
4
4
|
import { sqliteRuntime } from './sqlite-runtime.js';
|
|
5
5
|
import { codingCatalogPath } from './coding-update.js';
|
|
6
6
|
import { updateAdmissionPaused } from './desktop-update-gate.js';
|
|
7
|
-
import { learningIdentity, learningSha } from './project-learning.js';
|
|
7
|
+
import { acceptedIdentities, learningIdentity, learningSha } from './project-learning.js';
|
|
8
8
|
import { fileTaskProposal } from './proposal.js';
|
|
9
9
|
import { parseAcceptanceCriteria, proposeGuarded } from './scope.js';
|
|
10
10
|
import { validateTaskText } from './task-text.js';
|
|
@@ -164,7 +164,7 @@ export function readCodingHandoff(store, taskId) {
|
|
|
164
164
|
if (!row)
|
|
165
165
|
throw Error('The coding handoff receipt is missing. Restore it before review.');
|
|
166
166
|
const receipt = parseReceipt(row), scope = store.getScope(taskId), ref = store.lookupRef(taskId);
|
|
167
|
-
if (receipt.taskId !== taskId || ref?.repo !== receipt.repo || receipt.
|
|
167
|
+
if (receipt.taskId !== taskId || ref?.repo !== receipt.repo || !acceptedIdentities(store, receipt.repo).has(receipt.identity) || !scope || scope.termsProblem != null || scope.candidate !== receipt.candidate)
|
|
168
168
|
throw Error('The coding review no longer matches its saved project, candidate or scope.');
|
|
169
169
|
verifySeal(db, receipt, scope.digest);
|
|
170
170
|
return { taskId, scopeDigest: scope.digest, receipt };
|
package/dist/desktop-update.d.ts
CHANGED
|
@@ -125,13 +125,13 @@ export declare function prepareDesktopUpdate(stateDir: string, installed: string
|
|
|
125
125
|
export declare function sqliteLock(file: string, busyTimeoutMs?: number): DatabaseSync | null;
|
|
126
126
|
export declare const installationLock: (j: UpdateJournal) => DatabaseSync | null;
|
|
127
127
|
export declare function desktopUpdateStatus(stateDir: string): {
|
|
128
|
-
error?: never;
|
|
129
128
|
active: boolean;
|
|
130
129
|
phase: string;
|
|
131
130
|
detail: string;
|
|
132
131
|
running: boolean;
|
|
133
132
|
canResume: boolean;
|
|
134
133
|
canCancel: boolean;
|
|
134
|
+
error?: never;
|
|
135
135
|
automaticRecovery?: never;
|
|
136
136
|
recoveryAttempts?: never;
|
|
137
137
|
recoveryRepairs?: never;
|
package/dist/exec.d.ts
CHANGED
|
@@ -56,6 +56,8 @@ export type RunOptions = ProcessTreeObserver & {
|
|
|
56
56
|
* timeoutMs there is deliberately no absolute wall-clock ceiling.
|
|
57
57
|
*/
|
|
58
58
|
idleTimeoutMs?: number;
|
|
59
|
+
/** Ends a process-group run early, exactly as its timeout does: the whole group is killed and the result reads timed out. */
|
|
60
|
+
signal?: AbortSignal;
|
|
59
61
|
maxBuffer?: number;
|
|
60
62
|
/**
|
|
61
63
|
* Extra environment, merged over the process's own. A capability probe is a
|
package/dist/exec.js
CHANGED
|
@@ -526,6 +526,7 @@ export function run(file, args, options = {}) {
|
|
|
526
526
|
return retryTransientSpawn(() => runBufferedGroup(file, args, {
|
|
527
527
|
timeoutMs,
|
|
528
528
|
maxBuffer,
|
|
529
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
529
530
|
...(options.stdin === undefined ? {} : { stdin: options.stdin }),
|
|
530
531
|
...(cwd === undefined ? {} : { cwd }),
|
|
531
532
|
...(childEnv === undefined ? {} : { childEnv }),
|
|
@@ -610,6 +611,15 @@ function runBufferedGroup(file, args, bag) {
|
|
|
610
611
|
timedOut = true;
|
|
611
612
|
killGroup(child);
|
|
612
613
|
}, bag.timeoutMs);
|
|
614
|
+
// The caller's deadline, when it owns one: the same group kill as the timer.
|
|
615
|
+
const aborted = () => {
|
|
616
|
+
timedOut = true;
|
|
617
|
+
killGroup(child);
|
|
618
|
+
};
|
|
619
|
+
if (bag.signal?.aborted === true)
|
|
620
|
+
aborted();
|
|
621
|
+
else
|
|
622
|
+
bag.signal?.addEventListener("abort", aborted, { once: true });
|
|
613
623
|
child.stdout?.setEncoding("utf8");
|
|
614
624
|
child.stdout?.on("data", (chunk) => {
|
|
615
625
|
stdout += chunk;
|
|
@@ -638,6 +648,7 @@ function runBufferedGroup(file, args, bag) {
|
|
|
638
648
|
return;
|
|
639
649
|
settled = true;
|
|
640
650
|
clearTimeout(timer);
|
|
651
|
+
bag.signal?.removeEventListener("abort", aborted);
|
|
641
652
|
liveProviders.delete(child);
|
|
642
653
|
// The OS object is settled BEFORE the answer: a root that exited is
|
|
643
654
|
// not an empty container until the OS says so.
|
package/dist/guides.js
CHANGED
|
@@ -419,6 +419,9 @@ and crew agent reads from it before working:
|
|
|
419
419
|
- Instructions and references change on the Knowledge page under Settings,
|
|
420
420
|
or through the lead's knowledge cards. Every change keeps a revision you
|
|
421
421
|
can restore.
|
|
422
|
+
- If a project was replaced at the same path, its saved knowledge waits:
|
|
423
|
+
tasks run without it until an approver applies it on the Knowledge page
|
|
424
|
+
or with \`toolroll knowledge apply --repo PATH --as <you> --token <t>\`.
|
|
422
425
|
- Agent conclusions are not instructions: a crew agent reports; a person or
|
|
423
426
|
the lead's confirmed card records.
|
|
424
427
|
|
package/dist/knowledge-cli.d.ts
CHANGED
|
@@ -89,7 +89,38 @@ export declare const KNOWLEDGE_DESCRIPTORS: readonly [{
|
|
|
89
89
|
readonly takesValue: true;
|
|
90
90
|
readonly meaning: 'environment variable containing a scoped coordinator token';
|
|
91
91
|
}];
|
|
92
|
+
}, {
|
|
93
|
+
readonly action: 'apply';
|
|
94
|
+
readonly synopsis: "carry this project's saved knowledge from before a change forward as a new revision; an approver's --as/--token";
|
|
95
|
+
readonly mutation: 'identity-idempotent';
|
|
96
|
+
readonly takesQuery: false;
|
|
97
|
+
readonly flags: readonly [{
|
|
98
|
+
readonly name: 'repo';
|
|
99
|
+
readonly takesValue: true;
|
|
100
|
+
readonly meaning: 'admitted project checkout root';
|
|
101
|
+
}, {
|
|
102
|
+
readonly name: 'as';
|
|
103
|
+
readonly takesValue: true;
|
|
104
|
+
readonly meaning: 'approver account name';
|
|
105
|
+
}, {
|
|
106
|
+
readonly name: 'token';
|
|
107
|
+
readonly takesValue: true;
|
|
108
|
+
readonly meaning: "the approver's password (never a lead token)";
|
|
109
|
+
}, {
|
|
110
|
+
readonly name: 'json';
|
|
111
|
+
readonly takesValue: false;
|
|
112
|
+
readonly meaning: 'versioned structured response';
|
|
113
|
+
}, {
|
|
114
|
+
readonly name: 'db';
|
|
115
|
+
readonly takesValue: true;
|
|
116
|
+
readonly meaning: 'local Toolroll database';
|
|
117
|
+
}];
|
|
118
|
+
readonly audience: 'operator';
|
|
92
119
|
}];
|
|
120
|
+
/** `knowledge apply`: the caller has verified `actor` as an approver of `repo` (never the lead). */
|
|
121
|
+
export declare function runKnowledgeApply(options: Map<string, string | true>, context: KnowledgeCliContext & {
|
|
122
|
+
actor: string | null;
|
|
123
|
+
}): number;
|
|
93
124
|
export type KnowledgeCliContext = {
|
|
94
125
|
store: Store;
|
|
95
126
|
write: (line: string) => void;
|
package/dist/knowledge-cli.js
CHANGED
|
@@ -6,6 +6,7 @@ import { authenticateCoordinator } from './coordinator.js';
|
|
|
6
6
|
import { envelopeJson } from './envelope.js';
|
|
7
7
|
import { repositoryContext } from './repository-context.js';
|
|
8
8
|
import { namedPath } from './names.js';
|
|
9
|
+
import { applySavedKnowledge } from './project-knowledge.js';
|
|
9
10
|
const flags = [
|
|
10
11
|
{ name: 'repo', takesValue: true, meaning: 'admitted project checkout root' },
|
|
11
12
|
{ name: 'base', takesValue: true, meaning: 'exact committed task base; defaults to current HEAD' },
|
|
@@ -14,11 +15,43 @@ const flags = [
|
|
|
14
15
|
{ name: 'token-file', takesValue: true, meaning: 'explicit scoped coordinator credential file' },
|
|
15
16
|
{ name: 'token-env', takesValue: true, meaning: 'environment variable containing a scoped coordinator token' },
|
|
16
17
|
];
|
|
18
|
+
const applyFlags = [
|
|
19
|
+
{ name: 'repo', takesValue: true, meaning: 'admitted project checkout root' },
|
|
20
|
+
{ name: 'as', takesValue: true, meaning: 'approver account name' },
|
|
21
|
+
{ name: 'token', takesValue: true, meaning: "the approver's password (never a lead token)" },
|
|
22
|
+
{ name: 'json', takesValue: false, meaning: 'versioned structured response' },
|
|
23
|
+
{ name: 'db', takesValue: true, meaning: 'local Toolroll database' },
|
|
24
|
+
];
|
|
17
25
|
export const KNOWLEDGE_DESCRIPTORS = [
|
|
18
26
|
{ action: 'search', synopsis: 'find bounded source excerpts and relevant code symbols', mutation: 'none', takesQuery: true, flags },
|
|
19
27
|
{ action: 'impact', synopsis: 'inspect static imports affected by one file or unambiguous symbol', mutation: 'none', takesQuery: true, flags },
|
|
20
28
|
{ action: 'refresh', synopsis: 'refresh the optional local code index; never starts a model or task', mutation: 'identity-idempotent', takesQuery: false, flags },
|
|
29
|
+
// A person's action: the approver carries saved knowledge from before a project change forward.
|
|
30
|
+
{ action: 'apply', synopsis: "carry this project's saved knowledge from before a change forward as a new revision; an approver's --as/--token", mutation: 'identity-idempotent', takesQuery: false, flags: applyFlags, audience: 'operator' },
|
|
21
31
|
];
|
|
32
|
+
/** `knowledge apply`: the caller has verified `actor` as an approver of `repo` (never the lead). */
|
|
33
|
+
export function runKnowledgeApply(options, context) {
|
|
34
|
+
const command = 'knowledge apply';
|
|
35
|
+
const fail = (reason, message) => { context.write(context.json ? envelopeJson({ ok: false, command, reason, message }) : message); return reason === 'usage' ? 2 : 3; };
|
|
36
|
+
for (const key of options.keys())
|
|
37
|
+
if (!applyFlags.some(flag => flag.name === key))
|
|
38
|
+
return fail('usage', `--${key} is not a knowledge apply option.`);
|
|
39
|
+
const repo = options.get('repo');
|
|
40
|
+
if (typeof repo !== 'string' || !repo.trim() || repo.length > 4096 || /[\x00-\x1f]/.test(repo))
|
|
41
|
+
return fail('usage', 'Use --repo with the project checkout root.');
|
|
42
|
+
if (context.actor === null)
|
|
43
|
+
return fail('unauthenticated', 'An approver of this project applies saved knowledge: --as <you> --token <password>.');
|
|
44
|
+
let revision;
|
|
45
|
+
try {
|
|
46
|
+
revision = applySavedKnowledge(context.store, resolve(repo), context.actor, context.now);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
return fail('failed', error instanceof Error ? error.message : 'Saved knowledge could not be applied.');
|
|
50
|
+
}
|
|
51
|
+
const message = revision === null ? 'Nothing to apply.' : `Applied saved knowledge as version ${revision}.`;
|
|
52
|
+
context.write(context.json ? envelopeJson({ ok: true, command, result: { revision, message } }) : message);
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
22
55
|
function lines(result) {
|
|
23
56
|
return [
|
|
24
57
|
`Repository context: ${result.index.status}${result.index.engine === 'text-search' ? ' · source search' : ' · code relationships'}`,
|
|
@@ -32,8 +65,8 @@ export async function runKnowledgeCommand(positional, options, context) {
|
|
|
32
65
|
const spec = KNOWLEDGE_DESCRIPTORS.find(s => s.action === positional[0]);
|
|
33
66
|
const command = spec ? `knowledge ${spec.action}` : 'knowledge';
|
|
34
67
|
const fail = (reason, message) => { context.write(context.json ? envelopeJson({ ok: false, command, reason, message }) : message); return reason === 'usage' ? 2 : 3; };
|
|
35
|
-
if (!spec)
|
|
36
|
-
return fail('usage', 'Use knowledge search <query>, impact <file-or-symbol>, or
|
|
68
|
+
if (!spec || spec.action === 'apply')
|
|
69
|
+
return fail('usage', 'Use knowledge search <query>, impact <file-or-symbol>, refresh or apply with --repo PATH.');
|
|
37
70
|
if (positional.length !== (spec.takesQuery ? 2 : 1))
|
|
38
71
|
return fail('usage', spec.takesQuery ? 'Provide one quoted query or source path.' : 'knowledge refresh takes no query.');
|
|
39
72
|
for (const key of options.keys())
|
package/dist/knowledge-ui.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type KnowledgeDraft, type KnowledgeSelection, type KnowledgeView } from './project-knowledge.js';
|
|
2
2
|
export declare const KNOWLEDGE_CSS = ".knowledge{max-width:780px;min-width:0;overflow-wrap:anywhere}.knowledge .card{padding:20px;min-width:0;margin:16px 0}.knowledge h2{margin:0 0 12px}.knowledge form{display:grid;gap:12px;margin:0}.knowledge label{display:grid;gap:6px}.knowledge textarea,.knowledge input,.knowledge select{box-sizing:border-box;width:100%;min-width:0;max-width:100%}.knowledge textarea{resize:vertical;min-height:110px}.knowledge button,.knowledge summary,.knowledge .button-link{min-height:44px}.knowledge button{justify-self:start;white-space:nowrap}.knowledge summary{padding:12px 0;cursor:pointer}.knowledge pre{white-space:pre-wrap;overflow-wrap:anywhere;font:inherit}.knowledge .knowledge-actions{display:flex;gap:12px;flex-wrap:wrap}.knowledge .meta{font-size:.86rem}.knowledge .knowledge-source{display:block}.knowledge details+details{border-top:1px solid var(--so-line)}.memory-stats{display:flex;flex-wrap:wrap;gap:.4rem}.memory-stats span{padding:.15rem .55rem;border-radius:.375rem;background:var(--so-neutral-soft);color:var(--so-neutral-ink)}@media(max-width:600px){.knowledge .card{padding:16px}.knowledge h2{font-size:1.1rem}}";
|
|
3
3
|
export declare function knowledgeHtml(view: KnowledgeView, csrf: string, canManage: boolean, draft?: KnowledgeDraft, error?: string): string;
|
|
4
4
|
export declare function knowledgeContextHtml(snapshot: KnowledgeSelection | null): string;
|
package/dist/knowledge-ui.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { STALE_KNOWLEDGE } from './project-knowledge.js';
|
|
1
2
|
const e = (s) => String(s ?? '').replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
|
2
3
|
const fields = (data) => Object.entries(data).map(([k, v]) => `<input type="hidden" name="${e(k)}" value="${e(v)}">`).join('');
|
|
3
4
|
export const KNOWLEDGE_CSS = `.knowledge{max-width:780px;min-width:0;overflow-wrap:anywhere}.knowledge .card{padding:20px;min-width:0;margin:16px 0}.knowledge h2{margin:0 0 12px}.knowledge form{display:grid;gap:12px;margin:0}.knowledge label{display:grid;gap:6px}.knowledge textarea,.knowledge input,.knowledge select{box-sizing:border-box;width:100%;min-width:0;max-width:100%}.knowledge textarea{resize:vertical;min-height:110px}.knowledge button,.knowledge summary,.knowledge .button-link{min-height:44px}.knowledge button{justify-self:start;white-space:nowrap}.knowledge summary{padding:12px 0;cursor:pointer}.knowledge pre{white-space:pre-wrap;overflow-wrap:anywhere;font:inherit}.knowledge .knowledge-actions{display:flex;gap:12px;flex-wrap:wrap}.knowledge .meta{font-size:.86rem}.knowledge .knowledge-source{display:block}.knowledge details+details{border-top:1px solid var(--so-line)}.memory-stats{display:flex;flex-wrap:wrap;gap:.4rem}.memory-stats span{padding:.15rem .55rem;border-radius:.375rem;background:var(--so-neutral-soft);color:var(--so-neutral-ink)}@media(max-width:600px){.knowledge .card{padding:16px}.knowledge h2{font-size:1.1rem}}`;
|
|
@@ -8,6 +9,12 @@ export function knowledgeHtml(view, csrf, canManage, draft, error) {
|
|
|
8
9
|
const referenceFields = (d) => `<label>Name<input name="title" maxlength="120" value="${e(d.title)}" required></label><label>Reference text<textarea name="content" rows="5" maxlength="12000">${e(d.content)}</textarea></label><details${d.path ? ' open' : ''}><summary>Use a project document</summary><label>Committed .md or .txt path<input name="path" value="${e(d.path)}" placeholder="docs/design-guide.md"></label><p class="meta">Uses the committed file instead of the text above. Save again to refresh it.</p></details><button type="submit">Save reference</button>`;
|
|
9
10
|
const refs = view.knowledge.references.map(ref => `<details class="card"${draft?.id === ref.id ? ' open' : ''}><summary><strong>${e(ref.title)}</strong></summary>${ref.path ? `<span class="meta knowledge-source">${e(ref.path)} · ${e(ref.sourceRevision?.slice(0, 7))}</span>` : ''}<pre>${e(ref.content)}</pre>${manage ? `<details${draft?.id === ref.id ? ' open' : ''}><summary>Edit reference</summary>${form('save', referenceFields(draft?.id === ref.id ? draft : { title: ref.title, content: ref.path ? '' : ref.content, path: ref.path ?? '' }), { id: ref.id })}<details><summary>Remove reference</summary>${form('remove', '<p>Remove from future tasks. Previous run context stays available.</p><button>Remove reference</button>', { id: ref.id })}</details></details>` : ''}</details>`).join('');
|
|
10
11
|
const history = view.history.map(h => `<li><a class="button-link" href="/settings/knowledge?repo=${encodeURIComponent(view.repo)}&version=${h.revision}">Version ${h.revision}</a> · ${e(h.actor)} · ${e(h.at.slice(0, 16).replace('T', ' '))} UTC</li>`).join('');
|
|
12
|
+
if (view.stale) {
|
|
13
|
+
// One notice, the saved knowledge on demand, one approver action. Editing waits until it is applied.
|
|
14
|
+
const saved = view.stale.knowledge;
|
|
15
|
+
const review = `<details><summary>Review saved knowledge</summary><pre class="knowledge-instructions">${e(saved.instructions || 'No project instructions.')}</pre>${saved.references.map(ref => `<details><summary>${e(ref.title)}</summary><pre>${e(ref.content)}</pre></details>`).join('')}</details>`;
|
|
16
|
+
return `<section class="knowledge knowledge-library">${error ? `<p class="problem" role="alert">${e(error)}</p>` : ''}<section class="card knowledge-stale"><p role="status">${e(STALE_KNOWLEDGE)}.</p>${review}${manage ? form('apply', '<button>Apply saved knowledge</button>') : ''}</section></section>`;
|
|
17
|
+
}
|
|
11
18
|
const instructions = `<pre class="knowledge-instructions">${e(view.knowledge.instructions || 'No project instructions yet.')}</pre>`;
|
|
12
19
|
const editor = manage ? `<details class="knowledge-editor"${draft?.instructions !== undefined ? ' open' : ''}><summary>${view.knowledge.instructions ? 'Edit instructions' : 'Add instructions'}</summary>${form('instructions', `<textarea aria-label="Project instructions" name="instructions" rows="5" maxlength="4000" placeholder="Goals, preferences, and what good work looks like…">${e(draft?.instructions ?? view.knowledge.instructions)}</textarea><button>Save instructions</button>`)}</details>` : '';
|
|
13
20
|
return `<section class="knowledge knowledge-library">${error ? `<p class="problem" role="alert">${e(error)}</p>` : ''}<section class="knowledge-intro"><h2>Instructions</h2>${instructions}${editor}</section><section><h2>References</h2>${refs || '<p class="meta">Add a spec, design guide, or useful project document.</p>'}${manage ? `<details class="card"${draft?.title && !draft.id ? ' open' : ''}><summary>Add reference</summary>${form('save', referenceFields(draft?.id ? {} : draft ?? {}))}</details>` : ''}</section>${history ? `<details><summary>Change history</summary><ol>${history}</ol></details>` : ''}</section>`;
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* What the lead can rely on right now, in one read (get_capabilities): the agents a project's route would run, the
|
|
3
|
-
* workers that would run them, the project's tools and skills, the integrations, and the project's checks. Every entry
|
|
4
|
-
* says what it lets the lead do and, when it can't, the one next step and the settings control (show_control) that
|
|
5
|
-
* takes the owner there. Everything here reads what is already recorded — runner readiness reports, sign-in pauses,
|
|
6
|
-
* plan-limit readings, tool tests, skill tests, integration checks — and never probes anything that spends.
|
|
7
|
-
*/
|
|
8
1
|
import type { Store } from "./store.js";
|
|
9
2
|
import type { ChatControl } from "./chat-controls.js";
|
|
10
3
|
import type { ProviderId } from "./provider.js";
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the lead can rely on right now, in one read (get_capabilities): the agents a project's route would run, the
|
|
3
|
+
* workers that would run them, the project's tools and skills, the integrations, and the project's checks. Every entry
|
|
4
|
+
* says what it lets the lead do and, when it can't, the one next step and the settings control (show_control) that
|
|
5
|
+
* takes the owner there. Everything here reads what is already recorded — runner readiness reports, sign-in pauses,
|
|
6
|
+
* plan-limit readings, tool tests, skill tests, integration checks — and never probes anything that spends.
|
|
7
|
+
*/
|
|
8
|
+
import { projectBatchChecks } from "./batch-policy.js";
|
|
1
9
|
import { resolveRouteCandidates } from "./agentconfig.js";
|
|
2
10
|
import { authPauseOf, messageCommand, providerName } from "./provider-auth.js";
|
|
3
11
|
import { readAuthMode, SUBSCRIPTION_CAPABLE } from "./keys.js";
|
|
@@ -136,7 +144,9 @@ function checksOf(store, repo) {
|
|
|
136
144
|
return { ...base, state: "Off", ok: false, next: "Builds are not checked. Turn checks on for the project if results need them.", link: "projects" };
|
|
137
145
|
if (!releaseCheck)
|
|
138
146
|
return { ...base, state: `${CHECK_LEVEL_WORDS[level]} · no release check`, ok: false, next: "No release check is set up, so no result can show a passing check. Set one for the project.", link: "projects" };
|
|
139
|
-
|
|
147
|
+
// Batch checks are changed in Settings → Projects → Checks (or `project checks --batch on|off`), never from chat.
|
|
148
|
+
const batched = level === "full" && projectBatchChecks(store, repo).on ? " · batched with results that finish together" : "";
|
|
149
|
+
return { ...base, state: level === "quick" && !quickCheck ? "Quick · runs the release check" : `${CHECK_LEVEL_WORDS[level]} · release check set${batched}`, ok: true, next: null, link: null };
|
|
140
150
|
}
|
|
141
151
|
function workersOf(store, repos, now) {
|
|
142
152
|
const live = store.handle.prepare(`SELECT ref.external_id AS task, ref.repo AS repo FROM claim JOIN task_ref AS ref ON ref.id = claim.task_ref
|
package/dist/mate-doors.js
CHANGED
|
@@ -15,7 +15,7 @@ import { executeSharedAction, sharedActionPayload, sharedActionAllowsChallenge,
|
|
|
15
15
|
* silent re-read.
|
|
16
16
|
*/
|
|
17
17
|
import { isCheckLevel, setTaskCheckLevel } from "./check-levels.js";
|
|
18
|
-
import { verifiedAuthor } from "./store.js";
|
|
18
|
+
import { mateTurnKeepsProposals, verifiedAuthor } from "./store.js";
|
|
19
19
|
import { isVerifiedApprover, reproveApprover } from "./principal.js";
|
|
20
20
|
import { TeamLeads } from './team-leads.js';
|
|
21
21
|
import { fileTaskProposal } from "./proposal.js";
|
|
@@ -97,7 +97,7 @@ export function confirmMateProposal(store, who, proposalId, now, options) {
|
|
|
97
97
|
return { ok: false, kind: proposal.kind, reason: "ceiling-changed", said: "the admitted projects changed since this was proposed — it cannot be confirmed" };
|
|
98
98
|
}
|
|
99
99
|
const turn = store.getMateTurn(proposal.turn);
|
|
100
|
-
if (turn
|
|
100
|
+
if (!mateTurnKeepsProposals(turn) || proposal.state === "drafting") {
|
|
101
101
|
return { ok: false, kind: proposal.kind, reason: "turn-not-answered", said: "this proposal's turn did not finish — it cannot be confirmed" };
|
|
102
102
|
}
|
|
103
103
|
if (proposal.kind === "cancel") {
|
package/dist/mate.d.ts
CHANGED
|
@@ -93,6 +93,7 @@ export type MateTurnOutcome = {
|
|
|
93
93
|
failed: MateFailure;
|
|
94
94
|
message: string;
|
|
95
95
|
unknownSpend: boolean;
|
|
96
|
+
saved?: boolean;
|
|
96
97
|
};
|
|
97
98
|
/** What happened, what it means, and one next step: plain words a person can act on, never internal terms. */
|
|
98
99
|
export declare const MATE_REFUSAL_COPY: Record<MateRefusal, string>;
|
|
@@ -102,7 +103,7 @@ export declare const MATE_CHANNEL_COPY: Record<MateChannelProblem, string>;
|
|
|
102
103
|
export declare const MATE_FAILURE_COPY: {
|
|
103
104
|
readonly stopped: "This reply was stopped before it finished, from another window or by a newer message. Nothing it proposed was kept. Send your message again if you still need an answer.";
|
|
104
105
|
readonly ended: "Your sign-in or this conversation ended while the lead was answering. Nothing it proposed was kept. Sign in again or start a new chat, then send your message again.";
|
|
105
|
-
readonly tooLong:
|
|
106
|
+
readonly tooLong: string;
|
|
106
107
|
readonly secretRead: "Something the lead read looked like a password or key, so it stopped before sending it anywhere. Nothing was kept. Remove the password or key from that task or note, then ask again.";
|
|
107
108
|
readonly secretReply: "The lead's reply contained something that looked like a password or key, so it was thrown away. Nothing was kept. Ask again, without asking for a password or key.";
|
|
108
109
|
readonly paused: "An earlier reply stopped before its cost was known, so chat is paused. This message wasn't answered. Confirm that earlier reply on the Chat page, then send again.";
|
|
@@ -111,7 +112,7 @@ export declare const MATE_FAILURE_COPY: {
|
|
|
111
112
|
readonly unreadable: "The chat provider sent back an answer that couldn't be read, so it was thrown away. Nothing was kept. Send your message again.";
|
|
112
113
|
readonly signIn: "The chat provider couldn't answer; its sign-in may have expired. Nothing was kept. Sign in to it again on this computer, then send your message again.";
|
|
113
114
|
readonly refused: "The chat provider turned the request down. Nothing was kept or charged. Try again in a minute; if it keeps happening, check the provider in Settings → Lead.";
|
|
114
|
-
readonly tooLongUnknownCost:
|
|
115
|
+
readonly tooLongUnknownCost: string;
|
|
115
116
|
readonly lostUnknownCost: "The chat provider stopped responding partway through, and the cost isn't known, so chat is paused. Nothing it proposed was kept. Confirm the cost on the Chat page to turn chat back on.";
|
|
116
117
|
readonly unreadableUnknownCost: "The chat provider sent back an answer that couldn't be read, and its cost isn't known, so chat is paused. Nothing it proposed was kept. Confirm the cost on the Chat page to turn chat back on.";
|
|
117
118
|
readonly empty: "The lead sent back an empty answer. Nothing was kept. Send your message again.";
|
|
@@ -119,6 +120,9 @@ export declare const MATE_FAILURE_COPY: {
|
|
|
119
120
|
};
|
|
120
121
|
/** A failed turn's saved reason as plain words, for a surface that reads the turn back later (a restart, a delivery retry). */
|
|
121
122
|
export declare function mateFailureText(reason: string | null): string;
|
|
123
|
+
/** How long a provider that ignored its abort at the turn's deadline is waited for before the turn ends without it.
|
|
124
|
+
* Its process group was already killed at the deadline; this only bounds the wait for the transport to say so. */
|
|
125
|
+
export declare const MATE_ABORT_GRACE_MS = 5000;
|
|
122
126
|
/** The last messages of the thread as provider-neutral history, newest kept first until the byte cap. */
|
|
123
127
|
export declare function historyFor(store: Store, thread: number, queuedMessageId?: number): MateHistoryMessage[];
|
|
124
128
|
/** "read 3 · proposed 1 · 2 steps" — counts only, never results (ruling 11). */
|