pi-background-tasks 2.1.2 → 2.1.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.
@@ -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.
@@ -640,7 +642,8 @@ export type FusionErrorCode =
640
642
  | 'child_stdin_failed'
641
643
  | 'child_event_invalid'
642
644
  | 'child_exit_failed'
643
- | 'child_runtime_budget_exceeded'
645
+ | 'child_runtime_limit_exceeded'
646
+ | 'child_runtime_payload_invalid'
644
647
  | 'child_cache_policy_invalid'
645
648
  | 'child_timeout'
646
649
  | 'child_output_cap'
@@ -876,6 +879,91 @@ export interface FusionArtifactRef {
876
879
  sha256: string;
877
880
  }
878
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
+
879
967
  export interface FusionArtifactManifest {
880
968
  schema_version: typeof FUSION_MANIFEST_SCHEMA_VERSION;
881
969
  run_id: string;
@@ -11,8 +11,13 @@ 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 { cloneFusionUsage, type FusionUsage, type FusionWorkflowId } from './core/fusion/types.js';
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,
@@ -161,6 +166,16 @@ export interface FusionBackgroundResultDetails {
161
166
  answer_bytes?: number | undefined;
162
167
  answer_sha256?: string | undefined;
163
168
  usage_delivered?: boolean | undefined;
169
+ answer?: { present: false; reason: 'run_did_not_commit' } | undefined;
170
+ summary_status?: FusionFailureResultView['summary_status'] | undefined;
171
+ failure_summary_ref?: FusionFailureResultView['failure_summary_ref'] | undefined;
172
+ failure?: FusionFailureResultView['failure'] | undefined;
173
+ progress?: FusionFailureResultView['progress'] | undefined;
174
+ usage_so_far?: FusionFailureResultView['usage_so_far'] | undefined;
175
+ attempts?: FusionFailureResultView['attempts'] | undefined;
176
+ evidence_artifacts?: FusionFailureResultView['evidence_artifacts'] | undefined;
177
+ remediation_ids?: FusionFailureResultView['remediation_ids'] | undefined;
178
+ summary_unavailable_reason?: FusionFailureResultView['summary_unavailable_reason'] | undefined;
164
179
  }
165
180
 
166
181
  export type BackgroundResultDetails = DelegateResultDetails | FusionBackgroundResultDetails;
@@ -508,9 +523,48 @@ export function registerDelegateExtension(
508
523
  };
509
524
  }
510
525
  if (task.status !== 'completed' || fusion.outcome?.status !== 'committed') {
511
- throw new Error(
512
- `Fusion ${task.id} did not commit a result (${task.status}): ${fusion.outcome?.error ?? task.error ?? 'no terminal detail'}`,
513
- );
526
+ const terminal = await readFusionFailureResult({
527
+ artifactDirAbs: fusion.artifactDirAbs,
528
+ artifactDir: fusion.artifactDir,
529
+ runId: fusion.runId,
530
+ workflow: fusion.workflow,
531
+ });
532
+ const state =
533
+ fusion.outcome?.status === 'cancelled' || task.status === 'killed'
534
+ ? 'cancelled'
535
+ : 'failed';
536
+ const details: FusionBackgroundResultDetails = {
537
+ schema_version: 'pi-background-tasks.fusion-result-view.v1',
538
+ task_id: task.id,
539
+ state,
540
+ delivery: 'none',
541
+ workflow: fusion.workflow,
542
+ artifact_dir: fusion.artifactDir,
543
+ answer: terminal.answer,
544
+ summary_status: terminal.summary_status,
545
+ ...(terminal.failure_summary_ref === undefined
546
+ ? {}
547
+ : { failure_summary_ref: terminal.failure_summary_ref }),
548
+ ...(terminal.failure === undefined ? {} : { failure: terminal.failure }),
549
+ ...(terminal.progress === undefined ? {} : { progress: terminal.progress }),
550
+ ...(terminal.usage_so_far === undefined ? {} : { usage_so_far: terminal.usage_so_far }),
551
+ ...(terminal.attempts === undefined ? {} : { attempts: terminal.attempts }),
552
+ ...(terminal.evidence_artifacts === undefined
553
+ ? {}
554
+ : { evidence_artifacts: terminal.evidence_artifacts }),
555
+ ...(terminal.remediation_ids === undefined
556
+ ? {}
557
+ : { remediation_ids: terminal.remediation_ids }),
558
+ ...(terminal.summary_unavailable_reason === undefined
559
+ ? {}
560
+ : { summary_unavailable_reason: terminal.summary_unavailable_reason }),
561
+ };
562
+ return {
563
+ content: textContent(
564
+ `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.`,
565
+ ),
566
+ details,
567
+ };
514
568
  }
