principles-disciple 1.41.0 → 1.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/.planning/codebase/ARCHITECTURE.md +157 -0
  2. package/.planning/codebase/CONCERNS.md +145 -0
  3. package/.planning/codebase/CONVENTIONS.md +148 -0
  4. package/.planning/codebase/INTEGRATIONS.md +81 -0
  5. package/.planning/codebase/STACK.md +87 -0
  6. package/.planning/codebase/STRUCTURE.md +193 -0
  7. package/.planning/codebase/TESTING.md +243 -0
  8. package/openclaw.plugin.json +1 -1
  9. package/package.json +1 -1
  10. package/src/commands/pain.ts +12 -5
  11. package/src/commands/promote-impl.ts +13 -7
  12. package/src/commands/rollback.ts +10 -3
  13. package/src/core/event-log.ts +8 -6
  14. package/src/core/evolution-types.ts +33 -1
  15. package/src/hooks/message-sanitize.ts +18 -5
  16. package/src/hooks/prompt.ts +15 -4
  17. package/src/hooks/subagent.ts +2 -3
  18. package/src/http/principles-console-route.ts +21 -4
  19. package/src/service/evolution-worker.ts +89 -365
  20. package/src/service/queue-io.ts +375 -0
  21. package/src/service/queue-migration.ts +122 -0
  22. package/src/service/sleep-cycle.ts +157 -0
  23. package/src/service/subagent-workflow/runtime-direct-driver.ts +1 -1
  24. package/src/service/workflow-watchdog.ts +168 -0
  25. package/src/tools/deep-reflect.ts +22 -11
  26. package/src/types/event-payload.ts +80 -0
  27. package/src/types/queue.ts +70 -0
  28. package/src/utils/file-lock.ts +2 -2
  29. package/src/utils/io.ts +11 -3
  30. package/tests/core/evolution-migration.test.ts +325 -1
  31. package/tests/core/queue-purge.test.ts +337 -0
  32. package/tests/fixtures/legacy-queue-v1.json +74 -0
  33. package/tests/queue/async-lock.test.ts +200 -0
  34. package/tests/service/evolution-worker.queue.test.ts +296 -0
  35. package/tests/service/queue-io.test.ts +229 -0
  36. package/tests/service/queue-migration.test.ts +147 -0
  37. package/tests/service/workflow-watchdog.test.ts +372 -0
