create-principles-disciple 1.120.6 → 1.121.1

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 (92) hide show
  1. package/console/dist/server/index.js +4 -0
  2. package/console/dist/server/models/GovernanceConsoleModel.d.ts +10 -2
  3. package/console/dist/server/models/GovernanceConsoleModel.js +28 -3
  4. package/console/dist/server/routes/failed-tasks.d.ts +9 -5
  5. package/console/dist/server/routes/failed-tasks.js +220 -8
  6. package/console/dist/ui/api.d.ts +4 -3
  7. package/console/dist/ui/api.js +14 -2
  8. package/console/dist/ui/i18n/en.json +12 -0
  9. package/console/dist/ui/i18n/zh-CN.json +12 -0
  10. package/console/dist/ui/pages/failed-tasks/FailedTasksPage.js +78 -5
  11. package/console/dist/ui/pages/focus/FocusPage.js +5 -1
  12. package/console/dist/ui/utils/validators.d.ts +11 -0
  13. package/console/dist/ui/utils/validators.js +32 -0
  14. package/console/dist/web/assets/app.js +169 -6
  15. package/core/dist/runtime-v2/__tests__/owner-retry.test.d.ts +2 -0
  16. package/core/dist/runtime-v2/__tests__/owner-retry.test.d.ts.map +1 -0
  17. package/core/dist/runtime-v2/__tests__/owner-retry.test.js +144 -0
  18. package/core/dist/runtime-v2/__tests__/owner-retry.test.js.map +1 -0
  19. package/core/dist/runtime-v2/__tests__/pain-diagnosis-persistence.test.d.ts +2 -0
  20. package/core/dist/runtime-v2/__tests__/pain-diagnosis-persistence.test.d.ts.map +1 -0
  21. package/core/dist/runtime-v2/__tests__/pain-diagnosis-persistence.test.js +293 -0
  22. package/core/dist/runtime-v2/__tests__/pain-diagnosis-persistence.test.js.map +1 -0
  23. package/core/dist/runtime-v2/__tests__/recovery-actions-log.test.d.ts +2 -0
  24. package/core/dist/runtime-v2/__tests__/recovery-actions-log.test.d.ts.map +1 -0
  25. package/core/dist/runtime-v2/__tests__/recovery-actions-log.test.js +112 -0
  26. package/core/dist/runtime-v2/__tests__/recovery-actions-log.test.js.map +1 -0
  27. package/core/dist/runtime-v2/diagnostician/rootcause-prompt-builder.d.ts +10 -0
  28. package/core/dist/runtime-v2/diagnostician/rootcause-prompt-builder.d.ts.map +1 -1
  29. package/core/dist/runtime-v2/diagnostician/rootcause-prompt-builder.js +40 -4
  30. package/core/dist/runtime-v2/diagnostician/rootcause-prompt-builder.js.map +1 -1
  31. package/core/dist/runtime-v2/diagnostician-prompt-builder.d.ts +6 -0
  32. package/core/dist/runtime-v2/diagnostician-prompt-builder.d.ts.map +1 -1
  33. package/core/dist/runtime-v2/diagnostician-prompt-builder.js.map +1 -1
  34. package/core/dist/runtime-v2/feature-flags/__tests__/feature-flag-contract.test.js +28 -0
  35. package/core/dist/runtime-v2/feature-flags/__tests__/feature-flag-contract.test.js.map +1 -1
  36. package/core/dist/runtime-v2/feature-flags/__tests__/pain-diagnosis-persistence-flag.test.d.ts +2 -0
  37. package/core/dist/runtime-v2/feature-flags/__tests__/pain-diagnosis-persistence-flag.test.d.ts.map +1 -0
  38. package/core/dist/runtime-v2/feature-flags/__tests__/pain-diagnosis-persistence-flag.test.js +44 -0
  39. package/core/dist/runtime-v2/feature-flags/__tests__/pain-diagnosis-persistence-flag.test.js.map +1 -0
  40. package/core/dist/runtime-v2/feature-flags/feature-flag-contract.d.ts.map +1 -1
  41. package/core/dist/runtime-v2/feature-flags/feature-flag-contract.js +18 -0
  42. package/core/dist/runtime-v2/feature-flags/feature-flag-contract.js.map +1 -1
  43. package/core/dist/runtime-v2/index.d.ts +5 -0
  44. package/core/dist/runtime-v2/index.d.ts.map +1 -1
  45. package/core/dist/runtime-v2/index.js +5 -0
  46. package/core/dist/runtime-v2/index.js.map +1 -1
  47. package/core/dist/runtime-v2/internalization/diag-rootcause-runner.d.ts.map +1 -1
  48. package/core/dist/runtime-v2/internalization/diag-rootcause-runner.js +5 -0
  49. package/core/dist/runtime-v2/internalization/diag-rootcause-runner.js.map +1 -1
  50. package/core/dist/runtime-v2/internalization/owner-retry.d.ts +49 -0
  51. package/core/dist/runtime-v2/internalization/owner-retry.d.ts.map +1 -0
  52. package/core/dist/runtime-v2/internalization/owner-retry.js +51 -0
  53. package/core/dist/runtime-v2/internalization/owner-retry.js.map +1 -0
  54. package/core/dist/runtime-v2/pain-signal-bridge.d.ts +19 -0
  55. package/core/dist/runtime-v2/pain-signal-bridge.d.ts.map +1 -1
  56. package/core/dist/runtime-v2/pain-signal-bridge.js +61 -0
  57. package/core/dist/runtime-v2/pain-signal-bridge.js.map +1 -1
  58. package/core/dist/runtime-v2/pain-signal-runtime-factory.d.ts +12 -0
  59. package/core/dist/runtime-v2/pain-signal-runtime-factory.d.ts.map +1 -1
  60. package/core/dist/runtime-v2/pain-signal-runtime-factory.js +48 -2
  61. package/core/dist/runtime-v2/pain-signal-runtime-factory.js.map +1 -1
  62. package/core/dist/runtime-v2/recovery-actions-log.d.ts +46 -0
  63. package/core/dist/runtime-v2/recovery-actions-log.d.ts.map +1 -0
  64. package/core/dist/runtime-v2/recovery-actions-log.js +116 -0
  65. package/core/dist/runtime-v2/recovery-actions-log.js.map +1 -0
  66. package/core/dist/runtime-v2/recovery-sweep-service.d.ts +8 -0
  67. package/core/dist/runtime-v2/recovery-sweep-service.d.ts.map +1 -1
  68. package/core/dist/runtime-v2/recovery-sweep-service.js +4 -0
  69. package/core/dist/runtime-v2/recovery-sweep-service.js.map +1 -1
  70. package/core/dist/runtime-v2/store/pain-diagnosis/pain-diagnosis-store.d.ts +77 -0
  71. package/core/dist/runtime-v2/store/pain-diagnosis/pain-diagnosis-store.d.ts.map +1 -0
  72. package/core/dist/runtime-v2/store/pain-diagnosis/pain-diagnosis-store.js +40 -0
  73. package/core/dist/runtime-v2/store/pain-diagnosis/pain-diagnosis-store.js.map +1 -0
  74. package/core/dist/runtime-v2/store/pain-diagnosis/sqlite-pain-diagnosis-store.d.ts +9 -0
  75. package/core/dist/runtime-v2/store/pain-diagnosis/sqlite-pain-diagnosis-store.d.ts.map +1 -0
  76. package/core/dist/runtime-v2/store/pain-diagnosis/sqlite-pain-diagnosis-store.js +118 -0
  77. package/core/dist/runtime-v2/store/pain-diagnosis/sqlite-pain-diagnosis-store.js.map +1 -0
  78. package/core/dist/runtime-v2/store/runtime-state-manager.d.ts +11 -0
  79. package/core/dist/runtime-v2/store/runtime-state-manager.d.ts.map +1 -1
  80. package/core/dist/runtime-v2/store/runtime-state-manager.js +18 -0
  81. package/core/dist/runtime-v2/store/runtime-state-manager.js.map +1 -1
  82. package/core/dist/runtime-v2/store/sqlite-connection.d.ts.map +1 -1
  83. package/core/dist/runtime-v2/store/sqlite-connection.js +23 -0
  84. package/core/dist/runtime-v2/store/sqlite-connection.js.map +1 -1
  85. package/host-runtime/dist/production-pain-evidence.js +9 -2
  86. package/package.json +2 -2
  87. package/pd-cli/dist/commands/runtime-internalization-retry.d.ts.map +1 -1
  88. package/pd-cli/dist/commands/runtime-internalization-retry.js +32 -19
  89. package/pd-cli/dist/commands/runtime-internalization-retry.js.map +1 -1
  90. package/plugin/dist/bundle.js +438 -415
  91. package/plugin/openclaw.plugin.json +1 -1
  92. package/plugin/package.json +1 -1
