@opengeni/core 0.4.10 → 0.10.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 +165 -141
- package/dist/index.js +1132 -186
- 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 +192 -38
- package/src/billing/limits.ts +20 -1
- package/src/dependencies.ts +28 -2
- package/src/domain/capabilities.ts +171 -3
- package/src/domain/resources.ts +121 -31
- package/src/domain/scheduled-tasks.ts +4 -8
- package/src/domain/session-tool-policy.ts +211 -0
- package/src/domain/sessions.ts +658 -122
- package/src/index.ts +2 -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.10.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.7
|
|
41
|
-
"@opengeni/documents": "^0.2.
|
|
42
|
-
"@opengeni/events": "^0.3.
|
|
37
|
+
"@opengeni/codex": "^0.2.7",
|
|
38
|
+
"@opengeni/config": "^0.6.9",
|
|
39
|
+
"@opengeni/contracts": "^0.18.0",
|
|
40
|
+
"@opengeni/db": "^0.10.7",
|
|
41
|
+
"@opengeni/documents": "^0.2.28",
|
|
42
|
+
"@opengeni/events": "^0.3.19",
|
|
43
43
|
"@opengeni/observability": "^0.3.0",
|
|
44
|
-
"@opengeni/runtime": "^0.
|
|
45
|
-
"@opengeni/storage": "^0.2.
|
|
44
|
+
"@opengeni/runtime": "^0.13.0",
|
|
45
|
+
"@opengeni/storage": "^0.2.22",
|
|
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, {
|
|
@@ -285,6 +385,7 @@ function composerDraft(
|
|
|
285
385
|
text: row.text,
|
|
286
386
|
resources: row.resources as ComposerDraft["resources"],
|
|
287
387
|
tools: row.tools as ComposerDraft["tools"],
|
|
388
|
+
toolsProvided: row.toolsProvided,
|
|
288
389
|
model: row.model,
|
|
289
390
|
reasoningEffort: row.reasoningEffort as ComposerDraft["reasoningEffort"],
|
|
290
391
|
sourceTurnId: row.sourceTurnId,
|
|
@@ -293,18 +394,31 @@ function composerDraft(
|
|
|
293
394
|
};
|
|
294
395
|
}
|
|
295
396
|
|
|
296
|
-
async function authoritativeQueue(
|
|
397
|
+
async function authoritativeQueue(
|
|
398
|
+
db: Database,
|
|
399
|
+
workspaceId: string,
|
|
400
|
+
sessionId: string,
|
|
401
|
+
relatedSessionAccess: "target" | "root",
|
|
402
|
+
) {
|
|
297
403
|
const snapshot = await getSessionQueueSnapshot(db, workspaceId, sessionId);
|
|
298
404
|
if (!snapshot) throw new Error(`Session not found: ${sessionId}`);
|
|
299
|
-
return
|
|
405
|
+
return {
|
|
406
|
+
...snapshot,
|
|
407
|
+
effectiveControl: projectEffectiveControlForRelatedAccess(
|
|
408
|
+
snapshot.effectiveControl,
|
|
409
|
+
sessionId,
|
|
410
|
+
relatedSessionAccess,
|
|
411
|
+
),
|
|
412
|
+
};
|
|
300
413
|
}
|
|
301
414
|
|
|
302
415
|
export async function moveHumanQueuePrompt(
|
|
303
|
-
deps: { db: Database; bus: EventBus },
|
|
416
|
+
deps: { db: Database; bus: EventBus; sessionAuthorization?: SessionAuthorizationPort | null },
|
|
304
417
|
context: HumanSessionCommandContext,
|
|
305
418
|
turnId: string,
|
|
306
419
|
input: MoveSessionQueueItemRequest,
|
|
307
420
|
): Promise<SessionQueueMutationResponse> {
|
|
421
|
+
const authorization = await authorizeHumanSessionCommand(deps, context, "session.queue.control");
|
|
308
422
|
const result = await withWorkspaceRls(deps.db, context.workspaceId, (scoped) =>
|
|
309
423
|
scoped.transaction((tx) =>
|
|
310
424
|
moveQueuedTurnInTransaction(tx as unknown as Database, {
|
|
@@ -319,18 +433,24 @@ export async function moveHumanQueuePrompt(
|
|
|
319
433
|
);
|
|
320
434
|
const response = {
|
|
321
435
|
receipt: receipt(result.receipt),
|
|
322
|
-
snapshot: await authoritativeQueue(
|
|
436
|
+
snapshot: await authoritativeQueue(
|
|
437
|
+
deps.db,
|
|
438
|
+
context.workspaceId,
|
|
439
|
+
context.sessionId,
|
|
440
|
+
authorization?.relatedSessionAccess ?? "root",
|
|
441
|
+
),
|
|
323
442
|
};
|
|
324
443
|
await publishSessionEventIds(deps, context.workspaceId, context.sessionId, result.eventIds);
|
|
325
444
|
return response;
|
|
326
445
|
}
|
|
327
446
|
|
|
328
447
|
export async function deleteHumanQueuePrompt(
|
|
329
|
-
deps: { db: Database; bus: EventBus },
|
|
448
|
+
deps: { db: Database; bus: EventBus; sessionAuthorization?: SessionAuthorizationPort | null },
|
|
330
449
|
context: HumanSessionCommandContext,
|
|
331
450
|
turnId: string,
|
|
332
451
|
input: DeleteSessionQueueItemRequest,
|
|
333
452
|
): Promise<SessionQueueMutationResponse> {
|
|
453
|
+
const authorization = await authorizeHumanSessionCommand(deps, context, "session.queue.control");
|
|
334
454
|
const result = await withWorkspaceRls(deps.db, context.workspaceId, (scoped) =>
|
|
335
455
|
scoped.transaction((tx) =>
|
|
336
456
|
deleteSessionQueueItemInTransaction(tx as unknown as Database, {
|
|
@@ -345,18 +465,24 @@ export async function deleteHumanQueuePrompt(
|
|
|
345
465
|
);
|
|
346
466
|
const response = {
|
|
347
467
|
receipt: receipt(result.receipt),
|
|
348
|
-
snapshot: await authoritativeQueue(
|
|
468
|
+
snapshot: await authoritativeQueue(
|
|
469
|
+
deps.db,
|
|
470
|
+
context.workspaceId,
|
|
471
|
+
context.sessionId,
|
|
472
|
+
authorization?.relatedSessionAccess ?? "root",
|
|
473
|
+
),
|
|
349
474
|
};
|
|
350
475
|
await publishSessionEventIds(deps, context.workspaceId, context.sessionId, result.eventIds);
|
|
351
476
|
return response;
|
|
352
477
|
}
|
|
353
478
|
|
|
354
479
|
export async function editHumanQueuePrompt(
|
|
355
|
-
deps: { db: Database; bus: EventBus },
|
|
480
|
+
deps: { db: Database; bus: EventBus; sessionAuthorization?: SessionAuthorizationPort | null },
|
|
356
481
|
context: HumanSessionCommandContext,
|
|
357
482
|
turnId: string,
|
|
358
483
|
input: EditSessionQueueItemRequest,
|
|
359
484
|
): Promise<SessionQueueMutationResponse> {
|
|
485
|
+
const authorization = await authorizeHumanSessionCommand(deps, context, "session.queue.control");
|
|
360
486
|
const result = await withWorkspaceSubjectRls(
|
|
361
487
|
deps.db,
|
|
362
488
|
context.workspaceId,
|
|
@@ -376,7 +502,12 @@ export async function editHumanQueuePrompt(
|
|
|
376
502
|
);
|
|
377
503
|
const response = {
|
|
378
504
|
receipt: receipt(result.receipt),
|
|
379
|
-
snapshot: await authoritativeQueue(
|
|
505
|
+
snapshot: await authoritativeQueue(
|
|
506
|
+
deps.db,
|
|
507
|
+
context.workspaceId,
|
|
508
|
+
context.sessionId,
|
|
509
|
+
authorization?.relatedSessionAccess ?? "root",
|
|
510
|
+
),
|
|
380
511
|
draft: composerDraft(result.draft)!,
|
|
381
512
|
};
|
|
382
513
|
await publishSessionEventIds(deps, context.workspaceId, context.sessionId, result.eventIds);
|
|
@@ -384,11 +515,12 @@ export async function editHumanQueuePrompt(
|
|
|
384
515
|
}
|
|
385
516
|
|
|
386
517
|
export async function steerHumanQueuePrompt(
|
|
387
|
-
deps: { db: Database; bus: EventBus },
|
|
518
|
+
deps: { db: Database; bus: EventBus; sessionAuthorization?: SessionAuthorizationPort | null },
|
|
388
519
|
context: HumanSessionCommandContext,
|
|
389
520
|
turnId: string,
|
|
390
521
|
input: SteerSessionQueueItemRequest,
|
|
391
522
|
): Promise<SessionQueueMutationResponse> {
|
|
523
|
+
const authorization = await authorizeHumanSessionCommand(deps, context, "session.queue.control");
|
|
392
524
|
const result = await withWorkspaceRls(deps.db, context.workspaceId, (scoped) =>
|
|
393
525
|
scoped.transaction((tx) =>
|
|
394
526
|
steerQueuedTurnInTransaction(tx as unknown as Database, {
|
|
@@ -403,7 +535,12 @@ export async function steerHumanQueuePrompt(
|
|
|
403
535
|
);
|
|
404
536
|
const response = {
|
|
405
537
|
receipt: receipt(result.receipt),
|
|
406
|
-
snapshot: await authoritativeQueue(
|
|
538
|
+
snapshot: await authoritativeQueue(
|
|
539
|
+
deps.db,
|
|
540
|
+
context.workspaceId,
|
|
541
|
+
context.sessionId,
|
|
542
|
+
authorization?.relatedSessionAccess ?? "root",
|
|
543
|
+
),
|
|
407
544
|
};
|
|
408
545
|
await publishSessionEventIds(deps, context.workspaceId, context.sessionId, result.eventIds);
|
|
409
546
|
await publishWorkspaceControlEvent(deps, context.workspaceId, result.workspaceControlEventId);
|
|
@@ -415,10 +552,12 @@ export async function controlHumanSessionWorkstream(
|
|
|
415
552
|
db: Database;
|
|
416
553
|
bus: EventBus;
|
|
417
554
|
workflowClient: Pick<SessionWorkflowClient, "requestSessionWorkflowWakeDispatch">;
|
|
555
|
+
sessionAuthorization?: SessionAuthorizationPort | null;
|
|
418
556
|
},
|
|
419
557
|
context: HumanSessionCommandContext,
|
|
420
558
|
input: SessionControlRequest,
|
|
421
559
|
): Promise<SessionControlResponse> {
|
|
560
|
+
const authorization = await authorizeHumanSessionCommand(deps, context, "session.control");
|
|
422
561
|
const result = await withWorkspaceRls(deps.db, context.workspaceId, (scoped) =>
|
|
423
562
|
scoped.transaction((tx) =>
|
|
424
563
|
mutateSessionControlInTransaction(tx as unknown as Database, {
|
|
@@ -435,7 +574,11 @@ export async function controlHumanSessionWorkstream(
|
|
|
435
574
|
);
|
|
436
575
|
const response = {
|
|
437
576
|
receipt: receipt(result.receipt),
|
|
438
|
-
effectiveControl:
|
|
577
|
+
effectiveControl: projectEffectiveControlForRelatedAccess(
|
|
578
|
+
serializeEffectiveSessionControl(result.control),
|
|
579
|
+
context.sessionId,
|
|
580
|
+
authorization?.relatedSessionAccess ?? "root",
|
|
581
|
+
),
|
|
439
582
|
interruptionCount: result.interruptionCount,
|
|
440
583
|
wakeCount: result.wakeCount,
|
|
441
584
|
};
|
|
@@ -482,25 +625,31 @@ export async function controlHumanWorkspace(
|
|
|
482
625
|
}
|
|
483
626
|
|
|
484
627
|
export async function getHumanComposerDraft(
|
|
485
|
-
|
|
628
|
+
deps: SessionAuthorizationCommandDeps,
|
|
486
629
|
context: HumanSessionCommandContext,
|
|
487
630
|
): Promise<ComposerDraft> {
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
631
|
+
await authorizeHumanSessionCommand(deps, context, "session.composer.read");
|
|
632
|
+
const row = await withWorkspaceSubjectRls(
|
|
633
|
+
deps.db,
|
|
634
|
+
context.workspaceId,
|
|
635
|
+
context.subjectId,
|
|
636
|
+
(scoped) =>
|
|
637
|
+
getComposerDraftInTransaction(scoped, {
|
|
638
|
+
workspaceId: context.workspaceId,
|
|
639
|
+
sessionId: context.sessionId,
|
|
640
|
+
subjectId: context.subjectId,
|
|
641
|
+
}),
|
|
494
642
|
);
|
|
495
643
|
const mapped = composerDraft(row);
|
|
496
644
|
if (mapped) return mapped;
|
|
497
|
-
const session = await getSession(db, context.workspaceId, context.sessionId);
|
|
645
|
+
const session = await getSession(deps.db, context.workspaceId, context.sessionId);
|
|
498
646
|
if (!session) throw new Error(`Session not found: ${context.sessionId}`);
|
|
499
647
|
return {
|
|
500
648
|
revision: 0,
|
|
501
649
|
text: "",
|
|
502
650
|
resources: [],
|
|
503
651
|
tools: [],
|
|
652
|
+
toolsProvided: false,
|
|
504
653
|
model: session.model,
|
|
505
654
|
reasoningEffort: reasoningEffortForMetadata(session.metadata, "medium"),
|
|
506
655
|
sourceTurnId: null,
|
|
@@ -510,18 +659,23 @@ export async function getHumanComposerDraft(
|
|
|
510
659
|
}
|
|
511
660
|
|
|
512
661
|
export async function saveHumanComposerDraft(
|
|
513
|
-
|
|
662
|
+
deps: SessionAuthorizationCommandDeps,
|
|
514
663
|
context: HumanSessionCommandContext,
|
|
515
664
|
input: SaveComposerDraftRequest,
|
|
516
665
|
): Promise<ComposerDraft> {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
666
|
+
await authorizeHumanSessionCommand(deps, context, "session.composer.write");
|
|
667
|
+
const row = await withWorkspaceSubjectRls(
|
|
668
|
+
deps.db,
|
|
669
|
+
context.workspaceId,
|
|
670
|
+
context.subjectId,
|
|
671
|
+
(scoped) =>
|
|
672
|
+
scoped.transaction((tx) =>
|
|
673
|
+
saveComposerDraftInTransaction(tx as unknown as Database, {
|
|
674
|
+
...context,
|
|
675
|
+
...input,
|
|
676
|
+
subjectId: context.subjectId,
|
|
677
|
+
}),
|
|
678
|
+
),
|
|
525
679
|
);
|
|
526
680
|
return composerDraft(row)!;
|
|
527
681
|
}
|
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,7 +85,27 @@ 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;
|
|
107
|
+
/** Injectable Codex HTTP transport for deterministic API/provider tests. */
|
|
108
|
+
codexFetch?: typeof fetch;
|
|
83
109
|
// The API process's OWN agent-loop-free sandbox client (constructed from
|
|
84
110
|
// settings via @opengeni/runtime/sandbox). Undefined when sandboxBackend=none.
|
|
85
111
|
// This is the foundation of the API-direct control plane: the API resumes
|
|
@@ -118,7 +144,7 @@ export type ApiRouteDeps = AppDependencies & {
|
|
|
118
144
|
*/
|
|
119
145
|
export type AcceptSessionUserMessageDependencies = Pick<
|
|
120
146
|
AppDependencies,
|
|
121
|
-
"settings" | "db" | "bus"
|
|
147
|
+
"settings" | "db" | "bus" | "sessionAuthorization"
|
|
122
148
|
> & {
|
|
123
149
|
workflowClient: Pick<SessionWorkflowClient, "wakeSessionWorkflow">;
|
|
124
150
|
objectStorage: ObjectStorageDependency;
|