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
package/dist/server/remote.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { credentialProof, freshFacts, proveAccountNow, ssoNowAvailable } from "../scope.js";
|
|
1
2
|
import { handlersOf } from './handler-registry.js';
|
|
2
3
|
/** tasks handlers, moved without changing their route bodies. */
|
|
3
4
|
import {} from "node:http";
|
|
@@ -8,7 +9,6 @@ import { createOAuthHttp, resourceMetadataUrl } from "../mcp-oauth.js";
|
|
|
8
9
|
import { reproveRemote } from "../operate-remote.js";
|
|
9
10
|
import { rowVisible } from "../project.js";
|
|
10
11
|
import { RequestBudget, SourceAdmission } from "../request-budget.js";
|
|
11
|
-
import { authenticateAccount } from "../scope.js";
|
|
12
12
|
import { createSessionRunner } from '../session-remote.js';
|
|
13
13
|
import {} from "../store.js";
|
|
14
14
|
import { handleTeamHttp, TEAM_PASSWORD_REVERIFY_MS } from '../team-http.js';
|
|
@@ -17,7 +17,7 @@ import { flowHook, telegramHook } from "./remote-hooks.js";
|
|
|
17
17
|
import { respond } from "./http.js";
|
|
18
18
|
import {} from "./session.js";
|
|
19
19
|
export function createRemoteHandlers(runtime) {
|
|
20
|
-
const { identify, store, admitPasswordSource, admitBearer, allowedHost, team, teamBrowserReply, requestBudget, options, coding, codingProjects, liveCeiling, clock, evidenceRoot, consoleOrigin, managedRepos, joinSourceOf, ssoSettings,
|
|
20
|
+
const { identify, store, admitPasswordSource, admitBearer, allowedHost, team, teamBrowserReply, requestBudget, options, coding, codingProjects, liveCeiling, clock, evidenceRoot, consoleOrigin, managedRepos, joinSourceOf, ssoSettings, oauthTokenBudget, hookContext, teamsSourceBudget, teamsTenantBudget } = runtime;
|
|
21
21
|
/**
|
|
22
22
|
* The full principal a live `so_` API token stands for: its person and their generation, the token's own read/act
|
|
23
23
|
* scope, its project limit narrowed by the person's current access, and its id. Every route that accepts an API token
|
|
@@ -119,8 +119,9 @@ export function createRemoteHandlers(runtime) {
|
|
|
119
119
|
const who = identify(request, false);
|
|
120
120
|
if (who === null || who.via !== "cookie")
|
|
121
121
|
return null;
|
|
122
|
-
return { name: who.name, role: who.role, csrf: who.session.csrf,
|
|
123
|
-
sso: who.session.sso === undefined ? null : { label: ssoSettings()?.label ?? "your identity provider",
|
|
122
|
+
return { name: who.name, role: who.role, csrf: who.session.csrf, generation: who.session.generation,
|
|
123
|
+
sso: who.session.sso === undefined ? null : { label: ssoSettings()?.label ?? "your identity provider",
|
|
124
|
+
fresh: freshFacts.run(runtime.freshFactsOf(request, who), () => ssoNowAvailable(who.name)) } };
|
|
124
125
|
},
|
|
125
126
|
sameSite: request => {
|
|
126
127
|
const origin = request.headers.origin, referer = request.headers.referer;
|
|
@@ -129,7 +130,15 @@ export function createRemoteHandlers(runtime) {
|
|
|
129
130
|
},
|
|
130
131
|
// The step-up for a credential: the person's password, or (empty) a fresh identity-provider check. What they may
|
|
131
132
|
// grant (their role, their projects) is checked beside it.
|
|
132
|
-
|
|
133
|
+
// Proved on this request's live session at the moment of consent (signed in here still, same generation), through
|
|
134
|
+
// the one account step-up: a password typed now, or an explicit re-check (spent). Never a remembered password.
|
|
135
|
+
confirm: (request, session, typed) => {
|
|
136
|
+
const who = identify(request, false);
|
|
137
|
+
if (who === null || who.via !== "cookie" || who.name !== session.name || who.session.generation !== session.generation)
|
|
138
|
+
return false;
|
|
139
|
+
// Connecting an outside app is a credential act: never a session (Passwordless) or a remembered password.
|
|
140
|
+
return freshFacts.run(runtime.freshFactsOf(request, who), () => { const proved = proveAccountNow(store, who.name, typed, { credential: true }); return proved.ok && credentialProof(proved.method); });
|
|
141
|
+
},
|
|
133
142
|
projectsFor: account => store.knownRepos().filter(repo => store.accountCanAccess(account, repo)).sort(),
|
|
134
143
|
admitToken: request => oauthTokenBudget.admit(joinSourceOf(request)),
|
|
135
144
|
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** The conversation: chat and lead pages, proposal cards and the task chat. */
|
|
2
|
+
import type { TaskOrigin } from "../task-source.js";
|
|
1
3
|
import { type AssignmentSnapshot } from "../assignment.js";
|
|
2
4
|
import { type BrowserActionCard } from "../browser-workspace.js";
|
|
3
5
|
import { type ChatDraft } from "../converse.js";
|
|
@@ -10,6 +12,7 @@ import { type TaskControlView } from "../task-control.js";
|
|
|
10
12
|
import { type TeamChatProviderResolver } from "../team-chat-authorization.js";
|
|
11
13
|
import { type TeamSnapshot } from "../team-contract.js";
|
|
12
14
|
import { type DisplayStatus } from "../workspace-ui.js";
|
|
15
|
+
import type { LeadQueueRow } from "../lead-queue.js";
|
|
13
16
|
import { type Html } from "../html.js";
|
|
14
17
|
import { type Chrome, type Screen } from "./chrome.js";
|
|
15
18
|
import { type ProjectPeek } from "./render-pages.js";
|
|
@@ -77,6 +80,8 @@ export type TaskChatFocus = {
|
|
|
77
80
|
label: string;
|
|
78
81
|
filedAgo: string | null;
|
|
79
82
|
} | null;
|
|
83
|
+
/** v126: where the work came from (an outside event's line of context). */
|
|
84
|
+
origin?: TaskOrigin | null;
|
|
80
85
|
/** The repair chain row when this task is a machine-drafted repair. */
|
|
81
86
|
repairChain: RepairChainRow | null;
|
|
82
87
|
} | null;
|
|
@@ -214,7 +219,7 @@ export declare function chatPage(chrome: Chrome, data: {
|
|
|
214
219
|
/** Chat's first run is on the page: it already says what to do while there is no lead. */
|
|
215
220
|
firstRunShown?: boolean;
|
|
216
221
|
}): Screen;
|
|
217
|
-
export declare function chatAckPage(chrome: Chrome, turn: ChatTurn, nonce: string): Screen;
|
|
222
|
+
export declare function chatAckPage(chrome: Chrome, turn: ChatTurn, nonce: string, reply?: LeadTurn | null): Screen;
|
|
218
223
|
/** The card that starts a conversation: the one password ceremony (mate arc §1). */
|
|
219
224
|
export declare function leadMintCard(enabled: {
|
|
220
225
|
billing: "metered" | "subscription";
|
|
@@ -301,19 +306,23 @@ export type LeadThreadRows = {
|
|
|
301
306
|
focusTask: TaskChatFocus | null;
|
|
302
307
|
/** The lead's open question to this reader, drawn as buttons under its reply. */
|
|
303
308
|
ask?: LeadAsk | null;
|
|
309
|
+
/** The turn whose question the task has moved past: shown without buttons, marked out of date. */
|
|
310
|
+
staleAsk?: number | null;
|
|
304
311
|
/** Every question the lead asked in these messages, by turn: answered ones show their words without buttons. */
|
|
305
312
|
asks?: Map<number, LeadAsk>;
|
|
313
|
+
/** The person's lead queue rows for these messages, by message id (docs/plans/lead-message-queue.md). */
|
|
314
|
+
queue?: Map<number, LeadQueueRow>;
|
|
306
315
|
};
|
|
307
316
|
/** The lead's question as buttons: each option sends itself as the next message (the same POST as a typed one);
|
|
308
317
|
* "Something else" moves to the composer. Nothing shows while a reply is running. */
|
|
309
318
|
export declare function leadAskHtml(ask: LeadAsk, target: {
|
|
310
319
|
task: string | null;
|
|
311
320
|
project: string | null;
|
|
312
|
-
}, composer: string, open?: boolean): Html;
|
|
321
|
+
}, composer: string, open?: boolean, stale?: boolean): Html;
|
|
313
322
|
/** The thread's messages as the React conversation renders them, each
|
|
314
323
|
* with the proposal cards its turn produced; `back` is where a card's
|
|
315
324
|
* confirm or dismiss returns. */
|
|
316
|
-
export declare function leadBrowserMessages(rows: Pick<LeadThreadRows, "messages" | "proposals" | "decisions" | "pending" | "ask" | "asks">, back: string | null, target?: {
|
|
325
|
+
export declare function leadBrowserMessages(rows: Pick<LeadThreadRows, "messages" | "proposals" | "decisions" | "pending" | "ask" | "staleAsk" | "asks" | "queue">, back: string | null, target?: {
|
|
317
326
|
task: string | null;
|
|
318
327
|
project: string | null;
|
|
319
328
|
}): import("../browser-workspace.js").BrowserMessage[];
|
|
@@ -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";
|
|
@@ -145,14 +146,39 @@ export declare function systemPage(chrome: Chrome, data: {
|
|
|
145
146
|
}[];
|
|
146
147
|
now: Date;
|
|
147
148
|
}): Screen;
|
|
149
|
+
/**
|
|
150
|
+
* A build's stage as one of three steps — set up, writing, checking and
|
|
151
|
+
* committing — read from the machine's own phase, never a percent. The
|
|
152
|
+
* steps only move forward: evidence and proof checks run both before and
|
|
153
|
+
* after the commit (builder.ts), so everything after the agent's turn is
|
|
154
|
+
* one step, and the stage words say which part. Null for runs that are not
|
|
155
|
+
* builds (planning, review, investigation): those have no honest steps.
|
|
156
|
+
*/
|
|
157
|
+
export declare const BUILD_STEPS: readonly ["setting up", "writing", "checking and committing"];
|
|
158
|
+
export declare function buildStepOf(claim: NonNullable<BoardCard["claim"]>): number | null;
|
|
159
|
+
/**
|
|
160
|
+
* The workers a board's strip may show: not retired, bound to at least one
|
|
161
|
+
* project the server's ceiling admits (never a runner serving only projects
|
|
162
|
+
* this server must not speak about), and — on a one-project board — bound to
|
|
163
|
+
* that project.
|
|
164
|
+
*/
|
|
165
|
+
export declare function boardWorkers(runners: readonly Runner[], scope: {
|
|
166
|
+
all: boolean;
|
|
167
|
+
project: string | null;
|
|
168
|
+
visible: (repo: string) => boolean;
|
|
169
|
+
}): Runner[];
|
|
148
170
|
/**
|
|
149
171
|
* The board: the pipeline as lanes, position as meaning — attention, then
|
|
150
|
-
* queued, then waiting, then building, then recently done
|
|
151
|
-
* construction (every card is a link,
|
|
152
|
-
* makes it safe to re-render itself
|
|
172
|
+
* queued, then waiting, then building, then recently done — under a strip
|
|
173
|
+
* of the workers doing it. Read-only by construction (every card is a link,
|
|
174
|
+
* no forms, no nonces), which is what makes it safe to re-render itself
|
|
175
|
+
* while somebody watches; a card that changes lane slides there
|
|
176
|
+
* (regionScript's animate swap, keyed by data-vt).
|
|
153
177
|
*/
|
|
154
178
|
export declare function boardBody(data: {
|
|
155
179
|
cards: BoardCard[];
|
|
180
|
+
/** The workers this board's work can go to (retired ones left out). */
|
|
181
|
+
workers: readonly Runner[];
|
|
156
182
|
done: ReturnType<Store["listCompletedWorkScoped"]>;
|
|
157
183
|
saturated: boolean;
|
|
158
184
|
now: Date;
|
|
@@ -360,7 +386,7 @@ export declare function portfolioOverview(data: {
|
|
|
360
386
|
startedAt: string;
|
|
361
387
|
ranMinutes: number | null;
|
|
362
388
|
costUsd: number | null;
|
|
363
|
-
authMode: "subscription" | "api-key" | null;
|
|
389
|
+
authMode: "subscription" | "api-key" | "agent-login" | null;
|
|
364
390
|
prNumber: number | null;
|
|
365
391
|
prUrl: string | null;
|
|
366
392
|
}[];
|
|
@@ -519,6 +545,8 @@ export declare function nextPage(chrome: Chrome, data: {
|
|
|
519
545
|
deliverable?: "branch" | "report";
|
|
520
546
|
/** v48: the agents the yes freezes — the one block every ceremony shows. */
|
|
521
547
|
route: RouteView | null;
|
|
548
|
+
/** v126: where the work came from (an outside event's line of context, never a term agreed to). */
|
|
549
|
+
origin?: TaskOrigin | null;
|
|
522
550
|
csrf: string;
|
|
523
551
|
nonce: string;
|
|
524
552
|
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";
|
|
@@ -206,11 +207,74 @@ export function systemPage(chrome, data) {
|
|
|
206
207
|
: html `<span class="meta">syncing normally</span>`}</p>`), "\n")}`,
|
|
207
208
|
], "\n"), { chrome, refreshSeconds: data.building.length > 0 ? 10 : 60 });
|
|
208
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* A build's stage as one of three steps — set up, writing, checking and
|
|
212
|
+
* committing — read from the machine's own phase, never a percent. The
|
|
213
|
+
* steps only move forward: evidence and proof checks run both before and
|
|
214
|
+
* after the commit (builder.ts), so everything after the agent's turn is
|
|
215
|
+
* one step, and the stage words say which part. Null for runs that are not
|
|
216
|
+
* builds (planning, review, investigation): those have no honest steps.
|
|
217
|
+
*/
|
|
218
|
+
export const BUILD_STEPS = ["setting up", "writing", "checking and committing"];
|
|
219
|
+
export function buildStepOf(claim) {
|
|
220
|
+
if (claim.role === "planner" || claim.role === "reviewer" || claim.role === "scout")
|
|
221
|
+
return null;
|
|
222
|
+
if (claim.model === null)
|
|
223
|
+
return 0;
|
|
224
|
+
if (claim.phase === null || claim.phase === "agent-running")
|
|
225
|
+
return 1;
|
|
226
|
+
return 2;
|
|
227
|
+
}
|
|
228
|
+
/** A clock that ticks in the page (boardTickScript) from the server's own start time; the text is the no-script answer. */
|
|
229
|
+
function tickingClock(startedIso, now) {
|
|
230
|
+
const minutes = Math.max(0, Math.round((now.getTime() - new Date(startedIso).getTime()) / 60_000));
|
|
231
|
+
return html `<time class="clock" datetime="${startedIso}" data-tick>${minutes}m</time>`;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* The workers a board's strip may show: not retired, bound to at least one
|
|
235
|
+
* project the server's ceiling admits (never a runner serving only projects
|
|
236
|
+
* this server must not speak about), and — on a one-project board — bound to
|
|
237
|
+
* that project.
|
|
238
|
+
*/
|
|
239
|
+
export function boardWorkers(runners, scope) {
|
|
240
|
+
return runners.filter(runner => runner.retiredAt === null &&
|
|
241
|
+
runner.repos.some(repo => scope.visible(repo)) &&
|
|
242
|
+
(scope.all || scope.project === null || runner.repos.includes(scope.project)));
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* The worker strip across the top of the board: every worker this board's
|
|
246
|
+
* work can go to, what it is building here, and its stage and clock. Only
|
|
247
|
+
* builds the board already shows are named — a worker busy in a project
|
|
248
|
+
* this reader cannot see reads "no build on this board", never "free".
|
|
249
|
+
*/
|
|
250
|
+
function workerStrip(runners, cards, now) {
|
|
251
|
+
if (runners.length === 0)
|
|
252
|
+
return html `<p class="meta crew-empty">No workers yet — work waits in the queue until one joins.</p>`;
|
|
253
|
+
const tiles = runners.map(runner => {
|
|
254
|
+
const builds = cards
|
|
255
|
+
.filter(card => card.lane === "building" && card.claim !== null && card.claim.runner === runner.name)
|
|
256
|
+
.sort((a, b) => a.claim.claimedAt.localeCompare(b.claim.claimedAt));
|
|
257
|
+
const online = runnerAlive(runner, now);
|
|
258
|
+
const state = !online ? "offline" : builds.length > 0 ? "busy" : "idle";
|
|
259
|
+
const dot = state === "busy" ? html `<span class="dot dot-ok pulse"></span>` : html `<span class="dot dot-off"></span>`;
|
|
260
|
+
const jobs = builds.length > 0
|
|
261
|
+
? builds.map(card => {
|
|
262
|
+
const claim = card.claim;
|
|
263
|
+
const words = claim.model === null ? "preparing workspace" : claim.phase === null ? "the agent is working" : phaseWords(claim.phase);
|
|
264
|
+
return html `<a class="worker-job" href="${card.href}"><span class="t">${card.title}</span><span class="sub"><span>${words}</span>${tickingClock(claim.claimedAt, now)}</span></a>`;
|
|
265
|
+
})
|
|
266
|
+
: html `<p class="worker-job idle">${online ? "no build on this board" : html `offline since ${whenTime(runner.heartbeatAt)}`}</p>`;
|
|
267
|
+
return html `<div class="worker worker-${state}" data-flash="${runner.name}" data-flash-value="${builds.map(card => card.taskId).join(" ")}"><div class="worker-head">${dot}<span class="name">${runner.name}</span><span class="kind">${runner.agents.join(" · ")}</span>${runner.capacity > 1 ? html `<span class="slots">${builds.length}/${runner.capacity}</span>` : null}</div>${jobs}</div>`;
|
|
268
|
+
});
|
|
269
|
+
return html `<section class="crew" aria-label="Workers">${tiles}</section>`;
|
|
270
|
+
}
|
|
209
271
|
/**
|
|
210
272
|
* The board: the pipeline as lanes, position as meaning — attention, then
|
|
211
|
-
* queued, then waiting, then building, then recently done
|
|
212
|
-
* construction (every card is a link,
|
|
213
|
-
* makes it safe to re-render itself
|
|
273
|
+
* queued, then waiting, then building, then recently done — under a strip
|
|
274
|
+
* of the workers doing it. Read-only by construction (every card is a link,
|
|
275
|
+
* no forms, no nonces), which is what makes it safe to re-render itself
|
|
276
|
+
* while somebody watches; a card that changes lane slides there
|
|
277
|
+
* (regionScript's animate swap, keyed by data-vt).
|
|
214
278
|
*/
|
|
215
279
|
export function boardBody(data, ciRed) {
|
|
216
280
|
const chip = (repo) => !data.all || repo === null ? null : html `<span class="badge">${projectName(repo)}</span>`;
|
|
@@ -260,7 +324,7 @@ export function boardBody(data, ciRed) {
|
|
|
260
324
|
const kept = parts.filter((one) => one !== null);
|
|
261
325
|
return kept.length === 0 ? null : html `<span class="chips">${joinHtml(kept, " ")}</span>`;
|
|
262
326
|
};
|
|
263
|
-
const plain = (card) => html `<a class="lane-card" href="${card.href}"><span class="id">${card.taskId}</span><span class="t">${card.title}</span><span class="why">${card.reason}</span>${facts([
|
|
327
|
+
const plain = (card) => html `<a class="lane-card" href="${card.href}" data-vt="${card.taskId}"><span class="id">${card.taskId}</span><span class="t">${card.title}</span><span class="why">${card.reason}</span>${facts([
|
|
264
328
|
...(card.stalledSince === null ? [] : [["waiting", age(card.stalledSince)]]),
|
|
265
329
|
])}${chips([
|
|
266
330
|
chip(card.repo),
|
|
@@ -278,7 +342,7 @@ export function boardBody(data, ciRed) {
|
|
|
278
342
|
}
|
|
279
343
|
return heads;
|
|
280
344
|
})();
|
|
281
|
-
const queuedCard = (card) => html `<a class="lane-card" href="${card.href}"><span class="id">${card.taskId}</span><span class="t">${card.title}</span><span class="why">${card.reason}</span>${facts([
|
|
345
|
+
const queuedCard = (card) => html `<a class="lane-card" href="${card.href}" data-vt="${card.taskId}"><span class="id">${card.taskId}</span><span class="t">${card.title}</span><span class="why">${card.reason}</span>${facts([
|
|
282
346
|
["worker", card.assignedRunner === null ? "any free worker" : card.assignedRunner],
|
|
283
347
|
])}${chips([
|
|
284
348
|
queuedHeads.get(`${card.assignedRunner ?? ""}|${card.repo ?? ""}`) === card.taskId
|
|
@@ -291,14 +355,19 @@ export function boardBody(data, ciRed) {
|
|
|
291
355
|
const claim = card.claim;
|
|
292
356
|
if (claim === null)
|
|
293
357
|
return plain(card);
|
|
294
|
-
const minutes = Math.max(1, Math.round((data.now.getTime() - new Date(claim.claimedAt).getTime()) / 60_000));
|
|
295
358
|
const workspace = claim.worktree === null ? null : (claim.worktree.split("/").pop() ?? claim.worktree);
|
|
296
359
|
// Chip copy: unknown phases show nothing rather than raw tokens.
|
|
297
360
|
const phase = claim.phase === null ? undefined : PHASE_WORDS[claim.phase];
|
|
298
361
|
// The live strip is the run's own phase and clock — never a percent:
|
|
299
362
|
// a build has no honest progress figure, only a stage and an elapsed.
|
|
300
363
|
const live = claim.model === null ? "preparing workspace" : (phase ?? "the agent is working");
|
|
301
|
-
|
|
364
|
+
// The steps: where the build is among set up → writing → checking →
|
|
365
|
+
// committing, from its phase — a stage, never a percent.
|
|
366
|
+
const step = buildStepOf(claim);
|
|
367
|
+
const steps = step === null
|
|
368
|
+
? null
|
|
369
|
+
: html `<span class="steps" role="img" aria-label="${`step ${step + 1} of ${BUILD_STEPS.length}: ${BUILD_STEPS[step]}`}">${BUILD_STEPS.map((_, index) => html `<i class="${index < step ? "on" : index === step ? "now" : ""}"></i>`)}</span>`;
|
|
370
|
+
return html `<a class="lane-card building" href="${card.href}" data-vt="${card.taskId}"><span class="id">${card.taskId}</span><span class="t"><span class="dot dot-ok pulse"></span>${card.title}</span>${steps}<span class="live-line"><span class="stage">${live}</span>${tickingClock(claim.claimedAt, data.now)}</span>${facts([
|
|
302
371
|
["worker", claim.runner],
|
|
303
372
|
...(claim.model === null
|
|
304
373
|
? []
|
|
@@ -315,7 +384,7 @@ export function boardBody(data, ciRed) {
|
|
|
315
384
|
const pr = row.prNumber === null
|
|
316
385
|
? null
|
|
317
386
|
: html `<span class="badge badge-open">PR #${row.prNumber}</span>${ciRed(row.prNumber) ? html ` <span class="badge badge-failed">CI failing</span>` : null}`;
|
|
318
|
-
return html `<a class="lane-card" href="${taskHref(row.taskId)}"><span class="id">${row.taskId}</span><span class="t">${row.title}</span>${row.handoff === null ? null : html `<span class="why">${row.handoff.length > 120 ? row.handoff.slice(0, 120) + "…" : row.handoff}</span>`}${facts([
|
|
387
|
+
return html `<a class="lane-card" href="${taskHref(row.taskId)}" data-vt="${row.taskId}"><span class="id">${row.taskId}</span><span class="t">${row.title}</span>${row.handoff === null ? null : html `<span class="why">${row.handoff.length > 120 ? row.handoff.slice(0, 120) + "…" : row.handoff}</span>`}${facts([
|
|
319
388
|
...(row.ranMinutes === null ? [] : [["ran", `${row.ranMinutes}m`]]),
|
|
320
389
|
["usage", runCostWords({ authMode: row.authMode, costUsd: row.costUsd, tokensIn: null, tokensOut: null })],
|
|
321
390
|
])}${chips([
|
|
@@ -346,7 +415,8 @@ export function boardBody(data, ciRed) {
|
|
|
346
415
|
data.all ? null : html `<p class="meta board-view"><strong>State</strong> · <a href="/board?view=order">order →</a> <span class="meta">drag to reorder, or to reserve a task for one worker</span></p>`,
|
|
347
416
|
deltaLine,
|
|
348
417
|
toggle,
|
|
349
|
-
|
|
418
|
+
workerStrip(data.workers, data.cards, data.now),
|
|
419
|
+
html `<div class="board" data-now="${String(data.now.getTime())}">`,
|
|
350
420
|
lane("attention", "needs you", "these wait for a person", plain),
|
|
351
421
|
lane("queued", "queued", "starts when a worker is free", queuedCard),
|
|
352
422
|
lane("waiting", "waiting", "paused until a time, another task, or a requirement is ready", plain),
|
|
@@ -912,7 +982,7 @@ export function projectsPage(chrome, recent, candidates, open, problem, _unscope
|
|
|
912
982
|
onboard.record[1].large
|
|
913
983
|
? 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
984
|
: null,
|
|
915
|
-
html `<label>Your password, typed again <input type="password" name="token" autocomplete="current-password"></label
|
|
985
|
+
html `${reauth(html `<label>Your password, typed again <input type="password" name="token" autocomplete="current-password"></label>`)}`,
|
|
916
986
|
html `<div class="sticky-actions"><button type="submit">Clone and open</button></div>`,
|
|
917
987
|
], "\n"), { hidden: { nonce: onboard.record[0] } }),
|
|
918
988
|
html `</div>`,
|
|
@@ -1258,11 +1328,11 @@ export function nextPage(chrome, data) {
|
|
|
1258
1328
|
card =
|
|
1259
1329
|
html `<h1>${item.approval.taskId}</h1><p>${item.approval.title}</p>${data.planDocument === null
|
|
1260
1330
|
? 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)}${
|
|
1331
|
+
: 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
1332
|
// The AGENTS the yes freezes (v48): the same concise block the task
|
|
1263
1333
|
// page and chat sign under, before the password — runtime limits one
|
|
1264
1334
|
// tap away, never in the way.
|
|
1265
|
-
agentsCeremonyHtml(data.route)}${scope === null ? null : runtimeDetailsHtml(scope)}
|
|
1335
|
+
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
1336
|
}
|
|
1267
1337
|
else if (item.kind === "requeue") {
|
|
1268
1338
|
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;
|