@principles/core 1.83.0 → 1.84.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/dist/runtime-v2/index.d.ts +2 -0
- package/dist/runtime-v2/index.d.ts.map +1 -1
- package/dist/runtime-v2/index.js +2 -0
- package/dist/runtime-v2/index.js.map +1 -1
- package/dist/runtime-v2/internalization/dreamer-output.d.ts +4 -3
- package/dist/runtime-v2/internalization/dreamer-output.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/dreamer-output.js +11 -9
- package/dist/runtime-v2/internalization/dreamer-output.js.map +1 -1
- package/dist/runtime-v2/internalization/dreamer-runner.d.ts +31 -92
- package/dist/runtime-v2/internalization/dreamer-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/dreamer-runner.js +79 -495
- package/dist/runtime-v2/internalization/dreamer-runner.js.map +1 -1
- package/dist/runtime-v2/runner/__tests__/base-peer-runner-trust-boundary.test.d.ts +2 -0
- package/dist/runtime-v2/runner/__tests__/base-peer-runner-trust-boundary.test.d.ts.map +1 -0
- package/dist/runtime-v2/runner/__tests__/base-peer-runner-trust-boundary.test.js +238 -0
- package/dist/runtime-v2/runner/__tests__/base-peer-runner-trust-boundary.test.js.map +1 -0
- package/dist/runtime-v2/runner/base-peer-runner.d.ts +122 -0
- package/dist/runtime-v2/runner/base-peer-runner.d.ts.map +1 -0
- package/dist/runtime-v2/runner/base-peer-runner.js +516 -0
- package/dist/runtime-v2/runner/base-peer-runner.js.map +1 -0
- package/dist/runtime-v2/runner/peer-runner-types.d.ts +107 -0
- package/dist/runtime-v2/runner/peer-runner-types.d.ts.map +1 -0
- package/dist/runtime-v2/runner/peer-runner-types.js +13 -0
- package/dist/runtime-v2/runner/peer-runner-types.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { PDRuntimeError } from '../error-categories.js';
|
|
2
2
|
import { hydratePITaskRecord } from './pitask-metadata.js';
|
|
3
|
-
import { RunnerPhase } from '../runner/runner-phase.js';
|
|
4
3
|
import { DreamerPromptBuilder } from './dreamer-prompt-builder.js';
|
|
5
4
|
import { injectRunnerLineageIfAbsent } from './peer-runner-contracts.js';
|
|
6
|
-
|
|
5
|
+
import { BasePeerRunner } from '../runner/base-peer-runner.js';
|
|
6
|
+
export const DEFAULT_DREAMER_RUNNER_OPTIONS = {
|
|
7
7
|
pollIntervalMs: 5_000,
|
|
8
8
|
timeoutMs: 300_000,
|
|
9
9
|
defaultMaxAttempts: 3,
|
|
10
10
|
agentId: 'dreamer',
|
|
11
11
|
};
|
|
12
|
-
function resolveDreamerRunnerOptions(options) {
|
|
12
|
+
export function resolveDreamerRunnerOptions(options) {
|
|
13
13
|
return {
|
|
14
14
|
pollIntervalMs: options.pollIntervalMs ?? DEFAULT_DREAMER_RUNNER_OPTIONS.pollIntervalMs,
|
|
15
15
|
timeoutMs: options.timeoutMs ?? DEFAULT_DREAMER_RUNNER_OPTIONS.timeoutMs,
|
|
@@ -19,151 +19,23 @@ function resolveDreamerRunnerOptions(options) {
|
|
|
19
19
|
agentId: options.agentId ?? DEFAULT_DREAMER_RUNNER_OPTIONS.agentId,
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
|
-
// ── DreamerRunner
|
|
23
|
-
export class DreamerRunner {
|
|
24
|
-
phase = RunnerPhase.Idle;
|
|
25
|
-
resolvedOptions;
|
|
26
|
-
stateManager;
|
|
27
|
-
runtimeAdapter;
|
|
28
|
-
eventEmitter;
|
|
22
|
+
// ── DreamerRunner ────────────────────────────────────────────────────────────
|
|
23
|
+
export class DreamerRunner extends BasePeerRunner {
|
|
29
24
|
validator;
|
|
30
|
-
artifactStore;
|
|
31
25
|
constructor(deps, options) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
this.resolvedOptions = resolveDreamerRunnerOptions(options);
|
|
38
|
-
}
|
|
39
|
-
/** Get the current internal phase. For testing/observability only. */
|
|
40
|
-
get currentPhase() {
|
|
41
|
-
return this.phase;
|
|
42
|
-
}
|
|
43
|
-
emitDreamerEvent(eventType, taskId, payload) {
|
|
44
|
-
this.eventEmitter.emitTelemetry({
|
|
45
|
-
eventType: eventType,
|
|
46
|
-
traceId: taskId,
|
|
47
|
-
timestamp: new Date().toISOString(),
|
|
48
|
-
sessionId: this.resolvedOptions.owner,
|
|
49
|
-
agentId: this.resolvedOptions.agentId,
|
|
50
|
-
payload,
|
|
26
|
+
super(deps, options, {
|
|
27
|
+
runnerName: 'dreamer',
|
|
28
|
+
expectedTaskKind: 'dreamer',
|
|
29
|
+
defaultAgentId: 'dreamer',
|
|
30
|
+
resultRefPrefix: 'dreamer',
|
|
51
31
|
});
|
|
32
|
+
this.validator = deps.validator;
|
|
52
33
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
* Each invocation is independent.
|
|
58
|
-
*/
|
|
59
|
-
async run(taskId) {
|
|
60
|
-
this.phase = RunnerPhase.Idle;
|
|
61
|
-
// 1. Acquire lease — isolated try/catch so lease_conflict never uses synthetic TaskRecord
|
|
62
|
-
let leasedTask;
|
|
63
|
-
try {
|
|
64
|
-
leasedTask = await this.stateManager.acquireLease({
|
|
65
|
-
taskId,
|
|
66
|
-
owner: this.resolvedOptions.owner,
|
|
67
|
-
runtimeKind: this.resolvedOptions.runtimeKind,
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
catch (error) {
|
|
71
|
-
return await this.handleLeaseOrPhaseError(taskId, error);
|
|
72
|
-
}
|
|
73
|
-
this.emitDreamerEvent('dreamer_task_leased', taskId, {
|
|
74
|
-
taskKind: 'dreamer',
|
|
75
|
-
attemptCount: leasedTask.attemptCount,
|
|
76
|
-
});
|
|
77
|
-
if (leasedTask.taskKind !== 'dreamer') {
|
|
78
|
-
this.emitDreamerEvent('dreamer_wrong_task_kind', taskId, {
|
|
79
|
-
expectedKind: 'dreamer',
|
|
80
|
-
actualKind: leasedTask.taskKind,
|
|
81
|
-
});
|
|
82
|
-
await this.stateManager.markTaskFailed(taskId, 'input_invalid');
|
|
83
|
-
return {
|
|
84
|
-
status: 'failed',
|
|
85
|
-
taskId,
|
|
86
|
-
errorCategory: 'input_invalid',
|
|
87
|
-
failureReason: `Task kind must be 'dreamer', got '${leasedTask.taskKind}'`,
|
|
88
|
-
attemptCount: leasedTask.attemptCount,
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
// All subsequent errors use the real leasedTask — no synthetic TaskRecord allowed
|
|
92
|
-
try {
|
|
93
|
-
// acquireLease creates a RunRecord; resolve its runId for store operations
|
|
94
|
-
const storeRunId = await this.resolveStoreRunId(taskId);
|
|
95
|
-
// 2. Build context from predecessor task outputs (no ContextAssembler)
|
|
96
|
-
this.phase = RunnerPhase.BuildingContext;
|
|
97
|
-
const { contextHash, contextRefs, predecessorOutput } = await this.buildContext(taskId);
|
|
98
|
-
this.emitDreamerEvent('dreamer_context_built', taskId, { contextHash });
|
|
99
|
-
// 3. Invoke runtime
|
|
100
|
-
this.phase = RunnerPhase.Invoking;
|
|
101
|
-
const runHandle = await this.invokeRuntime({ taskId, contextHash, contextRefs, predecessorOutput });
|
|
102
|
-
this.emitDreamerEvent('dreamer_run_started', taskId, {
|
|
103
|
-
runtimeKind: this.resolvedOptions.runtimeKind,
|
|
104
|
-
});
|
|
105
|
-
// 4. Poll until terminal
|
|
106
|
-
this.phase = RunnerPhase.Polling;
|
|
107
|
-
const finalStatus = await this.pollUntilTerminal(runHandle);
|
|
108
|
-
// 5. Handle non-success terminal states
|
|
109
|
-
if (finalStatus.status !== 'succeeded') {
|
|
110
|
-
return await this.handleRuntimeFailure(taskId, leasedTask, finalStatus);
|
|
111
|
-
}
|
|
112
|
-
// 6. Fetch output
|
|
113
|
-
this.phase = RunnerPhase.FetchingOutput;
|
|
114
|
-
const output = await this.fetchAndParseOutput(runHandle.runId, taskId);
|
|
115
|
-
// Re-inject taskId if stripped by stripLineageFields (PRI-272 / ERR-008).
|
|
116
|
-
// Only fill when absent via Object.hasOwn — present-but-falsy values
|
|
117
|
-
// must reach validation and fail loud (Runtime Contract Rule 3).
|
|
118
|
-
injectRunnerLineageIfAbsent(output, 'taskId', taskId);
|
|
119
|
-
// 7. Validate
|
|
120
|
-
this.phase = RunnerPhase.Validating;
|
|
121
|
-
const validationResult = await this.validator.validate(output, taskId);
|
|
122
|
-
if (!validationResult.valid) {
|
|
123
|
-
return await this.handleValidationError({
|
|
124
|
-
taskId,
|
|
125
|
-
task: leasedTask,
|
|
126
|
-
errors: validationResult.errors,
|
|
127
|
-
errorCategory: validationResult.errorCategory,
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
this.emitDreamerEvent('dreamer_output_validated', taskId, {
|
|
131
|
-
candidateCount: output.candidates.length,
|
|
132
|
-
});
|
|
133
|
-
// 8. Succeed task
|
|
134
|
-
return await this.succeedTask({
|
|
135
|
-
taskId,
|
|
136
|
-
runId: storeRunId,
|
|
137
|
-
output,
|
|
138
|
-
task: leasedTask,
|
|
139
|
-
contextHash,
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
catch (error) {
|
|
143
|
-
// handleLeaseOrPhaseError is only for lease errors (before lease).
|
|
144
|
-
// Post-lease errors use retryOrFail with the real leasedTask.
|
|
145
|
-
return await this.handlePostLeaseError(taskId, leasedTask, error);
|
|
146
|
-
}
|
|
34
|
+
// ── Abstract implementations ───────────────────────────────────────────────
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this
|
|
36
|
+
get permanentErrorCategories() {
|
|
37
|
+
return new Set(['storage_unavailable', 'workspace_invalid', 'capability_missing', 'cancelled', 'input_invalid']);
|
|
147
38
|
}
|
|
148
|
-
// ── Phase methods ─────────────────────────────────────────────────────────
|
|
149
|
-
/**
|
|
150
|
-
* Build context from predecessor task outputs.
|
|
151
|
-
*
|
|
152
|
-
* Dreamer doesn't use ContextAssembler — it resolves predecessor context
|
|
153
|
-
* from dependencyTaskIds via stateManager.getTask(). The predecessor's
|
|
154
|
-
* resultRef/outputArtifactRefs become the Dreamer's input context.
|
|
155
|
-
*
|
|
156
|
-
* Degradation semantics (intentional partial failure):
|
|
157
|
-
* - If ALL dependencies fail → builds empty context (continues, not fail-closed)
|
|
158
|
-
* - If SOME dependencies fail → builds partial context + emits dreamer_context_partial
|
|
159
|
-
* - Only fulfilled results contribute contextRefs
|
|
160
|
-
*
|
|
161
|
-
* This is a deliberate design choice: Dreamer can produce output with partial
|
|
162
|
-
* context, whereas the host layer's task-ready validation is fail-closed.
|
|
163
|
-
* Partial context is reported via telemetry.
|
|
164
|
-
*
|
|
165
|
-
* @see hydratePITaskRecord (PRI-65) for fail-closed PI metadata access
|
|
166
|
-
*/
|
|
167
39
|
async buildContext(taskId) {
|
|
168
40
|
const task = await this.stateManager.getTask(taskId);
|
|
169
41
|
if (!task) {
|
|
@@ -207,434 +79,146 @@ export class DreamerRunner {
|
|
|
207
79
|
}
|
|
208
80
|
}
|
|
209
81
|
if (rejectedDeps.length > 0) {
|
|
210
|
-
this.
|
|
82
|
+
this.emitEvent('context_partial', taskId, {
|
|
211
83
|
rejectedCount: rejectedDeps.length,
|
|
212
84
|
rejectedDeps,
|
|
213
85
|
});
|
|
214
86
|
}
|
|
215
|
-
const contextHash =
|
|
87
|
+
const contextHash = BasePeerRunner.hashContextRefs(contextRefs);
|
|
216
88
|
return { contextHash, contextRefs, predecessorOutput };
|
|
217
89
|
}
|
|
218
|
-
|
|
219
|
-
* Compute a deterministic hash from context references.
|
|
220
|
-
* Used for telemetry and result tracking, not for caching.
|
|
221
|
-
*/
|
|
222
|
-
static hashContextRefs(refs) {
|
|
223
|
-
if (refs.length === 0)
|
|
224
|
-
return 'empty';
|
|
225
|
-
// Simple deterministic hash via DJB2-style accumulator
|
|
226
|
-
// Not cryptographic — only used for observability
|
|
227
|
-
const str = refs.join('|');
|
|
228
|
-
let hash = 0;
|
|
229
|
-
for (let i = 0; i < str.length; i++) {
|
|
230
|
-
hash = (Math.imul(31, hash) + str.charCodeAt(i)) | 0;
|
|
231
|
-
}
|
|
232
|
-
return `ctx-${Math.abs(hash).toString(16)}`;
|
|
233
|
-
}
|
|
234
|
-
async resolveStoreRunId(taskId) {
|
|
235
|
-
const runs = await this.stateManager.getRunsByTask(taskId);
|
|
236
|
-
const latestRun = runs[runs.length - 1];
|
|
237
|
-
if (!latestRun) {
|
|
238
|
-
throw new PDRuntimeError('execution_failed', `No run records found for task ${taskId} after lease acquisition`);
|
|
239
|
-
}
|
|
240
|
-
return latestRun.runId;
|
|
241
|
-
}
|
|
242
|
-
async resolveLineageArtifactIds(taskId) {
|
|
243
|
-
const task = await this.stateManager.getTask(taskId);
|
|
244
|
-
if (!task)
|
|
245
|
-
return { ids: [], hasRejected: false };
|
|
246
|
-
const piTask = hydratePITaskRecord(task);
|
|
247
|
-
const deps = piTask?.dependencyTaskIds ?? [];
|
|
248
|
-
if (deps.length === 0)
|
|
249
|
-
return { ids: [], hasRejected: false };
|
|
250
|
-
const lineageIds = [];
|
|
251
|
-
let hasRejected = false;
|
|
252
|
-
const results = await Promise.allSettled(deps.map((depId) => this.artifactStore.listBySourceTaskId(depId)));
|
|
253
|
-
for (const result of results) {
|
|
254
|
-
if (result.status === 'fulfilled') {
|
|
255
|
-
for (const artifact of result.value) {
|
|
256
|
-
lineageIds.push(artifact.artifactId);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
else {
|
|
260
|
-
hasRejected = true;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
return { ids: lineageIds, hasRejected };
|
|
264
|
-
}
|
|
265
|
-
async invokeRuntime(params) {
|
|
90
|
+
async invokeRuntime(taskId, context) {
|
|
266
91
|
const builder = new DreamerPromptBuilder();
|
|
267
92
|
const { message } = builder.buildPrompt({
|
|
268
|
-
taskId
|
|
269
|
-
contextHash:
|
|
270
|
-
contextRefs:
|
|
271
|
-
predecessorOutput:
|
|
93
|
+
taskId,
|
|
94
|
+
contextHash: context.contextHash,
|
|
95
|
+
contextRefs: context.contextRefs,
|
|
96
|
+
predecessorOutput: context.predecessorOutput,
|
|
272
97
|
});
|
|
273
|
-
|
|
98
|
+
return this.runtimeAdapter.startRun({
|
|
274
99
|
agentSpec: { agentId: this.resolvedOptions.agentId, schemaVersion: 'v1' },
|
|
275
|
-
taskRef: { taskId
|
|
100
|
+
taskRef: { taskId },
|
|
276
101
|
inputPayload: message,
|
|
277
102
|
contextItems: [],
|
|
278
103
|
outputSchemaRef: 'dreamer-output-v1',
|
|
279
104
|
timeoutMs: this.resolvedOptions.timeoutMs,
|
|
280
|
-
};
|
|
281
|
-
return this.runtimeAdapter.startRun(startInput);
|
|
282
|
-
}
|
|
283
|
-
async pollUntilTerminal(runHandle) {
|
|
284
|
-
const deadline = Date.now() + this.resolvedOptions.timeoutMs;
|
|
285
|
-
const terminalStatuses = ['succeeded', 'failed', 'timed_out', 'cancelled'];
|
|
286
|
-
while (Date.now() < deadline) {
|
|
287
|
-
const status = await this.runtimeAdapter.pollRun(runHandle.runId);
|
|
288
|
-
if (terminalStatuses.includes(status.status)) {
|
|
289
|
-
return status;
|
|
290
|
-
}
|
|
291
|
-
await this.sleep(this.resolvedOptions.pollIntervalMs);
|
|
292
|
-
}
|
|
293
|
-
// Timeout — cancel gracefully, preserve timeout error with cancel status
|
|
294
|
-
let cancelFailed = false;
|
|
295
|
-
try {
|
|
296
|
-
await this.runtimeAdapter.cancelRun(runHandle.runId);
|
|
297
|
-
}
|
|
298
|
-
catch (cancelErr) {
|
|
299
|
-
cancelFailed = true;
|
|
300
|
-
this.emitDreamerEvent('dreamer_cancel_run_failed', runHandle.runId, {
|
|
301
|
-
runId: runHandle.runId,
|
|
302
|
-
errorMessage: cancelErr instanceof Error ? cancelErr.message : String(cancelErr),
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
const cancelNote = cancelFailed ? ' (cancelRun also failed)' : '';
|
|
306
|
-
throw new PDRuntimeError('timeout', `Run ${runHandle.runId} timed out after ${this.resolvedOptions.timeoutMs}ms${cancelNote}`);
|
|
307
|
-
}
|
|
308
|
-
async fetchAndParseOutput(runId, taskId) {
|
|
309
|
-
const result = await this.fetchOutputWithTelemetry(runId, taskId);
|
|
310
|
-
if (!result || !result.payload) {
|
|
311
|
-
this.emitDreamerEvent('dreamer_output_extraction_failed', taskId, {
|
|
312
|
-
runId,
|
|
313
|
-
stage: 'payload_missing',
|
|
314
|
-
errorMessage: `No output available for run ${runId}`,
|
|
315
|
-
});
|
|
316
|
-
throw new PDRuntimeError('output_invalid', `No output available for run ${runId}`);
|
|
317
|
-
}
|
|
318
|
-
const payload = result.payload;
|
|
319
|
-
if (typeof payload !== 'object' || payload === null) {
|
|
320
|
-
this.emitDreamerEvent('dreamer_output_extraction_failed', taskId, {
|
|
321
|
-
runId,
|
|
322
|
-
stage: 'payload_not_object',
|
|
323
|
-
errorMessage: `Output payload is not an object for run ${runId}`,
|
|
324
|
-
});
|
|
325
|
-
throw new PDRuntimeError('output_invalid', `Output payload is not an object for run ${runId}`);
|
|
326
|
-
}
|
|
327
|
-
return result.payload;
|
|
105
|
+
});
|
|
328
106
|
}
|
|
329
|
-
async
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
stage: 'fetchOutput',
|
|
337
|
-
errorMessage: fetchErr instanceof Error ? fetchErr.message : String(fetchErr),
|
|
338
|
-
});
|
|
339
|
-
throw fetchErr;
|
|
340
|
-
}
|
|
107
|
+
async validateOutput(output, taskId) {
|
|
108
|
+
const result = await this.validator.validate(output, taskId);
|
|
109
|
+
return {
|
|
110
|
+
valid: result.valid,
|
|
111
|
+
errors: result.errors,
|
|
112
|
+
errorCategory: result.errorCategory,
|
|
113
|
+
};
|
|
341
114
|
}
|
|
342
|
-
|
|
343
|
-
|
|
115
|
+
// eslint-disable-next-line @typescript-eslint/max-params
|
|
116
|
+
async succeedTask(taskId, runId, output, task, contextHash, _context) {
|
|
117
|
+
// Store output before marking succeeded
|
|
344
118
|
try {
|
|
345
|
-
await this.stateManager.updateRunOutput(
|
|
119
|
+
await this.stateManager.updateRunOutput(runId, JSON.stringify(output));
|
|
346
120
|
}
|
|
347
121
|
catch (updateErr) {
|
|
348
|
-
this.
|
|
349
|
-
runId
|
|
122
|
+
this.emitEvent('update_output_failed', taskId, {
|
|
123
|
+
runId,
|
|
350
124
|
errorMessage: updateErr instanceof Error ? updateErr.message : String(updateErr),
|
|
351
125
|
});
|
|
352
126
|
throw updateErr;
|
|
353
127
|
}
|
|
354
|
-
// Emit per-candidate telemetry
|
|
355
|
-
for (const candidate of ctx.output.candidates) {
|
|
356
|
-
this.emitDreamerEvent('dreamer_candidate_generated', ctx.taskId, {
|
|
357
|
-
candidateIndex: candidate.candidateIndex,
|
|
358
|
-
confidence: candidate.confidence,
|
|
359
|
-
riskLevel: candidate.riskLevel,
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
128
|
// Write PIArtifact via artifactStore (idempotent upsert)
|
|
363
|
-
// PIArtifact is the core durable output of DreamerRunner.
|
|
364
|
-
// If artifact write fails, the task must NOT be marked succeeded —
|
|
365
|
-
// downstream runners (Philosopher/Scribe) require durable artifact input.
|
|
366
129
|
let lineageArtifactIds = [];
|
|
367
130
|
let lineageHasRejected = false;
|
|
368
131
|
try {
|
|
369
|
-
const lineageResult = await this.resolveLineageArtifactIds(
|
|
132
|
+
const lineageResult = await this.resolveLineageArtifactIds(taskId);
|
|
370
133
|
lineageArtifactIds = lineageResult.ids;
|
|
371
134
|
lineageHasRejected = lineageResult.hasRejected;
|
|
372
135
|
}
|
|
373
136
|
catch (lineageErr) {
|
|
374
|
-
this.
|
|
375
|
-
runId
|
|
137
|
+
this.emitEvent('lineage_resolve_failed', taskId, {
|
|
138
|
+
runId,
|
|
376
139
|
errorMessage: lineageErr instanceof Error ? lineageErr.message : String(lineageErr),
|
|
377
140
|
});
|
|
378
141
|
}
|
|
379
142
|
if (lineageHasRejected) {
|
|
380
|
-
this.
|
|
381
|
-
runId
|
|
143
|
+
this.emitEvent('lineage_partial', taskId, {
|
|
144
|
+
runId,
|
|
382
145
|
resolvedCount: lineageArtifactIds.length,
|
|
383
146
|
warning: 'Some dependency artifact queries were rejected; lineage may be incomplete',
|
|
384
147
|
});
|
|
385
148
|
}
|
|
386
|
-
const artifactId = `pi-art-${
|
|
149
|
+
const artifactId = `pi-art-${taskId}-${runId}`;
|
|
387
150
|
const now = new Date().toISOString();
|
|
388
151
|
try {
|
|
389
152
|
await this.artifactStore.upsertArtifact({
|
|
390
153
|
artifactId,
|
|
391
154
|
artifactKind: 'principle',
|
|
392
|
-
sourceTaskId:
|
|
155
|
+
sourceTaskId: taskId,
|
|
393
156
|
lineageArtifactIds,
|
|
394
157
|
validationStatus: 'pending',
|
|
395
|
-
contentJson: JSON.stringify(
|
|
158
|
+
contentJson: JSON.stringify(output),
|
|
396
159
|
createdAt: now,
|
|
397
160
|
updatedAt: now,
|
|
398
161
|
});
|
|
399
162
|
}
|
|
400
163
|
catch (artifactErr) {
|
|
401
|
-
this.
|
|
402
|
-
runId
|
|
164
|
+
this.emitEvent('artifact_write_failed', taskId, {
|
|
165
|
+
runId,
|
|
403
166
|
errorMessage: artifactErr instanceof Error ? artifactErr.message : String(artifactErr),
|
|
404
167
|
});
|
|
405
168
|
return this.retryOrFail({
|
|
406
|
-
taskId
|
|
407
|
-
task
|
|
169
|
+
taskId,
|
|
170
|
+
task,
|
|
408
171
|
errorCategory: 'artifact_commit_failed',
|
|
409
172
|
failureReason: `PIArtifact write failed: ${artifactErr instanceof Error ? artifactErr.message : String(artifactErr)}`,
|
|
410
173
|
});
|
|
411
174
|
}
|
|
412
|
-
// Mark task succeeded
|
|
413
|
-
const resultRef =
|
|
175
|
+
// Mark task succeeded
|
|
176
|
+
const resultRef = `${this.config.resultRefPrefix}://${runId}`;
|
|
414
177
|
try {
|
|
415
|
-
await this.stateManager.markTaskSucceeded(
|
|
178
|
+
await this.stateManager.markTaskSucceeded(taskId, resultRef);
|
|
416
179
|
}
|
|
417
180
|
catch (stateErr) {
|
|
418
|
-
this.
|
|
419
|
-
taskId
|
|
420
|
-
runId
|
|
181
|
+
this.emitEvent('mark_succeeded_failed', taskId, {
|
|
182
|
+
taskId,
|
|
183
|
+
runId,
|
|
421
184
|
errorMessage: stateErr instanceof Error ? stateErr.message : String(stateErr),
|
|
422
185
|
});
|
|
423
186
|
throw stateErr;
|
|
424
187
|
}
|
|
425
|
-
this.
|
|
426
|
-
attemptCount:
|
|
188
|
+
this.emitEvent('task_succeeded', taskId, {
|
|
189
|
+
attemptCount: task.attemptCount,
|
|
427
190
|
resultRef,
|
|
428
|
-
candidateCount:
|
|
191
|
+
candidateCount: output.candidates.length,
|
|
429
192
|
});
|
|
430
|
-
this.phase = RunnerPhase.Completed;
|
|
431
193
|
return {
|
|
432
194
|
status: 'succeeded',
|
|
433
|
-
taskId
|
|
434
|
-
runId
|
|
195
|
+
taskId,
|
|
196
|
+
runId,
|
|
435
197
|
artifactId,
|
|
436
198
|
resultRef,
|
|
437
|
-
contextHash
|
|
438
|
-
output
|
|
439
|
-
attemptCount:
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
async handleRuntimeFailure(taskId, task, runStatus) {
|
|
443
|
-
const errorCategory = this.mapRunStatusToErrorCategory(runStatus.status, runStatus.reason);
|
|
444
|
-
this.emitDreamerEvent('dreamer_run_failed', taskId, {
|
|
445
|
-
runStatus: runStatus.status,
|
|
446
|
-
errorCategory,
|
|
447
|
-
});
|
|
448
|
-
return this.retryOrFail({
|
|
449
|
-
taskId,
|
|
450
|
-
task,
|
|
451
|
-
errorCategory,
|
|
452
|
-
failureReason: `Runtime execution ended with status: ${runStatus.status}`,
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
async handleValidationError(ctx) {
|
|
456
|
-
const category = ctx.errorCategory ?? 'output_invalid';
|
|
457
|
-
this.emitDreamerEvent('dreamer_output_invalid', ctx.taskId, {
|
|
458
|
-
errorCount: ctx.errors.length,
|
|
459
|
-
errorCategory: category,
|
|
460
|
-
});
|
|
461
|
-
return this.retryOrFail({
|
|
462
|
-
taskId: ctx.taskId,
|
|
463
|
-
task: ctx.task,
|
|
464
|
-
errorCategory: category,
|
|
465
|
-
failureReason: `Validation failed: ${ctx.errors.join('; ')}`,
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
async handleLeaseOrPhaseError(taskId, error) {
|
|
469
|
-
const classified = this.classifyError(error);
|
|
470
|
-
// lease_conflict is concurrent-access conflict, not a state change.
|
|
471
|
-
// No mutation methods (markTaskFailed/markTaskRetryWait) are called.
|
|
472
|
-
if (classified.category === 'lease_conflict') {
|
|
473
|
-
this.emitDreamerEvent('dreamer_run_failed', taskId, {
|
|
474
|
-
errorCategory: 'lease_conflict',
|
|
475
|
-
errorMessage: classified.message,
|
|
476
|
-
});
|
|
477
|
-
return {
|
|
478
|
-
status: 'failed',
|
|
479
|
-
taskId,
|
|
480
|
-
errorCategory: 'lease_conflict',
|
|
481
|
-
failureReason: classified.message,
|
|
482
|
-
attemptCount: 1,
|
|
483
|
-
};
|
|
484
|
-
}
|
|
485
|
-
// Non-lease errors (e.g., storage_unavailable before lease) must not
|
|
486
|
-
// use synthetic TaskRecord. Build one with real defaults from options.
|
|
487
|
-
this.emitDreamerEvent('dreamer_run_failed', taskId, {
|
|
488
|
-
errorCategory: classified.category,
|
|
489
|
-
errorMessage: classified.message,
|
|
490
|
-
});
|
|
491
|
-
const task = {
|
|
492
|
-
taskId,
|
|
493
|
-
taskKind: 'dreamer',
|
|
494
|
-
status: 'leased',
|
|
495
|
-
createdAt: new Date().toISOString(),
|
|
496
|
-
updatedAt: new Date().toISOString(),
|
|
497
|
-
attemptCount: 1,
|
|
498
|
-
maxAttempts: this.resolvedOptions.defaultMaxAttempts,
|
|
499
|
-
};
|
|
500
|
-
return this.retryOrFail({ taskId, task, errorCategory: classified.category, failureReason: classified.message });
|
|
501
|
-
}
|
|
502
|
-
async handlePostLeaseError(taskId, task, error) {
|
|
503
|
-
const classified = this.classifyError(error);
|
|
504
|
-
this.emitDreamerEvent('dreamer_run_failed', taskId, {
|
|
505
|
-
errorCategory: classified.category,
|
|
506
|
-
errorMessage: classified.message,
|
|
507
|
-
});
|
|
508
|
-
return this.retryOrFail({ taskId, task, errorCategory: classified.category, failureReason: classified.message });
|
|
509
|
-
}
|
|
510
|
-
async retryOrFail(ctx) {
|
|
511
|
-
// Check if error is permanent (never retry)
|
|
512
|
-
if (this.isPermanentError(ctx.errorCategory)) {
|
|
513
|
-
try {
|
|
514
|
-
await this.stateManager.markTaskFailed(ctx.taskId, ctx.errorCategory);
|
|
515
|
-
}
|
|
516
|
-
catch (stateErr) {
|
|
517
|
-
this.emitDreamerEvent('dreamer_mark_failed_error', ctx.taskId, {
|
|
518
|
-
errorCategory: 'storage_unavailable',
|
|
519
|
-
attemptCount: ctx.task.attemptCount,
|
|
520
|
-
errorMessage: stateErr instanceof Error ? stateErr.message : String(stateErr),
|
|
521
|
-
});
|
|
522
|
-
return {
|
|
523
|
-
status: 'failed',
|
|
524
|
-
taskId: ctx.taskId,
|
|
525
|
-
errorCategory: 'storage_unavailable',
|
|
526
|
-
failureReason: `State manager error: ${ctx.failureReason}`,
|
|
527
|
-
attemptCount: ctx.task.attemptCount,
|
|
528
|
-
};
|
|
529
|
-
}
|
|
530
|
-
this.emitDreamerEvent('dreamer_task_failed', ctx.taskId, {
|
|
531
|
-
errorCategory: ctx.errorCategory,
|
|
532
|
-
attemptCount: ctx.task.attemptCount,
|
|
533
|
-
failureReason: ctx.failureReason,
|
|
534
|
-
});
|
|
535
|
-
this.phase = RunnerPhase.Failed;
|
|
536
|
-
return {
|
|
537
|
-
status: 'failed',
|
|
538
|
-
taskId: ctx.taskId,
|
|
539
|
-
errorCategory: ctx.errorCategory,
|
|
540
|
-
failureReason: ctx.failureReason,
|
|
541
|
-
attemptCount: ctx.task.attemptCount,
|
|
542
|
-
};
|
|
543
|
-
}
|
|
544
|
-
// Check retry policy
|
|
545
|
-
const shouldRetry = this.stateManager.getRetryPolicy().shouldRetry(ctx.task);
|
|
546
|
-
if (shouldRetry) {
|
|
547
|
-
try {
|
|
548
|
-
await this.stateManager.markTaskRetryWait(ctx.taskId, ctx.errorCategory);
|
|
549
|
-
}
|
|
550
|
-
catch (stateErr) {
|
|
551
|
-
this.emitDreamerEvent('dreamer_mark_retry_error', ctx.taskId, {
|
|
552
|
-
errorCategory: 'storage_unavailable',
|
|
553
|
-
attemptCount: ctx.task.attemptCount,
|
|
554
|
-
errorMessage: stateErr instanceof Error ? stateErr.message : String(stateErr),
|
|
555
|
-
});
|
|
556
|
-
return {
|
|
557
|
-
status: 'failed',
|
|
558
|
-
taskId: ctx.taskId,
|
|
559
|
-
errorCategory: 'storage_unavailable',
|
|
560
|
-
failureReason: `State manager error: ${ctx.failureReason}`,
|
|
561
|
-
attemptCount: ctx.task.attemptCount,
|
|
562
|
-
};
|
|
563
|
-
}
|
|
564
|
-
this.emitDreamerEvent('dreamer_task_retried', ctx.taskId, {
|
|
565
|
-
errorCategory: ctx.errorCategory,
|
|
566
|
-
attemptCount: ctx.task.attemptCount,
|
|
567
|
-
});
|
|
568
|
-
this.phase = RunnerPhase.RetryWaiting;
|
|
569
|
-
return {
|
|
570
|
-
status: 'retried',
|
|
571
|
-
taskId: ctx.taskId,
|
|
572
|
-
errorCategory: ctx.errorCategory,
|
|
573
|
-
failureReason: ctx.failureReason,
|
|
574
|
-
attemptCount: ctx.task.attemptCount,
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
// Max attempts exceeded
|
|
578
|
-
try {
|
|
579
|
-
await this.stateManager.markTaskFailed(ctx.taskId, 'max_attempts_exceeded');
|
|
580
|
-
}
|
|
581
|
-
catch (stateErr) {
|
|
582
|
-
this.emitDreamerEvent('dreamer_mark_failed_error', ctx.taskId, {
|
|
583
|
-
errorCategory: 'storage_unavailable',
|
|
584
|
-
attemptCount: ctx.task.attemptCount,
|
|
585
|
-
errorMessage: stateErr instanceof Error ? stateErr.message : String(stateErr),
|
|
586
|
-
});
|
|
587
|
-
return {
|
|
588
|
-
status: 'failed',
|
|
589
|
-
taskId: ctx.taskId,
|
|
590
|
-
errorCategory: 'storage_unavailable',
|
|
591
|
-
failureReason: `State manager error: ${ctx.failureReason}`,
|
|
592
|
-
attemptCount: ctx.task.attemptCount,
|
|
593
|
-
};
|
|
594
|
-
}
|
|
595
|
-
this.emitDreamerEvent('dreamer_task_failed', ctx.taskId, {
|
|
596
|
-
errorCategory: 'max_attempts_exceeded',
|
|
597
|
-
attemptCount: ctx.task.attemptCount,
|
|
598
|
-
failureReason: `Max attempts exceeded: ${ctx.failureReason}`,
|
|
599
|
-
});
|
|
600
|
-
this.phase = RunnerPhase.Failed;
|
|
601
|
-
return {
|
|
602
|
-
status: 'failed',
|
|
603
|
-
taskId: ctx.taskId,
|
|
604
|
-
errorCategory: 'max_attempts_exceeded',
|
|
605
|
-
failureReason: `Max attempts exceeded: ${ctx.failureReason}`,
|
|
606
|
-
attemptCount: ctx.task.attemptCount,
|
|
199
|
+
contextHash,
|
|
200
|
+
output,
|
|
201
|
+
attemptCount: task.attemptCount,
|
|
607
202
|
};
|
|
608
203
|
}
|
|
609
|
-
// ──
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
204
|
+
// ── Optional hooks ─────────────────────────────────────────────────────────
|
|
205
|
+
/**
|
|
206
|
+
* Re-inject taskId if stripped by stripLineageFields (PRI-272 / ERR-008).
|
|
207
|
+
* Only fill when absent via Object.hasOwn — present-but-falsy values
|
|
208
|
+
* must reach validation and fail loud (Runtime Contract Rule 3).
|
|
209
|
+
*/
|
|
614
210
|
// eslint-disable-next-line @typescript-eslint/class-methods-use-this
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
return { category: error.category, message: error.message };
|
|
618
|
-
}
|
|
619
|
-
if (error instanceof Error) {
|
|
620
|
-
return { category: 'execution_failed', message: error.message };
|
|
621
|
-
}
|
|
622
|
-
return { category: 'execution_failed', message: String(error) };
|
|
211
|
+
postFetchTransform(taskId, untrustedOutput) {
|
|
212
|
+
injectRunnerLineageIfAbsent(untrustedOutput, 'taskId', taskId);
|
|
623
213
|
}
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
214
|
+
emitSuccessTelemetry(taskId, output) {
|
|
215
|
+
for (const candidate of output.candidates) {
|
|
216
|
+
this.emitEvent('candidate_generated', taskId, {
|
|
217
|
+
candidateIndex: candidate.candidateIndex,
|
|
218
|
+
confidence: candidate.confidence,
|
|
219
|
+
riskLevel: candidate.riskLevel,
|
|
220
|
+
});
|
|
631
221
|
}
|
|
632
222
|
}
|
|
633
|
-
// eslint-disable-next-line @typescript-eslint/class-methods-use-this
|
|
634
|
-
sleep(ms) {
|
|
635
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
636
|
-
}
|
|
637
223
|
}
|
|
638
|
-
// ── Exports ───────────────────────────────────────────────────────────────────
|
|
639
|
-
export { resolveDreamerRunnerOptions, DEFAULT_DREAMER_RUNNER_OPTIONS };
|
|
640
224
|
//# sourceMappingURL=dreamer-runner.js.map
|