@opengeni/db 0.19.0 → 0.22.1
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-XOXEQ7HG.js → chunk-BNGEN5QZ.js} +85 -1
- package/dist/chunk-BNGEN5QZ.js.map +1 -0
- package/dist/{chunk-SZP6KRLC.js → chunk-CYGFLLMN.js} +2698 -1918
- package/dist/chunk-CYGFLLMN.js.map +1 -0
- package/dist/index.d.ts +108 -1
- package/dist/index.js +5940 -2907
- package/dist/index.js.map +1 -1
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +4 -4
- package/dist/schema.d.ts +1147 -101
- package/dist/schema.js +37 -1
- package/dist/scoped-knowledge-schema.d.ts +5341 -0
- package/dist/scoped-knowledge.d.ts +208 -0
- package/dist/session-control.d.ts +1 -0
- package/dist/session-queue-commands.d.ts +2 -1
- package/drizzle/0153_mcp_personal_connection_delegations.sql +181 -0
- package/drizzle/0154_scoped_knowledge_foundation.sql +2292 -0
- package/drizzle/0155_connector_action_policies.sql +218 -0
- package/package.json +3 -3
- package/src/connection-token-resolver.ts +3 -0
- package/src/index.ts +1211 -7
- package/src/provision-roles.ts +48 -0
- package/src/runtime-posture.ts +36 -0
- package/src/schema.ts +228 -2
- package/src/scoped-knowledge-schema.ts +603 -0
- package/src/scoped-knowledge.ts +2891 -0
- package/src/session-control.ts +3 -0
- package/src/session-queue-commands.ts +48 -0
- package/dist/chunk-SZP6KRLC.js.map +0 -1
- package/dist/chunk-XOXEQ7HG.js.map +0 -1
package/src/index.ts
CHANGED
|
@@ -33,6 +33,7 @@ import type {
|
|
|
33
33
|
HostUsageExport,
|
|
34
34
|
HostUsageExportBatch,
|
|
35
35
|
ManagedAccount,
|
|
36
|
+
McpPersonalConnectionDelegation,
|
|
36
37
|
Permission,
|
|
37
38
|
PackInstallation,
|
|
38
39
|
PackInstallationStatus,
|
|
@@ -103,6 +104,7 @@ import type {
|
|
|
103
104
|
} from "@opengeni/contracts";
|
|
104
105
|
import {
|
|
105
106
|
DEFAULT_FIRST_PARTY_MCP_TOOLS,
|
|
107
|
+
McpPersonalConnectionDelegations,
|
|
106
108
|
SESSION_AUTHORIZATION_LIST_SCOPE_MAX_IDS,
|
|
107
109
|
backendForNativeSnapshotProvider,
|
|
108
110
|
canonicalModalCheckpointProviderBinding,
|
|
@@ -110,6 +112,7 @@ import {
|
|
|
110
112
|
parseWorkspaceArchiveDescriptor,
|
|
111
113
|
stableJson,
|
|
112
114
|
} from "@opengeni/contracts";
|
|
115
|
+
|
|
113
116
|
import {
|
|
114
117
|
approvalIdentifier,
|
|
115
118
|
boundWorkspaceControlEvent,
|
|
@@ -239,6 +242,7 @@ export * from "./new-session-drafts";
|
|
|
239
242
|
export * from "./workspace-instruction-policies";
|
|
240
243
|
export * from "./preference-registry";
|
|
241
244
|
export * from "./memory-governance";
|
|
245
|
+
export * from "./scoped-knowledge";
|
|
242
246
|
export { interruptedToolCallResult } from "./session-tool-call-settlement";
|
|
243
247
|
export { decryptEnvironmentValue, encryptEnvironmentValue } from "./environment-crypto";
|
|
244
248
|
export {
|
|
@@ -286,6 +290,17 @@ export * from "./memory-domain";
|
|
|
286
290
|
// `userLookup` is the injection seam for hosts on a different driver.
|
|
287
291
|
// `PostgresJsDatabase<typeof schema>` is assignable to this, so standalone is
|
|
288
292
|
// unaffected.
|
|
293
|
+
function parsedPersonalConnectionDelegations(
|
|
294
|
+
value: unknown,
|
|
295
|
+
context: string,
|
|
296
|
+
): McpPersonalConnectionDelegation[] {
|
|
297
|
+
const parsed = McpPersonalConnectionDelegations.safeParse(value);
|
|
298
|
+
if (!parsed.success) {
|
|
299
|
+
throw new Error(`Invalid personal MCP delegation snapshot at ${context}`);
|
|
300
|
+
}
|
|
301
|
+
return parsed.data.map((delegation) => ({ ...delegation }));
|
|
302
|
+
}
|
|
303
|
+
|
|
289
304
|
export type Database = PgDatabase<any, typeof schema>;
|
|
290
305
|
|
|
291
306
|
/** Raised when a durable session tool-policy write lost its version fence. */
|
|
@@ -3296,6 +3311,10 @@ export type CreateScheduledTaskInput = {
|
|
|
3296
3311
|
runMode: ScheduledTaskRunMode;
|
|
3297
3312
|
overlapPolicy: ScheduledTaskOverlapPolicy;
|
|
3298
3313
|
agentConfig: ScheduledTaskAgentConfig;
|
|
3314
|
+
createdBy?: TurnInitiator;
|
|
3315
|
+
createdByContext?: TurnInitiatorContext;
|
|
3316
|
+
createdByActor?: AgentSessionCreationActor | null;
|
|
3317
|
+
personalConnectionDelegations?: McpPersonalConnectionDelegation[];
|
|
3299
3318
|
variableSetId?: string | null;
|
|
3300
3319
|
// The rig each run binds to (M3); active version resolved per fire at dispatch.
|
|
3301
3320
|
rigId?: string | null;
|
|
@@ -3309,6 +3328,7 @@ export type UpdateScheduledTaskInput = Partial<{
|
|
|
3309
3328
|
runMode: ScheduledTaskRunMode;
|
|
3310
3329
|
overlapPolicy: ScheduledTaskOverlapPolicy;
|
|
3311
3330
|
agentConfig: ScheduledTaskAgentConfig;
|
|
3331
|
+
personalConnectionDelegations: McpPersonalConnectionDelegation[];
|
|
3312
3332
|
reusableSessionId: string | null;
|
|
3313
3333
|
variableSetId: string | null;
|
|
3314
3334
|
rigId: string | null;
|
|
@@ -3732,6 +3752,7 @@ export type EnqueueSessionTurnInput = {
|
|
|
3732
3752
|
lineage?: Record<string, unknown>;
|
|
3733
3753
|
initiator: TurnInitiator;
|
|
3734
3754
|
initiatorContext?: TurnInitiatorContext;
|
|
3755
|
+
personalConnectionDelegations?: McpPersonalConnectionDelegation[];
|
|
3735
3756
|
/** Steer inserts before all waiting prompts; Send appends after them. */
|
|
3736
3757
|
placement?: "head" | "tail";
|
|
3737
3758
|
};
|
|
@@ -3742,6 +3763,7 @@ export type EnqueueSessionTurnInput = {
|
|
|
3742
3763
|
*/
|
|
3743
3764
|
export type SessionTurnForExecution = SessionTurn & {
|
|
3744
3765
|
turnInstructions: string | null;
|
|
3766
|
+
personalConnectionDelegations: McpPersonalConnectionDelegation[];
|
|
3745
3767
|
};
|
|
3746
3768
|
|
|
3747
3769
|
export async function createFileUpload(
|
|
@@ -7144,7 +7166,9 @@ export async function loadConnectionCredentialForBroker(
|
|
|
7144
7166
|
if (input.connectionId) {
|
|
7145
7167
|
conditions.push(eq(schema.connections.id, input.connectionId));
|
|
7146
7168
|
} else {
|
|
7147
|
-
conditions.push(
|
|
7169
|
+
conditions.push(
|
|
7170
|
+
sql`lower(${schema.connections.providerDomain}) = lower(${input.providerDomain})`,
|
|
7171
|
+
);
|
|
7148
7172
|
if (input.kind) {
|
|
7149
7173
|
conditions.push(eq(schema.connections.kind, input.kind));
|
|
7150
7174
|
}
|
|
@@ -7174,6 +7198,8 @@ export async function loadConnectionCredentialForBroker(
|
|
|
7174
7198
|
: [
|
|
7175
7199
|
desc(sql`(${schema.connections.status} = 'active')`),
|
|
7176
7200
|
desc(schema.connections.updatedAt),
|
|
7201
|
+
desc(schema.connections.createdAt),
|
|
7202
|
+
desc(schema.connections.id),
|
|
7177
7203
|
]),
|
|
7178
7204
|
)
|
|
7179
7205
|
.limit(1);
|
|
@@ -9018,7 +9044,27 @@ export async function createScheduledTask(
|
|
|
9018
9044
|
db,
|
|
9019
9045
|
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
9020
9046
|
async (scopedDb) => {
|
|
9021
|
-
const
|
|
9047
|
+
const frozenCreator = await frozenSessionCreatorForInsert(scopedDb, input);
|
|
9048
|
+
const [row] = await scopedDb
|
|
9049
|
+
.insert(schema.scheduledTasks)
|
|
9050
|
+
.values({
|
|
9051
|
+
id: input.id,
|
|
9052
|
+
accountId: input.accountId,
|
|
9053
|
+
workspaceId: input.workspaceId,
|
|
9054
|
+
name: input.name,
|
|
9055
|
+
status: input.status,
|
|
9056
|
+
schedule: input.schedule,
|
|
9057
|
+
temporalScheduleId: input.temporalScheduleId,
|
|
9058
|
+
runMode: input.runMode,
|
|
9059
|
+
overlapPolicy: input.overlapPolicy,
|
|
9060
|
+
agentConfig: input.agentConfig,
|
|
9061
|
+
...creatorColumns(frozenCreator),
|
|
9062
|
+
personalConnectionDelegations: input.personalConnectionDelegations ?? [],
|
|
9063
|
+
variableSetId: input.variableSetId ?? null,
|
|
9064
|
+
rigId: input.rigId ?? null,
|
|
9065
|
+
metadata: input.metadata,
|
|
9066
|
+
})
|
|
9067
|
+
.returning();
|
|
9022
9068
|
if (!row) {
|
|
9023
9069
|
throw new Error("Failed to create scheduled task");
|
|
9024
9070
|
}
|
|
@@ -9043,6 +9089,9 @@ export async function updateScheduledTask(
|
|
|
9043
9089
|
...(input.runMode !== undefined ? { runMode: input.runMode } : {}),
|
|
9044
9090
|
...(input.overlapPolicy !== undefined ? { overlapPolicy: input.overlapPolicy } : {}),
|
|
9045
9091
|
...(input.agentConfig !== undefined ? { agentConfig: input.agentConfig } : {}),
|
|
9092
|
+
...(input.personalConnectionDelegations !== undefined
|
|
9093
|
+
? { personalConnectionDelegations: input.personalConnectionDelegations }
|
|
9094
|
+
: {}),
|
|
9046
9095
|
...(input.reusableSessionId !== undefined
|
|
9047
9096
|
? { reusableSessionId: input.reusableSessionId }
|
|
9048
9097
|
: {}),
|
|
@@ -9085,6 +9134,31 @@ export async function getScheduledTask(
|
|
|
9085
9134
|
});
|
|
9086
9135
|
}
|
|
9087
9136
|
|
|
9137
|
+
export async function getScheduledTaskPersonalConnectionDelegations(
|
|
9138
|
+
db: Database,
|
|
9139
|
+
workspaceId: string,
|
|
9140
|
+
taskId: string,
|
|
9141
|
+
): Promise<McpPersonalConnectionDelegation[]> {
|
|
9142
|
+
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
9143
|
+
const [row] = await scopedDb
|
|
9144
|
+
.select({ delegations: schema.scheduledTasks.personalConnectionDelegations })
|
|
9145
|
+
.from(schema.scheduledTasks)
|
|
9146
|
+
.where(
|
|
9147
|
+
and(
|
|
9148
|
+
eq(schema.scheduledTasks.workspaceId, workspaceId),
|
|
9149
|
+
eq(schema.scheduledTasks.id, taskId),
|
|
9150
|
+
),
|
|
9151
|
+
)
|
|
9152
|
+
.limit(1);
|
|
9153
|
+
return row
|
|
9154
|
+
? parsedPersonalConnectionDelegations(
|
|
9155
|
+
row.delegations,
|
|
9156
|
+
`scheduled_tasks:${workspaceId}:${taskId}`,
|
|
9157
|
+
)
|
|
9158
|
+
: [];
|
|
9159
|
+
});
|
|
9160
|
+
}
|
|
9161
|
+
|
|
9088
9162
|
export async function requireScheduledTask(
|
|
9089
9163
|
db: Database,
|
|
9090
9164
|
workspaceId: string,
|
|
@@ -15672,6 +15746,816 @@ export async function listSessionMcpServersForRun(
|
|
|
15672
15746
|
});
|
|
15673
15747
|
}
|
|
15674
15748
|
|
|
15749
|
+
export type ConnectorActionPolicyDecision = schema.ConnectorActionPolicyDecision;
|
|
15750
|
+
export type ConnectorActionPolicySnapshotEntry = schema.ConnectorActionPolicySnapshotEntry;
|
|
15751
|
+
|
|
15752
|
+
export type ConnectorActionAttemptIdentity = {
|
|
15753
|
+
accountId: string;
|
|
15754
|
+
workspaceId: string;
|
|
15755
|
+
sessionId: string;
|
|
15756
|
+
turnId: string;
|
|
15757
|
+
attemptId: string;
|
|
15758
|
+
executionGeneration: number;
|
|
15759
|
+
initiator: Pick<TurnInitiator, "kind" | "subjectId">;
|
|
15760
|
+
};
|
|
15761
|
+
|
|
15762
|
+
export type ConnectorActionInvocation = {
|
|
15763
|
+
approvalId: string;
|
|
15764
|
+
connectionId?: string | null;
|
|
15765
|
+
serverId: string;
|
|
15766
|
+
toolName: string;
|
|
15767
|
+
arguments: unknown;
|
|
15768
|
+
};
|
|
15769
|
+
|
|
15770
|
+
export type PrepareConnectorActionApprovalResult =
|
|
15771
|
+
| { managed: false; decision: "unmanaged" }
|
|
15772
|
+
| {
|
|
15773
|
+
managed: true;
|
|
15774
|
+
decision: ConnectorActionPolicyDecision;
|
|
15775
|
+
requestId?: string;
|
|
15776
|
+
actionFingerprint: string;
|
|
15777
|
+
};
|
|
15778
|
+
|
|
15779
|
+
export type BeginConnectorActionExecutionResult =
|
|
15780
|
+
| { allowed: true; managed: false }
|
|
15781
|
+
| {
|
|
15782
|
+
allowed: true;
|
|
15783
|
+
managed: true;
|
|
15784
|
+
requestId: string;
|
|
15785
|
+
actionFingerprint: string;
|
|
15786
|
+
}
|
|
15787
|
+
| {
|
|
15788
|
+
allowed: false;
|
|
15789
|
+
managed: true;
|
|
15790
|
+
reason: "approval_required" | "blocked" | "rejected" | "already_executed" | "uncertain_retry";
|
|
15791
|
+
requestId: string;
|
|
15792
|
+
actionFingerprint: string;
|
|
15793
|
+
};
|
|
15794
|
+
|
|
15795
|
+
const CONNECTOR_ACTION_POLICY_SNAPSHOT_MAX = 2048;
|
|
15796
|
+
const CONNECTOR_ACTION_APPROVAL_ID_MAX = 1024;
|
|
15797
|
+
const CONNECTOR_ACTION_CONNECTION_ID_MAX = 512;
|
|
15798
|
+
const CONNECTOR_ACTION_SERVER_ID_MAX = 256;
|
|
15799
|
+
const CONNECTOR_ACTION_NAME_MAX = 512;
|
|
15800
|
+
|
|
15801
|
+
function boundedConnectorActionText(value: string, label: string, max: number): string {
|
|
15802
|
+
const trimmed = value.trim();
|
|
15803
|
+
if (trimmed.length === 0 || Buffer.byteLength(trimmed, "utf8") > max) {
|
|
15804
|
+
throw new Error(`${label} must be between 1 and ${max} UTF-8 bytes`);
|
|
15805
|
+
}
|
|
15806
|
+
return trimmed;
|
|
15807
|
+
}
|
|
15808
|
+
|
|
15809
|
+
/**
|
|
15810
|
+
* Resolve the request's policy selector. This caller-controlled value is used
|
|
15811
|
+
* only transiently to match the attempt-frozen policy snapshot; it must never
|
|
15812
|
+
* be copied into a request row or audit event.
|
|
15813
|
+
*/
|
|
15814
|
+
function connectorActionPolicySelector(toolName: string, args: unknown): string {
|
|
15815
|
+
if (args && typeof args === "object" && !Array.isArray(args)) {
|
|
15816
|
+
const action = (args as Record<string, unknown>).action;
|
|
15817
|
+
if (typeof action === "string" && action.trim().length > 0) {
|
|
15818
|
+
return boundedConnectorActionText(action, "connector action name", CONNECTOR_ACTION_NAME_MAX);
|
|
15819
|
+
}
|
|
15820
|
+
}
|
|
15821
|
+
return toolName;
|
|
15822
|
+
}
|
|
15823
|
+
|
|
15824
|
+
function connectorActionEvidenceName(
|
|
15825
|
+
resolved: Exclude<ResolvedConnectorActionPolicy, { managed: false }>,
|
|
15826
|
+
): string {
|
|
15827
|
+
return resolved.entry?.actionName ?? "*";
|
|
15828
|
+
}
|
|
15829
|
+
|
|
15830
|
+
function connectorActionFingerprint(input: {
|
|
15831
|
+
workspaceId: string;
|
|
15832
|
+
connectionId: string;
|
|
15833
|
+
serverId: string;
|
|
15834
|
+
toolName: string;
|
|
15835
|
+
actionName: string;
|
|
15836
|
+
arguments: unknown;
|
|
15837
|
+
}): string {
|
|
15838
|
+
return createHash("sha256")
|
|
15839
|
+
.update(
|
|
15840
|
+
stableJson({
|
|
15841
|
+
workspaceId: input.workspaceId,
|
|
15842
|
+
connectionId: input.connectionId,
|
|
15843
|
+
serverId: input.serverId,
|
|
15844
|
+
toolName: input.toolName,
|
|
15845
|
+
actionName: input.actionName,
|
|
15846
|
+
arguments: input.arguments ?? null,
|
|
15847
|
+
}),
|
|
15848
|
+
"utf8",
|
|
15849
|
+
)
|
|
15850
|
+
.digest("hex");
|
|
15851
|
+
}
|
|
15852
|
+
|
|
15853
|
+
type ResolvedConnectorActionPolicy =
|
|
15854
|
+
| { managed: false }
|
|
15855
|
+
| {
|
|
15856
|
+
managed: true;
|
|
15857
|
+
source: "explicit";
|
|
15858
|
+
entry: ConnectorActionPolicySnapshotEntry;
|
|
15859
|
+
}
|
|
15860
|
+
| {
|
|
15861
|
+
managed: true;
|
|
15862
|
+
source: "ambiguous";
|
|
15863
|
+
entry: null;
|
|
15864
|
+
decision: "block";
|
|
15865
|
+
};
|
|
15866
|
+
|
|
15867
|
+
/** Resolve one immutable attempt snapshot with exact-over-wildcard precedence. */
|
|
15868
|
+
export function resolveConnectorActionPolicy(
|
|
15869
|
+
snapshot: readonly ConnectorActionPolicySnapshotEntry[],
|
|
15870
|
+
input: { connectionId: string; serverId: string; toolName: string; actionName: string },
|
|
15871
|
+
): ResolvedConnectorActionPolicy {
|
|
15872
|
+
const candidates = snapshot
|
|
15873
|
+
.filter(
|
|
15874
|
+
(entry) =>
|
|
15875
|
+
entry.connectionId === input.connectionId &&
|
|
15876
|
+
(entry.serverId === input.serverId || entry.serverId === "*") &&
|
|
15877
|
+
(entry.toolName === input.toolName || entry.toolName === "*") &&
|
|
15878
|
+
(entry.actionName === input.actionName || entry.actionName === "*"),
|
|
15879
|
+
)
|
|
15880
|
+
.map((entry) => ({
|
|
15881
|
+
entry,
|
|
15882
|
+
specificity:
|
|
15883
|
+
Number(entry.serverId !== "*") +
|
|
15884
|
+
Number(entry.toolName !== "*") +
|
|
15885
|
+
Number(entry.actionName !== "*"),
|
|
15886
|
+
}))
|
|
15887
|
+
.sort(
|
|
15888
|
+
(left, right) =>
|
|
15889
|
+
right.specificity - left.specificity || left.entry.id.localeCompare(right.entry.id),
|
|
15890
|
+
);
|
|
15891
|
+
const selected = candidates[0];
|
|
15892
|
+
if (!selected) return { managed: false };
|
|
15893
|
+
if (candidates[1]?.specificity === selected.specificity) {
|
|
15894
|
+
return { managed: true, source: "ambiguous", entry: null, decision: "block" };
|
|
15895
|
+
}
|
|
15896
|
+
return { managed: true, source: "explicit", entry: selected.entry };
|
|
15897
|
+
}
|
|
15898
|
+
|
|
15899
|
+
function connectorActionAuditMetadata(
|
|
15900
|
+
row: typeof schema.connectorActionRequests.$inferSelect,
|
|
15901
|
+
extra: Record<string, unknown> = {},
|
|
15902
|
+
): Record<string, unknown> {
|
|
15903
|
+
return {
|
|
15904
|
+
requestId: row.id,
|
|
15905
|
+
sessionId: row.sessionId,
|
|
15906
|
+
turnId: row.turnId,
|
|
15907
|
+
attemptId: row.creationAttemptId,
|
|
15908
|
+
creationExecutionGeneration: row.creationExecutionGeneration,
|
|
15909
|
+
executionAttemptId: row.executionAttemptId,
|
|
15910
|
+
executionAttemptGeneration: row.executionAttemptGeneration,
|
|
15911
|
+
approvalId: row.approvalId,
|
|
15912
|
+
initiatorKind: row.initiatorKind,
|
|
15913
|
+
initiatorSubjectId: row.initiatorSubjectId,
|
|
15914
|
+
connectionId: row.connectionId,
|
|
15915
|
+
connectionVersion: row.connectionVersion,
|
|
15916
|
+
serverId: row.serverId,
|
|
15917
|
+
toolName: row.toolName,
|
|
15918
|
+
actionName: row.actionName,
|
|
15919
|
+
policyId: row.policyId,
|
|
15920
|
+
policyVersion: row.policyVersion,
|
|
15921
|
+
policySource: row.policySource,
|
|
15922
|
+
policyDecision: row.policyDecision,
|
|
15923
|
+
actionFingerprint: row.actionFingerprint,
|
|
15924
|
+
...extra,
|
|
15925
|
+
};
|
|
15926
|
+
}
|
|
15927
|
+
|
|
15928
|
+
async function insertConnectorActionAudit(
|
|
15929
|
+
db: Database,
|
|
15930
|
+
input: {
|
|
15931
|
+
row: typeof schema.connectorActionRequests.$inferSelect;
|
|
15932
|
+
action: string;
|
|
15933
|
+
subjectId: string;
|
|
15934
|
+
extra?: Record<string, unknown>;
|
|
15935
|
+
},
|
|
15936
|
+
): Promise<void> {
|
|
15937
|
+
await db.insert(schema.auditEvents).values({
|
|
15938
|
+
accountId: input.row.accountId,
|
|
15939
|
+
workspaceId: input.row.workspaceId,
|
|
15940
|
+
subjectId: input.subjectId,
|
|
15941
|
+
action: input.action,
|
|
15942
|
+
targetType: "connector_action_request",
|
|
15943
|
+
targetId: input.row.id,
|
|
15944
|
+
metadata: connectorActionAuditMetadata(input.row, input.extra),
|
|
15945
|
+
});
|
|
15946
|
+
}
|
|
15947
|
+
|
|
15948
|
+
function normalizedConnectorActionInvocation(
|
|
15949
|
+
identity: ConnectorActionAttemptIdentity,
|
|
15950
|
+
invocation: ConnectorActionInvocation,
|
|
15951
|
+
): {
|
|
15952
|
+
approvalId: string;
|
|
15953
|
+
connectionId: string | null;
|
|
15954
|
+
serverId: string;
|
|
15955
|
+
toolName: string;
|
|
15956
|
+
policyActionSelector: string;
|
|
15957
|
+
arguments: unknown;
|
|
15958
|
+
} {
|
|
15959
|
+
const approvalId = boundedConnectorActionText(
|
|
15960
|
+
invocation.approvalId,
|
|
15961
|
+
"connector approval id",
|
|
15962
|
+
CONNECTOR_ACTION_APPROVAL_ID_MAX,
|
|
15963
|
+
);
|
|
15964
|
+
const serverId = boundedConnectorActionText(
|
|
15965
|
+
invocation.serverId,
|
|
15966
|
+
"connector server id",
|
|
15967
|
+
CONNECTOR_ACTION_SERVER_ID_MAX,
|
|
15968
|
+
);
|
|
15969
|
+
const toolName = boundedConnectorActionText(
|
|
15970
|
+
invocation.toolName,
|
|
15971
|
+
"connector tool name",
|
|
15972
|
+
CONNECTOR_ACTION_NAME_MAX,
|
|
15973
|
+
);
|
|
15974
|
+
const policyActionSelector = connectorActionPolicySelector(toolName, invocation.arguments);
|
|
15975
|
+
const connectionId = invocation.connectionId?.trim()
|
|
15976
|
+
? boundedConnectorActionText(
|
|
15977
|
+
invocation.connectionId,
|
|
15978
|
+
"connector connection id",
|
|
15979
|
+
CONNECTOR_ACTION_CONNECTION_ID_MAX,
|
|
15980
|
+
)
|
|
15981
|
+
: null;
|
|
15982
|
+
return {
|
|
15983
|
+
approvalId,
|
|
15984
|
+
connectionId,
|
|
15985
|
+
serverId,
|
|
15986
|
+
toolName,
|
|
15987
|
+
policyActionSelector,
|
|
15988
|
+
arguments: invocation.arguments,
|
|
15989
|
+
};
|
|
15990
|
+
}
|
|
15991
|
+
|
|
15992
|
+
function durableConnectorActionInvocation(
|
|
15993
|
+
identity: ConnectorActionAttemptIdentity,
|
|
15994
|
+
invocation: ReturnType<typeof normalizedConnectorActionInvocation> & { connectionId: string },
|
|
15995
|
+
resolved: Exclude<ResolvedConnectorActionPolicy, { managed: false }>,
|
|
15996
|
+
): {
|
|
15997
|
+
approvalId: string;
|
|
15998
|
+
connectionId: string;
|
|
15999
|
+
serverId: string;
|
|
16000
|
+
toolName: string;
|
|
16001
|
+
actionName: string;
|
|
16002
|
+
actionFingerprint: string;
|
|
16003
|
+
} {
|
|
16004
|
+
const actionName = connectorActionEvidenceName(resolved);
|
|
16005
|
+
return {
|
|
16006
|
+
approvalId: invocation.approvalId,
|
|
16007
|
+
connectionId: invocation.connectionId,
|
|
16008
|
+
serverId: invocation.serverId,
|
|
16009
|
+
toolName: invocation.toolName,
|
|
16010
|
+
actionName,
|
|
16011
|
+
actionFingerprint: connectorActionFingerprint({
|
|
16012
|
+
workspaceId: identity.workspaceId,
|
|
16013
|
+
connectionId: invocation.connectionId,
|
|
16014
|
+
serverId: invocation.serverId,
|
|
16015
|
+
toolName: invocation.toolName,
|
|
16016
|
+
actionName,
|
|
16017
|
+
arguments: invocation.arguments,
|
|
16018
|
+
}),
|
|
16019
|
+
};
|
|
16020
|
+
}
|
|
16021
|
+
|
|
16022
|
+
async function connectorActionAttemptSnapshot(
|
|
16023
|
+
db: Database,
|
|
16024
|
+
identity: ConnectorActionAttemptIdentity,
|
|
16025
|
+
): Promise<ConnectorActionPolicySnapshotEntry[]> {
|
|
16026
|
+
const [attempt] = await db
|
|
16027
|
+
.select({
|
|
16028
|
+
accountId: schema.sessionTurnAttempts.accountId,
|
|
16029
|
+
sessionId: schema.sessionTurnAttempts.sessionId,
|
|
16030
|
+
turnId: schema.sessionTurnAttempts.turnId,
|
|
16031
|
+
executionGeneration: schema.sessionTurnAttempts.executionGeneration,
|
|
16032
|
+
state: schema.sessionTurnAttempts.state,
|
|
16033
|
+
connectorActionPolicies: schema.sessionTurnAttempts.connectorActionPolicies,
|
|
16034
|
+
})
|
|
16035
|
+
.from(schema.sessionTurnAttempts)
|
|
16036
|
+
.where(
|
|
16037
|
+
and(
|
|
16038
|
+
eq(schema.sessionTurnAttempts.workspaceId, identity.workspaceId),
|
|
16039
|
+
eq(schema.sessionTurnAttempts.id, identity.attemptId),
|
|
16040
|
+
),
|
|
16041
|
+
)
|
|
16042
|
+
.limit(1);
|
|
16043
|
+
if (
|
|
16044
|
+
!attempt ||
|
|
16045
|
+
attempt.accountId !== identity.accountId ||
|
|
16046
|
+
attempt.sessionId !== identity.sessionId ||
|
|
16047
|
+
attempt.turnId !== identity.turnId ||
|
|
16048
|
+
attempt.executionGeneration !== identity.executionGeneration ||
|
|
16049
|
+
!["claimed", "running"].includes(attempt.state)
|
|
16050
|
+
) {
|
|
16051
|
+
throw new Error(`connector action attempt ownership is unavailable: ${identity.attemptId}`);
|
|
16052
|
+
}
|
|
16053
|
+
if (attempt.connectorActionPolicies.length > CONNECTOR_ACTION_POLICY_SNAPSHOT_MAX) {
|
|
16054
|
+
throw new Error("connector action policy snapshot exceeds the runtime bound");
|
|
16055
|
+
}
|
|
16056
|
+
return attempt.connectorActionPolicies;
|
|
16057
|
+
}
|
|
16058
|
+
|
|
16059
|
+
function connectorActionRequestMatches(
|
|
16060
|
+
row: typeof schema.connectorActionRequests.$inferSelect,
|
|
16061
|
+
input: {
|
|
16062
|
+
identity: ConnectorActionAttemptIdentity;
|
|
16063
|
+
invocation: ReturnType<typeof durableConnectorActionInvocation>;
|
|
16064
|
+
resolved: Exclude<ResolvedConnectorActionPolicy, { managed: false }>;
|
|
16065
|
+
},
|
|
16066
|
+
): boolean {
|
|
16067
|
+
const entry = input.resolved.entry;
|
|
16068
|
+
return (
|
|
16069
|
+
row.accountId === input.identity.accountId &&
|
|
16070
|
+
row.workspaceId === input.identity.workspaceId &&
|
|
16071
|
+
row.sessionId === input.identity.sessionId &&
|
|
16072
|
+
row.turnId === input.identity.turnId &&
|
|
16073
|
+
row.creationAttemptId === input.identity.attemptId &&
|
|
16074
|
+
row.creationExecutionGeneration === input.identity.executionGeneration &&
|
|
16075
|
+
row.approvalId === input.invocation.approvalId &&
|
|
16076
|
+
row.initiatorKind === input.identity.initiator.kind &&
|
|
16077
|
+
row.initiatorSubjectId === input.identity.initiator.subjectId &&
|
|
16078
|
+
row.connectionId === input.invocation.connectionId &&
|
|
16079
|
+
row.serverId === input.invocation.serverId &&
|
|
16080
|
+
row.toolName === input.invocation.toolName &&
|
|
16081
|
+
row.actionName === input.invocation.actionName &&
|
|
16082
|
+
row.policyId === (entry?.id ?? null) &&
|
|
16083
|
+
row.policyVersion === (entry?.version ?? null) &&
|
|
16084
|
+
row.policySource === input.resolved.source &&
|
|
16085
|
+
row.policyDecision === (entry?.policy ?? "block") &&
|
|
16086
|
+
row.actionFingerprint === input.invocation.actionFingerprint
|
|
16087
|
+
);
|
|
16088
|
+
}
|
|
16089
|
+
|
|
16090
|
+
function connectorActionRequestMatchesLogicalCall(
|
|
16091
|
+
row: typeof schema.connectorActionRequests.$inferSelect,
|
|
16092
|
+
identity: ConnectorActionAttemptIdentity,
|
|
16093
|
+
invocation: ReturnType<typeof normalizedConnectorActionInvocation>,
|
|
16094
|
+
): boolean {
|
|
16095
|
+
if (!invocation.connectionId) return false;
|
|
16096
|
+
return (
|
|
16097
|
+
row.accountId === identity.accountId &&
|
|
16098
|
+
row.workspaceId === identity.workspaceId &&
|
|
16099
|
+
row.sessionId === identity.sessionId &&
|
|
16100
|
+
row.turnId === identity.turnId &&
|
|
16101
|
+
row.approvalId === invocation.approvalId &&
|
|
16102
|
+
row.initiatorKind === identity.initiator.kind &&
|
|
16103
|
+
row.initiatorSubjectId === identity.initiator.subjectId &&
|
|
16104
|
+
row.connectionId === invocation.connectionId &&
|
|
16105
|
+
row.serverId === invocation.serverId &&
|
|
16106
|
+
row.toolName === invocation.toolName &&
|
|
16107
|
+
row.actionFingerprint ===
|
|
16108
|
+
connectorActionFingerprint({
|
|
16109
|
+
workspaceId: identity.workspaceId,
|
|
16110
|
+
connectionId: invocation.connectionId,
|
|
16111
|
+
serverId: invocation.serverId,
|
|
16112
|
+
toolName: invocation.toolName,
|
|
16113
|
+
actionName: row.actionName,
|
|
16114
|
+
arguments: invocation.arguments,
|
|
16115
|
+
})
|
|
16116
|
+
);
|
|
16117
|
+
}
|
|
16118
|
+
|
|
16119
|
+
async function insertConnectorActionRequest(
|
|
16120
|
+
db: Database,
|
|
16121
|
+
input: {
|
|
16122
|
+
identity: ConnectorActionAttemptIdentity;
|
|
16123
|
+
invocation: ReturnType<typeof durableConnectorActionInvocation>;
|
|
16124
|
+
resolved: Exclude<ResolvedConnectorActionPolicy, { managed: false }>;
|
|
16125
|
+
status: "pending" | "blocked" | "executing";
|
|
16126
|
+
},
|
|
16127
|
+
): Promise<{ row: typeof schema.connectorActionRequests.$inferSelect; inserted: boolean }> {
|
|
16128
|
+
const entry = input.resolved.entry;
|
|
16129
|
+
const [inserted] = await db
|
|
16130
|
+
.insert(schema.connectorActionRequests)
|
|
16131
|
+
.values({
|
|
16132
|
+
accountId: input.identity.accountId,
|
|
16133
|
+
workspaceId: input.identity.workspaceId,
|
|
16134
|
+
sessionId: input.identity.sessionId,
|
|
16135
|
+
turnId: input.identity.turnId,
|
|
16136
|
+
creationAttemptId: input.identity.attemptId,
|
|
16137
|
+
creationExecutionGeneration: input.identity.executionGeneration,
|
|
16138
|
+
approvalId: input.invocation.approvalId,
|
|
16139
|
+
initiatorKind: input.identity.initiator.kind,
|
|
16140
|
+
initiatorSubjectId: input.identity.initiator.subjectId,
|
|
16141
|
+
connectionId: input.invocation.connectionId!,
|
|
16142
|
+
serverId: input.invocation.serverId,
|
|
16143
|
+
toolName: input.invocation.toolName,
|
|
16144
|
+
actionName: input.invocation.actionName,
|
|
16145
|
+
policyId: entry?.id ?? null,
|
|
16146
|
+
policyVersion: entry?.version ?? null,
|
|
16147
|
+
policySource: input.resolved.source,
|
|
16148
|
+
policyDecision: entry?.policy ?? "block",
|
|
16149
|
+
actionFingerprint: input.invocation.actionFingerprint!,
|
|
16150
|
+
status: input.status,
|
|
16151
|
+
...(input.status === "executing"
|
|
16152
|
+
? {
|
|
16153
|
+
executionAttemptId: input.identity.attemptId,
|
|
16154
|
+
executionAttemptGeneration: input.identity.executionGeneration,
|
|
16155
|
+
executionStartedAt: new Date(),
|
|
16156
|
+
}
|
|
16157
|
+
: {}),
|
|
16158
|
+
})
|
|
16159
|
+
.onConflictDoNothing({
|
|
16160
|
+
target: [
|
|
16161
|
+
schema.connectorActionRequests.workspaceId,
|
|
16162
|
+
schema.connectorActionRequests.sessionId,
|
|
16163
|
+
schema.connectorActionRequests.turnId,
|
|
16164
|
+
schema.connectorActionRequests.approvalId,
|
|
16165
|
+
],
|
|
16166
|
+
})
|
|
16167
|
+
.returning();
|
|
16168
|
+
if (inserted) return { row: inserted, inserted: true };
|
|
16169
|
+
const [existing] = await db
|
|
16170
|
+
.select()
|
|
16171
|
+
.from(schema.connectorActionRequests)
|
|
16172
|
+
.where(
|
|
16173
|
+
and(
|
|
16174
|
+
eq(schema.connectorActionRequests.workspaceId, input.identity.workspaceId),
|
|
16175
|
+
eq(schema.connectorActionRequests.sessionId, input.identity.sessionId),
|
|
16176
|
+
eq(schema.connectorActionRequests.turnId, input.identity.turnId),
|
|
16177
|
+
eq(schema.connectorActionRequests.approvalId, input.invocation.approvalId),
|
|
16178
|
+
),
|
|
16179
|
+
)
|
|
16180
|
+
.for("update")
|
|
16181
|
+
.limit(1);
|
|
16182
|
+
if (!existing || !connectorActionRequestMatches(existing, input)) {
|
|
16183
|
+
throw new Error("connector action approval id conflicts with different immutable inputs");
|
|
16184
|
+
}
|
|
16185
|
+
return { row: existing, inserted: false };
|
|
16186
|
+
}
|
|
16187
|
+
|
|
16188
|
+
export async function upsertConnectorActionPolicy(
|
|
16189
|
+
db: Database,
|
|
16190
|
+
input: {
|
|
16191
|
+
accountId: string;
|
|
16192
|
+
workspaceId: string;
|
|
16193
|
+
subjectId: string;
|
|
16194
|
+
connectionId: string;
|
|
16195
|
+
serverId: string;
|
|
16196
|
+
toolName: string;
|
|
16197
|
+
actionName: string;
|
|
16198
|
+
policy: ConnectorActionPolicyDecision;
|
|
16199
|
+
},
|
|
16200
|
+
): Promise<{ policy: typeof schema.connectorActionPolicies.$inferSelect; changed: boolean }> {
|
|
16201
|
+
const scope = {
|
|
16202
|
+
connectionId: boundedConnectorActionText(
|
|
16203
|
+
input.connectionId,
|
|
16204
|
+
"connector connection id",
|
|
16205
|
+
CONNECTOR_ACTION_CONNECTION_ID_MAX,
|
|
16206
|
+
),
|
|
16207
|
+
serverId: boundedConnectorActionText(
|
|
16208
|
+
input.serverId,
|
|
16209
|
+
"connector server id",
|
|
16210
|
+
CONNECTOR_ACTION_SERVER_ID_MAX,
|
|
16211
|
+
),
|
|
16212
|
+
toolName: boundedConnectorActionText(
|
|
16213
|
+
input.toolName,
|
|
16214
|
+
"connector tool name",
|
|
16215
|
+
CONNECTOR_ACTION_NAME_MAX,
|
|
16216
|
+
),
|
|
16217
|
+
actionName: boundedConnectorActionText(
|
|
16218
|
+
input.actionName,
|
|
16219
|
+
"connector action name",
|
|
16220
|
+
CONNECTOR_ACTION_NAME_MAX,
|
|
16221
|
+
),
|
|
16222
|
+
};
|
|
16223
|
+
const subjectId = boundedConnectorActionText(input.subjectId, "policy actor", 1024);
|
|
16224
|
+
return await withRlsContext(
|
|
16225
|
+
db,
|
|
16226
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
16227
|
+
async (scopedDb) =>
|
|
16228
|
+
await scopedDb.transaction(async (tx) => {
|
|
16229
|
+
await assertWorkspaceAccountPairInScope(tx, input.accountId, input.workspaceId);
|
|
16230
|
+
const [existing] = await tx
|
|
16231
|
+
.select()
|
|
16232
|
+
.from(schema.connectorActionPolicies)
|
|
16233
|
+
.where(
|
|
16234
|
+
and(
|
|
16235
|
+
eq(schema.connectorActionPolicies.workspaceId, input.workspaceId),
|
|
16236
|
+
eq(schema.connectorActionPolicies.connectionId, scope.connectionId),
|
|
16237
|
+
eq(schema.connectorActionPolicies.serverId, scope.serverId),
|
|
16238
|
+
eq(schema.connectorActionPolicies.toolName, scope.toolName),
|
|
16239
|
+
eq(schema.connectorActionPolicies.actionName, scope.actionName),
|
|
16240
|
+
),
|
|
16241
|
+
)
|
|
16242
|
+
.for("update")
|
|
16243
|
+
.limit(1);
|
|
16244
|
+
if (existing?.policy === input.policy) return { policy: existing, changed: false };
|
|
16245
|
+
const now = new Date();
|
|
16246
|
+
const [row] = existing
|
|
16247
|
+
? await tx
|
|
16248
|
+
.update(schema.connectorActionPolicies)
|
|
16249
|
+
.set({
|
|
16250
|
+
policy: input.policy,
|
|
16251
|
+
version: existing.version + 1,
|
|
16252
|
+
updatedBySubjectId: subjectId,
|
|
16253
|
+
updatedAt: now,
|
|
16254
|
+
})
|
|
16255
|
+
.where(eq(schema.connectorActionPolicies.id, existing.id))
|
|
16256
|
+
.returning()
|
|
16257
|
+
: await tx
|
|
16258
|
+
.insert(schema.connectorActionPolicies)
|
|
16259
|
+
.values({
|
|
16260
|
+
accountId: input.accountId,
|
|
16261
|
+
workspaceId: input.workspaceId,
|
|
16262
|
+
...scope,
|
|
16263
|
+
policy: input.policy,
|
|
16264
|
+
createdBySubjectId: subjectId,
|
|
16265
|
+
updatedBySubjectId: subjectId,
|
|
16266
|
+
})
|
|
16267
|
+
.returning();
|
|
16268
|
+
if (!row) throw new Error("Failed to persist connector action policy");
|
|
16269
|
+
await tx.insert(schema.auditEvents).values({
|
|
16270
|
+
accountId: input.accountId,
|
|
16271
|
+
workspaceId: input.workspaceId,
|
|
16272
|
+
subjectId,
|
|
16273
|
+
action: "connector.action.policy_changed",
|
|
16274
|
+
targetType: "connector_action_policy",
|
|
16275
|
+
targetId: row.id,
|
|
16276
|
+
metadata: {
|
|
16277
|
+
connectionId: row.connectionId,
|
|
16278
|
+
serverId: row.serverId,
|
|
16279
|
+
toolName: row.toolName,
|
|
16280
|
+
actionName: row.actionName,
|
|
16281
|
+
policy: row.policy,
|
|
16282
|
+
version: row.version,
|
|
16283
|
+
previousPolicy: existing?.policy ?? null,
|
|
16284
|
+
previousVersion: existing?.version ?? null,
|
|
16285
|
+
},
|
|
16286
|
+
});
|
|
16287
|
+
return { policy: row, changed: true };
|
|
16288
|
+
}),
|
|
16289
|
+
);
|
|
16290
|
+
}
|
|
16291
|
+
|
|
16292
|
+
export async function prepareConnectorActionApproval(
|
|
16293
|
+
db: Database,
|
|
16294
|
+
identity: ConnectorActionAttemptIdentity,
|
|
16295
|
+
invocation: ConnectorActionInvocation,
|
|
16296
|
+
): Promise<PrepareConnectorActionApprovalResult> {
|
|
16297
|
+
const normalized = normalizedConnectorActionInvocation(identity, invocation);
|
|
16298
|
+
if (!normalized.connectionId) {
|
|
16299
|
+
return { managed: false, decision: "unmanaged" };
|
|
16300
|
+
}
|
|
16301
|
+
return await withRlsContext(
|
|
16302
|
+
db,
|
|
16303
|
+
{ accountId: identity.accountId, workspaceId: identity.workspaceId },
|
|
16304
|
+
async (scopedDb) =>
|
|
16305
|
+
await scopedDb.transaction(async (tx) => {
|
|
16306
|
+
const snapshot = await connectorActionAttemptSnapshot(tx as unknown as Database, identity);
|
|
16307
|
+
const resolved = resolveConnectorActionPolicy(snapshot, {
|
|
16308
|
+
connectionId: normalized.connectionId!,
|
|
16309
|
+
serverId: normalized.serverId,
|
|
16310
|
+
toolName: normalized.toolName,
|
|
16311
|
+
actionName: normalized.policyActionSelector,
|
|
16312
|
+
});
|
|
16313
|
+
if (!resolved.managed) return { managed: false, decision: "unmanaged" } as const;
|
|
16314
|
+
const durable = durableConnectorActionInvocation(
|
|
16315
|
+
identity,
|
|
16316
|
+
{ ...normalized, connectionId: normalized.connectionId! },
|
|
16317
|
+
resolved,
|
|
16318
|
+
);
|
|
16319
|
+
const decision = resolved.entry?.policy ?? "block";
|
|
16320
|
+
if (decision === "allow") {
|
|
16321
|
+
return {
|
|
16322
|
+
managed: true,
|
|
16323
|
+
decision,
|
|
16324
|
+
actionFingerprint: durable.actionFingerprint,
|
|
16325
|
+
} as const;
|
|
16326
|
+
}
|
|
16327
|
+
const { row, inserted } = await insertConnectorActionRequest(tx as unknown as Database, {
|
|
16328
|
+
identity,
|
|
16329
|
+
invocation: durable,
|
|
16330
|
+
resolved,
|
|
16331
|
+
status: decision === "ask" ? "pending" : "blocked",
|
|
16332
|
+
});
|
|
16333
|
+
if (inserted) {
|
|
16334
|
+
await insertConnectorActionAudit(tx as unknown as Database, {
|
|
16335
|
+
row,
|
|
16336
|
+
action:
|
|
16337
|
+
decision === "ask"
|
|
16338
|
+
? "connector.action.approval_requested"
|
|
16339
|
+
: "connector.action.blocked",
|
|
16340
|
+
subjectId: identity.initiator.subjectId,
|
|
16341
|
+
extra: { outcome: decision },
|
|
16342
|
+
});
|
|
16343
|
+
}
|
|
16344
|
+
return {
|
|
16345
|
+
managed: true,
|
|
16346
|
+
decision,
|
|
16347
|
+
requestId: row.id,
|
|
16348
|
+
actionFingerprint: row.actionFingerprint,
|
|
16349
|
+
} as const;
|
|
16350
|
+
}),
|
|
16351
|
+
);
|
|
16352
|
+
}
|
|
16353
|
+
|
|
16354
|
+
export async function beginConnectorActionExecution(
|
|
16355
|
+
db: Database,
|
|
16356
|
+
identity: ConnectorActionAttemptIdentity,
|
|
16357
|
+
invocation: ConnectorActionInvocation,
|
|
16358
|
+
): Promise<BeginConnectorActionExecutionResult> {
|
|
16359
|
+
const normalized = normalizedConnectorActionInvocation(identity, invocation);
|
|
16360
|
+
if (!normalized.connectionId) {
|
|
16361
|
+
return { allowed: true, managed: false };
|
|
16362
|
+
}
|
|
16363
|
+
return await withRlsContext(
|
|
16364
|
+
db,
|
|
16365
|
+
{ accountId: identity.accountId, workspaceId: identity.workspaceId },
|
|
16366
|
+
async (scopedDb) =>
|
|
16367
|
+
await scopedDb.transaction(async (tx) => {
|
|
16368
|
+
const snapshot = await connectorActionAttemptSnapshot(tx as unknown as Database, identity);
|
|
16369
|
+
const [existing] = await tx
|
|
16370
|
+
.select()
|
|
16371
|
+
.from(schema.connectorActionRequests)
|
|
16372
|
+
.where(
|
|
16373
|
+
and(
|
|
16374
|
+
eq(schema.connectorActionRequests.workspaceId, identity.workspaceId),
|
|
16375
|
+
eq(schema.connectorActionRequests.sessionId, identity.sessionId),
|
|
16376
|
+
eq(schema.connectorActionRequests.turnId, identity.turnId),
|
|
16377
|
+
eq(schema.connectorActionRequests.approvalId, normalized.approvalId),
|
|
16378
|
+
),
|
|
16379
|
+
)
|
|
16380
|
+
.for("update")
|
|
16381
|
+
.limit(1);
|
|
16382
|
+
if (existing && !connectorActionRequestMatchesLogicalCall(existing, identity, normalized)) {
|
|
16383
|
+
throw new Error("connector action approval id conflicts with different immutable inputs");
|
|
16384
|
+
}
|
|
16385
|
+
let row = existing;
|
|
16386
|
+
let inserted = false;
|
|
16387
|
+
if (!row) {
|
|
16388
|
+
const resolved = resolveConnectorActionPolicy(snapshot, {
|
|
16389
|
+
connectionId: normalized.connectionId!,
|
|
16390
|
+
serverId: normalized.serverId,
|
|
16391
|
+
toolName: normalized.toolName,
|
|
16392
|
+
actionName: normalized.policyActionSelector,
|
|
16393
|
+
});
|
|
16394
|
+
if (!resolved.managed) return { allowed: true, managed: false } as const;
|
|
16395
|
+
const durable = durableConnectorActionInvocation(
|
|
16396
|
+
identity,
|
|
16397
|
+
{ ...normalized, connectionId: normalized.connectionId! },
|
|
16398
|
+
resolved,
|
|
16399
|
+
);
|
|
16400
|
+
const decision = resolved.entry?.policy ?? "block";
|
|
16401
|
+
const created = await insertConnectorActionRequest(tx as unknown as Database, {
|
|
16402
|
+
identity,
|
|
16403
|
+
invocation: durable,
|
|
16404
|
+
resolved,
|
|
16405
|
+
status: decision === "ask" ? "pending" : decision === "block" ? "blocked" : "executing",
|
|
16406
|
+
});
|
|
16407
|
+
row = created.row;
|
|
16408
|
+
inserted = created.inserted;
|
|
16409
|
+
if (inserted) {
|
|
16410
|
+
await insertConnectorActionAudit(tx as unknown as Database, {
|
|
16411
|
+
row,
|
|
16412
|
+
action:
|
|
16413
|
+
decision === "ask"
|
|
16414
|
+
? "connector.action.approval_requested"
|
|
16415
|
+
: decision === "block"
|
|
16416
|
+
? "connector.action.blocked"
|
|
16417
|
+
: "connector.action.execution_started",
|
|
16418
|
+
subjectId: identity.initiator.subjectId,
|
|
16419
|
+
extra: { outcome: decision === "allow" ? "started" : decision },
|
|
16420
|
+
});
|
|
16421
|
+
}
|
|
16422
|
+
}
|
|
16423
|
+
if (row.status === "approved") {
|
|
16424
|
+
const [executing] = await tx
|
|
16425
|
+
.update(schema.connectorActionRequests)
|
|
16426
|
+
.set({
|
|
16427
|
+
status: "executing",
|
|
16428
|
+
executionAttemptId: identity.attemptId,
|
|
16429
|
+
executionAttemptGeneration: identity.executionGeneration,
|
|
16430
|
+
executionStartedAt: new Date(),
|
|
16431
|
+
updatedAt: new Date(),
|
|
16432
|
+
})
|
|
16433
|
+
.where(eq(schema.connectorActionRequests.id, row.id))
|
|
16434
|
+
.returning();
|
|
16435
|
+
if (!executing) throw new Error("Approved connector action request disappeared");
|
|
16436
|
+
row = executing;
|
|
16437
|
+
await insertConnectorActionAudit(tx as unknown as Database, {
|
|
16438
|
+
row,
|
|
16439
|
+
action: "connector.action.execution_started",
|
|
16440
|
+
subjectId: identity.initiator.subjectId,
|
|
16441
|
+
extra: { outcome: "started" },
|
|
16442
|
+
});
|
|
16443
|
+
return {
|
|
16444
|
+
allowed: true,
|
|
16445
|
+
managed: true,
|
|
16446
|
+
requestId: row.id,
|
|
16447
|
+
actionFingerprint: row.actionFingerprint,
|
|
16448
|
+
} as const;
|
|
16449
|
+
}
|
|
16450
|
+
if (row.status === "executing") {
|
|
16451
|
+
if (inserted) {
|
|
16452
|
+
return {
|
|
16453
|
+
allowed: true,
|
|
16454
|
+
managed: true,
|
|
16455
|
+
requestId: row.id,
|
|
16456
|
+
actionFingerprint: row.actionFingerprint,
|
|
16457
|
+
} as const;
|
|
16458
|
+
}
|
|
16459
|
+
const [uncertain] = await tx
|
|
16460
|
+
.update(schema.connectorActionRequests)
|
|
16461
|
+
.set({
|
|
16462
|
+
status: "uncertain",
|
|
16463
|
+
outcome: "retry_after_execution_started",
|
|
16464
|
+
executionFinishedAt: new Date(),
|
|
16465
|
+
updatedAt: new Date(),
|
|
16466
|
+
})
|
|
16467
|
+
.where(eq(schema.connectorActionRequests.id, row.id))
|
|
16468
|
+
.returning();
|
|
16469
|
+
if (!uncertain) throw new Error("Executing connector action request disappeared");
|
|
16470
|
+
await insertConnectorActionAudit(tx as unknown as Database, {
|
|
16471
|
+
row: uncertain,
|
|
16472
|
+
action: "connector.action.execution_uncertain",
|
|
16473
|
+
subjectId: identity.initiator.subjectId,
|
|
16474
|
+
extra: { outcome: "retry_denied" },
|
|
16475
|
+
});
|
|
16476
|
+
return {
|
|
16477
|
+
allowed: false,
|
|
16478
|
+
managed: true,
|
|
16479
|
+
reason: "uncertain_retry",
|
|
16480
|
+
requestId: uncertain.id,
|
|
16481
|
+
actionFingerprint: uncertain.actionFingerprint,
|
|
16482
|
+
} as const;
|
|
16483
|
+
}
|
|
16484
|
+
const reason =
|
|
16485
|
+
row.status === "pending"
|
|
16486
|
+
? "approval_required"
|
|
16487
|
+
: row.status === "rejected"
|
|
16488
|
+
? "rejected"
|
|
16489
|
+
: row.status === "blocked"
|
|
16490
|
+
? "blocked"
|
|
16491
|
+
: "already_executed";
|
|
16492
|
+
return {
|
|
16493
|
+
allowed: false,
|
|
16494
|
+
managed: true,
|
|
16495
|
+
reason,
|
|
16496
|
+
requestId: row.id,
|
|
16497
|
+
actionFingerprint: row.actionFingerprint,
|
|
16498
|
+
} as const;
|
|
16499
|
+
}),
|
|
16500
|
+
);
|
|
16501
|
+
}
|
|
16502
|
+
|
|
16503
|
+
export async function completeConnectorActionExecution(
|
|
16504
|
+
db: Database,
|
|
16505
|
+
input: {
|
|
16506
|
+
accountId: string;
|
|
16507
|
+
workspaceId: string;
|
|
16508
|
+
requestId: string;
|
|
16509
|
+
attemptId: string;
|
|
16510
|
+
outcome: "completed" | "uncertain";
|
|
16511
|
+
},
|
|
16512
|
+
): Promise<void> {
|
|
16513
|
+
await withRlsContext(
|
|
16514
|
+
db,
|
|
16515
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
16516
|
+
async (scopedDb) =>
|
|
16517
|
+
await scopedDb.transaction(async (tx) => {
|
|
16518
|
+
const [existing] = await tx
|
|
16519
|
+
.select()
|
|
16520
|
+
.from(schema.connectorActionRequests)
|
|
16521
|
+
.where(
|
|
16522
|
+
and(
|
|
16523
|
+
eq(schema.connectorActionRequests.workspaceId, input.workspaceId),
|
|
16524
|
+
eq(schema.connectorActionRequests.id, input.requestId),
|
|
16525
|
+
eq(schema.connectorActionRequests.executionAttemptId, input.attemptId),
|
|
16526
|
+
),
|
|
16527
|
+
)
|
|
16528
|
+
.for("update")
|
|
16529
|
+
.limit(1);
|
|
16530
|
+
if (!existing) throw new Error("Connector action request not found for completion");
|
|
16531
|
+
if (existing.status === input.outcome) return;
|
|
16532
|
+
if (existing.status !== "executing") {
|
|
16533
|
+
throw new Error(`Connector action request cannot complete from ${existing.status}`);
|
|
16534
|
+
}
|
|
16535
|
+
const [row] = await tx
|
|
16536
|
+
.update(schema.connectorActionRequests)
|
|
16537
|
+
.set({
|
|
16538
|
+
status: input.outcome,
|
|
16539
|
+
outcome: input.outcome,
|
|
16540
|
+
executionFinishedAt: new Date(),
|
|
16541
|
+
updatedAt: new Date(),
|
|
16542
|
+
})
|
|
16543
|
+
.where(eq(schema.connectorActionRequests.id, existing.id))
|
|
16544
|
+
.returning();
|
|
16545
|
+
if (!row) throw new Error("Connector action request disappeared during completion");
|
|
16546
|
+
await insertConnectorActionAudit(tx as unknown as Database, {
|
|
16547
|
+
row,
|
|
16548
|
+
action:
|
|
16549
|
+
input.outcome === "completed"
|
|
16550
|
+
? "connector.action.execution_completed"
|
|
16551
|
+
: "connector.action.execution_uncertain",
|
|
16552
|
+
subjectId: row.initiatorSubjectId,
|
|
16553
|
+
extra: { outcome: input.outcome },
|
|
16554
|
+
});
|
|
16555
|
+
}),
|
|
16556
|
+
);
|
|
16557
|
+
}
|
|
16558
|
+
|
|
15675
16559
|
type DeploymentDepthPolicy = NestedAgentDepthDeploymentPolicy;
|
|
15676
16560
|
|
|
15677
16561
|
/** Read the persisted deployment fallback; process configuration is not policy authority. */
|
|
@@ -15759,6 +16643,7 @@ export type SessionCreateInput = {
|
|
|
15759
16643
|
sandboxGroupId?: string | null;
|
|
15760
16644
|
sandboxOs?: SandboxOs;
|
|
15761
16645
|
mcpServers?: CreateSessionMcpServerInput[];
|
|
16646
|
+
personalConnectionDelegations?: McpPersonalConnectionDelegation[];
|
|
15762
16647
|
maxNestedAgentDepthOverride?: number | null;
|
|
15763
16648
|
allowNestedAgentDepthIncrease?: boolean;
|
|
15764
16649
|
subjectId?: string | null;
|
|
@@ -16131,6 +17016,11 @@ async function createSessionInTransaction(
|
|
|
16131
17016
|
|
|
16132
17017
|
// Do not run mutable creator validation before keyed denial replay above.
|
|
16133
17018
|
const frozenCreator = await frozenSessionCreatorForInsert(tx, input);
|
|
17019
|
+
const parentTurnId = input.parentSessionId
|
|
17020
|
+
? input.createdByActor?.sessionId === input.parentSessionId
|
|
17021
|
+
? input.createdByActor.turnId
|
|
17022
|
+
: null
|
|
17023
|
+
: null;
|
|
16134
17024
|
const [inserted] = await tx
|
|
16135
17025
|
.insert(schema.sessions)
|
|
16136
17026
|
.values({
|
|
@@ -16157,8 +17047,10 @@ async function createSessionInTransaction(
|
|
|
16157
17047
|
rigVersionId: input.rigVersionId ?? null,
|
|
16158
17048
|
firstPartyMcpPermissions: input.firstPartyMcpPermissions ?? null,
|
|
16159
17049
|
firstPartyMcpTools: input.firstPartyMcpTools ?? [...DEFAULT_FIRST_PARTY_MCP_TOOLS],
|
|
17050
|
+
initialPersonalConnectionDelegations: input.personalConnectionDelegations ?? [],
|
|
16160
17051
|
instructions: input.instructions ?? null,
|
|
16161
17052
|
parentSessionId: input.parentSessionId ?? null,
|
|
17053
|
+
parentTurnId,
|
|
16162
17054
|
createIdempotencyKey,
|
|
16163
17055
|
rootSessionId: decision.rootSessionId,
|
|
16164
17056
|
nestedAgentDepth: decision.nestedAgentDepth,
|
|
@@ -16329,6 +17221,76 @@ export async function getSession(
|
|
|
16329
17221
|
});
|
|
16330
17222
|
}
|
|
16331
17223
|
|
|
17224
|
+
async function personalConnectionDelegationsForTurnInTransaction(
|
|
17225
|
+
db: Database,
|
|
17226
|
+
workspaceId: string,
|
|
17227
|
+
sessionId: string,
|
|
17228
|
+
turnId: string,
|
|
17229
|
+
): Promise<McpPersonalConnectionDelegation[]> {
|
|
17230
|
+
const [row] = await db
|
|
17231
|
+
.select({ delegations: schema.sessionTurns.personalConnectionDelegations })
|
|
17232
|
+
.from(schema.sessionTurns)
|
|
17233
|
+
.where(
|
|
17234
|
+
and(
|
|
17235
|
+
eq(schema.sessionTurns.workspaceId, workspaceId),
|
|
17236
|
+
eq(schema.sessionTurns.sessionId, sessionId),
|
|
17237
|
+
eq(schema.sessionTurns.id, turnId),
|
|
17238
|
+
),
|
|
17239
|
+
)
|
|
17240
|
+
.limit(1);
|
|
17241
|
+
return row
|
|
17242
|
+
? parsedPersonalConnectionDelegations(
|
|
17243
|
+
row.delegations,
|
|
17244
|
+
`session_turns:${workspaceId}:${sessionId}:${turnId}`,
|
|
17245
|
+
)
|
|
17246
|
+
: [];
|
|
17247
|
+
}
|
|
17248
|
+
|
|
17249
|
+
export async function getSessionTurnPersonalConnectionDelegations(
|
|
17250
|
+
db: Database,
|
|
17251
|
+
workspaceId: string,
|
|
17252
|
+
sessionId: string,
|
|
17253
|
+
turnId: string,
|
|
17254
|
+
): Promise<McpPersonalConnectionDelegation[]> {
|
|
17255
|
+
return await withWorkspaceRls(
|
|
17256
|
+
db,
|
|
17257
|
+
workspaceId,
|
|
17258
|
+
async (scopedDb) =>
|
|
17259
|
+
await personalConnectionDelegationsForTurnInTransaction(
|
|
17260
|
+
scopedDb,
|
|
17261
|
+
workspaceId,
|
|
17262
|
+
sessionId,
|
|
17263
|
+
turnId,
|
|
17264
|
+
),
|
|
17265
|
+
);
|
|
17266
|
+
}
|
|
17267
|
+
|
|
17268
|
+
export async function getSessionParentPersonalConnectionDelegations(
|
|
17269
|
+
db: Database,
|
|
17270
|
+
workspaceId: string,
|
|
17271
|
+
childSessionId: string,
|
|
17272
|
+
): Promise<McpPersonalConnectionDelegation[]> {
|
|
17273
|
+
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
17274
|
+
const [child] = await scopedDb
|
|
17275
|
+
.select({
|
|
17276
|
+
parentSessionId: schema.sessions.parentSessionId,
|
|
17277
|
+
parentTurnId: schema.sessions.parentTurnId,
|
|
17278
|
+
})
|
|
17279
|
+
.from(schema.sessions)
|
|
17280
|
+
.where(
|
|
17281
|
+
and(eq(schema.sessions.workspaceId, workspaceId), eq(schema.sessions.id, childSessionId)),
|
|
17282
|
+
)
|
|
17283
|
+
.limit(1);
|
|
17284
|
+
if (!child?.parentSessionId || !child.parentTurnId) return [];
|
|
17285
|
+
return await personalConnectionDelegationsForTurnInTransaction(
|
|
17286
|
+
scopedDb,
|
|
17287
|
+
workspaceId,
|
|
17288
|
+
child.parentSessionId,
|
|
17289
|
+
child.parentTurnId,
|
|
17290
|
+
);
|
|
17291
|
+
});
|
|
17292
|
+
}
|
|
17293
|
+
|
|
16332
17294
|
export async function getSessionSpawnDenial(
|
|
16333
17295
|
db: Database,
|
|
16334
17296
|
workspaceId: string,
|
|
@@ -17692,6 +18654,56 @@ export async function listSessions(
|
|
|
17692
18654
|
});
|
|
17693
18655
|
}
|
|
17694
18656
|
|
|
18657
|
+
/**
|
|
18658
|
+
* Return the model most recently chosen by one human subject in their own
|
|
18659
|
+
* workspace sessions. A later subject-initiated turn supersedes the session's
|
|
18660
|
+
* creation-time default; sessions with no such turn fall back to sessions.model.
|
|
18661
|
+
* Turn timestamp ties use queue position then UUID, and session ties use the
|
|
18662
|
+
* immutable session timestamp/UUID. Other subjects' turns never influence the
|
|
18663
|
+
* result. The workspace filter remains authoritative under FORCE RLS.
|
|
18664
|
+
*/
|
|
18665
|
+
export async function getLatestSessionModelForSubject(
|
|
18666
|
+
db: Database,
|
|
18667
|
+
workspaceId: string,
|
|
18668
|
+
subjectId: string,
|
|
18669
|
+
): Promise<string | null> {
|
|
18670
|
+
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
18671
|
+
const rows = await scopedDb.execute<{ model: string }>(sql`
|
|
18672
|
+
select coalesce(latest_subject_turn.model, subject_session.model) as model
|
|
18673
|
+
from ${schema.sessions} subject_session
|
|
18674
|
+
left join lateral (
|
|
18675
|
+
select
|
|
18676
|
+
subject_turn.id,
|
|
18677
|
+
subject_turn.model,
|
|
18678
|
+
subject_turn.position,
|
|
18679
|
+
subject_turn.created_at
|
|
18680
|
+
from ${schema.sessionTurns} subject_turn
|
|
18681
|
+
where subject_turn.workspace_id = ${workspaceId}
|
|
18682
|
+
and subject_turn.session_id = subject_session.id
|
|
18683
|
+
and subject_turn.initiator_kind = 'subject'
|
|
18684
|
+
and subject_turn.initiator_subject_id = ${subjectId}
|
|
18685
|
+
order by
|
|
18686
|
+
subject_turn.created_at desc,
|
|
18687
|
+
subject_turn.position desc,
|
|
18688
|
+
subject_turn.id desc
|
|
18689
|
+
limit 1
|
|
18690
|
+
) latest_subject_turn on true
|
|
18691
|
+
where subject_session.workspace_id = ${workspaceId}
|
|
18692
|
+
and subject_session.created_by_kind = 'subject'
|
|
18693
|
+
and subject_session.created_by_subject_id = ${subjectId}
|
|
18694
|
+
order by
|
|
18695
|
+
coalesce(latest_subject_turn.created_at, subject_session.created_at) desc,
|
|
18696
|
+
(latest_subject_turn.id is not null) desc,
|
|
18697
|
+
subject_session.created_at desc,
|
|
18698
|
+
subject_session.id desc,
|
|
18699
|
+
latest_subject_turn.position desc nulls last,
|
|
18700
|
+
latest_subject_turn.id desc nulls last
|
|
18701
|
+
limit 1
|
|
18702
|
+
`);
|
|
18703
|
+
return rows[0]?.model ?? null;
|
|
18704
|
+
});
|
|
18705
|
+
}
|
|
18706
|
+
|
|
17695
18707
|
export type SessionDiscoveryOrderBy = "createdAt" | "updatedAt";
|
|
17696
18708
|
export type SessionDiscoveryCursor = {
|
|
17697
18709
|
orderBy: SessionDiscoveryOrderBy;
|
|
@@ -34812,6 +35824,32 @@ export async function materializeGoalContinuation(
|
|
|
34812
35824
|
return { action: "paused", events: [mapEvent(event)] } as const;
|
|
34813
35825
|
}
|
|
34814
35826
|
|
|
35827
|
+
const [causalTurn] = await tx
|
|
35828
|
+
.select({
|
|
35829
|
+
id: schema.sessionTurns.id,
|
|
35830
|
+
personalConnectionDelegations: schema.sessionTurns.personalConnectionDelegations,
|
|
35831
|
+
})
|
|
35832
|
+
.from(schema.sessionTurns)
|
|
35833
|
+
.where(
|
|
35834
|
+
and(
|
|
35835
|
+
eq(schema.sessionTurns.workspaceId, input.workspaceId),
|
|
35836
|
+
eq(schema.sessionTurns.sessionId, input.sessionId),
|
|
35837
|
+
sql`${schema.sessionTurns.finishedAt} is not null`,
|
|
35838
|
+
),
|
|
35839
|
+
)
|
|
35840
|
+
.orderBy(
|
|
35841
|
+
desc(schema.sessionTurns.position),
|
|
35842
|
+
desc(schema.sessionTurns.createdAt),
|
|
35843
|
+
desc(schema.sessionTurns.id),
|
|
35844
|
+
)
|
|
35845
|
+
.limit(1);
|
|
35846
|
+
const personalConnectionDelegations = causalTurn
|
|
35847
|
+
? parsedPersonalConnectionDelegations(
|
|
35848
|
+
causalTurn.personalConnectionDelegations,
|
|
35849
|
+
`session_turns:${input.workspaceId}:${input.sessionId}:${causalTurn.id}`,
|
|
35850
|
+
)
|
|
35851
|
+
: [];
|
|
35852
|
+
|
|
34815
35853
|
const prompt = input.prompt(decision.goal, decision.autoContinuation, decision.cap);
|
|
34816
35854
|
const payload = {
|
|
34817
35855
|
type: "goal_continuation" as const,
|
|
@@ -34841,7 +35879,12 @@ export async function materializeGoalContinuation(
|
|
|
34841
35879
|
dedupeKey: `goal-continuation:${decision.goal.id}:wake:${goalWakeRevision}`,
|
|
34842
35880
|
summary: prompt,
|
|
34843
35881
|
payload,
|
|
34844
|
-
lineage: {
|
|
35882
|
+
lineage: {
|
|
35883
|
+
goalId: decision.goal.id,
|
|
35884
|
+
goalWakeRevision,
|
|
35885
|
+
...(causalTurn ? { causalTurnId: causalTurn.id } : {}),
|
|
35886
|
+
},
|
|
35887
|
+
personalConnectionDelegations,
|
|
34845
35888
|
state: "pending",
|
|
34846
35889
|
})
|
|
34847
35890
|
.returning();
|
|
@@ -35195,6 +36238,10 @@ export async function initializeSessionStartAtomically(
|
|
|
35195
36238
|
: {},
|
|
35196
36239
|
lineage: {},
|
|
35197
36240
|
...initiatorColumns(creator),
|
|
36241
|
+
personalConnectionDelegations: parsedPersonalConnectionDelegations(
|
|
36242
|
+
session.initialPersonalConnectionDelegations,
|
|
36243
|
+
`sessions:${session.workspaceId}:${session.id}:initial`,
|
|
36244
|
+
),
|
|
35198
36245
|
})
|
|
35199
36246
|
.returning();
|
|
35200
36247
|
if (!turn) throw new Error("Failed to create initial session turn");
|
|
@@ -35355,6 +36402,7 @@ export async function enqueueSessionTurn(
|
|
|
35355
36402
|
initiator: input.initiator,
|
|
35356
36403
|
context: input.initiatorContext ?? {},
|
|
35357
36404
|
}),
|
|
36405
|
+
personalConnectionDelegations: input.personalConnectionDelegations ?? [],
|
|
35358
36406
|
})
|
|
35359
36407
|
.returning();
|
|
35360
36408
|
if (!row) {
|
|
@@ -35392,7 +36440,14 @@ const MAX_INTERNAL_UPDATE_EVENT_SOURCE_BYTES = 256;
|
|
|
35392
36440
|
|
|
35393
36441
|
type BoundedSystemUpdate = Pick<
|
|
35394
36442
|
typeof schema.sessionSystemUpdates.$inferSelect,
|
|
35395
|
-
|
|
36443
|
+
| "id"
|
|
36444
|
+
| "kind"
|
|
36445
|
+
| "classification"
|
|
36446
|
+
| "sourceId"
|
|
36447
|
+
| "summary"
|
|
36448
|
+
| "payload"
|
|
36449
|
+
| "lineage"
|
|
36450
|
+
| "personalConnectionDelegations"
|
|
35396
36451
|
>;
|
|
35397
36452
|
|
|
35398
36453
|
function boundedInternalUpdateEventText(
|
|
@@ -35431,10 +36486,14 @@ function internalUpdateEventMember(update: BoundedSystemUpdate) {
|
|
|
35431
36486
|
};
|
|
35432
36487
|
}
|
|
35433
36488
|
|
|
35434
|
-
function selectBoundedSystemUpdateBatch<T extends BoundedSystemUpdate>(
|
|
36489
|
+
function selectBoundedSystemUpdateBatch<T extends BoundedSystemUpdate>(
|
|
36490
|
+
updates: readonly T[],
|
|
36491
|
+
canCoalesce: (first: T, candidate: T) => boolean = () => true,
|
|
36492
|
+
): T[] {
|
|
35435
36493
|
const selected: T[] = [];
|
|
35436
36494
|
let selectedBytes = 0;
|
|
35437
36495
|
for (const update of updates) {
|
|
36496
|
+
if (selected[0] && !canCoalesce(selected[0], update)) break;
|
|
35438
36497
|
const updateBytes = Buffer.byteLength(
|
|
35439
36498
|
JSON.stringify({
|
|
35440
36499
|
id: update.id,
|
|
@@ -35594,7 +36653,17 @@ export async function claimSessionWorkForAttempt(
|
|
|
35594
36653
|
}
|
|
35595
36654
|
validUpdates.push(update);
|
|
35596
36655
|
}
|
|
35597
|
-
const
|
|
36656
|
+
const delegationKey = (update: (typeof validUpdates)[number]): string =>
|
|
36657
|
+
stableJson(
|
|
36658
|
+
parsedPersonalConnectionDelegations(
|
|
36659
|
+
update.personalConnectionDelegations,
|
|
36660
|
+
`session_system_updates:${workspaceId}:${sessionId}:${update.id}`,
|
|
36661
|
+
),
|
|
36662
|
+
);
|
|
36663
|
+
const deliverable = selectBoundedSystemUpdateBatch(
|
|
36664
|
+
validUpdates,
|
|
36665
|
+
(first, candidate) => delegationKey(first) === delegationKey(candidate),
|
|
36666
|
+
);
|
|
35598
36667
|
if (deliverable.length === 0) {
|
|
35599
36668
|
const cancellationEvent =
|
|
35600
36669
|
cancelledUpdateIds.length > 0
|
|
@@ -35827,6 +36896,29 @@ export async function claimSessionWorkForAttempt(
|
|
|
35827
36896
|
const mcpApprovalPolicies: Record<string, SessionMcpApprovalPolicy> = Object.fromEntries(
|
|
35828
36897
|
policyRows.map((row) => [row.serverId, row.requireApproval ?? false]),
|
|
35829
36898
|
);
|
|
36899
|
+
const connectorPolicyRows = await tx
|
|
36900
|
+
.select({
|
|
36901
|
+
id: schema.connectorActionPolicies.id,
|
|
36902
|
+
connectionId: schema.connectorActionPolicies.connectionId,
|
|
36903
|
+
serverId: schema.connectorActionPolicies.serverId,
|
|
36904
|
+
toolName: schema.connectorActionPolicies.toolName,
|
|
36905
|
+
actionName: schema.connectorActionPolicies.actionName,
|
|
36906
|
+
policy: schema.connectorActionPolicies.policy,
|
|
36907
|
+
version: schema.connectorActionPolicies.version,
|
|
36908
|
+
})
|
|
36909
|
+
.from(schema.connectorActionPolicies)
|
|
36910
|
+
.where(eq(schema.connectorActionPolicies.workspaceId, workspaceId))
|
|
36911
|
+
.orderBy(
|
|
36912
|
+
asc(schema.connectorActionPolicies.connectionId),
|
|
36913
|
+
asc(schema.connectorActionPolicies.serverId),
|
|
36914
|
+
asc(schema.connectorActionPolicies.toolName),
|
|
36915
|
+
asc(schema.connectorActionPolicies.actionName),
|
|
36916
|
+
asc(schema.connectorActionPolicies.id),
|
|
36917
|
+
)
|
|
36918
|
+
.limit(2049);
|
|
36919
|
+
if (connectorPolicyRows.length > 2048) {
|
|
36920
|
+
throw new Error("Connector action policy snapshot exceeds the 2048-row bound");
|
|
36921
|
+
}
|
|
35830
36922
|
return await registerSessionTurnAttemptClaim(tx as unknown as Database, {
|
|
35831
36923
|
id: input.attemptId,
|
|
35832
36924
|
accountId: session.accountId,
|
|
@@ -35839,6 +36931,7 @@ export async function claimSessionWorkForAttempt(
|
|
|
35839
36931
|
temporalActivityId: input.dispatchId,
|
|
35840
36932
|
verifiedControlRevision: Number(workspaceControl.revision),
|
|
35841
36933
|
mcpApprovalPolicies,
|
|
36934
|
+
connectorActionPolicies: connectorPolicyRows,
|
|
35842
36935
|
});
|
|
35843
36936
|
};
|
|
35844
36937
|
if (session.activeTurnId !== null) {
|
|
@@ -36236,6 +37329,7 @@ export async function claimSessionWorkForAttempt(
|
|
|
36236
37329
|
},
|
|
36237
37330
|
),
|
|
36238
37331
|
...initiatorColumns(compactionInitiator),
|
|
37332
|
+
personalConnectionDelegations: [],
|
|
36239
37333
|
startedAt: now,
|
|
36240
37334
|
})
|
|
36241
37335
|
.returning();
|
|
@@ -36383,6 +37477,12 @@ export async function claimSessionWorkForAttempt(
|
|
|
36383
37477
|
},
|
|
36384
37478
|
});
|
|
36385
37479
|
let internalInitiator: FrozenTurnInitiator;
|
|
37480
|
+
const authorityUpdate = delivered.updates[0];
|
|
37481
|
+
if (!authorityUpdate) throw new Error("Delivered update batch has no authority source");
|
|
37482
|
+
const internalPersonalConnectionDelegations = parsedPersonalConnectionDelegations(
|
|
37483
|
+
authorityUpdate.personalConnectionDelegations,
|
|
37484
|
+
`session_system_updates:${workspaceId}:${sessionId}:${authorityUpdate.id}`,
|
|
37485
|
+
);
|
|
36386
37486
|
// Agent Steer is the causal command for this inference. Ordinary
|
|
36387
37487
|
// machine notices may coalesce into the same batch as context, but
|
|
36388
37488
|
// their timing must not erase the steering subject's authority.
|
|
@@ -36544,6 +37644,7 @@ export async function claimSessionWorkForAttempt(
|
|
|
36544
37644
|
{ id: input.dispatchId, generation: 1, triggerEventId },
|
|
36545
37645
|
),
|
|
36546
37646
|
...initiatorColumns(internalInitiator),
|
|
37647
|
+
personalConnectionDelegations: internalPersonalConnectionDelegations,
|
|
36547
37648
|
startedAt: now,
|
|
36548
37649
|
})
|
|
36549
37650
|
.returning();
|
|
@@ -37877,6 +38978,14 @@ export async function settleSessionIdleWithParentOutbox(
|
|
|
37877
38978
|
} as const;
|
|
37878
38979
|
}
|
|
37879
38980
|
const dedupeKey = `child-completion:${session.id}:${episodeKey}`;
|
|
38981
|
+
const personalConnectionDelegations = session.parentTurnId
|
|
38982
|
+
? await personalConnectionDelegationsForTurnInTransaction(
|
|
38983
|
+
tx as unknown as Database,
|
|
38984
|
+
workspaceId,
|
|
38985
|
+
session.parentSessionId,
|
|
38986
|
+
session.parentTurnId,
|
|
38987
|
+
)
|
|
38988
|
+
: [];
|
|
37880
38989
|
await tx
|
|
37881
38990
|
.insert(schema.sessionSystemUpdateOutbox)
|
|
37882
38991
|
.values({
|
|
@@ -37897,7 +39006,9 @@ export async function settleSessionIdleWithParentOutbox(
|
|
|
37897
39006
|
lineage: {
|
|
37898
39007
|
childSessionId: session.id,
|
|
37899
39008
|
parentSessionId: session.parentSessionId,
|
|
39009
|
+
...(session.parentTurnId ? { parentTurnId: session.parentTurnId } : {}),
|
|
37900
39010
|
},
|
|
39011
|
+
personalConnectionDelegations,
|
|
37901
39012
|
})
|
|
37902
39013
|
.onConflictDoNothing({
|
|
37903
39014
|
target: [
|
|
@@ -39982,6 +41093,16 @@ export async function getSessionQueueSnapshot(
|
|
|
39982
41093
|
.where(and(eq(schema.sessions.workspaceId, workspaceId), eq(schema.sessions.id, sessionId)))
|
|
39983
41094
|
.limit(1);
|
|
39984
41095
|
if (!session) return null;
|
|
41096
|
+
const activePersonalConnections = session.activeTurnId
|
|
41097
|
+
? (
|
|
41098
|
+
await personalConnectionDelegationsForTurnInTransaction(
|
|
41099
|
+
scopedDb,
|
|
41100
|
+
workspaceId,
|
|
41101
|
+
sessionId,
|
|
41102
|
+
session.activeTurnId,
|
|
41103
|
+
)
|
|
41104
|
+
).map(({ serverId, providerDomain }) => ({ serverId, providerDomain }))
|
|
41105
|
+
: [];
|
|
39985
41106
|
const rows = await scopedDb
|
|
39986
41107
|
.select()
|
|
39987
41108
|
.from(schema.sessionTurns)
|
|
@@ -40025,6 +41146,7 @@ export async function getSessionQueueSnapshot(
|
|
|
40025
41146
|
return {
|
|
40026
41147
|
version: session.queueVersion,
|
|
40027
41148
|
effectiveControl: serializeEffectiveSessionControl(effectiveControl),
|
|
41149
|
+
activePersonalConnections,
|
|
40028
41150
|
stoppingPreviousAttempt:
|
|
40029
41151
|
latestInterruption !== null &&
|
|
40030
41152
|
latestInterruption.interruptionState !== "rejected_stale" &&
|
|
@@ -40073,11 +41195,19 @@ function queuedSteerReplacementAttemptId(metadata: Record<string, unknown>): str
|
|
|
40073
41195
|
async function enqueueFailedChildOutboxForTurnTx(
|
|
40074
41196
|
tx: Database,
|
|
40075
41197
|
workspaceId: string,
|
|
40076
|
-
session: Pick<typeof schema.sessions.$inferSelect, "id" | "parentSessionId">,
|
|
41198
|
+
session: Pick<typeof schema.sessions.$inferSelect, "id" | "parentSessionId" | "parentTurnId">,
|
|
40077
41199
|
turn: Pick<typeof schema.sessionTurns.$inferSelect, "id" | "accountId" | "sessionId">,
|
|
40078
41200
|
): Promise<void> {
|
|
40079
41201
|
if (!session.parentSessionId) return;
|
|
40080
41202
|
const dedupeKey = `child-completion:${turn.sessionId}:turn:${turn.id}`;
|
|
41203
|
+
const personalConnectionDelegations = session.parentTurnId
|
|
41204
|
+
? await personalConnectionDelegationsForTurnInTransaction(
|
|
41205
|
+
tx,
|
|
41206
|
+
workspaceId,
|
|
41207
|
+
session.parentSessionId,
|
|
41208
|
+
session.parentTurnId,
|
|
41209
|
+
)
|
|
41210
|
+
: [];
|
|
40081
41211
|
await tx
|
|
40082
41212
|
.insert(schema.sessionSystemUpdateOutbox)
|
|
40083
41213
|
.values({
|
|
@@ -40099,8 +41229,10 @@ async function enqueueFailedChildOutboxForTurnTx(
|
|
|
40099
41229
|
lineage: {
|
|
40100
41230
|
childSessionId: turn.sessionId,
|
|
40101
41231
|
parentSessionId: session.parentSessionId,
|
|
41232
|
+
...(session.parentTurnId ? { parentTurnId: session.parentTurnId } : {}),
|
|
40102
41233
|
turnId: turn.id,
|
|
40103
41234
|
},
|
|
41235
|
+
personalConnectionDelegations,
|
|
40104
41236
|
})
|
|
40105
41237
|
.onConflictDoNothing({
|
|
40106
41238
|
target: [
|
|
@@ -40137,6 +41269,7 @@ export type SessionSystemUpdateOutboxDelivery = {
|
|
|
40137
41269
|
summary: string;
|
|
40138
41270
|
payload: ChildTerminalResultPayload;
|
|
40139
41271
|
lineage: Record<string, unknown>;
|
|
41272
|
+
personalConnectionDelegations: McpPersonalConnectionDelegation[];
|
|
40140
41273
|
};
|
|
40141
41274
|
|
|
40142
41275
|
function mapSystemUpdateOutboxRow(row: {
|
|
@@ -40152,6 +41285,7 @@ function mapSystemUpdateOutboxRow(row: {
|
|
|
40152
41285
|
summary: string;
|
|
40153
41286
|
payload: Record<string, unknown>;
|
|
40154
41287
|
lineage: Record<string, unknown>;
|
|
41288
|
+
personal_connection_delegations: unknown;
|
|
40155
41289
|
}): SessionSystemUpdateOutboxDelivery {
|
|
40156
41290
|
if (row.kind !== "child_terminal_result") {
|
|
40157
41291
|
throw new Error(`System-update outbox contains retired kind ${row.kind}`);
|
|
@@ -40170,6 +41304,10 @@ function mapSystemUpdateOutboxRow(row: {
|
|
|
40170
41304
|
summary: row.summary,
|
|
40171
41305
|
payload: parseChildTerminalResultPayload(row.payload),
|
|
40172
41306
|
lineage: row.lineage,
|
|
41307
|
+
personalConnectionDelegations: parsedPersonalConnectionDelegations(
|
|
41308
|
+
row.personal_connection_delegations,
|
|
41309
|
+
`session_system_update_outbox:${row.workspace_id}:${row.id}`,
|
|
41310
|
+
),
|
|
40173
41311
|
};
|
|
40174
41312
|
}
|
|
40175
41313
|
|
|
@@ -40214,6 +41352,10 @@ export async function getSessionSystemUpdateOutboxByDedupeKey(
|
|
|
40214
41352
|
summary: row.summary,
|
|
40215
41353
|
payload: parseChildTerminalResultPayload(row.payload),
|
|
40216
41354
|
lineage: row.lineage,
|
|
41355
|
+
personalConnectionDelegations: parsedPersonalConnectionDelegations(
|
|
41356
|
+
row.personalConnectionDelegations,
|
|
41357
|
+
`session_system_update_outbox:${row.workspaceId}:${row.id}`,
|
|
41358
|
+
),
|
|
40217
41359
|
};
|
|
40218
41360
|
},
|
|
40219
41361
|
);
|
|
@@ -40236,6 +41378,7 @@ export async function claimPendingSessionSystemUpdateOutbox(
|
|
|
40236
41378
|
summary: string;
|
|
40237
41379
|
payload: Record<string, unknown>;
|
|
40238
41380
|
lineage: Record<string, unknown>;
|
|
41381
|
+
personal_connection_delegations: unknown;
|
|
40239
41382
|
}>(db, sql`select * from opengeni_private.claim_session_system_update_outbox(${limit})`);
|
|
40240
41383
|
return rows.map(mapSystemUpdateOutboxRow);
|
|
40241
41384
|
}
|
|
@@ -40586,6 +41729,7 @@ export async function getOrCreateSessionSystemUpdateOutbox(
|
|
|
40586
41729
|
summary: input.summary,
|
|
40587
41730
|
payload: input.payload,
|
|
40588
41731
|
lineage: input.lineage,
|
|
41732
|
+
personalConnectionDelegations: input.personalConnectionDelegations,
|
|
40589
41733
|
})
|
|
40590
41734
|
.onConflictDoUpdate({
|
|
40591
41735
|
target: [
|
|
@@ -40618,6 +41762,10 @@ export async function getOrCreateSessionSystemUpdateOutbox(
|
|
|
40618
41762
|
summary: row.summary,
|
|
40619
41763
|
payload: parseChildTerminalResultPayload(row.payload),
|
|
40620
41764
|
lineage: row.lineage,
|
|
41765
|
+
personalConnectionDelegations: parsedPersonalConnectionDelegations(
|
|
41766
|
+
row.personalConnectionDelegations,
|
|
41767
|
+
`session_system_update_outbox:${row.workspaceId}:${row.id}`,
|
|
41768
|
+
),
|
|
40621
41769
|
};
|
|
40622
41770
|
});
|
|
40623
41771
|
}
|
|
@@ -40699,6 +41847,7 @@ export type AddSessionSystemUpdateInput = {
|
|
|
40699
41847
|
dedupeKey: string;
|
|
40700
41848
|
summary: string;
|
|
40701
41849
|
lineage?: Record<string, unknown>;
|
|
41850
|
+
personalConnectionDelegations?: McpPersonalConnectionDelegation[];
|
|
40702
41851
|
} & SessionSystemUpdateInputVariant;
|
|
40703
41852
|
|
|
40704
41853
|
export type AddSessionSystemUpdateResult =
|
|
@@ -40775,6 +41924,7 @@ export async function addSessionSystemUpdateWithSourceMutation(
|
|
|
40775
41924
|
summary: input.summary,
|
|
40776
41925
|
payload: input.payload,
|
|
40777
41926
|
lineage: input.lineage ?? {},
|
|
41927
|
+
personalConnectionDelegations: input.personalConnectionDelegations ?? [],
|
|
40778
41928
|
state: "pending",
|
|
40779
41929
|
})
|
|
40780
41930
|
.onConflictDoNothing({
|
|
@@ -41067,6 +42217,7 @@ export async function acceptSessionApprovalDecision(
|
|
|
41067
42217
|
accountId: string;
|
|
41068
42218
|
workspaceId: string;
|
|
41069
42219
|
sessionId: string;
|
|
42220
|
+
subjectId: string;
|
|
41070
42221
|
payload: Record<string, unknown>;
|
|
41071
42222
|
clientEventId?: string | null;
|
|
41072
42223
|
},
|
|
@@ -41205,6 +42356,41 @@ export async function acceptSessionApprovalDecision(
|
|
|
41205
42356
|
})
|
|
41206
42357
|
.returning();
|
|
41207
42358
|
if (!event) throw new Error("Failed to append approval decision");
|
|
42359
|
+
const approvalDecision = input.payload.decision;
|
|
42360
|
+
if (approvalDecision === "approve" || approvalDecision === "reject") {
|
|
42361
|
+
const [connectorRequest] = await tx
|
|
42362
|
+
.update(schema.connectorActionRequests)
|
|
42363
|
+
.set({
|
|
42364
|
+
status: approvalDecision === "approve" ? "approved" : "rejected",
|
|
42365
|
+
decision: approvalDecision,
|
|
42366
|
+
decisionBySubjectId: boundedConnectorActionText(
|
|
42367
|
+
input.subjectId,
|
|
42368
|
+
"approval actor",
|
|
42369
|
+
1024,
|
|
42370
|
+
),
|
|
42371
|
+
decisionEventId: event.id,
|
|
42372
|
+
decidedAt: event.occurredAt,
|
|
42373
|
+
updatedAt: new Date(),
|
|
42374
|
+
})
|
|
42375
|
+
.where(
|
|
42376
|
+
and(
|
|
42377
|
+
eq(schema.connectorActionRequests.workspaceId, input.workspaceId),
|
|
42378
|
+
eq(schema.connectorActionRequests.sessionId, input.sessionId),
|
|
42379
|
+
eq(schema.connectorActionRequests.turnId, turn.id),
|
|
42380
|
+
eq(schema.connectorActionRequests.approvalId, approvalId),
|
|
42381
|
+
eq(schema.connectorActionRequests.status, "pending"),
|
|
42382
|
+
),
|
|
42383
|
+
)
|
|
42384
|
+
.returning();
|
|
42385
|
+
if (connectorRequest) {
|
|
42386
|
+
await insertConnectorActionAudit(tx as unknown as Database, {
|
|
42387
|
+
row: connectorRequest,
|
|
42388
|
+
action: "connector.action.approval_decided",
|
|
42389
|
+
subjectId: input.subjectId,
|
|
42390
|
+
extra: { decision: approvalDecision },
|
|
42391
|
+
});
|
|
42392
|
+
}
|
|
42393
|
+
}
|
|
41208
42394
|
await tx
|
|
41209
42395
|
.update(schema.sessions)
|
|
41210
42396
|
.set({
|
|
@@ -41933,6 +43119,10 @@ function mapSessionTurn(row: typeof schema.sessionTurns.$inferSelect): SessionTu
|
|
|
41933
43119
|
row.initiatorContext ?? {},
|
|
41934
43120
|
),
|
|
41935
43121
|
initiatorContext: row.initiatorContext ?? {},
|
|
43122
|
+
personalConnections: parsedPersonalConnectionDelegations(
|
|
43123
|
+
row.personalConnectionDelegations,
|
|
43124
|
+
`session_turns:${row.workspaceId}:${row.sessionId}:${row.id}`,
|
|
43125
|
+
).map(({ serverId, providerDomain }) => ({ serverId, providerDomain })),
|
|
41936
43126
|
cancelledBy: row.cancelledBy,
|
|
41937
43127
|
cancelReason: row.cancelReason,
|
|
41938
43128
|
startedAt: row.startedAt ? row.startedAt.toISOString() : null,
|
|
@@ -41948,6 +43138,10 @@ function mapSessionTurnForExecution(
|
|
|
41948
43138
|
return {
|
|
41949
43139
|
...mapSessionTurn(row),
|
|
41950
43140
|
turnInstructions: row.turnInstructions ?? null,
|
|
43141
|
+
personalConnectionDelegations: parsedPersonalConnectionDelegations(
|
|
43142
|
+
row.personalConnectionDelegations,
|
|
43143
|
+
`session_turns:${row.workspaceId}:${row.sessionId}:${row.id}`,
|
|
43144
|
+
),
|
|
41951
43145
|
};
|
|
41952
43146
|
}
|
|
41953
43147
|
|
|
@@ -42011,6 +43205,16 @@ function mapScheduledTask(row: typeof schema.scheduledTasks.$inferSelect): Sched
|
|
|
42011
43205
|
runMode: row.runMode as ScheduledTaskRunMode,
|
|
42012
43206
|
overlapPolicy: row.overlapPolicy as ScheduledTaskOverlapPolicy,
|
|
42013
43207
|
agentConfig: row.agentConfig as ScheduledTaskAgentConfig,
|
|
43208
|
+
createdBy: initiatorFromStorage(
|
|
43209
|
+
row.createdByKind,
|
|
43210
|
+
row.createdBySubjectId,
|
|
43211
|
+
row.createdByContext as TurnInitiatorContext,
|
|
43212
|
+
),
|
|
43213
|
+
createdByContext: row.createdByContext as TurnInitiatorContext,
|
|
43214
|
+
personalConnections: parsedPersonalConnectionDelegations(
|
|
43215
|
+
row.personalConnectionDelegations,
|
|
43216
|
+
`scheduled_tasks:${row.workspaceId}:${row.id}`,
|
|
43217
|
+
).map(({ serverId, providerDomain }) => ({ serverId, providerDomain })),
|
|
42014
43218
|
reusableSessionId: row.reusableSessionId,
|
|
42015
43219
|
variableSetId: row.variableSetId,
|
|
42016
43220
|
environmentId: row.variableSetId,
|