@opengeni/db 0.9.3 → 0.10.7
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/{chunk-4LG5NBTC.js → chunk-P6PKXY5W.js} +93 -1
- package/dist/chunk-P6PKXY5W.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1332 -178
- package/dist/index.js.map +1 -1
- package/dist/provision-roles.d.ts +406 -32
- package/dist/{schema-CdPGTHlD.d.ts → schema-CqkzrBRS.d.ts} +513 -2
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +3 -1
- package/drizzle/0053_codex_credential_leases.sql +2 -2
- package/drizzle/0057_durable_queue_control.sql +1 -1
- package/drizzle/0061_session_workflow_wake_outbox.sql +1 -1
- package/drizzle/0062_session_list_snapshot_reaper.sql +1 -1
- package/drizzle/0063_session_control_mega_foundation.sql +1 -1
- package/drizzle/0064_rotation_strategy_sharded_backfill.sql +1 -1
- package/drizzle/0065_codex_subscription_overview.sql +168 -0
- package/drizzle/0065_session_tool_policy.sql +38 -0
- package/drizzle/0067_session_event_payload_bounds.sql +2 -2
- package/drizzle/0068_workspace_control_event_bounds.sql +2 -2
- package/drizzle/0069_session_event_history_backfill.sql +2 -2
- package/drizzle/0074_session_activity_revisions.sql +2 -2
- package/drizzle/0106_session_attempt_mcp_approval_policies.sql +29 -0
- package/drizzle/0107_host_export_lineage_contract.sql +381 -0
- package/drizzle/0108_fence_invalidated_warming_epochs.sql +76 -0
- package/package.json +5 -4
- package/src/codex-token-resolver.ts +175 -14
- package/src/connection-token-resolver.ts +143 -120
- package/src/event-payload-sanitizer.ts +32 -2
- package/src/index.ts +1888 -205
- package/src/schema.ts +107 -1
- package/src/session-control.ts +2 -0
- package/src/session-queue-commands.ts +94 -21
- package/dist/chunk-4LG5NBTC.js.map +0 -1
package/src/schema.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { McpServerConnectionRef } from "@opengeni/contracts";
|
|
1
|
+
import type { McpServerConnectionRef, SessionMcpApprovalPolicy } from "@opengeni/contracts";
|
|
2
2
|
import { sql } from "drizzle-orm";
|
|
3
|
+
import type { SessionToolPolicy } from "@opengeni/contracts";
|
|
3
4
|
import type { HumanInputQuestion, HumanInputResponse } from "@opengeni/contracts";
|
|
4
5
|
import {
|
|
5
6
|
bigint,
|
|
@@ -320,6 +321,19 @@ export const codexSubscriptionCredentials = pgTable(
|
|
|
320
321
|
// refresh, encrypted material, and already-frozen/in-flight turns are
|
|
321
322
|
// intentionally independent. account eligibility policy owns toggle OCC/audit and product UI.
|
|
322
323
|
allocatorEnabled: boolean("allocator_enabled").notNull().default(true),
|
|
324
|
+
// Independent OCC/audit sequence for the allocator toggle. Token refresh
|
|
325
|
+
// continues to own `version`; quota/cache writes own neither counter.
|
|
326
|
+
allocatorVersion: integer("allocator_version").notNull().default(1),
|
|
327
|
+
allocatorUpdatedBySubjectId: text("allocator_updated_by_subject_id"),
|
|
328
|
+
allocatorUpdatedAt: timestamp("allocator_updated_at", { withTimezone: true }),
|
|
329
|
+
// Authoritative count-only summary cached from /wham/usage. Detailed rows
|
|
330
|
+
// are never persisted as redemption authority; every first POST preflights
|
|
331
|
+
// the provider's fresh detail endpoint.
|
|
332
|
+
resetCreditAvailableCount: integer("reset_credit_available_count"),
|
|
333
|
+
resetCreditsCheckedAt: timestamp("reset_credits_checked_at", { withTimezone: true }),
|
|
334
|
+
// Set only by a direct Better Auth cookie connection/reconnection. Legacy,
|
|
335
|
+
// configured, delegated, API-key, and agent-created rows remain view-only.
|
|
336
|
+
connectedBySubjectId: text("connected_by_subject_id"),
|
|
323
337
|
selectionCount: integer("selection_count").notNull().default(0),
|
|
324
338
|
lastSelectedAt: timestamp("last_selected_at", { withTimezone: true }),
|
|
325
339
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
@@ -343,6 +357,82 @@ export const codexSubscriptionCredentials = pgTable(
|
|
|
343
357
|
}),
|
|
344
358
|
);
|
|
345
359
|
|
|
360
|
+
// One durable logical human redemption. `processing` means the fresh provider
|
|
361
|
+
// detail preflight is still owed; `provider_started` means the POST may have
|
|
362
|
+
// reached upstream and every retry must reuse upstreamIdempotencyKey without
|
|
363
|
+
// requiring the credit to remain visible as available.
|
|
364
|
+
export const codexResetRedemptionAttempts = pgTable(
|
|
365
|
+
"codex_reset_redemption_attempts",
|
|
366
|
+
{
|
|
367
|
+
id: uuid("id").primaryKey(),
|
|
368
|
+
accountId: uuid("account_id")
|
|
369
|
+
.notNull()
|
|
370
|
+
.references(() => managedAccounts.id, { onDelete: "cascade" }),
|
|
371
|
+
workspaceId: uuid("workspace_id")
|
|
372
|
+
.notNull()
|
|
373
|
+
.references(() => workspaces.id, { onDelete: "cascade" }),
|
|
374
|
+
credentialId: uuid("credential_id").notNull(),
|
|
375
|
+
subjectId: text("subject_id").notNull(),
|
|
376
|
+
browserSessionHash: text("browser_session_hash").notNull(),
|
|
377
|
+
creditId: text("credit_id").notNull(),
|
|
378
|
+
upstreamIdempotencyKey: uuid("upstream_idempotency_key").notNull().defaultRandom(),
|
|
379
|
+
status: text("status").notNull().default("processing"),
|
|
380
|
+
outcome: text("outcome"),
|
|
381
|
+
claimHolderId: uuid("claim_holder_id"),
|
|
382
|
+
claimExpiresAt: timestamp("claim_expires_at", { withTimezone: true }),
|
|
383
|
+
confirmationExpiresAt: timestamp("confirmation_expires_at", { withTimezone: true }).notNull(),
|
|
384
|
+
providerStartedAt: timestamp("provider_started_at", { withTimezone: true }),
|
|
385
|
+
completedAt: timestamp("completed_at", { withTimezone: true }),
|
|
386
|
+
lastFailureKind: text("last_failure_kind"),
|
|
387
|
+
retryCount: integer("retry_count").notNull().default(0),
|
|
388
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
389
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
390
|
+
},
|
|
391
|
+
(table) => ({
|
|
392
|
+
workspaceAccount: foreignKey({
|
|
393
|
+
name: "codex_reset_redemption_workspace_account_fk",
|
|
394
|
+
columns: [table.workspaceId, table.accountId],
|
|
395
|
+
foreignColumns: [workspaces.id, workspaces.accountId],
|
|
396
|
+
}).onDelete("cascade"),
|
|
397
|
+
upstreamKey: uniqueIndex("codex_reset_redemption_upstream_key_idx").on(
|
|
398
|
+
table.upstreamIdempotencyKey,
|
|
399
|
+
),
|
|
400
|
+
credentialCredit: uniqueIndex("codex_reset_redemption_credential_credit_idx")
|
|
401
|
+
.on(table.workspaceId, table.credentialId, table.creditId)
|
|
402
|
+
.where(
|
|
403
|
+
sql`${table.status} <> 'completed' or ${table.outcome} in ('reset', 'alreadyRedeemed')`,
|
|
404
|
+
),
|
|
405
|
+
workspaceCredential: index("codex_reset_redemption_workspace_credential_idx").on(
|
|
406
|
+
table.workspaceId,
|
|
407
|
+
table.credentialId,
|
|
408
|
+
table.createdAt,
|
|
409
|
+
),
|
|
410
|
+
claimExpiry: index("codex_reset_redemption_claim_expiry_idx")
|
|
411
|
+
.on(table.claimExpiresAt)
|
|
412
|
+
.where(sql`${table.status} <> 'completed'`),
|
|
413
|
+
statusValid: check(
|
|
414
|
+
"codex_reset_redemption_status_check",
|
|
415
|
+
sql`${table.status} in ('processing', 'provider_started', 'completed')`,
|
|
416
|
+
),
|
|
417
|
+
outcomeValid: check(
|
|
418
|
+
"codex_reset_redemption_outcome_check",
|
|
419
|
+
sql`${table.outcome} is null or ${table.outcome} in ('reset', 'nothingToReset', 'noCredit', 'alreadyRedeemed')`,
|
|
420
|
+
),
|
|
421
|
+
completionConsistent: check(
|
|
422
|
+
"codex_reset_redemption_completed_check",
|
|
423
|
+
sql`(${table.status} = 'completed') = (${table.outcome} is not null and ${table.completedAt} is not null)`,
|
|
424
|
+
),
|
|
425
|
+
retryCountValid: check(
|
|
426
|
+
"codex_reset_redemption_retry_count_check",
|
|
427
|
+
sql`${table.retryCount} >= 0`,
|
|
428
|
+
),
|
|
429
|
+
humanSubjectValid: check(
|
|
430
|
+
"codex_reset_redemption_human_subject_check",
|
|
431
|
+
sql`${table.subjectId} like 'user:_%'`,
|
|
432
|
+
),
|
|
433
|
+
}),
|
|
434
|
+
);
|
|
435
|
+
|
|
346
436
|
// Generic external-service credential spine. credential_encrypted is the ONLY
|
|
347
437
|
// secret-bearing column; normal API reads use metadata-only helpers below the DB
|
|
348
438
|
// layer. Runtime token material is decrypted only by the broker accessor.
|
|
@@ -634,6 +724,10 @@ export const sessions = pgTable(
|
|
|
634
724
|
// Non-default first-party MCP token permissions (manager-style sessions);
|
|
635
725
|
// null means the fixed worker default set in @opengeni/runtime.
|
|
636
726
|
firstPartyMcpPermissions: jsonb("first_party_mcp_permissions").$type<string[]>(),
|
|
727
|
+
// Durable tool-policy origin. NULL is retained for pre-migration rows;
|
|
728
|
+
// mapSession exposes those rows as `legacy` instead of guessing omitted vs
|
|
729
|
+
// explicit [].
|
|
730
|
+
toolPolicy: jsonb("tool_policy").$type<SessionToolPolicy>(),
|
|
637
731
|
// The manager session that spawned this one via session_create. Set only
|
|
638
732
|
// when the creating grant carried a worker-signed sessionId claim (a session
|
|
639
733
|
// spawning a worker); null for direct API creates and scheduled-task runs.
|
|
@@ -1156,6 +1250,9 @@ export const sessionTurns = pgTable(
|
|
|
1156
1250
|
turnInstructions: text("turn_instructions"),
|
|
1157
1251
|
resources: jsonb("resources").$type<unknown[]>().notNull().default([]),
|
|
1158
1252
|
tools: jsonb("tools").$type<unknown[]>().notNull().default([]),
|
|
1253
|
+
// false = inherit the durable session policy; true = this turn explicitly
|
|
1254
|
+
// replaces it with `tools` after the core subset fence.
|
|
1255
|
+
toolsProvided: boolean("tools_provided").notNull().default(false),
|
|
1159
1256
|
model: text("model").notNull(),
|
|
1160
1257
|
reasoningEffort: text("reasoning_effort").notNull(),
|
|
1161
1258
|
sandboxBackend: text("sandbox_backend").notNull(),
|
|
@@ -1230,6 +1327,10 @@ export const sessionTurnAttempts = pgTable(
|
|
|
1230
1327
|
verifiedControlRevision: bigint("verified_control_revision", {
|
|
1231
1328
|
mode: "number",
|
|
1232
1329
|
}).notNull(),
|
|
1330
|
+
// Immutable policy snapshot captured under the session lock at claim.
|
|
1331
|
+
mcpApprovalPolicies: jsonb("mcp_approval_policies")
|
|
1332
|
+
.$type<Record<string, SessionMcpApprovalPolicy>>()
|
|
1333
|
+
.notNull(),
|
|
1233
1334
|
startedAt: timestamp("started_at", { withTimezone: true }).notNull().defaultNow(),
|
|
1234
1335
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
1235
1336
|
closedAt: timestamp("closed_at", { withTimezone: true }),
|
|
@@ -1502,6 +1603,7 @@ export const composerDrafts = pgTable(
|
|
|
1502
1603
|
text: text("text").notNull().default(""),
|
|
1503
1604
|
resources: jsonb("resources").$type<unknown[]>().notNull().default([]),
|
|
1504
1605
|
tools: jsonb("tools").$type<unknown[]>().notNull().default([]),
|
|
1606
|
+
toolsProvided: boolean("tools_provided").notNull().default(false),
|
|
1505
1607
|
model: text("model").notNull(),
|
|
1506
1608
|
reasoningEffort: text("reasoning_effort").notNull(),
|
|
1507
1609
|
sourceTurnId: uuid("source_turn_id"),
|
|
@@ -2999,6 +3101,10 @@ export const hostExportOutbox = pgTable(
|
|
|
2999
3101
|
"host_export_outbox_kind_check",
|
|
3000
3102
|
sql`${table.exportKind} in ('session_event', 'usage_event')`,
|
|
3001
3103
|
),
|
|
3104
|
+
rootSessionCaptured: check(
|
|
3105
|
+
"host_export_outbox_root_session_check",
|
|
3106
|
+
sql`${table.sessionId} is null or ${table.rootSessionId} is not null`,
|
|
3107
|
+
),
|
|
3002
3108
|
sourceUnique: uniqueIndex("host_export_outbox_source_uq").on(table.exportKind, table.sourceId),
|
|
3003
3109
|
cursorUnique: uniqueIndex("host_export_outbox_cursor_uq")
|
|
3004
3110
|
.on(table.exportKind, table.exportCursor)
|
package/src/session-control.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import {
|
|
3
3
|
boundWorkspaceControlEvent,
|
|
4
4
|
workspaceControlUtf8Bytes,
|
|
5
|
+
type SessionMcpApprovalPolicy,
|
|
5
6
|
type TurnInitiatorContext,
|
|
6
7
|
} from "@opengeni/contracts";
|
|
7
8
|
import { and, eq, inArray, sql } from "drizzle-orm";
|
|
@@ -512,6 +513,7 @@ export async function registerSessionTurnAttemptClaim(
|
|
|
512
513
|
temporalWorkflowRunId: string;
|
|
513
514
|
temporalActivityId: string;
|
|
514
515
|
verifiedControlRevision: number;
|
|
516
|
+
mcpApprovalPolicies: Record<string, SessionMcpApprovalPolicy>;
|
|
515
517
|
},
|
|
516
518
|
): Promise<typeof schema.sessionTurnAttempts.$inferSelect> {
|
|
517
519
|
const [inserted] = await db
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
+
metadataWithTurnExecutionPolicyV1,
|
|
2
3
|
mergeResourceRefs,
|
|
3
4
|
mergeToolRefs,
|
|
5
|
+
turnExecutionPolicyAuditMetadata,
|
|
4
6
|
type ReasoningEffort,
|
|
5
7
|
type ResourceRef,
|
|
8
|
+
type SessionToolPolicy,
|
|
6
9
|
type ToolRef,
|
|
10
|
+
type TurnExecutionPolicyV1,
|
|
7
11
|
} from "@opengeni/contracts";
|
|
8
12
|
import { and, asc, eq, inArray, sql } from "drizzle-orm";
|
|
9
13
|
import type { Database } from "./index";
|
|
@@ -39,7 +43,8 @@ export type QueueCommandConflictCode =
|
|
|
39
43
|
| "QUEUE_ANCHOR_CHANGED"
|
|
40
44
|
| "PROMPT_CHANGED"
|
|
41
45
|
| "DRAFT_CHANGED"
|
|
42
|
-
| "DRAFT_NOT_EMPTY"
|
|
46
|
+
| "DRAFT_NOT_EMPTY"
|
|
47
|
+
| "EDIT_SOURCE_CHANGED";
|
|
43
48
|
|
|
44
49
|
export class QueueCommandConflictError extends Error {
|
|
45
50
|
readonly name = "QueueCommandConflictError";
|
|
@@ -377,6 +382,7 @@ function draftIsNonEmpty(draft: ComposerDraftRow): boolean {
|
|
|
377
382
|
draft.text.length > 0 ||
|
|
378
383
|
draft.resources.length > 0 ||
|
|
379
384
|
draft.tools.length > 0 ||
|
|
385
|
+
draft.toolsProvided ||
|
|
380
386
|
draft.sourceTurnId !== null
|
|
381
387
|
);
|
|
382
388
|
}
|
|
@@ -416,6 +422,7 @@ export async function saveComposerDraftInTransaction(
|
|
|
416
422
|
text: string;
|
|
417
423
|
resources: ResourceRef[];
|
|
418
424
|
tools: ToolRef[];
|
|
425
|
+
toolsProvided: boolean;
|
|
419
426
|
model: string;
|
|
420
427
|
reasoningEffort: ReasoningEffort;
|
|
421
428
|
},
|
|
@@ -447,6 +454,7 @@ export async function saveComposerDraftInTransaction(
|
|
|
447
454
|
text: input.text,
|
|
448
455
|
resources: input.resources,
|
|
449
456
|
tools: input.tools,
|
|
457
|
+
toolsProvided: input.toolsProvided,
|
|
450
458
|
model: input.model,
|
|
451
459
|
reasoningEffort: input.reasoningEffort,
|
|
452
460
|
// A queue edit is still the same accepted work item. Preserve its frozen
|
|
@@ -818,6 +826,7 @@ export async function editQueuedTurnInTransaction(
|
|
|
818
826
|
text: turn.prompt,
|
|
819
827
|
resources: turn.resources,
|
|
820
828
|
tools: turn.tools,
|
|
829
|
+
toolsProvided: turn.toolsProvided,
|
|
821
830
|
model: turn.model,
|
|
822
831
|
reasoningEffort: turn.reasoningEffort,
|
|
823
832
|
sourceTurnId: turn.id,
|
|
@@ -1134,9 +1143,12 @@ export async function submitHumanPromptInTransaction(
|
|
|
1134
1143
|
turnInstructions?: string | null;
|
|
1135
1144
|
resources: ResourceRef[];
|
|
1136
1145
|
tools: ToolRef[];
|
|
1146
|
+
toolsProvided?: boolean;
|
|
1137
1147
|
model?: string | null;
|
|
1138
1148
|
reasoningEffort?: ReasoningEffort | null;
|
|
1139
1149
|
reasoningEffortFallback: ReasoningEffort;
|
|
1150
|
+
/** Trusted API/core admission snapshot. Omitted only by legacy low-level callers. */
|
|
1151
|
+
turnExecutionPolicy?: TurnExecutionPolicyV1;
|
|
1140
1152
|
source: "user" | "api";
|
|
1141
1153
|
mcpCredentialUpdates?: Array<{
|
|
1142
1154
|
id: string;
|
|
@@ -1163,6 +1175,7 @@ export async function submitHumanPromptInTransaction(
|
|
|
1163
1175
|
turnInstructions: input.turnInstructions ?? null,
|
|
1164
1176
|
resources: input.resources,
|
|
1165
1177
|
tools: input.tools,
|
|
1178
|
+
toolsProvided: input.toolsProvided === true,
|
|
1166
1179
|
model: input.model ?? null,
|
|
1167
1180
|
reasoningEffort: input.reasoningEffort ?? null,
|
|
1168
1181
|
source: input.source,
|
|
@@ -1271,6 +1284,7 @@ export async function submitHumanPromptInTransaction(
|
|
|
1271
1284
|
text: draft.text,
|
|
1272
1285
|
resources: draft.resources,
|
|
1273
1286
|
tools: draft.tools,
|
|
1287
|
+
toolsProvided: draft.toolsProvided,
|
|
1274
1288
|
model: draft.model,
|
|
1275
1289
|
reasoningEffort: draft.reasoningEffort,
|
|
1276
1290
|
}) !==
|
|
@@ -1278,6 +1292,7 @@ export async function submitHumanPromptInTransaction(
|
|
|
1278
1292
|
text: input.text,
|
|
1279
1293
|
resources: input.resources,
|
|
1280
1294
|
tools: input.tools,
|
|
1295
|
+
toolsProvided: input.toolsProvided === true,
|
|
1281
1296
|
model: input.model ?? session.model,
|
|
1282
1297
|
reasoningEffort: input.reasoningEffort ?? input.reasoningEffortFallback,
|
|
1283
1298
|
})
|
|
@@ -1293,6 +1308,52 @@ export async function submitHumanPromptInTransaction(
|
|
|
1293
1308
|
}
|
|
1294
1309
|
}
|
|
1295
1310
|
|
|
1311
|
+
let editedSourceTurn: QueuedTurnRow | undefined;
|
|
1312
|
+
let editedSourceTurnInstructions: string | null | undefined;
|
|
1313
|
+
if (draft?.sourceTurnId) {
|
|
1314
|
+
const sourceLocks = await lockSessionEventWriteRows(db, {
|
|
1315
|
+
workspaceId: input.workspaceId,
|
|
1316
|
+
controlLock: "already_locked",
|
|
1317
|
+
workspaceLock: "already_locked",
|
|
1318
|
+
turnIds: [draft.sourceTurnId],
|
|
1319
|
+
});
|
|
1320
|
+
const sourceTurn = sourceLocks.turns[0];
|
|
1321
|
+
const sourceTurnVersion = draft.sourceTurnVersion;
|
|
1322
|
+
const sourceMetadata = sourceTurn?.metadata ?? {};
|
|
1323
|
+
const sourceIsExactWithdrawnRevision =
|
|
1324
|
+
sourceTurn !== undefined &&
|
|
1325
|
+
sourceTurn.accountId === input.accountId &&
|
|
1326
|
+
sourceTurn.workspaceId === input.workspaceId &&
|
|
1327
|
+
sourceTurn.sessionId === input.sessionId &&
|
|
1328
|
+
(sourceTurn.source === "user" || sourceTurn.source === "api") &&
|
|
1329
|
+
sourceTurn.status === "withdrawn_for_edit" &&
|
|
1330
|
+
sourceTurnVersion !== null &&
|
|
1331
|
+
sourceTurn.version === sourceTurnVersion + 1 &&
|
|
1332
|
+
sourceTurn.cancelledBy === input.subjectId &&
|
|
1333
|
+
sourceTurn.cancelReason === "withdrawn_for_edit" &&
|
|
1334
|
+
sourceTurn.activeAttemptId === null &&
|
|
1335
|
+
sourceMetadata.delivery !== "steer";
|
|
1336
|
+
if (!sourceIsExactWithdrawnRevision) {
|
|
1337
|
+
throw new QueueCommandConflictError(
|
|
1338
|
+
"EDIT_SOURCE_CHANGED",
|
|
1339
|
+
"Edited prompt source changed or is no longer withdrawn for edit",
|
|
1340
|
+
{
|
|
1341
|
+
queueVersion: session.queueVersion,
|
|
1342
|
+
draftRevision: draft.revision,
|
|
1343
|
+
...(sourceTurn ? { turnVersion: sourceTurn.version } : {}),
|
|
1344
|
+
},
|
|
1345
|
+
);
|
|
1346
|
+
}
|
|
1347
|
+
// This is the sole private-instruction source for an edited replacement.
|
|
1348
|
+
// It is deliberately held separately from the public draft and event
|
|
1349
|
+
// projections below. The public draft is expected to differ after editing;
|
|
1350
|
+
// source identity is fenced by its exact withdrawn row version, not by
|
|
1351
|
+
// comparing the replacement content with the old prompt. A client-supplied
|
|
1352
|
+
// instruction value must never override the private source value.
|
|
1353
|
+
editedSourceTurn = sourceTurn;
|
|
1354
|
+
editedSourceTurnInstructions = sourceTurn.turnInstructions ?? null;
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1296
1357
|
for (const update of input.mcpCredentialUpdates ?? []) {
|
|
1297
1358
|
const [server] = await db
|
|
1298
1359
|
.update(schema.sessionMcpServers)
|
|
@@ -1315,22 +1376,7 @@ export async function submitHumanPromptInTransaction(
|
|
|
1315
1376
|
const now = new Date();
|
|
1316
1377
|
let frozenInitiator: FrozenTurnInitiator;
|
|
1317
1378
|
if (input.delivery === "send" && draft?.sourceTurnId) {
|
|
1318
|
-
const
|
|
1319
|
-
.select({
|
|
1320
|
-
sessionId: schema.sessionTurns.sessionId,
|
|
1321
|
-
initiatorKind: schema.sessionTurns.initiatorKind,
|
|
1322
|
-
initiatorSubjectId: schema.sessionTurns.initiatorSubjectId,
|
|
1323
|
-
initiatorContext: schema.sessionTurns.initiatorContext,
|
|
1324
|
-
})
|
|
1325
|
-
.from(schema.sessionTurns)
|
|
1326
|
-
.where(
|
|
1327
|
-
and(
|
|
1328
|
-
eq(schema.sessionTurns.workspaceId, input.workspaceId),
|
|
1329
|
-
eq(schema.sessionTurns.sessionId, input.sessionId),
|
|
1330
|
-
eq(schema.sessionTurns.id, draft.sourceTurnId),
|
|
1331
|
-
),
|
|
1332
|
-
)
|
|
1333
|
-
.limit(1);
|
|
1379
|
+
const sourceTurn = editedSourceTurn;
|
|
1334
1380
|
if (!sourceTurn) {
|
|
1335
1381
|
throw new SessionControlInvariantError("Edited prompt source turn is missing");
|
|
1336
1382
|
}
|
|
@@ -1366,7 +1412,11 @@ export async function submitHumanPromptInTransaction(
|
|
|
1366
1412
|
payload: sanitizeEventPayload({
|
|
1367
1413
|
text: input.text,
|
|
1368
1414
|
...(input.resources.length ? { resources: input.resources } : {}),
|
|
1369
|
-
...(input.
|
|
1415
|
+
...(input.toolsProvided === true
|
|
1416
|
+
? { tools: input.tools }
|
|
1417
|
+
: input.tools.length
|
|
1418
|
+
? { tools: input.tools }
|
|
1419
|
+
: {}),
|
|
1370
1420
|
...(input.model ? { model: input.model } : {}),
|
|
1371
1421
|
...(input.reasoningEffort ? { reasoningEffort: input.reasoningEffort } : {}),
|
|
1372
1422
|
delivery: input.delivery,
|
|
@@ -1389,13 +1439,19 @@ export async function submitHumanPromptInTransaction(
|
|
|
1389
1439
|
source: input.source,
|
|
1390
1440
|
position: input.delivery === "steer" ? 0 : existingQueued.length + 1,
|
|
1391
1441
|
prompt: input.text,
|
|
1392
|
-
turnInstructions:
|
|
1442
|
+
turnInstructions:
|
|
1443
|
+
editedSourceTurnInstructions !== undefined
|
|
1444
|
+
? editedSourceTurnInstructions
|
|
1445
|
+
: (input.turnInstructions ?? null),
|
|
1393
1446
|
resources: input.resources,
|
|
1394
1447
|
tools: input.tools,
|
|
1448
|
+
toolsProvided: input.toolsProvided === true,
|
|
1395
1449
|
model: input.model ?? session.model,
|
|
1396
1450
|
reasoningEffort: input.reasoningEffort ?? input.reasoningEffortFallback,
|
|
1397
1451
|
sandboxBackend: session.sandboxBackend,
|
|
1398
|
-
metadata:
|
|
1452
|
+
metadata: input.turnExecutionPolicy
|
|
1453
|
+
? metadataWithTurnExecutionPolicyV1({}, input.turnExecutionPolicy)
|
|
1454
|
+
: {},
|
|
1399
1455
|
lineage: { actor: input.actor.type },
|
|
1400
1456
|
...initiatorColumns(frozenInitiator),
|
|
1401
1457
|
})
|
|
@@ -1455,6 +1511,7 @@ export async function submitHumanPromptInTransaction(
|
|
|
1455
1511
|
.update(schema.sessionTurns)
|
|
1456
1512
|
.set({
|
|
1457
1513
|
metadata: {
|
|
1514
|
+
...turn.metadata,
|
|
1458
1515
|
delivery: "steer",
|
|
1459
1516
|
replacedTurnId,
|
|
1460
1517
|
replacedAttemptId,
|
|
@@ -1532,7 +1589,9 @@ export async function submitHumanPromptInTransaction(
|
|
|
1532
1589
|
.update(schema.sessions)
|
|
1533
1590
|
.set({
|
|
1534
1591
|
resources: mergeResourceRefs(session.resources as ResourceRef[], input.resources),
|
|
1535
|
-
tools:
|
|
1592
|
+
tools: sessionToolPolicyIsFixed(session.toolPolicy)
|
|
1593
|
+
? session.tools
|
|
1594
|
+
: mergeToolRefs(session.tools as ToolRef[], input.tools),
|
|
1536
1595
|
activeTurnId: input.delivery === "steer" ? liveCurrentTurnId : session.activeTurnId,
|
|
1537
1596
|
status: nextStatus,
|
|
1538
1597
|
queueVersion,
|
|
@@ -1566,6 +1625,9 @@ export async function submitHumanPromptInTransaction(
|
|
|
1566
1625
|
operationId: reserved.receipt.id,
|
|
1567
1626
|
replacedTurnId,
|
|
1568
1627
|
interruptionCount,
|
|
1628
|
+
...(input.turnExecutionPolicy
|
|
1629
|
+
? turnExecutionPolicyAuditMetadata(input.turnExecutionPolicy, turnId)
|
|
1630
|
+
: {}),
|
|
1569
1631
|
},
|
|
1570
1632
|
});
|
|
1571
1633
|
const eventIds = eventRows.map((event) => event.id);
|
|
@@ -1582,6 +1644,11 @@ export async function submitHumanPromptInTransaction(
|
|
|
1582
1644
|
interruptionCount,
|
|
1583
1645
|
replacedTurnId,
|
|
1584
1646
|
workspaceControlEventId: resumed.workspaceControlEventId,
|
|
1647
|
+
...(input.turnExecutionPolicy
|
|
1648
|
+
? {
|
|
1649
|
+
executionPolicy: turnExecutionPolicyAuditMetadata(input.turnExecutionPolicy, turnId),
|
|
1650
|
+
}
|
|
1651
|
+
: {}),
|
|
1585
1652
|
},
|
|
1586
1653
|
});
|
|
1587
1654
|
return {
|
|
@@ -1597,6 +1664,12 @@ export async function submitHumanPromptInTransaction(
|
|
|
1597
1664
|
};
|
|
1598
1665
|
}
|
|
1599
1666
|
|
|
1667
|
+
function sessionToolPolicyIsFixed(value: unknown): boolean {
|
|
1668
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1669
|
+
const mode = (value as Partial<SessionToolPolicy>).mode;
|
|
1670
|
+
return mode === "workspace_default" || mode === "explicit" || mode === "inherited";
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1600
1673
|
export async function sendAgentMessageInTransaction(
|
|
1601
1674
|
db: Database,
|
|
1602
1675
|
input: {
|