@zackbart/connecta 0.32.0 → 0.34.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 +98 -0
- package/README.md +95 -116
- package/SECURITY.md +1 -1
- package/bin/connecta.mjs +24 -6
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.d.ts +167 -23
- package/dist/access-tokens.js +676 -140
- package/dist/activity.d.ts +3 -0
- package/dist/agent-identity.js +47 -0
- package/dist/auth/cloudflare-access.js +13 -5
- package/dist/branding.d.ts +7 -0
- package/dist/branding.js +76 -9
- 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 +15 -1
- package/dist/config-value-sources.js +16 -9
- package/dist/config.d.ts +48 -75
- package/dist/config.js +144 -36
- package/dist/connector-access.d.ts +4 -34
- package/dist/connector-access.js +15 -99
- package/dist/connector-caller.d.ts +7 -1
- package/dist/connector-caller.js +4 -2
- package/dist/connectors/api-connector.d.ts +27 -4
- package/dist/connectors/api-connector.js +2 -0
- package/dist/connectors/remote-mcp.js +2 -0
- package/dist/describe-config.d.ts +31 -7
- package/dist/describe-config.js +8 -17
- package/dist/execute.js +1 -11
- package/dist/fix-prompt.js +2 -2
- package/dist/index.d.ts +4 -3
- package/dist/index.js +30 -10
- package/dist/invocation.js +5 -3
- package/dist/membership.d.ts +53 -0
- package/dist/membership.js +121 -0
- package/dist/module-contracts.d.ts +23 -6
- package/dist/oauth-callback-outcome.js +2 -2
- package/dist/operator-record.d.ts +2 -2
- package/dist/operator-record.js +2 -4
- package/dist/operator-ui/agents.d.ts +74 -0
- package/dist/operator-ui/agents.js +104 -0
- package/dist/operator-ui/config-summary.js +562 -0
- package/dist/operator-ui/connection-map.js +375 -0
- package/dist/operator-ui/contract.d.ts +149 -28
- package/dist/operator-ui/endpoints.js +221 -0
- package/dist/operator-ui/fix-prompts.js +5 -5
- package/dist/operator-ui/generated.js +5 -4
- package/dist/operator-ui/member-page.js +97 -0
- package/dist/operator-ui/model.d.ts +14 -6
- package/dist/operator-ui/overview.js +134 -0
- package/dist/operator-ui/view.d.ts +107 -37
- package/dist/operator-ui/view.js +163 -86
- package/dist/page-styles.js +5 -3
- package/dist/provider.d.ts +61 -8
- package/dist/provider.js +46 -7
- package/dist/providers/_shared/google/workspace.d.ts +56 -17
- package/dist/providers/_shared/google/workspace.js +34 -8
- 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/ccb/index.js +2 -2
- package/dist/providers/cloudflare/index.js +3 -3
- package/dist/providers/cloudflare/rest.js +25 -3
- package/dist/providers/cloudflare/skill.generated.js +1 -1
- package/dist/providers/cloudflare/value-safety.js +32 -0
- package/dist/providers/docs/index.js +4 -4
- package/dist/providers/docs/skill.generated.js +2 -2
- package/dist/providers/drive/index.js +4 -4
- package/dist/providers/drive/skill.generated.js +2 -2
- package/dist/providers/forms/index.js +4 -4
- package/dist/providers/forms/skill.generated.js +2 -2
- package/dist/providers/gmail/index.js +11 -9
- package/dist/providers/gmail/skill.generated.js +2 -2
- package/dist/providers/mixpanel/index.js +1 -1
- package/dist/providers/mixpanel/skill.generated.js +3 -3
- package/dist/providers/notion/index.d.ts +7 -0
- package/dist/providers/notion/index.js +100 -11
- package/dist/providers/notion/rest.js +45 -5
- package/dist/providers/notion/skill.generated.js +2 -2
- package/dist/providers/notion/value-safety.js +30 -5
- package/dist/providers/overflow/index.js +2 -2
- package/dist/providers/overflow/skill.generated.js +2 -2
- package/dist/providers/planning-center/index.js +2 -2
- package/dist/providers/planning-center/skill.generated.js +4 -4
- package/dist/providers/revenuecat/skill.generated.js +2 -2
- package/dist/providers/sheets/index.d.ts +3 -0
- package/dist/providers/sheets/index.js +7 -4
- package/dist/providers/sheets/skill.generated.js +2 -2
- package/dist/providers/slides/index.js +4 -4
- package/dist/providers/slides/skill.generated.js +2 -2
- package/dist/providers/stripe/rest.js +9 -2
- package/dist/providers/stripe/skill.generated.js +1 -1
- package/dist/providers/stripe/value-safety.js +22 -0
- package/dist/providers/tithely/index.js +1 -1
- package/dist/providers/vercel/index.js +1 -1
- 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/registry.d.ts +2 -3
- package/dist/registry.js +23 -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 +161 -104
- package/dist/routes/oauth-management.js +11 -5
- package/dist/routes/oauth.js +5 -11
- package/dist/routes/operator.js +24 -23
- 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 +5 -0
- package/dist/skills.js +10 -0
- package/dist/storage/keys.js +9 -1
- package/dist/storage/sql.js +5 -1
- package/dist/types.d.ts +29 -7
- package/dist/ui.d.ts +1 -1
- package/dist/ui.js +23 -16
- package/dist/usage-guide.js +1 -1
- 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 +47 -23
- package/documentation/architecture.md +371 -61
- package/documentation/auth.md +897 -249
- package/documentation/code-mode.md +37 -16
- package/documentation/deploying.md +126 -25
- package/documentation/integrating.md +97 -23
- package/documentation/meta-tools.md +50 -31
- package/documentation/migration-0.33.md +352 -0
- package/documentation/migration-0.34.md +67 -0
- package/documentation/operating.md +3 -1
- package/documentation/operator-tests.md +147 -34
- package/documentation/operator-ui.md +519 -64
- package/documentation/provider-auth-migration.md +37 -23
- package/documentation/provider-migration-0.29.md +2 -2
- package/examples/worker/AGENTS.md +37 -9
- package/examples/worker/README.md +127 -61
- package/examples/worker/src/connecta.config.ts +40 -23
- package/examples/worker/wrangler.jsonc +4 -1
- package/package.json +19 -18
- package/templates/node/.env.example +3 -0
- package/templates/node/AGENTS.md +33 -6
- package/templates/node/Dockerfile +2 -2
- package/templates/node/README.md +80 -52
- 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 +28 -12
- package/templates/node/src/rotate-token.ts +17 -0
- package/templates/node/src/provision-token.ts +0 -17
|
@@ -0,0 +1,97 @@
|
|
|
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
|
+
/**
|
|
39
|
+
* A few words under the account's name, only where the badge alone would
|
|
40
|
+
* leave something out: a saved credential that no longer fits, or one saved
|
|
41
|
+
* but not working.
|
|
42
|
+
*/
|
|
43
|
+
function summaryFor(manage, connection) {
|
|
44
|
+
if (manage.kind !== "credential" || !credentialStored(manage))
|
|
45
|
+
return null;
|
|
46
|
+
if (connection.problem === "credential_mismatch")
|
|
47
|
+
return "Saved credential no longer fits";
|
|
48
|
+
return connection.state === "ok" ? null : "Saved, but not working";
|
|
49
|
+
}
|
|
50
|
+
const toolCount = (connector) => connector.tools.length;
|
|
51
|
+
export function memberPage(contract) {
|
|
52
|
+
if (contract?.role !== "member")
|
|
53
|
+
return { endpoints: [], accounts: [], shared: [] };
|
|
54
|
+
return {
|
|
55
|
+
endpoints: contract.pools.map((pool) => ({ pool: pool.name, path: pool.humanPath })),
|
|
56
|
+
accounts: contract.connectors
|
|
57
|
+
.filter((connector) => connector.authScope === "personal")
|
|
58
|
+
.map((connector) => {
|
|
59
|
+
const connection = connector.connection ?? { state: "auth_required" };
|
|
60
|
+
return {
|
|
61
|
+
id: connector.id,
|
|
62
|
+
title: connector.title || connector.id,
|
|
63
|
+
toolCount: toolCount(connector),
|
|
64
|
+
badge: connector.connection
|
|
65
|
+
? {
|
|
66
|
+
label: connectorStatusLabel(connection.state, connection.problem),
|
|
67
|
+
tone: connectorStatusTone(connection.state),
|
|
68
|
+
}
|
|
69
|
+
: null,
|
|
70
|
+
summary: summaryFor(connector.manage, connection),
|
|
71
|
+
manage: connector.manage,
|
|
72
|
+
controls: controlsFor(connector.manage, connection),
|
|
73
|
+
};
|
|
74
|
+
}),
|
|
75
|
+
// Names and counts only: a shared connector's state is not the member's to see.
|
|
76
|
+
shared: contract.connectors
|
|
77
|
+
.filter((connector) => connector.authScope === "shared")
|
|
78
|
+
.map((connector) => ({
|
|
79
|
+
id: connector.id,
|
|
80
|
+
title: connector.title || connector.id,
|
|
81
|
+
toolCount: toolCount(connector),
|
|
82
|
+
})),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export function toolCountLabel(count) {
|
|
86
|
+
return `${count} ${count === 1 ? "tool" : "tools"}`;
|
|
87
|
+
}
|
|
88
|
+
/** The in-page question before a member's action that takes something away. */
|
|
89
|
+
export function memberConfirmCopy(action, name) {
|
|
90
|
+
if (action === "oauth_restart") {
|
|
91
|
+
return { question: `Reconnect ${name}? It stops working until you finish in the new tab.`, confirm: "Reconnect" };
|
|
92
|
+
}
|
|
93
|
+
if (action === "oauth_disconnect") {
|
|
94
|
+
return { question: `Disconnect your ${name} account? Its tools stop working for you.`, confirm: "Disconnect" };
|
|
95
|
+
}
|
|
96
|
+
return { question: `Clear your ${name} credential? Its tools stop working for you.`, confirm: "Clear" };
|
|
97
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
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
4
|
/** Tool verdicts, independent of whether the selected pool is read-only. */
|
|
4
5
|
export type UiToolSafety = "runs_in_programs" | "needs_approval";
|
|
@@ -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;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { endpointRows } from "./endpoints.js";
|
|
2
|
+
import { joinList } from "./connection-map.js";
|
|
3
|
+
import { connectorVerdict } from "./view.js";
|
|
4
|
+
const PROBLEM_TITLE = {
|
|
5
|
+
connector_unavailable: "is unavailable",
|
|
6
|
+
oauth_required: "needs authorization",
|
|
7
|
+
credential_required: "needs a credential",
|
|
8
|
+
auth_required: "needs a secret in the deployment",
|
|
9
|
+
credential_mismatch: "can't use its saved credential",
|
|
10
|
+
catalog_failed: "couldn't load its tools",
|
|
11
|
+
};
|
|
12
|
+
function affects(paths) {
|
|
13
|
+
return paths.length ? `Affects ${joinList(paths)}` : "No endpoint serves it";
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* One row per connector that is not healthy, failing first, then one per
|
|
17
|
+
* agent that sits in an endpoint without a working token. Each row carries
|
|
18
|
+
* the fix the rest of the UI already offers for it.
|
|
19
|
+
*/
|
|
20
|
+
export function attentionItems(contract, options = {}) {
|
|
21
|
+
if (contract.role !== "maintainer")
|
|
22
|
+
return [];
|
|
23
|
+
const rows = endpointRows(contract);
|
|
24
|
+
const connectors = [];
|
|
25
|
+
for (const config of contract.config?.connectors ?? []) {
|
|
26
|
+
const live = contract.live.connectors.find((c) => c.id === config.id);
|
|
27
|
+
if (!live)
|
|
28
|
+
continue;
|
|
29
|
+
const signals = options.signals?.[config.id] ?? {};
|
|
30
|
+
const verdict = connectorVerdict(live, signals);
|
|
31
|
+
if ((verdict.tone !== "warn" && verdict.tone !== "danger") || !verdict.action)
|
|
32
|
+
continue;
|
|
33
|
+
const title = config.title ?? config.id;
|
|
34
|
+
const id = encodeURIComponent(config.id);
|
|
35
|
+
const paths = rows.filter((row) => row.groups.some((group) => group.config.id === config.id)).map((r) => r.path);
|
|
36
|
+
const connection = `/connectors/${id}#connection`;
|
|
37
|
+
const base = {
|
|
38
|
+
key: `connector:${config.id}`,
|
|
39
|
+
kind: "connector",
|
|
40
|
+
tone: verdict.tone,
|
|
41
|
+
subject: title,
|
|
42
|
+
action: { label: verdict.action, href: connection, ariaLabel: `${verdict.action} ${title}` },
|
|
43
|
+
href: connection,
|
|
44
|
+
};
|
|
45
|
+
// The server's problem code outranks a failed read on this page: the row
|
|
46
|
+
// keeps its fix prompt and says the details didn't load. "Unavailable"
|
|
47
|
+
// beside a failed read is the page's own (see connectorVerdict).
|
|
48
|
+
const reported = live.problem ?? (live.status === "error" ? "connector_unavailable" : undefined);
|
|
49
|
+
const problem = signals.failed && reported === "connector_unavailable" ? undefined : reported;
|
|
50
|
+
if (problem) {
|
|
51
|
+
connectors.push({
|
|
52
|
+
...base,
|
|
53
|
+
problem: PROBLEM_TITLE[problem],
|
|
54
|
+
body: signals.failed ? `Details didn't load · ${affects(paths)}` : affects(paths),
|
|
55
|
+
fix: { kind: problem, connectorId: config.id },
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
else if (signals.failed) {
|
|
59
|
+
connectors.push({
|
|
60
|
+
...base,
|
|
61
|
+
problem: "didn't load on this page",
|
|
62
|
+
body: affects(paths),
|
|
63
|
+
fix: null,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
else if (live.status !== "ok") {
|
|
67
|
+
connectors.push({
|
|
68
|
+
...base,
|
|
69
|
+
problem: verdict.label === "Authorization needed" ? "needs authorization" : "isn't connected",
|
|
70
|
+
body: affects(paths),
|
|
71
|
+
fix: null,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
// Healthy but drifted: the live tool list differs from the reviewed one.
|
|
76
|
+
connectors.push({
|
|
77
|
+
...base,
|
|
78
|
+
problem: "has a changed tool list",
|
|
79
|
+
body: affects(paths),
|
|
80
|
+
fix: { kind: "catalog_drift", connectorId: config.id },
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const agents = contract.agents
|
|
85
|
+
.filter((agent) => agent.pools.length && agent.token.state !== "issued")
|
|
86
|
+
.map((agent) => {
|
|
87
|
+
// An agent connects only at a pool's agent URL.
|
|
88
|
+
const paths = agent.pools.map((name) => contract.pools.find((pool) => pool.name === name)?.agentPath ?? `/agent/${name}`);
|
|
89
|
+
const revoked = agent.token.state === "revoked";
|
|
90
|
+
return {
|
|
91
|
+
key: `agent:${agent.name}`,
|
|
92
|
+
kind: "agent",
|
|
93
|
+
tone: "warn",
|
|
94
|
+
subject: agent.name,
|
|
95
|
+
problem: revoked ? "has a revoked token" : "has no token",
|
|
96
|
+
body: `Can't connect at ${joinList(paths)}`,
|
|
97
|
+
action: options.tokensPage
|
|
98
|
+
? { label: "Issue token", href: "/tokens", ariaLabel: `Issue a token for ${agent.name}` }
|
|
99
|
+
: null,
|
|
100
|
+
href: options.tokensPage ? "/tokens" : null,
|
|
101
|
+
fix: null,
|
|
102
|
+
};
|
|
103
|
+
});
|
|
104
|
+
return [
|
|
105
|
+
...connectors.filter((item) => item.tone === "danger"),
|
|
106
|
+
...connectors.filter((item) => item.tone === "warn"),
|
|
107
|
+
...agents,
|
|
108
|
+
];
|
|
109
|
+
}
|
|
110
|
+
/** The numbers above the list, counted from the same map the page draws. */
|
|
111
|
+
export function overviewHeadline(contract, map, items, signals = {}) {
|
|
112
|
+
const bar = (contract.config?.connectors ?? []).flatMap((config) => {
|
|
113
|
+
const live = contract.live.connectors.find((c) => c.id === config.id);
|
|
114
|
+
if (!live)
|
|
115
|
+
return [];
|
|
116
|
+
const health = connectorVerdict(live, signals[config.id]);
|
|
117
|
+
return [{ id: config.id, title: config.title ?? config.id, tone: health.tone, label: health.label }];
|
|
118
|
+
});
|
|
119
|
+
const healthy = bar.filter((entry) => entry.tone === "ok").length;
|
|
120
|
+
const warn = bar.filter((entry) => entry.tone === "warn").length;
|
|
121
|
+
const danger = bar.filter((entry) => entry.tone === "danger").length;
|
|
122
|
+
const count = (kind) => map.nodes.filter((node) => node.kind === kind).length;
|
|
123
|
+
return {
|
|
124
|
+
connectors: bar.length,
|
|
125
|
+
healthy,
|
|
126
|
+
warn,
|
|
127
|
+
danger,
|
|
128
|
+
bar,
|
|
129
|
+
endpoints: count("endpoint"),
|
|
130
|
+
readOnlyEndpoints: map.nodes.filter((node) => node.kind === "endpoint" && node.readOnly).length,
|
|
131
|
+
people: count("person"),
|
|
132
|
+
agents: contract.agents.length,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
@@ -1,30 +1,29 @@
|
|
|
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
|
-
|
|
8
|
-
* implementation it uses, since a dual provider's `auth` selects either the
|
|
9
|
-
* vendor's hosted MCP server or Connecta's REST connector.
|
|
10
|
-
*/
|
|
11
|
-
export declare function connectorSourceLabel(source: {
|
|
12
|
-
kind: string;
|
|
13
|
-
provider?: string;
|
|
14
|
-
}): string;
|
|
15
|
-
export type OperatorPage = "overview" | "tools" | "access" | "config" | "connector" | "tokens" | "connections" | "activity";
|
|
16
|
-
/** Pages the nav lists. */
|
|
7
|
+
export type OperatorPage = "overview" | "endpoints" | "settings" | "connector" | "tokens" | "connections" | "activity";
|
|
8
|
+
/** Pages the nav lists, in dashboard order. */
|
|
17
9
|
export declare const OPERATOR_PAGES: readonly OperatorPage[];
|
|
18
10
|
export declare const PAGE_META: Readonly<Record<OperatorPage, {
|
|
19
11
|
path: string;
|
|
20
12
|
label: string;
|
|
21
13
|
}>>;
|
|
14
|
+
/** Legacy links remain valid, with the browser replacing their URL. */
|
|
15
|
+
export declare function canonicalOperatorPath(path: string): string;
|
|
22
16
|
/**
|
|
23
17
|
* What each page is for, in one line under its heading — on the gate as well
|
|
24
18
|
* as once signed in, so the page an operator lands on reads the same before
|
|
25
19
|
* and after the session is checked.
|
|
26
20
|
*/
|
|
27
21
|
export declare function pageDescription(page: OperatorPage, productDescription: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Signed in, but neither a maintainer nor a member of any endpoint: who to
|
|
24
|
+
* ask, and that this is not an error to retry.
|
|
25
|
+
*/
|
|
26
|
+
export declare function noAccessCopy(kind: string, productName: string): string;
|
|
28
27
|
/** What the gate says while the session is checked, page by page. */
|
|
29
28
|
export declare function checkingCopy(): string;
|
|
30
29
|
export declare function pageForPath(path: string): OperatorPage;
|
|
@@ -83,12 +82,16 @@ export declare function failure(message: string, fix?: NoticeFix): Notice;
|
|
|
83
82
|
* mutation that is a permission, not an expired session, so it stays inline.
|
|
84
83
|
* - `network` — the browser could not reach the deployment at all.
|
|
85
84
|
* - `refused` — the route answered, and said no.
|
|
85
|
+
* - `stale` — never sent: what the action was checked against changed before
|
|
86
|
+
* dispatch (a refreshed slot list or member contract).
|
|
86
87
|
*/
|
|
87
|
-
export type RequestFailureKind = "session" | "forbidden" | "network" | "refused";
|
|
88
|
+
export type RequestFailureKind = "session" | "forbidden" | "network" | "refused" | "stale";
|
|
88
89
|
export interface RequestFailureFacts {
|
|
89
90
|
kind: RequestFailureKind;
|
|
90
91
|
status?: number | undefined;
|
|
91
92
|
problem?: unknown;
|
|
93
|
+
/** A refusal's machine `code` from a closed set, such as `slot_changed`; checked, never shown. */
|
|
94
|
+
code?: unknown;
|
|
92
95
|
}
|
|
93
96
|
/**
|
|
94
97
|
* What the notices for an OAuth action and a credential Test say. Each of
|
|
@@ -121,6 +124,8 @@ export declare function credentialTestNotice(connectorId: string, answer: {
|
|
|
121
124
|
* checked, not trusted.
|
|
122
125
|
*/
|
|
123
126
|
export declare function refusedNotice(action: DownstreamAction, connectorId: string, problem?: unknown): Notice;
|
|
127
|
+
/** An action whose permission or target changed before it was sent. */
|
|
128
|
+
export declare const STALE_COPY = "This changed before sending, so nothing was sent. Try again.";
|
|
124
129
|
/**
|
|
125
130
|
* Every row action's failure, in one voice. The page's own failures — a
|
|
126
131
|
* lapsed session, a denied identity, a dropped connection — read the same for
|
|
@@ -133,7 +138,7 @@ export declare function actionFailedNotice(action: RowAction, connectorId: strin
|
|
|
133
138
|
* server's `connector_unavailable` and keeps its own copy and fix prompt;
|
|
134
139
|
* these two have nothing for a coding agent to fix.
|
|
135
140
|
*/
|
|
136
|
-
export type ConnectorLoadFailure = "session" | "network";
|
|
141
|
+
export type ConnectorLoadFailure = "session" | "network" | "unavailable";
|
|
137
142
|
export declare function connectorLoadFailureCopy(failure: ConnectorLoadFailure, productName: string): string;
|
|
138
143
|
/** The signed-in page's answer when `/ui/data` could not be read. */
|
|
139
144
|
export declare function loadFailureCopy(failure: "server" | "network", productName: string): {
|
|
@@ -158,20 +163,27 @@ export declare function confirmCopy(action: PendingConfirm["action"], name: stri
|
|
|
158
163
|
* page fetch again after an identity change.
|
|
159
164
|
*/
|
|
160
165
|
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
166
|
export declare function accessTokenUnavailableCopy(capability?: string): string;
|
|
169
167
|
export interface OperatorState {
|
|
170
168
|
tokenPhase: LoadPhase;
|
|
171
169
|
tokenNotice: Notice | null;
|
|
172
|
-
tokens
|
|
170
|
+
/** One slot per declared agent, from `/ui/access-tokens`. */
|
|
171
|
+
tokens: AgentSlot[];
|
|
172
|
+
/** A just-issued secret, shown once in its dialog and dropped when that closes. */
|
|
173
173
|
createdToken: string | null;
|
|
174
|
-
|
|
174
|
+
/** The agent `createdToken` belongs to. */
|
|
175
|
+
createdTokenFor: string | null;
|
|
176
|
+
/** A rotation or revocation waiting on its in-row confirmation. */
|
|
177
|
+
tokenConfirm: {
|
|
178
|
+
name: string;
|
|
179
|
+
action: "rotate" | "revoke";
|
|
180
|
+
} | null;
|
|
181
|
+
/**
|
|
182
|
+
* Agents whose last token operation ended with an unknown outcome (503,
|
|
183
|
+
* 499, or a dropped connection). Their actions stay off until a slot list
|
|
184
|
+
* read that started after the failure succeeds.
|
|
185
|
+
*/
|
|
186
|
+
unresolvedSlots: string[];
|
|
175
187
|
tokenBusy: boolean;
|
|
176
188
|
page: OperatorPage;
|
|
177
189
|
/**
|
|
@@ -180,11 +192,17 @@ export interface OperatorState {
|
|
|
180
192
|
* the fence that keeps one identity's data off another identity's screen.
|
|
181
193
|
*/
|
|
182
194
|
generation: number;
|
|
183
|
-
/**
|
|
184
|
-
|
|
195
|
+
/**
|
|
196
|
+
* `loading` until `/ui/api/config` answers (and, for a maintainer,
|
|
197
|
+
* `/ui/data` too). `denied` is signed in as someone who is neither a
|
|
198
|
+
* maintainer nor a pool member.
|
|
199
|
+
*/
|
|
200
|
+
session: "loading" | "gated" | "ready" | "denied";
|
|
185
201
|
gate: Notice | null;
|
|
186
202
|
/** True while a signed-in operator's /ui/data is in flight. */
|
|
187
203
|
refreshing: boolean;
|
|
204
|
+
/** A bounded admission recovery is pending; Retry can supersede it. */
|
|
205
|
+
recoveryPending: boolean;
|
|
188
206
|
/**
|
|
189
207
|
* Why the last /ui/data read failed when the session itself was not the
|
|
190
208
|
* reason: the page stays signed in, with its chrome, and offers a retry.
|
|
@@ -204,7 +222,14 @@ export interface OperatorState {
|
|
|
204
222
|
*/
|
|
205
223
|
focusIfLost: string | null;
|
|
206
224
|
data: UiData | null;
|
|
207
|
-
contract
|
|
225
|
+
/** Operator contract v2, for either role; null until `/ui/api/config` answers. */
|
|
226
|
+
contract: OperatorUiContractV2 | null;
|
|
227
|
+
/**
|
|
228
|
+
* The role the last good contract confirmed. It outlives a failed refresh
|
|
229
|
+
* that clears `contract`, so the next answer with a different role is
|
|
230
|
+
* still recognized as a different viewer. Only an identity reset clears it.
|
|
231
|
+
*/
|
|
232
|
+
role: OperatorRole | null;
|
|
208
233
|
contractPhase: LoadPhase;
|
|
209
234
|
health: "loading" | "ok" | "unavailable";
|
|
210
235
|
connectorFilter: string;
|
|
@@ -218,6 +243,11 @@ export interface OperatorState {
|
|
|
218
243
|
* new tab: its row offers the link instead of the button.
|
|
219
244
|
*/
|
|
220
245
|
oauthBlocked: string | null;
|
|
246
|
+
/**
|
|
247
|
+
* The authorization link the route returned for `oauthBlocked`. A member has
|
|
248
|
+
* no `/ui/data` connector to keep it on, so it is held here.
|
|
249
|
+
*/
|
|
250
|
+
oauthBlockedUrl: string | null;
|
|
221
251
|
/** A row action waiting for its in-page confirmation. */
|
|
222
252
|
confirming: PendingConfirm | null;
|
|
223
253
|
/** Connectors whose details failed on this side of the deployment. */
|
|
@@ -235,6 +265,31 @@ export interface OperatorState {
|
|
|
235
265
|
activityCursor: string | null;
|
|
236
266
|
activitySearch: string;
|
|
237
267
|
}
|
|
268
|
+
/** Who the contract says this viewer is, or null before it loads. */
|
|
269
|
+
export declare function operatorRole(state: Pick<OperatorState, "contract">): OperatorRole | null;
|
|
270
|
+
/**
|
|
271
|
+
* The role pages and navigation render for. The contract's, or, while a
|
|
272
|
+
* refresh that failed has left no contract on screen, maintainer when the
|
|
273
|
+
* last good contract confirmed one and that maintainer's data is still here:
|
|
274
|
+
* their page stays mounted on its own retry state instead of being torn down
|
|
275
|
+
* mid-recovery. Never a request's permission; the store sends maintainer
|
|
276
|
+
* reads only while the contract itself says maintainer.
|
|
277
|
+
*/
|
|
278
|
+
export declare function displayRole(state: Pick<OperatorState, "contract" | "role" | "data">): OperatorRole | null;
|
|
279
|
+
/**
|
|
280
|
+
* A confirmed maintainer's page is mounted without a contract behind it: a
|
|
281
|
+
* refresh failed, or its read ran past the deadline. The shell shows one
|
|
282
|
+
* recovery banner with Retry above whichever page is open, and that page is
|
|
283
|
+
* stale until a contract confirms the role again.
|
|
284
|
+
*/
|
|
285
|
+
export declare function contractRecovering(state: Pick<OperatorState, "contract" | "role" | "data">): boolean;
|
|
286
|
+
/**
|
|
287
|
+
* Whether Activity is open to this viewer: maintainers only, on a deployment
|
|
288
|
+
* that records history. Members always read "forbidden".
|
|
289
|
+
*/
|
|
290
|
+
export declare function activityReadable(state: Pick<OperatorState, "contract" | "data">): boolean;
|
|
291
|
+
/** The contract's half of `activityReadable`: a maintainer, on a deployment that records. */
|
|
292
|
+
export declare function activityAllowed(state: Pick<OperatorState, "contract">): boolean;
|
|
238
293
|
export declare function initialState(page: OperatorPage): OperatorState;
|
|
239
294
|
/**
|
|
240
295
|
* Drop to the gate and forget the previous operator. Bumping the generation is
|
|
@@ -254,7 +309,7 @@ export declare function credentialUnavailableCopy(capability?: CredentialManagem
|
|
|
254
309
|
*/
|
|
255
310
|
export declare function connectorStatusLabel(status: string, problem?: UiProblem): string;
|
|
256
311
|
/** The tone a status carries wherever it is rendered as a badge or a tile. */
|
|
257
|
-
export type Tone = "ok" | "warn" | "danger" | "neutral";
|
|
312
|
+
export type Tone = "ok" | "warn" | "danger" | "neutral" | "accent";
|
|
258
313
|
export declare function connectorStatusTone(status: string): Tone;
|
|
259
314
|
export declare function problemCopy(problem: UiProblem | undefined): string | null;
|
|
260
315
|
/**
|
|
@@ -263,12 +318,31 @@ export declare function problemCopy(problem: UiProblem | undefined): string | nu
|
|
|
263
318
|
* error. The two used to share the red box, so the row contradicted itself.
|
|
264
319
|
*/
|
|
265
320
|
export declare function problemTone(problem: UiProblem): "warn" | "danger";
|
|
321
|
+
/** What the page knows about a connector beyond its live status: catalog drift and a failed detail read. */
|
|
322
|
+
export interface ConnectorSignals {
|
|
323
|
+
drift?: boolean;
|
|
324
|
+
failed?: boolean;
|
|
325
|
+
}
|
|
326
|
+
/** One connector's health everywhere it is shown: badge tone, status word, and the one action beside it. */
|
|
327
|
+
export interface ConnectorVerdict {
|
|
328
|
+
tone: "ok" | "warn" | "danger" | "neutral";
|
|
329
|
+
label: string;
|
|
330
|
+
/** The link to the Connection tab, or null when the connector is fine. */
|
|
331
|
+
action: "Connect" | "Replace key" | "Inspect" | "Review" | null;
|
|
332
|
+
}
|
|
266
333
|
/**
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
*
|
|
334
|
+
* The verdict the Connectors list, the Overview, Endpoints and a connector's
|
|
335
|
+
* page all show. The server's problem code comes first, then a failed detail
|
|
336
|
+
* read on this page, then a non-ok status, then catalog drift. A problem
|
|
337
|
+
* outranks the status, so a connector whose catalog failed is never
|
|
338
|
+
* "Connected". A failed read marks its connector `connector_unavailable` in
|
|
339
|
+
* the store, so that one code beside a failed read is the page's own and
|
|
340
|
+
* reads "Couldn't load" instead of blaming the downstream.
|
|
270
341
|
*/
|
|
271
|
-
export declare function
|
|
342
|
+
export declare function connectorVerdict(live: {
|
|
343
|
+
status: string;
|
|
344
|
+
problem?: UiProblem;
|
|
345
|
+
}, signals?: ConnectorSignals): ConnectorVerdict;
|
|
272
346
|
/**
|
|
273
347
|
* Hosted-provider catalog drift, as an operator reads it
|
|
274
348
|
* ([#343](https://github.com/zackbart/connecta/issues/343)).
|
|
@@ -314,6 +388,8 @@ export declare function activityOutcomeBadge(outcome: string): {
|
|
|
314
388
|
label: string;
|
|
315
389
|
tone: Tone;
|
|
316
390
|
};
|
|
391
|
+
/** Why a call stalled or failed, in words; "" when nothing was recorded. */
|
|
392
|
+
export declare function activityReason(event: UiActivityEvent): string;
|
|
317
393
|
/** The one-line detail under an address: where it ran, retries, and why it stalled. */
|
|
318
394
|
export declare function activityDetail(event: UiActivityEvent): string;
|
|
319
395
|
/**
|
|
@@ -321,12 +397,6 @@ export declare function activityDetail(event: UiActivityEvent): string;
|
|
|
321
397
|
* beside it is not shown: it can carry a vault's own words.
|
|
322
398
|
*/
|
|
323
399
|
export declare function credentialProblemCopy(problem: string | undefined): string;
|
|
324
|
-
export declare function credentialStateLabel(credential: {
|
|
325
|
-
configured: boolean;
|
|
326
|
-
fields?: unknown[];
|
|
327
|
-
lastFour?: string;
|
|
328
|
-
updatedAt?: string;
|
|
329
|
-
}): string;
|
|
330
400
|
/** Gate copy for each browser-auth shape, so the sign-in state is never a blank page. */
|
|
331
401
|
export declare function gateCopy(kind: string, signedIn: boolean): string;
|
|
332
402
|
export {};
|