@zackbart/connecta 0.31.0 → 0.33.0
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 +97 -0
- package/README.md +30 -20
- package/bin/connecta.mjs +25 -6
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.d.ts +167 -23
- package/dist/access-tokens.js +676 -140
- package/dist/agent-identity.js +47 -0
- package/dist/auth/cloudflare-access.js +13 -5
- package/dist/auth-elicitation.js +1 -1
- package/dist/call-admission-context.js +15 -0
- package/dist/call-admission.js +30 -43
- package/dist/call-recovery.js +6 -2
- package/dist/catalog-service.js +7 -2
- package/dist/config-value-sources.js +14 -9
- package/dist/config.d.ts +58 -78
- package/dist/config.js +143 -47
- package/dist/connector-access.d.ts +4 -33
- package/dist/connector-access.js +15 -99
- package/dist/connector-caller.d.ts +7 -1
- package/dist/connector-caller.js +3 -2
- package/dist/connectors/api-connector.d.ts +27 -4
- package/dist/connectors/api-connector.js +2 -0
- package/dist/connectors/guarded-fetch.d.ts +9 -0
- package/dist/connectors/guarded-fetch.js +7 -0
- package/dist/connectors/remote-mcp.js +2 -0
- package/dist/describe-config.d.ts +32 -7
- package/dist/describe-config.js +9 -18
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +1 -0
- package/dist/execute.js +16 -14
- package/dist/index.d.ts +4 -3
- package/dist/index.js +30 -10
- package/dist/invocation.js +12 -7
- package/dist/membership.d.ts +51 -0
- package/dist/membership.js +121 -0
- package/dist/meta-tools.js +17 -19
- package/dist/module-contracts.d.ts +23 -6
- package/dist/operator-record.d.ts +2 -2
- package/dist/operator-record.js +2 -2
- package/dist/operator-ui/agents.d.ts +74 -0
- package/dist/operator-ui/agents.js +104 -0
- package/dist/operator-ui/contract.d.ts +151 -29
- package/dist/operator-ui/endpoints.js +192 -0
- package/dist/operator-ui/fix-prompts.js +4 -4
- package/dist/operator-ui/generated.js +5 -4
- package/dist/operator-ui/member-page.js +112 -0
- package/dist/operator-ui/model.d.ts +15 -7
- package/dist/operator-ui/view.d.ts +84 -19
- package/dist/operator-ui/view.js +83 -21
- package/dist/page-styles.js +1 -1
- package/dist/program-writes.js +2 -2
- package/dist/provider.d.ts +63 -11
- package/dist/provider.js +46 -7
- package/dist/providers/_shared/google/workspace.d.ts +53 -16
- package/dist/providers/_shared/google/workspace.js +31 -9
- package/dist/providers/_shared/rest/tools.js +2 -2
- package/dist/providers/_shared/rest/value-safety.d.ts +50 -0
- package/dist/providers/_shared/rest/value-safety.js +699 -7
- package/dist/providers/breeze/skill.generated.js +2 -2
- package/dist/providers/ccb/index.js +1 -1
- package/dist/providers/cloudflare/index.js +3 -3
- package/dist/providers/cloudflare/rest.js +25 -3
- package/dist/providers/cloudflare/skill.generated.js +2 -2
- package/dist/providers/cloudflare/value-safety.js +32 -0
- package/dist/providers/docs/index.d.ts +1 -1
- package/dist/providers/docs/index.js +5 -5
- package/dist/providers/docs/skill.generated.js +2 -2
- package/dist/providers/drive/index.d.ts +1 -1
- package/dist/providers/drive/index.js +4 -4
- package/dist/providers/drive/skill.generated.js +2 -2
- package/dist/providers/forms/index.d.ts +1 -1
- package/dist/providers/forms/index.js +5 -5
- package/dist/providers/forms/skill.generated.js +2 -2
- package/dist/providers/github/skill.generated.js +2 -2
- package/dist/providers/gmail/index.d.ts +1 -1
- package/dist/providers/gmail/index.js +279 -26
- package/dist/providers/gmail/skill.generated.js +2 -2
- package/dist/providers/linear/index.js +1 -1
- package/dist/providers/mixpanel/index.d.ts +27 -12
- package/dist/providers/mixpanel/index.js +58 -6
- package/dist/providers/mixpanel/operations.js +20 -0
- package/dist/providers/mixpanel/rest.js +603 -0
- package/dist/providers/mixpanel/skill.generated.js +3 -2
- package/dist/providers/mixpanel/value-safety.js +16 -0
- package/dist/providers/notion/index.d.ts +7 -0
- package/dist/providers/notion/index.js +116 -11
- package/dist/providers/notion/rest.js +183 -24
- package/dist/providers/notion/skill.generated.js +4 -4
- package/dist/providers/notion/value-safety.js +30 -5
- package/dist/providers/overflow/index.js +1 -1
- package/dist/providers/planning-center/index.d.ts +27 -0
- package/dist/providers/planning-center/index.js +287 -25
- package/dist/providers/planning-center/skill.generated.js +24 -4
- package/dist/providers/sheets/index.d.ts +4 -1
- package/dist/providers/sheets/index.js +9 -6
- package/dist/providers/sheets/skill.generated.js +2 -2
- package/dist/providers/slides/index.d.ts +1 -1
- package/dist/providers/slides/index.js +6 -6
- package/dist/providers/slides/skill.generated.js +2 -2
- package/dist/providers/stripe/rest.js +9 -2
- package/dist/providers/stripe/skill.generated.js +2 -2
- package/dist/providers/stripe/value-safety.js +22 -0
- package/dist/providers/vercel/index.js +2 -2
- package/dist/providers/vercel/rest.js +20 -2
- package/dist/providers/vercel/skill.generated.js +2 -2
- package/dist/providers/vercel/value-safety.js +22 -0
- package/dist/providers/wispr-flow/index.d.ts +14 -0
- package/dist/providers/wispr-flow/index.js +117 -0
- package/dist/providers/wispr-flow/skill.generated.js +8 -0
- package/dist/registry.d.ts +2 -3
- package/dist/registry.js +8 -9
- package/dist/routes/access-tokens.js +82 -75
- package/dist/routes/activity-disclosure.js +17 -18
- package/dist/routes/activity.js +34 -54
- package/dist/routes/actor-labels.js +33 -4
- package/dist/routes/agent-path.js +43 -0
- package/dist/routes/connect.js +16 -12
- package/dist/routes/credentials.js +16 -40
- package/dist/routes/mcp.js +155 -85
- package/dist/routes/oauth-management.js +11 -5
- package/dist/routes/oauth.js +3 -9
- package/dist/routes/operator.js +25 -17
- package/dist/routes/shared.d.ts +60 -20
- package/dist/routes/shared.js +187 -96
- package/dist/routes/ui-config.js +437 -97
- package/dist/routes/ui-data.js +2 -2
- package/dist/routes/ui.js +9 -16
- package/dist/runtime/call-admission.js +9 -0
- package/dist/server.js +8 -0
- package/dist/skills.js +5 -4
- package/dist/storage/keys.js +9 -1
- package/dist/tool-safety.js +13 -5
- package/dist/types.d.ts +12 -1
- package/dist/ui.d.ts +2 -2
- package/dist/ui.js +17 -15
- package/dist/usage-guide.js +3 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/workspace-address.js +9 -0
- package/dist/workspace-subject.js +23 -0
- package/documentation/README.md +46 -22
- package/documentation/architecture.md +378 -58
- package/documentation/auth.md +806 -215
- package/documentation/code-mode.md +93 -81
- package/documentation/deploying.md +128 -26
- package/documentation/integrating.md +145 -22
- package/documentation/meta-tools.md +58 -46
- package/documentation/migration-0.33.md +344 -0
- package/documentation/operating.md +14 -9
- package/documentation/operator-tests.md +117 -31
- package/documentation/operator-ui.md +326 -55
- package/documentation/provider-auth-migration.md +42 -27
- package/documentation/provider-migration-0.29.md +3 -3
- package/examples/worker/AGENTS.md +35 -8
- package/examples/worker/README.md +92 -53
- package/examples/worker/src/connecta.config.ts +41 -20
- package/examples/worker/wrangler.jsonc +3 -0
- package/package.json +7 -2
- package/templates/node/.env.example +3 -0
- package/templates/node/AGENTS.md +33 -10
- package/templates/node/Dockerfile +2 -2
- package/templates/node/README.md +71 -44
- package/templates/node/docker-compose.yml +3 -2
- package/templates/node/package.json +4 -3
- package/templates/node/src/adopt-token.ts +23 -0
- package/templates/node/src/connecta.config.ts +33 -12
- package/templates/node/src/rotate-token.ts +17 -0
- package/dist/tool-safety.d.ts +0 -9
- package/templates/node/src/provision-token.ts +0 -17
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { connectorStatusLabel, connectorStatusTone } from "./view.js";
|
|
2
|
+
/**
|
|
3
|
+
* Whether the member's own partition holds a credential, read from `actions`
|
|
4
|
+
* alone (P6b): `clear` is listed only when a value is stored. Connection
|
|
5
|
+
* state says whether it works, never whether it exists.
|
|
6
|
+
*/
|
|
7
|
+
function credentialStored(manage) {
|
|
8
|
+
return manage.actions.includes("clear");
|
|
9
|
+
}
|
|
10
|
+
function controlsFor(manage, connection) {
|
|
11
|
+
const can = (action) => (manage.kind === "none" ? false : manage.actions.includes(action));
|
|
12
|
+
if (manage.kind === "oauth") {
|
|
13
|
+
const connected = connection.state === "ok";
|
|
14
|
+
const controls = [];
|
|
15
|
+
if (!connected && can("connect")) {
|
|
16
|
+
controls.push({ kind: "connect", label: connection.state === "auth_required" ? "Connect" : "Reconnect" });
|
|
17
|
+
}
|
|
18
|
+
if (connected && can("restart"))
|
|
19
|
+
controls.push({ kind: "restart", label: "Reconnect" });
|
|
20
|
+
if (connection.state !== "auth_required" && can("disconnect")) {
|
|
21
|
+
controls.push({ kind: "disconnect", label: "Disconnect" });
|
|
22
|
+
}
|
|
23
|
+
return controls;
|
|
24
|
+
}
|
|
25
|
+
if (manage.kind === "credential") {
|
|
26
|
+
// `save` without `clear`: nothing stored yet. `test` is listed only for a stored value.
|
|
27
|
+
const stored = credentialStored(manage);
|
|
28
|
+
return [
|
|
29
|
+
...(can("save")
|
|
30
|
+
? [{ kind: "save", label: stored ? "Replace" : "Add credential" }]
|
|
31
|
+
: []),
|
|
32
|
+
...(can("test") ? [{ kind: "test", label: "Test" }] : []),
|
|
33
|
+
...(can("clear") ? [{ kind: "clear", label: "Clear" }] : []),
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
function summaryFor(manage, connection) {
|
|
39
|
+
if (manage.kind === "credential") {
|
|
40
|
+
if (!credentialStored(manage)) {
|
|
41
|
+
return manage.actions.includes("save") ? "Add your credential to use its tools." : "No credential saved.";
|
|
42
|
+
}
|
|
43
|
+
if (connection.problem === "credential_mismatch")
|
|
44
|
+
return "Your saved credential no longer fits. Replace it.";
|
|
45
|
+
return connection.state === "ok" ? "Your credential is saved." : "Your credential is saved, but it isn't working.";
|
|
46
|
+
}
|
|
47
|
+
if (connection.state === "ok")
|
|
48
|
+
return "Connected with your account.";
|
|
49
|
+
if (connection.state === "auth_required") {
|
|
50
|
+
return manage.kind === "oauth" && manage.actions.includes("connect")
|
|
51
|
+
? "Not connected. Its tools won't work for you until you connect."
|
|
52
|
+
: "Not connected.";
|
|
53
|
+
}
|
|
54
|
+
return "Unavailable right now.";
|
|
55
|
+
}
|
|
56
|
+
const toolCount = (connector) => connector.tools.length;
|
|
57
|
+
export function memberPage(contract) {
|
|
58
|
+
if (contract?.role !== "member")
|
|
59
|
+
return { endpoints: [], accounts: [], shared: [] };
|
|
60
|
+
return {
|
|
61
|
+
endpoints: contract.pools.map((pool) => ({ pool: pool.name, path: pool.humanPath })),
|
|
62
|
+
accounts: contract.connectors
|
|
63
|
+
.filter((connector) => connector.authScope === "personal")
|
|
64
|
+
.map((connector) => {
|
|
65
|
+
const connection = connector.connection ?? { state: "auth_required" };
|
|
66
|
+
return {
|
|
67
|
+
id: connector.id,
|
|
68
|
+
title: connector.title || connector.id,
|
|
69
|
+
toolCount: toolCount(connector),
|
|
70
|
+
badge: connector.connection
|
|
71
|
+
? {
|
|
72
|
+
label: connectorStatusLabel(connection.state, connection.problem),
|
|
73
|
+
tone: connectorStatusTone(connection.state),
|
|
74
|
+
}
|
|
75
|
+
: null,
|
|
76
|
+
summary: summaryFor(connector.manage, connection),
|
|
77
|
+
manage: connector.manage,
|
|
78
|
+
controls: controlsFor(connector.manage, connection),
|
|
79
|
+
};
|
|
80
|
+
}),
|
|
81
|
+
// Names and counts only: a shared connector's state is not the member's to see.
|
|
82
|
+
shared: contract.connectors
|
|
83
|
+
.filter((connector) => connector.authScope === "shared")
|
|
84
|
+
.map((connector) => ({
|
|
85
|
+
id: connector.id,
|
|
86
|
+
title: connector.title || connector.id,
|
|
87
|
+
toolCount: toolCount(connector),
|
|
88
|
+
})),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
export function toolCountLabel(count) {
|
|
92
|
+
return `${count} ${count === 1 ? "tool" : "tools"}`;
|
|
93
|
+
}
|
|
94
|
+
/** The in-page question before a member's action that takes something away. */
|
|
95
|
+
export function memberConfirmCopy(action, name) {
|
|
96
|
+
if (action === "oauth_restart") {
|
|
97
|
+
return {
|
|
98
|
+
question: `Reconnect ${name}? Your current connection stops working until you finish authorizing in the new tab.`,
|
|
99
|
+
confirm: "Reconnect",
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (action === "oauth_disconnect") {
|
|
103
|
+
return {
|
|
104
|
+
question: `Disconnect your ${name} account? Its tools stop working for you until you connect again.`,
|
|
105
|
+
confirm: "Disconnect",
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
question: `Clear your ${name} credential? Its tools stop working for you until you add one again.`,
|
|
110
|
+
confirm: "Clear",
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ConnectaConfigDescription } from "../describe-config.js";
|
|
2
|
+
import type { OperatorConnectorOverlay, OperatorTool } from "./contract.js";
|
|
2
3
|
import type { CatalogAccessObservation, CatalogDriftReport, ResourceTemplateRefusalCode } from "../types.js";
|
|
3
|
-
/** Tool verdicts, independent of the selected pool
|
|
4
|
+
/** Tool verdicts, independent of whether the selected pool is read-only. */
|
|
4
5
|
export type UiToolSafety = "runs_in_programs" | "needs_approval";
|
|
5
6
|
export interface UiTool extends Pick<OperatorTool, "name" | "address" | "description"> {
|
|
6
7
|
/** Absent only in payloads older than the field; the page then shows no badge. */
|
|
@@ -28,7 +29,8 @@ interface UiCredentialField {
|
|
|
28
29
|
description?: string;
|
|
29
30
|
placeholder?: string;
|
|
30
31
|
inputType: "email" | "password" | "text";
|
|
31
|
-
|
|
32
|
+
/** null when metadata could not be read. */
|
|
33
|
+
configured: boolean | null;
|
|
32
34
|
lastFour?: string;
|
|
33
35
|
updatedAt?: string;
|
|
34
36
|
}
|
|
@@ -37,7 +39,8 @@ interface UiCredential {
|
|
|
37
39
|
description?: string;
|
|
38
40
|
placeholder?: string;
|
|
39
41
|
fields?: UiCredentialField[];
|
|
40
|
-
|
|
42
|
+
/** null when metadata could not be read. */
|
|
43
|
+
configured: boolean | null;
|
|
41
44
|
/** A stored value exists and may be deleted even if it cannot be decrypted. */
|
|
42
45
|
removable?: boolean;
|
|
43
46
|
lastFour?: string;
|
|
@@ -60,7 +63,12 @@ export interface UiConnector {
|
|
|
60
63
|
title?: string;
|
|
61
64
|
description?: string;
|
|
62
65
|
status: "loading" | OperatorConnectorOverlay["status"];
|
|
63
|
-
|
|
66
|
+
/** What this maintainer may do with the connector. */
|
|
67
|
+
permissions?: {
|
|
68
|
+
use: boolean;
|
|
69
|
+
manageSharedAuth: boolean;
|
|
70
|
+
connectPersonal: boolean;
|
|
71
|
+
};
|
|
64
72
|
/**
|
|
65
73
|
* Why this connector is not usable, when it is not. See `UiProblem`. The
|
|
66
74
|
* only failure detail the payload carries; the status message behind it is
|
|
@@ -91,9 +99,9 @@ export interface UiConnector {
|
|
|
91
99
|
export type CredentialManagementCapability = "available" | "requires_operator" | "vault_not_configured" | "no_slots";
|
|
92
100
|
export interface UiData {
|
|
93
101
|
accessTokenManagement?: "available" | "requires_operator";
|
|
94
|
-
serverInfo: Pick<
|
|
102
|
+
serverInfo: Pick<ConnectaConfigDescription["server"], "name" | "version">;
|
|
95
103
|
/** Version of the installed @zackbart/connecta package. */
|
|
96
|
-
connectaVersion:
|
|
104
|
+
connectaVersion: ConnectaConfigDescription["connectaVersion"];
|
|
97
105
|
connectors: UiConnector[];
|
|
98
106
|
activityEnabled: boolean;
|
|
99
107
|
credentialManagement: CredentialManagementCapability;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { OperatorRole, OperatorUiContractV2 } from "./contract.js";
|
|
2
2
|
import type { CatalogDriftReport } from "../types.js";
|
|
3
3
|
import type { CredentialManagementCapability, UiConnector, UiData, UiProblem } from "./model.js";
|
|
4
4
|
import type { FixPromptKind } from "./fix-prompts.js";
|
|
5
|
+
import type { AgentSlot } from "./agents.js";
|
|
5
6
|
export declare function registrationPathLabel(path: NonNullable<UiConnector["registrationPath"]>): string;
|
|
6
7
|
/**
|
|
7
8
|
* A connector's source column: the maintained provider with the
|
|
@@ -12,19 +13,26 @@ export declare function connectorSourceLabel(source: {
|
|
|
12
13
|
kind: string;
|
|
13
14
|
provider?: string;
|
|
14
15
|
}): string;
|
|
15
|
-
export type OperatorPage = "
|
|
16
|
-
/** Pages the nav lists. */
|
|
16
|
+
export type OperatorPage = "endpoints" | "settings" | "connector" | "tokens" | "connections" | "activity";
|
|
17
|
+
/** Pages the nav lists, in dashboard order. */
|
|
17
18
|
export declare const OPERATOR_PAGES: readonly OperatorPage[];
|
|
18
19
|
export declare const PAGE_META: Readonly<Record<OperatorPage, {
|
|
19
20
|
path: string;
|
|
20
21
|
label: string;
|
|
21
22
|
}>>;
|
|
23
|
+
/** Legacy links remain valid, with the browser replacing their URL. */
|
|
24
|
+
export declare function canonicalOperatorPath(path: string): string;
|
|
22
25
|
/**
|
|
23
26
|
* What each page is for, in one line under its heading — on the gate as well
|
|
24
27
|
* as once signed in, so the page an operator lands on reads the same before
|
|
25
28
|
* and after the session is checked.
|
|
26
29
|
*/
|
|
27
30
|
export declare function pageDescription(page: OperatorPage, productDescription: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* Signed in, but neither a maintainer nor a member of any endpoint: who to
|
|
33
|
+
* ask, and that this is not an error to retry.
|
|
34
|
+
*/
|
|
35
|
+
export declare function noAccessCopy(kind: string, productName: string): string;
|
|
28
36
|
/** What the gate says while the session is checked, page by page. */
|
|
29
37
|
export declare function checkingCopy(): string;
|
|
30
38
|
export declare function pageForPath(path: string): OperatorPage;
|
|
@@ -83,12 +91,16 @@ export declare function failure(message: string, fix?: NoticeFix): Notice;
|
|
|
83
91
|
* mutation that is a permission, not an expired session, so it stays inline.
|
|
84
92
|
* - `network` — the browser could not reach the deployment at all.
|
|
85
93
|
* - `refused` — the route answered, and said no.
|
|
94
|
+
* - `stale` — never sent: what the action was checked against changed before
|
|
95
|
+
* dispatch (a refreshed slot list or member contract).
|
|
86
96
|
*/
|
|
87
|
-
export type RequestFailureKind = "session" | "forbidden" | "network" | "refused";
|
|
97
|
+
export type RequestFailureKind = "session" | "forbidden" | "network" | "refused" | "stale";
|
|
88
98
|
export interface RequestFailureFacts {
|
|
89
99
|
kind: RequestFailureKind;
|
|
90
100
|
status?: number | undefined;
|
|
91
101
|
problem?: unknown;
|
|
102
|
+
/** A refusal's machine `code` from a closed set, such as `slot_changed`; checked, never shown. */
|
|
103
|
+
code?: unknown;
|
|
92
104
|
}
|
|
93
105
|
/**
|
|
94
106
|
* What the notices for an OAuth action and a credential Test say. Each of
|
|
@@ -121,6 +133,8 @@ export declare function credentialTestNotice(connectorId: string, answer: {
|
|
|
121
133
|
* checked, not trusted.
|
|
122
134
|
*/
|
|
123
135
|
export declare function refusedNotice(action: DownstreamAction, connectorId: string, problem?: unknown): Notice;
|
|
136
|
+
/** An action whose permission or target changed before it was sent. */
|
|
137
|
+
export declare const STALE_COPY = "This changed before the request was sent, so nothing was sent. Check it and try again.";
|
|
124
138
|
/**
|
|
125
139
|
* Every row action's failure, in one voice. The page's own failures — a
|
|
126
140
|
* lapsed session, a denied identity, a dropped connection — read the same for
|
|
@@ -133,7 +147,7 @@ export declare function actionFailedNotice(action: RowAction, connectorId: strin
|
|
|
133
147
|
* server's `connector_unavailable` and keeps its own copy and fix prompt;
|
|
134
148
|
* these two have nothing for a coding agent to fix.
|
|
135
149
|
*/
|
|
136
|
-
export type ConnectorLoadFailure = "session" | "network";
|
|
150
|
+
export type ConnectorLoadFailure = "session" | "network" | "unavailable";
|
|
137
151
|
export declare function connectorLoadFailureCopy(failure: ConnectorLoadFailure, productName: string): string;
|
|
138
152
|
/** The signed-in page's answer when `/ui/data` could not be read. */
|
|
139
153
|
export declare function loadFailureCopy(failure: "server" | "network", productName: string): {
|
|
@@ -158,20 +172,27 @@ export declare function confirmCopy(action: PendingConfirm["action"], name: stri
|
|
|
158
172
|
* page fetch again after an identity change.
|
|
159
173
|
*/
|
|
160
174
|
type LoadPhase = "idle" | "loading" | "ready" | "error";
|
|
161
|
-
export interface UiAccessToken {
|
|
162
|
-
id: string;
|
|
163
|
-
name: string;
|
|
164
|
-
tokenPrefix: string;
|
|
165
|
-
createdAt: string;
|
|
166
|
-
revokedAt?: string;
|
|
167
|
-
}
|
|
168
175
|
export declare function accessTokenUnavailableCopy(capability?: string): string;
|
|
169
176
|
export interface OperatorState {
|
|
170
177
|
tokenPhase: LoadPhase;
|
|
171
178
|
tokenNotice: Notice | null;
|
|
172
|
-
tokens
|
|
179
|
+
/** One slot per declared agent, from `/ui/access-tokens`. */
|
|
180
|
+
tokens: AgentSlot[];
|
|
181
|
+
/** A just-issued secret, shown once in its dialog and dropped when that closes. */
|
|
173
182
|
createdToken: string | null;
|
|
174
|
-
|
|
183
|
+
/** The agent `createdToken` belongs to. */
|
|
184
|
+
createdTokenFor: string | null;
|
|
185
|
+
/** A rotation or revocation waiting on its in-row confirmation. */
|
|
186
|
+
tokenConfirm: {
|
|
187
|
+
name: string;
|
|
188
|
+
action: "rotate" | "revoke";
|
|
189
|
+
} | null;
|
|
190
|
+
/**
|
|
191
|
+
* Agents whose last token operation ended with an unknown outcome (503,
|
|
192
|
+
* 499, or a dropped connection). Their actions stay off until a slot list
|
|
193
|
+
* read that started after the failure succeeds.
|
|
194
|
+
*/
|
|
195
|
+
unresolvedSlots: string[];
|
|
175
196
|
tokenBusy: boolean;
|
|
176
197
|
page: OperatorPage;
|
|
177
198
|
/**
|
|
@@ -180,11 +201,17 @@ export interface OperatorState {
|
|
|
180
201
|
* the fence that keeps one identity's data off another identity's screen.
|
|
181
202
|
*/
|
|
182
203
|
generation: number;
|
|
183
|
-
/**
|
|
184
|
-
|
|
204
|
+
/**
|
|
205
|
+
* `loading` until `/ui/api/config` answers (and, for a maintainer,
|
|
206
|
+
* `/ui/data` too). `denied` is signed in as someone who is neither a
|
|
207
|
+
* maintainer nor a pool member.
|
|
208
|
+
*/
|
|
209
|
+
session: "loading" | "gated" | "ready" | "denied";
|
|
185
210
|
gate: Notice | null;
|
|
186
211
|
/** True while a signed-in operator's /ui/data is in flight. */
|
|
187
212
|
refreshing: boolean;
|
|
213
|
+
/** A bounded admission recovery is pending; Retry can supersede it. */
|
|
214
|
+
recoveryPending: boolean;
|
|
188
215
|
/**
|
|
189
216
|
* Why the last /ui/data read failed when the session itself was not the
|
|
190
217
|
* reason: the page stays signed in, with its chrome, and offers a retry.
|
|
@@ -204,7 +231,14 @@ export interface OperatorState {
|
|
|
204
231
|
*/
|
|
205
232
|
focusIfLost: string | null;
|
|
206
233
|
data: UiData | null;
|
|
207
|
-
contract
|
|
234
|
+
/** Operator contract v2, for either role; null until `/ui/api/config` answers. */
|
|
235
|
+
contract: OperatorUiContractV2 | null;
|
|
236
|
+
/**
|
|
237
|
+
* The role the last good contract confirmed. It outlives a failed refresh
|
|
238
|
+
* that clears `contract`, so the next answer with a different role is
|
|
239
|
+
* still recognized as a different viewer. Only an identity reset clears it.
|
|
240
|
+
*/
|
|
241
|
+
role: OperatorRole | null;
|
|
208
242
|
contractPhase: LoadPhase;
|
|
209
243
|
health: "loading" | "ok" | "unavailable";
|
|
210
244
|
connectorFilter: string;
|
|
@@ -218,6 +252,11 @@ export interface OperatorState {
|
|
|
218
252
|
* new tab: its row offers the link instead of the button.
|
|
219
253
|
*/
|
|
220
254
|
oauthBlocked: string | null;
|
|
255
|
+
/**
|
|
256
|
+
* The authorization link the route returned for `oauthBlocked`. A member has
|
|
257
|
+
* no `/ui/data` connector to keep it on, so it is held here.
|
|
258
|
+
*/
|
|
259
|
+
oauthBlockedUrl: string | null;
|
|
221
260
|
/** A row action waiting for its in-page confirmation. */
|
|
222
261
|
confirming: PendingConfirm | null;
|
|
223
262
|
/** Connectors whose details failed on this side of the deployment. */
|
|
@@ -235,6 +274,31 @@ export interface OperatorState {
|
|
|
235
274
|
activityCursor: string | null;
|
|
236
275
|
activitySearch: string;
|
|
237
276
|
}
|
|
277
|
+
/** Who the contract says this viewer is, or null before it loads. */
|
|
278
|
+
export declare function operatorRole(state: Pick<OperatorState, "contract">): OperatorRole | null;
|
|
279
|
+
/**
|
|
280
|
+
* The role pages and navigation render for. The contract's, or, while a
|
|
281
|
+
* refresh that failed has left no contract on screen, maintainer when the
|
|
282
|
+
* last good contract confirmed one and that maintainer's data is still here:
|
|
283
|
+
* their page stays mounted on its own retry state instead of being torn down
|
|
284
|
+
* mid-recovery. Never a request's permission; the store sends maintainer
|
|
285
|
+
* reads only while the contract itself says maintainer.
|
|
286
|
+
*/
|
|
287
|
+
export declare function displayRole(state: Pick<OperatorState, "contract" | "role" | "data">): OperatorRole | null;
|
|
288
|
+
/**
|
|
289
|
+
* A confirmed maintainer's page is mounted without a contract behind it: a
|
|
290
|
+
* refresh failed, or its read ran past the deadline. The shell shows one
|
|
291
|
+
* recovery banner with Retry above whichever page is open, and that page is
|
|
292
|
+
* stale until a contract confirms the role again.
|
|
293
|
+
*/
|
|
294
|
+
export declare function contractRecovering(state: Pick<OperatorState, "contract" | "role" | "data">): boolean;
|
|
295
|
+
/**
|
|
296
|
+
* Whether Activity is open to this viewer: maintainers only, on a deployment
|
|
297
|
+
* that records history. Members always read "forbidden".
|
|
298
|
+
*/
|
|
299
|
+
export declare function activityReadable(state: Pick<OperatorState, "contract" | "data">): boolean;
|
|
300
|
+
/** The contract's half of `activityReadable`: a maintainer, on a deployment that records. */
|
|
301
|
+
export declare function activityAllowed(state: Pick<OperatorState, "contract">): boolean;
|
|
238
302
|
export declare function initialState(page: OperatorPage): OperatorState;
|
|
239
303
|
/**
|
|
240
304
|
* Drop to the gate and forget the previous operator. Bumping the generation is
|
|
@@ -254,7 +318,7 @@ export declare function credentialUnavailableCopy(capability?: CredentialManagem
|
|
|
254
318
|
*/
|
|
255
319
|
export declare function connectorStatusLabel(status: string, problem?: UiProblem): string;
|
|
256
320
|
/** The tone a status carries wherever it is rendered as a badge or a tile. */
|
|
257
|
-
export type Tone = "ok" | "warn" | "danger" | "neutral";
|
|
321
|
+
export type Tone = "ok" | "warn" | "danger" | "neutral" | "accent";
|
|
258
322
|
export declare function connectorStatusTone(status: string): Tone;
|
|
259
323
|
export declare function problemCopy(problem: UiProblem | undefined): string | null;
|
|
260
324
|
/**
|
|
@@ -322,7 +386,8 @@ export declare function activityDetail(event: UiActivityEvent): string;
|
|
|
322
386
|
*/
|
|
323
387
|
export declare function credentialProblemCopy(problem: string | undefined): string;
|
|
324
388
|
export declare function credentialStateLabel(credential: {
|
|
325
|
-
configured: boolean;
|
|
389
|
+
configured: boolean | null;
|
|
390
|
+
problem?: string;
|
|
326
391
|
fields?: unknown[];
|
|
327
392
|
lastFour?: string;
|
|
328
393
|
updatedAt?: string;
|
package/dist/operator-ui/view.js
CHANGED
|
@@ -25,25 +25,20 @@ export function connectorSourceLabel(source) {
|
|
|
25
25
|
const implementation = source.kind === "remote-mcp" ? "hosted MCP" : source.kind === "api" ? "REST" : source.kind;
|
|
26
26
|
return `${source.provider} · ${implementation}`;
|
|
27
27
|
}
|
|
28
|
-
/** Pages the nav lists. */
|
|
29
|
-
export const OPERATOR_PAGES = [
|
|
30
|
-
"overview",
|
|
31
|
-
"connections",
|
|
32
|
-
"tools",
|
|
33
|
-
"access",
|
|
34
|
-
"activity",
|
|
35
|
-
"config",
|
|
36
|
-
];
|
|
28
|
+
/** Pages the nav lists, in dashboard order. */
|
|
29
|
+
export const OPERATOR_PAGES = ["endpoints", "connections", "activity", "tokens", "settings"];
|
|
37
30
|
export const PAGE_META = {
|
|
38
|
-
|
|
31
|
+
endpoints: { path: "/", label: "Endpoints" },
|
|
39
32
|
connector: { path: "/connectors", label: "Connector" },
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
config: { path: "/config", label: "Config" },
|
|
43
|
-
tokens: { path: "/tokens", label: "Access tokens" },
|
|
33
|
+
settings: { path: "/settings", label: "Settings" },
|
|
34
|
+
tokens: { path: "/tokens", label: "Tokens" },
|
|
44
35
|
connections: { path: "/connectors", label: "Connectors" },
|
|
45
36
|
activity: { path: "/activity", label: "Activity" },
|
|
46
37
|
};
|
|
38
|
+
/** Legacy links remain valid, with the browser replacing their URL. */
|
|
39
|
+
export function canonicalOperatorPath(path) {
|
|
40
|
+
return { "/access": "/", "/tools": "/connectors", "/config": "/settings" }[path] ?? path;
|
|
41
|
+
}
|
|
47
42
|
/**
|
|
48
43
|
* What each page is for, in one line under its heading — on the gate as well
|
|
49
44
|
* as once signed in, so the page an operator lands on reads the same before
|
|
@@ -55,6 +50,18 @@ export function pageDescription(page, productDescription) {
|
|
|
55
50
|
}
|
|
56
51
|
return productDescription;
|
|
57
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Signed in, but neither a maintainer nor a member of any endpoint: who to
|
|
55
|
+
* ask, and that this is not an error to retry.
|
|
56
|
+
*/
|
|
57
|
+
export function noAccessCopy(kind, productName) {
|
|
58
|
+
const who = kind === "clerk"
|
|
59
|
+
? "This account is signed in"
|
|
60
|
+
: kind === "cloudflare-access"
|
|
61
|
+
? "Cloudflare Access signed you in"
|
|
62
|
+
: "This token is valid";
|
|
63
|
+
return `${who}, but it isn't a maintainer or a member of any endpoint on ${productName}. Ask a maintainer of this deployment to add you to an endpoint, then reload.`;
|
|
64
|
+
}
|
|
58
65
|
/** What the gate says while the session is checked, page by page. */
|
|
59
66
|
export function checkingCopy() {
|
|
60
67
|
return "Checking your session…";
|
|
@@ -64,7 +71,7 @@ export function pageForPath(path) {
|
|
|
64
71
|
return "connector";
|
|
65
72
|
if (path === "/tokens")
|
|
66
73
|
return "tokens";
|
|
67
|
-
const match = OPERATOR_PAGES.find((page) => PAGE_META[page].path === path);
|
|
74
|
+
const match = OPERATOR_PAGES.find((page) => PAGE_META[page].path === canonicalOperatorPath(path));
|
|
68
75
|
return match ?? "connections";
|
|
69
76
|
}
|
|
70
77
|
export function info(message) {
|
|
@@ -138,6 +145,8 @@ function credentialRefusedCopy(action, status, problem) {
|
|
|
138
145
|
}
|
|
139
146
|
return `The credential wasn't ${verb}. Try again; if it keeps failing, the deployment's log has the reason.`;
|
|
140
147
|
}
|
|
148
|
+
/** An action whose permission or target changed before it was sent. */
|
|
149
|
+
export const STALE_COPY = "This changed before the request was sent, so nothing was sent. Check it and try again.";
|
|
141
150
|
/**
|
|
142
151
|
* Every row action's failure, in one voice. The page's own failures — a
|
|
143
152
|
* lapsed session, a denied identity, a dropped connection — read the same for
|
|
@@ -153,14 +162,18 @@ export function actionFailedNotice(action, connectorId, facts, productName) {
|
|
|
153
162
|
if (facts.kind === "network") {
|
|
154
163
|
return failure(`Couldn't reach ${productName}. Check your connection and try again.`);
|
|
155
164
|
}
|
|
165
|
+
if (facts.kind === "stale")
|
|
166
|
+
return failure(STALE_COPY);
|
|
156
167
|
if (action === "credential_save" || action === "credential_remove") {
|
|
157
168
|
return failure(credentialRefusedCopy(action, facts.status, facts.problem));
|
|
158
169
|
}
|
|
159
170
|
return refusedNotice(action, connectorId, facts.problem);
|
|
160
171
|
}
|
|
161
172
|
export function connectorLoadFailureCopy(failure, productName) {
|
|
173
|
+
if (failure === "unavailable")
|
|
174
|
+
return "Session identity is temporarily unavailable. Retry this connection.";
|
|
162
175
|
return failure === "session"
|
|
163
|
-
? "Your session wasn't accepted while loading this connector. Sign in again
|
|
176
|
+
? "Your session wasn't accepted while loading this connector. Retry to check again. Sign in again if the session keeps failing."
|
|
164
177
|
: `Couldn't reach ${productName} to load this connector. Check your connection, then refresh it.`;
|
|
165
178
|
}
|
|
166
179
|
/** The signed-in page's answer when `/ui/data` could not be read. */
|
|
@@ -208,6 +221,46 @@ export function accessTokenUnavailableCopy(capability) {
|
|
|
208
221
|
? "Access tokens are not configured for this deployment."
|
|
209
222
|
: "Token management requires an interactive sign-in and explicit permission.";
|
|
210
223
|
}
|
|
224
|
+
/** Who the contract says this viewer is, or null before it loads. */
|
|
225
|
+
export function operatorRole(state) {
|
|
226
|
+
return state.contract?.role ?? null;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* The role pages and navigation render for. The contract's, or, while a
|
|
230
|
+
* refresh that failed has left no contract on screen, maintainer when the
|
|
231
|
+
* last good contract confirmed one and that maintainer's data is still here:
|
|
232
|
+
* their page stays mounted on its own retry state instead of being torn down
|
|
233
|
+
* mid-recovery. Never a request's permission; the store sends maintainer
|
|
234
|
+
* reads only while the contract itself says maintainer.
|
|
235
|
+
*/
|
|
236
|
+
export function displayRole(state) {
|
|
237
|
+
if (state.contract)
|
|
238
|
+
return state.contract.role;
|
|
239
|
+
return state.role === "maintainer" && state.data ? "maintainer" : null;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* A confirmed maintainer's page is mounted without a contract behind it: a
|
|
243
|
+
* refresh failed, or its read ran past the deadline. The shell shows one
|
|
244
|
+
* recovery banner with Retry above whichever page is open, and that page is
|
|
245
|
+
* stale until a contract confirms the role again.
|
|
246
|
+
*/
|
|
247
|
+
export function contractRecovering(state) {
|
|
248
|
+
return !state.contract && displayRole(state) === "maintainer";
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Whether Activity is open to this viewer: maintainers only, on a deployment
|
|
252
|
+
* that records history. Members always read "forbidden".
|
|
253
|
+
*/
|
|
254
|
+
export function activityReadable(state) {
|
|
255
|
+
return Boolean(state.data?.activityEnabled) && activityAllowed(state);
|
|
256
|
+
}
|
|
257
|
+
/** The contract's half of `activityReadable`: a maintainer, on a deployment that records. */
|
|
258
|
+
export function activityAllowed(state) {
|
|
259
|
+
const contract = state.contract;
|
|
260
|
+
return (contract?.role === "maintainer" &&
|
|
261
|
+
contract.live.activity !== "forbidden" &&
|
|
262
|
+
contract.live.activity !== "unconfigured");
|
|
263
|
+
}
|
|
211
264
|
export function initialState(page) {
|
|
212
265
|
return {
|
|
213
266
|
page,
|
|
@@ -228,8 +281,10 @@ export function initialState(page) {
|
|
|
228
281
|
*/
|
|
229
282
|
function identityScopedState() {
|
|
230
283
|
return {
|
|
284
|
+
recoveryPending: false,
|
|
231
285
|
data: null,
|
|
232
286
|
contract: null,
|
|
287
|
+
role: null,
|
|
233
288
|
contractPhase: "idle",
|
|
234
289
|
health: "loading",
|
|
235
290
|
connectorFilter: "",
|
|
@@ -237,6 +292,7 @@ function identityScopedState() {
|
|
|
237
292
|
oauthNoticeFor: null,
|
|
238
293
|
oauthBusy: null,
|
|
239
294
|
oauthBlocked: null,
|
|
295
|
+
oauthBlockedUrl: null,
|
|
240
296
|
confirming: null,
|
|
241
297
|
connectorFailures: {},
|
|
242
298
|
credentialNotice: null,
|
|
@@ -247,7 +303,9 @@ function identityScopedState() {
|
|
|
247
303
|
tokenNotice: null,
|
|
248
304
|
tokens: [],
|
|
249
305
|
createdToken: null,
|
|
250
|
-
|
|
306
|
+
createdTokenFor: null,
|
|
307
|
+
tokenConfirm: null,
|
|
308
|
+
unresolvedSlots: [],
|
|
251
309
|
tokenBusy: false,
|
|
252
310
|
activityPhase: "idle",
|
|
253
311
|
activityNotice: null,
|
|
@@ -283,7 +341,8 @@ export function withPage(state, page) {
|
|
|
283
341
|
...state,
|
|
284
342
|
page,
|
|
285
343
|
createdToken: null,
|
|
286
|
-
|
|
344
|
+
createdTokenFor: null,
|
|
345
|
+
tokenConfirm: null,
|
|
287
346
|
tokenNotice: null,
|
|
288
347
|
credentialEditing: null,
|
|
289
348
|
credentialNotice: null,
|
|
@@ -551,6 +610,7 @@ const REASON_LABELS = {
|
|
|
551
610
|
invalid_args: "arguments didn't match the schema",
|
|
552
611
|
destructive_reroute: "needed host approval",
|
|
553
612
|
destructive_tool_requires_approval: "needed host approval",
|
|
613
|
+
read_only_endpoint: "endpoint is read-only",
|
|
554
614
|
approval_required: "needed approval",
|
|
555
615
|
auth_required: "needed authorization",
|
|
556
616
|
result_too_large: "result too large to return inline",
|
|
@@ -585,20 +645,22 @@ export function credentialProblemCopy(problem) {
|
|
|
585
645
|
if (problem === "credential_mismatch")
|
|
586
646
|
return PROBLEM_COPY.credential_mismatch;
|
|
587
647
|
if (problem === "credential_unreadable") {
|
|
588
|
-
return "
|
|
648
|
+
return "Credential status is unavailable. Retry the status check before replacing or removing the credential.";
|
|
589
649
|
}
|
|
590
650
|
return "The stored credential can't be used. Replace it, or remove it and add a new one.";
|
|
591
651
|
}
|
|
592
652
|
export function credentialStateLabel(credential) {
|
|
653
|
+
if (credential.configured === null || credential.problem === "credential_unreadable")
|
|
654
|
+
return "unavailable";
|
|
593
655
|
if (!credential.configured)
|
|
594
656
|
return "not configured";
|
|
595
|
-
const masked =
|
|
657
|
+
const masked = "configured";
|
|
596
658
|
return credential.updatedAt ? `${masked} · updated ${formatDate(credential.updatedAt)}` : masked;
|
|
597
659
|
}
|
|
598
660
|
/** Gate copy for each browser-auth shape, so the sign-in state is never a blank page. */
|
|
599
661
|
export function gateCopy(kind, signedIn) {
|
|
600
662
|
if (kind === "cloudflare-access") {
|
|
601
|
-
return "Cloudflare Access
|
|
663
|
+
return "Checking Cloudflare Access identity failed. Retry to check whether this browser can open operator pages.";
|
|
602
664
|
}
|
|
603
665
|
if (kind !== "clerk") {
|
|
604
666
|
return "Paste an operator bearer token to open this page. Nothing is requested until you do.";
|
package/dist/page-styles.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Generated by scripts/build-operator-ui.mjs. Do not edit.
|
|
2
2
|
// Source: src/operator-ui/tokens.css and src/operator-ui/page.css.
|
|
3
3
|
/** Custom properties and both palettes; styles no element. */
|
|
4
|
-
export const TOKENS_CSS = ":root{color-scheme:light;--accent: #
|
|
4
|
+
export const TOKENS_CSS = ":root{color-scheme:light;--default-accent: #171717;--accent: var(--default-accent);--radius: 8px;--sans: \"Geist Sans\", ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;--mono: \"Geist Mono\", ui-monospace, \"SF Mono\", Menlo, Monaco, \"Cascadia Code\", Consolas, monospace;--bg: #fafafa;--surface: #ffffff;--surface-2: #f5f5f5;--border: #eaeaea;--border-strong: #d4d4d4;--text: #171717;--muted: #666666;--ok: #0a7d33;--warn: #a35200;--danger: #d8242c;--on-accent: #ffffff;--link: var(--accent);--blue: #0060df;--tint: color-mix(in srgb, var(--accent) 8%, var(--surface));--shell: 68rem;--pad: 1.25rem;--gap: 1rem;--space-1: .25rem;--space-2: .5rem;--space-3: .75rem;--space-4: 1rem;--space-6: 1.5rem;--space-8: 2rem;--text-xs: .75rem;--text-sm: .8125rem;--text-base: .875rem}@media(prefers-color-scheme:dark){html:not([data-scheme=light]){color-scheme:dark}}html[data-scheme=dark]{color-scheme:dark}@media(prefers-color-scheme:dark){html:not([data-scheme=light]){--default-accent: #ededed;--on-accent: #171717;--blue: #70b3ff;--bg: #0a0a0a;--surface: #111111;--surface-2: #1a1a1a;--border: #2e2e2e;--border-strong: #454545;--text: #ededed;--muted: #a1a1a1;--ok: #4cce83;--warn: #f5b85a;--danger: #ff777f;--link: color-mix(in srgb, var(--accent) 55%, #ffffff);--tint: color-mix(in srgb, var(--accent) 16%, var(--surface))}}html[data-scheme=dark]{--default-accent: #ededed;--on-accent: #171717;--blue: #70b3ff;--bg: #0a0a0a;--surface: #111111;--surface-2: #1a1a1a;--border: #2e2e2e;--border-strong: #454545;--text: #ededed;--muted: #a1a1a1;--ok: #4cce83;--warn: #f5b85a;--danger: #ff777f;--link: color-mix(in srgb, var(--accent) 55%, #ffffff);--tint: color-mix(in srgb, var(--accent) 16%, var(--surface))}";
|
|
5
5
|
/** Base typography, the shell, masthead, buttons, badges, and messages. */
|
|
6
6
|
export const PAGE_CSS = "*{box-sizing:border-box}html{background:var(--bg);color:var(--text);font-family:var(--sans);font-size:16px;line-height:1.5;-webkit-font-smoothing:antialiased}body{margin:0;min-height:100vh}:is(h1,h2,h3,p,ul,ol){margin:0;padding:0}:is(h1,h2,h3){font-weight:600;line-height:1.3}h1{font-size:1.5rem;letter-spacing:-.01em}h2{font-size:1rem}:is(ul,ol){list-style:none}a{color:var(--link);text-decoration-thickness:1px;text-underline-offset:2px}button,input{font:inherit}:is(a,button,input,summary):focus-visible{border-radius:calc(var(--radius) / 2);outline:2px solid var(--link);outline-offset:2px}.skip-link{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);left:var(--pad);padding:.5rem .75rem;position:fixed;top:-4rem;z-index:10}.skip-link:focus{top:var(--pad)}.shell{margin:0 auto;max-width:var(--shell);padding-left:var(--pad);padding-right:var(--pad)}.cap,.meta{color:var(--muted);font-size:.875rem}.mono{font-family:var(--mono);font-size:.8125rem}.visually-hidden{clip-path:inset(50%);height:1px;overflow:hidden;position:absolute;white-space:nowrap;width:1px}.masthead{align-items:center;background:var(--surface);border-bottom:1px solid var(--border);display:flex;gap:var(--gap);justify-content:space-between;max-width:none;padding-bottom:.75rem;padding-top:.75rem;position:sticky;top:0;z-index:5}.masthead-inner{align-items:center;display:flex;gap:var(--gap);justify-content:space-between;margin:0 auto;max-width:var(--shell);width:100%}.mast-nav,.mast-actions,.page-nav,.session-actions{align-items:center;display:flex;flex-wrap:wrap;gap:.25rem}.mast-nav,.mast-actions{gap:var(--gap)}.navlink{background:none;border:0;border-radius:calc(var(--radius) - 2px);color:var(--muted);cursor:pointer;font-size:.9375rem;padding:.35rem .6rem;text-decoration:none}.navlink:hover{background:var(--surface-2);color:var(--text)}.navlink[aria-current=page]{background:var(--tint);color:var(--link);font-weight:500}.brand,.product{padding-left:0;padding-right:0;text-decoration:none}.brand{color:var(--text);font-weight:600}.brand:hover,.product:hover{background:none}.product{color:var(--muted)}.page{padding-bottom:4rem;padding-top:2rem}.lead{margin-bottom:1.5rem}.lead-copy{color:var(--muted);display:grid;gap:.5rem;margin-top:.35rem}.lead-copy p{max-width:60ch}.btn{background:var(--surface);border:1px solid var(--border-strong);border-radius:calc(var(--radius) - 2px);color:var(--text);cursor:pointer;display:inline-flex;align-items:center;font-size:.875rem;gap:.35rem;padding:.35rem .7rem;text-decoration:none;white-space:nowrap}.btn:hover{background:var(--surface-2)}.btn:disabled{cursor:not-allowed;opacity:.55}.btn.primary{background:var(--accent);border-color:var(--accent);color:var(--on-accent)}.btn.primary:hover{background:color-mix(in srgb,var(--accent) 88%,black)}.btn.danger{color:var(--danger)}.btn.danger:hover{background:color-mix(in srgb,var(--danger) 10%,var(--surface))}.btn.quiet{background:none;border-color:var(--border);color:var(--muted)}.btn.quiet:hover{background:var(--surface-2);color:var(--text)}.badge{align-items:center;background:var(--surface-2);border-radius:999px;color:var(--muted);display:inline-flex;font-size:.75rem;gap:.3rem;line-height:1.6;padding:.1rem .5rem;white-space:nowrap}.badge.ok{background:color-mix(in srgb,var(--ok) 12%,var(--surface));color:var(--ok)}.badge.warn{background:color-mix(in srgb,var(--warn) 14%,var(--surface));color:var(--warn)}.badge.danger{background:color-mix(in srgb,var(--danger) 12%,var(--surface));color:var(--danger)}.badge.accent{background:var(--tint);color:var(--link)}.msg{background:color-mix(in srgb,var(--danger) 8%,var(--surface));border:1px solid color-mix(in srgb,var(--danger) 28%,var(--surface));border-radius:calc(var(--radius) - 2px);color:var(--danger);font-size:.875rem;padding:.5rem .75rem}.status-page{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);display:grid;gap:.75rem;margin:1.5rem auto 0;max-width:34rem;padding:1.5rem}.status-head{align-items:center;display:flex;gap:.6rem}.status-mark{align-items:center;background:var(--surface-2);border-radius:50%;color:var(--muted);display:inline-flex;flex:none;height:2rem;justify-content:center;width:2rem}.status-mark svg{height:1.25rem;width:1.25rem}.status-mark.ok{background:color-mix(in srgb,var(--ok) 14%,var(--surface));color:var(--ok)}.status-mark.danger{background:color-mix(in srgb,var(--danger) 12%,var(--surface));color:var(--danger)}.status-label{color:var(--muted);font-size:.875rem;font-weight:600}.status-label.ok{color:var(--ok)}.status-label.danger{color:var(--danger)}.status-page h1{overflow-wrap:anywhere}.status-copy{color:var(--muted)}.status-actions{display:flex;flex-wrap:wrap;gap:.5rem;margin-top:.25rem}.status-details{border-top:1px solid var(--border);color:var(--muted);font-size:.875rem;margin-top:.25rem;padding-top:.75rem}.status-details>summary{cursor:pointer;width:fit-content}.status-details>p{margin-top:.5rem}.status-details pre{background:var(--surface-2);border:1px solid var(--border);border-radius:calc(var(--radius) / 2);color:var(--text);font-family:var(--mono);font-size:.75rem;line-height:1.55;margin:.5rem 0 0;max-height:18rem;overflow:auto;overflow-wrap:anywhere;padding:.5rem .6rem;white-space:pre-wrap}@media(max-width:40rem){:root{--pad: 1rem}.status-page{margin-top:0;padding:1.25rem var(--pad)}}";
|
package/dist/program-writes.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Budget and outcome accounting for writes dispatched by
|
|
1
|
+
// Budget and outcome accounting for writes dispatched by programs on writable endpoints.
|
|
2
2
|
// Await each dispatched write once; never conceal an unknown outcome.
|
|
3
3
|
import { CONFIG_DEFAULTS } from "./config-defaults.js";
|
|
4
4
|
import { Effect } from "effect";
|
|
@@ -41,7 +41,7 @@ export function writeStateOf(outcome) {
|
|
|
41
41
|
: state;
|
|
42
42
|
}
|
|
43
43
|
/**
|
|
44
|
-
* The
|
|
44
|
+
* The writes one program sends. The play closes this when the
|
|
45
45
|
* program settles — a write that reaches the gate after is not sent — and
|
|
46
46
|
* drains what is on the wire before the run's scope aborts it, so the write
|
|
47
47
|
* finishes and its activity says how it did. The result never hides a write
|