@pi-unipi/background-tasks 2.6.2 → 2.6.4

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.
Files changed (59) hide show
  1. package/package.json +3 -2
  2. package/src/tools.ts +3 -1
  3. package/src/__tests__/anthropic-attribution.test.ts +0 -195
  4. package/src/__tests__/config.test.ts +0 -137
  5. package/src/__tests__/core.test.ts +0 -493
  6. package/src/__tests__/delegate-artifacts.test.ts +0 -528
  7. package/src/__tests__/delegate-budget.test.ts +0 -456
  8. package/src/__tests__/delegate-launch.test.ts +0 -676
  9. package/src/__tests__/delegate-result-package.test.ts +0 -350
  10. package/src/__tests__/delegate-seed.test.ts +0 -392
  11. package/src/__tests__/durable-fs.test.ts +0 -559
  12. package/src/__tests__/extension-api.test.ts +0 -579
  13. package/src/__tests__/fusion-artifacts.test.ts +0 -1039
  14. package/src/__tests__/fusion-budget.test.ts +0 -1356
  15. package/src/__tests__/fusion-claude-cache.test.ts +0 -320
  16. package/src/__tests__/fusion-config.test.ts +0 -335
  17. package/src/__tests__/fusion-context-prompts.test.ts +0 -670
  18. package/src/__tests__/fusion-evaluation.test.ts +0 -315
  19. package/src/__tests__/fusion-extraction-equivalence.test.ts +0 -58
  20. package/src/__tests__/fusion-golden-bytes.test.ts +0 -35
  21. package/src/__tests__/fusion-high-cardinality.test.ts +0 -192
  22. package/src/__tests__/fusion-model-selector.test.ts +0 -205
  23. package/src/__tests__/fusion-orchestrator.test.ts +0 -1194
  24. package/src/__tests__/fusion-rpc.test.ts +0 -369
  25. package/src/__tests__/fusion-sdk.test.ts +0 -1226
  26. package/src/__tests__/fusion-v5-core.test.ts +0 -219
  27. package/src/__tests__/fusion-validate-orchestrator.test.ts +0 -240
  28. package/src/__tests__/fusion-web-fetch.test.ts +0 -485
  29. package/src/__tests__/fusion-workflows.test.ts +0 -59
  30. package/src/__tests__/helpers/delegate-deterministic-seed.ts +0 -109
  31. package/src/__tests__/helpers/delegate-seed-subprocess.ts +0 -10
  32. package/src/__tests__/helpers/fusion-canonical-subprocess.ts +0 -21
  33. package/src/__tests__/helpers/fusion-canonical.ts +0 -140
  34. package/src/__tests__/helpers/fusion-fake-pi.ts +0 -279
  35. package/src/__tests__/helpers/fusion-golden-corpus.ts +0 -500
  36. package/src/__tests__/helpers/fusion-high-cardinality.ts +0 -140
  37. package/src/__tests__/helpers/normalize.ts +0 -22
  38. package/src/__tests__/helpers/pi-hook-contract-evidence.json +0 -18
  39. package/src/__tests__/pi-launch.test.ts +0 -202
  40. package/src/__tests__/registry.test.ts +0 -1580
  41. package/src/__tests__/scripted-provider/delegate-ambient-provider.test.ts +0 -130
  42. package/src/__tests__/scripted-provider/delegate-child-guard.test.ts +0 -631
  43. package/src/__tests__/scripted-provider/delegate-guard-provider.ts +0 -403
  44. package/src/__tests__/scripted-provider/follow-up.test.ts +0 -448
  45. package/src/__tests__/scripted-provider/fusion-output-recovery.test.ts +0 -132
  46. package/src/__tests__/scripted-provider/fusion-reason.test.ts +0 -310
  47. package/src/__tests__/scripted-provider/fusion-runtime-guard.test.ts +0 -163
  48. package/src/__tests__/scripted-provider/hook-contract-provider.ts +0 -179
  49. package/src/__tests__/scripted-provider/hook-probe-a.ts +0 -3
  50. package/src/__tests__/scripted-provider/hook-probe-b.ts +0 -3
  51. package/src/__tests__/scripted-provider/hook-probe-extension.ts +0 -126
  52. package/src/__tests__/scripted-provider/output-recovery-provider.ts +0 -153
  53. package/src/__tests__/scripted-provider/pi-hook-contract-evidence.json +0 -18
  54. package/src/__tests__/scripted-provider/pi-hook-contract.test.ts +0 -477
  55. package/src/__tests__/scripted-provider/runtime-guard-probe.ts +0 -28
  56. package/src/__tests__/scripted-provider/runtime-guard-provider.ts +0 -49
  57. package/src/__tests__/scripted-provider/scripted-provider-extension.ts +0 -408
  58. package/src/__tests__/task-manager.test.ts +0 -479
  59. package/src/__tests__/windows-taskkill.test.ts +0 -161
