principles-disciple 1.5.4 → 1.7.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 (88) hide show
  1. package/dist/commands/context.d.ts +5 -0
  2. package/dist/commands/context.js +312 -0
  3. package/dist/commands/evolution-status.d.ts +4 -0
  4. package/dist/commands/evolution-status.js +138 -0
  5. package/dist/commands/export.d.ts +2 -0
  6. package/dist/commands/export.js +45 -0
  7. package/dist/commands/focus.d.ts +14 -0
  8. package/dist/commands/focus.js +582 -0
  9. package/dist/commands/pain.js +143 -6
  10. package/dist/commands/principle-rollback.d.ts +4 -0
  11. package/dist/commands/principle-rollback.js +22 -0
  12. package/dist/commands/rollback.d.ts +19 -0
  13. package/dist/commands/rollback.js +119 -0
  14. package/dist/commands/samples.d.ts +2 -0
  15. package/dist/commands/samples.js +55 -0
  16. package/dist/core/config.d.ts +37 -0
  17. package/dist/core/config.js +47 -0
  18. package/dist/core/control-ui-db.d.ts +68 -0
  19. package/dist/core/control-ui-db.js +274 -0
  20. package/dist/core/detection-funnel.d.ts +1 -1
  21. package/dist/core/detection-funnel.js +4 -0
  22. package/dist/core/dictionary.d.ts +2 -0
  23. package/dist/core/dictionary.js +13 -0
  24. package/dist/core/event-log.d.ts +22 -1
  25. package/dist/core/event-log.js +319 -0
  26. package/dist/core/evolution-engine.d.ts +5 -5
  27. package/dist/core/evolution-engine.js +18 -18
  28. package/dist/core/evolution-migration.d.ts +5 -0
  29. package/dist/core/evolution-migration.js +65 -0
  30. package/dist/core/evolution-reducer.d.ts +69 -0
  31. package/dist/core/evolution-reducer.js +369 -0
  32. package/dist/core/evolution-types.d.ts +103 -0
  33. package/dist/core/focus-history.d.ts +65 -0
  34. package/dist/core/focus-history.js +266 -0
  35. package/dist/core/init.js +30 -7
  36. package/dist/core/migration.js +0 -2
  37. package/dist/core/path-resolver.d.ts +3 -0
  38. package/dist/core/path-resolver.js +90 -31
  39. package/dist/core/paths.d.ts +7 -8
  40. package/dist/core/paths.js +48 -40
  41. package/dist/core/profile.js +1 -1
  42. package/dist/core/session-tracker.d.ts +4 -0
  43. package/dist/core/session-tracker.js +15 -0
  44. package/dist/core/thinking-models.d.ts +38 -0
  45. package/dist/core/thinking-models.js +170 -0
  46. package/dist/core/trajectory.d.ts +184 -0
  47. package/dist/core/trajectory.js +817 -0
  48. package/dist/core/trust-engine.d.ts +2 -0
  49. package/dist/core/trust-engine.js +30 -4
  50. package/dist/core/workspace-context.d.ts +13 -0
  51. package/dist/core/workspace-context.js +50 -7
  52. package/dist/hooks/gate.js +301 -30
  53. package/dist/hooks/llm.d.ts +8 -0
  54. package/dist/hooks/llm.js +347 -69
  55. package/dist/hooks/message-sanitize.d.ts +3 -0
  56. package/dist/hooks/message-sanitize.js +37 -0
  57. package/dist/hooks/pain.js +105 -5
  58. package/dist/hooks/prompt.d.ts +20 -11
  59. package/dist/hooks/prompt.js +558 -158
  60. package/dist/hooks/subagent.d.ts +9 -2
  61. package/dist/hooks/subagent.js +40 -3
  62. package/dist/http/principles-console-route.d.ts +2 -0
  63. package/dist/http/principles-console-route.js +257 -0
  64. package/dist/i18n/commands.js +48 -20
  65. package/dist/index.js +264 -8
  66. package/dist/service/control-ui-query-service.d.ts +217 -0
  67. package/dist/service/control-ui-query-service.js +537 -0
  68. package/dist/service/empathy-observer-manager.d.ts +42 -0
  69. package/dist/service/empathy-observer-manager.js +147 -0
  70. package/dist/service/evolution-worker.d.ts +10 -0
  71. package/dist/service/evolution-worker.js +156 -24
  72. package/dist/service/trajectory-service.d.ts +2 -0
  73. package/dist/service/trajectory-service.js +15 -0
  74. package/dist/tools/agent-spawn.d.ts +27 -6
  75. package/dist/tools/agent-spawn.js +339 -87
  76. package/dist/tools/deep-reflect.d.ts +27 -7
  77. package/dist/tools/deep-reflect.js +282 -113
  78. package/dist/types/event-types.d.ts +84 -2
  79. package/dist/types/event-types.js +33 -0
  80. package/dist/types.d.ts +52 -0
  81. package/dist/types.js +24 -1
  82. package/openclaw.plugin.json +43 -11
  83. package/package.json +16 -6
  84. package/templates/langs/zh/core/HEARTBEAT.md +28 -4
  85. package/templates/langs/zh/skills/pd-daily/SKILL.md +97 -13
  86. package/templates/pain_settings.json +54 -2
  87. package/templates/workspace/.principles/PROFILE.json +2 -0
  88. package/templates/workspace/okr/CURRENT_FOCUS.md +57 -0
