toolroll 0.9.13 → 0.9.15
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 +23 -0
- package/dist/assignment-ui.d.ts +1 -0
- package/dist/assignment-ui.js +8 -5
- package/dist/assignment.d.ts +32 -0
- package/dist/assignment.js +81 -7
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +38 -38
- package/dist/browser-crew.d.ts +2 -1
- package/dist/browser-crew.js +4 -2
- package/dist/browser-workspace.d.ts +109 -11
- package/dist/chat-actions.d.ts +1 -1
- package/dist/chat-actions.js +6 -4
- package/dist/chat-delivery-state.d.ts +1 -1
- package/dist/chat-voice.d.ts +3 -3
- package/dist/chat-voice.js +3 -3
- package/dist/demo.js +82 -2
- package/dist/disclosure.d.ts +6 -0
- package/dist/disclosure.js +13 -0
- package/dist/dispatch.js +7 -2
- package/dist/lead-status.js +2 -2
- package/dist/needs-you.d.ts +72 -0
- package/dist/needs-you.js +193 -1
- package/dist/result-acts.d.ts +51 -5
- package/dist/result-acts.js +36 -3
- package/dist/result-review.d.ts +4 -3
- package/dist/result-review.js +7 -3
- package/dist/serve.d.ts +3 -0
- package/dist/serve.js +1235 -711
- package/dist/slack-chat.js +1 -1
- package/dist/task-status.d.ts +24 -3
- package/dist/task-status.js +48 -17
- package/dist/telegram-progress.js +2 -2
- package/dist/test-temp.js +1 -1
- package/dist/transitions-recipes.d.ts +1 -1
- package/dist/transitions-recipes.js +5 -9
- package/dist/when-html.d.ts +19 -2
- package/dist/when-html.js +78 -11
- package/dist/work-index.d.ts +3 -1
- package/dist/work-index.js +18 -5
- package/dist/workspace-motion.js +3 -3
- package/dist/workspace-ui.d.ts +86 -6
- package/dist/workspace-ui.js +143 -17
- package/package.json +1 -1
package/dist/needs-you.js
CHANGED
|
@@ -28,7 +28,7 @@ export const NEEDS = {
|
|
|
28
28
|
"connect-builder": { sentence: () => "No builder works on this project yet. Connect one so the task can start.", action: { code: "start-worker", label: "Connect a builder" } },
|
|
29
29
|
"start-builder": { sentence: () => "The builder for this project is offline. Start it and the task begins on its own.", action: { code: "start-worker", label: "Start the builder" } },
|
|
30
30
|
vanished: { sentence: context => `${Build(context)} stopped without finishing. Check it, then retry.`, action: { code: "reconcile-run", label: "Check the build" } },
|
|
31
|
-
"review-result": { sentence: () => "Check the result, then accept it or ask for changes.", action: { code: "open-result", label: "
|
|
31
|
+
"review-result": { sentence: () => "Check the result, then accept it or ask for changes.", action: { code: "open-result", label: "Open result" } },
|
|
32
32
|
// Accepting it would not help: it was built to terms that are no longer the plan.
|
|
33
33
|
rebuild: { sentence: context => `${Build(context)} was made to an earlier plan. Build it again to the current plan.`, action: { code: "retry-task", label: "Build again" } },
|
|
34
34
|
"earlier-version": { sentence: () => "An earlier version of this task still needs you. Finish it first.", action: { code: "inspect-task", label: "Open earlier version" } },
|
|
@@ -36,6 +36,41 @@ export const NEEDS = {
|
|
|
36
36
|
};
|
|
37
37
|
export const ASKS = ["decide", "review", "unblock"];
|
|
38
38
|
export const ASK_LABEL = { decide: "Decide", review: "Review", unblock: "Unblock" };
|
|
39
|
+
/** `mismatch`: the saved report doesn't match its changes; `planChanged`: the plan changed after it was built. */
|
|
40
|
+
export function askChipOf(read) {
|
|
41
|
+
if (read.headline === "Failed")
|
|
42
|
+
return "Failed";
|
|
43
|
+
if (read.planChanged === true || read.need === "rebuild")
|
|
44
|
+
return "Plan changed";
|
|
45
|
+
if (read.mismatch === true)
|
|
46
|
+
return "Mismatch";
|
|
47
|
+
if (read.headline === "Ready for review" || read.need === "review-result" || read.ask === "review")
|
|
48
|
+
return "Result";
|
|
49
|
+
if (read.need === "approval")
|
|
50
|
+
return "Plan";
|
|
51
|
+
if (read.need === "start-builder" || read.need === "connect-builder")
|
|
52
|
+
return "Builder offline";
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
export function resultHoldUpSentence(facts) {
|
|
56
|
+
if (facts.hold != null && facts.hold.trim() !== "" && !hasInternalWords(facts.hold))
|
|
57
|
+
return `On hold: ${facts.hold.trim().replace(/[.\s]*$/, "")}. Release the hold to accept it.`;
|
|
58
|
+
if (facts.hold != null)
|
|
59
|
+
return "A hold is on this result. Release it to accept the result.";
|
|
60
|
+
if (facts.planChanged)
|
|
61
|
+
return "The plan changed after this was built. Build it again to the current plan.";
|
|
62
|
+
if (facts.unapproved)
|
|
63
|
+
return "The current plan isn't approved. Approve it, then review the result.";
|
|
64
|
+
if (facts.question)
|
|
65
|
+
return "A question is still open. Answer it, then review the result.";
|
|
66
|
+
if (facts.running)
|
|
67
|
+
return "A build is still running on this task.";
|
|
68
|
+
if (facts.unfinished)
|
|
69
|
+
return "An earlier attempt never finished. Check it, then review the result.";
|
|
70
|
+
if (facts.noCommit)
|
|
71
|
+
return facts.report ? "The report is missing or incomplete. Open it to see what was saved." : "The build saved no commit to review. Open the result to see what was kept.";
|
|
72
|
+
return "Open the result to see what it needs before you can accept it.";
|
|
73
|
+
}
|
|
39
74
|
export const NEED_ASK = {
|
|
40
75
|
approval: "decide", card: "decide", answer: "decide", questions: "decide", "choose-project": "decide", "define-task": "decide",
|
|
41
76
|
"fix-request": "decide", "choose-agent": "decide",
|
|
@@ -90,3 +125,160 @@ export const CODE_NEED = {
|
|
|
90
125
|
"no-build-record": "review-result", "verification-needed": "review-result", "evidence-mismatch": "review-result",
|
|
91
126
|
"record-incomplete": "review-result", "evidence-damaged": "review-result",
|
|
92
127
|
};
|
|
128
|
+
/** A stopped run's recorded reason code, in words (the run record, the thread, a failed task's status). */
|
|
129
|
+
export const RUN_REASON_WORDS = {
|
|
130
|
+
agent: "the agent failed",
|
|
131
|
+
"agent-reported": "the agent reported it could not finish",
|
|
132
|
+
"no-op": "nothing changed when something should have",
|
|
133
|
+
"no-handoff": "the agent stopped before handing off; its work was kept and it is being resumed",
|
|
134
|
+
"moved-head": "the branch moved underneath the build",
|
|
135
|
+
"moved-branch": "the branch moved underneath the build",
|
|
136
|
+
timeout: "ran out of time",
|
|
137
|
+
git: "a git step failed",
|
|
138
|
+
"malformed-decision": "the agent's question was malformed",
|
|
139
|
+
"malformed-plan": "the plan was malformed",
|
|
140
|
+
fenced: "another worker took the task over",
|
|
141
|
+
unapproved: "the scope was not approved",
|
|
142
|
+
"scope-changed": "the scope changed after approval",
|
|
143
|
+
capability: "a requirement was missing",
|
|
144
|
+
setup: "the workspace preparation step failed",
|
|
145
|
+
"provider-init": "the agent could not start",
|
|
146
|
+
"commit-failure": "the commit failed",
|
|
147
|
+
"protected-branch": "refused to touch a protected branch",
|
|
148
|
+
"wrong-branch": "the checkout was on the wrong branch",
|
|
149
|
+
"not-leased": "the lease was not valid",
|
|
150
|
+
"no-claim": "the lease was not valid",
|
|
151
|
+
"not-yours": "the lease was not valid",
|
|
152
|
+
"no-run-record": "the run record was missing",
|
|
153
|
+
"missing-mailbox": "the resume mailbox could not be read",
|
|
154
|
+
"unreadable-mailbox": "the resume mailbox could not be read",
|
|
155
|
+
"revision-brief": "the revision brief could not be read",
|
|
156
|
+
"repaired-park": "resumed from a parked question",
|
|
157
|
+
stopped: "stopped by the operator",
|
|
158
|
+
acceptance: "the result didn't meet its signed requirements",
|
|
159
|
+
evidence: "its evidence could not be saved",
|
|
160
|
+
interrupted: "the attempt was interrupted",
|
|
161
|
+
orphaned: "the attempt lost its worker",
|
|
162
|
+
"retryable-infra": "a temporary problem on the machine stopped it",
|
|
163
|
+
unknown: "the attempt stopped unexpectedly",
|
|
164
|
+
"spawn-failed": "the agent process could not start",
|
|
165
|
+
"provider-protocol": "the agent's reply could not be read",
|
|
166
|
+
"auth-expired": "the agent's sign-in expired",
|
|
167
|
+
"budget-unenforceable": "its spending limit could not be enforced",
|
|
168
|
+
"stale-source": "the request changed while the plan was being made",
|
|
169
|
+
"stale-approval": "the approval no longer matched the scope",
|
|
170
|
+
"source-invalid": "the plan's source could not be recorded",
|
|
171
|
+
"external-closed": "the linked issue was closed while it was being built",
|
|
172
|
+
"repair-admission": "the repair could not start",
|
|
173
|
+
"reviewer-error": "the review failed",
|
|
174
|
+
"malformed-report": "the scout's report was malformed",
|
|
175
|
+
"attempts-exhausted": "it failed too many times in a row",
|
|
176
|
+
"plan-revised": "the plan changed, so a fresh attempt took over",
|
|
177
|
+
};
|
|
178
|
+
/** A run's recorded reason in words. A recorded code is never shown as it is
|
|
179
|
+
* stored: one with no words of its own reads as an unexpected stop; a reason
|
|
180
|
+
* already written as words (it has a space) is said as written. */
|
|
181
|
+
export function runReasonWords(reason) {
|
|
182
|
+
const known = RUN_REASON_WORDS[reason];
|
|
183
|
+
if (known !== undefined)
|
|
184
|
+
return known;
|
|
185
|
+
if (reason.startsWith("decision:"))
|
|
186
|
+
return "the agent asked a question";
|
|
187
|
+
return /\s/.test(reason.trim()) ? reason.trim() : "the attempt stopped unexpectedly";
|
|
188
|
+
}
|
|
189
|
+
/** The latest finished build attempt among a task's runs (any order), whatever its outcome: planning and reviewing
|
|
190
|
+
* are not attempts, and an older failure never stands in for a newer attempt. */
|
|
191
|
+
export function latestFinishedAttempt(runs) {
|
|
192
|
+
let latest = null;
|
|
193
|
+
// Ended means a finish time or an outcome: a reconcile can mark a run failed without stamping finished_at.
|
|
194
|
+
for (const run of runs)
|
|
195
|
+
if ((run.finishedAt !== null || (run.outcome ?? null) !== null) && run.role !== "planner" && run.role !== "reviewer" && (latest === null || run.id > latest.id))
|
|
196
|
+
latest = run;
|
|
197
|
+
return latest;
|
|
198
|
+
}
|
|
199
|
+
export const NO_REASON_RECORDED = "No reason was recorded for this attempt.";
|
|
200
|
+
export const INTERNAL_ERROR = "The attempt stopped with an internal error.";
|
|
201
|
+
/** About as long as one plain line reads. */
|
|
202
|
+
export const REASON_LINE_LIMIT = 140;
|
|
203
|
+
/** A recorded reason that is machine output, not words: a stack trace, a file path or "Error:" text. Its detail
|
|
204
|
+
* belongs behind a link, never in the line itself. */
|
|
205
|
+
export function isInternalErrorReason(reason) {
|
|
206
|
+
const text = reason?.trim() ?? "";
|
|
207
|
+
if (text === "" || RUN_REASON_WORDS[text] !== undefined)
|
|
208
|
+
return false;
|
|
209
|
+
return /\b[A-Za-z]*(?:Error|Exception):/.test(text) // Error:, TypeError:, SomeException:
|
|
210
|
+
|| /^\s*at\s+\S+/m.test(text) && /\n/.test(text) // a stack frame below the first line
|
|
211
|
+
// An absolute path of two or more segments (/Users/…, ~/x/y): plain sentences may name a project file (src/ledger.ts).
|
|
212
|
+
|| /(?:^|[\s(“"'])~?\/(?:[\w.@-]+\/)+[\w.@-]+/.test(text)
|
|
213
|
+
|| /[A-Za-z]:\\/.test(text) // C:\…
|
|
214
|
+
|| /\.[cm]?[jt]sx?:\d+/.test(text); // ledger.ts:14
|
|
215
|
+
}
|
|
216
|
+
/** A failed attempt in one plain line: its recorded reason in words (first line only, about 140 characters at most),
|
|
217
|
+
* that it stopped with an internal error when the reason is machine output, or that none was recorded. */
|
|
218
|
+
export function failedAttemptSentence(reason) {
|
|
219
|
+
const code = reason?.trim() ?? "";
|
|
220
|
+
if (code === "")
|
|
221
|
+
return NO_REASON_RECORDED;
|
|
222
|
+
if (isInternalErrorReason(code))
|
|
223
|
+
return INTERNAL_ERROR;
|
|
224
|
+
const words = runReasonWords(code.split(/\r?\n/, 1)[0].trim()).replace(/\s+/g, " ").replace(/[.\s]+$/, "");
|
|
225
|
+
const line = words.length <= REASON_LINE_LIMIT ? words
|
|
226
|
+
: `${words.slice(0, REASON_LINE_LIMIT - 1).replace(/\s+\S*$/, "").replace(/[,;:\s]+$/, "")}…`;
|
|
227
|
+
return `${line.charAt(0).toUpperCase()}${line.slice(1)}${line.endsWith("…") ? "" : "."}`;
|
|
228
|
+
}
|
|
229
|
+
/** What to change before the next attempt, by a stopped run's recorded reason: one plain suggestion, the note Retry
|
|
230
|
+
* starts with. A reason with no suggestion of its own gets the general one. */
|
|
231
|
+
const STOP_SUGGESTIONS = {
|
|
232
|
+
acceptance: "Before handing off, check each signed requirement against the changes.",
|
|
233
|
+
timeout: "Split the work into smaller steps, or name the one part to finish first.",
|
|
234
|
+
"no-op": "Say exactly which files or behaviour should change.",
|
|
235
|
+
agent: "Say what to do differently this time, or name the part to try first.",
|
|
236
|
+
"agent-reported": "Answer what the agent said it was missing, or narrow the task.",
|
|
237
|
+
"provider-init": "Check the agent is signed in and installed, then retry.",
|
|
238
|
+
"spawn-failed": "Check the agent is signed in and installed, then retry.",
|
|
239
|
+
"auth-expired": "Sign the agent in again, then retry.",
|
|
240
|
+
"provider-protocol": "Retry as it is; if it stops the same way, try another agent.",
|
|
241
|
+
setup: "Fix the project's setup step, then retry.",
|
|
242
|
+
git: "Make sure the project's branch is clean and reachable, then retry.",
|
|
243
|
+
"commit-failure": "Make sure the project's commit hooks pass, then retry.",
|
|
244
|
+
"moved-head": "Retry: the next attempt starts from where the branch is now.",
|
|
245
|
+
"moved-branch": "Retry: the next attempt starts from where the branch is now.",
|
|
246
|
+
"retryable-infra": "Retry as it is.",
|
|
247
|
+
interrupted: "Retry as it is.",
|
|
248
|
+
orphaned: "Retry as it is.",
|
|
249
|
+
capability: "Add what the project is missing, then retry.",
|
|
250
|
+
"attempts-exhausted": "Say what to do differently this time, or narrow the task.",
|
|
251
|
+
};
|
|
252
|
+
export const GENERAL_SUGGESTION = "Say what to do differently this time.";
|
|
253
|
+
export function stopSuggestionOf(reason) {
|
|
254
|
+
const code = reason?.trim() ?? "";
|
|
255
|
+
return STOP_SUGGESTIONS[code] ?? GENERAL_SUGGESTION;
|
|
256
|
+
}
|
|
257
|
+
/** A signed requirement in the middle of a sentence: its first letter lowered unless it starts a name or an acronym. */
|
|
258
|
+
function inSentence(statement) {
|
|
259
|
+
const plain = statement.trim().replace(/[.\s]+$/, "");
|
|
260
|
+
return /^[A-Z][a-z]/.test(plain) ? `${plain.charAt(0).toLowerCase()}${plain.slice(1)}` : plain;
|
|
261
|
+
}
|
|
262
|
+
/** The suggestion for a requirement the result missed: make sure of it before handing off. */
|
|
263
|
+
export function missedRequirementSuggestion(statement) {
|
|
264
|
+
return `Before handing off, make sure ${inSentence(statement)}.`;
|
|
265
|
+
}
|
|
266
|
+
/** The suggestion for a check that failed on the result: make it pass, starting with the line it ended on. */
|
|
267
|
+
export function failingCheckSuggestion(line) {
|
|
268
|
+
return `Make the check pass. It ended on: ${line.replace(/[.\s]+$/, "")}.`;
|
|
269
|
+
}
|
|
270
|
+
/** The line for a missed requirement: “Missed: <statement>”, cut to one line. */
|
|
271
|
+
export function missedRequirementLine(statement) {
|
|
272
|
+
const plain = statement.trim().replace(/\s+/g, " ").replace(/[.\s]+$/, "");
|
|
273
|
+
const cut = plain.length <= REASON_LINE_LIMIT - 10 ? plain : `${plain.slice(0, REASON_LINE_LIMIT - 11).replace(/\s+\S*$/, "")}…`;
|
|
274
|
+
return `Missed a requirement: ${cut}${cut.endsWith("…") ? "" : "."}`;
|
|
275
|
+
}
|
|
276
|
+
/** The longest note Retry's field takes. */
|
|
277
|
+
export const RETRY_NOTE_LIMIT = 500;
|
|
278
|
+
/** Retry's note, prefilled with the suggestion: cut to what the field takes, at a word, so the form always posts. */
|
|
279
|
+
export function retryNoteOf(suggestion) {
|
|
280
|
+
const plain = suggestion.trim();
|
|
281
|
+
if (plain.length <= RETRY_NOTE_LIMIT)
|
|
282
|
+
return plain;
|
|
283
|
+
return `${plain.slice(0, RETRY_NOTE_LIMIT - 1).replace(/\s+\S*$/, "").replace(/[,;:\s]+$/, "")}…`;
|
|
284
|
+
}
|
package/dist/result-acts.d.ts
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
* most one outline act beside it. A result that can't be accepted says why
|
|
4
4
|
* in one line before them.
|
|
5
5
|
*
|
|
6
|
-
* everything met → Accept
|
|
6
|
+
* everything met → Accept and finish
|
|
7
|
+
* a "You check this one" unanswered → the next check, then Accept without your check
|
|
8
|
+
* a check you marked Not right → Request changes, then Accept anyway
|
|
7
9
|
* checks queued or running → Checks running (disabled), then Accept without checks
|
|
8
10
|
* checks didn't run, a check exists → Run checks, then Accept without checks
|
|
9
11
|
* report and changes disagree,
|
|
@@ -11,12 +13,19 @@
|
|
|
11
13
|
* never Accept as ink, even with no form here
|
|
12
14
|
* a Needs you form resolves it → that form (Build again, Confirm it stopped)
|
|
13
15
|
* saved notes or a failing PR → Revise / Draft a repair task
|
|
16
|
+
* a failed build → Retry (its note starts with what to change), then Run checks when they
|
|
17
|
+
* didn't run; never Accept: a failed build is not a result to accept
|
|
18
|
+
* a failed task's delivered result → Retry, then Accept anyway (in outline, with a reason) where it may be
|
|
19
|
+
* accepted; Run checks stays in its Checks row
|
|
14
20
|
*
|
|
15
|
-
* Accepting
|
|
16
|
-
*
|
|
17
|
-
|
|
21
|
+
* Accepting finishes the task in one request (Accept and finish): the person's
|
|
22
|
+
* acceptance, when one is owed, and the completion together. This only decides
|
|
23
|
+
* which act is ink; the page recomputes it as the person answers each check. */
|
|
24
|
+
/** The words over the reason field an Accept of a refuted result requires. */
|
|
25
|
+
export declare const ACCEPT_NEEDS_REASON = "Accepting needs a reason";
|
|
26
|
+
export type ResultActKind = "retry" | "accept" | "accept-anyway" | "next-check" | "checks-running" | "run-checks" | "request-changes" | "rebuild" | "confirm-stopped" | "revise" | "draft-repair";
|
|
18
27
|
export type ResultActFacts = {
|
|
19
|
-
/** An Accept this page can post (
|
|
28
|
+
/** An Accept this page can post (Accept and finish, or the person's acceptance alone); `ready`: every requirement met and checks passed. */
|
|
20
29
|
accept: {
|
|
21
30
|
ready: boolean;
|
|
22
31
|
} | null;
|
|
@@ -32,6 +41,16 @@ export type ResultActFacts = {
|
|
|
32
41
|
need: "rebuild" | "confirm-stopped" | null;
|
|
33
42
|
/** Saved notes ready to become a revision, or a failing pull request's repair. */
|
|
34
43
|
next: "revise" | "draft-repair" | null;
|
|
44
|
+
/** A failed build: `retry` when the task can be retried from here; `acceptAnyway` when it delivered a result that
|
|
45
|
+
* may still be accepted, with a reason. */
|
|
46
|
+
failed?: {
|
|
47
|
+
retry: boolean;
|
|
48
|
+
acceptAnyway?: boolean;
|
|
49
|
+
} | null;
|
|
50
|
+
/** "You check this one" items not answered yet on this page. */
|
|
51
|
+
unanswered?: number;
|
|
52
|
+
/** Items the person marked Not right. */
|
|
53
|
+
notRight?: number;
|
|
35
54
|
};
|
|
36
55
|
export type ResultActs = {
|
|
37
56
|
/** The one ink act, or null when nothing waits on the person. */
|
|
@@ -42,3 +61,30 @@ export type ResultActs = {
|
|
|
42
61
|
line: string | null;
|
|
43
62
|
};
|
|
44
63
|
export declare function resultActsOf(facts: ResultActFacts): ResultActs;
|
|
64
|
+
/** The words a result's Accept wears and the one line before it, given the
|
|
65
|
+
* server's words for everything but the person's own checks (`base`) and how
|
|
66
|
+
* those stand on this page: an unanswered check names itself; a check marked
|
|
67
|
+
* Not right says so; otherwise the base words stand. */
|
|
68
|
+
export type AcceptLabel = "Accept and finish" | "Accept without checks" | "Accept without your check" | "Accept anyway";
|
|
69
|
+
export declare function acceptWithChecksOf(base: {
|
|
70
|
+
label: AcceptLabel;
|
|
71
|
+
ready: boolean;
|
|
72
|
+
why: string | null;
|
|
73
|
+
}, checks: {
|
|
74
|
+
unanswered: readonly string[];
|
|
75
|
+
notRight: readonly string[];
|
|
76
|
+
}): {
|
|
77
|
+
label: AcceptLabel;
|
|
78
|
+
ready: boolean;
|
|
79
|
+
why: string | null;
|
|
80
|
+
};
|
|
81
|
+
/** The Requirements row as the person answers their checks: each Looks right is met, each answer leaves "You check". */
|
|
82
|
+
export declare function requirementsWordsOf(req: {
|
|
83
|
+
met: number;
|
|
84
|
+
total: number;
|
|
85
|
+
yours: number;
|
|
86
|
+
}, looked: number, notRight: number): {
|
|
87
|
+
text: string;
|
|
88
|
+
done: boolean;
|
|
89
|
+
short: boolean;
|
|
90
|
+
};
|
package/dist/result-acts.js
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
* most one outline act beside it. A result that can't be accepted says why
|
|
4
4
|
* in one line before them.
|
|
5
5
|
*
|
|
6
|
-
* everything met → Accept
|
|
6
|
+
* everything met → Accept and finish
|
|
7
|
+
* a "You check this one" unanswered → the next check, then Accept without your check
|
|
8
|
+
* a check you marked Not right → Request changes, then Accept anyway
|
|
7
9
|
* checks queued or running → Checks running (disabled), then Accept without checks
|
|
8
10
|
* checks didn't run, a check exists → Run checks, then Accept without checks
|
|
9
11
|
* report and changes disagree,
|
|
@@ -11,12 +13,24 @@
|
|
|
11
13
|
* never Accept as ink, even with no form here
|
|
12
14
|
* a Needs you form resolves it → that form (Build again, Confirm it stopped)
|
|
13
15
|
* saved notes or a failing PR → Revise / Draft a repair task
|
|
16
|
+
* a failed build → Retry (its note starts with what to change), then Run checks when they
|
|
17
|
+
* didn't run; never Accept: a failed build is not a result to accept
|
|
18
|
+
* a failed task's delivered result → Retry, then Accept anyway (in outline, with a reason) where it may be
|
|
19
|
+
* accepted; Run checks stays in its Checks row
|
|
14
20
|
*
|
|
15
|
-
* Accepting
|
|
16
|
-
*
|
|
21
|
+
* Accepting finishes the task in one request (Accept and finish): the person's
|
|
22
|
+
* acceptance, when one is owed, and the completion together. This only decides
|
|
23
|
+
* which act is ink; the page recomputes it as the person answers each check. */
|
|
24
|
+
/** The words over the reason field an Accept of a refuted result requires. */
|
|
25
|
+
export const ACCEPT_NEEDS_REASON = "Accepting needs a reason";
|
|
17
26
|
export function resultActsOf(facts) {
|
|
18
27
|
const request = facts.canRequest ? "request-changes" : null;
|
|
19
28
|
const accept = facts.accept === null ? null : "accept";
|
|
29
|
+
if (facts.failed != null) {
|
|
30
|
+
const checks = facts.runChecks && !facts.checksRunning ? "run-checks" : facts.checksRunning ? "checks-running" : null;
|
|
31
|
+
const anyway = facts.failed.acceptAnyway === true ? "accept-anyway" : null;
|
|
32
|
+
return facts.failed.retry ? { primary: "retry", secondary: anyway ?? checks, line: null } : { primary: checks, secondary: anyway, line: null };
|
|
33
|
+
}
|
|
20
34
|
if (facts.need !== null)
|
|
21
35
|
return { primary: facts.need, secondary: request, line: null };
|
|
22
36
|
if (facts.next !== null)
|
|
@@ -24,6 +38,11 @@ export function resultActsOf(facts) {
|
|
|
24
38
|
// Can't be accepted yet: Accept is never the ink act, even when the feedback form isn't here.
|
|
25
39
|
if (facts.blocked !== null)
|
|
26
40
|
return { primary: request, secondary: accept, line: facts.blocked };
|
|
41
|
+
// The person's own checks come first: the next one is ink until each is answered.
|
|
42
|
+
if (accept !== null && (facts.unanswered ?? 0) > 0)
|
|
43
|
+
return { primary: "next-check", secondary: accept, line: null };
|
|
44
|
+
if (accept !== null && (facts.notRight ?? 0) > 0)
|
|
45
|
+
return { primary: request, secondary: accept, line: null };
|
|
27
46
|
if (facts.checksRunning)
|
|
28
47
|
return { primary: "checks-running", secondary: accept ?? request, line: null };
|
|
29
48
|
if (facts.accept === null)
|
|
@@ -34,3 +53,17 @@ export function resultActsOf(facts) {
|
|
|
34
53
|
return { primary: "run-checks", secondary: "accept", line: null };
|
|
35
54
|
return { primary: "accept", secondary: request, line: null };
|
|
36
55
|
}
|
|
56
|
+
export function acceptWithChecksOf(base, checks) {
|
|
57
|
+
const named = (all) => `“${all[0].replace(/[.!?]+$/, "")}”${all.length > 1 ? ` and ${all.length - 1} more` : ""}`;
|
|
58
|
+
if (checks.unanswered.length > 0)
|
|
59
|
+
return { label: "Accept without your check", ready: false, why: `Not checked yet: ${named(checks.unanswered)}.` };
|
|
60
|
+
if (checks.notRight.length > 0)
|
|
61
|
+
return { label: "Accept anyway", ready: false, why: `You marked ${named(checks.notRight)} not right.` };
|
|
62
|
+
return base;
|
|
63
|
+
}
|
|
64
|
+
/** The Requirements row as the person answers their checks: each Looks right is met, each answer leaves "You check". */
|
|
65
|
+
export function requirementsWordsOf(req, looked, notRight) {
|
|
66
|
+
const met = req.met + looked;
|
|
67
|
+
const yours = Math.max(0, req.yours - looked - notRight);
|
|
68
|
+
return { text: `${met} of ${req.total} met${yours > 0 ? ` · You check ${yours}` : ""}`, done: met === req.total, short: req.total - met - yours > 0 };
|
|
69
|
+
}
|
package/dist/result-review.d.ts
CHANGED
|
@@ -149,9 +149,10 @@ export declare const RESULT_FACT_KEYS: readonly ["run", "head", "base", "head-so
|
|
|
149
149
|
* (place, lead, task, user) are not facts and are left out. */
|
|
150
150
|
export declare function resultFactsFromHtml(html: string): Record<string, string>[];
|
|
151
151
|
/** Where a result form may send its reader back: the review cockpit's
|
|
152
|
-
* own deep link, the chat's result detail,
|
|
153
|
-
*
|
|
154
|
-
|
|
152
|
+
* own deep link, the chat's result detail, the page of a task named in
|
|
153
|
+
* `tasks` (the run's own), or the run page. Anything else lands on the run
|
|
154
|
+
* page. Matched exactly — never a bare open redirect. */
|
|
155
|
+
export declare function resultReturnTarget(raw: string | null | undefined, runId: number, tasks?: readonly string[]): string;
|
|
155
156
|
/** A form's own request token, minted per render — the same shape the
|
|
156
157
|
* chat composer uses. A replayed submission carries the same token, so
|
|
157
158
|
* the server can recognise it and mint nothing twice. */
|
package/dist/result-review.js
CHANGED
|
@@ -149,9 +149,10 @@ export function resultFactsFromHtml(html) {
|
|
|
149
149
|
return found;
|
|
150
150
|
}
|
|
151
151
|
/** Where a result form may send its reader back: the review cockpit's
|
|
152
|
-
* own deep link, the chat's result detail,
|
|
153
|
-
*
|
|
154
|
-
|
|
152
|
+
* own deep link, the chat's result detail, the page of a task named in
|
|
153
|
+
* `tasks` (the run's own), or the run page. Anything else lands on the run
|
|
154
|
+
* page. Matched exactly — never a bare open redirect. */
|
|
155
|
+
export function resultReturnTarget(raw, runId, tasks = []) {
|
|
155
156
|
if (raw !== null && raw !== undefined) {
|
|
156
157
|
const review = /^\/review\?result=[A-Za-z0-9._~%-]{1,200}(?:&run=([0-9]{1,15}))?(?:&tab=(?:summary|changes|checks))?$/.exec(raw);
|
|
157
158
|
if (review !== null && (review[1] === undefined || Number(review[1]) === runId))
|
|
@@ -159,6 +160,9 @@ export function resultReturnTarget(raw, runId) {
|
|
|
159
160
|
const chat = /^\/chat\?task=([A-Za-z0-9._~%-]{1,200})&result=([0-9]{1,15})(?:&conversation=[A-Za-z0-9-]{1,128})?$/.exec(raw);
|
|
160
161
|
if (chat !== null && Number(chat[2]) === runId)
|
|
161
162
|
return raw;
|
|
163
|
+
// The run's own task page, which offered Run checks in place; never another task's.
|
|
164
|
+
if (tasks.some(task => raw === `/t/${encodeURIComponent(task)}`))
|
|
165
|
+
return raw;
|
|
162
166
|
}
|
|
163
167
|
return `/r/${runId}`;
|
|
164
168
|
}
|
package/dist/serve.d.ts
CHANGED
|
@@ -208,6 +208,9 @@ export declare function createDecisionServer(options: ServeOptions): DecisionSer
|
|
|
208
208
|
* text, never to a 500. The scheme is a constant, never data.
|
|
209
209
|
*/
|
|
210
210
|
export declare function editorFileHref(worktree: string, path: string, line?: number | null): string | null;
|
|
211
|
+
/** Earlier versions of a task still in flight, in plain words: queued
|
|
212
|
+
* while they only wait, running once one has started; null when none. */
|
|
213
|
+
export declare function earlierVersionsWords(active: number, running: number): string | null;
|
|
211
214
|
/** The Agents view every console surface renders (v47): the SAME
|
|
212
215
|
* projection the CLI prints — who plans, builds, and revises,
|
|
213
216
|
* whether each was recommended, overridden, or pinned, its reasons, and
|