toolroll 0.9.65 → 0.9.68
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 +93 -0
- package/README.md +20 -2
- package/dist/acp-client.js +1 -3
- package/dist/agent-board.d.ts +0 -1
- package/dist/agent-board.js +0 -1
- package/dist/agent-fence.d.ts +69 -2
- package/dist/agent-fence.js +294 -11
- package/dist/agentconfig.js +4 -1
- package/dist/batch-checks.js +3 -2
- package/dist/browser/THIRD_PARTY_NOTICES.txt +46 -0
- package/dist/browser/terminal.js +134 -23
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +31 -29
- package/dist/builder.d.ts +4 -0
- package/dist/builder.js +56 -37
- package/dist/canonical-json.d.ts +14 -0
- package/dist/canonical-json.js +36 -0
- package/dist/chat-actions.js +1 -2
- package/dist/chat-admission.d.ts +19 -0
- package/dist/chat-admission.js +22 -0
- package/dist/chat-channel.js +1 -1
- package/dist/chat-delivery.js +0 -1
- package/dist/chat-follow.d.ts +17 -0
- package/dist/chat-follow.js +32 -0
- package/dist/chat-messages.js +0 -1
- package/dist/chat-settings-page.d.ts +23 -0
- package/dist/chat-settings-page.js +41 -0
- package/dist/claim.js +3 -6
- package/dist/claude-print.d.ts +12 -0
- package/dist/claude-print.js +34 -0
- package/dist/cli-http.d.ts +0 -7
- package/dist/cli-http.js +0 -21
- package/dist/cli.d.ts +3 -10
- package/dist/cli.js +15 -24
- package/dist/coding-provider.js +2 -1
- package/dist/coding-workspace.js +1 -1
- package/dist/{team-cli.d.ts → connect-cli.d.ts} +5 -9
- package/dist/connect-cli.js +331 -0
- package/dist/containment.d.ts +0 -2
- package/dist/containment.js +2 -6
- package/dist/contracts/chat-actions.d.ts +0 -1
- package/dist/contracts/chat-content.d.ts +0 -4
- package/dist/contracts/chat-content.js +0 -2
- package/dist/contracts/chat-tables.d.ts +0 -1
- package/dist/contracts/cli.d.ts +0 -62
- package/dist/contracts/cli.js +0 -4
- package/dist/contracts/coding-workspace.js +1 -1
- package/dist/contracts/console-api.d.ts +1 -0
- package/dist/contracts/console-api.js +1 -0
- package/dist/contracts/contract.js +1 -1
- package/dist/contracts/flow-card.d.ts +0 -1
- package/dist/contracts/flow-propose.d.ts +0 -1
- package/dist/contracts/flow-send.js +1 -1
- package/dist/contracts/gateway-tools.d.ts +0 -1
- package/dist/contracts/integration-metadata.js +1 -1
- package/dist/contracts/lead-tools.d.ts +0 -1
- package/dist/contracts/memory-pass.d.ts +0 -1
- package/dist/contracts/memory-pass.js +1 -1
- package/dist/contracts/plan.js +1 -1
- package/dist/contracts/project-knowledge.js +1 -1
- package/dist/contracts/project-skills.d.ts +0 -1
- package/dist/contracts/proof.js +1 -1
- package/dist/contracts/recipes.js +1 -1
- package/dist/contracts/scope.js +1 -1
- package/dist/contracts/scout-report.js +1 -1
- package/dist/contracts/teams-callback.d.ts +0 -1
- package/dist/converse.d.ts +0 -6
- package/dist/converse.js +0 -19
- package/dist/daemon.js +3 -2
- package/dist/decision.d.ts +1 -3
- package/dist/deprecations.d.ts +12 -4
- package/dist/deprecations.js +27 -4
- package/dist/desktop-update-recovery.js +1 -1
- package/dist/desktop-update.js +3 -2
- package/dist/discord-chat.js +4 -15
- package/dist/discord-settings.d.ts +2 -6
- package/dist/discord-settings.js +10 -38
- package/dist/discord.js +12 -23
- package/dist/evidence.d.ts +0 -1
- package/dist/evidence.js +0 -3
- package/dist/exec.d.ts +0 -9
- package/dist/exec.js +3 -81
- package/dist/flow-actions.js +0 -3
- package/dist/flow-draft.d.ts +1 -3
- package/dist/flow-draft.js +15 -28
- package/dist/flow-scripts.d.ts +1 -8
- package/dist/flow-scripts.js +0 -13
- package/dist/flow-send.js +1 -1
- package/dist/flow-share.d.ts +0 -2
- package/dist/flow-share.js +1 -6
- package/dist/flow-steps.js +1 -1
- package/dist/flow-triggers.d.ts +0 -1
- package/dist/flow-triggers.js +1 -2
- package/dist/flow-unattended.js +1 -12
- package/dist/flows.js +1 -1
- package/dist/health-cli.d.ts +2 -2
- package/dist/health-cli.js +1 -1
- package/dist/invoke.d.ts +18 -3
- package/dist/invoke.js +142 -17
- package/dist/is-record.d.ts +2 -0
- package/dist/is-record.js +2 -0
- package/dist/lead-doors.d.ts +1 -5
- package/dist/lead-doors.js +0 -4
- package/dist/lead-tools.js +3 -6
- package/dist/live-detail.d.ts +0 -1
- package/dist/live-detail.js +0 -6
- package/dist/live-terminal.d.ts +24 -13
- package/dist/live-terminal.js +81 -43
- package/dist/live.d.ts +0 -1
- package/dist/live.js +1 -2
- package/dist/mcp-core.js +0 -2
- package/dist/memory-pass.js +1 -1
- package/dist/model-catalog.js +1 -1
- package/dist/modes.js +1 -12
- package/dist/operate.d.ts +2 -0
- package/dist/operate.js +436 -745
- package/dist/password-bearer.d.ts +0 -2
- package/dist/password-bearer.js +0 -4
- package/dist/peek-cli.js +2 -1
- package/dist/peek.d.ts +2 -26
- package/dist/peek.js +2 -168
- package/dist/phase-routing.d.ts +0 -4
- package/dist/phase-routing.js +2 -21
- package/dist/planner-source.d.ts +2 -1
- package/dist/planner-source.js +2 -12
- package/dist/planner.js +2 -14
- package/dist/process-tree.d.ts +0 -2
- package/dist/process-tree.js +0 -10
- package/dist/project-delete-ui.d.ts +6 -0
- package/dist/project-delete-ui.js +20 -1
- package/dist/project-delete.d.ts +0 -2
- package/dist/project-delete.js +1 -3
- package/dist/project-learning.d.ts +0 -14
- package/dist/project-skills.d.ts +0 -1
- package/dist/project-skills.js +0 -14
- package/dist/project-tools.js +2 -10
- package/dist/provider.d.ts +0 -17
- package/dist/provider.js +0 -26
- package/dist/pulls.d.ts +0 -2
- package/dist/pulls.js +0 -9
- package/dist/release-adapters.d.ts +9 -0
- package/dist/release-adapters.js +81 -4
- package/dist/release.d.ts +7 -1
- package/dist/release.js +20 -7
- package/dist/remote-exec.d.ts +3 -2
- package/dist/remote-exec.js +3 -2
- package/dist/repos.d.ts +0 -5
- package/dist/repos.js +2 -10
- package/dist/repository-context.d.ts +0 -2
- package/dist/repository-context.js +0 -4
- package/dist/restart-certification.d.ts +0 -2
- package/dist/restart-certification.js +1 -5
- package/dist/result-follow-ups.d.ts +1 -4
- package/dist/result-follow-ups.js +4 -8
- package/dist/result-review.d.ts +0 -2
- package/dist/result-review.js +0 -2
- package/dist/retention.d.ts +0 -1
- package/dist/retention.js +0 -1
- package/dist/review-context.d.ts +0 -1
- package/dist/review-context.js +1 -12
- package/dist/reviewer.d.ts +0 -16
- package/dist/reviewer.js +0 -18
- package/dist/run-closeout.js +2 -1
- package/dist/scope.d.ts +13 -0
- package/dist/scope.js +11 -18
- package/dist/scout-browser.js +2 -1
- package/dist/serve.js +3 -2
- package/dist/server/chrome.d.ts +10 -1
- package/dist/server/chrome.js +45 -4
- package/dist/server/console.css +60 -137
- package/dist/server/flows.js +2 -2
- package/dist/server/http.d.ts +2 -0
- package/dist/server/http.js +5 -0
- package/dist/server/pages.js +2 -2
- package/dist/server/people-tokens.js +3 -4
- package/dist/server/remote.js +1 -6
- package/dist/server/render-chat.d.ts +9 -2
- package/dist/server/render-chat.js +53 -7
- package/dist/server/render-pages.d.ts +28 -3
- package/dist/server/render-pages.js +78 -9
- package/dist/server/render-results.d.ts +0 -6
- package/dist/server/render-results.js +3 -9
- package/dist/server/render-tasks.js +6 -6
- package/dist/server/route-table.d.ts +0 -4
- package/dist/server/route-table.js +1 -6
- package/dist/server/settings.js +43 -22
- package/dist/server/task-views.js +1 -1
- package/dist/server/tasks.js +10 -7
- package/dist/setup-guide.d.ts +2 -2
- package/dist/skills.d.ts +0 -6
- package/dist/skills.js +0 -8
- package/dist/slack-chat.d.ts +1 -7
- package/dist/slack-chat.js +5 -18
- package/dist/slack-settings.d.ts +2 -6
- package/dist/slack-settings.js +10 -38
- package/dist/slack.js +12 -23
- package/dist/sleep.d.ts +2 -0
- package/dist/sleep.js +2 -0
- package/dist/store.d.ts +11 -74
- package/dist/store.js +36 -179
- package/dist/structured-output.d.ts +0 -1
- package/dist/structured-output.js +0 -1
- package/dist/subagent-work.d.ts +0 -2
- package/dist/subagent-work.js +0 -2
- package/dist/subagents.d.ts +1 -3
- package/dist/subagents.js +20 -33
- package/dist/subscription-chat.d.ts +1 -3
- package/dist/subscription-chat.js +2 -3
- package/dist/task-sizing.d.ts +1 -4
- package/dist/task-sizing.js +3 -10
- package/dist/teams-chat.js +4 -11
- package/dist/teams.js +12 -19
- package/dist/teardown-git.d.ts +22 -0
- package/dist/teardown-git.js +136 -2
- package/dist/telegram-lead.js +2 -2
- package/dist/telegram.js +1 -1
- package/dist/token-notices.d.ts +0 -11
- package/dist/token-notices.js +1 -5
- package/dist/tool-broker.d.ts +12 -0
- package/dist/tool-broker.js +66 -2
- package/dist/tool-secret-store.d.ts +0 -3
- package/dist/tool-secret-store.js +2 -11
- package/dist/toolroll-update.d.ts +0 -2
- package/dist/toolroll-update.js +2 -2
- package/dist/unfenced-test-support.d.ts +2 -0
- package/dist/unfenced-test-support.js +11 -0
- package/dist/verification-evidence.d.ts +0 -1
- package/dist/verification-evidence.js +0 -1
- package/dist/webhooks.d.ts +0 -1
- package/dist/webhooks.js +0 -1
- package/dist/workspace-ui.d.ts +0 -8
- package/dist/workspace-ui.js +1 -6
- package/dist/writes-mode.d.ts +9 -0
- package/dist/writes-mode.js +8 -0
- package/package.json +5 -1
- package/dist/contracts/native-census.d.ts +0 -42
- package/dist/contracts/native-census.js +0 -36
- package/dist/process-recovery-anchor.d.ts +0 -75
- package/dist/process-recovery-anchor.js +0 -188
- package/dist/process-recovery-coalition.d.ts +0 -25
- package/dist/process-recovery-coalition.js +0 -105
- package/dist/process-recovery-eligibility.d.ts +0 -179
- package/dist/process-recovery-eligibility.js +0 -174
- package/dist/process-recovery-native.d.ts +0 -161
- package/dist/process-recovery-native.js +0 -595
- package/dist/process-recovery-provenance.d.ts +0 -163
- package/dist/process-recovery-provenance.js +0 -292
- package/dist/process-recovery-services.d.ts +0 -56
- package/dist/process-recovery-services.js +0 -191
- package/dist/process-recovery-settlement.d.ts +0 -32
- package/dist/process-recovery-settlement.js +0 -133
- package/dist/process-recovery.d.ts +0 -41
- package/dist/process-recovery.js +0 -91
- package/dist/session-brief.d.ts +0 -8
- package/dist/session-brief.js +0 -24
- package/dist/session-cli.d.ts +0 -42
- package/dist/session-cli.js +0 -271
- package/dist/session-contract.d.ts +0 -183
- package/dist/session-contract.js +0 -122
- package/dist/session-remote.d.ts +0 -19
- package/dist/session-remote.js +0 -86
- package/dist/session-service.d.ts +0 -22
- package/dist/session-service.js +0 -157
- package/dist/team-cli.js +0 -692
|
@@ -33,7 +33,6 @@ import { chatMoney, oneLineOf, relativeAge, safePrUrl, screen, sentenceCase, str
|
|
|
33
33
|
import { chatResultHref, projectChatHref, taskChatHref, taskHref } from "./http.js";
|
|
34
34
|
import {} from "./render-pages.js";
|
|
35
35
|
import { completionReceiptCard } from "./render-results.js";
|
|
36
|
-
import {} from "./render-settings.js";
|
|
37
36
|
import { agentsAvailabilityHtml, agentsBadgesHtml, agentsStripHtml, agentsSummaryWords, agentsWhyHtml, approvalSheetHtml, checkProgressHtml, consentClosedHtml, consentDoorOf, decisionAnswerCard, dispatchHeadline, milestoneProgressHtml, planRevisionLedgerHtml, profileWords, sizeConsequence, taskControlDetailsHtml, taskStatusCard, taskViewSwitch } from "./render-tasks.js";
|
|
38
37
|
/** The installation fact that the lead was turned on by default, once (its value: the provider). */
|
|
39
38
|
export const LEAD_BY_DEFAULT_FACT = "lead-on-by-default";
|
|
@@ -260,8 +259,8 @@ export function chatHeading(copy, projectCount, showProjectToggle = true) {
|
|
|
260
259
|
}
|
|
261
260
|
/** A deliberately small rich-text grammar for model copy. The reply is shaped first (the lead's voice, enforced:
|
|
262
261
|
* no headers, at most three bold anchors, labelled links, no internal ids unless `asked` wanted them); input is
|
|
263
|
-
* escaped before tags are introduced: bullets, numbered steps, bold, inline code
|
|
264
|
-
* presentation only—never executable HTML. */
|
|
262
|
+
* escaped before tags are introduced: bullets, numbered steps, bold, inline code, fenced code blocks and labelled
|
|
263
|
+
* http(s) links are presentation only—never executable HTML. */
|
|
265
264
|
export function renderChatText(raw, asked) {
|
|
266
265
|
const inline = replyHtmlInline;
|
|
267
266
|
const text = shapeReply(raw, { appOrigin: requestContext.getStore()?.appOrigins ?? null, ...(asked === undefined ? {} : { asked }) });
|
|
@@ -281,7 +280,31 @@ export function renderChatText(raw, asked) {
|
|
|
281
280
|
out.push(list.tag === "ul" ? html `<ul>${list.items}</ul>` : html `<ol>${list.items}</ol>`);
|
|
282
281
|
list = null;
|
|
283
282
|
};
|
|
283
|
+
// A fenced block (shaping leaves it exactly as written) is shown as written: escaped, never parsed.
|
|
284
|
+
let fence = null;
|
|
285
|
+
const closeFence = () => {
|
|
286
|
+
const open = fence;
|
|
287
|
+
if (open === null)
|
|
288
|
+
return;
|
|
289
|
+
out.push(html `<pre class="chat-code"${open.lang === "" ? "" : html ` data-lang="${open.lang}"`}><code>${open.lines.join("\n")}</code></pre>`);
|
|
290
|
+
fence = null;
|
|
291
|
+
};
|
|
284
292
|
for (const line of lines) {
|
|
293
|
+
const open = fence;
|
|
294
|
+
if (open !== null) {
|
|
295
|
+
if (/^\s{0,3}```\s*$/.test(line))
|
|
296
|
+
closeFence();
|
|
297
|
+
else
|
|
298
|
+
open.lines.push(line);
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
const fenceMark = /^\s{0,3}```\s*([A-Za-z0-9_+.#-]{0,24})\s*$/.exec(line);
|
|
302
|
+
if (fenceMark !== null) {
|
|
303
|
+
flushParagraph();
|
|
304
|
+
closeList();
|
|
305
|
+
fence = { lang: fenceMark[1], lines: [] };
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
285
308
|
const bullet = /^\s*[-*]\s+(.+)$/.exec(line);
|
|
286
309
|
const numbered = /^\s*(\d{1,9})[.)]\s+(.+)$/.exec(line);
|
|
287
310
|
if (bullet !== null || numbered !== null) {
|
|
@@ -304,10 +327,35 @@ export function renderChatText(raw, asked) {
|
|
|
304
327
|
paragraph.push(line);
|
|
305
328
|
}
|
|
306
329
|
}
|
|
330
|
+
// An unclosed fence still shows its lines as code: the reply may have been cut.
|
|
307
331
|
flushParagraph();
|
|
308
332
|
closeList();
|
|
333
|
+
closeFence();
|
|
309
334
|
return html `<div class="chat-copy">${out}</div>`;
|
|
310
335
|
}
|
|
336
|
+
/** A line comment from the chat Workbench (src/browser/line-steer.tsx steerMessage): On "<file, as a JSON string>":<line>
|
|
337
|
+
* [ (removed line)] (run #<n>): <comment>, then a blank line and "> <the line>" (just ">" for a blank line, once the
|
|
338
|
+
* chat POST has trimmed the message), always last.
|
|
339
|
+
* The words the lead reads stay exactly as sent; the thread shows them in three parts: a small reference line, the quoted
|
|
340
|
+
* code as written, and the comment. Null for any other message, which keeps its own plain rendering. Everything is
|
|
341
|
+
* escaped by the html template. */
|
|
342
|
+
export function steerCommentHtml(text) {
|
|
343
|
+
const steer = /^On ("(?:[^"\\\n]|\\.){1,1200}"):(\d{1,9})( \(removed line\))? \(run #(\d{1,12})\): ([\s\S]+)\n\n>(?: ([^\n]*))?$/.exec(text);
|
|
344
|
+
if (steer === null)
|
|
345
|
+
return null;
|
|
346
|
+
// A blank line's quote ("> ") reaches the store as ">": the chat POST trims the message (src/server/chat.ts).
|
|
347
|
+
const [, quoted, line, removed, run, comment, code = ""] = steer;
|
|
348
|
+
let path;
|
|
349
|
+
try {
|
|
350
|
+
path = JSON.parse(quoted);
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
if (typeof path !== "string" || path === "" || /[\u0000-\u001f]/.test(path) || comment.trim() === "")
|
|
356
|
+
return null;
|
|
357
|
+
return html `<div class="chat-steer" data-steer-comment><p class="chat-steer-ref">${path}:${line}${removed === undefined ? "" : " · removed line"} · run #${run}</p>${code === "" ? "" : html `<pre class="chat-steer-code"><code>${code}</code></pre>`}<p class="chat-steer-note" style="white-space:pre-wrap">${comment}</p></div>`;
|
|
358
|
+
}
|
|
311
359
|
export function chatActivity(activity) {
|
|
312
360
|
return chatActivityDetailsHtml(activity);
|
|
313
361
|
}
|
|
@@ -351,8 +399,6 @@ export function chatLimitsHtml(facts) {
|
|
|
351
399
|
return html `<details class="chat-limits"><summary>Model & limits<span class="meta">${facts.subscription ? "membership" : facts.provider}</span></summary><div class="chat-budget">${rows}</div></details>`;
|
|
352
400
|
}
|
|
353
401
|
export function chatPage(chrome, data) {
|
|
354
|
-
const formFacts = { keyFacts: data.keyFacts, openrouterModels: data.openrouterModels, ...(data.liveModels === undefined ? {} : { liveModels: data.liveModels }), csrf: data.csrf,
|
|
355
|
-
returnTo: data.focusTask === null ? "/chat" : taskChatHref(data.focusTask.id) };
|
|
356
402
|
const parts = [
|
|
357
403
|
data.focusTask === null
|
|
358
404
|
? chatHeading("", data.projects.length, data.enabled.ok)
|
|
@@ -803,7 +849,7 @@ export function leadBrowserMessages(rows, back, target = { task: null, project:
|
|
|
803
849
|
return rows.messages.map(message => ({
|
|
804
850
|
id: message.id, role: message.role, text: message.text,
|
|
805
851
|
html: htmlString(message.role === 'operator'
|
|
806
|
-
? (() => { const queued = rows.queue?.get(message.id); return html `${message.text === "" ? "" : html `<p>${message.text}</p>`}${queued === undefined ? "" : queuedStateHtml(queued, back)}`; })()
|
|
852
|
+
? (() => { const queued = rows.queue?.get(message.id); return html `${message.text === "" ? "" : steerCommentHtml(message.text) ?? html `<p>${message.text}</p>`}${queued === undefined ? "" : queuedStateHtml(queued, back)}`; })()
|
|
807
853
|
: html `${renderChatText(message.text, askedBy.get(message.id))}${asked(message.turn)}`),
|
|
808
854
|
activity: message.activity, createdAt: message.createdAt,
|
|
809
855
|
...(() => {
|
|
@@ -882,7 +928,7 @@ export function leadThreadHtml(data) {
|
|
|
882
928
|
asked = message.text;
|
|
883
929
|
// A message that waits its turn (the lead queue) says so, with Remove while it still waits — as in the React thread.
|
|
884
930
|
const queued = data.queue?.get(message.id);
|
|
885
|
-
thread.push(html `<div class="msg op" data-message-role="operator" data-key="m${message.id}">${message.text === "" ? "" : html `<p style="white-space:pre-wrap">${message.text}</p>`}${queued === undefined ? "" : queuedStateHtml(queued, returnTo)}</div>`);
|
|
931
|
+
thread.push(html `<div class="msg op" data-message-role="operator" data-key="m${message.id}">${message.text === "" ? "" : steerCommentHtml(message.text) ?? html `<p style="white-space:pre-wrap">${message.text}</p>`}${queued === undefined ? "" : queuedStateHtml(queued, returnTo)}</div>`);
|
|
886
932
|
continue;
|
|
887
933
|
}
|
|
888
934
|
const cards = message.turn === null ? [] : (byTurn.get(message.turn) ?? []);
|
|
@@ -146,14 +146,39 @@ export declare function systemPage(chrome: Chrome, data: {
|
|
|
146
146
|
}[];
|
|
147
147
|
now: Date;
|
|
148
148
|
}): Screen;
|
|
149
|
+
/**
|
|
150
|
+
* A build's stage as one of three steps — set up, writing, checking and
|
|
151
|
+
* committing — read from the machine's own phase, never a percent. The
|
|
152
|
+
* steps only move forward: evidence and proof checks run both before and
|
|
153
|
+
* after the commit (builder.ts), so everything after the agent's turn is
|
|
154
|
+
* one step, and the stage words say which part. Null for runs that are not
|
|
155
|
+
* builds (planning, review, investigation): those have no honest steps.
|
|
156
|
+
*/
|
|
157
|
+
export declare const BUILD_STEPS: readonly ["setting up", "writing", "checking and committing"];
|
|
158
|
+
export declare function buildStepOf(claim: NonNullable<BoardCard["claim"]>): number | null;
|
|
159
|
+
/**
|
|
160
|
+
* The workers a board's strip may show: not retired, bound to at least one
|
|
161
|
+
* project the server's ceiling admits (never a runner serving only projects
|
|
162
|
+
* this server must not speak about), and — on a one-project board — bound to
|
|
163
|
+
* that project.
|
|
164
|
+
*/
|
|
165
|
+
export declare function boardWorkers(runners: readonly Runner[], scope: {
|
|
166
|
+
all: boolean;
|
|
167
|
+
project: string | null;
|
|
168
|
+
visible: (repo: string) => boolean;
|
|
169
|
+
}): Runner[];
|
|
149
170
|
/**
|
|
150
171
|
* The board: the pipeline as lanes, position as meaning — attention, then
|
|
151
|
-
* queued, then waiting, then building, then recently done
|
|
152
|
-
* construction (every card is a link,
|
|
153
|
-
* makes it safe to re-render itself
|
|
172
|
+
* queued, then waiting, then building, then recently done — under a strip
|
|
173
|
+
* of the workers doing it. Read-only by construction (every card is a link,
|
|
174
|
+
* no forms, no nonces), which is what makes it safe to re-render itself
|
|
175
|
+
* while somebody watches; a card that changes lane slides there
|
|
176
|
+
* (regionScript's animate swap, keyed by data-vt).
|
|
154
177
|
*/
|
|
155
178
|
export declare function boardBody(data: {
|
|
156
179
|
cards: BoardCard[];
|
|
180
|
+
/** The workers this board's work can go to (retired ones left out). */
|
|
181
|
+
workers: readonly Runner[];
|
|
157
182
|
done: ReturnType<Store["listCompletedWorkScoped"]>;
|
|
158
183
|
saturated: boolean;
|
|
159
184
|
now: Date;
|
|
@@ -207,11 +207,74 @@ export function systemPage(chrome, data) {
|
|
|
207
207
|
: html `<span class="meta">syncing normally</span>`}</p>`), "\n")}`,
|
|
208
208
|
], "\n"), { chrome, refreshSeconds: data.building.length > 0 ? 10 : 60 });
|
|
209
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* A build's stage as one of three steps — set up, writing, checking and
|
|
212
|
+
* committing — read from the machine's own phase, never a percent. The
|
|
213
|
+
* steps only move forward: evidence and proof checks run both before and
|
|
214
|
+
* after the commit (builder.ts), so everything after the agent's turn is
|
|
215
|
+
* one step, and the stage words say which part. Null for runs that are not
|
|
216
|
+
* builds (planning, review, investigation): those have no honest steps.
|
|
217
|
+
*/
|
|
218
|
+
export const BUILD_STEPS = ["setting up", "writing", "checking and committing"];
|
|
219
|
+
export function buildStepOf(claim) {
|
|
220
|
+
if (claim.role === "planner" || claim.role === "reviewer" || claim.role === "scout")
|
|
221
|
+
return null;
|
|
222
|
+
if (claim.model === null)
|
|
223
|
+
return 0;
|
|
224
|
+
if (claim.phase === null || claim.phase === "agent-running")
|
|
225
|
+
return 1;
|
|
226
|
+
return 2;
|
|
227
|
+
}
|
|
228
|
+
/** A clock that ticks in the page (boardTickScript) from the server's own start time; the text is the no-script answer. */
|
|
229
|
+
function tickingClock(startedIso, now) {
|
|
230
|
+
const minutes = Math.max(0, Math.round((now.getTime() - new Date(startedIso).getTime()) / 60_000));
|
|
231
|
+
return html `<time class="clock" datetime="${startedIso}" data-tick>${minutes}m</time>`;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* The workers a board's strip may show: not retired, bound to at least one
|
|
235
|
+
* project the server's ceiling admits (never a runner serving only projects
|
|
236
|
+
* this server must not speak about), and — on a one-project board — bound to
|
|
237
|
+
* that project.
|
|
238
|
+
*/
|
|
239
|
+
export function boardWorkers(runners, scope) {
|
|
240
|
+
return runners.filter(runner => runner.retiredAt === null &&
|
|
241
|
+
runner.repos.some(repo => scope.visible(repo)) &&
|
|
242
|
+
(scope.all || scope.project === null || runner.repos.includes(scope.project)));
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* The worker strip across the top of the board: every worker this board's
|
|
246
|
+
* work can go to, what it is building here, and its stage and clock. Only
|
|
247
|
+
* builds the board already shows are named — a worker busy in a project
|
|
248
|
+
* this reader cannot see reads "no build on this board", never "free".
|
|
249
|
+
*/
|
|
250
|
+
function workerStrip(runners, cards, now) {
|
|
251
|
+
if (runners.length === 0)
|
|
252
|
+
return html `<p class="meta crew-empty">No workers yet — work waits in the queue until one joins.</p>`;
|
|
253
|
+
const tiles = runners.map(runner => {
|
|
254
|
+
const builds = cards
|
|
255
|
+
.filter(card => card.lane === "building" && card.claim !== null && card.claim.runner === runner.name)
|
|
256
|
+
.sort((a, b) => a.claim.claimedAt.localeCompare(b.claim.claimedAt));
|
|
257
|
+
const online = runnerAlive(runner, now);
|
|
258
|
+
const state = !online ? "offline" : builds.length > 0 ? "busy" : "idle";
|
|
259
|
+
const dot = state === "busy" ? html `<span class="dot dot-ok pulse"></span>` : html `<span class="dot dot-off"></span>`;
|
|
260
|
+
const jobs = builds.length > 0
|
|
261
|
+
? builds.map(card => {
|
|
262
|
+
const claim = card.claim;
|
|
263
|
+
const words = claim.model === null ? "preparing workspace" : claim.phase === null ? "the agent is working" : phaseWords(claim.phase);
|
|
264
|
+
return html `<a class="worker-job" href="${card.href}"><span class="t">${card.title}</span><span class="sub"><span>${words}</span>${tickingClock(claim.claimedAt, now)}</span></a>`;
|
|
265
|
+
})
|
|
266
|
+
: html `<p class="worker-job idle">${online ? "no build on this board" : html `offline since ${whenTime(runner.heartbeatAt)}`}</p>`;
|
|
267
|
+
return html `<div class="worker worker-${state}" data-flash="${runner.name}" data-flash-value="${builds.map(card => card.taskId).join(" ")}"><div class="worker-head">${dot}<span class="name">${runner.name}</span><span class="kind">${runner.agents.join(" · ")}</span>${runner.capacity > 1 ? html `<span class="slots">${builds.length}/${runner.capacity}</span>` : null}</div>${jobs}</div>`;
|
|
268
|
+
});
|
|
269
|
+
return html `<section class="crew" aria-label="Workers">${tiles}</section>`;
|
|
270
|
+
}
|
|
210
271
|
/**
|
|
211
272
|
* The board: the pipeline as lanes, position as meaning — attention, then
|
|
212
|
-
* queued, then waiting, then building, then recently done
|
|
213
|
-
* construction (every card is a link,
|
|
214
|
-
* makes it safe to re-render itself
|
|
273
|
+
* queued, then waiting, then building, then recently done — under a strip
|
|
274
|
+
* of the workers doing it. Read-only by construction (every card is a link,
|
|
275
|
+
* no forms, no nonces), which is what makes it safe to re-render itself
|
|
276
|
+
* while somebody watches; a card that changes lane slides there
|
|
277
|
+
* (regionScript's animate swap, keyed by data-vt).
|
|
215
278
|
*/
|
|
216
279
|
export function boardBody(data, ciRed) {
|
|
217
280
|
const chip = (repo) => !data.all || repo === null ? null : html `<span class="badge">${projectName(repo)}</span>`;
|
|
@@ -261,7 +324,7 @@ export function boardBody(data, ciRed) {
|
|
|
261
324
|
const kept = parts.filter((one) => one !== null);
|
|
262
325
|
return kept.length === 0 ? null : html `<span class="chips">${joinHtml(kept, " ")}</span>`;
|
|
263
326
|
};
|
|
264
|
-
const plain = (card) => html `<a class="lane-card" href="${card.href}"><span class="id">${card.taskId}</span><span class="t">${card.title}</span><span class="why">${card.reason}</span>${facts([
|
|
327
|
+
const plain = (card) => html `<a class="lane-card" href="${card.href}" data-vt="${card.taskId}"><span class="id">${card.taskId}</span><span class="t">${card.title}</span><span class="why">${card.reason}</span>${facts([
|
|
265
328
|
...(card.stalledSince === null ? [] : [["waiting", age(card.stalledSince)]]),
|
|
266
329
|
])}${chips([
|
|
267
330
|
chip(card.repo),
|
|
@@ -279,7 +342,7 @@ export function boardBody(data, ciRed) {
|
|
|
279
342
|
}
|
|
280
343
|
return heads;
|
|
281
344
|
})();
|
|
282
|
-
const queuedCard = (card) => html `<a class="lane-card" href="${card.href}"><span class="id">${card.taskId}</span><span class="t">${card.title}</span><span class="why">${card.reason}</span>${facts([
|
|
345
|
+
const queuedCard = (card) => html `<a class="lane-card" href="${card.href}" data-vt="${card.taskId}"><span class="id">${card.taskId}</span><span class="t">${card.title}</span><span class="why">${card.reason}</span>${facts([
|
|
283
346
|
["worker", card.assignedRunner === null ? "any free worker" : card.assignedRunner],
|
|
284
347
|
])}${chips([
|
|
285
348
|
queuedHeads.get(`${card.assignedRunner ?? ""}|${card.repo ?? ""}`) === card.taskId
|
|
@@ -292,14 +355,19 @@ export function boardBody(data, ciRed) {
|
|
|
292
355
|
const claim = card.claim;
|
|
293
356
|
if (claim === null)
|
|
294
357
|
return plain(card);
|
|
295
|
-
const minutes = Math.max(1, Math.round((data.now.getTime() - new Date(claim.claimedAt).getTime()) / 60_000));
|
|
296
358
|
const workspace = claim.worktree === null ? null : (claim.worktree.split("/").pop() ?? claim.worktree);
|
|
297
359
|
// Chip copy: unknown phases show nothing rather than raw tokens.
|
|
298
360
|
const phase = claim.phase === null ? undefined : PHASE_WORDS[claim.phase];
|
|
299
361
|
// The live strip is the run's own phase and clock — never a percent:
|
|
300
362
|
// a build has no honest progress figure, only a stage and an elapsed.
|
|
301
363
|
const live = claim.model === null ? "preparing workspace" : (phase ?? "the agent is working");
|
|
302
|
-
|
|
364
|
+
// The steps: where the build is among set up → writing → checking →
|
|
365
|
+
// committing, from its phase — a stage, never a percent.
|
|
366
|
+
const step = buildStepOf(claim);
|
|
367
|
+
const steps = step === null
|
|
368
|
+
? null
|
|
369
|
+
: html `<span class="steps" role="img" aria-label="${`step ${step + 1} of ${BUILD_STEPS.length}: ${BUILD_STEPS[step]}`}">${BUILD_STEPS.map((_, index) => html `<i class="${index < step ? "on" : index === step ? "now" : ""}"></i>`)}</span>`;
|
|
370
|
+
return html `<a class="lane-card building" href="${card.href}" data-vt="${card.taskId}"><span class="id">${card.taskId}</span><span class="t"><span class="dot dot-ok pulse"></span>${card.title}</span>${steps}<span class="live-line"><span class="stage">${live}</span>${tickingClock(claim.claimedAt, data.now)}</span>${facts([
|
|
303
371
|
["worker", claim.runner],
|
|
304
372
|
...(claim.model === null
|
|
305
373
|
? []
|
|
@@ -316,7 +384,7 @@ export function boardBody(data, ciRed) {
|
|
|
316
384
|
const pr = row.prNumber === null
|
|
317
385
|
? null
|
|
318
386
|
: html `<span class="badge badge-open">PR #${row.prNumber}</span>${ciRed(row.prNumber) ? html ` <span class="badge badge-failed">CI failing</span>` : null}`;
|
|
319
|
-
return html `<a class="lane-card" href="${taskHref(row.taskId)}"><span class="id">${row.taskId}</span><span class="t">${row.title}</span>${row.handoff === null ? null : html `<span class="why">${row.handoff.length > 120 ? row.handoff.slice(0, 120) + "…" : row.handoff}</span>`}${facts([
|
|
387
|
+
return html `<a class="lane-card" href="${taskHref(row.taskId)}" data-vt="${row.taskId}"><span class="id">${row.taskId}</span><span class="t">${row.title}</span>${row.handoff === null ? null : html `<span class="why">${row.handoff.length > 120 ? row.handoff.slice(0, 120) + "…" : row.handoff}</span>`}${facts([
|
|
320
388
|
...(row.ranMinutes === null ? [] : [["ran", `${row.ranMinutes}m`]]),
|
|
321
389
|
["usage", runCostWords({ authMode: row.authMode, costUsd: row.costUsd, tokensIn: null, tokensOut: null })],
|
|
322
390
|
])}${chips([
|
|
@@ -347,7 +415,8 @@ export function boardBody(data, ciRed) {
|
|
|
347
415
|
data.all ? null : html `<p class="meta board-view"><strong>State</strong> · <a href="/board?view=order">order →</a> <span class="meta">drag to reorder, or to reserve a task for one worker</span></p>`,
|
|
348
416
|
deltaLine,
|
|
349
417
|
toggle,
|
|
350
|
-
|
|
418
|
+
workerStrip(data.workers, data.cards, data.now),
|
|
419
|
+
html `<div class="board" data-now="${String(data.now.getTime())}">`,
|
|
351
420
|
lane("attention", "needs you", "these wait for a person", plain),
|
|
352
421
|
lane("queued", "queued", "starts when a worker is free", queuedCard),
|
|
353
422
|
lane("waiting", "waiting", "paused until a time, another task, or a requirement is ready", plain),
|
|
@@ -95,9 +95,6 @@ export declare function withRefusal(href: string, code: keyof typeof RESULT_REFU
|
|
|
95
95
|
/** The queue reads at most this many completed tasks — the store's own
|
|
96
96
|
* page ceiling, admission bound before it. */
|
|
97
97
|
export declare const REVIEW_QUEUE_CAP = 100;
|
|
98
|
-
/** The one return road the comment endpoint honors besides its own run
|
|
99
|
-
* page: the cockpit's selected-result link, exactly. */
|
|
100
|
-
export declare const REVIEW_RETURN: RegExp;
|
|
101
98
|
/** Review priority: a deterministic, LABELED presentation aid over facts
|
|
102
99
|
* the done list already carries. Band 0 goes first; the words name why.
|
|
103
100
|
* It is never a verdict — the stored proof verdict stays the authority,
|
|
@@ -287,9 +284,6 @@ export declare function reviewCockpitPage(chrome: Chrome, data: {
|
|
|
287
284
|
user: string;
|
|
288
285
|
now: Date;
|
|
289
286
|
}): Screen;
|
|
290
|
-
/** The selected result: intent → proof → changes → publication → acts,
|
|
291
|
-
* one scan path, every fact labeled by its source. */
|
|
292
|
-
export declare function reviewCockpitDetail(view: ReviewCockpitView, csrf: string, noted: boolean, canRetryReview?: boolean, tab?: ResultTab, user?: string): Html;
|
|
293
287
|
/** The selected result's HTML and, for the rebuilt page, the same result
|
|
294
288
|
* as data: every form and panel body is the one the HTML carries. */
|
|
295
289
|
/** Where a result is shown: its own page (/review), or the Result tab of its task's page (docs/plans/one-page-task-and-agent.md),
|
|
@@ -238,9 +238,6 @@ export function withRefusal(href, code) {
|
|
|
238
238
|
/** The queue reads at most this many completed tasks — the store's own
|
|
239
239
|
* page ceiling, admission bound before it. */
|
|
240
240
|
export const REVIEW_QUEUE_CAP = 100;
|
|
241
|
-
/** The one return road the comment endpoint honors besides its own run
|
|
242
|
-
* page: the cockpit's selected-result link, exactly. */
|
|
243
|
-
export const REVIEW_RETURN = /^\/review\?result=[A-Za-z0-9._~%-]{1,200}$/;
|
|
244
241
|
export const PRIORITY_LABELS = { 0: "needs action", 1: "review", 2: "no flags" };
|
|
245
242
|
export function reviewPriorityOf(row) {
|
|
246
243
|
if (row.assignment != null && (row.assignment.receipt?.runId ?? null) === row.runId) {
|
|
@@ -568,11 +565,6 @@ export function reviewCockpitPage(chrome, data) {
|
|
|
568
565
|
functional: { script: reviewEvidenceScript() + (selected !== null && selected.detail !== null ? RESULT_REVIEW_SCRIPT : ""), fetches: false },
|
|
569
566
|
});
|
|
570
567
|
}
|
|
571
|
-
/** The selected result: intent → proof → changes → publication → acts,
|
|
572
|
-
* one scan path, every fact labeled by its source. */
|
|
573
|
-
export function reviewCockpitDetail(view, csrf, noted, canRetryReview = false, tab = "summary", user = "") {
|
|
574
|
-
return reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab, user).html;
|
|
575
|
-
}
|
|
576
568
|
export function reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab, user, host = { kind: "review" }) {
|
|
577
569
|
const parts = [];
|
|
578
570
|
const run = view.run;
|
|
@@ -1268,7 +1260,9 @@ export function reviewDiffHtml(patch, stat, runId, commentable, anchors = new Ma
|
|
|
1268
1260
|
? html `<span class="diff-annotate-space"></span>`
|
|
1269
1261
|
: html `<button type="button" class="diff-annotate pick-line" data-path="${one.path}" data-line="${lineNumber}" data-side="${side}" aria-label="Annotate ${one.path}, ${side} line ${lineNumber}" title="Annotate this line">${annotateIcon}</button>`;
|
|
1270
1262
|
const marker = line.kind === "addition" ? "+" : line.kind === "deletion" ? "−" : line.kind === "context" ? " " : "·";
|
|
1271
|
-
|
|
1263
|
+
// Each real line names its file and line, so a comment (or a test) can find the exact row.
|
|
1264
|
+
const named = lineNumber === null || line.kind === "meta" ? "" : html ` data-diff-file="${one.path}" data-diff-line="${lineNumber}"`;
|
|
1265
|
+
return html `<div class="diff-line diff-${line.kind}"${named}>${annotate}<span class="diff-gutter">${line.oldLine ?? ""}</span><span class="diff-gutter">${line.newLine ?? ""}</span><code><b aria-hidden="true">${marker}</b>${line.text}</code></div>`;
|
|
1272
1266
|
})}</div></section>`;
|
|
1273
1267
|
});
|
|
1274
1268
|
const anchor = anchors.get(one.path);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Task pages: approval, agents, plans, controls, decisions and the task body. */
|
|
2
2
|
import { networkWords } from "../egress.js";
|
|
3
|
+
import { writesWords } from "../writes-mode.js";
|
|
3
4
|
import { ACP_AGENTS, isAcpProfile } from "../acp-agents.js";
|
|
4
5
|
import { html, htmlString, joinHtml, postForm, replaceMarkup, reauth } from "../html.js";
|
|
5
6
|
import { closedQuestionReason, closedQuestionWords } from "../decision.js";
|
|
@@ -21,17 +22,16 @@ import { starterForWork } from "../flow-starters.js";
|
|
|
21
22
|
import {} from "../gaps.js";
|
|
22
23
|
import { readAuthModeStrict } from "../keys.js";
|
|
23
24
|
import { failedAttemptSentence, retryNoteOf } from "../needs-you.js";
|
|
24
|
-
import {
|
|
25
|
+
import { chosenWords, makesNoPlan, PHASES as ROUTE_PHASES, TASK_SIZES } from "../phase-routing.js";
|
|
25
26
|
import { parseExecutionPlanDocument } from "../plan.js";
|
|
26
27
|
import {} from "../planner-source.js";
|
|
27
28
|
import { projectName } from "../project.js";
|
|
28
29
|
import { dispatchStatusToken, manualReviewOnly, plainReasonWords, reviewConflict } from "../proof.js";
|
|
29
30
|
import { providerName } from "../provider-auth.js";
|
|
30
|
-
import {
|
|
31
|
+
import {} from "../provider.js";
|
|
31
32
|
import { qualityModeTitle } from "../quality.js";
|
|
32
|
-
import {} from "../result-evidence-readers.js";
|
|
33
33
|
import { evidenceShortenedWords } from "../result-review.js";
|
|
34
|
-
import { acceptanceToLines, approvalOf,
|
|
34
|
+
import { acceptanceToLines, approvalOf, scopeAuthorityOf } from "../scope.js";
|
|
35
35
|
import {} from "../store.js";
|
|
36
36
|
import { runCostWords } from "../summary.js";
|
|
37
37
|
import {} from "../task-control.js";
|
|
@@ -43,7 +43,7 @@ import { isOverdue, oneLineOf, projectChip, regionScript, safePrUrl, screen, SEN
|
|
|
43
43
|
import { steerDestinationWords, steerStandingParts } from "../steer-status.js";
|
|
44
44
|
import { reviewHref, taskChatHref, taskHref } from "./http.js";
|
|
45
45
|
import { coordinatorProposalsSection } from "./render-chat.js";
|
|
46
|
-
import { completionReceiptCard, criterionMatrixHtml, evidenceWords, homePhaseWords, incidentWords, needsCheck, pullRequestCardHtml, reasonWords, receiptStatusOf, repairChainHtml, reportItemsHtml, reviewRetryPanel, runNoun, runOutcomeBadge, semanticCoverageHtml,
|
|
46
|
+
import { completionReceiptCard, criterionMatrixHtml, evidenceWords, homePhaseWords, incidentWords, needsCheck, pullRequestCardHtml, reasonWords, receiptStatusOf, repairChainHtml, reportItemsHtml, reviewRetryPanel, runNoun, runOutcomeBadge, semanticCoverageHtml, verificationExplanation, verificationRecovered } from "./render-results.js";
|
|
47
47
|
import {} from "./session.js";
|
|
48
48
|
import { linkWords, originNote } from "../task-source.js";
|
|
49
49
|
export function contractChangeHtml(change) {
|
|
@@ -541,7 +541,7 @@ export function profileWords(scope) {
|
|
|
541
541
|
? html `<p class="meta">Runs on <span class="mono">${ACP_AGENTS[profile.provider].name} (via ACP) · ${profile.model}</span> — full access inside the OS fence; the agent runs any tool it chooses and nothing asks. No turn limit: stops after ${Math.round(profile.timeoutSeconds / 60)} min, or ${Math.round(profile.idleTimeoutSeconds / 60)} min silent; repairs on ${repair}, ${Math.round(profile.repairTimeoutSeconds / 60)} min, or ${Math.round(profile.repairIdleTimeoutSeconds / 60)} min silent.</p>`
|
|
542
542
|
: html `<p class="meta">Runs on <span class="mono">${profile.provider} · ${profile.model}</span> — ${profile.sandboxMode === "danger-full-access" ? "FULL permissions via --dangerously-bypass-approvals-and-sandbox; nothing asks" : "workspace-write sandbox"}, no turn limit (${Math.round(profile.timeoutSeconds / 60)} min ${profile.timeoutKind === "idle" ? "without progress" : "per attempt"}); repairs on ${repair}, ${Math.round(profile.repairTimeoutSeconds / 60)} min</p>`;
|
|
543
543
|
// What the build may reach is part of what is approved (egress.ts): said on every approval form.
|
|
544
|
-
return html `${base}<p class="meta" data-scope-network>${networkWords(profile.network ?? null)}</p>`;
|
|
544
|
+
return html `${base}<p class="meta" data-scope-network>${networkWords(profile.network ?? null)}</p><p class="meta" data-scope-writes>Writes: ${writesWords(profile.writes)}.</p>`;
|
|
545
545
|
}
|
|
546
546
|
export const ROLE_NOUN = { plan: "Planner", build: "Builder", repair: "Repair", review: "Reviewer" };
|
|
547
547
|
export function agentsStandingWords(view) {
|
|
@@ -91,9 +91,5 @@ export interface RouteDeclaration {
|
|
|
91
91
|
export declare const ROUTES: readonly RouteDeclaration[];
|
|
92
92
|
/** The first row that declares this method and path, or null: an undeclared request never reaches a handler. */
|
|
93
93
|
export declare function matchRoute(method: string, pathname: string, stage?: Stage): RouteDeclaration | null;
|
|
94
|
-
/** True when some row declares this path for another method (a wrong-method request). */
|
|
95
|
-
export declare function declaredForAnotherMethod(method: string, pathname: string, stage?: Stage): boolean;
|
|
96
94
|
/** Refuses a malformed table at startup: duplicate ids, duplicate method+pattern rows, or a row shadowed by an earlier one for its own sample. */
|
|
97
95
|
export declare function assertRouteTable(routes?: readonly RouteDeclaration[]): void;
|
|
98
|
-
/** The effective policy of every row, as plain data — what the snapshot test compares. */
|
|
99
|
-
export declare const routePolicySnapshot: () => Array<Omit<RouteDeclaration, "sample">>;
|
|
@@ -283,6 +283,7 @@ post("settings", "spend.budget", "/spend/budget", { scope: "step-up", ...operato
|
|
|
283
283
|
post("settings", "settings.project-concurrency", "/settings/project/concurrency");
|
|
284
284
|
post("settings", "settings.project-live-view", "/settings/project/live-view", { scope: "step-up", callers: ["cookie"] });
|
|
285
285
|
post("settings", "settings.project-network", "/settings/project/network", { scope: "step-up", callers: ["cookie"] });
|
|
286
|
+
post("settings", "settings.project-writes", "/settings/project/writes", { scope: "step-up", callers: ["cookie"] });
|
|
286
287
|
post("settings", "settings.project-delete", "/settings/project/delete", { scope: "step-up", ...operator("An instance operator deletes projects.", "/settings/project") });
|
|
287
288
|
// Lead sweeps (docs/plans/lead-fan-out.md): the table, and cancelling what isn't ready. The handler shows a sweep only in
|
|
288
289
|
// a project the reader can see, and acts only for an approver.
|
|
@@ -430,10 +431,6 @@ export function matchRoute(method, pathname, stage) {
|
|
|
430
431
|
}
|
|
431
432
|
return null;
|
|
432
433
|
}
|
|
433
|
-
/** True when some row declares this path for another method (a wrong-method request). */
|
|
434
|
-
export function declaredForAnotherMethod(method, pathname, stage) {
|
|
435
|
-
return compiled.some(({ route, test }) => (stage === undefined || route.stage === stage) && !methodMatches(route.method, method) && test.test(pathname));
|
|
436
|
-
}
|
|
437
434
|
/** Refuses a malformed table at startup: duplicate ids, duplicate method+pattern rows, or a row shadowed by an earlier one for its own sample. */
|
|
438
435
|
export function assertRouteTable(routes = ROUTES) {
|
|
439
436
|
const ids = new Set(), keys = new Set();
|
|
@@ -465,5 +462,3 @@ export function assertRouteTable(routes = ROUTES) {
|
|
|
465
462
|
}
|
|
466
463
|
}
|
|
467
464
|
assertRouteTable();
|
|
468
|
-
/** The effective policy of every row, as plain data — what the snapshot test compares. */
|
|
469
|
-
export const routePolicySnapshot = () => ROUTES.map(({ sample: _sample, ...rest }) => ({ ...rest }));
|