@principles/pd-cli 1.115.0 → 1.116.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.
Files changed (44) hide show
  1. package/dist/commands/diagnose.d.ts.map +1 -1
  2. package/dist/commands/diagnose.js +153 -132
  3. package/dist/commands/diagnose.js.map +1 -1
  4. package/dist/commands/runtime-features.d.ts.map +1 -1
  5. package/dist/commands/runtime-features.js +2 -7
  6. package/dist/commands/runtime-features.js.map +1 -1
  7. package/dist/commands/runtime-internalization-integrity-repair.d.ts.map +1 -1
  8. package/dist/commands/runtime-internalization-integrity-repair.js +15 -31
  9. package/dist/commands/runtime-internalization-integrity-repair.js.map +1 -1
  10. package/dist/commands/runtime-internalization-run-once.d.ts.map +1 -1
  11. package/dist/commands/runtime-internalization-run-once.js +246 -326
  12. package/dist/commands/runtime-internalization-run-once.js.map +1 -1
  13. package/dist/commands/runtime-recovery.d.ts.map +1 -1
  14. package/dist/commands/runtime-recovery.js +9 -8
  15. package/dist/commands/runtime-recovery.js.map +1 -1
  16. package/dist/services/__tests__/cli-output.test.d.ts +18 -0
  17. package/dist/services/__tests__/cli-output.test.d.ts.map +1 -0
  18. package/dist/services/__tests__/cli-output.test.js +103 -0
  19. package/dist/services/__tests__/cli-output.test.js.map +1 -0
  20. package/dist/services/__tests__/runtime-adapter-resolver.test.d.ts +18 -0
  21. package/dist/services/__tests__/runtime-adapter-resolver.test.d.ts.map +1 -0
  22. package/dist/services/__tests__/runtime-adapter-resolver.test.js +651 -0
  23. package/dist/services/__tests__/runtime-adapter-resolver.test.js.map +1 -0
  24. package/dist/services/cli-output.d.ts +61 -0
  25. package/dist/services/cli-output.d.ts.map +1 -0
  26. package/dist/services/cli-output.js +72 -0
  27. package/dist/services/cli-output.js.map +1 -0
  28. package/dist/services/runtime-adapter-resolver.d.ts +105 -0
  29. package/dist/services/runtime-adapter-resolver.d.ts.map +1 -0
  30. package/dist/services/runtime-adapter-resolver.js +188 -0
  31. package/dist/services/runtime-adapter-resolver.js.map +1 -0
  32. package/package.json +1 -1
  33. package/src/commands/diagnose.ts +146 -138
  34. package/src/commands/runtime-features.ts +2 -6
  35. package/src/commands/runtime-internalization-integrity-repair.ts +16 -28
  36. package/src/commands/runtime-internalization-run-once.ts +242 -353
  37. package/src/commands/runtime-recovery.ts +9 -7
  38. package/src/services/__tests__/cli-output.test.ts +130 -0
  39. package/src/services/__tests__/runtime-adapter-resolver.test.ts +772 -0
  40. package/src/services/cli-output.ts +95 -0
  41. package/src/services/runtime-adapter-resolver.ts +339 -0
  42. package/tests/commands/diagnose.test.ts +7 -3
  43. package/tests/commands/runtime-internalization-run-once.test.ts +11 -0
  44. package/tests/commands/runtime-recovery.test.ts +27 -4
@@ -18,19 +18,14 @@ import {
18
18
  DefaultRolloutReviewerValidator,
19
19
  DefaultTrainerValidator,
20
20
  TestDoubleRuntimeAdapter,
21
- PiAiRuntimeAdapter,
22
- OpenClawCliRuntimeAdapter,
23
- L2AgentLoopAdapter,
24
- loadLedger,
25
- isRuntimeConfigError,
26
- validateRuntimeConfig,
27
21
  } from '@principles/core/runtime-v2';
28
- import type { PdL2ArtifactReader, PdL2PrincipleReader } from '@principles/core/runtime-v2';
29
- import { loadEffectiveFeatureFlags } from '../services/feature-flag-loader.js';
30
22
  import type { WakeOnceResult, DreamerRunnerResult, PhilosopherRunnerResult, ScribeRunnerResult, ArtificerRunnerResult, EvaluatorRunnerResult, RolloutReviewerRunnerResult, TrainerRunnerResult, PDRuntimeAdapter, PeerRunnerKind, OutputLanguage } from '@principles/core/runtime-v2';
