toolroll 0.9.54 → 0.9.55
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 +9 -0
- package/README.md +5 -4
- package/dist/agent-onboard.js +4 -1
- package/dist/approval-rules-ui.d.ts +3 -2
- package/dist/approval-rules-ui.js +15 -20
- 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 +2 -3
- package/dist/browser-workspace.js +0 -3
- package/dist/chat-polish.d.ts +5 -4
- package/dist/chat-polish.js +9 -10
- 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 +9 -3
- 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/cli.d.ts +1 -3
- package/dist/contracts/console-api.d.ts +1 -1
- package/dist/contracts/console-api.js +6 -5
- package/dist/contracts/health.d.ts +1 -3
- package/dist/control-ui.d.ts +5 -4
- package/dist/control-ui.js +9 -34
- 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/discord-settings.d.ts +3 -2
- package/dist/discord-settings.js +17 -42
- package/dist/envelope.js +10 -0
- 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/flow-gallery-ui.d.ts +4 -3
- package/dist/flow-gallery-ui.js +28 -43
- package/dist/flow-starters-ui.d.ts +2 -1
- package/dist/flow-starters-ui.js +12 -15
- package/dist/flow-triggers.d.ts +1 -0
- package/dist/flow-triggers.js +16 -14
- package/dist/flows-ui.d.ts +4 -3
- package/dist/flows-ui.js +19 -30
- package/dist/flows.js +2 -1
- package/dist/guides.js +108 -3
- 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/kits-ui.d.ts +5 -4
- package/dist/kits-ui.js +18 -20
- package/dist/knowledge-ui.d.ts +6 -5
- package/dist/knowledge-ui.js +26 -25
- package/dist/lead-context.d.ts +11 -1
- package/dist/lead-context.js +6 -8
- package/dist/ledger-view.d.ts +2 -1
- package/dist/ledger-view.js +27 -29
- package/dist/limits-ui.d.ts +2 -1
- package/dist/limits-ui.js +7 -7
- package/dist/live-bus.d.ts +50 -8
- package/dist/live-bus.js +133 -50
- package/dist/mcp-oauth.js +35 -36
- 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/openrouter-models.d.ts +2 -1
- package/dist/openrouter-models.js +5 -9
- package/dist/operate.js +11 -7
- 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/project-delete-ui.d.ts +4 -3
- package/dist/project-delete-ui.js +15 -27
- 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/serve.d.ts +11 -2
- package/dist/serve.js +69 -58
- package/dist/server/chat.d.ts +3 -6
- package/dist/server/chat.js +969 -1006
- 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 +840 -766
- package/dist/server/guards.d.ts +1 -1
- package/dist/server/guards.js +5 -2
- 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 +580 -572
- package/dist/server/remote.d.ts +4 -7
- package/dist/server/remote.js +37 -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 +147 -0
- package/dist/server/render-settings.js +577 -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 +163 -75
- package/dist/server/runtime.d.ts +15 -7
- 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 +2181 -2117
- 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 +1486 -1418
- package/dist/server-telemetry.d.ts +4 -3
- package/dist/server-telemetry.js +4 -8
- 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-settings.d.ts +3 -2
- package/dist/slack-settings.js +17 -42
- package/dist/spend-ui.d.ts +2 -1
- package/dist/spend-ui.js +9 -32
- 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/surface.js +14 -3
- 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-http.d.ts +5 -9
- package/dist/team-http.js +5 -63
- package/dist/team-ui.d.ts +2 -1
- package/dist/team-ui.js +2 -5
- package/dist/teammates-ui.d.ts +5 -4
- package/dist/teammates-ui.js +61 -90
- package/dist/teams-settings.d.ts +3 -2
- package/dist/teams-settings.js +20 -31
- package/dist/telegram-settings.d.ts +3 -2
- package/dist/telegram-settings.js +14 -25
- package/dist/toolroll-update-ui.d.ts +3 -2
- package/dist/toolroll-update-ui.js +35 -39
- 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 -1
- package/dist/server/shared.d.ts +0 -3151
- package/dist/server/shared.js +0 -12001
|
@@ -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 });
|
|
@@ -127,11 +149,11 @@ 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
151
|
get("flows", "teammates", "/teammates", free);
|
|
130
|
-
get("flows", "teammate.page", `^\\/teammates\\/[1-9][0-9]{0,9}
|
|
131
|
-
get("flows", "
|
|
152
|
+
get("flows", "teammate.page", `^\\/teammates\\/[1-9][0-9]{0,9}$`, { ...free, sample: "/teammates/1" });
|
|
153
|
+
get("flows", "teammate.soul-file", `^\\/teammates\\/[1-9][0-9]{0,9}\\/soul\\.md$`, { ...free, sample: "/teammates/1/soul.md" });
|
|
154
|
+
get("flows", "flow.insights", `^\\/flows\\/${FLOW}\\/insights$`, { project: "flow", limited: "resource", ...free, sample: "/flows/1/insights" });
|
|
155
|
+
get("flows", "flow.run", `^\\/flows\\/${FLOW}\\/runs\\/${FLOW}\\/${FLOW}$`, { project: "flow", limited: "resource", ...free, sample: "/flows/1/runs/2/3" });
|
|
132
156
|
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
157
|
get("flows", "flow.page", `^\\/flows\\/${FLOW}$`, { project: "flow", limited: "resource", ...free, sample: "/flows/1" });
|
|
136
158
|
get("flows", "recipes", "/recipes", { limited: "collection", ...free });
|
|
137
159
|
for (const one of ["run", "start", "new", "edit", "from-task", "preview", "export"])
|
|
@@ -140,26 +162,42 @@ for (const one of ["run", "start", "new", "edit", "from-task", "preview", "expor
|
|
|
140
162
|
get("flows", "recipes.other", "^\\/recipes\\/.*$", { sample: "/recipes/unknown" });
|
|
141
163
|
// v115: routines became scheduled flows; old links to them land on the flows page.
|
|
142
164
|
get("tasks", "routines", "/routines", { limited: "collection" });
|
|
143
|
-
|
|
144
|
-
|
|
165
|
+
// 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.
|
|
166
|
+
edge("live", "live", "GET", "/live", { callers: ["cookie", "bearer"], scope: "read", project: "adapter", proof: "adapter" });
|
|
167
|
+
get("chat", "chat.mate-status", "/chat/mate/status", { ...free, role: "approver", roleBrowser: true, roleRefusal: SESSION_JSON });
|
|
145
168
|
get("chat", "chat.demo-live", "/chat/demo/live", free);
|
|
146
169
|
get("chat", "chat.page", "/chat", { project: "conversation", limited: "conversation", ...free });
|
|
147
170
|
get("chat", "chat.ack", `^\\/chat\\/ack\\/${ID}$`, { sample: "/chat/ack/1" });
|
|
148
171
|
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" });
|
|
172
|
+
get("settings", "control", "/control", anyApprover("Project setup requires an approver."));
|
|
173
|
+
get("settings", "control.connection", "/control/connection", anyApprover("Project setup requires an approver."));
|
|
174
|
+
get("chat", "chat.action", `^\\/chat\\/action\\/${ID}$`, { project: "proposal", limited: "proposal", ...free, sample: "/chat/action/1", ...approver("Sign in to review this action.", "/projects") });
|
|
175
|
+
const SETTINGS_PAGE_ROLES = {
|
|
176
|
+
integrations: approver("An installation approver sees integrations.", "/settings"),
|
|
177
|
+
monitoring: operator("An instance operator sets up monitoring.", "/settings"),
|
|
178
|
+
updates: operator("An instance operator updates Toolroll.", "/settings"),
|
|
179
|
+
retention: operator("An instance operator sets retention.", "/settings"),
|
|
180
|
+
storage: operator("An instance operator looks after storage.", "/settings"),
|
|
181
|
+
backups: operator("An instance operator looks after backups.", "/settings"),
|
|
182
|
+
data: operator("An instance operator exports data.", "/settings"),
|
|
183
|
+
"sign-in": operator("An instance operator sets up sign-in.", "/settings"),
|
|
184
|
+
lead: approver("An approver sets up the lead.", "/settings"),
|
|
185
|
+
teams: approver("An installation approver can connect Teams.", "/settings"),
|
|
186
|
+
discord: approver("An installation approver can connect Discord.", "/settings"),
|
|
187
|
+
slack: approver("An installation approver can connect Slack.", "/settings"),
|
|
188
|
+
"slack/manifest": approver("An installation approver can connect Slack.", "/settings"),
|
|
189
|
+
};
|
|
152
190
|
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);
|
|
191
|
+
get(one === "sessions" ? "people" : "settings", `settings.${one}`, `/settings/${one}`, { ...free, ...SETTINGS_PAGE_ROLES[one] });
|
|
154
192
|
}
|
|
155
193
|
get("settings", "settings.skills", "/settings/skills", { limited: "unscoped", ...free });
|
|
156
194
|
get("settings", "settings.flows", "/settings/flows", { limited: "collection", ...free });
|
|
157
195
|
get("settings", "settings.knowledge", "/settings/knowledge", { limited: "unscoped", ...free });
|
|
158
196
|
get("settings", "settings.learning", "/settings/learning", { limited: "unscoped", ...free });
|
|
159
|
-
get("settings", "settings.telegram", "/settings/telegram", { limited: "self", ...free });
|
|
197
|
+
get("settings", "settings.telegram", "/settings/telegram", { limited: "self", ...free, ...approver("An approver can pair their own phone.", "/settings") });
|
|
160
198
|
get("settings", "settings.page", "/settings", { limited: "unscoped", ...free });
|
|
161
|
-
get("pages", "health", "/health", free);
|
|
162
|
-
get("pages", "metrics", "/metrics", free);
|
|
199
|
+
get("pages", "health", "/health", { ...free, ...anyOperator("An instance operator reads server health.", "/") });
|
|
200
|
+
get("pages", "metrics", "/metrics", { ...free, ...anyOperator("An instance operator reads metrics.", "/") });
|
|
163
201
|
get("chat", "push.key", "/push/key");
|
|
164
202
|
get("tasks", "decision.page", `^\\/d\\/${ID}$`, { project: "decision", limited: "resource", ...free, sample: "/d/1" });
|
|
165
203
|
get("chat", "lead.status", "/lead/status", free);
|
|
@@ -167,74 +205,115 @@ get("chat", "chat.task-status", "/chat/task-status", free);
|
|
|
167
205
|
get("tasks", "decision.evidence", `^\\/d\\/${ID}\\/evidence\\/${ID}$`, { project: "decision", limited: "resource", ...free, sample: "/d/1/evidence/2" });
|
|
168
206
|
// ---- console actions -------------------------------------------------------------------------------------------
|
|
169
207
|
// The scripted demo: the handler answers a non-browser caller "no page here".
|
|
170
|
-
post("chat", "chat.demo
|
|
208
|
+
post("chat", "chat.demo.ask", "/chat/demo/ask");
|
|
209
|
+
for (const one of ["approve", "change", "revise", "complete"])
|
|
210
|
+
post("chat", `chat.demo.${one}`, `^\\/chat\\/demo\\/[0-9]{1,9}\\/${one}$`, { sample: `/chat/demo/1/${one}` });
|
|
171
211
|
post("settings", "provider.resume", "^\\/providers\\/[a-z]+\\/resume$", { sample: "/providers/codex/resume" });
|
|
172
212
|
post("settings", "settings.updates-dismiss", "/settings/updates/dismiss");
|
|
173
213
|
post("settings", "settings.updates-checks", "/settings/updates/checks");
|
|
174
|
-
|
|
175
|
-
post("
|
|
176
|
-
|
|
177
|
-
post("
|
|
178
|
-
|
|
179
|
-
post("
|
|
180
|
-
post("
|
|
214
|
+
const CODING_ACT = { scope: "step-up", project: "coding", limited: "self" };
|
|
215
|
+
post("tasks", "code.start", "/code/start", CODING_ACT);
|
|
216
|
+
for (const one of ["send", "stop", "resume", "recover", "continue", "ship", "answer"])
|
|
217
|
+
post("tasks", `code.${one}-send`, `^\\/code\\/${SESSION}\\/${one}$`, { ...CODING_ACT, sample: `/code/${"a".repeat(32)}/${one}` });
|
|
218
|
+
// Any other coding action answers "not found" after the same access check.
|
|
219
|
+
post("tasks", "code.act-other", "^\\/code(\\/.*)?$", { ...CODING_ACT, sample: "/code/unknown" });
|
|
220
|
+
post("settings", "settings.skills-revise", "/settings/skills/revise", { project: "form", limited: "unscoped", ...approver("Sign in to revise a test.", "/settings/skills") });
|
|
221
|
+
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") });
|
|
222
|
+
post("settings", "settings.skills-change", "/settings/skills/change", { project: "form", limited: "unscoped", ...approver("Sign in as an approver to manage skills.", "/settings/skills") });
|
|
223
|
+
for (const one of ["setup", "sample", "github"])
|
|
224
|
+
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") });
|
|
225
|
+
const TEAMMATES = anyApprover("Sign in as an approver to look after teammates.", "/teammates");
|
|
226
|
+
post("flows", "teammate.new", "/teammates/new", { project: "form", ...TEAMMATES });
|
|
227
|
+
for (const one of ["soul", "state", "note", "settings", "summary", "tools", "memory", "routines", "week"])
|
|
228
|
+
post("flows", `teammate.${one}`, `^\\/teammates\\/[1-9][0-9]{0,9}\\/${one}$`, { sample: `/teammates/1/${one}`, ...TEAMMATES });
|
|
181
229
|
post("flows", "teammate.answer", "^\\/teammates\\/questions\\/[1-9][0-9]{0,9}\\/answer$", { sample: "/teammates/questions/1/answer" });
|
|
182
230
|
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("
|
|
231
|
+
// Flow editing answers JSON: a caller without approver standing hears it in the editor's own format.
|
|
232
|
+
const CREATE_FLOWS = approver("Sign in as an approver to create flows.", "/flows");
|
|
233
|
+
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." }) } };
|
|
234
|
+
post("flows", "flows.create", "/flows/new", { project: "form", limited: "resource", ...CREATE_FLOWS });
|
|
235
|
+
post("flows", "flows.example", "/flows/example", { project: "form", ...CREATE_FLOWS });
|
|
236
|
+
post("flows", "flows.import", "/flows/import", { project: "form", limited: "resource", bodyCap: LARGE_FORM, ...CREATE_FLOWS });
|
|
237
|
+
for (const one of ["save", "cards", "archive", "scripts"])
|
|
238
|
+
post("flows", `flow.${one}`, `^\\/flows\\/${FLOW}\\/${one}$`, { project: "flow", limited: "resource", sample: `/flows/1/${one}`, ...EDIT_FLOWS });
|
|
239
|
+
for (const one of ["linear-key", "hooks-address", "secrets"])
|
|
240
|
+
post("flows", `flow.${one}`, `^\\/flows\\/${FLOW}\\/${one}$`, { scope: "step-up", project: "flow", sample: `/flows/1/${one}`, ...EDIT_FLOWS });
|
|
241
|
+
post("flows", "flow.triggers", `^\\/flows\\/${FLOW}\\/triggers$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers", ...EDIT_FLOWS });
|
|
242
|
+
for (const one of ["move", "decide", "cancel", "comment", "assign", "watch"])
|
|
243
|
+
post("flows", `flow.card.${one}`, `^\\/flows\\/${FLOW}\\/cards\\/${FLOW}\\/${one}$`, { project: "flow", limited: "resource", sample: `/flows/1/cards/2/${one}`, ...EDIT_FLOWS });
|
|
244
|
+
post("flows", "flow.card.choose", `^\\/flows\\/${FLOW}\\/cards\\/${FLOW}\\/choose$`, { project: "flow", sample: "/flows/1/cards/2/choose", ...EDIT_FLOWS });
|
|
245
|
+
post("flows", "flow.trigger.pause", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/pause$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers/2/pause", ...EDIT_FLOWS });
|
|
246
|
+
post("flows", "flow.trigger.resume", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/resume$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers/2/resume", ...EDIT_FLOWS });
|
|
247
|
+
post("flows", "flow.trigger.remove", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/remove$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers/2/remove", ...EDIT_FLOWS });
|
|
248
|
+
post("flows", "flow.trigger.check", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/check$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers/2/check", ...EDIT_FLOWS });
|
|
249
|
+
post("flows", "flow.trigger.press", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/press$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers/2/press", ...EDIT_FLOWS });
|
|
250
|
+
post("flows", "flow.trigger.renew", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/renew$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers/2/renew", ...EDIT_FLOWS });
|
|
251
|
+
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 });
|
|
252
|
+
post("flows", "flow.trigger.share", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/share$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers/2/share", ...EDIT_FLOWS });
|
|
253
|
+
post("flows", "flow.trigger.unshare", `^\\/flows\\/${FLOW}\\/triggers\\/${FLOW}\\/unshare$`, { project: "flow", limited: "resource", sample: "/flows/1/triggers/2/unshare", ...EDIT_FLOWS });
|
|
254
|
+
post("settings", "spend.budget", "/spend/budget", { scope: "step-up", ...operator("An instance operator sets budgets.", "/spend") });
|
|
201
255
|
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");
|
|
256
|
+
post("settings", "settings.project-delete", "/settings/project/delete", { scope: "step-up", ...operator("An instance operator deletes projects.", "/settings/project") });
|
|
257
|
+
post("pages", "ledger.checkpoint", "/ledger/checkpoint", operator("An instance operator makes ledger checkpoints.", "/ledger"));
|
|
258
|
+
const SETTINGS_ACT_ROLES = {
|
|
259
|
+
policy: operator("An instance operator sets the policy.", "/settings/policy"),
|
|
260
|
+
approval: operator("An instance operator sets approval rules.", "/settings/approval"),
|
|
261
|
+
"request-limits": operator("An instance operator sets request limits.", "/settings/sessions"),
|
|
262
|
+
"sign-in": operator("An instance operator sets up sign-in.", "/settings"),
|
|
263
|
+
updates: operator("An instance operator updates Toolroll.", "/settings"),
|
|
264
|
+
"updates/seen": operator("An instance operator updates Toolroll.", "/settings"),
|
|
265
|
+
"updates/cancel": operator("An instance operator updates Toolroll.", "/settings"),
|
|
266
|
+
retention: operator("An instance operator sets retention.", "/settings"),
|
|
267
|
+
storage: operator("An instance operator looks after storage.", "/settings"),
|
|
268
|
+
"storage/clean": operator("An instance operator looks after storage.", "/settings"),
|
|
269
|
+
"storage/discard": operator("An instance operator looks after storage.", "/settings"),
|
|
270
|
+
"backups/now": operator("An instance operator looks after backups.", "/settings"),
|
|
271
|
+
backups: operator("An instance operator looks after backups.", "/settings"),
|
|
272
|
+
data: operator("An instance operator exports data.", "/settings"),
|
|
273
|
+
"integrations/test": approver("An installation approver tests integrations.", "/settings"),
|
|
274
|
+
monitoring: operator("An instance operator sets up monitoring.", "/settings"),
|
|
275
|
+
"tools/connect": approver("Sign in as an approver to connect tools.", "/settings/tools"),
|
|
276
|
+
"tools/change": approver("Sign in as an approver to manage tools.", "/settings/tools"),
|
|
277
|
+
};
|
|
204
278
|
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" } : {}) });
|
|
279
|
+
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
280
|
}
|
|
207
|
-
post("settings", "settings.flows-on", "/settings/flows/on", { limited: "collection" });
|
|
281
|
+
post("settings", "settings.flows-on", "/settings/flows/on", { limited: "collection", ...approver("An approver switches starter flows on.", "/settings/flows") });
|
|
282
|
+
const MODELS = approver("Sign in as an approver to change models.", "/settings/models");
|
|
283
|
+
for (const one of ["check", "watch", "update", "agent"])
|
|
284
|
+
post("settings", `settings.models-${one}`, `/settings/models/${one}`, MODELS);
|
|
208
285
|
// 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" });
|
|
286
|
+
post("settings", "settings.models-act", "^\\/settings\\/models\\/.*$", { sample: "/settings/models/unknown", ...MODELS });
|
|
287
|
+
post("settings", "settings.knowledge-refresh", "/settings/knowledge/refresh", { project: "form", limited: "unscoped", ...approver("Sign in as an approver to refresh project context.", "/settings/knowledge") });
|
|
288
|
+
post("settings", "settings.knowledge-proposal", "/settings/knowledge/proposal", { project: "form", ...approver("Sign in as an approver to decide memory proposals.", "/settings/knowledge") });
|
|
289
|
+
post("settings", "settings.knowledge-decision", "/settings/knowledge/decision", { project: "form", ...approver("Sign in as an approver to change project decisions.", "/settings/knowledge") });
|
|
290
|
+
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
291
|
post("settings", "settings.learning-change", "/settings/learning/change", { project: "form", limited: "unscoped" });
|
|
215
292
|
for (const one of ["setup-preview", "setup-approve", "instructions-preview", "instructions-approve"])
|
|
216
293
|
post("settings", `control.${one}`, `/control/${one}`, { ...(one.endsWith("approve") ? { scope: "step-up" } : {}) });
|
|
217
|
-
for (const service of ["slack", "teams", "discord"])
|
|
218
|
-
|
|
219
|
-
post("settings",
|
|
294
|
+
for (const [service, name] of [["slack", "Slack"], ["teams", "Teams"], ["discord", "Discord"]]) {
|
|
295
|
+
for (const one of ["connect", "pair", "unpair", "disconnect", "alerts"])
|
|
296
|
+
post("settings", `settings.${service}-${one}`, `/settings/${service}/${one}`, { scope: "step-up", ...approver(`An installation approver can connect ${name}.`, "/settings") });
|
|
297
|
+
}
|
|
298
|
+
post("settings", "settings.telegram-retry", "/settings/telegram/retry", { limited: "self", scope: "step-up", ...approver("An approver can retry their own replies.", "/settings") });
|
|
220
299
|
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" });
|
|
300
|
+
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") });
|
|
301
|
+
post("settings", "settings.telegram-pair", "/settings/telegram/pair", { limited: "self", scope: "step-up", ...approver("An approver can pair their own phone.", "/settings") });
|
|
302
|
+
post("settings", "settings.telegram-unpair", "/settings/telegram/unpair", { limited: "self", scope: "step-up", ...approver("An approver can pair their own phone.", "/settings") });
|
|
224
303
|
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
304
|
post("settings", `settings.${one.replaceAll("/", "-")}-send`, `/settings/${one}`);
|
|
226
305
|
}
|
|
227
306
|
post("pages", "projects.select", "/projects/select", { project: "form-path", limited: "unscoped", viewer: true });
|
|
228
|
-
post("pages", "projects.remove", "/projects/remove", { project: "form" });
|
|
307
|
+
post("pages", "projects.remove", "/projects/remove", { project: "form", ...operator("An instance operator removes projects.", "/settings/project") });
|
|
229
308
|
post("pages", "projects.open", "/projects/open", { project: "form-path" });
|
|
230
|
-
post("tasks", "tasks.add", "/tasks/add", { project: "form", limited: "collection" });
|
|
309
|
+
post("tasks", "tasks.add", "/tasks/add", { project: "form", limited: "collection", bodyCap: LARGE_FORM });
|
|
231
310
|
post("tasks", "queue.move", "/queue/move");
|
|
232
311
|
post("tasks", "queue.note", "/queue/note");
|
|
233
312
|
post("pages", "fleet.register", "/fleet/runner/register", { scope: "step-up" });
|
|
234
313
|
post("settings", "mode.confirm", "/mode/confirm", { scope: "step-up" });
|
|
235
314
|
post("settings", "mode.sign", "/mode/sign", { scope: "step-up" });
|
|
236
315
|
post("settings", "mode.revoke", "/mode/revoke");
|
|
237
|
-
post("people", "people.projects", "/people/projects", { scope: "step-up" });
|
|
316
|
+
post("people", "people.projects", "/people/projects", { scope: "step-up", ...operator("An instance operator manages project access.", "/people") });
|
|
238
317
|
post("people", "people.invite", "/people/invite", { scope: "step-up" });
|
|
239
318
|
post("people", "people.invite-revoke", "/people/invite-revoke", { scope: "step-up" });
|
|
240
319
|
post("people", "people.revoke", "/people/revoke", { scope: "step-up" });
|
|
@@ -245,7 +324,7 @@ post("tasks", "task.act.hold", `^\\/t\\/${TASK}\\/hold$`, { project: "task", lim
|
|
|
245
324
|
post("tasks", "task.act.unhold", `^\\/t\\/${TASK}\\/unhold$`, { project: "task", limited: "resource", sample: "/t/one/unhold" });
|
|
246
325
|
post("tasks", "task.act.requeue", `^\\/t\\/${TASK}\\/requeue$`, { project: "task", limited: "resource", sample: "/t/one/requeue" });
|
|
247
326
|
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" });
|
|
327
|
+
post("tasks", "task.act.scope", `^\\/t\\/${TASK}\\/scope$`, { project: "task", limited: "resource", sample: "/t/one/scope", bodyCap: LARGE_FORM });
|
|
249
328
|
post("tasks", "task.act.approve", `^\\/t\\/${TASK}\\/approve$`, { scope: "step-up", project: "task", limited: "resource", sample: "/t/one/approve" });
|
|
250
329
|
post("tasks", "task.act.plan", `^\\/t\\/${TASK}\\/plan$`, { project: "task", limited: "resource", sample: "/t/one/plan" });
|
|
251
330
|
post("tasks", "task.act.plan-edit", `^\\/t\\/${TASK}\\/plan-edit$`, { project: "task", limited: "resource", sample: "/t/one/plan-edit" });
|
|
@@ -257,33 +336,38 @@ post("tasks", "task.act.accept-revision", `^\\/t\\/${TASK}\\/accept-revision$`,
|
|
|
257
336
|
post("tasks", "task.act.reject-revision", `^\\/t\\/${TASK}\\/reject-revision$`, { scope: "step-up", project: "task", limited: "resource", sample: "/t/one/reject-revision" });
|
|
258
337
|
post("tasks", "task.act.route", `^\\/t\\/${TASK}\\/route$`, { project: "task", limited: "resource", sample: "/t/one/route" });
|
|
259
338
|
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" });
|
|
339
|
+
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.") });
|
|
340
|
+
post("tasks", "task.act.merge", `^\\/t\\/${TASK}\\/merge$`, { project: "task", limited: "resource", sample: "/t/one/merge", ...approver("Only an approver can merge.") });
|
|
341
|
+
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
342
|
post("tasks", "task.act.stop", `^\\/t\\/${TASK}\\/stop$`, { project: "task", limited: "resource", sample: "/t/one/stop" });
|
|
264
343
|
post("tasks", "task.act.resume-arm", `^\\/t\\/${TASK}\\/resume-arm$`, { project: "task", limited: "resource", sample: "/t/one/resume-arm" });
|
|
265
344
|
post("tasks", "task.act.resume", `^\\/t\\/${TASK}\\/resume$`, { scope: "step-up", project: "task", limited: "resource", sample: "/t/one/resume" });
|
|
266
|
-
|
|
345
|
+
for (const one of ["block", "unblock", "repair-dependency", "follow-up"])
|
|
346
|
+
post("tasks", `task.act.${one}`, `^\\/t\\/${TASK}\\/${one}$`, { project: "task", sample: `/t/one/${one}` });
|
|
267
347
|
// Onboarding names a GitHub repository, not a local project. Its root-index/nonce ceremony proves the future destination.
|
|
268
348
|
post("pages", "projects.onboard-preview", "/projects/onboard-preview", { project: "none" });
|
|
269
349
|
post("pages", "projects.onboard-confirm", "/projects/onboard-confirm", { scope: "step-up", project: "none" });
|
|
270
350
|
post("chat", "push.subscribe", "/push/subscribe", { scope: "step-up" });
|
|
271
351
|
post("chat", "push.remove", "/push/remove");
|
|
272
|
-
post("chat", "onboarding.phone-dismiss", "/onboarding/phone/dismiss");
|
|
352
|
+
post("chat", "onboarding.phone-dismiss", "/onboarding/phone/dismiss", approver("An approver puts this away.", "/chat"));
|
|
353
|
+
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
354
|
for (const one of ["identity", "about", "promise/cancel", "on"])
|
|
274
|
-
post("settings", `settings.lead-${one.replaceAll("/", "-")}`, `/settings/lead/${one}
|
|
355
|
+
post("settings", `settings.lead-${one.replaceAll("/", "-")}`, `/settings/lead/${one}`, approver(LEAD_WORDS[one], "/settings/lead"));
|
|
275
356
|
post("chat", "chat.config", "/chat/config", { scope: "step-up" });
|
|
276
357
|
for (const one of ["mint", "follow", "end", "stop"])
|
|
277
358
|
post("chat", `chat.mate-${one}`, `/chat/mate/${one}`, { callers: ["cookie"], ...(one === "mint" ? { scope: "step-up" } : {}) });
|
|
278
|
-
|
|
279
|
-
post("chat",
|
|
359
|
+
for (const one of ["confirm", "dismiss"])
|
|
360
|
+
post("chat", `chat.proposal.${one}`, `^\\/chat\\/proposal\\/${ID}\\/${one}$`, { project: "proposal", limited: "proposal", sample: `/chat/proposal/1/${one}` });
|
|
361
|
+
for (const one of ["confirm", "dismiss"])
|
|
362
|
+
post("chat", `coordinator.proposal.${one}`, `^\\/proposals\\/${ID}\\/${one}$`, { sample: `/proposals/1/${one}` });
|
|
280
363
|
post("chat", "chat.send", "/chat", { scope: "step-up", project: "conversation" });
|
|
281
364
|
post("chat", "chat.file", "^\\/chat\\/file\\/[0-9a-f]{32}$", { scope: "step-up", sample: `/chat/file/${"a".repeat(32)}` });
|
|
282
365
|
post("chat", "chat.ack-send", `^\\/chat\\/ack\\/${ID}$`, { scope: "step-up", sample: "/chat/ack/1" });
|
|
283
366
|
for (const one of ["prepare", "preview", "import", "save"])
|
|
284
367
|
post("flows", `recipes.${one}-send`, `/recipes/${one}`, { limited: "collection" });
|
|
285
368
|
post("flows", "recipes.launch-send", "/recipes/launch", { limited: "collection" });
|
|
286
|
-
|
|
369
|
+
for (const one of ["note", "comment", "revise", "draft-repair", "checks", "add-tests"])
|
|
370
|
+
post("tasks", `run.act.${one}`, `^\\/r\\/${ID}\\/${one}$`, { project: "run", limited: "resource", sample: `/r/1/${one}` });
|
|
287
371
|
post("tasks", "session.editor-links", "/session/editor-links", { viewer: true });
|
|
288
372
|
post("tasks", "incident.resolve", `^\\/i\\/${ID}\\/resolve$`, { project: "incident", sample: "/i/1/resolve" });
|
|
289
373
|
/** The sole route table, in match order. */
|
|
@@ -321,6 +405,10 @@ export function assertRouteTable(routes = ROUTES) {
|
|
|
321
405
|
throw new Error(`route table: ${route.id} cannot bypass console admission`);
|
|
322
406
|
if (route.callers.length === 0)
|
|
323
407
|
throw new Error(`route table: ${route.id} admits no caller`);
|
|
408
|
+
if ((route.role === "any") !== (route.roleRefusal === undefined))
|
|
409
|
+
throw new Error(`route table: ${route.id} role and its refusal go together`);
|
|
410
|
+
if (route.role !== "any" && route.stage !== "console")
|
|
411
|
+
throw new Error(`route table: ${route.id} edge routes prove roles in their protocol`);
|
|
324
412
|
const test = new RegExp(route.pattern);
|
|
325
413
|
if (!test.test(route.sample))
|
|
326
414
|
throw new Error(`route table: ${route.id} does not match its sample ${route.sample}`);
|