@opengeni/worker-bundle 2.2.0 → 2.2.1-canary.36927100590001
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-run-admission.d.ts +3 -1
- package/dist/activities/agent-turn/admission.d.ts +24 -3
- package/dist/activities/agent-turn/agent-build.d.ts +1 -0
- package/dist/activities/agent-turn/claude-usage-observer.d.ts +13 -1
- package/dist/activities/agent-turn/compaction-prep.d.ts +20 -0
- package/dist/activities/agent-turn/credential-restriction.d.ts +3 -0
- package/dist/activities/agent-turn/final-reply.d.ts +18 -0
- package/dist/activities/agent-turn/governance-model.d.ts +6 -0
- package/dist/activities/agent-turn/model-call-admission.d.ts +15 -0
- package/dist/activities/agent-turn/run-credentials.d.ts +4 -0
- package/dist/activities/agent-turn/sandbox-establish.d.ts +1 -0
- package/dist/activities/agent-turn/session-title.d.ts +3 -1
- package/dist/activities/agent-turn/stream-attempt.d.ts +1 -0
- package/dist/activities/agent-turn/turn-context.d.ts +1 -0
- package/dist/activities/automations.d.ts +3 -1
- package/dist/activities/browser-deadline-checkpoint.d.ts +29 -0
- package/dist/activities/goals.d.ts +8 -0
- package/dist/activities/run-credentials.d.ts +4 -2
- package/dist/activities/scheduled-tasks.d.ts +4 -35
- package/dist/activities/types.d.ts +4 -1
- package/dist/activities/workspace-capture.d.ts +2 -0
- package/dist/{activities-control-MHNA3CYV.js → activities-control-4F2A734B.js} +502 -133
- package/dist/activities-control-4F2A734B.js.map +1 -0
- package/dist/activities-control.d.ts +30 -0
- package/dist/{activities-turn-FQ5OO2WW.js → activities-turn-TESOIOEX.js} +696 -251
- package/dist/activities-turn-TESOIOEX.js.map +1 -0
- package/dist/activities.d.ts +30 -0
- package/dist/browser-deadline-checkpoint-contract.d.ts +2 -0
- package/dist/{chunk-RZAAGRFQ.js → chunk-4PJTUBWQ.js} +220 -88
- package/dist/chunk-4PJTUBWQ.js.map +1 -0
- package/dist/{chunk-IJZU752L.js → chunk-YG27NEMX.js} +34 -1
- package/dist/chunk-YG27NEMX.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +46 -24
- package/dist/index.js.map +1 -1
- package/dist/observability-metrics.d.ts +16 -1
- package/dist/sandbox-resume.d.ts +4 -1
- package/dist/workflow-bundle.js +89 -21
- package/dist/workflows/browser-deadline-checkpoint.d.ts +9 -0
- package/dist/workflows/scheduled-tasks.d.ts +4 -0
- package/dist/workflows.d.ts +1 -0
- package/package.json +21 -20
- package/src/activities/agent-run-admission.ts +17 -1
- package/src/activities/agent-turn/admission.ts +64 -1
- package/src/activities/agent-turn/agent-build.ts +24 -3
- package/src/activities/agent-turn/claim.ts +69 -15
- package/src/activities/agent-turn/claude-usage-observer.ts +33 -1
- package/src/activities/agent-turn/compaction-prep.ts +67 -16
- package/src/activities/agent-turn/credential-restriction.ts +14 -0
- package/src/activities/agent-turn/errors.ts +55 -0
- package/src/activities/agent-turn/failure-settlement.ts +26 -6
- package/src/activities/agent-turn/final-reply.ts +46 -0
- package/src/activities/agent-turn/governance-model.ts +12 -0
- package/src/activities/agent-turn/model-call-admission.ts +94 -0
- package/src/activities/agent-turn/run-credentials.ts +20 -3
- package/src/activities/agent-turn/run.ts +44 -5
- package/src/activities/agent-turn/sandbox-establish.ts +9 -5
- package/src/activities/agent-turn/session-title.ts +8 -2
- package/src/activities/agent-turn/stream-attempt.ts +144 -38
- package/src/activities/agent-turn/tool-environment.ts +55 -39
- package/src/activities/agent-turn/turn-context.ts +1 -0
- package/src/activities/automations.ts +78 -4
- package/src/activities/browser-deadline-checkpoint.ts +279 -0
- package/src/activities/capabilities.ts +52 -14
- package/src/activities/goals.ts +95 -98
- package/src/activities/knowledge-indexing.ts +29 -0
- package/src/activities/open-suffix-resume.ts +2 -1
- package/src/activities/run-credentials.ts +22 -16
- package/src/activities/sandbox-lease.ts +22 -0
- package/src/activities/scheduled-tasks.ts +172 -38
- package/src/activities/site-auth-maintenance.ts +1 -0
- package/src/activities/types.ts +4 -0
- package/src/activities/workspace-capture.ts +13 -2
- package/src/activities/workspace-credential-provider.ts +12 -0
- package/src/activities-control.ts +2 -0
- package/src/activities.ts +3 -0
- package/src/browser-deadline-checkpoint-contract.ts +5 -0
- package/src/index.ts +53 -25
- package/src/observability-metrics.ts +55 -1
- package/src/sandbox-resume.ts +23 -8
- package/src/sandbox-routing.ts +4 -2
- package/src/workflows/browser-deadline-checkpoint.ts +50 -0
- package/src/workflows/scheduled-tasks.ts +26 -13
- package/src/workflows.ts +4 -0
- package/dist/activities-control-MHNA3CYV.js.map +0 -1
- package/dist/activities-turn-FQ5OO2WW.js.map +0 -1
- package/dist/chunk-IJZU752L.js.map +0 -1
- package/dist/chunk-RZAAGRFQ.js.map +0 -1
|
@@ -18,8 +18,10 @@ import {
|
|
|
18
18
|
isMcpRequestTimeoutError,
|
|
19
19
|
isMcpTransportConnectivityError,
|
|
20
20
|
isModalTaskExecStartPreDispatchUnavailableError,
|
|
21
|
+
isModalCommandStartOutcomeUnknownError,
|
|
21
22
|
isRoutingMutationOutcomeUnknownError,
|
|
22
23
|
RoutingWorkspaceRootChangedError,
|
|
24
|
+
ResponsesStreamingTerminalError,
|
|
23
25
|
SandboxMaterializationVerificationError,
|
|
24
26
|
materializationVerificationDiagnostic,
|
|
25
27
|
type MaterializationVerificationDiagnostic,
|
|
@@ -729,6 +731,7 @@ export function shouldRecoverCompactionProviderFailure(error: unknown): boolean
|
|
|
729
731
|
export function classifyContextWindowOverflowError(
|
|
730
732
|
error: unknown,
|
|
731
733
|
): { message: string; code?: string; detail?: string } | null {
|
|
734
|
+
if (isProviderSafetyRefusal(error)) return null;
|
|
732
735
|
const fields = collectErrorStrings(error);
|
|
733
736
|
const matched = fields.find(
|
|
734
737
|
(value) =>
|
|
@@ -795,6 +798,9 @@ export function collectErrorStrings(value: unknown, seen = new WeakSet<object>()
|
|
|
795
798
|
}
|
|
796
799
|
seen.add(value);
|
|
797
800
|
const out: string[] = [];
|
|
801
|
+
// This diagnostic is already provider-owned and byte-bounded. Do not widen
|
|
802
|
+
// generic detail traversal to arbitrary application payloads.
|
|
803
|
+
if (value instanceof ResponsesStreamingTerminalError) out.push(value.detail);
|
|
798
804
|
const record = value as Record<string, unknown>;
|
|
799
805
|
for (const key of ["message", "code", "type", "name", "param"]) {
|
|
800
806
|
const field = record[key];
|
|
@@ -857,6 +863,9 @@ export function isExactStatuslessUpstreamConnectivityMessage(message: string): b
|
|
|
857
863
|
}
|
|
858
864
|
|
|
859
865
|
function providerSafetyRefusalDiagnostic(error: unknown): string | undefined {
|
|
866
|
+
if (error instanceof ResponsesStreamingTerminalError) {
|
|
867
|
+
return error.category === "safety" ? error.detail : undefined;
|
|
868
|
+
}
|
|
860
869
|
return collectErrorStrings(error).find(
|
|
861
870
|
(value) =>
|
|
862
871
|
/^(?:content_policy_violation|content_filter|safety_violation|bio_policy|cyber_policy)$/.test(
|
|
@@ -870,6 +879,9 @@ function isProviderSafetyRefusal(error: unknown): boolean {
|
|
|
870
879
|
}
|
|
871
880
|
|
|
872
881
|
export function isTransientProviderError(error: unknown): boolean {
|
|
882
|
+
if (error instanceof ResponsesStreamingTerminalError) {
|
|
883
|
+
return error.category === "unavailable";
|
|
884
|
+
}
|
|
873
885
|
// A semantic refusal can arrive inside a 5xx transport envelope.
|
|
874
886
|
if (isProviderSafetyRefusal(error)) return false;
|
|
875
887
|
const status =
|
|
@@ -1068,6 +1080,41 @@ function baseAgentRunFailurePayload(
|
|
|
1068
1080
|
detail: safetyRefusalDiagnostic,
|
|
1069
1081
|
};
|
|
1070
1082
|
}
|
|
1083
|
+
if (error instanceof ResponsesStreamingTerminalError) {
|
|
1084
|
+
const quota =
|
|
1085
|
+
error.category === "unknown" || error.category === "rate_limit"
|
|
1086
|
+
? classifyProviderQuotaExhaustionError({
|
|
1087
|
+
code: error.code,
|
|
1088
|
+
error: { code: error.code, type: error.type, message: error.detail },
|
|
1089
|
+
retryAfterSeconds: error.retryAfterSeconds,
|
|
1090
|
+
})
|
|
1091
|
+
: null;
|
|
1092
|
+
if (quota) {
|
|
1093
|
+
return {
|
|
1094
|
+
error: providerQuotaExhaustedMessage(quota.scope),
|
|
1095
|
+
code: PROVIDER_QUOTA_EXHAUSTED_CODE,
|
|
1096
|
+
retryable: false,
|
|
1097
|
+
quotaScope: quota.scope,
|
|
1098
|
+
detail: error.detail,
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
return {
|
|
1102
|
+
error:
|
|
1103
|
+
error.category === "rate_limit"
|
|
1104
|
+
? "Model provider rate limit hit. Try again in a minute or lower the reasoning effort."
|
|
1105
|
+
: error.category === "unavailable"
|
|
1106
|
+
? "The model provider is temporarily unavailable. The same turn will retry after a short delay."
|
|
1107
|
+
: "The model provider rejected the response. Automatic retries stopped.",
|
|
1108
|
+
code:
|
|
1109
|
+
error.category === "rate_limit"
|
|
1110
|
+
? "provider_rate_limited"
|
|
1111
|
+
: error.category === "unavailable"
|
|
1112
|
+
? "provider_unavailable"
|
|
1113
|
+
: "provider_request_rejected",
|
|
1114
|
+
retryable: error.category === "rate_limit" || error.category === "unavailable",
|
|
1115
|
+
detail: error.detail,
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1071
1118
|
const message = error instanceof Error ? error.message : String(error);
|
|
1072
1119
|
const status =
|
|
1073
1120
|
typeof error === "object" && error !== null
|
|
@@ -1106,6 +1153,14 @@ function baseAgentRunFailurePayload(
|
|
|
1106
1153
|
retryable: true,
|
|
1107
1154
|
};
|
|
1108
1155
|
}
|
|
1156
|
+
if (isModalCommandStartOutcomeUnknownError(error)) {
|
|
1157
|
+
return {
|
|
1158
|
+
error:
|
|
1159
|
+
"A managed sandbox command may have started, but its acknowledgement was lost. Automatic replay is disabled; inspect the sandbox state before retrying.",
|
|
1160
|
+
code: "sandbox_command_start_outcome_unknown",
|
|
1161
|
+
retryable: false,
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1109
1164
|
if (
|
|
1110
1165
|
!isRoutingMutationOutcomeUnknownError(error) &&
|
|
1111
1166
|
isModalTaskExecStartPreDispatchUnavailableError(error)
|
|
@@ -16,7 +16,10 @@ import {
|
|
|
16
16
|
type CodexLeaseAccountStatus,
|
|
17
17
|
} from "@opengeni/db";
|
|
18
18
|
import { publishDurableSessionEvents } from "@opengeni/events";
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
maxTurnsExceededRunState,
|
|
21
|
+
isModalTaskExecStartPreDispatchUnavailableError,
|
|
22
|
+
} from "@opengeni/runtime";
|
|
20
23
|
import { ApplicationFailure, CancelledFailure } from "@temporalio/activity";
|
|
21
24
|
import {
|
|
22
25
|
authoritativeCodexCapacityResetAt,
|
|
@@ -1536,12 +1539,16 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
|
|
|
1536
1539
|
if (
|
|
1537
1540
|
!(await eventing.settle!({
|
|
1538
1541
|
events: [
|
|
1542
|
+
...(error.allowance
|
|
1543
|
+
? [{ type: "usage.exhausted" as const, payload: error.allowance }]
|
|
1544
|
+
: []),
|
|
1539
1545
|
{
|
|
1540
1546
|
type: "turn.completed",
|
|
1541
1547
|
payload: {
|
|
1542
1548
|
output: "",
|
|
1543
1549
|
segmentLimit: "budget_exhausted",
|
|
1544
1550
|
detail: error.message,
|
|
1551
|
+
...(error.allowance ?? {}),
|
|
1545
1552
|
},
|
|
1546
1553
|
},
|
|
1547
1554
|
{ type: "session.status.changed", payload: { status: "idle" } },
|
|
@@ -1549,6 +1556,7 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
|
|
|
1549
1556
|
turnStatus: "completed",
|
|
1550
1557
|
sessionStatus: "idle",
|
|
1551
1558
|
activeTurnId: null,
|
|
1559
|
+
...(error.allowance ? { allowanceGoalPause: { rationale: error.allowance.message } } : {}),
|
|
1552
1560
|
}))
|
|
1553
1561
|
) {
|
|
1554
1562
|
return claimedResult({ status: "cancelled" });
|
|
@@ -1655,6 +1663,14 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
|
|
|
1655
1663
|
!!attempt.turnId &&
|
|
1656
1664
|
!!attempt.triggerEventId &&
|
|
1657
1665
|
attempt.executionGeneration > 0;
|
|
1666
|
+
const earlyCommandStartUnavailable =
|
|
1667
|
+
isModalTaskExecStartPreDispatchUnavailableError(error) &&
|
|
1668
|
+
!attempt.modelRequestStarted &&
|
|
1669
|
+
!eventing.turnStartedPublished &&
|
|
1670
|
+
!!attempt.turnId &&
|
|
1671
|
+
!!attempt.triggerEventId &&
|
|
1672
|
+
attempt.executionGeneration > 0;
|
|
1673
|
+
const earlyRecoverableSetup = earlyDefinitionMismatch || earlyCommandStartUnavailable;
|
|
1658
1674
|
let failure = (
|
|
1659
1675
|
earlyDefinitionMismatch
|
|
1660
1676
|
? { error: error.message, code: error.code, retryable: true }
|
|
@@ -1665,7 +1681,7 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
|
|
|
1665
1681
|
) as ReturnType<typeof agentRunFailurePayload>;
|
|
1666
1682
|
if (
|
|
1667
1683
|
attempt.turnId &&
|
|
1668
|
-
(
|
|
1684
|
+
(earlyRecoverableSetup ||
|
|
1669
1685
|
(failure.retryable && eventing.publish && eventing.turnStartedPublished))
|
|
1670
1686
|
) {
|
|
1671
1687
|
const nextProviderRecoveryCount = attempt.providerRecoveryCount + 1;
|
|
@@ -1676,7 +1692,7 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
|
|
|
1676
1692
|
});
|
|
1677
1693
|
try {
|
|
1678
1694
|
if (recoveryResult.status === "recovering") {
|
|
1679
|
-
if (!
|
|
1695
|
+
if (!earlyRecoverableSetup) {
|
|
1680
1696
|
await flushRuntimeBatcher();
|
|
1681
1697
|
await historySink.reconcileConversationTruth({ requireDurable: true });
|
|
1682
1698
|
}
|
|
@@ -1707,15 +1723,19 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
|
|
|
1707
1723
|
return claimedResult(recoveryResult);
|
|
1708
1724
|
}
|
|
1709
1725
|
failure = providerRecoveryExhaustedFailure(failure, recoveryResult);
|
|
1710
|
-
if (
|
|
1726
|
+
if (earlyRecoverableSetup) {
|
|
1711
1727
|
// Setup has no eventing sink yet. Carry only the fixed, safe diagnostic
|
|
1712
1728
|
// through Temporal into exact-attempt workflow failure settlement.
|
|
1713
1729
|
control.activityStatus = "failed";
|
|
1714
1730
|
control.turnMetricOutcome = "failed";
|
|
1715
1731
|
control.activityError = error;
|
|
1716
1732
|
throw ApplicationFailure.create({
|
|
1717
|
-
message:
|
|
1718
|
-
|
|
1733
|
+
message: earlyDefinitionMismatch
|
|
1734
|
+
? `${error.message}. Automatic same-turn configuration recovery exhausted after ${recoveryResult.providerRecoveryCount} retries.`
|
|
1735
|
+
: failure.error,
|
|
1736
|
+
type: earlyDefinitionMismatch
|
|
1737
|
+
? "TurnExecutionPolicyDefinitionMismatchError"
|
|
1738
|
+
: "SandboxCommandStartUnavailableError",
|
|
1719
1739
|
nonRetryable: true,
|
|
1720
1740
|
});
|
|
1721
1741
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** One durable, same-logical-turn handoff, not a retry of model/tool effects. */
|
|
2
|
+
export function finalReplyNudge(turnId: string) {
|
|
3
|
+
return {
|
|
4
|
+
type: "message" as const,
|
|
5
|
+
role: "developer" as const,
|
|
6
|
+
content: [
|
|
7
|
+
{
|
|
8
|
+
type: "input_text" as const,
|
|
9
|
+
text: `[Runtime final-reply handoff for turn ${turnId}]\nYour final user-facing answer was empty. Reply to the user now with the requested deliverable or a concise outcome and its retained artifact link. Goal evidence is a short ledger proof, not the answer, and goal_complete does not deliver a chat reply. Use the completed work and tool results already in history; do not repeat completed tools or restart the task. If this is a late child result after goal completion, integrate material new findings and deliver any still-missing handoff. Do not change the completed goal or invent a result.`,
|
|
10
|
+
},
|
|
11
|
+
],
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function hasFinalReplyNudge(history: readonly unknown[], turnId: string): boolean {
|
|
16
|
+
const text = finalReplyNudge(turnId).content[0]!.text;
|
|
17
|
+
return history.some((item) => {
|
|
18
|
+
const message = item as { role?: unknown; content?: unknown } | null;
|
|
19
|
+
return (
|
|
20
|
+
message?.role === "developer" &&
|
|
21
|
+
Array.isArray(message.content) &&
|
|
22
|
+
message.content.some(
|
|
23
|
+
(part: { type?: unknown; text?: unknown }) =>
|
|
24
|
+
part?.type === "input_text" && part.text === text,
|
|
25
|
+
)
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function needsFinalReply(input: {
|
|
31
|
+
output: unknown;
|
|
32
|
+
inputWaitYielded: boolean;
|
|
33
|
+
interrupted: boolean;
|
|
34
|
+
maintenance: boolean;
|
|
35
|
+
toolsExecuted: boolean;
|
|
36
|
+
completedGoal: boolean;
|
|
37
|
+
}): boolean {
|
|
38
|
+
return (
|
|
39
|
+
typeof input.output === "string" &&
|
|
40
|
+
input.output.trim().length === 0 &&
|
|
41
|
+
!input.inputWaitYielded &&
|
|
42
|
+
!input.interrupted &&
|
|
43
|
+
!input.maintenance &&
|
|
44
|
+
(input.toolsExecuted || input.completedGoal)
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -48,6 +48,7 @@ import { WorkspaceModelPolicyBlockedError } from "@opengeni/runtime";
|
|
|
48
48
|
import {
|
|
49
49
|
evaluateWorkspaceModelPolicy,
|
|
50
50
|
resolveWorkspaceAgentHumanInputEnabled,
|
|
51
|
+
resolveWorkspaceDefaultAgentIdentity,
|
|
51
52
|
type MediaGenerationResult,
|
|
52
53
|
} from "@opengeni/contracts";
|
|
53
54
|
import { codeSearchEnabledForTurn } from "@opengeni/contracts/code-search";
|
|
@@ -97,6 +98,12 @@ export type GovernanceModelOk = {
|
|
|
97
98
|
/** Deployment and workspace allow the Jev-backed code_search tool. */
|
|
98
99
|
codeSearchEnabled: boolean;
|
|
99
100
|
workspaceAgentInstructions: string | null | undefined;
|
|
101
|
+
/**
|
|
102
|
+
* Modular composer identity tier: the explicit workspace default identity,
|
|
103
|
+
* else the frozen legacy persona without `{{core}}`. Never dropped by
|
|
104
|
+
* instruction policies (only read for sessions with an agent configuration).
|
|
105
|
+
*/
|
|
106
|
+
workspaceAgentIdentity: string | null;
|
|
100
107
|
workspaceGovernance: ReturnType<typeof renderWorkspaceGovernanceContext>;
|
|
101
108
|
structuredWorkspacePolicyActive: boolean;
|
|
102
109
|
workspaceMemory: string | null | undefined;
|
|
@@ -247,6 +254,10 @@ export async function prepareGovernanceAndModel(
|
|
|
247
254
|
);
|
|
248
255
|
const contextSelection = await resolveCompanyBrainContextSelection(db, governanceClaims);
|
|
249
256
|
const workspaceAgentInstructions = contextSelection.legacyWorkspaceInstructions;
|
|
257
|
+
const workspaceAgentIdentity = resolveWorkspaceDefaultAgentIdentity(
|
|
258
|
+
workspace.settings,
|
|
259
|
+
workspaceAgentInstructions,
|
|
260
|
+
).identity;
|
|
250
261
|
const memoryPromptMode = contextSelection.receipt.memoryPromptMode;
|
|
251
262
|
assertWorkspaceHumanInputAllowed(agentHumanInputEnabled, "resume", humanInputResume !== null);
|
|
252
263
|
const companyProfileIncluded = contextSelection.receipt.companyProfileIncluded;
|
|
@@ -482,6 +493,7 @@ export async function prepareGovernanceAndModel(
|
|
|
482
493
|
agentHumanInputEnabled,
|
|
483
494
|
codeSearchEnabled,
|
|
484
495
|
workspaceAgentInstructions,
|
|
496
|
+
workspaceAgentIdentity,
|
|
485
497
|
workspaceGovernance,
|
|
486
498
|
structuredWorkspacePolicyActive,
|
|
487
499
|
workspaceMemory,
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { StreamEvent } from "@openai/agents";
|
|
2
|
+
|
|
3
|
+
/** SDK producer/consumer rendezvous. Usage stays on the sole consumer writer. */
|
|
4
|
+
export function createModelCallAdmission(input: {
|
|
5
|
+
signal: AbortSignal;
|
|
6
|
+
admit: () => Promise<void>;
|
|
7
|
+
}) {
|
|
8
|
+
type Receipt = {
|
|
9
|
+
promise: Promise<void>;
|
|
10
|
+
resolve: () => void;
|
|
11
|
+
reject: (error: unknown) => void;
|
|
12
|
+
};
|
|
13
|
+
const receipts = new WeakMap<object, Receipt>();
|
|
14
|
+
const responseReceipts = new Map<string, Receipt>();
|
|
15
|
+
const pending = new Set<Receipt>();
|
|
16
|
+
const responseId = (event: unknown): string | null => {
|
|
17
|
+
const id = (event as { response?: { id?: unknown } } | null)?.response?.id;
|
|
18
|
+
return typeof id === "string" && id.length > 0 ? id : null;
|
|
19
|
+
};
|
|
20
|
+
let latest: Promise<void> = Promise.resolve();
|
|
21
|
+
let failure: unknown;
|
|
22
|
+
let failed = false;
|
|
23
|
+
let rejectHalted!: (error: unknown) => void;
|
|
24
|
+
const halted = new Promise<never>((_, reject) => {
|
|
25
|
+
rejectHalted = reject;
|
|
26
|
+
});
|
|
27
|
+
void halted.catch(() => undefined);
|
|
28
|
+
const fail = (error: unknown) => {
|
|
29
|
+
if (failed) return;
|
|
30
|
+
failed = true;
|
|
31
|
+
failure = error;
|
|
32
|
+
rejectHalted(error);
|
|
33
|
+
for (const receipt of pending) receipt.reject(error);
|
|
34
|
+
pending.clear();
|
|
35
|
+
responseReceipts.clear();
|
|
36
|
+
};
|
|
37
|
+
const abort = () => fail(input.signal.reason ?? new Error("Model stream cancelled"));
|
|
38
|
+
input.signal.addEventListener("abort", abort, { once: true });
|
|
39
|
+
if (input.signal.aborted) abort();
|
|
40
|
+
return {
|
|
41
|
+
beforeModelRequest: async () => {
|
|
42
|
+
await Promise.race([latest, halted]);
|
|
43
|
+
if (failed) throw failure;
|
|
44
|
+
// Even a settled predecessor does not grant admission to the next call.
|
|
45
|
+
await Promise.race([input.admit(), halted]);
|
|
46
|
+
if (failed) throw failure;
|
|
47
|
+
},
|
|
48
|
+
onModelResponse: (event: StreamEvent): Promise<void> => {
|
|
49
|
+
if (failed) return Promise.reject(failure);
|
|
50
|
+
const id = responseId(event);
|
|
51
|
+
const existing =
|
|
52
|
+
receipts.get(event) ??
|
|
53
|
+
(id ? responseReceipts.get(id) : undefined) ??
|
|
54
|
+
(!id && pending.size === 1 ? pending.values().next().value : undefined);
|
|
55
|
+
if (existing) return existing.promise;
|
|
56
|
+
let resolve!: () => void;
|
|
57
|
+
let reject!: (error: unknown) => void;
|
|
58
|
+
const promise = new Promise<void>((yes, no) => {
|
|
59
|
+
resolve = yes;
|
|
60
|
+
reject = no;
|
|
61
|
+
});
|
|
62
|
+
// The next model entry awaits this receipt; rejection can happen sooner.
|
|
63
|
+
void promise.catch(() => undefined);
|
|
64
|
+
const receipt = { promise, resolve, reject };
|
|
65
|
+
receipts.set(event, receipt);
|
|
66
|
+
if (id) responseReceipts.set(id, receipt);
|
|
67
|
+
pending.add(receipt);
|
|
68
|
+
latest = promise;
|
|
69
|
+
return promise;
|
|
70
|
+
},
|
|
71
|
+
settle: (event: { type: string; data?: unknown }) => {
|
|
72
|
+
if (event.type !== "raw_model_stream_event" || !event.data || typeof event.data !== "object")
|
|
73
|
+
return;
|
|
74
|
+
if ((event.data as { type?: unknown }).type !== "response_done") return;
|
|
75
|
+
const id = responseId(event.data);
|
|
76
|
+
// Runner retry normalization copies response_done. It cannot create a
|
|
77
|
+
// second response before this receipt permits the next model entry.
|
|
78
|
+
const receipt =
|
|
79
|
+
receipts.get(event.data) ??
|
|
80
|
+
(id ? responseReceipts.get(id) : undefined) ??
|
|
81
|
+
(pending.size === 1 ? pending.values().next().value : undefined);
|
|
82
|
+
if (!receipt) return;
|
|
83
|
+
pending.delete(receipt);
|
|
84
|
+
if (id) responseReceipts.delete(id);
|
|
85
|
+
receipt.resolve();
|
|
86
|
+
},
|
|
87
|
+
fail,
|
|
88
|
+
close: () => {
|
|
89
|
+
input.signal.removeEventListener("abort", abort);
|
|
90
|
+
if (pending.size > 0) fail(new Error("Model stream closed before response settlement"));
|
|
91
|
+
responseReceipts.clear();
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -28,6 +28,8 @@ import {
|
|
|
28
28
|
type loadWorkspaceEnvironmentForRunWithCredentials,
|
|
29
29
|
} from "../environment";
|
|
30
30
|
import type { mergeResourceRefs } from "../common";
|
|
31
|
+
import type { withFirstPartyTools } from "../goals";
|
|
32
|
+
import { expandMcpAccountRoutes } from "../mcp-account-routes";
|
|
31
33
|
import { startGitCredentialRenewalLoop } from "../git-credential-renewal";
|
|
32
34
|
import {
|
|
33
35
|
RUN_CREDENTIAL_EXPIRY_LEAD_MS,
|
|
@@ -63,6 +65,7 @@ import {
|
|
|
63
65
|
sandboxSettingsForRoute,
|
|
64
66
|
} from "./sandbox-route";
|
|
65
67
|
import type { ClaimTurnOk } from "./claim";
|
|
68
|
+
import { turnCredentialRestriction } from "./credential-restriction";
|
|
66
69
|
import type { GovernanceModelOk } from "./governance-model";
|
|
67
70
|
import type { SandboxTurnRuntime } from "./sandbox-runtime";
|
|
68
71
|
import type {
|
|
@@ -74,6 +77,7 @@ import type {
|
|
|
74
77
|
|
|
75
78
|
export type PrepareRunCredentialsDeps = {
|
|
76
79
|
localMcpServerIds?: readonly string[];
|
|
80
|
+
turnTools: ReturnType<typeof withFirstPartyTools>;
|
|
77
81
|
input: RunAgentTurnInput;
|
|
78
82
|
settings: Settings;
|
|
79
83
|
db: ActivityServices["db"];
|
|
@@ -88,6 +92,7 @@ export type PrepareRunCredentialsDeps = {
|
|
|
88
92
|
sandboxRuntime: SandboxTurnRuntime;
|
|
89
93
|
turn: ClaimTurnOk["turn"];
|
|
90
94
|
session: ClaimTurnOk["session"];
|
|
95
|
+
turnExecutionPolicy: ClaimTurnOk["turnExecutionPolicy"];
|
|
91
96
|
fileAuthoritySubjectId: ClaimTurnOk["fileAuthoritySubjectId"];
|
|
92
97
|
runSettings: GovernanceModelOk["runSettings"];
|
|
93
98
|
workspaceVariableSet: Awaited<ReturnType<typeof loadWorkspaceEnvironmentForRunWithCredentials>>;
|
|
@@ -126,6 +131,7 @@ export async function prepareRunCredentials(deps: PrepareRunCredentialsDeps) {
|
|
|
126
131
|
sandboxState,
|
|
127
132
|
turn,
|
|
128
133
|
session,
|
|
134
|
+
turnExecutionPolicy,
|
|
129
135
|
fileAuthoritySubjectId,
|
|
130
136
|
runSettings,
|
|
131
137
|
workspaceVariableSet,
|
|
@@ -153,6 +159,14 @@ export async function prepareRunCredentials(deps: PrepareRunCredentialsDeps) {
|
|
|
153
159
|
const platformGitResources = turnResources.filter(
|
|
154
160
|
(resource) => resource.kind !== "repository" || repositoryHasExplicitGitConnection(resource),
|
|
155
161
|
);
|
|
162
|
+
// Credential targets follow the same accepted-account narrowing as execution.
|
|
163
|
+
// A canonical connection without an accepted account does not execute, and
|
|
164
|
+
// account-qualified aliases never inherit an attachment's product headers.
|
|
165
|
+
const accountRoutes = expandMcpAccountRoutes({
|
|
166
|
+
settings: runSettings,
|
|
167
|
+
tools: deps.turnTools,
|
|
168
|
+
bindings: turn.mcpAccountBindings,
|
|
169
|
+
});
|
|
156
170
|
|
|
157
171
|
const runCredentialResolver =
|
|
158
172
|
effectiveRunCredentialBackend === "none" || effectiveRunCredentialBackend === "selfhosted"
|
|
@@ -160,10 +174,11 @@ export async function prepareRunCredentials(deps: PrepareRunCredentialsDeps) {
|
|
|
160
174
|
: await waitForTurnOperation(
|
|
161
175
|
bindRunCredentialResolver({
|
|
162
176
|
db,
|
|
163
|
-
settings:
|
|
177
|
+
settings: accountRoutes.settings,
|
|
164
178
|
initiatingHumanSubjectId: turn.initiatingHumanSubjectId ?? null,
|
|
165
179
|
connectionCredentials: connectionCredentials ?? null,
|
|
166
180
|
localMcpServerIds: deps.localMcpServerIds ?? [],
|
|
181
|
+
effectiveTools: accountRoutes.tools,
|
|
167
182
|
accountId: input.accountId,
|
|
168
183
|
workspaceId: input.workspaceId,
|
|
169
184
|
session,
|
|
@@ -214,9 +229,9 @@ export async function prepareRunCredentials(deps: PrepareRunCredentialsDeps) {
|
|
|
214
229
|
: null;
|
|
215
230
|
const runMcpCredentials = new RunMcpCredentials(
|
|
216
231
|
selectedSessionRemoteMcpTargets(
|
|
217
|
-
|
|
232
|
+
accountRoutes.settings,
|
|
218
233
|
session.mcpServers ?? [],
|
|
219
|
-
|
|
234
|
+
accountRoutes.tools,
|
|
220
235
|
(deps.localMcpServerIds ?? []).map((id) => ({ id })),
|
|
221
236
|
),
|
|
222
237
|
{
|
|
@@ -334,11 +349,13 @@ export async function prepareRunCredentials(deps: PrepareRunCredentialsDeps) {
|
|
|
334
349
|
turn,
|
|
335
350
|
})
|
|
336
351
|
: undefined;
|
|
352
|
+
const credentialRestriction = turnCredentialRestriction(turnExecutionPolicy, session.metadata);
|
|
337
353
|
const codemodeAuthority = {
|
|
338
354
|
sessionId: input.sessionId,
|
|
339
355
|
turnId: turn.id,
|
|
340
356
|
attemptId: input.attemptId,
|
|
341
357
|
executionGeneration: turn.executionGeneration,
|
|
358
|
+
...(credentialRestriction ? { credentialRestriction } : {}),
|
|
342
359
|
};
|
|
343
360
|
const sandboxArtifactRuntime = sandboxArtifactRuntimeAdmission(
|
|
344
361
|
settings,
|
|
@@ -3,8 +3,11 @@ import {
|
|
|
3
3
|
readActiveSandbox,
|
|
4
4
|
getWorkspaceCredentialProvider,
|
|
5
5
|
loadClaudeSubscriptionUsageCredential,
|
|
6
|
+
resolveClaudeSubscriptionCredential,
|
|
7
|
+
ClaudeSubscriptionReconnectRequired,
|
|
6
8
|
} from "@opengeni/db";
|
|
7
9
|
import { routingEnabled } from "../../sandbox-routing";
|
|
10
|
+
import { resolveAgentToolFamilies } from "@opengeni/contracts";
|
|
8
11
|
import { createKnowledgeSourceSyncActivities } from "../knowledge-source-sync";
|
|
9
12
|
import {
|
|
10
13
|
assertModelConnectionAllowsTurn,
|
|
@@ -26,6 +29,7 @@ import {
|
|
|
26
29
|
import {
|
|
27
30
|
REMOTE_COMPACTION_V2_BETA_FEATURE,
|
|
28
31
|
REMOTE_COMPACTION_V2_IMPLEMENTATION,
|
|
32
|
+
awaitModelCallAdmission,
|
|
29
33
|
materializeSandboxFileDownloads,
|
|
30
34
|
sandboxFileDownloadFailureNote,
|
|
31
35
|
type SandboxFileDownload,
|
|
@@ -378,12 +382,14 @@ export function createRunAgentTurnActivity(services: () => Promise<ActivityServi
|
|
|
378
382
|
getModelRunSettings: () => eventing.modelRunSettings,
|
|
379
383
|
getExecutionGeneration: () => attempt.executionGeneration,
|
|
380
384
|
});
|
|
381
|
-
const checkpointBeforeProviderDispatch = () =>
|
|
382
|
-
|
|
385
|
+
const checkpointBeforeProviderDispatch = async () => {
|
|
386
|
+
await awaitModelCallAdmission();
|
|
387
|
+
await checkpointHistoryBeforeProviderDispatch(historySink, {
|
|
383
388
|
effectiveSandboxBackend: eventing.modelRunSettings.sandboxBackend,
|
|
384
389
|
routingEnabled: routingEnabled(settings),
|
|
385
390
|
readActiveSandbox: () => readActiveSandbox(db, input.workspaceId, input.sessionId),
|
|
386
391
|
});
|
|
392
|
+
};
|
|
387
393
|
|
|
388
394
|
try {
|
|
389
395
|
const claimed = await claimTurnAttempt({
|
|
@@ -554,6 +560,7 @@ export function createRunAgentTurnActivity(services: () => Promise<ActivityServi
|
|
|
554
560
|
agentHumanInputEnabled,
|
|
555
561
|
codeSearchEnabled,
|
|
556
562
|
workspaceAgentInstructions,
|
|
563
|
+
workspaceAgentIdentity,
|
|
557
564
|
workspaceGovernance,
|
|
558
565
|
structuredWorkspacePolicyActive,
|
|
559
566
|
workspaceMemory,
|
|
@@ -786,8 +793,12 @@ export function createRunAgentTurnActivity(services: () => Promise<ActivityServi
|
|
|
786
793
|
credentialId: providerTurn.effectiveXaiCredentialId,
|
|
787
794
|
authoritySnapshot: turn.xaiProviderAccountAuthoritySnapshot,
|
|
788
795
|
hostedSearch: {
|
|
789
|
-
webSearch:
|
|
790
|
-
|
|
796
|
+
webSearch: resolveAgentToolFamilies(session.agent, {
|
|
797
|
+
webSearch: runSettings.webSearchEnabled,
|
|
798
|
+
}).webSearch,
|
|
799
|
+
xSearch: resolveAgentToolFamilies(session.agent, {
|
|
800
|
+
webSearch: runSettings.webSearchEnabled,
|
|
801
|
+
}).webSearch,
|
|
791
802
|
},
|
|
792
803
|
streamIdleTimeoutMs: runSettings.supergrokResponseStreamIdleTimeoutMs,
|
|
793
804
|
nextRequestId: () => `${dispatchId}:xai:${++xaiModelRequestSequence}`,
|
|
@@ -932,7 +943,30 @@ export function createRunAgentTurnActivity(services: () => Promise<ActivityServi
|
|
|
932
943
|
}),
|
|
933
944
|
);
|
|
934
945
|
const withClaudeUsage = <T>(fn: () => Promise<T>): Promise<T> =>
|
|
935
|
-
withClaudeUsageObserver(claudeUsageObserver, fn)
|
|
946
|
+
withClaudeUsageObserver(claudeUsageObserver, fn, async (providerId, headers) => {
|
|
947
|
+
const binding = claudeUsageObserver.binding(providerId);
|
|
948
|
+
if (!binding) return headers;
|
|
949
|
+
const credential = await resolveClaudeSubscriptionCredential(
|
|
950
|
+
db,
|
|
951
|
+
settings,
|
|
952
|
+
{
|
|
953
|
+
accountId: input.accountId,
|
|
954
|
+
workspaceId: input.workspaceId,
|
|
955
|
+
scope: binding.scope,
|
|
956
|
+
},
|
|
957
|
+
{
|
|
958
|
+
expectedConnectionId: binding.expectedConnectionId,
|
|
959
|
+
expectedCredentialVersion: binding.expectedCredentialVersion,
|
|
960
|
+
},
|
|
961
|
+
);
|
|
962
|
+
if (!credential) return headers; // A replaced connection never lends its new token to this turn.
|
|
963
|
+
if ("reconnectRequired" in credential)
|
|
964
|
+
throw new ClaudeSubscriptionReconnectRequired();
|
|
965
|
+
claudeUsageObserver.renew(providerId, credential);
|
|
966
|
+
headers.set("authorization", `Bearer ${credential.token}`);
|
|
967
|
+
headers.delete("x-api-key");
|
|
968
|
+
return headers;
|
|
969
|
+
});
|
|
936
970
|
const withCodex = <T>(fn: () => Promise<T>): Promise<T> =>
|
|
937
971
|
codexContext ? codexRequestStorage.run(codexContext, fn) : fn();
|
|
938
972
|
const withProviderRequestContext = <T>(fn: () => Promise<T>): Promise<T> =>
|
|
@@ -1004,6 +1038,7 @@ export function createRunAgentTurnActivity(services: () => Promise<ActivityServi
|
|
|
1004
1038
|
turnExecutionPolicy,
|
|
1005
1039
|
resolvedModel,
|
|
1006
1040
|
workspaceAgentInstructions,
|
|
1041
|
+
workspaceAgentIdentity,
|
|
1007
1042
|
workspaceGovernance,
|
|
1008
1043
|
structuredWorkspacePolicyActive,
|
|
1009
1044
|
workspaceMemory,
|
|
@@ -1175,6 +1210,7 @@ export function createRunAgentTurnActivity(services: () => Promise<ActivityServi
|
|
|
1175
1210
|
);
|
|
1176
1211
|
|
|
1177
1212
|
const runCredentials = await prepareRunCredentials({
|
|
1213
|
+
turnTools,
|
|
1178
1214
|
localMcpServerIds: installedApiIntegrations.map((integration) => integration.serverId),
|
|
1179
1215
|
input,
|
|
1180
1216
|
settings,
|
|
@@ -1190,6 +1226,7 @@ export function createRunAgentTurnActivity(services: () => Promise<ActivityServi
|
|
|
1190
1226
|
sandboxRuntime,
|
|
1191
1227
|
turn,
|
|
1192
1228
|
session,
|
|
1229
|
+
turnExecutionPolicy,
|
|
1193
1230
|
fileAuthoritySubjectId,
|
|
1194
1231
|
runSettings,
|
|
1195
1232
|
workspaceVariableSet,
|
|
@@ -1410,6 +1447,7 @@ export function createRunAgentTurnActivity(services: () => Promise<ActivityServi
|
|
|
1410
1447
|
supportsImageInput,
|
|
1411
1448
|
agentHumanInputEnabled,
|
|
1412
1449
|
workspaceAgentInstructions,
|
|
1450
|
+
workspaceAgentIdentity,
|
|
1413
1451
|
workspaceGovernance,
|
|
1414
1452
|
structuredWorkspacePolicyActive,
|
|
1415
1453
|
workspaceMemory,
|
|
@@ -1539,6 +1577,7 @@ export function createRunAgentTurnActivity(services: () => Promise<ActivityServi
|
|
|
1539
1577
|
turnExecutionPolicy,
|
|
1540
1578
|
resolvedModel,
|
|
1541
1579
|
workspaceAgentInstructions,
|
|
1580
|
+
workspaceAgentIdentity,
|
|
1542
1581
|
workspaceGovernance,
|
|
1543
1582
|
structuredWorkspacePolicyActive,
|
|
1544
1583
|
workspaceMemory,
|
|
@@ -102,6 +102,7 @@ export type SandboxRouteOk = {
|
|
|
102
102
|
machinePrimary: boolean;
|
|
103
103
|
groupBoxBackend: Settings["sandboxBackend"];
|
|
104
104
|
groupBoxImage: ReturnType<typeof rigProviderImageSourceImage>;
|
|
105
|
+
groupBoxImagePolicy: "new_creates_only";
|
|
105
106
|
sandboxCreationBackend: Settings["sandboxBackend"];
|
|
106
107
|
effectiveRunCredentialBackend: Settings["sandboxBackend"];
|
|
107
108
|
};
|
|
@@ -299,6 +300,7 @@ export async function resolveSandboxRoute(deps: SandboxRouteDeps): Promise<Sandb
|
|
|
299
300
|
machinePrimary,
|
|
300
301
|
groupBoxBackend,
|
|
301
302
|
groupBoxImage,
|
|
303
|
+
groupBoxImagePolicy: "new_creates_only",
|
|
302
304
|
sandboxCreationBackend,
|
|
303
305
|
effectiveRunCredentialBackend,
|
|
304
306
|
};
|
|
@@ -341,6 +343,7 @@ export async function establishTurnSandbox(deps: EstablishTurnSandboxDeps): Prom
|
|
|
341
343
|
machinePrimary,
|
|
342
344
|
groupBoxBackend,
|
|
343
345
|
groupBoxImage,
|
|
346
|
+
groupBoxImagePolicy,
|
|
344
347
|
rigVersion,
|
|
345
348
|
turnResources,
|
|
346
349
|
} = deps;
|
|
@@ -665,6 +668,7 @@ export async function establishTurnSandbox(deps: EstablishTurnSandboxDeps): Prom
|
|
|
665
668
|
...(groupBoxImage
|
|
666
669
|
? {
|
|
667
670
|
image: groupBoxImage,
|
|
671
|
+
imagePolicy: groupBoxImagePolicy,
|
|
668
672
|
}
|
|
669
673
|
: {}),
|
|
670
674
|
// The lazy acquire must enforce the same frozen rig authority
|
|
@@ -744,17 +748,17 @@ export async function establishTurnSandbox(deps: EstablishTurnSandboxDeps): Prom
|
|
|
744
748
|
backend: groupBoxBackend,
|
|
745
749
|
os: groupBoxOs,
|
|
746
750
|
environment: sandboxEnvironment,
|
|
747
|
-
//
|
|
748
|
-
//
|
|
749
|
-
//
|
|
750
|
-
//
|
|
751
|
-
// the image for the actual group-box backend; a configured Modal image must
|
|
751
|
+
// Deployment/workspace pins select NEW creates only. The lease
|
|
752
|
+
// retains a live group's image through between-turn repins, even
|
|
753
|
+
// with other holders; only rotation/reaping elects a new image.
|
|
754
|
+
// Select the image for the actual group-box backend; a configured Modal image must
|
|
752
755
|
// never override a Docker run. The selfhosted branch
|
|
753
756
|
// (establishSelfhostedTurnSession) NEVER passes
|
|
754
757
|
// an image — B3 lives only on this managed-box branch.
|
|
755
758
|
...(groupBoxImage
|
|
756
759
|
? {
|
|
757
760
|
image: groupBoxImage,
|
|
761
|
+
imagePolicy: groupBoxImagePolicy,
|
|
758
762
|
}
|
|
759
763
|
: {}),
|
|
760
764
|
// RIG IS SHARED STATE (M3): stamp the frozen rig version so the lease
|