toolroll 0.9.4 → 0.9.5
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 +17 -0
- package/README.md +5 -4
- package/dist/assignment-presentation.js +4 -2
- package/dist/assignment-ui.js +2 -1
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +9 -9
- package/dist/browser-workspace.d.ts +10 -0
- package/dist/cli.js +6 -6
- package/dist/containment.d.ts +5 -0
- package/dist/containment.js +9 -0
- package/dist/demo-chat.d.ts +37 -0
- package/dist/demo-chat.js +150 -0
- package/dist/demo.d.ts +98 -0
- package/dist/demo.js +525 -21
- package/dist/flow-pull-request.js +26 -3
- package/dist/operate.js +25 -9
- package/dist/proof.d.ts +9 -0
- package/dist/proof.js +25 -1
- package/dist/publish.d.ts +15 -0
- package/dist/publish.js +41 -1
- package/dist/pull-request-flow.js +3 -3
- package/dist/serve.d.ts +2 -0
- package/dist/serve.js +197 -16
- package/dist/store.d.ts +1 -0
- package/dist/store.js +2 -1
- package/dist/workspace-ui.js +1 -1
- package/package.json +1 -1
package/dist/serve.js
CHANGED
|
@@ -58,6 +58,7 @@ import { prepareWorkspaceRevision, WorkspaceValidatorCache } from "./workspace-r
|
|
|
58
58
|
import { workIndexPage, workCountsByProject, WorkIndexCursorError } from "./work-index.js";
|
|
59
59
|
import { openWorkDecisionOf } from "./work-summary.js";
|
|
60
60
|
import { assignmentOf, checkAssignmentAsOperator } from './assignment.js';
|
|
61
|
+
import { demoChatHtml, demoThreadHtml, DEMO_CHAT_SCRIPT } from "./demo-chat.js";
|
|
61
62
|
import { pullRequestSettingsHtml, PULL_REQUEST_SETTINGS_CSS } from './pull-request-ui.js';
|
|
62
63
|
import { checkPublishing, completeAndOpenPullRequest, mergeAsPerson, pullRequestBlocker, publishingOf, saveMergeSettings, savePublishing, newestPullRequestOf, MERGE_METHODS } from './pull-request-flow.js';
|
|
63
64
|
import { leadBriefHtml, LEAD_CONTEXT_CSS } from './lead-context.js';
|
|
@@ -150,7 +151,7 @@ import { TEMPLATES, templateByName } from "./templates.js";
|
|
|
150
151
|
import { starterRecipes, savedRecipes, findRecipe, importRecipe, exportRecipe, createWorkflowPreview, workflowPreview, launchWorkflow, saveWorkflowRecipe, RecipeError, prepareRecipeRun } from "./recipes.js";
|
|
151
152
|
import { recipeFromForm, recipeLibraryHtml, recipeEditorHtml, workflowPreviewHtml, recipeScript, RECIPE_CSS, recipeDefinitionPreviewHtml, recipeRunHtml, recipeAnswersFromForm } from "./recipe-ui.js";
|
|
152
153
|
import { EVIDENCE_CAPS, readVerifiedArtifact, readVerifiedReport, readVerifiedProofForRun, storeEvidence, writeEvidenceFile, scanForSecrets } from "./evidence.js";
|
|
153
|
-
import { GOAL_ASSESSMENT_PENDING, reviewConflict, manualReviewOnly, dispatchStatusToken, passFraction, semanticCoverage, coverageWords, coverageStateWords } from "./proof.js";
|
|
154
|
+
import { GOAL_ASSESSMENT_PENDING, reviewConflict, manualReviewOnly, manualReviewCriterionOf, personCheckWords, plainReasonWords, dispatchStatusToken, passFraction, semanticCoverage, coverageWords, coverageStateWords } from "./proof.js";
|
|
154
155
|
import { WORK_VIEWS, REVIEW_TOKENS, parseWorkView, resultStatusOf, receiptHeadingOf, receiptPublicationWords, reviewFactsOf, workStatusOf, primaryDestinationOf, needsPerson, dispatchActionLabel, } from "./workspace-ui.js";
|
|
155
156
|
import { PRICED_BUILD_MODELS } from "./pricing.js";
|
|
156
157
|
import { RESULT_TABS, RESULT_REVIEW_SCRIPT, isRevisionFeedback, evidenceResultStatusOf, evidenceShortenedWords, parseResultTab, resultLeadOf, evidenceProblemDetailsOf, evidenceHealthOf, resultFactsAttributes, resultFactsAttributeMap, resultReturnTarget, commentSourceKey, REQUEST_TOKEN, revisionBatchOf, parseRevisionBatch, revisionSourceOf, } from "./result-review.js";
|
|
@@ -1727,6 +1728,7 @@ export function createDecisionServer(options) {
|
|
|
1727
1728
|
url.pathname !== "/projects/browse" && url.pathname !== "/projects/github" && url.pathname !== "/workbench" &&
|
|
1728
1729
|
url.pathname !== "/fleet" &&
|
|
1729
1730
|
url.pathname !== "/chat" && url.pathname !== "/chat/mate/status" && url.pathname !== "/chat/task-status" && url.pathname !== "/chat/stream" &&
|
|
1731
|
+
!url.pathname.startsWith("/chat/demo/") &&
|
|
1730
1732
|
!/^\/chat\/action\/[0-9]{1,15}$/.test(url.pathname) &&
|
|
1731
1733
|
!url.pathname.startsWith("/settings") && url.pathname !== "/logout" && url.pathname !== "/people" && url.pathname !== "/ledger" && url.pathname !== "/ledger/export" && url.pathname !== "/metrics" && url.pathname !== "/spend" && url.pathname !== "/spend/budget" &&
|
|
1732
1734
|
!(url.pathname === "/board" && url.searchParams.get("scope") === "all");
|
|
@@ -3237,6 +3239,26 @@ export function createDecisionServer(options) {
|
|
|
3237
3239
|
...(fragments === null ? {} : { fragments }),
|
|
3238
3240
|
}));
|
|
3239
3241
|
}
|
|
3242
|
+
// `toolroll demo`: Chat is the scripted lead, never a model.
|
|
3243
|
+
if (url.pathname === "/chat/demo/live") {
|
|
3244
|
+
const lead = demoLeadHere();
|
|
3245
|
+
if (lead === null || who.via !== "cookie")
|
|
3246
|
+
return respond(response, 404, "application/json", JSON.stringify({ ok: false }));
|
|
3247
|
+
const exchanges = lead.exchanges();
|
|
3248
|
+
return respond(response, 200, "application/json", JSON.stringify({
|
|
3249
|
+
version: lead.version(), working: exchanges.some(one => one.state === "working"),
|
|
3250
|
+
html: demoThreadHtml(exchanges, who.session.csrf, demoResultView),
|
|
3251
|
+
}));
|
|
3252
|
+
}
|
|
3253
|
+
if (url.pathname === "/chat" && demoLeadHere() !== null && who.via === "cookie" &&
|
|
3254
|
+
!["task", "result", "project", "conversation", "team", "proposal"].some(name => url.searchParams.has(name))) {
|
|
3255
|
+
const lead = demoLeadHere();
|
|
3256
|
+
const said = url.searchParams.get("said")?.slice(0, 300) ?? null;
|
|
3257
|
+
return sendScreen(response, 200, screen("Chat", demoChatHtml({ exchanges: lead.exchanges(), csrf: who.session.csrf, version: lead.version(), problem: said, resultOf: demoResultView }), {
|
|
3258
|
+
chrome: chromeFor(null, "chat", undefined, "all"),
|
|
3259
|
+
functional: { script: DEMO_CHAT_SCRIPT, fetches: true },
|
|
3260
|
+
}));
|
|
3261
|
+
}
|
|
3240
3262
|
if (url.pathname === "/chat") {
|
|
3241
3263
|
// Cookie sessions only (Codex v3 review, change 7): drafts live in
|
|
3242
3264
|
// THIS session's memory; a bearer caller has nowhere to keep them.
|
|
@@ -4146,6 +4168,42 @@ export function createDecisionServer(options) {
|
|
|
4146
4168
|
/** The frozen explicit repo list, digested canonically (sorted) — every
|
|
4147
4169
|
* candidate binds to it and filing re-proves it (v2 new finding 5). */
|
|
4148
4170
|
const chatCeilingDigest = (repos = managedRepos()) => createHash("sha256").update([...repos].sort().join("\n")).digest("hex");
|
|
4171
|
+
/** The scripted lead answers only inside a demo database. */
|
|
4172
|
+
function demoLeadHere() {
|
|
4173
|
+
return options.demoLead !== undefined && store.isDemo() ? options.demoLead : null;
|
|
4174
|
+
}
|
|
4175
|
+
/** A finished demo exchange's result, read back from the evidence its run actually stored. */
|
|
4176
|
+
function demoResultView(exchange) {
|
|
4177
|
+
if (exchange.runId === null || exchange.taskId === null)
|
|
4178
|
+
return null;
|
|
4179
|
+
const artifacts = store.artifactsFor(exchange.runId);
|
|
4180
|
+
const text = (kind) => {
|
|
4181
|
+
const artifact = artifacts.find(one => one.kind === kind);
|
|
4182
|
+
if (artifact === undefined)
|
|
4183
|
+
return null;
|
|
4184
|
+
const read = readVerifiedArtifact(evidenceRoot, artifact);
|
|
4185
|
+
return read.ok ? read.content.toString("utf8") : null;
|
|
4186
|
+
};
|
|
4187
|
+
const shot = artifacts.find(one => one.kind === "screenshot");
|
|
4188
|
+
const stat = (() => { try {
|
|
4189
|
+
return JSON.parse(text("diff-stat") ?? "null");
|
|
4190
|
+
}
|
|
4191
|
+
catch {
|
|
4192
|
+
return null;
|
|
4193
|
+
} })();
|
|
4194
|
+
const assignment = assignmentOf(store, exchange.taskId, clock(), { principal: "operator", repos: managedRepos() }, evidenceRoot);
|
|
4195
|
+
const log = text("check-log");
|
|
4196
|
+
return {
|
|
4197
|
+
diff: text("terminal-diff"),
|
|
4198
|
+
checkLog: log === null ? null : log.slice(log.indexOf("$ ") === -1 ? 0 : log.indexOf("$ ")),
|
|
4199
|
+
checks: { status: assignment?.receipt?.checks.status ?? "unavailable", detail: assignment?.receipt?.checks.detail ?? "Saved checks are unavailable." },
|
|
4200
|
+
screenshot: shot === undefined ? null : { href: `/r/${exchange.runId}/evidence/${shot.id}`, caption: exchange.plan.screenshot.caption },
|
|
4201
|
+
additions: stat?.additions ?? 0,
|
|
4202
|
+
deletions: stat?.deletions ?? 0,
|
|
4203
|
+
files: stat?.fileCount ?? 0,
|
|
4204
|
+
taskHref: taskHref(exchange.taskId),
|
|
4205
|
+
};
|
|
4206
|
+
}
|
|
4149
4207
|
/** Every condition re-proved per request — the render and the POST each
|
|
4150
4208
|
* ask again; nothing is cached into authority. */
|
|
4151
4209
|
function chatEnablement() {
|
|
@@ -4503,7 +4561,7 @@ export function createDecisionServer(options) {
|
|
|
4503
4561
|
s = { ...s, workspace: { ...s.workspace, view: { ...s.workspace.view, stepUp: { label: ssoFacts.label, fresh: ssoFacts.fresh, confirmHref: `/login/sso?reauth=1&return=${encodeURIComponent(back)}` } } } };
|
|
4504
4562
|
}
|
|
4505
4563
|
const teamEntry = requestContext.getStore()?.returnTo;
|
|
4506
|
-
if (teamEntry?.startsWith('/chat') && !s.workspace?.team && !teamEntry.includes('task=') && !teamEntry.includes('proposal=')) {
|
|
4564
|
+
if (teamEntry?.startsWith('/chat') && !s.workspace?.team && !teamEntry.includes('task=') && !teamEntry.includes('proposal=') && demoLeadHere() === null) {
|
|
4507
4565
|
s.body = '<p class="team-entry"><a href="/chat?team=1">Open team chat</a></p>' + s.body;
|
|
4508
4566
|
if (s.workspace?.conversation)
|
|
4509
4567
|
s.workspace.controlsHtml = '<p><a href="/chat?team=1">Open team chat</a></p>' + (s.workspace.controlsHtml ?? '');
|
|
@@ -4524,7 +4582,7 @@ export function createDecisionServer(options) {
|
|
|
4524
4582
|
const extras = s.workspace ?? {};
|
|
4525
4583
|
const notices = [...(extras.notices ?? [])];
|
|
4526
4584
|
if (s.chrome.demo)
|
|
4527
|
-
notices.unshift(
|
|
4585
|
+
notices.unshift(DEMO_BANNER);
|
|
4528
4586
|
if (s.chrome.modeBanner)
|
|
4529
4587
|
notices.push(s.chrome.modeBanner.words);
|
|
4530
4588
|
let crew = { crew: [], crewTruncated: false };
|
|
@@ -5922,6 +5980,43 @@ export function createDecisionServer(options) {
|
|
|
5922
5980
|
const denied = authorizeMutation(request, who, body);
|
|
5923
5981
|
if (denied !== null)
|
|
5924
5982
|
return refuse(response, who, denied.status, denied.message);
|
|
5983
|
+
// The scripted demo lead's acts: ask, approve, change a plan, complete or send back a result.
|
|
5984
|
+
const demoAct = /^\/chat\/demo\/(?:ask|([0-9]{1,9})\/(approve|change|revise|complete))$/.exec(url.pathname);
|
|
5985
|
+
if (demoAct !== null) {
|
|
5986
|
+
const lead = demoLeadHere();
|
|
5987
|
+
if (lead === null || who.via !== "cookie")
|
|
5988
|
+
return refuse(response, who, 404, "There's no page at this address.", "/chat");
|
|
5989
|
+
if (who.role !== "approver")
|
|
5990
|
+
return redirect(response, `/chat?said=${encodeURIComponent("Your login can watch. Approving and completing is an approver's act.")}`);
|
|
5991
|
+
const now = clock();
|
|
5992
|
+
if (demoAct[1] === undefined) {
|
|
5993
|
+
const message = (body.get("message") ?? "").trim();
|
|
5994
|
+
if (message === "")
|
|
5995
|
+
return redirect(response, `/chat?said=${encodeURIComponent("Type a request first.")}`);
|
|
5996
|
+
const asked = lead.ask(message, now);
|
|
5997
|
+
return redirect(response, `/chat#demo-${asked.id}`);
|
|
5998
|
+
}
|
|
5999
|
+
const id = Number(demoAct[1]);
|
|
6000
|
+
const note = body.get("note") ?? "";
|
|
6001
|
+
const done = demoAct[2] === "approve" ? lead.approve(id, now)
|
|
6002
|
+
: demoAct[2] === "change" ? lead.change(id, note, now)
|
|
6003
|
+
: demoAct[2] === "revise" ? lead.requestChanges(id, note, now)
|
|
6004
|
+
: lead.complete(id, now, (taskId, runId) => {
|
|
6005
|
+
const principal = matePrincipal(who);
|
|
6006
|
+
if (principal === null)
|
|
6007
|
+
return { ok: false, message: "Your access changed. Sign in again." };
|
|
6008
|
+
const current = assignmentOf(store, taskId, now, { principal: "operator", repos: principal.repos }, evidenceRoot);
|
|
6009
|
+
if (current?.receipt?.runId !== runId)
|
|
6010
|
+
return { ok: false, message: "This result changed. Reload before marking it complete." };
|
|
6011
|
+
const completed = checkAssignmentAsOperator(store, taskId, current.receipt.digest, principal, now, evidenceRoot);
|
|
6012
|
+
return completed.ok ? { ok: true } : { ok: false, message: completed.message };
|
|
6013
|
+
});
|
|
6014
|
+
bustBadge();
|
|
6015
|
+
if (!done.ok)
|
|
6016
|
+
return redirect(response, `/chat?said=${encodeURIComponent(done.message)}`);
|
|
6017
|
+
const latest = lead.exchanges().at(-1);
|
|
6018
|
+
return redirect(response, demoAct[2] === "change" || demoAct[2] === "revise" ? `/chat#demo-${latest?.id ?? id}` : `/chat#demo-${id}-work`);
|
|
6019
|
+
}
|
|
5925
6020
|
// A person says a provider's sign-in works again: its sign-in pause
|
|
5926
6021
|
// lifts, its tasks may start, and one short message says so.
|
|
5927
6022
|
const resumeProvider = /^\/providers\/([a-z]+)\/resume$/.exec(url.pathname);
|
|
@@ -10220,7 +10315,7 @@ export function createDecisionServer(options) {
|
|
|
10220
10315
|
note = validated.note;
|
|
10221
10316
|
}
|
|
10222
10317
|
store.acceptProof(latest.id, verifiedAuthor(who.name), note, now);
|
|
10223
|
-
return redirect(response, taskHref(taskId));
|
|
10318
|
+
return redirect(response, body.get("return") === null ? taskHref(taskId) : safeReturn(body.get("return")));
|
|
10224
10319
|
}
|
|
10225
10320
|
case "complete": {
|
|
10226
10321
|
if (who.via !== "cookie" || who.role !== "approver")
|
|
@@ -11249,7 +11344,7 @@ function criterionMatrixHtml(matrix, options = {}) {
|
|
|
11249
11344
|
const state = row.state;
|
|
11250
11345
|
const awaitingAssessment = row.assessment?.evidenceState === "pass" && row.review === null;
|
|
11251
11346
|
const confirmed = row.assessment !== undefined && state === "pass";
|
|
11252
|
-
const label = awaitingAssessment ? "Not assessed" : confirmed ? "Confirmed" : state === "pass" ? "
|
|
11347
|
+
const label = awaitingAssessment ? "Not assessed" : confirmed ? "Confirmed" : state === "pass" ? "Met" : state === "manual-review" ? "You check" : state === "missing" ? "Not shown yet" : "Not met";
|
|
11253
11348
|
const cls = awaitingAssessment ? "" : state === "pass" ? "badge-done" : state === "manual-review" ? "badge-manual-review" : "badge-failed";
|
|
11254
11349
|
const warnings = [];
|
|
11255
11350
|
// Only replace the known boilerplate. Other recorded failure details
|
|
@@ -11648,6 +11743,8 @@ function agentsStripHtml(view, taskId) {
|
|
|
11648
11743
|
return `<p class="task-chat-agents">${escape(agentsSummaryWords(view))} <a href="${taskHref(taskId)}#agents">Review agent setup</a></p>`;
|
|
11649
11744
|
return `<details class="task-chat-agents"><summary>Agent setup</summary><p>${escape(agentsSummaryWords(view))}</p><a href="${taskHref(taskId)}#agents">View agents</a></details>`;
|
|
11650
11745
|
}
|
|
11746
|
+
/** The demo's one-line promise, on every page. */
|
|
11747
|
+
const DEMO_BANNER = "Demo: a scripted lead and sample projects. Nothing calls a model, reaches outside or spends.";
|
|
11651
11748
|
/** Every character that could open a tag or an attribute, dead at the sink. */
|
|
11652
11749
|
function escape(text) {
|
|
11653
11750
|
return text
|
|
@@ -12599,6 +12696,66 @@ ${THEME_DARK}
|
|
|
12599
12696
|
padding: .375rem .9rem; font-size: .8125rem; color: var(--muted-foreground);
|
|
12600
12697
|
}
|
|
12601
12698
|
.banner .badge { margin-right: .5rem; }
|
|
12699
|
+
/* Chat in \`toolroll demo\`: the scripted lead's conversation. */
|
|
12700
|
+
.demo-chat { display: flex; flex-direction: column; gap: 1rem; max-width: 46rem; margin: 0 auto; padding-bottom: 1rem; }
|
|
12701
|
+
.demo-thread { display: flex; flex-direction: column; gap: 1.25rem; }
|
|
12702
|
+
.demo-turn { display: flex; flex-direction: column; gap: .75rem; scroll-margin-top: 1rem; }
|
|
12703
|
+
.demo-said p { margin: 0; }
|
|
12704
|
+
.demo-you { align-self: flex-end; max-width: min(34rem, 85%); background: var(--so-ink); color: var(--so-paper); border-radius: .875rem .875rem .25rem .875rem; padding: .55rem .85rem; overflow-wrap: anywhere; }
|
|
12705
|
+
.demo-lead { max-width: 40rem; }
|
|
12706
|
+
.demo-who { font-size: .75rem; font-weight: 600; color: var(--so-muted); margin-bottom: .15rem !important; }
|
|
12707
|
+
.demo-chat .card { margin: 0; padding: 1rem 1.125rem; }
|
|
12708
|
+
.demo-chat .card h2 { font-size: .9375rem; margin: .1rem 0 .4rem; }
|
|
12709
|
+
.demo-chat .card h3 { font-size: .75rem; font-weight: 600; color: var(--so-muted); margin: .75rem 0 .25rem; }
|
|
12710
|
+
.demo-chat .card ul { margin: 0; padding-left: 1.1rem; }
|
|
12711
|
+
.demo-chat .card li { margin: .15rem 0; }
|
|
12712
|
+
.demo-chat .card > p { margin: .25rem 0; }
|
|
12713
|
+
.demo-kicker { font-size: .75rem; color: var(--so-muted); margin: 0 !important; }
|
|
12714
|
+
.demo-actions { display: flex; flex-wrap: wrap; align-items: flex-start; gap: .5rem; margin-top: .9rem; }
|
|
12715
|
+
.demo-actions form { margin: 0; }
|
|
12716
|
+
.demo-actions button, .demo-more > summary { min-height: 2.5rem; }
|
|
12717
|
+
.demo-chat button.demo-primary { background: var(--primary); color: var(--primary-foreground); border-color: var(--primary); font-weight: 600; }
|
|
12718
|
+
.demo-chat details.demo-more { flex: 1 1 12rem; margin: 0; padding: 0; border: 0; border-radius: 0; background: none; }
|
|
12719
|
+
.demo-chat details.demo-more[open] { padding-bottom: 0; }
|
|
12720
|
+
.demo-chat .demo-more > summary { display: inline-flex; align-items: center; color: var(--so-ink); padding: .35rem .85rem; border: 1px solid var(--so-input-line); border-radius: calc(var(--radius) - 3px); background: var(--so-paper); font-weight: 500; font-size: .8125rem; cursor: pointer; list-style: none; }
|
|
12721
|
+
.demo-more > summary::-webkit-details-marker { display: none; }
|
|
12722
|
+
.demo-more[open] > summary { margin-bottom: .5rem; }
|
|
12723
|
+
.demo-more form, .demo-composer { display: flex; flex-direction: column; gap: .4rem; }
|
|
12724
|
+
.demo-more label { font-size: .8125rem; font-weight: 500; }
|
|
12725
|
+
.demo-more textarea, .demo-composer textarea { width: 100%; font: inherit; font-size: 1rem; resize: vertical; }
|
|
12726
|
+
.demo-more form button { align-self: flex-start; }
|
|
12727
|
+
.demo-steps { display: flex; flex-wrap: wrap; gap: .4rem 1rem; list-style: none; padding: 0 !important; margin: .4rem 0 !important; }
|
|
12728
|
+
.demo-steps li { display: inline-flex; align-items: center; gap: .4rem; color: var(--so-muted); font-size: .8125rem; margin: 0 !important; }
|
|
12729
|
+
.demo-steps li::before { content: ""; width: .55rem; height: .55rem; border-radius: 50%; border: 1.5px solid currentColor; }
|
|
12730
|
+
.demo-steps li.done { color: var(--so-ink); }
|
|
12731
|
+
.demo-steps li.done::before { background: var(--so-success); border-color: var(--so-success); }
|
|
12732
|
+
.demo-steps li.now { color: var(--so-ink); font-weight: 600; }
|
|
12733
|
+
.demo-steps li.now::before { border-color: var(--so-info); background: var(--so-info); animation: demo-pulse 1.2s ease-in-out infinite; }
|
|
12734
|
+
@keyframes demo-pulse { 50% { opacity: .35; } }
|
|
12735
|
+
@media (prefers-reduced-motion: reduce) { .demo-steps li.now::before { animation: none; } }
|
|
12736
|
+
.demo-state { display: flex; align-items: center; gap: .5rem; margin: 0 !important; }
|
|
12737
|
+
.demo-chat .badge.demo-ready { background: var(--so-info-soft); color: var(--so-info); }
|
|
12738
|
+
.demo-chat .badge.demo-complete { background: var(--so-success-soft); color: var(--so-success); }
|
|
12739
|
+
.demo-pass { color: var(--so-success); font-weight: 500; }
|
|
12740
|
+
.demo-fail { color: var(--so-danger); font-weight: 500; }
|
|
12741
|
+
.demo-chat details.demo-evidence { margin: .5rem 0 0; padding: 0; border: 0; border-top: 1px solid var(--so-line); border-radius: 0; background: none; box-shadow: none; }
|
|
12742
|
+
.demo-evidence > summary { min-height: 2.5rem; display: list-item; padding-block: .55rem; cursor: pointer; font-weight: 500; font-size: .8125rem; }
|
|
12743
|
+
.demo-evidence pre { margin: 0 0 .5rem; max-height: 22rem; overflow: auto; padding: .6rem .75rem; border-radius: .5rem; background: var(--so-raised); font: 400 .75rem/1.55 var(--font-mono); white-space: pre; overflow-wrap: normal; }
|
|
12744
|
+
.demo-diff span { display: block; min-width: max-content; }
|
|
12745
|
+
.demo-diff-add { background: var(--so-success-soft); color: var(--so-success); }
|
|
12746
|
+
.demo-diff-del { background: var(--so-danger-soft); color: var(--so-danger); }
|
|
12747
|
+
.demo-diff-hunk, .demo-diff-meta { color: var(--so-muted); }
|
|
12748
|
+
.demo-diff-file { font-weight: 600; }
|
|
12749
|
+
.demo-evidence figure { margin: 0 0 .5rem; }
|
|
12750
|
+
.demo-evidence img { display: block; width: 100%; height: auto; border: 1px solid var(--so-line); border-radius: .5rem; }
|
|
12751
|
+
.demo-evidence figcaption { margin-top: .35rem; }
|
|
12752
|
+
.demo-hint { padding: 1.25rem 0 .25rem; }
|
|
12753
|
+
.demo-hint-title { font-size: .9375rem; font-weight: 600; margin: 0 0 .75rem; }
|
|
12754
|
+
.demo-suggestions { display: flex; flex-wrap: wrap; gap: .5rem; margin: 0; }
|
|
12755
|
+
.demo-suggestions button { min-height: 2.5rem; border-radius: 999px; padding: .35rem .95rem; }
|
|
12756
|
+
.demo-composer { position: sticky; bottom: 0; padding: .75rem 0 .25rem; background: var(--so-paper); flex-direction: row; align-items: flex-end; }
|
|
12757
|
+
.demo-chat .demo-composer textarea { flex: 1 1 auto; width: auto; min-width: 0; min-height: 2.75rem; }
|
|
12758
|
+
.demo-chat .demo-composer button { flex: none; width: auto; min-height: 2.75rem; padding-inline: 1.1rem; }
|
|
12602
12759
|
.banner a { color: var(--muted-foreground); }
|
|
12603
12760
|
.sign-in-banner form { display: inline; margin: 0 0 0 .5rem; padding: 0; border: 0; background: transparent; }
|
|
12604
12761
|
|
|
@@ -13024,6 +13181,9 @@ ${THEME_DARK}
|
|
|
13024
13181
|
.result-notes .recap { margin: .25rem 0 .4rem; font-size: .875rem; }
|
|
13025
13182
|
.result-attention { margin: .75rem 0 .25rem; padding: .7rem .85rem; border-left: 1px solid var(--warning); border-radius: 0 calc(var(--radius) - 3px) calc(var(--radius) - 3px) 0; background: var(--warning-soft); font-size: .8125rem; }
|
|
13026
13183
|
.result-attention strong { display: block; font-size: .78rem; }
|
|
13184
|
+
.result-you-check { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem .75rem; margin: .75rem 0; padding: .65rem .85rem; border-radius: calc(var(--radius) - 3px); background: var(--muted); font-size: .8125rem; }
|
|
13185
|
+
.result-you-check ul { flex: 1 1 14rem; min-width: 0; margin: 0; padding: 0; list-style: none; }
|
|
13186
|
+
.result-you-check form { margin: 0; }
|
|
13027
13187
|
.result-attention ul { margin: .3rem 0 0; padding-left: 1.15rem; }
|
|
13028
13188
|
.result-attention li { margin: .15rem 0; overflow-wrap: anywhere; }
|
|
13029
13189
|
.result-tabs { display: flex; gap: .25rem; margin: .9rem 0 .75rem; border-bottom: 1px solid var(--border); overflow-x: auto; scrollbar-width: none; }
|
|
@@ -14754,7 +14914,7 @@ function shell(title, body, options = {}) {
|
|
|
14754
14914
|
// must not pass as production (adoption review, finding 8; the FENCE is
|
|
14755
14915
|
// the refuseDemo gate in operate.ts, this is the honest label).
|
|
14756
14916
|
const demoBanner = (chrome.demo === true
|
|
14757
|
-
? `<div class="banner"><span class="badge">
|
|
14917
|
+
? `<div class="banner"><span class="badge">demo</span>${escape(DEMO_BANNER.replace(/^Demo: /, ""))}</div>`
|
|
14758
14918
|
: "") +
|
|
14759
14919
|
(chrome.modeBanner === undefined
|
|
14760
14920
|
? ""
|
|
@@ -18449,7 +18609,7 @@ function milestoneProgressHtml(milestones) {
|
|
|
18449
18609
|
.map(one => `<li class="milestone milestone-${one.state}"><span class="milestone-badge">${milestoneStateWord(one.state)}</span> ` +
|
|
18450
18610
|
`${escape(one.description)}${one.note === null ? "" : ` <span class="meta">— ${escape(one.note)}</span>`}</li>`)
|
|
18451
18611
|
.join("\n") +
|
|
18452
|
-
`</ul><p class="meta milestone-progress-note">Live checkpoints from the agent
|
|
18612
|
+
`</ul><p class="meta milestone-progress-note">Live checkpoints from the agent.</p></section>`);
|
|
18453
18613
|
}
|
|
18454
18614
|
/** Adaptive execution plans (v44): the current plan revision's own reason
|
|
18455
18615
|
* and evidence, any revision still awaiting an operator's accept/reject,
|
|
@@ -18726,7 +18886,7 @@ function taskBodyParts(data) {
|
|
|
18726
18886
|
return `<details class="dispatch-proof-details"><summary>Previous assessment</summary><div class="dispatch-proof-body">` +
|
|
18727
18887
|
`<p class="meta">Saved assessment history. Current task status and checks are shown above.</p>` +
|
|
18728
18888
|
(accepted === null ? "" : `<p class="meta">Accepted with an exception by ${escape(accepted.approver)}. Check results are unchanged.${accepted.note === null ? "" : ` ${escape(accepted.note)}`}</p>`) +
|
|
18729
|
-
(proof.proofReasons.length === 0 ? "" : `<ul>${proof.proofReasons.map(reason => `<li>${escape(reason)}</li>`).join("")}</ul>`) +
|
|
18889
|
+
(proof.proofReasons.length === 0 ? "" : `<ul>${[...new Set(proof.proofReasons.map(plainReasonWords))].map(reason => `<li>${escape(reason)}</li>`).join("")}</ul>`) +
|
|
18730
18890
|
criterionMatrixHtml(proof.proofMatrix, { compact: true, runId: proof.runId, links: proof.proofMatrixLinks }) +
|
|
18731
18891
|
semanticCoverageHtml(proof.proofMatrix, proof.qualityMode ?? "default") + `</div></details>`;
|
|
18732
18892
|
}
|
|
@@ -19981,11 +20141,14 @@ function verificationExplanation(verdict, reasons) {
|
|
|
19981
20141
|
if (reason === "the sealed diff is unavailable or truncated; the claimed changed paths cannot be verified against it") {
|
|
19982
20142
|
return "The recorded changes were incomplete, so they could not be verified.";
|
|
19983
20143
|
}
|
|
20144
|
+
const plainWords = plainReasonWords(reason);
|
|
20145
|
+
if (plainWords !== reason)
|
|
20146
|
+
return plainWords;
|
|
19984
20147
|
const sentence = reason.trim();
|
|
19985
20148
|
return sentence === "" ? "" : `${sentence[0]?.toUpperCase() ?? ""}${sentence.slice(1)}${/[.!?]$/.test(sentence) ? "" : "."}`;
|
|
19986
20149
|
}).filter(Boolean);
|
|
19987
20150
|
if (plain.length > 0)
|
|
19988
|
-
return plain.join(" ");
|
|
20151
|
+
return [...new Set(plain)].join(" ");
|
|
19989
20152
|
if (verdict === "verified")
|
|
19990
20153
|
return "Toolroll independently verified this result.";
|
|
19991
20154
|
if (verdict === "attested")
|
|
@@ -20940,8 +21103,8 @@ function completionReceiptCard(view, taskId, place, standing = receiptStatusOf(v
|
|
|
20940
21103
|
(view.diff.binaryCount > 0 ? ` · ${view.diff.binaryCount} binary` : "") +
|
|
20941
21104
|
(view.diff.filesTruncated ? " · list shortened" : "");
|
|
20942
21105
|
const criteria = facts.checks === null
|
|
20943
|
-
? "No
|
|
20944
|
-
: `${facts.checks.passed}/${facts.checks.total}
|
|
21106
|
+
? "No requirements set"
|
|
21107
|
+
: `${facts.checks.passed}/${facts.checks.total} requirements met`;
|
|
20945
21108
|
// The deliverable leads (package 3): a scout's report by its title, UI
|
|
20946
21109
|
// work by its validated screenshots. Unverifiable shots are counted as
|
|
20947
21110
|
// unavailable, never shown, never called validated.
|
|
@@ -20962,7 +21125,7 @@ function completionReceiptCard(view, taskId, place, standing = receiptStatusOf(v
|
|
|
20962
21125
|
const attention = current === null ? [...facts.evidenceProblems, ...view.caveats] : [];
|
|
20963
21126
|
const caveats = attention.length === 0
|
|
20964
21127
|
? ""
|
|
20965
|
-
: `<div class="receipt-caveats" data-result-attention="${attention.length}"><strong>Before you move on</strong><ul>${attention.map(one => `<li>${escape(one)}</li>`).join("")}</ul></div>`;
|
|
21128
|
+
: `<div class="receipt-caveats" data-result-attention="${attention.length}"><strong>Before you move on</strong><ul>${attention.map(one => `<li>${escape(plainReasonWords(one))}</li>`).join("")}</ul></div>`;
|
|
20966
21129
|
// v51: semantic coverage, distinct from the machine proof word above —
|
|
20967
21130
|
// the same lines the CLI prints, so chat and terminal cannot disagree.
|
|
20968
21131
|
// Nothing owed (optional and unsettled, or satisfied) folds behind a
|
|
@@ -20994,7 +21157,7 @@ function completionReceiptCard(view, taskId, place, standing = receiptStatusOf(v
|
|
|
20994
21157
|
(inReview ? `<details class="receipt-history"><summary>Review history</summary><p class="receipt-review meta" data-receipt-review="${escape(status.token)}">${escape(status.detail)}</p></details>` : "") +
|
|
20995
21158
|
// The matrix count is the proof's own citation; it reads as verified
|
|
20996
21159
|
// only when the machine verified the result (workspace package 1).
|
|
20997
|
-
`<div class="receipt-facts"><span><strong>${escape(criteria)}</strong><small>${stored.tone === "problem" ? "
|
|
21160
|
+
`<div class="receipt-facts"><span><strong>${escape(criteria)}</strong><small>${stored.tone === "problem" ? "the agent's own claim — not checked" : stored.token === "agent-attested" ? "the agent's own claim, no project check" : "against the approved scope"}</small></span>` +
|
|
20998
21161
|
`<span><strong>${escape(diff)}</strong><small>${facts.head === null ? "no commit recorded" : `head ${escape(facts.head.slice(0, 12))} · ${escape(facts.headSource ?? "")}`}</small></span>` +
|
|
20999
21162
|
`<span><strong>${view.screenshots.length} screenshot${view.screenshots.length === 1 ? "" : "s"}</strong><small>${view.screenshots.length === 0 ? "none required or captured" : unavailable === 0 ? "validated visual proof" : `${unavailable} unavailable — not validated`}</small></span></div>` +
|
|
21000
21163
|
`<div class="receipt-actions">` +
|
|
@@ -21080,9 +21243,25 @@ function resultPanelParts(detail, o) {
|
|
|
21080
21243
|
}
|
|
21081
21244
|
if (detail.ciFailing && publication !== null)
|
|
21082
21245
|
attention.push(`CI is failing on PR #${publication.prNumber} at the last check.`);
|
|
21246
|
+
// Requirements only a person can confirm: plain words and one Accept,
|
|
21247
|
+
// in neutral ink — nothing failed. Accepting records the person's
|
|
21248
|
+
// decision and leaves the recorded checks as they are.
|
|
21249
|
+
const personChecks = proof === null || proof.accepted !== null ? [] : [
|
|
21250
|
+
...proof.matrix.filter(row => row.state === "manual-review").map(row => personCheckWords(row.statement)),
|
|
21251
|
+
...(proof.matrix.length > 0 ? [] : proof.reasons.filter(reason => manualReviewCriterionOf(reason) !== null).map(() => personCheckWords(null))),
|
|
21252
|
+
];
|
|
21253
|
+
const acceptable = personChecks.length > 0 && humanReview && o.csrf !== "" && (current != null || detail.assignment == null);
|
|
21254
|
+
const youCheck = personChecks.length === 0 ? null : {
|
|
21255
|
+
lines: [...new Set(personChecks)],
|
|
21256
|
+
accept: acceptable ? { action: `${taskHref(detail.taskId)}/accept-proof`, run: run.id, returnTo: o.returnTo } : null,
|
|
21257
|
+
};
|
|
21258
|
+
const youCheckHtml = youCheck === null ? "" :
|
|
21259
|
+
`<div class="result-you-check" data-result-you-check="${youCheck.lines.length}"><ul>${youCheck.lines.map(one => `<li>${escape(one)}</li>`).join("")}</ul>` +
|
|
21260
|
+
(youCheck.accept === null ? "" : `<form method="post" action="${escape(youCheck.accept.action)}"><input type="hidden" name="csrf" value="${escape(o.csrf)}"><input type="hidden" name="run" value="${youCheck.accept.run}"><input type="hidden" name="return" value="${escape(youCheck.accept.returnTo)}"><button type="submit" data-accept-result>Accept</button></form>`) +
|
|
21261
|
+
`</div>`;
|
|
21083
21262
|
const attentionHtml = attention.length === 0
|
|
21084
21263
|
? ""
|
|
21085
|
-
: `<div class="result-attention" data-result-attention="${attention.length}"><ul>${attention.map(one => `<li>${escape(one)}</li>`).join("")}</ul></div>`;
|
|
21264
|
+
: `<div class="result-attention" data-result-attention="${attention.length}"><ul>${attention.map(one => `<li>${escape(plainReasonWords(one))}</li>`).join("")}</ul></div>`;
|
|
21086
21265
|
// ---- one outcome, one action (repair 2026-09-14) ------------------------
|
|
21087
21266
|
// The outcome is the handoff's first sentence, bounded; the agent's full
|
|
21088
21267
|
// account waits behind a disclosure in Summary so nothing is said twice.
|
|
@@ -21343,9 +21522,10 @@ function resultPanelParts(detail, o) {
|
|
|
21343
21522
|
(current == null || o.place === "review" && (current.state === "ready-to-check" || current.state === "complete") ? "" :
|
|
21344
21523
|
`<div class="verdict" data-current-outcome>${verdictChipsHtml(current, facts)}` +
|
|
21345
21524
|
(current.receipt?.checks.status === "failed" || current.receipt?.checks.status === "unavailable" ? `<p class="problem">${escape(current.detail)}</p>` : "") + `</div>`) +
|
|
21525
|
+
attentionHtml +
|
|
21526
|
+
youCheckHtml +
|
|
21346
21527
|
action +
|
|
21347
21528
|
(REVIEW_TOKENS.has(status.token) ? `<details class="receipt-history"><summary>Review history</summary><p class="receipt-review meta" data-receipt-review="${escape(status.token)}">${escape(status.detail)}</p></details>` : "") +
|
|
21348
|
-
attentionHtml +
|
|
21349
21529
|
tabs +
|
|
21350
21530
|
view("summary", summaryParts) +
|
|
21351
21531
|
view("changes", changeParts) +
|
|
@@ -21358,7 +21538,8 @@ function resultPanelParts(detail, o) {
|
|
|
21358
21538
|
heading, outcome,
|
|
21359
21539
|
verdict: current == null ? null : verdictChipsOf(current, facts),
|
|
21360
21540
|
reviewHistory: REVIEW_TOKENS.has(status.token) ? status.detail : null,
|
|
21361
|
-
attention: attention.filter(one => !shortenedMaterialReason(one)),
|
|
21541
|
+
attention: attention.filter(one => !shortenedMaterialReason(one)).map(plainReasonWords),
|
|
21542
|
+
youCheck,
|
|
21362
21543
|
limits: attention.filter(one => shortenedMaterialReason(one)),
|
|
21363
21544
|
tabs: RESULT_TABS.map(tab => ({ key: tab.key, label: tab.key === "checks" && proof?.matrix.some(row => row.assessment !== undefined) ? "Requirements" : tab.label, count: tabCounts[tab.key], href: o.hrefFor(tab.key), active: tab.key === o.tab })),
|
|
21364
21545
|
views: [{ key: "summary", html: summaryParts.join("\n") }, { key: "changes", html: changeParts.join("\n") }, { key: "checks", html: checkParts.join("\n") }],
|
package/dist/store.d.ts
CHANGED
|
@@ -6063,6 +6063,7 @@ export declare class Store {
|
|
|
6063
6063
|
pendingPublications(): Publication[];
|
|
6064
6064
|
publicationForRun(run: number): Publication | null;
|
|
6065
6065
|
markPublicationPushed(id: number, now: Date): void;
|
|
6066
|
+
/** A PR that exists is recorded as opened, even if a concurrent pass gave up on the row meanwhile: it truly opened. */
|
|
6066
6067
|
markPublicationOpened(id: number, prNumber: number, prUrl: string, now: Date): void;
|
|
6067
6068
|
/** One more failed attempt, error kept. Returns the new count; the caller decides when enough is enough. */
|
|
6068
6069
|
recordPublicationError(id: number, error: string, now: Date): number;
|
package/dist/store.js
CHANGED
|
@@ -18509,9 +18509,10 @@ export class Store {
|
|
|
18509
18509
|
.prepare("UPDATE publication SET state = 'pushed', updated_at = ? WHERE id = ? AND state = 'intended'")
|
|
18510
18510
|
.run(now.toISOString(), id);
|
|
18511
18511
|
}
|
|
18512
|
+
/** A PR that exists is recorded as opened, even if a concurrent pass gave up on the row meanwhile: it truly opened. */
|
|
18512
18513
|
markPublicationOpened(id, prNumber, prUrl, now) {
|
|
18513
18514
|
this.db
|
|
18514
|
-
.prepare("UPDATE publication SET state = 'opened', pr_number = ?, pr_url = ?, updated_at = ? WHERE id = ? AND state
|
|
18515
|
+
.prepare("UPDATE publication SET state = 'opened', pr_number = ?, pr_url = ?, updated_at = ? WHERE id = ? AND state IN ('pushed','failed')")
|
|
18515
18516
|
.run(prNumber, prUrl, now.toISOString(), id);
|
|
18516
18517
|
}
|
|
18517
18518
|
/** One more failed attempt, error kept. Returns the new count; the caller decides when enough is enough. */
|
package/dist/workspace-ui.js
CHANGED
|
@@ -187,7 +187,7 @@ function storedResultStatusOf(result, publication) {
|
|
|
187
187
|
label: "Result saved, but its record does not match",
|
|
188
188
|
// A structural refutation is settled before the approved check is
|
|
189
189
|
// weighed, so it says nothing about whether that check passed.
|
|
190
|
-
detail: withPublication("
|
|
190
|
+
detail: withPublication("What the agent reported doesn't match the changes it saved. This doesn't say whether the project check passed."),
|
|
191
191
|
tone: "problem",
|
|
192
192
|
action: { label: "Open the record", kind: "open-review" },
|
|
193
193
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolroll",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.5",
|
|
4
4
|
"description": "A control plane for unattended coding agents — queue tasks, walk away, come back to pull requests. Agents build in leased worktrees, never touch your default branch, and interrupt you only for decisions that need a human.",
|
|
5
5
|
"directories": {
|
|
6
6
|
"doc": "docs"
|