@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,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
- }