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
@@ -8,6 +8,65 @@ function createProgressBar(value, max, length = 10) {
8
8
  const emptyLength = length - filledLength;
9
9
  return `[${'█'.repeat(filledLength)}${'░'.repeat(emptyLength)}]`;
10
10
  }
11
+ /**
12
+ * Creates a mini bar for daily trends
13
+ */
14
+ function createMiniBar(count, max, length = 6) {
15
+ const filledLength = Math.round((count / max) * length);
16
+ return '█'.repeat(filledLength) + '░'.repeat(length - filledLength);
17
+ }
18
+ /**
19
+ * Format empathy stats for display
20
+ */
21
+ function formatEmpathyCard(stats, range, isZh) {
22
+ if (stats.totalEvents === 0 && stats.dedupedCount === 0) {
23
+ return isZh
24
+ ? `🫀 **情绪事件统计** (${range})\n 暂无数据`
25
+ : `🫀 **Empathy Events** (${range})\n No data available`;
26
+ }
27
+ const lines = [];
28
+ lines.push(isZh ? `🫀 **情绪事件统计** (${range})` : `🫀 **Empathy Events** (${range})`);
29
+ lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
30
+ // Total events
31
+ const totalLabel = isZh ? '📊 总事件' : '📊 Total Events';
32
+ lines.push(`${totalLabel}: ${stats.totalEvents} 次`);
33
+ lines.push(` ├─ 😟 mild: ${stats.bySeverity.mild} 次 (${stats.scoreBySeverity.mild}分)`);
34
+ lines.push(` ├─ 😠 moderate: ${stats.bySeverity.moderate} 次 (${stats.scoreBySeverity.moderate}分)`);
35
+ lines.push(` └─ 😡 severe: ${stats.bySeverity.severe} 次 (${stats.scoreBySeverity.severe}分)`);
36
+ lines.push('');
37
+ // Dedupe hit rate
38
+ const dedupeRate = (stats.dedupeHitRate * 100).toFixed(0);
39
+ const dedupeLabel = isZh ? '🔄 去重命中率' : '🔄 Dedupe Hit Rate';
40
+ lines.push(`${dedupeLabel}: ${dedupeRate}% (${stats.dedupedCount}/${stats.totalEvents + stats.dedupedCount})`);
41
+ if (stats.dedupedCount > 0) {
42
+ lines.push(isZh ? ' ↳ 避免了重复惩罚' : ' ↳ Prevented duplicate penalties');
43
+ }
44
+ lines.push('');
45
+ // Rollback info
46
+ if (stats.rollbackCount > 0) {
47
+ const rollbackLabel = isZh ? '↩️ 已回滚' : '↩️ Rolled Back';
48
+ lines.push(`${rollbackLabel}: ${stats.rollbackCount} 次 (${stats.rolledBackScore}分)`);
49
+ lines.push('');
50
+ }
51
+ // Daily trend
52
+ if (stats.dailyTrend.length > 1) {
53
+ const trendLabel = isZh ? '📈 趋势 (按天)' : '📈 Trend (by day)';
54
+ lines.push(`${trendLabel}:`);
55
+ const maxCount = Math.max(...stats.dailyTrend.map(d => d.count), 1);
56
+ for (const day of stats.dailyTrend) {
57
+ const bar = createMiniBar(day.count, maxCount, 6);
58
+ const dateStr = day.date.slice(5); // MM-DD
59
+ lines.push(` ${dateStr}: ${bar} ${day.count}次`);
60
+ }
61
+ }
62
+ // Detection mode distribution
63
+ if (stats.byDetectionMode.structured > 0 || stats.byDetectionMode.legacy_tag > 0) {
64
+ lines.push('');
65
+ const modeLabel = isZh ? '🔍 检测模式' : '🔍 Detection Mode';
66
+ lines.push(`${modeLabel}: 结构化 ${stats.byDetectionMode.structured} | 标签 ${stats.byDetectionMode.legacy_tag}`);
67
+ }
68
+ return lines.join('\n');
69
+ }
11
70
  /**
12
71
  * Handles the /pd-status command
13
72
  */
