@opengeni/db 0.13.1 → 0.14.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
@@ -76,7 +76,7 @@ import {
76
76
  workspaceVariableSetVariables,
77
77
  workspaceVariableSets,
78
78
  workspaces
79
- } from "./chunk-N4WTE6SB.js";
79
+ } from "./chunk-PQKVUWQW.js";
80
80
  import {
81
81
  migrate,
82
82
  runMigrations
@@ -119,6 +119,7 @@ import {
119
119
  RigChange as RigChangeContract,
120
120
  SessionGoal as SessionGoalContract,
121
121
  SessionSystemUpdatePayload,
122
+ sessionSystemUpdateBatchHistoryItem,
122
123
  HostEventExport as HostEventExportContract,
123
124
  HostEventExportBatch as HostEventExportBatchContract,
124
125
  HostExportConsumerId,
@@ -2892,14 +2893,6 @@ async function supersedeSessionCurrentDirectionInTransaction(db, input) {
2892
2893
  finishedAt: now,
2893
2894
  updatedAt: now
2894
2895
  }).where(eq5(sessionTurns.id, current.id));
2895
- await db.update(sessionSystemUpdates).set({ state: "pending", deliveredTurnId: null, deliveredAt: null }).where(
2896
- and5(
2897
- eq5(sessionSystemUpdates.workspaceId, input.workspaceId),
2898
- eq5(sessionSystemUpdates.sessionId, input.sessionId),
2899
- eq5(sessionSystemUpdates.deliveredTurnId, current.id),
2900
- eq5(sessionSystemUpdates.state, "delivered")
2901
- )
2902
- );
2903
2896
  if (current.status === "waiting_capacity") {
2904
2897
  await db.update(codexCapacityWaiters).set({ status: "superseded", updatedAt: now }).where(
2905
2898
  and5(
@@ -4186,14 +4179,14 @@ async function steerAgentSessionInTransaction(db, input) {
4186
4179
  controlRevision: resumed.revision,
4187
4180
  lastSequence: session.lastSequence
4188
4181
  });
4189
- await db.update(sessionSystemUpdates).set({ state: "superseded" }).where(
4182
+ const supersededUpdates = await db.update(sessionSystemUpdates).set({ state: "superseded" }).where(
4190
4183
  and5(
4191
4184
  eq5(sessionSystemUpdates.workspaceId, input.workspaceId),
4192
4185
  eq5(sessionSystemUpdates.sessionId, input.targetSessionId),
4193
4186
  eq5(sessionSystemUpdates.kind, "agent_steer_instruction"),
4194
- inArray2(sessionSystemUpdates.state, ["pending", "deferred"])
4187
+ eq5(sessionSystemUpdates.state, "pending")
4195
4188
  )
4196
- );
4189
+ ).returning({ id: sessionSystemUpdates.id });
4197
4190
  const now = /* @__PURE__ */ new Date();
4198
4191
  const [update] = await db.insert(sessionSystemUpdates).values({
4199
4192
  accountId: input.accountId,
@@ -4252,6 +4245,22 @@ async function steerAgentSessionInTransaction(db, input) {
4252
4245
  },
4253
4246
  occurredAt: now
4254
4247
  },
4248
+ ...supersededUpdates.length > 0 ? [
4249
+ {
4250
+ accountId: input.accountId,
4251
+ workspaceId: input.workspaceId,
4252
+ sessionId: input.targetSessionId,
4253
+ sequence: ++sequence,
4254
+ type: "system.update.superseded",
4255
+ payload: {
4256
+ updateIds: supersededUpdates.map((entry) => entry.id),
4257
+ count: supersededUpdates.length,
4258
+ replacementUpdateId: update.id,
4259
+ reason: "newer_agent_steer"
4260
+ },
4261
+ occurredAt: now
4262
+ }
4263
+ ] : [],
4255
4264
  {
4256
4265
  accountId: input.accountId,
4257
4266
  workspaceId: input.workspaceId,
@@ -5194,11 +5203,15 @@ var REFRESH_WINDOW_MS = 6e4;
5194
5203
  var CONNECTION_REFRESH_TIMEOUT_MS = 1e4;
5195
5204
  function buildConnectionTokenResolver(db, settings, deps = defaultDeps2) {
5196
5205
  const load = async (input) => {
5206
+ const subjectOwned = input.connectionRef.subjectScope === "subject";
5207
+ if (subjectOwned && !input.subjectId) {
5208
+ return null;
5209
+ }
5197
5210
  const request = {
5198
5211
  workspaceId: input.workspaceId,
5199
5212
  providerDomain: input.connectionRef.providerDomain,
5200
- // I1 deliberately accepts workspace-shared connections only at runtime.
5201
- allowSubjectOwned: false
5213
+ allowSubjectOwned: subjectOwned,
5214
+ ...subjectOwned ? { subjectId: input.subjectId } : {}
5202
5215
  };
5203
5216
  if (input.connectionRef.connectionId !== void 0) {
5204
5217
  request.connectionId = input.connectionRef.connectionId;
@@ -5206,10 +5219,12 @@ function buildConnectionTokenResolver(db, settings, deps = defaultDeps2) {
5206
5219
  if (input.connectionRef.kind !== void 0) {
5207
5220
  request.kind = input.connectionRef.kind;
5208
5221
  }
5209
- if (input.subjectId !== void 0) {
5210
- request.subjectId = input.subjectId;
5222
+ const credential = await deps.loadCredential(db, settings, request);
5223
+ if (!credential) return null;
5224
+ if (subjectOwned) {
5225
+ return credential.subjectId === input.subjectId ? credential : null;
5211
5226
  }
5212
- return deps.loadCredential(db, settings, request);
5227
+ return credential.subjectId === null ? credential : null;
5213
5228
  };
5214
5229
  const snapshot = async (cred, ref, destinationUrl) => {
5215
5230
  if (cred.status !== "active") {
@@ -5244,7 +5259,7 @@ function buildConnectionTokenResolver(db, settings, deps = defaultDeps2) {
5244
5259
  ...ref.selectedResources ? { selectedResources: copySelectedResources(ref.selectedResources) } : {}
5245
5260
  };
5246
5261
  }
5247
- await deps.recordUsed(db, cred.workspaceId, cred.id);
5262
+ await deps.recordUsed(db, cred.workspaceId, cred.id, cred.subjectId);
5248
5263
  return {
5249
5264
  status: "ok",
5250
5265
  headers,
@@ -5264,7 +5279,8 @@ function buildConnectionTokenResolver(db, settings, deps = defaultDeps2) {
5264
5279
  workspaceId: cred.workspaceId,
5265
5280
  credentialEncrypted: deps.encrypt(key, JSON.stringify(refreshed.credential)),
5266
5281
  expiresAt: refreshed.expiresAt,
5267
- lastRefreshAt: deps.now()
5282
+ lastRefreshAt: deps.now(),
5283
+ subjectId: cred.subjectId
5268
5284
  };
5269
5285
  if (refreshed.grantedScopes !== void 0) {
5270
5286
  refreshRecord.grantedScopes = refreshed.grantedScopes;
@@ -5273,7 +5289,8 @@ function buildConnectionTokenResolver(db, settings, deps = defaultDeps2) {
5273
5289
  if (persisted) {
5274
5290
  const current = await load({
5275
5291
  workspaceId: cred.workspaceId,
5276
- connectionRef: { ...ref, connectionId: cred.id }
5292
+ connectionRef: { ...ref, connectionId: cred.id },
5293
+ ...cred.subjectId ? { subjectId: cred.subjectId } : {}
5277
5294
  });
5278
5295
  if (current) {
5279
5296
  return current;
@@ -5281,7 +5298,8 @@ function buildConnectionTokenResolver(db, settings, deps = defaultDeps2) {
5281
5298
  }
5282
5299
  const winner = await load({
5283
5300
  workspaceId: cred.workspaceId,
5284
- connectionRef: { ...ref, connectionId: cred.id }
5301
+ connectionRef: { ...ref, connectionId: cred.id },
5302
+ ...cred.subjectId ? { subjectId: cred.subjectId } : {}
5285
5303
  });
5286
5304
  if (winner?.status === "active") {
5287
5305
  return winner;
@@ -5289,7 +5307,7 @@ function buildConnectionTokenResolver(db, settings, deps = defaultDeps2) {
5289
5307
  throw new Error("connection credential changed during token refresh");
5290
5308
  };
5291
5309
  const refreshSingleFlight = (cred, ref) => {
5292
- const key = `${cred.id}:${cred.version}`;
5310
+ const key = `${cred.subjectId ?? "workspace"}:${cred.id}:${cred.version}`;
5293
5311
  const existing = inflight2.get(key);
5294
5312
  if (existing) {
5295
5313
  return existing;
@@ -5334,7 +5352,8 @@ function buildConnectionTokenResolver(db, settings, deps = defaultDeps2) {
5334
5352
  error instanceof Error ? error.message : String(error),
5335
5353
  {
5336
5354
  id: cred.id,
5337
- version: cred.version
5355
+ version: cred.version,
5356
+ subjectId: cred.subjectId
5338
5357
  }
5339
5358
  ).catch(() => void 0);
5340
5359
  }
@@ -6151,6 +6170,17 @@ async function bootstrapWorkspace(db, input) {
6151
6170
  updatedAt: /* @__PURE__ */ new Date()
6152
6171
  }).where(eq7(workspaceMemberships.id, membership.id));
6153
6172
  }
6173
+ await setRlsContext(tx, {
6174
+ accountId: workspace.accountId,
6175
+ workspaceId: null
6176
+ });
6177
+ const memberships = await tx.select({
6178
+ membership: workspaceMemberships,
6179
+ workspace: workspaces
6180
+ }).from(workspaceMemberships).innerJoin(
6181
+ workspaces,
6182
+ eq7(workspaceMemberships.workspaceId, workspaces.id)
6183
+ ).where(eq7(workspaceMemberships.subjectId, input.subjectId)).orderBy(desc2(workspaces.createdAt));
6154
6184
  return {
6155
6185
  mode: input.accountExternalSource === "opengeni:local" ? "local" : "configured",
6156
6186
  subjectId: input.subjectId,
@@ -6164,15 +6194,13 @@ async function bootstrapWorkspace(db, input) {
6164
6194
  permissions: input.accountPermissions ?? allAccountPermissions
6165
6195
  }
6166
6196
  ],
6167
- workspaceGrants: [
6168
- {
6169
- workspaceId: workspace.id,
6170
- accountId: account.id,
6171
- subjectId: input.subjectId,
6172
- ...input.subjectLabel ? { subjectLabel: input.subjectLabel } : {},
6173
- permissions: workspacePermissions
6174
- }
6175
- ],
6197
+ workspaceGrants: memberships.map((row) => ({
6198
+ workspaceId: row.workspace.id,
6199
+ accountId: row.workspace.accountId,
6200
+ subjectId: input.subjectId,
6201
+ ...input.subjectLabel ? { subjectLabel: input.subjectLabel } : {},
6202
+ permissions: row.membership.permissions
6203
+ })),
6176
6204
  defaultAccountId: account.id,
6177
6205
  defaultWorkspaceId: workspace.id
6178
6206
  };
@@ -8032,36 +8060,48 @@ var connectionMetadataColumns = {
8032
8060
  function connectionSubjectVisibility(subjectId) {
8033
8061
  return subjectId ? or(isNull2(connections.subjectId), eq7(connections.subjectId, subjectId)) : isNull2(connections.subjectId);
8034
8062
  }
8063
+ function connectionExactSubject(subjectId) {
8064
+ return subjectId ? eq7(connections.subjectId, subjectId) : isNull2(connections.subjectId);
8065
+ }
8066
+ async function withConnectionSubjectRls(db, workspaceId, subjectId, fn) {
8067
+ return subjectId ? await withWorkspaceSubjectRls(db, workspaceId, subjectId, fn) : await withWorkspaceRls(db, workspaceId, fn);
8068
+ }
8069
+ async function createConnectionInScope(db, input) {
8070
+ const [row] = await db.insert(connections).values({
8071
+ accountId: input.accountId,
8072
+ workspaceId: input.workspaceId,
8073
+ subjectId: input.subjectId ?? null,
8074
+ providerDomain: input.providerDomain,
8075
+ kind: input.kind,
8076
+ status: input.status ?? "active",
8077
+ credentialEncrypted: input.credentialEncrypted,
8078
+ grantedScopes: input.grantedScopes ?? [],
8079
+ expiresAt: input.expiresAt ?? null,
8080
+ verifiedInstallAt: input.verifiedInstallAt ?? null,
8081
+ verifiedInstallVersion: input.verifiedInstallVersion ?? null,
8082
+ metadata: input.metadata ?? {},
8083
+ createdBySubjectId: input.createdBySubjectId ?? null,
8084
+ updatedBySubjectId: input.updatedBySubjectId ?? input.createdBySubjectId ?? null
8085
+ }).returning(connectionMetadataColumns);
8086
+ if (!row) {
8087
+ throw new Error("Failed to create connection");
8088
+ }
8089
+ return mapConnectionMetadata(row);
8090
+ }
8035
8091
  async function createConnection(db, input) {
8036
8092
  return await withRlsContext(
8037
8093
  db,
8038
8094
  { accountId: input.accountId, workspaceId: input.workspaceId },
8039
8095
  async (scopedDb) => {
8040
- const [row] = await scopedDb.insert(connections).values({
8041
- accountId: input.accountId,
8042
- workspaceId: input.workspaceId,
8043
- subjectId: input.subjectId ?? null,
8044
- providerDomain: input.providerDomain,
8045
- kind: input.kind,
8046
- status: input.status ?? "active",
8047
- credentialEncrypted: input.credentialEncrypted,
8048
- grantedScopes: input.grantedScopes ?? [],
8049
- expiresAt: input.expiresAt ?? null,
8050
- verifiedInstallAt: input.verifiedInstallAt ?? null,
8051
- verifiedInstallVersion: input.verifiedInstallVersion ?? null,
8052
- metadata: input.metadata ?? {},
8053
- createdBySubjectId: input.createdBySubjectId ?? null,
8054
- updatedBySubjectId: input.updatedBySubjectId ?? input.createdBySubjectId ?? null
8055
- }).returning(connectionMetadataColumns);
8056
- if (!row) {
8057
- throw new Error("Failed to create connection");
8096
+ if (input.subjectId) {
8097
+ await setSubjectRlsContext(scopedDb, input.subjectId);
8058
8098
  }
8059
- return mapConnectionMetadata(row);
8099
+ return await createConnectionInScope(scopedDb, input);
8060
8100
  }
8061
8101
  );
8062
8102
  }
8063
8103
  async function listConnectionsMetadata(db, workspaceId, subjectId) {
8064
- return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
8104
+ return await withConnectionSubjectRls(db, workspaceId, subjectId, async (scopedDb) => {
8065
8105
  const rows = await scopedDb.select(connectionMetadataColumns).from(connections).where(
8066
8106
  and7(
8067
8107
  eq7(connections.workspaceId, workspaceId),
@@ -8072,7 +8112,7 @@ async function listConnectionsMetadata(db, workspaceId, subjectId) {
8072
8112
  });
8073
8113
  }
8074
8114
  async function getConnectionMetadata(db, workspaceId, connectionId, subjectId) {
8075
- return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
8115
+ return await withConnectionSubjectRls(db, workspaceId, subjectId, async (scopedDb) => {
8076
8116
  const [row] = await scopedDb.select(connectionMetadataColumns).from(connections).where(
8077
8117
  and7(
8078
8118
  eq7(connections.workspaceId, workspaceId),
@@ -8083,66 +8123,217 @@ async function getConnectionMetadata(db, workspaceId, connectionId, subjectId) {
8083
8123
  return row ? mapConnectionMetadata(row) : null;
8084
8124
  });
8085
8125
  }
8126
+ async function updateConnectionInScope(db, input) {
8127
+ const set = {
8128
+ updatedAt: /* @__PURE__ */ new Date(),
8129
+ ...input.providerDomain !== void 0 ? { providerDomain: input.providerDomain } : {},
8130
+ ...input.subjectId !== void 0 ? { subjectId: input.subjectId } : {},
8131
+ ...input.kind !== void 0 ? { kind: input.kind } : {},
8132
+ ...input.status !== void 0 ? { status: input.status } : {},
8133
+ ...input.credentialEncrypted !== void 0 ? {
8134
+ credentialEncrypted: input.credentialEncrypted,
8135
+ version: sql4`${connections.version} + 1`,
8136
+ lastError: null
8137
+ } : {},
8138
+ ...input.grantedScopes !== void 0 ? { grantedScopes: input.grantedScopes } : {},
8139
+ ...input.expiresAt !== void 0 ? { expiresAt: input.expiresAt } : {},
8140
+ ...input.verifiedInstallAt !== void 0 ? { verifiedInstallAt: input.verifiedInstallAt } : {},
8141
+ ...input.verifiedInstallVersion !== void 0 ? { verifiedInstallVersion: input.verifiedInstallVersion } : {},
8142
+ ...input.metadata !== void 0 ? { metadata: input.metadata } : {},
8143
+ ...input.updatedBySubjectId !== void 0 ? { updatedBySubjectId: input.updatedBySubjectId } : {}
8144
+ };
8145
+ const [row] = await db.update(connections).set(set).where(
8146
+ and7(
8147
+ eq7(connections.workspaceId, input.workspaceId),
8148
+ eq7(connections.id, input.connectionId),
8149
+ connectionSubjectVisibility(input.visibleToSubjectId),
8150
+ ...input.expectedVersion !== void 0 ? [eq7(connections.version, input.expectedVersion)] : []
8151
+ )
8152
+ ).returning(connectionMetadataColumns);
8153
+ return row ? mapConnectionMetadata(row) : null;
8154
+ }
8086
8155
  async function updateConnection(db, input) {
8087
- return await withWorkspaceRls(db, input.workspaceId, async (scopedDb) => {
8088
- const set = {
8089
- updatedAt: /* @__PURE__ */ new Date(),
8090
- ...input.providerDomain !== void 0 ? { providerDomain: input.providerDomain } : {},
8091
- ...input.subjectId !== void 0 ? { subjectId: input.subjectId } : {},
8092
- ...input.kind !== void 0 ? { kind: input.kind } : {},
8093
- ...input.status !== void 0 ? { status: input.status } : {},
8094
- ...input.credentialEncrypted !== void 0 ? {
8095
- credentialEncrypted: input.credentialEncrypted,
8096
- version: sql4`${connections.version} + 1`,
8097
- lastError: null
8098
- } : {},
8099
- ...input.grantedScopes !== void 0 ? { grantedScopes: input.grantedScopes } : {},
8100
- ...input.expiresAt !== void 0 ? { expiresAt: input.expiresAt } : {},
8101
- ...input.verifiedInstallAt !== void 0 ? { verifiedInstallAt: input.verifiedInstallAt } : {},
8102
- ...input.verifiedInstallVersion !== void 0 ? { verifiedInstallVersion: input.verifiedInstallVersion } : {},
8103
- ...input.metadata !== void 0 ? { metadata: input.metadata } : {},
8104
- ...input.updatedBySubjectId !== void 0 ? { updatedBySubjectId: input.updatedBySubjectId } : {}
8105
- };
8106
- const [row] = await scopedDb.update(connections).set(set).where(
8107
- and7(
8108
- eq7(connections.workspaceId, input.workspaceId),
8109
- eq7(connections.id, input.connectionId),
8110
- connectionSubjectVisibility(input.visibleToSubjectId),
8111
- ...input.expectedVersion !== void 0 ? [eq7(connections.version, input.expectedVersion)] : []
8112
- )
8113
- ).returning(connectionMetadataColumns);
8114
- return row ? mapConnectionMetadata(row) : null;
8115
- });
8156
+ return await withConnectionSubjectRls(
8157
+ db,
8158
+ input.workspaceId,
8159
+ input.visibleToSubjectId,
8160
+ async (scopedDb) => await updateConnectionInScope(scopedDb, input)
8161
+ );
8162
+ }
8163
+ async function revokeConnectionInScope(db, workspaceId, connectionId, updatedBySubjectId, expectedVersion) {
8164
+ const [row] = await db.update(connections).set({
8165
+ status: "revoked",
8166
+ // The version bump invalidates any in-flight refresh's (id, version) CAS,
8167
+ // so a racing refresh cannot commit and flip the row back to active.
8168
+ version: sql4`${connections.version} + 1`,
8169
+ // Status-only revocation does not replace the verified credential or bot
8170
+ // identity. Carry the marker to the same new CAS version so the dedicated
8171
+ // reinstall path can still recognize (but not use) the inactive row.
8172
+ verifiedInstallVersion: sql4`case
8173
+ when ${connections.verifiedInstallAt} is null then null
8174
+ else ${connections.version} + 1
8175
+ end`,
8176
+ updatedBySubjectId: updatedBySubjectId ?? null,
8177
+ updatedAt: /* @__PURE__ */ new Date()
8178
+ }).where(
8179
+ and7(
8180
+ eq7(connections.workspaceId, workspaceId),
8181
+ eq7(connections.id, connectionId),
8182
+ // Same visibility rule as get/update: shared rows plus the caller's own
8183
+ // subject rows. Cross-subject revocation (admin janitorial) arrives with
8184
+ // the subject-connections UX in I5, deliberately not before.
8185
+ connectionSubjectVisibility(updatedBySubjectId),
8186
+ ...expectedVersion !== void 0 ? [eq7(connections.version, expectedVersion)] : []
8187
+ )
8188
+ ).returning(connectionMetadataColumns);
8189
+ return row ? mapConnectionMetadata(row) : null;
8116
8190
  }
8117
8191
  async function revokeConnection(db, workspaceId, connectionId, updatedBySubjectId) {
8118
- return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
8119
- const [row] = await scopedDb.update(connections).set({
8120
- status: "revoked",
8121
- // The version bump invalidates any in-flight refresh's (id, version) CAS,
8122
- // so a racing refresh cannot commit and flip the row back to active.
8123
- version: sql4`${connections.version} + 1`,
8124
- // Status-only revocation does not replace the verified credential or bot
8125
- // identity. Carry the marker to the same new CAS version so the dedicated
8126
- // reinstall path can still recognize (but not use) the inactive row.
8127
- verifiedInstallVersion: sql4`case
8128
- when ${connections.verifiedInstallAt} is null then null
8129
- else ${connections.version} + 1
8130
- end`,
8131
- updatedBySubjectId: updatedBySubjectId ?? null,
8132
- updatedAt: /* @__PURE__ */ new Date()
8133
- }).where(
8134
- and7(
8135
- eq7(connections.workspaceId, workspaceId),
8136
- eq7(connections.id, connectionId),
8137
- // Same visibility rule as get/update: shared rows plus the caller's own
8138
- // subject rows. Cross-subject revocation (admin janitorial) arrives with
8139
- // the subject-connections UX in I5, deliberately not before.
8140
- connectionSubjectVisibility(updatedBySubjectId)
8141
- )
8142
- ).returning(connectionMetadataColumns);
8143
- return row ? mapConnectionMetadata(row) : null;
8192
+ return await withConnectionSubjectRls(
8193
+ db,
8194
+ workspaceId,
8195
+ updatedBySubjectId,
8196
+ async (scopedDb) => await revokeConnectionInScope(scopedDb, workspaceId, connectionId, updatedBySubjectId)
8197
+ );
8198
+ }
8199
+ var SlackBotLifecycleSuccessAuditError = class extends Error {
8200
+ constructor() {
8201
+ super("OpenGeni Slack bot lifecycle success audit failed");
8202
+ this.name = "SlackBotLifecycleSuccessAuditError";
8203
+ }
8204
+ };
8205
+ async function insertSlackBotLifecycleSuccessAuditInScope(db, input) {
8206
+ try {
8207
+ await db.insert(auditEvents).values({
8208
+ accountId: input.accountId,
8209
+ workspaceId: input.workspaceId,
8210
+ subjectId: input.subjectId,
8211
+ action: input.action,
8212
+ targetType: "connection",
8213
+ targetId: input.connectionId,
8214
+ metadata: {
8215
+ credentialRole: input.credentialRole,
8216
+ credentialLabel: input.credentialLabel,
8217
+ connectionId: input.connectionId,
8218
+ slackTeamId: input.slackTeamId,
8219
+ outcome: "succeeded"
8220
+ }
8221
+ });
8222
+ } catch {
8223
+ throw new SlackBotLifecycleSuccessAuditError();
8224
+ }
8225
+ }
8226
+ async function assertWorkspaceAccountPairInScope(db, accountId, workspaceId) {
8227
+ const [workspace] = await db.select({ id: workspaces.id }).from(workspaces).where(and7(eq7(workspaces.id, workspaceId), eq7(workspaces.accountId, accountId))).limit(1);
8228
+ if (!workspace) {
8229
+ throw new Error("Workspace does not belong to the expected account");
8230
+ }
8231
+ }
8232
+ async function withSlackBotLifecycleRls(db, input, fn) {
8233
+ return await withRlsContext(
8234
+ db,
8235
+ { accountId: input.accountId, workspaceId: input.workspaceId },
8236
+ async (scopedDb) => {
8237
+ await setSubjectRlsContext(scopedDb, input.subjectId);
8238
+ await assertWorkspaceAccountPairInScope(scopedDb, input.accountId, input.workspaceId);
8239
+ return await fn(scopedDb);
8240
+ }
8241
+ );
8242
+ }
8243
+ async function createConnectionWithSlackBotSuccessAudit(db, input) {
8244
+ if (input.connection.accountId !== input.accountId || input.connection.workspaceId !== input.workspaceId) {
8245
+ throw new Error("Slack bot connection and lifecycle audit tenant must match");
8246
+ }
8247
+ return await withSlackBotLifecycleRls(db, input, async (scopedDb) => {
8248
+ const connection = await createConnectionInScope(scopedDb, input.connection);
8249
+ await insertSlackBotLifecycleSuccessAuditInScope(scopedDb, {
8250
+ ...input,
8251
+ action: "slack_bot.connected",
8252
+ connectionId: connection.id
8253
+ });
8254
+ return connection;
8144
8255
  });
8145
8256
  }
8257
+ async function updateConnectionWithSlackBotSuccessAudit(db, input) {
8258
+ if (input.connection.workspaceId !== input.workspaceId) {
8259
+ throw new Error("Slack bot connection and lifecycle audit workspace must match");
8260
+ }
8261
+ return await withSlackBotLifecycleRls(db, input, async (scopedDb) => {
8262
+ const connection = await updateConnectionInScope(scopedDb, input.connection);
8263
+ if (!connection) return null;
8264
+ if (connection.accountId !== input.accountId) {
8265
+ throw new Error("Slack bot connection and lifecycle audit account must match");
8266
+ }
8267
+ await insertSlackBotLifecycleSuccessAuditInScope(scopedDb, {
8268
+ ...input,
8269
+ action: "slack_bot.reinstalled",
8270
+ connectionId: connection.id
8271
+ });
8272
+ return connection;
8273
+ });
8274
+ }
8275
+ async function revokeConnectionWithSlackBotSuccessAudit(db, input) {
8276
+ return await withSlackBotLifecycleRls(db, input, async (scopedDb) => {
8277
+ const connection = await revokeConnectionInScope(
8278
+ scopedDb,
8279
+ input.workspaceId,
8280
+ input.connectionId,
8281
+ input.subjectId,
8282
+ input.expectedVersion
8283
+ );
8284
+ if (!connection) return null;
8285
+ if (connection.accountId !== input.accountId) {
8286
+ throw new Error("Slack bot connection and lifecycle audit account must match");
8287
+ }
8288
+ await insertSlackBotLifecycleSuccessAuditInScope(scopedDb, {
8289
+ ...input,
8290
+ action: "slack_bot.disconnected",
8291
+ connectionId: connection.id
8292
+ });
8293
+ return connection;
8294
+ });
8295
+ }
8296
+ async function recordSlackBotInstallCallbackFailure(db, input) {
8297
+ if (!/^[a-f0-9]{64}$/.test(input.callbackDigest)) {
8298
+ throw new Error("Slack callback digest must be a lowercase SHA-256 value");
8299
+ }
8300
+ return await withRlsContext(
8301
+ db,
8302
+ { accountId: input.accountId, workspaceId: input.workspaceId },
8303
+ async (scopedDb) => {
8304
+ await setSubjectRlsContext(scopedDb, input.subjectId);
8305
+ await assertWorkspaceAccountPairInScope(scopedDb, input.accountId, input.workspaceId);
8306
+ await scopedDb.execute(
8307
+ sql4`select pg_advisory_xact_lock(hashtextextended(${`slack-callback-failure:${input.workspaceId}:${input.callbackDigest}`}, 0))`
8308
+ );
8309
+ const [existing] = await scopedDb.select({ id: auditEvents.id }).from(auditEvents).where(
8310
+ and7(
8311
+ eq7(auditEvents.accountId, input.accountId),
8312
+ eq7(auditEvents.workspaceId, input.workspaceId),
8313
+ eq7(auditEvents.action, "slack_bot.install.callback.failed"),
8314
+ eq7(auditEvents.targetType, "slack_oauth_callback"),
8315
+ eq7(auditEvents.targetId, input.callbackDigest)
8316
+ )
8317
+ ).limit(1);
8318
+ if (existing) return false;
8319
+ await scopedDb.insert(auditEvents).values({
8320
+ accountId: input.accountId,
8321
+ workspaceId: input.workspaceId,
8322
+ subjectId: input.subjectId,
8323
+ action: "slack_bot.install.callback.failed",
8324
+ targetType: "slack_oauth_callback",
8325
+ targetId: input.callbackDigest,
8326
+ metadata: {
8327
+ outcome: "failed",
8328
+ installMode: input.installMode,
8329
+ stage: input.stage,
8330
+ reason: input.reason
8331
+ }
8332
+ });
8333
+ return true;
8334
+ }
8335
+ );
8336
+ }
8146
8337
  async function claimSlackBotPostOperation(db, input) {
8147
8338
  const claimLeaseMs = Math.max(1, Math.min(Math.trunc(input.claimLeaseMs), 12e4));
8148
8339
  return await withRlsContext(
@@ -8302,13 +8493,16 @@ async function getSlackBotPostOperation(db, workspaceId, connectionId, operation
8302
8493
  });
8303
8494
  }
8304
8495
  async function loadConnectionCredentialForBroker(db, settings, input) {
8496
+ if (input.allowSubjectOwned && !input.subjectId) {
8497
+ return null;
8498
+ }
8305
8499
  const key = environmentsEncryptionKeyBytes3(settings);
8306
8500
  if (!key) {
8307
8501
  throw new Error(
8308
8502
  "connection credential present but OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY is not configured"
8309
8503
  );
8310
8504
  }
8311
- const subjectPredicate = input.allowSubjectOwned ? connectionSubjectVisibility(input.subjectId) : isNull2(connections.subjectId);
8505
+ const subjectPredicate = input.allowSubjectOwned ? connectionExactSubject(input.subjectId) : isNull2(connections.subjectId);
8312
8506
  const conditions = [
8313
8507
  eq7(connections.workspaceId, input.workspaceId),
8314
8508
  subjectPredicate
@@ -8321,70 +8515,81 @@ async function loadConnectionCredentialForBroker(db, settings, input) {
8321
8515
  conditions.push(eq7(connections.kind, input.kind));
8322
8516
  }
8323
8517
  }
8324
- return await withWorkspaceRls(db, input.workspaceId, async (scopedDb) => {
8325
- const [row] = await scopedDb.select().from(connections).where(and7(...conditions)).orderBy(
8326
- desc2(sql4`(${connections.status} = 'active')`),
8327
- desc2(connections.updatedAt)
8328
- ).limit(1);
8329
- if (!row) {
8330
- return null;
8331
- }
8332
- let credential;
8333
- try {
8334
- credential = JSON.parse(decryptEnvironmentValue(key, row.credentialEncrypted));
8335
- } catch (error) {
8336
- throw new Error(
8337
- `connection credential could not be decrypted for ${row.id}: ${error instanceof Error ? error.message : String(error)}`,
8338
- { cause: error }
8339
- );
8340
- }
8341
- if (!credential || typeof credential !== "object" || Array.isArray(credential)) {
8342
- throw new Error(`connection credential bundle for ${row.id} is not a JSON object`);
8518
+ return await withConnectionSubjectRls(
8519
+ db,
8520
+ input.workspaceId,
8521
+ input.allowSubjectOwned ? input.subjectId : null,
8522
+ async (scopedDb) => {
8523
+ const [row] = await scopedDb.select().from(connections).where(and7(...conditions)).orderBy(
8524
+ desc2(sql4`(${connections.status} = 'active')`),
8525
+ desc2(connections.updatedAt)
8526
+ ).limit(1);
8527
+ if (!row) {
8528
+ return null;
8529
+ }
8530
+ let credential;
8531
+ try {
8532
+ credential = JSON.parse(decryptEnvironmentValue(key, row.credentialEncrypted));
8533
+ } catch (error) {
8534
+ throw new Error(
8535
+ `connection credential could not be decrypted for ${row.id}: ${error instanceof Error ? error.message : String(error)}`,
8536
+ { cause: error }
8537
+ );
8538
+ }
8539
+ if (!credential || typeof credential !== "object" || Array.isArray(credential)) {
8540
+ throw new Error(`connection credential bundle for ${row.id} is not a JSON object`);
8541
+ }
8542
+ return {
8543
+ id: row.id,
8544
+ accountId: row.accountId,
8545
+ workspaceId: row.workspaceId,
8546
+ subjectId: row.subjectId,
8547
+ providerDomain: row.providerDomain,
8548
+ kind: row.kind,
8549
+ status: row.status,
8550
+ credential,
8551
+ grantedScopes: row.grantedScopes,
8552
+ expiresAt: row.expiresAt,
8553
+ lastRefreshAt: row.lastRefreshAt,
8554
+ version: row.version,
8555
+ metadata: row.metadata
8556
+ };
8343
8557
  }
8344
- return {
8345
- id: row.id,
8346
- accountId: row.accountId,
8347
- workspaceId: row.workspaceId,
8348
- subjectId: row.subjectId,
8349
- providerDomain: row.providerDomain,
8350
- kind: row.kind,
8351
- status: row.status,
8352
- credential,
8353
- grantedScopes: row.grantedScopes,
8354
- expiresAt: row.expiresAt,
8355
- lastRefreshAt: row.lastRefreshAt,
8356
- version: row.version,
8357
- metadata: row.metadata
8358
- };
8359
- });
8558
+ );
8360
8559
  }
8361
8560
  async function recordConnectionTokenRefresh(db, input) {
8362
- return await withWorkspaceRls(db, input.workspaceId, async (scopedDb) => {
8363
- const set = {
8364
- credentialEncrypted: input.credentialEncrypted,
8365
- expiresAt: input.expiresAt,
8366
- lastRefreshAt: input.lastRefreshAt,
8367
- status: "active",
8368
- lastError: null,
8369
- version: sql4`${connections.version} + 1`,
8370
- updatedAt: /* @__PURE__ */ new Date(),
8371
- ...input.grantedScopes !== void 0 ? { grantedScopes: input.grantedScopes } : {}
8372
- };
8373
- const updated = await scopedDb.update(connections).set(set).where(
8374
- and7(
8375
- eq7(connections.id, input.id),
8376
- eq7(connections.workspaceId, input.workspaceId),
8377
- eq7(connections.version, input.version),
8378
- // A refresh may only ever renew a live credential; revoked/errored rows
8379
- // stay dead even if a status change somewhere forgot to bump version.
8380
- eq7(connections.status, "active")
8381
- )
8382
- ).returning({ id: connections.id });
8383
- return updated.length > 0;
8384
- });
8561
+ return await withConnectionSubjectRls(
8562
+ db,
8563
+ input.workspaceId,
8564
+ input.subjectId,
8565
+ async (scopedDb) => {
8566
+ const set = {
8567
+ credentialEncrypted: input.credentialEncrypted,
8568
+ expiresAt: input.expiresAt,
8569
+ lastRefreshAt: input.lastRefreshAt,
8570
+ status: "active",
8571
+ lastError: null,
8572
+ version: sql4`${connections.version} + 1`,
8573
+ updatedAt: /* @__PURE__ */ new Date(),
8574
+ ...input.grantedScopes !== void 0 ? { grantedScopes: input.grantedScopes } : {}
8575
+ };
8576
+ const updated = await scopedDb.update(connections).set(set).where(
8577
+ and7(
8578
+ eq7(connections.id, input.id),
8579
+ eq7(connections.workspaceId, input.workspaceId),
8580
+ eq7(connections.version, input.version),
8581
+ connectionExactSubject(input.subjectId),
8582
+ // A refresh may only ever renew a live credential; revoked/errored rows
8583
+ // stay dead even if a status change somewhere forgot to bump version.
8584
+ eq7(connections.status, "active")
8585
+ )
8586
+ ).returning({ id: connections.id });
8587
+ return updated.length > 0;
8588
+ }
8589
+ );
8385
8590
  }
8386
8591
  async function setConnectionStatus(db, workspaceId, status, lastError, guard) {
8387
- return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
8592
+ return await withConnectionSubjectRls(db, workspaceId, guard.subjectId, async (scopedDb) => {
8388
8593
  const updated = await scopedDb.update(connections).set({
8389
8594
  status,
8390
8595
  lastError,
@@ -8398,21 +8603,23 @@ async function setConnectionStatus(db, workspaceId, status, lastError, guard) {
8398
8603
  and7(
8399
8604
  eq7(connections.workspaceId, workspaceId),
8400
8605
  eq7(connections.id, guard.id),
8401
- eq7(connections.version, guard.version)
8606
+ eq7(connections.version, guard.version),
8607
+ connectionExactSubject(guard.subjectId)
8402
8608
  )
8403
8609
  ).returning({ id: connections.id });
8404
8610
  return updated.length > 0;
8405
8611
  });
8406
8612
  }
8407
- async function recordConnectionUsed(db, workspaceId, connectionId) {
8408
- await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
8613
+ async function recordConnectionUsed(db, workspaceId, connectionId, subjectId) {
8614
+ await withConnectionSubjectRls(db, workspaceId, subjectId, async (scopedDb) => {
8409
8615
  await scopedDb.update(connections).set({
8410
8616
  lastUsedAt: /* @__PURE__ */ new Date(),
8411
8617
  updatedAt: /* @__PURE__ */ new Date()
8412
8618
  }).where(
8413
8619
  and7(
8414
8620
  eq7(connections.workspaceId, workspaceId),
8415
- eq7(connections.id, connectionId)
8621
+ eq7(connections.id, connectionId),
8622
+ connectionExactSubject(subjectId)
8416
8623
  )
8417
8624
  );
8418
8625
  });
@@ -13536,6 +13743,7 @@ async function createSessionInTransaction(tx, input, id) {
13536
13743
  initialMessage: input.initialMessage,
13537
13744
  initialTurnInstructions: input.initialTurnInstructions ?? null,
13538
13745
  resources: input.resources,
13746
+ skills: input.skills ?? [],
13539
13747
  tools: input.tools ?? [],
13540
13748
  toolPolicy: input.toolPolicy ?? null,
13541
13749
  metadata: input.metadata,
@@ -13548,6 +13756,7 @@ async function createSessionInTransaction(tx, input, id) {
13548
13756
  rigId: input.rigId ?? null,
13549
13757
  rigVersionId: input.rigVersionId ?? null,
13550
13758
  firstPartyMcpPermissions: input.firstPartyMcpPermissions ?? null,
13759
+ firstPartyMcpTools: input.firstPartyMcpTools ?? null,
13551
13760
  instructions: input.instructions ?? null,
13552
13761
  parentSessionId: input.parentSessionId ?? null,
13553
13762
  createIdempotencyKey,
@@ -14852,25 +15061,34 @@ async function getSessionLineage(db, workspaceId, sessionId) {
14852
15061
  if (rootRows.length === 0) {
14853
15062
  return null;
14854
15063
  }
14855
- const ancestorRows = await scopedDb.execute(sql4`
14856
- with recursive ancestors(id, parent_session_id, depth, path) as (
14857
- select ${sessions.id}, ${sessions.parentSessionId}, 0, array[${sessions.id}]
15064
+ const ancestorLineageRows = await scopedDb.execute(sql4`
15065
+ with recursive ancestors(id, parent_session_id, depth, path, cycle) as (
15066
+ select ${sessions.id}, ${sessions.parentSessionId}, 0, array[${sessions.id}], false
14858
15067
  from ${sessions}
14859
15068
  where ${sessions.workspaceId} = ${workspaceId}
14860
15069
  and ${sessions.id} = ${sessionId}
14861
15070
  union all
14862
- select parent.id, parent.parent_session_id, ancestors.depth + 1, ancestors.path || parent.id
15071
+ select
15072
+ parent.id,
15073
+ parent.parent_session_id,
15074
+ ancestors.depth + 1,
15075
+ ancestors.path || parent.id,
15076
+ parent.id = any(ancestors.path)
14863
15077
  from ${sessions} parent
14864
15078
  join ancestors on ancestors.parent_session_id = parent.id
14865
15079
  where parent.workspace_id = ${workspaceId}
14866
- and ancestors.depth < 10
14867
- and not parent.id = any(ancestors.path)
15080
+ and not ancestors.cycle
15081
+ and ancestors.depth < 64
14868
15082
  )
14869
- select id, parent_session_id as "parentSessionId", depth, path
15083
+ select id, parent_session_id as "parentSessionId", depth, path, cycle
14870
15084
  from ancestors
14871
- where depth > 0
14872
15085
  order by depth desc
14873
15086
  `);
15087
+ const frontier = ancestorLineageRows[0];
15088
+ if (!frontier || frontier.cycle || frontier.parentSessionId !== null || Number(frontier.depth) >= 64) {
15089
+ throw new Error(`session lineage for ${sessionId} has no valid workspace root`);
15090
+ }
15091
+ const ancestorRows = ancestorLineageRows.filter((row) => row.depth > 0);
14874
15092
  const childRows = await scopedDb.execute(sql4`
14875
15093
  with recursive descendants(id, parent_session_id, depth, path) as (
14876
15094
  select child.id, child.parent_session_id, 1, array[${sessionId}, child.id]
@@ -14885,7 +15103,7 @@ async function getSessionLineage(db, workspaceId, sessionId) {
14885
15103
  and descendants.depth < 5
14886
15104
  and not child.id = any(descendants.path)
14887
15105
  )
14888
- select id, parent_session_id as "parentSessionId", depth, path
15106
+ select id, parent_session_id as "parentSessionId", depth, path, false as cycle
14889
15107
  from descendants
14890
15108
  order by path
14891
15109
  limit ${descendantLimit + 1}
@@ -22005,7 +22223,7 @@ async function materializeGoalContinuation(db, input) {
22005
22223
  eq7(sessionSystemUpdates.workspaceId, input.workspaceId),
22006
22224
  eq7(sessionSystemUpdates.sessionId, input.sessionId),
22007
22225
  eq7(sessionSystemUpdates.kind, "agent_steer_instruction"),
22008
- inArray3(sessionSystemUpdates.state, ["pending", "deferred"])
22226
+ eq7(sessionSystemUpdates.state, "pending")
22009
22227
  )
22010
22228
  ).limit(1);
22011
22229
  if (pendingSteer) {
@@ -22128,6 +22346,7 @@ async function materializeGoalContinuation(db, input) {
22128
22346
  idempotencyKey: `agent_run.created:goal:${input.workspaceId}:${decision.goal.id}:${goalWakeRevision}`,
22129
22347
  occurredAt: now
22130
22348
  }).onConflictDoNothing({ target: usageEvents.idempotencyKey });
22349
+ const eventPreview = internalUpdateEventMember(update);
22131
22350
  const insertedEvents = await tx.insert(sessionEvents).values([
22132
22351
  {
22133
22352
  accountId: session.accountId,
@@ -22136,11 +22355,13 @@ async function materializeGoalContinuation(db, input) {
22136
22355
  sequence: session.lastSequence + 1,
22137
22356
  type: "system.update.pending",
22138
22357
  payload: sanitizeEventPayload({
22139
- updateId: update.id,
22140
- kind: update.kind,
22141
- classification: update.classification,
22142
- sourceId: update.sourceId,
22143
- summary: update.summary
22358
+ updateId: eventPreview.id,
22359
+ kind: eventPreview.kind,
22360
+ classification: eventPreview.classification,
22361
+ sourceId: eventPreview.sourceId,
22362
+ sourceIdTruncated: eventPreview.sourceIdTruncated,
22363
+ summary: eventPreview.summary,
22364
+ summaryTruncated: eventPreview.summaryTruncated
22144
22365
  }),
22145
22366
  occurredAt: now
22146
22367
  },
@@ -22521,6 +22742,62 @@ async function enqueueSessionTurn(db, input) {
22521
22742
  var MAX_INTERNAL_UPDATE_BYTES = 64 * 1024;
22522
22743
  var MAX_INTERNAL_UPDATE_BATCH_MEMBERS = 100;
22523
22744
  var MAX_INTERNAL_UPDATE_BATCH_BYTES = 256 * 1024;
22745
+ var MAX_INTERNAL_UPDATE_EVENT_SUMMARY_BYTES = 512;
22746
+ var MAX_INTERNAL_UPDATE_EVENT_SOURCE_BYTES = 256;
22747
+ function boundedInternalUpdateEventText(value, maxBytes) {
22748
+ if (Buffer.byteLength(value) <= maxBytes) return { text: value, truncated: false };
22749
+ const suffix = "\u2026";
22750
+ const bodyBudget = maxBytes - Buffer.byteLength(suffix);
22751
+ const bytes = Buffer.from(value);
22752
+ let text = bytes.subarray(0, bodyBudget).toString("utf8");
22753
+ if (text.endsWith("\uFFFD")) text = text.slice(0, -1);
22754
+ return { text: `${text}${suffix}`, truncated: true };
22755
+ }
22756
+ function internalUpdateEventMember(update) {
22757
+ const summary = boundedInternalUpdateEventText(
22758
+ update.summary,
22759
+ MAX_INTERNAL_UPDATE_EVENT_SUMMARY_BYTES
22760
+ );
22761
+ const source = boundedInternalUpdateEventText(
22762
+ update.sourceId,
22763
+ MAX_INTERNAL_UPDATE_EVENT_SOURCE_BYTES
22764
+ );
22765
+ return {
22766
+ id: update.id,
22767
+ kind: update.kind,
22768
+ classification: update.classification,
22769
+ sourceId: source.text,
22770
+ sourceIdTruncated: source.truncated,
22771
+ summary: summary.text,
22772
+ summaryTruncated: summary.truncated
22773
+ };
22774
+ }
22775
+ function selectBoundedSystemUpdateBatch(updates) {
22776
+ const selected = [];
22777
+ let selectedBytes = 0;
22778
+ for (const update of updates) {
22779
+ const updateBytes = Buffer.byteLength(
22780
+ JSON.stringify({
22781
+ id: update.id,
22782
+ kind: update.kind,
22783
+ classification: update.classification,
22784
+ sourceId: update.sourceId,
22785
+ summary: update.summary,
22786
+ payload: update.payload,
22787
+ lineage: update.lineage
22788
+ })
22789
+ );
22790
+ if (selected.length >= MAX_INTERNAL_UPDATE_BATCH_MEMBERS || // One individually large canonical input must still make progress. The
22791
+ // model/context boundary may reject it explicitly, but the queue cannot
22792
+ // wedge forever merely because the coalescing target is smaller.
22793
+ selected.length > 0 && selectedBytes + updateBytes > MAX_INTERNAL_UPDATE_BATCH_BYTES) {
22794
+ break;
22795
+ }
22796
+ selected.push(update);
22797
+ selectedBytes += updateBytes;
22798
+ }
22799
+ return selected;
22800
+ }
22524
22801
  async function claimSessionWorkForAttempt(db, workspaceId, input) {
22525
22802
  const { sessionId, workflowId } = input;
22526
22803
  return await withWorkspaceRls(
@@ -22532,29 +22809,18 @@ async function claimSessionWorkForAttempt(db, workspaceId, input) {
22532
22809
  and7(
22533
22810
  eq7(sessionSystemUpdates.workspaceId, workspaceId),
22534
22811
  eq7(sessionSystemUpdates.sessionId, sessionId),
22535
- inArray3(sessionSystemUpdates.state, ["pending", "deferred"]),
22812
+ eq7(sessionSystemUpdates.state, "pending"),
22536
22813
  eq7(sessionSystemUpdates.kind, "agent_steer_instruction")
22537
22814
  )
22538
22815
  ).orderBy(
22539
22816
  desc2(sessionSystemUpdates.createdAt),
22540
22817
  desc2(sessionSystemUpdates.id)
22541
22818
  ).limit(1).for("update");
22542
- if (agentSteer) {
22543
- await tx.update(sessionSystemUpdates).set({ state: "superseded" }).where(
22544
- and7(
22545
- eq7(sessionSystemUpdates.workspaceId, workspaceId),
22546
- eq7(sessionSystemUpdates.sessionId, sessionId),
22547
- eq7(sessionSystemUpdates.kind, "agent_steer_instruction"),
22548
- inArray3(sessionSystemUpdates.state, ["pending", "deferred"]),
22549
- ne(sessionSystemUpdates.id, agentSteer.id)
22550
- )
22551
- );
22552
- }
22553
22819
  const ordinary = await tx.select().from(sessionSystemUpdates).where(
22554
22820
  and7(
22555
22821
  eq7(sessionSystemUpdates.workspaceId, workspaceId),
22556
22822
  eq7(sessionSystemUpdates.sessionId, sessionId),
22557
- inArray3(sessionSystemUpdates.state, ["pending", "deferred"]),
22823
+ eq7(sessionSystemUpdates.state, "pending"),
22558
22824
  ne(sessionSystemUpdates.kind, "agent_steer_instruction")
22559
22825
  )
22560
22826
  ).orderBy(
@@ -22567,12 +22833,15 @@ async function claimSessionWorkForAttempt(db, workspaceId, input) {
22567
22833
  count: 0,
22568
22834
  lastSequence: nextSequence - 1,
22569
22835
  triggerEventId: null,
22836
+ historyItemId: null,
22837
+ historyItem: null,
22570
22838
  updates: [],
22839
+ events: [],
22571
22840
  event: null
22572
22841
  };
22573
22842
  }
22574
- const deliverable = [];
22575
- let deliveredBytes = 0;
22843
+ const validUpdates = [];
22844
+ const cancelledUpdateIds = [];
22576
22845
  for (const update of updates) {
22577
22846
  const payload = update.payload;
22578
22847
  if (payload.type === "goal_continuation") {
@@ -22590,38 +22859,56 @@ async function claimSessionWorkForAttempt(db, workspaceId, input) {
22590
22859
  ).for("update").limit(1) : [];
22591
22860
  if (!goal || goal.status !== "active" || goal.version !== goalVersion) {
22592
22861
  await tx.update(sessionSystemUpdates).set({ state: "cancelled" }).where(eq7(sessionSystemUpdates.id, update.id));
22862
+ cancelledUpdateIds.push(update.id);
22593
22863
  continue;
22594
22864
  }
22595
22865
  }
22596
- const updateBytes = Buffer.byteLength(
22597
- JSON.stringify({
22598
- id: update.id,
22599
- kind: update.kind,
22600
- classification: update.classification,
22601
- sourceId: update.sourceId,
22602
- summary: update.summary,
22603
- payload: update.payload,
22604
- lineage: update.lineage
22605
- })
22606
- );
22607
- if (deliverable.length >= MAX_INTERNAL_UPDATE_BATCH_MEMBERS || deliveredBytes + updateBytes > MAX_INTERNAL_UPDATE_BATCH_BYTES) {
22608
- break;
22609
- }
22610
- deliverable.push(update);
22611
- deliveredBytes += updateBytes;
22866
+ validUpdates.push(update);
22612
22867
  }
22868
+ const deliverable = selectBoundedSystemUpdateBatch(validUpdates);
22613
22869
  if (deliverable.length === 0) {
22870
+ const cancellationEvent = cancelledUpdateIds.length > 0 ? {
22871
+ accountId,
22872
+ workspaceId,
22873
+ sessionId,
22874
+ // No receiving turn exists when every candidate was
22875
+ // cancelled before a model batch could be persisted.
22876
+ turnId: null,
22877
+ turnGeneration: null,
22878
+ turnAttemptId: null,
22879
+ turnAssociation: null,
22880
+ sequence: nextSequence,
22881
+ type: "system.update.cancelled",
22882
+ payload: sanitizeEventPayload({
22883
+ updateIds: cancelledUpdateIds,
22884
+ count: cancelledUpdateIds.length,
22885
+ reason: "stale_goal_continuation"
22886
+ }),
22887
+ occurredAt
22888
+ } : null;
22614
22889
  return {
22615
22890
  count: 0,
22616
- lastSequence: nextSequence - 1,
22891
+ lastSequence: cancellationEvent ? nextSequence : nextSequence - 1,
22617
22892
  triggerEventId: null,
22893
+ historyItemId: null,
22894
+ historyItem: null,
22618
22895
  updates: [],
22896
+ events: cancellationEvent ? [cancellationEvent] : [],
22619
22897
  event: null
22620
22898
  };
22621
22899
  }
22900
+ const modelOrdered = [
22901
+ ...deliverable.filter((update) => update.kind !== "agent_steer_instruction"),
22902
+ ...deliverable.filter((update) => update.kind === "agent_steer_instruction")
22903
+ ];
22904
+ const historyItemId = crypto.randomUUID();
22905
+ const historyItem = sessionSystemUpdateBatchHistoryItem(
22906
+ modelOrdered.map((update) => mapSessionSystemUpdate(update))
22907
+ );
22622
22908
  await tx.update(sessionSystemUpdates).set({
22623
22909
  state: "delivered",
22624
22910
  deliveredTurnId: turnId,
22911
+ deliveredHistoryItemId: historyItemId,
22625
22912
  deliveredAt: occurredAt
22626
22913
  }).where(
22627
22914
  and7(
@@ -22634,6 +22921,27 @@ async function claimSessionWorkForAttempt(db, workspaceId, input) {
22634
22921
  )
22635
22922
  );
22636
22923
  const eventId = triggerEventId ?? crypto.randomUUID();
22924
+ let sequence = nextSequence - 1;
22925
+ const events = [];
22926
+ if (cancelledUpdateIds.length > 0) {
22927
+ events.push({
22928
+ accountId,
22929
+ workspaceId,
22930
+ sessionId,
22931
+ turnId,
22932
+ turnGeneration,
22933
+ turnAttemptId: input.attemptId,
22934
+ turnAssociation: "current",
22935
+ sequence: ++sequence,
22936
+ type: "system.update.cancelled",
22937
+ payload: sanitizeEventPayload({
22938
+ updateIds: cancelledUpdateIds,
22939
+ count: cancelledUpdateIds.length,
22940
+ reason: "stale_goal_continuation"
22941
+ }),
22942
+ occurredAt
22943
+ });
22944
+ }
22637
22945
  const event = {
22638
22946
  id: eventId,
22639
22947
  accountId,
@@ -22643,23 +22951,55 @@ async function claimSessionWorkForAttempt(db, workspaceId, input) {
22643
22951
  turnGeneration,
22644
22952
  turnAttemptId: input.attemptId,
22645
22953
  turnAssociation: "current",
22646
- sequence: nextSequence,
22954
+ sequence: ++sequence,
22647
22955
  type: "system.update.delivered",
22648
22956
  payload: sanitizeEventPayload({
22649
22957
  updateIds: deliverable.map((update) => update.id),
22958
+ historyItemId,
22650
22959
  count: deliverable.length,
22651
- classifications: [...new Set(deliverable.map((update) => update.classification))]
22960
+ classifications: [...new Set(deliverable.map((update) => update.classification))],
22961
+ members: modelOrdered.map(internalUpdateEventMember)
22652
22962
  }),
22653
22963
  occurredAt
22654
22964
  };
22965
+ events.push(event);
22655
22966
  return {
22656
22967
  count: deliverable.length,
22657
- lastSequence: nextSequence,
22968
+ lastSequence: sequence,
22658
22969
  triggerEventId: eventId,
22970
+ historyItemId,
22971
+ historyItem,
22659
22972
  updates: deliverable,
22973
+ events,
22660
22974
  event
22661
22975
  };
22662
22976
  };
22977
+ const persistDeliveredUpdateBatch = async (delivered2, accountId, turnId) => {
22978
+ if (!delivered2.historyItemId || !delivered2.historyItem) {
22979
+ if (delivered2.count !== 0) {
22980
+ throw new Error("Delivered machine-input batch has no model-memory item");
22981
+ }
22982
+ return;
22983
+ }
22984
+ const [{ position } = { position: 0 }] = await tx.select({
22985
+ position: sql4`coalesce(max(${sessionHistoryItems.position}), -1) + 1`
22986
+ }).from(sessionHistoryItems).where(
22987
+ and7(
22988
+ eq7(sessionHistoryItems.workspaceId, workspaceId),
22989
+ eq7(sessionHistoryItems.sessionId, sessionId)
22990
+ )
22991
+ );
22992
+ await tx.insert(sessionHistoryItems).values({
22993
+ id: delivered2.historyItemId,
22994
+ accountId,
22995
+ workspaceId,
22996
+ sessionId,
22997
+ turnId,
22998
+ position: Number(position),
22999
+ item: sanitizeModelPayload(delivered2.historyItem),
23000
+ producerCodexCredentialId: null
23001
+ });
23002
+ };
22663
23003
  const workspaceControl = await lockWorkspaceInferenceControl(
22664
23004
  tx,
22665
23005
  workspaceId,
@@ -22694,6 +23034,12 @@ async function claimSessionWorkForAttempt(db, workspaceId, input) {
22694
23034
  and7(
22695
23035
  eq7(sessionAttemptInterruptions.workspaceId, workspaceId),
22696
23036
  eq7(sessionAttemptInterruptions.sessionId, sessionId),
23037
+ inArray3(sessionAttemptInterruptions.state, [
23038
+ "pending",
23039
+ "delivered",
23040
+ "acknowledged",
23041
+ "settled"
23042
+ ]),
22697
23043
  isNull2(sessionTurnAttempts.quiescedAt)
22698
23044
  )
22699
23045
  ).orderBy(desc2(sessionAttemptInterruptions.requestedAt)).limit(1);
@@ -22899,17 +23245,21 @@ async function claimSessionWorkForAttempt(db, workspaceId, input) {
22899
23245
  eq7(sessionSystemUpdates.workspaceId, workspaceId),
22900
23246
  eq7(sessionSystemUpdates.sessionId, sessionId),
22901
23247
  eq7(sessionSystemUpdates.kind, "agent_steer_instruction"),
22902
- inArray3(sessionSystemUpdates.state, ["pending", "deferred"])
23248
+ eq7(sessionSystemUpdates.state, "pending")
22903
23249
  )
22904
23250
  ).orderBy(
22905
23251
  desc2(sessionSystemUpdates.createdAt),
22906
23252
  desc2(sessionSystemUpdates.id)
22907
23253
  ).limit(1).for("update");
22908
- const rows = pendingAgentSteer ? [] : await rawRows(
23254
+ const rows = await rawRows(
22909
23255
  tx,
22910
23256
  sql4`select id, trigger_event_id, metadata from session_turns
22911
23257
  where workspace_id = ${workspaceId} and session_id = ${sessionId}
22912
23258
  and status = 'queued' and source in ('user', 'api')
23259
+ and (
23260
+ ${Boolean(pendingAgentSteer)} = false
23261
+ or metadata->>'delivery' = 'steer'
23262
+ )
22913
23263
  order by position asc, created_at asc, id asc
22914
23264
  limit 1`
22915
23265
  );
@@ -23073,6 +23423,19 @@ async function claimSessionWorkForAttempt(db, workspaceId, input) {
23073
23423
  triggerEventId
23074
23424
  );
23075
23425
  if (delivered2.count === 0) {
23426
+ if (delivered2.events.length > 0) {
23427
+ await tx.insert(sessionEvents).values(delivered2.events);
23428
+ await tx.update(sessions).set({
23429
+ status: "idle",
23430
+ lastSequence: delivered2.lastSequence,
23431
+ updatedAt: now2
23432
+ }).where(
23433
+ and7(
23434
+ eq7(sessions.workspaceId, workspaceId),
23435
+ eq7(sessions.id, sessionId)
23436
+ )
23437
+ );
23438
+ }
23076
23439
  return { action: "unclaimed", reason: "no-work" };
23077
23440
  }
23078
23441
  const goalUpdate = delivered2.updates.find(
@@ -23211,9 +23574,10 @@ async function claimSessionWorkForAttempt(db, workspaceId, input) {
23211
23574
  startedAt: now2
23212
23575
  }).returning();
23213
23576
  if (!internalTurn) throw new Error("Failed to create internal update inference");
23577
+ await persistDeliveredUpdateBatch(delivered2, session.accountId, internalTurn.id);
23214
23578
  await registerAttempt(internalTurn);
23215
23579
  if (!delivered2.event) throw new Error("Delivered update batch has no durable event");
23216
- await tx.insert(sessionEvents).values(delivered2.event);
23580
+ await tx.insert(sessionEvents).values(delivered2.events);
23217
23581
  if (goalUpdate && typeof goalUpdate.payload.goalId === "string") {
23218
23582
  await tx.update(sessionGoals).set({ lastContinuationTurnId: internalTurn.id, updatedAt: now2 }).where(
23219
23583
  and7(
@@ -23310,8 +23674,9 @@ async function claimSessionWorkForAttempt(db, workspaceId, input) {
23310
23674
  session.lastSequence + 1,
23311
23675
  now
23312
23676
  );
23313
- if (delivered.event) {
23314
- await tx.insert(sessionEvents).values(delivered.event);
23677
+ await persistDeliveredUpdateBatch(delivered, session.accountId, row.id);
23678
+ if (delivered.events.length > 0) {
23679
+ await tx.insert(sessionEvents).values(delivered.events);
23315
23680
  }
23316
23681
  await tx.update(sessions).set({
23317
23682
  status: "running",
@@ -23455,6 +23820,127 @@ async function markSessionAttemptQuiesced(db, input) {
23455
23820
  return [mapEvent2(event)];
23456
23821
  });
23457
23822
  }
23823
+ async function getSessionAttemptActivityRef(db, input) {
23824
+ return await withRlsContext(
23825
+ db,
23826
+ { accountId: input.accountId, workspaceId: input.workspaceId },
23827
+ async (scopedDb) => {
23828
+ const [attempt] = await scopedDb.select({
23829
+ workflowId: sessionTurnAttempts.temporalWorkflowId,
23830
+ workflowRunId: sessionTurnAttempts.temporalWorkflowRunId,
23831
+ activityId: sessionTurnAttempts.temporalActivityId,
23832
+ quiescedAt: sessionTurnAttempts.quiescedAt
23833
+ }).from(sessionTurnAttempts).where(
23834
+ and7(
23835
+ eq7(sessionTurnAttempts.accountId, input.accountId),
23836
+ eq7(sessionTurnAttempts.workspaceId, input.workspaceId),
23837
+ eq7(sessionTurnAttempts.sessionId, input.sessionId),
23838
+ eq7(sessionTurnAttempts.id, input.attemptId),
23839
+ eq7(sessionTurnAttempts.temporalWorkflowId, input.temporalWorkflowId)
23840
+ )
23841
+ ).limit(1);
23842
+ return attempt ? {
23843
+ workflowId: attempt.workflowId,
23844
+ workflowRunId: attempt.workflowRunId,
23845
+ activityId: attempt.activityId,
23846
+ quiesced: attempt.quiescedAt !== null
23847
+ } : null;
23848
+ }
23849
+ );
23850
+ }
23851
+ async function reconcileSessionAttemptQuiescence(db, input) {
23852
+ const eligibility = await withRlsContext(
23853
+ db,
23854
+ { accountId: input.accountId, workspaceId: input.workspaceId },
23855
+ async (scopedDb) => {
23856
+ const rows = await scopedDb.execute(sql4`
23857
+ select
23858
+ attempt.account_id,
23859
+ attempt.state,
23860
+ attempt.quiesced_at,
23861
+ attempt.temporal_workflow_id,
23862
+ attempt.temporal_workflow_run_id,
23863
+ attempt.temporal_activity_id,
23864
+ exists (
23865
+ select 1
23866
+ from session_attempt_interruptions interruption
23867
+ where interruption.workspace_id = attempt.workspace_id
23868
+ and interruption.session_id = attempt.session_id
23869
+ and interruption.attempt_id = attempt.id
23870
+ and interruption.state in ('settled', 'rejected_stale')
23871
+ ) as interruption_settled,
23872
+ exists (
23873
+ select 1
23874
+ from session_attempt_interruptions interruption
23875
+ where interruption.workspace_id = attempt.workspace_id
23876
+ and interruption.session_id = attempt.session_id
23877
+ and interruption.attempt_id = attempt.id
23878
+ and interruption.state in ('pending', 'delivered', 'acknowledged')
23879
+ ) as interruption_pending,
23880
+ (
23881
+ exists (
23882
+ select 1
23883
+ from sandbox_workspace_mutation_admissions admission
23884
+ where admission.account_id = attempt.account_id
23885
+ and admission.workspace_id = attempt.workspace_id
23886
+ and admission.session_id = attempt.session_id
23887
+ and admission.settled_at is null
23888
+ and (
23889
+ admission.attempt_id = attempt.id
23890
+ or (
23891
+ admission.actor_kind = 'process'
23892
+ and exists (
23893
+ select 1
23894
+ from sandbox_retained_processes process
23895
+ where process.account_id = attempt.account_id
23896
+ and process.workspace_id = attempt.workspace_id
23897
+ and process.session_id = attempt.session_id
23898
+ and process.id = admission.actor_id
23899
+ and process.owner_attempt_id = attempt.id
23900
+ )
23901
+ )
23902
+ )
23903
+ )
23904
+ or exists (
23905
+ select 1
23906
+ from sandbox_retained_processes process
23907
+ where process.account_id = attempt.account_id
23908
+ and process.workspace_id = attempt.workspace_id
23909
+ and process.session_id = attempt.session_id
23910
+ and process.owner_attempt_id = attempt.id
23911
+ and process.state = 'active'
23912
+ )
23913
+ ) as writer_pending
23914
+ from session_turn_attempts attempt
23915
+ where attempt.account_id = ${input.accountId}
23916
+ and attempt.workspace_id = ${input.workspaceId}
23917
+ and attempt.session_id = ${input.sessionId}
23918
+ and attempt.id = ${input.attemptId}
23919
+ limit 1
23920
+ `);
23921
+ return rows[0] ?? null;
23922
+ }
23923
+ );
23924
+ if (!eligibility || eligibility.account_id !== input.accountId || eligibility.temporal_workflow_id !== input.temporalWorkflowId || eligibility.temporal_workflow_run_id !== input.temporalWorkflowRunId || eligibility.temporal_activity_id !== input.temporalActivityId || eligibility.state !== "closed" || !eligibility.interruption_settled || eligibility.interruption_pending) {
23925
+ return { action: "stale", events: [] };
23926
+ }
23927
+ if (eligibility.quiesced_at) {
23928
+ return { action: "quiesced", events: [] };
23929
+ }
23930
+ if (!input.activitySettled || eligibility.writer_pending) {
23931
+ return { action: "pending", events: [] };
23932
+ }
23933
+ const events = await markSessionAttemptQuiesced(db, {
23934
+ accountId: input.accountId,
23935
+ workspaceId: input.workspaceId,
23936
+ sessionId: input.sessionId,
23937
+ attemptId: input.attemptId,
23938
+ temporalWorkflowId: input.temporalWorkflowId,
23939
+ temporalWorkflowRunId: input.temporalWorkflowRunId,
23940
+ temporalActivityId: input.temporalActivityId
23941
+ });
23942
+ return { action: "quiesced", events };
23943
+ }
23458
23944
  async function settleSessionAttemptInterruptions(db, workspaceId, sessionId, attemptId) {
23459
23945
  return await withWorkspaceRls(
23460
23946
  db,
@@ -23583,12 +24069,6 @@ async function settleSessionAttemptInterruptions(db, workspaceId, sessionId, att
23583
24069
  outcome,
23584
24070
  closedAt: now
23585
24071
  });
23586
- await requeueInterruptedSessionSystemUpdatesForTurnTx(
23587
- tx,
23588
- workspaceId,
23589
- sessionId,
23590
- turn.id
23591
- );
23592
24072
  const eventValues = steer ? [
23593
24073
  {
23594
24074
  accountId: session.accountId,
@@ -24458,10 +24938,36 @@ async function applySessionTurnSettlement(db, workspaceId, input) {
24458
24938
  }
24459
24939
  })
24460
24940
  );
24941
+ const settledMachineInputs = ["completed", "failed", "cancelled", "superseded"].includes(
24942
+ input.turnStatus
24943
+ ) ? await tx.select({
24944
+ id: sessionSystemUpdates.id,
24945
+ historyItemId: sessionSystemUpdates.deliveredHistoryItemId
24946
+ }).from(sessionSystemUpdates).where(
24947
+ and7(
24948
+ eq7(sessionSystemUpdates.workspaceId, workspaceId),
24949
+ eq7(sessionSystemUpdates.sessionId, input.sessionId),
24950
+ eq7(sessionSystemUpdates.deliveredTurnId, input.turnId),
24951
+ eq7(sessionSystemUpdates.state, "delivered")
24952
+ )
24953
+ ).orderBy(
24954
+ asc4(sessionSystemUpdates.createdAt),
24955
+ asc4(sessionSystemUpdates.id)
24956
+ ) : [];
24957
+ const machineInputSettlementEvent = settledMachineInputs.length > 0 ? {
24958
+ type: "system.update.settled",
24959
+ payload: {
24960
+ updateIds: settledMachineInputs.map((update) => update.id),
24961
+ count: settledMachineInputs.length,
24962
+ historyItemId: settledMachineInputs[0].historyItemId,
24963
+ outcome: input.turnStatus
24964
+ }
24965
+ } : null;
24461
24966
  const settlementEvents = [
24462
24967
  ...recordingEvent ? [recordingEvent] : [],
24463
24968
  ...compactionRequestEvent ? [compactionRequestEvent] : [],
24464
24969
  ...terminalHumanInputEvents,
24970
+ ...machineInputSettlementEvent ? [machineInputSettlementEvent] : [],
24465
24971
  ...input.events
24466
24972
  ];
24467
24973
  const values = settlementEvents.map((event) => {
@@ -24517,21 +25023,6 @@ async function applySessionTurnSettlement(db, workspaceId, input) {
24517
25023
  turn
24518
25024
  );
24519
25025
  }
24520
- if (input.turnStatus === "failed") {
24521
- await deferFailedSessionSystemUpdatesForTurnTx(
24522
- tx,
24523
- workspaceId,
24524
- input.sessionId,
24525
- input.turnId
24526
- );
24527
- } else if (["cancelled", "superseded"].includes(input.turnStatus)) {
24528
- await requeueInterruptedSessionSystemUpdatesForTurnTx(
24529
- tx,
24530
- workspaceId,
24531
- input.sessionId,
24532
- input.turnId
24533
- );
24534
- }
24535
25026
  await tx.update(sessions).set({
24536
25027
  status: effectiveSessionStatus,
24537
25028
  activeTurnId: input.activeTurnId,
@@ -24729,14 +25220,6 @@ async function settleCodexCredentialLeaseLoss(db, input) {
24729
25220
  eq7(sessions.activeTurnId, input.turnId)
24730
25221
  )
24731
25222
  );
24732
- if (!input.checkpointDurable) {
24733
- await deferFailedSessionSystemUpdatesForTurnTx(
24734
- tx,
24735
- input.workspaceId,
24736
- input.sessionId,
24737
- input.turnId
24738
- );
24739
- }
24740
25223
  await tx.execute(sql4`
24741
25224
  delete from codex_credential_leases
24742
25225
  where account_id = ${input.accountId}
@@ -25407,16 +25890,49 @@ async function getSessionQueueSnapshot(db, workspaceId, sessionId) {
25407
25890
  inArray3(sessionTurns.source, ["user", "api"])
25408
25891
  )
25409
25892
  ).orderBy(asc4(sessionTurns.position), asc4(sessionTurns.createdAt));
25893
+ const pendingInputs = await scopedDb.select().from(sessionSystemUpdates).where(
25894
+ and7(
25895
+ eq7(sessionSystemUpdates.workspaceId, workspaceId),
25896
+ eq7(sessionSystemUpdates.sessionId, sessionId),
25897
+ eq7(sessionSystemUpdates.state, "pending")
25898
+ )
25899
+ ).orderBy(
25900
+ sql4`case when ${sessionSystemUpdates.kind} = 'agent_steer_instruction' then 0 else 1 end`,
25901
+ asc4(sessionSystemUpdates.createdAt),
25902
+ asc4(sessionSystemUpdates.id)
25903
+ );
25410
25904
  const latestInterruption = await latestSessionAttemptInterruption(
25411
25905
  scopedDb,
25412
25906
  workspaceId,
25413
25907
  sessionId
25414
25908
  );
25909
+ const items = rows.map(mapSessionTurn);
25910
+ const nextInputBatch = selectBoundedSystemUpdateBatch(pendingInputs);
25911
+ const hasPendingAgentSteer = pendingInputs.some(
25912
+ (update) => update.kind === "agent_steer_instruction"
25913
+ );
25914
+ const attachmentTurn = hasPendingAgentSteer ? items.find((turn) => turn.metadata.delivery === "steer") : items[0];
25415
25915
  return {
25416
25916
  version: session.queueVersion,
25417
25917
  effectiveControl: serializeEffectiveSessionControl(effectiveControl),
25418
- stoppingPreviousAttempt: latestInterruption !== null && latestInterruption.quiescedAt === null,
25419
- items: rows.map(mapSessionTurn)
25918
+ stoppingPreviousAttempt: latestInterruption !== null && latestInterruption.interruptionState !== "rejected_stale" && latestInterruption.quiescedAt === null,
25919
+ items,
25920
+ pendingInputs: pendingInputs.map((update) => {
25921
+ const canonical = mapSessionSystemUpdate(update);
25922
+ return {
25923
+ id: canonical.id,
25924
+ sessionId: canonical.sessionId,
25925
+ kind: canonical.kind,
25926
+ classification: canonical.classification,
25927
+ sourceId: boundedInternalUpdateEventText(canonical.sourceId, 256).text,
25928
+ summary: boundedInternalUpdateEventText(canonical.summary, 512).text,
25929
+ createdAt: canonical.createdAt
25930
+ };
25931
+ }),
25932
+ pendingInputAttachment: attachmentTurn && nextInputBatch.length > 0 ? {
25933
+ turnId: attachmentTurn.id,
25934
+ inputIds: nextInputBatch.map((update) => update.id)
25935
+ } : null
25420
25936
  };
25421
25937
  });
25422
25938
  }
@@ -25625,6 +26141,30 @@ async function markSessionWorkflowWakeDelivered(db, input) {
25625
26141
  if (pendingAgentSteer) {
25626
26142
  return { action: "pending_admission", blocker: "pending_agent_steer" };
25627
26143
  }
26144
+ const [pendingQuiescence] = await tx.select({ id: sessionTurnAttempts.id }).from(sessionTurnAttempts).innerJoin(
26145
+ sessionAttemptInterruptions,
26146
+ and7(
26147
+ eq7(
26148
+ sessionAttemptInterruptions.workspaceId,
26149
+ sessionTurnAttempts.workspaceId
26150
+ ),
26151
+ eq7(
26152
+ sessionAttemptInterruptions.sessionId,
26153
+ sessionTurnAttempts.sessionId
26154
+ ),
26155
+ eq7(sessionAttemptInterruptions.attemptId, sessionTurnAttempts.id)
26156
+ )
26157
+ ).where(
26158
+ and7(
26159
+ eq7(sessionTurnAttempts.workspaceId, input.workspaceId),
26160
+ eq7(sessionTurnAttempts.sessionId, input.sessionId),
26161
+ isNull2(sessionTurnAttempts.quiescedAt),
26162
+ inArray3(sessionAttemptInterruptions.state, ["settled", "rejected_stale"])
26163
+ )
26164
+ ).limit(1);
26165
+ if (pendingQuiescence) {
26166
+ return { action: "pending_admission", blocker: "pending_quiescence" };
26167
+ }
25628
26168
  }
25629
26169
  const [row] = await tx.update(sessionWorkflowWakeOutbox).set({
25630
26170
  deliveredRevision: sql4`greatest(${sessionWorkflowWakeOutbox.deliveredRevision}, ${input.wakeRevision})`,
@@ -25780,30 +26320,6 @@ async function addSessionSystemUpdate(db, input) {
25780
26320
  }
25781
26321
  return await addSessionSystemUpdateWithSourceMutation(db, input, async () => void 0);
25782
26322
  }
25783
- async function requeueInterruptedSessionSystemUpdatesForTurnTx(tx, workspaceId, sessionId, turnId) {
25784
- await tx.update(sessionSystemUpdates).set({ state: "pending", deliveredTurnId: null, deliveredAt: null }).where(
25785
- and7(
25786
- eq7(sessionSystemUpdates.workspaceId, workspaceId),
25787
- eq7(sessionSystemUpdates.sessionId, sessionId),
25788
- eq7(sessionSystemUpdates.deliveredTurnId, turnId),
25789
- eq7(sessionSystemUpdates.state, "delivered")
25790
- )
25791
- );
25792
- }
25793
- async function deferFailedSessionSystemUpdatesForTurnTx(tx, workspaceId, sessionId, turnId) {
25794
- await tx.update(sessionSystemUpdates).set({
25795
- state: sql4`case when ${sessionSystemUpdates.payload} ->> 'type' = 'goal_continuation' then 'failed' else 'deferred' end`,
25796
- deliveredTurnId: null,
25797
- deliveredAt: null
25798
- }).where(
25799
- and7(
25800
- eq7(sessionSystemUpdates.workspaceId, workspaceId),
25801
- eq7(sessionSystemUpdates.sessionId, sessionId),
25802
- eq7(sessionSystemUpdates.deliveredTurnId, turnId),
25803
- eq7(sessionSystemUpdates.state, "delivered")
25804
- )
25805
- );
25806
- }
25807
26323
  async function addSessionSystemUpdateWithSourceMutation(db, input, mutateSource) {
25808
26324
  if (Buffer.byteLength(JSON.stringify(input.payload)) > MAX_INTERNAL_UPDATE_BYTES) {
25809
26325
  throw new Error(`Internal update payload exceeds ${MAX_INTERNAL_UPDATE_BYTES} bytes`);
@@ -25882,6 +26398,7 @@ async function addSessionSystemUpdateWithSourceMutation(db, input, mutateSource)
25882
26398
  };
25883
26399
  }
25884
26400
  const now = /* @__PURE__ */ new Date();
26401
+ const eventPreview = internalUpdateEventMember(inserted);
25885
26402
  const [event] = await tx.insert(sessionEvents).values({
25886
26403
  accountId: session.accountId,
25887
26404
  workspaceId: input.workspaceId,
@@ -25889,11 +26406,13 @@ async function addSessionSystemUpdateWithSourceMutation(db, input, mutateSource)
25889
26406
  sequence: session.lastSequence + 1,
25890
26407
  type: "system.update.pending",
25891
26408
  payload: sanitizeEventPayload({
25892
- updateId: inserted.id,
25893
- kind: input.kind,
25894
- classification: input.classification,
25895
- sourceId: input.sourceId,
25896
- summary: input.summary
26409
+ updateId: eventPreview.id,
26410
+ kind: eventPreview.kind,
26411
+ classification: eventPreview.classification,
26412
+ sourceId: eventPreview.sourceId,
26413
+ sourceIdTruncated: eventPreview.sourceIdTruncated,
26414
+ summary: eventPreview.summary,
26415
+ summaryTruncated: eventPreview.summaryTruncated
25897
26416
  }),
25898
26417
  occurredAt: now
25899
26418
  }).returning();
@@ -25930,7 +26449,7 @@ async function listOutstandingSessionSystemUpdates(db, workspaceId, sessionId) {
25930
26449
  and7(
25931
26450
  eq7(sessionSystemUpdates.workspaceId, workspaceId),
25932
26451
  eq7(sessionSystemUpdates.sessionId, sessionId),
25933
- inArray3(sessionSystemUpdates.state, ["pending", "deferred"])
26452
+ eq7(sessionSystemUpdates.state, "pending")
25934
26453
  )
25935
26454
  ).orderBy(asc4(sessionSystemUpdates.createdAt), asc4(sessionSystemUpdates.id));
25936
26455
  return rows.map(mapSessionSystemUpdate);
@@ -25971,6 +26490,7 @@ function mapSessionSystemUpdate(row) {
25971
26490
  lineage: row.lineage,
25972
26491
  state: row.state,
25973
26492
  deliveredTurnId: row.deliveredTurnId,
26493
+ deliveredHistoryItemId: row.deliveredHistoryItemId,
25974
26494
  deliveredAt: row.deliveredAt?.toISOString() ?? null,
25975
26495
  createdAt: row.createdAt.toISOString()
25976
26496
  };
@@ -26571,6 +27091,7 @@ function mapSession(row, effectiveControl, mcpServers = [], pin = mapSessionPin(
26571
27091
  titleSource: row.titleSource ?? null,
26572
27092
  instructions: row.instructions ?? null,
26573
27093
  resources: row.resources,
27094
+ skills: row.skills ?? [],
26574
27095
  tools: row.tools,
26575
27096
  toolPolicy: row.toolPolicy ?? {
26576
27097
  mode: "legacy",
@@ -26600,6 +27121,7 @@ function mapSession(row, effectiveControl, mcpServers = [], pin = mapSessionPin(
26600
27121
  rigId: row.rigId ?? null,
26601
27122
  rigVersionId: row.rigVersionId ?? null,
26602
27123
  firstPartyMcpPermissions: row.firstPartyMcpPermissions ?? null,
27124
+ firstPartyMcpTools: row.firstPartyMcpTools ?? null,
26603
27125
  mcpServers,
26604
27126
  parentSessionId: row.parentSessionId ?? null,
26605
27127
  rootSessionId: row.rootSessionId,
@@ -27314,6 +27836,7 @@ export {
27314
27836
  SessionPinVersionConflictError,
27315
27837
  SessionSpawnDeniedDbError,
27316
27838
  SessionToolPolicyVersionConflictError,
27839
+ SlackBotLifecycleSuccessAuditError,
27317
27840
  WORKSPACE_MEMORY_BLOCK_EMPTY,
27318
27841
  WORKSPACE_MEMORY_BLOCK_HEADER_POPULATED,
27319
27842
  WORKSPACE_MEMORY_BLOCK_TOKEN_BUDGET,
@@ -27417,6 +27940,7 @@ export {
27417
27940
  countWorkspacesForAccount,
27418
27941
  createApiKey,
27419
27942
  createConnection,
27943
+ createConnectionWithSlackBotSuccessAudit,
27420
27944
  createDb,
27421
27945
  createDeviceEnrollmentRequest,
27422
27946
  createEnrollment,
@@ -27543,6 +28067,7 @@ export {
27543
28067
  getSandboxSessionEnvelope,
27544
28068
  getScheduledTask,
27545
28069
  getSession,
28070
+ getSessionAttemptActivityRef,
27546
28071
  getSessionByCreateIdempotencyKey,
27547
28072
  getSessionCodexState,
27548
28073
  getSessionEvent,
@@ -27714,6 +28239,7 @@ export {
27714
28239
  reapStaleLeaseHoldersGlobal,
27715
28240
  reconcileCodexCapacityWait,
27716
28241
  reconcileColdLostLeaseInstanceBlockers,
28242
+ reconcileSessionAttemptQuiescence,
27717
28243
  recordAuditEvent,
27718
28244
  recordCodexAccountConnectors,
27719
28245
  recordCodexAccountUsage,
@@ -27727,6 +28253,7 @@ export {
27727
28253
  recordRetainedProcessReconciliationProof,
27728
28254
  recordSessionActiveCodexCredential,
27729
28255
  recordSkippedContextCompaction,
28256
+ recordSlackBotInstallCallbackFailure,
27730
28257
  recordStreamAcknowledgment,
27731
28258
  recordStripeWebhookEvent,
27732
28259
  recordUsageEvent,
@@ -27765,6 +28292,7 @@ export {
27765
28292
  retireHostExportConsumer,
27766
28293
  revokeApiKey,
27767
28294
  revokeConnection,
28295
+ revokeConnectionWithSlackBotSuccessAudit,
27768
28296
  revokeEnrollment,
27769
28297
  revokeViewer,
27770
28298
  rewindHostExportConsumer,
@@ -27833,6 +28361,7 @@ export {
27833
28361
  updateCodexAllocatorEligibility,
27834
28362
  updateCodexRotationSettings,
27835
28363
  updateConnection,
28364
+ updateConnectionWithSlackBotSuccessAudit,
27836
28365
  updateImportBatchCounts,
27837
28366
  updateKnowledgeMemory,
27838
28367
  updatePackInstallationStatus,