@opengeni/api-router 0.23.1 → 0.30.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/dist/app.d.ts +3 -2
- package/dist/app.js +5 -3
- package/dist/auth/canonical-human-session-admission.d.ts +27 -0
- package/dist/browser-auth-broker.d.ts +20 -0
- package/dist/browser-controller-authority.d.ts +61 -0
- package/dist/browser-network-route.d.ts +8 -0
- package/dist/browser-state-authority.d.ts +27 -0
- package/dist/{chunk-T4T2PGU4.js → chunk-2PQMSRCB.js} +37891 -17463
- package/dist/chunk-2PQMSRCB.js.map +1 -0
- package/dist/codemode.d.ts +23 -0
- package/dist/controller-data-plane.d.ts +12 -0
- package/dist/editable-artifact-live-hints.d.ts +11 -0
- package/dist/editable-artifact-native-kernel.d.ts +37 -0
- package/dist/editable-artifact-office-import.d.ts +22 -0
- package/dist/editable-artifact-production.d.ts +29 -0
- package/dist/editable-artifact-websocket.d.ts +49 -0
- package/dist/editable-artifact-workspace-files.d.ts +23 -0
- package/dist/github-browser-flow.d.ts +6 -0
- package/dist/http/auth.d.ts +1 -1
- package/dist/http/cors.d.ts +1 -0
- package/dist/http/sse.d.ts +18 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1796 -22
- package/dist/index.js.map +1 -1
- package/dist/integrations/api-integrations.d.ts +24 -0
- package/dist/integrations/atlassian.d.ts +176 -0
- package/dist/integrations/fiken.d.ts +233 -0
- package/dist/integrations/github-skill-source.d.ts +5 -0
- package/dist/integrations/google-drive.d.ts +85 -0
- package/dist/integrations/oauth-client.d.ts +22 -1
- package/dist/integrations/provider-oauth.d.ts +17 -0
- package/dist/integrations/slack-bot.d.ts +175 -1
- package/dist/integrations/slack-interactions.d.ts +24 -6
- package/dist/integrations/social-api.d.ts +2 -1
- package/dist/interaction-holder-heartbeat.d.ts +17 -0
- package/dist/interaction-metrics.d.ts +11 -0
- package/dist/mcp/editable-artifact-query-schema.d.ts +4 -0
- package/dist/mcp/editable-artifacts.d.ts +13 -0
- package/dist/mcp/scheduled-task-view.d.ts +168 -0
- package/dist/mcp/server.d.ts +16 -4
- package/dist/memory-slack-delivery.d.ts +9 -0
- package/dist/model-catalog.d.ts +1 -0
- package/dist/routes/api-integrations.d.ts +8 -0
- package/dist/routes/browser-identities.d.ts +5 -0
- package/dist/routes/browser-sessions.d.ts +6 -0
- package/dist/routes/canonical-human-identities.d.ts +3 -0
- package/dist/routes/channels.d.ts +3 -0
- package/dist/routes/company-profile.d.ts +3 -0
- package/dist/routes/computer-sessions.d.ts +6 -0
- package/dist/routes/editable-artifacts.d.ts +44 -0
- package/dist/routes/integration-facets.d.ts +3 -0
- package/dist/routes/interaction-resources.d.ts +5 -0
- package/dist/routes/memory-slack-publications.d.ts +6 -0
- package/dist/routes/organization-memberships.d.ts +3 -0
- package/dist/routes/plugins.d.ts +8 -0
- package/dist/routes/sessions.d.ts +18 -2
- package/dist/routes/skills.d.ts +6 -0
- package/dist/routes/slack-task-policy.d.ts +3 -0
- package/dist/routes/supergrok.d.ts +3 -0
- package/dist/routes/video-generation.d.ts +3 -0
- package/dist/sandbox/channel-a.d.ts +62 -2
- package/dist/sandbox/machines.d.ts +2 -2
- package/dist/sandbox/metrics-ingestion.d.ts +6 -1
- package/dist/sandbox/viewer.d.ts +7 -3
- package/dist/slack-reaction-files.d.ts +27 -0
- package/dist/temporal-schedule-cleanup.d.ts +26 -0
- package/dist/transcription/providers/xai-subscription.d.ts +8 -0
- package/dist/xai-realtime.d.ts +26 -0
- package/dist/xai-subscription-auth.d.ts +23 -0
- package/package.json +20 -14
- package/src/app.ts +604 -64
- package/src/auth/canonical-human-session-admission.ts +116 -0
- package/src/auth/managed-auth.ts +101 -0
- package/src/browser-auth-broker.ts +155 -0
- package/src/browser-controller-authority.ts +186 -0
- package/src/browser-network-route.ts +34 -0
- package/src/browser-state-authority.ts +236 -0
- package/src/codemode.ts +186 -0
- package/src/controller-data-plane.ts +35 -0
- package/src/editable-artifact-live-hints.ts +64 -0
- package/src/editable-artifact-native-kernel.ts +659 -0
- package/src/editable-artifact-office-import.ts +230 -0
- package/src/editable-artifact-production.ts +419 -0
- package/src/editable-artifact-websocket.ts +311 -0
- package/src/editable-artifact-workspace-files.ts +186 -0
- package/src/github-browser-flow.ts +35 -6
- package/src/http/auth.ts +29 -9
- package/src/http/cors.ts +3 -0
- package/src/http/sse.ts +229 -5
- package/src/index.ts +119 -20
- package/src/integrations/api-integrations.ts +365 -0
- package/src/integrations/atlassian.ts +1621 -0
- package/src/integrations/fiken.ts +1230 -0
- package/src/integrations/github-skill-source.ts +142 -0
- package/src/integrations/google-drive.ts +1005 -64
- package/src/integrations/oauth-client.ts +123 -33
- package/src/integrations/provider-oauth.ts +756 -0
- package/src/integrations/slack-bot.ts +669 -11
- package/src/integrations/slack-interactions.ts +2063 -93
- package/src/integrations/social-api.ts +11 -0
- package/src/interaction-holder-heartbeat.ts +95 -0
- package/src/interaction-metrics.ts +159 -0
- package/src/mcp/documents.ts +146 -6
- package/src/mcp/editable-artifact-query-schema.ts +236 -0
- package/src/mcp/editable-artifacts.ts +448 -0
- package/src/mcp/scheduled-task-view.ts +34 -0
- package/src/mcp/server.ts +1756 -355
- package/src/memory-slack-delivery.ts +209 -0
- package/src/model-catalog.ts +19 -6
- package/src/routes/api-integrations.ts +410 -0
- package/src/routes/browser-identities.ts +160 -0
- package/src/routes/browser-sessions.ts +4482 -0
- package/src/routes/canonical-human-identities.ts +156 -0
- package/src/routes/channels.ts +90 -0
- package/src/routes/company-profile.ts +255 -0
- package/src/routes/computer-sessions.ts +1602 -0
- package/src/routes/connections.ts +421 -99
- package/src/routes/documents.ts +22 -3
- package/src/routes/editable-artifacts.ts +1159 -0
- package/src/routes/files.ts +106 -4
- package/src/routes/github.ts +18 -2
- package/src/routes/install.ts +7 -1
- package/src/routes/integration-facets.ts +258 -0
- package/src/routes/interaction-resources.ts +595 -0
- package/src/routes/memory-slack-publications.ts +216 -0
- package/src/routes/organization-memberships.ts +53 -0
- package/src/routes/packs.ts +437 -7
- package/src/routes/plugins.ts +752 -0
- package/src/routes/rigs.ts +77 -20
- package/src/routes/scheduled-tasks.ts +67 -40
- package/src/routes/sessions.ts +830 -278
- package/src/routes/skills.ts +256 -0
- package/src/routes/slack-task-policy.ts +115 -0
- package/src/routes/supergrok.ts +717 -0
- package/src/routes/transcription-recordings.ts +9 -2
- package/src/routes/transcriptions.ts +2 -1
- package/src/routes/video-generation.ts +162 -0
- package/src/routes/workspaces.ts +68 -28
- package/src/sandbox/channel-a.ts +750 -116
- package/src/sandbox/machines.ts +5 -5
- package/src/sandbox/metrics-ingestion.ts +121 -3
- package/src/sandbox/rematerialize.ts +35 -47
- package/src/sandbox/viewer.ts +39 -12
- package/src/slack-reaction-files.ts +181 -0
- package/src/temporal-schedule-cleanup.ts +135 -0
- package/src/transcription/providers/xai-subscription.ts +110 -0
- package/src/transcription/service.ts +17 -2
- package/src/xai-realtime.ts +216 -0
- package/src/xai-subscription-auth.ts +132 -0
- package/dist/chunk-T4T2PGU4.js.map +0 -1
- package/dist/mcp/toolspace.d.ts +0 -71
- package/src/mcp/toolspace.ts +0 -1190
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import {
|
|
2
|
+
InstallLibrarySkillRequest,
|
|
3
|
+
InstallSkillRequest,
|
|
4
|
+
InstalledSkill,
|
|
5
|
+
ListInstalledSkillsResponse,
|
|
6
|
+
PreviewSkillImportRequest,
|
|
7
|
+
SkillImportPreview,
|
|
8
|
+
SkillUninstallPreview,
|
|
9
|
+
UninstallSkillRequest,
|
|
10
|
+
UninstallSkillResult,
|
|
11
|
+
} from "@opengeni/contracts";
|
|
12
|
+
import {
|
|
13
|
+
portableSkillCapabilityId,
|
|
14
|
+
portableSkillPluginKey,
|
|
15
|
+
requireAccessGrant,
|
|
16
|
+
resolveSkillImport,
|
|
17
|
+
type ApiRouteDeps,
|
|
18
|
+
type GitHubSkillSourceClient,
|
|
19
|
+
} from "@opengeni/core";
|
|
20
|
+
import {
|
|
21
|
+
getPortableSkillUninstallPreview,
|
|
22
|
+
installPortableSkill,
|
|
23
|
+
listInstalledSkills,
|
|
24
|
+
PortableSkillInstallationVersionConflictError,
|
|
25
|
+
PortableSkillInstallationVersionRequiredError,
|
|
26
|
+
uninstallPortableSkill,
|
|
27
|
+
} from "@opengeni/db";
|
|
28
|
+
import { loadSkillLibrarySkill, skillLibraryRepositoryUrl } from "@opengeni/runtime/skill-library";
|
|
29
|
+
import { createHash } from "node:crypto";
|
|
30
|
+
import { HTTPException } from "hono/http-exception";
|
|
31
|
+
import type { Hono } from "hono";
|
|
32
|
+
|
|
33
|
+
import { createGitHubSkillSourceClient } from "../integrations/github-skill-source";
|
|
34
|
+
|
|
35
|
+
export type SkillRouteOverrides = Readonly<{
|
|
36
|
+
github?: GitHubSkillSourceClient;
|
|
37
|
+
}>;
|
|
38
|
+
|
|
39
|
+
export function registerSkillRoutes(
|
|
40
|
+
app: Hono,
|
|
41
|
+
deps: ApiRouteDeps,
|
|
42
|
+
overrides: SkillRouteOverrides = {},
|
|
43
|
+
): void {
|
|
44
|
+
const github = overrides.github ?? createGitHubSkillSourceClient(deps.settings);
|
|
45
|
+
|
|
46
|
+
app.get("/v1/workspaces/:workspaceId/skills", async (c) => {
|
|
47
|
+
const workspaceId = c.req.param("workspaceId");
|
|
48
|
+
await requireAccessGrant(c, deps, workspaceId, "workspace:read");
|
|
49
|
+
return c.json(
|
|
50
|
+
ListInstalledSkillsResponse.parse({
|
|
51
|
+
skills: await listInstalledSkills(deps.db, workspaceId),
|
|
52
|
+
}),
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
app.post("/v1/workspaces/:workspaceId/skills/library/:libraryId/install", async (c) => {
|
|
57
|
+
const workspaceId = c.req.param("workspaceId");
|
|
58
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
|
|
59
|
+
const libraryId = decodeURIComponent(c.req.param("libraryId"));
|
|
60
|
+
const payload = InstallLibrarySkillRequest.parse(await c.req.json());
|
|
61
|
+
let loaded: ReturnType<typeof loadSkillLibrarySkill>;
|
|
62
|
+
try {
|
|
63
|
+
loaded = loadSkillLibrarySkill(libraryId, payload.expectedVersion);
|
|
64
|
+
} catch {
|
|
65
|
+
throw new HTTPException(404, { message: "Skill library entry not found" });
|
|
66
|
+
}
|
|
67
|
+
if (loaded.entry.contentSha256 !== payload.expectedContentSha256) {
|
|
68
|
+
throw new HTTPException(409, {
|
|
69
|
+
message:
|
|
70
|
+
"The curated Skill changed after review. Review the current version before installing.",
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
const files = loaded.skill.files.map((file) => {
|
|
74
|
+
const bytes = new TextEncoder().encode(file.content);
|
|
75
|
+
return {
|
|
76
|
+
path: file.path,
|
|
77
|
+
content: file.content,
|
|
78
|
+
byteSize: bytes.byteLength,
|
|
79
|
+
contentSha256: createHash("sha256").update(bytes).digest("hex"),
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
try {
|
|
83
|
+
const installed = await installPortableSkill(deps.db, {
|
|
84
|
+
accountId: grant.accountId,
|
|
85
|
+
workspaceId,
|
|
86
|
+
subjectId: grant.subjectId,
|
|
87
|
+
capabilityId: `skill:${loaded.entry.id}`,
|
|
88
|
+
pluginKey: `skill/library/${loaded.entry.id}`,
|
|
89
|
+
source: "library",
|
|
90
|
+
version: loaded.entry.version,
|
|
91
|
+
sourceUrl: loaded.entry.sourceUrl,
|
|
92
|
+
repositoryUrl: skillLibraryRepositoryUrl(loaded.entry.sourceUrl),
|
|
93
|
+
sourceCommit: loaded.entry.sourceCommit,
|
|
94
|
+
sourcePath: loaded.entry.relativePath,
|
|
95
|
+
name: loaded.entry.name,
|
|
96
|
+
description: loaded.entry.description,
|
|
97
|
+
category: loaded.entry.category,
|
|
98
|
+
tags: [...loaded.entry.tags],
|
|
99
|
+
provenance: "platform",
|
|
100
|
+
sourceProvenance: loaded.entry.provenance,
|
|
101
|
+
contentSha256: loaded.entry.contentSha256,
|
|
102
|
+
totalBytes: files.reduce((total, file) => total + file.byteSize, 0),
|
|
103
|
+
license: loaded.entry.license,
|
|
104
|
+
files,
|
|
105
|
+
...(payload.expectedInstallationVersion !== undefined
|
|
106
|
+
? { expectedInstallationVersion: payload.expectedInstallationVersion }
|
|
107
|
+
: {}),
|
|
108
|
+
});
|
|
109
|
+
return c.json(
|
|
110
|
+
InstalledSkill.parse({ ...installed, status: "installed" }),
|
|
111
|
+
installed.created ? 201 : 200,
|
|
112
|
+
);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
throw portableSkillMutationHttpError(error);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
app.post("/v1/workspaces/:workspaceId/skills/preview", async (c) => {
|
|
119
|
+
const workspaceId = c.req.param("workspaceId");
|
|
120
|
+
await requireAccessGrant(c, deps, workspaceId, "workspace:read");
|
|
121
|
+
const payload = PreviewSkillImportRequest.parse(await c.req.json());
|
|
122
|
+
const resolved = await resolveForRoute(payload.url, github);
|
|
123
|
+
const installed = await getPortableSkillUninstallPreview(
|
|
124
|
+
deps.db,
|
|
125
|
+
workspaceId,
|
|
126
|
+
portableSkillCapabilityId(resolved.preview),
|
|
127
|
+
);
|
|
128
|
+
return c.json(
|
|
129
|
+
SkillImportPreview.parse({
|
|
130
|
+
...resolved.preview,
|
|
131
|
+
installed: installed.installed,
|
|
132
|
+
installationVersion: installed.installationVersion,
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
app.post("/v1/workspaces/:workspaceId/skills/install", async (c) => {
|
|
138
|
+
const workspaceId = c.req.param("workspaceId");
|
|
139
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
|
|
140
|
+
const payload = InstallSkillRequest.parse(await c.req.json());
|
|
141
|
+
const resolved = await resolveForRoute(payload.url, github);
|
|
142
|
+
if (
|
|
143
|
+
resolved.preview.sourceCommit !== payload.expectedSourceCommit ||
|
|
144
|
+
resolved.preview.contentSha256 !== payload.expectedContentSha256
|
|
145
|
+
) {
|
|
146
|
+
throw new HTTPException(409, {
|
|
147
|
+
message:
|
|
148
|
+
"The Skill source changed after preview. Review the new commit and contents before installing.",
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
const fileSummaryByPath = new Map(
|
|
152
|
+
resolved.preview.files.map((file) => [file.path, file] as const),
|
|
153
|
+
);
|
|
154
|
+
try {
|
|
155
|
+
const installed = await installPortableSkill(deps.db, {
|
|
156
|
+
accountId: grant.accountId,
|
|
157
|
+
workspaceId,
|
|
158
|
+
subjectId: grant.subjectId,
|
|
159
|
+
capabilityId: portableSkillCapabilityId(resolved.preview),
|
|
160
|
+
pluginKey: portableSkillPluginKey(resolved.preview),
|
|
161
|
+
source: resolved.preview.source,
|
|
162
|
+
sourceUrl: resolved.preview.sourceUrl,
|
|
163
|
+
repositoryUrl: resolved.preview.repositoryUrl,
|
|
164
|
+
version: resolved.preview.sourceCommit,
|
|
165
|
+
sourceCommit: resolved.preview.sourceCommit,
|
|
166
|
+
sourcePath: resolved.preview.sourcePath,
|
|
167
|
+
name: resolved.preview.name,
|
|
168
|
+
description: resolved.preview.description,
|
|
169
|
+
contentSha256: resolved.preview.contentSha256,
|
|
170
|
+
totalBytes: resolved.preview.totalBytes,
|
|
171
|
+
...(payload.expectedInstallationVersion !== undefined
|
|
172
|
+
? { expectedInstallationVersion: payload.expectedInstallationVersion }
|
|
173
|
+
: {}),
|
|
174
|
+
files: resolved.files.map((file) => {
|
|
175
|
+
const summary = fileSummaryByPath.get(file.path);
|
|
176
|
+
if (!summary) throw new Error(`Skill preview omitted ${file.path}`);
|
|
177
|
+
return {
|
|
178
|
+
path: file.path,
|
|
179
|
+
content: file.content,
|
|
180
|
+
byteSize: summary.byteSize,
|
|
181
|
+
contentSha256: summary.contentSha256,
|
|
182
|
+
};
|
|
183
|
+
}),
|
|
184
|
+
});
|
|
185
|
+
return c.json(
|
|
186
|
+
InstalledSkill.parse({ ...installed, status: "installed" }),
|
|
187
|
+
installed.created ? 201 : 200,
|
|
188
|
+
);
|
|
189
|
+
} catch (error) {
|
|
190
|
+
throw portableSkillMutationHttpError(error);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
app.get("/v1/workspaces/:workspaceId/skills/:capabilityId/uninstall-preview", async (c) => {
|
|
195
|
+
const workspaceId = c.req.param("workspaceId");
|
|
196
|
+
await requireAccessGrant(c, deps, workspaceId, "workspace:read");
|
|
197
|
+
const capabilityId = decodeURIComponent(c.req.param("capabilityId"));
|
|
198
|
+
return c.json(
|
|
199
|
+
SkillUninstallPreview.parse(
|
|
200
|
+
await getPortableSkillUninstallPreview(deps.db, workspaceId, capabilityId),
|
|
201
|
+
),
|
|
202
|
+
);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
app.delete("/v1/workspaces/:workspaceId/skills/:capabilityId", async (c) => {
|
|
206
|
+
const workspaceId = c.req.param("workspaceId");
|
|
207
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
|
|
208
|
+
const capabilityId = decodeURIComponent(c.req.param("capabilityId"));
|
|
209
|
+
const payload = UninstallSkillRequest.parse(await c.req.json());
|
|
210
|
+
try {
|
|
211
|
+
return c.json(
|
|
212
|
+
UninstallSkillResult.parse(
|
|
213
|
+
await uninstallPortableSkill(deps.db, {
|
|
214
|
+
accountId: grant.accountId,
|
|
215
|
+
workspaceId,
|
|
216
|
+
capabilityId,
|
|
217
|
+
expectedInstallationVersion: payload.expectedInstallationVersion,
|
|
218
|
+
}),
|
|
219
|
+
),
|
|
220
|
+
);
|
|
221
|
+
} catch (error) {
|
|
222
|
+
if (error instanceof PortableSkillInstallationVersionConflictError) {
|
|
223
|
+
throw new HTTPException(409, {
|
|
224
|
+
message: "The Skill changed after preview. Review uninstall impact again.",
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function portableSkillMutationHttpError(error: unknown): Error {
|
|
233
|
+
if (error instanceof PortableSkillInstallationVersionRequiredError) {
|
|
234
|
+
return new HTTPException(400, { message: error.message });
|
|
235
|
+
}
|
|
236
|
+
if (error instanceof PortableSkillInstallationVersionConflictError) {
|
|
237
|
+
return new HTTPException(409, {
|
|
238
|
+
message: "The Skill changed after review. Review the current installation again.",
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function resolveForRoute(
|
|
245
|
+
url: string,
|
|
246
|
+
github: Parameters<typeof resolveSkillImport>[1],
|
|
247
|
+
): ReturnType<typeof resolveSkillImport> {
|
|
248
|
+
try {
|
|
249
|
+
return await resolveSkillImport(url, github);
|
|
250
|
+
} catch (error) {
|
|
251
|
+
if (error instanceof HTTPException) throw error;
|
|
252
|
+
throw new HTTPException(502, {
|
|
253
|
+
message: error instanceof Error ? error.message : "The Skill source could not be read",
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
SlackTaskPolicyListResponse,
|
|
4
|
+
SlackTaskPolicyMutationResponse,
|
|
5
|
+
UpdateSlackTaskPolicyRequest,
|
|
6
|
+
} from "@opengeni/contracts";
|
|
7
|
+
import {
|
|
8
|
+
requireAccessGrant,
|
|
9
|
+
requireAccessGrantAuthorization,
|
|
10
|
+
type AccessGrantAuthorization,
|
|
11
|
+
type ApiRouteDeps,
|
|
12
|
+
} from "@opengeni/core";
|
|
13
|
+
import {
|
|
14
|
+
listSlackTaskPolicy,
|
|
15
|
+
SlackTaskPolicyAuthorityError,
|
|
16
|
+
SlackTaskPolicyConflictError,
|
|
17
|
+
SlackTaskPolicyInvalidOperationError,
|
|
18
|
+
SlackTaskPolicyOperationReuseError,
|
|
19
|
+
updateSlackTaskPolicy,
|
|
20
|
+
} from "@opengeni/db";
|
|
21
|
+
import type { Context, Hono } from "hono";
|
|
22
|
+
import { HTTPException } from "hono/http-exception";
|
|
23
|
+
|
|
24
|
+
function requireDirectWorkspaceAdmin(access: AccessGrantAuthorization): void {
|
|
25
|
+
const { grant } = access;
|
|
26
|
+
if (
|
|
27
|
+
!access.contextIntegrity ||
|
|
28
|
+
access.authenticatedSubjectId !== grant.subjectId ||
|
|
29
|
+
grant.principalKind !== "human_session" ||
|
|
30
|
+
grant.serviceInitiator ||
|
|
31
|
+
grant.serviceInitiatorContext ||
|
|
32
|
+
grant.subjectId.startsWith("api_key:")
|
|
33
|
+
) {
|
|
34
|
+
throw new HTTPException(403, {
|
|
35
|
+
message: "Slack task-policy administration requires a direct human-authorized request",
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function policyError(context: Context, error: unknown): Response {
|
|
41
|
+
if (error instanceof SlackTaskPolicyConflictError) {
|
|
42
|
+
return context.json(
|
|
43
|
+
{ code: error.code, message: error.message, currentHead: error.currentHead },
|
|
44
|
+
409,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
if (error instanceof SlackTaskPolicyOperationReuseError) {
|
|
48
|
+
return context.json({ code: error.code, message: error.message }, 409);
|
|
49
|
+
}
|
|
50
|
+
if (error instanceof SlackTaskPolicyAuthorityError) {
|
|
51
|
+
return context.json(
|
|
52
|
+
{ code: "SLACK_TASK_POLICY_AUTHORITY_REQUIRED", message: error.message },
|
|
53
|
+
403,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
if (error instanceof SlackTaskPolicyInvalidOperationError) {
|
|
57
|
+
return context.json(
|
|
58
|
+
{ code: "INVALID_SLACK_TASK_POLICY_OPERATION", message: error.message },
|
|
59
|
+
422,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function registerSlackTaskPolicyRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
66
|
+
const base = "/v1/workspaces/:workspaceId/slack-task-policy";
|
|
67
|
+
|
|
68
|
+
app.get(base, async (context) => {
|
|
69
|
+
const workspaceId = context.req.param("workspaceId");
|
|
70
|
+
const grant = await requireAccessGrant(context, deps, workspaceId, "workspace:read");
|
|
71
|
+
return context.json(
|
|
72
|
+
SlackTaskPolicyListResponse.parse(
|
|
73
|
+
await listSlackTaskPolicy(deps.db, {
|
|
74
|
+
accountId: grant.accountId,
|
|
75
|
+
workspaceId,
|
|
76
|
+
}),
|
|
77
|
+
),
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
app.put(base, async (context) => {
|
|
82
|
+
const workspaceId = context.req.param("workspaceId");
|
|
83
|
+
const access = await requireAccessGrantAuthorization(
|
|
84
|
+
context,
|
|
85
|
+
deps,
|
|
86
|
+
workspaceId,
|
|
87
|
+
"workspace:admin",
|
|
88
|
+
);
|
|
89
|
+
requireDirectWorkspaceAdmin(access);
|
|
90
|
+
const parsed = UpdateSlackTaskPolicyRequest.safeParse(
|
|
91
|
+
await context.req.json().catch(() => null),
|
|
92
|
+
);
|
|
93
|
+
if (!parsed.success)
|
|
94
|
+
throw new HTTPException(422, { message: "Invalid Slack task-policy request" });
|
|
95
|
+
try {
|
|
96
|
+
return context.json(
|
|
97
|
+
SlackTaskPolicyMutationResponse.parse(
|
|
98
|
+
await updateSlackTaskPolicy(deps.db, {
|
|
99
|
+
operationId: parsed.data.operationId ?? randomUUID(),
|
|
100
|
+
accountId: access.grant.accountId,
|
|
101
|
+
workspaceId,
|
|
102
|
+
policy: parsed.data.policy,
|
|
103
|
+
expectedCurrentRevisionId: parsed.data.expectedCurrentRevisionId,
|
|
104
|
+
expectedActivationVersion: parsed.data.expectedActivationVersion,
|
|
105
|
+
actorSubjectId: access.grant.subjectId,
|
|
106
|
+
principalKind: "human_session",
|
|
107
|
+
reason: parsed.data.reason,
|
|
108
|
+
}),
|
|
109
|
+
),
|
|
110
|
+
);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
return policyError(context, error);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|