@opengeni/core 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2698,6 +2698,22 @@ function enabledCapabilityMcpToolRefs(settings, runtimeSettings) {
2698
2698
  function withDefaultEnabledCapabilityMcpTools(tools, settings, runtimeSettings) {
2699
2699
  return mergeToolRefs(tools, enabledCapabilityMcpToolRefs(settings, runtimeSettings));
2700
2700
  }
2701
+ function availableToolRefs(tools, settings) {
2702
+ const available = new Set(settings.mcpServers.map((server) => server.id));
2703
+ return tools.filter((tool) => available.has(tool.id));
2704
+ }
2705
+ function assertToolRefsSubset(requested, allowed, message = "requested tools exceed the session tool policy") {
2706
+ const allowedIds = new Set(allowed.map((tool) => `${tool.kind}:${tool.id}`));
2707
+ const widened = requested.find((tool) => !allowedIds.has(`${tool.kind}:${tool.id}`));
2708
+ if (widened) {
2709
+ throw new HTTPException8(403, { message: `${message}: ${widened.id}` });
2710
+ }
2711
+ }
2712
+ function validateToolRefsForSessionPolicy(input) {
2713
+ const validated = validateToolRefs(input.requested, input.settings);
2714
+ assertToolRefsSubset(validated, input.allowedTools, input.message);
2715
+ return validated;
2716
+ }
2701
2717
  function normalizeResources(resources) {
2702
2718
  const mountPaths = /* @__PURE__ */ new Map();
2703
2719
  const identities = /* @__PURE__ */ new Map();
@@ -2902,6 +2918,128 @@ function positiveInteger(value) {
2902
2918
  return null;
2903
2919
  }
2904
2920
 
2921
+ // src/domain/session-tool-policy.ts
2922
+ import {
2923
+ SESSION_EFFECTIVE_TOOL_POLICY_ID_LIMIT,
2924
+ SESSION_EFFECTIVE_TOOL_POLICY_ID_MAX_LENGTH,
2925
+ mergeToolRefs as mergeToolRefs2
2926
+ } from "@opengeni/contracts";
2927
+ var MANDATORY_SESSION_MCP_SERVER_IDS = ["opengeni"];
2928
+ var PROJECTABLE_REGISTRY_ID = /^[A-Za-z0-9_-]+$/;
2929
+ function sortedIds(ids) {
2930
+ return [...new Set(ids)].sort();
2931
+ }
2932
+ function projectIds(ids) {
2933
+ const projectable = ids.filter(
2934
+ (id) => id.length <= SESSION_EFFECTIVE_TOOL_POLICY_ID_MAX_LENGTH && PROJECTABLE_REGISTRY_ID.test(id)
2935
+ );
2936
+ return {
2937
+ ids: projectable.slice(0, SESSION_EFFECTIVE_TOOL_POLICY_ID_LIMIT),
2938
+ truncated: projectable.length !== ids.length || projectable.length > SESSION_EFFECTIVE_TOOL_POLICY_ID_LIMIT
2939
+ };
2940
+ }
2941
+ function resolveSessionToolPolicy(input) {
2942
+ const policy = input.toolPolicy ?? { mode: "legacy", inheritedFromSessionId: null };
2943
+ const availableIds = new Set(input.availableMcpServerIds);
2944
+ const defaultIds = new Set(input.defaultMcpServerIds ?? []);
2945
+ const mandatoryIds = MANDATORY_SESSION_MCP_SERVER_IDS.filter(
2946
+ (id) => availableIds.has(id)
2947
+ );
2948
+ const mandatoryIdSet = new Set(mandatoryIds);
2949
+ const selectedRefs = input.turnToolsProvided === true ? mergeToolRefs2([], input.turnTools ?? []) : input.turnToolsProvided === false ? mergeToolRefs2([], input.sessionTools) : mergeToolRefs2(input.sessionTools, input.turnTools ?? []);
2950
+ const tracksWorkspaceDefaults = policy.mode === "workspace_default" && input.turnToolsProvided !== true;
2951
+ let toolRefs = selectedRefs.filter((tool) => tool.optional !== true || availableIds.has(tool.id));
2952
+ if (tracksWorkspaceDefaults) {
2953
+ toolRefs = mergeToolRefs2(
2954
+ toolRefs,
2955
+ sortedIds(defaultIds).filter((id) => availableIds.has(id)).map((id) => ({ kind: "mcp", id, optional: true }))
2956
+ );
2957
+ }
2958
+ toolRefs = mergeToolRefs2(
2959
+ toolRefs,
2960
+ mandatoryIds.map((id) => ({ kind: "mcp", id }))
2961
+ );
2962
+ const requestedEffectiveRefs = mergeToolRefs2(
2963
+ selectedRefs,
2964
+ tracksWorkspaceDefaults ? sortedIds(defaultIds).filter((id) => availableIds.has(id)).map((id) => ({ kind: "mcp", id, optional: true })) : []
2965
+ );
2966
+ const effectiveIds = sortedIds(
2967
+ mergeToolRefs2(
2968
+ requestedEffectiveRefs,
2969
+ mandatoryIds.map((id) => ({ kind: "mcp", id }))
2970
+ ).map((tool) => tool.id)
2971
+ );
2972
+ const configuredIds = effectiveIds.filter((id) => availableIds.has(id));
2973
+ const configuredIdSet = new Set(configuredIds);
2974
+ const droppedIds = effectiveIds.filter((id) => !configuredIdSet.has(id));
2975
+ const deferredIds = tracksWorkspaceDefaults ? sortedIds(
2976
+ toolRefs.filter(
2977
+ (tool) => tool.optional === true && configuredIdSet.has(tool.id) && !mandatoryIdSet.has(tool.id)
2978
+ ).map((tool) => tool.id)
2979
+ ) : [];
2980
+ const selectedIds = sortedIds(
2981
+ selectedRefs.filter(
2982
+ (tool) => !mandatoryIdSet.has(tool.id) && !(tracksWorkspaceDefaults && tool.optional === true)
2983
+ ).map((tool) => tool.id)
2984
+ );
2985
+ const projections = {
2986
+ selected: projectIds(selectedIds),
2987
+ effective: projectIds(effectiveIds),
2988
+ mandatory: projectIds(sortedIds(mandatoryIds)),
2989
+ deferred: projectIds(deferredIds),
2990
+ configured: projectIds(configuredIds),
2991
+ dropped: projectIds(droppedIds)
2992
+ };
2993
+ return {
2994
+ toolRefs,
2995
+ effectivePolicy: {
2996
+ mode: policy.mode,
2997
+ inheritedFromSessionId: policy.inheritedFromSessionId,
2998
+ selectedIds: projections.selected.ids,
2999
+ effectiveIds: projections.effective.ids,
3000
+ mandatoryIds: projections.mandatory.ids,
3001
+ lazyRouter: {
3002
+ state: tracksWorkspaceDefaults ? "required" : "disabled",
3003
+ deferredIds: projections.deferred.ids
3004
+ },
3005
+ configuredIds: projections.configured.ids,
3006
+ droppedIds: projections.dropped.ids,
3007
+ counts: {
3008
+ selected: selectedIds.length,
3009
+ effective: effectiveIds.length,
3010
+ mandatory: mandatoryIds.length,
3011
+ deferred: deferredIds.length,
3012
+ configured: configuredIds.length,
3013
+ dropped: droppedIds.length
3014
+ },
3015
+ idsTruncated: Object.values(projections).some((projection) => projection.truncated)
3016
+ }
3017
+ };
3018
+ }
3019
+ async function workspaceSessionToolPolicyServerIds(db, workspaceId, settings) {
3020
+ const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings);
3021
+ return sortedIds(runtimeSettings.mcpServers.map((server) => server.id));
3022
+ }
3023
+ async function workspaceSessionToolPolicyDefaultServerIds(db, workspaceId, settings) {
3024
+ const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings);
3025
+ return sortedIds(enabledCapabilityMcpToolRefs(settings, runtimeSettings).map((tool) => tool.id));
3026
+ }
3027
+ function sessionWithEffectiveToolPolicy(session, workspaceServerIds, workspaceDefaultServerIds = []) {
3028
+ const availableIds = new Set(workspaceServerIds);
3029
+ for (const server of session.mcpServers) {
3030
+ availableIds.add(server.id);
3031
+ }
3032
+ return {
3033
+ ...session,
3034
+ effectiveToolPolicy: resolveSessionToolPolicy({
3035
+ ...session.toolPolicy ? { toolPolicy: session.toolPolicy } : {},
3036
+ sessionTools: session.tools,
3037
+ availableMcpServerIds: availableIds,
3038
+ defaultMcpServerIds: workspaceDefaultServerIds
3039
+ }).effectivePolicy
3040
+ };
3041
+ }
3042
+
2905
3043
  // src/domain/scheduled-tasks.ts
