@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.
- package/package.json +3 -2
- package/src/tools.ts +3 -1
- package/src/__tests__/anthropic-attribution.test.ts +0 -195
- package/src/__tests__/config.test.ts +0 -137
- package/src/__tests__/core.test.ts +0 -493
- package/src/__tests__/delegate-artifacts.test.ts +0 -528
- package/src/__tests__/delegate-budget.test.ts +0 -456
- package/src/__tests__/delegate-launch.test.ts +0 -676
- package/src/__tests__/delegate-result-package.test.ts +0 -350
- package/src/__tests__/delegate-seed.test.ts +0 -392
- package/src/__tests__/durable-fs.test.ts +0 -559
- package/src/__tests__/extension-api.test.ts +0 -579
- package/src/__tests__/fusion-artifacts.test.ts +0 -1039
- package/src/__tests__/fusion-budget.test.ts +0 -1356
- package/src/__tests__/fusion-claude-cache.test.ts +0 -320
- package/src/__tests__/fusion-config.test.ts +0 -335
- package/src/__tests__/fusion-context-prompts.test.ts +0 -670
- package/src/__tests__/fusion-evaluation.test.ts +0 -315
- package/src/__tests__/fusion-extraction-equivalence.test.ts +0 -58
- package/src/__tests__/fusion-golden-bytes.test.ts +0 -35
- package/src/__tests__/fusion-high-cardinality.test.ts +0 -192
- package/src/__tests__/fusion-model-selector.test.ts +0 -205
- package/src/__tests__/fusion-orchestrator.test.ts +0 -1194
- package/src/__tests__/fusion-rpc.test.ts +0 -369
- package/src/__tests__/fusion-sdk.test.ts +0 -1226
- package/src/__tests__/fusion-v5-core.test.ts +0 -219
- package/src/__tests__/fusion-validate-orchestrator.test.ts +0 -240
- package/src/__tests__/fusion-web-fetch.test.ts +0 -485
- package/src/__tests__/fusion-workflows.test.ts +0 -59
- package/src/__tests__/helpers/delegate-deterministic-seed.ts +0 -109
- package/src/__tests__/helpers/delegate-seed-subprocess.ts +0 -10
- package/src/__tests__/helpers/fusion-canonical-subprocess.ts +0 -21
- package/src/__tests__/helpers/fusion-canonical.ts +0 -140
- package/src/__tests__/helpers/fusion-fake-pi.ts +0 -279
- package/src/__tests__/helpers/fusion-golden-corpus.ts +0 -500
- package/src/__tests__/helpers/fusion-high-cardinality.ts +0 -140
- package/src/__tests__/helpers/normalize.ts +0 -22
- package/src/__tests__/helpers/pi-hook-contract-evidence.json +0 -18
- package/src/__tests__/pi-launch.test.ts +0 -202
- package/src/__tests__/registry.test.ts +0 -1580
- package/src/__tests__/scripted-provider/delegate-ambient-provider.test.ts +0 -130
- package/src/__tests__/scripted-provider/delegate-child-guard.test.ts +0 -631
- package/src/__tests__/scripted-provider/delegate-guard-provider.ts +0 -403
- package/src/__tests__/scripted-provider/follow-up.test.ts +0 -448
- package/src/__tests__/scripted-provider/fusion-output-recovery.test.ts +0 -132
- package/src/__tests__/scripted-provider/fusion-reason.test.ts +0 -310
- package/src/__tests__/scripted-provider/fusion-runtime-guard.test.ts +0 -163
- package/src/__tests__/scripted-provider/hook-contract-provider.ts +0 -179
- package/src/__tests__/scripted-provider/hook-probe-a.ts +0 -3
- package/src/__tests__/scripted-provider/hook-probe-b.ts +0 -3
- package/src/__tests__/scripted-provider/hook-probe-extension.ts +0 -126
- package/src/__tests__/scripted-provider/output-recovery-provider.ts +0 -153
- package/src/__tests__/scripted-provider/pi-hook-contract-evidence.json +0 -18
- package/src/__tests__/scripted-provider/pi-hook-contract.test.ts +0 -477
- package/src/__tests__/scripted-provider/runtime-guard-probe.ts +0 -28
- package/src/__tests__/scripted-provider/runtime-guard-provider.ts +0 -49
- package/src/__tests__/scripted-provider/scripted-provider-extension.ts +0 -408
- package/src/__tests__/task-manager.test.ts +0 -479
- package/src/__tests__/windows-taskkill.test.ts +0 -161
|
@@ -1,485 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict';
|
|
2
|
-
import { createHash } from 'node:crypto';
|
|
3
|
-
import * as http from 'node:http';
|
|
4
|
-
import type { AddressInfo } from 'node:net';
|
|
5
|
-
import { describe, it } from 'node:test';
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
FUSION_WEB_FETCH_MAX_REDIRECTS,
|
|
9
|
-
FUSION_WEB_FETCH_MAX_RESPONSE_BYTES,
|
|
10
|
-
FUSION_WEB_FETCH_MAX_OUTPUT_BYTES,
|
|
11
|
-
FUSION_WEB_FETCH_TIMEOUT_MS,
|
|
12
|
-
FusionWebFetchError,
|
|
13
|
-
type FusionWebFetchErrorCode,
|
|
14
|
-
type FusionWebFetchOptions,
|
|
15
|
-
fusionWebFetch,
|
|
16
|
-
} from '../fusion/web-fetch.js';
|
|
17
|
-
|
|
18
|
-
type RouteHandler = (request: http.IncomingMessage, response: http.ServerResponse) => void;
|
|
19
|
-
|
|
20
|
-
async function withServer<T>(routes: Record<string, RouteHandler>, run: (baseUrl: string) => Promise<T>): Promise<T> {
|
|
21
|
-
const server = http.createServer((request, response) => {
|
|
22
|
-
const path = request.url?.split('?')[0] ?? '/';
|
|
23
|
-
const handler = routes[path];
|
|
24
|
-
if (handler === undefined) {
|
|
25
|
-
response.writeHead(404, { 'content-type': 'text/plain' });
|
|
26
|
-
response.end('missing route');
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
handler(request, response);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
await new Promise<void>((resolve, reject) => {
|
|
33
|
-
server.once('error', reject);
|
|
34
|
-
server.listen(0, '127.0.0.1', () => {
|
|
35
|
-
server.off('error', reject);
|
|
36
|
-
resolve();
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
const address = server.address();
|
|
42
|
-
assert.ok(address !== null && typeof address === 'object');
|
|
43
|
-
return await run(`http://public.test:${String((address as AddressInfo).port)}`);
|
|
44
|
-
} finally {
|
|
45
|
-
await new Promise<void>((resolve, reject) => {
|
|
46
|
-
server.close((error) => {
|
|
47
|
-
if (error !== undefined) reject(error);
|
|
48
|
-
else resolve();
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function localOptions(extra: FusionWebFetchOptions = {}): FusionWebFetchOptions {
|
|
55
|
-
return {
|
|
56
|
-
lookup: async () => [{ address: '127.0.0.1', family: 4 }],
|
|
57
|
-
allowBlockedAddressesForTests: true,
|
|
58
|
-
...extra,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async function expectError(
|
|
63
|
-
promise: Promise<unknown>,
|
|
64
|
-
code: FusionWebFetchErrorCode,
|
|
65
|
-
): Promise<FusionWebFetchError> {
|
|
66
|
-
let captured: FusionWebFetchError | undefined;
|
|
67
|
-
await assert.rejects(promise, (error: unknown) => {
|
|
68
|
-
assert.ok(error instanceof FusionWebFetchError);
|
|
69
|
-
assert.equal(error.code, code);
|
|
70
|
-
captured = error;
|
|
71
|
-
return true;
|
|
72
|
-
});
|
|
73
|
-
assert.ok(captured !== undefined);
|
|
74
|
-
return captured;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
void describe('fusion_web_fetch core', () => {
|
|
78
|
-
void it('pins the expanded research fetch envelope', () => {
|
|
79
|
-
assert.equal(FUSION_WEB_FETCH_TIMEOUT_MS, 90_000);
|
|
80
|
-
assert.equal(FUSION_WEB_FETCH_MAX_RESPONSE_BYTES, 4 * 1024 * 1024);
|
|
81
|
-
assert.equal(FUSION_WEB_FETCH_MAX_OUTPUT_BYTES, 32 * 1024);
|
|
82
|
-
assert.equal(FUSION_WEB_FETCH_MAX_REDIRECTS, 5);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
void it('rejects unsupported schemes before network access', async () => {
|
|
86
|
-
let lookupCalls = 0;
|
|
87
|
-
await expectError(
|
|
88
|
-
fusionWebFetch({ url: 'file:///tmp/page.html' }, { lookup: async () => {
|
|
89
|
-
lookupCalls += 1;
|
|
90
|
-
return [{ address: '93.184.216.34', family: 4 }];
|
|
91
|
-
} }),
|
|
92
|
-
'unsupported_scheme',
|
|
93
|
-
);
|
|
94
|
-
assert.equal(lookupCalls, 0);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
void it('rejects URL credentials before network access', async () => {
|
|
98
|
-
let lookupCalls = 0;
|
|
99
|
-
await expectError(
|
|
100
|
-
fusionWebFetch({ url: 'https://user:pass@example.com/' }, { lookup: async () => {
|
|
101
|
-
lookupCalls += 1;
|
|
102
|
-
return [{ address: '93.184.216.34', family: 4 }];
|
|
103
|
-
} }),
|
|
104
|
-
'invalid_url',
|
|
105
|
-
);
|
|
106
|
-
assert.equal(lookupCalls, 0);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
void it('rejects blocked address classes from DNS answers', async () => {
|
|
110
|
-
const blocked = [
|
|
111
|
-
{ address: '127.0.0.1', family: 4 },
|
|
112
|
-
{ address: '169.254.1.2', family: 4 },
|
|
113
|
-
{ address: '169.254.169.254', family: 4 },
|
|
114
|
-
{ address: '::ffff:127.0.0.1', family: 6 },
|
|
115
|
-
{ address: '64:ff9b::a00:1', family: 6 },
|
|
116
|
-
{ address: '64:ff9b:1::a00:1', family: 6 },
|
|
117
|
-
{ address: 'fe80::1', family: 6 },
|
|
118
|
-
{ address: 'fc00::1', family: 6 },
|
|
119
|
-
{ address: 'ff02::1', family: 6 },
|
|
120
|
-
] as const;
|
|
121
|
-
|
|
122
|
-
for (const address of blocked) {
|
|
123
|
-
await expectError(
|
|
124
|
-
fusionWebFetch({ url: 'http://public.test/' }, { lookup: async () => [address] }),
|
|
125
|
-
'blocked_address',
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
void it('consults the resolver exactly once and pins the connection to that vetted address', async () => {
|
|
131
|
-
// DNS-rebinding defence, expressed as the property that actually holds.
|
|
132
|
-
//
|
|
133
|
-
// Validating resolver answers is not sufficient on its own: a name can resolve again
|
|
134
|
-
// between validation and connect, so the socket could land somewhere never vetted. That
|
|
135
|
-
// is the window left open by implementations that validate and then delegate to global
|
|
136
|
-
// fetch, which re-resolves internally. Here the vetted address is captured once and the
|
|
137
|
-
// per-request lookup hook can return only that address, so a second resolver answer can
|
|
138
|
-
// never influence where the socket terminates. The post-connect socket check is then
|
|
139
|
-
// defence in depth rather than the primary control.
|
|
140
|
-
//
|
|
141
|
-
// A rebinding attempt is simulated by a resolver that would hand back loopback on every
|
|
142
|
-
// call after the first. If the implementation re-queried at connect time, the request
|
|
143
|
-
// would reach the loopback server and this assertion on the call count would fail.
|
|
144
|
-
let calls = 0;
|
|
145
|
-
await withServer(
|
|
146
|
-
{
|
|
147
|
-
'/': (_request, response) => {
|
|
148
|
-
response.writeHead(200, { 'content-type': 'text/plain' });
|
|
149
|
-
response.end('should never be reached');
|
|
150
|
-
},
|
|
151
|
-
},
|
|
152
|
-
async (baseUrl) => {
|
|
153
|
-
const rebindingLookup = async () => {
|
|
154
|
-
calls += 1;
|
|
155
|
-
return calls === 1
|
|
156
|
-
? [{ address: '127.0.0.1', family: 4 as const }]
|
|
157
|
-
: [{ address: '10.0.0.1', family: 4 as const }];
|
|
158
|
-
};
|
|
159
|
-
const result = await fusionWebFetch(
|
|
160
|
-
{ url: `${baseUrl}/` },
|
|
161
|
-
{ lookup: rebindingLookup, allowBlockedAddressesForTests: true },
|
|
162
|
-
);
|
|
163
|
-
assert.equal(result.status, 200);
|
|
164
|
-
},
|
|
165
|
-
);
|
|
166
|
-
assert.equal(calls, 1, 'the resolver must be consulted once; the vetted address is then pinned');
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
void it('rejects a DNS result that mixes public and blocked answers', async () => {
|
|
170
|
-
await expectError(
|
|
171
|
-
fusionWebFetch({ url: 'http://public.test/' }, {
|
|
172
|
-
lookup: async () => [
|
|
173
|
-
{ address: '93.184.216.34', family: 4 },
|
|
174
|
-
{ address: '10.0.0.1', family: 4 },
|
|
175
|
-
],
|
|
176
|
-
}),
|
|
177
|
-
'blocked_address',
|
|
178
|
-
);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
void it('follows a redirect chain', async () => {
|
|
182
|
-
await withServer(
|
|
183
|
-
{
|
|
184
|
-
'/start': (_request, response) => {
|
|
185
|
-
response.writeHead(302, { location: '/middle' });
|
|
186
|
-
response.end();
|
|
187
|
-
},
|
|
188
|
-
'/middle': (_request, response) => {
|
|
189
|
-
response.writeHead(301, { location: '/final' });
|
|
190
|
-
response.end();
|
|
191
|
-
},
|
|
192
|
-
'/final': (_request, response) => {
|
|
193
|
-
response.writeHead(200, { 'content-type': 'text/plain' });
|
|
194
|
-
response.end('done');
|
|
195
|
-
},
|
|
196
|
-
},
|
|
197
|
-
async (baseUrl) => {
|
|
198
|
-
const result = await fusionWebFetch({ url: `${baseUrl}/start` }, localOptions());
|
|
199
|
-
assert.equal(result.final_url, `${baseUrl}/final`);
|
|
200
|
-
assert.equal(result.status, 200);
|
|
201
|
-
assert.equal(result.content, 'done');
|
|
202
|
-
},
|
|
203
|
-
);
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
void it('destroys redirect responses without consuming unbounded bodies', async () => {
|
|
207
|
-
let redirectClosed = false;
|
|
208
|
-
await withServer(
|
|
209
|
-
{
|
|
210
|
-
'/start': (_request, response) => {
|
|
211
|
-
const interval = setInterval(() => response.write('unbounded redirect body'), 5);
|
|
212
|
-
response.once('close', () => {
|
|
213
|
-
redirectClosed = true;
|
|
214
|
-
clearInterval(interval);
|
|
215
|
-
});
|
|
216
|
-
response.writeHead(302, { location: '/final', 'content-type': 'text/plain' });
|
|
217
|
-
response.flushHeaders();
|
|
218
|
-
},
|
|
219
|
-
'/final': (_request, response) => {
|
|
220
|
-
response.writeHead(200, { 'content-type': 'text/plain' });
|
|
221
|
-
response.end('done');
|
|
222
|
-
},
|
|
223
|
-
},
|
|
224
|
-
async (baseUrl) => {
|
|
225
|
-
const result = await fusionWebFetch({ url: `${baseUrl}/start` }, localOptions());
|
|
226
|
-
assert.equal(result.content, 'done');
|
|
227
|
-
},
|
|
228
|
-
);
|
|
229
|
-
assert.equal(redirectClosed, true);
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
void it('rejects a redirect to a blocked host', async () => {
|
|
233
|
-
await withServer(
|
|
234
|
-
{
|
|
235
|
-
'/start': (_request, response) => {
|
|
236
|
-
response.writeHead(302, { location: 'http://localhost/final' });
|
|
237
|
-
response.end();
|
|
238
|
-
},
|
|
239
|
-
},
|
|
240
|
-
async (baseUrl) => {
|
|
241
|
-
await expectError(fusionWebFetch({ url: `${baseUrl}/start` }, localOptions()), 'redirect_blocked');
|
|
242
|
-
},
|
|
243
|
-
);
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
void it('rejects a redirect loop at the configured limit', async () => {
|
|
247
|
-
await withServer(
|
|
248
|
-
{
|
|
249
|
-
'/loop': (_request, response) => {
|
|
250
|
-
response.writeHead(302, { location: '/loop' });
|
|
251
|
-
response.end();
|
|
252
|
-
},
|
|
253
|
-
},
|
|
254
|
-
async (baseUrl) => {
|
|
255
|
-
await expectError(
|
|
256
|
-
fusionWebFetch({ url: `${baseUrl}/loop` }, localOptions({ maxRedirects: 2 })),
|
|
257
|
-
'redirect_limit',
|
|
258
|
-
);
|
|
259
|
-
},
|
|
260
|
-
);
|
|
261
|
-
assert.equal(FUSION_WEB_FETCH_MAX_REDIRECTS, 5);
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
void it('rejects an oversized Content-Length before reading body bytes', async () => {
|
|
265
|
-
let bodyWriteCount = 0;
|
|
266
|
-
await withServer(
|
|
267
|
-
{
|
|
268
|
-
'/large': (_request, response) => {
|
|
269
|
-
response.writeHead(200, { 'content-type': 'text/plain', 'content-length': '10' });
|
|
270
|
-
response.flushHeaders();
|
|
271
|
-
setTimeout(() => {
|
|
272
|
-
bodyWriteCount += 1;
|
|
273
|
-
response.end('0123456789');
|
|
274
|
-
}, 50);
|
|
275
|
-
},
|
|
276
|
-
},
|
|
277
|
-
async (baseUrl) => {
|
|
278
|
-
await expectError(
|
|
279
|
-
fusionWebFetch({ url: `${baseUrl}/large` }, localOptions({ maxResponseBytes: 5 })),
|
|
280
|
-
'response_too_large',
|
|
281
|
-
);
|
|
282
|
-
},
|
|
283
|
-
);
|
|
284
|
-
assert.equal(bodyWriteCount, 0);
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
void it('aborts loudly when a streamed body crosses the response cap', async () => {
|
|
288
|
-
await withServer(
|
|
289
|
-
{
|
|
290
|
-
'/stream': (_request, response) => {
|
|
291
|
-
response.writeHead(200, { 'content-type': 'text/plain' });
|
|
292
|
-
response.write('12345');
|
|
293
|
-
response.end('67890');
|
|
294
|
-
},
|
|
295
|
-
},
|
|
296
|
-
async (baseUrl) => {
|
|
297
|
-
await expectError(
|
|
298
|
-
fusionWebFetch({ url: `${baseUrl}/stream` }, localOptions({ maxResponseBytes: 8 })),
|
|
299
|
-
'response_too_large',
|
|
300
|
-
);
|
|
301
|
-
},
|
|
302
|
-
);
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
void it('rejects unsupported content types', async () => {
|
|
306
|
-
await withServer(
|
|
307
|
-
{
|
|
308
|
-
'/json': (_request, response) => {
|
|
309
|
-
response.writeHead(200, { 'content-type': 'application/json' });
|
|
310
|
-
response.end('{"ok":true}');
|
|
311
|
-
},
|
|
312
|
-
},
|
|
313
|
-
async (baseUrl) => {
|
|
314
|
-
await expectError(fusionWebFetch({ url: `${baseUrl}/json` }, localOptions()), 'unsupported_content_type');
|
|
315
|
-
},
|
|
316
|
-
);
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
void it('converts HTML to markdown with links, headings, tables, and code blocks', async () => {
|
|
320
|
-
const html = `<!doctype html><html><body>
|
|
321
|
-
<h1>Title</h1>
|
|
322
|
-
<p>Visit <a href="https://example.com/docs">docs</a>.</p>
|
|
323
|
-
<table><tr><th>Name</th><th>Value</th></tr><tr><td>alpha</td><td>one</td></tr></table>
|
|
324
|
-
<pre><code>const x = 1;</code></pre>
|
|
325
|
-
</body></html>`;
|
|
326
|
-
await withServer(
|
|
327
|
-
{
|
|
328
|
-
'/html': (_request, response) => {
|
|
329
|
-
response.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
|
|
330
|
-
response.end(html);
|
|
331
|
-
},
|
|
332
|
-
},
|
|
333
|
-
async (baseUrl) => {
|
|
334
|
-
const result = await fusionWebFetch({ url: `${baseUrl}/html` }, localOptions());
|
|
335
|
-
assert.equal(result.format, 'markdown');
|
|
336
|
-
assert.match(result.content, /# Title/u);
|
|
337
|
-
assert.match(result.content, /\[docs\]\(https:\/\/example\.com\/docs\)/u);
|
|
338
|
-
assert.match(result.content, /\| Name \| Value \|/u);
|
|
339
|
-
assert.match(result.content, /\| alpha \| one \|/u);
|
|
340
|
-
assert.match(result.content, /```/u);
|
|
341
|
-
assert.match(result.content, /const x = 1;/u);
|
|
342
|
-
},
|
|
343
|
-
);
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
void it('strips script and style content before extraction', async () => {
|
|
347
|
-
await withServer(
|
|
348
|
-
{
|
|
349
|
-
'/html': (_request, response) => {
|
|
350
|
-
response.writeHead(200, { 'content-type': 'text/html' });
|
|
351
|
-
response.end('<h1>Keep</h1><script>secret()</script><style>.secret{}</style><noscript>hidden</noscript>');
|
|
352
|
-
},
|
|
353
|
-
},
|
|
354
|
-
async (baseUrl) => {
|
|
355
|
-
const result = await fusionWebFetch({ url: `${baseUrl}/html` }, localOptions());
|
|
356
|
-
assert.match(result.content, /Keep/u);
|
|
357
|
-
assert.doesNotMatch(result.content, /secret|hidden/u);
|
|
358
|
-
},
|
|
359
|
-
);
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
void it('passes text/plain through and reports hash and byte count', async () => {
|
|
363
|
-
const body = 'plain\ntext & symbols';
|
|
364
|
-
await withServer(
|
|
365
|
-
{
|
|
366
|
-
'/plain': (_request, response) => {
|
|
367
|
-
response.writeHead(200, { 'content-type': 'text/plain; charset=utf-8' });
|
|
368
|
-
response.end(body);
|
|
369
|
-
},
|
|
370
|
-
},
|
|
371
|
-
async (baseUrl) => {
|
|
372
|
-
const result = await fusionWebFetch({ url: `${baseUrl}/plain`, extract: 'markdown' }, localOptions());
|
|
373
|
-
assert.equal(result.format, 'text');
|
|
374
|
-
assert.equal(result.content, body);
|
|
375
|
-
assert.equal(result.response_bytes, Buffer.byteLength(body));
|
|
376
|
-
assert.equal(result.content_sha256, createHash('sha256').update(body).digest('hex'));
|
|
377
|
-
},
|
|
378
|
-
);
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
void it('caps output without splitting UTF-8 characters', async () => {
|
|
382
|
-
await withServer(
|
|
383
|
-
{
|
|
384
|
-
'/emoji': (_request, response) => {
|
|
385
|
-
response.writeHead(200, { 'content-type': 'text/plain; charset=utf-8' });
|
|
386
|
-
response.end('😀😀😀');
|
|
387
|
-
},
|
|
388
|
-
},
|
|
389
|
-
async (baseUrl) => {
|
|
390
|
-
const result = await fusionWebFetch({ url: `${baseUrl}/emoji` }, localOptions({ maxOutputBytes: 5 }));
|
|
391
|
-
assert.equal(result.truncated, true);
|
|
392
|
-
assert.equal(result.content, '😀');
|
|
393
|
-
assert.equal(Buffer.byteLength(result.content), 4);
|
|
394
|
-
},
|
|
395
|
-
);
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
void it('includes content extraction inside the fetch deadline', async () => {
|
|
399
|
-
await withServer(
|
|
400
|
-
{
|
|
401
|
-
'/slow-extraction': (_request, response) => {
|
|
402
|
-
response.writeHead(200, { 'content-type': 'text/html' });
|
|
403
|
-
response.end('<h1>ready</h1>');
|
|
404
|
-
},
|
|
405
|
-
},
|
|
406
|
-
async (baseUrl) => {
|
|
407
|
-
const start = Date.now();
|
|
408
|
-
await expectError(
|
|
409
|
-
fusionWebFetch(
|
|
410
|
-
{ url: `${baseUrl}/slow-extraction` },
|
|
411
|
-
localOptions({
|
|
412
|
-
timeoutMs: 20,
|
|
413
|
-
extractContent: async () => {
|
|
414
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
415
|
-
return { content: 'late extraction', format: 'markdown' };
|
|
416
|
-
},
|
|
417
|
-
}),
|
|
418
|
-
),
|
|
419
|
-
'request_timeout',
|
|
420
|
-
);
|
|
421
|
-
assert.ok(Date.now() - start < 90, 'extraction must be raced against remaining deadline');
|
|
422
|
-
},
|
|
423
|
-
);
|
|
424
|
-
});
|
|
425
|
-
|
|
426
|
-
void it('includes DNS resolution inside the fetch deadline', async () => {
|
|
427
|
-
const start = Date.now();
|
|
428
|
-
await expectError(
|
|
429
|
-
fusionWebFetch(
|
|
430
|
-
{ url: 'https://dns-stall.test/' },
|
|
431
|
-
{ lookup: () => new Promise(() => undefined), timeoutMs: 20 },
|
|
432
|
-
),
|
|
433
|
-
'request_timeout',
|
|
434
|
-
);
|
|
435
|
-
assert.ok(Date.now() - start < 500, 'stalled DNS must be bounded by the request deadline');
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
void it('subtracts delayed successful DNS time from the HTTP deadline', async () => {
|
|
439
|
-
await withServer(
|
|
440
|
-
{
|
|
441
|
-
'/slow-after-dns': (_request, response) => {
|
|
442
|
-
setTimeout(() => {
|
|
443
|
-
response.writeHead(200, { 'content-type': 'text/plain' });
|
|
444
|
-
response.end('too late');
|
|
445
|
-
}, 40);
|
|
446
|
-
},
|
|
447
|
-
},
|
|
448
|
-
async (baseUrl) => {
|
|
449
|
-
await expectError(
|
|
450
|
-
fusionWebFetch(
|
|
451
|
-
{ url: `${baseUrl}/slow-after-dns` },
|
|
452
|
-
{
|
|
453
|
-
lookup: async () => {
|
|
454
|
-
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
455
|
-
return [{ address: '127.0.0.1', family: 4 }];
|
|
456
|
-
},
|
|
457
|
-
allowBlockedAddressesForTests: true,
|
|
458
|
-
timeoutMs: 50,
|
|
459
|
-
},
|
|
460
|
-
),
|
|
461
|
-
'request_timeout',
|
|
462
|
-
);
|
|
463
|
-
},
|
|
464
|
-
);
|
|
465
|
-
});
|
|
466
|
-
|
|
467
|
-
void it('fails with a typed timeout', async () => {
|
|
468
|
-
await withServer(
|
|
469
|
-
{
|
|
470
|
-
'/slow': (_request, response) => {
|
|
471
|
-
setTimeout(() => {
|
|
472
|
-
response.writeHead(200, { 'content-type': 'text/plain' });
|
|
473
|
-
response.end('late');
|
|
474
|
-
}, 100);
|
|
475
|
-
},
|
|
476
|
-
},
|
|
477
|
-
async (baseUrl) => {
|
|
478
|
-
await expectError(
|
|
479
|
-
fusionWebFetch({ url: `${baseUrl}/slow` }, localOptions({ timeoutMs: 20 })),
|
|
480
|
-
'request_timeout',
|
|
481
|
-
);
|
|
482
|
-
},
|
|
483
|
-
);
|
|
484
|
-
});
|
|
485
|
-
});
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { describe, it } from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import {
|
|
4
|
-
FUSION_INVESTIGATE,
|
|
5
|
-
FUSION_INVESTIGATE_WORKFLOW,
|
|
6
|
-
FUSION_REASON,
|
|
7
|
-
FUSION_REASON_WORKFLOW,
|
|
8
|
-
FUSION_RESEARCH,
|
|
9
|
-
FUSION_RESEARCH_WORKFLOW,
|
|
10
|
-
FUSION_VALIDATE,
|
|
11
|
-
FUSION_VALIDATE_WORKFLOW,
|
|
12
|
-
FUSION_WORKFLOW_PROFILES,
|
|
13
|
-
assertWorkflowCapability,
|
|
14
|
-
fusionWorkflowProfile,
|
|
15
|
-
} from '../fusion/workflows.js';
|
|
16
|
-
import { FUSION_WORKFLOW_IDS } from '../fusion/types.js';
|
|
17
|
-
|
|
18
|
-
void describe('fusion fixed v1 workflows', () => {
|
|
19
|
-
void it('exports exactly four public workflow profiles with fixed policies', () => {
|
|
20
|
-
assert.deepEqual([...FUSION_WORKFLOW_IDS], ['reason', 'investigate', 'research', 'validate']);
|
|
21
|
-
assert.deepEqual(FUSION_WORKFLOW_PROFILES.map((profile) => profile.publicName), ['fusion_reason', 'fusion_investigate', 'fusion_research', 'fusion_validate']);
|
|
22
|
-
assert.equal(FUSION_REASON, FUSION_REASON_WORKFLOW);
|
|
23
|
-
assert.equal(FUSION_INVESTIGATE, FUSION_INVESTIGATE_WORKFLOW);
|
|
24
|
-
assert.equal(FUSION_RESEARCH, FUSION_RESEARCH_WORKFLOW);
|
|
25
|
-
assert.equal(FUSION_VALIDATE, FUSION_VALIDATE_WORKFLOW);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
void it('pins context and tool policies by workflow', () => {
|
|
29
|
-
assert.equal(FUSION_REASON_WORKFLOW.contextKind, 'session_projection');
|
|
30
|
-
assert.equal(FUSION_REASON_WORKFLOW.candidateCapability, 'reason');
|
|
31
|
-
assert.deepEqual(FUSION_REASON_WORKFLOW.candidateTools, []);
|
|
32
|
-
assert.equal(FUSION_INVESTIGATE_WORKFLOW.contextKind, 'clean_task');
|
|
33
|
-
assert.equal(FUSION_INVESTIGATE_WORKFLOW.candidateCapability, 'inspect');
|
|
34
|
-
assert.deepEqual(FUSION_INVESTIGATE_WORKFLOW.candidateTools, ['read', 'grep', 'find', 'ls']);
|
|
35
|
-
assert.equal(FUSION_RESEARCH_WORKFLOW.contextKind, 'clean_task');
|
|
36
|
-
assert.equal(FUSION_RESEARCH_WORKFLOW.candidateCapability, 'research');
|
|
37
|
-
assert.ok(FUSION_RESEARCH_WORKFLOW.candidateTools.includes('fusion_web_fetch'));
|
|
38
|
-
assert.equal(FUSION_VALIDATE_WORKFLOW.contextKind, 'clean_task');
|
|
39
|
-
assert.equal(FUSION_VALIDATE_WORKFLOW.candidateCapability, 'inspect');
|
|
40
|
-
for (const profile of FUSION_WORKFLOW_PROFILES) {
|
|
41
|
-
assert.equal(profile.evaluatorCapability, 'reason');
|
|
42
|
-
assert.equal(profile.mergeCapability, 'reason');
|
|
43
|
-
assert.deepEqual(profile.evaluatorTools, []);
|
|
44
|
-
assert.deepEqual(profile.mergeTools, []);
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
void it('rejects mismatched caller capabilities instead of substituting', () => {
|
|
49
|
-
assert.equal(assertWorkflowCapability(FUSION_REASON_WORKFLOW, undefined), 'reason');
|
|
50
|
-
assert.equal(assertWorkflowCapability(FUSION_VALIDATE_WORKFLOW, 'inspect'), 'inspect');
|
|
51
|
-
assert.throws(() => assertWorkflowCapability(FUSION_VALIDATE_WORKFLOW, 'reason'), /always runs candidates with the inspect capability/);
|
|
52
|
-
assert.throws(() => assertWorkflowCapability(FUSION_RESEARCH_WORKFLOW, 'inspect'), /always runs candidates with the research capability/);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
void it('resolves every declared workflow id and fails closed on unknown ids', () => {
|
|
56
|
-
for (const id of FUSION_WORKFLOW_IDS) assert.equal(fusionWorkflowProfile(id).id, id);
|
|
57
|
-
assert.throws(() => fusionWorkflowProfile('brainstorm' as never), /unknown fusion workflow/);
|
|
58
|
-
});
|
|
59
|
-
});
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import type { SessionMessageEntry } from '@earendil-works/pi-coding-agent';
|
|
2
|
-
import type { AssistantMessage, ToolResultMessage, UserMessage } from '@earendil-works/pi-ai';
|
|
3
|
-
import { buildDelegateSeed, type BuiltDelegateSeed } from '../../delegate/seed.js';
|
|
4
|
-
import type { DelegateLimits, DelegatePinnedRoute } from '../../delegate/types.js';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Fully deterministic delegate seed fixture.
|
|
8
|
-
*
|
|
9
|
-
* Pi assigns random ids to session entries, so a `SessionManager` cannot be used
|
|
10
|
-
* to prove cross-process byte stability: its leaf id legitimately differs every
|
|
11
|
-
* time. This fixture supplies a hand-built session view with fixed entry ids, so
|
|
12
|
-
* the only thing that can vary between two builds is the projection itself.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
const ROUTE: DelegatePinnedRoute = {
|
|
16
|
-
provider: 'anthropic',
|
|
17
|
-
model: 'claude-test',
|
|
18
|
-
qualified_id: 'anthropic/claude-test',
|
|
19
|
-
context_window_tokens: 200_000,
|
|
20
|
-
thinking_level: 'medium',
|
|
21
|
-
origin: 'parent_current',
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const LIMITS: DelegateLimits = {
|
|
25
|
-
max_turns: 24,
|
|
26
|
-
max_tool_calls: 120,
|
|
27
|
-
timeout_seconds: 900,
|
|
28
|
-
max_tool_result_bytes: 65_536,
|
|
29
|
-
max_total_tool_output_bytes: 67_108_864,
|
|
30
|
-
max_answer_bytes: 4_194_304,
|
|
31
|
-
allowed_input_tokens: 171_712,
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
function messageEntry(
|
|
35
|
-
id: string,
|
|
36
|
-
parentId: string | null,
|
|
37
|
-
message: UserMessage | AssistantMessage | ToolResultMessage,
|
|
38
|
-
): SessionMessageEntry {
|
|
39
|
-
return { type: 'message', id, parentId, timestamp: '2024-01-01T00:00:00.000Z', message };
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const ENTRIES: readonly SessionMessageEntry[] = [
|
|
43
|
-
messageEntry('e0000001', null, {
|
|
44
|
-
role: 'user',
|
|
45
|
-
content: 'VISIBLE_USER_ONE about the failing test',
|
|
46
|
-
timestamp: 1,
|
|
47
|
-
}),
|
|
48
|
-
messageEntry('e0000002', 'e0000001', {
|
|
49
|
-
role: 'assistant',
|
|
50
|
-
api: 'openai-codex-responses',
|
|
51
|
-
provider: 'openai-codex',
|
|
52
|
-
model: 'gpt-5.5',
|
|
53
|
-
usage: {
|
|
54
|
-
input: 1,
|
|
55
|
-
output: 1,
|
|
56
|
-
cacheRead: 0,
|
|
57
|
-
cacheWrite: 0,
|
|
58
|
-
totalTokens: 2,
|
|
59
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
60
|
-
},
|
|
61
|
-
stopReason: 'toolUse',
|
|
62
|
-
content: [
|
|
63
|
-
{ type: 'text', text: 'VISIBLE_ASSISTANT_ONE here is my reading' },
|
|
64
|
-
{ type: 'thinking', thinking: 'SECRET_THINKING_PAYLOAD', thinkingSignature: '' },
|
|
65
|
-
{ type: 'toolCall', id: 'c1', name: 'read', arguments: { path: '/SECRET_TOOL_ARGUMENT' } },
|
|
66
|
-
],
|
|
67
|
-
timestamp: 2,
|
|
68
|
-
}),
|
|
69
|
-
messageEntry('e0000003', 'e0000002', {
|
|
70
|
-
role: 'toolResult',
|
|
71
|
-
toolCallId: 'c1',
|
|
72
|
-
toolName: 'read',
|
|
73
|
-
content: [{ type: 'text', text: 'SECRET_TOOL_RESULT_PAYLOAD' }],
|
|
74
|
-
isError: false,
|
|
75
|
-
timestamp: 3,
|
|
76
|
-
}),
|
|
77
|
-
messageEntry('e0000004', 'e0000003', {
|
|
78
|
-
role: 'user',
|
|
79
|
-
content: 'VISIBLE_USER_TWO follow-up',
|
|
80
|
-
timestamp: 4,
|
|
81
|
-
}),
|
|
82
|
-
];
|
|
83
|
-
|
|
84
|
-
/** Read-only session view with fixed ids and a fixed leaf. */
|
|
85
|
-
export const DETERMINISTIC_SESSION = {
|
|
86
|
-
getLeafId: (): string | null => 'e0000004',
|
|
87
|
-
getLeafEntry: (): SessionMessageEntry | undefined => ENTRIES[ENTRIES.length - 1],
|
|
88
|
-
getEntries: (): SessionMessageEntry[] => [...ENTRIES],
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
export function buildDeterministicFixtureSeed(): BuiltDelegateSeed {
|
|
92
|
-
return buildDelegateSeed(
|
|
93
|
-
{
|
|
94
|
-
cwd: '/tmp/project',
|
|
95
|
-
sessionManager: DETERMINISTIC_SESSION,
|
|
96
|
-
getSystemPrompt: () => 'parent system prompt',
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
taskId: 'd0123456789abcdef0123456789abcdef',
|
|
100
|
-
launchNonce: 'ffeeddccbbaa99887766554433221100',
|
|
101
|
-
toolCallId: 'delegate-call-1',
|
|
102
|
-
directive: 'investigate the failing gate',
|
|
103
|
-
capability: 'inspect',
|
|
104
|
-
extensionMode: 'isolated',
|
|
105
|
-
route: ROUTE,
|
|
106
|
-
limits: LIMITS,
|
|
107
|
-
},
|
|
108
|
-
);
|
|
109
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { buildDeterministicFixtureSeed } from './delegate-deterministic-seed.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Cross-process determinism probe.
|
|
5
|
-
*
|
|
6
|
-
* Builds the shared deterministic seed fixture in a fresh Node process and
|
|
7
|
-
* prints its digest, so the unit test can prove the seed bytes do not depend on
|
|
8
|
-
* process-local state such as Map iteration order or module load order.
|
|
9
|
-
*/
|
|
10
|
-
process.stdout.write(`${buildDeterministicFixtureSeed().sha256}\n`);
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
assistantMessage,
|
|
3
|
-
buildFrom,
|
|
4
|
-
toolResultMessage,
|
|
5
|
-
userMessage,
|
|
6
|
-
} from './fusion-canonical.js';
|
|
7
|
-
|
|
8
|
-
const messages = [
|
|
9
|
-
userMessage('repeatable question'),
|
|
10
|
-
assistantMessage([
|
|
11
|
-
{ type: 'thinking', thinking: 'hidden' },
|
|
12
|
-
{ type: 'text', text: 'visible' },
|
|
13
|
-
{ type: 'toolCall', id: 'c1', name: 'bash', arguments: { z: 1, a: 2 } },
|
|
14
|
-
]),
|
|
15
|
-
toolResultMessage('c1', 'bash', [{ type: 'text', text: 'file listing' }]),
|
|
16
|
-
];
|
|
17
|
-
|
|
18
|
-
const built = buildFrom(messages, { source: 'tool', request: 'again' });
|
|
19
|
-
process.stdout.write(
|
|
20
|
-
`${built.serialized}\n${built.input.conversation_projection.accounting.ledger_root_sha256}`,
|
|
21
|
-
);
|