@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,126 +0,0 @@
1
- import { appendFileSync } from 'node:fs';
2
- import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
3
-
4
- /**
5
- * Instrumented probe extension used by the Pi hook-contract characterisation
6
- * gate. Its behaviour is selected by `UNIPI_BG_HOOK_PROBE_MODE` so one extension
7
- * body can answer every question the delegate child-side guard depends on.
8
- */
9
- export type HookProbeMode =
10
- | 'observe'
11
- | 'context-replace'
12
- | 'context-throw'
13
- | 'context-abort'
14
- | 'tool-result-replace';
15
-
16
- type JsonObject = Record<PropertyKey, unknown>;
17
-
18
- export const HOOK_PROBE_INJECTED_TEXT = 'HOOK_PROBE_INJECTED_CONTEXT_MESSAGE';
19
- export const HOOK_PROBE_REPLACED_TOOL_TEXT = 'HOOK_PROBE_REPLACED_TOOL_RESULT';
20
-
21
- function record(event: JsonObject): void {
22
- const path = process.env['UNIPI_BG_HOOK_PROBE_LOG'];
23
- if (!path) return;
24
- appendFileSync(path, `${JSON.stringify(event)}\n`, 'utf8');
25
- }
26
-
27
- function mode(): HookProbeMode {
28
- const value = process.env['UNIPI_BG_HOOK_PROBE_MODE'];
29
- if (
30
- value === 'context-replace' ||
31
- value === 'context-throw' ||
32
- value === 'context-abort' ||
33
- value === 'tool-result-replace'
34
- )
35
- return value;
36
- return 'observe';
37
- }
38
-
39
- interface ContentPartLike {
40
- readonly type: string;
41
- readonly text?: unknown;
42
- }
43
-
44
- function messageText(message: object): string {
45
- const content: unknown = Reflect.get(message, 'content');
46
- if (typeof content === 'string') return content;
47
- if (!Array.isArray(content)) return '';
48
- const parts: readonly ContentPartLike[] = content.filter(
49
- (part): part is ContentPartLike =>
50
- typeof part === 'object' && part !== null && typeof Reflect.get(part, 'type') === 'string',
51
- );
52
- return parts.map((part) => (typeof part.text === 'string' ? part.text : `<${part.type}>`)).join('');
53
- }
54
-
55
- /**
56
- * Build the probe extension factory for one probe identity. Two distinct probe
57
- * modules share this body so handler ordering across separate extensions can be
58
- * observed directly.
59
- */
60
- export function createHookProbeExtension(probeId: string): (pi: ExtensionAPI) => void {
61
- return (pi: ExtensionAPI): void => {
62
- let contextFires = 0;
63
- let toolResultFires = 0;
64
-
65
- pi.on('context', (event, ctx) => {
66
- contextFires += 1;
67
- record({
68
- hook: 'context',
69
- probeId,
70
- contextFires,
71
- roles: event.messages.map((message) => message.role),
72
- texts: event.messages.map(messageText),
73
- });
74
- const selected = mode();
75
- if (selected === 'context-throw' && probeId === 'probe-a') {
76
- record({ hook: 'context_throwing', probeId });
77
- throw new Error('HOOK_PROBE_CONTEXT_THROW');
78
- }
79
- if (selected === 'context-abort' && probeId === 'probe-a') {
80
- record({ hook: 'context_aborting', probeId });
81
- ctx.abort();
82
- return undefined;
83
- }
84
- if (selected === 'context-replace' && probeId === 'probe-a') {
85
- return {
86
- messages: [
87
- ...event.messages,
88
- { role: 'user', content: HOOK_PROBE_INJECTED_TEXT, timestamp: Date.now() },
89
- ],
90
- };
91
- }
92
- return undefined;
93
- });
94
-
95
- pi.on('tool_result', (event) => {
96
- toolResultFires += 1;
97
- record({
98
- hook: 'tool_result',
99
- probeId,
100
- toolResultFires,
101
- toolName: event.toolName,
102
- toolCallId: event.toolCallId,
103
- isError: event.isError,
104
- texts: event.content.map((part) => (part.type === 'text' ? part.text : `<${part.type}>`)),
105
- });
106
- if (mode() === 'tool-result-replace' && probeId === 'probe-a') {
107
- return { content: [{ type: 'text' as const, text: HOOK_PROBE_REPLACED_TOOL_TEXT }] };
108
- }
109
- return undefined;
110
- });
111
-
112
- pi.on('tool_execution_end', (event) => {
113
- record({
114
- hook: 'tool_execution_end',
115
- probeId,
116
- toolName: event.toolName,
117
- toolCallId: event.toolCallId,
118
- isError: event.isError,
119
- });
120
- });
121
-
122
- pi.on('message_end', (event) => {
123
- record({ hook: 'message_end', probeId, role: event.message.role });
124
- });
125
- };
126
- }
@@ -1,153 +0,0 @@
1
- import { appendFileSync, writeFileSync } from 'node:fs';
2
- import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
3
- import {
4
- createAssistantMessageEventStream,
5
- type Api,
6
- type AssistantMessage,
7
- type AssistantMessageEventStream,
8
- type Context,
9
- type Message,
10
- type Model,
11
- type TextContent,
12
- } from '@earendil-works/pi-ai';
13
- import { FUSION_CANDIDATE_OUTPUT_COMPRESSION_PROMPT } from '../../fusion/output-contract.js';
14
-
15
- export const OUTPUT_RECOVERY_PROVIDER = 'pi-bg-output-recovery';
16
- export const OUTPUT_RECOVERY_MODEL = 'output-recovery-model';
17
- const API = 'pi-bg-output-recovery-api';
18
- export const OUTPUT_RECOVERY_ORIGINAL = 'x'.repeat(49_152);
19
- export const OUTPUT_RECOVERY_REPLACEMENT = 'compressed replacement';
20
-
21
- interface OutputRecoveryLogEvent {
22
- event: 'provider_call' | 'agent_settled';
23
- pid: number;
24
- calls?: number;
25
- call?: number;
26
- roles?: string[];
27
- tools?: string[];
28
- saw_original?: boolean;
29
- saw_compression_prompt?: boolean;
30
- }
31
-
32
- function record(value: OutputRecoveryLogEvent): void {
33
- const path = process.env['UNIPI_BG_OUTPUT_RECOVERY_LOG'];
34
- if (path) appendFileSync(path, `${JSON.stringify(value)}\n`, 'utf8');
35
- }
36
-
37
- function messageText(message: Message): string {
38
- if (typeof message.content === 'string') return message.content;
39
- if (!Array.isArray(message.content)) return '';
40
- return message.content.map((part) => ('text' in part ? part.text : '')).join('');
41
- }
42
-
43
- function pushText(stream: AssistantMessageEventStream, text: string): void {
44
- const usage = {
45
- input: 10,
46
- output: text.length,
47
- cacheRead: 0,
48
- cacheWrite: 0,
49
- totalTokens: 10 + text.length,
50
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
51
- };
52
- const message: AssistantMessage = {
53
- role: 'assistant',
54
- content: [{ type: 'text', text }],
55
- api: API,
56
- provider: OUTPUT_RECOVERY_PROVIDER,
57
- model: OUTPUT_RECOVERY_MODEL,
58
- usage,
59
- stopReason: 'stop',
60
- timestamp: Date.now(),
61
- };
62
- const partial: AssistantMessage = { ...message, content: [] };
63
- const partialText: TextContent = { type: 'text', text: '' };
64
- stream.push({ type: 'start', partial: { ...partial } });
65
- partial.content = [partialText];
66
- stream.push({ type: 'text_start', contentIndex: 0, partial: { ...partial } });
67
- partialText.text = text;
68
- stream.push({ type: 'text_delta', contentIndex: 0, delta: text, partial: { ...partial } });
69
- stream.push({ type: 'text_end', contentIndex: 0, content: text, partial: { ...partial } });
70
- stream.push({ type: 'done', reason: 'stop', message });
71
- stream.end(message);
72
- }
73
-
74
- const RecoveryProbeParams = {
75
- type: 'object',
76
- properties: {},
77
- additionalProperties: false,
78
- } as const;
79
-
80
- export default function outputRecoveryProvider(pi: ExtensionAPI): void {
81
- let calls = 0;
82
- let recoveryQueued = false;
83
-
84
- pi.registerTool<typeof RecoveryProbeParams, Record<string, never>>({
85
- name: 'recovery_probe',
86
- label: 'Recovery Probe',
87
- description: 'Test-only tool used to prove that output compression disables tools.',
88
- parameters: RecoveryProbeParams,
89
- execute() {
90
- return Promise.resolve({ content: [{ type: 'text' as const, text: 'unused' }], details: {} });
91
- },
92
- });
93
-
94
- pi.on('message_end', (event) => {
95
- if (event.message.role !== 'assistant' || recoveryQueued) return;
96
- const text = event.message.content
97
- .flatMap((part) => (part.type === 'text' ? [part.text] : []))
98
- .join('');
99
- if (text !== OUTPUT_RECOVERY_ORIGINAL) return;
100
- const recoveryPath = process.env['UNIPI_BG_OUTPUT_RECOVERY_ARTIFACT'];
101
- if (!recoveryPath) throw new Error('UNIPI_BG_OUTPUT_RECOVERY_ARTIFACT is required');
102
- writeFileSync(recoveryPath, text, { encoding: 'utf8', flag: 'wx', mode: 0o600 });
103
- recoveryQueued = true;
104
- pi.setActiveTools([]);
105
- pi.sendUserMessage(FUSION_CANDIDATE_OUTPUT_COMPRESSION_PROMPT, {
106
- deliverAs: 'followUp',
107
- });
108
- });
109
-
110
- // OUR SDK lacks agent_settled; agent_end carries the same end-of-run signal for probes.
111
- pi.on('agent_end', () => {
112
- record({ event: 'agent_settled', pid: process.pid, calls });
113
- });
114
-
115
- pi.registerProvider(OUTPUT_RECOVERY_PROVIDER, {
116
- name: 'Pi Background Tasks Output Recovery Provider',
117
- baseUrl: 'http://localhost:0',
118
- apiKey: 'UNIPI_BG_OUTPUT_RECOVERY_API_KEY',
119
- api: API,
120
- models: [
121
- {
122
- id: OUTPUT_RECOVERY_MODEL,
123
- name: 'Output Recovery Model',
124
- reasoning: false,
125
- input: ['text'],
126
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
127
- contextWindow: 200_000,
128
- maxTokens: 64_000,
129
- },
130
- ],
131
- streamSimple(_model: Model<Api>, context: Context): AssistantMessageEventStream {
132
- calls += 1;
133
- const texts = context.messages.map(messageText);
134
- const tools = Array.isArray(context.tools) ? context.tools.map((tool) => tool.name) : [];
135
- record({
136
- event: 'provider_call',
137
- pid: process.pid,
138
- call: calls,
139
- roles: context.messages.map((message) => message.role),
140
- tools,
141
- saw_original: texts.includes(OUTPUT_RECOVERY_ORIGINAL),
142
- saw_compression_prompt: texts.some((text) =>
143
- text.includes('Compress and restructure only your immediately previous answer'),
144
- ),
145
- });
146
- const stream = createAssistantMessageEventStream();
147
- queueMicrotask(() => {
148
- pushText(stream, calls === 1 ? OUTPUT_RECOVERY_ORIGINAL : OUTPUT_RECOVERY_REPLACEMENT);
149
- });
150
- return stream;
151
- },
152
- });
153
- }
@@ -1,18 +0,0 @@
1
- {
2
- "schema_version": "pi-background-tasks.delegate-hook-contract.v1",
3
- "contract_id": "context-measure-abort-v1+tool-result-spill-v1",
4
- "guarantees": {
5
- "context_fires_before_every_model_call": true,
6
- "context_result_messages_reach_provider": true,
7
- "context_abort_blocks_provider_call": true,
8
- "context_abort_skips_stream_invocation": false,
9
- "context_abort_terminates_run": true,
10
- "context_throw_blocks_provider_call": false,
11
- "context_throw_isolated_to_throwing_handler": true,
12
- "tool_result_fires_before_transcript_entry": true,
13
- "tool_result_replacement_reaches_provider": true,
14
- "tool_result_replacement_preserves_identity": true,
15
- "tool_result_chains_in_load_order": true,
16
- "handlers_run_in_extension_load_order": true
17
- }
18
- }