@principles/core 1.84.0 → 1.86.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/__tests__/artificer-runner-vslice.test.js +52 -0
- package/dist/runtime-v2/__tests__/artificer-runner-vslice.test.js.map +1 -1
- package/dist/runtime-v2/__tests__/telemetry-event.test.d.ts +2 -0
- package/dist/runtime-v2/__tests__/telemetry-event.test.d.ts.map +1 -0
- package/dist/runtime-v2/__tests__/telemetry-event.test.js +56 -0
- package/dist/runtime-v2/__tests__/telemetry-event.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/philosopher-runner-trust-boundary.test.d.ts +2 -0
- package/dist/runtime-v2/internalization/__tests__/philosopher-runner-trust-boundary.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/philosopher-runner-trust-boundary.test.js +398 -0
- package/dist/runtime-v2/internalization/__tests__/philosopher-runner-trust-boundary.test.js.map +1 -0
- package/dist/runtime-v2/internalization/artificer-output.d.ts +3 -2
- package/dist/runtime-v2/internalization/artificer-output.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/artificer-output.js +40 -31
- package/dist/runtime-v2/internalization/artificer-output.js.map +1 -1
- package/dist/runtime-v2/internalization/artificer-runner.d.ts +59 -38
- package/dist/runtime-v2/internalization/artificer-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/artificer-runner.js +121 -395
- package/dist/runtime-v2/internalization/artificer-runner.js.map +1 -1
- package/dist/runtime-v2/internalization/philosopher-output.d.ts +8 -2
- package/dist/runtime-v2/internalization/philosopher-output.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/philosopher-output.js +16 -11
- package/dist/runtime-v2/internalization/philosopher-output.js.map +1 -1
- package/dist/runtime-v2/internalization/philosopher-runner.d.ts +36 -55
- package/dist/runtime-v2/internalization/philosopher-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/philosopher-runner.js +117 -401
- package/dist/runtime-v2/internalization/philosopher-runner.js.map +1 -1
- package/dist/runtime-v2/runner/__tests__/base-peer-runner-trust-boundary.test.js +22 -0
- package/dist/runtime-v2/runner/__tests__/base-peer-runner-trust-boundary.test.js.map +1 -1
- package/dist/runtime-v2/runner/base-peer-runner.d.ts.map +1 -1
- package/dist/runtime-v2/runner/base-peer-runner.js +5 -1
- package/dist/runtime-v2/runner/base-peer-runner.js.map +1 -1
- package/dist/telemetry-event.d.ts +2 -2
- package/dist/telemetry-event.d.ts.map +1 -1
- package/dist/telemetry-event.js +88 -0
- package/dist/telemetry-event.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { PDRuntimeError } from '../error-categories.js';
|
|
1
|
+
import { PDRuntimeError, isPDErrorCategory } from '../error-categories.js';
|
|
2
2
|
import { hydratePITaskRecord } from './pitask-metadata.js';
|
|
3
|
-
import { RunnerPhase } from '../runner/runner-phase.js';
|
|
4
3
|
import { PhilosopherPromptBuilder } from './philosopher-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_PHILOSOPHER_RUNNER_OPTIONS = {
|
|
7
7
|
pollIntervalMs: 5_000,
|
|
8
8
|
timeoutMs: 300_000,
|
|
9
9
|
defaultMaxAttempts: 3,
|
|
10
10
|
agentId: 'philosopher',
|
|
11
11
|
};
|
|
12
|
-
function resolvePhilosopherRunnerOptions(options) {
|
|
12
|
+
export function resolvePhilosopherRunnerOptions(options) {
|
|
13
13
|
return {
|
|
14
14
|
pollIntervalMs: options.pollIntervalMs ?? DEFAULT_PHILOSOPHER_RUNNER_OPTIONS.pollIntervalMs,
|
|
15
15
|
timeoutMs: options.timeoutMs ?? DEFAULT_PHILOSOPHER_RUNNER_OPTIONS.timeoutMs,
|
|
@@ -19,120 +19,23 @@ function resolvePhilosopherRunnerOptions(options) {
|
|
|
19
19
|
agentId: options.agentId ?? DEFAULT_PHILOSOPHER_RUNNER_OPTIONS.agentId,
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
|
-
// ── PhilosopherRunner
|
|
23
|
-
export class PhilosopherRunner {
|
|
24
|
-
phase = RunnerPhase.Idle;
|
|
25
|
-
resolvedOptions;
|
|
26
|
-
stateManager;
|
|
27
|
-
runtimeAdapter;
|
|
28
|
-
eventEmitter;
|
|
22
|
+
// ── PhilosopherRunner ────────────────────────────────────────────────────────
|
|
23
|
+
export class PhilosopherRunner extends BasePeerRunner {
|
|
29
24
|
validator;
|
|
30
|
-
artifactStore;
|
|
31
25
|
constructor(deps, options) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
this.resolvedOptions = resolvePhilosopherRunnerOptions(options);
|
|
38
|
-
}
|
|
39
|
-
get currentPhase() {
|
|
40
|
-
return this.phase;
|
|
41
|
-
}
|
|
42
|
-
emitPhilosopherEvent(eventType, taskId, payload) {
|
|
43
|
-
this.eventEmitter.emitTelemetry({
|
|
44
|
-
eventType: eventType,
|
|
45
|
-
traceId: taskId,
|
|
46
|
-
timestamp: new Date().toISOString(),
|
|
47
|
-
sessionId: this.resolvedOptions.owner,
|
|
48
|
-
agentId: this.resolvedOptions.agentId,
|
|
49
|
-
payload,
|
|
26
|
+
super(deps, options, {
|
|
27
|
+
runnerName: 'philosopher',
|
|
28
|
+
expectedTaskKind: 'philosopher',
|
|
29
|
+
defaultAgentId: 'philosopher',
|
|
30
|
+
resultRefPrefix: 'philosopher',
|
|
50
31
|
});
|
|
32
|
+
this.validator = deps.validator;
|
|
51
33
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
leasedTask = await this.stateManager.acquireLease({
|
|
57
|
-
taskId,
|
|
58
|
-
owner: this.resolvedOptions.owner,
|
|
59
|
-
runtimeKind: this.resolvedOptions.runtimeKind,
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
catch (error) {
|
|
63
|
-
return await this.handleLeaseOrPhaseError(taskId, error);
|
|
64
|
-
}
|
|
65
|
-
if (leasedTask.taskKind !== 'philosopher') {
|
|
66
|
-
this.emitPhilosopherEvent('philosopher_wrong_task_kind', taskId, {
|
|
67
|
-
expectedKind: 'philosopher',
|
|
68
|
-
actualKind: leasedTask.taskKind,
|
|
69
|
-
});
|
|
70
|
-
await this.stateManager.markTaskFailed(taskId, 'input_invalid');
|
|
71
|
-
return {
|
|
72
|
-
status: 'failed',
|
|
73
|
-
taskId,
|
|
74
|
-
errorCategory: 'input_invalid',
|
|
75
|
-
failureReason: `Task kind must be 'philosopher', got '${leasedTask.taskKind}'`,
|
|
76
|
-
attemptCount: leasedTask.attemptCount,
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
this.emitPhilosopherEvent('philosopher_task_leased', taskId, {
|
|
80
|
-
taskKind: 'philosopher',
|
|
81
|
-
attemptCount: leasedTask.attemptCount,
|
|
82
|
-
});
|
|
83
|
-
try {
|
|
84
|
-
const storeRunId = await this.resolveStoreRunId(taskId);
|
|
85
|
-
this.phase = RunnerPhase.BuildingContext;
|
|
86
|
-
const { contextHash, dreamerArtifact, sourceDreamerArtifactId } = await this.buildContext(taskId);
|
|
87
|
-
if (!dreamerArtifact) {
|
|
88
|
-
return this.retryOrFail({
|
|
89
|
-
taskId,
|
|
90
|
-
task: leasedTask,
|
|
91
|
-
errorCategory: 'input_invalid',
|
|
92
|
-
failureReason: 'Dreamer dependency artifact not found',
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
this.emitPhilosopherEvent('philosopher_context_built', taskId, { contextHash });
|
|
96
|
-
this.phase = RunnerPhase.Invoking;
|
|
97
|
-
const runHandle = await this.invokeRuntime({ taskId, contextHash, dreamerArtifact, sourceDreamerArtifactId });
|
|
98
|
-
this.emitPhilosopherEvent('philosopher_run_started', taskId, {
|
|
99
|
-
runtimeKind: this.resolvedOptions.runtimeKind,
|
|
100
|
-
});
|
|
101
|
-
this.phase = RunnerPhase.Polling;
|
|
102
|
-
const finalStatus = await this.pollUntilTerminal(runHandle);
|
|
103
|
-
if (finalStatus.status !== 'succeeded') {
|
|
104
|
-
return await this.handleRuntimeFailure(taskId, leasedTask, finalStatus);
|
|
105
|
-
}
|
|
106
|
-
this.phase = RunnerPhase.FetchingOutput;
|
|
107
|
-
const output = await this.fetchAndParseOutput(runHandle.runId);
|
|
108
|
-
// Re-inject taskId if stripped by stripLineageFields (PRI-272 / ERR-008).
|
|
109
|
-
injectRunnerLineageIfAbsent(output, 'taskId', taskId);
|
|
110
|
-
this.phase = RunnerPhase.Validating;
|
|
111
|
-
const validationResult = await this.validator.validate(output, taskId);
|
|
112
|
-
if (!validationResult.valid) {
|
|
113
|
-
return await this.handleValidationError({
|
|
114
|
-
taskId,
|
|
115
|
-
task: leasedTask,
|
|
116
|
-
errors: validationResult.errors,
|
|
117
|
-
errorCategory: validationResult.errorCategory,
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
this.emitPhilosopherEvent('philosopher_output_validated', taskId, {
|
|
121
|
-
principleTitle: output.principleCandidate.title,
|
|
122
|
-
});
|
|
123
|
-
return await this.succeedTask({
|
|
124
|
-
taskId,
|
|
125
|
-
runId: storeRunId,
|
|
126
|
-
output,
|
|
127
|
-
task: leasedTask,
|
|
128
|
-
contextHash,
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
catch (error) {
|
|
132
|
-
return await this.handlePostLeaseError(taskId, leasedTask, error);
|
|
133
|
-
}
|
|
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']);
|
|
134
38
|
}
|
|
135
|
-
// ── Phase methods ─────────────────────────────────────────────────────────
|
|
136
39
|
async buildContext(taskId) {
|
|
137
40
|
const task = await this.stateManager.getTask(taskId);
|
|
138
41
|
if (!task) {
|
|
@@ -141,15 +44,14 @@ export class PhilosopherRunner {
|
|
|
141
44
|
const piTask = hydratePITaskRecord(task);
|
|
142
45
|
const deps = piTask?.dependencyTaskIds ?? [];
|
|
143
46
|
if (deps.length === 0) {
|
|
144
|
-
|
|
145
|
-
return { contextHash: 'empty', dreamerArtifact: null, sourceDreamerArtifactId: null };
|
|
47
|
+
throw new PDRuntimeError('input_invalid', 'Dreamer dependency artifact not found');
|
|
146
48
|
}
|
|
147
49
|
for (const depId of deps) {
|
|
148
50
|
const depTask = await this.stateManager.getTask(depId);
|
|
149
51
|
if (!depTask)
|
|
150
52
|
continue;
|
|
151
53
|
if (depTask.status !== 'succeeded') {
|
|
152
|
-
this.
|
|
54
|
+
this.emitEvent('dependency_not_succeeded', taskId, {
|
|
153
55
|
depTaskId: depId,
|
|
154
56
|
depStatus: depTask.status,
|
|
155
57
|
});
|
|
@@ -163,359 +65,173 @@ export class PhilosopherRunner {
|
|
|
163
65
|
continue;
|
|
164
66
|
const artifactRef = depPiTask?.outputArtifactRefs?.[0]?.ref ?? `pi-artifact://${depId}`;
|
|
165
67
|
return {
|
|
166
|
-
contextHash:
|
|
68
|
+
contextHash: BasePeerRunner.hashContextRefs([artifactRef]),
|
|
167
69
|
dreamerArtifact: firstArtifact.contentJson,
|
|
168
70
|
sourceDreamerArtifactId: firstArtifact.artifactId,
|
|
169
71
|
};
|
|
170
72
|
}
|
|
171
73
|
}
|
|
172
|
-
|
|
173
|
-
return { contextHash: 'empty', dreamerArtifact: null, sourceDreamerArtifactId: null };
|
|
74
|
+
throw new PDRuntimeError('input_invalid', 'Dreamer dependency artifact not found');
|
|
174
75
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
let hash = 0;
|
|
180
|
-
for (let i = 0; i < str.length; i++) {
|
|
181
|
-
hash = (Math.imul(31, hash) + str.charCodeAt(i)) | 0;
|
|
182
|
-
}
|
|
183
|
-
return `ctx-${Math.abs(hash).toString(16)}`;
|
|
184
|
-
}
|
|
185
|
-
async resolveStoreRunId(taskId) {
|
|
186
|
-
const runs = await this.stateManager.getRunsByTask(taskId);
|
|
187
|
-
const latestRun = runs[runs.length - 1];
|
|
188
|
-
if (!latestRun) {
|
|
189
|
-
throw new PDRuntimeError('execution_failed', `No run records found for task ${taskId} after lease acquisition`);
|
|
76
|
+
async invokeRuntime(taskId, context) {
|
|
77
|
+
let parsedDreamerArtifact;
|
|
78
|
+
try {
|
|
79
|
+
parsedDreamerArtifact = JSON.parse(context.dreamerArtifact);
|
|
190
80
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
async invokeRuntime(params) {
|
|
194
|
-
let parsedDreamerArtifact = null;
|
|
195
|
-
if (params.dreamerArtifact) {
|
|
196
|
-
try {
|
|
197
|
-
parsedDreamerArtifact = JSON.parse(params.dreamerArtifact);
|
|
198
|
-
}
|
|
199
|
-
catch {
|
|
200
|
-
parsedDreamerArtifact = params.dreamerArtifact;
|
|
201
|
-
}
|
|
81
|
+
catch {
|
|
82
|
+
parsedDreamerArtifact = context.dreamerArtifact;
|
|
202
83
|
}
|
|
203
84
|
const builder = new PhilosopherPromptBuilder();
|
|
204
85
|
const { message } = builder.buildPrompt({
|
|
205
|
-
taskId
|
|
206
|
-
contextHash:
|
|
86
|
+
taskId,
|
|
87
|
+
contextHash: context.contextHash,
|
|
207
88
|
dreamerArtifact: parsedDreamerArtifact,
|
|
208
|
-
sourceDreamerArtifactId:
|
|
89
|
+
sourceDreamerArtifactId: context.sourceDreamerArtifactId,
|
|
209
90
|
});
|
|
210
|
-
|
|
91
|
+
return this.runtimeAdapter.startRun({
|
|
211
92
|
agentSpec: { agentId: this.resolvedOptions.agentId, schemaVersion: 'v1' },
|
|
212
|
-
taskRef: { taskId
|
|
93
|
+
taskRef: { taskId },
|
|
213
94
|
inputPayload: message,
|
|
214
95
|
contextItems: [],
|
|
215
96
|
outputSchemaRef: 'philosopher-output-v1',
|
|
216
97
|
timeoutMs: this.resolvedOptions.timeoutMs,
|
|
217
|
-
};
|
|
218
|
-
return this.runtimeAdapter.startRun(startInput);
|
|
98
|
+
});
|
|
219
99
|
}
|
|
220
|
-
async
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
await this.sleep(this.resolvedOptions.pollIntervalMs);
|
|
100
|
+
async validateOutput(output, taskId) {
|
|
101
|
+
const result = await this.validator.validate(output, taskId);
|
|
102
|
+
// Trust-boundary: validator is an injected dependency returning `string | undefined`
|
|
103
|
+
// for errorCategory. We must not `as`-cast; validate at runtime (ERR-001, ERR-005).
|
|
104
|
+
const rawCategory = result.errorCategory;
|
|
105
|
+
let errorCategory;
|
|
106
|
+
if (rawCategory == null) {
|
|
107
|
+
errorCategory = undefined;
|
|
229
108
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
await this.runtimeAdapter.cancelRun(runHandle.runId);
|
|
109
|
+
else if (isPDErrorCategory(rawCategory)) {
|
|
110
|
+
errorCategory = rawCategory;
|
|
233
111
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
112
|
+
else {
|
|
113
|
+
// Invalid errorCategory from validator — fail loud, do not pass through
|
|
114
|
+
return {
|
|
115
|
+
valid: false,
|
|
116
|
+
errors: [...result.errors, `invalid errorCategory: ${rawCategory}`],
|
|
117
|
+
errorCategory: 'output_invalid',
|
|
118
|
+
};
|
|
240
119
|
}
|
|
241
|
-
|
|
242
|
-
|
|
120
|
+
return {
|
|
121
|
+
valid: result.valid,
|
|
122
|
+
errors: result.errors,
|
|
123
|
+
errorCategory,
|
|
124
|
+
};
|
|
243
125
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
126
|
+
// eslint-disable-next-line @typescript-eslint/max-params
|
|
127
|
+
async succeedTask(taskId, runId, output, task, contextHash, context) {
|
|
128
|
+
// Lineage consistency: sourceDreamerArtifactId must match buildContext result (ERR-004).
|
|
129
|
+
if (output.sourceDreamerArtifactId !== context.sourceDreamerArtifactId) {
|
|
130
|
+
throw new PDRuntimeError('output_invalid', `sourceDreamerArtifactId mismatch: expected ${context.sourceDreamerArtifactId}, got ${output.sourceDreamerArtifactId}`);
|
|
248
131
|
}
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
async succeedTask(ctx) {
|
|
132
|
+
// Store output before marking succeeded
|
|
252
133
|
try {
|
|
253
|
-
await this.stateManager.updateRunOutput(
|
|
134
|
+
await this.stateManager.updateRunOutput(runId, JSON.stringify(output));
|
|
254
135
|
}
|
|
255
136
|
catch (updateErr) {
|
|
256
|
-
this.
|
|
257
|
-
runId
|
|
137
|
+
this.emitEvent('update_output_failed', taskId, {
|
|
138
|
+
runId,
|
|
258
139
|
errorMessage: updateErr instanceof Error ? updateErr.message : String(updateErr),
|
|
259
140
|
});
|
|
260
141
|
throw updateErr;
|
|
261
142
|
}
|
|
262
|
-
|
|
263
|
-
const now = new Date().toISOString();
|
|
143
|
+
// Resolve lineage artifact IDs
|
|
264
144
|
let lineageArtifactIds = [];
|
|
145
|
+
let lineageHasRejected = false;
|
|
265
146
|
try {
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
147
|
+
const lineageResult = await this.resolveLineageArtifactIds(taskId);
|
|
148
|
+
lineageArtifactIds = lineageResult.ids;
|
|
149
|
+
lineageHasRejected = lineageResult.hasRejected;
|
|
150
|
+
}
|
|
151
|
+
catch (lineageErr) {
|
|
152
|
+
this.emitEvent('lineage_resolve_failed', taskId, {
|
|
153
|
+
runId,
|
|
154
|
+
errorMessage: lineageErr instanceof Error ? lineageErr.message : String(lineageErr),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
if (lineageHasRejected) {
|
|
158
|
+
this.emitEvent('lineage_partial', taskId, {
|
|
159
|
+
runId,
|
|
160
|
+
resolvedCount: lineageArtifactIds.length,
|
|
161
|
+
warning: 'Some dependency artifact queries were rejected; lineage may be incomplete',
|
|
162
|
+
});
|
|
276
163
|
}
|
|
277
|
-
|
|
164
|
+
// Write PIArtifact via artifactStore (idempotent upsert)
|
|
165
|
+
const artifactId = `pi-art-${taskId}-${runId}`;
|
|
166
|
+
const now = new Date().toISOString();
|
|
278
167
|
try {
|
|
279
168
|
await this.artifactStore.upsertArtifact({
|
|
280
169
|
artifactId,
|
|
281
170
|
artifactKind: 'principle',
|
|
282
|
-
sourceTaskId:
|
|
171
|
+
sourceTaskId: taskId,
|
|
283
172
|
lineageArtifactIds,
|
|
284
173
|
validationStatus: 'pending',
|
|
285
|
-
contentJson: JSON.stringify(
|
|
174
|
+
contentJson: JSON.stringify(output),
|
|
286
175
|
createdAt: now,
|
|
287
176
|
updatedAt: now,
|
|
288
177
|
});
|
|
289
178
|
}
|
|
290
179
|
catch (artifactErr) {
|
|
291
|
-
this.
|
|
292
|
-
runId
|
|
180
|
+
this.emitEvent('artifact_write_failed', taskId, {
|
|
181
|
+
runId,
|
|
293
182
|
errorMessage: artifactErr instanceof Error ? artifactErr.message : String(artifactErr),
|
|
294
183
|
});
|
|
295
184
|
return this.retryOrFail({
|
|
296
|
-
taskId
|
|
297
|
-
task
|
|
185
|
+
taskId,
|
|
186
|
+
task,
|
|
298
187
|
errorCategory: 'artifact_commit_failed',
|
|
299
188
|
failureReason: `PIArtifact write failed: ${artifactErr instanceof Error ? artifactErr.message : String(artifactErr)}`,
|
|
300
189
|
});
|
|
301
190
|
}
|
|
302
|
-
|
|
191
|
+
// Mark task succeeded
|
|
192
|
+
const resultRef = `${this.config.resultRefPrefix}://${runId}`;
|
|
303
193
|
try {
|
|
304
|
-
await this.stateManager.markTaskSucceeded(
|
|
194
|
+
await this.stateManager.markTaskSucceeded(taskId, resultRef);
|
|
305
195
|
}
|
|
306
196
|
catch (stateErr) {
|
|
307
|
-
this.
|
|
308
|
-
taskId
|
|
309
|
-
runId
|
|
197
|
+
this.emitEvent('mark_succeeded_failed', taskId, {
|
|
198
|
+
taskId,
|
|
199
|
+
runId,
|
|
310
200
|
errorMessage: stateErr instanceof Error ? stateErr.message : String(stateErr),
|
|
311
201
|
});
|
|
312
202
|
throw stateErr;
|
|
313
203
|
}
|
|
314
|
-
this.
|
|
315
|
-
attemptCount:
|
|
204
|
+
this.emitEvent('task_succeeded', taskId, {
|
|
205
|
+
attemptCount: task.attemptCount,
|
|
316
206
|
resultRef,
|
|
317
|
-
principleTitle:
|
|
207
|
+
principleTitle: output.principleCandidate.title,
|
|
318
208
|
});
|
|
319
|
-
this.phase = RunnerPhase.Completed;
|
|
320
209
|
return {
|
|
321
210
|
status: 'succeeded',
|
|
322
|
-
taskId
|
|
323
|
-
runId
|
|
211
|
+
taskId,
|
|
212
|
+
runId,
|
|
324
213
|
artifactId,
|
|
325
214
|
resultRef,
|
|
326
|
-
contextHash
|
|
327
|
-
output
|
|
328
|
-
attemptCount:
|
|
215
|
+
contextHash,
|
|
216
|
+
output,
|
|
217
|
+
attemptCount: task.attemptCount,
|
|
329
218
|
};
|
|
330
219
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
return this.retryOrFail({
|
|
338
|
-
taskId,
|
|
339
|
-
task,
|
|
340
|
-
errorCategory,
|
|
341
|
-
failureReason: `Runtime execution ended with status: ${runStatus.status}`,
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
async handleValidationError(ctx) {
|
|
345
|
-
const category = (ctx.errorCategory ?? 'output_invalid');
|
|
346
|
-
this.emitPhilosopherEvent('philosopher_output_invalid', ctx.taskId, {
|
|
347
|
-
errorCount: ctx.errors.length,
|
|
348
|
-
errorCategory: category,
|
|
349
|
-
});
|
|
350
|
-
return this.retryOrFail({
|
|
351
|
-
taskId: ctx.taskId,
|
|
352
|
-
task: ctx.task,
|
|
353
|
-
errorCategory: category,
|
|
354
|
-
failureReason: `Validation failed: ${ctx.errors.join('; ')}`,
|
|
355
|
-
});
|
|
356
|
-
}
|
|
357
|
-
async handleLeaseOrPhaseError(taskId, error) {
|
|
358
|
-
const classified = this.classifyError(error);
|
|
359
|
-
if (classified.category === 'lease_conflict') {
|
|
360
|
-
this.emitPhilosopherEvent('philosopher_run_failed', taskId, {
|
|
361
|
-
errorCategory: 'lease_conflict',
|
|
362
|
-
errorMessage: classified.message,
|
|
363
|
-
});
|
|
364
|
-
return {
|
|
365
|
-
status: 'failed',
|
|
366
|
-
taskId,
|
|
367
|
-
errorCategory: 'lease_conflict',
|
|
368
|
-
failureReason: classified.message,
|
|
369
|
-
attemptCount: 1,
|
|
370
|
-
};
|
|
371
|
-
}
|
|
372
|
-
this.emitPhilosopherEvent('philosopher_run_failed', taskId, {
|
|
373
|
-
errorCategory: classified.category,
|
|
374
|
-
errorMessage: classified.message,
|
|
375
|
-
});
|
|
376
|
-
const task = {
|
|
377
|
-
taskId,
|
|
378
|
-
taskKind: 'philosopher',
|
|
379
|
-
status: 'leased',
|
|
380
|
-
createdAt: new Date().toISOString(),
|
|
381
|
-
updatedAt: new Date().toISOString(),
|
|
382
|
-
attemptCount: 1,
|
|
383
|
-
maxAttempts: this.resolvedOptions.defaultMaxAttempts,
|
|
384
|
-
};
|
|
385
|
-
return this.retryOrFail({ taskId, task, errorCategory: classified.category, failureReason: classified.message });
|
|
386
|
-
}
|
|
387
|
-
async handlePostLeaseError(taskId, task, error) {
|
|
388
|
-
const classified = this.classifyError(error);
|
|
389
|
-
this.emitPhilosopherEvent('philosopher_run_failed', taskId, {
|
|
390
|
-
errorCategory: classified.category,
|
|
391
|
-
errorMessage: classified.message,
|
|
392
|
-
});
|
|
393
|
-
return this.retryOrFail({ taskId, task, errorCategory: classified.category, failureReason: classified.message });
|
|
394
|
-
}
|
|
395
|
-
async retryOrFail(ctx) {
|
|
396
|
-
if (this.isPermanentError(ctx.errorCategory)) {
|
|
397
|
-
try {
|
|
398
|
-
await this.stateManager.markTaskFailed(ctx.taskId, ctx.errorCategory);
|
|
399
|
-
}
|
|
400
|
-
catch (stateErr) {
|
|
401
|
-
this.emitPhilosopherEvent('philosopher_mark_failed_error', ctx.taskId, {
|
|
402
|
-
errorCategory: 'storage_unavailable',
|
|
403
|
-
attemptCount: ctx.task.attemptCount,
|
|
404
|
-
errorMessage: stateErr instanceof Error ? stateErr.message : String(stateErr),
|
|
405
|
-
});
|
|
406
|
-
return {
|
|
407
|
-
status: 'failed',
|
|
408
|
-
taskId: ctx.taskId,
|
|
409
|
-
errorCategory: 'storage_unavailable',
|
|
410
|
-
failureReason: `State manager error: ${ctx.failureReason}`,
|
|
411
|
-
attemptCount: ctx.task.attemptCount,
|
|
412
|
-
};
|
|
413
|
-
}
|
|
414
|
-
this.emitPhilosopherEvent('philosopher_task_failed', ctx.taskId, {
|
|
415
|
-
errorCategory: ctx.errorCategory,
|
|
416
|
-
attemptCount: ctx.task.attemptCount,
|
|
417
|
-
failureReason: ctx.failureReason,
|
|
418
|
-
});
|
|
419
|
-
this.phase = RunnerPhase.Failed;
|
|
420
|
-
return {
|
|
421
|
-
status: 'failed',
|
|
422
|
-
taskId: ctx.taskId,
|
|
423
|
-
errorCategory: ctx.errorCategory,
|
|
424
|
-
failureReason: ctx.failureReason,
|
|
425
|
-
attemptCount: ctx.task.attemptCount,
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
const shouldRetry = this.stateManager.getRetryPolicy().shouldRetry(ctx.task);
|
|
429
|
-
if (shouldRetry) {
|
|
430
|
-
try {
|
|
431
|
-
await this.stateManager.markTaskRetryWait(ctx.taskId, ctx.errorCategory);
|
|
432
|
-
}
|
|
433
|
-
catch (stateErr) {
|
|
434
|
-
this.emitPhilosopherEvent('philosopher_mark_retry_error', ctx.taskId, {
|
|
435
|
-
errorCategory: 'storage_unavailable',
|
|
436
|
-
attemptCount: ctx.task.attemptCount,
|
|
437
|
-
errorMessage: stateErr instanceof Error ? stateErr.message : String(stateErr),
|
|
438
|
-
});
|
|
439
|
-
return {
|
|
440
|
-
status: 'failed',
|
|
441
|
-
taskId: ctx.taskId,
|
|
442
|
-
errorCategory: 'storage_unavailable',
|
|
443
|
-
failureReason: `State manager error: ${ctx.failureReason}`,
|
|
444
|
-
attemptCount: ctx.task.attemptCount,
|
|
445
|
-
};
|
|
446
|
-
}
|
|
447
|
-
this.emitPhilosopherEvent('philosopher_task_retried', ctx.taskId, {
|
|
448
|
-
errorCategory: ctx.errorCategory,
|
|
449
|
-
attemptCount: ctx.task.attemptCount,
|
|
450
|
-
});
|
|
451
|
-
this.phase = RunnerPhase.RetryWaiting;
|
|
452
|
-
return {
|
|
453
|
-
status: 'retried',
|
|
454
|
-
taskId: ctx.taskId,
|
|
455
|
-
errorCategory: ctx.errorCategory,
|
|
456
|
-
failureReason: ctx.failureReason,
|
|
457
|
-
attemptCount: ctx.task.attemptCount,
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
try {
|
|
461
|
-
await this.stateManager.markTaskFailed(ctx.taskId, 'max_attempts_exceeded');
|
|
462
|
-
}
|
|
463
|
-
catch (stateErr) {
|
|
464
|
-
this.emitPhilosopherEvent('philosopher_mark_failed_error', ctx.taskId, {
|
|
465
|
-
errorCategory: 'storage_unavailable',
|
|
466
|
-
attemptCount: ctx.task.attemptCount,
|
|
467
|
-
errorMessage: stateErr instanceof Error ? stateErr.message : String(stateErr),
|
|
468
|
-
});
|
|
469
|
-
return {
|
|
470
|
-
status: 'failed',
|
|
471
|
-
taskId: ctx.taskId,
|
|
472
|
-
errorCategory: 'storage_unavailable',
|
|
473
|
-
failureReason: `State manager error: ${ctx.failureReason}`,
|
|
474
|
-
attemptCount: ctx.task.attemptCount,
|
|
475
|
-
};
|
|
476
|
-
}
|
|
477
|
-
this.emitPhilosopherEvent('philosopher_task_failed', ctx.taskId, {
|
|
478
|
-
errorCategory: 'max_attempts_exceeded',
|
|
479
|
-
attemptCount: ctx.task.attemptCount,
|
|
480
|
-
failureReason: `Max attempts exceeded: ${ctx.failureReason}`,
|
|
481
|
-
});
|
|
482
|
-
this.phase = RunnerPhase.Failed;
|
|
483
|
-
return {
|
|
484
|
-
status: 'failed',
|
|
485
|
-
taskId: ctx.taskId,
|
|
486
|
-
errorCategory: 'max_attempts_exceeded',
|
|
487
|
-
failureReason: `Max attempts exceeded: ${ctx.failureReason}`,
|
|
488
|
-
attemptCount: ctx.task.attemptCount,
|
|
489
|
-
};
|
|
490
|
-
}
|
|
491
|
-
// ── Error classification ──────────────────────────────────────────────────
|
|
492
|
-
PERMANENT_ERROR_CATEGORIES = new Set(Object.freeze(['storage_unavailable', 'workspace_invalid', 'capability_missing', 'cancelled', 'input_invalid']));
|
|
493
|
-
isPermanentError(category) {
|
|
494
|
-
return this.PERMANENT_ERROR_CATEGORIES.has(category);
|
|
495
|
-
}
|
|
220
|
+
// ── Optional hooks ─────────────────────────────────────────────────────────
|
|
221
|
+
/**
|
|
222
|
+
* Re-inject taskId if stripped by stripLineageFields (PRI-272 / ERR-008).
|
|
223
|
+
* Only fill when absent via Object.hasOwn — present-but-falsy values
|
|
224
|
+
* must reach validation and fail loud (Runtime Contract Rule 3).
|
|
225
|
+
*/
|
|
496
226
|
// eslint-disable-next-line @typescript-eslint/class-methods-use-this
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
return { category: error.category, message: error.message };
|
|
500
|
-
}
|
|
501
|
-
if (error instanceof Error) {
|
|
502
|
-
return { category: 'execution_failed', message: error.message };
|
|
503
|
-
}
|
|
504
|
-
return { category: 'execution_failed', message: String(error) };
|
|
227
|
+
postFetchTransform(taskId, untrustedOutput) {
|
|
228
|
+
injectRunnerLineageIfAbsent(untrustedOutput, 'taskId', taskId);
|
|
505
229
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
case 'cancelled': return 'cancelled';
|
|
512
|
-
default: return 'execution_failed';
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
// eslint-disable-next-line @typescript-eslint/class-methods-use-this
|
|
516
|
-
sleep(ms) {
|
|
517
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
230
|
+
emitSuccessTelemetry(taskId, output) {
|
|
231
|
+
this.emitEvent('principle_candidate_generated', taskId, {
|
|
232
|
+
principleTitle: output.principleCandidate.title,
|
|
233
|
+
confidence: output.principleCandidate.confidence,
|
|
234
|
+
});
|
|
518
235
|
}
|
|
519
236
|
}
|
|
520
|
-
export { resolvePhilosopherRunnerOptions, DEFAULT_PHILOSOPHER_RUNNER_OPTIONS };
|
|
521
237
|
//# sourceMappingURL=philosopher-runner.js.map
|