@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.
- package/package.json +3 -2
- package/src/tools.ts +3 -1
- package/src/__tests__/anthropic-attribution.test.ts +0 -195
- package/src/__tests__/config.test.ts +0 -137
- package/src/__tests__/core.test.ts +0 -493
- package/src/__tests__/delegate-artifacts.test.ts +0 -528
- package/src/__tests__/delegate-budget.test.ts +0 -456
- package/src/__tests__/delegate-launch.test.ts +0 -676
- package/src/__tests__/delegate-result-package.test.ts +0 -350
- package/src/__tests__/delegate-seed.test.ts +0 -392
- package/src/__tests__/durable-fs.test.ts +0 -559
- package/src/__tests__/extension-api.test.ts +0 -579
- package/src/__tests__/fusion-artifacts.test.ts +0 -1039
- package/src/__tests__/fusion-budget.test.ts +0 -1356
- package/src/__tests__/fusion-claude-cache.test.ts +0 -320
- package/src/__tests__/fusion-config.test.ts +0 -335
- package/src/__tests__/fusion-context-prompts.test.ts +0 -670
- package/src/__tests__/fusion-evaluation.test.ts +0 -315
- package/src/__tests__/fusion-extraction-equivalence.test.ts +0 -58
- package/src/__tests__/fusion-golden-bytes.test.ts +0 -35
- package/src/__tests__/fusion-high-cardinality.test.ts +0 -192
- package/src/__tests__/fusion-model-selector.test.ts +0 -205
- package/src/__tests__/fusion-orchestrator.test.ts +0 -1194
- package/src/__tests__/fusion-rpc.test.ts +0 -369
- package/src/__tests__/fusion-sdk.test.ts +0 -1226
- package/src/__tests__/fusion-v5-core.test.ts +0 -219
- package/src/__tests__/fusion-validate-orchestrator.test.ts +0 -240
- package/src/__tests__/fusion-web-fetch.test.ts +0 -485
- package/src/__tests__/fusion-workflows.test.ts +0 -59
- package/src/__tests__/helpers/delegate-deterministic-seed.ts +0 -109
- package/src/__tests__/helpers/delegate-seed-subprocess.ts +0 -10
- package/src/__tests__/helpers/fusion-canonical-subprocess.ts +0 -21
- package/src/__tests__/helpers/fusion-canonical.ts +0 -140
- package/src/__tests__/helpers/fusion-fake-pi.ts +0 -279
- package/src/__tests__/helpers/fusion-golden-corpus.ts +0 -500
- package/src/__tests__/helpers/fusion-high-cardinality.ts +0 -140
- package/src/__tests__/helpers/normalize.ts +0 -22
- package/src/__tests__/helpers/pi-hook-contract-evidence.json +0 -18
- package/src/__tests__/pi-launch.test.ts +0 -202
- package/src/__tests__/registry.test.ts +0 -1580
- package/src/__tests__/scripted-provider/delegate-ambient-provider.test.ts +0 -130
- package/src/__tests__/scripted-provider/delegate-child-guard.test.ts +0 -631
- package/src/__tests__/scripted-provider/delegate-guard-provider.ts +0 -403
- package/src/__tests__/scripted-provider/follow-up.test.ts +0 -448
- package/src/__tests__/scripted-provider/fusion-output-recovery.test.ts +0 -132
- package/src/__tests__/scripted-provider/fusion-reason.test.ts +0 -310
- package/src/__tests__/scripted-provider/fusion-runtime-guard.test.ts +0 -163
- package/src/__tests__/scripted-provider/hook-contract-provider.ts +0 -179
- package/src/__tests__/scripted-provider/hook-probe-a.ts +0 -3
- package/src/__tests__/scripted-provider/hook-probe-b.ts +0 -3
- package/src/__tests__/scripted-provider/hook-probe-extension.ts +0 -126
- package/src/__tests__/scripted-provider/output-recovery-provider.ts +0 -153
- package/src/__tests__/scripted-provider/pi-hook-contract-evidence.json +0 -18
- package/src/__tests__/scripted-provider/pi-hook-contract.test.ts +0 -477
- package/src/__tests__/scripted-provider/runtime-guard-probe.ts +0 -28
- package/src/__tests__/scripted-provider/runtime-guard-provider.ts +0 -49
- package/src/__tests__/scripted-provider/scripted-provider-extension.ts +0 -408
- package/src/__tests__/task-manager.test.ts +0 -479
- 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.
|
|
4
|
-
"description": "Background tasks for UniPi
|
|
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
|
],
|
package/src/tools.ts
CHANGED
|
@@ -330,7 +330,9 @@ export function registerToolsAndCommands(options: RegisterSurfaceOptions): void
|
|
|
330
330
|
"Use bg_run instead of bash for commands expected to run for a long time, such as test suites, dev servers, watchers, or builds.",
|
|
331
331
|
"Set isAgent:true only when the background task launches an LLM/agent process; false for scripts, tests, dev servers, sleeps.",
|
|
332
332
|
"Always set name to a concise 2-6 word human-readable label; do not use the raw command as the name.",
|
|
333
|
-
"
|
|
333
|
+
"bg_run returns immediately. With notifyOnCompletion:true and triggerOnCompletion:true (both defaults), completed, failed, or killed terminal state is delivered as <background-task-notification> and automatically starts a follow-up agent turn.",
|
|
334
|
+
"After a default bg_run launch, continue only independent useful work that does not merely wait for the task; otherwise briefly acknowledge it if useful, then end the current turn. Do not call sleep, bg_status, or bg_logs merely to wait; the terminal notification will wake you.",
|
|
335
|
+
"A running result is not an instruction to poll again.",
|
|
334
336
|
"Treat <background-task-notification> as durable terminal truth. Do not call bg_status to reconfirm it.",
|
|
335
337
|
"Do not set notifyOnCompletion:false or triggerOnCompletion:false unless intentionally opting out.",
|
|
336
338
|
],
|
|
@@ -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
|
-
});
|