@opengeni/core 0.21.10 → 0.28.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.
Files changed (132) hide show
  1. package/dist/access/index.d.ts +1 -1
  2. package/dist/application/new-session-drafts.d.ts +1 -1
  3. package/dist/application/session-commands.d.ts +2 -2
  4. package/dist/billing/limits.d.ts +1 -1
  5. package/dist/canonical-human-identities.d.ts +12 -0
  6. package/dist/canonical-human-identities.js +23 -0
  7. package/dist/canonical-human-identities.js.map +1 -0
  8. package/dist/chunk-6WKPWE5S.js +2380 -0
  9. package/dist/chunk-6WKPWE5S.js.map +1 -0
  10. package/dist/chunk-IBOEYG6N.js +34 -0
  11. package/dist/chunk-IBOEYG6N.js.map +1 -0
  12. package/dist/chunk-JJU6I5XD.js +2713 -0
  13. package/dist/chunk-JJU6I5XD.js.map +1 -0
  14. package/dist/chunk-NYPMQ7NO.js +2270 -0
  15. package/dist/chunk-NYPMQ7NO.js.map +1 -0
  16. package/dist/dependencies.d.ts +30 -6
  17. package/dist/domain/capabilities.d.ts +28 -3
  18. package/dist/domain/company-profile-durable-learning-adapter.d.ts +39 -0
  19. package/dist/domain/conversation-integrations.d.ts +225 -0
  20. package/dist/domain/durable-learning-slack-publication.d.ts +72 -0
  21. package/dist/domain/editable-artifacts/agent-application.d.ts +192 -0
  22. package/dist/domain/editable-artifacts/durable-export-postgres.d.ts +4 -0
  23. package/dist/domain/editable-artifacts/durable-export.d.ts +212 -0
  24. package/dist/domain/editable-artifacts/errors.d.ts +47 -0
  25. package/dist/domain/editable-artifacts/hash.d.ts +11 -0
  26. package/dist/domain/editable-artifacts/in-memory.d.ts +99 -0
  27. package/dist/domain/editable-artifacts/index.d.ts +14 -0
  28. package/dist/domain/editable-artifacts/ports.d.ts +341 -0
  29. package/dist/domain/editable-artifacts/postgres-adapters.d.ts +13 -0
  30. package/dist/domain/editable-artifacts/service.d.ts +105 -0
  31. package/dist/domain/editable-artifacts/snapshot-compaction.d.ts +44 -0
  32. package/dist/domain/editable-artifacts/snapshot-verifier-genesis.d.ts +50 -0
  33. package/dist/domain/editable-artifacts/snapshot-verifier-materialization.d.ts +21 -0
  34. package/dist/domain/editable-artifacts/snapshot-verifier.d.ts +118 -0
  35. package/dist/domain/editable-artifacts/types.d.ts +410 -0
  36. package/dist/domain/fiken.d.ts +35 -0
  37. package/dist/domain/memory-slack-delivery.d.ts +19 -0
  38. package/dist/domain/packs.d.ts +29 -1
  39. package/dist/domain/personal-connection-delegations.d.ts +9 -1
  40. package/dist/domain/scheduled-tasks.d.ts +2 -2
  41. package/dist/domain/session-tool-policy.d.ts +2 -2
  42. package/dist/domain/sessions.d.ts +24 -8
  43. package/dist/domain/skill-imports.d.ts +32 -0
  44. package/dist/domain/slack-publication-secret-safety.d.ts +8 -0
  45. package/dist/domain/timeline-annotations.d.ts +5 -0
  46. package/dist/domain/video-generation-capabilities.d.ts +10 -0
  47. package/dist/domain/video-generation.d.ts +47 -0
  48. package/dist/editable-artifact-live/application.d.ts +69 -0
  49. package/dist/editable-artifact-live/errors.d.ts +1 -0
  50. package/dist/editable-artifact-live/index.d.ts +8 -0
  51. package/dist/editable-artifact-live/outbox-dispatcher.d.ts +162 -0
  52. package/dist/editable-artifact-live/ports.d.ts +100 -0
  53. package/dist/editable-artifact-live/server.d.ts +71 -0
  54. package/dist/editable-artifact-live/ticket.d.ts +40 -0
  55. package/dist/editable-artifact-live/types.d.ts +213 -0
  56. package/dist/editable-artifact-live/wire.d.ts +34 -0
  57. package/dist/editable-artifact-live.d.ts +2 -0
  58. package/dist/editable-artifact-live.js +58 -0
  59. package/dist/editable-artifact-live.js.map +1 -0
  60. package/dist/editable-artifacts.d.ts +2 -0
  61. package/dist/editable-artifacts.js +179 -0
  62. package/dist/editable-artifacts.js.map +1 -0
  63. package/dist/index.d.ts +37 -26
  64. package/dist/index.js +4145 -959
  65. package/dist/index.js.map +1 -1
  66. package/dist/managed-session.d.ts +6 -2
  67. package/dist/rigs/index.d.ts +1 -1
  68. package/dist/rigs/provider-images.d.ts +31 -0
  69. package/dist/sandbox/fleet.d.ts +8 -5
  70. package/dist/sandbox/routing.d.ts +1 -0
  71. package/dist/session-authorization.d.ts +4 -2
  72. package/dist/transcription.d.ts +5 -0
  73. package/dist/workflow-wake-contract.d.ts +7 -0
  74. package/package.json +22 -10
  75. package/src/access/index.ts +14 -2
  76. package/src/application/new-session-drafts.ts +2 -0
  77. package/src/application/session-commands.ts +188 -107
  78. package/src/canonical-human-identities.ts +27 -0
  79. package/src/dependencies.ts +34 -2
  80. package/src/domain/capabilities.ts +729 -408
  81. package/src/domain/company-profile-durable-learning-adapter.ts +221 -0
  82. package/src/domain/conversation-integrations.ts +1238 -0
  83. package/src/domain/durable-learning-slack-publication.ts +202 -0
  84. package/src/domain/editable-artifacts/agent-application.ts +687 -0
  85. package/src/domain/editable-artifacts/durable-export-postgres.ts +139 -0
  86. package/src/domain/editable-artifacts/durable-export.ts +734 -0
  87. package/src/domain/editable-artifacts/errors.ts +115 -0
  88. package/src/domain/editable-artifacts/hash.ts +131 -0
  89. package/src/domain/editable-artifacts/in-memory.ts +1662 -0
  90. package/src/domain/editable-artifacts/index.ts +14 -0
  91. package/src/domain/editable-artifacts/ports.ts +462 -0
  92. package/src/domain/editable-artifacts/postgres-adapters.ts +49 -0
  93. package/src/domain/editable-artifacts/service.ts +2402 -0
  94. package/src/domain/editable-artifacts/snapshot-compaction.ts +246 -0
  95. package/src/domain/editable-artifacts/snapshot-verifier-genesis.ts +235 -0
  96. package/src/domain/editable-artifacts/snapshot-verifier-materialization.ts +74 -0
  97. package/src/domain/editable-artifacts/snapshot-verifier.ts +687 -0
  98. package/src/domain/editable-artifacts/types.ts +835 -0
  99. package/src/domain/fiken.ts +88 -0
  100. package/src/domain/insights.ts +163 -50
  101. package/src/domain/memory-slack-delivery.ts +204 -0
  102. package/src/domain/memory-slack-publication.ts +5 -2
  103. package/src/domain/packs.ts +266 -5
  104. package/src/domain/personal-connection-delegations.ts +97 -6
  105. package/src/domain/scheduled-tasks.ts +171 -29
  106. package/src/domain/session-tool-policy.ts +8 -2
  107. package/src/domain/sessions.ts +395 -148
  108. package/src/domain/skill-imports.ts +350 -0
  109. package/src/domain/slack-publication-secret-safety.ts +42 -0
  110. package/src/domain/timeline-annotations.ts +204 -0
  111. package/src/domain/video-generation-capabilities.ts +104 -0
  112. package/src/domain/video-generation.ts +249 -0
  113. package/src/editable-artifact-live/application.ts +182 -0
  114. package/src/editable-artifact-live/errors.ts +4 -0
  115. package/src/editable-artifact-live/index.ts +8 -0
  116. package/src/editable-artifact-live/outbox-dispatcher.ts +916 -0
  117. package/src/editable-artifact-live/ports.ts +139 -0
  118. package/src/editable-artifact-live/server.ts +1707 -0
  119. package/src/editable-artifact-live/ticket.ts +217 -0
  120. package/src/editable-artifact-live/types.ts +286 -0
  121. package/src/editable-artifact-live/wire.ts +95 -0
  122. package/src/editable-artifact-live.ts +2 -0
  123. package/src/editable-artifacts.ts +2 -0
  124. package/src/index.ts +15 -0
  125. package/src/managed-session.ts +19 -2
  126. package/src/rigs/index.ts +26 -19
  127. package/src/rigs/provider-images.ts +110 -0
  128. package/src/sandbox/fleet.ts +72 -66
  129. package/src/sandbox/routing.ts +6 -3
  130. package/src/session-authorization.ts +187 -52
  131. package/src/transcription.ts +5 -0
  132. package/src/workflow-wake-contract.ts +8 -0
