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/serve.js
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
|
+
import { taskMovedSince } from "./chat-ask.js";
|
|
2
|
+
import { LIVE_VIEW_CONSENT, liveCommandsShown, liveViewConsentDigest } from "./live-detail.js";
|
|
1
3
|
import { html, htmlString, jsonScript, textHtml } from "./html.js";
|
|
2
4
|
import { browserAssetsAvailable, browserWorkspaceDocument } from './browser-shell.js';
|
|
3
|
-
import {
|
|
5
|
+
import { NO_COUNTS, projectStates, STATE_WORDS } from './agent-state.js';
|
|
6
|
+
import { usageChipOf } from './limits-ui.js';
|
|
7
|
+
/** Tasks the rail lists under a project; the rest are a count that links to the project's tasks. */
|
|
8
|
+
const RAIL_TASKS = 6;
|
|
9
|
+
/** Any header a proxy adds: a request carrying one is never "from this computer" (password freshness, one-time links). */
|
|
10
|
+
const PROXY_HEADER = /^(forwarded|via|x-forwarded-.+|x-real-ip|x-client-ip|x-original-.+|x-cluster-client-ip|true-client-ip|cf-connecting-ip|fastly-client-ip|x-proxyuser-ip)$/i;
|
|
11
|
+
/** Asks a project names for the chime (its count is complete beyond them). */
|
|
12
|
+
const ASKS_PER_PROJECT = 100;
|
|
13
|
+
import { boundProjectSummary, browserCrewFromIndex, browserCrewOf, browserNavigationOf, browserProjectsOf, needsYouLabelOf } from './browser-workspace.js';
|
|
4
14
|
import { sharedActionPayload } from './chat-actions.js';
|
|
5
15
|
import { prepareCodingContext } from './coding-context.js';
|
|
6
16
|
import { CodingWorkspace } from './coding-workspace.js';
|
|
@@ -14,7 +24,7 @@ import { startMaintenance } from './maintenance.js';
|
|
|
14
24
|
import { connectionsOf, localConnectOf, oneClickOf } from "./mcp-connect.js";
|
|
15
25
|
import { MOBILE_VIEWPORT_SCRIPT } from "./mobile-viewport.js";
|
|
16
26
|
import { projectAuthority } from "./project-access.js";
|
|
17
|
-
import { discoverTools, projectToolsOf, secretsSetFor, TOOL_CATALOG, toolStanding } from "./project-tools.js";
|
|
27
|
+
import { discoverTools, projectToolsOf, secretsSetFor, TOOL_CATALOG, toolSecretStorage, toolStanding } from "./project-tools.js";
|
|
18
28
|
import { toolRefusal } from "./policy.js";
|
|
19
29
|
import { isEdgeAddress, refuseEdge } from "./server/edge-refusal.js";
|
|
20
30
|
import { announcePasswordBearer, isPasswordBearer, PASSWORD_BEARER_REFUSED, passwordBearerAccepted } from "./password-bearer.js";
|
|
@@ -28,8 +38,9 @@ import { createTaskViews } from "./server/task-views.js";
|
|
|
28
38
|
import { runActivityOf } from "./task-activity.js";
|
|
29
39
|
import { createTaskRooms, taskFingerprint } from "./task-live.js";
|
|
30
40
|
import { createTeamRuntime } from './team-runtime.js';
|
|
41
|
+
import { createPersonalQueueRuntime } from './lead-queue-runtime.js';
|
|
31
42
|
import {} from "./tools-ui.js";
|
|
32
|
-
import { workCountsByProject } from "./work-index.js";
|
|
43
|
+
import { WORK_INDEX_MAX_LIMIT, workCountsByProject, workIndexPage } from "./work-index.js";
|
|
33
44
|
import { WORKSPACE_MOTION_SCRIPT } from "./workspace-motion.js";
|
|
34
45
|
import { prepareWorkspaceRevision, WorkspaceValidatorCache } from "./workspace-revision.js";
|
|
35
46
|
/**
|
|
@@ -111,10 +122,13 @@ import { limitWords, FLUSH_MS as REQUEST_BUDGET_FLUSH_MS, RequestBudget, SOURCE_
|
|
|
111
122
|
import { REQUEST_TOKEN, RESULT_REVIEW_SCRIPT } from "./result-review.js";
|
|
112
123
|
import { buildReviewOf } from "./review-switch.js";
|
|
113
124
|
import { isAlive as runnerAlive } from "./runner.js";
|
|
114
|
-
import {
|
|
125
|
+
import { FRESH_WINDOW_MS, freshFacts, ssoNowAvailable } from "./scope.js";
|
|
115
126
|
import { instrumentRequest } from "./server-telemetry.js";
|
|
116
127
|
import { createChatHandlers } from './server/chat.js';
|
|
117
128
|
import { createLiveHandlers } from './server/live.js';
|
|
129
|
+
import { createTerminalHandlers, terminalHostFor } from './server/terminals.js';
|
|
130
|
+
import { passwordlessOn } from './passwordless.js';
|
|
131
|
+
import { createSweepHandlers } from './server/sweeps.js';
|
|
118
132
|
import { createFlowsHandlers } from './server/flows.js';
|
|
119
133
|
import { createPagesHandlers } from './server/pages.js';
|
|
120
134
|
import { createPeopleHandlers } from './server/people-tokens.js';
|
|
@@ -122,10 +136,10 @@ import { createSettingsHandlers } from './server/settings.js';
|
|
|
122
136
|
import { requestContext } from "./server/request-context.js";
|
|
123
137
|
import { chromeScript, DEMO_BANNER, DEMO_BANNER_SHORT, focusDocument, KBD_HELP, pinnedTheme, QUEUE_VIEW, refuse, screen, SENSITIVE_INPUT, shell, sidebarScript } from "./server/chrome.js";
|
|
124
138
|
import { BODY_CAP, form, loginHref, matchTaskPath, page, projectChatHref, redactedPath, redirect, respond, safeReturn, SHUTDOWN_WAIT_MS, taskChatHref } from "./server/http.js";
|
|
125
|
-
import { decisionsFor, LEAD_BY_DEFAULT_FACT, leadBrowserMessages, leadChatVersion, teamProposalCardParts } from "./server/render-chat.js";
|
|
139
|
+
import { decisionsFor, LEAD_BY_DEFAULT_FACT, leadBrowserMessages, leadChatVersion, TASK_COMPOSER_MODES, teamProposalCardParts } from "./server/render-chat.js";
|
|
126
140
|
import {} from "./server/render-pages.js";
|
|
127
141
|
import { ssoStepUps, wrongHostPage } from "./server/render-people.js";
|
|
128
|
-
import { NO_PROJECT, NO_TOUCH_FRAGMENTS, NONCE_CAP, NONCE_TTL_MS, PersistentSessions, SESSION_ABSOLUTE_MS, SESSION_IDLE_MS, SIGN_IN_LINK_MS, SIGN_IN_LINK_PATH } from "./server/session.js";
|
|
142
|
+
import { NO_PROJECT, NO_TOUCH_FRAGMENTS, NONCE_CAP, NONCE_TTL_MS, PersistentSessions, SESSION_ABSOLUTE_MS, SESSION_IDLE_MS, sessionWithinLifetime, SIGN_IN_LINK_MS, SIGN_IN_LINK_PATH } from "./server/session.js";
|
|
129
143
|
import { createTasksHandlers } from './server/tasks.js';
|
|
130
144
|
import { DEFAULT_GUARD_POLICY, passwordGuardOf, provenPasswordAccount, SourceBudget, withPasswordSource } from "./sign-in-guard.js";
|
|
131
145
|
import { sourceKey } from "./source-key.js";
|
|
@@ -156,7 +170,7 @@ export function createDecisionServer(options) {
|
|
|
156
170
|
get dockedConversation() { return dockedConversation; },
|
|
157
171
|
get liveRefreshSeconds() { return liveRefreshSeconds; },
|
|
158
172
|
});
|
|
159
|
-
const { authenticateApprover, authorizeMutation, projectOf, resolveRouteProject, projectRequestAllowed, identify, lookupSession, restricted, visible, admissionList, codingActorAllowed, codingProjectAllowed, workAccess, familyOf, taskRepoOf, runVisible } = createSharedGuards({
|
|
173
|
+
const { authenticateApprover, authenticateCredential, authorizeMutation, projectOf, resolveRouteProject, projectRequestAllowed, identify, lookupSession, restricted, visible, admissionList, codingActorAllowed, codingProjectAllowed, workAccess, familyOf, taskRepoOf, runVisible } = createSharedGuards({
|
|
160
174
|
get store() { return store; },
|
|
161
175
|
get liveCeiling() { return liveCeiling; },
|
|
162
176
|
get codingProjects() { return codingProjects; },
|
|
@@ -186,6 +200,8 @@ export function createDecisionServer(options) {
|
|
|
186
200
|
}
|
|
187
201
|
}
|
|
188
202
|
}
|
|
203
|
+
// Live terminals (docs/plans/live-terminal.md): on a machine that runs agents, when this install has a pty library.
|
|
204
|
+
const terminals = terminalHostFor(store, options);
|
|
189
205
|
const clock = options.clock ?? (() => new Date());
|
|
190
206
|
// Heavy reads (the work index behind `task list`) run on read-only worker connections, off the request loop.
|
|
191
207
|
const readFile = store.isDemo() ? null : store.databaseFile();
|
|
@@ -286,13 +302,14 @@ export function createDecisionServer(options) {
|
|
|
286
302
|
/** That source as every per-source budget counts it: a native IPv6 caller by its /64 (source-key.ts). */
|
|
287
303
|
function joinSourceOf(request) { return sourceKey(forwardedSourceOf(request)); }
|
|
288
304
|
const signInBudget = new SourceBudget();
|
|
305
|
+
// A re-check or a link is the session that started it: its kept hash and credential generation ride the visit and the
|
|
306
|
+
// hand-off, and finish refuses any other session (a sign-out and a new sign-in in between never inherit it).
|
|
289
307
|
const ssoVisits = new Map();
|
|
290
308
|
const ssoHandoffs = new Map();
|
|
291
309
|
let ssoProvider = null;
|
|
292
310
|
const ssoSettings = () => readSsoSettings(options.configDir);
|
|
293
311
|
const ssoOffer = () => { const settings = ssoSettings(); return settings === null ? null : { label: settings.label, operatorsOnly: settings.passwords === "operators" }; };
|
|
294
312
|
/** A step-up within this long of the provider checking someone needs no password. */
|
|
295
|
-
const SSO_FRESH_MS = 10 * 60_000;
|
|
296
313
|
// The one-time sign-in link `up` opens in the browser: single use, ten minutes, from this computer only. Only a hash
|
|
297
314
|
// of the code is kept, in memory; the code itself is never written to a log or the ledger.
|
|
298
315
|
const signInLinks = new Map();
|
|
@@ -310,13 +327,50 @@ export function createDecisionServer(options) {
|
|
|
310
327
|
signInLinks.set(linkKey(code), { account, expires: Date.now() + SIGN_IN_LINK_MS });
|
|
311
328
|
return `${SIGN_IN_LINK_PATH}${code}`;
|
|
312
329
|
}
|
|
313
|
-
/** This computer, and nothing in front of it: a loopback peer that names a loopback address, with no
|
|
330
|
+
/** This computer, and nothing in front of it: a loopback peer that names a loopback address, with no sign of a proxy —
|
|
331
|
+
* any forwarding header at all (Forwarded, X-Forwarded-*, X-Real-IP, Via, …) disqualifies the request. */
|
|
314
332
|
const fromThisComputer = (request) => {
|
|
315
333
|
const peer = request.socket.remoteAddress ?? "";
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
334
|
+
if (!LOOPBACK_PEERS.has(peer))
|
|
335
|
+
return false;
|
|
336
|
+
for (const name of Object.keys(request.headers))
|
|
337
|
+
if (PROXY_HEADER.test(name))
|
|
338
|
+
return false;
|
|
339
|
+
return /^(localhost|127\.0\.0\.1|\[::1\]):[0-9]{1,5}$/.test(request.headers.host ?? "");
|
|
319
340
|
};
|
|
341
|
+
/**
|
|
342
|
+
* Cockpit B2: a request's password-freshness facts — the one place they are made (scope.ts honours them). Every
|
|
343
|
+
* reader looks at the session as it is at the moment of the act, never a snapshot taken before the body arrived:
|
|
344
|
+
* still signed in here, under the account's current credential generation, after any Forget.
|
|
345
|
+
*/
|
|
346
|
+
function freshFactsOf(request, who, allowlisted = false) {
|
|
347
|
+
if (who?.via !== 'cookie')
|
|
348
|
+
return null;
|
|
349
|
+
const session = who.session, local = fromThisComputer(request);
|
|
350
|
+
// The same validity lookupSession applies, read again at the act: held here, within its idle and absolute
|
|
351
|
+
// lifetimes, under the account's current credential generation, not revoked.
|
|
352
|
+
const live = () => {
|
|
353
|
+
const now = Date.now(), account = store.accountOf(who.name);
|
|
354
|
+
return sessions.holds(session) && sessions.savedNow(session) && sessionWithinLifetime(session, now)
|
|
355
|
+
&& account !== null && account.revokedAt === null && account.generation === session.generation;
|
|
356
|
+
};
|
|
357
|
+
return { actor: who.name, local, allowlisted, live,
|
|
358
|
+
passwordAt: () => session.passwordAt ?? null, ssoReauthAt: () => session.ssoReauthAt ?? null,
|
|
359
|
+
ssoNow: () => session.ssoNowAt ?? null,
|
|
360
|
+
consumeSsoNow: () => {
|
|
361
|
+
const was = session.ssoNowAt;
|
|
362
|
+
delete session.ssoNowAt;
|
|
363
|
+
// A refund puts back only what this spend took, once, and only if nothing newer arrived (Forget still floors it).
|
|
364
|
+
let refunded = false;
|
|
365
|
+
return () => { if (refunded)
|
|
366
|
+
return; refunded = true; if (was !== undefined && session.ssoNowAt === undefined)
|
|
367
|
+
session.ssoNowAt = was; };
|
|
368
|
+
},
|
|
369
|
+
floor: () => session.freshFloor ?? 0,
|
|
370
|
+
// Only a direct local request may start a password window (a typed password elsewhere proves now, nothing more).
|
|
371
|
+
stamp: at => { if (local && live())
|
|
372
|
+
session.passwordAt = at; } };
|
|
373
|
+
}
|
|
320
374
|
async function providerFor(issuer) {
|
|
321
375
|
if (ssoProvider !== null && ssoProvider.issuer === issuer && Date.now() - ssoProvider.at < 10 * 60_000)
|
|
322
376
|
return { ok: true, provider: ssoProvider.provider };
|
|
@@ -392,6 +446,13 @@ export function createDecisionServer(options) {
|
|
|
392
446
|
canDelete: who.via === "cookie" && store.isInstanceOperator(who.name),
|
|
393
447
|
builds: projectBuildsOf(repo, who),
|
|
394
448
|
access: projectAccessOf(repo),
|
|
449
|
+
network: { setting: store.projectNetwork(repo), canChange: who.via === "cookie" && who.role === "approver" && !store.isDemo() && store.accountCanAccess(who.name, repo) },
|
|
450
|
+
liveView: (() => {
|
|
451
|
+
const commands = liveCommandsShown(store, repo);
|
|
452
|
+
const canChange = who.via === "cookie" && who.role === "approver" && !store.isDemo() && store.accountCanAccess(who.name, repo);
|
|
453
|
+
// Showing commands is consent to one exact change: a single-use nonce for this person, project and terms.
|
|
454
|
+
return { commands, canChange, ...(canChange && !commands ? { nonce: mintApprovalNonce(who.name, LIVE_VIEW_CONSENT, liveViewConsentDigest(repo)) } : {}) };
|
|
455
|
+
})(),
|
|
395
456
|
});
|
|
396
457
|
/** Settings → Project → Build access: the project's named tools and whether each loads for its builds. */
|
|
397
458
|
const projectAccessOf = (repo) => {
|
|
@@ -434,17 +495,25 @@ export function createDecisionServer(options) {
|
|
|
434
495
|
}
|
|
435
496
|
};
|
|
436
497
|
/** One project's Tools page: its tools with which secrets are set (never values), the common tools not yet added, and what this computer already has. */
|
|
437
|
-
|
|
498
|
+
// A test's own folder for tool secrets and found tools; Toolroll's own (beside its database, and this home) otherwise.
|
|
499
|
+
const toolHome = options.toolHome;
|
|
438
500
|
const toolsViewOf = (repo, codex, kit = null, wanted = null) => {
|
|
439
501
|
const tools = projectToolsOf(store, repo);
|
|
440
502
|
const names = new Set(tools.map(one => one.name));
|
|
441
503
|
return {
|
|
442
504
|
repo, project: repo.split(/[\\/]/).filter(Boolean).at(-1) ?? repo,
|
|
443
|
-
|
|
505
|
+
// Secrets that can't be read (a refused folder, a locked keychain) read as none set: the page says why once (storage).
|
|
506
|
+
tools: tools.map(tool => { let secretsSet = []; try {
|
|
507
|
+
secretsSet = secretsSetFor(repo, tool.spec, toolHome);
|
|
508
|
+
}
|
|
509
|
+
catch {
|
|
510
|
+
secretsSet = [];
|
|
511
|
+
} return { tool, secretsSet }; }),
|
|
444
512
|
// A service that connects by signing in (or an app's own Connect) is offered only that way.
|
|
445
513
|
catalog: TOOL_CATALOG.filter(one => !names.has(one.name) && oneClickOf(one.name) === null && localConnectOf(one.name) === null),
|
|
446
514
|
connections: connectionsOf(store, repo, toolHome), kit, wanted,
|
|
447
515
|
found: discoverTools(repo, codex, toolHome).filter(one => !names.has(one.spec.name)),
|
|
516
|
+
storage: toolSecretStorage(),
|
|
448
517
|
};
|
|
449
518
|
};
|
|
450
519
|
const codingProjects = () => [...new Set([...managedRepos(), ...store.listProjects().map(project => project.path)])].filter(repo => rowVisible(liveCeiling(), repo));
|
|
@@ -466,6 +535,32 @@ export function createDecisionServer(options) {
|
|
|
466
535
|
provider: teamChatProvider,
|
|
467
536
|
...(options.subscriptionChatRunner ? { subscriptionRunner: options.subscriptionChatRunner } : {}),
|
|
468
537
|
});
|
|
538
|
+
// The personal lead queue (docs/plans/lead-message-queue.md). Projects and a message's task are re-proved AS the
|
|
539
|
+
// person (never from a live request's context), and its live reply display is the console's own (chat handlers below,
|
|
540
|
+
// reached only once the server is listening).
|
|
541
|
+
const asActor = (account, work) => requestContext.run({ csrf: "", returnTo: "/", actor: account }, work);
|
|
542
|
+
const admittedReposFor = (account) => asActor(account, () => managedRepos());
|
|
543
|
+
const personalQueue = createPersonalQueueRuntime({
|
|
544
|
+
store, evidenceRoot, clock, provider: teamChatProvider, admittedReposFor,
|
|
545
|
+
contextStands: (account, context) => asActor(account, () => {
|
|
546
|
+
const admitted = managedRepos();
|
|
547
|
+
if (context.project !== undefined && !admitted.includes(context.project))
|
|
548
|
+
return false;
|
|
549
|
+
if (context.task === undefined)
|
|
550
|
+
return true;
|
|
551
|
+
const family = store.taskFamilyOf(context.task, admitted, false);
|
|
552
|
+
if (family === null)
|
|
553
|
+
return false;
|
|
554
|
+
if (context.result === undefined)
|
|
555
|
+
return true;
|
|
556
|
+
const run = store.getRun(context.result);
|
|
557
|
+
return run !== null && family.versions.some(one => one.refId === run.taskRef);
|
|
558
|
+
}),
|
|
559
|
+
names: { project: projectName, modes: TASK_COMPOSER_MODES },
|
|
560
|
+
live: { begin: thread => chatHandlers.beginLiveTurn(thread), end: (thread, ok, progress) => chatHandlers.endLiveTurn(thread, ok, progress) },
|
|
561
|
+
...(options.chatFetcher ? { fetcher: options.chatFetcher } : {}),
|
|
562
|
+
...(options.subscriptionChatRunner ? { subscriptionRunner: options.subscriptionChatRunner } : {}),
|
|
563
|
+
});
|
|
469
564
|
// Only browser sessions receive HTML cards. The CLI keeps its summary contract.
|
|
470
565
|
const teamBrowserReply = (reply, actor, csrf) => {
|
|
471
566
|
const snapshot = reply.snapshot;
|
|
@@ -668,7 +763,7 @@ export function createDecisionServer(options) {
|
|
|
668
763
|
// Passive polls authenticate without extending the browser session.
|
|
669
764
|
const fragmentName = url.searchParams.get("fragment");
|
|
670
765
|
const workspaceRead = method === "GET" && url.searchParams.get('format') === 'workspace';
|
|
671
|
-
const touch = !(method === "GET" && (edgeRoute?.domain === 'live' || workspaceRead || (fragmentName !== null && NO_TOUCH_FRAGMENTS.has(fragmentName)) || /^\/code\/[a-f0-9]{32}\/state$/.test(url.pathname)));
|
|
766
|
+
const touch = !(method === "GET" && (edgeRoute?.domain === 'live' || workspaceRead || (fragmentName !== null && NO_TOUCH_FRAGMENTS.has(fragmentName)) || /^\/code\/[a-f0-9]{32}\/state$/.test(url.pathname) || url.pathname === '/term/state'));
|
|
672
767
|
const refuseBudget = (admitted) => {
|
|
673
768
|
if (admitted.ok)
|
|
674
769
|
return false;
|
|
@@ -694,8 +789,9 @@ export function createDecisionServer(options) {
|
|
|
694
789
|
if (who?.via === "bearer" && refuseBudget(admitBearer(request, who)))
|
|
695
790
|
return;
|
|
696
791
|
const passwordProof = who?.via === 'cookie' ? { name: who.name, generation: who.session.generation } : null;
|
|
792
|
+
const fresh = freshFactsOf(request, who, matchedRoute?.freshness === 'allowlist');
|
|
697
793
|
if (edgeRoute?.domain === 'people')
|
|
698
|
-
return provenPasswordAccount.run(passwordProof, () => dispatchEdge(edgeRoute, { url, who, request, response, method }));
|
|
794
|
+
return freshFacts.run(fresh, () => provenPasswordAccount.run(passwordProof, () => dispatchEdge(edgeRoute, { url, who, request, response, method })));
|
|
699
795
|
if (who === null) {
|
|
700
796
|
// A password on a request that did not sign in is a machine's refusal, never a login page.
|
|
701
797
|
if (isPasswordBearer(request.headers.authorization))
|
|
@@ -717,15 +813,18 @@ export function createDecisionServer(options) {
|
|
|
717
813
|
appOrigins: [consoleOrigin(request.headers.host), publicOrigin?.origin ?? null, options.configDir === undefined ? null : loadConsoleUrl(process.env, options.configDir)],
|
|
718
814
|
// Every signed-in page shares the workspace shell; pages showing a one-time secret opt out (forceSensitive).
|
|
719
815
|
browser: who.via === 'cookie',
|
|
816
|
+
// Passwordless (read once for rendering; every act proves it again at the act): ceremony fields render as nothing.
|
|
817
|
+
passwordless: who.via === 'cookie' && passwordlessOn(store),
|
|
720
818
|
// v100: signed in with the identity provider: its label, and whether it checked them recently enough to stand in for a password.
|
|
721
|
-
|
|
819
|
+
// Cockpit B2: "fresh" is an explicit re-check made on request and not yet spent (read live; one step-up uses it).
|
|
820
|
+
sso: who.via === 'cookie' && who.session.sso !== undefined ? { label: ssoSettings()?.label ?? "your identity provider",
|
|
821
|
+
get fresh() { return freshFacts.getStore() != null && ssoNowAvailable(who.name); } } : undefined,
|
|
722
822
|
refusal: (answer, status, body) => sendScreen(answer, status, screen(status === 404 ? "Not found" : "Request refused", body, { chrome: chromeFor(who.via === "cookie" ? who.session.project : null, "work") })),
|
|
723
823
|
workspaceRead,
|
|
724
824
|
workspaceRequest: url.searchParams.get('request'),
|
|
725
825
|
};
|
|
726
|
-
// An empty password stands for a fresh identity-provider sign-in, for this person only (v100).
|
|
727
826
|
if (method === "GET" || method === "POST")
|
|
728
|
-
return
|
|
827
|
+
return freshFacts.run(fresh, () => provenPasswordAccount.run(passwordProof, () => requestContext.run(requestFacts, async () => {
|
|
729
828
|
if (edgeRoute?.domain === 'live')
|
|
730
829
|
return dispatchEdge(edgeRoute, { url, who, request, response, method });
|
|
731
830
|
const body = method === "POST" ? await form(request, (matchedRoute?.stage === "console" ? matchedRoute.bodyCap : undefined) ?? BODY_CAP) : null;
|
|
@@ -847,7 +946,7 @@ export function createDecisionServer(options) {
|
|
|
847
946
|
const ref = run === null ? null : store.refForId(run.taskRef);
|
|
848
947
|
return { repo: ref?.repo ?? null, taskId: ref?.externalId ?? null, runId, action: `${resource[1] === "d" ? "decision" : resource[1] === "i" ? "incident" : "run"} ${resource[3] ?? "view"}` };
|
|
849
948
|
}
|
|
850
|
-
const known = new Set(["/recipes/prepare", "/recipes/preview", "/recipes/import", "/recipes/save", "/recipes/launch", "/tasks/add", "/queue/move", "/queue/note", "/people/invite", "/people/invite-revoke", "/people/revoke", "/people/projects", "/projects/select", "/projects/open", "/mode/confirm", "/mode/sign", "/mode/revoke", "/settings/chat-approval/save", "/settings/chat-approval/off"]);
|
|
949
|
+
const known = new Set(["/recipes/prepare", "/recipes/preview", "/recipes/import", "/recipes/save", "/recipes/launch", "/tasks/add", "/queue/move", "/queue/note", "/people/invite", "/people/invite-revoke", "/people/revoke", "/people/projects", "/projects/select", "/projects/open", "/mode/confirm", "/mode/sign", "/mode/revoke", "/settings/chat-approval/save", "/settings/chat-approval/off", "/settings/project/live-view"]);
|
|
851
950
|
const placed = url.pathname === "/tasks/add" ? body?.get("repo")?.trim() : null;
|
|
852
951
|
return { repo: url.pathname.startsWith("/people/") ? null : placed ? canonicalProject(placed) ?? placed : projectOf(who, request) ?? null, taskId: null, runId: null,
|
|
853
952
|
action: known.has(url.pathname) ? url.pathname.slice(1).replaceAll("/", " ") : "console request" };
|
|
@@ -1036,9 +1135,15 @@ export function createDecisionServer(options) {
|
|
|
1036
1135
|
const messages = store.listLeadMessages(opened.thread.id, 40);
|
|
1037
1136
|
// The lead's question with buttons, while its reply is the last word in the thread.
|
|
1038
1137
|
const last = messages.at(-1);
|
|
1039
|
-
const
|
|
1138
|
+
const asking = last?.role === "assistant" && last.turn !== null && store.getLeadTurn(last.turn)?.approver === who.name ? store.leadAskOpen(last.turn, now) : null;
|
|
1139
|
+
// A question about this task loses its buttons once the version shown moved after it was asked (its plan
|
|
1140
|
+
// rewritten or approved, a new attempt started): its options answered a state that is gone. Its words stay, marked so.
|
|
1141
|
+
const staleAsk = asking !== null && focusTask !== null && taskMovedSince(store, focusTask.executionId, asking.createdAt) ? asking.turn : null;
|
|
1142
|
+
const ask = staleAsk === null ? asking : null;
|
|
1040
1143
|
// Answered questions stay readable above the answer; only their buttons go.
|
|
1041
1144
|
const asks = new Map(messages.flatMap(one => { const asked = one.role === "assistant" && one.turn !== null ? store.leadAsk(one.turn) : null; return asked === null ? [] : [[asked.turn, asked]]; }));
|
|
1145
|
+
// The person's queued messages in this thread: each shows waiting (with Remove) or why it was not sent.
|
|
1146
|
+
const queue = new Map(personalQueue.queue.forThread(opened.thread.id).map(row => [row.message, row]));
|
|
1042
1147
|
// The thread a ceiling change replaced (ruling 9): read after the open, display only.
|
|
1043
1148
|
const replaced = store.replacedLeadThread(opened.thread);
|
|
1044
1149
|
const previousProposals = replaced === null ? [] : store.listLeadProposals(replaced.id);
|
|
@@ -1046,7 +1151,9 @@ export function createDecisionServer(options) {
|
|
|
1046
1151
|
previous: replaced === null ? null : { messages: store.listLeadMessages(replaced.id, 40), proposals: previousProposals, decisions: decisionsFor(store, previousProposals) },
|
|
1047
1152
|
messages,
|
|
1048
1153
|
ask,
|
|
1154
|
+
staleAsk,
|
|
1049
1155
|
asks,
|
|
1156
|
+
queue,
|
|
1050
1157
|
proposals,
|
|
1051
1158
|
decisions: decisionsFor(store, [...proposals, ...coordinatorProposals]),
|
|
1052
1159
|
coordinatorProposals,
|
|
@@ -1262,6 +1369,12 @@ export function createDecisionServer(options) {
|
|
|
1262
1369
|
* are what sensitivity strips.
|
|
1263
1370
|
*/
|
|
1264
1371
|
function sendScreen(response, status, s) {
|
|
1372
|
+
// Sensitivity is the page as authored (cockpit §2, R14): its own explicit word, else a password field it was written
|
|
1373
|
+
// with — judged before an identity-provider sign-in replaces that field, so an SSO step-up page stays sensitive.
|
|
1374
|
+
const authored = s.sensitive ?? (s.forceSensitive === true ||
|
|
1375
|
+
SENSITIVE_INPUT.test(htmlString(s.body)) ||
|
|
1376
|
+
(s.workspace?.pageHtml != null && SENSITIVE_INPUT.test(htmlString(s.workspace.pageHtml))) ||
|
|
1377
|
+
(s.chrome?.listPane !== undefined && SENSITIVE_INPUT.test(htmlString(s.chrome.listPane))));
|
|
1265
1378
|
// v100: signed in with the identity provider, a step-up's password field is that sign-in instead.
|
|
1266
1379
|
const ssoFacts = requestContext.getStore()?.sso;
|
|
1267
1380
|
if (ssoFacts !== undefined) {
|
|
@@ -1271,9 +1384,7 @@ export function createDecisionServer(options) {
|
|
|
1271
1384
|
s = { ...s, workspace: { ...s.workspace, view: { ...s.workspace.view, stepUp: { label: ssoFacts.label, fresh: ssoFacts.fresh, confirmHref: `/login/sso?reauth=1&return=${encodeURIComponent(back)}` } } } };
|
|
1272
1385
|
}
|
|
1273
1386
|
// D5: team chat (the central team service) is deprecated; /chat?team=1 still opens this release, but nothing links to it.
|
|
1274
|
-
const sensitive = s.forceSensitive === true
|
|
1275
|
-
SENSITIVE_INPUT.test(htmlString(s.body)) ||
|
|
1276
|
-
(s.chrome?.listPane !== undefined && SENSITIVE_INPUT.test(htmlString(s.chrome.listPane)));
|
|
1387
|
+
const sensitive = authored || s.forceSensitive === true;
|
|
1277
1388
|
const requestFacts = requestContext.getStore();
|
|
1278
1389
|
if (requestFacts?.browser && s.chrome && !s.forceSensitive && (requestFacts.workspaceRead || browserAssetsAvailable())) {
|
|
1279
1390
|
const path = new URL(requestFacts.returnTo, 'http://standing-orders.local');
|
|
@@ -1344,11 +1455,44 @@ export function createDecisionServer(options) {
|
|
|
1344
1455
|
const request = requestFacts.workspaceRequest;
|
|
1345
1456
|
const workspace = {
|
|
1346
1457
|
version: 1, path: currentPath, title: s.title, user: requestFacts.actor ?? '', csrf: requestFacts.csrf, sensitive,
|
|
1458
|
+
...(requestFacts.passwordless === true ? { passwordless: true } : {}),
|
|
1347
1459
|
...(requestFacts.actor ? { leadName: leadNameOf(store, requestFacts.actor) } : {}),
|
|
1348
1460
|
refreshUrl: path.pathname + path.search,
|
|
1349
1461
|
receipt: conversation !== null && request && REQUEST_TOKEN.test(request)
|
|
1350
|
-
? { request, received: store.
|
|
1462
|
+
? { request, received: store.leadRequestReceived(conversation.sessionId, request) } : null,
|
|
1351
1463
|
projects: browserProjectsOf(s.chrome.projects ?? []), ...crew,
|
|
1464
|
+
// The sidebar's project states: never on a sensitive page (no rail, no summary stream there).
|
|
1465
|
+
...(() => { if (sensitive)
|
|
1466
|
+
return {}; try {
|
|
1467
|
+
return { projectStates: projectSummary() };
|
|
1468
|
+
}
|
|
1469
|
+
catch {
|
|
1470
|
+
return {};
|
|
1471
|
+
} })(),
|
|
1472
|
+
// Cockpit B2: the remembered password's chip, only where it could be used (a direct local request).
|
|
1473
|
+
...(() => {
|
|
1474
|
+
const facts = freshFacts.getStore();
|
|
1475
|
+
if (sensitive || facts == null || !facts.local || !facts.live())
|
|
1476
|
+
return {};
|
|
1477
|
+
const at = facts.passwordAt();
|
|
1478
|
+
if (at === null || at <= facts.floor())
|
|
1479
|
+
return {};
|
|
1480
|
+
const left = FRESH_WINDOW_MS - (Date.now() - at);
|
|
1481
|
+
return left <= 0 ? {} : { fresh: { minutes: Math.max(1, Math.ceil(left / 60_000)), forget: '/session/forget-password' } };
|
|
1482
|
+
})(),
|
|
1483
|
+
// The usage chip: the Limits view's own audience (an operator on a browser session), never a sensitive page.
|
|
1484
|
+
...(() => {
|
|
1485
|
+
const actor = requestFacts.actor;
|
|
1486
|
+
if (sensitive || !actor || requestFacts.csrf === '' || !store.isInstanceOperator(actor))
|
|
1487
|
+
return {};
|
|
1488
|
+
try {
|
|
1489
|
+
const usage = usageChipOf(store.providerLimits(), clock());
|
|
1490
|
+
return usage === null ? {} : { usage };
|
|
1491
|
+
}
|
|
1492
|
+
catch {
|
|
1493
|
+
return {};
|
|
1494
|
+
}
|
|
1495
|
+
})(),
|
|
1352
1496
|
conversation, ...(extras.team ? { team: extras.team } : {}), focus: extras.focus ?? null, result: extras.result ?? null,
|
|
1353
1497
|
catchUpHtml: extras.catchUpHtml === undefined ? '' : htmlString(extras.catchUpHtml), controlsHtml: extras.controlsHtml === undefined ? '' : htmlString(extras.controlsHtml), notices, view: extras.view ?? null,
|
|
1354
1498
|
...(s.chrome.demo ? { demo: { text: DEMO_BANNER, short: DEMO_BANNER_SHORT } } : {}),
|
|
@@ -1413,6 +1557,77 @@ export function createDecisionServer(options) {
|
|
|
1413
1557
|
const bustBadge = () => {
|
|
1414
1558
|
paletteCache.clear();
|
|
1415
1559
|
};
|
|
1560
|
+
/** Enrolled projects first (most recently opened), then the repos this server was told to serve that nobody has
|
|
1561
|
+
* opened yet — the switcher lists every project it is allowed to show, each exactly once. */
|
|
1562
|
+
function sidebarProjects() {
|
|
1563
|
+
const seen = new Set();
|
|
1564
|
+
const rows = [];
|
|
1565
|
+
for (const one of [
|
|
1566
|
+
...store.listProjects().map(one => ({ path: one.path, name: one.name })),
|
|
1567
|
+
...managedRepos().map(path => ({ path, name: projectName(path) })),
|
|
1568
|
+
]) {
|
|
1569
|
+
if (seen.has(one.path) || !visible(one.path) || goneRepos().has(one.path))
|
|
1570
|
+
continue;
|
|
1571
|
+
seen.add(one.path);
|
|
1572
|
+
rows.push(one);
|
|
1573
|
+
}
|
|
1574
|
+
return rows;
|
|
1575
|
+
}
|
|
1576
|
+
/** The sidebar's projects with their states (agent-state.ts), for the request's own reader: the page's first read
|
|
1577
|
+
* and the summary room's snapshots are this one function, so the two never disagree. Never marks anything seen.
|
|
1578
|
+
* Work with no project yet is its own entry when this reader may see it and something counts there. */
|
|
1579
|
+
function projectSummary() {
|
|
1580
|
+
const projects = sidebarProjects();
|
|
1581
|
+
const actor = requestContext.getStore()?.actor ?? '';
|
|
1582
|
+
const states = new Map(projectStates(store, workAccess(), actor, clock(), evidenceRoot, projects.map(one => one.path)).map(one => [one.repo, one]));
|
|
1583
|
+
// A row that needs a person or failed says what the Tasks list says it needs (its own next step); a row at work says
|
|
1584
|
+
// what its agent did last; a finished one says Done.
|
|
1585
|
+
const asking = [...states.values()].flatMap(one => one.families.slice(0, RAIL_TASKS)).filter(one => one.state === 'needs-you' || one.state === 'failed').map(one => one.rootId);
|
|
1586
|
+
const needWords = new Map();
|
|
1587
|
+
if (asking.length > 0) {
|
|
1588
|
+
try {
|
|
1589
|
+
// Every row the rail may list, in pages the index allows.
|
|
1590
|
+
for (let at = 0; at < asking.length; at += WORK_INDEX_MAX_LIMIT)
|
|
1591
|
+
for (const item of workIndexPage(store, clock(), workAccess(), { roots: asking.slice(at, at + WORK_INDEX_MAX_LIMIT), limit: WORK_INDEX_MAX_LIMIT, root: evidenceRoot }).items)
|
|
1592
|
+
needWords.set(item.rootId, item.status.detail !== '' ? item.status.detail : item.status.label);
|
|
1593
|
+
}
|
|
1594
|
+
catch { /* the rows keep their state's words */ }
|
|
1595
|
+
}
|
|
1596
|
+
// The chime's subjects: an opaque key per obligation and version, scoped to this reader (no words, no ids in it).
|
|
1597
|
+
const keyOf = (...parts) => createHash('sha256').update([actor, ...parts].join('\u0000')).digest('base64url').slice(0, 22);
|
|
1598
|
+
// Newest first, so a new ask always has a key however many older ones wait (those were heard when they were new).
|
|
1599
|
+
const asksOf = (state) => [
|
|
1600
|
+
...(state?.families ?? []).flatMap(one => one.ask === undefined ? [] : [{ at: one.ask.at, ask: { key: keyOf('task', one.rootId, one.ask.version), task: one.rootId, run: one.ask.run, at: one.ask.at } }]),
|
|
1601
|
+
...(state?.flowAsks ?? []).map(one => ({ at: one.at, ask: { key: keyOf('flow', one.card, one.stage, one.entry), task: null, run: null, at: one.at } })),
|
|
1602
|
+
].sort((a, b) => b.at.localeCompare(a.at)).slice(0, ASKS_PER_PROJECT).map(one => one.ask);
|
|
1603
|
+
const railOf = (state) => {
|
|
1604
|
+
const families = state?.families ?? [];
|
|
1605
|
+
const tasks = families.slice(0, RAIL_TASKS).flatMap((one) => {
|
|
1606
|
+
const task = store.getTask(one.rootId);
|
|
1607
|
+
const line = one.state === 'needs-you' || one.state === 'failed' ? needWords.get(one.rootId) ?? null : one.line;
|
|
1608
|
+
return task === null ? [] : [{ id: one.rootId, title: task.title, href: `/t/${encodeURIComponent(one.rootId)}`, state: one.state, line: line ?? STATE_WORDS[one.state] }];
|
|
1609
|
+
});
|
|
1610
|
+
return { tasks, more: Math.max(0, families.length - RAIL_TASKS), asks: asksOf(state) };
|
|
1611
|
+
};
|
|
1612
|
+
const rows = projects.map(one => {
|
|
1613
|
+
const state = states.get(one.path);
|
|
1614
|
+
return { path: one.path, name: one.name, href: `/work?project=${encodeURIComponent(one.path)}`, state: state?.state ?? 'idle', counts: state?.counts ?? { ...NO_COUNTS }, ...railOf(state) };
|
|
1615
|
+
});
|
|
1616
|
+
const unplaced = states.get(null);
|
|
1617
|
+
if (unplaced !== undefined && unplaced.state !== 'idle')
|
|
1618
|
+
rows.push({ path: null, name: 'No project', href: '/work', state: unplaced.state, counts: unplaced.counts, ...railOf(unplaced) });
|
|
1619
|
+
return boundProjectSummary(rows);
|
|
1620
|
+
}
|
|
1621
|
+
/** What one account may see now, as the summary room keys it: a narrowed ceiling or grant is a different key, so a
|
|
1622
|
+
* snapshot read for the wider scope never reaches it. Null: no such account. */
|
|
1623
|
+
const projectScopeFor = (account) => store.accountOf(account) === null ? null : requestContext.run({ csrf: '', returnTo: '/', actor: account }, () => {
|
|
1624
|
+
// Canonical: what may be seen, never the order it was last opened in.
|
|
1625
|
+
const access = workAccess();
|
|
1626
|
+
return JSON.stringify({ projects: sidebarProjects().map(one => one.path).sort(), access: { ...access, repos: access.repos === null ? null : [...access.repos].sort() } });
|
|
1627
|
+
});
|
|
1628
|
+
/** One account's summary snapshot, read as that account for this exact scope (the summary room's fingerprint): null
|
|
1629
|
+
* when the account is gone or its scope moved. */
|
|
1630
|
+
const projectSummaryFor = (account, scope) => projectScopeFor(account) !== scope ? null : requestContext.run({ csrf: '', returnTo: '/', actor: account }, () => JSON.stringify(projectSummary()));
|
|
1416
1631
|
/** The sidebar's facts for this request. */
|
|
1417
1632
|
function chromeFor(project, active, listPane, scope) {
|
|
1418
1633
|
if (restricted() && !visible(project))
|
|
@@ -1446,20 +1661,7 @@ export function createDecisionServer(options) {
|
|
|
1446
1661
|
// Enrolled projects first (most recently opened), then the repos this
|
|
1447
1662
|
// server was told to serve that nobody has opened yet — the switcher
|
|
1448
1663
|
// lists every project it is allowed to show, each exactly once.
|
|
1449
|
-
projects: (
|
|
1450
|
-
const seen = new Set();
|
|
1451
|
-
const rows = [];
|
|
1452
|
-
for (const one of [
|
|
1453
|
-
...store.listProjects().map(one => ({ path: one.path, name: one.name })),
|
|
1454
|
-
...managedRepos().map(path => ({ path, name: projectName(path) })),
|
|
1455
|
-
]) {
|
|
1456
|
-
if (seen.has(one.path) || !visible(one.path) || goneRepos().has(one.path))
|
|
1457
|
-
continue;
|
|
1458
|
-
seen.add(one.path);
|
|
1459
|
-
rows.push(one);
|
|
1460
|
-
}
|
|
1461
|
-
return rows;
|
|
1462
|
-
})(),
|
|
1664
|
+
projects: sidebarProjects(),
|
|
1463
1665
|
...(facts === undefined ? {} : { csrf: facts.csrf, returnTo: facts.returnTo }),
|
|
1464
1666
|
inboxCount: badge.count,
|
|
1465
1667
|
inboxSaturated: badge.saturated,
|
|
@@ -1495,6 +1697,8 @@ export function createDecisionServer(options) {
|
|
|
1495
1697
|
},
|
|
1496
1698
|
}),
|
|
1497
1699
|
...(!restricted() && !unscopedMode && managedRepos().length > 0 ? { chat: true } : {}),
|
|
1700
|
+
// Live terminals: offered to an installation-wide operator (the routes prove it again).
|
|
1701
|
+
...(actor !== undefined && !store.isDemo() && store.isInstanceOperator(actor) ? { code: true } : {}),
|
|
1498
1702
|
...(listPane === undefined ? {} : { listPane }),
|
|
1499
1703
|
...(scope === undefined ? {} : { scope }),
|
|
1500
1704
|
};
|
|
@@ -1584,6 +1788,7 @@ export function createDecisionServer(options) {
|
|
|
1584
1788
|
let leadClosing = false;
|
|
1585
1789
|
server.once('listening', () => {
|
|
1586
1790
|
team.start();
|
|
1791
|
+
personalQueue.start();
|
|
1587
1792
|
leadMaintenance = startMaintenance({ intervalMs: 5_000, shouldStop: () => leadClosing,
|
|
1588
1793
|
run: () => runLeadFollowPass({ store, repos: managedRepos, evidenceRoot, clock,
|
|
1589
1794
|
provider: () => { const enabled = chatEnablement(); return enabled.ok ? { config: enabled.config, key: enabled.key } : null; },
|
|
@@ -1597,6 +1802,8 @@ export function createDecisionServer(options) {
|
|
|
1597
1802
|
// it closes at once and never waits behind a model turn: a stop must release
|
|
1598
1803
|
// the owner record well inside the service's exit window (Oct 2: deploys
|
|
1599
1804
|
// over 0.9.11 found it still held by a killed process).
|
|
1805
|
+
/** Set once the terminal handlers exist (below): drops every socket and ends every program. */
|
|
1806
|
+
const terminalClosing = {};
|
|
1600
1807
|
let codingClosing = null;
|
|
1601
1808
|
const closeCoding = () => {
|
|
1602
1809
|
leadClosing = true;
|
|
@@ -1618,7 +1825,9 @@ export function createDecisionServer(options) {
|
|
|
1618
1825
|
liveHandlers.close();
|
|
1619
1826
|
detachReads();
|
|
1620
1827
|
liveFollower.close();
|
|
1621
|
-
void Promise.all([closeCoding(), bounded(team.close()), bounded(leadMaintenance?.stop()), bounded(reads?.close())])
|
|
1828
|
+
void Promise.all([closeCoding(), terminalClosing.close?.().then(problem => { terminalClosing.problem = problem; }), bounded(team.close()), bounded(personalQueue.close()), bounded(leadMaintenance?.stop()), bounded(reads?.close())])
|
|
1829
|
+
// The server always closes; a terminal whose processes couldn't be proved stopped is the close's error.
|
|
1830
|
+
.then(() => closeServer(error => callback?.(error ?? terminalClosing.problem ?? undefined))).catch(error => {
|
|
1622
1831
|
if (callback)
|
|
1623
1832
|
callback(error instanceof Error ? error : Error('Coding session shutdown failed.'));
|
|
1624
1833
|
else
|
|
@@ -1678,6 +1887,7 @@ export function createDecisionServer(options) {
|
|
|
1678
1887
|
get chatScopeOf() { return chatScopeOf; },
|
|
1679
1888
|
get liveTurns() { return liveTurns; },
|
|
1680
1889
|
get liveTurnStarted() { return liveTurnStarted; },
|
|
1890
|
+
get personalQueue() { return personalQueue; },
|
|
1681
1891
|
get leadConversationRows() { return leadConversationRows; },
|
|
1682
1892
|
get demoLeadHere() { return demoLeadHere; },
|
|
1683
1893
|
get teamBrowserReply() { return teamBrowserReply; },
|
|
@@ -1736,6 +1946,7 @@ export function createDecisionServer(options) {
|
|
|
1736
1946
|
get signInLinks() { return signInLinks; },
|
|
1737
1947
|
get linkKey() { return linkKey; },
|
|
1738
1948
|
get fromThisComputer() { return fromThisComputer; },
|
|
1949
|
+
get freshFactsOf() { return freshFactsOf; },
|
|
1739
1950
|
get passwordAllowed() { return passwordAllowed; },
|
|
1740
1951
|
get arrival() { return arrival; },
|
|
1741
1952
|
get cookieSecure() { return cookieSecure; },
|
|
@@ -1768,6 +1979,7 @@ export function createDecisionServer(options) {
|
|
|
1768
1979
|
get revisionViewOf() { return revisionViewOf; },
|
|
1769
1980
|
get revisionLedgerOf() { return revisionLedgerOf; },
|
|
1770
1981
|
get firstTasks() { return firstTasks; },
|
|
1982
|
+
get authenticateCredential() { return authenticateCredential; },
|
|
1771
1983
|
};
|
|
1772
1984
|
const tasksHandlers = createTasksHandlers(handlerRuntime);
|
|
1773
1985
|
const pagesHandlers = createPagesHandlers(handlerRuntime);
|
|
@@ -1777,6 +1989,7 @@ export function createDecisionServer(options) {
|
|
|
1777
1989
|
const peopleHandlers = createPeopleHandlers(handlerRuntime);
|
|
1778
1990
|
const remoteRuntime = {
|
|
1779
1991
|
get identify() { return identify; },
|
|
1992
|
+
get freshFactsOf() { return freshFactsOf; },
|
|
1780
1993
|
get store() { return store; },
|
|
1781
1994
|
get admitPasswordSource() { return admitPasswordSource; },
|
|
1782
1995
|
get admitBearer() { return admitBearer; },
|
|
@@ -1794,19 +2007,21 @@ export function createDecisionServer(options) {
|
|
|
1794
2007
|
get managedRepos() { return managedRepos; },
|
|
1795
2008
|
get joinSourceOf() { return joinSourceOf; },
|
|
1796
2009
|
get ssoSettings() { return ssoSettings; },
|
|
1797
|
-
get SSO_FRESH_MS() { return SSO_FRESH_MS; },
|
|
1798
2010
|
get oauthTokenBudget() { return oauthTokenBudget; },
|
|
1799
2011
|
get hookContext() { return hookContext; },
|
|
1800
2012
|
get teamsSourceBudget() { return teamsSourceBudget; },
|
|
1801
2013
|
get teamsTenantBudget() { return teamsTenantBudget; },
|
|
1802
2014
|
};
|
|
1803
|
-
const liveHandlers = createLiveHandlers(handlerRuntime, { bus: liveBus, workspaceRevision, chatProjectOf: chatHandlers.chatProjectOf });
|
|
2015
|
+
const liveHandlers = createLiveHandlers(handlerRuntime, { bus: liveBus, workspaceRevision, chatProjectOf: chatHandlers.chatProjectOf, projectSummaryFor, projectScopeFor });
|
|
1804
2016
|
liveTurnStarted = liveHandlers.turnStarted;
|
|
2017
|
+
// The one upgrade this server takes is a terminal's socket (terminals.ts listens); anything else is dropped.
|
|
2018
|
+
const terminalHandlers = createTerminalHandlers(handlerRuntime, { ...terminals, bus: liveBus, allowedHost, publicOrigin, forwardedSourceOf, server });
|
|
2019
|
+
terminalClosing.close = terminalHandlers.close;
|
|
1805
2020
|
const remoteHandlers = createRemoteHandlers(remoteRuntime);
|
|
1806
2021
|
const registry = createHandlerRegistry([
|
|
1807
2022
|
...tasksHandlers.registrations, ...flowsHandlers.registrations, ...chatHandlers.registrations,
|
|
1808
2023
|
...settingsHandlers.registrations, ...peopleHandlers.registrations, ...pagesHandlers.registrations,
|
|
1809
|
-
...remoteHandlers.registrations, ...liveHandlers.registrations,
|
|
2024
|
+
...remoteHandlers.registrations, ...liveHandlers.registrations, ...terminalHandlers.registrations, ...createSweepHandlers(handlerRuntime).registrations,
|
|
1810
2025
|
]);
|
|
1811
2026
|
/** The row's role, after admission and before its one handler. */
|
|
1812
2027
|
function dispatchConsole(route, ctx) {
|
package/dist/server/chat.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export declare function createChatHandlers(runtime: ServerRuntime): {
|
|
|
15
15
|
}, now: Date) => BrowserPhoneCard | undefined;
|
|
16
16
|
chatProjectOf: (values: string[]) => string | null | undefined;
|
|
17
17
|
beginLiveTurn: (thread: number) => (event: LeadProgress) => void;
|
|
18
|
-
endLiveTurn: (thread: number, ok: boolean) => void;
|
|
18
|
+
endLiveTurn: (thread: number, ok: boolean, progress?: (event: LeadProgress) => void) => void;
|
|
19
19
|
noteLead: (csrf: string, turn: number | null, message: string) => void;
|
|
20
20
|
takeLeadNote: (csrf: string, liveSession: number | null) => string | null;
|
|
21
21
|
demoResultView: (exchange: DemoExchange) => DemoResultView | null;
|