toolroll 0.9.103 → 0.9.104
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 +8 -0
- package/README.md +10 -8
- package/dist/assignment.d.ts +32 -0
- package/dist/assignment.js +140 -4
- package/dist/browser/workspace.js +25 -25
- package/dist/browser-workspace.d.ts +13 -0
- package/dist/contracts/console-api.d.ts +3 -2
- package/dist/contracts/console-api.js +4 -2
- package/dist/desktop-update.d.ts +1 -1
- package/dist/keymap.d.ts +3 -1
- package/dist/keymap.js +3 -0
- package/dist/local-owner-acts.d.ts +83 -0
- package/dist/local-owner-acts.js +221 -0
- package/dist/local-owner.d.ts +12 -1
- package/dist/local-owner.js +26 -0
- package/dist/operate-remote-arguments.js +4 -2
- package/dist/operate.js +76 -1
- package/dist/plan-auto.js +5 -0
- package/dist/project-knowledge.d.ts +3 -3
- package/dist/public-access.d.ts +9 -0
- package/dist/public-access.js +15 -0
- package/dist/pull-request-flow.js +9 -0
- package/dist/release-card.d.ts +15 -1
- package/dist/release-card.js +35 -12
- package/dist/result-completion.d.ts +6 -2
- package/dist/result-completion.js +13 -7
- package/dist/scope.d.ts +32 -1
- package/dist/scope.js +110 -25
- package/dist/serve.js +13 -14
- package/dist/server/guards.d.ts +1 -0
- package/dist/server/guards.js +11 -1
- package/dist/server/remote.d.ts +1 -0
- package/dist/server/render-results.d.ts +4 -0
- package/dist/server/render-tasks.d.ts +9 -0
- package/dist/server/render-tasks.js +4 -4
- package/dist/server/route-table.js +4 -0
- package/dist/server/runtime.d.ts +1 -0
- package/dist/server/task-views.js +3 -0
- package/dist/server/tasks.d.ts +25 -0
- package/dist/server/tasks.js +46 -6
- package/dist/surface.js +12 -2
- package/dist/task-outcome-cli.d.ts +18 -0
- package/dist/task-outcome-cli.js +60 -2
- package/dist/tui-contract.d.ts +61 -3
- package/dist/tui-source.js +91 -20
- package/dist/tui.js +54 -54
- package/package.json +2 -2
package/dist/tui-source.js
CHANGED
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
* nothing ever prompts on the terminal the screen owns.
|
|
9
9
|
*/
|
|
10
10
|
import { dirname, join } from "node:path";
|
|
11
|
-
import { assignmentOf } from "./assignment.js";
|
|
11
|
+
import { assignmentOf, owedAcceptance } from "./assignment.js";
|
|
12
|
+
import { familyChangedFiles } from "./result-completion.js";
|
|
13
|
+
import { checkBackingOf, receiptChecksOf, requirementWordOf } from "./task-status.js";
|
|
12
14
|
import { checklistLines, mergeChecklistOf } from "./merge-checklist.js";
|
|
13
15
|
import { runAsLocalOwner } from "./local-owner.js";
|
|
14
16
|
import { snapshotLiveRuns } from "./peek-cli.js";
|
|
@@ -21,14 +23,18 @@ import { memoryWaiting } from "./lesson-notes.js";
|
|
|
21
23
|
import { homeGroupOf } from "./home-groups.js";
|
|
22
24
|
import { terminalText } from "./terminal-text.js";
|
|
23
25
|
import { welcomeHere, welcomeLines } from "./welcome-facts.js";
|
|
26
|
+
import { planSourceOf, retryPinsOf } from "./local-owner-acts.js";
|
|
24
27
|
const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
25
28
|
const words = (value, fallback = "") => typeof value === "string" ? value : fallback;
|
|
26
29
|
/** Text for the screen: whatever wrote it (an agent, a person, a project's name), escapes and controls never reach the
|
|
27
30
|
* terminal (terminal-text.ts). Every displayed string below goes through here; ids and digests are only acted on. */
|
|
28
31
|
const shown = (value, fallback = "") => terminalText(words(value, fallback));
|
|
29
|
-
/** What a refused write says, and whether it is a local-owner refusal (the screen then leads to a password)
|
|
32
|
+
/** What a refused write says, and whether it is a local-owner refusal (the screen then leads to a password), or a
|
|
33
|
+
* result that changed since the screen read it. */
|
|
30
34
|
function refusalOf(envelope) {
|
|
31
35
|
const message = shown(envelope.message, "That was refused.");
|
|
36
|
+
if (envelope.reason === "stale")
|
|
37
|
+
return { ok: false, message, needsPassword: false, changed: true };
|
|
32
38
|
return { ok: false, message, needsPassword: envelope.reason === "refused" || envelope.reason === "machine-proof" || envelope.reason === "person-required" || /password/i.test(message) };
|
|
33
39
|
}
|
|
34
40
|
const routeLines = (store, id) => { const words = routeChangeOf(store, id); return words === null ? [] : [words]; };
|
|
@@ -116,6 +122,8 @@ export function createTuiSource(options) {
|
|
|
116
122
|
return {
|
|
117
123
|
id, title: shown(task["title"], id), status: shown(status["label"], words(work["state"])), detail: shown(status["detail"]),
|
|
118
124
|
plan, liveRunId: typeof work["liveRunId"] === "number" ? work["liveRunId"] : null, hasResult, decisions, ready,
|
|
125
|
+
planAsk: store === null ? null : planAskOf(store, id),
|
|
126
|
+
retry: (() => { const pins = store === null ? null : retryPinsOf(store, id); return pins === null ? null : { runId: pins.run, digest: pins.digest }; })(),
|
|
119
127
|
};
|
|
120
128
|
},
|
|
121
129
|
live(id) {
|
|
@@ -141,9 +149,14 @@ export function createTuiSource(options) {
|
|
|
141
149
|
let argv;
|
|
142
150
|
let done;
|
|
143
151
|
switch (act.kind) {
|
|
152
|
+
// A task too big to build straight away: filed, then its plan asked for at once (local-owner slice 2), below.
|
|
144
153
|
case "new":
|
|
145
154
|
argv = ["task", "add", act.text, ...(act.repo === null ? [] : ["--repo", act.repo])];
|
|
146
|
-
done = "Filed.
|
|
155
|
+
done = "Filed.";
|
|
156
|
+
break;
|
|
157
|
+
case "plan":
|
|
158
|
+
argv = ["task", "plan", act.id, "--digest", act.source];
|
|
159
|
+
done = "Asked for a plan. You'll approve it before anything is built.";
|
|
147
160
|
break;
|
|
148
161
|
case "build":
|
|
149
162
|
argv = ["task", "new", "--build", act.preview, "--digest", act.digest];
|
|
@@ -165,33 +178,49 @@ export function createTuiSource(options) {
|
|
|
165
178
|
argv = ["decide", String(act.decision), "--choose", act.option];
|
|
166
179
|
done = "Answered.";
|
|
167
180
|
break;
|
|
168
|
-
// Retry
|
|
181
|
+
// Retry: exactly the build and approved plan the screen read (local-owner slice 3), never the current ones.
|
|
169
182
|
case "retry":
|
|
170
|
-
argv = ["task", "
|
|
171
|
-
done = "
|
|
183
|
+
argv = ["task", "requeue", act.id, "--run", String(act.runId), "--digest", act.digest];
|
|
184
|
+
done = "Retrying. A builder starts it next.";
|
|
172
185
|
break;
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
catch {
|
|
180
|
-
digest = undefined;
|
|
181
|
-
}
|
|
182
|
-
if (digest === undefined)
|
|
183
|
-
return { ok: false, message: "This task has no finished result to accept.", needsPassword: false };
|
|
184
|
-
argv = ["task", "complete", act.id, "--digest", digest];
|
|
185
|
-
done = "Accepted.";
|
|
186
|
+
// Accept: exactly the result the Accept screen read, never the one current when this is sent (the command
|
|
187
|
+
// refuses a result that changed meanwhile). The person's yes to their own check, or a reason, only when given.
|
|
188
|
+
case "complete":
|
|
189
|
+
argv = ["task", "complete", act.id, "--run", String(act.runId), "--digest", act.digest, ...(act.checked ? ["--checked"] : []), ...(act.reason === null ? [] : ["--reason", act.reason])];
|
|
190
|
+
done = "Accepted. The task is done.";
|
|
186
191
|
break;
|
|
187
|
-
}
|
|
188
192
|
}
|
|
189
193
|
const answer = await command(argv);
|
|
190
194
|
if (answer.ok !== true)
|
|
191
195
|
return refusalOf(answer);
|
|
196
|
+
if (act.kind === "complete" && answer["replayed"] === true)
|
|
197
|
+
return { ok: true, message: "Already accepted. The task is done." };
|
|
198
|
+
if (act.kind === "plan" && answer["replayed"] === true)
|
|
199
|
+
return { ok: true, message: "Already asked for a plan." };
|
|
200
|
+
if (act.kind === "retry" && answer["replayed"] === true)
|
|
201
|
+
return { ok: true, message: "Already retrying. A builder starts it next." };
|
|
192
202
|
const filed = act.kind === "new" ? words(record(answer["task"])["id"], words(answer["id"])) : act.kind === "build" ? words(answer["task"]) : "";
|
|
203
|
+
// Filed in a project: ask for its plan now, bound to the task just filed (the screen promised a plan).
|
|
204
|
+
if (act.kind === "new" && filed !== "" && act.repo !== null) {
|
|
205
|
+
const store = read();
|
|
206
|
+
const ask = store === null ? null : planAskOf(store, filed);
|
|
207
|
+
const asked = ask === null ? null : await command(["task", "plan", filed, "--digest", ask.source]);
|
|
208
|
+
const message = asked?.ok === true ? "Filed. Planning: you'll approve the plan before anything is built." : `Filed. It wasn't sent for a plan: ${shown(asked?.message, "it can't be planned now.")}`;
|
|
209
|
+
return { ok: true, message, id: filed };
|
|
210
|
+
}
|
|
193
211
|
return { ok: true, message: done, ...(filed === "" ? {} : { id: filed }) };
|
|
194
212
|
},
|
|
213
|
+
async result(id) {
|
|
214
|
+
const store = read();
|
|
215
|
+
if (store === null)
|
|
216
|
+
return null;
|
|
217
|
+
try {
|
|
218
|
+
return resultOf(store, id, clock(), evidenceRoot);
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
},
|
|
195
224
|
async fastTerms(text, repo) {
|
|
196
225
|
if (repo === null)
|
|
197
226
|
return null;
|
|
@@ -242,3 +271,45 @@ function learningLine(store, repos, now) {
|
|
|
242
271
|
const parts = [flaky === 0 ? "" : `${flaky} flaky test${flaky === 1 ? "" : "s"}: open the project's Flaky tests in the browser`, memory === 0 ? "" : `${memory} project memory suggestion${memory === 1 ? "" : "s"}: open Settings → Knowledge`].filter(Boolean);
|
|
243
272
|
return parts.length === 0 ? null : parts.join(" · ");
|
|
244
273
|
}
|
|
274
|
+
/** The Accept screen's one read of a task's current result (local-owner slice 1): which build and receipt it pins, and
|
|
275
|
+
* what the result page would show about it — the agent's summary, the files changed, each requirement and how it is
|
|
276
|
+
* shown, the actual check status, and what accepting it needs from the person. Text from outside is cleaned here. */
|
|
277
|
+
function resultOf(store, id, now, evidenceRoot) {
|
|
278
|
+
const assignment = assignmentOf(store, id, now, { principal: "operator", repos: null }, evidenceRoot);
|
|
279
|
+
const receipt = assignment?.receipt ?? null;
|
|
280
|
+
if (assignment === null || receipt === null)
|
|
281
|
+
return null;
|
|
282
|
+
const blockers = assignment.readiness?.blockers ?? [];
|
|
283
|
+
const owed = assignment.state === "ready-to-check" ? owedAcceptance(receipt, blockers) : null;
|
|
284
|
+
// An exception's own blocker is what its reason answers; any other one stands in the way of accepting.
|
|
285
|
+
const standing = blockers.filter(one => !(owed === "exception" && one.key === "criteria")).map(one => terminalText(one.message));
|
|
286
|
+
const notNow = assignment.state === "complete" ? "This result is already accepted." : assignment.state === "ready-to-check" ? null : terminalText(assignment.detail || "This result isn't ready to accept.");
|
|
287
|
+
const changed = familyChangedFiles(store, receipt.taskId, receipt.runId, evidenceRoot);
|
|
288
|
+
const backing = checkBackingOf(receiptChecksOf(receipt));
|
|
289
|
+
let checks = [];
|
|
290
|
+
try {
|
|
291
|
+
const list = mergeChecklistOf(store, id, now, { principal: "operator", repos: null }, evidenceRoot);
|
|
292
|
+
checks = list === null ? [] : checklistLines(list).map(line => terminalText(line));
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
checks = [];
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
id, runId: receipt.runId, digest: receipt.digest, title: terminalText(assignment.title),
|
|
299
|
+
ready: notNow === null && standing.length === 0, blockers: notNow === null ? standing : [notNow],
|
|
300
|
+
summary: (receipt.agentReport ?? "").split("\n").map(line => terminalText(line).trimEnd()).filter(line => line.trim() !== "").slice(0, 8),
|
|
301
|
+
files: changed === null ? null : { names: changed.slice(0, 10).map(name => terminalText(name)), more: Math.max(0, changed.length - 10) },
|
|
302
|
+
requirements: (receipt.proof?.matrix ?? []).map(row => ({ word: requirementWordOf(row, receipt.proof?.verdict ?? null, backing), statement: terminalText(row.statement ?? row.id ?? "") })),
|
|
303
|
+
checks, owed,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
/** Whether a plan can be asked for this task now, and the planning source a request binds to (local-owner slice 2): a
|
|
307
|
+
* queued task with no plan asked or drafted and no approved plan. What the screen offers only; the command decides. */
|
|
308
|
+
function planAskOf(store, id) {
|
|
309
|
+
const ref = store.lookupRef(id);
|
|
310
|
+
const scope = store.getScope(id);
|
|
311
|
+
if (ref === null || ref.plan !== null || store.getTask(id)?.state !== "queued" || (scope !== null && scope.approvedAt !== null && scope.approvedDigest === scope.digest))
|
|
312
|
+
return null;
|
|
313
|
+
const source = planSourceOf(store, id);
|
|
314
|
+
return source === null ? null : { source };
|
|
315
|
+
}
|