@@ -0,0 +1,274 @@
1
+ import Database from 'better-sqlite3';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { withLock } from '../utils/file-lock.js';
5
+ import { resolvePdPath } from './paths.js';
6
+ const DEFAULT_BUSY_TIMEOUT_MS = 5000;
7
+ function safeJson(value) {
8
+ return JSON.stringify(value ?? []);
9
+ }
10
+ export class ControlUiDatabase {
11
+ workspaceDir;
12
+ dbPath;
13
+ blobDir;
14
+ db;
15
+ constructor(opts) {
16
+ this.workspaceDir = path.resolve(opts.workspaceDir);
17
+ this.dbPath = resolvePdPath(this.workspaceDir, 'TRAJECTORY_DB');
18
+ this.blobDir = resolvePdPath(this.workspaceDir, 'TRAJECTORY_BLOBS_DIR');
19
+ fs.mkdirSync(path.dirname(this.dbPath), { recursive: true });
20
+ fs.mkdirSync(this.blobDir, { recursive: true });
21
+ this.db = new Database(this.dbPath);
22
+ this.db.pragma('journal_mode = WAL');
23
+ this.db.pragma('foreign_keys = ON');
24
+ this.db.pragma('synchronous = NORMAL');
25
+ this.db.pragma(`busy_timeout = ${Math.max(0, opts.busyTimeoutMs ?? DEFAULT_BUSY_TIMEOUT_MS)}`);
26
+ this.initSchema();
27
+ }
28
+ dispose() {
29
+ this.db.close();
30
+ }
31
+ recordThinkingModelEvent(input) {
32
+ return this.withWrite(() => {
33
+ const result = this.db.prepare(`
34
+ INSERT INTO thinking_model_events (
35
+ session_id, run_id, assistant_turn_id, model_id, matched_pattern, scenario_json,
36
+ tool_context_json, pain_context_json, principle_context_json, trigger_excerpt, created_at
37
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
38
+ `).run(input.sessionId, input.runId, input.assistantTurnId, input.modelId, input.matchedPattern, safeJson(input.scenarioJson), safeJson(input.toolContextJson), safeJson(input.painContextJson), safeJson(input.principleContextJson), input.triggerExcerpt, input.createdAt);
39
+ return Number(result.lastInsertRowid);
40
+ });
41
+ }
42
+ getRecentThinkingContext(sessionId, beforeCreatedAt, limit = 5) {
43
+ return {
44
+ toolCalls: this.all(`
45
+ SELECT id, tool_name, outcome, error_type, error_message, created_at
46
+ FROM tool_calls
47
+ WHERE session_id = ? AND created_at <= ?
48
+ ORDER BY created_at DESC
49
+ LIMIT ?
50
+ `, sessionId, beforeCreatedAt, limit).map((row) => ({
51
+ id: Number(row.id),
52
+ toolName: String(row.tool_name),
53
+ outcome: row.outcome,
54
+ errorType: row.error_type,
55
+ errorMessage: row.error_message,
56
+ createdAt: String(row.created_at),
57
+ })),
58
+ painEvents: this.all(`
59
+ SELECT id, source, score, reason, created_at
60
+ FROM pain_events
61
+ WHERE session_id = ? AND created_at <= ?
62
+ ORDER BY created_at DESC
63
+ LIMIT ?
64
+ `, sessionId, beforeCreatedAt, limit).map((row) => ({
65
+ id: Number(row.id),
66
+ source: String(row.source),
67
+ score: Number(row.score),
68
+ reason: row.reason,
69
+ createdAt: String(row.created_at),
70
+ })),
71
+ gateBlocks: this.all(`
72
+ SELECT id, tool_name, reason, file_path, created_at
73
+ FROM gate_blocks
74
+ WHERE session_id = ? AND created_at <= ?
75
+ ORDER BY created_at DESC
76
+ LIMIT ?
77
+ `, sessionId, beforeCreatedAt, limit).map((row) => ({
78
+ id: Number(row.id),
79
+ toolName: String(row.tool_name),
80
+ reason: String(row.reason),
81
+ filePath: row.file_path,
82
+ createdAt: String(row.created_at),
83
+ })),
84
+ userCorrections: this.all(`
85
+ SELECT id, correction_cue, raw_excerpt, created_at
86
+ FROM user_turns
87
+ WHERE session_id = ? AND correction_detected = 1 AND created_at <= ?
88
+ ORDER BY created_at DESC
89
+ LIMIT ?
90
+ `, sessionId, beforeCreatedAt, limit).map((row) => ({
91
+ id: Number(row.id),
92
+ correctionCue: row.correction_cue,
93
+ rawExcerpt: row.raw_excerpt,
94
+ createdAt: String(row.created_at),
95
+ })),
96
+ principleEvents: this.all(`
97
+ SELECT id, principle_id, event_type, created_at
98
+ FROM principle_events
99
+ WHERE created_at <= ?
100
+ ORDER BY created_at DESC
101
+ LIMIT ?
102
+ `, beforeCreatedAt, limit).map((row) => ({
103
+ id: Number(row.id),
104
+ principleId: row.principle_id,
105
+ eventType: String(row.event_type),
106
+ createdAt: String(row.created_at),
107
+ })),
108
+ };
109
+ }
110
+ all(sql, ...params) {
111
+ return this.db.prepare(sql).all(...params);
112
+ }
113
+ get(sql, ...params) {
114
+ return this.db.prepare(sql).get(...params);
115
+ }
116
+ restoreRawText(inlineText, blobRef) {
117
+ if (inlineText)
118
+ return inlineText;
119
+ if (!blobRef)
120
+ return '';
121
+ const fullPath = path.join(this.blobDir, blobRef);
122
+ return fs.existsSync(fullPath) ? fs.readFileSync(fullPath, 'utf8') : '';
123
+ }
124
+ initSchema() {
125
+ this.db.exec(`
126
+ CREATE TABLE IF NOT EXISTS thinking_model_events (
127
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
128
+ session_id TEXT NOT NULL,
129
+ run_id TEXT NOT NULL,
130
+ assistant_turn_id INTEGER NOT NULL,
131
+ model_id TEXT NOT NULL,
132
+ matched_pattern TEXT NOT NULL,
133
+ scenario_json TEXT NOT NULL,
134
+ tool_context_json TEXT NOT NULL,
135
+ pain_context_json TEXT NOT NULL,
136
+ principle_context_json TEXT NOT NULL,
137
+ trigger_excerpt TEXT NOT NULL,
138
+ created_at TEXT NOT NULL
139
+ );
140
+ CREATE INDEX IF NOT EXISTS idx_thinking_model_events_model_created
141
+ ON thinking_model_events(model_id, created_at);
142
+ CREATE INDEX IF NOT EXISTS idx_thinking_model_events_session_created
143
+ ON thinking_model_events(session_id, created_at);
144
+ CREATE INDEX IF NOT EXISTS idx_thinking_model_events_assistant_turn
145
+ ON thinking_model_events(assistant_turn_id);
146
+ CREATE INDEX IF NOT EXISTS idx_thinking_model_events_run_id
147
+ ON thinking_model_events(run_id);
148
+
149
+ DROP VIEW IF EXISTS v_thinking_model_usage;
150
+ CREATE VIEW v_thinking_model_usage AS
151
+ WITH totals AS (
152
+ SELECT COUNT(*) AS assistant_turns FROM assistant_turns
153
+ ),
154
+ usage_rows AS (
155
+ SELECT
156
+ model_id,
157
+ COUNT(*) AS hits,
158
+ COUNT(DISTINCT session_id) AS distinct_sessions,
159
+ COUNT(DISTINCT assistant_turn_id) AS distinct_turns
160
+ FROM thinking_model_events
161
+ GROUP BY model_id
162
+ )
163
+ SELECT
164
+ usage_rows.model_id AS model_id,
165
+ usage_rows.hits AS hits,
166
+ usage_rows.distinct_sessions AS distinct_sessions,
167
+ usage_rows.distinct_turns AS distinct_turns,
168
+ CASE
169
+ WHEN totals.assistant_turns = 0 THEN 0
170
+ ELSE ROUND(CAST(usage_rows.distinct_turns AS REAL) / CAST(totals.assistant_turns AS REAL), 4)
171
+ END AS coverage_rate
172
+ FROM usage_rows, totals
173
+ ORDER BY usage_rows.hits DESC, usage_rows.model_id ASC;
174
+
175
+ DROP VIEW IF EXISTS v_thinking_model_effectiveness;
176
+ CREATE VIEW v_thinking_model_effectiveness AS
177
+ WITH event_windows AS (
178
+ SELECT
179
+ e.id,
180
+ e.session_id,
181
+ e.model_id,
182
+ e.created_at,
183
+ (
184
+ SELECT MIN(a.created_at)
185
+ FROM assistant_turns a
186
+ WHERE a.session_id = e.session_id AND a.created_at > e.created_at
187
+ ) AS next_assistant_at,
188
+ datetime(e.created_at, '+10 minutes') AS max_window_end
189
+ FROM thinking_model_events e
190
+ ),
191
+ bounded_windows AS (
192
+ SELECT
193
+ id,
194
+ session_id,
195
+ model_id,
196
+ created_at,
197
+ CASE
198
+ WHEN next_assistant_at IS NULL THEN max_window_end
199
+ WHEN next_assistant_at < max_window_end THEN next_assistant_at
200
+ ELSE max_window_end
201
+ END AS window_end
202
+ FROM event_windows
203
+ )
204
+ SELECT
205
+ b.model_id AS model_id,
206
+ COUNT(*) AS events,
207
+ SUM(CASE WHEN EXISTS (
208
+ SELECT 1 FROM tool_calls t
209
+ WHERE t.session_id = b.session_id
210
+ AND t.created_at > b.created_at
211
+ AND t.created_at <= b.window_end
212
+ AND t.outcome = 'success'
213
+ ) THEN 1 ELSE 0 END) AS success_windows,
214
+ SUM(CASE WHEN EXISTS (
215
+ SELECT 1 FROM tool_calls t
216
+ WHERE t.session_id = b.session_id
217
+ AND t.created_at > b.created_at
218
+ AND t.created_at <= b.window_end
219
+ AND t.outcome = 'failure'
220
+ ) THEN 1 ELSE 0 END) AS failure_windows,
221
+ SUM(CASE WHEN EXISTS (
222
+ SELECT 1 FROM pain_events p
223
+ WHERE p.session_id = b.session_id
224
+ AND p.created_at > b.created_at
225
+ AND p.created_at <= b.window_end
226
+ ) THEN 1 ELSE 0 END) AS pain_windows,
227
+ SUM(CASE WHEN EXISTS (
228
+ SELECT 1 FROM user_turns u
229
+ WHERE u.session_id = b.session_id
230
+ AND u.created_at > b.created_at
231
+ AND u.created_at <= b.window_end
232
+ AND u.correction_detected = 1
233
+ ) THEN 1 ELSE 0 END) AS correction_windows,
234
+ SUM(CASE WHEN EXISTS (
235
+ SELECT 1 FROM correction_samples c
236
+ WHERE c.session_id = b.session_id
237
+ AND c.created_at > b.created_at
238
+ AND c.created_at <= b.window_end
239
+ ) THEN 1 ELSE 0 END) AS correction_sample_windows
240
+ FROM bounded_windows b
241
+ GROUP BY b.model_id
242
+ ORDER BY events DESC, model_id ASC;
243
+
244
+ DROP VIEW IF EXISTS v_thinking_model_scenarios;
245
+ CREATE VIEW v_thinking_model_scenarios AS
246
+ SELECT
247
+ e.model_id AS model_id,
248
+ CAST(j.value AS TEXT) AS scenario,
249
+ COUNT(*) AS hits
250
+ FROM thinking_model_events e
251
+ JOIN json_each(
252
+ CASE
253
+ WHEN json_valid(e.scenario_json) THEN e.scenario_json
254
+ ELSE '[]'
255
+ END
256
+ ) AS j
257
+ GROUP BY e.model_id, CAST(j.value AS TEXT)
258
+ ORDER BY hits DESC, scenario ASC;
259
+
260
+ DROP VIEW IF EXISTS v_thinking_model_daily_trend;
261
+ CREATE VIEW v_thinking_model_daily_trend AS
262
+ SELECT
263
+ substr(created_at, 1, 10) AS day,
264
+ model_id,
265
+ COUNT(*) AS hits
266
+ FROM thinking_model_events
267
+ GROUP BY substr(created_at, 1, 10), model_id
268
+ ORDER BY day ASC, model_id ASC;
269
+ `);
270
+ }
271
+ withWrite(fn) {
272
+ return withLock(this.dbPath, fn, { lockSuffix: '.trajectory.lock', lockStaleMs: 30000 });
273
+ }
274
+ }
@@ -1,4 +1,4 @@
1
- import type { PainDictionary } from './dictionary.js';
1
+ import { type PainDictionary } from './dictionary.js';
2
2
  export interface DetectionResult {
3
3
  detected: boolean;
4
4
  severity?: number;
@@ -1,4 +1,5 @@
1
1
  import { createHash } from 'crypto';
2
+ import { shouldIgnorePainProtocolText } from './dictionary.js';
2
3
  /**
3
4
  * A simple LRU Cache implementation using Map.
4
5
  */
@@ -46,6 +47,9 @@ export class DetectionFunnel {
46
47
  * Detects pain in the given text using L1 (Exact), L2 (Cache), and L3 (Async).
47
48
  */
48
49
  detect(text) {
50
+ if (shouldIgnorePainProtocolText(text)) {
51
+ return { detected: false, source: 'l1_exact' };
52
+ }
49
53
  // --- Layer 1: Exact Match (Sync) ---
50
54
  const exactMatch = this.dictionary.match(text);
51
55
  if (exactMatch) {
@@ -10,6 +10,8 @@ export interface PainRule {
10
10
  export interface PainDictionaryData {
11
11
  rules: Record<string, PainRule>;
12
12
  }
13
+ export declare const PAIN_PROTOCOL_TOKENS: readonly ["[EVOLUTION_ACK]", "HEARTBEAT_OK", "HEARTBEAT_CHECK"];
14
+ export declare function shouldIgnorePainProtocolText(text: string): boolean;
13
15
  export declare class PainDictionary {
14
16
  private stateDir;
15
17
  private data;
@@ -1,5 +1,16 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
+ export const PAIN_PROTOCOL_TOKENS = [
4
+ '[EVOLUTION_ACK]',
5
+ 'HEARTBEAT_OK',
6
+ 'HEARTBEAT_CHECK',
7
+ ];
8
+ export function shouldIgnorePainProtocolText(text) {
9
+ const normalized = text.trim();
10
+ if (!normalized)
11
+ return false;
12
+ return PAIN_PROTOCOL_TOKENS.some((token) => normalized === token || normalized.startsWith(`${token} `) || normalized.includes(token));
13
+ }
3
14
  const DEFAULT_RULES = {
4
15
  'P_CONFUSION_ZH': {
5
16
  type: 'regex',
@@ -84,6 +95,8 @@ export class PainDictionary {
84
95
  }
85
96
  }
86
97
  match(text) {
98
+ if (shouldIgnorePainProtocolText(text))
99
+ return undefined;
87
100
  let bestMatch = undefined;
88
101
  for (const [id, rule] of Object.entries(this.data.rules)) {
89
102
  if (rule.status !== 'active')
@@ -1,4 +1,4 @@
1
- import type { DailyStats, ToolCallEventData, PainSignalEventData, RuleMatchEventData, RulePromotionEventData, HookExecutionEventData, GateBlockEventData, PlanApprovalEventData, EvolutionTaskEventData, DeepReflectionEventData, TrustChangeEventData } from '../types/event-types.js';
1
+ import type { DailyStats, EmpathyEventStats, ToolCallEventData, PainSignalEventData, RuleMatchEventData, RulePromotionEventData, HookExecutionEventData, GateBlockEventData, GateBypassEventData, PlanApprovalEventData, EvolutionTaskEventData, DeepReflectionEventData, TrustChangeEventData, EmpathyRollbackEventData } from '../types/event-types.js';
2
2
  import type { PluginLogger } from '../openclaw-sdk.js';
3
3
  /**
4
4
  * EventLog - Structured event logging with daily statistics aggregation.
@@ -19,10 +19,12 @@ export declare class EventLog {
19
19
  recordRulePromotion(data: RulePromotionEventData): void;
20
20
  recordHookExecution(data: HookExecutionEventData): void;
21
21
  recordGateBlock(sessionId: string | undefined, data: GateBlockEventData): void;
22
+ recordGateBypass(sessionId: string | undefined, data: GateBypassEventData): void;
22
23
  recordPlanApproval(sessionId: string | undefined, data: PlanApprovalEventData): void;
23
24
  recordEvolutionTask(data: EvolutionTaskEventData): void;
24
25
  recordDeepReflection(sessionId: string | undefined, data: DeepReflectionEventData): void;
25
26
  recordTrustChange(sessionId: string | undefined, data: TrustChangeEventData): void;
27
+ recordEmpathyRollback(sessionId: string | undefined, data: EmpathyRollbackEventData): void;
26
28
  recordError(sessionId: string | undefined, message: string, context?: Record<string, unknown>): void;
27
29
  recordWarn(sessionId: string | undefined, message: string, context?: Record<string, unknown>): void;
28
30
  private record;
@@ -38,6 +40,25 @@ export declare class EventLog {
38
40
  * Returns empty stats if no events recorded for that date.
39
41
  */
40
42
  getDailyStats(date: string): DailyStats;
43
+ /**
44
+ * Get aggregated empathy statistics for multiple time ranges.
45
+ * @param range 'today' | 'week' | 'session'
46
+ * @param sessionId Optional session ID for session-scoped stats
47
+ */
48
+ getEmpathyStats(range: 'today' | 'week' | 'session', sessionId?: string): EmpathyEventStats;
49
+ /**
50
+ * Aggregate empathy stats for a specific session.
51
+ */
52
+ private aggregateSessionEmpathy;
53
+ /**
54
+ * Rollback an empathy event by ID.
55
+ * Returns the rolled back score, or 0 if event not found.
56
+ */
57
+ rollbackEmpathyEvent(eventId: string, sessionId: string | undefined, reason: string, triggeredBy: 'user_command' | 'natural_language' | 'system'): number;
58
+ /**
59
+ * Get the last empathy event ID for a session (for rollback).
60
+ */
61
+ getLastEmpathyEventId(sessionId: string): string | null;
41
62
  /**
42
63
  * Dispose of the EventLog, flushing pending data and clearing timer.
43
64
  */