principles-disciple 1.16.0 → 1.18.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 (132) hide show
  1. package/README.md +13 -5
  2. package/openclaw.plugin.json +4 -4
  3. package/package.json +1 -1
  4. package/src/commands/archive-impl.ts +3 -3
  5. package/src/commands/capabilities.ts +1 -1
  6. package/src/commands/context.ts +3 -3
  7. package/src/commands/disable-impl.ts +1 -1
  8. package/src/commands/evolution-status.ts +2 -2
  9. package/src/commands/focus.ts +2 -2
  10. package/src/commands/nocturnal-train.ts +6 -6
  11. package/src/commands/pain.ts +4 -4
  12. package/src/commands/pd-reflect.ts +87 -0
  13. package/src/commands/rollback-impl.ts +4 -4
  14. package/src/commands/rollback.ts +2 -2
  15. package/src/commands/samples.ts +2 -2
  16. package/src/commands/workflow-debug.ts +1 -1
  17. package/src/config/errors.ts +1 -1
  18. package/src/core/adaptive-thresholds.ts +1 -1
  19. package/src/core/code-implementation-storage.ts +2 -2
  20. package/src/core/config.ts +1 -1
  21. package/src/core/diagnostician-task-store.ts +2 -2
  22. package/src/core/empathy-keyword-matcher.ts +3 -3
  23. package/src/core/event-log.ts +5 -5
  24. package/src/core/evolution-engine.ts +4 -4
  25. package/src/core/evolution-logger.ts +1 -1
  26. package/src/core/evolution-reducer.ts +3 -3
  27. package/src/core/evolution-types.ts +5 -5
  28. package/src/core/external-training-contract.ts +1 -1
  29. package/src/core/focus-history.ts +14 -14
  30. package/src/core/hygiene/tracker.ts +1 -1
  31. package/src/core/init.ts +2 -2
  32. package/src/core/model-deployment-registry.ts +2 -2
  33. package/src/core/model-training-registry.ts +2 -2
  34. package/src/core/nocturnal-arbiter.ts +1 -1
  35. package/src/core/nocturnal-artificer.ts +2 -2
  36. package/src/core/nocturnal-candidate-scoring.ts +2 -2
  37. package/src/core/nocturnal-compliance.ts +4 -3
  38. package/src/core/nocturnal-dataset.ts +3 -3
  39. package/src/core/nocturnal-export.ts +4 -4
  40. package/src/core/nocturnal-rule-implementation-validator.ts +1 -1
  41. package/src/core/nocturnal-snapshot-contract.ts +112 -0
  42. package/src/core/nocturnal-trajectory-extractor.ts +7 -5
  43. package/src/core/nocturnal-trinity.ts +480 -158
  44. package/src/core/pain-context-extractor.ts +3 -3
  45. package/src/core/pain.ts +124 -11
  46. package/src/core/path-resolver.ts +4 -4
  47. package/src/core/pd-task-reconciler.ts +10 -10
  48. package/src/core/pd-task-service.ts +1 -1
  49. package/src/core/pd-task-store.ts +1 -1
  50. package/src/core/principle-internalization/deprecated-readiness.ts +1 -1
  51. package/src/core/principle-training-state.ts +2 -2
  52. package/src/core/principle-tree-ledger.ts +7 -7
  53. package/src/core/promotion-gate.ts +9 -9
  54. package/src/core/replay-engine.ts +12 -12
  55. package/src/core/risk-calculator.ts +1 -1
  56. package/src/core/rule-host-types.ts +2 -2
  57. package/src/core/rule-host.ts +5 -5
  58. package/src/core/schema/db-types.ts +1 -1
  59. package/src/core/schema/schema-definitions.ts +1 -1
  60. package/src/core/session-tracker.ts +96 -4
  61. package/src/core/shadow-observation-registry.ts +3 -3
  62. package/src/core/system-logger.ts +2 -2
  63. package/src/core/thinking-os-parser.ts +1 -1
  64. package/src/core/training-program.ts +2 -2
  65. package/src/core/trajectory.ts +8 -8
  66. package/src/core/workspace-context.ts +2 -2
  67. package/src/core/workspace-dir-service.ts +85 -0
  68. package/src/core/workspace-dir-validation.ts +30 -107
  69. package/src/hooks/bash-risk.ts +3 -3
  70. package/src/hooks/edit-verification.ts +4 -4
  71. package/src/hooks/gate-block-helper.ts +4 -4
  72. package/src/hooks/gate.ts +10 -10
  73. package/src/hooks/gfi-gate.ts +7 -7
  74. package/src/hooks/lifecycle.ts +2 -2
  75. package/src/hooks/llm.ts +1 -1
  76. package/src/hooks/pain.ts +25 -5
  77. package/src/hooks/progressive-trust-gate.ts +7 -7
  78. package/src/hooks/prompt.ts +24 -5
  79. package/src/hooks/subagent.ts +2 -2
  80. package/src/hooks/thinking-checkpoint.ts +2 -2
  81. package/src/hooks/trajectory-collector.ts +1 -1
  82. package/src/http/principles-console-route.ts +14 -6
  83. package/src/i18n/commands.ts +4 -0
  84. package/src/index.ts +181 -185
  85. package/src/service/central-health-service.ts +1 -1
  86. package/src/service/central-overview-service.ts +3 -3
  87. package/src/service/evolution-query-service.ts +1 -1
  88. package/src/service/evolution-worker.ts +221 -109
  89. package/src/service/health-query-service.ts +27 -17
  90. package/src/service/monitoring-query-service.ts +3 -3
  91. package/src/service/nocturnal-runtime.ts +4 -4
  92. package/src/service/nocturnal-service.ts +40 -23
  93. package/src/service/nocturnal-target-selector.ts +11 -4
  94. package/src/service/runtime-summary-service.ts +1 -1
  95. package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +1 -1
  96. package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +3 -3
  97. package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +16 -13
  98. package/src/service/subagent-workflow/runtime-direct-driver.ts +10 -6
  99. package/src/service/subagent-workflow/types.ts +4 -4
  100. package/src/service/subagent-workflow/workflow-manager-base.ts +5 -5
  101. package/src/service/subagent-workflow/workflow-store.ts +2 -2
  102. package/src/tools/critique-prompt.ts +2 -3
  103. package/src/tools/deep-reflect.ts +17 -16
  104. package/src/tools/model-index.ts +1 -1
  105. package/src/utils/file-lock.ts +1 -1
  106. package/src/utils/io.ts +7 -2
  107. package/src/utils/nlp.ts +1 -1
  108. package/src/utils/plugin-logger.ts +2 -2
  109. package/src/utils/retry.ts +3 -2
  110. package/src/utils/subagent-probe.ts +20 -33
  111. package/templates/langs/en/skills/pd-pain-signal/SKILL.md +8 -7
  112. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/nocturnal-trinity-quality-enhancement.json +111 -0
  113. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/task-specs.mjs +1 -1
  114. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/run.mjs +1 -1
  115. package/templates/langs/zh/skills/pd-pain-signal/SKILL.md +8 -7
  116. package/templates/pain_settings.json +1 -1
  117. package/tests/build-artifacts.test.ts +4 -58
  118. package/tests/commands/pd-reflect.test.ts +49 -0
  119. package/tests/core/nocturnal-snapshot-contract.test.ts +70 -0
  120. package/tests/core/pain-auto-repair.test.ts +96 -0
  121. package/tests/core/pain-integration.test.ts +483 -0
  122. package/tests/core/pain.test.ts +5 -4
  123. package/tests/core/workspace-dir-service.test.ts +68 -0
  124. package/tests/core/workspace-dir-validation.test.ts +56 -192
  125. package/tests/hooks/pain.test.ts +20 -0
  126. package/tests/http/principles-console-route.test.ts +42 -20
  127. package/tests/integration/empathy-workflow-integration.test.ts +1 -2
  128. package/tests/integration/tool-hooks-workspace-dir.e2e.test.ts +9 -17
  129. package/tests/service/empathy-observer-workflow-manager.test.ts +1 -2
  130. package/tests/service/evolution-worker.nocturnal.test.ts +118 -109
  131. package/tests/service/nocturnal-runtime-hardening.test.ts +33 -0
  132. package/tests/utils/subagent-probe.test.ts +32 -0
