create-principles-disciple 1.102.0 → 1.103.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 (139) hide show
  1. package/console/dist/server/config/pd-config-store.d.ts +50 -1
  2. package/console/dist/server/config/pd-config-store.js +364 -1
  3. package/console/dist/server/models/ActivationsConsoleModel.d.ts +7 -4
  4. package/console/dist/server/models/ActivationsConsoleModel.js +7 -6
  5. package/console/dist/server/routes/config.js +92 -2
  6. package/console/dist/ui/api.d.ts +44 -3
  7. package/console/dist/ui/api.js +58 -2
  8. package/console/dist/ui/components/onboarding/DemoResultView.js +19 -1
  9. package/console/dist/ui/i18n/en.json +63 -2
  10. package/console/dist/ui/i18n/zh-CN.json +63 -2
  11. package/console/dist/ui/pages/activation/ActivationPage.js +11 -11
  12. package/console/dist/ui/pages/activation/ActivationValidators.js +14 -13
  13. package/console/dist/ui/pages/control-center/AgentCard.js +56 -7
  14. package/console/dist/ui/pages/control-center/ControlCenterPage.js +2 -1
  15. package/console/dist/ui/pages/control-center/RuntimeProfileManager.d.ts +31 -0
  16. package/console/dist/ui/pages/control-center/RuntimeProfileManager.js +234 -0
  17. package/console/dist/ui/pages/debt/DebtPage.js +2 -2
  18. package/console/dist/ui/utils/validators.d.ts +26 -3
  19. package/console/dist/ui/utils/validators.js +23 -6
  20. package/console/dist/web/assets/app.css +41 -0
  21. package/console/dist/web/assets/app.js +1918 -1160
  22. package/core/dist/runtime-v2/__tests__/resolve-runtime-config-from-pd-config.test.js +8 -10
  23. package/core/dist/runtime-v2/__tests__/resolve-runtime-config-from-pd-config.test.js.map +1 -1
  24. package/core/dist/runtime-v2/adapter/__tests__/l2-resolve-model-compat.test.d.ts +2 -0
  25. package/core/dist/runtime-v2/adapter/__tests__/l2-resolve-model-compat.test.d.ts.map +1 -0
  26. package/core/dist/runtime-v2/adapter/__tests__/l2-resolve-model-compat.test.js +81 -0
  27. package/core/dist/runtime-v2/adapter/__tests__/l2-resolve-model-compat.test.js.map +1 -0
  28. package/core/dist/runtime-v2/adapter/__tests__/pi-ai-runtime-adapter.test.js +19 -0
  29. package/core/dist/runtime-v2/adapter/__tests__/pi-ai-runtime-adapter.test.js.map +1 -1
  30. package/core/dist/runtime-v2/adapter/pi-ai-runtime-adapter.d.ts +8 -0
  31. package/core/dist/runtime-v2/adapter/pi-ai-runtime-adapter.d.ts.map +1 -1
  32. package/core/dist/runtime-v2/adapter/pi-ai-runtime-adapter.js +4 -1
  33. package/core/dist/runtime-v2/adapter/pi-ai-runtime-adapter.js.map +1 -1
  34. package/core/dist/runtime-v2/candidate-intake-service.d.ts.map +1 -1
  35. package/core/dist/runtime-v2/candidate-intake-service.js +92 -6
  36. package/core/dist/runtime-v2/candidate-intake-service.js.map +1 -1
  37. package/core/dist/runtime-v2/candidate-intake.d.ts +38 -0
  38. package/core/dist/runtime-v2/candidate-intake.d.ts.map +1 -1
  39. package/core/dist/runtime-v2/candidate-intake.js +48 -0
  40. package/core/dist/runtime-v2/candidate-intake.js.map +1 -1
  41. package/core/dist/runtime-v2/config/__tests__/pd-config-agent-binding.test.js +26 -1
  42. package/core/dist/runtime-v2/config/__tests__/pd-config-agent-binding.test.js.map +1 -1
  43. package/core/dist/runtime-v2/config/__tests__/pd-config-contract.test.js +9 -5
  44. package/core/dist/runtime-v2/config/__tests__/pd-config-contract.test.js.map +1 -1
  45. package/core/dist/runtime-v2/config/__tests__/pd-config-validation-edge-cases.test.js +53 -33
  46. package/core/dist/runtime-v2/config/__tests__/pd-config-validation-edge-cases.test.js.map +1 -1
  47. package/core/dist/runtime-v2/config/pd-config-agent-binding.d.ts +2 -0
  48. package/core/dist/runtime-v2/config/pd-config-agent-binding.d.ts.map +1 -1
  49. package/core/dist/runtime-v2/config/pd-config-agent-binding.js +3 -0
  50. package/core/dist/runtime-v2/config/pd-config-agent-binding.js.map +1 -1
  51. package/core/dist/runtime-v2/config/pd-config-defaults.d.ts +7 -3
  52. package/core/dist/runtime-v2/config/pd-config-defaults.d.ts.map +1 -1
  53. package/core/dist/runtime-v2/config/pd-config-defaults.js +27 -3
  54. package/core/dist/runtime-v2/config/pd-config-defaults.js.map +1 -1
  55. package/core/dist/runtime-v2/config/pd-config-redaction.d.ts.map +1 -1
  56. package/core/dist/runtime-v2/config/pd-config-redaction.js +5 -2
  57. package/core/dist/runtime-v2/config/pd-config-redaction.js.map +1 -1
  58. package/core/dist/runtime-v2/config/pd-config-types.d.ts +6 -0
  59. package/core/dist/runtime-v2/config/pd-config-types.d.ts.map +1 -1
  60. package/core/dist/runtime-v2/config/pd-config-types.js.map +1 -1
  61. package/core/dist/runtime-v2/config/pd-config-validate.d.ts.map +1 -1
  62. package/core/dist/runtime-v2/config/pd-config-validate.js +11 -6
  63. package/core/dist/runtime-v2/config/pd-config-validate.js.map +1 -1
  64. package/core/dist/runtime-v2/index.d.ts +2 -1
  65. package/core/dist/runtime-v2/index.d.ts.map +1 -1
  66. package/core/dist/runtime-v2/index.js +1 -0
  67. package/core/dist/runtime-v2/index.js.map +1 -1
  68. package/core/dist/runtime-v2/internalization/__tests__/artificer-prompt-builder-v2.test.js +7 -0
  69. package/core/dist/runtime-v2/internalization/__tests__/artificer-prompt-builder-v2.test.js.map +1 -1
  70. package/core/dist/runtime-v2/internalization/__tests__/diag-rootcause-runner.test.js +7 -6
  71. package/core/dist/runtime-v2/internalization/__tests__/diag-rootcause-runner.test.js.map +1 -1
  72. package/core/dist/runtime-v2/internalization/artificer-prompt-builder.js +1 -1
  73. package/core/dist/runtime-v2/internalization/diag-distiller-runner.js +4 -4
  74. package/core/dist/runtime-v2/internalization/diag-distiller-runner.js.map +1 -1
  75. package/core/dist/runtime-v2/internalization/diag-rootcause-runner.js +5 -5
  76. package/core/dist/runtime-v2/internalization/diag-rootcause-runner.js.map +1 -1
  77. package/core/dist/runtime-v2/internalization/diag-router-runner.js +4 -4
  78. package/core/dist/runtime-v2/internalization/diag-router-runner.js.map +1 -1
  79. package/core/dist/runtime-v2/pain-signal-runtime-factory.d.ts +2 -0
  80. package/core/dist/runtime-v2/pain-signal-runtime-factory.d.ts.map +1 -1
  81. package/core/dist/runtime-v2/pain-signal-runtime-factory.js +2 -0
  82. package/core/dist/runtime-v2/pain-signal-runtime-factory.js.map +1 -1
  83. package/dist/installer.d.ts.map +1 -1
  84. package/dist/installer.js +5 -1
  85. package/dist/installer.js.map +1 -1
  86. package/dist/mvp-config.d.ts.map +1 -1
  87. package/dist/mvp-config.js +22 -10
  88. package/dist/mvp-config.js.map +1 -1
  89. package/package.json +1 -1
  90. package/pd-cli/dist/commands/__tests__/runtime-probe-config.test.js +2 -2
  91. package/pd-cli/dist/commands/__tests__/runtime-probe-config.test.js.map +1 -1
  92. package/pd-cli/dist/commands/demo-story-a.d.ts.map +1 -1
  93. package/pd-cli/dist/commands/demo-story-a.js +4 -2
  94. package/pd-cli/dist/commands/demo-story-a.js.map +1 -1
  95. package/pd-cli/dist/commands/proven-channel-baseline.d.ts.map +1 -1
  96. package/pd-cli/dist/commands/proven-channel-baseline.js +4 -2
  97. package/pd-cli/dist/commands/proven-channel-baseline.js.map +1 -1
  98. package/pd-cli/dist/commands/runtime-activation.d.ts.map +1 -1
  99. package/pd-cli/dist/commands/runtime-activation.js +8 -2
  100. package/pd-cli/dist/commands/runtime-activation.js.map +1 -1
  101. package/pd-cli/dist/commands/runtime-canary.d.ts.map +1 -1
  102. package/pd-cli/dist/commands/runtime-canary.js +4 -2
  103. package/pd-cli/dist/commands/runtime-canary.js.map +1 -1
  104. package/pd-cli/dist/commands/runtime-health-snapshot.d.ts.map +1 -1
  105. package/pd-cli/dist/commands/runtime-health-snapshot.js +4 -2
  106. package/pd-cli/dist/commands/runtime-health-snapshot.js.map +1 -1
  107. package/pd-cli/dist/commands/runtime-internalization-integrity.d.ts.map +1 -1
  108. package/pd-cli/dist/commands/runtime-internalization-integrity.js +4 -2
  109. package/pd-cli/dist/commands/runtime-internalization-integrity.js.map +1 -1
  110. package/pd-cli/dist/services/config-doctor.d.ts.map +1 -1
  111. package/pd-cli/dist/services/config-doctor.js +9 -2
  112. package/pd-cli/dist/services/config-doctor.js.map +1 -1
  113. package/pd-cli/dist/services/runtime-adapter-resolver.d.ts.map +1 -1
  114. package/pd-cli/dist/services/runtime-adapter-resolver.js +1 -0
  115. package/pd-cli/dist/services/runtime-adapter-resolver.js.map +1 -1
  116. package/plugin/dist/core/rule-host.d.ts +5 -2
  117. package/plugin/dist/core/rule-host.js +15 -2
  118. package/plugin/dist/hooks/gate.js +1 -1
  119. package/plugin/dist/service/evolution-dedup.d.ts +1 -1
  120. package/plugin/dist/service/evolution-worker.d.ts +7 -31
  121. package/plugin/dist/service/evolution-worker.js +17 -36
  122. package/plugin/dist/service/queue-io.js +14 -2
  123. package/plugin/dist/service/queue-migration.d.ts +27 -26
  124. package/plugin/dist/service/queue-migration.js +57 -0
  125. package/plugin/templates/langs/en/core/AGENTS.md +2 -2
  126. package/plugin/templates/langs/en/core/TOOLS.md +1 -1
  127. package/plugin/templates/langs/zh/core/AGENTS.md +2 -2
  128. package/plugin/templates/langs/zh/core/TOOLS.md +1 -1
  129. package/templates/langs/en/core/AGENTS.md +2 -2
  130. package/templates/langs/en/core/TOOLS.md +1 -1
  131. package/templates/langs/zh/core/AGENTS.md +2 -2
  132. package/templates/langs/zh/core/TOOLS.md +1 -1
  133. package/plugin/dist/constants/diagnostician.d.ts +0 -12
  134. package/plugin/dist/constants/diagnostician.js +0 -65
  135. package/plugin/dist/service/evolution-queue-migration.d.ts +0 -128
  136. package/plugin/dist/service/evolution-queue-migration.js +0 -53
  137. package/plugin/templates/langs/en/skills/pd-diagnostician/SKILL.md +0 -466
  138. package/plugin/templates/langs/zh/skills/pd-diagnostician/SKILL.md +0 -391
  139. package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +0 -287
