@pi-unipi/background-tasks 2.6.3 → 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 (58) hide show
  1. package/package.json +3 -2
  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
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pi-unipi/background-tasks",
3
- "version": "2.6.3",
4
- "description": "Background tasks for UniPi \u2014 durable shell jobs, delegated agents, attested Pi runs, and fixed-purpose Fusion workflows",
3
+ "version": "2.6.4",
4
+ "description": "Background tasks for UniPi durable shell jobs, delegated agents, attested Pi runs, and fixed-purpose Fusion workflows",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
7
7
  "types": "src/index.ts",
@@ -25,6 +25,7 @@
25
25
  "files": [
26
26
  "src/**/*.ts",
27
27
  "src/**/*.json",
28
+ "!src/__tests__/**",
28
29
  "extensions/*.ts",
29
30
  "README.md"
30
31
  ],
@@ -1,195 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import { describe, it } from 'node:test';
3
- import spawnAnthropicAttribution, {
4
- ANTHROPIC_ATTRIBUTION_CLAIM_CHANNEL,
5
- rewriteAnthropicRequestPayload,
6
- type PiExtensionHost,
7
- type PiContextLike,
8
- } from '../anthropic-attribution.js';
9
- import { isJsonObject } from '../types.js';
10
- import { buildAttestedPiArgv } from '../attested-pi-run.js';
11
-
12
- const BAD_SYSTEM_LINES = [
13
- '- When asked about: extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), pi packages (docs/packages.md)',
14
- '- When asked about: extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), pi packages (docs/packages.md), environment variables (docs/environment-variables.md)',
15
- '- When working on pi topics, read the docs and examples, and follow .md cross-references before implementing',
16
- ] as const;
17
-
18
- function context(provider = 'anthropic'): PiContextLike {
19
- return {
20
- model: {
21
- provider,
22
- id: provider === 'anthropic' ? 'claude-sonnet-4-5' : 'gpt-5.5',
23
- maxTokens: 64_000,
24
- reasoning: true,
25
- },
26
- sessionManager: {
27
- getSessionId: () => '11111111-2222-4333-8444-555555555555',
28
- getBranch: () => [],
29
- },
30
- };
31
- }
32
-
33
- class SynchronousTestBus {
34
- private readonly handlers = new Map<string, Array<(data: unknown) => void>>();
35
-
36
- emit(channel: string, data: unknown): void {
37
- for (const handler of this.handlers.get(channel) ?? []) handler(data);
38
- }
39
-
40
- on(channel: string, handler: (data: unknown) => void): () => void {
41
- const handlers = this.handlers.get(channel) ?? [];
42
- handlers.push(handler);
43
- this.handlers.set(channel, handlers);
44
- return () => {
45
- this.handlers.set(
46
- channel,
47
- (this.handlers.get(channel) ?? []).filter((candidate) => candidate !== handler),
48
- );
49
- };
50
- }
51
-
52
- listenerCount(channel: string): number {
53
- return this.handlers.get(channel)?.length ?? 0;
54
- }
55
- }
56
-
57
- function recordingHost(bus: SynchronousTestBus): {
58
- host: PiExtensionHost;
59
- registrations: { commands: number; handlers: number; providers: number; entries: number };
60
- } {
61
- const registrations = { commands: 0, handlers: 0, providers: 0, entries: 0 };
62
- const on: PiExtensionHost['on'] = () => {
63
- registrations.handlers += 1;
64
- };
65
- return {
66
- host: {
67
- events: bus,
68
- on,
69
- registerCommand: () => {
70
- registrations.commands += 1;
71
- },
72
- registerProvider: () => {
73
- registrations.providers += 1;
74
- },
75
- appendEntry: () => {
76
- registrations.entries += 1;
77
- },
78
- },
79
- registrations,
80
- };
81
- }
82
-
83
- void describe('global Anthropic attribution extension', () => {
84
- void it('matches all SPS exact-line variants while preserving unrelated blocks and cache controls', () => {
85
- const original = {
86
- model: 'claude-sonnet-4-5',
87
- max_tokens: 64_000,
88
- system: [
89
- {
90
- type: 'text',
91
- text: ['keep before', ...BAD_SYSTEM_LINES, 'keep after'].join('\n'),
92
- cache_control: { type: 'ephemeral', ttl: '1h' },
93
- custom_field: 'preserved',
94
- },
95
- { type: 'custom', payload: 'unchanged' },
96
- ],
97
- messages: [{ role: 'user', content: 'hello' }],
98
- };
99
-
100
- const rewritten = rewriteAnthropicRequestPayload({
101
- payload: original,
102
- ctx: context(),
103
- account: {
104
- deviceId: 'd'.repeat(64),
105
- accountUuid: 'aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee',
106
- },
107
- });
108
- assert.ok(isJsonObject(rewritten));
109
- const system: unknown = rewritten['system'];
110
- assert.ok(Array.isArray(system));
111
- const retained = system.find(
112
- (block) => isJsonObject(block) && block['custom_field'] === 'preserved',
113
- );
114
- assert.ok(isJsonObject(retained));
115
- assert.equal(retained['text'], 'keep before\nkeep after');
116
- assert.deepEqual(retained['cache_control'], { type: 'ephemeral', ttl: '1h' });
117
- assert.equal(retained['custom_field'], 'preserved');
118
- assert.deepEqual(system.at(-1), { type: 'custom', payload: 'unchanged' });
119
- assert.deepEqual(original.system[0]?.cache_control, { type: 'ephemeral', ttl: '1h' });
120
- for (const rejected of BAD_SYSTEM_LINES) {
121
- assert.equal(JSON.stringify(rewritten).includes(rejected), false);
122
- }
123
- });
124
-
125
- void it('leaves non-Anthropic payloads untouched', () => {
126
- const payload = { model: 'gpt-5.5', metadata: { untouched: true } };
127
- assert.equal(
128
- rewriteAnthropicRequestPayload({
129
- payload,
130
- ctx: context('openai-codex'),
131
- account: { deviceId: 'd', accountUuid: 'a' },
132
- }),
133
- undefined,
134
- );
135
- assert.deepEqual(payload, { model: 'gpt-5.5', metadata: { untouched: true } });
136
- });
137
-
138
- void it('adds package attribution to attested Anthropic argv only', () => {
139
- const base = {
140
- name: 'Attested child',
141
- model: 'model',
142
- prompt: 'write report.md',
143
- reportPath: 'report.md',
144
- };
145
- assert.deepEqual(
146
- buildAttestedPiArgv(
147
- { ...base, provider: 'anthropic' },
148
- '/pkg/extensions/anthropic-attribution.ts',
149
- ),
150
- [
151
- 'pi',
152
- '--mode',
153
- 'json',
154
- '--provider',
155
- 'anthropic',
156
- '--model',
157
- 'model',
158
- '--extension',
159
- '/pkg/extensions/anthropic-attribution.ts',
160
- 'write report.md',
161
- ],
162
- );
163
- assert.deepEqual(buildAttestedPiArgv({ ...base, provider: 'openai-codex' }), [
164
- 'pi',
165
- '--mode',
166
- 'json',
167
- '--provider',
168
- 'openai-codex',
169
- '--model',
170
- 'model',
171
- 'write report.md',
172
- ]);
173
- assert.throws(
174
- () => buildAttestedPiArgv({ ...base, provider: 'anthropic' }),
175
- /require the package attribution extension/,
176
- );
177
- });
178
-
179
- void it('allows exactly one independently loaded copy to own global registration', () => {
180
- const bus = new SynchronousTestBus();
181
- const first = recordingHost(bus);
182
- const second = recordingHost(bus);
183
-
184
- spawnAnthropicAttribution(first.host);
185
- spawnAnthropicAttribution(second.host);
186
-
187
- assert.equal(first.registrations.commands, 1);
188
- assert.equal(first.registrations.handlers, 4);
189
- assert.equal(first.registrations.providers, 1);
190
- assert.equal(second.registrations.commands, 0);
191
- assert.equal(second.registrations.handlers, 0);
192
- assert.equal(second.registrations.providers, 0);
193
- assert.equal(bus.listenerCount(ANTHROPIC_ATTRIBUTION_CLAIM_CHANNEL), 1);
194
- });
195
- });
@@ -1,137 +0,0 @@
1
- /**
2
- * Config layering + validation tests for @pi-unipi/background-tasks.
3
- * Adapted from reference config/operations specs to OUR paths and layering.
4
- */
5
-
6
- import test from "node:test";
7
- import assert from "node:assert/strict";
8
- import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
9
- import { homedir, tmpdir } from "node:os";
10
- import { join } from "node:path";
11
- import {
12
- DEFAULT_CONFIG,
13
- loadBackgroundTasksConfig,
14
- validateBackgroundTasksConfig,
15
- } from "../config.js";
16
-
17
- /** Run loadBackgroundTasksConfig with HOME pointed at a temp dir. */
18
- function withTempHome<T>(fn: (home: string, cwd: string) => T): T {
19
- const home = mkdtempSync(join(tmpdir(), "unipi-bg-cfg-home-"));
20
- const cwd = mkdtempSync(join(tmpdir(), "unipi-bg-cfg-ws-"));
21
- const realHome = process.env.HOME;
22
- const realUserProfile = process.env.USERPROFILE;
23
- process.env.HOME = home;
24
- process.env.USERPROFILE = home;
25
- try {
26
- return fn(home, cwd);
27
- } finally {
28
- if (realHome === undefined) delete process.env.HOME;
29
- else process.env.HOME = realHome;
30
- if (realUserProfile === undefined) delete process.env.USERPROFILE;
31
- else process.env.USERPROFILE = realUserProfile;
32
- rmSync(home, { recursive: true, force: true });
33
- rmSync(cwd, { recursive: true, force: true });
34
- }
35
- }
36
-
37
- test("defaults: enabled true, notify+wake on, 20MiB cap", () => {
38
- assert.equal(DEFAULT_CONFIG.enabled, true);
39
- assert.equal(DEFAULT_CONFIG.notifyOnCompletion, true);
40
- assert.equal(DEFAULT_CONFIG.triggerOnCompletion, true);
41
- assert.equal(DEFAULT_CONFIG.maxOutputBytes, 20 * 1024 * 1024);
42
- assert.equal(DEFAULT_CONFIG.delegate.extensionMode, "isolated");
43
- });
44
-
45
- test("first run creates the global config file", () => {
46
- withTempHome((home) => {
47
- const { config } = loadBackgroundTasksConfig("");
48
- assert.equal(config.enabled, true);
49
- const written = JSON.parse(readFileSync(join(home, ".unipi", "config", "background-tasks.json"), "utf-8"));
50
- assert.equal(written.enabled, true);
51
- });
52
- });
53
-
54
- test("workspace config wins over global", () => {
55
- withTempHome((home, cwd) => {
56
- const globalPath = join(home, ".unipi", "config", "background-tasks.json");
57
- mkdirSync(join(globalPath, ".."), { recursive: true });
58
- writeFileSync(globalPath, JSON.stringify({ maxFinishedTasks: 10 }));
59
- const wsPath = join(cwd, ".unipi", "config", "background-tasks.json");
60
- mkdirSync(join(wsPath, ".."), { recursive: true });
61
- writeFileSync(wsPath, JSON.stringify({ maxFinishedTasks: 7, triggerOnCompletion: false }));
62
-
63
- const { config } = loadBackgroundTasksConfig(cwd);
64
- assert.equal(config.maxFinishedTasks, 7);
65
- assert.equal(config.triggerOnCompletion, false);
66
- assert.equal(config.enabled, true); // untouched default
67
- });
68
- });
69
-
70
- test("corrupt layers are skipped with warnings, not crashes", () => {
71
- withTempHome((home, cwd) => {
72
- const globalPath = join(home, ".unipi", "config", "background-tasks.json");
73
- mkdirSync(join(globalPath, ".."), { recursive: true });
74
- writeFileSync(globalPath, "{not json");
75
- const wsPath = join(cwd, ".unipi", "config", "background-tasks.json");
76
- mkdirSync(join(wsPath, ".."), { recursive: true });
77
- writeFileSync(wsPath, "[1,2,3]");
78
-
79
- const { config, warnings } = loadBackgroundTasksConfig(cwd);
80
- assert.equal(config.enabled, true);
81
- assert.equal(config.maxFinishedTasks, DEFAULT_CONFIG.maxFinishedTasks);
82
- assert.ok(warnings.length >= 1);
83
- });
84
- });
85
-
86
- test("validation rejects bad values with visible messages", () => {
87
- const problems = validateBackgroundTasksConfig({
88
- enabled: "yes",
89
- maxOutputBytes: 10,
90
- delegate: { extensionMode: "wild" },
91
- fusion: { candidates: ["a"] },
92
- });
93
- assert.ok(problems.some((p) => p.includes("enabled")));
94
- assert.ok(problems.some((p) => p.includes("maxOutputBytes")));
95
- assert.ok(problems.some((p) => p.includes("extensionMode")));
96
- assert.ok(problems.some((p) => p.includes("candidates")));
97
- });
98
-
99
- test("validation accepts a full valid config", () => {
100
- const problems = validateBackgroundTasksConfig({
101
- enabled: false,
102
- notifyOnCompletion: false,
103
- triggerOnCompletion: false,
104
- defaultTimeoutSeconds: 600,
105
- maxFinishedTasks: 5,
106
- maxOutputBytes: 1024 * 1024,
107
- delegate: { extensionMode: "ambient", autoDeliver: "always", maxTurns: 10, maxToolCalls: 20, timeoutSeconds: 60 },
108
- fusion: { candidates: ["m1", "m2", "m3"], evaluator: "e", merger: "g" },
109
- });
110
- assert.deepEqual(problems, []);
111
- });
112
-
113
- test("invalid values sanitize to defaults instead of crashing the loader", () => {
114
- withTempHome((home) => {
115
- const globalPath = join(home, ".unipi", "config", "background-tasks.json");
116
- mkdirSync(join(globalPath, ".."), { recursive: true });
117
- writeFileSync(
118
- globalPath,
119
- JSON.stringify({ enabled: "nope", maxFinishedTasks: -1, delegate: { extensionMode: "wild" } }),
120
- );
121
- const { config, warnings } = loadBackgroundTasksConfig("");
122
- assert.equal(config.enabled, true);
123
- assert.equal(config.maxFinishedTasks, DEFAULT_CONFIG.maxFinishedTasks);
124
- assert.equal(config.delegate.extensionMode, "isolated");
125
- assert.ok(warnings.length >= 1);
126
- });
127
- });
128
-
129
- test("master toggle off is preserved through load", () => {
130
- withTempHome((home) => {
131
- const globalPath = join(home, ".unipi", "config", "background-tasks.json");
132
- mkdirSync(join(globalPath, ".."), { recursive: true });
133
- writeFileSync(globalPath, JSON.stringify({ enabled: false }));
134
- const { config } = loadBackgroundTasksConfig("");
135
- assert.equal(config.enabled, false);
136
- });
137
- });