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/flows.js
CHANGED
|
@@ -18,7 +18,7 @@ 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
24
|
import { prepareUnattendedCandidates, parseUnattendedAsk, revokeUnattended, signUnattended, untilWords } from "../flow-unattended.js";
|
|
@@ -43,7 +43,7 @@ import { redirect, respond, SAFETY, taskHref } from "./http.js";
|
|
|
43
43
|
import { html } from "../html.js";
|
|
44
44
|
import {} from "./session.js";
|
|
45
45
|
export function createFlowsHandlers(runtime) {
|
|
46
|
-
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;
|
|
47
47
|
// ---- reads ---------------------------------------------------------------
|
|
48
48
|
/** A gallery template's page (Flows → New → Use this): its questions, the preview of these answers, and Create. */
|
|
49
49
|
function sendGalleryUse(response, who, template, projects, repo, given, name, notice, said = null) {
|
|
@@ -572,7 +572,11 @@ export function createFlowsHandlers(runtime) {
|
|
|
572
572
|
catch {
|
|
573
573
|
return answer(400, { ok: false, said: "That trigger couldn't be read." });
|
|
574
574
|
}
|
|
575
|
-
|
|
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" } : {});
|
|
576
580
|
if (made.ok)
|
|
577
581
|
await prepareUnattendedCandidates(store, flow.id);
|
|
578
582
|
return made.ok ? settle(made.said, made.reveal === null ? {} : { reveal: made.reveal }) : answer(400, { ok: false, said: made.message });
|
|
@@ -729,6 +733,10 @@ export function createFlowsHandlers(runtime) {
|
|
|
729
733
|
removeFlowTrigger(store, trigger, now, dir, who.name);
|
|
730
734
|
return settle("Trigger removed.");
|
|
731
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
|
+
}
|
|
732
740
|
if (verb === "share") {
|
|
733
741
|
const shared = shareFlowButton(store, trigger, now, dir);
|
|
734
742
|
return shared.ok ? settle(shared.said, { reveal: shared.reveal }) : answer(409, { ok: false, said: shared.message });
|
|
@@ -737,6 +745,11 @@ export function createFlowsHandlers(runtime) {
|
|
|
737
745
|
stopSharingFlowButton(store, trigger, now);
|
|
738
746
|
return settle("The form link no longer works.");
|
|
739
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
|
+
}
|
|
740
753
|
if (verb === "press") {
|
|
741
754
|
let answers;
|
|
742
755
|
try {
|
|
@@ -764,11 +777,24 @@ export function createFlowsHandlers(runtime) {
|
|
|
764
777
|
const renewed = renewFlowHook(store, trigger, now, dir);
|
|
765
778
|
return renewed.ok ? settle(renewed.said, { reveal: renewed.reveal }) : answer(409, { ok: false, said: renewed.message });
|
|
766
779
|
}
|
|
767
|
-
//
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
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 });
|
|
772
798
|
};
|
|
773
799
|
const recipeAct = (step) => async (ctx) => {
|
|
774
800
|
const { who, request, response, now, posted } = ctx;
|
|
@@ -924,6 +950,10 @@ export function createFlowsHandlers(runtime) {
|
|
|
924
950
|
"flow.trigger.press": triggerAct("press"),
|
|
925
951
|
"flow.trigger.renew": triggerAct("renew"),
|
|
926
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"),
|
|
927
957
|
"flow.trigger.share": triggerAct("share"),
|
|
928
958
|
"flow.trigger.unshare": triggerAct("unshare"),
|
|
929
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")
|