@opengeni/runtime 3.0.1 → 4.0.0-canary.35585322809001
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/assets/codemode-client.json +1 -0
- package/dist/chunk-4A4XTKKX.js +1 -0
- package/dist/{chunk-RIJ6DCYS.js → chunk-HCGLD7NH.js} +9817 -9494
- package/dist/chunk-HCGLD7NH.js.map +1 -0
- package/dist/{chunk-FHGLYLBL.js → chunk-SPT3PLMX.js} +1 -87
- package/dist/chunk-SPT3PLMX.js.map +1 -0
- package/dist/{chunk-GB763TSV.js → chunk-TE27J65Q.js} +1 -1
- package/dist/chunk-TE27J65Q.js.map +1 -0
- package/dist/{chunk-46BFUGFL.js → chunk-WIHICNWS.js} +1660 -388
- package/dist/chunk-WIHICNWS.js.map +1 -0
- package/dist/{chunk-UU6EJR52.js → chunk-YU4VRTR4.js} +2 -2
- package/dist/chunk-YU4VRTR4.js.map +1 -0
- package/dist/chunk-ZG2YH25H.js +94 -0
- package/dist/chunk-ZG2YH25H.js.map +1 -0
- package/dist/codex-tool-search.d.ts +5 -4
- package/dist/context-compaction.d.ts +19 -0
- package/dist/gmail-rest-mcp.js +1 -1
- package/dist/index.d.ts +30 -22
- package/dist/index.js +25 -5
- package/dist/lazy-manifest-OEYEHNVY.js +45 -0
- package/dist/lazy-manifest-OEYEHNVY.js.map +1 -0
- package/dist/lazy-manifest.d.ts +6 -0
- package/dist/lazy-tool-transport.d.ts +3 -2
- package/dist/{localSources-PVFCAPN2.js → localSources-TWHTEKGO.js} +3 -2
- package/dist/localSources-TWHTEKGO.js.map +1 -0
- package/dist/metrics.d.ts +6 -0
- package/dist/operational-instructions.d.ts +1 -1
- package/dist/prepared-compaction-request.d.ts +8 -0
- package/dist/provider-history-adapter.d.ts +2 -2
- package/dist/runtime-skills.d.ts +3 -10
- package/dist/sandbox/codemode-client.d.ts +17 -0
- package/dist/sandbox/codemode-token.d.ts +3 -3
- package/dist/sandbox/index.d.ts +2 -1
- package/dist/sandbox/index.js +8 -2
- package/dist/sandbox/materialization-verification-error.d.ts +27 -0
- package/dist/sandbox/provider-command-session.d.ts +44 -2
- package/dist/sandbox/providers/modal-command-control.d.ts +32 -29
- package/dist/sandbox/providers/modal-command-router-wire.d.ts +49 -0
- package/dist/sandbox/providers/modal-command-session.d.ts +2 -1
- package/dist/sandbox/providers/modal-legacy-command-control.d.ts +50 -0
- package/dist/sandbox/providers/modal-materialization-verification.d.ts +7 -0
- package/dist/sandbox/providers/modal.d.ts +0 -8
- package/dist/sandbox/routing/routing-session.d.ts +21 -0
- package/dist/sandbox/turn-tool-cancellation.d.ts +1 -0
- package/dist/skill-library.d.ts +0 -6
- package/dist/skill-library.js +1 -1
- package/dist/workspace-tool-gateway.js +7 -5
- package/package.json +13 -11
- package/src/bundled_artifact_skills/opengeni-documents/SKILL.md +19 -0
- package/src/bundled_artifact_skills/opengeni-documents/references/api.md +22 -0
- package/src/bundled_default_skills/opengeni-client/SKILL.md +349 -0
- package/src/bundled_default_skills/opengeni-client/agents/openai.yaml +4 -0
- package/src/bundled_default_skills/opengeni-client/references/api-workflows.md +256 -0
- package/src/bundled_default_skills/opengeni-client/references/compatibility-and-troubleshooting.md +84 -0
- package/src/bundled_default_skills/opengeni-client/references/customer-skill-template.md +96 -0
- package/src/bundled_default_skills/opengeni-client/references/data-tools-and-credentials.md +99 -0
- package/src/bundled_default_skills/opengeni-client/references/discovery-and-autonomy.md +81 -0
- package/src/bundled_default_skills/opengeni-client/references/external-users-and-connect.md +457 -0
- package/src/bundled_default_skills/opengeni-client/references/isolation-and-authorization.md +77 -0
- package/src/bundled_default_skills/opengeni-client/references/product-integration-shapes.md +232 -0
- package/src/bundled_default_skills/opengeni-client/references/product-shapes-and-ui.md +116 -0
- package/src/bundled_default_skills/opengeni-client/references/runtime-profile-and-verification.md +157 -0
- package/src/bundled_default_skills/opengeni-help/SKILL.md +101 -0
- package/src/bundled_management_skills/opengeni-skills/SKILL.md +33 -3
- package/src/codex-tool-search.ts +13 -5
- package/src/context-compaction.ts +87 -4
- package/src/gmail-rest-mcp.ts +1 -2
- package/src/index.ts +363 -131
- package/src/lazy-manifest.ts +55 -0
- package/src/lazy-tool-transport.ts +15 -2
- package/src/metrics.ts +6 -0
- package/src/model-request-capture.ts +3 -0
- package/src/operational-instructions.ts +11 -2
- package/src/prepared-compaction-request.ts +51 -0
- package/src/provider-history-adapter.ts +12 -3
- package/src/runtime-skills.ts +3 -17
- package/src/sandbox/codemode-client.ts +204 -0
- package/src/sandbox/codemode-token.ts +7 -2
- package/src/sandbox/index.ts +7 -0
- package/src/sandbox/materialization-verification-error.ts +65 -0
- package/src/sandbox/provider-command-session.ts +70 -2
- package/src/sandbox/providers/modal-command-control.ts +386 -242
- package/src/sandbox/providers/modal-command-router-wire.ts +248 -0
- package/src/sandbox/providers/modal-command-session.ts +177 -4
- package/src/sandbox/providers/modal-legacy-command-control.ts +351 -0
- package/src/sandbox/providers/modal-materialization-verification.ts +122 -0
- package/src/sandbox/providers/modal.ts +1 -9
- package/src/sandbox/routing/routing-session.ts +417 -79
- package/src/sandbox/turn-tool-cancellation.ts +38 -2
- package/src/skill-catalog.ts +1 -0
- package/src/skill-library.ts +0 -6
- package/src/workspace-governance.ts +0 -1
- package/src/workspace-skills.ts +114 -74
- package/dist/chunk-46BFUGFL.js.map +0 -1
- package/dist/chunk-FHGLYLBL.js.map +0 -1
- package/dist/chunk-GB763TSV.js.map +0 -1
- package/dist/chunk-RIJ6DCYS.js.map +0 -1
- package/dist/chunk-UU6EJR52.js.map +0 -1
- /package/dist/{localSources-PVFCAPN2.js.map → chunk-4A4XTKKX.js.map} +0 -0
package/src/index.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
withPreparedCompactionRequest,
|
|
3
|
+
deferCompactionToModelBoundary,
|
|
4
|
+
} from "./prepared-compaction-request";
|
|
5
|
+
export { preparedCompactionRequest, queuePreparedCompaction } from "./prepared-compaction-request";
|
|
1
6
|
import type { ModelProviderApi, ResolvedModelProvider, Settings } from "@opengeni/config";
|
|
2
7
|
import { executeCommandReadWithRefresh } from "./command-read-refresh";
|
|
3
8
|
import {
|
|
@@ -233,6 +238,7 @@ import {
|
|
|
233
238
|
CODEX_APPS_MCP_SERVER_ID,
|
|
234
239
|
CODEX_APPS_MCP_URL,
|
|
235
240
|
CODEX_ORIGINATOR,
|
|
241
|
+
classifyCodexEncryptedArtifactRejection,
|
|
236
242
|
codexAppsSanitizingFetch,
|
|
237
243
|
} from "@opengeni/codex";
|
|
238
244
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
@@ -275,6 +281,11 @@ import {
|
|
|
275
281
|
sandboxCommandStdout,
|
|
276
282
|
} from "./sandbox/command-result";
|
|
277
283
|
import { shellCodemodePath } from "./sandbox/codemode-token";
|
|
284
|
+
import {
|
|
285
|
+
installManagedCodemodeClient,
|
|
286
|
+
loadManagedCodemodeClient,
|
|
287
|
+
managedCodemodeClientDirectory,
|
|
288
|
+
} from "./sandbox/codemode-client";
|
|
278
289
|
import { InputWaitYield, type InputWaitYieldStream } from "./input-wait-yield";
|
|
279
290
|
export { InputWaitYield } from "./input-wait-yield";
|
|
280
291
|
import {
|
|
@@ -319,7 +330,6 @@ export {
|
|
|
319
330
|
type EffectiveSkillSelection,
|
|
320
331
|
type InstalledSkillActivation,
|
|
321
332
|
type NativeToolSkillSet,
|
|
322
|
-
type PackSkillActivation,
|
|
323
333
|
type RuntimeSkillActivation,
|
|
324
334
|
type RuntimeSkillArtifact,
|
|
325
335
|
type RuntimeSkillArtifactFile,
|
|
@@ -338,6 +348,7 @@ import {
|
|
|
338
348
|
import {
|
|
339
349
|
ModelRequestCaptureModel,
|
|
340
350
|
ModelRequestCaptureProvider,
|
|
351
|
+
notifyModelRequestCapture,
|
|
341
352
|
withModelRequestCapture,
|
|
342
353
|
type ModelRequestCapture,
|
|
343
354
|
nextModelContextCaptureIndex,
|
|
@@ -598,6 +609,10 @@ export {
|
|
|
598
609
|
CompactionNeededError,
|
|
599
610
|
CompactionProviderResponseError,
|
|
600
611
|
EmptyCompactionSummaryError,
|
|
612
|
+
compactionProviderRejection,
|
|
613
|
+
compactionProviderRejectionFromDiagnostics,
|
|
614
|
+
describeCompactionProviderRejection,
|
|
615
|
+
type CompactionProviderRejection,
|
|
601
616
|
buildCompactionPromptInput,
|
|
602
617
|
buildCompactionReplacementHistory,
|
|
603
618
|
compactionReplacementFingerprint,
|
|
@@ -1258,10 +1273,9 @@ function serializeToolForRemoteCompaction(tool: Tool): SerializedTool | null {
|
|
|
1258
1273
|
* exactly one `{ type: "compaction", encrypted_content }` output item.
|
|
1259
1274
|
* Must run inside Codex ALS with `remote_compaction_v2` beta + turn metadata.
|
|
1260
1275
|
*
|
|
1261
|
-
*
|
|
1262
|
-
*
|
|
1263
|
-
*
|
|
1264
|
-
* busts the shared tools→instructions prefix and is rejected here.
|
|
1276
|
+
* Reuse the complete prepared ordinary request, after sandbox and lazy-tool
|
|
1277
|
+
* preparation. Never reconstruct its prefix from Agent configuration. Empty
|
|
1278
|
+
* instructions are rejected.
|
|
1265
1279
|
*
|
|
1266
1280
|
* Tools are schema context only. This is a single `_fetchResponse` (no tool
|
|
1267
1281
|
* loop), and extract still requires exactly one compaction item, so a
|
|
@@ -1274,54 +1288,44 @@ export async function requestRemoteCompactionV2(
|
|
|
1274
1288
|
client: OpenAI;
|
|
1275
1289
|
provider?: ResolvedModelProvider;
|
|
1276
1290
|
model: string;
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
*/
|
|
1281
|
-
systemInstructions: string;
|
|
1282
|
-
promptCacheKey?: string;
|
|
1283
|
-
/** Model-visible tool schemas for the compact request (CLI parity). */
|
|
1284
|
-
tools?: readonly SerializedTool[];
|
|
1291
|
+
preparedRequest: Omit<ModelRequest, "input">;
|
|
1292
|
+
captureAgent?: object;
|
|
1293
|
+
signal?: AbortSignal | undefined;
|
|
1285
1294
|
onUsage?: (usage: ModelResponseUsage) => void | Promise<void>;
|
|
1286
1295
|
},
|
|
1287
1296
|
): Promise<Record<string, unknown>> {
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
// keep leading/trailing whitespace and bust the tools→instructions prefix.
|
|
1291
|
-
if (options.systemInstructions.trim() === "") {
|
|
1297
|
+
const { signal: _priorSignal, ...prefix } = options.preparedRequest;
|
|
1298
|
+
if (!prefix.systemInstructions?.trim()) {
|
|
1292
1299
|
throw new EmptyCompactionSummaryError({
|
|
1293
1300
|
stage: "remote_v2_instructions",
|
|
1294
1301
|
reason: "empty_system_instructions",
|
|
1295
1302
|
});
|
|
1296
1303
|
}
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
const tools = options.tools ? [...options.tools] : [];
|
|
1304
|
+
// Reuse the complete prepared request. New SDK/model settings flow through
|
|
1305
|
+
// automatically; only history and the transient compaction marker differ.
|
|
1300
1306
|
const request: ModelRequest = {
|
|
1301
|
-
|
|
1302
|
-
input:
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
...(settings.openaiProvider === "azure" ? {} : { store: false }),
|
|
1306
|
-
...(options.promptCacheKey
|
|
1307
|
-
? { providerData: { prompt_cache_key: options.promptCacheKey } }
|
|
1308
|
-
: {}),
|
|
1309
|
-
},
|
|
1310
|
-
tools,
|
|
1311
|
-
toolsExplicitlyProvided: true,
|
|
1312
|
-
outputType: "text",
|
|
1313
|
-
handoffs: [],
|
|
1307
|
+
...prefix,
|
|
1308
|
+
input: buildRemoteCompactionV2PromptInput(input) as AgentInputItem[],
|
|
1309
|
+
// This call outlives the SDK Runner trace. Tracing is client-side metadata,
|
|
1310
|
+
// not part of the provider request or cache prefix.
|
|
1314
1311
|
tracing: false,
|
|
1312
|
+
// The stopped inference stream may have aborted its per-call signal.
|
|
1313
|
+
// Compaction uses the still-active turn cancellation signal instead.
|
|
1314
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
1315
1315
|
};
|
|
1316
1316
|
let response: unknown;
|
|
1317
1317
|
try {
|
|
1318
1318
|
const provider = options.provider ?? configuredProviders(settings)[0];
|
|
1319
1319
|
if (!provider) throw new Error("Built-in model provider is unavailable");
|
|
1320
|
-
response = await
|
|
1321
|
-
options.
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1320
|
+
response = await withModelRequestCapture(
|
|
1321
|
+
options.captureAgent ? agentModelContextCaptures.get(options.captureAgent) : undefined,
|
|
1322
|
+
async () => {
|
|
1323
|
+
void notifyModelRequestCapture(request);
|
|
1324
|
+
return new CompactionResponsesModel(options.client, options.model, provider).fetchResponse(
|
|
1325
|
+
request,
|
|
1326
|
+
);
|
|
1327
|
+
},
|
|
1328
|
+
);
|
|
1325
1329
|
} catch (error) {
|
|
1326
1330
|
throw new CompactionProviderResponseError(compactionProviderFailureDiagnostics(error), error);
|
|
1327
1331
|
}
|
|
@@ -1366,8 +1370,13 @@ export function compactionProviderFailureDiagnostics(error: unknown): Record<str
|
|
|
1366
1370
|
let code: string | null = null;
|
|
1367
1371
|
let type: string | null = null;
|
|
1368
1372
|
let requestId: string | null = null;
|
|
1373
|
+
let param: string | null = null;
|
|
1369
1374
|
let eventType: string | null = null;
|
|
1370
|
-
let rejectionReason:
|
|
1375
|
+
let rejectionReason: CompactionRejectionReason | null = classifyCodexEncryptedArtifactRejection(
|
|
1376
|
+
error,
|
|
1377
|
+
)
|
|
1378
|
+
? "encrypted_content_rejected"
|
|
1379
|
+
: null;
|
|
1371
1380
|
const seen = new Set<object>();
|
|
1372
1381
|
for (let depth = 0; depth < 6 && current && typeof current === "object"; depth += 1) {
|
|
1373
1382
|
if (seen.has(current)) break;
|
|
@@ -1377,6 +1386,11 @@ export function compactionProviderFailureDiagnostics(error: unknown): Record<str
|
|
|
1377
1386
|
if (!errorName && current instanceof Error) {
|
|
1378
1387
|
errorName = boundCompactionDiagnosticField(current.name);
|
|
1379
1388
|
}
|
|
1389
|
+
// `error.param` names the rejected request field (an identifier path such
|
|
1390
|
+
// as `input[3].encrypted_content`), never conversation content.
|
|
1391
|
+
if (param === null && typeof record.param === "string" && record.param.length > 0) {
|
|
1392
|
+
param = boundCompactionDiagnosticField(record.param);
|
|
1393
|
+
}
|
|
1380
1394
|
if (
|
|
1381
1395
|
httpStatus === null &&
|
|
1382
1396
|
typeof record.status === "number" &&
|
|
@@ -1406,7 +1420,10 @@ export function compactionProviderFailureDiagnostics(error: unknown): Record<str
|
|
|
1406
1420
|
eventType = boundCompactionDiagnosticField(directEventType);
|
|
1407
1421
|
}
|
|
1408
1422
|
if (requestId === null) {
|
|
1409
|
-
|
|
1423
|
+
// `requestID` is the OpenAI SDK's APIError property; the others are
|
|
1424
|
+
// provider-body and legacy spellings.
|
|
1425
|
+
const directRequestId =
|
|
1426
|
+
record.request_id ?? record.requestId ?? record.requestID ?? record._request_id;
|
|
1410
1427
|
if (typeof directRequestId === "string") {
|
|
1411
1428
|
requestId = boundCompactionDiagnosticField(directRequestId);
|
|
1412
1429
|
}
|
|
@@ -1431,6 +1448,9 @@ export function compactionProviderFailureDiagnostics(error: unknown): Record<str
|
|
|
1431
1448
|
if (type === null && typeof nested.type === "string") {
|
|
1432
1449
|
type = boundCompactionDiagnosticField(nested.type);
|
|
1433
1450
|
}
|
|
1451
|
+
if (param === null && typeof nested.param === "string" && nested.param.length > 0) {
|
|
1452
|
+
param = boundCompactionDiagnosticField(nested.param);
|
|
1453
|
+
}
|
|
1434
1454
|
const nestedResponseStatus = nested.response_status ?? nested.responseStatus;
|
|
1435
1455
|
if (responseStatus === null && typeof nestedResponseStatus === "string") {
|
|
1436
1456
|
responseStatus = boundCompactionDiagnosticField(nestedResponseStatus);
|
|
@@ -1453,12 +1473,21 @@ export function compactionProviderFailureDiagnostics(error: unknown): Record<str
|
|
|
1453
1473
|
responseId,
|
|
1454
1474
|
code,
|
|
1455
1475
|
type,
|
|
1476
|
+
param,
|
|
1456
1477
|
requestId,
|
|
1457
1478
|
...(eventType ? { eventType } : {}),
|
|
1458
1479
|
...(rejectionReason ? { rejectionReason } : {}),
|
|
1459
1480
|
};
|
|
1460
1481
|
}
|
|
1461
1482
|
|
|
1483
|
+
/**
|
|
1484
|
+
* Closed provider rejection families the worker reacts to. Anything else is a
|
|
1485
|
+
* bounded status/code/param record with no classification.
|
|
1486
|
+
*/
|
|
1487
|
+
export type CompactionRejectionReason =
|
|
1488
|
+
| "missing_required_reasoning_item"
|
|
1489
|
+
| "encrypted_content_rejected";
|
|
1490
|
+
|
|
1462
1491
|
function compactionRejectionReason(
|
|
1463
1492
|
record: Record<string, unknown>,
|
|
1464
1493
|
): "missing_required_reasoning_item" | null {
|
|
@@ -1978,15 +2007,12 @@ export type BuildAgentOptions = {
|
|
|
1978
2007
|
// timeline message. Omitted ⇒ the composed instructions are byte-identical to
|
|
1979
2008
|
// a workspace-only persona.
|
|
1980
2009
|
sessionInstructions?: string;
|
|
1981
|
-
|
|
1982
|
-
* Exact Skill activations admitted for this turn. Optional/domain Skills
|
|
1983
|
-
* enter only through an explicit installation, Pack owner, or session
|
|
1984
|
-
* selection; native tool-bound Skills are derived separately from the exact
|
|
1985
|
-
* executable tool catalog.
|
|
1986
|
-
*/
|
|
2010
|
+
|
|
1987
2011
|
skillActivations?: readonly RuntimeSkillActivation[];
|
|
1988
2012
|
/** Host-owned descriptors and reader; mutually exclusive with skillActivations. */
|
|
1989
2013
|
skillCatalog?: readonly SkillCatalogDescriptor[];
|
|
2014
|
+
/** Worker has persisted the catalog in conversation history before inference. */
|
|
2015
|
+
skillCatalogInHistory?: boolean;
|
|
1990
2016
|
/**
|
|
1991
2017
|
* Internal per-attempt cancellation boundary. The worker supplies Temporal's
|
|
1992
2018
|
* signal so an in-flight shell process is interrupted immediately instead of
|
|
@@ -2034,10 +2060,10 @@ export type RigInstructionsContext = {
|
|
|
2034
2060
|
|
|
2035
2061
|
export function rigInstructions(rig: RigInstructionsContext): string[] {
|
|
2036
2062
|
return [
|
|
2037
|
-
`This session
|
|
2038
|
-
"Your sandbox is an EPHEMERAL FORK of
|
|
2039
|
-
"
|
|
2040
|
-
"If tooling you expect is missing, consult rig_get to see the
|
|
2063
|
+
`This session uses sandbox environment "${rig.name}" (active version v${rig.version}) — a versioned definition of custom sandbox setup and health checks.`,
|
|
2064
|
+
"Your sandbox is an EPHEMERAL FORK of this environment. You may install tools here, but local changes do not update the environment definition or other sessions.",
|
|
2065
|
+
"To make a verified setup change available to future sessions using this environment, call rig_propose_change with the exact command that already worked here. Never assume an unverified change propagates.",
|
|
2066
|
+
"If tooling you expect is missing, consult rig_get to see the sandbox environment's current setup and checks before reinstalling.",
|
|
2041
2067
|
];
|
|
2042
2068
|
}
|
|
2043
2069
|
|
|
@@ -2075,7 +2101,9 @@ export function coreInstructions(
|
|
|
2075
2101
|
): string[] {
|
|
2076
2102
|
return [
|
|
2077
2103
|
"If the session has a goal, you own it: keep working until you call opengeni__goal_complete with concrete evidence or opengeni__goal_pause with a rationale; resume a paused goal with opengeni__goal_resume regardless of who paused it or why; revise it with opengeni__goal_update; create one with opengeni__goal_set when given a long-running objective.",
|
|
2078
|
-
|
|
2104
|
+
'Choose durable storage by purpose, including requests to "remember this" or keep something "for future sessions". Knowledge is retrieval-only information, not standing behavior. Use instruction_policy_get then instruction_policy_save for short always-on workspace rules, such as "Keep replies concise; expand when asked." Use Skills for reusable procedures and context-specific or personal behavioral preferences; make the Skill description state when it applies so the prompt index can guide skill_read. Do not save behavioral preferences as Knowledge by rephrasing them as facts like "the user prefers concise replies". Preserve the intended scope: do not turn a personal preference into a workspace-wide rule; use an authorized personal Skill when appropriate, or explain the unavailable scope. Split mixed requests into a short rule and detailed Skill steps without duplicating them in Knowledge.',
|
|
2105
|
+
"Before changing workspace instructions, read the current instruction and exact baseline, preserve unrelated rules, append new rules, and use only a localized exact anchored edit for updates or removals. Agents cannot replace the complete instruction; whole-policy rewrites belong in the manual workspace editor. Do not bypass a destination's Agent learning setting, review, scope, limits, or unavailable tools by saving to another destination. Report the actual destination and receipt: active, pending review, or not saved. Do not promise future behavior from a Knowledge save.",
|
|
2106
|
+
"Use knowledge_search and knowledge_get when retained information is relevant. For questions about what the workspace knows, ground the answer in authorized Knowledge and attached sources. Missing internal facts remain unknown; do not infer a person's role or cite unrelated public search results as evidence. Keep any relevant external research clearly separate from workspace records. Default retrieval returns published information. Before retaining or correcting anything, also search view=needs_review for existing pending entries and collections; read those with knowledge_get view=needs_review. Pending means unapproved: you may inspect and improve it, but do not present it as accepted knowledge or activate behavioral guidance from it. Reuse the existing entryId and current version instead of creating another proposal each run. Save durable facts, decisions, requirements, incidents, fixes and outcomes autonomously with knowledge_save only when they help a plausible future task. Do not retain routine approvals, acknowledgments, temporary task instructions, status chatter or raw screenshots merely because they occurred. Before saving, use knowledge_prepare_save when available to fetch the authorized collection map with descriptions and parent IDs plus related published and pending entries; otherwise search both views and browse collections. Skip unchanged duplicates, improve the existing entry when appropriate, and create a new entry only for distinct useful information. Read the current entry before correcting it and preserve uncertainty, conflicting evidence and existing relationships. When a user supplies or confirms a durable fact, use knowledge_retain_message to retain the actual message and cite its returned entryId/revisionId in the finding evidence. Preserve existing source evidence and relationships when correcting an entry; do not replace them with empty arrays merely because the user confirmed a new value. Explicit confirmation can supersede a conflicting pending proposal, but explain that outcome to the user. Preserve uncertainty and exact supporting evidence; the fact label is not a verification claim. Chat attachments retain their original files for the conversation without automatically publishing OCR or source text into Knowledge. Inspect an image visually in the current task; save only a useful supported observation, requirement or incident when warranted. Use knowledge_retain_file purpose=evidence only when a selected finding needs the original as supporting evidence, or purpose=reference when deliberately retaining a reusable source document. Supporting sources remain accessible through evidence links and explicit includeEvidence searches, but do not fill ordinary Knowledge discovery. An upload alone is not a reason to save Knowledge. A source entry contains retained original text; a finding states a useful conclusion and cites the exact source revision. Do not create both when they would simply repeat the same text. Reuse collections for customers, products, systems or subjects across sources, and link one entry to multiple collections instead of copying it. Technical incidents belong with the affected system and should include cause, fix and outcome when known. Reorganize references when useful; do not erase source evidence or revision history. Private tasks author personal Knowledge; shared tasks author workspace Knowledge. Automatic publishes immediately, Review first keeps a pending proposal without pausing your task, and Off prevents authoring while permitting retrieval. Never bypass review by making a new entry, switching tools, or asking for another approval. Reuse the same operationId and exact request after an uncertain save, including recovery. Use task_note_save for temporary coordination in this task tree. Conversation history is separate. Workspace instructions are concise standing rules; Skills are reusable procedures with their own Agent learning settings. Do not save the same content in multiple authorities.",
|
|
2079
2107
|
...(workspaceEnvironment ? workspaceEnvironmentInstructions(workspaceEnvironment) : []),
|
|
2080
2108
|
// Rig doctrine (M3): data-conditional, inside the non-bypassable CORE so a
|
|
2081
2109
|
// white-label persona template can never drop it. Absent for rig-less sessions.
|
|
@@ -2195,12 +2223,12 @@ export function inspectPersistentAgentInstructions(
|
|
|
2195
2223
|
});
|
|
2196
2224
|
}
|
|
2197
2225
|
push("workspace_memory", "Workspace memory", options.workspaceMemory);
|
|
2198
|
-
if (options.skillCatalog)
|
|
2226
|
+
if (options.skillCatalog && !options.skillCatalogInHistory)
|
|
2199
2227
|
push("skill_catalog", "Skills", formatSkillCatalog(options.skillCatalog));
|
|
2200
2228
|
push("session_instructions", "Session instructions", options.sessionInstructions);
|
|
2201
2229
|
} else {
|
|
2202
2230
|
push("workspace_governance", "Workspace governance", options.workspaceGovernance);
|
|
2203
|
-
if (options.skillCatalog)
|
|
2231
|
+
if (options.skillCatalog && !options.skillCatalogInHistory)
|
|
2204
2232
|
push("skill_catalog", "Skills", formatSkillCatalog(options.skillCatalog));
|
|
2205
2233
|
push("session_instructions", "Session instructions", options.sessionInstructions);
|
|
2206
2234
|
if (codemodeIsAvailable(options)) {
|
|
@@ -2847,8 +2875,8 @@ function maybeInstallLazyToolTransport(
|
|
|
2847
2875
|
if (!enabled) return;
|
|
2848
2876
|
|
|
2849
2877
|
const mcpServers = options.mcpServers ?? [];
|
|
2850
|
-
// Prepared servers use
|
|
2851
|
-
//
|
|
2878
|
+
// Prepared servers use exact model-name mappings, not SDK lifecycle names
|
|
2879
|
+
// or parseable prefixes. Preserve legacy embedded/test-server classification.
|
|
2852
2880
|
const mcpServerIds = new Set(mcpServers.map((server) => mcpServerRegistryId(server)));
|
|
2853
2881
|
const deferredMcpServerIds = new Set(
|
|
2854
2882
|
mcpServers
|
|
@@ -2862,6 +2890,18 @@ function maybeInstallLazyToolTransport(
|
|
|
2862
2890
|
options.toolPreparationReady,
|
|
2863
2891
|
deferredMcpServerIds,
|
|
2864
2892
|
new Set(options.preparationIndependentToolNames ?? []),
|
|
2893
|
+
() => {
|
|
2894
|
+
const identities = new Map<string, string>();
|
|
2895
|
+
for (const server of mcpServers) {
|
|
2896
|
+
if (!(server instanceof PrefixedMcpServer || server instanceof DeferredPreparedMcpServer))
|
|
2897
|
+
continue;
|
|
2898
|
+
for (const name of server.modelToolNames()) {
|
|
2899
|
+
if (identities.has(name)) throw new Error("MCP model tool identity collision");
|
|
2900
|
+
identities.set(name, server.registryId);
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
return identities;
|
|
2904
|
+
},
|
|
2865
2905
|
);
|
|
2866
2906
|
}
|
|
2867
2907
|
|
|
@@ -2894,7 +2934,7 @@ function sessionMcpApprovalConnectionId(serverId: string, url: string): string {
|
|
|
2894
2934
|
return `session-mcp:${serverId}:${targetHash}`;
|
|
2895
2935
|
}
|
|
2896
2936
|
|
|
2897
|
-
/**
|
|
2937
|
+
/** Exact server policy. Prefix matching is only for legacy unwrapped servers. */
|
|
2898
2938
|
type McpApprovalPolicy = {
|
|
2899
2939
|
prefix: string;
|
|
2900
2940
|
serverId: string;
|
|
@@ -2906,6 +2946,7 @@ type McpApprovalPolicy = {
|
|
|
2906
2946
|
/** The subset of the agent surface the approval wrap needs — including `clone`. */
|
|
2907
2947
|
type ApprovalCapableAgent = {
|
|
2908
2948
|
getMcpTools: (runContext: unknown) => Promise<Tool<any>[]>;
|
|
2949
|
+
mcpServers?: MCPServer[];
|
|
2909
2950
|
clone?: (config: unknown) => ApprovalCapableAgent;
|
|
2910
2951
|
};
|
|
2911
2952
|
|
|
@@ -2948,9 +2989,9 @@ function modelMcpSourceCallId(modelName: string): string | undefined {
|
|
|
2948
2989
|
/**
|
|
2949
2990
|
* Install the approval wrap on a single agent instance: replace `getMcpTools`
|
|
2950
2991
|
* with one that stamps `needsApproval: () => true` on every MCP tool whose
|
|
2951
|
-
* server policy demands it.
|
|
2952
|
-
*
|
|
2953
|
-
*
|
|
2992
|
+
* server policy demands it. Prepared tools resolve through their frozen model
|
|
2993
|
+
* name -> original server/tool map; legacy unwrapped servers retain longest-
|
|
2994
|
+
* prefix matching. Never infer account authority from a sanitized SDK name.
|
|
2954
2995
|
*
|
|
2955
2996
|
* CLONE SURVIVAL (also used by `installLazyToolRuntime`): the sandbox runtime
|
|
2956
2997
|
* resolves tools not on the agent we build here but on a FRESH clone —
|
|
@@ -2973,20 +3014,35 @@ function installMcpApprovalPolicy(
|
|
|
2973
3014
|
const listMcpTools = agent.getMcpTools.bind(agent);
|
|
2974
3015
|
agent.getMcpTools = async (resolutionContext: unknown) => {
|
|
2975
3016
|
const tools = await listMcpTools(resolutionContext);
|
|
3017
|
+
const identities = new Map<string, { serverId: string; toolName: string }>();
|
|
3018
|
+
for (const server of agent.mcpServers ?? []) {
|
|
3019
|
+
if (!(server instanceof PrefixedMcpServer || server instanceof DeferredPreparedMcpServer))
|
|
3020
|
+
continue;
|
|
3021
|
+
for (const descriptor of await server.listTools()) {
|
|
3022
|
+
if (identities.has(descriptor.name)) throw new Error("MCP model tool identity collision");
|
|
3023
|
+
identities.set(descriptor.name, {
|
|
3024
|
+
serverId: server.registryId,
|
|
3025
|
+
toolName: await server.unprefixedToolName(descriptor.name),
|
|
3026
|
+
});
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
2976
3029
|
return tools.map((tool) => {
|
|
2977
3030
|
if (tool.type !== "function") {
|
|
2978
3031
|
return tool;
|
|
2979
3032
|
}
|
|
2980
|
-
const
|
|
3033
|
+
const identity = identities.get(tool.name);
|
|
3034
|
+
const policy = identity
|
|
3035
|
+
? policies.find((entry) => entry.serverId === identity.serverId)
|
|
3036
|
+
: policies.find((entry) => tool.name.startsWith(entry.prefix));
|
|
2981
3037
|
if (!policy) {
|
|
2982
3038
|
return tool;
|
|
2983
3039
|
}
|
|
2984
|
-
const unprefixed = tool.name.slice(policy.prefix.length);
|
|
3040
|
+
const unprefixed = identity?.toolName ?? tool.name.slice(policy.prefix.length);
|
|
2985
3041
|
const originalNeedsApproval = tool.needsApproval.bind(tool);
|
|
2986
3042
|
const originalInvoke = tool.invoke.bind(tool);
|
|
2987
3043
|
const legacyApproval =
|
|
2988
3044
|
!policy.connectorBacked && mcpToolRequiresApproval(policy.requireApproval, unprefixed);
|
|
2989
|
-
if (!policy.connectorBacked && !legacyApproval) {
|
|
3045
|
+
if (!policy.connectorBacked && !legacyApproval && !connectorActionPolicy) {
|
|
2990
3046
|
return tool;
|
|
2991
3047
|
}
|
|
2992
3048
|
const connectorCall = (approvalId: string, args: unknown): ConnectorActionToolCall => {
|
|
@@ -3295,7 +3351,8 @@ function installInteractionInterventionPolicy(
|
|
|
3295
3351
|
* tools to function tools with `needsApproval` unset (defaults false) and exposes
|
|
3296
3352
|
* no per-server/agent approval knob, so we wrap the agent's `getMcpTools` to
|
|
3297
3353
|
* attach a `needsApproval: () => true` predicate to the matching tools — matched
|
|
3298
|
-
* by
|
|
3354
|
+
* by exact prepared server/tool identity (legacy unwrapped servers use their
|
|
3355
|
+
* `<id>__` prefix). A tool that
|
|
3299
3356
|
* needs approval raises a run INTERRUPTION, which the worker turns into
|
|
3300
3357
|
* `session.requiresAction` and resolves via `user.approvalDecision`
|
|
3301
3358
|
* (resumeApproval) — the same generic path other tool approvals use, so
|
|
@@ -3324,6 +3381,7 @@ function applyMcpApprovalPolicy(
|
|
|
3324
3381
|
.filter(
|
|
3325
3382
|
(server) =>
|
|
3326
3383
|
Boolean(server.connectionRef) ||
|
|
3384
|
+
Boolean(connectorActionPolicy && attemptToolSource(server.id) === "mcp") ||
|
|
3327
3385
|
server.requireApproval === true ||
|
|
3328
3386
|
(Array.isArray(server.requireApproval) && server.requireApproval.length > 0),
|
|
3329
3387
|
)
|
|
@@ -3338,7 +3396,9 @@ function applyMcpApprovalPolicy(
|
|
|
3338
3396
|
prefix: prefixedMcpToolName(server.id, ""),
|
|
3339
3397
|
serverId: server.id,
|
|
3340
3398
|
requireApproval:
|
|
3341
|
-
server.requireApproval === true
|
|
3399
|
+
server.requireApproval === true
|
|
3400
|
+
? true
|
|
3401
|
+
: new Set(Array.isArray(server.requireApproval) ? server.requireApproval : []),
|
|
3342
3402
|
connectorBacked: Boolean(server.connectionRef),
|
|
3343
3403
|
connectionId,
|
|
3344
3404
|
};
|
|
@@ -3676,11 +3736,15 @@ export type ToolPreparationPhase =
|
|
|
3676
3736
|
|
|
3677
3737
|
export type ToolPreparationPhaseMeasurement = {
|
|
3678
3738
|
phase: ToolPreparationPhase;
|
|
3739
|
+
/** Background preparation starts immediately but is not a first-request dependency. */
|
|
3740
|
+
execution: "blocking" | "background";
|
|
3679
3741
|
outcome: "completed" | "failed";
|
|
3680
3742
|
durationSeconds: number;
|
|
3681
3743
|
};
|
|
3682
3744
|
|
|
3683
3745
|
export type PrepareToolsOptions = {
|
|
3746
|
+
/** Frozen, explicitly selected account labels keyed by execution route, not provider. */
|
|
3747
|
+
mcpAccountLabels?: ReadonlyMap<string, string>;
|
|
3684
3748
|
/** Opt-in exact-attempt persistence; absence preserves ordinary MCP execution. */
|
|
3685
3749
|
mcpOperationPersistence?: McpOperationPersistence;
|
|
3686
3750
|
/** Live exact-owner control refresh; API remains read/observation authority. */
|
|
@@ -3792,6 +3856,7 @@ async function measureToolPreparationPhase<T>(
|
|
|
3792
3856
|
options: PrepareToolsOptions,
|
|
3793
3857
|
phase: ToolPreparationPhase,
|
|
3794
3858
|
operation: () => Promise<T>,
|
|
3859
|
+
execution: ToolPreparationPhaseMeasurement["execution"] = "blocking",
|
|
3795
3860
|
): Promise<T> {
|
|
3796
3861
|
const startedAt = performance.now();
|
|
3797
3862
|
let outcome: ToolPreparationPhaseMeasurement["outcome"] = "completed";
|
|
@@ -3804,6 +3869,7 @@ async function measureToolPreparationPhase<T>(
|
|
|
3804
3869
|
try {
|
|
3805
3870
|
options.onPreparationPhase?.({
|
|
3806
3871
|
phase,
|
|
3872
|
+
execution,
|
|
3807
3873
|
outcome,
|
|
3808
3874
|
durationSeconds: (performance.now() - startedAt) / 1_000,
|
|
3809
3875
|
});
|
|
@@ -3929,6 +3995,7 @@ class DeferredPreparedMcpServer implements MCPServer {
|
|
|
3929
3995
|
readonly cacheToolsList = false;
|
|
3930
3996
|
readonly deferredPreparation = true;
|
|
3931
3997
|
readonly name: string;
|
|
3998
|
+
private readonly listedModelNames = new Set<string>();
|
|
3932
3999
|
|
|
3933
4000
|
constructor(
|
|
3934
4001
|
readonly registryId: string,
|
|
@@ -3959,7 +4026,20 @@ class DeferredPreparedMcpServer implements MCPServer {
|
|
|
3959
4026
|
async listTools(): Promise<RuntimeMcpTool[]> {
|
|
3960
4027
|
if (!this.isPrepared()) return [];
|
|
3961
4028
|
const target = await this.resolveTarget();
|
|
3962
|
-
|
|
4029
|
+
const tools = target ? ((await target.listTools()) as RuntimeMcpTool[]) : [];
|
|
4030
|
+
this.listedModelNames.clear();
|
|
4031
|
+
for (const tool of tools) this.listedModelNames.add(tool.name);
|
|
4032
|
+
return tools;
|
|
4033
|
+
}
|
|
4034
|
+
|
|
4035
|
+
modelToolNames(): Iterable<string> {
|
|
4036
|
+
return this.listedModelNames.values();
|
|
4037
|
+
}
|
|
4038
|
+
|
|
4039
|
+
async unprefixedToolName(name: string): Promise<string> {
|
|
4040
|
+
const target = await this.requiredTarget();
|
|
4041
|
+
if (!(target instanceof PrefixedMcpServer)) throw new Error("Unknown prepared MCP identity");
|
|
4042
|
+
return target.unprefixedToolName(name);
|
|
3963
4043
|
}
|
|
3964
4044
|
|
|
3965
4045
|
async callTool(
|
|
@@ -4076,6 +4156,9 @@ export async function prepareAgentTools(
|
|
|
4076
4156
|
tool.eager !== true,
|
|
4077
4157
|
local.preflightCall,
|
|
4078
4158
|
local.approvalAuthority,
|
|
4159
|
+
undefined,
|
|
4160
|
+
undefined,
|
|
4161
|
+
options.mcpAccountLabels?.get(config.id),
|
|
4079
4162
|
),
|
|
4080
4163
|
config,
|
|
4081
4164
|
options,
|
|
@@ -4129,7 +4212,7 @@ export async function prepareAgentTools(
|
|
|
4129
4212
|
// reject the bearer at the initialize/tools-list handshake, so a 401/403
|
|
4130
4213
|
// (or a missing/failed token) drops the server.
|
|
4131
4214
|
// - an optional ToolRef: either an auto-attached workspace-default
|
|
4132
|
-
|
|
4215
|
+
|
|
4133
4216
|
// broken/expired credential or unavailable endpoint skips the server
|
|
4134
4217
|
// with a warning, never killing the turn before the model runs. Bare
|
|
4135
4218
|
// refs stay strict (below), preserving the fail-loud default.
|
|
@@ -4217,6 +4300,7 @@ export async function prepareAgentTools(
|
|
|
4217
4300
|
firstParty && config.id === "opengeni" && !config.connectionRef && !bridge
|
|
4218
4301
|
? options.refreshOwnedCommand
|
|
4219
4302
|
: undefined,
|
|
4303
|
+
options.mcpAccountLabels?.get(config.id),
|
|
4220
4304
|
),
|
|
4221
4305
|
config,
|
|
4222
4306
|
options,
|
|
@@ -4263,6 +4347,7 @@ export async function prepareAgentTools(
|
|
|
4263
4347
|
entries: typeof servers,
|
|
4264
4348
|
strict: boolean,
|
|
4265
4349
|
phase: "required_connect" | "optional_connect",
|
|
4350
|
+
execution: ToolPreparationPhaseMeasurement["execution"],
|
|
4266
4351
|
): Promise<ConnectedMcpServerBatches | null> =>
|
|
4267
4352
|
entries.length === 0
|
|
4268
4353
|
? null
|
|
@@ -4277,6 +4362,7 @@ export async function prepareAgentTools(
|
|
|
4277
4362
|
connectTimeoutMs: mcpOuterConnectTimeoutMs(entries.map((entry) => entry.timeoutMs)),
|
|
4278
4363
|
},
|
|
4279
4364
|
),
|
|
4365
|
+
execution,
|
|
4280
4366
|
);
|
|
4281
4367
|
const warnBestEffortFailures = (connected: ConnectedMcpServerBatches | null): void => {
|
|
4282
4368
|
if (!connected) return;
|
|
@@ -4303,13 +4389,14 @@ export async function prepareAgentTools(
|
|
|
4303
4389
|
const connectEntryGroups = async (
|
|
4304
4390
|
required: typeof servers,
|
|
4305
4391
|
bestEffort: typeof servers,
|
|
4392
|
+
execution: ToolPreparationPhaseMeasurement["execution"],
|
|
4306
4393
|
): Promise<{
|
|
4307
4394
|
required: ConnectedMcpServerBatches | null;
|
|
4308
4395
|
bestEffort: ConnectedMcpServerBatches | null;
|
|
4309
4396
|
}> => {
|
|
4310
4397
|
const [requiredResult, bestEffortResult] = await Promise.allSettled([
|
|
4311
|
-
connectEntries(required, true, "required_connect"),
|
|
4312
|
-
connectEntries(bestEffort, false, "optional_connect"),
|
|
4398
|
+
connectEntries(required, true, "required_connect", execution),
|
|
4399
|
+
connectEntries(bestEffort, false, "optional_connect", execution),
|
|
4313
4400
|
]);
|
|
4314
4401
|
const connectedBestEffort =
|
|
4315
4402
|
bestEffortResult.status === "fulfilled" ? bestEffortResult.value : null;
|
|
@@ -4324,7 +4411,11 @@ export async function prepareAgentTools(
|
|
|
4324
4411
|
}
|
|
4325
4412
|
return { required: requiredResult.value, bestEffort: connectedBestEffort };
|
|
4326
4413
|
};
|
|
4327
|
-
const connectedEager = await connectEntryGroups(
|
|
4414
|
+
const connectedEager = await connectEntryGroups(
|
|
4415
|
+
eagerRequiredEntries,
|
|
4416
|
+
eagerBestEffortEntries,
|
|
4417
|
+
"blocking",
|
|
4418
|
+
);
|
|
4328
4419
|
const connectedEagerRequired = connectedEager.required;
|
|
4329
4420
|
const connectedEagerBestEffort = connectedEager.bestEffort;
|
|
4330
4421
|
warnBestEffortFailures(connectedEagerBestEffort);
|
|
@@ -4344,6 +4435,7 @@ export async function prepareAgentTools(
|
|
|
4344
4435
|
await connectedEagerRequired?.close().catch(() => undefined);
|
|
4345
4436
|
};
|
|
4346
4437
|
const completePreparation = async (): Promise<PreparedAgentTools> => {
|
|
4438
|
+
const execution = exposesDeferredPreparation ? "background" : "blocking";
|
|
4347
4439
|
let attemptToolEnvironment: AttemptToolEnvironment | null = null;
|
|
4348
4440
|
let toolGatewayCatalog: ToolGatewayCatalog | null = null;
|
|
4349
4441
|
let toolGateway: ToolGateway | null = null;
|
|
@@ -4351,6 +4443,7 @@ export async function prepareAgentTools(
|
|
|
4351
4443
|
const connectedDeferred = await connectEntryGroups(
|
|
4352
4444
|
deferredRequiredEntries,
|
|
4353
4445
|
deferredBestEffortEntries,
|
|
4446
|
+
execution,
|
|
4354
4447
|
);
|
|
4355
4448
|
connectedDeferredRequired = connectedDeferred.required;
|
|
4356
4449
|
connectedDeferredBestEffort = connectedDeferred.bestEffort;
|
|
@@ -4371,14 +4464,20 @@ export async function prepareAgentTools(
|
|
|
4371
4464
|
resolvedMcpConnectionIds,
|
|
4372
4465
|
options,
|
|
4373
4466
|
),
|
|
4467
|
+
execution,
|
|
4374
4468
|
);
|
|
4375
4469
|
if (attemptToolEnvironment && localToolServer) {
|
|
4376
4470
|
localToolServer.bindAttemptToolEnvironment(attemptToolEnvironment);
|
|
4377
4471
|
}
|
|
4378
4472
|
if (attemptToolEnvironment) {
|
|
4379
|
-
await measureToolPreparationPhase(
|
|
4380
|
-
|
|
4381
|
-
|
|
4473
|
+
await measureToolPreparationPhase(
|
|
4474
|
+
options,
|
|
4475
|
+
"attempt_catalog_persist",
|
|
4476
|
+
async () => {
|
|
4477
|
+
await options.onAttemptToolCatalog?.(attemptToolEnvironment!.catalog);
|
|
4478
|
+
},
|
|
4479
|
+
execution,
|
|
4480
|
+
);
|
|
4382
4481
|
}
|
|
4383
4482
|
if (options.workspaceToolGateway) {
|
|
4384
4483
|
const prepared = await measureToolPreparationPhase(
|
|
@@ -4386,6 +4485,7 @@ export async function prepareAgentTools(
|
|
|
4386
4485
|
"workspace_gateway_catalog_build",
|
|
4387
4486
|
async () =>
|
|
4388
4487
|
await prepareWorkspaceToolGatewayEnvironment(activeMcpServers, registry, options),
|
|
4488
|
+
execution,
|
|
4389
4489
|
);
|
|
4390
4490
|
toolGatewayCatalog = prepared.catalog;
|
|
4391
4491
|
toolGateway = prepared.gateway;
|
|
@@ -4655,7 +4755,11 @@ function installAttemptConnectorActionGatewayLifecycle(
|
|
|
4655
4755
|
const config = registry.get(definition.identity.serverId);
|
|
4656
4756
|
const legacyMcpApproval =
|
|
4657
4757
|
Boolean(config) && !config!.connectionRef && definition.approval === "human";
|
|
4658
|
-
|
|
4758
|
+
const workspaceConnectorPolicy = Boolean(
|
|
4759
|
+
connectorActionPolicy && config && attemptToolSource(config.id) === "mcp",
|
|
4760
|
+
);
|
|
4761
|
+
if (!binding && !config?.connectionRef && !legacyMcpApproval && !workspaceConnectorPolicy)
|
|
4762
|
+
return definition;
|
|
4659
4763
|
if (definition.lifecycle) {
|
|
4660
4764
|
throw new Error(`Connector action tool already owns a lifecycle: ${definition.modelName}`);
|
|
4661
4765
|
}
|
|
@@ -4684,7 +4788,7 @@ function installAttemptConnectorActionGatewayLifecycle(
|
|
|
4684
4788
|
serverId: definition.identity.serverId,
|
|
4685
4789
|
toolName: definition.identity.toolName,
|
|
4686
4790
|
arguments: arguments_,
|
|
4687
|
-
approvalMode: "session_mcp",
|
|
4791
|
+
...(legacyMcpApproval ? { approvalMode: "session_mcp" as const } : {}),
|
|
4688
4792
|
});
|
|
4689
4793
|
return {
|
|
4690
4794
|
...definition,
|
|
@@ -6408,7 +6512,15 @@ function normalizeUrl(raw: string): string | null {
|
|
|
6408
6512
|
}
|
|
6409
6513
|
|
|
6410
6514
|
export function prefixedMcpToolName(registryId: string, toolName: string): string {
|
|
6411
|
-
|
|
6515
|
+
const name = sharedPrefixedMcpToolName(registryId, toolName);
|
|
6516
|
+
// The SDK normalizes punctuation (including hyphens) and providers cap names
|
|
6517
|
+
// at 64 characters. Use the complete digest, not lossy truncation. Hex names
|
|
6518
|
+
// cannot collide with ordinary names, which always contain the __ separator.
|
|
6519
|
+
return name.length <= 64 && /^[A-Za-z0-9_]+$/.test(name)
|
|
6520
|
+
? name
|
|
6521
|
+
: createHash("sha256")
|
|
6522
|
+
.update(JSON.stringify([registryId, toolName]))
|
|
6523
|
+
.digest("hex");
|
|
6412
6524
|
}
|
|
6413
6525
|
|
|
6414
6526
|
const MCP_SDK_LIFECYCLE_NAME = "opengeni-mcp-lifecycle";
|
|
@@ -6605,6 +6717,11 @@ export class PrefixedMcpServer implements MCPServer {
|
|
|
6605
6717
|
private loggedListToolsFailure = false;
|
|
6606
6718
|
private listedToolSchemaTokens = 0;
|
|
6607
6719
|
private frozenTools: Promise<RuntimeMcpTool[]> | null = null;
|
|
6720
|
+
private readonly originalToolNames = new Map<string, string>();
|
|
6721
|
+
/** Exact attempt-local classification, including names too long for a prefix. */
|
|
6722
|
+
modelToolNames(): Iterable<string> {
|
|
6723
|
+
return this.originalToolNames.keys();
|
|
6724
|
+
}
|
|
6608
6725
|
private attemptToolEnvironment: AttemptToolEnvironment | null = null;
|
|
6609
6726
|
private attemptToolSubjectId = "worker:mcp-model";
|
|
6610
6727
|
private readonly resultCustomDataBridge: McpResultCustomDataBridge;
|
|
@@ -6626,6 +6743,7 @@ export class PrefixedMcpServer implements MCPServer {
|
|
|
6626
6743
|
private readonly approvalAuthority?: unknown,
|
|
6627
6744
|
private readonly inputWaitYield?: InputWaitYield,
|
|
6628
6745
|
private readonly refreshOwnedCommand?: (commandId: string) => Promise<boolean>,
|
|
6746
|
+
private readonly accountLabel?: string,
|
|
6629
6747
|
) {
|
|
6630
6748
|
this.registryId = registryId;
|
|
6631
6749
|
// The SDK uses `name` for cache keys, traces, and lifecycle diagnostics.
|
|
@@ -6802,10 +6920,23 @@ export class PrefixedMcpServer implements MCPServer {
|
|
|
6802
6920
|
const tools = assertMcpToolListWithinBounds(await this.inner.listTools()) as RuntimeMcpTool[];
|
|
6803
6921
|
const exposed = tools
|
|
6804
6922
|
.filter((tool) => this.isAllowed(tool.name))
|
|
6805
|
-
.map((tool) =>
|
|
6806
|
-
|
|
6807
|
-
|
|
6808
|
-
|
|
6923
|
+
.map((tool) => {
|
|
6924
|
+
const name = prefixedMcpToolName(this.registryId, tool.name);
|
|
6925
|
+
const prior = this.originalToolNames.get(name);
|
|
6926
|
+
if (prior !== undefined && prior !== tool.name)
|
|
6927
|
+
throw new Error("MCP model tool identity collision");
|
|
6928
|
+
this.originalToolNames.set(name, tool.name);
|
|
6929
|
+
return {
|
|
6930
|
+
...tool,
|
|
6931
|
+
name,
|
|
6932
|
+
...(this.accountLabel
|
|
6933
|
+
? {
|
|
6934
|
+
description:
|
|
6935
|
+
`Account: ${this.accountLabel}. Use only this account; if the intended account for a write is unclear, ask before calling.\n${tool.description ?? ""}`.trimEnd(),
|
|
6936
|
+
}
|
|
6937
|
+
: {}),
|
|
6938
|
+
};
|
|
6939
|
+
});
|
|
6809
6940
|
const bounded = (this.aggregateToolBudget?.replace(this.aggregateSourceId, exposed) ??
|
|
6810
6941
|
assertMcpToolListWithinBounds(exposed)) as RuntimeMcpTool[];
|
|
6811
6942
|
this.listedToolSchemaTokens = estimateSerializedValueTokens(bounded);
|
|
@@ -7115,7 +7246,21 @@ export class PrefixedMcpServer implements MCPServer {
|
|
|
7115
7246
|
}
|
|
7116
7247
|
|
|
7117
7248
|
private unprefixToolName(toolName: string): string {
|
|
7118
|
-
|
|
7249
|
+
const original = this.originalToolNames.get(toolName);
|
|
7250
|
+
if (original !== undefined) return original;
|
|
7251
|
+
// Existing in-process callers may still use the raw qualified name. This
|
|
7252
|
+
// is not a model identifier: accept it only for an exact frozen catalog
|
|
7253
|
+
// entry on this server, never by normalizing or guessing a different route.
|
|
7254
|
+
const rawPrefix = sharedPrefixedMcpToolName(this.registryId, "");
|
|
7255
|
+
if (toolName.startsWith(rawPrefix)) {
|
|
7256
|
+
const candidate = toolName.slice(rawPrefix.length);
|
|
7257
|
+
if (
|
|
7258
|
+
this.originalToolNames.get(prefixedMcpToolName(this.registryId, candidate)) === candidate
|
|
7259
|
+
) {
|
|
7260
|
+
return candidate;
|
|
7261
|
+
}
|
|
7262
|
+
}
|
|
7263
|
+
if (!this.prefix.endsWith("__") || !toolName.startsWith(this.prefix)) {
|
|
7119
7264
|
throw new Error(`MCP tool ${toolName} is missing expected ${this.registryId} prefix`);
|
|
7120
7265
|
}
|
|
7121
7266
|
return toolName.slice(this.prefix.length);
|
|
@@ -7155,6 +7300,8 @@ export async function restoreInterruptedRunState(
|
|
|
7155
7300
|
});
|
|
7156
7301
|
}
|
|
7157
7302
|
|
|
7303
|
+
export const TURN_OPERATIONAL_NOTICE_PREFIX = "[OpenGeni turn-scoped operational notice]\n";
|
|
7304
|
+
|
|
7158
7305
|
export async function prepareRunInput(
|
|
7159
7306
|
agent: Agent<any, any>,
|
|
7160
7307
|
input: AgentSegmentInput,
|
|
@@ -7166,7 +7313,12 @@ export async function prepareRunInput(
|
|
|
7166
7313
|
trailingMessages.push({
|
|
7167
7314
|
type: "message",
|
|
7168
7315
|
role: "system",
|
|
7169
|
-
content:
|
|
7316
|
+
content:
|
|
7317
|
+
TURN_OPERATIONAL_NOTICE_PREFIX +
|
|
7318
|
+
"This status applies to the execution turn where this notice first appears. " +
|
|
7319
|
+
"On later turns it is historical context, not current availability or authorization. " +
|
|
7320
|
+
"Use the latest operational status and tool results; this notice grants no permissions.\n\n" +
|
|
7321
|
+
input.internalContext,
|
|
7170
7322
|
} as AgentInputItem);
|
|
7171
7323
|
}
|
|
7172
7324
|
if (input.text?.trim()) {
|
|
@@ -7385,6 +7537,7 @@ function takeGenesisTitleInputFilter(agent: Agent<any, any>): CallModelInputFilt
|
|
|
7385
7537
|
// environments can use the exact pinned package hint.
|
|
7386
7538
|
export const CODEMODE_PROGRAMMATIC_DIRECTIVE =
|
|
7387
7539
|
"Default `ogtool list` enumerates every authorized tool with a compact summary, without schemas or an output-size cutoff. " +
|
|
7540
|
+
"Managed sandboxes select the worker-release client on PATH for every command, including warm boxes. When OPENGENI_CODEMODE_CLIENT_MODULE is set, persistent Bun programs must use `const { tools, openGeni } = await import(process.env.OPENGENI_CODEMODE_CLIENT_MODULE!)`; do not import the older image-baked package or invoke /usr/local/bin/ogtool directly. The stock-package import below is only for environments without that deployment-selected module. " +
|
|
7388
7541
|
'Every tool available to you is also callable programmatically from the sandbox through the same frozen catalog, authority, credentials, policy, and execution path. In stock sandboxes, write persistent Bun code with `import { tools, openGeni } from "@opengeni/codemode"`; run `ogtool declarations <file.d.ts>` when project-local catalog types are useful. For shell calls, discover tools with `ogtool list`, inspect an unfamiliar tool with `ogtool show <tool-path>`, then use `ogtool call <tool-path> \'<json-args>\'`. Listing is compact by default; `list --json` gives compact structured discovery and `list --full` explicitly includes all schemas. When $OPENGENI_CODEMODE_NATIVE_CLIENT is available, prefer the connection-bound native client even if an older `ogtool` is installed: use `"$OPENGENI_CODEMODE_NATIVE_CLIENT" codemode` with the same list, show, and call commands; this uses the same public Codemode operation journal, not another tool path. If no compatible installed client is available and Bun plus $OPENGENI_OGTOOL_PACKAGE_SPEC are available, run the exact deployment-pinned package with `bun x -p "$OPENGENI_OGTOOL_PACKAGE_SPEC" ogtool ...`; never guess a version or install `latest`. Prefer Codemode for loops, polling, bulk filtering, and intermediate data that should remain in the sandbox instead of consuming your context window. Tools requiring human approval return a typed error in Codemode and must be invoked normally.';
|
|
7389
7542
|
|
|
7390
7543
|
function modelModalityProjectionFilterForAgent(
|
|
@@ -7426,11 +7579,16 @@ function measuredModelInputFilter(
|
|
|
7426
7579
|
};
|
|
7427
7580
|
}
|
|
7428
7581
|
|
|
7582
|
+
const agentModelContextCaptures = new WeakMap<object, ModelRequestCapture>();
|
|
7583
|
+
|
|
7429
7584
|
function bindModelVisibleContextCapture(
|
|
7430
7585
|
agent: Agent<any, any>,
|
|
7431
7586
|
onCapture: RunAgentStreamOptions["onModelVisibleContext"],
|
|
7432
7587
|
): ModelRequestCapture | undefined {
|
|
7433
|
-
if (!onCapture)
|
|
7588
|
+
if (!onCapture) {
|
|
7589
|
+
agentModelContextCaptures.delete(agent);
|
|
7590
|
+
return undefined;
|
|
7591
|
+
}
|
|
7434
7592
|
const capture: ModelRequestCapture = async (request) => {
|
|
7435
7593
|
const requestIndex = nextModelContextCaptureIndex(agent);
|
|
7436
7594
|
await onCapture(
|
|
@@ -7455,6 +7613,7 @@ function bindModelVisibleContextCapture(
|
|
|
7455
7613
|
}),
|
|
7456
7614
|
);
|
|
7457
7615
|
};
|
|
7616
|
+
agentModelContextCaptures.set(agent, capture);
|
|
7458
7617
|
return capture;
|
|
7459
7618
|
}
|
|
7460
7619
|
|
|
@@ -7483,7 +7642,9 @@ export async function runAgentStream(
|
|
|
7483
7642
|
const scope = gate?.beginStream(overrides.signal);
|
|
7484
7643
|
try {
|
|
7485
7644
|
if (scope) agent.toolUseBehavior = scope.toolUseBehavior;
|
|
7486
|
-
const stream = await
|
|
7645
|
+
const stream = await withPreparedCompactionRequest(agent, () =>
|
|
7646
|
+
runAgentStreamInternal(agent, input, settings, overrides, scope),
|
|
7647
|
+
);
|
|
7487
7648
|
// Observe the SDK's own settlement promise before exposing the stream. Do
|
|
7488
7649
|
// not wrap/replace SDK history, errors, cancellation, or stream iteration.
|
|
7489
7650
|
// In particular a fatal sibling tool error must not leave admission open
|
|
@@ -7515,12 +7676,16 @@ async function runAgentStreamInternal(
|
|
|
7515
7676
|
const environment = overrides.sandboxEnvironment ?? collectSandboxEnvironment(settings);
|
|
7516
7677
|
const codemodeTokenFile = codemodeTokenFileForAgent(agent, environment);
|
|
7517
7678
|
const codemodeUrl = environment.OPENGENI_CODEMODE_URL;
|
|
7679
|
+
const codemodeClientDirectory =
|
|
7680
|
+
codemodeTokenFile && agentActiveSandboxBackend.get(agent) !== "selfhosted"
|
|
7681
|
+
? managedCodemodeClientDirectory(await loadManagedCodemodeClient())
|
|
7682
|
+
: undefined;
|
|
7518
7683
|
const genesisTitleInputFilter = takeGenesisTitleInputFilter(agent);
|
|
7519
7684
|
const modelRequestCapture = bindModelVisibleContextCapture(
|
|
7520
7685
|
agent,
|
|
7521
7686
|
overrides.onModelVisibleContext,
|
|
7522
7687
|
);
|
|
7523
|
-
|
|
7688
|
+
installNonLazyModelRequestCapture(agent);
|
|
7524
7689
|
if (overrides.onRunCredentialSessionReady && !overrides.runCredentialSessionId) {
|
|
7525
7690
|
throw new Error("runCredentialSessionId is required when run credential setup is enabled");
|
|
7526
7691
|
}
|
|
@@ -7546,13 +7711,23 @@ async function runAgentStreamInternal(
|
|
|
7546
7711
|
? withRunCredentialsSession(session as SandboxSessionLike, overrides.runCredentialSessionId)
|
|
7547
7712
|
: (session as SandboxSessionLike);
|
|
7548
7713
|
const agentSession = codemodeTokenFile
|
|
7549
|
-
? withCodemodeTokenSession(
|
|
7714
|
+
? withCodemodeTokenSession(
|
|
7715
|
+
credentialAgentSession,
|
|
7716
|
+
codemodeTokenFile,
|
|
7717
|
+
codemodeUrl,
|
|
7718
|
+
codemodeClientDirectory,
|
|
7719
|
+
)
|
|
7550
7720
|
: credentialAgentSession;
|
|
7551
7721
|
const credentialSetupSession = overrides.runCredentialSessionId
|
|
7552
7722
|
? withRunCredentialsSession(setupSession, overrides.runCredentialSessionId)
|
|
7553
7723
|
: setupSession;
|
|
7554
7724
|
const decoratedSetupSession = codemodeTokenFile
|
|
7555
|
-
? withCodemodeTokenSession(
|
|
7725
|
+
? withCodemodeTokenSession(
|
|
7726
|
+
credentialSetupSession,
|
|
7727
|
+
codemodeTokenFile,
|
|
7728
|
+
codemodeUrl,
|
|
7729
|
+
codemodeClientDirectory,
|
|
7730
|
+
)
|
|
7556
7731
|
: credentialSetupSession;
|
|
7557
7732
|
// Platform setup (manifest-env pin + beforeAgentStart hooks + file downloads)
|
|
7558
7733
|
// against the UN-proxied established box — the ONE-TRUTH helper shared with the
|
|
@@ -7640,7 +7815,12 @@ async function runAgentStreamInternal(
|
|
|
7640
7815
|
)
|
|
7641
7816
|
: resourceClient;
|
|
7642
7817
|
const codemodeResourceClient = codemodeTokenFile
|
|
7643
|
-
? withCodemodeTokenClient(
|
|
7818
|
+
? withCodemodeTokenClient(
|
|
7819
|
+
credentialResourceClient,
|
|
7820
|
+
codemodeTokenFile,
|
|
7821
|
+
codemodeUrl,
|
|
7822
|
+
codemodeClientDirectory,
|
|
7823
|
+
)
|
|
7644
7824
|
: credentialResourceClient;
|
|
7645
7825
|
const decoratedClient = withSandboxLifecycleHooks(
|
|
7646
7826
|
codemodeResourceClient,
|
|
@@ -7669,19 +7849,21 @@ async function runAgentStreamInternal(
|
|
|
7669
7849
|
),
|
|
7670
7850
|
measuredModelInputFilter(
|
|
7671
7851
|
"input_filter_context",
|
|
7672
|
-
|
|
7673
|
-
|
|
7674
|
-
|
|
7675
|
-
|
|
7676
|
-
|
|
7677
|
-
|
|
7678
|
-
|
|
7679
|
-
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
|
|
7852
|
+
deferCompactionToModelBoundary(
|
|
7853
|
+
contextRobustnessFilterForSettings(settings, {
|
|
7854
|
+
throwOnCompactionNeeded: Boolean(
|
|
7855
|
+
overrides.contextCompactionSignal || overrides.contextCompactionRequested,
|
|
7856
|
+
),
|
|
7857
|
+
...(overrides.contextCompactionSignal
|
|
7858
|
+
? { contextCompactionSignal: overrides.contextCompactionSignal }
|
|
7859
|
+
: {}),
|
|
7860
|
+
...(overrides.contextCompactionRequested
|
|
7861
|
+
? {
|
|
7862
|
+
contextCompactionRequested: overrides.contextCompactionRequested,
|
|
7863
|
+
}
|
|
7864
|
+
: {}),
|
|
7865
|
+
}),
|
|
7866
|
+
),
|
|
7685
7867
|
),
|
|
7686
7868
|
// Seal admission before any provider preparation/transport awaits.
|
|
7687
7869
|
inputWaitYield?.modelDispatchFilter,
|
|
@@ -7753,7 +7935,12 @@ async function runAgentStreamInternal(
|
|
|
7753
7935
|
: resourceClient;
|
|
7754
7936
|
const codemodeClient =
|
|
7755
7937
|
credentialClient && codemodeTokenFile
|
|
7756
|
-
? withCodemodeTokenClient(
|
|
7938
|
+
? withCodemodeTokenClient(
|
|
7939
|
+
credentialClient,
|
|
7940
|
+
codemodeTokenFile,
|
|
7941
|
+
codemodeUrl,
|
|
7942
|
+
codemodeClientDirectory,
|
|
7943
|
+
)
|
|
7757
7944
|
: credentialClient;
|
|
7758
7945
|
// TOKEN-BROKER (B1): the per-turn git token seed, forwarded OFF-MANIFEST so the
|
|
7759
7946
|
// repository-clone hook seeds it to the box's token file before the clone.
|
|
@@ -7820,19 +8007,21 @@ async function runAgentStreamInternal(
|
|
|
7820
8007
|
),
|
|
7821
8008
|
measuredModelInputFilter(
|
|
7822
8009
|
"input_filter_context",
|
|
7823
|
-
|
|
7824
|
-
|
|
7825
|
-
|
|
7826
|
-
|
|
7827
|
-
|
|
7828
|
-
|
|
7829
|
-
|
|
7830
|
-
|
|
7831
|
-
|
|
7832
|
-
|
|
7833
|
-
|
|
7834
|
-
|
|
7835
|
-
|
|
8010
|
+
deferCompactionToModelBoundary(
|
|
8011
|
+
contextRobustnessFilterForSettings(settings, {
|
|
8012
|
+
throwOnCompactionNeeded: Boolean(
|
|
8013
|
+
overrides.contextCompactionSignal || overrides.contextCompactionRequested,
|
|
8014
|
+
),
|
|
8015
|
+
...(overrides.contextCompactionSignal
|
|
8016
|
+
? { contextCompactionSignal: overrides.contextCompactionSignal }
|
|
8017
|
+
: {}),
|
|
8018
|
+
...(overrides.contextCompactionRequested
|
|
8019
|
+
? {
|
|
8020
|
+
contextCompactionRequested: overrides.contextCompactionRequested,
|
|
8021
|
+
}
|
|
8022
|
+
: {}),
|
|
8023
|
+
}),
|
|
8024
|
+
),
|
|
7836
8025
|
),
|
|
7837
8026
|
inputWaitYield?.modelDispatchFilter,
|
|
7838
8027
|
].filter((f): f is CallModelInputFilter => Boolean(f)),
|
|
@@ -8283,6 +8472,8 @@ export async function runOwnedSandboxSetup(
|
|
|
8283
8472
|
environment: Record<string, string>;
|
|
8284
8473
|
preparedInput?: PreparedAgentInput;
|
|
8285
8474
|
fileDownloadsMaterialized?: boolean;
|
|
8475
|
+
/** Lazy setup runs after SDK preparation has already observed the logical manifest. */
|
|
8476
|
+
recordLazyManifest?: boolean;
|
|
8286
8477
|
onRuntimeEvent?: SandboxLifecycleHookContext["onRuntimeEvent"];
|
|
8287
8478
|
gitTokenSeedsOverride?: GitTokenSeeds;
|
|
8288
8479
|
gitTokenSeedOverride?: string;
|
|
@@ -8397,6 +8588,13 @@ export async function runOwnedSandboxSetup(
|
|
|
8397
8588
|
appendSandboxFileDownloadFailureNote(opts.preparedInput, materialized.failures);
|
|
8398
8589
|
}
|
|
8399
8590
|
}
|
|
8591
|
+
if (opts.recordLazyManifest) {
|
|
8592
|
+
const manifest = (agent as { defaultManifest?: Manifest }).defaultManifest;
|
|
8593
|
+
if (manifest) {
|
|
8594
|
+
const { recordLazyMaterializedDirectories } = await import("./lazy-manifest");
|
|
8595
|
+
await recordLazyMaterializedDirectories(session, manifest);
|
|
8596
|
+
}
|
|
8597
|
+
}
|
|
8400
8598
|
}
|
|
8401
8599
|
|
|
8402
8600
|
function mergePathGrants(
|
|
@@ -8715,7 +8913,7 @@ export function buildManifest(
|
|
|
8715
8913
|
}
|
|
8716
8914
|
// No extraPathGrants here: remote sandbox clients (Modal) reject manifests
|
|
8717
8915
|
// that carry them at create/apply time, which broke every Modal session.
|
|
8718
|
-
|
|
8916
|
+
|
|
8719
8917
|
// sandbox-safe in-memory or staged local-dir sources, so no host path grant
|
|
8720
8918
|
// is required here.
|
|
8721
8919
|
return new Manifest({
|
|
@@ -9277,6 +9475,15 @@ function codemodeTokenFileForAgent(
|
|
|
9277
9475
|
function sandboxCodemodeTokenHooksForAgent(agent: Agent<any, any>): SandboxLifecycleHook[] {
|
|
9278
9476
|
return codemodeTokenSeedForAgent(agent)
|
|
9279
9477
|
? [
|
|
9478
|
+
...(agentActiveSandboxBackend.get(agent) !== "selfhosted"
|
|
9479
|
+
? [
|
|
9480
|
+
{
|
|
9481
|
+
id: "codemode-client",
|
|
9482
|
+
phase: "beforeAgentStart" as const,
|
|
9483
|
+
run: runManagedCodemodeClientHook,
|
|
9484
|
+
},
|
|
9485
|
+
]
|
|
9486
|
+
: []),
|
|
9280
9487
|
{
|
|
9281
9488
|
id: "codemode-token",
|
|
9282
9489
|
phase: "beforeAgentStart",
|
|
@@ -10518,6 +10725,29 @@ export async function runCodemodeTokenSeedHook(
|
|
|
10518
10725
|
assertSandboxCommandSucceeded(result, "Codemode token seed hook");
|
|
10519
10726
|
}
|
|
10520
10727
|
|
|
10728
|
+
export async function runManagedCodemodeClientHook(
|
|
10729
|
+
session: SandboxSessionLike,
|
|
10730
|
+
context: SandboxLifecycleHookContext,
|
|
10731
|
+
): Promise<void> {
|
|
10732
|
+
await installManagedCodemodeClient(
|
|
10733
|
+
session,
|
|
10734
|
+
await loadManagedCodemodeClient(),
|
|
10735
|
+
async (cmd) =>
|
|
10736
|
+
await runSandboxLifecycleCommand(
|
|
10737
|
+
session,
|
|
10738
|
+
{
|
|
10739
|
+
cmd,
|
|
10740
|
+
workdir: "/workspace",
|
|
10741
|
+
...(context.runAs ? { runAs: context.runAs } : {}),
|
|
10742
|
+
yieldTimeMs: SANDBOX_LIFECYCLE_COMMAND_TIMEOUT_MS,
|
|
10743
|
+
maxOutputTokens: 1_000,
|
|
10744
|
+
},
|
|
10745
|
+
context.commandRunner,
|
|
10746
|
+
),
|
|
10747
|
+
context.runAs,
|
|
10748
|
+
);
|
|
10749
|
+
}
|
|
10750
|
+
|
|
10521
10751
|
export async function refreshCodemodeTokenFile(
|
|
10522
10752
|
session: CodemodeTokenWriterSession,
|
|
10523
10753
|
token: string,
|
|
@@ -10589,7 +10819,7 @@ function rigSetupHeredocDelimiter(script: string): string {
|
|
|
10589
10819
|
|
|
10590
10820
|
function rigSetupExistingMarkerProbe(versionId: string, contentHash?: string): string {
|
|
10591
10821
|
if (contentHash !== undefined && !/^sha256:[0-9a-f]{64}$/u.test(contentHash)) {
|
|
10592
|
-
throw new Error("
|
|
10822
|
+
throw new Error("Sandbox Environment setup content hash must be a canonical SHA-256 value");
|
|
10593
10823
|
}
|
|
10594
10824
|
const markers = [`${RIG_SETUP_RUNTIME_MARKER_ROOT}/rig-setup-${versionId}.done`];
|
|
10595
10825
|
if (contentHash) {
|
|
@@ -10632,14 +10862,14 @@ export function rigSetupScriptCommand(
|
|
|
10632
10862
|
["trusted content marker", trustedContentMarkerRoot],
|
|
10633
10863
|
] as const) {
|
|
10634
10864
|
if (!isAbsolute(root) || root === "/") {
|
|
10635
|
-
throw new Error(`
|
|
10865
|
+
throw new Error(`Sandbox Environment setup ${label} root must be a non-root absolute path`);
|
|
10636
10866
|
}
|
|
10637
10867
|
}
|
|
10638
10868
|
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
10639
|
-
throw new Error("
|
|
10869
|
+
throw new Error("Sandbox Environment setup timeout must be a positive finite duration");
|
|
10640
10870
|
}
|
|
10641
10871
|
if (contentHash !== undefined && !/^sha256:[0-9a-f]{64}$/u.test(contentHash)) {
|
|
10642
|
-
throw new Error("
|
|
10872
|
+
throw new Error("Sandbox Environment setup content hash must be a canonical SHA-256 value");
|
|
10643
10873
|
}
|
|
10644
10874
|
const timeoutSecs = Math.max(1, Math.ceil(timeoutMs / 1000));
|
|
10645
10875
|
const lockWaitSecs = timeoutSecs + 6;
|
|
@@ -10658,7 +10888,7 @@ export function rigSetupScriptCommand(
|
|
|
10658
10888
|
const heredocDelimiter = rigSetupHeredocDelimiter(script);
|
|
10659
10889
|
return [
|
|
10660
10890
|
"set -u",
|
|
10661
|
-
`if ! mkdir -p ${shellQuote(markerRoot)}; then printf '%s\\n' 'unable to create
|
|
10891
|
+
`if ! mkdir -p ${shellQuote(markerRoot)}; then printf '%s\\n' 'unable to create sandbox environment setup marker root' >&2; exit 73; fi`,
|
|
10662
10892
|
`__OG_RIG_VERSION_MARKER=${shellQuote(versionMarker)}`,
|
|
10663
10893
|
`__OG_RIG_CONTENT_MARKER=${shellQuote(contentMarker ?? "")}`,
|
|
10664
10894
|
`__OG_RIG_TRUSTED_CONTENT_MARKER=${shellQuote(trustedContentMarker ?? "")}`,
|
|
@@ -10671,7 +10901,7 @@ export function rigSetupScriptCommand(
|
|
|
10671
10901
|
' if mkdir "$__OG_RIG_LOCK" 2>/dev/null; then',
|
|
10672
10902
|
" trap 'rm -rf \"$__OG_RIG_LOCK\"' EXIT",
|
|
10673
10903
|
` if ${markerReady}; then printf '%s\\n' ${shellQuote(RIG_SETUP_SKIPPED_SENTINEL)}; exit 0; fi`,
|
|
10674
|
-
" if ! __OG_RIG_SCRIPT=\"$(mktemp)\"; then printf '%s\\n' 'unable to create
|
|
10904
|
+
" if ! __OG_RIG_SCRIPT=\"$(mktemp)\"; then printf '%s\\n' 'unable to create sandbox environment setup script file' >&2; exit 73; fi",
|
|
10675
10905
|
`cat > "$__OG_RIG_SCRIPT" <<'${heredocDelimiter}'`,
|
|
10676
10906
|
script,
|
|
10677
10907
|
heredocDelimiter,
|
|
@@ -10679,12 +10909,12 @@ export function rigSetupScriptCommand(
|
|
|
10679
10909
|
"__OG_RIG_RC=$?",
|
|
10680
10910
|
' rm -f "$__OG_RIG_SCRIPT"',
|
|
10681
10911
|
' if [ "$__OG_RIG_RC" -eq 0 ]; then',
|
|
10682
|
-
" if ! touch \"$__OG_RIG_VERSION_MARKER\"; then printf '%s\\n' 'unable to write
|
|
10683
|
-
" if [ -n \"$__OG_RIG_CONTENT_MARKER\" ] && ! touch \"$__OG_RIG_CONTENT_MARKER\"; then printf '%s\\n' 'unable to write
|
|
10912
|
+
" if ! touch \"$__OG_RIG_VERSION_MARKER\"; then printf '%s\\n' 'unable to write sandbox environment setup version marker' >&2; exit 73; fi",
|
|
10913
|
+
" if [ -n \"$__OG_RIG_CONTENT_MARKER\" ] && ! touch \"$__OG_RIG_CONTENT_MARKER\"; then printf '%s\\n' 'unable to write sandbox environment setup content marker' >&2; exit 73; fi",
|
|
10684
10914
|
" fi",
|
|
10685
10915
|
' exit "$__OG_RIG_RC"',
|
|
10686
10916
|
" fi",
|
|
10687
|
-
" if [ ! -d \"$__OG_RIG_LOCK\" ]; then printf '%s\\n' 'unable to create
|
|
10917
|
+
" if [ ! -d \"$__OG_RIG_LOCK\" ]; then printf '%s\\n' 'unable to create sandbox environment setup lock' >&2; exit 73; fi",
|
|
10688
10918
|
" __OG_RIG_WAITED=0",
|
|
10689
10919
|
' while [ "$__OG_RIG_WAITED" -lt "$__OG_RIG_LOCK_WAIT_SECS" ]; do',
|
|
10690
10920
|
` if ${markerReady}; then printf '%s\\n' ${shellQuote(RIG_SETUP_SKIPPED_SENTINEL)}; exit 0; fi`,
|
|
@@ -10694,7 +10924,7 @@ export function rigSetupScriptCommand(
|
|
|
10694
10924
|
" done",
|
|
10695
10925
|
` if ${markerReady}; then printf '%s\\n' ${shellQuote(RIG_SETUP_SKIPPED_SENTINEL)}; exit 0; fi`,
|
|
10696
10926
|
' if [ ! -d "$__OG_RIG_LOCK" ]; then continue; fi',
|
|
10697
|
-
" if ! rmdir \"$__OG_RIG_LOCK\" 2>/dev/null && [ -d \"$__OG_RIG_LOCK\" ]; then printf '%s\\n' 'unable to reclaim stale
|
|
10927
|
+
" if ! rmdir \"$__OG_RIG_LOCK\" 2>/dev/null && [ -d \"$__OG_RIG_LOCK\" ]; then printf '%s\\n' 'unable to reclaim stale sandbox environment setup lock' >&2; exit 73; fi",
|
|
10698
10928
|
"done",
|
|
10699
10929
|
].join("\n");
|
|
10700
10930
|
}
|
|
@@ -10731,7 +10961,7 @@ async function stageRigSetupScript(
|
|
|
10731
10961
|
},
|
|
10732
10962
|
context.commandRunner,
|
|
10733
10963
|
);
|
|
10734
|
-
assertSandboxCommandSucceeded(result, "
|
|
10964
|
+
assertSandboxCommandSucceeded(result, "Sandbox Environment setup payload staging");
|
|
10735
10965
|
}
|
|
10736
10966
|
return payloadPath;
|
|
10737
10967
|
} catch (error) {
|
|
@@ -10833,8 +11063,10 @@ export async function runRigSetupHook(
|
|
|
10833
11063
|
) {
|
|
10834
11064
|
result = markerProbe;
|
|
10835
11065
|
} else if (markerProbeExitCode !== 42) {
|
|
10836
|
-
assertSandboxCommandSucceeded(markerProbe, "
|
|
10837
|
-
throw new Error(
|
|
11066
|
+
assertSandboxCommandSucceeded(markerProbe, "Sandbox Environment setup marker probe");
|
|
11067
|
+
throw new Error(
|
|
11068
|
+
"Sandbox Environment setup marker probe returned success without its sentinel",
|
|
11069
|
+
);
|
|
10838
11070
|
}
|
|
10839
11071
|
if (result === undefined) {
|
|
10840
11072
|
stagedScriptPath = await stageRigSetupScript(session, rigSetup.script, context);
|
|
@@ -10859,7 +11091,7 @@ export async function runRigSetupHook(
|
|
|
10859
11091
|
},
|
|
10860
11092
|
});
|
|
10861
11093
|
throw new Error(
|
|
10862
|
-
`
|
|
11094
|
+
`Sandbox Environment setup failed for sandbox environment "${rigSetup.rigName}" (version ${rigSetup.versionId}): ${message}`,
|
|
10863
11095
|
{ cause: error },
|
|
10864
11096
|
);
|
|
10865
11097
|
} finally {
|
|
@@ -10894,12 +11126,12 @@ export async function runRigSetupHook(
|
|
|
10894
11126
|
: output;
|
|
10895
11127
|
const timedOut = stillRunning || exitCode === 124 || exitCode === 137;
|
|
10896
11128
|
const reason = timedOut
|
|
10897
|
-
? `did not finish within the
|
|
11129
|
+
? `did not finish within the sandbox environment setup timeout (${rigSetup.timeoutMs}ms)`
|
|
10898
11130
|
: exitCode === null
|
|
10899
11131
|
? "did not report an exit code"
|
|
10900
11132
|
: `exited with code ${exitCode}`;
|
|
10901
11133
|
const failure = new Error(
|
|
10902
|
-
`
|
|
11134
|
+
`Sandbox Environment setup failed for sandbox environment "${rigSetup.rigName}" (version ${rigSetup.versionId}): the setup script ${reason}${tail ? `:\n${tail}` : ""}`,
|
|
10903
11135
|
);
|
|
10904
11136
|
await context.onRuntimeEvent?.({
|
|
10905
11137
|
type: "rig.setup.failed",
|