@opengeni/worker-bundle 1.0.0-canary.0 → 1.0.1-canary.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/errors.d.ts +6 -0
- 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/quiescence.d.ts +1 -0
- package/dist/activities/agent-turn/turn-context.d.ts +2 -0
- package/dist/{activities-control-HMFGSKC5.js → activities-control-O5U6OLT2.js} +4 -19
- package/dist/{activities-control-HMFGSKC5.js.map → activities-control-O5U6OLT2.js.map} +1 -1
- package/dist/{activities-turn-AP67JADS.js → activities-turn-OAPNO2IH.js} +167 -116
- package/dist/activities-turn-OAPNO2IH.js.map +1 -0
- package/dist/{chunk-CYITU5UH.js → chunk-GENYJQ5B.js} +18 -1
- package/dist/{chunk-CYITU5UH.js.map → chunk-GENYJQ5B.js.map} +1 -1
- package/dist/index.js +1 -1
- package/package.json +19 -19
- package/src/activities/agent-turn/claim.ts +1 -0
- package/src/activities/agent-turn/errors.ts +21 -0
- package/src/activities/agent-turn/finalization-monitor.ts +121 -0
- package/src/activities/agent-turn/finalization.ts +52 -17
- package/src/activities/agent-turn/quiescence.ts +3 -0
- package/src/activities/agent-turn/run.ts +1 -39
- package/src/activities/agent-turn/skill-selection.ts +1 -0
- package/src/activities/agent-turn/tool-environment.ts +10 -2
- package/src/activities/agent-turn/turn-context.ts +3 -0
- package/src/activities/workflow-wake.ts +2 -17
- package/dist/activities/agent-turn/knowledge-sources.d.ts +0 -20
- package/dist/activities-turn-AP67JADS.js.map +0 -1
- package/src/activities/agent-turn/knowledge-sources.ts +0 -86
package/dist/index.js
CHANGED
|
@@ -784,7 +784,7 @@ function temporalWorkflowExecutionNotFound(error) {
|
|
|
784
784
|
return isGrpcServiceError(error) && error.code === 5;
|
|
785
785
|
}
|
|
786
786
|
async function createDefaultWorkerActivities(role, dependencies = {}) {
|
|
787
|
-
return role === "control" ? (await import("./activities-control-
|
|
787
|
+
return role === "control" ? (await import("./activities-control-O5U6OLT2.js")).createControlActivities(dependencies) : (await import("./activities-turn-OAPNO2IH.js")).createTurnActivities(dependencies);
|
|
788
788
|
}
|
|
789
789
|
function resolveOpenGeniWorkflowDefinition(moduleUrl = import.meta.url) {
|
|
790
790
|
const modulePath = fileURLToPath(moduleUrl).replaceAll("\\", "/");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/worker-bundle",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1-canary.0",
|
|
4
4
|
"description": "OpenGeni worker entry and reusable embedded lifecycle, shipped with a release-coherent pre-bundled Temporal workflow artifact.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -54,24 +54,24 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@llamaindex/liteparse": "2.14.2",
|
|
57
|
-
"@opengeni/agent-proto": "^0.6.0-canary.
|
|
58
|
-
"@opengeni/capabilities": "^0.3.
|
|
59
|
-
"@opengeni/codemode": "^0.5.
|
|
60
|
-
"@opengeni/codex": "^0.2.23-canary.
|
|
61
|
-
"@opengeni/config": "^1.2.
|
|
62
|
-
"@opengeni/contracts": "^4.
|
|
63
|
-
"@opengeni/core": "^3.0.
|
|
64
|
-
"@opengeni/db": "^5.0.
|
|
65
|
-
"@opengeni/documents": "^0.8.
|
|
66
|
-
"@opengeni/events": "^0.4.
|
|
67
|
-
"@opengeni/github": "^0.7.
|
|
68
|
-
"@opengeni/network": "^0.3.1-canary.
|
|
69
|
-
"@opengeni/observability": "^0.8.
|
|
70
|
-
"@opengeni/runtime": "^3.0.
|
|
71
|
-
"@opengeni/sdk": "^6.
|
|
72
|
-
"@opengeni/storage": "^0.2.
|
|
73
|
-
"@opengeni/tool-gateway": "^0.1.
|
|
74
|
-
"@opengeni/xai-subscription": "^0.1.4-canary.
|
|
57
|
+
"@opengeni/agent-proto": "^0.6.0-canary.5",
|
|
58
|
+
"@opengeni/capabilities": "^0.3.4-canary.0",
|
|
59
|
+
"@opengeni/codemode": "^0.5.9-canary.0",
|
|
60
|
+
"@opengeni/codex": "^0.2.23-canary.2",
|
|
61
|
+
"@opengeni/config": "^1.2.2-canary.0",
|
|
62
|
+
"@opengeni/contracts": "^4.1.0-canary.0",
|
|
63
|
+
"@opengeni/core": "^3.0.1-canary.0",
|
|
64
|
+
"@opengeni/db": "^5.0.1-canary.0",
|
|
65
|
+
"@opengeni/documents": "^0.8.30-canary.0",
|
|
66
|
+
"@opengeni/events": "^0.4.28-canary.0",
|
|
67
|
+
"@opengeni/github": "^0.7.13-canary.0",
|
|
68
|
+
"@opengeni/network": "^0.3.1-canary.17",
|
|
69
|
+
"@opengeni/observability": "^0.8.29-canary.0",
|
|
70
|
+
"@opengeni/runtime": "^3.0.1-canary.0",
|
|
71
|
+
"@opengeni/sdk": "^6.1.0-canary.0",
|
|
72
|
+
"@opengeni/storage": "^0.2.130-canary.0",
|
|
73
|
+
"@opengeni/tool-gateway": "^0.1.10-canary.0",
|
|
74
|
+
"@opengeni/xai-subscription": "^0.1.4-canary.17",
|
|
75
75
|
"@temporalio/activity": "^1.17.0",
|
|
76
76
|
"@temporalio/client": "^1.17.0",
|
|
77
77
|
"@temporalio/worker": "^1.17.0",
|
|
@@ -387,6 +387,7 @@ export async function claimTurnAttempt(deps: ClaimTurnDeps): Promise<ClaimTurnOu
|
|
|
387
387
|
turnId: attempt.turnId,
|
|
388
388
|
opAcks: {},
|
|
389
389
|
};
|
|
390
|
+
eventing.heartbeatDetails = heartbeatDetails;
|
|
390
391
|
const opJournal = makeTurnOpJournal(activityContext, heartbeatDetails);
|
|
391
392
|
eventing.heartbeatTimer = startActivityHeartbeat(activityContext, heartbeatDetails);
|
|
392
393
|
let producerSeq = 0;
|
|
@@ -15,6 +15,9 @@ import {
|
|
|
15
15
|
isMcpTransportConnectivityError,
|
|
16
16
|
isModalTaskExecStartDnsResolutionError,
|
|
17
17
|
RoutingWorkspaceRootChangedError,
|
|
18
|
+
SandboxMaterializationVerificationError,
|
|
19
|
+
materializationVerificationDiagnostic,
|
|
20
|
+
type MaterializationVerificationDiagnostic,
|
|
18
21
|
SelfhostedWorkspaceRootChangedError,
|
|
19
22
|
UNKNOWN_MODEL_FINISH_REASON_CODE,
|
|
20
23
|
} from "@opengeni/runtime";
|
|
@@ -851,6 +854,15 @@ function findPostgresDriverError(error: unknown): Record<string, unknown> | null
|
|
|
851
854
|
export function agentRunFailurePayload(
|
|
852
855
|
error: unknown,
|
|
853
856
|
options: { isCodexTurn?: boolean } = {},
|
|
857
|
+
): ReturnType<typeof baseAgentRunFailurePayload> {
|
|
858
|
+
const failure = baseAgentRunFailurePayload(error, options);
|
|
859
|
+
const diagnostic = materializationVerificationDiagnostic(error);
|
|
860
|
+
return diagnostic ? { ...failure, materializationDiagnostic: diagnostic } : failure;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function baseAgentRunFailurePayload(
|
|
864
|
+
error: unknown,
|
|
865
|
+
options: { isCodexTurn?: boolean } = {},
|
|
854
866
|
): {
|
|
855
867
|
error: string;
|
|
856
868
|
code?: string;
|
|
@@ -870,7 +882,16 @@ export function agentRunFailurePayload(
|
|
|
870
882
|
database?: Record<string, string>;
|
|
871
883
|
historyPersistenceStage?: MandatoryHistoryPersistenceStage;
|
|
872
884
|
mcpTransportDiagnostic?: McpTransportRequestFailureDiagnostic;
|
|
885
|
+
materializationDiagnostic?: MaterializationVerificationDiagnostic;
|
|
873
886
|
} {
|
|
887
|
+
if (error instanceof SandboxMaterializationVerificationError) {
|
|
888
|
+
return {
|
|
889
|
+
error: error.message,
|
|
890
|
+
code: error.code,
|
|
891
|
+
retryable: false,
|
|
892
|
+
materializationDiagnostic: error.diagnostic,
|
|
893
|
+
};
|
|
894
|
+
}
|
|
874
895
|
if (error instanceof RetainedAttachmentTransportLimitError) {
|
|
875
896
|
return { error: error.message, code: "retained_attachment_transport_limit", retryable: false };
|
|
876
897
|
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { Observability } from "@opengeni/observability";
|
|
2
|
+
import type { TurnHeartbeatDetails } from "../../op-journal";
|
|
3
|
+
import { armTurnQuiescenceWatchdog } from "./quiescence";
|
|
4
|
+
|
|
5
|
+
export const TURN_FINALIZATION_STAGES = [
|
|
6
|
+
"tool_writers",
|
|
7
|
+
"credential_renewals",
|
|
8
|
+
"credential_cleanup",
|
|
9
|
+
"quiescence_receipt",
|
|
10
|
+
"event_flush",
|
|
11
|
+
"provider_leases",
|
|
12
|
+
"workspace_capture",
|
|
13
|
+
"tool_close",
|
|
14
|
+
"sandbox_provisioning",
|
|
15
|
+
"sandbox_rotation",
|
|
16
|
+
"workspace_snapshot",
|
|
17
|
+
"sandbox_release",
|
|
18
|
+
] as const;
|
|
19
|
+
export type TurnFinalizationStage = (typeof TURN_FINALIZATION_STAGES)[number];
|
|
20
|
+
|
|
21
|
+
const INFLIGHT = {
|
|
22
|
+
name: "opengeni_turn_finalization_inflight",
|
|
23
|
+
help: "Physical turn finalizers currently waiting in each cleanup stage.",
|
|
24
|
+
};
|
|
25
|
+
const SLOW_STAGES = {
|
|
26
|
+
name: "opengeni_turn_finalization_slow_total",
|
|
27
|
+
help: "Turn cleanup stages observed to exceed thirty seconds.",
|
|
28
|
+
};
|
|
29
|
+
const initialized = new WeakSet<Observability>();
|
|
30
|
+
function diagnose(operation: () => void): void {
|
|
31
|
+
try {
|
|
32
|
+
operation();
|
|
33
|
+
} catch {
|
|
34
|
+
// Diagnostics cannot own physical cleanup or containment.
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Starts only after agent execution ends. A cleanup-stage deadline never caps
|
|
39
|
+
* agent work and never detaches a physical writer to manufacture quiescence. */
|
|
40
|
+
export function startTurnFinalizationMonitor(input: {
|
|
41
|
+
observability: Observability;
|
|
42
|
+
details: TurnHeartbeatDetails;
|
|
43
|
+
heartbeat: (details: TurnHeartbeatDetails) => void;
|
|
44
|
+
terminateWorker: () => void;
|
|
45
|
+
timeoutMs?: number;
|
|
46
|
+
slowAfterMs?: number;
|
|
47
|
+
}) {
|
|
48
|
+
const { observability, details } = input;
|
|
49
|
+
if (!initialized.has(observability)) {
|
|
50
|
+
for (const stage of TURN_FINALIZATION_STAGES) {
|
|
51
|
+
diagnose(() => observability.incrementGauge({ ...INFLIGHT, labels: { stage }, amount: 0 }));
|
|
52
|
+
diagnose(() =>
|
|
53
|
+
observability.incrementCounter({ ...SLOW_STAGES, labels: { stage }, amount: 0 }),
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
initialized.add(observability);
|
|
57
|
+
}
|
|
58
|
+
let stage: TurnFinalizationStage | null = null;
|
|
59
|
+
let disarm = () => {};
|
|
60
|
+
let stopped = false;
|
|
61
|
+
let slowTimer: ReturnType<typeof setTimeout> | undefined;
|
|
62
|
+
const leave = () => {
|
|
63
|
+
disarm();
|
|
64
|
+
clearTimeout(slowTimer);
|
|
65
|
+
if (stage) {
|
|
66
|
+
const previous = stage;
|
|
67
|
+
diagnose(() =>
|
|
68
|
+
observability.incrementGauge({ ...INFLIGHT, labels: { stage: previous }, amount: -1 }),
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
stage = null;
|
|
72
|
+
};
|
|
73
|
+
return {
|
|
74
|
+
enter(next: TurnFinalizationStage) {
|
|
75
|
+
if (stopped || stage === next) return;
|
|
76
|
+
leave();
|
|
77
|
+
stage = next;
|
|
78
|
+
details.phase = "finalizing";
|
|
79
|
+
details.finalizationStage = next;
|
|
80
|
+
details.finalizationStageStartedAt = new Date().toISOString();
|
|
81
|
+
diagnose(() => observability.incrementGauge({ ...INFLIGHT, labels: { stage: next } }));
|
|
82
|
+
disarm = armTurnQuiescenceWatchdog({
|
|
83
|
+
enabled: true,
|
|
84
|
+
...(input.timeoutMs === undefined ? {} : { timeoutMs: input.timeoutMs }),
|
|
85
|
+
onTimeout: () => {
|
|
86
|
+
try {
|
|
87
|
+
observability.error("turn finalization stalled; containing worker", {
|
|
88
|
+
surface: "turn_finalization",
|
|
89
|
+
outcome: "containment",
|
|
90
|
+
reason: next,
|
|
91
|
+
});
|
|
92
|
+
} catch {
|
|
93
|
+
// Diagnostic failure must never disable physical containment.
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
terminateWorker: input.terminateWorker,
|
|
97
|
+
});
|
|
98
|
+
// Record the precursor while the process remains scrapeable. A counter
|
|
99
|
+
// increment immediately before process.exit can never reach Prometheus.
|
|
100
|
+
slowTimer = setTimeout(
|
|
101
|
+
() =>
|
|
102
|
+
diagnose(() => {
|
|
103
|
+
observability.incrementCounter({ ...SLOW_STAGES, labels: { stage: next } });
|
|
104
|
+
observability.warn("turn finalization is taking longer than expected", {
|
|
105
|
+
surface: "turn_finalization",
|
|
106
|
+
outcome: "slow",
|
|
107
|
+
reason: next,
|
|
108
|
+
});
|
|
109
|
+
}),
|
|
110
|
+
input.slowAfterMs ?? 30_000,
|
|
111
|
+
);
|
|
112
|
+
slowTimer.unref?.();
|
|
113
|
+
diagnose(() => input.heartbeat(details));
|
|
114
|
+
},
|
|
115
|
+
stop() {
|
|
116
|
+
if (stopped) return;
|
|
117
|
+
stopped = true;
|
|
118
|
+
leave();
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -13,7 +13,8 @@ import { clearRunCredentialsForAttempt } from "@opengeni/runtime";
|
|
|
13
13
|
import { fetchXaiSubscriptionQuota } from "@opengeni/xai-subscription";
|
|
14
14
|
import type { Settings } from "@opengeni/config";
|
|
15
15
|
import { signalCodexCapacityWakeTargets } from "../codex-capacity";
|
|
16
|
-
import type
|
|
16
|
+
import { startActivityHeartbeat, type currentActivityContext } from "../streaming";
|
|
17
|
+
import { startTurnFinalizationMonitor } from "./finalization-monitor";
|
|
17
18
|
import type { CodemodeTokenRenewalController } from "../codemode-token-renewal";
|
|
18
19
|
import type { RunCredentialRenewalController } from "../run-credential-renewal";
|
|
19
20
|
import type {
|
|
@@ -43,7 +44,6 @@ import { safeErrorDiagnostic, safeErrorForTelemetry } from "./errors";
|
|
|
43
44
|
import {
|
|
44
45
|
assertPhysicalToolQuiescenceForCancellation,
|
|
45
46
|
assertSessionAttemptQuiescenceRecoveryDurable,
|
|
46
|
-
armTurnQuiescenceWatchdog,
|
|
47
47
|
clearAttemptCredentialsWithSettledFence,
|
|
48
48
|
drainAttemptOwnedSandboxWriters,
|
|
49
49
|
persistOrSignalSessionAttemptQuiescence,
|
|
@@ -99,6 +99,42 @@ export type TurnFinalizationDeps = {
|
|
|
99
99
|
};
|
|
100
100
|
|
|
101
101
|
export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<void> {
|
|
102
|
+
const details = deps.eventing.heartbeatDetails ?? {
|
|
103
|
+
sessionId: deps.input.sessionId,
|
|
104
|
+
turnId: deps.attempt.turnId,
|
|
105
|
+
opAcks: {},
|
|
106
|
+
};
|
|
107
|
+
deps.eventing.heartbeatDetails = details;
|
|
108
|
+
// Keep one heartbeat owner through all physical cleanup, with the same op
|
|
109
|
+
// acknowledgements used during execution. It must no longer say "running".
|
|
110
|
+
if (deps.eventing.heartbeatTimer) clearInterval(deps.eventing.heartbeatTimer);
|
|
111
|
+
deps.eventing.heartbeatTimer = startActivityHeartbeat(deps.activityContext, details);
|
|
112
|
+
const monitor = startTurnFinalizationMonitor({
|
|
113
|
+
observability: deps.observability,
|
|
114
|
+
details,
|
|
115
|
+
heartbeat: (value) => {
|
|
116
|
+
try {
|
|
117
|
+
deps.activityContext?.heartbeat({ ...value, at: new Date().toISOString() });
|
|
118
|
+
} catch {
|
|
119
|
+
// A closed Temporal transport is not proof of physical quiescence.
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
terminateWorker: () => process.exit(1),
|
|
123
|
+
});
|
|
124
|
+
try {
|
|
125
|
+
monitor.enter("tool_writers");
|
|
126
|
+
await finalizeTurnAttemptSteps(deps, monitor);
|
|
127
|
+
} finally {
|
|
128
|
+
monitor.stop();
|
|
129
|
+
if (deps.eventing.heartbeatTimer) clearInterval(deps.eventing.heartbeatTimer);
|
|
130
|
+
deps.eventing.heartbeatTimer = undefined;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function finalizeTurnAttemptSteps(
|
|
135
|
+
deps: TurnFinalizationDeps,
|
|
136
|
+
monitor: ReturnType<typeof startTurnFinalizationMonitor>,
|
|
137
|
+
): Promise<void> {
|
|
102
138
|
const {
|
|
103
139
|
input,
|
|
104
140
|
settings,
|
|
@@ -142,17 +178,6 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
|
|
|
142
178
|
let finalizationError: unknown;
|
|
143
179
|
let physicalToolQuiescenceConfirmed = !control.acknowledgeQuiescence;
|
|
144
180
|
let quiescenceReceiptOrProofDurable = !control.acknowledgeQuiescence;
|
|
145
|
-
const disarmQuiescenceWatchdog = armTurnQuiescenceWatchdog({
|
|
146
|
-
enabled: control.acknowledgeQuiescence,
|
|
147
|
-
onTimeout: () => {
|
|
148
|
-
observability.error("turn quiescence drain exceeded hard containment deadline", {
|
|
149
|
-
"opengeni.session_id": input.sessionId,
|
|
150
|
-
"opengeni.turn_id": attempt.turnId ?? "",
|
|
151
|
-
"opengeni.attempt_id": input.attemptId,
|
|
152
|
-
});
|
|
153
|
-
},
|
|
154
|
-
terminateWorker: () => process.exit(1),
|
|
155
|
-
});
|
|
156
181
|
const finalizerSignal = turnFinalizerCancellationSignal(
|
|
157
182
|
cancellationSignal,
|
|
158
183
|
control.activityStatus,
|
|
@@ -200,6 +225,7 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
|
|
|
200
225
|
// and durably settled before workspace capture. Only receipt
|
|
201
226
|
// publication remains conditional on acknowledgeQuiescence.
|
|
202
227
|
toolCancellationFence,
|
|
228
|
+
onStage: monitor.enter,
|
|
203
229
|
cancellationReason: cancellationSignal?.reason ?? new Error("TURN_ATTEMPT_FINALIZED"),
|
|
204
230
|
gitCredentialRenewals: gitRenewalsToStop,
|
|
205
231
|
codemodeTokenRenewal: codemodeRenewalToStop,
|
|
@@ -211,6 +237,7 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
|
|
|
211
237
|
// attempt's generation before publishing physical quiescence. A failure
|
|
212
238
|
// deliberately keeps the receipt closed.
|
|
213
239
|
if (credentialSessionToClear) {
|
|
240
|
+
monitor.enter("credential_cleanup");
|
|
214
241
|
const clearAttemptCredentials = async (): Promise<void> =>
|
|
215
242
|
await clearRunCredentialsForAttempt(credentialSessionToClear, {
|
|
216
243
|
sessionId: input.sessionId,
|
|
@@ -258,6 +285,7 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
|
|
|
258
285
|
// and before lease, cache, recording, or provider cleanup. Its
|
|
259
286
|
// transaction also enqueues the exact workflow wake that will admit
|
|
260
287
|
// the replacement; Temporal activity terminalization does neither.
|
|
288
|
+
monitor.enter("quiescence_receipt");
|
|
261
289
|
const proof: SessionAttemptQuiescenceProof = {
|
|
262
290
|
accountId: input.accountId,
|
|
263
291
|
workspaceId: input.workspaceId,
|
|
@@ -304,6 +332,7 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
|
|
|
304
332
|
signalProof: signalSessionAttemptQuiesced,
|
|
305
333
|
heartbeat: (deliveryAttempt, retryMs) => {
|
|
306
334
|
activityContext?.heartbeat({
|
|
335
|
+
...eventing.heartbeatDetails,
|
|
307
336
|
phase: "quiescence-proof-delivery",
|
|
308
337
|
sessionId: input.sessionId,
|
|
309
338
|
attemptId: input.attemptId,
|
|
@@ -336,7 +365,6 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
|
|
|
336
365
|
},
|
|
337
366
|
});
|
|
338
367
|
quiescenceReceiptOrProofDurable = true;
|
|
339
|
-
disarmQuiescenceWatchdog();
|
|
340
368
|
if (recoveryMode === "signal") {
|
|
341
369
|
observability.info("agent turn quiescence proof handed to workflow recovery", {
|
|
342
370
|
"opengeni.session_id": input.sessionId,
|
|
@@ -351,6 +379,7 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
|
|
|
351
379
|
// the turn. Sync observer → buffer → single awaited append here (no unawaited
|
|
352
380
|
// DB write inside the activity). Scoped to this turn; skipped if no turnId
|
|
353
381
|
// (the op ran under a turn, so on the normal path turnId is set).
|
|
382
|
+
monitor.enter("event_flush");
|
|
354
383
|
const machineOpEvents = machineOpObserver.drainEvents();
|
|
355
384
|
if (machineOpEvents.length > 0 && attempt.turnId && attempt.executionGeneration > 0) {
|
|
356
385
|
await waitForTurnFinalizerStep(
|
|
@@ -377,6 +406,7 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
|
|
|
377
406
|
// Multi-account P4: flush the serving account's free per-turn caches ONCE,
|
|
378
407
|
// best-effort (same discipline as today's usage write). Both writers skip
|
|
379
408
|
// version/updatedAt, so neither can race the token-refresh CAS.
|
|
409
|
+
monitor.enter("provider_leases");
|
|
380
410
|
if (providerTurn.effectiveCodexCredentialId) {
|
|
381
411
|
// Part A: the latest scraped usage-header snapshot → the P2 usage cache. A
|
|
382
412
|
// full both-windows snapshot (parseCodexUsageHeaders gates on both), so this
|
|
@@ -502,6 +532,7 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
|
|
|
502
532
|
) {
|
|
503
533
|
// Review reads yield to queued work. Recovery snapshots have their own
|
|
504
534
|
// physical-settlement requirement and are drained later in finalization.
|
|
535
|
+
monitor.enter("workspace_capture");
|
|
505
536
|
sandboxState.turnEndCaptureInProgress = true;
|
|
506
537
|
const captureSandbox = sandboxState.resolvedSandbox;
|
|
507
538
|
const captureSession = sandboxState.setupBoxSession as ChannelASession;
|
|
@@ -549,6 +580,7 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
|
|
|
549
580
|
},
|
|
550
581
|
});
|
|
551
582
|
}
|
|
583
|
+
monitor.enter("tool_close");
|
|
552
584
|
eventing.toolPreparationClosing = true;
|
|
553
585
|
if (eventing.toolPreparationReady) {
|
|
554
586
|
await waitForTurnFinalizerStep(
|
|
@@ -569,9 +601,7 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
|
|
|
569
601
|
finalizerSignal,
|
|
570
602
|
);
|
|
571
603
|
}
|
|
572
|
-
|
|
573
|
-
clearInterval(eventing.heartbeatTimer);
|
|
574
|
-
}
|
|
604
|
+
monitor.enter("sandbox_provisioning");
|
|
575
605
|
if (sandboxState.turnSandboxProvisioner?.hasStarted()) {
|
|
576
606
|
await waitForTurnFinalizerStep(
|
|
577
607
|
sandboxState.turnSandboxProvisioner.waitForSettled(30_000),
|
|
@@ -599,12 +629,14 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
|
|
|
599
629
|
if (sandboxState.leaseHeartbeatTimer) {
|
|
600
630
|
stopLeaseHeartbeat();
|
|
601
631
|
}
|
|
632
|
+
monitor.enter("sandbox_rotation");
|
|
602
633
|
if (sandboxState.rotationPreemptionInFlight) {
|
|
603
634
|
await sandboxState.rotationPreemptionInFlight.catch(() => undefined);
|
|
604
635
|
}
|
|
605
636
|
// No heartbeat can register another capture after this point. Join the
|
|
606
637
|
// actual provider continuation before either starting a turn-end capture
|
|
607
638
|
// or entering the proof-bearing holder release.
|
|
639
|
+
monitor.enter("workspace_snapshot");
|
|
608
640
|
await drainInFlightWarmSnapshot();
|
|
609
641
|
if (sandboxState.resolvedSandbox) {
|
|
610
642
|
// TURN-END mid-session snapshot (sandbox-file-persistence): fold the
|
|
@@ -667,10 +699,13 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
|
|
|
667
699
|
// Temporal cancellation: the eager listener may already have dropped
|
|
668
700
|
// the holder, and this idempotent proof-bearing pass still must run.
|
|
669
701
|
stopLeaseHeartbeat();
|
|
702
|
+
monitor.enter("sandbox_rotation");
|
|
670
703
|
if (sandboxState.rotationPreemptionInFlight) {
|
|
671
704
|
await sandboxState.rotationPreemptionInFlight.catch(() => undefined);
|
|
672
705
|
}
|
|
706
|
+
monitor.enter("workspace_snapshot");
|
|
673
707
|
await drainInFlightWarmSnapshot();
|
|
708
|
+
monitor.enter("sandbox_release");
|
|
674
709
|
const sandboxReleaseTargets = new Set(sandboxState.lateSandboxesAwaitingWriterDrain);
|
|
675
710
|
sandboxState.lateSandboxesAwaitingWriterDrain.clear();
|
|
676
711
|
if (sandboxState.resolvedSandbox) {
|
|
@@ -198,13 +198,16 @@ export async function drainAttemptOwnedSandboxWriters(input: {
|
|
|
198
198
|
gitCredentialRenewals: readonly Pick<GitCredentialRenewalController, "stop">[];
|
|
199
199
|
codemodeTokenRenewal: Pick<CodemodeTokenRenewalController, "stop"> | null;
|
|
200
200
|
runCredentialRenewal: Pick<RunCredentialRenewalController, "stop"> | null;
|
|
201
|
+
onStage?: (stage: "tool_writers" | "credential_renewals") => void;
|
|
201
202
|
}): Promise<void> {
|
|
203
|
+
input.onStage?.("tool_writers");
|
|
202
204
|
if (input.toolCancellationFence) {
|
|
203
205
|
input.toolCancellationFence.cancel(
|
|
204
206
|
input.cancellationReason ?? new Error("TURN_ATTEMPT_FENCED"),
|
|
205
207
|
);
|
|
206
208
|
await input.toolCancellationFence.waitForQuiescence();
|
|
207
209
|
}
|
|
210
|
+
input.onStage?.("credential_renewals");
|
|
208
211
|
await Promise.all(input.gitCredentialRenewals.map(async (renewal) => await renewal.stop()));
|
|
209
212
|
await input.codemodeTokenRenewal?.stop();
|
|
210
213
|
await input.runCredentialRenewal?.stop();
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { createKnowledgeSourceSyncActivities } from "../knowledge-source-sync";
|
|
2
|
-
import { prepareTurnKnowledgeSources } from "./knowledge-sources";
|
|
3
2
|
import {
|
|
4
3
|
assertModelConnectionAllowsTurn,
|
|
5
4
|
freezeAgentLearningPolicy,
|
|
@@ -1030,44 +1029,7 @@ export function createRunAgentTurnActivity(services: () => Promise<ActivityServi
|
|
|
1030
1029
|
knowledgeRecoveryNote =
|
|
1031
1030
|
"[OpenGeni confirmation recovery unavailable] Do not assume an earlier remember confirmation was published. Inspect the current Knowledge or instruction review.";
|
|
1032
1031
|
}
|
|
1033
|
-
const
|
|
1034
|
-
settings: runSettings,
|
|
1035
|
-
scope: {
|
|
1036
|
-
accountId: input.accountId,
|
|
1037
|
-
workspaceId: input.workspaceId,
|
|
1038
|
-
sessionId: input.sessionId,
|
|
1039
|
-
turnId: turn.id,
|
|
1040
|
-
attemptId: input.attemptId,
|
|
1041
|
-
executionGeneration: attempt.executionGeneration,
|
|
1042
|
-
},
|
|
1043
|
-
// Only attachments on this accepted turn are new source inputs.
|
|
1044
|
-
// Session-level file receipts remain historical context and must
|
|
1045
|
-
// never trigger metadata reads, downloads, or re-preparation.
|
|
1046
|
-
resources: turn.resources,
|
|
1047
|
-
selectedTools: session.firstPartyMcpTools,
|
|
1048
|
-
permissions: session.firstPartyMcpPermissions,
|
|
1049
|
-
learningMode: learning.effective.knowledge,
|
|
1050
|
-
signal: cancellationSignal,
|
|
1051
|
-
onOutcome: async (outcome) => {
|
|
1052
|
-
if (outcome.status === "disabled") return;
|
|
1053
|
-
await eventing.publish!(
|
|
1054
|
-
[
|
|
1055
|
-
{
|
|
1056
|
-
type:
|
|
1057
|
-
outcome.status === "failed"
|
|
1058
|
-
? "knowledge.source.failed"
|
|
1059
|
-
: "knowledge.source.prepared",
|
|
1060
|
-
payload: outcome,
|
|
1061
|
-
},
|
|
1062
|
-
],
|
|
1063
|
-
true,
|
|
1064
|
-
);
|
|
1065
|
-
},
|
|
1066
|
-
});
|
|
1067
|
-
|
|
1068
|
-
const knowledgeSourcePreparationNote =
|
|
1069
|
-
[knowledgeRecoveryNote, attachmentPreparationNote].filter(Boolean).join("\n\n") ||
|
|
1070
|
-
undefined;
|
|
1032
|
+
const knowledgeSourcePreparationNote = knowledgeRecoveryNote;
|
|
1071
1033
|
|
|
1072
1034
|
const sandboxRoute = await resolveSandboxRoute({
|
|
1073
1035
|
input,
|
|
@@ -14,6 +14,7 @@ export function configuredBundledSkillNames(context: BundledSkillConfiguration):
|
|
|
14
14
|
const tools = new Set(context.firstPartyTools);
|
|
15
15
|
const artifacts = () => tools.has("editable_artifact_list") && tools.has("editable_artifact_get");
|
|
16
16
|
const definitions = [
|
|
17
|
+
{ name: "opengeni-help", include: () => true },
|
|
17
18
|
{ name: "opengeni-visualize", include: () => true },
|
|
18
19
|
{ name: "document-parsing", include: () => true },
|
|
19
20
|
{ name: "opengeni-skills", include: () => true },
|
|
@@ -51,7 +51,7 @@ import { buildCodexTokenResolver } from "../codex-auth";
|
|
|
51
51
|
import { CODEX_CLIENT_VERSION } from "@opengeni/codex";
|
|
52
52
|
import { mergeResourceRefs } from "../common";
|
|
53
53
|
import {
|
|
54
|
-
|
|
54
|
+
workspaceSessionToolPolicyDefaultServerIds,
|
|
55
55
|
loadRigDefaultVariableSetEnvironment,
|
|
56
56
|
mergeRigDefaultVariableSetEnvironment,
|
|
57
57
|
buildApiIntegrationMcpServers,
|
|
@@ -212,7 +212,15 @@ export async function prepareTurnToolPolicy(deps: PrepareTurnToolPolicyDeps) {
|
|
|
212
212
|
? scheduledEffectiveMcpServerIds.filter((id) => currentMcpServerIds.has(id))
|
|
213
213
|
: [...currentMcpServerIds],
|
|
214
214
|
defaultMcpServerIds:
|
|
215
|
-
scheduledEffectiveMcpServerIds ??
|
|
215
|
+
scheduledEffectiveMcpServerIds ??
|
|
216
|
+
(session.toolPolicy.mode === "workspace_default"
|
|
217
|
+
? await workspaceSessionToolPolicyDefaultServerIds(
|
|
218
|
+
db,
|
|
219
|
+
input.workspaceId,
|
|
220
|
+
capabilitySettings,
|
|
221
|
+
fileAuthoritySubjectId ?? undefined,
|
|
222
|
+
)
|
|
223
|
+
: []),
|
|
216
224
|
});
|
|
217
225
|
const mcpAvailabilityNote = unavailableMcpOperationalContext({
|
|
218
226
|
droppedIds: resolvedToolPolicy.effectivePolicy.droppedIds,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TurnHeartbeatDetails } from "../../op-journal";
|
|
1
2
|
import type { Settings } from "@opengeni/config";
|
|
2
3
|
import type { CodexUsageHeaderSnapshot } from "@opengeni/codex";
|
|
3
4
|
import type { AppendEventInput, ApplySessionTurnSettlementInput } from "@opengeni/db";
|
|
@@ -123,6 +124,7 @@ export type RenewalState = {
|
|
|
123
124
|
};
|
|
124
125
|
|
|
125
126
|
export type EventingState = {
|
|
127
|
+
heartbeatDetails: TurnHeartbeatDetails | null;
|
|
126
128
|
heartbeatTimer: ReturnType<typeof startActivityHeartbeat> | undefined;
|
|
127
129
|
batcher: ReturnType<typeof createRuntimeBatcher> | null;
|
|
128
130
|
preparedTools: Awaited<ReturnType<OpenGeniRuntime["prepareTools"]>> | null;
|
|
@@ -254,6 +256,7 @@ export function createTurnContext(input: {
|
|
|
254
256
|
publishedRunCredentialNotices: new Set(),
|
|
255
257
|
},
|
|
256
258
|
eventing: {
|
|
259
|
+
heartbeatDetails: null,
|
|
257
260
|
heartbeatTimer: undefined,
|
|
258
261
|
batcher: null,
|
|
259
262
|
preparedTools: null,
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
type Database,
|
|
7
7
|
} from "@opengeni/db";
|
|
8
8
|
import { publishDurableWorkspaceControlEvent } from "@opengeni/events";
|
|
9
|
+
import { recordWorkflowWakeReconciliation } from "@opengeni/observability";
|
|
9
10
|
import type { ControlActivityServices } from "./types";
|
|
10
11
|
import {
|
|
11
12
|
reconcileAutomaticSessionTitleFanout,
|
|
@@ -115,23 +116,7 @@ export function createWorkflowWakeActivities(services: () => Promise<ControlActi
|
|
|
115
116
|
pendingAdmissionBlockers,
|
|
116
117
|
exhaustedBatchLimit,
|
|
117
118
|
};
|
|
118
|
-
|
|
119
|
-
service.observability.info("session workflow wake delivery reconciled", {
|
|
120
|
-
claimed,
|
|
121
|
-
signaled,
|
|
122
|
-
delivered,
|
|
123
|
-
pendingAdmission,
|
|
124
|
-
unconfirmed,
|
|
125
|
-
failed,
|
|
126
|
-
exhaustedBatchLimit,
|
|
127
|
-
...Object.fromEntries(
|
|
128
|
-
Object.entries(pendingAdmissionBlockers).map(([blocker, count]) => [
|
|
129
|
-
`pendingAdmissionBlockers.${blocker}`,
|
|
130
|
-
count,
|
|
131
|
-
]),
|
|
132
|
-
),
|
|
133
|
-
});
|
|
134
|
-
}
|
|
119
|
+
recordWorkflowWakeReconciliation(service.observability, result);
|
|
135
120
|
return result;
|
|
136
121
|
},
|
|
137
122
|
};
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { type FirstPartyMcpToolName, type Permission, type ResourceRef, type KnowledgeFilePreparationResult } from "@opengeni/contracts";
|
|
2
|
-
import { type Settings } from "@opengeni/config";
|
|
3
|
-
import { createFirstPartyAttemptClient } from "@opengeni/runtime";
|
|
4
|
-
export type KnowledgeSourcePreparationOutcome = KnowledgeFilePreparationResult | {
|
|
5
|
-
status: "failed";
|
|
6
|
-
fileId: string;
|
|
7
|
-
message: string;
|
|
8
|
-
};
|
|
9
|
-
/** Accepted chat/schedule resources use ordinary agent authority and policy. */
|
|
10
|
-
export declare function prepareTurnKnowledgeSources(input: {
|
|
11
|
-
settings: Settings;
|
|
12
|
-
scope: Parameters<typeof createFirstPartyAttemptClient>[0]["scope"];
|
|
13
|
-
resources: readonly ResourceRef[];
|
|
14
|
-
selectedTools?: readonly FirstPartyMcpToolName[] | undefined;
|
|
15
|
-
permissions?: readonly Permission[] | null | undefined;
|
|
16
|
-
learningMode: "automatic" | "review_first" | "off";
|
|
17
|
-
signal?: AbortSignal | undefined;
|
|
18
|
-
onOutcome: (outcome: KnowledgeSourcePreparationOutcome) => Promise<void>;
|
|
19
|
-
prepare?: (fileId: string) => Promise<KnowledgeFilePreparationResult>;
|
|
20
|
-
}): Promise<string | undefined>;
|