@opengeni/core 0.28.1 → 2.1.0-canary.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/access/index.d.ts +37 -0
- package/dist/application/new-session-drafts.d.ts +8 -1
- package/dist/application/session-commands.d.ts +18 -11
- package/dist/application/session-tenancy.d.ts +18 -0
- package/dist/application/user-resource-grants.d.ts +20 -0
- package/dist/{chunk-6WKPWE5S.js → chunk-H7X52RI2.js} +27 -18
- package/dist/chunk-H7X52RI2.js.map +1 -0
- package/dist/{chunk-JJU6I5XD.js → chunk-VCZFFEPB.js} +21 -9
- package/dist/chunk-VCZFFEPB.js.map +1 -0
- package/dist/{chunk-NYPMQ7NO.js → chunk-ZYCNLYGH.js} +29 -27
- package/dist/chunk-ZYCNLYGH.js.map +1 -0
- package/dist/dependencies.d.ts +12 -1
- package/dist/domain/automations.d.ts +51 -0
- package/dist/domain/company-brain-governed-writes.d.ts +70 -0
- package/dist/domain/company-profile-agent-admin.d.ts +24 -0
- package/dist/domain/connection-authority.d.ts +97 -0
- package/dist/domain/connection-authority.js +169 -0
- package/dist/domain/connection-authority.js.map +1 -0
- package/dist/domain/editable-artifacts/service.d.ts +1 -2
- package/dist/domain/environments.d.ts +1 -1
- package/dist/domain/github-repository-bindings.d.ts +117 -0
- package/dist/domain/governed-learning-activation.d.ts +16 -0
- package/dist/domain/governed-learning-evaluator.d.ts +6 -0
- package/dist/domain/governed-learning-slack-publication.d.ts +45 -0
- package/dist/domain/memory-slack-delivery.d.ts +1 -4
- package/dist/domain/organization-membership-lifecycle.d.ts +1 -0
- package/dist/domain/packs.d.ts +5 -1
- package/dist/domain/personal-connection-delegations.d.ts +44 -2
- package/dist/domain/pr-review.d.ts +149 -0
- package/dist/domain/remember.d.ts +69 -0
- package/dist/domain/resources.d.ts +23 -2
- package/dist/domain/scheduled-tasks.d.ts +43 -2
- package/dist/domain/session-tool-policy.d.ts +1 -1
- package/dist/domain/sessions.d.ts +43 -9
- package/dist/editable-artifact-live/application.d.ts +10 -2
- package/dist/editable-artifact-live/ports.d.ts +0 -1
- package/dist/editable-artifact-live/server.d.ts +0 -1
- package/dist/editable-artifact-live/ticket.d.ts +0 -1
- package/dist/editable-artifact-live/types.d.ts +5 -2
- package/dist/editable-artifact-live.js +2 -2
- package/dist/editable-artifacts.js +2 -4
- package/dist/index.d.ts +13 -1
- package/dist/index.js +4973 -1243
- package/dist/index.js.map +1 -1
- package/dist/rigs/index.d.ts +22 -5
- package/dist/sandbox/fleet.d.ts +34 -6
- package/dist/sandbox/routing.d.ts +15 -1
- package/dist/sandbox/runtime-settings.d.ts +39 -0
- package/package.json +16 -11
- package/src/access/index.ts +151 -31
- package/src/application/new-session-drafts.ts +32 -5
- package/src/application/session-commands.ts +305 -71
- package/src/application/session-tenancy.ts +321 -0
- package/src/application/user-resource-grants.ts +140 -0
- package/src/billing/limits.ts +25 -15
- package/src/dependencies.ts +12 -1
- package/src/domain/automations.ts +196 -0
- package/src/domain/capabilities.ts +7 -1
- package/src/domain/company-brain-governed-writes.ts +330 -0
- package/src/domain/company-profile-agent-admin.ts +55 -0
- package/src/domain/connection-authority.ts +329 -0
- package/src/domain/editable-artifacts/agent-application.ts +17 -9
- package/src/domain/editable-artifacts/service.ts +12 -1
- package/src/domain/editable-artifacts/snapshot-compaction.ts +9 -5
- package/src/domain/editable-artifacts/snapshot-verifier-genesis.ts +10 -8
- package/src/domain/editable-artifacts/snapshot-verifier.ts +10 -3
- package/src/domain/environments.ts +16 -3
- package/src/domain/github-repository-bindings.ts +295 -0
- package/src/domain/governed-learning-activation.ts +44 -0
- package/src/domain/governed-learning-evaluator.ts +49 -0
- package/src/domain/governed-learning-slack-publication.ts +157 -0
- package/src/domain/insights.ts +9 -0
- package/src/domain/memory-slack-delivery.ts +0 -30
- package/src/domain/organization-membership-lifecycle.ts +9 -0
- package/src/domain/packs.ts +106 -3
- package/src/domain/personal-connection-delegations.ts +597 -42
- package/src/domain/pr-review.ts +671 -0
- package/src/domain/remember.ts +549 -0
- package/src/domain/resources.ts +144 -6
- package/src/domain/scheduled-tasks.ts +504 -77
- package/src/domain/session-tool-policy.ts +12 -9
- package/src/domain/sessions.ts +629 -176
- package/src/editable-artifact-live/application.ts +21 -3
- package/src/editable-artifact-live/ports.ts +0 -1
- package/src/editable-artifact-live/server.ts +13 -7
- package/src/editable-artifact-live/ticket.ts +1 -5
- package/src/editable-artifact-live/types.ts +6 -2
- package/src/editable-artifact-live/wire.ts +0 -10
- package/src/index.ts +13 -1
- package/src/rigs/index.ts +165 -48
- package/src/sandbox/fleet.ts +376 -30
- package/src/sandbox/routing.ts +91 -29
- package/src/sandbox/runtime-settings.ts +274 -0
- package/src/session-authorization.ts +17 -49
- package/dist/chunk-6WKPWE5S.js.map +0 -1
- package/dist/chunk-JJU6I5XD.js.map +0 -1
- package/dist/chunk-NYPMQ7NO.js.map +0 -1
- package/dist/domain/durable-learning-slack-publication.d.ts +0 -72
- package/src/domain/durable-learning-slack-publication.ts +0 -202
package/src/domain/sessions.ts
CHANGED
|
@@ -13,24 +13,28 @@ import {
|
|
|
13
13
|
CreateSessionRequest,
|
|
14
14
|
DEFAULT_FIRST_PARTY_MCP_PERMISSIONS,
|
|
15
15
|
DEFAULT_FIRST_PARTY_MCP_TOOLS,
|
|
16
|
+
DraftTimelineAnnotations,
|
|
16
17
|
FIRST_PARTY_MCP_TOOL_NAMES,
|
|
17
18
|
OPENGENI_SLACK_BOT_SESSION_METADATA_KEY,
|
|
18
19
|
SessionSpawnDenial,
|
|
19
20
|
ServiceTurnInitiator,
|
|
20
21
|
ServiceTurnInitiatorContext,
|
|
21
22
|
evaluateWorkspaceModelPolicy,
|
|
22
|
-
|
|
23
|
-
reasoningEffortForMetadata,
|
|
23
|
+
resolveWorkspaceSessionToolDefaults,
|
|
24
24
|
stableJson,
|
|
25
25
|
type AccessGrant,
|
|
26
|
+
type ComposerDraft,
|
|
26
27
|
type CreateSessionResponse,
|
|
27
28
|
type GoalSpec,
|
|
28
29
|
type FirstPartyMcpToolName,
|
|
29
30
|
type McpPersonalConnectionDelegation,
|
|
31
|
+
type McpConnectionAuthoritySelection,
|
|
30
32
|
type Permission,
|
|
33
|
+
type PersonalResourceAttachmentIntent,
|
|
31
34
|
type ReasoningEffort,
|
|
32
35
|
type ResourceRef,
|
|
33
36
|
type Session,
|
|
37
|
+
type SessionCommandReceipt,
|
|
34
38
|
type SessionSkill,
|
|
35
39
|
type SessionEvent,
|
|
36
40
|
SessionMcpApprovalPolicy,
|
|
@@ -44,6 +48,8 @@ import {
|
|
|
44
48
|
type SessionAuthorizationPort,
|
|
45
49
|
type SessionToolPolicy,
|
|
46
50
|
type SessionTurn,
|
|
51
|
+
type SessionPromptRouting,
|
|
52
|
+
type SessionGoalSnapshot,
|
|
47
53
|
type ToolRef,
|
|
48
54
|
type TurnInitiator,
|
|
49
55
|
type TurnInitiatorContext,
|
|
@@ -63,13 +69,18 @@ import {
|
|
|
63
69
|
listDistinctRigVersionIdsInGroup,
|
|
64
70
|
getSandbox,
|
|
65
71
|
getSession,
|
|
72
|
+
getSessionAuthorityProjection,
|
|
66
73
|
SessionIdConflictError,
|
|
74
|
+
NewSessionDraftConflictError,
|
|
67
75
|
getSessionSpawnDenialByIdempotencyKey,
|
|
68
76
|
getWorkspaceControlEvent,
|
|
69
77
|
getSessionLineage,
|
|
70
78
|
getSessionTurn,
|
|
79
|
+
getSessionTurnForAttempt,
|
|
80
|
+
getSessionTurnPersonalConnectionDelegations,
|
|
71
81
|
getSessionTurnXaiProviderAccountAuthoritySnapshot,
|
|
72
82
|
getWorkspaceModelPolicy,
|
|
83
|
+
requireWorkspace,
|
|
73
84
|
initializeSessionStartAtomically,
|
|
74
85
|
listSessionTurns,
|
|
75
86
|
listSessionMcpServersForChildInheritance,
|
|
@@ -87,7 +98,13 @@ import {
|
|
|
87
98
|
SessionSpawnDeniedDbError,
|
|
88
99
|
SessionControlConflictError,
|
|
89
100
|
SessionToolPolicyVersionConflictError,
|
|
101
|
+
SessionCreateIdempotencyConflictError,
|
|
102
|
+
PersonalResourceAttachmentAcceptanceError,
|
|
103
|
+
sessionTenancyProductActivated,
|
|
104
|
+
workspaceControlRequestLockTimeoutMs,
|
|
105
|
+
WorkspaceControlBusyError,
|
|
90
106
|
type SessionCommandActor,
|
|
107
|
+
type NewSessionDraftSnapshot,
|
|
91
108
|
} from "@opengeni/db";
|
|
92
109
|
import {
|
|
93
110
|
appendAndPublishEvents,
|
|
@@ -96,7 +113,7 @@ import {
|
|
|
96
113
|
type EventBus,
|
|
97
114
|
} from "@opengeni/events";
|
|
98
115
|
import { HTTPException } from "hono/http-exception";
|
|
99
|
-
import { hasPermission, requirePermission } from "../access";
|
|
116
|
+
import { hasPermission, requirePermission, type AccessGrantAuthorization } from "../access";
|
|
100
117
|
import { recordWorkspaceUsage, requireLimit } from "../billing/limits";
|
|
101
118
|
import type {
|
|
102
119
|
AcceptSessionUserMessageDependencies,
|
|
@@ -104,6 +121,7 @@ import type {
|
|
|
104
121
|
SessionWorkflowClient,
|
|
105
122
|
} from "../dependencies";
|
|
106
123
|
import {
|
|
124
|
+
requireLiveAgentAttemptAuthorization,
|
|
107
125
|
requireSessionAuthorization,
|
|
108
126
|
SessionAuthorizationDeniedError,
|
|
109
127
|
} from "../session-authorization";
|
|
@@ -116,6 +134,10 @@ import {
|
|
|
116
134
|
personalConnectionDelegationSourceForGrant,
|
|
117
135
|
} from "./personal-connection-delegations";
|
|
118
136
|
import { hasReservedOpenGeniSlackBotSessionMetadata } from "./slack-bot";
|
|
137
|
+
import {
|
|
138
|
+
requireCanonicalManagedHuman,
|
|
139
|
+
requireManagedHumanPrivateSessionCreate,
|
|
140
|
+
} from "../application/session-tenancy";
|
|
119
141
|
import {
|
|
120
142
|
assertToolRefsSubset,
|
|
121
143
|
availableToolRefs,
|
|
@@ -124,7 +146,7 @@ import {
|
|
|
124
146
|
validateFileResources,
|
|
125
147
|
validateGitHubRepositorySelection,
|
|
126
148
|
validateToolRefs,
|
|
127
|
-
|
|
149
|
+
withWorkspaceDefaultMcpTools,
|
|
128
150
|
} from "./resources";
|
|
129
151
|
|
|
130
152
|
const reservedSessionMcpServerIds = new Set(["opengeni", "files", "docs", "codex_apps"]);
|
|
@@ -136,6 +158,39 @@ const maxToolPolicyAuditRefs = 40;
|
|
|
136
158
|
// RFC 9110 field-name token characters.
|
|
137
159
|
const sessionMcpCredentialHeaderName = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
|
|
138
160
|
|
|
161
|
+
async function requireAtomicPersonalResourceAttachment(
|
|
162
|
+
deps: Pick<ApiRouteDeps, "db">,
|
|
163
|
+
authorization: AccessGrantAuthorization | undefined,
|
|
164
|
+
workspaceId: string,
|
|
165
|
+
intent: PersonalResourceAttachmentIntent | undefined,
|
|
166
|
+
existingSession: boolean,
|
|
167
|
+
): Promise<void> {
|
|
168
|
+
if (!intent) return;
|
|
169
|
+
if (!authorization) {
|
|
170
|
+
throw new HTTPException(403, {
|
|
171
|
+
message: "Personal resources require the owning managed-human session.",
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
requireCanonicalManagedHuman(authorization, workspaceId);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
throw new HTTPException(403, {
|
|
178
|
+
message: "Personal resources require the owning managed-human session.",
|
|
179
|
+
cause: error,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
if (!(await sessionTenancyProductActivated(deps.db, workspaceId))) {
|
|
183
|
+
throw new HTTPException(409, {
|
|
184
|
+
message: "Session tenancy is not activated for this organization.",
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
if (existingSession && intent.expectedAuthorityEpoch === undefined) {
|
|
188
|
+
throw new HTTPException(422, {
|
|
189
|
+
message: "Personal-resource attachment requires expectedAuthorityEpoch.",
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
139
194
|
/** Transport-neutral typed denial raised only after its audit row committed. */
|
|
140
195
|
export class SessionSpawnDeniedError extends Error {
|
|
141
196
|
readonly denial: SessionSpawnDenial;
|
|
@@ -562,10 +617,11 @@ export async function createAndStartSessionWithOutcome(input: {
|
|
|
562
617
|
beforeCreateCommit?: (tx: Database, sessionId: string) => Promise<void>;
|
|
563
618
|
accountId: string;
|
|
564
619
|
workspaceId: string;
|
|
620
|
+
visibility?: "user_private" | "workspace_shared";
|
|
565
621
|
initialMessage: string;
|
|
566
622
|
/** Create the session shell without an initial user event/agent turn. */
|
|
567
623
|
deferInitialTurn?: boolean;
|
|
568
|
-
|
|
624
|
+
modelContext?: string | null;
|
|
569
625
|
resources: ResourceRef[];
|
|
570
626
|
skills?: SessionSkill[];
|
|
571
627
|
tools: ToolRef[];
|
|
@@ -613,6 +669,7 @@ export async function createAndStartSessionWithOutcome(input: {
|
|
|
613
669
|
mcpServers?: CreateSessionMcpServerInput[];
|
|
614
670
|
sessionMcpServers?: SessionMcpServerMetadata[];
|
|
615
671
|
personalConnectionDelegations?: McpPersonalConnectionDelegation[];
|
|
672
|
+
initialPersonalResourceAttachmentIntent?: PersonalResourceAttachmentIntent | null;
|
|
616
673
|
xaiProviderAccountAuthoritySnapshot?: XaiProviderAccountAuthoritySnapshotV1;
|
|
617
674
|
// The manager session spawning this worker (a worker-signed sessionId claim
|
|
618
675
|
// on the creating grant); null for direct API creates and scheduled runs.
|
|
@@ -644,12 +701,27 @@ export async function createAndStartSessionWithOutcome(input: {
|
|
|
644
701
|
sandboxId: string;
|
|
645
702
|
settings: Settings;
|
|
646
703
|
workingDir?: string | null;
|
|
704
|
+
resourceSubjectId?: string | null;
|
|
647
705
|
} | null;
|
|
648
706
|
// Exact actor-private pre-session draft represented by this create. The
|
|
649
707
|
// initializer consumes it only after the first durable runnable unit commits.
|
|
650
708
|
consumeNewSessionDraft?: {
|
|
651
709
|
subjectId: string;
|
|
652
710
|
expectedRevision: number;
|
|
711
|
+
expectedSnapshot: NewSessionDraftSnapshot;
|
|
712
|
+
acceptedSelection: {
|
|
713
|
+
channelId: string | null;
|
|
714
|
+
targetSandboxId: string | null;
|
|
715
|
+
workingDir: string | null;
|
|
716
|
+
};
|
|
717
|
+
} | null;
|
|
718
|
+
rememberNewSessionSelection?: {
|
|
719
|
+
subjectId: string;
|
|
720
|
+
acceptedSelection: {
|
|
721
|
+
channelId: string | null;
|
|
722
|
+
targetSandboxId: string | null;
|
|
723
|
+
workingDir: string | null;
|
|
724
|
+
};
|
|
653
725
|
} | null;
|
|
654
726
|
// A child may lower its inherited nested-agent depth limit freely; increases
|
|
655
727
|
// are authorized by the caller's workspace:admin grant and checked again by
|
|
@@ -673,8 +745,9 @@ export async function createAndStartSessionWithOutcome(input: {
|
|
|
673
745
|
...(input.requestedSessionId ? { requestedSessionId: input.requestedSessionId } : {}),
|
|
674
746
|
accountId: input.accountId,
|
|
675
747
|
workspaceId: input.workspaceId,
|
|
748
|
+
visibility: input.visibility ?? "workspace_shared",
|
|
676
749
|
initialMessage: input.initialMessage,
|
|
677
|
-
|
|
750
|
+
initialModelContext: input.modelContext ?? null,
|
|
678
751
|
resources: input.resources,
|
|
679
752
|
skills: input.skills ?? [],
|
|
680
753
|
tools: input.tools,
|
|
@@ -684,6 +757,8 @@ export async function createAndStartSessionWithOutcome(input: {
|
|
|
684
757
|
...(input.createdByContext ? { createdByContext: input.createdByContext } : {}),
|
|
685
758
|
createdByActor: input.createdByActor ?? null,
|
|
686
759
|
model: input.model,
|
|
760
|
+
reasoningEffort: input.reasoningEffort,
|
|
761
|
+
latencyMode: input.latencyMode ?? "standard",
|
|
687
762
|
sandboxBackend: input.sandboxBackend,
|
|
688
763
|
variableSetId: input.variableSet?.id ?? null,
|
|
689
764
|
rigId: input.rigId ?? null,
|
|
@@ -699,6 +774,8 @@ export async function createAndStartSessionWithOutcome(input: {
|
|
|
699
774
|
...(input.sandboxOs ? { sandboxOs: input.sandboxOs } : {}),
|
|
700
775
|
mcpServers: input.mcpServers ?? [],
|
|
701
776
|
personalConnectionDelegations: input.personalConnectionDelegations ?? [],
|
|
777
|
+
initialPersonalResourceAttachmentIntent:
|
|
778
|
+
input.initialPersonalResourceAttachmentIntent ?? null,
|
|
702
779
|
...(input.xaiProviderAccountAuthoritySnapshot
|
|
703
780
|
? {
|
|
704
781
|
initialXaiProviderAccountAuthoritySnapshot: input.xaiProviderAccountAuthoritySnapshot,
|
|
@@ -739,8 +816,9 @@ export async function createAndStartSessionWithOutcome(input: {
|
|
|
739
816
|
...(input.requestedSessionId ? { requestedSessionId: input.requestedSessionId } : {}),
|
|
740
817
|
accountId: input.accountId,
|
|
741
818
|
workspaceId: input.workspaceId,
|
|
819
|
+
visibility: input.visibility ?? "workspace_shared",
|
|
742
820
|
initialMessage: input.initialMessage,
|
|
743
|
-
|
|
821
|
+
initialModelContext: input.modelContext ?? null,
|
|
744
822
|
resources: input.resources,
|
|
745
823
|
skills: input.skills ?? [],
|
|
746
824
|
tools: input.tools,
|
|
@@ -750,6 +828,8 @@ export async function createAndStartSessionWithOutcome(input: {
|
|
|
750
828
|
...(input.createdByContext ? { createdByContext: input.createdByContext } : {}),
|
|
751
829
|
createdByActor: input.createdByActor ?? null,
|
|
752
830
|
model: input.model,
|
|
831
|
+
reasoningEffort: input.reasoningEffort,
|
|
832
|
+
latencyMode: input.latencyMode ?? "standard",
|
|
753
833
|
sandboxBackend: input.sandboxBackend,
|
|
754
834
|
variableSetId: input.variableSet?.id ?? null,
|
|
755
835
|
rigId: input.rigId ?? null,
|
|
@@ -764,6 +844,8 @@ export async function createAndStartSessionWithOutcome(input: {
|
|
|
764
844
|
...(input.sandboxOs ? { sandboxOs: input.sandboxOs } : {}),
|
|
765
845
|
mcpServers: input.mcpServers ?? [],
|
|
766
846
|
personalConnectionDelegations: input.personalConnectionDelegations ?? [],
|
|
847
|
+
initialPersonalResourceAttachmentIntent:
|
|
848
|
+
input.initialPersonalResourceAttachmentIntent ?? null,
|
|
767
849
|
...(input.xaiProviderAccountAuthoritySnapshot
|
|
768
850
|
? {
|
|
769
851
|
initialXaiProviderAccountAuthoritySnapshot: input.xaiProviderAccountAuthoritySnapshot,
|
|
@@ -809,7 +891,7 @@ async function finishStartSession(
|
|
|
809
891
|
workflowClient: Pick<SessionWorkflowClient, "wakeSessionWorkflow">;
|
|
810
892
|
initialMessage: string;
|
|
811
893
|
deferInitialTurn?: boolean;
|
|
812
|
-
|
|
894
|
+
modelContext?: string | null;
|
|
813
895
|
resources: ResourceRef[];
|
|
814
896
|
tools: ToolRef[];
|
|
815
897
|
toolPolicy: SessionToolPolicy;
|
|
@@ -825,10 +907,25 @@ async function finishStartSession(
|
|
|
825
907
|
sandboxId: string;
|
|
826
908
|
settings: Settings;
|
|
827
909
|
workingDir?: string | null;
|
|
910
|
+
resourceSubjectId?: string | null;
|
|
828
911
|
} | null;
|
|
829
912
|
consumeNewSessionDraft?: {
|
|
830
913
|
subjectId: string;
|
|
831
914
|
expectedRevision: number;
|
|
915
|
+
expectedSnapshot: NewSessionDraftSnapshot;
|
|
916
|
+
acceptedSelection: {
|
|
917
|
+
channelId: string | null;
|
|
918
|
+
targetSandboxId: string | null;
|
|
919
|
+
workingDir: string | null;
|
|
920
|
+
};
|
|
921
|
+
} | null;
|
|
922
|
+
rememberNewSessionSelection?: {
|
|
923
|
+
subjectId: string;
|
|
924
|
+
acceptedSelection: {
|
|
925
|
+
channelId: string | null;
|
|
926
|
+
targetSandboxId: string | null;
|
|
927
|
+
workingDir: string | null;
|
|
928
|
+
};
|
|
832
929
|
} | null;
|
|
833
930
|
},
|
|
834
931
|
session: Session,
|
|
@@ -850,6 +947,9 @@ async function finishStartSession(
|
|
|
850
947
|
sessionId: session.id,
|
|
851
948
|
sessionBackend: session.sandboxBackend,
|
|
852
949
|
sessionGroupId: session.sandboxGroupId,
|
|
950
|
+
...(input.seedTargetSandbox.resourceSubjectId
|
|
951
|
+
? { subjectId: input.seedTargetSandbox.resourceSubjectId }
|
|
952
|
+
: {}),
|
|
853
953
|
};
|
|
854
954
|
const seeded = await swapActiveSandbox(
|
|
855
955
|
{
|
|
@@ -892,12 +992,19 @@ async function finishStartSession(
|
|
|
892
992
|
...(input.goal.successCriteria !== undefined
|
|
893
993
|
? { successCriteria: input.goal.successCriteria }
|
|
894
994
|
: {}),
|
|
995
|
+
...(input.goal.rootConstraints !== undefined
|
|
996
|
+
? { rootConstraints: input.goal.rootConstraints }
|
|
997
|
+
: {}),
|
|
895
998
|
...(input.goal.maxAutoContinuations !== undefined
|
|
896
999
|
? { maxAutoContinuations: input.goal.maxAutoContinuations }
|
|
897
1000
|
: {}),
|
|
1001
|
+
...(input.goal.mutationPolicy !== undefined
|
|
1002
|
+
? { mutationPolicy: input.goal.mutationPolicy }
|
|
1003
|
+
: {}),
|
|
898
1004
|
}
|
|
899
1005
|
: null,
|
|
900
1006
|
consumeNewSessionDraft: input.consumeNewSessionDraft ?? null,
|
|
1007
|
+
rememberNewSessionSelection: input.rememberNewSessionSelection ?? null,
|
|
901
1008
|
deferInitialTurn: input.deferInitialTurn === true,
|
|
902
1009
|
});
|
|
903
1010
|
await publishDurableSessionEvents(input.bus, session.workspaceId, session.id, started.events);
|
|
@@ -1072,20 +1179,6 @@ export async function requireQueuedTurnForApi(
|
|
|
1072
1179
|
return turn;
|
|
1073
1180
|
}
|
|
1074
1181
|
|
|
1075
|
-
export function reasoningEffortForSession(
|
|
1076
|
-
metadata: Record<string, unknown>,
|
|
1077
|
-
fallback: Settings["openaiReasoningEffort"],
|
|
1078
|
-
): Settings["openaiReasoningEffort"] {
|
|
1079
|
-
return reasoningEffortForMetadata(metadata, fallback);
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
export function latencyModeForSession(
|
|
1083
|
-
metadata: Record<string, unknown>,
|
|
1084
|
-
fallback: "standard" | "priority" | "fast" = "standard",
|
|
1085
|
-
): "standard" | "priority" | "fast" {
|
|
1086
|
-
return latencyModeForMetadata(metadata, fallback);
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
1182
|
/**
|
|
1090
1183
|
* Appends a `user.message` to an existing session and enqueues the resulting
|
|
1091
1184
|
* turn, merging requested resources/tools into the session and waking the
|
|
@@ -1103,7 +1196,7 @@ export async function postUserMessageTurn(input: {
|
|
|
1103
1196
|
sessionId: string;
|
|
1104
1197
|
text: string;
|
|
1105
1198
|
annotations?: TimelineAnnotation[];
|
|
1106
|
-
|
|
1199
|
+
modelContext?: string | null;
|
|
1107
1200
|
resources: ResourceRef[];
|
|
1108
1201
|
model?: string | null;
|
|
1109
1202
|
reasoningEffort?: Settings["openaiReasoningEffort"] | null;
|
|
@@ -1111,6 +1204,7 @@ export async function postUserMessageTurn(input: {
|
|
|
1111
1204
|
clientEventId?: string;
|
|
1112
1205
|
mcpCredentialUpdates?: UpdateSessionMcpServerCredentialsInput[];
|
|
1113
1206
|
personalConnectionDelegations?: McpPersonalConnectionDelegation[];
|
|
1207
|
+
personalResourceAttachment?: PersonalResourceAttachmentIntent;
|
|
1114
1208
|
delivery?: "send" | "steer";
|
|
1115
1209
|
origin?: "human" | "operator";
|
|
1116
1210
|
actor?: string;
|
|
@@ -1125,6 +1219,9 @@ export async function postUserMessageTurn(input: {
|
|
|
1125
1219
|
}): Promise<{
|
|
1126
1220
|
accepted: SessionEvent;
|
|
1127
1221
|
turn: SessionTurn;
|
|
1222
|
+
draft: ComposerDraft | null;
|
|
1223
|
+
receipt: SessionCommandReceipt;
|
|
1224
|
+
routing: SessionPromptRouting;
|
|
1128
1225
|
interruptionCount: number;
|
|
1129
1226
|
replay: boolean;
|
|
1130
1227
|
}> {
|
|
@@ -1176,7 +1273,7 @@ export async function postUserMessageTurn(input: {
|
|
|
1176
1273
|
expectedDraftRevision: input.expectedDraftRevision ?? null,
|
|
1177
1274
|
text: input.text,
|
|
1178
1275
|
annotations: input.annotations ?? [],
|
|
1179
|
-
|
|
1276
|
+
modelContext: input.modelContext ?? null,
|
|
1180
1277
|
resources: input.resources,
|
|
1181
1278
|
model: requestedModel,
|
|
1182
1279
|
reasoningEffort: requestedReasoningEffort,
|
|
@@ -1189,11 +1286,28 @@ export async function postUserMessageTurn(input: {
|
|
|
1189
1286
|
? { recordAgentRunUsage: input.recordAgentRunUsage }
|
|
1190
1287
|
: {}),
|
|
1191
1288
|
personalConnectionDelegations: input.personalConnectionDelegations ?? [],
|
|
1289
|
+
...(input.personalResourceAttachment
|
|
1290
|
+
? {
|
|
1291
|
+
personalResourceAttachment: input.personalResourceAttachment,
|
|
1292
|
+
}
|
|
1293
|
+
: {}),
|
|
1192
1294
|
mcpCredentialUpdates: input.mcpCredentialUpdates ?? [],
|
|
1295
|
+
controlLockTimeoutMs: workspaceControlRequestLockTimeoutMs(),
|
|
1193
1296
|
}),
|
|
1194
1297
|
),
|
|
1195
1298
|
);
|
|
1196
1299
|
} catch (error) {
|
|
1300
|
+
if (error instanceof WorkspaceControlBusyError) {
|
|
1301
|
+
// Bounded control-prefix wait expired before any write; the request may
|
|
1302
|
+
// be retried. The API layer renders the retryable 503 envelope.
|
|
1303
|
+
throw error;
|
|
1304
|
+
}
|
|
1305
|
+
if (error instanceof PersonalResourceAttachmentAcceptanceError) {
|
|
1306
|
+
throw new HTTPException(
|
|
1307
|
+
error.kind === "invalid" ? 422 : error.kind === "forbidden" ? 403 : 409,
|
|
1308
|
+
{ message: error.message, cause: error },
|
|
1309
|
+
);
|
|
1310
|
+
}
|
|
1197
1311
|
if (
|
|
1198
1312
|
error instanceof QueueCommandConflictError ||
|
|
1199
1313
|
error instanceof SessionControlConflictError
|
|
@@ -1209,46 +1323,52 @@ export async function postUserMessageTurn(input: {
|
|
|
1209
1323
|
throw error;
|
|
1210
1324
|
}
|
|
1211
1325
|
const postCommitTask = async () => {
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1326
|
+
await Promise.all([
|
|
1327
|
+
(async () => {
|
|
1328
|
+
try {
|
|
1329
|
+
await publishDurableSessionEvents(bus, workspaceId, sessionId, result.events);
|
|
1330
|
+
if (result.workspaceControlEventId) {
|
|
1331
|
+
const controlEvent = await getWorkspaceControlEvent(
|
|
1332
|
+
db,
|
|
1333
|
+
workspaceId,
|
|
1334
|
+
result.workspaceControlEventId,
|
|
1335
|
+
);
|
|
1336
|
+
if (!controlEvent) {
|
|
1337
|
+
throw new Error(
|
|
1338
|
+
`Committed workspace control event disappeared: ${result.workspaceControlEventId}`,
|
|
1339
|
+
);
|
|
1340
|
+
}
|
|
1341
|
+
await publishDurableWorkspaceControlEvent(bus, workspaceId, controlEvent);
|
|
1342
|
+
}
|
|
1343
|
+
} catch {
|
|
1344
|
+
console.warn("[sessions] prompt event fanout failed; durable rows remain replayable", {
|
|
1345
|
+
errorClass: "PromptEventFanoutOperationError",
|
|
1346
|
+
errorCode: "session_prompt_event_fanout_failed",
|
|
1347
|
+
origin: "core",
|
|
1348
|
+
});
|
|
1224
1349
|
}
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
errorClass: "WorkflowWakeOperationError",
|
|
1248
|
-
errorCode: "session_workflow_wake_failed",
|
|
1249
|
-
origin: "core",
|
|
1250
|
-
});
|
|
1251
|
-
}
|
|
1350
|
+
})(),
|
|
1351
|
+
(async () => {
|
|
1352
|
+
try {
|
|
1353
|
+
await workflowClient.wakeSessionWorkflow({
|
|
1354
|
+
accountId,
|
|
1355
|
+
workspaceId,
|
|
1356
|
+
sessionId,
|
|
1357
|
+
workflowId: result.turn.temporalWorkflowId,
|
|
1358
|
+
wakeRevision: result.wakeRevision,
|
|
1359
|
+
...((input.delivery ?? "send") === "steer" || result.interruptionCount > 0
|
|
1360
|
+
? { interruptionRequested: true }
|
|
1361
|
+
: {}),
|
|
1362
|
+
});
|
|
1363
|
+
} catch {
|
|
1364
|
+
console.warn("[sessions] workflow wake failed; durable outbox will retry", {
|
|
1365
|
+
errorClass: "WorkflowWakeOperationError",
|
|
1366
|
+
errorCode: "session_workflow_wake_failed",
|
|
1367
|
+
origin: "core",
|
|
1368
|
+
});
|
|
1369
|
+
}
|
|
1370
|
+
})(),
|
|
1371
|
+
]);
|
|
1252
1372
|
};
|
|
1253
1373
|
const schedulePostCommit =
|
|
1254
1374
|
input.schedulePostCommit ??
|
|
@@ -1267,6 +1387,33 @@ export async function postUserMessageTurn(input: {
|
|
|
1267
1387
|
return {
|
|
1268
1388
|
accepted: result.accepted,
|
|
1269
1389
|
turn: result.turn,
|
|
1390
|
+
receipt: {
|
|
1391
|
+
id: result.receipt.id,
|
|
1392
|
+
action: result.receipt.action,
|
|
1393
|
+
operationKey: result.receipt.operationKey,
|
|
1394
|
+
targetSessionId: result.receipt.targetSessionId,
|
|
1395
|
+
targetTurnId: result.receipt.targetTurnId,
|
|
1396
|
+
appliedControlRevision: result.receipt.appliedControlRevision,
|
|
1397
|
+
appliedQueueVersion: result.receipt.appliedQueueVersion,
|
|
1398
|
+
appliedTurnVersion: result.receipt.appliedTurnVersion,
|
|
1399
|
+
appliedDraftRevision: result.receipt.appliedDraftRevision,
|
|
1400
|
+
createdAt: result.receipt.createdAt.toISOString(),
|
|
1401
|
+
},
|
|
1402
|
+
routing: result.routing,
|
|
1403
|
+
draft: result.draft
|
|
1404
|
+
? {
|
|
1405
|
+
revision: result.draft.revision,
|
|
1406
|
+
text: result.draft.text,
|
|
1407
|
+
annotations: DraftTimelineAnnotations.parse(result.draft.annotations),
|
|
1408
|
+
resources: result.draft.resources as ResourceRef[],
|
|
1409
|
+
model: result.draft.model,
|
|
1410
|
+
reasoningEffort: result.draft.reasoningEffort as ReasoningEffort,
|
|
1411
|
+
latencyMode: result.draft.latencyMode as ComposerDraft["latencyMode"],
|
|
1412
|
+
sourceTurnId: result.draft.sourceTurnId,
|
|
1413
|
+
sourceTurnVersion: result.draft.sourceTurnVersion,
|
|
1414
|
+
updatedAt: result.draft.updatedAt.toISOString(),
|
|
1415
|
+
}
|
|
1416
|
+
: null,
|
|
1270
1417
|
interruptionCount: result.interruptionCount,
|
|
1271
1418
|
replay: result.replay,
|
|
1272
1419
|
};
|
|
@@ -1281,14 +1428,69 @@ export async function postUserMessageTurn(input: {
|
|
|
1281
1428
|
* trusted immediate parent, while explicit arrays (including []) win. A
|
|
1282
1429
|
* top-level create with omitted tools applies workspace-default capability MCPs.
|
|
1283
1430
|
*/
|
|
1431
|
+
export function resolveChildGoalFromAcceptedSnapshot(
|
|
1432
|
+
goal: GoalSpec,
|
|
1433
|
+
parentGoalSnapshot: SessionGoalSnapshot,
|
|
1434
|
+
): GoalSpec {
|
|
1435
|
+
const inheritedRootConstraints =
|
|
1436
|
+
parentGoalSnapshot.state === "none" ? [] : parentGoalSnapshot.rootConstraints;
|
|
1437
|
+
const requestedRootConstraints = goal.rootConstraints;
|
|
1438
|
+
if (
|
|
1439
|
+
requestedRootConstraints?.some((constraint) => !inheritedRootConstraints.includes(constraint))
|
|
1440
|
+
) {
|
|
1441
|
+
throw new Error(
|
|
1442
|
+
"child goal rootConstraints must be an exact subset of the calling turn's frozen root constraints",
|
|
1443
|
+
);
|
|
1444
|
+
}
|
|
1445
|
+
return {
|
|
1446
|
+
...goal,
|
|
1447
|
+
rootConstraints: requestedRootConstraints ?? inheritedRootConstraints,
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
export function resolveSessionCreateVisibility(input: {
|
|
1452
|
+
requestedVisibility: "private" | "workspace";
|
|
1453
|
+
visibilityProvided: boolean;
|
|
1454
|
+
parentVisibility: "user_private" | "workspace_shared" | null;
|
|
1455
|
+
}): "user_private" | "workspace_shared" {
|
|
1456
|
+
if (input.parentVisibility === "user_private") {
|
|
1457
|
+
if (input.visibilityProvided && input.requestedVisibility !== "private") {
|
|
1458
|
+
throw new Error("A private parent cannot create a workspace-visible child");
|
|
1459
|
+
}
|
|
1460
|
+
return "user_private";
|
|
1461
|
+
}
|
|
1462
|
+
if (input.parentVisibility === "workspace_shared") {
|
|
1463
|
+
if (input.visibilityProvided && input.requestedVisibility === "private") {
|
|
1464
|
+
throw new Error("A workspace-visible parent cannot create a private child");
|
|
1465
|
+
}
|
|
1466
|
+
return "workspace_shared";
|
|
1467
|
+
}
|
|
1468
|
+
return input.requestedVisibility === "private" ? "user_private" : "workspace_shared";
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1284
1471
|
export async function createSessionForRequestWithOutcome(
|
|
1285
1472
|
deps: ApiRouteDeps,
|
|
1286
1473
|
grant: AccessGrant,
|
|
1287
1474
|
workspaceId: string,
|
|
1288
1475
|
rawPayload: unknown,
|
|
1476
|
+
authorization?: AccessGrantAuthorization,
|
|
1289
1477
|
): Promise<CreateSessionRequestOutcome> {
|
|
1290
1478
|
const { settings, db, bus, workflowClient, objectStorage } = deps;
|
|
1291
1479
|
const payload = CreateSessionRequest.parse(rawPayload);
|
|
1480
|
+
const visibilityProvided = hasOwnProperty(rawPayload, "visibility");
|
|
1481
|
+
if (payload.visibility === "private" && !grant.metadata?.["sessionId"]) {
|
|
1482
|
+
if (!authorization) {
|
|
1483
|
+
throw new HTTPException(403, {
|
|
1484
|
+
message: "managed human session required",
|
|
1485
|
+
});
|
|
1486
|
+
}
|
|
1487
|
+
await requireManagedHumanPrivateSessionCreate(deps, authorization, workspaceId);
|
|
1488
|
+
if (payload.sandbox === "shared" || typeof payload.sandbox === "object") {
|
|
1489
|
+
throw new HTTPException(422, {
|
|
1490
|
+
message: "Only-me sessions require their own sandbox",
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1292
1494
|
if (hasReservedOpenGeniSlackBotSessionMetadata(payload.metadata)) {
|
|
1293
1495
|
throw new HTTPException(422, {
|
|
1294
1496
|
message: `${OPENGENI_SLACK_BOT_SESSION_METADATA_KEY} is reserved for scheduler routing`,
|
|
@@ -1308,6 +1510,13 @@ export async function createSessionForRequestWithOutcome(
|
|
|
1308
1510
|
throw new SessionSpawnDeniedError(SessionSpawnDenial.parse(denial));
|
|
1309
1511
|
}
|
|
1310
1512
|
}
|
|
1513
|
+
await requireAtomicPersonalResourceAttachment(
|
|
1514
|
+
deps,
|
|
1515
|
+
authorization,
|
|
1516
|
+
workspaceId,
|
|
1517
|
+
payload.personalResourceAttachment,
|
|
1518
|
+
false,
|
|
1519
|
+
);
|
|
1311
1520
|
// Parent linkage and execution-context inheritance come ONLY from the
|
|
1312
1521
|
// worker-signed sessionId claim. A caller cannot nominate a parent in the
|
|
1313
1522
|
// payload, so inheriting an existing repository/tool/credential snapshot does
|
|
@@ -1336,10 +1545,37 @@ export async function createSessionForRequestWithOutcome(
|
|
|
1336
1545
|
message: `parent session not found in workspace: ${parentSessionId}`,
|
|
1337
1546
|
});
|
|
1338
1547
|
}
|
|
1548
|
+
const workspace = await requireWorkspace(db, workspaceId);
|
|
1549
|
+
const workspaceSessionToolDefaults = resolveWorkspaceSessionToolDefaults(workspace.settings);
|
|
1550
|
+
const parentAuthority = parentSession
|
|
1551
|
+
? await getSessionAuthorityProjection(db, workspaceId, parentSession.id)
|
|
1552
|
+
: null;
|
|
1553
|
+
if (parentSession && !parentAuthority) {
|
|
1554
|
+
throw new HTTPException(403, {
|
|
1555
|
+
message: "parent session authority is unavailable",
|
|
1556
|
+
});
|
|
1557
|
+
}
|
|
1558
|
+
let effectiveVisibility: "user_private" | "workspace_shared";
|
|
1559
|
+
try {
|
|
1560
|
+
effectiveVisibility = resolveSessionCreateVisibility({
|
|
1561
|
+
requestedVisibility: payload.visibility,
|
|
1562
|
+
visibilityProvided,
|
|
1563
|
+
parentVisibility: parentAuthority?.visibility ?? null,
|
|
1564
|
+
});
|
|
1565
|
+
} catch (error) {
|
|
1566
|
+
throw new HTTPException(422, {
|
|
1567
|
+
message: error instanceof Error ? error.message : "invalid child visibility",
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1339
1570
|
const creationInitiator = creationInitiatorForGrant(grant);
|
|
1340
1571
|
const parentCallingTurn =
|
|
1341
1572
|
parentSession && creationInitiator.actor
|
|
1342
|
-
? await
|
|
1573
|
+
? await getSessionTurnForAttempt(
|
|
1574
|
+
db,
|
|
1575
|
+
workspaceId,
|
|
1576
|
+
parentSession.id,
|
|
1577
|
+
creationInitiator.actor.attemptId,
|
|
1578
|
+
)
|
|
1343
1579
|
: null;
|
|
1344
1580
|
if (
|
|
1345
1581
|
creationInitiator.actor &&
|
|
@@ -1349,6 +1585,26 @@ export async function createSessionForRequestWithOutcome(
|
|
|
1349
1585
|
message: "caller attempt does not belong to the parent session",
|
|
1350
1586
|
});
|
|
1351
1587
|
}
|
|
1588
|
+
let effectiveGoal = payload.goal;
|
|
1589
|
+
if (parentSession && payload.goal) {
|
|
1590
|
+
try {
|
|
1591
|
+
effectiveGoal = resolveChildGoalFromAcceptedSnapshot(
|
|
1592
|
+
payload.goal,
|
|
1593
|
+
parentCallingTurn?.goalSnapshot ?? {
|
|
1594
|
+
state: "none",
|
|
1595
|
+
capturedAt: "unavailable",
|
|
1596
|
+
},
|
|
1597
|
+
);
|
|
1598
|
+
} catch (error) {
|
|
1599
|
+
throw new HTTPException(422, {
|
|
1600
|
+
message: error instanceof Error ? error.message : "invalid child goal root constraints",
|
|
1601
|
+
});
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
const personalResourceSubjectId = creationInitiator.actor
|
|
1605
|
+
? (await requireLiveAgentAttemptAuthorization(db, grant, creationInitiator.actor.sessionId))
|
|
1606
|
+
.initiatingHumanSubjectId
|
|
1607
|
+
: grant.subjectId;
|
|
1352
1608
|
const xaiProviderAccountAuthoritySnapshot =
|
|
1353
1609
|
parentSession && creationInitiator.actor
|
|
1354
1610
|
? await getSessionTurnXaiProviderAccountAuthoritySnapshot(
|
|
@@ -1358,13 +1614,25 @@ export async function createSessionForRequestWithOutcome(
|
|
|
1358
1614
|
creationInitiator.actor.turnId,
|
|
1359
1615
|
)
|
|
1360
1616
|
: undefined;
|
|
1617
|
+
const connectionDelegationSource = personalConnectionDelegationSourceForGrant(grant);
|
|
1618
|
+
const inheritedPersonalConnectionDelegations =
|
|
1619
|
+
connectionDelegationSource.kind === "turn"
|
|
1620
|
+
? await getSessionTurnPersonalConnectionDelegations(
|
|
1621
|
+
db,
|
|
1622
|
+
workspaceId,
|
|
1623
|
+
connectionDelegationSource.sessionId,
|
|
1624
|
+
connectionDelegationSource.turnId,
|
|
1625
|
+
)
|
|
1626
|
+
: null;
|
|
1361
1627
|
const capabilityRuntimeSettings = await settingsWithEnabledCapabilityMcpServers(
|
|
1362
1628
|
db,
|
|
1363
1629
|
workspaceId,
|
|
1364
1630
|
settings,
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1631
|
+
inheritedPersonalConnectionDelegations
|
|
1632
|
+
? {
|
|
1633
|
+
personalConnectionDelegations: inheritedPersonalConnectionDelegations,
|
|
1634
|
+
}
|
|
1635
|
+
: { subjectId: grant.subjectId },
|
|
1368
1636
|
);
|
|
1369
1637
|
const sessionMcpServers = hasOwnProperty(rawPayload, "mcpServers")
|
|
1370
1638
|
? validateSessionMcpServersForCreate(capabilityRuntimeSettings, grant, payload.mcpServers)
|
|
@@ -1386,6 +1654,10 @@ export async function createSessionForRequestWithOutcome(
|
|
|
1386
1654
|
? payload.skills
|
|
1387
1655
|
: (parentSession?.skills ?? payload.skills);
|
|
1388
1656
|
const toolsProvided = hasOwnProperty(rawPayload, "tools");
|
|
1657
|
+
// Visibility became durable draft state after older clients had already
|
|
1658
|
+
// written rows without it. Compare it only when the create request supplied
|
|
1659
|
+
// the field explicitly; the parsed schema default must not manufacture a
|
|
1660
|
+
// mismatch for a legacy draft.
|
|
1389
1661
|
const requestedTools = validateToolRefs(
|
|
1390
1662
|
toolsProvided ? payload.tools : (parentSession?.tools ?? payload.tools),
|
|
1391
1663
|
runtimeSettings,
|
|
@@ -1396,10 +1668,11 @@ export async function createSessionForRequestWithOutcome(
|
|
|
1396
1668
|
const parentTracksWorkspaceDefaults = parentSession.toolPolicy?.mode === "workspace_default";
|
|
1397
1669
|
const parentEffective = withFirstPartyTools(
|
|
1398
1670
|
parentTracksWorkspaceDefaults
|
|
1399
|
-
?
|
|
1671
|
+
? withWorkspaceDefaultMcpTools(
|
|
1400
1672
|
availableToolRefs(parentSession.tools, runtimeSettings),
|
|
1401
1673
|
settings,
|
|
1402
1674
|
runtimeSettings,
|
|
1675
|
+
workspaceSessionToolDefaults,
|
|
1403
1676
|
)
|
|
1404
1677
|
: parentSession.tools,
|
|
1405
1678
|
runtimeSettings,
|
|
@@ -1426,10 +1699,11 @@ export async function createSessionForRequestWithOutcome(
|
|
|
1426
1699
|
selectedTools = requestedTools;
|
|
1427
1700
|
toolPolicy = { mode: "explicit", inheritedFromSessionId: null };
|
|
1428
1701
|
} else {
|
|
1429
|
-
selectedTools =
|
|
1702
|
+
selectedTools = withWorkspaceDefaultMcpTools(
|
|
1430
1703
|
requestedTools,
|
|
1431
1704
|
settings,
|
|
1432
1705
|
capabilityRuntimeSettings,
|
|
1706
|
+
workspaceSessionToolDefaults,
|
|
1433
1707
|
);
|
|
1434
1708
|
toolPolicy = { mode: "workspace_default", inheritedFromSessionId: null };
|
|
1435
1709
|
}
|
|
@@ -1438,20 +1712,13 @@ export async function createSessionForRequestWithOutcome(
|
|
|
1438
1712
|
// tool's permission/target authorization predicate, so attachment alone
|
|
1439
1713
|
// exposes nothing.
|
|
1440
1714
|
const tools = withFirstPartyTools(selectedTools, runtimeSettings);
|
|
1441
|
-
const personalConnectionDelegations = await freezePersonalConnectionDelegations({
|
|
1442
|
-
db,
|
|
1443
|
-
workspaceId,
|
|
1444
|
-
settings: runtimeSettings,
|
|
1445
|
-
tools,
|
|
1446
|
-
source: personalConnectionDelegationSourceForGrant(grant),
|
|
1447
|
-
});
|
|
1448
1715
|
await validateGitHubRepositorySelection(db, workspaceId, resources);
|
|
1449
1716
|
if (resources.some((resource) => resource.kind === "file") && !objectStorage) {
|
|
1450
1717
|
throw new HTTPException(503, {
|
|
1451
1718
|
message: "object storage is not configured",
|
|
1452
1719
|
});
|
|
1453
1720
|
}
|
|
1454
|
-
await validateFileResources(db, workspaceId, resources);
|
|
1721
|
+
await validateFileResources(db, grant.accountId, workspaceId, grant.subjectId, resources);
|
|
1455
1722
|
// VariableSet attachment requires variable-sets:use on the calling grant
|
|
1456
1723
|
// (validateVariableSetAttachment enforces it), preserving the invariant
|
|
1457
1724
|
// that sandboxed agents cannot self-attach workspace secrets.
|
|
@@ -1479,7 +1746,7 @@ export async function createSessionForRequestWithOutcome(
|
|
|
1479
1746
|
let frozenRigId: string | null = null;
|
|
1480
1747
|
let frozenRigVersionId: string | null = null;
|
|
1481
1748
|
if (requestedRigId) {
|
|
1482
|
-
const rig = await getRig(db,
|
|
1749
|
+
const rig = await getRig(db, grant, requestedRigId);
|
|
1483
1750
|
if (!rig || !rig.activeVersion) {
|
|
1484
1751
|
if (payload.rigId) {
|
|
1485
1752
|
throw new HTTPException(422, {
|
|
@@ -1490,6 +1757,14 @@ export async function createSessionForRequestWithOutcome(
|
|
|
1490
1757
|
}
|
|
1491
1758
|
// else: workspace-default fallback that no longer resolves → rig-less.
|
|
1492
1759
|
} else {
|
|
1760
|
+
for (const defaultVariableSetId of new Set(rig.activeVersion.defaultVariableSetIds)) {
|
|
1761
|
+
await validateVariableSetAttachment(
|
|
1762
|
+
{ settings, db },
|
|
1763
|
+
grant,
|
|
1764
|
+
workspaceId,
|
|
1765
|
+
defaultVariableSetId,
|
|
1766
|
+
);
|
|
1767
|
+
}
|
|
1493
1768
|
frozenRigId = rig.id;
|
|
1494
1769
|
frozenRigVersionId = rig.activeVersion.id;
|
|
1495
1770
|
}
|
|
@@ -1526,14 +1801,44 @@ export async function createSessionForRequestWithOutcome(
|
|
|
1526
1801
|
await assertWorkspaceModelPolicyAllows(db, settings, workspaceId, model);
|
|
1527
1802
|
const inheritedReasoningEffort =
|
|
1528
1803
|
parentCallingTurn?.reasoningEffort ??
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
: settings.openaiReasoningEffort);
|
|
1804
|
+
parentSession?.reasoningEffort ??
|
|
1805
|
+
settings.openaiReasoningEffort;
|
|
1532
1806
|
const inheritedLatencyMode =
|
|
1533
|
-
parentCallingTurn?.latencyMode ??
|
|
1534
|
-
(parentSession ? latencyModeForSession(parentSession.metadata, "standard") : "standard");
|
|
1807
|
+
parentCallingTurn?.latencyMode ?? parentSession?.latencyMode ?? "standard";
|
|
1535
1808
|
const reasoningEffort = payload.reasoningEffort ?? inheritedReasoningEffort;
|
|
1536
1809
|
const latencyMode = payload.latencyMode ?? inheritedLatencyMode;
|
|
1810
|
+
if (payload.expectedNewSessionDraftRevision !== undefined && payload.rigId === null) {
|
|
1811
|
+
throw new HTTPException(409, {
|
|
1812
|
+
message: "The submitted session options are not represented by the new-session draft",
|
|
1813
|
+
});
|
|
1814
|
+
}
|
|
1815
|
+
const expectedNewSessionDraftSnapshot: NewSessionDraftSnapshot | null =
|
|
1816
|
+
payload.expectedNewSessionDraftRevision !== undefined
|
|
1817
|
+
? {
|
|
1818
|
+
text: payload.initialMessage ?? "",
|
|
1819
|
+
resources,
|
|
1820
|
+
tools: toolsProvided ? requestedTools : [],
|
|
1821
|
+
toolsProvided,
|
|
1822
|
+
model,
|
|
1823
|
+
reasoningEffort,
|
|
1824
|
+
latencyMode,
|
|
1825
|
+
options: {
|
|
1826
|
+
...(visibilityProvided ? { visibility: payload.visibility } : {}),
|
|
1827
|
+
...(payload.sandboxBackend ? { sandboxBackend: payload.sandboxBackend } : {}),
|
|
1828
|
+
...(payload.targetSandboxId ? { targetSandboxId: payload.targetSandboxId } : {}),
|
|
1829
|
+
...(payload.workingDir ? { workingDir: payload.workingDir } : {}),
|
|
1830
|
+
...(payload.variableSetId ? { variableSetId: payload.variableSetId } : {}),
|
|
1831
|
+
...(payload.rigId ? { rigId: payload.rigId } : {}),
|
|
1832
|
+
...(payload.goal ? { goal: payload.goal } : {}),
|
|
1833
|
+
...(payload.firstPartyMcpPermissions
|
|
1834
|
+
? { firstPartyMcpPermissions: payload.firstPartyMcpPermissions }
|
|
1835
|
+
: {}),
|
|
1836
|
+
...(payload.firstPartyMcpTools
|
|
1837
|
+
? { firstPartyMcpTools: payload.firstPartyMcpTools }
|
|
1838
|
+
: {}),
|
|
1839
|
+
},
|
|
1840
|
+
}
|
|
1841
|
+
: null;
|
|
1537
1842
|
const inheritedFromParent = parentSession !== null;
|
|
1538
1843
|
const turnExecutionPolicy = resolveTurnExecutionPolicyV1(settings, {
|
|
1539
1844
|
modelId: model,
|
|
@@ -1615,7 +1920,7 @@ export async function createSessionForRequestWithOutcome(
|
|
|
1615
1920
|
// and never gains an unrequested permission. Top-level omission remains the
|
|
1616
1921
|
// deployment's worker default, which includes the goal tools.
|
|
1617
1922
|
if (
|
|
1618
|
-
|
|
1923
|
+
effectiveGoal &&
|
|
1619
1924
|
firstPartyMcpPermissions &&
|
|
1620
1925
|
!firstPartyMcpPermissions.includes("goals:manage")
|
|
1621
1926
|
) {
|
|
@@ -1626,7 +1931,8 @@ export async function createSessionForRequestWithOutcome(
|
|
|
1626
1931
|
}
|
|
1627
1932
|
// Tool visibility is independent from permission authority. A child that
|
|
1628
1933
|
// omits the field inherits the parent's exact effective selection; a
|
|
1629
|
-
// top-level omission selects the
|
|
1934
|
+
// top-level omission selects the workspace's exact default catalog (or the
|
|
1935
|
+
// deployment default when the workspace has not configured one).
|
|
1630
1936
|
const deploymentFirstPartyMcpToolPolicy = resolveFirstPartyMcpToolPolicy(settings);
|
|
1631
1937
|
const disallowedFirstPartyMcpTool = payload.firstPartyMcpTools?.find(
|
|
1632
1938
|
(tool) => !deploymentFirstPartyMcpToolPolicy.allowed.includes(tool),
|
|
@@ -1636,12 +1942,37 @@ export async function createSessionForRequestWithOutcome(
|
|
|
1636
1942
|
message: `first-party MCP tool is disabled by deployment policy: ${disallowedFirstPartyMcpTool}`,
|
|
1637
1943
|
});
|
|
1638
1944
|
}
|
|
1945
|
+
const workspaceFirstPartyDefaults = workspaceSessionToolDefaults?.firstPartyMcpTools.filter(
|
|
1946
|
+
(tool) => deploymentFirstPartyMcpToolPolicy.allowed.includes(tool),
|
|
1947
|
+
);
|
|
1639
1948
|
const firstPartyMcpTools = resolveFirstPartyMcpToolsForCreate(
|
|
1640
1949
|
payload.firstPartyMcpTools,
|
|
1641
1950
|
parentSession ? parentSession.firstPartyMcpTools : undefined,
|
|
1642
|
-
|
|
1951
|
+
workspaceFirstPartyDefaults && !parentSession
|
|
1952
|
+
? { ...deploymentFirstPartyMcpToolPolicy, default: workspaceFirstPartyDefaults }
|
|
1953
|
+
: deploymentFirstPartyMcpToolPolicy,
|
|
1643
1954
|
);
|
|
1644
|
-
|
|
1955
|
+
const googleDrivePublicationEnabled =
|
|
1956
|
+
firstPartyMcpTools.includes("editable_artifact_export") &&
|
|
1957
|
+
firstPartyMcpTools.includes("editable_artifact_export_status") &&
|
|
1958
|
+
(!firstPartyMcpPermissions?.length ||
|
|
1959
|
+
(firstPartyMcpPermissions.includes("artifacts:read") &&
|
|
1960
|
+
firstPartyMcpPermissions.includes("artifacts:publish")));
|
|
1961
|
+
const atlassianEnabled =
|
|
1962
|
+
firstPartyMcpTools.some((tool) => tool.startsWith("atlassian_")) &&
|
|
1963
|
+
(!firstPartyMcpPermissions?.length || firstPartyMcpPermissions.includes("connections:read"));
|
|
1964
|
+
const personalConnectionDelegations = await freezePersonalConnectionDelegations({
|
|
1965
|
+
db,
|
|
1966
|
+
workspaceId,
|
|
1967
|
+
settings: runtimeSettings,
|
|
1968
|
+
tools,
|
|
1969
|
+
resources,
|
|
1970
|
+
source: connectionDelegationSource,
|
|
1971
|
+
authoritySelections: payload.connectionAuthorities,
|
|
1972
|
+
googleDrivePublicationEnabled,
|
|
1973
|
+
atlassianEnabled,
|
|
1974
|
+
});
|
|
1975
|
+
if (effectiveGoal) {
|
|
1645
1976
|
const missingGoalTools = ["goal_update", "goal_progress", "goal_complete", "goal_pause"].filter(
|
|
1646
1977
|
(name) => !firstPartyMcpTools.includes(name as FirstPartyMcpToolName),
|
|
1647
1978
|
);
|
|
@@ -1669,7 +2000,12 @@ export async function createSessionForRequestWithOutcome(
|
|
|
1669
2000
|
// spawned FROM INSIDE a session (parentSessionId present ⇒ a worker-signed
|
|
1670
2001
|
// sessionId claim) defaults to "shared" (join the creator's box); a top-level
|
|
1671
2002
|
// create (no parent) defaults to "new" (a private singleton box). Explicit
|
|
1672
|
-
// values always win
|
|
2003
|
+
// values always win — except a named `targetSandboxId` is a different compute
|
|
2004
|
+
// home, not a share of the creator's box. Omission plus a machine target
|
|
2005
|
+
// therefore defaults to "new" so the honest-label selfhosted home can fire
|
|
2006
|
+
// (a backend:"none" parent has no box to share; inheriting "none" then 422s
|
|
2007
|
+
// at seed). Explicit shared/{groupId} plus a machine target is contradictory
|
|
2008
|
+
// and 422s rather than silently dropping the target.
|
|
1673
2009
|
//
|
|
1674
2010
|
// null sandboxGroupId ⇒ createSession seeds the new row's own id (singleton,
|
|
1675
2011
|
// today's 1:1 behavior). A shared/{groupId} spawn inherits the box's backend
|
|
@@ -1678,7 +2014,14 @@ export async function createSessionForRequestWithOutcome(
|
|
|
1678
2014
|
// getAnySessionInGroup are RLS-workspace-scoped, so a foreign parent/group
|
|
1679
2015
|
// returns null → 404; the group uuid is NOT an access boundary, the workspace
|
|
1680
2016
|
// filter is (stress (e)).
|
|
1681
|
-
|
|
2017
|
+
if (payload.targetSandboxId && payload.sandbox !== undefined && payload.sandbox !== "new") {
|
|
2018
|
+
throw new HTTPException(422, {
|
|
2019
|
+
message:
|
|
2020
|
+
"targetSandboxId requires an own sandbox (omit sandbox or pass 'new'); it cannot join a shared group",
|
|
2021
|
+
});
|
|
2022
|
+
}
|
|
2023
|
+
const sandboxChoice =
|
|
2024
|
+
payload.sandbox ?? (payload.targetSandboxId ? "new" : parentSessionId ? "shared" : "new");
|
|
1682
2025
|
let sandboxGroupId: string | null = null;
|
|
1683
2026
|
let inheritedBackend: Session["sandboxBackend"] | undefined;
|
|
1684
2027
|
// ENV-AWARE GROUPING: under the CURRENT mechanics the workspace VariableSet is
|
|
@@ -1816,19 +2159,22 @@ export async function createSessionForRequestWithOutcome(
|
|
|
1816
2159
|
"workingDir requires targetSandboxId (it is the targeted machine's working directory)",
|
|
1817
2160
|
});
|
|
1818
2161
|
}
|
|
1819
|
-
// Honest-label (Stage-D closure): a
|
|
2162
|
+
// Honest-label (Stage-D closure): a session TARGETED at a Connected
|
|
1820
2163
|
// Machine (a selfhosted sandbox) runs machine-primary every turn, so its HOME
|
|
1821
2164
|
// sandbox_backend must read "selfhosted" — not the deployment cloud default —
|
|
1822
2165
|
// so the session row + first turn honestly reflect where the agent runs (the
|
|
1823
2166
|
// Machines dashboard, the turn's warm-metering, and the file-download plane all
|
|
1824
|
-
// key off this). GUARDS: (1) only
|
|
1825
|
-
// undefined)
|
|
1826
|
-
//
|
|
1827
|
-
//
|
|
1828
|
-
//
|
|
1829
|
-
//
|
|
1830
|
-
//
|
|
1831
|
-
//
|
|
2167
|
+
// key off this). GUARDS: (1) only when not inheriting a shared box
|
|
2168
|
+
// (inheritedBackend undefined). Named targetSandboxId already 422s
|
|
2169
|
+
// shared/{groupId} and defaults omission to own-box above, so this check is a
|
|
2170
|
+
// backstop if those placement rules change; a shared spawn without a target
|
|
2171
|
+
// is still literally the creator's box and must NOT be relabeled; (2) only
|
|
2172
|
+
// when the target's kind is actually "selfhosted" — targetSandboxId also
|
|
2173
|
+
// accepts a first-class MODAL sandbox id (resolveTarget), which must never be
|
|
2174
|
+
// mislabeled. A not-found / non-selfhosted / modal target falls through to the
|
|
2175
|
+
// default; the seed swap in createAndStartSession still validates
|
|
2176
|
+
// ownership/liveness and 422s a bad target. (3) only when the feature flags
|
|
2177
|
+
// that make the worker actually take the machine-primary path are ON
|
|
1832
2178
|
// (sandboxOwnershipEnabled + sandboxSelfhostedEnabled/routing) — otherwise the
|
|
1833
2179
|
// worker ignores the active pointer and a home="selfhosted" turn would fall to
|
|
1834
2180
|
// the registry client with no bound agentId and throw; with the flags off we
|
|
@@ -1849,11 +2195,19 @@ export async function createSessionForRequestWithOutcome(
|
|
|
1849
2195
|
settings.sandboxOwnershipEnabled &&
|
|
1850
2196
|
settings.sandboxSelfhostedEnabled
|
|
1851
2197
|
) {
|
|
1852
|
-
const targetSandbox = await getSandbox(
|
|
2198
|
+
const targetSandbox = await getSandbox(
|
|
2199
|
+
db,
|
|
2200
|
+
personalResourceSubjectId ? { ...grant, subjectId: personalResourceSubjectId } : grant,
|
|
2201
|
+
payload.targetSandboxId,
|
|
2202
|
+
);
|
|
1853
2203
|
if (targetSandbox?.kind === "selfhosted") {
|
|
1854
2204
|
machineHomeBackend = "selfhosted";
|
|
1855
2205
|
if (targetSandbox.enrollmentId) {
|
|
1856
|
-
const enrollment = await getEnrollment(
|
|
2206
|
+
const enrollment = await getEnrollment(
|
|
2207
|
+
db,
|
|
2208
|
+
targetSandbox.workspaceId,
|
|
2209
|
+
targetSandbox.enrollmentId,
|
|
2210
|
+
);
|
|
1857
2211
|
if (
|
|
1858
2212
|
enrollment &&
|
|
1859
2213
|
(enrollment.os === "macos" || enrollment.os === "windows" || enrollment.os === "linux")
|
|
@@ -1881,9 +2235,10 @@ export async function createSessionForRequestWithOutcome(
|
|
|
1881
2235
|
workflowClient,
|
|
1882
2236
|
accountId: grant.accountId,
|
|
1883
2237
|
workspaceId,
|
|
2238
|
+
visibility: effectiveVisibility,
|
|
1884
2239
|
initialMessage: payload.initialMessage ?? "",
|
|
1885
2240
|
deferInitialTurn: payload.startMode === "realtime",
|
|
1886
|
-
|
|
2241
|
+
modelContext: payload.modelContext ?? null,
|
|
1887
2242
|
resources,
|
|
1888
2243
|
skills,
|
|
1889
2244
|
tools,
|
|
@@ -1895,14 +2250,13 @@ export async function createSessionForRequestWithOutcome(
|
|
|
1895
2250
|
turnExecutionPolicy,
|
|
1896
2251
|
// A shared spawn inherits the box's backend; a caller-supplied
|
|
1897
2252
|
// sandboxBackend on a shared spawn is ignored (it is the same box). A
|
|
1898
|
-
// machine-targeted top-level
|
|
1899
|
-
// (machineHomeBackend), overriding the caller/deployment
|
|
1900
|
-
// matches where the session actually runs.
|
|
2253
|
+
// machine-targeted create (top-level or own-box child) labels the home
|
|
2254
|
+
// "selfhosted" (machineHomeBackend), overriding the caller/deployment
|
|
2255
|
+
// default so the row matches where the session actually runs.
|
|
1901
2256
|
sandboxBackend:
|
|
1902
2257
|
inheritedBackend ?? machineHomeBackend ?? payload.sandboxBackend ?? settings.sandboxBackend,
|
|
1903
|
-
// Mirror the backend relabel on the OS axis:
|
|
1904
|
-
//
|
|
1905
|
-
// "linux" default holds (shared spawns keep the parent-box behavior).
|
|
2258
|
+
// Mirror the backend relabel on the OS axis: a machine-targeted own-box
|
|
2259
|
+
// create carries a derived OS; shared spawns keep the parent-box behavior.
|
|
1906
2260
|
...(machineHomeOs ? { sandboxOs: machineHomeOs } : {}),
|
|
1907
2261
|
sandboxGroupId,
|
|
1908
2262
|
metadata: payload.metadata,
|
|
@@ -1914,7 +2268,7 @@ export async function createSessionForRequestWithOutcome(
|
|
|
1914
2268
|
rigId: frozenRigId,
|
|
1915
2269
|
rigVersionId: frozenRigVersionId,
|
|
1916
2270
|
channelId,
|
|
1917
|
-
goal:
|
|
2271
|
+
goal: effectiveGoal ?? null,
|
|
1918
2272
|
// Per-session persona instructions (already trimmed/validated by the
|
|
1919
2273
|
// contracts schema). Persisted on the row; composed system-level at turn
|
|
1920
2274
|
// time. Not surfaced as an event.
|
|
@@ -1925,6 +2279,7 @@ export async function createSessionForRequestWithOutcome(
|
|
|
1925
2279
|
mcpServers: sessionMcpServers.dbServers,
|
|
1926
2280
|
sessionMcpServers: sessionMcpServers.metadata,
|
|
1927
2281
|
personalConnectionDelegations,
|
|
2282
|
+
initialPersonalResourceAttachmentIntent: payload.personalResourceAttachment ?? null,
|
|
1928
2283
|
...(xaiProviderAccountAuthoritySnapshot ? { xaiProviderAccountAuthoritySnapshot } : {}),
|
|
1929
2284
|
parentSessionId,
|
|
1930
2285
|
createIdempotencyKey: payload.idempotencyKey ?? null,
|
|
@@ -1940,17 +2295,41 @@ export async function createSessionForRequestWithOutcome(
|
|
|
1940
2295
|
sandboxId: payload.targetSandboxId,
|
|
1941
2296
|
settings,
|
|
1942
2297
|
workingDir: payload.workingDir ?? null,
|
|
2298
|
+
resourceSubjectId: personalResourceSubjectId,
|
|
1943
2299
|
}
|
|
1944
2300
|
: null,
|
|
1945
2301
|
consumeNewSessionDraft:
|
|
1946
|
-
payload.expectedNewSessionDraftRevision !== undefined
|
|
2302
|
+
payload.expectedNewSessionDraftRevision !== undefined && payload.startMode !== "realtime"
|
|
1947
2303
|
? {
|
|
1948
2304
|
subjectId: grant.subjectId,
|
|
1949
2305
|
expectedRevision: payload.expectedNewSessionDraftRevision,
|
|
2306
|
+
expectedSnapshot: expectedNewSessionDraftSnapshot!,
|
|
2307
|
+
acceptedSelection: {
|
|
2308
|
+
channelId,
|
|
2309
|
+
targetSandboxId: payload.targetSandboxId ?? null,
|
|
2310
|
+
workingDir: payload.targetSandboxId ? (payload.workingDir ?? null) : null,
|
|
2311
|
+
},
|
|
2312
|
+
}
|
|
2313
|
+
: null,
|
|
2314
|
+
rememberNewSessionSelection:
|
|
2315
|
+
payload.expectedNewSessionDraftRevision !== undefined && payload.startMode === "realtime"
|
|
2316
|
+
? {
|
|
2317
|
+
subjectId: grant.subjectId,
|
|
2318
|
+
acceptedSelection: {
|
|
2319
|
+
channelId,
|
|
2320
|
+
targetSandboxId: payload.targetSandboxId ?? null,
|
|
2321
|
+
workingDir: payload.targetSandboxId ? (payload.workingDir ?? null) : null,
|
|
2322
|
+
},
|
|
1950
2323
|
}
|
|
1951
2324
|
: null,
|
|
1952
2325
|
});
|
|
1953
2326
|
} catch (error) {
|
|
2327
|
+
if (error instanceof PersonalResourceAttachmentAcceptanceError) {
|
|
2328
|
+
throw new HTTPException(
|
|
2329
|
+
error.kind === "invalid" ? 422 : error.kind === "forbidden" ? 403 : 409,
|
|
2330
|
+
{ message: error.message, cause: error },
|
|
2331
|
+
);
|
|
2332
|
+
}
|
|
1954
2333
|
if (error instanceof AgentCommandAuthorityError) {
|
|
1955
2334
|
throw new HTTPException(403, { message: error.message });
|
|
1956
2335
|
}
|
|
@@ -1959,6 +2338,15 @@ export async function createSessionForRequestWithOutcome(
|
|
|
1959
2338
|
message: "requested session id is already in use",
|
|
1960
2339
|
});
|
|
1961
2340
|
}
|
|
2341
|
+
if (error instanceof NewSessionDraftConflictError) {
|
|
2342
|
+
throw new HTTPException(409, {
|
|
2343
|
+
message: error.message,
|
|
2344
|
+
cause: error,
|
|
2345
|
+
});
|
|
2346
|
+
}
|
|
2347
|
+
if (error instanceof SessionCreateIdempotencyConflictError) {
|
|
2348
|
+
throw new HTTPException(409, { message: error.message, cause: error });
|
|
2349
|
+
}
|
|
1962
2350
|
throw error;
|
|
1963
2351
|
}
|
|
1964
2352
|
let usageRecording: CreateSessionRequestOutcome["usageRecording"] = "recorded";
|
|
@@ -2005,8 +2393,11 @@ export async function createSessionForRequest(
|
|
|
2005
2393
|
grant: AccessGrant,
|
|
2006
2394
|
workspaceId: string,
|
|
2007
2395
|
rawPayload: unknown,
|
|
2396
|
+
authorization?: AccessGrantAuthorization,
|
|
2008
2397
|
): Promise<CreateSessionResponse> {
|
|
2009
|
-
return (
|
|
2398
|
+
return (
|
|
2399
|
+
await createSessionForRequestWithOutcome(deps, grant, workspaceId, rawPayload, authorization)
|
|
2400
|
+
).session;
|
|
2010
2401
|
}
|
|
2011
2402
|
|
|
2012
2403
|
/**
|
|
@@ -2024,21 +2415,27 @@ export async function acceptSessionUserMessageWithOutcome(
|
|
|
2024
2415
|
input: {
|
|
2025
2416
|
text: string;
|
|
2026
2417
|
annotations?: SubmittedTimelineAnnotation[];
|
|
2027
|
-
|
|
2418
|
+
modelContext?: string | null;
|
|
2028
2419
|
resources?: ResourceRef[];
|
|
2029
2420
|
model?: string | null;
|
|
2030
2421
|
reasoningEffort?: ReasoningEffort | null;
|
|
2031
2422
|
latencyMode?: "standard" | "priority" | "fast" | null;
|
|
2032
2423
|
clientEventId?: string;
|
|
2033
2424
|
mcpCredentialUpdates?: SessionMcpCredentialUpdateInput[];
|
|
2425
|
+
connectionAuthorities?: McpConnectionAuthoritySelection[];
|
|
2034
2426
|
delivery?: "send" | "steer";
|
|
2035
2427
|
origin?: "human" | "operator";
|
|
2036
2428
|
controlEtag?: string | null;
|
|
2037
2429
|
expectedDraftRevision?: number | null;
|
|
2430
|
+
personalResourceAttachment?: PersonalResourceAttachmentIntent;
|
|
2431
|
+
authorization?: AccessGrantAuthorization;
|
|
2038
2432
|
},
|
|
2039
2433
|
): Promise<{
|
|
2040
2434
|
accepted: SessionEvent;
|
|
2041
2435
|
turn: SessionTurn;
|
|
2436
|
+
draft: ComposerDraft | null;
|
|
2437
|
+
receipt: SessionCommandReceipt;
|
|
2438
|
+
routing: SessionPromptRouting;
|
|
2042
2439
|
interruptionCount: number;
|
|
2043
2440
|
replay: boolean;
|
|
2044
2441
|
}> {
|
|
@@ -2049,6 +2446,13 @@ export async function acceptSessionUserMessageWithOutcome(
|
|
|
2049
2446
|
operation: input.delivery === "steer" ? "session.steer" : "session.append",
|
|
2050
2447
|
surface: "core",
|
|
2051
2448
|
});
|
|
2449
|
+
await requireAtomicPersonalResourceAttachment(
|
|
2450
|
+
deps,
|
|
2451
|
+
input.authorization,
|
|
2452
|
+
workspaceId,
|
|
2453
|
+
input.personalResourceAttachment,
|
|
2454
|
+
true,
|
|
2455
|
+
);
|
|
2052
2456
|
// Hoisted above requireLimit so the codex-billed predicate can resolve the
|
|
2053
2457
|
// turn's effective model (a follow-up turn inherits the session's model). A
|
|
2054
2458
|
// pure read with no side effects.
|
|
@@ -2067,12 +2471,9 @@ export async function acceptSessionUserMessageWithOutcome(
|
|
|
2067
2471
|
}
|
|
2068
2472
|
throw error;
|
|
2069
2473
|
}
|
|
2070
|
-
const sessionReasoningEffort =
|
|
2071
|
-
existingSession.metadata,
|
|
2072
|
-
settings.openaiReasoningEffort,
|
|
2073
|
-
);
|
|
2474
|
+
const sessionReasoningEffort = existingSession.reasoningEffort;
|
|
2074
2475
|
const effectiveReasoningEffort = input.reasoningEffort ?? sessionReasoningEffort;
|
|
2075
|
-
const sessionLatencyMode =
|
|
2476
|
+
const sessionLatencyMode = existingSession.latencyMode;
|
|
2076
2477
|
const effectiveLatencyMode = input.latencyMode ?? sessionLatencyMode;
|
|
2077
2478
|
const turnExecutionPolicy = resolveTurnExecutionPolicyV1(settings, {
|
|
2078
2479
|
modelId: effectiveModel,
|
|
@@ -2102,7 +2503,13 @@ export async function acceptSessionUserMessageWithOutcome(
|
|
|
2102
2503
|
message: "object storage is not configured",
|
|
2103
2504
|
});
|
|
2104
2505
|
}
|
|
2105
|
-
await validateFileResources(
|
|
2506
|
+
await validateFileResources(
|
|
2507
|
+
db,
|
|
2508
|
+
grant.accountId,
|
|
2509
|
+
workspaceId,
|
|
2510
|
+
grant.subjectId,
|
|
2511
|
+
requestedResources,
|
|
2512
|
+
);
|
|
2106
2513
|
await validateGitHubRepositorySelection(db, workspaceId, [
|
|
2107
2514
|
...existingSession.resources,
|
|
2108
2515
|
...requestedResources,
|
|
@@ -2113,60 +2520,96 @@ export async function acceptSessionUserMessageWithOutcome(
|
|
|
2113
2520
|
session: existingSession,
|
|
2114
2521
|
updates: input.mcpCredentialUpdates ?? [],
|
|
2115
2522
|
});
|
|
2116
|
-
const
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2523
|
+
const connectionDelegationSource = personalConnectionDelegationSourceForGrant(grant);
|
|
2524
|
+
const inheritedPersonalConnectionDelegations =
|
|
2525
|
+
connectionDelegationSource.kind === "turn"
|
|
2526
|
+
? await getSessionTurnPersonalConnectionDelegations(
|
|
2527
|
+
db,
|
|
2528
|
+
workspaceId,
|
|
2529
|
+
connectionDelegationSource.sessionId,
|
|
2530
|
+
connectionDelegationSource.turnId,
|
|
2531
|
+
)
|
|
2532
|
+
: null;
|
|
2533
|
+
const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(
|
|
2120
2534
|
db,
|
|
2121
2535
|
workspaceId,
|
|
2122
|
-
settings: runtimeSettings,
|
|
2123
|
-
tools: existingSession.tools,
|
|
2124
|
-
source: personalConnectionDelegationSourceForGrant(grant),
|
|
2125
|
-
});
|
|
2126
|
-
const { accepted, turn, interruptionCount, replay } = await postUserMessageTurn({
|
|
2127
|
-
db,
|
|
2128
|
-
bus,
|
|
2129
|
-
workflowClient,
|
|
2130
2536
|
settings,
|
|
2131
|
-
|
|
2132
|
-
workspaceId,
|
|
2133
|
-
sessionId,
|
|
2134
|
-
text: input.text,
|
|
2135
|
-
annotations,
|
|
2136
|
-
turnInstructions: input.turnInstructions ?? null,
|
|
2137
|
-
resources: requestedResources,
|
|
2138
|
-
model: input.model ?? null,
|
|
2139
|
-
reasoningEffort: input.reasoningEffort ?? null,
|
|
2140
|
-
latencyMode: input.latencyMode ?? null,
|
|
2141
|
-
reasoningEffortFallback: sessionReasoningEffort,
|
|
2142
|
-
turnExecutionPolicy,
|
|
2143
|
-
mcpCredentialUpdates,
|
|
2144
|
-
personalConnectionDelegations,
|
|
2145
|
-
delivery: input.delivery ?? "send",
|
|
2146
|
-
origin: delegatedServiceInitiator ? "operator" : (input.origin ?? "human"),
|
|
2147
|
-
actor: grant.subjectId,
|
|
2148
|
-
...(grant.subjectLabel ? { actorLabel: grant.subjectLabel } : {}),
|
|
2149
|
-
...(delegatedServiceInitiator
|
|
2537
|
+
inheritedPersonalConnectionDelegations
|
|
2150
2538
|
? {
|
|
2151
|
-
|
|
2152
|
-
type: "service" as const,
|
|
2153
|
-
subjectId: delegatedServiceInitiator.initiator.subjectId,
|
|
2154
|
-
...(delegatedServiceInitiator.initiator.label
|
|
2155
|
-
? { subjectLabel: delegatedServiceInitiator.initiator.label }
|
|
2156
|
-
: {}),
|
|
2157
|
-
context: delegatedServiceInitiator.context,
|
|
2158
|
-
},
|
|
2539
|
+
personalConnectionDelegations: inheritedPersonalConnectionDelegations,
|
|
2159
2540
|
}
|
|
2160
|
-
: {}
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2541
|
+
: { subjectId: grant.subjectId },
|
|
2542
|
+
);
|
|
2543
|
+
const personalConnectionDelegations = await freezePersonalConnectionDelegations({
|
|
2544
|
+
db,
|
|
2545
|
+
workspaceId,
|
|
2546
|
+
settings: runtimeSettings,
|
|
2547
|
+
tools: existingSession.tools,
|
|
2548
|
+
resources: [...existingSession.resources, ...requestedResources],
|
|
2549
|
+
source: connectionDelegationSource,
|
|
2550
|
+
targetSessionId: sessionId,
|
|
2551
|
+
googleDrivePublicationEnabled:
|
|
2552
|
+
existingSession.firstPartyMcpTools.includes("editable_artifact_export") &&
|
|
2553
|
+
existingSession.firstPartyMcpTools.includes("editable_artifact_export_status") &&
|
|
2554
|
+
(!existingSession.firstPartyMcpPermissions?.length ||
|
|
2555
|
+
(existingSession.firstPartyMcpPermissions.includes("artifacts:read") &&
|
|
2556
|
+
existingSession.firstPartyMcpPermissions.includes("artifacts:publish"))),
|
|
2557
|
+
atlassianEnabled:
|
|
2558
|
+
existingSession.firstPartyMcpTools.some((tool) => tool.startsWith("atlassian_")) &&
|
|
2559
|
+
(!existingSession.firstPartyMcpPermissions?.length ||
|
|
2560
|
+
existingSession.firstPartyMcpPermissions.includes("connections:read")),
|
|
2561
|
+
...(input.connectionAuthorities ? { authoritySelections: input.connectionAuthorities } : {}),
|
|
2168
2562
|
});
|
|
2169
|
-
|
|
2563
|
+
const { accepted, turn, draft, receipt, routing, interruptionCount, replay } =
|
|
2564
|
+
await postUserMessageTurn({
|
|
2565
|
+
db,
|
|
2566
|
+
bus,
|
|
2567
|
+
workflowClient,
|
|
2568
|
+
settings,
|
|
2569
|
+
accountId: grant.accountId,
|
|
2570
|
+
workspaceId,
|
|
2571
|
+
sessionId,
|
|
2572
|
+
text: input.text,
|
|
2573
|
+
annotations,
|
|
2574
|
+
modelContext: input.modelContext ?? null,
|
|
2575
|
+
resources: requestedResources,
|
|
2576
|
+
model: input.model ?? null,
|
|
2577
|
+
reasoningEffort: input.reasoningEffort ?? null,
|
|
2578
|
+
latencyMode: input.latencyMode ?? null,
|
|
2579
|
+
reasoningEffortFallback: sessionReasoningEffort,
|
|
2580
|
+
turnExecutionPolicy,
|
|
2581
|
+
mcpCredentialUpdates,
|
|
2582
|
+
personalConnectionDelegations,
|
|
2583
|
+
...(input.personalResourceAttachment
|
|
2584
|
+
? { personalResourceAttachment: input.personalResourceAttachment }
|
|
2585
|
+
: {}),
|
|
2586
|
+
delivery: input.delivery ?? "send",
|
|
2587
|
+
origin: delegatedServiceInitiator ? "operator" : (input.origin ?? "human"),
|
|
2588
|
+
actor: grant.subjectId,
|
|
2589
|
+
...(grant.subjectLabel ? { actorLabel: grant.subjectLabel } : {}),
|
|
2590
|
+
...(delegatedServiceInitiator
|
|
2591
|
+
? {
|
|
2592
|
+
commandActor: {
|
|
2593
|
+
type: "service" as const,
|
|
2594
|
+
subjectId: delegatedServiceInitiator.initiator.subjectId,
|
|
2595
|
+
...(delegatedServiceInitiator.initiator.label
|
|
2596
|
+
? { subjectLabel: delegatedServiceInitiator.initiator.label }
|
|
2597
|
+
: {}),
|
|
2598
|
+
context: delegatedServiceInitiator.context,
|
|
2599
|
+
},
|
|
2600
|
+
}
|
|
2601
|
+
: {}),
|
|
2602
|
+
...(input.controlEtag !== undefined ? { controlEtag: input.controlEtag } : {}),
|
|
2603
|
+
...(input.expectedDraftRevision !== undefined
|
|
2604
|
+
? { expectedDraftRevision: input.expectedDraftRevision }
|
|
2605
|
+
: {}),
|
|
2606
|
+
...(input.clientEventId ? { clientEventId: input.clientEventId } : {}),
|
|
2607
|
+
recordAgentRunUsage: true,
|
|
2608
|
+
...(deps.schedulePromptPostCommit
|
|
2609
|
+
? { schedulePostCommit: deps.schedulePromptPostCommit }
|
|
2610
|
+
: {}),
|
|
2611
|
+
});
|
|
2612
|
+
return { accepted, turn, draft, receipt, routing, interruptionCount, replay };
|
|
2170
2613
|
}
|
|
2171
2614
|
|
|
2172
2615
|
/** Backward-compatible entity-returning path used by existing REST callers. */
|
|
@@ -2179,17 +2622,14 @@ export async function acceptSessionUserMessage(
|
|
|
2179
2622
|
): Promise<{
|
|
2180
2623
|
accepted: SessionEvent;
|
|
2181
2624
|
turn: SessionTurn;
|
|
2625
|
+
receipt: SessionCommandReceipt;
|
|
2626
|
+
routing: SessionPromptRouting;
|
|
2182
2627
|
interruptionCount: number;
|
|
2183
2628
|
replay: boolean;
|
|
2184
2629
|
}> {
|
|
2185
|
-
const { accepted, turn, interruptionCount, replay } =
|
|
2186
|
-
deps,
|
|
2187
|
-
|
|
2188
|
-
workspaceId,
|
|
2189
|
-
sessionId,
|
|
2190
|
-
input,
|
|
2191
|
-
);
|
|
2192
|
-
return { accepted, turn, interruptionCount, replay };
|
|
2630
|
+
const { accepted, turn, receipt, routing, interruptionCount, replay } =
|
|
2631
|
+
await acceptSessionUserMessageWithOutcome(deps, grant, workspaceId, sessionId, input);
|
|
2632
|
+
return { accepted, turn, receipt, routing, interruptionCount, replay };
|
|
2193
2633
|
}
|
|
2194
2634
|
|
|
2195
2635
|
/**
|
|
@@ -2336,6 +2776,7 @@ function toolPolicyAuditSnapshot(
|
|
|
2336
2776
|
kind: tool.kind,
|
|
2337
2777
|
id: tool.id,
|
|
2338
2778
|
...(tool.optional === undefined ? {} : { optional: tool.optional }),
|
|
2779
|
+
...(tool.eager === undefined ? {} : { eager: tool.eager }),
|
|
2339
2780
|
}));
|
|
2340
2781
|
const toolRefs = allToolRefs.slice(0, maxToolPolicyAuditRefs);
|
|
2341
2782
|
return {
|
|
@@ -2378,6 +2819,8 @@ export async function updateSessionToolPolicy(
|
|
|
2378
2819
|
requirePermission(grant, "sessions:control");
|
|
2379
2820
|
|
|
2380
2821
|
const existingSession = await requireSession(deps.db, grant.workspaceId, sessionId);
|
|
2822
|
+
const workspace = await requireWorkspace(deps.db, grant.workspaceId);
|
|
2823
|
+
const workspaceSessionToolDefaults = resolveWorkspaceSessionToolDefaults(workspace.settings);
|
|
2381
2824
|
const capabilityRuntimeSettings = await settingsWithEnabledCapabilityMcpServers(
|
|
2382
2825
|
deps.db,
|
|
2383
2826
|
grant.workspaceId,
|
|
@@ -2418,10 +2861,19 @@ export async function updateSessionToolPolicy(
|
|
|
2418
2861
|
});
|
|
2419
2862
|
}
|
|
2420
2863
|
const workspaceDefaultTools = withFirstPartyTools(
|
|
2421
|
-
|
|
2864
|
+
withWorkspaceDefaultMcpTools(
|
|
2865
|
+
[],
|
|
2866
|
+
deps.settings,
|
|
2867
|
+
capabilityRuntimeSettings,
|
|
2868
|
+
workspaceSessionToolDefaults,
|
|
2869
|
+
),
|
|
2422
2870
|
runtimeSettings,
|
|
2423
2871
|
);
|
|
2424
|
-
const workspaceDefaultFirstPartyTools = [
|
|
2872
|
+
const workspaceDefaultFirstPartyTools = [
|
|
2873
|
+
...(workspaceSessionToolDefaults?.firstPartyMcpTools.filter((tool) =>
|
|
2874
|
+
deploymentFirstPartyMcpToolPolicy.allowed.includes(tool),
|
|
2875
|
+
) ?? deploymentFirstPartyMcpToolPolicy.default),
|
|
2876
|
+
];
|
|
2425
2877
|
const events = await appendSessionEventsWithLockedSessionUpdate(
|
|
2426
2878
|
deps.db,
|
|
2427
2879
|
grant.workspaceId,
|
|
@@ -2445,10 +2897,11 @@ export async function updateSessionToolPolicy(
|
|
|
2445
2897
|
const parentTracksWorkspaceDefaults = parent.toolPolicy?.mode === "workspace_default";
|
|
2446
2898
|
const parentEffective = withFirstPartyTools(
|
|
2447
2899
|
parentTracksWorkspaceDefaults
|
|
2448
|
-
?
|
|
2900
|
+
? withWorkspaceDefaultMcpTools(
|
|
2449
2901
|
availableToolRefs(parent.tools, runtimeSettings),
|
|
2450
2902
|
deps.settings,
|
|
2451
2903
|
runtimeSettings,
|
|
2904
|
+
workspaceSessionToolDefaults,
|
|
2452
2905
|
)
|
|
2453
2906
|
: parent.tools,
|
|
2454
2907
|
runtimeSettings,
|