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