@opengeni/core 0.20.10 → 0.20.12

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/core",
3
- "version": "0.20.10",
3
+ "version": "0.20.12",
4
4
  "description": "OpenGeni framework-agnostic core: the domain, access, and billing layers (neutral access, off-HTTP V2 surface). Behavior-preserving extraction from apps/api — keeps Hono's HTTPException for error throwing (typed-errors cleanup deferred).",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -35,14 +35,14 @@
35
35
  "dependencies": {
36
36
  "@modelcontextprotocol/sdk": "^1.29.0",
37
37
  "@opengeni/codex": "^0.2.10",
38
- "@opengeni/config": "^0.10.11",
39
- "@opengeni/contracts": "^0.38.1",
40
- "@opengeni/db": "^0.27.7",
41
- "@opengeni/documents": "^0.5.3",
42
- "@opengeni/events": "^0.3.73",
43
- "@opengeni/observability": "^0.4.14",
44
- "@opengeni/runtime": "^0.18.10",
45
- "@opengeni/storage": "^0.2.64",
38
+ "@opengeni/config": "^0.10.12",
39
+ "@opengeni/contracts": "^0.38.2",
40
+ "@opengeni/db": "^0.27.9",
41
+ "@opengeni/documents": "^0.5.5",
42
+ "@opengeni/events": "^0.3.75",
43
+ "@opengeni/observability": "^0.4.15",
44
+ "@opengeni/runtime": "^0.18.11",
45
+ "@opengeni/storage": "^0.2.65",
46
46
  "hono": "^4.12.18"
47
47
  },
48
48
  "engines": {
@@ -184,6 +184,7 @@ async function publishAndWakeAgentCommand(
184
184
  wakeRevision: number | null;
185
185
  shouldSignal: boolean;
186
186
  interruptionCount: number;
187
+ controlRequested?: boolean;
187
188
  },
188
189
  ): Promise<void> {
189
190
  await publishSessionEventIds(deps, input.workspaceId, input.sessionId, input.eventIds);
@@ -195,7 +196,9 @@ async function publishAndWakeAgentCommand(
195
196
  sessionId: input.sessionId,
196
197
  workflowId: input.workflowId,
197
198
  wakeRevision: input.wakeRevision,
198
- ...(input.interruptionCount > 0 ? { interruptionRequested: true } : {}),
199
+ ...(input.controlRequested || input.interruptionCount > 0
200
+ ? { interruptionRequested: true }
201
+ : {}),
199
202
  });