@@ -18,6 +77,10 @@ export function handlePainCommand(ctx) {
18
77
  const isZh = lang === 'zh';
19
78
  const sessionId = ctx.sessionId;
20
79
  const args = (ctx.args || '').trim();
80
+ // Handle empathy subcommand
81
+ if (args.startsWith('empathy')) {
82
+ return handleEmpathySubcommand(wctx, args, sessionId, isZh);
83
+ }
21
84
  if (args === 'reset') {
22
85
  if (sessionId) {
23
86
  resetFriction(sessionId);
@@ -30,6 +93,14 @@ export function handlePainCommand(ctx) {
30
93
  const newScore = wctx.trust.getScore();
31
94
  return { text: isZh ? `✅ 智能体信任分已重置为初始值 (${newScore})。` : `✅ Agent trust score has been reset to initial value (${newScore}).` };
32
95
  }
96
+ if (args === 'data') {
97
+ const stats = wctx.trajectory.getDataStats();
98
+ return {
99
+ text: isZh
100
+ ? `轨迹数据状态\n- 数据库: ${stats.dbPath}\n- 助手轮次: ${stats.assistantTurns}\n- 用户轮次: ${stats.userTurns}\n- 工具调用: ${stats.toolCalls}\n- 痛感事件: ${stats.painEvents}\n- 待审核样本: ${stats.pendingSamples}\n- 已通过样本: ${stats.approvedSamples}\n- Blob 字节数: ${stats.blobBytes}\n- 最近写入: ${stats.lastIngestAt ?? 'none'}`
101
+ : `Trajectory Data Status\n- DB: ${stats.dbPath}\n- assistant turns: ${stats.assistantTurns}\n- user turns: ${stats.userTurns}\n- tool calls: ${stats.toolCalls}\n- pain events: ${stats.painEvents}\n- pending samples: ${stats.pendingSamples}\n- approved samples: ${stats.approvedSamples}\n- blob bytes: ${stats.blobBytes}\n- last ingest: ${stats.lastIngestAt ?? 'none'}`
102
+ };
103
+ }
33
104
  // Default: Show status
34
105
  const session = sessionId ? getSession(sessionId) : undefined;
35
106
  const gfi = session ? session.currentGfi : 0;
@@ -40,6 +111,24 @@ export function handlePainCommand(ctx) {
40
111
  const trustStage = trust.getStage();
41
112
  const gfiBar = createProgressBar(gfi, 100, 15);
42
113
  const trustBar = createProgressBar(trustScore, 100, 15);
114
+ // Determine Mental Mode (aligned with prompt.ts logic)
115
+ let mentalMode = '';
116
+ if (isZh) {
117
+ if (gfi >= 70)
118
+ mentalMode = '🚑 救赎模式 (HUMBLE_RECOVERY)';
119
+ else if (gfi >= 40)
120
+ mentalMode = '🤝 安抚模式 (CONCILIATORY)';
121
+ else
122
+ mentalMode = '⚡ 高效模式 (EFFICIENT)';
123
+ }
124
+ else {
125
+ if (gfi >= 70)
126
+ mentalMode = '🚑 HUMBLE_RECOVERY';
127
+ else if (gfi >= 40)
128
+ mentalMode = '🤝 CONCILIATORY';
129
+ else
130
+ mentalMode = '⚡ EFFICIENT';
131
+ }
43
132
  // Determine health status based on GFI
44
133
  let healthLabel = 'Healthy';
45
134
  let suggestionText = '';
@@ -77,20 +166,30 @@ export function handlePainCommand(ctx) {
77
166
  else
78
167
  healthLabel = 'Healthy 🟢';
79
168
  }
169
+ // Get session empathy stats for inline display
170
+ const sessionEmpathy = sessionId ? wctx.eventLog.getEmpathyStats('session', sessionId) : null;
171
+ let empathyInline = '';
172
+ if (sessionEmpathy && sessionEmpathy.totalEvents > 0) {
173
+ empathyInline = isZh
174
+ ? `\n🫀 **情绪事件 (当前会话)**: ${sessionEmpathy.totalEvents} 次 (${sessionEmpathy.totalPenaltyScore}分)`
175
+ : `\n🫀 **Empathy Events (Session)**: ${sessionEmpathy.totalEvents} (${sessionEmpathy.totalPenaltyScore}pts)`;
176
+ }
80
177
  if (isZh) {
81
178
  let text = `📊 **Principles Disciple - 系统健康度监控**\n`;
82
179
  text += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
83
180
  text += `💊 **当前疲劳指数 (GFI)**: ${gfiBar} ${gfi}/100\n`;
84
181
  text += `💰 **当前信任积分 (Trust)**: ${trustBar} ${trustScore}/100 (Stage ${trustStage})\n`;
85
- text += ` ↳ 状态诊断: ${healthLabel}\n\n`;
86
- text += `🧠 **痛苦进化词典**: 已吸收 ${stats.totalRules} 条规则\n`;
182
+ text += `🧠 **当前心智模式**: ${mentalMode}\n`;
183
+ text += ` ↳ 状态诊断: ${healthLabel}\n`;
184
+ text += empathyInline;
185
+ text += `\n\n🧠 **痛苦进化词典**: 已吸收 ${stats.totalRules} 条规则\n`;
87
186
  text += ` ↳ 累计帮您拦截了 ${stats.totalHits} 次无效操作\n`;
88
187
  text += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
89
188
  if (suggestionText) {
90
189
  text += suggestionText;
91
190
  }
92
191
  else {
93
- text += `*💡 提示: 如果 AI 陷入死循环,您可以使用 \`/pd-status reset\` 来强制清零疲劳值。*`;
192
+ text += `*💡 提示: 使用 \`/pd-status empathy\` 查看详细情绪事件统计。*`;
94
193
  }
95
194
  return { text };
96
195
  }
@@ -99,16 +198,54 @@ export function handlePainCommand(ctx) {
99
198
  text += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
100
199
  text += `💊 **Current Friction (GFI)**: ${gfiBar} ${gfi}/100\n`;
101
200
  text += `💰 **Current Trust Score**: ${trustBar} ${trustScore}/100 (Stage ${trustStage})\n`;
102
- text += ` ↳ Diagnosis: ${healthLabel}\n\n`;
103
- text += `🧠 **Evolution Dictionary**: ${stats.totalRules} active rules\n`;
201
+ text += `🧠 **Current Mental Mode**: ${mentalMode}\n`;
202
+ text += ` ↳ Diagnosis: ${healthLabel}\n`;
203
+ text += empathyInline;
204
+ text += `\n\n🧠 **Evolution Dictionary**: ${stats.totalRules} active rules\n`;
104
205
  text += ` ↳ Successfully blocked ${stats.totalHits} invalid operations\n`;
105
206
  text += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
106
207
  if (suggestionText) {
107
208
  text += suggestionText;
108
209
  }
109
210
  else {
110
- text += `*💡 Hint: If the AI is stuck in a loop, use \`/pd-status reset\` to clear friction and restart.*`;
211
+ text += `*💡 Hint: Use \`/pd-status empathy\` to view detailed empathy event statistics.*`;
111
212
  }
112
213
  return { text };
113
214
  }
114
215
  }
216
+ /**
217
+ * Handle /pd-status empathy subcommand
218
+ */
219
+ function handleEmpathySubcommand(wctx, args, sessionId, isZh) {
220
+ // Parse range argument
221
+ let range = 'today';
222
+ if (args.includes('--week') || args.includes('-w')) {
223
+ range = 'week';
224
+ }
225
+ else if (args.includes('--session') || args.includes('-s')) {
226
+ range = 'session';
227
+ }
228
+ else if (args.includes('--today') || args.includes('-t')) {
229
+ range = 'today';
230
+ }
231
+ // Validate session range
232
+ if (range === 'session' && !sessionId) {
233
+ return {
234
+ text: isZh
235
+ ? `❌ 无法获取会话统计,请在聊天会话中使用此命令。`
236
+ : `❌ Session not found. Use this command in a chat session.`
237
+ };
238
+ }
239
+ const stats = wctx.eventLog.getEmpathyStats(range, sessionId);
240
+ const rangeLabel = isZh
241
+ ? { today: '今天', week: '最近 7 天', session: '当前会话' }[range]
242
+ : { today: 'Today', week: 'Last 7 Days', session: 'Current Session' }[range];
243
+ let text = formatEmpathyCard(stats, rangeLabel, isZh);
244
+ // Add usage hint
245
+ if (range === 'today') {
246
+ text += isZh
247
+ ? `\n\n*💡 使用 \`/pd-status empathy --week\` 查看周统计,\`--session\` 查看会话统计。*`
248
+ : `\n\n*💡 Use \`/pd-status empathy --week\` for weekly stats, \`--session\` for session stats.*`;
249
+ }
250
+ return { text };
251
+ }
@@ -0,0 +1,4 @@
1
+ import type { PluginCommandContext } from '../openclaw-sdk.js';
2
+ export declare function handlePrincipleRollbackCommand(ctx: PluginCommandContext): {
3
+ text: string;
4
+ };
@@ -0,0 +1,22 @@
1
+ import { EvolutionReducerImpl } from '../core/evolution-reducer.js';
2
+ export function handlePrincipleRollbackCommand(ctx) {
3
+ const workspaceDir = ctx.config?.workspaceDir || process.cwd();
4
+ const argText = (ctx.args || '').trim();
5
+ const [principleId = '', ...reasonParts] = argText.split(/\s+/);
6
+ const reason = (reasonParts.join(' ') || 'manual rollback').trim();
7
+ const isZh = ctx.config?.language?.startsWith('zh');
8
+ if (!principleId) {
9
+ return { text: isZh ? '用法: /pd-principle-rollback <principleId> [reason]' : 'Usage: /pd-principle-rollback <principleId> [reason]' };
10
+ }
11
+ const reducer = new EvolutionReducerImpl({ workspaceDir });
12
+ const principle = reducer.getPrincipleById(principleId);
13
+ if (!principle) {
14
+ return { text: isZh ? `未找到原则: ${principleId}` : `Principle not found: ${principleId}` };
15
+ }
16
+ reducer.rollbackPrinciple(principleId, reason);
17
+ return {
18
+ text: isZh
19
+ ? `✅ 已回滚原则 ${principleId}\n原因: ${reason}`
20
+ : `✅ Rolled back principle ${principleId}\nReason: ${reason}`,
21
+ };
22
+ }
@@ -0,0 +1,19 @@
1
+ import { WorkspaceContext } from '../core/workspace-context.js';
2
+ import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
3
+ /**
4
+ * Handles the /pd-rollback command
5
+ *
6
+ * Usage:
7
+ * /pd-rollback <event-id> - Rollback a specific empathy event by ID
8
+ * /pd-rollback last - Rollback the last empathy event in current session
9
+ */
10
+ export declare function handleRollbackCommand(ctx: PluginCommandContext): PluginCommandResult;
11
+ /**
12
+ * Handle natural language rollback request
13
+ * Called from prompt hook when user says things like "撤销刚才的惩罚"
14
+ */
15
+ export declare function handleNaturalLanguageRollback(wctx: WorkspaceContext, sessionId: string | undefined, reason: string): {
16
+ success: boolean;
17
+ score: number;
18
+ message: string;
19
+ };
@@ -0,0 +1,119 @@
1
+ import { WorkspaceContext } from '../core/workspace-context.js';
2
+ import { resetFriction } from '../core/session-tracker.js';
3
+ /**
4
+ * Handles the /pd-rollback command
5
+ *
6
+ * Usage:
7
+ * /pd-rollback <event-id> - Rollback a specific empathy event by ID
8
+ * /pd-rollback last - Rollback the last empathy event in current session
9
+ */
10
+ export function handleRollbackCommand(ctx) {
11
+ const workspaceDir = ctx.config?.workspaceDir || process.cwd();
12
+ const wctx = WorkspaceContext.fromHookContext({ workspaceDir, ...ctx.config });
13
+ const lang = ctx.config?.language || 'en';
14
+ const isZh = lang === 'zh';
15
+ const sessionId = ctx.sessionId;
16
+ const args = (ctx.args || '').trim();
17
+ if (!args) {
18
+ return {
19
+ text: isZh
20
+ ? `❌ 请指定要回滚的事件 ID 或使用 "last" 回滚最近一次事件。
21
+
22
+ 用法:
23
+ /pd-rollback <event-id> - 回滚指定事件
24
+ /pd-rollback last - 回滚当前会话最近一次事件`
25
+ : `❌ Please specify an event ID or use "last" to rollback the most recent event.
26
+
27
+ Usage:
28
+ /pd-rollback <event-id> - Rollback a specific event
29
+ /pd-rollback last - Rollback the last event in current session`
30
+ };
31
+ }
32
+ if (!sessionId) {
33
+ return {
34
+ text: isZh
35
+ ? `❌ 无法识别当前会话,请在聊天会话中使用此命令。`
36
+ : `❌ Session ID not found. Use this command in a chat session.`
37
+ };
38
+ }
39
+ let eventId = null;
40
+ let triggerMethod = 'user_command';
41
+ if (args === 'last') {
42
+ // Find the last empathy event in current session
43
+ eventId = wctx.eventLog.getLastEmpathyEventId(sessionId);
44
+ if (!eventId) {
45
+ return {
46
+ text: isZh
47
+ ? `❌ 当前会话没有可回滚的情绪事件。`
48
+ : `❌ No empathy events found in current session to rollback.`
49
+ };
50
+ }
51
+ }
52
+ else {
53
+ // Use the provided event ID
54
+ eventId = args;
55
+ }
56
+ // Perform rollback
57
+ const rolledBackScore = wctx.eventLog.rollbackEmpathyEvent(eventId, sessionId, isZh ? '用户手动回滚' : 'User manual rollback', 'user_command');
58
+ if (rolledBackScore === 0) {
59
+ return {
60
+ text: isZh
61
+ ? `❌ 无法回滚事件 "${eventId}"。可能事件不存在或已被去重。`
62
+ : `❌ Failed to rollback event "${eventId}". Event may not exist or was deduped.`
63
+ };
64
+ }
65
+ // Reduce the GFI by the rolled back score
66
+ resetFriction(sessionId);
67
+ return {
68
+ text: isZh
69
+ ? `✅ 已回滚情绪事件
70
+ 📋 事件 ID: ${eventId}
71
+ 💰 撤销分数: ${rolledBackScore} 分
72
+
73
+ 💡 提示: 已重置当前会话的疲劳指数。`
74
+ : `✅ Empathy event rolled back successfully
75
+ 📋 Event ID: ${eventId}
76
+ 💰 Score recovered: ${rolledBackScore} pts
77
+
78
+ 💡 Hint: Session friction has been reset.`
79
+ };
80
+ }
81
+ /**
82
+ * Handle natural language rollback request
83
+ * Called from prompt hook when user says things like "撤销刚才的惩罚"
84
+ */
85
+ export function handleNaturalLanguageRollback(wctx, sessionId, reason) {
86
+ const isZh = wctx.config.get('language') === 'zh';
87
+ if (!sessionId) {
88
+ return {
89
+ success: false,
90
+ score: 0,
91
+ message: isZh ? '无法识别当前会话' : 'Session not found'
92
+ };
93
+ }
94
+ const eventId = wctx.eventLog.getLastEmpathyEventId(sessionId);
95
+ if (!eventId) {
96
+ return {
97
+ success: false,
98
+ score: 0,
99
+ message: isZh ? '当前会话没有可回滚的情绪事件' : 'No empathy events to rollback'
100
+ };
101
+ }
102
+ const rolledBackScore = wctx.eventLog.rollbackEmpathyEvent(eventId, sessionId, reason, 'natural_language');
103
+ if (rolledBackScore === 0) {
104
+ return {
105
+ success: false,
106
+ score: 0,
107
+ message: isZh ? '回滚失败,事件可能不存在' : 'Rollback failed, event may not exist'
108
+ };
109
+ }
110
+ // Reduce the GFI
111
+ resetFriction(sessionId);
112
+ return {
113
+ success: true,
114
+ score: rolledBackScore,
115
+ message: isZh
116
+ ? `已回滚 ${rolledBackScore} 分`
117
+ : `Rolled back ${rolledBackScore} pts`
118
+ };
119
+ }
@@ -0,0 +1,2 @@
1
+ import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
2
+ export declare function handleSamplesCommand(ctx: PluginCommandContext): PluginCommandResult;
@@ -0,0 +1,55 @@
1
+ import { WorkspaceContext } from '../core/workspace-context.js';
2
+ function isZh(ctx) {
3
+ return String(ctx.config?.language || 'en').startsWith('zh');
4
+ }
5
+ export function handleSamplesCommand(ctx) {
6
+ const workspaceDir = ctx.config?.workspaceDir || process.cwd();
7
+ const zh = isZh(ctx);
8
+ const args = (ctx.args || '').trim();
9
+ const wctx = WorkspaceContext.fromHookContext({ workspaceDir, ...ctx.config });
10
+ if (args.startsWith('review ')) {
11
+ const [, decision = '', sampleId = '', ...noteParts] = args.split(/\s+/);
12
+ if (decision !== 'approve' && decision !== 'reject') {
13
+ return {
14
+ text: zh
15
+ ? '无效的审核动作。请使用 `review approve <sample-id> [note]` 或 `review reject <sample-id> [note]`。'
16
+ : 'Invalid review action. Use `review approve <sample-id> [note]` or `review reject <sample-id> [note]`.',
17
+ };
18
+ }
19
+ if (!sampleId) {
20
+ return {
21
+ text: zh
22
+ ? '缺少 sample-id。'
23
+ : 'Missing sample-id.',
24
+ };
25
+ }
26
+ const normalizedDecision = decision === 'approve' ? 'approved' : 'rejected';
27
+ const note = noteParts.join(' ').trim();
28
+ let record;
29
+ try {
30
+ record = wctx.trajectory.reviewCorrectionSample(sampleId, normalizedDecision, note);
31
+ }
32
+ catch (error) {
33
+ return {
34
+ text: zh
35
+ ? `审核样本失败:${sampleId}`
36
+ : `Failed to review sample: ${sampleId}`,
37
+ };
38
+ }
39
+ return {
40
+ text: zh
41
+ ? `样本 ${record.sampleId} 已标记为 ${record.reviewStatus}。`
42
+ : `Sample ${record.sampleId} marked as ${record.reviewStatus}.`,
43
+ };
44
+ }
45
+ const samples = wctx.trajectory.listCorrectionSamples('pending');
46
+ if (samples.length === 0) {
47
+ return { text: zh ? '当前没有待审核纠错样本。' : 'No pending correction samples.' };
48
+ }
49
+ const lines = samples.map((sample) => `- ${sample.sampleId} | session=${sample.sessionId} | score=${sample.qualityScore}`);
50
+ return {
51
+ text: zh
52
+ ? `待审核纠错样本:\n${lines.join('\n')}`
53
+ : `Pending correction samples:\n${lines.join('\n')}`,
54
+ };
55
+ }
@@ -13,6 +13,23 @@ export interface DeepReflectionSettings {
13
13
  timeout_ms?: number;
14
14
  modelsDir?: string;
15
15
  }
16
+ export interface GfiGateSettings {
17
+ enabled: boolean;
18
+ thresholds: {
19
+ low_risk_block: number;
20
+ high_risk_block: number;
21
+ large_change_block: number;
22
+ };
23
+ large_change_lines: number;
24
+ trust_stage_multipliers: {
25
+ '1': number;
26
+ '2': number;
27
+ '3': number;
28
+ '4': number;
29
+ };
30
+ bash_safe_patterns: string[];
31
+ bash_dangerous_patterns: string[];
32
+ }
16
33
  export interface TrustSettings {
17
34
  stages: {
18
35
  stage_1_observer: number;
@@ -48,6 +65,11 @@ export interface TrustSettings {
48
65
  }
49
66
  export interface PainSettings {
50
67
  language: 'en' | 'zh';
68
+ trajectory?: {
69
+ blob_inline_threshold_bytes?: number;
70
+ busy_timeout_ms?: number;
71
+ orphan_blob_grace_days?: number;
72
+ };
51
73
  thresholds: {
52
74
  pain_trigger: number;
53
75
  cognitive_paralysis_input: number;
@@ -79,6 +101,21 @@ export interface PainSettings {
79
101
  };
80
102
  trust: TrustSettings;
81
103
  deep_reflection?: DeepReflectionSettings;
104
+ empathy_engine?: {
105
+ enabled?: boolean;
106
+ dedupe_window_ms?: number;
107
+ penalties?: {
108
+ mild?: number;
109
+ moderate?: number;
110
+ severe?: number;
111
+ };
112
+ rate_limit?: {
113
+ max_per_turn?: number;
114
+ max_per_hour?: number;
115
+ };
116
+ model_calibration?: Record<string, number>;
117
+ };
118
+ gfi_gate?: GfiGateSettings;
82
119
  }
83
120
  export declare const DEFAULT_SETTINGS: PainSettings;
84
121
  export declare class PainConfig {
@@ -89,6 +89,53 @@ export const DEFAULT_SETTINGS = {
89
89
  default_model: 'T-01',
90
90
  default_depth: 2,
91
91
  timeout_ms: 60000
92
+ },
93
+ empathy_engine: {
94
+ enabled: true,
95
+ dedupe_window_ms: 60000,
96
+ penalties: {
97
+ mild: 10,
98
+ moderate: 25,
99
+ severe: 40,
100
+ },
101
+ rate_limit: {
102
+ max_per_turn: 40,
103
+ max_per_hour: 120,
104
+ },
105
+ model_calibration: {}
106
+ },
107
+ gfi_gate: {
108
+ enabled: true,
109
+ thresholds: {
110
+ low_risk_block: 70, // TIER 1: write, edit, spawn_agent
111
+ high_risk_block: 40, // TIER 2: delete_file, move_file
112
+ large_change_block: 50, // 大规模修改警告阈值
113
+ },
114
+ large_change_lines: 50,
115
+ trust_stage_multipliers: {
116
+ '1': 0.5, // Observer: 更严格,阈值减半
117
+ '2': 0.75, // Editor: 阈值降低 25%
118
+ '3': 1.0, // Developer: 标准阈值
119
+ '4': 1.5, // Architect: 更宽松,阈值提高 50%
120
+ },
121
+ bash_safe_patterns: [
122
+ '^(ls|dir|pwd|which|where|echo|env|cat|type|head|tail|less|more)\\b',
123
+ '^git\\s+(status|log|diff|branch|show|remote)\\b',
124
+ '^npm\\s+(run|test|build|start)\\b',
125
+ '^make\\s*$', // 只允许纯 make 命令
126
+ '^make\\s+(-j\\d+|--jobs\\s*\\d+)$', // 允许 make -j4 等
127
+ '^(gradle|mvn)\\s+(clean|build|test|compile)\\b', // 只允许安全参数
128
+ ],
129
+ bash_dangerous_patterns: [
130
+ 'rm\\s+(-[a-z]*r[a-z]*f|-rf)',
131
+ 'del\\s+/s',
132
+ 'rmdir\\s+/s',
133
+ 'git\\s+(push\\s+.*--force|reset\\s+--hard|clean\\s+-fd)',
134
+ 'npm\\s+publish',
135
+ 'pip\\s+upload',
136
+ 'docker\\s+push',
137
+ '(curl|wget).*\\|\\s*(ba)?sh',
138
+ ],
92
139
  }
93
140
  };
94
141
  export class PainConfig {
@@ -0,0 +1,68 @@
1
+ export interface ThinkingModelEventInput {
2
+ sessionId: string;
3
+ runId: string;
4
+ assistantTurnId: number;
5
+ modelId: string;
6
+ matchedPattern: string;
7
+ scenarioJson: unknown;
8
+ toolContextJson: unknown;
9
+ painContextJson: unknown;
10
+ principleContextJson: unknown;
11
+ triggerExcerpt: string;
12
+ createdAt: string;
13
+ }
14
+ export interface ControlUiDatabaseOptions {
15
+ workspaceDir: string;
16
+ busyTimeoutMs?: number;
17
+ }
18
+ export interface RecentThinkingContext {
19
+ toolCalls: Array<{
20
+ id: number;
21
+ toolName: string;
22
+ outcome: 'success' | 'failure' | 'blocked';
23
+ errorType: string | null;
24
+ errorMessage: string | null;
25
+ createdAt: string;
26
+ }>;
27
+ painEvents: Array<{
28
+ id: number;
29
+ source: string;
30
+ score: number;
31
+ reason: string | null;
32
+ createdAt: string;
33
+ }>;
34
+ gateBlocks: Array<{
35
+ id: number;
36
+ toolName: string;
37
+ reason: string;
38
+ filePath: string | null;
39
+ createdAt: string;
40
+ }>;
41
+ userCorrections: Array<{
42
+ id: number;
43
+ correctionCue: string | null;
44
+ rawExcerpt: string | null;
45
+ createdAt: string;
46
+ }>;
47
+ principleEvents: Array<{
48
+ id: number;
49
+ principleId: string | null;
50
+ eventType: string;
51
+ createdAt: string;
52
+ }>;
53
+ }
54
+ export declare class ControlUiDatabase {
55
+ private readonly workspaceDir;
56
+ private readonly dbPath;
57
+ private readonly blobDir;
58
+ private readonly db;
59
+ constructor(opts: ControlUiDatabaseOptions);
60
+ dispose(): void;
61
+ recordThinkingModelEvent(input: ThinkingModelEventInput): number;
62
+ getRecentThinkingContext(sessionId: string, beforeCreatedAt: string, limit?: number): RecentThinkingContext;
63
+ all<T>(sql: string, ...params: unknown[]): T[];
64
+ get<T>(sql: string, ...params: unknown[]): T | undefined;
65
+ restoreRawText(inlineText?: string | null, blobRef?: string | null): string;
66
+ private initSchema;
67
+ private withWrite;
68
+ }