principles-disciple 1.32.0 → 1.34.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 (37) hide show
  1. package/openclaw.plugin.json +4 -4
  2. package/package.json +1 -1
  3. package/src/core/correction-cue-learner.ts +203 -0
  4. package/src/core/correction-types.ts +88 -0
  5. package/src/core/evolution-logger.ts +3 -3
  6. package/src/core/init.ts +67 -0
  7. package/src/service/correction-observer-types.ts +58 -0
  8. package/src/service/correction-observer-workflow-manager.ts +218 -0
  9. package/src/service/evolution-worker.ts +172 -146
  10. package/src/service/nocturnal-service.ts +4 -1
  11. package/src/service/subagent-workflow/index.ts +14 -0
  12. package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +3 -1
  13. package/tests/service/evolution-worker.nocturnal.test.ts +14 -1
  14. package/tests/service/evolution-worker.timeout.test.ts +350 -0
  15. package/tests/commands/implementation-lifecycle.test.ts +0 -362
  16. package/tests/core/detection-funnel.test.ts +0 -63
  17. package/tests/core/evolution-e2e.test.ts +0 -58
  18. package/tests/core/evolution-engine-gate-integration.test.ts +0 -543
  19. package/tests/core/evolution-engine.test.ts +0 -562
  20. package/tests/core/evolution-reducer.test.ts +0 -180
  21. package/tests/core/evolution-user-stories.e2e.test.ts +0 -249
  22. package/tests/core/local-worker-routing.test.ts +0 -757
  23. package/tests/core/rule-host.test.ts +0 -389
  24. package/tests/core/trajectory-correction-pain.test.ts +0 -180
  25. package/tests/hooks/gate-edit-verification.test.ts +0 -435
  26. package/tests/hooks/llm.test.ts +0 -308
  27. package/tests/hooks/progressive-trust-gate.test.ts +0 -277
  28. package/tests/hooks/prompt.test.ts +0 -1473
  29. package/tests/index.integration.test.ts +0 -179
  30. package/tests/index.shadow-routing.integration.test.ts +0 -140
  31. package/tests/service/evolution-worker.test.ts +0 -462
  32. package/tests/service/nocturnal-service.test.ts +0 -577
  33. package/tests/service/nocturnal-workflow-manager.test.ts +0 -441
  34. package/tests/tools/critique-prompt.test.ts +0 -260
  35. package/tests/tools/deep-reflect.test.ts +0 -232
  36. package/tests/tools/model-index.test.ts +0 -246
  37. package/tests/ui/app.test.tsx +0 -114
