@pi-unipi/background-tasks 2.16.1 → 2.17.0

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 (46) hide show
  1. package/README.md +21 -27
  2. package/package.json +3 -4
  3. package/src/cards.ts +76 -0
  4. package/src/child-process.ts +1 -1
  5. package/src/config.ts +0 -42
  6. package/src/context-visible-conversation-v2.ts +1 -1
  7. package/src/delegate/artifacts.ts +1 -1
  8. package/src/delegate/launch.ts +17 -30
  9. package/src/delegate/result-package.ts +1 -1
  10. package/src/delegate/runner.ts +1 -20
  11. package/src/delegate/seed.ts +1 -1
  12. package/src/delegate-extension.ts +16 -168
  13. package/src/index.ts +53 -25
  14. package/src/json-utils.ts +56 -0
  15. package/src/package-assets.ts +51 -0
  16. package/src/registry.ts +8 -459
  17. package/src/task-manager.ts +13 -2
  18. package/src/tools.ts +4 -189
  19. package/src/types.ts +17 -70
  20. package/extensions/anthropic-attribution.ts +0 -1
  21. package/extensions/fusion-child.ts +0 -1
  22. package/src/anthropic-attribution-path.ts +0 -21
  23. package/src/anthropic-attribution.ts +0 -1983
  24. package/src/attested-pi-run.ts +0 -612
  25. package/src/fixtures/fusion-golden-bytes.json +0 -310
  26. package/src/fixtures/fusion-validate-golden-bytes.json +0 -282
  27. package/src/fusion/artifacts.ts +0 -967
  28. package/src/fusion/budget.ts +0 -1162
  29. package/src/fusion/child-protocol.ts +0 -305
  30. package/src/fusion/claude-cache.ts +0 -207
  31. package/src/fusion/clean-context.ts +0 -91
  32. package/src/fusion/config.ts +0 -449
  33. package/src/fusion/context.ts +0 -265
  34. package/src/fusion/evaluation.ts +0 -800
  35. package/src/fusion/orchestrator.ts +0 -1288
  36. package/src/fusion/output-contract.ts +0 -34
  37. package/src/fusion/pi-child.ts +0 -2373
  38. package/src/fusion/prompts.ts +0 -345
  39. package/src/fusion/result-package.ts +0 -959
  40. package/src/fusion/source-policy.ts +0 -257
  41. package/src/fusion/types.ts +0 -1139
  42. package/src/fusion/web-fetch.ts +0 -1060
  43. package/src/fusion/workflows.ts +0 -184
  44. package/src/fusion-child-extension.ts +0 -1052
  45. package/src/fusion-extension.ts +0 -1293
  46. package/src/ui/fusion-model-selector.ts +0 -322
