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,67 +1,75 @@
1
1
  import * as path from 'path';
2
+ const posixJoin = (...parts) => path.posix.join(...parts);
3
+ function isWindowsPath(input) {
4
+ return /^[A-Za-z]:[\\/]/.test(input) || input.startsWith('\\\\');
5
+ }
6
+ function joinWorkspacePath(workspaceDir, relativePath) {
7
+ if (isWindowsPath(workspaceDir)) {
8
+ return path.win32.join(workspaceDir, relativePath);
9
+ }
10
+ if (workspaceDir.startsWith('/')) {
11
+ return path.posix.join(workspaceDir, relativePath);
12
+ }
13
+ return path.join(workspaceDir, relativePath);
14
+ }
2
15
  /**
3
16
  * Principles Disciple Directory Constants
4
17
  * Establishing a logical separation between Identity, State, and Memory.
5
18
  */
6
19
  export const PD_DIRS = {
7
- /** 🧬 Core configuration, identity, and kernel rules (hidden) */
8
20
  IDENTITY: '.principles',
9
- /** 🧠 Deep Reflection mental models */
10
- MODELS: path.join('.principles', 'models'),
11
- /** ⚡ Volatile operational data, queues, and task status (hidden) */
21
+ MODELS: posixJoin('.principles', 'models'),
12
22
  STATE: '.state',
13
- /** 💾 Historical records, logs, and long-term memory */
14
23
  MEMORY: 'memory',
15
- /** 🎯 Strategic objectives and focus areas */
16
- OKR: path.join('memory', 'okr'),
17
- /** 📁 Internal logs directory */
18
- LOGS: path.join('memory', 'logs'),
19
- /** 🧠 Session persistence directory */
20
- SESSIONS: path.join('.state', 'sessions'),
21
- /** 🩹 Semantic pain samples for L3 retrieval */
22
- PAIN_SAMPLES: path.join('memory', 'pain'),
24
+ OKR: posixJoin('memory', 'okr'),
25
+ LOGS: posixJoin('memory', 'logs'),
26
+ SESSIONS: posixJoin('.state', 'sessions'),
27
+ PAIN_SAMPLES: posixJoin('memory', 'pain'),
28
+ LOCKS: posixJoin('memory', '.locks'),
23
29
  };
24
30
  /**
25
31
  * Standard File Path Mappings
26
32
  */
