toolroll 0.9.8 → 0.9.10
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 +30 -0
- package/dist/assignment-brief.d.ts +2 -0
- package/dist/assignment-brief.js +2 -1
- package/dist/assignment-presentation.js +3 -1
- package/dist/assignment-status.d.ts +1 -1
- package/dist/assignment-ui.js +5 -0
- package/dist/assignment.d.ts +9 -0
- package/dist/assignment.js +42 -8
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +18 -18
- package/dist/browser-crew.js +4 -0
- package/dist/browser-workspace.d.ts +40 -0
- package/dist/builder.d.ts +2 -0
- package/dist/builder.js +114 -8
- package/dist/chat-controls.d.ts +1 -1
- package/dist/chat-controls.js +1 -1
- package/dist/chat-delivery-state.d.ts +5 -0
- package/dist/chat-delivery.js +45 -8
- package/dist/chat-quiet.d.ts +10 -4
- package/dist/chat-quiet.js +87 -22
- package/dist/chat-voice.d.ts +68 -0
- package/dist/chat-voice.js +175 -0
- package/dist/check-progress.js +4 -3
- package/dist/checkout-cleanup.d.ts +40 -2
- package/dist/checkout-cleanup.js +129 -9
- package/dist/desktop-update.d.ts +52 -0
- package/dist/desktop-update.js +147 -5
- package/dist/discord-chat.js +1 -0
- package/dist/evidence.d.ts +6 -0
- package/dist/evidence.js +8 -0
- package/dist/first-run.d.ts +1 -1
- package/dist/first-run.js +7 -5
- package/dist/lead-context.d.ts +1 -1
- package/dist/lead-context.js +4 -2
- package/dist/lead-status.d.ts +10 -1
- package/dist/lead-status.js +21 -24
- package/dist/needs-you.d.ts +55 -0
- package/dist/needs-you.js +83 -0
- package/dist/operate.d.ts +2 -0
- package/dist/operate.js +122 -15
- package/dist/plane-review.js +4 -0
- package/dist/process-recovery-eligibility.d.ts +1 -0
- package/dist/project-delete.js +1 -0
- package/dist/project-learning.d.ts +3 -3
- package/dist/project-learning.js +28 -17
- package/dist/proof.d.ts +3 -1
- package/dist/proof.js +4 -2
- package/dist/releases.d.ts +9 -0
- package/dist/releases.js +30 -0
- package/dist/result-completion.d.ts +4 -0
- package/dist/result-completion.js +11 -0
- package/dist/review-context.d.ts +3 -0
- package/dist/review-context.js +45 -5
- package/dist/run-closeout.d.ts +89 -0
- package/dist/run-closeout.js +302 -0
- package/dist/serve.d.ts +15 -1
- package/dist/serve.js +129 -14
- package/dist/setup-guide.d.ts +2 -2
- package/dist/shared-deps.d.ts +94 -0
- package/dist/shared-deps.js +365 -0
- package/dist/slack-chat.js +2 -0
- package/dist/storage.js +2 -0
- package/dist/store.d.ts +60 -0
- package/dist/store.js +207 -30
- package/dist/task-status.d.ts +18 -4
- package/dist/task-status.js +35 -18
- package/dist/teams-chat.js +1 -1
- package/dist/telegram-progress.d.ts +6 -1
- package/dist/telegram-progress.js +13 -5
- package/dist/telegram-status.d.ts +2 -1
- package/dist/telegram-status.js +6 -5
- package/dist/telegram.d.ts +1 -1
- package/dist/telegram.js +93 -43
- package/dist/test-temp.d.ts +19 -0
- package/dist/test-temp.js +98 -0
- package/dist/toolroll-update-ui.js +7 -2
- package/dist/toolroll-update.d.ts +21 -1
- package/dist/toolroll-update.js +135 -5
- package/dist/work-index.d.ts +11 -0
- package/dist/work-index.js +103 -16
- package/dist/work-summary.d.ts +3 -1
- package/dist/workspace-ui.js +1 -1
- package/dist/worktree.d.ts +16 -0
- package/dist/worktree.js +47 -0
- package/package.json +1 -1
package/dist/serve.js
CHANGED
|
@@ -71,8 +71,9 @@ import { hasDisguisedText } from './decision.js';
|
|
|
71
71
|
import { checkPublishing, completeAndOpenPullRequest, mergeAsPerson, pullRequestBlocker, publishingOf, saveMergeSettings, savePublishing, newestPullRequestOf, MERGE_METHODS } from './pull-request-flow.js';
|
|
72
72
|
import { leadBriefHtml, LEAD_CONTEXT_CSS } from './lead-context.js';
|
|
73
73
|
import { assignmentCatchUp } from './assignment-brief.js';
|
|
74
|
-
import { assignmentCardOf, assignmentStatusOf, assignmentSummaryHtml, assignmentWithEvidence, ASSIGNMENT_CSS } from './assignment-ui.js';
|
|
74
|
+
import { assignmentActionHref, assignmentCardOf, assignmentStatusOf, assignmentSummaryHtml, assignmentWithEvidence, ASSIGNMENT_CSS } from './assignment-ui.js';
|
|
75
75
|
import { assignmentStageOf, pullRequestFactOf, stageOfCode, stageOfDispatch, statusDetailsHtml, statusIconSvg, statusWhyHtml, taskStatusOf, TASK_STATUS_CSS } from './task-status.js';
|
|
76
|
+
import { NEEDS } from './needs-you.js';
|
|
76
77
|
import { assignmentPresentationOf, historicalAssessmentReason, shortenedMaterialReason } from './assignment-presentation.js';
|
|
77
78
|
import { ledgerBody } from "./ledger-view.js";
|
|
78
79
|
import { EVIDENCE_PACK_CSS, evidencePack, evidencePackHtml, exportDay, ledgerExportChunks } from "./evidence-pack.js";
|
|
@@ -227,7 +228,7 @@ import { STORAGE_CSS, storageHtml } from "./storage-ui.js";
|
|
|
227
228
|
import { bytesWords, parseCleanup } from "./storage.js";
|
|
228
229
|
import { checkoutPlan, cleanCheckouts, discardCheckout, previewDigest } from "./checkout-cleanup.js";
|
|
229
230
|
import { UPDATES_CSS, newerThan, updateStepsHtml, updatesHtml, updatesScript } from "./toolroll-update-ui.js";
|
|
230
|
-
import { abandonRuntimeUpdate, launchRuntimeUpdate, markWhatsNewSeen, prepareRuntimeUpdate, requestRuntimeUpdateCancel, runningWorkWords, runtimeUpdateStatus, runtimeUpdateTerminal, currentRuntime } from "./toolroll-update.js";
|
|
231
|
+
import { abandonRuntimeUpdate, launchRuntimeUpdate, markWhatsNewSeen, prepareRuntimeUpdate, requestRuntimeUpdateCancel, runningWorkWords, runtimeUpdateStatus, runtimeUpdateTerminal, currentRuntime, releaseStalledUpdate, waitingUpdate } from "./toolroll-update.js";
|
|
231
232
|
import { latestVersionNow } from "./releases.js";
|
|
232
233
|
import { RETENTION_KINDS, lastSweepAt, parsePeriod, periodChoices, retentionPlan } from "./retention.js";
|
|
233
234
|
import { EXPORT_CSS, dataExportHtml } from "./export-ui.js";
|
|
@@ -1696,7 +1697,7 @@ export function createDecisionServer(options) {
|
|
|
1696
1697
|
}
|
|
1697
1698
|
const read = new Set(["/settings/flows", "/settings/skills", "/settings/knowledge", "/settings", "/settings/learning", "/recipes", "/recipes/run", "/recipes/start", "/recipes/new", "/recipes/edit", "/recipes/from-task", "/recipes/preview", "/recipes/export", "/", "/inbox", "/work", "/projects", "/people", "/ledger", "/ledger/export", "/next", "/board", "/tasks", "/tasks/new", "/runs", "/review", "/done", "/routines", "/menu"]);
|
|
1698
1699
|
const write = new Set(["/settings/flows/on", "/settings/skills/import", "/settings/skills/change", "/settings/skills/revise", "/settings/knowledge/change", "/settings/knowledge/refresh", "/settings/learning/change", "/recipes/prepare", "/recipes/preview", "/recipes/import", "/recipes/save", "/recipes/launch", "/projects/select", "/tasks/add", "/routines/add"]);
|
|
1699
|
-
const task = matchTaskPath(path, request.method === "GET" ? "(/evidence)?$" : "/(hold|unhold|requeue|cancel|scope|approve|plan|plan-edit|next|reopen|steer|accept-proof|accept-revision|reject-revision|route|retry-review|complete|merge|stop|resume-arm|resume)$");
|
|
1700
|
+
const task = matchTaskPath(path, request.method === "GET" ? "(/evidence)?$" : "/(hold|unhold|requeue|cancel|scope|approve|plan|plan-edit|next|reopen|steer|accept-proof|accept-revision|reject-revision|route|retry-review|complete|merge|confirm-stopped|stop|resume-arm|resume)$");
|
|
1700
1701
|
const resource = request.method === "GET"
|
|
1701
1702
|
? /^\/(?:r|d)\/[0-9]{1,15}(?:\/evidence\/[0-9]{1,15})?$/.test(path) || /^\/routines\/[0-9]{1,15}$/.test(path) || path === "/flows" || /^\/flows\/[0-9]{1,15}(\/insights|\/export|\/runs\/[0-9]{1,15}\/[0-9]{1,15})?$/.test(path) || path === "/flows/new" || /^\/flows\/new\/[a-z-]{1,40}$/.test(path)
|
|
1702
1703
|
: /^\/d\/[0-9]{1,15}\/answer$/.test(path) || /^\/routines\/[0-9]{1,15}\/(approve|refresh|pause|resume|run-now)$/.test(path) || path === "/flows/new" || /^\/flows\/new\/[a-z-]{1,40}$/.test(path) || path === "/flows/import" || /^\/flows\/[0-9]{1,15}\/(save|cards|archive|scripts)$/.test(path) || /^\/flows\/[0-9]{1,15}\/cards\/[0-9]{1,15}\/(move|decide|cancel|comment|assign|watch)$/.test(path) || /^\/flows\/[0-9]{1,15}\/triggers(\/[0-9]{1,15}\/(pause|resume|remove|check|press|renew|secret|share|unshare))?$/.test(path) || /^\/r\/[0-9]{1,15}\/(note|comment|revise|draft-repair|checks|add-tests)$/.test(path);
|
|
@@ -3885,6 +3886,11 @@ export function createDecisionServer(options) {
|
|
|
3885
3886
|
const databaseFile = store.databaseFile();
|
|
3886
3887
|
if (who.via !== "cookie" || !store.isInstanceOperator(who.name) || databaseFile === null)
|
|
3887
3888
|
return refuse(response, who, 403, "An instance operator updates Toolroll.", "/settings");
|
|
3889
|
+
// A record it cannot release is shown as it is; the page never fails over it.
|
|
3890
|
+
try {
|
|
3891
|
+
releaseStalledUpdate(dirname(databaseFile), clock());
|
|
3892
|
+
}
|
|
3893
|
+
catch { /* shown as last saved */ }
|
|
3888
3894
|
const status = runtimeUpdateStatus(dirname(databaseFile));
|
|
3889
3895
|
if (url.searchParams.get("fragment") === "steps")
|
|
3890
3896
|
return respond(response, 200, "text/html; charset=utf-8", status.journal ? updateStepsHtml(status.journal, status.running) : `<div id="update-live" data-done="1"></div>`);
|
|
@@ -4889,6 +4895,8 @@ export function createDecisionServer(options) {
|
|
|
4889
4895
|
notices.unshift(DEMO_BANNER);
|
|
4890
4896
|
if (s.chrome.modeBanner)
|
|
4891
4897
|
notices.push(s.chrome.modeBanner.words);
|
|
4898
|
+
if (s.chrome.updateWaiting)
|
|
4899
|
+
notices.push(s.chrome.updateWaiting.words);
|
|
4892
4900
|
let crew = { crew: [], crewTruncated: false };
|
|
4893
4901
|
try {
|
|
4894
4902
|
const project = s.chrome.active === 'chat' ? null : s.chrome.project;
|
|
@@ -5018,14 +5026,20 @@ export function createDecisionServer(options) {
|
|
|
5018
5026
|
const weekAgo = new Date(now.getTime() - 7 * 86_400_000).toISOString();
|
|
5019
5027
|
const counts = [
|
|
5020
5028
|
{ key: "working", label: "Working now", value: agents.length, href: "/work?view=running" },
|
|
5021
|
-
|
|
5022
|
-
{ key: "
|
|
5029
|
+
// Each by its shared headline, as the Tasks list words it: a result whose checks failed waits on you, it isn't ready.
|
|
5030
|
+
{ key: "waiting", label: "Waiting on you", value: needs.items.filter(one => one.status.label !== "Ready for review").length, href: "/work?view=needs-you" },
|
|
5031
|
+
{ key: "ready", label: "Ready to review", value: needs.items.filter(one => one.status.label === "Ready for review").length, href: "/work?view=needs-you" },
|
|
5023
5032
|
{ key: "done", label: "Done this week", value: done.items.filter(one => (one.completion?.at ?? one.updatedAt) >= weekAgo).length, href: "/work?view=completed" },
|
|
5024
5033
|
];
|
|
5025
|
-
const tabOf = (
|
|
5026
|
-
|
|
5027
|
-
|
|
5034
|
+
const tabOf = (one) => one.assignmentState === "ready-to-check" ? one.status.label === "Ready for review" ? "ready" : "needs-you"
|
|
5035
|
+
: one.assignmentState === "needs-decision" ? "needs-you" : one.assignmentState === "working" || one.assignmentState === "checking" ? "running" : "finished";
|
|
5036
|
+
const catchUp = all.items.filter(one => tabOf(one) !== "finished" || one.updatedAt >= weekAgo).slice(0, 40).map(one => ({
|
|
5037
|
+
id: one.rootId, title: one.title, href: taskHref(one.rootId), project: one.repo === null ? null : projectName(one.repo), tab: tabOf(one),
|
|
5028
5038
|
label: one.status.label, tone: one.status.tone, detail: one.status.detail, at: one.updatedAt,
|
|
5039
|
+
action: (() => {
|
|
5040
|
+
const href = one.status.label === "Needs you" && one.primaryAction !== null ? browserWorkActionHref(one) : null;
|
|
5041
|
+
return href === null ? null : { label: one.primaryAction.label, href };
|
|
5042
|
+
})(),
|
|
5029
5043
|
}));
|
|
5030
5044
|
const windows = who.via === "cookie" && store.isInstanceOperator(who.name) ? limitsView(store.providerLimits(), [], { project: projectName, teammate: id => `Teammate ${id}` }, now) : null;
|
|
5031
5045
|
return {
|
|
@@ -5105,6 +5119,14 @@ export function createDecisionServer(options) {
|
|
|
5105
5119
|
return {};
|
|
5106
5120
|
return { update: { version: update.release.version, security: update.release.security, href: "/settings#updates", dismissHref: "/settings/updates/dismiss" } };
|
|
5107
5121
|
})(),
|
|
5122
|
+
...(() => {
|
|
5123
|
+
const databaseFile = store.databaseFile();
|
|
5124
|
+
// Settings → Updates says a `toolroll update` in full; every other page (and the app's update everywhere) names it once.
|
|
5125
|
+
if (!actor || databaseFile === null || !store.isInstanceOperator(actor))
|
|
5126
|
+
return {};
|
|
5127
|
+
const waiting = waitingUpdate(databaseFile, run => store.stopQuiescenceProblem(run) !== null, clock());
|
|
5128
|
+
return waiting === null || (!waiting.app && facts?.returnTo?.startsWith("/settings/updates")) ? {} : { updateWaiting: { words: waiting.short } };
|
|
5129
|
+
})(),
|
|
5108
5130
|
...(liveMode === null || liveModeTerms === null
|
|
5109
5131
|
? {}
|
|
5110
5132
|
: {
|
|
@@ -8782,7 +8804,38 @@ export function createDecisionServer(options) {
|
|
|
8782
8804
|
if (attendAct !== null) {
|
|
8783
8805
|
return attendMutation(response, who, attendAct.taskId, attendAct.verb, body, now);
|
|
8784
8806
|
}
|
|
8785
|
-
const act = matchTaskPath(url.pathname, "/(hold|unhold|requeue|cancel|scope|approve|plan|plan-edit|block|unblock|repair-dependency|next|reopen|steer|follow-up|accept-proof|accept-revision|reject-revision|route|retry-review|complete|merge|stop|resume-arm|resume)$");
|
|
8807
|
+
const act = matchTaskPath(url.pathname, "/(hold|unhold|requeue|cancel|scope|approve|plan|plan-edit|block|unblock|repair-dependency|next|reopen|steer|follow-up|accept-proof|accept-revision|reject-revision|route|retry-review|complete|merge|confirm-stopped|stop|resume-arm|resume)$");
|
|
8808
|
+
if (act !== null && act.verb === "confirm-stopped") {
|
|
8809
|
+
// Confirming a finished build stopped is an approver's act behind the password, typed again — the same
|
|
8810
|
+
// record as `toolroll run settle`, refused while anything of the run may still be running.
|
|
8811
|
+
const back = taskHref(act.taskId);
|
|
8812
|
+
if (who.via !== "cookie" || who.role !== "approver")
|
|
8813
|
+
return refuse(response, who, 403, "Only an approver can confirm a build stopped.", back);
|
|
8814
|
+
const ref = store.lookupRef(act.taskId);
|
|
8815
|
+
if (ref === null || !visible(ref.repo))
|
|
8816
|
+
return refuse(response, who, 404, "no such task", "/tasks");
|
|
8817
|
+
const named = (body.get("run") ?? "").trim();
|
|
8818
|
+
const run = /^[0-9]{1,15}$/.test(named) ? store.getRun(Number(named)) : null;
|
|
8819
|
+
const runTask = run === null ? null : store.externalIdFor(run.taskRef);
|
|
8820
|
+
if (run === null || runTask === null || familyOf(runTask)?.root.id !== (familyOf(act.taskId)?.root.id ?? act.taskId)) {
|
|
8821
|
+
return taskScreen(response, who, act.taskId, "That build isn't part of this task.", 409);
|
|
8822
|
+
}
|
|
8823
|
+
if (!authenticateApprover(store, who.name, body.get("token") ?? "", ref.repo).ok || !store.accountCanAccess(who.name, ref.repo)) {
|
|
8824
|
+
return taskScreen(response, who, act.taskId, "That password didn't match. Nothing changed.", 403);
|
|
8825
|
+
}
|
|
8826
|
+
// When Toolroll can't check at all, the approver also says they checked: never a one-click confirmation.
|
|
8827
|
+
if (store.stopQuiescenceFact(run.id)?.kind === "unknown" && body.get("checked") !== "yes") {
|
|
8828
|
+
return taskScreen(response, who, act.taskId, `Make sure nothing from build #${run.id} is running, then tick the box to confirm.`, 409);
|
|
8829
|
+
}
|
|
8830
|
+
const settled = store.settleRunWitnessesByApprover({ runId: run.id, by: who.name, why: "Confirmed in the console that nothing from this build is running." }, now);
|
|
8831
|
+
if (!settled.ok) {
|
|
8832
|
+
return taskScreen(response, who, act.taskId, settled.reason === "alive" || settled.reason === "still-running"
|
|
8833
|
+
? `Something from build #${run.id} may still be running, so it can't be confirmed stopped yet.` : `Build #${run.id} can't be confirmed stopped.`, 409);
|
|
8834
|
+
}
|
|
8835
|
+
bustBadge();
|
|
8836
|
+
const to = resultReturnTarget(body.get("return"), run.id);
|
|
8837
|
+
return redirect(response, body.get("return") ? to : back);
|
|
8838
|
+
}
|
|
8786
8839
|
if (act !== null && act.verb === "merge") {
|
|
8787
8840
|
// Merging is a person's act behind their password, typed again: a browser session, an approver, the exact
|
|
8788
8841
|
// result's pull request. The flow re-reads GitHub before merging and records who merged in the ledger.
|
|
@@ -11945,6 +11998,7 @@ function criterionMatrixHtml(matrix, options = {}) {
|
|
|
11945
11998
|
(state !== "pass" || detail.length === 0 ? "" : `<div class="requirement-evidence-group"><strong>Verification notes</strong><ul>${detail.map(line => `<li>${escape(line)}</li>`).join("")}</ul></div>`) +
|
|
11946
11999
|
reviewDetails +
|
|
11947
12000
|
(coverage === undefined ? "" : `<p class="meta"${coverage.state === "gap" ? "" : ` data-context-coverage="${escape(coverage.state)}"`}>Review context: ${escape(coverageStateWords(coverage))}</p>`) +
|
|
12001
|
+
(coverage?.assets === undefined ? "" : `<p class="meta" data-context-assets>${escape(coverage.assets)}</p>`) +
|
|
11948
12002
|
`<p class="meta">Requirement ID: <code>${escape(row.id)}</code></p></div></details></li>`;
|
|
11949
12003
|
}).join("");
|
|
11950
12004
|
return `<div class="result-section criterion-matrix"><strong>Requirements · ${matrix.length}</strong><ol class="requirement-list">${rows}</ol></div>`;
|
|
@@ -15504,6 +15558,9 @@ function shell(title, body, options = {}) {
|
|
|
15504
15558
|
: `<div class="banner"><span class="badge badge-running">mode</span>${escape(chrome.modeBanner.words)} \u00b7 <a href="/mode">the terms \u00b7 end it</a></div>`) +
|
|
15505
15559
|
(chrome.signIn ?? []).map(one => `<div class="banner sign-in-banner" data-sign-in="${escape(one.provider)}"><strong>${escape(one.title)}</strong> \u00b7 run <code>${escape(one.command)}</code> on this computer, then resume.${one.detail === "" ? "" : ` ${escape(one.detail)}`}` +
|
|
15506
15560
|
`<form method="post" action="${escape(one.resumeHref)}" class="inline"><input type="hidden" name="csrf" value="${escape(chrome.csrf ?? "")}"><button type="submit">${escape(one.resumeLabel)}</button></form></div>`).join("") +
|
|
15561
|
+
(chrome.updateWaiting === undefined
|
|
15562
|
+
? ""
|
|
15563
|
+
: `<div class="banner update-waiting" role="status">${escape(chrome.updateWaiting.words)} \u00b7 <a href="/settings/updates">Update status</a></div>`) +
|
|
15507
15564
|
(chrome.update === undefined
|
|
15508
15565
|
? ""
|
|
15509
15566
|
: `<div class="banner update-banner" data-update="${escape(chrome.update.version)}">${escape(updateNoticeWords(chrome.update))} \u00b7 <a href="${escape(chrome.update.href)}">What's new</a>` +
|
|
@@ -16517,7 +16574,7 @@ function taskChatApproval(focus, csrf) {
|
|
|
16517
16574
|
`<p class="chat-plan-outcome">${escape(oneLineOf(scope.goal, 200))}</p>` +
|
|
16518
16575
|
`<p class="chat-plan-facts">${facts.map(escape).join(" · ")}</p>` +
|
|
16519
16576
|
`<details class="chat-approval">` +
|
|
16520
|
-
`<summary data-primary-action><span class="button-link">
|
|
16577
|
+
`<summary data-primary-action><span class="button-link">Approve plan</span></summary>` +
|
|
16521
16578
|
`<form method="post" action="${taskHref(focus.executionId)}/approve" class="chat-approval-form approve-form">` +
|
|
16522
16579
|
`<input type="hidden" name="csrf" value="${escape(csrf)}">` +
|
|
16523
16580
|
`<input type="hidden" name="nonce" value="${escape(approval.nonce)}">` +
|
|
@@ -16548,7 +16605,7 @@ function taskChatLiveRegion(focus, csrf, fragment = false, inert = false, approv
|
|
|
16548
16605
|
const approvalContent = (inert && focus.approval !== null && focus.plan !== "requested"
|
|
16549
16606
|
? `<section class="card chat-action-card"><span class="eyebrow">approval ready</span><h2>Finish the current chat response first</h2><p class="meta">The secure approval step appears here as soon as this response lands.</p></section>`
|
|
16550
16607
|
: fragment && focus.approval !== null && focus.plan !== "requested"
|
|
16551
|
-
? `<section class="card chat-action-card chat-refresh-action"><a class="button-link" href="${escape(approvalHref)}" data-primary-action>
|
|
16608
|
+
? `<section class="card chat-action-card chat-refresh-action"><a class="button-link" href="${escape(approvalHref)}" data-primary-action>Approve plan</a></section>`
|
|
16552
16609
|
: taskChatApproval(focus, csrf));
|
|
16553
16610
|
const approvalCard = focus.approval !== null && focus.dispatch?.action !== "approve-scope"
|
|
16554
16611
|
? `<details class="task-secondary-approval"><summary>Updated approval terms</summary>${approvalContent}</details>` : approvalContent;
|
|
@@ -20450,7 +20507,7 @@ function taskBodyParts(data) {
|
|
|
20450
20507
|
`<p class="meta"><strong>Plan first</strong> drafts it from the repository, or <a href="#scope">write it yourself</a>.</p></div>`
|
|
20451
20508
|
: "";
|
|
20452
20509
|
const approvalHtml = dependencyChoiceNeeded || approveForm === "" ? "" : data.dispatch?.action === "approve-scope"
|
|
20453
|
-
? `<details class="task-plan-review"><summary data-primary-action><span class="button-link">
|
|
20510
|
+
? `<details class="task-plan-review"><summary data-primary-action><span class="button-link">Approve plan</span></summary>${approveForm}</details>`
|
|
20454
20511
|
: `<details class="task-secondary-approval"><summary>Updated approval terms</summary>${approveForm}</details>`;
|
|
20455
20512
|
const optionsHtml = `${identity}${dispatchStatus}${dependencyChoiceNeeded ? "" : actsBar}`;
|
|
20456
20513
|
const optionsOpen = data.assignment?.primaryAction?.code === "unhold" || data.assignment?.primaryAction?.code === "retry-task";
|
|
@@ -20651,6 +20708,10 @@ function taskBodyParts(data) {
|
|
|
20651
20708
|
status: data.assignment != null && assignmentOptions !== null ? assignmentCardOf(data.assignment, assignmentOptions) : null,
|
|
20652
20709
|
statusHtml,
|
|
20653
20710
|
approval: approvalHtml,
|
|
20711
|
+
confirmStopped: data.csrf !== "" && data.assignment?.primaryAction?.code === "confirm-stopped" && data.assignment.primaryAction.target.runId !== null
|
|
20712
|
+
? { action: `${taskHref(data.assignment.rootId)}/confirm-stopped`, run: data.assignment.primaryAction.target.runId, checked: needsCheck(data.assignment) } : null,
|
|
20713
|
+
rebuild: data.csrf !== "" && data.assignment?.primaryAction?.code === "retry-task" && data.assignment.need != null && "key" in data.assignment.need && data.assignment.need.key === "rebuild"
|
|
20714
|
+
? { action: `${taskHref(data.assignment.rootId)}/requeue` } : null,
|
|
20654
20715
|
// The plan, progress and plan changes are thread entries now; the rest still needs a person here.
|
|
20655
20716
|
lead: [
|
|
20656
20717
|
{ key: "history", html: data.history ?? "" }, { key: "control", html: controlHtml }, { key: "problem", html: problemHtml },
|
|
@@ -22091,6 +22152,13 @@ function resultPanelParts(detail, o) {
|
|
|
22091
22152
|
const status = presentation?.status ?? resultHeadlineOf(receiptStatusOf(receipt));
|
|
22092
22153
|
const stored = receiptStatusOf(receipt, null);
|
|
22093
22154
|
const humanReview = manualReviewOnly(proof === null ? null : { ...proof, verdict: proof.verdict ?? "" });
|
|
22155
|
+
// Needs you: the action that resolves it comes first; Request changes stays beside it, never alone.
|
|
22156
|
+
// On the result itself, "Review result" would link here: accepting it is what resolves it.
|
|
22157
|
+
const need = needActionOf(current, taskStatus, o.csrf, o.returnTo, {
|
|
22158
|
+
accepted: proof?.accepted != null, humanReview, run: run.id, action: `${taskHref(detail.taskId)}/accept-proof`,
|
|
22159
|
+
// What Store.finalResultReason refuses: a verified, attested, accepted or published result.
|
|
22160
|
+
rebuildable: proof?.accepted == null && proof?.verdict !== "verified" && proof?.verdict !== "attested" && !(detail.publication !== null && ["pushed", "opened"].includes(detail.publication.state))
|
|
22161
|
+
});
|
|
22094
22162
|
const directAssessment = proof?.matrix.some(row => row.assessment !== undefined) === true;
|
|
22095
22163
|
const awaitingGoalReview = directAssessment && proof?.reasons.length === 1 && proof.reasons[0] === GOAL_ASSESSMENT_PENDING;
|
|
22096
22164
|
const assessmentReasons = directAssessment ? new Set(proof.matrix.flatMap(row => row.review ? [`${row.review.author} ${row.review.judgement === "contradicts" ? "contradicts" : "needs more evidence for"} criterion "${row.id}": ${row.review.note}`] : [])) : new Set();
|
|
@@ -22154,7 +22222,7 @@ function resultPanelParts(detail, o) {
|
|
|
22154
22222
|
const acceptable = personChecks.length > 0 && humanReview && o.csrf !== "" && (current != null || detail.assignment == null);
|
|
22155
22223
|
const youCheck = personChecks.length === 0 ? null : {
|
|
22156
22224
|
lines: [...new Set(personChecks)],
|
|
22157
|
-
accept: acceptable ? { action: `${taskHref(detail.taskId)}/accept-proof`, run: run.id, returnTo: o.returnTo } : null,
|
|
22225
|
+
accept: acceptable && need?.accept == null ? { action: `${taskHref(detail.taskId)}/accept-proof`, run: run.id, returnTo: o.returnTo } : null,
|
|
22158
22226
|
};
|
|
22159
22227
|
const youCheckHtml = youCheck === null ? "" :
|
|
22160
22228
|
`<div class="result-you-check" data-result-you-check="${youCheck.lines.length}"><ul>${youCheck.lines.map(one => `<li>${escape(one)}</li>`).join("")}</ul>` +
|
|
@@ -22168,7 +22236,8 @@ function resultPanelParts(detail, o) {
|
|
|
22168
22236
|
// account waits behind a disclosure in Summary so nothing is said twice.
|
|
22169
22237
|
const conclusion = plainConclusionOf(receipt.summary ?? (run.outcome === "no-change" ? "The agent found that no repository change was needed." : "The build finished without a concise handoff."));
|
|
22170
22238
|
const outcome = conciseOutcomeOf(conclusion);
|
|
22171
|
-
|
|
22239
|
+
// A failing pull request keeps its repair draft beside the need's action.
|
|
22240
|
+
const action = o.action === false ? "" : need !== null ? resultNeedAction(need, o, detail.canAnnotate) + (detail.ciFailing && o.csrf !== "" ? resultPrimaryAction(detail, o, prUrl) : "") : resultPrimaryAction(detail, o, prUrl);
|
|
22172
22241
|
// ---- summary: the deliverable first ----------------------------------
|
|
22173
22242
|
const summaryParts = [];
|
|
22174
22243
|
const report = receipt.report;
|
|
@@ -22456,10 +22525,56 @@ function resultPanelParts(detail, o) {
|
|
|
22456
22525
|
learning: detail.learning ?? "",
|
|
22457
22526
|
request: detail.skillTest ? null : requestParts.join("\n"),
|
|
22458
22527
|
canRequest: detail.canAnnotate && o.csrf !== "",
|
|
22528
|
+
need,
|
|
22459
22529
|
requestQuiet: detail.canAnnotate && o.csrf !== "" && detail.comments.length === 0 && (detail.pastComments?.length ?? 0) === 0 && detail.revisions.length === 0,
|
|
22460
22530
|
};
|
|
22461
22531
|
return { html, panel };
|
|
22462
22532
|
}
|
|
22533
|
+
/** A Needs you result's one action (needs-you.ts): a link to the act that resolves it, or Confirm it
|
|
22534
|
+
* stopped behind the password. Null under every other headline. */
|
|
22535
|
+
export function needActionOf(assignment, status, csrf, returnTo, result) {
|
|
22536
|
+
if (assignment === null || status === null || status.headline !== "Needs you" || status.need == null)
|
|
22537
|
+
return null;
|
|
22538
|
+
const action = assignment.primaryAction;
|
|
22539
|
+
const rebuild = { href: null, confirm: null, rebuild: { action: `${taskHref(assignment.rootId)}/requeue` } };
|
|
22540
|
+
// Built to an earlier plan: Build again, in place (the task page's requeue).
|
|
22541
|
+
if (action?.code === "retry-task" && status.need.key === "rebuild" && csrf !== "")
|
|
22542
|
+
return { label: status.need.action.label, ...rebuild };
|
|
22543
|
+
if (result !== undefined) {
|
|
22544
|
+
if (status.need.key === "review-result" && !result.accepted && csrf !== "")
|
|
22545
|
+
return { label: result.humanReview ? "Accept result" : "Accept with exception", href: null, confirm: null,
|
|
22546
|
+
accept: { action: result.action, run: result.run, returnTo, note: result.humanReview ? null : "Why is this safe to accept?" } };
|
|
22547
|
+
// The task page sends the person here: never back to it. What this page can do leads — Accept
|
|
22548
|
+
// a check only a person can make, or Build again a result that may run again.
|
|
22549
|
+
const here = (action?.code === "open-result" || action?.code === "inspect-run") && action.target.runId === result.run;
|
|
22550
|
+
if (here && csrf !== "" && !result.accepted && result.humanReview)
|
|
22551
|
+
return { label: "Accept result", href: null, confirm: null, accept: { action: result.action, run: result.run, returnTo, note: null } };
|
|
22552
|
+
if (here && csrf !== "" && result.rebuildable === true)
|
|
22553
|
+
return { label: NEEDS.rebuild.action.label, ...rebuild };
|
|
22554
|
+
if (status.need.key === "review-result" || here)
|
|
22555
|
+
return { label: "Open the task", href: taskHref(assignment.rootId), confirm: null };
|
|
22556
|
+
}
|
|
22557
|
+
const confirm = action?.code === "confirm-stopped" && action.target.runId !== null && csrf !== ""
|
|
22558
|
+
? { action: `${taskHref(assignment.rootId)}/confirm-stopped`, run: action.target.runId, returnTo, checked: needsCheck(assignment) } : null;
|
|
22559
|
+
return { label: status.need.action.label, href: assignmentActionHref(assignment) ?? taskHref(assignment.rootId), confirm };
|
|
22560
|
+
}
|
|
22561
|
+
/** A build Toolroll can't check at all: the approver ticks that they checked before confirming. */
|
|
22562
|
+
const needsCheck = (assignment) => assignment.need != null && "key" in assignment.need && assignment.need.key === "check-stopped";
|
|
22563
|
+
/** The server page's form of the same: the need's action first, Request changes as the quiet second. */
|
|
22564
|
+
function resultNeedAction(need, o, canRequest) {
|
|
22565
|
+
const control = need.accept != null
|
|
22566
|
+
? `<form method="post" action="${escape(need.accept.action)}" class="accept-result"><input type="hidden" name="csrf" value="${escape(o.csrf)}"><input type="hidden" name="run" value="${need.accept.run}"><input type="hidden" name="return" value="${escape(need.accept.returnTo)}">` +
|
|
22567
|
+
(need.accept.note === null ? "" : `<input type="text" name="note" maxlength="500" required placeholder="${escape(need.accept.note)}" aria-label="${escape(need.accept.note)}">`) +
|
|
22568
|
+
`<button type="submit" data-primary-action data-accept-result style="min-height:44px">${escape(need.label)}</button></form>`
|
|
22569
|
+
: need.rebuild != null
|
|
22570
|
+
? `<form method="post" action="${escape(need.rebuild.action)}" class="rebuild"><input type="hidden" name="csrf" value="${escape(o.csrf)}"><button type="submit" data-primary-action data-rebuild style="min-height:44px">${escape(need.label)}</button></form>`
|
|
22571
|
+
: need.confirm !== null
|
|
22572
|
+
? `<form method="post" action="${escape(need.confirm.action)}" id="confirm-stopped" class="confirm-stopped"><input type="hidden" name="csrf" value="${escape(o.csrf)}"><input type="hidden" name="run" value="${need.confirm.run}"><input type="hidden" name="return" value="${escape(need.confirm.returnTo)}">` +
|
|
22573
|
+
(need.confirm.checked === true ? `<label><input type="checkbox" name="checked" value="yes" required> Nothing from build #${need.confirm.run} is running</label>` : "") +
|
|
22574
|
+
`<label>Your password<input type="password" name="token" autocomplete="current-password" required></label><button type="submit" style="min-height:44px">${escape(need.label)}</button></form>`
|
|
22575
|
+
: `<a class="button-link" href="${escape(need.href ?? "#")}" data-primary-action>${escape(need.label)}</a>`;
|
|
22576
|
+
return `<div class="result-action" data-result-action="need">${control}${canRequest && o.csrf !== "" ? `<a class="result-feedback-link" href="#request-changes">Request changes</a>` : ""}</div>`;
|
|
22577
|
+
}
|
|
22463
22578
|
/** The outcome in one bounded sentence (repair 2026-09-14): the handoff's
|
|
22464
22579
|
* first sentence, cut at a word when it runs past 180 characters. The
|
|
22465
22580
|
* full conclusion stays available behind the Summary's disclosure. */
|
package/dist/setup-guide.d.ts
CHANGED
|
@@ -15,10 +15,10 @@ export declare function detectPreparation(repo: string): {
|
|
|
15
15
|
} | null;
|
|
16
16
|
/** Browser installation may write only this known file inside its project. */
|
|
17
17
|
export declare function previewProjectInstructions(repo: string): {
|
|
18
|
+
content?: never;
|
|
18
19
|
ok: false;
|
|
19
20
|
message: string;
|
|
20
21
|
plan?: never;
|
|
21
|
-
content?: never;
|
|
22
22
|
fingerprint?: never;
|
|
23
23
|
installed?: never;
|
|
24
24
|
} | {
|
|
@@ -30,10 +30,10 @@ export declare function previewProjectInstructions(repo: string): {
|
|
|
30
30
|
installed: boolean;
|
|
31
31
|
};
|
|
32
32
|
export declare function addProjectInstructions(repo: string, fingerprint: string): {
|
|
33
|
+
content?: never;
|
|
33
34
|
ok: false;
|
|
34
35
|
message: string;
|
|
35
36
|
plan?: never;
|
|
36
|
-
content?: never;
|
|
37
37
|
fingerprint?: never;
|
|
38
38
|
installed?: never;
|
|
39
39
|
} | import("./skills.js").InstallResult;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export declare const DEPS_FOLDER = "deps";
|
|
2
|
+
/** A copy linked or made this recently is never removed by a clean-up: a lease may be about to link it. */
|
|
3
|
+
export declare const SHARED_GRACE_MS: number;
|
|
4
|
+
export type SharedCopy = {
|
|
5
|
+
key: string;
|
|
6
|
+
/** The copy's folder, holding node_modules and ready.json. */
|
|
7
|
+
dir: string;
|
|
8
|
+
repo: string;
|
|
9
|
+
/** sha256 of the lockfile and package.json it was installed from. */
|
|
10
|
+
lock: string;
|
|
11
|
+
node: string;
|
|
12
|
+
createdAt: string;
|
|
13
|
+
/** Last linked (ready.json's modification time). */
|
|
14
|
+
usedAt: string;
|
|
15
|
+
/** Its installed tree changed after it was made: never linked again. */
|
|
16
|
+
retired: boolean;
|
|
17
|
+
};
|
|
18
|
+
export declare function sharedDepsRoot(databaseFile: string): string;
|
|
19
|
+
/** A setup whose whole effect is node_modules: `npm ci` or `npm install`, with plain flags only. */
|
|
20
|
+
export declare function installsOnly(command: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* The checkout's dependency identity: its npm lockfile and package.json, hashed together. null when there is no
|
|
23
|
+
* lockfile, or the lockfile links local folders (workspaces, `file:`), whose links would point into the shared
|
|
24
|
+
* folder instead of the checkout.
|
|
25
|
+
*/
|
|
26
|
+
export declare function lockDigest(checkout: string): string | null;
|
|
27
|
+
/** The node a setup on this PATH would install with: version, platform and architecture (native modules differ). */
|
|
28
|
+
export declare function nodeIdentity(path?: string): string | null;
|
|
29
|
+
export declare function depsKey(repo: string, setupDigest: string, lock: string, node: string): string;
|
|
30
|
+
/** What a checkout would share: its key and the facts behind it, or null when it can't share. */
|
|
31
|
+
export declare function sharingFor(args: {
|
|
32
|
+
repo: string;
|
|
33
|
+
worktree: string;
|
|
34
|
+
setup: {
|
|
35
|
+
command: string;
|
|
36
|
+
digest: string;
|
|
37
|
+
};
|
|
38
|
+
}): {
|
|
39
|
+
key: string;
|
|
40
|
+
lock: string;
|
|
41
|
+
node: string;
|
|
42
|
+
} | null;
|
|
43
|
+
/** The shared node_modules for a key, when one is ready and unchanged since it was made. A changed one is retired. */
|
|
44
|
+
export declare function readyCopy(root: string, key: string): string | null;
|
|
45
|
+
/**
|
|
46
|
+
* The shared copy a checkout's node_modules links, or null (none, or its own install). A linked node_modules is a
|
|
47
|
+
* small real folder: one link per top-level entry of the shared copy and a note naming it. Tools that write a cache
|
|
48
|
+
* there (`node_modules/.vite`, `.cache`) write into the checkout's own folder, never the shared copy.
|
|
49
|
+
*/
|
|
50
|
+
export declare function linkedKey(worktree: string, root: string): string | null;
|
|
51
|
+
/** Remove a checkout's linked node_modules (its links and caches, never what they point to). True when there was one. */
|
|
52
|
+
export declare function detachShared(worktree: string, root: string): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Put a shared copy into a checkout that has no node_modules: links (a junction per folder on Windows), else a
|
|
55
|
+
* copy-on-write clone the build owns. null: neither worked, and the caller runs the plain setup.
|
|
56
|
+
*/
|
|
57
|
+
export declare function linkInto(worktree: string, shared: string): "link" | "clone" | null;
|
|
58
|
+
/**
|
|
59
|
+
* After the approved setup succeeded in a checkout with no copy for its key: move its node_modules into
|
|
60
|
+
* `deps/<key>` (one rename), make it read-only and link it back. Returns how the checkout now has it, or null when it
|
|
61
|
+
* kept its own install (a different volume, a setup that rewrote the lockfile, a race lost cleanly).
|
|
62
|
+
*/
|
|
63
|
+
export declare function promoteInstall(args: {
|
|
64
|
+
root: string;
|
|
65
|
+
repo: string;
|
|
66
|
+
worktree: string;
|
|
67
|
+
key: string;
|
|
68
|
+
lock: string;
|
|
69
|
+
node: string;
|
|
70
|
+
setupDigest: string;
|
|
71
|
+
now: Date;
|
|
72
|
+
}): "link" | "clone" | null;
|
|
73
|
+
/** Every shared copy on disk, newest first. */
|
|
74
|
+
export declare function sharedCopies(root: string): SharedCopy[];
|
|
75
|
+
export type SharedUse = SharedCopy & {
|
|
76
|
+
checkouts: string[];
|
|
77
|
+
};
|
|
78
|
+
/** Which checkouts use each copy: those linking it, and those whose lockfile it was installed from. */
|
|
79
|
+
export declare function sharedUse(root: string, checkouts: readonly {
|
|
80
|
+
path: string;
|
|
81
|
+
repo: string;
|
|
82
|
+
}[]): SharedUse[];
|
|
83
|
+
/** Half-made copies under deps/ (a crash mid-promotion), old enough that nothing is still making them. */
|
|
84
|
+
export declare function sharedDebris(root: string, now: Date): string[];
|
|
85
|
+
/** What `storage clean` removes: copies no checkout uses and none linked within the grace period, plus debris. */
|
|
86
|
+
export declare function unusedShared(root: string, checkouts: readonly {
|
|
87
|
+
path: string;
|
|
88
|
+
repo: string;
|
|
89
|
+
}[], now: Date): {
|
|
90
|
+
copies: SharedUse[];
|
|
91
|
+
debris: string[];
|
|
92
|
+
};
|
|
93
|
+
/** Remove one copy or debris folder (read-only on disk, so made writable first). */
|
|
94
|
+
export declare function removeShared(path: string): boolean;
|