@pi-unipi/background-tasks 2.6.3 → 2.9.0

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 +6 -5
  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,403 +0,0 @@
1
- import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
2
- import {
3
- createAssistantMessageEventStream,
4
- type Api,
5
- type AssistantMessage,
6
- type AssistantMessageEventStream,
7
- type Context,
8
- type Model,
9
- type TextContent,
10
- type ToolCall,
11
- } from '@earendil-works/pi-ai';
12
-
13
- /**
14
- * Deterministic provider used by the delegate child-guard gate.
15
- *
16
- * Scenarios drive the guard through spill, budget refusal, route drift, turn
17
- * limits, and the clean commit path, all inside a real Pi agent loop.
18
- */
19
- const PROVIDER = 'pi-bg-delegate';
20
- const MODEL_ID = 'delegate-model';
21
- const API = 'pi-bg-delegate-api';
22
-
23
- const USAGE = {
24
- input: 12,
25
- output: 6,
26
- cacheRead: 0,
27
- cacheWrite: 0,
28
- totalTokens: 18,
29
- cost: { input: 0.001, output: 0.002, cacheRead: 0, cacheWrite: 0, total: 0.003 },
30
- };
31
-
32
- type JsonObject = Record<PropertyKey, unknown>;
33
-
34
- interface ScriptedToolCall extends Omit<ToolCall, 'arguments'> {
35
- arguments: JsonObject;
36
- }
37
-
38
- type ScriptedBlock = TextContent | ScriptedToolCall;
39
-
40
- interface ScriptedAssistantMessage extends Omit<AssistantMessage, 'content' | 'stopReason'> {
41
- content: ScriptedBlock[];
42
- stopReason: 'stop' | 'toolUse' | 'length';
43
- }
44
-
45
- function scenario(): string {
46
- return process.env['UNIPI_BG_DELEGATE_SCENARIO'] ?? 'plain-answer';
47
- }
48
-
49
- function assistant(
50
- content: ScriptedBlock[],
51
- stopReason: 'stop' | 'toolUse' | 'length',
52
- overrides: { provider?: string; model?: string } = {},
53
- ): ScriptedAssistantMessage {
54
- return {
55
- role: 'assistant',
56
- content,
57
- api: API,
58
- provider: overrides.provider ?? PROVIDER,
59
- model: overrides.model ?? MODEL_ID,
60
- usage: USAGE,
61
- stopReason,
62
- timestamp: Date.now(),
63
- };
64
- }
65
-
66
- function pushMessage(stream: AssistantMessageEventStream, message: ScriptedAssistantMessage): void {
67
- const partial: AssistantMessage = { ...message, content: [] };
68
- stream.push({ type: 'start', partial: { ...partial } });
69
- message.content.forEach((block, contentIndex) => {
70
- if (block.type === 'text') {
71
- const partialText: TextContent = { type: 'text', text: '' };
72
- partial.content = [...partial.content, partialText];
73
- stream.push({ type: 'text_start', contentIndex, partial: { ...partial } });
74
- partialText.text = block.text;
75
- stream.push({ type: 'text_delta', contentIndex, delta: block.text, partial: { ...partial } });
76
- stream.push({ type: 'text_end', contentIndex, content: block.text, partial: { ...partial } });
77
- return;
78
- }
79
- const partialToolCall: ToolCall = {
80
- type: 'toolCall',
81
- id: block.id,
82
- name: block.name,
83
- arguments: {},
84
- };
85
- partial.content = [...partial.content, partialToolCall];
86
- stream.push({ type: 'toolcall_start', contentIndex, partial: { ...partial } });
87
- stream.push({
88
- type: 'toolcall_delta',
89
- contentIndex,
90
- delta: JSON.stringify(block.arguments),
91
- partial: { ...partial },
92
- });
93
- partialToolCall.arguments = block.arguments;
94
- stream.push({ type: 'toolcall_end', contentIndex, toolCall: block, partial: { ...partial } });
95
- });
96
- stream.push({ type: 'done', reason: message.stopReason, message });
97
- stream.end(message);
98
- }
99
-
100
- const GuardProbeParams = {
101
- type: 'object',
102
- properties: {
103
- size: { type: 'number' },
104
- marker: { type: 'string' },
105
- },
106
- additionalProperties: false,
107
- } as const;
108
-
109
- const GuardImageParams = {
110
- type: 'object',
111
- properties: {},
112
- additionalProperties: false,
113
- } as const;
114
-
115
- function findSpillReceiptPath(context: Context): string | undefined {
116
- for (const message of context.messages) {
117
- if (message.role !== 'toolResult') continue;
118
- const content = message.content;
119
- if (!Array.isArray(content)) continue;
120
- for (const part of content) {
121
- if (part.type !== 'text') continue;
122
- const match = /spill\/[A-Za-z0-9._-]+\.bin/.exec(part.text);
123
- if (match) return match[0];
124
- }
125
- }
126
- return undefined;
127
- }
128
-
129
- export default function delegateGuardProviderExtension(pi: ExtensionAPI): void {
130
- let calls = 0;
131
-
132
- pi.registerTool<typeof GuardProbeParams, { bytes: number }>({
133
- name: 'guard_probe',
134
- label: 'Guard Probe',
135
- description: 'Delegate guard characterisation probe that emits a payload of a requested size.',
136
- parameters: GuardProbeParams,
137
- execute(_toolCallId, input) {
138
- const size = typeof input.size === 'number' ? input.size : 16;
139
- const marker = typeof input.marker === 'string' ? input.marker : 'HUGEPAYLOAD';
140
- // A repeated marker makes leakage into the transcript unambiguous.
141
- const unit = marker.padEnd(16, '.');
142
- const text = unit.repeat(Math.ceil(size / unit.length)).slice(0, size);
143
- return Promise.resolve({
144
- content: [{ type: 'text' as const, text }],
145
- details: { bytes: text.length },
146
- });
147
- },
148
- });
149
-
150
- pi.registerTool<typeof GuardImageParams, { bytes: number }>({
151
- name: 'guard_image_probe',
152
- label: 'Guard Image Probe',
153
- description: 'Delegate guard probe that emits image-bearing tool content.',
154
- parameters: GuardImageParams,
155
- execute() {
156
- const data = Buffer.from('IMAGE_SENTINEL_BYTES'.repeat(256), 'utf8').toString('base64');
157
- return Promise.resolve({
158
- content: [
159
- { type: 'text' as const, text: 'image preface' },
160
- { type: 'image' as const, data, mimeType: 'image/png' },
161
- ],
162
- details: { bytes: Buffer.byteLength(data, 'utf8') },
163
- });
164
- },
165
- });
166
-
167
- pi.registerProvider(PROVIDER, {
168
- name: 'Pi Background Tasks Delegate Guard Provider',
169
- baseUrl: 'http://localhost:0',
170
- apiKey: 'UNIPI_BG_DELEGATE_API_KEY',
171
- api: API,
172
- models: [
173
- {
174
- id: MODEL_ID,
175
- name: 'Delegate Guard Model',
176
- reasoning: false,
177
- input: ['text'],
178
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
179
- contextWindow: 200_000,
180
- maxTokens: 4096,
181
- },
182
- ],
183
- streamSimple(_model: Model<Api>, context: Context): AssistantMessageEventStream {
184
- calls += 1;
185
- const current = scenario();
186
- const stream = createAssistantMessageEventStream();
187
- const call = calls;
188
- queueMicrotask(() => {
189
- pushMessage(stream, responseFor(current, call, context));
190
- });
191
- return stream;
192
- },
193
- });
194
- }
195
-
196
- function responseFor(
197
- current: string,
198
- call: number,
199
- context: Context,
200
- ): ScriptedAssistantMessage {
201
- if (current === 'invalid-unicode-tool-result') {
202
- if (call === 1) {
203
- return assistant(
204
- [
205
- {
206
- type: 'toolCall',
207
- id: 'guard-call-invalid-unicode',
208
- name: 'guard_probe',
209
- arguments: { size: 16, marker: '\uD800' },
210
- },
211
- ],
212
- 'toolUse',
213
- );
214
- }
215
- return assistant([{ type: 'text', text: 'MUST_NOT_COMMIT' }], 'stop');
216
- }
217
-
218
- if (current === 'intermediate-narration') {
219
- if (call === 1) {
220
- return assistant(
221
- [
222
- { type: 'text', text: 'INTERMEDIATE_NARRATION_MUST_NOT_BE_COMMITTED' },
223
- {
224
- type: 'toolCall',
225
- id: 'guard-call-narration',
226
- name: 'guard_probe',
227
- arguments: { size: 16, marker: 'evidence' },
228
- },
229
- ],
230
- 'toolUse',
231
- );
232
- }
233
- return assistant([{ type: 'text', text: 'DELEGATE_FINAL_ANSWER' }], 'stop');
234
- }
235
-
236
- if (current === 'image-tool-result') {
237
- if (call === 1) {
238
- return assistant(
239
- [
240
- {
241
- type: 'toolCall',
242
- id: 'guard-call-image',
243
- name: 'guard_image_probe',
244
- arguments: {},
245
- },
246
- ],
247
- 'toolUse',
248
- );
249
- }
250
- return assistant([{ type: 'text', text: 'DELEGATE_FINAL_ANSWER' }], 'stop');
251
- }
252
-
253
- if (current === 'huge-tool-result') {
254
- if (call === 1) {
255
- return assistant(
256
- [
257
- {
258
- type: 'toolCall',
259
- id: 'guard-call-huge',
260
- name: 'guard_probe',
261
- arguments: { size: 2 * 1024 * 1024, marker: 'HUGEPAYLOAD' },
262
- },
263
- ],
264
- 'toolUse',
265
- );
266
- }
267
- return assistant([{ type: 'text', text: 'DELEGATE_FINAL_ANSWER' }], 'stop');
268
- }
269
-
270
- if (current === 'split-utf8-range') {
271
- if (call === 1) {
272
- return assistant(
273
- [
274
- {
275
- type: 'toolCall',
276
- id: 'guard-call-utf8',
277
- name: 'guard_probe',
278
- arguments: { size: 2048, marker: 'é' },
279
- },
280
- ],
281
- 'toolUse',
282
- );
283
- }
284
- const artifact = findSpillReceiptPath(context);
285
- if (call === 2 && artifact !== undefined) {
286
- return assistant(
287
- [
288
- {
289
- type: 'toolCall',
290
- id: 'guard-call-split-byte',
291
- name: 'delegate_read_artifact',
292
- arguments: { artifact, offset: 1, length: 1 },
293
- },
294
- ],
295
- 'toolUse',
296
- );
297
- }
298
- return assistant([{ type: 'text', text: 'DELEGATE_FINAL_ANSWER' }], 'stop');
299
- }
300
-
301
- if (current === 'spill-then-read') {
302
- if (call === 1) {
303
- return assistant(
304
- [
305
- {
306
- type: 'toolCall',
307
- id: 'guard-call-spill',
308
- name: 'guard_probe',
309
- arguments: { size: 8192, marker: 'RANGEMARKER' },
310
- },
311
- ],
312
- 'toolUse',
313
- );
314
- }
315
- const artifact = findSpillReceiptPath(context);
316
- if (call === 2 && artifact !== undefined) {
317
- return assistant(
318
- [
319
- {
320
- type: 'toolCall',
321
- id: 'guard-call-range',
322
- name: 'delegate_read_artifact',
323
- arguments: { artifact, offset: 0, length: 11 },
324
- },
325
- ],
326
- 'toolUse',
327
- );
328
- }
329
- if (call === 3 && artifact !== undefined) {
330
- // Deliberately over-long: the bounded reader must refuse rather than
331
- // return a short read.
332
- return assistant(
333
- [
334
- {
335
- type: 'toolCall',
336
- id: 'guard-call-overlong',
337
- name: 'delegate_read_artifact',
338
- arguments: { artifact, offset: 8000, length: 100_000 },
339
- },
340
- ],
341
- 'toolUse',
342
- );
343
- }
344
- return assistant([{ type: 'text', text: 'DELEGATE_FINAL_ANSWER' }], 'stop');
345
- }
346
-
347
- if (current === 'subthreshold-growth') {
348
- if (call <= 8) {
349
- return assistant(
350
- [
351
- {
352
- type: 'toolCall',
353
- id: `guard-call-growth-${String(call)}`,
354
- name: 'guard_probe',
355
- arguments: { size: 32 * 1024, marker: `GROWTH${String(call)}` },
356
- },
357
- ],
358
- 'toolUse',
359
- );
360
- }
361
- return assistant([{ type: 'text', text: 'DELEGATE_FINAL_ANSWER' }], 'stop');
362
- }
363
-
364
- if (current === 'truncated-answer') {
365
- // A response cut short by the output-token limit. Its bytes are intact but
366
- // the answer is incomplete, so it must never be committed as a result.
367
- return assistant([{ type: 'text', text: 'PARTIAL ANSWER CUT OFF' }], 'length');
368
- }
369
-
370
- if (current === 'whitespace-answer') {
371
- return assistant([{ type: 'text', text: ' \n ' }], 'stop');
372
- }
373
-
374
- if (current === 'guard-throw') {
375
- return assistant([{ type: 'text', text: 'GUARD_THROW_SENTINEL_ANSWER' }], 'stop');
376
- }
377
-
378
- if (current === 'route-drift') {
379
- return assistant([{ type: 'text', text: 'DELEGATE_FINAL_ANSWER' }], 'stop', {
380
- provider: 'someone-else',
381
- model: 'other-model',
382
- });
383
- }
384
-
385
- if (current === 'many-turns') {
386
- if (call <= 4) {
387
- return assistant(
388
- [
389
- {
390
- type: 'toolCall',
391
- id: `guard-call-loop-${String(call)}`,
392
- name: 'guard_probe',
393
- arguments: { size: 16, marker: 'LOOP' },
394
- },
395
- ],
396
- 'toolUse',
397
- );
398
- }
399
- return assistant([{ type: 'text', text: 'DELEGATE_FINAL_ANSWER' }], 'stop');
400
- }
401
-
402
- return assistant([{ type: 'text', text: 'DELEGATE_FINAL_ANSWER' }], 'stop');
403
- }