toolroll 0.9.14 → 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 +15 -0
- package/dist/assignment-ui.d.ts +1 -0
- package/dist/assignment-ui.js +6 -4
- 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 +2 -1
- package/dist/browser-workspace.d.ts +88 -18
- 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/lead-status.js +2 -2
- package/dist/needs-you.d.ts +48 -0
- package/dist/needs-you.js +93 -1
- package/dist/result-acts.d.ts +49 -5
- package/dist/result-acts.js +34 -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 +1121 -677
- package/dist/slack-chat.js +1 -1
- package/dist/task-status.d.ts +21 -4
- package/dist/task-status.js +41 -16
- 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 +13 -3
- package/dist/workspace-motion.js +3 -3
- package/dist/workspace-ui.d.ts +43 -7
- package/dist/workspace-ui.js +59 -15
- package/package.json +1 -1
package/dist/browser-crew.d.ts
CHANGED
|
@@ -4,7 +4,8 @@ import type { WorkIndexItem, WorkIndexPage } from './work-index.js';
|
|
|
4
4
|
/** The index is navigation only. Controls retain their exact owning task, run,
|
|
5
5
|
* decision and approval ceremony; opening a result keeps its saved execution. */
|
|
6
6
|
export declare function browserWorkActionHref(item: WorkIndexItem): string | null;
|
|
7
|
-
/** Render an already-admitted page without repeating its database query.
|
|
7
|
+
/** Render an already-admitted page without repeating its database query. Every task reads its one headline, the
|
|
8
|
+
* same words as the Tasks list, task page and result page. */
|
|
8
9
|
export declare function browserCrewFromIndex(page: WorkIndexPage, conversationId?: string): {
|
|
9
10
|
crew: BrowserCrewItem[];
|
|
10
11
|
crewTruncated: boolean;
|
package/dist/browser-crew.js
CHANGED
|
@@ -32,7 +32,8 @@ export function browserWorkActionHref(item) {
|
|
|
32
32
|
: action.code === 'start-worker' || action.code === 'repair-dependency' ? '#run-status' : '';
|
|
33
33
|
return `/t/${encodeURIComponent(item.rootId)}?version=${encodeURIComponent(taskId)}` + anchor;
|
|
34
34
|
}
|
|
35
|
-
/** Render an already-admitted page without repeating its database query.
|
|
35
|
+
/** Render an already-admitted page without repeating its database query. Every task reads its one headline, the
|
|
36
|
+
* same words as the Tasks list, task page and result page. */
|
|
36
37
|
export function browserCrewFromIndex(page, conversationId) {
|
|
37
38
|
const link = (href) => conversationId && href.startsWith('/chat?') ? href + '&conversation=' + encodeURIComponent(conversationId) : href;
|
|
38
39
|
const rows = page.items.map(summary => {
|
|
@@ -7,11 +7,11 @@ export { browserCrewFromIndex, browserWorkActionHref } from './browser-crew.js';
|
|
|
7
7
|
import type { Store } from './store.js';
|
|
8
8
|
import type { WorkSummaryAccess } from './work-summary.js';
|
|
9
9
|
import type { StatusTone } from './workspace-ui.js';
|
|
10
|
-
import type { Ask } from './needs-you.js';
|
|
10
|
+
import type { Ask, AskChip } from './needs-you.js';
|
|
11
11
|
import type { WorkIndexGroup } from './work-index.js';
|
|
12
12
|
import type { AssignmentCard } from './assignment-ui.js';
|
|
13
13
|
import type { TaskStatus } from './task-status.js';
|
|
14
|
-
import type { ResultActs } from './result-acts.js';
|
|
14
|
+
import type { AcceptLabel, ResultActFacts, ResultActs } from './result-acts.js';
|
|
15
15
|
import type { FirstRunStep, FirstTaskSuggestion, JourneyStep } from './first-run.js';
|
|
16
16
|
export type BrowserProject = {
|
|
17
17
|
name: string;
|
|
@@ -125,8 +125,10 @@ export type BrowserTasksView = {
|
|
|
125
125
|
tone: StatusTone;
|
|
126
126
|
token: string;
|
|
127
127
|
};
|
|
128
|
-
/** What a Needs you row asks
|
|
128
|
+
/** What a Needs you row asks: its group. */
|
|
129
129
|
ask: Ask | null;
|
|
130
|
+
/** The row's chip under its group: the specific ask (Plan, Result, Mismatch, Plan changed, Failed, Builder offline), or none. */
|
|
131
|
+
chip: AskChip | null;
|
|
130
132
|
group: BrowserTaskGroup;
|
|
131
133
|
action: BrowserLink | null;
|
|
132
134
|
detail: string | null;
|
|
@@ -367,17 +369,41 @@ export type BrowserTaskView = {
|
|
|
367
369
|
rebuild?: {
|
|
368
370
|
action: string;
|
|
369
371
|
} | null;
|
|
370
|
-
/**
|
|
371
|
-
*
|
|
372
|
-
|
|
372
|
+
/** What a failed task missed, in one line (the requirement it missed, the failing check's error line, or the stop
|
|
373
|
+
* reason), the evidence line behind it, one suggestion of what to change, and where to see it (its build's result
|
|
374
|
+
* page, or that exact check-log line); and Retry itself, its note starting with the suggestion. */
|
|
375
|
+
failure?: BrowserFailure | null;
|
|
376
|
+
retry?: {
|
|
377
|
+
action: string;
|
|
378
|
+
note?: string;
|
|
379
|
+
} | null;
|
|
380
|
+
/** Run checks in place, on a status row that offers it: posts and comes back to this page. */
|
|
381
|
+
runChecks?: BrowserRunChecks | null;
|
|
382
|
+
/** A live build: the step it is on (or stuck on, with the act that helps), and the earlier attempts that stopped
|
|
383
|
+
* before it, folded into one quiet line. */
|
|
384
|
+
progress?: {
|
|
373
385
|
line: string;
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
386
|
+
stuck: {
|
|
387
|
+
step: number;
|
|
388
|
+
why: string | null;
|
|
389
|
+
line: string;
|
|
390
|
+
action: BrowserLink | null;
|
|
377
391
|
} | null;
|
|
378
392
|
} | null;
|
|
379
|
-
|
|
393
|
+
/** Stop, on the Building card: the exact live build's stop form (posts its run id). */
|
|
394
|
+
stop?: {
|
|
380
395
|
action: string;
|
|
396
|
+
run: number;
|
|
397
|
+
} | null;
|
|
398
|
+
/** The Building card's link to the live build's own record ("Build #N record", /r/<id>). */
|
|
399
|
+
record?: BrowserLink | null;
|
|
400
|
+
earlier?: {
|
|
401
|
+
summary: string;
|
|
402
|
+
attempts: {
|
|
403
|
+
label: string;
|
|
404
|
+
href: string;
|
|
405
|
+
text: string | null;
|
|
406
|
+
}[];
|
|
381
407
|
} | null;
|
|
382
408
|
/** Server cards that may need a person now (stop/resume, scope prompt, plan, live attempt). */
|
|
383
409
|
lead: {
|
|
@@ -470,6 +496,12 @@ export type BrowserResultPanel = {
|
|
|
470
496
|
returnTo: string;
|
|
471
497
|
} | null;
|
|
472
498
|
} | null;
|
|
499
|
+
/** The Requirements row's counts, so it updates as the person answers their checks (null when it doesn't count them). */
|
|
500
|
+
requirements?: {
|
|
501
|
+
met: number;
|
|
502
|
+
total: number;
|
|
503
|
+
yours: number;
|
|
504
|
+
} | null;
|
|
473
505
|
/** Storage limits on saved output (shortened logs or diffs): shown on request. */
|
|
474
506
|
limits: string[];
|
|
475
507
|
tabs: {
|
|
@@ -518,13 +550,19 @@ export type BrowserCheckItem = {
|
|
|
518
550
|
}[];
|
|
519
551
|
};
|
|
520
552
|
/** The result's one decision: what Accept is called, why it isn't plain
|
|
521
|
-
* Accept, and what pressing it does.
|
|
553
|
+
* Accept and finish, and what pressing it does. `base`: the same words before
|
|
554
|
+
* the person's own checks, which the page adds as they answer them. */
|
|
522
555
|
export type BrowserResultDecision = {
|
|
523
|
-
label:
|
|
556
|
+
label: AcceptLabel;
|
|
524
557
|
ready: boolean;
|
|
525
558
|
why: string | null;
|
|
526
559
|
effect: string;
|
|
527
560
|
sentence: string;
|
|
561
|
+
base?: {
|
|
562
|
+
label: AcceptLabel;
|
|
563
|
+
ready: boolean;
|
|
564
|
+
why: string | null;
|
|
565
|
+
};
|
|
528
566
|
};
|
|
529
567
|
/** A Needs you action: a link, (Confirm it stopped) a form behind the password, (Build again) one
|
|
530
568
|
* button, or (on the result itself) Accept, which records the person's acceptance; `note` asks why
|
|
@@ -587,12 +625,17 @@ export type BrowserResultView = {
|
|
|
587
625
|
detail: string;
|
|
588
626
|
control: string;
|
|
589
627
|
} | null;
|
|
628
|
+
/** Accept and finish: the exact receipt read; `accept` when the one request also records the person's acceptance
|
|
629
|
+
* (`note`: the words over the reason field it asks for, or null for none). */
|
|
590
630
|
complete: {
|
|
591
631
|
action: string;
|
|
592
632
|
receipt: string;
|
|
593
633
|
run: number;
|
|
634
|
+
accept?: {
|
|
635
|
+
note: string | null;
|
|
636
|
+
} | null;
|
|
594
637
|
} | null;
|
|
595
|
-
/** Words for the result's Accept:
|
|
638
|
+
/** Words for the result's Accept: Accept and finish when offered, else the Needs you acceptance. */
|
|
596
639
|
decision: BrowserResultDecision | null;
|
|
597
640
|
checks: {
|
|
598
641
|
detail: string;
|
|
@@ -617,12 +660,24 @@ export type BrowserResultView = {
|
|
|
617
660
|
} | null;
|
|
618
661
|
/** The one ink act that resolves the result, the one outline act beside it, and why it can't be accepted yet (result-acts.ts). */
|
|
619
662
|
acts: ResultActs;
|
|
663
|
+
/** The facts the acts were chosen from, so the page chooses again as the person answers their checks. */
|
|
664
|
+
actFacts?: ResultActFacts;
|
|
620
665
|
/** Run checks on this result's commit: the project's check, when it didn't run. */
|
|
621
|
-
runChecks:
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
666
|
+
runChecks: BrowserRunChecks | null;
|
|
667
|
+
/** A failed build's result: what it missed and the suggestion Retry's note starts with; Retry itself when the task
|
|
668
|
+
* can be retried from here. Null for every other result. */
|
|
669
|
+
failure?: (BrowserFailure & {
|
|
670
|
+
retry: {
|
|
671
|
+
action: string;
|
|
672
|
+
note: string;
|
|
673
|
+
} | null;
|
|
674
|
+
/** A failed task's delivered result, accepted only in outline with a reason (the task's accept-proof). */
|
|
675
|
+
acceptAnyway?: {
|
|
676
|
+
action: string;
|
|
677
|
+
run: number;
|
|
678
|
+
returnTo: string;
|
|
679
|
+
} | null;
|
|
680
|
+
}) | null;
|
|
626
681
|
/** The raw run record, under Details: its facts and the full record. */
|
|
627
682
|
record: {
|
|
628
683
|
build: number;
|
|
@@ -635,6 +690,7 @@ export type BrowserResultView = {
|
|
|
635
690
|
/** The signed scope; null when none was filed. */
|
|
636
691
|
intent: {
|
|
637
692
|
approval: string;
|
|
693
|
+
approvedAt: string | null;
|
|
638
694
|
html: string;
|
|
639
695
|
} | null;
|
|
640
696
|
noRun: string | null;
|
|
@@ -647,6 +703,20 @@ export type BrowserResultView = {
|
|
|
647
703
|
}[];
|
|
648
704
|
} | null;
|
|
649
705
|
};
|
|
706
|
+
export type BrowserFailure = {
|
|
707
|
+
line: string;
|
|
708
|
+
evidence: string | null;
|
|
709
|
+
suggestion: string;
|
|
710
|
+
link: {
|
|
711
|
+
label: string;
|
|
712
|
+
href: string;
|
|
713
|
+
} | null;
|
|
714
|
+
};
|
|
715
|
+
export type BrowserRunChecks = {
|
|
716
|
+
action: string;
|
|
717
|
+
level: "quick" | "full";
|
|
718
|
+
returnTo: string;
|
|
719
|
+
};
|
|
650
720
|
/** One zone on a flow's canvas: its step, where it leads, and where it sits. */
|
|
651
721
|
export type BrowserFlowStage = {
|
|
652
722
|
id: string;
|
package/dist/chat-actions.d.ts
CHANGED
package/dist/chat-actions.js
CHANGED
|
@@ -12,7 +12,7 @@ import { approve, authenticateApprover, describeScope, scopeAuthorityOf, } from
|
|
|
12
12
|
import { readVerifiedArtifact, scanForSecrets } from "./evidence.js";
|
|
13
13
|
import { readAuthModeStrict } from "./keys.js";
|
|
14
14
|
import { parseProof } from "./proof.js";
|
|
15
|
-
import {
|
|
15
|
+
import { acceptAndCompleteAsOperator, assignmentOf } from "./assignment.js";
|
|
16
16
|
import { getDecision, recordDecision, retireDecision } from "./project-memory.js";
|
|
17
17
|
import { resumeTaskStop, taskControlOf } from "./task-control.js";
|
|
18
18
|
import { addToolTo, catalogTool, projectToolsOf, removeToolFrom, toolCommandLine, validateToolSpec } from "./project-tools.js";
|
|
@@ -108,7 +108,7 @@ export const CHAT_ACTIONS = {
|
|
|
108
108
|
decision_record: { label: "Record decision", protected: false, password: false },
|
|
109
109
|
decision_retire: { label: "Retire decision", protected: false, password: false },
|
|
110
110
|
scope_approve: { label: "Approve work", protected: true, password: true },
|
|
111
|
-
result_accept: { label: "
|
|
111
|
+
result_accept: { label: "Accept and finish", protected: true, password: false },
|
|
112
112
|
task_cancel: { label: "Cancel task", protected: true, password: false },
|
|
113
113
|
task_resume: { label: "Resume task", protected: true, password: true },
|
|
114
114
|
};
|
|
@@ -1098,7 +1098,9 @@ export function executeSharedAction(store, who, id, payload, now, options) {
|
|
|
1098
1098
|
throw Error("The comparison terms changed. Nothing was approved.");
|
|
1099
1099
|
}
|
|
1100
1100
|
else if (payload.operation === "result_accept") {
|
|
1101
|
-
|
|
1101
|
+
// Accept and finish, as on the result page: whether a check only the person makes is accepted
|
|
1102
|
+
// too is decided inside the same transaction, against the receipt as it stands.
|
|
1103
|
+
const completed = acceptAndCompleteAsOperator(store, task, { runId: null, receiptDigest: String(payload.state["receipt"] ?? ""), note: null }, who, now, options.root);
|
|
1102
1104
|
if (!completed.ok)
|
|
1103
1105
|
throw Error(completed.message);
|
|
1104
1106
|
}
|
|
@@ -1125,7 +1127,7 @@ export function executeSharedAction(store, who, id, payload, now, options) {
|
|
|
1125
1127
|
const said = payload.operation === "skill_test"
|
|
1126
1128
|
? "Skill test created. Existing approval rules apply."
|
|
1127
1129
|
: payload.operation === "result_accept"
|
|
1128
|
-
? "
|
|
1130
|
+
? "Accepted and finished. The recorded checks are unchanged."
|
|
1129
1131
|
: payload.operation === "scope_approve"
|
|
1130
1132
|
? "The exact work is approved."
|
|
1131
1133
|
: payload.operation === "task_cancel"
|
|
@@ -76,7 +76,7 @@ export type ChatContent = {
|
|
|
76
76
|
label: string;
|
|
77
77
|
path: string;
|
|
78
78
|
};
|
|
79
|
-
/** More link buttons after `link`: [Look first] beside [Merge] or [
|
|
79
|
+
/** More link buttons after `link`: [Look first] beside [Merge] or [Accept and finish]. */
|
|
80
80
|
also?: Array<{
|
|
81
81
|
label: string;
|
|
82
82
|
path: string;
|
package/dist/chat-voice.d.ts
CHANGED
|
@@ -24,7 +24,7 @@ export type FinishedFact = {
|
|
|
24
24
|
checks: "passed" | "failed" | "not-run" | "off" | null;
|
|
25
25
|
report: boolean;
|
|
26
26
|
completedBy: string | null;
|
|
27
|
-
/** An open pull request carries this result: the ask is "Merge it?" rather than "
|
|
27
|
+
/** An open pull request carries this result: the ask is "Merge it?" rather than "Accept and finish it?". */
|
|
28
28
|
pullRequest?: boolean;
|
|
29
29
|
/** The lead did this (built it, or marked it complete), so the lead says "I". */
|
|
30
30
|
lead?: boolean;
|
|
@@ -40,10 +40,10 @@ export type FinishedFact = {
|
|
|
40
40
|
/** The two buttons under a result ready for a person: the real next step, then a look first. */
|
|
41
41
|
export declare const READY_ACTIONS: {
|
|
42
42
|
readonly merge: "Merge";
|
|
43
|
-
readonly complete: "
|
|
43
|
+
readonly complete: "Accept and finish";
|
|
44
44
|
readonly look: "Look first";
|
|
45
45
|
};
|
|
46
|
-
/** Whether this line asks for the result's next step, so it carries [Merge] or [
|
|
46
|
+
/** Whether this line asks for the result's next step, so it carries [Merge] or [Accept and finish] and [Look first]. */
|
|
47
47
|
export declare function asksToFinish(fact: FinishedFact): boolean;
|
|
48
48
|
/** One update, said plainly: what happened, then what happens next. The lead says "I" for what it did. */
|
|
49
49
|
export declare function finishedLine(fact: FinishedFact): string;
|
package/dist/chat-voice.js
CHANGED
|
@@ -42,8 +42,8 @@ export function inSentence(summary) {
|
|
|
42
42
|
return /^\p{Lu}\p{Ll}/u.test(summary) ? summary[0].toLowerCase() + summary.slice(1) : summary;
|
|
43
43
|
}
|
|
44
44
|
/** The two buttons under a result ready for a person: the real next step, then a look first. */
|
|
45
|
-
export const READY_ACTIONS = { merge: "Merge", complete: "
|
|
46
|
-
/** Whether this line asks for the result's next step, so it carries [Merge] or [
|
|
45
|
+
export const READY_ACTIONS = { merge: "Merge", complete: "Accept and finish", look: "Look first" };
|
|
46
|
+
/** Whether this line asks for the result's next step, so it carries [Merge] or [Accept and finish] and [Look first]. */
|
|
47
47
|
export function asksToFinish(fact) {
|
|
48
48
|
return fact.update == null && fact.headline === "Ready for review" && !fact.report;
|
|
49
49
|
}
|
|
@@ -61,7 +61,7 @@ export function finishedLine(fact) {
|
|
|
61
61
|
if (fact.report)
|
|
62
62
|
return fact.lead === true ? `I wrote up ${lower}. The report is ready to read.` : `${name}: the report is ready to read.`;
|
|
63
63
|
if (fact.checks === "passed")
|
|
64
|
-
return `${ready}. Your tests passed. ${fact.pullRequest === true ? "Merge it?" : "
|
|
64
|
+
return `${ready}. Your tests passed. ${fact.pullRequest === true ? "Merge it?" : "Accept and finish it?"}`;
|
|
65
65
|
if (fact.checks === "off")
|
|
66
66
|
return `${ready}. Checks are off for this project, so look it over first.`;
|
|
67
67
|
return `${ready}, but no tests ran. Look it over first.`;
|
package/dist/demo.js
CHANGED
|
@@ -97,6 +97,57 @@ const DEMO_HANDOFF = {
|
|
|
97
97
|
followUps: [],
|
|
98
98
|
decisionsIncorporated: [],
|
|
99
99
|
};
|
|
100
|
+
/** The failed demo build: it took the flag out of settlement and config, but
|
|
101
|
+
* not out of the admin override, and its own report says so. */
|
|
102
|
+
const DEMO_FAILED_PATCH = `diff --git a/src/payout.ts b/src/payout.ts
|
|
103
|
+
index 7c1d2e3..5a1f0c7 100644
|
|
104
|
+
--- a/src/payout.ts
|
|
105
|
+
+++ b/src/payout.ts
|
|
106
|
+
@@ -18,9 +18,5 @@ export function settleAll(rows: PayoutRow[]): number {
|
|
107
|
+
- if (config.LEGACY_PAYOUT) {
|
|
108
|
+
- return rows.reduce((sum, row) => sum + legacySettle(row), 0);
|
|
109
|
+
- }
|
|
110
|
+
-
|
|
111
|
+
- return rows.reduce((sum, row) => sum + settle(row.cents, row.rate), 0);
|
|
112
|
+
+ return rows.reduce((sum, row) => sum + settle(row.cents, row.rate), 0);
|
|
113
|
+
}
|
|
114
|
+
diff --git a/src/config.ts b/src/config.ts
|
|
115
|
+
index 2b3c4d5..8e9f0a1 100644
|
|
116
|
+
--- a/src/config.ts
|
|
117
|
+
+++ b/src/config.ts
|
|
118
|
+
@@ -4,8 +4,6 @@ export const config = {
|
|
119
|
+
PAYOUT_BATCH_SIZE: 500,
|
|
120
|
+
- // Retired once every merchant is on the new settlement path.
|
|
121
|
+
- LEGACY_PAYOUT: process.env.LEGACY_PAYOUT === "1",
|
|
122
|
+
PAYOUT_CURRENCY: "USD",
|
|
123
|
+
};
|
|
124
|
+
`;
|
|
125
|
+
const DEMO_FAILED_HANDOFF = {
|
|
126
|
+
schema: 1,
|
|
127
|
+
outcome: "built",
|
|
128
|
+
committed: true,
|
|
129
|
+
conclusion: "Removed LEGACY_PAYOUT from settlement and config. The admin override still reads it, so one reference remains.",
|
|
130
|
+
changes: ["Dropped the legacy settlement branch in src/payout.ts.", "Removed the LEGACY_PAYOUT flag from src/config.ts."],
|
|
131
|
+
verification: ["Searched the codebase for LEGACY_PAYOUT after the change."],
|
|
132
|
+
followUps: [],
|
|
133
|
+
decisionsIncorporated: [],
|
|
134
|
+
};
|
|
135
|
+
const DEMO_FAILED_PROOF = {
|
|
136
|
+
version: 1,
|
|
137
|
+
criteria: [
|
|
138
|
+
{
|
|
139
|
+
id: "c1",
|
|
140
|
+
statement: "No reference to LEGACY_PAYOUT remains in the codebase.",
|
|
141
|
+
verdict: "met",
|
|
142
|
+
how: "Removed the flag and the settlement branch behind it.",
|
|
143
|
+
evidence: [{ kind: "changed-path", ref: "src/payout.ts" }, { kind: "changed-path", ref: "src/config.ts" }],
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
checks: [],
|
|
147
|
+
changed: ["src/payout.ts", "src/config.ts"],
|
|
148
|
+
caveats: ["c1: src/admin/overrides.ts still reads LEGACY_PAYOUT for the per-merchant override toggle, so one reference remains."],
|
|
149
|
+
screenshots: [],
|
|
150
|
+
};
|
|
100
151
|
/** The evidence bundle's own manifest (Acceptance Contract v2 demo): the
|
|
101
152
|
* seeded sandbox answers its OWN signed rubric by exact id, with typed
|
|
102
153
|
* evidence references — the same shape a real builder writes — so a
|
|
@@ -472,14 +523,15 @@ export function seedDemo(store, repos, evidenceRoot, now) {
|
|
|
472
523
|
store.finishRun(buildingPriorRun, { outcome: "refused", reason: "plan-revised", now: hoursAgo(1.6) });
|
|
473
524
|
// The board's "building" lane keys off a live claim — take one through
|
|
474
525
|
// the real claim machinery so the card wears worker and lease honestly.
|
|
475
|
-
acquire(store, buildingRef, "night-shift-1", {
|
|
526
|
+
const liveClaim = acquire(store, buildingRef, "night-shift-1", {
|
|
476
527
|
now: hoursAgo(0.4),
|
|
477
528
|
token: nightShift.token,
|
|
478
529
|
ttlMs: 4 * 3_600_000,
|
|
479
530
|
});
|
|
480
531
|
const liveRun = store.startRun({
|
|
481
532
|
taskRef: buildingRef,
|
|
482
|
-
|
|
533
|
+
// The claim's own lease, so the build reads as live: the run the task's current claim owns.
|
|
534
|
+
leaseId: liveClaim.ok ? liveClaim.claim.leaseId : "demo-lease-live",
|
|
483
535
|
runner: "night-shift-1",
|
|
484
536
|
branch: `toolroll/${building}`,
|
|
485
537
|
worktree: join(repos.api, ".demo-worktree-2"),
|
|
@@ -603,6 +655,34 @@ export function seedDemo(store, repos, evidenceRoot, now) {
|
|
|
603
655
|
now: hoursAgo(6),
|
|
604
656
|
...presentedRoute(store, store.refFor("built-in", failed).id, "builder"),
|
|
605
657
|
});
|
|
658
|
+
// What the failed build left: its saved changes, its report and the
|
|
659
|
+
// machine's reading of them. It removed the flag from settlement but left
|
|
660
|
+
// the admin override reading it, and said so in its own note — so the
|
|
661
|
+
// signed requirement is missed, and the task card can name it.
|
|
662
|
+
store.stampRun(failedRun, { baseRevision: "4b825dc642cb6eb9a060e54bf8d69288fbee4904", scopeDigest: failedProposed.digest });
|
|
663
|
+
store.recordOutcomeFacts(failedRun, { headRevision: "5a1f0c7e2b9d4e6f8a0b1c2d3e4f5a6b7c8d9e0f" });
|
|
664
|
+
storeEvidence(store, evidenceRoot, failedRun, "terminal-diff", "terminal-diff.patch", Buffer.from(DEMO_FAILED_PATCH, "utf8"), "git diff --no-ext-diff --no-textconv --no-color 4b825dc6..HEAD (exit 0) [demo: synthetic]", hoursAgo(5.6));
|
|
665
|
+
const failedStat = {
|
|
666
|
+
schema: 1, base: "4b825dc642cb6eb9a060e54bf8d69288fbee4904", head: "5a1f0c7e2b9d4e6f8a0b1c2d3e4f5a6b7c8d9e0f",
|
|
667
|
+
fileCount: 2, additions: 1, deletions: 7, binaryCount: 0,
|
|
668
|
+
files: [{ path: "src/payout.ts", additions: 1, deletions: 5 }, { path: "src/config.ts", additions: 0, deletions: 2 }],
|
|
669
|
+
filesTruncated: false,
|
|
670
|
+
};
|
|
671
|
+
storeEvidence(store, evidenceRoot, failedRun, "diff-stat", "diff-stat.json", budgetedStatJson(failedStat), "parsed from git diff --numstat -z [demo: synthetic]", hoursAgo(5.6));
|
|
672
|
+
storeEvidence(store, evidenceRoot, failedRun, "handoff", "handoff.json", Buffer.from(JSON.stringify(DEMO_FAILED_HANDOFF, null, 2), "utf8"), "composed at completion [demo: synthetic]", hoursAgo(5.6));
|
|
673
|
+
storeEvidence(store, evidenceRoot, failedRun, "proof", "proof.json", Buffer.from(JSON.stringify(DEMO_FAILED_PROOF, null, 2), "utf8"), "agent-authored proof (validated, re-serialized) [demo: synthetic]", hoursAgo(5.6));
|
|
674
|
+
const failedAdjudicated = adjudicate({
|
|
675
|
+
proofArtifactPresent: true,
|
|
676
|
+
proofParse: parseProof(JSON.stringify(DEMO_FAILED_PROOF)),
|
|
677
|
+
handoffPresent: true,
|
|
678
|
+
terminalDiffPresent: true,
|
|
679
|
+
terminalDiffCaptureStatus: "ok",
|
|
680
|
+
diffStat: { captured: true, truncated: false, paths: new Set(failedStat.files.map(one => one.path)) },
|
|
681
|
+
verifyCommand: { configured: false },
|
|
682
|
+
screenshots: [],
|
|
683
|
+
approvedCriteria: failedProposed.acceptance,
|
|
684
|
+
});
|
|
685
|
+
store.saveProofVerdict(failedRun, failedAdjudicated.verdict, failedAdjudicated.reasons, hoursAgo(5.5), failedAdjudicated.matrix);
|
|
606
686
|
store.finishRun(failedRun, {
|
|
607
687
|
outcome: "failed",
|
|
608
688
|
reason: "acceptance",
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** One disclosure glyph everywhere: the chevron. It points right while a fold is shut and turns down when it opens,
|
|
2
|
+
* on the server pages, the enhanced folds (workspace-motion.ts) and the React views (lucide's ChevronRight). Every
|
|
3
|
+
* other marker (the native triangle, ▸/▾, +/−, ⌄ and the old arrows) is turned off here, last, so no page can bring
|
|
4
|
+
* a second glyph back. A summary that carries its own icon keeps it. Drawn with two borders: the page's content
|
|
5
|
+
* policy allows no image, not even a data: one. */
|
|
6
|
+
export declare const DISCLOSURE_CSS: string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** One disclosure glyph everywhere: the chevron. It points right while a fold is shut and turns down when it opens,
|
|
2
|
+
* on the server pages, the enhanced folds (workspace-motion.ts) and the React views (lucide's ChevronRight). Every
|
|
3
|
+
* other marker (the native triangle, ▸/▾, +/−, ⌄ and the old arrows) is turned off here, last, so no page can bring
|
|
4
|
+
* a second glyph back. A summary that carries its own icon keeps it. Drawn with two borders: the page's content
|
|
5
|
+
* policy allows no image, not even a data: one. */
|
|
6
|
+
export const DISCLOSURE_CSS = `details>summary{list-style:none}details>summary::-webkit-details-marker{display:none}` +
|
|
7
|
+
`details>summary::after{content:none!important}` +
|
|
8
|
+
`details>summary:not(:has(>svg))::before{content:""!important;display:inline-block;flex:none;box-sizing:border-box;width:7px!important;height:7px!important;` +
|
|
9
|
+
`margin:0 .55rem 0 .2rem!important;padding:0!important;position:static!important;vertical-align:.1em;font-size:0;background:none!important;` +
|
|
10
|
+
`border:0 solid currentColor!important;border-right-width:1.5px!important;border-bottom-width:1.5px!important;opacity:.65;` +
|
|
11
|
+
`transform:rotate(-45deg)!important;transition:transform .15s ease}` +
|
|
12
|
+
`details[open]>summary:not(:has(>svg))::before{transform:rotate(45deg)!important}` +
|
|
13
|
+
`@media (prefers-reduced-motion:reduce){details>summary::before{transition:none!important}}`;
|
package/dist/lead-status.js
CHANGED
|
@@ -90,14 +90,14 @@ export function taskWaitSnapshot(store, taskId, now, watchedRun) {
|
|
|
90
90
|
if (outcome !== "built" && outcome !== "no-change")
|
|
91
91
|
return answer("Failed", "Inspect attempt", true, 1, "failed");
|
|
92
92
|
if (state !== "done")
|
|
93
|
-
return answer("Ready", "
|
|
93
|
+
return answer("Ready", "Open result", true, 0, "ready");
|
|
94
94
|
}
|
|
95
95
|
// A completed family is Complete from any of its tasks.
|
|
96
96
|
const completed = state === "done" && familyCompleted(store, taskId);
|
|
97
97
|
if (completed)
|
|
98
98
|
return answer("Complete", "No action needed", true, 0, "complete");
|
|
99
99
|
if (state === "done")
|
|
100
|
-
return answer("Ready", "
|
|
100
|
+
return answer("Ready", "Open result", true, 0, "ready");
|
|
101
101
|
if (state === "failed" || state === "cancelled")
|
|
102
102
|
return answer("Failed", state === "cancelled" ? "Review cancellation" : "Inspect failure", true, 1, "failed");
|
|
103
103
|
const decision = store.handle.prepare(`SELECT decision.id FROM run INDEXED BY lead_status_task_run
|
package/dist/needs-you.d.ts
CHANGED
|
@@ -35,6 +35,30 @@ export declare const NEEDS: Readonly<Record<NeedKey, Need>>;
|
|
|
35
35
|
export type Ask = "decide" | "review" | "unblock";
|
|
36
36
|
export declare const ASKS: readonly Ask[];
|
|
37
37
|
export declare const ASK_LABEL: Readonly<Record<Ask, string>>;
|
|
38
|
+
/** A waiting row's chip: the specific thing asked, never its group's own word (no "Decide" chip under Decide).
|
|
39
|
+
* Null: the group heading and the row's sentence already say it, so the row wears no chip. */
|
|
40
|
+
export type AskChip = "Plan" | "Result" | "Mismatch" | "Plan changed" | "Failed" | "Builder offline";
|
|
41
|
+
/** `mismatch`: the saved report doesn't match its changes; `planChanged`: the plan changed after it was built. */
|
|
42
|
+
export declare function askChipOf(read: {
|
|
43
|
+
headline: string;
|
|
44
|
+
need?: NeedKey | null;
|
|
45
|
+
mismatch?: boolean;
|
|
46
|
+
planChanged?: boolean;
|
|
47
|
+
ask?: Ask | null;
|
|
48
|
+
}): AskChip | null;
|
|
49
|
+
/** Why a saved result can't simply be accepted yet, in its own words (each Review row says its own reason,
|
|
50
|
+
* never a boilerplate line). The facts are the ones the Tasks list and the task page both read. */
|
|
51
|
+
export type ResultHoldUp = {
|
|
52
|
+
hold: string | null;
|
|
53
|
+
planChanged: boolean;
|
|
54
|
+
unapproved: boolean;
|
|
55
|
+
running: boolean;
|
|
56
|
+
unfinished: boolean;
|
|
57
|
+
noCommit: boolean;
|
|
58
|
+
question: boolean;
|
|
59
|
+
report: boolean;
|
|
60
|
+
};
|
|
61
|
+
export declare function resultHoldUpSentence(facts: Partial<ResultHoldUp>): string;
|
|
38
62
|
export declare const NEED_ASK: Readonly<Record<NeedKey, Ask>>;
|
|
39
63
|
export declare const WAITS: Readonly<Record<WaitKey, (context: NeedContext) => string>>;
|
|
40
64
|
/** Words that name the machine's internals; no Needs you or Waiting sentence carries them. */
|
|
@@ -83,4 +107,28 @@ export declare function isInternalErrorReason(reason: string | null | undefined)
|
|
|
83
107
|
/** A failed attempt in one plain line: its recorded reason in words (first line only, about 140 characters at most),
|
|
84
108
|
* that it stopped with an internal error when the reason is machine output, or that none was recorded. */
|
|
85
109
|
export declare function failedAttemptSentence(reason: string | null | undefined): string;
|
|
110
|
+
export declare const GENERAL_SUGGESTION = "Say what to do differently this time.";
|
|
111
|
+
export declare function stopSuggestionOf(reason: string | null | undefined): string;
|
|
112
|
+
/** The suggestion for a requirement the result missed: make sure of it before handing off. */
|
|
113
|
+
export declare function missedRequirementSuggestion(statement: string): string;
|
|
114
|
+
/** The suggestion for a check that failed on the result: make it pass, starting with the line it ended on. */
|
|
115
|
+
export declare function failingCheckSuggestion(line: string): string;
|
|
116
|
+
/** What a failed attempt missed, in plain words: what failed (the missed requirement, the failing check's line or the
|
|
117
|
+
* stop reason), the evidence line behind it, and one suggestion of what to change (Retry's note starts with it). */
|
|
118
|
+
export type FailureExplanation = {
|
|
119
|
+
kind: "requirement" | "check" | "reason";
|
|
120
|
+
line: string;
|
|
121
|
+
evidence: string | null;
|
|
122
|
+
suggestion: string;
|
|
123
|
+
link: {
|
|
124
|
+
label: string;
|
|
125
|
+
href: string;
|
|
126
|
+
} | null;
|
|
127
|
+
};
|
|
128
|
+
/** The line for a missed requirement: “Missed: <statement>”, cut to one line. */
|
|
129
|
+
export declare function missedRequirementLine(statement: string): string;
|
|
130
|
+
/** The longest note Retry's field takes. */
|
|
131
|
+
export declare const RETRY_NOTE_LIMIT = 500;
|
|
132
|
+
/** Retry's note, prefilled with the suggestion: cut to what the field takes, at a word, so the form always posts. */
|
|
133
|
+
export declare function retryNoteOf(suggestion: string): string;
|
|
86
134
|
export {};
|
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",
|
|
@@ -138,6 +173,7 @@ export const RUN_REASON_WORDS = {
|
|
|
138
173
|
"reviewer-error": "the review failed",
|
|
139
174
|
"malformed-report": "the scout's report was malformed",
|
|
140
175
|
"attempts-exhausted": "it failed too many times in a row",
|
|
176
|
+
"plan-revised": "the plan changed, so a fresh attempt took over",
|
|
141
177
|
};
|
|
142
178
|
/** A run's recorded reason in words. A recorded code is never shown as it is
|
|
143
179
|
* stored: one with no words of its own reads as an unexpected stop; a reason
|
|
@@ -190,3 +226,59 @@ export function failedAttemptSentence(reason) {
|
|
|
190
226
|
: `${words.slice(0, REASON_LINE_LIMIT - 1).replace(/\s+\S*$/, "").replace(/[,;:\s]+$/, "")}…`;
|
|
191
227
|
return `${line.charAt(0).toUpperCase()}${line.slice(1)}${line.endsWith("…") ? "" : "."}`;
|
|
192
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
|
+
}
|