@opengeni/core 0.28.1 → 1.0.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/README.md +1 -1
- package/dist/access/external-actor-authority.d.ts +47 -0
- package/dist/access/index.d.ts +182 -3
- package/dist/access/service-initiator.d.ts +3 -0
- package/dist/application/api-integration-servers.d.ts +20 -0
- package/dist/application/archived-session-imports.d.ts +6 -0
- package/dist/application/composer-submit.d.ts +24 -0
- package/dist/application/connect-authority.d.ts +13 -0
- package/dist/application/connect-operation.d.ts +31 -0
- package/dist/application/external-continuation.d.ts +15 -0
- package/dist/application/external-identity-lifecycle.d.ts +20 -0
- package/dist/application/external-link-work-admission.d.ts +12 -0
- package/dist/application/external-workspace-members.d.ts +34 -0
- package/dist/application/modal-native-worker-host-transport.d.ts +297 -0
- package/dist/application/new-session-drafts.d.ts +39 -2
- package/dist/application/sandbox-recovery.d.ts +58 -0
- package/dist/application/session-commands.d.ts +26 -13
- package/dist/application/session-mcp-credential-rotation.d.ts +29 -0
- package/dist/application/session-tenancy.d.ts +19 -0
- package/dist/application/user-resource-grants.d.ts +20 -0
- package/dist/billing/limits.d.ts +11 -0
- package/dist/canonical-human-identities.d.ts +7 -2
- package/dist/canonical-human-identities.js +17 -4
- package/dist/canonical-human-identities.js.map +1 -1
- package/dist/{chunk-6WKPWE5S.js → chunk-2MKMRGSY.js} +108 -24
- package/dist/chunk-2MKMRGSY.js.map +1 -0
- package/dist/{chunk-NYPMQ7NO.js → chunk-GVO3N37G.js} +205 -37
- package/dist/chunk-GVO3N37G.js.map +1 -0
- package/dist/{chunk-JJU6I5XD.js → chunk-LCQXNR62.js} +36 -10
- package/dist/chunk-LCQXNR62.js.map +1 -0
- package/dist/chunk-MM6N7CTO.js +387 -0
- package/dist/chunk-MM6N7CTO.js.map +1 -0
- package/dist/chunk-QO5GVFFO.js +235 -0
- package/dist/chunk-QO5GVFFO.js.map +1 -0
- package/dist/codex-model-availability.d.ts +14 -0
- package/dist/default-session-model.d.ts +139 -0
- package/dist/dependencies.d.ts +87 -4
- package/dist/developer-setup-credential-ceiling.d.ts +10 -0
- package/dist/domain/agent-config-resolution.d.ts +49 -0
- package/dist/domain/automations.d.ts +58 -0
- package/dist/domain/capabilities.d.ts +22 -4
- package/dist/domain/company-profile-agent-admin.d.ts +25 -0
- package/dist/domain/connection-account-selection-error.d.ts +8 -0
- package/dist/domain/connector-tool-permissions.d.ts +36 -0
- package/dist/domain/direct-model-provider.d.ts +3 -0
- package/dist/domain/editable-artifacts/agent-application.d.ts +10 -1
- package/dist/domain/editable-artifacts/durable-export.d.ts +22 -1
- package/dist/domain/editable-artifacts/ports.d.ts +10 -0
- package/dist/domain/editable-artifacts/service.d.ts +4 -3
- package/dist/domain/environments.d.ts +5 -9
- package/dist/domain/external-creation-attribution.d.ts +6 -0
- package/dist/domain/file-owner.d.ts +8 -0
- package/dist/domain/github-action-policies.d.ts +36 -0
- package/dist/domain/github-repository-bindings.d.ts +117 -0
- package/dist/domain/github-skill-source.d.ts +5 -0
- package/dist/domain/governed-learning-slack-publication.d.ts +45 -0
- package/dist/domain/insights-timing.d.ts +16 -0
- package/dist/domain/insights-usage.d.ts +27 -0
- package/dist/domain/insights.d.ts +28 -1
- package/dist/domain/knowledge-files.d.ts +20 -0
- package/dist/domain/knowledge-messages.d.ts +39 -0
- package/dist/domain/knowledge-messages.test.d.ts +1 -0
- package/dist/domain/knowledge-preparation.d.ts +10 -0
- package/dist/domain/knowledge-preparation.test.d.ts +1 -0
- package/dist/domain/knowledge-search.d.ts +15 -0
- package/dist/domain/knowledge-search.test.d.ts +1 -0
- package/dist/domain/knowledge.d.ts +9 -0
- package/dist/domain/mcp-account-bindings.d.ts +19 -0
- package/dist/domain/mcp-account-routes.d.ts +21 -0
- package/dist/domain/native-mcp-connection-admission.d.ts +3 -0
- package/dist/domain/organization-integration-catalog.d.ts +15 -0
- package/dist/domain/organization-membership-lifecycle.d.ts +1 -0
- package/dist/domain/personal-connection-delegations.d.ts +87 -4
- package/dist/domain/pr-review.d.ts +249 -0
- package/dist/domain/resources.d.ts +24 -3
- package/dist/domain/scheduled-task-access.d.ts +247 -0
- package/dist/domain/scheduled-tasks.d.ts +103 -6
- package/dist/domain/session-connection-accounts.d.ts +16 -0
- package/dist/domain/session-file-access.d.ts +4 -0
- package/dist/domain/session-native-connection-replacement.d.ts +14 -0
- package/dist/domain/session-start-timing.d.ts +7 -0
- package/dist/domain/session-tool-policy.d.ts +93 -4
- package/dist/domain/sessions.d.ts +275 -28
- package/dist/domain/skill-imports.d.ts +1 -0
- package/dist/domain/skill-search.d.ts +30 -0
- package/dist/domain/skills.d.ts +117 -0
- package/dist/domain/slack-bot.d.ts +46 -1
- package/dist/domain/workspace-members.d.ts +8 -1
- package/dist/editable-artifact-live/application.d.ts +12 -3
- package/dist/editable-artifact-live/ports.d.ts +0 -1
- package/dist/editable-artifact-live/server.d.ts +5 -2
- package/dist/editable-artifact-live/ticket.d.ts +2 -2
- package/dist/editable-artifact-live/types.d.ts +14 -2
- package/dist/editable-artifact-live.js +2 -2
- package/dist/editable-artifacts.js +10 -4
- package/dist/index.d.ts +49 -3
- package/dist/index.js +18812 -6833
- package/dist/index.js.map +1 -1
- package/dist/managed-auth-session-sets.d.ts +131 -0
- package/dist/managed-auth-session-sets.js +51 -0
- package/dist/managed-auth-session-sets.js.map +1 -0
- package/dist/managed-session.d.ts +98 -11
- package/dist/model-catalog.d.ts +117 -0
- package/dist/product-usage-metrics.d.ts +30 -0
- package/dist/rigs/index.d.ts +22 -5
- package/dist/rigs/provider-images.d.ts +1 -1
- package/dist/sandbox/fleet.d.ts +59 -10
- package/dist/sandbox/routing.d.ts +22 -2
- package/dist/sandbox/runtime-settings.d.ts +56 -0
- package/dist/session-authorization.d.ts +38 -1
- package/dist/site-session-origin.d.ts +9 -0
- package/dist/transcription.d.ts +12 -1
- package/dist/turn-surface.d.ts +18 -0
- package/dist/user-presence.d.ts +32 -0
- package/package.json +18 -11
- package/src/access/external-actor-authority.ts +94 -0
- package/src/access/index.ts +1386 -44
- package/src/access/service-initiator.ts +50 -0
- package/src/application/api-integration-servers.ts +215 -0
- package/src/application/archived-session-imports.ts +161 -0
- package/src/application/composer-submit.ts +77 -0
- package/src/application/connect-authority.ts +77 -0
- package/src/application/connect-operation.ts +80 -0
- package/src/application/external-continuation.ts +129 -0
- package/src/application/external-identity-lifecycle.ts +48 -0
- package/src/application/external-link-work-admission.ts +90 -0
- package/src/application/external-workspace-members.ts +232 -0
- package/src/application/modal-native-worker-host-transport.ts +263 -0
- package/src/application/new-session-drafts.ts +222 -29
- package/src/application/sandbox-recovery.ts +78 -0
- package/src/application/session-commands.ts +430 -73
- package/src/application/session-mcp-credential-rotation.ts +217 -0
- package/src/application/session-tenancy.ts +445 -0
- package/src/application/user-resource-grants.ts +154 -0
- package/src/billing/limits.ts +103 -24
- package/src/canonical-human-identities.ts +22 -3
- package/src/codex-model-availability.ts +136 -0
- package/src/default-session-model.ts +656 -0
- package/src/dependencies.ts +97 -5
- package/src/developer-setup-credential-ceiling.ts +60 -0
- package/src/domain/agent-config-resolution.ts +148 -0
- package/src/domain/automations.ts +270 -0
- package/src/domain/capabilities.ts +244 -151
- package/src/domain/company-profile-agent-admin.ts +56 -0
- package/src/domain/connection-account-selection-error.ts +19 -0
- package/src/domain/connector-tool-permissions.ts +371 -0
- package/src/domain/direct-model-provider.ts +62 -0
- package/src/domain/editable-artifacts/agent-application.ts +61 -15
- package/src/domain/editable-artifacts/durable-export.ts +100 -5
- package/src/domain/editable-artifacts/in-memory.ts +6 -0
- package/src/domain/editable-artifacts/ports.ts +11 -0
- package/src/domain/editable-artifacts/service.ts +32 -1
- package/src/domain/editable-artifacts/snapshot-compaction.ts +9 -5
- package/src/domain/editable-artifacts/snapshot-verifier-genesis.ts +10 -8
- package/src/domain/editable-artifacts/snapshot-verifier.ts +10 -3
- package/src/domain/environments.ts +25 -45
- package/src/domain/external-creation-attribution.ts +22 -0
- package/src/domain/file-owner.ts +67 -0
- package/src/domain/github-action-policies.ts +169 -0
- package/src/domain/github-repository-bindings.ts +295 -0
- package/src/domain/github-skill-source.ts +233 -0
- package/src/domain/governed-learning-slack-publication.ts +157 -0
- package/src/domain/insights-timing.ts +95 -0
- package/src/domain/insights-usage.ts +67 -0
- package/src/domain/insights.ts +270 -169
- package/src/domain/knowledge-files.ts +46 -0
- package/src/domain/knowledge-messages.test.ts +91 -0
- package/src/domain/knowledge-messages.ts +106 -0
- package/src/domain/knowledge-preparation.test.ts +276 -0
- package/src/domain/knowledge-preparation.ts +147 -0
- package/src/domain/knowledge-search.test.ts +561 -0
- package/src/domain/knowledge-search.ts +256 -0
- package/src/domain/knowledge.ts +133 -0
- package/src/domain/mcp-account-bindings.ts +171 -0
- package/src/domain/mcp-account-routes.ts +121 -0
- package/src/domain/native-mcp-connection-admission.ts +14 -0
- package/src/domain/organization-integration-catalog.ts +88 -0
- package/src/domain/organization-membership-lifecycle.ts +10 -0
- package/src/domain/personal-connection-delegations.ts +758 -115
- package/src/domain/pr-review.ts +687 -0
- package/src/domain/resources.ts +182 -25
- package/src/domain/scheduled-task-access.ts +1052 -0
- package/src/domain/scheduled-tasks.ts +1952 -215
- package/src/domain/session-connection-accounts.ts +67 -0
- package/src/domain/session-file-access.ts +47 -0
- package/src/domain/session-native-connection-replacement.ts +64 -0
- package/src/domain/session-start-timing.ts +55 -0
- package/src/domain/session-tool-policy.ts +443 -19
- package/src/domain/sessions.ts +3417 -619
- package/src/domain/skill-imports.ts +137 -17
- package/src/domain/skill-search.ts +221 -0
- package/src/domain/skills.ts +104 -0
- package/src/domain/slack-bot.ts +128 -3
- package/src/domain/workspace-members.ts +34 -2
- package/src/editable-artifact-live/application.ts +29 -4
- package/src/editable-artifact-live/ports.ts +0 -1
- package/src/editable-artifact-live/server.ts +167 -13
- package/src/editable-artifact-live/ticket.ts +87 -6
- package/src/editable-artifact-live/types.ts +16 -2
- package/src/editable-artifact-live/wire.ts +0 -10
- package/src/index.ts +69 -4
- package/src/managed-auth-session-sets.ts +371 -0
- package/src/managed-session.ts +520 -2
- package/src/model-catalog.ts +873 -0
- package/src/product-usage-metrics.ts +101 -0
- package/src/rigs/index.ts +179 -63
- package/src/rigs/provider-images.ts +2 -3
- package/src/sandbox/fleet.ts +471 -59
- package/src/sandbox/routing.ts +160 -33
- package/src/sandbox/runtime-settings.ts +281 -0
- package/src/session-authorization.ts +262 -78
- package/src/site-session-origin.ts +22 -0
- package/src/transcription.ts +13 -1
- package/src/turn-surface.ts +45 -0
- package/src/user-presence.ts +134 -0
- package/dist/chunk-6WKPWE5S.js.map +0 -1
- package/dist/chunk-IBOEYG6N.js +0 -34
- package/dist/chunk-IBOEYG6N.js.map +0 -1
- package/dist/chunk-JJU6I5XD.js.map +0 -1
- package/dist/chunk-NYPMQ7NO.js.map +0 -1
- package/dist/domain/durable-learning-slack-publication.d.ts +0 -72
- package/dist/domain/packs.d.ts +0 -55
- package/src/domain/durable-learning-slack-publication.ts +0 -202
- package/src/domain/packs.ts +0 -556
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CompanyProfileAgentAttempt,
|
|
3
|
+
CompanyProfileAgentConfirmRequest,
|
|
4
|
+
CompanyProfileAgentProposalRequest,
|
|
5
|
+
type CompanyProfileAgentConfirmationReceipt,
|
|
6
|
+
type CompanyProfileAgentProposalReceipt,
|
|
7
|
+
} from "@opengeni/contracts";
|
|
8
|
+
import {
|
|
9
|
+
confirmCompanyProfileForAgent,
|
|
10
|
+
proposeCompanyProfileForAgent,
|
|
11
|
+
type Database,
|
|
12
|
+
} from "@opengeni/db";
|
|
13
|
+
export { CompanyProfileAgentAdminError } from "@opengeni/db";
|
|
14
|
+
|
|
15
|
+
export type CompanyProfileAgentAdminRouterOptions = {
|
|
16
|
+
db: Database;
|
|
17
|
+
propose?: typeof proposeCompanyProfileForAgent;
|
|
18
|
+
confirm?: typeof confirmCompanyProfileForAgent;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Explicit organization administration is intentionally separate from derived
|
|
23
|
+
* workspace learning. The database capabilities own exact-attempt admission,
|
|
24
|
+
* current organization-owner authority, the separate organization policy,
|
|
25
|
+
* canonical review when required, tenant isolation, CAS, and immutable
|
|
26
|
+
* receipts.
|
|
27
|
+
*/
|
|
28
|
+
export function createCompanyProfileAgentAdminRouter(
|
|
29
|
+
options: CompanyProfileAgentAdminRouterOptions,
|
|
30
|
+
): {
|
|
31
|
+
propose: (input: {
|
|
32
|
+
attempt: unknown;
|
|
33
|
+
request: unknown;
|
|
34
|
+
}) => Promise<CompanyProfileAgentProposalReceipt>;
|
|
35
|
+
confirm: (input: {
|
|
36
|
+
attempt: unknown;
|
|
37
|
+
request: unknown;
|
|
38
|
+
}) => Promise<CompanyProfileAgentConfirmationReceipt>;
|
|
39
|
+
} {
|
|
40
|
+
const propose = options.propose ?? proposeCompanyProfileForAgent;
|
|
41
|
+
const confirm = options.confirm ?? confirmCompanyProfileForAgent;
|
|
42
|
+
return {
|
|
43
|
+
async propose(input) {
|
|
44
|
+
return await propose(options.db, {
|
|
45
|
+
attempt: CompanyProfileAgentAttempt.parse(input.attempt),
|
|
46
|
+
request: CompanyProfileAgentProposalRequest.parse(input.request),
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
async confirm(input) {
|
|
50
|
+
return await confirm(options.db, {
|
|
51
|
+
attempt: CompanyProfileAgentAttempt.parse(input.attempt),
|
|
52
|
+
request: CompanyProfileAgentConfirmRequest.parse(input.request),
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { HTTPException } from "hono/http-exception";
|
|
2
|
+
import { ConnectionAccountSelectionDiagnostic } from "@opengeni/contracts";
|
|
3
|
+
|
|
4
|
+
/** An account choice needs attention; retrying the same work cannot fix it. */
|
|
5
|
+
export class ConnectionAccountSelectionError extends HTTPException {
|
|
6
|
+
override name = "ConnectionAccountSelectionError";
|
|
7
|
+
readonly diagnostic: ConnectionAccountSelectionDiagnostic;
|
|
8
|
+
constructor(
|
|
9
|
+
message: string,
|
|
10
|
+
diagnostic: ConnectionAccountSelectionDiagnostic = {
|
|
11
|
+
version: 1,
|
|
12
|
+
reason: "selection_unavailable",
|
|
13
|
+
accounts: [],
|
|
14
|
+
},
|
|
15
|
+
) {
|
|
16
|
+
super(422, { message });
|
|
17
|
+
this.diagnostic = ConnectionAccountSelectionDiagnostic.parse(diagnostic);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { slackRestMcpToolsForScopes } from "@opengeni/contracts/slack-rest-mcp";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
4
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
5
|
+
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
6
|
+
import {
|
|
7
|
+
boundMcpResponseBody,
|
|
8
|
+
MCP_MAX_RESPONSE_BYTES,
|
|
9
|
+
assertMcpToolListWithinBounds,
|
|
10
|
+
} from "@opengeni/runtime/mcp-network";
|
|
11
|
+
import { createPinnedIntegrationTransport } from "@opengeni/capabilities";
|
|
12
|
+
import type { Settings } from "@opengeni/config";
|
|
13
|
+
import type {
|
|
14
|
+
AccessGrant,
|
|
15
|
+
ConnectorToolPermission,
|
|
16
|
+
ConnectorToolPermissionEntry,
|
|
17
|
+
ConnectorToolPermissionsResponse,
|
|
18
|
+
UpdateConnectorToolPermissionsRequest,
|
|
19
|
+
ConnectionMetadata,
|
|
20
|
+
McpServerConnectionRef,
|
|
21
|
+
} from "@opengeni/contracts";
|
|
22
|
+
import {
|
|
23
|
+
buildConnectionTokenResolver,
|
|
24
|
+
getConnectionMetadata,
|
|
25
|
+
listConnectionsMetadata,
|
|
26
|
+
listEnabledMcpCapabilityServers,
|
|
27
|
+
listConnectorToolPermissionPolicies,
|
|
28
|
+
updateConnectorToolPermissionPolicies,
|
|
29
|
+
resolveConnectorActionPolicy,
|
|
30
|
+
type Database,
|
|
31
|
+
} from "@opengeni/db";
|
|
32
|
+
import { HTTPException } from "hono/http-exception";
|
|
33
|
+
import {
|
|
34
|
+
GMAIL_REST_MCP_TOOLS,
|
|
35
|
+
gmailToolSupportsScopes,
|
|
36
|
+
isOfficialGmailMcpConfig,
|
|
37
|
+
isOfficialSlackMcpConfig,
|
|
38
|
+
} from "@opengeni/runtime";
|
|
39
|
+
import { hasPermission } from "../access";
|
|
40
|
+
import { buildCapabilityCatalog, settingsWithMcpCapabilityServers } from "./capabilities";
|
|
41
|
+
|
|
42
|
+
type Input = {
|
|
43
|
+
db: Database;
|
|
44
|
+
settings: Settings;
|
|
45
|
+
workspaceId: string;
|
|
46
|
+
grant: AccessGrant;
|
|
47
|
+
capabilityId: string;
|
|
48
|
+
personalOwnerVerified: boolean;
|
|
49
|
+
};
|
|
50
|
+
type ListedTool = {
|
|
51
|
+
name: string;
|
|
52
|
+
title?: string | undefined;
|
|
53
|
+
description?: string | undefined;
|
|
54
|
+
annotations?:
|
|
55
|
+
| {
|
|
56
|
+
readOnlyHint?: boolean | undefined;
|
|
57
|
+
destructiveHint?: boolean | undefined;
|
|
58
|
+
title?: string | undefined;
|
|
59
|
+
}
|
|
60
|
+
| undefined;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export function connectorToolGroup(tool: ListedTool): "read" | "write" | "other" {
|
|
64
|
+
if (tool.annotations?.destructiveHint === true || tool.annotations?.readOnlyHint === false)
|
|
65
|
+
return "write";
|
|
66
|
+
if (tool.annotations?.readOnlyHint === true) return "read";
|
|
67
|
+
return "other";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** A selector can supply discovery credentials only when one visible account
|
|
71
|
+
* matches. Never apply one account's approval choices to an arbitrary sibling. */
|
|
72
|
+
export function unpinnedConnectorToolPermissionAccount<T extends ConnectionMetadata>(
|
|
73
|
+
ref: McpServerConnectionRef,
|
|
74
|
+
visible: T[],
|
|
75
|
+
subjectId: string,
|
|
76
|
+
): T | null {
|
|
77
|
+
const matches = visible.filter(
|
|
78
|
+
(candidate) =>
|
|
79
|
+
candidate.subjectId === (ref.subjectScope === "subject" ? subjectId : null) &&
|
|
80
|
+
candidate.providerDomain === ref.providerDomain &&
|
|
81
|
+
(!ref.kind || candidate.kind === ref.kind) &&
|
|
82
|
+
candidate.status === "active",
|
|
83
|
+
);
|
|
84
|
+
if (matches.length > 1)
|
|
85
|
+
throw new HTTPException(409, {
|
|
86
|
+
message: "Choose one account to manage its tool permissions",
|
|
87
|
+
});
|
|
88
|
+
return matches[0] ?? null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function connectorToolPermissionReference(
|
|
92
|
+
ref: McpServerConnectionRef,
|
|
93
|
+
connectionId: string,
|
|
94
|
+
): McpServerConnectionRef {
|
|
95
|
+
return { ...ref, accountSelection: undefined, connectionId };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function resolveTarget(input: Input) {
|
|
99
|
+
if (input.capabilityId.startsWith("api:")) {
|
|
100
|
+
// API Integrations are not MCP connectors: their per-tool approval is part
|
|
101
|
+
// of the reviewed installation, not this connector policy surface.
|
|
102
|
+
throw new HTTPException(404, {
|
|
103
|
+
message:
|
|
104
|
+
"Connected MCP connector not found; API Integration tool approval is set with installApiIntegration autoApprovedTools",
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
const catalog = await buildCapabilityCatalog(input);
|
|
108
|
+
const item = catalog.items.find((candidate) => candidate.id === input.capabilityId);
|
|
109
|
+
if (
|
|
110
|
+
!item ||
|
|
111
|
+
!item.enabled ||
|
|
112
|
+
item.kind !== "mcp" ||
|
|
113
|
+
!item.runtime.mcpServerId ||
|
|
114
|
+
item.source === "built_in"
|
|
115
|
+
) {
|
|
116
|
+
throw new HTTPException(404, { message: "Connected MCP connector not found" });
|
|
117
|
+
}
|
|
118
|
+
const enabled = await listEnabledMcpCapabilityServers(input.db, input.workspaceId);
|
|
119
|
+
const settings = settingsWithMcpCapabilityServers(input.settings, enabled);
|
|
120
|
+
const server = settings.mcpServers.find((candidate) => candidate.id === item.runtime.mcpServerId);
|
|
121
|
+
if (!server || server.url !== item.endpointUrl)
|
|
122
|
+
throw new HTTPException(409, { message: "Connector configuration is unavailable" });
|
|
123
|
+
const ref = server.connectionRef;
|
|
124
|
+
if (ref?.authoritySource === "host")
|
|
125
|
+
throw new HTTPException(409, {
|
|
126
|
+
message: "Tool permissions for this connection are managed by its host",
|
|
127
|
+
});
|
|
128
|
+
let connection = ref?.connectionId
|
|
129
|
+
? await getConnectionMetadata(
|
|
130
|
+
input.db,
|
|
131
|
+
input.workspaceId,
|
|
132
|
+
ref.connectionId,
|
|
133
|
+
input.grant.subjectId,
|
|
134
|
+
)
|
|
135
|
+
: null;
|
|
136
|
+
if (
|
|
137
|
+
ref &&
|
|
138
|
+
!ref.connectionId &&
|
|
139
|
+
(ref.subjectScope === "subject" || ref.accountSelection === "all_eligible")
|
|
140
|
+
) {
|
|
141
|
+
const visible = await listConnectionsMetadata(
|
|
142
|
+
input.db,
|
|
143
|
+
input.workspaceId,
|
|
144
|
+
input.grant.subjectId,
|
|
145
|
+
);
|
|
146
|
+
connection = unpinnedConnectorToolPermissionAccount(ref, visible, input.grant.subjectId);
|
|
147
|
+
}
|
|
148
|
+
if (
|
|
149
|
+
ref &&
|
|
150
|
+
(!connection ||
|
|
151
|
+
connection.providerDomain !== ref.providerDomain ||
|
|
152
|
+
(ref.kind && connection.kind !== ref.kind) ||
|
|
153
|
+
(ref.subjectScope === "subject"
|
|
154
|
+
? connection.subjectId !== input.grant.subjectId
|
|
155
|
+
: connection.subjectId !== null))
|
|
156
|
+
) {
|
|
157
|
+
throw new HTTPException(409, { message: "Reconnect this connector to manage its tools" });
|
|
158
|
+
}
|
|
159
|
+
if (connection?.subjectId && !input.personalOwnerVerified)
|
|
160
|
+
throw new HTTPException(403, {
|
|
161
|
+
message: "Only the authenticated connection owner may manage personal tool permissions",
|
|
162
|
+
});
|
|
163
|
+
// Same secret-free target identity used by runtime sessionMcpApprovalConnectionId.
|
|
164
|
+
const connectionId =
|
|
165
|
+
connection?.id ??
|
|
166
|
+
`session-mcp:${server.id}:${createHash("sha256").update(server.url, "utf8").digest("hex")}`;
|
|
167
|
+
return { server, connection, connectionId };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Use the same adapter-owned identities as execution for reviewed local catalogs. */
|
|
171
|
+
export function reviewedConnectorToolCatalog(
|
|
172
|
+
server: Pick<Settings["mcpServers"][number], "url" | "connectionRef" | "allowedTools">,
|
|
173
|
+
grantedScopes: readonly string[],
|
|
174
|
+
): ListedTool[] | null {
|
|
175
|
+
const tools = isOfficialGmailMcpConfig(server.url, server.connectionRef)
|
|
176
|
+
? GMAIL_REST_MCP_TOOLS.filter((tool) => gmailToolSupportsScopes(tool.name, grantedScopes))
|
|
177
|
+
: isOfficialSlackMcpConfig(server.url, server.connectionRef)
|
|
178
|
+
? slackRestMcpToolsForScopes(grantedScopes)
|
|
179
|
+
: null;
|
|
180
|
+
return (
|
|
181
|
+
tools?.filter((tool) => !server.allowedTools || server.allowedTools.includes(tool.name)) ?? null
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function listTools(
|
|
186
|
+
input: Input,
|
|
187
|
+
target: Awaited<ReturnType<typeof resolveTarget>>,
|
|
188
|
+
): Promise<ListedTool[]> {
|
|
189
|
+
const reviewed = reviewedConnectorToolCatalog(
|
|
190
|
+
target.server,
|
|
191
|
+
target.connection?.grantedScopes ?? [],
|
|
192
|
+
);
|
|
193
|
+
if (reviewed !== null) return reviewed;
|
|
194
|
+
let headers = { ...target.server.headers };
|
|
195
|
+
if (target.server.connectionRef) {
|
|
196
|
+
const result = await buildConnectionTokenResolver(
|
|
197
|
+
input.db,
|
|
198
|
+
input.settings,
|
|
199
|
+
)({
|
|
200
|
+
workspaceId: input.workspaceId,
|
|
201
|
+
...(target.connection?.subjectId ? { subjectId: input.grant.subjectId } : {}),
|
|
202
|
+
serverId: target.server.id,
|
|
203
|
+
toolName: "tools/list",
|
|
204
|
+
connectionRef: connectorToolPermissionReference(
|
|
205
|
+
target.server.connectionRef,
|
|
206
|
+
target.connectionId,
|
|
207
|
+
),
|
|
208
|
+
destinationUrl: target.server.url,
|
|
209
|
+
});
|
|
210
|
+
if (result.status !== "ok") throw new Error("Reconnect this connector to load its tools.");
|
|
211
|
+
headers = { ...headers, ...result.headers };
|
|
212
|
+
}
|
|
213
|
+
const pinned = createPinnedIntegrationTransport({ network: input.settings });
|
|
214
|
+
const deadline = AbortSignal.timeout(15_000);
|
|
215
|
+
const client = new Client(
|
|
216
|
+
{ name: "opengeni-tool-permissions", version: "0.1.0" },
|
|
217
|
+
{ capabilities: {} },
|
|
218
|
+
);
|
|
219
|
+
try {
|
|
220
|
+
const transport = new StreamableHTTPClientTransport(new URL(target.server.url), {
|
|
221
|
+
requestInit: { headers },
|
|
222
|
+
fetch: async (url, init) =>
|
|
223
|
+
boundMcpResponseBody(
|
|
224
|
+
await pinned.fetch(url.toString(), {
|
|
225
|
+
...init,
|
|
226
|
+
signal: init?.signal ? AbortSignal.any([deadline, init.signal]) : deadline,
|
|
227
|
+
}),
|
|
228
|
+
MCP_MAX_RESPONSE_BYTES,
|
|
229
|
+
),
|
|
230
|
+
});
|
|
231
|
+
await client.connect(transport as unknown as Transport, {
|
|
232
|
+
timeout: 15_000,
|
|
233
|
+
maxTotalTimeout: 15_000,
|
|
234
|
+
});
|
|
235
|
+
const tools = await collectConnectorToolPages(client, deadline);
|
|
236
|
+
return tools.filter(
|
|
237
|
+
(tool) => !target.server.allowedTools || target.server.allowedTools.includes(tool.name),
|
|
238
|
+
);
|
|
239
|
+
} finally {
|
|
240
|
+
await client.close().catch(() => undefined);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** Collect discovery pages without changing the caller's connection or authority. */
|
|
245
|
+
export async function collectConnectorToolPages(
|
|
246
|
+
client: Pick<Client, "listTools">,
|
|
247
|
+
deadline: AbortSignal,
|
|
248
|
+
): Promise<ListedTool[]> {
|
|
249
|
+
const tools: ListedTool[] = [];
|
|
250
|
+
const cursors = new Set<string>();
|
|
251
|
+
let cursor: string | undefined;
|
|
252
|
+
do {
|
|
253
|
+
const page = await client.listTools(cursor ? { cursor } : undefined, {
|
|
254
|
+
timeout: 15_000,
|
|
255
|
+
maxTotalTimeout: 15_000,
|
|
256
|
+
signal: deadline,
|
|
257
|
+
});
|
|
258
|
+
tools.push(...page.tools);
|
|
259
|
+
assertMcpToolListWithinBounds(tools);
|
|
260
|
+
if (new Set(tools.map((tool) => tool.name)).size !== tools.length)
|
|
261
|
+
throw new Error("The connector returned duplicate tool names.");
|
|
262
|
+
cursor = page.nextCursor;
|
|
263
|
+
if (cursor && cursors.has(cursor))
|
|
264
|
+
throw new Error("The connector returned an invalid or oversized tool catalog.");
|
|
265
|
+
if (cursor) cursors.add(cursor);
|
|
266
|
+
} while (cursor);
|
|
267
|
+
return tools;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export async function getConnectorToolPermissions(
|
|
271
|
+
input: Input,
|
|
272
|
+
): Promise<ConnectorToolPermissionsResponse> {
|
|
273
|
+
const target = await resolveTarget(input);
|
|
274
|
+
const policies = await listConnectorToolPermissionPolicies(input.db, {
|
|
275
|
+
accountId: input.grant.accountId,
|
|
276
|
+
workspaceId: input.workspaceId,
|
|
277
|
+
connectionId: target.connectionId,
|
|
278
|
+
});
|
|
279
|
+
const permission = (
|
|
280
|
+
name: string,
|
|
281
|
+
): { permission: ConnectorToolPermission; inherited: boolean } => {
|
|
282
|
+
const resolved = resolveConnectorActionPolicy(policies, {
|
|
283
|
+
connectionId: target.connectionId,
|
|
284
|
+
serverId: target.server.id,
|
|
285
|
+
toolName: name,
|
|
286
|
+
actionName: "*",
|
|
287
|
+
});
|
|
288
|
+
if (!resolved.managed) return { permission: "allow", inherited: true };
|
|
289
|
+
return {
|
|
290
|
+
permission: resolved.entry?.policy ?? "block",
|
|
291
|
+
inherited: resolved.entry?.toolName !== name,
|
|
292
|
+
};
|
|
293
|
+
};
|
|
294
|
+
const defaultPolicy = policies.find(
|
|
295
|
+
(row) => row.serverId === target.server.id && row.toolName === "*" && row.actionName === "*",
|
|
296
|
+
);
|
|
297
|
+
let tools: ListedTool[] = [];
|
|
298
|
+
let discoveryError: string | null = null;
|
|
299
|
+
try {
|
|
300
|
+
tools = await listTools(input, target);
|
|
301
|
+
if (tools.some((tool) => tool.name === "*" || tool.name !== tool.name.trim())) {
|
|
302
|
+
tools = tools.filter((tool) => tool.name !== "*" && tool.name === tool.name.trim());
|
|
303
|
+
discoveryError =
|
|
304
|
+
"The connector exposes a reserved or whitespace-padded tool name. Individual permissions for these names are unsupported, so they are omitted from tool groups. The connector default still applies.";
|
|
305
|
+
}
|
|
306
|
+
} catch {
|
|
307
|
+
discoveryError =
|
|
308
|
+
"Could not load the connector's tools. Try again or reconnect. Your saved permissions still apply.";
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
connectionId: target.connectionId,
|
|
312
|
+
serverId: target.server.id,
|
|
313
|
+
defaultPermission: defaultPolicy?.policy ?? null,
|
|
314
|
+
tools: tools.map(
|
|
315
|
+
(tool): ConnectorToolPermissionEntry => ({
|
|
316
|
+
name: tool.name,
|
|
317
|
+
...((tool.title ?? tool.annotations?.title)
|
|
318
|
+
? { title: tool.title ?? tool.annotations?.title }
|
|
319
|
+
: {}),
|
|
320
|
+
...(tool.description ? { description: tool.description } : {}),
|
|
321
|
+
group: connectorToolGroup(tool),
|
|
322
|
+
...permission(tool.name),
|
|
323
|
+
approvalRequired:
|
|
324
|
+
target.server.requireApproval === true ||
|
|
325
|
+
(Array.isArray(target.server.requireApproval) &&
|
|
326
|
+
target.server.requireApproval.includes(tool.name)),
|
|
327
|
+
}),
|
|
328
|
+
),
|
|
329
|
+
discoveryError,
|
|
330
|
+
canManage: canManageConnectorPermissions(input.grant),
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function canManageConnectorPermissions(grant: AccessGrant): boolean {
|
|
335
|
+
return (
|
|
336
|
+
hasPermission(grant.permissions, "capabilities:manage") &&
|
|
337
|
+
grant.principalKind !== "agent_attempt" &&
|
|
338
|
+
grant.principalKind !== "service" &&
|
|
339
|
+
!grant.serviceInitiator &&
|
|
340
|
+
!grant.serviceInitiatorContext
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export async function updateConnectorToolPermissions(
|
|
345
|
+
input: Input & { payload: UpdateConnectorToolPermissionsRequest },
|
|
346
|
+
): Promise<void> {
|
|
347
|
+
if (!canManageConnectorPermissions(input.grant))
|
|
348
|
+
throw new HTTPException(403, { message: "Connector management permission required" });
|
|
349
|
+
if (
|
|
350
|
+
input.payload.target === "tools" &&
|
|
351
|
+
input.payload.toolNames.some((name) => name === "*" || name !== name.trim())
|
|
352
|
+
)
|
|
353
|
+
throw new HTTPException(400, {
|
|
354
|
+
message: "Tool names must be exact and cannot use the connector-default wildcard.",
|
|
355
|
+
});
|
|
356
|
+
const target = await resolveTarget(input);
|
|
357
|
+
// Prevent a reconnect while the sheet is open from applying old choices to a new account.
|
|
358
|
+
if (target.connectionId !== input.payload.connectionId)
|
|
359
|
+
throw new HTTPException(409, {
|
|
360
|
+
message: "The connector account changed. Reload its permissions.",
|
|
361
|
+
});
|
|
362
|
+
await updateConnectorToolPermissionPolicies(input.db, {
|
|
363
|
+
accountId: input.grant.accountId,
|
|
364
|
+
workspaceId: input.workspaceId,
|
|
365
|
+
subjectId: input.grant.subjectId,
|
|
366
|
+
connectionId: target.connectionId,
|
|
367
|
+
serverId: target.server.id,
|
|
368
|
+
toolNames: input.payload.target === "default" ? ["*"] : input.payload.toolNames,
|
|
369
|
+
policy: input.payload.permission,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { DirectModelProviderMetadata } from "@opengeni/contracts";
|
|
2
|
+
import { HTTPException } from "hono/http-exception";
|
|
3
|
+
|
|
4
|
+
/** Verify the customer's exact route without persisting their key or test response. */
|
|
5
|
+
export async function verifyDirectModelAccess(
|
|
6
|
+
input: DirectModelProviderMetadata,
|
|
7
|
+
apiKey: string,
|
|
8
|
+
fetchImpl: typeof fetch = fetch,
|
|
9
|
+
): Promise<void> {
|
|
10
|
+
const config = DirectModelProviderMetadata.parse(input);
|
|
11
|
+
const baseUrl = config.provider === "openai" ? "https://api.openai.com/v1" : config.endpoint!;
|
|
12
|
+
let response: Response;
|
|
13
|
+
try {
|
|
14
|
+
response = await fetchImpl(`${baseUrl}/responses`, {
|
|
15
|
+
method: "POST",
|
|
16
|
+
redirect: "error",
|
|
17
|
+
signal: AbortSignal.timeout(15_000),
|
|
18
|
+
headers: {
|
|
19
|
+
"content-type": "application/json",
|
|
20
|
+
...(config.provider === "openai"
|
|
21
|
+
? { authorization: `Bearer ${apiKey}` }
|
|
22
|
+
: { "api-key": apiKey }),
|
|
23
|
+
},
|
|
24
|
+
body: JSON.stringify({
|
|
25
|
+
model: config.model,
|
|
26
|
+
input: "Reply OK.",
|
|
27
|
+
max_output_tokens: 32,
|
|
28
|
+
store: false,
|
|
29
|
+
}),
|
|
30
|
+
});
|
|
31
|
+
} catch {
|
|
32
|
+
throw new HTTPException(502, {
|
|
33
|
+
message: "We couldn’t reach your model provider. Check the endpoint and try again.",
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
// Never reflect provider responses: they can include credentials or request details.
|
|
37
|
+
if (response.status === 401 || response.status === 403) {
|
|
38
|
+
throw new HTTPException(422, {
|
|
39
|
+
message: "Your provider didn’t accept this API key. Check the key and its permissions.",
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (response.status === 404 || response.status === 400) {
|
|
43
|
+
throw new HTTPException(422, {
|
|
44
|
+
message:
|
|
45
|
+
config.provider === "azure_openai"
|
|
46
|
+
? "Azure couldn’t use this deployment. Check the endpoint and deployment name."
|
|
47
|
+
: "OpenAI couldn’t use this model. Check the model name and your account’s access.",
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (response.status === 429) {
|
|
51
|
+
throw new HTTPException(422, {
|
|
52
|
+
message:
|
|
53
|
+
"Your provider’s quota or rate limit blocked the check. Check your provider billing or try again shortly.",
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const body = response.ok ? await response.json().catch(() => null) : null;
|
|
57
|
+
if (!body || !["completed", "incomplete"].includes(body.status)) {
|
|
58
|
+
throw new HTTPException(502, {
|
|
59
|
+
message: "Your provider couldn’t complete the connection check. Try again shortly.",
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
COMMITTED_TRANSACTION_PROTOCOL_VERSION,
|
|
5
|
-
EDITABLE_ARTIFACT_MODEL_SCHEMA_VERSION,
|
|
6
|
-
} from "@opengeni/contracts/editable-artifact-versions";
|
|
7
3
|
import {
|
|
8
4
|
DOCUMENT_ARTIFACT_COMMAND_VERSION,
|
|
5
|
+
EDITABLE_ARTIFACT_INTENT_PROTOCOL_VERSION,
|
|
9
6
|
EDITABLE_ARTIFACT_INTENT_VERSION,
|
|
10
7
|
PRESENTATION_ARTIFACT_COMMAND_VERSION,
|
|
11
8
|
SPREADSHEET_ARTIFACT_COMMAND_VERSION,
|
|
9
|
+
currentEditableArtifactCompatibility,
|
|
12
10
|
decodeEditableArtifactMutationIntent,
|
|
13
11
|
decodeDocumentArtifactQueryResponse,
|
|
14
12
|
decodePresentationArtifactQueryResponse,
|
|
@@ -28,10 +26,10 @@ import {
|
|
|
28
26
|
type SpreadsheetArtifactKernelQuery,
|
|
29
27
|
} from "@opengeni/contracts/editable-artifacts";
|
|
30
28
|
|
|
31
|
-
import
|
|
32
|
-
EditableArtifactDurableExportService,
|
|
33
|
-
EditableArtifactMaterializationFormat,
|
|
34
|
-
EditableArtifactMaterializationJob,
|
|
29
|
+
import {
|
|
30
|
+
type EditableArtifactDurableExportService,
|
|
31
|
+
type EditableArtifactMaterializationFormat,
|
|
32
|
+
type EditableArtifactMaterializationJob,
|
|
35
33
|
} from "./durable-export";
|
|
36
34
|
import {
|
|
37
35
|
EditableArtifactDomainError,
|
|
@@ -78,6 +76,7 @@ export type EditableArtifactAgentContext = Readonly<{
|
|
|
78
76
|
|
|
79
77
|
export type EditableArtifactAgentMetadata = Readonly<{
|
|
80
78
|
id: string;
|
|
79
|
+
artifactReference: string;
|
|
81
80
|
modality: EditableArtifactModality;
|
|
82
81
|
title: string;
|
|
83
82
|
lifecycle: EditableArtifact["lifecycle"];
|
|
@@ -176,6 +175,14 @@ export type EditableArtifactAgentApplicationDependencies = Readonly<{
|
|
|
176
175
|
exports: EditableArtifactDurableExportService;
|
|
177
176
|
associations: EditableArtifactAgentAssociationPort;
|
|
178
177
|
inspector: EditableArtifactAgentInspectionKernelPort;
|
|
178
|
+
/** Production persists proof only after a successful native query. */
|
|
179
|
+
recordInspection?: (
|
|
180
|
+
input: EditableArtifactAgentContext & {
|
|
181
|
+
artifact: EditableArtifactAgentMetadata;
|
|
182
|
+
queryHash: string;
|
|
183
|
+
queryKind: string;
|
|
184
|
+
},
|
|
185
|
+
) => Promise<string>;
|
|
179
186
|
officeImports: EditableArtifactOfficeImportPort;
|
|
180
187
|
workspaceFiles: EditableArtifactAgentWorkspaceFilePort;
|
|
181
188
|
}>;
|
|
@@ -185,6 +192,9 @@ export type EditableArtifactAgentApplicationDependencies = Readonly<{
|
|
|
185
192
|
* not another artifact engine: every mutation terminates at EditableArtifactService.
|
|
186
193
|
*/
|
|
187
194
|
export class EditableArtifactAgentApplication {
|
|
195
|
+
describeExportFormats(): string {
|
|
196
|
+
return this.dependencies.exports.describeFormats();
|
|
197
|
+
}
|
|
188
198
|
constructor(private readonly dependencies: EditableArtifactAgentApplicationDependencies) {}
|
|
189
199
|
|
|
190
200
|
async list(
|
|
@@ -293,7 +303,13 @@ export class EditableArtifactAgentApplication {
|
|
|
293
303
|
async inspect(
|
|
294
304
|
input: EditableArtifactAgentContext &
|
|
295
305
|
Readonly<{ artifactId: EditableArtifactId; request: EditableArtifactAgentQuery }>,
|
|
296
|
-
): Promise<
|
|
306
|
+
): Promise<
|
|
307
|
+
Readonly<{
|
|
308
|
+
artifact: EditableArtifactAgentMetadata;
|
|
309
|
+
projection: unknown;
|
|
310
|
+
inspectionReceiptId?: string;
|
|
311
|
+
}>
|
|
312
|
+
> {
|
|
297
313
|
const context = agentContext(input);
|
|
298
314
|
const artifactId = editableArtifactId(input.artifactId);
|
|
299
315
|
const state = await this.dependencies.domain.readCurrentKernelState({
|
|
@@ -306,8 +322,21 @@ export class EditableArtifactAgentApplication {
|
|
|
306
322
|
const queryBytes = encodeQuery(input.request);
|
|
307
323
|
const responseBytes = await this.dependencies.inspector.query({ state, queryBytes });
|
|
308
324
|
const projection = decodeQueryResponse(input.request.modality, responseBytes);
|
|
325
|
+
const inspectionReceiptId =
|
|
326
|
+
state.modality === "document"
|
|
327
|
+
? await this.dependencies.recordInspection?.({
|
|
328
|
+
...context,
|
|
329
|
+
artifact: projectArtifact(state.artifact),
|
|
330
|
+
queryHash: createHash("sha256").update(queryBytes).digest("hex"),
|
|
331
|
+
queryKind: input.request.query.kind,
|
|
332
|
+
})
|
|
333
|
+
: undefined;
|
|
309
334
|
await this.touch(context, artifactId);
|
|
310
|
-
return Object.freeze({
|
|
335
|
+
return Object.freeze({
|
|
336
|
+
artifact: projectArtifact(state.artifact),
|
|
337
|
+
projection,
|
|
338
|
+
...(inspectionReceiptId ? { inspectionReceiptId } : {}),
|
|
339
|
+
});
|
|
311
340
|
}
|
|
312
341
|
|
|
313
342
|
async apply(
|
|
@@ -364,8 +393,9 @@ export class EditableArtifactAgentApplication {
|
|
|
364
393
|
}
|
|
365
394
|
const authored = hashEditableArtifactMutationIntent({
|
|
366
395
|
envelopeVersion: EDITABLE_ARTIFACT_INTENT_VERSION,
|
|
367
|
-
protocolVersion:
|
|
368
|
-
modelSchemaVersion:
|
|
396
|
+
protocolVersion: EDITABLE_ARTIFACT_INTENT_PROTOCOL_VERSION,
|
|
397
|
+
modelSchemaVersion: currentEditableArtifactCompatibility(input.batch.modality)
|
|
398
|
+
.modelSchemaVersion,
|
|
369
399
|
commandProtocolVersion: commandProtocolVersion(input.batch.modality),
|
|
370
400
|
artifactId,
|
|
371
401
|
clientTransactionId,
|
|
@@ -446,6 +476,12 @@ export class EditableArtifactAgentApplication {
|
|
|
446
476
|
actor: context.actor,
|
|
447
477
|
artifactId,
|
|
448
478
|
});
|
|
479
|
+
// Refuse before pinning a version: the agent learns what does exist.
|
|
480
|
+
await this.dependencies.exports.preflight({
|
|
481
|
+
modality: artifact.modality,
|
|
482
|
+
format: input.format,
|
|
483
|
+
...(input.options ? { options: input.options } : {}),
|
|
484
|
+
});
|
|
449
485
|
const version = await this.dependencies.exports.pinVersion({
|
|
450
486
|
scope: context.scope,
|
|
451
487
|
actor: context.actor,
|
|
@@ -564,12 +600,21 @@ function agentContext(input: EditableArtifactAgentContext): EditableArtifactAgen
|
|
|
564
600
|
|
|
565
601
|
function encodeCommands(batch: EditableArtifactAgentCommandBatch): Uint8Array {
|
|
566
602
|
if (batch.modality === "spreadsheet") {
|
|
567
|
-
return encodeSpreadsheetArtifactCommandBatch({
|
|
603
|
+
return encodeSpreadsheetArtifactCommandBatch({
|
|
604
|
+
version: SPREADSHEET_ARTIFACT_COMMAND_VERSION,
|
|
605
|
+
commands: batch.commands,
|
|
606
|
+
});
|
|
568
607
|
}
|
|
569
608
|
if (batch.modality === "document") {
|
|
570
|
-
return encodeDocumentArtifactCommandBatch({
|
|
609
|
+
return encodeDocumentArtifactCommandBatch({
|
|
610
|
+
version: DOCUMENT_ARTIFACT_COMMAND_VERSION,
|
|
611
|
+
commands: batch.commands,
|
|
612
|
+
});
|
|
571
613
|
}
|
|
572
|
-
return encodePresentationArtifactCommandBatch({
|
|
614
|
+
return encodePresentationArtifactCommandBatch({
|
|
615
|
+
version: PRESENTATION_ARTIFACT_COMMAND_VERSION,
|
|
616
|
+
commands: batch.commands,
|
|
617
|
+
});
|
|
573
618
|
}
|
|
574
619
|
|
|
575
620
|
function encodeQuery(request: EditableArtifactAgentQuery): Uint8Array {
|
|
@@ -595,6 +640,7 @@ function commandProtocolVersion(modality: EditableArtifactModality): number {
|
|
|
595
640
|
function projectArtifact(artifact: EditableArtifact): EditableArtifactAgentMetadata {
|
|
596
641
|
return Object.freeze({
|
|
597
642
|
id: artifact.id,
|
|
643
|
+
artifactReference: `[Open report](/workspaces/${artifact.scope.workspaceId}/artifacts/editable/${artifact.id})`,
|
|
598
644
|
modality: artifact.modality,
|
|
599
645
|
title: artifact.title,
|
|
600
646
|
lifecycle: artifact.lifecycle,
|