@@ -161,7 +161,7 @@ export class RuleHost {
161
161
  try {
162
162
  const { loaded, skipped } = this._loadFromActivationsTable(this.workspaceDir, supportsContextV2);
163
163
  if (loaded.length === 0) {
164
- this._emitEmptyLoadWarn('armed but empty — 0 active code_tool_hook activations loaded (RuleHost will not block or require approval)', 'If this is unexpected, run `pd runtime activation list --channel code_tool_hook` to inspect activations, or `pd runtime activation promote` to enable a live rule');
164
+ this._emitEmptyLoadWarn('armed but empty — 0 active code_tool_hook activations loaded (RuleHost will not block or require approval)', 'If this is unexpected, run `pd activation list --channel code_tool_hook` to inspect activations, or `pd activation promote --activation-id <id> --confirm` to enable a live rule');
165
165
  }
166
166
  return { loaded, skipped };
167
167
  }
@@ -258,7 +258,13 @@ export class RuleHost {
258
258
  }
259
259
  }
260
260
  // Emit structured unhealthy evidence for duplicate groups; collect valid (non-duplicate) rows.
261
+ // PRI-497: duplicate activations are ALSO pushed to the `skipped` array
262
+ // so callers (gate.ts, CLI, Console) can observe them via
263
+ // skippedActivations — not just via rulehost_unhealthy event log.
264
+ // Both paths are populated (rc-9-no-silent-fallback): event log for
265
+ // telemetry, skipped array for structured caller-facing observability.
261
266
  const validRows = [];
