@effect-agent/testing 0.1.0-beta.6 → 0.1.0-beta.61
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/Certification.d.mts +105 -0
- package/dist/Certification.mjs +590 -0
- package/dist/Certification.mjs.map +1 -0
- package/dist/Chaos.d.mts +126 -0
- package/dist/Chaos.mjs +755 -0
- package/dist/Chaos.mjs.map +1 -0
- package/dist/CodeExecutorConformance.d.mts +33 -0
- package/dist/CodeExecutorConformance.mjs +231 -0
- package/dist/CodeExecutorConformance.mjs.map +1 -0
- package/dist/CodeExecutorSubstitute.d.mts +21 -0
- package/dist/CodeExecutorSubstitute.mjs +364 -0
- package/dist/CodeExecutorSubstitute.mjs.map +1 -0
- package/dist/DocsResearcher.d.mts +270 -0
- package/dist/DocsResearcher.mjs +490 -0
- package/dist/DocsResearcher.mjs.map +1 -0
- package/dist/ScriptedModel-DAvxIiud.d.mts +220 -0
- package/dist/ScriptedModel.d.mts +2 -0
- package/dist/ScriptedModel.mjs +155 -0
- package/dist/ScriptedModel.mjs.map +1 -0
- package/dist/TravelPlanner.d.mts +1665 -0
- package/dist/TravelPlanner.mjs +1968 -0
- package/dist/TravelPlanner.mjs.map +1 -0
- package/dist/deterministic-layers-Eka0fMZq.mjs +358 -0
- package/dist/deterministic-layers-Eka0fMZq.mjs.map +1 -0
- package/dist/index.d.mts +2 -3378
- package/dist/index.mjs +2 -4766
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/package.json +1 -48
- package/src/{certification.ts → Certification.ts} +251 -115
- package/src/{chaos.ts → Chaos.ts} +246 -122
- package/src/{code-executor-conformance.ts → CodeExecutorConformance.ts} +29 -6
- package/src/{code-executor-substitute.ts → CodeExecutorSubstitute.ts} +104 -42
- package/src/{fixtures/docs-researcher/index.ts → DocsResearcher.ts} +68 -5
- package/src/{scripted-model.ts → ScriptedModel.ts} +22 -25
- package/src/TravelPlanner.ts +232 -0
- package/src/fixtures/docs-researcher/definition.ts +19 -10
- package/src/fixtures/docs-researcher/harness.ts +41 -30
- package/src/fixtures/docs-researcher/mcp.ts +49 -3
- package/src/fixtures/travel-planner/definition.ts +15 -2
- package/src/fixtures/travel-planner/deterministic-layers.ts +47 -4
- package/src/fixtures/travel-planner/phase2.ts +4 -3
- package/src/fixtures/travel-planner/phase3.ts +18 -37
- package/src/fixtures/travel-planner/phase4.ts +23 -36
- package/src/fixtures/travel-planner/phase5.ts +38 -41
- package/src/fixtures/travel-planner/phase6.ts +192 -84
- package/src/fixtures/travel-planner/phase7.ts +4 -102
- package/src/fixtures/travel-planner/scenarios.ts +3 -4
- package/src/fixtures/travel-planner/subagents-durable.ts +33 -57
- package/src/fixtures/travel-planner/subagents.ts +35 -13
- package/src/index.ts +1 -11
- package/dist/index.mjs.map +0 -1
- package/src/code-executor-conformance.d.ts +0 -30
- package/src/fixtures/travel-planner/index.ts +0 -11
- package/src/fixtures/warehouse/index.ts +0 -412
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
|
-
CodeExecutionError,
|
|
3
2
|
CodeExecutionHost,
|
|
4
3
|
CodeExecutionLimits,
|
|
5
4
|
CodeExecutionNamespace,
|
|
6
5
|
CodeExecutionRequest,
|
|
7
|
-
CodeExecutionResult,
|
|
8
6
|
CodeExecutor,
|
|
9
|
-
CodeHostCall,
|
|
10
7
|
CodeHostCallFailure,
|
|
11
|
-
CodeHostCallResult,
|
|
12
8
|
CodeHostCallSuccess,
|
|
9
|
+
type CodeExecutionError,
|
|
10
|
+
type CodeExecutionResult,
|
|
11
|
+
type CodeHostCall,
|
|
12
|
+
type CodeHostCallResult,
|
|
13
|
+
} from "@effect-agent/sandbox/CodeExecutor";
|
|
14
|
+
import {
|
|
13
15
|
NetworkAllowlist,
|
|
14
16
|
NetworkDisabled,
|
|
15
|
-
SandboxImplementation,
|
|
16
|
-
} from "@effect-agent/sandbox";
|
|
17
|
+
type SandboxImplementation,
|
|
18
|
+
} from "@effect-agent/sandbox/Sandbox";
|
|
17
19
|
import { Deferred, Duration, Effect, Fiber, Schema } from "effect";
|
|
18
20
|
|
|
19
21
|
/**
|
|
@@ -88,12 +90,14 @@ const respondingHost = (
|
|
|
88
90
|
respond: (call: CodeHostCall) => CodeHostCallResult,
|
|
89
91
|
): { readonly host: CodeExecutionHost["Service"]; readonly calls: Array<CodeHostCall> } => {
|
|
90
92
|
const calls: Array<CodeHostCall> = [];
|
|
93
|
+
|
|
91
94
|
return {
|
|
92
95
|
calls,
|
|
93
96
|
host: {
|
|
94
97
|
call: (call) =>
|
|
95
98
|
Effect.sync(() => {
|
|
96
99
|
calls.push(call);
|
|
100
|
+
|
|
97
101
|
return respond(call);
|
|
98
102
|
}),
|
|
99
103
|
},
|
|
@@ -106,6 +110,7 @@ const runPass = (
|
|
|
106
110
|
): Effect.Effect<CodeExecutionResult, CodeExecutionError, CodeExecutor> =>
|
|
107
111
|
Effect.gen(function* () {
|
|
108
112
|
const executor = yield* CodeExecutor;
|
|
113
|
+
|
|
109
114
|
return yield* executor
|
|
110
115
|
.execute(request)
|
|
111
116
|
.pipe(Effect.provideService(CodeExecutionHost, CodeExecutionHost.of(host)));
|
|
@@ -134,6 +139,7 @@ const expectSuccess = (
|
|
|
134
139
|
),
|
|
135
140
|
Effect.flatMap((result) => {
|
|
136
141
|
const complaint = check(result);
|
|
142
|
+
|
|
137
143
|
return complaint === undefined ? Effect.void : Effect.fail(violation(caseName, complaint));
|
|
138
144
|
}),
|
|
139
145
|
);
|
|
@@ -157,6 +163,7 @@ const expectFailure = (
|
|
|
157
163
|
);
|
|
158
164
|
}
|
|
159
165
|
const complaint = check?.(error);
|
|
166
|
+
|
|
160
167
|
return complaint === undefined ? Effect.void : Effect.fail(violation(caseName, complaint));
|
|
161
168
|
}),
|
|
162
169
|
);
|
|
@@ -165,6 +172,7 @@ export const codeExecutorConformanceCases = (
|
|
|
165
172
|
options: CodeExecutorConformanceOptions,
|
|
166
173
|
): ReadonlyArray<CodeExecutorConformanceCase> => {
|
|
167
174
|
const posture = options.implementation;
|
|
175
|
+
|
|
168
176
|
return [
|
|
169
177
|
{
|
|
170
178
|
name: "TEST-015 executes bounded JSON computation and returns the program value",
|
|
@@ -184,9 +192,11 @@ export const codeExecutorConformanceCases = (
|
|
|
184
192
|
name: "CAP-015 reports its isolation posture honestly in results and errors",
|
|
185
193
|
run: Effect.gen(function* () {
|
|
186
194
|
const caseName = "CAP-015 reports its isolation posture honestly in results and errors";
|
|
195
|
+
|
|
187
196
|
const result = yield* runPass(makeRequest("async () => 1"), unusedHost).pipe(
|
|
188
197
|
Effect.mapError((error) => violation(caseName, `expected success, got ${error._tag}`)),
|
|
189
198
|
);
|
|
199
|
+
|
|
190
200
|
if (
|
|
191
201
|
result.implementation.isolation !== posture.isolation ||
|
|
192
202
|
result.implementation.identity !== posture.identity
|
|
@@ -196,10 +206,12 @@ export const codeExecutorConformanceCases = (
|
|
|
196
206
|
`result posture ${preview(result.implementation)} does not match the declared ${preview(posture)}`,
|
|
197
207
|
);
|
|
198
208
|
}
|
|
209
|
+
|
|
199
210
|
const error = yield* runPass(makeRequest("async () => {"), unusedHost).pipe(
|
|
200
211
|
Effect.flip,
|
|
201
212
|
Effect.mapError(() => violation(caseName, "expected the invalid-source pass to fail")),
|
|
202
213
|
);
|
|
214
|
+
|
|
203
215
|
// Every expected execution failure carries the posture; an adapter
|
|
204
216
|
// omitting the field must fail this case, not slip past a probe.
|
|
205
217
|
if (
|
|
@@ -219,11 +231,13 @@ export const codeExecutorConformanceCases = (
|
|
|
219
231
|
run: Effect.gen(function* () {
|
|
220
232
|
const caseName =
|
|
221
233
|
"TEST-015 routes host calls through the CodeExecutionHost in program order";
|
|
234
|
+
|
|
222
235
|
const { host, calls } = respondingHost((call) =>
|
|
223
236
|
call.method === "query"
|
|
224
237
|
? CodeHostCallSuccess.make({ value: { rows: [1, 2, 3] } })
|
|
225
238
|
: CodeHostCallSuccess.make({ value: 3 }),
|
|
226
239
|
);
|
|
240
|
+
|
|
227
241
|
const result = yield* runPass(
|
|
228
242
|
makeRequest(
|
|
229
243
|
"async () => { const q = await warehouse.query({ sql: 'select' }); const c = await warehouse.count({ table: 't' }); return { rows: q.rows, count: c }; }",
|
|
@@ -235,10 +249,12 @@ export const codeExecutorConformanceCases = (
|
|
|
235
249
|
violation(caseName, `expected success, got ${error._tag}: ${preview(error)}`),
|
|
236
250
|
),
|
|
237
251
|
);
|
|
252
|
+
|
|
238
253
|
if (JSON.stringify(result.value) !== JSON.stringify({ rows: [1, 2, 3], count: 3 })) {
|
|
239
254
|
return yield* violation(caseName, `unexpected value ${preview(result.value)}`);
|
|
240
255
|
}
|
|
241
256
|
const observed = calls.map((call) => `${call.namespace}.${call.method}`);
|
|
257
|
+
|
|
242
258
|
if (JSON.stringify(observed) !== JSON.stringify(["warehouse.query", "warehouse.count"])) {
|
|
243
259
|
return yield* violation(caseName, `unexpected host call order ${preview(observed)}`);
|
|
244
260
|
}
|
|
@@ -376,6 +392,7 @@ export const codeExecutorConformanceCases = (
|
|
|
376
392
|
run: Effect.gen(function* () {
|
|
377
393
|
const caseName = "TEST-015 fails typed when host calls exceed the executor cap";
|
|
378
394
|
const { host, calls } = respondingHost(() => CodeHostCallSuccess.make({ value: null }));
|
|
395
|
+
|
|
379
396
|
yield* expectFailure(
|
|
380
397
|
caseName,
|
|
381
398
|
makeRequest(
|
|
@@ -404,6 +421,8 @@ export const codeExecutorConformanceCases = (
|
|
|
404
421
|
"TEST-015 fails typed on a host outcome outside the protocol schema",
|
|
405
422
|
makeRequest("async () => warehouse.query({})", { namespaces: [warehouseNamespace] }),
|
|
406
423
|
{
|
|
424
|
+
// Deliberately violate the compile-time host contract to verify that an adapter
|
|
425
|
+
// independently decodes the runtime protocol boundary.
|
|
407
426
|
call: () => Effect.succeed({ bogus: true } as unknown as CodeHostCallResult),
|
|
408
427
|
},
|
|
409
428
|
"CodeExecutionProtocolError",
|
|
@@ -460,6 +479,7 @@ export const codeExecutorConformanceCases = (
|
|
|
460
479
|
const caseName = "TEST-015 interruption reaches in-flight host calls and pass teardown";
|
|
461
480
|
const started = yield* Deferred.make<void>();
|
|
462
481
|
const witness = { hostCallInterrupted: false };
|
|
482
|
+
|
|
463
483
|
const host: CodeExecutionHost["Service"] = {
|
|
464
484
|
call: () =>
|
|
465
485
|
Deferred.succeed(started, undefined).pipe(
|
|
@@ -471,16 +491,19 @@ export const codeExecutorConformanceCases = (
|
|
|
471
491
|
),
|
|
472
492
|
),
|
|
473
493
|
};
|
|
494
|
+
|
|
474
495
|
const fiber = yield* runPass(
|
|
475
496
|
makeRequest("async () => warehouse.query({})", { namespaces: [warehouseNamespace] }),
|
|
476
497
|
host,
|
|
477
498
|
).pipe(Effect.forkChild);
|
|
499
|
+
|
|
478
500
|
// Guard against a broken adapter that settles the pass without ever
|
|
479
501
|
// reaching the host: the case must report a violation, not hang.
|
|
480
502
|
const winner = yield* Effect.raceFirst(
|
|
481
503
|
Deferred.await(started).pipe(Effect.as("started" as const)),
|
|
482
504
|
Fiber.join(fiber).pipe(Effect.exit, Effect.as("exited" as const)),
|
|
483
505
|
);
|
|
506
|
+
|
|
484
507
|
if (winner === "exited") {
|
|
485
508
|
return yield* violation(
|
|
486
509
|
caseName,
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CodeExecutionHost,
|
|
3
|
-
CodeExecutionLimits,
|
|
4
|
-
CodeExecutionNamespace,
|
|
5
3
|
CodeExecutionProtocolError,
|
|
6
|
-
CodeExecutionRequest,
|
|
7
4
|
CodeExecutionResourceUse,
|
|
8
5
|
CodeExecutionResult,
|
|
9
6
|
CodeExecutionTimeoutError,
|
|
@@ -16,8 +13,11 @@ import {
|
|
|
16
13
|
CodeOutputLimitError,
|
|
17
14
|
CodeProgramFailedError,
|
|
18
15
|
CodeSourceError,
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
type CodeExecutionLimits,
|
|
17
|
+
type CodeExecutionNamespace,
|
|
18
|
+
type CodeExecutionRequest,
|
|
19
|
+
} from "@effect-agent/sandbox/CodeExecutor";
|
|
20
|
+
import { SandboxImplementation } from "@effect-agent/sandbox/Sandbox";
|
|
21
21
|
import { Clock, Duration, Effect, Fiber, Layer, Option, Queue, Schema } from "effect";
|
|
22
22
|
|
|
23
23
|
/**
|
|
@@ -91,6 +91,7 @@ const formatLogValue = (value: unknown): string => {
|
|
|
91
91
|
if (typeof value === "string") {
|
|
92
92
|
return value;
|
|
93
93
|
}
|
|
94
|
+
|
|
94
95
|
return JSON.stringify(value) ?? String(value);
|
|
95
96
|
} catch {
|
|
96
97
|
try {
|
|
@@ -104,17 +105,21 @@ const formatLogValue = (value: unknown): string => {
|
|
|
104
105
|
const makeConsole = (capture: LogCapture, limits: CodeExecutionLimits) => {
|
|
105
106
|
const write = (...values: ReadonlyArray<unknown>): void => {
|
|
106
107
|
const joined = values.map(formatLogValue).join(" ");
|
|
108
|
+
|
|
107
109
|
const line =
|
|
108
110
|
joined.length > MAX_LOG_LINE_CHARACTERS
|
|
109
111
|
? `${joined.slice(0, MAX_LOG_LINE_CHARACTERS - 1)}…`
|
|
110
112
|
: joined;
|
|
113
|
+
|
|
111
114
|
const bytes = utf8ByteLength(line);
|
|
115
|
+
|
|
112
116
|
if (capture.lines.length >= MAX_LOG_LINES || capture.bytes + bytes > limits.maxLogBytes) {
|
|
113
117
|
throw new LogLimitSignal(capture.bytes + bytes);
|
|
114
118
|
}
|
|
115
119
|
capture.lines.push(line);
|
|
116
120
|
capture.bytes += bytes;
|
|
117
121
|
};
|
|
122
|
+
|
|
118
123
|
return { debug: write, error: write, info: write, log: write, warn: write };
|
|
119
124
|
};
|
|
120
125
|
|
|
@@ -131,18 +136,21 @@ const buildNamespaceObject = (
|
|
|
131
136
|
offer: (pending: PendingHostCall) => void,
|
|
132
137
|
): Record<string, unknown> => {
|
|
133
138
|
const methods: Record<string, unknown> = {};
|
|
139
|
+
|
|
134
140
|
for (const method of namespace.methods) {
|
|
135
141
|
methods[method] = (argument: unknown) =>
|
|
136
142
|
new Promise((resolve, reject) => {
|
|
137
143
|
offer({ namespace: namespace.name, method, argument, resolve, reject });
|
|
138
144
|
});
|
|
139
145
|
}
|
|
146
|
+
|
|
140
147
|
return methods;
|
|
141
148
|
};
|
|
142
149
|
|
|
143
150
|
const boundedText = (value: unknown): string => {
|
|
144
151
|
try {
|
|
145
152
|
const text = value instanceof Error ? `${value.name}: ${value.message}` : formatLogValue(value);
|
|
153
|
+
|
|
146
154
|
return text.slice(0, MAX_THROWN_CHARACTERS);
|
|
147
155
|
} catch {
|
|
148
156
|
return "[unserializable thrown value]";
|
|
@@ -158,24 +166,44 @@ const safeDecodeJson = (value: unknown): Option.Option<Schema.Json> => {
|
|
|
158
166
|
}
|
|
159
167
|
};
|
|
160
168
|
|
|
169
|
+
const decodeJsonText = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Json));
|
|
170
|
+
|
|
171
|
+
/** Own serialized result text before bounding and decoding its detached JSON snapshot. */
|
|
172
|
+
const serializeJson = (value: unknown): Option.Option<string> => {
|
|
173
|
+
const decoded = safeDecodeJson(value);
|
|
174
|
+
|
|
175
|
+
if (Option.isNone(decoded)) return Option.none();
|
|
176
|
+
try {
|
|
177
|
+
return Option.fromUndefinedOr(JSON.stringify(decoded.value));
|
|
178
|
+
} catch {
|
|
179
|
+
return Option.none();
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
161
183
|
const boundedThrown = (value: unknown): Schema.Json => {
|
|
162
184
|
const decoded = safeDecodeJson(value);
|
|
185
|
+
|
|
163
186
|
if (Option.isSome(decoded)) {
|
|
164
187
|
try {
|
|
165
188
|
const encoded = JSON.stringify(decoded.value);
|
|
189
|
+
|
|
166
190
|
if (encoded !== undefined && encoded.length <= MAX_THROWN_CHARACTERS) {
|
|
167
|
-
|
|
191
|
+
const snapshot = decodeJsonText(encoded);
|
|
192
|
+
|
|
193
|
+
if (Option.isSome(snapshot)) return snapshot.value;
|
|
168
194
|
}
|
|
169
195
|
} catch {
|
|
170
196
|
// fall through to the bounded string form
|
|
171
197
|
}
|
|
172
198
|
}
|
|
199
|
+
|
|
173
200
|
return boundedText(value);
|
|
174
201
|
};
|
|
175
202
|
|
|
176
203
|
const encodedJsonByteLength = (value: Schema.Json): number | undefined => {
|
|
177
204
|
try {
|
|
178
205
|
const encoded = JSON.stringify(value);
|
|
206
|
+
|
|
179
207
|
return encoded === undefined ? undefined : utf8ByteLength(encoded);
|
|
180
208
|
} catch {
|
|
181
209
|
return undefined;
|
|
@@ -213,6 +241,7 @@ const validateRequest = (
|
|
|
213
241
|
}
|
|
214
242
|
const reservedNames = new Set<string>([...shadowedGlobals, "console"]);
|
|
215
243
|
const seen = new Set<string>();
|
|
244
|
+
|
|
216
245
|
for (const namespace of request.namespaces) {
|
|
217
246
|
if (reservedNames.has(namespace.name) || seen.has(namespace.name)) {
|
|
218
247
|
return yield* CodeExecutorUnsupportedError.make({
|
|
@@ -224,6 +253,7 @@ const validateRequest = (
|
|
|
224
253
|
seen.add(namespace.name);
|
|
225
254
|
}
|
|
226
255
|
const sourceBytes = utf8ByteLength(request.source);
|
|
256
|
+
|
|
227
257
|
if (sourceBytes > request.limits.maxSourceBytes) {
|
|
228
258
|
return yield* CodeSourceError.make({
|
|
229
259
|
implementation: inProcessCodeExecutorImplementation,
|
|
@@ -246,6 +276,7 @@ const serveHostCalls = (
|
|
|
246
276
|
Effect.gen(function* () {
|
|
247
277
|
while (true) {
|
|
248
278
|
const pending = yield* Queue.take(queue);
|
|
279
|
+
|
|
249
280
|
counter.calls += 1;
|
|
250
281
|
if (counter.calls > limits.maxHostCalls) {
|
|
251
282
|
return yield* CodeHostCallLimitError.make({
|
|
@@ -255,11 +286,13 @@ const serveHostCalls = (
|
|
|
255
286
|
});
|
|
256
287
|
}
|
|
257
288
|
const argument = safeDecodeJson(pending.argument);
|
|
289
|
+
|
|
258
290
|
if (Option.isNone(argument)) {
|
|
259
291
|
pending.reject(new TypeError("host call arguments must be JSON values"));
|
|
260
292
|
continue;
|
|
261
293
|
}
|
|
262
294
|
const argumentBytes = encodedJsonByteLength(argument.value);
|
|
295
|
+
|
|
263
296
|
if (argumentBytes === undefined || argumentBytes > limits.maxHostCallArgumentBytes) {
|
|
264
297
|
return yield* CodeOutputLimitError.make({
|
|
265
298
|
implementation: inProcessCodeExecutorImplementation,
|
|
@@ -269,6 +302,7 @@ const serveHostCalls = (
|
|
|
269
302
|
logs: [...capture.lines],
|
|
270
303
|
});
|
|
271
304
|
}
|
|
305
|
+
|
|
272
306
|
const rawOutcome = yield* host.call(
|
|
273
307
|
CodeHostCall.make({
|
|
274
308
|
namespace: pending.namespace,
|
|
@@ -276,7 +310,9 @@ const serveHostCalls = (
|
|
|
276
310
|
argument: argument.value,
|
|
277
311
|
}),
|
|
278
312
|
);
|
|
313
|
+
|
|
279
314
|
const outcome = decodeHostOutcome(rawOutcome);
|
|
315
|
+
|
|
280
316
|
if (Option.isNone(outcome)) {
|
|
281
317
|
return yield* CodeExecutionProtocolError.make({
|
|
282
318
|
implementation: inProcessCodeExecutorImplementation,
|
|
@@ -288,6 +324,7 @@ const serveHostCalls = (
|
|
|
288
324
|
continue;
|
|
289
325
|
}
|
|
290
326
|
const resultBytes = encodedJsonByteLength(outcome.value.value);
|
|
327
|
+
|
|
291
328
|
if (resultBytes === undefined || resultBytes > limits.maxHostCallResultBytes) {
|
|
292
329
|
return yield* CodeOutputLimitError.make({
|
|
293
330
|
implementation: inProcessCodeExecutorImplementation,
|
|
@@ -306,27 +343,37 @@ const classifyProgramFailure = (
|
|
|
306
343
|
limits: CodeExecutionLimits,
|
|
307
344
|
capture: LogCapture,
|
|
308
345
|
): CodeOutputLimitError | CodeSourceError | CodeProgramFailedError => {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
}
|
|
346
|
+
let inner = thrown;
|
|
347
|
+
let reason: "threw" | "rejected" = "rejected";
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
const evaluationThrew = thrown instanceof EvaluationThrew;
|
|
351
|
+
|
|
352
|
+
inner = evaluationThrew ? thrown.inner : thrown;
|
|
353
|
+
if (inner instanceof LogLimitSignal) {
|
|
354
|
+
return CodeOutputLimitError.make({
|
|
355
|
+
implementation: inProcessCodeExecutorImplementation,
|
|
356
|
+
surface: "logs",
|
|
357
|
+
limit: limits.maxLogBytes,
|
|
358
|
+
observed: inner.observed,
|
|
359
|
+
logs: [...capture.lines],
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
if (inner instanceof NotAFunction) {
|
|
363
|
+
return CodeSourceError.make({
|
|
364
|
+
implementation: inProcessCodeExecutorImplementation,
|
|
365
|
+
reason: "not-a-function",
|
|
366
|
+
message: `The source expression evaluated to ${inner.actual}; it must evaluate to one async function`,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
// Async body throws become rejections: exception-like values read as `threw`,
|
|
370
|
+
// while plain values such as uncaught host failure envelopes read as `rejected`.
|
|
371
|
+
reason = evaluationThrew || inner instanceof Error ? "threw" : "rejected";
|
|
372
|
+
} catch {
|
|
373
|
+
// A program-owned Proxy can throw from instanceof's prototype lookup. Keep
|
|
374
|
+
// that failure inside the same guarded diagnostic boundary as its value.
|
|
325
375
|
}
|
|
326
|
-
|
|
327
|
-
// split is by value shape: exception-like values read as `threw`, plain
|
|
328
|
-
// rejection values (an uncaught host failure envelope) read as `rejected`.
|
|
329
|
-
const reason = thrown instanceof EvaluationThrew || inner instanceof Error ? "threw" : "rejected";
|
|
376
|
+
|
|
330
377
|
return CodeProgramFailedError.make({
|
|
331
378
|
implementation: inProcessCodeExecutorImplementation,
|
|
332
379
|
reason,
|
|
@@ -346,6 +393,9 @@ const executeInProcess: CodeExecutorExecute = Effect.fn("InProcessCodeExecutor.e
|
|
|
346
393
|
|
|
347
394
|
const factory = yield* Effect.try({
|
|
348
395
|
try: () =>
|
|
396
|
+
// This substitute intentionally evaluates authored test programs in-process and reports
|
|
397
|
+
// an `unisolated` posture. Real adapters own the security boundary and never use this path.
|
|
398
|
+
// oxlint-disable-next-line typescript/no-implied-eval
|
|
349
399
|
new Function(
|
|
350
400
|
...shadowedGlobals,
|
|
351
401
|
"console",
|
|
@@ -367,11 +417,13 @@ const executeInProcess: CodeExecutorExecute = Effect.fn("InProcessCodeExecutor.e
|
|
|
367
417
|
// rejected synchronously, so the queue stays bounded against hostile
|
|
368
418
|
// programs.
|
|
369
419
|
let issuedHostCalls = 0;
|
|
420
|
+
|
|
370
421
|
const namespaceObjects = request.namespaces.map((namespace) =>
|
|
371
422
|
buildNamespaceObject(namespace, (pending) => {
|
|
372
423
|
issuedHostCalls += 1;
|
|
373
424
|
if (issuedHostCalls > request.limits.maxHostCalls + 1) {
|
|
374
425
|
pending.reject(new Error(`host-call limit of ${request.limits.maxHostCalls} exceeded`));
|
|
426
|
+
|
|
375
427
|
return;
|
|
376
428
|
}
|
|
377
429
|
Queue.offerUnsafe(queue, pending);
|
|
@@ -385,6 +437,7 @@ const executeInProcess: CodeExecutorExecute = Effect.fn("InProcessCodeExecutor.e
|
|
|
385
437
|
const program = Effect.tryPromise({
|
|
386
438
|
try: async () => {
|
|
387
439
|
let candidate: unknown;
|
|
440
|
+
|
|
388
441
|
try {
|
|
389
442
|
candidate = factory(
|
|
390
443
|
...shadowedGlobals.map(() => undefined),
|
|
@@ -398,17 +451,20 @@ const executeInProcess: CodeExecutorExecute = Effect.fn("InProcessCodeExecutor.e
|
|
|
398
451
|
throw new EvaluationThrew(new NotAFunction(typeof candidate));
|
|
399
452
|
}
|
|
400
453
|
let outcome: unknown;
|
|
454
|
+
|
|
401
455
|
try {
|
|
402
|
-
outcome =
|
|
456
|
+
outcome = candidate();
|
|
403
457
|
} catch (cause) {
|
|
404
458
|
throw new EvaluationThrew(cause);
|
|
405
459
|
}
|
|
460
|
+
|
|
406
461
|
return await Promise.resolve(outcome);
|
|
407
462
|
},
|
|
408
463
|
catch: (thrown) => classifyProgramFailure(thrown, request.limits, capture),
|
|
409
464
|
});
|
|
410
465
|
|
|
411
466
|
const startedAt = yield* Clock.currentTimeMillis;
|
|
467
|
+
|
|
412
468
|
// The wall-clock deadline interrupts only at asynchronous suspension
|
|
413
469
|
// points: a synchronous runaway shares the host thread and cannot be
|
|
414
470
|
// stopped in-process — exactly why the platform CPU enforcement cases
|
|
@@ -428,6 +484,7 @@ const executeInProcess: CodeExecutorExecute = Effect.fn("InProcessCodeExecutor.e
|
|
|
428
484
|
}),
|
|
429
485
|
Effect.ensuring(Fiber.interrupt(server)),
|
|
430
486
|
);
|
|
487
|
+
|
|
431
488
|
const finishedAt = yield* Clock.currentTimeMillis;
|
|
432
489
|
|
|
433
490
|
// An unawaited burst can outrun the server: the program may return before
|
|
@@ -442,31 +499,36 @@ const executeInProcess: CodeExecutorExecute = Effect.fn("InProcessCodeExecutor.e
|
|
|
442
499
|
});
|
|
443
500
|
}
|
|
444
501
|
|
|
445
|
-
const
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
502
|
+
const nonJsonResult = () =>
|
|
503
|
+
CodeProgramFailedError.make({
|
|
504
|
+
implementation: inProcessCodeExecutorImplementation,
|
|
505
|
+
reason: "non-json-result",
|
|
506
|
+
thrown: null,
|
|
507
|
+
message: "The program must return a JSON value",
|
|
508
|
+
logs: [...capture.lines],
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
const encoded = serializeJson(returned);
|
|
512
|
+
|
|
513
|
+
if (Option.isNone(encoded)) return yield* nonJsonResult();
|
|
514
|
+
const resultBytes = utf8ByteLength(encoded.value);
|
|
515
|
+
|
|
516
|
+
if (resultBytes > request.limits.maxResultBytes) {
|
|
458
517
|
return yield* CodeOutputLimitError.make({
|
|
459
518
|
implementation: inProcessCodeExecutorImplementation,
|
|
460
519
|
surface: "result",
|
|
461
520
|
limit: request.limits.maxResultBytes,
|
|
462
|
-
observed: resultBytes
|
|
521
|
+
observed: resultBytes,
|
|
463
522
|
logs: [...capture.lines],
|
|
464
523
|
});
|
|
465
524
|
}
|
|
525
|
+
const decoded = decodeJsonText(encoded.value);
|
|
526
|
+
|
|
527
|
+
if (Option.isNone(decoded)) return yield* nonJsonResult();
|
|
466
528
|
|
|
467
529
|
return CodeExecutionResult.make({
|
|
468
530
|
implementation: inProcessCodeExecutorImplementation,
|
|
469
|
-
value,
|
|
531
|
+
value: decoded.value,
|
|
470
532
|
logs: [...capture.lines],
|
|
471
533
|
resourceUse: CodeExecutionResourceUse.make({
|
|
472
534
|
wallTime: Duration.millis(Math.max(0, finishedAt - startedAt)),
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
+
/** Shared Docs Researcher definitions and MCP delegation fixtures. */
|
|
2
|
+
|
|
1
3
|
// ---------------------------------------------------------------------------
|
|
2
4
|
// Docs Researcher fixture (P7 internal agent #3): S2 durable delegation on DN
|
|
3
|
-
// with MCP-discovered content tools. See definition
|
|
5
|
+
// with MCP-discovered content tools. See fixtures/docs-researcher/{definition,mcp,harness}.ts.
|
|
4
6
|
//
|
|
5
7
|
// Authoring friction note (WP7 input; real observations from writing this
|
|
6
8
|
// agent):
|
|
7
9
|
//
|
|
8
10
|
// 1. MCP discovery and Agent authoring do not meet in the type system. The
|
|
9
11
|
// connector's validated Toolkit arrives as `Toolkit.Any`, while
|
|
10
|
-
// `Agent.
|
|
12
|
+
// `Agent.make` needs the statically typed toolkit — so the binding
|
|
11
13
|
// between "what discovery served" and "what the child was authored
|
|
12
14
|
// against" had to be re-proved by hand (`assertDiscoveryMatchesAuthoredToolkit`
|
|
13
15
|
// re-deriving `Tool.getJsonSchema` on both sides). A framework helper that
|
|
@@ -29,6 +31,67 @@
|
|
|
29
31
|
// exactly where a reviewer looks for them.
|
|
30
32
|
// ---------------------------------------------------------------------------
|
|
31
33
|
|
|
32
|
-
export
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
export {
|
|
35
|
+
ResearchDocumentId,
|
|
36
|
+
BoundedSummary,
|
|
37
|
+
DocumentQuery,
|
|
38
|
+
ResearchDocument,
|
|
39
|
+
DocumentUnavailable,
|
|
40
|
+
DocumentLibrary,
|
|
41
|
+
FetchDocument,
|
|
42
|
+
DocContentToolkit,
|
|
43
|
+
docContentToolkitLayer,
|
|
44
|
+
docsDocumentBodySecret,
|
|
45
|
+
researchCorpusDocumentIds,
|
|
46
|
+
researchDocumentLookup,
|
|
47
|
+
researchDocumentFor,
|
|
48
|
+
documentBodyPhrase,
|
|
49
|
+
SummaryBrief,
|
|
50
|
+
DocumentSummary,
|
|
51
|
+
documentSummaryFor,
|
|
52
|
+
encodedDocumentSummary,
|
|
53
|
+
DocSummarizer,
|
|
54
|
+
SummaryRequest,
|
|
55
|
+
SummaryFinding,
|
|
56
|
+
DocumentSummaryFailed,
|
|
57
|
+
documentSummaryPolicy,
|
|
58
|
+
delegateDocumentSummary,
|
|
59
|
+
mapSummaryChildFailure,
|
|
60
|
+
docsSummarizerDigestStrings,
|
|
61
|
+
docsSummaryHandlersLayer,
|
|
62
|
+
ResearchRequest,
|
|
63
|
+
ResearchDigest,
|
|
64
|
+
docsCoordinatorConfidentialMarker,
|
|
65
|
+
docsMissionConfidentialMarker,
|
|
66
|
+
DocsResearcherToolkit,
|
|
67
|
+
DocsResearcher,
|
|
68
|
+
researchMissionRequest,
|
|
69
|
+
expectedResearchDigest,
|
|
70
|
+
} from "./fixtures/docs-researcher/definition.ts";
|
|
71
|
+
|
|
72
|
+
export {
|
|
73
|
+
docsResearcherDeploymentId,
|
|
74
|
+
docsResearcherProducerId,
|
|
75
|
+
docsResearcherPrincipal,
|
|
76
|
+
docsCoordinatorDigests,
|
|
77
|
+
docsSummarizerDigests,
|
|
78
|
+
docsResearcherSubmitOptions,
|
|
79
|
+
docsResearcherSubmitAgent,
|
|
80
|
+
summarizeCallId,
|
|
81
|
+
fetchCallId,
|
|
82
|
+
type DocsResearcherHarnessOptions,
|
|
83
|
+
type DocsResearcherHarness,
|
|
84
|
+
makeDocsResearcherHarness,
|
|
85
|
+
redactedDocumentPreview,
|
|
86
|
+
type DocsResearcherHarnessRequirements,
|
|
87
|
+
} from "./fixtures/docs-researcher/harness.ts";
|
|
88
|
+
|
|
89
|
+
export {
|
|
90
|
+
docsMcpRequest,
|
|
91
|
+
docsMcpIdentity,
|
|
92
|
+
docsMcpConnectorLayer,
|
|
93
|
+
docsMcpOversizedConnectorLayer,
|
|
94
|
+
docsMcpMismatchedConnectorLayer,
|
|
95
|
+
assertDiscoveryMatchesAuthoredToolkit,
|
|
96
|
+
DocsMcpDiscoveryEvidence,
|
|
97
|
+
} from "./fixtures/docs-researcher/mcp.ts";
|