toolroll 0.9.11 → 0.9.12
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 +18 -0
- package/dist/assignment.d.ts +3 -0
- package/dist/assignment.js +3 -1
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +20 -18
- package/dist/browser-workspace.d.ts +61 -0
- package/dist/chat-delivery.js +17 -0
- package/dist/chat-quiet.js +1 -0
- package/dist/chat-voice.d.ts +3 -1
- package/dist/chat-voice.js +13 -5
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +1 -0
- package/dist/coding-update.d.ts +13 -0
- package/dist/coding-update.js +68 -0
- package/dist/desktop-update.d.ts +7 -0
- package/dist/desktop-update.js +42 -1
- package/dist/flow-triggers.js +17 -1
- package/dist/guides.js +9 -0
- package/dist/lead-status.d.ts +10 -1
- package/dist/lead-status.js +13 -3
- package/dist/lead-voice.d.ts +81 -0
- package/dist/lead-voice.js +275 -0
- package/dist/needs-you.d.ts +7 -0
- package/dist/needs-you.js +9 -0
- package/dist/operate.js +80 -4
- package/dist/serve.d.ts +5 -1
- package/dist/serve.js +438 -224
- package/dist/setup-guide.d.ts +2 -2
- package/dist/store.js +8 -0
- package/dist/surface.js +8 -1
- package/dist/task-status.d.ts +6 -0
- package/dist/task-status.js +19 -2
- package/dist/team-cli.js +1 -1
- package/dist/telegram-progress.js +7 -0
- package/dist/telegram.js +34 -3
- package/dist/test-temp.js +1 -1
- package/dist/toolroll-update.d.ts +3 -0
- package/dist/toolroll-update.js +13 -2
- package/dist/work-index.d.ts +9 -0
- package/dist/work-index.js +57 -20
- package/dist/work-summary.d.ts +4 -1
- package/dist/workspace-ui.d.ts +25 -0
- package/dist/workspace-ui.js +20 -1
- package/package.json +1 -1
package/dist/serve.js
CHANGED
|
@@ -62,6 +62,7 @@ import { teamWorkspaceHtml } from './team-ui.js';
|
|
|
62
62
|
import { createTeamRuntime } from './team-runtime.js';
|
|
63
63
|
import { prepareWorkspaceRevision, WorkspaceValidatorCache } from "./workspace-revision.js";
|
|
64
64
|
import { workIndexPage, workCountsByProject, WorkIndexCursorError, WORK_INDEX_MAX_LIMIT } from "./work-index.js";
|
|
65
|
+
import { leadActivity } from "./lead-voice.js";
|
|
65
66
|
import { openWorkDecisionOf } from "./work-summary.js";
|
|
66
67
|
import { assignmentOf, checkAssignmentAsOperator } from './assignment.js';
|
|
67
68
|
import { demoChatHtml, demoThreadHtml, DEMO_CHAT_SCRIPT } from "./demo-chat.js";
|
|
@@ -73,7 +74,7 @@ import { leadBriefHtml, LEAD_CONTEXT_CSS } from './lead-context.js';
|
|
|
73
74
|
import { assignmentCatchUp } from './assignment-brief.js';
|
|
74
75
|
import { assignmentActionHref, assignmentCardOf, assignmentStatusOf, assignmentSummaryHtml, assignmentWithEvidence, ASSIGNMENT_CSS } from './assignment-ui.js';
|
|
75
76
|
import { assignmentStageOf, pullRequestFactOf, stageOfCode, stageOfDispatch, statusDetailsHtml, statusIconSvg, statusWhyHtml, taskStatusOf, TASK_STATUS_CSS } from './task-status.js';
|
|
76
|
-
import { NEEDS } from './needs-you.js';
|
|
77
|
+
import { ASKS, ASK_LABEL, NEEDS } from './needs-you.js';
|
|
77
78
|
import { assignmentPresentationOf, historicalAssessmentReason, shortenedMaterialReason } from './assignment-presentation.js';
|
|
78
79
|
import { ledgerBody } from "./ledger-view.js";
|
|
79
80
|
import { EVIDENCE_PACK_CSS, evidencePack, evidencePackHtml, exportDay, ledgerExportChunks } from "./evidence-pack.js";
|
|
@@ -162,7 +163,7 @@ import { starterRecipes, savedRecipes, findRecipe, importRecipe, exportRecipe, c
|
|
|
162
163
|
import { recipeFromForm, recipeLibraryHtml, recipeEditorHtml, workflowPreviewHtml, recipeScript, RECIPE_CSS, recipeDefinitionPreviewHtml, recipeRunHtml, recipeAnswersFromForm } from "./recipe-ui.js";
|
|
163
164
|
import { EVIDENCE_CAPS, readVerifiedArtifact, readVerifiedReport, readVerifiedProofForRun, storeEvidence, writeEvidenceFile, scanForSecrets } from "./evidence.js";
|
|
164
165
|
import { GOAL_ASSESSMENT_PENDING, reviewConflict, manualReviewOnly, manualReviewCriterionOf, personCheckWords, plainReasonWords, dispatchStatusToken, passFraction, semanticCoverage, coverageWords, coverageStateWords } from "./proof.js";
|
|
165
|
-
import { WORK_VIEWS, REVIEW_TOKENS, parseWorkView, resultStatusOf, resultHeadlineOf, receiptHeadingOf, receiptPublicationWords, reviewFactsOf, workStatusOf, primaryDestinationOf, needsPerson, dispatchActionLabel, } from "./workspace-ui.js";
|
|
166
|
+
import { WORK_VIEWS, REVIEW_TOKENS, RESULT_DECISION_SENTENCE, acceptWordsOf, parseWorkView, resultStatusOf, resultHeadlineOf, receiptHeadingOf, receiptPublicationWords, reviewFactsOf, workStatusOf, primaryDestinationOf, needsPerson, dispatchActionLabel, } from "./workspace-ui.js";
|
|
166
167
|
import { PRICED_BUILD_MODELS } from "./pricing.js";
|
|
167
168
|
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";
|
|
168
169
|
import { parseReport } from "./scout-report.js";
|
|
@@ -408,6 +409,8 @@ class PersistentSessions extends Map {
|
|
|
408
409
|
}
|
|
409
410
|
hashOf(session) { const id = this.ids.get(session); return id === undefined ? null : PersistentSessions.hash(id); }
|
|
410
411
|
}
|
|
412
|
+
/** How long a stop waits for teammates and the lead's follow pass before closing anyway. */
|
|
413
|
+
export const SHUTDOWN_WAIT_MS = 5_000;
|
|
411
414
|
export function createDecisionServer(options) {
|
|
412
415
|
const { store, evidenceRoot } = options;
|
|
413
416
|
let coding = options.codingWorkspace ?? null;
|
|
@@ -2026,7 +2029,7 @@ export function createDecisionServer(options) {
|
|
|
2026
2029
|
needsVerification: store
|
|
2027
2030
|
.listCompletedWorkScoped(project, 10, admission)
|
|
2028
2031
|
.filter(one => visible(one.repo) && (one.proofVerdict === "short" || one.proofVerdict === "refuted") && !one.proofAccepted &&
|
|
2029
|
-
assignmentOf(store, one.taskId, now,
|
|
2032
|
+
assignmentOf(store, one.taskId, now, workAccess(), evidenceRoot) === null)
|
|
2030
2033
|
.map(one => ({
|
|
2031
2034
|
taskId: one.taskId,
|
|
2032
2035
|
title: one.title,
|
|
@@ -2364,7 +2367,7 @@ export function createDecisionServer(options) {
|
|
|
2364
2367
|
// Admission binds BEFORE the SQL limit (the done page's own rule),
|
|
2365
2368
|
// and every row is re-proved against the ceiling before ranking.
|
|
2366
2369
|
const resultRow = (row) => {
|
|
2367
|
-
const assignment = assignmentOf(store, row.taskId, now,
|
|
2370
|
+
const assignment = assignmentOf(store, row.taskId, now, workAccess(), evidenceRoot);
|
|
2368
2371
|
return { ...row, proofReasons: row.runId === null ? [] : store.proofVerdictFor(row.runId)?.reasons ?? [], ciFailing: ciFailingFor(row.runId, row.prNumber),
|
|
2369
2372
|
assignment: assignment?.activeTaskId === row.taskId && (assignment.receipt?.runId ?? null) === row.runId ? assignment : null };
|
|
2370
2373
|
};
|
|
@@ -2859,7 +2862,7 @@ export function createDecisionServer(options) {
|
|
|
2859
2862
|
// v103: a task's evidence pack, as a printable page or JSON (the whole family, sealed ledger entries included).
|
|
2860
2863
|
const evidence = matchTaskPath(url.pathname, "/evidence$");
|
|
2861
2864
|
if (evidence !== null) {
|
|
2862
|
-
const access =
|
|
2865
|
+
const access = workAccess();
|
|
2863
2866
|
const family = store.taskFamilyOf(evidence.taskId, access.repos, access.includeUnplaced);
|
|
2864
2867
|
if (family === null || !visible(family.root.repo))
|
|
2865
2868
|
return refuse(response, who, 404, "no such task", "/tasks");
|
|
@@ -2883,7 +2886,7 @@ export function createDecisionServer(options) {
|
|
|
2883
2886
|
const to = new Date(Date.parse(last) + 86_400_000).toISOString();
|
|
2884
2887
|
if (to <= from || Date.parse(to) - Date.parse(from) > 366 * 86_400_000)
|
|
2885
2888
|
return refuse(response, who, 400, "Choose a range of a year or less, ending on or after its start.", "/ledger");
|
|
2886
|
-
const access =
|
|
2889
|
+
const access = workAccess();
|
|
2887
2890
|
store.recordAction({ at: now.toISOString(), actor: who.name, repo: null, taskId: null, runId: null, action: "ledger exported", outcome: "exported", source: "access",
|
|
2888
2891
|
detail: `${from.slice(0, 10)} to ${last.slice(0, 10)}` });
|
|
2889
2892
|
// A piece at a time (a page of entries, or one pack), yielding between them, and stopping if the reader's access
|
|
@@ -3597,7 +3600,7 @@ export function createDecisionServer(options) {
|
|
|
3597
3600
|
if (repos.length > 0) {
|
|
3598
3601
|
try {
|
|
3599
3602
|
fleetSnapshot = store.chatSnapshot(repos, now);
|
|
3600
|
-
const summaries = workIndexPage(store, now, { principal: 'operator', repos, includeUnplaced: false }, { limit: 100 }).items;
|
|
3603
|
+
const summaries = workIndexPage(store, now, { principal: 'operator', repos, includeUnplaced: false, viewer: who.name }, { limit: 100 }).items;
|
|
3601
3604
|
fleetSnapshot.assignmentStates = Object.fromEntries(summaries.flatMap(value => [value.rootId, value.activeTaskId].map(id => [id, { state: value.assignmentState, label: value.status.label, detail: value.status.detail }])));
|
|
3602
3605
|
fleetSnapshot.attentionCount = needsYouBadge(null);
|
|
3603
3606
|
}
|
|
@@ -4891,8 +4894,6 @@ export function createDecisionServer(options) {
|
|
|
4891
4894
|
const pageHtml = s.body;
|
|
4892
4895
|
const extras = s.workspace ?? {};
|
|
4893
4896
|
const notices = [...(extras.notices ?? [])];
|
|
4894
|
-
if (s.chrome.demo)
|
|
4895
|
-
notices.unshift(DEMO_BANNER);
|
|
4896
4897
|
if (s.chrome.modeBanner)
|
|
4897
4898
|
notices.push(s.chrome.modeBanner.words);
|
|
4898
4899
|
if (s.chrome.updateWaiting)
|
|
@@ -4907,10 +4908,12 @@ export function createDecisionServer(options) {
|
|
|
4907
4908
|
notices.push('Crew updates are unavailable. Open Tasks to inspect saved work.');
|
|
4908
4909
|
}
|
|
4909
4910
|
// "Wake me only for these": the navigation carries the count of tasks
|
|
4910
|
-
// waiting on a person
|
|
4911
|
+
// waiting on a person behind its Tasks link — the open project's, or
|
|
4912
|
+
// everything this person may see — so it matches that page's Needs you tab.
|
|
4911
4913
|
let needsYou = 0;
|
|
4912
4914
|
try {
|
|
4913
|
-
needsYou = (requestFacts.workCounts ?? workCountsByProject(store, clock(), workAccess()))
|
|
4915
|
+
needsYou = (requestFacts.workCounts ?? workCountsByProject(store, clock(), workAccess()))
|
|
4916
|
+
.filter(one => s.chrome?.project == null || one.repo === s.chrome.project).reduce((sum, one) => sum + one.totals['needs-you'], 0);
|
|
4914
4917
|
}
|
|
4915
4918
|
catch {
|
|
4916
4919
|
needsYou = 0;
|
|
@@ -4947,6 +4950,7 @@ export function createDecisionServer(options) {
|
|
|
4947
4950
|
projects: browserProjectsOf(s.chrome.projects ?? []), ...crew,
|
|
4948
4951
|
conversation, ...(extras.team ? { team: extras.team } : {}), focus: extras.focus ?? null, result: extras.result ?? null,
|
|
4949
4952
|
catchUpHtml: extras.catchUpHtml ?? '', controlsHtml: extras.controlsHtml ?? '', notices, view: extras.view ?? null,
|
|
4953
|
+
...(s.chrome.demo ? { demo: { text: DEMO_BANNER, short: DEMO_BANNER_SHORT } } : {}),
|
|
4950
4954
|
pageHtml: extras.pageHtml === undefined ? (conversation === null ? pageHtml : null) : extras.pageHtml,
|
|
4951
4955
|
navigation: [...browserNavigationOf(currentPath, s.chrome.project, needsYou), { label: 'Workspace tools', href: '/menu', active: path.pathname === '/menu' }],
|
|
4952
4956
|
chats,
|
|
@@ -5013,7 +5017,7 @@ export function createDecisionServer(options) {
|
|
|
5013
5017
|
* phase, four counts, plan-window use (never dollars: subscriptions don't
|
|
5014
5018
|
* bill per run) and Catch up's items, each tagged with its tab. Reads only. */
|
|
5015
5019
|
function chatHomeOf(who, repos, now) {
|
|
5016
|
-
const access =
|
|
5020
|
+
const access = workAccess();
|
|
5017
5021
|
const admitted = (repo) => repo === null ? visible(null) : repos.includes(repo);
|
|
5018
5022
|
const agents = store.liveRuns(now).filter(run => admitted(run.repo)).map(run => {
|
|
5019
5023
|
const root = familyOf(run.taskId)?.root ?? null;
|
|
@@ -5042,8 +5046,13 @@ export function createDecisionServer(options) {
|
|
|
5042
5046
|
})(),
|
|
5043
5047
|
}));
|
|
5044
5048
|
const windows = who.via === "cookie" && store.isInstanceOperator(who.name) ? limitsView(store.providerLimits(), [], { project: projectName, teammate: id => `Teammate ${id}` }, now) : null;
|
|
5049
|
+
// One line: what this person's lead is doing now and when it last acted.
|
|
5050
|
+
const activity = leadActivity(store, who.name);
|
|
5051
|
+
const leadTask = activity?.taskId == null ? null : store.lookupRef(activity.taskId);
|
|
5052
|
+
const lead = activity === null ? null : { doing: activity.doing, at: activity.at,
|
|
5053
|
+
href: leadTask == null || !admitted(leadTask.repo) ? null : taskHref(familyOf(activity.taskId)?.root.id ?? activity.taskId) };
|
|
5045
5054
|
return {
|
|
5046
|
-
agents, counts, catchUp, allHref: "/work",
|
|
5055
|
+
agents, counts, catchUp, allHref: "/work", ...(lead === null ? {} : { lead }),
|
|
5047
5056
|
planUse: (windows?.tiles ?? []).map(one => ({ name: one.name, window: one.window, percent: one.percent, detail: one.detail, tone: one.tone })),
|
|
5048
5057
|
};
|
|
5049
5058
|
}
|
|
@@ -5141,7 +5150,8 @@ export function createDecisionServer(options) {
|
|
|
5141
5150
|
};
|
|
5142
5151
|
}
|
|
5143
5152
|
function workAccess() {
|
|
5144
|
-
|
|
5153
|
+
// The reader's own lead's claims read "Your lead is on it" (lead-voice.ts); nobody else's.
|
|
5154
|
+
return { principal: 'operator', repos: admissionList(), includeUnplaced: visible(null), viewer: requestContext.getStore()?.actor ?? null };
|
|
5145
5155
|
}
|
|
5146
5156
|
// All project counts are read together. No task artifacts or processes are
|
|
5147
5157
|
// inspected to paint a badge or a project switcher.
|
|
@@ -5272,7 +5282,7 @@ export function createDecisionServer(options) {
|
|
|
5272
5282
|
if (earlierLive.length > 0 && !status.views.includes("running"))
|
|
5273
5283
|
status.views = [...status.views, "running"];
|
|
5274
5284
|
const otherActive = new Set([...(task.family?.otherActive.map(one => one.id) ?? []), ...earlierLive]).size;
|
|
5275
|
-
const recordedAssignment = task.family === undefined ? null : assignmentOf(store, task.id, now,
|
|
5285
|
+
const recordedAssignment = task.family === undefined ? null : assignmentOf(store, task.id, now, workAccess(), evidenceRoot);
|
|
5276
5286
|
const assignment = freshAssignment(recordedAssignment, result ?? null);
|
|
5277
5287
|
const assignmentStatus = assignment === null ? status : assignmentStatusOf(assignment, status);
|
|
5278
5288
|
if (status.views.includes("running") && !assignmentStatus.views.includes("running"))
|
|
@@ -5498,7 +5508,7 @@ export function createDecisionServer(options) {
|
|
|
5498
5508
|
const planRevisions = ref === null ? null : revisionLedgerOf(ref.id);
|
|
5499
5509
|
const milestoneProgress = ref === null ? null : progressOf(ref.id, planRevisions?.current?.document ?? null);
|
|
5500
5510
|
const family = familyOf(taskId);
|
|
5501
|
-
const recordedAssignment = family?.current.id !== taskId ? null : assignmentOf(store, taskId, now,
|
|
5511
|
+
const recordedAssignment = family?.current.id !== taskId ? null : assignmentOf(store, taskId, now, workAccess(), evidenceRoot);
|
|
5502
5512
|
const assignment = freshAssignment(recordedAssignment, completion?.receipt ?? null);
|
|
5503
5513
|
const progressRun = runs.find(one => runIsLive(one))?.id ?? completion?.runId ?? null;
|
|
5504
5514
|
return {
|
|
@@ -5799,7 +5809,7 @@ export function createDecisionServer(options) {
|
|
|
5799
5809
|
title: family.root.title,
|
|
5800
5810
|
state: task.state,
|
|
5801
5811
|
status: view?.status ?? workRowOf({ ...task, repo: ref.repo }, now).status,
|
|
5802
|
-
assignment: view?.assignment ?? assignmentOf(store, task.id, now,
|
|
5812
|
+
assignment: view?.assignment ?? assignmentOf(store, task.id, now, workAccess(), evidenceRoot),
|
|
5803
5813
|
project: ref.repo === null ? null : projectName(ref.repo),
|
|
5804
5814
|
now,
|
|
5805
5815
|
dispatch: diagnoseTaskDispatch(store, taskId, now),
|
|
@@ -5827,6 +5837,7 @@ export function createDecisionServer(options) {
|
|
|
5827
5837
|
raceTerms: view.raceTerms ?? null,
|
|
5828
5838
|
revision: view.revision ?? null,
|
|
5829
5839
|
coordinator: view.coordinator ?? null,
|
|
5840
|
+
repairChain: view.repairChain ?? null,
|
|
5830
5841
|
},
|
|
5831
5842
|
decisions: (view?.decisions ?? store.decisionsForTask(ref.id))
|
|
5832
5843
|
.filter(one => one.state === "open" || one.state === "expired")
|
|
@@ -5864,7 +5875,7 @@ export function createDecisionServer(options) {
|
|
|
5864
5875
|
}
|
|
5865
5876
|
if (!publishing.on || who.via !== "cookie" || who.role !== "approver")
|
|
5866
5877
|
return null;
|
|
5867
|
-
const assignment = assignmentOf(store, taskId, clock(),
|
|
5878
|
+
const assignment = assignmentOf(store, taskId, clock(), workAccess(), evidenceRoot);
|
|
5868
5879
|
const receipt = assignment?.receipt ?? null;
|
|
5869
5880
|
if (assignment?.state !== "complete" || receipt === null || store.publicationForRun(receipt.runId) !== null || pullRequestBlocker(store, receipt.runId) !== null)
|
|
5870
5881
|
return null;
|
|
@@ -11478,7 +11489,7 @@ export function createDecisionServer(options) {
|
|
|
11478
11489
|
run,
|
|
11479
11490
|
receipt,
|
|
11480
11491
|
assignment: (() => {
|
|
11481
|
-
const value = assignmentOf(store, taskId, now,
|
|
11492
|
+
const value = assignmentOf(store, taskId, now, workAccess(), evidenceRoot);
|
|
11482
11493
|
return value?.receipt?.runId === run.id && value.activeTaskId === taskId ? freshAssignment(value, receipt) : null;
|
|
11483
11494
|
})(),
|
|
11484
11495
|
handoff,
|
|
@@ -11486,6 +11497,7 @@ export function createDecisionServer(options) {
|
|
|
11486
11497
|
terminal,
|
|
11487
11498
|
publication,
|
|
11488
11499
|
pullRequestTo: pullRequestTargetOf(run.id),
|
|
11500
|
+
publishing: pullRequestTargetOf(run.id) !== null ? "pull-request" : publishingOf(store, store.refById(run.taskRef)?.repo ?? null).on ? "other" : "off",
|
|
11489
11501
|
ciFailing: publication !== null && publication.prNumber !== null && store.hasOpenCiEpisode(publication.githubRepo, publication.prNumber),
|
|
11490
11502
|
files,
|
|
11491
11503
|
outsideTouches: files.filter(one => one.outsideTouches).map(one => one.path),
|
|
@@ -11665,6 +11677,19 @@ export function createDecisionServer(options) {
|
|
|
11665
11677
|
});
|
|
11666
11678
|
});
|
|
11667
11679
|
// Keep native process custody until shutdown has verified agent/tool exit.
|
|
11680
|
+
// Coding custody does not depend on teammates or the lead's follow pass, so
|
|
11681
|
+
// it closes at once and never waits behind a model turn: a stop must release
|
|
11682
|
+
// the owner record well inside the service's exit window (Oct 2: deploys
|
|
11683
|
+
// over 0.9.11 found it still held by a killed process).
|
|
11684
|
+
let codingClosing = null;
|
|
11685
|
+
const closeCoding = () => {
|
|
11686
|
+
leadClosing = true;
|
|
11687
|
+
return codingClosing ??= Promise.resolve().then(() => coding?.close());
|
|
11688
|
+
};
|
|
11689
|
+
const bounded = (work) => new Promise(done => {
|
|
11690
|
+
const timer = setTimeout(done, SHUTDOWN_WAIT_MS);
|
|
11691
|
+
void Promise.resolve(work).catch(() => { }).finally(() => { clearTimeout(timer); done(); });
|
|
11692
|
+
});
|
|
11668
11693
|
const closeServer = server.close.bind(server);
|
|
11669
11694
|
server.close = ((callback) => {
|
|
11670
11695
|
leadClosing = true;
|
|
@@ -11675,7 +11700,7 @@ export function createDecisionServer(options) {
|
|
|
11675
11700
|
stream.end();
|
|
11676
11701
|
chatStreams.clear();
|
|
11677
11702
|
flowRooms.close();
|
|
11678
|
-
void (
|
|
11703
|
+
void Promise.all([closeCoding(), bounded(team.close()), bounded(leadMaintenance?.stop())]).then(() => closeServer(callback)).catch(error => {
|
|
11679
11704
|
if (callback)
|
|
11680
11705
|
callback(error instanceof Error ? error : Error('Coding session shutdown failed.'));
|
|
11681
11706
|
else
|
|
@@ -11683,7 +11708,7 @@ export function createDecisionServer(options) {
|
|
|
11683
11708
|
});
|
|
11684
11709
|
return server;
|
|
11685
11710
|
});
|
|
11686
|
-
return Object.assign(server, { mintSignInLink });
|
|
11711
|
+
return Object.assign(server, { mintSignInLink, closeCoding });
|
|
11687
11712
|
}
|
|
11688
11713
|
// ---- path plumbing ---------------------------------------------------------
|
|
11689
11714
|
/**
|
|
@@ -12103,6 +12128,199 @@ function acceptanceCeremonyHtml(criteria) {
|
|
|
12103
12128
|
.join("") +
|
|
12104
12129
|
`</ul>`);
|
|
12105
12130
|
}
|
|
12131
|
+
/** "claude" + "opus" → "Claude Opus"; "claude-opus-5-5" → "Claude Opus 5.5"; anything else keeps its exact id. */
|
|
12132
|
+
function agentNameWords(provider, model) {
|
|
12133
|
+
const name = providerName(provider);
|
|
12134
|
+
if (model === null || model === "" || model === "default")
|
|
12135
|
+
return name;
|
|
12136
|
+
const bare = model.startsWith(`${provider}-`) ? model.slice(provider.length + 1) : model;
|
|
12137
|
+
const title = (word) => `${word[0].toUpperCase()}${word.slice(1)}`;
|
|
12138
|
+
if (/^[a-z]+$/.test(bare))
|
|
12139
|
+
return `${name} ${title(bare)}`;
|
|
12140
|
+
const versioned = /^([a-z]+)-(\d+(?:-\d+)*)$/.exec(bare);
|
|
12141
|
+
if (versioned !== null)
|
|
12142
|
+
return `${name} ${title(versioned[1])} ${versioned[2].replace(/-/g, ".")}`;
|
|
12143
|
+
return `${name} ${model}`;
|
|
12144
|
+
}
|
|
12145
|
+
/** The permission a sealed profile grants, in two or three words. */
|
|
12146
|
+
function permissionWordsOf(profile) {
|
|
12147
|
+
if (profile === null || profile === undefined)
|
|
12148
|
+
return null;
|
|
12149
|
+
return profile.provider === "claude"
|
|
12150
|
+
? profile.permissionArgv === "bypassPermissions" ? "Full access" : "Auto permissions"
|
|
12151
|
+
: profile.provider === "gemini"
|
|
12152
|
+
? profile.approvalArgv === "yolo" ? "Full access" : "Auto permissions"
|
|
12153
|
+
: profile.sandboxMode === "danger-full-access" ? "Full access" : "Workspace sandbox";
|
|
12154
|
+
}
|
|
12155
|
+
/** One plain sentence for a revision (approval critique, Oct 2):
|
|
12156
|
+
* "Fixes what build #9 missed: <criterion>." The notes, paths and lineage
|
|
12157
|
+
* stay in the Details fold. */
|
|
12158
|
+
function revisionSentence(revision, acceptance) {
|
|
12159
|
+
const build = `build #${revision.sourceRun}`;
|
|
12160
|
+
if (revision.kind === "ci-repair")
|
|
12161
|
+
return `Fixes the checks that failed in ${build}.`;
|
|
12162
|
+
const clean = (text) => oneLineOf(text, 280).replace(/[.\s]+$/, "");
|
|
12163
|
+
if (revision.kind === "criterion-repair") {
|
|
12164
|
+
const ids = revision.comments.flatMap(one => (/Unmet: ([^.]+)\./.exec(one.note)?.[1] ?? "").split(",").map(id => id.trim()).filter(id => id !== ""));
|
|
12165
|
+
const missed = ids.map(id => acceptance.find(one => one.id === id)?.statement ?? null).filter((one) => one !== null);
|
|
12166
|
+
if (missed.length > 0)
|
|
12167
|
+
return `Fixes what ${build} missed: ${missed.map(clean).join("; ")}.`;
|
|
12168
|
+
}
|
|
12169
|
+
const notes = revision.comments.map(one => clean(one.note)).filter(one => one !== "");
|
|
12170
|
+
return notes.length === 0 ? `Revises ${build}.` : `Fixes what ${build} missed: ${notes.join("; ")}.`;
|
|
12171
|
+
}
|
|
12172
|
+
/** A planner's amendment, said plainly and kept in view: approving binds
|
|
12173
|
+
* the amended terms, so this is never folded away. */
|
|
12174
|
+
function approvalAmendmentHtml(view) {
|
|
12175
|
+
if (view === null || view === undefined || "problem" in view || !view.filed && view.revision !== true || view.changes.length === 0)
|
|
12176
|
+
return "";
|
|
12177
|
+
const line = (change) => {
|
|
12178
|
+
switch (change.field) {
|
|
12179
|
+
case "goal": return `Goal was: ${escape(change.before)}`;
|
|
12180
|
+
case "outOfScope": return change.after === null ? "No longer rules anything out" : change.before === null ? `Now rules out: ${escape(change.after)}` : `Won't touch was: ${escape(change.before)}`;
|
|
12181
|
+
case "touches": return `${change.kind === "added" ? "Adds the path" : "Drops the path"} <span class="mono">${escape(change.path)}</span>`;
|
|
12182
|
+
case "acceptance": return change.kind === "added" ? `Adds a check: ${escape(change.after?.statement ?? "")}` : change.kind === "removed" ? `Drops a check: ${escape(change.before?.statement ?? "")}` : `Rewords a check: ${escape(change.after?.statement ?? "")}`;
|
|
12183
|
+
}
|
|
12184
|
+
};
|
|
12185
|
+
const count = `${view.changes.length} change${view.changes.length === 1 ? "" : "s"}`;
|
|
12186
|
+
return (`<div class="approval-amendment" id="contract-amendment">` +
|
|
12187
|
+
`<p><strong>${view.revision === true ? `The plan makes ${count} from your notes` : `The plan makes ${count} to what you filed`}</strong></p>` +
|
|
12188
|
+
`<ul>${view.changes.map(one => `<li>${line(one)}</li>`).join("")}</ul>` +
|
|
12189
|
+
(view.amendment === null ? "" : `<p class="meta">Why: ${escape(view.amendment)}</p>`) +
|
|
12190
|
+
`</div>`);
|
|
12191
|
+
}
|
|
12192
|
+
/**
|
|
12193
|
+
* The approval sheet (approval critique, Oct 2): the plan open, in plain
|
|
12194
|
+
* rows — Goal, Changes, Won't touch, Done when — one line of who builds,
|
|
12195
|
+
* one Approve & start beside the password, and every remaining signed
|
|
12196
|
+
* term (lineage, routing, runtime limits, the seal) in one Details fold.
|
|
12197
|
+
* Presentation only: the same hidden fields, nonce, digest and password
|
|
12198
|
+
* post to the same route, and the task page and chat card share it.
|
|
12199
|
+
*/
|
|
12200
|
+
function approvalSheetHtml(input) {
|
|
12201
|
+
const { scope, route, raceTerms } = input;
|
|
12202
|
+
// A machine-drafted repair appends its brief to the signed goal; the row
|
|
12203
|
+
// shows the goal and one sentence says what the repair fixes. The whole
|
|
12204
|
+
// signed goal stays in Details.
|
|
12205
|
+
const repairText = / — (repair exactly the unmet criteria named below; a comment cannot widen the scope\. Unmet: ([^.]+)\.|Collect only the missing observations for ([^.]+)\. .*|Diagnose the saved failed project check and repair within the original scope\. .*)$/s.exec(scope.goal);
|
|
12206
|
+
const goalShown = repairText === null ? scope.goal : scope.goal.slice(0, repairText.index);
|
|
12207
|
+
const statementsOf = (ids) => ids.split(",").map(id => id.trim()).map(id => scope.acceptance.find(one => one.id === id)?.statement ?? id).map(one => one.replace(/[.\s]+$/, "")).join("; ");
|
|
12208
|
+
const repairBuild = input.revision?.sourceRun ?? input.repairChain?.sourceRun ?? null;
|
|
12209
|
+
const builtBy = repairBuild === null ? "the last build" : `build #${repairBuild}`;
|
|
12210
|
+
const sentence = input.revision !== null && (repairText === null || input.revision.comments.length > 0) ? revisionSentence(input.revision, scope.acceptance)
|
|
12211
|
+
: repairText === null ? null
|
|
12212
|
+
: repairText[2] !== undefined ? `Fixes what ${builtBy} missed: ${statementsOf(repairText[2])}.`
|
|
12213
|
+
: repairText[3] !== undefined ? `Collects the evidence ${builtBy} was missing for: ${statementsOf(repairText[3])}. The saved code stays the same.`
|
|
12214
|
+
: `Fixes the checks that failed in ${builtBy}.`;
|
|
12215
|
+
const plan = input.planDocument === null ? null : parseExecutionPlanDocument(input.planDocument);
|
|
12216
|
+
const milestones = plan !== null && plan.ok ? plan.document.milestones : [];
|
|
12217
|
+
const permission = permissionWordsOf(scope.profile);
|
|
12218
|
+
const money = (micro) => `$${(micro / 1_000_000).toFixed(2)}`;
|
|
12219
|
+
const row = (label, body) => `<div class="approval-row"><dt>${label}</dt><dd>${body}</dd></div>`;
|
|
12220
|
+
const changes = (milestones.length > 0 ? `<ol class="approval-steps">${milestones.map(one => `<li>${escape(one)}</li>`).join("")}</ol>` : "") +
|
|
12221
|
+
(scope.touches.length > 0
|
|
12222
|
+
? `<p>Only in these paths:</p>${approvalPathsHtml(scope.touches)}`
|
|
12223
|
+
: `<p${milestones.length > 0 ? ` class="meta"` : ""}>Any file in the project.</p>`);
|
|
12224
|
+
const doneWhen = scope.acceptance.length === 0
|
|
12225
|
+
? `<p>You decide when you review the result.</p>`
|
|
12226
|
+
: `<ul class="approval-done">${scope.acceptance.map(one => `<li>${escape(one.statement)}</li>`).join("")}</ul>`;
|
|
12227
|
+
// Who builds, in one line: the builder and planner by name, the cap, and full access when granted.
|
|
12228
|
+
const legs = route?.projection?.legs ?? [];
|
|
12229
|
+
const legOf = (phase) => legs.find(one => one.phase === phase);
|
|
12230
|
+
const who = [];
|
|
12231
|
+
const builder = legOf("build");
|
|
12232
|
+
if (raceTerms === null) {
|
|
12233
|
+
if (builder !== undefined)
|
|
12234
|
+
who.push(`Builder ${agentNameWords(builder.provider, builder.model)}`);
|
|
12235
|
+
else if (route?.legacy != null)
|
|
12236
|
+
who.push(`Builder ${agentNameWords(route.legacy.provider, route.legacy.model)}`);
|
|
12237
|
+
else if (scope.profile != null)
|
|
12238
|
+
who.push(`Builder ${agentNameWords(scope.profile.provider, scope.profile.model)}`);
|
|
12239
|
+
}
|
|
12240
|
+
const planner = legOf("plan");
|
|
12241
|
+
if (planner !== undefined)
|
|
12242
|
+
who.push(`Planner ${agentNameWords(planner.provider, planner.model)}`);
|
|
12243
|
+
if (scope.budgetMicrousd !== null)
|
|
12244
|
+
who.push(`Cap ${money(scope.budgetMicrousd)} per attempt`);
|
|
12245
|
+
if (permission === "Full access")
|
|
12246
|
+
who.push("Full access");
|
|
12247
|
+
const race = raceTerms === null
|
|
12248
|
+
? ""
|
|
12249
|
+
: `<p class="approval-race">${raceTerms.n} agents build this separately: ${raceTerms.agents.map(one => escape(agentNameWords(one.provider, one.model))).join(" and ")}. ` +
|
|
12250
|
+
(raceTerms.kind === "comparison"
|
|
12251
|
+
? "No dollar cap; you compare the results and pick one.</p>"
|
|
12252
|
+
: `Each may spend ${money(raceTerms.perAgentBudgetMicrousd)} plus a ${money(raceTerms.overrunReserveMicrousd)} reserve, ${money(raceTerms.totalBudgetMicrousd)} in all; you compare the results and pick one.</p>`);
|
|
12253
|
+
const after = input.deliverable === "report"
|
|
12254
|
+
? "An agent investigates without changing the repository. You'll hear when its report is ready."
|
|
12255
|
+
: raceTerms !== null
|
|
12256
|
+
? "Each agent starts in its own branch. You'll hear when the results are ready to compare."
|
|
12257
|
+
: "An agent starts in its own branch. You'll hear when it's ready to review.";
|
|
12258
|
+
const submit = raceTerms === null ? "Approve & start" : raceTerms.kind === "comparison" ? "Approve comparison" : "Approve tournament";
|
|
12259
|
+
// Everything else the digest binds, one tap away.
|
|
12260
|
+
const detail = (title, body, attrs = "") => body === "" ? "" : `<section class="approval-detail"${attrs}><h3>${title}</h3>${body}</section>`;
|
|
12261
|
+
const revisionDetail = input.revision === null ? "" : detail("Earlier build", `<div class="revision-card" data-revision-feedback>` +
|
|
12262
|
+
`<p><a href="${escape(input.revisionSourceHref)}"${input.surface === "chat" ? " data-revision-source" : ""}>${input.surface === "chat" ? `Original result: build #${input.revision.sourceRun} →` : `build #${input.revision.sourceRun}`}</a></p>` +
|
|
12263
|
+
(input.revision.comments.length === 0 ? "" : `<ul>${input.revision.comments.map(one => `<li>${one.path === null ? "" : `<span class="mono">${escape(one.path)}${one.line === null ? "" : `:${one.line}`}</span> · `}${escape(one.note)} <span class="meta">— ${escape(one.author)}</span></li>`).join("")}</ul>`) +
|
|
12264
|
+
revisionLineageHtml(input.revision.lineage) +
|
|
12265
|
+
`</div>`);
|
|
12266
|
+
const contract = input.planContract;
|
|
12267
|
+
const contractDetail = input.planDocument === null || approvalAmendmentHtml(contract) !== "" ? ""
|
|
12268
|
+
: contract === null || contract === undefined ? `<p class="meta">No record compares this plan with what was filed. Check the rows above against your request.</p>`
|
|
12269
|
+
: "problem" in contract ? `<p class="meta">${escape(contract.problem)} · <a href="/r/${contract.run}">run ${contract.run}</a></p>`
|
|
12270
|
+
: !contract.filed && contract.revision !== true ? `<p class="meta">Nothing was filed before planning, so every term is the planner's proposal.</p>`
|
|
12271
|
+
: `<p class="meta">${contract.revision === true ? "Same terms as the previous version." : "The plan keeps exactly what you filed."} <a href="/r/${contract.run}">Planning run ${contract.run}</a></p>`;
|
|
12272
|
+
const criteriaDetail = scope.acceptance.length === 0 ? ""
|
|
12273
|
+
: `<ul class="approval-signed-criteria">${scope.acceptance.map(one => `<li><code>${escape(one.id)}</code> ${escape(one.statement)} <span class="meta">needs ${one.evidence.map(escape).join(", ")}</span></li>`).join("")}</ul>`;
|
|
12274
|
+
const projection = route?.projection ?? null;
|
|
12275
|
+
const agentsDetail = route === null || route === undefined ? ""
|
|
12276
|
+
: projection === null
|
|
12277
|
+
? route.legacy === null ? "" : `<p>${escape(agentsSummaryWords(route))}</p>`
|
|
12278
|
+
: `<p>${escape(projection.summary)}</p>` +
|
|
12279
|
+
`<p class="meta">${escape(riskTitle(route.riskLevel))}: ${escape(riskConsequence(route.riskLevel))}. Uses ${escape(projection.postureWords)}.</p>` +
|
|
12280
|
+
(projection.demands.length === 0 ? "" : `<ul class="meta">${projection.demands.map(one => `<li>${escape(one)}</li>`).join("")}</ul>`) +
|
|
12281
|
+
`<dl class="approval-roles">${projection.legs.map(leg => `<div><dt>${escape(ROLE_NOUN[leg.phase])}</dt><dd><span class="mono">${escape(leg.provider)} · ${escape(leg.model)}</span> <span class="meta">${escape(chosenWords(leg))}</span>` +
|
|
12282
|
+
`<ul>${leg.reasons.map(reason => `<li>${escape(reason)}</li>`).join("")}${leg.problem === null ? "" : `<li><strong>${escape(leg.problem)}</strong></li>`}</ul></dd></div>`).join("")}</dl>` +
|
|
12283
|
+
`<p class="meta">These exact agents are part of what you approve. Changing any of them asks for a fresh approval.</p>`;
|
|
12284
|
+
const limits = profileWords(scope) +
|
|
12285
|
+
`<p class="meta">Checks level: ${escape(qualityModeTitle(scope.qualityMode ?? "default"))}${permission === null ? "" : ` · ${escape(permission)}`}</p>` +
|
|
12286
|
+
(scope.budgetMicrousd === null ? "" : `<p class="meta">Each build attempt has a ${money(scope.budgetMicrousd)} agent-reported usage cap. On a subscription this limits work; it is not an API charge.</p>`);
|
|
12287
|
+
const details = `<details class="approval-details"><summary>Details</summary>` +
|
|
12288
|
+
revisionDetail +
|
|
12289
|
+
(repairText === null ? "" : detail("Signed goal", `<p>${escape(scope.goal)}</p>`)) +
|
|
12290
|
+
detail("Plan record", contractDetail) +
|
|
12291
|
+
detail("Signed criteria", criteriaDetail) +
|
|
12292
|
+
detail("Why these agents", agentsDetail, ` id="${input.surface === "task" ? "approval-agents" : "chat-approval-agents"}"`) +
|
|
12293
|
+
detail("Runtime limits", limits) +
|
|
12294
|
+
(scope.candidate ? detail("Saved commit", `<p><code class="approval-commit">${escape(scope.candidate)}</code></p>`) : "") +
|
|
12295
|
+
detail("Seal", `<p class="meta">Approval binds to this exact wording. <span class="seal mono">signs ${shortDigest(scope.digest)}</span></p>`) +
|
|
12296
|
+
`</details>`;
|
|
12297
|
+
return (`<form method="post" action="${input.action}" class="approve-form approval-sheet"${input.surface === "task" ? ` id="approve"` : ""}${input.sticky ? " data-sticky" : ""}>` +
|
|
12298
|
+
`<input type="hidden" name="csrf" value="${escape(input.csrf)}">` +
|
|
12299
|
+
`<input type="hidden" name="nonce" value="${escape(input.nonce)}">` +
|
|
12300
|
+
`<input type="hidden" name="digest" value="${escape(input.digest)}">` +
|
|
12301
|
+
(input.returnTo === null ? "" : `<input type="hidden" name="return" value="${escape(input.returnTo)}">`) +
|
|
12302
|
+
`<input type="text" name="username" autocomplete="username" class="visually-hidden" tabindex="-1" aria-hidden="true">` +
|
|
12303
|
+
`<h2 class="approval-sheet-title">${input.deliverable === "report" ? "The investigation" : "The plan"}</h2>` +
|
|
12304
|
+
(sentence === null ? "" : `<p class="approval-revision">${escape(sentence)}</p>`) +
|
|
12305
|
+
(input.coordinator === null ? "" : `<p class="approval-note">An agent filed this: <span class="mono">${escape(input.coordinator.label)}</span>${input.coordinator.filedAgo === null ? "" : `, ${escape(input.coordinator.filedAgo)}`}. Nothing runs until you approve, and approving runs its request.</p>`) +
|
|
12306
|
+
(input.deliverable === "report" ? `<p class="approval-note">Read-only: it reports back and changes nothing in the repository.</p>` : "") +
|
|
12307
|
+
`<dl class="approval-rows">` +
|
|
12308
|
+
row("Goal", `<p class="approval-goal">${escape(goalShown)}</p>`) +
|
|
12309
|
+
row("Changes", changes) +
|
|
12310
|
+
row("Won’t touch", `<p>${scope.outOfScope === null ? "Nothing is ruled out." : escape(scope.outOfScope)}</p>`) +
|
|
12311
|
+
row("Done when", doneWhen) +
|
|
12312
|
+
`</dl>` +
|
|
12313
|
+
approvalAmendmentHtml(contract) +
|
|
12314
|
+
race +
|
|
12315
|
+
(who.length === 0 ? "" : `<p class="approval-who">${who.map(escape).join(" · ")}</p>`) +
|
|
12316
|
+
`<div class="approval-act" id="${input.surface === "task" ? "approval-confirm" : "chat-approval-confirm"}">` +
|
|
12317
|
+
`<label class="approval-password"><span class="visually-hidden">Your password</span><input type="password" name="token" autocomplete="current-password" placeholder="Your password"></label>` +
|
|
12318
|
+
`<button type="submit" data-primary-action>${submit}</button></div>` +
|
|
12319
|
+
`<p class="approval-after">${after}</p>` +
|
|
12320
|
+
`<p class="approval-secondary"><a class="approval-link" href="${escape(input.editHref)}">Edit plan</a><a class="approval-link" href="${escape(input.notNowHref)}">Not now</a></p>` +
|
|
12321
|
+
details +
|
|
12322
|
+
`</form>`);
|
|
12323
|
+
}
|
|
12106
12324
|
function consentDoorOf(scope, route) {
|
|
12107
12325
|
if (scope === null)
|
|
12108
12326
|
return { open: false, title: "There is no scope to approve yet", why: "write the scope first", road: "scope" };
|
|
@@ -12374,6 +12592,8 @@ function agentsStripHtml(view, taskId) {
|
|
|
12374
12592
|
}
|
|
12375
12593
|
/** The demo's one-line promise, on every page. */
|
|
12376
12594
|
const DEMO_BANNER = `Demo: a scripted lead and sample projects. Nothing calls a model, reaches outside or spends. For your own project, run ${START_COMMAND} in its folder.`;
|
|
12595
|
+
/** The demo notice on a phone: one line. */
|
|
12596
|
+
const DEMO_BANNER_SHORT = "Demo: sample projects. Nothing calls a model or spends.";
|
|
12377
12597
|
/** Every character that could open a tag or an attribute, dead at the sink. */
|
|
12378
12598
|
function escape(text) {
|
|
12379
12599
|
return text
|
|
@@ -12864,7 +13084,7 @@ ${THEME_DARK}
|
|
|
12864
13084
|
/* Thumbnails, never a full-width poster: auto-fill leaves a lone
|
|
12865
13085
|
screenshot at thumbnail size (UI polish 2026-09-13). */
|
|
12866
13086
|
.receipt-visuals { display: grid; grid-template-columns: repeat(auto-fill, minmax(8rem, 14rem)); gap: .55rem; margin-top: .75rem; }
|
|
12867
|
-
.receipt-shot { display: grid; gap: .35rem; color: var(--muted-foreground); font-size: .
|
|
13087
|
+
.receipt-shot { display: grid; gap: .35rem; color: var(--muted-foreground); font-size: .75rem; text-decoration: none; }
|
|
12868
13088
|
.receipt-shot img { display: block; width: 100%; aspect-ratio: 16 / 10; object-fit: cover; border: 1px solid var(--glass-border); border-radius: calc(var(--radius) - 3px); background: var(--muted); }
|
|
12869
13089
|
@media (hover: hover) and (pointer: fine) { .receipt-shot:hover { color: var(--foreground); } }
|
|
12870
13090
|
.receipt-caveats, .receipt-coverage { margin-top: .8rem; padding: .7rem .8rem; border-left: 1px solid var(--border); border-radius: 0 calc(var(--radius) - 3px) calc(var(--radius) - 3px) 0; background: color-mix(in srgb, var(--muted) 62%, transparent); font-size: .78rem; }
|
|
@@ -12997,7 +13217,7 @@ ${THEME_DARK}
|
|
|
12997
13217
|
}
|
|
12998
13218
|
.diff-file[open] > summary::before { transform: rotate(45deg) translateY(-.1rem); }
|
|
12999
13219
|
.diff-file-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: 500 .75rem/1.4 var(--font-mono); }
|
|
13000
|
-
.diff-file-counts { display: inline-flex; gap: .45rem; flex: none; margin-left: auto; font: 500 .
|
|
13220
|
+
.diff-file-counts { display: inline-flex; gap: .45rem; flex: none; margin-left: auto; font: 500 .75rem/1 var(--font-mono); }
|
|
13001
13221
|
.diff-file-counts b { color: var(--success); font-weight: 500; }
|
|
13002
13222
|
.diff-file-counts i { color: var(--destructive); font-style: normal; }
|
|
13003
13223
|
.diff-rename { margin: 0; padding: .4rem .85rem; border-top: 1px solid var(--glass-border); }
|
|
@@ -13005,12 +13225,12 @@ ${THEME_DARK}
|
|
|
13005
13225
|
.diff-hunk-head {
|
|
13006
13226
|
overflow-x: auto; padding: .42rem .85rem; border-top: 1px solid var(--glass-border); border-bottom: 1px solid var(--glass-border);
|
|
13007
13227
|
background: color-mix(in srgb, var(--running) 8%, var(--card)); color: color-mix(in srgb, var(--running) 72%, var(--foreground));
|
|
13008
|
-
font: 500 .
|
|
13228
|
+
font: 500 .75rem/1.4 var(--font-mono); white-space: pre;
|
|
13009
13229
|
}
|
|
13010
13230
|
.diff-lines { max-width: 100%; overflow-x: auto; background: color-mix(in srgb, var(--background) 48%, var(--card)); }
|
|
13011
13231
|
.diff-line {
|
|
13012
13232
|
display: grid; grid-template-columns: 2rem 3.2rem 3.2rem minmax(max-content, 1fr); align-items: stretch;
|
|
13013
|
-
min-width: max-content; min-height: 1.8rem; font: 400 .
|
|
13233
|
+
min-width: max-content; min-height: 1.8rem; font: 400 .75rem/1.55 var(--font-mono);
|
|
13014
13234
|
}
|
|
13015
13235
|
@media (hover: hover) and (pointer: fine) { .diff-line:hover { background: color-mix(in srgb, var(--foreground) 4%, transparent); } }
|
|
13016
13236
|
.diff-line code { display: flex; min-width: 0; padding: .3rem .75rem .3rem .6rem; color: inherit; white-space: pre; }
|
|
@@ -13548,7 +13768,9 @@ ${THEME_DARK}
|
|
|
13548
13768
|
.approval-title { display: grid; gap: .15rem; }
|
|
13549
13769
|
.approval-kicker { color: var(--muted-foreground); font: 500 .66rem/1.3 var(--font-mono); letter-spacing: .06em; text-transform: uppercase; }
|
|
13550
13770
|
.approval-title strong { font-size: 1.05rem; letter-spacing: -.02em; }
|
|
13551
|
-
.approval-label { margin: .9rem 0 .2rem; color: var(--muted-foreground); font: 500
|
|
13771
|
+
.approval-label { display: block; margin: .9rem 0 .2rem; color: var(--muted-foreground); font: 500 12px/1.4 var(--font-sans); }
|
|
13772
|
+
.approval-label::first-letter, .planner-plan .eyebrow::first-letter { text-transform: uppercase; }
|
|
13773
|
+
.planner-plan .eyebrow { font: 500 12px/1.4 var(--font-sans); letter-spacing: 0; text-transform: none; }
|
|
13552
13774
|
.approval-goal { margin: 0; font-size: 1rem; line-height: 1.55; white-space: pre-wrap; }
|
|
13553
13775
|
.approval-boundaries { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .6rem; margin-top: .75rem; }
|
|
13554
13776
|
.approval-boundary { padding: .7rem .8rem; border: 1px solid var(--glass-border); border-radius: calc(var(--radius) - 3px); background: color-mix(in srgb, var(--muted) 45%, transparent); }
|
|
@@ -13634,6 +13856,66 @@ ${THEME_DARK}
|
|
|
13634
13856
|
.approval-confirm { scroll-margin-top: 5rem; }
|
|
13635
13857
|
.approval-paths { margin: 0; padding: 0; list-style: none; display: grid; gap: .15rem; }
|
|
13636
13858
|
.approval-paths li { color: var(--muted-foreground); font-size: .8rem; overflow-wrap: anywhere; }
|
|
13859
|
+
/* The approval sheet (approval critique, Oct 2): the plan open in plain
|
|
13860
|
+
rows, who builds in one line, one Approve & start beside the password,
|
|
13861
|
+
and every other signed term in one Details fold at the end. Sentence
|
|
13862
|
+
case, Geist, 12px and up; prose capped at 75ch. */
|
|
13863
|
+
.approval-sheet { display: grid; gap: 16px; margin: 0; padding: 20px; border: 1px solid var(--border); border-radius: 10px; background: var(--card); }
|
|
13864
|
+
.approval-sheet.approve-form > :not(input) { margin: 0; }
|
|
13865
|
+
.approval-sheet-title { margin: 0; font-size: 15px; font-weight: 600; line-height: 1.4; letter-spacing: -.01em; }
|
|
13866
|
+
.approval-revision, .approval-note, .approval-race { max-width: 75ch; font-size: 14px; line-height: 1.6; overflow-wrap: anywhere; }
|
|
13867
|
+
.approval-note { color: var(--muted-foreground); }
|
|
13868
|
+
.approval-rows { display: grid; gap: 14px; margin: 0; }
|
|
13869
|
+
.approval-row { display: grid; grid-template-columns: 7.5rem minmax(0, 1fr); gap: 16px; }
|
|
13870
|
+
.approval-row dt { margin: 0; color: var(--muted-foreground); font-size: 13px; font-weight: 500; line-height: 1.6; }
|
|
13871
|
+
.approval-row dd { min-width: 0; max-width: 75ch; margin: 0; font-size: 14px; line-height: 1.6; overflow-wrap: anywhere; }
|
|
13872
|
+
.approval-row dd p, .approval-sheet .approval-goal { margin: 0; font-size: 14px; line-height: 1.6; }
|
|
13873
|
+
.approval-row dd :is(ol, ul) { display: grid; gap: 4px; margin: 0; padding-left: 1.25rem; }
|
|
13874
|
+
.approval-row dd ol + p { margin-top: 6px; }
|
|
13875
|
+
.approval-row dd .approval-paths { margin-top: 2px; padding-left: 0; }
|
|
13876
|
+
.approval-row dd .approval-paths li { font-size: 13px; }
|
|
13877
|
+
.approval-amendment { max-width: 75ch; padding: 12px 14px; border-radius: 8px; background: var(--muted); font-size: 14px; line-height: 1.55; overflow-wrap: anywhere; }
|
|
13878
|
+
.approval-amendment p { margin: 0; }
|
|
13879
|
+
.approval-amendment ul { display: grid; gap: 4px; margin: 6px 0; padding-left: 1.25rem; }
|
|
13880
|
+
.approval-who { max-width: 75ch; color: var(--muted-foreground); font-size: 13px; line-height: 1.5; }
|
|
13881
|
+
.approval-act { display: grid; grid-template-columns: minmax(0, 20rem) auto; justify-content: start; align-items: center; gap: 8px; }
|
|
13882
|
+
.approval-password { margin: 0; }
|
|
13883
|
+
.approval-act input[type=password] { margin: 0; font-family: var(--font-sans); }
|
|
13884
|
+
.approval-act button { margin: 0; min-height: 2.125rem; white-space: nowrap; }
|
|
13885
|
+
.approval-after { max-width: 75ch; color: var(--muted-foreground); font-size: 13px; line-height: 1.5; }
|
|
13886
|
+
.approval-secondary { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
13887
|
+
.approval-link { display: inline-flex; align-items: center; min-height: 32px; padding: 0 12px; border: 1px solid var(--border); border-radius: 8px; background: var(--card); color: var(--foreground); font-size: 13px; font-weight: 500; text-decoration: none; }
|
|
13888
|
+
.approval-link + .approval-link { border-color: transparent; background: none; color: var(--muted-foreground); }
|
|
13889
|
+
@media (hover: hover) and (pointer: fine) { .approval-link:hover { background: var(--muted); color: var(--foreground); } }
|
|
13890
|
+
details.approval-details { margin: 0; padding: 0; border: 0; border-top: 1px solid var(--border); border-radius: 0; background: none; }
|
|
13891
|
+
details.approval-details[open] { padding-bottom: 0; }
|
|
13892
|
+
.approval-details > summary { display: flex; align-items: center; min-height: 40px; padding: 4px 0 0; font-size: 13px; }
|
|
13893
|
+
.approval-detail { max-width: 75ch; margin: 12px 0 0; font-size: 13px; line-height: 1.55; overflow-wrap: anywhere; }
|
|
13894
|
+
.approval-detail h3 { margin: 0 0 4px; font-size: 13px; font-weight: 600; letter-spacing: 0; text-transform: none; }
|
|
13895
|
+
.approval-detail p, .approval-detail ul, .approval-detail dl { margin: 4px 0; }
|
|
13896
|
+
.approval-detail ul { padding-left: 1.25rem; }
|
|
13897
|
+
.approval-detail .revision-card { margin: 0; padding: 0; border: 0; background: none; }
|
|
13898
|
+
.approval-roles { display: grid; gap: 8px; }
|
|
13899
|
+
.approval-roles > div { display: grid; grid-template-columns: 6rem minmax(0, 1fr); gap: 12px; }
|
|
13900
|
+
.approval-roles dt { color: var(--muted-foreground); font-weight: 500; }
|
|
13901
|
+
.approval-roles dd { margin: 0; }
|
|
13902
|
+
.approval-roles dd ul { margin: 2px 0 0; color: var(--muted-foreground); }
|
|
13903
|
+
.chat-plan .approval-sheet { padding: 0; border: 0; background: none; }
|
|
13904
|
+
@media (max-width: 760px) {
|
|
13905
|
+
.approval-sheet { gap: 12px; padding: 14px; }
|
|
13906
|
+
.approval-row { grid-template-columns: 1fr; gap: 2px; }
|
|
13907
|
+
.approval-act { grid-template-columns: 1fr; }
|
|
13908
|
+
.approval-act input[type=password], .approval-act button { min-height: 44px; font-size: 16px; }
|
|
13909
|
+
.approval-act button { width: 100%; }
|
|
13910
|
+
/* The one act stays under the thumb: sticky at the bottom, clear of the home indicator. */
|
|
13911
|
+
.approval-sheet[data-sticky] .approval-act {
|
|
13912
|
+
position: sticky; bottom: 0; z-index: 20; margin: 0 -14px; padding: 10px 14px calc(10px + env(safe-area-inset-bottom, 0px));
|
|
13913
|
+
border-top: 1px solid var(--border); background: var(--card);
|
|
13914
|
+
}
|
|
13915
|
+
.approval-link { min-height: 44px; padding: 0 16px; }
|
|
13916
|
+
.approval-details > summary { min-height: 44px; }
|
|
13917
|
+
.approval-roles > div { grid-template-columns: 1fr; gap: 2px; }
|
|
13918
|
+
}
|
|
13637
13919
|
.run-facts-details { margin-top: 1.25rem; }
|
|
13638
13920
|
.run-facts-details > summary { display: flex; align-items: center; justify-content: space-between; gap: .75rem; }
|
|
13639
13921
|
.run-facts-details > summary .meta { font-size: .75rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
@@ -13867,7 +14149,7 @@ ${THEME_DARK}
|
|
|
13867
14149
|
.result-pin[open] > summary::before { content: "▾"; }
|
|
13868
14150
|
.result-pin > summary .meta { font-weight: 400; }
|
|
13869
14151
|
.result-pin .diff-comment-target { margin: .25rem 0 .5rem; }
|
|
13870
|
-
.
|
|
14152
|
+
.revision-card, .revision-lineage, .task-history, .diff-comment p { min-width: 0; overflow-wrap: anywhere; }
|
|
13871
14153
|
.result-feedback-history > summary, .task-history > summary, .task-history a { display: inline-flex; align-items: center; min-height: 44px; padding: .25rem .5rem; }
|
|
13872
14154
|
.task-history > summary::before { content: "▸"; margin-right: .35rem; }
|
|
13873
14155
|
.task-history[open] > summary::before { content: "▾"; }
|
|
@@ -14332,11 +14614,10 @@ ${THEME_DARK}
|
|
|
14332
14614
|
.check-progress[data-final="passed"] { color: var(--success); }
|
|
14333
14615
|
.check-progress[data-final="failed"] { color: var(--destructive); }
|
|
14334
14616
|
.check-progress[data-final="unknown"] { color: var(--warning); }
|
|
14335
|
-
.receipt-actions [data-primary-action]
|
|
14617
|
+
.receipt-actions [data-primary-action] { min-height: 44px; }
|
|
14336
14618
|
.task-status-reason { margin-top: .75rem; }
|
|
14337
14619
|
.task-status-reason summary, .task-status-details > summary { color: var(--muted-foreground); }
|
|
14338
14620
|
.task-status-details > summary { min-height: 44px; }
|
|
14339
|
-
.task-plan-review > summary { min-height: 44px; display: flex; align-items: center; cursor: pointer; font-weight: 650; }
|
|
14340
14621
|
.task-status-details, #task-control-details { margin: 1rem 0; }
|
|
14341
14622
|
.task-control-copy { min-width: 0; overflow-wrap: anywhere; }
|
|
14342
14623
|
[data-primary-action] { max-width: 100%; }
|
|
@@ -14351,16 +14632,9 @@ ${THEME_DARK}
|
|
|
14351
14632
|
.chat-action-card > summary strong { font-size: .92rem; }
|
|
14352
14633
|
.chat-action-card > summary small { color: var(--muted-foreground); font-size: .68rem; font-weight: 400; }
|
|
14353
14634
|
.chat-action-card[open] > summary { border-bottom: 1px solid var(--border); }
|
|
14354
|
-
.chat-approval-form { display: grid; gap: .85rem; padding: 1.05rem; }
|
|
14355
|
-
.chat-approval-section { display: grid; gap: .3rem; }
|
|
14356
|
-
.chat-approval-section > p, .chat-approval-section > pre { margin: 0; }
|
|
14357
|
-
.chat-approval-form .approval-boundaries { margin: 0; }
|
|
14358
14635
|
.chat-run-details { padding: .65rem .75rem; border: 1px solid var(--border); border-radius: calc(var(--radius) - 3px); background: color-mix(in srgb,var(--muted) 45%,transparent); }
|
|
14359
14636
|
.chat-run-details > summary { color: var(--muted-foreground); cursor: pointer; font-size: .68rem; }
|
|
14360
14637
|
.chat-run-details > .meta { margin-bottom: 0; }
|
|
14361
|
-
.chat-approval-form .approval-confirm { align-items: end; margin-top: .15rem; }
|
|
14362
|
-
.chat-approval-form .approval-confirm label { flex: 1 1 18rem; }
|
|
14363
|
-
.chat-approval-form .approval-confirm button { min-height: 2.65rem; }
|
|
14364
14638
|
.chat-decisions { display: grid; gap: .7rem; }
|
|
14365
14639
|
.chat-section-head { margin: .15rem .15rem 0; }
|
|
14366
14640
|
.chat-section-head h2 { margin: .2rem 0 0; font-size: 1rem; }
|
|
@@ -14557,18 +14831,9 @@ ${THEME_DARK}
|
|
|
14557
14831
|
/* The concise plan (package 2): a title, the outcome, one line of
|
|
14558
14832
|
counts and limits, then one Review plan disclosure over the unchanged
|
|
14559
14833
|
exact-terms form. */
|
|
14560
|
-
.chat-plan-head h2 { margin: 0; font-size: 1rem; letter-spacing: -.025em; }
|
|
14561
|
-
.chat-plan-outcome { margin: .65rem 0 .55rem; font-size: .88rem; line-height: 1.5; overflow-wrap: anywhere; }
|
|
14562
|
-
.chat-plan-facts { margin: 0; color: var(--muted-foreground); font-size: .74rem; line-height: 1.5; overflow-wrap: anywhere; }
|
|
14563
|
-
.chat-plan > details.chat-approval { margin: .85rem 0 0; padding: .85rem 0 0; border: 0; border-top: 1px solid var(--border); border-radius: 0; background: transparent; }
|
|
14564
|
-
.chat-plan > details.chat-approval > summary { display: flex; align-items: center; gap: .75rem; cursor: pointer; list-style: none; }
|
|
14565
|
-
.chat-plan > details.chat-approval > summary::-webkit-details-marker { display: none; }
|
|
14566
|
-
.chat-plan > details.chat-approval > summary .button-link { flex: 0 0 auto; white-space: nowrap; min-height: 44px; }
|
|
14567
|
-
.chat-plan > details.chat-approval > .chat-approval-form { padding: .85rem 0 0; }
|
|
14568
|
-
.chat-plan .chat-approval-lead { margin: 0 0 .85rem; }
|
|
14569
14834
|
.chat-approval-stale { margin: .75rem 0 0; }
|
|
14570
14835
|
.chat-approval-stale a { font-weight: 600; }
|
|
14571
|
-
form.approve-form[data-stale="1"] .approval-confirm { opacity: .55; }
|
|
14836
|
+
form.approve-form[data-stale="1"] .approval-confirm, form.approve-form[data-stale="1"] .approval-act { opacity: .55; }
|
|
14572
14837
|
.proposal-filed { display: flex; align-items: center; flex-wrap: wrap; gap: .6rem; margin: .5rem 0 0; }
|
|
14573
14838
|
.chat-thinking { display: flex; align-items: center; gap: .75rem; padding: .75rem .85rem; }
|
|
14574
14839
|
.chat-thinking p { flex: 1; margin: 0; }
|
|
@@ -14695,10 +14960,6 @@ ${THEME_DARK}
|
|
|
14695
14960
|
.chat-action-card > summary { align-items: flex-start; padding: .85rem; }
|
|
14696
14961
|
.chat-action-card > summary .button-link { min-height: 2.1rem; padding-inline: .6rem; font-size: .65rem; white-space: nowrap; }
|
|
14697
14962
|
.chat-action-card > summary small { max-width: 14rem; }
|
|
14698
|
-
.chat-approval-form { padding: .85rem; }
|
|
14699
|
-
.chat-approval-form .approval-boundaries { grid-template-columns: 1fr; }
|
|
14700
|
-
.chat-approval-form .approval-confirm { display: grid; }
|
|
14701
|
-
.chat-approval-form .approval-confirm button { width: 100%; }
|
|
14702
14963
|
.chat-budget { gap: .3rem; margin: .65rem 0 .9rem; }
|
|
14703
14964
|
.chat-budget > span { padding: .25rem .48rem; }
|
|
14704
14965
|
.chat-budget > span:first-child { max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
|
|
@@ -15012,6 +15273,15 @@ button { min-height: 44px; }
|
|
|
15012
15273
|
chips flow after it, never above the name. */
|
|
15013
15274
|
.queue-card p.row > a:first-of-type { flex: 1 1 12rem; min-width: 0; }
|
|
15014
15275
|
}
|
|
15276
|
+
/* A phone reads a long changed line wrapped, never scrolled sideways: the
|
|
15277
|
+
line keeps its numbers and marker, the code column takes what is left. */
|
|
15278
|
+
@media (max-width: 760px) {
|
|
15279
|
+
.diff-lines { overflow-x: visible; }
|
|
15280
|
+
.diff-line, .diff-review .diff-line { min-width: 0; grid-template-columns: 2.75rem 2.65rem 2.65rem minmax(0, 1fr); }
|
|
15281
|
+
.diff-review[data-mode="view"] .diff-line { grid-template-columns: 0 2.65rem 2.65rem minmax(0, 1fr); }
|
|
15282
|
+
.diff-line code { white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
15283
|
+
.diff-hunk-head { overflow-x: visible; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
15284
|
+
}
|
|
15015
15285
|
@media (max-width: 30rem) {
|
|
15016
15286
|
.dependency-repair-actions .dependency-repair-replace { grid-template-columns: minmax(0, 1fr); }
|
|
15017
15287
|
.dependency-repair-actions .dependency-repair-replace > button[type=submit] { width: 100%; }
|
|
@@ -15155,6 +15425,7 @@ const INBOX_TABS_CSS = '.inbox-tabs{display:inline-flex;gap:2px;max-width:100%;o
|
|
|
15155
15425
|
'.inbox-tabs a[aria-current="page"]{background:var(--so-paper);color:var(--so-ink);box-shadow:var(--so-pill-shadow)}' +
|
|
15156
15426
|
'.inbox-tab-count{min-width:18px;padding:0 5px;border-radius:9999px;font:500 11px/18px var(--so-mono,ui-monospace,monospace);font-variant-numeric:tabular-nums;text-align:center}' +
|
|
15157
15427
|
'.inbox-tab-count--needs{background:var(--so-signal);color:var(--so-on-signal)}' +
|
|
15428
|
+
'.inbox-ask{margin:18px 0 0}.inbox-ask>h2{display:flex;align-items:baseline;gap:8px;margin:0 0 4px;font-size:15px;font-weight:600}.inbox-ask>h2 .count{font:500 12px var(--so-mono,ui-monospace,monospace);font-variant-numeric:tabular-nums;color:var(--so-muted)}.inbox-ask h3{font-size:13px;font-weight:600;margin:12px 0 4px}' +
|
|
15158
15429
|
'.inbox-unread{display:none;position:absolute;top:4px;right:3px;width:6px;height:6px;border-radius:50%;background:var(--so-signal)}' +
|
|
15159
15430
|
'@media (max-width:760px){.inbox-tabs{display:flex;width:100%}.inbox-tabs a{flex:1;justify-content:center;min-height:44px;padding:0 6px}.inbox-unread{display:block}}';
|
|
15160
15431
|
const WORKSPACE_STYLE = styleAsset(STYLE + INBOX_TABS_CSS + APPROVAL_RULES_CSS + SPEND_CSS + RETENTION_CSS + STORAGE_CSS + UPDATES_CSS + LIMITS_CSS + MONITORING_CSS + INTEGRATIONS_CSS + BACKUP_CSS + EXPORT_CSS + PROJECT_DELETE_CSS + POLICY_CSS + EVIDENCE_PACK_CSS + THEME_CONTROLS_CSS + CODING_CSS + CODING_SHIPPING_CSS + RECIPE_CSS + SKILLS_CSS + TOOLS_CSS + FLOWS_CSS + TEAMMATE_CSS + KITS_CSS + STARTERS_CSS + GALLERY_CSS + SSO_CSS + CREDENTIALS_CSS + KNOWLEDGE_CSS + MODELS_CSS + CHAT_POLISH_CSS + TRANSITIONS_CSS + WORKSPACE_MOTION_CSS + ASSIGNMENT_CSS + TASK_STATUS_CSS + LEAD_CONTEXT_CSS + PULL_REQUEST_SETTINGS_CSS + CHECK_SETTINGS_CSS + '.learning{min-width:0;overflow-wrap:anywhere}.learning .card{min-width:0}.learning code,.learning blockquote,.learning pre{white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word}.learning button,.learning summary,.learning .button-link{min-height:44px}.learning button{white-space:nowrap}.learning summary{padding:12px 0;cursor:pointer}.learning form{margin:12px 0}.learning select{max-width:100%}.learning blockquote{margin:8px 0}.learning ul{padding-left:20px}');
|
|
@@ -15880,13 +16151,17 @@ function inboxPage(chrome, data) {
|
|
|
15880
16151
|
const listRows = (rows) => rows.map(one => `<p class="row"><a href="${taskHref(one.taskId)}">${escape(one.title)}</a>${chip(one.repo)} <span class="meta">${escape(one.detail)}</span></p>`).join("\n");
|
|
15881
16152
|
const ready = (data.ready ?? []).length === 0 ? "" : `<h2>ready to review</h2>` + listRows(data.ready ?? []);
|
|
15882
16153
|
const running = (data.running ?? []).length === 0 ? "" : `<h2>running now</h2>` + listRows(data.running ?? []);
|
|
15883
|
-
const
|
|
15884
|
-
|
|
16154
|
+
const asks = { decide: data.decisions.length + data.approvals.length, unblock: data.requeueables.length + data.cancelledBlockers.length + data.gaps.length };
|
|
16155
|
+
const counts = { ...asks,
|
|
16156
|
+
"needs-you": asks.decide + asks.unblock,
|
|
15885
16157
|
ready: data.needsVerification.length + (data.ready ?? []).length,
|
|
15886
16158
|
running: (data.running ?? []).length,
|
|
15887
16159
|
all: 0,
|
|
15888
16160
|
};
|
|
15889
16161
|
counts.all = counts["needs-you"] + counts.ready + counts.running;
|
|
16162
|
+
// Each ask is a small heading with its count over its sections (their own headings one step down).
|
|
16163
|
+
const askGroup = (ask, count, sections) => count === 0 ? ""
|
|
16164
|
+
: `<section class="inbox-ask" data-ask="${ask}"><h2>${ASK_LABEL[ask]} <span class="count">${count}</span></h2>${sections.join("").replace(/<(\/?)h2>/g, "<$1h3>")}</section>`;
|
|
15890
16165
|
const tabs = data.tab === undefined ? "" : `<nav class="inbox-tabs" aria-label="Inbox views">` + INBOX_TABS.map(one => `<a href="/inbox?tab=${one.id}"${one.id === tab ? ` aria-current="page"` : ""} data-inbox-tab="${one.id}">${one.label}` +
|
|
15891
16166
|
`<span class="inbox-tab-count${one.id === "needs-you" && counts[one.id] > 0 ? " inbox-tab-count--needs" : ""}">${counts[one.id]}</span>` +
|
|
15892
16167
|
`${one.id !== tab && (data.unread ?? []).includes(one.id) ? `<span class="inbox-unread" aria-label="new"></span>` : ""}</a>`).join("") + `</nav>`;
|
|
@@ -15900,14 +16175,11 @@ function inboxPage(chrome, data) {
|
|
|
15900
16175
|
wizard,
|
|
15901
16176
|
empty || !shows("needs-you") ? "" : `<p><a class="new-task" style="display:inline-block" href="/next">clear the queue \u2192 one thing at a time</a></p>`,
|
|
15902
16177
|
empty && data.wizard === null && shows("needs-you") && counts.all === 0 ? `<div class="card"><p><strong>Nothing needs you.</strong></p><p class="meta">The queue is either working or waiting on its own timers. <a href="/board">Watch the board</a> or <a href="/activity">read the activity report</a>.</p></div>` : tabEmpty,
|
|
15903
|
-
|
|
15904
|
-
shows("needs-you") ? approvals : "",
|
|
15905
|
-
shows("
|
|
15906
|
-
shows("
|
|
15907
|
-
shows("ready") ? ready : "",
|
|
16178
|
+
// What waits on a person, grouped by what it asks: Decide, then Review, then Unblock.
|
|
16179
|
+
shows("needs-you") ? askGroup("decide", counts.decide, [approvals, decisions]) : "",
|
|
16180
|
+
shows("ready") ? askGroup("review", counts.ready, [needsVerification, ready]) : "",
|
|
16181
|
+
shows("needs-you") ? askGroup("unblock", counts.unblock, [requeueables, cancelled, gaps]) : "",
|
|
15908
16182
|
shows("running") ? running : "",
|
|
15909
|
-
shows("needs-you") ? cancelled : "",
|
|
15910
|
-
shows("needs-you") ? gaps : "",
|
|
15911
16183
|
data.rollup
|
|
15912
16184
|
? `<p class="meta">requirement gaps are checked one project at a time \u2014 open a project to see and fill its gaps · <a href="/projects">open a project</a></p>`
|
|
15913
16185
|
: "",
|
|
@@ -16530,80 +16802,31 @@ function taskChatApproval(focus, csrf) {
|
|
|
16530
16802
|
if (approval.nonce === "") {
|
|
16531
16803
|
return `<section class="card chat-action-card" id="task-chat-action"><span class="eyebrow">approval needs attention</span><h2>The agent setup isn’t ready yet</h2>${profileWords(scope)}<a class="button-link" href="${taskHref(focus.id)}#scope">Fix the agent setup →</a></section>`;
|
|
16532
16804
|
}
|
|
16533
|
-
|
|
16534
|
-
|
|
16535
|
-
? null
|
|
16536
|
-
: profile.provider === "claude"
|
|
16537
|
-
? profile.permissionArgv === "bypassPermissions" ? "Full access" : "Auto permissions"
|
|
16538
|
-
: profile.provider === "gemini"
|
|
16539
|
-
? profile.approvalArgv === "yolo" ? "Full access" : "Auto permissions"
|
|
16540
|
-
: profile.sandboxMode === "danger-full-access" ? "Full access" : "Workspace sandbox";
|
|
16541
|
-
const revision = approval.revision === null
|
|
16542
|
-
? ""
|
|
16543
|
-
: `<div class="chat-approval-section"><span class="approval-label">${approval.revision.kind === "ci-repair" ? "CI repair" : approval.revision.kind === "criterion-repair" ? "criterion repair" : "revision notes"}</span>` +
|
|
16544
|
-
(approval.revision.comments.length === 0 ? "" : `<ul class="recap">${approval.revision.comments.map(one => `<li>${one.path === null ? "" : `<span class="mono">${escape(one.path)}${one.line === null ? "" : `:${one.line}`}</span> · `}${escape(one.note)} <span class="meta">— ${escape(one.author)}</span></li>`).join("")}</ul>`) +
|
|
16545
|
-
// The link back (package 3): the original result stays on record
|
|
16546
|
-
// and one tap away from the revision it produced, in both directions.
|
|
16547
|
-
`<p class="meta"><a href="${escape(chatResultHref(focus.id, approval.revision.sourceRun))}" data-revision-source>Original result: build #${approval.revision.sourceRun} →</a></p>` +
|
|
16548
|
-
revisionLineageHtml(approval.revision.lineage) +
|
|
16549
|
-
`</div>`;
|
|
16550
|
-
const race = approval.raceTerms === null
|
|
16551
|
-
? ""
|
|
16552
|
-
: `<div class="chat-approval-section"><span class="approval-label">${approval.raceTerms.kind === "comparison" ? "comparison" : "tournament"}</span>` +
|
|
16553
|
-
`<p>${approval.raceTerms.agents.length} agents build independently: ${approval.raceTerms.agents.map(one => `<span class="mono">${escape(one.provider)} · ${escape(one.model)}</span>`).join(" vs ")}.</p>` +
|
|
16554
|
-
(approval.raceTerms.kind === "comparison"
|
|
16555
|
-
? `<p class="meta">No dollar caps; every result and its evidence is kept for you to compare.</p>`
|
|
16556
|
-
: `<p class="meta">$${(approval.raceTerms.perAgentBudgetMicrousd / 1_000_000).toFixed(2)} per agent plus $${(approval.raceTerms.overrunReserveMicrousd / 1_000_000).toFixed(2)} reserve; $${(approval.raceTerms.totalBudgetMicrousd / 1_000_000).toFixed(2)} total.</p>`) +
|
|
16557
|
-
`</div>`;
|
|
16558
|
-
// The concise plan (package 2, revised on the operator's screenshot
|
|
16559
|
-
// feedback and AGENTS.md): a short title, the outcome in one bounded
|
|
16560
|
-
// line, one compact line of counts and limits, and ONE Review plan
|
|
16561
|
-
// action — no eyebrow, no second sentence saying the same thing, no
|
|
16562
|
-
// footer. Every word is a signed fact the exact terms below restate in
|
|
16563
|
-
// full; the password and approval instructions live inside the
|
|
16564
|
-
// expanded review, where Approve & start is the only submit.
|
|
16565
|
-
const plan = approval.planDocument === null ? null : parseExecutionPlanDocument(approval.planDocument);
|
|
16566
|
-
const milestones = plan !== null && plan.ok ? plan.document.milestones : [];
|
|
16567
|
-
const count = (n, singular) => `${n} ${singular}${n === 1 ? "" : "s"}`;
|
|
16568
|
-
const facts = [
|
|
16569
|
-
milestones.length > 0 ? count(milestones.length, "step") : count(scope.touches.length, "path"),
|
|
16570
|
-
count(scope.acceptance.length, "check"),
|
|
16571
|
-
];
|
|
16572
|
-
if (approval.deliverable === "report")
|
|
16573
|
-
facts.push("Report only");
|
|
16574
|
-
if (scope.budgetMicrousd !== null)
|
|
16575
|
-
facts.push(`$${(scope.budgetMicrousd / 1_000_000).toFixed(2)} attempt cap`);
|
|
16576
|
-
if (permission !== null)
|
|
16577
|
-
facts.push(permission);
|
|
16578
|
-
if (approval.raceTerms !== null)
|
|
16579
|
-
facts.push(`${approval.raceTerms.agents.length}-agent ${approval.raceTerms.kind}`);
|
|
16805
|
+
// The same sheet as the task page (approval critique, Oct 2): the plan
|
|
16806
|
+
// open in plain rows, one Approve & start, the rest in Details.
|
|
16580
16807
|
return (`<section class="card chat-action-card chat-plan" id="task-chat-action" data-approval="${escape(approval.digest)}">` +
|
|
16581
|
-
|
|
16582
|
-
|
|
16583
|
-
|
|
16584
|
-
|
|
16585
|
-
|
|
16586
|
-
|
|
16587
|
-
|
|
16588
|
-
|
|
16589
|
-
|
|
16590
|
-
|
|
16591
|
-
|
|
16592
|
-
|
|
16593
|
-
|
|
16594
|
-
|
|
16595
|
-
|
|
16596
|
-
|
|
16597
|
-
|
|
16598
|
-
|
|
16599
|
-
|
|
16600
|
-
|
|
16601
|
-
|
|
16602
|
-
`</
|
|
16603
|
-
agentsCeremonyHtml(focus.route) +
|
|
16604
|
-
runtimeDetailsHtml(scope) +
|
|
16605
|
-
`<div class="approval-confirm"><label>Your password <span class="meta">— confirms this exact scope</span><input type="password" name="token" autocomplete="current-password" placeholder="Password"></label>` +
|
|
16606
|
-
`<button type="submit">Approve & start</button></div></form></details></section>`);
|
|
16808
|
+
approvalSheetHtml({
|
|
16809
|
+
surface: "chat",
|
|
16810
|
+
action: `${taskHref(focus.executionId)}/approve`,
|
|
16811
|
+
csrf,
|
|
16812
|
+
nonce: approval.nonce,
|
|
16813
|
+
digest: approval.digest,
|
|
16814
|
+
returnTo,
|
|
16815
|
+
scope,
|
|
16816
|
+
planDocument: approval.planDocument,
|
|
16817
|
+
planContract: approval.planDocument === null ? null : approval.planContract,
|
|
16818
|
+
revision: approval.revision,
|
|
16819
|
+
revisionSourceHref: approval.revision === null ? "" : chatResultHref(focus.id, approval.revision.sourceRun),
|
|
16820
|
+
repairChain: approval.repairChain,
|
|
16821
|
+
raceTerms: approval.raceTerms,
|
|
16822
|
+
route: focus.route,
|
|
16823
|
+
coordinator: approval.coordinator,
|
|
16824
|
+
deliverable: approval.deliverable,
|
|
16825
|
+
editHref: `${taskHref(focus.id)}#${approval.planDocument === null ? "scope" : "plan-edit"}`,
|
|
16826
|
+
notNowHref: "/chat",
|
|
16827
|
+
sticky: false,
|
|
16828
|
+
}) +
|
|
16829
|
+
`</section>`);
|
|
16607
16830
|
}
|
|
16608
16831
|
/** One live, server-derived journey from request to proof. The fragment is
|
|
16609
16832
|
* safe to refresh independently, so an in-progress message is never lost. */
|
|
@@ -17972,6 +18195,7 @@ function statusLineHtml(status, extra = "") {
|
|
|
17972
18195
|
function workDiagnosticsHtml(diagnostics) {
|
|
17973
18196
|
return (diagnostics ?? []).map(one => `<p class="work-detail" data-work-diagnostic="${escape(one.token)}"><strong>${escape(one.label)}</strong> · ${escape(one.detail)}</p>`).join("");
|
|
17974
18197
|
}
|
|
18198
|
+
const TASK_GROUP_LABEL = { ...ASK_LABEL, building: 'Building', rest: 'Recent' };
|
|
17975
18199
|
function workPage(chrome, data) {
|
|
17976
18200
|
const href = (view, cursor) => {
|
|
17977
18201
|
const query = new URLSearchParams();
|
|
@@ -18014,6 +18238,10 @@ function workPage(chrome, data) {
|
|
|
18014
18238
|
const view = {
|
|
18015
18239
|
kind: 'tasks',
|
|
18016
18240
|
tabs: WORK_VIEWS.map(one => ({ label: one.label, href: href(one.key), count: data.work.totals[one.key], active: one.key === data.view })),
|
|
18241
|
+
needsYou: data.work.totals['needs-you'],
|
|
18242
|
+
groups: data.view !== 'all' && data.view !== 'needs-you' ? null
|
|
18243
|
+
: [...ASKS, ...(data.view === 'all' ? ['building', 'rest'] : [])]
|
|
18244
|
+
.map(key => ({ key, label: TASK_GROUP_LABEL[key], count: data.work.groups[key] })).filter(one => one.count > 0),
|
|
18017
18245
|
rows: data.work.items.map(row => {
|
|
18018
18246
|
const target = row.primaryAction?.target;
|
|
18019
18247
|
const actionHref = row.primaryAction?.code === 'open-result' && target?.runId != null
|
|
@@ -18025,6 +18253,7 @@ function workPage(chrome, data) {
|
|
|
18025
18253
|
project: data.multiProject || row.repo === null ? (row.repo === null ? 'Unplaced' : projectName(row.repo)) : null,
|
|
18026
18254
|
age: relativeAge(row.updatedAt, data.now),
|
|
18027
18255
|
status: { label: row.status.label, tone: row.status.tone, token: row.status.token },
|
|
18256
|
+
ask: row.ask, group: row.ask ?? (row.status.rank === 1 ? 'building' : 'rest'),
|
|
18028
18257
|
action: actionHref === null || row.primaryAction === null ? null : { label: row.primaryAction.label, href: actionHref },
|
|
18029
18258
|
detail: needsYouDetail ? row.status.detail : null,
|
|
18030
18259
|
problem: row.familyProblem,
|
|
@@ -19846,6 +20075,10 @@ function taskBodyParts(data) {
|
|
|
19846
20075
|
: approval.approved
|
|
19847
20076
|
? "approved"
|
|
19848
20077
|
: "review before starting";
|
|
20078
|
+
// While the approval sheet is open it states any amendment; the plan
|
|
20079
|
+
// card does not repeat it (one #contract-amendment on the page).
|
|
20080
|
+
const sheetStatesContract = scope !== null && !approval.approved && data.plan === "drafted" && data.dispatch?.action !== "repair-dependency" &&
|
|
20081
|
+
!(data.revision != null && "problem" in data.revision) && scope.profileState !== "unresolved" && consentDoorOf(scope, data.route).open;
|
|
19849
20082
|
const planCard = data.planDocument === null
|
|
19850
20083
|
? data.plan === "requested"
|
|
19851
20084
|
? data.revision != null && !("problem" in data.revision)
|
|
@@ -19859,10 +20092,10 @@ function taskBodyParts(data) {
|
|
|
19859
20092
|
(approval.approved ? `<p class="meta">The agent can adapt this route when evidence changes; your approved outcome stays fixed.</p>` : "") +
|
|
19860
20093
|
`</div><span class="plan-lock">${planStanding}</span>${approval.approved ? `</summary><div class="planner-plan-body">` : `</div>`}` +
|
|
19861
20094
|
executionPlanHtml(displayedPlanDocument ?? data.planDocument) +
|
|
19862
|
-
(approval.approved ? "" : planContractHtml(data.planContract ?? null, "full")) +
|
|
20095
|
+
(approval.approved || sheetStatesContract ? "" : planContractHtml(data.planContract ?? null, "full")) +
|
|
19863
20096
|
(data.csrf === "" || data.planSha == null || approval.approved
|
|
19864
20097
|
? ""
|
|
19865
|
-
: `<details class="plan-editor"><summary>Edit plan</summary><form method="post" action="${taskHref(task.id)}/plan-edit">` +
|
|
20098
|
+
: `<details class="plan-editor" id="plan-edit"><summary>Edit plan</summary><form method="post" action="${taskHref(task.id)}/plan-edit">` +
|
|
19866
20099
|
`<input type="hidden" name="csrf" value="${escape(data.csrf)}"><input type="hidden" name="saw-plan" value="${escape(data.planSha)}">` +
|
|
19867
20100
|
`<label>plan details <span class="meta">Keep the five headings. Approval locks this version for the build.</span>` +
|
|
19868
20101
|
`<textarea name="plan-document" rows="14">${escape(data.planDocument)}</textarea></label>` +
|
|
@@ -19889,20 +20122,10 @@ function taskBodyParts(data) {
|
|
|
19889
20122
|
// The approval form restates every field the digest binds — an operator
|
|
19890
20123
|
// approves what is on this form, not what is elsewhere on the page — and
|
|
19891
20124
|
// requires the token typed again. The session got you here; only the
|
|
19892
|
-
// token agrees.
|
|
19893
|
-
//
|
|
19894
|
-
//
|
|
19895
|
-
//
|
|
19896
|
-
// shape. An unapprovable scope gets the problem and the edit road
|
|
19897
|
-
// instead of a password it cannot use.
|
|
19898
|
-
const approvalProfile = scope?.profile ?? null;
|
|
19899
|
-
const approvalPermission = approvalProfile === null
|
|
19900
|
-
? null
|
|
19901
|
-
: approvalProfile.provider === "claude"
|
|
19902
|
-
? approvalProfile.permissionArgv === "bypassPermissions" ? "Full access" : "Auto permissions"
|
|
19903
|
-
: approvalProfile.provider === "gemini"
|
|
19904
|
-
? approvalProfile.approvalArgv === "yolo" ? "Full access" : "Auto permissions"
|
|
19905
|
-
: approvalProfile.sandboxMode === "danger-full-access" ? "Full access" : "Workspace sandbox";
|
|
20125
|
+
// token agrees. The sheet shows the plan open in plain rows with one
|
|
20126
|
+
// Approve & start; the rest of the signed terms sit in its Details fold.
|
|
20127
|
+
// An unapprovable scope gets the problem and the edit road instead of a
|
|
20128
|
+
// password it cannot use.
|
|
19906
20129
|
const consentDoor = consentDoorOf(scope, data.route);
|
|
19907
20130
|
const revisionInApproval = scope !== null && !approval.approved && data.plan !== "requested" &&
|
|
19908
20131
|
scope.profileState !== "unresolved" && consentDoor.open && data.dispatch?.action !== "repair-dependency" &&
|
|
@@ -19917,79 +20140,27 @@ function taskBodyParts(data) {
|
|
|
19917
20140
|
`<p class="ceremony-road"><a class="button-link" href="#scope">edit the scope to fix it →</a></p></div>`
|
|
19918
20141
|
: !consentDoor.open
|
|
19919
20142
|
? consentClosedHtml(task.id, consentDoor, "task")
|
|
19920
|
-
:
|
|
19921
|
-
|
|
19922
|
-
|
|
19923
|
-
|
|
19924
|
-
|
|
19925
|
-
|
|
19926
|
-
|
|
19927
|
-
|
|
19928
|
-
|
|
19929
|
-
|
|
19930
|
-
|
|
19931
|
-
|
|
19932
|
-
|
|
19933
|
-
|
|
19934
|
-
data.
|
|
19935
|
-
|
|
19936
|
-
|
|
19937
|
-
|
|
19938
|
-
|
|
19939
|
-
|
|
19940
|
-
|
|
19941
|
-
? ""
|
|
19942
|
-
: `<p class="meta">filed by <span class="mono">${escape(data.coordinator.label)}</span>${data.coordinator.filedAgo === null ? "" : ` \u00b7 ${escape(data.coordinator.filedAgo)}`} — an agent asked for this; nothing plans, claims, or runs until you sign, and your signature runs THEIR request</p>`,
|
|
19943
|
-
`<p class="approval-label">goal</p><p class="approval-goal">${escape(scope.goal)}</p>`,
|
|
19944
|
-
scope.candidate ? `<p class="approval-label">Saved commit</p><p style="overflow-wrap:anywhere"><code>${escape(scope.candidate)}</code></p>` : "",
|
|
19945
|
-
revisionCard,
|
|
19946
|
-
`<div class="approval-boundaries">`,
|
|
19947
|
-
`<div class="approval-boundary"><p class="approval-label">not this</p><p>${scope.outOfScope === null ? "<em>no exclusions</em>" : escape(scope.outOfScope)}</p></div>`,
|
|
19948
|
-
`<div class="approval-boundary"><p class="approval-label">touches</p>${approvalPathsHtml(scope.touches)}</div>`,
|
|
19949
|
-
`</div>`,
|
|
19950
|
-
acceptanceCeremonyHtml(scope.acceptance),
|
|
19951
|
-
// The contract amendment INSIDE the ceremony (contract handoff,
|
|
19952
|
-
// task 1): what the yes accepts that the operator did not file.
|
|
19953
|
-
data.plan === "drafted" ? planContractHtml(data.planContract ?? null, "ceremony") : "",
|
|
19954
|
-
`</div>`,
|
|
19955
|
-
`<div class="approval-chips"><span class="approval-chip">quality · <strong>${escape(qualityModeTitle(scope.qualityMode ?? "default"))}</strong></span>` +
|
|
19956
|
-
(approvalPermission === null ? "" : `<span class="approval-chip">${escape(approvalPermission)}</span>`) +
|
|
19957
|
-
`</div>`,
|
|
19958
|
-
// The revision feedback above includes its lineage: the source,
|
|
19959
|
-
// carried terms, re-resolved terms, and grants that never inherit.
|
|
19960
|
-
// The AGENTS inside the ceremony (v47/v48): who plans, builds,
|
|
19961
|
-
// repairs, and reviews, and why — said where the yes is given, in
|
|
19962
|
-
// the same block chat and /next show. Availability is volatile and
|
|
19963
|
-
// stays outside these terms; the runtime limits the profile seals
|
|
19964
|
-
// are one tap away, never in the way.
|
|
19965
|
-
agentsCeremonyHtml(data.route),
|
|
19966
|
-
runtimeDetailsHtml(scope),
|
|
19967
|
-
scope.budgetMicrousd === null
|
|
19968
|
-
? ""
|
|
19969
|
-
: `<p class="meta">each build attempt has a $${(scope.budgetMicrousd / 1_000_000).toFixed(2)} agent-reported usage cap — on a subscription this is a work limiter, not an API charge</p>`,
|
|
19970
|
-
// One yes covers BOTH documents (finding 31): the race terms are
|
|
19971
|
-
// restated on the same card the password signs, or they are not
|
|
19972
|
-
// approved at all.
|
|
19973
|
-
data.raceTerms === null || data.raceTerms === undefined
|
|
19974
|
-
? ""
|
|
19975
|
-
: data.raceTerms.kind === "comparison"
|
|
19976
|
-
? `<p><strong>and this comparison:</strong></p>` +
|
|
19977
|
-
`<p class="recap" style="margin-top:0">${data.raceTerms.n} agents build this independently — ` +
|
|
19978
|
-
`${data.raceTerms.agents.map(agent => `${escape(agent.provider)} · ${escape(agent.model)}`).join(" vs ")}. ` +
|
|
19979
|
-
`No dollar caps exist on a comparison — each agent runs until it finishes or stops making progress; ` +
|
|
19980
|
-
`spend lands measured only where the harness reports dollars (` +
|
|
19981
|
-
`${data.raceTerms.agents.filter(agent => agent.provider === "claude").length} of ${data.raceTerms.n} lanes here). ` +
|
|
19982
|
-
`You will compare the results and pick one.</p>`
|
|
19983
|
-
: `<p><strong>and this tournament:</strong></p>` +
|
|
19984
|
-
`<p class="recap" style="margin-top:0">${data.raceTerms.n} agents build this independently — ` +
|
|
19985
|
-
`${data.raceTerms.agents.map(agent => `${escape(agent.provider)} · ${escape(agent.model)}`).join(" vs ")}. ` +
|
|
19986
|
-
`Each may spend $${(data.raceTerms.perAgentBudgetMicrousd / 1_000_000).toFixed(2)} plus a ` +
|
|
19987
|
-
`$${(data.raceTerms.overrunReserveMicrousd / 1_000_000).toFixed(2)} overrun reserve; the whole tournament is capped at ` +
|
|
19988
|
-
`$${(data.raceTerms.totalBudgetMicrousd / 1_000_000).toFixed(2)}. You will compare the results and pick one.</p>`,
|
|
19989
|
-
`<div class="approval-confirm" id="approval-confirm"><label>Your password <span class="meta">— confirms exactly the terms above</span><input type="password" name="token" autocomplete="current-password" placeholder="Password"></label>`,
|
|
19990
|
-
`<div class="sticky-actions"><button type="submit">${data.raceTerms === null || data.raceTerms === undefined ? "Approve & start" : data.raceTerms.kind === "comparison" ? "Approve comparison" : "Approve tournament"}</button></div></div>`,
|
|
19991
|
-
`</form>`,
|
|
19992
|
-
].join("\n");
|
|
20143
|
+
: approvalSheetHtml({
|
|
20144
|
+
surface: "task",
|
|
20145
|
+
action: `${taskHref(task.id)}/approve`,
|
|
20146
|
+
csrf: data.csrf,
|
|
20147
|
+
nonce: data.nonce,
|
|
20148
|
+
digest: data.approvalDigest ?? scope.digest,
|
|
20149
|
+
returnTo: null,
|
|
20150
|
+
scope,
|
|
20151
|
+
planDocument: data.plan === "drafted" ? data.planDocument : null,
|
|
20152
|
+
planContract: data.plan === "drafted" ? data.planContract ?? null : null,
|
|
20153
|
+
revision: data.revision == null || "problem" in data.revision ? null : data.revision,
|
|
20154
|
+
revisionSourceHref: data.revision == null || "problem" in data.revision ? "" : `/r/${data.revision.sourceRun}`,
|
|
20155
|
+
repairChain: data.repairChain ?? null,
|
|
20156
|
+
raceTerms: data.raceTerms ?? null,
|
|
20157
|
+
route: data.route,
|
|
20158
|
+
coordinator: data.coordinator ?? null,
|
|
20159
|
+
deliverable: data.deliverable ?? "branch",
|
|
20160
|
+
editHref: data.csrf !== "" && data.planSha != null && data.planDocument !== null ? "#plan-edit" : "#scope",
|
|
20161
|
+
notNowHref: "/work",
|
|
20162
|
+
sticky: data.dispatch?.action === "approve-scope",
|
|
20163
|
+
});
|
|
19993
20164
|
// The attended road (Phase 2E): beside the approval, never replacing it.
|
|
19994
20165
|
// The mint button leads to the CONFIRM screen where every term renders
|
|
19995
20166
|
// and the password signs; an open authorization shows its state, its
|
|
@@ -20514,7 +20685,7 @@ function taskBodyParts(data) {
|
|
|
20514
20685
|
`<p class="meta"><strong>Plan first</strong> drafts it from the repository, or <a href="#scope">write it yourself</a>.</p></div>`
|
|
20515
20686
|
: "";
|
|
20516
20687
|
const approvalHtml = dependencyChoiceNeeded || approveForm === "" ? "" : data.dispatch?.action === "approve-scope"
|
|
20517
|
-
? `<
|
|
20688
|
+
? `<section class="task-plan-review" aria-label="Approve the plan">${approveForm}</section>`
|
|
20518
20689
|
: `<details class="task-secondary-approval"><summary>Updated approval terms</summary>${approveForm}</details>`;
|
|
20519
20690
|
const optionsHtml = `${identity}${dispatchStatus}${dependencyChoiceNeeded ? "" : actsBar}`;
|
|
20520
20691
|
const optionsOpen = data.assignment?.primaryAction?.code === "unhold" || data.assignment?.primaryAction?.code === "retry-task";
|
|
@@ -21204,7 +21375,7 @@ function reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab, user)
|
|
|
21204
21375
|
taskHref: taskHref(view.taskId), chatHref: taskChatHref(view.taskId),
|
|
21205
21376
|
status: { label: status.label, tone: status.tone, token: status.token },
|
|
21206
21377
|
problem: view.detail === null ? view.historyProblem : null,
|
|
21207
|
-
next: reviewNextActionOf(view, csrf), complete: null, checks: null, intent: intentView, noRun: null, panel: null, contest: "",
|
|
21378
|
+
next: reviewNextActionOf(view, csrf), complete: null, decision: null, checks: null, intent: intentView, noRun: null, panel: null, contest: "",
|
|
21208
21379
|
notes: view.notes.map(one => ({ author: one.author, at: one.createdAt, note: one.note })),
|
|
21209
21380
|
};
|
|
21210
21381
|
if (run === null || view.detail === null) {
|
|
@@ -21245,6 +21416,17 @@ function reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab, user)
|
|
|
21245
21416
|
? { action: `${taskHref(view.taskId)}/complete`, receipt: assignment.receipt.digest, run: run.id } : null;
|
|
21246
21417
|
if (complete !== null)
|
|
21247
21418
|
parts.push(completionForm(view.taskId, run.id, complete.receipt, csrf, view.detail?.pullRequestTo ?? null));
|
|
21419
|
+
// The one decision, after the evidence: Accept only when every requirement is met and the checks passed.
|
|
21420
|
+
const acceptsHere = complete !== null || panel.panel.need?.accept != null;
|
|
21421
|
+
const matrix = proof === null || proof.proofProblem !== null ? [] : proof.matrix;
|
|
21422
|
+
const decision = !acceptsHere ? null : { sentence: RESULT_DECISION_SENTENCE, ...acceptWordsOf({
|
|
21423
|
+
checks: checks === undefined ? null : checks.running != null ? "running" : checks.level === "off" && checks.status !== "passed" ? "off" : checks.status,
|
|
21424
|
+
unmet: matrix.filter(row => row.state !== "pass" && row.state !== "manual-review").length,
|
|
21425
|
+
yours: complete === null || accepted ? 0 : panel.panel.youCheck?.lines.length ?? 0,
|
|
21426
|
+
action: complete !== null ? "complete" : "accept",
|
|
21427
|
+
publishing: view.detail?.publishing ?? "other",
|
|
21428
|
+
proof: proof !== null && proof.proof !== null && proof.proofProblem === null,
|
|
21429
|
+
}) };
|
|
21248
21430
|
if (view.notes.length > 0) {
|
|
21249
21431
|
parts.push(`<section class="card cockpit-section" data-cockpit-section="notes"><h3>operator notes</h3>` +
|
|
21250
21432
|
view.notes.map(one => `<p class="row"><span class="meta">${escape(one.author)} · ${whenTime(one.createdAt)}</span> ${escape(one.note)}</p>`).join("\n") +
|
|
@@ -21253,7 +21435,7 @@ function reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab, user)
|
|
|
21253
21435
|
return {
|
|
21254
21436
|
html: `<section class="cockpit-detail">${parts.join("\n")}</section>`,
|
|
21255
21437
|
selected: {
|
|
21256
|
-
...selected, complete, panel: panel.panel, contest,
|
|
21438
|
+
...selected, complete, decision, panel: panel.panel, contest,
|
|
21257
21439
|
checks: checks === undefined ? null : { detail: checks.detail, problem: checks.status === "failed" || checks.status === "unavailable", logHref: checks.logArtifactId === null ? null : `/r/${run.id}/evidence/${checks.logArtifactId}` },
|
|
21258
21440
|
},
|
|
21259
21441
|
};
|
|
@@ -22229,6 +22411,7 @@ function resultPanelParts(detail, o) {
|
|
|
22229
22411
|
const acceptable = personChecks.length > 0 && humanReview && o.csrf !== "" && (current != null || detail.assignment == null);
|
|
22230
22412
|
const youCheck = personChecks.length === 0 ? null : {
|
|
22231
22413
|
lines: [...new Set(personChecks)],
|
|
22414
|
+
items: personCheckItems(proof, patchOk ? patch.text : null, shown, runId),
|
|
22232
22415
|
accept: acceptable && need?.accept == null ? { action: `${taskHref(detail.taskId)}/accept-proof`, run: run.id, returnTo: o.returnTo } : null,
|
|
22233
22416
|
};
|
|
22234
22417
|
const youCheckHtml = youCheck === null ? "" :
|
|
@@ -22537,6 +22720,37 @@ function resultPanelParts(detail, o) {
|
|
|
22537
22720
|
};
|
|
22538
22721
|
return { html, panel };
|
|
22539
22722
|
}
|
|
22723
|
+
/** Changed lines shown beside one "You check this one" item: at most this many per file, three files. */
|
|
22724
|
+
const CHECK_EXCERPT_LINES = 12;
|
|
22725
|
+
/** Each requirement only a person can confirm, with the evidence to judge
|
|
22726
|
+
* it by, inline: the changed lines in the files it cites (citing none, the
|
|
22727
|
+
* change's first file, labelled as not cited), the screenshots it cites
|
|
22728
|
+
* (none cited: every validated one), and the agent's own note. */
|
|
22729
|
+
function personCheckItems(proof, patchText, shots, runId) {
|
|
22730
|
+
const rows = proof.matrix.filter(row => row.state === "manual-review");
|
|
22731
|
+
if (rows.length === 0)
|
|
22732
|
+
return proof.reasons.some(reason => manualReviewCriterionOf(reason) !== null)
|
|
22733
|
+
? [{ id: "you-check", statement: "", words: personCheckWords(null), note: null, excerpts: [], shots: [] }] : [];
|
|
22734
|
+
const diff = patchText === null ? null : parseReviewDiff(patchText);
|
|
22735
|
+
const excerptOf = (file, cited) => {
|
|
22736
|
+
const changed = file.hunks.flatMap(hunk => hunk.lines).filter((line) => line.kind === "addition" || line.kind === "deletion");
|
|
22737
|
+
return { path: file.path, cited, lines: changed.slice(0, CHECK_EXCERPT_LINES).map(line => ({ kind: line.kind, line: line.newLine ?? line.oldLine, text: line.text })), more: Math.max(0, changed.length - CHECK_EXCERPT_LINES) };
|
|
22738
|
+
};
|
|
22739
|
+
return rows.map(row => {
|
|
22740
|
+
const refs = row.answered ?? [];
|
|
22741
|
+
const paths = refs.filter(one => one.kind === "changed-path").map(one => one.ref);
|
|
22742
|
+
const files = diff === null ? [] : paths.length > 0 ? diff.files.filter(file => paths.includes(file.path)).map(file => excerptOf(file, true))
|
|
22743
|
+
: diff.files.slice(0, 1).map(file => excerptOf(file, false));
|
|
22744
|
+
const cited = refs.filter(one => one.kind === "screenshot").map(one => one.ref);
|
|
22745
|
+
const pictures = cited.length > 0 ? shots.filter(shot => cited.includes(shot.path)) : shots;
|
|
22746
|
+
const notes = refs.filter(one => one.kind === "manual-review").map(one => one.ref.trim()).filter(one => one !== "");
|
|
22747
|
+
return {
|
|
22748
|
+
id: row.id, statement: row.statement, words: personCheckWords(row.statement), note: notes.length === 0 ? null : notes.join(" "),
|
|
22749
|
+
excerpts: files.filter(one => one.lines.length > 0).slice(0, 3),
|
|
22750
|
+
shots: pictures.slice(0, 4).map(shot => ({ src: `/r/${runId}/evidence/${shot.artifactId}`, href: `/r/${runId}/evidence/${shot.artifactId}`, caption: shot.caption })),
|
|
22751
|
+
};
|
|
22752
|
+
});
|
|
22753
|
+
}
|
|
22540
22754
|
/** A Needs you result's one action (needs-you.ts): a link to the act that resolves it, or Confirm it
|
|
22541
22755
|
* stopped behind the password. Null under every other headline. */
|
|
22542
22756
|
export function needActionOf(assignment, status, csrf, returnTo, result) {
|