@opengeni/api-router 0.22.2 → 0.26.1
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 +2 -2
- package/dist/app.js +1 -1
- package/dist/auth/managed-auth.d.ts +0 -30
- package/dist/browser-controller-authority.d.ts +43 -0
- package/dist/browser-state-authority.d.ts +27 -0
- package/dist/{chunk-HWXJW5C7.js → chunk-JIKNR5YL.js} +27993 -14546
- package/dist/chunk-JIKNR5YL.js.map +1 -0
- package/dist/codemode.d.ts +23 -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/cors.d.ts +1 -0
- package/dist/http/sse.d.ts +2 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1824 -30
- 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/github-skill-source.d.ts +5 -0
- package/dist/integrations/google-drive.d.ts +85 -0
- package/dist/integrations/oauth-client.d.ts +30 -1
- package/dist/integrations/provider-oauth.d.ts +19 -0
- package/dist/integrations/slack-bot.d.ts +4 -0
- package/dist/integrations/slack-interactions.d.ts +14 -2
- package/dist/integrations/social-api.d.ts +2 -1
- package/dist/mcp/editable-artifact-query-schema.d.ts +4 -0
- package/dist/mcp/editable-artifacts.d.ts +13 -0
- package/dist/mcp/receipts.d.ts +28 -0
- package/dist/mcp/scheduled-task-view.d.ts +518 -0
- package/dist/mcp/server.d.ts +14 -3
- package/dist/memory-slack-delivery.d.ts +9 -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/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-features.d.ts +3 -0
- package/dist/routes/memory-slack-publications.d.ts +6 -0
- package/dist/routes/plugins.d.ts +8 -0
- package/dist/routes/sessions.d.ts +17 -2
- package/dist/routes/skills.d.ts +6 -0
- package/dist/routes/video-generation.d.ts +3 -0
- package/dist/sandbox/auth-callout.d.ts +2 -0
- package/dist/sandbox/channel-a.d.ts +59 -2
- package/dist/sandbox/metrics-ingestion.d.ts +6 -1
- package/dist/sandbox/viewer.d.ts +4 -2
- package/dist/temporal-schedule-cleanup.d.ts +26 -0
- package/package.json +19 -14
- package/src/app.ts +277 -41
- package/src/auth/managed-auth.ts +0 -16
- package/src/browser-controller-authority.ts +137 -0
- package/src/browser-state-authority.ts +236 -0
- package/src/codemode.ts +186 -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 +2 -0
- package/src/http/cors.ts +3 -0
- package/src/http/sse.ts +101 -6
- package/src/index.ts +147 -23
- package/src/integrations/api-integrations.ts +350 -0
- package/src/integrations/atlassian.ts +1621 -0
- package/src/integrations/github-skill-source.ts +142 -0
- package/src/integrations/google-drive.ts +1000 -64
- package/src/integrations/oauth-client.ts +159 -89
- package/src/integrations/provider-oauth.ts +777 -0
- package/src/integrations/slack-bot.ts +31 -2
- package/src/integrations/slack-interactions.ts +610 -42
- package/src/integrations/social-api.ts +11 -0
- package/src/mcp/documents.ts +74 -26
- package/src/mcp/editable-artifact-query-schema.ts +236 -0
- package/src/mcp/editable-artifacts.ts +448 -0
- package/src/mcp/receipts.ts +95 -0
- package/src/mcp/scheduled-task-view.ts +642 -0
- package/src/mcp/server.ts +1718 -310
- package/src/memory-slack-delivery.ts +209 -0
- package/src/observability.ts +3 -3
- package/src/routes/api-integrations.ts +407 -0
- package/src/routes/api-keys.ts +7 -1
- package/src/routes/browser-identities.ts +136 -0
- package/src/routes/browser-sessions.ts +2543 -0
- package/src/routes/codex.ts +7 -4
- package/src/routes/company-profile.ts +255 -0
- package/src/routes/computer-sessions.ts +1247 -0
- package/src/routes/connections.ts +358 -102
- package/src/routes/documents.ts +22 -3
- package/src/routes/editable-artifacts.ts +1159 -0
- package/src/routes/enrollments.ts +54 -12
- package/src/routes/environments.ts +60 -11
- package/src/routes/files.ts +277 -65
- package/src/routes/github.ts +18 -2
- package/src/routes/install.ts +38 -2
- package/src/routes/integration-features.ts +258 -0
- package/src/routes/machines.ts +1 -1
- package/src/routes/memory-slack-publications.ts +216 -0
- package/src/routes/packs.ts +437 -7
- package/src/routes/plugins.ts +751 -0
- package/src/routes/rigs.ts +77 -20
- package/src/routes/scheduled-tasks.ts +94 -42
- package/src/routes/sessions.ts +475 -234
- package/src/routes/skills.ts +174 -0
- package/src/routes/transcription-recordings.ts +65 -33
- package/src/routes/video-generation.ts +132 -0
- package/src/routes/workspaces.ts +46 -24
- package/src/sandbox/auth-callout.ts +16 -4
- package/src/sandbox/channel-a.ts +809 -85
- package/src/sandbox/enrollment.ts +13 -3
- package/src/sandbox/machines.ts +1 -1
- package/src/sandbox/metrics-ingestion.ts +121 -3
- package/src/sandbox/rematerialize.ts +35 -47
- package/src/sandbox/viewer.ts +58 -29
- package/src/temporal-schedule-cleanup.ts +135 -0
- package/dist/chunk-HWXJW5C7.js.map +0 -1
- package/dist/mcp/toolspace.d.ts +0 -62
- package/src/mcp/toolspace.ts +0 -1186
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IntegrationFeatureMutationResult,
|
|
3
|
+
IntegrationFeatureRemovalResult,
|
|
4
|
+
IntegrationInstanceFeaturesResponse,
|
|
5
|
+
MutateIntegrationFeatureRequest,
|
|
6
|
+
UpsertIntegrationFeatureRequest,
|
|
7
|
+
} from "@opengeni/contracts";
|
|
8
|
+
import {
|
|
9
|
+
hasPermission,
|
|
10
|
+
requireAccessGrant,
|
|
11
|
+
requireAccessGrantAuthorization,
|
|
12
|
+
type ApiRouteDeps,
|
|
13
|
+
} from "@opengeni/core";
|
|
14
|
+
import {
|
|
15
|
+
configureIntegrationFeature,
|
|
16
|
+
IntegrationFeatureBindingOwnershipConflictError,
|
|
17
|
+
IntegrationFeatureBindingVersionConflictError,
|
|
18
|
+
IntegrationFeatureBindingVersionRequiredError,
|
|
19
|
+
IntegrationFeatureConfigError,
|
|
20
|
+
IntegrationFeatureConnectionError,
|
|
21
|
+
IntegrationFeatureNotFoundError,
|
|
22
|
+
IntegrationFeatureOperationIdempotencyError,
|
|
23
|
+
listIntegrationInstanceFeatures,
|
|
24
|
+
removeIntegrationFeature,
|
|
25
|
+
setIntegrationFeatureLifecycle,
|
|
26
|
+
} from "@opengeni/db";
|
|
27
|
+
import type { Hono } from "hono";
|
|
28
|
+
import { HTTPException } from "hono/http-exception";
|
|
29
|
+
|
|
30
|
+
import {
|
|
31
|
+
browseGoogleDriveIntegrationSource,
|
|
32
|
+
saveGoogleDriveIntegrationSource,
|
|
33
|
+
} from "../integrations/google-drive";
|
|
34
|
+
|
|
35
|
+
export function registerIntegrationFeatureRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
36
|
+
app.get(
|
|
37
|
+
"/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/features/:featureKey/browse",
|
|
38
|
+
async (c) => {
|
|
39
|
+
const workspaceId = c.req.param("workspaceId");
|
|
40
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "connections:read");
|
|
41
|
+
try {
|
|
42
|
+
return c.json(
|
|
43
|
+
await browseGoogleDriveIntegrationSource(deps, {
|
|
44
|
+
workspaceId,
|
|
45
|
+
subjectId: grant.subjectId,
|
|
46
|
+
capabilityId: decoded(c.req.param("capabilityId")),
|
|
47
|
+
instanceKey: decoded(c.req.param("instanceKey")),
|
|
48
|
+
featureKey: decoded(c.req.param("featureKey")),
|
|
49
|
+
parentId: c.req.query("parentId") ?? "root",
|
|
50
|
+
...(c.req.query("pageToken") ? { pageToken: c.req.query("pageToken") } : {}),
|
|
51
|
+
}),
|
|
52
|
+
);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
throw featureHttpError(error);
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
app.put(
|
|
60
|
+
"/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/features/:featureKey/source",
|
|
61
|
+
async (c) => {
|
|
62
|
+
const workspaceId = c.req.param("workspaceId");
|
|
63
|
+
const authorization = await requireAccessGrantAuthorization(
|
|
64
|
+
c,
|
|
65
|
+
deps,
|
|
66
|
+
workspaceId,
|
|
67
|
+
"workspace:admin",
|
|
68
|
+
);
|
|
69
|
+
const { grant } = authorization;
|
|
70
|
+
try {
|
|
71
|
+
return c.json(
|
|
72
|
+
IntegrationFeatureMutationResult.parse(
|
|
73
|
+
await saveGoogleDriveIntegrationSource(deps, {
|
|
74
|
+
accountId: grant.accountId,
|
|
75
|
+
workspaceId,
|
|
76
|
+
subjectId: grant.subjectId,
|
|
77
|
+
capabilityId: decoded(c.req.param("capabilityId")),
|
|
78
|
+
instanceKey: decoded(c.req.param("instanceKey")),
|
|
79
|
+
featureKey: decoded(c.req.param("featureKey")),
|
|
80
|
+
payload: await c.req.json(),
|
|
81
|
+
canManageOrganizationDestination:
|
|
82
|
+
authorization.accountGrant?.permissions.includes("account:admin") === true,
|
|
83
|
+
canManageWorkspaceDestination: hasPermission(grant.permissions, "workspace:admin"),
|
|
84
|
+
canManagePersonalDestination:
|
|
85
|
+
authorization.contextIntegrity &&
|
|
86
|
+
authorization.authenticatedSubjectId === grant.subjectId,
|
|
87
|
+
}),
|
|
88
|
+
),
|
|
89
|
+
);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
throw featureHttpError(error);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
app.get(
|
|
97
|
+
"/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/features",
|
|
98
|
+
async (c) => {
|
|
99
|
+
const workspaceId = c.req.param("workspaceId");
|
|
100
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:read");
|
|
101
|
+
try {
|
|
102
|
+
return c.json(
|
|
103
|
+
IntegrationInstanceFeaturesResponse.parse(
|
|
104
|
+
await listIntegrationInstanceFeatures(
|
|
105
|
+
deps.db,
|
|
106
|
+
workspaceId,
|
|
107
|
+
grant.subjectId,
|
|
108
|
+
decoded(c.req.param("capabilityId")),
|
|
109
|
+
decoded(c.req.param("instanceKey")),
|
|
110
|
+
),
|
|
111
|
+
),
|
|
112
|
+
);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
throw featureHttpError(error);
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
app.put(
|
|
120
|
+
"/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/features/:featureKey",
|
|
121
|
+
async (c) => {
|
|
122
|
+
const workspaceId = c.req.param("workspaceId");
|
|
123
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
|
|
124
|
+
const payload = UpsertIntegrationFeatureRequest.parse(await c.req.json());
|
|
125
|
+
const capabilityId = decoded(c.req.param("capabilityId"));
|
|
126
|
+
const instanceKey = decoded(c.req.param("instanceKey"));
|
|
127
|
+
const featureKey = decoded(c.req.param("featureKey"));
|
|
128
|
+
try {
|
|
129
|
+
const instance = await listIntegrationInstanceFeatures(
|
|
130
|
+
deps.db,
|
|
131
|
+
workspaceId,
|
|
132
|
+
grant.subjectId,
|
|
133
|
+
capabilityId,
|
|
134
|
+
instanceKey,
|
|
135
|
+
);
|
|
136
|
+
const definition = instance.features.find(
|
|
137
|
+
(feature) => feature.definition.featureKey === featureKey,
|
|
138
|
+
)?.definition;
|
|
139
|
+
if (
|
|
140
|
+
definition?.kind === "knowledge_source" &&
|
|
141
|
+
definition.capabilities.provider === "google-drive"
|
|
142
|
+
) {
|
|
143
|
+
throw new HTTPException(422, {
|
|
144
|
+
message:
|
|
145
|
+
"Google Drive source configuration requires the provider-specific source route",
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return c.json(
|
|
149
|
+
IntegrationFeatureMutationResult.parse(
|
|
150
|
+
await configureIntegrationFeature(deps.db, {
|
|
151
|
+
accountId: grant.accountId,
|
|
152
|
+
workspaceId,
|
|
153
|
+
subjectId: grant.subjectId,
|
|
154
|
+
capabilityId,
|
|
155
|
+
instanceKey,
|
|
156
|
+
featureKey,
|
|
157
|
+
displayName: payload.displayName,
|
|
158
|
+
config: payload.config,
|
|
159
|
+
...(payload.expectedVersion !== undefined
|
|
160
|
+
? { expectedVersion: payload.expectedVersion }
|
|
161
|
+
: {}),
|
|
162
|
+
idempotencyKey: payload.idempotencyKey,
|
|
163
|
+
}),
|
|
164
|
+
),
|
|
165
|
+
payload.expectedVersion === undefined ? 201 : 200,
|
|
166
|
+
);
|
|
167
|
+
} catch (error) {
|
|
168
|
+
throw featureHttpError(error);
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
for (const action of ["pause", "resume"] as const) {
|
|
174
|
+
app.post(
|
|
175
|
+
`/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/features/:featureKey/${action}`,
|
|
176
|
+
async (c) => {
|
|
177
|
+
const workspaceId = c.req.param("workspaceId");
|
|
178
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
|
|
179
|
+
const payload = MutateIntegrationFeatureRequest.parse(await c.req.json());
|
|
180
|
+
try {
|
|
181
|
+
return c.json(
|
|
182
|
+
IntegrationFeatureMutationResult.parse(
|
|
183
|
+
await setIntegrationFeatureLifecycle(deps.db, {
|
|
184
|
+
accountId: grant.accountId,
|
|
185
|
+
workspaceId,
|
|
186
|
+
subjectId: grant.subjectId,
|
|
187
|
+
capabilityId: decoded(c.req.param("capabilityId")),
|
|
188
|
+
instanceKey: decoded(c.req.param("instanceKey")),
|
|
189
|
+
featureKey: decoded(c.req.param("featureKey")),
|
|
190
|
+
action,
|
|
191
|
+
expectedVersion: payload.expectedVersion,
|
|
192
|
+
idempotencyKey: payload.idempotencyKey,
|
|
193
|
+
}),
|
|
194
|
+
),
|
|
195
|
+
);
|
|
196
|
+
} catch (error) {
|
|
197
|
+
throw featureHttpError(error);
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
app.delete(
|
|
204
|
+
"/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/features/:featureKey",
|
|
205
|
+
async (c) => {
|
|
206
|
+
const workspaceId = c.req.param("workspaceId");
|
|
207
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
|
|
208
|
+
const payload = MutateIntegrationFeatureRequest.parse(await c.req.json());
|
|
209
|
+
try {
|
|
210
|
+
return c.json(
|
|
211
|
+
IntegrationFeatureRemovalResult.parse(
|
|
212
|
+
await removeIntegrationFeature(deps.db, {
|
|
213
|
+
accountId: grant.accountId,
|
|
214
|
+
workspaceId,
|
|
215
|
+
subjectId: grant.subjectId,
|
|
216
|
+
capabilityId: decoded(c.req.param("capabilityId")),
|
|
217
|
+
instanceKey: decoded(c.req.param("instanceKey")),
|
|
218
|
+
featureKey: decoded(c.req.param("featureKey")),
|
|
219
|
+
expectedVersion: payload.expectedVersion,
|
|
220
|
+
idempotencyKey: payload.idempotencyKey,
|
|
221
|
+
}),
|
|
222
|
+
),
|
|
223
|
+
);
|
|
224
|
+
} catch (error) {
|
|
225
|
+
throw featureHttpError(error);
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function decoded(value: string): string {
|
|
232
|
+
return decodeURIComponent(value);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function featureHttpError(error: unknown): HTTPException {
|
|
236
|
+
if (error instanceof HTTPException) return error;
|
|
237
|
+
if (error instanceof IntegrationFeatureNotFoundError) {
|
|
238
|
+
return new HTTPException(404, { message: error.message });
|
|
239
|
+
}
|
|
240
|
+
if (
|
|
241
|
+
error instanceof IntegrationFeatureBindingVersionConflictError ||
|
|
242
|
+
error instanceof IntegrationFeatureBindingVersionRequiredError ||
|
|
243
|
+
error instanceof IntegrationFeatureBindingOwnershipConflictError ||
|
|
244
|
+
error instanceof IntegrationFeatureOperationIdempotencyError
|
|
245
|
+
) {
|
|
246
|
+
return new HTTPException(409, {
|
|
247
|
+
message:
|
|
248
|
+
"The Integration feature changed, is shared, or reused an idempotency key. Refresh before retrying.",
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
if (
|
|
252
|
+
error instanceof IntegrationFeatureConfigError ||
|
|
253
|
+
error instanceof IntegrationFeatureConnectionError
|
|
254
|
+
) {
|
|
255
|
+
return new HTTPException(422, { message: error.message });
|
|
256
|
+
}
|
|
257
|
+
throw error;
|
|
258
|
+
}
|
package/src/routes/machines.ts
CHANGED
|
@@ -73,7 +73,7 @@ export function registerMachineRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
73
73
|
// Validate the machine belongs to this workspace (RLS already scopes the read,
|
|
74
74
|
// but a clear 404 beats an empty series for an unknown/cross-workspace id).
|
|
75
75
|
const enrollment = await getEnrollment(db, workspaceId, enrollmentId);
|
|
76
|
-
if (!enrollment) {
|
|
76
|
+
if (!enrollment || enrollment.status !== "active") {
|
|
77
77
|
throw new HTTPException(404, { message: "machine not found in this workspace" });
|
|
78
78
|
}
|
|
79
79
|
const windowMs = SERIES_WINDOWS_MS[c.req.query("window") ?? ""] ?? DEFAULT_SERIES_WINDOW_MS;
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MemorySlackPublication,
|
|
3
|
+
MemorySlackPublicationActionRequest,
|
|
4
|
+
MemorySlackPublicationConfigurationResponse,
|
|
5
|
+
MemorySlackPublicationHistoryResponse,
|
|
6
|
+
SlackPublicationChannelListResponse,
|
|
7
|
+
UpdateMemorySlackPublicationConfigurationRequest,
|
|
8
|
+
type MemorySlackPublication as MemorySlackPublicationRecord,
|
|
9
|
+
} from "@opengeni/contracts";
|
|
10
|
+
import {
|
|
11
|
+
actOnMemorySlackPublication,
|
|
12
|
+
createMemorySlackPublicationConfiguration,
|
|
13
|
+
listMemorySlackPublicationConfigurations,
|
|
14
|
+
listMemorySlackPublications,
|
|
15
|
+
MemorySlackPublicationRevisionConflictError,
|
|
16
|
+
recordAuditEvent,
|
|
17
|
+
} from "@opengeni/db";
|
|
18
|
+
import {
|
|
19
|
+
openGeniSlackBotMetadata,
|
|
20
|
+
requireAccessGrant,
|
|
21
|
+
sanitizeSlackPublicationText,
|
|
22
|
+
validateOpenGeniSlackBotConnectionSelection,
|
|
23
|
+
type ApiRouteDeps,
|
|
24
|
+
} from "@opengeni/core";
|
|
25
|
+
import type { Hono } from "hono";
|
|
26
|
+
import { HTTPException } from "hono/http-exception";
|
|
27
|
+
import { createOpenGeniSlackBotInteractionClient } from "../integrations/slack-bot";
|
|
28
|
+
|
|
29
|
+
export function registerMemorySlackPublicationRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
30
|
+
const base = "/v1/workspaces/:workspaceId/memory-slack-publications";
|
|
31
|
+
|
|
32
|
+
app.get(`${base}/configuration`, async (c) => {
|
|
33
|
+
const workspaceId = c.req.param("workspaceId");
|
|
34
|
+
await requireAccessGrant(c, deps, workspaceId, "workspace:read");
|
|
35
|
+
const history = await listMemorySlackPublicationConfigurations(deps.db, workspaceId, 25);
|
|
36
|
+
c.header("cache-control", "private, no-store");
|
|
37
|
+
return c.json(
|
|
38
|
+
MemorySlackPublicationConfigurationResponse.parse({
|
|
39
|
+
current: history[0] ?? null,
|
|
40
|
+
history,
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
app.put(`${base}/configuration`, async (c) => {
|
|
46
|
+
const workspaceId = c.req.param("workspaceId");
|
|
47
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
|
|
48
|
+
const payload = UpdateMemorySlackPublicationConfigurationRequest.parse(await c.req.json());
|
|
49
|
+
let slackTeamId: string | null = null;
|
|
50
|
+
let channelName = payload.slackChannelName;
|
|
51
|
+
if (payload.connectionId || payload.slackChannelId) {
|
|
52
|
+
if (!payload.connectionId || !payload.slackChannelId) {
|
|
53
|
+
throw new HTTPException(400, {
|
|
54
|
+
message: "connectionId and slackChannelId must be configured together",
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
const connection = await validateOpenGeniSlackBotConnectionSelection(
|
|
58
|
+
deps.db,
|
|
59
|
+
grant,
|
|
60
|
+
workspaceId,
|
|
61
|
+
payload.connectionId,
|
|
62
|
+
);
|
|
63
|
+
const metadata = openGeniSlackBotMetadata(connection.metadata);
|
|
64
|
+
if (!metadata) {
|
|
65
|
+
throw new HTTPException(422, { message: "Slack bot installation metadata is invalid" });
|
|
66
|
+
}
|
|
67
|
+
const client = await createOpenGeniSlackBotInteractionClient(deps, {
|
|
68
|
+
accountId: grant.accountId,
|
|
69
|
+
workspaceId,
|
|
70
|
+
connectionId: connection.id,
|
|
71
|
+
subjectId: grant.subjectId,
|
|
72
|
+
});
|
|
73
|
+
const channel = await client.verifyChannelAccess(payload.slackChannelId);
|
|
74
|
+
if (
|
|
75
|
+
channel.isShared ||
|
|
76
|
+
channel.isExternallyShared ||
|
|
77
|
+
channel.isOrgShared ||
|
|
78
|
+
channel.isArchived
|
|
79
|
+
) {
|
|
80
|
+
throw new HTTPException(422, {
|
|
81
|
+
message: "Slack publication requires an active, non-shared bot-member channel",
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
slackTeamId = metadata.slackTeamId;
|
|
85
|
+
channelName = channel.name ?? payload.slackChannelName;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const configuration = await createMemorySlackPublicationConfiguration(deps.db, {
|
|
89
|
+
accountId: grant.accountId,
|
|
90
|
+
workspaceId,
|
|
91
|
+
expectedRevision: payload.expectedRevision,
|
|
92
|
+
enabled: payload.enabled,
|
|
93
|
+
connectionId: payload.connectionId,
|
|
94
|
+
slackTeamId,
|
|
95
|
+
slackChannelId: payload.slackChannelId,
|
|
96
|
+
slackChannelName: channelName,
|
|
97
|
+
autoImportances: payload.autoImportances,
|
|
98
|
+
reviewImportances: payload.reviewImportances,
|
|
99
|
+
subjectId: grant.subjectId,
|
|
100
|
+
});
|
|
101
|
+
await recordAuditEvent(deps.db, {
|
|
102
|
+
accountId: grant.accountId,
|
|
103
|
+
workspaceId,
|
|
104
|
+
subjectId: grant.subjectId,
|
|
105
|
+
action: "memory_slack_publication.configuration.created",
|
|
106
|
+
targetType: "memory_slack_publication_configuration",
|
|
107
|
+
targetId: configuration.id,
|
|
108
|
+
metadata: {
|
|
109
|
+
revision: configuration.revision,
|
|
110
|
+
enabled: configuration.enabled,
|
|
111
|
+
connectionId: configuration.connectionId,
|
|
112
|
+
slackTeamId: configuration.slackTeamId,
|
|
113
|
+
slackChannelId: configuration.slackChannelId,
|
|
114
|
+
autoImportances: configuration.autoImportances,
|
|
115
|
+
reviewImportances: configuration.reviewImportances,
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
return c.json(configuration);
|
|
119
|
+
} catch (error) {
|
|
120
|
+
if (error instanceof MemorySlackPublicationRevisionConflictError) {
|
|
121
|
+
throw new HTTPException(409, {
|
|
122
|
+
message: `configuration revision conflict; current revision is ${error.currentRevision}`,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
app.get(`${base}/channels`, async (c) => {
|
|
130
|
+
const workspaceId = c.req.param("workspaceId");
|
|
131
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
|
|
132
|
+
const connectionId = c.req.query("connectionId");
|
|
133
|
+
if (!connectionId) throw new HTTPException(400, { message: "connectionId is required" });
|
|
134
|
+
await validateOpenGeniSlackBotConnectionSelection(deps.db, grant, workspaceId, connectionId);
|
|
135
|
+
const client = await createOpenGeniSlackBotInteractionClient(deps, {
|
|
136
|
+
accountId: grant.accountId,
|
|
137
|
+
workspaceId,
|
|
138
|
+
connectionId,
|
|
139
|
+
subjectId: grant.subjectId,
|
|
140
|
+
});
|
|
141
|
+
const result = await client.listChannels({
|
|
142
|
+
limit: 200,
|
|
143
|
+
...(c.req.query("cursor") ? { cursor: c.req.query("cursor")! } : {}),
|
|
144
|
+
});
|
|
145
|
+
return c.json(
|
|
146
|
+
SlackPublicationChannelListResponse.parse({
|
|
147
|
+
channels: result.channels
|
|
148
|
+
.filter(
|
|
149
|
+
(channel) =>
|
|
150
|
+
channel.isMember &&
|
|
151
|
+
!channel.isArchived &&
|
|
152
|
+
!channel.isShared &&
|
|
153
|
+
!channel.isExternallyShared &&
|
|
154
|
+
!channel.isOrgShared,
|
|
155
|
+
)
|
|
156
|
+
.map((channel) => ({
|
|
157
|
+
id: channel.id,
|
|
158
|
+
name: channel.name,
|
|
159
|
+
isPrivate: channel.isPrivate,
|
|
160
|
+
})),
|
|
161
|
+
nextCursor: result.nextCursor || null,
|
|
162
|
+
}),
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
app.get(base, async (c) => {
|
|
167
|
+
const workspaceId = c.req.param("workspaceId");
|
|
168
|
+
await requireAccessGrant(c, deps, workspaceId, "workspace:read");
|
|
169
|
+
const publications = await listMemorySlackPublications(deps.db, workspaceId, { limit: 50 });
|
|
170
|
+
c.header("cache-control", "private, no-store");
|
|
171
|
+
return c.json(
|
|
172
|
+
MemorySlackPublicationHistoryResponse.parse({
|
|
173
|
+
publications: publications.map(sanitizeMemorySlackPublicationHistory),
|
|
174
|
+
nextCursor: null,
|
|
175
|
+
}),
|
|
176
|
+
);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
app.post(`${base}/:publicationId/action`, async (c) => {
|
|
180
|
+
const workspaceId = c.req.param("workspaceId");
|
|
181
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
|
|
182
|
+
const payload = MemorySlackPublicationActionRequest.parse(await c.req.json());
|
|
183
|
+
const publication = await actOnMemorySlackPublication(deps.db, {
|
|
184
|
+
workspaceId,
|
|
185
|
+
publicationId: c.req.param("publicationId"),
|
|
186
|
+
expectedState: payload.expectedState,
|
|
187
|
+
action: payload.action,
|
|
188
|
+
subjectId: grant.subjectId,
|
|
189
|
+
});
|
|
190
|
+
if (!publication) {
|
|
191
|
+
throw new HTTPException(409, { message: "publication state changed or action is invalid" });
|
|
192
|
+
}
|
|
193
|
+
await recordAuditEvent(deps.db, {
|
|
194
|
+
accountId: grant.accountId,
|
|
195
|
+
workspaceId,
|
|
196
|
+
subjectId: grant.subjectId,
|
|
197
|
+
action: `memory_slack_publication.${payload.action}`,
|
|
198
|
+
targetType: "memory_slack_publication",
|
|
199
|
+
targetId: publication.id,
|
|
200
|
+
metadata: {
|
|
201
|
+
expectedState: payload.expectedState,
|
|
202
|
+
resultState: publication.state,
|
|
203
|
+
operationId: publication.receipts.at(-1)?.operationId ?? null,
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
return c.json(MemorySlackPublication.parse(sanitizeMemorySlackPublicationHistory(publication)));
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Defensively protects legacy rows without rewriting their durable projection. */
|
|
211
|
+
export function sanitizeMemorySlackPublicationHistory(
|
|
212
|
+
publication: MemorySlackPublicationRecord,
|
|
213
|
+
): MemorySlackPublicationRecord {
|
|
214
|
+
const summary = sanitizeSlackPublicationText(publication.summary);
|
|
215
|
+
return summary === publication.summary ? publication : { ...publication, summary };
|
|
216
|
+
}
|