@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
|
@@ -1,493 +0,0 @@
|
|
|
1
|
-
import { describe, it } from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { mkdtemp, writeFile, rm } from 'node:fs/promises';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
import { tmpdir } from 'node:os';
|
|
6
|
-
import {
|
|
7
|
-
boundedRead,
|
|
8
|
-
compareSemver,
|
|
9
|
-
deriveCompletionDeliveryGuidance,
|
|
10
|
-
deriveTaskNameFromCommand,
|
|
11
|
-
formatAgentActivityLine,
|
|
12
|
-
parseAgentActivity,
|
|
13
|
-
formatCompactNumber,
|
|
14
|
-
formatDuration,
|
|
15
|
-
formatModelSummary,
|
|
16
|
-
formatSnapshotList,
|
|
17
|
-
formatUpdateSegment,
|
|
18
|
-
isNewerVersion,
|
|
19
|
-
normalizeMaxBytes,
|
|
20
|
-
normalizeTaskName,
|
|
21
|
-
parseBgCommandArgs,
|
|
22
|
-
parseSemver,
|
|
23
|
-
sanitizePathSegment,
|
|
24
|
-
shellInvocation,
|
|
25
|
-
TASK_STATUS_VALUES,
|
|
26
|
-
taskDisplayName,
|
|
27
|
-
TERMINAL_TASK_STATUS_VALUES,
|
|
28
|
-
truncateChars,
|
|
29
|
-
} from '../types.js';
|
|
30
|
-
|
|
31
|
-
void describe('core', () => {
|
|
32
|
-
void it('parses /bg names, aliases, equals forms, quotes, and empty commands', () => {
|
|
33
|
-
assert.deepEqual(parseBgCommandArgs('--name "Build Docs" npm run docs'), {
|
|
34
|
-
name: 'Build Docs',
|
|
35
|
-
command: 'npm run docs',
|
|
36
|
-
isAgent: false,
|
|
37
|
-
});
|
|
38
|
-
assert.deepEqual(parseBgCommandArgs('--name=Build npm test'), {
|
|
39
|
-
name: 'Build',
|
|
40
|
-
command: 'npm test',
|
|
41
|
-
isAgent: false,
|
|
42
|
-
});
|
|
43
|
-
assert.deepEqual(parseBgCommandArgs("-n 'Quoted Name' printf ok"), {
|
|
44
|
-
name: 'Quoted Name',
|
|
45
|
-
command: 'printf ok',
|
|
46
|
-
isAgent: false,
|
|
47
|
-
});
|
|
48
|
-
assert.deepEqual(parseBgCommandArgs('-n=One printf one'), {
|
|
49
|
-
name: 'One',
|
|
50
|
-
command: 'printf one',
|
|
51
|
-
isAgent: false,
|
|
52
|
-
});
|
|
53
|
-
assert.deepEqual(parseBgCommandArgs('--agent --name Agent pi -p hi'), {
|
|
54
|
-
name: 'Agent',
|
|
55
|
-
command: 'pi -p hi',
|
|
56
|
-
isAgent: true,
|
|
57
|
-
});
|
|
58
|
-
assert.deepEqual(parseBgCommandArgs('--name Agent --script pi -p hi'), {
|
|
59
|
-
name: 'Agent',
|
|
60
|
-
command: 'pi -p hi',
|
|
61
|
-
isAgent: false,
|
|
62
|
-
});
|
|
63
|
-
assert.deepEqual(parseBgCommandArgs('echo ok'), { command: 'echo ok', isAgent: false });
|
|
64
|
-
assert.deepEqual(parseBgCommandArgs("--name 'Only Name'"), {
|
|
65
|
-
name: 'Only Name',
|
|
66
|
-
command: '',
|
|
67
|
-
isAgent: false,
|
|
68
|
-
});
|
|
69
|
-
assert.throws(() => parseBgCommandArgs('--name'), /requires a task name/);
|
|
70
|
-
assert.throws(() => parseBgCommandArgs('--name "unterminated'), /requires a task name/);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
void it('normalizes task names and derives stable fallbacks', () => {
|
|
74
|
-
assert.equal(normalizeTaskName(' "A B" '), 'A B');
|
|
75
|
-
assert.equal(normalizeTaskName('\n\t'), undefined);
|
|
76
|
-
assert.equal(normalizeTaskName(123), undefined);
|
|
77
|
-
assert.equal(normalizeTaskName('x'.repeat(100)), `${'x'.repeat(79)}…`);
|
|
78
|
-
assert.equal(deriveTaskNameFromCommand('npm run test -- --watch'), 'npm run test');
|
|
79
|
-
assert.equal(deriveTaskNameFromCommand('pnpm build && echo done'), 'pnpm build');
|
|
80
|
-
assert.equal(deriveTaskNameFromCommand(''), 'Background task');
|
|
81
|
-
assert.equal(
|
|
82
|
-
taskDisplayName({ description: 'Longer description', command: 'echo ok' }),
|
|
83
|
-
'Longer description',
|
|
84
|
-
);
|
|
85
|
-
assert.equal(
|
|
86
|
-
taskDisplayName({ command: 'echo one two three four five six' }),
|
|
87
|
-
'echo one two three four',
|
|
88
|
-
);
|
|
89
|
-
assert.equal(taskDisplayName({ id: 'b123' }), 'b123');
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
void it('derives truthful completion delivery for every notification and wake combination', () => {
|
|
93
|
-
assert.deepEqual(deriveCompletionDeliveryGuidance(true, true), {
|
|
94
|
-
mode: 'notification-and-wake',
|
|
95
|
-
notificationEnabled: true,
|
|
96
|
-
automaticWakeEnabled: true,
|
|
97
|
-
text: [
|
|
98
|
-
'Terminal notification: enabled.',
|
|
99
|
-
'Automatic follow-up turn: enabled.',
|
|
100
|
-
'Next action: do not poll or sleep merely to wait; continue only independent useful work, otherwise end this turn and wait for <background-task-notification>.',
|
|
101
|
-
].join('\n'),
|
|
102
|
-
});
|
|
103
|
-
assert.deepEqual(deriveCompletionDeliveryGuidance(true, false), {
|
|
104
|
-
mode: 'notification-only',
|
|
105
|
-
notificationEnabled: true,
|
|
106
|
-
automaticWakeEnabled: false,
|
|
107
|
-
text: [
|
|
108
|
-
'Terminal notification: enabled.',
|
|
109
|
-
'Automatic follow-up turn: disabled. The terminal notification will be delivered, but it will not start an agent turn.',
|
|
110
|
-
'Next action: automatic wake-up was explicitly disabled; use bg_status/bg_logs only when deliberate monitoring is required, without tight polling.',
|
|
111
|
-
].join('\n'),
|
|
112
|
-
});
|
|
113
|
-
const notifyDisabledWithRequestedWake = deriveCompletionDeliveryGuidance(false, true);
|
|
114
|
-
assert.equal(notifyDisabledWithRequestedWake.mode, 'manual-monitoring');
|
|
115
|
-
assert.equal(notifyDisabledWithRequestedWake.notificationEnabled, false);
|
|
116
|
-
assert.equal(notifyDisabledWithRequestedWake.automaticWakeEnabled, false);
|
|
117
|
-
assert.match(notifyDisabledWithRequestedWake.text, /triggerOnCompletion has no effect/);
|
|
118
|
-
const manual = deriveCompletionDeliveryGuidance(false, false);
|
|
119
|
-
assert.equal(manual.mode, 'manual-monitoring');
|
|
120
|
-
assert.match(manual.text, /deliberate manual monitoring/);
|
|
121
|
-
assert.doesNotMatch(manual.text, /triggerOnCompletion has no effect/);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
void it('formats durations, paths, snapshots, and byte limits', () => {
|
|
125
|
-
assert.equal(formatDuration(999), '999ms');
|
|
126
|
-
assert.equal(formatDuration(1000), '1s');
|
|
127
|
-
assert.equal(formatDuration(65_000), '1m5s');
|
|
128
|
-
assert.equal(formatDuration(3_660_000), '1h1m');
|
|
129
|
-
assert.equal(formatCompactNumber(999), '999');
|
|
130
|
-
assert.equal(formatCompactNumber(1250), '1.3k');
|
|
131
|
-
assert.equal(formatCompactNumber(42_000), '42k');
|
|
132
|
-
assert.equal(sanitizePathSegment('a/b c'), 'a-b-c');
|
|
133
|
-
assert.equal(sanitizePathSegment('///'), 'session');
|
|
134
|
-
// Pin both dialects explicitly. The host default differs by platform:
|
|
135
|
-
// cmd.exe requires the paired outer-quoted form, POSIX passes the command
|
|
136
|
-
// through verbatim, so an unpinned assertion fails on Windows.
|
|
137
|
-
assert.equal(shellInvocation('echo ok', 'linux', {}).args.includes('echo ok'), true);
|
|
138
|
-
assert.deepEqual(shellInvocation('echo ok', 'win32', { ComSpec: 'cmd.exe' }).args, [
|
|
139
|
-
'/d',
|
|
140
|
-
'/s',
|
|
141
|
-
'/c',
|
|
142
|
-
'"echo ok"',
|
|
143
|
-
]);
|
|
144
|
-
assert.equal(normalizeMaxBytes(-1, 123), 1);
|
|
145
|
-
assert.equal(normalizeMaxBytes(Number.NaN, 123), 123);
|
|
146
|
-
assert.equal(normalizeMaxBytes(1.9, 123), 1);
|
|
147
|
-
assert.equal(truncateChars('abcdef', 4), 'abc…');
|
|
148
|
-
assert.deepEqual([...TASK_STATUS_VALUES], ['running', 'completed', 'failed', 'killed']);
|
|
149
|
-
assert.deepEqual([...TERMINAL_TASK_STATUS_VALUES], ['completed', 'failed', 'killed']);
|
|
150
|
-
|
|
151
|
-
const text = formatSnapshotList(
|
|
152
|
-
[
|
|
153
|
-
{
|
|
154
|
-
id: 'b12345678',
|
|
155
|
-
name: 'Unit Task',
|
|
156
|
-
command: 'echo ok',
|
|
157
|
-
status: 'completed',
|
|
158
|
-
outputPath: '.pi/tasks/run/b12345678.output',
|
|
159
|
-
cwd: '/tmp',
|
|
160
|
-
startTime: 1000,
|
|
161
|
-
endTime: 2000,
|
|
162
|
-
exitCode: 0,
|
|
163
|
-
bytesWritten: 3,
|
|
164
|
-
isAgent: true,
|
|
165
|
-
notified: true,
|
|
166
|
-
notifyOnCompletion: true,
|
|
167
|
-
triggerOnCompletion: false,
|
|
168
|
-
contextUsage: { tokens: 1250, contextWindow: 200_000, percent: 0.625 },
|
|
169
|
-
tokenUsage: {
|
|
170
|
-
input: 1000,
|
|
171
|
-
output: 200,
|
|
172
|
-
cacheRead: 30,
|
|
173
|
-
cacheWrite: 20,
|
|
174
|
-
totalTokens: 1250,
|
|
175
|
-
},
|
|
176
|
-
toolUsage: { total: 2, failed: 1, byName: { read: 1, bash: 1 } },
|
|
177
|
-
model: 'anthropic/claude-sonnet-4',
|
|
178
|
-
},
|
|
179
|
-
{
|
|
180
|
-
id: 'b99999999',
|
|
181
|
-
command: 'bad',
|
|
182
|
-
status: 'failed',
|
|
183
|
-
outputPath: '.pi/tasks/run/b99999999.output',
|
|
184
|
-
cwd: '/tmp',
|
|
185
|
-
startTime: 1000,
|
|
186
|
-
endTime: 2000,
|
|
187
|
-
exitCode: 1,
|
|
188
|
-
bytesWritten: 0,
|
|
189
|
-
isAgent: false,
|
|
190
|
-
error: 'x'.repeat(100),
|
|
191
|
-
notified: false,
|
|
192
|
-
notifyOnCompletion: true,
|
|
193
|
-
triggerOnCompletion: true,
|
|
194
|
-
},
|
|
195
|
-
],
|
|
196
|
-
2000,
|
|
197
|
-
);
|
|
198
|
-
assert.match(text, /Unit Task/);
|
|
199
|
-
assert.match(text, /ctx=0\.6%\/200k/);
|
|
200
|
-
assert.match(text, /model=anthropic\/claude-sonnet-4/);
|
|
201
|
-
assert.match(text, /tokens=1\.3k/);
|
|
202
|
-
assert.match(text, /tools=2 failed=1/);
|
|
203
|
-
assert.match(text, /✗ b99999999 failed/);
|
|
204
|
-
assert.match(text, /output: \.pi\/tasks/);
|
|
205
|
-
assert.equal(
|
|
206
|
-
formatModelSummary('anthropic/claude-sonnet-4'),
|
|
207
|
-
'model=anthropic/claude-sonnet-4',
|
|
208
|
-
);
|
|
209
|
-
assert.equal(formatModelSummary(undefined), undefined);
|
|
210
|
-
assert.equal(formatModelSummary(''), undefined);
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
void it('parses and compares semver including prerelease precedence', () => {
|
|
214
|
-
assert.deepEqual(parseSemver('1.2.3'), { major: 1, minor: 2, patch: 3, prerelease: [] });
|
|
215
|
-
assert.deepEqual(parseSemver('v0.4.0'), { major: 0, minor: 4, patch: 0, prerelease: [] });
|
|
216
|
-
assert.deepEqual(parseSemver('1.0.0-beta.2'), {
|
|
217
|
-
major: 1,
|
|
218
|
-
minor: 0,
|
|
219
|
-
patch: 0,
|
|
220
|
-
prerelease: ['beta', '2'],
|
|
221
|
-
});
|
|
222
|
-
assert.deepEqual(parseSemver('1.2.3+build.5'), {
|
|
223
|
-
major: 1,
|
|
224
|
-
minor: 2,
|
|
225
|
-
patch: 3,
|
|
226
|
-
prerelease: [],
|
|
227
|
-
});
|
|
228
|
-
assert.equal(parseSemver('1.2'), undefined);
|
|
229
|
-
assert.equal(parseSemver('latest'), undefined);
|
|
230
|
-
assert.equal(parseSemver(''), undefined);
|
|
231
|
-
|
|
232
|
-
assert.equal(compareSemver('1.2.3', '1.2.2'), 1);
|
|
233
|
-
assert.equal(compareSemver('1.2.3', '1.2.3'), 0);
|
|
234
|
-
assert.equal(compareSemver('1.2.3', '1.3.0'), -1);
|
|
235
|
-
assert.equal(compareSemver('2.0.0', '1.9.9'), 1);
|
|
236
|
-
assert.equal(compareSemver('1.0.0', '1.0.0-beta'), 1);
|
|
237
|
-
assert.equal(compareSemver('1.0.0-alpha', '1.0.0'), -1);
|
|
238
|
-
assert.equal(compareSemver('1.0.0-alpha', '1.0.0-beta'), -1);
|
|
239
|
-
assert.equal(compareSemver('1.0.0-alpha.2', '1.0.0-alpha.1'), 1);
|
|
240
|
-
assert.equal(compareSemver('1.0.0-alpha.1', '1.0.0-alpha'), 1);
|
|
241
|
-
assert.equal(compareSemver('1.0.0-1', '1.0.0-alpha'), -1);
|
|
242
|
-
assert.equal(compareSemver('1.0.0-2', '1.0.0-10'), -1);
|
|
243
|
-
assert.equal(compareSemver('garbage', '1.0.0'), undefined);
|
|
244
|
-
assert.equal(compareSemver('1.0.0', 'garbage'), undefined);
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
void it('derives newer-version flags and footer update segments', () => {
|
|
248
|
-
assert.equal(isNewerVersion('0.4.0', '0.3.0'), true);
|
|
249
|
-
assert.equal(isNewerVersion('v0.4.0', '0.3.0'), true);
|
|
250
|
-
assert.equal(isNewerVersion('0.3.0', '0.3.0'), false);
|
|
251
|
-
assert.equal(isNewerVersion('0.2.0', '0.3.0'), false);
|
|
252
|
-
assert.equal(isNewerVersion('garbage', '0.3.0'), false);
|
|
253
|
-
assert.equal(formatUpdateSegment('0.4.0', '0.3.0'), '\u2b06 v0.4.0 /unipi:bg-update');
|
|
254
|
-
assert.equal(formatUpdateSegment('0.3.0', '0.3.0'), undefined);
|
|
255
|
-
assert.equal(formatUpdateSegment('0.2.0', '0.3.0'), undefined);
|
|
256
|
-
assert.equal(formatUpdateSegment(undefined, '0.3.0'), undefined);
|
|
257
|
-
assert.equal(formatUpdateSegment('garbage', '0.3.0'), undefined);
|
|
258
|
-
assert.equal(formatUpdateSegment('0.4.0', ''), undefined);
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
void it('applies the Windows shell dialect policy', async () => {
|
|
262
|
-
const root = await mkdtemp(join(tmpdir(), 'pi-bg-shell-'));
|
|
263
|
-
try {
|
|
264
|
-
const bashPath = join(root, 'bash.exe');
|
|
265
|
-
const cmdPath = join(root, 'cmd.com');
|
|
266
|
-
await writeFile(bashPath, '', 'utf8');
|
|
267
|
-
await writeFile(cmdPath, '', 'utf8');
|
|
268
|
-
|
|
269
|
-
assert.deepEqual(
|
|
270
|
-
shellInvocation('echo %USERPROFILE%', 'win32', {
|
|
271
|
-
SHELL: bashPath,
|
|
272
|
-
ComSpec: 'C:\\Windows\\System32\\cmd.exe',
|
|
273
|
-
}),
|
|
274
|
-
{
|
|
275
|
-
shell: 'C:\\Windows\\System32\\cmd.exe',
|
|
276
|
-
args: ['/d', '/s', '/c', '"echo %USERPROFILE%"'],
|
|
277
|
-
dialect: 'cmd',
|
|
278
|
-
windowsVerbatimArguments: true,
|
|
279
|
-
},
|
|
280
|
-
);
|
|
281
|
-
assert.deepEqual(shellInvocation('dir C:\\work', 'win32', {}), {
|
|
282
|
-
shell: 'cmd.exe',
|
|
283
|
-
args: ['/d', '/s', '/c', '"dir C:\\work"'],
|
|
284
|
-
dialect: 'cmd',
|
|
285
|
-
windowsVerbatimArguments: true,
|
|
286
|
-
});
|
|
287
|
-
assert.deepEqual(
|
|
288
|
-
shellInvocation('printf ok', 'win32', {
|
|
289
|
-
UNIPI_BG_SHELL: 'bash',
|
|
290
|
-
UNIPI_BG_SHELL_PATH: bashPath,
|
|
291
|
-
}),
|
|
292
|
-
{
|
|
293
|
-
shell: bashPath,
|
|
294
|
-
args: ['-c', 'printf ok'],
|
|
295
|
-
dialect: 'posix',
|
|
296
|
-
windowsVerbatimArguments: false,
|
|
297
|
-
},
|
|
298
|
-
);
|
|
299
|
-
assert.notDeepEqual(
|
|
300
|
-
shellInvocation('printf ok', 'win32', {
|
|
301
|
-
UNIPI_BG_SHELL: 'bash',
|
|
302
|
-
UNIPI_BG_SHELL_PATH: bashPath,
|
|
303
|
-
}).args,
|
|
304
|
-
['-lc', 'printf ok'],
|
|
305
|
-
);
|
|
306
|
-
assert.equal(
|
|
307
|
-
shellInvocation('printf ok', 'win32', { UNIPI_BG_SHELL: 'bash', PATH: root }).shell,
|
|
308
|
-
bashPath,
|
|
309
|
-
);
|
|
310
|
-
assert.equal(
|
|
311
|
-
shellInvocation('set X=1', 'win32', { UNIPI_BG_SHELL: 'cmd', UNIPI_BG_SHELL_PATH: cmdPath })
|
|
312
|
-
.shell,
|
|
313
|
-
cmdPath,
|
|
314
|
-
);
|
|
315
|
-
for (const value of ['', ' bash', 'bash ', 'zsh']) {
|
|
316
|
-
assert.throws(() => shellInvocation('echo bad', 'win32', { UNIPI_BG_SHELL: value }), /cmd or bash/);
|
|
317
|
-
}
|
|
318
|
-
assert.throws(
|
|
319
|
-
() => shellInvocation('echo bad', 'win32', { UNIPI_BG_SHELL_PATH: bashPath }),
|
|
320
|
-
/requires UNIPI_BG_SHELL/,
|
|
321
|
-
);
|
|
322
|
-
assert.throws(
|
|
323
|
-
() =>
|
|
324
|
-
shellInvocation('echo bad', 'win32', {
|
|
325
|
-
UNIPI_BG_SHELL: 'bash',
|
|
326
|
-
UNIPI_BG_SHELL_PATH: 'bash.exe',
|
|
327
|
-
}),
|
|
328
|
-
/absolute path/,
|
|
329
|
-
);
|
|
330
|
-
assert.deepEqual(
|
|
331
|
-
shellInvocation('echo $SHELL', 'linux', {
|
|
332
|
-
SHELL: '/bin/zsh',
|
|
333
|
-
UNIPI_BG_SHELL: 'cmd',
|
|
334
|
-
UNIPI_BG_SHELL_PATH: bashPath,
|
|
335
|
-
}),
|
|
336
|
-
{
|
|
337
|
-
shell: '/bin/zsh',
|
|
338
|
-
args: ['-c', 'echo $SHELL'],
|
|
339
|
-
dialect: 'posix',
|
|
340
|
-
windowsVerbatimArguments: false,
|
|
341
|
-
},
|
|
342
|
-
);
|
|
343
|
-
} finally {
|
|
344
|
-
await rm(root, { recursive: true, force: true });
|
|
345
|
-
}
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
void it('parses and formats agent activity transcript lines', () => {
|
|
349
|
-
assert.deepEqual(
|
|
350
|
-
parseAgentActivity({
|
|
351
|
-
type: 'background-task-activity',
|
|
352
|
-
kind: 'assistant_text',
|
|
353
|
-
text: 'Done.\n',
|
|
354
|
-
}),
|
|
355
|
-
{ kind: 'assistant_text', text: 'Done.\n' },
|
|
356
|
-
);
|
|
357
|
-
assert.equal(
|
|
358
|
-
formatAgentActivityLine({ kind: 'assistant_text', text: 'Looks good\n\n' }),
|
|
359
|
-
'Looks good',
|
|
360
|
-
);
|
|
361
|
-
assert.equal(formatAgentActivityLine({ kind: 'assistant_text', text: ' \n ' }), undefined);
|
|
362
|
-
|
|
363
|
-
assert.equal(
|
|
364
|
-
formatAgentActivityLine({ kind: 'reasoning', text: 'weighing options' }),
|
|
365
|
-
'\u2026 weighing options',
|
|
366
|
-
);
|
|
367
|
-
assert.equal(formatAgentActivityLine({ kind: 'reasoning', text: ' ' }), undefined);
|
|
368
|
-
|
|
369
|
-
assert.deepEqual(
|
|
370
|
-
parseAgentActivity({
|
|
371
|
-
type: 'background-task-activity',
|
|
372
|
-
kind: 'tool_start',
|
|
373
|
-
tool: 'read',
|
|
374
|
-
argsSummary: 'README.md',
|
|
375
|
-
}),
|
|
376
|
-
{ kind: 'tool_start', tool: 'read', argsSummary: 'README.md' },
|
|
377
|
-
);
|
|
378
|
-
assert.equal(
|
|
379
|
-
formatAgentActivityLine({
|
|
380
|
-
kind: 'tool_start',
|
|
381
|
-
tool: 'read',
|
|
382
|
-
argsSummary: ' src/index.ts ',
|
|
383
|
-
}),
|
|
384
|
-
'\u2192 read src/index.ts',
|
|
385
|
-
);
|
|
386
|
-
assert.equal(
|
|
387
|
-
formatAgentActivityLine({ kind: 'tool_start', tool: 'bash', argsSummary: '' }),
|
|
388
|
-
'\u2192 bash',
|
|
389
|
-
);
|
|
390
|
-
assert.equal(
|
|
391
|
-
formatAgentActivityLine({ kind: 'tool_start', tool: 'bash', argsSummary: 'x'.repeat(120) }),
|
|
392
|
-
`\u2192 bash ${'x'.repeat(79)}\u2026`,
|
|
393
|
-
);
|
|
394
|
-
assert.deepEqual(
|
|
395
|
-
parseAgentActivity({ type: 'background-task-activity', kind: 'tool_start', tool: 'ls' }),
|
|
396
|
-
{ kind: 'tool_start', tool: 'ls', argsSummary: '' },
|
|
397
|
-
);
|
|
398
|
-
|
|
399
|
-
assert.deepEqual(
|
|
400
|
-
parseAgentActivity({
|
|
401
|
-
type: 'background-task-activity',
|
|
402
|
-
kind: 'tool_end',
|
|
403
|
-
tool: 'bash',
|
|
404
|
-
isError: true,
|
|
405
|
-
error: 'boom',
|
|
406
|
-
}),
|
|
407
|
-
{ kind: 'tool_end', tool: 'bash', isError: true, error: 'boom' },
|
|
408
|
-
);
|
|
409
|
-
assert.equal(
|
|
410
|
-
formatAgentActivityLine({ kind: 'tool_end', tool: 'read', isError: false }),
|
|
411
|
-
undefined,
|
|
412
|
-
);
|
|
413
|
-
assert.equal(
|
|
414
|
-
formatAgentActivityLine({ kind: 'tool_end', tool: 'bash', isError: true }),
|
|
415
|
-
'\u2717 bash failed',
|
|
416
|
-
);
|
|
417
|
-
assert.equal(
|
|
418
|
-
formatAgentActivityLine({
|
|
419
|
-
kind: 'tool_end',
|
|
420
|
-
tool: 'bash',
|
|
421
|
-
isError: true,
|
|
422
|
-
error: 'exit 1\nmore',
|
|
423
|
-
}),
|
|
424
|
-
'\u2717 bash failed: exit 1 more',
|
|
425
|
-
);
|
|
426
|
-
|
|
427
|
-
assert.equal(parseAgentActivity(null), undefined);
|
|
428
|
-
assert.equal(parseAgentActivity('background-task-activity'), undefined);
|
|
429
|
-
assert.equal(
|
|
430
|
-
parseAgentActivity({ type: 'background-task-telemetry', kind: 'tool_start', tool: 'read' }),
|
|
431
|
-
undefined,
|
|
432
|
-
);
|
|
433
|
-
assert.equal(
|
|
434
|
-
parseAgentActivity({ type: 'background-task-activity', kind: 'mystery' }),
|
|
435
|
-
undefined,
|
|
436
|
-
);
|
|
437
|
-
assert.equal(
|
|
438
|
-
parseAgentActivity({ type: 'background-task-activity', kind: 'tool_start' }),
|
|
439
|
-
undefined,
|
|
440
|
-
);
|
|
441
|
-
assert.equal(
|
|
442
|
-
parseAgentActivity({ type: 'background-task-activity', kind: 'assistant_text' }),
|
|
443
|
-
undefined,
|
|
444
|
-
);
|
|
445
|
-
const end = parseAgentActivity({
|
|
446
|
-
type: 'background-task-activity',
|
|
447
|
-
kind: 'tool_end',
|
|
448
|
-
tool: 'x',
|
|
449
|
-
isError: false,
|
|
450
|
-
error: ' ',
|
|
451
|
-
});
|
|
452
|
-
assert.equal(end?.kind, 'tool_end');
|
|
453
|
-
assert.equal(end?.tool, 'x');
|
|
454
|
-
assert.equal(end?.isError, false);
|
|
455
|
-
assert.ok(!end || !('error' in end) || end.error === undefined);
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
void it('boundedRead supports head, tail, truncation, and empty files', async () => {
|
|
459
|
-
const dir = await mkdtemp(join(tmpdir(), 'pi-bg-unit-'));
|
|
460
|
-
try {
|
|
461
|
-
const f = join(dir, 'out');
|
|
462
|
-
await writeFile(f, 'abcdef');
|
|
463
|
-
assert.deepEqual(await boundedRead(f, 3, true), {
|
|
464
|
-
content: 'def',
|
|
465
|
-
truncated: true,
|
|
466
|
-
bytesRead: 3,
|
|
467
|
-
totalBytes: 6,
|
|
468
|
-
});
|
|
469
|
-
assert.deepEqual(await boundedRead(f, 3, false), {
|
|
470
|
-
content: 'abc',
|
|
471
|
-
truncated: true,
|
|
472
|
-
bytesRead: 3,
|
|
473
|
-
totalBytes: 6,
|
|
474
|
-
});
|
|
475
|
-
assert.deepEqual(await boundedRead(f, 99, false), {
|
|
476
|
-
content: 'abcdef',
|
|
477
|
-
truncated: false,
|
|
478
|
-
bytesRead: 6,
|
|
479
|
-
totalBytes: 6,
|
|
480
|
-
});
|
|
481
|
-
const empty = join(dir, 'empty');
|
|
482
|
-
await writeFile(empty, '');
|
|
483
|
-
assert.deepEqual(await boundedRead(empty, 3, true), {
|
|
484
|
-
content: '',
|
|
485
|
-
truncated: false,
|
|
486
|
-
bytesRead: 0,
|
|
487
|
-
totalBytes: 0,
|
|
488
|
-
});
|
|
489
|
-
} finally {
|
|
490
|
-
await rm(dir, { recursive: true, force: true });
|
|
491
|
-
}
|
|
492
|
-
});
|
|
493
|
-
});
|