@pi-unipi/background-tasks 2.6.3 → 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 (58) hide show
  1. package/package.json +3 -2
  2. package/src/__tests__/anthropic-attribution.test.ts +0 -195
  3. package/src/__tests__/config.test.ts +0 -137
  4. package/src/__tests__/core.test.ts +0 -493
  5. package/src/__tests__/delegate-artifacts.test.ts +0 -528
  6. package/src/__tests__/delegate-budget.test.ts +0 -456
  7. package/src/__tests__/delegate-launch.test.ts +0 -676
  8. package/src/__tests__/delegate-result-package.test.ts +0 -350
  9. package/src/__tests__/delegate-seed.test.ts +0 -392
  10. package/src/__tests__/durable-fs.test.ts +0 -559
  11. package/src/__tests__/extension-api.test.ts +0 -579
  12. package/src/__tests__/fusion-artifacts.test.ts +0 -1039
  13. package/src/__tests__/fusion-budget.test.ts +0 -1356
  14. package/src/__tests__/fusion-claude-cache.test.ts +0 -320
  15. package/src/__tests__/fusion-config.test.ts +0 -335
  16. package/src/__tests__/fusion-context-prompts.test.ts +0 -670
  17. package/src/__tests__/fusion-evaluation.test.ts +0 -315
  18. package/src/__tests__/fusion-extraction-equivalence.test.ts +0 -58
  19. package/src/__tests__/fusion-golden-bytes.test.ts +0 -35
  20. package/src/__tests__/fusion-high-cardinality.test.ts +0 -192
  21. package/src/__tests__/fusion-model-selector.test.ts +0 -205
  22. package/src/__tests__/fusion-orchestrator.test.ts +0 -1194
  23. package/src/__tests__/fusion-rpc.test.ts +0 -369
  24. package/src/__tests__/fusion-sdk.test.ts +0 -1226
  25. package/src/__tests__/fusion-v5-core.test.ts +0 -219
  26. package/src/__tests__/fusion-validate-orchestrator.test.ts +0 -240
  27. package/src/__tests__/fusion-web-fetch.test.ts +0 -485
  28. package/src/__tests__/fusion-workflows.test.ts +0 -59
  29. package/src/__tests__/helpers/delegate-deterministic-seed.ts +0 -109
  30. package/src/__tests__/helpers/delegate-seed-subprocess.ts +0 -10
  31. package/src/__tests__/helpers/fusion-canonical-subprocess.ts +0 -21
  32. package/src/__tests__/helpers/fusion-canonical.ts +0 -140
  33. package/src/__tests__/helpers/fusion-fake-pi.ts +0 -279
  34. package/src/__tests__/helpers/fusion-golden-corpus.ts +0 -500
  35. package/src/__tests__/helpers/fusion-high-cardinality.ts +0 -140
  36. package/src/__tests__/helpers/normalize.ts +0 -22
  37. package/src/__tests__/helpers/pi-hook-contract-evidence.json +0 -18
  38. package/src/__tests__/pi-launch.test.ts +0 -202
  39. package/src/__tests__/registry.test.ts +0 -1580
  40. package/src/__tests__/scripted-provider/delegate-ambient-provider.test.ts +0 -130
  41. package/src/__tests__/scripted-provider/delegate-child-guard.test.ts +0 -631
  42. package/src/__tests__/scripted-provider/delegate-guard-provider.ts +0 -403
  43. package/src/__tests__/scripted-provider/follow-up.test.ts +0 -448
  44. package/src/__tests__/scripted-provider/fusion-output-recovery.test.ts +0 -132
  45. package/src/__tests__/scripted-provider/fusion-reason.test.ts +0 -310
  46. package/src/__tests__/scripted-provider/fusion-runtime-guard.test.ts +0 -163
  47. package/src/__tests__/scripted-provider/hook-contract-provider.ts +0 -179
  48. package/src/__tests__/scripted-provider/hook-probe-a.ts +0 -3
  49. package/src/__tests__/scripted-provider/hook-probe-b.ts +0 -3
  50. package/src/__tests__/scripted-provider/hook-probe-extension.ts +0 -126
  51. package/src/__tests__/scripted-provider/output-recovery-provider.ts +0 -153
  52. package/src/__tests__/scripted-provider/pi-hook-contract-evidence.json +0 -18
  53. package/src/__tests__/scripted-provider/pi-hook-contract.test.ts +0 -477
  54. package/src/__tests__/scripted-provider/runtime-guard-probe.ts +0 -28
  55. package/src/__tests__/scripted-provider/runtime-guard-provider.ts +0 -49
  56. package/src/__tests__/scripted-provider/scripted-provider-extension.ts +0 -408
  57. package/src/__tests__/task-manager.test.ts +0 -479
  58. package/src/__tests__/windows-taskkill.test.ts +0 -161
