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.
- package/README.md +13 -5
- package/openclaw.plugin.json +4 -4
- package/package.json +1 -1
- package/src/commands/archive-impl.ts +3 -3
- package/src/commands/capabilities.ts +1 -1
- package/src/commands/context.ts +3 -3
- package/src/commands/disable-impl.ts +1 -1
- package/src/commands/evolution-status.ts +2 -2
- package/src/commands/focus.ts +2 -2
- package/src/commands/nocturnal-train.ts +6 -6
- package/src/commands/pain.ts +4 -4
- package/src/commands/pd-reflect.ts +87 -0
- package/src/commands/rollback-impl.ts +4 -4
- package/src/commands/rollback.ts +2 -2
- package/src/commands/samples.ts +2 -2
- package/src/commands/workflow-debug.ts +1 -1
- package/src/config/errors.ts +1 -1
- package/src/core/adaptive-thresholds.ts +1 -1
- package/src/core/code-implementation-storage.ts +2 -2
- package/src/core/config.ts +1 -1
- package/src/core/diagnostician-task-store.ts +2 -2
- package/src/core/empathy-keyword-matcher.ts +3 -3
- package/src/core/event-log.ts +5 -5
- package/src/core/evolution-engine.ts +4 -4
- package/src/core/evolution-logger.ts +1 -1
- package/src/core/evolution-reducer.ts +3 -3
- package/src/core/evolution-types.ts +5 -5
- package/src/core/external-training-contract.ts +1 -1
- package/src/core/focus-history.ts +14 -14
- package/src/core/hygiene/tracker.ts +1 -1
- package/src/core/init.ts +2 -2
- package/src/core/model-deployment-registry.ts +2 -2
- package/src/core/model-training-registry.ts +2 -2
- package/src/core/nocturnal-arbiter.ts +1 -1
- package/src/core/nocturnal-artificer.ts +2 -2
- package/src/core/nocturnal-candidate-scoring.ts +2 -2
- package/src/core/nocturnal-compliance.ts +4 -3
- package/src/core/nocturnal-dataset.ts +3 -3
- package/src/core/nocturnal-export.ts +4 -4
- package/src/core/nocturnal-rule-implementation-validator.ts +1 -1
- package/src/core/nocturnal-snapshot-contract.ts +112 -0
- package/src/core/nocturnal-trajectory-extractor.ts +7 -5
- package/src/core/nocturnal-trinity.ts +480 -158
- package/src/core/pain-context-extractor.ts +3 -3
- package/src/core/pain.ts +124 -11
- package/src/core/path-resolver.ts +4 -4
- package/src/core/pd-task-reconciler.ts +10 -10
- package/src/core/pd-task-service.ts +1 -1
- package/src/core/pd-task-store.ts +1 -1
- package/src/core/principle-internalization/deprecated-readiness.ts +1 -1
- package/src/core/principle-training-state.ts +2 -2
- package/src/core/principle-tree-ledger.ts +7 -7
- package/src/core/promotion-gate.ts +9 -9
- package/src/core/replay-engine.ts +12 -12
- package/src/core/risk-calculator.ts +1 -1
- package/src/core/rule-host-types.ts +2 -2
- package/src/core/rule-host.ts +5 -5
- package/src/core/schema/db-types.ts +1 -1
- package/src/core/schema/schema-definitions.ts +1 -1
- package/src/core/session-tracker.ts +96 -4
- package/src/core/shadow-observation-registry.ts +3 -3
- package/src/core/system-logger.ts +2 -2
- package/src/core/thinking-os-parser.ts +1 -1
- package/src/core/training-program.ts +2 -2
- package/src/core/trajectory.ts +8 -8
- package/src/core/workspace-context.ts +2 -2
- package/src/core/workspace-dir-service.ts +85 -0
- package/src/core/workspace-dir-validation.ts +30 -107
- package/src/hooks/bash-risk.ts +3 -3
- package/src/hooks/edit-verification.ts +4 -4
- package/src/hooks/gate-block-helper.ts +4 -4
- package/src/hooks/gate.ts +10 -10
- package/src/hooks/gfi-gate.ts +7 -7
- package/src/hooks/lifecycle.ts +2 -2
- package/src/hooks/llm.ts +1 -1
- package/src/hooks/pain.ts +25 -5
- package/src/hooks/progressive-trust-gate.ts +7 -7
- package/src/hooks/prompt.ts +24 -5
- package/src/hooks/subagent.ts +2 -2
- package/src/hooks/thinking-checkpoint.ts +2 -2
- package/src/hooks/trajectory-collector.ts +1 -1
- package/src/http/principles-console-route.ts +14 -6
- package/src/i18n/commands.ts +4 -0
- package/src/index.ts +181 -185
- package/src/service/central-health-service.ts +1 -1
- package/src/service/central-overview-service.ts +3 -3
- package/src/service/evolution-query-service.ts +1 -1
- package/src/service/evolution-worker.ts +221 -109
- package/src/service/health-query-service.ts +27 -17
- package/src/service/monitoring-query-service.ts +3 -3
- package/src/service/nocturnal-runtime.ts +4 -4
- package/src/service/nocturnal-service.ts +40 -23
- package/src/service/nocturnal-target-selector.ts +11 -4
- package/src/service/runtime-summary-service.ts +1 -1
- package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +1 -1
- package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +3 -3
- package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +16 -13
- package/src/service/subagent-workflow/runtime-direct-driver.ts +10 -6
- package/src/service/subagent-workflow/types.ts +4 -4
- package/src/service/subagent-workflow/workflow-manager-base.ts +5 -5
- package/src/service/subagent-workflow/workflow-store.ts +2 -2
- package/src/tools/critique-prompt.ts +2 -3
- package/src/tools/deep-reflect.ts +17 -16
- package/src/tools/model-index.ts +1 -1
- package/src/utils/file-lock.ts +1 -1
- package/src/utils/io.ts +7 -2
- package/src/utils/nlp.ts +1 -1
- package/src/utils/plugin-logger.ts +2 -2
- package/src/utils/retry.ts +3 -2
- package/src/utils/subagent-probe.ts +20 -33
- package/templates/langs/en/skills/pd-pain-signal/SKILL.md +8 -7
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/nocturnal-trinity-quality-enhancement.json +111 -0
- package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/lib/task-specs.mjs +1 -1
- package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/run.mjs +1 -1
- package/templates/langs/zh/skills/pd-pain-signal/SKILL.md +8 -7
- package/templates/pain_settings.json +1 -1
- package/tests/build-artifacts.test.ts +4 -58
- package/tests/commands/pd-reflect.test.ts +49 -0
- package/tests/core/nocturnal-snapshot-contract.test.ts +70 -0
- package/tests/core/pain-auto-repair.test.ts +96 -0
- package/tests/core/pain-integration.test.ts +483 -0
- package/tests/core/pain.test.ts +5 -4
- package/tests/core/workspace-dir-service.test.ts +68 -0
- package/tests/core/workspace-dir-validation.test.ts +56 -192
- package/tests/hooks/pain.test.ts +20 -0
- package/tests/http/principles-console-route.test.ts +42 -20
- package/tests/integration/empathy-workflow-integration.test.ts +1 -2
- package/tests/integration/tool-hooks-workspace-dir.e2e.test.ts +9 -17
- package/tests/service/empathy-observer-workflow-manager.test.ts +1 -2
- package/tests/service/evolution-worker.nocturnal.test.ts +118 -109
- package/tests/service/nocturnal-runtime-hardening.test.ts +33 -0
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
314
|
+
|
|
315
315
|
let workflow;
|
|
316
316
|
try {
|
|
317
317
|
workflow = this.store.getWorkflow(workflowId);
|
|
318
|
-
/* eslint-disable @typescript-eslint/no-unused-vars
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
123
|
+
|
|
126
124
|
return await executeReflectionWorkflow(effectiveWorkspaceDir, config, context, depth, model_id, api);
|
|
127
125
|
} catch (err) {
|
|
128
|
-
|
|
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
|
|
139
|
-
|
|
140
|
-
|| resolveWorkspaceDirFromApi(api)
|
|
141
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
277
|
+
|
|
277
278
|
function handleReflectionError(
|
|
278
279
|
err: unknown,
|
|
279
280
|
context: string,
|
package/src/tools/model-index.ts
CHANGED
|
@@ -61,7 +61,7 @@ export function loadModelIndex(
|
|
|
61
61
|
const wctx = WorkspaceContext.fromHookContext({ workspaceDir });
|
|
62
62
|
const customConfig = loadCustomConfig(wctx);
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
|
|
65
65
|
let modelsDir: string;
|
|
66
66
|
if (customConfig?.modelsDir) {
|
|
67
67
|
modelsDir = path.isAbsolute(customConfig.modelsDir)
|
package/src/utils/file-lock.ts
CHANGED
|
@@ -334,7 +334,7 @@ export async function withAsyncLock<T>(
|
|
|
334
334
|
let queue = asyncLockQueues.get(lockPath);
|
|
335
335
|
|
|
336
336
|
// 创建新的 Promise 链
|
|
337
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
10
|
+
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export interface PluginLoggerConfig {
|
package/src/utils/retry.ts
CHANGED
|
@@ -34,7 +34,7 @@ export interface RetryOptions {
|
|
|
34
34
|
operation?: string;
|
|
35
35
|
/** Logger instance (optional, defaults to console) */
|
|
36
36
|
logger?: RetryLogger;
|
|
37
|
-
|
|
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
|
-
//
|
|
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
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
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
|
|
35
|
+
* @returns availability status and reason
|
|
41
36
|
*/
|
|
42
|
-
export function
|
|
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')
|
|
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
|
-
|
|
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,
|
|
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
|
-
**
|
|
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
|
-
|
|
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
|
-
|
|
38
|
+
**注意**: `trace_id` 和 `trigger_text_preview` 由系统自动生成,人工注入时**不需要**写这两个字段。
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { describe, it, expect } from 'vitest';
|
|
11
|
-
import { existsSync, readdirSync
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
});
|