515
569
  const verified = await readFusionCommittedResult({
516
570
  artifactDirAbs: fusion.artifactDirAbs,
@@ -673,6 +727,13 @@ export function registerDelegateExtension(
673
727
  0,
674
728
  0,
675
729
  );
730
+ if (fusion && (details.state === 'failed' || details.state === 'cancelled')) {
731
+ return new Text(
732
+ theme.fg('warning', `${details.state} fusion; no committed answer · ${details.summary_status ?? 'unavailable'}`),
733
+ 0,
734
+ 0,
735
+ );
736
+ }
676
737
  return new Text(
677
738
  `${theme.fg('success', fusion ? '✓ fusion answer' : '✓ delegate answer')} ${theme.fg('dim', `${String(details.answer_bytes ?? 0)}B · ${details.delivery}`)}`,
678
739
  0,
@@ -12,11 +12,6 @@ import { dirname, isAbsolute } from 'node:path';
12
12
  import { parseJsonText } from './core/common.js';
13
13
  import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
14
14
  import { Type, type Static } from 'typebox';
15
- import {
16
- estimateInputTokens,
17
- knownJsonSegment,
18
- resolveTokenBudgetFamily,
19
- } from './core/context/token-budget.js';
20
15
  import {
21
16
  FUSION_TOOL_CALL_LOG_SCHEMA_VERSION,
22
17
  FUSION_WEB_FETCH_TOOL_NAME,
@@ -42,9 +37,7 @@ import {
42
37
  FUSION_CHILD_MAX_PROVIDER_REQUESTS,
43
38
  FUSION_CHILD_MAX_TOOL_CALLS,
44
39
  FUSION_CHILD_MAX_TOTAL_TOOL_RESULT_BYTES,
45
- FUSION_CHILD_MIN_OUTPUT_RESERVE_TOKENS,
46
40
  FUSION_CHILD_RESULT_PREFIX,
47
- FUSION_CHILD_SAFETY_RESERVE_TOKENS,
48
41
  FUSION_CHILD_SETTLEMENT_PREFIX,
49
42
  FUSION_RESEARCH_ENABLED_ENV,
50
43
  FUSION_RUNTIME_GUARD_PREFIX,
@@ -88,10 +81,8 @@ export {
88
81
  FUSION_CHILD_MAX_PROVIDER_REQUESTS,
89
82
  FUSION_CHILD_MAX_TOOL_CALLS,
90
83
  FUSION_CHILD_MAX_TOTAL_TOOL_RESULT_BYTES,
91
- FUSION_CHILD_MIN_OUTPUT_RESERVE_TOKENS,
92
84
  FUSION_CHILD_RESULT_PREFIX,
93
85
  FUSION_CHILD_RESULT_SCHEMA_VERSION,
94
- FUSION_CHILD_SAFETY_RESERVE_TOKENS,
95
86
  FUSION_CHILD_SETTLEMENT_PREFIX,
96
87
  FUSION_CHILD_SETTLEMENT_SCHEMA_VERSION,
97
88
  FUSION_RESEARCH_ENABLED_ENV,
@@ -370,8 +361,6 @@ export interface FusionRuntimeRequestEvaluationInput {
370
361
  payload: unknown;
371
362
  provider: string | undefined;
372
363
  model: string | undefined;
373
- contextWindowTokens: number | undefined;
374
- maxOutputTokens: number | undefined;
375
364
  requestOrdinal: number;
376
365
  toolCallCount: number;
377
366
  }
@@ -384,23 +373,6 @@ function invalidFusionRuntimeRequest(
384
373
  'provider_payload_invalid' | 'claude_cache_policy'
385
374
  > = 'provider_payload_invalid',
386
375
  ): FusionRuntimeGuardRecord {
387
- const contextWindowTokens =
388
- input.contextWindowTokens !== undefined &&
389
- Number.isSafeInteger(input.contextWindowTokens) &&
390
- input.contextWindowTokens > 0
391
- ? input.contextWindowTokens
392
- : 0;
393
- const modelOutputTokens =
394
- input.maxOutputTokens !== undefined &&
395
- Number.isSafeInteger(input.maxOutputTokens) &&
396
- input.maxOutputTokens > 0
397
- ? input.maxOutputTokens
398
- : 0;
399
- const reservedOutputTokens = Math.max(FUSION_CHILD_MIN_OUTPUT_RESERVE_TOKENS, modelOutputTokens);
400
- const allowedInputTokens = Math.max(
401
- 0,
402
- contextWindowTokens - reservedOutputTokens - FUSION_CHILD_SAFETY_RESERVE_TOKENS,
403
- );
404
376
  const emptyPayload = Buffer.alloc(0);
405
377
  return {
406
378
  schema_version: FUSION_RUNTIME_GUARD_SCHEMA_VERSION,
@@ -411,11 +383,6 @@ function invalidFusionRuntimeRequest(
411
383
  tool_call_count: input.toolCallCount,
412
384
  payload_bytes: 0,
413
385
  payload_sha256: sha256(emptyPayload),
414
- estimated_input_tokens: 0,
415
- context_window_tokens: contextWindowTokens,
416
- reserved_output_tokens: reservedOutputTokens,
417
- safety_reserve_tokens: FUSION_CHILD_SAFETY_RESERVE_TOKENS,
418
- allowed_input_tokens: allowedInputTokens,
419
386
  message: `fusion child could not validate provider request ${String(input.requestOrdinal)}: ${detail}`,
420
387
  };
421
388
  }
@@ -455,84 +422,31 @@ export function prepareFusionRuntimeRequest(
455
422
  export function evaluateFusionRuntimeRequest(
456
423
  input: FusionRuntimeRequestEvaluationInput,
457
424
  ): FusionRuntimeGuardRecord | undefined {
458
- let code: FusionRuntimeGuardCode | undefined;
459
- let message = '';
460
- let payloadBytes = Buffer.alloc(0);
461
- let estimatedInputTokens = 0;
462
- let allowedInputTokens = 0;
463
- const provider = input.provider ?? 'unknown';
464
- const model = input.model ?? 'unknown';
465
- const contextWindowTokens = input.contextWindowTokens ?? 0;
466
- const reservedOutputTokens = Math.max(
467
- FUSION_CHILD_MIN_OUTPUT_RESERVE_TOKENS,
468
- input.maxOutputTokens ?? 0,
469
- );
470
- try {
471
- if (input.provider === undefined || input.model === undefined) {
472
- throw new Error('active model is unavailable');
473
- }
474
- if (!Number.isSafeInteger(contextWindowTokens) || contextWindowTokens <= 0) {
475
- throw new Error('active model context window is unavailable');
476
- }
477
- if (
478
- input.maxOutputTokens === undefined ||
479
- !Number.isSafeInteger(input.maxOutputTokens) ||
480
- input.maxOutputTokens <= 0
481
- ) {
482
- throw new Error('active model maximum output tokens are unavailable');
483
- }
484
- allowedInputTokens =
485
- contextWindowTokens - reservedOutputTokens - FUSION_CHILD_SAFETY_RESERVE_TOKENS;
486
- if (allowedInputTokens <= 0) {
487
- throw new Error('active model has no safe provider input capacity');
488
- }
489
- const payloadText = JSON.stringify(input.payload);
490
- if (payloadText === undefined) throw new Error('provider payload serialized to undefined');
491
- payloadBytes = Buffer.from(payloadText, 'utf8');
492
- const family = resolveTokenBudgetFamily({ provider, model });
493
- estimatedInputTokens = estimateInputTokens({
494
- family: family.family,
495
- calibrationBacked: family.backed,
496
- familyResolution: family.resolution,
497
- allowedInputTokens,
498
- scope: 'fusion',
499
- segments: [knownJsonSegment(payloadText)],
500
- }).tokens;
501
- if (input.requestOrdinal > FUSION_CHILD_MAX_PROVIDER_REQUESTS) {
502
- code = 'provider_request_limit';
503
- message = `fusion child reached provider request ${String(input.requestOrdinal)}, exceeding the ${String(FUSION_CHILD_MAX_PROVIDER_REQUESTS)}-request execution limit`;
504
- } else if (estimatedInputTokens > allowedInputTokens) {
505
- code = 'provider_request_budget';
506
- message = `fusion child blocked provider request ${String(input.requestOrdinal)} before transport: exact final payload is ${String(payloadBytes.length)} UTF-8 bytes (estimated <= ${String(estimatedInputTokens)} input tokens), exceeding ${String(allowedInputTokens)} allowed input tokens after reserving ${String(reservedOutputTokens)} model output + ${String(FUSION_CHILD_SAFETY_RESERVE_TOKENS)} safety from the ${String(contextWindowTokens)}-token context window`;
507
- }
508
- } catch (error) {
509
- code = 'provider_payload_invalid';
510
- message = `fusion child could not validate provider request ${String(input.requestOrdinal)}: ${error instanceof Error ? error.message : String(error)}`;
425
+ if (input.provider === undefined || input.model === undefined) {
426
+ return invalidFusionRuntimeRequest(input, 'active model is unavailable');
511
427
  }
512
- if (code === undefined) return undefined;
428
+ if (input.requestOrdinal <= FUSION_CHILD_MAX_PROVIDER_REQUESTS) return undefined;
429
+ const serialized: unknown = JSON.stringify(input.payload);
430
+ if (typeof serialized !== 'string') {
431
+ return invalidFusionRuntimeRequest(input, 'provider payload serialized to a non-string value');
432
+ }
433
+ const payloadBytes = Buffer.from(serialized, 'utf8');
513
434
  return {
514
435
  schema_version: FUSION_RUNTIME_GUARD_SCHEMA_VERSION,
515
- code,
516
- provider,
517
- model,
436
+ code: 'provider_request_limit',
437
+ provider: input.provider,
438
+ model: input.model,
518
439
  request_ordinal: input.requestOrdinal,
519
440
  tool_call_count: input.toolCallCount,
520
441
  payload_bytes: payloadBytes.length,
521
442
  payload_sha256: sha256(payloadBytes),
522
- estimated_input_tokens: estimatedInputTokens,
523
- context_window_tokens: contextWindowTokens,
524
- reserved_output_tokens: reservedOutputTokens,
525
- safety_reserve_tokens: FUSION_CHILD_SAFETY_RESERVE_TOKENS,
526
- allowed_input_tokens: allowedInputTokens,
527
- message,
443
+ message: `fusion child reached provider request ${String(input.requestOrdinal)}, exceeding the ${String(FUSION_CHILD_MAX_PROVIDER_REQUESTS)}-request execution limit`,
528
444
  };
529
445
  }
530
446
 
531
447
  export interface FusionRuntimeToolLimitEvaluationInput {
532
448
  provider: string | undefined;
533
449
  model: string | undefined;
534
- contextWindowTokens: number | undefined;
535
- maxOutputTokens: number | undefined;
536
450
  requestOrdinal: number;
537
451
  toolCallCount: number;
538
452
  }
@@ -541,23 +455,6 @@ export function evaluateFusionRuntimeToolLimit(
541
455
  input: FusionRuntimeToolLimitEvaluationInput,
542
456
  ): FusionRuntimeGuardRecord | undefined {
543
457
  if (input.toolCallCount <= FUSION_CHILD_MAX_TOOL_CALLS) return undefined;
544
- const contextWindowTokens =
545
- input.contextWindowTokens !== undefined &&
546
- Number.isSafeInteger(input.contextWindowTokens) &&
547
- input.contextWindowTokens > 0
548
- ? input.contextWindowTokens
549
- : 0;
550
- const modelOutputTokens =
551
- input.maxOutputTokens !== undefined &&
552
- Number.isSafeInteger(input.maxOutputTokens) &&
553
- input.maxOutputTokens > 0
554
- ? input.maxOutputTokens
555
- : 0;
556
- const reservedOutputTokens = Math.max(FUSION_CHILD_MIN_OUTPUT_RESERVE_TOKENS, modelOutputTokens);
557
- const allowedInputTokens = Math.max(
558
- 0,
559
- contextWindowTokens - reservedOutputTokens - FUSION_CHILD_SAFETY_RESERVE_TOKENS,
560
- );
561
458
  const emptyPayload = Buffer.alloc(0);
562
459
  return {
563
460
  schema_version: FUSION_RUNTIME_GUARD_SCHEMA_VERSION,
@@ -568,11 +465,6 @@ export function evaluateFusionRuntimeToolLimit(
568
465
  tool_call_count: input.toolCallCount,
569
466
  payload_bytes: 0,
570
467
  payload_sha256: sha256(emptyPayload),
571
- estimated_input_tokens: 0,
572
- context_window_tokens: contextWindowTokens,
573
- reserved_output_tokens: reservedOutputTokens,
574
- safety_reserve_tokens: FUSION_CHILD_SAFETY_RESERVE_TOKENS,
575
- allowed_input_tokens: allowedInputTokens,
576
468
  message: `fusion child reached tool call ${String(input.toolCallCount)}, exceeding the ${String(FUSION_CHILD_MAX_TOOL_CALLS)}-call execution limit`,
577
469
  };
578
470
  }
@@ -773,8 +665,6 @@ export default function fusionChildExtension(pi: ExtensionAPI): void {
773
665
  payload: event.payload,
774
666
  provider: model?.provider,
775
667
  model: model?.id,
776
- contextWindowTokens: model?.contextWindow,
777
- maxOutputTokens: model?.maxTokens,
778
668
  requestOrdinal: providerRequestCount,
779
669
  toolCallCount,
780
670
  },
@@ -793,8 +683,6 @@ export default function fusionChildExtension(pi: ExtensionAPI): void {
793
683
  payload: cacheNormalizedPayload,
794
684
  provider: model?.provider,
795
685
  model: model?.id,
796
- contextWindowTokens: model?.contextWindow,
797
- maxOutputTokens: model?.maxTokens,
798
686
  requestOrdinal: providerRequestCount,
799
687
  toolCallCount,
800
688
  });
@@ -888,8 +776,6 @@ export default function fusionChildExtension(pi: ExtensionAPI): void {
888
776
  const guard = evaluateFusionRuntimeToolLimit({
889
777
  provider: model?.provider,
890
778
  model: model?.id,
891
- contextWindowTokens: model?.contextWindow,
892
- maxOutputTokens: model?.maxTokens,
893
779
  requestOrdinal: providerRequestCount,
894
780
  toolCallCount,
895
781
  });