toolroll 0.9.6 → 0.9.8
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 +64 -1
- package/dist/actor.d.ts +21 -0
- package/dist/actor.js +48 -0
- package/dist/assignment-adapters.d.ts +2 -0
- package/dist/assignment-adapters.js +2 -0
- package/dist/assignment-ui.js +3 -1
- package/dist/assignment.d.ts +15 -0
- package/dist/assignment.js +80 -16
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +28 -24
- package/dist/browser-workspace.d.ts +117 -2
- package/dist/build-review.d.ts +38 -0
- package/dist/build-review.js +341 -0
- package/dist/builder.d.ts +1 -0
- package/dist/builder.js +33 -13
- package/dist/chat-actions.js +2 -1
- package/dist/chat-delivery.js +2 -0
- package/dist/chat-quiet.js +11 -1
- package/dist/chat-task-actions.d.ts +13 -0
- package/dist/chat-task-actions.js +19 -1
- package/dist/check-levels-ui.d.ts +26 -0
- package/dist/check-levels-ui.js +84 -0
- package/dist/check-levels.d.ts +67 -0
- package/dist/check-levels.js +171 -0
- package/dist/claim.d.ts +22 -0
- package/dist/claim.js +43 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +6 -0
- package/dist/demo-chat.js +4 -1
- package/dist/dispatch.js +9 -3
- package/dist/dispose.d.ts +12 -3
- package/dist/dispose.js +19 -5
- package/dist/exec.d.ts +5 -0
- package/dist/exec.js +30 -4
- package/dist/export.js +3 -2
- package/dist/first-run.d.ts +34 -0
- package/dist/first-run.js +37 -1
- package/dist/flow-gallery-ui.d.ts +30 -0
- package/dist/flow-gallery-ui.js +85 -0
- package/dist/flow-gallery.d.ts +111 -0
- package/dist/flow-gallery.js +445 -0
- package/dist/flow-share.d.ts +102 -0
- package/dist/flow-share.js +614 -0
- package/dist/flow-starters.d.ts +2 -0
- package/dist/flow-starters.js +1 -1
- package/dist/flow-steps.js +7 -0
- package/dist/flow-triggers.js +2 -0
- package/dist/flows-cli.d.ts +56 -0
- package/dist/flows-cli.js +109 -3
- package/dist/flows-ui.d.ts +15 -1
- package/dist/flows-ui.js +31 -6
- package/dist/guides.js +27 -2
- package/dist/invoke.js +5 -0
- package/dist/lead-context.js +6 -2
- package/dist/mate-doors.d.ts +0 -15
- package/dist/mate-doors.js +5 -1
- package/dist/mate-tools.js +9 -4
- package/dist/models-ui.js +1 -1
- package/dist/modes.d.ts +3 -1
- package/dist/modes.js +3 -1
- package/dist/operate.d.ts +4 -1
- package/dist/operate.js +711 -98
- package/dist/process-custody.js +8 -0
- package/dist/project-concurrency.d.ts +62 -0
- package/dist/project-concurrency.js +116 -0
- package/dist/project-delete-ui.d.ts +7 -0
- package/dist/project-delete-ui.js +19 -2
- package/dist/project-delete.js +4 -0
- package/dist/provider.js +16 -0
- package/dist/pull-request-flow.d.ts +9 -0
- package/dist/pull-request-flow.js +21 -3
- package/dist/result-follow-ups.d.ts +101 -0
- package/dist/result-follow-ups.js +333 -0
- package/dist/retention-ui.d.ts +3 -1
- package/dist/retention-ui.js +4 -4
- package/dist/retention.d.ts +13 -6
- package/dist/retention.js +50 -12
- package/dist/review-switch.d.ts +41 -0
- package/dist/review-switch.js +88 -0
- package/dist/reviewer.d.ts +24 -2
- package/dist/reviewer.js +49 -1
- package/dist/scope.js +11 -0
- package/dist/serve.d.ts +211 -2
- package/dist/serve.js +1038 -145
- package/dist/store.d.ts +112 -4
- package/dist/store.js +447 -27
- package/dist/surface.js +36 -9
- package/dist/tailnet.d.ts +11 -0
- package/dist/tailnet.js +47 -0
- package/dist/task-outcome-cli.js +2 -2
- package/dist/task-status.d.ts +13 -3
- package/dist/task-status.js +38 -16
- package/dist/team-cli.js +3 -0
- package/dist/telegram-status.js +2 -2
- package/dist/verification-evidence.js +21 -1
- package/dist/webhooks.js +5 -0
- package/dist/work-index.js +39 -6
- package/dist/work-summary.d.ts +0 -4
- package/dist/work-summary.js +4 -1
- package/dist/workspace-ui.d.ts +6 -1
- package/dist/workspace-ui.js +14 -11
- package/package.json +1 -1
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export const REVIEW_SCHEMA = `
|
|
2
|
+
-- A project's explicit review switch. No row: on while the project is under a hands-off mode, off otherwise.
|
|
3
|
+
CREATE TABLE IF NOT EXISTS review_switch (
|
|
4
|
+
repo TEXT PRIMARY KEY,
|
|
5
|
+
enabled INTEGER NOT NULL CHECK (enabled IN (0, 1)),
|
|
6
|
+
changed_by TEXT NOT NULL,
|
|
7
|
+
changed_at TEXT NOT NULL
|
|
8
|
+
);
|
|
9
|
+
-- One row per reviewed build run: pending until its one reviewer run settles it.
|
|
10
|
+
CREATE TABLE IF NOT EXISTS build_review (
|
|
11
|
+
run INTEGER PRIMARY KEY,
|
|
12
|
+
task_id TEXT NOT NULL,
|
|
13
|
+
repo TEXT NOT NULL,
|
|
14
|
+
state TEXT NOT NULL CHECK (state IN ('pending', 'reviewed', 'not-reviewed')),
|
|
15
|
+
request INTEGER,
|
|
16
|
+
reviewer_run INTEGER,
|
|
17
|
+
findings_json TEXT,
|
|
18
|
+
reason TEXT,
|
|
19
|
+
sent_back_as TEXT,
|
|
20
|
+
queued_at TEXT NOT NULL,
|
|
21
|
+
finished_at TEXT
|
|
22
|
+
);
|
|
23
|
+
CREATE INDEX IF NOT EXISTS build_review_pending ON build_review (repo) WHERE state = 'pending';
|
|
24
|
+
CREATE INDEX IF NOT EXISTS build_review_sent_back ON build_review (sent_back_as) WHERE sent_back_as IS NOT NULL;
|
|
25
|
+
`;
|
|
26
|
+
/** How the policy log words a switch state. */
|
|
27
|
+
export function reviewSwitchWords(state) {
|
|
28
|
+
return state.source === "project" ? (state.on ? "on" : "off") : state.on ? "on (hands-off default)" : "off (default)";
|
|
29
|
+
}
|
|
30
|
+
export function buildReviewOf(store, runId) {
|
|
31
|
+
let row;
|
|
32
|
+
// A deploy reads this with the candidate's code over the INSTALLED runtime's file, before this version's migration
|
|
33
|
+
// made the table: no table reads as no review.
|
|
34
|
+
try {
|
|
35
|
+
row = store.handle.prepare("SELECT * FROM build_review WHERE run = ?").get(runId);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
if (/no such table: build_review/.test(String(error)))
|
|
39
|
+
return null;
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
if (row === undefined)
|
|
43
|
+
return null;
|
|
44
|
+
// The row was validated when it was written (parseBuildFindings).
|
|
45
|
+
let findings = [];
|
|
46
|
+
try {
|
|
47
|
+
const saved = row["findings_json"] === null ? null : JSON.parse(String(row["findings_json"]));
|
|
48
|
+
if (Array.isArray(saved?.findings))
|
|
49
|
+
findings = saved.findings;
|
|
50
|
+
}
|
|
51
|
+
catch { /* An unreadable saved row shows no findings; its state stays. */ }
|
|
52
|
+
return {
|
|
53
|
+
run: runId,
|
|
54
|
+
state: String(row["state"]),
|
|
55
|
+
reviewerRun: row["reviewer_run"] === null ? null : Number(row["reviewer_run"]),
|
|
56
|
+
reason: row["reason"] === null ? null : String(row["reason"]),
|
|
57
|
+
high: findings.filter(one => one.severity === "HIGH"),
|
|
58
|
+
followUps: findings.filter(one => one.severity !== "HIGH"),
|
|
59
|
+
sentBackAs: row["sent_back_as"] === null ? null : String(row["sent_back_as"]),
|
|
60
|
+
finishedAt: row["finished_at"] === null ? null : String(row["finished_at"]),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export const findingWords = (finding) => `${finding.severity} ${finding.file}:${finding.line} — ${finding.scenario}`;
|
|
64
|
+
/** The review's standing in one line, or null while nothing was asked. */
|
|
65
|
+
export function buildReviewHeadline(view) {
|
|
66
|
+
if (view === null)
|
|
67
|
+
return null;
|
|
68
|
+
if (view.state === "pending")
|
|
69
|
+
return "Reviewing — an automatic review is reading this result.";
|
|
70
|
+
if (view.state === "not-reviewed")
|
|
71
|
+
return `Not reviewed${view.reason === null ? "." : `: ${view.reason}`}`;
|
|
72
|
+
if (view.sentBackAs !== null)
|
|
73
|
+
return `Sent back as ${view.sentBackAs}: ${view.high.length} high finding${view.high.length === 1 ? "" : "s"}.`;
|
|
74
|
+
if (view.high.length > 0)
|
|
75
|
+
return `Reviewed: ${view.high.length} high finding${view.high.length === 1 ? "" : "s"} for you to decide.`;
|
|
76
|
+
return `Reviewed: no high findings${view.followUps.length === 0 ? "." : `; ${view.followUps.length} suggested follow-up${view.followUps.length === 1 ? "" : "s"}.`}`;
|
|
77
|
+
}
|
|
78
|
+
/** Plain lines for `task show` and `assignment show`. */
|
|
79
|
+
export function buildReviewLines(view) {
|
|
80
|
+
const headline = buildReviewHeadline(view);
|
|
81
|
+
if (view === null || headline === null)
|
|
82
|
+
return [];
|
|
83
|
+
return [
|
|
84
|
+
` review: ${headline}`,
|
|
85
|
+
...view.high.map(one => ` ${findingWords(one)}`),
|
|
86
|
+
...view.followUps.map(one => ` suggested follow-up: ${findingWords(one)}`),
|
|
87
|
+
];
|
|
88
|
+
}
|
package/dist/reviewer.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
/** Pure
|
|
2
|
-
*
|
|
1
|
+
/** Pure parsers for review payloads: retained historical reviews, and the one
|
|
2
|
+
* automatic build review's findings (build-review.ts runs it). This module
|
|
3
|
+
* cannot launch providers, create runs or retry work. */
|
|
3
4
|
import type { CriterionJudgementWord } from "./proof.js";
|
|
5
|
+
import type { BuildFinding } from "./review-switch.js";
|
|
4
6
|
/** The files the pass writes INTO the scratch directory for the agent —
|
|
5
7
|
* the patch always; the rubric, re-serialized proof, check log, and
|
|
6
8
|
* screenshots only when this run actually has one (v40) — a grandfathered
|
|
@@ -95,3 +97,23 @@ export declare function parseReview(raw: string, patchPaths: ReadonlySet<string>
|
|
|
95
97
|
ok: false;
|
|
96
98
|
problems: ReviewProblem[];
|
|
97
99
|
};
|
|
100
|
+
export type { BuildFinding, FindingSeverity } from "./review-switch.js";
|
|
101
|
+
export declare const FINDING_LIMITS: {
|
|
102
|
+
readonly findings: 40;
|
|
103
|
+
readonly file: 300;
|
|
104
|
+
readonly scenario: 500;
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* The automatic build review's reply, strict and wholesale like parseReview:
|
|
108
|
+
* `{"version":1,"findings":[{"severity","file","line","scenario"}]}`. Any
|
|
109
|
+
* invalid finding refuses the whole reply — a review that did not answer
|
|
110
|
+
* what it was asked is "not reviewed", never a partial pass. Text is kept
|
|
111
|
+
* to one line so it can travel in a revision note and a terminal.
|
|
112
|
+
*/
|
|
113
|
+
export declare function parseBuildFindings(raw: string): {
|
|
114
|
+
ok: true;
|
|
115
|
+
findings: BuildFinding[];
|
|
116
|
+
} | {
|
|
117
|
+
ok: false;
|
|
118
|
+
problem: string;
|
|
119
|
+
};
|
package/dist/reviewer.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { citesSuppliedProvenance } from "./review-context.js";
|
|
2
|
-
import { REVIEW_OUTPUT_LIMITS } from "./structured-output.js";
|
|
2
|
+
import { normalizeStructuredJson, REVIEW_OUTPUT_LIMITS } from "./structured-output.js";
|
|
3
3
|
/** The files the pass writes INTO the scratch directory for the agent —
|
|
4
4
|
* the patch always; the rubric, re-serialized proof, check log, and
|
|
5
5
|
* screenshots only when this run actually has one (v40) — a grandfathered
|
|
@@ -200,3 +200,51 @@ export function parseReview(raw, patchPaths, approvedCriteriaIds = new Set(), pr
|
|
|
200
200
|
return { ok: false, problems };
|
|
201
201
|
return { ok: true, comments, criteria, ...(payload["learning"] === undefined ? {} : { learning: payload["learning"] }), ...(payload["learningAssessment"] === undefined ? {} : { learningAssessment: payload["learningAssessment"] }) };
|
|
202
202
|
}
|
|
203
|
+
export const FINDING_LIMITS = { findings: 40, file: REVIEW_OUTPUT_LIMITS.path, scenario: REVIEW_OUTPUT_LIMITS.note };
|
|
204
|
+
/**
|
|
205
|
+
* The automatic build review's reply, strict and wholesale like parseReview:
|
|
206
|
+
* `{"version":1,"findings":[{"severity","file","line","scenario"}]}`. Any
|
|
207
|
+
* invalid finding refuses the whole reply — a review that did not answer
|
|
208
|
+
* what it was asked is "not reviewed", never a partial pass. Text is kept
|
|
209
|
+
* to one line so it can travel in a revision note and a terminal.
|
|
210
|
+
*/
|
|
211
|
+
export function parseBuildFindings(raw) {
|
|
212
|
+
let parsed;
|
|
213
|
+
try {
|
|
214
|
+
parsed = JSON.parse(normalizeStructuredJson(raw).text);
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
return { ok: false, problem: "the reply is not JSON" };
|
|
218
|
+
}
|
|
219
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
220
|
+
return { ok: false, problem: "the reply is not an object" };
|
|
221
|
+
const payload = parsed;
|
|
222
|
+
if (payload["version"] !== 1)
|
|
223
|
+
return { ok: false, problem: "version must be 1" };
|
|
224
|
+
const list = payload["findings"];
|
|
225
|
+
if (!Array.isArray(list))
|
|
226
|
+
return { ok: false, problem: "findings must be an array" };
|
|
227
|
+
if (list.length > FINDING_LIMITS.findings)
|
|
228
|
+
return { ok: false, problem: `at most ${FINDING_LIMITS.findings} findings` };
|
|
229
|
+
const findings = [];
|
|
230
|
+
const oneLine = (text) => text.replace(/[\u0000-\u001f\u007f-\u009f\u061c\u200e\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069]+/g, " ").replace(/\s+/g, " ").trim();
|
|
231
|
+
for (const [index, one] of list.entries()) {
|
|
232
|
+
if (one === null || typeof one !== "object" || Array.isArray(one))
|
|
233
|
+
return { ok: false, problem: `finding ${index}: not an object` };
|
|
234
|
+
const finding = one;
|
|
235
|
+
const severity = finding["severity"];
|
|
236
|
+
if (severity !== "HIGH" && severity !== "MEDIUM" && severity !== "LOW")
|
|
237
|
+
return { ok: false, problem: `finding ${index}: severity must be HIGH, MEDIUM or LOW` };
|
|
238
|
+
const file = typeof finding["file"] === "string" ? oneLine(finding["file"]) : "";
|
|
239
|
+
if (file.length === 0 || file.length > FINDING_LIMITS.file)
|
|
240
|
+
return { ok: false, problem: `finding ${index}: file must be a path of 1..${FINDING_LIMITS.file} characters` };
|
|
241
|
+
const line = finding["line"];
|
|
242
|
+
if (typeof line !== "number" || !Number.isInteger(line) || line < 1)
|
|
243
|
+
return { ok: false, problem: `finding ${index}: line must be a positive whole number` };
|
|
244
|
+
const scenario = typeof finding["scenario"] === "string" ? oneLine(finding["scenario"]) : "";
|
|
245
|
+
if (scenario.length === 0 || scenario.length > FINDING_LIMITS.scenario)
|
|
246
|
+
return { ok: false, problem: `finding ${index}: scenario must be one sentence of 1..${FINDING_LIMITS.scenario} characters` };
|
|
247
|
+
findings.push({ severity, file, line, scenario });
|
|
248
|
+
}
|
|
249
|
+
return { ok: true, findings };
|
|
250
|
+
}
|
package/dist/scope.js
CHANGED
|
@@ -2,6 +2,7 @@ import { passwordGuardOf } from "./sign-in-guard.js";
|
|
|
2
2
|
import { validateScopeText } from "./task-text.js";
|
|
3
3
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
4
|
import { projectAuthority } from "./project-access.js";
|
|
5
|
+
import { claimActor, currentActor, parseLeadToken } from "./actor.js";
|
|
5
6
|
/**
|
|
6
7
|
* What a task is allowed to become, agreed before anything builds it.
|
|
7
8
|
*
|
|
@@ -791,6 +792,14 @@ password) {
|
|
|
791
792
|
export function authenticateAccount(store, by, secret) {
|
|
792
793
|
if (store.listApprovers().length === 0)
|
|
793
794
|
return { ok: false, reason: "no-approvers" };
|
|
795
|
+
// A lead token signs in as its owner, only inside a command the lead runs with it (never a console sign-in).
|
|
796
|
+
const acting = currentActor();
|
|
797
|
+
if (acting?.lead === true && parseLeadToken(secret) !== null) {
|
|
798
|
+
const lead = store.leadFor(secret), account = store.accountOf(by);
|
|
799
|
+
if (lead === null || lead.owner !== by || acting.account !== by || account === null)
|
|
800
|
+
return { ok: false, reason: "unknown" };
|
|
801
|
+
return { ok: true, role: account.role, generation: account.generation };
|
|
802
|
+
}
|
|
794
803
|
// v99: wrong passwords in a row lock the name for a while, whatever road they came by.
|
|
795
804
|
const guard = passwordGuardOf(store), now = Date.now();
|
|
796
805
|
if (guard.lockedFor(by, now) > 0)
|
|
@@ -803,6 +812,8 @@ export function authenticateAccount(store, by, secret) {
|
|
|
803
812
|
guard.succeeded(by);
|
|
804
813
|
if (account.revokedAt !== null)
|
|
805
814
|
return { ok: false, reason: "revoked" };
|
|
815
|
+
// Inside a command, the person who signed in is who acted (their own acts never ping them).
|
|
816
|
+
claimActor(by);
|
|
806
817
|
return { ok: true, role: account.role, generation: account.generation };
|
|
807
818
|
}
|
|
808
819
|
/**
|
package/dist/serve.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { type BrowserWorkspace, type BrowserSignIn, type BrowserUpdateNotice } from './browser-workspace.js';
|
|
1
2
|
import { CodingWorkspace } from './coding-workspace.js';
|
|
3
|
+
import { type FetchLike } from "./flow-share.js";
|
|
2
4
|
import { type MailSender } from "./flow-actions.js";
|
|
3
5
|
import { type TriggerIo } from "./flow-triggers.js";
|
|
4
6
|
import { type AssignmentSnapshot } from './assignment.js';
|
|
@@ -6,14 +8,17 @@ import type { PublishExec } from './publish.js';
|
|
|
6
8
|
import { type VersionRunner } from "./model-catalog.js";
|
|
7
9
|
import { type Server } from "node:http";
|
|
8
10
|
import { type ProofVerdict, type CriterionMatrixRow } from "./proof.js";
|
|
9
|
-
import { type Run, type Store } from "./store.js";
|
|
11
|
+
import { type Decision, type Run, type Store, type TaskState } from "./store.js";
|
|
10
12
|
import { previewGithubRepo, cloneGithubRepo, listGithubRepos } from "./onboard.js";
|
|
11
13
|
import { run as execRun } from "./exec.js";
|
|
14
|
+
import { type FirstRunStep } from "./first-run.js";
|
|
15
|
+
import { type Gap } from "./gaps.js";
|
|
12
16
|
import { type AgentChoice } from "./agentconfig.js";
|
|
13
17
|
import { type RouteProjection, type RouteOverride, type RouteStamp, type RiskLevel } from "./phase-routing.js";
|
|
14
18
|
import { type Phase } from "./provider.js";
|
|
15
19
|
import { type IntegrationIo } from "./integrations.js";
|
|
16
20
|
import { launchRuntimeUpdate } from "./toolroll-update.js";
|
|
21
|
+
import type { RepairChainRow } from "./store.js";
|
|
17
22
|
import type { RevisionLineage } from "./store.js";
|
|
18
23
|
import type { SubscriptionMateRunner } from "./subscription-chat.js";
|
|
19
24
|
import { type InstallMethod } from "./install-method.js";
|
|
@@ -39,8 +44,22 @@ export type ServeOptions = {
|
|
|
39
44
|
registryPath?: string;
|
|
40
45
|
/** This console fronts an `up` process: onboarding copy says how to watch. */
|
|
41
46
|
upConsole?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* The lead on by default (onboarding): with no lead set up, the agent CLI
|
|
49
|
+
* signed in on this computer runs it, once. `up` and `serve` turn this on;
|
|
50
|
+
* tests opt in.
|
|
51
|
+
*/
|
|
52
|
+
leadByDefault?: boolean;
|
|
53
|
+
/** Test seam: the operating system the sign-in command is written for. */
|
|
54
|
+
platform?: NodeJS.Platform;
|
|
42
55
|
/** Extra Host values this server answers as (a Tailscale name, a LAN ip:port). */
|
|
43
56
|
allowedHosts?: readonly string[];
|
|
57
|
+
/**
|
|
58
|
+
* This computer's names on its tailnet (onboarding): read at start and every
|
|
59
|
+
* few minutes; the console answers to each on its own port without an
|
|
60
|
+
* --allow-host, and the phone card names the address. Absent = none.
|
|
61
|
+
*/
|
|
62
|
+
tailnetNames?: () => Promise<readonly string[]>;
|
|
44
63
|
/**
|
|
45
64
|
* The first-account road (setup review): while NO approver exists, the
|
|
46
65
|
* login page offers "create the first account", gated by this code —
|
|
@@ -107,6 +126,8 @@ export type ServeOptions = {
|
|
|
107
126
|
toolHome?: string;
|
|
108
127
|
/** v87: sends Send email steps' mail and the settings test (tests inject one). */
|
|
109
128
|
mailSender?: MailSender;
|
|
129
|
+
/** Injected by tests: how Flows → Import fetches a flow file's address. */
|
|
130
|
+
flowFetch?: FetchLike;
|
|
110
131
|
/** v89: Google's token endpoint (tests inject a scripted one). */
|
|
111
132
|
googleFetch?: typeof fetch;
|
|
112
133
|
/** Tests: every request a one-click connection makes (discovery, registration, tokens). */
|
|
@@ -164,7 +185,14 @@ export type ServeOptions = {
|
|
|
164
185
|
/** Injected by tests: the git and gh calls pull-request setup and Merge make. */
|
|
165
186
|
publishExec?: PublishExec;
|
|
166
187
|
};
|
|
167
|
-
|
|
188
|
+
/** Where `up`'s one-time sign-in link points, and how long it works. */
|
|
189
|
+
export declare const SIGN_IN_LINK_PATH = "/login/once/";
|
|
190
|
+
export declare const SIGN_IN_LINK_MS: number;
|
|
191
|
+
/** The console's server, and the one-time sign-in link `up` opens: a path on this server, or null for no such approver. */
|
|
192
|
+
export type DecisionServer = Server & {
|
|
193
|
+
mintSignInLink(account: string): string | null;
|
|
194
|
+
};
|
|
195
|
+
export declare function createDecisionServer(options: ServeOptions): DecisionServer;
|
|
168
196
|
/**
|
|
169
197
|
* A vscode://file href, or null (arc 6, finding 2): the link exists only
|
|
170
198
|
* when every part is provably tame — an absolute, control-free worktree;
|
|
@@ -211,6 +239,79 @@ export type RouteView = {
|
|
|
211
239
|
};
|
|
212
240
|
/** The page CSS this module writes itself (not the imported modules'), for the stylesheet contract tests. */
|
|
213
241
|
export declare const PAGE_CSS: string;
|
|
242
|
+
/** Everything the sidebar needs to draw itself for one request. */
|
|
243
|
+
type Chrome = {
|
|
244
|
+
projectScoped?: boolean;
|
|
245
|
+
active: "code" | "inbox" | "board" | "queue" | "fleet" | "workbench" | "work" | "done" | "activity" | "review" | "system" | "tasks" | "runs" | "caps" | "routines" | "recipes" | "projects" | "flows" | "settings" | "chat" | "people" | "ledger" | "spend" | "mode" | "menu" | "none";
|
|
246
|
+
project: string | null;
|
|
247
|
+
/** The surface's scope for the scope bar — which rows this screen can
|
|
248
|
+
* show. Derived from the ROUTE, not the session: portfolio and fleet are
|
|
249
|
+
* all-project even while a project is open; the board says which of its
|
|
250
|
+
* two modes it is in. Absent = the session default (open project, else
|
|
251
|
+
* all projects). Display only — switching stays POST + CSRF. */
|
|
252
|
+
scope?: "all" | "project" | "board-all";
|
|
253
|
+
/** The saturated inbox count — never a sum of unbounded list reads. */
|
|
254
|
+
inboxCount: number;
|
|
255
|
+
inboxSaturated: boolean;
|
|
256
|
+
settings: boolean;
|
|
257
|
+
/** This database is a demo sandbox: banner every page, spend fenced. */
|
|
258
|
+
demo?: boolean;
|
|
259
|
+
/** The active operating mode's banner (M1): rides every page scoped to
|
|
260
|
+
* a repo with a live mode — a signed posture is never invisible. */
|
|
261
|
+
modeBanner?: {
|
|
262
|
+
words: string;
|
|
263
|
+
name: string;
|
|
264
|
+
};
|
|
265
|
+
/** Providers whose sign-in stopped working: every page says so, once. */
|
|
266
|
+
signIn?: BrowserSignIn[];
|
|
267
|
+
/** A newer Toolroll: a quiet notice for an operator until they dismiss this version. */
|
|
268
|
+
update?: BrowserUpdateNotice;
|
|
269
|
+
/** The chat tab renders only where chat could ever be allowed. */
|
|
270
|
+
chat?: boolean;
|
|
271
|
+
code?: boolean;
|
|
272
|
+
/** A rendered list pane makes the page master-detail. */
|
|
273
|
+
listPane?: string;
|
|
274
|
+
/** A compact pulse for the currently open workspace. Null in the
|
|
275
|
+
* cross-workspace view; absent only when the store could not answer. */
|
|
276
|
+
projectPeek?: ProjectPeek | null;
|
|
277
|
+
/** The switcher (board pass): every enrolled project inside the ceiling,
|
|
278
|
+
* most recently opened first — the one-tap switch on any screen. */
|
|
279
|
+
projects?: {
|
|
280
|
+
path: string;
|
|
281
|
+
name: string;
|
|
282
|
+
}[];
|
|
283
|
+
/** The session's csrf token, for the switcher's forms; "" when the
|
|
284
|
+
* request has no cookie session (bearer), which renders the switcher
|
|
285
|
+
* inert. */
|
|
286
|
+
csrf?: string;
|
|
287
|
+
/** Where a switch made on this screen returns to. */
|
|
288
|
+
returnTo?: string;
|
|
289
|
+
};
|
|
290
|
+
/**
|
|
291
|
+
* A page described, not yet rendered (arc 4): every chromed HTML route
|
|
292
|
+
* returns one of these and ONE helper (sendScreen, inside the server)
|
|
293
|
+
* owns the nonce, the palette index, the shortcuts overlay, script
|
|
294
|
+
* composition, and the CSP. Renderers stopped calling shell() themselves
|
|
295
|
+
* so those five things cannot drift apart per route.
|
|
296
|
+
*/
|
|
297
|
+
type Screen = {
|
|
298
|
+
title: string;
|
|
299
|
+
body: string;
|
|
300
|
+
chrome?: Chrome;
|
|
301
|
+
/** The page's own executable behavior (a region poller, the push
|
|
302
|
+
* enrollment script). fetches: true when it calls fetch — connect-src
|
|
303
|
+
* is granted only then. */
|
|
304
|
+
functional?: {
|
|
305
|
+
script: string;
|
|
306
|
+
fetches?: boolean;
|
|
307
|
+
};
|
|
308
|
+
refreshSeconds?: number;
|
|
309
|
+
/** Render sensitive even when no password field is visible — one-time
|
|
310
|
+
* secrets and judgment calls the classifier cannot see. */
|
|
311
|
+
forceSensitive?: boolean;
|
|
312
|
+
/** Structured conversation; complex guarded forms stay native islands. */
|
|
313
|
+
workspace?: Partial<Pick<BrowserWorkspace, 'conversation' | 'team' | 'focus' | 'result' | 'catchUpHtml' | 'controlsHtml' | 'notices' | 'pageHtml' | 'view' | 'firstRun' | 'phone' | 'home'>>;
|
|
314
|
+
};
|
|
214
315
|
/**
|
|
215
316
|
* The sensitivity classifier (arc 4, findings 3/17): a body showing a
|
|
216
317
|
* password input renders WITHOUT the chrome additions (palette, overlay,
|
|
@@ -232,6 +333,105 @@ export declare function ssoStepUps(html: string, sso: {
|
|
|
232
333
|
label: string;
|
|
233
334
|
fresh: boolean;
|
|
234
335
|
}, returnTo: string): string;
|
|
336
|
+
/**
|
|
337
|
+
* The page for an address the console doesn't answer to (onboarding): what was opened, where it answers, and the exact
|
|
338
|
+
* command that admits this address. It stands alone (styles inline, no script, no font), because every other asset
|
|
339
|
+
* would be refused at this address too; and it holds nothing a stranger's page doesn't already know.
|
|
340
|
+
*/
|
|
341
|
+
export declare function wrongHostPage(facts: {
|
|
342
|
+
opened: string | null;
|
|
343
|
+
served: string;
|
|
344
|
+
command: string | null;
|
|
345
|
+
}): string;
|
|
346
|
+
/**
|
|
347
|
+
* The inbox (v3): only things stalling progress without a person, one card
|
|
348
|
+
* per underlying stall, each with its verb inline or one step away. No
|
|
349
|
+
* auto-refresh — approval links lead to the step-up screen, and a page
|
|
350
|
+
* that might hold typed input never re-renders itself.
|
|
351
|
+
*/
|
|
352
|
+
export type InboxTab = "needs-you" | "ready" | "running" | "all";
|
|
353
|
+
export declare function parseInboxTab(raw: string | null): InboxTab | undefined;
|
|
354
|
+
/** What each tab holds, as a short fingerprint: a tab is unread when its
|
|
355
|
+
* fingerprint differs from the one this browser saw there last. */
|
|
356
|
+
export declare function inboxFingerprints(data: Pick<Parameters<typeof inboxPage>[1], "decisions" | "approvals" | "requeueables" | "cancelledBlockers" | "gaps" | "needsVerification" | "ready" | "running">): Record<InboxTab, string>;
|
|
357
|
+
declare function inboxPage(chrome: Chrome, data: {
|
|
358
|
+
csrf: string;
|
|
359
|
+
revision: number;
|
|
360
|
+
/** No project open in scoped mode: every admitted project at once,
|
|
361
|
+
* chips on every row, links only — acting means opening the project. */
|
|
362
|
+
rollup: boolean;
|
|
363
|
+
/** A project is OPEN: only then do decisions answer on the card. The
|
|
364
|
+
* legacy unscoped projectless inbox stays links-only too (commit-1
|
|
365
|
+
* review, finding 4) — the partial belongs to a chosen project. */
|
|
366
|
+
interactive: boolean;
|
|
367
|
+
decisions: (Decision & {
|
|
368
|
+
taskId: string;
|
|
369
|
+
repo?: string | null;
|
|
370
|
+
})[];
|
|
371
|
+
approvals: {
|
|
372
|
+
taskId: string;
|
|
373
|
+
title: string;
|
|
374
|
+
goal: string;
|
|
375
|
+
proposedAt: string;
|
|
376
|
+
repo?: string | null; /** v48 authority repair: the closed consent door's title, or null when a yes could bind. */
|
|
377
|
+
closed?: string | null;
|
|
378
|
+
}[];
|
|
379
|
+
requeueables: {
|
|
380
|
+
taskId: string;
|
|
381
|
+
title: string;
|
|
382
|
+
state: TaskState;
|
|
383
|
+
strikes: number;
|
|
384
|
+
incidentCount: number;
|
|
385
|
+
repo?: string | null;
|
|
386
|
+
}[];
|
|
387
|
+
cancelledBlockers: {
|
|
388
|
+
blockerId: string;
|
|
389
|
+
dependentCount: number;
|
|
390
|
+
exampleDependent: string;
|
|
391
|
+
repo?: string | null;
|
|
392
|
+
blockerRepo?: string | null;
|
|
393
|
+
}[];
|
|
394
|
+
gaps: Gap[];
|
|
395
|
+
/** A completed task whose proof is short or refuted and not yet accepted
|
|
396
|
+
* (Priority 2) — reads "needs verification" here too, never silently
|
|
397
|
+
* "done" just because the inbox does not otherwise look at finished work. */
|
|
398
|
+
needsVerification: {
|
|
399
|
+
taskId: string;
|
|
400
|
+
title: string;
|
|
401
|
+
verdict: "short" | "refuted";
|
|
402
|
+
repo?: string | null;
|
|
403
|
+
matrix?: CriterionMatrixRow[];
|
|
404
|
+
repairChain?: RepairChainRow | null;
|
|
405
|
+
}[];
|
|
406
|
+
/** The first-run steps; null once the installation has had its first Ready result. */
|
|
407
|
+
wizard: FirstRunStep[] | null;
|
|
408
|
+
/** Whether any worker is answering right now — said at the top when none is. */
|
|
409
|
+
worker: {
|
|
410
|
+
answering: number;
|
|
411
|
+
registered: number;
|
|
412
|
+
lastHeard: string | null;
|
|
413
|
+
};
|
|
414
|
+
now: Date;
|
|
415
|
+
/** Console v2: the tab shown, results ready to review, work running now,
|
|
416
|
+
* and the tabs holding something this browser hasn't seen (dots on a phone). */
|
|
417
|
+
tab?: InboxTab;
|
|
418
|
+
ready?: {
|
|
419
|
+
taskId: string;
|
|
420
|
+
title: string;
|
|
421
|
+
detail: string;
|
|
422
|
+
repo: string | null;
|
|
423
|
+
}[];
|
|
424
|
+
running?: {
|
|
425
|
+
taskId: string;
|
|
426
|
+
title: string;
|
|
427
|
+
detail: string;
|
|
428
|
+
repo: string | null;
|
|
429
|
+
}[];
|
|
430
|
+
unread?: readonly InboxTab[];
|
|
431
|
+
}): Screen;
|
|
432
|
+
/** The task composer's modes (console v2), as the words the lead reads for that one turn. */
|
|
433
|
+
export type TaskComposerMode = "build" | "plan" | "answer";
|
|
434
|
+
export declare const TASK_COMPOSER_MODES: Record<TaskComposerMode, string>;
|
|
235
435
|
/**
|
|
236
436
|
* The inline-answer enhancement (portfolio arc §2): submits a reversible
|
|
237
437
|
* option's form as the urlencoded POST the forms-only gate expects, follows
|
|
@@ -242,6 +442,12 @@ export declare function ssoStepUps(html: string, sso: {
|
|
|
242
442
|
* Nothing else on the page is touched: typed input elsewhere survives.
|
|
243
443
|
*/
|
|
244
444
|
export declare function decisionAnswerScript(): string;
|
|
445
|
+
type ProjectPeek = {
|
|
446
|
+
waiting: number;
|
|
447
|
+
queued: number;
|
|
448
|
+
running: number;
|
|
449
|
+
doneRecently: number;
|
|
450
|
+
};
|
|
245
451
|
/**
|
|
246
452
|
* Creating work is the product's first verb, so it gets a whole calm page:
|
|
247
453
|
* a title, the goal that becomes the scope draft, and the project it lands
|
|
@@ -257,6 +463,8 @@ export declare function decisionAnswerScript(): string;
|
|
|
257
463
|
*/
|
|
258
464
|
/** The person's pinned theme from their own cookie; null follows the device. */
|
|
259
465
|
export declare function pinnedTheme(cookieHeader: string | undefined): "light" | "dark" | null;
|
|
466
|
+
/** A request path fit for a log: the one-time sign-in link's code is left out. */
|
|
467
|
+
export declare function redactedPath(path: string): string;
|
|
260
468
|
/** The /queue page's one nonce'd script: delegated pointer-event drag (it
|
|
261
469
|
* survives every fragment swap — finding 17) plus a poller that re-checks
|
|
262
470
|
* focus, dirty inputs, and an in-flight drag AT SWAP TIME, never only
|
|
@@ -339,3 +547,4 @@ export declare function plainConclusionOf(conclusion: string): string;
|
|
|
339
547
|
* stop marker, so an open tab quits refetching a dead build (round-4
|
|
340
548
|
* finding 15). */
|
|
341
549
|
export declare function runFactsFragment(run: Run, taskId: string, live: boolean, route?: RouteStamp | null): string;
|
|
550
|
+
export {};
|