@opengeni/api-router 2.1.0-canary.0 → 2.2.0-canary.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.js +1 -1
- package/dist/{chunk-QKDFBBUE.js → chunk-3TP54PPX.js} +564 -73
- package/dist/chunk-3TP54PPX.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/integrations/slack-bot.d.ts +8 -0
- package/dist/integrations/slack-interactions.d.ts +35 -2
- package/package.json +18 -18
- package/src/app.ts +8 -0
- package/src/integrations/slack-bot.ts +5 -0
- package/src/integrations/slack-interactions.ts +653 -71
- package/src/integrations/slack-routing.ts +25 -12
- package/src/routes/documents.ts +134 -3
- package/dist/chunk-QKDFBBUE.js.map +0 -1
|
@@ -26394,6 +26394,8 @@ import {
|
|
|
26394
26394
|
DocumentAuthorityReclassification,
|
|
26395
26395
|
DocumentBase,
|
|
26396
26396
|
DocumentDefaultCollectionBackfill,
|
|
26397
|
+
DocumentDefaultCollectionBackfillAudit,
|
|
26398
|
+
GetDocumentDefaultCollectionBackfillAuditQuery,
|
|
26397
26399
|
DocumentSearchRequest,
|
|
26398
26400
|
DocumentSearchResponse,
|
|
26399
26401
|
FileAsset as FileAsset2,
|
|
@@ -26402,6 +26404,9 @@ import {
|
|
|
26402
26404
|
KnowledgeMemorySearchRequest,
|
|
26403
26405
|
ListDocumentAuthorityReclassificationsQuery,
|
|
26404
26406
|
ListDocumentAuthorityReclassificationsResponse,
|
|
26407
|
+
ListDocumentDefaultCollectionBackfillRunsResponse,
|
|
26408
|
+
ListDocumentMigrationAuditQuery,
|
|
26409
|
+
ListOrganizationDocumentAuthorityReclassificationsResponse,
|
|
26405
26410
|
MoveDocumentRequest,
|
|
26406
26411
|
ReclassifyDocumentAuthorityRequest,
|
|
26407
26412
|
RunDocumentDefaultCollectionBackfillRequest,
|
|
@@ -26427,10 +26432,13 @@ import {
|
|
|
26427
26432
|
getDocument,
|
|
26428
26433
|
getDocumentOriginalFile,
|
|
26429
26434
|
getDocumentBase,
|
|
26435
|
+
getDocumentDefaultCollectionBackfillAudit,
|
|
26430
26436
|
listAccessibleDocuments,
|
|
26431
26437
|
listDocumentAuthorityReclassifications,
|
|
26438
|
+
listDocumentDefaultCollectionBackfillRuns,
|
|
26432
26439
|
listDocumentBasesEnsuringDefault,
|
|
26433
26440
|
listDocuments,
|
|
26441
|
+
listOrganizationDocumentAuthorityReclassifications,
|
|
26434
26442
|
moveDocumentToBase,
|
|
26435
26443
|
queueDocumentForReindex,
|
|
26436
26444
|
reclassifyDocumentAuthority,
|
|
@@ -26886,6 +26894,103 @@ function registerDocumentRoutes(app, deps) {
|
|
|
26886
26894
|
throw documentHttpException(error);
|
|
26887
26895
|
}
|
|
26888
26896
|
});
|
|
26897
|
+
app.get("/v1/workspaces/:workspaceId/document-default-collection-backfills", async (c) => {
|
|
26898
|
+
const workspaceId = c.req.param("workspaceId");
|
|
26899
|
+
const authorization = await requireAccessGrantAuthorization2(
|
|
26900
|
+
c,
|
|
26901
|
+
deps,
|
|
26902
|
+
workspaceId,
|
|
26903
|
+
"documents:manage"
|
|
26904
|
+
);
|
|
26905
|
+
if (!hasAccountAdminAuthority(authorization)) {
|
|
26906
|
+
throw new HTTPException26(403, { message: "missing permission: account:admin" });
|
|
26907
|
+
}
|
|
26908
|
+
const query = ListDocumentMigrationAuditQuery.safeParse(c.req.query());
|
|
26909
|
+
if (!query.success) {
|
|
26910
|
+
throw new HTTPException26(400, { message: "invalid document migration audit query" });
|
|
26911
|
+
}
|
|
26912
|
+
try {
|
|
26913
|
+
return c.json(
|
|
26914
|
+
ListDocumentDefaultCollectionBackfillRunsResponse.parse(
|
|
26915
|
+
await listDocumentDefaultCollectionBackfillRuns(db, {
|
|
26916
|
+
accountId: authorization.grant.accountId,
|
|
26917
|
+
workspaceId,
|
|
26918
|
+
actorSubjectId: authorization.grant.subjectId,
|
|
26919
|
+
accountAdminAuthorization: requireAccountAdminAuthorizationStamp(authorization),
|
|
26920
|
+
...query.data
|
|
26921
|
+
})
|
|
26922
|
+
)
|
|
26923
|
+
);
|
|
26924
|
+
} catch (error) {
|
|
26925
|
+
throw documentHttpException(error);
|
|
26926
|
+
}
|
|
26927
|
+
});
|
|
26928
|
+
app.get("/v1/workspaces/:workspaceId/document-default-collection-backfills/:runId", async (c) => {
|
|
26929
|
+
const workspaceId = c.req.param("workspaceId");
|
|
26930
|
+
const authorization = await requireAccessGrantAuthorization2(
|
|
26931
|
+
c,
|
|
26932
|
+
deps,
|
|
26933
|
+
workspaceId,
|
|
26934
|
+
"documents:manage"
|
|
26935
|
+
);
|
|
26936
|
+
if (!hasAccountAdminAuthority(authorization)) {
|
|
26937
|
+
throw new HTTPException26(403, { message: "missing permission: account:admin" });
|
|
26938
|
+
}
|
|
26939
|
+
const runId = RunDocumentDefaultCollectionBackfillRequest.shape.runId.safeParse(
|
|
26940
|
+
c.req.param("runId")
|
|
26941
|
+
);
|
|
26942
|
+
const query = GetDocumentDefaultCollectionBackfillAuditQuery.safeParse(c.req.query());
|
|
26943
|
+
if (!runId.success || !query.success) {
|
|
26944
|
+
throw new HTTPException26(400, { message: "invalid document migration audit query" });
|
|
26945
|
+
}
|
|
26946
|
+
try {
|
|
26947
|
+
return c.json(
|
|
26948
|
+
DocumentDefaultCollectionBackfillAudit.parse(
|
|
26949
|
+
await getDocumentDefaultCollectionBackfillAudit(db, {
|
|
26950
|
+
accountId: authorization.grant.accountId,
|
|
26951
|
+
workspaceId,
|
|
26952
|
+
actorSubjectId: authorization.grant.subjectId,
|
|
26953
|
+
accountAdminAuthorization: requireAccountAdminAuthorizationStamp(authorization),
|
|
26954
|
+
runId: runId.data,
|
|
26955
|
+
...query.data
|
|
26956
|
+
})
|
|
26957
|
+
)
|
|
26958
|
+
);
|
|
26959
|
+
} catch (error) {
|
|
26960
|
+
throw documentHttpException(error);
|
|
26961
|
+
}
|
|
26962
|
+
});
|
|
26963
|
+
app.get("/v1/workspaces/:workspaceId/document-authority-reclassifications", async (c) => {
|
|
26964
|
+
const workspaceId = c.req.param("workspaceId");
|
|
26965
|
+
const authorization = await requireAccessGrantAuthorization2(
|
|
26966
|
+
c,
|
|
26967
|
+
deps,
|
|
26968
|
+
workspaceId,
|
|
26969
|
+
"documents:manage"
|
|
26970
|
+
);
|
|
26971
|
+
if (!hasAccountAdminAuthority(authorization)) {
|
|
26972
|
+
throw new HTTPException26(403, { message: "missing permission: account:admin" });
|
|
26973
|
+
}
|
|
26974
|
+
const query = ListDocumentMigrationAuditQuery.safeParse(c.req.query());
|
|
26975
|
+
if (!query.success) {
|
|
26976
|
+
throw new HTTPException26(400, { message: "invalid document migration audit query" });
|
|
26977
|
+
}
|
|
26978
|
+
try {
|
|
26979
|
+
return c.json(
|
|
26980
|
+
ListOrganizationDocumentAuthorityReclassificationsResponse.parse(
|
|
26981
|
+
await listOrganizationDocumentAuthorityReclassifications(db, {
|
|
26982
|
+
accountId: authorization.grant.accountId,
|
|
26983
|
+
workspaceId,
|
|
26984
|
+
actorSubjectId: authorization.grant.subjectId,
|
|
26985
|
+
accountAdminAuthorization: requireAccountAdminAuthorizationStamp(authorization),
|
|
26986
|
+
...query.data
|
|
26987
|
+
})
|
|
26988
|
+
)
|
|
26989
|
+
);
|
|
26990
|
+
} catch (error) {
|
|
26991
|
+
throw documentHttpException(error);
|
|
26992
|
+
}
|
|
26993
|
+
});
|
|
26889
26994
|
app.post("/v1/workspaces/:workspaceId/document-bases/:baseId/documents", async (c) => {
|
|
26890
26995
|
const workspaceId = c.req.param("workspaceId");
|
|
26891
26996
|
const access = await requireAccessGrantAuthorization2(c, deps, workspaceId, "documents:manage");
|
|
@@ -27569,23 +27674,26 @@ function dropFilename(preferred) {
|
|
|
27569
27674
|
return /\.[A-Za-z0-9]{1,8}$/.test(stem) ? stem : `${stem}.txt`;
|
|
27570
27675
|
}
|
|
27571
27676
|
function documentHttpException(error) {
|
|
27572
|
-
const message =
|
|
27677
|
+
const message = documentDomainErrorMessage(error);
|
|
27573
27678
|
if (message.includes("organization document") && message.includes("exact account authority")) {
|
|
27574
27679
|
return new HTTPException26(403, { message: "missing permission: account:admin" });
|
|
27575
27680
|
}
|
|
27576
27681
|
if (message.includes("not found")) {
|
|
27577
27682
|
return new HTTPException26(404, { message });
|
|
27578
27683
|
}
|
|
27684
|
+
if (message.includes("document Default collection backfill audit run is unavailable")) {
|
|
27685
|
+
return new HTTPException26(404, { message });
|
|
27686
|
+
}
|
|
27579
27687
|
if (message.includes("already exists")) {
|
|
27580
27688
|
return new HTTPException26(409, { message });
|
|
27581
27689
|
}
|
|
27582
27690
|
if (message.includes("operation id was reused") || message.includes("authority changed before reclassification") || message.includes("run identity mismatch")) {
|
|
27583
27691
|
return new HTTPException26(409, { message });
|
|
27584
27692
|
}
|
|
27585
|
-
if (message.includes("reclassification requires") || message.includes("backfill requires organization administration")) {
|
|
27693
|
+
if (message.includes("reclassification requires") || message.includes("backfill requires organization administration") || message.includes("document migration audit requires organization administration") || message.includes("document migration audit scope is invalid")) {
|
|
27586
27694
|
return new HTTPException26(403, { message });
|
|
27587
27695
|
}
|
|
27588
|
-
if (message.includes("reclassification input is invalid") || message.includes("reclassification authority is invalid") || message.includes("invalid document authority receipt cursor") || message.includes("document authority receipt limit")) {
|
|
27696
|
+
if (message.includes("reclassification input is invalid") || message.includes("reclassification authority is invalid") || message.includes("invalid document authority receipt cursor") || message.includes("document authority receipt limit") || message.includes("invalid document migration audit cursor") || message.includes("document migration audit cursor is invalid") || message.includes("document migration audit limit") || message.includes("document migration audit authority is incomplete") || message.includes("document Default collection backfill audit run id is required")) {
|
|
27589
27697
|
return new HTTPException26(400, { message });
|
|
27590
27698
|
}
|
|
27591
27699
|
if (message.includes("no suggested base")) {
|
|
@@ -27599,6 +27707,18 @@ function documentHttpException(error) {
|
|
|
27599
27707
|
}
|
|
27600
27708
|
return new HTTPException26(500, { message });
|
|
27601
27709
|
}
|
|
27710
|
+
function documentDomainErrorMessage(error) {
|
|
27711
|
+
let current = error;
|
|
27712
|
+
let message = error instanceof Error ? error.message : String(error);
|
|
27713
|
+
const seen = /* @__PURE__ */ new Set();
|
|
27714
|
+
while (current && typeof current === "object" && !seen.has(current) && seen.size < 8) {
|
|
27715
|
+
seen.add(current);
|
|
27716
|
+
const record7 = current;
|
|
27717
|
+
if (typeof record7.message === "string") message = record7.message;
|
|
27718
|
+
current = record7.cause;
|
|
27719
|
+
}
|
|
27720
|
+
return message;
|
|
27721
|
+
}
|
|
27602
27722
|
function hasAccountAdminAuthority(authorization) {
|
|
27603
27723
|
return authorization.accountGrant?.permissions.includes("account:admin") === true;
|
|
27604
27724
|
}
|
|
@@ -55146,7 +55266,9 @@ import {
|
|
|
55146
55266
|
evaluateSlackTaskPolicy,
|
|
55147
55267
|
resolveWorkspaceSlackOrchestrationNoticeSettings,
|
|
55148
55268
|
resolveWorkspaceSlackReactionSummonSettings,
|
|
55269
|
+
SlackChannelRouteListResponse,
|
|
55149
55270
|
SlackReactionChannelListResponse,
|
|
55271
|
+
UpdateSlackChannelRoutesRequest,
|
|
55150
55272
|
SlackUserLinkAccessMutationRequest,
|
|
55151
55273
|
SlackUserLinkAccessRequest,
|
|
55152
55274
|
workspaceSlackReactionChannelAllowed
|
|
@@ -55188,7 +55310,14 @@ import {
|
|
|
55188
55310
|
getSlackInteractionById,
|
|
55189
55311
|
getSlackInteractionByConnectionRoute,
|
|
55190
55312
|
probeSlackActionHandleTenancy,
|
|
55313
|
+
openSlackRoutePrompt,
|
|
55314
|
+
getSlackRoutePromptByOption,
|
|
55315
|
+
settleSlackRoutePrompt,
|
|
55316
|
+
answerSlackRoutePrompt,
|
|
55191
55317
|
getSlackChannelRoute,
|
|
55318
|
+
listSlackChannelRoutes,
|
|
55319
|
+
upsertSlackChannelRoute,
|
|
55320
|
+
deleteSlackChannelRoute,
|
|
55192
55321
|
getSlackUserDmRoute,
|
|
55193
55322
|
listNamedSubjectSlackRoutableWorkspaces,
|
|
55194
55323
|
getActiveSlackTaskPolicy,
|
|
@@ -55196,11 +55325,14 @@ import {
|
|
|
55196
55325
|
getSessionEventByClientEventId,
|
|
55197
55326
|
getWorkspace as getWorkspace6,
|
|
55198
55327
|
getWorkspaceGrant as getWorkspaceGrant9,
|
|
55328
|
+
namedSubjectPersonalWorkspaceId,
|
|
55199
55329
|
resolveSlackTargetAuthority,
|
|
55200
55330
|
listSlackInteractionProgressDeliveryEvidence,
|
|
55201
55331
|
listSessionEventPage as listSessionEventPage3,
|
|
55202
55332
|
listSessionHumanInputRequests as listSessionHumanInputRequests2,
|
|
55203
55333
|
listPendingSlackUserLinkAccessRequests,
|
|
55334
|
+
listSlackInstallationBindings as listSlackInstallationBindings2,
|
|
55335
|
+
slackUserLinkAccessRequestTeam,
|
|
55204
55336
|
rekeySlackInteractionRoute,
|
|
55205
55337
|
reopenSlackInteractionDelivery,
|
|
55206
55338
|
reserveSlackInteractionActionHandles,
|
|
@@ -55273,11 +55405,13 @@ function labelFor(candidates, workspaceId) {
|
|
|
55273
55405
|
return candidates.find((candidate) => candidate.workspaceId === workspaceId)?.label ?? null;
|
|
55274
55406
|
}
|
|
55275
55407
|
function resolveSlackWorkspaceRoute(input) {
|
|
55408
|
+
const directMessage = isSlackDirectMessageConversation(input.entry);
|
|
55409
|
+
const candidates = directMessage ? input.candidates : input.candidates.filter((candidate) => !candidate.personal);
|
|
55276
55410
|
const installation = {
|
|
55277
55411
|
kind: "resolved",
|
|
55278
55412
|
accountId: input.home.accountId,
|
|
55279
55413
|
workspaceId: input.home.workspaceId,
|
|
55280
|
-
label: labelFor(
|
|
55414
|
+
label: labelFor(candidates, input.home.workspaceId),
|
|
55281
55415
|
source: "installation"
|
|
55282
55416
|
};
|
|
55283
55417
|
if (!input.routingEnabled) {
|
|
@@ -55294,19 +55428,19 @@ function resolveSlackWorkspaceRoute(input) {
|
|
|
55294
55428
|
kind: "resolved",
|
|
55295
55429
|
accountId: input.threadTenancy.accountId,
|
|
55296
55430
|
workspaceId: input.threadTenancy.workspaceId,
|
|
55297
|
-
label: labelFor(
|
|
55431
|
+
label: labelFor(candidates, input.threadTenancy.workspaceId),
|
|
55298
55432
|
source: "thread"
|
|
55299
55433
|
};
|
|
55300
55434
|
}
|
|
55301
55435
|
const prefix = AUTHORED_BY_INVOKER.has(input.entry.triggerKind) ? parseSlackWorkspacePrefix(splitSlackLeadingMention(input.entry.text, input.botUserId).rest) : null;
|
|
55302
55436
|
if (prefix) {
|
|
55303
|
-
const named = matchCandidate(
|
|
55437
|
+
const named = matchCandidate(candidates, prefix.requested);
|
|
55304
55438
|
if (named === "ambiguous" || !named) {
|
|
55305
55439
|
return {
|
|
55306
55440
|
kind: "denied",
|
|
55307
55441
|
reason: "no_access_to_named",
|
|
55308
55442
|
requested: prefix.requested,
|
|
55309
|
-
candidates
|
|
55443
|
+
candidates
|
|
55310
55444
|
};
|
|
55311
55445
|
}
|
|
55312
55446
|
return {
|
|
@@ -55322,17 +55456,17 @@ function resolveSlackWorkspaceRoute(input) {
|
|
|
55322
55456
|
kind: "resolved",
|
|
55323
55457
|
accountId: input.channelRoute.targetAccountId,
|
|
55324
55458
|
workspaceId: input.channelRoute.targetWorkspaceId,
|
|
55325
|
-
label: labelFor(
|
|
55459
|
+
label: labelFor(candidates, input.channelRoute.targetWorkspaceId),
|
|
55326
55460
|
source: "channel"
|
|
55327
55461
|
};
|
|
55328
55462
|
}
|
|
55329
|
-
if (
|
|
55463
|
+
if (directMessage) {
|
|
55330
55464
|
if (input.dmRoute) {
|
|
55331
55465
|
return {
|
|
55332
55466
|
kind: "resolved",
|
|
55333
55467
|
accountId: input.dmRoute.targetAccountId,
|
|
55334
55468
|
workspaceId: input.dmRoute.targetWorkspaceId,
|
|
55335
|
-
label: labelFor(
|
|
55469
|
+
label: labelFor(candidates, input.dmRoute.targetWorkspaceId),
|
|
55336
55470
|
source: "dm_route"
|
|
55337
55471
|
};
|
|
55338
55472
|
}
|
|
@@ -55341,13 +55475,13 @@ function resolveSlackWorkspaceRoute(input) {
|
|
|
55341
55475
|
kind: "resolved",
|
|
55342
55476
|
accountId: input.home.accountId,
|
|
55343
55477
|
workspaceId: input.personalWorkspaceId,
|
|
55344
|
-
label: labelFor(
|
|
55478
|
+
label: labelFor(candidates, input.personalWorkspaceId),
|
|
55345
55479
|
source: "dm_personal"
|
|
55346
55480
|
};
|
|
55347
55481
|
}
|
|
55348
55482
|
}
|
|
55349
|
-
const sole =
|
|
55350
|
-
if (
|
|
55483
|
+
const sole = candidates[0];
|
|
55484
|
+
if (candidates.length === 1 && sole) {
|
|
55351
55485
|
return {
|
|
55352
55486
|
kind: "resolved",
|
|
55353
55487
|
accountId: sole.accountId,
|
|
@@ -55356,10 +55490,10 @@ function resolveSlackWorkspaceRoute(input) {
|
|
|
55356
55490
|
source: "sole_candidate"
|
|
55357
55491
|
};
|
|
55358
55492
|
}
|
|
55359
|
-
if (
|
|
55493
|
+
if (candidates.length === 0) {
|
|
55360
55494
|
return { kind: "denied", reason: "no_candidates", requested: null, candidates: [] };
|
|
55361
55495
|
}
|
|
55362
|
-
return input.askEnabled ? { kind: "ask", candidates
|
|
55496
|
+
return input.askEnabled ? { kind: "ask", candidates } : installation;
|
|
55363
55497
|
}
|
|
55364
55498
|
function slackRoutedRequestText(text, resolution, botUserId) {
|
|
55365
55499
|
if (resolution.kind !== "resolved" || resolution.source !== "prefix") return text;
|
|
@@ -55883,6 +56017,20 @@ function slackReactionInboxEntry(payload, bot, settings) {
|
|
|
55883
56017
|
hasFiles: false
|
|
55884
56018
|
};
|
|
55885
56019
|
}
|
|
56020
|
+
async function resolveSlackLinkTargetInstallation(deps, link, workspaceId) {
|
|
56021
|
+
const route = await resolveSlackInstallationRoute(deps.db, link.slackTeamId);
|
|
56022
|
+
if (!route || route.connectionId !== link.connectionId || route.accountId.length === 0) {
|
|
56023
|
+
return null;
|
|
56024
|
+
}
|
|
56025
|
+
const workspace = await getWorkspace6(deps.db, workspaceId);
|
|
56026
|
+
if (!workspace || workspace.accountId !== route.accountId) return null;
|
|
56027
|
+
return {
|
|
56028
|
+
accountId: route.accountId,
|
|
56029
|
+
connectionId: route.connectionId,
|
|
56030
|
+
workspaceName: workspace.name,
|
|
56031
|
+
identityHome: { accountId: route.accountId, workspaceId: route.workspaceId }
|
|
56032
|
+
};
|
|
56033
|
+
}
|
|
55886
56034
|
function registerSlackInteractionRoutes(app, deps) {
|
|
55887
56035
|
app.post("/v1/integrations/slack/events", async (c) => {
|
|
55888
56036
|
const signed = await readSignedSlackRequest(c, deps);
|
|
@@ -56035,17 +56183,13 @@ function registerSlackInteractionRoutes(app, deps) {
|
|
|
56035
56183
|
if (!link || link.workspaceId !== workspaceId) {
|
|
56036
56184
|
throw freshSlackLinkRequired();
|
|
56037
56185
|
}
|
|
56038
|
-
const
|
|
56039
|
-
if (!
|
|
56040
|
-
throw freshSlackLinkRequired();
|
|
56041
|
-
}
|
|
56042
|
-
const workspace = await getWorkspace6(deps.db, workspaceId);
|
|
56043
|
-
if (!workspace || workspace.accountId !== route.accountId) {
|
|
56186
|
+
const installation = await resolveSlackLinkTargetInstallation(deps, link, workspaceId);
|
|
56187
|
+
if (!installation) {
|
|
56044
56188
|
throw freshSlackLinkRequired();
|
|
56045
56189
|
}
|
|
56046
56190
|
try {
|
|
56047
56191
|
const prepared = await prepareSlackUserLinkAccessRequest(deps.db, {
|
|
56048
|
-
accountId:
|
|
56192
|
+
accountId: installation.accountId,
|
|
56049
56193
|
workspaceId,
|
|
56050
56194
|
tokenDigest: createHash22("sha256").update(payload.linkToken).digest("hex"),
|
|
56051
56195
|
connectionId: link.connectionId,
|
|
@@ -56058,13 +56202,14 @@ function registerSlackInteractionRoutes(app, deps) {
|
|
|
56058
56202
|
const completed = await completeSlackUserLinkAccessIfGranted(deps.db, {
|
|
56059
56203
|
workspaceId,
|
|
56060
56204
|
requestId: prepared.id,
|
|
56061
|
-
subjectId: context.subjectId
|
|
56205
|
+
subjectId: context.subjectId,
|
|
56206
|
+
identityHome: installation.identityHome
|
|
56062
56207
|
});
|
|
56063
56208
|
if (!completed) throw freshSlackLinkRequired();
|
|
56064
56209
|
return c.json(
|
|
56065
56210
|
SlackUserLinkAccessRequest.parse({
|
|
56066
56211
|
...completed,
|
|
56067
|
-
workspaceDisplayName:
|
|
56212
|
+
workspaceDisplayName: installation.workspaceName
|
|
56068
56213
|
}),
|
|
56069
56214
|
201
|
|
56070
56215
|
);
|
|
@@ -56167,14 +56312,26 @@ function registerSlackInteractionRoutes(app, deps) {
|
|
|
56167
56312
|
const workspaceId = c.req.param("workspaceId");
|
|
56168
56313
|
const grant = await requireAccessGrant44(c, deps, workspaceId, "members:manage");
|
|
56169
56314
|
const payload = ApproveSlackUserLinkAccessRequest.parse(await c.req.json());
|
|
56315
|
+
const requestId = c.req.param("requestId");
|
|
56316
|
+
const requestTeam = await slackUserLinkAccessRequestTeam(deps.db, {
|
|
56317
|
+
workspaceId,
|
|
56318
|
+
requestId
|
|
56319
|
+
});
|
|
56320
|
+
const installation = requestTeam ? await resolveSlackInstallationRoute(deps.db, requestTeam.slackTeamId) : null;
|
|
56170
56321
|
try {
|
|
56171
56322
|
const request = await approveSlackUserLinkAccessRequest(deps.db, {
|
|
56172
56323
|
workspaceId,
|
|
56173
|
-
requestId
|
|
56324
|
+
requestId,
|
|
56174
56325
|
actorSubjectId: grant.subjectId,
|
|
56175
56326
|
expectedVersion: payload.expectedVersion,
|
|
56176
56327
|
idempotencyKey: payload.idempotencyKey,
|
|
56177
56328
|
permissions: payload.permissions,
|
|
56329
|
+
...installation ? {
|
|
56330
|
+
identityHome: {
|
|
56331
|
+
accountId: installation.accountId,
|
|
56332
|
+
workspaceId: installation.workspaceId
|
|
56333
|
+
}
|
|
56334
|
+
} : {},
|
|
56178
56335
|
...payload.role !== void 0 ? { role: payload.role } : {}
|
|
56179
56336
|
});
|
|
56180
56337
|
const workspace = await getWorkspace6(deps.db, workspaceId);
|
|
@@ -56231,8 +56388,8 @@ function registerSlackInteractionRoutes(app, deps) {
|
|
|
56231
56388
|
message: "invalid or expired Slack identity link"
|
|
56232
56389
|
});
|
|
56233
56390
|
}
|
|
56234
|
-
const
|
|
56235
|
-
if (!
|
|
56391
|
+
const installation = await resolveSlackLinkTargetInstallation(deps, link, workspaceId);
|
|
56392
|
+
if (!installation) {
|
|
56236
56393
|
throw new HTTPException70(404, {
|
|
56237
56394
|
message: "Slack installation not found"
|
|
56238
56395
|
});
|
|
@@ -56252,7 +56409,8 @@ function registerSlackInteractionRoutes(app, deps) {
|
|
|
56252
56409
|
const completed = await completeSlackUserLinkAccessIfGranted(deps.db, {
|
|
56253
56410
|
workspaceId,
|
|
56254
56411
|
requestId: prepared.id,
|
|
56255
|
-
subjectId: grant.subjectId
|
|
56412
|
+
subjectId: grant.subjectId,
|
|
56413
|
+
identityHome: installation.identityHome
|
|
56256
56414
|
});
|
|
56257
56415
|
if (completed?.status !== "completed") throw freshSlackLinkRequired();
|
|
56258
56416
|
const saved = await getSlackBotUserLink(
|
|
@@ -56284,6 +56442,92 @@ function registerSlackInteractionRoutes(app, deps) {
|
|
|
56284
56442
|
});
|
|
56285
56443
|
}
|
|
56286
56444
|
);
|
|
56445
|
+
app.get("/v1/workspaces/:workspaceId/integrations/slack/channel-routes", async (c) => {
|
|
56446
|
+
const workspaceId = c.req.param("workspaceId");
|
|
56447
|
+
const grant = await requireAccessGrant44(c, deps, workspaceId, "workspace:admin");
|
|
56448
|
+
const connectionId = boundedString(c.req.query("connectionId"), 64);
|
|
56449
|
+
if (!connectionId) throw new HTTPException70(400, { message: "connectionId is required" });
|
|
56450
|
+
const routes = await listSlackChannelRoutes(
|
|
56451
|
+
deps.db,
|
|
56452
|
+
{ accountId: grant.accountId, workspaceId },
|
|
56453
|
+
{ connectionId }
|
|
56454
|
+
);
|
|
56455
|
+
const names = /* @__PURE__ */ new Map();
|
|
56456
|
+
for (const route of routes) {
|
|
56457
|
+
if (names.has(route.targetWorkspaceId)) continue;
|
|
56458
|
+
names.set(
|
|
56459
|
+
route.targetWorkspaceId,
|
|
56460
|
+
(await getWorkspace6(deps.db, route.targetWorkspaceId))?.name ?? null
|
|
56461
|
+
);
|
|
56462
|
+
}
|
|
56463
|
+
return c.json(
|
|
56464
|
+
SlackChannelRouteListResponse.parse({
|
|
56465
|
+
routingEnabled: deps.settings.slackWorkspaceRoutingEnabled,
|
|
56466
|
+
routes: routes.map((route) => ({
|
|
56467
|
+
slackChannelId: route.slackChannelId,
|
|
56468
|
+
targetWorkspaceId: route.targetWorkspaceId,
|
|
56469
|
+
targetWorkspaceName: names.get(route.targetWorkspaceId) ?? null,
|
|
56470
|
+
source: route.source,
|
|
56471
|
+
updatedAt: route.updatedAt.toISOString()
|
|
56472
|
+
}))
|
|
56473
|
+
})
|
|
56474
|
+
);
|
|
56475
|
+
});
|
|
56476
|
+
app.put("/v1/workspaces/:workspaceId/integrations/slack/channel-routes", async (c) => {
|
|
56477
|
+
const workspaceId = c.req.param("workspaceId");
|
|
56478
|
+
const grant = await requireAccessGrant44(c, deps, workspaceId, "workspace:admin");
|
|
56479
|
+
const payload = UpdateSlackChannelRoutesRequest.parse(await c.req.json());
|
|
56480
|
+
const home = { accountId: grant.accountId, workspaceId };
|
|
56481
|
+
const installation = await listSlackInstallationBindings2(deps.db, home);
|
|
56482
|
+
const binding = installation.find(
|
|
56483
|
+
(candidate) => candidate.connectionId === payload.connectionId
|
|
56484
|
+
);
|
|
56485
|
+
if (!binding) {
|
|
56486
|
+
throw new HTTPException70(404, { message: "Slack installation not found" });
|
|
56487
|
+
}
|
|
56488
|
+
const callerPersonalWorkspaceId = await namedSubjectPersonalWorkspaceId(deps.db, {
|
|
56489
|
+
accountId: grant.accountId,
|
|
56490
|
+
subjectId: grant.subjectId
|
|
56491
|
+
});
|
|
56492
|
+
for (const route of payload.routes) {
|
|
56493
|
+
if (route.targetWorkspaceId === null) continue;
|
|
56494
|
+
const authorized = await resolveSlackTargetAuthority(deps.db, {
|
|
56495
|
+
subjectId: grant.subjectId,
|
|
56496
|
+
targetAccountId: grant.accountId,
|
|
56497
|
+
targetWorkspaceId: route.targetWorkspaceId
|
|
56498
|
+
});
|
|
56499
|
+
if (!authorized || !hasPermission22(authorized.permissions, "sessions:create")) {
|
|
56500
|
+
throw new HTTPException70(403, {
|
|
56501
|
+
message: `you cannot start work in the workspace chosen for ${route.slackChannelId}`
|
|
56502
|
+
});
|
|
56503
|
+
}
|
|
56504
|
+
if (callerPersonalWorkspaceId !== null && route.targetWorkspaceId === callerPersonalWorkspaceId) {
|
|
56505
|
+
throw new HTTPException70(422, {
|
|
56506
|
+
message: `a channel cannot be routed to a personal workspace: work started in ${route.slackChannelId} would be invisible to everyone else in it`
|
|
56507
|
+
});
|
|
56508
|
+
}
|
|
56509
|
+
}
|
|
56510
|
+
for (const route of payload.routes) {
|
|
56511
|
+
if (route.targetWorkspaceId === null) {
|
|
56512
|
+
await deleteSlackChannelRoute(deps.db, home, {
|
|
56513
|
+
connectionId: payload.connectionId,
|
|
56514
|
+
slackChannelId: route.slackChannelId
|
|
56515
|
+
});
|
|
56516
|
+
continue;
|
|
56517
|
+
}
|
|
56518
|
+
await upsertSlackChannelRoute(deps.db, home, {
|
|
56519
|
+
connectionId: payload.connectionId,
|
|
56520
|
+
slackTeamId: binding.slackTeamId,
|
|
56521
|
+
slackChannelId: route.slackChannelId,
|
|
56522
|
+
targetAccountId: grant.accountId,
|
|
56523
|
+
targetWorkspaceId: route.targetWorkspaceId,
|
|
56524
|
+
decidedBySubjectId: grant.subjectId,
|
|
56525
|
+
decidedBySlackUserId: "admin",
|
|
56526
|
+
source: "admin"
|
|
56527
|
+
});
|
|
56528
|
+
}
|
|
56529
|
+
return c.json({ ok: true });
|
|
56530
|
+
});
|
|
56287
56531
|
app.get("/v1/workspaces/:workspaceId/integrations/slack/reaction-channels", async (c) => {
|
|
56288
56532
|
const workspaceId = c.req.param("workspaceId");
|
|
56289
56533
|
const grant = await requireAccessGrant44(c, deps, workspaceId, "workspace:admin");
|
|
@@ -56680,13 +56924,222 @@ async function resolveSlackRouteForEntry(deps, home, entry2, subjectId, options)
|
|
|
56680
56924
|
}
|
|
56681
56925
|
async function postSlackRouteRefusal(deps, client, entry2, refusal) {
|
|
56682
56926
|
const available = refusal.candidates.map((candidate) => candidate.label);
|
|
56683
|
-
const text = refusal.reason === "no_access_to_named" ? `OpenGeni does not see a workspace named ${JSON.stringify(refusal.requested ?? "")} that you can start work in.${available.length > 0 ? ` You can use: ${available.join(", ")}.` : ""} No session was created.` : refusal.reason === "no_access_to_route" ? `OpenGeni starts work from this conversation in ${refusal.requested ?? "another workspace"}, and you do not have access to it.
|
|
56927
|
+
const text = refusal.reason === "no_access_to_named" ? `OpenGeni does not see a workspace named ${JSON.stringify(refusal.requested ?? "")} that you can start work in from here.${available.length > 0 ? ` You can use: ${available.join(", ")}.` : ""} No session was created.` : refusal.reason === "no_access_to_route" ? `OpenGeni starts work from this conversation in ${refusal.requested ?? "another workspace"}, and you do not have access to it. Request access: ${linkUrl(
|
|
56928
|
+
deps,
|
|
56929
|
+
{
|
|
56930
|
+
// Minted for the workspace they actually need, not the
|
|
56931
|
+
// installation's. The token only decides which workspace an
|
|
56932
|
+
// access request may be raised for; approval is unchanged.
|
|
56933
|
+
workspaceId: refusal.workspaceId ?? entry2.workspaceId,
|
|
56934
|
+
connectionId: entry2.connectionId,
|
|
56935
|
+
slackTeamId: entry2.slackTeamId,
|
|
56936
|
+
slackUserId: entry2.slackUserId
|
|
56937
|
+
},
|
|
56938
|
+
entry2.createdAt.getTime()
|
|
56939
|
+
)}. No session was created.` : (
|
|
56940
|
+
// Deliberately "from this conversation" rather than "for you": in a
|
|
56941
|
+
// channel this also fires for someone whose only workspace is their
|
|
56942
|
+
// own personal one, and telling that person they have no workspace
|
|
56943
|
+
// at all would be false. Their bot DM still works.
|
|
56944
|
+
"OpenGeni has no workspace it can start this task in from this conversation. Ask an OpenGeni administrator to give you access to one, or message OpenGeni directly to work in your own workspace. No session was created."
|
|
56945
|
+
);
|
|
56684
56946
|
await client.postMessage({
|
|
56685
56947
|
operationId: deterministicUuid2(`slack-route-denied:${entry2.id}:${refusal.reason}`),
|
|
56686
56948
|
userId: entry2.slackUserId,
|
|
56687
56949
|
text: boundedOutput(text)
|
|
56688
56950
|
});
|
|
56689
56951
|
}
|
|
56952
|
+
function boundedSlackRouteLabel(label) {
|
|
56953
|
+
const encoder3 = new TextEncoder();
|
|
56954
|
+
if (encoder3.encode(label).length <= 72) return label;
|
|
56955
|
+
let bounded = label;
|
|
56956
|
+
while (bounded.length > 0 && encoder3.encode(`${bounded}...`).length > 72) {
|
|
56957
|
+
bounded = bounded.slice(0, -1);
|
|
56958
|
+
}
|
|
56959
|
+
return `${bounded}...`;
|
|
56960
|
+
}
|
|
56961
|
+
function withWorkspaceLine(label, text, blocks) {
|
|
56962
|
+
if (!label) return blocks ? { text, blocks } : { text };
|
|
56963
|
+
const suffix = `
|
|
56964
|
+
-> ${label}`;
|
|
56965
|
+
const body4 = boundedOutput(text, MAX_SLACK_TEXT_CHARS - suffix.length);
|
|
56966
|
+
const line = {
|
|
56967
|
+
type: "context",
|
|
56968
|
+
elements: [{ type: "mrkdwn", text: `-> ${label}` }]
|
|
56969
|
+
};
|
|
56970
|
+
return {
|
|
56971
|
+
text: `${body4}${suffix}`,
|
|
56972
|
+
...blocks ? { blocks: [...blocks, line] } : {}
|
|
56973
|
+
};
|
|
56974
|
+
}
|
|
56975
|
+
function slackPostSeed(interaction, seed) {
|
|
56976
|
+
return interaction.routedWorkspaceLabel ? `${seed}:v2` : seed;
|
|
56977
|
+
}
|
|
56978
|
+
var SLACK_ROUTE_SOURCES_WORTH_NAMING = /* @__PURE__ */ new Set([
|
|
56979
|
+
// Not "thread": a mapped thread reuses the interaction that already exists,
|
|
56980
|
+
// so it inherits the label frozen when that interaction was created and never
|
|
56981
|
+
// reaches the writer below.
|
|
56982
|
+
"prefix",
|
|
56983
|
+
"channel",
|
|
56984
|
+
"dm_route",
|
|
56985
|
+
"dm_personal"
|
|
56986
|
+
]);
|
|
56987
|
+
async function routedWorkspaceLabelFor(deps, resolution) {
|
|
56988
|
+
if (!SLACK_ROUTE_SOURCES_WORTH_NAMING.has(resolution.source)) return null;
|
|
56989
|
+
const label = resolution.label ?? (await getWorkspace6(deps.db, resolution.workspaceId))?.name;
|
|
56990
|
+
if (!label) return null;
|
|
56991
|
+
const bounded = boundedSlackRouteLabel(label);
|
|
56992
|
+
return bounded.length > 0 ? bounded : null;
|
|
56993
|
+
}
|
|
56994
|
+
var SLACK_ROUTE_PROMPT_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
56995
|
+
var MAX_SLACK_ROUTE_PROMPT_BUTTONS = 5;
|
|
56996
|
+
async function askSlackRouteChoice(deps, client, home, entry2, candidates) {
|
|
56997
|
+
const offered = candidates.slice(0, MAX_SLACK_ROUTE_PROMPT_BUTTONS);
|
|
56998
|
+
const bounded = offered.map((candidate) => ({
|
|
56999
|
+
...candidate,
|
|
57000
|
+
label: boundedSlackRouteLabel(candidate.label)
|
|
57001
|
+
}));
|
|
57002
|
+
const opened = await openSlackRoutePrompt(deps.db, home, {
|
|
57003
|
+
connectionId: entry2.connectionId,
|
|
57004
|
+
inboxId: entry2.id,
|
|
57005
|
+
slackTeamId: entry2.slackTeamId,
|
|
57006
|
+
slackUserId: entry2.slackUserId,
|
|
57007
|
+
slackChannelId: entry2.slackChannelId,
|
|
57008
|
+
slackMessageTs: entry2.slackMessageTs,
|
|
57009
|
+
providerEventId: entry2.providerEventId,
|
|
57010
|
+
triggerKind: entry2.triggerKind,
|
|
57011
|
+
requestText: entry2.text,
|
|
57012
|
+
hasFiles: entry2.hasFiles,
|
|
57013
|
+
slackThreadTs: entry2.slackThreadTs,
|
|
57014
|
+
messageOperationId: deterministicUuid2(`slack-route-prompt:${entry2.id}`),
|
|
57015
|
+
expiresAt: new Date(Date.now() + SLACK_ROUTE_PROMPT_TTL_MS),
|
|
57016
|
+
candidates: bounded
|
|
57017
|
+
});
|
|
57018
|
+
if (!opened) return;
|
|
57019
|
+
const where = opened.prompt.slackChannelId.startsWith("D") ? "your messages with OpenGeni" : `<#${opened.prompt.slackChannelId}>`;
|
|
57020
|
+
const blocks = [
|
|
57021
|
+
{
|
|
57022
|
+
type: "section",
|
|
57023
|
+
text: {
|
|
57024
|
+
type: "mrkdwn",
|
|
57025
|
+
text: `OpenGeni can start this task in more than one workspace. Pick where ${where} should work. This choice is remembered.`
|
|
57026
|
+
}
|
|
57027
|
+
},
|
|
57028
|
+
{
|
|
57029
|
+
type: "actions",
|
|
57030
|
+
block_id: "opengeni-route-choice",
|
|
57031
|
+
elements: [
|
|
57032
|
+
...opened.prompt.options.map((option) => ({
|
|
57033
|
+
type: "button",
|
|
57034
|
+
action_id: "opengeni.route.select",
|
|
57035
|
+
value: option.id,
|
|
57036
|
+
text: { type: "plain_text", text: option.candidateLabel }
|
|
57037
|
+
})),
|
|
57038
|
+
{
|
|
57039
|
+
type: "button",
|
|
57040
|
+
action_id: "opengeni.route.cancel",
|
|
57041
|
+
value: opened.prompt.options[0]?.id ?? opened.prompt.id,
|
|
57042
|
+
text: { type: "plain_text", text: "Not now" }
|
|
57043
|
+
}
|
|
57044
|
+
]
|
|
57045
|
+
}
|
|
57046
|
+
];
|
|
57047
|
+
const url = slackWorkspaceUrl(deps, home.workspaceId);
|
|
57048
|
+
blocks.push({
|
|
57049
|
+
type: "section",
|
|
57050
|
+
text: {
|
|
57051
|
+
type: "mrkdwn",
|
|
57052
|
+
text: url ? `You can change this later in <${url}|OpenGeni>, under Capabilities, then Slack.` : "You can change this later in OpenGeni, under Capabilities, then Slack."
|
|
57053
|
+
}
|
|
57054
|
+
});
|
|
57055
|
+
await client.postMessage({
|
|
57056
|
+
operationId: opened.prompt.messageOperationId,
|
|
57057
|
+
userId: opened.prompt.slackUserId,
|
|
57058
|
+
text: boundedOutput(
|
|
57059
|
+
`OpenGeni can start this task in more than one workspace. Pick where ${where} should work.`
|
|
57060
|
+
),
|
|
57061
|
+
blocks
|
|
57062
|
+
});
|
|
57063
|
+
}
|
|
57064
|
+
async function handleSlackRouteChoice(deps, home, entry2, actionId, optionId) {
|
|
57065
|
+
if (!deps.settings.slackWorkspaceRoutingEnabled) {
|
|
57066
|
+
throw new SlackInteractionPermanentError("slack_route_choice_unavailable");
|
|
57067
|
+
}
|
|
57068
|
+
const found = await getSlackRoutePromptByOption(deps.db, home, optionId);
|
|
57069
|
+
if (!found) throw new SlackInteractionPermanentError("slack_route_prompt_invalid");
|
|
57070
|
+
const { prompt, chosen } = found;
|
|
57071
|
+
if (prompt.slackUserId !== entry2.slackUserId) {
|
|
57072
|
+
throw new SlackInteractionPermanentError("slack_route_prompt_user_mismatch");
|
|
57073
|
+
}
|
|
57074
|
+
const client = await createOpenGeniSlackBotInteractionClient(deps, {
|
|
57075
|
+
...home,
|
|
57076
|
+
connectionId: entry2.connectionId,
|
|
57077
|
+
subjectId: SLACK_INTERACTION_BOT_SUBJECT_ID
|
|
57078
|
+
});
|
|
57079
|
+
const replaceCard = async (outcome, text) => {
|
|
57080
|
+
await client.updateMessage({
|
|
57081
|
+
operationId: deterministicUuid2(
|
|
57082
|
+
`slack-route-answered:${prompt.id}:${actionId}:${chosen.id}:${outcome}`
|
|
57083
|
+
),
|
|
57084
|
+
channelId: entry2.slackChannelId,
|
|
57085
|
+
timestamp: entry2.slackMessageTs,
|
|
57086
|
+
text,
|
|
57087
|
+
// Every button goes with the card, so a sibling option cannot be pressed
|
|
57088
|
+
// after the question is settled.
|
|
57089
|
+
blocks: [{ type: "section", text: { type: "mrkdwn", text } }]
|
|
57090
|
+
});
|
|
57091
|
+
};
|
|
57092
|
+
if (actionId === "opengeni.route.cancel") {
|
|
57093
|
+
const cancelled = await settleSlackRoutePrompt(deps.db, home, {
|
|
57094
|
+
promptId: prompt.id,
|
|
57095
|
+
status: "cancelled"
|
|
57096
|
+
});
|
|
57097
|
+
await replaceCard(
|
|
57098
|
+
cancelled ? "cancelled" : "already_settled",
|
|
57099
|
+
cancelled ? "OpenGeni did not start this task. No session was created." : "OpenGeni already settled this choice."
|
|
57100
|
+
);
|
|
57101
|
+
return;
|
|
57102
|
+
}
|
|
57103
|
+
const link = await getSlackBotUserLink(
|
|
57104
|
+
deps.db,
|
|
57105
|
+
home.workspaceId,
|
|
57106
|
+
entry2.connectionId,
|
|
57107
|
+
entry2.slackUserId
|
|
57108
|
+
);
|
|
57109
|
+
const grant = link ? await resolveSlackTargetAuthority(deps.db, {
|
|
57110
|
+
subjectId: link.subjectId,
|
|
57111
|
+
targetAccountId: chosen.candidateAccountId,
|
|
57112
|
+
targetWorkspaceId: chosen.candidateWorkspaceId
|
|
57113
|
+
}) : null;
|
|
57114
|
+
if (!grant || !hasPermission22(grant.permissions, "sessions:create")) {
|
|
57115
|
+
await client.postMessage({
|
|
57116
|
+
operationId: deterministicUuid2(`slack-route-denied:${prompt.id}:${chosen.id}`),
|
|
57117
|
+
userId: entry2.slackUserId,
|
|
57118
|
+
text: boundedOutput(
|
|
57119
|
+
`OpenGeni cannot start work in ${chosen.candidateLabel}: you do not have access to it. No session was created. Pick another workspace on the card, or ask an OpenGeni administrator for access.`
|
|
57120
|
+
)
|
|
57121
|
+
});
|
|
57122
|
+
return;
|
|
57123
|
+
}
|
|
57124
|
+
const answered = await answerSlackRoutePrompt(deps.db, home, {
|
|
57125
|
+
promptId: prompt.id,
|
|
57126
|
+
targetAccountId: chosen.candidateAccountId,
|
|
57127
|
+
targetWorkspaceId: chosen.candidateWorkspaceId,
|
|
57128
|
+
answeredBySubjectId: grant.subjectId,
|
|
57129
|
+
decidedBySlackUserId: entry2.slackUserId
|
|
57130
|
+
});
|
|
57131
|
+
if (!answered.answered) {
|
|
57132
|
+
await replaceCard(
|
|
57133
|
+
"settled",
|
|
57134
|
+
"OpenGeni already settled this choice. No new session was created."
|
|
57135
|
+
);
|
|
57136
|
+
return;
|
|
57137
|
+
}
|
|
57138
|
+
await replaceCard(
|
|
57139
|
+
"answered",
|
|
57140
|
+
`OpenGeni will use *${chosen.candidateLabel}* here. You can change this in OpenGeni under Capabilities, then Slack.`
|
|
57141
|
+
);
|
|
57142
|
+
}
|
|
56690
57143
|
async function processSlackInboxEntry(deps, entry2) {
|
|
56691
57144
|
if (entry2.triggerKind === "block_action") {
|
|
56692
57145
|
await processSlackBlockAction(deps, entry2);
|
|
@@ -56743,19 +57196,18 @@ async function processSlackInboxEntry(deps, entry2) {
|
|
|
56743
57196
|
await client.postMessage({
|
|
56744
57197
|
operationId: deterministicUuid2(`slack-link:${entry2.id}`),
|
|
56745
57198
|
userId: entry2.slackUserId,
|
|
56746
|
-
text: `Link your Slack identity to OpenGeni before starting work: ${linkUrl(deps, entry2)}. No session was created.`
|
|
57199
|
+
text: `Link your Slack identity to OpenGeni before starting work: ${linkUrl(deps, entry2, entry2.createdAt.getTime())}. No session was created.`
|
|
56747
57200
|
});
|
|
56748
57201
|
return;
|
|
56749
57202
|
}
|
|
56750
57203
|
const resolution = await resolveSlackRouteForEntry(deps, home, entry2, link.subjectId, {
|
|
56751
57204
|
botUserId: installation.botUserId,
|
|
56752
57205
|
threadTenancy: existing ? { accountId: existing.accountId, workspaceId: existing.workspaceId } : null,
|
|
56753
|
-
|
|
56754
|
-
// installation's workspace rather than inventing an answer.
|
|
56755
|
-
askEnabled: false
|
|
57206
|
+
askEnabled: true
|
|
56756
57207
|
});
|
|
56757
57208
|
if (resolution.kind === "ask") {
|
|
56758
|
-
|
|
57209
|
+
await askSlackRouteChoice(deps, client, home, entry2, resolution.candidates);
|
|
57210
|
+
return;
|
|
56759
57211
|
}
|
|
56760
57212
|
if (resolution.kind === "denied") {
|
|
56761
57213
|
await postSlackRouteRefusal(deps, client, entry2, resolution);
|
|
@@ -56862,6 +57314,11 @@ async function processSlackInboxEntry(deps, entry2) {
|
|
|
56862
57314
|
}
|
|
56863
57315
|
const { interaction } = await getOrCreateSlackInteraction(deps.db, {
|
|
56864
57316
|
...target,
|
|
57317
|
+
// Frozen here, after authorization, so a workspace name is never shown to
|
|
57318
|
+
// someone who could not reach it. Null when the routing decision was not a
|
|
57319
|
+
// genuine choice, which is what keeps the line off every message in an
|
|
57320
|
+
// install that only ever had one workspace.
|
|
57321
|
+
routedWorkspaceLabel: await routedWorkspaceLabelFor(deps, resolution),
|
|
56865
57322
|
connectionId: entry2.connectionId,
|
|
56866
57323
|
slackTeamId: entry2.slackTeamId,
|
|
56867
57324
|
slackChannelId: entry2.slackChannelId,
|
|
@@ -57087,7 +57544,7 @@ async function acknowledgeSlackSession(deps, client, interaction, entry2) {
|
|
|
57087
57544
|
const directMessageShortcut = isDirectMessageShortcut(entry2);
|
|
57088
57545
|
const privateHandoff = !directMessageShortcut && interaction.visibility === "private" && entry2.triggerKind !== "dm";
|
|
57089
57546
|
const privateBotDm = directMessageShortcut || privateHandoff;
|
|
57090
|
-
const operationId = deterministicUuid2(`slack-ack:${interaction.id}`);
|
|
57547
|
+
const operationId = deterministicUuid2(slackPostSeed(interaction, `slack-ack:${interaction.id}`));
|
|
57091
57548
|
const started = directMessageShortcut ? `OpenGeni started a private task from the selected DM message. ${openSessionText(deps, interaction.workspaceId, interaction.sessionId)} The source DM was not opened to the bot or made workspace-visible.` : privateHandoff ? `OpenGeni started a private task from the selected Slack conversation. ${openSessionText(deps, interaction.workspaceId, interaction.sessionId)} Results stay private unless a separate authorized publication is approved.` : `OpenGeni started this task. ${openSessionText(deps, interaction.workspaceId, interaction.sessionId)}`;
|
|
57092
57549
|
const showHint = await resolveSlackInteractionFirstTaskHint(deps.db, {
|
|
57093
57550
|
// The identity link is HOME; the interaction row is TARGET.
|
|
@@ -57103,19 +57560,19 @@ async function acknowledgeSlackSession(deps, client, interaction, entry2) {
|
|
|
57103
57560
|
sessionEventSequence: 0,
|
|
57104
57561
|
state: "active"
|
|
57105
57562
|
});
|
|
57563
|
+
const acknowledgement = withWorkspaceLine(
|
|
57564
|
+
interaction.routedWorkspaceLabel,
|
|
57565
|
+
text,
|
|
57566
|
+
controls.length > 0 ? [{ type: "section", text: { type: "mrkdwn", text } }, ...controls] : void 0
|
|
57567
|
+
);
|
|
57106
57568
|
const ack = await client.postMessage({
|
|
57107
57569
|
operationId,
|
|
57108
57570
|
...privateBotDm ? { userId: entry2.slackUserId } : {
|
|
57109
57571
|
channelId: entry2.slackChannelId,
|
|
57110
57572
|
...entry2.triggerKind === "slash_command" ? {} : { threadTimestamp: entry2.slackThreadTs ?? entry2.slackMessageTs }
|
|
57111
57573
|
},
|
|
57112
|
-
text,
|
|
57113
|
-
...
|
|
57114
|
-
blocks: [
|
|
57115
|
-
{ type: "section", text: { type: "mrkdwn", text } },
|
|
57116
|
-
...controls
|
|
57117
|
-
]
|
|
57118
|
-
} : {}
|
|
57574
|
+
text: acknowledgement.text,
|
|
57575
|
+
...acknowledgement.blocks ? { blocks: acknowledgement.blocks } : {}
|
|
57119
57576
|
});
|
|
57120
57577
|
if (entry2.triggerKind === "slash_command" || privateBotDm) {
|
|
57121
57578
|
const rekeyed = await rekeySlackInteractionRoute(deps.db, {
|
|
@@ -57283,6 +57740,10 @@ async function processSlackReactionInboxEntry(deps, entry2) {
|
|
|
57283
57740
|
}
|
|
57284
57741
|
const { interaction } = await getOrCreateSlackInteraction(deps.db, {
|
|
57285
57742
|
...target,
|
|
57743
|
+
// A reaction routes exactly like a mention, so it names its workspace the
|
|
57744
|
+
// same way. Resolved after `authorizeTarget`, so a name never reaches
|
|
57745
|
+
// somebody who could not reach the workspace.
|
|
57746
|
+
routedWorkspaceLabel: await routedWorkspaceLabelFor(deps, routeResolution),
|
|
57286
57747
|
connectionId: entry2.connectionId,
|
|
57287
57748
|
slackTeamId: entry2.slackTeamId,
|
|
57288
57749
|
slackChannelId: entry2.slackChannelId,
|
|
@@ -57360,11 +57821,15 @@ async function acknowledgeSlackReactionSession(deps, client, interaction, emoji)
|
|
|
57360
57821
|
if (!interaction.sessionId) {
|
|
57361
57822
|
throw new Error("Slack reaction acknowledgement requires a bound session");
|
|
57362
57823
|
}
|
|
57824
|
+
const started = `OpenGeni started from the :${emoji}: reaction. ${openSessionText(deps, interaction.workspaceId, interaction.sessionId)} If the intended action is unclear, OpenGeni will ask in this thread. Reply here to continue, or reply \`stop\` to stop.`;
|
|
57825
|
+
const rendered = withWorkspaceLine(interaction.routedWorkspaceLabel, started);
|
|
57363
57826
|
await client.postMessage({
|
|
57364
|
-
operationId: deterministicUuid2(
|
|
57827
|
+
operationId: deterministicUuid2(
|
|
57828
|
+
slackPostSeed(interaction, `slack-reaction-ack:${interaction.id}`)
|
|
57829
|
+
),
|
|
57365
57830
|
channelId: interaction.slackChannelId,
|
|
57366
57831
|
threadTimestamp: interaction.slackThreadTs,
|
|
57367
|
-
text:
|
|
57832
|
+
text: rendered.text
|
|
57368
57833
|
});
|
|
57369
57834
|
}
|
|
57370
57835
|
function slackReactionPreparedEntry(entry2, context, prepared) {
|
|
@@ -57715,6 +58180,10 @@ async function processSlackBlockAction(deps, entry2) {
|
|
|
57715
58180
|
if (!route || route.accountId !== entry2.accountId || route.workspaceId !== entry2.workspaceId || route.connectionId !== entry2.connectionId) {
|
|
57716
58181
|
throw new SlackInteractionPermanentError("slack_action_installation_changed");
|
|
57717
58182
|
}
|
|
58183
|
+
if (actionId === "opengeni.route.select" || actionId === "opengeni.route.cancel") {
|
|
58184
|
+
await handleSlackRouteChoice(deps, home, entry2, actionId, handleId);
|
|
58185
|
+
return;
|
|
58186
|
+
}
|
|
57718
58187
|
const handleTenancy = await probeSlackActionHandleTenancy(deps.db, {
|
|
57719
58188
|
accountId: home.accountId,
|
|
57720
58189
|
connectionId: entry2.connectionId,
|
|
@@ -57774,14 +58243,21 @@ async function processSlackBlockAction(deps, entry2) {
|
|
|
57774
58243
|
sessionId: handle.sessionId
|
|
57775
58244
|
});
|
|
57776
58245
|
const outcome = await executeSlackAction(deps, grant, interaction, handle);
|
|
57777
|
-
const acknowledgementOperationId = deterministicUuid2(
|
|
58246
|
+
const acknowledgementOperationId = deterministicUuid2(
|
|
58247
|
+
slackPostSeed(interaction, `slack-ack:${interaction.id}`)
|
|
58248
|
+
);
|
|
57778
58249
|
const updateText = interaction.firstTaskHint === true && handle.messageOperationId === acknowledgementOperationId ? `${outcome.text}${slackFirstTaskHintText(deps)}` : outcome.text;
|
|
58250
|
+
const rendered = withWorkspaceLine(interaction.routedWorkspaceLabel, updateText, [
|
|
58251
|
+
{ type: "section", text: { type: "mrkdwn", text: updateText } }
|
|
58252
|
+
]);
|
|
57779
58253
|
await client.updateMessage({
|
|
57780
|
-
operationId: deterministicUuid2(
|
|
58254
|
+
operationId: deterministicUuid2(
|
|
58255
|
+
slackPostSeed(interaction, `slack-action-update:${handle.id}:${outcome.result}`)
|
|
58256
|
+
),
|
|
57781
58257
|
channelId: entry2.slackChannelId,
|
|
57782
58258
|
timestamp: entry2.slackMessageTs,
|
|
57783
|
-
text:
|
|
57784
|
-
blocks
|
|
58259
|
+
text: rendered.text,
|
|
58260
|
+
...rendered.blocks ? { blocks: rendered.blocks } : {}
|
|
57785
58261
|
});
|
|
57786
58262
|
await settleSlackInteractionActionHandles(deps.db, {
|
|
57787
58263
|
...target,
|
|
@@ -58125,7 +58601,7 @@ function slackApprovalSummaries(payload) {
|
|
|
58125
58601
|
}
|
|
58126
58602
|
async function slackApprovalCard(deps, interaction, event, mention, requesterAuthorized) {
|
|
58127
58603
|
const operationId = deterministicUuid2(
|
|
58128
|
-
`slack-delivery:${interaction.id}:${event.sequence}:approval`
|
|
58604
|
+
slackPostSeed(interaction, `slack-delivery:${interaction.id}:${event.sequence}:approval`)
|
|
58129
58605
|
);
|
|
58130
58606
|
const approvals = slackApprovalSummaries(event.payload);
|
|
58131
58607
|
const fallback = approvals.length ? `${mention}OpenGeni needs approval for ${approvals.map((approval) => approval.name).join(", ")}.` : `${mention}OpenGeni needs approval. Open the task to review it.`;
|
|
@@ -58253,7 +58729,7 @@ async function slackHumanInputCard(deps, interaction, event, mention, requesterA
|
|
|
58253
58729
|
);
|
|
58254
58730
|
if (!request || request.status !== "pending") return null;
|
|
58255
58731
|
const operationId = deterministicUuid2(
|
|
58256
|
-
`slack-delivery:${interaction.id}:${event.sequence}:human-input`
|
|
58732
|
+
slackPostSeed(interaction, `slack-delivery:${interaction.id}:${event.sequence}:human-input`)
|
|
58257
58733
|
);
|
|
58258
58734
|
const text = `${mention}OpenGeni needs your input:
|
|
58259
58735
|
${formatQuestions(request.questions)}
|
|
@@ -58558,30 +59034,32 @@ async function deliverSlackSessionEvents(deps, interaction, claimHolderId) {
|
|
|
58558
59034
|
);
|
|
58559
59035
|
if (posted?.slackChannelId && posted.slackMessageTimestamp) {
|
|
58560
59036
|
const text = boundedOutput(`${requester.mention}${existingProgress.text}`);
|
|
59037
|
+
const rendered = withWorkspaceLine(
|
|
59038
|
+
interaction.routedWorkspaceLabel,
|
|
59039
|
+
text,
|
|
59040
|
+
publicationBlocks.length > 0 ? [
|
|
59041
|
+
{ type: "section", text: { type: "mrkdwn", text } },
|
|
59042
|
+
...publicationBlocks
|
|
59043
|
+
] : void 0
|
|
59044
|
+
);
|
|
58561
59045
|
await client.updateMessage({
|
|
58562
59046
|
operationId: deterministicUuid2(
|
|
58563
|
-
|
|
59047
|
+
slackPostSeed(
|
|
59048
|
+
interaction,
|
|
59049
|
+
`slack-terminal-update:${interaction.id}:${event.sequence}`
|
|
59050
|
+
)
|
|
58564
59051
|
),
|
|
58565
59052
|
channelId: posted.slackChannelId,
|
|
58566
59053
|
timestamp: posted.slackMessageTimestamp,
|
|
58567
|
-
text,
|
|
58568
|
-
...
|
|
58569
|
-
blocks:
|
|
58570
|
-
{
|
|
58571
|
-
type: "section",
|
|
58572
|
-
text: {
|
|
58573
|
-
type: "mrkdwn",
|
|
58574
|
-
text
|
|
58575
|
-
}
|
|
58576
|
-
},
|
|
58577
|
-
...publicationBlocks
|
|
58578
|
-
]
|
|
59054
|
+
text: rendered.text,
|
|
59055
|
+
...rendered.blocks ? {
|
|
59056
|
+
blocks: rendered.blocks
|
|
58579
59057
|
} : {}
|
|
58580
59058
|
});
|
|
58581
59059
|
}
|
|
58582
59060
|
} else {
|
|
58583
59061
|
const operationId = deterministicUuid2(
|
|
58584
|
-
`slack-delivery:${interaction.id}:${event.sequence}:final`
|
|
59062
|
+
slackPostSeed(interaction, `slack-delivery:${interaction.id}:${event.sequence}:final`)
|
|
58585
59063
|
);
|
|
58586
59064
|
const text = boundedOutput(
|
|
58587
59065
|
`${requester.mention}${output || "OpenGeni finished this task."}`
|
|
@@ -58658,13 +59136,16 @@ function slackTerminalProgressSameTurn(event, delivery, interaction, samePageTer
|
|
|
58658
59136
|
if (event.turnId || delivery.turnId) return false;
|
|
58659
59137
|
return delivery.sessionEventSequence === interaction.lastDeliveredSessionEventSequence || samePageTerminalSequences.has(delivery.sessionEventSequence);
|
|
58660
59138
|
}
|
|
58661
|
-
async function postDelivery(client, interaction, event, text, kind, operationId = deterministicUuid2(
|
|
59139
|
+
async function postDelivery(client, interaction, event, text, kind, operationId = deterministicUuid2(
|
|
59140
|
+
slackPostSeed(interaction, `slack-delivery:${interaction.id}:${event.sequence}:${kind}`)
|
|
59141
|
+
), blocks) {
|
|
59142
|
+
const rendered = withWorkspaceLine(interaction.routedWorkspaceLabel, boundedOutput(text), blocks);
|
|
58662
59143
|
await client.postMessage({
|
|
58663
59144
|
operationId,
|
|
58664
59145
|
channelId: interaction.slackChannelId,
|
|
58665
59146
|
threadTimestamp: interaction.slackThreadTs,
|
|
58666
|
-
text:
|
|
58667
|
-
...blocks ? { blocks } : {}
|
|
59147
|
+
text: rendered.text,
|
|
59148
|
+
...rendered.blocks ? { blocks: rendered.blocks } : {}
|
|
58668
59149
|
});
|
|
58669
59150
|
}
|
|
58670
59151
|
async function enqueueNormalizedSlackInteraction(deps, route, entry2) {
|
|
@@ -58730,7 +59211,9 @@ var SLACK_BLOCK_ACTION_IDS = /* @__PURE__ */ new Set([
|
|
|
58730
59211
|
"opengeni.session.status",
|
|
58731
59212
|
"opengeni.session.pause",
|
|
58732
59213
|
"opengeni.session.resume",
|
|
58733
|
-
"opengeni.shared_result.publish"
|
|
59214
|
+
"opengeni.shared_result.publish",
|
|
59215
|
+
"opengeni.route.select",
|
|
59216
|
+
"opengeni.route.cancel"
|
|
58734
59217
|
]);
|
|
58735
59218
|
function normalizedBlockActionInteraction(payload) {
|
|
58736
59219
|
const team = record6(payload.team);
|
|
@@ -58926,13 +59409,13 @@ function makeRecurringText(deps, workspaceId, sessionId) {
|
|
|
58926
59409
|
function slackSchedulesUrl(deps, workspaceId) {
|
|
58927
59410
|
return safeSlackActionUrl(deps, `/workspaces/${encodeURIComponent(workspaceId)}/schedules`);
|
|
58928
59411
|
}
|
|
58929
|
-
function linkUrl(deps, entry2) {
|
|
59412
|
+
function linkUrl(deps, entry2, mintedAtMs) {
|
|
58930
59413
|
const base = deps.settings.webBaseUrl ?? deps.settings.publicBaseUrl;
|
|
58931
59414
|
const signingSecret = deps.settings.slackSigningSecret;
|
|
58932
59415
|
if (!base || !signingSecret) return "OpenGeni Settings \u2192 Integrations \u2192 Slack";
|
|
58933
59416
|
const url = new URL(`/workspaces/${entry2.workspaceId}/capabilities`, base);
|
|
58934
59417
|
url.hash = new URLSearchParams({
|
|
58935
|
-
slack_link: createSlackUserLinkToken(signingSecret, entry2)
|
|
59418
|
+
slack_link: createSlackUserLinkToken(signingSecret, entry2, mintedAtMs)
|
|
58936
59419
|
}).toString();
|
|
58937
59420
|
return url.toString();
|
|
58938
59421
|
}
|
|
@@ -59046,8 +59529,8 @@ function boundedSlackChildDetail(value) {
|
|
|
59046
59529
|
const collapsed = value.replace(/\s+/gu, " ").trim();
|
|
59047
59530
|
return collapsed.length <= MAX_SLACK_CHILD_DETAIL_CHARS ? collapsed : `${collapsed.slice(0, MAX_SLACK_CHILD_DETAIL_CHARS - 1)}\u2026`;
|
|
59048
59531
|
}
|
|
59049
|
-
function boundedOutput(value) {
|
|
59050
|
-
return value.length <=
|
|
59532
|
+
function boundedOutput(value, limit = MAX_SLACK_TEXT_CHARS) {
|
|
59533
|
+
return value.length <= limit ? value : `${value.slice(0, limit - 20)}
|
|
59051
59534
|
\u2026 output truncated`;
|
|
59052
59535
|
}
|
|
59053
59536
|
function safePayloadText(payload, field) {
|
|
@@ -60220,6 +60703,10 @@ var routeLabelPatterns = [
|
|
|
60220
60703
|
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/events$/,
|
|
60221
60704
|
label: "/v1/workspaces/:workspaceId/sessions/:id/events"
|
|
60222
60705
|
},
|
|
60706
|
+
{
|
|
60707
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/turns$/,
|
|
60708
|
+
label: "/v1/workspaces/:workspaceId/sessions/:id/turns"
|
|
60709
|
+
},
|
|
60223
60710
|
{
|
|
60224
60711
|
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/queue\/[^/]+\/(move|edit|steer|delete)$/,
|
|
60225
60712
|
label: "/v1/workspaces/:workspaceId/sessions/:id/queue/:turnId/:action"
|
|
@@ -60673,6 +61160,10 @@ var routeLabelPatterns = [
|
|
|
60673
61160
|
{
|
|
60674
61161
|
pattern: /^\/v1\/github\/oauth\/callback$/,
|
|
60675
61162
|
label: "/v1/github/oauth/callback"
|
|
61163
|
+
},
|
|
61164
|
+
{
|
|
61165
|
+
pattern: /^\/v1\/workspaces\/[^/]+$/,
|
|
61166
|
+
label: "/v1/workspaces/:workspaceId"
|
|
60676
61167
|
}
|
|
60677
61168
|
];
|
|
60678
61169
|
function routeLabel(pathname) {
|
|
@@ -60729,4 +61220,4 @@ export {
|
|
|
60729
61220
|
withDefaultEnabledCapabilityMcpTools,
|
|
60730
61221
|
workflowIdForSession3 as workflowIdForSession
|
|
60731
61222
|
};
|
|
60732
|
-
//# sourceMappingURL=chunk-
|
|
61223
|
+
//# sourceMappingURL=chunk-3TP54PPX.js.map
|