267
+ const skipped = [];
262
268
  for (const [targetRef, group] of rowsByTargetRef) {
263
269
  if (group.length > 1) {
264
270
  const activationIds = [];
@@ -280,6 +286,14 @@ export class RuleHost {
280
286
  const artifactId = typeof r['artifact_id'] === 'string' ? r['artifact_id'] : '';
281
287
  if (activationId && artifactId) {
282
288
  this._recordUnhealthy(activationId, artifactId, targetRef, `duplicate active activation for target_ref ${targetRef}`, 'Deactivate all but one activation for this target_ref');
289
+ // PRI-497: also surface in skippedActivations for caller observability
290
+ skipped.push({
291
+ activationId,
292
+ ruleId: targetRef,
293
+ mode: 'live',
294
+ reason: 'duplicate_active_activation',
295
+ nextAction: 'Deactivate all but one activation for this target_ref',
296
+ });
283
297
  }
284
298
  }
285
299
  }
@@ -290,7 +304,6 @@ export class RuleHost {
290
304
  }
291
305
  }
292
306
  const loaded = [];
293
- const skipped = [];
294
307
  for (const r of validRows) {
295
308
  const activationId = typeof r['activation_id'] === 'string' ? r['activation_id'] : '';
296
309
  const artifactId = typeof r['artifact_id'] === 'string' ? r['artifact_id'] : '';
@@ -80,7 +80,7 @@ export function handleBeforeToolCall(event, ctx) {
80
80
  };
81
81
  const report = typeof ruleHost.evaluateDetailed === 'function'
82
82
  ? ruleHost.evaluateDetailed(hostInput)
83
- : { liveDecision: ruleHost.evaluate(hostInput), shadowDecisions: [] };
83
+ : { liveDecision: ruleHost.evaluate(hostInput), shadowDecisions: [], skippedActivations: [] };
84
84
  const hostResult = report.liveDecision;
85
85
  for (const shadowDecision of report.shadowDecisions) {
86
86
  try {
@@ -4,7 +4,7 @@
4
4
  * Dedup logic for preventing duplicate pain tasks and redundant reflections.
5
5
  * Extracted from evolution-worker.ts.
6
6
  */
7
- import type { EvolutionQueueItem } from './evolution-queue-migration.js';
7
+ import type { EvolutionQueueItem } from '../core/evolution-types.js';
8
8
  /**
9
9
  * Dedup window for pain queue tasks (30 minutes).
10
10
  */
@@ -1,6 +1,5 @@
1
1
  import type { OpenClawPluginServiceContext, OpenClawPluginApi, PluginLogger } from '../openclaw-sdk.js';
2
2
  import { WorkspaceContext } from '../core/workspace-context.js';
3
- import type { TaskKind, TaskPriority } from '../core/trajectory-types.js';
4
3
  export type { TaskKind, TaskPriority } from '../core/trajectory-types.js';
5
4
  export { loadEvolutionQueue, saveEvolutionQueue, withQueueLock, acquireQueueLock, requireQueueLock } from './queue-io.js';
6
5
  export { EVOLUTION_QUEUE_LOCK_SUFFIX, LOCK_MAX_RETRIES, LOCK_RETRY_DELAY_MS, LOCK_STALE_MS } from './queue-io.js';
@@ -24,37 +23,14 @@ export type { WatchdogResult };
24
23
  * Pain diagnosis is Runtime v2 only: after_tool_call / pd pain record ->
25
24
  * PainSignalBridge -> SplitDiagnosticianRunner. EvolutionWorker does not read
26
25
  * .pain_flag or process pain_diagnosis queue items.
26
+ *
27
+ * Types (QueueStatus / TaskResolution / EvolutionQueueItem) are re-exported
28
+ * from queue-migration.ts, which sources them from evolution-types.ts
29
+ * (canonical single source of truth).
27
30
  */
28
- /** @deprecated Use PDTaskStatus from '@principles/core/runtime-v2'. M2 migration will replace this. */
29
- export type QueueStatus = 'pending' | 'in_progress' | 'completed' | 'failed' | 'canceled';
30
- export type TaskResolution = 'marker_detected' | 'auto_completed_timeout' | 'failed_max_retries' | 'runtime_unavailable' | 'canceled' | 'late_marker_principle_created' | 'late_marker_no_principle' | 'stub_fallback' | 'skipped_thin_violation' | 'noise_classified' | 'retired';
31
- export interface EvolutionQueueItem {
32
- id: string;
33
- taskKind: TaskKind;
34
- priority: TaskPriority;
35
- source: string;
36
- traceId?: string;
37
- task?: string;
38
- score: number;
39
- reason: string;
40
- timestamp: string;
41
- enqueued_at?: string;
42
- started_at?: string;
43
- completed_at?: string;
44
- assigned_session_key?: string;
45
- trigger_text_preview?: string;
46
- status: QueueStatus;
47
- resolution?: TaskResolution;
48
- session_id?: string;
49
- agent_id?: string;
50
- retryCount: number;
51
- maxRetries: number;
52
- lastError?: string;
53
- resultRef?: string;
54
- painEventId?: number;
55
- }
56
- import { migrateToV2, isLegacyQueueItem, migrateQueueToV2, LegacyEvolutionQueueItem, DEFAULT_TASK_KIND, DEFAULT_PRIORITY, DEFAULT_MAX_RETRIES, type RawQueueItem } from './queue-migration.js';
57
- export { migrateToV2, isLegacyQueueItem, migrateQueueToV2, LegacyEvolutionQueueItem, DEFAULT_TASK_KIND, DEFAULT_PRIORITY, DEFAULT_MAX_RETRIES };
31
+ export type { QueueStatus, TaskResolution, EvolutionQueueItem } from './queue-migration.js';
32
+ import { migrateToV2, isLegacyQueueItem, migrateQueueToV2, LegacyEvolutionQueueItem, DEFAULT_TASK_KIND, DEFAULT_PRIORITY, DEFAULT_MAX_RETRIES, validateQueueItem, VALID_TASK_KINDS, isValidQueueItem, type RawQueueItem, type EvolutionQueueItem } from './queue-migration.js';
33
+ export { migrateToV2, isLegacyQueueItem, migrateQueueToV2, LegacyEvolutionQueueItem, DEFAULT_TASK_KIND, DEFAULT_PRIORITY, DEFAULT_MAX_RETRIES, validateQueueItem, VALID_TASK_KINDS, isValidQueueItem };
58
34
  export type { RawQueueItem };
59
35
  export declare function extractEvolutionTaskId(task: string): string | null;
60
36
  export declare function purgeStaleFailedTasks(queue: EvolutionQueueItem[], logger: PluginLogger): {
@@ -11,7 +11,7 @@ import { atomicWriteFileSync } from '../utils/io.js';
11
11
  // Re-export queue I/O (extracted to queue-io.ts)
12
12
  export { loadEvolutionQueue, saveEvolutionQueue, withQueueLock, acquireQueueLock, requireQueueLock } from './queue-io.js';
13
13
  export { EVOLUTION_QUEUE_LOCK_SUFFIX, LOCK_MAX_RETRIES, LOCK_RETRY_DELAY_MS, LOCK_STALE_MS } from './queue-io.js';
14
- import { saveEvolutionQueue, requireQueueLock } from './queue-io.js';
14
+ import { saveEvolutionQueue, requireQueueLock, loadEvolutionQueue } from './queue-io.js';
15
15
  import { WorkflowStore } from './subagent-workflow/workflow-store.js';
16
16
  import { PrincipleCompiler } from '../core/principle-compiler/index.js';
17
17
  import { loadLedger, updatePrinciple } from '../core/principle-tree-ledger.js';
@@ -58,8 +58,8 @@ import { runWorkflowWatchdog } from './workflow-watchdog.js';
58
58
  export { runWorkflowWatchdog };
59
59
  let timeoutId = null;
60
60
  // ── Queue Migration (extracted to queue-migration.ts) ────────────────────────
61
- import { migrateToV2, isLegacyQueueItem, migrateQueueToV2, DEFAULT_TASK_KIND, DEFAULT_PRIORITY, DEFAULT_MAX_RETRIES } from './queue-migration.js';
62
- export { migrateToV2, isLegacyQueueItem, migrateQueueToV2, DEFAULT_TASK_KIND, DEFAULT_PRIORITY, DEFAULT_MAX_RETRIES };
61
+ import { migrateToV2, isLegacyQueueItem, migrateQueueToV2, DEFAULT_TASK_KIND, DEFAULT_PRIORITY, DEFAULT_MAX_RETRIES, validateQueueItem, VALID_TASK_KINDS, isValidQueueItem } from './queue-migration.js';
62
+ export { migrateToV2, isLegacyQueueItem, migrateQueueToV2, DEFAULT_TASK_KIND, DEFAULT_PRIORITY, DEFAULT_MAX_RETRIES, validateQueueItem, VALID_TASK_KINDS, isValidQueueItem };
63
63
  // Queue lock constants and requireQueueLock are imported from queue-io.ts
64
64
  export function extractEvolutionTaskId(task) {
65
65
  if (!task)
@@ -302,30 +302,12 @@ async function processEvolutionQueue(wctx, logger, _eventLog, _api) {
302
302
  logger?.info?.(`[PD:EvolutionWorker] Dropped ${beforeLegacyPainDrop - queue.length} legacy pain_diagnosis queue item(s); use PainSignalBridge/pd pain record`);
303
303
  }
304
304
  // Validate queue items — filter out malformed entries before processing.
305
- // Malformed items are logged + skipped; they never crash the evolution cycle.
305
+ // rc-1/rc-4 (ERR-007): consolidated in validateQueueItem (queue-migration.ts)
306
+ // so every load path applies the same filter. Malformed items are logged +
307
+ // skipped; they never crash the evolution cycle.
306
308
  const beforeValidation = queue.length;
307
309
  queue = queue.filter((item) => {
308
- const errors = [];
309
- if (!item.id || typeof item.id !== 'string')
310
- errors.push('missing/invalid id');
311
- if (!item.source || typeof item.source !== 'string')
312
- errors.push('missing/invalid source');
313
- if (typeof item.score !== 'number')
314
- errors.push('missing/invalid score');
315
- if (!item.status || typeof item.status !== 'string')
316
- errors.push('missing/invalid status');
317
- if (!item.taskKind || typeof item.taskKind !== 'string')
318
- errors.push('missing/invalid taskKind');
319
- else {
320
- const validTaskKinds = ['model_eval'];
321
- if (!validTaskKinds.includes(item.taskKind)) {
322
- errors.push(`invalid taskKind value '${item.taskKind}' (expected one of: ${validTaskKinds.join(', ')})`);
323
- }
324
- }
325
- if (typeof item.retryCount !== 'number')
326
- errors.push('missing/invalid retryCount');
327
- if (typeof item.maxRetries !== 'number')
328
- errors.push('missing/invalid maxRetries');
310
+ const errors = validateQueueItem(item);
329
311
  if (errors.length > 0) {
330
312
  logger?.warn?.(`[PD:EvolutionWorker] Skipping malformed queue item: ${errors.join(', ')} | ${JSON.stringify(item).slice(0, 200)}`);
331
313
  SystemLogger.log(wctx.workspaceDir, 'QUEUE_ITEM_MALFORMED', `Skipped: ${errors.join(', ')} | id=${item.id || 'N/A'}`);
@@ -361,16 +343,11 @@ export async function registerEvolutionTaskSession(workspaceResolve, taskId, ses
361
343
  return false;
362
344
  const releaseLock = await requireQueueLock(queuePath, logger, 'registerEvolutionTaskSession');
363
345
  try {
364
- let rawQueue;
365
- try {
366
- rawQueue = JSON.parse(fs.readFileSync(queuePath, 'utf8'));
367
- }
368
- catch (parseErr) {
369
- logger?.warn?.(`[PD:EvolutionWorker] Failed to parse EVOLUTION_QUEUE for session registration: ${queuePath} - ${parseErr instanceof Error ? parseErr.message : String(parseErr)}`);
370
- return false;
371
- }
372
- // V2: Migrate queue to current schema
373
- const queue = migrateQueueToV2(rawQueue);
346
+ // loadEvolutionQueue handles JSON parse + migrate + validate (rc-1/rc-4).
347
+ // Previously this inlined JSON.parse + migrate cast without validating,
348
+ // so malformed queue items could reach the find() below. Reusing the
349
+ // shared loader keeps a single chokepoint for queue validation.
350
+ const queue = loadEvolutionQueue(queuePath);
374
351
  const task = queue.find((item) => item.id === taskId && item.status === 'in_progress');
375
352
  if (!task) {
376
353
  logger?.warn?.(`[PD:EvolutionWorker] Could not find in-progress evolution task ${taskId} for session assignment`);
@@ -406,7 +383,11 @@ async function processEvolutionQueueWithResult(wctx, logger, eventLog, api) {
406
383
  if (!fs.existsSync(queuePath)) {
407
384
  return { queue: queueResult, errors };
408
385
  }
409
- const queue = JSON.parse(fs.readFileSync(queuePath, 'utf8'));
386
+ // rc-1/rc-2/rc-4 (ERR-001/ERR-005/ERR-007): use the canonical loader
387
+ // instead of raw JSON.parse + cast. loadEvolutionQueue handles parse,
388
+ // migrate, and element-wise validation; malformed items are dropped
389
+ // with a console.warn (rc-9: no silent fallback).
390
+ const queue = loadEvolutionQueue(queuePath);
410
391
  // Purge stale failed tasks before processing (keeps queue lean)
411
392
  const purgeResult = purgeStaleFailedTasks(queue, logger);
412
393
  if (purgeResult.purged > 0) {
@@ -11,7 +11,7 @@ import { createHash } from 'crypto';
11
11
  import { acquireLockAsync, releaseLock as releaseImportedLock } from '../utils/file-lock.js';
12
12
  import { atomicWriteFileSync } from '../utils/io.js';
13
13
  import { LockUnavailableError } from '../config/errors.js';
14
- import { migrateQueueToV2 } from './queue-migration.js';
14
+ import { migrateQueueToV2, validateQueueItem } from './queue-migration.js';
15
15
  export const EVOLUTION_QUEUE_LOCK_SUFFIX = '.lock';
16
16
  export const LOCK_MAX_RETRIES = 50;
17
17
  export const LOCK_RETRY_DELAY_MS = 50;
@@ -72,7 +72,19 @@ export function loadEvolutionQueue(queuePath) {
72
72
  rawQueue = [];
73
73
  }
74
74
  }
75
- return migrateQueueToV2(rawQueue);
75
+ // rc-1/rc-4 (ERR-007): every element of the parsed array is untrusted disk
76
+ // JSON — validate before returning. Malformed items are filtered out and
77
+ // reported via console.warn (rc-9: no silent drop). This is the single
78
+ // chokepoint that loadEvolutionQueue callers inherit.
79
+ const migrated = migrateQueueToV2(rawQueue);
80
+ return migrated.filter((item) => {
81
+ const errors = validateQueueItem(item);
82
+ if (errors.length > 0) {
83
+ console.warn(`[queue-io] Dropping malformed queue item: ${errors.join(', ')} | id=${item?.id ?? 'N/A'}`);
84
+ return false;
85
+ }
86
+ return true;
87
+ });
76
88
  }
77
89
  export function saveEvolutionQueue(queuePath, queue) {
78
90
  atomicWriteFileSync(queuePath, JSON.stringify(queue, null, 2));
@@ -5,32 +5,8 @@
5
5
  * to the V2 schema. Zero I/O, zero imports from evolution-worker.ts.
6
6
  */
7
7
  import type { TaskKind, TaskPriority } from '../core/trajectory-types.js';
8
- export type QueueStatus = 'pending' | 'in_progress' | 'completed' | 'failed';
9
- export type TaskResolution = 'success' | 'failure' | 'skipped';
10
- export interface EvolutionQueueItem {
11
- id: string;
12
- taskKind: TaskKind;
13
- priority: TaskPriority;
14
- source: string;
15
- traceId?: string;
16
- task?: string;
17
- score: number;
18
- reason: string;
19
- timestamp: string;
20
- enqueued_at?: string;
21
- started_at?: string;
22
- completed_at?: string;
23
- assigned_session_key?: string;
24
- trigger_text_preview?: string;
25
- status: QueueStatus;
26
- resolution?: TaskResolution;
27
- session_id?: string;
28
- agent_id?: string;
29
- retryCount: number;
30
- maxRetries: number;
31
- lastError?: string;
32
- resultRef?: string;
33
- }
8
+ import type { EvolutionQueueItem } from '../core/evolution-types.js';
9
+ export type { QueueStatus, TaskResolution, EvolutionQueueItem } from '../core/evolution-types.js';
34
10
  /**
35
11
  * Legacy queue item shape (pre-V2) for migration compatibility.
36
12
  * These items lack taskKind, priority, retryCount, maxRetries, lastError fields.
@@ -81,3 +57,28 @@ export declare function isLegacyQueueItem(item: RawQueueItem): boolean;
81
57
  * Returns a new array with all items migrated to V2 format.
82
58
  */
83
59
  export declare function migrateQueueToV2(queue: RawQueueItem[]): EvolutionQueueItem[];
60
+ /**
61
+ * Canonical set of valid taskKind values. Single source of truth — mirrors
62
+ * TaskKind in trajectory-types.ts. Previously the inline validator in
63
+ * evolution-worker.ts hard-coded `['model_eval']`, which silently rejected
64
+ * three legitimate taskKind values (pain_diagnosis / sleep_reflection /
65
+ * keyword_optimization). Keep this in sync with TaskKind.
66
+ */
67
+ export declare const VALID_TASK_KINDS: readonly TaskKind[];
68
+ /**
69
+ * Validate a single queue item loaded from untrusted disk JSON.
70
+ *
71
+ * Returns an array of human-readable error reasons; an empty array means the
72
+ * item is valid. Use {@link isValidQueueItem} for a type-guard form.
73
+ *
74
+ * This consolidates the inline validation that lived in processEvolutionQueue
75
+ * (evolution-worker.ts) so every queue load path (loadEvolutionQueue,
76
+ * registerEvolutionTaskSession) applies the same filter. rc-4 (ERR-007):
77
+ * array elements from parsed JSON must be element-wise validated.
78
+ */
79
+ export declare function validateQueueItem(item: unknown): string[];
80
+ /**
81
+ * Type-guard convenience over {@link validateQueueItem}.
82
+ * Drops the reason strings (callers that need them should call validateQueueItem).
83
+ */
84
+ export declare function isValidQueueItem(item: unknown): item is EvolutionQueueItem;
@@ -54,3 +54,60 @@ export function isLegacyQueueItem(item) {
54
54
  export function migrateQueueToV2(queue) {
55
55
  return queue.map(item => isLegacyQueueItem(item) ? migrateToV2(item) : item);
56
56
  }
57
+ // ── Validation (rc-1/rc-2, rc-4: validate array elements from untrusted JSON) ─
58
+ /**
59
+ * Canonical set of valid taskKind values. Single source of truth — mirrors
60
+ * TaskKind in trajectory-types.ts. Previously the inline validator in
61
+ * evolution-worker.ts hard-coded `['model_eval']`, which silently rejected
62
+ * three legitimate taskKind values (pain_diagnosis / sleep_reflection /
63
+ * keyword_optimization). Keep this in sync with TaskKind.
64
+ */
65
+ export const VALID_TASK_KINDS = [
66
+ 'pain_diagnosis',
67
+ 'sleep_reflection',
68
+ 'model_eval',
69
+ 'keyword_optimization',
70
+ ];
71
+ /**
72
+ * Validate a single queue item loaded from untrusted disk JSON.
73
+ *
74
+ * Returns an array of human-readable error reasons; an empty array means the
75
+ * item is valid. Use {@link isValidQueueItem} for a type-guard form.
76
+ *
77
+ * This consolidates the inline validation that lived in processEvolutionQueue
78
+ * (evolution-worker.ts) so every queue load path (loadEvolutionQueue,
79
+ * registerEvolutionTaskSession) applies the same filter. rc-4 (ERR-007):
80
+ * array elements from parsed JSON must be element-wise validated.
81
+ */
82
+ export function validateQueueItem(item) {
83
+ const errors = [];
84
+ if (item === null || typeof item !== 'object' || Array.isArray(item)) {
85
+ return ['item is not an object'];
86
+ }
87
+ const it = item;
88
+ if (!it.id || typeof it.id !== 'string')
89
+ errors.push('missing/invalid id');
90
+ if (!it.source || typeof it.source !== 'string')
91
+ errors.push('missing/invalid source');
92
+ if (typeof it.score !== 'number')
93
+ errors.push('missing/invalid score');
94
+ if (!it.status || typeof it.status !== 'string')
95
+ errors.push('missing/invalid status');
96
+ if (!it.taskKind || typeof it.taskKind !== 'string')
97
+ errors.push('missing/invalid taskKind');
98
+ else if (!VALID_TASK_KINDS.includes(it.taskKind)) {
99
+ errors.push(`invalid taskKind value '${it.taskKind}' (expected one of: ${VALID_TASK_KINDS.join(', ')})`);
100
+ }
101
+ if (typeof it.retryCount !== 'number')
102
+ errors.push('missing/invalid retryCount');
103
+ if (typeof it.maxRetries !== 'number')
104
+ errors.push('missing/invalid maxRetries');
105
+ return errors;
106
+ }
107
+ /**
108
+ * Type-guard convenience over {@link validateQueueItem}.
109
+ * Drops the reason strings (callers that need them should call validateQueueItem).
110
+ */
111
+ export function isValidQueueItem(item) {
112
+ return validateQueueItem(item).length === 0;
113
+ }
@@ -200,6 +200,6 @@ When completing any coding task (via AI coding assistant or direct):
200
200
  Use this to avoid confusing peer agents with Principles internal workers:
201
201
 
202
202
  - **Peer agents / peer sessions**: `agents_list`, `sessions_list`, `sessions_send`, `sessions_spawn`
203
- - **Internal workers** (for example `diagnostician`, `explorer`): use `sessions_spawn with pd-diagnostician/pd-explorer skills`
203
+ - **Internal workers** (for example `explorer`): use `sessions_spawn with pd-explorer skill`
204
204
  - **Inspect internal workers**: use `subagents`
205
- - **Do not** treat `diagnostician` or `explorer` as peer session targets
205
+ - **Do not** treat `explorer` as a peer session target
@@ -10,6 +10,6 @@
10
10
  ## 4. Agent Routing Clarification
11
11
 
12
12
  - `agents_list`, `sessions_list`, `sessions_send`, and `sessions_spawn` are for peer agents and peer sessions
13
- - Use `sessions_spawn` with `pd-diagnostician` or `pd-explorer` skills to start Principles internal workers
13
+ - Use `sessions_spawn` with `pd-explorer` skill to start Principles internal workers (e.g., explorer)
14
14
  - `subagents` inspects already-started internal workers and their outputs
15
15
  - Do not use peer-session tools to pretend an internal worker is a peer agent
@@ -192,6 +192,6 @@ _This folder is home. Treat it that way._
192
192
  用下面这几条避免把同级代理和 Principles 内部 worker 混淆:
193
193
 
194
194
  - **同级代理 / 同级会话**:`agents_list`、`sessions_list`、`sessions_send`、`sessions_spawn`
195
- - **内部 worker**(例如 `diagnostician`、`explorer`):使用 `sessions_spawn` 配合 `pd-diagnostician/pd-explorer` skill 启动
195
+ - **内部 worker**(例如 `explorer`):使用 `sessions_spawn` 配合 `pd-explorer` skill 启动
196
196
  - **查询内部 worker**:使用 `subagents`
197
- - **不要**把 `diagnostician` 或 `explorer` 当成同级 peer session 目标
197
+ - **不要**把 `explorer` 当成同级 peer session 目标
@@ -10,6 +10,6 @@
10
10
  ## 4. 智能体路由澄清
11
11
 
12
12
  - `agents_list`、`sessions_list`、`sessions_send`、`sessions_spawn` 用于同级代理和同级会话
13
- - 使用 `sessions_spawn` 配合 `pd-diagnostician/pd-explorer` skill 启动 Principles 内部 worker,例如 `diagnostician`、`explorer`
13
+ - 使用 `sessions_spawn` 配合 `pd-explorer` skill 启动 Principles 内部 worker,例如 `explorer`
14
14
  - `subagents` 用于查看已启动内部 worker 的状态和输出
15
15
  - 不要用同级会话工具把内部 worker 伪装成同级代理
@@ -200,6 +200,6 @@ When completing any coding task (via AI coding assistant or direct):
200
200
  Use this to avoid confusing peer agents with Principles internal workers:
201
201
 
202
202
  - **Peer agents / peer sessions**: `agents_list`, `sessions_list`, `sessions_send`, `sessions_spawn`
203
- - **Internal workers** (for example `diagnostician`, `explorer`): use `sessions_spawn with pd-diagnostician/pd-explorer skills`
203
+ - **Internal workers** (for example `explorer`): use `sessions_spawn with pd-explorer skill`
204
204
  - **Inspect internal workers**: use `subagents`
205
- - **Do not** treat `diagnostician` or `explorer` as peer session targets
205
+ - **Do not** treat `explorer` as a peer session target
@@ -53,6 +53,6 @@ Tool returns: Blind Spots → Risk Warnings → Alternative Approaches → Recom
53
53
  ## 4. Agent Routing Clarification
54
54
 
55
55
  - `agents_list`, `sessions_list`, `sessions_send`, and `sessions_spawn` are for peer agents and peer sessions
56
- - Use `sessions_spawn` with `pd-diagnostician` or `pd-explorer` skills to start Principles internal workers
56
+ - Use `sessions_spawn` with `pd-explorer` skill to start Principles internal workers (e.g., explorer)
57
57
  - `subagents` inspects already-started internal workers and their outputs
58
58
  - Do not use peer-session tools to pretend an internal worker is a peer agent
@@ -192,6 +192,6 @@ _This folder is home. Treat it that way._
192
192
  用下面这几条避免把同级代理和 Principles 内部 worker 混淆:
193
193
 
194
194
  - **同级代理 / 同级会话**:`agents_list`、`sessions_list`、`sessions_send`、`sessions_spawn`
195
- - **内部 worker**(例如 `diagnostician`、`explorer`):使用 `sessions_spawn` 配合 `pd-diagnostician/pd-explorer` skill 启动
195
+ - **内部 worker**(例如 `explorer`):使用 `sessions_spawn` 配合 `pd-explorer` skill 启动
196
196
  - **查询内部 worker**:使用 `subagents`
197
- - **不要**把 `diagnostician` 或 `explorer` 当成同级 peer session 目标
197
+ - **不要**把 `explorer` 当成同级 peer session 目标
@@ -53,6 +53,6 @@ deep_reflect(
53
53
  ## 4. 智能体路由澄清
54
54
 
55
55
  - `agents_list`、`sessions_list`、`sessions_send`、`sessions_spawn` 用于同级代理和同级会话
56
- - 使用 `sessions_spawn` 配合 `pd-diagnostician/pd-explorer` skill 启动 Principles 内部 worker,例如 `diagnostician`、`explorer`
56
+ - 使用 `sessions_spawn` 配合 `pd-explorer` skill 启动 Principles 内部 worker,例如 `explorer`
57
57
  - `subagents` 用于查看已启动内部 worker 的状态和输出
58
58
  - 不要用同级会话工具把内部 worker 伪装成同级代理
@@ -1,12 +0,0 @@
1
- /**
2
- * Diagnostician 协议常量
3
- *
4
- * 用于 Runtime V2 diagnostician prompt
5
- * 与 templates/langs/zh/skills/pd-diagnostician/SKILL.md 保持一致
6
- */
7
- /**
8
- * 诊断协议摘要(用于 HEARTBEAT.md 注入)
9
- *
10
- * 完整版见: templates/langs/zh/skills/pd-diagnostician/SKILL.md
11
- */
12
- export declare const DIAGNOSTICIAN_PROTOCOL_SUMMARY = "## Diagnostic Protocol (5 Whys)\n\n**Phase 1 - Evidence Gathering**:\n- Use the task input/context provided by Runtime V2 for pain context\n- Read .state/logs/events.jsonl recent entries\n- Search codebase for error patterns from Reason field\n- Record all evidence sources (file:line)\n\n**Phase 2 - Causal Chain (5 Whys)**:\n- Why 1: Surface symptom (what you can see)\n- Why 2: Direct cause (what triggered it)\n- Why 3: Process gap (what should have caught it)\n- Why 4: Design flaw (why the gap exists)\n- Why 5: Root cause (systemic issue to fix)\n- Each Why MUST have evidence. Stop at actionable root cause.\n\n**Phase 3 - Root Cause Classification**:\nClassify into one of:\n- **People**: Human error, missing knowledge, communication gap\n- **Design**: Architecture flaw, missing validation, poor abstraction\n- **Assumption**: Invalid assumption, outdated context, edge case\n- **Tooling**: Tool limitation, environment issue, dependency problem\n- For Design: analyze why existing hooks/rules didn't catch this\n\n**Phase 4 - Principle Extraction**:\nExtract a HIGHLY ABSTRACTED principle (not an operational rule):\n- **trigger_pattern**: regex/keywords for when this occurs\n- **action**: what to do differently\n- **abstracted_principle**: ONE sentence, max 40 chars, cross-scenario applicable\n - \u274C Bad: \"\u5199\u5165\u524D\u68C0\u67E5\u76EE\u5F55\u662F\u5426\u5B58\u5728\" (too specific)\n - \u2705 Good: \"\u4EFB\u4F55\u5199\u5165\u64CD\u4F5C\u5FC5\u987B\u786E\u4FDD\u76EE\u6807\u73AF\u5883\u7684\u5B8C\u6574\u6027\" (abstract, reusable)\n\n**Output Format** (single JSON object):\n```json\n{\n \"diagnosis_report\": {\n \"task_id\": \"pain-xxx\",\n \"summary\": \"One-line root cause\",\n \"causal_chain\": [\n { \"why\": 1, \"answer\": \"...\", \"evidence\": \"file:line\" }\n ],\n \"root_cause\": {\n \"category\": \"Design|People|Assumption|Tooling\",\n \"description\": \"...\"\n },\n \"principle\": {\n \"trigger_pattern\": \"...\",\n \"action\": \"...\",\n \"abstracted_principle\": \"\u9AD8\u5EA6\u62BD\u8C61\u7684\u4E00\u53E5\u8BDD\u539F\u5219\"\n }\n }\n}\n```\n";
@@ -1,65 +0,0 @@
1
- /**
2
- * Diagnostician 协议常量
3
- *
4
- * 用于 Runtime V2 diagnostician prompt
5
- * 与 templates/langs/zh/skills/pd-diagnostician/SKILL.md 保持一致
6
- */
7
- /**
8
- * 诊断协议摘要(用于 HEARTBEAT.md 注入)
9
- *
10
- * 完整版见: templates/langs/zh/skills/pd-diagnostician/SKILL.md
11
- */
12
- export const DIAGNOSTICIAN_PROTOCOL_SUMMARY = `## Diagnostic Protocol (5 Whys)
13
-
14
- **Phase 1 - Evidence Gathering**:
15
- - Use the task input/context provided by Runtime V2 for pain context
16
- - Read .state/logs/events.jsonl recent entries
17
- - Search codebase for error patterns from Reason field
18
- - Record all evidence sources (file:line)
19
-
20
- **Phase 2 - Causal Chain (5 Whys)**:
21
- - Why 1: Surface symptom (what you can see)
22
- - Why 2: Direct cause (what triggered it)
23
- - Why 3: Process gap (what should have caught it)
24
- - Why 4: Design flaw (why the gap exists)
25
- - Why 5: Root cause (systemic issue to fix)
26
- - Each Why MUST have evidence. Stop at actionable root cause.
27
-
28
- **Phase 3 - Root Cause Classification**:
29
- Classify into one of:
30
- - **People**: Human error, missing knowledge, communication gap
31
- - **Design**: Architecture flaw, missing validation, poor abstraction
32
- - **Assumption**: Invalid assumption, outdated context, edge case
33
- - **Tooling**: Tool limitation, environment issue, dependency problem
34
- - For Design: analyze why existing hooks/rules didn't catch this
35
-
36
- **Phase 4 - Principle Extraction**:
37
- Extract a HIGHLY ABSTRACTED principle (not an operational rule):
38
- - **trigger_pattern**: regex/keywords for when this occurs
39
- - **action**: what to do differently
40
- - **abstracted_principle**: ONE sentence, max 40 chars, cross-scenario applicable
41
- - ❌ Bad: "写入前检查目录是否存在" (too specific)
42
- - ✅ Good: "任何写入操作必须确保目标环境的完整性" (abstract, reusable)
43
-
44
- **Output Format** (single JSON object):
45
- \`\`\`json
46
- {
47
- "diagnosis_report": {
48
- "task_id": "pain-xxx",
49
- "summary": "One-line root cause",
50
- "causal_chain": [
51
- { "why": 1, "answer": "...", "evidence": "file:line" }
52
- ],
53
- "root_cause": {
54
- "category": "Design|People|Assumption|Tooling",
55
- "description": "..."
56
- },
57
- "principle": {
58
- "trigger_pattern": "...",
59
- "action": "...",
60
- "abstracted_principle": "高度抽象的一句话原则"
61
- }
62
- }
63
- }
64
- \`\`\`
65
- `;