2906
3044
  import {
2907
3045
  createScheduledTask,
@@ -2914,13 +3052,20 @@ import { HTTPException as HTTPException10 } from "hono/http-exception";
2914
3052
 
2915
3053
  // src/domain/sessions.ts
2916
3054
  import { CODEX_MODEL_ID_PREFIX } from "@opengeni/codex";
2917
- import { configuredAllowedModels, policyProviderIdForModel } from "@opengeni/config";
3055
+ import {
3056
+ canonicalizeConfiguredModelId,
3057
+ configuredAllowedModels,
3058
+ policyProviderIdForModel,
3059
+ resolveTurnExecutionPolicyV1
3060
+ } from "@opengeni/config";
2918
3061
  import {
2919
3062
  CreateSessionRequest,
3063
+ DEFAULT_FIRST_PARTY_MCP_PERMISSIONS,
2920
3064
  ServiceTurnInitiator,
2921
3065
  ServiceTurnInitiatorContext,
2922
3066
  evaluateWorkspaceModelPolicy,
2923
- reasoningEffortForMetadata
3067
+ reasoningEffortForMetadata,
3068
+ SessionMcpApprovalPolicy
2924
3069
  } from "@opengeni/contracts";
2925
3070
  import {
2926
3071
  createSession,
@@ -2946,6 +3091,7 @@ import {
2946
3091
  listSessionMcpServersForChildInheritance,
2947
3092
  requireSession as requireSession2,
2948
3093
  submitHumanPromptInTransaction,
3094
+ appendSessionEventsWithLockedSessionUpdate,
2949
3095
  updateSessionTitle as updateSessionTitleRow,
2950
3096
  withWorkspaceSubjectRls,
2951
3097
  QueueCommandConflictError,
@@ -3091,6 +3237,7 @@ function mcpServerConfigFromMetadata(server) {
3091
3237
  ...server.name ? { name: server.name } : {},
3092
3238
  url: server.url,
3093
3239
  cacheToolsList: false,
3240
+ requireApproval: server.requireApproval,
3094
3241
  ...server.connectionRef ? { connectionRef: server.connectionRef } : {}
3095
3242
  };
3096
3243
  }
@@ -3151,6 +3298,7 @@ function validateSessionMcpServersForCreate(settings, grant, servers) {
3151
3298
  url: server.url,
3152
3299
  headerNames: Object.keys(headersEncrypted).sort(),
3153
3300
  credentialVersion: 1,
3301
+ requireApproval: server.requireApproval ?? false,
3154
3302
  connectionRef: server.connectionRef ?? null
3155
3303
  });
3156
3304
  }