200
203
  } catch (error) {
201
204
  console.warn(
@@ -329,6 +332,7 @@ export async function steerAgentSession(
329
332
  wakeRevision: result.wakeRevision,
330
333
  shouldSignal: result.shouldSignal,
331
334
  interruptionCount: result.interruptionCount,
335
+ controlRequested: true,
332
336
  });
333
337
  await publishWorkspaceControlEvent(deps, context.workspaceId, result.workspaceControlEventId);
334
338
  return result;
@@ -72,6 +72,7 @@ export async function buildCapabilityCatalog(input: {
72
72
  db: Database;
73
73
  workspaceId: string;
74
74
  settings: Settings;
75
+ subjectId?: string | null;
75
76
  }): Promise<CapabilityCatalogResponse> {
76
77
  const [
77
78
  persistedItems,
@@ -86,7 +87,7 @@ export async function buildCapabilityCatalog(input: {
86
87
  listCapabilityInstallations(input.db, input.workspaceId),
87
88
  listPackInstallations(input.db, input.workspaceId),
88
89
  listWorkspaceCapabilityPacks(input.db, input.workspaceId),
89
- listSocialConnections(input.db, input.workspaceId, 500),
90
+ listSocialConnections(input.db, input.workspaceId, 500, input.subjectId),
90
91
  discoverBundledSkills(),
91
92
  discoverCuratedSkillLibraryItems(),
92
93
  ]);
@@ -998,13 +999,13 @@ function platformApiCatalogItems(socialConnections: SocialConnection[]): Capabil
998
999
  enabled: xEnabled,
999
1000
  enabledReason: xEnabled
1000
1001
  ? xConnection.status === "connected"
1001
- ? "workspace social account connected"
1002
- : "workspace social account needs reconnection"
1002
+ ? `${xConnection.ownership} social account connected`
1003
+ : `${xConnection.ownership} social account needs reconnection`
1003
1004
  : null,
1004
1005
  metadata: {
1005
1006
  connectorMode: "first_party_social",
1006
1007
  provider: "x",
1007
- ownership: "workspace",
1008
+ ownership: xConnection?.ownership ?? "workspace",
1008
1009
  firstPartyMcpTools: [
1009
1010
  "social_connections_list",
1010
1011
  "social_posts_recent",
@@ -4,12 +4,15 @@ import type {
4
4
  ConnectionMetadata,
5
5
  McpPersonalConnectionDelegation,
6
6
  SessionTurn,
7
+ SocialConnection,
7
8
  ToolRef,
8
9
  } from "@opengeni/contracts";
9
10
  import {
10
11
  getSessionTurnPersonalConnectionDelegations,
12
+ getSocialConnection,
11
13
  getWorkspaceGrant,
12
14
  listConnectionsMetadata,
15
+ listSocialConnections,
13
16
  type Database,
14
17
  type ResolveConnectionCredentialInput,
15
18
  type ResolveConnectionCredentialResult,
@@ -19,6 +22,7 @@ export type PersonalConnectionDelegationSource =
19
22
  | { kind: "subject"; subjectId: string }
20
23
  | { kind: "turn"; sessionId: string; turnId: string }
21
24
  | { kind: "none" };
25
+ export type AuthorizedSocialConnection = { connection: SocialConnection; subjectId: string | null };
22
26
 
23
27
  export function directPersonalConnectionSubjectId(
24
28
  turn: Pick<SessionTurn, "source" | "initiator" | "initiatorContext">,
@@ -51,6 +55,58 @@ export function personalConnectionDelegationSourceForGrant(
51
55
  return { kind: "subject", subjectId: grant.subjectId };
52
56
  }
53
57
 
58
+ export async function authorizedSocialConnectionsForGrant(input: {
59
+ db: Database;
60
+ grant: AccessGrant;
61
+ limit?: number;
62
+ }): Promise<AuthorizedSocialConnection[]> {
63
+ const workspace = await listSocialConnections(
64
+ input.db,
65
+ input.grant.workspaceId,
66
+ input.limit ?? 500,
67
+ null,
68
+ );
69
+ const source = personalConnectionDelegationSourceForGrant(input.grant);
70
+ if (source.kind === "none")
71
+ return workspace.map((connection) => ({ connection, subjectId: null }));
72
+ if (source.kind === "subject") {
73
+ const visible = await listSocialConnections(
74
+ input.db,
75
+ input.grant.workspaceId,
76
+ input.limit ?? 500,
77
+ source.subjectId,
78
+ );
79
+ return visible.map((connection) => ({
80
+ connection,
81
+ subjectId: connection.ownership === "personal" ? source.subjectId : null,
82
+ }));
83
+ }
84
+ const delegations = (
85
+ await getSessionTurnPersonalConnectionDelegations(
86
+ input.db,
87
+ input.grant.workspaceId,
88
+ source.sessionId,
89
+ source.turnId,
90
+ )
91
+ ).filter((item) => item.connectionType === "social");
92
+ const personal: AuthorizedSocialConnection[] = [];
93
+ for (const delegation of delegations) {
94
+ if (!(await getWorkspaceGrant(input.db, delegation.ownerSubjectId, input.grant.workspaceId)))
95
+ continue;
96
+ const connection = await getSocialConnection(
97
+ input.db,
98
+ input.grant.workspaceId,
99
+ delegation.connectionId,
100
+ delegation.ownerSubjectId,
101
+ );
102
+ if (!connection || connection.ownership !== "personal") continue;
103
+ const domain = connection.provider === "x" ? "x.com" : `${connection.provider}.com`;
104
+ if (!sameProviderDomain(domain, delegation.providerDomain)) continue;
105
+ personal.push({ connection, subjectId: delegation.ownerSubjectId });
106
+ }
107
+ return [...workspace.map((connection) => ({ connection, subjectId: null })), ...personal];
108
+ }
109
+
54
110
  export function selectedPersonalConnectionServers(
55
111
  settings: Pick<Settings, "mcpServers">,
56
112
  tools: ToolRef[],
@@ -111,7 +167,7 @@ export function personalConnectionDelegationsFromParent(input: {
111
167
  servers: McpServerConfig[];
112
168
  parentDelegations: McpPersonalConnectionDelegation[];
113
169
  }): McpPersonalConnectionDelegation[] {
114
- return input.servers.flatMap((server) => {
170
+ const mcp = input.servers.flatMap((server) => {
115
171
  const ref = server.connectionRef;
116
172
  if (!ref || ref.subjectScope !== "subject") return [];
117
173
  const delegation = input.parentDelegations.find(
@@ -122,6 +178,12 @@ export function personalConnectionDelegationsFromParent(input: {
122
178
  );
123
179
  return delegation ? [{ ...delegation }] : [];
124
180
  });
181
+ return [
182
+ ...mcp,
183
+ ...input.parentDelegations
184
+ .filter((item) => item.connectionType === "social")
185
+ .map((item) => ({ ...item })),
186
+ ];
125
187
  }
126
188
 
127
189
  export function personalConnectionDelegationsEqual(
@@ -136,7 +198,8 @@ export function personalConnectionDelegationsEqual(
136
198
  other?.connectionId === delegation.connectionId &&
137
199
  other.ownerSubjectId === delegation.ownerSubjectId &&
138
200
  sameProviderDomain(other.providerDomain, delegation.providerDomain) &&
139
- other.kind === delegation.kind
201
+ other.kind === delegation.kind &&
202
+ other.connectionType === delegation.connectionType
140
203
  );
141
204
  });
142
205
  }
@@ -225,9 +288,10 @@ export async function freezePersonalConnectionDelegations(input: {
225
288
  source: PersonalConnectionDelegationSource;
226
289
  }): Promise<McpPersonalConnectionDelegation[]> {
227
290
  const servers = selectedPersonalConnectionServers(input.settings, input.tools);
228
- if (servers.length === 0 || input.source.kind === "none") return [];
291
+ const includeSocial = input.tools.some((tool) => tool.id === "opengeni");
292
+ if ((servers.length === 0 && !includeSocial) || input.source.kind === "none") return [];
229
293
  if (input.source.kind === "turn") {
230
- return personalConnectionDelegationsFromParent({
294
+ const inherited = personalConnectionDelegationsFromParent({
231
295
  servers,
232
296
  parentDelegations: await getSessionTurnPersonalConnectionDelegations(
233
297
  input.db,
@@ -236,12 +300,39 @@ export async function freezePersonalConnectionDelegations(input: {
236
300
  input.source.turnId,
237
301
  ),
238
302
  });
303
+ return includeSocial ? inherited : inherited.filter((item) => item.connectionType !== "social");
239
304
  }
240
305
  const membership = await getWorkspaceGrant(input.db, input.source.subjectId, input.workspaceId);
241
306
  if (!membership) return [];
242
- return personalConnectionDelegationsFromVisibleConnections({
307
+ const mcp = personalConnectionDelegationsFromVisibleConnections({
243
308
  servers,
244
309
  subjectId: input.source.subjectId,
245
310
  connections: await listConnectionsMetadata(input.db, input.workspaceId, input.source.subjectId),
246
311
  });
312
+ if (!includeSocial) return mcp;
313
+ const ownerSubjectId = input.source.subjectId;
314
+ const visible = await listSocialConnections(input.db, input.workspaceId, 500, ownerSubjectId);
315
+ const latest = new Map<"x" | "reddit", (typeof visible)[number]>();
316
+ for (const connection of visible) {
317
+ if (
318
+ connection.ownership !== "personal" ||
319
+ connection.status !== "connected" ||
320
+ (connection.provider !== "x" && connection.provider !== "reddit")
321
+ )
322
+ continue;
323
+ const prior = latest.get(connection.provider);
324
+ if (!prior || connection.updatedAt > prior.updatedAt)
325
+ latest.set(connection.provider, connection);
326
+ }
327
+ return [
328
+ ...mcp,
329
+ ...[...latest.values()].map((connection) => ({
330
+ serverId: `social:${connection.provider}`,
331
+ connectionId: connection.id,
332
+ ownerSubjectId,
333
+ providerDomain: connection.provider === "x" ? "x.com" : `${connection.provider}.com`,
334
+ kind: "oauth2" as const,
335
+ connectionType: "social" as const,
336
+ })),
337
+ ];
247
338
  }
@@ -113,7 +113,7 @@ export async function createValidatedScheduledTask(input: {
113
113
  db: input.db,
114
114
  workspaceId: input.grant.workspaceId,
115
115
  settings: runtimeSettings,
116
- tools: agentConfig.tools,
116
+ tools: [...agentConfig.tools, { kind: "mcp", id: "opengeni" }],
117
117
  source: personalConnectionDelegationSourceForGrant(input.grant),
118
118
  });
119
119
  const creationInitiator = creationInitiatorForGrant(input.grant);
@@ -260,7 +260,7 @@ export async function validatedScheduledTaskUpdate(input: {
260
260
  db: input.db,
261
261
  workspaceId: input.existing.workspaceId,
262
262
  settings: runtimeSettings,
263
- tools: nextAgentConfig.tools,
263
+ tools: [...nextAgentConfig.tools, { kind: "mcp", id: "opengeni" }],
264
264
  source: personalConnectionDelegationSourceForGrant(input.grant),
265
265
  });
266
266
  if (input.existing.reusableSessionId && input.existing.runMode === "reusable_session") {
@@ -1099,7 +1099,9 @@ export async function postUserMessageTurn(input: {
1099
1099
  sessionId,
1100
1100
  workflowId: turn.temporalWorkflowId,
1101
1101
  wakeRevision: result.wakeRevision,
1102
- ...(result.interruptionCount > 0 ? { interruptionRequested: true } : {}),
1102
+ ...((input.delivery ?? "send") === "steer" || result.interruptionCount > 0
1103
+ ? { interruptionRequested: true }
1104
+ : {}),
1103
1105
  });
1104
1106
  } catch (error) {
1105
1107
  console.warn(