@@ -1,528 +0,0 @@
1
- import { describe, it, afterEach } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { createHash } from 'node:crypto';
4
- import { existsSync } from 'node:fs';
5
- import { mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises';
6
- import { tmpdir } from 'node:os';
7
- import { join } from 'node:path';
8
- import { DelegateArtifactStore } from '../delegate/artifacts.js';
9
- import { evaluateDelegateTerminal, decideDelegateDelivery, inlineTooLarge } from '../delegate/runner.js';
10
- import {
11
- buildDelegateResultPackage,
12
- serializeDelegateResultPackage,
13
- } from '../delegate/result-package.js';
14
- import { DelegateError, type DelegateLimits, type DelegatePinnedRoute } from '../delegate/types.js';
15
- import { DELEGATE_INLINE_ANSWER_BYTES } from '../delegate/budget.js';
16
-
17
- const roots: string[] = [];
18
-
19
- const ROUTE: DelegatePinnedRoute = {
20
- provider: 'anthropic',
21
- model: 'claude-test',
22
- qualified_id: 'anthropic/claude-test',
23
- context_window_tokens: 200_000,
24
- thinking_level: 'medium',
25
- origin: 'parent_current',
26
- };
27
-
28
- const LIMITS: DelegateLimits = {
29
- max_turns: 24,
30
- max_tool_calls: 120,
31
- timeout_seconds: 900,
32
- max_tool_result_bytes: 1024,
33
- max_total_tool_output_bytes: 8192,
34
- max_answer_bytes: 4_194_304,
35
- allowed_input_tokens: 171_712,
36
- };
37
-
38
- const TASK_ID = 'd0123456789abcdef0123456789abcdef';
39
- const NONCE = 'ffeeddccbbaa99887766554433221100';
40
- const SEED_SHA = 'a'.repeat(64);
41
-
42
- async function makeStore(taskId = TASK_ID) {
43
- const root = await mkdtemp(join(tmpdir(), 'pi-bg-delegate-artifacts-'));
44
- roots.push(root);
45
- const store = await DelegateArtifactStore.create({
46
- cwd: root,
47
- taskId,
48
- launchNonce: NONCE,
49
- sessionId: 'unit-session',
50
- childSessionId: 'delegate-child-session',
51
- childSessionDir: '',
52
- extensionMode: 'isolated',
53
- route: ROUTE,
54
- limits: LIMITS,
55
- seedSha256: SEED_SHA,
56
- });
57
- return { root, store };
58
- }
59
-
60
- afterEach(async () => {
61
- for (const root of roots.splice(0)) await rm(root, { recursive: true, force: true });
62
- });
63
-
64
- void describe('delegate artifact store', () => {
65
- void it('creates a private task-owned directory with a spill subdirectory', async () => {
66
- const { store } = await makeStore();
67
- assert.ok(existsSync(store.artifactDirAbs));
68
- assert.ok(existsSync(store.spillDirAbs));
69
- assert.equal(store.snapshot().extension_mode, 'isolated');
70
- if (process.platform !== 'win32') {
71
- const stats = await stat(store.artifactDirAbs);
72
- assert.equal(stats.mode & 0o777, 0o700, 'artifact directory must be private');
73
- }
74
- });
75
-
76
- void it('refuses to reuse an existing task directory', async () => {
77
- const { root } = await makeStore();
78
- await assert.rejects(
79
- DelegateArtifactStore.create({
80
- cwd: root,
81
- taskId: TASK_ID,
82
- launchNonce: NONCE,
83
- sessionId: 'unit-session',
84
- childSessionId: 'x',
85
- childSessionDir: '',
86
- extensionMode: 'isolated',
87
- route: ROUTE,
88
- limits: LIMITS,
89
- seedSha256: SEED_SHA,
90
- }),
91
- (error: unknown) => error instanceof DelegateError && error.code === 'artifact_error',
92
- );
93
- });
94
-
95
- void it('gives concurrent delegates non-colliding directories', async () => {
96
- const first = await makeStore('d11111111111111111111111111111111');
97
- const second = await DelegateArtifactStore.create({
98
- cwd: first.root,
99
- taskId: 'd22222222222222222222222222222222',
100
- launchNonce: NONCE,
101
- sessionId: 'unit-session',
102
- childSessionId: 'x',
103
- childSessionDir: '',
104
- extensionMode: 'ambient',
105
- route: ROUTE,
106
- limits: LIMITS,
107
- seedSha256: SEED_SHA,
108
- });
109
- assert.notEqual(first.store.artifactDirAbs, second.artifactDirAbs);
110
- assert.ok(existsSync(first.store.artifactDirAbs));
111
- assert.ok(existsSync(second.artifactDirAbs));
112
- });
113
-
114
- void it('persists the seed bytes exactly as given', async () => {
115
- const { store } = await makeStore();
116
- const seed = '{"schema_version":"pi-background-tasks.delegate-seed.v2","x":"\u2028\u2029👩"}';
117
- const ref = await store.writeSeed(seed);
118
- const onDisk = await readFile(join(store.artifactDirAbs, 'seed.json'), 'utf8');
119
- assert.equal(onDisk, seed, 'persisted seed bytes must equal the bytes handed to the child');
120
- assert.equal(ref.sha256, createHash('sha256').update(seed, 'utf8').digest('hex'));
121
- });
122
-
123
- void it('readCommittedResult advertises only control artifacts that exist', async () => {
124
- const { store } = await makeStore();
125
- await assert.rejects(
126
- store.readCommittedResult(),
127
- (error: unknown) => {
128
- assert.ok(error instanceof DelegateError);
129
- assert.equal(error.code, 'result_unavailable');
130
- assert.doesNotMatch(error.describe(), /child\.stdout|child\.stderr/);
131
- assert.ok(error.preserved.length > 0);
132
- assert.ok(
133
- error.preserved.every((path) => existsSync(join(store.artifactDirAbs, path))),
134
- );
135
- return true;
136
- },
137
- );
138
- });
139
-
140
- void it('spills an oversized payload and returns a receipt naming its coordinates', async () => {
141
- const { store } = await makeStore();
142
- const payload = Buffer.alloc(2 * 1024 * 1024, 0x41);
143
- const receipt = await store.spillToolPayload({
144
- toolName: 'read',
145
- toolCallId: 'call-abc',
146
- turnSequence: 3,
147
- sourceCallIndex: 1,
148
- payload,
149
- maxTotalBytes: 64 * 1024 * 1024,
150
- });
151
- assert.equal(receipt.byte_length, payload.length);
152
- assert.equal(receipt.sha256, createHash('sha256').update(payload).digest('hex'));
153
- assert.equal(receipt.turn_sequence, 3);
154
- assert.equal(receipt.source_call_index, 1);
155
- const onDisk = await readFile(join(store.artifactDirAbs, receipt.artifact));
156
- assert.equal(onDisk.length, payload.length, 'the complete payload must be preserved');
157
- assert.ok(onDisk.equals(payload), 'nothing may be truncated on the way to disk');
158
- });
159
-
160
- void it('associates parallel spills with the right call even when they interleave', async () => {
161
- const { store } = await makeStore();
162
- // Coordinates are assigned before execution, so completion order cannot
163
- // mis-associate a receipt with another call's bytes.
164
- const payloads = [
165
- { id: 'call-a', index: 0, byte: 0x61 },
166
- { id: 'call-b', index: 1, byte: 0x62 },
167
- { id: 'call-c', index: 2, byte: 0x63 },
168
- ];
169
- const receipts = await Promise.all(
170
- // Reverse order deliberately, to emulate out-of-order completion.
171
- [...payloads].reverse().map((entry) =>
172
- store.spillToolPayload({
173
- toolName: 'read',
174
- toolCallId: entry.id,
175
- turnSequence: 1,
176
- sourceCallIndex: entry.index,
177
- payload: Buffer.alloc(4096, entry.byte),
178
- maxTotalBytes: 64 * 1024 * 1024,
179
- }),
180
- ),
181
- );
182
- for (const receipt of receipts) {
183
- const source = payloads.find((entry) => entry.id === receipt.tool_call_id);
184
- assert.ok(source);
185
- assert.equal(receipt.source_call_index, source.index);
186
- const bytes = await readFile(join(store.artifactDirAbs, receipt.artifact));
187
- assert.ok(
188
- bytes.every((value) => value === source.byte),
189
- `${receipt.tool_call_id} receipt must point at its own bytes`,
190
- );
191
- }
192
- assert.equal(new Set(receipts.map((receipt) => receipt.artifact)).size, 3);
193
- });
194
-
195
- void it('refuses a spill that would exceed the aggregate cap, and emits no receipt', async () => {
196
- const { store } = await makeStore();
197
- await store.spillToolPayload({
198
- toolName: 'read',
199
- toolCallId: 'a',
200
- turnSequence: 1,
201
- sourceCallIndex: 0,
202
- payload: Buffer.alloc(4096, 1),
203
- maxTotalBytes: 8192,
204
- });
205
- const before = await readdir(store.spillDirAbs);
206
- await assert.rejects(
207
- store.spillToolPayload({
208
- toolName: 'read',
209
- toolCallId: 'b',
210
- turnSequence: 1,
211
- sourceCallIndex: 1,
212
- payload: Buffer.alloc(8192, 2),
213
- maxTotalBytes: 8192,
214
- }),
215
- (error: unknown) =>
216
- error instanceof DelegateError && error.code === 'aggregate_tool_output_cap',
217
- );
218
- assert.deepEqual(await readdir(store.spillDirAbs), before, 'a refused spill writes nothing');
219
- });
220
-
221
- void it('returns exactly the requested byte range', async () => {
222
- const { store } = await makeStore();
223
- const payload = Buffer.from('0123456789abcdefghij', 'utf8');
224
- const receipt = await store.spillToolPayload({
225
- toolName: 'read',
226
- toolCallId: 'range',
227
- turnSequence: 1,
228
- sourceCallIndex: 0,
229
- payload,
230
- maxTotalBytes: 65_536,
231
- });
232
- const read = await store.readSpillRange(receipt.artifact, 5, 6);
233
- assert.equal(read.bytes.toString('utf8'), '56789a');
234
- assert.equal(read.totalBytes, payload.length);
235
- });
236
-
237
- void it('refuses a range past end-of-file instead of silently shortening it', async () => {
238
- const { store } = await makeStore();
239
- const receipt = await store.spillToolPayload({
240
- toolName: 'read',
241
- toolCallId: 'short',
242
- turnSequence: 1,
243
- sourceCallIndex: 0,
244
- payload: Buffer.from('0123456789', 'utf8'),
245
- maxTotalBytes: 65_536,
246
- });
247
- await assert.rejects(
248
- store.readSpillRange(receipt.artifact, 5, 100),
249
- (error: unknown) =>
250
- error instanceof DelegateError &&
251
- error.code === 'artifact_read_failed' &&
252
- /refused rather than silently shortened/.test(error.message),
253
- );
254
- });
255
-
256
- void it('refuses a range read that escapes the artifact directory', async () => {
257
- const { store } = await makeStore();
258
- for (const path of ['../escape.txt', '../../etc/passwd', 'spill/../../outside']) {
259
- await assert.rejects(
260
- store.readSpillRange(path, 0, 1),
261
- (error: unknown) => error instanceof DelegateError && error.code === 'artifact_read_failed',
262
- );
263
- }
264
- });
265
-
266
- void it('refuses non-positive lengths and negative offsets', async () => {
267
- const { store } = await makeStore();
268
- for (const [offset, length] of [
269
- [-1, 1],
270
- [0, 0],
271
- [0, -5],
272
- [1.5, 1],
273
- ] as const) {
274
- await assert.rejects(
275
- store.readSpillRange('spill/whatever.bin', offset, length),
276
- (error: unknown) => error instanceof DelegateError && error.code === 'artifact_read_failed',
277
- );
278
- }
279
- });
280
- });
281
-
282
- void describe('delegate terminal evaluation', () => {
283
- void it('accepts a committed package and reports verified answer facts', async () => {
284
- const { store } = await makeStore();
285
- const pkg = buildDelegateResultPackage({
286
- taskId: TASK_ID,
287
- launchNonce: NONCE,
288
- seedSha256: SEED_SHA,
289
- directiveSha256: 'b'.repeat(64),
290
- route: { provider: ROUTE.provider, model: ROUTE.model },
291
- routeAttestations: [
292
- { provider: ROUTE.provider, model: ROUTE.model, stop_reason: 'stop' },
293
- ],
294
- stopReason: 'stop',
295
- turns: 4,
296
- toolCalls: 9,
297
- usage: { status: 'unavailable', reason: 'no usage reported' },
298
- answerBlocks: ['the verified answer'],
299
- spilledArtifacts: [],
300
- });
301
- await store.commitResult(pkg);
302
- const evaluation = await evaluateDelegateTerminal({
303
- artifactDirAbs: store.artifactDirAbs,
304
- taskId: TASK_ID,
305
- launchNonce: NONCE,
306
- seedSha256: SEED_SHA,
307
- route: { provider: ROUTE.provider, model: ROUTE.model },
308
- taskStatus: 'completed',
309
- taskError: undefined,
310
- });
311
- assert.equal(evaluation.outcome.status, 'committed');
312
- assert.equal(evaluation.result?.answer, 'the verified answer');
313
- assert.equal(evaluation.outcome.turns, 4);
314
- assert.equal(evaluation.outcome.toolCalls, 9);
315
- });
316
-
317
- void it('treats a zero-exit child with no committed package as a typed failure', async () => {
318
- const { store } = await makeStore();
319
- const evaluation = await evaluateDelegateTerminal({
320
- artifactDirAbs: store.artifactDirAbs,
321
- taskId: TASK_ID,
322
- launchNonce: NONCE,
323
- seedSha256: SEED_SHA,
324
- route: { provider: ROUTE.provider, model: ROUTE.model },
325
- taskStatus: 'completed',
326
- taskError: undefined,
327
- });
328
- assert.equal(evaluation.outcome.status, 'failed');
329
- assert.equal(evaluation.outcome.errorCode, 'child_exited_without_commit');
330
- assert.match(evaluation.error?.message ?? '', /no committed answer/);
331
- assert.match(evaluation.error?.describe() ?? '', /Preserved:/);
332
- assert.doesNotMatch(
333
- evaluation.error?.describe() ?? '',
334
- /child\.stderr\.txt|child\.stdout\.txt|Inspect child-terminal\.json/,
335
- 'terminal reporting must never advertise evidence files that do not exist',
336
- );
337
- });
338
-
339
- void it('does not advertise a configured merged output path that does not exist', async () => {
340
- const { root, store } = await makeStore();
341
- const missingAbsPath = join(root, '.pi', 'tasks', 'missing.output');
342
- const evaluation = await evaluateDelegateTerminal({
343
- artifactDirAbs: store.artifactDirAbs,
344
- taskId: TASK_ID,
345
- launchNonce: NONCE,
346
- seedSha256: SEED_SHA,
347
- route: { provider: ROUTE.provider, model: ROUTE.model },
348
- taskStatus: 'failed',
349
- taskError: 'spawn failed before output creation',
350
- taskOutputPath: '.pi/tasks/missing.output',
351
- taskOutputAbsPath: missingAbsPath,
352
- });
353
- assert.doesNotMatch(evaluation.error?.describe() ?? '', /missing\.output/);
354
- assert.doesNotMatch(evaluation.error?.describe() ?? '', /Inspect child-terminal\.json/);
355
- });
356
-
357
- void it('reports only a real merged task output path as preserved diagnostics', async () => {
358
- const { root, store } = await makeStore();
359
- const outputAbsPath = join(root, '.pi', 'tasks', 'delegate.output');
360
- await mkdir(join(root, '.pi', 'tasks'), { recursive: true });
361
- await writeFile(outputAbsPath, 'merged stdout and stderr', 'utf8');
362
- const evaluation = await evaluateDelegateTerminal({
363
- artifactDirAbs: store.artifactDirAbs,
364
- taskId: TASK_ID,
365
- launchNonce: NONCE,
366
- seedSha256: SEED_SHA,
367
- route: { provider: ROUTE.provider, model: ROUTE.model },
368
- taskStatus: 'failed',
369
- taskError: 'Exited with code 1',
370
- taskOutputPath: '.pi/tasks/delegate.output',
371
- taskOutputAbsPath: outputAbsPath,
372
- });
373
- assert.match(evaluation.error?.describe() ?? '', /\.pi\/tasks\/delegate\.output/);
374
- assert.doesNotMatch(evaluation.error?.describe() ?? '', /child\.stderr\.txt/);
375
- });
376
-
377
- void it('reports the child-recorded terminal reason when one exists', async () => {
378
- const { store } = await makeStore();
379
- await writeFile(
380
- join(store.artifactDirAbs, 'child-terminal.json'),
381
- JSON.stringify({
382
- schema_version: 'pi-background-tasks.delegate-child-terminal.v1',
383
- code: 'provider_context_budget_exhausted',
384
- message: 'context exceeded the pinned route window',
385
- }),
386
- 'utf8',
387
- );
388
- const evaluation = await evaluateDelegateTerminal({
389
- artifactDirAbs: store.artifactDirAbs,
390
- taskId: TASK_ID,
391
- launchNonce: NONCE,
392
- seedSha256: SEED_SHA,
393
- route: { provider: ROUTE.provider, model: ROUTE.model },
394
- taskStatus: 'failed',
395
- taskError: 'Exited with code 1',
396
- });
397
- assert.equal(evaluation.outcome.errorCode, 'provider_context_budget_exhausted');
398
- assert.match(evaluation.error?.message ?? '', /context exceeded the pinned route window/);
399
- });
400
-
401
- void it('records the parent adjudication separately from the child package', async () => {
402
- const { store } = await makeStore();
403
- const pkg = buildDelegateResultPackage({
404
- taskId: TASK_ID,
405
- launchNonce: NONCE,
406
- seedSha256: SEED_SHA,
407
- directiveSha256: 'b'.repeat(64),
408
- route: { provider: ROUTE.provider, model: ROUTE.model },
409
- routeAttestations: [{ provider: ROUTE.provider, model: ROUTE.model, stop_reason: 'stop' }],
410
- stopReason: 'stop',
411
- turns: 1,
412
- toolCalls: 0,
413
- usage: { status: 'unavailable', reason: 'none' },
414
- answerBlocks: ['answer'],
415
- spilledArtifacts: [],
416
- });
417
- await store.commitResult(pkg);
418
- await evaluateDelegateTerminal({
419
- artifactDirAbs: store.artifactDirAbs,
420
- taskId: TASK_ID,
421
- launchNonce: NONCE,
422
- seedSha256: SEED_SHA,
423
- route: { provider: ROUTE.provider, model: ROUTE.model },
424
- taskStatus: 'completed',
425
- taskError: undefined,
426
- });
427
- // The child writes result.json; the parent writes outcome.json. Keeping the
428
- // two writers on separate artifacts means neither can claim a state it did
429
- // not observe, and a stale manifest field can never imply success.
430
- const outcome = JSON.parse(
431
- await readFile(join(store.artifactDirAbs, 'outcome.json'), 'utf8'),
432
- ) as Record<string, unknown>;
433
- assert.equal(outcome['schema_version'], 'pi-background-tasks.delegate-outcome.v1');
434
- assert.equal(outcome['task_id'], TASK_ID);
435
- assert.equal(outcome['observed_task_status'], 'completed');
436
- assert.equal(outcome['error_code'], null);
437
- const recorded = outcome['outcome'];
438
- assert.ok(typeof recorded === 'object' && recorded !== null);
439
- assert.equal(Reflect.get(recorded, 'status'), 'committed');
440
- });
441
-
442
- void it('records a typed adjudication for a child that never committed', async () => {
443
- const { store } = await makeStore();
444
- await evaluateDelegateTerminal({
445
- artifactDirAbs: store.artifactDirAbs,
446
- taskId: TASK_ID,
447
- launchNonce: NONCE,
448
- seedSha256: SEED_SHA,
449
- route: { provider: ROUTE.provider, model: ROUTE.model },
450
- taskStatus: 'completed',
451
- taskError: undefined,
452
- });
453
- const outcome = JSON.parse(
454
- await readFile(join(store.artifactDirAbs, 'outcome.json'), 'utf8'),
455
- ) as Record<string, unknown>;
456
- assert.equal(outcome['error_code'], 'child_exited_without_commit');
457
- });
458
-
459
- void it('classifies a killed task as cancelled', async () => {
460
- const { store } = await makeStore();
461
- const evaluation = await evaluateDelegateTerminal({
462
- artifactDirAbs: store.artifactDirAbs,
463
- taskId: TASK_ID,
464
- launchNonce: NONCE,
465
- seedSha256: SEED_SHA,
466
- route: { provider: ROUTE.provider, model: ROUTE.model },
467
- taskStatus: 'killed',
468
- taskError: 'killed by user',
469
- });
470
- assert.equal(evaluation.outcome.status, 'cancelled');
471
- assert.equal(evaluation.outcome.errorCode, 'child_cancelled');
472
- });
473
-
474
- void it('detects a corrupted committed package rather than returning its bytes', async () => {
475
- const { store } = await makeStore();
476
- const pkg = buildDelegateResultPackage({
477
- taskId: TASK_ID,
478
- launchNonce: NONCE,
479
- seedSha256: SEED_SHA,
480
- directiveSha256: 'b'.repeat(64),
481
- route: { provider: ROUTE.provider, model: ROUTE.model },
482
- routeAttestations: [{ provider: ROUTE.provider, model: ROUTE.model, stop_reason: 'stop' }],
483
- stopReason: 'stop',
484
- turns: 1,
485
- toolCalls: 0,
486
- usage: { status: 'unavailable', reason: 'none' },
487
- answerBlocks: ['original answer'],
488
- spilledArtifacts: [],
489
- });
490
- const serialized = serializeDelegateResultPackage(pkg);
491
- const corrupted = serialized.replace(
492
- pkg.answer.blocks[0]?.data_base64 ?? '',
493
- Buffer.from('substituted', 'utf8').toString('base64'),
494
- );
495
- await writeFile(join(store.artifactDirAbs, 'result.json'), corrupted, 'utf8');
496
- const evaluation = await evaluateDelegateTerminal({
497
- artifactDirAbs: store.artifactDirAbs,
498
- taskId: TASK_ID,
499
- launchNonce: NONCE,
500
- seedSha256: SEED_SHA,
501
- route: { provider: ROUTE.provider, model: ROUTE.model },
502
- taskStatus: 'completed',
503
- taskError: undefined,
504
- });
505
- assert.equal(evaluation.outcome.status, 'failed');
506
- assert.equal(evaluation.outcome.errorCode, 'answer_hash_mismatch');
507
- assert.equal(evaluation.result, undefined, 'a corrupted answer is never returned');
508
- });
509
- });
510
-
511
- void describe('delegate delivery decisions', () => {
512
- void it('inlines at exactly the cap and degrades one byte past it', () => {
513
- assert.equal(decideDelegateDelivery(DELEGATE_INLINE_ANSWER_BYTES, undefined).mode, 'inline');
514
- assert.equal(decideDelegateDelivery(DELEGATE_INLINE_ANSWER_BYTES + 1, undefined).mode, 'artifact');
515
- });
516
-
517
- void it('honours an explicit artifact request for a small answer', () => {
518
- assert.equal(decideDelegateDelivery(10, 'artifact').mode, 'artifact');
519
- });
520
-
521
- void it('never truncates: an explicit oversized inline request is a typed failure', () => {
522
- const error = inlineTooLarge('dtask', '.pi/delegate/x', DELEGATE_INLINE_ANSWER_BYTES + 1);
523
- assert.equal(error.code, 'result_too_large_for_inline');
524
- assert.match(error.message, /not truncated to fit/);
525
- assert.ok(error.remediation.length > 0);
526
- assert.ok(error.preserved.length > 0);
527
- });
528
- });