@@ -1,322 +0,0 @@
1
- import type { Theme } from '@earendil-works/pi-coding-agent';
2
- import { Key, matchesKey, truncateToWidth, type Component } from '@earendil-works/pi-tui';
3
- import { CURRENT_MODEL_SELECTION, defaultFusionModelConfig } from '../fusion/config.js';
4
- import type { FusionModelConfigV1, FusionModelSelection } from '../fusion/types.js';
5
-
6
- export const FUSION_MODEL_SLOT_IDS = [
7
- 'candidate-1',
8
- 'candidate-2',
9
- 'candidate-3',
10
- 'evaluator',
11
- 'merger',
12
- ] as const;
13
-
14
- export type FusionModelSlotId = (typeof FUSION_MODEL_SLOT_IDS)[number];
15
-
16
- export interface FusionModelChoice {
17
- value: FusionModelSelection;
18
- label: string;
19
- description: string;
20
- available: boolean;
21
- }
22
-
23
- export type FusionModelSelectorResult =
24
- | { type: 'saved'; config: FusionModelConfigV1 }
25
- | { type: 'cancelled' };
26
-
27
- export interface FusionModelSelectorOptions {
28
- initialConfig: FusionModelConfigV1;
29
- choices: readonly FusionModelChoice[];
30
- theme: Theme;
31
- onSave: (config: FusionModelConfigV1) => Promise<void>;
32
- onDone: (result: FusionModelSelectorResult) => void;
33
- onRenderRequest?: (() => void) | undefined;
34
- }
35
-
36
- type SelectorMode = 'slots' | 'models';
37
-
38
- interface SlotView {
39
- id: FusionModelSlotId;
40
- label: string;
41
- value: FusionModelSelection;
42
- }
43
-
44
- function slotLabel(id: FusionModelSlotId): string {
45
- if (id === 'candidate-1') return 'Candidate 1';
46
- if (id === 'candidate-2') return 'Candidate 2';
47
- if (id === 'candidate-3') return 'Candidate 3';
48
- if (id === 'evaluator') return 'Evaluator';
49
- return 'Merger';
50
- }
51
-
52
- function configValue(config: FusionModelConfigV1, id: FusionModelSlotId): FusionModelSelection {
53
- if (id === 'candidate-1') return config.candidates[0];
54
- if (id === 'candidate-2') return config.candidates[1];
55
- if (id === 'candidate-3') return config.candidates[2];
56
- if (id === 'evaluator') return config.evaluator;
57
- return config.merger;
58
- }
59
-
60
- function updateConfig(
61
- config: FusionModelConfigV1,
62
- id: FusionModelSlotId,
63
- value: FusionModelSelection,
64
- ): FusionModelConfigV1 {
65
- if (id === 'candidate-1')
66
- return { ...config, candidates: [value, config.candidates[1], config.candidates[2]] };
67
- if (id === 'candidate-2')
68
- return { ...config, candidates: [config.candidates[0], value, config.candidates[2]] };
69
- if (id === 'candidate-3')
70
- return { ...config, candidates: [config.candidates[0], config.candidates[1], value] };
71
- if (id === 'evaluator') return { ...config, evaluator: value };
72
- return { ...config, merger: value };
73
- }
74
-
75
- function queryMatches(choice: FusionModelChoice, query: string): boolean {
76
- if (query.length === 0) return true;
77
- const haystack = `${choice.label} ${choice.description} ${choice.value}`.toLowerCase();
78
- return haystack.includes(query.toLowerCase());
79
- }
80
-
81
- function printable(data: string): string | undefined {
82
- if (data.length !== 1) return undefined;
83
- const code = data.charCodeAt(0);
84
- if (code < 32 || code === 127) return undefined;
85
- return data;
86
- }
87
-
88
- function modelChoiceLine(choice: FusionModelChoice): string {
89
- const status = choice.available ? '' : ' (unavailable)';
90
- return `${choice.label}${status}${choice.description ? ` — ${choice.description}` : ''}`;
91
- }
92
-
93
- function slotViews(config: FusionModelConfigV1): SlotView[] {
94
- return FUSION_MODEL_SLOT_IDS.map((id) => ({
95
- id,
96
- label: slotLabel(id),
97
- value: configValue(config, id),
98
- }));
99
- }
100
-
101
- export class FusionModelSelector implements Component {
102
- private readonly choices: readonly FusionModelChoice[];
103
- private readonly theme: Theme;
104
- private readonly onSave: (config: FusionModelConfigV1) => Promise<void>;
105
- private readonly onDone: (result: FusionModelSelectorResult) => void;
106
- private readonly onRenderRequest: (() => void) | undefined;
107
- private draft: FusionModelConfigV1;
108
- private mode: SelectorMode = 'slots';
109
- private selectedSlot = 0;
110
- private selectedChoice = 0;
111
- private search = '';
112
- private saving = false;
113
- private error: string | undefined;
114
- private cachedWidth: number | undefined;
115
- private cachedLines: string[] | undefined;
116
-
117
- constructor(options: FusionModelSelectorOptions) {
118
- this.draft = options.initialConfig;
119
- this.choices = options.choices;
120
- this.theme = options.theme;
121
- this.onSave = options.onSave;
122
- this.onDone = options.onDone;
123
- this.onRenderRequest = options.onRenderRequest;
124
- }
125
-
126
- render(width: number): string[] {
127
- if (this.cachedWidth === width && this.cachedLines !== undefined) return this.cachedLines;
128
- const lines = this.mode === 'slots' ? this.renderSlots(width) : this.renderChoices(width);
129
- this.cachedWidth = width;
130
- this.cachedLines = lines.map((line) => truncateToWidth(line, Math.max(1, width)));
131
- return this.cachedLines;
132
- }
133
-
134
- handleInput(data: string): void {
135
- if (this.saving) return;
136
- if (this.mode === 'slots') this.handleSlotInput(data);
137
- else this.handleChoiceInput(data);
138
- }
139
-
140
- invalidate(): void {
141
- this.cachedWidth = undefined;
142
- this.cachedLines = undefined;
143
- }
144
-
145
- private renderSlots(width: number): string[] {
146
- const slots = slotViews(this.draft);
147
- const lines = [
148
- this.theme.fg('accent', this.theme.bold('Fusion models')),
149
- this.theme.fg('dim', 'Select five slots. Duplicate models are allowed.'),
150
- '',
151
- ];
152
- for (const [index, slot] of slots.entries()) {
153
- const marker = index === this.selectedSlot ? this.theme.fg('accent', '›') : ' ';
154
- const value = this.describeSelection(slot.value);
155
- lines.push(`${marker} ${slot.label.padEnd(12)} ${value}`);
156
- }
157
- lines.push('');
158
- if (this.error !== undefined) lines.push(this.theme.fg('error', this.error));
159
- const saveText = this.saving ? 'saving…' : 's save';
160
- lines.push(this.theme.fg('dim', `↑↓ slot • enter choose • r reset • ${saveText} • esc cancel`));
161
- return lines.map((line) => truncateToWidth(line, width));
162
- }
163
-
164
- private renderChoices(width: number): string[] {
165
- const selectedSlot = FUSION_MODEL_SLOT_IDS[this.selectedSlot];
166
- const title =
167
- selectedSlot === undefined ? 'Choose model' : `Choose model for ${slotLabel(selectedSlot)}`;
168
- const filtered = this.filteredChoices();
169
- const lines = [
170
- this.theme.fg('accent', this.theme.bold(title)),
171
- `${this.theme.fg('dim', 'Search:')} ${this.search || this.theme.fg('dim', '(type to filter)')}`,
172
- '',
173
- ];
174
- if (filtered.length === 0) {
175
- lines.push(this.theme.fg('warning', 'No matching models.'));
176
- } else {
177
- const windowStart = Math.max(
178
- 0,
179
- Math.min(this.selectedChoice - 6, Math.max(0, filtered.length - 12)),
180
- );
181
- const windowItems = filtered.slice(windowStart, windowStart + 12);
182
- for (const [offset, choice] of windowItems.entries()) {
183
- const index = windowStart + offset;
184
- const marker = index === this.selectedChoice ? this.theme.fg('accent', '›') : ' ';
185
- const label = choice.available ? choice.label : this.theme.fg('warning', choice.label);
186
- const description = choice.description
187
- ? this.theme.fg('dim', ` — ${choice.description}`)
188
- : '';
189
- const stale = choice.available ? '' : this.theme.fg('warning', ' (unavailable)');
190
- lines.push(`${marker} ${label}${stale}${description}`);
191
- }
192
- if (filtered.length > windowItems.length) {
193
- lines.push(
194
- this.theme.fg(
195
- 'dim',
196
- `${String(windowStart + 1)}-${String(windowStart + windowItems.length)} of ${String(filtered.length)}`,
197
- ),
198
- );
199
- }
200
- }
201
- lines.push('');
202
- lines.push(this.theme.fg('dim', '↑↓ choose • enter apply • backspace edit search • esc back'));
203
- return lines.map((line) => truncateToWidth(line, width));
204
- }
205
-
206
- private handleSlotInput(data: string): void {
207
- if (matchesKey(data, Key.up)) {
208
- this.selectedSlot = Math.max(0, this.selectedSlot - 1);
209
- this.changed();
210
- return;
211
- }
212
- if (matchesKey(data, Key.down)) {
213
- this.selectedSlot = Math.min(FUSION_MODEL_SLOT_IDS.length - 1, this.selectedSlot + 1);
214
- this.changed();
215
- return;
216
- }
217
- if (matchesKey(data, Key.enter) || data === '\r') {
218
- this.mode = 'models';
219
- this.search = '';
220
- this.selectedChoice = this.selectedChoiceForCurrentSlot();
221
- this.changed();
222
- return;
223
- }
224
- if (data === 'r') {
225
- this.draft = defaultFusionModelConfig();
226
- this.error = undefined;
227
- this.changed();
228
- return;
229
- }
230
- if (data === 's') {
231
- void this.save();
232
- return;
233
- }
234
- if (matchesKey(data, Key.escape) || data === 'q') {
235
- this.onDone({ type: 'cancelled' });
236
- }
237
- }
238
-
239
- private handleChoiceInput(data: string): void {
240
- const filtered = this.filteredChoices();
241
- if (matchesKey(data, Key.up)) {
242
- this.selectedChoice = Math.max(0, this.selectedChoice - 1);
243
- this.changed();
244
- return;
245
- }
246
- if (matchesKey(data, Key.down)) {
247
- this.selectedChoice = Math.min(Math.max(0, filtered.length - 1), this.selectedChoice + 1);
248
- this.changed();
249
- return;
250
- }
251
- if (matchesKey(data, Key.enter) || data === '\r') {
252
- const choice = filtered[this.selectedChoice];
253
- const slot = FUSION_MODEL_SLOT_IDS[this.selectedSlot];
254
- if (choice !== undefined && slot !== undefined) {
255
- this.draft = updateConfig(this.draft, slot, choice.value);
256
- this.mode = 'slots';
257
- this.error = undefined;
258
- }
259
- this.changed();
260
- return;
261
- }
262
- if (matchesKey(data, Key.escape)) {
263
- this.mode = 'slots';
264
- this.changed();
265
- return;
266
- }
267
- if (matchesKey(data, Key.backspace) || data === '\x7f') {
268
- this.search = this.search.slice(0, -1);
269
- this.selectedChoice = 0;
270
- this.changed();
271
- return;
272
- }
273
- const char = printable(data);
274
- if (char !== undefined) {
275
- this.search += char;
276
- this.selectedChoice = 0;
277
- this.changed();
278
- }
279
- }
280
-
281
- private async save(): Promise<void> {
282
- this.saving = true;
283
- this.error = undefined;
284
- this.changed();
285
- try {
286
- await this.onSave(this.draft);
287
- this.onDone({ type: 'saved', config: this.draft });
288
- } catch (error) {
289
- this.error = error instanceof Error ? error.message : String(error);
290
- this.saving = false;
291
- this.changed();
292
- }
293
- }
294
-
295
- private changed(): void {
296
- this.invalidate();
297
- this.onRenderRequest?.();
298
- }
299
-
300
- private filteredChoices(): readonly FusionModelChoice[] {
301
- return this.choices.filter((choice) => queryMatches(choice, this.search));
302
- }
303
-
304
- private selectedChoiceForCurrentSlot(): number {
305
- const slot = FUSION_MODEL_SLOT_IDS[this.selectedSlot];
306
- if (slot === undefined) return 0;
307
- const current = configValue(this.draft, slot);
308
- const filtered = this.filteredChoices();
309
- const index = filtered.findIndex((choice) => choice.value === current);
310
- return index >= 0 ? index : 0;
311
- }
312
-
313
- private describeSelection(value: FusionModelSelection): string {
314
- if (value === CURRENT_MODEL_SELECTION) {
315
- const current = this.choices.find((choice) => choice.value === CURRENT_MODEL_SELECTION);
316
- return current === undefined ? CURRENT_MODEL_SELECTION : modelChoiceLine(current);
317
- }
318
- const found = this.choices.find((choice) => choice.value === value);
319
- if (found === undefined) return `${value} (unavailable)`;
320
- return modelChoiceLine(found);
321
- }
322
- }