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