@@ -211,10 +211,14 @@ async function initServices(workspaceDir, authConfig) {
211
211
  const feedbackChannelEnabled = pdFlags.flags.feedback_channel?.enabled ?? false;
212
212
  const failedTasksObservabilityEnabled = pdFlags.flags.failed_tasks_observability?.enabled ?? true;
213
213
  const governanceProjectionEnabled = pdFlags.flags.principle_governance_projection_v2?.enabled ?? false;
214
+ // Governance Recovery Actions v1: fail-closed — recovery stays disabled unless
215
+ // explicitly enabled (default off keeps the Console read-only).
216
+ const failedTaskRecoveryEnabled = pdFlags.flags.failed_task_recovery_console?.enabled ?? false;
214
217
  const feedbackFlags = {
215
218
  feedback_channel: { enabled: feedbackChannelEnabled },
216
219
  failed_tasks_observability: { enabled: failedTasksObservabilityEnabled },
217
220
  principle_governance_projection_v2: { enabled: governanceProjectionEnabled },
221
+ failed_task_recovery_console: { enabled: failedTaskRecoveryEnabled },
218
222
  };
219
223
  if (!configResult.ok) {
220
224
  console.warn('[pd-console] PD config loading failed (using defaults for feedback channel):', configResult.errors.map(e => e.reason).join('; '));
@@ -64,11 +64,19 @@ export interface DegradedFailureSummary {
64
64
  * Frontend maps these via i18n; some codes support interpolation
65
65
  * (e.g. {{count}} from pendingReviewCount).
66
66
  */
67
- export type StateReasonCode = 'state_db_missing' | 'no_pipeline_activity' | 'pending_approvals' | 'pipeline_active' | 'consumed_candidates' | 'degraded_state';
68
- export type NextActionCode = 'run_config_doctor' | 'wait_for_pipeline' | 'review_approvals' | 'check_degraded_signals' | 'check_pipeline_status';
67
+ export type StateReasonCode = 'state_db_missing' | 'no_pipeline_activity' | 'pending_approvals' | 'tasks_need_human_review' | 'pipeline_active' | 'consumed_candidates' | 'degraded_state';
68
+ export type NextActionCode = 'run_config_doctor' | 'wait_for_pipeline' | 'review_approvals' | 'review_failed_tasks' | 'check_degraded_signals' | 'check_pipeline_status';
69
69
  export interface GovernanceQueueResponse {
70
70
  /** Number of pending approvals awaiting owner review */
71
71
  pendingReviewCount: number;
72
+ /**
73
+ * Governance Recovery Actions v1 (Phase 0 signal fix): number of
74
+ * internalization tasks in needs_human_review status (all peer-runner
75
+ * kinds, incl. rollout_reviewer). These are owner-attention items — they
76
+ * wait for an explicit recovery decision, so unlike degraded signals they
77
+ * carry no time window. Set only when > 0 (mirrors evidenceInProgressCount).
78
+ */
79
+ pendingHumanReviewCount?: number;
72
80
  /** Number of high/critical risk pending approvals */
73
81
  behaviorDeviationCount: number;
74
82
  /** Stagnation signals for approvals older than 7 days */
@@ -124,14 +124,27 @@ export class GovernanceConsoleModel {
124
124
  // 2. Check internalization pipeline activity (tasks)
125
125
  // PRI-556: pipeline activity still counts ALL tasks (drives in_progress),
126
126
  // but only in-window failures/retries produce degraded signals.
127
+ // Governance Recovery Actions v1 (Phase 0 signal fix): the kind list now
128
+ // includes rollout_reviewer (previously invisible even for degraded
129
+ // signals), and needs_human_review tasks surface as owner-attention
130
+ // items via pendingHumanReviewCount — previously NO stage's
131
+ // needs_human_review entered this queue.
127
132
  let hasInternalizationTasks = false;
128
133
  const retryWaitDetails = [];
129
134
  const failedDetails = [];
135
+ let pendingHumanReviewCount = 0;
130
136
  try {
131
- const tasks = db.prepare(`SELECT task_id, task_kind, status, last_error, updated_at FROM tasks WHERE task_kind IN ('dreamer', 'philosopher', 'scribe', 'artificer', 'evaluator')`).all();
137
+ const tasks = db.prepare(`SELECT task_id, task_kind, status, last_error, updated_at FROM tasks WHERE task_kind IN ('dreamer', 'philosopher', 'scribe', 'artificer', 'evaluator', 'rollout_reviewer')`).all();
132
138
  hasInternalizationTasks = tasks.length > 0;
133
139
  const actionableCutoff = Date.now() - DEGRADED_SIGNAL_WINDOW_MS;
134
140
  for (const task of tasks) {
141
+ if (task.status === 'needs_human_review') {
142
+ // Owner-attention item — NO time window: it waits for an explicit
143
+ // recovery decision (unlike terminal `failed` rows, which PRI-556
144
+ // windowed to avoid permanent signal pollution).
145
+ pendingHumanReviewCount += 1;
146
+ continue;
147
+ }
135
148
  if (task.status !== 'retry_wait' && task.status !== 'failed')
136
149
  continue;
137
150
  // updated_at is untrusted row data — malformed timestamps fall
@@ -207,7 +220,9 @@ export class GovernanceConsoleModel {
207
220
  });
208
221
  }
209
222
  // ── Determine governance state ──────────────────────────────────────
210
- const hasOwnerReadyItems = pendingReviewCount > 0;
223
+ // needs_human_review tasks are owner-attention items alongside pending
224
+ // approvals (Governance Recovery Actions v1 Phase 0): both flip the
225
+ // queue into owner_review_ready.
211
226
  const hasPipelineActivity = hasInternalizationTasks || hasConsumedCandidates || hasPendingCandidates;
212
227
  const hasDegradation = degradedSignals.length > 0;
213
228
  let governanceState;
@@ -216,13 +231,20 @@ export class GovernanceConsoleModel {
216
231
  let stateReason;
217
232
  let nextAction;
218
233
  let inProgressSummary;
219
- if (hasOwnerReadyItems) {
234
+ if (pendingReviewCount > 0) {
220
235
  governanceState = 'owner_review_ready';
221
236
  stateReasonCode = 'pending_approvals';
222
237
  nextActionCode = 'review_approvals';
223
238
  stateReason = `${pendingReviewCount} principle(s) pending your review and decision.`;
224
239
  nextAction = 'Review pending principles and approve, reject, or park.';
225
240
  }
241
+ else if (pendingHumanReviewCount > 0) {
242
+ governanceState = 'owner_review_ready';
243
+ stateReasonCode = 'tasks_need_human_review';
244
+ nextActionCode = 'review_failed_tasks';
245
+ stateReason = `${pendingHumanReviewCount} internalization task(s) are waiting for your review decision (needs_human_review).`;
246
+ nextAction = 'Open Failed Tasks to inspect and recover needs_human_review tasks.';
247
+ }
226
248
  else if (hasDegradation) {
227
249
  governanceState = 'degraded';
228
250
  stateReasonCode = 'degraded_state';
@@ -350,6 +372,9 @@ export class GovernanceConsoleModel {
350
372
  if (evidenceInProgressCount > 0) {
351
373
  response.evidenceInProgressCount = evidenceInProgressCount;
352
374
  }
375
+ if (pendingHumanReviewCount > 0) {
376
+ response.pendingHumanReviewCount = pendingHumanReviewCount;
377
+ }
353
378
  if (gateBlocksToday > 0) {
354
379
  response.gateBlocksToday = gateBlocksToday;
355
380
  }
@@ -14,11 +14,15 @@ export type FailedTasksContext = {
14
14
  * Handle requests to `/api/v1/failed-tasks*`.
15
15
  *
16
16
  * Routes:
17
- * GET /api/v1/failed-tasks — list failed/needs_human_review tasks
18
- * GET /api/v1/failed-tasks/:id — fetch single task detail (task + runs + lastError)
19
- * other methods 405 Method Not Allowed
17
+ * GET /api/v1/failed-tasks — list failed/needs_human_review tasks
18
+ * GET /api/v1/failed-tasks/:id — fetch single task detail (task + runs + lastError)
19
+ * POST /api/v1/failed-tasks/:id/recover Owner recovery (failed | needs_human_review → pending)
20
+ * other methods — 405 Method Not Allowed
20
21
  *
21
- * Feature flag gate: when `ctx.featureFlags.failed_tasks_observability.enabled`
22
- * is `false`, returns 403 `failed_tasks_observability_disabled`.
22
+ * Feature flag gates (checked before any DB access so a disabled flag
23
+ * short-circuits cleanly rc-9: error responses include reason + next action):
24
+ * - `failed_tasks_observability.enabled === false` → 403 for the whole route
25
+ * - `failed_task_recovery_console.enabled !== true` → 403 for POST recover
26
+ * (fail-closed mutation gate; default on, explicit disable keeps the Console read-only)
23
27
  */
24
28
  export declare function handleFailedTasksRoute(req: IncomingMessage, res: ServerResponse, ctx: FailedTasksContext): Promise<void>;
@@ -5,14 +5,26 @@
5
5
  // detail (task record + run history + last error category). Gated by the
6
6
  // failed_tasks_observability feature flag.
7
7
  //
8
+ // Governance Recovery Actions v1: POST /api/v1/failed-tasks/:id/recover lets the
9
+ // Owner recover a task from the Console. failed → pending reuses
10
+ // RecoverySweepService.recoverFailedTask; needs_human_review → pending reuses
11
+ // the extracted owner-retry sequence (ownerRetryNeedsHumanReviewTask — the same
12
+ // implementation `pd runtime internalization retry --confirm` calls). No
13
+ // recovery logic is reimplemented here. Gated by failed_task_recovery_console
14
+ // (default off → Console stays read-only). Every successful recovery appends a
15
+ // RecoveryAction audit record (.state/recovery_actions.jsonl).
16
+ //
8
17
  // Trust boundary (rc-1, rc-2): all query string values are read via
9
18
  // URLSearchParams.get() and treated as `unknown` until validated with typeof /
10
- // Number.isFinite. Store return values are typed by the core contract and
11
- // further validated by SqliteTaskStore's row readers (rc-1 at the DB boundary).
19
+ // Number.isFinite. POST bodies are parsed to `unknown` and field-checked with
20
+ // Object.hasOwn + typeof before use. Store return values are typed by the core
21
+ // contract and further validated by SqliteTaskStore's row readers (rc-1 at the
22
+ // DB boundary).
12
23
  import * as fs from 'node:fs';
13
24
  import * as path from 'node:path';
14
- import { SqliteConnection, SqliteTaskStore, } from '@principles/core/runtime-v2';
25
+ import { SqliteConnection, SqliteTaskStore, PDRuntimeError, createRecoverySweepService, appendRecoveryAction, } from '@principles/core/runtime-v2';
15
26
  import { sendSuccess, sendError, sendNotFound, sendMethodNotAllowed, sendBadRequest, } from '../utils/response.js';
27
+ import { readBody } from '../utils/request.js';
16
28
  const entries = new Map();
17
29
  function stateDbExists(workspaceDir) {
18
30
  return fs.existsSync(path.join(workspaceDir, '.pd', 'state.db'));
@@ -79,16 +91,143 @@ function extractQueryParams(req) {
79
91
  return null;
80
92
  return new URLSearchParams(urlStr.slice(qIdx + 1));
81
93
  }
94
+ // ── Recovery helpers (Governance Recovery Actions v1) ───────────────────────
95
+ const MAX_REASON_LENGTH = 2000;
96
+ function isRecord(value) {
97
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
98
+ }
99
+ function tryParseJson(body) {
100
+ try {
101
+ return JSON.parse(body);
102
+ }
103
+ catch {
104
+ return undefined;
105
+ }
106
+ }
107
+ /**
108
+ * Parse the optional recover request body. Accepts an empty body (reason is
109
+ * optional) or a JSON object with an optional string `reason` field
110
+ * (rc-1/rc-2: parsed value stays `unknown` until Object.hasOwn + typeof
111
+ * checks pass). Returns a discriminated result; `ok === false` carries the
112
+ * bad-request message.
113
+ */
114
+ function parseRecoverBody(body) {
115
+ if (body.trim().length === 0)
116
+ return { ok: true };
117
+ const raw = tryParseJson(body);
118
+ if (raw === undefined)
119
+ return { ok: false, error: 'Request body must be valid JSON' };
120
+ if (!isRecord(raw))
121
+ return { ok: false, error: 'Request body must be a JSON object' };
122
+ if (Object.hasOwn(raw, 'reason')) {
123
+ if (typeof raw.reason !== 'string') {
124
+ return { ok: false, error: 'reason must be a string' };
125
+ }
126
+ if (raw.reason.length > MAX_REASON_LENGTH) {
127
+ return { ok: false, error: `reason must be at most ${MAX_REASON_LENGTH} characters` };
128
+ }
129
+ }
130
+ const reason = typeof raw.reason === 'string' && raw.reason.length > 0 ? raw.reason : undefined;
131
+ return { ok: true, reason };
132
+ }
133
+ /**
134
+ * Dispatch one recovery attempt through the shared core services.
135
+ *
136
+ * Status dispatch (SPEC §6.1/§6.2): failed → recoverFailedTask; then
137
+ * needs_human_review → owner authority reset. A null first result means
138
+ * "missing or not failed"; the second call distinguishes not_found /
139
+ * wrong-status / metadata_invalid. No recovery logic lives here.
140
+ */
141
+ async function dispatchRecovery(workspaceDir, taskId) {
142
+ const { service, close } = await createRecoverySweepService({ workspaceDir });
143
+ try {
144
+ // failed → pending (crash-retry semantics: completionIntent preserved)
145
+ try {
146
+ const failedResult = await service.recoverFailedTask(taskId);
147
+ if (failedResult) {
148
+ return {
149
+ ok: true,
150
+ result: 'recovered',
151
+ previousStatus: failedResult.previousStatus,
152
+ newStatus: failedResult.newStatus,
153
+ };
154
+ }
155
+ }
156
+ catch (err) {
157
+ if (err instanceof PDRuntimeError && err.category === 'input_invalid') {
158
+ // attempt budget exhausted; Console v1 does not expose force
159
+ return {
160
+ ok: false,
161
+ failure: {
162
+ httpStatus: 409,
163
+ errorCode: 'task_attempts_exhausted',
164
+ message: err.message,
165
+ nextAction: 'This task exhausted its attempt budget. Recover it with force via CLI: pd runtime recovery failed-tasks --confirm --force',
166
+ },
167
+ };
168
+ }
169
+ throw err;
170
+ }
171
+ // not failed (or missing) → owner authority reset path
172
+ const outcome = await service.recoverNeedsHumanReviewTask(taskId);
173
+ if (outcome.status === 'requeued') {
174
+ return {
175
+ ok: true,
176
+ result: 'requeued',
177
+ previousStatus: outcome.previousStatus,
178
+ newStatus: 'pending',
179
+ };
180
+ }
181
+ if (outcome.status === 'not_found') {
182
+ return {
183
+ ok: false,
184
+ failure: {
185
+ httpStatus: 404,
186
+ errorCode: 'task_not_found',
187
+ message: `Task ${taskId} not found`,
188
+ },
189
+ };
190
+ }
191
+ if (outcome.status === 'metadata_invalid') {
192
+ return {
193
+ ok: false,
194
+ failure: {
195
+ httpStatus: 409,
196
+ errorCode: 'metadata_invalid',
197
+ message: 'Task metadata failed PI hydration; a recovery now would risk a partial authority reset.',
198
+ nextAction: 'Run pd runtime internalization integrity --json to inspect the task metadata',
199
+ },
200
+ };
201
+ }
202
+ return {
203
+ ok: false,
204
+ failure: {
205
+ httpStatus: 409,
206
+ errorCode: 'task_not_recoverable',
207
+ message: `Task ${taskId} is in status '${outcome.previousStatus}' — only failed / needs_human_review tasks are recoverable.`,
208
+ nextAction: 'Wait for the current attempt to finish, or check pipeline status: pd runtime internalization queue --json',
209
+ extras: { previousStatus: outcome.previousStatus },
210
+ },
211
+ };
212
+ }
213
+ finally {
214
+ await close();
215
+ }
216
+ }
82
217
  /**
83
218
  * Handle requests to `/api/v1/failed-tasks*`.
84
219
  *
85
220
  * Routes:
86
- * GET /api/v1/failed-tasks — list failed/needs_human_review tasks
87
- * GET /api/v1/failed-tasks/:id — fetch single task detail (task + runs + lastError)
88
- * other methods 405 Method Not Allowed
221
+ * GET /api/v1/failed-tasks — list failed/needs_human_review tasks
222
+ * GET /api/v1/failed-tasks/:id — fetch single task detail (task + runs + lastError)
223
+ * POST /api/v1/failed-tasks/:id/recover Owner recovery (failed | needs_human_review → pending)
224
+ * other methods — 405 Method Not Allowed
89
225
  *
90
- * Feature flag gate: when `ctx.featureFlags.failed_tasks_observability.enabled`
91
- * is `false`, returns 403 `failed_tasks_observability_disabled`.
226
+ * Feature flag gates (checked before any DB access so a disabled flag
227
+ * short-circuits cleanly rc-9: error responses include reason + next action):
228
+ * - `failed_tasks_observability.enabled === false` → 403 for the whole route
229
+ * - `failed_task_recovery_console.enabled !== true` → 403 for POST recover
230
+ * (fail-closed mutation gate; default on, explicit disable keeps the Console read-only)
92
231
  */
93
232
  export async function handleFailedTasksRoute(req, res, ctx) {
94
233
  const { workspaceDir, subPath } = ctx;
@@ -173,6 +312,79 @@ export async function handleFailedTasksRoute(req, res, ctx) {
173
312
  }
174
313
  return;
175
314
  }
315
+ // Recovery: POST /api/v1/failed-tasks/:id/recover (Governance Recovery
316
+ // Actions v1). Checked before the generic /:id detail branch.
317
+ // eslint-disable-next-line @typescript-eslint/prefer-regexp-exec -- String#match is semantically identical for a non-global regex; the repo's security write-gate false-positives on `<expr>.exec(userInput)` as command injection, so RegExp#exec is not writable here.
318
+ const recoverMatch = subPath.match(/^\/([^/]+)\/recover$/);
319
+ if (recoverMatch) {
320
+ // recoverMatch[1] = captured task id (segment between the leading '/'
321
+ // and the trailing '/recover')
322
+ const taskId = recoverMatch[1] ?? '';
323
+ // Fail-closed gate: this is a mutation endpoint — recovery is enabled by
324
+ // default (2026-08-24 owner decision) and 403 only when explicitly disabled
325
+ // via config (features.failed_task_recovery_console.enabled: false).
326
+ if (ctx.featureFlags?.failed_task_recovery_console?.enabled !== true) {
327
+ sendError(res, 403, 'failed_task_recovery_console_disabled', 'failed_task_recovery_console feature flag is disabled. Enable it via .pd/config.yaml (features.failed_task_recovery_console.enabled: true) to allow recovery from Console.');
328
+ return;
329
+ }
330
+ if (method !== 'POST') {
331
+ sendMethodNotAllowed(res);
332
+ return;
333
+ }
334
+ try {
335
+ if (taskId.length === 0 || !stateDbExists(workspaceDir)) {
336
+ sendNotFound(res, `Task ${taskId} not found`);
337
+ return;
338
+ }
339
+ let bodyResult;
340
+ try {
341
+ const body = await readBody(req);
342
+ bodyResult = parseRecoverBody(body);
343
+ }
344
+ catch (err) {
345
+ sendBadRequest(res, err instanceof Error ? err.message : 'Failed to read request body');
346
+ return;
347
+ }
348
+ if (bodyResult.ok === false) {
349
+ sendBadRequest(res, bodyResult.error);
350
+ return;
351
+ }
352
+ const dispatch = await dispatchRecovery(workspaceDir, taskId);
353
+ if (!dispatch.ok) {
354
+ sendError(res, dispatch.failure.httpStatus, dispatch.failure.errorCode, dispatch.failure.message, dispatch.failure.nextAction ? { nextAction: dispatch.failure.nextAction, ...dispatch.failure.extras } : dispatch.failure.extras);
355
+ return;
356
+ }
357
+ // Audit the owner action (SPEC §10). Best-effort: the recovery is
358
+ // already committed; an audit-write failure must not report the whole
359
+ // action as failed, and it is never silent (rc-9).
360
+ try {
361
+ appendRecoveryAction(workspaceDir, {
362
+ taskId,
363
+ previousStatus: dispatch.previousStatus,
364
+ result: dispatch.result,
365
+ operator: 'console',
366
+ reason: bodyResult.reason ?? null,
367
+ });
368
+ }
369
+ catch (auditErr) {
370
+ console.warn('[failed-tasks] recovery audit append failed:', auditErr instanceof Error ? auditErr.message : String(auditErr));
371
+ }
372
+ // SPEC §6.4: "Recovery Accepted", not "Task Completed" — execution is
373
+ // asynchronous; the task merely re-enters the pending → leased →
374
+ // running → succeeded cycle.
375
+ sendSuccess(res, {
376
+ taskId,
377
+ previousStatus: dispatch.previousStatus,
378
+ newStatus: dispatch.newStatus,
379
+ result: dispatch.result,
380
+ nextAction: 'Recovery accepted. The task is pending again and will be picked up by the internalization consumer (or advance it manually: pd runtime internalization run-once).',
381
+ });
382
+ }
383
+ catch (err) {
384
+ sendError(res, 500, 'failed_task_recovery_error', errorMessage(err));
385
+ }
386
+ return;
387
+ }
176
388
  // Detail: GET /api/v1/failed-tasks/:id
177
389
  const id = subPath.replace(/^\//, '');
178
390
  if (id.length === 0) {
@@ -1,7 +1,7 @@
1
1
  import type { ApiResponse } from "../types.js";
2
2
  import { listActiveSignalKeywords, listPendingSignalTerms, fetchKeywordStore, fetchPendingTerms, updateKeywordStore, admitPendingTerm, rejectPendingTerm } from "./utils/signal-keywords-api.js";
3
3
  import type { OwnerGovernanceView } from '@principles/core/runtime-v2';
4
- import type { FeedbackReportData, FeedbackDraftSummaryData, FeedbackDraftEnvelopeData, FeedbackChannelsData, FeedbackSubmitResultData, DeleteEnvelopeData, WorkspaceEntryData, RemovedEnvelopeData, SyncResultData, ConfigSummaryData, ConfigCatalogData, AgentBindingUpdateData, DefaultRuntimeUpdateData, RuntimeProfileMutationData, ReadinessCheckData, FeatureFlagUpdateData, OutputLanguageData, GovernanceQueueData, ActivationsData, DisableActivationData, LifecycleMetricsData, UpdateStatusData, UpdateHistoryData, ApplyUpdateResultData, RollbackResultData, ApprovalRecordData, PrinciplesListData, ApprovalsGroupedData, EvidenceChainData, TrajectoryData, IntentSummaryData, IntentDecisionRecordData, IntentDecisionResultData, IntentDecisionSummaryData, FollowUpResponseData, IntentInitResultData, IntentSaveResultData, IntentRawContentData, IntentVersionData, RuleCodeOwnerReviewData, RuleCodeMutationData } from "./utils/validators.js";
4
+ import type { FeedbackReportData, FeedbackDraftSummaryData, FeedbackDraftEnvelopeData, FeedbackChannelsData, FeedbackSubmitResultData, DeleteEnvelopeData, WorkspaceEntryData, RemovedEnvelopeData, SyncResultData, ConfigSummaryData, ConfigCatalogData, AgentBindingUpdateData, DefaultRuntimeUpdateData, RuntimeProfileMutationData, ReadinessCheckData, FeatureFlagUpdateData, OutputLanguageData, GovernanceQueueData, RecoveryResultData, ActivationsData, DisableActivationData, LifecycleMetricsData, UpdateStatusData, UpdateHistoryData, ApplyUpdateResultData, RollbackResultData, ApprovalRecordData, PrinciplesListData, ApprovalsGroupedData, EvidenceChainData, TrajectoryData, IntentSummaryData, IntentDecisionRecordData, IntentDecisionResultData, IntentDecisionSummaryData, FollowUpResponseData, IntentInitResultData, IntentSaveResultData, IntentRawContentData, IntentVersionData, RuleCodeOwnerReviewData, RuleCodeMutationData } from "./utils/validators.js";
5
5
  declare function getToken(): string | null;
6
6
  declare function setToken(token: string): void;
7
7
  declare function clearToken(): void;
@@ -129,6 +129,7 @@ declare function patchFeatureFlag(featureName: string, enabled: boolean): Promis
129
129
  declare function fetchOutputLanguage(): Promise<ApiResponse<OutputLanguageData>>;
130
130
  declare function updateOutputLanguage(outputLanguage: string): Promise<ApiResponse<OutputLanguageData>>;
131
131
  declare function fetchGovernanceQueue(): Promise<ApiResponse<GovernanceQueueData>>;
132
+ declare function recoverFailedTask(taskId: string, reason?: string): Promise<ApiResponse<RecoveryResultData>>;
132
133
  declare function fetchApprovalsGrouped(): Promise<ApiResponse<ApprovalsGroupedData>>;
133
134
  declare function fetchAllActivations(): Promise<ApiResponse<ActivationsData>>;
134
135
  declare function disableActivation(activationId: string): Promise<ApiResponse<DisableActivationData>>;
@@ -195,8 +196,8 @@ export interface FollowUpPayload {
195
196
  candidateId?: string;
196
197
  }
197
198
  declare function dispatchFollowUp(decisionId: string, payload: FollowUpPayload): Promise<ApiResponse<FollowUpResponseData>>;
198
- export { request, listActiveSignalKeywords, listPendingSignalTerms, fetchKeywordStore, fetchPendingTerms, updateKeywordStore, admitPendingTerm, rejectPendingTerm, getToken, setToken, clearToken, checkAuth, approveApproval, rejectApproval, editApproval, fetchPrinciples, fetchPrincipleDetail, fetchPrincipleGovernance, fetchPrincipleTrajectory, fetchPrincipleReceipts, fetchReceiptCounts, archivePrinciple, unarchivePrinciple, createFeedbackReport, listFeedbackReports, getFeedbackReport, deleteFeedbackReport, fetchFeedbackChannels, submitFeedbackReport, markFeedbackReportSent, fetchConfigSummary, fetchConfigCatalog, updateAgentBinding, updateDefaultRuntime, createRuntimeProfile, updateRuntimeProfile, deleteRuntimeProfile, fetchAgentReadiness, patchFeatureFlag, fetchOutputLanguage, updateOutputLanguage, fetchWorkspaces, addWorkspace, removeWorkspace, syncWorkspace, fetchGovernanceQueue, fetchApprovalsGrouped, fetchAllActivations, disableActivation, fetchRuleCodeOwnerReview, ruleCodeDecision, pauseAllRuleCode, releaseRuleCodePause, fetchLifecycleMetrics, fetchUpdateStatus, fetchUpdateHistory, applyUpdate, applyFullUpdate, rollbackUpdate, fetchEvidenceChain, fetchIntentSummary, fetchIntentContent, createIntentTemplate, saveIntentContent, fetchIntentVersions, recordIntentDecision, listIntentDecisionsByPainId, listIntentDecisionsByTaskId, fetchIntentDecisionSummary, dispatchFollowUp, };
199
- export type { FeedbackReportData, FeedbackDraftSummaryData, FeedbackDraftEnvelopeData, FeedbackChannelsData, FeedbackSubmitResultData, DeleteEnvelopeData, WorkspaceEntryData, RemovedEnvelopeData, SyncResultData, ConfigSummaryData, ConfigCatalogData, AgentBindingUpdateData, DefaultRuntimeUpdateData, RuntimeProfileMutationData, ReadinessCheckData, FeatureFlagUpdateData, OutputLanguageData, GovernanceQueueData, ActivationsData, DisableActivationData, RuleCodeOwnerReviewData, RuleCodeMutationData, LifecycleMetricsData, UpdateStatusData, UpdateHistoryData, ApplyUpdateResultData, RollbackResultData, ApprovalRecordData, PrinciplesListData, ApprovalsGroupedData, EvidenceChainData, EvidenceChainRecordData, EvidenceChainStateData, TrajectoryData, TrajectoryStageData, IntentSummaryData, IntentSectionsData, IntentDocWarningData, IntentDecisionRecordData, IntentDecisionResultData, IntentDecisionSummaryData, FollowUpResponseData, LinkCandidateFollowUpData, GuideRulehostFollowUpData, GeneratePatchProposalFollowUpData, IntentVersionEntry, IntentVersionData, } from "./utils/validators.js";
199
+ export { request, listActiveSignalKeywords, listPendingSignalTerms, fetchKeywordStore, fetchPendingTerms, updateKeywordStore, admitPendingTerm, rejectPendingTerm, getToken, setToken, clearToken, checkAuth, approveApproval, rejectApproval, editApproval, fetchPrinciples, fetchPrincipleDetail, fetchPrincipleGovernance, fetchPrincipleTrajectory, fetchPrincipleReceipts, fetchReceiptCounts, archivePrinciple, unarchivePrinciple, createFeedbackReport, listFeedbackReports, getFeedbackReport, deleteFeedbackReport, fetchFeedbackChannels, submitFeedbackReport, markFeedbackReportSent, fetchConfigSummary, fetchConfigCatalog, updateAgentBinding, updateDefaultRuntime, createRuntimeProfile, updateRuntimeProfile, deleteRuntimeProfile, fetchAgentReadiness, patchFeatureFlag, fetchOutputLanguage, updateOutputLanguage, fetchWorkspaces, addWorkspace, removeWorkspace, syncWorkspace, fetchGovernanceQueue, recoverFailedTask, fetchApprovalsGrouped, fetchAllActivations, disableActivation, fetchRuleCodeOwnerReview, ruleCodeDecision, pauseAllRuleCode, releaseRuleCodePause, fetchLifecycleMetrics, fetchUpdateStatus, fetchUpdateHistory, applyUpdate, applyFullUpdate, rollbackUpdate, fetchEvidenceChain, fetchIntentSummary, fetchIntentContent, createIntentTemplate, saveIntentContent, fetchIntentVersions, recordIntentDecision, listIntentDecisionsByPainId, listIntentDecisionsByTaskId, fetchIntentDecisionSummary, dispatchFollowUp, };
200
+ export type { FeedbackReportData, FeedbackDraftSummaryData, FeedbackDraftEnvelopeData, FeedbackChannelsData, FeedbackSubmitResultData, DeleteEnvelopeData, WorkspaceEntryData, RemovedEnvelopeData, SyncResultData, ConfigSummaryData, ConfigCatalogData, AgentBindingUpdateData, DefaultRuntimeUpdateData, RuntimeProfileMutationData, ReadinessCheckData, FeatureFlagUpdateData, OutputLanguageData, GovernanceQueueData, RecoveryResultData, ActivationsData, DisableActivationData, RuleCodeOwnerReviewData, RuleCodeMutationData, LifecycleMetricsData, UpdateStatusData, UpdateHistoryData, ApplyUpdateResultData, RollbackResultData, ApprovalRecordData, PrinciplesListData, ApprovalsGroupedData, EvidenceChainData, EvidenceChainRecordData, EvidenceChainStateData, TrajectoryData, TrajectoryStageData, IntentSummaryData, IntentSectionsData, IntentDocWarningData, IntentDecisionRecordData, IntentDecisionResultData, IntentDecisionSummaryData, FollowUpResponseData, LinkCandidateFollowUpData, GuideRulehostFollowUpData, GeneratePatchProposalFollowUpData, IntentVersionEntry, IntentVersionData, } from "./utils/validators.js";
200
201
  export type { SignalKeyword, PendingSignalTerm } from "./utils/signal-keywords-types.js";
201
202
  export type { ActivationRecordData as ActivationRecord } from "./utils/validators.js";
202
203
  export type { ApprovalRecordData as ApprovalRecord } from "./utils/validators.js";
@@ -1,5 +1,5 @@
1
1
  import { listActiveSignalKeywords, listPendingSignalTerms, fetchKeywordStore, fetchPendingTerms, updateKeywordStore, admitPendingTerm, rejectPendingTerm, } from "./utils/signal-keywords-api.js";
2
- import { validateErrorResponse, validateHeaders, validateFeedbackReport, validateFeedbackDraftsList, validateFeedbackDraftEnvelope, validateFeedbackChannels, validateFeedbackSubmitResult, validateDeleteEnvelope, validateWorkspaceEntry, validateWorkspaceList, validateRemovedEnvelope, validateSyncResult, validateConfigSummary, validateConfigCatalog, validateAgentBindingUpdate, validateDefaultRuntimeUpdate, validateRuntimeProfileMutation, validateReadinessCheck, validateFeatureFlagUpdate, validateOutputLanguage, validateGovernanceQueue, validateActivations, validateDisableActivation, validateLifecycleMetrics, validateUpdateStatus, validateUpdateHistory, validateApplyUpdateResult, validateRollbackResult, validateApprovalRecordDirect, validatePrinciplesList, validateApprovalsGrouped, validateEvidenceChain, validateTrajectoryData, validateIntentSummary, validateIntentDecisionList, validateIntentDecisionResult, validateIntentDecisionSummary, validateFollowUpResponse, validateIntentInitResult, validateIntentSaveResult, validateIntentRawContent, validateIntentVersions, validateOwnerGovernanceView, validateRuleCodeOwnerReview, validateRuleCodeMutation, } from "./utils/validators.js";
2
+ import { validateErrorResponse, validateHeaders, validateFeedbackReport, validateFeedbackDraftsList, validateFeedbackDraftEnvelope, validateFeedbackChannels, validateFeedbackSubmitResult, validateDeleteEnvelope, validateWorkspaceEntry, validateWorkspaceList, validateRemovedEnvelope, validateSyncResult, validateConfigSummary, validateConfigCatalog, validateAgentBindingUpdate, validateDefaultRuntimeUpdate, validateRuntimeProfileMutation, validateReadinessCheck, validateFeatureFlagUpdate, validateOutputLanguage, validateGovernanceQueue, validateRecoveryResult, validateActivations, validateDisableActivation, validateLifecycleMetrics, validateUpdateStatus, validateUpdateHistory, validateApplyUpdateResult, validateRollbackResult, validateApprovalRecordDirect, validatePrinciplesList, validateApprovalsGrouped, validateEvidenceChain, validateTrajectoryData, validateIntentSummary, validateIntentDecisionList, validateIntentDecisionResult, validateIntentDecisionSummary, validateFollowUpResponse, validateIntentInitResult, validateIntentSaveResult, validateIntentRawContent, validateIntentVersions, validateOwnerGovernanceView, validateRuleCodeOwnerReview, validateRuleCodeMutation, } from "./utils/validators.js";
3
3
  // ── Auth ──────────────────────────────────────────────────────────────────────
4
4
  function getToken() {
5
5
  return sessionStorage.getItem("pd_token");
@@ -382,6 +382,18 @@ async function updateOutputLanguage(outputLanguage) {
382
382
  async function fetchGovernanceQueue() {
383
383
  return request('/api/v1/governance/queue', undefined, validateGovernanceQueue);
384
384
  }
385
+ // Governance Recovery Actions v1: Owner-triggered recovery of a failed /
386
+ // needs_human_review internalization task (failed→pending | needs_human_review→pending).
387
+ async function recoverFailedTask(taskId, reason) {
388
+ const body = {};
389
+ if (reason !== undefined && reason.length > 0) {
390
+ body.reason = reason;
391
+ }
392
+ return request('/api/v1/failed-tasks/' + encodeURIComponent(taskId) + '/recover', {
393
+ method: 'POST',
394
+ body: JSON.stringify(body),
395
+ }, validateRecoveryResult);
396
+ }
385
397
  async function fetchApprovalsGrouped() {
386
398
  return request('/api/v1/approvals/grouped', undefined, validateApprovalsGrouped);
387
399
  }
@@ -498,4 +510,4 @@ listActiveSignalKeywords, listPendingSignalTerms, fetchKeywordStore, fetchPendin
498
510
  // auth
499
511
  getToken, setToken, clearToken, checkAuth, approveApproval, rejectApproval, editApproval, fetchPrinciples, fetchPrincipleDetail, fetchPrincipleGovernance, fetchPrincipleTrajectory, fetchPrincipleReceipts, fetchReceiptCounts, archivePrinciple, unarchivePrinciple, createFeedbackReport, listFeedbackReports, getFeedbackReport, deleteFeedbackReport,
500
512
  // Feedback submit ladder (Slice 3, spec §8)
501
- fetchFeedbackChannels, submitFeedbackReport, markFeedbackReportSent, fetchConfigSummary, fetchConfigCatalog, updateAgentBinding, updateDefaultRuntime, createRuntimeProfile, updateRuntimeProfile, deleteRuntimeProfile, fetchAgentReadiness, patchFeatureFlag, fetchOutputLanguage, updateOutputLanguage, fetchWorkspaces, addWorkspace, removeWorkspace, syncWorkspace, fetchGovernanceQueue, fetchApprovalsGrouped, fetchAllActivations, disableActivation, fetchRuleCodeOwnerReview, ruleCodeDecision, pauseAllRuleCode, releaseRuleCodePause, fetchLifecycleMetrics, fetchUpdateStatus, fetchUpdateHistory, applyUpdate, applyFullUpdate, rollbackUpdate, fetchEvidenceChain, fetchIntentSummary, fetchIntentContent, createIntentTemplate, saveIntentContent, fetchIntentVersions, recordIntentDecision, listIntentDecisionsByPainId, listIntentDecisionsByTaskId, fetchIntentDecisionSummary, dispatchFollowUp, };
513
+ fetchFeedbackChannels, submitFeedbackReport, markFeedbackReportSent, fetchConfigSummary, fetchConfigCatalog, updateAgentBinding, updateDefaultRuntime, createRuntimeProfile, updateRuntimeProfile, deleteRuntimeProfile, fetchAgentReadiness, patchFeatureFlag, fetchOutputLanguage, updateOutputLanguage, fetchWorkspaces, addWorkspace, removeWorkspace, syncWorkspace, fetchGovernanceQueue, recoverFailedTask, fetchApprovalsGrouped, fetchAllActivations, disableActivation, fetchRuleCodeOwnerReview, ruleCodeDecision, pauseAllRuleCode, releaseRuleCodePause, fetchLifecycleMetrics, fetchUpdateStatus, fetchUpdateHistory, applyUpdate, applyFullUpdate, rollbackUpdate, fetchEvidenceChain, fetchIntentSummary, fetchIntentContent, createIntentTemplate, saveIntentContent, fetchIntentVersions, recordIntentDecision, listIntentDecisionsByPainId, listIntentDecisionsByTaskId, fetchIntentDecisionSummary, dispatchFollowUp, };
@@ -149,6 +149,7 @@
149
149
  "state_db_missing": "State database not initialized. PD has not run in this workspace yet.",
150
150
  "no_pipeline_activity": "No governance activity recorded yet.",
151
151
  "pending_approvals": "{{count}} principle(s) pending your review and decision.",
152
+ "tasks_need_human_review": "{{count}} internalization task(s) are waiting for your review decision.",
152
153
  "pipeline_active": "Pipeline has activity, but candidate principles are not ready for review.",
153
154
  "consumed_candidates": "Candidate records exist, but have not entered the review stage.",
154
155
  "degraded_state": "Part of the governance pipeline or data source is degraded."
@@ -157,6 +158,7 @@
157
158
  "run_config_doctor": "Ensure the OpenClaw plugin is enabled, or run pd config doctor.",
158
159
  "wait_for_pipeline": "PD will surface principle candidates here once behavior deviations are captured and reviewable artifacts are generated.",
159
160
  "review_approvals": "Review pending principles and approve, reject, or park.",
161
+ "review_failed_tasks": "Open Failed Tasks to inspect and recover tasks awaiting your decision.",
160
162
  "check_degraded_signals": "Check degraded signal details and follow suggested actions.",
161
163
  "check_pipeline_status": "Wait for internalization tasks to complete. If no change for a long time, check pipeline status."
162
164
  },
@@ -1366,6 +1368,16 @@
1366
1368
  "error": "Failed to load failed tasks",
1367
1369
  "disabled": "Failed tasks observability is disabled. Enable 'failed_tasks_observability' in .pd/config.yaml to view.",
1368
1370
  "createDraft": "Create Feedback Draft",
1371
+ "recover": "Resume Evolution",
1372
+ "recoverConfirmTitle": "Recover this evolution task?",
1373
+ "recoverConfirmStatus": "Current status",
1374
+ "recoverConfirmActionLabel": "Action",
1375
+ "recoverConfirmActionDesc": "Reset the task and allow runtime execution",
1376
+ "recoverConfirmImpactLabel": "Impact",
1377
+ "recoverConfirmImpactDesc": "Existing artifacts are preserved. A new execution attempt will be created.",
1378
+ "recoverConfirmButton": "Recover",
1379
+ "recoverSuccess": "Recovery accepted — the task is pending again",
1380
+ "recoverFailed": "Recovery failed",
1369
1381
  "taskId": "Task ID",
1370
1382
  "painId": "Pain ID",
1371
1383
  "kind": "Kind",
@@ -149,6 +149,7 @@
149
149
  "state_db_missing": "状态数据库未初始化。PD 尚未在此工作空间中运行。",
150
150
  "no_pipeline_activity": "尚未记录到任何治理链路活动。",
151
151
  "pending_approvals": "有 {{count}} 条原则需要你审查并决定是否批准。",
152
+ "tasks_need_human_review": "有 {{count}} 个内化任务正在等待你的审查决定。",
152
153
  "pipeline_active": "管线中有活动,但候选原则尚未准备好审查。",
153
154
  "consumed_candidates": "已存在候选人记录,但尚未进入审查阶段。",
154
155
  "degraded_state": "部分治理链路或数据源处于降级状态。"
@@ -157,6 +158,7 @@
157
158
  "run_config_doctor": "确保 OpenClaw 插件已启用,或运行 pd config doctor 检查配置。",
158
159
  "wait_for_pipeline": "PD 会在捕获到行为偏差并生成可审查候选后,将其放到这里。",
159
160
  "review_approvals": "审查待审批的原则,做出批准、拒绝或暂存的决定。",
161
+ "review_failed_tasks": "打开「失败任务」页面,查看并恢复等待你决定的任务。",
160
162
  "check_degraded_signals": "检查降级信号详情,按建议操作修复。",
161
163
  "check_pipeline_status": "等待内化任务完成。如果长期无变化,检查内化管线状态。"
162
164
  },
@@ -1366,6 +1368,16 @@
1366
1368
  "error": "加载失败任务失败",
1367
1369
  "disabled": "失败任务可观测性已禁用。请在 .pd/config.yaml 中启用 'failed_tasks_observability' 以查看。",
1368
1370
  "createDraft": "创建反馈草稿",
1371
+ "recover": "继续演化",
1372
+ "recoverConfirmTitle": "恢复此演化任务?",
1373
+ "recoverConfirmStatus": "当前状态",
1374
+ "recoverConfirmActionLabel": "恢复动作",
1375
+ "recoverConfirmActionDesc": "重置任务并允许运行时重新执行",
1376
+ "recoverConfirmImpactLabel": "影响",
1377
+ "recoverConfirmImpactDesc": "既有产物将被保留,并将创建一次新的执行尝试。",
1378
+ "recoverConfirmButton": "恢复",
1379
+ "recoverSuccess": "恢复已接受——任务已重新进入待处理状态",
1380
+ "recoverFailed": "恢复失败",
1369
1381
  "taskId": "任务 ID",
1370
1382
  "painId": "Pain ID",
1371
1383
  "kind": "类型",