@opengeni/worker-bundle 0.27.3 → 0.28.3-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/model-usage.d.ts +1 -1
- package/dist/activities/mcp-credentials.d.ts +3 -1
- package/dist/activities/sandbox-lease.d.ts +4 -4
- package/dist/activities/workspace-capture.d.ts +12 -1
- package/dist/{activities-control-WZRFEIJH.js → activities-control-JUBMDBDZ.js} +58 -73
- package/dist/activities-control-JUBMDBDZ.js.map +1 -0
- package/dist/{activities-turn-V7J34ZOF.js → activities-turn-I2ZKGZE6.js} +268 -187
- package/dist/activities-turn-I2ZKGZE6.js.map +1 -0
- package/dist/{chunk-BARTLVBK.js → chunk-A4XQWVZK.js} +25 -25
- package/dist/chunk-A4XQWVZK.js.map +1 -0
- package/dist/{chunk-C54VDT6I.js → chunk-ZNIKZQMN.js} +148 -111
- package/dist/chunk-ZNIKZQMN.js.map +1 -0
- package/dist/index.js +2 -2
- package/dist/observability-metrics.d.ts +12 -4
- package/dist/sandbox-archive-storage.d.ts +15 -3
- package/dist/sandbox-resume.d.ts +1 -1
- package/package.json +17 -17
- package/src/activities/agent-turn/agent-build.ts +30 -1
- package/src/activities/agent-turn/claim.ts +3 -3
- package/src/activities/agent-turn/finalization.ts +62 -41
- package/src/activities/agent-turn/model-usage.ts +10 -1
- package/src/activities/agent-turn/skill-read.ts +2 -21
- package/src/activities/agent-turn/stream-attempt.ts +3 -2
- package/src/activities/agent-turn/tool-environment.ts +52 -10
- package/src/activities/goals.ts +2 -0
- package/src/activities/mcp-credentials.ts +42 -2
- package/src/activities/sandbox-lease.ts +64 -71
- package/src/activities/scheduled-tasks.ts +8 -6
- package/src/activities/workspace-capture.ts +148 -126
- package/src/observability-metrics.ts +28 -24
- package/src/pr-review-credentials.ts +2 -0
- package/src/sandbox-archive-storage.ts +82 -18
- package/src/sandbox-resume.ts +79 -63
- package/dist/activities-control-WZRFEIJH.js.map +0 -1
- package/dist/activities-turn-V7J34ZOF.js.map +0 -1
- package/dist/chunk-BARTLVBK.js.map +0 -1
- package/dist/chunk-C54VDT6I.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
startContextCompactionPendingMonitor,
|
|
20
20
|
startSessionRecoveryMonitor,
|
|
21
21
|
startTurnCapacityMonitor
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-A4XQWVZK.js";
|
|
23
23
|
|
|
24
24
|
// src/index.ts
|
|
25
25
|
import {
|
|
@@ -781,7 +781,7 @@ function temporalWorkflowExecutionNotFound(error) {
|
|
|
781
781
|
return isGrpcServiceError(error) && error.code === 5;
|
|
782
782
|
}
|
|
783
783
|
async function createDefaultWorkerActivities(role, dependencies = {}) {
|
|
784
|
-
return role === "control" ? (await import("./activities-control-
|
|
784
|
+
return role === "control" ? (await import("./activities-control-JUBMDBDZ.js")).createControlActivities(dependencies) : (await import("./activities-turn-I2ZKGZE6.js")).createTurnActivities(dependencies);
|
|
785
785
|
}
|
|
786
786
|
function resolveOpenGeniWorkflowDefinition(moduleUrl = import.meta.url) {
|
|
787
787
|
const modulePath = fileURLToPath(moduleUrl).replaceAll("\\", "/");
|
|
@@ -91,15 +91,23 @@ export declare function recordWorkerDeathRecoveryMetrics(observability: Observab
|
|
|
91
91
|
export declare class TurnLifecycleMetrics {
|
|
92
92
|
private readonly observability;
|
|
93
93
|
private readonly options;
|
|
94
|
-
private readonly
|
|
94
|
+
private readonly attempts;
|
|
95
95
|
private timer;
|
|
96
96
|
constructor(observability: Observability, options?: {
|
|
97
97
|
now?: () => number;
|
|
98
98
|
refreshIntervalMs?: number;
|
|
99
99
|
});
|
|
100
|
-
start(
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
start(input: {
|
|
101
|
+
attemptId: string;
|
|
102
|
+
}): void;
|
|
103
|
+
progress(input: {
|
|
104
|
+
attemptId: string;
|
|
105
|
+
}): void;
|
|
106
|
+
finish(input: {
|
|
107
|
+
attemptId: string;
|
|
108
|
+
outcome: TurnOutcome | null;
|
|
109
|
+
durationSeconds?: number;
|
|
110
|
+
}): void;
|
|
103
111
|
refreshGauges(): void;
|
|
104
112
|
stop(): void;
|
|
105
113
|
private oldestInflightAgeSeconds;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ObjectStorage } from "@opengeni/storage";
|
|
2
|
+
import type { VerifiedHostWorkspaceArchive } from "@opengeni/runtime/sandbox";
|
|
2
3
|
import { type WorkspaceArchiveDescriptor, type WorkspaceArchiveObjectRef } from "@opengeni/contracts";
|
|
3
4
|
export declare class WorkspaceArchiveObjectStorageRequiredError extends Error {
|
|
4
5
|
readonly code: "workspace_archive_object_storage_required";
|
|
@@ -13,15 +14,26 @@ export declare function putTarWorkspaceArchiveObject(input: {
|
|
|
13
14
|
archive: {
|
|
14
15
|
bytes: Uint8Array;
|
|
15
16
|
descriptor: WorkspaceArchiveDescriptor;
|
|
16
|
-
};
|
|
17
|
+
} | VerifiedHostWorkspaceArchive;
|
|
17
18
|
}): Promise<WorkspaceArchiveObjectRef>;
|
|
19
|
+
/** The caller owns a fresh candidate returned by this module. A thrown or lost
|
|
20
|
+
* database response is UNKNOWN: it may have committed, so never delete on catch.
|
|
21
|
+
* Only the transaction's exact candidate disposition can authorize cleanup. */
|
|
22
|
+
export declare function persistWorkspaceArchiveCandidate<T extends {
|
|
23
|
+
wrote: boolean;
|
|
24
|
+
candidateDisposition?: "adopted" | "already_referenced" | "unused";
|
|
25
|
+
}>(input: {
|
|
26
|
+
objectStorage?: ObjectStorage | null;
|
|
27
|
+
ref?: WorkspaceArchiveObjectRef;
|
|
28
|
+
persist: () => Promise<T>;
|
|
29
|
+
}): Promise<T>;
|
|
18
30
|
export declare function putVersion1TarArchiveOrInline(input: {
|
|
19
31
|
backend: string;
|
|
20
32
|
objectStorage?: ObjectStorage | null | undefined;
|
|
21
33
|
accountId: string;
|
|
22
34
|
workspaceId: string;
|
|
23
35
|
sandboxGroupId: string;
|
|
24
|
-
archive: {
|
|
36
|
+
archive: VerifiedHostWorkspaceArchive | {
|
|
25
37
|
bytes: Uint8Array;
|
|
26
38
|
descriptor: WorkspaceArchiveDescriptor;
|
|
27
39
|
base64: string;
|
package/dist/sandbox-resume.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Settings } from "@opengeni/config";
|
|
2
2
|
import { readLease, reinstateTurnLeaseHolder, SandboxLeaseSupersededError, type Database, type LeaseHolderKind } from "@opengeni/db";
|
|
3
3
|
import { establishSandboxSessionFromEnvelope, type EstablishedSandboxSession, type RuntimeMetricsHooks } from "@opengeni/runtime";
|
|
4
|
-
import type
|
|
4
|
+
import { type ObjectStorage } from "@opengeni/storage";
|
|
5
5
|
import type { Observability } from "@opengeni/observability";
|
|
6
6
|
export type ResumeHolderKind = LeaseHolderKind;
|
|
7
7
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/worker-bundle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.3-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,22 +54,22 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@llamaindex/liteparse": "2.14.2",
|
|
57
|
-
"@opengeni/agent-proto": "^0.5.1",
|
|
58
|
-
"@opengeni/capabilities": "^0.3.3",
|
|
59
|
-
"@opengeni/codemode": "^0.5.
|
|
60
|
-
"@opengeni/codex": "^0.2.22",
|
|
61
|
-
"@opengeni/config": "^1.0
|
|
62
|
-
"@opengeni/contracts": "^
|
|
63
|
-
"@opengeni/core": "^2.
|
|
64
|
-
"@opengeni/db": "^4.2.
|
|
65
|
-
"@opengeni/documents": "^0.8.
|
|
66
|
-
"@opengeni/events": "^0.4.
|
|
67
|
-
"@opengeni/github": "^0.7.
|
|
68
|
-
"@opengeni/network": "^0.3.1",
|
|
69
|
-
"@opengeni/observability": "^0.8.
|
|
70
|
-
"@opengeni/runtime": "^2.
|
|
71
|
-
"@opengeni/storage": "^0.2.
|
|
72
|
-
"@opengeni/xai-subscription": "^0.1.4",
|
|
57
|
+
"@opengeni/agent-proto": "^0.5.1-canary.25",
|
|
58
|
+
"@opengeni/capabilities": "^0.3.3-canary.7",
|
|
59
|
+
"@opengeni/codemode": "^0.5.5-canary.0",
|
|
60
|
+
"@opengeni/codex": "^0.2.22-canary.7",
|
|
61
|
+
"@opengeni/config": "^1.1.1-canary.0",
|
|
62
|
+
"@opengeni/contracts": "^3.0.1-canary.0",
|
|
63
|
+
"@opengeni/core": "^2.9.3-canary.0",
|
|
64
|
+
"@opengeni/db": "^4.3.2-canary.0",
|
|
65
|
+
"@opengeni/documents": "^0.8.26-canary.0",
|
|
66
|
+
"@opengeni/events": "^0.4.24-canary.0",
|
|
67
|
+
"@opengeni/github": "^0.7.9-canary.0",
|
|
68
|
+
"@opengeni/network": "^0.3.1-canary.7",
|
|
69
|
+
"@opengeni/observability": "^0.8.25-canary.0",
|
|
70
|
+
"@opengeni/runtime": "^2.5.2-canary.0",
|
|
71
|
+
"@opengeni/storage": "^0.2.126-canary.0",
|
|
72
|
+
"@opengeni/xai-subscription": "^0.1.4-canary.7",
|
|
73
73
|
"@temporalio/activity": "^1.17.0",
|
|
74
74
|
"@temporalio/client": "^1.17.0",
|
|
75
75
|
"@temporalio/worker": "^1.17.0",
|
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
setXaiSessionAccountPin,
|
|
7
7
|
getWorkspaceVideoGenerationPolicy,
|
|
8
8
|
loadWorkspaceVercelAiGatewayCredentialLease,
|
|
9
|
+
getExternalLinkTurnAuthorization,
|
|
10
|
+
getSessionTurnForAttempt,
|
|
9
11
|
} from "@opengeni/db";
|
|
10
12
|
import {
|
|
11
13
|
formatSkillCatalog,
|
|
@@ -569,11 +571,39 @@ export async function buildTurnAgent(deps: BuildTurnAgentDeps) {
|
|
|
569
571
|
outcome: "completed",
|
|
570
572
|
durationSeconds: (performance.now() - postToolPreparationStartedAt) / 1_000,
|
|
571
573
|
});
|
|
574
|
+
const linkedToolAuthority = await getExternalLinkTurnAuthorization(
|
|
575
|
+
db,
|
|
576
|
+
{
|
|
577
|
+
accountId: input.accountId,
|
|
578
|
+
workspaceId: input.workspaceId,
|
|
579
|
+
},
|
|
580
|
+
turn.id,
|
|
581
|
+
);
|
|
582
|
+
if (linkedToolAuthority && !linkedToolAuthority.authorized)
|
|
583
|
+
throw new Error("Native identity link was revoked");
|
|
572
584
|
const agent = (() => {
|
|
573
585
|
const agentConstructionStartedAt = performance.now();
|
|
574
586
|
let agentConstructionOutcome: "completed" | "failed" = "completed";
|
|
575
587
|
try {
|
|
576
588
|
return runtime.buildAgent(eventing.modelRunSettings, runtimeResources, {
|
|
589
|
+
...(linkedToolAuthority
|
|
590
|
+
? {
|
|
591
|
+
authorizeAttemptExecution: async () => {
|
|
592
|
+
const current = await getSessionTurnForAttempt(
|
|
593
|
+
db,
|
|
594
|
+
input.workspaceId,
|
|
595
|
+
input.sessionId,
|
|
596
|
+
input.attemptId,
|
|
597
|
+
);
|
|
598
|
+
if (
|
|
599
|
+
!current ||
|
|
600
|
+
current.id !== turn.id ||
|
|
601
|
+
current.executionGeneration !== turn.executionGeneration
|
|
602
|
+
)
|
|
603
|
+
throw new Error("The linked agent attempt is no longer authorized");
|
|
604
|
+
},
|
|
605
|
+
}
|
|
606
|
+
: {}),
|
|
577
607
|
...(preparedTools.inputWaitYield ? { inputWaitYield: preparedTools.inputWaitYield } : {}),
|
|
578
608
|
reasoningEffort: turn.reasoningEffort,
|
|
579
609
|
latencyMode: turnExecutionPolicy.latencyMode,
|
|
@@ -677,7 +707,6 @@ export async function buildTurnAgent(deps: BuildTurnAgentDeps) {
|
|
|
677
707
|
}),
|
|
678
708
|
onRetainableSessionImageOutput: media.retainSessionImageAtToolBoundary,
|
|
679
709
|
skillCatalog: deps.skillCatalog,
|
|
680
|
-
serverSkillReading: true,
|
|
681
710
|
...(!structuredWorkspacePolicyActive && workspaceAgentInstructions
|
|
682
711
|
? { instructionsTemplate: workspaceAgentInstructions }
|
|
683
712
|
: {}),
|
|
@@ -347,7 +347,7 @@ export async function claimTurnAttempt(deps: ClaimTurnDeps): Promise<ClaimTurnOu
|
|
|
347
347
|
});
|
|
348
348
|
return attached.action === "claimed" && attached.turn.id === turn.id;
|
|
349
349
|
};
|
|
350
|
-
turnLifecycleMetricsFor(observability).start(
|
|
350
|
+
turnLifecycleMetricsFor(observability).start({ attemptId: input.attemptId });
|
|
351
351
|
// §7.5 P3 — pass the accepted billing attribution (externally funded turns
|
|
352
352
|
// bypass OpenGeni credit/token gates)
|
|
353
353
|
// AND the optional host `entitlements` port (when bound, its admitRun replaces
|
|
@@ -451,7 +451,7 @@ export async function claimTurnAttempt(deps: ClaimTurnDeps): Promise<ClaimTurnOu
|
|
|
451
451
|
}
|
|
452
452
|
recordCanonicalStartupMilestones(appended.canonicalStartupMilestones);
|
|
453
453
|
if (inputs.length > 0) {
|
|
454
|
-
turnLifecycleMetricsFor(observability).progress(
|
|
454
|
+
turnLifecycleMetricsFor(observability).progress({ attemptId: input.attemptId });
|
|
455
455
|
}
|
|
456
456
|
activityContext?.heartbeat({
|
|
457
457
|
...heartbeatDetails,
|
|
@@ -515,7 +515,7 @@ export async function claimTurnAttempt(deps: ClaimTurnDeps): Promise<ClaimTurnOu
|
|
|
515
515
|
return false;
|
|
516
516
|
}
|
|
517
517
|
recordCanonicalStartupMilestones(result.canonicalStartupMilestones);
|
|
518
|
-
turnLifecycleMetricsFor(observability).progress(
|
|
518
|
+
turnLifecycleMetricsFor(observability).progress({ attemptId: input.attemptId });
|
|
519
519
|
await publishDurableSessionEvents(bus, input.workspaceId, input.sessionId, result.events);
|
|
520
520
|
if (inputSettlement.turnStatus === "requires_action") {
|
|
521
521
|
// The settlement transaction committed the parent's child_requires_action
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
commitSessionAttemptQuiescence,
|
|
3
|
+
listPendingSessionTurns,
|
|
3
4
|
recordCodexAccountUsageWithWakeTargets,
|
|
4
5
|
releaseCodexCredentialLease,
|
|
5
6
|
releaseXaiCredentialLease,
|
|
@@ -20,7 +21,11 @@ import type {
|
|
|
20
21
|
SessionAttemptQuiescenceProof,
|
|
21
22
|
TurnActivityServices as ActivityServices,
|
|
22
23
|
} from "../types";
|
|
23
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
captureWhileIdle,
|
|
26
|
+
captureWorkspaceRevision,
|
|
27
|
+
openFreshWorkspaceCaptureSession,
|
|
28
|
+
} from "../workspace-capture";
|
|
24
29
|
import { type ChannelASession } from "@opengeni/runtime/sandbox";
|
|
25
30
|
import { createModelCheckpointMemoryCollector } from "../../model-checkpoint-memory-collector";
|
|
26
31
|
import {
|
|
@@ -488,49 +493,60 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
|
|
|
488
493
|
deadlineRotationRequested: sandboxState.deadlineRotationRequested,
|
|
489
494
|
});
|
|
490
495
|
if (
|
|
496
|
+
settings.workspaceCaptureEnabled &&
|
|
491
497
|
runTurnEndPersistence &&
|
|
492
498
|
attempt.turnId &&
|
|
493
499
|
sandboxState.resolvedSandbox &&
|
|
494
500
|
sandboxState.setupBoxSession &&
|
|
495
501
|
sandboxState.sandboxGroupId
|
|
496
502
|
) {
|
|
497
|
-
//
|
|
498
|
-
//
|
|
499
|
-
// exceed the 90s holder TTL, and the reaper must remain unable to drain
|
|
500
|
-
// the exact box while this holder still reads it.
|
|
503
|
+
// Review reads yield to queued work. Recovery snapshots have their own
|
|
504
|
+
// physical-settlement requirement and are drained later in finalization.
|
|
501
505
|
sandboxState.turnEndCaptureInProgress = true;
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
);
|
|
508
|
-
}
|
|
509
|
-
const captureEstablished = sandboxState.resolvedSandbox.established;
|
|
510
|
-
await captureWorkspaceRevision({
|
|
511
|
-
db,
|
|
512
|
-
objectStorage,
|
|
513
|
-
settings,
|
|
514
|
-
publish: async (events) => {
|
|
515
|
-
await publishDurableSessionEvents(bus, input.workspaceId, input.sessionId, events);
|
|
516
|
-
},
|
|
517
|
-
session: sandboxState.setupBoxSession as ChannelASession,
|
|
518
|
-
openReadSession: async () =>
|
|
519
|
-
await openFreshWorkspaceCaptureSession({
|
|
520
|
-
backendId: captureEstablished.backendId,
|
|
521
|
-
client: captureEstablished.client,
|
|
522
|
-
session: sandboxState.setupBoxSession as ChannelASession,
|
|
523
|
-
expectedInstanceId: captureEstablished.instanceId,
|
|
524
|
-
}),
|
|
525
|
-
leaseEpoch: sandboxState.resolvedSandbox.leaseEpoch,
|
|
526
|
-
sandboxGroupId: sandboxState.sandboxGroupId,
|
|
527
|
-
accountId: input.accountId,
|
|
528
|
-
workspaceId: input.workspaceId,
|
|
529
|
-
sessionId: input.sessionId,
|
|
530
|
-
turnId: attempt.turnId,
|
|
531
|
-
attemptId: input.attemptId,
|
|
532
|
-
observability,
|
|
506
|
+
const captureSandbox = sandboxState.resolvedSandbox;
|
|
507
|
+
const captureSession = sandboxState.setupBoxSession as ChannelASession;
|
|
508
|
+
const captureGroupId = sandboxState.sandboxGroupId;
|
|
509
|
+
const captureTurnId = attempt.turnId;
|
|
510
|
+
await captureWhileIdle({
|
|
533
511
|
...(finalizerSignal ? { signal: finalizerSignal } : {}),
|
|
512
|
+
hasPendingWork: async () =>
|
|
513
|
+
(await listPendingSessionTurns(db, input.workspaceId, input.sessionId)).some(
|
|
514
|
+
(turn) => turn.id !== captureTurnId,
|
|
515
|
+
),
|
|
516
|
+
capture: async (signal) => {
|
|
517
|
+
if (sandboxState.snapshotInFlight) {
|
|
518
|
+
await waitForWarmSnapshot(
|
|
519
|
+
sandboxState.snapshotInFlight,
|
|
520
|
+
settings.sandboxSnapshotTimeoutMs,
|
|
521
|
+
signal,
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
if (signal.aborted) return;
|
|
525
|
+
await captureWorkspaceRevision({
|
|
526
|
+
db,
|
|
527
|
+
objectStorage,
|
|
528
|
+
settings,
|
|
529
|
+
publish: async (events) =>
|
|
530
|
+
publishDurableSessionEvents(bus, input.workspaceId, input.sessionId, events),
|
|
531
|
+
session: captureSession,
|
|
532
|
+
openReadSession: async () =>
|
|
533
|
+
openFreshWorkspaceCaptureSession({
|
|
534
|
+
backendId: captureSandbox.established.backendId,
|
|
535
|
+
client: captureSandbox.established.client,
|
|
536
|
+
session: captureSession,
|
|
537
|
+
expectedInstanceId: captureSandbox.established.instanceId,
|
|
538
|
+
}),
|
|
539
|
+
leaseEpoch: captureSandbox.leaseEpoch,
|
|
540
|
+
sandboxGroupId: captureGroupId,
|
|
541
|
+
accountId: input.accountId,
|
|
542
|
+
workspaceId: input.workspaceId,
|
|
543
|
+
sessionId: input.sessionId,
|
|
544
|
+
turnId: captureTurnId,
|
|
545
|
+
attemptId: input.attemptId,
|
|
546
|
+
observability,
|
|
547
|
+
signal,
|
|
548
|
+
});
|
|
549
|
+
},
|
|
534
550
|
});
|
|
535
551
|
}
|
|
536
552
|
eventing.toolPreparationClosing = true;
|
|
@@ -729,12 +745,17 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
|
|
|
729
745
|
status: finalizationError ? "cleanup_failed" : control.activityStatus,
|
|
730
746
|
durationSeconds,
|
|
731
747
|
});
|
|
732
|
-
if (attempt.turnId
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
748
|
+
if (attempt.turnId) {
|
|
749
|
+
// This registry describes physical activity ownership, not durable turn
|
|
750
|
+
// settlement. Always clear the exact attempt when its activity unwinds,
|
|
751
|
+
// even when failure settlement could not classify an outcome. Otherwise
|
|
752
|
+
// a recovered successor can make progress while the predecessor leaves a
|
|
753
|
+
// process-local false stuck-turn alert behind.
|
|
754
|
+
turnLifecycleMetricsFor(observability).finish({
|
|
755
|
+
attemptId: input.attemptId,
|
|
756
|
+
outcome: control.turnMetricOutcome,
|
|
736
757
|
durationSeconds,
|
|
737
|
-
);
|
|
758
|
+
});
|
|
738
759
|
}
|
|
739
760
|
activitySpan.end({
|
|
740
761
|
attributes: {
|
|
@@ -129,8 +129,17 @@ export const createSessionTitleModelUsageEventState = createCompactionModelUsage
|
|
|
129
129
|
|
|
130
130
|
export function modelResponseContextSignal(
|
|
131
131
|
state: ModelResponseEventState,
|
|
132
|
+
responseCountBeforeStream = 0,
|
|
132
133
|
): { revision: number; totalTokens: number } | null {
|
|
133
|
-
|
|
134
|
+
const signal = state.contextSignal;
|
|
135
|
+
// A compaction retry creates a new SDK request counter, but usage identities
|
|
136
|
+
// remain activity-wide. Never bind a pre-stream report to a reused request
|
|
137
|
+
// ordinal; translate only this stream's reports without mutating usage state.
|
|
138
|
+
if (!signal || signal.revision <= responseCountBeforeStream) return null;
|
|
139
|
+
return {
|
|
140
|
+
revision: signal.revision - responseCountBeforeStream,
|
|
141
|
+
totalTokens: signal.totalTokens,
|
|
142
|
+
};
|
|
134
143
|
}
|
|
135
144
|
|
|
136
145
|
export function assertModelResponseLatencyMode(input: {
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { AttemptToolDefinition } from "@opengeni/codemode";
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
listSkillPaths,
|
|
4
4
|
PORTABLE_SKILL_MAX_FILES,
|
|
5
5
|
readSkillFiles,
|
|
6
|
-
SKILL_READ_MAX_OUTPUT_BYTES,
|
|
7
6
|
SKILL_READ_MAX_PATHS,
|
|
8
7
|
type SkillTextFile,
|
|
9
8
|
} from "@opengeni/runtime/skill-library";
|
|
@@ -74,7 +73,7 @@ export function createSkillReadAttemptToolDefinition(input: {
|
|
|
74
73
|
const files = metadata ? metadata.files : (loaded as readonly SkillTextFile[]);
|
|
75
74
|
const selected =
|
|
76
75
|
args.listFiles === true
|
|
77
|
-
? listSkillPaths(files)
|
|
76
|
+
? listSkillPaths(files, PORTABLE_SKILL_MAX_FILES)
|
|
78
77
|
: readSkillFiles(files, args.paths as string[] | undefined);
|
|
79
78
|
const output = {
|
|
80
79
|
...(metadata
|
|
@@ -97,21 +96,3 @@ export function createSkillReadAttemptToolDefinition(input: {
|
|
|
97
96
|
},
|
|
98
97
|
};
|
|
99
98
|
}
|
|
100
|
-
|
|
101
|
-
/** Inventory never reads or serializes file bodies, including the entry point. */
|
|
102
|
-
function listSkillPaths(files: readonly SkillTextFile[]): { paths: string[] } {
|
|
103
|
-
if (files.length > PORTABLE_SKILL_MAX_FILES) {
|
|
104
|
-
throw new Error(`Skill inventory exceeds ${PORTABLE_SKILL_MAX_FILES} files.`);
|
|
105
|
-
}
|
|
106
|
-
const seen = new Set<string>();
|
|
107
|
-
for (const { path } of files) {
|
|
108
|
-
assertSkillRelativePath(path);
|
|
109
|
-
if (seen.has(path)) throw new Error(`Duplicate stored Skill path: ${path}`);
|
|
110
|
-
seen.add(path);
|
|
111
|
-
}
|
|
112
|
-
const result = { paths: [...seen].sort() };
|
|
113
|
-
if (Buffer.byteLength(JSON.stringify(result), "utf8") > SKILL_READ_MAX_OUTPUT_BYTES) {
|
|
114
|
-
throw new Error("Skill inventory exceeds the read output limit.");
|
|
115
|
-
}
|
|
116
|
-
return result;
|
|
117
|
-
}
|
|
@@ -865,7 +865,8 @@ export async function runTurnStreamAttempt(
|
|
|
865
865
|
},
|
|
866
866
|
}
|
|
867
867
|
: {}),
|
|
868
|
-
contextCompactionSignal: () =>
|
|
868
|
+
contextCompactionSignal: () =>
|
|
869
|
+
modelResponseContextSignal(modelResponseState, responseCountBeforeStream),
|
|
869
870
|
contextCompactionRequested: () =>
|
|
870
871
|
isSessionCompactionRequested(db, input.workspaceId, input.sessionId),
|
|
871
872
|
onModelPreparationPhase: (measurement) => {
|
|
@@ -1061,7 +1062,7 @@ export async function runTurnStreamAttempt(
|
|
|
1061
1062
|
currentToolBatchCallIds = new Set<string>();
|
|
1062
1063
|
currentToolBatchCompletedCallIds = new Set<string>();
|
|
1063
1064
|
await historySink.reconcileConversationTruth();
|
|
1064
|
-
turnLifecycleMetricsFor(observability).progress(
|
|
1065
|
+
turnLifecycleMetricsFor(observability).progress({ attemptId: input.attemptId });
|
|
1065
1066
|
modelCheckpointMemoryCollector.schedule(observability);
|
|
1066
1067
|
try {
|
|
1067
1068
|
await ensureRunAllowed(
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { getWorkspaceConnectionModelRestrictions } from "@opengeni/db";
|
|
2
2
|
import {
|
|
3
3
|
beginConnectorActionExecution,
|
|
4
|
+
getExternalLinkTurnAuthorization,
|
|
5
|
+
getSessionTurnForAttempt,
|
|
4
6
|
getWorkspaceVideoGenerationPolicy,
|
|
5
7
|
listSkillDescriptors,
|
|
6
8
|
completeConnectorActionExecution,
|
|
@@ -51,6 +53,7 @@ import {
|
|
|
51
53
|
resolveWorkspaceModelSelection,
|
|
52
54
|
withFrozenPersonalConnectionDelegations,
|
|
53
55
|
resolveSessionToolPolicy,
|
|
56
|
+
hasPermission,
|
|
54
57
|
} from "@opengeni/core";
|
|
55
58
|
import { loadWorkspaceEnvironmentForRunWithCredentials } from "../environment";
|
|
56
59
|
import { withFirstPartyTools } from "../goals";
|
|
@@ -60,7 +63,10 @@ import { ToolResultSpill } from "./tool-result-spill";
|
|
|
60
63
|
import { createTurnMediaArtifacts } from "./media-artifacts";
|
|
61
64
|
import { SandboxChannelAService } from "@opengeni/runtime/sandbox";
|
|
62
65
|
import { sandboxRunAs } from "@opengeni/runtime";
|
|
63
|
-
import {
|
|
66
|
+
import {
|
|
67
|
+
DEFAULT_FIRST_PARTY_MCP_PERMISSIONS,
|
|
68
|
+
type ToolAuthNeededPayload,
|
|
69
|
+
} from "@opengeni/contracts";
|
|
64
70
|
|
|
65
71
|
import {
|
|
66
72
|
rollingSafeToolAuthNeededPayload,
|
|
@@ -508,6 +514,21 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
|
|
|
508
514
|
};
|
|
509
515
|
})()
|
|
510
516
|
: undefined;
|
|
517
|
+
const linkedAuthority = await getExternalLinkTurnAuthorization(
|
|
518
|
+
db,
|
|
519
|
+
{
|
|
520
|
+
accountId: input.accountId,
|
|
521
|
+
workspaceId: input.workspaceId,
|
|
522
|
+
},
|
|
523
|
+
turn.id,
|
|
524
|
+
);
|
|
525
|
+
if (linkedAuthority && !linkedAuthority.authorized)
|
|
526
|
+
throw new Error("Native identity link was revoked");
|
|
527
|
+
const effectiveFirstPartyPermissions = linkedAuthority
|
|
528
|
+
? (session.firstPartyMcpPermissions ?? DEFAULT_FIRST_PARTY_MCP_PERMISSIONS).filter(
|
|
529
|
+
(permission) => hasPermission(linkedAuthority.permissions, permission),
|
|
530
|
+
)
|
|
531
|
+
: session.firstPartyMcpPermissions;
|
|
511
532
|
const selectedFirstPartyMcpTools = allowedFirstPartyMcpToolsForSession(
|
|
512
533
|
runSettings,
|
|
513
534
|
session.firstPartyMcpTools,
|
|
@@ -519,16 +540,21 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
|
|
|
519
540
|
title: session.title,
|
|
520
541
|
titleSource: session.titleSource,
|
|
521
542
|
firstPartyMcpTools: selectedFirstPartyMcpTools,
|
|
522
|
-
firstPartyMcpPermissions:
|
|
543
|
+
firstPartyMcpPermissions: effectiveFirstPartyPermissions,
|
|
523
544
|
}),
|
|
524
545
|
parallelGenerationAvailable: typeof runtime.generateSessionTitle === "function",
|
|
525
546
|
});
|
|
526
547
|
const googleDrivePublicationAllowed =
|
|
527
548
|
selectedFirstPartyMcpTools.includes("editable_artifact_export") &&
|
|
528
549
|
selectedFirstPartyMcpTools.includes("editable_artifact_export_status") &&
|
|
529
|
-
(
|
|
530
|
-
(
|
|
531
|
-
|
|
550
|
+
hasPermission(
|
|
551
|
+
[...(effectiveFirstPartyPermissions ?? DEFAULT_FIRST_PARTY_MCP_PERMISSIONS)],
|
|
552
|
+
"artifacts:read",
|
|
553
|
+
) &&
|
|
554
|
+
hasPermission(
|
|
555
|
+
[...(effectiveFirstPartyPermissions ?? DEFAULT_FIRST_PARTY_MCP_PERMISSIONS)],
|
|
556
|
+
"artifacts:publish",
|
|
557
|
+
);
|
|
532
558
|
const googleDriveConnectorBindings: readonly AttemptConnectorActionBinding[] =
|
|
533
559
|
googleDrivePublicationTool && googleDrivePublicationTarget && googleDrivePublicationAllowed
|
|
534
560
|
? [
|
|
@@ -760,9 +786,7 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
|
|
|
760
786
|
attemptId: input.attemptId,
|
|
761
787
|
executionGeneration: attempt.executionGeneration,
|
|
762
788
|
},
|
|
763
|
-
...(
|
|
764
|
-
? { permissions: session.firstPartyMcpPermissions }
|
|
765
|
-
: {}),
|
|
789
|
+
...(effectiveFirstPartyPermissions ? { permissions: effectiveFirstPartyPermissions } : {}),
|
|
766
790
|
selectedTools: selectedFirstPartyMcpTools,
|
|
767
791
|
subjectId: "worker:first-party-mcp",
|
|
768
792
|
subjectLabel: "OpenGeni worker",
|
|
@@ -852,6 +876,24 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
|
|
|
852
876
|
...(credentialSubjectId ? { credentialSubjectId } : {}),
|
|
853
877
|
...(codexAppsAuth ? { codexAppsAuth } : {}),
|
|
854
878
|
resolveCredential,
|
|
879
|
+
...(linkedAuthority
|
|
880
|
+
? {
|
|
881
|
+
authorizeAttemptExecution: async () => {
|
|
882
|
+
const current = await getSessionTurnForAttempt(
|
|
883
|
+
db,
|
|
884
|
+
input.workspaceId,
|
|
885
|
+
input.sessionId,
|
|
886
|
+
input.attemptId,
|
|
887
|
+
);
|
|
888
|
+
if (
|
|
889
|
+
!current ||
|
|
890
|
+
current.id !== turn.id ||
|
|
891
|
+
current.executionGeneration !== attempt.executionGeneration
|
|
892
|
+
)
|
|
893
|
+
throw new Error("The linked agent attempt is no longer authorized");
|
|
894
|
+
},
|
|
895
|
+
}
|
|
896
|
+
: {}),
|
|
855
897
|
onAuthNeeded: publishToolAuthNeeded,
|
|
856
898
|
materializeConnectorAttachments,
|
|
857
899
|
refreshOwnedCommand: async (commandId) => {
|
|
@@ -885,8 +927,8 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
|
|
|
885
927
|
},
|
|
886
928
|
// Manager-style sessions carry a creation-validated permission set
|
|
887
929
|
// for their first-party MCP token; null keeps the fixed default.
|
|
888
|
-
...(
|
|
889
|
-
? { firstPartyPermissions:
|
|
930
|
+
...(effectiveFirstPartyPermissions
|
|
931
|
+
? { firstPartyPermissions: effectiveFirstPartyPermissions }
|
|
890
932
|
: {}),
|
|
891
933
|
firstPartyTools: titleToolPlan.remoteFirstPartyMcpTools,
|
|
892
934
|
nestedAgentDepth: session.nestedAgentDepth,
|
package/src/activities/goals.ts
CHANGED
|
@@ -286,6 +286,8 @@ export function goalContinuationPrompt(
|
|
|
286
286
|
"Continuation behavior:",
|
|
287
287
|
"- This goal persists across turns. A runtime boundary can end one turn without shrinking the objective; the next continuation resumes the same full objective.",
|
|
288
288
|
"- Keep working until the requested end state is true and verified. Do not end the turn merely because one useful action completed, and do not redefine success around a smaller or easier task.",
|
|
289
|
+
"- An incomplete-status report is not a substitute for continuing the work. If the remaining problem can be investigated or addressed within your current authority, continue that work in this turn rather than returning another equivalent status-only final.",
|
|
290
|
+
"- Distinguish unfinished work from a blocker that actually requires human input or external change; use the existing waiting and blocked audits only when their conditions hold.",
|
|
289
291
|
"- Temporary rough edges are acceptable while the work is moving in the right direction. Completion still requires the requested end state to be true and verified.",
|
|
290
292
|
"",
|
|
291
293
|
"Work from evidence:",
|
|
@@ -3,6 +3,8 @@ import type { ConnectionCredentialsPort } from "@opengeni/contracts";
|
|
|
3
3
|
import {
|
|
4
4
|
buildConnectionTokenResolver,
|
|
5
5
|
buildHostConnectionTokenResolver,
|
|
6
|
+
getSessionTurnForAttempt,
|
|
7
|
+
authorizeDirectHostMcpUse,
|
|
6
8
|
resolveAcceptedConnectionUse,
|
|
7
9
|
sessionTenancyProductActivated,
|
|
8
10
|
type Database,
|
|
@@ -26,13 +28,18 @@ export function connectionTokenResolverForTurn(input: {
|
|
|
26
28
|
attemptId: string;
|
|
27
29
|
turn: SessionTurnForExecution;
|
|
28
30
|
authorizeAcceptedUse?: typeof resolveAcceptedConnectionUse;
|
|
31
|
+
/** Test seam; production always reads the canonical active-attempt projection. */
|
|
32
|
+
getHostTurnForAttempt?: typeof getSessionTurnForAttempt;
|
|
29
33
|
/** Test seam for the activation fence on pre-snapshot workspace refs. */
|
|
30
34
|
isSessionTenancyProductActivated?: typeof sessionTenancyProductActivated;
|
|
31
35
|
/** Optional; used only for content-free compatibility-lane counters. */
|
|
32
36
|
observability?: Observability | null | undefined;
|
|
33
37
|
}): (request: ResolveConnectionCredentialInput) => Promise<ResolveConnectionCredentialResult> {
|
|
34
38
|
const hostResolver = input.connectionCredentials?.mcpCredentials;
|
|
35
|
-
const
|
|
39
|
+
const readHostTurn = input.getHostTurnForAttempt ?? getSessionTurnForAttempt;
|
|
40
|
+
const hostDb = input.db;
|
|
41
|
+
const nativeResolver = buildConnectionTokenResolver(input.db, input.settings);
|
|
42
|
+
const configuredResolver = hostResolver
|
|
36
43
|
? buildHostConnectionTokenResolver(hostResolver, {
|
|
37
44
|
accountId: input.accountId,
|
|
38
45
|
workspaceId: input.workspaceId,
|
|
@@ -44,8 +51,40 @@ export function connectionTokenResolverForTurn(input: {
|
|
|
44
51
|
initiator: input.turn.initiator,
|
|
45
52
|
initiatorContext: input.turn.initiatorContext,
|
|
46
53
|
surface: "model",
|
|
54
|
+
// Only immutable captured direct-turn authority is currently supported.
|
|
55
|
+
// Direct, scheduled and exact causal/child snapshots are validated;
|
|
56
|
+
// no authority follows merely from a binding or creator identity.
|
|
57
|
+
authorizeDurableBinding: (request) => authorizeDirectHostMcpUse(hostDb, request),
|
|
58
|
+
authorizeExecution: async (request) => {
|
|
59
|
+
// Native UUID references already pass the accepted-use boundary below.
|
|
60
|
+
// Legacy opaque host IDs retain their compatibility path, but not a
|
|
61
|
+
// right to execute after their accepted attempt has stopped.
|
|
62
|
+
if (
|
|
63
|
+
request.connectionRef.authoritySource !== "host" &&
|
|
64
|
+
(!request.connectionRef.connectionId ||
|
|
65
|
+
OPENGENI_CONNECTION_ID_PATTERN.test(request.connectionRef.connectionId))
|
|
66
|
+
)
|
|
67
|
+
return true;
|
|
68
|
+
if (!request.attemptId) return false;
|
|
69
|
+
const current = await readHostTurn(
|
|
70
|
+
hostDb,
|
|
71
|
+
request.workspaceId,
|
|
72
|
+
request.sessionId,
|
|
73
|
+
request.attemptId,
|
|
74
|
+
);
|
|
75
|
+
return (
|
|
76
|
+
current !== null &&
|
|
77
|
+
current.id === request.turnId &&
|
|
78
|
+
current.executionGeneration === request.executionGeneration
|
|
79
|
+
);
|
|
80
|
+
},
|
|
47
81
|
})
|
|
48
|
-
:
|
|
82
|
+
: nativeResolver;
|
|
83
|
+
const baseResolver = (request: ResolveConnectionCredentialInput) =>
|
|
84
|
+
input.connectionCredentials?.mcpAuthoritySource === "host" &&
|
|
85
|
+
request.connectionRef.authoritySource !== "host"
|
|
86
|
+
? nativeResolver(request)
|
|
87
|
+
: configuredResolver(request);
|
|
49
88
|
return async (request) => {
|
|
50
89
|
// Host-owned refs carry explicit provenance because their opaque ids may
|
|
51
90
|
// themselves be valid UUIDs. They never enter OpenGeni's native connection
|
|
@@ -76,6 +115,7 @@ export function connectionTokenResolverForTurn(input: {
|
|
|
76
115
|
// so UUID-shaped host ids are unambiguous.
|
|
77
116
|
if (
|
|
78
117
|
hostResolver &&
|
|
118
|
+
input.connectionCredentials?.mcpAuthoritySource !== "host" &&
|
|
79
119
|
request.connectionRef.connectionId &&
|
|
80
120
|
!OPENGENI_CONNECTION_ID_PATTERN.test(request.connectionRef.connectionId)
|
|
81
121
|
) {
|