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
|
@@ -44,6 +44,7 @@ function row(stage, domain, id, method, pattern, options = {}) {
|
|
|
44
44
|
roleBrowser: options.roleBrowser ?? false,
|
|
45
45
|
...(options.roleRefusal === undefined ? {} : { roleRefusal: options.roleRefusal }),
|
|
46
46
|
...(options.bodyCap === undefined ? {} : { bodyCap: options.bodyCap }),
|
|
47
|
+
freshness: options.freshness ?? "never",
|
|
47
48
|
});
|
|
48
49
|
}
|
|
49
50
|
const edge = (domain, id, method, path, options = {}) => row("edge", domain, id, method, path.startsWith("^") ? path : exact(path), options);
|
|
@@ -65,6 +66,9 @@ const LARGE_FORM = 1024 * 1024;
|
|
|
65
66
|
// ---- edge: answered before the console's sign-in ---------------------------------------------------------------
|
|
66
67
|
// Ordered exactly as the server checks them: health and webhooks before the Host check, the rest after it.
|
|
67
68
|
edge("pages", "edge.healthz", "GET,HEAD", "/healthz", { host: "before" });
|
|
69
|
+
// Cockpit B1: the running console proves itself to this installation's CLI (a fresh challenge, answered with the
|
|
70
|
+
// instance secret and the address this connection was received on). It reveals nothing and changes nothing.
|
|
71
|
+
edge("pages", "edge.instance", "GET", "/__instance");
|
|
68
72
|
edge("remote", "edge.telegram-hook", "POST", "/hooks/telegram", { callers: ["service"], host: "before", proof: "adapter" });
|
|
69
73
|
edge("remote", "edge.flow-form", "GET,HEAD", "^\\/hooks\\/form\\/[^/]+$", { project: "flow", sample: "/hooks/form/abc", host: "before", proof: "adapter" });
|
|
70
74
|
edge("remote", "edge.flow-form-send", "POST", "^\\/hooks\\/form\\/[^/]+$", { project: "flow", sample: "/hooks/form/abc", host: "before", proof: "adapter" });
|
|
@@ -83,9 +87,9 @@ edge("remote", "edge.team-read", "GET", "/api/team", { callers: ["cookie", "bear
|
|
|
83
87
|
edge("remote", "edge.team-send", "POST", "/api/team", { callers: ["cookie", "bearer"], scope: "read", project: "adapter", proof: "adapter" });
|
|
84
88
|
edge("remote", "edge.cli", "POST", "/api/cli", { callers: ["bearer"], scope: "read", project: "adapter", proof: "adapter" });
|
|
85
89
|
edge("remote", "edge.teams", "POST", "/teams/messages", { callers: ["service"], proof: "adapter" });
|
|
86
|
-
edge("pages", "edge.browser-asset", "GET,HEAD", "^\\/assets\\/(workspace\\.js|workspace\\.css|THIRD_PARTY_NOTICES\\.txt)$", { sample: "/assets/workspace.js" });
|
|
90
|
+
edge("pages", "edge.browser-asset", "GET,HEAD", "^\\/assets\\/(workspace\\.js|workspace\\.css|terminal\\.js|terminal\\.css|THIRD_PARTY_NOTICES\\.txt)$", { sample: "/assets/workspace.js" });
|
|
87
91
|
edge("pages", "edge.workspace-style", "GET,HEAD", "^\\/assets\\/workspace-[0-9a-f]{64}\\.css$", { sample: `/assets/workspace-${"0".repeat(64)}.css` });
|
|
88
|
-
edge("pages", "edge.install-asset", "GET", "^\\/(manifest\\.webmanifest|favicon\\.ico|icon\\.svg|icon-192\\.png|icon-512\\.png|apple-touch-icon\\.png|sw\\.js)$", { sample: "/sw.js" });
|
|
92
|
+
edge("pages", "edge.install-asset", "GET", "^\\/(manifest\\.webmanifest|favicon\\.ico|icon\\.svg|icon-attention\\.svg|icon-192\\.png|icon-512\\.png|apple-touch-icon\\.png|sw\\.js)$", { sample: "/sw.js" });
|
|
89
93
|
edge("pages", "edge.font", "GET", "^\\/fonts\\/geist-(sans|mono)-(400|500|600)\\.woff2$", { sample: "/fonts/geist-sans-400.woff2" });
|
|
90
94
|
edge("pages", "edge.desktop-health", "GET", "/desktop/health");
|
|
91
95
|
edge("people", "edge.login-page", "GET", "/login");
|
|
@@ -108,6 +112,14 @@ for (const one of ["state", "changes", "ship"])
|
|
|
108
112
|
get("tasks", `code.${one}`, `^\\/code\\/${SESSION}\\/${one}$`, { ...CODING, sample: `/code/${"a".repeat(32)}/${one}` });
|
|
109
113
|
// Any other coding address answers "not found" after the same access check.
|
|
110
114
|
get("tasks", "code.other", "^\\/code\\/.*$", { ...CODING, sample: "/code/unknown" });
|
|
115
|
+
// Live terminals (docs/plans/live-terminal.md): an installation-wide operator's own, from a browser. The handlers prove
|
|
116
|
+
// the operator's generation, the terminal's owner and its project; the socket's upgrade is checked in terminals.ts.
|
|
117
|
+
const TERM = { ...free, callers: ["cookie"], project: "none", limited: "deny", ...operator("Terminals are for installation-wide operators.", "/") };
|
|
118
|
+
const TERMINAL = "[a-f0-9]{32}";
|
|
119
|
+
get("tasks", "term.page", "/term", TERM);
|
|
120
|
+
get("tasks", "term.state", "/term/state", TERM);
|
|
121
|
+
get("tasks", "term.session", `^\\/term\\/${TERMINAL}$`, { ...TERM, sample: `/term/${"a".repeat(32)}` });
|
|
122
|
+
get("tasks", "term.socket", `^\\/term\\/${TERMINAL}\\/socket$`, { ...TERM, sample: `/term/${"a".repeat(32)}/socket` });
|
|
111
123
|
get("pages", "projects.browse", "/projects/browse", free);
|
|
112
124
|
get("pages", "projects.github", "/projects/github", { ...free, callers: ["cookie"], callerRefusal: { status: 403, type: "text/plain; charset=utf-8", body: "listing repositories feeds a browser session's act" },
|
|
113
125
|
...anyApprover("your login can watch — adding projects is an approver's act") });
|
|
@@ -211,8 +223,14 @@ for (const one of ["approve", "change", "revise", "complete"])
|
|
|
211
223
|
post("settings", "provider.resume", "^\\/providers\\/[a-z]+\\/resume$", { sample: "/providers/codex/resume" });
|
|
212
224
|
post("settings", "settings.updates-dismiss", "/settings/updates/dismiss");
|
|
213
225
|
post("settings", "settings.updates-checks", "/settings/updates/checks");
|
|
226
|
+
// Cockpit B2: Forget ends this session's remembered password now.
|
|
227
|
+
// Forget is about the caller's own session only: anyone signed in with a browser may use it (a viewer, a project-limited person).
|
|
228
|
+
post("settings", "session.forget-password", "/session/forget-password", { ...free, limited: "self", viewer: true, callers: ["cookie"] });
|
|
214
229
|
const CODING_ACT = { scope: "step-up", project: "coding", limited: "self" };
|
|
215
230
|
post("tasks", "code.start", "/code/start", CODING_ACT);
|
|
231
|
+
post("tasks", "term.start", "/term/start", { ...TERM, scope: "step-up" });
|
|
232
|
+
for (const one of ["close", "remove"])
|
|
233
|
+
post("tasks", `term.${one}`, `^\\/term\\/${TERMINAL}\\/${one}$`, { ...TERM, sample: `/term/${"a".repeat(32)}/${one}` });
|
|
216
234
|
for (const one of ["send", "stop", "resume", "recover", "continue", "ship", "answer"])
|
|
217
235
|
post("tasks", `code.${one}-send`, `^\\/code\\/${SESSION}\\/${one}$`, { ...CODING_ACT, sample: `/code/${"a".repeat(32)}/${one}` });
|
|
218
236
|
// Any other coding action answers "not found" after the same access check.
|
|
@@ -238,6 +256,9 @@ for (const one of ["save", "cards", "archive", "scripts"])
|
|
|
238
256
|
post("flows", `flow.${one}`, `^\\/flows\\/${FLOW}\\/${one}$`, { project: "flow", limited: "resource", sample: `/flows/1/${one}`, ...EDIT_FLOWS });
|
|
239
257
|
for (const one of ["linear-key", "hooks-address", "secrets"])
|
|
240
258
|
post("flows", `flow.${one}`, `^\\/flows\\/${FLOW}\\/${one}$`, { scope: "step-up", project: "flow", sample: `/flows/1/${one}`, ...EDIT_FLOWS });
|
|
259
|
+
// "Runs on its own": signing raises authority (the owner's password, a step-up); revoking is one click for any approver.
|
|
260
|
+
post("flows", "flow.unattended", `^\\/flows\\/${FLOW}\\/unattended$`, { scope: "step-up", project: "flow", sample: "/flows/1/unattended", ...EDIT_FLOWS });
|
|
261
|
+
post("flows", "flow.unattended-off", `^\\/flows\\/${FLOW}\\/unattended-off$`, { project: "flow", sample: "/flows/1/unattended-off", ...EDIT_FLOWS });
|
|
241
262
|
post("flows", "flow.triggers", `^\\/flows\\/${FLOW}\\/triggers$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers", ...EDIT_FLOWS });
|
|
242
263
|
for (const one of ["move", "decide", "cancel", "comment", "assign", "watch"])
|
|
243
264
|
post("flows", `flow.card.${one}`, `^\\/flows\\/${FLOW}\\/cards\\/${FLOW}\\/${one}$`, { project: "flow", limited: "resource", sample: `/flows/1/cards/2/${one}`, ...EDIT_FLOWS });
|
|
@@ -249,11 +270,28 @@ post("flows", "flow.trigger.check", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/
|
|
|
249
270
|
post("flows", "flow.trigger.press", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/press$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers/2/press", ...EDIT_FLOWS });
|
|
250
271
|
post("flows", "flow.trigger.renew", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/renew$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers/2/renew", ...EDIT_FLOWS });
|
|
251
272
|
post("flows", "flow.trigger.secret", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/secret$`, { scope: "step-up", project: "flow", limited: "resource", sample: "/flows/1/triggers/2/secret", ...EDIT_FLOWS });
|
|
273
|
+
// v126: a signed webhook's set-up (a new address, and a secret made here shown once) and who signs it: behind the password.
|
|
274
|
+
post("flows", "flow.trigger.setup", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/setup$`, { scope: "step-up", project: "flow", limited: "resource", sample: "/flows/1/triggers/2/setup", ...EDIT_FLOWS });
|
|
275
|
+
// v126: clear a webhook's turned-away count once it has been seen.
|
|
276
|
+
post("flows", "flow.trigger.dropped", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/dropped$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers/2/dropped", ...EDIT_FLOWS });
|
|
277
|
+
// v126: which project a Sentry or Vercel webhook takes deliveries from: behind the password, as its secret is.
|
|
278
|
+
post("flows", "flow.trigger.scope", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/scope$`, { scope: "step-up", project: "flow", limited: "resource", sample: "/flows/1/triggers/2/scope", ...EDIT_FLOWS });
|
|
279
|
+
post("flows", "flow.trigger.sender", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/sender$`, { scope: "step-up", project: "flow", limited: "resource", sample: "/flows/1/triggers/2/sender", ...EDIT_FLOWS });
|
|
252
280
|
post("flows", "flow.trigger.share", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/share$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers/2/share", ...EDIT_FLOWS });
|
|
253
281
|
post("flows", "flow.trigger.unshare", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/unshare$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers/2/unshare", ...EDIT_FLOWS });
|
|
254
282
|
post("settings", "spend.budget", "/spend/budget", { scope: "step-up", ...operator("An instance operator sets budgets.", "/spend") });
|
|
255
283
|
post("settings", "settings.project-concurrency", "/settings/project/concurrency");
|
|
284
|
+
post("settings", "settings.project-live-view", "/settings/project/live-view", { scope: "step-up", callers: ["cookie"] });
|
|
285
|
+
post("settings", "settings.project-network", "/settings/project/network", { scope: "step-up", callers: ["cookie"] });
|
|
256
286
|
post("settings", "settings.project-delete", "/settings/project/delete", { scope: "step-up", ...operator("An instance operator deletes projects.", "/settings/project") });
|
|
287
|
+
// Lead sweeps (docs/plans/lead-fan-out.md): the table, and cancelling what isn't ready. The handler shows a sweep only in
|
|
288
|
+
// a project the reader can see, and acts only for an approver.
|
|
289
|
+
get("tasks", "sweep.page", "^\\/sweeps\\/[1-9][0-9]{0,9}$", { ...free, limited: "unscoped", sample: "/sweeps/1" });
|
|
290
|
+
post("tasks", "sweep.cancel-rest", "^\\/sweeps\\/[1-9][0-9]{0,9}\\/cancel-rest$", { limited: "unscoped", sample: "/sweeps/1/cancel-rest", ...approver("An approver cancels a sweep's work.", "/chat") });
|
|
291
|
+
// Passwordless (docs/plans/passwordless.md): an operator's installation setting. Turning it on takes a typed password
|
|
292
|
+
// (the handler proves it as a credential act); turning it off is one click, so the row itself is not a step-up.
|
|
293
|
+
get("settings", "settings.passwordless", "/settings/passwordless", { ...free, ...operator("An instance operator sets Passwordless.", "/settings") });
|
|
294
|
+
post("settings", "settings.passwordless-send", "/settings/passwordless", { callers: ["cookie"], ...operator("An instance operator sets Passwordless.", "/settings") });
|
|
257
295
|
post("pages", "ledger.checkpoint", "/ledger/checkpoint", operator("An instance operator makes ledger checkpoints.", "/ledger"));
|
|
258
296
|
const SETTINGS_ACT_ROLES = {
|
|
259
297
|
policy: operator("An instance operator sets the policy.", "/settings/policy"),
|
|
@@ -325,7 +363,7 @@ post("tasks", "task.act.unhold", `^\\/t\\/${TASK}\\/unhold$`, { project: "task",
|
|
|
325
363
|
post("tasks", "task.act.requeue", `^\\/t\\/${TASK}\\/requeue$`, { project: "task", limited: "resource", sample: "/t/one/requeue" });
|
|
326
364
|
post("tasks", "task.act.cancel", `^\\/t\\/${TASK}\\/cancel$`, { project: "task", limited: "resource", sample: "/t/one/cancel" });
|
|
327
365
|
post("tasks", "task.act.scope", `^\\/t\\/${TASK}\\/scope$`, { project: "task", limited: "resource", sample: "/t/one/scope", bodyCap: LARGE_FORM });
|
|
328
|
-
post("tasks", "task.act.approve", `^\\/t\\/${TASK}\\/approve$`, { scope: "step-up", project: "task", limited: "resource", sample: "/t/one/approve" });
|
|
366
|
+
post("tasks", "task.act.approve", `^\\/t\\/${TASK}\\/approve$`, { scope: "step-up", project: "task", limited: "resource", sample: "/t/one/approve", freshness: "allowlist" });
|
|
329
367
|
post("tasks", "task.act.plan", `^\\/t\\/${TASK}\\/plan$`, { project: "task", limited: "resource", sample: "/t/one/plan" });
|
|
330
368
|
post("tasks", "task.act.plan-edit", `^\\/t\\/${TASK}\\/plan-edit$`, { project: "task", limited: "resource", sample: "/t/one/plan-edit" });
|
|
331
369
|
post("tasks", "task.act.next", `^\\/t\\/${TASK}\\/next$`, { project: "task", limited: "resource", sample: "/t/one/next" });
|
|
@@ -358,11 +396,15 @@ for (const one of ["identity", "about", "promise/cancel", "on"])
|
|
|
358
396
|
post("chat", "chat.config", "/chat/config", { scope: "step-up" });
|
|
359
397
|
for (const one of ["mint", "follow", "end", "stop"])
|
|
360
398
|
post("chat", `chat.mate-${one}`, `/chat/mate/${one}`, { callers: ["cookie"], ...(one === "mint" ? { scope: "step-up" } : {}) });
|
|
399
|
+
// Remove a message still waiting in the person's lead queue (docs/plans/lead-message-queue.md): their own, while it waits.
|
|
400
|
+
post("chat", "chat.queued-remove", "^\\/chat\\/queued\\/[1-9][0-9]{0,14}\\/remove$", { callers: ["cookie"], sample: "/chat/queued/1/remove" });
|
|
401
|
+
// Confirming a proposal may approve a scope (scope_approve): that one act takes the same standard-approval freshness as
|
|
402
|
+
// /t/:id/approve (chat-actions.ts scopes it); every other operation it confirms still needs a password typed now.
|
|
361
403
|
for (const one of ["confirm", "dismiss"])
|
|
362
|
-
post("chat", `chat.proposal.${one}`, `^\\/chat\\/proposal\\/${ID}\\/${one}$`, { project: "proposal", limited: "proposal", sample: `/chat/proposal/1/${one}
|
|
404
|
+
post("chat", `chat.proposal.${one}`, `^\\/chat\\/proposal\\/${ID}\\/${one}$`, { project: "proposal", limited: "proposal", sample: `/chat/proposal/1/${one}`, ...(one === "confirm" ? { freshness: "allowlist" } : {}) });
|
|
363
405
|
for (const one of ["confirm", "dismiss"])
|
|
364
406
|
post("chat", `coordinator.proposal.${one}`, `^\\/proposals\\/${ID}\\/${one}$`, { sample: `/proposals/1/${one}` });
|
|
365
|
-
post("chat", "chat.send", "/chat", { scope: "step-up", project: "conversation" });
|
|
407
|
+
post("chat", "chat.send", "/chat", { scope: "step-up", project: "conversation", freshness: "allowlist" });
|
|
366
408
|
post("chat", "chat.file", "^\\/chat\\/file\\/[0-9a-f]{32}$", { scope: "step-up", sample: `/chat/file/${"a".repeat(32)}` });
|
|
367
409
|
post("chat", "chat.ack-send", `^\\/chat\\/ack\\/${ID}$`, { scope: "step-up", sample: "/chat/ack/1" });
|
|
368
410
|
for (const one of ["prepare", "preview", "import", "save"])
|
|
@@ -370,6 +412,8 @@ for (const one of ["prepare", "preview", "import", "save"])
|
|
|
370
412
|
post("flows", "recipes.launch-send", "/recipes/launch", { limited: "collection" });
|
|
371
413
|
for (const one of ["note", "comment", "revise", "draft-repair", "checks", "add-tests"])
|
|
372
414
|
post("tasks", `run.act.${one}`, `^\\/r\\/${ID}\\/${one}$`, { project: "run", limited: "resource", sample: `/r/1/${one}` });
|
|
415
|
+
// Cockpit E: reveal a running attempt's proved checkout in Finder (nothing is launched in it).
|
|
416
|
+
post("tasks", "run.act.reveal", `^\\/r\\/${ID}\\/reveal$`, { project: "run", limited: "resource", callers: ["cookie"], sample: "/r/1/reveal", ...approver("Only an approver can show an agent's files.") });
|
|
373
417
|
post("tasks", "session.editor-links", "/session/editor-links", { viewer: true });
|
|
374
418
|
post("tasks", "incident.resolve", `^\\/i\\/${ID}\\/resolve$`, { project: "incident", sample: "/i/1/resolve" });
|
|
375
419
|
/** The sole route table, in match order. */
|
package/dist/server/runtime.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { FreshFacts } from '../scope.js';
|
|
2
|
+
import type { createPersonalQueueRuntime } from '../lead-queue-runtime.js';
|
|
1
3
|
import type { FlowRooms } from '../flow-live.js';
|
|
2
4
|
import type { IntegrationMonitor } from '../integrations.js';
|
|
3
5
|
import type { OidcSettings } from '../oidc.js';
|
|
@@ -7,7 +9,8 @@ import type { TaskRooms } from '../task-live.js';
|
|
|
7
9
|
/** Captured server state and shared guards. Getters preserve live state across requests. */
|
|
8
10
|
import { type IncomingMessage, type ServerResponse } from "node:http";
|
|
9
11
|
import { type AssignmentSnapshot } from '../assignment.js';
|
|
10
|
-
import { type BrowserPhoneCard, type BrowserUpdates } from '../browser-workspace.js';
|
|
12
|
+
import { type BrowserPhoneCard, type BrowserResultView, type BrowserTaskTab, type BrowserUpdates } from '../browser-workspace.js';
|
|
13
|
+
import type { Html } from '../html.js';
|
|
11
14
|
import { CodingWorkspace } from '../coding-workspace.js';
|
|
12
15
|
import { type FormFieldOf, type FormView } from "../contracts/console-api.js";
|
|
13
16
|
import type { DemoLead } from "../demo.js";
|
|
@@ -93,7 +96,7 @@ export interface ServerRuntime {
|
|
|
93
96
|
repos: string[];
|
|
94
97
|
roots: readonly string[];
|
|
95
98
|
};
|
|
96
|
-
taskScreen: (response: ServerResponse, who: Who, taskId: string, problem: string | null, status: number, scopeDraft?: URLSearchParams, cancelDraft?: string, editPlan?: boolean) => void;
|
|
99
|
+
taskScreen: (response: ServerResponse, who: Who, taskId: string, problem: string | null, status: number, scopeDraft?: URLSearchParams, cancelDraft?: string, editPlan?: boolean, page?: TaskPageExtras) => void;
|
|
97
100
|
runVisible: (run: Run) => boolean;
|
|
98
101
|
runIsLive: (run: Pick<Run, "outcome" | "leaseId" | "taskRef">) => boolean;
|
|
99
102
|
options: ServeOptions;
|
|
@@ -107,7 +110,7 @@ export interface ServerRuntime {
|
|
|
107
110
|
consoleProjects: () => string[];
|
|
108
111
|
flowRooms: FlowRooms;
|
|
109
112
|
providerHome: string;
|
|
110
|
-
toolHome: string;
|
|
113
|
+
toolHome: string | undefined;
|
|
111
114
|
leadPrincipal: (who: Who & {
|
|
112
115
|
via: "cookie";
|
|
113
116
|
}) => VerifiedApprover | null;
|
|
@@ -134,6 +137,8 @@ export interface ServerRuntime {
|
|
|
134
137
|
generation: number;
|
|
135
138
|
}, csrf: string) => TeamResponse;
|
|
136
139
|
team: ReturnType<typeof createTeamRuntime>;
|
|
140
|
+
/** The personal lead queue (lead-queue-runtime.ts): a message sent while the person's lead is busy waits here. */
|
|
141
|
+
personalQueue: ReturnType<typeof createPersonalQueueRuntime>;
|
|
137
142
|
teamChatProvider: TeamChatProviderResolver;
|
|
138
143
|
pullRequestTargetOf: (runId: number) => string | null;
|
|
139
144
|
chatEnablement: () => ChatEnablement;
|
|
@@ -176,6 +181,8 @@ export interface ServerRuntime {
|
|
|
176
181
|
settingsUpdates: (actor: string, csrf: string) => BrowserUpdates | null;
|
|
177
182
|
phoneSetup: (who: Who) => BrowserPhoneCard | undefined;
|
|
178
183
|
authenticateApprover: (who: Who, token: string, repo?: string | null) => ReturnType<typeof checkApproverPassword>;
|
|
184
|
+
/** A credential act (passwordless.md's closed list): a password typed now, never a session or a remembered one. */
|
|
185
|
+
authenticateCredential: (who: Who, token: string, repo?: string | null) => ReturnType<typeof checkApproverPassword>;
|
|
179
186
|
projectOf: (who: Who, request: IncomingMessage) => string | null | undefined;
|
|
180
187
|
bustBadge: () => void;
|
|
181
188
|
revisionDestination: (child: string, back: string) => string;
|
|
@@ -223,6 +230,8 @@ export interface ServerRuntime {
|
|
|
223
230
|
}>;
|
|
224
231
|
linkKey: (code: string) => string;
|
|
225
232
|
fromThisComputer: (request: IncomingMessage) => boolean;
|
|
233
|
+
/** Cockpit B2: the request's live password-freshness facts (null off a browser session). */
|
|
234
|
+
freshFactsOf: (request: IncomingMessage, who: Who | null) => FreshFacts | null;
|
|
226
235
|
passwordAllowed: (name: string) => boolean;
|
|
227
236
|
arrival: (request: IncomingMessage) => {
|
|
228
237
|
agent: string | null;
|
|
@@ -236,6 +245,11 @@ export interface ServerRuntime {
|
|
|
236
245
|
intent: SsoIntent;
|
|
237
246
|
returnTo: string;
|
|
238
247
|
expires: number;
|
|
248
|
+
startedAt: number;
|
|
249
|
+
session: {
|
|
250
|
+
hash: string;
|
|
251
|
+
generation: number;
|
|
252
|
+
} | null;
|
|
239
253
|
}>;
|
|
240
254
|
ssoHandoffs: Map<string, {
|
|
241
255
|
claims: OidcClaims;
|
|
@@ -243,6 +257,11 @@ export interface ServerRuntime {
|
|
|
243
257
|
intent: SsoIntent;
|
|
244
258
|
returnTo: string;
|
|
245
259
|
expires: number;
|
|
260
|
+
checkedAt: number | null;
|
|
261
|
+
session: {
|
|
262
|
+
hash: string;
|
|
263
|
+
generation: number;
|
|
264
|
+
} | null;
|
|
246
265
|
}>;
|
|
247
266
|
joinSourceOf: (request: IncomingMessage) => string;
|
|
248
267
|
signInBudget: SourceBudget;
|
|
@@ -286,3 +305,15 @@ export interface ServerRuntime {
|
|
|
286
305
|
reading: boolean;
|
|
287
306
|
}>;
|
|
288
307
|
}
|
|
308
|
+
/** What the task page's GET adds (docs/plans/one-page-task-and-agent.md): the tab its address names, and the Result tab's
|
|
309
|
+
* selected result (its no-script HTML and its view), whether it is the task's current one. Refusals re-render without. */
|
|
310
|
+
export type TaskPageExtras = {
|
|
311
|
+
tab?: BrowserTaskTab;
|
|
312
|
+
result?: {
|
|
313
|
+
html: Html;
|
|
314
|
+
selected: NonNullable<BrowserResultView["selected"]>;
|
|
315
|
+
current: boolean;
|
|
316
|
+
} | null;
|
|
317
|
+
/** The result runs the attempt picker may offer (the Result tab would show each); absent: every finished result run. */
|
|
318
|
+
admissible?: ReadonlySet<number>;
|
|
319
|
+
};
|
package/dist/server/session.d.ts
CHANGED
|
@@ -14,6 +14,11 @@ export declare const SIGN_IN_LINK_MS: number;
|
|
|
14
14
|
/** A cookie idles out after half a day and dies outright after a week. */
|
|
15
15
|
export declare const SESSION_IDLE_MS: number;
|
|
16
16
|
export declare const SESSION_ABSOLUTE_MS: number;
|
|
17
|
+
/** A session's lifetime, the one reading: within its idle and absolute limits (sign-in lookup and every act use it). */
|
|
18
|
+
export declare const sessionWithinLifetime: (session: {
|
|
19
|
+
lastSeen: number;
|
|
20
|
+
createdAt: number;
|
|
21
|
+
}, now: number) => boolean;
|
|
17
22
|
/** An approval nonce is a rendered form, not a standing right — it ages out fast. */
|
|
18
23
|
export declare const NONCE_TTL_MS: number;
|
|
19
24
|
export declare const NONCE_CAP = 500;
|
|
@@ -37,6 +42,15 @@ export type Session = {
|
|
|
37
42
|
sso?: {
|
|
38
43
|
at: number;
|
|
39
44
|
};
|
|
45
|
+
/** Cockpit B2: when a password was last verified on this session (in memory only: a restart forgets it). */
|
|
46
|
+
passwordAt?: number;
|
|
47
|
+
/** Cockpit B2: when the identity provider last re-checked this person on request (a step-up's re-authentication).
|
|
48
|
+
* Only this, or a password, starts a freshness window; an ordinary sign-in never does. In memory only. */
|
|
49
|
+
ssoReauthAt?: number;
|
|
50
|
+
/** Cockpit B2: an explicit provider re-check not yet spent: stands in for ONE step-up (consumed by it). In memory only. */
|
|
51
|
+
ssoNowAt?: number;
|
|
52
|
+
/** Forget: nothing verified before this counts as fresh. */
|
|
53
|
+
freshFloor?: number;
|
|
40
54
|
/** v101: the browser and address it signed in from, for the person to recognise it. */
|
|
41
55
|
agent?: string | null;
|
|
42
56
|
address?: string | null;
|
|
@@ -121,12 +135,22 @@ export declare class PersistentSessions extends Map<string, Session> {
|
|
|
121
135
|
static hash(id: string): string;
|
|
122
136
|
set(id: string, session: Session): this;
|
|
123
137
|
delete(id: string): boolean;
|
|
124
|
-
/**
|
|
138
|
+
/**
|
|
139
|
+
* Keep a changed session (its project, its provider check); `seen` only once a minute. Only ever an update: a session
|
|
140
|
+
* ended elsewhere (another process, Sessions) is dropped here, never written back by its cached copy (live terminals
|
|
141
|
+
* review, rounds 2–3, finding 1).
|
|
142
|
+
*/
|
|
125
143
|
persist(session: Session, seen?: boolean): void;
|
|
144
|
+
/** Whether this exact session is still saved in the database (ended from another process, it isn't): read at the act. */
|
|
145
|
+
savedNow(session: Session): boolean;
|
|
146
|
+
/** Whether this cookie's session is still saved (one ended in another process is not). */
|
|
147
|
+
stillSaved(id: string): boolean;
|
|
126
148
|
/** A cookie from before a restart, if its session is still kept. */
|
|
127
149
|
load(id: string): Session | null;
|
|
128
150
|
/** End one session by its kept hash (from the Sessions page). */
|
|
129
151
|
endByHash(idHash: string): void;
|
|
152
|
+
/** Whether this exact session is still signed in here (not signed out, ended from Sessions, expired or replaced). */
|
|
153
|
+
holds(session: Session): boolean;
|
|
130
154
|
hashOf(session: Session): string | null;
|
|
131
155
|
}
|
|
132
156
|
/** The console's server, and the one-time sign-in link `up` opens: a path on this server, or null for no such approver.
|
package/dist/server/session.js
CHANGED
|
@@ -26,6 +26,8 @@ export const SIGN_IN_LINK_MS = 10 * 60_000;
|
|
|
26
26
|
/** A cookie idles out after half a day and dies outright after a week. */
|
|
27
27
|
export const SESSION_IDLE_MS = 12 * 60 * 60_000;
|
|
28
28
|
export const SESSION_ABSOLUTE_MS = 7 * 24 * 60 * 60_000;
|
|
29
|
+
/** A session's lifetime, the one reading: within its idle and absolute limits (sign-in lookup and every act use it). */
|
|
30
|
+
export const sessionWithinLifetime = (session, now) => now - session.lastSeen <= SESSION_IDLE_MS && now - session.createdAt <= SESSION_ABSOLUTE_MS;
|
|
29
31
|
/** An approval nonce is a rendered form, not a standing right — it ages out fast. */
|
|
30
32
|
export const NONCE_TTL_MS = 15 * 60_000;
|
|
31
33
|
export const NONCE_CAP = 500;
|
|
@@ -55,22 +57,32 @@ export class PersistentSessions extends Map {
|
|
|
55
57
|
set(id, session) {
|
|
56
58
|
super.set(id, session);
|
|
57
59
|
this.ids.set(session, id);
|
|
58
|
-
this.
|
|
60
|
+
this.savedAt.set(session, Date.now());
|
|
61
|
+
this.db.saveWebSession({ idHash: PersistentSessions.hash(id), account: session.name, csrf: session.csrf, role: session.role, generation: session.generation, createdAt: session.createdAt,
|
|
62
|
+
lastSeen: session.lastSeen, project: session.project, projectRevision: session.projectRevision, ssoAt: session.sso?.at ?? null, agent: session.agent ?? null, address: session.address ?? null });
|
|
59
63
|
return this;
|
|
60
64
|
}
|
|
61
65
|
delete(id) {
|
|
62
66
|
this.db.dropWebSession(PersistentSessions.hash(id));
|
|
63
67
|
return super.delete(id);
|
|
64
68
|
}
|
|
65
|
-
/**
|
|
69
|
+
/**
|
|
70
|
+
* Keep a changed session (its project, its provider check); `seen` only once a minute. Only ever an update: a session
|
|
71
|
+
* ended elsewhere (another process, Sessions) is dropped here, never written back by its cached copy (live terminals
|
|
72
|
+
* review, rounds 2–3, finding 1).
|
|
73
|
+
*/
|
|
66
74
|
persist(session, seen = false) {
|
|
67
75
|
const id = this.ids.get(session);
|
|
68
76
|
if (id === undefined || (seen && Date.now() - (this.savedAt.get(session) ?? 0) < 60_000))
|
|
69
77
|
return;
|
|
70
78
|
this.savedAt.set(session, Date.now());
|
|
71
|
-
this.db.
|
|
72
|
-
|
|
79
|
+
if (!this.db.updateWebSession({ idHash: PersistentSessions.hash(id), lastSeen: session.lastSeen, project: session.project, projectRevision: session.projectRevision, ssoAt: session.sso?.at ?? null }))
|
|
80
|
+
super.delete(id);
|
|
73
81
|
}
|
|
82
|
+
/** Whether this exact session is still saved in the database (ended from another process, it isn't): read at the act. */
|
|
83
|
+
savedNow(session) { const hash = this.hashOf(session); return hash !== null && this.db.webSession(hash) !== null; }
|
|
84
|
+
/** Whether this cookie's session is still saved (one ended in another process is not). */
|
|
85
|
+
stillSaved(id) { return this.db.webSession(PersistentSessions.hash(id)) !== null; }
|
|
74
86
|
/** A cookie from before a restart, if its session is still kept. */
|
|
75
87
|
load(id) {
|
|
76
88
|
const row = this.db.webSession(PersistentSessions.hash(id));
|
|
@@ -90,5 +102,7 @@ export class PersistentSessions extends Map {
|
|
|
90
102
|
super.delete(id);
|
|
91
103
|
this.db.dropWebSession(idHash);
|
|
92
104
|
}
|
|
105
|
+
/** Whether this exact session is still signed in here (not signed out, ended from Sessions, expired or replaced). */
|
|
106
|
+
holds(session) { const id = this.ids.get(session); return id !== undefined && super.get(id) === session; }
|
|
93
107
|
hashOf(session) { const id = this.ids.get(session); return id === undefined ? null : PersistentSessions.hash(id); }
|
|
94
108
|
}
|