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
package/dist/server/chat.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { freshFacts, freshlyApprovable, freshnessFor, spendingOnlyOnSuccess, withFreshness } from "../scope.js";
|
|
1
2
|
import { handlersOf } from './handler-registry.js';
|
|
2
3
|
import { DEPRECATED_PAGE } from "../deprecations.js";
|
|
3
4
|
/** chat handlers, moved without changing their route bodies. */
|
|
@@ -34,9 +35,9 @@ import { runActivityOf } from "../task-activity.js";
|
|
|
34
35
|
import { teamWorkspaceHtml } from '../team-ui.js';
|
|
35
36
|
import { loadBotToken, redactToken } from "../telegram.js";
|
|
36
37
|
import { WORK_INDEX_MAX_LIMIT, workIndexPage } from "../work-index.js";
|
|
37
|
-
import { html, htmlString, postForm } from "../html.js";
|
|
38
|
+
import { html, htmlString, postForm, reauth } from "../html.js";
|
|
38
39
|
import { requestContext } from "./request-context.js";
|
|
39
|
-
import { refuse, screen } from "./chrome.js";
|
|
40
|
+
import { refuse, screen, whenTime } from "./chrome.js";
|
|
40
41
|
import { chatResultHref, chatReturnWithLatest, chatReturnWithSaid, projectChatHref, redirect, respond, safeChatReturn, safeReturn, taskChatHref, taskHref } from "./http.js";
|
|
41
42
|
import { chatAckPage, chatPage, coordinatorProposalsSection, decisionsFor, leadAfterComposerHtml, leadChatVersion, leadMintCard, leadPage, leadThreadHtml, PHONE_CARD_FACT, TASK_COMPOSER_MODES, taskChatLiveRegion, teamProposalCardParts } from "./render-chat.js";
|
|
42
43
|
import {} from "./render-pages.js";
|
|
@@ -60,17 +61,18 @@ export function createChatHandlers(runtime) {
|
|
|
60
61
|
}
|
|
61
62
|
store.sweepStaleLeadTurns(now);
|
|
62
63
|
const request = url.searchParams.get("request") ?? "";
|
|
63
|
-
|
|
64
|
+
// Received: the engine took it, or the lead queue holds it (a queued acknowledgement lost on the way still frees the draft).
|
|
65
|
+
const received = /^[a-f0-9]{32}$/.test(request) && store.leadRequestReceived(session.id, request);
|
|
64
66
|
const requestedTask = url.searchParams.get("task");
|
|
65
67
|
const focusTask = requestedTask === null || requestedTask === "" ? null : taskChatFocus(requestedTask, now, who, { mintNonce: false });
|
|
66
68
|
if (requestedTask !== null && requestedTask !== "" && focusTask === null) {
|
|
67
69
|
// The lens's task is gone or no longer admitted: the page keeps its
|
|
68
70
|
// draft and says so; it never falls back to the unified thread.
|
|
69
|
-
return respond(response, 200, "application/json", JSON.stringify({ session: session.id, task: requestedTask, unavailable: true, received
|
|
71
|
+
return respond(response, 200, "application/json", JSON.stringify({ session: session.id, task: requestedTask, unavailable: true, received }));
|
|
70
72
|
}
|
|
71
73
|
const chatProject = focusTask !== null ? null : chatProjectOf(url.searchParams.getAll("project").filter(one => one !== ""));
|
|
72
74
|
if (chatProject === undefined)
|
|
73
|
-
return respond(response, 200, "application/json", JSON.stringify({ session: session.id, unavailable: true, received
|
|
75
|
+
return respond(response, 200, "application/json", JSON.stringify({ session: session.id, unavailable: true, received }));
|
|
74
76
|
const rows = leadConversationRows(who, principal, focusTask, now, chatProject);
|
|
75
77
|
const version = leadChatVersion({ ...rows, focusTask });
|
|
76
78
|
const known = url.searchParams.get("version") ?? "";
|
|
@@ -87,7 +89,7 @@ export function createChatHandlers(runtime) {
|
|
|
87
89
|
task: focusTask?.id ?? "",
|
|
88
90
|
version,
|
|
89
91
|
pending: rows.pending !== null,
|
|
90
|
-
received
|
|
92
|
+
received,
|
|
91
93
|
approval: focusTask?.approval?.digest ?? "",
|
|
92
94
|
...(fragments === null ? {} : { fragments }),
|
|
93
95
|
}));
|
|
@@ -357,7 +359,7 @@ export function createChatHandlers(runtime) {
|
|
|
357
359
|
// The one nonce in chat — this screen restates exact financial terms
|
|
358
360
|
// and re-enables spend, which is precisely what nonces are for.
|
|
359
361
|
const nonce = mintApprovalNonce(who.name, `chat-ack-${turn.id}`, String(turn.reservedMicrousd));
|
|
360
|
-
return sendScreen(response, 200, chatAckPage(chromeFor(project, "chat"), turn, nonce));
|
|
362
|
+
return sendScreen(response, 200, chatAckPage(chromeFor(project, "chat"), turn, nonce, store.leadTurnOfStep(turn.id)));
|
|
361
363
|
}
|
|
362
364
|
async function sharedActionReview(ctx) {
|
|
363
365
|
const { url, response } = ctx;
|
|
@@ -391,7 +393,22 @@ export function createChatHandlers(runtime) {
|
|
|
391
393
|
const review = mintSharedActionReview(store, principal, id, evidenceRoot, clock()), action = review.payload;
|
|
392
394
|
const terms = action.terms.map(term => html `<p style="white-space:pre-wrap;overflow-wrap:anywhere">${term}</p>`);
|
|
393
395
|
const evidenceLink = action.operation === 'result_accept' ? html `<a href="/r/${Number(action.request['run'])}">Inspect this result</a>` : '';
|
|
394
|
-
|
|
396
|
+
// Cockpit B2: approving standard work may stand on a password remembered on this session, exactly as on the task
|
|
397
|
+
// page (the same rule approve() applies when the form comes back); the field then may be left empty.
|
|
398
|
+
const freshSince = (() => {
|
|
399
|
+
const task = typeof action.request['task'] === 'string' ? action.request['task'] : null;
|
|
400
|
+
if (action.operation !== 'scope_approve' || task === null)
|
|
401
|
+
return null;
|
|
402
|
+
const scope = store.getScope(task), fresh = withFreshness('approve-standard', () => freshnessFor(who.name, Date.now(), true));
|
|
403
|
+
if (scope === null || fresh === null)
|
|
404
|
+
return null;
|
|
405
|
+
const gate = store.approvalGate(task, who.name, 'person');
|
|
406
|
+
return freshlyApprovable(store, task, scope, gate.verdict === 'seal' && !gate.protectedWork) ? new Date(fresh.at) : null;
|
|
407
|
+
})();
|
|
408
|
+
const passwordField = !CHAT_ACTIONS[action.operation].password ? '' : reauth(freshSince === null
|
|
409
|
+
? html `<label>Your password<input type="password" name="token" autocomplete="current-password" required></label>`
|
|
410
|
+
: html `<label>Your password<input type="password" name="token" autocomplete="current-password"></label><p class="approval-password-note" data-approval-fresh>Your password from ${whenTime(freshSince.toISOString())} is remembered on this computer — leave this blank to approve with it, or type it again.</p>`);
|
|
411
|
+
const content = html `<section class="shared-action"><h1>${action.title}</h1>${evidenceLink}${terms}${postForm(`/chat/proposal/${id}/confirm`, html `<label class="arm"><input type="checkbox" name="confirm" value="yes" required>I confirm this exact action</label>${passwordField}<button>${CHAT_ACTIONS[action.operation].label}</button>`, { returnTo: '/chat', hidden: { nonce: review.nonce } })}</section>`;
|
|
395
412
|
return sendScreen(response, 200, screen('Review action', content, { chrome: chromeFor(action.repo, 'chat') }));
|
|
396
413
|
}
|
|
397
414
|
catch (error) {
|
|
@@ -665,6 +682,22 @@ export function createChatHandlers(runtime) {
|
|
|
665
682
|
leadSaid.delete(who.session.csrf);
|
|
666
683
|
return redirect(response, back);
|
|
667
684
|
}
|
|
685
|
+
/** Remove a message still waiting in the person's lead queue: theirs only, only while it waits; its words are forgotten. */
|
|
686
|
+
async function leadQueuedRemove(ctx) {
|
|
687
|
+
const { url, who, request, response, now, posted } = ctx;
|
|
688
|
+
const body = readForm(posted, CONSOLE_FORMS.leadQueuedRemove);
|
|
689
|
+
if (who.via !== "cookie")
|
|
690
|
+
return refuse(response, who, 403, "the lead is a browser surface");
|
|
691
|
+
const wantsJson = String(request.headers["accept"] ?? "").includes("application/json");
|
|
692
|
+
const back = safeChatReturn(body.get("return"));
|
|
693
|
+
const outcome = runtime.personalQueue.queue.remove(who.name, Number(url.pathname.split("/")[3]), now);
|
|
694
|
+
const words = outcome === "removed" ? "Removed. It won't be sent." : outcome === "started" ? "Already sent: it started before it could be removed." : "That message isn't waiting.";
|
|
695
|
+
if (wantsJson)
|
|
696
|
+
return respond(response, outcome === "not-found" ? 404 : outcome === "started" ? 409 : 200, "application/json", JSON.stringify({ ok: outcome === "removed", said: words }));
|
|
697
|
+
if (outcome === "not-found")
|
|
698
|
+
return refuse(response, who, 404, words, back);
|
|
699
|
+
return redirect(response, chatReturnWithSaid(back, words));
|
|
700
|
+
}
|
|
668
701
|
async function leadStop(ctx) {
|
|
669
702
|
const { who, response, now, posted } = ctx;
|
|
670
703
|
const body = readForm(posted, CONSOLE_FORMS.leadStop);
|
|
@@ -722,7 +755,12 @@ export function createChatHandlers(runtime) {
|
|
|
722
755
|
noteLead(who.session.csrf, null, "that proposal was already acted on");
|
|
723
756
|
return redirect(response, chatReturnWithLatest(back));
|
|
724
757
|
}
|
|
725
|
-
|
|
758
|
+
// A scope approval's re-check is spent only if the whole confirmation commits (one transaction: the proposal's
|
|
759
|
+
// settlement included). Any other action spends it whatever comes of it (its effects aren't all in the database).
|
|
760
|
+
const confirmIt = () => confirmLeadProposal(store, principal, id, now, { chatProvider: teamChatProvider, confirm: body.get("confirm") === "yes", via: "web", evidenceRoot, ...(body.has("nonce") ? { actionReview: { nonce: body.get("nonce") ?? "", password: body.get("token") ?? "" } } : {}) });
|
|
761
|
+
const proposed = store.getLeadProposal(id);
|
|
762
|
+
const approving = proposed?.kind === "action" && sharedActionPayload(proposed.payload)?.operation === "scope_approve";
|
|
763
|
+
const outcome = approving ? spendingOnlyOnSuccess(confirmIt, result => !result.ok || ("already" in result && result.already === true)) : confirmIt();
|
|
726
764
|
if (cardJson) {
|
|
727
765
|
if (!outcome.ok)
|
|
728
766
|
return cardAnswer(outcome.reason === "standing" ? 403 : outcome.reason === "not-yours" ? 404 : 409, false, outcome.said);
|
|
@@ -839,10 +877,12 @@ export function createChatHandlers(runtime) {
|
|
|
839
877
|
// its own terms and the thread shows why.
|
|
840
878
|
const leadSession = who.role === "approver" ? store.activeLeadSession(who.name) : null;
|
|
841
879
|
if (leadSession !== null) {
|
|
842
|
-
const
|
|
843
|
-
if (
|
|
880
|
+
const sentRequest = body.get("request");
|
|
881
|
+
if (sentRequest !== null && (body.getAll("request").length !== 1 || body.getAll("request-session").length !== 1 || body.get("request-session") !== String(leadSession.id))) {
|
|
844
882
|
return said(409, "This conversation changed. Reload it before sending your message.");
|
|
845
883
|
}
|
|
884
|
+
// Every send has one identity (a form without one gets it here): a message that waits is known by it.
|
|
885
|
+
const requestId = sentRequest ?? randomBytes(16).toString("hex");
|
|
846
886
|
const principal = leadPrincipal(who);
|
|
847
887
|
if (principal === null)
|
|
848
888
|
return wantsJson ? said(403, "your approver standing changed — sign in again") : refuse(response, who, 403, "your approver standing changed — sign in again", back);
|
|
@@ -851,15 +891,50 @@ export function createChatHandlers(runtime) {
|
|
|
851
891
|
return said(400, `a message is 1 to ${LEAD_MESSAGE_MAX_CHARS} characters`, leadSession.id);
|
|
852
892
|
}
|
|
853
893
|
const opened = store.openLeadThread(who.name, principal.ceilingDigest, now, chatScopeOf(focusTask, chatProject));
|
|
854
|
-
|
|
855
|
-
|
|
894
|
+
// The personal queue (docs/plans/lead-message-queue.md): while this person's lead is busy, or something of theirs
|
|
895
|
+
// waits, the message waits its turn instead of being refused. A request the engine already took replays as before.
|
|
896
|
+
const queueContext = focusTask !== null
|
|
897
|
+
? { task: focusTask.id, ...(viewedRun === null ? {} : { result: viewedRun.id }), ...(modeValue === null ? {} : { mode: modeValue }) }
|
|
898
|
+
: chatProject !== null ? { project: chatProject } : null;
|
|
899
|
+
const queueIt = () => runtime.personalQueue.queue.sendOrQueue({ approver: who.name, approverGeneration: principal.generation, session: leadSession.id,
|
|
900
|
+
ceilingDigest: principal.ceilingDigest, thread: opened.thread.id, text: message, requestId, context: queueContext }, clock());
|
|
901
|
+
// A message the engine would refuse for a secret never waits (nothing stored): it goes straight to the engine,
|
|
902
|
+
// which refuses it before any row exists, exactly as when the lead is free.
|
|
903
|
+
if (scanForSecrets(message).length === 0 && store.leadRequestReceipt(leadSession.id, requestId) === null) {
|
|
904
|
+
const decided = queueIt();
|
|
905
|
+
if (decided.kind === "conflict")
|
|
906
|
+
return said(409, "This message was already sent with different words. Reload the conversation to see it.", leadSession.id);
|
|
907
|
+
if (decided.kind === "full")
|
|
908
|
+
return said(409, decided.message, leadSession.id);
|
|
909
|
+
if (decided.kind === "queued") {
|
|
910
|
+
runtime.personalQueue.kick();
|
|
911
|
+
return wantsJson
|
|
912
|
+
? respond(response, 202, "application/json", JSON.stringify({ ok: true, queued: true, session: leadSession.id, task: focusTask?.id ?? "", ...(chatProject === null ? {} : { project: chatProject }), request: requestId, message: decided.row.message, status: decided.row.status }))
|
|
913
|
+
: redirect(response, chatReturnWithLatest(back));
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
// The live display starts once this message holds a turn: a refusal (a secret, a turn still running) never blanks
|
|
917
|
+
// the display of a reply that is still running in the same conversation.
|
|
918
|
+
let onProgress;
|
|
919
|
+
const ended = (ok) => { if (onProgress !== undefined)
|
|
920
|
+
endLiveTurn(opened.thread.id, ok, onProgress); };
|
|
921
|
+
// Detached work never carries this request's browser session (Passwordless reads it): it runs with no session facts.
|
|
922
|
+
void freshFacts.run(null, () => runLeadTurn({ store, who: principal, session: leadSession, thread: opened.thread, config: enabled.config, key: enabled.key, message,
|
|
923
|
+
onAdmitted: () => { onProgress = beginLiveTurn(opened.thread.id); }, onProgress: event => onProgress?.(event), channel: "console", requestId, ...(focusTask === null && chatProject !== null ? { context: `Current project: ${projectName(chatProject)} (${chatProject}). Keep this conversation about that project unless the operator explicitly asks to broaden it; use it as the repo for project tools.` } : {}), ...(focusTask === null ? {} : { context: `Current task: ${focusTask.id}. Read it with get_task before answering or proposing changes. Read its currentExecution next and bind new actions to that exact execution. Never replace the target of a prior proposal with a newer revision. Keep this turn about that task unless the operator explicitly asks to broaden it.${resultContext}${modeContext}` }), fetcher: chatFetcher, ...(options.subscriptionChatRunner === undefined ? {} : { subscriptionRunner: options.subscriptionChatRunner }), clock, evidenceRoot }))
|
|
856
924
|
.then(outcome => {
|
|
925
|
+
// Lost a race to another turn of this person's between deciding and admission: it waits instead of being refused.
|
|
926
|
+
if (!outcome.ok && "refused" in outcome && (outcome.refused === "concurrent" || outcome.refused === "backlog") && queueIt().kind === "queued") {
|
|
927
|
+
ended(false);
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
857
930
|
// A turn that saved its outcome in the thread (one stopped at its deadline) is said there, once.
|
|
858
931
|
if (!outcome.ok && !("saved" in outcome && outcome.saved === true))
|
|
859
932
|
noteLead(who.session.csrf, "turn" in outcome ? outcome.turn : null, outcome.message);
|
|
860
|
-
|
|
933
|
+
ended(outcome.ok);
|
|
861
934
|
})
|
|
862
|
-
.catch(() => { noteLead(who.session.csrf, null, "the turn failed unexpectedly");
|
|
935
|
+
.catch(() => { noteLead(who.session.csrf, null, "the turn failed unexpectedly"); ended(false); })
|
|
936
|
+
// A personal turn ended: the next waiting message (this person's or anyone's) may start now.
|
|
937
|
+
.finally(() => runtime.personalQueue.kick());
|
|
863
938
|
// Accepted for the engine, not received: the receipt is written by
|
|
864
939
|
// the turn's own admission, and the status poll is where the page
|
|
865
940
|
// learns of it — the same road as after a native send.
|
|
@@ -877,10 +952,6 @@ export function createChatHandlers(runtime) {
|
|
|
877
952
|
}
|
|
878
953
|
// The password, typed again, on EVERY message (v2 ruling 2): chat is
|
|
879
954
|
// spend, and a seven-day cookie is not a spend credential.
|
|
880
|
-
const password = body.get("token") ?? "";
|
|
881
|
-
if (password === "" || !authenticateApprover(who, password).ok) {
|
|
882
|
-
return redirect(response, `/chat?said=${encodeURIComponent("chat spends — your password, typed again, with every message")}`);
|
|
883
|
-
}
|
|
884
955
|
const message = (body.get("message") ?? "").trim();
|
|
885
956
|
if (message === "" || message.length > 2_000) {
|
|
886
957
|
return redirect(response, `/chat?said=${encodeURIComponent("a message is 1 to 2000 characters")}`);
|
|
@@ -912,16 +983,28 @@ export function createChatHandlers(runtime) {
|
|
|
912
983
|
if (disallowed !== null)
|
|
913
984
|
return redirect(response, `/chat?said=${encodeURIComponent(disallowed)}`);
|
|
914
985
|
const reserved = worstCaseForPrice(enabled.price, Buffer.byteLength(composed.body, "utf8"));
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
986
|
+
// The password comes last, right where the turn opens: every refusal above leaves an explicit re-check unspent,
|
|
987
|
+
// and a turn that doesn't open (a cap, a budget) puts it back.
|
|
988
|
+
const password = body.get("token") ?? "";
|
|
989
|
+
const opened = spendingOnlyOnSuccess(() => {
|
|
990
|
+
// Typed now, or (cockpit B2) a password or explicit provider re-check given on this session, on a direct local
|
|
991
|
+
// request, within its window. An ordinary provider sign-in never starts that window.
|
|
992
|
+
if (!withFreshness("chat-send", () => authenticateApprover(who, password)).ok)
|
|
993
|
+
return null;
|
|
994
|
+
return store.openChatTurn({
|
|
995
|
+
approver: who.name,
|
|
996
|
+
credentialKey: enabled.credentialKey,
|
|
997
|
+
provider: enabled.config.provider,
|
|
998
|
+
model: enabled.config.model,
|
|
999
|
+
reservedMicrousd: reserved,
|
|
1000
|
+
dailyTurns: enabled.config.dailyTurns,
|
|
1001
|
+
weeklyCeilingMicrousd: enabled.config.weeklyCeilingMicrousd,
|
|
1002
|
+
deadlineMs: TURN_WALL_CLOCK_MS + 10_000,
|
|
1003
|
+
}, now);
|
|
1004
|
+
}, result => result === null || !result.ok);
|
|
1005
|
+
if (opened === null) {
|
|
1006
|
+
return redirect(response, `/chat?said=${encodeURIComponent("chat spends — your password, typed again, with every message")}`);
|
|
1007
|
+
}
|
|
925
1008
|
if (!opened.ok) {
|
|
926
1009
|
const said = opened.reason === "latched"
|
|
927
1010
|
? "a turn with unknown cost blocks this credential — acknowledge it below first"
|
|
@@ -1046,6 +1129,9 @@ export function createChatHandlers(runtime) {
|
|
|
1046
1129
|
const wanted = values[0] ?? "";
|
|
1047
1130
|
return values.length === 1 && visible(wanted) && managedRepos().includes(wanted) ? wanted : undefined;
|
|
1048
1131
|
}
|
|
1132
|
+
/** Which live display each progress handle started: ending a turn ends only its own display (a turn stopped from
|
|
1133
|
+
* another window that settles late can never end the next turn's). */
|
|
1134
|
+
const progressOf = new WeakMap();
|
|
1049
1135
|
function beginLiveTurn(thread) {
|
|
1050
1136
|
const previous = liveTurns.get(thread);
|
|
1051
1137
|
if (previous?.expiry)
|
|
@@ -1055,7 +1141,7 @@ export function createChatHandlers(runtime) {
|
|
|
1055
1141
|
// A page that has this conversation open follows the reply (GET /live, its chat room).
|
|
1056
1142
|
runtime.liveTurnStarted(thread);
|
|
1057
1143
|
const current = () => live.steps.at(-1) ?? (live.steps.push({ tools: [], toolCalls: [], text: "" }), live.steps.at(-1));
|
|
1058
|
-
|
|
1144
|
+
const progress = (event) => {
|
|
1059
1145
|
if (live.done)
|
|
1060
1146
|
return;
|
|
1061
1147
|
if (event.kind === "step")
|
|
@@ -1078,11 +1164,15 @@ export function createChatHandlers(runtime) {
|
|
|
1078
1164
|
for (const listener of live.listeners)
|
|
1079
1165
|
listener();
|
|
1080
1166
|
};
|
|
1167
|
+
progressOf.set(progress, live);
|
|
1168
|
+
return progress;
|
|
1081
1169
|
}
|
|
1082
|
-
function endLiveTurn(thread, ok) {
|
|
1170
|
+
function endLiveTurn(thread, ok, progress) {
|
|
1083
1171
|
const live = liveTurns.get(thread);
|
|
1084
1172
|
if (live === undefined || live.done)
|
|
1085
1173
|
return;
|
|
1174
|
+
if (progress !== undefined && progressOf.get(progress) !== live)
|
|
1175
|
+
return;
|
|
1086
1176
|
live.done = true;
|
|
1087
1177
|
live.ok = ok;
|
|
1088
1178
|
for (const listener of live.listeners)
|
|
@@ -1388,6 +1478,7 @@ export function createChatHandlers(runtime) {
|
|
|
1388
1478
|
"chat.mate-follow": leadFollow,
|
|
1389
1479
|
"chat.mate-end": leadEnd,
|
|
1390
1480
|
"chat.mate-stop": leadStop,
|
|
1481
|
+
"chat.queued-remove": leadQueuedRemove,
|
|
1391
1482
|
"chat.proposal.confirm": chatProposalAct("confirm"),
|
|
1392
1483
|
"chat.proposal.dismiss": chatProposalAct("dismiss"),
|
|
1393
1484
|
"coordinator.proposal.confirm": coordinatorProposalAct("confirm"),
|
package/dist/server/chrome.d.ts
CHANGED
|
@@ -134,7 +134,8 @@ export declare function regionScript(regionId: string, fragmentName: string, pat
|
|
|
134
134
|
* protocol, not an HTML fragment — the response is raw sanitized text and
|
|
135
135
|
* the ONLY sink is textContent, so nothing here can become markup. One
|
|
136
136
|
* request in flight, visibility-paused, stops on `final`, and a `replaced`
|
|
137
|
-
* answer restarts from zero with a visible
|
|
137
|
+
* answer — or another file's generation — restarts from zero with a visible
|
|
138
|
+
* line, never a silent re-read.
|
|
138
139
|
*/
|
|
139
140
|
export declare function transcriptScript(path?: string, elementId?: string): string;
|
|
140
141
|
/**
|
|
@@ -170,6 +171,10 @@ export type Screen = {
|
|
|
170
171
|
/** Render sensitive even when no password field is visible — one-time
|
|
171
172
|
* secrets and judgment calls the classifier cannot see. */
|
|
172
173
|
forceSensitive?: boolean;
|
|
174
|
+
/** The page's sensitivity, said explicitly (cockpit §2, R14): true for a page that asks for a password or shows a
|
|
175
|
+
* secret even when no password field renders (an SSO step-up, a fresh-password grant); false only where a page is
|
|
176
|
+
* known safe. Unset: judged from the page as authored. A sensitive page has no sidebar states and no summary room. */
|
|
177
|
+
sensitive?: boolean;
|
|
173
178
|
/** Structured conversation; complex guarded forms stay native islands. */
|
|
174
179
|
workspace?: Partial<Pick<BrowserWorkspace, 'conversation' | 'team' | 'focus' | 'result' | 'notices' | 'view' | 'firstRun' | 'phone' | 'home'>> & {
|
|
175
180
|
/** Markup the workspace shows in place; it becomes bytes where the page's JSON is written (sendScreen). */
|
package/dist/server/chrome.js
CHANGED
|
@@ -223,9 +223,17 @@ export function regionScript(regionId, fragmentName, path) {
|
|
|
223
223
|
// A fragment that marks itself final stops: a finished or abandoned build is not fetched again.
|
|
224
224
|
`.then(function(){busy=false;tell();if(region.querySelector("[data-region-stop]")){stopped=true;if(stamp)stamp.textContent="";return;}if(again)refresh();});}` +
|
|
225
225
|
`function heard(name){if(name==="change"||name==="reload")refresh();}` +
|
|
226
|
-
|
|
227
|
-
`
|
|
226
|
+
// On a workspace page the page's one stream (browser/live.ts) carries the workspace room: ask it, never open a second.
|
|
227
|
+
`function mine(e){var d=e.detail;if(d&&(d.room==="workspace"||d.room===null))heard(d.event);}` +
|
|
228
|
+
`if(document.getElementById("standing-orders-workspace")){(window.__soLiveRooms=window.__soLiveRooms||[]).push("workspace");addEventListener("so:live",mine);}` +
|
|
229
|
+
`else if(document.documentElement.dataset.live==="1"){addEventListener("so:live",mine);}` +
|
|
230
|
+
// Refused (signed out, access lost): stop instead of reconnecting at once into a request that can't sign in, and
|
|
231
|
+
// listen again in 30 s, as the page's own stream does (browser/live.ts RETRY_MS).
|
|
232
|
+
`else if(typeof EventSource!=="undefined"){var listen=function(){var es=new EventSource("/live?room=workspace");` +
|
|
228
233
|
`es.addEventListener("change",function(){heard("change");});es.addEventListener("reload",function(){heard("reload");});` +
|
|
234
|
+
// A retry the server answers with an error (still signed out) is given up by the browser: try again later too.
|
|
235
|
+
`var again=function(){es.close();setTimeout(listen,30000);};` +
|
|
236
|
+
`es.addEventListener("ended",again);es.addEventListener("error",function(){if(es.readyState===2)again();});};listen();` +
|
|
229
237
|
`document.addEventListener("visibilitychange",function(){if(!document.hidden)refresh();});}` +
|
|
230
238
|
`})();`);
|
|
231
239
|
}
|
|
@@ -234,23 +242,24 @@ export function regionScript(regionId, fragmentName, path) {
|
|
|
234
242
|
* protocol, not an HTML fragment — the response is raw sanitized text and
|
|
235
243
|
* the ONLY sink is textContent, so nothing here can become markup. One
|
|
236
244
|
* request in flight, visibility-paused, stops on `final`, and a `replaced`
|
|
237
|
-
* answer restarts from zero with a visible
|
|
245
|
+
* answer — or another file's generation — restarts from zero with a visible
|
|
246
|
+
* line, never a silent re-read.
|
|
238
247
|
*/
|
|
239
248
|
export function transcriptScript(path, elementId = "live-transcript") {
|
|
240
249
|
const base = path === undefined ? "location.pathname" : JSON.stringify(path);
|
|
241
250
|
return (`(function(){var out=document.getElementById(${JSON.stringify(elementId)});if(!out)return;` +
|
|
242
|
-
`var from=0,busy=false,stopped=false;` +
|
|
251
|
+
`var from=0,gen="",inkey=false,ktail="",busy=false,stopped=false;` +
|
|
243
252
|
`function near(){return out.scrollHeight-out.scrollTop-out.clientHeight<40;}` +
|
|
244
253
|
`function go(){if(stopped)return;if(busy||document.hidden){later();return;}busy=true;` +
|
|
245
|
-
`fetch(${base}+"?fragment=transcript&from="+from,{redirect:"manual",cache:"no-store"})` +
|
|
254
|
+
`fetch(${base}+"?fragment=transcript&from="+from+(gen?"&gen="+encodeURIComponent(gen):"")+(inkey?"&inkey=1":"")+(ktail?"&ktail="+encodeURIComponent(ktail):""),{redirect:"manual",cache:"no-store"})` +
|
|
246
255
|
`.then(function(r){if(r.type==="opaqueredirect"||r.status===401||r.status===403){stopped=true;return null;}` +
|
|
247
256
|
`if(r.status===409)return{error:"replaced"};return r.ok?r.json():null;})` +
|
|
248
257
|
`.then(function(d){busy=false;if(d===null){later();return;}if(stopped)return;` +
|
|
249
|
-
`if(d.error==="replaced"){from=0;out.textContent="[the view restarted]\\n";later();return;}` +
|
|
258
|
+
`if(d.error==="replaced"){from=0;gen="";inkey=false;ktail="";out.textContent="[the view restarted]\\n";later();return;}` +
|
|
250
259
|
`if(d.error){stopped=true;return;}` +
|
|
251
260
|
`var stick=near();` +
|
|
252
261
|
`if(typeof d.text==="string"&&d.text!==""){out.appendChild(document.createTextNode(d.text));if(stick)out.scrollTop=out.scrollHeight;}` +
|
|
253
|
-
`if(typeof d.nextOffset==="number"&&d.nextOffset>=from){from=d.nextOffset;}
|
|
262
|
+
`if(typeof d.nextOffset==="number"&&d.nextOffset>=from){from=d.nextOffset;}if(typeof d.gen==="string")gen=d.gen;if(typeof d.inKey==="boolean")inkey=d.inKey;if(typeof d.keyTail==="string")ktail=d.keyTail;` +
|
|
254
263
|
`if(d.final===true){stopped=true;var m=document.getElementById(${JSON.stringify(`${elementId}-state`)});` +
|
|
255
264
|
`if(m)m.textContent="the agent finished — the record on this page is the story";return;}` +
|
|
256
265
|
`later();})` +
|
|
@@ -680,6 +689,8 @@ export const NAV_ICONS = {
|
|
|
680
689
|
export function workToolRows(scoped = false, _offersChat = false, offersCode = false) {
|
|
681
690
|
const rows = [
|
|
682
691
|
// D5: coding sessions are deprecated; /code still opens this release, but nothing links to it.
|
|
692
|
+
// Live terminals: an operator's own Claude Code, Codex or shell, in a real terminal (docs/plans/live-terminal.md).
|
|
693
|
+
...(offersCode ? [{ key: "code", href: "/term", label: "Live", hint: "your own terminals: Claude Code, Codex or a shell" }] : []),
|
|
683
694
|
{ key: "inbox", href: "/inbox", label: "Inbox", hint: "questions, approvals, and repairs to act on" },
|
|
684
695
|
{ key: "board", href: "/board", label: "Board", hint: "lanes by state, with the order view" },
|
|
685
696
|
{ key: "tasks", href: "/tasks", label: "Task list", hint: "everything, filterable by state" },
|
package/dist/server/console.css
CHANGED
|
@@ -182,7 +182,7 @@
|
|
|
182
182
|
the act that resolves the screen. Cards, frames, and seals are neutral. */
|
|
183
183
|
.badge-open, .badge-parked { color: var(--foreground); }
|
|
184
184
|
.count {
|
|
185
|
-
min-width: 1.25rem; padding-inline: .35rem; border-radius:
|
|
185
|
+
min-width: 1.25rem; padding-inline: .35rem; border-radius: var(--so-radius-full);
|
|
186
186
|
}
|
|
187
187
|
.count.badge-open {
|
|
188
188
|
background: var(--brand-soft); color: var(--brand);
|
|
@@ -627,7 +627,7 @@
|
|
|
627
627
|
.requirement-heading .badge { margin: 0; white-space: normal; }
|
|
628
628
|
.requirement-statement { margin: .5rem 0; line-height: 1.55; white-space: pre-wrap; }
|
|
629
629
|
.requirement-review { margin: .4rem 0; font-size: .78rem; color: var(--muted-foreground); }
|
|
630
|
-
.requirement-warning { margin: .65rem 0; padding: .6rem .75rem; border: 1px solid color-mix(in srgb, var(--warning) 35%, var(--border)); border-radius:
|
|
630
|
+
.requirement-warning { margin: .65rem 0; padding: .6rem .75rem; border: 1px solid color-mix(in srgb, var(--warning) 35%, var(--border)); border-radius: var(--so-radius-md); background: var(--warning-soft); font-size: .8125rem; }
|
|
631
631
|
.requirement-warning strong { font-size: .78rem; }
|
|
632
632
|
.requirement-warning p { margin: .3rem 0 0; }
|
|
633
633
|
.requirement-issues { font-size: .8125rem; }
|
|
@@ -663,7 +663,7 @@
|
|
|
663
663
|
|
|
664
664
|
.filters { font-size: 0.8125rem; color: var(--muted-foreground); }
|
|
665
665
|
.filters a, .filters strong {
|
|
666
|
-
display: inline-block; padding: .25rem .625rem; border-radius:
|
|
666
|
+
display: inline-block; padding: .25rem .625rem; border-radius: var(--so-radius-full); text-decoration: none;
|
|
667
667
|
color: var(--muted-foreground); font-weight: 500;
|
|
668
668
|
}
|
|
669
669
|
.filters strong { background: var(--muted); color: var(--foreground); }
|
|
@@ -884,13 +884,18 @@
|
|
|
884
884
|
}
|
|
885
885
|
.banner .badge { margin-right: .5rem; }
|
|
886
886
|
/* Chat in `toolroll demo`: the scripted lead's conversation. */
|
|
887
|
-
.demo-chat { display: flex; flex-direction: column; gap: 1rem; max-width: 46rem; margin: 0 auto;
|
|
887
|
+
.demo-chat { display: flex; flex-direction: column; gap: 1rem; max-width: 46rem; min-height: 100%; margin: 0 auto; }
|
|
888
|
+
.demo-thread { flex: 1 0 auto; }
|
|
889
|
+
/* Empty: the greeting, the suggestions and the composer sit together in the middle, as in any modern chat. */
|
|
890
|
+
.demo-chat--empty { justify-content: center; min-height: min(70vh, 40rem); }
|
|
891
|
+
.demo-chat--empty .demo-thread { flex: 0 0 auto; }
|
|
888
892
|
.demo-thread { display: flex; flex-direction: column; gap: 1.25rem; }
|
|
889
893
|
.demo-turn { display: flex; flex-direction: column; gap: .75rem; scroll-margin-top: 1rem; }
|
|
890
894
|
.demo-said p { margin: 0; }
|
|
891
|
-
.demo-you { align-self: flex-end; max-width: min(34rem, 85%); background: var(--so-
|
|
895
|
+
.demo-you { align-self: flex-end; max-width: min(34rem, 85%); background: var(--so-soft); color: var(--so-ink); border: 1px solid var(--so-line); border-radius: 1rem 1rem .3rem 1rem; padding: .5rem .85rem; overflow-wrap: anywhere; }
|
|
892
896
|
.demo-lead { max-width: 40rem; }
|
|
893
|
-
.demo-who { font-size: .
|
|
897
|
+
.demo-who { display: flex; align-items: center; gap: .45rem; font-size: .8125rem; font-weight: 600; color: var(--so-ink); margin-bottom: .3rem !important; }
|
|
898
|
+
.demo-avatar { display: inline-grid; place-items: center; width: 1.25rem; height: 1.25rem; border-radius: 50%; background: var(--so-ink); color: var(--so-paper); font-size: .6875rem; font-weight: 700; }
|
|
894
899
|
.demo-chat .card { margin: 0; padding: 1rem 1.125rem; }
|
|
895
900
|
.demo-chat .card h2 { font-size: .9375rem; margin: .1rem 0 .4rem; }
|
|
896
901
|
.demo-chat .card h3 { font-size: .75rem; font-weight: 600; color: var(--so-muted); margin: .75rem 0 .25rem; }
|
|
@@ -938,13 +943,22 @@
|
|
|
938
943
|
.demo-evidence figure { margin: 0 0 .5rem; }
|
|
939
944
|
.demo-evidence img { display: block; width: 100%; height: auto; border: 1px solid var(--so-line); border-radius: .5rem; }
|
|
940
945
|
.demo-evidence figcaption { margin-top: .35rem; }
|
|
941
|
-
.demo-hint { padding:
|
|
942
|
-
.demo-hint-title { font-size: .
|
|
943
|
-
.demo-
|
|
944
|
-
.demo-suggestions
|
|
945
|
-
.demo-
|
|
946
|
-
.demo-chat .demo-
|
|
947
|
-
|
|
946
|
+
.demo-hint { text-align: center; padding: 0 0 .5rem; }
|
|
947
|
+
.demo-hint-title { font-size: 1.375rem; font-weight: 600; letter-spacing: -.01em; margin: 0 0 .35rem; }
|
|
948
|
+
.demo-hint-sub { color: var(--so-muted); font-size: .875rem; margin: 0 0 1rem; }
|
|
949
|
+
.demo-suggestions { display: flex; flex-wrap: wrap; justify-content: center; gap: .5rem; margin: 0; padding: 0; border: 0; background: none; }
|
|
950
|
+
.demo-chat .demo-suggestions button { min-height: 2.25rem; border-radius: 999px; padding: .3rem .9rem; border: 1px solid var(--so-line); background: var(--so-paper); color: var(--so-muted); font-weight: 500; box-shadow: none; }
|
|
951
|
+
@media (hover: hover) and (pointer: fine) { .demo-chat .demo-suggestions button:hover { color: var(--so-ink); border-color: var(--so-input-line); background: var(--so-soft); } }
|
|
952
|
+
/* The composer: one card docked at the bottom, a borderless box that grows, the hint and a round send inside. */
|
|
953
|
+
.demo-composer { position: sticky; bottom: 0; margin: 0; padding: .75rem 0 1rem; border: 0; border-radius: 0; box-shadow: none; background: linear-gradient(to bottom, transparent, var(--so-paper) 1.25rem); }
|
|
954
|
+
.demo-composer-card { display: flex; flex-direction: column; gap: .35rem; padding: .7rem .7rem .55rem .9rem; border: 1px solid var(--so-input-line); border-radius: 1rem; background: var(--so-paper); box-shadow: 0 1px 2px rgba(0,0,0,.04), 0 4px 16px rgba(0,0,0,.04); transition: border-color .12s var(--so-ease-out), box-shadow .12s var(--so-ease-out); }
|
|
955
|
+
.demo-composer-card:focus-within { border-color: var(--so-muted); box-shadow: 0 0 0 3px var(--so-soft); }
|
|
956
|
+
.demo-chat .demo-composer textarea { width: 100%; min-height: 1.6rem; max-height: 12rem; field-sizing: content; padding: .15rem 0; border: 0; border-radius: 0; background: none; box-shadow: none; resize: none; font-size: .9375rem; line-height: 1.55; }
|
|
957
|
+
.demo-chat .demo-composer textarea:focus-visible { outline: 0; }
|
|
958
|
+
.demo-composer-bar { display: flex; align-items: center; justify-content: space-between; gap: .75rem; }
|
|
959
|
+
.demo-composer-bar > span { color: var(--so-muted); font-size: .75rem; }
|
|
960
|
+
.demo-chat .demo-composer .demo-send { display: inline-grid; place-items: center; flex: none; width: 2rem; min-height: 2rem; height: 2rem; padding: 0; border-radius: 50%; }
|
|
961
|
+
.demo-composer-card:has(textarea:placeholder-shown) .demo-send { opacity: .35; }
|
|
948
962
|
.banner a { color: var(--muted-foreground); }
|
|
949
963
|
.sign-in-banner form { display: inline; margin: 0 0 0 .5rem; padding: 0; border: 0; background: transparent; }
|
|
950
964
|
|
|
@@ -1190,26 +1204,26 @@
|
|
|
1190
1204
|
rows, who builds in one line, one Approve & start beside the password,
|
|
1191
1205
|
and every other signed term in one Details fold at the end. Sentence
|
|
1192
1206
|
case, Geist, 12px and up; prose capped at 75ch. */
|
|
1193
|
-
.approval-sheet { display: grid; gap:
|
|
1207
|
+
.approval-sheet { display: grid; gap: var(--so-space-lg); margin: 0; padding: var(--so-space-xl); border: 1px solid var(--border); border-radius: var(--so-radius-card); background: var(--card); }
|
|
1194
1208
|
.approval-sheet.approve-form > :not(input) { margin: 0; }
|
|
1195
1209
|
.approval-sheet-title { margin: 0; font-size: 15px; font-weight: 600; line-height: 1.4; letter-spacing: -.01em; }
|
|
1196
1210
|
.approval-revision, .approval-note { max-width: 75ch; font-size: 14px; line-height: 1.6; overflow-wrap: anywhere; }
|
|
1197
1211
|
.approval-note { color: var(--muted-foreground); }
|
|
1198
1212
|
.approval-rows { display: grid; gap: 14px; margin: 0; }
|
|
1199
|
-
.approval-row { display: grid; grid-template-columns: 7.5rem minmax(0, 1fr); gap:
|
|
1213
|
+
.approval-row { display: grid; grid-template-columns: 7.5rem minmax(0, 1fr); gap: var(--so-space-lg); }
|
|
1200
1214
|
.approval-row dt { margin: 0; color: var(--muted-foreground); font-size: 13px; font-weight: 500; line-height: 1.6; }
|
|
1201
1215
|
.approval-row dd { min-width: 0; max-width: 75ch; margin: 0; font-size: 14px; line-height: 1.6; overflow-wrap: anywhere; }
|
|
1202
1216
|
.approval-row dd p, .approval-sheet .approval-goal { margin: 0; font-size: 14px; line-height: 1.6; }
|
|
1203
|
-
.approval-row dd :is(ol, ul) { display: grid; gap:
|
|
1217
|
+
.approval-row dd :is(ol, ul) { display: grid; gap: var(--so-space-xs); margin: 0; padding-left: 1.25rem; }
|
|
1204
1218
|
.approval-row dd ol + p { margin-top: 6px; }
|
|
1205
1219
|
.approval-row dd .approval-paths { margin-top: 2px; padding-left: 0; }
|
|
1206
1220
|
.approval-row dd .approval-paths li { font-size: 13px; }
|
|
1207
|
-
.approval-amendment { max-width: 75ch; padding:
|
|
1221
|
+
.approval-amendment { max-width: 75ch; padding: var(--so-space-md) 14px; border-radius: var(--so-radius-md); background: var(--muted); font-size: 14px; line-height: 1.55; overflow-wrap: anywhere; }
|
|
1208
1222
|
.approval-amendment p { margin: 0; }
|
|
1209
|
-
.approval-amendment ul { display: grid; gap:
|
|
1223
|
+
.approval-amendment ul { display: grid; gap: var(--so-space-xs); margin: 6px 0; padding-left: 1.25rem; }
|
|
1210
1224
|
.agents-size { margin: 0; font-size: 13px; line-height: 1.5; }
|
|
1211
1225
|
.approval-who { max-width: 75ch; color: var(--muted-foreground); font-size: 13px; line-height: 1.5; }
|
|
1212
|
-
.approval-act { display: grid; grid-template-columns: minmax(0, 20rem) auto; justify-content: start; align-items: center; gap:
|
|
1226
|
+
.approval-act { display: grid; grid-template-columns: minmax(0, 20rem) auto; justify-content: start; align-items: center; gap: var(--so-space-sm); }
|
|
1213
1227
|
.approval-password { margin: 0; }
|
|
1214
1228
|
.approval-act input[type=password] { margin: 0; font-family: var(--font-sans); }
|
|
1215
1229
|
.approval-act button { margin: 0; min-height: 2.125rem; white-space: nowrap; }
|
|
@@ -1218,7 +1232,7 @@
|
|
|
1218
1232
|
.approval-sheet .approval-allowing + .approval-you-check { margin-top: -10px; }
|
|
1219
1233
|
.approval-act > button { grid-column: 2; grid-row: 1; }
|
|
1220
1234
|
.approval-password-note { grid-column: 1; grid-row: 2; margin: 0; color: var(--muted-foreground); font-size: 13px; line-height: 1.5; }
|
|
1221
|
-
.approval-secondary { display: flex; flex-wrap: wrap; align-items: center; gap:
|
|
1235
|
+
.approval-secondary { display: flex; flex-wrap: wrap; align-items: center; gap: var(--so-space-sm); }
|
|
1222
1236
|
details.approval-edit, details.approval-edit[open] { margin: 0; padding: 0; border: 0; border-radius: 0; background: none; }
|
|
1223
1237
|
/* Edit plan, in place: the rows become fields where they stood; the
|
|
1224
1238
|
approval act and Details step aside until it is saved or cancelled. */
|
|
@@ -1233,32 +1247,32 @@
|
|
|
1233
1247
|
.approval-secondary:has(> .approval-edit[open]) > a { display: none; }
|
|
1234
1248
|
.approval-editor { display: grid; gap: 14px; }
|
|
1235
1249
|
.approval-editor .problem { margin: 0; }
|
|
1236
|
-
.approval-field { display: grid; gap:
|
|
1250
|
+
.approval-field { display: grid; gap: var(--so-space-xs); min-width: 0; margin: 0; padding: 0; border: 0; }
|
|
1237
1251
|
.approval-field-label { padding: 0; color: var(--muted-foreground); font-size: 13px; font-weight: 500; line-height: 1.6; }
|
|
1238
1252
|
.approval-field-hint { color: var(--muted-foreground); font-size: 12px; line-height: 1.5; }
|
|
1239
1253
|
.approval-field textarea, .approval-field input[type=text] { width: 100%; max-width: 75ch; margin: 0; font-family: var(--font-sans); font-size: 14px; line-height: 1.55; }
|
|
1240
1254
|
.approval-requirements { display: grid; gap: 6px; margin: 2px 0 0; padding: 0; list-style: none; }
|
|
1241
|
-
.approval-edit-act { display: flex; flex-wrap: wrap; align-items: center; gap:
|
|
1255
|
+
.approval-edit-act { display: flex; flex-wrap: wrap; align-items: center; gap: var(--so-space-sm); }
|
|
1242
1256
|
.approval-edit-act button { margin: 0; min-height: 2.125rem; white-space: nowrap; }
|
|
1243
|
-
.approval-link { display: inline-flex; align-items: center; min-height: 32px; padding: 0
|
|
1257
|
+
.approval-link { display: inline-flex; align-items: center; min-height: 32px; padding: 0 var(--so-space-md); border: 1px solid var(--border); border-radius: var(--so-radius-md); background: var(--card); color: var(--foreground); font-size: 13px; font-weight: 500; text-decoration: none; }
|
|
1244
1258
|
.approval-secondary > .approval-link:not(:first-child) { border-color: transparent; background: none; color: var(--muted-foreground); }
|
|
1245
1259
|
@media (hover: hover) and (pointer: fine) { .approval-link:hover { background: var(--muted); color: var(--foreground); } }
|
|
1246
1260
|
details.approval-details { margin: 0; padding: 0; border: 0; border-top: 1px solid var(--border); border-radius: 0; background: none; }
|
|
1247
1261
|
details.approval-details[open] { padding-bottom: 0; }
|
|
1248
|
-
.approval-details > summary { display: flex; align-items: center; min-height: 40px; padding:
|
|
1249
|
-
.approval-detail { max-width: 75ch; margin:
|
|
1250
|
-
.approval-detail h3 { margin: 0 0
|
|
1251
|
-
.approval-detail p, .approval-detail ul, .approval-detail dl { margin:
|
|
1262
|
+
.approval-details > summary { display: flex; align-items: center; min-height: 40px; padding: var(--so-space-xs) 0 0; font-size: 13px; }
|
|
1263
|
+
.approval-detail { max-width: 75ch; margin: var(--so-space-md) 0 0; font-size: 13px; line-height: 1.55; overflow-wrap: anywhere; }
|
|
1264
|
+
.approval-detail h3 { margin: 0 0 var(--so-space-xs); font-size: 13px; font-weight: 600; letter-spacing: 0; text-transform: none; }
|
|
1265
|
+
.approval-detail p, .approval-detail ul, .approval-detail dl { margin: var(--so-space-xs) 0; }
|
|
1252
1266
|
.approval-detail ul { padding-left: 1.25rem; }
|
|
1253
1267
|
.approval-detail .revision-card { margin: 0; padding: 0; border: 0; background: none; }
|
|
1254
|
-
.approval-roles { display: grid; gap:
|
|
1255
|
-
.approval-roles > div { display: grid; grid-template-columns: 6rem minmax(0, 1fr); gap:
|
|
1268
|
+
.approval-roles { display: grid; gap: var(--so-space-sm); }
|
|
1269
|
+
.approval-roles > div { display: grid; grid-template-columns: 6rem minmax(0, 1fr); gap: var(--so-space-md); }
|
|
1256
1270
|
.approval-roles dt { color: var(--muted-foreground); font-weight: 500; }
|
|
1257
1271
|
.approval-roles dd { margin: 0; }
|
|
1258
1272
|
.approval-roles dd ul { margin: 2px 0 0; color: var(--muted-foreground); }
|
|
1259
1273
|
.chat-plan .approval-sheet { padding: 0; border: 0; background: none; }
|
|
1260
1274
|
@media (max-width: 760px) {
|
|
1261
|
-
.approval-sheet { gap:
|
|
1275
|
+
.approval-sheet { gap: var(--so-space-md); padding: 14px; }
|
|
1262
1276
|
.approval-row { grid-template-columns: 1fr; gap: 2px; }
|
|
1263
1277
|
.approval-act { grid-template-columns: 1fr; }
|
|
1264
1278
|
.approval-act > button, .approval-password-note { grid-column: auto; grid-row: auto; }
|
|
@@ -1269,7 +1283,7 @@
|
|
|
1269
1283
|
position: sticky; bottom: 0; z-index: 20; margin: 0 -14px; padding: 10px 14px calc(10px + env(safe-area-inset-bottom, 0px));
|
|
1270
1284
|
border-top: 1px solid var(--border); background: var(--card);
|
|
1271
1285
|
}
|
|
1272
|
-
.approval-link { min-height: 44px; padding: 0
|
|
1286
|
+
.approval-link { min-height: 44px; padding: 0 var(--so-space-lg); }
|
|
1273
1287
|
.approval-edit[open] > summary { top: 6px; right: 6px; }
|
|
1274
1288
|
.approval-field textarea, .approval-field input[type=text] { font-size: 16px; }
|
|
1275
1289
|
.approval-edit-act button { width: 100%; min-height: 44px; font-size: 16px; }
|
|
@@ -1719,7 +1733,7 @@
|
|
|
1719
1733
|
.tabbar a { position: relative; }
|
|
1720
1734
|
.tabbar a .dot-badge {
|
|
1721
1735
|
position: absolute; top: .3125rem; left: calc(50% + .375rem);
|
|
1722
|
-
width: .375rem; height: .375rem; border-radius:
|
|
1736
|
+
width: .375rem; height: .375rem; border-radius: var(--so-radius-full); background: var(--brand);
|
|
1723
1737
|
}
|
|
1724
1738
|
.content > main { padding: 1rem 1rem calc(4.5rem + env(safe-area-inset-bottom, 0rem)); }
|
|
1725
1739
|
.project-add-card { padding: 1rem; }
|
|
@@ -1752,7 +1766,7 @@
|
|
|
1752
1766
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
1753
1767
|
.stat-card .v { font-size: .75rem; color: var(--muted-foreground); margin-top: .25rem;
|
|
1754
1768
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
1755
|
-
.dot { display: inline-block; width: .5rem; height: .5rem; border-radius:
|
|
1769
|
+
.dot { display: inline-block; width: .5rem; height: .5rem; border-radius: var(--so-radius-full); flex: none; }
|
|
1756
1770
|
.dot-ok { background: var(--success); }
|
|
1757
1771
|
/* Caution without a claim on the operator: quiet, not magenta. */
|
|
1758
1772
|
.dot-warn { background: var(--muted-foreground); }
|
|
@@ -1787,13 +1801,13 @@
|
|
|
1787
1801
|
.sso-step-up { display: inline-flex; align-items: center; min-height: 36px; font-size: .9rem; }
|
|
1788
1802
|
.sso-step-up[data-sso-step-up="confirmed"] { color: var(--so-success); }
|
|
1789
1803
|
.sso-step-up[data-sso-step-up="confirmed"]::before { content: "✓"; margin-right: 6px; }
|
|
1790
|
-
.login-shell .login-sso { display: flex; justify-content: center; align-items: center; min-height: 44px; border-radius:
|
|
1804
|
+
.login-shell .login-sso { display: flex; justify-content: center; align-items: center; min-height: 44px; border-radius: var(--so-radius-md); background: var(--so-accent); color: var(--so-on-accent); font-weight: 600; text-decoration: none; margin: 0 0 1rem; }
|
|
1791
1805
|
@media (hover: hover) and (pointer: fine) { .login-shell .login-sso:hover { background: var(--so-accent-hover); } }
|
|
1792
1806
|
.login-shell .login-password > summary { text-align: center; font-size: .875rem; color: var(--muted-foreground); cursor: pointer; margin: 0 0 .75rem; }
|
|
1793
1807
|
.focus-page { max-width: 40rem; margin: 0 auto; padding: 2rem 1.25rem 3rem; }
|
|
1794
1808
|
.focus-page .focus-brand { display: inline-flex; margin: 0 0 2.5rem; }
|
|
1795
1809
|
.focus-page h1 { margin: 0 0 1rem; }
|
|
1796
|
-
.secret-value { overflow-wrap: anywhere; font-size: .95rem; padding: .75rem .875rem; margin: .75rem 0; border: 1px solid var(--border); border-radius:
|
|
1810
|
+
.secret-value { overflow-wrap: anywhere; font-size: .95rem; padding: .75rem .875rem; margin: .75rem 0; border: 1px solid var(--border); border-radius: var(--so-radius-md); background: var(--background); user-select: all; }
|
|
1797
1811
|
|
|
1798
1812
|
@media (max-width: 640px) {
|
|
1799
1813
|
button, form.option button { min-height: 2.75rem; }
|
|
@@ -1845,7 +1859,7 @@
|
|
|
1845
1859
|
.lane .hint { margin-top: 0; }
|
|
1846
1860
|
.lane-count { color: var(--muted-foreground); font-weight: 400; font-variant-numeric: tabular-nums; }
|
|
1847
1861
|
.lane h2::before {
|
|
1848
|
-
content: ""; width: .375rem; height: .375rem; border-radius:
|
|
1862
|
+
content: ""; width: .375rem; height: .375rem; border-radius: var(--so-radius-full); flex: none;
|
|
1849
1863
|
background: var(--muted-foreground); opacity: .55;
|
|
1850
1864
|
}
|
|
1851
1865
|
.lane-attention h2::before { background: var(--brand); opacity: 1; }
|
|
@@ -1901,7 +1915,7 @@
|
|
|
1901
1915
|
.command-metric .value { display: block; margin-top: .25rem; font-size: 1.5rem; font-weight: 600; line-height: 1.15; font-variant-numeric: tabular-nums; font-family: var(--font-mono); }
|
|
1902
1916
|
.command-metric .label { display: flex; align-items: center; gap: .4rem; font-weight: 500; font-size: .8125rem; }
|
|
1903
1917
|
.command-metric .label::before {
|
|
1904
|
-
content: ""; width: .375rem; height: .375rem; border-radius:
|
|
1918
|
+
content: ""; width: .375rem; height: .375rem; border-radius: var(--so-radius-full);
|
|
1905
1919
|
background: var(--muted-foreground); flex: none;
|
|
1906
1920
|
}
|
|
1907
1921
|
.command-metric .detail { display: block; color: var(--muted-foreground); font-size: .6875rem; margin-top: .18rem; }
|
|
@@ -2433,7 +2447,7 @@
|
|
|
2433
2447
|
}
|
|
2434
2448
|
.workspace-stats .pulse-stat b { display: block; color: var(--foreground); font-weight: 600; font-family: var(--font-mono); font-size: 1.125rem; line-height: 1.2; font-variant-numeric: tabular-nums; }
|
|
2435
2449
|
.workspace-stats .pulse-stat.hot b { color: var(--brand); }
|
|
2436
|
-
.workspace-bar { display: flex; gap: 2px; height: .375rem; margin-top: .625rem; border-radius:
|
|
2450
|
+
.workspace-bar { display: flex; gap: 2px; height: .375rem; margin-top: .625rem; border-radius: var(--so-radius-full); overflow: hidden; background: var(--muted); }
|
|
2437
2451
|
.workspace-bar .seg { flex: 1 1 0; }
|
|
2438
2452
|
.workspace-bar .seg.attention { background: var(--brand); }
|
|
2439
2453
|
.workspace-bar .seg.building { background: var(--running); }
|