@@ -1,676 +0,0 @@
1
- import { describe, it, afterEach } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { existsSync } from 'node:fs';
4
- import { mkdir, mkdtemp, readFile, readdir, rm } from 'node:fs/promises';
5
- import { tmpdir } from 'node:os';
6
- import { join } from 'node:path';
7
- import {
8
- DELEGATE_FORBIDDEN_TOOLS,
9
- DELEGATE_INSPECT_TOOLS,
10
- assertDelegateHookContract,
11
- buildDelegateChildArgv,
12
- buildDelegateChildSystemPrompt,
13
- delegateChildEnv,
14
- delegateToolsFor,
15
- loadDelegateHookContractEvidence,
16
- makeDelegateChildSessionId,
17
- makeDelegateTaskId,
18
- preflightDelegateLaunch,
19
- resolveDelegateRoute,
20
- DELEGATE_TASK_ID_PATTERN,
21
- } from '../delegate/launch.js';
22
- import { prepareDelegateLaunch } from '../delegate/runner.js';
23
- import { DelegateError } from '../delegate/types.js';
24
- import {
25
- DELEGATE_HOOK_CONTRACT_ID,
26
- DELEGATE_REQUIRED_HOOK_GUARANTEES,
27
- type DelegateHookContractEvidence,
28
- } from '../delegate/hook-contract.js';
29
- import { sessionWith, userMessage } from './helpers/fusion-canonical.js';
30
-
31
- const roots: string[] = [];
32
-
33
- const OBSERVED_EVIDENCE: DelegateHookContractEvidence = {
34
- schema_version: 'pi-background-tasks.delegate-hook-contract.v1',
35
- contract_id: DELEGATE_HOOK_CONTRACT_ID,
36
- guarantees: {
37
- context_fires_before_every_model_call: true,
38
- context_result_messages_reach_provider: true,
39
- context_abort_blocks_provider_call: true,
40
- context_abort_skips_stream_invocation: false,
41
- context_abort_terminates_run: true,
42
- context_throw_blocks_provider_call: false,
43
- context_throw_isolated_to_throwing_handler: true,
44
- tool_result_fires_before_transcript_entry: true,
45
- tool_result_replacement_reaches_provider: true,
46
- tool_result_replacement_preserves_identity: true,
47
- tool_result_chains_in_load_order: true,
48
- handlers_run_in_extension_load_order: true,
49
- },
50
- };
51
-
52
- const AVAILABLE = [
53
- { provider: 'anthropic', id: 'claude-test', contextWindow: 200_000 },
54
- { provider: 'openai-codex', id: 'gpt-test', contextWindow: 400_000 },
55
- { provider: 'tiny', id: 'no-window', contextWindow: undefined },
56
- ];
57
-
58
- afterEach(async () => {
59
- for (const root of roots.splice(0)) await rm(root, { recursive: true, force: true });
60
- });
61
-
62
- void describe('delegate route pinning', () => {
63
- void it('defaults to the parent current model and records that origin', () => {
64
- const route = resolveDelegateRoute({
65
- currentModel: AVAILABLE[0],
66
- availableModels: AVAILABLE,
67
- thinkingLevel: 'high',
68
- });
69
- assert.equal(route.qualified_id, 'anthropic/claude-test');
70
- assert.equal(route.origin, 'parent_current');
71
- assert.equal(route.thinking_level, 'high');
72
- });
73
-
74
- void it('pins an explicit route exactly', () => {
75
- const route = resolveDelegateRoute({
76
- requested: { provider: 'openai-codex', model: 'gpt-test' },
77
- currentModel: AVAILABLE[0],
78
- availableModels: AVAILABLE,
79
- thinkingLevel: 'medium',
80
- });
81
- assert.equal(route.qualified_id, 'openai-codex/gpt-test');
82
- assert.equal(route.origin, 'explicit');
83
- });
84
-
85
- void it('never substitutes an unavailable route', () => {
86
- try {
87
- resolveDelegateRoute({
88
- requested: { provider: 'anthropic', model: 'does-not-exist' },
89
- currentModel: AVAILABLE[0],
90
- availableModels: AVAILABLE,
91
- thinkingLevel: 'medium',
92
- });
93
- assert.fail('an unavailable route must be refused');
94
- } catch (error) {
95
- assert.ok(error instanceof DelegateError);
96
- assert.equal(error.code, 'route_unresolved');
97
- assert.equal(error.childCreated, false);
98
- assert.match(error.message, /No substitute route was selected/);
99
- }
100
- });
101
-
102
- void it('refuses a route with no declared context window rather than assuming one', () => {
103
- assert.throws(
104
- () =>
105
- resolveDelegateRoute({
106
- requested: { provider: 'tiny', model: 'no-window' },
107
- availableModels: AVAILABLE,
108
- thinkingLevel: 'medium',
109
- }),
110
- (error: unknown) =>
111
- error instanceof DelegateError &&
112
- error.code === 'route_capacity_unknown' &&
113
- error.childCreated === false,
114
- );
115
- });
116
-
117
- void it('refuses when there is neither an explicit route nor a current model', () => {
118
- assert.throws(
119
- () => resolveDelegateRoute({ availableModels: AVAILABLE, thinkingLevel: 'medium' }),
120
- (error: unknown) => error instanceof DelegateError && error.code === 'route_unresolved',
121
- );
122
- });
123
- });
124
-
125
- void describe('delegate child isolation', () => {
126
- const argv = buildDelegateChildArgv({
127
- route: {
128
- provider: 'anthropic',
129
- model: 'claude-test',
130
- qualified_id: 'anthropic/claude-test',
131
- context_window_tokens: 200_000,
132
- thinking_level: 'medium',
133
- origin: 'parent_current',
134
- },
135
- capability: 'inspect',
136
- extensionMode: 'isolated',
137
- childSessionId: 'delegate-child-1',
138
- childSessionDir: '/tmp/task/child-session',
139
- childExtensionPath: '/pkg/extensions/delegate-child.ts',
140
- attributionExtensionPath: '/pkg/extensions/anthropic-attribution.ts',
141
- systemPrompt: 'child system prompt',
142
- });
143
-
144
- void it('gives the child its own session id and task-owned session directory', () => {
145
- assert.ok(argv.includes('--session-id'));
146
- assert.equal(argv[argv.indexOf('--session-id') + 1], 'delegate-child-1');
147
- assert.ok(argv.includes('--session-dir'));
148
- assert.equal(argv[argv.indexOf('--session-dir') + 1], '/tmp/task/child-session');
149
- assert.ok(!argv.includes('--continue'));
150
- assert.ok(!argv.includes('--resume'));
151
- assert.ok(!argv.includes('--session'));
152
- assert.ok(!argv.includes('--fork'));
153
- });
154
-
155
- void it('enables only the inspect tool set by argv, not by prompt', () => {
156
- const tools = argv[argv.indexOf('--tools') + 1]?.split(',') ?? [];
157
- assert.deepEqual(tools, [...DELEGATE_INSPECT_TOOLS]);
158
- assert.ok(argv.includes('--no-builtin-tools'));
159
- });
160
-
161
- void it('explicitly denies every forbidden tool', () => {
162
- const excluded = argv[argv.indexOf('--exclude-tools') + 1]?.split(',') ?? [];
163
- for (const forbidden of DELEGATE_FORBIDDEN_TOOLS) {
164
- assert.ok(excluded.includes(forbidden), `${forbidden} must be denied`);
165
- }
166
- for (const forbidden of ['bash', 'edit', 'write', 'bg_delegate', 'fusion_brainstorm']) {
167
- assert.ok(!DELEGATE_INSPECT_TOOLS.includes(forbidden));
168
- }
169
- });
170
-
171
- void it('disables ambient discovery and loads attribution before the package guard', () => {
172
- for (const flag of [
173
- '--no-extensions',
174
- '--no-skills',
175
- '--no-prompt-templates',
176
- '--no-themes',
177
- '--no-context-files',
178
- ]) {
179
- assert.ok(argv.includes(flag), `${flag} must be set`);
180
- }
181
- const extensionPaths = argv.flatMap((entry, index) =>
182
- entry === '--extension' ? [argv[index + 1] ?? ''] : [],
183
- );
184
- assert.deepEqual(extensionPaths, [
185
- '/pkg/extensions/anthropic-attribution.ts',
186
- '/pkg/extensions/delegate-child.ts',
187
- ]);
188
- });
189
-
190
- void it('keeps non-Anthropic argv at one explicit guard and requires attribution for Anthropic', () => {
191
- const common = {
192
- capability: 'inspect' as const,
193
- extensionMode: 'isolated' as const,
194
- childSessionId: 'delegate-child-2',
195
- childSessionDir: '/tmp/task/child-session-2',
196
- childExtensionPath: '/pkg/extensions/delegate-child.ts',
197
- systemPrompt: 'child system prompt',
198
- };
199
- const codexArgv = buildDelegateChildArgv({
200
- ...common,
201
- route: {
202
- provider: 'openai-codex',
203
- model: 'gpt-test',
204
- qualified_id: 'openai-codex/gpt-test',
205
- context_window_tokens: 200_000,
206
- thinking_level: 'medium',
207
- origin: 'explicit',
208
- },
209
- });
210
- assert.deepEqual(
211
- codexArgv.flatMap((entry, index) =>
212
- entry === '--extension' ? [codexArgv[index + 1] ?? ''] : [],
213
- ),
214
- ['/pkg/extensions/delegate-child.ts'],
215
- );
216
- assert.throws(
217
- () =>
218
- buildDelegateChildArgv({
219
- ...common,
220
- route: {
221
- provider: 'anthropic',
222
- model: 'claude-test',
223
- qualified_id: 'anthropic/claude-test',
224
- context_window_tokens: 200_000,
225
- thinking_level: 'medium',
226
- origin: 'explicit',
227
- },
228
- }),
229
- (error: unknown) =>
230
- error instanceof DelegateError && error.code === 'delegate_isolation_unsupported',
231
- );
232
- });
233
-
234
- void it('ambient mode enables extension discovery but preserves every other boundary', () => {
235
- const ambient = buildDelegateChildArgv({
236
- route: {
237
- provider: 'anthropic',
238
- model: 'claude-test',
239
- qualified_id: 'anthropic/claude-test',
240
- context_window_tokens: 200_000,
241
- thinking_level: 'medium',
242
- origin: 'explicit',
243
- },
244
- capability: 'inspect',
245
- extensionMode: 'ambient',
246
- childSessionId: 'delegate-ambient',
247
- childSessionDir: '/tmp/task/ambient-session',
248
- childExtensionPath: '/pkg/extensions/delegate-child.ts',
249
- attributionExtensionPath: '/pkg/extensions/anthropic-attribution.ts',
250
- systemPrompt: 'child system prompt',
251
- });
252
- assert.ok(!ambient.includes('--no-extensions'));
253
- for (const flag of [
254
- '--no-skills',
255
- '--no-prompt-templates',
256
- '--no-themes',
257
- '--no-context-files',
258
- '--no-builtin-tools',
259
- '--tools',
260
- '--exclude-tools',
261
- ]) {
262
- assert.ok(ambient.includes(flag), `${flag} must remain set in ambient mode`);
263
- }
264
- assert.deepEqual(
265
- ambient.flatMap((entry, index) =>
266
- entry === '--extension' ? [ambient[index + 1] ?? ''] : [],
267
- ),
268
- ['/pkg/extensions/anthropic-attribution.ts', '/pkg/extensions/delegate-child.ts'],
269
- );
270
- assert.equal(ambient[ambient.indexOf('--provider') + 1], 'anthropic');
271
- assert.equal(ambient[ambient.indexOf('--model') + 1], 'claude-test');
272
- assert.deepEqual(ambient[ambient.indexOf('--tools') + 1]?.split(','), [
273
- ...DELEGATE_INSPECT_TOOLS,
274
- ]);
275
- });
276
-
277
- void it('refuses an unknown extension mode', () => {
278
- assert.throws(
279
- () =>
280
- buildDelegateChildArgv({
281
- route: {
282
- provider: 'openai-codex',
283
- model: 'gpt-test',
284
- qualified_id: 'openai-codex/gpt-test',
285
- context_window_tokens: 200_000,
286
- thinking_level: 'medium',
287
- origin: 'explicit',
288
- },
289
- capability: 'inspect',
290
- extensionMode: 'custom-path' as never,
291
- childSessionId: 'delegate-invalid',
292
- childSessionDir: '/tmp/task/invalid-session',
293
- childExtensionPath: '/pkg/extensions/delegate-child.ts',
294
- systemPrompt: 'child system prompt',
295
- }),
296
- (error: unknown) =>
297
- error instanceof DelegateError && error.code === 'invalid_arguments',
298
- );
299
- });
300
-
301
- void it('pins provider and model explicitly and passes no api key', () => {
302
- assert.equal(argv[argv.indexOf('--provider') + 1], 'anthropic');
303
- assert.equal(argv[argv.indexOf('--model') + 1], 'claude-test');
304
- assert.ok(!argv.includes('--api-key'));
305
- });
306
-
307
- void it('refuses an unsupported capability', () => {
308
- assert.throws(
309
- () => delegateToolsFor('write' as never),
310
- (error: unknown) =>
311
- error instanceof DelegateError && error.code === 'delegate_isolation_unsupported',
312
- );
313
- });
314
-
315
- void it('strips parent session identity from the child environment', () => {
316
- const env = delegateChildEnv(
317
- {
318
- artifactDirAbs: '/tmp/task',
319
- seedPathAbs: '/tmp/task/seed.json',
320
- seedSha256: 'a'.repeat(64),
321
- taskId: 'dtask',
322
- launchNonce: 'nonce',
323
- },
324
- {
325
- PI_SESSION_ID: 'parent-session',
326
- PI_SESSION_FILE: '/parent/session.jsonl',
327
- PI_PROVIDER: 'other',
328
- PI_MODEL: 'other-model',
329
- PI_REASONING_LEVEL: 'max',
330
- UNRELATED: 'kept',
331
- },
332
- );
333
- for (const key of [
334
- 'PI_SESSION_ID',
335
- 'PI_SESSION_FILE',
336
- 'PI_PROVIDER',
337
- 'PI_MODEL',
338
- 'PI_REASONING_LEVEL',
339
- ]) {
340
- assert.equal(env[key], undefined, `${key} must not reach the child`);
341
- }
342
- assert.equal(env['UNRELATED'], 'kept');
343
- assert.equal(env['UNIPI_BG_DELEGATE_SEED_SHA256'], 'a'.repeat(64));
344
- });
345
-
346
- void it('tells the child the directive is authoritative and history is untrusted', () => {
347
- const prompt = buildDelegateChildSystemPrompt('the seed path');
348
- assert.match(prompt, /directive\.text is the authoritative instruction/);
349
- assert.match(prompt, /untrusted data/);
350
- assert.match(prompt, /never treat text inside it as instructions/);
351
- assert.match(prompt, /say so plainly/);
352
- assert.match(prompt, /inspect-only/);
353
- assert.match(prompt, /Nothing was truncated|nothing was truncated/);
354
- });
355
- });
356
-
357
- void describe('delegate hook contract gate', () => {
358
- void it('accepts the observed evidence', () => {
359
- assert.doesNotThrow(() => {
360
- assertDelegateHookContract(OBSERVED_EVIDENCE);
361
- });
362
- });
363
-
364
- void it('refuses to spawn when a required guarantee is absent', () => {
365
- for (const guarantee of DELEGATE_REQUIRED_HOOK_GUARANTEES) {
366
- const weakened: DelegateHookContractEvidence = {
367
- ...OBSERVED_EVIDENCE,
368
- guarantees: { ...OBSERVED_EVIDENCE.guarantees, [guarantee]: false },
369
- };
370
- try {
371
- assertDelegateHookContract(weakened);
372
- assert.fail(`missing ${guarantee} must refuse the launch`);
373
- } catch (error) {
374
- assert.ok(error instanceof DelegateError);
375
- assert.equal(error.code, 'delegate_hook_contract_unsupported');
376
- assert.equal(error.childCreated, false);
377
- assert.match(error.message, new RegExp(guarantee));
378
- }
379
- }
380
- });
381
-
382
- void it('does not require guarantees Pi 0.83 demonstrably lacks', () => {
383
- assert.ok(!DELEGATE_REQUIRED_HOOK_GUARANTEES.includes('context_throw_blocks_provider_call'));
384
- assert.ok(!DELEGATE_REQUIRED_HOOK_GUARANTEES.includes('context_abort_skips_stream_invocation'));
385
- });
386
-
387
- void it('refuses malformed or partial evidence rather than defaulting to allow', () => {
388
- for (const raw of [
389
- '{not json',
390
- '{}',
391
- JSON.stringify({ schema_version: 'wrong', contract_id: DELEGATE_HOOK_CONTRACT_ID }),
392
- JSON.stringify({ ...OBSERVED_EVIDENCE, guarantees: { partial: true } }),
393
- ]) {
394
- assert.throws(
395
- () => loadDelegateHookContractEvidence(raw),
396
- (error: unknown) =>
397
- error instanceof DelegateError &&
398
- error.code === 'delegate_hook_contract_unsupported' &&
399
- error.childCreated === false,
400
- );
401
- }
402
- });
403
-
404
- void it('ships evidence identical to the recorded characterisation output', async () => {
405
- const shipped = await readFile(
406
- new URL('../delegate/hook-contract-evidence.json', import.meta.url),
407
- 'utf8',
408
- );
409
- const recorded = await readFile(
410
- new URL('./helpers/pi-hook-contract-evidence.json', import.meta.url),
411
- 'utf8',
412
- );
413
- assert.equal(
414
- shipped,
415
- recorded,
416
- 'the shipped hook evidence must be byte-identical to what the characterisation gate observed',
417
- );
418
- const parsed = loadDelegateHookContractEvidence(shipped);
419
- assert.doesNotThrow(() => {
420
- assertDelegateHookContract(parsed);
421
- });
422
- });
423
- });
424
-
425
- void describe('delegate task identity', () => {
426
- void it('produces 128-bit random task ids', () => {
427
- const ids = new Set(Array.from({ length: 200 }, () => makeDelegateTaskId()));
428
- assert.equal(ids.size, 200, 'task ids must not collide');
429
- for (const id of ids) assert.match(id, DELEGATE_TASK_ID_PATTERN);
430
- });
431
-
432
- void it('produces child session ids unrelated to the parent', () => {
433
- const ids = new Set(Array.from({ length: 100 }, () => makeDelegateChildSessionId()));
434
- assert.equal(ids.size, 100);
435
- for (const id of ids) assert.match(id, /^delegate-[0-9a-f]{32}$/);
436
- });
437
- });
438
-
439
- void describe('delegate preflight ordering', () => {
440
- const source = () => ({
441
- cwd: '/tmp/project',
442
- sessionManager: sessionWith([userMessage('history')]),
443
- getSystemPrompt: () => 'parent system prompt',
444
- });
445
-
446
- const baseRoute = {
447
- provider: 'anthropic',
448
- model: 'claude-test',
449
- qualified_id: 'anthropic/claude-test',
450
- context_window_tokens: 200_000,
451
- thinking_level: 'medium',
452
- origin: 'parent_current' as const,
453
- };
454
-
455
- void it('checks the hook contract before doing anything else', () => {
456
- const weakened: DelegateHookContractEvidence = {
457
- ...OBSERVED_EVIDENCE,
458
- guarantees: { ...OBSERVED_EVIDENCE.guarantees, context_abort_blocks_provider_call: false },
459
- };
460
- assert.throws(
461
- () =>
462
- preflightDelegateLaunch({
463
- ctx: source(),
464
- toolCallId: 'call-1',
465
- prompt: 'investigate',
466
- capability: 'inspect',
467
- extensionMode: 'isolated',
468
- route: baseRoute,
469
- limitOverrides: {},
470
- hookEvidence: weakened,
471
- }),
472
- (error: unknown) =>
473
- error instanceof DelegateError && error.code === 'delegate_hook_contract_unsupported',
474
- );
475
- });
476
-
477
- void it('produces a complete plan when everything admits', () => {
478
- const result = preflightDelegateLaunch({
479
- ctx: source(),
480
- toolCallId: 'call-1',
481
- prompt: 'investigate the regression',
482
- capability: 'inspect',
483
- extensionMode: 'isolated',
484
- route: baseRoute,
485
- limitOverrides: { maxTurns: 5, maxToolCalls: 10, timeoutSeconds: 60 },
486
- hookEvidence: OBSERVED_EVIDENCE,
487
- });
488
- assert.match(result.taskId, DELEGATE_TASK_ID_PATTERN);
489
- assert.equal(result.limits.max_turns, 5);
490
- assert.equal(result.limits.max_tool_calls, 10);
491
- assert.equal(result.limits.timeout_seconds, 60);
492
- assert.equal(result.plan.fits, true);
493
- assert.equal(result.seed.seed.directive.text, 'investigate the regression');
494
- assert.equal(result.seed.sha256.length, 64);
495
- });
496
-
497
- void it('refuses a too-small route before constructing a seed', () => {
498
- assert.throws(
499
- () =>
500
- preflightDelegateLaunch({
501
- ctx: source(),
502
- toolCallId: 'call-1',
503
- prompt: 'investigate',
504
- capability: 'inspect',
505
- extensionMode: 'isolated',
506
- route: { ...baseRoute, context_window_tokens: 1000 },
507
- limitOverrides: {},
508
- hookEvidence: OBSERVED_EVIDENCE,
509
- }),
510
- (error: unknown) =>
511
- error instanceof DelegateError &&
512
- error.code === 'route_capacity_unknown' &&
513
- error.childCreated === false,
514
- );
515
- });
516
- });
517
-
518
- void describe('delegate launch preparation creates nothing on refusal', () => {
519
- async function attempt(overrides: Record<string, unknown>) {
520
- const root = await mkdtemp(join(tmpdir(), 'pi-bg-delegate-launch-'));
521
- roots.push(root);
522
- // OUR layout keeps artifacts under the OS temp root; isolate per test root.
523
- process.env['UNIPI_BG_TMP_DIR'] = join(root, 'tmp');
524
- await mkdir(join(root, 'tmp'), { recursive: true });
525
- const input = {
526
- ctx: {
527
- cwd: root,
528
- sessionManager: sessionWith([userMessage('history')]),
529
- getSystemPrompt: () => 'parent system prompt',
530
- },
531
- toolCallId: 'call-1',
532
- prompt: 'investigate',
533
- capability: 'inspect' as const,
534
- extensionMode: 'isolated' as const,
535
- route: {
536
- provider: 'anthropic',
537
- model: 'claude-test',
538
- qualified_id: 'anthropic/claude-test',
539
- context_window_tokens: 200_000,
540
- thinking_level: 'medium',
541
- origin: 'parent_current' as const,
542
- },
543
- limitOverrides: {},
544
- hookEvidence: OBSERVED_EVIDENCE,
545
- cwd: root,
546
- sessionId: 'unit-session',
547
- autoDeliver: 'never' as const,
548
- childExtensionPath: '/pkg/extensions/delegate-child.ts',
549
- ...overrides,
550
- };
551
- return { root, input };
552
- }
553
-
554
- async function delegateDirEntries(root: string): Promise<string[]> {
555
- // USER DECISION: delegate artifacts under <cwd>/.unipi/delegate.
556
- const base = join(root, '.unipi', 'delegate');
557
- if (!existsSync(base)) return [];
558
- const sessions = await readdir(base);
559
- const entries: string[] = [];
560
- for (const session of sessions) entries.push(...(await readdir(join(base, session))));
561
- return entries;
562
- }
563
-
564
- void it('creates zero artifacts when the hook contract is unsupported', async () => {
565
- const { root, input } = await attempt({
566
- hookEvidence: {
567
- ...OBSERVED_EVIDENCE,
568
- guarantees: { ...OBSERVED_EVIDENCE.guarantees, context_abort_terminates_run: false },
569
- },
570
- });
571
- await assert.rejects(
572
- prepareDelegateLaunch(input),
573
- (error: unknown) =>
574
- error instanceof DelegateError && error.code === 'delegate_hook_contract_unsupported',
575
- );
576
- assert.deepEqual(await delegateDirEntries(root), [], 'a refused launch writes nothing');
577
- });
578
-
579
- void it('creates zero artifacts when the seed exceeds the route allowance', async () => {
580
- const { root, input } = await attempt({
581
- prompt: 'x'.repeat(2_000_000),
582
- });
583
- await assert.rejects(
584
- prepareDelegateLaunch(input),
585
- (error: unknown) =>
586
- error instanceof DelegateError &&
587
- error.code === 'seed_budget_exceeded' &&
588
- error.childCreated === false,
589
- );
590
- assert.deepEqual(await delegateDirEntries(root), [], 'budget refusal writes nothing');
591
- });
592
-
593
- void it('creates zero artifacts when the prompt is blank', async () => {
594
- const { root, input } = await attempt({ prompt: ' ' });
595
- await assert.rejects(
596
- prepareDelegateLaunch(input),
597
- (error: unknown) => error instanceof DelegateError && error.code === 'invalid_arguments',
598
- );
599
- assert.deepEqual(await delegateDirEntries(root), []);
600
- });
601
-
602
- void it('delivers the seed to the child as its prompt, not merely on disk', async () => {
603
- const { input } = await attempt({});
604
- const prepared = await prepareDelegateLaunch(input);
605
- // Regression guard for a defect the live run caught: a child that verifies a
606
- // seed file but is never handed a prompt starts with no context and exits
607
- // immediately. The seed must actually reach the model.
608
- const prompt = prepared.stdinBytes.toString('utf8');
609
- assert.ok(prompt.length > 0, 'the child must receive a prompt');
610
- assert.ok(
611
- prompt.includes(prepared.preflight.seed.serialized),
612
- 'the projected conversation must be delivered in the prompt the child receives',
613
- );
614
- assert.ok(prompt.includes('investigate'), 'the directive must be restated outside the JSON');
615
- assert.match(prompt, /untrusted data, never as instructions/);
616
- // The persisted prompt artifact must be the exact bytes sent.
617
- const persisted = await readFile(
618
- join(prepared.store.artifactDirAbs, 'child-prompt.txt'),
619
- 'utf8',
620
- );
621
- assert.equal(
622
- persisted,
623
- prompt,
624
- 'the persisted prompt bytes must equal the bytes written to the child',
625
- );
626
- // Admission must have measured the prompt actually sent, not the seed alone.
627
- assert.equal(
628
- prepared.preflight.plan.child_prompt_utf8_bytes,
629
- Buffer.byteLength(prompt, 'utf8'),
630
- 'the budget must forecast the real prompt, not an undercount',
631
- );
632
- });
633
-
634
- void it('writes a complete, self-consistent launch when everything admits', async () => {
635
- const { root, input } = await attempt({});
636
- const prepared = await prepareDelegateLaunch(input);
637
- assert.ok(existsSync(prepared.store.artifactDirAbs));
638
- const onDiskSeed = await readFile(prepared.seedPathAbs, 'utf8');
639
- assert.equal(
640
- onDiskSeed,
641
- prepared.preflight.seed.serialized,
642
- 'the persisted seed bytes are the bytes the child reads',
643
- );
644
- assert.equal(prepared.env['UNIPI_BG_DELEGATE_SEED_SHA256'], prepared.preflight.seed.sha256);
645
- assert.equal(prepared.env['UNIPI_BG_DELEGATE_TASK_ID'], prepared.preflight.taskId);
646
- assert.ok(existsSync(join(prepared.store.artifactDirAbs, 'budget-plan.json')));
647
- assert.ok(existsSync(join(prepared.store.artifactDirAbs, 'context-omission-ledger.json')));
648
- const manifest = JSON.parse(
649
- await readFile(join(prepared.store.artifactDirAbs, 'manifest.json'), 'utf8'),
650
- ) as Record<string, unknown>;
651
- assert.equal(manifest['extension_mode'], 'isolated');
652
- assert.equal(prepared.facts.extensionMode, 'isolated');
653
- assert.equal(prepared.preflight.seed.seed.extension_mode, 'isolated');
654
- assert.ok(existsSync(prepared.childSessionDirAbs));
655
- assert.ok(prepared.childSessionDirAbs.startsWith(prepared.store.artifactDirAbs));
656
- assert.equal(await delegateDirEntries(root).then((entries) => entries.length), 1);
657
- void root;
658
- });
659
-
660
- void it('leaves no half-formed run when a post-directory step fails', async () => {
661
- const { root, input } = await attempt({ childExtensionPath: undefined });
662
- // Resolution of a missing child extension happens before directory creation,
663
- // so the refusal must still leave nothing behind.
664
- await assert.rejects(
665
- prepareDelegateLaunch({
666
- ...input,
667
- childExtensionPath: '/definitely/not/a/real/path/delegate-child.ts',
668
- }).then(async (prepared) => {
669
- // If it unexpectedly succeeded, clean up so the assertion below is honest.
670
- await rm(prepared.store.artifactDirAbs, { recursive: true, force: true });
671
- throw new Error('expected the launch to be refused');
672
- }),
673
- );
674
- void root;
675
- });
676
- });