toolroll 0.9.54 → 0.9.56
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 +19 -0
- package/README.md +29 -12
- package/dist/action-ledger.d.ts +2 -4
- package/dist/action-ledger.js +2 -10
- package/dist/agent-onboard.js +4 -1
- package/dist/approval-policy.d.ts +5 -5
- package/dist/approval-policy.js +4 -4
- package/dist/approval-rules-ui.d.ts +3 -2
- package/dist/approval-rules-ui.js +17 -22
- package/dist/assignment-ui.d.ts +3 -2
- package/dist/assignment-ui.js +4 -11
- package/dist/backup-ui.d.ts +2 -1
- package/dist/backup-ui.js +16 -27
- package/dist/brand-mark.d.ts +3 -2
- package/dist/brand-mark.js +4 -4
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +25 -25
- package/dist/browser-shell.d.ts +2 -1
- package/dist/browser-shell.js +9 -9
- package/dist/browser-workspace.d.ts +14 -15
- package/dist/browser-workspace.js +0 -3
- package/dist/budget-alerts.js +3 -3
- package/dist/chat-actions.d.ts +23 -18
- package/dist/chat-actions.js +110 -88
- package/dist/chat-ask.d.ts +2 -2
- package/dist/chat-ask.js +5 -5
- package/dist/chat-channel.d.ts +15 -15
- package/dist/chat-channel.js +20 -20
- package/dist/chat-controls.d.ts +0 -4
- package/dist/chat-controls.js +0 -1
- package/dist/chat-core.js +1 -1
- package/dist/chat-delivery-state.js +2 -2
- package/dist/chat-delivery.d.ts +2 -2
- package/dist/chat-delivery.js +27 -34
- package/dist/chat-flow.js +3 -3
- package/dist/chat-messages.d.ts +1 -1
- package/dist/chat-messages.js +4 -4
- package/dist/chat-migration.d.ts +1 -1
- package/dist/chat-migration.js +11 -11
- package/dist/chat-polish.d.ts +5 -4
- package/dist/chat-polish.js +9 -10
- package/dist/chat-rooms.js +3 -3
- package/dist/chat-warmth.d.ts +2 -2
- package/dist/check-levels-ui.d.ts +2 -1
- package/dist/check-levels-ui.js +26 -53
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +15 -10
- package/dist/coding-shipping-ui.d.ts +2 -1
- package/dist/coding-shipping-ui.js +6 -6
- package/dist/coding-ui.d.ts +3 -1
- package/dist/coding-ui.js +30 -29
- package/dist/contracts/chat-actions.d.ts +32 -27
- package/dist/contracts/chat-actions.js +29 -11
- package/dist/contracts/chat-content.d.ts +1 -1
- package/dist/contracts/chat-content.js +1 -1
- package/dist/contracts/chat-tables.js +4 -4
- package/dist/contracts/cli.d.ts +1 -3
- package/dist/contracts/console-api.d.ts +7 -7
- package/dist/contracts/console-api.js +13 -12
- package/dist/contracts/flow-propose.d.ts +3 -3
- package/dist/contracts/flow.d.ts +132 -125
- package/dist/contracts/flow.js +27 -12
- package/dist/contracts/health.d.ts +1 -3
- package/dist/contracts/lead-context.d.ts +1 -1
- package/dist/contracts/lead-context.js +2 -2
- package/dist/contracts/lead-tools.d.ts +20 -18
- package/dist/contracts/lead-tools.js +15 -15
- package/dist/contracts/stage-output.d.ts +1 -1
- package/dist/contracts/stage-output.js +1 -1
- package/dist/contracts/store-json.d.ts +23 -22
- package/dist/contracts/store-json.js +17 -17
- package/dist/contracts/{teammate-turn.d.ts → subagent-turn.d.ts} +5 -5
- package/dist/contracts/{teammate-turn.js → subagent-turn.js} +7 -7
- package/dist/control-ui.d.ts +5 -4
- package/dist/control-ui.js +9 -34
- package/dist/converse.d.ts +23 -23
- package/dist/converse.js +28 -28
- package/dist/coordinator-proposals.d.ts +2 -2
- package/dist/coordinator-proposals.js +1 -1
- package/dist/coordinator.js +1 -1
- package/dist/credentials-ui.d.ts +5 -4
- package/dist/credentials-ui.js +23 -24
- package/dist/demo-chat.d.ts +3 -3
- package/dist/demo-chat.js +29 -76
- package/dist/deprecations.d.ts +12 -0
- package/dist/deprecations.js +20 -0
- package/dist/desktop-update-gate.js +8 -4
- package/dist/desktop-update.js +6 -1
- package/dist/discord-chat.js +6 -6
- package/dist/discord-settings.d.ts +3 -2
- package/dist/discord-settings.js +17 -42
- package/dist/dispatch.d.ts +1 -1
- package/dist/dispatch.js +1 -1
- package/dist/envelope.js +10 -1
- package/dist/evidence-pack.d.ts +2 -1
- package/dist/evidence-pack.js +45 -49
- package/dist/export-ui.d.ts +7 -2
- package/dist/export-ui.js +4 -10
- package/dist/export.js +7 -7
- package/dist/flow-engine.d.ts +2 -2
- package/dist/flow-engine.js +21 -21
- package/dist/flow-gallery-ui.d.ts +4 -3
- package/dist/flow-gallery-ui.js +28 -43
- package/dist/flow-insights.d.ts +1 -1
- package/dist/flow-share.js +13 -8
- package/dist/flow-starters-ui.d.ts +2 -1
- package/dist/flow-starters-ui.js +12 -15
- package/dist/flow-steps.d.ts +5 -5
- package/dist/flow-steps.js +19 -19
- package/dist/flow-triggers.d.ts +2 -1
- package/dist/flow-triggers.js +17 -15
- package/dist/flows-cli.js +1 -1
- package/dist/flows-ui.d.ts +4 -3
- package/dist/flows-ui.js +38 -49
- package/dist/flows.js +21 -20
- package/dist/guides.js +112 -6
- package/dist/html.d.ts +76 -0
- package/dist/html.js +173 -0
- package/dist/integrations-ui.d.ts +2 -1
- package/dist/integrations-ui.js +19 -23
- package/dist/invoke.js +1 -1
- package/dist/kits-ui.d.ts +5 -4
- package/dist/kits-ui.js +22 -24
- package/dist/kits.d.ts +12 -12
- package/dist/kits.js +31 -31
- package/dist/knowledge-ui.d.ts +6 -5
- package/dist/knowledge-ui.js +26 -25
- package/dist/{mate-cli.d.ts → lead-cli.d.ts} +11 -11
- package/dist/{mate-cli.js → lead-cli.js} +38 -38
- package/dist/lead-commitments.js +4 -4
- package/dist/lead-context.d.ts +12 -2
- package/dist/lead-context.js +15 -17
- package/dist/{mate-contract.d.ts → lead-contract.d.ts} +3 -3
- package/dist/{mate-contract.js → lead-contract.js} +7 -6
- package/dist/{mate-doors.d.ts → lead-doors.d.ts} +6 -6
- package/dist/{mate-doors.js → lead-doors.js} +24 -24
- package/dist/lead-follow.d.ts +5 -5
- package/dist/lead-follow.js +9 -9
- package/dist/lead-people.d.ts +5 -5
- package/dist/lead-people.js +14 -14
- package/dist/{mate-progress.d.ts → lead-progress.d.ts} +9 -9
- package/dist/{mate-progress.js → lead-progress.js} +3 -3
- package/dist/{mate-tools.d.ts → lead-tools.d.ts} +19 -19
- package/dist/{mate-tools.js → lead-tools.js} +109 -101
- package/dist/{mate.d.ts → lead.d.ts} +27 -27
- package/dist/{mate.js → lead.js} +102 -102
- package/dist/ledger-view.d.ts +2 -1
- package/dist/ledger-view.js +27 -29
- package/dist/limits-ui.d.ts +3 -2
- package/dist/limits-ui.js +8 -8
- package/dist/live-bus.d.ts +50 -8
- package/dist/live-bus.js +133 -50
- package/dist/mcp-connect.d.ts +1 -1
- package/dist/mcp-connect.js +3 -3
- package/dist/mcp-core.js +1 -1
- package/dist/mcp-oauth.js +35 -36
- package/dist/memory-pass.d.ts +2 -2
- package/dist/memory-pass.js +7 -7
- package/dist/models-ui.d.ts +2 -1
- package/dist/models-ui.js +25 -35
- package/dist/monitoring-ui.d.ts +4 -3
- package/dist/monitoring-ui.js +13 -26
- package/dist/notification-rows.d.ts +2 -2
- package/dist/openrouter-models.d.ts +2 -1
- package/dist/openrouter-models.js +5 -9
- package/dist/operate.d.ts +3 -3
- package/dist/operate.js +46 -41
- package/dist/people-audit-ui.d.ts +2 -1
- package/dist/people-audit-ui.js +13 -13
- package/dist/policy-ui.d.ts +3 -2
- package/dist/policy-ui.js +17 -23
- package/dist/policy.d.ts +1 -1
- package/dist/principal.d.ts +1 -1
- package/dist/principal.js +2 -2
- package/dist/project-delete-ui.d.ts +4 -3
- package/dist/project-delete-ui.js +16 -28
- package/dist/project-delete.d.ts +1 -1
- package/dist/project-delete.js +26 -26
- package/dist/project-memory.js +1 -1
- package/dist/project-tools.d.ts +1 -1
- package/dist/project-tools.js +1 -1
- package/dist/pull-request-ui.d.ts +2 -1
- package/dist/pull-request-ui.js +17 -32
- package/dist/recipe-ui.d.ts +8 -7
- package/dist/recipe-ui.js +39 -32
- package/dist/release-adapters.d.ts +60 -0
- package/dist/release-adapters.js +435 -0
- package/dist/release-cli.d.ts +24 -0
- package/dist/release-cli.js +181 -0
- package/dist/release-gate.d.ts +80 -0
- package/dist/release-gate.js +150 -0
- package/dist/release-links.d.ts +29 -0
- package/dist/release-links.js +102 -0
- package/dist/release.d.ts +331 -0
- package/dist/release.js +550 -0
- package/dist/reply-shape.d.ts +2 -1
- package/dist/reply-shape.js +5 -5
- package/dist/repository-context-ui.d.ts +2 -1
- package/dist/repository-context-ui.js +2 -7
- package/dist/request-budget-ui.d.ts +2 -1
- package/dist/request-budget-ui.js +16 -16
- package/dist/result-review.d.ts +2 -3
- package/dist/result-review.js +3 -9
- package/dist/retention-ui.d.ts +7 -1
- package/dist/retention-ui.js +7 -11
- package/dist/retention.js +4 -4
- package/dist/scope.d.ts +2 -2
- package/dist/scout-report.d.ts +1 -1
- package/dist/scout-report.js +1 -1
- package/dist/serve.d.ts +11 -2
- package/dist/serve.js +105 -99
- package/dist/server/chat.d.ts +7 -10
- package/dist/server/chat.js +984 -1021
- package/dist/server/chrome.d.ts +291 -0
- package/dist/server/chrome.js +747 -0
- package/dist/server/console.css +2819 -0
- package/dist/server/flows.d.ts +2 -6
- package/dist/server/flows.js +854 -773
- package/dist/server/guards.d.ts +1 -1
- package/dist/server/guards.js +8 -5
- package/dist/server/handler-context.d.ts +1 -1
- package/dist/server/handler-context.js +1 -1
- package/dist/server/handler-registry.d.ts +8 -6
- package/dist/server/handler-registry.js +14 -2
- package/dist/server/http.d.ts +274 -0
- package/dist/server/http.js +239 -0
- package/dist/server/live.d.ts +34 -0
- package/dist/server/live.js +309 -0
- package/dist/server/pages.d.ts +2 -6
- package/dist/server/pages.js +808 -789
- package/dist/server/people-tokens.d.ts +1 -6
- package/dist/server/people-tokens.js +581 -573
- package/dist/server/remote.d.ts +4 -7
- package/dist/server/remote.js +39 -70
- package/dist/server/render-chat.d.ts +402 -0
- package/dist/server/render-chat.js +927 -0
- package/dist/server/render-pages.d.ts +535 -0
- package/dist/server/render-pages.js +1277 -0
- package/dist/server/render-people.d.ts +32 -0
- package/dist/server/render-people.js +121 -0
- package/dist/server/render-results.d.ts +744 -0
- package/dist/server/render-results.js +2237 -0
- package/dist/server/render-settings.d.ts +154 -0
- package/dist/server/render-settings.js +580 -0
- package/dist/server/render-tasks.d.ts +632 -0
- package/dist/server/render-tasks.js +2345 -0
- package/dist/server/request-context.d.ts +46 -0
- package/dist/server/request-context.js +17 -0
- package/dist/server/route-table.d.ts +25 -3
- package/dist/server/route-table.js +167 -77
- package/dist/server/runtime.d.ts +28 -20
- package/dist/server/runtime.js +7 -1
- package/dist/server/session.d.ts +138 -0
- package/dist/server/session.js +94 -0
- package/dist/server/settings.d.ts +1 -5
- package/dist/server/settings.js +2186 -2121
- package/dist/server/task-views.d.ts +11 -4
- package/dist/server/task-views.js +26 -22
- package/dist/server/tasks.d.ts +9 -9
- package/dist/server/tasks.js +1489 -1420
- package/dist/server-telemetry.d.ts +4 -3
- package/dist/server-telemetry.js +4 -8
- package/dist/setup-guide.d.ts +2 -2
- package/dist/skills-ui.d.ts +4 -3
- package/dist/skills-ui.js +14 -17
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +2 -1
- package/dist/slack-chat.d.ts +2 -2
- package/dist/slack-chat.js +6 -6
- package/dist/slack-settings.d.ts +3 -2
- package/dist/slack-settings.js +17 -42
- package/dist/spend-ui.d.ts +5 -4
- package/dist/spend-ui.js +17 -40
- package/dist/spend.d.ts +14 -14
- package/dist/spend.js +31 -31
- package/dist/sso-ui.d.ts +3 -2
- package/dist/sso-ui.js +26 -27
- package/dist/storage-ui.d.ts +2 -1
- package/dist/storage-ui.js +14 -33
- package/dist/store-baseline.d.ts +19 -0
- package/dist/store-baseline.js +2873 -0
- package/dist/store.d.ts +197 -249
- package/dist/store.js +747 -3084
- package/dist/subagent-admin.d.ts +53 -0
- package/dist/{teammate-admin.js → subagent-admin.js} +37 -37
- package/dist/{teammate-desk.d.ts → subagent-desk.d.ts} +22 -23
- package/dist/{teammate-desk.js → subagent-desk.js} +26 -35
- package/dist/{teammate-memory.d.ts → subagent-memory.d.ts} +17 -17
- package/dist/{teammate-memory.js → subagent-memory.js} +41 -41
- package/dist/{teammate-question.d.ts → subagent-question.d.ts} +6 -6
- package/dist/{teammate-question.js → subagent-question.js} +17 -17
- package/dist/{teammate-tools.d.ts → subagent-tools.d.ts} +18 -18
- package/dist/{teammate-tools.js → subagent-tools.js} +36 -36
- package/dist/{teammate-week.d.ts → subagent-week.d.ts} +10 -10
- package/dist/{teammate-week.js → subagent-week.js} +23 -23
- package/dist/{teammate-work.d.ts → subagent-work.d.ts} +21 -21
- package/dist/{teammate-work.js → subagent-work.js} +50 -50
- package/dist/subagents-ui.d.ts +22 -0
- package/dist/subagents-ui.js +137 -0
- package/dist/{teammates.d.ts → subagents.d.ts} +11 -11
- package/dist/{teammates.js → subagents.js} +21 -21
- package/dist/subscription-chat.d.ts +13 -13
- package/dist/subscription-chat.js +19 -19
- package/dist/surface.d.ts +19 -2
- package/dist/surface.js +20 -33
- package/dist/task-composer.d.ts +2 -1
- package/dist/task-composer.js +7 -7
- package/dist/task-status.d.ts +4 -3
- package/dist/task-status.js +15 -16
- package/dist/team-cli.d.ts +1 -0
- package/dist/team-cli.js +14 -2
- package/dist/team-http.d.ts +5 -9
- package/dist/team-http.js +5 -63
- package/dist/team-leads.js +13 -13
- package/dist/team-runtime.d.ts +2 -2
- package/dist/team-runtime.js +8 -8
- package/dist/team-ui.d.ts +2 -1
- package/dist/team-ui.js +2 -5
- package/dist/teams-chat.js +6 -6
- package/dist/teams-settings.d.ts +3 -2
- package/dist/teams-settings.js +20 -31
- package/dist/telegram-flow.d.ts +3 -3
- package/dist/telegram-flow.js +3 -3
- package/dist/{telegram-mate.d.ts → telegram-lead.d.ts} +5 -5
- package/dist/{telegram-mate.js → telegram-lead.js} +27 -27
- package/dist/telegram-settings.d.ts +3 -2
- package/dist/telegram-settings.js +14 -25
- package/dist/telegram-status.js +1 -1
- package/dist/telegram-team.d.ts +1 -1
- package/dist/telegram-team.js +5 -5
- package/dist/telegram.d.ts +1 -1
- package/dist/telegram.js +9 -30
- package/dist/text-limits.d.ts +18 -18
- package/dist/text-limits.js +18 -18
- package/dist/toolroll-update-ui.d.ts +3 -2
- package/dist/toolroll-update-ui.js +35 -39
- package/dist/toolroll-update.d.ts +8 -0
- package/dist/toolroll-update.js +45 -8
- package/dist/tools-ui.d.ts +5 -2
- package/dist/tools-ui.js +31 -31
- package/dist/when-html.d.ts +11 -3
- package/dist/when-html.js +5 -6
- package/dist/workspace-ui.d.ts +4 -2
- package/dist/workspace-ui.js +10 -10
- package/package.json +1 -2
- package/dist/server/shared.d.ts +0 -3151
- package/dist/server/shared.js +0 -12001
- package/dist/teammate-admin.d.ts +0 -53
- package/dist/teammates-ui.d.ts +0 -20
- package/dist/teammates-ui.js +0 -166
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** What one console request knows about itself, readable anywhere below its handler. */
|
|
2
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
+
import type { ServerResponse } from "node:http";
|
|
4
|
+
import { type Html } from "../html.js";
|
|
5
|
+
import type { workCountsByProject, WorkIndexPage } from "../work-index.js";
|
|
6
|
+
export type RequestFacts = {
|
|
7
|
+
appOrigins?: readonly (string | null)[];
|
|
8
|
+
sso?: {
|
|
9
|
+
label: string;
|
|
10
|
+
fresh: boolean;
|
|
11
|
+
} | undefined;
|
|
12
|
+
refusal?: (response: ServerResponse, status: number, body: Html) => void;
|
|
13
|
+
theme?: "light" | "dark" | null;
|
|
14
|
+
accent?: string | null;
|
|
15
|
+
updateSeen?: string | null;
|
|
16
|
+
/** The signed-in browser session's form token ("" for an API token): every POST form carries it (postForm). */
|
|
17
|
+
csrf: string;
|
|
18
|
+
returnTo: string;
|
|
19
|
+
actor?: string;
|
|
20
|
+
createdTask?: string;
|
|
21
|
+
browser?: boolean;
|
|
22
|
+
workspaceRead?: boolean;
|
|
23
|
+
workspaceRequest?: string | null;
|
|
24
|
+
workCounts?: ReturnType<typeof workCountsByProject>;
|
|
25
|
+
workCrew?: {
|
|
26
|
+
project: string | null;
|
|
27
|
+
page: WorkIndexPage;
|
|
28
|
+
};
|
|
29
|
+
lens?: string | null;
|
|
30
|
+
workspaceValidator?: {
|
|
31
|
+
key: string;
|
|
32
|
+
revision: string;
|
|
33
|
+
expiresAt: number;
|
|
34
|
+
etag: string;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* The request's own session facts, readable from anywhere below the
|
|
39
|
+
* dispatcher without threading them through forty call sites: the csrf
|
|
40
|
+
* token every POST form carries, and the path a switch returns
|
|
41
|
+
* to. AsyncLocalStorage follows the request's own async chain, so two
|
|
42
|
+
* interleaved requests never read each other's token.
|
|
43
|
+
*/
|
|
44
|
+
export declare const requestContext: AsyncLocalStorage<RequestFacts>;
|
|
45
|
+
/** Run a render outside a live request (a test, a background render) as if a browser session with this token asked. */
|
|
46
|
+
export declare function withFormToken<T>(csrf: string, render: () => T): T;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** What one console request knows about itself, readable anywhere below its handler. */
|
|
2
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
+
import { provideFormToken } from "../html.js";
|
|
4
|
+
/**
|
|
5
|
+
* The request's own session facts, readable from anywhere below the
|
|
6
|
+
* dispatcher without threading them through forty call sites: the csrf
|
|
7
|
+
* token every POST form carries, and the path a switch returns
|
|
8
|
+
* to. AsyncLocalStorage follows the request's own async chain, so two
|
|
9
|
+
* interleaved requests never read each other's token.
|
|
10
|
+
*/
|
|
11
|
+
export const requestContext = new AsyncLocalStorage();
|
|
12
|
+
provideFormToken(() => requestContext.getStore()?.csrf);
|
|
13
|
+
/** Run a render outside a live request (a test, a background render) as if a browser session with this token asked. */
|
|
14
|
+
export function withFormToken(csrf, render) {
|
|
15
|
+
const facts = requestContext.getStore();
|
|
16
|
+
return requestContext.run({ ...(facts ?? { returnTo: "/" }), csrf }, render);
|
|
17
|
+
}
|
|
@@ -9,8 +9,12 @@
|
|
|
9
9
|
* account's read/write sets, the viewer's POST set, the selected-project
|
|
10
10
|
* exceptions) are projections of this table.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* A row's `role` says who among its admitted callers may use it (an approver,
|
|
13
|
+
* or an instance operator), with the words a caller without it hears. Every
|
|
14
|
+
* row has exactly one handler (handler-registry.ts); handlers do not re-match
|
|
15
|
+
* paths or restate these checks. They still re-prove what can change after
|
|
16
|
+
* admission: passwords, nonces, webhook signatures, object membership, stale
|
|
17
|
+
* sessions and revoked tokens.
|
|
14
18
|
*/
|
|
15
19
|
/** Who presents the request. `service` is a verified external sender (a signed webhook, an OAuth return carrying its one-time state). */
|
|
16
20
|
export type Caller = "anonymous" | "service" | "cookie" | "bearer" | "oauth" | "coordinator" | "lead";
|
|
@@ -29,10 +33,21 @@ export type ProjectResolver = "none" | "session" | "form" | "form-path" | "task"
|
|
|
29
33
|
* - `conversation`: a shared room's own audience admission, else instance access.
|
|
30
34
|
*/
|
|
31
35
|
export type LimitedAccess = "deny" | "collection" | "unscoped" | "resource" | "self" | "proposal" | "conversation";
|
|
32
|
-
export type Domain = "tasks" | "flows" | "chat" | "settings" | "people" | "remote" | "pages";
|
|
36
|
+
export type Domain = "tasks" | "flows" | "chat" | "settings" | "people" | "remote" | "pages" | "live";
|
|
33
37
|
/** `edge`: answered before the console's sign-in (its own protocol proves the caller). `console`: after identify(). */
|
|
34
38
|
export type Stage = "edge" | "console";
|
|
35
39
|
export type RouteMethod = "GET" | "POST" | "GET,HEAD";
|
|
40
|
+
/** Who among the admitted callers may use the route: anyone, an approver, or an instance operator (an approver with instance-wide access). */
|
|
41
|
+
export type RouteRole = "any" | "approver" | "operator";
|
|
42
|
+
/** What a caller without the role hears: the console's refusal page (plain text for a token), or a protocol answer. */
|
|
43
|
+
export type RouteRefusal = {
|
|
44
|
+
readonly message: string;
|
|
45
|
+
readonly back?: string;
|
|
46
|
+
} | {
|
|
47
|
+
readonly status: number;
|
|
48
|
+
readonly type: string;
|
|
49
|
+
readonly body: string;
|
|
50
|
+
};
|
|
36
51
|
export interface RouteDeclaration {
|
|
37
52
|
readonly id: string;
|
|
38
53
|
readonly domain: Domain;
|
|
@@ -61,6 +76,13 @@ export interface RouteDeclaration {
|
|
|
61
76
|
readonly type: string;
|
|
62
77
|
readonly body: string;
|
|
63
78
|
};
|
|
79
|
+
/** Checked after caller, scope and project admission, before the handler. */
|
|
80
|
+
readonly role: RouteRole;
|
|
81
|
+
/** The role counts only through a browser sign-in: an API token hears the same refusal. */
|
|
82
|
+
readonly roleBrowser: boolean;
|
|
83
|
+
readonly roleRefusal?: RouteRefusal;
|
|
84
|
+
/** The largest form body a POST here reads (else the console's default). */
|
|
85
|
+
readonly bodyCap?: number;
|
|
64
86
|
}
|
|
65
87
|
/** The sole route table, in match order. */
|
|
66
88
|
export declare const ROUTES: readonly RouteDeclaration[];
|
|
@@ -9,8 +9,12 @@
|
|
|
9
9
|
* account's read/write sets, the viewer's POST set, the selected-project
|
|
10
10
|
* exceptions) are projections of this table.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* A row's `role` says who among its admitted callers may use it (an approver,
|
|
13
|
+
* or an instance operator), with the words a caller without it hears. Every
|
|
14
|
+
* row has exactly one handler (handler-registry.ts); handlers do not re-match
|
|
15
|
+
* paths or restate these checks. They still re-prove what can change after
|
|
16
|
+
* admission: passwords, nonces, webhook signatures, object membership, stale
|
|
17
|
+
* sessions and revoked tokens.
|
|
14
18
|
*/
|
|
15
19
|
const CONSOLE = ["cookie", "bearer"];
|
|
16
20
|
const exact = (path) => `^${path.replace(/[.*+?^${}()|[\]\\/]/g, m => (m === "/" ? "\\/" : `\\${m}`))}$`;
|
|
@@ -36,6 +40,10 @@ function row(stage, domain, id, method, pattern, options = {}) {
|
|
|
36
40
|
sample: options.sample ?? sampleOf(pattern),
|
|
37
41
|
...(options.scopeRefusal === undefined ? {} : { scopeRefusal: options.scopeRefusal }),
|
|
38
42
|
...(options.callerRefusal === undefined ? {} : { callerRefusal: options.callerRefusal }),
|
|
43
|
+
role: options.role ?? "any",
|
|
44
|
+
roleBrowser: options.roleBrowser ?? false,
|
|
45
|
+
...(options.roleRefusal === undefined ? {} : { roleRefusal: options.roleRefusal }),
|
|
46
|
+
...(options.bodyCap === undefined ? {} : { bodyCap: options.bodyCap }),
|
|
39
47
|
});
|
|
40
48
|
}
|
|
41
49
|
const edge = (domain, id, method, path, options = {}) => row("edge", domain, id, method, path.startsWith("^") ? path : exact(path), options);
|
|
@@ -46,6 +54,14 @@ const FLOW = "[1-9][0-9]{0,9}";
|
|
|
46
54
|
const TASK = "[^/]+";
|
|
47
55
|
/** Free of a project: no opener redirect for a session with nothing open. */
|
|
48
56
|
const free = { needsProject: false };
|
|
57
|
+
const approver = (message, back) => ({ role: "approver", roleBrowser: true, roleRefusal: back === undefined ? { message } : { message, back } });
|
|
58
|
+
const operator = (message, back) => ({ role: "operator", roleBrowser: true, roleRefusal: back === undefined ? { message } : { message, back } });
|
|
59
|
+
const anyApprover = (message, back) => ({ ...approver(message, back), roleBrowser: false });
|
|
60
|
+
const anyOperator = (message, back) => ({ ...operator(message, back), roleBrowser: false });
|
|
61
|
+
const SESSION_JSON = { status: 403, type: "application/json", body: JSON.stringify({ error: "session" }) };
|
|
62
|
+
// URL encoding can triple UTF-8 bytes. Task forms admit their bounded fields (a goal and exclusions of 32,000 bytes
|
|
63
|
+
// each, paths and rubric) before canonical text validation; an imported flow is a file.
|
|
64
|
+
const LARGE_FORM = 1024 * 1024;
|
|
49
65
|
// ---- edge: answered before the console's sign-in ---------------------------------------------------------------
|
|
50
66
|
// Ordered exactly as the server checks them: health and webhooks before the Host check, the rest after it.
|
|
51
67
|
edge("pages", "edge.healthz", "GET,HEAD", "/healthz", { host: "before" });
|
|
@@ -68,7 +84,6 @@ for (const operation of ["list", "show", "changes", "start", "send", "stop", "re
|
|
|
68
84
|
}
|
|
69
85
|
edge("remote", "edge.team-read", "GET", "/api/team", { callers: ["cookie", "bearer"], scope: "read", project: "adapter", proof: "adapter" });
|
|
70
86
|
edge("remote", "edge.team-send", "POST", "/api/team", { callers: ["cookie", "bearer"], scope: "read", project: "adapter", proof: "adapter" });
|
|
71
|
-
edge("remote", "edge.team-events", "GET", "/api/team/events", { callers: ["cookie", "bearer"], scope: "read", project: "adapter", proof: "adapter" });
|
|
72
87
|
edge("remote", "edge.cli", "POST", "/api/cli", { callers: ["bearer"], scope: "read", project: "adapter", proof: "adapter" });
|
|
73
88
|
edge("remote", "edge.teams", "POST", "/teams/messages", { callers: ["service"], proof: "adapter" });
|
|
74
89
|
edge("pages", "edge.browser-asset", "GET,HEAD", "^\\/assets\\/(workspace\\.js|workspace\\.css|THIRD_PARTY_NOTICES\\.txt)$", { sample: "/assets/workspace.js" });
|
|
@@ -87,10 +102,18 @@ edge("people", "edge.logout", "POST", "/logout", { callers: ["anonymous", "cooki
|
|
|
87
102
|
edge("people", "edge.join", "GET", "^\\/join\\/[A-Za-z0-9_-]{16,64}$", { sample: "/join/abcdefghijklmnop" });
|
|
88
103
|
edge("people", "edge.join-send", "POST", "^\\/join\\/[A-Za-z0-9_-]{16,64}$", { sample: "/join/abcdefghijklmnop" });
|
|
89
104
|
// ---- console reads ---------------------------------------------------------------------------------------------
|
|
90
|
-
// Coding: the handler answers any other caller in its own format (JSON for the workspace's fetches).
|
|
91
|
-
|
|
105
|
+
// Coding: the workspace, a session and its reads. The handler answers any other caller in its own format (JSON for the workspace's fetches).
|
|
106
|
+
const CODING = { project: "coding", limited: "self", ...free };
|
|
107
|
+
const SESSION = "[a-f0-9]{32}";
|
|
108
|
+
get("tasks", "code.page", "/code", CODING);
|
|
109
|
+
get("tasks", "code.session", `^\\/code\\/${SESSION}$`, { ...CODING, sample: `/code/${"a".repeat(32)}` });
|
|
110
|
+
for (const one of ["state", "changes", "ship"])
|
|
111
|
+
get("tasks", `code.${one}`, `^\\/code\\/${SESSION}\\/${one}$`, { ...CODING, sample: `/code/${"a".repeat(32)}/${one}` });
|
|
112
|
+
// Any other coding address answers "not found" after the same access check.
|
|
113
|
+
get("tasks", "code.other", "^\\/code\\/.*$", { ...CODING, sample: "/code/unknown" });
|
|
92
114
|
get("pages", "projects.browse", "/projects/browse", free);
|
|
93
|
-
get("pages", "projects.github", "/projects/github", free
|
|
115
|
+
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" },
|
|
116
|
+
...anyApprover("your login can watch — adding projects is an approver's act") });
|
|
94
117
|
get("pages", "projects.page", "/projects", { limited: "unscoped", ...free });
|
|
95
118
|
get("pages", "home", "/", { limited: "collection", ...free });
|
|
96
119
|
get("tasks", "inbox", "/inbox", { limited: "collection", ...free });
|
|
@@ -100,7 +123,7 @@ get("pages", "morning", "/morning");
|
|
|
100
123
|
get("pages", "workbench", "/workbench", free);
|
|
101
124
|
get("tasks", "board", "/board", { limited: "collection" });
|
|
102
125
|
get("tasks", "review", "/review", { limited: "collection" });
|
|
103
|
-
get("settings", "spend", "/spend", free);
|
|
126
|
+
get("settings", "spend", "/spend", { ...free, ...anyOperator("An instance operator sees spend.", "/") });
|
|
104
127
|
get("pages", "ledger", "/ledger", { limited: "unscoped", ...free });
|
|
105
128
|
get("pages", "activity", "/activity");
|
|
106
129
|
get("tasks", "done", "/done", { limited: "collection" });
|
|
@@ -112,7 +135,6 @@ get("tasks", "queue", "/queue");
|
|
|
112
135
|
get("pages", "peek", "/peek");
|
|
113
136
|
get("pages", "fleet", "/fleet", free);
|
|
114
137
|
get("tasks", "tasks.new", "/tasks/new", { limited: "collection", ...free });
|
|
115
|
-
get("tasks", "task.live", `^\\/t\\/${TASK}\\/live$`, { project: "task", limited: "resource", ...free, sample: "/t/one/live" });
|
|
116
138
|
get("tasks", "task.page", `^\\/t\\/${TASK}$`, { project: "task", limited: "resource", ...free, sample: "/t/one" });
|
|
117
139
|
get("tasks", "task.evidence", `^\\/t\\/${TASK}\\/evidence$`, { project: "task", limited: "resource", ...free, sample: "/t/one/evidence" });
|
|
118
140
|
get("pages", "ledger.export", "/ledger/export", { limited: "unscoped", ...free });
|
|
@@ -126,12 +148,14 @@ get("flows", "flows.gallery", "^\\/flows\\/new\\/[a-z-]{1,40}$", { limited: "res
|
|
|
126
148
|
get("flows", "flows.page", "/flows", { limited: "resource", ...free });
|
|
127
149
|
get("flows", "kits", "/kits");
|
|
128
150
|
get("flows", "kit.page", "^\\/kits\\/[a-z-]{1,40}$", { sample: "/kits/support" });
|
|
129
|
-
get("flows", "
|
|
130
|
-
|
|
131
|
-
get("flows", "
|
|
151
|
+
get("flows", "subagents", "/settings/lead/subagents", free);
|
|
152
|
+
// D5: subagents were teammates; old links to them land on the same pages under Settings → Lead.
|
|
153
|
+
get("flows", "subagents.legacy", "^\\/teammates(\\/[1-9][0-9]{0,9}(\\/soul\\.md)?)?$", { ...free, sample: "/teammates/1" });
|
|
154
|
+
get("flows", "subagent.page", `^\\/settings\\/lead\\/subagents\\/[1-9][0-9]{0,9}$`, { ...free, sample: "/settings/lead/subagents/1" });
|
|
155
|
+
get("flows", "subagent.soul-file", `^\\/settings\\/lead\\/subagents\\/[1-9][0-9]{0,9}\\/soul\\.md$`, { ...free, sample: "/settings/lead/subagents/1/soul.md" });
|
|
156
|
+
get("flows", "flow.insights", `^\\/flows\\/${FLOW}\\/insights$`, { project: "flow", limited: "resource", ...free, sample: "/flows/1/insights" });
|
|
157
|
+
get("flows", "flow.run", `^\\/flows\\/${FLOW}\\/runs\\/${FLOW}\\/${FLOW}$`, { project: "flow", limited: "resource", ...free, sample: "/flows/1/runs/2/3" });
|
|
132
158
|
get("flows", "flow.export", `^\\/flows\\/${FLOW}\\/export$`, { project: "flow", limited: "resource", ...free, sample: "/flows/1/export" });
|
|
133
|
-
get("flows", "flow.live", `^\\/flows\\/${FLOW}\\/live$`, { callers: ["cookie"], project: "flow", limited: "resource", ...free, sample: "/flows/1/live",
|
|
134
|
-
callerRefusal: { status: 403, type: "application/json", body: JSON.stringify({ error: "session" }) } });
|
|
135
159
|
get("flows", "flow.page", `^\\/flows\\/${FLOW}$`, { project: "flow", limited: "resource", ...free, sample: "/flows/1" });
|
|
136
160
|
get("flows", "recipes", "/recipes", { limited: "collection", ...free });
|
|
137
161
|
for (const one of ["run", "start", "new", "edit", "from-task", "preview", "export"])
|
|
@@ -140,26 +164,42 @@ for (const one of ["run", "start", "new", "edit", "from-task", "preview", "expor
|
|
|
140
164
|
get("flows", "recipes.other", "^\\/recipes\\/.*$", { sample: "/recipes/unknown" });
|
|
141
165
|
// v115: routines became scheduled flows; old links to them land on the flows page.
|
|
142
166
|
get("tasks", "routines", "/routines", { limited: "collection" });
|
|
143
|
-
|
|
144
|
-
|
|
167
|
+
// One live stream per page: every room it shows (server/live.ts). Each room admits its own callers; a token reads only the team room.
|
|
168
|
+
edge("live", "live", "GET", "/live", { callers: ["cookie", "bearer"], scope: "read", project: "adapter", proof: "adapter" });
|
|
169
|
+
get("chat", "chat.mate-status", "/chat/mate/status", { ...free, role: "approver", roleBrowser: true, roleRefusal: SESSION_JSON });
|
|
145
170
|
get("chat", "chat.demo-live", "/chat/demo/live", free);
|
|
146
171
|
get("chat", "chat.page", "/chat", { project: "conversation", limited: "conversation", ...free });
|
|
147
172
|
get("chat", "chat.ack", `^\\/chat\\/ack\\/${ID}$`, { sample: "/chat/ack/1" });
|
|
148
173
|
get("tasks", "routine.page", `^\\/routines\\/${ID}$`, { limited: "resource", sample: "/routines/1" });
|
|
149
|
-
get("settings", "control", "/control");
|
|
150
|
-
get("settings", "control.connection", "/control/connection");
|
|
151
|
-
get("chat", "chat.action", `^\\/chat\\/action\\/${ID}$`, { project: "proposal", limited: "proposal", ...free, sample: "/chat/action/1" });
|
|
174
|
+
get("settings", "control", "/control", anyApprover("Project setup requires an approver."));
|
|
175
|
+
get("settings", "control.connection", "/control/connection", anyApprover("Project setup requires an approver."));
|
|
176
|
+
get("chat", "chat.action", `^\\/chat\\/action\\/${ID}$`, { project: "proposal", limited: "proposal", ...free, sample: "/chat/action/1", ...approver("Sign in to review this action.", "/projects") });
|
|
177
|
+
const SETTINGS_PAGE_ROLES = {
|
|
178
|
+
integrations: approver("An installation approver sees integrations.", "/settings"),
|
|
179
|
+
monitoring: operator("An instance operator sets up monitoring.", "/settings"),
|
|
180
|
+
updates: operator("An instance operator updates Toolroll.", "/settings"),
|
|
181
|
+
retention: operator("An instance operator sets retention.", "/settings"),
|
|
182
|
+
storage: operator("An instance operator looks after storage.", "/settings"),
|
|
183
|
+
backups: operator("An instance operator looks after backups.", "/settings"),
|
|
184
|
+
data: operator("An instance operator exports data.", "/settings"),
|
|
185
|
+
"sign-in": operator("An instance operator sets up sign-in.", "/settings"),
|
|
186
|
+
lead: approver("An approver sets up the lead.", "/settings"),
|
|
187
|
+
teams: approver("An installation approver can connect Teams.", "/settings"),
|
|
188
|
+
discord: approver("An installation approver can connect Discord.", "/settings"),
|
|
189
|
+
slack: approver("An installation approver can connect Slack.", "/settings"),
|
|
190
|
+
"slack/manifest": approver("An installation approver can connect Slack.", "/settings"),
|
|
191
|
+
};
|
|
152
192
|
for (const one of ["models", "tools", "project", "approval", "policy", "sessions", "integrations", "monitoring", "updates", "retention", "storage", "pull-requests", "checks", "backups", "data", "sign-in", "lead", "teams", "discord", "slack", "slack/manifest"]) {
|
|
153
|
-
get(one === "sessions" ? "people" : "settings", `settings.${one}`, `/settings/${one}`, free);
|
|
193
|
+
get(one === "sessions" ? "people" : "settings", `settings.${one}`, `/settings/${one}`, { ...free, ...SETTINGS_PAGE_ROLES[one] });
|
|
154
194
|
}
|
|
155
195
|
get("settings", "settings.skills", "/settings/skills", { limited: "unscoped", ...free });
|
|
156
196
|
get("settings", "settings.flows", "/settings/flows", { limited: "collection", ...free });
|
|
157
197
|
get("settings", "settings.knowledge", "/settings/knowledge", { limited: "unscoped", ...free });
|
|
158
198
|
get("settings", "settings.learning", "/settings/learning", { limited: "unscoped", ...free });
|
|
159
|
-
get("settings", "settings.telegram", "/settings/telegram", { limited: "self", ...free });
|
|
199
|
+
get("settings", "settings.telegram", "/settings/telegram", { limited: "self", ...free, ...approver("An approver can pair their own phone.", "/settings") });
|
|
160
200
|
get("settings", "settings.page", "/settings", { limited: "unscoped", ...free });
|
|
161
|
-
get("pages", "health", "/health", free);
|
|
162
|
-
get("pages", "metrics", "/metrics", free);
|
|
201
|
+
get("pages", "health", "/health", { ...free, ...anyOperator("An instance operator reads server health.", "/") });
|
|
202
|
+
get("pages", "metrics", "/metrics", { ...free, ...anyOperator("An instance operator reads metrics.", "/") });
|
|
163
203
|
get("chat", "push.key", "/push/key");
|
|
164
204
|
get("tasks", "decision.page", `^\\/d\\/${ID}$`, { project: "decision", limited: "resource", ...free, sample: "/d/1" });
|
|
165
205
|
get("chat", "lead.status", "/lead/status", free);
|
|
@@ -167,74 +207,115 @@ get("chat", "chat.task-status", "/chat/task-status", free);
|
|
|
167
207
|
get("tasks", "decision.evidence", `^\\/d\\/${ID}\\/evidence\\/${ID}$`, { project: "decision", limited: "resource", ...free, sample: "/d/1/evidence/2" });
|
|
168
208
|
// ---- console actions -------------------------------------------------------------------------------------------
|
|
169
209
|
// The scripted demo: the handler answers a non-browser caller "no page here".
|
|
170
|
-
post("chat", "chat.demo
|
|
210
|
+
post("chat", "chat.demo.ask", "/chat/demo/ask");
|
|
211
|
+
for (const one of ["approve", "change", "revise", "complete"])
|
|
212
|
+
post("chat", `chat.demo.${one}`, `^\\/chat\\/demo\\/[0-9]{1,9}\\/${one}$`, { sample: `/chat/demo/1/${one}` });
|
|
171
213
|
post("settings", "provider.resume", "^\\/providers\\/[a-z]+\\/resume$", { sample: "/providers/codex/resume" });
|
|
172
214
|
post("settings", "settings.updates-dismiss", "/settings/updates/dismiss");
|
|
173
215
|
post("settings", "settings.updates-checks", "/settings/updates/checks");
|
|
174
|
-
|
|
175
|
-
post("
|
|
176
|
-
|
|
177
|
-
post("
|
|
178
|
-
|
|
179
|
-
post("
|
|
180
|
-
post("
|
|
181
|
-
post("
|
|
216
|
+
const CODING_ACT = { scope: "step-up", project: "coding", limited: "self" };
|
|
217
|
+
post("tasks", "code.start", "/code/start", CODING_ACT);
|
|
218
|
+
for (const one of ["send", "stop", "resume", "recover", "continue", "ship", "answer"])
|
|
219
|
+
post("tasks", `code.${one}-send`, `^\\/code\\/${SESSION}\\/${one}$`, { ...CODING_ACT, sample: `/code/${"a".repeat(32)}/${one}` });
|
|
220
|
+
// Any other coding action answers "not found" after the same access check.
|
|
221
|
+
post("tasks", "code.act-other", "^\\/code(\\/.*)?$", { ...CODING_ACT, sample: "/code/unknown" });
|
|
222
|
+
post("settings", "settings.skills-revise", "/settings/skills/revise", { project: "form", limited: "unscoped", ...approver("Sign in to revise a test.", "/settings/skills") });
|
|
223
|
+
post("settings", "settings.skills-import", "/settings/skills/import", { project: "form", limited: "unscoped", bodyCap: 2 * 1024 * 1024, ...approver("Sign in as an approver to manage skills.", "/settings/skills") });
|
|
224
|
+
post("settings", "settings.skills-change", "/settings/skills/change", { project: "form", limited: "unscoped", ...approver("Sign in as an approver to manage skills.", "/settings/skills") });
|
|
225
|
+
for (const one of ["setup", "sample", "github"])
|
|
226
|
+
post("flows", `kit.${one}`, `^\\/kits\\/[a-z-]{1,40}\\/${one}$`, { project: "form", sample: `/kits/support/${one}`, ...approver("Sign in as an approver to set kits up.", "/kits") });
|
|
227
|
+
const SUBAGENTS = anyApprover("Sign in as an approver to look after subagents.", "/settings/lead/subagents");
|
|
228
|
+
post("flows", "subagent.new", "/settings/lead/subagents/new", { project: "form", ...SUBAGENTS });
|
|
229
|
+
for (const one of ["soul", "state", "note", "settings", "summary", "tools", "memory", "routines", "week"])
|
|
230
|
+
post("flows", `subagent.${one}`, `^\\/settings\\/lead\\/subagents\\/[1-9][0-9]{0,9}\\/${one}$`, { sample: `/settings/lead/subagents/1/${one}`, ...SUBAGENTS });
|
|
231
|
+
post("flows", "subagent.answer", "^\\/settings\\/lead\\/subagents\\/questions\\/[1-9][0-9]{0,9}\\/answer$", { sample: "/settings/lead/subagents/questions/1/answer" });
|
|
182
232
|
post("flows", "flows.gallery-create", "^\\/flows\\/new\\/[a-z-]{1,40}$", { project: "form", limited: "resource", sample: "/flows/new/triage" });
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
post("flows", "
|
|
187
|
-
post("flows", "
|
|
188
|
-
post("flows", "
|
|
189
|
-
|
|
190
|
-
post("flows",
|
|
191
|
-
|
|
192
|
-
post("flows",
|
|
193
|
-
post("flows", "flow.
|
|
194
|
-
|
|
195
|
-
post("flows",
|
|
196
|
-
post("flows", "flow.
|
|
197
|
-
post("flows", "flow.trigger.
|
|
198
|
-
post("flows", "flow.trigger.
|
|
199
|
-
post("flows", "flow.trigger.
|
|
200
|
-
post("
|
|
233
|
+
// Flow editing answers JSON: a caller without approver standing hears it in the editor's own format.
|
|
234
|
+
const CREATE_FLOWS = approver("Sign in as an approver to create flows.", "/flows");
|
|
235
|
+
const EDIT_FLOWS = { role: "approver", roleBrowser: true, roleRefusal: { status: 403, type: "application/json; charset=utf-8", body: JSON.stringify({ ok: false, said: "Sign in as an approver to change flows." }) } };
|
|
236
|
+
post("flows", "flows.create", "/flows/new", { project: "form", limited: "resource", ...CREATE_FLOWS });
|
|
237
|
+
post("flows", "flows.example", "/flows/example", { project: "form", ...CREATE_FLOWS });
|
|
238
|
+
post("flows", "flows.import", "/flows/import", { project: "form", limited: "resource", bodyCap: LARGE_FORM, ...CREATE_FLOWS });
|
|
239
|
+
for (const one of ["save", "cards", "archive", "scripts"])
|
|
240
|
+
post("flows", `flow.${one}`, `^\\/flows\\/${FLOW}\\/${one}$`, { project: "flow", limited: "resource", sample: `/flows/1/${one}`, ...EDIT_FLOWS });
|
|
241
|
+
for (const one of ["linear-key", "hooks-address", "secrets"])
|
|
242
|
+
post("flows", `flow.${one}`, `^\\/flows\\/${FLOW}\\/${one}$`, { scope: "step-up", project: "flow", sample: `/flows/1/${one}`, ...EDIT_FLOWS });
|
|
243
|
+
post("flows", "flow.triggers", `^\\/flows\\/${FLOW}\\/triggers$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers", ...EDIT_FLOWS });
|
|
244
|
+
for (const one of ["move", "decide", "cancel", "comment", "assign", "watch"])
|
|
245
|
+
post("flows", `flow.card.${one}`, `^\\/flows\\/${FLOW}\\/cards\\/${FLOW}\\/${one}$`, { project: "flow", limited: "resource", sample: `/flows/1/cards/2/${one}`, ...EDIT_FLOWS });
|
|
246
|
+
post("flows", "flow.card.choose", `^\\/flows\\/${FLOW}\\/cards\\/${FLOW}\\/choose$`, { project: "flow", sample: "/flows/1/cards/2/choose", ...EDIT_FLOWS });
|
|
247
|
+
post("flows", "flow.trigger.pause", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/pause$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers/2/pause", ...EDIT_FLOWS });
|
|
248
|
+
post("flows", "flow.trigger.resume", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/resume$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers/2/resume", ...EDIT_FLOWS });
|
|
249
|
+
post("flows", "flow.trigger.remove", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/remove$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers/2/remove", ...EDIT_FLOWS });
|
|
250
|
+
post("flows", "flow.trigger.check", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/check$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers/2/check", ...EDIT_FLOWS });
|
|
251
|
+
post("flows", "flow.trigger.press", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/press$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers/2/press", ...EDIT_FLOWS });
|
|
252
|
+
post("flows", "flow.trigger.renew", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/renew$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers/2/renew", ...EDIT_FLOWS });
|
|
253
|
+
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 });
|
|
254
|
+
post("flows", "flow.trigger.share", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/share$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers/2/share", ...EDIT_FLOWS });
|
|
255
|
+
post("flows", "flow.trigger.unshare", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/unshare$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers/2/unshare", ...EDIT_FLOWS });
|
|
256
|
+
post("settings", "spend.budget", "/spend/budget", { scope: "step-up", ...operator("An instance operator sets budgets.", "/spend") });
|
|
201
257
|
post("settings", "settings.project-concurrency", "/settings/project/concurrency");
|
|
202
|
-
post("settings", "settings.project-delete", "/settings/project/delete", { scope: "step-up" });
|
|
203
|
-
post("pages", "ledger.checkpoint", "/ledger/checkpoint");
|
|
258
|
+
post("settings", "settings.project-delete", "/settings/project/delete", { scope: "step-up", ...operator("An instance operator deletes projects.", "/settings/project") });
|
|
259
|
+
post("pages", "ledger.checkpoint", "/ledger/checkpoint", operator("An instance operator makes ledger checkpoints.", "/ledger"));
|
|
260
|
+
const SETTINGS_ACT_ROLES = {
|
|
261
|
+
policy: operator("An instance operator sets the policy.", "/settings/policy"),
|
|
262
|
+
approval: operator("An instance operator sets approval rules.", "/settings/approval"),
|
|
263
|
+
"request-limits": operator("An instance operator sets request limits.", "/settings/sessions"),
|
|
264
|
+
"sign-in": operator("An instance operator sets up sign-in.", "/settings"),
|
|
265
|
+
updates: operator("An instance operator updates Toolroll.", "/settings"),
|
|
266
|
+
"updates/seen": operator("An instance operator updates Toolroll.", "/settings"),
|
|
267
|
+
"updates/cancel": operator("An instance operator updates Toolroll.", "/settings"),
|
|
268
|
+
retention: operator("An instance operator sets retention.", "/settings"),
|
|
269
|
+
storage: operator("An instance operator looks after storage.", "/settings"),
|
|
270
|
+
"storage/clean": operator("An instance operator looks after storage.", "/settings"),
|
|
271
|
+
"storage/discard": operator("An instance operator looks after storage.", "/settings"),
|
|
272
|
+
"backups/now": operator("An instance operator looks after backups.", "/settings"),
|
|
273
|
+
backups: operator("An instance operator looks after backups.", "/settings"),
|
|
274
|
+
data: operator("An instance operator exports data.", "/settings"),
|
|
275
|
+
"integrations/test": approver("An installation approver tests integrations.", "/settings"),
|
|
276
|
+
monitoring: operator("An instance operator sets up monitoring.", "/settings"),
|
|
277
|
+
"tools/connect": approver("Sign in as an approver to connect tools.", "/settings/tools"),
|
|
278
|
+
"tools/change": approver("Sign in as an approver to manage tools.", "/settings/tools"),
|
|
279
|
+
};
|
|
204
280
|
for (const one of ["policy", "approval", "request-limits", "sessions", "sign-in", "updates", "updates/seen", "updates/cancel", "retention", "storage", "storage/clean", "storage/discard", "pull-requests", "checks", "backups/now", "backups", "data", "integrations/test", "monitoring", "tools/connect", "tools/change", "appearance"]) {
|
|
205
|
-
post(one === "sessions" ? "people" : "settings", `settings.${one.replaceAll("/", "-")}-send`, `/settings/${one}`, { ...(["policy", "approval", "request-limits", "sessions", "sign-in", "updates", "updates/seen", "updates/cancel", "retention", "storage", "storage/clean", "storage/discard", "pull-requests", "checks", "backups", "data", "monitoring", "tools/connect", "tools/change"].includes(one) ? { scope: "step-up" } : {}) });
|
|
281
|
+
post(one === "sessions" ? "people" : "settings", `settings.${one.replaceAll("/", "-")}-send`, `/settings/${one}`, { ...(["policy", "approval", "request-limits", "sessions", "sign-in", "updates", "updates/seen", "updates/cancel", "retention", "storage", "storage/clean", "storage/discard", "pull-requests", "checks", "backups", "data", "monitoring", "tools/connect", "tools/change"].includes(one) ? { scope: "step-up" } : {}), ...SETTINGS_ACT_ROLES[one] });
|
|
206
282
|
}
|
|
207
|
-
post("settings", "settings.flows-on", "/settings/flows/on", { limited: "collection" });
|
|
283
|
+
post("settings", "settings.flows-on", "/settings/flows/on", { limited: "collection", ...approver("An approver switches starter flows on.", "/settings/flows") });
|
|
284
|
+
const MODELS = approver("Sign in as an approver to change models.", "/settings/models");
|
|
285
|
+
for (const one of ["check", "watch", "update", "agent"])
|
|
286
|
+
post("settings", `settings.models-${one}`, `/settings/models/${one}`, MODELS);
|
|
208
287
|
// Any other models action answers the models area's own refusal.
|
|
209
|
-
post("settings", "settings.models-act", "^\\/settings\\/models\\/.*$", { sample: "/settings/models/
|
|
210
|
-
post("settings", "settings.knowledge-refresh", "/settings/knowledge/refresh", { project: "form", limited: "unscoped" });
|
|
211
|
-
post("settings", "settings.knowledge-proposal", "/settings/knowledge/proposal", { project: "form" });
|
|
212
|
-
post("settings", "settings.knowledge-decision", "/settings/knowledge/decision", { project: "form" });
|
|
213
|
-
post("settings", "settings.knowledge-change", "/settings/knowledge/change", { project: "form", limited: "unscoped" });
|
|
288
|
+
post("settings", "settings.models-act", "^\\/settings\\/models\\/.*$", { sample: "/settings/models/unknown", ...MODELS });
|
|
289
|
+
post("settings", "settings.knowledge-refresh", "/settings/knowledge/refresh", { project: "form", limited: "unscoped", ...approver("Sign in as an approver to refresh project context.", "/settings/knowledge") });
|
|
290
|
+
post("settings", "settings.knowledge-proposal", "/settings/knowledge/proposal", { project: "form", ...approver("Sign in as an approver to decide memory proposals.", "/settings/knowledge") });
|
|
291
|
+
post("settings", "settings.knowledge-decision", "/settings/knowledge/decision", { project: "form", ...approver("Sign in as an approver to change project decisions.", "/settings/knowledge") });
|
|
292
|
+
post("settings", "settings.knowledge-change", "/settings/knowledge/change", { project: "form", limited: "unscoped", ...approver("Sign in as an approver to change project knowledge.", "/settings/knowledge") });
|
|
214
293
|
post("settings", "settings.learning-change", "/settings/learning/change", { project: "form", limited: "unscoped" });
|
|
215
294
|
for (const one of ["setup-preview", "setup-approve", "instructions-preview", "instructions-approve"])
|
|
216
295
|
post("settings", `control.${one}`, `/control/${one}`, { ...(one.endsWith("approve") ? { scope: "step-up" } : {}) });
|
|
217
|
-
for (const service of ["slack", "teams", "discord"])
|
|
218
|
-
|
|
219
|
-
post("settings",
|
|
296
|
+
for (const [service, name] of [["slack", "Slack"], ["teams", "Teams"], ["discord", "Discord"]]) {
|
|
297
|
+
for (const one of ["connect", "pair", "unpair", "disconnect", "alerts"])
|
|
298
|
+
post("settings", `settings.${service}-${one}`, `/settings/${service}/${one}`, { scope: "step-up", ...approver(`An installation approver can connect ${name}.`, "/settings") });
|
|
299
|
+
}
|
|
300
|
+
post("settings", "settings.telegram-retry", "/settings/telegram/retry", { limited: "self", scope: "step-up", ...approver("An approver can retry their own replies.", "/settings") });
|
|
220
301
|
for (const one of ["confirm", "save", "off"])
|
|
221
|
-
post("settings", `settings.chat-approval-${one}`, `/settings/chat-approval/${one}`, { limited: "self", callers: ["cookie"], ...(one === "save" ? { scope: "step-up" } : {}) });
|
|
222
|
-
post("settings", "settings.telegram-pair", "/settings/telegram/pair", { limited: "self", scope: "step-up" });
|
|
223
|
-
post("settings", "settings.telegram-unpair", "/settings/telegram/unpair", { limited: "self", scope: "step-up" });
|
|
302
|
+
post("settings", `settings.chat-approval-${one}`, `/settings/chat-approval/${one}`, { limited: "self", callers: ["cookie"], ...(one === "save" ? { scope: "step-up" } : {}), ...approver("An approver turns on approving from their own chat.", "/settings") });
|
|
303
|
+
post("settings", "settings.telegram-pair", "/settings/telegram/pair", { limited: "self", scope: "step-up", ...approver("An approver can pair their own phone.", "/settings") });
|
|
304
|
+
post("settings", "settings.telegram-unpair", "/settings/telegram/unpair", { limited: "self", scope: "step-up", ...approver("An approver can pair their own phone.", "/settings") });
|
|
224
305
|
for (const one of ["messaging", "permission-default", "quality-default", "notifications", "notifications/mute", "telegram-digest", "provider-key", "provider-key-clear", "telegram-token", "email", "email-test", "email-read-test", "google", "google/disconnect"]) {
|
|
225
306
|
post("settings", `settings.${one.replaceAll("/", "-")}-send`, `/settings/${one}`);
|
|
226
307
|
}
|
|
227
308
|
post("pages", "projects.select", "/projects/select", { project: "form-path", limited: "unscoped", viewer: true });
|
|
228
|
-
post("pages", "projects.remove", "/projects/remove", { project: "form" });
|
|
309
|
+
post("pages", "projects.remove", "/projects/remove", { project: "form", ...operator("An instance operator removes projects.", "/settings/project") });
|
|
229
310
|
post("pages", "projects.open", "/projects/open", { project: "form-path" });
|
|
230
|
-
post("tasks", "tasks.add", "/tasks/add", { project: "form", limited: "collection" });
|
|
311
|
+
post("tasks", "tasks.add", "/tasks/add", { project: "form", limited: "collection", bodyCap: LARGE_FORM });
|
|
231
312
|
post("tasks", "queue.move", "/queue/move");
|
|
232
313
|
post("tasks", "queue.note", "/queue/note");
|
|
233
314
|
post("pages", "fleet.register", "/fleet/runner/register", { scope: "step-up" });
|
|
234
315
|
post("settings", "mode.confirm", "/mode/confirm", { scope: "step-up" });
|
|
235
316
|
post("settings", "mode.sign", "/mode/sign", { scope: "step-up" });
|
|
236
317
|
post("settings", "mode.revoke", "/mode/revoke");
|
|
237
|
-
post("people", "people.projects", "/people/projects", { scope: "step-up" });
|
|
318
|
+
post("people", "people.projects", "/people/projects", { scope: "step-up", ...operator("An instance operator manages project access.", "/people") });
|
|
238
319
|
post("people", "people.invite", "/people/invite", { scope: "step-up" });
|
|
239
320
|
post("people", "people.invite-revoke", "/people/invite-revoke", { scope: "step-up" });
|
|
240
321
|
post("people", "people.revoke", "/people/revoke", { scope: "step-up" });
|
|
@@ -245,7 +326,7 @@ post("tasks", "task.act.hold", `^\\/t\\/${TASK}\\/hold$`, { project: "task", lim
|
|
|
245
326
|
post("tasks", "task.act.unhold", `^\\/t\\/${TASK}\\/unhold$`, { project: "task", limited: "resource", sample: "/t/one/unhold" });
|
|
246
327
|
post("tasks", "task.act.requeue", `^\\/t\\/${TASK}\\/requeue$`, { project: "task", limited: "resource", sample: "/t/one/requeue" });
|
|
247
328
|
post("tasks", "task.act.cancel", `^\\/t\\/${TASK}\\/cancel$`, { project: "task", limited: "resource", sample: "/t/one/cancel" });
|
|
248
|
-
post("tasks", "task.act.scope", `^\\/t\\/${TASK}\\/scope$`, { project: "task", limited: "resource", sample: "/t/one/scope" });
|
|
329
|
+
post("tasks", "task.act.scope", `^\\/t\\/${TASK}\\/scope$`, { project: "task", limited: "resource", sample: "/t/one/scope", bodyCap: LARGE_FORM });
|
|
249
330
|
post("tasks", "task.act.approve", `^\\/t\\/${TASK}\\/approve$`, { scope: "step-up", project: "task", limited: "resource", sample: "/t/one/approve" });
|
|
250
331
|
post("tasks", "task.act.plan", `^\\/t\\/${TASK}\\/plan$`, { project: "task", limited: "resource", sample: "/t/one/plan" });
|
|
251
332
|
post("tasks", "task.act.plan-edit", `^\\/t\\/${TASK}\\/plan-edit$`, { project: "task", limited: "resource", sample: "/t/one/plan-edit" });
|
|
@@ -257,33 +338,38 @@ post("tasks", "task.act.accept-revision", `^\\/t\\/${TASK}\\/accept-revision$`,
|
|
|
257
338
|
post("tasks", "task.act.reject-revision", `^\\/t\\/${TASK}\\/reject-revision$`, { scope: "step-up", project: "task", limited: "resource", sample: "/t/one/reject-revision" });
|
|
258
339
|
post("tasks", "task.act.route", `^\\/t\\/${TASK}\\/route$`, { project: "task", limited: "resource", sample: "/t/one/route" });
|
|
259
340
|
post("tasks", "task.act.retry-review", `^\\/t\\/${TASK}\\/retry-review$`, { project: "task", limited: "resource", sample: "/t/one/retry-review" });
|
|
260
|
-
post("tasks", "task.act.complete", `^\\/t\\/${TASK}\\/complete$`, { project: "task", limited: "resource", sample: "/t/one/complete" });
|
|
261
|
-
post("tasks", "task.act.merge", `^\\/t\\/${TASK}\\/merge$`, { project: "task", limited: "resource", sample: "/t/one/merge" });
|
|
262
|
-
post("tasks", "task.act.confirm-stopped", `^\\/t\\/${TASK}\\/confirm-stopped$`, { scope: "step-up", project: "task", limited: "resource", sample: "/t/one/confirm-stopped" });
|
|
341
|
+
post("tasks", "task.act.complete", `^\\/t\\/${TASK}\\/complete$`, { project: "task", limited: "resource", sample: "/t/one/complete", ...approver("Only an approver can mark a result complete.") });
|
|
342
|
+
post("tasks", "task.act.merge", `^\\/t\\/${TASK}\\/merge$`, { project: "task", limited: "resource", sample: "/t/one/merge", ...approver("Only an approver can merge.") });
|
|
343
|
+
post("tasks", "task.act.confirm-stopped", `^\\/t\\/${TASK}\\/confirm-stopped$`, { scope: "step-up", project: "task", limited: "resource", sample: "/t/one/confirm-stopped", ...approver("Only an approver can confirm a build stopped.") });
|
|
263
344
|
post("tasks", "task.act.stop", `^\\/t\\/${TASK}\\/stop$`, { project: "task", limited: "resource", sample: "/t/one/stop" });
|
|
264
345
|
post("tasks", "task.act.resume-arm", `^\\/t\\/${TASK}\\/resume-arm$`, { project: "task", limited: "resource", sample: "/t/one/resume-arm" });
|
|
265
346
|
post("tasks", "task.act.resume", `^\\/t\\/${TASK}\\/resume$`, { scope: "step-up", project: "task", limited: "resource", sample: "/t/one/resume" });
|
|
266
|
-
|
|
347
|
+
for (const one of ["block", "unblock", "repair-dependency", "follow-up"])
|
|
348
|
+
post("tasks", `task.act.${one}`, `^\\/t\\/${TASK}\\/${one}$`, { project: "task", sample: `/t/one/${one}` });
|
|
267
349
|
// Onboarding names a GitHub repository, not a local project. Its root-index/nonce ceremony proves the future destination.
|
|
268
350
|
post("pages", "projects.onboard-preview", "/projects/onboard-preview", { project: "none" });
|
|
269
351
|
post("pages", "projects.onboard-confirm", "/projects/onboard-confirm", { scope: "step-up", project: "none" });
|
|
270
352
|
post("chat", "push.subscribe", "/push/subscribe", { scope: "step-up" });
|
|
271
353
|
post("chat", "push.remove", "/push/remove");
|
|
272
|
-
post("chat", "onboarding.phone-dismiss", "/onboarding/phone/dismiss");
|
|
354
|
+
post("chat", "onboarding.phone-dismiss", "/onboarding/phone/dismiss", approver("An approver puts this away.", "/chat"));
|
|
355
|
+
const LEAD_WORDS = { identity: "An approver names their lead.", about: "An approver edits what their lead knows about them.", "promise/cancel": "An approver manages the lead's promises.", on: "An approver turns the lead on." };
|
|
273
356
|
for (const one of ["identity", "about", "promise/cancel", "on"])
|
|
274
|
-
post("settings", `settings.lead-${one.replaceAll("/", "-")}`, `/settings/lead/${one}
|
|
357
|
+
post("settings", `settings.lead-${one.replaceAll("/", "-")}`, `/settings/lead/${one}`, approver(LEAD_WORDS[one], "/settings/lead"));
|
|
275
358
|
post("chat", "chat.config", "/chat/config", { scope: "step-up" });
|
|
276
359
|
for (const one of ["mint", "follow", "end", "stop"])
|
|
277
360
|
post("chat", `chat.mate-${one}`, `/chat/mate/${one}`, { callers: ["cookie"], ...(one === "mint" ? { scope: "step-up" } : {}) });
|
|
278
|
-
|
|
279
|
-
post("chat",
|
|
361
|
+
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}` });
|
|
363
|
+
for (const one of ["confirm", "dismiss"])
|
|
364
|
+
post("chat", `coordinator.proposal.${one}`, `^\\/proposals\\/${ID}\\/${one}$`, { sample: `/proposals/1/${one}` });
|
|
280
365
|
post("chat", "chat.send", "/chat", { scope: "step-up", project: "conversation" });
|
|
281
366
|
post("chat", "chat.file", "^\\/chat\\/file\\/[0-9a-f]{32}$", { scope: "step-up", sample: `/chat/file/${"a".repeat(32)}` });
|
|
282
367
|
post("chat", "chat.ack-send", `^\\/chat\\/ack\\/${ID}$`, { scope: "step-up", sample: "/chat/ack/1" });
|
|
283
368
|
for (const one of ["prepare", "preview", "import", "save"])
|
|
284
369
|
post("flows", `recipes.${one}-send`, `/recipes/${one}`, { limited: "collection" });
|
|
285
370
|
post("flows", "recipes.launch-send", "/recipes/launch", { limited: "collection" });
|
|
286
|
-
|
|
371
|
+
for (const one of ["note", "comment", "revise", "draft-repair", "checks", "add-tests"])
|
|
372
|
+
post("tasks", `run.act.${one}`, `^\\/r\\/${ID}\\/${one}$`, { project: "run", limited: "resource", sample: `/r/1/${one}` });
|
|
287
373
|
post("tasks", "session.editor-links", "/session/editor-links", { viewer: true });
|
|
288
374
|
post("tasks", "incident.resolve", `^\\/i\\/${ID}\\/resolve$`, { project: "incident", sample: "/i/1/resolve" });
|
|
289
375
|
/** The sole route table, in match order. */
|
|
@@ -321,6 +407,10 @@ export function assertRouteTable(routes = ROUTES) {
|
|
|
321
407
|
throw new Error(`route table: ${route.id} cannot bypass console admission`);
|
|
322
408
|
if (route.callers.length === 0)
|
|
323
409
|
throw new Error(`route table: ${route.id} admits no caller`);
|
|
410
|
+
if ((route.role === "any") !== (route.roleRefusal === undefined))
|
|
411
|
+
throw new Error(`route table: ${route.id} role and its refusal go together`);
|
|
412
|
+
if (route.role !== "any" && route.stage !== "console")
|
|
413
|
+
throw new Error(`route table: ${route.id} edge routes prove roles in their protocol`);
|
|
324
414
|
const test = new RegExp(route.pattern);
|
|
325
415
|
if (!test.test(route.sample))
|
|
326
416
|
throw new Error(`route table: ${route.id} does not match its sample ${route.sample}`);
|