principles-disciple 1.34.2 → 1.36.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/.dependency-cruiser.json +19 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +6 -3
- package/src/config/defaults/runtime.ts +100 -24
- package/src/core/correction-cue-learner.ts +23 -8
- package/src/core/event-log.ts +87 -20
- package/src/core/init.ts +2 -2
- package/src/core/nocturnal-candidate-scoring.ts +6 -6
- package/src/core/nocturnal-trinity-types.ts +94 -0
- package/src/core/nocturnal-trinity.ts +35 -99
- package/src/core/session-tracker.ts +7 -6
- package/src/core/system-logger.ts +104 -12
- package/src/core/workspace-dir-service.ts +40 -6
- package/src/core/workspace-dir-validation.ts +5 -37
- package/src/hooks/prompt.ts +3 -3
- package/src/hooks/trajectory-collector.ts +7 -7
- package/src/index.ts +8 -68
- package/src/service/central-sync-service.ts +3 -8
- package/src/service/correction-observer-workflow-manager.ts +2 -2
- package/src/service/evolution-worker.ts +13 -22
- package/src/service/keyword-optimization-service.ts +2 -2
- package/src/service/nocturnal-service.ts +62 -43
- package/src/service/subagent-workflow/correction-observer-types.ts +69 -0
- package/src/service/subagent-workflow/correction-observer-workflow-manager.ts +246 -0
- package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +4 -4
- package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +4 -4
- package/src/service/subagent-workflow/index.ts +13 -0
- package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +2 -2
- package/src/service/subagent-workflow/types.ts +69 -3
- package/src/utils/shadow-fingerprint.ts +42 -0
- package/src/utils/workspace-resolver.ts +54 -0
- package/tests/core/correction-cue-learner.test.ts +345 -0
- package/tests/core/workspace-dir-validation.test.ts +1 -1
- package/tests/integration/tool-hooks-workspace-dir.e2e.test.ts +3 -3
- package/vitest.config.ts +53 -6
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CorrectionObserverWorkflowManager
|
|
3
|
+
*
|
|
4
|
+
* Workflow manager that dispatches an LLM subagent to optimize correction
|
|
5
|
+
* keywords based on recent match performance data and user feedback.
|
|
6
|
+
*
|
|
7
|
+
* Follows the established WorkflowManagerBase pattern from EmpathyObserverWorkflowManager.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { PluginLogger } from '../../openclaw-sdk.js';
|
|
11
|
+
import type {
|
|
12
|
+
SubagentWorkflowSpec,
|
|
13
|
+
WorkflowMetadata,
|
|
14
|
+
WorkflowResultContext,
|
|
15
|
+
WorkflowPersistContext,
|
|
16
|
+
WorkflowHandle,
|
|
17
|
+
} from './types.js';
|
|
18
|
+
import type { RuntimeDirectDriver } from './runtime-direct-driver.js';
|
|
19
|
+
import { WorkflowManagerBase } from './workflow-manager-base.js';
|
|
20
|
+
import { isSubagentRuntimeAvailable } from '../../utils/subagent-probe.js';
|
|
21
|
+
import type {
|
|
22
|
+
CorrectionObserverPayload,
|
|
23
|
+
CorrectionObserverResult,
|
|
24
|
+
} from './correction-observer-types.js';
|
|
25
|
+
|
|
26
|
+
const WORKFLOW_SESSION_PREFIX = 'agent:main:subagent:workflow-correction-';
|
|
27
|
+
|
|
28
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
29
|
+
const DEFAULT_TTL_MS = 5 * 60 * 1000;
|
|
30
|
+
|
|
31
|
+
// Prompt formatting constants
|
|
32
|
+
const MAX_TRAJECTORY_MESSAGE_LENGTH = 80;
|
|
33
|
+
|
|
34
|
+
// ── Options ─────────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
export interface CorrectionObserverWorkflowOptions {
|
|
37
|
+
workspaceDir: string;
|
|
38
|
+
logger: PluginLogger;
|
|
39
|
+
subagent: RuntimeDirectDriver['subagent'];
|
|
40
|
+
/** Pass api.runtime.agent.session to enable heartbeat-safe cleanup (#188) */
|
|
41
|
+
agentSession?: RuntimeDirectDriver['agentSession'];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ── Helper Functions ─────────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Extract raw assistant text from messages or assistantTexts array.
|
|
48
|
+
*/
|
|
49
|
+
function extractAssistantTextForSpec(messages: unknown[], assistantTexts?: string[]): string {
|
|
50
|
+
if (assistantTexts && assistantTexts.length > 0) {
|
|
51
|
+
return assistantTexts[assistantTexts.length - 1] || '';
|
|
52
|
+
}
|
|
53
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
54
|
+
const msg = messages[i] as { role?: string; content?: unknown };
|
|
55
|
+
if (msg?.role !== 'assistant') continue;
|
|
56
|
+
if (typeof msg.content === 'string') return msg.content;
|
|
57
|
+
if (Array.isArray(msg.content)) {
|
|
58
|
+
const txt = msg.content
|
|
59
|
+
.filter((part: unknown) => part && typeof part === 'object' && (part as { type?: string }).type === 'text' && typeof (part as { text?: unknown }).text === 'string')
|
|
60
|
+
.map((part: unknown) => (part as { text: string }).text)
|
|
61
|
+
.join('\n');
|
|
62
|
+
if (txt) return txt;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return '';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Parse correction observer JSON payload from raw text.
|
|
70
|
+
*/
|
|
71
|
+
function parseCorrectionObserverPayload(rawText: string): CorrectionObserverResult | null {
|
|
72
|
+
if (!rawText?.trim()) return null;
|
|
73
|
+
try {
|
|
74
|
+
return JSON.parse(rawText.trim()) as CorrectionObserverResult;
|
|
75
|
+
} catch {
|
|
76
|
+
const match = /\{[\s\S]*\}/.exec(rawText);
|
|
77
|
+
if (!match) return null;
|
|
78
|
+
try {
|
|
79
|
+
return JSON.parse(match[0]) as CorrectionObserverResult;
|
|
80
|
+
} catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── Workflow Spec ─────────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
export const correctionObserverWorkflowSpec: SubagentWorkflowSpec<CorrectionObserverResult> = {
|
|
89
|
+
workflowType: 'correction_observer',
|
|
90
|
+
transport: 'runtime_direct',
|
|
91
|
+
timeoutMs: 30_000,
|
|
92
|
+
ttlMs: 300_000,
|
|
93
|
+
shouldDeleteSessionAfterFinalize: true,
|
|
94
|
+
|
|
95
|
+
buildPrompt(taskInput: unknown, _metadata: WorkflowMetadata): string {
|
|
96
|
+
const payload = taskInput as CorrectionObserverPayload;
|
|
97
|
+
const { keywordStoreSummary, recentMessages, trajectoryHistory } = payload;
|
|
98
|
+
|
|
99
|
+
const termsList = keywordStoreSummary.terms
|
|
100
|
+
.map(t => ` - term="${t.term}", weight=${t.weight}, hits=${t.hitCount}, TP=${t.truePositiveCount}, FP=${t.falsePositiveCount}`)
|
|
101
|
+
.join('\n');
|
|
102
|
+
|
|
103
|
+
const messages = recentMessages.length > 0
|
|
104
|
+
? recentMessages.map(m => ` - ${JSON.stringify(m)}`).join('\n')
|
|
105
|
+
: ' (none)';
|
|
106
|
+
|
|
107
|
+
const trajectory = trajectoryHistory.length > 0
|
|
108
|
+
? trajectoryHistory.map(t => ` - [${t.sessionId}] ${t.term} (${t.timestamp}): ${t.userMessage.substring(0, MAX_TRAJECTORY_MESSAGE_LENGTH)}`)
|
|
109
|
+
.join('\n')
|
|
110
|
+
: ' (none)';
|
|
111
|
+
|
|
112
|
+
return [
|
|
113
|
+
'You are a correction keyword optimizer.',
|
|
114
|
+
'',
|
|
115
|
+
'## TASK',
|
|
116
|
+
'Analyze the current correction keyword store and recent user messages.',
|
|
117
|
+
'Recommend ADD/UPDATE/REMOVE actions to improve correction cue accuracy.',
|
|
118
|
+
'',
|
|
119
|
+
'## Current Keyword Store (' + keywordStoreSummary.totalKeywords + ' terms):',
|
|
120
|
+
termsList,
|
|
121
|
+
'',
|
|
122
|
+
'## Recent User Messages (' + recentMessages.length + ' messages):',
|
|
123
|
+
messages,
|
|
124
|
+
'',
|
|
125
|
+
'## Correction Trajectory (recent confirmed corrections, D-40-08):',
|
|
126
|
+
trajectory,
|
|
127
|
+
'',
|
|
128
|
+
'## Rules:',
|
|
129
|
+
'- ADD: If a correction pattern is detected in messages but not in store',
|
|
130
|
+
'- UPDATE: If a term\'s weight should change based on TP/FP ratio',
|
|
131
|
+
'- REMOVE: If a term has 0 hits after many uses AND high false positive rate (>0.3)',
|
|
132
|
+
'- Keep reasoning concise (max 100 chars)',
|
|
133
|
+
'- Weight range: 0.1-0.9',
|
|
134
|
+
'',
|
|
135
|
+
'Return strict JSON (no markdown):',
|
|
136
|
+
'{"updated": boolean, "updates": {...}, "summary": string}',
|
|
137
|
+
].join('\n');
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
async parseResult(ctx: WorkflowResultContext): Promise<CorrectionObserverResult | null> {
|
|
141
|
+
const rawText = extractAssistantTextForSpec(ctx.messages, ctx.assistantTexts);
|
|
142
|
+
return parseCorrectionObserverPayload(rawText);
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
async persistResult(_ctx: WorkflowPersistContext<CorrectionObserverResult>): Promise<void> {
|
|
146
|
+
// Result persistence is handled by the caller (evolution-worker.ts)
|
|
147
|
+
// which reads the result and applies keyword store updates.
|
|
148
|
+
// This spec handles only the LLM dispatch and result parsing.
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
shouldFinalizeOnWaitStatus(status: 'ok' | 'error' | 'timeout'): boolean {
|
|
152
|
+
return status === 'ok';
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// ── Manager Class ─────────────────────────────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
export class CorrectionObserverWorkflowManager extends WorkflowManagerBase {
|
|
159
|
+
constructor(opts: CorrectionObserverWorkflowOptions) {
|
|
160
|
+
super({
|
|
161
|
+
workspaceDir: opts.workspaceDir,
|
|
162
|
+
logger: opts.logger,
|
|
163
|
+
subagent: opts.subagent,
|
|
164
|
+
agentSession: opts.agentSession,
|
|
165
|
+
workflowType: 'correction_observer',
|
|
166
|
+
sessionPrefix: WORKFLOW_SESSION_PREFIX,
|
|
167
|
+
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
|
168
|
+
defaultTtlMs: DEFAULT_TTL_MS,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async startWorkflow<TResult>(
|
|
173
|
+
spec: SubagentWorkflowSpec<TResult>,
|
|
174
|
+
options: {
|
|
175
|
+
parentSessionId: string;
|
|
176
|
+
workspaceDir?: string;
|
|
177
|
+
taskInput: unknown;
|
|
178
|
+
metadata?: Record<string, unknown>;
|
|
179
|
+
}
|
|
180
|
+
): Promise<WorkflowHandle> {
|
|
181
|
+
// Surface degrade: skip boot sessions
|
|
182
|
+
if (options.parentSessionId.startsWith('boot-')) {
|
|
183
|
+
this.logger.info(`[PD:CorrectionObserver] Skipping workflow: boot session`);
|
|
184
|
+
throw new Error(`CorrectionObserverWorkflowManager: cannot start workflow for boot session`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Surface degrade: check subagent runtime availability
|
|
188
|
+
if (!isSubagentRuntimeAvailable(this.driver.getSubagent())) {
|
|
189
|
+
this.logger.info(`[PD:CorrectionObserver] Skipping workflow: subagent runtime unavailable`);
|
|
190
|
+
throw new Error(`CorrectionObserverWorkflowManager: subagent runtime unavailable`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (spec.transport !== 'runtime_direct') {
|
|
194
|
+
throw new Error(`CorrectionObserverWorkflowManager only supports runtime_direct transport`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return super.startWorkflow(spec, options);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Get the parsed workflow result for a completed workflow.
|
|
202
|
+
* Used by callers (evolution-worker.ts) to retrieve LLM optimization results
|
|
203
|
+
* after the workflow completes, so mutations can be applied to the keyword store.
|
|
204
|
+
*/
|
|
205
|
+
async getWorkflowResult(workflowId: string): Promise<CorrectionObserverResult | null> {
|
|
206
|
+
const workflow = this.store.getWorkflow(workflowId);
|
|
207
|
+
if (!workflow) return null;
|
|
208
|
+
|
|
209
|
+
const result = await this.driver.getResult({ sessionKey: workflow.child_session_key, limit: 20 });
|
|
210
|
+
return correctionObserverWorkflowSpec.parseResult({
|
|
211
|
+
messages: result.messages,
|
|
212
|
+
assistantTexts: result.assistantTexts,
|
|
213
|
+
metadata: JSON.parse(workflow.metadata_json) as WorkflowMetadata,
|
|
214
|
+
waitStatus: 'ok',
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this
|
|
219
|
+
protected override createWorkflowMetadata<TResult>(
|
|
220
|
+
spec: SubagentWorkflowSpec<TResult>,
|
|
221
|
+
options: {
|
|
222
|
+
parentSessionId: string;
|
|
223
|
+
workspaceDir?: string;
|
|
224
|
+
taskInput: unknown;
|
|
225
|
+
metadata?: Record<string, unknown>;
|
|
226
|
+
},
|
|
227
|
+
now: number
|
|
228
|
+
): WorkflowMetadata {
|
|
229
|
+
return {
|
|
230
|
+
parentSessionId: options.parentSessionId,
|
|
231
|
+
workspaceDir: options.workspaceDir,
|
|
232
|
+
taskInput: options.taskInput,
|
|
233
|
+
startedAt: now,
|
|
234
|
+
workflowType: spec.workflowType,
|
|
235
|
+
...options.metadata,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ── Factory ─────────────────────────────────────────────────────────────────
|
|
241
|
+
|
|
242
|
+
export function createCorrectionObserverWorkflowManager(
|
|
243
|
+
opts: CorrectionObserverWorkflowOptions
|
|
244
|
+
): CorrectionObserverWorkflowManager {
|
|
245
|
+
return new CorrectionObserverWorkflowManager(opts);
|
|
246
|
+
}
|
|
@@ -13,11 +13,11 @@ import type { RuntimeDirectDriver } from './runtime-direct-driver.js';
|
|
|
13
13
|
import { isSubagentRuntimeAvailable } from '../../utils/subagent-probe.js';
|
|
14
14
|
import { buildCritiquePromptV2 } from '../../tools/critique-prompt.js';
|
|
15
15
|
import { WorkflowManagerBase } from './workflow-manager-base.js';
|
|
16
|
+
import { DEEP_REFLECT_TTL_MS } from '../../config/defaults/runtime.js';
|
|
16
17
|
|
|
17
18
|
const WORKFLOW_SESSION_PREFIX = 'agent:main:subagent:workflow-';
|
|
18
19
|
|
|
19
20
|
const DEFAULT_TIMEOUT_MS = 60_000; // Deep-reflect needs more time than empathy
|
|
20
|
-
const DEFAULT_TTL_MS = 10 * 60 * 1000;
|
|
21
21
|
|
|
22
22
|
export interface DeepReflectWorkflowOptions {
|
|
23
23
|
workspaceDir: string;
|
|
@@ -37,7 +37,7 @@ export class DeepReflectWorkflowManager extends WorkflowManagerBase {
|
|
|
37
37
|
workflowType: 'deep-reflect',
|
|
38
38
|
sessionPrefix: WORKFLOW_SESSION_PREFIX,
|
|
39
39
|
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
|
40
|
-
defaultTtlMs:
|
|
40
|
+
defaultTtlMs: DEEP_REFLECT_TTL_MS,
|
|
41
41
|
});
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -70,7 +70,7 @@ export class DeepReflectWorkflowManager extends WorkflowManagerBase {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
|
|
74
74
|
protected override generateWorkflowId(): string {
|
|
75
75
|
return `wf_dr_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;
|
|
76
76
|
}
|
|
@@ -98,7 +98,7 @@ export const deepReflectWorkflowSpec: SubagentWorkflowSpec<DeepReflectResult> =
|
|
|
98
98
|
workflowType: 'deep-reflect',
|
|
99
99
|
transport: 'runtime_direct',
|
|
100
100
|
timeoutMs: 60_000,
|
|
101
|
-
ttlMs:
|
|
101
|
+
ttlMs: DEEP_REFLECT_TTL_MS,
|
|
102
102
|
shouldDeleteSessionAfterFinalize: true,
|
|
103
103
|
|
|
104
104
|
buildPrompt(taskInput: unknown, ctx: DeepReflectBuildPromptContext): string {
|
|
@@ -14,11 +14,11 @@ import { trackFriction } from '../../core/session-tracker.js';
|
|
|
14
14
|
import { isSubagentRuntimeAvailable } from '../../utils/subagent-probe.js';
|
|
15
15
|
import { WorkflowManagerBase } from './workflow-manager-base.js';
|
|
16
16
|
import { normalizeSeverity } from '../../core/empathy-types.js';
|
|
17
|
+
import { WORKFLOW_TTL_MS } from '../../config/defaults/runtime.js';
|
|
17
18
|
|
|
18
19
|
const WORKFLOW_SESSION_PREFIX = 'agent:main:subagent:workflow-';
|
|
19
20
|
|
|
20
21
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
21
|
-
const DEFAULT_TTL_MS = 5 * 60 * 1000;
|
|
22
22
|
|
|
23
23
|
export interface EmpathyObserverWorkflowOptions {
|
|
24
24
|
workspaceDir: string;
|
|
@@ -38,7 +38,7 @@ export class EmpathyObserverWorkflowManager extends WorkflowManagerBase {
|
|
|
38
38
|
workflowType: 'empathy-observer',
|
|
39
39
|
sessionPrefix: WORKFLOW_SESSION_PREFIX,
|
|
40
40
|
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
|
41
|
-
defaultTtlMs:
|
|
41
|
+
defaultTtlMs: WORKFLOW_TTL_MS,
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -71,7 +71,7 @@ export class EmpathyObserverWorkflowManager extends WorkflowManagerBase {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
|
|
75
75
|
protected override createWorkflowMetadata<TResult>(
|
|
76
76
|
spec: SubagentWorkflowSpec<TResult>,
|
|
77
77
|
options: {
|
|
@@ -105,7 +105,7 @@ export class EmpathyObserverWorkflowManager extends WorkflowManagerBase {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
|
|
109
109
|
protected override generateWorkflowId(): string {
|
|
110
110
|
return `wf_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;
|
|
111
111
|
}
|
|
@@ -65,3 +65,16 @@ export type {
|
|
|
65
65
|
WorkflowEventRow,
|
|
66
66
|
WorkflowDebugSummary,
|
|
67
67
|
} from './types.js';
|
|
68
|
+
|
|
69
|
+
export {
|
|
70
|
+
CorrectionObserverWorkflowManager,
|
|
71
|
+
createCorrectionObserverWorkflowManager,
|
|
72
|
+
correctionObserverWorkflowSpec,
|
|
73
|
+
type CorrectionObserverWorkflowOptions,
|
|
74
|
+
} from './correction-observer-workflow-manager.js';
|
|
75
|
+
|
|
76
|
+
export type {
|
|
77
|
+
CorrectionObserverPayload,
|
|
78
|
+
CorrectionObserverResult,
|
|
79
|
+
CorrectionObserverWorkflowSpec,
|
|
80
|
+
} from './correction-observer-types.js';
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
} from '../nocturnal-service.js';
|
|
37
37
|
import { type TrinityStageFailure, type TrinityResult } from '../../core/nocturnal-trinity.js';
|
|
38
38
|
import type { TrinityRuntimeAdapter } from '../../core/nocturnal-trinity.js';
|
|
39
|
-
import type { RecentPainContext } from '
|
|
39
|
+
import type { RecentPainContext } from './types.js';
|
|
40
40
|
import * as fs from 'fs';
|
|
41
41
|
import * as path from 'path';
|
|
42
42
|
import { validateNocturnalSnapshotIngress } from '../../core/nocturnal-snapshot-contract.js';
|
|
@@ -394,7 +394,7 @@ export class NocturnalWorkflowManager implements WorkflowManager {
|
|
|
394
394
|
}
|
|
395
395
|
|
|
396
396
|
|
|
397
|
-
|
|
397
|
+
|
|
398
398
|
async notifyLifecycleEvent(
|
|
399
399
|
|
|
400
400
|
_workflowId: string,
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import type {
|
|
13
13
|
NocturnalArtifact,
|
|
14
|
+
ArbiterResult,
|
|
14
15
|
} from '../../core/nocturnal-arbiter.js';
|
|
15
16
|
import type {
|
|
16
17
|
BoundedAction,
|
|
@@ -18,12 +19,16 @@ import type {
|
|
|
18
19
|
import type {
|
|
19
20
|
NocturnalSessionSnapshot,
|
|
20
21
|
} from '../../core/nocturnal-trajectory-extractor.js';
|
|
21
|
-
import type {
|
|
22
|
-
NocturnalRunDiagnostics,
|
|
23
|
-
} from '../nocturnal-service.js';
|
|
24
22
|
import type {
|
|
25
23
|
TrinityResult,
|
|
26
24
|
} from '../../core/nocturnal-trinity.js';
|
|
25
|
+
import type {
|
|
26
|
+
IdleCheckResult,
|
|
27
|
+
PreflightCheckResult,
|
|
28
|
+
} from '../nocturnal-runtime.js';
|
|
29
|
+
import type {
|
|
30
|
+
NocturnalSelectionResult,
|
|
31
|
+
} from '../nocturnal-target-selector.js';
|
|
27
32
|
|
|
28
33
|
// ── Workflow Transport ────────────────────────────────────────────────────────
|
|
29
34
|
|
|
@@ -366,6 +371,27 @@ export type { PluginLogger } from '../../openclaw-sdk.js';
|
|
|
366
371
|
|
|
367
372
|
// ── Nocturnal Workflow Types ───────────────────────────────────────────────────
|
|
368
373
|
|
|
374
|
+
/**
|
|
375
|
+
* Recent pain context for sleep_reflection tasks.
|
|
376
|
+
* Used by target selector for ranking bias and context enrichment.
|
|
377
|
+
* Originally from evolution-worker.ts, moved here to break circular dependency.
|
|
378
|
+
*/
|
|
379
|
+
export interface RecentPainContext {
|
|
380
|
+
/** Most recent unresolved pain event */
|
|
381
|
+
mostRecent: {
|
|
382
|
+
score: number;
|
|
383
|
+
source: string;
|
|
384
|
+
reason: string;
|
|
385
|
+
timestamp: string;
|
|
386
|
+
/** Session ID where the pain occurred */
|
|
387
|
+
sessionId: string;
|
|
388
|
+
} | null;
|
|
389
|
+
/** Count of pain events in the recent window (for signal strength) */
|
|
390
|
+
recentPainCount: number;
|
|
391
|
+
/** Highest pain score in the recent window */
|
|
392
|
+
recentMaxPainScore: number;
|
|
393
|
+
}
|
|
394
|
+
|
|
369
395
|
/**
|
|
370
396
|
* Nocturnal workflow result type.
|
|
371
397
|
* Mirrors NocturnalRunResult from nocturnal-service.ts (per D-02).
|
|
@@ -381,3 +407,43 @@ export type NocturnalResult = {
|
|
|
381
407
|
diagnostics: NocturnalRunDiagnostics;
|
|
382
408
|
trinityTelemetry?: TrinityResult['telemetry'];
|
|
383
409
|
};
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Diagnostics from each pipeline stage.
|
|
413
|
+
* Duplicated from nocturnal-service.ts to break circular dependency.
|
|
414
|
+
*/
|
|
415
|
+
export interface NocturnalRunDiagnostics {
|
|
416
|
+
/** Pre-flight check result */
|
|
417
|
+
preflight: PreflightCheckResult | null;
|
|
418
|
+
/** Selection result */
|
|
419
|
+
selection: NocturnalSelectionResult | null;
|
|
420
|
+
/** Idle check result */
|
|
421
|
+
idle: IdleCheckResult | null;
|
|
422
|
+
/** Whether Trinity chain was attempted */
|
|
423
|
+
trinityAttempted: boolean;
|
|
424
|
+
/** Trinity result (if trinityAttempted === true) */
|
|
425
|
+
trinityResult: TrinityResult | null;
|
|
426
|
+
/** Which chain mode was used */
|
|
427
|
+
chainModeUsed: 'trinity' | 'single-reflector' | null;
|
|
428
|
+
/** Arbiter validation result */
|
|
429
|
+
arbiterResult: ArbiterResult | null;
|
|
430
|
+
/** Executability validation result (if arbiter passed) */
|
|
431
|
+
executabilityResult: { executable: boolean; failures: string[] } | null;
|
|
432
|
+
/** Whether artifact was persisted */
|
|
433
|
+
persisted: boolean;
|
|
434
|
+
/** Persistence path (if persisted) */
|
|
435
|
+
persistedPath?: string;
|
|
436
|
+
/** Code-candidate sidecar diagnostics */
|
|
437
|
+
artificer: NocturnalArtificerDiagnostics;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export interface NocturnalArtificerDiagnostics {
|
|
441
|
+
status: 'skipped' | 'validation_failed' | 'persisted_candidate';
|
|
442
|
+
reason?:
|
|
443
|
+
| 'behavioral_artifact_unavailable'
|
|
444
|
+
| 'no_deterministic_rule'
|
|
445
|
+
| 'persistence_failed'
|
|
446
|
+
| 'cancelled';
|
|
447
|
+
validationErrors?: string[];
|
|
448
|
+
persistedPath?: string;
|
|
449
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shadow Observation Fingerprint Utilities
|
|
3
|
+
*
|
|
4
|
+
* Computes fingerprints for shadow task routing and tracking.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as crypto from 'crypto';
|
|
8
|
+
import type { PluginHookSubagentSpawningEvent } from '../openclaw-sdk.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* PD local worker profiles that are managed by the shadow routing policy.
|
|
12
|
+
*/
|
|
13
|
+
const _pdLocalProfiles = new Set(['local-reader', 'local-editor']);
|
|
14
|
+
export const PD_LOCAL_PROFILES: ReadonlySet<string> = _pdLocalProfiles;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Check if a profile is a local PD profile.
|
|
18
|
+
*/
|
|
19
|
+
export function isLocalProfile(profile: string): boolean {
|
|
20
|
+
return _pdLocalProfiles.has(profile);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const RUNTIME_SHADOW_FINGERPRINT_HEX_LENGTH = 16;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Compute a fingerprint for runtime shadow task tracking.
|
|
27
|
+
* Used to correlate shadow routing decisions with subagent lifecycle events.
|
|
28
|
+
*/
|
|
29
|
+
export function computeRuntimeShadowTaskFingerprint(
|
|
30
|
+
event: PluginHookSubagentSpawningEvent,
|
|
31
|
+
): string {
|
|
32
|
+
const payload = {
|
|
33
|
+
childSessionKey: event.childSessionKey,
|
|
34
|
+
agentId: event.agentId,
|
|
35
|
+
label: event.label ?? '',
|
|
36
|
+
mode: event.mode,
|
|
37
|
+
threadRequested: event.threadRequested,
|
|
38
|
+
requesterChannel: event.requester?.channel ?? '',
|
|
39
|
+
requesterThreadId: event.requester?.threadId ?? '',
|
|
40
|
+
};
|
|
41
|
+
return crypto.createHash('sha256').update(JSON.stringify(payload)).digest('hex').slice(0, RUNTIME_SHADOW_FINGERPRINT_HEX_LENGTH);
|
|
42
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace Directory Resolution Utilities
|
|
3
|
+
*
|
|
4
|
+
* Shared helpers for resolving workspace directories across commands and hooks.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { OpenClawPluginApi } from '../openclaw-sdk.js';
|
|
8
|
+
import { validateWorkspaceDir, type WorkspaceResolutionContext } from '../core/workspace-dir-validation.js';
|
|
9
|
+
import { resolveWorkspaceDir } from '../core/workspace-dir-service.js';
|
|
10
|
+
import { resolveWorkspaceDirFromApi } from '../core/path-resolver.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resolve workspace directory for command execution.
|
|
14
|
+
*
|
|
15
|
+
* Chain: ctx.workspaceDir → resolveWorkspaceDirFromApi (official OpenClaw API + env vars)
|
|
16
|
+
*
|
|
17
|
+
* CRITICAL: Throws if workspaceDir cannot be resolved. Silent failures are dangerous
|
|
18
|
+
* because commands might operate on the wrong directory.
|
|
19
|
+
*/
|
|
20
|
+
export function resolveCommandWorkspaceDir(
|
|
21
|
+
api: OpenClawPluginApi,
|
|
22
|
+
ctx: { workspaceDir?: string },
|
|
23
|
+
): string {
|
|
24
|
+
// 1. Direct from command context (most reliable — set by OpenClaw for current session)
|
|
25
|
+
if (ctx.workspaceDir) {
|
|
26
|
+
const issue = validateWorkspaceDir(ctx.workspaceDir);
|
|
27
|
+
if (!issue) return ctx.workspaceDir;
|
|
28
|
+
api.logger.error(`[PD:Command] ctx.workspaceDir="${ctx.workspaceDir}" is invalid: ${issue}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 2. Official OpenClaw API → env vars → config file
|
|
32
|
+
const resolved = resolveWorkspaceDirFromApi(api);
|
|
33
|
+
if (resolved) return resolved;
|
|
34
|
+
|
|
35
|
+
// CRITICAL FAILURE: Cannot determine workspace directory
|
|
36
|
+
const errorMsg = `[PD:Command] CRITICAL: Cannot resolve workspace directory. ` +
|
|
37
|
+
`ctx.workspaceDir="${ctx.workspaceDir}" is invalid, and all fallbacks failed. ` +
|
|
38
|
+
`Commands will NOT execute to prevent data corruption.`;
|
|
39
|
+
api.logger.error(errorMsg);
|
|
40
|
+
|
|
41
|
+
throw new Error(errorMsg);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Resolve workspace directory for tool hook execution (safe version).
|
|
46
|
+
* Returns undefined instead of throwing if resolution fails.
|
|
47
|
+
*/
|
|
48
|
+
export function resolveToolHookWorkspaceDirSafe(
|
|
49
|
+
ctx: WorkspaceResolutionContext,
|
|
50
|
+
api: OpenClawPluginApi,
|
|
51
|
+
source: string,
|
|
52
|
+
): string | undefined {
|
|
53
|
+
return resolveWorkspaceDir(api, ctx, { source });
|
|
54
|
+
}
|