@@ -3186,6 +3334,7 @@ function validateInheritedSessionMcpServersForCreate(servers) {
3186
3334
  url: server.url,
3187
3335
  headerNames: Object.keys(server.headersEncrypted ?? {}).sort(),
3188
3336
  credentialVersion: 1,
3337
+ requireApproval: server.requireApproval ?? false,
3189
3338
  connectionRef: server.connectionRef ?? null
3190
3339
  }))
3191
3340
  };
@@ -3250,6 +3399,7 @@ async function createAndStartSession(input) {
3250
3399
  initialTurnInstructions: input.turnInstructions ?? null,
3251
3400
  resources: input.resources,
3252
3401
  tools: input.tools,
3402
+ ...input.toolPolicy ? { toolPolicy: input.toolPolicy } : {},
3253
3403
  metadata: sessionMetadata,
3254
3404
  ...input.createdBy ? { createdBy: input.createdBy } : {},
3255
3405
  ...input.createdByContext ? { createdByContext: input.createdByContext } : {},
@@ -3283,6 +3433,7 @@ async function createAndStartSession(input) {
3283
3433
  initialTurnInstructions: input.turnInstructions ?? null,
3284
3434
  resources: input.resources,
3285
3435
  tools: input.tools,
3436
+ ...input.toolPolicy ? { toolPolicy: input.toolPolicy } : {},
3286
3437
  metadata: sessionMetadata,
3287
3438
  ...input.createdBy ? { createdBy: input.createdBy } : {},
3288
3439
  ...input.createdByContext ? { createdByContext: input.createdByContext } : {},
@@ -3335,7 +3486,9 @@ async function finishStartSession(input, session) {
3335
3486
  sessionId: session.id,
3336
3487
  ...input.clientEventId ? { clientEventId: input.clientEventId } : {},
3337
3488
  reasoningEffortFallback: input.reasoningEffort,
3489
+ turnExecutionPolicy: input.turnExecutionPolicy,
3338
3490
  createdEventPayload: {
3491
+ ...input.toolPolicy ? { toolPolicy: input.toolPolicy } : {},
3339
3492
  ...input.variableSet ? { variableSetId: input.variableSet.id, variableSetName: input.variableSet.name } : {},
3340
3493
  ...input.sessionMcpServers?.length ? { mcpServers: input.sessionMcpServers } : {}
3341
3494
  },
@@ -3362,31 +3515,42 @@ async function finishStartSession(input, session) {
3362
3515
  function workflowIdForSession(sessionId) {
3363
3516
  return `session-${sessionId}`;
3364
3517
  }
3365
- function assertConfiguredModel(settings, model) {
3518
+ function canonicalConfiguredModel(settings, model) {
3366
3519
  if (model === null || model === void 0) {
3367
- return;
3520
+ return model;
3368
3521
  }
3369
- if (configuredAllowedModels(settings).includes(model)) {
3370
- return;
3522
+ const canonicalModel = canonicalizeConfiguredModelId(settings, model);
3523
+ if (configuredAllowedModels(settings).includes(canonicalModel)) {
3524
+ return canonicalModel;
3371
3525
  }
3372
- if (settings.codexSubscriptionEnabled && model.startsWith(CODEX_MODEL_ID_PREFIX)) {
3373
- return;
3526
+ if (settings.codexSubscriptionEnabled && canonicalModel.startsWith(CODEX_MODEL_ID_PREFIX)) {
3527
+ return canonicalModel;
3374
3528
  }
3375
3529
  throw new HTTPException9(422, { message: `model is not available: ${model}` });
3376
3530
  }
3531
+ function assertConfiguredModel(settings, model) {
3532
+ canonicalConfiguredModel(settings, model);
3533
+ }
3377
3534
  async function assertWorkspaceModelPolicyAllows(db, settings, workspaceId, model) {
3378
3535
  if (model === null || model === void 0) {
3379
3536
  return;
3380
3537
  }
3538
+ const canonicalModel = canonicalConfiguredModel(settings, model);
3539
+ if (canonicalModel === null || canonicalModel === void 0) {
3540
+ return;
3541
+ }
3381
3542
  const policy = await getWorkspaceModelPolicy(db, workspaceId);
3382
3543
  if (!policy) {
3383
3544
  return;
3384
3545
  }
3385
- const providerId = policyProviderIdForModel(settings, model);
3386
- const verdict = evaluateWorkspaceModelPolicy(policy, { providerId, modelId: model });
3546
+ const providerId = policyProviderIdForModel(settings, canonicalModel);
3547
+ const verdict = evaluateWorkspaceModelPolicy(policy, {
3548
+ providerId,
3549
+ modelId: canonicalModel
3550
+ });
3387
3551
  if (!verdict.allowed) {
3388
3552
  throw new HTTPException9(422, {
3389
- message: verdict.reason === "provider" ? `model "${model}" is not allowed by this workspace's model policy: provider "${providerId}" is not in the allowed providers` : `model "${model}" is not allowed by this workspace's model policy`
3553
+ message: verdict.reason === "provider" ? `model "${canonicalModel}" is not allowed by this workspace's model policy: provider "${providerId}" is not in the allowed providers` : `model "${canonicalModel}" is not allowed by this workspace's model policy`
3390
3554
  });
3391
3555
  }
3392
3556
  }
@@ -3407,7 +3571,7 @@ function reasoningEffortForSession(metadata, fallback) {
3407
3571
  }
3408
3572
  async function postUserMessageTurn(input) {
3409
3573
  const { db, bus, workflowClient, settings, accountId, workspaceId, sessionId } = input;
3410
- const requestedModel = input.model ?? null;
3574
+ const requestedModel = canonicalConfiguredModel(settings, input.model ?? null) ?? null;
3411
3575
  const requestedReasoningEffort = input.reasoningEffort ?? null;
3412
3576
  assertConfiguredModel(settings, requestedModel);
3413
3577
  await assertWorkspaceModelPolicyAllows(db, settings, workspaceId, requestedModel);
@@ -3437,9 +3601,11 @@ async function postUserMessageTurn(input) {
3437
3601
  turnInstructions: input.turnInstructions ?? null,
3438
3602
  resources: input.resources,
3439
3603
  tools: input.tools,
3604
+ toolsProvided: input.toolsProvided,
3440
3605
  model: requestedModel,
3441
3606
  reasoningEffort: requestedReasoningEffort,
3442
- reasoningEffortFallback: settings.openaiReasoningEffort,
3607
+ reasoningEffortFallback: input.reasoningEffortFallback ?? settings.openaiReasoningEffort,
3608
+ turnExecutionPolicy: input.turnExecutionPolicy,
3443
3609
  source: input.origin === "operator" ? "api" : "user",
3444
3610
  mcpCredentialUpdates: input.mcpCredentialUpdates ?? []
3445
3611
  })
@@ -3535,12 +3701,50 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
3535
3701
  const resources = normalizeResources(
3536
3702
  hasOwnProperty(rawPayload, "resources") ? payload.resources : parentSession?.resources ?? payload.resources
3537
3703
  );
3704
+ const toolsProvided = hasOwnProperty(rawPayload, "tools");
3538
3705
  const requestedTools = validateToolRefs(
3539
- hasOwnProperty(rawPayload, "tools") ? payload.tools : parentSession?.tools ?? payload.tools,
3706
+ toolsProvided ? payload.tools : parentSession?.tools ?? payload.tools,
3540
3707
  runtimeSettings
3541
3708
  );
3542
- const defaultedTools = hasOwnProperty(rawPayload, "tools") || parentSession ? requestedTools : withDefaultEnabledCapabilityMcpTools(requestedTools, settings, capabilityRuntimeSettings);
3543
- const tools = withFirstPartyTools(defaultedTools, runtimeSettings);
3709
+ let selectedTools;
3710
+ let toolPolicy;
3711
+ if (parentSession) {
3712
+ const parentTracksWorkspaceDefaults = parentSession.toolPolicy?.mode === "workspace_default";
3713
+ const parentEffective = withFirstPartyTools(
3714
+ parentTracksWorkspaceDefaults ? withDefaultEnabledCapabilityMcpTools(
3715
+ availableToolRefs(parentSession.tools, runtimeSettings),
3716
+ settings,
3717
+ runtimeSettings
3718
+ ) : parentSession.tools,
3719
+ runtimeSettings
3720
+ );
3721
+ if (toolsProvided) {
3722
+ assertToolRefsSubset(
3723
+ requestedTools,
3724
+ parentEffective,
3725
+ "child tools may only narrow the parent session tool policy"
3726
+ );
3727
+ selectedTools = requestedTools;
3728
+ toolPolicy = { mode: "explicit", inheritedFromSessionId: parentSession.id };
3729
+ } else {
3730
+ selectedTools = parentEffective;
3731
+ toolPolicy = {
3732
+ mode: parentTracksWorkspaceDefaults ? "workspace_default" : "inherited",
3733
+ inheritedFromSessionId: parentSession.id
3734
+ };
3735
+ }
3736
+ } else if (toolsProvided) {
3737
+ selectedTools = requestedTools;
3738
+ toolPolicy = { mode: "explicit", inheritedFromSessionId: null };
3739
+ } else {
3740
+ selectedTools = withDefaultEnabledCapabilityMcpTools(
3741
+ requestedTools,
3742
+ settings,
3743
+ capabilityRuntimeSettings
3744
+ );
3745
+ toolPolicy = { mode: "workspace_default", inheritedFromSessionId: null };
3746
+ }
3747
+ const tools = withFirstPartyTools(selectedTools, runtimeSettings);
3544
3748
  await validateGitHubRepositorySelection(db, workspaceId, resources);
3545
3749
  if (resources.some((resource) => resource.kind === "file") && !objectStorage) {
3546
3750
  throw new HTTPException9(503, { message: "object storage is not configured" });
@@ -3568,16 +3772,30 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
3568
3772
  frozenRigVersionId = rig.activeVersion.id;
3569
3773
  }
3570
3774
  }
3571
- assertConfiguredModel(settings, payload.model);
3572
- await assertWorkspaceModelPolicyAllows(
3573
- db,
3574
- settings,
3575
- workspaceId,
3576
- payload.model ?? settings.openaiModel
3577
- );
3578
- const model = payload.model ?? settings.openaiModel;
3775
+ const model = canonicalConfiguredModel(settings, payload.model ?? settings.openaiModel);
3776
+ if (model === null || model === void 0) {
3777
+ throw new Error("effective session model unexpectedly resolved to null");
3778
+ }
3779
+ await assertWorkspaceModelPolicyAllows(db, settings, workspaceId, model);
3579
3780
  const reasoningEffort = payload.reasoningEffort ?? settings.openaiReasoningEffort;
3580
- let firstPartyMcpPermissions = payload.firstPartyMcpPermissions ?? (parentSessionId ? [...new Set(grant.permissions)] : null);
3781
+ const turnExecutionPolicy = resolveTurnExecutionPolicyV1(settings, {
3782
+ modelId: model,
3783
+ requestedModelId: payload.model ?? null,
3784
+ modelSource: payload.model === void 0 ? "deployment" : "explicit",
3785
+ reasoningEffort,
3786
+ reasoningSource: payload.reasoningEffort === void 0 ? "deployment" : "explicit"
3787
+ });
3788
+ const parentFirstPartyMcpPermissions = parentSession ? [...parentSession.firstPartyMcpPermissions ?? DEFAULT_FIRST_PARTY_MCP_PERMISSIONS] : null;
3789
+ if (parentFirstPartyMcpPermissions && payload.firstPartyMcpPermissions?.some(
3790
+ (permission) => !hasPermission(parentFirstPartyMcpPermissions, permission)
3791
+ )) {
3792
+ throw new HTTPException9(403, {
3793
+ message: "child first-party MCP permissions may only narrow the parent session grant"
3794
+ });
3795
+ }
3796
+ let firstPartyMcpPermissions = payload.firstPartyMcpPermissions ?? (parentFirstPartyMcpPermissions ? parentFirstPartyMcpPermissions.filter(
3797
+ (permission) => hasPermission(grant.permissions, permission)
3798
+ ) : null);
3581
3799
  if (firstPartyMcpPermissions && firstPartyMcpPermissions.length === 0) {
3582
3800
  throw new HTTPException9(422, {
3583
3801
  message: "firstPartyMcpPermissions must not be empty; omit it for the default worker permission set"
@@ -3710,9 +3928,11 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
3710
3928
  turnInstructions: payload.turnInstructions ?? null,
3711
3929
  resources,
3712
3930
  tools,
3931
+ toolPolicy,
3713
3932
  ...payload.clientEventId ? { clientEventId: payload.clientEventId } : {},
3714
3933
  model,
3715
3934
  reasoningEffort,
3935
+ turnExecutionPolicy,
3716
3936
  // A shared spawn inherits the box's backend; a caller-supplied
3717
3937
  // sandboxBackend on a shared spawn is ignored (it is the same box). A
3718
3938
  // machine-targeted top-level create labels the home "selfhosted"
@@ -3777,6 +3997,11 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
3777
3997
  return session;
3778
3998
  }
3779
3999
  async function acceptSessionUserMessage(deps, grant, workspaceId, sessionId, input) {
4000
+ if (input.toolsProvided && !deps.settings.sessionTurnToolReplacementEnabled) {
4001
+ throw new HTTPException9(503, {
4002
+ message: "explicit follow-up tool replacement is temporarily unavailable until provenance-aware turn workers finish rolling out; omit tools to inherit the session policy and retry"
4003
+ });
4004
+ }
3780
4005
  const { settings, db, bus, workflowClient, objectStorage } = deps;
3781
4006
  await requireSessionAuthorization(deps, grant, {
3782
4007
  sessionId,
@@ -3789,19 +4014,50 @@ async function acceptSessionUserMessage(deps, grant, workspaceId, sessionId, inp
3789
4014
  settings
3790
4015
  );
3791
4016
  const existingSession = await requireSession2(db, workspaceId, sessionId);
4017
+ const requestedModel = canonicalConfiguredModel(settings, input.model ?? null) ?? null;
4018
+ const effectiveModel = canonicalConfiguredModel(settings, requestedModel ?? existingSession.model) ?? null;
4019
+ if (effectiveModel === null) {
4020
+ throw new Error("effective follow-up model unexpectedly resolved to null");
4021
+ }
4022
+ const sessionReasoningEffort = reasoningEffortForSession(
4023
+ existingSession.metadata,
4024
+ settings.openaiReasoningEffort
4025
+ );
4026
+ const effectiveReasoningEffort = input.reasoningEffort ?? sessionReasoningEffort;
4027
+ const turnExecutionPolicy = resolveTurnExecutionPolicyV1(settings, {
4028
+ modelId: effectiveModel,
4029
+ requestedModelId: input.model ?? null,
4030
+ modelSource: input.model == null ? "session" : "explicit",
4031
+ reasoningEffort: effectiveReasoningEffort,
4032
+ reasoningSource: input.reasoningEffort == null ? "session" : "explicit"
4033
+ });
3792
4034
  const runtimeSettings = settingsWithSessionMcpServerMetadata(
3793
4035
  capabilityRuntimeSettings,
3794
4036
  existingSession.mcpServers
3795
4037
  );
3796
4038
  const requestedResources = normalizeResources(input.resources ?? []);
3797
- const validatedTools = validateToolRefs(input.tools ?? [], runtimeSettings);
3798
- const requestedTools = input.toolsProvided ? validatedTools : withDefaultEnabledCapabilityMcpTools(validatedTools, settings, capabilityRuntimeSettings);
4039
+ const tracksWorkspaceDefaults = existingSession.toolPolicy?.mode === "workspace_default";
4040
+ const sessionPolicyTools = withFirstPartyTools(
4041
+ tracksWorkspaceDefaults ? withDefaultEnabledCapabilityMcpTools(
4042
+ availableToolRefs(existingSession.tools, runtimeSettings),
4043
+ settings,
4044
+ capabilityRuntimeSettings
4045
+ ) : existingSession.tools,
4046
+ runtimeSettings
4047
+ );
4048
+ const validatedTools = input.toolsProvided ? validateToolRefsForSessionPolicy({
4049
+ requested: input.tools ?? [],
4050
+ settings: runtimeSettings,
4051
+ allowedTools: sessionPolicyTools,
4052
+ message: "message tools may only narrow the session tool policy"
4053
+ }) : [];
4054
+ const requestedTools = input.toolsProvided ? validatedTools : [];
3799
4055
  await requireLimit(deps, {
3800
4056
  accountId: grant.accountId,
3801
4057
  workspaceId,
3802
4058
  action: "agent_run:create",
3803
4059
  quantity: 1,
3804
- model: input.model ?? existingSession.model
4060
+ model: effectiveModel
3805
4061
  });
3806
4062
  if (requestedResources.some((resource) => resource.kind === "file") && !objectStorage) {
3807
4063
  throw new HTTPException9(503, { message: "object storage is not configured" });
@@ -3830,8 +4086,11 @@ async function acceptSessionUserMessage(deps, grant, workspaceId, sessionId, inp
3830
4086
  turnInstructions: input.turnInstructions ?? null,
3831
4087
  resources: requestedResources,
3832
4088
  tools: requestedTools,
4089
+ toolsProvided: input.toolsProvided,
3833
4090
  model: input.model ?? null,
3834
4091
  reasoningEffort: input.reasoningEffort ?? null,
4092
+ reasoningEffortFallback: sessionReasoningEffort,
4093
+ turnExecutionPolicy,
3835
4094
  mcpCredentialUpdates,
3836
4095
  delivery: input.delivery ?? "send",
3837
4096
  origin: delegatedServiceInitiator ? "operator" : input.origin ?? "human",
@@ -3892,6 +4151,48 @@ async function updateSessionTitle(deps, grant, sessionId, title, source) {
3892
4151
  relatedSessionAccess: authorization?.relatedSessionAccess ?? "root"
3893
4152
  };
3894
4153
  }
4154
+ async function updateSessionMcpApprovalPolicy(deps, grant, sessionId, serverId, requireApproval) {
4155
+ const normalizedPolicy = SessionMcpApprovalPolicy.parse(requireApproval);
4156
+ await requireSessionAuthorization(deps, grant, {
4157
+ sessionId,
4158
+ operation: "session.mcp.approval_policy.write",
4159
+ surface: "core"
4160
+ });
4161
+ requirePermission(grant, "sessions:control");
4162
+ const outcome = {};
4163
+ const events = await appendSessionEventsWithLockedSessionUpdate(
4164
+ deps.db,
4165
+ grant.workspaceId,
4166
+ sessionId,
4167
+ async (_session, context) => {
4168
+ const result = await context.updateSessionMcpApprovalPolicy(serverId, normalizedPolicy);
4169
+ if (!result.server) {
4170
+ throw new HTTPException9(404, { message: "session MCP server not found" });
4171
+ }
4172
+ outcome.server = result.server;
4173
+ return {
4174
+ events: result.changed ? [
4175
+ {
4176
+ type: "session.mcp.approval_policy.updated",
4177
+ payload: {
4178
+ serverId,
4179
+ effectiveFrom: "next_attempt"
4180
+ }
4181
+ }
4182
+ ] : []
4183
+ };
4184
+ }
4185
+ );
4186
+ const updatedServer = outcome.server;
4187
+ if (!updatedServer) {
4188
+ throw new Error("session MCP approval policy update returned no server");
4189
+ }
4190
+ await publishDurableSessionEvents(deps.bus, grant.workspaceId, sessionId, events);
4191
+ return {
4192
+ server: updatedServer,
4193
+ effectiveFrom: "next_attempt"
4194
+ };
4195
+ }
3895
4196
  async function readSessionLineage(deps, grant, sessionId) {
3896
4197
  const authorization = await requireSessionAuthorization(deps, grant, {
3897
4198
  sessionId,
@@ -4095,13 +4396,8 @@ function manualScheduledTaskTriggerUsageKey(workspaceId, taskId, triggerToken) {
4095
4396
  return `agent_run.created:scheduled-trigger:${workspaceId}:${taskId}:${triggerToken}`;
4096
4397
  }
4097
4398
  async function validateScheduledTaskAgentConfig(input) {
4098
- assertConfiguredModel(input.settings, input.payload.agentConfig.model);
4099
- await assertWorkspaceModelPolicyAllows(
4100
- input.db,
4101
- input.settings,
4102
- input.workspaceId,
4103
- input.payload.agentConfig.model
4104
- );
4399
+ const model = canonicalConfiguredModel(input.settings, input.payload.agentConfig.model);
4400
+ await assertWorkspaceModelPolicyAllows(input.db, input.settings, input.workspaceId, model);
4105
4401
  const resources = normalizeResources(input.payload.agentConfig.resources ?? []);
4106
4402
  const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(
4107
4403
  input.db,
@@ -4121,6 +4417,7 @@ async function validateScheduledTaskAgentConfig(input) {
4121
4417
  await validateFileResources(input.db, input.workspaceId, resources);
4122
4418
  return {
4123
4419
  ...input.payload.agentConfig,
4420
+ ...model === void 0 || model === null ? {} : { model },
4124
4421
  prompt,
4125
4422
  resources,
4126
4423
  tools
@@ -4423,6 +4720,7 @@ function composerDraft(row) {
4423
4720
  text: row.text,
4424
4721
  resources: row.resources,
4425
4722
  tools: row.tools,
4723
+ toolsProvided: row.toolsProvided,
4426
4724
  model: row.model,
4427
4725
  reasoningEffort: row.reasoningEffort,
4428
4726
  sourceTurnId: row.sourceTurnId,
@@ -4641,6 +4939,7 @@ async function getHumanComposerDraft(deps, context) {
4641
4939
  text: "",
4642
4940
  resources: [],
4643
4941
  tools: [],
4942
+ toolsProvided: false,
4644
4943
  model: session.model,
4645
4944
  reasoningEffort: reasoningEffortForMetadata2(session.metadata, "medium"),
4646
4945
  sourceTurnId: null,
@@ -4686,12 +4985,15 @@ export {
4686
4985
  assertAllowedVariableSetVariableName,
4687
4986
  assertConfiguredModel,
4688
4987
  assertPackSandboxImageCompatible,
4988
+ assertToolRefsSubset,
4689
4989
  assertWorkspaceDeletable,
4690
4990
  assertWorkspaceMemberRemovable,
4691
4991
  assertWorkspaceModelPolicyAllows,
4992
+ availableToolRefs,
4692
4993
  buildCapabilityCatalog,
4693
4994
  buildFleetContextForSession,
4694
4995
  buildMarketingDailyAnalysisAgentConfig,
4996
+ canonicalConfiguredModel,
4695
4997
  checkLimit,
4696
4998
  classifyRigVerificationOutcome,
4697
4999
  controlAgentSessionWorkstream,
@@ -4755,6 +5057,7 @@ export {
4755
5057
  requireVariableSetForApi,
4756
5058
  resolveCapabilityPack,
4757
5059
  resolveMemberSubjectId,
5060
+ resolveSessionToolPolicy,
4758
5061
  restoreScheduledTask,
4759
5062
  rigActorForGrant,
4760
5063
  routingEnabled,
@@ -4764,6 +5067,7 @@ export {
4764
5067
  scheduledTaskToolsProvided,
4765
5068
  scheduledTaskTriggerToken,
4766
5069
  sendAgentSessionMessage,
5070
+ sessionWithEffectiveToolPolicy,
4767
5071
  settingsWithEnabledCapabilityMcpServers,
4768
5072
  settingsWithMcpCapabilityServers,
4769
5073
  settingsWithSessionMcpServerMetadata,
@@ -4774,6 +5078,7 @@ export {
4774
5078
  syncCreatedScheduledTask,
4775
5079
  syncUpdatedScheduledTask,
4776
5080
  updateRigForApi,
5081
+ updateSessionMcpApprovalPolicy,
4777
5082
  updateSessionTitle,
4778
5083
  validateFileResources,
4779
5084
  validateGitHubRepositorySelection,
@@ -4781,10 +5086,13 @@ export {
4781
5086
  validateGitHubRepositorySelectionShapes,
4782
5087
  validateMcpCapabilityConnection,
4783
5088
  validateToolRefs,
5089
+ validateToolRefsForSessionPolicy,
4784
5090
  validateVariableSetAttachment,
4785
5091
  validatedScheduledTaskUpdate,
4786
5092
  withDefaultEnabledCapabilityMcpTools,
4787
5093
  workflowIdForSession,
5094
+ workspaceSessionToolPolicyDefaultServerIds,
5095
+ workspaceSessionToolPolicyServerIds,
4788
5096
  wrapChannelABoxWithRouting
4789
5097
  };
4790
5098
  //# sourceMappingURL=index.js.map