@@ -0,0 +1,193 @@
1
+ # Codebase Structure
2
+
3
+ **Analysis Date:** 2026-04-15
4
+
5
+ ## Directory Layout
6
+
7
+ ```
8
+ openclaw-plugin/
9
+ ├── src/ # Main source code
10
+ │ ├── commands/ # Slash command implementations
11
+ │ ├── config/ # Configuration defaults and errors
12
+ │ ├── constants/ # Shared constants
13
+ │ ├── core/ # Core business logic
14
+ │ │ ├── hygiene/ # Hygiene tracking
15
+ │ │ ├── principle-internalization/ # Principle lifecycle
16
+ │ │ └── schema/ # Database schema and migrations
17
+ │ ├── hooks/ # OpenClaw hook handlers
18
+ │ ├── http/ # HTTP route handlers
19
+ │ ├── i18n/ # Internationalization
20
+ │ ├── service/ # Background services
21
+ │ │ └── subagent-workflow/ # Workflow managers
22
+ │ ├── tools/ # Plugin tools
23
+ │ ├── types/ # TypeScript type definitions
24
+ │ └── utils/ # Utility functions
25
+ ├── ui/ # React UI
26
+ │ └── src/
27
+ │ ├── components/ # React components
28
+ │ ├── context/ # React contexts
29
+ │ ├── hooks/ # React hooks
30
+ │ └── pages/ # Page components
31
+ ├── tests/ # Test suite
32
+ │ ├── commands/ # Command tests
33
+ │ ├── core/ # Core module tests
34
+ │ ├── fixtures/ # Test fixtures
35
+ │ ├── hooks/ # Hook tests
36
+ │ ├── integration/ # Integration tests
37
+ │ ├── service/ # Service tests
38
+ │ └── utils/ # Utility tests
39
+ ├── templates/ # Workspace templates
40
+ │ ├── langs/ # Language-specific templates
41
+ │ └── workspace/ # Workspace structure templates
42
+ ├── dist/ # Build output
43
+ ├── scripts/ # Build scripts
44
+ ├── .state/ # Runtime state (gitignored)
45
+ └── .tmp/ # Temporary files (gitignored)
46
+ ```
47
+
48
+ ## Directory Purposes
49
+
50
+ **src/commands/:**
51
+ - Purpose: Slash command implementations
52
+ - Contains: 20+ command handlers (strategy, focus, pain, rollback, nocturnal-review, nocturnal-train, etc.)
53
+ - Key files: `strategy.ts`, `focus.ts`, `nocturnal-train.ts`, `nocturnal-rollout.ts`
54
+
55
+ **src/core/:**
56
+ - Purpose: Core business logic (evolution, trajectory, pain, training, rules)
57
+ - Contains: 70+ core modules including `evolution-engine.ts`, `trajectory.ts`, `nocturnal-trinity.ts`, `pain.ts`, `principle-tree-ledger.ts`
58
+ - Key files: `evolution-engine.ts`, `nocturnal-trinity.ts`, `rule-host.ts`
59
+
60
+ **src/hooks/:**
61
+ - Purpose: OpenClaw hook handlers for intercepting agent behavior
62
+ - Contains: `prompt.ts`, `gate.ts`, `pain.ts`, `llm.ts`, `lifecycle.ts`, `subagent.ts`, `trajectory-collector.ts`
63
+ - Key files: `gate.ts` (security), `prompt.ts` (context injection)
64
+
65
+ **src/service/:**
66
+ - Purpose: Background worker services
67
+ - Contains: `evolution-worker.ts` (main worker), `nocturnal-service.ts`, `trajectory-service.ts`, `central-database.ts`
68
+ - Key files: `evolution-worker.ts` (144KB, main async processor)
69
+
70
+ **src/service/subagent-workflow/:**
71
+ - Purpose: Workflow orchestration for complex subagent operations
72
+ - Contains: `nocturnal-workflow-manager.ts`, `deep-reflect-workflow-manager.ts`, `empathy-observer-workflow-manager.ts`
73
+ - Key files: `workflow-manager-base.ts`, `dynamic-timeout.ts`
74
+
75
+ **src/utils/:**
76
+ - Purpose: Shared utility functions
77
+ - Contains: `io.ts` (atomic writes), `plugin-logger.ts`, `retry.ts`, `hashing.ts`, `file-lock.ts`
78
+ - Key files: `io.ts` (critical for safe file operations)
79
+
80
+ **src/schema/:**
81
+ - Purpose: SQLite database schema and migrations
82
+ - Contains: `schema-definitions.ts`, `migration-runner.ts`, `migrations/*.ts`
83
+ - Migrations: 4 migrations (001-004)
84
+
85
+ **ui/src/:**
86
+ - Purpose: React-based plugin UI
87
+ - Contains: Pages (Overview, Evolution, Feedback, GateMonitor), components (Shell, ProtectedRoute)
88
+ - Key files: `App.tsx`, `pages/EvolutionPage.tsx`, `pages/FeedbackPage.tsx`
89
+
90
+ ## Key File Locations
91
+
92
+ **Entry Points:**
93
+ - `src/index.ts`: Plugin entry point, registers all hooks/commands/tools
94
+
95
+ **Configuration:**
96
+ - `src/core/config.ts`: PainSettings defaults
97
+ - `src/core/paths.ts`: Directory and file path constants (PD_DIRS, PD_FILES)
98
+ - `openclaw.plugin.json`: Plugin manifest
99
+
100
+ **Core Logic:**
101
+ - `src/core/evolution-engine.ts`: Evolution processing (18KB)
102
+ - `src/core/nocturnal-trinity.ts`: Nocturnal training orchestration (87KB - largest file)
103
+ - `src/core/trajectory.ts`: Trajectory tracking (64KB)
104
+ - `src/core/principle-tree-ledger.ts`: Principle lifecycle management (22KB)
105
+ - `src/core/rule-host.ts`: Sandboxed rule execution (7KB)
106
+
107
+ **Service Layer:**
108
+ - `src/service/evolution-worker.ts`: Background evolution worker (144KB - largest file)
109
+ - `src/service/nocturnal-service.ts`: Nocturnal training service (59KB)
110
+ - `src/service/nocturnal-runtime.ts`: Runtime for nocturnal operations (24KB)
111
+
112
+ **Testing:**
113
+ - `tests/`: Test suite with unit and integration layers
114
+ - `vitest.config.ts`: Test configuration with unit/integration project separation
115
+
116
+ ## Naming Conventions
117
+
118
+ **Files:**
119
+ - TypeScript: `kebab-case.ts` or `camelCase.ts` depending on module type
120
+ - Commands: `kebab-case.ts` (e.g., `nocturnal-review.ts`)
121
+ - Core modules: `camelCase.ts` (e.g., `evolutionEngine.ts`)
122
+ - React components: `PascalCase.tsx`
123
+
124
+ **Directories:**
125
+ - kebab-case: `subagent-workflow`, `principle-internalization`
126
+
127
+ **Types:**
128
+ - Interfaces: `PascalCase` (e.g., `PainSettings`, `EvolutionContext`)
129
+ - Type aliases: `PascalCase`
130
+ - Enums: `PascalCase`
131
+
132
+ ## Where to Add New Code
133
+
134
+ **New Command:**
135
+ - Primary code: `src/commands/<name>.ts`
136
+ - Handler registration: `src/index.ts` in `registerCommandWithAlias()` or `api.registerCommand()`
137
+ - Tests: `tests/commands/<name>.test.ts`
138
+
139
+ **New Hook Handler:**
140
+ - Implementation: `src/hooks/<name>.ts`
141
+ - Registration: `src/index.ts` in `api.on('<hook_name>', ...)` call
142
+ - Tests: `tests/hooks/<name>.test.ts`
143
+
144
+ **New Core Service:**
145
+ - Implementation: `src/core/<name>.ts` or `src/service/<name>.ts`
146
+ - Registration: `src/index.ts` in `api.registerService()`
147
+ - Tests: `tests/core/<name>.test.ts` or `tests/service/<name>.test.ts`
148
+
149
+ **New Workflow Manager:**
150
+ - Implementation: `src/service/subagent-workflow/<name>-workflow-manager.ts`
151
+ - Base class: `src/service/subagent-workflow/workflow-manager-base.ts`
152
+ - Tests: `tests/service/subagent-workflow/<name>.test.ts`
153
+
154
+ **New Database Migration:**
155
+ - Implementation: `src/core/schema/migrations/<number>-<description>.ts`
156
+ - Registration: `src/core/schema/migrations/index.ts`
157
+ - Tests: Integration test in `tests/core/control-ui-db.test.ts`
158
+
159
+ **New Utility:**
160
+ - Shared: `src/utils/<name>.ts`
161
+ - Tests: `tests/utils/<name>.test.ts`
162
+
163
+ ## Special Directories
164
+
165
+ **.state/:**
166
+ - Purpose: Runtime state (per workspace)
167
+ - Generated: Yes (created at runtime)
168
+ - Committed: No (gitignored)
169
+
170
+ **.tmp/:**
171
+ - Purpose: Temporary files during build/dev
172
+ - Generated: Yes
173
+ - Committed: No (gitignored)
174
+
175
+ **dist/:**
176
+ - Purpose: Build output
177
+ - Generated: Yes (by `npm run build`)
178
+ - Committed: Yes (in some branches)
179
+
180
+ **templates/:**
181
+ - Purpose: Workspace template files copied on init
182
+ - Generated: No
183
+ - Committed: Yes
184
+ - Contains: Language-specific templates (en, zh), workspace structure
185
+
186
+ **node_modules/:**
187
+ - Purpose: Dependencies
188
+ - Generated: Yes (by pnpm install)
189
+ - Committed: No
190
+
191
+ ---
192
+
193
+ *Structure analysis: 2026-04-15*
@@ -0,0 +1,243 @@
1
+ # Testing Patterns
2
+
3
+ **Analysis Date:** 2026-04-15
4
+
5
+ ## Test Framework
6
+
7
+ **Runner:** Vitest 4.1.0
8
+ - Config: `vitest.config.ts`
9
+ - Environment: `node`
10
+ - Pool: `threads` (required for `better-sqlite3` native handle cleanup)
11
+
12
+ **Assertion Library:** Vitest built-in (`expect`)
13
+
14
+ **Coverage:** `@vitest/coverage-v8`
15
+ - Thresholds: lines 70%, functions 70%, branches 60%, statements 70%
16
+ - Excludes: `tests/**`
17
+
18
+ **Run Commands:**
19
+ ```bash
20
+ npm test # Run unit tests (fast, parallel)
21
+ npm run test:unit # Alias for above
22
+ npm run test:integration # Run integration tests only
23
+ npm run test:coverage # Run with coverage report
24
+ npm run test:all # Run all tests (unit + integration)
25
+ ```
26
+
27
+ ## Test File Organization
28
+
29
+ **Location:**
30
+ - Tests co-located in `tests/` directory, mirroring `src/` structure
31
+ - `tests/core/`, `tests/commands/`, `tests/hooks/`, `tests/service/`, `tests/utils/`
32
+ - Integration tests in `tests/integration/`
33
+
34
+ **Naming:**
35
+ - `*.test.ts` suffix for all test files
36
+ - Example: `tests/core/detection-service.test.ts`
37
+
38
+ **Structure:**
39
+ ```
40
+ tests/
41
+ ├── commands/ # Command handlers
42
+ ├── core/ # Core services and logic
43
+ ├── fixtures/ # Shared fixtures
44
+ ├── hooks/ # Hook handlers
45
+ ├── integration/ # End-to-end tests
46
+ ├── scripts/ # Script tests
47
+ └── service/ # Service layer
48
+ ```
49
+
50
+ ## Test Structure
51
+
52
+ **Suite Organization:**
53
+ ```typescript
54
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
55
+
56
+ describe('DetectionService', () => {
57
+ beforeEach(() => {
58
+ vi.clearAllMocks();
59
+ // Reset state between tests
60
+ DetectionService.reset();
61
+ });
62
+
63
+ it('should create a new instance on first get', () => {
64
+ // Test implementation
65
+ });
66
+ });
67
+ ```
68
+
69
+ **Patterns:**
70
+ - `beforeEach` for setup, `afterEach` for teardown
71
+ - `vi.clearAllMocks()` between tests (not `vi.resetAllMocks()`)
72
+ - `vi.useFakeTimers()` / `vi.useRealTimers()` for time-sensitive tests
73
+
74
+ ## Mocking
75
+
76
+ **Framework:** Vitest's `vi.fn()` and `vi.mock()`
77
+
78
+ **Module Mocking:**
79
+ ```typescript
80
+ vi.mock('../../src/core/dictionary-service.js');
81
+ vi.mock('../../src/core/detection-funnel.js');
82
+
83
+ // Then configure mock implementations
84
+ vi.mocked(DictionaryService.get).mockReturnValue(mockDict as any);
85
+ ```
86
+
87
+ **Built-in Module Mocking:**
88
+ ```typescript
89
+ vi.mock('fs');
90
+ vi.mocked(fs.existsSync).mockReturnValue(true);
91
+ vi.mocked(fs.readFileSync).mockImplementation((p) => {
92
+ if (p.toString() === configPath) return JSON.stringify(mockConfig);
93
+ return '';
94
+ });
95
+ ```
96
+
97
+ **Mock Reset:**
98
+ - `vi.clearAllMocks()` clears call history but keeps implementations
99
+ - `vi.resetAllMocks()` clears both (use with caution)
100
+ - Reset singleton state: `DetectionService.reset()`, `WorkspaceContext.clearCache()`
101
+
102
+ **Partial Mocks:**
103
+ ```typescript
104
+ vi.mocked(fs.existsSync).mockImplementation((p) => p.toString() === configPath);
105
+ ```
106
+
107
+ ## Fixtures and Factories
108
+
109
+ **Temp Directory Pattern (integration tests):**
110
+ ```typescript
111
+ const tempDirs: string[] = [];
112
+
113
+ function makeWorkspace(): string {
114
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-runtime-summary-'));
115
+ tempDirs.push(dir);
116
+ fs.mkdirSync(path.join(dir, '.state', 'sessions'), { recursive: true });
117
+ return dir;
118
+ }
119
+
120
+ afterEach(() => {
121
+ for (const dir of tempDirs.splice(0)) {
122
+ fs.rmSync(dir, { recursive: true, force: true });
123
+ }
124
+ });
125
+ ```
126
+
127
+ **JSON Fixture Writing:**
128
+ ```typescript
129
+ function writeJson(filePath: string, value: unknown): void {
130
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
131
+ fs.writeFileSync(filePath, JSON.stringify(value, null, 2), 'utf8');
132
+ }
133
+ ```
134
+
135
+ **Session File Helper:**
136
+ ```typescript
137
+ function writeSession(workspace: string, sessionId: string, payload: Record<string, unknown>): void {
138
+ writeJson(path.join(workspace, '.state', 'sessions', `${sessionId}.json`), {
139
+ sessionId,
140
+ ...payload,
141
+ });
142
+ }
143
+ ```
144
+
145
+ ## Integration Tests
146
+
147
+ **Requirements:**
148
+ - Real SQLite database via `better-sqlite3`
149
+ - Thread pool required (not `vm` pool) due to native handle cleanup issues
150
+ - Explicit file list in `vitest.config.ts` integration array
151
+
152
+ **Integration Test Files:**
153
+ ```typescript
154
+ const integrationTests = [
155
+ 'tests/core/control-ui-db.test.ts',
156
+ 'tests/core/evolution-logger.test.ts',
157
+ 'tests/core/nocturnal-e2e.test.ts',
158
+ // ...
159
+ ];
160
+ ```
161
+
162
+ ## Common Patterns
163
+
164
+ **Async Testing with Fake Timers:**
165
+ ```typescript
166
+ beforeEach(() => {
167
+ vi.useFakeTimers();
168
+ });
169
+
170
+ afterEach(() => {
171
+ vi.useRealTimers();
172
+ });
173
+
174
+ it('should retry on retryable error', async () => {
175
+ const fn = vi.fn()
176
+ .mockRejectedValueOnce(new Error('ETIMEDOUT'))
177
+ .mockResolvedValue('success');
178
+
179
+ const resultPromise = retryAsync(fn, { initialDelayMs: 100 });
180
+ await vi.advanceTimersByTimeAsync(100);
181
+ const result = await resultPromise;
182
+ expect(result).toBe('success');
183
+ });
184
+ ```
185
+
186
+ **Error Testing:**
187
+ ```typescript
188
+ it('should throw after max retries exceeded', async () => {
189
+ vi.useRealTimers();
190
+ const fn = vi.fn().mockRejectedValue(new Error('ETIMEDOUT'));
191
+
192
+ await expect(retryAsync(fn, { maxRetries: 1, initialDelayMs: 1, logger: { warn: vi.fn() } }))
193
+ .rejects.toThrow('ETIMEDOUT');
194
+ });
195
+ ```
196
+
197
+ **Singleton Reset Pattern:**
198
+ ```typescript
199
+ // Many services use singleton pattern requiring reset between tests
200
+ DetectionService.reset();
201
+ WorkspaceContext.clearCache();
202
+ clearSession('live-session');
203
+ ```
204
+
205
+ ## Test Naming and Documentation
206
+
207
+ **Descriptive Test Names:**
208
+ ```typescript
209
+ it('should block risk path writes at Seed tier (EP system)', () => { ... });
210
+ it('should return default values if file does not exist', () => { ... });
211
+ ```
212
+
213
+ **Test Comments for Context:**
214
+ ```typescript
215
+ // Task 4: Default Values Consistency Tests
216
+ describe('Gate Default Values Consistency', () => {
217
+ /**
218
+ * PURPOSE: Prove that gate.ts inline defaults match PROFILE_DEFAULTS.
219
+ * If gate.ts has inline defaults that differ from normalizeProfile(),
220
+ * this is a bug - the defaults should come from a single source of truth.
221
+ */
222
+ });
223
+ ```
224
+
225
+ ## Test Isolation
226
+
227
+ **Environment Variable Injection:**
228
+ ```typescript
229
+ // Set env before module load
230
+ process.env.PD_TEST_AGENTS_DIR = TEST_AGENTS_DIR;
231
+ ```
232
+
233
+ **Path Traversal Protection Tests:**
234
+ ```typescript
235
+ it('rejects path traversal session IDs', async () => {
236
+ const result = await extractRecentConversation('../../etc/passwd', 'main');
237
+ expect(result).toBe('');
238
+ });
239
+ ```
240
+
241
+ ---
242
+
243
+ *Testing analysis: 2026-04-15*
@@ -2,7 +2,7 @@
2
2
  "id": "principles-disciple",