@@ -4,6 +4,7 @@ import {
4
4
  type KnowledgeMemoryKind,
5
5
  type KnowledgeMemoryStatus,
6
6
  } from "@opengeni/contracts";
7
+ import { sanitizeSlackPublicationText } from "./slack-publication-secret-safety";
7
8
 
8
9
  const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
9
10
  const SELECTOR_SEGMENT_PATTERN = /^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/;
@@ -197,7 +198,9 @@ export function evaluateMemorySlackPublication(
197
198
  const deliveryMode = effectiveDeliveryMode(policy, input.distribution);
198
199
  if (!deliveryMode) return denied("below_noise_policy");
199
200
 
200
- const collapsedSummary = collapseText(input.distribution.shareSummary);
201
+ const collapsedSummary = sanitizeSlackPublicationText(
202
+ collapseText(input.distribution.shareSummary),
203
+ );
201
204
  if (!collapsedSummary) return denied("missing_summary");
202
205
  const summary = truncateUtf8(collapsedSummary, MEMORY_SLACK_SUMMARY_MAX_UTF8_BYTES);
203
206
 
@@ -397,7 +400,7 @@ function boundedOptionalText(
397
400
  value: string | null | undefined,
398
401
  maxBytes: number,
399
402
  ): { value: string | null; truncated: boolean } {
400
- const collapsed = collapseText(value ?? "");
403
+ const collapsed = sanitizeSlackPublicationText(collapseText(value ?? ""));
401
404
  if (!collapsed) return { value: null, truncated: false };
402
405
  const bounded = truncateUtf8(collapsed, maxBytes);
403
406
  return {
@@ -1,14 +1,27 @@
1
+ import { createHash } from "node:crypto";
2
+
1
3
  import {
2
4
  CapabilityPack,
5
+ stableJson,
6
+ type CapabilityPackSkill,
7
+ type PackComponentResolution,
8
+ type PackInstallationPreview,
9
+ type PackRigResolution,
3
10
  type ScheduledTaskAgentConfig,
4
11
  type SocialConnection,
5
12
  } from "@opengeni/contracts";
6
13
  import {
14
+ getPackInstallation,
15
+ getRig,
16
+ getVariableSet,
7
17
  getWorkspacePack,
8
18
  listPackInstallations,
9
19
  listWorkspacePacks,
20
+ resolvePackComponentReferences,
21
+ resolvePackInlineSkillReferences,
10
22
  type Database,
11
23
  } from "@opengeni/db";
24
+ import { buildPortableSkillArtifact } from "@opengeni/runtime/skill-library";
12
25
  import { HTTPException } from "hono/http-exception";
13
26
 
14
27
  export const MARKETING_SOCIAL_PACK_ID = "marketing-social-daily-analysis";
@@ -25,6 +38,7 @@ const marketingSocialPack: CapabilityPack = {
25
38
  // worker's pack-runtime resolution only reads manifest-registered packs
26
39
  // (see apps/worker/src/activities/packs.ts), and a test enforces this.
27
40
  skills: [],
41
+ components: [],
28
42
  tools: [
29
43
  { kind: "mcp", id: "opengeni" },
30
44
  { kind: "mcp", id: "docs" },
@@ -146,11 +160,18 @@ const marketingSocialPack: CapabilityPack = {
146
160
  "social_connections_list",
147
161
  "social_posts_recent",
148
162
  "social_daily_analysis_context",
149
- "social_search_live",
150
- "social_mentions_live",
151
- "social_thread_fetch",
152
- "social_posts_sync",
153
- "social_post_reply",
163
+ "x_accounts_list",
164
+ "x_search_live",
165
+ "x_mentions_live",
166
+ "x_thread_fetch",
167
+ "x_posts_sync",
168
+ "x_post_reply",
169
+ "reddit_accounts_list",
170
+ "reddit_search_live",
171
+ "reddit_mentions_live",
172
+ "reddit_thread_fetch",
173
+ "reddit_posts_sync",
174
+ "reddit_post_reply",
154
175
  ],
155
176
  },
156
177
  };
@@ -207,6 +228,246 @@ export async function resolveCapabilityPack(
207
228
  return parsed.success ? parsed.data : null;
208
229
  }
209
230
 
231
+ export function capabilityPackManifestDigest(pack: CapabilityPack): string {
232
+ return createHash("sha256").update(stableJson(pack)).digest("hex");
233
+ }
234
+
235
+ /** True when the Pack must use the owner-aware V2 preview/install lifecycle. */
236
+ export function capabilityPackRequiresInstallationPlan(pack: CapabilityPack): boolean {
237
+ return (
238
+ pack.components.length > 0 ||
239
+ pack.skills.length > 0 ||
240
+ pack.rig !== undefined ||
241
+ pack.sandboxImage !== undefined ||
242
+ pack.sandboxProviderImages !== undefined
243
+ );
244
+ }
245
+
246
+ export type InlinePackSkillInstall = {
247
+ componentKey: string;
248
+ capabilityId: string;
249
+ pluginKey: string;
250
+ sourceUrl: string;
251
+ repositoryUrl: string;
252
+ sourceCommit: string;
253
+ sourcePath: string;
254
+ name: string;
255
+ description: string;
256
+ contentSha256: string;
257
+ totalBytes: number;
258
+ files: Array<{ path: string; content: string; byteSize: number; contentSha256: string }>;
259
+ };
260
+
261
+ /** Convert a legacy inline Pack Skill into the ordinary immutable Skill model. */
262
+ export function inlinePackSkillInstall(
263
+ pack: CapabilityPack,
264
+ skill: CapabilityPackSkill,
265
+ ): InlinePackSkillInstall {
266
+ const artifact = buildPortableSkillArtifact(skill.files);
267
+ if (artifact.name.toLowerCase() !== skill.name.toLowerCase()) {
268
+ throw new HTTPException(422, {
269
+ message: `Pack Skill ${skill.name} has SKILL.md name ${artifact.name}; the names must match`,
270
+ });
271
+ }
272
+ const normalizedName = skill.name.toLowerCase();
273
+ const encodedSkill = encodeURIComponent(normalizedName);
274
+ const sourceUrl = `https://opengeni.invalid/pack-inline-skills/${encodedSkill}/${artifact.contentSha256}`;
275
+ const capabilityId = `skill:pack-inline/${normalizedName}@${artifact.contentSha256}`;
276
+ return {
277
+ componentKey: `inline-skill/${normalizedName}`,
278
+ capabilityId,
279
+ pluginKey: `pack-skill/${normalizedName}/${artifact.contentSha256}`,
280
+ sourceUrl,
281
+ repositoryUrl: "https://opengeni.invalid/pack-inline-skills",
282
+ sourceCommit: artifact.contentSha256,
283
+ sourcePath: normalizedName,
284
+ name: artifact.name,
285
+ description: artifact.description,
286
+ contentSha256: artifact.contentSha256,
287
+ totalBytes: artifact.totalBytes,
288
+ files: artifact.files.map((file) => ({
289
+ path: file.path,
290
+ content: file.content,
291
+ byteSize: new TextEncoder().encode(file.content).byteLength,
292
+ contentSha256: createHash("sha256").update(file.content).digest("hex"),
293
+ })),
294
+ };
295
+ }
296
+
297
+ export async function previewCapabilityPackInstallation(
298
+ db: Database,
299
+ workspaceId: string,
300
+ pack: CapabilityPack,
301
+ options: { rigId?: string; variableSetId?: string } = {},
302
+ ): Promise<PackInstallationPreview> {
303
+ const installation = await getPackInstallation(db, workspaceId, pack.id);
304
+ const inlineInstalls = pack.skills.map((skill) => inlinePackSkillInstall(pack, skill));
305
+ const [referencedComponents, inlineComponents] = await Promise.all([
306
+ resolvePackComponentReferences(db, workspaceId, pack.components),
307
+ resolvePackInlineSkillReferences(
308
+ db,
309
+ workspaceId,
310
+ inlineInstalls.map((inline) => ({
311
+ key: inline.componentKey,
312
+ capabilityId: inline.capabilityId,
313
+ name: inline.name,
314
+ contentSha256: inline.contentSha256,
315
+ })),
316
+ installation?.id,
317
+ ),
318
+ ]);
319
+ const manifestDigest = capabilityPackManifestDigest(pack);
320
+ const components: PackComponentResolution[] = [...referencedComponents, ...inlineComponents];
321
+ const blockers = components
322
+ .filter((component) => component.required && component.status !== "ready")
323
+ .map((component) =>
324
+ component.status === "missing"
325
+ ? `${component.label} is not installed in this workspace`
326
+ : `${component.label} does not match the Pack's pinned version`,
327
+ );
328
+ const rig = await resolvePackRig(
329
+ db,
330
+ workspaceId,
331
+ pack,
332
+ options.rigId,
333
+ installation?.selectedRigId,
334
+ );
335
+ if (
336
+ rig.status !== "ready" &&
337
+ rig.status !== "not_required" &&
338
+ (rig.required || rig.requestedRigId !== null || rig.rigId !== null)
339
+ ) {
340
+ blockers.push(packRigBlocker(rig, pack.sandboxImage ?? null));
341
+ }
342
+ const variableSetId =
343
+ options.variableSetId ?? storedVariableSetId(installation?.metadata) ?? null;
344
+ if (pack.variableSet?.required && !variableSetId) {
345
+ blockers.push("Choose saved configuration before installing this Pack");
346
+ } else if (variableSetId) {
347
+ const variableSet = await getVariableSet(db, workspaceId, variableSetId);
348
+ if (!variableSet) {
349
+ blockers.push("The selected configuration no longer exists in this workspace");
350
+ } else {
351
+ const missing = (pack.variableSet?.requiredVariables ?? []).filter(
352
+ (name) => !variableSet.variables.some((variable) => variable.name === name),
353
+ );
354
+ if (missing.length > 0) {
355
+ blockers.push(`Configuration is missing required value(s): ${missing.join(", ")}`);
356
+ }
357
+ }
358
+ }
359
+ const action =
360
+ !installation || installation.status === "disabled"
361
+ ? "install"
362
+ : installation.manifestDigest === manifestDigest
363
+ ? "repair"
364
+ : "update";
365
+ return {
366
+ packId: pack.id,
367
+ packVersion: pack.version,
368
+ manifestDigest,
369
+ installationVersion: installation?.version ?? null,
370
+ action,
371
+ ready: blockers.length === 0,
372
+ blockers,
373
+ components,
374
+ rig,
375
+ variableSetId,
376
+ legacyInlineSkillCount: pack.skills.length,
377
+ legacySandboxImage: pack.sandboxImage ?? null,
378
+ };
379
+ }
380
+
381
+ async function resolvePackRig(
382
+ db: Database,
383
+ workspaceId: string,
384
+ pack: CapabilityPack,
385
+ requestedRigId: string | undefined,
386
+ storedRigId: string | null | undefined,
387
+ ): Promise<PackRigResolution> {
388
+ const required = pack.rig?.required === true || Boolean(pack.sandboxImage);
389
+ const selectedRigId = pack.rig?.rigId ?? requestedRigId ?? storedRigId ?? null;
390
+ if (!required && !selectedRigId) {
391
+ return {
392
+ required: false,
393
+ status: "not_required",
394
+ requestedRigId: requestedRigId ?? null,
395
+ rigId: null,
396
+ rigVersionId: null,
397
+ name: null,
398
+ image: null,
399
+ };
400
+ }
401
+ if (pack.rig?.rigId && requestedRigId && pack.rig.rigId !== requestedRigId) {
402
+ return {
403
+ required,
404
+ status: "mismatch",
405
+ requestedRigId,
406
+ rigId: pack.rig.rigId,
407
+ rigVersionId: null,
408
+ name: null,
409
+ image: null,
410
+ };
411
+ }
412
+ if (!selectedRigId) {
413
+ return {
414
+ required,
415
+ status: "missing",
416
+ requestedRigId: requestedRigId ?? null,
417
+ rigId: null,
418
+ rigVersionId: null,
419
+ name: null,
420
+ image: null,
421
+ };
422
+ }
423
+ const rig = await getRig(db, workspaceId, selectedRigId);
424
+ if (!rig?.activeVersion) {
425
+ return {
426
+ required,
427
+ status: "missing",
428
+ requestedRigId: requestedRigId ?? null,
429
+ rigId: selectedRigId,
430
+ rigVersionId: null,
431
+ name: rig?.name ?? null,
432
+ image: null,
433
+ };
434
+ }
435
+ const image = rig.activeVersion.image ?? null;
436
+ const status =
437
+ pack.sandboxImage && image !== pack.sandboxImage
438
+ ? "mismatch"
439
+ : pack.rig?.requireVerified && rig.activeVersionHealth?.checkHealth !== "passing"
440
+ ? "unverified"
441
+ : "ready";
442
+ return {
443
+ required,
444
+ status,
445
+ requestedRigId: requestedRigId ?? null,
446
+ rigId: rig.id,
447
+ rigVersionId: rig.activeVersion.id,
448
+ name: rig.name,
449
+ image,
450
+ };
451
+ }
452
+
453
+ function packRigBlocker(rig: PackRigResolution, legacyImage: string | null): string {
454
+ if (rig.status === "missing") {
455
+ return "Choose an available compute environment before installing this Pack";
456
+ }
457
+ if (rig.status === "unverified") {
458
+ return "Verify the selected compute environment before installing this Pack";
459
+ }
460
+ if (legacyImage) {
461
+ return `The selected compute environment must use the Pack's previous image ${legacyImage}; the Pack will not replace workspace compute`;
462
+ }
463
+ return "The selected compute environment does not satisfy this Pack's requirement";
464
+ }
465
+
466
+ function storedVariableSetId(metadata: Record<string, unknown> | undefined): string | null {
467
+ const value = metadata?.variableSetId ?? metadata?.environmentId;
468
+ return typeof value === "string" && value.length > 0 ? value : null;
469
+ }
470
+
210
471
  /**
211
472
  * v1 pack-scoped runtime rule: at most one enabled pack per workspace may
212
473
  * declare a `sandboxImage` — there is deliberately no image composition or
@@ -9,6 +9,7 @@ import type {
9
9
  } from "@opengeni/contracts";
10
10
  import {
11
11
  getSessionTurnPersonalConnectionDelegations,
12
+ getConnectionMetadata,
12
13
  getSocialConnection,
13
14
  getWorkspaceGrant,
14
15
  listConnectionsMetadata,
@@ -23,6 +24,10 @@ export type PersonalConnectionDelegationSource =
23
24
  | { kind: "turn"; sessionId: string; turnId: string }
24
25
  | { kind: "none" };
25
26
  export type AuthorizedSocialConnection = { connection: SocialConnection; subjectId: string | null };
27
+ export type AuthorizedAtlassianConnection = {
28
+ connection: ConnectionMetadata;
29
+ subjectId: string | null;
30
+ };
26
31
 
27
32
  export function directPersonalConnectionSubjectId(
28
33
  turn: Pick<SessionTurn, "source" | "initiator" | "initiatorContext">,
@@ -107,6 +112,70 @@ export async function authorizedSocialConnectionsForGrant(input: {
107
112
  return [...workspace.map((connection) => ({ connection, subjectId: null })), ...personal];
108
113
  }
109
114
 
115
+ export async function authorizedAtlassianConnectionsForGrant(input: {
116
+ db: Database;
117
+ grant: AccessGrant;
118
+ }): Promise<AuthorizedAtlassianConnection[]> {
119
+ const workspace = (await listConnectionsMetadata(input.db, input.grant.workspaceId, null)).filter(
120
+ (connection) =>
121
+ connection.subjectId === null &&
122
+ connection.status === "active" &&
123
+ sameProviderDomain(connection.providerDomain, "api.atlassian.com"),
124
+ );
125
+ const source = personalConnectionDelegationSourceForGrant(input.grant);
126
+ if (source.kind === "none") {
127
+ return workspace.map((connection) => ({ connection, subjectId: null }));
128
+ }
129
+ if (source.kind === "subject") {
130
+ const visible = await listConnectionsMetadata(
131
+ input.db,
132
+ input.grant.workspaceId,
133
+ source.subjectId,
134
+ );
135
+ return visible
136
+ .filter(
137
+ (connection) =>
138
+ connection.status === "active" &&
139
+ sameProviderDomain(connection.providerDomain, "api.atlassian.com"),
140
+ )
141
+ .map((connection) => ({
142
+ connection,
143
+ subjectId: connection.subjectId === null ? null : source.subjectId,
144
+ }));
145
+ }
146
+ const delegations = (
147
+ await getSessionTurnPersonalConnectionDelegations(
148
+ input.db,
149
+ input.grant.workspaceId,
150
+ source.sessionId,
151
+ source.turnId,
152
+ )
153
+ ).filter((item) => item.connectionType === "atlassian");
154
+ const personal: AuthorizedAtlassianConnection[] = [];
155
+ for (const delegation of delegations) {
156
+ if (!(await getWorkspaceGrant(input.db, delegation.ownerSubjectId, input.grant.workspaceId))) {
157
+ continue;
158
+ }
159
+ const connection = await getConnectionMetadata(
160
+ input.db,
161
+ input.grant.workspaceId,
162
+ delegation.connectionId,
163
+ delegation.ownerSubjectId,
164
+ );
165
+ if (
166
+ !connection ||
167
+ connection.subjectId !== delegation.ownerSubjectId ||
168
+ connection.status !== "active" ||
169
+ !sameProviderDomain(connection.providerDomain, delegation.providerDomain) ||
170
+ !sameProviderDomain(connection.providerDomain, "api.atlassian.com")
171
+ ) {
172
+ continue;
173
+ }
174
+ personal.push({ connection, subjectId: delegation.ownerSubjectId });
175
+ }
176
+ return [...workspace.map((connection) => ({ connection, subjectId: null })), ...personal];
177
+ }
178
+
110
179
  export function selectedPersonalConnectionServers(
111
180
  settings: Pick<Settings, "mcpServers">,
112
181
  tools: ToolRef[],
@@ -181,7 +250,7 @@ export function personalConnectionDelegationsFromParent(input: {
181
250
  return [
182
251
  ...mcp,
183
252
  ...input.parentDelegations
184
- .filter((item) => item.connectionType === "social")
253
+ .filter((item) => item.connectionType === "social" || item.connectionType === "atlassian")
185
254
  .map((item) => ({ ...item })),
186
255
  ];
187
256
  }
@@ -241,7 +310,7 @@ function personalAuthorityUnavailable(
241
310
 
242
311
  /**
243
312
  * Resolves subject-owned MCP credentials only through the exact authority
244
- * frozen on the causal turn. A direct human subject, worker Toolspace caller,
313
+ * frozen on the causal turn. A direct human subject, worker Codemode caller,
245
314
  * retry, or recovery can identify the caller, but none may widen or replace
246
315
  * the persisted connection UUID.
247
316
  */
@@ -288,8 +357,10 @@ export async function freezePersonalConnectionDelegations(input: {
288
357
  source: PersonalConnectionDelegationSource;
289
358
  }): Promise<McpPersonalConnectionDelegation[]> {
290
359
  const servers = selectedPersonalConnectionServers(input.settings, input.tools);
291
- const includeSocial = input.tools.some((tool) => tool.id === "opengeni");
292
- if ((servers.length === 0 && !includeSocial) || input.source.kind === "none") return [];
360
+ const includeFirstPartyConnections = input.tools.some((tool) => tool.id === "opengeni");
361
+ if ((servers.length === 0 && !includeFirstPartyConnections) || input.source.kind === "none") {
362
+ return [];
363
+ }
293
364
  if (input.source.kind === "turn") {
294
365
  const inherited = personalConnectionDelegationsFromParent({
295
366
  servers,
@@ -300,7 +371,11 @@ export async function freezePersonalConnectionDelegations(input: {
300
371
  input.source.turnId,
301
372
  ),
302
373
  });
303
- return includeSocial ? inherited : inherited.filter((item) => item.connectionType !== "social");
374
+ return includeFirstPartyConnections
375
+ ? inherited
376
+ : inherited.filter(
377
+ (item) => item.connectionType !== "social" && item.connectionType !== "atlassian",
378
+ );
304
379
  }
305
380
  const membership = await getWorkspaceGrant(input.db, input.source.subjectId, input.workspaceId);
306
381
  if (!membership) return [];
@@ -309,7 +384,7 @@ export async function freezePersonalConnectionDelegations(input: {
309
384
  subjectId: input.source.subjectId,
310
385
  connections: await listConnectionsMetadata(input.db, input.workspaceId, input.source.subjectId),
311
386
  });
312
- if (!includeSocial) return mcp;
387
+ if (!includeFirstPartyConnections) return mcp;
313
388
  const ownerSubjectId = input.source.subjectId;
314
389
  const visible = await listSocialConnections(input.db, input.workspaceId, 500, ownerSubjectId);
315
390
  const latest = new Map<"x" | "reddit", (typeof visible)[number]>();
@@ -324,6 +399,14 @@ export async function freezePersonalConnectionDelegations(input: {
324
399
  if (!prior || connection.updatedAt > prior.updatedAt)
325
400
  latest.set(connection.provider, connection);
326
401
  }
402
+ const personalAtlassian = (
403
+ await listConnectionsMetadata(input.db, input.workspaceId, ownerSubjectId)
404
+ ).filter(
405
+ (connection) =>
406
+ connection.subjectId === ownerSubjectId &&
407
+ connection.status === "active" &&
408
+ sameProviderDomain(connection.providerDomain, "api.atlassian.com"),
409
+ );
327
410
  return [
328
411
  ...mcp,
329
412
  ...[...latest.values()].map((connection) => ({
@@ -334,5 +417,13 @@ export async function freezePersonalConnectionDelegations(input: {
334
417
  kind: "oauth2" as const,
335
418
  connectionType: "social" as const,
336
419
  })),
420
+ ...personalAtlassian.map((connection) => ({
421
+ serverId: `atlassian:${connection.id}`,
422
+ connectionId: connection.id,
423
+ ownerSubjectId,
424
+ providerDomain: connection.providerDomain,
425
+ kind: connection.kind,
426
+ connectionType: "atlassian" as const,
427
+ })),
337
428
  ];
338
429
  }