@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
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import { posix } from "node:path";
|
|
2
|
+
import { ModalClient } from "modal";
|
|
3
|
+
import { ModalLegacyProviderCommand } from "@opengeni/contracts";
|
|
4
|
+
import type { ChannelAExecArgs } from "../channel-a";
|
|
5
|
+
import { shellQuote } from "@openai/agents-core/sandbox/internal";
|
|
6
|
+
|
|
7
|
+
type ControlPlane = Pick<
|
|
8
|
+
ModalClient["cpClient"],
|
|
9
|
+
| "sandboxGetTaskId"
|
|
10
|
+
| "containerExec"
|
|
11
|
+
| "containerExecGetOutput"
|
|
12
|
+
| "containerExecPutInput"
|
|
13
|
+
| "taskGetCommandRouterAccess"
|
|
14
|
+
>;
|
|
15
|
+
|
|
16
|
+
type CommandClient = Pick<ModalClient, "cpClient" | "version"> &
|
|
17
|
+
Partial<Pick<ModalClient, "profile" | "logger">>;
|
|
18
|
+
type GrpcMiddleware = NonNullable<
|
|
19
|
+
NonNullable<ConstructorParameters<typeof ModalClient>[0]>["grpcMiddleware"]
|
|
20
|
+
>[number];
|
|
21
|
+
/** This dedicated command client never retries a request behind the caller's
|
|
22
|
+
* back and preserves the exact cancellation signal, including streaming RPCs. */
|
|
23
|
+
export const modalCommandAbortMiddleware: GrpcMiddleware = async function* (call, options) {
|
|
24
|
+
options.signal?.throwIfAborted();
|
|
25
|
+
return yield* call.next(call.request, options);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Version-pinned compatibility boundary: 0.9.0's retryMiddleware drops signal
|
|
29
|
+
// for streaming and retries=0. Its public custom middleware runs OUTSIDE that
|
|
30
|
+
// branch, so cannot repair it. Override only this dedicated instance's factory
|
|
31
|
+
// hook before construction; never mutate the SDK's global prototype or use
|
|
32
|
+
// negative/fractional retry counts to manipulate its branch conditions.
|
|
33
|
+
type PublicModalClient = Pick<ModalClient, keyof ModalClient>;
|
|
34
|
+
const ModalClientBase: new (
|
|
35
|
+
options?: ConstructorParameters<typeof ModalClient>[0],
|
|
36
|
+
) => PublicModalClient = ModalClient;
|
|
37
|
+
class ModalCommandClient extends ModalClientBase {
|
|
38
|
+
retryMiddleware(): GrpcMiddleware {
|
|
39
|
+
return modalCommandAbortMiddleware;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const controlClients = new WeakMap<object, PublicModalClient>();
|
|
43
|
+
|
|
44
|
+
export function commandControlPlane(client: CommandClient): ControlPlane {
|
|
45
|
+
// Minimal injected control planes are used by deterministic unit tests.
|
|
46
|
+
// Production installation requires the authenticated SDK profile below.
|
|
47
|
+
if (!client.profile) return client.cpClient;
|
|
48
|
+
if (typeof Reflect.get(ModalClient.prototype, "retryMiddleware") !== "function")
|
|
49
|
+
throw new Error("Verified Modal command cancellation contract is unavailable");
|
|
50
|
+
let controlled = controlClients.get(client);
|
|
51
|
+
if (!controlled) {
|
|
52
|
+
const profile = client.profile;
|
|
53
|
+
if (!profile.tokenId || !profile.tokenSecret)
|
|
54
|
+
throw new Error("Modal command control requires its original authenticated profile");
|
|
55
|
+
controlled = new ModalCommandClient({
|
|
56
|
+
tokenId: profile.tokenId,
|
|
57
|
+
tokenSecret: profile.tokenSecret,
|
|
58
|
+
endpoint: profile.serverUrl,
|
|
59
|
+
environment: profile.environment ?? "",
|
|
60
|
+
...(profile.maxThrottleWaitSecs !== undefined
|
|
61
|
+
? { maxThrottleWaitSecs: profile.maxThrottleWaitSecs }
|
|
62
|
+
: {}),
|
|
63
|
+
...(client.logger ? { logger: client.logger } : {}),
|
|
64
|
+
});
|
|
65
|
+
controlClients.set(client, controlled);
|
|
66
|
+
}
|
|
67
|
+
return controlled.cpClient;
|
|
68
|
+
}
|
|
69
|
+
export type ModalCommandStream = "stdout" | "stderr";
|
|
70
|
+
export type ModalProviderCommand = ModalLegacyProviderCommand;
|
|
71
|
+
export type ModalProviderOutputPage = {
|
|
72
|
+
command: ModalProviderCommand;
|
|
73
|
+
chunks: Array<{ stream: ModalCommandStream; chunkId: string; text: string }>;
|
|
74
|
+
exitCode: number | null;
|
|
75
|
+
streamFidelity: "separate" | "merged";
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// A provider batch may end halfway through a UTF-8 character. Keep only that
|
|
79
|
+
// unfinished suffix in protected cursor state; invalid complete bytes still
|
|
80
|
+
// use the usual replacement-character decoding policy.
|
|
81
|
+
export function decodePage(prior: string, input: Uint8Array[], terminal: boolean) {
|
|
82
|
+
const bytes = Buffer.concat([Buffer.from(prior, "base64"), ...input]);
|
|
83
|
+
let boundary = bytes.length;
|
|
84
|
+
if (!terminal) {
|
|
85
|
+
for (let start = Math.max(0, bytes.length - 3); start < bytes.length; start++) {
|
|
86
|
+
const first = bytes[start]!;
|
|
87
|
+
const width =
|
|
88
|
+
first >= 0xc2 && first <= 0xdf
|
|
89
|
+
? 2
|
|
90
|
+
: first >= 0xe0 && first <= 0xef
|
|
91
|
+
? 3
|
|
92
|
+
: first >= 0xf0 && first <= 0xf4
|
|
93
|
+
? 4
|
|
94
|
+
: 0;
|
|
95
|
+
if (!width || bytes.length - start >= width) continue;
|
|
96
|
+
const suffix = bytes.subarray(start + 1);
|
|
97
|
+
if (!suffix.every((byte) => byte >= 0x80 && byte <= 0xbf)) continue;
|
|
98
|
+
const second = suffix[0];
|
|
99
|
+
if (
|
|
100
|
+
second !== undefined &&
|
|
101
|
+
((first === 0xe0 && second < 0xa0) ||
|
|
102
|
+
(first === 0xed && second > 0x9f) ||
|
|
103
|
+
(first === 0xf0 && second < 0x90) ||
|
|
104
|
+
(first === 0xf4 && second > 0x8f))
|
|
105
|
+
)
|
|
106
|
+
continue;
|
|
107
|
+
boundary = start;
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
text: bytes.subarray(0, boundary).toString("utf8"),
|
|
113
|
+
remainder: bytes.subarray(boundary).toString("base64"),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Version-pinned Modal control-plane boundary. Execution identity, output
|
|
118
|
+
* batches and exit status come from authenticated provider RPCs, never sandbox
|
|
119
|
+
* files or an SDK object's in-memory process map. Callers must store the
|
|
120
|
+
* returned locator/cursors outside the command's write authority. */
|
|
121
|
+
export class ModalCommandControl {
|
|
122
|
+
private constructor(
|
|
123
|
+
private readonly client: ControlPlane,
|
|
124
|
+
private readonly sandboxIdentity: string | (() => string),
|
|
125
|
+
private readonly root: string,
|
|
126
|
+
private readonly environment: Record<string, string> | (() => Record<string, string>) = {},
|
|
127
|
+
) {}
|
|
128
|
+
|
|
129
|
+
static forSandbox(
|
|
130
|
+
client: CommandClient,
|
|
131
|
+
sandboxId: string | (() => string),
|
|
132
|
+
root: string,
|
|
133
|
+
environment: Record<string, string> | (() => Record<string, string>) = {},
|
|
134
|
+
): ModalCommandControl {
|
|
135
|
+
if (client.version() !== "0.9.0")
|
|
136
|
+
throw new Error("Modal command control requires the verified 0.9.0 SDK contract");
|
|
137
|
+
return new ModalCommandControl(commandControlPlane(client), sandboxId, root, environment);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private get sandboxId(): string {
|
|
141
|
+
return typeof this.sandboxIdentity === "function"
|
|
142
|
+
? this.sandboxIdentity()
|
|
143
|
+
: this.sandboxIdentity;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async start(args: ChannelAExecArgs, signal?: AbortSignal): Promise<ModalProviderCommand> {
|
|
147
|
+
signal?.throwIfAborted();
|
|
148
|
+
const workdir = posix.resolve(this.root, args.workdir ?? this.root);
|
|
149
|
+
if (workdir !== this.root && !workdir.startsWith(`${this.root.replace(/\/$/u, "")}/`))
|
|
150
|
+
throw new Error("Command workdir is outside the sandbox workspace");
|
|
151
|
+
// Hydration replaces the SDK sandbox. Freeze its current identity for this
|
|
152
|
+
// start so an asynchronous replacement cannot relabel the returned locator.
|
|
153
|
+
const sandboxId = this.sandboxId;
|
|
154
|
+
const task = await this.client.sandboxGetTaskId({ sandboxId }, signal ? { signal } : undefined);
|
|
155
|
+
if (!task.taskId || task.taskResult) throw new Error("Modal command task is unavailable");
|
|
156
|
+
if (sandboxId !== this.sandboxId)
|
|
157
|
+
throw new Error("Modal sandbox changed during command preparation");
|
|
158
|
+
const login = args.shell ? (args.login ?? true) : false;
|
|
159
|
+
let command = [args.shell ?? "/bin/sh", login ? "-lc" : "-c", args.cmd];
|
|
160
|
+
if (args.runAs) {
|
|
161
|
+
const user = shellQuote(args.runAs);
|
|
162
|
+
const invocation = command.map(shellQuote).join(" ");
|
|
163
|
+
const script = `if [ "$(id -u)" = ${user} ] || [ "$(id -un 2>/dev/null)" = ${user} ]; then exec ${invocation}; elif [ "$(id -u)" = 0 ]; then exec su -s /bin/sh ${user} -c ${shellQuote(`exec ${invocation}`)}; else exec sudo -n -u ${user} -- ${invocation}; fi`;
|
|
164
|
+
command = ["/bin/sh", "-c", script];
|
|
165
|
+
}
|
|
166
|
+
const environment =
|
|
167
|
+
typeof this.environment === "function" ? this.environment() : this.environment;
|
|
168
|
+
if (Object.keys(environment).length) {
|
|
169
|
+
command = [
|
|
170
|
+
"/usr/bin/env",
|
|
171
|
+
"--",
|
|
172
|
+
...Object.entries(environment).map(([key, value]) => `${key}=${value}`),
|
|
173
|
+
...command,
|
|
174
|
+
];
|
|
175
|
+
}
|
|
176
|
+
// Do not retry this mutating start on an ambiguous transport error: this
|
|
177
|
+
// provider API assigns the execution id in its response, not in our request.
|
|
178
|
+
const result = await this.client.containerExec(
|
|
179
|
+
{
|
|
180
|
+
taskId: task.taskId,
|
|
181
|
+
command,
|
|
182
|
+
terminateContainerOnExit: false,
|
|
183
|
+
runtimeDebug: false,
|
|
184
|
+
stdoutOutput: 2,
|
|
185
|
+
stderrOutput: 2,
|
|
186
|
+
timeoutSecs: 0,
|
|
187
|
+
workdir,
|
|
188
|
+
secretIds: [],
|
|
189
|
+
...(args.tty
|
|
190
|
+
? {
|
|
191
|
+
ptyInfo: {
|
|
192
|
+
enabled: true,
|
|
193
|
+
winszRows: 24,
|
|
194
|
+
winszCols: 80,
|
|
195
|
+
envTerm: "xterm",
|
|
196
|
+
envColorterm: "",
|
|
197
|
+
envTermProgram: "",
|
|
198
|
+
ptyType: 1,
|
|
199
|
+
noTerminateOnIdleStdin: true,
|
|
200
|
+
},
|
|
201
|
+
}
|
|
202
|
+
: {}),
|
|
203
|
+
},
|
|
204
|
+
{ retries: 0, ...(signal ? { signal } : {}) },
|
|
205
|
+
);
|
|
206
|
+
if (!result.execId) throw new Error("Modal command start returned no execution identity");
|
|
207
|
+
return {
|
|
208
|
+
kind: "modal-control-v1",
|
|
209
|
+
sandboxId,
|
|
210
|
+
taskId: task.taskId,
|
|
211
|
+
execId: result.execId,
|
|
212
|
+
...(args.tty ? { pty: true } : {}),
|
|
213
|
+
streams: {
|
|
214
|
+
stdout: { batchIndex: 0, utf8Remainder: "", exitCode: null },
|
|
215
|
+
stderr: { batchIndex: 0, utf8Remainder: "", exitCode: null },
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async read(
|
|
221
|
+
command: ModalProviderCommand,
|
|
222
|
+
yieldTimeMs: number,
|
|
223
|
+
signal?: AbortSignal,
|
|
224
|
+
): Promise<ModalProviderOutputPage> {
|
|
225
|
+
return this.readOutput(command, yieldTimeMs, signal);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Fixed internal probes own their cancellation controller and must drain
|
|
229
|
+
* both reads before relinquishing the surrounding provider-operation gate.
|
|
230
|
+
* Retained-command readers keep their existing signal/receipt contract.
|
|
231
|
+
*/
|
|
232
|
+
async readProbe(
|
|
233
|
+
command: ModalProviderCommand,
|
|
234
|
+
yieldTimeMs: number,
|
|
235
|
+
cancellation: AbortController,
|
|
236
|
+
): Promise<ModalProviderOutputPage> {
|
|
237
|
+
return this.readOutput(command, yieldTimeMs, cancellation.signal, (error) =>
|
|
238
|
+
cancellation.abort(error),
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private async readOutput(
|
|
243
|
+
command: ModalProviderCommand,
|
|
244
|
+
yieldTimeMs: number,
|
|
245
|
+
signal?: AbortSignal,
|
|
246
|
+
cancelSiblings?: (error: unknown) => void,
|
|
247
|
+
): Promise<ModalProviderOutputPage> {
|
|
248
|
+
signal?.throwIfAborted();
|
|
249
|
+
this.assertIdentity(command);
|
|
250
|
+
const next = structuredClone(command);
|
|
251
|
+
let failure: { error: unknown } | undefined;
|
|
252
|
+
const reads = (["stdout", "stderr"] as const)
|
|
253
|
+
.map(async (stream) => {
|
|
254
|
+
const cursor = command.streams[stream];
|
|
255
|
+
if (cursor.exitCode !== null) return null;
|
|
256
|
+
const descriptor = stream === "stdout" ? 1 : 2;
|
|
257
|
+
for await (const batch of this.client.containerExecGetOutput(
|
|
258
|
+
{
|
|
259
|
+
execId: command.execId,
|
|
260
|
+
timeout: Math.max(0, yieldTimeMs) / 1000,
|
|
261
|
+
lastBatchIndex: cursor.batchIndex,
|
|
262
|
+
fileDescriptor: descriptor,
|
|
263
|
+
getRawBytes: true,
|
|
264
|
+
},
|
|
265
|
+
signal ? { signal } : undefined,
|
|
266
|
+
)) {
|
|
267
|
+
if (batch.batchIndex <= cursor.batchIndex) continue;
|
|
268
|
+
if (!Number.isSafeInteger(batch.batchIndex))
|
|
269
|
+
throw new Error("Invalid Modal output cursor");
|
|
270
|
+
const terminal = batch.exitCode !== undefined;
|
|
271
|
+
if (terminal && !Number.isSafeInteger(batch.exitCode))
|
|
272
|
+
throw new Error("Invalid Modal exit status");
|
|
273
|
+
const decoded = decodePage(
|
|
274
|
+
cursor.utf8Remainder,
|
|
275
|
+
batch.items
|
|
276
|
+
.filter((item) => item.fileDescriptor === descriptor)
|
|
277
|
+
.map((item) => item.messageBytes),
|
|
278
|
+
terminal,
|
|
279
|
+
);
|
|
280
|
+
next.streams[stream] = {
|
|
281
|
+
batchIndex: batch.batchIndex,
|
|
282
|
+
utf8Remainder: decoded.remainder,
|
|
283
|
+
exitCode: terminal ? batch.exitCode! : null,
|
|
284
|
+
};
|
|
285
|
+
return {
|
|
286
|
+
stream,
|
|
287
|
+
chunkId: `modal:${command.execId}:${stream}:${cursor.batchIndex}:${batch.batchIndex}`,
|
|
288
|
+
text: decoded.text,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
return null;
|
|
292
|
+
})
|
|
293
|
+
.map((read) =>
|
|
294
|
+
!cancelSiblings
|
|
295
|
+
? read
|
|
296
|
+
: read.catch((error: unknown) => {
|
|
297
|
+
// A logical read owns both output streams. Promise.all alone releases
|
|
298
|
+
// its caller on the first rejection while a sibling RPC may still be
|
|
299
|
+
// running. Cancel that sibling, retain the original error, and drain
|
|
300
|
+
// both reads before returning control to mutation/capture ownership.
|
|
301
|
+
failure ??= { error };
|
|
302
|
+
cancelSiblings(error);
|
|
303
|
+
throw error;
|
|
304
|
+
}),
|
|
305
|
+
);
|
|
306
|
+
const pages = cancelSiblings
|
|
307
|
+
? await Promise.allSettled(reads).then((results) => {
|
|
308
|
+
if (failure) throw failure.error;
|
|
309
|
+
return results.map((result) => {
|
|
310
|
+
if (result.status === "rejected") throw result.reason;
|
|
311
|
+
return result.value;
|
|
312
|
+
});
|
|
313
|
+
})
|
|
314
|
+
: await Promise.all(reads);
|
|
315
|
+
const stdoutExit = next.streams.stdout.exitCode;
|
|
316
|
+
const stderrExit = next.streams.stderr.exitCode;
|
|
317
|
+
if (stdoutExit !== null && stderrExit !== null && stdoutExit !== stderrExit)
|
|
318
|
+
throw new Error("Modal output streams disagree about command exit status");
|
|
319
|
+
return {
|
|
320
|
+
command: next,
|
|
321
|
+
streamFidelity: command.pty ? "merged" : "separate",
|
|
322
|
+
chunks: pages.filter((page): page is NonNullable<typeof page> => page !== null),
|
|
323
|
+
exitCode: stdoutExit !== null && stderrExit !== null ? stdoutExit : null,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async write(command: ModalProviderCommand, chars: string, messageIndex: number): Promise<void> {
|
|
328
|
+
this.assertIdentity(command);
|
|
329
|
+
if (!chars) return;
|
|
330
|
+
if (!Number.isSafeInteger(messageIndex) || messageIndex < 1)
|
|
331
|
+
throw new Error("Modal stdin requires a protected monotonic message index");
|
|
332
|
+
await this.client.containerExecPutInput(
|
|
333
|
+
{
|
|
334
|
+
execId: command.execId,
|
|
335
|
+
input: { message: Buffer.from(chars), messageIndex, eof: false },
|
|
336
|
+
},
|
|
337
|
+
{ retries: 0 },
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private assertIdentity(command: ModalProviderCommand): void {
|
|
342
|
+
ModalLegacyProviderCommand.parse(command);
|
|
343
|
+
if (
|
|
344
|
+
command.kind !== "modal-control-v1" ||
|
|
345
|
+
command.sandboxId !== this.sandboxId ||
|
|
346
|
+
!command.taskId ||
|
|
347
|
+
!command.execId
|
|
348
|
+
)
|
|
349
|
+
throw new Error("Modal command locator does not match the original sandbox");
|
|
350
|
+
}
|
|
351
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SandboxMaterializationVerificationError,
|
|
3
|
+
retainMaterializationVerificationDiagnostic,
|
|
4
|
+
type MaterializationVerificationDiagnostic,
|
|
5
|
+
} from "../materialization-verification-error";
|
|
6
|
+
import type { ModalCommandControl, ModalProviderCommand } from "./modal-command-control";
|
|
7
|
+
|
|
8
|
+
const MARKER = "__OPENGENI_MATERIALIZED_PATH_VISIBLE__";
|
|
9
|
+
const OUTPUT_LIMIT = 16 * 1024;
|
|
10
|
+
|
|
11
|
+
/** A fixed read-only probe, not an admitted user command. Advance its private
|
|
12
|
+
* provider cursor locally; never borrow the surrounding mutation's retained
|
|
13
|
+
* handle or relax retained-command persistence requirements. The caller keeps
|
|
14
|
+
* the original backend and provider-operation gate for the entire observation.
|
|
15
|
+
*/
|
|
16
|
+
export async function verifyModalMaterializedPath(
|
|
17
|
+
control: Pick<ModalCommandControl, "start" | "readProbe">,
|
|
18
|
+
path: string,
|
|
19
|
+
workdir: string,
|
|
20
|
+
pending: Set<AbortController>,
|
|
21
|
+
observationTimeoutMs = 30_000,
|
|
22
|
+
): Promise<void> {
|
|
23
|
+
const quote = (value: string) => `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
24
|
+
const command = `test -e ${quote(path)} && printf %s ${quote(MARKER)}`;
|
|
25
|
+
const cancellation = new AbortController();
|
|
26
|
+
const deadlineReason = new Error("Materialization visibility observation deadline exceeded");
|
|
27
|
+
let providerCommand: ModalProviderCommand | undefined;
|
|
28
|
+
let stdout = "";
|
|
29
|
+
let stderr = "";
|
|
30
|
+
const diagnostic = (
|
|
31
|
+
reason: MaterializationVerificationDiagnostic["reason"],
|
|
32
|
+
exitCode: number | null = null,
|
|
33
|
+
): MaterializationVerificationDiagnostic => ({
|
|
34
|
+
reason,
|
|
35
|
+
path,
|
|
36
|
+
workdir,
|
|
37
|
+
command,
|
|
38
|
+
output: (stdout + stderr).slice(0, OUTPUT_LIMIT),
|
|
39
|
+
exitCode,
|
|
40
|
+
providerSessionId: null,
|
|
41
|
+
...(providerCommand
|
|
42
|
+
? {
|
|
43
|
+
providerExecution: {
|
|
44
|
+
sandboxId: providerCommand.sandboxId,
|
|
45
|
+
taskId: providerCommand.taskId,
|
|
46
|
+
execId: providerCommand.execId,
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
: {}),
|
|
50
|
+
});
|
|
51
|
+
const timer = setTimeout(() => {
|
|
52
|
+
cancellation.abort(deadlineReason);
|
|
53
|
+
}, observationTimeoutMs);
|
|
54
|
+
timer.unref();
|
|
55
|
+
pending.add(cancellation);
|
|
56
|
+
try {
|
|
57
|
+
providerCommand = await control.start(
|
|
58
|
+
{ cmd: command, workdir, shell: "sh", login: false, tty: false },
|
|
59
|
+
cancellation.signal,
|
|
60
|
+
);
|
|
61
|
+
for (;;) {
|
|
62
|
+
cancellation.signal.throwIfAborted();
|
|
63
|
+
const page = await control.readProbe(providerCommand, 1_000, cancellation);
|
|
64
|
+
// Cancellation ends observation, not the process. Never accept a late
|
|
65
|
+
// success after the attempt requested cancellation.
|
|
66
|
+
cancellation.signal.throwIfAborted();
|
|
67
|
+
if (
|
|
68
|
+
page.command.sandboxId !== providerCommand.sandboxId ||
|
|
69
|
+
page.command.taskId !== providerCommand.taskId ||
|
|
70
|
+
page.command.execId !== providerCommand.execId
|
|
71
|
+
) {
|
|
72
|
+
throw new SandboxMaterializationVerificationError(diagnostic("invalid_response"));
|
|
73
|
+
}
|
|
74
|
+
providerCommand = page.command;
|
|
75
|
+
for (const chunk of page.chunks) {
|
|
76
|
+
if (chunk.stream === "stdout") stdout += chunk.text;
|
|
77
|
+
else stderr += chunk.text;
|
|
78
|
+
}
|
|
79
|
+
if (stdout.length + stderr.length > OUTPUT_LIMIT) {
|
|
80
|
+
throw new SandboxMaterializationVerificationError(diagnostic("invalid_response"));
|
|
81
|
+
}
|
|
82
|
+
if (page.exitCode === null) {
|
|
83
|
+
// An empty provider page may return immediately. Yield to cancellation
|
|
84
|
+
// and the deadline instead of starving timers with resolved promises.
|
|
85
|
+
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (page.exitCode === 0 && stdout === MARKER) return;
|
|
89
|
+
throw new SandboxMaterializationVerificationError(
|
|
90
|
+
diagnostic(
|
|
91
|
+
page.exitCode === 1
|
|
92
|
+
? "path_not_visible"
|
|
93
|
+
: page.exitCode === 0
|
|
94
|
+
? "invalid_response"
|
|
95
|
+
: "command_failed",
|
|
96
|
+
page.exitCode,
|
|
97
|
+
),
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (cancellation.signal.reason === deadlineReason) {
|
|
102
|
+
throw new SandboxMaterializationVerificationError(
|
|
103
|
+
{
|
|
104
|
+
...diagnostic("command_pending"),
|
|
105
|
+
causeMessage: "Observation deadline exceeded; provider process completion is unconfirmed",
|
|
106
|
+
},
|
|
107
|
+
{ cause: error },
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
if (!(error instanceof SandboxMaterializationVerificationError)) {
|
|
111
|
+
retainMaterializationVerificationDiagnostic(error, {
|
|
112
|
+
...diagnostic("command_error"),
|
|
113
|
+
causeMessage: error instanceof Error ? error.message : String(error),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
throw error;
|
|
117
|
+
} finally {
|
|
118
|
+
cancellation.abort(new Error("Materialization visibility observation finished"));
|
|
119
|
+
clearTimeout(timer);
|
|
120
|
+
pending.delete(cancellation);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -796,7 +796,7 @@ type ModalClientLike = InstanceType<ModalModule["ModalClient"]>;
|
|
|
796
796
|
// a `ModalImageSelector.fromImage(...)`. `build` is synchronous and modal is imported
|
|
797
797
|
// lazily (never loaded for non-modal backends), so resolution can't happen inside
|
|
798
798
|
// `build`; the worker awaits `ensureModalRegistryImage` at boot for global refs and
|
|
799
|
-
|
|
799
|
+
|
|
800
800
|
// images are lazy, workspace-scoped definitions, so an image built by this module's
|
|
801
801
|
// client is usable by the ModalSandboxClient's own client.
|
|
802
802
|
|
|
@@ -819,14 +819,6 @@ function registryImageCacheKey(settings: Settings): string {
|
|
|
819
819
|
].join("|");
|
|
820
820
|
}
|
|
821
821
|
|
|
822
|
-
/**
|
|
823
|
-
* Resolve + cache the private-registry Modal image. No-op unless BOTH
|
|
824
|
-
* `modalImageRef` and `modalImageRegistrySecret` are set. Memoized per
|
|
825
|
-
* (imageRef, secret, environment) so it runs once per worker process. Awaited at
|
|
826
|
-
* worker boot for the deployment-global image and at turn time for pack-scoped
|
|
827
|
-
* images BEFORE the first sandbox using that ref is created; `build` then reads the
|
|
828
|
-
* resolved image and otherwise falls back to the public `fromTag` path.
|
|
829
|
-
*/
|
|
830
822
|
export async function ensureModalRegistryImage(
|
|
831
823
|
settings: Settings,
|
|
832
824
|
loadModal: ModalModuleLoader = defaultModalLoader,
|