@@ -0,0 +1,218 @@
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 './subagent-workflow/types.js';
18
+ import type { RuntimeDirectDriver } from './subagent-workflow/runtime-direct-driver.js';
19
+ import { WorkflowManagerBase } from './subagent-workflow/workflow-manager-base.js';
20
+ import { isSubagentRuntimeAvailable } from '../utils/subagent-probe.js';
21
+ import type {
22
+ CorrectionObserverPayload,
23
+ CorrectionObserverResult,
24
+ CorrectionObserverWorkflowSpec,
25
+ } from './correction-observer-types.js';
26
+
27
+ const WORKFLOW_SESSION_PREFIX = 'agent:main:subagent:workflow-correction-';
28
+
29
+ const DEFAULT_TIMEOUT_MS = 30_000;
30
+ const DEFAULT_TTL_MS = 5 * 60 * 1000;
31
+
32
+ // ── Options ─────────────────────────────────────────────────────────────────
33
+
34
+ export interface CorrectionObserverWorkflowOptions {
35
+ workspaceDir: string;
36
+ logger: PluginLogger;
37
+ subagent: RuntimeDirectDriver['subagent'];
38
+ /** Pass api.runtime.agent.session to enable heartbeat-safe cleanup (#188) */
39
+ agentSession?: RuntimeDirectDriver['agentSession'];
40
+ }
41
+
42
+ // ── Helper Functions ─────────────────────────────────────────────────────────
43
+
44
+ /**
45
+ * Extract raw assistant text from messages or assistantTexts array.
46
+ */
47
+ function extractAssistantTextForSpec(messages: unknown[], assistantTexts?: string[]): string {
48
+ if (assistantTexts && assistantTexts.length > 0) {
49
+ return assistantTexts[assistantTexts.length - 1] || '';
50
+ }
51
+ for (let i = messages.length - 1; i >= 0; i--) {
52
+ const msg = messages[i] as { role?: string; content?: unknown };
53
+ if (msg?.role !== 'assistant') continue;
54
+ if (typeof msg.content === 'string') return msg.content;
55
+ if (Array.isArray(msg.content)) {
56
+ const txt = msg.content
57
+ .filter((part: unknown) => part && typeof part === 'object' && (part as { type?: string }).type === 'text' && typeof (part as { text?: unknown }).text === 'string')
58
+ .map((part: unknown) => (part as { text: string }).text)
59
+ .join('\n');
60
+ if (txt) return txt;
61
+ }
62
+ }
63
+ return '';
64
+ }
65
+
66
+ /**
67
+ * Parse correction observer JSON payload from raw text.
68
+ */
69
+ function parseCorrectionObserverPayload(rawText: string): CorrectionObserverResult | null {
70
+ if (!rawText?.trim()) return null;
71
+ try {
72
+ return JSON.parse(rawText.trim()) as CorrectionObserverResult;
73
+ } catch {
74
+ const match = /\{[\s\S]*\}/.exec(rawText);
75
+ if (!match) return null;
76
+ try {
77
+ return JSON.parse(match[0]) as CorrectionObserverResult;
78
+ } catch {
79
+ return null;
80
+ }
81
+ }
82
+ }
83
+
84
+ // ── Workflow Spec ─────────────────────────────────────────────────────────────
85
+
86
+ export const correctionObserverWorkflowSpec: SubagentWorkflowSpec<CorrectionObserverResult> = {
87
+ workflowType: 'correction_observer',
88
+ transport: 'runtime_direct',
89
+ timeoutMs: 30_000,
90
+ ttlMs: 300_000,
91
+ shouldDeleteSessionAfterFinalize: true,
92
+
93
+ buildPrompt(taskInput: unknown, _metadata: WorkflowMetadata): string {
94
+ const payload = taskInput as CorrectionObserverPayload;
95
+ const { keywordStoreSummary, recentMessages } = payload;
96
+
97
+ const termsList = keywordStoreSummary.terms
98
+ .map(t => ` - term="${t.term}", weight=${t.weight}, hits=${t.hitCount}, TP=${t.truePositiveCount}, FP=${t.falsePositiveCount}`)
99
+ .join('\n');
100
+
101
+ const messages = recentMessages.length > 0
102
+ ? recentMessages.map(m => ` - ${JSON.stringify(m)}`).join('\n')
103
+ : ' (none)';
104
+
105
+ return [
106
+ 'You are a correction keyword optimizer.',
107
+ '',
108
+ '## TASK',
109
+ 'Analyze the current correction keyword store and recent user messages.',
110
+ 'Recommend ADD/UPDATE/REMOVE actions to improve correction cue accuracy.',
111
+ '',
112
+ '## Current Keyword Store (' + keywordStoreSummary.totalKeywords + ' terms):',
113
+ termsList,
114
+ '',
115
+ '## Recent User Messages (' + recentMessages.length + ' messages):',
116
+ messages,
117
+ '',
118
+ '## Rules:',
119
+ '- ADD: If a correction pattern is detected in messages but not in store',
120
+ '- UPDATE: If a term\'s weight should change based on TP/FP ratio',
121
+ '- REMOVE: If a term has 0 hits after many uses AND high false positive rate (>0.3)',
122
+ '- Keep reasoning concise (max 100 chars)',
123
+ '- Weight range: 0.1-0.9',
124
+ '',
125
+ 'Return strict JSON (no markdown):',
126
+ '{"updated": boolean, "updates": {...}, "summary": string}',
127
+ ].join('\n');
128
+ },
129
+
130
+ async parseResult(ctx: WorkflowResultContext): Promise<CorrectionObserverResult | null> {
131
+ const rawText = extractAssistantTextForSpec(ctx.messages, ctx.assistantTexts);
132
+ return parseCorrectionObserverPayload(rawText);
133
+ },
134
+
135
+ async persistResult(_ctx: WorkflowPersistContext<CorrectionObserverResult>): Promise<void> {
136
+ // Result persistence is handled by the caller (evolution-worker.ts)
137
+ // which reads the result and applies keyword store updates.
138
+ // This spec handles only the LLM dispatch and result parsing.
139
+ },
140
+
141
+ shouldFinalizeOnWaitStatus(status: 'ok' | 'error' | 'timeout'): boolean {
142
+ return status === 'ok';
143
+ },
144
+ };
145
+
146
+ // ── Manager Class ─────────────────────────────────────────────────────────────
147
+
148
+ export class CorrectionObserverWorkflowManager extends WorkflowManagerBase {
149
+ constructor(opts: CorrectionObserverWorkflowOptions) {
150
+ super({
151
+ workspaceDir: opts.workspaceDir,
152
+ logger: opts.logger,
153
+ subagent: opts.subagent,
154
+ agentSession: opts.agentSession,
155
+ workflowType: 'correction_observer',
156
+ sessionPrefix: WORKFLOW_SESSION_PREFIX,
157
+ defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
158
+ defaultTtlMs: DEFAULT_TTL_MS,
159
+ });
160
+ }
161
+
162
+ async startWorkflow<TResult>(
163
+ spec: SubagentWorkflowSpec<TResult>,
164
+ options: {
165
+ parentSessionId: string;
166
+ workspaceDir?: string;
167
+ taskInput: unknown;
168
+ metadata?: Record<string, unknown>;
169
+ }
170
+ ): Promise<WorkflowHandle> {
171
+ // Surface degrade: skip boot sessions
172
+ if (options.parentSessionId.startsWith('boot-')) {
173
+ this.logger.info(`[PD:CorrectionObserver] Skipping workflow: boot session`);
174
+ throw new Error(`CorrectionObserverWorkflowManager: cannot start workflow for boot session`);
175
+ }
176
+
177
+ // Surface degrade: check subagent runtime availability
178
+ if (!isSubagentRuntimeAvailable(this.driver.getSubagent())) {
179
+ this.logger.info(`[PD:CorrectionObserver] Skipping workflow: subagent runtime unavailable`);
180
+ throw new Error(`CorrectionObserverWorkflowManager: subagent runtime unavailable`);
181
+ }
182
+
183
+ if (spec.transport !== 'runtime_direct') {
184
+ throw new Error(`CorrectionObserverWorkflowManager only supports runtime_direct transport`);
185
+ }
186
+
187
+ return super.startWorkflow(spec, options);
188
+ }
189
+
190
+ // eslint-disable-next-line @typescript-eslint/class-methods-use-this
191
+ protected override createWorkflowMetadata<TResult>(
192
+ spec: SubagentWorkflowSpec<TResult>,
193
+ options: {
194
+ parentSessionId: string;
195
+ workspaceDir?: string;
196
+ taskInput: unknown;
197
+ metadata?: Record<string, unknown>;
198
+ },
199
+ now: number
200
+ ): WorkflowMetadata {
201
+ return {
202
+ parentSessionId: options.parentSessionId,
203
+ workspaceDir: options.workspaceDir,
204
+ taskInput: options.taskInput,
205
+ startedAt: now,
206
+ workflowType: spec.workflowType,
207
+ ...options.metadata,
208
+ };
209
+ }
210
+ }
211
+
212
+ // ── Factory ─────────────────────────────────────────────────────────────────
213
+
214
+ export function createCorrectionObserverWorkflowManager(
215
+ opts: CorrectionObserverWorkflowOptions
216
+ ): CorrectionObserverWorkflowManager {
217
+ return new CorrectionObserverWorkflowManager(opts);
218
+ }