@opengeni/core 0.4.10 → 0.8.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/index.d.ts +103 -138
- package/dist/index.js +792 -154
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
- package/src/access/index.ts +4 -0
- package/src/application/session-commands.ts +190 -38
- package/src/billing/limits.ts +20 -1
- package/src/dependencies.ts +26 -2
- package/src/domain/capabilities.ts +171 -3
- package/src/domain/resources.ts +89 -30
- package/src/domain/sessions.ts +409 -93
- package/src/index.ts +1 -0
- package/src/sandbox/fleet.ts +2 -2
- package/src/sandbox/routing.ts +1 -1
- package/src/session-authorization.ts +208 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
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": {
|
|
@@ -34,15 +34,15 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
37
|
-
"@opengeni/codex": "^0.2.
|
|
38
|
-
"@opengeni/config": "^0.
|
|
39
|
-
"@opengeni/contracts": "^0.
|
|
40
|
-
"@opengeni/db": "^0.
|
|
41
|
-
"@opengeni/documents": "^0.2.
|
|
42
|
-
"@opengeni/events": "^0.3.
|
|
37
|
+
"@opengeni/codex": "^0.2.5",
|
|
38
|
+
"@opengeni/config": "^0.6.2",
|
|
39
|
+
"@opengeni/contracts": "^0.15.0",
|
|
40
|
+
"@opengeni/db": "^0.9.3",
|
|
41
|
+
"@opengeni/documents": "^0.2.19",
|
|
42
|
+
"@opengeni/events": "^0.3.10",
|
|
43
43
|
"@opengeni/observability": "^0.3.0",
|
|
44
|
-
"@opengeni/runtime": "^0.
|
|
45
|
-
"@opengeni/storage": "^0.2.
|
|
44
|
+
"@opengeni/runtime": "^0.11.0",
|
|
45
|
+
"@opengeni/storage": "^0.2.15",
|
|
46
46
|
"hono": "^4.12.18"
|
|
47
47
|
},
|
|
48
48
|
"engines": {
|
package/src/access/index.ts
CHANGED
|
@@ -240,6 +240,10 @@ async function delegatedAccessContext(
|
|
|
240
240
|
? { executionGeneration: payload.executionGeneration }
|
|
241
241
|
: {}),
|
|
242
242
|
},
|
|
243
|
+
...(payload.serviceInitiator ? { serviceInitiator: payload.serviceInitiator } : {}),
|
|
244
|
+
...(payload.serviceInitiatorContext
|
|
245
|
+
? { serviceInitiatorContext: payload.serviceInitiatorContext }
|
|
246
|
+
: {}),
|
|
243
247
|
},
|
|
244
248
|
],
|
|
245
249
|
defaultAccountId: payload.accountId,
|
|
@@ -4,6 +4,9 @@ import type {
|
|
|
4
4
|
EditSessionQueueItemRequest,
|
|
5
5
|
MoveSessionQueueItemRequest,
|
|
6
6
|
SaveComposerDraftRequest,
|
|
7
|
+
AccessGrant,
|
|
8
|
+
SessionAuthorizationOperation,
|
|
9
|
+
SessionAuthorizationPort,
|
|
7
10
|
SessionCommandReceipt,
|
|
8
11
|
SessionControlRequest,
|
|
9
12
|
SessionControlResponse,
|
|
@@ -24,6 +27,8 @@ import {
|
|
|
24
27
|
moveQueuedTurnInTransaction,
|
|
25
28
|
mutateSessionControlInTransaction,
|
|
26
29
|
mutateWorkspaceControlInTransaction,
|
|
30
|
+
projectEffectiveControlForRelatedAccess,
|
|
31
|
+
runIdempotentPersistenceTransaction,
|
|
27
32
|
saveComposerDraftInTransaction,
|
|
28
33
|
sendAgentMessageInTransaction,
|
|
29
34
|
serializeEffectiveSessionControl,
|
|
@@ -40,6 +45,10 @@ import {
|
|
|
40
45
|
type EventBus,
|
|
41
46
|
} from "@opengeni/events";
|
|
42
47
|
import type { SessionWorkflowClient } from "../dependencies";
|
|
48
|
+
import {
|
|
49
|
+
requireSessionAuthorization,
|
|
50
|
+
type ResolvedSessionAuthorization,
|
|
51
|
+
} from "../session-authorization";
|
|
43
52
|
|
|
44
53
|
export type HumanSessionCommandContext = {
|
|
45
54
|
accountId: string;
|
|
@@ -51,12 +60,67 @@ export type HumanSessionCommandContext = {
|
|
|
51
60
|
export type AgentSessionCommandContext = {
|
|
52
61
|
accountId: string;
|
|
53
62
|
workspaceId: string;
|
|
63
|
+
subjectId: string;
|
|
54
64
|
callerSessionId: string;
|
|
55
65
|
callerTurnId: string;
|
|
56
66
|
callerAttemptId: string;
|
|
57
67
|
callerExecutionGeneration: number;
|
|
58
68
|
};
|
|
59
69
|
|
|
70
|
+
type SessionAuthorizationCommandDeps = {
|
|
71
|
+
db: Database;
|
|
72
|
+
sessionAuthorization?: SessionAuthorizationPort | null;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
function humanAccessGrant(context: HumanSessionCommandContext): AccessGrant {
|
|
76
|
+
return {
|
|
77
|
+
accountId: context.accountId,
|
|
78
|
+
workspaceId: context.workspaceId,
|
|
79
|
+
subjectId: context.subjectId,
|
|
80
|
+
permissions: [],
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function agentAccessGrant(context: AgentSessionCommandContext): AccessGrant {
|
|
85
|
+
return {
|
|
86
|
+
accountId: context.accountId,
|
|
87
|
+
workspaceId: context.workspaceId,
|
|
88
|
+
subjectId: context.subjectId,
|
|
89
|
+
permissions: [],
|
|
90
|
+
metadata: {
|
|
91
|
+
sessionId: context.callerSessionId,
|
|
92
|
+
turnId: context.callerTurnId,
|
|
93
|
+
attemptId: context.callerAttemptId,
|
|
94
|
+
executionGeneration: context.callerExecutionGeneration,
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function authorizeHumanSessionCommand(
|
|
100
|
+
deps: SessionAuthorizationCommandDeps,
|
|
101
|
+
context: HumanSessionCommandContext,
|
|
102
|
+
operation: SessionAuthorizationOperation,
|
|
103
|
+
): Promise<ResolvedSessionAuthorization | null> {
|
|
104
|
+
return await requireSessionAuthorization(deps, humanAccessGrant(context), {
|
|
105
|
+
sessionId: context.sessionId,
|
|
106
|
+
operation,
|
|
107
|
+
surface: "core",
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function authorizeAgentSessionCommand(
|
|
112
|
+
deps: SessionAuthorizationCommandDeps,
|
|
113
|
+
context: AgentSessionCommandContext,
|
|
114
|
+
targetSessionId: string,
|
|
115
|
+
operation: SessionAuthorizationOperation,
|
|
116
|
+
): Promise<ResolvedSessionAuthorization | null> {
|
|
117
|
+
return await requireSessionAuthorization(deps, agentAccessGrant(context), {
|
|
118
|
+
sessionId: targetSessionId,
|
|
119
|
+
operation,
|
|
120
|
+
surface: "core",
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
60
124
|
function agentActor(context: AgentSessionCommandContext) {
|
|
61
125
|
return {
|
|
62
126
|
type: "agent_attempt" as const,
|
|
@@ -67,11 +131,39 @@ function agentActor(context: AgentSessionCommandContext) {
|
|
|
67
131
|
};
|
|
68
132
|
}
|
|
69
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Retry only one operation-keyed Agent command transaction. The caller keeps
|
|
136
|
+
* event publication and Temporal wake delivery after this returns, so a
|
|
137
|
+
* deadlock/serialization retry can never replay an external effect.
|
|
138
|
+
*/
|
|
139
|
+
async function runAgentCommandPersistenceTransaction<T>(
|
|
140
|
+
deps: { db: Database },
|
|
141
|
+
context: AgentSessionCommandContext,
|
|
142
|
+
input: {
|
|
143
|
+
stage: string;
|
|
144
|
+
eventTypes: string[];
|
|
145
|
+
transaction: (tx: Database) => Promise<T>;
|
|
146
|
+
},
|
|
147
|
+
): Promise<T> {
|
|
148
|
+
return await runIdempotentPersistenceTransaction(
|
|
149
|
+
{
|
|
150
|
+
stage: input.stage,
|
|
151
|
+
eventTypes: input.eventTypes,
|
|
152
|
+
maxAttempts: 3,
|
|
153
|
+
},
|
|
154
|
+
async () =>
|
|
155
|
+
await withWorkspaceRls(deps.db, context.workspaceId, async (scoped) =>
|
|
156
|
+
scoped.transaction(async (tx) => await input.transaction(tx as unknown as Database)),
|
|
157
|
+
),
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
70
161
|
async function publishAndWakeAgentCommand(
|
|
71
162
|
deps: {
|
|
72
163
|
db: Database;
|
|
73
164
|
bus: EventBus;
|
|
74
165
|
workflowClient: Pick<SessionWorkflowClient, "wakeSessionWorkflow">;
|
|
166
|
+
sessionAuthorization?: SessionAuthorizationPort | null;
|
|
75
167
|
},
|
|
76
168
|
input: {
|
|
77
169
|
accountId: string;
|
|
@@ -161,13 +253,17 @@ export async function sendAgentSessionMessage(
|
|
|
161
253
|
db: Database;
|
|
162
254
|
bus: EventBus;
|
|
163
255
|
workflowClient: Pick<SessionWorkflowClient, "wakeSessionWorkflow">;
|
|
256
|
+
sessionAuthorization?: SessionAuthorizationPort | null;
|
|
164
257
|
},
|
|
165
258
|
context: AgentSessionCommandContext,
|
|
166
259
|
input: { targetSessionId: string; text: string; idempotencyKey: string },
|
|
167
260
|
) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
261
|
+
await authorizeAgentSessionCommand(deps, context, input.targetSessionId, "session.append");
|
|
262
|
+
const result = await runAgentCommandPersistenceTransaction(deps, context, {
|
|
263
|
+
stage: "session_commands.agent_message",
|
|
264
|
+
eventTypes: ["system.update.pending"],
|
|
265
|
+
transaction: async (tx) =>
|
|
266
|
+
await sendAgentMessageInTransaction(tx, {
|
|
171
267
|
accountId: context.accountId,
|
|
172
268
|
workspaceId: context.workspaceId,
|
|
173
269
|
targetSessionId: input.targetSessionId,
|
|
@@ -175,8 +271,7 @@ export async function sendAgentSessionMessage(
|
|
|
175
271
|
operationKey: input.idempotencyKey,
|
|
176
272
|
text: input.text,
|
|
177
273
|
}),
|
|
178
|
-
|
|
179
|
-
);
|
|
274
|
+
});
|
|
180
275
|
await publishAndWakeAgentCommand(deps, {
|
|
181
276
|
accountId: context.accountId,
|
|
182
277
|
workspaceId: context.workspaceId,
|
|
@@ -196,13 +291,17 @@ export async function steerAgentSession(
|
|
|
196
291
|
db: Database;
|
|
197
292
|
bus: EventBus;
|
|
198
293
|
workflowClient: Pick<SessionWorkflowClient, "wakeSessionWorkflow">;
|
|
294
|
+
sessionAuthorization?: SessionAuthorizationPort | null;
|
|
199
295
|
},
|
|
200
296
|
context: AgentSessionCommandContext,
|
|
201
297
|
input: { targetSessionId: string; instruction: string; idempotencyKey: string },
|
|
202
298
|
) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
299
|
+
await authorizeAgentSessionCommand(deps, context, input.targetSessionId, "session.steer");
|
|
300
|
+
const result = await runAgentCommandPersistenceTransaction(deps, context, {
|
|
301
|
+
stage: "session_commands.agent_steer",
|
|
302
|
+
eventTypes: ["session.control.steer_requested", "system.update.pending", "turn.superseded"],
|
|
303
|
+
transaction: async (tx) =>
|
|
304
|
+
await steerAgentSessionInTransaction(tx, {
|
|
206
305
|
accountId: context.accountId,
|
|
207
306
|
workspaceId: context.workspaceId,
|
|
208
307
|
targetSessionId: input.targetSessionId,
|
|
@@ -210,8 +309,7 @@ export async function steerAgentSession(
|
|
|
210
309
|
operationKey: input.idempotencyKey,
|
|
211
310
|
instruction: input.instruction,
|
|
212
311
|
}),
|
|
213
|
-
|
|
214
|
-
);
|
|
312
|
+
});
|
|
215
313
|
await publishAndWakeAgentCommand(deps, {
|
|
216
314
|
accountId: context.accountId,
|
|
217
315
|
workspaceId: context.workspaceId,
|
|
@@ -231,6 +329,7 @@ export async function controlAgentSessionWorkstream(
|
|
|
231
329
|
db: Database;
|
|
232
330
|
bus: EventBus;
|
|
233
331
|
workflowClient: Pick<SessionWorkflowClient, "requestSessionWorkflowWakeDispatch">;
|
|
332
|
+
sessionAuthorization?: SessionAuthorizationPort | null;
|
|
234
333
|
},
|
|
235
334
|
context: AgentSessionCommandContext,
|
|
236
335
|
input: {
|
|
@@ -240,6 +339,7 @@ export async function controlAgentSessionWorkstream(
|
|
|
240
339
|
reason?: string | null;
|
|
241
340
|
},
|
|
242
341
|
) {
|
|
342
|
+
await authorizeAgentSessionCommand(deps, context, input.targetSessionId, "session.control");
|
|
243
343
|
const result = await withWorkspaceRls(deps.db, context.workspaceId, (scoped) =>
|
|
244
344
|
scoped.transaction((tx) =>
|
|
245
345
|
mutateSessionControlInTransaction(tx as unknown as Database, {
|
|
@@ -293,18 +393,31 @@ function composerDraft(
|
|
|
293
393
|
};
|
|
294
394
|
}
|
|
295
395
|
|
|
296
|
-
async function authoritativeQueue(
|
|
396
|
+
async function authoritativeQueue(
|
|
397
|
+
db: Database,
|
|
398
|
+
workspaceId: string,
|
|
399
|
+
sessionId: string,
|
|
400
|
+
relatedSessionAccess: "target" | "root",
|
|
401
|
+
) {
|
|
297
402
|
const snapshot = await getSessionQueueSnapshot(db, workspaceId, sessionId);
|
|
298
403
|
if (!snapshot) throw new Error(`Session not found: ${sessionId}`);
|
|
299
|
-
return
|
|
404
|
+
return {
|
|
405
|
+
...snapshot,
|
|
406
|
+
effectiveControl: projectEffectiveControlForRelatedAccess(
|
|
407
|
+
snapshot.effectiveControl,
|
|
408
|
+
sessionId,
|
|
409
|
+
relatedSessionAccess,
|
|
410
|
+
),
|
|
411
|
+
};
|
|
300
412
|
}
|
|
301
413
|
|
|
302
414
|
export async function moveHumanQueuePrompt(
|
|
303
|
-
deps: { db: Database; bus: EventBus },
|
|
415
|
+
deps: { db: Database; bus: EventBus; sessionAuthorization?: SessionAuthorizationPort | null },
|
|
304
416
|
context: HumanSessionCommandContext,
|
|
305
417
|
turnId: string,
|
|
306
418
|
input: MoveSessionQueueItemRequest,
|
|
307
419
|
): Promise<SessionQueueMutationResponse> {
|
|
420
|
+
const authorization = await authorizeHumanSessionCommand(deps, context, "session.queue.control");
|
|
308
421
|
const result = await withWorkspaceRls(deps.db, context.workspaceId, (scoped) =>
|
|
309
422
|
scoped.transaction((tx) =>
|
|
310
423
|
moveQueuedTurnInTransaction(tx as unknown as Database, {
|
|
@@ -319,18 +432,24 @@ export async function moveHumanQueuePrompt(
|
|
|
319
432
|
);
|
|
320
433
|
const response = {
|
|
321
434
|
receipt: receipt(result.receipt),
|
|
322
|
-
snapshot: await authoritativeQueue(
|
|
435
|
+
snapshot: await authoritativeQueue(
|
|
436
|
+
deps.db,
|
|
437
|
+
context.workspaceId,
|
|
438
|
+
context.sessionId,
|
|
439
|
+
authorization?.relatedSessionAccess ?? "root",
|
|
440
|
+
),
|
|
323
441
|
};
|
|
324
442
|
await publishSessionEventIds(deps, context.workspaceId, context.sessionId, result.eventIds);
|
|
325
443
|
return response;
|
|
326
444
|
}
|
|
327
445
|
|
|
328
446
|
export async function deleteHumanQueuePrompt(
|
|
329
|
-
deps: { db: Database; bus: EventBus },
|
|
447
|
+
deps: { db: Database; bus: EventBus; sessionAuthorization?: SessionAuthorizationPort | null },
|
|
330
448
|
context: HumanSessionCommandContext,
|
|
331
449
|
turnId: string,
|
|
332
450
|
input: DeleteSessionQueueItemRequest,
|
|
333
451
|
): Promise<SessionQueueMutationResponse> {
|
|
452
|
+
const authorization = await authorizeHumanSessionCommand(deps, context, "session.queue.control");
|
|
334
453
|
const result = await withWorkspaceRls(deps.db, context.workspaceId, (scoped) =>
|
|
335
454
|
scoped.transaction((tx) =>
|
|
336
455
|
deleteSessionQueueItemInTransaction(tx as unknown as Database, {
|
|
@@ -345,18 +464,24 @@ export async function deleteHumanQueuePrompt(
|
|
|
345
464
|
);
|
|
346
465
|
const response = {
|
|
347
466
|
receipt: receipt(result.receipt),
|
|
348
|
-
snapshot: await authoritativeQueue(
|
|
467
|
+
snapshot: await authoritativeQueue(
|
|
468
|
+
deps.db,
|
|
469
|
+
context.workspaceId,
|
|
470
|
+
context.sessionId,
|
|
471
|
+
authorization?.relatedSessionAccess ?? "root",
|
|
472
|
+
),
|
|
349
473
|
};
|
|
350
474
|
await publishSessionEventIds(deps, context.workspaceId, context.sessionId, result.eventIds);
|
|
351
475
|
return response;
|
|
352
476
|
}
|
|
353
477
|
|
|
354
478
|
export async function editHumanQueuePrompt(
|
|
355
|
-
deps: { db: Database; bus: EventBus },
|
|
479
|
+
deps: { db: Database; bus: EventBus; sessionAuthorization?: SessionAuthorizationPort | null },
|
|
356
480
|
context: HumanSessionCommandContext,
|
|
357
481
|
turnId: string,
|
|
358
482
|
input: EditSessionQueueItemRequest,
|
|
359
483
|
): Promise<SessionQueueMutationResponse> {
|
|
484
|
+
const authorization = await authorizeHumanSessionCommand(deps, context, "session.queue.control");
|
|
360
485
|
const result = await withWorkspaceSubjectRls(
|
|
361
486
|
deps.db,
|
|
362
487
|
context.workspaceId,
|
|
@@ -376,7 +501,12 @@ export async function editHumanQueuePrompt(
|
|
|
376
501
|
);
|
|
377
502
|
const response = {
|
|
378
503
|
receipt: receipt(result.receipt),
|
|
379
|
-
snapshot: await authoritativeQueue(
|
|
504
|
+
snapshot: await authoritativeQueue(
|
|
505
|
+
deps.db,
|
|
506
|
+
context.workspaceId,
|
|
507
|
+
context.sessionId,
|
|
508
|
+
authorization?.relatedSessionAccess ?? "root",
|
|
509
|
+
),
|
|
380
510
|
draft: composerDraft(result.draft)!,
|
|
381
511
|
};
|
|
382
512
|
await publishSessionEventIds(deps, context.workspaceId, context.sessionId, result.eventIds);
|
|
@@ -384,11 +514,12 @@ export async function editHumanQueuePrompt(
|
|
|
384
514
|
}
|
|
385
515
|
|
|
386
516
|
export async function steerHumanQueuePrompt(
|
|
387
|
-
deps: { db: Database; bus: EventBus },
|
|
517
|
+
deps: { db: Database; bus: EventBus; sessionAuthorization?: SessionAuthorizationPort | null },
|
|
388
518
|
context: HumanSessionCommandContext,
|
|
389
519
|
turnId: string,
|
|
390
520
|
input: SteerSessionQueueItemRequest,
|
|
391
521
|
): Promise<SessionQueueMutationResponse> {
|
|
522
|
+
const authorization = await authorizeHumanSessionCommand(deps, context, "session.queue.control");
|
|
392
523
|
const result = await withWorkspaceRls(deps.db, context.workspaceId, (scoped) =>
|
|
393
524
|
scoped.transaction((tx) =>
|
|
394
525
|
steerQueuedTurnInTransaction(tx as unknown as Database, {
|
|
@@ -403,7 +534,12 @@ export async function steerHumanQueuePrompt(
|
|
|
403
534
|
);
|
|
404
535
|
const response = {
|
|
405
536
|
receipt: receipt(result.receipt),
|
|
406
|
-
snapshot: await authoritativeQueue(
|
|
537
|
+
snapshot: await authoritativeQueue(
|
|
538
|
+
deps.db,
|
|
539
|
+
context.workspaceId,
|
|
540
|
+
context.sessionId,
|
|
541
|
+
authorization?.relatedSessionAccess ?? "root",
|
|
542
|
+
),
|
|
407
543
|
};
|
|
408
544
|
await publishSessionEventIds(deps, context.workspaceId, context.sessionId, result.eventIds);
|
|
409
545
|
await publishWorkspaceControlEvent(deps, context.workspaceId, result.workspaceControlEventId);
|
|
@@ -415,10 +551,12 @@ export async function controlHumanSessionWorkstream(
|
|
|
415
551
|
db: Database;
|
|
416
552
|
bus: EventBus;
|
|
417
553
|
workflowClient: Pick<SessionWorkflowClient, "requestSessionWorkflowWakeDispatch">;
|
|
554
|
+
sessionAuthorization?: SessionAuthorizationPort | null;
|
|
418
555
|
},
|
|
419
556
|
context: HumanSessionCommandContext,
|
|
420
557
|
input: SessionControlRequest,
|
|
421
558
|
): Promise<SessionControlResponse> {
|
|
559
|
+
const authorization = await authorizeHumanSessionCommand(deps, context, "session.control");
|
|
422
560
|
const result = await withWorkspaceRls(deps.db, context.workspaceId, (scoped) =>
|
|
423
561
|
scoped.transaction((tx) =>
|
|
424
562
|
mutateSessionControlInTransaction(tx as unknown as Database, {
|
|
@@ -435,7 +573,11 @@ export async function controlHumanSessionWorkstream(
|
|
|
435
573
|
);
|
|
436
574
|
const response = {
|
|
437
575
|
receipt: receipt(result.receipt),
|
|
438
|
-
effectiveControl:
|
|
576
|
+
effectiveControl: projectEffectiveControlForRelatedAccess(
|
|
577
|
+
serializeEffectiveSessionControl(result.control),
|
|
578
|
+
context.sessionId,
|
|
579
|
+
authorization?.relatedSessionAccess ?? "root",
|
|
580
|
+
),
|
|
439
581
|
interruptionCount: result.interruptionCount,
|
|
440
582
|
wakeCount: result.wakeCount,
|
|
441
583
|
};
|
|
@@ -482,19 +624,24 @@ export async function controlHumanWorkspace(
|
|
|
482
624
|
}
|
|
483
625
|
|
|
484
626
|
export async function getHumanComposerDraft(
|
|
485
|
-
|
|
627
|
+
deps: SessionAuthorizationCommandDeps,
|
|
486
628
|
context: HumanSessionCommandContext,
|
|
487
629
|
): Promise<ComposerDraft> {
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
630
|
+
await authorizeHumanSessionCommand(deps, context, "session.composer.read");
|
|
631
|
+
const row = await withWorkspaceSubjectRls(
|
|
632
|
+
deps.db,
|
|
633
|
+
context.workspaceId,
|
|
634
|
+
context.subjectId,
|
|
635
|
+
(scoped) =>
|
|
636
|
+
getComposerDraftInTransaction(scoped, {
|
|
637
|
+
workspaceId: context.workspaceId,
|
|
638
|
+
sessionId: context.sessionId,
|
|
639
|
+
subjectId: context.subjectId,
|
|
640
|
+
}),
|
|
494
641
|
);
|
|
495
642
|
const mapped = composerDraft(row);
|
|
496
643
|
if (mapped) return mapped;
|
|
497
|
-
const session = await getSession(db, context.workspaceId, context.sessionId);
|
|
644
|
+
const session = await getSession(deps.db, context.workspaceId, context.sessionId);
|
|
498
645
|
if (!session) throw new Error(`Session not found: ${context.sessionId}`);
|
|
499
646
|
return {
|
|
500
647
|
revision: 0,
|
|
@@ -510,18 +657,23 @@ export async function getHumanComposerDraft(
|
|
|
510
657
|
}
|
|
511
658
|
|
|
512
659
|
export async function saveHumanComposerDraft(
|
|
513
|
-
|
|
660
|
+
deps: SessionAuthorizationCommandDeps,
|
|
514
661
|
context: HumanSessionCommandContext,
|
|
515
662
|
input: SaveComposerDraftRequest,
|
|
516
663
|
): Promise<ComposerDraft> {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
664
|
+
await authorizeHumanSessionCommand(deps, context, "session.composer.write");
|
|
665
|
+
const row = await withWorkspaceSubjectRls(
|
|
666
|
+
deps.db,
|
|
667
|
+
context.workspaceId,
|
|
668
|
+
context.subjectId,
|
|
669
|
+
(scoped) =>
|
|
670
|
+
scoped.transaction((tx) =>
|
|
671
|
+
saveComposerDraftInTransaction(tx as unknown as Database, {
|
|
672
|
+
...context,
|
|
673
|
+
...input,
|
|
674
|
+
subjectId: context.subjectId,
|
|
675
|
+
}),
|
|
676
|
+
),
|
|
525
677
|
);
|
|
526
678
|
return composerDraft(row)!;
|
|
527
679
|
}
|
package/src/billing/limits.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { configuredStaticUsageLimits } from "@opengeni/config";
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
LimitAction,
|
|
4
|
+
LimitDecision,
|
|
5
|
+
SessionTurnSource,
|
|
6
|
+
TurnInitiator,
|
|
7
|
+
TurnInitiatorContext,
|
|
8
|
+
} from "@opengeni/contracts";
|
|
3
9
|
import {
|
|
4
10
|
countActiveApiKeysForWorkspace,
|
|
5
11
|
countScheduledTasksForWorkspace,
|
|
@@ -213,6 +219,12 @@ export async function recordWorkspaceUsage(
|
|
|
213
219
|
unit: string;
|
|
214
220
|
sourceResourceType: string;
|
|
215
221
|
sourceResourceId: string;
|
|
222
|
+
sessionId?: string | null;
|
|
223
|
+
turnId?: string | null;
|
|
224
|
+
turnAttemptId?: string | null;
|
|
225
|
+
initiator?: TurnInitiator | null;
|
|
226
|
+
initiatorContext?: TurnInitiatorContext;
|
|
227
|
+
origin?: SessionTurnSource | null;
|
|
216
228
|
idempotencyKey: string;
|
|
217
229
|
},
|
|
218
230
|
): Promise<void> {
|
|
@@ -225,6 +237,13 @@ export async function recordWorkspaceUsage(
|
|
|
225
237
|
unit: input.unit,
|
|
226
238
|
sourceResourceType: input.sourceResourceType,
|
|
227
239
|
sourceResourceId: input.sourceResourceId,
|
|
240
|
+
sessionId: input.sessionId ?? null,
|
|
241
|
+
turnId: input.turnId ?? null,
|
|
242
|
+
turnAttemptId: input.turnAttemptId ?? null,
|
|
243
|
+
initiator:
|
|
244
|
+
input.initiator ?? (input.subjectId ? { kind: "subject", subjectId: input.subjectId } : null),
|
|
245
|
+
...(input.initiatorContext ? { initiatorContext: input.initiatorContext } : {}),
|
|
246
|
+
origin: input.origin ?? null,
|
|
228
247
|
idempotencyKey: input.idempotencyKey,
|
|
229
248
|
});
|
|
230
249
|
}
|
package/src/dependencies.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { Settings } from "@opengeni/config";
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
ConnectionCredentialsPort,
|
|
4
|
+
Document,
|
|
5
|
+
GitHubAppApiPort,
|
|
6
|
+
ScheduledTask,
|
|
7
|
+
SessionAuthorizationPort,
|
|
8
|
+
} from "@opengeni/contracts";
|
|
3
9
|
import type { Database } from "@opengeni/db";
|
|
4
10
|
import type { DocumentServices } from "@opengeni/documents";
|
|
5
11
|
import type { EventBus } from "@opengeni/events";
|
|
@@ -79,6 +85,24 @@ export type AppDependencies = {
|
|
|
79
85
|
observability?: Observability;
|
|
80
86
|
readinessChecks?: Partial<Record<"db" | "nats" | "temporal", () => Promise<void> | void>>;
|
|
81
87
|
githubStateSecret?: string;
|
|
88
|
+
/**
|
|
89
|
+
* Optional host-provided GitHub App API seam. Embedded hosts can authorize
|
|
90
|
+
* users, inspect installations, and list repositories with their own GitHub
|
|
91
|
+
* App credentials; standalone deployments fall back to @opengeni/github.
|
|
92
|
+
*/
|
|
93
|
+
githubAppApi?: GitHubAppApiPort;
|
|
94
|
+
/**
|
|
95
|
+
* Optional host-owned connection credential seam. API-side consumers use
|
|
96
|
+
* the MCP leg for Toolspace/Code Mode; worker consumers bind the same port
|
|
97
|
+
* for model MCP, Git, and sandbox-secret resolution.
|
|
98
|
+
*/
|
|
99
|
+
connectionCredentials?: ConnectionCredentialsPort | null;
|
|
100
|
+
/**
|
|
101
|
+
* Optional embedding-host session ACL. Unset preserves standalone workspace
|
|
102
|
+
* authorization; once bound, every session-addressed surface fails closed on
|
|
103
|
+
* an unavailable or invalid host decision.
|
|
104
|
+
*/
|
|
105
|
+
sessionAuthorization?: SessionAuthorizationPort | null;
|
|
82
106
|
managedAuth?: ManagedAuth | null;
|
|
83
107
|
// The API process's OWN agent-loop-free sandbox client (constructed from
|
|
84
108
|
// settings via @opengeni/runtime/sandbox). Undefined when sandboxBackend=none.
|
|
@@ -118,7 +142,7 @@ export type ApiRouteDeps = AppDependencies & {
|
|
|
118
142
|
*/
|
|
119
143
|
export type AcceptSessionUserMessageDependencies = Pick<
|
|
120
144
|
AppDependencies,
|
|
121
|
-
"settings" | "db" | "bus"
|
|
145
|
+
"settings" | "db" | "bus" | "sessionAuthorization"
|
|
122
146
|
> & {
|
|
123
147
|
workflowClient: Pick<SessionWorkflowClient, "wakeSessionWorkflow">;
|
|
124
148
|
objectStorage: ObjectStorageDependency;
|