27
33
  export const PD_FILES = {
28
- // Identity Layer
29
- PROFILE: path.join(PD_DIRS.IDENTITY, 'PROFILE.json'),
30
- PRINCIPLES: path.join(PD_DIRS.IDENTITY, 'PRINCIPLES.md'),
31
- THINKING_OS: path.join(PD_DIRS.IDENTITY, 'THINKING_OS.md'),
32
- KERNEL: path.join(PD_DIRS.IDENTITY, '00-kernel.md'),
33
- DECISION_POLICY: path.join(PD_DIRS.IDENTITY, 'DECISION_POLICY.json'),
34
+ PROFILE: posixJoin(PD_DIRS.IDENTITY, 'PROFILE.json'),
35
+ PRINCIPLES: posixJoin(PD_DIRS.IDENTITY, 'PRINCIPLES.md'),
36
+ THINKING_OS: posixJoin(PD_DIRS.IDENTITY, 'THINKING_OS.md'),
37
+ KERNEL: posixJoin(PD_DIRS.IDENTITY, '00-kernel.md'),
38
+ DECISION_POLICY: posixJoin(PD_DIRS.IDENTITY, 'DECISION_POLICY.json'),
34
39
  MODELS_DIR: PD_DIRS.MODELS,
35
- // State Layer
36
40
  STATE_DIR: PD_DIRS.STATE,
37
- EVOLUTION_QUEUE: path.join(PD_DIRS.STATE, 'evolution_queue.json'),
38
- EVOLUTION_DIRECTIVE: path.join(PD_DIRS.STATE, 'evolution_directive.json'),
39
- WORKBOARD: path.join(PD_DIRS.STATE, 'WORKBOARD.json'),
40
- AGENT_SCORECARD: path.join(PD_DIRS.STATE, 'AGENT_SCORECARD.json'),
41
- PAIN_FLAG: path.join(PD_DIRS.STATE, '.pain_flag'),
42
- SYSTEM_CAPABILITIES: path.join(PD_DIRS.STATE, 'SYSTEM_CAPABILITIES.json'),
43
- PAIN_SETTINGS: path.join(PD_DIRS.STATE, 'pain_settings.json'),
44
- PAIN_CANDIDATES: path.join(PD_DIRS.STATE, 'pain_candidates.json'),
45
- THINKING_OS_USAGE: path.join(PD_DIRS.STATE, 'thinking_os_usage.json'),
41
+ EVOLUTION_QUEUE: posixJoin(PD_DIRS.STATE, 'evolution_queue.json'),
42
+ EVOLUTION_DIRECTIVE: posixJoin(PD_DIRS.STATE, 'evolution_directive.json'),
43
+ WORKBOARD: posixJoin(PD_DIRS.STATE, 'WORKBOARD.json'),
44
+ AGENT_SCORECARD: posixJoin(PD_DIRS.STATE, 'AGENT_SCORECARD.json'),
45
+ PAIN_FLAG: posixJoin(PD_DIRS.STATE, '.pain_flag'),
46
+ SYSTEM_CAPABILITIES: posixJoin(PD_DIRS.STATE, 'SYSTEM_CAPABILITIES.json'),
47
+ PAIN_SETTINGS: posixJoin(PD_DIRS.STATE, 'pain_settings.json'),
48
+ PAIN_CANDIDATES: posixJoin(PD_DIRS.STATE, 'pain_candidates.json'),
49
+ THINKING_OS_USAGE: posixJoin(PD_DIRS.STATE, 'thinking_os_usage.json'),
50
+ TRAJECTORY_DB: posixJoin(PD_DIRS.STATE, 'trajectory.db'),
51
+ TRAJECTORY_BLOBS_DIR: posixJoin(PD_DIRS.STATE, 'blobs'),
52
+ EXPORTS_DIR: posixJoin(PD_DIRS.STATE, 'exports'),
46
53
  SESSION_DIR: PD_DIRS.SESSIONS,
47
- DICTIONARY: path.join(PD_DIRS.STATE, 'pain_dictionary.json'),
48
- // Workflow Layer (Project Root)
54
+ DICTIONARY: posixJoin(PD_DIRS.STATE, 'pain_dictionary.json'),
55
+ PRINCIPLE_BLACKLIST: posixJoin(PD_DIRS.STATE, 'principle_blacklist.json'),
49
56
  PLAN: 'PLAN.md',
50
57
  MEMORY_MD: 'MEMORY.md',
51
58
  HEARTBEAT: 'HEARTBEAT.md',
52
- // Memory Layer
53
- SYSTEM_LOG: path.join(PD_DIRS.LOGS, 'SYSTEM.log'),
54
- REFLECTION_LOG: path.join(PD_DIRS.MEMORY, 'reflection-log.md'),
55
- USER_CONTEXT: path.join(PD_DIRS.MEMORY, 'USER_CONTEXT.md'),
59
+ SYSTEM_LOG: posixJoin(PD_DIRS.LOGS, 'SYSTEM.log'),
60
+ REFLECTION_LOG: posixJoin(PD_DIRS.MEMORY, 'reflection-log.md'),
61
+ USER_CONTEXT: posixJoin(PD_DIRS.MEMORY, 'USER_CONTEXT.md'),
56
62
  OKR_DIR: PD_DIRS.OKR,
57
- CURRENT_FOCUS: path.join(PD_DIRS.OKR, 'CURRENT_FOCUS.md'),
58
- WEEK_STATE: path.join(PD_DIRS.OKR, 'WEEK_STATE.json'),
59
- THINKING_OS_CANDIDATES: path.join(PD_DIRS.MEMORY, 'THINKING_OS_CANDIDATES.md'),
60
- SEMANTIC_PAIN: path.join(PD_DIRS.PAIN_SAMPLES, 'confusion_samples.md'),
63
+ CURRENT_FOCUS: posixJoin(PD_DIRS.OKR, 'CURRENT_FOCUS.md'),
64
+ WEEK_STATE: posixJoin(PD_DIRS.OKR, 'WEEK_STATE.json'),
65
+ THINKING_OS_CANDIDATES: posixJoin(PD_DIRS.MEMORY, 'THINKING_OS_CANDIDATES.md'),
66
+ SEMANTIC_PAIN: posixJoin(PD_DIRS.PAIN_SAMPLES, 'confusion_samples.md'),
67
+ EVOLUTION_STREAM: posixJoin(PD_DIRS.MEMORY, 'evolution.jsonl'),
68
+ EVOLUTION_LOCK: posixJoin(PD_DIRS.LOCKS, 'evolution'),
61
69
  };
