@opengeni/core 0.21.2 → 0.24.1
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/access/index.d.ts +9 -1
- package/dist/application/new-session-drafts.d.ts +1 -1
- package/dist/application/session-commands.d.ts +9 -4
- package/dist/billing/limits.d.ts +1 -1
- package/dist/chunk-6WKPWE5S.js +2380 -0
- package/dist/chunk-6WKPWE5S.js.map +1 -0
- package/dist/chunk-JJU6I5XD.js +2713 -0
- package/dist/chunk-JJU6I5XD.js.map +1 -0
- package/dist/chunk-NYPMQ7NO.js +2270 -0
- package/dist/chunk-NYPMQ7NO.js.map +1 -0
- package/dist/dependencies.d.ts +26 -6
- package/dist/domain/capabilities.d.ts +35 -3
- package/dist/domain/company-profile-durable-learning-adapter.d.ts +39 -0
- package/dist/domain/durable-learning-slack-publication.d.ts +72 -0
- package/dist/domain/editable-artifacts/agent-application.d.ts +192 -0
- package/dist/domain/editable-artifacts/durable-export-postgres.d.ts +4 -0
- package/dist/domain/editable-artifacts/durable-export.d.ts +212 -0
- package/dist/domain/editable-artifacts/errors.d.ts +47 -0
- package/dist/domain/editable-artifacts/hash.d.ts +11 -0
- package/dist/domain/editable-artifacts/in-memory.d.ts +99 -0
- package/dist/domain/editable-artifacts/index.d.ts +14 -0
- package/dist/domain/editable-artifacts/ports.d.ts +341 -0
- package/dist/domain/editable-artifacts/postgres-adapters.d.ts +13 -0
- package/dist/domain/editable-artifacts/service.d.ts +105 -0
- package/dist/domain/editable-artifacts/snapshot-compaction.d.ts +44 -0
- package/dist/domain/editable-artifacts/snapshot-verifier-genesis.d.ts +50 -0
- package/dist/domain/editable-artifacts/snapshot-verifier-materialization.d.ts +21 -0
- package/dist/domain/editable-artifacts/snapshot-verifier.d.ts +118 -0
- package/dist/domain/editable-artifacts/types.d.ts +410 -0
- package/dist/domain/memory-slack-delivery.d.ts +19 -0
- package/dist/domain/memory-slack-publication.d.ts +0 -2
- package/dist/domain/packs.d.ts +29 -1
- package/dist/domain/personal-connection-delegations.d.ts +9 -1
- package/dist/domain/scheduled-tasks.d.ts +27 -3
- package/dist/domain/session-tool-policy.d.ts +2 -2
- package/dist/domain/sessions.d.ts +44 -10
- package/dist/domain/skill-imports.d.ts +32 -0
- package/dist/domain/slack-publication-secret-safety.d.ts +8 -0
- package/dist/domain/timeline-annotations.d.ts +5 -0
- package/dist/domain/video-generation-capabilities.d.ts +9 -0
- package/dist/domain/video-generation.d.ts +47 -0
- package/dist/editable-artifact-live/application.d.ts +69 -0
- package/dist/editable-artifact-live/errors.d.ts +1 -0
- package/dist/editable-artifact-live/index.d.ts +8 -0
- package/dist/editable-artifact-live/outbox-dispatcher.d.ts +162 -0
- package/dist/editable-artifact-live/ports.d.ts +100 -0
- package/dist/editable-artifact-live/server.d.ts +71 -0
- package/dist/editable-artifact-live/ticket.d.ts +40 -0
- package/dist/editable-artifact-live/types.d.ts +213 -0
- package/dist/editable-artifact-live/wire.d.ts +34 -0
- package/dist/editable-artifact-live.d.ts +2 -0
- package/dist/editable-artifact-live.js +58 -0
- package/dist/editable-artifact-live.js.map +1 -0
- package/dist/editable-artifacts.d.ts +2 -0
- package/dist/editable-artifacts.js +179 -0
- package/dist/editable-artifacts.js.map +1 -0
- package/dist/index.d.ts +35 -25
- package/dist/index.js +3184 -652
- package/dist/index.js.map +1 -1
- package/dist/managed-session.d.ts +53 -0
- package/dist/rigs/index.d.ts +1 -1
- package/dist/rigs/provider-images.d.ts +31 -0
- package/dist/sandbox/fleet.d.ts +9 -1
- package/dist/sandbox/routing.d.ts +1 -0
- package/dist/session-authorization.d.ts +10 -1
- package/dist/workflow-wake-contract.d.ts +7 -0
- package/package.json +18 -10
- package/src/access/index.ts +43 -3
- package/src/application/new-session-drafts.ts +2 -0
- package/src/application/session-commands.ts +217 -116
- package/src/dependencies.ts +30 -2
- package/src/domain/capabilities.ts +672 -218
- package/src/domain/company-profile-durable-learning-adapter.ts +221 -0
- package/src/domain/durable-learning-slack-publication.ts +202 -0
- package/src/domain/editable-artifacts/agent-application.ts +687 -0
- package/src/domain/editable-artifacts/durable-export-postgres.ts +139 -0
- package/src/domain/editable-artifacts/durable-export.ts +734 -0
- package/src/domain/editable-artifacts/errors.ts +115 -0
- package/src/domain/editable-artifacts/hash.ts +131 -0
- package/src/domain/editable-artifacts/in-memory.ts +1662 -0
- package/src/domain/editable-artifacts/index.ts +14 -0
- package/src/domain/editable-artifacts/ports.ts +462 -0
- package/src/domain/editable-artifacts/postgres-adapters.ts +49 -0
- package/src/domain/editable-artifacts/service.ts +2402 -0
- package/src/domain/editable-artifacts/snapshot-compaction.ts +246 -0
- package/src/domain/editable-artifacts/snapshot-verifier-genesis.ts +235 -0
- package/src/domain/editable-artifacts/snapshot-verifier-materialization.ts +74 -0
- package/src/domain/editable-artifacts/snapshot-verifier.ts +687 -0
- package/src/domain/editable-artifacts/types.ts +835 -0
- package/src/domain/insights.ts +136 -33
- package/src/domain/memory-slack-delivery.ts +204 -0
- package/src/domain/memory-slack-publication.ts +10 -19
- package/src/domain/packs.ts +266 -5
- package/src/domain/personal-connection-delegations.ts +97 -6
- package/src/domain/resources.ts +10 -20
- package/src/domain/scheduled-tasks.ts +365 -25
- package/src/domain/session-tool-policy.ts +8 -2
- package/src/domain/sessions.ts +388 -155
- package/src/domain/skill-imports.ts +350 -0
- package/src/domain/slack-publication-secret-safety.ts +42 -0
- package/src/domain/timeline-annotations.ts +204 -0
- package/src/domain/video-generation-capabilities.ts +69 -0
- package/src/domain/video-generation.ts +249 -0
- package/src/editable-artifact-live/application.ts +182 -0
- package/src/editable-artifact-live/errors.ts +4 -0
- package/src/editable-artifact-live/index.ts +8 -0
- package/src/editable-artifact-live/outbox-dispatcher.ts +916 -0
- package/src/editable-artifact-live/ports.ts +139 -0
- package/src/editable-artifact-live/server.ts +1707 -0
- package/src/editable-artifact-live/ticket.ts +217 -0
- package/src/editable-artifact-live/types.ts +286 -0
- package/src/editable-artifact-live/wire.ts +95 -0
- package/src/editable-artifact-live.ts +2 -0
- package/src/editable-artifacts.ts +2 -0
- package/src/index.ts +14 -0
- package/src/managed-auth-type.ts +4 -3
- package/src/managed-session.ts +36 -0
- package/src/rigs/index.ts +26 -19
- package/src/rigs/provider-images.ts +110 -0
- package/src/sandbox/fleet.ts +56 -1
- package/src/sandbox/routing.ts +32 -3
- package/src/session-authorization.ts +20 -0
- package/src/workflow-wake-contract.ts +8 -0
package/src/domain/sessions.ts
CHANGED
|
@@ -35,6 +35,8 @@ import {
|
|
|
35
35
|
type SessionMcpCredentialUpdateInput,
|
|
36
36
|
type SessionMcpServerInput,
|
|
37
37
|
type SessionMcpServerMetadata,
|
|
38
|
+
type SubmittedTimelineAnnotation,
|
|
39
|
+
type TimelineAnnotation,
|
|
38
40
|
type UpdateSessionMcpApprovalPolicyResponse,
|
|
39
41
|
type UpdateSessionToolPolicyRequest,
|
|
40
42
|
type SessionAuthorizationPort,
|
|
@@ -59,7 +61,6 @@ import {
|
|
|
59
61
|
getSession,
|
|
60
62
|
SessionIdConflictError,
|
|
61
63
|
getSessionSpawnDenialByIdempotencyKey,
|
|
62
|
-
getSessionEvent,
|
|
63
64
|
getWorkspaceControlEvent,
|
|
64
65
|
getSessionLineage,
|
|
65
66
|
getSessionTurn,
|
|
@@ -71,12 +72,13 @@ import {
|
|
|
71
72
|
submitHumanPromptInTransaction,
|
|
72
73
|
appendSessionEventsWithLockedSessionUpdate,
|
|
73
74
|
updateSessionTitle as updateSessionTitleRow,
|
|
74
|
-
|
|
75
|
+
withWorkspaceSubjectSessionActivityRls,
|
|
75
76
|
type CreateSessionMcpServerInput,
|
|
76
77
|
type Database,
|
|
77
78
|
type UpdateSessionMcpServerCredentialsInput,
|
|
78
79
|
QueueCommandConflictError,
|
|
79
80
|
AgentCommandAuthorityError,
|
|
81
|
+
runIdempotentPersistenceTransaction,
|
|
80
82
|
SessionSpawnDeniedDbError,
|
|
81
83
|
SessionControlConflictError,
|
|
82
84
|
SessionToolPolicyVersionConflictError,
|
|
@@ -99,6 +101,7 @@ import type {
|
|
|
99
101
|
import { requireSessionAuthorization } from "../session-authorization";
|
|
100
102
|
import { swapActiveSandbox, type FleetContext } from "../sandbox/fleet";
|
|
101
103
|
import { settingsWithEnabledCapabilityMcpServers } from "./capabilities";
|
|
104
|
+
import { validateSubmittedTimelineAnnotations } from "./timeline-annotations";
|
|
102
105
|
import { requireVariableSetEncryption, validateVariableSetAttachment } from "./environments";
|
|
103
106
|
import {
|
|
104
107
|
freezePersonalConnectionDelegations,
|
|
@@ -241,7 +244,9 @@ export function creationInitiatorForGrant(grant: AccessGrant): FrozenCreationIni
|
|
|
241
244
|
!Number.isSafeInteger(callerExecutionGeneration) ||
|
|
242
245
|
callerExecutionGeneration < 1
|
|
243
246
|
) {
|
|
244
|
-
throw new HTTPException(403, {
|
|
247
|
+
throw new HTTPException(403, {
|
|
248
|
+
message: "caller attempt claims are incomplete",
|
|
249
|
+
});
|
|
245
250
|
}
|
|
246
251
|
const actor = {
|
|
247
252
|
type: "agent_attempt",
|
|
@@ -284,11 +289,15 @@ function normalizedSessionMcpCredentialHeaders(
|
|
|
284
289
|
const seen = new Set<string>();
|
|
285
290
|
for (const [name, value] of entries) {
|
|
286
291
|
if (!sessionMcpCredentialHeaderName.test(name)) {
|
|
287
|
-
throw new HTTPException(422, {
|
|
292
|
+
throw new HTTPException(422, {
|
|
293
|
+
message: `invalid credential header name: ${name}`,
|
|
294
|
+
});
|
|
288
295
|
}
|
|
289
296
|
const lower = name.toLowerCase();
|
|
290
297
|
if (seen.has(lower)) {
|
|
291
|
-
throw new HTTPException(422, {
|
|
298
|
+
throw new HTTPException(422, {
|
|
299
|
+
message: `duplicate credential header name: ${name}`,
|
|
300
|
+
});
|
|
292
301
|
}
|
|
293
302
|
seen.add(lower);
|
|
294
303
|
if (value.length === 0 || value.length > maxSessionMcpCredentialHeaderValueLength) {
|
|
@@ -388,11 +397,15 @@ function validateSessionMcpServersForCreate(
|
|
|
388
397
|
const metadata: SessionMcpServerMetadata[] = [];
|
|
389
398
|
for (const server of servers) {
|
|
390
399
|
if (seenIds.has(server.id)) {
|
|
391
|
-
throw new HTTPException(422, {
|
|
400
|
+
throw new HTTPException(422, {
|
|
401
|
+
message: `duplicate session MCP server id: ${server.id}`,
|
|
402
|
+
});
|
|
392
403
|
}
|
|
393
404
|
seenIds.add(server.id);
|
|
394
405
|
if (reservedSessionMcpServerIds.has(server.id) || existingIds.has(server.id)) {
|
|
395
|
-
throw new HTTPException(422, {
|
|
406
|
+
throw new HTTPException(422, {
|
|
407
|
+
message: `MCP server id already exists: ${server.id}`,
|
|
408
|
+
});
|
|
396
409
|
}
|
|
397
410
|
const headers = normalizedSessionMcpCredentialHeaders(server.headers);
|
|
398
411
|
const headersEncrypted = Object.fromEntries(
|
|
@@ -490,7 +503,9 @@ function validateSessionMcpCredentialUpdates(input: {
|
|
|
490
503
|
}
|
|
491
504
|
seenIds.add(update.id);
|
|
492
505
|
if (!knownIds.has(update.id)) {
|
|
493
|
-
throw new HTTPException(422, {
|
|
506
|
+
throw new HTTPException(422, {
|
|
507
|
+
message: `unknown session MCP server id: ${update.id}`,
|
|
508
|
+
});
|
|
494
509
|
}
|
|
495
510
|
const headers = normalizedSessionMcpCredentialHeaders(update.headers);
|
|
496
511
|
return {
|
|
@@ -506,7 +521,22 @@ function validateSessionMcpCredentialUpdates(input: {
|
|
|
506
521
|
return encryptedUpdates;
|
|
507
522
|
}
|
|
508
523
|
|
|
509
|
-
export
|
|
524
|
+
export type CreateSessionOutcome = {
|
|
525
|
+
session: CreateSessionResponse;
|
|
526
|
+
/** The committed create/start effect represented by this request. */
|
|
527
|
+
outcome: "created" | "repaired" | "replayed";
|
|
528
|
+
/** Backward-compatible replay flag for existing entity-oriented callers. */
|
|
529
|
+
replay: boolean;
|
|
530
|
+
/** True when the request created/repaired start state or committed a new wake revision. */
|
|
531
|
+
changed: boolean;
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
export type CreateSessionRequestOutcome = CreateSessionOutcome & {
|
|
535
|
+
/** Billing telemetry is recorded after the committed session start. */
|
|
536
|
+
usageRecording: "recorded" | "failed";
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
export async function createAndStartSessionWithOutcome(input: {
|
|
510
540
|
requestedSessionId?: string;
|
|
511
541
|
db: Database;
|
|
512
542
|
bus: EventBus;
|
|
@@ -587,17 +617,24 @@ export async function createAndStartSession(input: {
|
|
|
587
617
|
// target fails the create (422) — never a silent fall-back to the default box.
|
|
588
618
|
// `workingDir` (optional) is the path/cwd base the chosen machine runs under,
|
|
589
619
|
// seeded alongside the pointer through the epoch-fenced CAS.
|
|
590
|
-
seedTargetSandbox?: {
|
|
620
|
+
seedTargetSandbox?: {
|
|
621
|
+
sandboxId: string;
|
|
622
|
+
settings: Settings;
|
|
623
|
+
workingDir?: string | null;
|
|
624
|
+
} | null;
|
|
591
625
|
// Exact actor-private pre-session draft represented by this create. The
|
|
592
626
|
// initializer consumes it only after the first durable runnable unit commits.
|
|
593
|
-
consumeNewSessionDraft?: {
|
|
627
|
+
consumeNewSessionDraft?: {
|
|
628
|
+
subjectId: string;
|
|
629
|
+
expectedRevision: number;
|
|
630
|
+
} | null;
|
|
594
631
|
// A child may lower its inherited nested-agent depth limit freely; increases
|
|
595
632
|
// are authorized by the caller's workspace:admin grant and checked again by
|
|
596
633
|
// the database admission transaction.
|
|
597
634
|
maxNestedAgentDepthOverride?: number | null;
|
|
598
635
|
allowNestedAgentDepthIncrease?: boolean;
|
|
599
636
|
subjectId?: string | null;
|
|
600
|
-
}): Promise<
|
|
637
|
+
}): Promise<CreateSessionOutcome> {
|
|
601
638
|
const sessionMetadata = {
|
|
602
639
|
...input.metadata,
|
|
603
640
|
model: input.model,
|
|
@@ -647,12 +684,24 @@ export async function createAndStartSession(input: {
|
|
|
647
684
|
}
|
|
648
685
|
const { session: keyed, created } = keyedResult;
|
|
649
686
|
if (!created) {
|
|
650
|
-
|
|
687
|
+
const finished = await finishStartSession(
|
|
651
688
|
keyed.temporalWorkflowId ? { ...input, seedTargetSandbox: null } : input,
|
|
652
689
|
keyed,
|
|
653
690
|
);
|
|
691
|
+
return {
|
|
692
|
+
session: finished.session,
|
|
693
|
+
outcome: finished.changed ? "repaired" : "replayed",
|
|
694
|
+
replay: !finished.changed,
|
|
695
|
+
changed: finished.changed,
|
|
696
|
+
};
|
|
654
697
|
}
|
|
655
|
-
|
|
698
|
+
const finished = await finishStartSession(input, keyed);
|
|
699
|
+
return {
|
|
700
|
+
session: finished.session,
|
|
701
|
+
outcome: "created",
|
|
702
|
+
replay: false,
|
|
703
|
+
changed: true,
|
|
704
|
+
};
|
|
656
705
|
}
|
|
657
706
|
let session: Session;
|
|
658
707
|
try {
|
|
@@ -694,7 +743,20 @@ export async function createAndStartSession(input: {
|
|
|
694
743
|
}
|
|
695
744
|
throw error;
|
|
696
745
|
}
|
|
697
|
-
|
|
746
|
+
const finished = await finishStartSession(input, session);
|
|
747
|
+
return {
|
|
748
|
+
session: finished.session,
|
|
749
|
+
outcome: "created",
|
|
750
|
+
replay: false,
|
|
751
|
+
changed: true,
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/** Backward-compatible entity-returning create path used by existing callers. */
|
|
756
|
+
export async function createAndStartSession(
|
|
757
|
+
input: Parameters<typeof createAndStartSessionWithOutcome>[0],
|
|
758
|
+
): Promise<CreateSessionResponse> {
|
|
759
|
+
return (await createAndStartSessionWithOutcome(input)).session;
|
|
698
760
|
}
|
|
699
761
|
|
|
700
762
|
/**
|
|
@@ -727,10 +789,13 @@ async function finishStartSession(
|
|
|
727
789
|
settings: Settings;
|
|
728
790
|
workingDir?: string | null;
|
|
729
791
|
} | null;
|
|
730
|
-
consumeNewSessionDraft?: {
|
|
792
|
+
consumeNewSessionDraft?: {
|
|
793
|
+
subjectId: string;
|
|
794
|
+
expectedRevision: number;
|
|
795
|
+
} | null;
|
|
731
796
|
},
|
|
732
797
|
session: Session,
|
|
733
|
-
): Promise<CreateSessionResponse> {
|
|
798
|
+
): Promise<{ session: CreateSessionResponse; changed: boolean }> {
|
|
734
799
|
// Create-time machine targeting (A-2a): seed the active-sandbox pointer BEFORE
|
|
735
800
|
// the atomic initial turn transaction, so the FIRST turn routes to the chosen
|
|
736
801
|
// machine. swapActiveSandbox does
|
|
@@ -750,7 +815,11 @@ async function finishStartSession(
|
|
|
750
815
|
sessionGroupId: session.sandboxGroupId,
|
|
751
816
|
};
|
|
752
817
|
const seeded = await swapActiveSandbox(
|
|
753
|
-
{
|
|
818
|
+
{
|
|
819
|
+
db: input.db,
|
|
820
|
+
settings: input.seedTargetSandbox.settings,
|
|
821
|
+
bus: input.bus,
|
|
822
|
+
},
|
|
754
823
|
ctx,
|
|
755
824
|
input.seedTargetSandbox.sandboxId,
|
|
756
825
|
// The working dir is committed in the SAME epoch-fenced CAS that seeds the
|
|
@@ -773,7 +842,10 @@ async function finishStartSession(
|
|
|
773
842
|
createdEventPayload: {
|
|
774
843
|
toolPolicy: input.toolPolicy,
|
|
775
844
|
...(input.variableSet
|
|
776
|
-
? {
|
|
845
|
+
? {
|
|
846
|
+
variableSetId: input.variableSet.id,
|
|
847
|
+
variableSetName: input.variableSet.name,
|
|
848
|
+
}
|
|
777
849
|
: {}),
|
|
778
850
|
...(input.sessionMcpServers?.length ? { mcpServers: input.sessionMcpServers } : {}),
|
|
779
851
|
},
|
|
@@ -806,7 +878,10 @@ async function finishStartSession(
|
|
|
806
878
|
started.turn?.id ??
|
|
807
879
|
(await listSessionTurns(input.db, session.workspaceId, session.id, 1))[0]?.id ??
|
|
808
880
|
null;
|
|
809
|
-
return {
|
|
881
|
+
return {
|
|
882
|
+
session: { ...persisted, initialTurnId },
|
|
883
|
+
changed: started.changed,
|
|
884
|
+
};
|
|
810
885
|
}
|
|
811
886
|
|
|
812
887
|
export function workflowIdForSession(sessionId: string): string {
|
|
@@ -820,10 +895,10 @@ export function workflowIdForSession(sessionId: string): string {
|
|
|
820
895
|
* it cannot be resolved to a provider at run time, so we fail the request at
|
|
821
896
|
* the API edge with 422 rather than enqueuing a turn the worker can't honor.
|
|
822
897
|
*
|
|
823
|
-
* `model` is the
|
|
824
|
-
*
|
|
825
|
-
*
|
|
826
|
-
*
|
|
898
|
+
* `model` is the effective value selected by the caller's boundary. Top-level
|
|
899
|
+
* omission defaults to `settings.openaiModel`; child-session omission inherits
|
|
900
|
+
* the worker-signed calling turn before reaching this helper. Centralized here
|
|
901
|
+
* so every model-carrying choke point
|
|
827
902
|
* (create-session, user-message/turn-accept, queued-turn update, and
|
|
828
903
|
* scheduled-task agentConfig — a scheduled task is a session the worker runs
|
|
829
904
|
* later) and the MCP surfaces that share them validate identically and cannot
|
|
@@ -980,6 +1055,7 @@ export async function postUserMessageTurn(input: {
|
|
|
980
1055
|
workspaceId: string;
|
|
981
1056
|
sessionId: string;
|
|
982
1057
|
text: string;
|
|
1058
|
+
annotations?: TimelineAnnotation[];
|
|
983
1059
|
turnInstructions?: string | null;
|
|
984
1060
|
resources: ResourceRef[];
|
|
985
1061
|
model?: string | null;
|
|
@@ -997,7 +1073,14 @@ export async function postUserMessageTurn(input: {
|
|
|
997
1073
|
expectedDraftRevision?: number | null;
|
|
998
1074
|
reasoningEffortFallback?: Settings["openaiReasoningEffort"];
|
|
999
1075
|
turnExecutionPolicy: TurnExecutionPolicyV1;
|
|
1000
|
-
|
|
1076
|
+
recordAgentRunUsage?: boolean;
|
|
1077
|
+
schedulePostCommit?: (task: () => Promise<void>) => void;
|
|
1078
|
+
}): Promise<{
|
|
1079
|
+
accepted: SessionEvent;
|
|
1080
|
+
turn: SessionTurn;
|
|
1081
|
+
interruptionCount: number;
|
|
1082
|
+
replay: boolean;
|
|
1083
|
+
}> {
|
|
1001
1084
|
const { db, bus, workflowClient, settings, accountId, workspaceId, sessionId } = input;
|
|
1002
1085
|
const requestedModel = canonicalConfiguredModel(settings, input.model ?? null) ?? null;
|
|
1003
1086
|
const requestedReasoningEffort = input.reasoningEffort ?? null;
|
|
@@ -1018,35 +1101,50 @@ export async function postUserMessageTurn(input: {
|
|
|
1018
1101
|
const operationKey = input.clientEventId ?? crypto.randomUUID();
|
|
1019
1102
|
let result;
|
|
1020
1103
|
try {
|
|
1021
|
-
result = await
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1104
|
+
result = await runIdempotentPersistenceTransaction(
|
|
1105
|
+
{
|
|
1106
|
+
stage: "session.prompt.submit",
|
|
1107
|
+
eventTypes: ["user.message", "turn.queued", "session.status.changed"],
|
|
1108
|
+
maxAttempts: 3,
|
|
1109
|
+
},
|
|
1110
|
+
async () =>
|
|
1111
|
+
await withWorkspaceSubjectSessionActivityRls(
|
|
1112
|
+
db,
|
|
1025
1113
|
workspaceId,
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1114
|
+
input.actor ?? accountId,
|
|
1115
|
+
(scoped) =>
|
|
1116
|
+
submitHumanPromptInTransaction(scoped, {
|
|
1117
|
+
accountId,
|
|
1118
|
+
workspaceId,
|
|
1119
|
+
sessionId,
|
|
1120
|
+
subjectId: input.actor ?? accountId,
|
|
1121
|
+
...(input.actorLabel ? { subjectLabel: input.actorLabel } : {}),
|
|
1122
|
+
actor: input.commandActor ?? {
|
|
1123
|
+
type: "human",
|
|
1124
|
+
subjectId: input.actor ?? accountId,
|
|
1125
|
+
},
|
|
1126
|
+
operationKey,
|
|
1127
|
+
delivery: input.delivery ?? "send",
|
|
1128
|
+
controlEtag: input.controlEtag ?? null,
|
|
1129
|
+
expectedDraftRevision: input.expectedDraftRevision ?? null,
|
|
1130
|
+
text: input.text,
|
|
1131
|
+
annotations: input.annotations ?? [],
|
|
1132
|
+
turnInstructions: input.turnInstructions ?? null,
|
|
1133
|
+
resources: input.resources,
|
|
1134
|
+
model: requestedModel,
|
|
1135
|
+
reasoningEffort: requestedReasoningEffort,
|
|
1136
|
+
latencyMode: input.latencyMode ?? null,
|
|
1137
|
+
reasoningEffortFallback:
|
|
1138
|
+
input.reasoningEffortFallback ?? settings.openaiReasoningEffort,
|
|
1139
|
+
turnExecutionPolicy: input.turnExecutionPolicy,
|
|
1140
|
+
source: input.origin === "operator" ? "api" : "user",
|
|
1141
|
+
...(input.recordAgentRunUsage !== undefined
|
|
1142
|
+
? { recordAgentRunUsage: input.recordAgentRunUsage }
|
|
1143
|
+
: {}),
|
|
1144
|
+
personalConnectionDelegations: input.personalConnectionDelegations ?? [],
|
|
1145
|
+
mcpCredentialUpdates: input.mcpCredentialUpdates ?? [],
|
|
1146
|
+
}),
|
|
1147
|
+
),
|
|
1050
1148
|
);
|
|
1051
1149
|
} catch (error) {
|
|
1052
1150
|
if (
|
|
@@ -1063,53 +1161,68 @@ export async function postUserMessageTurn(input: {
|
|
|
1063
1161
|
}
|
|
1064
1162
|
throw error;
|
|
1065
1163
|
}
|
|
1066
|
-
const
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
if (!controlEvent) {
|
|
1089
|
-
throw new Error(
|
|
1090
|
-
`Committed workspace control event disappeared: ${result.workspaceControlEventId}`,
|
|
1091
|
-
);
|
|
1164
|
+
const postCommitTask = async () => {
|
|
1165
|
+
try {
|
|
1166
|
+
await publishDurableSessionEvents(bus, workspaceId, sessionId, result.events);
|
|
1167
|
+
if (result.workspaceControlEventId) {
|
|
1168
|
+
const controlEvent = await getWorkspaceControlEvent(
|
|
1169
|
+
db,
|
|
1170
|
+
workspaceId,
|
|
1171
|
+
result.workspaceControlEventId,
|
|
1172
|
+
);
|
|
1173
|
+
if (!controlEvent) {
|
|
1174
|
+
throw new Error(
|
|
1175
|
+
`Committed workspace control event disappeared: ${result.workspaceControlEventId}`,
|
|
1176
|
+
);
|
|
1177
|
+
}
|
|
1178
|
+
await publishDurableWorkspaceControlEvent(bus, workspaceId, controlEvent);
|
|
1179
|
+
}
|
|
1180
|
+
} catch {
|
|
1181
|
+
console.warn("[sessions] prompt event fanout failed; durable rows remain replayable", {
|
|
1182
|
+
errorClass: "PromptEventFanoutOperationError",
|
|
1183
|
+
errorCode: "session_prompt_event_fanout_failed",
|
|
1184
|
+
origin: "core",
|
|
1185
|
+
});
|
|
1092
1186
|
}
|
|
1093
|
-
|
|
1094
|
-
|
|
1187
|
+
try {
|
|
1188
|
+
await workflowClient.wakeSessionWorkflow({
|
|
1189
|
+
accountId,
|
|
1190
|
+
workspaceId,
|
|
1191
|
+
sessionId,
|
|
1192
|
+
workflowId: result.turn.temporalWorkflowId,
|
|
1193
|
+
wakeRevision: result.wakeRevision,
|
|
1194
|
+
...((input.delivery ?? "send") === "steer" || result.interruptionCount > 0
|
|
1195
|
+
? { interruptionRequested: true }
|
|
1196
|
+
: {}),
|
|
1197
|
+
});
|
|
1198
|
+
} catch {
|
|
1199
|
+
console.warn("[sessions] workflow wake failed; durable outbox will retry", {
|
|
1200
|
+
errorClass: "WorkflowWakeOperationError",
|
|
1201
|
+
errorCode: "session_workflow_wake_failed",
|
|
1202
|
+
origin: "core",
|
|
1203
|
+
});
|
|
1204
|
+
}
|
|
1205
|
+
};
|
|
1206
|
+
const schedulePostCommit =
|
|
1207
|
+
input.schedulePostCommit ??
|
|
1208
|
+
((task: () => Promise<void>) => {
|
|
1209
|
+
void task();
|
|
1210
|
+
});
|
|
1095
1211
|
try {
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
...((input.delivery ?? "send") === "steer" || result.interruptionCount > 0
|
|
1103
|
-
? { interruptionRequested: true }
|
|
1104
|
-
: {}),
|
|
1212
|
+
schedulePostCommit(postCommitTask);
|
|
1213
|
+
} catch {
|
|
1214
|
+
console.warn("[sessions] prompt post-commit scheduling failed; durable recovery remains", {
|
|
1215
|
+
errorClass: "PromptPostCommitScheduleError",
|
|
1216
|
+
errorCode: "session_prompt_post_commit_schedule_failed",
|
|
1217
|
+
origin: "core",
|
|
1105
1218
|
});
|
|
1106
|
-
} catch (error) {
|
|
1107
|
-
console.warn(
|
|
1108
|
-
`[sessions] workflow wake failed for committed prompt ${workspaceId}/${sessionId}; durable outbox will retry`,
|
|
1109
|
-
error,
|
|
1110
|
-
);
|
|
1111
1219
|
}
|
|
1112
|
-
return {
|
|
1220
|
+
return {
|
|
1221
|
+
accepted: result.accepted,
|
|
1222
|
+
turn: result.turn,
|
|
1223
|
+
interruptionCount: result.interruptionCount,
|
|
1224
|
+
replay: result.replay,
|
|
1225
|
+
};
|
|
1113
1226
|
}
|
|
1114
1227
|
|
|
1115
1228
|
/**
|
|
@@ -1121,12 +1234,12 @@ export async function postUserMessageTurn(input: {
|
|
|
1121
1234
|
* trusted immediate parent, while explicit arrays (including []) win. A
|
|
1122
1235
|
* top-level create with omitted tools applies workspace-default capability MCPs.
|
|
1123
1236
|
*/
|
|
1124
|
-
export async function
|
|
1237
|
+
export async function createSessionForRequestWithOutcome(
|
|
1125
1238
|
deps: ApiRouteDeps,
|
|
1126
1239
|
grant: AccessGrant,
|
|
1127
1240
|
workspaceId: string,
|
|
1128
1241
|
rawPayload: unknown,
|
|
1129
|
-
): Promise<
|
|
1242
|
+
): Promise<CreateSessionRequestOutcome> {
|
|
1130
1243
|
const { settings, db, bus, workflowClient, objectStorage } = deps;
|
|
1131
1244
|
const payload = CreateSessionRequest.parse(rawPayload);
|
|
1132
1245
|
if (hasReservedOpenGeniSlackBotSessionMetadata(payload.metadata)) {
|
|
@@ -1169,10 +1282,24 @@ export async function createSessionForRequest(
|
|
|
1169
1282
|
message: `parent session not found in workspace: ${parentSessionId}`,
|
|
1170
1283
|
});
|
|
1171
1284
|
}
|
|
1285
|
+
const creationInitiator = creationInitiatorForGrant(grant);
|
|
1286
|
+
const parentCallingTurn =
|
|
1287
|
+
parentSession && creationInitiator.actor
|
|
1288
|
+
? await getSessionTurn(db, workspaceId, creationInitiator.actor.turnId)
|
|
1289
|
+
: null;
|
|
1290
|
+
if (
|
|
1291
|
+
creationInitiator.actor &&
|
|
1292
|
+
(!parentCallingTurn || parentCallingTurn.sessionId !== parentSession?.id)
|
|
1293
|
+
) {
|
|
1294
|
+
throw new HTTPException(403, {
|
|
1295
|
+
message: "caller attempt does not belong to the parent session",
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1172
1298
|
const capabilityRuntimeSettings = await settingsWithEnabledCapabilityMcpServers(
|
|
1173
1299
|
db,
|
|
1174
1300
|
workspaceId,
|
|
1175
1301
|
settings,
|
|
1302
|
+
{ subjectId: grant.subjectId },
|
|
1176
1303
|
);
|
|
1177
1304
|
const sessionMcpServers = hasOwnProperty(rawPayload, "mcpServers")
|
|
1178
1305
|
? validateSessionMcpServersForCreate(capabilityRuntimeSettings, grant, payload.mcpServers)
|
|
@@ -1219,7 +1346,10 @@ export async function createSessionForRequest(
|
|
|
1219
1346
|
"child tools may only narrow the parent session tool policy",
|
|
1220
1347
|
);
|
|
1221
1348
|
selectedTools = requestedTools;
|
|
1222
|
-
toolPolicy = {
|
|
1349
|
+
toolPolicy = {
|
|
1350
|
+
mode: "explicit",
|
|
1351
|
+
inheritedFromSessionId: parentSession.id,
|
|
1352
|
+
};
|
|
1223
1353
|
} else {
|
|
1224
1354
|
selectedTools = parentEffective;
|
|
1225
1355
|
toolPolicy = {
|
|
@@ -1252,7 +1382,9 @@ export async function createSessionForRequest(
|
|
|
1252
1382
|
});
|
|
1253
1383
|
await validateGitHubRepositorySelection(db, workspaceId, resources);
|
|
1254
1384
|
if (resources.some((resource) => resource.kind === "file") && !objectStorage) {
|
|
1255
|
-
throw new HTTPException(503, {
|
|
1385
|
+
throw new HTTPException(503, {
|
|
1386
|
+
message: "object storage is not configured",
|
|
1387
|
+
});
|
|
1256
1388
|
}
|
|
1257
1389
|
await validateFileResources(db, workspaceId, resources);
|
|
1258
1390
|
// VariableSet attachment requires variable-sets:use on the calling grant
|
|
@@ -1297,25 +1429,56 @@ export async function createSessionForRequest(
|
|
|
1297
1429
|
frozenRigVersionId = rig.activeVersion.id;
|
|
1298
1430
|
}
|
|
1299
1431
|
}
|
|
1300
|
-
|
|
1432
|
+
// A spawned worker is causally part of the exact turn that created it. Omitted
|
|
1433
|
+
// execution policy fields therefore inherit that calling turn rather than the
|
|
1434
|
+
// deployment defaults. This is especially important for Codex subscription
|
|
1435
|
+
// managers: falling back to the deployment model would silently move a child
|
|
1436
|
+
// onto the OpenGeni-credits billing path. Legacy session-bound grants without
|
|
1437
|
+
// exact attempt claims fall back to the parent session's persisted defaults.
|
|
1438
|
+
const inheritedModel = parentCallingTurn?.model ?? parentSession?.model ?? settings.openaiModel;
|
|
1439
|
+
const model = canonicalConfiguredModel(settings, payload.model ?? inheritedModel);
|
|
1301
1440
|
if (model === null || model === undefined) {
|
|
1302
1441
|
throw new Error("effective session model unexpectedly resolved to null");
|
|
1303
1442
|
}
|
|
1304
|
-
// Session creation persists the EFFECTIVE model —
|
|
1305
|
-
//
|
|
1306
|
-
// that effective value, not just explicit ones (a restricted workspace's
|
|
1307
|
-
// default-model session would otherwise be born blocked).
|
|
1443
|
+
// Session creation persists the EFFECTIVE model — the explicit selection,
|
|
1444
|
+
// inherited calling-turn model, or deployment default — so the policy must
|
|
1445
|
+
// vet that effective value, not just explicit ones (a restricted workspace's
|
|
1446
|
+
// inherited/default-model session would otherwise be born blocked).
|
|
1308
1447
|
await assertWorkspaceModelPolicyAllows(db, settings, workspaceId, model);
|
|
1309
|
-
const
|
|
1310
|
-
|
|
1448
|
+
const inheritedReasoningEffort =
|
|
1449
|
+
parentCallingTurn?.reasoningEffort ??
|
|
1450
|
+
(parentSession
|
|
1451
|
+
? reasoningEffortForSession(parentSession.metadata, settings.openaiReasoningEffort)
|
|
1452
|
+
: settings.openaiReasoningEffort);
|
|
1453
|
+
const inheritedLatencyMode =
|
|
1454
|
+
parentCallingTurn?.latencyMode ??
|
|
1455
|
+
(parentSession ? latencyModeForSession(parentSession.metadata, "standard") : "standard");
|
|
1456
|
+
const reasoningEffort = payload.reasoningEffort ?? inheritedReasoningEffort;
|
|
1457
|
+
const latencyMode = payload.latencyMode ?? inheritedLatencyMode;
|
|
1458
|
+
const inheritedFromParent = parentSession !== null;
|
|
1311
1459
|
const turnExecutionPolicy = resolveTurnExecutionPolicyV1(settings, {
|
|
1312
1460
|
modelId: model,
|
|
1313
1461
|
requestedModelId: payload.model ?? null,
|
|
1314
|
-
modelSource:
|
|
1462
|
+
modelSource:
|
|
1463
|
+
payload.model === undefined
|
|
1464
|
+
? inheritedFromParent
|
|
1465
|
+
? "continuation"
|
|
1466
|
+
: "deployment"
|
|
1467
|
+
: "explicit",
|
|
1315
1468
|
reasoningEffort,
|
|
1316
|
-
reasoningSource:
|
|
1469
|
+
reasoningSource:
|
|
1470
|
+
payload.reasoningEffort === undefined
|
|
1471
|
+
? inheritedFromParent
|
|
1472
|
+
? "continuation"
|
|
1473
|
+
: "deployment"
|
|
1474
|
+
: "explicit",
|
|
1317
1475
|
latencyMode,
|
|
1318
|
-
latencyModeSource:
|
|
1476
|
+
latencyModeSource:
|
|
1477
|
+
payload.latencyMode === undefined
|
|
1478
|
+
? inheritedFromParent
|
|
1479
|
+
? "continuation"
|
|
1480
|
+
: "deployment"
|
|
1481
|
+
: "explicit",
|
|
1319
1482
|
});
|
|
1320
1483
|
// Parent linkage was resolved above, before context validation. A child with
|
|
1321
1484
|
// no explicit permission override inherits the creating session's effective
|
|
@@ -1620,10 +1783,9 @@ export async function createSessionForRequest(
|
|
|
1620
1783
|
model,
|
|
1621
1784
|
});
|
|
1622
1785
|
}
|
|
1623
|
-
|
|
1624
|
-
let session: CreateSessionResponse;
|
|
1786
|
+
let createOutcome: CreateSessionOutcome;
|
|
1625
1787
|
try {
|
|
1626
|
-
|
|
1788
|
+
createOutcome = await createAndStartSessionWithOutcome({
|
|
1627
1789
|
...(payload.requestedSessionId ? { requestedSessionId: payload.requestedSessionId } : {}),
|
|
1628
1790
|
db,
|
|
1629
1791
|
bus,
|
|
@@ -1683,7 +1845,11 @@ export async function createSessionForRequest(
|
|
|
1683
1845
|
// (after the row exists, before the first turn dispatches). Validation
|
|
1684
1846
|
// (ownership/liveness) lives in swapActiveSandbox; an invalid target 422s.
|
|
1685
1847
|
seedTargetSandbox: payload.targetSandboxId
|
|
1686
|
-
? {
|
|
1848
|
+
? {
|
|
1849
|
+
sandboxId: payload.targetSandboxId,
|
|
1850
|
+
settings,
|
|
1851
|
+
workingDir: payload.workingDir ?? null,
|
|
1852
|
+
}
|
|
1687
1853
|
: null,
|
|
1688
1854
|
consumeNewSessionDraft:
|
|
1689
1855
|
payload.expectedNewSessionDraftRevision !== undefined
|
|
@@ -1704,24 +1870,52 @@ export async function createSessionForRequest(
|
|
|
1704
1870
|
}
|
|
1705
1871
|
throw error;
|
|
1706
1872
|
}
|
|
1873
|
+
let usageRecording: CreateSessionRequestOutcome["usageRecording"] = "recorded";
|
|
1707
1874
|
if (payload.startMode !== "realtime") {
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1875
|
+
try {
|
|
1876
|
+
await recordWorkspaceUsage(deps, {
|
|
1877
|
+
accountId: grant.accountId,
|
|
1878
|
+
workspaceId,
|
|
1879
|
+
subjectId: grant.subjectId,
|
|
1880
|
+
eventType: "agent_run.created",
|
|
1881
|
+
quantity: 1,
|
|
1882
|
+
unit: "run",
|
|
1883
|
+
sourceResourceType: "session",
|
|
1884
|
+
sourceResourceId: createOutcome.session.id,
|
|
1885
|
+
sessionId: createOutcome.session.id,
|
|
1886
|
+
initiator: createOutcome.session.createdBy,
|
|
1887
|
+
initiatorContext: createOutcome.session.createdByContext,
|
|
1888
|
+
origin: creationInitiator.actor ? "system" : "user",
|
|
1889
|
+
idempotencyKey: `agent_run.created:${workspaceId}:${createOutcome.session.id}`,
|
|
1890
|
+
});
|
|
1891
|
+
} catch (error) {
|
|
1892
|
+
usageRecording = "failed";
|
|
1893
|
+
reportSessionUsageRecordingFailure(error);
|
|
1894
|
+
}
|
|
1723
1895
|
}
|
|
1724
|
-
return
|
|
1896
|
+
return { ...createOutcome, usageRecording };
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
/** @internal Fixed public projection; the committed session outcome remains authoritative. */
|
|
1900
|
+
export function reportSessionUsageRecordingFailure(_error: unknown): void {
|
|
1901
|
+
console.warn(
|
|
1902
|
+
"[sessions] usage recording failed after committed session create; returning committed outcome",
|
|
1903
|
+
{
|
|
1904
|
+
errorClass: "UsageRecordingError",
|
|
1905
|
+
errorCode: "session_create_usage_recording_failed",
|
|
1906
|
+
origin: "core",
|
|
1907
|
+
},
|
|
1908
|
+
);
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
/** Backward-compatible entity-returning request path for REST and core callers. */
|
|
1912
|
+
export async function createSessionForRequest(
|
|
1913
|
+
deps: ApiRouteDeps,
|
|
1914
|
+
grant: AccessGrant,
|
|
1915
|
+
workspaceId: string,
|
|
1916
|
+
rawPayload: unknown,
|
|
1917
|
+
): Promise<CreateSessionResponse> {
|
|
1918
|
+
return (await createSessionForRequestWithOutcome(deps, grant, workspaceId, rawPayload)).session;
|
|
1725
1919
|
}
|
|
1726
1920
|
|
|
1727
1921
|
/**
|
|
@@ -1731,13 +1925,14 @@ export async function createSessionForRequest(
|
|
|
1731
1925
|
* enqueue, and usage recording. `toolsProvided: false` durably preserves an
|
|
1732
1926
|
* Tool selection is durable session state and never rides a follow-up prompt.
|
|
1733
1927
|
*/
|
|
1734
|
-
export async function
|
|
1928
|
+
export async function acceptSessionUserMessageWithOutcome(
|
|
1735
1929
|
deps: AcceptSessionUserMessageDependencies,
|
|
1736
1930
|
grant: AccessGrant,
|
|
1737
1931
|
workspaceId: string,
|
|
1738
1932
|
sessionId: string,
|
|
1739
1933
|
input: {
|
|
1740
1934
|
text: string;
|
|
1935
|
+
annotations?: SubmittedTimelineAnnotation[];
|
|
1741
1936
|
turnInstructions?: string | null;
|
|
1742
1937
|
resources?: ResourceRef[];
|
|
1743
1938
|
model?: string | null;
|
|
@@ -1750,7 +1945,12 @@ export async function acceptSessionUserMessage(
|
|
|
1750
1945
|
controlEtag?: string | null;
|
|
1751
1946
|
expectedDraftRevision?: number | null;
|
|
1752
1947
|
},
|
|
1753
|
-
): Promise<{
|
|
1948
|
+
): Promise<{
|
|
1949
|
+
accepted: SessionEvent;
|
|
1950
|
+
turn: SessionTurn;
|
|
1951
|
+
interruptionCount: number;
|
|
1952
|
+
replay: boolean;
|
|
1953
|
+
}> {
|
|
1754
1954
|
const { settings, db, bus, workflowClient, objectStorage } = deps;
|
|
1755
1955
|
await requireSessionAuthorization(deps, grant, {
|
|
1756
1956
|
sessionId,
|
|
@@ -1792,6 +1992,12 @@ export async function acceptSessionUserMessage(
|
|
|
1792
1992
|
latencyModeSource: input.latencyMode == null ? "session" : "explicit",
|
|
1793
1993
|
});
|
|
1794
1994
|
const requestedResources = normalizeResources(input.resources ?? []);
|
|
1995
|
+
const annotations = await validateSubmittedTimelineAnnotations(
|
|
1996
|
+
db,
|
|
1997
|
+
workspaceId,
|
|
1998
|
+
sessionId,
|
|
1999
|
+
input.annotations ?? [],
|
|
2000
|
+
);
|
|
1795
2001
|
await requireLimit(deps, {
|
|
1796
2002
|
accountId: grant.accountId,
|
|
1797
2003
|
workspaceId,
|
|
@@ -1800,7 +2006,9 @@ export async function acceptSessionUserMessage(
|
|
|
1800
2006
|
model: effectiveModel,
|
|
1801
2007
|
});
|
|
1802
2008
|
if (requestedResources.some((resource) => resource.kind === "file") && !objectStorage) {
|
|
1803
|
-
throw new HTTPException(503, {
|
|
2009
|
+
throw new HTTPException(503, {
|
|
2010
|
+
message: "object storage is not configured",
|
|
2011
|
+
});
|
|
1804
2012
|
}
|
|
1805
2013
|
await validateFileResources(db, workspaceId, requestedResources);
|
|
1806
2014
|
await validateGitHubRepositorySelection(db, workspaceId, [
|
|
@@ -1813,7 +2021,9 @@ export async function acceptSessionUserMessage(
|
|
|
1813
2021
|
session: existingSession,
|
|
1814
2022
|
updates: input.mcpCredentialUpdates ?? [],
|
|
1815
2023
|
});
|
|
1816
|
-
const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings
|
|
2024
|
+
const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings, {
|
|
2025
|
+
subjectId: grant.subjectId,
|
|
2026
|
+
});
|
|
1817
2027
|
const personalConnectionDelegations = await freezePersonalConnectionDelegations({
|
|
1818
2028
|
db,
|
|
1819
2029
|
workspaceId,
|
|
@@ -1822,7 +2032,7 @@ export async function acceptSessionUserMessage(
|
|
|
1822
2032
|
source: personalConnectionDelegationSourceForGrant(grant),
|
|
1823
2033
|
});
|
|
1824
2034
|
const delegatedServiceInitiator = serviceInitiatorForGrant(grant);
|
|
1825
|
-
const { accepted, turn } = await postUserMessageTurn({
|
|
2035
|
+
const { accepted, turn, interruptionCount, replay } = await postUserMessageTurn({
|
|
1826
2036
|
db,
|
|
1827
2037
|
bus,
|
|
1828
2038
|
workflowClient,
|
|
@@ -1831,6 +2041,7 @@ export async function acceptSessionUserMessage(
|
|
|
1831
2041
|
workspaceId,
|
|
1832
2042
|
sessionId,
|
|
1833
2043
|
text: input.text,
|
|
2044
|
+
annotations,
|
|
1834
2045
|
turnInstructions: input.turnInstructions ?? null,
|
|
1835
2046
|
resources: requestedResources,
|
|
1836
2047
|
model: input.model ?? null,
|
|
@@ -1861,24 +2072,33 @@ export async function acceptSessionUserMessage(
|
|
|
1861
2072
|
? { expectedDraftRevision: input.expectedDraftRevision }
|
|
1862
2073
|
: {}),
|
|
1863
2074
|
...(input.clientEventId ? { clientEventId: input.clientEventId } : {}),
|
|
2075
|
+
recordAgentRunUsage: true,
|
|
2076
|
+
...(deps.schedulePromptPostCommit ? { schedulePostCommit: deps.schedulePromptPostCommit } : {}),
|
|
1864
2077
|
});
|
|
1865
|
-
|
|
1866
|
-
|
|
2078
|
+
return { accepted, turn, interruptionCount, replay };
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
/** Backward-compatible entity-returning path used by existing REST callers. */
|
|
2082
|
+
export async function acceptSessionUserMessage(
|
|
2083
|
+
deps: Parameters<typeof acceptSessionUserMessageWithOutcome>[0],
|
|
2084
|
+
grant: Parameters<typeof acceptSessionUserMessageWithOutcome>[1],
|
|
2085
|
+
workspaceId: Parameters<typeof acceptSessionUserMessageWithOutcome>[2],
|
|
2086
|
+
sessionId: Parameters<typeof acceptSessionUserMessageWithOutcome>[3],
|
|
2087
|
+
input: Parameters<typeof acceptSessionUserMessageWithOutcome>[4],
|
|
2088
|
+
): Promise<{
|
|
2089
|
+
accepted: SessionEvent;
|
|
2090
|
+
turn: SessionTurn;
|
|
2091
|
+
interruptionCount: number;
|
|
2092
|
+
replay: boolean;
|
|
2093
|
+
}> {
|
|
2094
|
+
const { accepted, turn, interruptionCount, replay } = await acceptSessionUserMessageWithOutcome(
|
|
2095
|
+
deps,
|
|
2096
|
+
grant,
|
|
1867
2097
|
workspaceId,
|
|
1868
|
-
subjectId: grant.subjectId,
|
|
1869
|
-
eventType: "agent_run.created",
|
|
1870
|
-
quantity: 1,
|
|
1871
|
-
unit: "run",
|
|
1872
|
-
sourceResourceType: "session_turn",
|
|
1873
|
-
sourceResourceId: turn.id,
|
|
1874
2098
|
sessionId,
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
origin: turn.source,
|
|
1879
|
-
idempotencyKey: `agent_run.created:${workspaceId}:${turn.id}`,
|
|
1880
|
-
});
|
|
1881
|
-
return { accepted, turn };
|
|
2099
|
+
input,
|
|
2100
|
+
);
|
|
2101
|
+
return { accepted, turn, interruptionCount, replay };
|
|
1882
2102
|
}
|
|
1883
2103
|
|
|
1884
2104
|
/**
|
|
@@ -1912,7 +2132,12 @@ export async function updateSessionTitle(
|
|
|
1912
2132
|
surface: "core",
|
|
1913
2133
|
});
|
|
1914
2134
|
const workspaceId = grant.workspaceId;
|
|
1915
|
-
const result = await updateSessionTitleRow(db, {
|
|
2135
|
+
const result = await updateSessionTitleRow(db, {
|
|
2136
|
+
workspaceId,
|
|
2137
|
+
sessionId,
|
|
2138
|
+
title,
|
|
2139
|
+
source,
|
|
2140
|
+
});
|
|
1916
2141
|
if (result.updated) {
|
|
1917
2142
|
await appendAndPublishEvents(db, bus, workspaceId, sessionId, [
|
|
1918
2143
|
{
|
|
@@ -1963,7 +2188,9 @@ export async function updateSessionMcpApprovalPolicy(
|
|
|
1963
2188
|
async (_session, context) => {
|
|
1964
2189
|
const result = await context.updateSessionMcpApprovalPolicy(serverId, normalizedPolicy);
|
|
1965
2190
|
if (!result.server) {
|
|
1966
|
-
throw new HTTPException(404, {
|
|
2191
|
+
throw new HTTPException(404, {
|
|
2192
|
+
message: "session MCP server not found",
|
|
2193
|
+
});
|
|
1967
2194
|
}
|
|
1968
2195
|
outcome.server = result.server;
|
|
1969
2196
|
return {
|
|
@@ -1980,6 +2207,7 @@ export async function updateSessionMcpApprovalPolicy(
|
|
|
1980
2207
|
: [],
|
|
1981
2208
|
};
|
|
1982
2209
|
},
|
|
2210
|
+
{ activity: "semantic" },
|
|
1983
2211
|
);
|
|
1984
2212
|
const updatedServer = outcome.server;
|
|
1985
2213
|
if (!updatedServer) {
|
|
@@ -2063,6 +2291,7 @@ export async function updateSessionToolPolicy(
|
|
|
2063
2291
|
deps.db,
|
|
2064
2292
|
grant.workspaceId,
|
|
2065
2293
|
deps.settings,
|
|
2294
|
+
{ subjectId: grant.subjectId },
|
|
2066
2295
|
);
|
|
2067
2296
|
const runtimeSettings = settingsWithSessionMcpServerMetadata(
|
|
2068
2297
|
capabilityRuntimeSettings,
|
|
@@ -2078,7 +2307,9 @@ export async function updateSessionToolPolicy(
|
|
|
2078
2307
|
(tool) => !validatedIds.has(`${tool.kind}:${tool.id}`),
|
|
2079
2308
|
);
|
|
2080
2309
|
if (unknown) {
|
|
2081
|
-
throw new HTTPException(422, {
|
|
2310
|
+
throw new HTTPException(422, {
|
|
2311
|
+
message: `unknown MCP server id: ${unknown.id}`,
|
|
2312
|
+
});
|
|
2082
2313
|
}
|
|
2083
2314
|
return withFirstPartyTools(validatedTools, runtimeSettings);
|
|
2084
2315
|
})()
|
|
@@ -2107,7 +2338,9 @@ export async function updateSessionToolPolicy(
|
|
|
2107
2338
|
if (session.parentSessionId) {
|
|
2108
2339
|
const parent = await context.getLockedSession(session.parentSessionId);
|
|
2109
2340
|
if (!parent) {
|
|
2110
|
-
throw new HTTPException(409, {
|
|
2341
|
+
throw new HTTPException(409, {
|
|
2342
|
+
message: "parent session is no longer available",
|
|
2343
|
+
});
|
|
2111
2344
|
}
|
|
2112
2345
|
const parentTracksWorkspaceDefaults = parent.toolPolicy?.mode === "workspace_default";
|
|
2113
2346
|
const parentEffective = withFirstPartyTools(
|
|
@@ -2220,7 +2453,7 @@ export async function updateSessionToolPolicy(
|
|
|
2220
2453
|
},
|
|
2221
2454
|
};
|
|
2222
2455
|
},
|
|
2223
|
-
{ lockParentSession: true },
|
|
2456
|
+
{ activity: "semantic", lockParentSession: true },
|
|
2224
2457
|
);
|
|
2225
2458
|
if (events.length > 0) {
|
|
2226
2459
|
await publishDurableSessionEvents(deps.bus, grant.workspaceId, sessionId, events);
|