@opengeni/runtime 4.6.0 → 4.7.0-canary.37090756480001
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/agent-instructions/modules/media.d.ts +3 -0
- package/dist/anthropic-messages.d.ts +13 -3
- package/dist/anthropic-request-error.d.ts +10 -0
- package/dist/assets/codemode-client.json +1 -1
- package/dist/{chunk-A22BKN4P.js → chunk-LJNCGUMB.js} +3 -3
- package/dist/chunk-LJNCGUMB.js.map +1 -0
- package/dist/{chunk-GTWKWJ67.js → chunk-P2OVGKDG.js} +505 -100
- package/dist/chunk-P2OVGKDG.js.map +1 -0
- package/dist/{chunk-D5DPVA2C.js → chunk-R25L27T3.js} +2047 -1275
- package/dist/chunk-R25L27T3.js.map +1 -0
- package/dist/claude-subscription-usage.d.ts +14 -4
- package/dist/context-compaction.d.ts +7 -7
- package/dist/index.d.ts +2 -0
- package/dist/index.js +13 -3
- package/dist/interaction-tools.d.ts +1 -1
- package/dist/mcp-network.js +1 -1
- package/dist/runtime-skills.d.ts +1 -0
- package/dist/sandbox/channel-a.d.ts +3 -0
- package/dist/sandbox/index.d.ts +3 -1
- package/dist/sandbox/index.js +7 -1
- package/dist/sandbox/provider-command-session.d.ts +21 -0
- package/dist/sandbox/providers/modal-command-argv.d.ts +4 -0
- package/dist/sandbox/providers/modal-command-control.d.ts +17 -5
- package/dist/sandbox/providers/modal-command-observation-errors.d.ts +3 -0
- package/dist/sandbox/providers/modal-command-raw-page.d.ts +100 -0
- package/dist/sandbox/providers/modal-command-router-wire.d.ts +17 -0
- package/dist/sandbox/routing/routing-session.d.ts +10 -0
- package/dist/sandbox/turn-tool-cancellation.d.ts +4 -0
- package/dist/workspace-tool-gateway.js +3 -3
- package/package.json +12 -12
- package/src/agent-instructions/PROMPT_CHANGELOG.md +23 -1
- package/src/agent-instructions/compose.ts +2 -0
- package/src/agent-instructions/modules/media.ts +20 -0
- package/src/agent-instructions/runtime-mechanics.ts +8 -0
- package/src/anthropic-messages.ts +223 -31
- package/src/anthropic-request-error.ts +45 -0
- package/src/bundled_default_skills/opengeni-client/SKILL.md +9 -7
- package/src/bundled_default_skills/opengeni-client/references/agent-recipes.md +1 -1
- package/src/bundled_default_skills/opengeni-client/references/api-workflows.md +61 -0
- package/src/bundled_default_skills/opengeni-client/references/compatibility-and-troubleshooting.md +86 -0
- package/src/bundled_default_skills/opengeni-client/references/data-tools-and-credentials.md +82 -0
- package/src/bundled_default_skills/opengeni-client/references/discovery-and-autonomy.md +32 -25
- package/src/bundled_default_skills/opengeni-client/references/external-users-and-connect.md +17 -0
- package/src/bundled_default_skills/opengeni-client/references/product-shapes-and-ui.md +156 -0
- package/src/bundled_schedule_skills/opengeni-schedules/SKILL.md +56 -0
- package/src/claude-subscription-usage.ts +55 -9
- package/src/context-compaction.ts +16 -9
- package/src/index.ts +85 -18
- package/src/interaction-tools.ts +113 -3
- package/src/lazy-tool-transport.ts +26 -10
- package/src/mcp-network.ts +4 -2
- package/src/model-provider-client.ts +11 -3
- package/src/model-provider-routing.ts +4 -0
- package/src/runtime-skills.ts +8 -0
- package/src/sandbox/channel-a.ts +3 -0
- package/src/sandbox/index.ts +11 -1
- package/src/sandbox/provider-command-session.ts +58 -0
- package/src/sandbox/provider-errors.ts +43 -9
- package/src/sandbox/providers/modal-command-argv.ts +20 -0
- package/src/sandbox/providers/modal-command-control.ts +422 -116
- package/src/sandbox/providers/modal-command-observation-errors.ts +65 -0
- package/src/sandbox/providers/modal-command-raw-page.ts +147 -0
- package/src/sandbox/providers/modal-command-router-wire.ts +104 -12
- package/src/sandbox/providers/modal-command-session.ts +26 -4
- package/src/sandbox/providers/modal-legacy-command-control.ts +2 -9
- package/src/sandbox/providers/modal-materialization-verification.ts +70 -6
- package/src/sandbox/routing/routing-session.ts +140 -41
- package/src/sandbox/run-credentials.ts +62 -19
- package/src/sandbox/turn-tool-cancellation.ts +159 -42
- package/dist/chunk-A22BKN4P.js.map +0 -1
- package/dist/chunk-D5DPVA2C.js.map +0 -1
- package/dist/chunk-GTWKWJ67.js.map +0 -1
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
withSupervisedLaunchReservation,
|
|
42
42
|
ProviderCommandStartRejectedError,
|
|
43
43
|
ProviderCommandStartOutcomeUnknownError,
|
|
44
|
+
ProviderCommandInputOutcomeUnknownError,
|
|
44
45
|
type ProviderCommandPersistence,
|
|
45
46
|
type ProviderCommandSession,
|
|
46
47
|
} from "../provider-command-session";
|
|
@@ -281,6 +282,12 @@ export interface RoutingSandboxSessionDeps {
|
|
|
281
282
|
process: RoutingRetainedProcess;
|
|
282
283
|
proof: RoutingRetainedProcessTerminalProof;
|
|
283
284
|
}) => Promise<void>;
|
|
285
|
+
/** Read terminal truth for this exact copied process/backend. Missing rows,
|
|
286
|
+
* failed observations and active rows must never count as physical proof. */
|
|
287
|
+
isProcessSettled?: (input: {
|
|
288
|
+
backend: ResolvedActiveBackend;
|
|
289
|
+
process: RoutingRetainedProcess;
|
|
290
|
+
}) => Promise<boolean>;
|
|
284
291
|
/** A terminal result is being returned to the model, not merely drained by
|
|
285
292
|
* control/reaper work. Never invoke this for a running receipt. */
|
|
286
293
|
observeProcessTerminal?: (input: {
|
|
@@ -1236,6 +1243,12 @@ export class RoutingSandboxSession implements RoutableBackendSession {
|
|
|
1236
1243
|
write.call(record.backend.session, args),
|
|
1237
1244
|
);
|
|
1238
1245
|
} catch (error) {
|
|
1246
|
+
let inputUnknown = false;
|
|
1247
|
+
try {
|
|
1248
|
+
inputUnknown = error instanceof ProviderCommandInputOutcomeUnknownError;
|
|
1249
|
+
} catch {
|
|
1250
|
+
// An unreadable provider graph cannot manufacture typed input proof.
|
|
1251
|
+
}
|
|
1239
1252
|
if (this.deps.afterProcessMutation) {
|
|
1240
1253
|
const pending: PendingProcessMutationSettlement = {
|
|
1241
1254
|
op,
|
|
@@ -1250,11 +1263,28 @@ export class RoutingSandboxSession implements RoutableBackendSession {
|
|
|
1250
1263
|
record.pendingMutationSettlement = pending;
|
|
1251
1264
|
throw new RoutingMutationOutcomeUnknownError(
|
|
1252
1265
|
op,
|
|
1253
|
-
|
|
1254
|
-
|
|
1266
|
+
inputUnknown
|
|
1267
|
+
? "Retained-process stdin acknowledgement and durable settlement unavailable; input may have been accepted. Do not resend stdin."
|
|
1268
|
+
: "Retained-process stdin rejected at the provider but lost durable settlement; it was not replayed",
|
|
1269
|
+
inputUnknown
|
|
1270
|
+
? {
|
|
1271
|
+
cause: new AggregateError([error, settlementError]),
|
|
1272
|
+
retainedProcess: record.process,
|
|
1273
|
+
}
|
|
1274
|
+
: { cause: settlementError },
|
|
1255
1275
|
);
|
|
1256
1276
|
}
|
|
1257
1277
|
}
|
|
1278
|
+
if (inputUnknown) {
|
|
1279
|
+
// Preserve native input proof across the retained route, including its
|
|
1280
|
+
// genuine byte-range cause and exact locator. Both SDK-facing rendering
|
|
1281
|
+
// and direct tool faults must forbid input replay.
|
|
1282
|
+
throw new RoutingMutationOutcomeUnknownError(
|
|
1283
|
+
op,
|
|
1284
|
+
"Provider stdin acknowledgement unavailable; input may have been accepted and was not resent. Do not resend stdin.",
|
|
1285
|
+
{ cause: error, retainedProcess: record.process },
|
|
1286
|
+
);
|
|
1287
|
+
}
|
|
1258
1288
|
throw error;
|
|
1259
1289
|
}
|
|
1260
1290
|
if (this.deps.afterProcessMutation) {
|
|
@@ -2131,7 +2161,17 @@ export class RoutingSandboxSession implements RoutableBackendSession {
|
|
|
2131
2161
|
async writeStdin(args: unknown): Promise<string> {
|
|
2132
2162
|
const providerSessionId = providerSessionIdFromArgs(args);
|
|
2133
2163
|
if (providerSessionId !== null && this.retainedProcesses.has(providerSessionId)) {
|
|
2134
|
-
|
|
2164
|
+
try {
|
|
2165
|
+
return await this.dispatchProcessMutation(args);
|
|
2166
|
+
} catch (error) {
|
|
2167
|
+
// The SDK's write_stdin tool has no configurable errorFunction. Render
|
|
2168
|
+
// genuine routing uncertainty here, before its generic retry advice.
|
|
2169
|
+
// Direct/control methods still throw; this is not an output receipt or
|
|
2170
|
+
// terminal proof and cannot acknowledge bytes or release the writer.
|
|
2171
|
+
if (isRoutingMutationOutcomeUnknownError(error))
|
|
2172
|
+
return renderRoutingMutationOutcomeUnknownToolResult(error);
|
|
2173
|
+
throw error;
|
|
2174
|
+
}
|
|
2135
2175
|
}
|
|
2136
2176
|
return this.dispatch("writeStdin", true, async (s) => {
|
|
2137
2177
|
if (!s.writeStdin) {
|
|
@@ -2158,6 +2198,20 @@ export class RoutingSandboxSession implements RoutableBackendSession {
|
|
|
2158
2198
|
);
|
|
2159
2199
|
}
|
|
2160
2200
|
|
|
2201
|
+
/** A reaper may have settled the command while a local capture/control receipt
|
|
2202
|
+
* remained pending. Cleanup may consume that same durable proof without
|
|
2203
|
+
* replaying a provider operation or accepting pending output into the model. */
|
|
2204
|
+
async reconcileRetainedProcess(providerSessionId: number): Promise<boolean> {
|
|
2205
|
+
const record = this.retainedProcesses.get(providerSessionId);
|
|
2206
|
+
if (!record || !this.deps.isProcessSettled) return false;
|
|
2207
|
+
if (!(await this.deps.isProcessSettled({ backend: record.backend, process: record.process })))
|
|
2208
|
+
return false;
|
|
2209
|
+
// Do not erase a rival route installed while the durable read was pending.
|
|
2210
|
+
if (this.retainedProcesses.get(providerSessionId) !== record) return false;
|
|
2211
|
+
this.retainedProcesses.delete(providerSessionId);
|
|
2212
|
+
return true;
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2161
2215
|
/** Local, Docker, and OpenSandbox process ids address an in-memory table on one worker
|
|
2162
2216
|
* session object. They are not valid durable locators for the independently
|
|
2163
2217
|
* scheduled reaper, so their yielded handles stay turn-owned until terminal
|
|
@@ -2404,9 +2458,7 @@ export class RoutingSandboxSession implements RoutableBackendSession {
|
|
|
2404
2458
|
async writePlacementPrivate(args: unknown): Promise<unknown> {
|
|
2405
2459
|
const input = placementPrivateWrite(args);
|
|
2406
2460
|
return this.dispatch("writePlacementPrivate", false, async (session, backend) => {
|
|
2407
|
-
|
|
2408
|
-
if (session.writeFile) return await session.writeFile(input);
|
|
2409
|
-
return await streamPlacementPrivateFile(session, input, backend.kind);
|
|
2461
|
+
return await writePlacementPrivateOnBackend(session, input, backend.kind);
|
|
2410
2462
|
});
|
|
2411
2463
|
}
|
|
2412
2464
|
|
|
@@ -2414,36 +2466,8 @@ export class RoutingSandboxSession implements RoutableBackendSession {
|
|
|
2414
2466
|
* control operation cannot be repurposed into a generic mutation bypass. */
|
|
2415
2467
|
async deletePlacementPrivate(path: string, runAs?: string): Promise<void> {
|
|
2416
2468
|
const privatePath = placementPrivatePath(path);
|
|
2417
|
-
await this.dispatch("deletePlacementPrivate", false, async (session) => {
|
|
2418
|
-
|
|
2419
|
-
await session.deletePlacementPrivate(privatePath, runAs);
|
|
2420
|
-
return;
|
|
2421
|
-
}
|
|
2422
|
-
const args = {
|
|
2423
|
-
cmd: `rm -f ${shellSingleQuote(privatePath)}`,
|
|
2424
|
-
...(runAs ? { runAs } : {}),
|
|
2425
|
-
};
|
|
2426
|
-
if (session.exec) {
|
|
2427
|
-
const result = await session.exec(args);
|
|
2428
|
-
if (
|
|
2429
|
-
result &&
|
|
2430
|
-
typeof result === "object" &&
|
|
2431
|
-
typeof (result as { exitCode?: unknown }).exitCode === "number" &&
|
|
2432
|
-
(result as { exitCode: number }).exitCode !== 0
|
|
2433
|
-
) {
|
|
2434
|
-
throw new Error("placement-private cleanup failed");
|
|
2435
|
-
}
|
|
2436
|
-
return;
|
|
2437
|
-
}
|
|
2438
|
-
if (session.execCommand) {
|
|
2439
|
-
const result = await session.execCommand(args);
|
|
2440
|
-
const exitCode = parseExecBannerExitCode(result);
|
|
2441
|
-
if (exitCode !== null && exitCode !== 0) {
|
|
2442
|
-
throw new Error("placement-private cleanup failed");
|
|
2443
|
-
}
|
|
2444
|
-
return;
|
|
2445
|
-
}
|
|
2446
|
-
throw new RoutingUnsupportedError("deletePlacementPrivate", this.cached?.kind ?? "unknown");
|
|
2469
|
+
await this.dispatch("deletePlacementPrivate", false, async (session, backend) => {
|
|
2470
|
+
await deletePlacementPrivateOnBackend(session, privatePath, runAs, backend.kind);
|
|
2447
2471
|
});
|
|
2448
2472
|
}
|
|
2449
2473
|
|
|
@@ -2455,7 +2479,7 @@ export class RoutingSandboxSession implements RoutableBackendSession {
|
|
|
2455
2479
|
): Promise<WorkspaceFileImportReceipt> {
|
|
2456
2480
|
return await this.dispatch("importWorkspaceFile", true, async (session, backend) => {
|
|
2457
2481
|
const channel = new SandboxChannelAService({
|
|
2458
|
-
session: session
|
|
2482
|
+
session: withPlacementPrivateControl(session, backend.kind),
|
|
2459
2483
|
workspaceRoot: input.workspaceRoot,
|
|
2460
2484
|
...(backend.kind === "selfhosted"
|
|
2461
2485
|
? { providerPathMode: "workspace-relative" as const }
|
|
@@ -2476,7 +2500,7 @@ export class RoutingSandboxSession implements RoutableBackendSession {
|
|
|
2476
2500
|
): Promise<readonly WorkspaceFileImportReceipt[]> {
|
|
2477
2501
|
return await this.dispatch("importWorkspaceFiles", true, async (session, backend) => {
|
|
2478
2502
|
const channel = new SandboxChannelAService({
|
|
2479
|
-
session: session
|
|
2503
|
+
session: withPlacementPrivateControl(session, backend.kind),
|
|
2480
2504
|
workspaceRoot: input.workspaceRoot,
|
|
2481
2505
|
...(backend.kind === "selfhosted"
|
|
2482
2506
|
? { providerPathMode: "workspace-relative" as const }
|
|
@@ -2805,12 +2829,87 @@ function placementPrivateWrite(value: unknown): {
|
|
|
2805
2829
|
};
|
|
2806
2830
|
}
|
|
2807
2831
|
|
|
2832
|
+
/** Preserve private staging on the already resolved provider. Calling the
|
|
2833
|
+
* routing proxy here would re-resolve between staging, import and cleanup. */
|
|
2834
|
+
function withPlacementPrivateControl(
|
|
2835
|
+
session: RoutableBackendSession,
|
|
2836
|
+
backendKind: string,
|
|
2837
|
+
): ChannelASession {
|
|
2838
|
+
return new Proxy(session, {
|
|
2839
|
+
get(target, property) {
|
|
2840
|
+
if (property === "writePlacementPrivate") {
|
|
2841
|
+
return async (args: unknown) =>
|
|
2842
|
+
await writePlacementPrivateOnBackend(target, placementPrivateWrite(args), backendKind);
|
|
2843
|
+
}
|
|
2844
|
+
if (property === "deletePlacementPrivate") {
|
|
2845
|
+
return async (path: string, runAs?: string) =>
|
|
2846
|
+
await deletePlacementPrivateOnBackend(
|
|
2847
|
+
target,
|
|
2848
|
+
placementPrivatePath(path),
|
|
2849
|
+
runAs,
|
|
2850
|
+
backendKind,
|
|
2851
|
+
);
|
|
2852
|
+
}
|
|
2853
|
+
const value: unknown = Reflect.get(target, property, target);
|
|
2854
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
2855
|
+
},
|
|
2856
|
+
}) as ChannelASession;
|
|
2857
|
+
}
|
|
2858
|
+
|
|
2859
|
+
async function writePlacementPrivateOnBackend(
|
|
2860
|
+
session: RoutableBackendSession,
|
|
2861
|
+
input: ReturnType<typeof placementPrivateWrite>,
|
|
2862
|
+
backendKind: string,
|
|
2863
|
+
): Promise<unknown> {
|
|
2864
|
+
if (session.writePlacementPrivate) return await session.writePlacementPrivate(input);
|
|
2865
|
+
if (session.writeFile) return await session.writeFile(input);
|
|
2866
|
+
return await streamPlacementPrivateFile(session, input, backendKind);
|
|
2867
|
+
}
|
|
2868
|
+
|
|
2869
|
+
async function deletePlacementPrivateOnBackend(
|
|
2870
|
+
session: RoutableBackendSession,
|
|
2871
|
+
privatePath: string,
|
|
2872
|
+
runAs: string | undefined,
|
|
2873
|
+
backendKind: string,
|
|
2874
|
+
): Promise<void> {
|
|
2875
|
+
if (session.deletePlacementPrivate) {
|
|
2876
|
+
await session.deletePlacementPrivate(privatePath, runAs);
|
|
2877
|
+
return;
|
|
2878
|
+
}
|
|
2879
|
+
const args = {
|
|
2880
|
+
cmd: `rm -f ${shellSingleQuote(privatePath)}`,
|
|
2881
|
+
...(runAs ? { runAs } : {}),
|
|
2882
|
+
};
|
|
2883
|
+
if (session.exec) {
|
|
2884
|
+
const result = await session.exec(args);
|
|
2885
|
+
if (
|
|
2886
|
+
result &&
|
|
2887
|
+
typeof result === "object" &&
|
|
2888
|
+
typeof (result as { exitCode?: unknown }).exitCode === "number" &&
|
|
2889
|
+
(result as { exitCode: number }).exitCode !== 0
|
|
2890
|
+
) {
|
|
2891
|
+
throw new Error("placement-private cleanup failed");
|
|
2892
|
+
}
|
|
2893
|
+
return;
|
|
2894
|
+
}
|
|
2895
|
+
if (session.execCommand) {
|
|
2896
|
+
const result = await session.execCommand(args);
|
|
2897
|
+
const exitCode = parseExecBannerExitCode(result);
|
|
2898
|
+
if (exitCode !== null && exitCode !== 0) {
|
|
2899
|
+
throw new Error("placement-private cleanup failed");
|
|
2900
|
+
}
|
|
2901
|
+
return;
|
|
2902
|
+
}
|
|
2903
|
+
throw new RoutingUnsupportedError("deletePlacementPrivate", backendKind);
|
|
2904
|
+
}
|
|
2905
|
+
|
|
2808
2906
|
async function streamPlacementPrivateFile(
|
|
2809
2907
|
session: RoutableBackendSession,
|
|
2810
2908
|
input: ReturnType<typeof placementPrivateWrite>,
|
|
2811
2909
|
backendKind: string,
|
|
2812
2910
|
): Promise<void> {
|
|
2813
|
-
|
|
2911
|
+
const exec = session.exec?.bind(session) ?? session.execCommand?.bind(session);
|
|
2912
|
+
if (!exec || !session.writeStdin) {
|
|
2814
2913
|
throw new RoutingUnsupportedError("writePlacementPrivate", backendKind);
|
|
2815
2914
|
}
|
|
2816
2915
|
const bytes = typeof input.content === "string" ? Buffer.from(input.content) : input.content;
|
|
@@ -2821,7 +2920,7 @@ async function streamPlacementPrivateFile(
|
|
|
2821
2920
|
...(input.createParents ? [`install -d -m 0700 -- ${shellSingleQuote(parent)}`] : []),
|
|
2822
2921
|
];
|
|
2823
2922
|
if (bytes.byteLength === 0) {
|
|
2824
|
-
const result = await
|
|
2923
|
+
const result = await exec({
|
|
2825
2924
|
cmd: [
|
|
2826
2925
|
...prelude,
|
|
2827
2926
|
`: > ${shellSingleQuote(input.path)}`,
|
|
@@ -2849,7 +2948,7 @@ async function streamPlacementPrivateFile(
|
|
|
2849
2948
|
`chmod 0600 -- ${shellSingleQuote(input.path)}`,
|
|
2850
2949
|
`printf %s ${shellSingleQuote(marker)}`,
|
|
2851
2950
|
].join("; ");
|
|
2852
|
-
const started = await
|
|
2951
|
+
const started = await exec({
|
|
2853
2952
|
cmd: command,
|
|
2854
2953
|
...(input.runAs ? { runAs: input.runAs } : {}),
|
|
2855
2954
|
yieldTimeMs: 250,
|
|
@@ -18,7 +18,10 @@ const MAX_CREDENTIAL_FILES = 64;
|
|
|
18
18
|
const MAX_CREDENTIAL_FILE_BYTES = 1024 * 1024;
|
|
19
19
|
const MAX_TOTAL_MATERIAL_BYTES = 4 * 1024 * 1024;
|
|
20
20
|
const MAX_AUTH_NEEDED_NOTICES = 32;
|
|
21
|
-
|
|
21
|
+
// Encoded chunks must leave room for both the cancellation shell and the SDK
|
|
22
|
+
// run-as wrapper, each of which repeats and quotes the command. Keep chunks
|
|
23
|
+
// base64-aligned and bounded without dropping any credential bytes.
|
|
24
|
+
const WRITE_CHUNK_BYTES = 1536;
|
|
22
25
|
const COMMAND_OK_MARKER = "__OPENGENI_RUN_CREDENTIAL_COMMAND_OK__";
|
|
23
26
|
const AUTH_NEEDED_REASONS = new Set([
|
|
24
27
|
"missing_connection",
|
|
@@ -496,27 +499,54 @@ async function runCredentialCommand(
|
|
|
496
499
|
|
|
497
500
|
async function writeCredentialFile(
|
|
498
501
|
session: RunCredentialCommandSession,
|
|
499
|
-
|
|
502
|
+
directory: string,
|
|
503
|
+
relativePath: string,
|
|
500
504
|
content: string,
|
|
501
505
|
commandRunner?: RunCredentialCommandRunner,
|
|
502
506
|
): Promise<void> {
|
|
503
|
-
await runCredentialCommand(
|
|
507
|
+
await runCredentialCommand(
|
|
508
|
+
session,
|
|
509
|
+
credentialFileCommand(directory, relativePath, ': > "$_opengeni_credential_path"'),
|
|
510
|
+
commandRunner,
|
|
511
|
+
);
|
|
504
512
|
const encoded = base64(content);
|
|
505
513
|
for (let offset = 0; offset < encoded.length; offset += WRITE_CHUNK_BYTES) {
|
|
506
514
|
const chunk = encoded.slice(offset, offset + WRITE_CHUNK_BYTES);
|
|
507
515
|
await runCredentialCommand(
|
|
508
516
|
session,
|
|
509
|
-
|
|
517
|
+
credentialFileCommand(
|
|
518
|
+
directory,
|
|
519
|
+
relativePath,
|
|
520
|
+
`printf %s ${shellQuote(chunk)} | ${portableBase64DecodeCommand()} >> "$_opengeni_credential_path"`,
|
|
521
|
+
),
|
|
510
522
|
commandRunner,
|
|
511
523
|
);
|
|
512
524
|
}
|
|
513
525
|
await runCredentialCommand(
|
|
514
526
|
session,
|
|
515
|
-
|
|
527
|
+
credentialFileCommand(
|
|
528
|
+
directory,
|
|
529
|
+
relativePath,
|
|
530
|
+
`[ "$(wc -c < "$_opengeni_credential_path" | tr -d '[:space:]')" = ${shellQuote(String(byteLength(content)))} ]`,
|
|
531
|
+
),
|
|
516
532
|
commandRunner,
|
|
517
533
|
);
|
|
518
534
|
}
|
|
519
535
|
|
|
536
|
+
function credentialFileCommand(directory: string, relativePath: string, command: string): string {
|
|
537
|
+
// Avoid multiplying accepted path quotes through nested shell wrappers. The
|
|
538
|
+
// existing outer Bash decodes only literal UTF-8 bytes, including trailing
|
|
539
|
+
// newlines, without evaluating host-provided text or invoking a path decoder.
|
|
540
|
+
const escapedPath = [...Buffer.from(relativePath, "utf8")]
|
|
541
|
+
.map((byte) => `\\x${byte.toString(16).padStart(2, "0")}`)
|
|
542
|
+
.join("");
|
|
543
|
+
return [
|
|
544
|
+
`printf -v _opengeni_credential_path %b ${shellQuote(escapedPath)}`,
|
|
545
|
+
`_opengeni_credential_path=${shellQuote(directory)}/"$_opengeni_credential_path"`,
|
|
546
|
+
command,
|
|
547
|
+
].join("\n");
|
|
548
|
+
}
|
|
549
|
+
|
|
520
550
|
export type MaterializeRunCredentialsOptions = {
|
|
521
551
|
sessionId: string;
|
|
522
552
|
attemptId: string;
|
|
@@ -574,7 +604,8 @@ export async function materializeRunCredentials(
|
|
|
574
604
|
];
|
|
575
605
|
await writeCredentialFile(
|
|
576
606
|
session,
|
|
577
|
-
|
|
607
|
+
stage,
|
|
608
|
+
"env",
|
|
578
609
|
environmentLines.length > 0 ? `${environmentLines.join("\n")}\n` : "",
|
|
579
610
|
options.commandRunner,
|
|
580
611
|
);
|
|
@@ -585,13 +616,20 @@ export async function materializeRunCredentials(
|
|
|
585
616
|
);
|
|
586
617
|
|
|
587
618
|
for (const file of material.files) {
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
619
|
+
const directory = `${stage}/files`;
|
|
620
|
+
await runCredentialCommand(
|
|
621
|
+
session,
|
|
622
|
+
credentialFileCommand(directory, file.path, 'mkdir -p -- "${_opengeni_credential_path%/*}"'),
|
|
623
|
+
options.commandRunner,
|
|
624
|
+
);
|
|
625
|
+
await writeCredentialFile(session, directory, file.path, file.content, options.commandRunner);
|
|
592
626
|
await runCredentialCommand(
|
|
593
627
|
session,
|
|
594
|
-
|
|
628
|
+
credentialFileCommand(
|
|
629
|
+
directory,
|
|
630
|
+
file.path,
|
|
631
|
+
`chmod -- ${shellQuote(file.mode ?? "0600")} "$_opengeni_credential_path"`,
|
|
632
|
+
),
|
|
595
633
|
options.commandRunner,
|
|
596
634
|
);
|
|
597
635
|
}
|
|
@@ -599,25 +637,30 @@ export async function materializeRunCredentials(
|
|
|
599
637
|
await runCredentialCommand(
|
|
600
638
|
session,
|
|
601
639
|
[
|
|
640
|
+
// Bind validated identifiers once rather than repeatedly expanding long
|
|
641
|
+
// paths through cancellation and SDK run-as shell quoting.
|
|
642
|
+
`_opengeni_credential_root=${shellQuote(root)}`,
|
|
643
|
+
`_opengeni_credential_version_name=${shellQuote(versionName)}`,
|
|
644
|
+
`_opengeni_credential_prefix=${shellQuote(prefix)}`,
|
|
602
645
|
...pointerLockAcquireCommands(root),
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
646
|
+
'previous=$(cat -- "$_opengeni_credential_root/current" 2>/dev/null || :)',
|
|
647
|
+
'mv -- "$_opengeni_credential_root/versions/.stage-$_opengeni_credential_version_name" "$_opengeni_credential_root/versions/$_opengeni_credential_version_name"',
|
|
648
|
+
'printf \'%s\\n\' "$_opengeni_credential_version_name" > "$_opengeni_credential_root/.next"',
|
|
649
|
+
'mv -f -- "$_opengeni_credential_root/.next" "$_opengeni_credential_root/current"',
|
|
607
650
|
...(options.prunePreviousGenerations
|
|
608
651
|
? [
|
|
609
|
-
|
|
652
|
+
'find "$_opengeni_credential_root/versions" -mindepth 1 -maxdepth 1 ! -name "$_opengeni_credential_version_name" -exec rm -rf -- {} +',
|
|
610
653
|
]
|
|
611
654
|
: options.pruneOtherAttempts
|
|
612
655
|
? [
|
|
613
|
-
|
|
656
|
+
'find "$_opengeni_credential_root/versions" -mindepth 1 -maxdepth 1 ! -name "${_opengeni_credential_prefix}*" -exec rm -rf -- {} +',
|
|
614
657
|
]
|
|
615
658
|
: options.pruneSupersededGenerations
|
|
616
659
|
? [
|
|
617
|
-
|
|
660
|
+
'for candidate in "$_opengeni_credential_root/versions/$_opengeni_credential_prefix"*; do',
|
|
618
661
|
` [ -e "$candidate" ] || continue`,
|
|
619
662
|
` name=\${candidate##*/}`,
|
|
620
|
-
|
|
663
|
+
' [ "$name" = "$_opengeni_credential_version_name" ] || [ "$name" = "$previous" ] || rm -rf -- "$candidate"',
|
|
621
664
|
"done",
|
|
622
665
|
]
|
|
623
666
|
: []),
|