principles-disciple 1.6.0 โ†’ 1.7.1

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 (75) hide show
  1. package/dist/commands/context.js +7 -3
  2. package/dist/commands/evolution-status.d.ts +4 -0
  3. package/dist/commands/evolution-status.js +134 -0
  4. package/dist/commands/export.d.ts +2 -0
  5. package/dist/commands/export.js +45 -0
  6. package/dist/commands/focus.js +9 -6
  7. package/dist/commands/pain.js +8 -0
  8. package/dist/commands/principle-rollback.d.ts +4 -0
  9. package/dist/commands/principle-rollback.js +22 -0
  10. package/dist/commands/rollback.js +9 -3
  11. package/dist/commands/samples.d.ts +2 -0
  12. package/dist/commands/samples.js +55 -0
  13. package/dist/commands/trust.js +64 -81
  14. package/dist/core/config.d.ts +5 -0
  15. package/dist/core/control-ui-db.d.ts +68 -0
  16. package/dist/core/control-ui-db.js +274 -0
  17. package/dist/core/detection-funnel.d.ts +1 -1
  18. package/dist/core/detection-funnel.js +4 -0
  19. package/dist/core/dictionary.d.ts +2 -0
  20. package/dist/core/dictionary.js +13 -0
  21. package/dist/core/event-log.d.ts +7 -1
  22. package/dist/core/event-log.js +10 -0
  23. package/dist/core/evolution-engine.d.ts +5 -5
  24. package/dist/core/evolution-engine.js +18 -18
  25. package/dist/core/evolution-migration.d.ts +5 -0
  26. package/dist/core/evolution-migration.js +65 -0
  27. package/dist/core/evolution-reducer.d.ts +69 -0
  28. package/dist/core/evolution-reducer.js +369 -0
  29. package/dist/core/evolution-types.d.ts +103 -0
  30. package/dist/core/path-resolver.js +75 -36
  31. package/dist/core/paths.d.ts +7 -8
  32. package/dist/core/paths.js +48 -40
  33. package/dist/core/profile.js +1 -1
  34. package/dist/core/session-tracker.d.ts +14 -2
  35. package/dist/core/session-tracker.js +75 -9
  36. package/dist/core/thinking-models.d.ts +38 -0
  37. package/dist/core/thinking-models.js +170 -0
  38. package/dist/core/trajectory.d.ts +184 -0
  39. package/dist/core/trajectory.js +817 -0
  40. package/dist/core/trust-engine.d.ts +6 -0
  41. package/dist/core/trust-engine.js +50 -29
  42. package/dist/core/workspace-context.d.ts +13 -0
  43. package/dist/core/workspace-context.js +50 -7
  44. package/dist/hooks/gate.js +171 -87
  45. package/dist/hooks/llm.js +119 -71
  46. package/dist/hooks/pain.js +105 -5
  47. package/dist/hooks/prompt.d.ts +11 -14
  48. package/dist/hooks/prompt.js +283 -57
  49. package/dist/hooks/subagent.js +69 -28
  50. package/dist/hooks/trajectory-collector.d.ts +32 -0
  51. package/dist/hooks/trajectory-collector.js +256 -0
  52. package/dist/http/principles-console-route.d.ts +2 -0
  53. package/dist/http/principles-console-route.js +257 -0
  54. package/dist/i18n/commands.js +16 -0
  55. package/dist/index.js +105 -4
  56. package/dist/service/control-ui-query-service.d.ts +217 -0
  57. package/dist/service/control-ui-query-service.js +537 -0
  58. package/dist/service/empathy-observer-manager.d.ts +2 -0
  59. package/dist/service/empathy-observer-manager.js +43 -1
  60. package/dist/service/evolution-worker.d.ts +27 -0
  61. package/dist/service/evolution-worker.js +256 -41
  62. package/dist/service/runtime-summary-service.d.ts +79 -0
  63. package/dist/service/runtime-summary-service.js +319 -0
  64. package/dist/service/trajectory-service.d.ts +2 -0
  65. package/dist/service/trajectory-service.js +15 -0
  66. package/dist/tools/agent-spawn.d.ts +27 -6
  67. package/dist/tools/agent-spawn.js +339 -87
  68. package/dist/tools/deep-reflect.d.ts +27 -7
  69. package/dist/tools/deep-reflect.js +210 -121
  70. package/dist/types/event-types.d.ts +10 -2
  71. package/dist/types.d.ts +10 -0
  72. package/dist/types.js +5 -0
  73. package/openclaw.plugin.json +43 -11
  74. package/package.json +14 -4
  75. package/templates/langs/zh/skills/pd-daily/SKILL.md +97 -13
