toolroll 0.9.56 → 0.9.57
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/dist/assignment-adapters.d.ts +5 -0
- package/dist/assignment.d.ts +24 -3
- package/dist/assignment.js +36 -10
- package/dist/browser-workspace.js +1 -1
- package/dist/chat-delivery.js +3 -3
- package/dist/chat-voice.js +1 -1
- package/dist/check-levels.d.ts +3 -0
- package/dist/check-levels.js +5 -0
- package/dist/dispose.js +0 -5
- package/dist/lead-status.d.ts +1 -1
- package/dist/lead-status.js +2 -2
- package/dist/operate.js +17 -8
- package/dist/proof.d.ts +9 -0
- package/dist/proof.js +12 -0
- package/dist/release-coverage.d.ts +16 -0
- package/dist/release-coverage.js +92 -0
- package/dist/server/chat.js +8 -6
- package/dist/server/render-results.d.ts +4 -1
- package/dist/server/render-results.js +23 -9
- package/dist/server/tasks.js +28 -25
- package/dist/store.d.ts +3 -5
- package/dist/store.js +17 -2
- package/dist/task-outcome-cli.js +3 -1
- package/dist/task-status.d.ts +59 -7
- package/dist/task-status.js +132 -16
- package/dist/team-runtime.js +1 -1
- package/dist/telegram-status.d.ts +11 -4
- package/dist/telegram-status.js +17 -10
- package/dist/telegram.js +4 -4
- package/dist/work-index.d.ts +6 -1
- package/dist/work-index.js +50 -8
- package/dist/workspace-ui.js +2 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.57 — 2026-10-09
|
|
4
|
+
|
|
5
|
+
- **Truthful status.** Finished tasks read Complete everywhere. With checks
|
|
6
|
+
off, criteria read "checked at release" instead of Needs you.
|
|
7
|
+
- **No false finish.** Completing or deploying refuses a failed or missing
|
|
8
|
+
check, an unmet criterion, a serious review finding, or missing evidence.
|
|
9
|
+
- **Clear revisions.** Revisions are named after their task: -v2, -v3.
|
|
10
|
+
|
|
3
11
|
## 0.9.56 — 2026-10-09
|
|
4
12
|
|
|
5
13
|
- **One lead.** Your assistant in the console, on your phone, in chat and in
|
|
@@ -109,6 +109,11 @@ export declare function assignmentForCoordinator(store: Store, token: string, op
|
|
|
109
109
|
deployment: {
|
|
110
110
|
status: "not-recorded";
|
|
111
111
|
};
|
|
112
|
+
readiness: {
|
|
113
|
+
verdict: import("./proof.js").ProofVerdict | null;
|
|
114
|
+
checksOff: boolean;
|
|
115
|
+
blockers: import("./task-status.js").CompletionBlocker[];
|
|
116
|
+
} | null;
|
|
112
117
|
};
|
|
113
118
|
}[];
|
|
114
119
|
nextCursor: number;
|
package/dist/assignment.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { type Store, type ProofVerdictRow, type ProofAcceptanceRow } from "./store.js";
|
|
2
2
|
import { type WorkAction, type WorkSummaryAccess } from "./work-summary.js";
|
|
3
3
|
import { type VerifiedApprover } from "./principal.js";
|
|
4
|
+
import { type ProofVerdict } from "./proof.js";
|
|
4
5
|
import { type CheckLevel } from "./check-levels.js";
|
|
5
6
|
import { type BuildReviewView } from "./review-switch.js";
|
|
6
7
|
import { type NeedKey, type WaitKey } from "./needs-you.js";
|
|
7
|
-
import { type ChecksBatch } from "./task-status.js";
|
|
8
|
+
import { type ChecksBatch, type CompletionBlocker } from "./task-status.js";
|
|
9
|
+
import { type ReleaseCoverage, type ReleaseMemo } from "./release-coverage.js";
|
|
8
10
|
import { type LeadClaim } from "./lead-voice.js";
|
|
9
11
|
export type AssignmentAccess = WorkSummaryAccess;
|
|
10
12
|
export type AssignmentOwner = {
|
|
@@ -24,6 +26,8 @@ export type AssignmentChecks = {
|
|
|
24
26
|
running?: "quick" | "full" | null;
|
|
25
27
|
/** A batch check (batch-checks.ts): waiting for its batch, or how it was checked. */
|
|
26
28
|
batch?: ChecksBatch | null;
|
|
29
|
+
/** Checks Off: the passing full release check that contains this commit (release-coverage.ts), read now, never sealed. */
|
|
30
|
+
release?: ReleaseCoverage | null;
|
|
27
31
|
};
|
|
28
32
|
export type AssignmentReceipt = {
|
|
29
33
|
digest: string;
|
|
@@ -118,6 +122,13 @@ export type AssignmentSnapshot = {
|
|
|
118
122
|
earlierActive?: number;
|
|
119
123
|
/** Of those, how many are running rather than only queued; present with earlierActive. */
|
|
120
124
|
earlierRunning?: number;
|
|
125
|
+
/** The result as it reads today (task-status.ts): the verdict it shows, whether its checks were deliberately Off, and
|
|
126
|
+
* what refuses Complete. Derived at read time; never part of the sealed receipt. Null without a result. */
|
|
127
|
+
readiness?: {
|
|
128
|
+
verdict: ProofVerdict | null;
|
|
129
|
+
checksOff: boolean;
|
|
130
|
+
blockers: CompletionBlocker[];
|
|
131
|
+
} | null;
|
|
121
132
|
};
|
|
122
133
|
/** Status-first handoff for routine reads. Fetch get_assignment only when
|
|
123
134
|
* inspecting its exact evidence; do not repeat a full proof in every poll. */
|
|
@@ -169,9 +180,14 @@ export declare function assignmentBrief(assignment: AssignmentSnapshot | null):
|
|
|
169
180
|
deployment: {
|
|
170
181
|
status: "not-recorded";
|
|
171
182
|
};
|
|
183
|
+
readiness: {
|
|
184
|
+
verdict: ProofVerdict | null;
|
|
185
|
+
checksOff: boolean;
|
|
186
|
+
blockers: CompletionBlocker[];
|
|
187
|
+
} | null;
|
|
172
188
|
} | null;
|
|
173
189
|
export declare function assignmentChecksForRun(store: Store, root: string | undefined, runId: number, now: Date): AssignmentChecks;
|
|
174
|
-
export declare function assignmentOf(store: Store, taskId: string, now: Date, access: AssignmentAccess, root?: string): AssignmentSnapshot | null;
|
|
190
|
+
export declare function assignmentOf(store: Store, taskId: string, now: Date, access: AssignmentAccess, root?: string, memo?: ReleaseMemo): AssignmentSnapshot | null;
|
|
175
191
|
type MutationResult = {
|
|
176
192
|
ok: true;
|
|
177
193
|
assignment: AssignmentSnapshot;
|
|
@@ -203,7 +219,7 @@ type Finished = {
|
|
|
203
219
|
* (no reason needed), or an exception a report that doesn't match its changes needs a reason for. Null when
|
|
204
220
|
* Accept and finish is a plain completion: nothing is owed (no proof, a failed check, or a verified result),
|
|
205
221
|
* or it was already accepted. */
|
|
206
|
-
export declare function owedAcceptance(receipt: AssignmentReceipt | null): "person-check" | "exception" | null;
|
|
222
|
+
export declare function owedAcceptance(receipt: AssignmentReceipt | null, blockers?: readonly CompletionBlocker[]): "person-check" | "exception" | null;
|
|
207
223
|
/** Accept and finish: the person's acceptance of the exact result they read and its completion, in one
|
|
208
224
|
* transaction. Whether an acceptance is owed is decided here, against the receipt as it stands: only a
|
|
209
225
|
* person's own check or a reasoned exception records one (with its ledger act); an exception without its
|
|
@@ -278,6 +294,11 @@ export declare function assignmentUpdates(store: Store, now: Date, access: Assig
|
|
|
278
294
|
deployment: {
|
|
279
295
|
status: "not-recorded";
|
|
280
296
|
};
|
|
297
|
+
readiness: {
|
|
298
|
+
verdict: ProofVerdict | null;
|
|
299
|
+
checksOff: boolean;
|
|
300
|
+
blockers: CompletionBlocker[];
|
|
301
|
+
} | null;
|
|
281
302
|
};
|
|
282
303
|
}[];
|
|
283
304
|
nextCursor: number;
|
package/dist/assignment.js
CHANGED
|
@@ -14,13 +14,14 @@ import { verificationEvidence } from "./verification-evidence.js";
|
|
|
14
14
|
import { reproveApprover } from "./principal.js";
|
|
15
15
|
import { noteAssignmentStatus } from "./assignment-status.js";
|
|
16
16
|
import { historicalAssessmentReason } from "./assignment-presentation.js";
|
|
17
|
-
import { manualReviewOnly } from "./proof.js";
|
|
17
|
+
import { manualReviewOnly, shownVerdictOf } from "./proof.js";
|
|
18
18
|
import { ACCEPT_NEEDS_REASON, cantAcceptYetOf } from "./result-acts.js";
|
|
19
|
-
import { runCheckLevel } from "./check-levels.js";
|
|
19
|
+
import { requiredCheckCommandFor, runCheckLevel } from "./check-levels.js";
|
|
20
20
|
import { followUpChecksOf, withFollowUps } from "./result-follow-ups.js";
|
|
21
21
|
import { buildReviewOf, findingWords } from "./review-switch.js";
|
|
22
22
|
import { NEEDS, WAITS, processNeedOf, resultHoldUpSentence } from "./needs-you.js";
|
|
23
|
-
import { assignmentStageOf } from "./task-status.js";
|
|
23
|
+
import { assignmentStageOf, checkBackingOf, completionBlockersOf, requirementWordOf } from "./task-status.js";
|
|
24
|
+
import { releaseCoverageOf } from "./release-coverage.js";
|
|
24
25
|
import { leadClaimOf } from "./lead-voice.js";
|
|
25
26
|
/** Status-first handoff for routine reads. Fetch get_assignment only when
|
|
26
27
|
* inspecting its exact evidence; do not repeat a full proof in every poll. */
|
|
@@ -35,7 +36,7 @@ export function assignmentBrief(assignment) {
|
|
|
35
36
|
base: receipt.base, head: receipt.head, completionKind: receipt.completionKind,
|
|
36
37
|
checks: receipt.checks, proofAcceptance: receipt.proofAcceptance, verdict: receipt.proof?.verdict ?? null,
|
|
37
38
|
criteria: { passed: receipt.proof?.matrix.filter(row => row.state === "pass").length ?? 0, total: receipt.proof?.matrix.length ?? 0 }, evidence: receipt.evidence },
|
|
38
|
-
publication: assignment.publication, review: assignment.review, deployment: assignment.deployment };
|
|
39
|
+
publication: assignment.publication, review: assignment.review, deployment: assignment.deployment, readiness: assignment.readiness ?? null };
|
|
39
40
|
}
|
|
40
41
|
const digest = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
41
42
|
/** A lookup added with lead-quiet, read safely: a deploy proves completion with this code over the INSTALLED runtime's
|
|
@@ -51,7 +52,7 @@ function olderStoreSafe(read, absent) {
|
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
/** A check as the receipt digest seals it: presentation-only fields (check-levels.ts) left out. */
|
|
54
|
-
const sealedChecks = ({ level: _level, running: _running, batch: _batch, ...proof }) => proof;
|
|
55
|
+
const sealedChecks = ({ level: _level, running: _running, batch: _batch, release: _release, ...proof }) => proof;
|
|
55
56
|
const OWNER_ACTION = "assignment claimed";
|
|
56
57
|
const CHECK_ACTION = COMPLETION_ACTION;
|
|
57
58
|
const actorOf = (owner) => `${owner.kind}:${owner.id}`;
|
|
@@ -127,7 +128,7 @@ function buildChecks(store, root, runId) {
|
|
|
127
128
|
return unavailable("Saved checks could not be read.");
|
|
128
129
|
}
|
|
129
130
|
}
|
|
130
|
-
export function assignmentOf(store, taskId, now, access, root) {
|
|
131
|
+
export function assignmentOf(store, taskId, now, access, root, memo) {
|
|
131
132
|
const family = store.taskFamilyOf(taskId, access.repos, access.principal === "operator" && access.includeUnplaced === true);
|
|
132
133
|
if (family === null)
|
|
133
134
|
return null;
|
|
@@ -163,7 +164,10 @@ export function assignmentOf(store, taskId, now, access, root) {
|
|
|
163
164
|
const reports = artifacts.filter(a => a.kind === "report");
|
|
164
165
|
const unavailable = family.versions.flatMap(version => store.runsFor(version.refId).flatMap(run => store.artifactsFor(run.id).filter(artifact => root === undefined || !readVerifiedArtifact(root, artifact).ok)
|
|
165
166
|
.map(artifact => `Saved ${artifact.kind} #${artifact.id} (run ${run.id}) is unavailable or changed.`)));
|
|
166
|
-
const
|
|
167
|
+
const ownChecks = result === null ? null : assignmentChecksForRun(store, root, result.id, now);
|
|
168
|
+
// Checks Off: a passing full release check that contains this commit covers it, read now from the existing receipts.
|
|
169
|
+
const checks = ownChecks === null || ownChecks.level !== "off" || ownChecks.status === "passed" || ownChecks.status === "failed" ? ownChecks
|
|
170
|
+
: { ...ownChecks, release: releaseCoverageOf(store, root, current.repo, result.headRevision, memo === undefined ? {} : { memo }) };
|
|
167
171
|
const finishedBuild = result?.role === "builder" && (result.outcome === "built" || result.outcome === "no-change") &&
|
|
168
172
|
result.finishedAt !== null && /^[a-f0-9]{40}$/.test(result.headRevision ?? "");
|
|
169
173
|
// Finished work returns to its lead or user. Strict terms and previous model
|
|
@@ -339,6 +343,7 @@ export function assignmentOf(store, taskId, now, access, root) {
|
|
|
339
343
|
attention, receipt: receipt?.digest ?? null }), acknowledged: false } : null;
|
|
340
344
|
const publication = result === null ? null : store.publicationForRun(result.id);
|
|
341
345
|
const lead = state === "complete" || state === "cancelled" || access.principal !== "operator" ? null : leadClaimOf(store, family.root.id, now, access.viewer);
|
|
346
|
+
const readiness = receipt === null ? null : readinessOf(store, receipt, current.repo, review);
|
|
342
347
|
// Existing saved inputs and output, read only after family admission. Keep
|
|
343
348
|
// polling briefs small; full reads disclose exactly which excerpts are shortened.
|
|
344
349
|
const planId = result?.planRevision == null ? null : store.getPlanRevision(result.planRevision)?.artifact;
|
|
@@ -355,7 +360,21 @@ export function assignmentOf(store, taskId, now, access, root) {
|
|
|
355
360
|
return { version: 1, rootId: family.root.id, activeTaskId: current.id, repo: current.repo, title: family.root.title,
|
|
356
361
|
state, detail, primaryAction, attention: [...new Set(attention)], attempts, owner, receipt, savedContext, completion, handoff,
|
|
357
362
|
publication: publication === null ? null : { state: publication.state, prUrl: publication.prUrl, remoteState: publication.remoteState },
|
|
358
|
-
review, deployment: { status: "not-recorded" }, ...(need === null ? {} : { need }), ...(lead === null ? {} : { lead }), ...(earlierActive.length === 0 ? {} : { earlierActive: earlierActive.length, earlierRunning }) };
|
|
363
|
+
review, deployment: { status: "not-recorded" }, readiness, ...(need === null ? {} : { need }), ...(lead === null ? {} : { lead }), ...(earlierActive.length === 0 ? {} : { earlierActive: earlierActive.length, earlierRunning }) };
|
|
364
|
+
}
|
|
365
|
+
/** The result's shown verdict and completion blockers (task-status.ts), from the receipt, the project's approved check
|
|
366
|
+
* and the automatic review. A HIGH finding a revision already took is resolved there. */
|
|
367
|
+
function readinessOf(store, receipt, repo, review) {
|
|
368
|
+
const report = receipt.completionKind === "research-report";
|
|
369
|
+
const high = review === null || review.sentBackAs !== null || review.state !== "reviewed" ? 0 : review.high.length;
|
|
370
|
+
const checkRequired = olderStoreSafe(() => requiredCheckCommandFor(store, repo, receipt.taskId, receipt.checks.level), null) !== null;
|
|
371
|
+
const matrix = receipt.proof?.matrix ?? [];
|
|
372
|
+
const backing = checkBackingOf(receipt.checks);
|
|
373
|
+
const verdict = shownVerdictOf(receipt.proof?.verdict ?? null, { checkFailed: !report && receipt.checks.status === "failed", highFindings: high,
|
|
374
|
+
reviewPending: review?.state === "pending", criteriaPassed: matrix.every(row => requirementWordOf(row, null, backing) === "Met") });
|
|
375
|
+
const blockers = completionBlockersOf({ report, checks: receipt.checks, checkRequired, matrix, verdict: receipt.proof?.verdict ?? null,
|
|
376
|
+
accepted: receipt.proofAcceptance !== null, high });
|
|
377
|
+
return { verdict, checksOff: !report && receipt.checks.level === "off", blockers };
|
|
359
378
|
}
|
|
360
379
|
function admittedOwner(store, taskId, owner, now, root) {
|
|
361
380
|
if (owner.kind !== "coordinator")
|
|
@@ -417,6 +436,10 @@ function acknowledgeCurrent(store, current, receiptDigest, actor, now, root, acc
|
|
|
417
436
|
if (current.state !== "ready-to-check" && current.state !== "complete")
|
|
418
437
|
return { ok: false, reason: "not-ready", message: "This assignment still has unresolved execution, scope or decisions." };
|
|
419
438
|
if (current.state !== "complete") {
|
|
439
|
+
// The one readiness guard: a failed or missing required check, an unresolved requirement or HIGH finding refuses.
|
|
440
|
+
const blockers = current.readiness?.blockers ?? [];
|
|
441
|
+
if (blockers.length > 0)
|
|
442
|
+
return { ok: false, reason: "not-ready", message: blockers.map(one => one.message).join(" ") };
|
|
420
443
|
// v102: declared paths are only a promise. A result whose actual diff reaches protected files, on a
|
|
421
444
|
// scope one person approved, completes only by someone else — two people have then seen the work.
|
|
422
445
|
const problem = store.protectedResultProblem(receipt.taskId, familyChangedFiles(store, receipt.taskId, receipt.runId, root), actor.startsWith("operator:") ? actor.slice("operator:".length) : null);
|
|
@@ -476,11 +499,14 @@ class Undone extends Error {
|
|
|
476
499
|
* (no reason needed), or an exception a report that doesn't match its changes needs a reason for. Null when
|
|
477
500
|
* Accept and finish is a plain completion: nothing is owed (no proof, a failed check, or a verified result),
|
|
478
501
|
* or it was already accepted. */
|
|
479
|
-
export function owedAcceptance(receipt) {
|
|
502
|
+
export function owedAcceptance(receipt, blockers = []) {
|
|
480
503
|
if (receipt === null || receipt.proofAcceptance !== null)
|
|
481
504
|
return null;
|
|
482
505
|
if (personCheckPending(receipt))
|
|
483
506
|
return "person-check";
|
|
507
|
+
// An unresolved requirement is resolved only by an acceptance that says why.
|
|
508
|
+
if (blockers.some(one => one.key === "criteria"))
|
|
509
|
+
return "exception";
|
|
484
510
|
return cantAcceptYetOf(receipt.proof?.verdict ?? null, receipt.proof?.reasons ?? [], false) === ACCEPT_NEEDS_REASON ? "exception" : null;
|
|
485
511
|
}
|
|
486
512
|
/** Accept and finish: the person's acceptance of the exact result they read and its completion, in one
|
|
@@ -505,7 +531,7 @@ export function acceptAndCompleteAsOperator(store, taskId, input, who, now, root
|
|
|
505
531
|
return { ok: false, reason: "stale", message: "This result changed. Open the current result before accepting it." };
|
|
506
532
|
}
|
|
507
533
|
// Already complete, or nothing owed: the plain completion of the receipt as read.
|
|
508
|
-
const owed = before.state === "ready-to-check" ? owedAcceptance(receipt) : null;
|
|
534
|
+
const owed = before.state === "ready-to-check" ? owedAcceptance(receipt, before.readiness?.blockers ?? []) : null;
|
|
509
535
|
// An exception is accepted only with its reason: never a completion that skips it.
|
|
510
536
|
if (owed === "exception" && input.note === null)
|
|
511
537
|
return { ok: false, reason: "needs-reason", message: `${ACCEPT_NEEDS_REASON}.` };
|
|
@@ -19,7 +19,7 @@ export function browserCrewOf(store, now, access, options = {}) {
|
|
|
19
19
|
return { crew: [], crewTruncated: false };
|
|
20
20
|
const limit = Number.isFinite(options.limit)
|
|
21
21
|
? Math.max(1, Math.min(BROWSER_CREW_LIMIT, Math.floor(options.limit))) : BROWSER_CREW_LIMIT;
|
|
22
|
-
const page = workIndexPage(store, now, access, { limit, project });
|
|
22
|
+
const page = workIndexPage(store, now, access, { limit, project, ...(options.evidenceRoot === undefined ? {} : { root: options.evidenceRoot }) });
|
|
23
23
|
return browserCrewFromIndex(page);
|
|
24
24
|
}
|
|
25
25
|
/** Navigation input must already be admitted by the caller. No extra project
|
package/dist/chat-delivery.js
CHANGED
|
@@ -232,9 +232,9 @@ export async function processChatEvent(options) {
|
|
|
232
232
|
if (command.kind === "help")
|
|
233
233
|
state.plan(event.id, [{ text: PHONE_HELP }], now_);
|
|
234
234
|
else if (command.kind === "status")
|
|
235
|
-
state.plan(event.id, [{ text: phoneStatus(store, repos, now_, focusedTitle) }], now_);
|
|
235
|
+
state.plan(event.id, [{ text: phoneStatus(store, repos, now_, focusedTitle, options.evidenceRoot) }], now_);
|
|
236
236
|
else if (command.kind === "tasks")
|
|
237
|
-
state.plan(event.id, [{ text: phoneTaskListText(phoneTaskChoices(store, repos, now_), focusedTitle) }], now_);
|
|
237
|
+
state.plan(event.id, [{ text: phoneTaskListText(phoneTaskChoices(store, repos, now_, null, 8, options.evidenceRoot), focusedTitle) }], now_);
|
|
238
238
|
else if (command.kind === "lead") {
|
|
239
239
|
store.setChatFocus(surface, binding.id, null, now_);
|
|
240
240
|
state.plan(event.id, [{ text: PHONE_BACK_TO_LEAD }], now_);
|
|
@@ -243,7 +243,7 @@ export async function processChatEvent(options) {
|
|
|
243
243
|
const pick = resolvePhoneTask(store, repos, now_, command.id);
|
|
244
244
|
if (pick.kind === "one") {
|
|
245
245
|
store.setChatFocus(surface, binding.id, pick.id, now_);
|
|
246
|
-
const view = phoneTaskView(store, repos, pick.view, now_);
|
|
246
|
+
const view = phoneTaskView(store, repos, pick.view, now_, options.evidenceRoot);
|
|
247
247
|
// The status names its task (and result): a reply to it is about that task.
|
|
248
248
|
state.plan(event.id, [{ text: phoneFocusText(view.text), ...(view.link === null ? {} : { link: view.link }), task: pick.view, ...(view.run === null ? {} : { run: view.run }) }], now_);
|
|
249
249
|
}
|
package/dist/chat-voice.js
CHANGED
|
@@ -69,7 +69,7 @@ export function finishedLine(fact) {
|
|
|
69
69
|
if (fact.checks === "passed")
|
|
70
70
|
return `${ready}. Your tests passed. ${fact.pullRequest === true ? "Merge it?" : "Accept and finish it?"}`;
|
|
71
71
|
if (fact.checks === "off")
|
|
72
|
-
return `${ready}.
|
|
72
|
+
return `${ready}. Its checks run at release, so look it over first.`;
|
|
73
73
|
return `${ready}, but no tests ran. Look it over first.`;
|
|
74
74
|
case "Failed":
|
|
75
75
|
if (fact.leadOnIt === true)
|
package/dist/check-levels.d.ts
CHANGED
|
@@ -52,6 +52,9 @@ export declare function checkCommandFor(store: Store, repo: string, level: Check
|
|
|
52
52
|
level: CheckLevel;
|
|
53
53
|
command: VerifyCommand | null;
|
|
54
54
|
};
|
|
55
|
+
/** Readiness uses the result's level, falling back to the task/project for legacy
|
|
56
|
+
* runs. Quick's approved command (or its Full fallback) is required; Off is not. */
|
|
57
|
+
export declare function requiredCheckCommandFor(store: Store, repo: string | null, taskId: string, level: CheckLevel | null | undefined): VerifyCommand | null;
|
|
55
58
|
/** The builder records which level a run used, so its result says it honestly. */
|
|
56
59
|
export declare function recordRunCheckLevel(store: Store, run: {
|
|
57
60
|
id: number;
|
package/dist/check-levels.js
CHANGED
|
@@ -118,6 +118,11 @@ export function checkCommandFor(store, repo, level) {
|
|
|
118
118
|
}
|
|
119
119
|
return { level: "full", command: store.liveVerifyCommand(repo) };
|
|
120
120
|
}
|
|
121
|
+
/** Readiness uses the result's level, falling back to the task/project for legacy
|
|
122
|
+
* runs. Quick's approved command (or its Full fallback) is required; Off is not. */
|
|
123
|
+
export function requiredCheckCommandFor(store, repo, taskId, level) {
|
|
124
|
+
return repo === null ? null : checkCommandFor(store, repo, level ?? effectiveCheckLevel(store, repo, taskId).level).command;
|
|
125
|
+
}
|
|
121
126
|
/** The builder records which level a run used, so its result says it honestly. */
|
|
122
127
|
export function recordRunCheckLevel(store, run, level, from, now) {
|
|
123
128
|
store.recordAction({ at: now.toISOString(), actor: "system", repo: run.repo, taskId: run.taskId, runId: run.id, action: RUN_LEVEL_ACTION, outcome: level, source: "work",
|
package/dist/dispose.js
CHANGED
|
@@ -707,10 +707,6 @@ export function maybeTriggerRepair(store, repo, evidenceRoot, sourceRunId, verdi
|
|
|
707
707
|
};
|
|
708
708
|
const briefBytes = Buffer.from(JSON.stringify(draftBrief, null, 2), "utf8");
|
|
709
709
|
const key = writeEvidenceFile(evidenceRoot, sourceRunId, `repair-brief-${sourceRunId}-${attempt}.json`, briefBytes);
|
|
710
|
-
// Suffixes survive truncation (the CI-repair rule, verbatim): the prefix
|
|
711
|
-
// gives way, the identity-bearing tail never does.
|
|
712
|
-
const suffix = observation ? `-evidence-${attempt}` : `-fix-${attempt}`;
|
|
713
|
-
const draftId = `${rootTask.slice(0, 64 - suffix.length)}${suffix}`;
|
|
714
710
|
// The draft files through the ONE revision boundary: the source's goal,
|
|
715
711
|
// exclusions, touches, rubric, risk, quality, posture, budget, overrides
|
|
716
712
|
// and pins are read from the SOURCE ROWS inside the seal — the scope
|
|
@@ -719,7 +715,6 @@ export function maybeTriggerRepair(store, repo, evidenceRoot, sourceRunId, verdi
|
|
|
719
715
|
source: { task: ref.externalId, run: sourceRunId, scopeDigest: scope.digest },
|
|
720
716
|
brief: { evidenceRoot, key, sha256: createHash("sha256").update(briefBytes).digest("hex"), bytes: briefBytes.length, capture: "machine-authored repair brief (exit 0)" },
|
|
721
717
|
child: {
|
|
722
|
-
id: draftId,
|
|
723
718
|
title: observation ? `Collect missing evidence for ${ref.externalId}` : verification === undefined ? `repair ${ref.externalId}: ${unresolved.length} criteri${unresolved.length === 1 ? "on" : "a"} unmet` : `Fix failed checks for ${ref.externalId}`,
|
|
724
719
|
repair: observation ? `Collect only the missing observations for ${unresolved.join(", ")}. Keep the saved candidate unchanged. The machine runs supported focused test observations and reuses its intact passing gate; a fresh review assesses the added evidence.` : verification === undefined ? `repair exactly the unmet criteria named below; a comment cannot widen the scope. Unmet: ${unresolved.join(", ")}.` : "Diagnose the saved failed project check and repair within the original scope. Preserve the acceptance criteria and verification command; the revision brief binds the exact failure evidence.",
|
|
725
720
|
},
|
package/dist/lead-status.d.ts
CHANGED
|
@@ -105,7 +105,7 @@ export declare function repairStaleStatuses(store: Store, now: Date): {
|
|
|
105
105
|
* `repos`: the only projects to report (a remote person limited to some projects); null is the whole installation.
|
|
106
106
|
* A limited report counts only those projects' tasks and leaves out what belongs to the installation as a whole:
|
|
107
107
|
* the release check, plan windows and the lead line are empty, never another project's. */
|
|
108
|
-
export declare function installationStatus(store: Store, now: Date, viewer?: string | null, repos?: readonly string[] | null): InstallationStatus;
|
|
108
|
+
export declare function installationStatus(store: Store, now: Date, viewer?: string | null, repos?: readonly string[] | null, root?: string): InstallationStatus;
|
|
109
109
|
/** `limited`: a report over some projects (installationStatus's `repos`), which has no release check or plan windows to state. */
|
|
110
110
|
export declare function renderInstallationStatus(status: InstallationStatus, limited?: boolean): string[];
|
|
111
111
|
export declare function renderTaskWait(snapshot: TaskWaitSnapshot, outcome?: string): string;
|
package/dist/lead-status.js
CHANGED
|
@@ -183,7 +183,7 @@ export function repairStaleStatuses(store, now) {
|
|
|
183
183
|
* `repos`: the only projects to report (a remote person limited to some projects); null is the whole installation.
|
|
184
184
|
* A limited report counts only those projects' tasks and leaves out what belongs to the installation as a whole:
|
|
185
185
|
* the release check, plan windows and the lead line are empty, never another project's. */
|
|
186
|
-
export function installationStatus(store, now, viewer = currentActor()?.account ?? null, repos = null) {
|
|
186
|
+
export function installationStatus(store, now, viewer = currentActor()?.account ?? null, repos = null, root) {
|
|
187
187
|
const admitted = (row) => repos === null || typeof row["repo"] === "string" && repos.includes(row["repo"]);
|
|
188
188
|
const access = { principal: "operator", repos, includeUnplaced: repos === null };
|
|
189
189
|
const runningRows = store.handle.prepare(`SELECT run.id, run.role, run.phase, ref.id AS task_ref, ref.external_id AS task, ref.repo
|
|
@@ -262,7 +262,7 @@ export function installationStatus(store, now, viewer = currentActor()?.account
|
|
|
262
262
|
observedAt: String(row["observed_at"]),
|
|
263
263
|
})),
|
|
264
264
|
signIn: pauses.map(one => ({ provider: one.provider, reason: signInReason(one), command: signInCommand(one), since: one.openedAt })),
|
|
265
|
-
tasks: workIndexPage(store, now, { ...access, viewer }, { limit: 8 }).items
|
|
265
|
+
tasks: workIndexPage(store, now, { ...access, viewer }, { limit: 8, ...(root === undefined ? {} : { root }) }).items
|
|
266
266
|
.map(one => ({ task: one.rootId, title: one.title, headline: one.status.label, sentence: one.status.detail })),
|
|
267
267
|
lead: repos !== null ? null : (() => {
|
|
268
268
|
// Only the asker's own lead: another person's lead is never "Your lead".
|
package/dist/operate.js
CHANGED
|
@@ -179,7 +179,7 @@ import { CHAT_APPROVE_ALL, presetTerms, modeTermsJson, modeDigestOf, modeTermsFr
|
|
|
179
179
|
import { WorktreePool } from "./worktree.js";
|
|
180
180
|
import { requestTaskStop, resumeTaskStop, taskControlOf } from "./task-control.js";
|
|
181
181
|
import { worktreeAdoptionNotice } from "./worktree-notices.js";
|
|
182
|
-
import { workIndexPage, WorkIndexCursorError } from "./work-index.js";
|
|
182
|
+
import { workIndexPage, workIndexTask, WorkIndexCursorError } from "./work-index.js";
|
|
183
183
|
import { readExecutorOf } from "./read-executor.js";
|
|
184
184
|
import { parseWorkView } from "./workspace-ui.js";
|
|
185
185
|
import { taskWorkSummaryOf } from "./work-summary.js";
|
|
@@ -8307,10 +8307,10 @@ async function statusCommand(positional, flags, context) {
|
|
|
8307
8307
|
// that belongs to the installation or other projects (updates, integrations, builds by project, unsent replies).
|
|
8308
8308
|
const lens = context.principal?.lens ?? null;
|
|
8309
8309
|
if (lens !== null) {
|
|
8310
|
-
const status = installationStatus(context.store, context.clock(), viewer, lens);
|
|
8310
|
+
const status = installationStatus(context.store, context.clock(), viewer, lens, context.evidenceRoot);
|
|
8311
8311
|
return succeed(context.write, context.json, command, { ...status, projects: [] }, () => renderInstallationStatus(status, true));
|
|
8312
8312
|
}
|
|
8313
|
-
const status = installationStatus(context.store, context.clock(), viewer);
|
|
8313
|
+
const status = installationStatus(context.store, context.clock(), viewer, null, context.evidenceRoot);
|
|
8314
8314
|
// One line, only when a newer Toolroll exists; offline or switched off says nothing.
|
|
8315
8315
|
const current = PACKAGE_VERSION;
|
|
8316
8316
|
const method = installMethod(context.installBin);
|
|
@@ -8927,6 +8927,7 @@ function listTasks(flags, context) {
|
|
|
8927
8927
|
view: parseWorkView(rawView ?? null), limit, cursor: text(flags, 'cursor') ?? null,
|
|
8928
8928
|
...(wanted === undefined ? {} : { state: wanted }),
|
|
8929
8929
|
...(text(flags, 'repo') === undefined ? {} : { project: text(flags, 'repo') }),
|
|
8930
|
+
root: context.evidenceRoot,
|
|
8930
8931
|
};
|
|
8931
8932
|
const refused = (error) => {
|
|
8932
8933
|
if (!(error instanceof WorkIndexCursorError))
|
|
@@ -8990,10 +8991,18 @@ function showTask(positional, context) {
|
|
|
8990
8991
|
const repo = store.lookupRef(blocker)?.repo;
|
|
8991
8992
|
return repo != null && lens.includes(repo);
|
|
8992
8993
|
});
|
|
8994
|
+
const work = taskWorkSummaryOf(store, id, now, access);
|
|
8995
|
+
const current = assignmentOf(store, id, now, access, context.evidenceRoot);
|
|
8996
|
+
// Read the same saved facts and statusOf path as task list. File health remains
|
|
8997
|
+
// in the assignment diagnostics and action-time guards; it cannot rewrite history.
|
|
8998
|
+
const listed = workIndexTask(store, id, now, access, context.evidenceRoot);
|
|
8999
|
+
const shared = listed === null ? null : { headline: listed.status.label, sentence: listed.status.detail };
|
|
9000
|
+
const primaryAction = listed === null ? work?.primaryAction ?? null : listed.primaryAction;
|
|
8993
9001
|
const detail = {
|
|
8994
9002
|
task,
|
|
8995
|
-
work:
|
|
8996
|
-
|
|
9003
|
+
work: work === null || listed === null ? work : { ...work, status: { ...work.status, label: listed.status.label, detail: listed.status.detail, tone: listed.status.tone }, primaryAction },
|
|
9004
|
+
status: shared === null ? null : { headline: shared.headline, sentence: shared.sentence },
|
|
9005
|
+
assignment: assignmentBrief(current),
|
|
8997
9006
|
ref: ref.id,
|
|
8998
9007
|
// One phrase for every inaccessible blocker, so even their number stays private.
|
|
8999
9008
|
blockedBy: visibleBlockers.length === blockers.length ? visibleBlockers : [...visibleBlockers, "a task in another project"],
|
|
@@ -9033,8 +9042,8 @@ function showTask(positional, context) {
|
|
|
9033
9042
|
`${task.id} ${task.state}${ref.deliverable === "report" ? " (scout — delivers a report)" : ""}`,
|
|
9034
9043
|
` ${task.title}`,
|
|
9035
9044
|
...(detail.work === null ? [] : [
|
|
9036
|
-
` status: ${detail.work.status.label}`,
|
|
9037
|
-
...(
|
|
9045
|
+
` status: ${shared?.headline ?? detail.work.status.label}${shared === null ? "" : ` — ${shared.sentence}`}`,
|
|
9046
|
+
...(primaryAction === null ? [] : [` next: ${primaryAction.label}${primaryAction.target.decisionId === null ? "" : ` — decision #${primaryAction.target.decisionId}`}`]),
|
|
9038
9047
|
]),
|
|
9039
9048
|
// The closed machine-authored verdict (Priority 2), computed once at
|
|
9040
9049
|
// completion — never re-derived here. Same words `verdictWords`
|
|
@@ -9042,7 +9051,7 @@ function showTask(positional, context) {
|
|
|
9042
9051
|
...(task.state !== "done" || detail.proofVerdict === null
|
|
9043
9052
|
? []
|
|
9044
9053
|
: [
|
|
9045
|
-
` proof: ${proofVerdictWords(detail.proofVerdict, detail.proofReasons).word}${detail.proofAccepted ? " (accepted)" : ""}`,
|
|
9054
|
+
` proof: ${proofVerdictWords(current?.receipt?.runId === latestFinished?.id ? current?.readiness?.verdict ?? detail.proofVerdict : detail.proofVerdict, detail.proofReasons).word}${detail.proofAccepted ? " (accepted)" : ""}`,
|
|
9046
9055
|
...(detail.proofReasons.length > 0 ? [` ${detail.proofReasons.join("; ")}`] : []),
|
|
9047
9056
|
...matrixWords(detail.proofMatrix),
|
|
9048
9057
|
...(detail.semanticCoverage === null ? [] : coverageWords(detail.semanticCoverage).map(line => ` ${line}`)),
|
package/dist/proof.d.ts
CHANGED
|
@@ -437,6 +437,15 @@ export declare function matrixWords(matrix: readonly CriterionMatrixRow[]): stri
|
|
|
437
437
|
/** One criterion's context standing in words — shared by the CLI matrix
|
|
438
438
|
* and the console badge title so the two never drift. */
|
|
439
439
|
export declare function coverageStateWords(coverage: CriterionCoverage): string;
|
|
440
|
+
/** The verdict a result shows today, read from the stored one and the facts recorded since, never written back: a
|
|
441
|
+
* failed check or an unresolved HIGH review finding refutes; a pending review, or any acceptance criterion that has
|
|
442
|
+
* not passed, keeps it short. It only ever lowers the stored verdict. */
|
|
443
|
+
export declare function shownVerdictOf(stored: ProofVerdict | null, facts: {
|
|
444
|
+
checkFailed: boolean;
|
|
445
|
+
highFindings: number;
|
|
446
|
+
reviewPending: boolean;
|
|
447
|
+
criteriaPassed: boolean;
|
|
448
|
+
}): ProofVerdict | null;
|
|
440
449
|
/** Plain words for a verdict, shared by every surface (the `summary.ts`
|
|
441
450
|
* pattern) so the task page, run page, board, CLI, and chat cannot say
|
|
442
451
|
* three different things about the same run. */
|
package/dist/proof.js
CHANGED
|
@@ -976,6 +976,18 @@ export function coverageStateWords(coverage) {
|
|
|
976
976
|
return `GAP — inherited context is missing: ${coverage.gaps.join("; ")}${coverage.items.length > 0 ? ` (partial: ${coverage.items.join(", ")})` : ""}${prior}`;
|
|
977
977
|
}
|
|
978
978
|
}
|
|
979
|
+
/** The verdict a result shows today, read from the stored one and the facts recorded since, never written back: a
|
|
980
|
+
* failed check or an unresolved HIGH review finding refutes; a pending review, or any acceptance criterion that has
|
|
981
|
+
* not passed, keeps it short. It only ever lowers the stored verdict. */
|
|
982
|
+
export function shownVerdictOf(stored, facts) {
|
|
983
|
+
if (facts.checkFailed || facts.highFindings > 0 || stored === "refuted")
|
|
984
|
+
return "refuted";
|
|
985
|
+
if (stored === null)
|
|
986
|
+
return null;
|
|
987
|
+
if (facts.reviewPending || !facts.criteriaPassed)
|
|
988
|
+
return "short";
|
|
989
|
+
return stored;
|
|
990
|
+
}
|
|
979
991
|
/** Plain words for a verdict, shared by every surface (the `summary.ts`
|
|
980
992
|
* pattern) so the task page, run page, board, CLI, and chat cannot say
|
|
981
993
|
* three different things about the same run. */
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Store } from "./store.js";
|
|
2
|
+
export type ReleaseCoverage = {
|
|
3
|
+
run: number;
|
|
4
|
+
head: string;
|
|
5
|
+
};
|
|
6
|
+
/** How many of the newest passing release checks one read considers. */
|
|
7
|
+
export declare const RELEASE_SCAN_LIMIT = 20;
|
|
8
|
+
export type IsAncestor = (repo: string, ancestor: string, descendant: string) => boolean | null;
|
|
9
|
+
/** `git merge-base --is-ancestor`: true, false, or null when Git couldn't say. */
|
|
10
|
+
export declare const gitIsAncestor: IsAncestor;
|
|
11
|
+
export type ReleaseMemo = Map<string, ReleaseCoverage[]>;
|
|
12
|
+
/** The release check covering `head` in `repo`, or null. `memo` keeps one read's receipts. */
|
|
13
|
+
export declare function releaseCoverageOf(store: Store, root: string | undefined, repo: string | null, head: string | null, options?: {
|
|
14
|
+
memo?: ReleaseMemo;
|
|
15
|
+
isAncestor?: IsAncestor;
|
|
16
|
+
}): ReleaseCoverage | null;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/** Whether a release check already covers a result whose checks were Off: a
|
|
2
|
+
* passing FULL release check (run_check.release) on the same project whose
|
|
3
|
+
* checked commit contains the result's commit. Read at read time from the
|
|
4
|
+
* existing records and Git; nothing is written back, so no proof, receipt,
|
|
5
|
+
* scope digest or schema changes. Fails closed: a receipt that can't be read,
|
|
6
|
+
* a missing commit or any Git error leaves the result uncovered. */
|
|
7
|
+
import { execFileSync } from "node:child_process";
|
|
8
|
+
import { verificationEvidence } from "./verification-evidence.js";
|
|
9
|
+
import { runCheckLevel } from "./check-levels.js";
|
|
10
|
+
/** How many of the newest passing release checks one read considers. */
|
|
11
|
+
export const RELEASE_SCAN_LIMIT = 20;
|
|
12
|
+
const SHA = /^[a-f0-9]{40}$/;
|
|
13
|
+
/** Ancestry between two fixed commits never changes, so a settled answer is kept; errors are not. */
|
|
14
|
+
const ancestry = new Map();
|
|
15
|
+
const ANCESTRY_LIMIT = 4096;
|
|
16
|
+
/** `git merge-base --is-ancestor`: true, false, or null when Git couldn't say. */
|
|
17
|
+
export const gitIsAncestor = (repo, ancestor, descendant) => {
|
|
18
|
+
if (!SHA.test(ancestor) || !SHA.test(descendant))
|
|
19
|
+
return null;
|
|
20
|
+
if (ancestor === descendant)
|
|
21
|
+
return true;
|
|
22
|
+
const key = `${repo}\0${ancestor}\0${descendant}`;
|
|
23
|
+
const known = ancestry.get(key);
|
|
24
|
+
if (known !== undefined)
|
|
25
|
+
return known;
|
|
26
|
+
let answer;
|
|
27
|
+
try {
|
|
28
|
+
execFileSync("git", ["-C", repo, "merge-base", "--is-ancestor", ancestor, descendant], { stdio: "ignore", timeout: 5_000 });
|
|
29
|
+
answer = true;
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
// Exit 1 is Git's "no"; anything else (a missing object, no repository, a timeout) is no answer.
|
|
33
|
+
answer = error.status === 1 ? false : null;
|
|
34
|
+
}
|
|
35
|
+
if (answer !== null) {
|
|
36
|
+
if (ancestry.size >= ANCESTRY_LIMIT)
|
|
37
|
+
ancestry.clear();
|
|
38
|
+
ancestry.set(key, answer);
|
|
39
|
+
}
|
|
40
|
+
return answer;
|
|
41
|
+
};
|
|
42
|
+
/** The newest passing full release checks of one project, each with its sealed, verified commit. Read once per
|
|
43
|
+
* caller-held memo, so a list of results reads the receipts once. */
|
|
44
|
+
function passingReleases(store, root, repo) {
|
|
45
|
+
let rows;
|
|
46
|
+
try {
|
|
47
|
+
rows = store.handle.prepare(`SELECT rc.run FROM run_check AS rc INDEXED BY run_check_release
|
|
48
|
+
JOIN run ON run.id = rc.run JOIN task_ref AS ref ON ref.id = run.task_ref
|
|
49
|
+
WHERE rc.release = 1 AND rc.status = 'passed' AND rc.exit_code = 0 AND ref.repo = ?
|
|
50
|
+
ORDER BY rc.run DESC LIMIT ${RELEASE_SCAN_LIMIT}`).all(repo);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
return rows.flatMap(row => {
|
|
56
|
+
const run = Number(row["run"]);
|
|
57
|
+
try {
|
|
58
|
+
// Full only: a quick or Off release check covers nothing.
|
|
59
|
+
const level = runCheckLevel(store, run);
|
|
60
|
+
if (level !== null && level !== "full")
|
|
61
|
+
return [];
|
|
62
|
+
const gate = verificationEvidence(store, root, run);
|
|
63
|
+
if (!gate.ok || gate.bytes === null)
|
|
64
|
+
return [];
|
|
65
|
+
const receipt = JSON.parse(gate.bytes);
|
|
66
|
+
const head = store.getRun(run)?.headRevision ?? null;
|
|
67
|
+
if (receipt.run !== run || receipt.result?.ran !== true || receipt.result.exitCode !== 0)
|
|
68
|
+
return [];
|
|
69
|
+
if (typeof receipt.command?.repo === "string" && receipt.command.repo.startsWith("quick:"))
|
|
70
|
+
return [];
|
|
71
|
+
if (typeof receipt.head !== "string" || !SHA.test(receipt.head) || receipt.head !== head)
|
|
72
|
+
return [];
|
|
73
|
+
return [{ run, head: receipt.head }];
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/** The release check covering `head` in `repo`, or null. `memo` keeps one read's receipts. */
|
|
81
|
+
export function releaseCoverageOf(store, root, repo, head, options = {}) {
|
|
82
|
+
if (root === undefined || repo === null || head === null || !SHA.test(head))
|
|
83
|
+
return null;
|
|
84
|
+
const memo = options.memo ?? new Map();
|
|
85
|
+
let releases = memo.get(repo);
|
|
86
|
+
if (releases === undefined) {
|
|
87
|
+
releases = passingReleases(store, root, repo);
|
|
88
|
+
memo.set(repo, releases);
|
|
89
|
+
}
|
|
90
|
+
const isAncestor = options.isAncestor ?? gitIsAncestor;
|
|
91
|
+
return releases.find(one => isAncestor(repo, head, one.head) === true) ?? null;
|
|
92
|
+
}
|
package/dist/server/chat.js
CHANGED
|
@@ -40,7 +40,7 @@ import { refuse, screen } from "./chrome.js";
|
|
|
40
40
|
import { chatResultHref, chatReturnWithLatest, chatReturnWithSaid, projectChatHref, redirect, respond, safeChatReturn, safeReturn, taskChatHref, taskHref } from "./http.js";
|
|
41
41
|
import { chatAckPage, chatPage, coordinatorProposalsSection, decisionsFor, leadAfterComposerHtml, leadChatVersion, leadMintCard, leadPage, leadThreadHtml, PHONE_CARD_FACT, TASK_COMPOSER_MODES, taskChatLiveRegion, teamProposalCardParts } from "./render-chat.js";
|
|
42
42
|
import {} from "./render-pages.js";
|
|
43
|
-
import { completionForm, homePhaseWords, owedAcceptanceOf, resultPanelHtml } from "./render-results.js";
|
|
43
|
+
import { completionForm, hardBlockerOf, homePhaseWords, owedAcceptanceOf, resultPanelHtml } from "./render-results.js";
|
|
44
44
|
import { browserCaller, oneLineUa } from "./session.js";
|
|
45
45
|
export function createChatHandlers(runtime) {
|
|
46
46
|
const { firstRunStepsNow, managedRepos, leadWords, store, options, phoneSetup, visible, liveTurns, leadSaid, evidenceRoot, clock, sessions, CHAT_CANDIDATE_TTL_MS, chatFetcher, CHAT_CANDIDATES_PER_APPROVER, chatCeilingDigest, workAccess, familyOf, firstTasks, leadPrincipal, taskChatFocus, chatScopeOf, leadConversationRows, demoLeadHere, sendScreen, chromeFor, teamBrowserReply, team, teamChatProvider, runVisible, runIsLive, resultDetailOf, pullRequestTargetOf, chatEnablement, startLeadConversation, projectFamilyPeek, needsYouBadge, liveRefreshSeconds, chatKeyFor, chatCatalog, providerHome, mintApprovalNonce, checkLocalAgents, agentSignInCommand, bustBadge, authenticateApprover, revisionDestination, armTaskResume, consumeApprovalNonce } = runtime;
|
|
@@ -185,7 +185,9 @@ export function createChatHandlers(runtime) {
|
|
|
185
185
|
const roomId = url.searchParams.get('conversation');
|
|
186
186
|
const resultLink = (href) => roomId ? href + '&conversation=' + encodeURIComponent(roomId) : href;
|
|
187
187
|
// Accept and finish is here: the one act that accepts the person's own checks and finishes the task.
|
|
188
|
-
|
|
188
|
+
// Never offered where Complete is refused whatever is accepted (a failed check, a HIGH finding).
|
|
189
|
+
const finishes = resultRun !== null && who.role === 'approver' && focusTask?.assignment?.state === 'ready-to-check' && focusTask.assignment.receipt?.runId === resultRun.id &&
|
|
190
|
+
hardBlockerOf(focusTask.assignment) === null;
|
|
189
191
|
const resultPanel = resultRun === null
|
|
190
192
|
? null
|
|
191
193
|
: html `${resultPanelHtml(resultDetailOf(resultRun, who, now), {
|
|
@@ -267,7 +269,7 @@ export function createChatHandlers(runtime) {
|
|
|
267
269
|
if (repos.length > 0) {
|
|
268
270
|
try {
|
|
269
271
|
fleetSnapshot = store.chatSnapshot(repos, now);
|
|
270
|
-
const summaries = workIndexPage(store, now, { principal: 'operator', repos, includeUnplaced: false, viewer: who.name }, { limit: 100 }).items;
|
|
272
|
+
const summaries = workIndexPage(store, now, { principal: 'operator', repos, includeUnplaced: false, viewer: who.name }, { limit: 100, root: evidenceRoot }).items;
|
|
271
273
|
fleetSnapshot.assignmentStates = Object.fromEntries(summaries.flatMap(value => [value.rootId, value.activeTaskId].map(id => [id, { state: value.assignmentState, label: value.status.label, detail: value.status.detail }])));
|
|
272
274
|
fleetSnapshot.attentionCount = needsYouBadge(null);
|
|
273
275
|
}
|
|
@@ -1291,9 +1293,9 @@ export function createChatHandlers(runtime) {
|
|
|
1291
1293
|
agent: `${providerName(run.provider)} on ${run.runner}`, phase: homePhaseWords(run), project: run.repo === null ? null : projectName(run.repo), since: run.startedAt,
|
|
1292
1294
|
activity: runActivityOf(store, run, now) };
|
|
1293
1295
|
});
|
|
1294
|
-
const all = workIndexPage(store, now, access, { view: "all", limit: WORK_INDEX_MAX_LIMIT });
|
|
1295
|
-
const needs = workIndexPage(store, now, access, { view: "needs-you", limit: WORK_INDEX_MAX_LIMIT });
|
|
1296
|
-
const done = workIndexPage(store, now, access, { view: "completed", limit: WORK_INDEX_MAX_LIMIT });
|
|
1296
|
+
const all = workIndexPage(store, now, access, { view: "all", limit: WORK_INDEX_MAX_LIMIT, root: evidenceRoot });
|
|
1297
|
+
const needs = workIndexPage(store, now, access, { view: "needs-you", limit: WORK_INDEX_MAX_LIMIT, root: evidenceRoot });
|
|
1298
|
+
const done = workIndexPage(store, now, access, { view: "completed", limit: WORK_INDEX_MAX_LIMIT, root: evidenceRoot });
|
|
1297
1299
|
const weekAgo = new Date(now.getTime() - 7 * 86_400_000).toISOString();
|
|
1298
1300
|
const counts = [
|
|
1299
1301
|
{ key: "working", label: "Working now", value: agents.length, href: "/work?view=running" },
|