@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
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
ReasoningEffort,
|
|
13
13
|
type FirstPartyMcpToolName,
|
|
14
14
|
type Permission,
|
|
15
|
+
type ResolvedAgentConfig,
|
|
15
16
|
type ToolRef,
|
|
16
17
|
} from "@opengeni/contracts";
|
|
17
18
|
|
|
@@ -23,6 +24,7 @@ const SESSION_TITLE_DESCRIPTION =
|
|
|
23
24
|
export function shouldRequestMissingSessionTitle(input: {
|
|
24
25
|
title: string | null;
|
|
25
26
|
titleSource: "user" | "agent" | null;
|
|
27
|
+
agentConfig?: ResolvedAgentConfig | null;
|
|
26
28
|
firstPartyMcpTools: readonly FirstPartyMcpToolName[];
|
|
27
29
|
firstPartyMcpPermissions: readonly Permission[] | null;
|
|
28
30
|
}): boolean {
|
|
@@ -30,7 +32,9 @@ export function shouldRequestMissingSessionTitle(input: {
|
|
|
30
32
|
const needsSemanticTitle =
|
|
31
33
|
input.titleSource !== "user" && (!title || title === AUTOMATIC_SESSION_TITLE_FALLBACK);
|
|
32
34
|
if (!needsSemanticTitle) return false;
|
|
33
|
-
|
|
35
|
+
// Configured sessions title through the runtime, not an opted-in capability.
|
|
36
|
+
// Legacy sessions keep their exact selected-tool and permission admission.
|
|
37
|
+
if (!input.agentConfig && !input.firstPartyMcpTools.includes("set_session_title")) return false;
|
|
34
38
|
const permissions = input.firstPartyMcpPermissions ?? DEFAULT_FIRST_PARTY_MCP_PERMISSIONS;
|
|
35
39
|
return hasPermission([...permissions], "sessions:control");
|
|
36
40
|
}
|
|
@@ -51,6 +55,7 @@ export function routeAllowsSessionTitleRequests(
|
|
|
51
55
|
|
|
52
56
|
export function sessionTitleToolPlan(input: {
|
|
53
57
|
tools: readonly ToolRef[];
|
|
58
|
+
agentConfig?: ResolvedAgentConfig | null;
|
|
54
59
|
selectedFirstPartyMcpTools: readonly FirstPartyMcpToolName[];
|
|
55
60
|
shouldRequestTitle: boolean;
|
|
56
61
|
parallelGenerationAvailable: boolean;
|
|
@@ -63,7 +68,8 @@ export function sessionTitleToolPlan(input: {
|
|
|
63
68
|
} {
|
|
64
69
|
const titleToolAvailable =
|
|
65
70
|
input.shouldRequestTitle &&
|
|
66
|
-
input.
|
|
71
|
+
(input.agentConfig != null ||
|
|
72
|
+
input.tools.some((tool) => tool.kind === "mcp" && tool.id === "opengeni"));
|
|
67
73
|
const titleRequestAllowed = titleToolAvailable && input.routeAllowsTitleRequests;
|
|
68
74
|
const generateTitleInParallel = titleRequestAllowed && input.parallelGenerationAvailable;
|
|
69
75
|
const promoteTitleTool = titleRequestAllowed && !generateTitleInParallel;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { measureMcpPhase, withMcpCallIdentity } from "@opengeni/observability";
|
|
2
2
|
import {
|
|
3
|
+
appendSessionHistoryItems,
|
|
4
|
+
sessionTurnFinalReplyFacts,
|
|
5
|
+
sessionTurnHasFinalReplyNudge,
|
|
3
6
|
getSessionEvent,
|
|
4
7
|
getHumanInputResumeForEvent,
|
|
5
8
|
getSessionHumanInputRequest,
|
|
@@ -85,19 +88,20 @@ import {
|
|
|
85
88
|
import {
|
|
86
89
|
OPEN_SUFFIX_RUN_STATE_BLOB,
|
|
87
90
|
resolveWorkspaceAgentHumanInputEnabled,
|
|
91
|
+
withPublicApprovalFields,
|
|
88
92
|
type RetainedArtifactMetadata,
|
|
89
93
|
type SessionEvent,
|
|
90
94
|
type SessionTurn,
|
|
91
95
|
} from "@opengeni/contracts";
|
|
92
96
|
import { createModelCheckpointMemoryCollector } from "../../model-checkpoint-memory-collector";
|
|
97
|
+
import { createModelCallAdmission } from "./model-call-admission";
|
|
93
98
|
|
|
94
99
|
import {
|
|
95
100
|
assertWorkspaceHumanInputAllowed,
|
|
96
101
|
stableHumanInputRequestId,
|
|
97
102
|
stableInteractionInterventionId,
|
|
98
103
|
stableInteractionInterventionOperationId,
|
|
99
|
-
|
|
100
|
-
ensureRunAllowed,
|
|
104
|
+
ensureRunAllowedBetweenModelCalls,
|
|
101
105
|
} from "./admission";
|
|
102
106
|
import {
|
|
103
107
|
compactionFailureReason,
|
|
@@ -141,6 +145,7 @@ import {
|
|
|
141
145
|
import { inputWaitReply, latestDurableTurnMessageText } from "./input-wait-reply";
|
|
142
146
|
import { waitForTurnOperation } from "./sandbox-provision";
|
|
143
147
|
import { createSharedRigSetupCoordinator } from "./sandbox-shared-preparation";
|
|
148
|
+
import { finalReplyNudge, needsFinalReply } from "./final-reply";
|
|
144
149
|
|
|
145
150
|
import type { CompactionSummarizer } from "../context-compaction";
|
|
146
151
|
import type { TurnExecutionPolicyV1 } from "@opengeni/contracts";
|
|
@@ -235,6 +240,7 @@ export type TurnStreamAttemptDeps = {
|
|
|
235
240
|
groupBoxBackend: Settings["sandboxBackend"];
|
|
236
241
|
turnExecutionPolicy: TurnExecutionPolicyV1;
|
|
237
242
|
turn: Pick<SessionTurn, "initiator" | "initiatorContext"> & {
|
|
243
|
+
initiatingHumanSubjectId: string | null;
|
|
238
244
|
id: string;
|
|
239
245
|
executionGeneration: number;
|
|
240
246
|
model: string;
|
|
@@ -583,14 +589,41 @@ export async function runTurnStreamAttempt(
|
|
|
583
589
|
// durably: the reply a wait-ended human turn records on turn.completed.
|
|
584
590
|
let latestAssistantMessageText: string | null = null;
|
|
585
591
|
let workerPreparationTotalRecorded = false;
|
|
592
|
+
let toolsExecuted = false;
|
|
593
|
+
let finalReplyNudged = false;
|
|
594
|
+
const revalidateModelCallAdmission = async () => {
|
|
595
|
+
await historySink.reconcileConversationTruth({ requireDurable: true });
|
|
596
|
+
await ensureRunAllowedBetweenModelCalls({
|
|
597
|
+
settings,
|
|
598
|
+
db,
|
|
599
|
+
accountId: input.accountId,
|
|
600
|
+
workspaceId: input.workspaceId,
|
|
601
|
+
isExternallyBilledTurn: billingState.isExternallyBilledTurn,
|
|
602
|
+
entitlements,
|
|
603
|
+
chargesOpenGeniCredits: billingState.chargesOpenGeniCredits,
|
|
604
|
+
countsTowardTokenCap: billingState.countsTowardTokenCap,
|
|
605
|
+
initiatingHumanSubjectId: turn.initiatingHumanSubjectId,
|
|
606
|
+
});
|
|
607
|
+
};
|
|
586
608
|
const runStreamAttempt = async (options: {
|
|
587
609
|
requireTerminalModelResponse: boolean;
|
|
588
|
-
}): Promise<RunAgentTurnResult> => {
|
|
610
|
+
}): Promise<RunAgentTurnResult | "reply_nudge"> => {
|
|
589
611
|
if (!runInput) {
|
|
590
612
|
throw new Error("Run input was not prepared");
|
|
591
613
|
}
|
|
592
|
-
|
|
614
|
+
// The previous stream was persisted before compaction; the sink is now
|
|
615
|
+
// seeded from its durable replacement. Do not reconcile the old prefix
|
|
616
|
+
// against that replacement while checking the next stream's admission.
|
|
593
617
|
eventing.stream = undefined;
|
|
618
|
+
// Compaction commits its paid usage and replacement history before this
|
|
619
|
+
// boundary, both during preparation and in-activity recovery. Revalidate
|
|
620
|
+
// the accepted turn's frozen human before another stream can dispatch.
|
|
621
|
+
if (options.requireTerminalModelResponse) await revalidateModelCallAdmission();
|
|
622
|
+
const modelCallAdmission = createModelCallAdmission({
|
|
623
|
+
signal: runtimeCancellationSignal,
|
|
624
|
+
admit: revalidateModelCallAdmission,
|
|
625
|
+
});
|
|
626
|
+
const responseCountBeforeStream = modelResponseState.responseCount;
|
|
594
627
|
eventing.batcher = null;
|
|
595
628
|
// The SDK emits every processed call item for one model response before
|
|
596
629
|
// it emits any result for that response. Keep that response-local batch
|
|
@@ -806,6 +839,8 @@ export async function runTurnStreamAttempt(
|
|
|
806
839
|
}
|
|
807
840
|
attempt.modelRequestStarted = true;
|
|
808
841
|
return await runtime.runStream(agent, runInput!, eventing.modelRunSettings, {
|
|
842
|
+
beforeModelRequest: modelCallAdmission.beforeModelRequest,
|
|
843
|
+
onModelResponse: modelCallAdmission.onModelResponse,
|
|
809
844
|
signal: runtimeCancellationSignal,
|
|
810
845
|
sandboxEnvironment,
|
|
811
846
|
onModelVisibleContext: async (snapshot) => {
|
|
@@ -941,7 +976,13 @@ export async function runTurnStreamAttempt(
|
|
|
941
976
|
if (leases.xai.lost) {
|
|
942
977
|
throw new Error("xAI credential lease expired before the model run");
|
|
943
978
|
}
|
|
944
|
-
|
|
979
|
+
try {
|
|
980
|
+
eventing.stream = await withProviderRequestContext(runStreamOnce);
|
|
981
|
+
} catch (error) {
|
|
982
|
+
modelCallAdmission.fail(error);
|
|
983
|
+
modelCallAdmission.close();
|
|
984
|
+
throw error;
|
|
985
|
+
}
|
|
945
986
|
// Bounded provider label for the streaming SLIs — the resolved registry
|
|
946
987
|
// provider id (or the built-in OpenAI/Azure provider), never a raw
|
|
947
988
|
// user-supplied model string.
|
|
@@ -1094,35 +1135,22 @@ export async function runTurnStreamAttempt(
|
|
|
1094
1135
|
await historySink.reconcileConversationTruth();
|
|
1095
1136
|
turnLifecycleMetricsFor(observability).progress({ attemptId: input.attemptId });
|
|
1096
1137
|
modelCheckpointMemoryCollector.schedule(observability);
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
)
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
// the outer catch can end this segment gracefully with full
|
|
1111
|
-
// conversation context preserved for the post-top-up resume.
|
|
1112
|
-
let serializedRunState: string | null = null;
|
|
1113
|
-
try {
|
|
1114
|
-
serializedRunState = media.compactMediaRunState(
|
|
1115
|
-
String(eventing.stream.state.toString()),
|
|
1116
|
-
);
|
|
1117
|
-
} catch {
|
|
1118
|
-
serializedRunState = null;
|
|
1119
|
-
}
|
|
1120
|
-
throw new BudgetExhaustedError(
|
|
1121
|
-
limitError instanceof Error ? limitError.message : String(limitError),
|
|
1122
|
-
serializedRunState,
|
|
1123
|
-
);
|
|
1124
|
-
}
|
|
1138
|
+
await ensureRunAllowedBetweenModelCalls({
|
|
1139
|
+
settings,
|
|
1140
|
+
db,
|
|
1141
|
+
accountId: input.accountId,
|
|
1142
|
+
workspaceId: input.workspaceId,
|
|
1143
|
+
isExternallyBilledTurn: billingState.isExternallyBilledTurn,
|
|
1144
|
+
entitlements,
|
|
1145
|
+
chargesOpenGeniCredits: billingState.chargesOpenGeniCredits,
|
|
1146
|
+
countsTowardTokenCap: billingState.countsTowardTokenCap,
|
|
1147
|
+
initiatingHumanSubjectId: turn.initiatingHumanSubjectId,
|
|
1148
|
+
serializedRunState: () =>
|
|
1149
|
+
media.compactMediaRunState(String(eventing.stream!.state.toString())),
|
|
1150
|
+
});
|
|
1125
1151
|
}
|
|
1152
|
+
// Release only after both the debit and frozen-human admission finish.
|
|
1153
|
+
modelCallAdmission.settle(next.value);
|
|
1126
1154
|
const durableSdkEvent = generatedImageReceipt
|
|
1127
1155
|
? compactGeneratedImageSdkEvent(next.value, generatedImageReceipt)
|
|
1128
1156
|
: next.value;
|
|
@@ -1159,6 +1187,7 @@ export async function runTurnStreamAttempt(
|
|
|
1159
1187
|
}
|
|
1160
1188
|
const completedToolCall = completedToolCallFromSdkEvent(durableSdkEvent);
|
|
1161
1189
|
if (completedToolCall) {
|
|
1190
|
+
toolsExecuted = true;
|
|
1162
1191
|
retainedScreenshotMetadata =
|
|
1163
1192
|
media.retainedScreenshotReceiptsByCallId.get(completedToolCall.callId) ?? null;
|
|
1164
1193
|
const typedScreenshot = retainedScreenshotMetadata
|
|
@@ -1396,6 +1425,7 @@ export async function runTurnStreamAttempt(
|
|
|
1396
1425
|
}
|
|
1397
1426
|
}
|
|
1398
1427
|
} catch (error) {
|
|
1428
|
+
modelCallAdmission.fail(error);
|
|
1399
1429
|
// Event processing can fail while SDK completion is still pending.
|
|
1400
1430
|
// Close this stream before any failure publication; a legitimate
|
|
1401
1431
|
// compaction retry may start a new, independently fenced generation.
|
|
@@ -1451,6 +1481,7 @@ export async function runTurnStreamAttempt(
|
|
|
1451
1481
|
}
|
|
1452
1482
|
throw error;
|
|
1453
1483
|
} finally {
|
|
1484
|
+
modelCallAdmission.close();
|
|
1454
1485
|
if (!streamDone) {
|
|
1455
1486
|
// ReadableStream cancellation synchronously trips the Agents SDK's
|
|
1456
1487
|
// abort controller, but its returned promise may wait for an
|
|
@@ -1472,7 +1503,10 @@ export async function runTurnStreamAttempt(
|
|
|
1472
1503
|
// External Codemode stays reachable until finalization. Close wait
|
|
1473
1504
|
// admission before any terminal output/history decision, and drain an
|
|
1474
1505
|
// already-admitted wait before consulting the actual runner-yield latch.
|
|
1475
|
-
|
|
1506
|
+
// Drain trusted receipts before deciding whether an empty stream may need
|
|
1507
|
+
// a handoff. Do not terminally seal the attempt until that decision: the
|
|
1508
|
+
// one same-turn stream still uses the ordinary input-wait admission gate.
|
|
1509
|
+
await eventing.preparedTools?.inputWaitYield?.drainForHandoff(runtimeCancellationSignal);
|
|
1476
1510
|
if (
|
|
1477
1511
|
options.requireTerminalModelResponse &&
|
|
1478
1512
|
!eventing.preparedTools?.inputWaitYield?.yielded &&
|
|
@@ -1585,6 +1619,7 @@ export async function runTurnStreamAttempt(
|
|
|
1585
1619
|
}
|
|
1586
1620
|
}
|
|
1587
1621
|
if (eventing.stream.interruptions.length > 0) {
|
|
1622
|
+
await eventing.preparedTools?.inputWaitYield?.sealForSettlement(runtimeCancellationSignal);
|
|
1588
1623
|
await historySink.reconcileConversationTruth({ requireDurable: true });
|
|
1589
1624
|
const approvals = runtime.serializeApprovals(eventing.stream.interruptions);
|
|
1590
1625
|
const humanInputInterruptions =
|
|
@@ -1702,9 +1737,11 @@ export async function runTurnStreamAttempt(
|
|
|
1702
1737
|
type: "session.requiresAction" as const,
|
|
1703
1738
|
payload: {
|
|
1704
1739
|
approvals: approvals.map((approval) =>
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1740
|
+
withPublicApprovalFields(
|
|
1741
|
+
withMcpToolDisplayMetadata(
|
|
1742
|
+
eventing.preparedTools?.mcpServers ?? [],
|
|
1743
|
+
approval,
|
|
1744
|
+
),
|
|
1708
1745
|
),
|
|
1709
1746
|
),
|
|
1710
1747
|
},
|
|
@@ -1739,6 +1776,60 @@ export async function runTurnStreamAttempt(
|
|
|
1739
1776
|
const finalOutput = String(
|
|
1740
1777
|
requireAgentStreamFinalOutput(eventing.stream.finalOutput, inputWaitYielded),
|
|
1741
1778
|
);
|
|
1779
|
+
const durableReplyFacts =
|
|
1780
|
+
!inputWaitYielded && finalOutput.trim().length === 0
|
|
1781
|
+
? await sessionTurnFinalReplyFacts(db, input.workspaceId, input.sessionId, activeTurnId)
|
|
1782
|
+
: { toolsExecuted: false, completedGoal: false };
|
|
1783
|
+
let emptyFinalReply = false;
|
|
1784
|
+
if (
|
|
1785
|
+
needsFinalReply({
|
|
1786
|
+
output: finalOutput,
|
|
1787
|
+
inputWaitYielded:
|
|
1788
|
+
inputWaitYielded || eventing.preparedTools?.inputWaitYield?.requested === true,
|
|
1789
|
+
interrupted: false, // interruption settlement returned above
|
|
1790
|
+
maintenance: turn.source === "compaction",
|
|
1791
|
+
toolsExecuted: toolsExecuted || finalReplyNudged || durableReplyFacts.toolsExecuted,
|
|
1792
|
+
completedGoal: durableReplyFacts.completedGoal,
|
|
1793
|
+
})
|
|
1794
|
+
) {
|
|
1795
|
+
await historySink.reconcileConversationTruth({ requireDurable: true });
|
|
1796
|
+
// Consult retained truth, including inactive compacted rows, so an
|
|
1797
|
+
// attempt replacement or compaction cannot spend this bound again.
|
|
1798
|
+
if (
|
|
1799
|
+
finalReplyNudged ||
|
|
1800
|
+
(await sessionTurnHasFinalReplyNudge(
|
|
1801
|
+
db,
|
|
1802
|
+
input.workspaceId,
|
|
1803
|
+
input.sessionId,
|
|
1804
|
+
activeTurnId,
|
|
1805
|
+
finalReplyNudge(activeTurnId).content[0]!.text,
|
|
1806
|
+
))
|
|
1807
|
+
) {
|
|
1808
|
+
// A second empty response is a delivery-quality notice, not failed
|
|
1809
|
+
// execution: preserve goal continuation and later machine-input wakes.
|
|
1810
|
+
emptyFinalReply = true;
|
|
1811
|
+
} else {
|
|
1812
|
+
const appended = await appendSessionHistoryItems(db, {
|
|
1813
|
+
accountId: input.accountId,
|
|
1814
|
+
workspaceId: input.workspaceId,
|
|
1815
|
+
sessionId: input.sessionId,
|
|
1816
|
+
turnId: activeTurnId,
|
|
1817
|
+
expectedExecutionGeneration: attempt.executionGeneration,
|
|
1818
|
+
expectedAttemptId: input.attemptId,
|
|
1819
|
+
items: [
|
|
1820
|
+
{
|
|
1821
|
+
position: await nextSessionHistoryPosition(db, input.workspaceId, input.sessionId),
|
|
1822
|
+
item: finalReplyNudge(activeTurnId),
|
|
1823
|
+
},
|
|
1824
|
+
],
|
|
1825
|
+
});
|
|
1826
|
+
if (!appended) throw new TurnAttemptFencedError("turn ended before final reply handoff");
|
|
1827
|
+
finalReplyNudged = true;
|
|
1828
|
+
await prepareRunAttemptInput();
|
|
1829
|
+
return "reply_nudge";
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
await eventing.preparedTools?.inputWaitYield?.sealForSettlement(runtimeCancellationSignal);
|
|
1742
1833
|
// The final output is the newest message this stream completed, already
|
|
1743
1834
|
// durable with its provider identity and phase. A phase-less settlement
|
|
1744
1835
|
// copy is published only when this stream did not complete that text.
|
|
@@ -1773,7 +1864,11 @@ export async function runTurnStreamAttempt(
|
|
|
1773
1864
|
: [{ type: "agent.message.completed" as const, payload: { text: finalOutput } }]),
|
|
1774
1865
|
{
|
|
1775
1866
|
type: "turn.completed",
|
|
1776
|
-
payload: {
|
|
1867
|
+
payload: {
|
|
1868
|
+
output: finalOutput,
|
|
1869
|
+
...(reply === null ? {} : { reply }),
|
|
1870
|
+
...(emptyFinalReply ? { emptyFinalReply: true } : {}),
|
|
1871
|
+
},
|
|
1777
1872
|
},
|
|
1778
1873
|
{ type: "session.status.changed", payload: { status: "idle" } },
|
|
1779
1874
|
],
|
|
@@ -1834,6 +1929,9 @@ export async function runTurnStreamAttempt(
|
|
|
1834
1929
|
) {
|
|
1835
1930
|
return claimedResult({ status: "cancelled" });
|
|
1836
1931
|
}
|
|
1932
|
+
// Preparation may have spent the last allowance on a completed summary.
|
|
1933
|
+
// Neither the title sidecar nor ordinary inference may dispatch afterward.
|
|
1934
|
+
await revalidateModelCallAdmission();
|
|
1837
1935
|
if (
|
|
1838
1936
|
turn.source !== "compaction" &&
|
|
1839
1937
|
generateSessionTitleInParallel &&
|
|
@@ -1871,11 +1969,19 @@ export async function runTurnStreamAttempt(
|
|
|
1871
1969
|
}
|
|
1872
1970
|
try {
|
|
1873
1971
|
let retriedAfterCompaction = false;
|
|
1972
|
+
finalReplyNudged = await sessionTurnHasFinalReplyNudge(
|
|
1973
|
+
db,
|
|
1974
|
+
input.workspaceId,
|
|
1975
|
+
input.sessionId,
|
|
1976
|
+
activeTurnId,
|
|
1977
|
+
finalReplyNudge(activeTurnId).content[0]!.text,
|
|
1978
|
+
);
|
|
1874
1979
|
while (true) {
|
|
1875
1980
|
try {
|
|
1876
1981
|
const result = await runStreamAttempt({
|
|
1877
1982
|
requireTerminalModelResponse: retriedAfterCompaction,
|
|
1878
1983
|
});
|
|
1984
|
+
if (result === "reply_nudge") continue;
|
|
1879
1985
|
if (retriedAfterCompaction) {
|
|
1880
1986
|
observability.info("context compaction recovery succeeded after in-activity retry", {
|
|
1881
1987
|
sessionId: input.sessionId,
|
|
@@ -68,8 +68,10 @@ import {
|
|
|
68
68
|
buildApiIntegrationMcpServers,
|
|
69
69
|
resolveCatalogSettings,
|
|
70
70
|
resolveWorkspaceModelSelection,
|
|
71
|
+
loadWorkspaceCodexModelAvailability,
|
|
71
72
|
withFrozenPersonalConnectionDelegations,
|
|
72
|
-
|
|
73
|
+
resolveTurnToolPolicy,
|
|
74
|
+
scheduledTurnMcpServerIds,
|
|
73
75
|
hasPermission,
|
|
74
76
|
} from "@opengeni/core";
|
|
75
77
|
import { loadWorkspaceEnvironmentForRunWithCredentials } from "../environment";
|
|
@@ -87,6 +89,7 @@ import { SandboxChannelAService } from "@opengeni/runtime/sandbox";
|
|
|
87
89
|
import { sandboxRunAs } from "@opengeni/runtime";
|
|
88
90
|
import {
|
|
89
91
|
DEFAULT_FIRST_PARTY_MCP_PERMISSIONS,
|
|
92
|
+
resolveAgentToolFamilies,
|
|
90
93
|
type ResourceRef,
|
|
91
94
|
type ToolAuthNeededPayload,
|
|
92
95
|
} from "@opengeni/contracts";
|
|
@@ -124,6 +127,7 @@ import { codeSearchToolDefinitions, codeSearchWorkspaceFromChannel } from "./cod
|
|
|
124
127
|
import { createWorkspaceSkillTools } from "./skill-tools";
|
|
125
128
|
import { loadConfiguredBundledSkills } from "./skill-selection";
|
|
126
129
|
import { guardSkillFilesystem } from "./skill-transfer";
|
|
130
|
+
import { turnCredentialRestriction } from "./credential-restriction";
|
|
127
131
|
|
|
128
132
|
export type PrepareTurnToolPolicyDeps = {
|
|
129
133
|
input: RunAgentTurnInput;
|
|
@@ -223,38 +227,36 @@ export async function prepareTurnToolPolicy(deps: PrepareTurnToolPolicyDeps) {
|
|
|
223
227
|
// sessions follow the current configured MCP set,
|
|
224
228
|
// while explicit, inherited-fixed, and legacy sessions remain narrowed
|
|
225
229
|
// to their stored materialized allow-list.
|
|
226
|
-
const
|
|
227
|
-
const value =
|
|
228
|
-
turn.metadata && typeof turn.metadata === "object" && !Array.isArray(turn.metadata)
|
|
229
|
-
? (turn.metadata as Record<string, unknown>).scheduledEffectiveMcpServerIds
|
|
230
|
-
: null;
|
|
231
|
-
return Array.isArray(value) && value.every((id) => typeof id === "string")
|
|
232
|
-
? [...new Set(value)].sort()
|
|
233
|
-
: null;
|
|
234
|
-
})();
|
|
235
|
-
const currentMcpServerIds = new Set(runSettings.mcpServers.map((server) => server.id));
|
|
236
|
-
const resolvedToolPolicy = resolveSessionToolPolicy({
|
|
230
|
+
const resolvedToolPolicy = resolveTurnToolPolicy({
|
|
237
231
|
toolPolicy: session.toolPolicy,
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
: [...currentMcpServerIds],
|
|
232
|
+
session,
|
|
233
|
+
turn,
|
|
234
|
+
availableMcpServerIds: runSettings.mcpServers.map((server) => server.id),
|
|
242
235
|
defaultMcpServerIds:
|
|
243
|
-
|
|
244
|
-
(session.toolPolicy.mode === "workspace_default"
|
|
236
|
+
scheduledTurnMcpServerIds(turn) === null && session.toolPolicy.mode === "workspace_default"
|
|
245
237
|
? await workspaceSessionToolPolicyDefaultServerIds(
|
|
246
238
|
db,
|
|
247
239
|
input.workspaceId,
|
|
248
240
|
capabilitySettings,
|
|
249
241
|
fileAuthoritySubjectId ?? undefined,
|
|
250
242
|
)
|
|
251
|
-
: []
|
|
243
|
+
: [],
|
|
252
244
|
});
|
|
253
245
|
const mcpAvailabilityNote = unavailableMcpOperationalContext({
|
|
254
246
|
droppedIds: resolvedToolPolicy.effectivePolicy.droppedIds,
|
|
255
247
|
droppedCount: resolvedToolPolicy.effectivePolicy.counts.dropped,
|
|
256
248
|
});
|
|
257
|
-
const
|
|
249
|
+
const families = resolveAgentToolFamilies(session.agent, {
|
|
250
|
+
productServerIds: new Set([
|
|
251
|
+
...session.mcpServers.map((server) => server.id),
|
|
252
|
+
...(session.toolPolicy.mode !== "workspace_default"
|
|
253
|
+
? session.tools.map((tool) => tool.id)
|
|
254
|
+
: []),
|
|
255
|
+
]),
|
|
256
|
+
});
|
|
257
|
+
const effectivePolicyTools = resolvedToolPolicy.toolRefs.filter(
|
|
258
|
+
(tool) => tool.kind !== "mcp" || families.allowsMcpServer(tool.id),
|
|
259
|
+
);
|
|
258
260
|
const turnTools = withFirstPartyTools(runSettings, effectivePolicyTools);
|
|
259
261
|
// §7.6 connection-credential provider — load (and decrypt) selected Variable Sets via the
|
|
260
262
|
// host `sandboxSecrets` provider when bound; unset → today's local decrypt. Preserve the
|
|
@@ -421,6 +423,7 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
|
|
|
421
423
|
});
|
|
422
424
|
const runSettings = accountRoutes.settings;
|
|
423
425
|
const turnTools = accountRoutes.tools;
|
|
426
|
+
const credentialRestriction = turnCredentialRestriction(turnExecutionPolicy, session.metadata);
|
|
424
427
|
const toolContextPreparationStartedAt = performance.now();
|
|
425
428
|
throwIfWorkerShuttingDown();
|
|
426
429
|
throwIfTurnCancelled();
|
|
@@ -590,14 +593,16 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
|
|
|
590
593
|
(permission) => hasPermission(linkedAuthority.permissions, permission),
|
|
591
594
|
)
|
|
592
595
|
: session.firstPartyMcpPermissions;
|
|
593
|
-
const
|
|
594
|
-
|
|
595
|
-
session.firstPartyMcpTools,
|
|
596
|
+
const toolFamilies = resolveAgentToolFamilies(session.agent);
|
|
597
|
+
const selectedFirstPartyMcpTools = toolFamilies.firstPartyTools(
|
|
598
|
+
allowedFirstPartyMcpToolsForSession(runSettings, session.firstPartyMcpTools),
|
|
596
599
|
);
|
|
597
600
|
const titleToolPlan = sessionTitleToolPlan({
|
|
601
|
+
agentConfig: session.agent,
|
|
598
602
|
tools: turnTools,
|
|
599
603
|
selectedFirstPartyMcpTools,
|
|
600
604
|
shouldRequestTitle: shouldRequestMissingSessionTitle({
|
|
605
|
+
agentConfig: session.agent,
|
|
601
606
|
title: session.title,
|
|
602
607
|
titleSource: session.titleSource,
|
|
603
608
|
firstPartyMcpTools: selectedFirstPartyMcpTools,
|
|
@@ -669,20 +674,23 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
|
|
|
669
674
|
workspaceId: input.workspaceId,
|
|
670
675
|
...(deps.fileAuthoritySubjectId ? { subjectId: deps.fileAuthoritySubjectId } : {}),
|
|
671
676
|
});
|
|
672
|
-
const skillCatalog =
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
677
|
+
const skillCatalog =
|
|
678
|
+
toolFamilies.skills === false
|
|
679
|
+
? []
|
|
680
|
+
: [
|
|
681
|
+
...sharedSkillDescriptors
|
|
682
|
+
.filter((entry) => entry.activationMode === "workspace_managed")
|
|
683
|
+
.map((entry) => ({
|
|
684
|
+
id: entry.id,
|
|
685
|
+
name: entry.title,
|
|
686
|
+
description: entry.description,
|
|
687
|
+
})),
|
|
688
|
+
...selectedSkills.map((entry) => ({
|
|
689
|
+
id: entry.id,
|
|
690
|
+
name: entry.artifact.name,
|
|
691
|
+
description: entry.artifact.description || entry.artifact.name,
|
|
692
|
+
})),
|
|
693
|
+
];
|
|
686
694
|
const skillTools = createWorkspaceSkillTools({
|
|
687
695
|
db,
|
|
688
696
|
settings: runSettings,
|
|
@@ -861,6 +869,9 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
|
|
|
861
869
|
);
|
|
862
870
|
},
|
|
863
871
|
});
|
|
872
|
+
const attemptToolFamilies = resolveAgentToolFamilies(session.agent, {
|
|
873
|
+
hasSkills: skillCatalog.length > 0,
|
|
874
|
+
});
|
|
864
875
|
const attemptToolDefinitions = [
|
|
865
876
|
...(operationReadStore
|
|
866
877
|
? [
|
|
@@ -908,6 +919,7 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
|
|
|
908
919
|
organizationGatewayCustomModels,
|
|
909
920
|
organizationOpenRouterConnectionActive,
|
|
910
921
|
organizationOpenRouterCustomModels,
|
|
922
|
+
codexModelAvailability,
|
|
911
923
|
] = await Promise.all([
|
|
912
924
|
getWorkspaceConnectionModelRestrictions(
|
|
913
925
|
db,
|
|
@@ -953,9 +965,11 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
|
|
|
953
965
|
workspaceId: input.workspaceId,
|
|
954
966
|
providerKind: "openrouter",
|
|
955
967
|
}),
|
|
968
|
+
loadWorkspaceCodexModelAvailability(db, currentSettings, input.workspaceId),
|
|
956
969
|
]);
|
|
957
970
|
return {
|
|
958
971
|
selections: resolveWorkspaceModelSelection({
|
|
972
|
+
observations: codexModelAvailability,
|
|
959
973
|
connectionModelRestrictions,
|
|
960
974
|
settings: currentSettings,
|
|
961
975
|
policy,
|
|
@@ -997,6 +1011,7 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
|
|
|
997
1011
|
: []),
|
|
998
1012
|
...createFirstPartyInteractionAttemptToolDefinitions({
|
|
999
1013
|
settings: runSettings,
|
|
1014
|
+
...(credentialRestriction ? { credentialRestriction } : {}),
|
|
1000
1015
|
scope: {
|
|
1001
1016
|
accountId: input.accountId,
|
|
1002
1017
|
workspaceId: input.workspaceId,
|
|
@@ -1017,7 +1032,7 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
|
|
|
1017
1032
|
? [googleDrivePublicationTool]
|
|
1018
1033
|
: []),
|
|
1019
1034
|
...codeSearchTools,
|
|
1020
|
-
];
|
|
1035
|
+
].filter((tool) => attemptToolFamilies.allowsFunctionTool(tool.modelName));
|
|
1021
1036
|
recordTurnStartupPhase(observability, {
|
|
1022
1037
|
phase: "tool_context_preparation",
|
|
1023
1038
|
provider: turnExecutionPolicy.providerId,
|
|
@@ -1092,6 +1107,7 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
|
|
|
1092
1107
|
try {
|
|
1093
1108
|
eventing.preparedTools = await waitForTurnOperation(
|
|
1094
1109
|
runtime.prepareTools(githubRestMcp.settings, githubRestMcp.tools, {
|
|
1110
|
+
...(credentialRestriction ? { credentialRestriction } : {}),
|
|
1095
1111
|
mcpAccountLabels: accountRoutes.accountLabels,
|
|
1096
1112
|
accountId: input.accountId,
|
|
1097
1113
|
workspaceId: input.workspaceId,
|
|
@@ -1110,7 +1126,7 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
|
|
|
1110
1126
|
sessionAttachedRemoteMcpTargets: selectedSessionRemoteMcpTargets(
|
|
1111
1127
|
githubRestMcp.settings,
|
|
1112
1128
|
session.mcpServers ?? [],
|
|
1113
|
-
|
|
1129
|
+
githubRestMcp.tools,
|
|
1114
1130
|
localMcpServers,
|
|
1115
1131
|
),
|
|
1116
1132
|
...(deps.runMcpCredentials ? { runMcpCredentials: deps.runMcpCredentials } : {}),
|
|
@@ -48,6 +48,7 @@ export type TurnSettleFn = (input: {
|
|
|
48
48
|
sessionStatus: SessionStatus;
|
|
49
49
|
activeTurnId: string | null;
|
|
50
50
|
suppressGoalContinuation?: boolean;
|
|
51
|
+
allowanceGoalPause?: ApplySessionTurnSettlementInput["allowanceGoalPause"];
|
|
51
52
|
consumeRequestedCompactionFailure?: boolean;
|
|
52
53
|
runState?: ApplySessionTurnSettlementInput["runState"];
|
|
53
54
|
}) => Promise<boolean>;
|