toolroll 0.9.62 → 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 +80 -0
- package/README.md +66 -8
- 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 +203 -10
- package/dist/agent-fence.js +623 -41
- 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 +263 -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 +199 -17
- 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/check-levels.js +3 -0
- 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 +20 -1
- package/dist/cli-help.d.ts +1 -1
- package/dist/cli-help.js +5 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +24 -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/cli.d.ts +8 -0
- package/dist/contracts/cli.js +3 -1
- package/dist/contracts/console-api.d.ts +30 -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/integration-metadata.d.ts +2 -1
- package/dist/contracts/integration-metadata.js +2 -1
- 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/discord-chat.js +4 -1
- package/dist/dispatch.js +7 -4
- package/dist/doctor.d.ts +53 -0
- package/dist/doctor.js +427 -0
- 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/evidence-pack.d.ts +1 -1
- package/dist/evidence-pack.js +2 -2
- package/dist/exec.d.ts +81 -0
- package/dist/exec.js +368 -9
- package/dist/export-ui.js +2 -2
- package/dist/flow-code.d.ts +2 -2
- package/dist/flow-code.js +4 -15
- package/dist/flow-engine.js +35 -11
- 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 +84 -11
- package/dist/flow-triggers.js +554 -87
- package/dist/flow-unattended-words.d.ts +28 -0
- package/dist/flow-unattended-words.js +50 -0
- package/dist/flow-unattended.d.ts +227 -0
- package/dist/flow-unattended.js +776 -0
- package/dist/flows-cli.d.ts +28 -1
- package/dist/flows-cli.js +63 -12
- package/dist/flows-ui.d.ts +2 -3
- package/dist/flows-ui.js +56 -5
- package/dist/flows.d.ts +4 -0
- package/dist/flows.js +9 -1
- package/dist/google-mail.d.ts +1 -0
- package/dist/google-mail.js +61 -18
- package/dist/html.d.ts +9 -0
- package/dist/html.js +11 -0
- package/dist/integrations-ui.d.ts +2 -1
- package/dist/integrations-ui.js +6 -3
- package/dist/integrations.d.ts +17 -3
- package/dist/integrations.js +38 -18
- package/dist/invoke.d.ts +17 -0
- package/dist/invoke.js +510 -182
- package/dist/keys.d.ts +18 -14
- package/dist/keys.js +5 -0
- package/dist/landed-cleanup.d.ts +3 -2
- package/dist/landed-cleanup.js +12 -5
- 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 +163 -4
- 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-connect.d.ts +8 -7
- package/dist/mcp-connect.js +49 -51
- 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/oauth-run-token.d.ts +51 -0
- package/dist/oauth-run-token.js +127 -0
- package/dist/operate.d.ts +2 -0
- package/dist/operate.js +282 -18
- 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/plan-auto.d.ts +11 -0
- package/dist/plan-auto.js +62 -11
- 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/process-recovery-eligibility.d.ts +1 -1
- package/dist/project-access.d.ts +3 -0
- package/dist/project-access.js +3 -0
- package/dist/project-delete-ui.d.ts +12 -0
- package/dist/project-delete-ui.js +41 -9
- package/dist/project-delete.d.ts +3 -1
- package/dist/project-delete.js +12 -6
- package/dist/project-file.d.ts +3 -0
- package/dist/project-file.js +16 -0
- package/dist/project-memory.d.ts +3 -1
- package/dist/project-memory.js +57 -25
- package/dist/project-tools.d.ts +103 -27
- package/dist/project-tools.js +399 -100
- 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-home.d.ts +22 -0
- package/dist/provider-home.js +99 -0
- package/dist/provider.d.ts +42 -5
- package/dist/provider.js +97 -19
- 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/remote-main.d.ts +4 -0
- package/dist/remote-main.js +3 -3
- 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 +171 -6
- 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 +258 -43
- 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 +65 -13
- 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 +49 -5
- package/dist/server/runtime.d.ts +34 -3
- package/dist/server/session.d.ts +25 -1
- package/dist/server/session.js +18 -4
- package/dist/server/settings.js +132 -13
- 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-sweep-child.js +3 -1
- package/dist/storage-sweep-process.d.ts +12 -5
- package/dist/storage-sweep-process.js +23 -6
- 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 +466 -19
- package/dist/store.js +1185 -63
- package/dist/subagent-desk.js +1 -1
- package/dist/subagent-tools.d.ts +1 -1
- package/dist/summary.js +3 -0
- package/dist/surface.js +6 -4
- 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 +14 -0
- package/dist/teardown-git.js +119 -0
- 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 +39 -0
- package/dist/tool-broker.js +370 -0
- package/dist/tool-launcher.js +5 -4
- package/dist/tool-secret-store.d.ts +170 -0
- package/dist/tool-secret-store.js +834 -0
- package/dist/toolroll-update-ui.js +2 -2
- package/dist/toolroll-update.js +14 -3
- package/dist/tools-ui.d.ts +7 -1
- package/dist/tools-ui.js +19 -10
- 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 +21 -5
- package/dist/workspace-revision.js +3 -1
- package/dist/worktree.d.ts +10 -4
- package/dist/worktree.js +70 -29
- package/package.json +13 -2
- package/scripts/provider-canary.mjs +210 -31
package/dist/server/flows.js
CHANGED
|
@@ -18,9 +18,10 @@ import { assignFlowCard, commentOnFlowCard, watchFlowCard } from "../flow-people
|
|
|
18
18
|
import { saveScript } from "../flow-scripts.js";
|
|
19
19
|
import { chooseFlowCard } from "../flow-send.js";
|
|
20
20
|
import { exportFlow, fetchFlowFile, FlowFileError, importFlow, parseFlowFile, planFlowImport } from "../flow-share.js";
|
|
21
|
-
import { addFlowTriggerTo, checkFlowTriggerNow, pressFlowButton, removeFlowTrigger, removeLinearKey, setFlowTriggerOn, renewFlowHook, saveHooksBase, saveLinearKey,
|
|
21
|
+
import { addFlowTriggerTo, checkFlowTriggerNow, pressFlowButton, removeFlowTrigger, removeLinearKey, setFlowTriggerOn, renewFlowHook, saveHooksBase, saveLinearKey, saveSigningSecret, setUpFlowHook, changeFlowHookSender, setFlowHookScope, shareFlowButton, stopSharingFlowButton, rawTakesDeliveries } from "../flow-triggers.js";
|
|
22
22
|
import { FLOW_IMPORT_SCRIPT, flowFallbackHtml, flowImportHtml, flowsListHtml, flowView } from "../flows-ui.js";
|
|
23
23
|
import { FLOW_TEMPLATES, FlowContractError, validateFlowDefinition, withZoneNames } from "../flows.js";
|
|
24
|
+
import { prepareUnattendedCandidates, parseUnattendedAsk, revokeUnattended, signUnattended, untilWords } from "../flow-unattended.js";
|
|
24
25
|
import { keyStatus } from "../keys.js";
|
|
25
26
|
import { kitPageHtml, kitsGalleryHtml } from "../kits-ui.js";
|
|
26
27
|
import { addKitGithubTrigger, addKitSample, kitOf, setUpKit } from "../kits.js";
|
|
@@ -42,7 +43,7 @@ import { redirect, respond, SAFETY, taskHref } from "./http.js";
|
|
|
42
43
|
import { html } from "../html.js";
|
|
43
44
|
import {} from "./session.js";
|
|
44
45
|
export function createFlowsHandlers(runtime) {
|
|
45
|
-
const { store, clock, sendScreen, chromeFor, consoleProjects, options, visible, flowRooms, identify, liveCeiling, providerHome, toolHome, restricted, evidenceRoot, authenticateApprover, projectOf, unscopedMode, ceiling, authorizeMutation } = runtime;
|
|
46
|
+
const { store, clock, sendScreen, chromeFor, consoleProjects, options, visible, flowRooms, identify, liveCeiling, providerHome, toolHome, restricted, evidenceRoot, authenticateApprover, authenticateCredential, projectOf, unscopedMode, ceiling, authorizeMutation } = runtime;
|
|
46
47
|
// ---- reads ---------------------------------------------------------------
|
|
47
48
|
/** A gallery template's page (Flows → New → Use this): its questions, the preview of these answers, and Create. */
|
|
48
49
|
function sendGalleryUse(response, who, template, projects, repo, given, name, notice, said = null) {
|
|
@@ -148,7 +149,7 @@ export function createFlowsHandlers(runtime) {
|
|
|
148
149
|
return refuse(response, who, 404, "No such flow in your projects.", "/flows");
|
|
149
150
|
const selected = Number(url.searchParams.get("card")), start = Number(url.searchParams.get("start"));
|
|
150
151
|
const view = flowView(store, flow, { name: who.name, approver: who.via === "cookie" && who.role === "approver" }, Number.isSafeInteger(selected) && selected > 0 ? selected : null, { dir: options.configDir ?? null, repos: consoleProjects(), startTrigger: Number.isSafeInteger(start) && start > 0 ? start : null,
|
|
151
|
-
sortReady: keyStatus("openrouter", providerHome).set, toolHome });
|
|
152
|
+
sortReady: keyStatus("openrouter", providerHome).set, toolHome, now: clock() });
|
|
152
153
|
if (url.searchParams.get("format") === "json")
|
|
153
154
|
return respond(response, 200, "application/json; charset=utf-8", JSON.stringify(checkResponse("flowView", view)));
|
|
154
155
|
return sendScreen(response, 200, screen(flow.name, html `<p><a href="/flows">Flows</a></p><h1>${flow.name}</h1>${flowFallbackHtml(view)}`, { chrome: chromeFor(flow.repo, "flows"), workspace: { view } }));
|
|
@@ -546,7 +547,7 @@ export function createFlowsHandlers(runtime) {
|
|
|
546
547
|
}
|
|
547
548
|
const definition = flowDefinitionOf(flow);
|
|
548
549
|
const dir = options.configDir ?? null;
|
|
549
|
-
const viewNow = () => flowView(store, store.getFlow(flow.id) ?? flow, { name: who.name, approver: true }, null, { dir, repos: projects, sortReady: keyStatus("openrouter", providerHome).set, toolHome });
|
|
550
|
+
const viewNow = () => flowView(store, store.getFlow(flow.id) ?? flow, { name: who.name, approver: true }, null, { dir, repos: projects, sortReady: keyStatus("openrouter", providerHome).set, toolHome, now });
|
|
550
551
|
const settle = (said, extra = {}) => {
|
|
551
552
|
// Move what can move right away (a message, a decision's next zone); workers file and run the tasks.
|
|
552
553
|
try {
|
|
@@ -571,7 +572,13 @@ export function createFlowsHandlers(runtime) {
|
|
|
571
572
|
catch {
|
|
572
573
|
return answer(400, { ok: false, said: "That trigger couldn't be read." });
|
|
573
574
|
}
|
|
574
|
-
|
|
575
|
+
// A trigger that takes deliveries mints an address: a credential act (docs/plans/passwordless.md).
|
|
576
|
+
const ingress = rawTakesDeliveries(raw);
|
|
577
|
+
if (ingress && !authenticateCredential(who, body.get("password") ?? "").ok)
|
|
578
|
+
return answer(403, { ok: false, said: "Enter your Toolroll password to make a webhook address." });
|
|
579
|
+
const made = addFlowTriggerTo(store, flow, raw, who.name, now, dir, ingress ? { ingress: "proved" } : {});
|
|
580
|
+
if (made.ok)
|
|
581
|
+
await prepareUnattendedCandidates(store, flow.id);
|
|
575
582
|
return made.ok ? settle(made.said, made.reveal === null ? {} : { reveal: made.reveal }) : answer(400, { ok: false, said: made.message });
|
|
576
583
|
}
|
|
577
584
|
if (action === "scripts") {
|
|
@@ -579,8 +586,10 @@ export function createFlowsHandlers(runtime) {
|
|
|
579
586
|
if (body.get("approve") === "yes")
|
|
580
587
|
return store.approveFlowScript(flow.repo, body.get("name") ?? "") ? settle("Approved. Zones that run it go on.") : answer(404, { ok: false, said: "That script isn't waiting for approval." });
|
|
581
588
|
if (body.get("remove") === "yes")
|
|
582
|
-
return store.removeFlowScript(flow.repo, body.get("name") ?? "") ? settle("Script removed. Zones that ran it wait until it's back.") : answer(404, { ok: false, said: "There's no script by that name." });
|
|
589
|
+
return store.removeFlowScript(flow.repo, body.get("name") ?? "", who.name, now) ? settle("Script removed. Zones that ran it wait until it's back.") : answer(404, { ok: false, said: "There's no script by that name." });
|
|
583
590
|
const saved = saveScript(store, flow.repo, { name: body.get("name"), about: body.get("about"), body: body.get("body"), timeoutMinutes: body.get("timeoutMinutes"), language: body.get("language"), file: body.get("file") }, who.name, now);
|
|
591
|
+
if (saved.ok)
|
|
592
|
+
await prepareUnattendedCandidates(store, flow.id);
|
|
584
593
|
return saved.ok ? settle(saved.said) : answer(400, { ok: false, said: saved.message });
|
|
585
594
|
}
|
|
586
595
|
if (action === "linear-key" || action === "hooks-address") {
|
|
@@ -604,6 +613,19 @@ export function createFlowsHandlers(runtime) {
|
|
|
604
613
|
store.archiveFlow(flow.id, who.name, now);
|
|
605
614
|
return redirect(response, "/flows");
|
|
606
615
|
}
|
|
616
|
+
if (action === "unattended") {
|
|
617
|
+
// The owner's password signs exactly the terms the panel showed; a flow saved meanwhile is refused (sawRevision).
|
|
618
|
+
if (!authenticateApprover(who, body.get("password") ?? "").ok)
|
|
619
|
+
return answer(403, { ok: false, said: "That password isn't right. Nothing was signed." });
|
|
620
|
+
const asked = parseUnattendedAsk({ days: body.get("days"), budget: body.get("budget"), runs: body.get("runs"), include: body.get("include") });
|
|
621
|
+
if (!asked.ok)
|
|
622
|
+
return answer(400, { ok: false, said: asked.message });
|
|
623
|
+
const signed = await signUnattended(store, { flow: flow.id, signer: who.name, ask: asked.ask, sawRevision: Number(body.get("revision")), sawSchedules: body.get("schedules") ?? "" }, now);
|
|
624
|
+
return signed.ok ? settle(`Runs on its own until ${untilWords(signed.grant.absoluteExpiry)}.`) : answer(409, { ok: false, said: signed.message });
|
|
625
|
+
}
|
|
626
|
+
if (action === "unattended-off") {
|
|
627
|
+
return revokeUnattended(store, flow.id, who.name, now) ? settle("It no longer runs on its own. New cards wait for you.") : answer(409, { ok: false, said: "It wasn't running on its own." });
|
|
628
|
+
}
|
|
607
629
|
if (action === "save") {
|
|
608
630
|
let saved, drawn;
|
|
609
631
|
try {
|
|
@@ -637,6 +659,8 @@ export function createFlowsHandlers(runtime) {
|
|
|
637
659
|
if (dir === null)
|
|
638
660
|
return answer(409, { ok: false, said: "Secrets are kept on the console's computer." });
|
|
639
661
|
const saved = setFlowSecret(dir, flow.repo, (body.get("name") ?? "").trim(), body.get("value") ?? "");
|
|
662
|
+
if (saved.ok)
|
|
663
|
+
await prepareUnattendedCandidates(store, flow.id);
|
|
640
664
|
return saved.ok ? settle(saved.said) : answer(400, { ok: false, said: saved.message });
|
|
641
665
|
}
|
|
642
666
|
// Adding a card.
|
|
@@ -702,13 +726,17 @@ export function createFlowsHandlers(runtime) {
|
|
|
702
726
|
if (trigger === null || trigger.flow !== flow.id || trigger.state === "removed")
|
|
703
727
|
return answer(404, { ok: false, said: "That trigger isn't on this flow." });
|
|
704
728
|
if (verb === "pause" || verb === "resume") {
|
|
705
|
-
setFlowTriggerOn(store, trigger, verb === "resume", now);
|
|
729
|
+
setFlowTriggerOn(store, trigger, verb === "resume", now, who.name);
|
|
706
730
|
return settle(verb === "pause" ? "Trigger paused." : "Trigger on again.");
|
|
707
731
|
}
|
|
708
732
|
if (verb === "remove") {
|
|
709
|
-
removeFlowTrigger(store, trigger, now, dir);
|
|
733
|
+
removeFlowTrigger(store, trigger, now, dir, who.name);
|
|
710
734
|
return settle("Trigger removed.");
|
|
711
735
|
}
|
|
736
|
+
// A public form link and a new webhook address each open a way in: credential acts, a password typed now.
|
|
737
|
+
if ((verb === "share" || verb === "renew") && !authenticateCredential(who, body.get("password") ?? "").ok) {
|
|
738
|
+
return answer(403, { ok: false, said: verb === "share" ? "Enter your Toolroll password to share a form link." : "Enter your Toolroll password to make a new address." });
|
|
739
|
+
}
|
|
712
740
|
if (verb === "share") {
|
|
713
741
|
const shared = shareFlowButton(store, trigger, now, dir);
|
|
714
742
|
return shared.ok ? settle(shared.said, { reveal: shared.reveal }) : answer(409, { ok: false, said: shared.message });
|
|
@@ -717,6 +745,11 @@ export function createFlowsHandlers(runtime) {
|
|
|
717
745
|
stopSharingFlowButton(store, trigger, now);
|
|
718
746
|
return settle("The form link no longer works.");
|
|
719
747
|
}
|
|
748
|
+
// v126: a person has seen that deliveries were turned away (the banner stays until then).
|
|
749
|
+
if (verb === "dropped") {
|
|
750
|
+
store.clearFlowDeliveriesDropped(trigger.id);
|
|
751
|
+
return settle("Cleared.");
|
|
752
|
+
}
|
|
720
753
|
if (verb === "press") {
|
|
721
754
|
let answers;
|
|
722
755
|
try {
|
|
@@ -744,11 +777,24 @@ export function createFlowsHandlers(runtime) {
|
|
|
744
777
|
const renewed = renewFlowHook(store, trigger, now, dir);
|
|
745
778
|
return renewed.ok ? settle(renewed.said, { reveal: renewed.reveal }) : answer(409, { ok: false, said: renewed.message });
|
|
746
779
|
}
|
|
747
|
-
//
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
780
|
+
// A webhook's credentials change only on this secure panel, behind the password — never in chat (v126): a secret
|
|
781
|
+
// pasted (Linear's, Sentry's, Vercel's), one made and shown once with a new address, or who signs its deliveries.
|
|
782
|
+
if (!authenticateCredential(who, body.get("password") ?? "").ok)
|
|
783
|
+
return answer(403, { ok: false, said: verb === "secret" ? "Enter your Toolroll password to save a secret." : "Enter your Toolroll password to change how this webhook is proved." });
|
|
784
|
+
if (verb === "setup") {
|
|
785
|
+
const made = setUpFlowHook(store, trigger, now, dir);
|
|
786
|
+
return made.ok ? settle(made.said, { reveal: made.reveal }) : answer(409, { ok: false, said: made.message });
|
|
787
|
+
}
|
|
788
|
+
if (verb === "scope") {
|
|
789
|
+
const scoped = setFlowHookScope(store, trigger, { project: body.get("project"), team: body.get("team") }, who.name, now);
|
|
790
|
+
return scoped.ok ? settle(scoped.said) : answer(400, { ok: false, said: scoped.message });
|
|
791
|
+
}
|
|
792
|
+
if (verb === "sender") {
|
|
793
|
+
const changed = changeFlowHookSender(store, trigger, body.get("sender"), who.name, now, dir);
|
|
794
|
+
return changed.ok ? settle(changed.said) : answer(400, { ok: false, said: changed.message });
|
|
795
|
+
}
|
|
796
|
+
const saved = saveSigningSecret(store, trigger, body.get("secret") ?? "", now, dir);
|
|
797
|
+
return saved.ok ? settle(saved.said) : answer(400, { ok: false, said: saved.message });
|
|
752
798
|
};
|
|
753
799
|
const recipeAct = (step) => async (ctx) => {
|
|
754
800
|
const { who, request, response, now, posted } = ctx;
|
|
@@ -887,6 +933,8 @@ export function createFlowsHandlers(runtime) {
|
|
|
887
933
|
"flow.linear-key": flowAct("linear-key"),
|
|
888
934
|
"flow.hooks-address": flowAct("hooks-address"),
|
|
889
935
|
"flow.secrets": flowAct("secrets"),
|
|
936
|
+
"flow.unattended": flowAct("unattended"),
|
|
937
|
+
"flow.unattended-off": flowAct("unattended-off"),
|
|
890
938
|
"flow.triggers": flowAct("triggers"),
|
|
891
939
|
"flow.card.move": cardAct("move"),
|
|
892
940
|
"flow.card.decide": cardAct("decide"),
|
|
@@ -902,6 +950,10 @@ export function createFlowsHandlers(runtime) {
|
|
|
902
950
|
"flow.trigger.press": triggerAct("press"),
|
|
903
951
|
"flow.trigger.renew": triggerAct("renew"),
|
|
904
952
|
"flow.trigger.secret": triggerAct("secret"),
|
|
953
|
+
"flow.trigger.setup": triggerAct("setup"),
|
|
954
|
+
"flow.trigger.sender": triggerAct("sender"),
|
|
955
|
+
"flow.trigger.scope": triggerAct("scope"),
|
|
956
|
+
"flow.trigger.dropped": triggerAct("dropped"),
|
|
905
957
|
"flow.trigger.share": triggerAct("share"),
|
|
906
958
|
"flow.trigger.unshare": triggerAct("unshare"),
|
|
907
959
|
"recipes.prepare-send": recipeAct("prepare"),
|
package/dist/server/guards.d.ts
CHANGED
|
@@ -41,6 +41,7 @@ export interface GuardsRuntime {
|
|
|
41
41
|
}
|
|
42
42
|
export declare function createSharedGuards(runtime: GuardsRuntime): {
|
|
43
43
|
authenticateApprover: (who: Who, token: string, repo?: string | null) => ReturnType<typeof checkApproverPassword>;
|
|
44
|
+
authenticateCredential: (who: Who, token: string, repo?: string | null) => ReturnType<typeof checkApproverPassword>;
|
|
44
45
|
authorizeMutation: (request: IncomingMessage, who: Who, body: FormView<FormFieldOf<"mutationGuard">>) => {
|
|
45
46
|
status: number;
|
|
46
47
|
message: string;
|
package/dist/server/guards.js
CHANGED
|
@@ -6,23 +6,29 @@ import { tokenLive, tokenProjects } from "../api-tokens.js";
|
|
|
6
6
|
import { oauthProjects, oauthTokenAllowed } from "../mcp-oauth.js";
|
|
7
7
|
import { REMOTE_MESSAGES, reproveRemote } from "../operate-remote.js";
|
|
8
8
|
import { canonicalProject, rowVisible } from "../project.js";
|
|
9
|
-
import { authenticateAccount, authenticateApprover as checkApproverPassword } from "../scope.js";
|
|
9
|
+
import { authenticateAccount, authenticateApprover as checkApproverPassword, credentialProof } from "../scope.js";
|
|
10
10
|
import { SourceBudget } from "../sign-in-guard.js";
|
|
11
11
|
import {} from "../store.js";
|
|
12
12
|
import { requestContext } from "./request-context.js";
|
|
13
13
|
import { refuse } from "./chrome.js";
|
|
14
14
|
import { matchTaskPath } from "./http.js";
|
|
15
|
-
import { PersistentSessions,
|
|
15
|
+
import { PersistentSessions, SESSION_COOKIE, sessionWithinLifetime } from "./session.js";
|
|
16
16
|
export function createSharedGuards(runtime) {
|
|
17
17
|
// v99: wrong passwords. One address guessing across names runs out of tries here; each name
|
|
18
18
|
// locks on its own (sign-in-guard.ts), and every lock, whatever road it came by, is kept.
|
|
19
19
|
/**
|
|
20
|
-
* Every step-up in the console (
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
20
|
+
* Every step-up in the console: a password typed now, or (an empty field) an explicit identity-provider re-check
|
|
21
|
+
* made on request and not yet spent, or — inside an allowlisted act only — a remembered password (cockpit B2,
|
|
22
|
+
* scope.ts proveAccountNow / freshnessFor, reading the request's live facts). Standing (an approver, with access to
|
|
23
|
+
* the project) is checked either way.
|
|
24
24
|
*/
|
|
25
|
-
|
|
25
|
+
/** A credential act (docs/plans/passwordless.md): only a password typed now (or a one-use provider re-check) proves it. */
|
|
26
|
+
function authenticateCredential(who, token, repo) {
|
|
27
|
+
if (who.via !== "cookie")
|
|
28
|
+
return { ok: false, reason: "not-an-approver" };
|
|
29
|
+
const proved = checkApproverPassword(runtime.store, who.name, token, repo, { credential: true });
|
|
30
|
+
return !proved.ok || credentialProof(proved.method) ? proved : { ok: false, reason: "not-an-approver" };
|
|
31
|
+
}
|
|
26
32
|
function authenticateApprover(who, token, repo) {
|
|
27
33
|
// Re-entering a password never upgrades a machine credential into a browser ceremony.
|
|
28
34
|
if (who.via !== "cookie")
|
|
@@ -332,7 +338,7 @@ export function createSharedGuards(runtime) {
|
|
|
332
338
|
}
|
|
333
339
|
for (const [id, session] of found === null ? [] : [found]) {
|
|
334
340
|
const now = Date.now();
|
|
335
|
-
if (
|
|
341
|
+
if (!sessionWithinLifetime(session, now)) {
|
|
336
342
|
runtime.sessions.delete(id);
|
|
337
343
|
return null;
|
|
338
344
|
}
|
|
@@ -340,10 +346,18 @@ export function createSharedGuards(runtime) {
|
|
|
340
346
|
runtime.sessions.delete(id);
|
|
341
347
|
return null;
|
|
342
348
|
}
|
|
349
|
+
// Ended in another process (or from Sessions): the cached copy is dropped, never used.
|
|
350
|
+
if (!runtime.sessions.stillSaved(id)) {
|
|
351
|
+
Map.prototype.delete.call(runtime.sessions, id);
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
343
354
|
if (touch) {
|
|
344
355
|
session.lastSeen = now;
|
|
345
356
|
runtime.sessions.persist(session, true);
|
|
346
357
|
}
|
|
358
|
+
// A touch that found the session ended elsewhere dropped it: this request is not signed in by it.
|
|
359
|
+
if (touch && !runtime.sessions.holds(session))
|
|
360
|
+
return null;
|
|
347
361
|
return session;
|
|
348
362
|
}
|
|
349
363
|
return null;
|
|
@@ -356,5 +370,5 @@ export function createSharedGuards(runtime) {
|
|
|
356
370
|
function runVisible(run) {
|
|
357
371
|
return visible(taskRepoOf(run.taskRef));
|
|
358
372
|
}
|
|
359
|
-
return { authenticateApprover, authorizeMutation, projectOf, resolveRouteProject, projectRequestAllowed, identify, lookupSession, restricted, visible, admissionList, codingActorAllowed, codingProjectAllowed, workAccess, familyOf, taskRepoOf, runVisible };
|
|
373
|
+
return { authenticateApprover, authenticateCredential, authorizeMutation, projectOf, resolveRouteProject, projectRequestAllowed, identify, lookupSession, restricted, visible, admissionList, codingActorAllowed, codingProjectAllowed, workAccess, familyOf, taskRepoOf, runVisible };
|
|
360
374
|
}
|
package/dist/server/http.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** HTTP answers every console route shares: server options, body limits, safe responses, redirects and return paths. */
|
|
2
|
+
import type { TerminalHost } from "../live-terminal.js";
|
|
2
3
|
import { type Html } from "../html.js";
|
|
3
4
|
import { type CliHttpOptions, type RunOperateAs } from "../cli-http.js";
|
|
4
5
|
import { type CodingWorkspace } from "../coding-workspace.js";
|
|
@@ -25,6 +26,8 @@ export type ServeOptions = {
|
|
|
25
26
|
installBin?: string;
|
|
26
27
|
/** Native coding workspace injection for isolated integration tests. */
|
|
27
28
|
codingWorkspace?: CodingWorkspace;
|
|
29
|
+
/** Live terminals (tests stand their own in; null turns them off). Default: one on a machine that runs agents. */
|
|
30
|
+
terminalHost?: TerminalHost | null;
|
|
28
31
|
/** Read-only worker connections for heavy reads (read-executor.ts); 0 reads in-process. Default: off under tests. */
|
|
29
32
|
readWorkers?: number;
|
|
30
33
|
store: Store;
|
|
@@ -141,6 +144,10 @@ export type ServeOptions = {
|
|
|
141
144
|
connectFetch?: typeof fetch;
|
|
142
145
|
/** Tests: every request sign-in with the identity provider makes (discovery, keys, tokens). */
|
|
143
146
|
ssoFetch?: typeof fetch;
|
|
147
|
+
/** Cockpit B1: this `up` console's instance secret (null: none — the instance route answers 404). */
|
|
148
|
+
instanceSecret?: () => string | null;
|
|
149
|
+
/** Cockpit E: how Finder is asked to reveal a proved checkout (tests pass their own; never a launch). True: shown. */
|
|
150
|
+
revealPath?: (path: string) => Promise<boolean> | boolean;
|
|
144
151
|
chatEnv?: Record<string, string | undefined>;
|
|
145
152
|
/**
|
|
146
153
|
* The live peek's locality ASSERTION (live-peek v3 §3): the administrator
|
package/dist/server/http.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/** HTTP answers every console route shares: server options, body limits, safe responses, redirects and return paths. */
|
|
2
1
|
import { html, htmlString, isHtml } from "../html.js";
|
|
3
2
|
import { chatResultHref as sharedResultHref } from "../chat-controls.js";
|
|
4
3
|
import {} from "../cli-http.js";
|
|
@@ -117,7 +116,9 @@ export function goOutside(response, to, words, bind) {
|
|
|
117
116
|
response.end(htmlString(html `<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta http-equiv="refresh" content="0;url=${to}"><title>Toolroll</title>${HANDOFF_STYLE}<p>${words} <a href="${to}">Continue</a></p>`));
|
|
118
117
|
}
|
|
119
118
|
export function redirect(response, to) {
|
|
120
|
-
|
|
119
|
+
// Set as a header (not only in writeHead) so the answer can be read back after: a refused act's redirect names it.
|
|
120
|
+
response.setHeader("Location", to);
|
|
121
|
+
response.writeHead(303, { ...SAFETY });
|
|
121
122
|
response.end();
|
|
122
123
|
}
|
|
123
124
|
export const taskChatHref = (taskId) => `/chat?task=${encodeURIComponent(taskId)}`;
|
package/dist/server/live.d.ts
CHANGED
|
@@ -1,10 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET /live: one stream per page, joined to every room the page shows.
|
|
3
|
+
*
|
|
4
|
+
* room=workspace the reader's workspace may have changed (any meaningful write, or a lapse)
|
|
5
|
+
* room=task:<id> the task family changed; who else has it open
|
|
6
|
+
* room=flow:<id>?card=&editing= the flow changed; who else is on it, and on which card
|
|
7
|
+
* room=chat[?task=|?project=] the reader's own lead reply, as it runs (the only room that carries content)
|
|
8
|
+
* room=team[?conversation=] the team conversation's cursor moved
|
|
9
|
+
* room=agent:<run> one agent on the Agents board changed; while open, this person watches it (no pings)
|
|
10
|
+
* room=summary the reader's project states (the sidebar): each change carries the snapshot; never watching or seeing
|
|
11
|
+
*
|
|
12
|
+
* Every room is validated before anything is looked up, and admitted with
|
|
13
|
+
* the same checks and answers as the page it belongs to, before the stream
|
|
14
|
+
* opens; each is re-proved on every signal and its safety-net check, and a
|
|
15
|
+
* viewer who no longer passes hears `gone`. Events name their room. A page
|
|
16
|
+
* that falls behind gets one `reload` and the stream ends, so its
|
|
17
|
+
* EventSource reconnects (live-bus.ts).
|
|
18
|
+
*/
|
|
19
|
+
import type { IncomingMessage } from "node:http";
|
|
1
20
|
import { type LiveBus } from "../live-bus.js";
|
|
2
21
|
import type { WorkspaceRevision } from "../workspace-revision.js";
|
|
3
22
|
import type { ServerRuntime } from "./runtime.js";
|
|
23
|
+
import type { Who } from "./session.js";
|
|
4
24
|
/** A page asks for a few rooms at once: its workspace, what it shows, its conversation. */
|
|
5
|
-
export declare const LIVE_ROOMS_PER_PAGE =
|
|
25
|
+
export declare const LIVE_ROOMS_PER_PAGE = 6;
|
|
6
26
|
export type LiveRoom = {
|
|
7
27
|
kind: "workspace";
|
|
28
|
+
} | {
|
|
29
|
+
kind: "summary";
|
|
8
30
|
} | {
|
|
9
31
|
kind: "task";
|
|
10
32
|
task: string;
|
|
@@ -26,10 +48,21 @@ export type LiveRoom = {
|
|
|
26
48
|
};
|
|
27
49
|
/** A room's name, read strictly: anything else is not a room. */
|
|
28
50
|
export declare function parseLiveRoom(name: string): LiveRoom | null;
|
|
51
|
+
/** The same session, still signed in, from the cookie and the account (never this request's context): a live room's and
|
|
52
|
+
* a live terminal's re-proof. Null when it was signed out, ended elsewhere, revoked, rotated or re-roled. */
|
|
53
|
+
export declare function rereadBrowser(runtime: Pick<ServerRuntime, "identify" | "sessions" | "store">, request: IncomingMessage, who: Who & {
|
|
54
|
+
via: "cookie";
|
|
55
|
+
}): (Who & {
|
|
56
|
+
via: "cookie";
|
|
57
|
+
}) | null;
|
|
29
58
|
export declare function createLiveHandlers(runtime: ServerRuntime, live: {
|
|
30
59
|
bus: LiveBus;
|
|
31
60
|
workspaceRevision: WorkspaceRevision;
|
|
32
61
|
chatProjectOf: (projects: string[]) => string | null | undefined;
|
|
62
|
+
/** One account's sidebar snapshot for one scope (serve.ts projectSummaryFor); null: gone, or the scope moved. */
|
|
63
|
+
projectSummaryFor: (account: string, scope: string) => string | null;
|
|
64
|
+
/** What one account may see now (serve.ts projectScopeFor); null: no such account. */
|
|
65
|
+
projectScopeFor: (account: string) => string | null;
|
|
33
66
|
}): {
|
|
34
67
|
registrations: import("./handler-registry.js").Registration[];
|
|
35
68
|
turnStarted: (thread: number) => void;
|
package/dist/server/live.js
CHANGED
|
@@ -10,8 +10,8 @@ import { respond } from "./http.js";
|
|
|
10
10
|
import { adapterPolicy } from "./route-policy.js";
|
|
11
11
|
import { requestContext } from "./request-context.js";
|
|
12
12
|
/** A page asks for a few rooms at once: its workspace, what it shows, its conversation. */
|
|
13
|
-
export const LIVE_ROOMS_PER_PAGE =
|
|
14
|
-
const ROOM_NAME = /^(workspace|task|flow|chat|team|agent)(?::([^?]{1,400}))?(?:\?([^#]{0,600}))?$/;
|
|
13
|
+
export const LIVE_ROOMS_PER_PAGE = 6;
|
|
14
|
+
const ROOM_NAME = /^(workspace|task|flow|chat|team|agent|summary)(?::([^?]{1,400}))?(?:\?([^#]{0,600}))?$/;
|
|
15
15
|
/** At most one workspace nudge a second: a burst of writes is one re-read, the first heard at once. */
|
|
16
16
|
const WORKSPACE_SETTLE_MS = 1_000;
|
|
17
17
|
/** A running reply's preview: at most one snapshot every 80 ms; the last one always goes out. */
|
|
@@ -33,6 +33,7 @@ export function parseLiveRoom(name) {
|
|
|
33
33
|
const only = (...allowed) => [...params.keys()].every(one => allowed.includes(one)) && allowed.every(one => params.getAll(one).length <= 1);
|
|
34
34
|
switch (kind) {
|
|
35
35
|
case "workspace": return key === null && query === undefined ? { kind } : null;
|
|
36
|
+
case "summary": return key === null && query === undefined ? { kind } : null;
|
|
36
37
|
case "task": return key !== null && key !== "" && query === undefined ? { kind, task: key } : null;
|
|
37
38
|
case "flow": {
|
|
38
39
|
const flow = Number(key), card = params.get("card");
|
|
@@ -53,11 +54,34 @@ export function parseLiveRoom(name) {
|
|
|
53
54
|
}
|
|
54
55
|
return null;
|
|
55
56
|
}
|
|
57
|
+
/** The same session, still signed in, from the cookie and the account (never this request's context): a live room's and
|
|
58
|
+
* a live terminal's re-proof. Null when it was signed out, ended elsewhere, revoked, rotated or re-roled. */
|
|
59
|
+
export function rereadBrowser(runtime, request, who) {
|
|
60
|
+
const again = runtime.identify(request, false);
|
|
61
|
+
if (again?.via !== "cookie" || again.name !== who.name || again.session.generation !== who.session.generation)
|
|
62
|
+
return null;
|
|
63
|
+
// The in-memory session may still exist after another process ends the saved session.
|
|
64
|
+
const hash = runtime.sessions.hashOf(again.session);
|
|
65
|
+
const saved = hash === null ? null : runtime.store.webSession(hash), account = runtime.store.accountOf(again.name);
|
|
66
|
+
if (saved === null || account === null || account.revokedAt !== null || account.generation !== again.session.generation
|
|
67
|
+
|| saved.generation !== account.generation || saved.role !== account.role || again.role !== account.role)
|
|
68
|
+
return null;
|
|
69
|
+
return again;
|
|
70
|
+
}
|
|
56
71
|
const json = (response, status, value) => respond(response, status, "application/json", JSON.stringify(value));
|
|
57
72
|
export function createLiveHandlers(runtime, live) {
|
|
58
73
|
const { store, identify, liveCeiling, familyOf, taskRooms, flowRooms, clock, leadPrincipal, taskChatFocus, chatScopeOf, liveTurns, team, visible } = runtime;
|
|
59
74
|
// The reader's workspace: one room for everyone, nudged by any meaningful write or a clock-bound lapse.
|
|
60
75
|
const workspaceRooms = createLiveRooms(() => `${live.workspaceRevision.current()}:${live.workspaceRevision.expiresAt(clock())}`, null, { bus: live.bus, minIntervalMs: WORKSPACE_SETTLE_MS });
|
|
76
|
+
// The reader's project states, per account AND scope: the fingerprint IS the snapshot, so each `change` carries it
|
|
77
|
+
// (`at`). Every delivery re-proves the viewer's scope, so a snapshot read for a wider scope (a cached one, a held
|
|
78
|
+
// one, one a newcomer would be handed on joining) never reaches a narrowed reader: it hears `gone` instead. Reads
|
|
79
|
+
// settle: a burst of writes is one read, while every signal still re-proves at once.
|
|
80
|
+
const summaryKey = (account, scope) => JSON.stringify([account, scope]);
|
|
81
|
+
const summaryRooms = createLiveRooms(key => {
|
|
82
|
+
const [account, scope] = JSON.parse(key);
|
|
83
|
+
return live.projectSummaryFor(account, scope);
|
|
84
|
+
}, null, { bus: live.bus, minIntervalMs: WORKSPACE_SETTLE_MS, readSettleMs: WORKSPACE_SETTLE_MS });
|
|
61
85
|
// A team audience is per actor (and per token): its key carries who reads, the room keeps how to read as them.
|
|
62
86
|
const teamActors = new Map();
|
|
63
87
|
const teamRooms = createLiveRooms(key => {
|
|
@@ -68,19 +92,7 @@ export function createLiveHandlers(runtime, live) {
|
|
|
68
92
|
const chatViewers = new Set();
|
|
69
93
|
// One agent's open view (Agents board): what it shows, keyed by the exact run.
|
|
70
94
|
const agentRooms = createLiveRooms(run => agentRunFingerprint(store, run), null, { bus: live.bus });
|
|
71
|
-
|
|
72
|
-
const readBrowser = (request, who) => {
|
|
73
|
-
const again = identify(request, false);
|
|
74
|
-
if (again?.via !== "cookie" || again.name !== who.name || again.session.generation !== who.session.generation)
|
|
75
|
-
return null;
|
|
76
|
-
// The in-memory session may still exist after another process ends the saved session.
|
|
77
|
-
const hash = runtime.sessions.hashOf(again.session);
|
|
78
|
-
const saved = hash === null ? null : store.webSession(hash), account = store.accountOf(again.name);
|
|
79
|
-
if (saved === null || account === null || account.revokedAt !== null || account.generation !== again.session.generation
|
|
80
|
-
|| saved.generation !== account.generation || saved.role !== account.role || again.role !== account.role)
|
|
81
|
-
return null;
|
|
82
|
-
return again;
|
|
83
|
-
};
|
|
95
|
+
const readBrowser = (request, who) => rereadBrowser(runtime, request, who);
|
|
84
96
|
const sameBrowser = (request, who) => () => readBrowser(request, who) !== null;
|
|
85
97
|
function admitted(viewer, thread) {
|
|
86
98
|
let valid = false;
|
|
@@ -90,7 +102,7 @@ export function createLiveHandlers(runtime, live) {
|
|
|
90
102
|
catch { /* Failed proof stops this room. */ }
|
|
91
103
|
if (!valid) {
|
|
92
104
|
viewer.connection.send(viewer.room, "gone");
|
|
93
|
-
viewer.leave();
|
|
105
|
+
viewer.connection.refuse(() => viewer.leave());
|
|
94
106
|
}
|
|
95
107
|
return valid;
|
|
96
108
|
}
|
|
@@ -184,7 +196,7 @@ export function createLiveHandlers(runtime, live) {
|
|
|
184
196
|
if ([...url.searchParams.keys()].some(key => key !== "room") || names.length === 0 || names.length > LIVE_ROOMS_PER_PAGE || new Set(names).size !== names.length)
|
|
185
197
|
return json(response, 400, { error: "room" });
|
|
186
198
|
const rooms = names.map(name => ({ name, room: parseLiveRoom(name) }));
|
|
187
|
-
if (rooms.some(one => one.room === null) || rooms.filter(one => one.room?.kind === "agent").length > 1 || rooms.filter(one => one.room?.kind === "workspace").length > 1 || rooms.filter(one => one.room?.kind === "chat").length > 1 || rooms.filter(one => one.room?.kind === "team").length > 1)
|
|
199
|
+
if (rooms.some(one => one.room === null) || rooms.filter(one => one.room?.kind === "agent").length > 1 || rooms.filter(one => one.room?.kind === "workspace").length > 1 || rooms.filter(one => one.room?.kind === "chat").length > 1 || rooms.filter(one => one.room?.kind === "team").length > 1 || rooms.filter(one => one.room?.kind === "summary").length > 1)
|
|
188
200
|
return json(response, 400, { error: "room" });
|
|
189
201
|
// Admit every room before the stream opens, with the answers each page's own stream gave.
|
|
190
202
|
const joins = [];
|
|
@@ -195,6 +207,15 @@ export function createLiveHandlers(runtime, live) {
|
|
|
195
207
|
const browser = who;
|
|
196
208
|
joins.push(connection => workspaceRooms.join("workspace", { name: who.name, room: name, connection, valid: sameBrowser(request, browser) }));
|
|
197
209
|
}
|
|
210
|
+
else if (room.kind === "summary") {
|
|
211
|
+
const browser = who;
|
|
212
|
+
const scope = live.projectScopeFor(who.name);
|
|
213
|
+
if (scope === null)
|
|
214
|
+
return json(response, 404, { error: "summary" });
|
|
215
|
+
const signedIn = sameBrowser(request, browser);
|
|
216
|
+
joins.push(connection => summaryRooms.join(summaryKey(who.name, scope), { name: who.name, room: name, connection,
|
|
217
|
+
valid: () => signedIn() && live.projectScopeFor(who.name) === scope }));
|
|
218
|
+
}
|
|
198
219
|
else if (room.kind === "task") {
|
|
199
220
|
const browser = who;
|
|
200
221
|
const family = familyOf(room.task);
|
|
@@ -333,6 +354,7 @@ export function createLiveHandlers(runtime, live) {
|
|
|
333
354
|
close() {
|
|
334
355
|
detachChat();
|
|
335
356
|
workspaceRooms.close();
|
|
357
|
+
summaryRooms.close();
|
|
336
358
|
teamRooms.close();
|
|
337
359
|
agentRooms.close();
|
|
338
360
|
for (const viewer of [...chatViewers])
|
|
@@ -6,6 +6,9 @@ const PWA_ICON_192 = "iVBORw0KGgoAAAANSUhEUgAAAMAAAADACAIAAADdvvtQAAAB1klEQVR42u
|
|
|
6
6
|
const PWA_ICON_512 = "iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAIAAAB7GkOtAAAJF0lEQVR42u3VwQ0AEBBFQXU4KUD/TW0R3JzcRLJhfqYCwivDzMy+XHEEZmYCYGZmAmBmZgJgZmYCYGZmAmBmZgJgZmYCYGZmAmBmZgJgZmYCYGZmAmBmZgJgZmYCYGZmAmBmZgJgZmYCYGZmAmBmZgJgZmYCYGZmAmBmZgJgZmYCYGZmAmBmJgBmZiYAZmYmAGZmJgBmZiYAZmYmAGZmJgDb1dYBOCEAAAIgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACAIAAACAAAAgAAAIAgAAAIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACAIAAACAAAAgAAAIAgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIAAACAIAAACAAAAgAAAIAgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIAAACAIAAACAAAAgAAAIAIAACACAAAgAgAAIAIAACACAAAgAgAAIAIAACACAAAgAgAAIAIAACACAAAgAgAAIAIAACACAAAgAgAAIAIAACcF+Y5Z5HKgACIAAmAAgAAmACgAAgACYACAACYAKAACAAJgAIAAJgAoAAIAAmAAgAAmACIAACgP/FBEAABEAAzARAAARAAMwEQAAEQADMBEAABEAAzARAAARAAMwEQAAEQADMBEAABEAAzARAAARAAMwEQAAEQADMBEAABEAAzARAAARAAMwEQAAEQADMBEAABEAAzARAAARAAMwEQAAEQABMABAAARAAEwAEAAEwAUAAEAATAAQAATABQAAQABMABAABMAFAABAAEwABEAAEwARAAARAAMwEQAAEQADMBEAABEAAzARAAARAAMwEQAAEQADMBEAABEAAzARAAARAAMwEQAAEQADMBEAABEAAzARAAARAAMwEQAAEQADMBEAABEAAzARAAARAAMwEQAAEAEAABABAAAQAQAAAEAAABAAAAQBAAAAQAAAEAEAABABAAAQAQAAEAEAABABAAAQAQAAEAEAABABAAAQAQAAEAEAABABAAAQAQAAEAEAABABAAAQAQAAEAEAABABAANwcgAAAIAAACAAAAgCAAAAgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIAgAAAIAAACAAAAgCAAAAgAAACIAC/C7Pc80gFQAAEwAQAAUAATAAQAATABAABQABMABAABMAEAAFAAEwAEAAEwAQAAUAATAAEQADwv5gACIAACICZAAiAAAiAmQAIgAAIgJkACIAACICZAAiAAAiAmQAIgAAIgJkACIAACICZAAiAAAiAmQAIgAAIgJkACIAACICZAAiAAAiAmQAIgAAIgJkACIAACICZAAiAAAiAmQAIgAAIgAkAAiAAAmACgAAgACYACAACYAKAACAAJgAIAAJgAoAAIAAmAAgAAmACIAACgACYAAiAAAiAmQAIgAAIgJkACIAACICZAAiAAAiAmQAIgAAIgJkACIAACICZAAiAAAiAmQAIgAAIgJkACIAACICZAAiAAAiAmQAIgAAIgJkACIAACICZAAiAAAiAmQAIgAAACIAAAAiAAAAIAAACAIAAACAAAAgAAAIAgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACIAAAAiAmwMQAAAEAAABAEAAABAAAAQAQAAEAEAABABAAAQAQAAEAEAABABAAAQAQAAEAEAABABAAAQAQAAEAEAABABAAAQAQAAEAEAABABAAAQAQAAEAEAABABAAAAQAAAEAAABAEAAABAAAASAJcxyzyMVAAEQABMABAABMAFAABAAEwAEAAEwAUAAEAATAAQAATABQAAQABMABAABMAEQAAHA/2ICIAACIABmAiAAAiAAZgIgAAIgAGYCIAACIABmAiAAAiAAZgIgAAIgAGYCIAACIABmAiAAAiAAZgIgAAIgAGYCIAACIABmAiAAAiAAZgIgAAIgAGYCIAACIABmAiAAAiAAZgIgAAIgACYACIAACIAJAAKAAJgAIAAIgAkAAoAAmAAgAAiACQACgACYACAACIAJgAAIAAJgAiAAAiAAZgIgAAIgAGYCIAACIABmAiAAAiAAZgIgAAIgAGYCIAACIABmAiAAAiAAZgIgAAIgAGYCIAACIABmAiAAAiAAZgIgAAIgAGYCIAACIABmAiAAAiAAZgIgAAIAIAACACAAAgAgAAAIAAACAIAAACAAAAgAAAIAIAACACAAAgAgAAIAIAACACAAAgAgAAIAIAACACAAAgAgAAIAIAACACAAAgAgAAIAIAACACAAAgAgAAIAIAACACAAbg5AAAAQAAAEAAABAEAAABAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAAQBAAAAQAAAEAAABAEAAABAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABcHkAAgCAAAAgAAAIAAACAIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIgJmZPT4BMDMTADMzEwAzMxMAMzMTADMzEwAzMxMAMzMTADMzEwAzMxMAMzMTADMzEwAzMxMAMzMTADMzEwAzMxMAMzMTADMzEwAzMxMAMzMTADMzEwAzMxMAMzMTADMzEwAzMwEwMzMBMDMzATAzMwEwMzMBMDOzJzYBVJhD+Nnu218AAAAASUVORK5CYII=";
|
|
7
7
|
const PWA_ICON_APPLE = "iVBORw0KGgoAAAANSUhEUgAAALQAAAC0CAIAAACyr5FlAAABqklEQVR42u3aMQ0AIAxFwepgQgD+TSECFHQiYWjv5ylobmwcs2ThBAaHwWFwGBwGh8FhcBgcBofBkW7MparBITgEh+AQHIJDcAgOwQEHHHDAAYfgEByCQ3AIDsEhOASHI8IBBxxwCA7BITgEh+AQHIJDcAgOOOCAQ3AIDsEhOASH4BAcguNPu9PggAMOOOCAAw444IADDjjggAMOOOCAAw444IADDjjggAMOOOCAAw44PPsIDsEhOASH4BAcgkNwCA444IADDjgEh+AQHIJDcAgOwSE44IDDg7EHYzjggAMOOOCAAw444IADDjjggAMOOOCAAw444IADDjjggAMOOOCAAw559hEcgkNwwAEHHHDAITgEh+AQHIJDcAgOwQEHHHDAAYfgEByCQ3B4MIYDDjjggAMOOOCAAw444IADDjjggAMOOOCAAw444IADDjjggAMOOODw7OPZBw7BITgEh+AQHIJDcAgOOOCAAw44BIfgEByCQ3AIDsEhOAQHHHDAITgEh+AQHIJDcAgOwSE44IADDjgecVjbwWFwGBwGh8FhcBgcBofBYeV3AaohX51oqNRKAAAAAElFTkSuQmCC";
|
|
8
8
|
const PWA_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><rect x="6" y="6" width="88" height="88" rx="14" fill="#1a202c"/><rect x="28" y="28" width="44" height="8" rx="4" fill="#ebebeb"/><rect x="28" y="47" width="44" height="8" rx="4" fill="#ebebeb"/><rect x="28" y="66" width="44" height="8" rx="4" fill="#ebebeb"/></svg>`;
|
|
9
|
+
/** The icon with the attention dot (cockpit §3): a tab whose reader has something waiting shows it. Same origin, so
|
|
10
|
+
* the page's img-src 'self' allows it; nothing about the reader rides the asset. */
|
|
11
|
+
const PWA_ICON_ATTENTION_SVG = PWA_ICON_SVG.replace("</svg>", `<circle cx="78" cy="22" r="20" fill="#bb2649" stroke="#ffffff" stroke-width="6"/></svg>`);
|
|
9
12
|
/** The typefaces by route: exact names only, served pre-auth like the icons. */
|
|
10
13
|
const FONT_FILES = {
|
|
11
14
|
"/fonts/geist-sans-400.woff2": GEIST_SANS_400,
|
|
@@ -87,6 +90,8 @@ export function serveInstallAsset(response, pathname, respond) {
|
|
|
87
90
|
return asset("image/png", Buffer.from(PWA_ICON_APPLE, "base64"));
|
|
88
91
|
if (pathname === "/icon.svg")
|
|
89
92
|
return asset("image/svg+xml", PWA_ICON_SVG);
|
|
93
|
+
if (pathname === "/icon-attention.svg")
|
|
94
|
+
return asset("image/svg+xml", PWA_ICON_ATTENTION_SVG);
|
|
90
95
|
if (pathname === "/icon-192.png")
|
|
91
96
|
return asset("image/png", Buffer.from(PWA_ICON_192, "base64"));
|
|
92
97
|
if (pathname === "/icon-512.png")
|