3
3
  "name": "Principles Disciple",
4
4
  "description": "Evolutionary programming agent framework with strategic guardrails and reflection loops.",
5
- "version": "1.41.0",
5
+ "version": "1.42.0",
6
6
  "skills": [
7
7
  "./skills"
8
8
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "principles-disciple",
3
- "version": "1.41.0",
3
+ "version": "1.42.0",
4
4
  "description": "Native OpenClaw plugin for Principles Disciple",
5
5
  "type": "module",
6
6
  "main": "./dist/bundle.js",
@@ -80,6 +80,14 @@ function formatEmpathyCard(stats: EmpathyEventStats, range: string, isZh: boolea
80
80
  return lines.join('\n');
81
81
  }
82
82
 
83
+ /**
84
+ * Extended context interface that includes sessionId injected by the plugin framework.
85
+ * PluginCommandContext does not include sessionId in its type definition.
86
+ */
87
+ interface SessionAwareCommandContext extends PluginCommandContext {
88
+ sessionId: string;
89
+ }
90
+
83
91
  /**
84
92
  * Handles the /pd-status command
85
93
  */
@@ -89,15 +97,14 @@ export function handlePainCommand(ctx: PluginCommandContext): PluginCommandResul
89
97
  const wctx = WorkspaceContext.fromHookContext({ workspaceDir, ...ctx.config });
90
98
  const lang = (ctx.config?.language as string) || 'en';
91
99
  const isZh = lang === 'zh';
92
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Reason: sessionId injected by OpenClaw plugin framework - type not available in PluginCommandContext
93
- const {sessionId} = (ctx as any);
100
+ const { sessionId } = ctx as SessionAwareCommandContext;
94
101
 
95
102
  const args = (ctx.args || '').trim();
96
103
 
97
104
  // Handle empathy subcommand
98
105
  if (args.startsWith('empathy')) {
99
106
 
100
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
107
+
101
108
  return handleEmpathySubcommand(wctx, args, sessionId, isZh);
102
109
  }
103
110
 
@@ -138,7 +145,7 @@ export function handlePainCommand(ctx: PluginCommandContext): PluginCommandResul
138
145
 
139
146
  // Determine health status based on GFI
140
147
 
141
- // eslint-disable-next-line @typescript-eslint/init-declarations
148
+
142
149
  let healthLabel: string;
143
150
  let suggestionText = '';
144
151
 
@@ -218,7 +225,7 @@ export function handlePainCommand(ctx: PluginCommandContext): PluginCommandResul
218
225
  * Handle /pd-status empathy subcommand
219
226
  */
220
227
 
221
- // eslint-disable-next-line @typescript-eslint/max-params -- complexity 13, refactor candidate
228
+
222
229
  function handleEmpathySubcommand(
223
230
  wctx: WorkspaceContext,
224
231
  args: string,
@@ -28,7 +28,7 @@ import {
28
28
  } from '../core/principle-tree-ledger.js';
29
29
  import { WorkspaceContext } from '../core/workspace-context.js';
30
30
  import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
31
- import type { Implementation } from '../types/principle-tree-schema.js';
31
+ import type { Implementation, ImplementationLifecycleState } from '../types/principle-tree-schema.js';
32
32
  import { withLock } from '../utils/file-lock.js';
33
33
  import { atomicWriteFileSync } from '../utils/io.js';
34
34
 
@@ -37,16 +37,23 @@ function getAllImplementations(stateDir: string): Implementation[] {
37
37
  return Object.values(ledger.tree.implementations);
38
38
  }
39
39
 
40
+ /**
41
+ * Type predicate: true if impl has lifecycleState of 'candidate' or 'disabled'.
42
+ * The ledger adds lifecycleState at runtime beyond what's in the manifest interface.
43
+ */
44
+ function isCandidateOrDisabled(
45
+ impl: Implementation
46
+ ): impl is Implementation & { lifecycleState: ImplementationLifecycleState } {
47
+ return impl.lifecycleState === 'candidate' || impl.lifecycleState === 'disabled';
48
+ }
49
+
40
50
  function _handleListCandidates(
41
51
  stateDir: string,
42
52
  isZh: boolean,
43
53
  ): PluginCommandResult {
44
54
  const engine = new ReplayEngine('', stateDir);
45
55
  const allImpls = getAllImplementations(stateDir);
46
- const candidates = allImpls.filter(
47
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Reason: lifecycleState is a dynamic property added by the system - type not in official interface
48
- (impl) => (impl as any).lifecycleState === 'candidate',
49
- );
56
+ const candidates = allImpls.filter(isCandidateOrDisabled);
50
57
 
51
58
  if (candidates.length === 0) {
52
59
  return {
@@ -141,8 +148,7 @@ function _handlePromoteImpl(options: PromoteImplOptions): PluginCommandResult {
141
148
  };
142
149
  }
143
150
 
144
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Reason: lifecycleState is a dynamic property added by the system - type not in official interface
145
- const currentState = (candidate as any).lifecycleState || 'candidate';
151
+ const currentState = candidate.lifecycleState || 'candidate';
146
152
 
147
153
  if (currentState !== 'candidate' && currentState !== 'disabled') {
148
154
  return {
@@ -2,6 +2,14 @@ import { WorkspaceContext } from '../core/workspace-context.js';
2
2
  import { resetFriction } from '../core/session-tracker.js';
3
3
  import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
4
4
 
5
+ /**
6
+ * Extended context interface that includes sessionId injected by the plugin framework.
7
+ * PluginCommandContext does not include sessionId in its type definition.
8
+ */
9
+ interface SessionAwareCommandContext extends PluginCommandContext {
10
+ sessionId: string;
11
+ }
12
+
5
13
  /**
6
14
  * Handles the /pd-rollback command
7
15
  *
@@ -15,8 +23,7 @@ export function handleRollbackCommand(ctx: PluginCommandContext): PluginCommandR
15
23
  const wctx = WorkspaceContext.fromHookContext({ workspaceDir, ...ctx.config });
16
24
  const lang = (ctx.config?.language as string) || 'en';
17
25
  const isZh = lang === 'zh';
18
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Reason: sessionId injected by OpenClaw plugin framework - type not available in PluginCommandContext
19
- const {sessionId} = (ctx as any);
26
+ const { sessionId } = ctx as SessionAwareCommandContext;
20
27
 
21
28
  const args = (ctx.args || '').trim();
22
29
 
@@ -45,7 +52,7 @@ Usage:
45
52
  }
46
53
 
47
54
 
48
- // eslint-disable-next-line @typescript-eslint/init-declarations
55
+
49
56
  let eventId: string | null;
50
57
 
51
58
  const _triggerMethod = 'user_command' as const;
@@ -1,3 +1,4 @@
1
+ /* eslint-disable max-lines */
1
2
  import * as fs from 'fs';
2
3
  import * as path from 'path';
3
4
  import type {
@@ -94,7 +95,7 @@ export class EventLog {
94
95
  /**
95
96
  * Clean up event files older than EVENT_LOG_RETENTION_DAYS.
96
97
  */
97
- private cleanupOldEventFiles(today: string): void {
98
+ private cleanupOldEventFiles(_today: string): void {
98
99
  if (EVENT_LOG_RETENTION_DAYS <= 0) return;
99
100
 
100
101
  try {
@@ -110,8 +111,8 @@ export class EventLog {
110
111
  fs.unlinkSync(filePath);
111
112
  }
112
113
  }
113
- } catch {
114
- // Silently fail cleanup
114
+ } catch (err) {
115
+ this.logger?.debug?.(`[PD] Event file cleanup failed (non-blocking): ${String(err)}`);
115
116
  }
116
117
  }
117
118
 
@@ -220,6 +221,7 @@ export class EventLog {
220
221
  }
221
222
  }
222
223
 
224
+ /* eslint-disable complexity */
223
225
  private updateStats(entry: EventLogEntry): void {
224
226
  let stats = this.statsCache.get(entry.date);
225
227
  if (!stats) {
@@ -228,8 +230,6 @@ export class EventLog {
228
230
  }
229
231
 
230
232
  if (entry.type === 'tool_call') {
231
-
232
- const _data = entry.data as unknown as ToolCallEventData;
233
233
  stats.tools.total++;
234
234
  if (entry.category === 'success') stats.tools.success++;
235
235
  else stats.tools.failure++;
@@ -349,7 +349,8 @@ export class EventLog {
349
349
  .map((line) => {
350
350
  try {
351
351
  return JSON.parse(line) as EventLogEntry;
352
- } catch {
352
+ } catch (err) {
353
+ this.logger?.warn?.(`[PD] Corrupted event line skipped: ${String(err).slice(0, 100)}`);
353
354
  return null;
354
355
  }
355
356
  })
@@ -499,6 +500,7 @@ export class EventLog {
499
500
  /**
500
501
  * Aggregate empathy stats for a specific session.
501
502
  */
503
+ /* eslint-disable complexity */
502
504
  private aggregateSessionEmpathy(sessionId: string, result: EmpathyEventStats): void {
503
505
  for (const entry of this.getMergedEvents()) {
504
506
  if (entry.sessionId === sessionId && entry.type === 'pain_signal') {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Evolution Points System V2.0 - MVP
3
- *
3
+ *
4
4
  * Core Philosophy: Growth-driven替代Penalty-driven
5
5
  * - 起点0分,只能增加,不扣分
6
6
  * - 失败记录教训,不扣分
@@ -8,6 +8,9 @@
8
8
  * - 5级成长路径:Seed → Forest
9
9
  */
10
10
 
11
+ // V2 queue types require TaskKind/TaskPriority from trajectory-types
12
+ import type { TaskKind, TaskPriority } from './trajectory-types.js';
13
+
11
14
  // ===== 等级定义 =====
12
15
 
13
16
 
@@ -464,3 +467,32 @@ export type EvolutionLoopEvent =
464
467
  | { ts: string; type: 'principle_rolled_back'; data: PrincipleRolledBackData }
465
468
  | { ts: string; type: 'circuit_breaker_opened'; data: CircuitBreakerOpenedData }
466
469
  | { ts: string; type: 'legacy_import'; data: LegacyImportData };
470
+
471
+ // V2 Queue Types (moved from evolution-worker.ts for shared use)
472
+ export type QueueStatus = 'pending' | 'in_progress' | 'completed' | 'failed' | 'canceled';
473
+ export type TaskResolution = 'marker_detected' | 'auto_completed_timeout' | 'failed_max_retries' | 'runtime_unavailable' | 'canceled' | 'late_marker_principle_created' | 'late_marker_no_principle' | 'stub_fallback' | 'skipped_thin_violation' | 'success' | 'failure' | 'skipped';
474
+
475
+ export interface EvolutionQueueItem {
476
+ id: string;
477
+ taskKind: TaskKind;
478
+ priority: TaskPriority;
479
+ source: string;
480
+ traceId?: string;
481
+ task?: string;
482
+ score: number;
483
+ reason: string;
484
+ timestamp: string;
485
+ enqueued_at?: string;
486
+ started_at?: string;
487
+ completed_at?: string;
488
+ assigned_session_key?: string;
489
+ trigger_text_preview?: string;
490
+ status: QueueStatus;
491
+ resolution?: TaskResolution;
492
+ session_id?: string;
493
+ agent_id?: string;
494
+ retryCount: number;
495
+ maxRetries: number;
496
+ lastError?: string;
497
+ resultRef?: string;
498
+ }