@opengeni/db 0.13.4 → 0.14.3
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-IQOXTFKA.js → chunk-DW24CKCT.js} +22 -10
- package/dist/chunk-DW24CKCT.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +492 -145
- package/dist/index.js.map +1 -1
- package/dist/provision-roles.d.ts +70 -15
- package/dist/{schema-SbaSoRdr.d.ts → schema-DhkRhcuQ.d.ts} +52 -16
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +1 -1
- package/drizzle/0134_session_first_party_mcp_tools.sql +15 -0
- package/drizzle/0135_durable_machine_input_batches.sql +359 -0
- package/drizzle/0136_unified_session_tool_policy.sql +109 -0
- package/package.json +3 -3
- package/src/index.ts +642 -151
- package/src/schema.ts +28 -10
- package/src/session-queue-commands.ts +30 -54
- package/dist/chunk-IQOXTFKA.js.map +0 -1
package/src/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
FileAsset,
|
|
17
17
|
FileStatus,
|
|
18
18
|
FileUploadStatus,
|
|
19
|
+
FirstPartyMcpToolName,
|
|
19
20
|
HumanInputAnswer,
|
|
20
21
|
HumanInputQuestion,
|
|
21
22
|
HumanInputResponse,
|
|
@@ -94,7 +95,10 @@ import type {
|
|
|
94
95
|
RigChangeStatus,
|
|
95
96
|
RigCheck,
|
|
96
97
|
} from "@opengeni/contracts";
|
|
97
|
-
import {
|
|
98
|
+
import {
|
|
99
|
+
DEFAULT_FIRST_PARTY_MCP_TOOLS,
|
|
100
|
+
SESSION_AUTHORIZATION_LIST_SCOPE_MAX_IDS,
|
|
101
|
+
} from "@opengeni/contracts";
|
|
98
102
|
import {
|
|
99
103
|
approvalIdentifier,
|
|
100
104
|
boundWorkspaceControlEvent,
|
|
@@ -113,6 +117,7 @@ import {
|
|
|
113
117
|
RigChange as RigChangeContract,
|
|
114
118
|
SessionGoal as SessionGoalContract,
|
|
115
119
|
SessionSystemUpdatePayload,
|
|
120
|
+
sessionSystemUpdateBatchHistoryItem,
|
|
116
121
|
HostEventExport as HostEventExportContract,
|
|
117
122
|
HostEventExportBatch as HostEventExportBatchContract,
|
|
118
123
|
HostExportConsumerId,
|
|
@@ -13877,7 +13882,7 @@ export type SessionCreateInput = {
|
|
|
13877
13882
|
resources: ResourceRef[];
|
|
13878
13883
|
skills?: SessionSkill[];
|
|
13879
13884
|
tools?: ToolRef[];
|
|
13880
|
-
toolPolicy?: SessionToolPolicy
|
|
13885
|
+
toolPolicy?: SessionToolPolicy;
|
|
13881
13886
|
metadata: Record<string, unknown>;
|
|
13882
13887
|
createdBy?: TurnInitiator;
|
|
13883
13888
|
createdByContext?: TurnInitiatorContext;
|
|
@@ -13888,6 +13893,7 @@ export type SessionCreateInput = {
|
|
|
13888
13893
|
rigId?: string | null;
|
|
13889
13894
|
rigVersionId?: string | null;
|
|
13890
13895
|
firstPartyMcpPermissions?: Permission[] | null;
|
|
13896
|
+
firstPartyMcpTools?: FirstPartyMcpToolName[];
|
|
13891
13897
|
instructions?: string | null;
|
|
13892
13898
|
parentSessionId?: string | null;
|
|
13893
13899
|
createIdempotencyKey?: string | null;
|
|
@@ -14277,7 +14283,10 @@ async function createSessionInTransaction(
|
|
|
14277
14283
|
resources: input.resources,
|
|
14278
14284
|
skills: input.skills ?? [],
|
|
14279
14285
|
tools: input.tools ?? [],
|
|
14280
|
-
toolPolicy: input.toolPolicy ??
|
|
14286
|
+
toolPolicy: input.toolPolicy ?? {
|
|
14287
|
+
mode: "explicit",
|
|
14288
|
+
inheritedFromSessionId: input.parentSessionId ?? null,
|
|
14289
|
+
},
|
|
14281
14290
|
metadata: input.metadata,
|
|
14282
14291
|
...creatorColumns(frozenCreator),
|
|
14283
14292
|
model: input.model,
|
|
@@ -14288,6 +14297,7 @@ async function createSessionInTransaction(
|
|
|
14288
14297
|
rigId: input.rigId ?? null,
|
|
14289
14298
|
rigVersionId: input.rigVersionId ?? null,
|
|
14290
14299
|
firstPartyMcpPermissions: input.firstPartyMcpPermissions ?? null,
|
|
14300
|
+
firstPartyMcpTools: input.firstPartyMcpTools ?? [...DEFAULT_FIRST_PARTY_MCP_TOOLS],
|
|
14291
14301
|
instructions: input.instructions ?? null,
|
|
14292
14302
|
parentSessionId: input.parentSessionId ?? null,
|
|
14293
14303
|
createIdempotencyKey,
|
|
@@ -25343,6 +25353,89 @@ export async function latestWorkspaceCapture(
|
|
|
25343
25353
|
});
|
|
25344
25354
|
}
|
|
25345
25355
|
|
|
25356
|
+
export type SessionWorkspaceCaptureLookup = {
|
|
25357
|
+
sessionExists: boolean;
|
|
25358
|
+
capture: WorkspaceCaptureRow | null;
|
|
25359
|
+
};
|
|
25360
|
+
|
|
25361
|
+
/**
|
|
25362
|
+
* Resolve session existence and its newest capture in one RLS-scoped query.
|
|
25363
|
+
*
|
|
25364
|
+
* The capture metadata endpoint only needs existence for its 404 contract. Using
|
|
25365
|
+
* `getSession` there mapped the complete session, MCP metadata, and control
|
|
25366
|
+
* projection before issuing a second transaction for the capture. A lateral
|
|
25367
|
+
* lookup preserves the exact absent-session / absent-capture distinction without
|
|
25368
|
+
* loading unrelated session state or adding another database round trip.
|
|
25369
|
+
*/
|
|
25370
|
+
export async function sessionLatestWorkspaceCapture(
|
|
25371
|
+
db: Database,
|
|
25372
|
+
workspaceId: string,
|
|
25373
|
+
sessionId: string,
|
|
25374
|
+
): Promise<SessionWorkspaceCaptureLookup> {
|
|
25375
|
+
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
25376
|
+
const rows = await scopedDb.execute<{
|
|
25377
|
+
found_session_id: string;
|
|
25378
|
+
capture_id: string | null;
|
|
25379
|
+
capture_session_id: string | null;
|
|
25380
|
+
capture_turn_id: string | null;
|
|
25381
|
+
capture_revision: number | string | null;
|
|
25382
|
+
capture_lease_epoch: number | string | null;
|
|
25383
|
+
capture_state: string | null;
|
|
25384
|
+
capture_manifest_key: string | null;
|
|
25385
|
+
capture_tree_index_key: string | null;
|
|
25386
|
+
capture_blob_keys: unknown;
|
|
25387
|
+
capture_size_bytes: number | string | null;
|
|
25388
|
+
capture_stats: unknown;
|
|
25389
|
+
capture_captured_at: string | Date | null;
|
|
25390
|
+
}>(sql`
|
|
25391
|
+
select
|
|
25392
|
+
sessions.id as found_session_id,
|
|
25393
|
+
capture.id as capture_id,
|
|
25394
|
+
capture.session_id as capture_session_id,
|
|
25395
|
+
capture.turn_id as capture_turn_id,
|
|
25396
|
+
capture.revision as capture_revision,
|
|
25397
|
+
capture.lease_epoch as capture_lease_epoch,
|
|
25398
|
+
capture.state as capture_state,
|
|
25399
|
+
capture.manifest_key as capture_manifest_key,
|
|
25400
|
+
capture.tree_index_key as capture_tree_index_key,
|
|
25401
|
+
capture.blob_keys as capture_blob_keys,
|
|
25402
|
+
capture.size_bytes as capture_size_bytes,
|
|
25403
|
+
capture.stats as capture_stats,
|
|
25404
|
+
capture.captured_at as capture_captured_at
|
|
25405
|
+
from sessions
|
|
25406
|
+
left join lateral (
|
|
25407
|
+
select ${WORKSPACE_CAPTURE_COLUMNS}
|
|
25408
|
+
from workspace_captures
|
|
25409
|
+
where workspace_captures.session_id = sessions.id
|
|
25410
|
+
order by workspace_captures.revision desc
|
|
25411
|
+
limit 1
|
|
25412
|
+
) capture on true
|
|
25413
|
+
where sessions.workspace_id = ${workspaceId} and sessions.id = ${sessionId}
|
|
25414
|
+
limit 1
|
|
25415
|
+
`);
|
|
25416
|
+
const row = rows[0];
|
|
25417
|
+
if (!row) return { sessionExists: false, capture: null };
|
|
25418
|
+
if (!row.capture_id) return { sessionExists: true, capture: null };
|
|
25419
|
+
return {
|
|
25420
|
+
sessionExists: true,
|
|
25421
|
+
capture: mapWorkspaceCaptureRow({
|
|
25422
|
+
id: row.capture_id,
|
|
25423
|
+
session_id: row.capture_session_id!,
|
|
25424
|
+
turn_id: row.capture_turn_id,
|
|
25425
|
+
revision: row.capture_revision!,
|
|
25426
|
+
lease_epoch: row.capture_lease_epoch!,
|
|
25427
|
+
state: row.capture_state!,
|
|
25428
|
+
manifest_key: row.capture_manifest_key,
|
|
25429
|
+
tree_index_key: row.capture_tree_index_key,
|
|
25430
|
+
blob_keys: row.capture_blob_keys,
|
|
25431
|
+
size_bytes: row.capture_size_bytes,
|
|
25432
|
+
stats: row.capture_stats,
|
|
25433
|
+
captured_at: row.capture_captured_at!,
|
|
25434
|
+
}),
|
|
25435
|
+
};
|
|
25436
|
+
});
|
|
25437
|
+
}
|
|
25438
|
+
|
|
25346
25439
|
/** A specific capture revision for a session (the M2 file route with an explicit
|
|
25347
25440
|
* `?revision=`), or null if that revision was never captured / already GC'd. */
|
|
25348
25441
|
export async function workspaceCaptureAtRevision(
|
|
@@ -28985,7 +29078,7 @@ export async function materializeGoalContinuation(
|
|
|
28985
29078
|
eq(schema.sessionSystemUpdates.workspaceId, input.workspaceId),
|
|
28986
29079
|
eq(schema.sessionSystemUpdates.sessionId, input.sessionId),
|
|
28987
29080
|
eq(schema.sessionSystemUpdates.kind, "agent_steer_instruction"),
|
|
28988
|
-
|
|
29081
|
+
eq(schema.sessionSystemUpdates.state, "pending"),
|
|
28989
29082
|
),
|
|
28990
29083
|
)
|
|
28991
29084
|
.limit(1);
|
|
@@ -29148,6 +29241,7 @@ export async function materializeGoalContinuation(
|
|
|
29148
29241
|
})
|
|
29149
29242
|
.onConflictDoNothing({ target: schema.usageEvents.idempotencyKey });
|
|
29150
29243
|
|
|
29244
|
+
const eventPreview = internalUpdateEventMember(update);
|
|
29151
29245
|
const insertedEvents = await tx
|
|
29152
29246
|
.insert(schema.sessionEvents)
|
|
29153
29247
|
.values([
|
|
@@ -29158,11 +29252,13 @@ export async function materializeGoalContinuation(
|
|
|
29158
29252
|
sequence: session.lastSequence + 1,
|
|
29159
29253
|
type: "system.update.pending",
|
|
29160
29254
|
payload: sanitizeEventPayload({
|
|
29161
|
-
updateId:
|
|
29162
|
-
kind:
|
|
29163
|
-
classification:
|
|
29164
|
-
sourceId:
|
|
29165
|
-
|
|
29255
|
+
updateId: eventPreview.id,
|
|
29256
|
+
kind: eventPreview.kind,
|
|
29257
|
+
classification: eventPreview.classification,
|
|
29258
|
+
sourceId: eventPreview.sourceId,
|
|
29259
|
+
sourceIdTruncated: eventPreview.sourceIdTruncated,
|
|
29260
|
+
summary: eventPreview.summary,
|
|
29261
|
+
summaryTruncated: eventPreview.summaryTruncated,
|
|
29166
29262
|
}),
|
|
29167
29263
|
occurredAt: now,
|
|
29168
29264
|
},
|
|
@@ -29663,6 +29759,79 @@ export type SessionWorkTrigger = { kind: "next" } | { kind: "approval"; triggerE
|
|
|
29663
29759
|
export const MAX_INTERNAL_UPDATE_BYTES = 64 * 1024;
|
|
29664
29760
|
export const MAX_INTERNAL_UPDATE_BATCH_MEMBERS = 100;
|
|
29665
29761
|
export const MAX_INTERNAL_UPDATE_BATCH_BYTES = 256 * 1024;
|
|
29762
|
+
const MAX_INTERNAL_UPDATE_EVENT_SUMMARY_BYTES = 512;
|
|
29763
|
+
const MAX_INTERNAL_UPDATE_EVENT_SOURCE_BYTES = 256;
|
|
29764
|
+
|
|
29765
|
+
type BoundedSystemUpdate = Pick<
|
|
29766
|
+
typeof schema.sessionSystemUpdates.$inferSelect,
|
|
29767
|
+
"id" | "kind" | "classification" | "sourceId" | "summary" | "payload" | "lineage"
|
|
29768
|
+
>;
|
|
29769
|
+
|
|
29770
|
+
function boundedInternalUpdateEventText(
|
|
29771
|
+
value: string,
|
|
29772
|
+
maxBytes: number,
|
|
29773
|
+
): {
|
|
29774
|
+
text: string;
|
|
29775
|
+
truncated: boolean;
|
|
29776
|
+
} {
|
|
29777
|
+
if (Buffer.byteLength(value) <= maxBytes) return { text: value, truncated: false };
|
|
29778
|
+
const suffix = "…";
|
|
29779
|
+
const bodyBudget = maxBytes - Buffer.byteLength(suffix);
|
|
29780
|
+
const bytes = Buffer.from(value);
|
|
29781
|
+
let text = bytes.subarray(0, bodyBudget).toString("utf8");
|
|
29782
|
+
if (text.endsWith("\uFFFD")) text = text.slice(0, -1);
|
|
29783
|
+
return { text: `${text}${suffix}`, truncated: true };
|
|
29784
|
+
}
|
|
29785
|
+
|
|
29786
|
+
function internalUpdateEventMember(update: BoundedSystemUpdate) {
|
|
29787
|
+
const summary = boundedInternalUpdateEventText(
|
|
29788
|
+
update.summary,
|
|
29789
|
+
MAX_INTERNAL_UPDATE_EVENT_SUMMARY_BYTES,
|
|
29790
|
+
);
|
|
29791
|
+
const source = boundedInternalUpdateEventText(
|
|
29792
|
+
update.sourceId,
|
|
29793
|
+
MAX_INTERNAL_UPDATE_EVENT_SOURCE_BYTES,
|
|
29794
|
+
);
|
|
29795
|
+
return {
|
|
29796
|
+
id: update.id,
|
|
29797
|
+
kind: update.kind,
|
|
29798
|
+
classification: update.classification,
|
|
29799
|
+
sourceId: source.text,
|
|
29800
|
+
sourceIdTruncated: source.truncated,
|
|
29801
|
+
summary: summary.text,
|
|
29802
|
+
summaryTruncated: summary.truncated,
|
|
29803
|
+
};
|
|
29804
|
+
}
|
|
29805
|
+
|
|
29806
|
+
function selectBoundedSystemUpdateBatch<T extends BoundedSystemUpdate>(updates: readonly T[]): T[] {
|
|
29807
|
+
const selected: T[] = [];
|
|
29808
|
+
let selectedBytes = 0;
|
|
29809
|
+
for (const update of updates) {
|
|
29810
|
+
const updateBytes = Buffer.byteLength(
|
|
29811
|
+
JSON.stringify({
|
|
29812
|
+
id: update.id,
|
|
29813
|
+
kind: update.kind,
|
|
29814
|
+
classification: update.classification,
|
|
29815
|
+
sourceId: update.sourceId,
|
|
29816
|
+
summary: update.summary,
|
|
29817
|
+
payload: update.payload,
|
|
29818
|
+
lineage: update.lineage,
|
|
29819
|
+
}),
|
|
29820
|
+
);
|
|
29821
|
+
if (
|
|
29822
|
+
selected.length >= MAX_INTERNAL_UPDATE_BATCH_MEMBERS ||
|
|
29823
|
+
// One individually large canonical input must still make progress. The
|
|
29824
|
+
// model/context boundary may reject it explicitly, but the queue cannot
|
|
29825
|
+
// wedge forever merely because the coalescing target is smaller.
|
|
29826
|
+
(selected.length > 0 && selectedBytes + updateBytes > MAX_INTERNAL_UPDATE_BATCH_BYTES)
|
|
29827
|
+
) {
|
|
29828
|
+
break;
|
|
29829
|
+
}
|
|
29830
|
+
selected.push(update);
|
|
29831
|
+
selectedBytes += updateBytes;
|
|
29832
|
+
}
|
|
29833
|
+
return selected;
|
|
29834
|
+
}
|
|
29666
29835
|
|
|
29667
29836
|
export type ClaimSessionWorkForAttemptInput = {
|
|
29668
29837
|
sessionId: string;
|
|
@@ -29708,7 +29877,10 @@ export async function claimSessionWorkForAttempt(
|
|
|
29708
29877
|
count: number;
|
|
29709
29878
|
lastSequence: number;
|
|
29710
29879
|
triggerEventId: string | null;
|
|
29880
|
+
historyItemId: string | null;
|
|
29881
|
+
historyItem: Record<string, unknown> | null;
|
|
29711
29882
|
updates: Array<typeof schema.sessionSystemUpdates.$inferSelect>;
|
|
29883
|
+
events: Array<typeof schema.sessionEvents.$inferInsert>;
|
|
29712
29884
|
event: typeof schema.sessionEvents.$inferInsert | null;
|
|
29713
29885
|
}> => {
|
|
29714
29886
|
const [agentSteer] = await tx
|
|
@@ -29718,7 +29890,7 @@ export async function claimSessionWorkForAttempt(
|
|
|
29718
29890
|
and(
|
|
29719
29891
|
eq(schema.sessionSystemUpdates.workspaceId, workspaceId),
|
|
29720
29892
|
eq(schema.sessionSystemUpdates.sessionId, sessionId),
|
|
29721
|
-
|
|
29893
|
+
eq(schema.sessionSystemUpdates.state, "pending"),
|
|
29722
29894
|
eq(schema.sessionSystemUpdates.kind, "agent_steer_instruction"),
|
|
29723
29895
|
),
|
|
29724
29896
|
)
|
|
@@ -29728,20 +29900,6 @@ export async function claimSessionWorkForAttempt(
|
|
|
29728
29900
|
)
|
|
29729
29901
|
.limit(1)
|
|
29730
29902
|
.for("update");
|
|
29731
|
-
if (agentSteer) {
|
|
29732
|
-
await tx
|
|
29733
|
-
.update(schema.sessionSystemUpdates)
|
|
29734
|
-
.set({ state: "superseded" })
|
|
29735
|
-
.where(
|
|
29736
|
-
and(
|
|
29737
|
-
eq(schema.sessionSystemUpdates.workspaceId, workspaceId),
|
|
29738
|
-
eq(schema.sessionSystemUpdates.sessionId, sessionId),
|
|
29739
|
-
eq(schema.sessionSystemUpdates.kind, "agent_steer_instruction"),
|
|
29740
|
-
inArray(schema.sessionSystemUpdates.state, ["pending", "deferred"]),
|
|
29741
|
-
ne(schema.sessionSystemUpdates.id, agentSteer.id),
|
|
29742
|
-
),
|
|
29743
|
-
);
|
|
29744
|
-
}
|
|
29745
29903
|
const ordinary = await tx
|
|
29746
29904
|
.select()
|
|
29747
29905
|
.from(schema.sessionSystemUpdates)
|
|
@@ -29749,7 +29907,7 @@ export async function claimSessionWorkForAttempt(
|
|
|
29749
29907
|
and(
|
|
29750
29908
|
eq(schema.sessionSystemUpdates.workspaceId, workspaceId),
|
|
29751
29909
|
eq(schema.sessionSystemUpdates.sessionId, sessionId),
|
|
29752
|
-
|
|
29910
|
+
eq(schema.sessionSystemUpdates.state, "pending"),
|
|
29753
29911
|
ne(schema.sessionSystemUpdates.kind, "agent_steer_instruction"),
|
|
29754
29912
|
),
|
|
29755
29913
|
)
|
|
@@ -29765,12 +29923,15 @@ export async function claimSessionWorkForAttempt(
|
|
|
29765
29923
|
count: 0,
|
|
29766
29924
|
lastSequence: nextSequence - 1,
|
|
29767
29925
|
triggerEventId: null,
|
|
29926
|
+
historyItemId: null,
|
|
29927
|
+
historyItem: null,
|
|
29768
29928
|
updates: [],
|
|
29929
|
+
events: [],
|
|
29769
29930
|
event: null,
|
|
29770
29931
|
};
|
|
29771
29932
|
}
|
|
29772
|
-
const
|
|
29773
|
-
|
|
29933
|
+
const validUpdates: typeof updates = [];
|
|
29934
|
+
const cancelledUpdateIds: string[] = [];
|
|
29774
29935
|
for (const update of updates) {
|
|
29775
29936
|
const payload = update.payload;
|
|
29776
29937
|
if (payload.type === "goal_continuation") {
|
|
@@ -29799,43 +29960,66 @@ export async function claimSessionWorkForAttempt(
|
|
|
29799
29960
|
.update(schema.sessionSystemUpdates)
|
|
29800
29961
|
.set({ state: "cancelled" })
|
|
29801
29962
|
.where(eq(schema.sessionSystemUpdates.id, update.id));
|
|
29963
|
+
cancelledUpdateIds.push(update.id);
|
|
29802
29964
|
continue;
|
|
29803
29965
|
}
|
|
29804
29966
|
}
|
|
29805
|
-
|
|
29806
|
-
JSON.stringify({
|
|
29807
|
-
id: update.id,
|
|
29808
|
-
kind: update.kind,
|
|
29809
|
-
classification: update.classification,
|
|
29810
|
-
sourceId: update.sourceId,
|
|
29811
|
-
summary: update.summary,
|
|
29812
|
-
payload: update.payload,
|
|
29813
|
-
lineage: update.lineage,
|
|
29814
|
-
}),
|
|
29815
|
-
);
|
|
29816
|
-
if (
|
|
29817
|
-
deliverable.length >= MAX_INTERNAL_UPDATE_BATCH_MEMBERS ||
|
|
29818
|
-
deliveredBytes + updateBytes > MAX_INTERNAL_UPDATE_BATCH_BYTES
|
|
29819
|
-
) {
|
|
29820
|
-
break;
|
|
29821
|
-
}
|
|
29822
|
-
deliverable.push(update);
|
|
29823
|
-
deliveredBytes += updateBytes;
|
|
29967
|
+
validUpdates.push(update);
|
|
29824
29968
|
}
|
|
29969
|
+
const deliverable = selectBoundedSystemUpdateBatch(validUpdates);
|
|
29825
29970
|
if (deliverable.length === 0) {
|
|
29971
|
+
const cancellationEvent =
|
|
29972
|
+
cancelledUpdateIds.length > 0
|
|
29973
|
+
? {
|
|
29974
|
+
accountId,
|
|
29975
|
+
workspaceId,
|
|
29976
|
+
sessionId,
|
|
29977
|
+
// No receiving turn exists when every candidate was
|
|
29978
|
+
// cancelled before a model batch could be persisted.
|
|
29979
|
+
turnId: null,
|
|
29980
|
+
turnGeneration: null,
|
|
29981
|
+
turnAttemptId: null,
|
|
29982
|
+
turnAssociation: null,
|
|
29983
|
+
sequence: nextSequence,
|
|
29984
|
+
type: "system.update.cancelled" as const,
|
|
29985
|
+
payload: sanitizeEventPayload({
|
|
29986
|
+
updateIds: cancelledUpdateIds,
|
|
29987
|
+
count: cancelledUpdateIds.length,
|
|
29988
|
+
reason: "stale_goal_continuation",
|
|
29989
|
+
}),
|
|
29990
|
+
occurredAt,
|
|
29991
|
+
}
|
|
29992
|
+
: null;
|
|
29826
29993
|
return {
|
|
29827
29994
|
count: 0,
|
|
29828
|
-
lastSequence: nextSequence - 1,
|
|
29995
|
+
lastSequence: cancellationEvent ? nextSequence : nextSequence - 1,
|
|
29829
29996
|
triggerEventId: null,
|
|
29997
|
+
historyItemId: null,
|
|
29998
|
+
historyItem: null,
|
|
29830
29999
|
updates: [],
|
|
30000
|
+
events: cancellationEvent ? [cancellationEvent] : [],
|
|
29831
30001
|
event: null,
|
|
29832
30002
|
};
|
|
29833
30003
|
}
|
|
30004
|
+
// Inclusion gives the newest Steer first refusal on the bounded
|
|
30005
|
+
// batch. Model ordering is deliberately the opposite: ordinary
|
|
30006
|
+
// updates establish context, then the authoritative replacement
|
|
30007
|
+
// direction is last so it cannot be overridden by an older goal or
|
|
30008
|
+
// lifecycle notice.
|
|
30009
|
+
const modelOrdered = [
|
|
30010
|
+
...deliverable.filter((update) => update.kind !== "agent_steer_instruction"),
|
|
30011
|
+
...deliverable.filter((update) => update.kind === "agent_steer_instruction"),
|
|
30012
|
+
];
|
|
30013
|
+
const historyItemId = crypto.randomUUID();
|
|
30014
|
+
const historyItem = sessionSystemUpdateBatchHistoryItem(
|
|
30015
|
+
modelOrdered.map((update) => mapSessionSystemUpdate(update)),
|
|
30016
|
+
) as Record<string, unknown>;
|
|
29834
30017
|
await tx
|
|
29835
30018
|
.update(schema.sessionSystemUpdates)
|
|
29836
30019
|
.set({
|
|
29837
30020
|
state: "delivered",
|
|
29838
30021
|
deliveredTurnId: turnId,
|
|
30022
|
+
deliveredHistoryItemId: historyItemId,
|
|
29839
30023
|
deliveredAt: occurredAt,
|
|
29840
30024
|
})
|
|
29841
30025
|
.where(
|
|
@@ -29849,6 +30033,27 @@ export async function claimSessionWorkForAttempt(
|
|
|
29849
30033
|
),
|
|
29850
30034
|
);
|
|
29851
30035
|
const eventId = triggerEventId ?? crypto.randomUUID();
|
|
30036
|
+
let sequence = nextSequence - 1;
|
|
30037
|
+
const events: Array<typeof schema.sessionEvents.$inferInsert> = [];
|
|
30038
|
+
if (cancelledUpdateIds.length > 0) {
|
|
30039
|
+
events.push({
|
|
30040
|
+
accountId,
|
|
30041
|
+
workspaceId,
|
|
30042
|
+
sessionId,
|
|
30043
|
+
turnId,
|
|
30044
|
+
turnGeneration,
|
|
30045
|
+
turnAttemptId: input.attemptId,
|
|
30046
|
+
turnAssociation: "current",
|
|
30047
|
+
sequence: ++sequence,
|
|
30048
|
+
type: "system.update.cancelled",
|
|
30049
|
+
payload: sanitizeEventPayload({
|
|
30050
|
+
updateIds: cancelledUpdateIds,
|
|
30051
|
+
count: cancelledUpdateIds.length,
|
|
30052
|
+
reason: "stale_goal_continuation",
|
|
30053
|
+
}),
|
|
30054
|
+
occurredAt,
|
|
30055
|
+
});
|
|
30056
|
+
}
|
|
29852
30057
|
const event: typeof schema.sessionEvents.$inferInsert = {
|
|
29853
30058
|
id: eventId,
|
|
29854
30059
|
accountId,
|
|
@@ -29858,24 +30063,64 @@ export async function claimSessionWorkForAttempt(
|
|
|
29858
30063
|
turnGeneration,
|
|
29859
30064
|
turnAttemptId: input.attemptId,
|
|
29860
30065
|
turnAssociation: "current",
|
|
29861
|
-
sequence:
|
|
30066
|
+
sequence: ++sequence,
|
|
29862
30067
|
type: "system.update.delivered",
|
|
29863
30068
|
payload: sanitizeEventPayload({
|
|
29864
30069
|
updateIds: deliverable.map((update) => update.id),
|
|
30070
|
+
historyItemId,
|
|
29865
30071
|
count: deliverable.length,
|
|
29866
30072
|
classifications: [...new Set(deliverable.map((update) => update.classification))],
|
|
30073
|
+
members: modelOrdered.map(internalUpdateEventMember),
|
|
29867
30074
|
}),
|
|
29868
30075
|
occurredAt,
|
|
29869
30076
|
};
|
|
30077
|
+
events.push(event);
|
|
29870
30078
|
return {
|
|
29871
30079
|
count: deliverable.length,
|
|
29872
|
-
lastSequence:
|
|
30080
|
+
lastSequence: sequence,
|
|
29873
30081
|
triggerEventId: eventId,
|
|
30082
|
+
historyItemId,
|
|
30083
|
+
historyItem,
|
|
29874
30084
|
updates: deliverable,
|
|
30085
|
+
events,
|
|
29875
30086
|
event,
|
|
29876
30087
|
};
|
|
29877
30088
|
};
|
|
29878
30089
|
|
|
30090
|
+
const persistDeliveredUpdateBatch = async (
|
|
30091
|
+
delivered: Awaited<ReturnType<typeof deliverPendingUpdates>>,
|
|
30092
|
+
accountId: string,
|
|
30093
|
+
turnId: string,
|
|
30094
|
+
): Promise<void> => {
|
|
30095
|
+
if (!delivered.historyItemId || !delivered.historyItem) {
|
|
30096
|
+
if (delivered.count !== 0) {
|
|
30097
|
+
throw new Error("Delivered machine-input batch has no model-memory item");
|
|
30098
|
+
}
|
|
30099
|
+
return;
|
|
30100
|
+
}
|
|
30101
|
+
const [{ position } = { position: 0 }] = await tx
|
|
30102
|
+
.select({
|
|
30103
|
+
position: sql<number>`coalesce(max(${schema.sessionHistoryItems.position}), -1) + 1`,
|
|
30104
|
+
})
|
|
30105
|
+
.from(schema.sessionHistoryItems)
|
|
30106
|
+
.where(
|
|
30107
|
+
and(
|
|
30108
|
+
eq(schema.sessionHistoryItems.workspaceId, workspaceId),
|
|
30109
|
+
eq(schema.sessionHistoryItems.sessionId, sessionId),
|
|
30110
|
+
),
|
|
30111
|
+
);
|
|
30112
|
+
await tx.insert(schema.sessionHistoryItems).values({
|
|
30113
|
+
id: delivered.historyItemId,
|
|
30114
|
+
accountId,
|
|
30115
|
+
workspaceId,
|
|
30116
|
+
sessionId,
|
|
30117
|
+
turnId,
|
|
30118
|
+
position: Number(position),
|
|
30119
|
+
item: sanitizeModelPayload(delivered.historyItem),
|
|
30120
|
+
producerCodexCredentialId: null,
|
|
30121
|
+
});
|
|
30122
|
+
};
|
|
30123
|
+
|
|
29879
30124
|
// Capacity settlement and resume use session -> turn after their
|
|
29880
30125
|
// workspace rotation lock. Claiming must preserve that shared order:
|
|
29881
30126
|
// taking a queued turn first can deadlock with a settlement that owns
|
|
@@ -30211,7 +30456,7 @@ export async function claimSessionWorkForAttempt(
|
|
|
30211
30456
|
eq(schema.sessionSystemUpdates.workspaceId, workspaceId),
|
|
30212
30457
|
eq(schema.sessionSystemUpdates.sessionId, sessionId),
|
|
30213
30458
|
eq(schema.sessionSystemUpdates.kind, "agent_steer_instruction"),
|
|
30214
|
-
|
|
30459
|
+
eq(schema.sessionSystemUpdates.state, "pending"),
|
|
30215
30460
|
),
|
|
30216
30461
|
)
|
|
30217
30462
|
.orderBy(
|
|
@@ -30448,6 +30693,22 @@ export async function claimSessionWorkForAttempt(
|
|
|
30448
30693
|
triggerEventId,
|
|
30449
30694
|
);
|
|
30450
30695
|
if (delivered.count === 0) {
|
|
30696
|
+
if (delivered.events.length > 0) {
|
|
30697
|
+
await tx.insert(schema.sessionEvents).values(delivered.events);
|
|
30698
|
+
await tx
|
|
30699
|
+
.update(schema.sessions)
|
|
30700
|
+
.set({
|
|
30701
|
+
status: "idle",
|
|
30702
|
+
lastSequence: delivered.lastSequence,
|
|
30703
|
+
updatedAt: now,
|
|
30704
|
+
})
|
|
30705
|
+
.where(
|
|
30706
|
+
and(
|
|
30707
|
+
eq(schema.sessions.workspaceId, workspaceId),
|
|
30708
|
+
eq(schema.sessions.id, sessionId),
|
|
30709
|
+
),
|
|
30710
|
+
);
|
|
30711
|
+
}
|
|
30451
30712
|
return { action: "unclaimed", reason: "no-work" };
|
|
30452
30713
|
}
|
|
30453
30714
|
const goalUpdate = delivered.updates.find(
|
|
@@ -30634,9 +30895,10 @@ export async function claimSessionWorkForAttempt(
|
|
|
30634
30895
|
})
|
|
30635
30896
|
.returning();
|
|
30636
30897
|
if (!internalTurn) throw new Error("Failed to create internal update inference");
|
|
30898
|
+
await persistDeliveredUpdateBatch(delivered, session.accountId, internalTurn.id);
|
|
30637
30899
|
await registerAttempt(internalTurn);
|
|
30638
30900
|
if (!delivered.event) throw new Error("Delivered update batch has no durable event");
|
|
30639
|
-
await tx.insert(schema.sessionEvents).values(delivered.
|
|
30901
|
+
await tx.insert(schema.sessionEvents).values(delivered.events);
|
|
30640
30902
|
if (goalUpdate && typeof goalUpdate.payload.goalId === "string") {
|
|
30641
30903
|
await tx
|
|
30642
30904
|
.update(schema.sessionGoals)
|
|
@@ -30753,8 +31015,9 @@ export async function claimSessionWorkForAttempt(
|
|
|
30753
31015
|
session.lastSequence + 1,
|
|
30754
31016
|
now,
|
|
30755
31017
|
);
|
|
30756
|
-
|
|
30757
|
-
|
|
31018
|
+
await persistDeliveredUpdateBatch(delivered, session.accountId, row.id);
|
|
31019
|
+
if (delivered.events.length > 0) {
|
|
31020
|
+
await tx.insert(schema.sessionEvents).values(delivered.events);
|
|
30758
31021
|
}
|
|
30759
31022
|
await tx
|
|
30760
31023
|
.update(schema.sessions)
|
|
@@ -30998,6 +31261,196 @@ export async function markSessionAttemptQuiesced(
|
|
|
30998
31261
|
});
|
|
30999
31262
|
}
|
|
31000
31263
|
|
|
31264
|
+
export type ReconcileSessionAttemptQuiescenceResult =
|
|
31265
|
+
| { action: "quiesced"; events: SessionEvent[] }
|
|
31266
|
+
| { action: "pending"; events: [] }
|
|
31267
|
+
| { action: "stale"; events: [] };
|
|
31268
|
+
|
|
31269
|
+
export type SessionAttemptActivityRef = {
|
|
31270
|
+
workflowId: string;
|
|
31271
|
+
workflowRunId: string;
|
|
31272
|
+
activityId: string;
|
|
31273
|
+
quiesced: boolean;
|
|
31274
|
+
};
|
|
31275
|
+
|
|
31276
|
+
export async function getSessionAttemptActivityRef(
|
|
31277
|
+
db: Database,
|
|
31278
|
+
input: {
|
|
31279
|
+
accountId: string;
|
|
31280
|
+
workspaceId: string;
|
|
31281
|
+
sessionId: string;
|
|
31282
|
+
attemptId: string;
|
|
31283
|
+
temporalWorkflowId: string;
|
|
31284
|
+
},
|
|
31285
|
+
): Promise<SessionAttemptActivityRef | null> {
|
|
31286
|
+
return await withRlsContext(
|
|
31287
|
+
db,
|
|
31288
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
31289
|
+
async (scopedDb) => {
|
|
31290
|
+
const [attempt] = await scopedDb
|
|
31291
|
+
.select({
|
|
31292
|
+
workflowId: schema.sessionTurnAttempts.temporalWorkflowId,
|
|
31293
|
+
workflowRunId: schema.sessionTurnAttempts.temporalWorkflowRunId,
|
|
31294
|
+
activityId: schema.sessionTurnAttempts.temporalActivityId,
|
|
31295
|
+
quiescedAt: schema.sessionTurnAttempts.quiescedAt,
|
|
31296
|
+
})
|
|
31297
|
+
.from(schema.sessionTurnAttempts)
|
|
31298
|
+
.where(
|
|
31299
|
+
and(
|
|
31300
|
+
eq(schema.sessionTurnAttempts.accountId, input.accountId),
|
|
31301
|
+
eq(schema.sessionTurnAttempts.workspaceId, input.workspaceId),
|
|
31302
|
+
eq(schema.sessionTurnAttempts.sessionId, input.sessionId),
|
|
31303
|
+
eq(schema.sessionTurnAttempts.id, input.attemptId),
|
|
31304
|
+
eq(schema.sessionTurnAttempts.temporalWorkflowId, input.temporalWorkflowId),
|
|
31305
|
+
),
|
|
31306
|
+
)
|
|
31307
|
+
.limit(1);
|
|
31308
|
+
return attempt
|
|
31309
|
+
? {
|
|
31310
|
+
workflowId: attempt.workflowId,
|
|
31311
|
+
workflowRunId: attempt.workflowRunId,
|
|
31312
|
+
activityId: attempt.activityId,
|
|
31313
|
+
quiesced: attempt.quiescedAt !== null,
|
|
31314
|
+
}
|
|
31315
|
+
: null;
|
|
31316
|
+
},
|
|
31317
|
+
);
|
|
31318
|
+
}
|
|
31319
|
+
|
|
31320
|
+
/**
|
|
31321
|
+
* Recover the quiescence receipt when the original activity disappeared after
|
|
31322
|
+
* its attempt was durably interrupted. The caller first proves through
|
|
31323
|
+
* Temporal that the exact activity is absent or its server-owned heartbeat
|
|
31324
|
+
* lease expired. The closed attempt then cannot admit another workspace writer,
|
|
31325
|
+
* and every writer it did admit (including retained-process child writes) must
|
|
31326
|
+
* carry a physical settlement before the ordinary receipt transaction is
|
|
31327
|
+
* allowed to run.
|
|
31328
|
+
*/
|
|
31329
|
+
export async function reconcileSessionAttemptQuiescence(
|
|
31330
|
+
db: Database,
|
|
31331
|
+
input: {
|
|
31332
|
+
accountId: string;
|
|
31333
|
+
workspaceId: string;
|
|
31334
|
+
sessionId: string;
|
|
31335
|
+
attemptId: string;
|
|
31336
|
+
temporalWorkflowId: string;
|
|
31337
|
+
temporalWorkflowRunId: string;
|
|
31338
|
+
temporalActivityId: string;
|
|
31339
|
+
activitySettled: boolean;
|
|
31340
|
+
},
|
|
31341
|
+
): Promise<ReconcileSessionAttemptQuiescenceResult> {
|
|
31342
|
+
const eligibility = await withRlsContext(
|
|
31343
|
+
db,
|
|
31344
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
31345
|
+
async (scopedDb) => {
|
|
31346
|
+
const rows = await scopedDb.execute<{
|
|
31347
|
+
account_id: string;
|
|
31348
|
+
state: string;
|
|
31349
|
+
quiesced_at: Date | string | null;
|
|
31350
|
+
temporal_workflow_id: string;
|
|
31351
|
+
temporal_workflow_run_id: string;
|
|
31352
|
+
temporal_activity_id: string;
|
|
31353
|
+
interruption_settled: boolean;
|
|
31354
|
+
interruption_pending: boolean;
|
|
31355
|
+
writer_pending: boolean;
|
|
31356
|
+
}>(sql`
|
|
31357
|
+
select
|
|
31358
|
+
attempt.account_id,
|
|
31359
|
+
attempt.state,
|
|
31360
|
+
attempt.quiesced_at,
|
|
31361
|
+
attempt.temporal_workflow_id,
|
|
31362
|
+
attempt.temporal_workflow_run_id,
|
|
31363
|
+
attempt.temporal_activity_id,
|
|
31364
|
+
exists (
|
|
31365
|
+
select 1
|
|
31366
|
+
from session_attempt_interruptions interruption
|
|
31367
|
+
where interruption.workspace_id = attempt.workspace_id
|
|
31368
|
+
and interruption.session_id = attempt.session_id
|
|
31369
|
+
and interruption.attempt_id = attempt.id
|
|
31370
|
+
and interruption.state in ('settled', 'rejected_stale')
|
|
31371
|
+
) as interruption_settled,
|
|
31372
|
+
exists (
|
|
31373
|
+
select 1
|
|
31374
|
+
from session_attempt_interruptions interruption
|
|
31375
|
+
where interruption.workspace_id = attempt.workspace_id
|
|
31376
|
+
and interruption.session_id = attempt.session_id
|
|
31377
|
+
and interruption.attempt_id = attempt.id
|
|
31378
|
+
and interruption.state in ('pending', 'delivered', 'acknowledged')
|
|
31379
|
+
) as interruption_pending,
|
|
31380
|
+
(
|
|
31381
|
+
exists (
|
|
31382
|
+
select 1
|
|
31383
|
+
from sandbox_workspace_mutation_admissions admission
|
|
31384
|
+
where admission.account_id = attempt.account_id
|
|
31385
|
+
and admission.workspace_id = attempt.workspace_id
|
|
31386
|
+
and admission.session_id = attempt.session_id
|
|
31387
|
+
and admission.settled_at is null
|
|
31388
|
+
and (
|
|
31389
|
+
admission.attempt_id = attempt.id
|
|
31390
|
+
or (
|
|
31391
|
+
admission.actor_kind = 'process'
|
|
31392
|
+
and exists (
|
|
31393
|
+
select 1
|
|
31394
|
+
from sandbox_retained_processes process
|
|
31395
|
+
where process.account_id = attempt.account_id
|
|
31396
|
+
and process.workspace_id = attempt.workspace_id
|
|
31397
|
+
and process.session_id = attempt.session_id
|
|
31398
|
+
and process.id = admission.actor_id
|
|
31399
|
+
and process.owner_attempt_id = attempt.id
|
|
31400
|
+
)
|
|
31401
|
+
)
|
|
31402
|
+
)
|
|
31403
|
+
)
|
|
31404
|
+
or exists (
|
|
31405
|
+
select 1
|
|
31406
|
+
from sandbox_retained_processes process
|
|
31407
|
+
where process.account_id = attempt.account_id
|
|
31408
|
+
and process.workspace_id = attempt.workspace_id
|
|
31409
|
+
and process.session_id = attempt.session_id
|
|
31410
|
+
and process.owner_attempt_id = attempt.id
|
|
31411
|
+
and process.state = 'active'
|
|
31412
|
+
)
|
|
31413
|
+
) as writer_pending
|
|
31414
|
+
from session_turn_attempts attempt
|
|
31415
|
+
where attempt.account_id = ${input.accountId}
|
|
31416
|
+
and attempt.workspace_id = ${input.workspaceId}
|
|
31417
|
+
and attempt.session_id = ${input.sessionId}
|
|
31418
|
+
and attempt.id = ${input.attemptId}
|
|
31419
|
+
limit 1
|
|
31420
|
+
`);
|
|
31421
|
+
return rows[0] ?? null;
|
|
31422
|
+
},
|
|
31423
|
+
);
|
|
31424
|
+
if (
|
|
31425
|
+
!eligibility ||
|
|
31426
|
+
eligibility.account_id !== input.accountId ||
|
|
31427
|
+
eligibility.temporal_workflow_id !== input.temporalWorkflowId ||
|
|
31428
|
+
eligibility.temporal_workflow_run_id !== input.temporalWorkflowRunId ||
|
|
31429
|
+
eligibility.temporal_activity_id !== input.temporalActivityId ||
|
|
31430
|
+
eligibility.state !== "closed" ||
|
|
31431
|
+
!eligibility.interruption_settled ||
|
|
31432
|
+
eligibility.interruption_pending
|
|
31433
|
+
) {
|
|
31434
|
+
return { action: "stale", events: [] };
|
|
31435
|
+
}
|
|
31436
|
+
if (eligibility.quiesced_at) {
|
|
31437
|
+
return { action: "quiesced", events: [] };
|
|
31438
|
+
}
|
|
31439
|
+
if (!input.activitySettled || eligibility.writer_pending) {
|
|
31440
|
+
return { action: "pending", events: [] };
|
|
31441
|
+
}
|
|
31442
|
+
const events = await markSessionAttemptQuiesced(db, {
|
|
31443
|
+
accountId: input.accountId,
|
|
31444
|
+
workspaceId: input.workspaceId,
|
|
31445
|
+
sessionId: input.sessionId,
|
|
31446
|
+
attemptId: input.attemptId,
|
|
31447
|
+
temporalWorkflowId: input.temporalWorkflowId,
|
|
31448
|
+
temporalWorkflowRunId: input.temporalWorkflowRunId,
|
|
31449
|
+
temporalActivityId: input.temporalActivityId,
|
|
31450
|
+
});
|
|
31451
|
+
return { action: "quiesced", events };
|
|
31452
|
+
}
|
|
31453
|
+
|
|
31001
31454
|
/**
|
|
31002
31455
|
* Settle every durable interruption cause for one exact first-class attempt.
|
|
31003
31456
|
* Steer wins the logical-turn fate when causes coexist; effective control after
|
|
@@ -31168,13 +31621,6 @@ export async function settleSessionAttemptInterruptions(
|
|
|
31168
31621
|
outcome,
|
|
31169
31622
|
closedAt: now,
|
|
31170
31623
|
});
|
|
31171
|
-
await requeueInterruptedSessionSystemUpdatesForTurnTx(
|
|
31172
|
-
tx as unknown as Database,
|
|
31173
|
-
workspaceId,
|
|
31174
|
-
sessionId,
|
|
31175
|
-
turn.id,
|
|
31176
|
-
);
|
|
31177
|
-
|
|
31178
31624
|
const eventValues: Array<typeof schema.sessionEvents.$inferInsert> = steer
|
|
31179
31625
|
? [
|
|
31180
31626
|
{
|
|
@@ -32459,10 +32905,45 @@ export async function applySessionTurnSettlement(
|
|
|
32459
32905
|
},
|
|
32460
32906
|
}),
|
|
32461
32907
|
);
|
|
32908
|
+
const settledMachineInputs = ["completed", "failed", "cancelled", "superseded"].includes(
|
|
32909
|
+
input.turnStatus,
|
|
32910
|
+
)
|
|
32911
|
+
? await tx
|
|
32912
|
+
.select({
|
|
32913
|
+
id: schema.sessionSystemUpdates.id,
|
|
32914
|
+
historyItemId: schema.sessionSystemUpdates.deliveredHistoryItemId,
|
|
32915
|
+
})
|
|
32916
|
+
.from(schema.sessionSystemUpdates)
|
|
32917
|
+
.where(
|
|
32918
|
+
and(
|
|
32919
|
+
eq(schema.sessionSystemUpdates.workspaceId, workspaceId),
|
|
32920
|
+
eq(schema.sessionSystemUpdates.sessionId, input.sessionId),
|
|
32921
|
+
eq(schema.sessionSystemUpdates.deliveredTurnId, input.turnId),
|
|
32922
|
+
eq(schema.sessionSystemUpdates.state, "delivered"),
|
|
32923
|
+
),
|
|
32924
|
+
)
|
|
32925
|
+
.orderBy(
|
|
32926
|
+
asc(schema.sessionSystemUpdates.createdAt),
|
|
32927
|
+
asc(schema.sessionSystemUpdates.id),
|
|
32928
|
+
)
|
|
32929
|
+
: [];
|
|
32930
|
+
const machineInputSettlementEvent: AppendEventInput | null =
|
|
32931
|
+
settledMachineInputs.length > 0
|
|
32932
|
+
? {
|
|
32933
|
+
type: "system.update.settled",
|
|
32934
|
+
payload: {
|
|
32935
|
+
updateIds: settledMachineInputs.map((update) => update.id),
|
|
32936
|
+
count: settledMachineInputs.length,
|
|
32937
|
+
historyItemId: settledMachineInputs[0]!.historyItemId,
|
|
32938
|
+
outcome: input.turnStatus,
|
|
32939
|
+
},
|
|
32940
|
+
}
|
|
32941
|
+
: null;
|
|
32462
32942
|
const settlementEvents = [
|
|
32463
32943
|
...(recordingEvent ? [recordingEvent] : []),
|
|
32464
32944
|
...(compactionRequestEvent ? [compactionRequestEvent] : []),
|
|
32465
32945
|
...terminalHumanInputEvents,
|
|
32946
|
+
...(machineInputSettlementEvent ? [machineInputSettlementEvent] : []),
|
|
32466
32947
|
...input.events,
|
|
32467
32948
|
];
|
|
32468
32949
|
const values = settlementEvents.map((event) => {
|
|
@@ -32545,21 +33026,6 @@ export async function applySessionTurnSettlement(
|
|
|
32545
33026
|
turn,
|
|
32546
33027
|
);
|
|
32547
33028
|
}
|
|
32548
|
-
if (input.turnStatus === "failed") {
|
|
32549
|
-
await deferFailedSessionSystemUpdatesForTurnTx(
|
|
32550
|
-
tx as unknown as Database,
|
|
32551
|
-
workspaceId,
|
|
32552
|
-
input.sessionId,
|
|
32553
|
-
input.turnId,
|
|
32554
|
-
);
|
|
32555
|
-
} else if (["cancelled", "superseded"].includes(input.turnStatus)) {
|
|
32556
|
-
await requeueInterruptedSessionSystemUpdatesForTurnTx(
|
|
32557
|
-
tx as unknown as Database,
|
|
32558
|
-
workspaceId,
|
|
32559
|
-
input.sessionId,
|
|
32560
|
-
input.turnId,
|
|
32561
|
-
);
|
|
32562
|
-
}
|
|
32563
33029
|
await tx
|
|
32564
33030
|
.update(schema.sessions)
|
|
32565
33031
|
.set({
|
|
@@ -32843,14 +33309,6 @@ export async function settleCodexCredentialLeaseLoss(
|
|
|
32843
33309
|
eq(schema.sessions.activeTurnId, input.turnId),
|
|
32844
33310
|
),
|
|
32845
33311
|
);
|
|
32846
|
-
if (!input.checkpointDurable) {
|
|
32847
|
-
await deferFailedSessionSystemUpdatesForTurnTx(
|
|
32848
|
-
tx as unknown as Database,
|
|
32849
|
-
input.workspaceId,
|
|
32850
|
-
input.sessionId,
|
|
32851
|
-
input.turnId,
|
|
32852
|
-
);
|
|
32853
|
-
}
|
|
32854
33312
|
await tx.execute(sql`
|
|
32855
33313
|
delete from codex_credential_leases
|
|
32856
33314
|
where account_id = ${input.accountId}
|
|
@@ -33794,11 +34252,34 @@ export async function getSessionQueueSnapshot(
|
|
|
33794
34252
|
),
|
|
33795
34253
|
)
|
|
33796
34254
|
.orderBy(asc(schema.sessionTurns.position), asc(schema.sessionTurns.createdAt));
|
|
34255
|
+
const pendingInputs = await scopedDb
|
|
34256
|
+
.select()
|
|
34257
|
+
.from(schema.sessionSystemUpdates)
|
|
34258
|
+
.where(
|
|
34259
|
+
and(
|
|
34260
|
+
eq(schema.sessionSystemUpdates.workspaceId, workspaceId),
|
|
34261
|
+
eq(schema.sessionSystemUpdates.sessionId, sessionId),
|
|
34262
|
+
eq(schema.sessionSystemUpdates.state, "pending"),
|
|
34263
|
+
),
|
|
34264
|
+
)
|
|
34265
|
+
.orderBy(
|
|
34266
|
+
sql`case when ${schema.sessionSystemUpdates.kind} = 'agent_steer_instruction' then 0 else 1 end`,
|
|
34267
|
+
asc(schema.sessionSystemUpdates.createdAt),
|
|
34268
|
+
asc(schema.sessionSystemUpdates.id),
|
|
34269
|
+
);
|
|
33797
34270
|
const latestInterruption = await latestSessionAttemptInterruption(
|
|
33798
34271
|
scopedDb,
|
|
33799
34272
|
workspaceId,
|
|
33800
34273
|
sessionId,
|
|
33801
34274
|
);
|
|
34275
|
+
const items = rows.map(mapSessionTurn);
|
|
34276
|
+
const nextInputBatch = selectBoundedSystemUpdateBatch(pendingInputs);
|
|
34277
|
+
const hasPendingAgentSteer = pendingInputs.some(
|
|
34278
|
+
(update) => update.kind === "agent_steer_instruction",
|
|
34279
|
+
);
|
|
34280
|
+
const attachmentTurn = hasPendingAgentSteer
|
|
34281
|
+
? items.find((turn) => turn.metadata.delivery === "steer")
|
|
34282
|
+
: items[0];
|
|
33802
34283
|
return {
|
|
33803
34284
|
version: session.queueVersion,
|
|
33804
34285
|
effectiveControl: serializeEffectiveSessionControl(effectiveControl),
|
|
@@ -33806,7 +34287,26 @@ export async function getSessionQueueSnapshot(
|
|
|
33806
34287
|
latestInterruption !== null &&
|
|
33807
34288
|
latestInterruption.interruptionState !== "rejected_stale" &&
|
|
33808
34289
|
latestInterruption.quiescedAt === null,
|
|
33809
|
-
items
|
|
34290
|
+
items,
|
|
34291
|
+
pendingInputs: pendingInputs.map((update) => {
|
|
34292
|
+
const canonical = mapSessionSystemUpdate(update);
|
|
34293
|
+
return {
|
|
34294
|
+
id: canonical.id,
|
|
34295
|
+
sessionId: canonical.sessionId,
|
|
34296
|
+
kind: canonical.kind,
|
|
34297
|
+
classification: canonical.classification,
|
|
34298
|
+
sourceId: boundedInternalUpdateEventText(canonical.sourceId, 256).text,
|
|
34299
|
+
summary: boundedInternalUpdateEventText(canonical.summary, 512).text,
|
|
34300
|
+
createdAt: canonical.createdAt,
|
|
34301
|
+
};
|
|
34302
|
+
}),
|
|
34303
|
+
pendingInputAttachment:
|
|
34304
|
+
attachmentTurn && nextInputBatch.length > 0
|
|
34305
|
+
? {
|
|
34306
|
+
turnId: attachmentTurn.id,
|
|
34307
|
+
inputIds: nextInputBatch.map((update) => update.id),
|
|
34308
|
+
}
|
|
34309
|
+
: null,
|
|
33810
34310
|
};
|
|
33811
34311
|
});
|
|
33812
34312
|
}
|
|
@@ -34155,12 +34655,13 @@ export async function claimPendingSessionWorkflowWakes(
|
|
|
34155
34655
|
|
|
34156
34656
|
/**
|
|
34157
34657
|
* Acknowledge an immediate post-commit signal only after it cannot strand an
|
|
34158
|
-
* accepted
|
|
34159
|
-
*
|
|
34160
|
-
*
|
|
34161
|
-
* the revision so the bounded outbox
|
|
34162
|
-
* attempt-fenced claim consumes
|
|
34163
|
-
*
|
|
34658
|
+
* accepted direction or an interrupted attempt awaiting writer-set proof.
|
|
34659
|
+
* Temporal accepting a signal is transport evidence, not proof that a closing
|
|
34660
|
+
* workflow observed Postgres. While active control still has an actionable
|
|
34661
|
+
* Agent Steer or pending quiescence, retain the revision so the bounded outbox
|
|
34662
|
+
* dispatcher retries signalWithStart. The attempt-fenced claim consumes an
|
|
34663
|
+
* Agent Steer once; a real Pause is the typed blocker and may acknowledge this
|
|
34664
|
+
* revision because Resume commits a new one.
|
|
34164
34665
|
*
|
|
34165
34666
|
* An older sender may advance only its own revision; it cannot clear a claim or
|
|
34166
34667
|
* failure state belonging to a newer revision. The control -> workspace ->
|
|
@@ -34169,7 +34670,10 @@ export async function claimPendingSessionWorkflowWakes(
|
|
|
34169
34670
|
*/
|
|
34170
34671
|
export type SessionWorkflowWakeDeliveryResult =
|
|
34171
34672
|
| { action: "acknowledged" }
|
|
34172
|
-
| {
|
|
34673
|
+
| {
|
|
34674
|
+
action: "pending_admission";
|
|
34675
|
+
blocker: "pending_agent_steer" | "pending_quiescence";
|
|
34676
|
+
};
|
|
34173
34677
|
|
|
34174
34678
|
export async function markSessionWorkflowWakeDelivered(
|
|
34175
34679
|
db: Database,
|
|
@@ -34209,6 +34713,35 @@ export async function markSessionWorkflowWakeDelivered(
|
|
|
34209
34713
|
if (pendingAgentSteer) {
|
|
34210
34714
|
return { action: "pending_admission", blocker: "pending_agent_steer" } as const;
|
|
34211
34715
|
}
|
|
34716
|
+
const [pendingQuiescence] = await tx
|
|
34717
|
+
.select({ id: schema.sessionTurnAttempts.id })
|
|
34718
|
+
.from(schema.sessionTurnAttempts)
|
|
34719
|
+
.innerJoin(
|
|
34720
|
+
schema.sessionAttemptInterruptions,
|
|
34721
|
+
and(
|
|
34722
|
+
eq(
|
|
34723
|
+
schema.sessionAttemptInterruptions.workspaceId,
|
|
34724
|
+
schema.sessionTurnAttempts.workspaceId,
|
|
34725
|
+
),
|
|
34726
|
+
eq(
|
|
34727
|
+
schema.sessionAttemptInterruptions.sessionId,
|
|
34728
|
+
schema.sessionTurnAttempts.sessionId,
|
|
34729
|
+
),
|
|
34730
|
+
eq(schema.sessionAttemptInterruptions.attemptId, schema.sessionTurnAttempts.id),
|
|
34731
|
+
),
|
|
34732
|
+
)
|
|
34733
|
+
.where(
|
|
34734
|
+
and(
|
|
34735
|
+
eq(schema.sessionTurnAttempts.workspaceId, input.workspaceId),
|
|
34736
|
+
eq(schema.sessionTurnAttempts.sessionId, input.sessionId),
|
|
34737
|
+
isNull(schema.sessionTurnAttempts.quiescedAt),
|
|
34738
|
+
inArray(schema.sessionAttemptInterruptions.state, ["settled", "rejected_stale"]),
|
|
34739
|
+
),
|
|
34740
|
+
)
|
|
34741
|
+
.limit(1);
|
|
34742
|
+
if (pendingQuiescence) {
|
|
34743
|
+
return { action: "pending_admission", blocker: "pending_quiescence" } as const;
|
|
34744
|
+
}
|
|
34212
34745
|
}
|
|
34213
34746
|
const [row] = await tx
|
|
34214
34747
|
.update(schema.sessionWorkflowWakeOutbox)
|
|
@@ -34443,55 +34976,6 @@ export async function addSessionSystemUpdate(
|
|
|
34443
34976
|
return await addSessionSystemUpdateWithSourceMutation(db, input, async () => undefined);
|
|
34444
34977
|
}
|
|
34445
34978
|
|
|
34446
|
-
async function requeueInterruptedSessionSystemUpdatesForTurnTx(
|
|
34447
|
-
tx: Database,
|
|
34448
|
-
workspaceId: string,
|
|
34449
|
-
sessionId: string,
|
|
34450
|
-
turnId: string,
|
|
34451
|
-
): Promise<void> {
|
|
34452
|
-
await tx
|
|
34453
|
-
.update(schema.sessionSystemUpdates)
|
|
34454
|
-
.set({ state: "pending", deliveredTurnId: null, deliveredAt: null })
|
|
34455
|
-
.where(
|
|
34456
|
-
and(
|
|
34457
|
-
eq(schema.sessionSystemUpdates.workspaceId, workspaceId),
|
|
34458
|
-
eq(schema.sessionSystemUpdates.sessionId, sessionId),
|
|
34459
|
-
eq(schema.sessionSystemUpdates.deliveredTurnId, turnId),
|
|
34460
|
-
eq(schema.sessionSystemUpdates.state, "delivered"),
|
|
34461
|
-
),
|
|
34462
|
-
);
|
|
34463
|
-
}
|
|
34464
|
-
|
|
34465
|
-
/**
|
|
34466
|
-
* A failed internal-only inference must not manufacture another inference by
|
|
34467
|
-
* making its inputs immediately runnable again. Preserve ordinary internal
|
|
34468
|
-
* updates as deferred input for the next real prompt/new update. Goal
|
|
34469
|
-
* continuation notices are derivable from the durable goal and become terminal
|
|
34470
|
-
* so the goal evaluator can pause or synthesize the next valid continuation.
|
|
34471
|
-
*/
|
|
34472
|
-
async function deferFailedSessionSystemUpdatesForTurnTx(
|
|
34473
|
-
tx: Database,
|
|
34474
|
-
workspaceId: string,
|
|
34475
|
-
sessionId: string,
|
|
34476
|
-
turnId: string,
|
|
34477
|
-
): Promise<void> {
|
|
34478
|
-
await tx
|
|
34479
|
-
.update(schema.sessionSystemUpdates)
|
|
34480
|
-
.set({
|
|
34481
|
-
state: sql`case when ${schema.sessionSystemUpdates.payload} ->> 'type' = 'goal_continuation' then 'failed' else 'deferred' end`,
|
|
34482
|
-
deliveredTurnId: null,
|
|
34483
|
-
deliveredAt: null,
|
|
34484
|
-
})
|
|
34485
|
-
.where(
|
|
34486
|
-
and(
|
|
34487
|
-
eq(schema.sessionSystemUpdates.workspaceId, workspaceId),
|
|
34488
|
-
eq(schema.sessionSystemUpdates.sessionId, sessionId),
|
|
34489
|
-
eq(schema.sessionSystemUpdates.deliveredTurnId, turnId),
|
|
34490
|
-
eq(schema.sessionSystemUpdates.state, "delivered"),
|
|
34491
|
-
),
|
|
34492
|
-
);
|
|
34493
|
-
}
|
|
34494
|
-
|
|
34495
34979
|
/**
|
|
34496
34980
|
* Persist one internal update without fabricating a user prompt or queue row.
|
|
34497
34981
|
* Dedupe and any producer/outbox mutation commit in the same transaction.
|
|
@@ -34593,6 +35077,7 @@ export async function addSessionSystemUpdateWithSourceMutation(
|
|
|
34593
35077
|
}
|
|
34594
35078
|
|
|
34595
35079
|
const now = new Date();
|
|
35080
|
+
const eventPreview = internalUpdateEventMember(inserted);
|
|
34596
35081
|
const [event] = await tx
|
|
34597
35082
|
.insert(schema.sessionEvents)
|
|
34598
35083
|
.values({
|
|
@@ -34602,11 +35087,13 @@ export async function addSessionSystemUpdateWithSourceMutation(
|
|
|
34602
35087
|
sequence: session.lastSequence + 1,
|
|
34603
35088
|
type: "system.update.pending",
|
|
34604
35089
|
payload: sanitizeEventPayload({
|
|
34605
|
-
updateId:
|
|
34606
|
-
kind:
|
|
34607
|
-
classification:
|
|
34608
|
-
sourceId:
|
|
34609
|
-
|
|
35090
|
+
updateId: eventPreview.id,
|
|
35091
|
+
kind: eventPreview.kind,
|
|
35092
|
+
classification: eventPreview.classification,
|
|
35093
|
+
sourceId: eventPreview.sourceId,
|
|
35094
|
+
sourceIdTruncated: eventPreview.sourceIdTruncated,
|
|
35095
|
+
summary: eventPreview.summary,
|
|
35096
|
+
summaryTruncated: eventPreview.summaryTruncated,
|
|
34610
35097
|
}),
|
|
34611
35098
|
occurredAt: now,
|
|
34612
35099
|
})
|
|
@@ -34657,7 +35144,7 @@ export async function listOutstandingSessionSystemUpdates(
|
|
|
34657
35144
|
and(
|
|
34658
35145
|
eq(schema.sessionSystemUpdates.workspaceId, workspaceId),
|
|
34659
35146
|
eq(schema.sessionSystemUpdates.sessionId, sessionId),
|
|
34660
|
-
|
|
35147
|
+
eq(schema.sessionSystemUpdates.state, "pending"),
|
|
34661
35148
|
),
|
|
34662
35149
|
)
|
|
34663
35150
|
.orderBy(asc(schema.sessionSystemUpdates.createdAt), asc(schema.sessionSystemUpdates.id));
|
|
@@ -34713,6 +35200,7 @@ function mapSessionSystemUpdate(
|
|
|
34713
35200
|
lineage: row.lineage,
|
|
34714
35201
|
state: row.state as SessionSystemUpdateState,
|
|
34715
35202
|
deliveredTurnId: row.deliveredTurnId,
|
|
35203
|
+
deliveredHistoryItemId: row.deliveredHistoryItemId,
|
|
34716
35204
|
deliveredAt: row.deliveredAt?.toISOString() ?? null,
|
|
34717
35205
|
createdAt: row.createdAt.toISOString(),
|
|
34718
35206
|
};
|
|
@@ -34730,6 +35218,7 @@ function sessionEventTypesAdvanceActivity(inputs: ReadonlyArray<{ type: string }
|
|
|
34730
35218
|
function sessionMutationAdvancesActivity(update: {
|
|
34731
35219
|
resources?: ResourceRef[];
|
|
34732
35220
|
tools?: ToolRef[];
|
|
35221
|
+
firstPartyMcpTools?: FirstPartyMcpToolName[];
|
|
34733
35222
|
toolPolicy?: SessionToolPolicy;
|
|
34734
35223
|
toolPolicyVersion?: number;
|
|
34735
35224
|
expectedToolPolicyVersion?: number;
|
|
@@ -35329,6 +35818,7 @@ type LockedSessionUpdateResult = {
|
|
|
35329
35818
|
update?: {
|
|
35330
35819
|
resources?: ResourceRef[];
|
|
35331
35820
|
tools?: ToolRef[];
|
|
35821
|
+
firstPartyMcpTools?: FirstPartyMcpToolName[];
|
|
35332
35822
|
toolPolicy?: SessionToolPolicy;
|
|
35333
35823
|
toolPolicyVersion?: number;
|
|
35334
35824
|
expectedToolPolicyVersion?: number;
|
|
@@ -35473,6 +35963,9 @@ export async function appendSessionEventsWithLockedSessionUpdate(
|
|
|
35473
35963
|
lastSequence: sequence,
|
|
35474
35964
|
...(update.resources !== undefined ? { resources: update.resources } : {}),
|
|
35475
35965
|
...(update.tools !== undefined ? { tools: update.tools } : {}),
|
|
35966
|
+
...(update.firstPartyMcpTools !== undefined
|
|
35967
|
+
? { firstPartyMcpTools: update.firstPartyMcpTools }
|
|
35968
|
+
: {}),
|
|
35476
35969
|
...(update.toolPolicy !== undefined ? { toolPolicy: update.toolPolicy } : {}),
|
|
35477
35970
|
...(update.toolPolicyVersion !== undefined
|
|
35478
35971
|
? { toolPolicyVersion: update.toolPolicyVersion }
|
|
@@ -35562,11 +36055,8 @@ function mapSession(
|
|
|
35562
36055
|
resources: row.resources as ResourceRef[],
|
|
35563
36056
|
skills: (row.skills as SessionSkill[]) ?? [],
|
|
35564
36057
|
tools: row.tools as ToolRef[],
|
|
35565
|
-
toolPolicy:
|
|
35566
|
-
|
|
35567
|
-
inheritedFromSessionId: null,
|
|
35568
|
-
},
|
|
35569
|
-
toolPolicyVersion: Number(row.toolPolicyVersion ?? 1),
|
|
36058
|
+
toolPolicy: row.toolPolicy as SessionToolPolicy,
|
|
36059
|
+
toolPolicyVersion: Number(row.toolPolicyVersion),
|
|
35570
36060
|
metadata: row.metadata,
|
|
35571
36061
|
createdBy: initiatorFromStorage(
|
|
35572
36062
|
row.createdByKind,
|
|
@@ -35590,6 +36080,7 @@ function mapSession(
|
|
|
35590
36080
|
rigId: row.rigId ?? null,
|
|
35591
36081
|
rigVersionId: row.rigVersionId ?? null,
|
|
35592
36082
|
firstPartyMcpPermissions: (row.firstPartyMcpPermissions as Permission[] | null) ?? null,
|
|
36083
|
+
firstPartyMcpTools: row.firstPartyMcpTools as FirstPartyMcpToolName[],
|
|
35593
36084
|
mcpServers,
|
|
35594
36085
|
parentSessionId: row.parentSessionId ?? null,
|
|
35595
36086
|
rootSessionId: row.rootSessionId,
|