@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,248 @@
|
|
|
1
|
+
import { Client, Metadata, credentials, status, type ServiceError } from "@grpc/grpc-js";
|
|
2
|
+
import protobuf from "protobufjs";
|
|
3
|
+
import { ProviderCommandStartRejectedError } from "../provider-command-session";
|
|
4
|
+
|
|
5
|
+
// Narrow wire projection of Modal 0.9.0's task_command_router.proto. The public
|
|
6
|
+
// SDK does not expose resumable byte offsets or cancellable command RPCs. Keep
|
|
7
|
+
// this boundary pinned and exercise it against an actual gRPC server in tests.
|
|
8
|
+
// Neither the JWT nor the router URL belongs in a durable command locator.
|
|
9
|
+
export const modalRouterWire = protobuf.parse(`syntax = "proto3";
|
|
10
|
+
message Identity { string task_id = 1; string exec_id = 2; }
|
|
11
|
+
message Pty {
|
|
12
|
+
bool enabled = 1; uint32 winsz_rows = 2; uint32 winsz_cols = 3;
|
|
13
|
+
string env_term = 4; string env_colorterm = 5; string env_term_program = 6;
|
|
14
|
+
int32 pty_type = 7; bool no_terminate_on_idle_stdin = 8;
|
|
15
|
+
}
|
|
16
|
+
message Start {
|
|
17
|
+
string task_id = 1; string exec_id = 2; repeated string command_args = 3;
|
|
18
|
+
int32 stdout_config = 4; int32 stderr_config = 5;
|
|
19
|
+
optional uint32 timeout_secs = 6; optional string workdir = 7;
|
|
20
|
+
repeated string secret_ids = 8; optional Pty pty_info = 9;
|
|
21
|
+
bool runtime_debug = 10; string container_id = 11; map<string,string> env = 12;
|
|
22
|
+
}
|
|
23
|
+
message Read {
|
|
24
|
+
string task_id = 1; string exec_id = 2; uint64 offset = 3; int32 file_descriptor = 4;
|
|
25
|
+
}
|
|
26
|
+
message Data { bytes data = 1; }
|
|
27
|
+
message Poll { optional int32 code = 1; optional int32 signal = 2; }
|
|
28
|
+
message Write {
|
|
29
|
+
string task_id = 1; string exec_id = 2; uint64 offset = 3; bytes data = 4; bool eof = 5;
|
|
30
|
+
}
|
|
31
|
+
message Empty {}
|
|
32
|
+
`).root;
|
|
33
|
+
|
|
34
|
+
const prefix = "/modal.task_command_router.TaskCommandRouter/";
|
|
35
|
+
const maxPageBytes = 64 * 1024;
|
|
36
|
+
const maxWireBytes = 4 * 1024 * 1024;
|
|
37
|
+
|
|
38
|
+
/** Only constructed at the authenticated Start RPC boundary. Transport loss,
|
|
39
|
+
* deadline, cancellation and UNKNOWN are deliberately not rejection proof. */
|
|
40
|
+
export class ModalCommandStartRejectedError extends ProviderCommandStartRejectedError {
|
|
41
|
+
constructor(
|
|
42
|
+
readonly code: number,
|
|
43
|
+
cause: unknown,
|
|
44
|
+
) {
|
|
45
|
+
super(cause);
|
|
46
|
+
this.name = "ModalCommandStartRejectedError";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type ModalRouterIdentity = { taskId: string; execId: string };
|
|
51
|
+
export type ModalRouterAccess = { url: string; jwt: string };
|
|
52
|
+
export type ModalRouterStart = ModalRouterIdentity & {
|
|
53
|
+
commandArgs: string[];
|
|
54
|
+
workdir: string;
|
|
55
|
+
env: Record<string, string>;
|
|
56
|
+
ptyInfo?: {
|
|
57
|
+
enabled: boolean;
|
|
58
|
+
winszRows: number;
|
|
59
|
+
winszCols: number;
|
|
60
|
+
envTerm: string;
|
|
61
|
+
ptyType: number;
|
|
62
|
+
noTerminateOnIdleStdin: boolean;
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
function encode(type: string, value: object): Buffer {
|
|
67
|
+
const codec = modalRouterWire.lookupType(type);
|
|
68
|
+
return Buffer.from(codec.encode(codec.fromObject(value)).finish());
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** A single authenticated transport, owned and closed by its command adapter.
|
|
72
|
+
* Reads may stop at a byte boundary because the next read resumes at that exact
|
|
73
|
+
* offset. Mutations are never retried by this layer, including ambiguous starts.
|
|
74
|
+
*/
|
|
75
|
+
export class ModalCommandRouterWire {
|
|
76
|
+
private readonly client: Client;
|
|
77
|
+
private readonly metadata: Metadata;
|
|
78
|
+
private closed = false;
|
|
79
|
+
|
|
80
|
+
constructor(access: ModalRouterAccess, trustedRoots?: Buffer) {
|
|
81
|
+
const url = new URL(access.url);
|
|
82
|
+
if (url.protocol !== "https:" || url.username || url.password || !access.jwt)
|
|
83
|
+
throw new Error("Modal command router requires authenticated TLS");
|
|
84
|
+
this.client = new Client(url.host, credentials.createSsl(trustedRoots), {
|
|
85
|
+
"grpc.max_receive_message_length": maxWireBytes,
|
|
86
|
+
"grpc.max_send_message_length": maxWireBytes,
|
|
87
|
+
"grpc.enable_retries": 0,
|
|
88
|
+
});
|
|
89
|
+
this.metadata = new Metadata();
|
|
90
|
+
this.metadata.set("authorization", `Bearer ${access.jwt}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
close(): void {
|
|
94
|
+
this.closed = true;
|
|
95
|
+
this.client.close();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private async unary(
|
|
99
|
+
method: "TaskExecStart" | "TaskExecStdinWrite" | "TaskExecPoll",
|
|
100
|
+
requestType: string,
|
|
101
|
+
responseType: string,
|
|
102
|
+
request: object,
|
|
103
|
+
signal?: AbortSignal,
|
|
104
|
+
): Promise<protobuf.Message> {
|
|
105
|
+
signal?.throwIfAborted();
|
|
106
|
+
if (this.closed) throw new Error("Modal command router is closed");
|
|
107
|
+
return await new Promise((resolve, reject) => {
|
|
108
|
+
const call = this.client.makeUnaryRequest(
|
|
109
|
+
prefix + method,
|
|
110
|
+
(value: object) => encode(requestType, value),
|
|
111
|
+
(bytes) => modalRouterWire.lookupType(responseType).decode(bytes),
|
|
112
|
+
request,
|
|
113
|
+
this.metadata,
|
|
114
|
+
{ deadline: Date.now() + 30_000 },
|
|
115
|
+
(error, result) => {
|
|
116
|
+
signal?.removeEventListener("abort", abort);
|
|
117
|
+
if (signal?.aborted) reject(signal.reason);
|
|
118
|
+
else if (error) reject(error);
|
|
119
|
+
else if (result) resolve(result);
|
|
120
|
+
else reject(new Error("Modal command router returned no response"));
|
|
121
|
+
},
|
|
122
|
+
);
|
|
123
|
+
const abort = () => call.cancel();
|
|
124
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
125
|
+
if (signal?.aborted) abort();
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async start(request: ModalRouterStart, signal?: AbortSignal): Promise<void> {
|
|
130
|
+
try {
|
|
131
|
+
await this.unary(
|
|
132
|
+
"TaskExecStart",
|
|
133
|
+
"Start",
|
|
134
|
+
"Empty",
|
|
135
|
+
{
|
|
136
|
+
...request,
|
|
137
|
+
stdoutConfig: 1,
|
|
138
|
+
stderrConfig: 1,
|
|
139
|
+
},
|
|
140
|
+
signal,
|
|
141
|
+
);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
const code = (error as Partial<ServiceError> | null)?.code;
|
|
144
|
+
if (
|
|
145
|
+
typeof code === "number" &&
|
|
146
|
+
[
|
|
147
|
+
status.INVALID_ARGUMENT,
|
|
148
|
+
status.NOT_FOUND,
|
|
149
|
+
status.PERMISSION_DENIED,
|
|
150
|
+
status.UNAUTHENTICATED,
|
|
151
|
+
status.UNIMPLEMENTED,
|
|
152
|
+
].includes(code)
|
|
153
|
+
)
|
|
154
|
+
throw new ModalCommandStartRejectedError(code, error);
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async write(
|
|
160
|
+
identity: ModalRouterIdentity,
|
|
161
|
+
offset: number,
|
|
162
|
+
data: Uint8Array,
|
|
163
|
+
signal?: AbortSignal,
|
|
164
|
+
): Promise<void> {
|
|
165
|
+
if (!Number.isSafeInteger(offset) || offset < 0)
|
|
166
|
+
throw new Error("Invalid Modal stdin byte offset");
|
|
167
|
+
await this.unary(
|
|
168
|
+
"TaskExecStdinWrite",
|
|
169
|
+
"Write",
|
|
170
|
+
"Empty",
|
|
171
|
+
{ ...identity, offset, data, eof: false },
|
|
172
|
+
signal,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async poll(identity: ModalRouterIdentity, signal?: AbortSignal): Promise<number | null> {
|
|
177
|
+
const value = await this.unary("TaskExecPoll", "Identity", "Poll", identity, signal);
|
|
178
|
+
const decoded = modalRouterWire.lookupType("Poll").toObject(value) as {
|
|
179
|
+
code?: number;
|
|
180
|
+
signal?: number;
|
|
181
|
+
};
|
|
182
|
+
if (decoded.code !== undefined) return decoded.code;
|
|
183
|
+
if (decoded.signal !== undefined) return 128 + decoded.signal;
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async read(
|
|
188
|
+
identity: ModalRouterIdentity,
|
|
189
|
+
stream: "stdout" | "stderr",
|
|
190
|
+
offset: number,
|
|
191
|
+
waitMs: number,
|
|
192
|
+
signal?: AbortSignal,
|
|
193
|
+
): Promise<{ bytes: Buffer; eof: boolean }> {
|
|
194
|
+
signal?.throwIfAborted();
|
|
195
|
+
if (this.closed) throw new Error("Modal command router is closed");
|
|
196
|
+
if (
|
|
197
|
+
!Number.isSafeInteger(offset) ||
|
|
198
|
+
offset < 0 ||
|
|
199
|
+
!Number.isFinite(waitMs) ||
|
|
200
|
+
waitMs < 0 ||
|
|
201
|
+
waitMs > 50_000
|
|
202
|
+
)
|
|
203
|
+
throw new Error("Invalid Modal output read bounds");
|
|
204
|
+
const chunks: Buffer[] = [];
|
|
205
|
+
let length = 0;
|
|
206
|
+
let limited = false;
|
|
207
|
+
return await new Promise((resolve, reject) => {
|
|
208
|
+
const call = this.client.makeServerStreamRequest(
|
|
209
|
+
prefix + "TaskExecStdioRead",
|
|
210
|
+
(value: object) => encode("Read", value),
|
|
211
|
+
(bytes) =>
|
|
212
|
+
modalRouterWire.lookupType("Data").decode(bytes) as protobuf.Message & {
|
|
213
|
+
data: Uint8Array;
|
|
214
|
+
},
|
|
215
|
+
{ ...identity, offset, fileDescriptor: stream === "stdout" ? 0 : 1 },
|
|
216
|
+
this.metadata,
|
|
217
|
+
{ deadline: Date.now() + Math.max(1, waitMs) },
|
|
218
|
+
);
|
|
219
|
+
const abort = () => call.cancel();
|
|
220
|
+
const finish = (eof: boolean, error?: unknown) => {
|
|
221
|
+
signal?.removeEventListener("abort", abort);
|
|
222
|
+
if (signal?.aborted) reject(signal.reason);
|
|
223
|
+
else if (error) reject(error);
|
|
224
|
+
else resolve({ bytes: Buffer.concat(chunks, length), eof });
|
|
225
|
+
};
|
|
226
|
+
call.on("data", (value: { data: Uint8Array }) => {
|
|
227
|
+
if (limited) return;
|
|
228
|
+
const part = Buffer.from(value.data).subarray(0, maxPageBytes - length);
|
|
229
|
+
if (part.length) {
|
|
230
|
+
chunks.push(part);
|
|
231
|
+
length += part.length;
|
|
232
|
+
}
|
|
233
|
+
if (length === maxPageBytes) {
|
|
234
|
+
limited = true;
|
|
235
|
+
call.cancel();
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
call.once("error", (error: ServiceError) => {
|
|
239
|
+
if ((limited && error.code === status.CANCELLED) || error.code === status.DEADLINE_EXCEEDED)
|
|
240
|
+
finish(false);
|
|
241
|
+
else finish(false, error);
|
|
242
|
+
});
|
|
243
|
+
call.once("end", () => finish(!limited));
|
|
244
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
245
|
+
if (signal?.aborted) abort();
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
3
|
import { truncateOutput } from "@openai/agents-core/sandbox/internal";
|
|
3
4
|
import type { ChannelASession } from "../channel-a";
|
|
4
5
|
import { ModalProcessObservationUnavailableError } from "../errors";
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
admittedProviderCommandHandle,
|
|
12
13
|
} from "../provider-command-session";
|
|
13
14
|
import type { ModalCommandControl, ModalProviderCommand } from "./modal-command-control";
|
|
15
|
+
import { verifyModalMaterializedPath } from "./modal-materialization-verification";
|
|
14
16
|
|
|
15
17
|
type Entry = {
|
|
16
18
|
command: ModalProviderCommand;
|
|
@@ -19,9 +21,14 @@ type Entry = {
|
|
|
19
21
|
|
|
20
22
|
function sameExecution(a: ModalProviderCommand, b: ModalProviderCommand): boolean {
|
|
21
23
|
return (
|
|
24
|
+
a.kind === b.kind &&
|
|
22
25
|
a.sandboxId === b.sandboxId &&
|
|
23
26
|
a.taskId === b.taskId &&
|
|
24
27
|
a.execId === b.execId &&
|
|
28
|
+
isDeepStrictEqual(
|
|
29
|
+
"supervision" in a ? a.supervision : undefined,
|
|
30
|
+
"supervision" in b ? b.supervision : undefined,
|
|
31
|
+
) &&
|
|
25
32
|
Boolean(a.pty) === Boolean(b.pty)
|
|
26
33
|
);
|
|
27
34
|
}
|
|
@@ -33,14 +40,23 @@ export function installModalCommandSession(
|
|
|
33
40
|
session: ProviderCommandSession & {
|
|
34
41
|
execCommand?: ChannelASession["execCommand"];
|
|
35
42
|
writeStdin?: ChannelASession["writeStdin"];
|
|
43
|
+
verifyMaterializedPath?: (path: string, workdir: string) => Promise<void>;
|
|
36
44
|
},
|
|
37
|
-
control: ModalCommandControl,
|
|
45
|
+
control: Pick<ModalCommandControl, "start" | "read" | "readProbe" | "write"> &
|
|
46
|
+
Partial<Pick<ModalCommandControl, "supervisionControl" | "verifySupervisionCapability">>,
|
|
38
47
|
): void {
|
|
39
48
|
markTypedExecHandleLoss(session);
|
|
40
49
|
const originalExec = session.execCommand?.bind(session);
|
|
41
50
|
const originalWrite = session.writeStdin?.bind(session);
|
|
42
51
|
const cancelLegacyStart = session.cancelPendingExecCommand?.bind(session);
|
|
43
52
|
const pendingStarts = new Set<AbortController>();
|
|
53
|
+
session.verifyCommandSupervisionCapability = async () => {
|
|
54
|
+
if (!control.verifySupervisionCapability)
|
|
55
|
+
throw new Error("Modal instance lacks supervised command capability verification");
|
|
56
|
+
return await control.verifySupervisionCapability();
|
|
57
|
+
};
|
|
58
|
+
session.verifyMaterializedPath = (path, workdir) =>
|
|
59
|
+
verifyModalMaterializedPath(control, path, workdir, pendingStarts);
|
|
44
60
|
const entries = new Map<number, Entry>();
|
|
45
61
|
// Current SDK setup commands are not retained/admitted commands. Give their
|
|
46
62
|
// live observer handles a disjoint, adapter-local range; never use a missing
|
|
@@ -48,6 +64,64 @@ export function installModalCommandSession(
|
|
|
48
64
|
const setupHandles = new Map<number, number>();
|
|
49
65
|
let nextSetupHandle = MAX_PROVIDER_COMMAND_HANDLE + 1;
|
|
50
66
|
const receipts = new Map<string, { handle: number; page: ProviderCommandOutput }>();
|
|
67
|
+
const atomicallyCaptured = new WeakSet<ProviderCommandOutput>();
|
|
68
|
+
|
|
69
|
+
const supervise = async (entry: Entry, release = false): Promise<void> => {
|
|
70
|
+
if (entry.command.kind !== "modal-router-v1" || !entry.command.supervision) return;
|
|
71
|
+
const persistence = entry.persistence;
|
|
72
|
+
if (
|
|
73
|
+
!persistence?.recordSupervisionReceipt ||
|
|
74
|
+
!persistence.loadSupervisionReceipt ||
|
|
75
|
+
!persistence.cancellationRequested ||
|
|
76
|
+
!control.supervisionControl
|
|
77
|
+
)
|
|
78
|
+
throw new Error("Supervised command requires durable control persistence");
|
|
79
|
+
let receipt = await persistence.loadSupervisionReceipt();
|
|
80
|
+
if (!receipt) {
|
|
81
|
+
const action = (await persistence.cancellationRequested())
|
|
82
|
+
? "cancel"
|
|
83
|
+
: release
|
|
84
|
+
? "release"
|
|
85
|
+
: "status";
|
|
86
|
+
const observation = await control.supervisionControl(entry.command, action);
|
|
87
|
+
receipt = observation.receipt ?? null;
|
|
88
|
+
if (receipt) await persistence.recordSupervisionReceipt(receipt);
|
|
89
|
+
}
|
|
90
|
+
if (receipt) {
|
|
91
|
+
// Lost ACK responses are safe to retry. A dead control socket is not
|
|
92
|
+
// terminal proof: the subsequent authenticated provider read still must
|
|
93
|
+
// prove exit and commit every output byte.
|
|
94
|
+
await control
|
|
95
|
+
.supervisionControl(entry.command, "ack", receipt.receiptId)
|
|
96
|
+
.catch(() => undefined);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const captureRouterPage = async (entry: Entry, page: ProviderCommandOutput): Promise<boolean> => {
|
|
101
|
+
if (
|
|
102
|
+
page.command.kind !== "modal-router-v1" ||
|
|
103
|
+
!page.expected ||
|
|
104
|
+
!entry.persistence?.captureRouterPage
|
|
105
|
+
)
|
|
106
|
+
throw new Error("Modal byte-offset output requires atomic retained capture");
|
|
107
|
+
const result = await entry.persistence.captureRouterPage({
|
|
108
|
+
expected: page.expected,
|
|
109
|
+
command: page.command,
|
|
110
|
+
stdout: page.chunks
|
|
111
|
+
.filter((chunk) => chunk.stream === "stdout")
|
|
112
|
+
.map((chunk) => chunk.text)
|
|
113
|
+
.join(""),
|
|
114
|
+
stderr: page.chunks
|
|
115
|
+
.filter((chunk) => chunk.stream === "stderr")
|
|
116
|
+
.map((chunk) => chunk.text)
|
|
117
|
+
.join(""),
|
|
118
|
+
});
|
|
119
|
+
if (!sameExecution(entry.command, result.command))
|
|
120
|
+
throw new Error("Modal output capture changed execution identity");
|
|
121
|
+
entry.command = result.command;
|
|
122
|
+
if (result.captured) atomicallyCaptured.add(page);
|
|
123
|
+
return result.captured;
|
|
124
|
+
};
|
|
51
125
|
|
|
52
126
|
const setupPage = (raw: string, handle: number, sdkHandle: number): string => {
|
|
53
127
|
const banner = parseExecResponseBanner(raw);
|
|
@@ -97,8 +171,30 @@ export function installModalCommandSession(
|
|
|
97
171
|
if (!retained || !sameExecution(entry.command, retained))
|
|
98
172
|
throw new Error("Original Modal command identity is unavailable");
|
|
99
173
|
entry.command = retained;
|
|
174
|
+
// Output observation stays available when control/proof persistence is
|
|
175
|
+
// unavailable. Terminal exposure below remains fenced by durable proof.
|
|
176
|
+
await supervise(entry).catch(() => undefined);
|
|
177
|
+
}
|
|
178
|
+
let page = await control.read(entry.command, yieldTimeMs, signal);
|
|
179
|
+
if (entry.persistence && page.command.kind === "modal-router-v1") {
|
|
180
|
+
// A stale concurrent observer must reread from the committed cursor before
|
|
181
|
+
// exposing a terminal receipt; its uncommitted tail is not settlement proof.
|
|
182
|
+
let captured = false;
|
|
183
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
184
|
+
if (await captureRouterPage(entry, page)) {
|
|
185
|
+
captured = true;
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
if (attempt < 2) page = await control.read(entry.command, yieldTimeMs, signal);
|
|
189
|
+
}
|
|
190
|
+
if (!captured) throw new Error("Modal command output cursor changed during bounded capture");
|
|
191
|
+
}
|
|
192
|
+
if (page.command.kind === "modal-router-v1" && page.command.supervision) {
|
|
193
|
+
const receipt = await entry.persistence?.loadSupervisionReceipt?.();
|
|
194
|
+
// The supervisor exits 0 after ACK; that is provider terminal evidence,
|
|
195
|
+
// not the original shell result. Retain both without conflating them.
|
|
196
|
+
page = { ...page, exitCode: receipt && page.exitCode === 0 ? receipt.leaderExitCode : null };
|
|
100
197
|
}
|
|
101
|
-
const page = await control.read(entry.command, yieldTimeMs, signal);
|
|
102
198
|
// The receipt is generated here, not parsed from command output. Its only
|
|
103
199
|
// purpose is correlating this return value with a trusted in-memory page.
|
|
104
200
|
return formatPage(handle, page, maxOutputTokens);
|
|
@@ -122,8 +218,17 @@ export function installModalCommandSession(
|
|
|
122
218
|
const cancellation = new AbortController();
|
|
123
219
|
pendingStarts.add(cancellation);
|
|
124
220
|
try {
|
|
125
|
-
const
|
|
221
|
+
const command = await control.start(args, cancellation.signal);
|
|
222
|
+
// Pre-dispatch reservation can already bind protected persistence.
|
|
223
|
+
const entry = entries.get(handle) ?? { command };
|
|
224
|
+
if (!sameExecution(entry.command, command))
|
|
225
|
+
throw new Error("Reserved Modal command changed during dispatch");
|
|
126
226
|
entries.set(handle, entry);
|
|
227
|
+
if (entry.command.kind === "modal-router-v1" && entry.command.supervision) {
|
|
228
|
+
// The launch cannot run user code until the routing layer commits
|
|
229
|
+
// initial retention and calls releaseSupervisedCommand.
|
|
230
|
+
return formatPage(handle, { command: entry.command, chunks: [], exitCode: null });
|
|
231
|
+
}
|
|
127
232
|
try {
|
|
128
233
|
return await read(
|
|
129
234
|
handle,
|
|
@@ -160,6 +265,25 @@ export function installModalCommandSession(
|
|
|
160
265
|
const entry = entries.get(handle);
|
|
161
266
|
return entry ? structuredClone(entry.command) : null;
|
|
162
267
|
};
|
|
268
|
+
session.releaseSupervisedCommand = async (handle) => {
|
|
269
|
+
const entry = entries.get(handle);
|
|
270
|
+
if (!entry || entry.command.kind !== "modal-router-v1" || !entry.command.supervision) return;
|
|
271
|
+
const retained = await entry.persistence?.load();
|
|
272
|
+
if (!retained || !sameExecution(entry.command, retained))
|
|
273
|
+
throw new Error("Supervisor release requires committed initial retention");
|
|
274
|
+
entry.command = retained;
|
|
275
|
+
await supervise(entry, true);
|
|
276
|
+
};
|
|
277
|
+
session.cancelSupervisedCommand = async (handle, reason) => {
|
|
278
|
+
const entry = entries.get(handle);
|
|
279
|
+
if (!entry || entry.command.kind !== "modal-router-v1" || !entry.command.supervision)
|
|
280
|
+
return false;
|
|
281
|
+
if (!entry.persistence?.requestCancellation)
|
|
282
|
+
throw new Error("Supervisor cancellation requires durable intent");
|
|
283
|
+
await entry.persistence.requestCancellation(reason);
|
|
284
|
+
await supervise(entry);
|
|
285
|
+
return true;
|
|
286
|
+
};
|
|
163
287
|
session.bindProviderCommand = (handle, command, persistence) => {
|
|
164
288
|
if (!Number.isSafeInteger(handle) || handle <= 0 || handle > MAX_PROVIDER_COMMAND_HANDLE)
|
|
165
289
|
throw new Error("Invalid retained Modal command handle");
|
|
@@ -170,6 +294,55 @@ export function installModalCommandSession(
|
|
|
170
294
|
};
|
|
171
295
|
session.getProviderCommandOutput = (result) =>
|
|
172
296
|
typeof result === "string" ? (receipts.get(result)?.page ?? null) : null;
|
|
297
|
+
session.captureCommandOutput = async (result) => {
|
|
298
|
+
const receipt = receipts.get(result);
|
|
299
|
+
if (!receipt || receipt.page.command.kind !== "modal-router-v1") return false;
|
|
300
|
+
if (
|
|
301
|
+
receipt.page.command.supervision &&
|
|
302
|
+
!receipt.page.expected &&
|
|
303
|
+
receipt.page.exitCode === null &&
|
|
304
|
+
receipt.page.chunks.length === 0
|
|
305
|
+
) {
|
|
306
|
+
// Idle-launch receipt carries identity only; there are no bytes to ACK.
|
|
307
|
+
receipts.delete(result);
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
if (atomicallyCaptured.has(receipt.page)) {
|
|
311
|
+
receipts.delete(result);
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
const entry = entries.get(receipt.handle);
|
|
315
|
+
if (!entry?.persistence?.captureRouterPage || !receipt.page.expected)
|
|
316
|
+
throw new Error("Modal byte-offset output requires atomic retained capture");
|
|
317
|
+
if (!(await captureRouterPage(entry, receipt.page))) {
|
|
318
|
+
const target = receipt.page.command;
|
|
319
|
+
const covered = () =>
|
|
320
|
+
entry.command.kind === "modal-router-v1" &&
|
|
321
|
+
(["stdout", "stderr"] as const).every((stream) => {
|
|
322
|
+
const current = entry.command.streams[stream];
|
|
323
|
+
// This narrowing stays inside the discriminator's synchronous scope.
|
|
324
|
+
if (!("byteOffset" in current)) return false;
|
|
325
|
+
const wanted = target.streams[stream];
|
|
326
|
+
return (
|
|
327
|
+
current.byteOffset >= wanted.byteOffset &&
|
|
328
|
+
(!wanted.eof || current.eof) &&
|
|
329
|
+
(wanted.exitCode === null || current.exitCode === wanted.exitCode)
|
|
330
|
+
);
|
|
331
|
+
});
|
|
332
|
+
for (let attempt = 0; !covered() && attempt < 3; attempt++) {
|
|
333
|
+
// Initial promotion can race reconciliation before background adoption.
|
|
334
|
+
// Re-read, never re-execute; only a committed cursor covering the original
|
|
335
|
+
// page makes that page's terminal proof eligible for settlement.
|
|
336
|
+
const refreshedReceipt = await read(receipt.handle, entry, 250);
|
|
337
|
+
const refreshed = receipts.get(refreshedReceipt);
|
|
338
|
+
if (refreshed && atomicallyCaptured.has(refreshed.page)) receipts.delete(refreshedReceipt);
|
|
339
|
+
}
|
|
340
|
+
if (!covered())
|
|
341
|
+
throw new Error("Initial Modal output tail remains uncommitted after bounded capture");
|
|
342
|
+
}
|
|
343
|
+
receipts.delete(result);
|
|
344
|
+
return true;
|
|
345
|
+
};
|
|
173
346
|
session.acknowledgeCommandOutput = async (result) => {
|
|
174
347
|
const receipt = receipts.get(result);
|
|
175
348
|
if (!receipt) return;
|
|
@@ -197,7 +370,7 @@ export function installModalCommandSession(
|
|
|
197
370
|
const retained = await entry.persistence.load();
|
|
198
371
|
if (!retained || !sameExecution(entry.command, retained))
|
|
199
372
|
throw new Error("Original Modal command identity is unavailable");
|
|
200
|
-
const index = await entry.persistence.reserveInput();
|
|
373
|
+
const index = await entry.persistence.reserveInput(Buffer.byteLength(args.chars));
|
|
201
374
|
await control.write(retained, args.chars, index);
|
|
202
375
|
}
|
|
203
376
|
return read(args.sessionId, entry, args.yieldTimeMs ?? 250, args.maxOutputTokens);
|