@@ -1,5 +1,6 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
+ import { defaultContextConfig } from '../types.js';
3
4
  import { loadContextInjectionConfig } from '../hooks/prompt.js';
4
5
  /**
5
6
  * Get workspace directory from context
@@ -185,7 +186,8 @@ function applyPreset(workspaceDir, preset, isZh) {
185
186
  thinkingOs: false,
186
187
  trustScore: true,
187
188
  reflectionLog: false,
188
- projectFocus: 'off'
189
+ projectFocus: 'off',
190
+ evolutionContext: { ...defaultContextConfig.evolutionContext }
189
191
  };
190
192
  break;
191
193
  case 'standard':
@@ -193,7 +195,8 @@ function applyPreset(workspaceDir, preset, isZh) {
193
195
  thinkingOs: true,
194
196
  trustScore: true,
195
197
  reflectionLog: false,
196
- projectFocus: 'off'
198
+ projectFocus: 'off',
199
+ evolutionContext: { ...defaultContextConfig.evolutionContext }
197
200
  };
198
201
  break;
199
202
  case 'full':
@@ -201,7 +204,8 @@ function applyPreset(workspaceDir, preset, isZh) {
201
204
  thinkingOs: true,
202
205
  trustScore: true,
203
206
  reflectionLog: true,
204
- projectFocus: 'summary'
207
+ projectFocus: 'summary',
208
+ evolutionContext: { ...defaultContextConfig.evolutionContext }
205
209
  };
206
210
  break;
207
211
  }
@@ -0,0 +1,4 @@
1
+ import type { PluginCommandContext } from '../openclaw-sdk.js';
2
+ export declare function handleEvolutionStatusCommand(ctx: PluginCommandContext): {
3
+ text: string;
4
+ };
@@ -0,0 +1,134 @@
1
+ import { EvolutionReducerImpl } from '../core/evolution-reducer.js';
2
+ import { normalizeLanguage } from '../i18n/commands.js';
3
+ import { RuntimeSummaryService } from '../service/runtime-summary-service.js';
4
+ function formatAge(ageSeconds, lang) {
5
+ if (ageSeconds === null) {
6
+ return '--';
7
+ }
8
+ if (ageSeconds < 60) {
9
+ return lang === 'zh' ? `${ageSeconds} \u79d2` : `${ageSeconds}s`;
10
+ }
11
+ const minutes = Math.floor(ageSeconds / 60);
12
+ if (minutes < 60) {
13
+ return lang === 'zh' ? `${minutes} \u5206\u949f` : `${minutes}m`;
14
+ }
15
+ const hours = Math.floor(minutes / 60);
16
+ if (hours < 24) {
17
+ return lang === 'zh' ? `${hours} \u5c0f\u65f6` : `${hours}h`;
18
+ }
19
+ const days = Math.floor(hours / 24);
20
+ return lang === 'zh' ? `${days} \u5929` : `${days}d`;
21
+ }
22
+ function formatNumber(value) {
23
+ if (value === null || Number.isNaN(value)) {
24
+ return '--';
25
+ }
26
+ return Number.isInteger(value) ? String(value) : value.toFixed(1);
27
+ }
28
+ function formatStage(value) {
29
+ return value === null ? '--' : String(value);
30
+ }
31
+ function formatSources(sources) {
32
+ if (sources.length === 0) {
33
+ return '--';
34
+ }
35
+ return sources
36
+ .map((source) => source.score === undefined
37
+ ? source.source
38
+ : `${source.source}(${formatNumber(source.score)})`)
39
+ .join(', ');
40
+ }
41
+ function buildEnglishOutput(workspaceDir, sessionId, warnings, stats, summary) {
42
+ const lines = [
43
+ 'Evolution Status',
44
+ '================',
45
+ '',
46
+ 'Control Plane',
47
+ `- Legacy Trust: ${formatNumber(summary.legacyTrust.score)}/100 (stage ${formatStage(summary.legacyTrust.stage)}, legacy/frozen, ${summary.legacyTrust.rewardPolicy})`,
48
+ `- Session GFI: current ${formatNumber(summary.gfi.current)}, peak ${formatNumber(summary.gfi.peak)} (${summary.gfi.dataQuality})`,
49
+ `- GFI Sources: ${formatSources(summary.gfi.sources)}`,
50
+ `- Pain Flag: ${summary.pain.activeFlag ? 'active' : 'inactive'}${summary.pain.activeFlagSource ? ` (${summary.pain.activeFlagSource})` : ''}`,
51
+ `- Last Pain Signal: ${summary.pain.lastSignal ? `${summary.pain.lastSignal.source}${summary.pain.lastSignal.reason ? ` - ${summary.pain.lastSignal.reason}` : ''}` : '--'}`,
52
+ `- Gate Events: blocks ${formatNumber(summary.gate.recentBlocks)}, bypasses ${formatNumber(summary.gate.recentBypasses)} (${summary.gate.dataQuality})`,
53
+ '',
54
+ 'Evolution',
55
+ `- Queue: pending ${summary.evolution.queue.pending}, in_progress ${summary.evolution.queue.inProgress}, completed ${summary.evolution.queue.completed} (${summary.evolution.dataQuality})`,
56
+ `- Directive: ${summary.evolution.directive.exists ? 'present' : 'missing'}, active ${summary.evolution.directive.active === null ? '--' : summary.evolution.directive.active ? 'yes' : 'no'}, age ${formatAge(summary.evolution.directive.ageSeconds, 'en')}`,
57
+ `- Directive Task: ${summary.evolution.directive.taskPreview ?? '--'}`,
58
+ '',
59
+ 'Principles',
60
+ `- candidate principles: ${stats.candidateCount}`,
61
+ `- probation principles: ${stats.probationCount}`,
62
+ `- active principles: ${stats.activeCount}`,
63
+ `- deprecated principles: ${stats.deprecatedCount}`,
64
+ `- last promoted: ${stats.lastPromotedAt ?? 'none'}`,
65
+ '',
66
+ 'Metadata',
67
+ `- workspace: ${workspaceDir}`,
68
+ `- session: ${sessionId ?? '--'} (${summary.metadata.selectedSessionReason})`,
69
+ `- generatedAt: ${summary.metadata.generatedAt}`,
70
+ ];
71
+ if (warnings.length > 0) {
72
+ lines.push('', 'Warnings');
73
+ for (const warning of warnings) {
74
+ lines.push(`- ${warning}`);
75
+ }
76
+ }
77
+ return lines.join('\n');
78
+ }
79
+ function buildChineseOutput(workspaceDir, sessionId, warnings, stats, summary) {
80
+ const lines = [
81
+ '\u8fdb\u5316\u72b6\u6001',
82
+ '================',
83
+ '',
84
+ '\u63a7\u5236\u9762',
85
+ `- Legacy Trust: ${formatNumber(summary.legacyTrust.score)}/100\uff08\u9636\u6bb5 ${formatStage(summary.legacyTrust.stage)}\uff0clegacy/frozen\uff0c${summary.legacyTrust.rewardPolicy}\uff09`,
86
+ `- \u4f1a\u8bdd GFI: \u5f53\u524d ${formatNumber(summary.gfi.current)}\uff0c\u5cf0\u503c ${formatNumber(summary.gfi.peak)}\uff08${summary.gfi.dataQuality}\uff09`,
87
+ `- GFI \u6765\u6e90: ${formatSources(summary.gfi.sources)}`,
88
+ `- Pain Flag: ${summary.pain.activeFlag ? 'active' : 'inactive'}${summary.pain.activeFlagSource ? `\uff08${summary.pain.activeFlagSource}\uff09` : ''}`,
89
+ `- \u6700\u8fd1 Pain \u4fe1\u53f7: ${summary.pain.lastSignal ? `${summary.pain.lastSignal.source}${summary.pain.lastSignal.reason ? ` - ${summary.pain.lastSignal.reason}` : ''}` : '--'}`,
90
+ `- Gate \u4e8b\u4ef6: block ${formatNumber(summary.gate.recentBlocks)}\uff0cbypass ${formatNumber(summary.gate.recentBypasses)}\uff08${summary.gate.dataQuality}\uff09`,
91
+ '',
92
+ '\u8fdb\u5316',
93
+ `- \u961f\u5217: pending ${summary.evolution.queue.pending}\uff0cin_progress ${summary.evolution.queue.inProgress}\uff0ccompleted ${summary.evolution.queue.completed}\uff08${summary.evolution.dataQuality}\uff09`,
94
+ `- Directive: ${summary.evolution.directive.exists ? 'present' : 'missing'}\uff0cactive ${summary.evolution.directive.active === null ? '--' : summary.evolution.directive.active ? 'yes' : 'no'}\uff0cage ${formatAge(summary.evolution.directive.ageSeconds, 'zh')}`,
95
+ `- Directive \u4efb\u52a1: ${summary.evolution.directive.taskPreview ?? '--'}`,
96
+ '',
97
+ '\u539f\u5219\u7edf\u8ba1',
98
+ `- \u5019\u9009\u539f\u5219: ${stats.candidateCount}`,
99
+ `- \u89c2\u5bdf\u671f\u539f\u5219: ${stats.probationCount}`,
100
+ `- \u751f\u6548\u539f\u5219: ${stats.activeCount}`,
101
+ `- \u5df2\u5e9f\u5f03\u539f\u5219: ${stats.deprecatedCount}`,
102
+ `- \u6700\u8fd1\u664b\u5347: ${stats.lastPromotedAt ?? '\u65e0'}`,
103
+ '',
104
+ '\u5143\u6570\u636e',
105
+ `- \u5de5\u4f5c\u533a: ${workspaceDir}`,
106
+ `- Session: ${sessionId ?? '--'}\uff08${summary.metadata.selectedSessionReason}\uff09`,
107
+ `- \u751f\u6210\u65f6\u95f4: ${summary.metadata.generatedAt}`,
108
+ ];
109
+ if (warnings.length > 0) {
110
+ lines.push('', '่ญฆๅ‘Š');
111
+ for (const warning of warnings) {
112
+ lines.push(`- ${warning}`);
113
+ }
114
+ }
115
+ return lines.join('\n');
116
+ }
117
+ export function handleEvolutionStatusCommand(ctx) {
118
+ const workspaceDir = ctx.config?.workspaceDir || process.cwd();
119
+ const sessionId = ctx.sessionId ?? null;
120
+ const reducer = new EvolutionReducerImpl({ workspaceDir });
121
+ const stats = reducer.getStats();
122
+ const summary = RuntimeSummaryService.getSummary(workspaceDir, { sessionId });
123
+ const rawLang = ctx.config?.language || 'en';
124
+ const lang = normalizeLanguage(rawLang);
125
+ const warnings = summary.metadata.warnings.slice(0, 12);
126
+ if (lang === 'zh') {
127
+ return {
128
+ text: buildChineseOutput(workspaceDir, summary.metadata.sessionId, warnings, stats, summary),
129
+ };
130
+ }
131
+ return {
132
+ text: buildEnglishOutput(workspaceDir, summary.metadata.sessionId, warnings, stats, summary),
133
+ };
134
+ }
@@ -0,0 +1,2 @@
1
+ import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
2
+ export declare function handleExportCommand(ctx: PluginCommandContext): PluginCommandResult;
@@ -0,0 +1,45 @@
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 handleExportCommand(ctx) {
6
+ const workspaceDir = ctx.config?.workspaceDir || process.cwd();
7
+ const zh = isZh(ctx);
8
+ const args = (ctx.args || '').trim();
9
+ const [subcommand = 'corrections'] = args.split(/\s+/).filter(Boolean);
10
+ const wctx = WorkspaceContext.fromHookContext({ workspaceDir, ...ctx.config });
11
+ if (subcommand !== 'analytics' && subcommand !== 'corrections') {
12
+ return {
13
+ text: zh
14
+ ? 'ๆ— ๆ•ˆ็š„ๅฏผๅ‡บ็ฑปๅž‹ใ€‚่ฏทไฝฟ็”จ `analytics` ๆˆ– `corrections [--redacted]`ใ€‚'
15
+ : 'Invalid export target. Use `analytics` or `corrections [--redacted]`.',
16
+ };
17
+ }
18
+ try {
19
+ if (subcommand === 'analytics') {
20
+ const result = wctx.trajectory.exportAnalytics();
21
+ return {
22
+ text: zh
23
+ ? `ๅทฒๅฏผๅ‡บ analytics ๅฟซ็…งๅˆฐ ${result.filePath}๏ผŒๅ…ฑ ${result.count} ๆก่šๅˆ่ฎฐๅฝ•ใ€‚`
24
+ : `Exported analytics snapshot to ${result.filePath} (${result.count} aggregated rows).`,
25
+ };
26
+ }
27
+ const redacted = args.includes('--redacted');
28
+ const result = wctx.trajectory.exportCorrections({
29
+ mode: redacted ? 'redacted' : 'raw',
30
+ approvedOnly: true,
31
+ });
32
+ return {
33
+ text: zh
34
+ ? `ๅทฒๅฏผๅ‡บ็บ ้”™ๆ ทๆœฌๅˆฐ ${result.filePath}๏ผŒๆจกๅผ ${result.mode}๏ผŒๅ…ฑ ${result.count} ๆกใ€‚`
35
+ : `Exported correction samples to ${result.filePath} (mode=${result.mode}, count=${result.count}).`,
36
+ };
37
+ }
38
+ catch {
39
+ return {
40
+ text: zh
41
+ ? 'ๅฏผๅ‡บๅคฑ่ดฅ๏ผŒ่ฏทๆฃ€ๆŸฅๆ—ฅๅฟ—ใ€‚'
42
+ : 'Export failed. Check logs.',
43
+ };
44
+ }
45
+ }
@@ -9,9 +9,10 @@
9
9
  */
