pi-background-tasks 2.1.3 → 2.3.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 -9
- package/TESTING.md +15 -10
- package/TEST_PLAN.md +12 -11
- 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/getting-started.md +3 -0
- package/docs/manifest.json +84 -19
- package/docs/operations/configuration.md +15 -1
- package/docs/operations/releasing.md +6 -3
- package/docs/read-before-edit.md +4 -1
- package/docs/reference/runtime-contracts.md +72 -71
- 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 +12 -4
- package/docs/subsystems/docs-freshness-gate.md +6 -6
- package/docs/subsystems/fusion.md +6 -2
- package/docs/tools/bg_delegate.md +21 -9
- package/docs/tools/bg_result.md +8 -1
- 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/delegate/artifacts.ts +4 -0
- package/src/core/delegate/launch.ts +44 -14
- package/src/core/delegate/runner.ts +24 -0
- package/src/core/delegate/seed.ts +12 -0
- package/src/core/delegate/types.ts +10 -2
- package/src/core/fusion/artifacts.ts +265 -3
- package/src/core/fusion/config.ts +1 -1
- package/src/core/fusion/orchestrator.ts +47 -57
- package/src/core/fusion/pi-child.ts +7 -124
- package/src/core/fusion/result-package.ts +550 -3
- package/src/core/fusion/types.ts +87 -0
- package/src/core/registry.ts +4 -1
- package/src/delegate-child-extension.ts +9 -2
- package/src/delegate-extension.ts +119 -9
package/src/core/fusion/types.ts
CHANGED
|
@@ -30,6 +30,8 @@ export const FUSION_CALIBRATION_VIOLATION_SCHEMA_VERSION =
|
|
|
30
30
|
export const FUSION_VALIDATE_CANDIDATE_CONTRACT_EVENT_SCHEMA_VERSION =
|
|
31
31
|
'pi-background-tasks.fusion-validation-candidate-contract-event.v1';
|
|
32
32
|
export const FUSION_TOOL_CALL_LOG_SCHEMA_VERSION = 'pi-background-tasks.fusion-tool-call.v1';
|
|
33
|
+
export const FUSION_FAILURE_SUMMARY_SCHEMA_VERSION =
|
|
34
|
+
'pi-background-tasks.fusion-failure-summary.v1';
|
|
33
35
|
|
|
34
36
|
/**
|
|
35
37
|
* Conversation-projection transform shared by every Fusion entry point.
|
|
@@ -877,6 +879,91 @@ export interface FusionArtifactRef {
|
|
|
877
879
|
sha256: string;
|
|
878
880
|
}
|
|
879
881
|
|
|
882
|
+
export type FusionFailureArtifactClassification =
|
|
883
|
+
| 'complete_stage_output'
|
|
884
|
+
| 'partial_stage_output'
|
|
885
|
+
| 'oversized_original'
|
|
886
|
+
| 'empty_rejected_output'
|
|
887
|
+
| 'evidence_only';
|
|
888
|
+
|
|
889
|
+
export interface FusionFailureEvidenceArtifact {
|
|
890
|
+
name: string;
|
|
891
|
+
classification: FusionFailureArtifactClassification;
|
|
892
|
+
ref: FusionArtifactRef;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
export interface FusionFailureAttemptMetadata {
|
|
896
|
+
stage: FusionStage;
|
|
897
|
+
slot?: 1 | 2 | 3 | undefined;
|
|
898
|
+
attempt: number;
|
|
899
|
+
status: 'completed' | 'failed' | 'cancelled';
|
|
900
|
+
child_created: boolean;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
export interface FusionFailureList<T> {
|
|
904
|
+
listed: readonly T[];
|
|
905
|
+
omitted_count: number;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
export interface FusionFailureMessageMetadata {
|
|
909
|
+
byte_length: number;
|
|
910
|
+
sha256: string;
|
|
911
|
+
inline_message?: string | undefined;
|
|
912
|
+
omission_reason?: 'exceeds_inline_message_bytes_cap' | 'result_view_byte_budget' | undefined;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
export interface FusionFailureSummaryV1 {
|
|
916
|
+
schema_version: typeof FUSION_FAILURE_SUMMARY_SCHEMA_VERSION;
|
|
917
|
+
run_id: string;
|
|
918
|
+
workflow: FusionWorkflowId;
|
|
919
|
+
source: FusionSource;
|
|
920
|
+
terminal_state: Exclude<FusionTerminalState, 'completed'>;
|
|
921
|
+
created_at: string;
|
|
922
|
+
answer: { present: false; reason: 'run_did_not_commit' };
|
|
923
|
+
failure: {
|
|
924
|
+
code: FusionErrorCode | null;
|
|
925
|
+
stage?: FusionStage | undefined;
|
|
926
|
+
slot?: 1 | 2 | 3 | undefined;
|
|
927
|
+
attempt?: number | undefined;
|
|
928
|
+
child_created: boolean;
|
|
929
|
+
message: FusionFailureMessageMetadata;
|
|
930
|
+
};
|
|
931
|
+
progress: FusionRunProgress;
|
|
932
|
+
usage_so_far: FusionUsage;
|
|
933
|
+
attempts: FusionFailureList<FusionFailureAttemptMetadata>;
|
|
934
|
+
evidence_artifacts: FusionFailureList<FusionFailureEvidenceArtifact>;
|
|
935
|
+
remediation_ids: readonly (
|
|
936
|
+
| 'inspect_manifest_bound_evidence'
|
|
937
|
+
| 'inspect_terminal_error'
|
|
938
|
+
| 'split_or_reduce_work'
|
|
939
|
+
| 'retry_same_route_after_operator_review'
|
|
940
|
+
)[];
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
export interface FusionFailureViewFailure {
|
|
944
|
+
code?: FusionErrorCode | null | undefined;
|
|
945
|
+
stage?: FusionStage | undefined;
|
|
946
|
+
slot?: 1 | 2 | 3 | undefined;
|
|
947
|
+
attempt?: number | undefined;
|
|
948
|
+
child_created?: boolean | undefined;
|
|
949
|
+
message: FusionFailureMessageMetadata;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
export interface FusionFailureResultView {
|
|
953
|
+
schema_version: typeof FUSION_FAILURE_SUMMARY_SCHEMA_VERSION;
|
|
954
|
+
summary_status: 'verified' | 'legacy_manifest_only' | 'unavailable' | 'integrity_failed';
|
|
955
|
+
terminal_state: Exclude<FusionTerminalState, 'completed'>;
|
|
956
|
+
answer: { present: false; reason: 'run_did_not_commit' };
|
|
957
|
+
failure?: FusionFailureViewFailure | undefined;
|
|
958
|
+
progress?: FusionRunProgress | undefined;
|
|
959
|
+
usage_so_far?: FusionUsage | undefined;
|
|
960
|
+
attempts?: FusionFailureList<FusionFailureAttemptMetadata> | undefined;
|
|
961
|
+
evidence_artifacts?: FusionFailureList<FusionFailureEvidenceArtifact> | undefined;
|
|
962
|
+
remediation_ids?: FusionFailureSummaryV1['remediation_ids'] | undefined;
|
|
963
|
+
failure_summary_ref?: FusionArtifactRef | undefined;
|
|
964
|
+
summary_unavailable_reason?: 'no_durable_summary' | 'manifest_untrusted' | 'summary_integrity_failed' | undefined;
|
|
965
|
+
}
|
|
966
|
+
|
|
880
967
|
export interface FusionArtifactManifest {
|
|
881
968
|
schema_version: typeof FUSION_MANIFEST_SCHEMA_VERSION;
|
|
882
969
|
run_id: string;
|
package/src/core/registry.ts
CHANGED
|
@@ -58,6 +58,7 @@ import {
|
|
|
58
58
|
resolvePiLaunch,
|
|
59
59
|
type PiLaunchSpec,
|
|
60
60
|
} from './pi-launch.js';
|
|
61
|
+
import { resolveAnthropicAttributionExtensionPath } from './anthropic-attribution-path.js';
|
|
61
62
|
import {
|
|
62
63
|
runWindowsTaskkill,
|
|
63
64
|
type TaskkillOutcome,
|
|
@@ -1234,7 +1235,9 @@ export class BackgroundTaskRegistry {
|
|
|
1234
1235
|
if (this.shuttingDown)
|
|
1235
1236
|
throw new Error('Cannot start an attested Pi task while Pi is shutting down');
|
|
1236
1237
|
|
|
1237
|
-
const
|
|
1238
|
+
const attributionExtensionPath =
|
|
1239
|
+
request.provider === 'anthropic' ? resolveAnthropicAttributionExtensionPath() : undefined;
|
|
1240
|
+
const argv = buildAttestedPiArgv(request, attributionExtensionPath);
|
|
1238
1241
|
const attestedPiLaunch = resolvePiLaunch({ platform: this.platform });
|
|
1239
1242
|
assertWindowsCommandLineWithinLimit(
|
|
1240
1243
|
attestedPiLaunch,
|
|
@@ -7,6 +7,7 @@ import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
|
7
7
|
import type { Usage } from '@earendil-works/pi-ai';
|
|
8
8
|
import {
|
|
9
9
|
DELEGATE_RECEIPT_SCHEMA_VERSION,
|
|
10
|
+
DELEGATE_CAPABILITIES,
|
|
10
11
|
type DelegateRouteAttestation,
|
|
11
12
|
type DelegateSeedV1,
|
|
12
13
|
type DelegateSpillReceipt,
|
|
@@ -23,8 +24,10 @@ import {
|
|
|
23
24
|
/**
|
|
24
25
|
* Package-owned delegate child extension.
|
|
25
26
|
*
|
|
26
|
-
* This runs inside
|
|
27
|
-
*
|
|
27
|
+
* This runs inside every delegate child Pi process and is the package-owned
|
|
28
|
+
* child guard. Anthropic routes load attribution first, and ambient mode may
|
|
29
|
+
* also execute discovered extensions; this guard remains
|
|
30
|
+
* responsible for every isolation guarantee that cannot be enforced from the
|
|
28
31
|
* parent:
|
|
29
32
|
*
|
|
30
33
|
* - verifying the frozen seed bytes before the first model call;
|
|
@@ -289,6 +292,10 @@ export default function delegateChildExtension(pi: ExtensionAPI): void {
|
|
|
289
292
|
launchNonce: expectedNonce,
|
|
290
293
|
});
|
|
291
294
|
|
|
295
|
+
if (!DELEGATE_CAPABILITIES.includes(seed.capability)) {
|
|
296
|
+
throw new Error(`delegate child cannot enforce capability ${seed.capability}`);
|
|
297
|
+
}
|
|
298
|
+
|
|
292
299
|
const state: GuardState = {
|
|
293
300
|
seed,
|
|
294
301
|
artifactDirAbs,
|
|
@@ -11,11 +11,17 @@ import { Type, type Static } from 'typebox';
|
|
|
11
11
|
import type { BgTask, BgTaskSnapshot, StartDelegateTaskOptions } from './core/common.js';
|
|
12
12
|
import { truncateChars } from './core/common.js';
|
|
13
13
|
import { sha256Buffer } from './core/attested-pi-run.js';
|
|
14
|
-
import { readFusionCommittedResult } from './core/fusion/result-package.js';
|
|
15
|
-
import {
|
|
14
|
+
import { readFusionCommittedResult, readFusionFailureResult } from './core/fusion/result-package.js';
|
|
15
|
+
import {
|
|
16
|
+
cloneFusionUsage,
|
|
17
|
+
type FusionFailureResultView,
|
|
18
|
+
type FusionUsage,
|
|
19
|
+
type FusionWorkflowId,
|
|
20
|
+
} from './core/fusion/types.js';
|
|
16
21
|
import {
|
|
17
22
|
DELEGATE_AUTO_DELIVER_MODES,
|
|
18
23
|
DELEGATE_CAPABILITIES,
|
|
24
|
+
DELEGATE_EXTENSION_MODES,
|
|
19
25
|
DELEGATE_RESULT_TOOL_NAME,
|
|
20
26
|
DELEGATE_TOOL_NAME,
|
|
21
27
|
DelegateError,
|
|
@@ -23,6 +29,7 @@ import {
|
|
|
23
29
|
type DelegateCapability,
|
|
24
30
|
type DelegateDeliveryMode,
|
|
25
31
|
type DelegateBudgetRouteSource,
|
|
32
|
+
type DelegateExtensionMode,
|
|
26
33
|
type DelegateRoute,
|
|
27
34
|
} from './core/delegate/types.js';
|
|
28
35
|
import {
|
|
@@ -62,7 +69,7 @@ const HOOK_EVIDENCE_PATH = fileURLToPath(
|
|
|
62
69
|
new URL('./core/delegate/hook-contract-evidence.json', import.meta.url),
|
|
63
70
|
);
|
|
64
71
|
|
|
65
|
-
const DelegateParams = Type.Object(
|
|
72
|
+
export const DelegateParams = Type.Object(
|
|
66
73
|
{
|
|
67
74
|
name: Type.String({
|
|
68
75
|
description: 'Short human-readable task name shown in the bg footer dock. Use 2-6 words.',
|
|
@@ -88,6 +95,12 @@ const DelegateParams = Type.Object(
|
|
|
88
95
|
description: `Capability profile. Only "inspect" (read/search/list, no shell, no writes, no network, no recursion) is supported.`,
|
|
89
96
|
}),
|
|
90
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
|
+
),
|
|
91
104
|
maxTurns: Type.Optional(
|
|
92
105
|
Type.Number({
|
|
93
106
|
description: `Maximum agent turns. Default ${String(DELEGATE_DEFAULT_MAX_TURNS)}.`,
|
|
@@ -137,6 +150,20 @@ const ResultParams = Type.Object(
|
|
|
137
150
|
type DelegateParamsValue = Static<typeof DelegateParams>;
|
|
138
151
|
type ResultParamsValue = Static<typeof ResultParams>;
|
|
139
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
|
+
|
|
140
167
|
export interface DelegateLaunchDetails {
|
|
141
168
|
schema_version: 'pi-background-tasks.delegate-launch.v1';
|
|
142
169
|
task: BgTaskSnapshot;
|
|
@@ -146,6 +173,7 @@ export interface DelegateLaunchDetails {
|
|
|
146
173
|
seed_sha256: string;
|
|
147
174
|
seed_utf8_bytes: number;
|
|
148
175
|
budget: DelegateBudgetRouteSource;
|
|
176
|
+
extension_mode: DelegateExtensionMode;
|
|
149
177
|
auto_deliver: DelegateAutoDeliverMode;
|
|
150
178
|
notify_on_completion: boolean;
|
|
151
179
|
trigger_on_completion: boolean;
|
|
@@ -161,6 +189,16 @@ export interface FusionBackgroundResultDetails {
|
|
|
161
189
|
answer_bytes?: number | undefined;
|
|
162
190
|
answer_sha256?: string | undefined;
|
|
163
191
|
usage_delivered?: boolean | undefined;
|
|
192
|
+
answer?: { present: false; reason: 'run_did_not_commit' } | undefined;
|
|
193
|
+
summary_status?: FusionFailureResultView['summary_status'] | undefined;
|
|
194
|
+
failure_summary_ref?: FusionFailureResultView['failure_summary_ref'] | undefined;
|
|
195
|
+
failure?: FusionFailureResultView['failure'] | undefined;
|
|
196
|
+
progress?: FusionFailureResultView['progress'] | undefined;
|
|
197
|
+
usage_so_far?: FusionFailureResultView['usage_so_far'] | undefined;
|
|
198
|
+
attempts?: FusionFailureResultView['attempts'] | undefined;
|
|
199
|
+
evidence_artifacts?: FusionFailureResultView['evidence_artifacts'] | undefined;
|
|
200
|
+
remediation_ids?: FusionFailureResultView['remediation_ids'] | undefined;
|
|
201
|
+
summary_unavailable_reason?: FusionFailureResultView['summary_unavailable_reason'] | undefined;
|
|
164
202
|
}
|
|
165
203
|
|
|
166
204
|
export type BackgroundResultDetails = DelegateResultDetails | FusionBackgroundResultDetails;
|
|
@@ -172,6 +210,7 @@ export interface DelegateResultDetails {
|
|
|
172
210
|
delivery: DelegateDeliveryMode | 'none';
|
|
173
211
|
route?: { provider: string; model: string } | undefined;
|
|
174
212
|
budget?: DelegateBudgetRouteSource | undefined;
|
|
213
|
+
extension_mode?: DelegateExtensionMode | undefined;
|
|
175
214
|
answer_bytes?: number | undefined;
|
|
176
215
|
answer_sha256?: string | undefined;
|
|
177
216
|
turns?: number | undefined;
|
|
@@ -198,6 +237,15 @@ function requireCapability(value: unknown): DelegateCapability {
|
|
|
198
237
|
);
|
|
199
238
|
}
|
|
200
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
|
+
|
|
201
249
|
function requireAutoDeliver(value: unknown): DelegateAutoDeliverMode {
|
|
202
250
|
if (value === undefined) return 'never';
|
|
203
251
|
if (value === 'never' || value === 'when_small' || value === 'always') return value;
|
|
@@ -293,13 +341,15 @@ export function registerDelegateExtension(
|
|
|
293
341
|
name: DELEGATE_TOOL_NAME,
|
|
294
342
|
label: 'Background Delegate',
|
|
295
343
|
description:
|
|
296
|
-
'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.',
|
|
297
345
|
promptSnippet:
|
|
298
346
|
'Delegate an investigation to a background agent that already has this conversation as context',
|
|
299
347
|
promptGuidelines: [
|
|
300
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.',
|
|
301
349
|
'The prompt is authoritative. State exactly what you want investigated and what the answer should contain.',
|
|
302
|
-
'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.',
|
|
303
353
|
'Facts that exist only inside omitted tool output are not available to the delegate. Restate such findings in the prompt.',
|
|
304
354
|
'bg_delegate returns immediately. Do not poll; retrieve the answer with bg_result after the terminal notification arrives.',
|
|
305
355
|
],
|
|
@@ -310,6 +360,13 @@ export function registerDelegateExtension(
|
|
|
310
360
|
code: 'invalid_arguments',
|
|
311
361
|
childCreated: false,
|
|
312
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
|
+
}
|
|
313
370
|
const name = args['name'];
|
|
314
371
|
const prompt = args['prompt'];
|
|
315
372
|
if (typeof name !== 'string' || name.trim().length === 0)
|
|
@@ -326,6 +383,7 @@ export function registerDelegateExtension(
|
|
|
326
383
|
const route = requireRoute(args['route']);
|
|
327
384
|
if (route !== undefined) prepared.route = route;
|
|
328
385
|
prepared.capability = requireCapability(args['capability']);
|
|
386
|
+
prepared.extensionMode = requireExtensionMode(args['extensionMode']);
|
|
329
387
|
prepared.autoDeliver = requireAutoDeliver(args['autoDeliver']);
|
|
330
388
|
const maxTurns = optionalPositiveInteger(args['maxTurns'], 'maxTurns');
|
|
331
389
|
if (maxTurns !== undefined) prepared.maxTurns = maxTurns;
|
|
@@ -341,6 +399,7 @@ export function registerDelegateExtension(
|
|
|
341
399
|
},
|
|
342
400
|
async execute(toolCallId, params, _signal, _onUpdate, ctx) {
|
|
343
401
|
const capability = requireCapability(params.capability);
|
|
402
|
+
const extensionMode = requireExtensionMode(params.extensionMode);
|
|
344
403
|
const autoDeliver = requireAutoDeliver(params.autoDeliver);
|
|
345
404
|
const hookEvidence = await loadEvidence();
|
|
346
405
|
const route = resolveDelegateRoute({
|
|
@@ -370,6 +429,7 @@ export function registerDelegateExtension(
|
|
|
370
429
|
toolCallId,
|
|
371
430
|
prompt: params.prompt,
|
|
372
431
|
capability,
|
|
432
|
+
extensionMode,
|
|
373
433
|
route,
|
|
374
434
|
limitOverrides: {
|
|
375
435
|
maxTurns: params.maxTurns,
|
|
@@ -408,6 +468,7 @@ export function registerDelegateExtension(
|
|
|
408
468
|
seed_sha256: prepared.facts.seedSha256,
|
|
409
469
|
seed_utf8_bytes: prepared.preflight.plan.seed_utf8_bytes,
|
|
410
470
|
budget: prepared.facts.budget,
|
|
471
|
+
extension_mode: extensionMode,
|
|
411
472
|
auto_deliver: autoDeliver,
|
|
412
473
|
notify_on_completion: launchOptions.notifyOnCompletion,
|
|
413
474
|
trigger_on_completion: launchOptions.triggerOnCompletion,
|
|
@@ -422,6 +483,7 @@ export function registerDelegateExtension(
|
|
|
422
483
|
`Seed: ${String(prepared.preflight.plan.seed_utf8_bytes)} bytes, sha256 ${prepared.facts.seedSha256}`,
|
|
423
484
|
`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}`}`,
|
|
424
485
|
`Capability: ${capability} (read/search/list only)`,
|
|
486
|
+
`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)'}`,
|
|
425
487
|
`Limits: ${String(prepared.preflight.limits.max_turns)} turns, ${String(prepared.preflight.limits.max_tool_calls)} tool calls, ${String(prepared.preflight.limits.timeout_seconds)}s`,
|
|
426
488
|
`Auto-deliver: ${autoDeliver}`,
|
|
427
489
|
launchOptions.notifyOnCompletion
|
|
@@ -443,7 +505,7 @@ export function registerDelegateExtension(
|
|
|
443
505
|
renderResult(result, _options, theme) {
|
|
444
506
|
const details = result.details;
|
|
445
507
|
return new Text(
|
|
446
|
-
`${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}`)}`,
|
|
508
|
+
`${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}`)}`,
|
|
447
509
|
0,
|
|
448
510
|
0,
|
|
449
511
|
);
|
|
@@ -508,9 +570,48 @@ export function registerDelegateExtension(
|
|
|
508
570
|
};
|
|
509
571
|
}
|
|
510
572
|
if (task.status !== 'completed' || fusion.outcome?.status !== 'committed') {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
573
|
+
const terminal = await readFusionFailureResult({
|
|
574
|
+
artifactDirAbs: fusion.artifactDirAbs,
|
|
575
|
+
artifactDir: fusion.artifactDir,
|
|
576
|
+
runId: fusion.runId,
|
|
577
|
+
workflow: fusion.workflow,
|
|
578
|
+
});
|
|
579
|
+
const state =
|
|
580
|
+
fusion.outcome?.status === 'cancelled' || task.status === 'killed'
|
|
581
|
+
? 'cancelled'
|
|
582
|
+
: 'failed';
|
|
583
|
+
const details: FusionBackgroundResultDetails = {
|
|
584
|
+
schema_version: 'pi-background-tasks.fusion-result-view.v1',
|
|
585
|
+
task_id: task.id,
|
|
586
|
+
state,
|
|
587
|
+
delivery: 'none',
|
|
588
|
+
workflow: fusion.workflow,
|
|
589
|
+
artifact_dir: fusion.artifactDir,
|
|
590
|
+
answer: terminal.answer,
|
|
591
|
+
summary_status: terminal.summary_status,
|
|
592
|
+
...(terminal.failure_summary_ref === undefined
|
|
593
|
+
? {}
|
|
594
|
+
: { failure_summary_ref: terminal.failure_summary_ref }),
|
|
595
|
+
...(terminal.failure === undefined ? {} : { failure: terminal.failure }),
|
|
596
|
+
...(terminal.progress === undefined ? {} : { progress: terminal.progress }),
|
|
597
|
+
...(terminal.usage_so_far === undefined ? {} : { usage_so_far: terminal.usage_so_far }),
|
|
598
|
+
...(terminal.attempts === undefined ? {} : { attempts: terminal.attempts }),
|
|
599
|
+
...(terminal.evidence_artifacts === undefined
|
|
600
|
+
? {}
|
|
601
|
+
: { evidence_artifacts: terminal.evidence_artifacts }),
|
|
602
|
+
...(terminal.remediation_ids === undefined
|
|
603
|
+
? {}
|
|
604
|
+
: { remediation_ids: terminal.remediation_ids }),
|
|
605
|
+
...(terminal.summary_unavailable_reason === undefined
|
|
606
|
+
? {}
|
|
607
|
+
: { summary_unavailable_reason: terminal.summary_unavailable_reason }),
|
|
608
|
+
};
|
|
609
|
+
return {
|
|
610
|
+
content: textContent(
|
|
611
|
+
`Fusion ${task.id} ${state}; no answer was committed. Terminal evidence status: ${terminal.summary_status}. Delivery is none; use only the manifest-bound artifact references in details.`,
|
|
612
|
+
),
|
|
613
|
+
details,
|
|
614
|
+
};
|
|
514
615
|
}
|
|
515
616
|
const verified = await readFusionCommittedResult({
|
|
516
617
|
artifactDirAbs: fusion.artifactDirAbs,
|
|
@@ -579,6 +680,7 @@ export function registerDelegateExtension(
|
|
|
579
680
|
delivery: 'none',
|
|
580
681
|
artifact_dir: facts.artifactDir,
|
|
581
682
|
budget: facts.budget,
|
|
683
|
+
extension_mode: facts.extensionMode,
|
|
582
684
|
};
|
|
583
685
|
return {
|
|
584
686
|
content: textContent(
|
|
@@ -632,6 +734,7 @@ export function registerDelegateExtension(
|
|
|
632
734
|
delivery: decision.mode,
|
|
633
735
|
route: verified.package.route,
|
|
634
736
|
budget: facts.budget,
|
|
737
|
+
extension_mode: facts.extensionMode,
|
|
635
738
|
answer_bytes: verified.package.answer.byte_length,
|
|
636
739
|
answer_sha256: verified.package.answer.sha256,
|
|
637
740
|
turns: verified.package.turns,
|
|
@@ -673,6 +776,13 @@ export function registerDelegateExtension(
|
|
|
673
776
|
0,
|
|
674
777
|
0,
|
|
675
778
|
);
|
|
779
|
+
if (fusion && (details.state === 'failed' || details.state === 'cancelled')) {
|
|
780
|
+
return new Text(
|
|
781
|
+
theme.fg('warning', `${details.state} fusion; no committed answer · ${details.summary_status ?? 'unavailable'}`),
|
|
782
|
+
0,
|
|
783
|
+
0,
|
|
784
|
+
);
|
|
785
|
+
}
|
|
676
786
|
return new Text(
|
|
677
787
|
`${theme.fg('success', fusion ? '✓ fusion answer' : '✓ delegate answer')} ${theme.fg('dim', `${String(details.answer_bytes ?? 0)}B · ${details.delivery}`)}`,
|
|
678
788
|
0,
|