62
70
  /**
63
71
  * Resolves a PD file path within a given workspace.
64
72
  */
65
73
  export function resolvePdPath(workspaceDir, fileKey) {
66
- return path.join(workspaceDir, PD_FILES[fileKey]);
74
+ return joinWorkspacePath(workspaceDir, PD_FILES[fileKey]);
67
75
  }
@@ -51,7 +51,7 @@ export const PROFILE_DEFAULTS = {
51
51
  thinking_checkpoint: {
52
52
  enabled: false, // Default OFF to avoid blocking new users
53
53
  window_ms: 5 * 60 * 1000, // 5 minute window
54
- high_risk_tools: ['run_shell_command', 'delete_file', 'move_file', 'pd_spawn_agent'],
54
+ high_risk_tools: ['run_shell_command', 'delete_file', 'move_file', 'pd_run_worker'],
55
55
  },
56
56
  custom_guards: [],
57
57
  };
@@ -18,6 +18,8 @@ export interface SessionState {
18
18
  cacheHits: number;
19
19
  stuckLoops: number;
20
20
  currentGfi: number;
21
+ gfiBySource?: Record<string, number>;
22
+ lastErrorSource?: string;
21
23
  lastErrorHash: string;
22
24
  consecutiveErrors: number;
23
25
  dailyToolCalls: number;
@@ -25,6 +27,7 @@ export interface SessionState {
25
27
  dailyPainSignals: number;
26
28
  dailyGfiPeak: number;
27
29
  lastThinkingTimestamp: number;
30
+ injectedProbationIds?: string[];
28
31
  }
29
32
  /**
30
33
  * Initialize persistence for session state.
@@ -40,11 +43,16 @@ export declare function trackLlmOutput(sessionId: string, usage: TokenUsage | un
40
43
  /**
41
44
  * Tracks physical friction based on tool execution failures.
42
45
  */
43
- export declare function trackFriction(sessionId: string, deltaF: number, hash: string, workspaceDir?: string): SessionState;
46
+ export declare function trackFriction(sessionId: string, deltaF: number, hash: string, workspaceDir?: string, options?: {
47
+ source?: string;
48
+ }): SessionState;
44
49
  /**
45
50
  * Resets the friction index upon successful action.
46
51
  */
47
- export declare function resetFriction(sessionId: string, workspaceDir?: string): SessionState;
52
+ export declare function resetFriction(sessionId: string, workspaceDir?: string, options?: {
53
+ source?: string;
54
+ amount?: number;
55
+ }): SessionState;
48
56
  /**
49
57
  * Records that deep thinking (Thinking OS) was performed in this session.
50
58
  * Used by the Thinking OS checkpoint to allow high-risk operations.
@@ -58,7 +66,11 @@ export declare function recordThinkingCheckpoint(sessionId: string, workspaceDir
58
66
  */
59
67
  export declare function hasRecentThinking(sessionId: string, windowMs?: number): boolean;
60
68
  export declare function trackBlock(sessionId: string): SessionState;
69
+ export declare function setInjectedProbationIds(sessionId: string, ids: string[], workspaceDir?: string): SessionState;
70
+ export declare function getInjectedProbationIds(sessionId: string, workspaceDir?: string): string[];
71
+ export declare function clearInjectedProbationIds(sessionId: string, workspaceDir?: string): SessionState;
61
72
  export declare function getSession(sessionId: string): SessionState | undefined;
73
+ export declare function listSessions(workspaceDir?: string): SessionState[];
62
74
  export declare function clearSession(sessionId: string): void;
63
75
  export declare function garbageCollectSessions(): void;
64
76
  /**
@@ -6,6 +6,11 @@ const sessions = new Map();
6
6
  let persistDir = null;
7
7
  /** Debounce timer for persistence */
8
8
  let persistTimer = null;
9
+ function logSessionTrackerWarning(message, error) {
10
+ const detail = error instanceof Error ? error.message : error ? String(error) : '';
11
+ const suffix = detail ? `: ${detail}` : '';
12
+ console.warn(`[PD:SessionTracker] ${message}${suffix}`);
13
+ }
9
14
  /**
10
15
  * Initialize persistence for session state.
11
16
  * Call this once during plugin startup.
@@ -48,13 +53,13 @@ function loadAllSessions() {
48
53
  }
49
54
  sessions.set(state.sessionId, state);
50
55
  }
51
- catch {
52
- // Ignore corrupted files
56
+ catch (error) {
57
+ logSessionTrackerWarning(`Failed to load session snapshot ${file}`, error);
53
58
  }
54
59
  }
55
60
  }
56
61
  catch (err) {
57
- // Ignore errors during load
62
+ logSessionTrackerWarning('Failed to load persisted sessions', err);
58
63
  }
59
64
  }
60
65
  /**
@@ -69,8 +74,8 @@ function persistSession(state) {
69
74
  try {
70
75
  fs.writeFileSync(sessionPath, JSON.stringify(state, null, 2), 'utf-8');
71
76
  }
72
- catch {
73
- // Ignore persistence errors
77
+ catch (error) {
78
+ logSessionTrackerWarning(`Failed to persist session ${state.sessionId}`, error);
74
79
  }
75
80
  }
76
81
  /**
@@ -108,6 +113,8 @@ function getOrCreateSession(sessionId, workspaceDir) {
108
113
  cacheHits: 0,
109
114
  stuckLoops: 0,
110
115
  currentGfi: 0,
116
+ gfiBySource: {},
117
+ lastErrorSource: '',
111
118
  lastErrorHash: '',
112
119
  consecutiveErrors: 0,
113
120
  dailyToolCalls: 0,
@@ -115,6 +122,7 @@ function getOrCreateSession(sessionId, workspaceDir) {
115
122
  dailyPainSignals: 0,
116
123
  dailyGfiPeak: 0,
117
124
  lastThinkingTimestamp: 0,
125
+ injectedProbationIds: [],
118
126
  };
119
127
  sessions.set(sessionId, state);
120
128
  }
@@ -123,6 +131,12 @@ function getOrCreateSession(sessionId, workspaceDir) {
123
131
  }
124
132
  return state;
125
133
  }
134
+ function ensureGfiLedger(state) {
135
+ if (!state.gfiBySource || typeof state.gfiBySource !== 'object') {
136
+ state.gfiBySource = {};
137
+ }
138
+ return state.gfiBySource;
139
+ }
126
140
  export function trackToolRead(sessionId, filePath, workspaceDir) {
127
141
  const state = getOrCreateSession(sessionId, workspaceDir);
128
142
  const normalizedPath = path.posix.normalize(filePath.replace(/\\/g, '/'));
@@ -164,12 +178,14 @@ export function trackLlmOutput(sessionId, usage, config, workspaceDir) {
164
178
  /**
165
179
  * Tracks physical friction based on tool execution failures.
166
180
  */
167
- export function trackFriction(sessionId, deltaF, hash, workspaceDir) {
181
+ export function trackFriction(sessionId, deltaF, hash, workspaceDir, options) {
168
182
  const state = getOrCreateSession(sessionId, workspaceDir);
183
+ const ledger = ensureGfiLedger(state);
169
184
  if (hash && hash === state.lastErrorHash) {
170
185
  state.consecutiveErrors++;
171
186
  }
172
187
  else {
188
+ state.lastErrorSource = options?.source || (hash ? `unattributed:${hash}` : 'unattributed:unknown');
173
189
  state.lastErrorHash = hash;
174
190
  state.consecutiveErrors = 1;
175
191
  }
@@ -177,6 +193,8 @@ export function trackFriction(sessionId, deltaF, hash, workspaceDir) {
177
193
  const multiplier = Math.pow(1.5, state.consecutiveErrors - 1);
178
194
  const addedFriction = deltaF * multiplier;
179
195
  state.currentGfi = (state.currentGfi || 0) + addedFriction;
196
+ const sourceKey = options?.source || (hash ? `unattributed:${hash}` : 'unattributed:unknown');
197
+ ledger[sourceKey] = (ledger[sourceKey] || 0) + addedFriction;
180
198
  state.lastActivityAt = Date.now();
181
199
  SystemLogger.log(state.workspaceDir, 'GFI_INC', `Friction added: +${addedFriction.toFixed(1)} (Base: ${deltaF}, Mult: ${multiplier.toFixed(2)}). Total GFI: ${state.currentGfi.toFixed(1)}`);
182
200
  // Update daily stats
@@ -189,12 +207,36 @@ export function trackFriction(sessionId, deltaF, hash, workspaceDir) {
189
207
  /**
190
208
  * Resets the friction index upon successful action.
191
209
  */
192
- export function resetFriction(sessionId, workspaceDir) {
210
+ export function resetFriction(sessionId, workspaceDir, options) {
193
211
  const state = getOrCreateSession(sessionId, workspaceDir);
212
+ const ledger = ensureGfiLedger(state);
213
+ if (options?.source) {
214
+ const sourceKey = options.source;
215
+ const currentSource = ledger[sourceKey] || 0;
216
+ const requestedAmount = Number.isFinite(options.amount) ? Number(options.amount) : currentSource;
217
+ const amountToRemove = Math.max(0, Math.min(currentSource, requestedAmount));
218
+ if (amountToRemove > 0) {
219
+ ledger[sourceKey] = Math.max(0, currentSource - amountToRemove);
220
+ if (ledger[sourceKey] === 0) {
221
+ delete ledger[sourceKey];
222
+ }
223
+ state.currentGfi = Math.max(0, (state.currentGfi || 0) - amountToRemove);
224
+ SystemLogger.log(state.workspaceDir, 'GFI_SLICE_RESET', `Friction slice reset for ${sourceKey}: -${amountToRemove.toFixed(1)}. Total GFI: ${state.currentGfi.toFixed(1)}`);
225
+ if (state.lastErrorSource === sourceKey) {
226
+ state.consecutiveErrors = 0;
227
+ state.lastErrorHash = '';
228
+ state.lastErrorSource = '';
229
+ }
230
+ }
231
+ schedulePersistence(state);
232
+ return state;
233
+ }
194
234
  if (state.currentGfi > 0) {
195
235
  SystemLogger.log(state.workspaceDir, 'GFI_RESET', `Friction reset to 0 (Was: ${state.currentGfi.toFixed(1)}). Action successful.`);
196
236
  }
197
237
  state.currentGfi = 0;
238
+ state.gfiBySource = {};
239
+ state.lastErrorSource = '';
198
240
  state.consecutiveErrors = 0;
199
241
  state.lastErrorHash = '';
200
242
  // Schedule persistence
@@ -230,9 +272,33 @@ export function trackBlock(sessionId) {
230
272
  state.lastActivityAt = Date.now();
231
273
  return state;
232
274
  }
275
+ export function setInjectedProbationIds(sessionId, ids, workspaceDir) {
276
+ const state = getOrCreateSession(sessionId, workspaceDir);
277
+ state.injectedProbationIds = [...ids];
278
+ state.lastActivityAt = Date.now();
279
+ schedulePersistence(state);
280
+ return state;
281
+ }
282
+ export function getInjectedProbationIds(sessionId, workspaceDir) {
283
+ const state = getOrCreateSession(sessionId, workspaceDir);
284
+ return [...(state.injectedProbationIds || [])];
285
+ }
286
+ export function clearInjectedProbationIds(sessionId, workspaceDir) {
287
+ return setInjectedProbationIds(sessionId, [], workspaceDir);
288
+ }
233
289
  export function getSession(sessionId) {
234
290
  return sessions.get(sessionId);
235
291
  }
292
+ export function listSessions(workspaceDir) {
293
+ return [...sessions.values()]
294
+ .filter((state) => !workspaceDir || !state.workspaceDir || state.workspaceDir === workspaceDir)
295
+ .map((state) => ({
296
+ ...state,
297
+ toolReadsByFile: { ...state.toolReadsByFile },
298
+ gfiBySource: state.gfiBySource ? { ...state.gfiBySource } : undefined,
299
+ injectedProbationIds: state.injectedProbationIds ? [...state.injectedProbationIds] : undefined,
300
+ }));
301
+ }
236
302
  export function clearSession(sessionId) {
237
303
  sessions.delete(sessionId);
238
304
  }
@@ -249,8 +315,8 @@ export function garbageCollectSessions() {
249
315
  try {
250
316
  fs.unlinkSync(sessionPath);
251
317
  }
252
- catch {
253
- // Ignore deletion errors
318
+ catch (error) {
319
+ logSessionTrackerWarning(`Failed to delete session snapshot for ${id}`, error);
254
320
  }
255
321
  }
256
322
  }
@@ -0,0 +1,38 @@
1
+ export interface ThinkingModelDefinition {
2
+ id: string;
3
+ name: string;
4
+ description: string;
5
+ patterns: RegExp[];
6
+ baselineScenarios: string[];
7
+ }
8
+ export interface ThinkingModelMatch {
9
+ modelId: string;
10
+ matchedPattern: string;
11
+ }
12
+ export interface ThinkingScenarioContext {
13
+ recentToolCalls?: Array<{
14
+ toolName: string;
15
+ outcome: 'success' | 'failure' | 'blocked';
16
+ errorType?: string | null;
17
+ }>;
18
+ recentPainEvents?: Array<{
19
+ source: string;
20
+ score: number;
21
+ }>;
22
+ recentGateBlocks?: Array<{
23
+ toolName: string;
24
+ reason: string;
25
+ }>;
26
+ recentUserCorrections?: Array<{
27
+ correctionCue?: string | null;
28
+ }>;
29
+ recentPrincipleEvents?: Array<{
30
+ eventType: string;
31
+ principleId?: string | null;
32
+ }>;
33
+ }
34
+ export declare const THINKING_MODEL_MAP: Map<string, ThinkingModelDefinition>;
35
+ export declare function listThinkingModels(): ThinkingModelDefinition[];
36
+ export declare function getThinkingModel(modelId: string): ThinkingModelDefinition | undefined;
37
+ export declare function detectThinkingModelMatches(text: string): ThinkingModelMatch[];
38
+ export declare function deriveThinkingScenarios(modelId: string, context: ThinkingScenarioContext): string[];
@@ -0,0 +1,170 @@
1
+ const THINKING_MODELS = [
2
+ {
3
+ id: 'T-01',
4
+ name: 'Survey Before Acting',
5
+ description: 'Understand the structure first before making changes.',
6
+ baselineScenarios: ['exploration', 'discovery'],
7
+ patterns: [
8
+ /let me (first )?(understand|map|outline|survey|review the (structure|architecture|dependencies))/i,
9
+ /before (changing|editing|touching) anything/i,
10
+ /让我先(梳理|理解|看看|盘点).*(结构|架构|依赖|全貌)/i,
11
+ ],
12
+ },
13
+ {
14
+ id: 'T-02',
15
+ name: 'Respect Constraints',
16
+ description: 'Explicitly reason about contracts, tests, schemas, and requirements.',
17
+ baselineScenarios: ['constraint-check', 'contract-verification'],
18
+ patterns: [
19
+ /(type|test|contract|schema|interface) (constraint|requirement|check|validation)/i,
20
+ /we (must|need to) (respect|follow|adhere to) the/i,
21
+ /(必须|需要).*(遵守|符合|满足).*(类型|测试|契约|接口|规范)/i,
22
+ ],
23
+ },
24
+ {
25
+ id: 'T-03',
26
+ name: 'Evidence Over Assumption',
27
+ description: 'Use logs, code, and outputs before inferring causes.',
28
+ baselineScenarios: ['evidence-gathering', 'verification'],
29
+ patterns: [
30
+ /based on (the |this )?(evidence|logs?|output|error|stack trace|test result)/i,
31
+ /let me (check|verify|confirm|read|look at) (the |)(actual|source|code|file|log)/i,
32
+ /根据(日志|证据|输出|报错|堆栈|测试结果)/i,
33
+ ],
34
+ },
35
+ {
36
+ id: 'T-04',
37
+ name: 'Reversible First',
38
+ description: 'Prefer changes that are safe to roll back when risk is high.',
39
+ baselineScenarios: ['risk-management', 'reversibility'],
40
+ patterns: [
41
+ /this (is|would be) (irreversible|destructive|permanent|not easily undone)/i,
42
+ /(reversible|can be undone|safely roll back)/i,
43
+ /(不可逆|破坏性|无法回滚|可以回滚|安全撤销)/i,
44
+ ],
45
+ },
46
+ {
47
+ id: 'T-05',
48
+ name: 'Safety Rails',
49
+ description: 'Call out guardrails, prohibitions, and failure-prevention constraints.',
50
+ baselineScenarios: ['guardrails', 'safety-rails'],
51
+ patterns: [
52
+ /we (must|should) (not|never|avoid|prevent|ensure we don't)/i,
53
+ /(critical|important) (not to|that we don't|to avoid)/i,
54
+ /(绝不能|必须避免|不可|禁止|确保不会)/i,
55
+ ],
56
+ },
57
+ {
58
+ id: 'T-06',
59
+ name: 'Simplicity First',
60
+ description: 'Prefer the smallest understandable solution over over-engineering.',
61
+ baselineScenarios: ['simplification', 'pragmatism'],
62
+ patterns: [
63
+ /(simpl(er|est|ify)|minimal|straightforward|lean) (approach|solution|fix|implementation)/i,
64
+ /(simple is better|keep it simple|no need to over)/i,
65
+ /(最简|更简单|精简|没必要过度设计)/i,
66
+ ],
67
+ },
68
+ {
69
+ id: 'T-07',
70
+ name: 'Minimal Change Surface',
71
+ description: 'Limit the blast radius and touch only what is necessary.',
72
+ baselineScenarios: ['minimal-diff', 'blast-radius-control'],
73
+ patterns: [
74
+ /(minimal|smallest|narrowest|least) (change|diff|modification|impact)/i,
75
+ /only (change|modify|touch|edit) (the |what)/i,
76
+ /(最小改动|最小变更|只改|只动必要部分)/i,
77
+ ],
78
+ },
79
+ {
80
+ id: 'T-08',
81
+ name: 'Pain As Signal',
82
+ description: 'Treat failures and friction as clues to step back and rethink.',
83
+ baselineScenarios: ['reflection', 'pain-response'],
84
+ patterns: [
85
+ /this (error|failure|issue) (tells us|indicates|signals|suggests|means)/i,
86
+ /let me (stop|pause|step back|reconsider|rethink)/i,
87
+ /这个(错误|失败|问题).*(说明|意味着|提示)/i,
88
+ /让我(停下|暂停|退一步|重新考虑|重新审视)/i,
89
+ ],
90
+ },
91
+ {
92
+ id: 'T-09',
93
+ name: 'Divide And Conquer',
94
+ description: 'Split the task into smaller phases before execution.',
95
+ baselineScenarios: ['decomposition', 'phased-execution'],
96
+ patterns: [
97
+ /(break|split|decompose|divide) (this |the task |it )?(into|down)/i,
98
+ /(step 1|first,? (we|i|let's)|phase 1)/i,
99
+ /(拆分|分解|分步|分阶段|第一步)/i,
100
+ ],
101
+ },
102
+ ];
103
+ export const THINKING_MODEL_MAP = new Map(THINKING_MODELS.map((model) => [model.id, model]));
104
+ export function listThinkingModels() {
105
+ return THINKING_MODELS.slice();
106
+ }
107
+ export function getThinkingModel(modelId) {
108
+ return THINKING_MODEL_MAP.get(modelId);
109
+ }
110
+ export function detectThinkingModelMatches(text) {
111
+ if (!text)
112
+ return [];
113
+ const matches = [];
114
+ for (const model of THINKING_MODELS) {
115
+ for (const pattern of model.patterns) {
116
+ if (pattern.test(text)) {
117
+ matches.push({
118
+ modelId: model.id,
119
+ matchedPattern: pattern.source,
120
+ });
121
+ break;
122
+ }
123
+ }
124
+ }
125
+ return matches;
126
+ }
127
+ export function deriveThinkingScenarios(modelId, context) {
128
+ const scenarios = new Set(getThinkingModel(modelId)?.baselineScenarios ?? []);
129
+ if ((context.recentToolCalls ?? []).some((call) => call.outcome === 'failure')) {
130
+ scenarios.add('after-tool-failure');
131
+ }
132
+ // after-recovery: success that follows a failure (not just any success)
133
+ const calls = context.recentToolCalls ?? [];
134
+ const hasFailure = calls.some((call) => call.outcome === 'failure');
135
+ const hasSuccess = calls.some((call) => call.outcome === 'success');
136
+ if (hasFailure && hasSuccess) {
137
+ scenarios.add('after-recovery');
138
+ }
139
+ if ((context.recentToolCalls ?? []).some((call) => call.outcome === 'blocked')) {
140
+ scenarios.add('blocked-execution');
141
+ }
142
+ if ((context.recentToolCalls ?? []).some((call) => Boolean(call.errorType))) {
143
+ scenarios.add('incident-response');
144
+ }
145
+ if ((context.recentPainEvents ?? []).length > 0) {
146
+ scenarios.add('user-friction');
147
+ }
148
+ if ((context.recentGateBlocks ?? []).length > 0) {
149
+ scenarios.add('gate-block');
150
+ }
151
+ if ((context.recentUserCorrections ?? []).length > 0) {
152
+ scenarios.add('user-correction');
153
+ }
154
+ if ((context.recentPrincipleEvents ?? []).length > 0) {
155
+ scenarios.add('principle-feedback');
156
+ }
157
+ if (modelId === 'T-03') {
158
+ scenarios.add('root-cause-analysis');
159
+ }
160
+ if (modelId === 'T-04' || modelId === 'T-05') {
161
+ scenarios.add('risk-review');
162
+ }
163
+ if (modelId === 'T-08') {
164
+ scenarios.add('reflection-loop');
165
+ }
166
+ if (modelId === 'T-09') {
167
+ scenarios.add('task-planning');
168
+ }
169
+ return Array.from(scenarios);
170
+ }