toolroll 0.9.63 → 0.9.65
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 +51 -0
- package/README.md +15 -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 +50 -2
- package/dist/agent-fence.js +157 -10
- 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 +36 -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/browser/THIRD_PARTY_NOTICES.txt +48 -0
- package/dist/browser/terminal.css +37 -0
- package/dist/browser/terminal.js +117 -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 +6 -1
- package/dist/builder.js +34 -13
- 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 +12 -4
- package/dist/contracts/console-api.js +12 -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 +17 -0
- package/dist/invoke.js +217 -21
- 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 +258 -0
- package/dist/live-terminal.js +649 -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 +2 -0
- package/dist/operate.js +275 -17
- 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 +12 -0
- package/dist/project-delete-ui.js +41 -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.d.ts +3 -0
- package/dist/release.js +14 -8
- package/dist/request-budget-ui.js +2 -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 +170 -5
- package/dist/scope.js +251 -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 +247 -40
- package/dist/server/chat.d.ts +1 -1
- package/dist/server/chat.js +122 -31
- package/dist/server/chrome.d.ts +6 -1
- package/dist/server/chrome.js +18 -7
- package/dist/server/console.css +52 -38
- 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 +4 -1
- package/dist/server/render-pages.js +5 -4
- 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 +110 -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 +46 -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 +121 -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 +320 -74
- 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 +430 -15
- package/dist/store.js +1041 -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/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 +2 -0
- package/dist/tool-broker.js +1 -0
- package/dist/toolroll-update-ui.js +2 -2
- package/dist/toolroll-update.js +13 -3
- package/dist/tools-ui.js +2 -2
- 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/package.json +11 -2
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/** The conversation: chat and lead pages, proposal cards and the task chat. */
|
|
2
1
|
import { assignmentSummaryHtml } from "../assignment-ui.js";
|
|
3
2
|
import {} from "../assignment.js";
|
|
4
3
|
import {} from "../browser-workspace.js";
|
|
@@ -27,7 +26,7 @@ import { assignmentStageOf, stageOfCode } from "../task-status.js";
|
|
|
27
26
|
import {} from "../team-chat-authorization.js";
|
|
28
27
|
import {} from "../team-contract.js";
|
|
29
28
|
import { buildProgressOf } from "../workspace-ui.js";
|
|
30
|
-
import { html, htmlString, isHtml, joinHtml, postForm, replaceMarkup } from "../html.js";
|
|
29
|
+
import { html, htmlString, isHtml, joinHtml, postForm, replaceMarkup, reauth } from "../html.js";
|
|
31
30
|
import { requestContext, withFormToken } from "./request-context.js";
|
|
32
31
|
import { createHash, randomBytes } from "node:crypto";
|
|
33
32
|
import { chatMoney, oneLineOf, relativeAge, safePrUrl, screen, sentenceCase, strokeIcon } from "./chrome.js";
|
|
@@ -90,7 +89,9 @@ export function taskChatApproval(focus, csrf) {
|
|
|
90
89
|
repairChain: approval.repairChain,
|
|
91
90
|
route: focus.route,
|
|
92
91
|
coordinator: approval.coordinator,
|
|
92
|
+
origin: approval.origin ?? null,
|
|
93
93
|
deliverable: approval.deliverable,
|
|
94
|
+
rebuild: focus.assignment?.need != null && "key" in focus.assignment.need && focus.assignment.need.key === "replan" ? focus.assignment.need.build : null,
|
|
94
95
|
// Edit plan opens the task page's sheet with its fields open for editing.
|
|
95
96
|
editHref: `${taskHref(focus.id)}?edit=plan#plan-editor`,
|
|
96
97
|
notNowHref: "/chat",
|
|
@@ -116,12 +117,15 @@ export function taskChatLiveRegion(focus, csrf, fragment = false, inert = false,
|
|
|
116
117
|
: fragment && focus.approval !== null && focus.plan !== "requested"
|
|
117
118
|
? html `<section class="card chat-action-card chat-refresh-action"><a class="button-link" href="${approvalHref}" data-primary-action>Approve plan</a></section>`
|
|
118
119
|
: taskChatApproval(focus, csrf));
|
|
119
|
-
|
|
120
|
+
// The approval leads when dispatch waits on it or the plan was rewritten after a build (the assignment's one action).
|
|
121
|
+
const approvalLeads = focus.dispatch?.action === "approve-scope" ||
|
|
122
|
+
(focus.assignment?.primaryAction?.code === "approve-scope" && focus.assignment.primaryAction.target.taskId === focus.executionId);
|
|
123
|
+
const approvalCard = focus.approval !== null && !approvalLeads
|
|
120
124
|
? html `<details class="task-secondary-approval"><summary>Updated approval terms</summary>${approvalContent}</details>` : approvalContent;
|
|
121
125
|
const polling = !inert && ((focus.approval === null || focus.plan === "requested") && focus.state !== "done" && focus.state !== "cancelled" || focus.control.kind === "stopping" || focus.control.kind === "stop");
|
|
122
126
|
const status = focus.assignment !== null
|
|
123
|
-
? html `${assignmentSummaryHtml(focus.assignment, { workStatus: focus.status, hideAction: focus.approval !== null &&
|
|
124
|
-
: receiptLeads ? completionReceiptCard(focus.result, focus.id, "chat", focus.status) : taskStatusCard(focus.status, focus.id, focus.dispatch, focus.liveRun?.id ?? null, focus.approval !== null &&
|
|
127
|
+
? html `${assignmentSummaryHtml(focus.assignment, { workStatus: focus.status, hideAction: focus.approval !== null && approvalLeads, sentence: chatStatusSentence(focus), ...(receiptLeads ? { resultHref: chatResultHref(focus.id, focus.result.runId) } : {}) })}${receiptLeads ? completionReceiptCard(focus.result, focus.id, "chat", focus.status, focus.assignment, false) : ""}`
|
|
128
|
+
: receiptLeads ? completionReceiptCard(focus.result, focus.id, "chat", focus.status) : taskStatusCard(focus.status, focus.id, focus.dispatch, focus.liveRun?.id ?? null, focus.approval !== null && approvalLeads);
|
|
125
129
|
const publication = focus.publication;
|
|
126
130
|
const prUrl = publication === null ? null : safePrUrl(publication.prUrl);
|
|
127
131
|
return html `<section id="task-chat-live" aria-live="polite" data-task="${focus.id}" data-execution="${focus.executionId}" data-source="/chat/task-status?task=${encodeURIComponent(focus.id)}" data-poll="${polling ? "1" : "0"}" data-approval="${focus.approval?.digest ?? ""}" data-plan="${focus.plan ?? ""}">${focus.guide === true && focus.state !== "cancelled" ? firstTaskJourneyHtml(focus) : ""}<div class="task-live-summary">${status}${checkProgressHtml(focus.checkProgress)}${
|
|
@@ -406,12 +410,12 @@ export function chatPage(chrome, data) {
|
|
|
406
410
|
const candidates = data.chat === null ? [] : [...data.chat.candidates.values()];
|
|
407
411
|
for (const one of candidates) {
|
|
408
412
|
const draft = one.draft;
|
|
409
|
-
parts.push(html `<div class="card"><p><strong>${draft.title}</strong> <span class="badge">Draft ${draft.kind}</span></p><p class="meta">Drafted by the model from fleet context — nothing is filed; drafts do not survive a restart</p><p style="white-space:pre-wrap">${draft.goal}</p>${draft.outOfScope === null ? "" : html `<p class="meta">Not: ${draft.outOfScope}</p>`}${draft.touches.length > 0 ? html `<p class="meta">Touches: ${draft.touches.join(", ")}</p>` : ""}<p class="meta">Repo: <span class="mono">${projectName(one.repoPath)}</span></p>${postForm(`/chat/file/${one.key}`, html `<input type="password" name="token" placeholder="your password" autocomplete="current-password"
|
|
413
|
+
parts.push(html `<div class="card"><p><strong>${draft.title}</strong> <span class="badge">Draft ${draft.kind}</span></p><p class="meta">Drafted by the model from fleet context — nothing is filed; drafts do not survive a restart</p><p style="white-space:pre-wrap">${draft.goal}</p>${draft.outOfScope === null ? "" : html `<p class="meta">Not: ${draft.outOfScope}</p>`}${draft.touches.length > 0 ? html `<p class="meta">Touches: ${draft.touches.join(", ")}</p>` : ""}<p class="meta">Repo: <span class="mono">${projectName(one.repoPath)}</span></p>${postForm(`/chat/file/${one.key}`, html `${reauth(html `<input type="password" name="token" placeholder="your password" autocomplete="current-password">`)}<button type="submit">File unapproved</button>`, { attrs: { class: "inline" } })}</div>`);
|
|
410
414
|
}
|
|
411
415
|
if (!subscription && data.canManage && data.focusTask === null) {
|
|
412
416
|
parts.push(html `<h2>Ask</h2>`, postForm("/chat", joinHtml([
|
|
413
417
|
html `<label>Message<textarea name="message" rows="3" maxlength="2000"></textarea></label>`,
|
|
414
|
-
html `<label>Your password <span class="meta">(every message — chat spends)</span><input type="password" name="token" autocomplete="current-password"></label
|
|
418
|
+
html `${reauth(html `<label>Your password <span class="meta">(every message — chat spends)</span><input type="password" name="token" autocomplete="current-password"></label>`)}`,
|
|
415
419
|
html `<button type="submit">Ask</button>`,
|
|
416
420
|
], "\n"), { attrs: { class: "card" } }));
|
|
417
421
|
}
|
|
@@ -423,15 +427,20 @@ export function chatPage(chrome, data) {
|
|
|
423
427
|
}
|
|
424
428
|
return screen("chat", chatWorkspace(joinHtml(parts, "\n"), data.projects, true, data.focusTask, data.resultPanel ?? null), { chrome, functional: { script: CHAT_UI_SCRIPT + (data.focusTask === null ? "" : RESULT_REVIEW_SCRIPT), fetches: data.focusTask !== null } });
|
|
425
429
|
}
|
|
426
|
-
export function chatAckPage(chrome, turn, nonce) {
|
|
430
|
+
export function chatAckPage(chrome, turn, nonce, reply = null) {
|
|
431
|
+
// A lead reply's step reserves nothing itself: the reply set the money aside, and a reply whose cost is unknown has
|
|
432
|
+
// already counted that whole amount against its conversation. Say that amount, never the step's zero.
|
|
433
|
+
const replyCharge = reply === null ? null : reply.settledMicrousd ?? reply.reservedMicrousd;
|
|
427
434
|
return screen("chat", joinHtml([
|
|
428
435
|
html `<h1>Unknown spend</h1>`,
|
|
429
436
|
html `<div class="card">`,
|
|
430
437
|
html `<p>Turn <span class="mono">#${turn.id}</span> on <span class="mono">${turn.provider} · ${turn.model}</span> may have started before it failed (${turn.failureReason ?? "crashed"}), and its cost could not be measured.</p>`,
|
|
431
|
-
|
|
438
|
+
replyCharge === null
|
|
439
|
+
? html `<p><strong>Acknowledging charges the reserved worst case, ${chatMoney(turn.reservedMicrousd)}, to the ledger and re-enables chat on this credential.</strong></p>`
|
|
440
|
+
: html `<p><strong>This lead reply's worst case, ${chatMoney(replyCharge)}, has already been counted against its conversation. Acknowledging adds nothing more and re-enables chat on this credential.</strong></p>`,
|
|
432
441
|
html `<p class="meta">Check the provider's own usage dashboard if you want the exact figure first; the ledger keeps whichever is known.</p>`,
|
|
433
442
|
postForm(`/chat/ack/${turn.id}`, joinHtml([
|
|
434
|
-
html `<label>Your password<input type="password" name="token" autocomplete="current-password"></label
|
|
443
|
+
html `${reauth(html `<label>Your password<input type="password" name="token" autocomplete="current-password"></label>`)}`,
|
|
435
444
|
html `<button type="submit">Accept the charge — re-enable chat</button>`,
|
|
436
445
|
], "\n"), { hidden: { nonce } }),
|
|
437
446
|
html `</div>`,
|
|
@@ -752,9 +761,26 @@ export function replacedThreadHtml(previous) {
|
|
|
752
761
|
? html `<div class="msg op" data-previous-message="${one.id}"><p style="white-space:pre-wrap">${one.text}</p></div>`
|
|
753
762
|
: html `<div class="msg mate" data-previous-message="${one.id}">${renderChatText(one.text)}</div>`)}<p class="chat-previous-divider" role="separator" data-thread-divider>${REPLACED_THREAD_DIVIDER}</p></section>`;
|
|
754
763
|
}
|
|
764
|
+
/** A queued message's state under its words: waiting (with Remove), or why it was not sent. Nothing for one that ran.
|
|
765
|
+
* A div, not a p: the Remove form can't sit inside a paragraph (the parser would close it first). */
|
|
766
|
+
function queuedStateHtml(row, back) {
|
|
767
|
+
if (row.status === "queued") {
|
|
768
|
+
const waiting = row.error?.startsWith("Waiting: ") === true ? row.error.slice("Waiting: ".length) : "It goes to the lead when it finishes.";
|
|
769
|
+
return html `<div class="so-queued" data-queued="${row.message}" data-queue-state="queued"><span class="so-queued-state">Queued</span> · ${waiting}${postForm(`/chat/queued/${row.message}/remove`, html `<button type="submit" class="so-queued-remove quiet">Remove</button>`, { attrs: { class: "inline" }, ...(back === null ? {} : { returnTo: back }) })}</div>`;
|
|
770
|
+
}
|
|
771
|
+
if (row.status === "running" && row.turnId === null)
|
|
772
|
+
return html `<div class="so-queued" data-queued="${row.message}" data-queue-state="starting"><span class="so-queued-state">Starting</span></div>`;
|
|
773
|
+
if (row.status === "cancelled" || row.status === "failed")
|
|
774
|
+
return html `<div class="so-queued" data-queued="${row.message}" data-queue-state="${row.status}"><span class="so-queued-state">Not sent</span> · ${row.error ?? "It was not sent."}</div>`;
|
|
775
|
+
if (row.status === "uncertain")
|
|
776
|
+
return html `<div class="so-queued" data-queued="${row.message}" data-queue-state="uncertain"><span class="so-queued-state">Not confirmed</span> · ${row.error ?? "Delivery could not be confirmed. Nothing was sent again."}</div>`;
|
|
777
|
+
return html ``;
|
|
778
|
+
}
|
|
755
779
|
/** The lead's question as buttons: each option sends itself as the next message (the same POST as a typed one);
|
|
756
780
|
* "Something else" moves to the composer. Nothing shows while a reply is running. */
|
|
757
|
-
export function leadAskHtml(ask, target, composer, open = true) {
|
|
781
|
+
export function leadAskHtml(ask, target, composer, open = true, stale = false) {
|
|
782
|
+
if (stale)
|
|
783
|
+
return html `<div class="so-owner-ask" data-ask="${ask.turn}" data-ask-stale><p class="so-owner-ask-question"><strong>${ask.question}</strong></p><p class="so-owner-ask-stale">Out of date: the task changed after this was asked. Its status above says what it needs now.</p></div>`;
|
|
758
784
|
if (!open)
|
|
759
785
|
return html `<div class="so-owner-ask" data-ask="${ask.turn}"><p class="so-owner-ask-question"><strong>${ask.question}</strong></p></div>`;
|
|
760
786
|
const hidden = target.task !== null ? { task: target.task } : target.project !== null ? { project: target.project } : {};
|
|
@@ -767,7 +793,7 @@ export function leadBrowserMessages(rows, back, target = { task: null, project:
|
|
|
767
793
|
const open = rows.pending === null ? rows.ask?.turn ?? null : null;
|
|
768
794
|
const asked = (turn) => {
|
|
769
795
|
const ask = turn === null ? undefined : rows.asks?.get(turn);
|
|
770
|
-
return ask === undefined ? "" : leadAskHtml(ask, target, target.task === null ? "lead-message" : "task-message", ask.turn === open);
|
|
796
|
+
return ask === undefined ? "" : leadAskHtml(ask, target, target.task === null ? "lead-message" : "task-message", ask.turn === open, ask.turn === rows.staleAsk);
|
|
771
797
|
};
|
|
772
798
|
// The owner's message each reply answers: when it asked for ids, the reply keeps them.
|
|
773
799
|
const askedBy = new Map();
|
|
@@ -776,7 +802,9 @@ export function leadBrowserMessages(rows, back, target = { task: null, project:
|
|
|
776
802
|
askedBy.set(message.id, last); return last; }, undefined);
|
|
777
803
|
return rows.messages.map(message => ({
|
|
778
804
|
id: message.id, role: message.role, text: message.text,
|
|
779
|
-
html: htmlString(message.role === 'operator'
|
|
805
|
+
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)}`; })()
|
|
807
|
+
: html `${renderChatText(message.text, askedBy.get(message.id))}${asked(message.turn)}`),
|
|
780
808
|
activity: message.activity, createdAt: message.createdAt,
|
|
781
809
|
...(() => {
|
|
782
810
|
// Under the lead's message only, as the server-rendered thread does: the person's message shares the turn.
|
|
@@ -796,6 +824,7 @@ export function leadChatVersion(rows) {
|
|
|
796
824
|
const focus = rows.focusTask;
|
|
797
825
|
const facts = [
|
|
798
826
|
rows.messages.map(one => [one.id, one.role, one.turn]),
|
|
827
|
+
rows.queue === undefined ? null : [...rows.queue.values()].map(one => [one.message, one.status, one.revision, one.error]),
|
|
799
828
|
rows.previous == null ? null : rows.previous.messages.map(one => one.id),
|
|
800
829
|
rows.proposals.map(one => [one.id, one.state, one.outcome === null ? null : JSON.stringify(one.outcome)]),
|
|
801
830
|
rows.coordinatorProposals.map(one => [one.id, one.state, one.outcome === null ? null : JSON.stringify(one.outcome)]),
|
|
@@ -851,7 +880,9 @@ export function leadThreadHtml(data) {
|
|
|
851
880
|
for (const message of data.messages) {
|
|
852
881
|
if (message.role === "operator") {
|
|
853
882
|
asked = message.text;
|
|
854
|
-
|
|
883
|
+
// A message that waits its turn (the lead queue) says so, with Remove while it still waits — as in the React thread.
|
|
884
|
+
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>`);
|
|
855
886
|
continue;
|
|
856
887
|
}
|
|
857
888
|
const cards = message.turn === null ? [] : (byTurn.get(message.turn) ?? []);
|
|
@@ -917,7 +948,7 @@ export function leadPage(chrome, data) {
|
|
|
917
948
|
// keyboard reaches it. Only this act moves the reader.
|
|
918
949
|
html `<div class="chat-new-update-holder"><button type="button" class="chat-new-update" id="chat-new-update" hidden>New update ↓</button></div>`, postForm("/chat", joinHtml([
|
|
919
950
|
html `<label>Message<textarea id="chat-message" name="message" rows="1" maxlength="${LEAD_MESSAGE_MAX_CHARS}" placeholder="${data.focusTask !== null ? "Ask about this task…" : chatProject !== null ? `Ask about ${projectName(chatProject)}…` : "Describe what you want done…"}"></textarea></label>`,
|
|
920
|
-
html `<button type="submit" aria-label="${data.pending === null ? "send message" : "
|
|
951
|
+
html `<button type="submit" aria-label="${data.pending === null ? "send message" : "send message — it waits for the current reply"}">Send</button>`,
|
|
921
952
|
], "\n"), {
|
|
922
953
|
attrs: { class: "card composer", id: latestReply === null && data.pending === null ? "latest" : "chat-composer", "aria-label": "message the lead", "data-chat-session": data.session.id, "data-chat-task": data.focusTask?.id ?? "", "data-chat-user": data.session.approver, "data-chat-busy": data.pending === null ? "0" : "1", "data-chat-version": leadChatVersion(data), "data-chat-approval": data.focusTask?.approval?.digest ?? "" },
|
|
923
954
|
hidden: { request: randomBytes(16).toString("hex"), "request-session": data.session.id, task: data.focusTask?.id, project: chatProject, result: data.resultRunId },
|
|
@@ -925,7 +956,7 @@ export function leadPage(chrome, data) {
|
|
|
925
956
|
// Concise pass (2026-09-13): the status line speaks only when there is
|
|
926
957
|
// a state to report — a reply in progress here, the connection from
|
|
927
958
|
// the continuity script — and the composer carries no second intro.
|
|
928
|
-
html `<p class="meta composer-hint" id="chat-connection" role="status" aria-live="polite">${data.pending === null ? "" : "Reply in progress.
|
|
959
|
+
html `<p class="meta composer-hint" id="chat-connection" role="status" aria-live="polite">${data.pending === null ? "" : "Reply in progress. A message you send now waits its turn."}</p>`,
|
|
929
960
|
// The explicit reconnection (package 2): shown only once the session
|
|
930
961
|
// or sign-in changed under this page; it reloads on the reader's act.
|
|
931
962
|
html `<p class="meta composer-hint" id="chat-reconnect" hidden><button type="button" class="quiet">Reconnect</button></p>`, leadAfterComposerHtml(data), controlsHtml);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** Everyday console pages: home, inbox, board, work, projects, fleet and the queue. */
|
|
2
|
+
import { type TaskOrigin } from "../task-source.js";
|
|
2
3
|
import { type Html } from "../html.js";
|
|
3
4
|
import { type AssignmentSnapshot } from "../assignment.js";
|
|
4
5
|
import { type BoardCard } from "../board.js";
|
|
@@ -360,7 +361,7 @@ export declare function portfolioOverview(data: {
|
|
|
360
361
|
startedAt: string;
|
|
361
362
|
ranMinutes: number | null;
|
|
362
363
|
costUsd: number | null;
|
|
363
|
-
authMode: "subscription" | "api-key" | null;
|
|
364
|
+
authMode: "subscription" | "api-key" | "agent-login" | null;
|
|
364
365
|
prNumber: number | null;
|
|
365
366
|
prUrl: string | null;
|
|
366
367
|
}[];
|
|
@@ -519,6 +520,8 @@ export declare function nextPage(chrome: Chrome, data: {
|
|
|
519
520
|
deliverable?: "branch" | "report";
|
|
520
521
|
/** v48: the agents the yes freezes — the one block every ceremony shows. */
|
|
521
522
|
route: RouteView | null;
|
|
523
|
+
/** v126: where the work came from (an outside event's line of context, never a term agreed to). */
|
|
524
|
+
origin?: TaskOrigin | null;
|
|
522
525
|
csrf: string;
|
|
523
526
|
nonce: string;
|
|
524
527
|
remaining: number;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Everyday console pages: home, inbox, board, work, projects, fleet and the queue. */
|
|
2
|
-
import {
|
|
2
|
+
import { originNote } from "../task-source.js";
|
|
3
|
+
import { html, htmlString, joinHtml, postForm, replaceMarkup, reauth } from "../html.js";
|
|
3
4
|
import {} from "../assignment.js";
|
|
4
5
|
import {} from "../board.js";
|
|
5
6
|
import { browserWorkActionHref } from "../browser-workspace.js";
|
|
@@ -912,7 +913,7 @@ export function projectsPage(chrome, recent, candidates, open, problem, _unscope
|
|
|
912
913
|
onboard.record[1].large
|
|
913
914
|
? html `<label class="row"><input type="checkbox" name="big-ok" value="1"> this is a large repository (or its size is unknown) — clone it anyway</label>`
|
|
914
915
|
: null,
|
|
915
|
-
html `<label>Your password, typed again <input type="password" name="token" autocomplete="current-password"></label
|
|
916
|
+
html `${reauth(html `<label>Your password, typed again <input type="password" name="token" autocomplete="current-password"></label>`)}`,
|
|
916
917
|
html `<div class="sticky-actions"><button type="submit">Clone and open</button></div>`,
|
|
917
918
|
], "\n"), { hidden: { nonce: onboard.record[0] } }),
|
|
918
919
|
html `</div>`,
|
|
@@ -1258,11 +1259,11 @@ export function nextPage(chrome, data) {
|
|
|
1258
1259
|
card =
|
|
1259
1260
|
html `<h1>${item.approval.taskId}</h1><p>${item.approval.title}</p>${data.planDocument === null
|
|
1260
1261
|
? null
|
|
1261
|
-
: html `<div class="card"><p><strong>The plan</strong> <span class="meta">drafted by a planning session</span></p>${executionPlanHtml(data.planDocument, true)}${planContractHtml(data.planContract ?? null, "ceremony")}</div>`}${postForm(`${taskHref(item.approval.taskId)}/approve`, html `<p><strong>Approve exactly this:</strong></p>${data.deliverable === "report" ? html `<p class="meta"><span class="badge">Scout</span> a read-only session investigates this goal and delivers a report — no branch, nothing changes in the repository</p>` : null}${scope === null || scope.profileState !== "unresolved" ? null : profileWords(scope)}<p class="meta">Goal</p><p class="recap" style="margin-top:0">${scope?.goal ?? item.approval.goal}</p><p class="meta">Not this</p><p class="recap" style="margin-top:0">${scope?.outOfScope == null ? html `<em>no exclusions</em>` : scope.outOfScope}</p><p class="meta">Touches · ${scope === null || scope.touches.length === 0 ? "anything" : joinHtml(scope.touches, ", ")}</p>${scope === null ? null : acceptanceCeremonyHtml(scope.acceptance)}${
|
|
1262
|
+
: html `<div class="card"><p><strong>The plan</strong> <span class="meta">drafted by a planning session</span></p>${executionPlanHtml(data.planDocument, true)}${planContractHtml(data.planContract ?? null, "ceremony")}</div>`}${postForm(`${taskHref(item.approval.taskId)}/approve`, html `<p><strong>Approve exactly this:</strong></p>${data.deliverable === "report" ? html `<p class="meta"><span class="badge">Scout</span> a read-only session investigates this goal and delivers a report — no branch, nothing changes in the repository</p>` : null}${scope === null || scope.profileState !== "unresolved" ? null : profileWords(scope)}${originNote(data.origin) === null ? null : html `<p class="meta" data-approval-origin>${originNote(data.origin)}</p>`}<p class="meta">Goal</p><p class="recap" style="margin-top:0">${scope?.goal ?? item.approval.goal}</p><p class="meta">Not this</p><p class="recap" style="margin-top:0">${scope?.outOfScope == null ? html `<em>no exclusions</em>` : scope.outOfScope}</p><p class="meta">Touches · ${scope === null || scope.touches.length === 0 ? "anything" : joinHtml(scope.touches, ", ")}</p>${scope === null ? null : acceptanceCeremonyHtml(scope.acceptance)}${
|
|
1262
1263
|
// The AGENTS the yes freezes (v48): the same concise block the task
|
|
1263
1264
|
// page and chat sign under, before the password — runtime limits one
|
|
1264
1265
|
// tap away, never in the way.
|
|
1265
|
-
agentsCeremonyHtml(data.route)}${scope === null ? null : runtimeDetailsHtml(scope)}
|
|
1266
|
+
agentsCeremonyHtml(data.route)}${scope === null ? null : runtimeDetailsHtml(scope)}${reauth(html `<label>Your password, typed again — a signed-in session alone cannot agree to work<input type="password" name="token" autocomplete="current-password"></label>`)}<div class="sticky-actions"><button type="submit">Approve this scope</button></div>`, { attrs: { class: "card approve-form" }, returnTo: "next", hidden: { nonce: data.nonce, digest: data.approvalDigest ?? item.approval.digest } })}<p class="meta"><a href="${taskHref(item.approval.taskId)}">open the full task</a> to edit the scope first</p>`;
|
|
1266
1267
|
}
|
|
1267
1268
|
else if (item.kind === "requeue") {
|
|
1268
1269
|
card =
|
|
@@ -7,8 +7,9 @@ export declare function joinDeadPage(): Html;
|
|
|
7
7
|
/**
|
|
8
8
|
* v100: for someone signed in with the identity provider, every step-up
|
|
9
9
|
* password field (autocomplete="current-password"; a secret like a bot token
|
|
10
|
-
* is never one) becomes that sign-in: confirmed, when
|
|
11
|
-
*
|
|
10
|
+
* is never one) becomes that sign-in: confirmed, when an explicit re-check
|
|
11
|
+
* made on request awaits its one step-up (cockpit B2), or a link to be
|
|
12
|
+
* checked again, then back here. An ordinary sign-in never confirms.
|
|
12
13
|
*/
|
|
13
14
|
export declare function ssoStepUps(page: Html, sso: {
|
|
14
15
|
label: string;
|
|
@@ -33,8 +33,9 @@ export function joinDeadPage() {
|
|
|
33
33
|
/**
|
|
34
34
|
* v100: for someone signed in with the identity provider, every step-up
|
|
35
35
|
* password field (autocomplete="current-password"; a secret like a bot token
|
|
36
|
-
* is never one) becomes that sign-in: confirmed, when
|
|
37
|
-
*
|
|
36
|
+
* is never one) becomes that sign-in: confirmed, when an explicit re-check
|
|
37
|
+
* made on request awaits its one step-up (cockpit B2), or a link to be
|
|
38
|
+
* checked again, then back here. An ordinary sign-in never confirms.
|
|
38
39
|
*/
|
|
39
40
|
export function ssoStepUps(page, sso, returnTo) {
|
|
40
41
|
const stepUp = (attributes) => /\btype="password"/.test(attributes) && /\bautocomplete="current-password"/.test(attributes);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** Results and runs: the review cockpit, evidence, receipts, diffs and the result panel. */
|
|
2
|
+
import type { RunNetworkRecord } from "../egress.js";
|
|
1
3
|
import { type AssignmentSnapshot } from "../assignment.js";
|
|
2
4
|
import { type BrowserCheckItem, type BrowserNeedAction, type BrowserResultPanel, type BrowserResultView } from "../browser-workspace.js";
|
|
3
5
|
import { type ReportShot } from "../evidence.js";
|
|
@@ -7,6 +9,7 @@ import { type CriterionMatrixRow, type ProofVerdict } from "../proof.js";
|
|
|
7
9
|
import { type PullRequestView } from "../pull-request-flow.js";
|
|
8
10
|
import { type StructuredHandoffView, type TerminalDiffView } from "../result-evidence-readers.js";
|
|
9
11
|
import { type FollowUpCheck } from "../result-follow-ups.js";
|
|
12
|
+
import type { RunEfficiency } from "../run-efficiency.js";
|
|
10
13
|
import { type ResultScreenshot, type ResultTab, type SharedResultFacts } from "../result-review.js";
|
|
11
14
|
import { type BuildReviewView } from "../review-switch.js";
|
|
12
15
|
import { type AcceptanceCriterion, type approvalOf } from "../scope.js";
|
|
@@ -180,6 +183,10 @@ export type RankedReviewRow = CompletedWorkRow & {
|
|
|
180
183
|
* scope, plan, run, artifact, verdict, and publication records,
|
|
181
184
|
* each read through the verifier the run page already uses. */
|
|
182
185
|
export type ReviewCockpitView = {
|
|
186
|
+
/** How the result's run went (docs/plans/run-efficiency.md): display only, its sources and their gaps said. */
|
|
187
|
+
efficiency?: RunEfficiency | null;
|
|
188
|
+
/** What the run's network was and what its proxy saw (egress.ts): display only. */
|
|
189
|
+
network?: RunNetworkRecord | null;
|
|
183
190
|
taskId: string;
|
|
184
191
|
title: string;
|
|
185
192
|
repo: string | null;
|
|
@@ -285,7 +292,16 @@ export declare function reviewCockpitPage(chrome: Chrome, data: {
|
|
|
285
292
|
export declare function reviewCockpitDetail(view: ReviewCockpitView, csrf: string, noted: boolean, canRetryReview?: boolean, tab?: ResultTab, user?: string): Html;
|
|
286
293
|
/** The selected result's HTML and, for the rebuilt page, the same result
|
|
287
294
|
* as data: every form and panel body is the one the HTML carries. */
|
|
288
|
-
|
|
295
|
+
/** 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),
|
|
296
|
+
* whose address is `base` (the task page with tab=result, and its version when not the current one). The result's own
|
|
297
|
+
* view (Summary / Changes / Checks) rides `section=` there, so it never fights the task page's `tab`. */
|
|
298
|
+
export type ResultHost = {
|
|
299
|
+
kind: "review";
|
|
300
|
+
} | {
|
|
301
|
+
kind: "task";
|
|
302
|
+
base: string;
|
|
303
|
+
};
|
|
304
|
+
export declare function reviewCockpitDetailParts(view: ReviewCockpitView, csrf: string, noted: boolean, canRetryReview: boolean, tab: ResultTab, user: string, host?: ResultHost): {
|
|
289
305
|
html: Html;
|
|
290
306
|
selected: NonNullable<BrowserResultView["selected"]>;
|
|
291
307
|
};
|
|
@@ -319,7 +335,7 @@ export declare function pullRequestCardHtml(pr: TaskPullRequest, csrf: string):
|
|
|
319
335
|
* bearer session (no token) sees the road named, never a form. */
|
|
320
336
|
export declare function reviewNextAction(view: ReviewCockpitView, csrf: string, _accepted: boolean, _canAnnotate: boolean): Html;
|
|
321
337
|
/** The next action for the browser workspace's JSON: its control as markup text. */
|
|
322
|
-
export declare function reviewNextActionOf(view: ReviewCockpitView, csrf: string): {
|
|
338
|
+
export declare function reviewNextActionOf(view: ReviewCockpitView, csrf: string, back?: string): {
|
|
323
339
|
kind: string;
|
|
324
340
|
title: string;
|
|
325
341
|
detail: string;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/** Results and runs: the review cockpit, evidence, receipts, diffs and the result panel. */
|
|
2
1
|
import { assignmentPresentationOf, historicalAssessmentReason, shortenedMaterialReason } from "../assignment-presentation.js";
|
|
3
2
|
import { assignmentActionHref, assignmentStatusOf, assignmentWithEvidence } from "../assignment-ui.js";
|
|
4
3
|
import { owedAcceptance } from "../assignment.js";
|
|
@@ -26,7 +25,7 @@ import { ACCEPT_NEEDS_REASON, acceptWordsOf, cantAcceptYetOf, evidenceProblemOf,
|
|
|
26
25
|
import { createHash, randomBytes } from "node:crypto";
|
|
27
26
|
import { buildsViews, oneLineOf, projectChip, safePrUrl, screen, sentenceCase, strokeIcon, when, whenTime } from "./chrome.js";
|
|
28
27
|
import { chatResultHref, reviewHref, taskChatHref, taskHref } from "./http.js";
|
|
29
|
-
import { attributes, html, htmlString, joinHtml, postForm } from "../html.js";
|
|
28
|
+
import { attributes, html, htmlString, joinHtml, postForm, reauth } from "../html.js";
|
|
30
29
|
import { publicationFactsOf, statusActionHref, statusLineHtml } from "./render-tasks.js";
|
|
31
30
|
export const RUNS_PAGE = 50;
|
|
32
31
|
/**
|
|
@@ -574,23 +573,23 @@ export function reviewCockpitPage(chrome, data) {
|
|
|
574
573
|
export function reviewCockpitDetail(view, csrf, noted, canRetryReview = false, tab = "summary", user = "") {
|
|
575
574
|
return reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab, user).html;
|
|
576
575
|
}
|
|
577
|
-
|
|
578
|
-
* as data: every form and panel body is the one the HTML carries. */
|
|
579
|
-
export function reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab, user) {
|
|
576
|
+
export function reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab, user, host = { kind: "review" }) {
|
|
580
577
|
const parts = [];
|
|
581
578
|
const run = view.run;
|
|
582
579
|
const proof = view.detail?.proof ?? null;
|
|
583
580
|
const accepted = proof?.accepted !== null && proof?.accepted !== undefined;
|
|
584
581
|
const canAnnotate = canAnnotateDiff(view, csrf);
|
|
585
582
|
const status = cockpitStatusOf(view);
|
|
586
|
-
// Header: what this is, its verdict word, and why it sits where it does.
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
583
|
+
// Header: what this is, its verdict word, and why it sits where it does. On a task page the task's own header says
|
|
584
|
+
// all of it, once.
|
|
585
|
+
if (host.kind === "review")
|
|
586
|
+
parts.push(joinHtml([
|
|
587
|
+
html `<header class="cockpit-head" data-review-task="${view.taskId}">`,
|
|
588
|
+
html `<h1>${view.title}</h1>`,
|
|
589
|
+
html `<p class="meta">${run === null ? "No build" : `Build #${run.id}`} · <a href="${taskHref(view.taskId)}">Open task</a>${projectChip(view.repo)}</p>`,
|
|
590
|
+
html `<p class="cockpit-chips">${statusLineHtml(status)}</p>`,
|
|
591
|
+
html `</header>`,
|
|
592
|
+
]));
|
|
594
593
|
if (view.detail === null && view.historyProblem !== null)
|
|
595
594
|
parts.push(html `<p class="problem" data-history-problem>${view.historyProblem} <a href="${taskHref(view.taskId)}">Open task</a></p>`);
|
|
596
595
|
// The primary next act — exactly one road, chosen from the state.
|
|
@@ -629,8 +628,10 @@ export function reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab,
|
|
|
629
628
|
taskHref: taskHref(view.taskId), chatHref: taskChatHref(view.taskId),
|
|
630
629
|
status: { label: status.label, tone: status.tone, token: status.token },
|
|
631
630
|
problem: view.detail === null ? view.historyProblem : null,
|
|
632
|
-
next: reviewNextActionOf(view, csrf), complete: null, decision: null, checks: null, intent: intentView, noRun: null, panel: null,
|
|
631
|
+
next: reviewNextActionOf(view, csrf, host.kind === "task" && run !== null ? `${host.base}&run=${run.id}` : undefined), complete: null, decision: null, checks: null, intent: intentView, noRun: null, panel: null,
|
|
633
632
|
notes: view.notes.map(one => ({ author: one.author, at: one.createdAt, note: one.note })),
|
|
633
|
+
efficiency: view.efficiency ?? null,
|
|
634
|
+
network: view.network ?? null,
|
|
634
635
|
acts: { primary: null, secondary: null, line: null }, runChecks: null, mismatch: null,
|
|
635
636
|
// The raw run record lives under Details now (2026-10-02): /r/<id> for this result redirects here.
|
|
636
637
|
record: run === null ? null : { build: run.id, href: `/r/${run.id}?record=1`, facts: [
|
|
@@ -651,6 +652,10 @@ export function reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab,
|
|
|
651
652
|
// v50 review-retry panel and the accept-with-exception form.
|
|
652
653
|
const extraChecks = reviewRetryPanel(view.taskId, run.id, view.reviewRetry, { csrf, canAct: false, returnTo: null });
|
|
653
654
|
const here = reviewHref(view.taskId);
|
|
655
|
+
/** This result's own address on its host, at one of its views (summary when none). */
|
|
656
|
+
const at = (section = "summary") => host.kind === "task"
|
|
657
|
+
? `${host.base}&run=${run.id}${section === "summary" ? "" : `§ion=${section}`}`
|
|
658
|
+
: `${here}&run=${run.id}${section === "summary" ? "" : `&tab=${section}`}`;
|
|
654
659
|
const assignment = view.assignment === null ? null : assignmentWithEvidence(view.assignment, receiptStatusOf(view.detail.receipt), run.id);
|
|
655
660
|
const checks = assignment?.receipt?.checks;
|
|
656
661
|
if (checks !== undefined)
|
|
@@ -664,13 +669,16 @@ export function reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab,
|
|
|
664
669
|
user,
|
|
665
670
|
noted,
|
|
666
671
|
requestToken: randomBytes(16).toString("hex"),
|
|
667
|
-
hrefFor: one =>
|
|
668
|
-
returnTo:
|
|
672
|
+
hrefFor: one => at(one),
|
|
673
|
+
returnTo: at(),
|
|
669
674
|
back: null,
|
|
670
675
|
extraChecks,
|
|
671
676
|
headStatus: false,
|
|
672
677
|
action: false,
|
|
673
678
|
});
|
|
679
|
+
// On a task page the result script keeps the result's own view in `section=` (the page's `tab` is the task's).
|
|
680
|
+
if (host.kind === "task")
|
|
681
|
+
panel.panel.attributes = { ...panel.panel.attributes, "data-result-param": "section" };
|
|
674
682
|
parts.push(html `<div id="verification" data-cockpit-section="result">${panel.html}</div>`);
|
|
675
683
|
// What no acceptance resolves (a failed check, a HIGH finding): Complete is refused, so no Accept is offered and
|
|
676
684
|
// the one line says the next step.
|
|
@@ -682,7 +690,7 @@ export function reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab,
|
|
|
682
690
|
const owed = accepted ? null : youCheck?.accept != null ? { note: null } : panel.panel.need?.accept != null ? { note: panel.panel.need.accept.note }
|
|
683
691
|
: blocked === ACCEPT_NEEDS_REASON ? { note: "Why is this safe to accept?" } : null;
|
|
684
692
|
const complete = hardStop === null && assignment?.state === "ready-to-check" && assignment.receipt !== null && canRetryReview && csrf !== ""
|
|
685
|
-
? { action: `${taskHref(view.taskId)}/complete`, receipt: assignment.receipt.digest, run: run.id, accept: owed } : null;
|
|
693
|
+
? { action: `${taskHref(view.taskId)}/complete`, receipt: assignment.receipt.digest, run: run.id, accept: owed, ...(host.kind === "task" ? { returnTo: at() } : {}) } : null;
|
|
686
694
|
if (complete !== null)
|
|
687
695
|
parts.push(completionForm(view.taskId, run.id, complete.receipt, view.detail?.pullRequestTo ?? null, owed));
|
|
688
696
|
// The one decision, after the evidence: Accept and finish only when every requirement is met and the checks passed.
|
|
@@ -708,13 +716,14 @@ export function reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab,
|
|
|
708
716
|
const runChecks = canRetryReview && csrf !== "" && followUps !== null && (followUps.full || followUps.quick) && !checksRunning &&
|
|
709
717
|
(checks === undefined || ((checks.status === "not-run" || checks.status === "unavailable") && checks.release == null))
|
|
710
718
|
// Back on this result's Checks tab, where #follow-ups shows the run it started.
|
|
711
|
-
? { action: `/r/${run.id}/checks`, level: followUps.full ? "full" : "quick", returnTo:
|
|
719
|
+
? { action: `/r/${run.id}/checks`, level: followUps.full ? "full" : "quick", returnTo: at("checks") } : null;
|
|
712
720
|
const need = panel.panel.need;
|
|
713
721
|
const nextKind = selected.next?.kind;
|
|
714
722
|
// A failed build: what went wrong is the card, Retry the act; a link to this same page goes nowhere, so it isn't one.
|
|
715
723
|
const failure = view.failure == null ? null : { line: view.failure.line, evidence: view.failure.evidence, suggestion: view.failure.suggestion, retry: view.failure.retry,
|
|
716
|
-
link: view.failure.link === null || (view.failure.link.href.startsWith(`${here}&run=${run.id}`) && !view.failure.link.href.includes("#")) ? null
|
|
717
|
-
|
|
724
|
+
link: view.failure.link === null || (view.failure.link.href.startsWith(`${here}&run=${run.id}`) && !view.failure.link.href.includes("#")) ? null
|
|
725
|
+
: host.kind === "task" && view.failure.link.href.startsWith(`${here}&run=${run.id}&tab=`) ? { ...view.failure.link, href: view.failure.link.href.replace(`${here}&run=${run.id}&tab=`, `${host.base}&run=${run.id}§ion=`) } : view.failure.link,
|
|
726
|
+
...(view.failure.acceptAnyway == null ? {} : { acceptAnyway: { ...view.failure.acceptAnyway, returnTo: at() } }) };
|
|
718
727
|
const actFacts = failure !== null
|
|
719
728
|
? { accept: null, runChecks: runChecks !== null, checksRunning, blocked: null, canRequest: false, need: null, next: null, failed: { retry: failure.retry !== null, acceptAnyway: failure.acceptAnyway !== undefined } }
|
|
720
729
|
: {
|
|
@@ -744,7 +753,7 @@ export function reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab,
|
|
|
744
753
|
const lines = (added.length > 0 ? added : shown).map(line => line.newLine);
|
|
745
754
|
return [file.path, lines.length === 0 ? null : { from: Math.min(...lines), to: Math.max(...lines) }];
|
|
746
755
|
}));
|
|
747
|
-
const changesHref =
|
|
756
|
+
const changesHref = at("changes");
|
|
748
757
|
const found = reportMismatchesOf(proof.reasons, proof.proofProblem === null ? proof.matrix : [], changes);
|
|
749
758
|
// The saved report itself can't be read: that is the evidence problem, said once.
|
|
750
759
|
if (proof.proofProblem !== null)
|
|
@@ -757,7 +766,7 @@ export function reviewCockpitDetailParts(view, csrf, noted, canRetryReview, tab,
|
|
|
757
766
|
text: one.text, path: one.path, absent: one.inChanges === false,
|
|
758
767
|
lines: one.lines === null ? null : one.lines.from === one.lines.to ? `line ${one.lines.from}` : `lines ${one.lines.from}–${one.lines.to}`,
|
|
759
768
|
href: one.path !== null && one.inChanges === true ? `${changesHref}#${diffFileAnchor(one.path)}${hunkStart.has(one.path) ? `-L${hunkStart.get(one.path)}` : ""}`
|
|
760
|
-
: one.note !== null && (proof.proof?.caveats.length ?? 0) >= one.note ? `${
|
|
769
|
+
: one.note !== null && (proof.proof?.caveats.length ?? 0) >= one.note ? `${at()}#report-note-${one.note}` : null,
|
|
761
770
|
noteLabel: one.path === null && one.note !== null ? `The report, note ${one.note}` : null,
|
|
762
771
|
})),
|
|
763
772
|
said: [...new Set([...proof.reasons, ...found.map(one => one.reason)].flatMap(reason => [reason, plainReasonWords(reason)]))],
|
|
@@ -825,7 +834,7 @@ export function pullRequestCardHtml(pr, csrf) {
|
|
|
825
834
|
const merged = view.mergeCommit === null ? "" : html `<p class="meta">Merge commit <span class="mono">${view.mergeCommit.slice(0, 12)}</span></p>`;
|
|
826
835
|
const target = pr.target === null ? "the base branch" : pr.target;
|
|
827
836
|
const act = !view.canMerge || csrf === "" || view.prNumber === null ? "" :
|
|
828
|
-
postForm(`${taskHref(pr.taskId)}/merge`, html `<p class="meta">${view.mergeMethod === "squash" ? "Squash-merges" : view.mergeMethod === "rebase" ? "Rebase-merges" : "Merges"} PR #${view.prNumber} into ${target} and deletes its branch.</p
|
|
837
|
+
postForm(`${taskHref(pr.taskId)}/merge`, html `<p class="meta">${view.mergeMethod === "squash" ? "Squash-merges" : view.mergeMethod === "rebase" ? "Rebase-merges" : "Merges"} PR #${view.prNumber} into ${target} and deletes its branch.</p>${reauth(html `<label>Password<input type="password" name="token" autocomplete="current-password" required></label>`)}${view.fullCheck === null ? html `<button type="submit">Merge</button>` : html `<input type="hidden" name="anyway" value="1"><button type="submit" class="secondary">Merge anyway</button>`}`, { attrs: { class: "pull-request-act", "data-merge-form": true }, hidden: { run: view.runId } });
|
|
829
838
|
return html `<section class="card pull-request pull-request--${tone}" id="merge" data-pull-request="${view.state}"><div class="pull-request-head"><strong>${view.label}</strong>${link}</div><p class="meta">${view.detail}${revision}</p>${merged}${act}</section>`;
|
|
830
839
|
}
|
|
831
840
|
/** Exactly one primary road per result, chosen from its state; the
|
|
@@ -837,11 +846,11 @@ export function reviewNextAction(view, csrf, _accepted, _canAnnotate) {
|
|
|
837
846
|
return next === null ? html `` : html `<div class="card cockpit-next" data-next-action="${next.kind}"><div><strong>${next.title}</strong><span class="meta">${next.detail}</span></div>${next.control}</div>`;
|
|
838
847
|
}
|
|
839
848
|
/** The next action for the browser workspace's JSON: its control as markup text. */
|
|
840
|
-
export function reviewNextActionOf(view, csrf) {
|
|
841
|
-
const next = reviewNextActionParts(view, csrf);
|
|
849
|
+
export function reviewNextActionOf(view, csrf, back) {
|
|
850
|
+
const next = reviewNextActionParts(view, csrf, back);
|
|
842
851
|
return next === null ? null : { ...next, control: htmlString(next.control) };
|
|
843
852
|
}
|
|
844
|
-
function reviewNextActionParts(view, csrf) {
|
|
853
|
+
function reviewNextActionParts(view, csrf, back) {
|
|
845
854
|
const run = view.run;
|
|
846
855
|
const detail = view.detail;
|
|
847
856
|
const card = (kind, title, detailWords, control) => ({ kind, title, detail: detailWords, control });
|
|
@@ -852,7 +861,7 @@ function reviewNextActionParts(view, csrf) {
|
|
|
852
861
|
return card("draft-repair", "CI is failing on its pull request", "Toolroll confirmed the failure. Draft one repair task, then approve it before it runs.", postForm(`/r/${run.id}/draft-repair`, html `<button type="submit">Draft a repair task</button>`));
|
|
853
862
|
}
|
|
854
863
|
if (detail.comments.length > 0 && csrf !== "") {
|
|
855
|
-
return card("revise", `${detail.comments.length} note${detail.comments.length === 1 ? "" : "s"} ready`, "Create one revision from these notes. You approve it before it runs.", postForm(`/r/${run.id}/revise`, html `${revisionSealFields(detail.comments, detail.sourceDigest)}<button type="submit">Revise</button>`, { returnTo: reviewHref(view.taskId) }));
|
|
864
|
+
return card("revise", `${detail.comments.length} note${detail.comments.length === 1 ? "" : "s"} ready`, "Create one revision from these notes. You approve it before it runs.", postForm(`/r/${run.id}/revise`, html `${revisionSealFields(detail.comments, detail.sourceDigest)}<button type="submit">Revise</button>`, { returnTo: back ?? reviewHref(view.taskId) }));
|
|
856
865
|
}
|
|
857
866
|
return null;
|
|
858
867
|
}
|
|
@@ -1982,7 +1991,7 @@ export function resultNeedAction(need, o, canRequest) {
|
|
|
1982
1991
|
: need.rebuild != null
|
|
1983
1992
|
? postForm(need.rebuild.action, html `<button type="submit" data-primary-action data-rebuild style="min-height:44px">${need.label}</button>`, { attrs: { class: "rebuild" } })
|
|
1984
1993
|
: need.confirm !== null
|
|
1985
|
-
? postForm(need.confirm.action, html `${need.confirm.checked === true ? html `<label><input type="checkbox" name="checked" value="yes" required> Nothing from build #${need.confirm.run} is running</label>` : ""}
|
|
1994
|
+
? postForm(need.confirm.action, html `${need.confirm.checked === true ? html `<label><input type="checkbox" name="checked" value="yes" required> Nothing from build #${need.confirm.run} is running</label>` : ""}${reauth(html `<label>Your password<input type="password" name="token" autocomplete="current-password" required></label>`)}<button type="submit" style="min-height:44px">${need.label}</button>`, { attrs: { id: "confirm-stopped", class: "confirm-stopped" }, returnTo: need.confirm.returnTo, hidden: { run: need.confirm.run } })
|
|
1986
1995
|
: html `<a class="button-link" href="${need.href ?? "#"}" data-primary-action>${need.label}</a>`;
|
|
1987
1996
|
return html `<div class="result-action" data-result-action="need">${control}${canRequest && o.csrf !== "" ? html `<a class="result-feedback-link" href="#request-changes">Request changes</a>` : ""}</div>`;
|
|
1988
1997
|
}
|
|
@@ -23,7 +23,7 @@ import { redactToken, TOKEN_ENV } from "../telegram.js";
|
|
|
23
23
|
import { requestContext } from "./request-context.js";
|
|
24
24
|
import { chatMoney, screen, strokeIcon, when, whenTime } from "./chrome.js";
|
|
25
25
|
import { taskHref } from "./http.js";
|
|
26
|
-
import { html, joinHtml, postForm } from "../html.js";
|
|
26
|
+
import { html, joinHtml, postForm, reauth } from "../html.js";
|
|
27
27
|
import { permissionModeChoices, qualityModeChoices } from "./render-tasks.js";
|
|
28
28
|
/** Settings → Lead: one line for what runs the lead and one action; the full form, turning it off and stored keys under Advanced. */
|
|
29
29
|
export function leadSettingsHtml(data) {
|
|
@@ -36,7 +36,7 @@ export function leadSettingsHtml(data) {
|
|
|
36
36
|
: data.words !== null
|
|
37
37
|
? html `<p>The lead uses ${data.words}.</p><p class="meta">${config.model === "default" ? "Its default model" : `Model ${config.model}`} · up to ${config.dailyTurns} turns a day · no dollar spend. Every action it proposes waits for you to confirm it.</p>`
|
|
38
38
|
: html `<p>The lead uses the ${config.provider === "anthropic-api" ? "Anthropic" : "OpenRouter"} API with ${config.model}.</p><p class="meta">Up to ${chatMoney(config.weeklyCeilingMicrousd)} a week · up to ${config.dailyTurns} turns a day.</p>`;
|
|
39
|
-
const forget = facts.keyFacts.filter(one => one.state === "stored").map(one => postForm("/chat/config", html `<input type="password" name="token" placeholder="your password" autocomplete="current-password" aria-label="Your password"
|
|
39
|
+
const forget = facts.keyFacts.filter(one => one.state === "stored").map(one => postForm("/chat/config", html `${reauth(html `<input type="password" name="token" placeholder="your password" autocomplete="current-password" aria-label="Your password">`)}<button type="submit" class="secondary">Forget the stored ${one.provider} key</button>`, { attrs: { class: "inline" }, returnTo, hidden: { "forget-key": one.provider } }));
|
|
40
40
|
return joinHtml([
|
|
41
41
|
html `<p><a href="/settings">Settings</a></p><h1>Lead</h1>`,
|
|
42
42
|
data.said === null ? "" : html `<p class="problem" role="status">${data.said}</p>`,
|
|
@@ -58,7 +58,7 @@ ${postForm("/settings/lead/promise/cancel", html `<button type="submit" class="s
|
|
|
58
58
|
: html `<ul class="lead-subagent-list">${data.subagents.map(one => html `<li data-subagent="${one.id}"><a href="/settings/lead/subagents/${one.id}">${one.label}</a><span class="meta">${one.project}${one.paused ? " · Paused" : ""}</span></li>`)}</ul>`}<p><a class="button-link" href="/settings/lead/subagents?add=1#add" data-add-subagent>Add subagent</a></p></section>`,
|
|
59
59
|
html `<details class="lead-advanced" data-lead-advanced><summary>Advanced</summary>`,
|
|
60
60
|
leadConfigForm(config, facts),
|
|
61
|
-
config === null ? "" : postForm("/chat/config", html `<input type="password" name="token" placeholder="your password" autocomplete="current-password" aria-label="Your password"
|
|
61
|
+
config === null ? "" : postForm("/chat/config", html `${reauth(html `<input type="password" name="token" placeholder="your password" autocomplete="current-password" aria-label="Your password">`)}<button type="submit" class="secondary">Turn the lead off</button>`, { attrs: { class: "inline" }, returnTo, hidden: { off: "1" } }),
|
|
62
62
|
forget,
|
|
63
63
|
html `</details>`,
|
|
64
64
|
], "\n");
|
|
@@ -102,7 +102,7 @@ ${models.map(model => html `<option value="${model}">${labels.get(model) ?? ""}<
|
|
|
102
102
|
.join(" · ")})</span>\
|
|
103
103
|
<input type="password" name="key" placeholder="direct API only — leave empty to keep" autocomplete="off"></label>`,
|
|
104
104
|
html `<label>Your password <span class="meta">(typed again to change the provider)</span>\
|
|
105
|
-
|
|
105
|
+
${reauth(html `<input type="password" name="token" autocomplete="current-password">`)}</label>`,
|
|
106
106
|
html `<button type="submit">${current === null ? "turn chat on" : "save"}</button>`,
|
|
107
107
|
"",
|
|
108
108
|
], "\n"), { attrs: { class: "card" }, returnTo: data.returnTo }),
|
|
@@ -290,7 +290,7 @@ ${[
|
|
|
290
290
|
postForm("/push/subscribe", joinHtml([
|
|
291
291
|
"",
|
|
292
292
|
html `<input type="hidden" name="endpoint" value=""><input type="hidden" name="p256dh" value=""><input type="hidden" name="auth" value="">`,
|
|
293
|
-
html `<label>Your password <input type="password" name="token" autocomplete="current-password"></label
|
|
293
|
+
html `${reauth(html `<label>Your password <input type="password" name="token" autocomplete="current-password"></label>`)}`,
|
|
294
294
|
html `<button type="submit" id="push-enable">Get alerts on this device</button>`,
|
|
295
295
|
html `<p class="meta" id="push-state"></p>`,
|
|
296
296
|
"",
|
|
@@ -528,6 +528,7 @@ export const SETTINGS_GROUPS = [
|
|
|
528
528
|
]],
|
|
529
529
|
["Access and rules", [
|
|
530
530
|
["/settings/sign-in", "Sign-in", html `<rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>`],
|
|
531
|
+
["/settings/passwordless", "Passwordless", html `<rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 9.9-1"/>`],
|
|
531
532
|
["/settings/sessions", "Sessions & tokens", html `<circle cx="7.5" cy="15.5" r="5.5"/><path d="m21 2-9.6 9.6M15.5 7.5l3 3L22 7l-3-3"/>`],
|
|
532
533
|
["/settings/project", "Project", html `<path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>`],
|
|
533
534
|
["/settings/policy", "Policy", html `<path d="M9 12l2 2 4-4"/><rect x="4" y="3" width="16" height="18" rx="2"/>`],
|