31
23
  import { resolveWorkspaceDir } from '../resolve-workspace.js';
32
24
  import { readOutputLanguageFromWorkspace } from '../config-reader.js';
33
- import { resolveRuntimeFromPdConfig } from '../services/resolve-runtime-from-pd-config.js';
25
+ import {
26
+ resolveRuntimeAdapterFromConfig,
27
+ ConfigResolutionError,
28
+ } from '../services/runtime-adapter-resolver.js';
34
29
 
35
30
  interface RunOnceOptions {
36
31
  workspace?: string;
@@ -45,13 +40,6 @@ interface RunOnceOptions {
45
40
  const OWNER = 'pd-cli-internalization-run-once';
46
41
  const RUNTIME_KIND = 'local-worker';
47
42
 
48
- class ConfigResolutionError extends Error {
49
- constructor(message: string) {
50
- super(message);
51
- this.name = 'ConfigResolutionError';
52
- }
53
- }
54
-
55
43
  const SUPPORTED_RUNNERS = new Set(['dreamer', 'philosopher', 'scribe', 'artificer', 'evaluator', 'rollout_reviewer', 'trainer']);
56
44
 
57
45
  interface RunOnceOutput {
@@ -207,275 +195,122 @@ function formatTextOutput(output: RunOnceOutput): string {
207
195
  return lines.join('\n');
208
196
  }
209
197
 
210
- interface ResolveAdapterOptions {
211
- runtimeKind: string;
212
- taskId: string;
213
- workspaceDir: string;
214
- runnerKind: string;
215
- timeoutMs?: number;
216
- /** PRI-419: stateManager for the L2 artifact reader (only used when l2_dreamer is on). */
217
- l2ArtifactReader?: PdL2ArtifactReader;
218
- /** PRI-419: workspace stateDir for the L2 principle reader (only used when l2_dreamer is on). */
219
- l2StateDir?: string;
220
- }
221
-
222
198
  /**
223
- * PRI-419: build a read-only principle reader from the workspace ledger.
224
- * Returns active internalized principles (id + statement). Degrades to empty on missing ledger.
199
+ * PRI-431: Local test-double payload builder.
200
+ * The 7 runner-specific payloads are unique to run-once.ts; the shared resolver
201
+ * calls this via `testDoublePayloadBuilder` callback.
225
202
  */
226
- function makeDreamerPrincipleReader(stateDir: string): PdL2PrincipleReader {
227
- return {
228
- listActivePrinciples: async () => {
229
- try {
230
- const ledger = loadLedger(stateDir);
231
- const principles = ledger.tree.principles ?? {};
232
- const active = Object.values(principles).filter(p => p.status === 'active' && typeof p.id === 'string' && typeof p.text === 'string');
233
- return active.map(p => ({ id: p.id, statement: p.text }));
234
- } catch (error) {
235
- // Graceful degradation WITH an observable reason (Runtime Contract R9): the L2
236
- // dreamer proceeds with only core axioms; the degradation is logged for debugging.
237
- const reason = error instanceof Error ? error.message : String(error);
238
- console.warn(`[l2_dreamer] listActivePrinciples degraded — no internalized principles loaded: ${reason}`);
239
- return [];
240
- }
241
- },
242
- };
243
- }
244
-
245
- function resolveRuntimeAdapter(opts: ResolveAdapterOptions): PDRuntimeAdapter {
246
- if (opts.runtimeKind === 'test-double') {
247
- if (opts.runnerKind === 'philosopher') {
248
- return new TestDoubleRuntimeAdapter({
249
- onPollRun: (_runId: string) => ({
250
- runId: _runId,
251
- status: 'succeeded',
252
- startedAt: new Date().toISOString(),
253
- endedAt: new Date().toISOString(),
254
- }),
255
- onFetchOutput: (_runId: string) => ({
256
- runId: _runId,
257
- payload: {
258
- taskId: opts.taskId,
259
- sourceDreamerArtifactId: 'pi-art-test-dreamer',
260
- thesis: 'Test thesis from test-double',
261
- principleCandidate: {
262
- title: 'Test Principle',
263
- rationale: 'Test rationale',
264
- scope: 'Test scope',
265
- confidence: 0.8,
266
- },
267
- risks: [],
268
- generatedAt: new Date().toISOString(),
269
- },
270
- }),
271
- });
272
- }
273
- if (opts.runnerKind === 'scribe') {
274
- return new TestDoubleRuntimeAdapter({
275
- onPollRun: (_runId: string) => ({
276
- runId: _runId,
277
- status: 'succeeded',
278
- startedAt: new Date().toISOString(),
279
- endedAt: new Date().toISOString(),
280
- }),
281
- onFetchOutput: (_runId: string) => ({
282
- runId: _runId,
283
- payload: {
284
- taskId: opts.taskId,
285
- sourcePhilosopherArtifactId: 'pi-art-test-philosopher',
286
- principleDraft: {
287
- title: 'Test Principle Draft',
288
- statement: 'Test principle statement',
289
- rationale: 'Test rationale',
290
- applicability: ['All operations'],
291
- antiPatterns: ['Ignoring validation'],
292
- confidence: 0.8,
293
- },
294
- sourceTrace: {
295
- philosopherArtifactId: 'pi-art-test-philosopher',
296
- },
297
- risks: [],
298
- generatedAt: new Date().toISOString(),
203
+ function buildTestDoubleAdapter(runnerKind: string, taskId: string): PDRuntimeAdapter {
204
+ if (runnerKind === 'philosopher') {
205
+ return new TestDoubleRuntimeAdapter({
206
+ onPollRun: (_runId: string) => ({
207
+ runId: _runId,
208
+ status: 'succeeded',
209
+ startedAt: new Date().toISOString(),
210
+ endedAt: new Date().toISOString(),
211
+ }),
212
+ onFetchOutput: (_runId: string) => ({
213
+ runId: _runId,
214
+ payload: {
215
+ taskId,
216
+ sourceDreamerArtifactId: 'pi-art-test-dreamer',
217
+ thesis: 'Test thesis from test-double',
218
+ principleCandidate: {
219
+ title: 'Test Principle',
220
+ rationale: 'Test rationale',
221
+ scope: 'Test scope',
222
+ confidence: 0.8,
299
223
  },
300
- }),
301
- });
302
- }
303
- if (opts.runnerKind === 'artificer') {
304
- let capturedSourceScribeArtifactId = 'pi-art-test-scribe';
305
- return new TestDoubleRuntimeAdapter({
306
- onStartRun: (input) => {
307
- try {
308
- const payloadStr = typeof input.inputPayload === 'string' ? input.inputPayload : JSON.stringify(input.inputPayload);
309
- const parsed = JSON.parse(payloadStr);
310
- if (typeof parsed.sourceScribeArtifactId === 'string' && parsed.sourceScribeArtifactId.trim() !== '') {
311
- capturedSourceScribeArtifactId = parsed.sourceScribeArtifactId;
312
- }
313
- } catch { /* use default */ }
314
- return { runId: `td-artificer-${Date.now()}`, runtimeKind: 'test-double', startedAt: new Date().toISOString() };
224
+ risks: [],
225
+ generatedAt: new Date().toISOString(),
315
226
  },
316
- onPollRun: (_runId: string) => ({
317
- runId: _runId,
318
- status: 'succeeded',
319
- startedAt: new Date().toISOString(),
320
- endedAt: new Date().toISOString(),
321
- }),
322
- onFetchOutput: (_runId: string) => ({
323
- runId: _runId,
324
- payload: {
325
- taskId: opts.taskId,
326
- sourceScribeArtifactId: capturedSourceScribeArtifactId,
327
- implementationPlan: {
328
- summary: 'Test implementation summary',
329
- targetSurface: 'src/test/*.ts',
330
- changes: ['Add validation to test module'],
331
- tests: ['Unit test for validation'],
332
- rolloutNotes: ['Deploy behind feature flag'],
333
- confidence: 0.8,
334
- },
335
- sourceTrace: {
336
- scribeArtifactId: capturedSourceScribeArtifactId,
337
- },
338
- risks: [],
339
- generatedAt: new Date().toISOString(),
227
+ }),
228
+ });
229
+ }
230
+ if (runnerKind === 'scribe') {
231
+ return new TestDoubleRuntimeAdapter({
232
+ onPollRun: (_runId: string) => ({
233
+ runId: _runId,
234
+ status: 'succeeded',
235
+ startedAt: new Date().toISOString(),
236
+ endedAt: new Date().toISOString(),
237
+ }),
238
+ onFetchOutput: (_runId: string) => ({
239
+ runId: _runId,
240
+ payload: {
241
+ taskId,
242
+ sourcePhilosopherArtifactId: 'pi-art-test-philosopher',
243
+ principleDraft: {
244
+ title: 'Test Principle Draft',
245
+ statement: 'Test principle statement',
246
+ rationale: 'Test rationale',
247
+ applicability: ['All operations'],
248
+ antiPatterns: ['Ignoring validation'],
249
+ confidence: 0.8,
340
250
  },
341
- }),
342
- });
343
- }
344
- if (opts.runnerKind === 'evaluator') {
345
- let capturedSourceArtificerArtifactId = 'pi-art-test-artificer';
346
- return new TestDoubleRuntimeAdapter({
347
- onStartRun: (input) => {
348
- try {
349
- const payloadStr = typeof input.inputPayload === 'string' ? input.inputPayload : JSON.stringify(input.inputPayload);
350
- const parsed = JSON.parse(payloadStr);
351
- if (typeof parsed.sourceArtificerArtifactId === 'string' && parsed.sourceArtificerArtifactId.trim() !== '') {
352
- capturedSourceArtificerArtifactId = parsed.sourceArtificerArtifactId;
353
- }
354
- } catch { /* use default */ }
355
- return { runId: `td-evaluator-${Date.now()}`, runtimeKind: 'test-double', startedAt: new Date().toISOString() };
356
- },
357
- onPollRun: (_runId: string) => ({
358
- runId: _runId,
359
- status: 'succeeded',
360
- startedAt: new Date().toISOString(),
361
- endedAt: new Date().toISOString(),
362
- }),
363
- onFetchOutput: (_runId: string) => ({
364
- runId: _runId,
365
- payload: {
366
- taskId: opts.taskId,
367
- sourceArtificerArtifactId: capturedSourceArtificerArtifactId,
368
- evaluation: {
369
- decision: 'approved',
370
- summary: 'Test evaluation summary',
371
- score: 0.85,
372
- strengths: ['Well-structured plan'],
373
- concerns: [],
374
- requiredChanges: [],
375
- },
376
- sourceTrace: {
377
- artificerArtifactId: capturedSourceArtificerArtifactId,
378
- },
379
- risks: [],
380
- generatedAt: new Date().toISOString(),
251
+ sourceTrace: {
252
+ philosopherArtifactId: 'pi-art-test-philosopher',
381
253
  },
382
- }),
383
- });
384
- }
385
- if (opts.runnerKind === 'rollout_reviewer') {
386
- let capturedSourceEvaluatorArtifactId = 'pi-art-test-evaluator';
387
- return new TestDoubleRuntimeAdapter({
388
- onStartRun: (input) => {
389
- try {
390
- const payloadStr = typeof input.inputPayload === 'string' ? input.inputPayload : JSON.stringify(input.inputPayload);
391
- const parsed = JSON.parse(payloadStr);
392
- if (typeof parsed.sourceEvaluatorArtifactId === 'string' && parsed.sourceEvaluatorArtifactId.trim() !== '') {
393
- capturedSourceEvaluatorArtifactId = parsed.sourceEvaluatorArtifactId;
394
- }
395
- } catch { /* use default */ }
396
- return { runId: `td-rollout-reviewer-${Date.now()}`, runtimeKind: 'test-double', startedAt: new Date().toISOString() };
254
+ risks: [],
255
+ generatedAt: new Date().toISOString(),
397
256
  },
398
- onPollRun: (_runId: string) => ({
399
- runId: _runId,
400
- status: 'succeeded',
401
- startedAt: new Date().toISOString(),
402
- endedAt: new Date().toISOString(),
403
- }),
404
- onFetchOutput: (_runId: string) => ({
405
- runId: _runId,
406
- payload: {
407
- taskId: opts.taskId,
408
- sourceEvaluatorArtifactId: capturedSourceEvaluatorArtifactId,
409
- review: {
410
- decision: 'approve_rollout',
411
- summary: 'Test rollout review summary',
412
- confidence: 0.9,
413
- requiredChanges: [],
414
- rolloutRisks: [],
415
- safetyChecks: ['Verify feature flag is properly configured'],
416
- },
417
- sourceTrace: {
418
- evaluatorArtifactId: capturedSourceEvaluatorArtifactId,
419
- },
420
- risks: [],
421
- generatedAt: new Date().toISOString(),
257
+ }),
258
+ });
259
+ }
260
+ if (runnerKind === 'artificer') {
261
+ let capturedSourceScribeArtifactId = 'pi-art-test-scribe';
262
+ return new TestDoubleRuntimeAdapter({
263
+ onStartRun: (input) => {
264
+ try {
265
+ const payloadStr = typeof input.inputPayload === 'string' ? input.inputPayload : JSON.stringify(input.inputPayload);
266
+ const parsed = JSON.parse(payloadStr);
267
+ if (typeof parsed.sourceScribeArtifactId === 'string' && parsed.sourceScribeArtifactId.trim() !== '') {
268
+ capturedSourceScribeArtifactId = parsed.sourceScribeArtifactId;
269
+ }
270
+ } catch { /* use default */ }
271
+ return { runId: `td-artificer-${Date.now()}`, runtimeKind: 'test-double', startedAt: new Date().toISOString() };
272
+ },
273
+ onPollRun: (_runId: string) => ({
274
+ runId: _runId,
275
+ status: 'succeeded',
276
+ startedAt: new Date().toISOString(),
277
+ endedAt: new Date().toISOString(),
278
+ }),
279
+ onFetchOutput: (_runId: string) => ({
280
+ runId: _runId,
281
+ payload: {
282
+ taskId,
283
+ sourceScribeArtifactId: capturedSourceScribeArtifactId,
284
+ implementationPlan: {
285
+ summary: 'Test implementation summary',
286
+ targetSurface: 'src/test/*.ts',
287
+ changes: ['Add validation to test module'],
288
+ tests: ['Unit test for validation'],
289
+ rolloutNotes: ['Deploy behind feature flag'],
290
+ confidence: 0.8,
422
291
  },
423
- }),
424
- });
425
- }
426
- if (opts.runnerKind === 'trainer') {
427
- let capturedSourceRolloutReviewerArtifactId = 'pi-art-test-rollout-reviewer';
428
- return new TestDoubleRuntimeAdapter({
429
- onStartRun: (input) => {
430
- try {
431
- const payloadStr = typeof input.inputPayload === 'string' ? input.inputPayload : JSON.stringify(input.inputPayload);
432
- const parsed = JSON.parse(payloadStr);
433
- if (typeof parsed.sourceRolloutReviewerArtifactId === 'string' && parsed.sourceRolloutReviewerArtifactId.trim() !== '') {
434
- capturedSourceRolloutReviewerArtifactId = parsed.sourceRolloutReviewerArtifactId;
435
- }
436
- } catch { /* use default */ }
437
- return { runId: `td-trainer-${Date.now()}`, runtimeKind: 'test-double', startedAt: new Date().toISOString() };
438
- },
439
- onPollRun: (_runId: string) => ({
440
- runId: _runId,
441
- status: 'succeeded',
442
- startedAt: new Date().toISOString(),
443
- endedAt: new Date().toISOString(),
444
- }),
445
- onFetchOutput: (_runId: string) => ({
446
- runId: _runId,
447
- payload: {
448
- taskId: opts.taskId,
449
- sourceRolloutReviewerArtifactId: capturedSourceRolloutReviewerArtifactId,
450
- ruleCandidate: {
451
- toolScope: 'src/**/*.ts',
452
- triggerCondition: 'TypeScript file edit with schema mismatch',
453
- proposedDecision: 'auto_correct',
454
- proposedCorrection: {
455
- description: 'Auto-correct by adding input validation before processing',
456
- proposedParams: {
457
- strategy: 'prepend',
458
- snippet: 'const validated = schema.parse(input); if (!validated.success) throw new ValidationError(validated.error);',
459
- },
460
- },
461
- rationale: 'Auto-correct validates input before processing to prevent downstream errors',
462
- confidence: 0.88,
463
- },
464
- safety: {
465
- limitations: ['Requires schema definition for all input types', 'May not handle complex nested structures'],
466
- falsePositiveRisks: ['Could over-correct on intentional dynamic patterns'],
467
- requiredReplayCases: ['Schema validation edge case', 'Nested object validation'],
468
- },
469
- sourceTrace: {
470
- rolloutReviewerArtifactId: capturedSourceRolloutReviewerArtifactId,
471
- },
472
- risks: [],
473
- generatedAt: new Date().toISOString(),
292
+ sourceTrace: {
293
+ scribeArtifactId: capturedSourceScribeArtifactId,
474
294
  },
475
- }),
476
- });
477
- }
295
+ risks: [],
296
+ generatedAt: new Date().toISOString(),
297
+ },
298
+ }),
299
+ });
300
+ }
301
+ if (runnerKind === 'evaluator') {
302
+ let capturedSourceArtificerArtifactId = 'pi-art-test-artificer';
478
303
  return new TestDoubleRuntimeAdapter({
304
+ onStartRun: (input) => {
305
+ try {
306
+ const payloadStr = typeof input.inputPayload === 'string' ? input.inputPayload : JSON.stringify(input.inputPayload);
307
+ const parsed = JSON.parse(payloadStr);
308
+ if (typeof parsed.sourceArtificerArtifactId === 'string' && parsed.sourceArtificerArtifactId.trim() !== '') {
309
+ capturedSourceArtificerArtifactId = parsed.sourceArtificerArtifactId;
310
+ }
311
+ } catch { /* use default */ }
312
+ return { runId: `td-evaluator-${Date.now()}`, runtimeKind: 'test-double', startedAt: new Date().toISOString() };
313
+ },
479
314
  onPollRun: (_runId: string) => ({
480
315
  runId: _runId,
481
316
  status: 'succeeded',
@@ -485,92 +320,145 @@ function resolveRuntimeAdapter(opts: ResolveAdapterOptions): PDRuntimeAdapter {
485
320
  onFetchOutput: (_runId: string) => ({
486
321
  runId: _runId,
487
322
  payload: {
488
- valid: true,
489
- taskId: opts.taskId,
490
- candidates: [{
491
- candidateIndex: 0,
492
- badDecision: 'Ignored input validation requirement',
493
- betterDecision: 'Validate all inputs against schema before processing',
494
- rationale: 'Input validation prevents downstream errors and data corruption',
495
- confidence: 0.85,
496
- riskLevel: 'low',
497
- strategicPerspective: 'defensive-programming',
498
- }],
499
- contextRefs: [],
323
+ taskId,
324
+ sourceArtificerArtifactId: capturedSourceArtificerArtifactId,
325
+ evaluation: {
326
+ decision: 'approved',
327
+ summary: 'Test evaluation summary',
328
+ score: 0.85,
329
+ strengths: ['Well-structured plan'],
330
+ concerns: [],
331
+ requiredChanges: [],
332
+ },
333
+ sourceTrace: {
334
+ artificerArtifactId: capturedSourceArtificerArtifactId,
335
+ },
336
+ risks: [],
500
337
  generatedAt: new Date().toISOString(),
501
338
  },
502
339
  }),
503
340
  });
504
341
  }
505
-
506
- // PRI-393: resolve runtime from .pd/config.yaml (not .state/workflows.yaml)
507
- const resolved = resolveRuntimeFromPdConfig(opts.workspaceDir);
508
- const configResult = resolved.result;
509
-
510
- if (isRuntimeConfigError(configResult)) {
511
- throw new ConfigResolutionError(
512
- `Config resolution from .pd/config.yaml failed: ${configResult.reason}. ` +
513
- `${configResult.message}. nextAction: ${configResult.nextAction}`,
514
- );
515
- }
516
-
517
- if (opts.runtimeKind === 'pi-ai' || (opts.runtimeKind === 'config' && configResult.runtimeKind === 'pi-ai')) {
518
- validateRuntimeConfig(configResult);
519
- // CLI --timeout-ms overrides config timeoutMs
520
- const adapterTimeoutMs = opts.timeoutMs ?? configResult.timeoutMs;
521
-
522
- // PRI-419: when l2_dreamer flag is on AND this is the dreamer runner, route through the
523
- // L2 multi-turn agent loop adapter. Other runners (philosopher/scribe/...) stay on L1.
524
- if (opts.runnerKind === 'dreamer' && opts.l2ArtifactReader && opts.l2StateDir) {
525
- const effectiveFlags = loadEffectiveFeatureFlags(opts.workspaceDir);
526
- const l2Flag = Object.hasOwn(effectiveFlags.flags, 'l2_dreamer')
527
- ? effectiveFlags.flags.l2_dreamer.enabled
528
- : false;
529
- if (l2Flag) {
530
- return new L2AgentLoopAdapter(
531
- {
532
- provider: String(configResult.provider),
533
- model: String(configResult.model),
534
- apiKeyEnv: String(configResult.apiKeyEnv),
535
- baseUrl: configResult.baseUrl,
536
- workspace: opts.workspaceDir,
537
- totalBudgetMs: adapterTimeoutMs,
342
+ if (runnerKind === 'rollout_reviewer') {
343
+ let capturedSourceEvaluatorArtifactId = 'pi-art-test-evaluator';
344
+ return new TestDoubleRuntimeAdapter({
345
+ onStartRun: (input) => {
346
+ try {
347
+ const payloadStr = typeof input.inputPayload === 'string' ? input.inputPayload : JSON.stringify(input.inputPayload);
348
+ const parsed = JSON.parse(payloadStr);
349
+ if (typeof parsed.sourceEvaluatorArtifactId === 'string' && parsed.sourceEvaluatorArtifactId.trim() !== '') {
350
+ capturedSourceEvaluatorArtifactId = parsed.sourceEvaluatorArtifactId;
351
+ }
352
+ } catch { /* use default */ }
353
+ return { runId: `td-rollout-reviewer-${Date.now()}`, runtimeKind: 'test-double', startedAt: new Date().toISOString() };
354
+ },
355
+ onPollRun: (_runId: string) => ({
356
+ runId: _runId,
357
+ status: 'succeeded',
358
+ startedAt: new Date().toISOString(),
359
+ endedAt: new Date().toISOString(),
360
+ }),
361
+ onFetchOutput: (_runId: string) => ({
362
+ runId: _runId,
363
+ payload: {
364
+ taskId,
365
+ sourceEvaluatorArtifactId: capturedSourceEvaluatorArtifactId,
366
+ review: {
367
+ decision: 'approve_rollout',
368
+ summary: 'Test rollout review summary',
369
+ confidence: 0.9,
370
+ requiredChanges: [],
371
+ rolloutRisks: [],
372
+ safetyChecks: ['Verify feature flag is properly configured'],
538
373
  },
539
- {
540
- artifactReader: opts.l2ArtifactReader,
541
- principleReader: makeDreamerPrincipleReader(opts.l2StateDir),
374
+ sourceTrace: {
375
+ evaluatorArtifactId: capturedSourceEvaluatorArtifactId,
542
376
  },
543
- );
544
- }
545
- }
546
-
547
- return new PiAiRuntimeAdapter({
548
- provider: String(configResult.provider),
549
- model: String(configResult.model),
550
- apiKeyEnv: String(configResult.apiKeyEnv),
551
- maxRetries: configResult.maxRetries,
552
- timeoutMs: adapterTimeoutMs,
553
- baseUrl: configResult.baseUrl,
554
- workspace: opts.workspaceDir,
377
+ risks: [],
378
+ generatedAt: new Date().toISOString(),
379
+ },
380
+ }),
555
381
  });
556
382
  }
557
-
558
- if (opts.runtimeKind === 'openclaw-cli' || (opts.runtimeKind === 'config' && configResult.runtimeKind === 'openclaw-cli')) {
559
- const { openclawMode } = configResult;
560
- if (!openclawMode) {
561
- throw new ConfigResolutionError(
562
- `runtimeKind 'openclaw-cli' requires openclawMode. ` +
563
- `Provide --openclaw-local or --openclaw-gateway, or set openclawMode in .pd/config.yaml. ` +
564
- `nextAction: Add openclawMode: local|gateway to your .pd/config.yaml runtime profile or use CLI flags.`,
565
- );
566
- }
567
- return new OpenClawCliRuntimeAdapter({
568
- runtimeMode: openclawMode,
569
- workspaceDir: opts.workspaceDir,
383
+ if (runnerKind === 'trainer') {
384
+ let capturedSourceRolloutReviewerArtifactId = 'pi-art-test-rollout-reviewer';
385
+ return new TestDoubleRuntimeAdapter({
386
+ onStartRun: (input) => {
387
+ try {
388
+ const payloadStr = typeof input.inputPayload === 'string' ? input.inputPayload : JSON.stringify(input.inputPayload);
389
+ const parsed = JSON.parse(payloadStr);
390
+ if (typeof parsed.sourceRolloutReviewerArtifactId === 'string' && parsed.sourceRolloutReviewerArtifactId.trim() !== '') {
391
+ capturedSourceRolloutReviewerArtifactId = parsed.sourceRolloutReviewerArtifactId;
392
+ }
393
+ } catch { /* use default */ }
394
+ return { runId: `td-trainer-${Date.now()}`, runtimeKind: 'test-double', startedAt: new Date().toISOString() };
395
+ },
396
+ onPollRun: (_runId: string) => ({
397
+ runId: _runId,
398
+ status: 'succeeded',
399
+ startedAt: new Date().toISOString(),
400
+ endedAt: new Date().toISOString(),
401
+ }),
402
+ onFetchOutput: (_runId: string) => ({
403
+ runId: _runId,
404
+ payload: {
405
+ taskId,
406
+ sourceRolloutReviewerArtifactId: capturedSourceRolloutReviewerArtifactId,
407
+ ruleCandidate: {
408
+ toolScope: 'src/**/*.ts',
409
+ triggerCondition: 'TypeScript file edit with schema mismatch',
410
+ proposedDecision: 'auto_correct',
411
+ proposedCorrection: {
412
+ description: 'Auto-correct by adding input validation before processing',
413
+ proposedParams: {
414
+ strategy: 'prepend',
415
+ snippet: 'const validated = schema.parse(input); if (!validated.success) throw new ValidationError(validated.error);',
416
+ },
417
+ },
418
+ rationale: 'Auto-correct validates input before processing to prevent downstream errors',
419
+ confidence: 0.88,
420
+ },
421
+ safety: {
422
+ limitations: ['Requires schema definition for all input types', 'May not handle complex nested structures'],
423
+ falsePositiveRisks: ['Could over-correct on intentional dynamic patterns'],
424
+ requiredReplayCases: ['Schema validation edge case', 'Nested object validation'],
425
+ },
426
+ sourceTrace: {
427
+ rolloutReviewerArtifactId: capturedSourceRolloutReviewerArtifactId,
428
+ },
429
+ risks: [],
430
+ generatedAt: new Date().toISOString(),
431
+ },
432
+ }),
570
433
  });
571
434
  }
572
-
573
- throw new Error(`Unsupported runtime kind: ${opts.runtimeKind}. Supported: test-double, pi-ai, openclaw-cli, config`);
435
+ // dreamer / default
436
+ return new TestDoubleRuntimeAdapter({
437
+ onPollRun: (_runId: string) => ({
438
+ runId: _runId,
439
+ status: 'succeeded',
440
+ startedAt: new Date().toISOString(),
441
+ endedAt: new Date().toISOString(),
442
+ }),
443
+ onFetchOutput: (_runId: string) => ({
444
+ runId: _runId,
445
+ payload: {
446
+ valid: true,
447
+ taskId,
448
+ candidates: [{
449
+ candidateIndex: 0,
450
+ badDecision: 'Ignored input validation requirement',
451
+ betterDecision: 'Validate all inputs against schema before processing',
452
+ rationale: 'Input validation prevents downstream errors and data corruption',
453
+ confidence: 0.85,
454
+ riskLevel: 'low',
455
+ strategicPerspective: 'defensive-programming',
456
+ }],
457
+ contextRefs: [],
458
+ generatedAt: new Date().toISOString(),
459
+ },
460
+ }),
461
+ });
574
462
  }
575
463
 
576
464
  export async function handleRuntimeInternalizationRunOnce(opts: RunOnceOptions): Promise<void> {
@@ -631,13 +519,14 @@ export async function handleRuntimeInternalizationRunOnce(opts: RunOnceOptions):
631
519
 
632
520
  const eventEmitter = new StoreEventEmitter();
633
521
  const artifactStore = stateManager.piArtifactStore;
634
- const runtimeAdapter = resolveRuntimeAdapter({
522
+ const runtimeAdapter = resolveRuntimeAdapterFromConfig({
635
523
  runtimeKind,
636
- taskId: wakeResult.taskId,
637
524
  workspaceDir,
638
525
  runnerKind,
639
526
  timeoutMs: cliTimeoutMs,
640
- // PRI-419: pass the L2 readers so resolveRuntimeAdapter can build an L2AgentLoopAdapter
527
+ allowTestDouble: true,
528
+ testDoublePayloadBuilder: () => buildTestDoubleAdapter(runnerKind, wakeResult.taskId),
529
+ // PRI-419: pass the L2 readers so the resolver can build an L2AgentLoopAdapter
641
530
  // when l2_dreamer is enabled. Only consumed for dreamer; harmless for other runners.
642
531
  l2ArtifactReader: artifactStore,
643
532
  l2StateDir: `${workspaceDir}/.state`,