10
10
  import * as fs from 'fs';
11
11
  import * as path from 'path';
12
+ import { randomUUID } from 'node:crypto';
12
13
  import { WorkspaceContext } from '../core/workspace-context.js';
13
14
  import { getHistoryDir, backupToHistory, cleanupHistory, extractVersion, extractDate, } from '../core/focus-history.js';
14
- import { agentSpawnTool } from '../tools/agent-spawn.js';
15
+ import { createAgentSpawnTool } from '../tools/agent-spawn.js';
15
16
  /**
16
17
  * ๆธ…็† Markdown ไปฃ็ ๅ—ๅ›ดๆ 
17
18
  * ็งป้™คๅผ€ๅคด็š„ ```lang ๅ’Œ็ป“ๅฐพ็š„ ```
@@ -329,14 +330,16 @@ If no milestones to record, leave ===MEMORY=== section empty.`;
329
330
  let memoryUpdated = false;
330
331
  try {
331
332
  // ่ฐƒ็”จๅญๆ™บ่ƒฝไฝ“่ฟ›่กŒๅŽ‹็ผฉ
332
- const result = await agentSpawnTool.execute({
333
+ const tool = createAgentSpawnTool(api);
334
+ const result = await tool.execute(`focus-compress-${randomUUID()}`, {
333
335
  agentType: 'reporter', // ไฝฟ็”จ reporter ็ฑปๅž‹๏ผŒ้€‚ๅˆๆ€ป็ป“ๅ’ŒๅŽ‹็ผฉ
334
336
  task: compressPrompt,
335
- }, api);
337
+ });
336
338
  // ่งฃๆž่พ“ๅ‡บ๏ผŒๆๅ– MEMORY ๅ’Œ COMPRESSED ้ƒจๅˆ†
337
- if (result && result.trim()) {
338
- const memoryMatch = result.match(/===MEMORY===([\s\S]*?)===COMPRESSED===/);
339
- const compressedMatch = result.match(/===COMPRESSED===([\s\S]*?)$/);
339
+ const resultText = result?.content?.[0]?.text || '';
340
+ if (resultText.trim()) {
341
+ const memoryMatch = resultText.match(/===MEMORY===([\s\S]*?)===COMPRESSED===/);
342
+ const compressedMatch = resultText.match(/===COMPRESSED===([\s\S]*?)$/);
340
343
  if (compressedMatch && compressedMatch[1].trim()) {
341
344
  // ๆธ…็† Markdown ไปฃ็ ๅ—ๅ›ดๆ 
342
345
  compressedContent = stripMarkdownFence(compressedMatch[1]);
@@ -93,6 +93,14 @@ export function handlePainCommand(ctx) {
93
93
  const newScore = wctx.trust.getScore();
94
94
  return { text: isZh ? `โœ… ๆ™บ่ƒฝไฝ“ไฟกไปปๅˆ†ๅทฒ้‡็ฝฎไธบๅˆๅง‹ๅ€ผ (${newScore})ใ€‚` : `โœ… Agent trust score has been reset to initial value (${newScore}).` };
95
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
+ }
96
104
  // Default: Show status
97
105
  const session = sessionId ? getSession(sessionId) : undefined;
98
106
  const gfi = session ? session.currentGfi : 0;
@@ -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
+ }
@@ -63,7 +63,10 @@ Usage:
63
63
  };
64
64
  }
65
65
  // Reduce the GFI by the rolled back score
66
- resetFriction(sessionId);
66
+ resetFriction(sessionId, workspaceDir, {
67
+ source: 'user_empathy',
68
+ amount: rolledBackScore,
69
+ });
67
70
  return {
68
71
  text: isZh
69
72
  ? `โœ… ๅทฒๅ›žๆปšๆƒ…็ปชไบ‹ไปถ
@@ -107,8 +110,11 @@ export function handleNaturalLanguageRollback(wctx, sessionId, reason) {
107
110
  message: isZh ? 'ๅ›žๆปšๅคฑ่ดฅ๏ผŒไบ‹ไปถๅฏ่ƒฝไธๅญ˜ๅœจ' : 'Rollback failed, event may not exist'
108
111
  };
109
112
  }
110
- // Reduce the GFI
111
- resetFriction(sessionId);
113
+ // Reduce only the empathy slice instead of wiping the whole session GFI.
114
+ resetFriction(sessionId, wctx.workspaceDir, {
115
+ source: 'user_empathy',
116
+ amount: rolledBackScore,
117
+ });
112
118
  return {
113
119
  success: true,
114
120
  score: rolledBackScore,
@@ -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
+ }
@@ -1,11 +1,15 @@
1
1
  import { WorkspaceContext } from '../core/workspace-context.js';
2
- /**
3
- * Creates a visual progress bar (e.g., [โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–‘โ–‘โ–‘โ–‘])
4
- */
5
- function createProgressBar(value, max, length = 10) {
6
- const filledLength = Math.round((value / max) * length);
7
- const emptyLength = length - filledLength;
8
- return `[${'โ–ˆ'.repeat(filledLength)}${'โ–‘'.repeat(emptyLength)}]`;
2
+ function createProgressBar(value, max, length = 12) {
3
+ const safeValue = Math.max(0, Math.min(max, value));
4
+ const filledLength = Math.round((safeValue / max) * length);
5
+ const emptyLength = Math.max(0, length - filledLength);
6
+ return `[${'#'.repeat(filledLength)}${'-'.repeat(emptyLength)}]`;
7
+ }
8
+ function formatStageTitle(stage, isZh) {
9
+ const titles = isZh
10
+ ? ['Observer', 'Editor', 'Developer', 'Architect']
11
+ : ['Observer', 'Editor', 'Developer', 'Architect'];
12
+ return titles[Math.max(0, Math.min(3, stage - 1))] ?? 'Unknown';
9
13
  }
10
14
  export function handleTrustCommand(ctx) {
11
15
  const { workspaceDir } = ctx;
@@ -14,82 +18,61 @@ export function handleTrustCommand(ctx) {
14
18
  const wctx = WorkspaceContext.fromHookContext(ctx);
15
19
  const trustEngine = wctx.trust;
16
20
  const trustScore = trustEngine.getScore();
17
- const trustSettings = wctx.config.get('trust');
21
+ const stage = trustEngine.getStage();
18
22
  const isZh = wctx.config.get('language') === 'zh';
19
- let stage = 2;
20
- let title = isZh ? '็ผ–่พ‘่€…' : 'Editor';
21
- let permissions = isZh
22
- ? `- ๅ…่ฎธๅฐ่Œƒๅ›ดไฟฎๆ”น (< ${trustSettings.limits.stage_2_max_lines} ่กŒ)\n- ็ฆๆญขไฟฎๆ”น้ฃŽ้™ฉ็›ฎๅฝ•`
23
- : `- Small modifications (< ${trustSettings.limits.stage_2_max_lines} lines)\n- Non-risk paths only`;
24
- let nextLevel = `Trust Score >= ${trustSettings.stages.stage_3_developer}`;
25
- if (trustScore < trustSettings.stages.stage_1_observer) {
26
- stage = 1;
27
- title = isZh ? '่ง‚ๅฏŸ่€… (ๅช่ฏปๆจกๅผ)' : 'Observer (Read-only)';
28
- permissions = isZh ? '- ไป…้™ๅช่ฏป่ฎฟ้—ฎ\n- ไป…้™่ฏŠๆ–ญๅทฅๅ…ท' : '- Read-only access\n- Diagnosis tools only';
29
- nextLevel = `Trust Score >= ${trustSettings.stages.stage_1_observer}`;
30
- }
31
- else if (trustScore < trustSettings.stages.stage_2_editor) {
32
- // Default stage 2
33
- nextLevel = `Trust Score >= ${trustSettings.stages.stage_2_editor}`;
34
- }
35
- else if (trustScore < trustSettings.stages.stage_3_developer) {
36
- stage = 3;
37
- title = isZh ? 'ๅผ€ๅ‘่€…' : 'Developer';
38
- permissions = isZh
39
- ? `- ๅ…่ฎธไธญ็ญ‰่Œƒๅ›ดไฟฎๆ”น (< ${trustSettings.limits.stage_3_max_lines} ่กŒ)\n- ้ฃŽ้™ฉ็›ฎๅฝ•ไฟฎๆ”น้œ€่ฆๆœ‰ PLAN (่ฎกๅˆ’)`
40
- : `- Medium modifications (< ${trustSettings.limits.stage_3_max_lines} lines)\n- Risk paths require READY plan`;
41
- nextLevel = `Trust Score >= ${trustSettings.stages.stage_3_developer}`;
42
- }
43
- else {
44
- stage = 4;
45
- title = isZh ? 'ๆžถๆž„ๅธˆ' : 'Architect';
46
- permissions = isZh ? '- ๐Ÿšจ **ๆ— ้™ๅˆถ่ฎฟ้—ฎ**\n- ่ฎกๅˆ’ๅ’Œๅฎก่ฎก้žๅผบๅˆถ่ฆๆฑ‚' : '- ๐Ÿšจ **UNRESTRICTED access**\n- Plan and Audit are optional';
47
- nextLevel = isZh ? 'ๅทฒ่พพๆœ€้ซ˜็บง' : 'MAX LEVEL REACHED';
48
- }
49
- const progressBar = createProgressBar(trustScore, 100, 15);
23
+ const scorecard = trustEngine.getScorecard();
50
24
  const hygiene = wctx.hygiene.getStats();
51
- const persistenceScore = Math.min(100, hygiene.persistenceCount * 10); // 10 points per persistence
52
- const hygieneBar = createProgressBar(persistenceScore, 100, 15);
25
+ const trustBar = createProgressBar(trustScore, 100);
26
+ const hygieneScore = Math.min(100, hygiene.persistenceCount * 10);
27
+ const hygieneBar = createProgressBar(hygieneScore, 100);
28
+ const rewardPolicy = scorecard.reward_policy ?? 'frozen_all_positive';
29
+ const lastUpdated = scorecard.last_updated ?? '--';
53
30
  if (isZh) {
54
- return `
55
- ๐Ÿ“Š **Principles Disciple - ็ณป็ปŸ็Šถๆ€็œ‹ๆฟ**
56
- โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
57
- ๐Ÿ›ก๏ธ **ๅฎ‰ๅ…จ้˜ถ็บง**: Stage ${stage} (${title})
58
- ๐Ÿ’ฐ **ไฟกไปป็งฏๅˆ†**: ${progressBar} ${trustScore}/100
59
- ๐Ÿง  **่ฎค็Ÿฅๅซ็”Ÿ**: ${hygieneBar} ${hygiene.persistenceCount} ๆฌก่ฝ็›˜ (ไปŠๆ—ฅ)
60
-
61
- **ๅฝ“ๅ‰ๆ“ไฝœๆƒ้™**:
62
- ${permissions}
63
-
64
- **ไปŠๆ—ฅ็Ÿฅ่ฏ†ๆฒ‰ๆท€**:
65
- - ็‰ฉ็†่ฝ็›˜: ${hygiene.persistenceCount} ๆฌก
66
- - ็ดฏ่ฎกๅญ—็ฌฆ: ${hygiene.totalCharsPersisted} chars
67
- - ็ฉบ้—ดๆ•ด็†: ${hygiene.groomingExecutedCount > 0 ? '๐ŸŸข ่‰ฏๅฅฝ' : '๐ŸŸก ๅพ…ๆ•ด็†'}
68
-
69
- **ไธ‹ไธ€ๆฌกๆ™‹ๅ‡ๆกไปถ**: ${nextLevel}
70
- โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
71
- *๐Ÿ’ก ๆ็คบ๏ผšๅ‹ค่ฎฐ็ฌ”่ฎฐ (PLAN.md/memory) ๆœ‰ๅŠฉไบŽๅฏนๆŠ—ไธŠไธ‹ๆ–‡้—ๅฟ˜๏ผŒๆๅ‡็ณป็ปŸ็จณๅฎšๆ€งใ€‚*
72
- `.trim();
73
- }
74
- else {
75
- return `
76
- ๐Ÿ“Š **Principles Disciple - System Dashboard**
77
- โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
78
- ๐Ÿ›ก๏ธ **Security Stage**: Stage ${stage} (${title})
79
- ๐Ÿ’ฐ **Trust Score**: ${progressBar} ${trustScore}/100
80
- ๐Ÿง  **Cognitive Hygiene**: ${hygieneBar} ${hygiene.persistenceCount} actions (Today)
81
-
82
- **Current Permissions**:
83
- ${permissions}
84
-
85
- **Knowledge Assets Today**:
86
- - Physical Persists: ${hygiene.persistenceCount} actions
87
- - Total Chars: ${hygiene.totalCharsPersisted} chars
88
- - Workspace Grooming: ${hygiene.groomingExecutedCount > 0 ? '๐ŸŸข Good' : '๐ŸŸก Pending'}
89
-
90
- **Next Promotion Requirement**: ${nextLevel}
91
- โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
92
- *๐Ÿ’ก Hint: Frequent notes (PLAN.md/memory) prevent amnesia and ensure task continuity.*
93
- `.trim();
31
+ return [
32
+ 'Principles Disciple - Legacy Trust',
33
+ '=================================',
34
+ '',
35
+ `- Legacy Trust: ${trustBar} ${trustScore}/100`,
36
+ `- Stage: ${stage} (${formatStageTitle(stage, true)})`,
37
+ `- Status: legacy/frozen`,
38
+ `- Reward Policy: ${rewardPolicy}`,
39
+ `- Last Updated: ${lastUpdated}`,
40
+ '',
41
+ '่ฏดๆ˜Ž',
42
+ '- ่ฟ™ๆ˜ฏๅ…ผๅฎนๆ—งๆŽงๅˆถ้ข็š„ legacy trust ่ง†ๅ›พ๏ผŒไธๅ†ไปฃ่กจๆ–ฐ็š„้•ฟๆœŸ่ƒฝๅŠ›ๆจกๅž‹ใ€‚',
43
+ '- `tool_success` ไธŽ `subagent_success` ไธๅ†่‡ชๅŠจๆๅ‡ trustใ€‚',
44
+ '- ๅฝ“ๅ‰่ง‚ๅฏŸ็ช—ๅฃๅ†…๏ผŒtrust ไธป่ฆไฟ็•™ไธบๅ…ผๅฎน่ฏปๆ•ฐไธŽ gate ๅ…ผๅฎน่พ“ๅ…ฅใ€‚',
45
+ '',
46
+ '่ฎค็Ÿฅๅซ็”Ÿ',
47
+ `- Persistence: ${hygieneBar} ${hygiene.persistenceCount} actions today`,
48
+ `- Total Chars Persisted: ${hygiene.totalCharsPersisted}`,
49
+ `- Workspace Grooming: ${hygiene.groomingExecutedCount > 0 ? 'done' : 'pending'}`,
50
+ '',
51
+ 'ไธ‹ไธ€ๆญฅ',
52
+ '- ๅœจ่ฟ›ๅ…ฅ Phase 3 capability shadow ไน‹ๅ‰๏ผŒๅ…ˆ็œ‹ production observation ๆ•ฐๆฎๆ˜ฏๅฆ็จณๅฎšใ€‚',
53
+ ].join('\n');
94
54
  }
55
+ return [
56
+ 'Principles Disciple - Legacy Trust',
57
+ '=================================',
58
+ '',
59
+ `- Legacy Trust: ${trustBar} ${trustScore}/100`,
60
+ `- Stage: ${stage} (${formatStageTitle(stage, false)})`,
61
+ '- Status: legacy/frozen',
62
+ `- Reward Policy: ${rewardPolicy}`,
63
+ `- Last Updated: ${lastUpdated}`,
64
+ '',
65
+ 'Notes',
66
+ '- This is a compatibility view of legacy trust, not the future long-term capability model.',
67
+ '- `tool_success` and `subagent_success` no longer raise trust automatically.',
68
+ '- During the current observation window, trust remains mainly for compatibility reads and gate compatibility.',
69
+ '',
70
+ 'Cognitive Hygiene',
71
+ `- Persistence: ${hygieneBar} ${hygiene.persistenceCount} actions today`,
72
+ `- Total Chars Persisted: ${hygiene.totalCharsPersisted}`,
73
+ `- Workspace Grooming: ${hygiene.groomingExecutedCount > 0 ? 'done' : 'pending'}`,
74
+ '',
75
+ 'Next',
76
+ '- Do not move into Phase 3 capability shadow until production observation data is stable.',
77
+ ].join('\n');
95
78
  }
@@ -65,6 +65,11 @@ export interface TrustSettings {
65
65
  }
66
66
  export interface PainSettings {
67
67
  language: 'en' | 'zh';
68
+ trajectory?: {
69
+ blob_inline_threshold_bytes?: number;
70
+ busy_timeout_ms?: number;
71
+ orphan_blob_grace_days?: number;
72
+ };
68
73
  thresholds: {
69
74
  pain_trigger: number;
70
75
  cognitive_paralysis_input: number;
@@ -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
+ }