@@ -155,7 +155,7 @@ export abstract class WorkflowManagerBase implements WorkflowManager {
155
155
  * Create workflow metadata for store.createWorkflow().
156
156
  * Subclasses override to add type-specific fields.
157
157
  */
158
- /* eslint-disable @typescript-eslint/class-methods-use-this -- Reason: Subclass hook that returns value via spec, not class state */
158
+
159
159
  protected createWorkflowMetadata<TResult>(
160
160
  spec: SubagentWorkflowSpec<TResult>,
161
161
  options: {
@@ -181,7 +181,7 @@ export abstract class WorkflowManagerBase implements WorkflowManager {
181
181
  * Called after driver.run() succeeds.
182
182
  * Subclasses override to call store.createWorkflow() with type-specific metadata.
183
183
  */
184
- /* eslint-disable @typescript-eslint/max-params -- Reason: Interface hook requires all params from caller context */
184
+
185
185
  protected async createWorkflowRecord<TResult>(
186
186
  workflowId: string,
187
187
  childSessionKey: string,
@@ -213,7 +213,7 @@ export abstract class WorkflowManagerBase implements WorkflowManager {
213
213
 
214
214
  // ── Protected Helpers ────────────────────────────────────────────────────
215
215
 
216
- /* eslint-disable @typescript-eslint/class-methods-use-this -- Reason: Helper method that delegates to spec.buildPrompt and driver.run */
216
+
217
217
  protected buildRunParams<TResult>(
218
218
  spec: SubagentWorkflowSpec<TResult>,
219
219
  options: {
@@ -311,11 +311,11 @@ export abstract class WorkflowManagerBase implements WorkflowManager {
311
311
  status: 'ok' | 'error' | 'timeout',
312
312
  error?: string
313
313
  ): Promise<void> {
314
- // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned in try, catch has early returns
314
+
315
315
  let workflow;
316
316
  try {
317
317
  workflow = this.store.getWorkflow(workflowId);
318
- /* eslint-disable @typescript-eslint/no-unused-vars, no-unused-vars -- Reason: Error is handled via early returns based on status, not error value */
318
+ /* eslint-disable @typescript-eslint/no-unused-vars -- Reason: Error is handled via early returns based on status, not error value */
319
319
  } catch (_dbError) {
320
320
  // Database connection closed (e.g., by lifecycle notification dispose).
321
321
  // If subagent succeeded, this is a known race condition — the workflow
@@ -232,7 +232,7 @@ export class WorkflowStore {
232
232
  `).all() as WorkflowRow[];
233
233
  }
234
234
 
235
- /* eslint-disable @typescript-eslint/max-params -- Reason: Store interface requires full event context (workflowId, fromState, toState, reason, payload) */
235
+
236
236
  recordEvent(
237
237
  workflowId: string,
238
238
  eventType: string,
@@ -268,7 +268,7 @@ export class WorkflowStore {
268
268
  * idempotencyKey must be unique per (workflow_id, stage). If a row with the
269
269
  * same idempotency_key already exists, this is a no-op (idempotent).
270
270
  */
271
- /* eslint-disable @typescript-eslint/max-params -- Reason: Store interface requires workflowId, stage, output, and idempotencyKey */
271
+
272
272
  recordStageOutput(
273
273
  workflowId: string,
274
274
  stage: 'dreamer' | 'philosopher',
@@ -28,11 +28,10 @@ export function buildCritiquePromptV2(
28
28
  ): string {
29
29
  const { context, depth = 2, workspaceDir, api } = params;
30
30
 
31
- // 1. 确定工作区目录 (优先级:显式传入 > api.config > official API > api.resolvePath)
31
+ // 1. 确定工作区目录 (优先级:显式传入 > api.config > official API)
32
32
  const effectiveWorkspaceDir = workspaceDir
33
33
  || (api?.config?.workspaceDir as string)
34
- || resolveWorkspaceDirFromApi(api)
35
- || api?.resolvePath?.('.');
34
+ || resolveWorkspaceDirFromApi(api);
36
35
 
37
36
  if (!effectiveWorkspaceDir) {
38
37
  throw new Error('Workspace directory is required for deep reflection.');
@@ -4,6 +4,7 @@ import * as fs from 'fs';
4
4
  import { EventLogService } from '../core/event-log.js';
5
5
  import { resolvePdPath } from '../core/paths.js';
6
6
  import { resolveWorkspaceDirFromApi } from '../core/path-resolver.js';
7
+ import { WorkspaceNotFoundError } from '../config/index.js';
7
8
  import {
8
9
  DeepReflectWorkflowManager,
9
10
  deepReflectWorkflowSpec,
@@ -106,11 +107,8 @@ export function createDeepReflectTool(api: OpenClawPluginApi) {
106
107
  return { content: [{ type: 'text', text: '❌ 错误: 必须提供反思上下文 (context)。' }] };
107
108
  }
108
109
 
109
- // eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: mutual recursion between helper functions - reordering would break logical grouping
110
+
110
111
  const effectiveWorkspaceDir = resolveReflectionWorkspace(api);
111
- if (!effectiveWorkspaceDir) {
112
- return { content: [{ type: 'text', text: '❌ 反思执行失败: Workspace directory is required for deep reflection。请检查 API 配置或网络连接。' }] };
113
- }
114
112
 
115
113
  const config = loadConfig(effectiveWorkspaceDir, api);
116
114
  if (config.mode === 'disabled' || !config.enabled) {
@@ -122,10 +120,10 @@ export function createDeepReflectTool(api: OpenClawPluginApi) {
122
120
  }
123
121
 
124
122
  try {
125
- // eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: mutual recursion between helper functions - reordering would break logical grouping
123
+
126
124
  return await executeReflectionWorkflow(effectiveWorkspaceDir, config, context, depth, model_id, api);
127
125
  } catch (err) {
128
- // eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: mutual recursion between helper functions - reordering would break logical grouping
126
+
129
127
  return handleReflectionError(err, context, depth, model_id, effectiveWorkspaceDir, api);
130
128
  }
131
129
  }
@@ -135,16 +133,19 @@ export function createDeepReflectTool(api: OpenClawPluginApi) {
135
133
  /**
136
134
  * Resolve workspace directory for deep reflection tool.
137
135
  */
138
- function resolveReflectionWorkspace(api: OpenClawPluginApi): string | undefined {
139
- return (api.config?.workspaceDir as string)
140
- || resolveWorkspaceDirFromApi(api)
141
- || api.resolvePath?.('.');
136
+ function resolveReflectionWorkspace(api: OpenClawPluginApi): string {
137
+ const dir = (api.config?.workspaceDir as string)
138
+ || resolveWorkspaceDirFromApi(api);
139
+ if (!dir) {
140
+ throw new WorkspaceNotFoundError('deep-reflect: workspace directory could not be resolved via API or config');
141
+ }
142
+ return dir;
142
143
  }
143
144
 
144
145
  /**
145
146
  * Execute the deep reflection workflow: start, poll, collect results.
146
147
  */
147
- /* eslint-disable @typescript-eslint/max-params -- Reason: Function signature requires all parameters for type-safe reflection workflow */
148
+
148
149
  async function executeReflectionWorkflow(
149
150
  effectiveWorkspaceDir: string,
150
151
  config: DeepReflectionConfig,
@@ -175,7 +176,7 @@ async function executeReflectionWorkflow(
175
176
 
176
177
  const startTime = Date.now();
177
178
  const timeoutMs = config.timeout_ms ?? 60000;
178
- // eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: mutual recursion between helper functions - reordering would break logical grouping
179
+
179
180
  return await pollReflectionCompletion(manager, handle, timeoutMs, startTime, eventLog, effectiveWorkspaceDir, context, model_id, depth);
180
181
  } finally {
181
182
  manager.dispose();
@@ -185,7 +186,7 @@ async function executeReflectionWorkflow(
185
186
  /**
186
187
  * Poll the reflection workflow until completion, timeout, or error.
187
188
  */
188
- /* eslint-disable @typescript-eslint/max-params -- Reason: Function signature requires all parameters for type-safe polling */
189
+
189
190
  async function pollReflectionCompletion(
190
191
  manager: DeepReflectWorkflowManager,
191
192
  handle: { workflowId: string; childSessionKey: string },
@@ -205,7 +206,7 @@ async function pollReflectionCompletion(
205
206
  if (!workflowState) break;
206
207
 
207
208
  if (workflowState === 'completed') {
208
- // eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: mutual recursion between helper functions - reordering would break logical grouping
209
+
209
210
  return formatReflectionSuccess(handle, context, depth, model_id, startTime, eventLog, workspaceDir);
210
211
  }
211
212
 
@@ -220,7 +221,7 @@ async function pollReflectionCompletion(
220
221
  /**
221
222
  * Format the success response from a completed reflection.
222
223
  */
223
- /* eslint-disable @typescript-eslint/max-params -- Reason: Function signature requires all parameters for type-safe formatting */
224
+
224
225
  function formatReflectionSuccess(
225
226
  handle: { childSessionKey: string },
226
227
  context: string,
@@ -273,7 +274,7 @@ ${insights || '反思完成,详见 REFLECTION_LOG。'}
273
274
  /**
274
275
  * Handle reflection errors and format error response.
275
276
  */
276
- /* eslint-disable @typescript-eslint/max-params -- Reason: Function signature requires all parameters for type-safe error handling */
277
+
277
278
  function handleReflectionError(
278
279
  err: unknown,
279
280
  context: string,
@@ -61,7 +61,7 @@ export function loadModelIndex(
61
61
  const wctx = WorkspaceContext.fromHookContext({ workspaceDir });
62
62
  const customConfig = loadCustomConfig(wctx);
63
63
 
64
- // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned in both if/else branches
64
+
65
65
  let modelsDir: string;
66
66
  if (customConfig?.modelsDir) {
67
67
  modelsDir = path.isAbsolute(customConfig.modelsDir)
@@ -334,7 +334,7 @@ export async function withAsyncLock<T>(
334
334
  let queue = asyncLockQueues.get(lockPath);
335
335
 
336
336
  // 创建新的 Promise 链
337
- // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned in Promise executor before use
337
+
338
338
  let resolveRelease: () => void;
339
339
  const releasePromise = new Promise<void>(resolve => {
340
340
  resolveRelease = resolve;
package/src/utils/io.ts CHANGED
@@ -16,7 +16,7 @@ export function normalizePath(filePath: string, projectDir: string): string {
16
16
  }
17
17
  }
18
18
 
19
- // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned in both if/else branches
19
+
20
20
  let rel: string;
21
21
  if (projectIsWin) {
22
22
  const projectAbs = path.resolve(projectDir);
@@ -80,6 +80,11 @@ export function serializeKvLines(data: Record<string, any>): string {
80
80
  const keys = Object.keys(data).sort();
81
81
  for (const k of keys) {
82
82
  const v = data[k];
83
+ // Skip empty/undefined values — prevents writing blank lines that cause
84
+ // agent confusion (SKILL.md lists fields that aren't actually present on disk)
85
+ if (v === '' || v === undefined || v === null) {
86
+ continue;
87
+ }
83
88
  if (Array.isArray(v)) {
84
89
  lines.push(`${k}: ${v.join(',')}`);
85
90
  } else if (typeof v === 'object' && v !== null) {
@@ -105,7 +110,7 @@ export function planStatus(projectDir: string): string {
105
110
  }
106
111
  }
107
112
  }
108
- /* eslint-disable @typescript-eslint/no-unused-vars, no-unused-vars -- Reason: Error is intentionally ignored for graceful degradation */
113
+ /* eslint-disable @typescript-eslint/no-unused-vars -- Reason: Error is intentionally ignored for graceful degradation */
109
114
  } catch (_e) {
110
115
  // Ignore read errors
111
116
  }
package/src/utils/nlp.ts CHANGED
@@ -19,7 +19,7 @@ export function extractCommonPhrases(samples: string[], minOccurrence = 3): stri
19
19
 
20
20
  // Filter phrases that meet the threshold
21
21
  return Array.from(phrases.entries())
22
- /* eslint-disable @typescript-eslint/no-unused-vars, no-unused-vars -- Reason: Destructuring with _placeholder for unused array element */
22
+
23
23
  .filter(([_elem, count]) => count >= minOccurrence)
24
24
  .map(([phrase, _count]) => phrase);
25
25
  }
@@ -2,12 +2,12 @@ import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
 
4
4
  export interface PluginLogger {
5
- /* eslint-disable no-unused-vars -- Reason: interface method params are type signatures, implementations use actual values */
5
+
6
6
  info(message: string, meta?: Record<string, unknown>): void;
7
7
  warn(message: string, meta?: Record<string, unknown>): void;
8
8
  error(message: string, meta?: Record<string, unknown>): void;
9
9
  debug(message: string, meta?: Record<string, unknown>): void;
10
- /* eslint-enable no-unused-vars */
10
+
11
11
  }
12
12
 
13
13
  export interface PluginLoggerConfig {
@@ -34,7 +34,7 @@ export interface RetryOptions {
34
34
  operation?: string;
35
35
  /** Logger instance (optional, defaults to console) */
36
36
  logger?: RetryLogger;
37
- /* eslint-disable no-unused-vars -- Reason: callback param names are part of type signature, unused implementations are valid */
37
+
38
38
  isRetryable?: (_error: unknown) => boolean;
39
39
  }
40
40
 
@@ -424,7 +424,7 @@ export function computeDynamicTimeout(
424
424
  if (history.length < MIN_SAMPLES) {
425
425
  // Not enough data — use the spec's static timeout
426
426
  const fallback = clampTimeout(defaultTimeout);
427
- // Use console.info since we don't have logger access here; this appears in journalctl
427
+ // eslint-disable-next-line no-console -- Monitoring output for fallback diagnostics
428
428
  console.info(`[PD:DynamicTimeout] Insufficient samples (${history.length} < ${MIN_SAMPLES}) for '${workflowType}', falling back to static timeout: ${fallback}ms`);
429
429
  return fallback;
430
430
  }
@@ -432,6 +432,7 @@ export function computeDynamicTimeout(
432
432
  const p95 = percentile(history, 95);
433
433
  const adaptive = p95 * SAFETY_MULTIPLIER;
434
434
  const result = clampTimeout(adaptive);
435
+ // eslint-disable-next-line no-console -- Monitoring output for adaptive timeout diagnostics
435
436
  console.info(`[PD:DynamicTimeout] Computed adaptive timeout for '${workflowType}': P95=${p95}ms (from ${history.length} samples) × ${SAFETY_MULTIPLIER} = ${result}ms`);
436
437
  return result;
437
438
  }
@@ -1,11 +1,9 @@
1
1
  /**
2
2
  * Subagent Runtime Availability Probe
3
3
  *
4
- * OpenClaw has two runtime modes:
5
- * - Gateway mode: api.runtime.subagent methods are real async functions
6
- * - Embedded mode: api.runtime.subagent is a Proxy that throws synchronously
7
- *
8
- * This utility provides a reliable way to detect which mode we're in.
4
+ * This utility intentionally avoids inferring runtime availability from
5
+ * JavaScript implementation details like constructor names. The only contract
6
+ * we trust here is whether a callable `run` entrypoint exists.
9
7
  */
10
8
 
11
9
  import type { OpenClawPluginApi } from '../openclaw-sdk.js';
@@ -29,46 +27,35 @@ function getGlobalGatewaySubagent(): SubagentRuntime | null {
29
27
  }
30
28
 
31
29
  /**
32
- * Check if the subagent runtime is actually functional.
33
- *
34
- * In gateway mode, subagent.run is an AsyncFunction (constructor.name === 'AsyncFunction').
35
- * In embedded mode, subagent.run is a regular Function that throws synchronously.
36
- *
37
- * We use constructor check first because it's fast and has no side effects.
30
+ * Return a small, explicit availability assessment for the subagent runtime.
31
+ * This is a shape check only. Actual invocation failures are classified by the
32
+ * caller as runtime-unavailable vs downstream task failures.
38
33
  *
39
34
  * @param subagent - The subagent runtime object from api.runtime.subagent
40
- * @returns true if the runtime is functional (gateway mode), false otherwise
35
+ * @returns availability status and reason
41
36
  */
42
- export function isSubagentRuntimeAvailable(
37
+ export function getSubagentRuntimeAvailability(
43
38
  subagent: { run?: unknown } | undefined
44
- ): boolean {
45
- if (!subagent) return false;
39
+ ): { available: boolean; reason: 'missing_runtime' | 'missing_run' | 'callable' } {
40
+ if (!subagent) return { available: false, reason: 'missing_runtime' };
46
41
 
47
42
  try {
48
43
  const runFn = subagent.run;
49
- if (typeof runFn !== 'function') return false;
50
-
51
- // In gateway mode, methods are AsyncFunction instances
52
- // In embedded mode, methods are regular Function instances that throw
53
- const isAsync = runFn.constructor?.name === 'AsyncFunction';
54
-
55
- if (isAsync) return true;
56
-
57
- // Fallback: Check if it's a Proxy that might late-bind to the gateway subagent
58
- // This handles the case where the plugin was loaded with allowGatewaySubagentBinding
59
- // but the proxy hasn't resolved yet
60
- const globalGateway = getGlobalGatewaySubagent();
61
- if (globalGateway && typeof globalGateway.run === 'function') {
62
- return globalGateway.run.constructor?.name === 'AsyncFunction';
44
+ if (typeof runFn !== 'function') {
45
+ return { available: false, reason: 'missing_run' };
63
46
  }
64
-
65
- return false;
47
+ return { available: true, reason: 'callable' };
66
48
  } catch {
67
- // Any error means unavailable
68
- return false;
49
+ return { available: false, reason: 'missing_run' };
69
50
  }
70
51
  }
71
52
 
53
+ export function isSubagentRuntimeAvailable(
54
+ subagent: { run?: unknown } | undefined
55
+ ): boolean {
56
+ return getSubagentRuntimeAvailability(subagent).available;
57
+ }
58
+
72
59
  /**
73
60
  * Get the actual subagent runtime, preferring the global gateway subagent
74
61
  * if the passed one is not available.
@@ -12,7 +12,7 @@ You are now the "Manual Intervention Pain" component.
12
12
  1. Write the user's feedback `$ARGUMENTS` as a **high-priority** pain signal to `.state/.pain_flag`.
13
13
  2. Inform the user that the signal has been injected, and suggest waiting for the next Hook trigger (e.g., Stop or PreCompact) or manually running `/reflection-log`.
14
14
 
15
- **Write Format** (must use this KV format, consistent with auto-detection channels):
15
+ **Write Format** (must use this KV format, fields sorted alphabetically):
16
16
 
17
17
  ```
18
18
  agent_id: <current agent ID, e.g., main/builder/diagnostician>
@@ -22,16 +22,17 @@ score: 80
22
22
  session_id: <current session ID>
23
23
  source: human_intervention
24
24
  time: <ISO 8601 timestamp>
25
- trace_id:
26
- trigger_text_preview:
27
25
  ```
28
26
 
29
- **Field Notes**:
27
+ **Required fields** (4):
30
28
  - `source`: Fixed as `human_intervention`
31
29
  - `score`: Default `80` for manual intervention (high priority)
30
+ - `time`: ISO 8601 timestamp
31
+ - `reason`: User's feedback verbatim
32
+
33
+ **Optional fields** (auto-filled by system, but must be provided for manual injection):
34
+ - `agent_id`: Current agent ID (e.g., main/builder/diagnostician)
32
35
  - `session_id`: Current session ID (from context)
33
- - `agent_id`: Current agent ID (from context)
34
36
  - `is_risky`: Fixed as `false`
35
- - `trace_id` / `trigger_text_preview`: Leave empty
36
37
 
37
- **⚠️ Important**: Do NOT use other formats (like writing only Source/Reason/Time lines). Downstream diagnostic systems depend on the complete KV field set.
38
+ **Note**: `trace_id` and `trigger_text_preview` are auto-generated by the system do NOT include them when manually injecting pain signals.
@@ -0,0 +1,111 @@
1
+ {
2
+ "id": "nocturnal-trinity-quality-enhancement",
3
+ "title": "Enhance nocturnal Trinity prompt quality",
4
+ "description": "Enhance nocturnal Trinity prompt quality — add Dreamer perspective diversity constraints and Scribe rejected-decision analysis",
5
+ "workspace": "/home/csuzngjh/code/principles",
6
+ "branch": "fix/bugs-231-228",
7
+ "requiresTaskContract": true,
8
+ "maxRoundsPerStage": 2,
9
+ "maxRuntimeMinutes": 60,
10
+ "stages": [
11
+ "investigate",
12
+ "implement-pass-1",
13
+ "verify"
14
+ ],
15
+ "taskContract": {
16
+ "goal": "Improve nocturnal Trinity output quality by adding perspective diversity to Dreamer and rejected-decision analysis to Scribe",
17
+ "inScope": [
18
+ "nocturnal-trinity.ts prompt modifications",
19
+ "nocturnal-trinity.test.ts assertion updates",
20
+ "nocturnal-arbiter.ts compatibility verification"
21
+ ],
22
+ "outOfScope": [
23
+ "Runtime or infrastructure changes",
24
+ "New file creation",
25
+ "Non-Trinity prompt changes"
26
+ ],
27
+ "validationCommands": [
28
+ "npx vitest run packages/openclaw-plugin/tests/core/nocturnal --reporter=verbose"
29
+ ],
30
+ "expectedArtifacts": [
31
+ "packages/openclaw-plugin/src/core/nocturnal-trinity.ts"
32
+ ]
33
+ },
34
+ "producer": {
35
+ "agent": "iflow",
36
+ "model": "glm-5",
37
+ "timeoutSeconds": 1800
38
+ },
39
+ "reviewerA": {
40
+ "agent": "iflow",
41
+ "model": "glm-4.7",
42
+ "timeoutSeconds": 1200,
43
+ "role": "code-quality",
44
+ "focus": "Verify prompt changes are minimal, backward-compatible, and don't break existing arbiter validation"
45
+ },
46
+ "reviewerB": {
47
+ "agent": "iflow",
48
+ "model": "glm-4.7",
49
+ "timeoutSeconds": 1200,
50
+ "role": "functional-correctness",
51
+ "focus": "Verify tests pass and the new prompt constraints produce structurally valid Trinity output"
52
+ },
53
+ "escalationReviewer": {
54
+ "agent": "iflow",
55
+ "model": "glm-5",
56
+ "timeoutSeconds": 1800
57
+ },
58
+ "stageGoals": {
59
+ "investigate": [
60
+ "Read nocturnal-trinity.ts lines 64-298 (all three prompts) and nocturnal-trinity.test.ts",
61
+ "Identify exact insertion points for Dreamer diversity section and Scribe analysis section",
62
+ "List all test assertions that reference prompt content",
63
+ "Report findings in producer.md"
64
+ ],
65
+ "implement-pass-1": [
66
+ "Apply Dreamer perspective diversity constraints to NOCTURNAL_DREAMER_PROMPT",
67
+ "Apply Scribe rejected-decision analysis to NOCTURNAL_SCRIBE_PROMPT",
68
+ "Update test assertions in nocturnal-trinity.test.ts if needed",
69
+ "Run nocturnal-trinity and nocturnal-arbiter tests to verify no breakage"
70
+ ],
71
+ "verify": [
72
+ "Run full nocturnal test suite: npx vitest run packages/openclaw-plugin/tests/core/nocturnal --reporter=verbose",
73
+ "Verify all tests pass with 0 failures",
74
+ "Confirm arbiter validation is unchanged",
75
+ "Confirm no new files were created"
76
+ ]
77
+ },
78
+ "stageCriteria": {
79
+ "investigate": {
80
+ "scoringDimensions": [
81
+ "completeness",
82
+ "accuracy"
83
+ ],
84
+ "dimensionThreshold": 3,
85
+ "requiredDeliverables": [
86
+ "producer.md"
87
+ ]
88
+ },
89
+ "implement-pass-1": {
90
+ "scoringDimensions": [
91
+ "correctness",
92
+ "completeness"
93
+ ],
94
+ "dimensionThreshold": 3,
95
+ "requiredDeliverables": [
96
+ "producer.md",
97
+ "reviewer-a.md",
98
+ "reviewer-b.md"
99
+ ]
100
+ },
101
+ "verify": {
102
+ "scoringDimensions": [
103
+ "correctness"
104
+ ],
105
+ "dimensionThreshold": 3,
106
+ "requiredDeliverables": [
107
+ "producer.md"
108
+ ]
109
+ }
110
+ }
111
+ }
@@ -325,7 +325,7 @@ export function buildStageBrief(spec, stage, round, previousDecision, handoff =
325
325
  carryForward.trimEnd(),
326
326
  '',
327
327
  `## Constraints`,
328
- ...spec.context.map((line) => `- ${line}`),
328
+ ...((spec.context ?? []).map((line) => `- ${line}`)),
329
329
  '',
330
330
  ...(spec.taskContract
331
331
  ? [
@@ -3413,7 +3413,7 @@ if (process.argv[1] === fileURLToPath(import.meta.url)) {
3413
3413
  main().catch((err) => {
3414
3414
  // main() is async and may throw. The try/catch inside main() handles
3415
3415
  // errors within its body, but rejections from the Promise itself land here.
3416
- console.error('Fatal error:', err.message);
3416
+ console.error('Fatal error:', err.message, err.stack);
3417
3417
  process.exit(1);
3418
3418
  });
3419
3419
  }
@@ -12,7 +12,7 @@ disable-model-invocation: true
12
12
  1. 将用户的反馈 `$ARGUMENTS` 作为一条**高优先级**的痛苦信号,写入 `.state/.pain_flag`。
13
13
  2. 告知用户信号已注入,并建议其等待下一个 Hook 触发(如 Stop 或 PreCompact)或手动运行 `/reflection-log`。
14
14
 
15
- **写入格式**(必须使用以下 KV 格式,与自动检测渠道保持一致):
15
+ **写入格式**(必须使用以下 KV 格式,字段按字母排序):
16
16
 
17
17
  ```
18
18
  agent_id: <当前 agent ID,如 main/builder/diagnostician>
@@ -22,16 +22,17 @@ score: 80
22
22
  session_id: <当前 session ID>
23
23
  source: human_intervention
24
24
  time: <ISO 8601 时间>
25
- trace_id:
26
- trigger_text_preview:
27
25
  ```
28
26
 
29
- **字段说明**:
27
+ **必填字段**(4 个):
30
28
  - `source`: 固定为 `human_intervention`
31
29
  - `score`: 人工干预信号默认设为 `80`(高优先级)
30
+ - `time`: ISO 8601 时间戳
31
+ - `reason`: 用户反馈的原文
32
+
33
+ **可选字段**(自动写入时由系统填充,人工注入时必须填写):
34
+ - `agent_id`: 当前智能体 ID(如 main/builder/diagnostician)
32
35
  - `session_id`: 当前会话 ID(从上下文中获取)
33
- - `agent_id`: 当前智能体 ID(从上下文中获取)
34
36
  - `is_risky`: 固定为 `false`
35
- - `trace_id` / `trigger_text_preview`: 留空即可
36
37
 
37
- **⚠️ 注意**: 不要使用其他格式(如只写 Source/Reason/Time 三行),下游诊断系统依赖完整的 KV 字段。
38
+ **注意**: `trace_id` `trigger_text_preview` 由系统自动生成,人工注入时**不需要**写这两个字段。
@@ -27,7 +27,7 @@
27
27
  "intervals": {
28
28
  "worker_poll_ms": 900000,
29
29
  "initial_delay_ms": 5000,
30
- "task_timeout_ms": 1800000
30
+ "task_timeout_ms": 3600000
31
31
  },
32
32
  "trust": {
33
33
  "stages": {
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import { describe, it, expect } from 'vitest';
11
- import { existsSync, readdirSync, statSync } from 'fs';
11
+ import { existsSync, readdirSync } from 'fs';
12
12
  import { join, dirname } from 'path';
13
13
  import { fileURLToPath } from 'url';
14
14
 
@@ -35,45 +35,9 @@ describe('Build Artifacts', () => {
35
35
  }
36
36
  });
37
37
 
38
- describe('Agent definitions', () => {
39
- const expectedAgents = [
40
- 'diagnostician',
41
- 'explorer',
42
- 'auditor',
43
- 'planner',
44
- 'implementer',
45
- 'reviewer',
46
- 'reporter',
47
- ];
48
-
49
- it('should have agents directory with correct files', () => {
50
- const agentsDir = join(packageRoot, 'dist/agents');
51
-
52
- if (!existsSync(agentsDir)) {
53
- // Skip test if dist/agents doesn't exist (not a production build)
54
- return;
55
- }
56
-
57
- const files = readdirSync(agentsDir)
58
- .filter(f => f.endsWith('.md'))
59
- .map(f => f.replace('.md', ''));
60
-
61
- // At least 5 agents should be present
62
- expect(files.length, 'Should have at least 5 agent definitions').toBeGreaterThanOrEqual(5);
63
- });
64
-
65
- for (const agent of expectedAgents) {
66
- it(`should include ${agent}.md`, () => {
67
- const agentPath = join(packageRoot, `dist/agents/${agent}.md`);
68
-
69
- if (!existsSync(join(packageRoot, 'dist/agents'))) {
70
- return; // Skip if directory doesn't exist
71
- }
72
-
73
- expect(existsSync(agentPath), `${agent}.md should exist in dist/agents`).toBe(true);
74
- });
75
- }
76
- });
38
+ // NOTE: agents/ directory was removed in favor of embedded prompts.
39
+ // All role prompts are now inlined in nocturnal-trinity.ts at build time.
40
+ // See: nocturnal-trinity.ts NOCTURNAL_DREAMER_PROMPT, etc.
77
41
 
78
42
  describe('Templates', () => {
79
43
  it('should have templates directory with subdirectories', () => {
@@ -91,21 +55,3 @@ describe('Build Artifacts', () => {
91
55
  });
92
56
  });
93
57
 
94
- describe('Agent Loader Integration', () => {
95
- it('should be able to import agent-loader from dist', async () => {
96
- const distPath = join(packageRoot, 'dist/core/agent-loader.js');
97
-
98
- if (!existsSync(distPath)) {
99
- return; // Skip if dist not built
100
- }
101
-
102
- // Dynamic import from dist
103
- const { listAvailableAgents } = await import(join(packageRoot, 'dist/core/agent-loader.js'));
104
-
105
- const agents = listAvailableAgents();
106
-
107
- expect(agents.length, 'listAvailableAgents should return at least 5 agents').toBeGreaterThanOrEqual(5);
108
- expect(agents, 'Should include diagnostician').toContain('diagnostician');
109
- expect(agents, 'Should include explorer').toContain('explorer');
110
- });
111
- });