toolroll 0.9.86 → 0.9.87
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 +59 -0
- package/dist/agents-board.d.ts +12 -0
- package/dist/agents-board.js +26 -0
- package/dist/approval-rules-ui.d.ts +4 -1
- package/dist/approval-rules-ui.js +31 -15
- package/dist/assignment-ui.js +1 -1
- package/dist/backup-ui.d.ts +5 -4
- package/dist/backup-ui.js +65 -32
- package/dist/browser/terminal.css +1 -1
- package/dist/browser/terminal.js +57 -54
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +35 -70
- package/dist/browser-crew.js +1 -1
- package/dist/browser-workspace.d.ts +329 -183
- package/dist/browser-workspace.js +10 -9
- package/dist/chat-controls.d.ts +1 -1
- package/dist/chat-controls.js +1 -1
- package/dist/chat-settings-page.d.ts +29 -9
- package/dist/chat-settings-page.js +59 -28
- package/dist/check-levels-ui.d.ts +2 -1
- package/dist/check-levels-ui.js +67 -58
- package/dist/contracts/cli.d.ts +43 -0
- package/dist/contracts/cli.js +4 -0
- package/dist/contracts/console-api.d.ts +1 -1
- package/dist/contracts/console-api.js +1 -1
- package/dist/credentials-ui.d.ts +5 -3
- package/dist/credentials-ui.js +58 -31
- package/dist/design/kit.css +685 -0
- package/dist/design/kit.d.ts +208 -0
- package/dist/design/kit.js +177 -0
- package/dist/design/tokens.css +19 -1
- package/dist/discord-settings.js +8 -4
- package/dist/export-ui.d.ts +6 -4
- package/dist/export-ui.js +18 -8
- package/dist/flow-changed.d.ts +14 -0
- package/dist/flow-changed.js +53 -0
- package/dist/flow-gallery-ui.d.ts +41 -4
- package/dist/flow-gallery-ui.js +95 -63
- package/dist/flow-starters-ui.d.ts +3 -9
- package/dist/flow-starters-ui.js +12 -23
- package/dist/flow-unattended-words.d.ts +20 -0
- package/dist/flow-unattended-words.js +24 -0
- package/dist/flow-unattended.d.ts +9 -5
- package/dist/flow-unattended.js +45 -23
- package/dist/flows-cli.js +7 -3
- package/dist/flows-ui.d.ts +12 -6
- package/dist/flows-ui.js +141 -30
- package/dist/integrations-ui.d.ts +6 -4
- package/dist/integrations-ui.js +34 -40
- package/dist/integrations.d.ts +2 -2
- package/dist/integrations.js +4 -4
- package/dist/kits-ui.d.ts +8 -10
- package/dist/kits-ui.js +29 -33
- package/dist/kits.js +1 -1
- package/dist/knowledge-ui.js +76 -19
- package/dist/ledger-view.d.ts +2 -0
- package/dist/ledger-view.js +41 -38
- package/dist/models-ui.d.ts +8 -3
- package/dist/models-ui.js +82 -59
- package/dist/monitoring-ui.d.ts +4 -2
- package/dist/monitoring-ui.js +44 -13
- package/dist/needs-you.d.ts +1 -1
- package/dist/needs-you.js +8 -0
- package/dist/operate-remote-arguments.js +1 -0
- package/dist/operate-remote.js +1 -1
- package/dist/operate.d.ts +1 -1
- package/dist/operate.js +38 -4
- package/dist/passwordless.d.ts +1 -1
- package/dist/passwordless.js +1 -1
- package/dist/people-audit-ui.d.ts +2 -1
- package/dist/people-audit-ui.js +25 -23
- package/dist/policy-ui.d.ts +3 -2
- package/dist/policy-ui.js +27 -21
- package/dist/policy.d.ts +3 -0
- package/dist/policy.js +14 -1
- package/dist/project-delete-ui.d.ts +4 -2
- package/dist/project-delete-ui.js +135 -126
- package/dist/project-knowledge.d.ts +10 -10
- package/dist/publish.d.ts +11 -0
- package/dist/publish.js +16 -3
- package/dist/pull-request-ui.d.ts +2 -1
- package/dist/pull-request-ui.js +39 -28
- package/dist/recipe-ui.d.ts +4 -2
- package/dist/recipe-ui.js +14 -4
- package/dist/release-adapters.d.ts +3 -0
- package/dist/release-adapters.js +22 -2
- package/dist/release-cli.js +81 -9
- package/dist/release.d.ts +21 -0
- package/dist/release.js +40 -4
- package/dist/releases.js +1 -1
- package/dist/request-budget-ui.d.ts +4 -2
- package/dist/request-budget-ui.js +30 -19
- package/dist/retention-ui.d.ts +4 -3
- package/dist/retention-ui.js +15 -12
- package/dist/scope.d.ts +3 -0
- package/dist/scope.js +6 -0
- package/dist/serve.d.ts +0 -1
- package/dist/serve.js +51 -7
- package/dist/server/agents-computers.d.ts +9 -0
- package/dist/server/agents-computers.js +67 -0
- package/dist/server/chat.js +16 -16
- package/dist/server/chrome.d.ts +1 -1
- package/dist/server/chrome.js +19 -9
- package/dist/server/console.css +2 -2
- package/dist/server/flows.js +64 -36
- package/dist/server/home.d.ts +35 -0
- package/dist/server/home.js +104 -0
- package/dist/server/http.d.ts +3 -0
- package/dist/server/http.js +5 -0
- package/dist/server/kit-standalone.d.ts +22 -0
- package/dist/server/kit-standalone.js +69 -0
- package/dist/server/kit.d.ts +405 -0
- package/dist/server/kit.js +361 -0
- package/dist/server/pages.d.ts +2 -1
- package/dist/server/pages.js +213 -174
- package/dist/server/people-tokens.js +26 -85
- package/dist/server/render-history.d.ts +51 -0
- package/dist/server/render-history.js +228 -0
- package/dist/server/render-new-task.d.ts +29 -0
- package/dist/server/render-new-task.js +96 -0
- package/dist/server/render-next.d.ts +63 -0
- package/dist/server/render-next.js +109 -0
- package/dist/server/render-pages.d.ts +22 -190
- package/dist/server/render-pages.js +123 -575
- package/dist/server/render-people.d.ts +24 -3
- package/dist/server/render-people.js +93 -72
- package/dist/server/render-projects.d.ts +103 -0
- package/dist/server/render-projects.js +247 -0
- package/dist/server/render-results.d.ts +5 -24
- package/dist/server/render-results.js +48 -185
- package/dist/server/render-settings.d.ts +5 -74
- package/dist/server/render-settings.js +102 -493
- package/dist/server/render-tasks.d.ts +35 -5
- package/dist/server/render-tasks.js +114 -85
- package/dist/server/route-table.js +20 -5
- package/dist/server/settings.js +276 -255
- package/dist/server/task-views.d.ts +8 -0
- package/dist/server/task-views.js +85 -2
- package/dist/server/tasks.d.ts +8 -0
- package/dist/server/tasks.js +95 -40
- package/dist/server/terminals.js +1 -1
- package/dist/server/welcome.d.ts +22 -0
- package/dist/server/welcome.js +69 -0
- package/dist/settings-groups-ui.d.ts +279 -0
- package/dist/settings-groups-ui.js +615 -0
- package/dist/settings-kit.d.ts +42 -6
- package/dist/settings-kit.js +69 -7
- package/dist/setup-guide.d.ts +2 -2
- package/dist/skills-ui.js +59 -7
- package/dist/slack-settings.js +12 -5
- package/dist/spend-ui.d.ts +45 -26
- package/dist/spend-ui.js +124 -56
- package/dist/sso-ui.d.ts +5 -2
- package/dist/sso-ui.js +63 -31
- package/dist/storage-ui.d.ts +3 -2
- package/dist/storage-ui.js +56 -50
- package/dist/store.d.ts +112 -3
- package/dist/store.js +292 -20
- package/dist/surface.js +5 -1
- package/dist/teams-settings.d.ts +2 -1
- package/dist/teams-settings.js +24 -41
- package/dist/telegram-settings.d.ts +0 -4
- package/dist/telegram-settings.js +52 -25
- package/dist/toolroll-update-ui.d.ts +8 -4
- package/dist/toolroll-update-ui.js +55 -45
- package/dist/tools-ui.d.ts +5 -3
- package/dist/tools-ui.js +107 -42
- package/dist/work-index.js +11 -7
- package/dist/workspace-ui.d.ts +1 -1
- package/dist/workspace-ui.js +7 -4
- package/package.json +1 -1
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* New task (Wave E, src/design/KIT.md): one describe box, the project it lands in and "Read code before planning" in
|
|
3
|
+
* the box's bottom row, the round send; every other choice behind More options; ideas under it. The form posts the
|
|
4
|
+
* same fields /tasks/add has always read (title, repo, plan-first with planning-policy=choice, quality-mode, after,
|
|
5
|
+
* goal, acceptance, not, touches, scout, id, permission-mode, projectRevision), so every server check is unchanged.
|
|
6
|
+
*/
|
|
7
|
+
import { html, joinHtml, postForm } from "../html.js";
|
|
8
|
+
import { projectName } from "../project.js";
|
|
9
|
+
import { screen } from "./chrome.js";
|
|
10
|
+
import { btn, composer, disclose, doc, docHeader, dock, field, ideas, input, note, phoneHeader, radios, select, sendButton, switchInput, textarea } from "./kit.js";
|
|
11
|
+
const FORM_ID = "nt-form";
|
|
12
|
+
const PLAN_ON = "The agent reads the code and asks you anything unclear, then writes the plan.";
|
|
13
|
+
const PLAN_OFF = "The agent writes the plan straight from your words. Faster for small, obvious changes.";
|
|
14
|
+
/** The projects as the select offers them: names, with the parent folder when two share a name; the open one first
|
|
15
|
+
* picked. Values are the paths the server proves. */
|
|
16
|
+
function projectItems(projects, open) {
|
|
17
|
+
const all = open !== null && !projects.some(one => one.path === open) ? [{ path: open, name: projectName(open) }, ...projects] : [...projects];
|
|
18
|
+
const counts = new Map();
|
|
19
|
+
for (const one of all)
|
|
20
|
+
counts.set(one.name, (counts.get(one.name) ?? 0) + 1);
|
|
21
|
+
return all.map(one => {
|
|
22
|
+
if ((counts.get(one.name) ?? 0) < 2)
|
|
23
|
+
return [one.path, one.name];
|
|
24
|
+
const parent = one.path.split(/[\\/]/).filter(Boolean).slice(-2, -1)[0] ?? one.path;
|
|
25
|
+
return [one.path, `${one.name} (${parent})`];
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
export function newTaskPage(chrome, project,
|
|
29
|
+
/** undefined: keep the revision the refused form posted (its draft), so a project switch since still refuses. */
|
|
30
|
+
projectRevision, problem, candidates = [], permissionDefault = "auto", qualityDefault = "default", projects = [],
|
|
31
|
+
/** A shipped template's fields, or a refused filing's own words, back in the form. */
|
|
32
|
+
prefill = null,
|
|
33
|
+
/** Words an idea's link put in the box (GET ?text=). */
|
|
34
|
+
draft = null,
|
|
35
|
+
/** Ideas for the chosen project: one tap drafts the words into the box. */
|
|
36
|
+
suggestions = []) {
|
|
37
|
+
const head = html `${phoneHeader("New task", { back: "/work" })}`;
|
|
38
|
+
const header = docHeader({ title: "New task", back: ["/work", "Tasks"], meta: "Nothing is built until you approve its plan.", same: true });
|
|
39
|
+
const items = projectItems(projects, project);
|
|
40
|
+
const values = prefill?.values;
|
|
41
|
+
// No project to choose from: the task names its folder (the server proves it), and adding a project is one link away.
|
|
42
|
+
const typed = items.length === 0;
|
|
43
|
+
const chosen = values?.get("repo") ?? project ?? items[0]?.[0] ?? "";
|
|
44
|
+
const planOn = values === undefined || values.get("plan-first") === "1";
|
|
45
|
+
const quality = values?.get("quality-mode") ?? qualityDefault;
|
|
46
|
+
const after = values?.get("after") ?? "";
|
|
47
|
+
const permission = values?.get("permission-mode") === "bypassPermissions" ? "bypassPermissions" : values?.get("permission-mode") === "auto" ? "auto" : permissionDefault;
|
|
48
|
+
const text = prefill?.title ?? draft ?? "";
|
|
49
|
+
// More options opens on a template, or when a refused filing had used any of them.
|
|
50
|
+
const optionsUsed = prefill !== null && (values === undefined || ["goal", "acceptance", "not", "touches", "id", "after"].some(name => (values.get(name) ?? "") !== "") || values.get("scout") === "1");
|
|
51
|
+
const folder = !typed ? "" : field({ id: "nt-repo", label: "Project folder", hint: "No projects yet: name the folder of a git repository.",
|
|
52
|
+
control: input({ id: "nt-repo", name: "repo", value: chosen, hint: true, attrs: { required: true, placeholder: "/path/to/repository", autocapitalize: "none", spellcheck: "false" } }) });
|
|
53
|
+
const bar = joinHtml([
|
|
54
|
+
typed ? "" : select({ id: "nt-project", name: "repo", label: "Project", items, current: chosen, sm: true }),
|
|
55
|
+
switchInput({ id: "nt-plan", name: "plan-first", label: "Read code before planning", on: planOn, attrs: { value: "1" } }),
|
|
56
|
+
]);
|
|
57
|
+
const box = composer({
|
|
58
|
+
id: "nt", name: "title", label: "What should change?", placeholder: "What should change? One or two sentences is enough.",
|
|
59
|
+
value: text, rows: 4, maxRows: 10, before: bar, error: problem,
|
|
60
|
+
send: sendButton({ label: "File task" }),
|
|
61
|
+
note: html `<span class="k-on-note">${PLAN_ON}</span><span class="k-off-note">${PLAN_OFF}</span>`,
|
|
62
|
+
textAttrs: { maxlength: 200, autofocus: true },
|
|
63
|
+
});
|
|
64
|
+
const options = html `<div class="k-form">\
|
|
65
|
+
${field({ id: "nt-quality", label: "Checks", control: select({ id: "nt-quality", name: "quality-mode", items: [["default", "Default checks"], ["strict", "Strict (release)"]], current: quality, wide: true }) })}\
|
|
66
|
+
${candidates.length === 0 ? "" : field({ id: "nt-after", label: "Starts", control: select({ id: "nt-after", name: "after", wide: true, current: after,
|
|
67
|
+
items: [["", "Right away"], ...candidates.map(one => [one.id, `After ${one.id}: ${one.title}`])] }) })}\
|
|
68
|
+
${field({ id: "nt-goal", label: "Goal (optional)", hint: "With acceptance below, automatic approval can sign an unchanged plan.", control: textarea({ id: "nt-goal", name: "goal", rows: 2, value: prefill?.goal ?? "", attrs: { placeholder: "What success looks like" } }) })}\
|
|
69
|
+
${field({ id: "nt-acceptance", label: "Acceptance", hint: "Needed with a goal. One per line: statement | evidence,kinds | how", control: textarea({ id: "nt-acceptance", name: "acceptance", rows: 2, value: prefill?.acceptance ?? "", attrs: { placeholder: "Requests over the limit return 429 | check" } }) })}\
|
|
70
|
+
${field({ id: "nt-not", label: "Not this (optional)", control: input({ id: "nt-not", name: "not", value: prefill?.not ?? "" }) })}\
|
|
71
|
+
${field({ id: "nt-touches", label: "Likely touches (optional)", hint: "Paths, separated by commas.", control: input({ id: "nt-touches", name: "touches", value: prefill?.touches ?? "", hint: true }) })}\
|
|
72
|
+
<div class="k-checks"><label><input type="checkbox" name="scout" value="1"${values?.get("scout") === "1" ? html ` checked` : ""}>Research only: a read-only report, no code changes</label></div>\
|
|
73
|
+
${field({ id: "nt-id", label: "Task id (optional)", control: input({ id: "nt-id", name: "id", value: values?.get("id") ?? "", attrs: { placeholder: "Made from the request" } }) })}\
|
|
74
|
+
<div class="k-field"><span class="k-label">Agent permissions</span>${radios({ name: "permission-mode", legend: "Agent permissions", current: permission, items: [
|
|
75
|
+
{ value: "auto", label: "Auto", text: "Asks before risky actions." },
|
|
76
|
+
{ value: "bypassPermissions", label: "Full access", text: "Never asks and can change files anywhere on this computer. Trusted repositories only." },
|
|
77
|
+
] })}<p class="k-hint">From Settings. You can still change it on the plan before you approve.</p></div></div>`;
|
|
78
|
+
const form = postForm("/tasks/add", joinHtml([
|
|
79
|
+
prefill !== null && values === undefined ? note("Filled in from a template. Change anything; it still waits for your approval.") : "",
|
|
80
|
+
folder,
|
|
81
|
+
typed ? note(html `<a class="k-link" href="/projects?return=%2Ftasks%2Fnew">Add a project</a> to choose it here next time.`) : "",
|
|
82
|
+
box,
|
|
83
|
+
disclose("More options", options, optionsUsed),
|
|
84
|
+
]), {
|
|
85
|
+
attrs: { class: "k-form k-dock-send", id: FORM_ID },
|
|
86
|
+
projectRevision: projectRevision === undefined && values?.get("projectRevision") == null ? null : String(projectRevision ?? values?.get("projectRevision") ?? ""),
|
|
87
|
+
hidden: { "planning-policy": "choice" },
|
|
88
|
+
});
|
|
89
|
+
const chosenName = items.find(([path]) => path === chosen)?.[1] ?? (chosen === "" ? "your project" : projectName(chosen));
|
|
90
|
+
const ideaList = ideas({
|
|
91
|
+
label: `Ideas for ${chosenName}`, into: "nt",
|
|
92
|
+
items: suggestions.map(one => ({ label: one.label, draft: one.draft, href: `/tasks/new?text=${encodeURIComponent(one.draft)}` })),
|
|
93
|
+
});
|
|
94
|
+
const recipes = note(html `Work you do often? <a class="k-link" href="/recipes">Use a saved recipe</a>.`);
|
|
95
|
+
return screen("New task", html `${head}${doc(header, form, ideaList, recipes)}${dock(btn("File task", { variant: "primary", type: "submit", attrs: { form: FORM_ID } }))}`, { chrome });
|
|
96
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /next (Wave E, src/design/KIT.md): everything waiting on a person, one at a time, each in the approval slots with
|
|
3
|
+
* its one act, every act landing back here. The forms post exactly what they always have (the approve form's nonce,
|
|
4
|
+
* digest and password; a decision's choice, confirm and note; a requeue), so every server check is unchanged. The
|
|
5
|
+
* approval shows the password only when the rules ask (approveForm, standard): none with Passwordless or a remembered
|
|
6
|
+
* password the task page's own preview says may approve these exact terms; otherwise the password, required.
|
|
7
|
+
*/
|
|
8
|
+
import { type TaskOrigin } from "../task-source.js";
|
|
9
|
+
import type { Gap } from "../gaps.js";
|
|
10
|
+
import type { Scope } from "../scope.js";
|
|
11
|
+
import type { Decision } from "../store.js";
|
|
12
|
+
import { type Chrome, type Screen } from "./chrome.js";
|
|
13
|
+
import { type PlanContractView, type RouteView } from "./render-tasks.js";
|
|
14
|
+
export type NextItem = {
|
|
15
|
+
key: string;
|
|
16
|
+
kind: "decision";
|
|
17
|
+
decision: Decision & {
|
|
18
|
+
taskId: string;
|
|
19
|
+
};
|
|
20
|
+
} | {
|
|
21
|
+
key: string;
|
|
22
|
+
kind: "approval";
|
|
23
|
+
approval: {
|
|
24
|
+
taskId: string;
|
|
25
|
+
title: string;
|
|
26
|
+
goal: string;
|
|
27
|
+
digest: string;
|
|
28
|
+
proposedAt: string;
|
|
29
|
+
};
|
|
30
|
+
} | {
|
|
31
|
+
key: string;
|
|
32
|
+
kind: "requeue";
|
|
33
|
+
stalled: {
|
|
34
|
+
taskId: string;
|
|
35
|
+
title: string;
|
|
36
|
+
strikes: number;
|
|
37
|
+
incidentCount: number;
|
|
38
|
+
};
|
|
39
|
+
} | {
|
|
40
|
+
key: string;
|
|
41
|
+
kind: "gap";
|
|
42
|
+
gap: Gap;
|
|
43
|
+
} | null;
|
|
44
|
+
export declare function nextPage(chrome: Chrome, data: {
|
|
45
|
+
item: NextItem;
|
|
46
|
+
scope: Scope | null;
|
|
47
|
+
planDocument: string | null;
|
|
48
|
+
planContract?: PlanContractView | null;
|
|
49
|
+
approvalDigest: string | null;
|
|
50
|
+
/** v34: said inside the ceremony when the yes buys a report, not a branch. */
|
|
51
|
+
deliverable?: "branch" | "report";
|
|
52
|
+
/** v48: the agents the yes freezes — the one block every ceremony shows. */
|
|
53
|
+
route: RouteView | null;
|
|
54
|
+
/** v126: where the work came from (an outside event's line of context, never a term agreed to). */
|
|
55
|
+
origin?: TaskOrigin | null;
|
|
56
|
+
/** A remembered password stands in for these exact terms (the task page's preview): one click, no field. */
|
|
57
|
+
remembered?: boolean;
|
|
58
|
+
csrf: string;
|
|
59
|
+
nonce: string;
|
|
60
|
+
remaining: number;
|
|
61
|
+
skipped: string[];
|
|
62
|
+
now: Date;
|
|
63
|
+
}): Screen;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /next (Wave E, src/design/KIT.md): everything waiting on a person, one at a time, each in the approval slots with
|
|
3
|
+
* its one act, every act landing back here. The forms post exactly what they always have (the approve form's nonce,
|
|
4
|
+
* digest and password; a decision's choice, confirm and note; a requeue), so every server check is unchanged. The
|
|
5
|
+
* approval shows the password only when the rules ask (approveForm, standard): none with Passwordless or a remembered
|
|
6
|
+
* password the task page's own preview says may approve these exact terms; otherwise the password, required.
|
|
7
|
+
*/
|
|
8
|
+
import { originNote } from "../task-source.js";
|
|
9
|
+
import { html, joinHtml, postForm } from "../html.js";
|
|
10
|
+
import { screen, when } from "./chrome.js";
|
|
11
|
+
import { taskHref } from "./http.js";
|
|
12
|
+
import { acceptanceCeremonyHtml, agentsCeremonyHtml, consentClosedHtml, consentDoorOf, executionPlanHtml, planContractHtml, profileWords, runtimeDetailsHtml, } from "./render-tasks.js";
|
|
13
|
+
import { approvalFactsNow, approvalTerms, approveForm, btn, chip, disclose, doc, docHeader, empty, facts, note, phoneHeader, slots } from "./kit.js";
|
|
14
|
+
/** One decision option: a one-click answer (an irreversible one behind its own disclosure, with the confirm the server
|
|
15
|
+
* requires) and its consequence. A note travels from the task's own page. */
|
|
16
|
+
function optionForm(decision, option) {
|
|
17
|
+
const recommended = option.id === decision.recommendation;
|
|
18
|
+
const form = postForm(`/d/${decision.id}/answer`, html `<div class="k-acts">${btn(option.label, { variant: option.reversible ? (recommended ? "primary" : "outline") : "danger", type: "submit" })}</div>\
|
|
19
|
+
<p class="k-note">${recommended ? html `${chip("Recommended", "", { dot: false })} ` : ""}${option.consequence}</p>`, { attrs: { class: "k-form" }, returnTo: "next", hidden: { choice: option.id, confirm: option.reversible ? null : "yes" } });
|
|
20
|
+
return option.reversible ? form : disclose(`${option.label} (can't be undone)`, form);
|
|
21
|
+
}
|
|
22
|
+
export function nextPage(chrome, data) {
|
|
23
|
+
const { item } = data;
|
|
24
|
+
const title = "Needs you";
|
|
25
|
+
if (item === null) {
|
|
26
|
+
const held = data.skipped.length;
|
|
27
|
+
return screen(title, html `${phoneHeader(title)}${doc(docHeader({ title, same: true }), held > 0
|
|
28
|
+
? empty({ title: `Only the ${held} you set aside ${held === 1 ? "is" : "are"} left.`, text: "Look at them again now, or come back later.", action: btn("Look at them again", { sm: true, href: "/next" }) })
|
|
29
|
+
: empty({ title: "Nothing needs you.", text: "Everything is either working or waiting on its own clock.", action: btn("Open Tasks", { sm: true, href: "/work" }) }))}`, { chrome });
|
|
30
|
+
}
|
|
31
|
+
const skipHref = `/next?skip=${encodeURIComponent([...data.skipped, item.key].join(","))}`;
|
|
32
|
+
const header = docHeader({
|
|
33
|
+
title, same: true,
|
|
34
|
+
meta: data.remaining === 1 ? "The last thing waiting on you" : `1 of ${data.remaining} waiting on you`,
|
|
35
|
+
right: btn("Not now", { variant: "ghost", sm: true, href: skipHref }),
|
|
36
|
+
});
|
|
37
|
+
const openTask = (taskId, words = "Open the task") => html `<a class="k-link" href="${taskHref(taskId)}">${words}</a>`;
|
|
38
|
+
let card;
|
|
39
|
+
if (item.kind === "decision") {
|
|
40
|
+
const { decision } = item;
|
|
41
|
+
card = slots({
|
|
42
|
+
wait: true, attrs: { "data-decision": String(decision.id) },
|
|
43
|
+
status: html `${chip("Question", "ask")}<h2>${decision.question}</h2>`,
|
|
44
|
+
text: decision.recap,
|
|
45
|
+
acts: joinHtml(decision.options.map(option => optionForm(decision, option))),
|
|
46
|
+
terms: html `${decision.taskId} · asked ${when(decision.createdAt)} · ${openTask(decision.taskId, "Answer with a note on the task")}`,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
else if (item.kind === "approval" && !consentDoorOf(data.scope, data.route).open) {
|
|
50
|
+
const door = consentDoorOf(data.scope, data.route);
|
|
51
|
+
card = slots({
|
|
52
|
+
wait: true,
|
|
53
|
+
status: html `${chip("Plan", "ask")}<h2>${item.approval.title}</h2>`,
|
|
54
|
+
text: item.approval.taskId,
|
|
55
|
+
acts: consentClosedHtml(item.approval.taskId, door, "next"),
|
|
56
|
+
terms: openTask(item.approval.taskId),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
else if (item.kind === "approval") {
|
|
60
|
+
const scope = data.scope;
|
|
61
|
+
const facts_ = approvalFactsNow({ remembered: data.remembered === true });
|
|
62
|
+
const terms = joinHtml([
|
|
63
|
+
data.deliverable === "report" ? note("Research only: a read-only session investigates this goal and delivers a report. No branch; nothing in the repository changes.") : "",
|
|
64
|
+
scope === null || scope.profileState !== "unresolved" ? "" : profileWords(scope),
|
|
65
|
+
originNote(data.origin) === null ? "" : html `<p class="k-note" data-approval-origin>${originNote(data.origin)}</p>`,
|
|
66
|
+
facts([
|
|
67
|
+
["Goal", scope?.goal ?? item.approval.goal],
|
|
68
|
+
["Not this", scope?.outOfScope == null ? "No exclusions" : scope.outOfScope],
|
|
69
|
+
["Touches", scope === null || scope.touches.length === 0 ? "Anything" : scope.touches.join(", ")],
|
|
70
|
+
]),
|
|
71
|
+
scope === null ? "" : acceptanceCeremonyHtml(scope.acceptance),
|
|
72
|
+
// The agents the yes freezes (v48): the same block the task page and chat sign under, before the password.
|
|
73
|
+
agentsCeremonyHtml(data.route),
|
|
74
|
+
scope === null ? "" : runtimeDetailsHtml(scope),
|
|
75
|
+
]);
|
|
76
|
+
const plan = data.planDocument === null ? "" : html `<section class="k-sec"><h3>The plan</h3>${executionPlanHtml(data.planDocument, true)}${planContractHtml(data.planContract ?? null, "ceremony")}</section>`;
|
|
77
|
+
const digest = data.approvalDigest ?? item.approval.digest;
|
|
78
|
+
// The terms ride inside the form, before the password: what the yes covers is on the form that says it.
|
|
79
|
+
card = html `${plan}${slots({
|
|
80
|
+
wait: true, attrs: { "data-approval": digest },
|
|
81
|
+
status: html `${chip("Plan", "ask")}<h2>${item.approval.title}</h2>`,
|
|
82
|
+
text: html `${item.approval.taskId} · ${openTask(item.approval.taskId, "Open the task to change the scope first")}`,
|
|
83
|
+
acts: approveForm({ action: `${taskHref(item.approval.taskId)}/approve`, kind: "standard", facts: facts_, label: "Approve and start", primary: true,
|
|
84
|
+
before: html `<section class="k-sec"><h3>Approve exactly this</h3>${terms}</section>`,
|
|
85
|
+
form: { returnTo: "next", hidden: { nonce: data.nonce, digest }, attrs: { class: "k-form" } } }),
|
|
86
|
+
terms: approvalTerms("standard", facts_),
|
|
87
|
+
})}`;
|
|
88
|
+
}
|
|
89
|
+
else if (item.kind === "requeue") {
|
|
90
|
+
card = slots({
|
|
91
|
+
wait: true,
|
|
92
|
+
status: html `${chip("Stopped", "ask")}<h2>${item.stalled.title}</h2>`,
|
|
93
|
+
text: `Stopped with ${item.stalled.incidentCount} incident${item.stalled.incidentCount === 1 ? "" : "s"}${item.stalled.strikes > 0 ? ` after ${item.stalled.strikes} attempt${item.stalled.strikes === 1 ? "" : "s"}` : ""}. Retrying resolves the incidents, clears the failed attempts and puts it back in line.`,
|
|
94
|
+
acts: postForm(`${taskHref(item.stalled.taskId)}/requeue`, btn("Retry this work", { variant: "primary", type: "submit" }), { returnTo: "next" }),
|
|
95
|
+
terms: html `${item.stalled.taskId} · ${openTask(item.stalled.taskId, "Read the attempts first")}`,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
const { gap } = item;
|
|
100
|
+
card = slots({
|
|
101
|
+
wait: true,
|
|
102
|
+
status: html `${chip("Missing", "ask")}<h2>Supply ${gap.key}</h2>`,
|
|
103
|
+
text: html `${gap.state}. Filling it starts ${gap.unblocks.length} task${gap.unblocks.length === 1 ? "" : "s"}: ${joinHtml(gap.unblocks.map(one => html `<span class="k-mono">${one}</span>`), ", ")}.`,
|
|
104
|
+
acts: "",
|
|
105
|
+
terms: html `Prove it is filled from a terminal: <code class="k-mono">${gap.verify}</code>`,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return screen(title, html `${phoneHeader(title)}${doc(header, card)}`, { chrome });
|
|
109
|
+
}
|
|
@@ -1,26 +1,21 @@
|
|
|
1
1
|
/** Everyday console pages: home, inbox, board, work, projects, fleet and the queue. */
|
|
2
2
|
import { type TidyItem } from "../tidy.js";
|
|
3
|
-
import { type TaskOrigin } from "../task-source.js";
|
|
4
3
|
import { type Html } from "../html.js";
|
|
5
4
|
import { type AssignmentSnapshot } from "../assignment.js";
|
|
6
5
|
import { type BoardCard } from "../board.js";
|
|
7
|
-
import { type BrowserLimits
|
|
8
|
-
import { type CheckLevel } from "../check-levels.js";
|
|
6
|
+
import { type BrowserLimits } from "../browser-workspace.js";
|
|
9
7
|
import { type DispatchDiagnosis } from "../dispatch.js";
|
|
10
8
|
import { type FirstRunStep } from "../first-run.js";
|
|
11
9
|
import { type Gap } from "../gaps.js";
|
|
12
|
-
import { type ListOutcome } from "../onboard.js";
|
|
13
10
|
import { type parseBaseTreeSnapshot } from "../peek.js";
|
|
14
11
|
import { type CriterionMatrixRow } from "../proof.js";
|
|
15
|
-
import { type QualityMode } from "../quality.js";
|
|
16
12
|
import { type Runner } from "../runner.js";
|
|
17
|
-
import { type Scope, type UnattendedPermissionMode } from "../scope.js";
|
|
18
13
|
import { type Decision, type Incident, type RepairChainRow, type Run, type Store, type TaskState, type WorktreeRow } from "../store.js";
|
|
19
14
|
import { tally } from "../summary.js";
|
|
20
|
-
import { type WorkIndexGroup, type WorkIndexPage } from "../work-index.js";
|
|
15
|
+
import { type WorkIndexGroup, type WorkIndexItem, type WorkIndexPage } from "../work-index.js";
|
|
16
|
+
import { type ChipTone } from "../design/kit.js";
|
|
21
17
|
import { type WorkFacts, type WorkStatus, type WorkView } from "../workspace-ui.js";
|
|
22
18
|
import { type Chrome, type Screen } from "./chrome.js";
|
|
23
|
-
import { type PlanContractView, type RouteView } from "./render-tasks.js";
|
|
24
19
|
export declare const TASK_STATES: readonly TaskState[];
|
|
25
20
|
/**
|
|
26
21
|
* The inbox (v3): only things stalling progress without a person, one card
|
|
@@ -112,9 +107,8 @@ export declare function inboxPage(chrome: Chrome, data: {
|
|
|
112
107
|
}[];
|
|
113
108
|
unread?: readonly InboxTab[];
|
|
114
109
|
}): Screen;
|
|
115
|
-
/** System: the machinery — workers, background service, workspaces. */
|
|
116
110
|
/** Start a builder: the one command, as text to select (/start-builder, where every "Connect a builder" leads, and
|
|
117
|
-
*
|
|
111
|
+
* the Agents page). `project`: the open project's name, when the viewer may see it; `connected`: whether a builder
|
|
118
112
|
* that may build there is checking in (null: say nothing); `managed`: whether this person can start one themselves
|
|
119
113
|
* (registering a builder takes an instance operator), else who to ask. */
|
|
120
114
|
export declare function startBuilderHtml(data: {
|
|
@@ -123,40 +117,6 @@ export declare function startBuilderHtml(data: {
|
|
|
123
117
|
connected: boolean | null;
|
|
124
118
|
managed: boolean;
|
|
125
119
|
}): Html;
|
|
126
|
-
export declare function systemPage(chrome: Chrome, data: {
|
|
127
|
-
agents: {
|
|
128
|
-
phase: "plan" | "build" | "repair" | "review";
|
|
129
|
-
provider?: string;
|
|
130
|
-
model?: string | null;
|
|
131
|
-
source?: "pinned" | "flag" | "project" | "installation" | "default";
|
|
132
|
-
problem?: string;
|
|
133
|
-
setBy: string | null;
|
|
134
|
-
}[];
|
|
135
|
-
building: {
|
|
136
|
-
taskId: string;
|
|
137
|
-
runner: string;
|
|
138
|
-
claimedAt: string;
|
|
139
|
-
expiresAt: string;
|
|
140
|
-
model: string | null;
|
|
141
|
-
}[];
|
|
142
|
-
runners: Runner[];
|
|
143
|
-
worktrees: WorktreeRow[];
|
|
144
|
-
episode: {
|
|
145
|
-
id: number;
|
|
146
|
-
startedAt: string;
|
|
147
|
-
endedAt: string | null;
|
|
148
|
-
ticks: number;
|
|
149
|
-
built: number;
|
|
150
|
-
broke: number;
|
|
151
|
-
} | null;
|
|
152
|
-
outboxPending: number;
|
|
153
|
-
externalWork?: {
|
|
154
|
-
remoteRepo: string;
|
|
155
|
-
blocked: string | null;
|
|
156
|
-
openEpisode: string | null;
|
|
157
|
-
}[];
|
|
158
|
-
now: Date;
|
|
159
|
-
}): Screen;
|
|
160
120
|
/**
|
|
161
121
|
* A build's stage as one of three steps — set up, writing, checking and
|
|
162
122
|
* committing — read from the machine's own phase, never a percent. The
|
|
@@ -203,8 +163,6 @@ export declare function boardBody(data: {
|
|
|
203
163
|
questions: number;
|
|
204
164
|
} | null;
|
|
205
165
|
}, ciRed: (pr: number) => boolean): Html;
|
|
206
|
-
/** Completed work: one row per done task, its final run and PR attached. */
|
|
207
|
-
export declare function donePage(chrome: Chrome, rows: ReturnType<Store["listCompletedWorkScoped"]>, ciRed: (pr: number) => boolean): Screen;
|
|
208
166
|
export declare function homePage(chrome: Chrome, data: {
|
|
209
167
|
csrf: string;
|
|
210
168
|
taskCount: number;
|
|
@@ -244,40 +202,6 @@ export declare function homePage(chrome: Chrome, data: {
|
|
|
244
202
|
settings: boolean;
|
|
245
203
|
now: Date;
|
|
246
204
|
}): Screen;
|
|
247
|
-
export type TaskComposerPrefill = {
|
|
248
|
-
title: string;
|
|
249
|
-
goal: string;
|
|
250
|
-
not: string;
|
|
251
|
-
touches: string;
|
|
252
|
-
acceptance: string;
|
|
253
|
-
values?: URLSearchParams;
|
|
254
|
-
};
|
|
255
|
-
/** The one front door for new work. The common path is one prompt and one
|
|
256
|
-
* button; the detailed contract remains available in-place for templates,
|
|
257
|
-
* experts, and the rare task that should skip repository-aware planning. */
|
|
258
|
-
/** Where new work goes when no project is open: a choice from the projects
|
|
259
|
-
* this person already has, never a typed path; a new project is one link
|
|
260
|
-
* away. Same-named checkouts show their parent folder to tell them apart. */
|
|
261
|
-
export declare function projectPickerHtml(projects: {
|
|
262
|
-
path: string;
|
|
263
|
-
name: string;
|
|
264
|
-
}[], chosen: string): Html;
|
|
265
|
-
export declare function taskComposerHtml(data: {
|
|
266
|
-
project: string | null;
|
|
267
|
-
projectRevision?: number;
|
|
268
|
-
prefill?: TaskComposerPrefill | null;
|
|
269
|
-
candidates?: {
|
|
270
|
-
id: string;
|
|
271
|
-
title: string;
|
|
272
|
-
}[];
|
|
273
|
-
permissionDefault: UnattendedPermissionMode;
|
|
274
|
-
qualityDefault: QualityMode;
|
|
275
|
-
/** Projects this person may place work in, most recently opened first. */
|
|
276
|
-
projects?: {
|
|
277
|
-
path: string;
|
|
278
|
-
name: string;
|
|
279
|
-
}[];
|
|
280
|
-
}): Html;
|
|
281
205
|
export type WorkRow = WorkFacts & {
|
|
282
206
|
assignment?: AssignmentSnapshot | null;
|
|
283
207
|
assignmentProblem?: boolean;
|
|
@@ -287,6 +211,19 @@ export type WorkRow = WorkFacts & {
|
|
|
287
211
|
resultRunId: number | null;
|
|
288
212
|
};
|
|
289
213
|
export declare const TASK_GROUP_LABEL: Readonly<Record<WorkIndexGroup, string>>;
|
|
214
|
+
/** A list row's chip (src/design/KIT.md): a waiting row names its ask ("Plan", "Result") in the ask tone, or says
|
|
215
|
+
* Needs you; every other row wears its headline in the tone of the status it belongs to. */
|
|
216
|
+
export declare function workRowChip(row: Pick<WorkIndexItem, 'ask' | 'chip' | 'status' | 'archived'>): {
|
|
217
|
+
word: string;
|
|
218
|
+
tone: ChipTone;
|
|
219
|
+
};
|
|
220
|
+
/** The board's four columns: what needs you, what will start on its own, what runs, what finished. */
|
|
221
|
+
export type WorkBoard = {
|
|
222
|
+
needs: WorkIndexPage;
|
|
223
|
+
queued: WorkIndexPage;
|
|
224
|
+
running: WorkIndexPage;
|
|
225
|
+
finished: WorkIndexPage;
|
|
226
|
+
};
|
|
290
227
|
export declare function workPage(chrome: Chrome, data: {
|
|
291
228
|
view: WorkView;
|
|
292
229
|
projectFilter?: string;
|
|
@@ -298,19 +235,11 @@ export declare function workPage(chrome: Chrome, data: {
|
|
|
298
235
|
state?: TaskState | null;
|
|
299
236
|
/** An approver in a browser session: rows offer Put away (or Restore, archived), and the page offers Put away old tasks. */
|
|
300
237
|
canPutAway?: boolean;
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
parent: string | null;
|
|
307
|
-
entries: {
|
|
308
|
-
name: string;
|
|
309
|
-
path: string;
|
|
310
|
-
git: boolean;
|
|
311
|
-
}[];
|
|
312
|
-
/** Unused: postForm adds the request's CSRF field. Kept for callers. */
|
|
313
|
-
csrf: string;
|
|
238
|
+
/** The project this list shows (null: every project in view) and whether All was asked for by name (`all=1`). */
|
|
239
|
+
lens?: string | null;
|
|
240
|
+
all?: boolean;
|
|
241
|
+
/** The Board view's columns, read from the same index as the list; absent for the List view. */
|
|
242
|
+
board?: WorkBoard | null;
|
|
314
243
|
}): Screen;
|
|
315
244
|
/** The compact completed row shared by both halves of the control room. */
|
|
316
245
|
export type WorkbenchDone = {
|
|
@@ -435,26 +364,6 @@ export type ProjectPeek = {
|
|
|
435
364
|
* GitHub identity. Everything else is null.
|
|
436
365
|
*/
|
|
437
366
|
export declare function githubIdentityOf(remoteUrl: string): string | null;
|
|
438
|
-
/** The GitHub listing page: the account's repositories with ONE honest
|
|
439
|
-
* action each. Names, descriptions, and paths are gh/filesystem DATA —
|
|
440
|
-
* escaped at the sink like everything else. */
|
|
441
|
-
export declare function githubReposPage(chrome: Chrome, data: {
|
|
442
|
-
/** The last listing; null until someone lists (a POST: listing asks GitHub, so a page visit never does). */
|
|
443
|
-
listed: ListOutcome | null;
|
|
444
|
-
local: Map<string, string>;
|
|
445
|
-
registered: Set<string>;
|
|
446
|
-
/** Unused: postForm adds the request's CSRF field. Kept for callers. */
|
|
447
|
-
csrf: string;
|
|
448
|
-
cloneReady: boolean;
|
|
449
|
-
openProject: string | null;
|
|
450
|
-
/** A projects folder didn't answer in time: local copies weren't matched this time. */
|
|
451
|
-
localUnanswered?: boolean;
|
|
452
|
-
}): Screen;
|
|
453
|
-
export declare function projectsPage(chrome: Chrome, recent: {
|
|
454
|
-
path: string;
|
|
455
|
-
name: string;
|
|
456
|
-
lastOpenedAt: string;
|
|
457
|
-
}[], candidates: string[], open: string | null, problem: string | null, _unscopedMode: boolean, browsable?: boolean, onboard?: OnboardCardState | null, peeks?: Record<string, ProjectPeek | null>, returnTo?: string, pullRequestsOn?: (path: string) => boolean, checkLevelOf?: (path: string) => CheckLevel, activity?: BrowserProjectsView["activity"]): Screen;
|
|
458
367
|
/** The no-script "move to the front" sentinel: the form cannot name the
|
|
459
368
|
* front of a partition, so the handler resolves it (slice 1b, fix 1). */
|
|
460
369
|
export declare const QUEUE_FRONT = "__TOP__";
|
|
@@ -473,18 +382,6 @@ export declare function queueBody(tasks: QueueCardTask[], workers: {
|
|
|
473
382
|
capacity: number;
|
|
474
383
|
building: number;
|
|
475
384
|
}[], revision: number, queueRevision: number): Html;
|
|
476
|
-
/**
|
|
477
|
-
* The fleet screen — one lane per runner, and the work in front of it.
|
|
478
|
-
* Building claims pin to the top of their worker's lane (live — never
|
|
479
|
-
* draggable); queued reservations sit below it (draggable to another
|
|
480
|
-
* worker, re-reserving them). Every card wears its project chip, which is
|
|
481
|
-
* the whole point: which agent is on which project is the page's answer.
|
|
482
|
-
* Form-free like the board, except the per-worker note, so the lane stack
|
|
483
|
-
* can re-render itself while somebody watches.
|
|
484
|
-
*/
|
|
485
|
-
export declare function fleetBody(queued: ReturnType<Store["fleetQueue"]>, building: ReturnType<Store["liveClaims"]>, runners: Runner[], queueRevision: number, visibleRepo: (repo: string | null) => boolean): Html;
|
|
486
|
-
/** Identical drag mechanics to the queue — the pointer events land on the worker's column. */
|
|
487
|
-
export declare function fleetScript(): string;
|
|
488
385
|
/** The /queue page's one nonce'd script: delegated pointer-event drag (it
|
|
489
386
|
* survives every fragment swap — finding 17) plus a poller that re-checks
|
|
490
387
|
* focus, dirty inputs, and an in-flight drag AT SWAP TIME, never only
|
|
@@ -492,71 +389,6 @@ export declare function fleetScript(): string;
|
|
|
492
389
|
* (finding 18); missing that, a select counts clean.
|
|
493
390
|
*/
|
|
494
391
|
export declare function queueScript(): string;
|
|
495
|
-
export declare function newTaskPage(chrome: Chrome, project: string | null,
|
|
496
|
-
/** undefined: keep the revision the refused form posted (its draft), so a project switch since still refuses. */
|
|
497
|
-
projectRevision: number | undefined, problem: string | null, candidates?: {
|
|
498
|
-
id: string;
|
|
499
|
-
title: string;
|
|
500
|
-
}[], permissionDefault?: UnattendedPermissionMode, qualityDefault?: QualityMode, projects?: {
|
|
501
|
-
path: string;
|
|
502
|
-
name: string;
|
|
503
|
-
}[],
|
|
504
|
-
/** A shipped template's fields, or a refused filing's own words, back in the form. */
|
|
505
|
-
prefill?: TaskComposerPrefill | null): Screen;
|
|
506
|
-
/**
|
|
507
|
-
* The triage flow: everything waiting on a person, one card at a time.
|
|
508
|
-
* Full context ON the card, the act inline, and every act lands back here
|
|
509
|
-
* — clearing the queue is taps, not navigation. Read state travels in the
|
|
510
|
-
* URL (the bounded skip cursor), never in the session: two tabs cannot
|
|
511
|
-
* fight, and a shared link shows the same queue.
|
|
512
|
-
*/
|
|
513
|
-
export declare function nextPage(chrome: Chrome, data: {
|
|
514
|
-
item: {
|
|
515
|
-
key: string;
|
|
516
|
-
kind: "decision";
|
|
517
|
-
decision: Decision & {
|
|
518
|
-
taskId: string;
|
|
519
|
-
};
|
|
520
|
-
} | {
|
|
521
|
-
key: string;
|
|
522
|
-
kind: "approval";
|
|
523
|
-
approval: {
|
|
524
|
-
taskId: string;
|
|
525
|
-
title: string;
|
|
526
|
-
goal: string;
|
|
527
|
-
digest: string;
|
|
528
|
-
proposedAt: string;
|
|
529
|
-
};
|
|
530
|
-
} | {
|
|
531
|
-
key: string;
|
|
532
|
-
kind: "requeue";
|
|
533
|
-
stalled: {
|
|
534
|
-
taskId: string;
|
|
535
|
-
title: string;
|
|
536
|
-
strikes: number;
|
|
537
|
-
incidentCount: number;
|
|
538
|
-
};
|
|
539
|
-
} | {
|
|
540
|
-
key: string;
|
|
541
|
-
kind: "gap";
|
|
542
|
-
gap: Gap;
|
|
543
|
-
} | null;
|
|
544
|
-
scope: Scope | null;
|
|
545
|
-
planDocument: string | null;
|
|
546
|
-
planContract?: PlanContractView | null;
|
|
547
|
-
approvalDigest: string | null;
|
|
548
|
-
/** v34: said inside the ceremony when the yes buys a report, not a branch. */
|
|
549
|
-
deliverable?: "branch" | "report";
|
|
550
|
-
/** v48: the agents the yes freezes — the one block every ceremony shows. */
|
|
551
|
-
route: RouteView | null;
|
|
552
|
-
/** v126: where the work came from (an outside event's line of context, never a term agreed to). */
|
|
553
|
-
origin?: TaskOrigin | null;
|
|
554
|
-
csrf: string;
|
|
555
|
-
nonce: string;
|
|
556
|
-
remaining: number;
|
|
557
|
-
skipped: string[];
|
|
558
|
-
now: Date;
|
|
559
|
-
}): Screen;
|
|
560
392
|
export type PeekAdmission = {
|
|
561
393
|
run: Run;
|
|
562
394
|
/** The run's checkout, PROVEN non-null by the guards: a reviewer run
|