@opengeni/worker-bundle 1.0.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/activities/agent-turn/codex.d.ts +1 -1
- package/dist/activities/agent-turn/compaction-prep.d.ts +1 -5
- package/dist/activities/agent-turn/errors.d.ts +31 -5
- package/dist/activities/agent-turn/finalization-monitor.d.ts +17 -0
- package/dist/activities/agent-turn/finalization.d.ts +1 -1
- package/dist/activities/agent-turn/governance-model.d.ts +0 -2
- package/dist/activities/agent-turn/quiescence.d.ts +1 -0
- package/dist/activities/agent-turn/recovery-warning.d.ts +8 -0
- package/dist/activities/agent-turn/run-credentials.d.ts +0 -1
- package/dist/activities/agent-turn/sandbox-establish.d.ts +1 -1
- package/dist/activities/agent-turn/sandbox-route.d.ts +0 -20
- package/dist/activities/agent-turn/sandbox-runtime.d.ts +1 -1
- package/dist/activities/agent-turn/skill-remove.d.ts +14 -0
- package/dist/activities/agent-turn/skill-search.d.ts +1 -1
- package/dist/activities/agent-turn/stream-attempt.d.ts +1 -0
- package/dist/activities/agent-turn/tool-environment.d.ts +0 -4
- package/dist/activities/agent-turn/turn-context.d.ts +2 -0
- package/dist/activities/agent-turn/turn-sandbox-access.d.ts +3 -0
- package/dist/activities/codex-capacity.d.ts +6 -1
- package/dist/activities/codex-rotation.d.ts +1 -1
- package/dist/activities/context-compaction.d.ts +7 -1
- package/dist/activities/mcp-account-routes.d.ts +22 -0
- package/dist/activities/mcp-credentials.d.ts +12 -13
- package/dist/activities/mcp-operation-authority.d.ts +0 -5
- package/dist/activities/rig-verification.d.ts +2 -3
- package/dist/activities/sandbox-images.d.ts +1 -1
- package/dist/activities/sandbox-lease.d.ts +4 -3
- package/dist/activities/session-state.d.ts +57 -2
- package/dist/activities/types.d.ts +12 -3
- package/dist/{activities-control-HMFGSKC5.js → activities-control-BOLIY3H4.js} +366 -187
- package/dist/activities-control-BOLIY3H4.js.map +1 -0
- package/dist/activities-control.d.ts +110 -2
- package/dist/{activities-turn-AP67JADS.js → activities-turn-2B3WMHBB.js} +767 -471
- package/dist/activities-turn-2B3WMHBB.js.map +1 -0
- package/dist/activities.d.ts +110 -2
- package/dist/artifact-materializer-entry.js +5 -2
- package/dist/artifact-materializer-entry.js.map +1 -1
- package/dist/artifact-outbox-entry.js +2 -2
- package/dist/artifact-outbox-entry.js.map +1 -1
- package/dist/{chunk-A4XQWVZK.js → chunk-I7HKKMUJ.js} +77 -1
- package/dist/chunk-I7HKKMUJ.js.map +1 -0
- package/dist/{chunk-CYITU5UH.js → chunk-TVEE2UUC.js} +275 -146
- package/dist/chunk-TVEE2UUC.js.map +1 -0
- package/dist/editable-artifact-materializer-service.d.ts +2 -2
- package/dist/editable-artifact-outbox-service.d.ts +2 -2
- package/dist/index.d.ts +55 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/observability-metrics.d.ts +11 -3
- package/dist/retained-process-diagnostics.d.ts +4 -0
- package/dist/sandbox-resume.d.ts +2 -12
- package/dist/sandbox-routing.d.ts +5 -3
- package/dist/sandbox-snapshot-diagnostics.d.ts +20 -0
- package/dist/warm-capture-holder.d.ts +3 -0
- package/dist/workflow-bundle.js +51 -11
- package/package.json +15 -15
- package/src/activities/agent-turn/agent-build.ts +42 -5
- package/src/activities/agent-turn/claim.ts +12 -0
- package/src/activities/agent-turn/codex-capacity.ts +26 -4
- package/src/activities/agent-turn/codex.ts +6 -2
- package/src/activities/agent-turn/compaction-prep.ts +53 -72
- package/src/activities/agent-turn/errors.ts +98 -10
- package/src/activities/agent-turn/failure-settlement.ts +49 -28
- package/src/activities/agent-turn/finalization-monitor.ts +121 -0
- package/src/activities/agent-turn/finalization.ts +79 -21
- package/src/activities/agent-turn/governance-model.ts +15 -19
- package/src/activities/agent-turn/history.ts +24 -10
- package/src/activities/agent-turn/quiescence.ts +3 -0
- package/src/activities/agent-turn/recovery-warning.ts +18 -0
- package/src/activities/agent-turn/run-credentials.ts +3 -7
- package/src/activities/agent-turn/run.ts +68 -52
- package/src/activities/agent-turn/sandbox-establish.ts +10 -2
- package/src/activities/agent-turn/sandbox-route.ts +0 -20
- package/src/activities/agent-turn/sandbox-runtime.ts +40 -7
- package/src/activities/agent-turn/skill-remove.ts +46 -0
- package/src/activities/agent-turn/skill-save.ts +1 -1
- package/src/activities/agent-turn/skill-search.ts +1 -1
- package/src/activities/agent-turn/skill-selection.ts +2 -0
- package/src/activities/agent-turn/skill-tools.ts +10 -5
- package/src/activities/agent-turn/stream-attempt.ts +38 -15
- package/src/activities/agent-turn/tool-environment.ts +44 -29
- package/src/activities/agent-turn/turn-context.ts +3 -0
- package/src/activities/agent-turn/turn-sandbox-access.ts +7 -1
- package/src/activities/capabilities.ts +17 -2
- package/src/activities/codex-capacity.ts +38 -9
- package/src/activities/codex-rotation.ts +18 -5
- package/src/activities/context-compaction.ts +26 -4
- package/src/activities/goals.ts +1 -0
- package/src/activities/google-drive-publication.ts +1 -3
- package/src/activities/mcp-account-routes.ts +139 -0
- package/src/activities/mcp-credentials.ts +83 -194
- package/src/activities/mcp-operation-authority.ts +16 -41
- package/src/activities/parent-wake.ts +8 -0
- package/src/activities/rig-verification.ts +38 -37
- package/src/activities/run-input.ts +25 -16
- package/src/activities/sandbox-images.ts +0 -1
- package/src/activities/sandbox-lease.ts +177 -44
- package/src/activities/scheduled-tasks.ts +118 -93
- package/src/activities/session-state.ts +83 -3
- package/src/activities/types.ts +18 -2
- package/src/activities/workflow-wake.ts +2 -17
- package/src/editable-artifact-materializer-service.ts +7 -3
- package/src/editable-artifact-outbox-service.ts +4 -4
- package/src/github-rest-mcp.ts +0 -1
- package/src/index.ts +2 -1
- package/src/observability-metrics.ts +105 -2
- package/src/personal-github-git-credentials.ts +0 -1
- package/src/pr-review-credentials.ts +0 -2
- package/src/retained-process-diagnostics.ts +50 -0
- package/src/sandbox-resume.ts +58 -73
- package/src/sandbox-routing.ts +44 -19
- package/src/sandbox-snapshot-diagnostics.ts +112 -0
- package/src/warm-capture-holder.ts +15 -0
- package/src/workflows/session.ts +64 -2
- package/dist/activities/agent-turn/knowledge-sources.d.ts +0 -20
- package/dist/activities/packs.d.ts +0 -55
- package/dist/activities-control-HMFGSKC5.js.map +0 -1
- package/dist/activities-turn-AP67JADS.js.map +0 -1
- package/dist/chunk-A4XQWVZK.js.map +0 -1
- package/dist/chunk-CYITU5UH.js.map +0 -1
- package/src/activities/agent-turn/knowledge-sources.ts +0 -86
- package/src/activities/packs.ts +0 -166
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readReasoningConfiguration } from "@opengeni/codex";
|
|
1
2
|
import {
|
|
2
3
|
applyContextCompaction,
|
|
3
4
|
getActiveSessionHistoryItemsPaged,
|
|
@@ -21,6 +22,7 @@ import {
|
|
|
21
22
|
sanitizeHistoryItemsForModel,
|
|
22
23
|
summarizeForCompaction,
|
|
23
24
|
type CompactionItem,
|
|
25
|
+
type CompactionProviderRejection,
|
|
24
26
|
} from "@opengeni/runtime";
|
|
25
27
|
import { contextInputBudgetTokens, type Settings } from "@opengeni/config";
|
|
26
28
|
import type { SessionEvent } from "@opengeni/contracts";
|
|
@@ -260,6 +262,12 @@ export async function settleFailedContextCompactionLandmark(
|
|
|
260
262
|
options: {
|
|
261
263
|
clearRequestedCompaction?: boolean;
|
|
262
264
|
publishLiveEvents?: (events: SessionEvent[]) => Promise<void>;
|
|
265
|
+
/**
|
|
266
|
+
* Closed identifier record of a definitive provider rejection. Carried on
|
|
267
|
+
* the visible `compaction.skipped` landmark so the timeline can name the
|
|
268
|
+
* rejected field instead of offering a retry that cannot succeed.
|
|
269
|
+
*/
|
|
270
|
+
providerRejection?: CompactionProviderRejection | null;
|
|
263
271
|
} = {},
|
|
264
272
|
): Promise<Extract<MaybeCompactResult, { compacted: false }>> {
|
|
265
273
|
const settled = await settleSkippedAfterStart(db, scope, options, "summarization_failed");
|
|
@@ -287,6 +295,7 @@ async function settleSkippedAfterStart(
|
|
|
287
295
|
},
|
|
288
296
|
options: {
|
|
289
297
|
clearRequestedCompaction?: boolean;
|
|
298
|
+
providerRejection?: CompactionProviderRejection | null;
|
|
290
299
|
},
|
|
291
300
|
reason:
|
|
292
301
|
| "no_history"
|
|
@@ -304,6 +313,7 @@ async function settleSkippedAfterStart(
|
|
|
304
313
|
// an operator `/compact` flag — auto/overflow never set one.
|
|
305
314
|
requirePendingRequest: false,
|
|
306
315
|
clearRequestedCompaction,
|
|
316
|
+
...(options.providerRejection ? { providerRejection: options.providerRejection } : {}),
|
|
307
317
|
});
|
|
308
318
|
if (!skipped.recorded) {
|
|
309
319
|
throw new TurnAttemptFencedError(
|
|
@@ -370,7 +380,10 @@ async function compactContextRemoteV2(
|
|
|
370
380
|
);
|
|
371
381
|
const estimatedTokensAfter = estimateTokens(await projectForWire(replacementHistory));
|
|
372
382
|
const replacementFingerprint = compactionReplacementFingerprint(replacementHistory);
|
|
373
|
-
const
|
|
383
|
+
const summaryIndex =
|
|
384
|
+
replacementHistory.length -
|
|
385
|
+
(readReasoningConfiguration(replacementHistory.at(-1) ?? {}) ? 2 : 1);
|
|
386
|
+
const tailItem = replacementHistory[summaryIndex];
|
|
374
387
|
if (!tailItem) {
|
|
375
388
|
throw new EmptyCompactionSummaryError({
|
|
376
389
|
stage: "remote_v2_replacement",
|
|
@@ -384,7 +397,10 @@ async function compactContextRemoteV2(
|
|
|
384
397
|
turnId: scope.turnId,
|
|
385
398
|
expectedExecutionGeneration: scope.executionGeneration,
|
|
386
399
|
expectedAttemptId: scope.attemptId,
|
|
387
|
-
replacementItems: replacementHistory.slice(0,
|
|
400
|
+
replacementItems: replacementHistory.slice(0, summaryIndex),
|
|
401
|
+
...(replacementHistory.length > summaryIndex + 1
|
|
402
|
+
? { trailingItems: replacementHistory.slice(summaryIndex + 1) }
|
|
403
|
+
: {}),
|
|
388
404
|
summaryItem: tailItem as Record<string, unknown>,
|
|
389
405
|
...(options.clearRequestedCompaction ? { clearRequestedCompaction: true } : {}),
|
|
390
406
|
eventPayload: {
|
|
@@ -447,7 +463,10 @@ async function compactContextPortable(
|
|
|
447
463
|
const estimatedTokensAfter = estimateTokens(await projectForWire(replacementHistory));
|
|
448
464
|
const replacementFingerprint = compactionReplacementFingerprint(replacementHistory);
|
|
449
465
|
const previousReplacementFingerprint = latestCompactionReplacementFingerprint(canonicalItems);
|
|
450
|
-
const
|
|
466
|
+
const summaryIndex =
|
|
467
|
+
replacementHistory.length -
|
|
468
|
+
(readReasoningConfiguration(replacementHistory.at(-1) ?? {}) ? 2 : 1);
|
|
469
|
+
const summaryItem = replacementHistory[summaryIndex];
|
|
451
470
|
if (!summaryItem) {
|
|
452
471
|
// Started already fanout; settle visibly so the landmark cannot stick on
|
|
453
472
|
// "Compacting…". This is not an operator-request clear path.
|
|
@@ -466,7 +485,10 @@ async function compactContextPortable(
|
|
|
466
485
|
turnId: scope.turnId,
|
|
467
486
|
expectedExecutionGeneration: scope.executionGeneration,
|
|
468
487
|
expectedAttemptId: scope.attemptId,
|
|
469
|
-
replacementItems: replacementHistory.slice(0,
|
|
488
|
+
replacementItems: replacementHistory.slice(0, summaryIndex),
|
|
489
|
+
...(replacementHistory.length > summaryIndex + 1
|
|
490
|
+
? { trailingItems: replacementHistory.slice(summaryIndex + 1) }
|
|
491
|
+
: {}),
|
|
470
492
|
summaryItem: summaryItem as Record<string, unknown>,
|
|
471
493
|
...(options.clearRequestedCompaction ? { clearRequestedCompaction: true } : {}),
|
|
472
494
|
eventPayload: {
|
package/src/activities/goals.ts
CHANGED
|
@@ -308,6 +308,7 @@ export function goalContinuationPrompt(
|
|
|
308
308
|
"- For every explicit requirement, named artifact, command, test, gate, invariant, and deliverable, identify and inspect the authoritative evidence that would prove it.",
|
|
309
309
|
"- Match verification scope to requirement scope. Treat uncertain, indirect, incomplete, or missing evidence as not achieved and continue working.",
|
|
310
310
|
"- The audit must prove completion, not merely fail to find obvious remaining work.",
|
|
311
|
+
"- For user-facing report deliverables, including reports produced during another task, follow the Documents Skill: create the durable native document first, inspect its relevant final head after the last edit, and provide the returned artifact reference. Declare report requirements through the available goal tools before authoring and satisfy every persisted report requirement with verified artifact delivery evidence before completion. Sandbox paths and raw file IDs do not prove report delivery. If artifact tooling or access is unavailable, keep that deliverable incomplete and state the blocker; never invent proof or silently substitute a local report. Ordinary chat answers, short progress updates, source-code links, and explicitly requested local-file work remain outside this report contract.",
|
|
311
312
|
"",
|
|
312
313
|
"Do not rely on intent, partial progress, memory of earlier work, or a plausible final answer as proof of completion. Call opengeni__goal_complete with concrete evidence only when the full objective is actually achieved and no required work remains.",
|
|
313
314
|
"",
|
|
@@ -90,9 +90,7 @@ export async function resolveGoogleDrivePublicationTarget(
|
|
|
90
90
|
);
|
|
91
91
|
if (frozen.length !== 1) return null;
|
|
92
92
|
const delegation = frozen[0]!;
|
|
93
|
-
const activated = delegation.userDelegation !== undefined;
|
|
94
93
|
if (
|
|
95
|
-
!activated &&
|
|
96
94
|
!(await ports.getMembership(db, {
|
|
97
95
|
accountId: workspace.accountId,
|
|
98
96
|
workspaceId,
|
|
@@ -101,7 +99,7 @@ export async function resolveGoogleDrivePublicationTarget(
|
|
|
101
99
|
) {
|
|
102
100
|
return null;
|
|
103
101
|
}
|
|
104
|
-
const originWorkspaceId =
|
|
102
|
+
const originWorkspaceId = delegation.originWorkspaceId;
|
|
105
103
|
if (!originWorkspaceId) return null;
|
|
106
104
|
const connection = await ports.getConnection(
|
|
107
105
|
db,
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { Settings } from "@opengeni/config";
|
|
2
|
+
import type {
|
|
3
|
+
McpConnectionAccountBinding,
|
|
4
|
+
ToolRef,
|
|
5
|
+
ToolAuthNeededPayload,
|
|
6
|
+
} from "@opengeni/contracts";
|
|
7
|
+
import type { ApiIntegrationRuntime } from "@opengeni/db";
|
|
8
|
+
|
|
9
|
+
export function accountRouteAuthNeededPayload(
|
|
10
|
+
payload: ToolAuthNeededPayload,
|
|
11
|
+
bindings: readonly McpConnectionAccountBinding[] | null | undefined,
|
|
12
|
+
): ToolAuthNeededPayload {
|
|
13
|
+
const binding = bindings?.find((candidate) => candidate.serverId === payload.serverId);
|
|
14
|
+
return binding
|
|
15
|
+
? {
|
|
16
|
+
...payload,
|
|
17
|
+
canonicalServerId: binding.canonicalServerId,
|
|
18
|
+
connectionSubjectScope: binding.subjectScope,
|
|
19
|
+
}
|
|
20
|
+
: payload;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Expand only after canonical tool-policy admission. Aliases are execution
|
|
24
|
+
* identities, never an alternate way to gain admission to a connector. */
|
|
25
|
+
export function expandMcpAccountRoutes(input: {
|
|
26
|
+
settings: Settings;
|
|
27
|
+
tools: ToolRef[];
|
|
28
|
+
bindings: readonly McpConnectionAccountBinding[] | null | undefined;
|
|
29
|
+
}): { settings: Settings; tools: ToolRef[]; accountLabels: ReadonlyMap<string, string> } {
|
|
30
|
+
// Only historical work has no binding snapshot. A present empty snapshot is
|
|
31
|
+
// an explicit lack of account authority, never a request for current defaults.
|
|
32
|
+
if (input.bindings == null) {
|
|
33
|
+
return { settings: input.settings, tools: input.tools, accountLabels: new Map() };
|
|
34
|
+
}
|
|
35
|
+
const configured = new Map(input.settings.mcpServers.map((server) => [server.id, server]));
|
|
36
|
+
const byCanonical = new Map<string, McpConnectionAccountBinding[]>();
|
|
37
|
+
const routes = new Set<string>();
|
|
38
|
+
const accounts = new Set<string>();
|
|
39
|
+
for (const binding of input.bindings) {
|
|
40
|
+
const account = JSON.stringify([binding.canonicalServerId, binding.connectionId]);
|
|
41
|
+
if (
|
|
42
|
+
routes.has(binding.serverId) ||
|
|
43
|
+
accounts.has(account) ||
|
|
44
|
+
configured.has(binding.serverId) ||
|
|
45
|
+
(binding.subjectScope === "subject") !== (binding.ownerSubjectId !== null) ||
|
|
46
|
+
binding.connectionRef.authoritySource === "host" ||
|
|
47
|
+
binding.connectionRef.connectionId !== binding.connectionId ||
|
|
48
|
+
binding.connectionRef.subjectScope !== binding.subjectScope ||
|
|
49
|
+
binding.connectionRef.providerDomain !== binding.providerDomain ||
|
|
50
|
+
binding.connectionRef.kind !== binding.kind
|
|
51
|
+
) {
|
|
52
|
+
throw new Error("Invalid accepted MCP account route");
|
|
53
|
+
}
|
|
54
|
+
routes.add(binding.serverId);
|
|
55
|
+
accounts.add(account);
|
|
56
|
+
const siblings = byCanonical.get(binding.canonicalServerId) ?? [];
|
|
57
|
+
siblings.push(binding);
|
|
58
|
+
byCanonical.set(binding.canonicalServerId, siblings);
|
|
59
|
+
}
|
|
60
|
+
const servers: Settings["mcpServers"] = [];
|
|
61
|
+
const tools: ToolRef[] = [];
|
|
62
|
+
const accountLabels = new Map<string, string>();
|
|
63
|
+
for (const tool of input.tools) {
|
|
64
|
+
const bindings = byCanonical.get(tool.id);
|
|
65
|
+
if (!bindings) {
|
|
66
|
+
if (!configured.get(tool.id)?.connectionRef) tools.push(tool);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const canonical = configured.get(tool.id);
|
|
70
|
+
if (!canonical?.connectionRef) {
|
|
71
|
+
throw new Error("Accepted MCP account route has no canonical connection configuration");
|
|
72
|
+
}
|
|
73
|
+
for (const binding of bindings) {
|
|
74
|
+
if (
|
|
75
|
+
canonical.connectionRef.authoritySource === "host" ||
|
|
76
|
+
canonical.connectionRef.providerDomain !== binding.providerDomain ||
|
|
77
|
+
(canonical.connectionRef.kind && canonical.connectionRef.kind !== binding.kind)
|
|
78
|
+
) {
|
|
79
|
+
throw new Error("Accepted MCP account route does not match canonical provider");
|
|
80
|
+
}
|
|
81
|
+
// Static headers can belong to the formerly selected account. Credentials
|
|
82
|
+
// for account-qualified routes come exclusively from the native resolver.
|
|
83
|
+
const { headers: _headers, ...config } = canonical;
|
|
84
|
+
const label = `${canonical.name ?? canonical.id} — ${binding.subjectScope === "subject" ? "Personal" : "Workspace"}: ${binding.accountLabel}`;
|
|
85
|
+
servers.push({
|
|
86
|
+
...config,
|
|
87
|
+
id: binding.serverId,
|
|
88
|
+
name: label,
|
|
89
|
+
connectionRef: structuredClone(binding.connectionRef),
|
|
90
|
+
});
|
|
91
|
+
tools.push({ ...tool, id: binding.serverId });
|
|
92
|
+
accountLabels.set(binding.serverId, label);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
settings: {
|
|
97
|
+
...input.settings,
|
|
98
|
+
mcpServers: [
|
|
99
|
+
...input.settings.mcpServers.filter((server) => !server.connectionRef),
|
|
100
|
+
...servers,
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
tools,
|
|
104
|
+
accountLabels,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Each adapter closes over its own immutable credential ref and authority
|
|
109
|
+
* generation. Sharing a provider transport never shares a credential resolver. */
|
|
110
|
+
export function expandApiIntegrationAccountRoutes(input: {
|
|
111
|
+
integrations: readonly ApiIntegrationRuntime[];
|
|
112
|
+
bindings: readonly McpConnectionAccountBinding[] | null | undefined;
|
|
113
|
+
tools: readonly ToolRef[];
|
|
114
|
+
}): ApiIntegrationRuntime[] {
|
|
115
|
+
const selected = new Set(input.tools.map((tool) => tool.id));
|
|
116
|
+
if (input.bindings == null)
|
|
117
|
+
return input.integrations.filter((item) => selected.has(item.serverId));
|
|
118
|
+
const acceptedBindings = input.bindings;
|
|
119
|
+
return input.integrations.flatMap((integration) => {
|
|
120
|
+
const bindings = acceptedBindings.filter(
|
|
121
|
+
(binding) => binding.canonicalServerId === integration.serverId,
|
|
122
|
+
);
|
|
123
|
+
if (bindings.length === 0)
|
|
124
|
+
return !integration.connectionRef && selected.has(integration.serverId) ? [integration] : [];
|
|
125
|
+
return bindings
|
|
126
|
+
.filter((binding) => selected.has(binding.serverId))
|
|
127
|
+
.map((binding) => {
|
|
128
|
+
if (binding.connectionAuthorityGeneration === undefined) {
|
|
129
|
+
throw new Error("Accepted API integration account route has no authority generation");
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
...integration,
|
|
133
|
+
serverId: binding.serverId,
|
|
134
|
+
connectionRef: structuredClone(binding.connectionRef),
|
|
135
|
+
connectionAuthorityGeneration: binding.connectionAuthorityGeneration,
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
}
|
|
@@ -1,47 +1,46 @@
|
|
|
1
1
|
import type { Settings } from "@opengeni/config";
|
|
2
|
-
import
|
|
3
|
-
import type { HostMcpAcceptedAuthority } from "@opengeni/contracts/host-mcp-bindings";
|
|
2
|
+
import { stableJson } from "@opengeni/contracts";
|
|
4
3
|
import {
|
|
5
4
|
buildConnectionTokenResolver,
|
|
6
|
-
buildHostConnectionTokenResolver,
|
|
7
|
-
getSessionTurnForAttempt,
|
|
8
|
-
authorizeDirectHostMcpUse,
|
|
9
|
-
resolveAcceptedHostMcpBinding,
|
|
10
5
|
resolveAcceptedConnectionUse,
|
|
11
|
-
sessionTenancyProductActivated,
|
|
12
6
|
type Database,
|
|
13
7
|
type ResolveConnectionCredentialInput,
|
|
14
8
|
type ResolveConnectionCredentialResult,
|
|
15
9
|
type SessionTurnForExecution,
|
|
16
10
|
} from "@opengeni/db";
|
|
17
|
-
import { recordTenancyCompatibilityLaneUse, type Observability } from "@opengeni/observability";
|
|
18
11
|
import { mcpOperationAuthorityDigest } from "./mcp-operation-authority";
|
|
19
12
|
|
|
20
|
-
|
|
21
|
-
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
|
|
22
|
-
|
|
23
|
-
export function connectionTokenResolverForTurn(input: {
|
|
13
|
+
type TurnConnectionInput = {
|
|
24
14
|
db: Database;
|
|
25
15
|
settings: Settings;
|
|
26
|
-
connectionCredentials?: ConnectionCredentialsPort | null;
|
|
27
16
|
accountId: string;
|
|
28
17
|
workspaceId: string;
|
|
29
18
|
sessionId: string;
|
|
30
|
-
rootSessionId: string;
|
|
31
19
|
attemptId: string;
|
|
32
20
|
turn: SessionTurnForExecution;
|
|
21
|
+
/** Policy identities before account expansion; never used to select credentials. */
|
|
22
|
+
canonicalMcpServerIds?: readonly string[];
|
|
33
23
|
authorizeAcceptedUse?: typeof resolveAcceptedConnectionUse;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type CredentialResolver = (
|
|
27
|
+
request: ResolveConnectionCredentialInput,
|
|
28
|
+
) => Promise<ResolveConnectionCredentialResult>;
|
|
29
|
+
|
|
30
|
+
export function connectionTokenResolverForTurn(input: TurnConnectionInput): CredentialResolver {
|
|
31
|
+
return bindNativeConnectionCredentialsToTurn(
|
|
32
|
+
input,
|
|
33
|
+
buildConnectionTokenResolver(input.db, input.settings),
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Bind the ordinary credential resolver to one immutable accepted turn.
|
|
38
|
+
* Credential acquisition and refresh stay inside the native connection engine;
|
|
39
|
+
* this layer adds the exact execution context and physical-request checks. */
|
|
40
|
+
export function bindNativeConnectionCredentialsToTurn(
|
|
41
|
+
input: TurnConnectionInput,
|
|
42
|
+
nativeResolver: CredentialResolver,
|
|
43
|
+
): CredentialResolver {
|
|
45
44
|
const recoveryEnabled = (serverId: string) =>
|
|
46
45
|
input.settings.mcpServers.some(
|
|
47
46
|
(server) =>
|
|
@@ -50,84 +49,14 @@ export function connectionTokenResolverForTurn(input: {
|
|
|
50
49
|
server.operationRecovery !== undefined &&
|
|
51
50
|
Object.keys(server.operationRecovery ?? {}).length > 0,
|
|
52
51
|
);
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
let authorizedSnapshot: HostMcpAcceptedAuthority | undefined;
|
|
58
|
-
const wantsDigest = recoveryEnabled(credentialRequest.serverId);
|
|
59
|
-
const resolver = buildHostConnectionTokenResolver(hostResolver, {
|
|
60
|
-
accountId: input.accountId,
|
|
61
|
-
workspaceId: input.workspaceId,
|
|
62
|
-
sessionId: input.sessionId,
|
|
63
|
-
rootSessionId: input.rootSessionId,
|
|
64
|
-
turnId: input.turn.id,
|
|
65
|
-
attemptId: input.attemptId,
|
|
66
|
-
executionGeneration: input.turn.executionGeneration,
|
|
67
|
-
initiator: input.turn.initiator,
|
|
68
|
-
initiatorContext: input.turn.initiatorContext,
|
|
69
|
-
surface: "model",
|
|
70
|
-
resolveAcceptedBinding: (request) => resolveAcceptedHostMcpBinding(hostDb, request),
|
|
71
|
-
// Only immutable captured direct-turn authority is currently supported.
|
|
72
|
-
// Direct, scheduled and exact causal/child snapshots are validated;
|
|
73
|
-
// no authority follows merely from a binding or creator identity.
|
|
74
|
-
authorizeDurableBinding: (request) =>
|
|
75
|
-
authorizeDirectHostMcpUse(
|
|
76
|
-
hostDb,
|
|
77
|
-
request,
|
|
78
|
-
wantsDigest
|
|
79
|
-
? (snapshot) => {
|
|
80
|
-
authorizedSnapshot = snapshot;
|
|
81
|
-
}
|
|
82
|
-
: undefined,
|
|
83
|
-
),
|
|
84
|
-
authorizeExecution: async (request) => {
|
|
85
|
-
// Native UUID references already pass the accepted-use boundary below.
|
|
86
|
-
// Legacy opaque host IDs retain their compatibility path, but not a
|
|
87
|
-
// right to execute after their accepted attempt has stopped.
|
|
88
|
-
if (
|
|
89
|
-
request.connectionRef.authoritySource !== "host" &&
|
|
90
|
-
(!request.connectionRef.connectionId ||
|
|
91
|
-
OPENGENI_CONNECTION_ID_PATTERN.test(request.connectionRef.connectionId))
|
|
92
|
-
)
|
|
93
|
-
return true;
|
|
94
|
-
if (!request.attemptId) return false;
|
|
95
|
-
const current = await readHostTurn(
|
|
96
|
-
hostDb,
|
|
97
|
-
request.workspaceId,
|
|
98
|
-
request.sessionId,
|
|
99
|
-
request.attemptId,
|
|
100
|
-
);
|
|
101
|
-
return (
|
|
102
|
-
current !== null &&
|
|
103
|
-
current.id === request.turnId &&
|
|
104
|
-
current.executionGeneration === request.executionGeneration
|
|
105
|
-
);
|
|
106
|
-
},
|
|
107
|
-
});
|
|
108
|
-
const result = await resolver(credentialRequest);
|
|
109
|
-
return result.status === "ok" && authorizedSnapshot
|
|
110
|
-
? {
|
|
111
|
-
...result,
|
|
112
|
-
operationAuthorityDigest: mcpOperationAuthorityDigest(credentialRequest, {
|
|
113
|
-
host: authorizedSnapshot,
|
|
114
|
-
}),
|
|
115
|
-
}
|
|
116
|
-
: result;
|
|
117
|
-
}
|
|
118
|
-
: nativeResolver;
|
|
119
|
-
const baseResolver = (request: ResolveConnectionCredentialInput) =>
|
|
120
|
-
input.connectionCredentials?.mcpAuthoritySource === "host" &&
|
|
121
|
-
request.connectionRef.authoritySource !== "host"
|
|
122
|
-
? nativeResolver(request)
|
|
123
|
-
: configuredResolver(request);
|
|
52
|
+
const canonicalMcpServerIds = new Set(
|
|
53
|
+
input.canonicalMcpServerIds ??
|
|
54
|
+
input.settings.mcpServers.filter((server) => server.connectionRef).map((server) => server.id),
|
|
55
|
+
);
|
|
124
56
|
return async (request) => {
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
// authority or PostgreSQL UUID lookup; the bound host authorizes the exact
|
|
128
|
-
// immutable turn context on every credential request.
|
|
57
|
+
// Superseded host references must never fall through to native lookup,
|
|
58
|
+
// even when their opaque identifier happens to be a valid native UUID.
|
|
129
59
|
if (request.connectionRef.authoritySource === "host") {
|
|
130
|
-
if (hostResolver) return await baseResolver(request);
|
|
131
60
|
return {
|
|
132
61
|
status: "auth_needed",
|
|
133
62
|
reason: "unsupported_auth",
|
|
@@ -144,34 +73,31 @@ export function connectionTokenResolverForTurn(input: {
|
|
|
144
73
|
: {}),
|
|
145
74
|
};
|
|
146
75
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
// so UUID-shaped host ids are unambiguous.
|
|
76
|
+
const accountBindings = input.turn.mcpAccountBindings ?? [];
|
|
77
|
+
const accountBinding = accountBindings.find((binding) => binding.serverId === request.serverId);
|
|
78
|
+
// Canonical policy identity cannot be invoked as a credential fallback once
|
|
79
|
+
// this turn selected explicit account routes for that connector.
|
|
152
80
|
if (
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
!
|
|
81
|
+
(input.turn.mcpAccountBindings != null &&
|
|
82
|
+
!accountBinding &&
|
|
83
|
+
canonicalMcpServerIds.has(request.serverId)) ||
|
|
84
|
+
(!accountBinding &&
|
|
85
|
+
accountBindings.some((binding) => binding.canonicalServerId === request.serverId)) ||
|
|
86
|
+
(accountBinding &&
|
|
87
|
+
(request.connectionRef.connectionId !== accountBinding.connectionId ||
|
|
88
|
+
request.connectionRef.providerDomain !== accountBinding.providerDomain ||
|
|
89
|
+
request.connectionRef.kind !== accountBinding.kind ||
|
|
90
|
+
request.connectionRef.subjectScope !== accountBinding.subjectScope ||
|
|
91
|
+
stableJson(request.connectionRef) !== stableJson(accountBinding.connectionRef) ||
|
|
92
|
+
(accountBinding.subjectScope === "subject" &&
|
|
93
|
+
request.subjectId !== accountBinding.ownerSubjectId)))
|
|
157
94
|
) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
providerDomain: request.connectionRef.providerDomain,
|
|
165
|
-
authoritySource: "host",
|
|
166
|
-
...(request.connectionRef.provider ? { provider: request.connectionRef.provider } : {}),
|
|
167
|
-
connectionId: request.connectionRef.connectionId,
|
|
168
|
-
...(request.connectionRef.scopes ? { scopes: request.connectionRef.scopes } : {}),
|
|
169
|
-
...(request.connectionRef.resource ? { resource: request.connectionRef.resource } : {}),
|
|
170
|
-
...(request.connectionRef.selectedResources
|
|
171
|
-
? { selectedResources: request.connectionRef.selectedResources }
|
|
172
|
-
: {}),
|
|
173
|
-
};
|
|
174
|
-
}
|
|
95
|
+
return {
|
|
96
|
+
status: "auth_needed",
|
|
97
|
+
reason: "personal_authority_unavailable",
|
|
98
|
+
providerDomain: request.connectionRef.providerDomain,
|
|
99
|
+
...(request.connectionRef.provider ? { provider: request.connectionRef.provider } : {}),
|
|
100
|
+
};
|
|
175
101
|
}
|
|
176
102
|
const acceptedDelegation = input.turn.personalConnectionDelegations.find(
|
|
177
103
|
(delegation) =>
|
|
@@ -191,9 +117,7 @@ export function connectionTokenResolverForTurn(input: {
|
|
|
191
117
|
const subjectScope: "subject" | "workspace" =
|
|
192
118
|
request.connectionRef.subjectScope === "subject" ? "subject" : "workspace";
|
|
193
119
|
// Every subject-scoped request must match an exact connection frozen on the
|
|
194
|
-
// accepted turn.
|
|
195
|
-
// lack a userDelegation: the DB resolver denies those rows because only a
|
|
196
|
-
// true legacy_user connection is eligible for bounded compatibility.
|
|
120
|
+
// accepted turn. The database then checks its immutable sender snapshot.
|
|
197
121
|
if (
|
|
198
122
|
subjectScope === "subject" &&
|
|
199
123
|
(!acceptedDelegation || !request.connectionRef.connectionId)
|
|
@@ -205,31 +129,15 @@ export function connectionTokenResolverForTurn(input: {
|
|
|
205
129
|
...(request.connectionRef.provider ? { provider: request.connectionRef.provider } : {}),
|
|
206
130
|
};
|
|
207
131
|
}
|
|
208
|
-
// Workspace
|
|
209
|
-
//
|
|
210
|
-
// inside the canonical lifecycle fences and every use leaves an idempotent
|
|
211
|
-
// audit fact. A ref with no connection id is the bounded pre-snapshot
|
|
212
|
-
// legacy path - it cannot be authorized by exact identity, so it keeps the
|
|
213
|
-
// unprivileged resolution the old short-circuit used.
|
|
132
|
+
// Workspace connections also need an exact identity for accepted-use
|
|
133
|
+
// validation and attribution. Never rediscover a credential by domain here.
|
|
214
134
|
if (subjectScope === "workspace" && !request.connectionRef.connectionId) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
)
|
|
220
|
-
|
|
221
|
-
return {
|
|
222
|
-
status: "auth_needed",
|
|
223
|
-
reason: "missing_connection",
|
|
224
|
-
providerDomain: request.connectionRef.providerDomain,
|
|
225
|
-
...(request.connectionRef.provider ? { provider: request.connectionRef.provider } : {}),
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
// This lane writes no `connection_use_audit_facts` row, so this counter is
|
|
229
|
-
// the only evidence it was taken. Lane name only - never the server,
|
|
230
|
-
// provider domain, connection, or subject.
|
|
231
|
-
recordTenancyCompatibilityLaneUse(input.observability, "connection_pre_snapshot_ref");
|
|
232
|
-
return await baseResolver(request);
|
|
135
|
+
return {
|
|
136
|
+
status: "auth_needed",
|
|
137
|
+
reason: "missing_connection",
|
|
138
|
+
providerDomain: request.connectionRef.providerDomain,
|
|
139
|
+
...(request.connectionRef.provider ? { provider: request.connectionRef.provider } : {}),
|
|
140
|
+
};
|
|
233
141
|
}
|
|
234
142
|
const credentialUseContext = {
|
|
235
143
|
accountId: input.accountId,
|
|
@@ -299,50 +207,31 @@ export function connectionTokenResolverForTurn(input: {
|
|
|
299
207
|
},
|
|
300
208
|
};
|
|
301
209
|
};
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
210
|
+
const { subjectId: requestSubjectId, ...requestWithoutSubject } = request;
|
|
211
|
+
const result = await nativeResolver({
|
|
212
|
+
...requestWithoutSubject,
|
|
213
|
+
...(accountBinding?.connectionAuthorityGeneration !== undefined
|
|
214
|
+
? { expectedAuthorityGeneration: accountBinding.connectionAuthorityGeneration }
|
|
215
|
+
: {}),
|
|
216
|
+
// Workspace authority has no personal owner, even when the turn has a
|
|
217
|
+
// causal human. Do not pass that human as credential ownership context.
|
|
218
|
+
...(accountBinding?.subjectScope !== "workspace" && requestSubjectId !== undefined
|
|
219
|
+
? { subjectId: requestSubjectId }
|
|
220
|
+
: {}),
|
|
221
|
+
connectionUseContext: credentialUseContext,
|
|
222
|
+
});
|
|
223
|
+
if (
|
|
224
|
+
accountBinding &&
|
|
225
|
+
result.status === "ok" &&
|
|
226
|
+
result.connectionId !== accountBinding.connectionId
|
|
227
|
+
) {
|
|
320
228
|
return {
|
|
321
229
|
status: "auth_needed",
|
|
322
|
-
reason:
|
|
323
|
-
|
|
324
|
-
? "personal_authority_unavailable"
|
|
325
|
-
: "missing_connection",
|
|
326
|
-
providerDomain: request.connectionRef.providerDomain,
|
|
327
|
-
...(request.connectionRef.provider ? { provider: request.connectionRef.provider } : {}),
|
|
230
|
+
reason: "personal_authority_unavailable",
|
|
231
|
+
providerDomain: accountBinding.providerDomain,
|
|
328
232
|
};
|
|
329
233
|
}
|
|
330
|
-
|
|
331
|
-
const result = await baseResolver({
|
|
332
|
-
...request,
|
|
333
|
-
connectionUseContext: credentialUseContext,
|
|
334
|
-
connectionUseAuthority: authorization.attribution,
|
|
335
|
-
connectionRef: {
|
|
336
|
-
...request.connectionRef,
|
|
337
|
-
connectionId: authorization.attribution.connectionId,
|
|
338
|
-
kind: authorization.connectionKind,
|
|
339
|
-
subjectScope: authorization.attribution.scope === "workspace" ? "workspace" : "subject",
|
|
340
|
-
},
|
|
341
|
-
...(authorization.attribution.ownerSubjectId
|
|
342
|
-
? { subjectId: authorization.attribution.ownerSubjectId }
|
|
343
|
-
: {}),
|
|
344
|
-
});
|
|
345
|
-
return withProviderRequestAuthorization(result, authorization.attribution);
|
|
234
|
+
return withProviderRequestAuthorization(result);
|
|
346
235
|
};
|
|
347
236
|
}
|
|
348
237
|
|