pi-background-tasks 2.1.4 → 2.4.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.
- package/BACKGROUND-TASKS-INSTRUCTIONS.md +1 -1
- package/PUBLISHING.md +2 -0
- package/README.md +16 -8
- package/TESTING.md +23 -14
- package/TEST_PLAN.md +13 -12
- package/THIRD_PARTY_NOTICES.md +30 -0
- package/docs/INDEX.md +8 -4
- package/docs/choose-a-workflow.md +5 -2
- package/docs/commands/claude-cache.md +50 -0
- package/docs/concepts/context-projection-and-budgeting.md +4 -2
- package/docs/getting-started.md +3 -0
- package/docs/manifest.json +86 -22
- package/docs/operations/configuration.md +15 -1
- package/docs/operations/releasing.md +6 -3
- package/docs/operations/troubleshooting.md +3 -1
- package/docs/read-before-edit.md +4 -1
- package/docs/reference/runtime-contracts.md +53 -53
- package/docs/subsystems/anthropic-attribution.md +63 -0
- package/docs/subsystems/attested-pi-runs.md +2 -2
- package/docs/subsystems/child-launch-durability-and-safety.md +3 -3
- package/docs/subsystems/delegation.md +34 -17
- package/docs/subsystems/docs-freshness-gate.md +6 -6
- package/docs/subsystems/fusion.md +2 -2
- package/docs/tools/bg_delegate.md +33 -16
- package/docs/tools/bg_result.md +2 -2
- package/docs/tools/bg_run.md +5 -0
- package/extensions/anthropic-attribution.ts +1 -0
- package/package.json +4 -2
- package/src/core/anthropic-attribution-path.ts +26 -0
- package/src/core/{fusion/anthropic-attribution.ts → anthropic-attribution.ts} +61 -8
- package/src/core/attested-pi-run.ts +10 -1
- package/src/core/common.ts +2 -1
- package/src/core/context/token-budget.ts +16 -3
- package/src/core/delegate/artifacts.ts +18 -12
- package/src/core/delegate/budget.ts +78 -33
- package/src/core/delegate/launch.ts +48 -16
- package/src/core/delegate/result-package.ts +16 -0
- package/src/core/delegate/runner.ts +45 -2
- package/src/core/delegate/seed.ts +12 -0
- package/src/core/delegate/types.ts +22 -3
- package/src/core/fusion/config.ts +1 -1
- package/src/core/fusion/pi-child.ts +7 -124
- package/src/core/registry.ts +4 -1
- package/src/delegate-child-extension.ts +377 -71
- package/src/delegate-extension.ts +58 -6
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
import {
|
|
22
22
|
DELEGATE_AUTO_DELIVER_MODES,
|
|
23
23
|
DELEGATE_CAPABILITIES,
|
|
24
|
+
DELEGATE_EXTENSION_MODES,
|
|
24
25
|
DELEGATE_RESULT_TOOL_NAME,
|
|
25
26
|
DELEGATE_TOOL_NAME,
|
|
26
27
|
DelegateError,
|
|
@@ -28,6 +29,7 @@ import {
|
|
|
28
29
|
type DelegateCapability,
|
|
29
30
|
type DelegateDeliveryMode,
|
|
30
31
|
type DelegateBudgetRouteSource,
|
|
32
|
+
type DelegateExtensionMode,
|
|
31
33
|
type DelegateRoute,
|
|
32
34
|
} from './core/delegate/types.js';
|
|
33
35
|
import {
|
|
@@ -67,7 +69,7 @@ const HOOK_EVIDENCE_PATH = fileURLToPath(
|
|
|
67
69
|
new URL('./core/delegate/hook-contract-evidence.json', import.meta.url),
|
|
68
70
|
);
|
|
69
71
|
|
|
70
|
-
const DelegateParams = Type.Object(
|
|
72
|
+
export const DelegateParams = Type.Object(
|
|
71
73
|
{
|
|
72
74
|
name: Type.String({
|
|
73
75
|
description: 'Short human-readable task name shown in the bg footer dock. Use 2-6 words.',
|
|
@@ -93,6 +95,12 @@ const DelegateParams = Type.Object(
|
|
|
93
95
|
description: `Capability profile. Only "inspect" (read/search/list, no shell, no writes, no network, no recursion) is supported.`,
|
|
94
96
|
}),
|
|
95
97
|
),
|
|
98
|
+
extensionMode: Type.Optional(
|
|
99
|
+
Type.String({
|
|
100
|
+
description:
|
|
101
|
+
'Extension discovery: isolated | ambient. Default isolated. Ambient is for extension-registered providers and executes arbitrary discovered extension code, weakening process isolation.',
|
|
102
|
+
}),
|
|
103
|
+
),
|
|
96
104
|
maxTurns: Type.Optional(
|
|
97
105
|
Type.Number({
|
|
98
106
|
description: `Maximum agent turns. Default ${String(DELEGATE_DEFAULT_MAX_TURNS)}.`,
|
|
@@ -142,6 +150,20 @@ const ResultParams = Type.Object(
|
|
|
142
150
|
type DelegateParamsValue = Static<typeof DelegateParams>;
|
|
143
151
|
type ResultParamsValue = Static<typeof ResultParams>;
|
|
144
152
|
|
|
153
|
+
const DELEGATE_PARAM_KEYS = new Set([
|
|
154
|
+
'name',
|
|
155
|
+
'prompt',
|
|
156
|
+
'route',
|
|
157
|
+
'capability',
|
|
158
|
+
'extensionMode',
|
|
159
|
+
'maxTurns',
|
|
160
|
+
'maxToolCalls',
|
|
161
|
+
'timeoutSeconds',
|
|
162
|
+
'autoDeliver',
|
|
163
|
+
'notifyOnCompletion',
|
|
164
|
+
'triggerOnCompletion',
|
|
165
|
+
]);
|
|
166
|
+
|
|
145
167
|
export interface DelegateLaunchDetails {
|
|
146
168
|
schema_version: 'pi-background-tasks.delegate-launch.v1';
|
|
147
169
|
task: BgTaskSnapshot;
|
|
@@ -151,6 +173,7 @@ export interface DelegateLaunchDetails {
|
|
|
151
173
|
seed_sha256: string;
|
|
152
174
|
seed_utf8_bytes: number;
|
|
153
175
|
budget: DelegateBudgetRouteSource;
|
|
176
|
+
extension_mode: DelegateExtensionMode;
|
|
154
177
|
auto_deliver: DelegateAutoDeliverMode;
|
|
155
178
|
notify_on_completion: boolean;
|
|
156
179
|
trigger_on_completion: boolean;
|
|
@@ -187,6 +210,7 @@ export interface DelegateResultDetails {
|
|
|
187
210
|
delivery: DelegateDeliveryMode | 'none';
|
|
188
211
|
route?: { provider: string; model: string } | undefined;
|
|
189
212
|
budget?: DelegateBudgetRouteSource | undefined;
|
|
213
|
+
extension_mode?: DelegateExtensionMode | undefined;
|
|
190
214
|
answer_bytes?: number | undefined;
|
|
191
215
|
answer_sha256?: string | undefined;
|
|
192
216
|
turns?: number | undefined;
|
|
@@ -213,6 +237,15 @@ function requireCapability(value: unknown): DelegateCapability {
|
|
|
213
237
|
);
|
|
214
238
|
}
|
|
215
239
|
|
|
240
|
+
function requireExtensionMode(value: unknown): DelegateExtensionMode {
|
|
241
|
+
if (value === undefined) return 'isolated';
|
|
242
|
+
if (value === 'isolated' || value === 'ambient') return value;
|
|
243
|
+
throw new DelegateError(
|
|
244
|
+
`bg_delegate extensionMode must be one of ${DELEGATE_EXTENSION_MODES.join(', ')}`,
|
|
245
|
+
{ code: 'invalid_arguments', childCreated: false },
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
216
249
|
function requireAutoDeliver(value: unknown): DelegateAutoDeliverMode {
|
|
217
250
|
if (value === undefined) return 'never';
|
|
218
251
|
if (value === 'never' || value === 'when_small' || value === 'always') return value;
|
|
@@ -308,13 +341,15 @@ export function registerDelegateExtension(
|
|
|
308
341
|
name: DELEGATE_TOOL_NAME,
|
|
309
342
|
label: 'Background Delegate',
|
|
310
343
|
description:
|
|
311
|
-
'Launch one background Pi agent seeded with a frozen projection of the current conversation, then return a launch receipt immediately. The child has its own session, a route pinned at launch that is never substituted, and read-only tools. Retrieve its verified answer with bg_result.',
|
|
344
|
+
'Launch one background Pi agent seeded with a frozen projection of the current conversation, then return a launch receipt immediately. The child has its own session, a route pinned at launch that is never substituted, and read-only tools. Extension discovery is isolated by default; ambient mode supports extension-registered providers but executes arbitrary discovered extension code. Retrieve its verified answer with bg_result.',
|
|
312
345
|
promptSnippet:
|
|
313
346
|
'Delegate an investigation to a background agent that already has this conversation as context',
|
|
314
347
|
promptGuidelines: [
|
|
315
348
|
'Use bg_delegate when work should continue in the background and the worker needs what you already know: it is seeded with a projection of this conversation.',
|
|
316
349
|
'The prompt is authoritative. State exactly what you want investigated and what the answer should contain.',
|
|
317
|
-
'The delegate is inspect-only: it can read, search, and list files, but cannot run shell commands, edit or write files, use the network, or delegate further.',
|
|
350
|
+
'The delegate is inspect-only at the model-visible tool boundary: it can read, search, and list files, but cannot run shell commands, edit or write files, use the network, or delegate further.',
|
|
351
|
+
'Extension discovery is isolated by default. Use extensionMode:"ambient" only when the pinned provider is registered by an ambient user/project extension.',
|
|
352
|
+
'Ambient mode executes arbitrary discovered extension code in the child process. Tool allowlists do not sandbox extension code, so ambient mode weakens inspect-only process isolation.',
|
|
318
353
|
'Facts that exist only inside omitted tool output are not available to the delegate. Restate such findings in the prompt.',
|
|
319
354
|
'bg_delegate returns immediately. Do not poll; retrieve the answer with bg_result after the terminal notification arrives.',
|
|
320
355
|
],
|
|
@@ -325,6 +360,13 @@ export function registerDelegateExtension(
|
|
|
325
360
|
code: 'invalid_arguments',
|
|
326
361
|
childCreated: false,
|
|
327
362
|
});
|
|
363
|
+
const unknownKeys = Object.keys(args).filter((key) => !DELEGATE_PARAM_KEYS.has(key));
|
|
364
|
+
if (unknownKeys.length > 0) {
|
|
365
|
+
throw new DelegateError(
|
|
366
|
+
`bg_delegate contains unsupported key(s): ${unknownKeys.sort().join(', ')}`,
|
|
367
|
+
{ code: 'invalid_arguments', childCreated: false },
|
|
368
|
+
);
|
|
369
|
+
}
|
|
328
370
|
const name = args['name'];
|
|
329
371
|
const prompt = args['prompt'];
|
|
330
372
|
if (typeof name !== 'string' || name.trim().length === 0)
|
|
@@ -341,6 +383,7 @@ export function registerDelegateExtension(
|
|
|
341
383
|
const route = requireRoute(args['route']);
|
|
342
384
|
if (route !== undefined) prepared.route = route;
|
|
343
385
|
prepared.capability = requireCapability(args['capability']);
|
|
386
|
+
prepared.extensionMode = requireExtensionMode(args['extensionMode']);
|
|
344
387
|
prepared.autoDeliver = requireAutoDeliver(args['autoDeliver']);
|
|
345
388
|
const maxTurns = optionalPositiveInteger(args['maxTurns'], 'maxTurns');
|
|
346
389
|
if (maxTurns !== undefined) prepared.maxTurns = maxTurns;
|
|
@@ -356,6 +399,7 @@ export function registerDelegateExtension(
|
|
|
356
399
|
},
|
|
357
400
|
async execute(toolCallId, params, _signal, _onUpdate, ctx) {
|
|
358
401
|
const capability = requireCapability(params.capability);
|
|
402
|
+
const extensionMode = requireExtensionMode(params.extensionMode);
|
|
359
403
|
const autoDeliver = requireAutoDeliver(params.autoDeliver);
|
|
360
404
|
const hookEvidence = await loadEvidence();
|
|
361
405
|
const route = resolveDelegateRoute({
|
|
@@ -385,6 +429,7 @@ export function registerDelegateExtension(
|
|
|
385
429
|
toolCallId,
|
|
386
430
|
prompt: params.prompt,
|
|
387
431
|
capability,
|
|
432
|
+
extensionMode,
|
|
388
433
|
route,
|
|
389
434
|
limitOverrides: {
|
|
390
435
|
maxTurns: params.maxTurns,
|
|
@@ -421,8 +466,9 @@ export function registerDelegateExtension(
|
|
|
421
466
|
child_session_id: prepared.preflight.childSessionId,
|
|
422
467
|
artifact_dir: prepared.facts.artifactDir,
|
|
423
468
|
seed_sha256: prepared.facts.seedSha256,
|
|
424
|
-
seed_utf8_bytes: prepared.preflight.
|
|
469
|
+
seed_utf8_bytes: Buffer.byteLength(prepared.preflight.seed.serialized, 'utf8'),
|
|
425
470
|
budget: prepared.facts.budget,
|
|
471
|
+
extension_mode: extensionMode,
|
|
426
472
|
auto_deliver: autoDeliver,
|
|
427
473
|
notify_on_completion: launchOptions.notifyOnCompletion,
|
|
428
474
|
trigger_on_completion: launchOptions.triggerOnCompletion,
|
|
@@ -434,9 +480,11 @@ export function registerDelegateExtension(
|
|
|
434
480
|
`Route pinned: ${route.qualified_id} (${route.origin}); it is never substituted.`,
|
|
435
481
|
`Child session: ${prepared.preflight.childSessionId} (separate from this session)`,
|
|
436
482
|
`Artifacts: ${prepared.facts.artifactDir}`,
|
|
437
|
-
`Seed: ${String(prepared.preflight.
|
|
483
|
+
`Seed: ${String(Buffer.byteLength(prepared.preflight.seed.serialized, 'utf8'))} bytes, sha256 ${prepared.facts.seedSha256}`,
|
|
484
|
+
`Child prompt: ${String(prepared.preflight.plan.child_prompt_utf8_bytes)} bytes; launch estimate ${String(prepared.preflight.plan.launch_input_tokens_upper_bound)} / ${String(prepared.preflight.plan.route.allowed_input_tokens)} allowed input tokens; protected retained-growth runway ${String(prepared.preflight.plan.retained_growth_budget_tokens)} tokens.`,
|
|
438
485
|
`Estimator: family ${prepared.facts.budget.family}, source ${prepared.facts.budget.rate_source.source}, rate ${String(prepared.facts.budget.rate_source.effective_rate_bytes_per_token_x100)}/100 B/tok + ${String(prepared.facts.budget.rate_source.affine_f_tokens)} tokens${prepared.facts.budget.rate_source.warning === null ? '' : `; warning: ${prepared.facts.budget.rate_source.warning}`}`,
|
|
439
486
|
`Capability: ${capability} (read/search/list only)`,
|
|
487
|
+
`Extension mode: ${extensionMode}${extensionMode === 'ambient' ? ' — WARNING: arbitrary discovered extension code executes in the child; the tool allowlist does not sandbox it, so inspect-only process isolation is weakened.' : ' (ambient extension discovery disabled)'}`,
|
|
440
488
|
`Limits: ${String(prepared.preflight.limits.max_turns)} turns, ${String(prepared.preflight.limits.max_tool_calls)} tool calls, ${String(prepared.preflight.limits.timeout_seconds)}s`,
|
|
441
489
|
`Auto-deliver: ${autoDeliver}`,
|
|
442
490
|
launchOptions.notifyOnCompletion
|
|
@@ -458,7 +506,7 @@ export function registerDelegateExtension(
|
|
|
458
506
|
renderResult(result, _options, theme) {
|
|
459
507
|
const details = result.details;
|
|
460
508
|
return new Text(
|
|
461
|
-
`${theme.fg('success', '✓ delegated')} ${theme.fg('accent', details.task.id)}\n${theme.fg('dim', `route ${details.route.qualified_id} · seed ${String(details.seed_utf8_bytes)}B · ${details.artifact_dir}`)}`,
|
|
509
|
+
`${theme.fg('success', '✓ delegated')} ${theme.fg('accent', details.task.id)}\n${theme.fg('dim', `route ${details.route.qualified_id} · extensions ${details.extension_mode} · seed ${String(details.seed_utf8_bytes)}B · ${details.artifact_dir}`)}`,
|
|
462
510
|
0,
|
|
463
511
|
0,
|
|
464
512
|
);
|
|
@@ -633,6 +681,7 @@ export function registerDelegateExtension(
|
|
|
633
681
|
delivery: 'none',
|
|
634
682
|
artifact_dir: facts.artifactDir,
|
|
635
683
|
budget: facts.budget,
|
|
684
|
+
extension_mode: facts.extensionMode,
|
|
636
685
|
};
|
|
637
686
|
return {
|
|
638
687
|
content: textContent(
|
|
@@ -650,6 +699,8 @@ export function registerDelegateExtension(
|
|
|
650
699
|
route: { provider: facts.route.provider, model: facts.route.model },
|
|
651
700
|
taskStatus: task.status === 'completed' ? 'completed' : task.status,
|
|
652
701
|
taskError: task.error,
|
|
702
|
+
taskOutputPath: task.outputPath,
|
|
703
|
+
taskOutputAbsPath: task.outputAbsPath,
|
|
653
704
|
});
|
|
654
705
|
|
|
655
706
|
if (terminal.error !== undefined || terminal.result === undefined) {
|
|
@@ -686,6 +737,7 @@ export function registerDelegateExtension(
|
|
|
686
737
|
delivery: decision.mode,
|
|
687
738
|
route: verified.package.route,
|
|
688
739
|
budget: facts.budget,
|
|
740
|
+
extension_mode: facts.extensionMode,
|
|
689
741
|
answer_bytes: verified.package.answer.byte_length,
|
|
690
742
|
answer_sha256: verified.package.answer.sha256,
|
|
691
743
|
turns: verified.package.turns,
|