toolroll 0.9.63 → 0.9.67
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 +108 -0
- package/README.md +33 -5
- package/dist/acp-agents.d.ts +43 -0
- package/dist/acp-agents.js +62 -0
- package/dist/acp-attest.d.ts +44 -0
- package/dist/acp-attest.js +269 -0
- package/dist/acp-check.d.ts +28 -0
- package/dist/acp-check.js +410 -0
- package/dist/acp-client.d.ts +163 -0
- package/dist/acp-client.js +738 -0
- package/dist/acp-inspect.d.ts +23 -0
- package/dist/acp-inspect.js +109 -0
- package/dist/agent-board.d.ts +19 -6
- package/dist/agent-board.js +30 -9
- package/dist/agent-fence.d.ts +117 -2
- package/dist/agent-fence.js +439 -12
- package/dist/agent-pulse.js +9 -2
- package/dist/agent-state-words.d.ts +19 -0
- package/dist/agent-state-words.js +20 -0
- package/dist/agent-state.d.ts +65 -0
- package/dist/agent-state.js +229 -0
- package/dist/agentconfig.js +39 -13
- package/dist/agents-board.d.ts +34 -2
- package/dist/agents-board.js +110 -1
- package/dist/approval-rules-ui.js +2 -2
- package/dist/assignment-delivery.d.ts +14 -0
- package/dist/assignment-delivery.js +13 -0
- package/dist/assignment.js +13 -0
- package/dist/attest.js +7 -0
- package/dist/backup-ui.js +2 -2
- package/dist/batch-checks.js +3 -2
- package/dist/browser/THIRD_PARTY_NOTICES.txt +94 -0
- package/dist/browser/terminal.css +37 -0
- package/dist/browser/terminal.js +228 -0
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +42 -38
- package/dist/browser-shell.js +6 -3
- package/dist/browser-workspace.d.ts +235 -1
- package/dist/browser-workspace.js +60 -0
- package/dist/builder.d.ts +10 -1
- package/dist/builder.js +87 -24
- package/dist/chat-actions.d.ts +13 -1
- package/dist/chat-actions.js +197 -15
- package/dist/chat-ask.d.ts +4 -0
- package/dist/chat-ask.js +14 -0
- package/dist/chat-channel.js +20 -0
- package/dist/chat-continuity.js +5 -4
- package/dist/chat-decide.d.ts +0 -14
- package/dist/chat-decide.js +4 -1
- package/dist/chat-delivery.d.ts +9 -0
- package/dist/chat-delivery.js +45 -6
- package/dist/chat-messages.d.ts +8 -0
- package/dist/chat-messages.js +36 -4
- package/dist/check-levels-ui.js +2 -2
- package/dist/checkout-proof.d.ts +28 -0
- package/dist/checkout-proof.js +62 -0
- package/dist/claim.d.ts +12 -0
- package/dist/claim.js +19 -0
- package/dist/cli-help.d.ts +1 -1
- package/dist/cli-help.js +4 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +21 -2
- package/dist/coding-ui.js +6 -4
- package/dist/console-instance.d.ts +124 -0
- package/dist/console-instance.js +337 -0
- package/dist/contracts/chat-actions.d.ts +16 -0
- package/dist/contracts/chat-actions.js +4 -0
- package/dist/contracts/chat-content.d.ts +19 -0
- package/dist/contracts/chat-content.js +6 -0
- package/dist/contracts/console-api.d.ts +13 -4
- package/dist/contracts/console-api.js +13 -4
- package/dist/contracts/flow-card.d.ts +4 -0
- package/dist/contracts/flow-card.js +3 -0
- package/dist/contracts/flow.d.ts +46 -0
- package/dist/contracts/flow.js +11 -2
- package/dist/contracts/lead-tools.d.ts +92 -0
- package/dist/contracts/lead-tools.js +13 -0
- package/dist/contracts/provider.d.ts +11 -1
- package/dist/contracts/provider.js +1 -1
- package/dist/contracts/route.d.ts +76 -1
- package/dist/contracts/route.js +1 -1
- package/dist/contracts/store-json.d.ts +2 -0
- package/dist/control-setup.d.ts +1 -1
- package/dist/control-ui.js +2 -2
- package/dist/converse.d.ts +16 -0
- package/dist/converse.js +32 -4
- package/dist/demo-chat.js +4 -4
- package/dist/demo.js +2 -2
- package/dist/desktop-update-gate.js +6 -0
- package/dist/desktop-update.d.ts +1 -1
- package/dist/discord-chat.js +4 -1
- package/dist/doctor.js +1 -1
- package/dist/egress-bridge.d.ts +17 -0
- package/dist/egress-bridge.js +111 -0
- package/dist/egress.d.ts +119 -0
- package/dist/egress.js +423 -0
- package/dist/exec.d.ts +78 -0
- package/dist/exec.js +367 -9
- package/dist/export-ui.js +2 -2
- package/dist/flow-engine.js +24 -5
- package/dist/flow-gallery-ui.js +2 -2
- package/dist/flow-gallery.js +27 -8
- package/dist/flow-share.js +16 -5
- package/dist/flow-triggers.d.ts +77 -8
- package/dist/flow-triggers.js +517 -70
- package/dist/flows-cli.js +8 -2
- package/dist/flows-ui.d.ts +0 -2
- package/dist/flows-ui.js +52 -4
- package/dist/flows.d.ts +4 -0
- package/dist/flows.js +9 -1
- package/dist/html.d.ts +9 -0
- package/dist/html.js +11 -0
- package/dist/invoke.d.ts +35 -0
- package/dist/invoke.js +347 -28
- package/dist/keys.d.ts +18 -14
- package/dist/keys.js +5 -0
- package/dist/lead-capabilities.js +3 -2
- package/dist/lead-contract.js +2 -0
- package/dist/lead-doors.d.ts +2 -0
- package/dist/lead-doors.js +7 -0
- package/dist/lead-follow.d.ts +2 -0
- package/dist/lead-follow.js +58 -2
- package/dist/lead-progress.js +1 -0
- package/dist/lead-queue-runtime.d.ts +46 -0
- package/dist/lead-queue-runtime.js +191 -0
- package/dist/lead-queue.d.ts +104 -0
- package/dist/lead-queue.js +187 -0
- package/dist/lead-sweep.d.ts +67 -0
- package/dist/lead-sweep.js +99 -0
- package/dist/lead-tools.d.ts +22 -0
- package/dist/lead-tools.js +152 -1
- package/dist/lead.d.ts +13 -5
- package/dist/lead.js +89 -29
- package/dist/limits-ui.d.ts +8 -1
- package/dist/limits-ui.js +26 -0
- package/dist/live-bus.d.ts +8 -0
- package/dist/live-bus.js +40 -4
- package/dist/live-detail.d.ts +27 -0
- package/dist/live-detail.js +57 -0
- package/dist/live-notes.d.ts +20 -0
- package/dist/live-notes.js +47 -0
- package/dist/live-records.d.ts +140 -0
- package/dist/live-records.js +935 -0
- package/dist/live-terminal.d.ts +269 -0
- package/dist/live-terminal.js +686 -0
- package/dist/live.d.ts +38 -27
- package/dist/live.js +142 -124
- package/dist/mcp-oauth.d.ts +6 -3
- package/dist/mcp-oauth.js +8 -4
- package/dist/memory-pass.js +2 -2
- package/dist/mobile-viewport.d.ts +5 -1
- package/dist/mobile-viewport.js +18 -2
- package/dist/model-catalog.js +4 -0
- package/dist/monitoring-ui.js +2 -2
- package/dist/needs-you.d.ts +1 -1
- package/dist/needs-you.js +11 -4
- package/dist/operate.d.ts +4 -0
- package/dist/operate.js +335 -19
- package/dist/passwordless.d.ts +23 -0
- package/dist/passwordless.js +26 -0
- package/dist/peek-cli.d.ts +12 -4
- package/dist/peek-cli.js +19 -11
- package/dist/phase-routing.js +10 -3
- package/dist/planner-source.d.ts +3 -0
- package/dist/planner-source.js +14 -0
- package/dist/planner.d.ts +2 -1
- package/dist/planner.js +7 -1
- package/dist/policy-ui.js +2 -2
- package/dist/policy.d.ts +1 -20
- package/dist/policy.js +29 -2
- package/dist/project-delete-ui.d.ts +18 -0
- package/dist/project-delete-ui.js +60 -9
- package/dist/project-delete.js +2 -2
- package/dist/project-memory.d.ts +3 -1
- package/dist/project-memory.js +57 -25
- package/dist/project-tools.d.ts +5 -3
- package/dist/project-tools.js +12 -9
- package/dist/provider-auth.d.ts +4 -3
- package/dist/provider-auth.js +5 -4
- package/dist/provider-connection.d.ts +2 -1
- package/dist/provider-connection.js +5 -0
- package/dist/provider.d.ts +25 -2
- package/dist/provider.js +63 -9
- package/dist/pull-request-ui.js +2 -2
- package/dist/push.js +1 -1
- package/dist/quick-start.d.ts +24 -0
- package/dist/quick-start.js +127 -0
- package/dist/release-adapters.d.ts +6 -0
- package/dist/release-adapters.js +70 -3
- package/dist/release.d.ts +10 -1
- package/dist/release.js +34 -15
- package/dist/request-budget-ui.js +2 -2
- package/dist/result-follow-ups.js +3 -2
- package/dist/result-review.d.ts +1 -1
- package/dist/result-review.js +9 -3
- package/dist/retention-ui.js +2 -2
- package/dist/retention.js +10 -1
- package/dist/run-efficiency.d.ts +73 -0
- package/dist/run-efficiency.js +228 -0
- package/dist/runner.d.ts +5 -1
- package/dist/runner.js +22 -1
- package/dist/scope.d.ts +183 -5
- package/dist/scope.js +256 -31
- package/dist/scout-net.d.ts +4 -1
- package/dist/scout-net.js +37 -12
- package/dist/scout.d.ts +20 -6
- package/dist/scout.js +27 -3
- package/dist/serve.js +248 -40
- package/dist/server/chat.d.ts +1 -1
- package/dist/server/chat.js +122 -31
- package/dist/server/chrome.d.ts +16 -2
- package/dist/server/chrome.js +63 -11
- package/dist/server/console.css +107 -42
- package/dist/server/flows.js +38 -8
- package/dist/server/guards.d.ts +1 -0
- package/dist/server/guards.js +23 -9
- package/dist/server/http.d.ts +7 -0
- package/dist/server/http.js +3 -2
- package/dist/server/live.d.ts +34 -1
- package/dist/server/live.js +39 -17
- package/dist/server/pages-assets.js +5 -0
- package/dist/server/pages.js +90 -13
- package/dist/server/people-tokens.d.ts +3 -0
- package/dist/server/people-tokens.js +55 -17
- package/dist/server/remote-hooks.js +12 -6
- package/dist/server/remote.d.ts +2 -1
- package/dist/server/remote.js +14 -5
- package/dist/server/render-chat.d.ts +12 -3
- package/dist/server/render-chat.js +47 -16
- package/dist/server/render-pages.d.ts +32 -4
- package/dist/server/render-pages.js +83 -13
- package/dist/server/render-people.d.ts +3 -2
- package/dist/server/render-people.js +3 -2
- package/dist/server/render-results.d.ts +18 -2
- package/dist/server/render-results.js +37 -28
- package/dist/server/render-settings.js +6 -5
- package/dist/server/render-tasks.d.ts +35 -10
- package/dist/server/render-tasks.js +111 -27
- package/dist/server/request-context.d.ts +2 -0
- package/dist/server/request-context.js +2 -1
- package/dist/server/route-table.d.ts +3 -0
- package/dist/server/route-table.js +47 -5
- package/dist/server/runtime.d.ts +33 -2
- package/dist/server/session.d.ts +25 -1
- package/dist/server/session.js +18 -4
- package/dist/server/settings.js +142 -10
- package/dist/server/sweeps.d.ts +4 -0
- package/dist/server/sweeps.js +58 -0
- package/dist/server/task-views.d.ts +2 -1
- package/dist/server/task-views.js +96 -6
- package/dist/server/tasks.js +327 -78
- package/dist/server/terminals.d.ts +31 -0
- package/dist/server/terminals.js +365 -0
- package/dist/setup-guide.js +3 -2
- package/dist/slack-chat.js +4 -1
- package/dist/spend-ui.js +3 -3
- package/dist/spend.js +2 -1
- package/dist/steer-capabilities.d.ts +32 -0
- package/dist/steer-capabilities.js +60 -0
- package/dist/steer-controls.d.ts +14 -0
- package/dist/steer-controls.js +36 -0
- package/dist/steer-status.js +2 -2
- package/dist/storage-ui.js +4 -4
- package/dist/store-baseline.d.ts +1 -1
- package/dist/store-baseline.js +1 -1
- package/dist/store.d.ts +440 -15
- package/dist/store.js +1072 -52
- package/dist/subagent-desk.js +1 -1
- package/dist/summary.js +3 -0
- package/dist/task-source.d.ts +58 -0
- package/dist/task-source.js +93 -0
- package/dist/team-leads.js +1 -1
- package/dist/teams-chat.js +4 -1
- package/dist/teardown-git.d.ts +22 -0
- package/dist/teardown-git.js +136 -2
- package/dist/telegram-lead.d.ts +3 -0
- package/dist/telegram-lead.js +44 -7
- package/dist/telegram.d.ts +11 -2
- package/dist/telegram.js +51 -17
- package/dist/terminal-catalog.d.ts +11 -0
- package/dist/terminal-catalog.js +129 -0
- package/dist/text-limits.d.ts +2 -0
- package/dist/text-limits.js +2 -0
- package/dist/tool-broker.d.ts +14 -0
- package/dist/tool-broker.js +67 -2
- package/dist/toolroll-update-ui.js +2 -2
- package/dist/toolroll-update.js +13 -3
- package/dist/tools-ui.js +2 -2
- package/dist/unfenced-test-support.d.ts +2 -0
- package/dist/unfenced-test-support.js +11 -0
- package/dist/webhook-limits.d.ts +23 -0
- package/dist/webhook-limits.js +30 -0
- package/dist/webhook-readers.d.ts +56 -0
- package/dist/webhook-readers.js +138 -0
- package/dist/webhook-signing.d.ts +27 -0
- package/dist/webhook-signing.js +119 -0
- package/dist/work-index.d.ts +29 -0
- package/dist/work-index.js +16 -2
- package/dist/workspace-revision.js +3 -1
- package/dist/writes-mode.d.ts +9 -0
- package/dist/writes-mode.js +8 -0
- package/package.json +15 -2
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/** Task pages: approval, agents, plans, controls, decisions and the task body. */
|
|
2
|
-
import {
|
|
2
|
+
import { networkWords } from "../egress.js";
|
|
3
|
+
import { writesWords } from "../writes-mode.js";
|
|
4
|
+
import { ACP_AGENTS, isAcpProfile } from "../acp-agents.js";
|
|
5
|
+
import { html, htmlString, joinHtml, postForm, replaceMarkup, reauth } from "../html.js";
|
|
3
6
|
import { closedQuestionReason, closedQuestionWords } from "../decision.js";
|
|
4
7
|
import {} from "../activity-line.js";
|
|
5
8
|
import {} from "../agentconfig.js";
|
|
@@ -43,6 +46,7 @@ import { reviewHref, taskChatHref, taskHref } from "./http.js";
|
|
|
43
46
|
import { coordinatorProposalsSection } from "./render-chat.js";
|
|
44
47
|
import { completionReceiptCard, criterionMatrixHtml, evidenceWords, homePhaseWords, incidentWords, needsCheck, pullRequestCardHtml, reasonWords, receiptStatusOf, repairChainHtml, reportItemsHtml, reviewRetryPanel, runNoun, runOutcomeBadge, semanticCoverageHtml, terminalDiffCard, verificationExplanation, verificationRecovered } from "./render-results.js";
|
|
45
48
|
import {} from "./session.js";
|
|
49
|
+
import { linkWords, originNote } from "../task-source.js";
|
|
46
50
|
export function contractChangeHtml(change) {
|
|
47
51
|
const tag = html `<span class="contract-change-kind contract-change-${change.kind}">${change.kind}</span>`;
|
|
48
52
|
switch (change.field) {
|
|
@@ -147,7 +151,8 @@ export function permissionWordsOf(profile) {
|
|
|
147
151
|
? profile.permissionArgv === "bypassPermissions" ? "Full access" : "Auto permissions"
|
|
148
152
|
: profile.provider === "gemini"
|
|
149
153
|
? profile.approvalArgv === "yolo" ? "Full access" : "Auto permissions"
|
|
150
|
-
: profile
|
|
154
|
+
: isAcpProfile(profile) ? "Full access"
|
|
155
|
+
: profile.sandboxMode === "danger-full-access" ? "Full access" : "Workspace sandbox";
|
|
151
156
|
}
|
|
152
157
|
/** One plain sentence for a revision (approval critique, Oct 2):
|
|
153
158
|
* "Fixes what build #9 missed: <criterion>." The notes, paths and lineage
|
|
@@ -200,6 +205,28 @@ const fragmentString = (fragment) => fragment === "" ? "" : htmlString(fragment)
|
|
|
200
205
|
* Presentation only: the same hidden fields, nonce, digest and password
|
|
201
206
|
* post to the same route, and the task page and chat card share it.
|
|
202
207
|
*/
|
|
208
|
+
/** v126: a task's origin as a line of words (the identity line). */
|
|
209
|
+
function originWords(origin) {
|
|
210
|
+
const flow = origin.flow === null ? null : origin.flow === "hidden" ? "from a flow" : origin.flow === "removed" ? "from a flow (removed)" : `from flow “${origin.flow.name}”`;
|
|
211
|
+
const source = origin.source === null ? null : `${origin.source.label} (${origin.source.proved})`;
|
|
212
|
+
return [flow, source].filter(one => one !== null).join(" · ");
|
|
213
|
+
}
|
|
214
|
+
/** v126: a task's origin as a fact: the flow, linked for those who may open it, and the outside event, linked to where
|
|
215
|
+
* it lives, with that link's host always shown. */
|
|
216
|
+
function originParts(origin) {
|
|
217
|
+
const parts = [];
|
|
218
|
+
if (origin.flow !== null)
|
|
219
|
+
parts.push(origin.flow === "hidden" ? "A flow" : origin.flow === "removed" ? "A flow (removed)" : { label: `Flow “${origin.flow.name}”`, href: origin.flow.href });
|
|
220
|
+
if (origin.source !== null) {
|
|
221
|
+
if (parts.length > 0)
|
|
222
|
+
parts.push(" · ");
|
|
223
|
+
// The link shows where it goes (host and path), beside what it is.
|
|
224
|
+
parts.push(origin.source.label, ` (${origin.source.proved})`);
|
|
225
|
+
if (origin.source.url !== null)
|
|
226
|
+
parts.push(" · ", { label: linkWords(origin.source.url), href: origin.source.url });
|
|
227
|
+
}
|
|
228
|
+
return parts;
|
|
229
|
+
}
|
|
203
230
|
export function approvalSheetHtml(input) {
|
|
204
231
|
const { scope, route } = input;
|
|
205
232
|
// A machine-drafted repair appends its brief to the signed goal; the row
|
|
@@ -261,10 +288,13 @@ export function approvalSheetHtml(input) {
|
|
|
261
288
|
allowing.length === 0 ? "" : html `<p class="approval-allowing" data-approval-allowing>You’re allowing: ${allowing.join(" · ")}</p>`,
|
|
262
289
|
yoursToCheck.length === 0 ? "" : html `<p class="approval-you-check" data-approval-you-check>You’ll check: ${yoursToCheck.join("; ")}</p>`,
|
|
263
290
|
]);
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
291
|
+
const rebuild = input.rebuild ?? null;
|
|
292
|
+
const after = rebuild !== null
|
|
293
|
+
? html `Build #${rebuild} was made to the earlier plan. Approving builds it again to this one.`
|
|
294
|
+
: input.deliverable === "report"
|
|
295
|
+
? html `An agent investigates without changing the repository. You'll hear when its report is ready.`
|
|
296
|
+
: html `An agent starts in its own branch. You'll hear when it's ready to review.`;
|
|
297
|
+
const submit = rebuild !== null ? html `Approve & build again` : html `Approve & start`;
|
|
268
298
|
// Everything else the digest binds, one tap away.
|
|
269
299
|
const detail = (title, body, attrs = html ``) => isEmptyHtml(body) ? html `` : html `<section class="approval-detail"${attrs}><h3>${title}</h3>${body}</section>`;
|
|
270
300
|
const revisionDetail = input.revision === null ? "" : detail("Earlier build", joinHtml([
|
|
@@ -366,6 +396,7 @@ export function approvalSheetHtml(input) {
|
|
|
366
396
|
sentence === null ? "" : html `<p class="approval-revision">${sentence}</p>`,
|
|
367
397
|
input.coordinator === null ? "" : html `<p class="approval-note">An agent filed this: <span class="mono">${input.coordinator.label}</span>${input.coordinator.filedAgo === null ? "" : html `, ${input.coordinator.filedAgo}`}. Nothing runs until you approve, and approving runs its request.</p>`,
|
|
368
398
|
input.deliverable === "report" ? html `<p class="approval-note">Read-only: it reports back and changes nothing in the repository.</p>` : "",
|
|
399
|
+
originNote(input.origin) === null ? "" : html `<p class="approval-note" data-approval-origin>${originNote(input.origin)}</p>`,
|
|
369
400
|
html `<dl class="approval-rows">`,
|
|
370
401
|
row("Goal", html `<p class="approval-goal">${goalShown}</p>`),
|
|
371
402
|
row("Changes", changes),
|
|
@@ -378,15 +409,16 @@ export function approvalSheetHtml(input) {
|
|
|
378
409
|
who.length === 0 ? "" : html `<p class="approval-who">${who.join(" · ")}</p>`,
|
|
379
410
|
consent,
|
|
380
411
|
html `<div class="approval-act" id="${input.surface === "task" ? "approval-confirm" : "chat-approval-confirm"}">`,
|
|
381
|
-
html `<label class="approval-password"><span class="visually-hidden">Your password</span><input type="password" name="token" autocomplete="current-password" placeholder="Your password" aria-describedby="${passwordNote}"></label
|
|
382
|
-
html `<p class="approval-password-note" id="${passwordNote}">Your password signs this approval.</p
|
|
412
|
+
html `${reauth(html `<label class="approval-password"><span class="visually-hidden">Your password</span><input type="password" name="token" autocomplete="current-password" placeholder="Your password" aria-describedby="${passwordNote}"></label>`)}`,
|
|
413
|
+
input.freshSince == null ? html `<p class="approval-password-note" id="${passwordNote}">Your password signs this approval.</p>`
|
|
414
|
+
: html `<p class="approval-password-note" id="${passwordNote}" data-approval-fresh>Your password from ${whenTime(input.freshSince)} is remembered on this computer — leave this blank to approve with it, or type it again.</p>`,
|
|
383
415
|
html `<button type="submit" data-primary-action>${submit}</button></div>`,
|
|
384
416
|
html `<p class="approval-after">${after}</p>`,
|
|
385
417
|
html `<div class="approval-secondary">${editor ?? html `<a class="approval-link" href="${input.editHref}">Edit plan</a>`}<a class="approval-link" href="${input.notNowHref}">Not now</a></div>`,
|
|
386
418
|
details,
|
|
387
419
|
]), {
|
|
388
420
|
attrs: { class: "approve-form approval-sheet", id: input.surface === "task" ? "approve" : null, "data-sticky": input.sticky },
|
|
389
|
-
hidden: { nonce: input.nonce, digest: input.digest },
|
|
421
|
+
hidden: { nonce: input.nonce, digest: input.digest, ...(rebuild === null ? {} : { rebuild: String(rebuild) }) },
|
|
390
422
|
returnTo: input.returnTo,
|
|
391
423
|
}),
|
|
392
424
|
editorForm,
|
|
@@ -413,7 +445,8 @@ export function permissionModeOfProfile(profile) {
|
|
|
413
445
|
? profile.permissionArgv === "bypassPermissions" ? "bypassPermissions" : "auto"
|
|
414
446
|
: profile.provider === "gemini"
|
|
415
447
|
? profile.approvalArgv === "yolo" ? "bypassPermissions" : "auto"
|
|
416
|
-
: profile
|
|
448
|
+
: isAcpProfile(profile) ? "bypassPermissions"
|
|
449
|
+
: profile.sandboxMode === "danger-full-access" ? "bypassPermissions" : "auto";
|
|
417
450
|
}
|
|
418
451
|
/** Done when, from the in-place editor: each line keeps its requirement's
|
|
419
452
|
* id by position, and its evidence and guidance while its words stand; a
|
|
@@ -505,8 +538,11 @@ export function profileWords(scope) {
|
|
|
505
538
|
: "legacy acceptEdits; edits proceed, commands that ask are denied unattended"}, stops after ${profile.maxTurns} turns, ${Math.round(profile.timeoutSeconds / 60)} min ${profile.timeoutKind === "idle" ? "without progress" : "per attempt"}; repairs on ${repair}, ${profile.repairMaxTurns} turns / ${Math.round(profile.repairTimeoutSeconds / 60)} min</p>`
|
|
506
539
|
: profile.provider === "gemini"
|
|
507
540
|
? html `<p class="meta">Runs on <span class="mono">gemini · ${profile.model}</span> — ${profile.approvalArgv === "yolo" ? "Full access via --approval-mode yolo; every tool auto-approved" : "Auto via --approval-mode auto_edit; edits auto-approved, other tools refused"}, no turn limit (${Math.round(profile.timeoutSeconds / 60)} min ${profile.timeoutKind === "idle" ? "without progress" : "per attempt"}), spend reported in tokens only; repairs on ${repair}, ${Math.round(profile.repairTimeoutSeconds / 60)} min</p>`
|
|
508
|
-
:
|
|
509
|
-
|
|
541
|
+
: isAcpProfile(profile)
|
|
542
|
+
? 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>`
|
|
543
|
+
: 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>`;
|
|
544
|
+
// What the build may reach is part of what is approved (egress.ts): said on every approval form.
|
|
545
|
+
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>`;
|
|
510
546
|
}
|
|
511
547
|
export const ROLE_NOUN = { plan: "Planner", build: "Builder", repair: "Repair", review: "Reviewer" };
|
|
512
548
|
export function agentsStandingWords(view) {
|
|
@@ -706,6 +742,21 @@ export function nonceHashOf(value) {
|
|
|
706
742
|
return createHash("sha256").update(value).digest("hex");
|
|
707
743
|
}
|
|
708
744
|
/** The approve POST's refusal, in words a person can act on. */
|
|
745
|
+
/** " (password remembered from 10:42)" when the approval stood on a remembered password or sign-in; nothing otherwise. */
|
|
746
|
+
export function approvalProofWords(proof) {
|
|
747
|
+
if (proof == null)
|
|
748
|
+
return "";
|
|
749
|
+
if (proof.method === "fresh-password")
|
|
750
|
+
return ` (password remembered from ${whenText(proof.at)})`;
|
|
751
|
+
if (proof.method === "fresh-sso")
|
|
752
|
+
return ` (sign-in remembered from ${whenText(proof.at)})`;
|
|
753
|
+
if (proof.method === "session")
|
|
754
|
+
return " (passwordless: your signed-in session)";
|
|
755
|
+
if (proof.method === "cli-login")
|
|
756
|
+
return " (passwordless: this computer's remembered login)";
|
|
757
|
+
return "";
|
|
758
|
+
}
|
|
759
|
+
const whenText = (iso) => new Intl.DateTimeFormat("en-US", { hour: "numeric", minute: "2-digit" }).format(new Date(iso));
|
|
709
760
|
export function approveRefusalWords(reason) {
|
|
710
761
|
if (reason === "requester" || reason === "person-required")
|
|
711
762
|
return gateWords({ verdict: "refuse", reason });
|
|
@@ -713,6 +764,8 @@ export function approveRefusalWords(reason) {
|
|
|
713
764
|
return "not approved: this scope predates agent routing and its old approval no longer stands — edit and re-file the scope so it is routed under today’s agents, then approve it";
|
|
714
765
|
if (reason === "profile-unresolved")
|
|
715
766
|
return "not approved: the scope cannot name an exact agent for every role — fix the agent setup and re-file it";
|
|
767
|
+
if (reason === "password-now")
|
|
768
|
+
return "not approved: this needs your password typed now — protected work, raised permissions, or a plan an agent wrote can't use a remembered one";
|
|
716
769
|
return `not approved: ${reason}`;
|
|
717
770
|
}
|
|
718
771
|
/** The browser's one-click approval binds not only the signed scope but the
|
|
@@ -897,7 +950,7 @@ export function resumeCeremonyPage(chrome, data) {
|
|
|
897
950
|
data.gate === null ? "" : html `<p class="row"><strong>Before work starts:</strong> ${data.gate.summary} — ${data.gate.detail}</p>`,
|
|
898
951
|
html `</div>`,
|
|
899
952
|
postForm(`${taskHref(data.taskId)}/resume`, joinHtml([
|
|
900
|
-
html `<label>Confirm with your password<input type="password" name="token" autocomplete="current-password"></label
|
|
953
|
+
html `${reauth(html `<label>Confirm with your password<input type="password" name="token" autocomplete="current-password"></label>`)}`,
|
|
901
954
|
html `<button type="submit">Resume task</button>`,
|
|
902
955
|
], "\n"), { attrs: { class: "card resume-form" }, returnTo: data.returnTo, hidden: { nonce: data.nonceValue, run: control.run } }),
|
|
903
956
|
back,
|
|
@@ -1067,7 +1120,7 @@ export function planRevisionLedgerHtml(ledger, taskId, csrf) {
|
|
|
1067
1120
|
: joinHtml([
|
|
1068
1121
|
postForm(`${taskHref(taskId)}/accept-revision`, joinHtml([
|
|
1069
1122
|
pendingRevision.authorityKind === "authority-change"
|
|
1070
|
-
? html `<input type="password" name="token" placeholder="approval password" required autocomplete="current-password">`
|
|
1123
|
+
? html `${reauth(html `<input type="password" name="token" placeholder="approval password" required autocomplete="current-password">`)}`
|
|
1071
1124
|
: "",
|
|
1072
1125
|
html `<button type="submit">${pendingRevision.authorityKind === "authority-change" ? html `Approve changes & continue` : html `Use revised plan & continue`}</button>`,
|
|
1073
1126
|
]), { attrs: { class: "inline" }, hidden: { "revision-id": pendingRevision.id } }),
|
|
@@ -1399,7 +1452,7 @@ export function taskBodyParts(data) {
|
|
|
1399
1452
|
const steerForm = data.csrf === "" || task.state === "done" || task.state === "cancelled"
|
|
1400
1453
|
? ""
|
|
1401
1454
|
: joinHtml([
|
|
1402
|
-
postForm(`${taskHref(task.id)}/steer`, html `<input type="text" name="note" placeholder="guidance for the agent" aria-label="steering note" style="width:100%;max-width:28rem"><button type="submit"
|
|
1455
|
+
postForm(`${taskHref(task.id)}/steer`, html `<input type="text" name="note" placeholder="guidance for the agent" aria-label="steering note" style="width:100%;max-width:28rem">${data.steeringRun == null ? "" : html `<input type="hidden" name="run" value="${String(data.steeringRun)}"><input type="hidden" name="key" value="${randomUUID()}"><input type="hidden" name="return" value="task">`}<button type="submit">${data.steeringRun == null ? "Save for the next attempt" : "Steer"}</button>`, { attrs: { class: "row" } }),
|
|
1403
1456
|
html `<p class="meta">${steerDestinationWords(steeringLive)} A note can't widen the approved scope.</p>`,
|
|
1404
1457
|
]);
|
|
1405
1458
|
const steeringCard = steerRows === "" && steerForm === "" ? "" : html `<h2>Steering</h2>${steerRows}${steerForm}`;
|
|
@@ -1425,7 +1478,7 @@ export function taskBodyParts(data) {
|
|
|
1425
1478
|
html `<p><strong>Quality</strong> ${qualityModeTitle(scope.qualityMode ?? "default")}</p>`,
|
|
1426
1479
|
acceptanceCeremonyHtml(scope.acceptance),
|
|
1427
1480
|
approval.approved
|
|
1428
|
-
? html `<p class="meta scope-seal">Approved by ${approval.by} · ${whenTime(approval.at)} · <span class="seal">signs ${shortDigest(scope.digest)}</span><span class="so-sr-only"> — approval binds to this exact wording</span></p>`
|
|
1481
|
+
? html `<p class="meta scope-seal">Approved by ${approval.by}${approvalProofWords(data.approvalProof)} · ${whenTime(approval.at)} · <span class="seal">signs ${shortDigest(scope.digest)}</span><span class="so-sr-only"> — approval binds to this exact wording</span></p>`
|
|
1429
1482
|
: html `<p class="meta"><span class="seal">signs ${shortDigest(scope.digest)}</span> — approval binds to this exact wording</p><p class="meta">Not approved${approval.reason === "changed" ? " — approved once, then rewritten" : ""}</p>`,
|
|
1430
1483
|
html `</div>`,
|
|
1431
1484
|
], "\n");
|
|
@@ -1533,6 +1586,10 @@ export function taskBodyParts(data) {
|
|
|
1533
1586
|
const revisionInApproval = scope !== null && !approval.approved && data.plan !== "requested" &&
|
|
1534
1587
|
scope.profileState !== "unresolved" && consentDoor.open && data.dispatch?.action !== "repair-dependency" &&
|
|
1535
1588
|
(data.revision == null || !("problem" in data.revision));
|
|
1589
|
+
// The approval is the next step when dispatch waits on it, or when the plan was rewritten after a build (the
|
|
1590
|
+
// assignment's one action): then it is shown open under the status, never folded.
|
|
1591
|
+
const approvalLeads = data.dispatch?.action === "approve-scope" ||
|
|
1592
|
+
(data.assignment?.primaryAction?.code === "approve-scope" && data.assignment.primaryAction.target.taskId === task.id);
|
|
1536
1593
|
const approveForm = scope === null || approval.approved || data.plan === "requested"
|
|
1537
1594
|
? ""
|
|
1538
1595
|
: data.revision !== null && data.revision !== undefined && "problem" in data.revision
|
|
@@ -1547,6 +1604,7 @@ export function taskBodyParts(data) {
|
|
|
1547
1604
|
csrf: data.csrf,
|
|
1548
1605
|
nonce: data.nonce,
|
|
1549
1606
|
digest: data.approvalDigest ?? scope.digest,
|
|
1607
|
+
freshSince: data.approvalFresh ?? null,
|
|
1550
1608
|
returnTo: null,
|
|
1551
1609
|
scope,
|
|
1552
1610
|
planDocument: data.plan === "drafted" ? data.planDocument : null,
|
|
@@ -1556,10 +1614,12 @@ export function taskBodyParts(data) {
|
|
|
1556
1614
|
repairChain: data.repairChain ?? null,
|
|
1557
1615
|
route: data.route,
|
|
1558
1616
|
coordinator: data.coordinator ?? null,
|
|
1617
|
+
origin: data.origin ?? null,
|
|
1559
1618
|
deliverable: data.deliverable ?? "branch",
|
|
1619
|
+
rebuild: data.assignment?.need != null && "key" in data.assignment.need && data.assignment.need.key === "replan" ? data.assignment.need.build : null,
|
|
1560
1620
|
editHref: data.csrf !== "" && data.planSha != null && data.planDocument !== null ? "#plan-edit" : "#scope",
|
|
1561
1621
|
notNowHref: "/work",
|
|
1562
|
-
sticky:
|
|
1622
|
+
sticky: approvalLeads,
|
|
1563
1623
|
earlier: { active: data.assignment?.earlierActive ?? 0, running: data.assignment?.earlierRunning ?? 0 },
|
|
1564
1624
|
edit: data.csrf === "" ? null
|
|
1565
1625
|
: { action: `${taskHref(task.id)}/scope`, draft: inlineScopeDraft ? data.scopeDraft ?? null : null, problem: inlineScopeDraft ? data.problem : null,
|
|
@@ -1711,7 +1771,7 @@ export function taskBodyParts(data) {
|
|
|
1711
1771
|
const scopeRow = scope === null
|
|
1712
1772
|
? prop("scope", "none yet")
|
|
1713
1773
|
: approval.approved
|
|
1714
|
-
? prop("approved scope", html `<span class="seal">signs ${shortDigest(scope.digest)}</span> · ${qualityModeTitle(scope.qualityMode ?? "default")} · approved by ${approval.by} · ${whenTime(approval.at)}`)
|
|
1774
|
+
? prop("approved scope", html `<span class="seal">signs ${shortDigest(scope.digest)}</span> · ${qualityModeTitle(scope.qualityMode ?? "default")} · approved by ${approval.by}${approvalProofWords(data.approvalProof)} · ${whenTime(approval.at)}`)
|
|
1715
1775
|
: prop("scope", approval.reason === "changed" ? "rewritten since its approval — needs a new yes" : "not approved");
|
|
1716
1776
|
const strikesRow = data.strikes > 0 ? prop("strikes", `${data.strikes} failed attempt(s)`) : "";
|
|
1717
1777
|
const propsCard = html `<div class="card props">${workerRow}${queueRow}${scopeRow}${publishesRow}${economics}${strikesRow}</div>`;
|
|
@@ -1727,7 +1787,7 @@ export function taskBodyParts(data) {
|
|
|
1727
1787
|
facts.push({ label: "Queue", parts: [`${data.position.position} of ${data.position.total}${data.position.column === null ? " in the shared queue" : ` in ${data.position.column}'s queue`} · `, { label: "Reorder", href: "/board?view=order" }] });
|
|
1728
1788
|
}
|
|
1729
1789
|
facts.push(scope === null ? { label: "Scope", parts: ["none yet"] }
|
|
1730
|
-
: approval.approved ? { label: "Approved scope", parts: [{ seal: `signs ${scope.digest.length <= 12 ? scope.digest : `${scope.digest.slice(0, 12)}…`}` }, ` · ${qualityModeTitle(scope.qualityMode ?? "default")} · approved by ${approval.by}`, ...(approval.at === null ? [] : [" · ", { at: approval.at }])] }
|
|
1790
|
+
: approval.approved ? { label: "Approved scope", parts: [{ seal: `signs ${scope.digest.length <= 12 ? scope.digest : `${scope.digest.slice(0, 12)}…`}` }, ` · ${qualityModeTitle(scope.qualityMode ?? "default")} · approved by ${approval.by}${approvalProofWords(data.approvalProof)}`, ...(approval.at === null ? [] : [" · ", { at: approval.at }])] }
|
|
1731
1791
|
: { label: "Scope", parts: [approval.reason === "changed" ? "rewritten since its approval — needs a new yes" : "not approved"] });
|
|
1732
1792
|
facts.push({ label: "Publishes as", parts: [publishesAs] });
|
|
1733
1793
|
// v102: who asked for it, and what the project's approval rules need.
|
|
@@ -1893,20 +1953,22 @@ export function taskBodyParts(data) {
|
|
|
1893
1953
|
// status and property rail, and approval provenance stays in the ceremony.
|
|
1894
1954
|
const identity = html `<p class="meta task-identity">Task ID <span class="mono">${task.id}</span>${data.rootTitle !== undefined && data.rootTitle !== task.title ? ` · Execution: ${task.title}` : ""}${data.repo === null ? "" : ` · ${projectName(data.repo)}`}${data.coordinator !== null && data.coordinator !== undefined
|
|
1895
1955
|
? html ` · filed by <span class="mono">${data.coordinator.label}</span>${data.coordinator.filedAgo === null ? "" : ` ${data.coordinator.filedAgo}`}`
|
|
1896
|
-
: data.
|
|
1897
|
-
?
|
|
1898
|
-
:
|
|
1956
|
+
: data.origin != null
|
|
1957
|
+
? ` · ${originWords(data.origin)}`
|
|
1958
|
+
: data.filedVia === null || data.filedVia === undefined
|
|
1959
|
+
? ""
|
|
1960
|
+
: ` · filed via ${data.filedVia}`}${data.deliverable === "report" ? html ` · <span class="badge">Scout</span>` : ""}</p>`;
|
|
1899
1961
|
// A pull request that can be merged (or opened) owns the one primary action; the result stays in its section.
|
|
1900
1962
|
const pullRequestActs = data.csrf !== "" && (data.pullRequest?.view?.canMerge === true || (data.pullRequest?.view == null && data.pullRequest?.offer != null));
|
|
1901
1963
|
const pullRequestFact = data.pullRequest?.fact != null && data.pullRequest.view?.runId === data.assignment?.receipt?.runId ? data.pullRequest.fact : undefined;
|
|
1902
|
-
const assignmentOptions = data.assignment == null ? null : { workStatus: status, hideAction: (approveForm !== "" &&
|
|
1964
|
+
const assignmentOptions = data.assignment == null ? null : { workStatus: status, hideAction: (approveForm !== "" && approvalLeads) || pullRequestActs, problem: status.tone === "problem",
|
|
1903
1965
|
planning: data.dispatch?.code === "running" && data.dispatch.role === "planner", ...(pullRequestFact === undefined ? {} : { pullRequest: pullRequestFact }),
|
|
1904
1966
|
diagnostics: [...(status.diagnostics ?? []), ...(status.tone === "problem" && status.detail !== data.assignment.detail ? [status] : [])] };
|
|
1905
1967
|
// Say it once: a pull request that couldn't open, or merged, is the status
|
|
1906
1968
|
// card's own row (its reason or merge commit under Details), not a second card.
|
|
1907
1969
|
const pullRequestCard = data.pullRequest == null || (pullRequestFact !== undefined && (data.pullRequest.view?.state === "failed" || data.pullRequest.view?.state === "merged")) ? "" : pullRequestCardHtml(data.pullRequest, data.csrf);
|
|
1908
1970
|
// The result takes over from the task status as soon as it is ready.
|
|
1909
|
-
const statusHtml = html `${data.assignment != null && assignmentOptions !== null ? assignmentSummaryHtml(data.assignment, assignmentOptions) : receiptLeads ? completionReceiptCard(data.completion.receipt, task.id, "task", status) : taskStatusCard(status, task.id, data.dispatch ?? null, liveRunId, approveForm !== "" &&
|
|
1971
|
+
const statusHtml = html `${data.assignment != null && assignmentOptions !== null ? assignmentSummaryHtml(data.assignment, assignmentOptions) : receiptLeads ? completionReceiptCard(data.completion.receipt, task.id, "task", status) : taskStatusCard(status, task.id, data.dispatch ?? null, liveRunId, approveForm !== "" && approvalLeads)}${checkProgressHtml(data.checkProgress ?? null)}`;
|
|
1910
1972
|
// The exact-run control (v52), directly under the scheduler's answer:
|
|
1911
1973
|
// the one place a person stops or resumes THIS attempt.
|
|
1912
1974
|
const controlHtml = taskControlDetailsHtml(data.control ?? { kind: "none" }, task.id, data.csrf, "task", false, false, data.livePause ?? null);
|
|
@@ -1939,7 +2001,7 @@ export function taskBodyParts(data) {
|
|
|
1939
2001
|
return joinHtml([
|
|
1940
2002
|
html `<div class="card"><p><strong>External work</strong> <span class="meta">${link} · ${state}</span></p>`,
|
|
1941
2003
|
reopenable
|
|
1942
|
-
? postForm(`${taskHref(task.id)}/reopen`, html `<input type="password" name="token" placeholder="your password" aria-label="your password" autocomplete="current-password"
|
|
2004
|
+
? postForm(`${taskHref(task.id)}/reopen`, html `${reauth(html `<input type="password" name="token" placeholder="your password" aria-label="your password" autocomplete="current-password">`)}<button type="submit">Reopen — the approved scope stands</button>`, { attrs: { class: "row" } })
|
|
1943
2005
|
: "",
|
|
1944
2006
|
html `</div>`,
|
|
1945
2007
|
]);
|
|
@@ -1956,7 +2018,7 @@ export function taskBodyParts(data) {
|
|
|
1956
2018
|
? html `<div class="card"><p><strong>This task is waiting on you: an agent filed it, and it has no scope.</strong></p><p class="meta">Filed by <span class="mono">${data.coordinator.label}</span> — nothing plans, claims, or runs until you write a scope below and sign it. Your signature runs their request.</p></div>`
|
|
1957
2019
|
: html `<div class="card task-scope-needed"><p><strong>No approved scope yet</strong></p><p class="meta"><strong>Plan first</strong> drafts it from the repository, or <a href="#scope">write it yourself</a>.</p></div>`
|
|
1958
2020
|
: "";
|
|
1959
|
-
const approvalHtml = dependencyChoiceNeeded || approveForm === "" ? "" :
|
|
2021
|
+
const approvalHtml = dependencyChoiceNeeded || approveForm === "" ? "" : approvalLeads
|
|
1960
2022
|
? html `<section class="task-plan-review" aria-label="Approve the plan">${approveForm}</section>`
|
|
1961
2023
|
: html `<details class="task-secondary-approval"><summary>Updated approval terms</summary>${approveForm}</details>`;
|
|
1962
2024
|
const optionsHtml = html `${identity}${dispatchStatus}${dependencyChoiceNeeded ? "" : actsBar}`;
|
|
@@ -2145,7 +2207,10 @@ export function taskBodyParts(data) {
|
|
|
2145
2207
|
review.push({ label: "Publishes as", parts: [publishesAs] });
|
|
2146
2208
|
const about = [];
|
|
2147
2209
|
const filedFact = facts.find(one => one.label === "Filed by");
|
|
2148
|
-
about.push(filedFact ?? { label: "Filed by", parts: [filedBy ?? (data.filedVia == null ? "Not recorded" : `via ${data.filedVia}`)] });
|
|
2210
|
+
about.push(filedFact ?? { label: "Filed by", parts: [filedBy ?? (data.origin != null ? "An automation" : data.filedVia == null ? "Not recorded" : `via ${data.filedVia}`)] });
|
|
2211
|
+
// v126: where it came from — the flow (linked only for those who can see it) and the outside event, with its host.
|
|
2212
|
+
if (data.origin != null)
|
|
2213
|
+
about.push({ label: "From", parts: originParts(data.origin) });
|
|
2149
2214
|
about.push({ label: "Filed", parts: [{ at: rootFiled }] });
|
|
2150
2215
|
about.push({ label: "Updated", parts: [{ at: task.updatedAt }] });
|
|
2151
2216
|
for (const label of ["Audit", "This attempt", "Task total"]) {
|
|
@@ -2204,6 +2269,21 @@ export function taskBodyParts(data) {
|
|
|
2204
2269
|
attempts: [...earlierStopped].sort((a, b) => a.id - b.id).map(run => ({ label: `${runNoun(run)[0].toUpperCase()}${runNoun(run).slice(1)} #${run.id}`, href: `/r/${run.id}`,
|
|
2205
2270
|
text: run.outcome === null ? "Never finished." : run.outcome === "failed" ? failedAttemptSentence(run.reason) : run.reason === null ? null : (words => `${words.charAt(0).toUpperCase()}${words.slice(1)}.`)(reasonWords(run.reason)) })),
|
|
2206
2271
|
};
|
|
2272
|
+
// ⋯ More actions (docs/plans/one-page-task-and-agent.md): each secondary act once, in one place — Work, Share & reuse,
|
|
2273
|
+
// then Danger last. A ceremony is never cloned here: its item goes to the tab that holds its one form.
|
|
2274
|
+
const sectionIds = new Set(sectionParts.map(one => one.id));
|
|
2275
|
+
const rootOf = data.rootId ?? task.id;
|
|
2276
|
+
const more = [
|
|
2277
|
+
...(runChecks !== null && data.csrf !== "" ? [{ group: "work", label: "Run checks", kind: "post", action: runChecks.action, fields: { level: runChecks.level, return: runChecks.returnTo } }] : []),
|
|
2278
|
+
...(sectionIds.has("scope") && data.csrf !== "" ? [{ group: "work", label: approval.approved || finished ? "Plan and agents" : "Edit plan", kind: "goto", tab: "plan", target: "scope" }] : []),
|
|
2279
|
+
...(sectionIds.has("waits-for") ? [{ group: "work", label: "Dependencies", kind: "goto", tab: "plan", target: "waits-for" }] : []),
|
|
2280
|
+
...(sectionIds.has("holds") ? [{ group: "work", label: "Holds", kind: "goto", tab: "plan", target: "holds" }] : []),
|
|
2281
|
+
...(data.csrf !== "" ? [{ group: "work", label: "Task options", kind: "goto", tab: "plan", target: "task-diagnostics" }] : []),
|
|
2282
|
+
{ group: "share", label: "Copy link", kind: "copy" },
|
|
2283
|
+
...(facts.some(one => one.label === "Audit") ? [{ group: "share", label: "Evidence pack", kind: "link", href: `${taskHref(rootOf)}/evidence` }] : []),
|
|
2284
|
+
...(data.repo !== null && data.scope !== null ? [{ group: "share", label: "Reuse as recipe", kind: "link", href: `/recipes/from-task?task=${encodeURIComponent(task.id)}` }] : []),
|
|
2285
|
+
...(cancelForm !== "" ? [{ group: "danger", label: "Cancel task", kind: "goto", tab: "plan", target: "cancel-task" }] : []),
|
|
2286
|
+
];
|
|
2207
2287
|
const view = {
|
|
2208
2288
|
kind: "task",
|
|
2209
2289
|
id: task.id,
|
|
@@ -2227,9 +2307,13 @@ export function taskBodyParts(data) {
|
|
|
2227
2307
|
activity: liveRun === undefined ? null : data.activity ?? null,
|
|
2228
2308
|
stop,
|
|
2229
2309
|
pause,
|
|
2310
|
+
steering: data.csrf !== "" ? data.steerComposer ?? null : null,
|
|
2311
|
+
...(data.csrf !== "" && (data.steerRecent?.length ?? 0) > 0 ? { steerRecent: data.steerRecent } : {}),
|
|
2230
2312
|
record,
|
|
2231
2313
|
earlier,
|
|
2232
2314
|
approval: fragmentString(approvalHtml),
|
|
2315
|
+
approvalPlacement: approvalLeads ? "lead" : "plan",
|
|
2316
|
+
more,
|
|
2233
2317
|
confirmStopped: data.csrf !== "" && data.assignment?.primaryAction?.code === "confirm-stopped" && data.assignment.primaryAction.target.runId !== null
|
|
2234
2318
|
? { action: `${taskHref(data.assignment.rootId)}/confirm-stopped`, run: data.assignment.primaryAction.target.runId, checked: needsCheck(data.assignment) } : null,
|
|
2235
2319
|
rebuild,
|
|
@@ -33,6 +33,8 @@ export type RequestFacts = {
|
|
|
33
33
|
expiresAt: number;
|
|
34
34
|
etag: string;
|
|
35
35
|
};
|
|
36
|
+
/** Passwordless is on and this is a signed-in browser: ceremony password fields render as nothing (html.ts reauth). */
|
|
37
|
+
passwordless?: boolean;
|
|
36
38
|
};
|
|
37
39
|
/**
|
|
38
40
|
* The request's own session facts, readable from anywhere below the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** What one console request knows about itself, readable anywhere below its handler. */
|
|
2
2
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
-
import { provideFormToken } from "../html.js";
|
|
3
|
+
import { provideFormToken, providePasswordless } from "../html.js";
|
|
4
4
|
/**
|
|
5
5
|
* The request's own session facts, readable from anywhere below the
|
|
6
6
|
* dispatcher without threading them through forty call sites: the csrf
|
|
@@ -10,6 +10,7 @@ import { provideFormToken } from "../html.js";
|
|
|
10
10
|
*/
|
|
11
11
|
export const requestContext = new AsyncLocalStorage();
|
|
12
12
|
provideFormToken(() => requestContext.getStore()?.csrf);
|
|
13
|
+
providePasswordless(() => requestContext.getStore()?.passwordless === true);
|
|
13
14
|
/** Run a render outside a live request (a test, a background render) as if a browser session with this token asked. */
|
|
14
15
|
export function withFormToken(csrf, render) {
|
|
15
16
|
const facts = requestContext.getStore();
|
|
@@ -83,6 +83,9 @@ export interface RouteDeclaration {
|
|
|
83
83
|
readonly roleRefusal?: RouteRefusal;
|
|
84
84
|
/** The largest form body a POST here reads (else the console's default). */
|
|
85
85
|
readonly bodyCap?: number;
|
|
86
|
+
/** Cockpit B2: whether a password remembered on this session may stand in here (`allowlist`), or never. Only the
|
|
87
|
+
* allowlisted acts run their check inside `withFreshness` (scope.ts); every other step-up is `never`. */
|
|
88
|
+
readonly freshness: "never" | "allowlist";
|
|
86
89
|
}
|
|
87
90
|
/** The sole route table, in match order. */
|
|
88
91
|
export declare const ROUTES: readonly RouteDeclaration[];
|
|
@@ -44,6 +44,7 @@ function row(stage, domain, id, method, pattern, options = {}) {
|
|
|
44
44
|
roleBrowser: options.roleBrowser ?? false,
|
|
45
45
|
...(options.roleRefusal === undefined ? {} : { roleRefusal: options.roleRefusal }),
|
|
46
46
|
...(options.bodyCap === undefined ? {} : { bodyCap: options.bodyCap }),
|
|
47
|
+
freshness: options.freshness ?? "never",
|
|
47
48
|
});
|
|
48
49
|
}
|
|
49
50
|
const edge = (domain, id, method, path, options = {}) => row("edge", domain, id, method, path.startsWith("^") ? path : exact(path), options);
|
|
@@ -65,6 +66,9 @@ const LARGE_FORM = 1024 * 1024;
|
|
|
65
66
|
// ---- edge: answered before the console's sign-in ---------------------------------------------------------------
|
|
66
67
|
// Ordered exactly as the server checks them: health and webhooks before the Host check, the rest after it.
|
|
67
68
|
edge("pages", "edge.healthz", "GET,HEAD", "/healthz", { host: "before" });
|
|
69
|
+
// Cockpit B1: the running console proves itself to this installation's CLI (a fresh challenge, answered with the
|
|
70
|
+
// instance secret and the address this connection was received on). It reveals nothing and changes nothing.
|
|
71
|
+
edge("pages", "edge.instance", "GET", "/__instance");
|
|
68
72
|
edge("remote", "edge.telegram-hook", "POST", "/hooks/telegram", { callers: ["service"], host: "before", proof: "adapter" });
|
|
69
73
|
edge("remote", "edge.flow-form", "GET,HEAD", "^\\/hooks\\/form\\/[^/]+$", { project: "flow", sample: "/hooks/form/abc", host: "before", proof: "adapter" });
|
|
70
74
|
edge("remote", "edge.flow-form-send", "POST", "^\\/hooks\\/form\\/[^/]+$", { project: "flow", sample: "/hooks/form/abc", host: "before", proof: "adapter" });
|
|
@@ -83,9 +87,9 @@ edge("remote", "edge.team-read", "GET", "/api/team", { callers: ["cookie", "bear
|
|
|
83
87
|
edge("remote", "edge.team-send", "POST", "/api/team", { callers: ["cookie", "bearer"], scope: "read", project: "adapter", proof: "adapter" });
|
|
84
88
|
edge("remote", "edge.cli", "POST", "/api/cli", { callers: ["bearer"], scope: "read", project: "adapter", proof: "adapter" });
|
|
85
89
|
edge("remote", "edge.teams", "POST", "/teams/messages", { callers: ["service"], proof: "adapter" });
|
|
86
|
-
edge("pages", "edge.browser-asset", "GET,HEAD", "^\\/assets\\/(workspace\\.js|workspace\\.css|THIRD_PARTY_NOTICES\\.txt)$", { sample: "/assets/workspace.js" });
|
|
90
|
+
edge("pages", "edge.browser-asset", "GET,HEAD", "^\\/assets\\/(workspace\\.js|workspace\\.css|terminal\\.js|terminal\\.css|THIRD_PARTY_NOTICES\\.txt)$", { sample: "/assets/workspace.js" });
|
|
87
91
|
edge("pages", "edge.workspace-style", "GET,HEAD", "^\\/assets\\/workspace-[0-9a-f]{64}\\.css$", { sample: `/assets/workspace-${"0".repeat(64)}.css` });
|
|
88
|
-
edge("pages", "edge.install-asset", "GET", "^\\/(manifest\\.webmanifest|favicon\\.ico|icon\\.svg|icon-192\\.png|icon-512\\.png|apple-touch-icon\\.png|sw\\.js)$", { sample: "/sw.js" });
|
|
92
|
+
edge("pages", "edge.install-asset", "GET", "^\\/(manifest\\.webmanifest|favicon\\.ico|icon\\.svg|icon-attention\\.svg|icon-192\\.png|icon-512\\.png|apple-touch-icon\\.png|sw\\.js)$", { sample: "/sw.js" });
|
|
89
93
|
edge("pages", "edge.font", "GET", "^\\/fonts\\/geist-(sans|mono)-(400|500|600)\\.woff2$", { sample: "/fonts/geist-sans-400.woff2" });
|
|
90
94
|
edge("pages", "edge.desktop-health", "GET", "/desktop/health");
|
|
91
95
|
edge("people", "edge.login-page", "GET", "/login");
|
|
@@ -108,6 +112,14 @@ for (const one of ["state", "changes", "ship"])
|
|
|
108
112
|
get("tasks", `code.${one}`, `^\\/code\\/${SESSION}\\/${one}$`, { ...CODING, sample: `/code/${"a".repeat(32)}/${one}` });
|
|
109
113
|
// Any other coding address answers "not found" after the same access check.
|
|
110
114
|
get("tasks", "code.other", "^\\/code\\/.*$", { ...CODING, sample: "/code/unknown" });
|
|
115
|
+
// Live terminals (docs/plans/live-terminal.md): an installation-wide operator's own, from a browser. The handlers prove
|
|
116
|
+
// the operator's generation, the terminal's owner and its project; the socket's upgrade is checked in terminals.ts.
|
|
117
|
+
const TERM = { ...free, callers: ["cookie"], project: "none", limited: "deny", ...operator("Terminals are for installation-wide operators.", "/") };
|
|
118
|
+
const TERMINAL = "[a-f0-9]{32}";
|
|
119
|
+
get("tasks", "term.page", "/term", TERM);
|
|
120
|
+
get("tasks", "term.state", "/term/state", TERM);
|
|
121
|
+
get("tasks", "term.session", `^\\/term\\/${TERMINAL}$`, { ...TERM, sample: `/term/${"a".repeat(32)}` });
|
|
122
|
+
get("tasks", "term.socket", `^\\/term\\/${TERMINAL}\\/socket$`, { ...TERM, sample: `/term/${"a".repeat(32)}/socket` });
|
|
111
123
|
get("pages", "projects.browse", "/projects/browse", free);
|
|
112
124
|
get("pages", "projects.github", "/projects/github", { ...free, callers: ["cookie"], callerRefusal: { status: 403, type: "text/plain; charset=utf-8", body: "listing repositories feeds a browser session's act" },
|
|
113
125
|
...anyApprover("your login can watch — adding projects is an approver's act") });
|
|
@@ -211,8 +223,14 @@ for (const one of ["approve", "change", "revise", "complete"])
|
|
|
211
223
|
post("settings", "provider.resume", "^\\/providers\\/[a-z]+\\/resume$", { sample: "/providers/codex/resume" });
|
|
212
224
|
post("settings", "settings.updates-dismiss", "/settings/updates/dismiss");
|
|
213
225
|
post("settings", "settings.updates-checks", "/settings/updates/checks");
|
|
226
|
+
// Cockpit B2: Forget ends this session's remembered password now.
|
|
227
|
+
// Forget is about the caller's own session only: anyone signed in with a browser may use it (a viewer, a project-limited person).
|
|
228
|
+
post("settings", "session.forget-password", "/session/forget-password", { ...free, limited: "self", viewer: true, callers: ["cookie"] });
|
|
214
229
|
const CODING_ACT = { scope: "step-up", project: "coding", limited: "self" };
|
|
215
230
|
post("tasks", "code.start", "/code/start", CODING_ACT);
|
|
231
|
+
post("tasks", "term.start", "/term/start", { ...TERM, scope: "step-up" });
|
|
232
|
+
for (const one of ["close", "remove"])
|
|
233
|
+
post("tasks", `term.${one}`, `^\\/term\\/${TERMINAL}\\/${one}$`, { ...TERM, sample: `/term/${"a".repeat(32)}/${one}` });
|
|
216
234
|
for (const one of ["send", "stop", "resume", "recover", "continue", "ship", "answer"])
|
|
217
235
|
post("tasks", `code.${one}-send`, `^\\/code\\/${SESSION}\\/${one}$`, { ...CODING_ACT, sample: `/code/${"a".repeat(32)}/${one}` });
|
|
218
236
|
// Any other coding action answers "not found" after the same access check.
|
|
@@ -252,11 +270,29 @@ post("flows", "flow.trigger.check", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/
|
|
|
252
270
|
post("flows", "flow.trigger.press", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/press$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers/2/press", ...EDIT_FLOWS });
|
|
253
271
|
post("flows", "flow.trigger.renew", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/renew$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers/2/renew", ...EDIT_FLOWS });
|
|
254
272
|
post("flows", "flow.trigger.secret", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/secret$`, { scope: "step-up", project: "flow", limited: "resource", sample: "/flows/1/triggers/2/secret", ...EDIT_FLOWS });
|
|
273
|
+
// v126: a signed webhook's set-up (a new address, and a secret made here shown once) and who signs it: behind the password.
|
|
274
|
+
post("flows", "flow.trigger.setup", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/setup$`, { scope: "step-up", project: "flow", limited: "resource", sample: "/flows/1/triggers/2/setup", ...EDIT_FLOWS });
|
|
275
|
+
// v126: clear a webhook's turned-away count once it has been seen.
|
|
276
|
+
post("flows", "flow.trigger.dropped", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/dropped$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers/2/dropped", ...EDIT_FLOWS });
|
|
277
|
+
// v126: which project a Sentry or Vercel webhook takes deliveries from: behind the password, as its secret is.
|
|
278
|
+
post("flows", "flow.trigger.scope", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/scope$`, { scope: "step-up", project: "flow", limited: "resource", sample: "/flows/1/triggers/2/scope", ...EDIT_FLOWS });
|
|
279
|
+
post("flows", "flow.trigger.sender", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/sender$`, { scope: "step-up", project: "flow", limited: "resource", sample: "/flows/1/triggers/2/sender", ...EDIT_FLOWS });
|
|
255
280
|
post("flows", "flow.trigger.share", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/share$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers/2/share", ...EDIT_FLOWS });
|
|
256
281
|
post("flows", "flow.trigger.unshare", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/unshare$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers/2/unshare", ...EDIT_FLOWS });
|
|
257
282
|
post("settings", "spend.budget", "/spend/budget", { scope: "step-up", ...operator("An instance operator sets budgets.", "/spend") });
|
|
258
283
|
post("settings", "settings.project-concurrency", "/settings/project/concurrency");
|
|
284
|
+
post("settings", "settings.project-live-view", "/settings/project/live-view", { scope: "step-up", callers: ["cookie"] });
|
|
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"] });
|
|
259
287
|
post("settings", "settings.project-delete", "/settings/project/delete", { scope: "step-up", ...operator("An instance operator deletes projects.", "/settings/project") });
|
|
288
|
+
// Lead sweeps (docs/plans/lead-fan-out.md): the table, and cancelling what isn't ready. The handler shows a sweep only in
|
|
289
|
+
// a project the reader can see, and acts only for an approver.
|
|
290
|
+
get("tasks", "sweep.page", "^\\/sweeps\\/[1-9][0-9]{0,9}$", { ...free, limited: "unscoped", sample: "/sweeps/1" });
|
|
291
|
+
post("tasks", "sweep.cancel-rest", "^\\/sweeps\\/[1-9][0-9]{0,9}\\/cancel-rest$", { limited: "unscoped", sample: "/sweeps/1/cancel-rest", ...approver("An approver cancels a sweep's work.", "/chat") });
|
|
292
|
+
// Passwordless (docs/plans/passwordless.md): an operator's installation setting. Turning it on takes a typed password
|
|
293
|
+
// (the handler proves it as a credential act); turning it off is one click, so the row itself is not a step-up.
|
|
294
|
+
get("settings", "settings.passwordless", "/settings/passwordless", { ...free, ...operator("An instance operator sets Passwordless.", "/settings") });
|
|
295
|
+
post("settings", "settings.passwordless-send", "/settings/passwordless", { callers: ["cookie"], ...operator("An instance operator sets Passwordless.", "/settings") });
|
|
260
296
|
post("pages", "ledger.checkpoint", "/ledger/checkpoint", operator("An instance operator makes ledger checkpoints.", "/ledger"));
|
|
261
297
|
const SETTINGS_ACT_ROLES = {
|
|
262
298
|
policy: operator("An instance operator sets the policy.", "/settings/policy"),
|
|
@@ -328,7 +364,7 @@ post("tasks", "task.act.unhold", `^\\/t\\/${TASK}\\/unhold$`, { project: "task",
|
|
|
328
364
|
post("tasks", "task.act.requeue", `^\\/t\\/${TASK}\\/requeue$`, { project: "task", limited: "resource", sample: "/t/one/requeue" });
|
|
329
365
|
post("tasks", "task.act.cancel", `^\\/t\\/${TASK}\\/cancel$`, { project: "task", limited: "resource", sample: "/t/one/cancel" });
|
|
330
366
|
post("tasks", "task.act.scope", `^\\/t\\/${TASK}\\/scope$`, { project: "task", limited: "resource", sample: "/t/one/scope", bodyCap: LARGE_FORM });
|
|
331
|
-
post("tasks", "task.act.approve", `^\\/t\\/${TASK}\\/approve$`, { scope: "step-up", project: "task", limited: "resource", sample: "/t/one/approve" });
|
|
367
|
+
post("tasks", "task.act.approve", `^\\/t\\/${TASK}\\/approve$`, { scope: "step-up", project: "task", limited: "resource", sample: "/t/one/approve", freshness: "allowlist" });
|
|
332
368
|
post("tasks", "task.act.plan", `^\\/t\\/${TASK}\\/plan$`, { project: "task", limited: "resource", sample: "/t/one/plan" });
|
|
333
369
|
post("tasks", "task.act.plan-edit", `^\\/t\\/${TASK}\\/plan-edit$`, { project: "task", limited: "resource", sample: "/t/one/plan-edit" });
|
|
334
370
|
post("tasks", "task.act.next", `^\\/t\\/${TASK}\\/next$`, { project: "task", limited: "resource", sample: "/t/one/next" });
|
|
@@ -361,11 +397,15 @@ for (const one of ["identity", "about", "promise/cancel", "on"])
|
|
|
361
397
|
post("chat", "chat.config", "/chat/config", { scope: "step-up" });
|
|
362
398
|
for (const one of ["mint", "follow", "end", "stop"])
|
|
363
399
|
post("chat", `chat.mate-${one}`, `/chat/mate/${one}`, { callers: ["cookie"], ...(one === "mint" ? { scope: "step-up" } : {}) });
|
|
400
|
+
// Remove a message still waiting in the person's lead queue (docs/plans/lead-message-queue.md): their own, while it waits.
|
|
401
|
+
post("chat", "chat.queued-remove", "^\\/chat\\/queued\\/[1-9][0-9]{0,14}\\/remove$", { callers: ["cookie"], sample: "/chat/queued/1/remove" });
|
|
402
|
+
// Confirming a proposal may approve a scope (scope_approve): that one act takes the same standard-approval freshness as
|
|
403
|
+
// /t/:id/approve (chat-actions.ts scopes it); every other operation it confirms still needs a password typed now.
|
|
364
404
|
for (const one of ["confirm", "dismiss"])
|
|
365
|
-
post("chat", `chat.proposal.${one}`, `^\\/chat\\/proposal\\/${ID}\\/${one}$`, { project: "proposal", limited: "proposal", sample: `/chat/proposal/1/${one}
|
|
405
|
+
post("chat", `chat.proposal.${one}`, `^\\/chat\\/proposal\\/${ID}\\/${one}$`, { project: "proposal", limited: "proposal", sample: `/chat/proposal/1/${one}`, ...(one === "confirm" ? { freshness: "allowlist" } : {}) });
|
|
366
406
|
for (const one of ["confirm", "dismiss"])
|
|
367
407
|
post("chat", `coordinator.proposal.${one}`, `^\\/proposals\\/${ID}\\/${one}$`, { sample: `/proposals/1/${one}` });
|
|
368
|
-
post("chat", "chat.send", "/chat", { scope: "step-up", project: "conversation" });
|
|
408
|
+
post("chat", "chat.send", "/chat", { scope: "step-up", project: "conversation", freshness: "allowlist" });
|
|
369
409
|
post("chat", "chat.file", "^\\/chat\\/file\\/[0-9a-f]{32}$", { scope: "step-up", sample: `/chat/file/${"a".repeat(32)}` });
|
|
370
410
|
post("chat", "chat.ack-send", `^\\/chat\\/ack\\/${ID}$`, { scope: "step-up", sample: "/chat/ack/1" });
|
|
371
411
|
for (const one of ["prepare", "preview", "import", "save"])
|
|
@@ -373,6 +413,8 @@ for (const one of ["prepare", "preview", "import", "save"])
|
|
|
373
413
|
post("flows", "recipes.launch-send", "/recipes/launch", { limited: "collection" });
|
|
374
414
|
for (const one of ["note", "comment", "revise", "draft-repair", "checks", "add-tests"])
|
|
375
415
|
post("tasks", `run.act.${one}`, `^\\/r\\/${ID}\\/${one}$`, { project: "run", limited: "resource", sample: `/r/1/${one}` });
|
|
416
|
+
// Cockpit E: reveal a running attempt's proved checkout in Finder (nothing is launched in it).
|
|
417
|
+
post("tasks", "run.act.reveal", `^\\/r\\/${ID}\\/reveal$`, { project: "run", limited: "resource", callers: ["cookie"], sample: "/r/1/reveal", ...approver("Only an approver can show an agent's files.") });
|
|
376
418
|
post("tasks", "session.editor-links", "/session/editor-links", { viewer: true });
|
|
377
419
|
post("tasks", "incident.resolve", `^\\/i\\/${ID}\\/resolve$`, { project: "incident", sample: "/i/1/resolve" });
|
|
378
420
|
/** The sole route table, in match order. */
|