create-principles-disciple 1.79.0 → 1.81.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/console/package.json +1 -0
- package/core/dist/runtime-v2/__tests__/admission-gate.test.js +6 -6
- package/core/dist/runtime-v2/__tests__/admission-gate.test.js.map +1 -1
- package/core/dist/runtime-v2/__tests__/architecture-regression.test.js +39 -0
- package/core/dist/runtime-v2/__tests__/architecture-regression.test.js.map +1 -1
- package/core/dist/runtime-v2/__tests__/artificer-runner-vslice.test.js +52 -0
- package/core/dist/runtime-v2/__tests__/artificer-runner-vslice.test.js.map +1 -1
- package/core/dist/runtime-v2/__tests__/evaluator-runner-vslice.test.js +245 -1
- package/core/dist/runtime-v2/__tests__/evaluator-runner-vslice.test.js.map +1 -1
- package/core/dist/runtime-v2/__tests__/feedback/redact-sensitive.test.js +131 -1
- package/core/dist/runtime-v2/__tests__/feedback/redact-sensitive.test.js.map +1 -1
- package/core/dist/runtime-v2/__tests__/fixtures/llm-e2e-config.d.ts.map +1 -1
- package/core/dist/runtime-v2/__tests__/fixtures/llm-e2e-config.js +4 -2
- package/core/dist/runtime-v2/__tests__/fixtures/llm-e2e-config.js.map +1 -1
- package/core/dist/runtime-v2/__tests__/scribe-runner-vslice.test.js +425 -2
- package/core/dist/runtime-v2/__tests__/scribe-runner-vslice.test.js.map +1 -1
- package/core/dist/runtime-v2/__tests__/telemetry-event.test.d.ts +2 -0
- package/core/dist/runtime-v2/__tests__/telemetry-event.test.d.ts.map +1 -0
- package/core/dist/runtime-v2/__tests__/telemetry-event.test.js +69 -0
- package/core/dist/runtime-v2/__tests__/telemetry-event.test.js.map +1 -0
- package/core/dist/runtime-v2/admission-gate.d.ts.map +1 -1
- package/core/dist/runtime-v2/admission-gate.js +0 -8
- package/core/dist/runtime-v2/admission-gate.js.map +1 -1
- package/core/dist/runtime-v2/config/__tests__/pd-config-contract.test.d.ts +15 -0
- package/core/dist/runtime-v2/config/__tests__/pd-config-contract.test.d.ts.map +1 -0
- package/core/dist/runtime-v2/config/__tests__/pd-config-contract.test.js +685 -0
- package/core/dist/runtime-v2/config/__tests__/pd-config-contract.test.js.map +1 -0
- package/core/dist/runtime-v2/config/index.d.ts +14 -0
- package/core/dist/runtime-v2/config/index.d.ts.map +1 -0
- package/core/dist/runtime-v2/config/index.js +16 -0
- package/core/dist/runtime-v2/config/index.js.map +1 -0
- package/core/dist/runtime-v2/config/pd-config-defaults.d.ts +14 -0
- package/core/dist/runtime-v2/config/pd-config-defaults.d.ts.map +1 -0
- package/core/dist/runtime-v2/config/pd-config-defaults.js +74 -0
- package/core/dist/runtime-v2/config/pd-config-defaults.js.map +1 -0
- package/core/dist/runtime-v2/config/pd-config-effective.d.ts +14 -0
- package/core/dist/runtime-v2/config/pd-config-effective.d.ts.map +1 -0
- package/core/dist/runtime-v2/config/pd-config-effective.js +118 -0
- package/core/dist/runtime-v2/config/pd-config-effective.js.map +1 -0
- package/core/dist/runtime-v2/config/pd-config-feature-flags.d.ts +35 -0
- package/core/dist/runtime-v2/config/pd-config-feature-flags.d.ts.map +1 -0
- package/core/dist/runtime-v2/config/pd-config-feature-flags.js +99 -0
- package/core/dist/runtime-v2/config/pd-config-feature-flags.js.map +1 -0
- package/core/dist/runtime-v2/config/pd-config-redaction.d.ts +22 -0
- package/core/dist/runtime-v2/config/pd-config-redaction.d.ts.map +1 -0
- package/core/dist/runtime-v2/config/pd-config-redaction.js +179 -0
- package/core/dist/runtime-v2/config/pd-config-redaction.js.map +1 -0
- package/core/dist/runtime-v2/config/pd-config-types.d.ts +124 -0
- package/core/dist/runtime-v2/config/pd-config-types.d.ts.map +1 -0
- package/core/dist/runtime-v2/config/pd-config-types.js +35 -0
- package/core/dist/runtime-v2/config/pd-config-types.js.map +1 -0
- package/core/dist/runtime-v2/config/pd-config-validate.d.ts +16 -0
- package/core/dist/runtime-v2/config/pd-config-validate.d.ts.map +1 -0
- package/core/dist/runtime-v2/config/pd-config-validate.js +443 -0
- package/core/dist/runtime-v2/config/pd-config-validate.js.map +1 -0
- package/core/dist/runtime-v2/feature-flags/plugin-surface-registry.js +10 -10
- package/core/dist/runtime-v2/feature-flags/plugin-surface-registry.js.map +1 -1
- package/core/dist/runtime-v2/feedback/index.d.ts +1 -1
- package/core/dist/runtime-v2/feedback/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/feedback/index.js +1 -1
- package/core/dist/runtime-v2/feedback/index.js.map +1 -1
- package/core/dist/runtime-v2/feedback/redact-sensitive.d.ts +2 -0
- package/core/dist/runtime-v2/feedback/redact-sensitive.d.ts.map +1 -1
- package/core/dist/runtime-v2/feedback/redact-sensitive.js +32 -3
- package/core/dist/runtime-v2/feedback/redact-sensitive.js.map +1 -1
- package/core/dist/runtime-v2/index.d.ts +5 -1
- package/core/dist/runtime-v2/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/index.js +9 -1
- package/core/dist/runtime-v2/index.js.map +1 -1
- package/core/dist/runtime-v2/internalization/__tests__/peer-runner-lineage-injection.test.d.ts +2 -0
- package/core/dist/runtime-v2/internalization/__tests__/peer-runner-lineage-injection.test.d.ts.map +1 -0
- package/core/dist/runtime-v2/internalization/__tests__/peer-runner-lineage-injection.test.js +95 -0
- package/core/dist/runtime-v2/internalization/__tests__/peer-runner-lineage-injection.test.js.map +1 -0
- package/core/dist/runtime-v2/internalization/__tests__/philosopher-runner-trust-boundary.test.d.ts +2 -0
- package/core/dist/runtime-v2/internalization/__tests__/philosopher-runner-trust-boundary.test.d.ts.map +1 -0
- package/core/dist/runtime-v2/internalization/__tests__/philosopher-runner-trust-boundary.test.js +398 -0
- package/core/dist/runtime-v2/internalization/__tests__/philosopher-runner-trust-boundary.test.js.map +1 -0
- package/core/dist/runtime-v2/internalization/artificer-output.d.ts +3 -2
- package/core/dist/runtime-v2/internalization/artificer-output.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/artificer-output.js +40 -31
- package/core/dist/runtime-v2/internalization/artificer-output.js.map +1 -1
- package/core/dist/runtime-v2/internalization/artificer-runner.d.ts +59 -38
- package/core/dist/runtime-v2/internalization/artificer-runner.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/artificer-runner.js +122 -394
- package/core/dist/runtime-v2/internalization/artificer-runner.js.map +1 -1
- package/core/dist/runtime-v2/internalization/dreamer-output.d.ts +4 -3
- package/core/dist/runtime-v2/internalization/dreamer-output.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/dreamer-output.js +11 -9
- package/core/dist/runtime-v2/internalization/dreamer-output.js.map +1 -1
- package/core/dist/runtime-v2/internalization/dreamer-runner.d.ts +31 -92
- package/core/dist/runtime-v2/internalization/dreamer-runner.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/dreamer-runner.js +80 -492
- package/core/dist/runtime-v2/internalization/dreamer-runner.js.map +1 -1
- package/core/dist/runtime-v2/internalization/evaluator-output.d.ts +2 -2
- package/core/dist/runtime-v2/internalization/evaluator-output.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/evaluator-output.js +29 -23
- package/core/dist/runtime-v2/internalization/evaluator-output.js.map +1 -1
- package/core/dist/runtime-v2/internalization/evaluator-runner.d.ts +74 -48
- package/core/dist/runtime-v2/internalization/evaluator-runner.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/evaluator-runner.js +165 -443
- package/core/dist/runtime-v2/internalization/evaluator-runner.js.map +1 -1
- package/core/dist/runtime-v2/internalization/peer-runner-contracts.d.ts +14 -0
- package/core/dist/runtime-v2/internalization/peer-runner-contracts.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/peer-runner-contracts.js +21 -0
- package/core/dist/runtime-v2/internalization/peer-runner-contracts.js.map +1 -1
- package/core/dist/runtime-v2/internalization/philosopher-output.d.ts +8 -2
- package/core/dist/runtime-v2/internalization/philosopher-output.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/philosopher-output.js +16 -11
- package/core/dist/runtime-v2/internalization/philosopher-output.js.map +1 -1
- package/core/dist/runtime-v2/internalization/philosopher-runner.d.ts +36 -55
- package/core/dist/runtime-v2/internalization/philosopher-runner.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/philosopher-runner.js +118 -400
- package/core/dist/runtime-v2/internalization/philosopher-runner.js.map +1 -1
- package/core/dist/runtime-v2/internalization/rollout-reviewer-runner.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/rollout-reviewer-runner.js +3 -1
- package/core/dist/runtime-v2/internalization/rollout-reviewer-runner.js.map +1 -1
- package/core/dist/runtime-v2/internalization/scribe-output.d.ts +8 -2
- package/core/dist/runtime-v2/internalization/scribe-output.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/scribe-output.js +33 -17
- package/core/dist/runtime-v2/internalization/scribe-output.js.map +1 -1
- package/core/dist/runtime-v2/internalization/scribe-runner.d.ts +56 -49
- package/core/dist/runtime-v2/internalization/scribe-runner.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/scribe-runner.js +117 -397
- package/core/dist/runtime-v2/internalization/scribe-runner.js.map +1 -1
- package/core/dist/runtime-v2/internalization/trainer-runner.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/trainer-runner.js +3 -1
- package/core/dist/runtime-v2/internalization/trainer-runner.js.map +1 -1
- package/core/dist/runtime-v2/runner/__tests__/base-peer-runner-trust-boundary.test.d.ts +2 -0
- package/core/dist/runtime-v2/runner/__tests__/base-peer-runner-trust-boundary.test.d.ts.map +1 -0
- package/core/dist/runtime-v2/runner/__tests__/base-peer-runner-trust-boundary.test.js +379 -0
- package/core/dist/runtime-v2/runner/__tests__/base-peer-runner-trust-boundary.test.js.map +1 -0
- package/core/dist/runtime-v2/runner/base-peer-runner.d.ts +122 -0
- package/core/dist/runtime-v2/runner/base-peer-runner.d.ts.map +1 -0
- package/core/dist/runtime-v2/runner/base-peer-runner.js +528 -0
- package/core/dist/runtime-v2/runner/base-peer-runner.js.map +1 -0
- package/core/dist/runtime-v2/runner/peer-runner-types.d.ts +107 -0
- package/core/dist/runtime-v2/runner/peer-runner-types.d.ts.map +1 -0
- package/core/dist/runtime-v2/runner/peer-runner-types.js +13 -0
- package/core/dist/runtime-v2/runner/peer-runner-types.js.map +1 -0
- package/core/dist/telemetry-event.d.ts +2 -2
- package/core/dist/telemetry-event.d.ts.map +1 -1
- package/core/dist/telemetry-event.js +89 -0
- package/core/dist/telemetry-event.js.map +1 -1
- package/dist/installer.d.ts +1 -1
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +39 -10
- package/dist/installer.js.map +1 -1
- package/dist/mvp-config.d.ts +33 -0
- package/dist/mvp-config.d.ts.map +1 -1
- package/dist/mvp-config.js +174 -0
- package/dist/mvp-config.js.map +1 -1
- package/package.json +3 -3
- package/pd-cli/dist/commands/config-doctor.d.ts +17 -0
- package/pd-cli/dist/commands/config-doctor.d.ts.map +1 -0
- package/pd-cli/dist/commands/config-doctor.js +143 -0
- package/pd-cli/dist/commands/config-doctor.js.map +1 -0
- package/pd-cli/dist/commands/console.d.ts +18 -0
- package/pd-cli/dist/commands/console.d.ts.map +1 -1
- package/pd-cli/dist/commands/console.js +439 -0
- package/pd-cli/dist/commands/console.js.map +1 -1
- package/pd-cli/dist/commands/runtime-features.d.ts +23 -8
- package/pd-cli/dist/commands/runtime-features.d.ts.map +1 -1
- package/pd-cli/dist/commands/runtime-features.js +72 -31
- package/pd-cli/dist/commands/runtime-features.js.map +1 -1
- package/pd-cli/dist/index.d.ts +1 -1
- package/pd-cli/dist/index.js +83 -31
- package/pd-cli/dist/index.js.map +1 -1
- package/pd-cli/dist/services/config-doctor.d.ts +102 -0
- package/pd-cli/dist/services/config-doctor.d.ts.map +1 -0
- package/pd-cli/dist/services/config-doctor.js +426 -0
- package/pd-cli/dist/services/config-doctor.js.map +1 -0
- package/pd-cli/dist/services/console-launcher.d.ts +110 -0
- package/pd-cli/dist/services/console-launcher.d.ts.map +1 -0
- package/pd-cli/dist/services/console-launcher.js +282 -0
- package/pd-cli/dist/services/console-launcher.js.map +1 -0
- package/pd-cli/dist/services/pd-config-loader.d.ts +64 -0
- package/pd-cli/dist/services/pd-config-loader.d.ts.map +1 -0
- package/pd-cli/dist/services/pd-config-loader.js +156 -0
- package/pd-cli/dist/services/pd-config-loader.js.map +1 -0
- package/pd-cli/package.json +3 -1
- package/plugin/dist/core/event-log.d.ts +10 -0
- package/plugin/dist/core/event-log.js +85 -1
- package/plugin/dist/core/pd-config-loader.d.ts +96 -0
- package/plugin/dist/core/pd-config-loader.js +307 -0
- package/plugin/dist/core/runtime-v2-prompt-activation-reader.d.ts +4 -0
- package/plugin/dist/core/runtime-v2-prompt-activation-reader.js +12 -56
- package/plugin/dist/core/surface-guard.d.ts +12 -8
- package/plugin/dist/core/surface-guard.js +51 -2
- package/plugin/dist/hooks/gate.js +3 -0
- package/plugin/dist/hooks/prompt.js +4 -5
- package/plugin/dist/index.d.ts +4 -0
- package/plugin/dist/index.js +70 -94
- package/plugin/dist/service/correction-observer-service.d.ts +8 -0
- package/plugin/dist/service/correction-observer-service.js +48 -29
- package/plugin/dist/utils/workspace-resolver.d.ts +33 -1
- package/plugin/dist/utils/workspace-resolver.js +182 -3
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BasePeerRunner — Abstract base class for all peer runners (PRI-302).
|
|
3
|
+
*
|
|
4
|
+
* Extracts the shared lease → buildContext → invoke → poll → fetch →
|
|
5
|
+
* validate → succeed/fail pipeline from 8 duplicated runner implementations.
|
|
6
|
+
*
|
|
7
|
+
* Subclasses implement:
|
|
8
|
+
* - permanentErrorCategories (abstract getter)
|
|
9
|
+
* - buildContext() — runner-specific context assembly
|
|
10
|
+
* - invokeRuntime() — runner-specific LLM invocation
|
|
11
|
+
* - validateOutput() — runner-specific output validation
|
|
12
|
+
* - succeedTask() — runner-specific artifact commit + task success
|
|
13
|
+
*
|
|
14
|
+
* Optional hooks:
|
|
15
|
+
* - emitSuccessTelemetry() — runner-specific success telemetry (default: no-op)
|
|
16
|
+
* - checkLineageIntegrity() — lineage strip contract check (default: no-op)
|
|
17
|
+
*
|
|
18
|
+
* @see docs/adr/0003-peer-agent-state-machine-orchestration.md
|
|
19
|
+
* @see Linear PRI-302
|
|
20
|
+
*/
|
|
21
|
+
import type { RuntimeStateManager } from '../store/runtime-state-manager.js';
|
|
22
|
+
import type { PDRuntimeAdapter, RunHandle } from '../runtime-protocol.js';
|
|
23
|
+
import type { StoreEventEmitter } from '../store/event-emitter.js';
|
|
24
|
+
import type { PIArtifactStore } from '../internalization/pi-artifact.js';
|
|
25
|
+
import type { TaskRecord } from '../task-status.js';
|
|
26
|
+
import { type PDErrorCategory } from '../error-categories.js';
|
|
27
|
+
import { RunnerPhase } from './runner-phase.js';
|
|
28
|
+
import type { PeerRunnerOptions, ResolvedPeerRunnerOptions, PeerRunnerDeps, PeerRunnerConfig, PeerRunnerResult, PeerRunnerValidationResult, FailureContext } from './peer-runner-types.js';
|
|
29
|
+
/**
|
|
30
|
+
* Abstract base class for peer runners.
|
|
31
|
+
*
|
|
32
|
+
* TContext — the type returned by buildContext(). Must include contextHash.
|
|
33
|
+
* TOutput — the type of the validated LLM output.
|
|
34
|
+
*/
|
|
35
|
+
export declare abstract class BasePeerRunner<TContext extends {
|
|
36
|
+
contextHash: string;
|
|
37
|
+
}, TOutput> {
|
|
38
|
+
protected readonly stateManager: RuntimeStateManager;
|
|
39
|
+
protected readonly runtimeAdapter: PDRuntimeAdapter;
|
|
40
|
+
protected readonly eventEmitter: StoreEventEmitter;
|
|
41
|
+
protected readonly artifactStore: PIArtifactStore;
|
|
42
|
+
protected readonly resolvedOptions: ResolvedPeerRunnerOptions;
|
|
43
|
+
protected readonly config: PeerRunnerConfig;
|
|
44
|
+
private phase;
|
|
45
|
+
constructor(deps: PeerRunnerDeps, options: PeerRunnerOptions, config: PeerRunnerConfig);
|
|
46
|
+
/** Current internal phase. For testing/observability only. */
|
|
47
|
+
get currentPhase(): RunnerPhase;
|
|
48
|
+
/** Permanent error categories — runner's inherent property. */
|
|
49
|
+
abstract get permanentErrorCategories(): ReadonlySet<PDErrorCategory>;
|
|
50
|
+
/** Build runner-specific context from task and predecessor outputs. */
|
|
51
|
+
abstract buildContext(taskId: string): Promise<TContext>;
|
|
52
|
+
/** Invoke the runtime with runner-specific prompt builder. */
|
|
53
|
+
abstract invokeRuntime(taskId: string, context: TContext): Promise<RunHandle>;
|
|
54
|
+
/** Validate the LLM output. Receives untrusted data — must perform runtime validation. */
|
|
55
|
+
abstract validateOutput(output: unknown, taskId: string, context: TContext): Promise<PeerRunnerValidationResult>;
|
|
56
|
+
/** Commit artifact + mark task succeeded. Runner-specific commit strategy. */
|
|
57
|
+
abstract succeedTask(taskId: string, runId: string, output: TOutput, task: TaskRecord, contextHash: string, context: TContext): Promise<PeerRunnerResult<TOutput>>;
|
|
58
|
+
/** Emit runner-specific success telemetry. Called after validation passes. */
|
|
59
|
+
protected emitSuccessTelemetry(_taskId: string, _output: TOutput, _context: TContext): void;
|
|
60
|
+
/**
|
|
61
|
+
* Check lineage strip contract. Called AFTER validation passes.
|
|
62
|
+
* Receives validated output — safe to treat as TOutput.
|
|
63
|
+
*/
|
|
64
|
+
protected checkLineageIntegrity(_taskId: string, _output: TOutput): void;
|
|
65
|
+
/**
|
|
66
|
+
* Transform output after fetch, before validation.
|
|
67
|
+
* Used by runners that need to re-inject lineage fields stripped by the adapter.
|
|
68
|
+
* Receives untrusted data — must NOT assume TOutput shape.
|
|
69
|
+
*/
|
|
70
|
+
protected postFetchTransform(_taskId: string, _untrustedOutput: unknown): void;
|
|
71
|
+
/**
|
|
72
|
+
* Execute the full peer runner lifecycle for a task.
|
|
73
|
+
*
|
|
74
|
+
* Pipeline: lease → resolveRunId → buildContext → invoke → poll →
|
|
75
|
+
* fetch → validate → succeedTask (abstract)
|
|
76
|
+
*
|
|
77
|
+
* Each invocation is independent — no mutable state between run() calls.
|
|
78
|
+
*/
|
|
79
|
+
run(taskId: string): Promise<PeerRunnerResult<TOutput>>;
|
|
80
|
+
/**
|
|
81
|
+
* Emit a telemetry event with the runner's name prefix.
|
|
82
|
+
* Event type: `{runnerName}_{eventType}`
|
|
83
|
+
*/
|
|
84
|
+
protected emitEvent(eventType: string, taskId: string, payload: Record<string, unknown>): void;
|
|
85
|
+
private pollUntilTerminal;
|
|
86
|
+
/**
|
|
87
|
+
* Fetch raw output from the runtime adapter.
|
|
88
|
+
*
|
|
89
|
+
* Returns `unknown` — the payload is untrusted LLM/runtime output.
|
|
90
|
+
* Callers MUST validate before treating as TOutput (ERR-001, ERR-005).
|
|
91
|
+
*/
|
|
92
|
+
private fetchAndParseOutput;
|
|
93
|
+
/** Resolve artifact IDs from predecessor tasks for lineage tracking. */
|
|
94
|
+
protected resolveLineageArtifactIds(taskId: string): Promise<{
|
|
95
|
+
ids: string[];
|
|
96
|
+
hasRejected: boolean;
|
|
97
|
+
}>;
|
|
98
|
+
private resolveStoreRunId;
|
|
99
|
+
/** Compute a deterministic hash from context references (observability only). */
|
|
100
|
+
protected static hashContextRefs(refs: readonly string[]): string;
|
|
101
|
+
private handleLeaseOrPhaseError;
|
|
102
|
+
private handlePostLeaseError;
|
|
103
|
+
private handleRuntimeFailure;
|
|
104
|
+
protected handleValidationError(ctx: {
|
|
105
|
+
taskId: string;
|
|
106
|
+
task: TaskRecord;
|
|
107
|
+
errors: readonly string[];
|
|
108
|
+
errorCategory?: PDErrorCategory;
|
|
109
|
+
}): Promise<PeerRunnerResult<TOutput>>;
|
|
110
|
+
/**
|
|
111
|
+
* Core retry-or-fail decision.
|
|
112
|
+
*
|
|
113
|
+
* Uses try/catch around markTaskFailed/markTaskRetryWait for robustness.
|
|
114
|
+
* If state manager operations fail, returns storage_unavailable instead of
|
|
115
|
+
* propagating the exception (ERR-002: graceful degradation with reason).
|
|
116
|
+
*/
|
|
117
|
+
protected retryOrFail(ctx: FailureContext): Promise<PeerRunnerResult<TOutput>>;
|
|
118
|
+
private classifyError;
|
|
119
|
+
private mapRunStatusToErrorCategory;
|
|
120
|
+
protected sleep(ms: number): Promise<void>;
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=base-peer-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-peer-runner.d.ts","sourceRoot":"","sources":["../../../src/runtime-v2/runner/base-peer-runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BasePeerRunner — Abstract base class for all peer runners (PRI-302).
|
|
3
|
+
*
|
|
4
|
+
* Extracts the shared lease → buildContext → invoke → poll → fetch →
|
|
5
|
+
* validate → succeed/fail pipeline from 8 duplicated runner implementations.
|
|
6
|
+
*
|
|
7
|
+
* Subclasses implement:
|
|
8
|
+
* - permanentErrorCategories (abstract getter)
|
|
9
|
+
* - buildContext() — runner-specific context assembly
|
|
10
|
+
* - invokeRuntime() — runner-specific LLM invocation
|
|
11
|
+
* - validateOutput() — runner-specific output validation
|
|
12
|
+
* - succeedTask() — runner-specific artifact commit + task success
|
|
13
|
+
*
|
|
14
|
+
* Optional hooks:
|
|
15
|
+
* - emitSuccessTelemetry() — runner-specific success telemetry (default: no-op)
|
|
16
|
+
* - checkLineageIntegrity() — lineage strip contract check (default: no-op)
|
|
17
|
+
*
|
|
18
|
+
* @see docs/adr/0003-peer-agent-state-machine-orchestration.md
|
|
19
|
+
* @see Linear PRI-302
|
|
20
|
+
*/
|
|
21
|
+
import { PDRuntimeError } from '../error-categories.js';
|
|
22
|
+
import { RunnerPhase } from './runner-phase.js';
|
|
23
|
+
// ── Defaults ─────────────────────────────────────────────────────────────────
|
|
24
|
+
const DEFAULT_PEER_RUNNER_OPTIONS = {
|
|
25
|
+
pollIntervalMs: 5_000,
|
|
26
|
+
timeoutMs: 300_000,
|
|
27
|
+
defaultMaxAttempts: 3,
|
|
28
|
+
agentId: 'peer-runner',
|
|
29
|
+
};
|
|
30
|
+
function resolvePeerRunnerOptions(options, defaultAgentId) {
|
|
31
|
+
return {
|
|
32
|
+
pollIntervalMs: options.pollIntervalMs ?? DEFAULT_PEER_RUNNER_OPTIONS.pollIntervalMs,
|
|
33
|
+
timeoutMs: options.timeoutMs ?? DEFAULT_PEER_RUNNER_OPTIONS.timeoutMs,
|
|
34
|
+
defaultMaxAttempts: options.defaultMaxAttempts ?? DEFAULT_PEER_RUNNER_OPTIONS.defaultMaxAttempts,
|
|
35
|
+
owner: options.owner,
|
|
36
|
+
runtimeKind: options.runtimeKind,
|
|
37
|
+
agentId: options.agentId ?? defaultAgentId,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
// ── BasePeerRunner ───────────────────────────────────────────────────────────
|
|
41
|
+
/**
|
|
42
|
+
* Abstract base class for peer runners.
|
|
43
|
+
*
|
|
44
|
+
* TContext — the type returned by buildContext(). Must include contextHash.
|
|
45
|
+
* TOutput — the type of the validated LLM output.
|
|
46
|
+
*/
|
|
47
|
+
export class BasePeerRunner {
|
|
48
|
+
// ── Shared dependencies (protected for subclass access) ──
|
|
49
|
+
stateManager;
|
|
50
|
+
runtimeAdapter;
|
|
51
|
+
eventEmitter;
|
|
52
|
+
artifactStore;
|
|
53
|
+
resolvedOptions;
|
|
54
|
+
config;
|
|
55
|
+
phase = RunnerPhase.Idle;
|
|
56
|
+
constructor(deps, options, config) {
|
|
57
|
+
this.stateManager = deps.stateManager;
|
|
58
|
+
this.runtimeAdapter = deps.runtimeAdapter;
|
|
59
|
+
this.eventEmitter = deps.eventEmitter;
|
|
60
|
+
this.artifactStore = deps.artifactStore;
|
|
61
|
+
this.config = config;
|
|
62
|
+
this.resolvedOptions = resolvePeerRunnerOptions(options, config.defaultAgentId);
|
|
63
|
+
}
|
|
64
|
+
// ── Observability ──────────────────────────────────────────────────────────
|
|
65
|
+
/** Current internal phase. For testing/observability only. */
|
|
66
|
+
get currentPhase() {
|
|
67
|
+
return this.phase;
|
|
68
|
+
}
|
|
69
|
+
// ── Optional hooks (default no-op) ─────────────────────────────────────────
|
|
70
|
+
/** Emit runner-specific success telemetry. Called after validation passes. */
|
|
71
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this
|
|
72
|
+
emitSuccessTelemetry(_taskId, _output, _context) {
|
|
73
|
+
// default no-op
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Check lineage strip contract. Called AFTER validation passes.
|
|
77
|
+
* Receives validated output — safe to treat as TOutput.
|
|
78
|
+
*/
|
|
79
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this
|
|
80
|
+
checkLineageIntegrity(_taskId, _output) {
|
|
81
|
+
// default no-op
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Transform output after fetch, before validation.
|
|
85
|
+
* Used by runners that need to re-inject lineage fields stripped by the adapter.
|
|
86
|
+
* Receives untrusted data — must NOT assume TOutput shape.
|
|
87
|
+
*/
|
|
88
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this
|
|
89
|
+
postFetchTransform(_taskId, _untrustedOutput) {
|
|
90
|
+
// default no-op
|
|
91
|
+
}
|
|
92
|
+
// ── Template method: full pipeline ─────────────────────────────────────────
|
|
93
|
+
/**
|
|
94
|
+
* Execute the full peer runner lifecycle for a task.
|
|
95
|
+
*
|
|
96
|
+
* Pipeline: lease → resolveRunId → buildContext → invoke → poll →
|
|
97
|
+
* fetch → validate → succeedTask (abstract)
|
|
98
|
+
*
|
|
99
|
+
* Each invocation is independent — no mutable state between run() calls.
|
|
100
|
+
*/
|
|
101
|
+
async run(taskId) {
|
|
102
|
+
this.phase = RunnerPhase.Idle;
|
|
103
|
+
// 1. Acquire lease — isolated try/catch so lease_conflict never uses synthetic TaskRecord
|
|
104
|
+
let leasedTask;
|
|
105
|
+
try {
|
|
106
|
+
leasedTask = await this.stateManager.acquireLease({
|
|
107
|
+
taskId,
|
|
108
|
+
owner: this.resolvedOptions.owner,
|
|
109
|
+
runtimeKind: this.resolvedOptions.runtimeKind,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
return await this.handleLeaseOrPhaseError(taskId, error);
|
|
114
|
+
}
|
|
115
|
+
// 1b. Validate task kind
|
|
116
|
+
if (leasedTask.taskKind !== this.config.expectedTaskKind) {
|
|
117
|
+
this.emitEvent('wrong_task_kind', taskId, {
|
|
118
|
+
expectedKind: this.config.expectedTaskKind,
|
|
119
|
+
actualKind: leasedTask.taskKind,
|
|
120
|
+
});
|
|
121
|
+
await this.stateManager.markTaskFailed(taskId, 'input_invalid');
|
|
122
|
+
return {
|
|
123
|
+
status: 'failed',
|
|
124
|
+
taskId,
|
|
125
|
+
errorCategory: 'input_invalid',
|
|
126
|
+
failureReason: `Task kind must be '${this.config.expectedTaskKind}', got '${leasedTask.taskKind}'`,
|
|
127
|
+
attemptCount: leasedTask.attemptCount,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
this.emitEvent('task_leased', taskId, {
|
|
131
|
+
taskKind: this.config.expectedTaskKind,
|
|
132
|
+
attemptCount: leasedTask.attemptCount,
|
|
133
|
+
});
|
|
134
|
+
// All subsequent errors use the real leasedTask — no synthetic TaskRecord allowed
|
|
135
|
+
try {
|
|
136
|
+
// 2. Resolve store runId
|
|
137
|
+
const storeRunId = await this.resolveStoreRunId(taskId);
|
|
138
|
+
// 3. Build context
|
|
139
|
+
this.phase = RunnerPhase.BuildingContext;
|
|
140
|
+
const context = await this.buildContext(taskId);
|
|
141
|
+
this.emitEvent('context_built', taskId, { contextHash: context.contextHash });
|
|
142
|
+
// 4. Invoke runtime
|
|
143
|
+
this.phase = RunnerPhase.Invoking;
|
|
144
|
+
const runHandle = await this.invokeRuntime(taskId, context);
|
|
145
|
+
this.emitEvent('run_started', taskId, {
|
|
146
|
+
runtimeKind: this.resolvedOptions.runtimeKind,
|
|
147
|
+
});
|
|
148
|
+
// 5. Poll until terminal
|
|
149
|
+
this.phase = RunnerPhase.Polling;
|
|
150
|
+
const finalStatus = await this.pollUntilTerminal(runHandle);
|
|
151
|
+
// 6. Handle non-success terminal states
|
|
152
|
+
if (finalStatus.status !== 'succeeded') {
|
|
153
|
+
return await this.handleRuntimeFailure(taskId, leasedTask, finalStatus);
|
|
154
|
+
}
|
|
155
|
+
// 7. Fetch output (returns unknown — untrusted LLM/runtime payload)
|
|
156
|
+
this.phase = RunnerPhase.FetchingOutput;
|
|
157
|
+
const untrustedOutput = await this.fetchAndParseOutput(runHandle.runId, taskId);
|
|
158
|
+
// 7b. Post-fetch transform on untrusted data (e.g., re-inject lineage fields).
|
|
159
|
+
// Operates on `unknown` — must NOT assume TOutput shape (ERR-001).
|
|
160
|
+
this.postFetchTransform(taskId, untrustedOutput);
|
|
161
|
+
// 8. Validate — the trust boundary. Only validated output becomes TOutput.
|
|
162
|
+
this.phase = RunnerPhase.Validating;
|
|
163
|
+
const validationResult = await this.validateOutput(untrustedOutput, taskId, context);
|
|
164
|
+
if (!validationResult.valid) {
|
|
165
|
+
return await this.handleValidationError({
|
|
166
|
+
taskId,
|
|
167
|
+
task: leasedTask,
|
|
168
|
+
errors: validationResult.errors,
|
|
169
|
+
errorCategory: validationResult.errorCategory,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
// Validation passed — safe to treat as TOutput.
|
|
173
|
+
const output = untrustedOutput;
|
|
174
|
+
this.emitEvent('output_validated', taskId, {});
|
|
175
|
+
// 8b. Check lineage integrity (receives validated output)
|
|
176
|
+
this.checkLineageIntegrity(taskId, output);
|
|
177
|
+
// 8c. Emit success telemetry (receives validated output)
|
|
178
|
+
this.emitSuccessTelemetry(taskId, output, context);
|
|
179
|
+
// 9. Succeed task (abstract — subclass implements commit strategy)
|
|
180
|
+
const result = await this.succeedTask(taskId, storeRunId, output, leasedTask, context.contextHash, context);
|
|
181
|
+
if (result.status === 'succeeded') {
|
|
182
|
+
this.phase = RunnerPhase.Completed;
|
|
183
|
+
}
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
// Post-lease errors use retryOrFail with the real leasedTask
|
|
188
|
+
return await this.handlePostLeaseError(taskId, leasedTask, error);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// ── Telemetry ──────────────────────────────────────────────────────────────
|
|
192
|
+
/**
|
|
193
|
+
* Emit a telemetry event with the runner's name prefix.
|
|
194
|
+
* Event type: `{runnerName}_{eventType}`
|
|
195
|
+
*/
|
|
196
|
+
emitEvent(eventType, taskId, payload) {
|
|
197
|
+
this.eventEmitter.emitTelemetry({
|
|
198
|
+
eventType: `${this.config.runnerName}_${eventType}`,
|
|
199
|
+
traceId: taskId,
|
|
200
|
+
timestamp: new Date().toISOString(),
|
|
201
|
+
sessionId: this.resolvedOptions.owner,
|
|
202
|
+
agentId: this.resolvedOptions.agentId,
|
|
203
|
+
payload,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
// ── Polling ────────────────────────────────────────────────────────────────
|
|
207
|
+
async pollUntilTerminal(runHandle) {
|
|
208
|
+
const deadline = Date.now() + this.resolvedOptions.timeoutMs;
|
|
209
|
+
const terminalStatuses = ['succeeded', 'failed', 'timed_out', 'cancelled'];
|
|
210
|
+
while (Date.now() < deadline) {
|
|
211
|
+
const status = await this.runtimeAdapter.pollRun(runHandle.runId);
|
|
212
|
+
if (terminalStatuses.includes(status.status)) {
|
|
213
|
+
return status;
|
|
214
|
+
}
|
|
215
|
+
await this.sleep(this.resolvedOptions.pollIntervalMs);
|
|
216
|
+
}
|
|
217
|
+
// Deadline reached — one final poll before cancelling
|
|
218
|
+
try {
|
|
219
|
+
const finalPoll = await this.runtimeAdapter.pollRun(runHandle.runId);
|
|
220
|
+
if (terminalStatuses.includes(finalPoll.status)) {
|
|
221
|
+
return finalPoll;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
catch { /* fall through to cancel/timeout */ }
|
|
225
|
+
// Timeout — cancel gracefully
|
|
226
|
+
let cancelFailed = false;
|
|
227
|
+
try {
|
|
228
|
+
await this.runtimeAdapter.cancelRun(runHandle.runId);
|
|
229
|
+
}
|
|
230
|
+
catch (cancelErr) {
|
|
231
|
+
cancelFailed = true;
|
|
232
|
+
this.emitEvent('cancel_run_failed', runHandle.runId, {
|
|
233
|
+
runId: runHandle.runId,
|
|
234
|
+
errorMessage: cancelErr instanceof Error ? cancelErr.message : String(cancelErr),
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
const cancelNote = cancelFailed ? ' (cancelRun also failed)' : '';
|
|
238
|
+
throw new PDRuntimeError('timeout', `Run ${runHandle.runId} timed out after ${this.resolvedOptions.timeoutMs}ms${cancelNote}`);
|
|
239
|
+
}
|
|
240
|
+
// ── Output fetching ────────────────────────────────────────────────────────
|
|
241
|
+
/**
|
|
242
|
+
* Fetch raw output from the runtime adapter.
|
|
243
|
+
*
|
|
244
|
+
* Returns `unknown` — the payload is untrusted LLM/runtime output.
|
|
245
|
+
* Callers MUST validate before treating as TOutput (ERR-001, ERR-005).
|
|
246
|
+
*/
|
|
247
|
+
async fetchAndParseOutput(runId, taskId) {
|
|
248
|
+
let result;
|
|
249
|
+
try {
|
|
250
|
+
result = await this.runtimeAdapter.fetchOutput(runId);
|
|
251
|
+
}
|
|
252
|
+
catch (fetchErr) {
|
|
253
|
+
this.emitEvent('output_extraction_failed', taskId, {
|
|
254
|
+
runId,
|
|
255
|
+
stage: 'fetchOutput',
|
|
256
|
+
errorMessage: fetchErr instanceof Error ? fetchErr.message : String(fetchErr),
|
|
257
|
+
});
|
|
258
|
+
throw fetchErr;
|
|
259
|
+
}
|
|
260
|
+
if (!result || !result.payload) {
|
|
261
|
+
this.emitEvent('output_extraction_failed', taskId, {
|
|
262
|
+
runId,
|
|
263
|
+
stage: 'payload_missing',
|
|
264
|
+
errorMessage: `No output available for run ${runId}`,
|
|
265
|
+
});
|
|
266
|
+
throw new PDRuntimeError('output_invalid', `No output available for run ${runId}`);
|
|
267
|
+
}
|
|
268
|
+
// Structural check: payload must be a non-null object.
|
|
269
|
+
// This is NOT a type assertion — we still return `unknown`.
|
|
270
|
+
const { payload } = result;
|
|
271
|
+
if (typeof payload !== 'object' || payload === null) {
|
|
272
|
+
this.emitEvent('output_extraction_failed', taskId, {
|
|
273
|
+
runId,
|
|
274
|
+
stage: 'payload_not_object',
|
|
275
|
+
errorMessage: `Output payload is not an object for run ${runId}`,
|
|
276
|
+
});
|
|
277
|
+
throw new PDRuntimeError('output_invalid', `Output payload is not an object for run ${runId}`);
|
|
278
|
+
}
|
|
279
|
+
return payload;
|
|
280
|
+
}
|
|
281
|
+
// ── Lineage resolution ─────────────────────────────────────────────────────
|
|
282
|
+
/** Resolve artifact IDs from predecessor tasks for lineage tracking. */
|
|
283
|
+
async resolveLineageArtifactIds(taskId) {
|
|
284
|
+
const { hydratePITaskRecord } = await import('../internalization/pitask-metadata.js');
|
|
285
|
+
const task = await this.stateManager.getTask(taskId);
|
|
286
|
+
if (!task)
|
|
287
|
+
return { ids: [], hasRejected: false };
|
|
288
|
+
const piTask = hydratePITaskRecord(task);
|
|
289
|
+
const deps = piTask?.dependencyTaskIds ?? [];
|
|
290
|
+
if (deps.length === 0)
|
|
291
|
+
return { ids: [], hasRejected: false };
|
|
292
|
+
const lineageIds = [];
|
|
293
|
+
let hasRejected = false;
|
|
294
|
+
const results = await Promise.allSettled(deps.map((depId) => this.artifactStore.listBySourceTaskId(depId)));
|
|
295
|
+
for (const result of results) {
|
|
296
|
+
if (result.status === 'fulfilled') {
|
|
297
|
+
for (const artifact of result.value) {
|
|
298
|
+
lineageIds.push(artifact.artifactId);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
hasRejected = true;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return { ids: lineageIds, hasRejected };
|
|
306
|
+
}
|
|
307
|
+
// ── Store helpers ──────────────────────────────────────────────────────────
|
|
308
|
+
async resolveStoreRunId(taskId) {
|
|
309
|
+
const runs = await this.stateManager.getRunsByTask(taskId);
|
|
310
|
+
const latestRun = runs[runs.length - 1];
|
|
311
|
+
if (!latestRun) {
|
|
312
|
+
throw new PDRuntimeError('execution_failed', `No run records found for task ${taskId} after lease acquisition`);
|
|
313
|
+
}
|
|
314
|
+
return latestRun.runId;
|
|
315
|
+
}
|
|
316
|
+
/** Compute a deterministic hash from context references (observability only). */
|
|
317
|
+
static hashContextRefs(refs) {
|
|
318
|
+
if (refs.length === 0)
|
|
319
|
+
return 'empty';
|
|
320
|
+
const str = refs.join('|');
|
|
321
|
+
let hash = 0;
|
|
322
|
+
for (let i = 0; i < str.length; i++) {
|
|
323
|
+
hash = (Math.imul(31, hash) + str.charCodeAt(i)) | 0;
|
|
324
|
+
}
|
|
325
|
+
return `ctx-${Math.abs(hash).toString(16)}`;
|
|
326
|
+
}
|
|
327
|
+
// ── Error handling ─────────────────────────────────────────────────────────
|
|
328
|
+
async handleLeaseOrPhaseError(taskId, error) {
|
|
329
|
+
const classified = this.classifyError(error);
|
|
330
|
+
// lease_conflict is concurrent-access conflict, not a state change.
|
|
331
|
+
// No mutation methods (markTaskFailed/markTaskRetryWait) are called.
|
|
332
|
+
if (classified.category === 'lease_conflict') {
|
|
333
|
+
this.emitEvent('run_failed', taskId, {
|
|
334
|
+
errorCategory: 'lease_conflict',
|
|
335
|
+
errorMessage: classified.message,
|
|
336
|
+
});
|
|
337
|
+
return {
|
|
338
|
+
status: 'failed',
|
|
339
|
+
taskId,
|
|
340
|
+
errorCategory: 'lease_conflict',
|
|
341
|
+
failureReason: classified.message,
|
|
342
|
+
attemptCount: 1,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
// Non-lease errors (e.g., storage_unavailable before lease) must not
|
|
346
|
+
// use synthetic TaskRecord. Build one with real defaults from options.
|
|
347
|
+
this.emitEvent('run_failed', taskId, {
|
|
348
|
+
errorCategory: classified.category,
|
|
349
|
+
errorMessage: classified.message,
|
|
350
|
+
});
|
|
351
|
+
const task = {
|
|
352
|
+
taskId,
|
|
353
|
+
taskKind: this.config.expectedTaskKind,
|
|
354
|
+
status: 'leased',
|
|
355
|
+
createdAt: new Date().toISOString(),
|
|
356
|
+
updatedAt: new Date().toISOString(),
|
|
357
|
+
attemptCount: 1,
|
|
358
|
+
maxAttempts: this.resolvedOptions.defaultMaxAttempts,
|
|
359
|
+
};
|
|
360
|
+
return this.retryOrFail({ taskId, task, errorCategory: classified.category, failureReason: classified.message });
|
|
361
|
+
}
|
|
362
|
+
async handlePostLeaseError(taskId, task, error) {
|
|
363
|
+
const classified = this.classifyError(error);
|
|
364
|
+
this.emitEvent('run_failed', taskId, {
|
|
365
|
+
errorCategory: classified.category,
|
|
366
|
+
errorMessage: classified.message,
|
|
367
|
+
});
|
|
368
|
+
return this.retryOrFail({ taskId, task, errorCategory: classified.category, failureReason: classified.message });
|
|
369
|
+
}
|
|
370
|
+
async handleRuntimeFailure(taskId, task, runStatus) {
|
|
371
|
+
const errorCategory = this.mapRunStatusToErrorCategory(runStatus.status, runStatus.reason);
|
|
372
|
+
this.emitEvent('run_failed', taskId, {
|
|
373
|
+
runStatus: runStatus.status,
|
|
374
|
+
errorCategory,
|
|
375
|
+
});
|
|
376
|
+
return this.retryOrFail({
|
|
377
|
+
taskId,
|
|
378
|
+
task,
|
|
379
|
+
errorCategory,
|
|
380
|
+
failureReason: `Runtime execution ended with status: ${runStatus.status}`,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
async handleValidationError(ctx) {
|
|
384
|
+
const category = ctx.errorCategory ?? 'output_invalid';
|
|
385
|
+
this.emitEvent('output_invalid', ctx.taskId, {
|
|
386
|
+
errorCount: ctx.errors.length,
|
|
387
|
+
errorCategory: category,
|
|
388
|
+
});
|
|
389
|
+
return this.retryOrFail({
|
|
390
|
+
taskId: ctx.taskId,
|
|
391
|
+
task: ctx.task,
|
|
392
|
+
errorCategory: category,
|
|
393
|
+
failureReason: `Validation failed: ${ctx.errors.join('; ')}`,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
// ── Retry/fail state machine ───────────────────────────────────────────────
|
|
397
|
+
/**
|
|
398
|
+
* Core retry-or-fail decision.
|
|
399
|
+
*
|
|
400
|
+
* Uses try/catch around markTaskFailed/markTaskRetryWait for robustness.
|
|
401
|
+
* If state manager operations fail, returns storage_unavailable instead of
|
|
402
|
+
* propagating the exception (ERR-002: graceful degradation with reason).
|
|
403
|
+
*/
|
|
404
|
+
async retryOrFail(ctx) {
|
|
405
|
+
// Permanent errors — never retry
|
|
406
|
+
if (this.permanentErrorCategories.has(ctx.errorCategory)) {
|
|
407
|
+
try {
|
|
408
|
+
await this.stateManager.markTaskFailed(ctx.taskId, ctx.errorCategory);
|
|
409
|
+
}
|
|
410
|
+
catch (stateErr) {
|
|
411
|
+
this.emitEvent('mark_failed_error', ctx.taskId, {
|
|
412
|
+
errorCategory: 'storage_unavailable',
|
|
413
|
+
attemptCount: ctx.task.attemptCount,
|
|
414
|
+
errorMessage: stateErr instanceof Error ? stateErr.message : String(stateErr),
|
|
415
|
+
});
|
|
416
|
+
return {
|
|
417
|
+
status: 'failed',
|
|
418
|
+
taskId: ctx.taskId,
|
|
419
|
+
errorCategory: 'storage_unavailable',
|
|
420
|
+
failureReason: `State manager error: ${ctx.failureReason}`,
|
|
421
|
+
attemptCount: ctx.task.attemptCount,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
this.emitEvent('task_failed', ctx.taskId, {
|
|
425
|
+
errorCategory: ctx.errorCategory,
|
|
426
|
+
attemptCount: ctx.task.attemptCount,
|
|
427
|
+
failureReason: ctx.failureReason,
|
|
428
|
+
});
|
|
429
|
+
this.phase = RunnerPhase.Failed;
|
|
430
|
+
return {
|
|
431
|
+
status: 'failed',
|
|
432
|
+
taskId: ctx.taskId,
|
|
433
|
+
errorCategory: ctx.errorCategory,
|
|
434
|
+
failureReason: ctx.failureReason,
|
|
435
|
+
attemptCount: ctx.task.attemptCount,
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
// Retry policy check
|
|
439
|
+
const shouldRetry = this.stateManager.getRetryPolicy().shouldRetry(ctx.task);
|
|
440
|
+
if (shouldRetry) {
|
|
441
|
+
try {
|
|
442
|
+
await this.stateManager.markTaskRetryWait(ctx.taskId, ctx.errorCategory);
|
|
443
|
+
}
|
|
444
|
+
catch (stateErr) {
|
|
445
|
+
this.emitEvent('mark_retry_error', ctx.taskId, {
|
|
446
|
+
errorCategory: 'storage_unavailable',
|
|
447
|
+
attemptCount: ctx.task.attemptCount,
|
|
448
|
+
errorMessage: stateErr instanceof Error ? stateErr.message : String(stateErr),
|
|
449
|
+
});
|
|
450
|
+
return {
|
|
451
|
+
status: 'failed',
|
|
452
|
+
taskId: ctx.taskId,
|
|
453
|
+
errorCategory: 'storage_unavailable',
|
|
454
|
+
failureReason: `State manager error: ${ctx.failureReason}`,
|
|
455
|
+
attemptCount: ctx.task.attemptCount,
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
this.emitEvent('task_retried', ctx.taskId, {
|
|
459
|
+
errorCategory: ctx.errorCategory,
|
|
460
|
+
attemptCount: ctx.task.attemptCount,
|
|
461
|
+
});
|
|
462
|
+
this.phase = RunnerPhase.RetryWaiting;
|
|
463
|
+
return {
|
|
464
|
+
status: 'retried',
|
|
465
|
+
taskId: ctx.taskId,
|
|
466
|
+
errorCategory: ctx.errorCategory,
|
|
467
|
+
failureReason: ctx.failureReason,
|
|
468
|
+
attemptCount: ctx.task.attemptCount,
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
// Max attempts exceeded
|
|
472
|
+
try {
|
|
473
|
+
await this.stateManager.markTaskFailed(ctx.taskId, 'max_attempts_exceeded');
|
|
474
|
+
}
|
|
475
|
+
catch (stateErr) {
|
|
476
|
+
this.emitEvent('mark_failed_error', ctx.taskId, {
|
|
477
|
+
errorCategory: 'storage_unavailable',
|
|
478
|
+
attemptCount: ctx.task.attemptCount,
|
|
479
|
+
errorMessage: stateErr instanceof Error ? stateErr.message : String(stateErr),
|
|
480
|
+
});
|
|
481
|
+
return {
|
|
482
|
+
status: 'failed',
|
|
483
|
+
taskId: ctx.taskId,
|
|
484
|
+
errorCategory: 'storage_unavailable',
|
|
485
|
+
failureReason: `State manager error: ${ctx.failureReason}`,
|
|
486
|
+
attemptCount: ctx.task.attemptCount,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
this.emitEvent('task_failed', ctx.taskId, {
|
|
490
|
+
errorCategory: 'max_attempts_exceeded',
|
|
491
|
+
attemptCount: ctx.task.attemptCount,
|
|
492
|
+
failureReason: `Max attempts exceeded: ${ctx.failureReason}`,
|
|
493
|
+
});
|
|
494
|
+
this.phase = RunnerPhase.Failed;
|
|
495
|
+
return {
|
|
496
|
+
status: 'failed',
|
|
497
|
+
taskId: ctx.taskId,
|
|
498
|
+
errorCategory: 'max_attempts_exceeded',
|
|
499
|
+
failureReason: `Max attempts exceeded: ${ctx.failureReason}`,
|
|
500
|
+
attemptCount: ctx.task.attemptCount,
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
// ── Error classification ──────────────────────────────────────────────────
|
|
504
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this
|
|
505
|
+
classifyError(error) {
|
|
506
|
+
if (error instanceof PDRuntimeError) {
|
|
507
|
+
return { category: error.category, message: error.message };
|
|
508
|
+
}
|
|
509
|
+
if (error instanceof Error) {
|
|
510
|
+
return { category: 'execution_failed', message: error.message };
|
|
511
|
+
}
|
|
512
|
+
return { category: 'execution_failed', message: String(error) };
|
|
513
|
+
}
|
|
514
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this
|
|
515
|
+
mapRunStatusToErrorCategory(status, _reason) {
|
|
516
|
+
switch (status) {
|
|
517
|
+
case 'failed': return 'execution_failed';
|
|
518
|
+
case 'timed_out': return 'timeout';
|
|
519
|
+
case 'cancelled': return 'cancelled';
|
|
520
|
+
default: return 'execution_failed';
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this
|
|
524
|
+
sleep(ms) {
|
|
525
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
//# sourceMappingURL=base-peer-runner.js.map
|