principles-disciple 1.11.0 → 1.13.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.
- package/CHANGELOG.md +26 -0
- package/README.md +66 -0
- package/esbuild.config.js +1 -1
- package/openclaw.plugin.json +4 -4
- package/package.json +2 -3
- package/run-nocturnal.mjs +30 -0
- package/scripts/db-migrate.mjs +170 -0
- package/scripts/sync-plugin.mjs +250 -6
- package/src/commands/archive-impl.ts +136 -0
- package/src/commands/capabilities.ts +4 -2
- package/src/commands/context.ts +5 -1
- package/src/commands/disable-impl.ts +151 -0
- package/src/commands/evolution-status.ts +64 -19
- package/src/commands/export.ts +8 -6
- package/src/commands/focus.ts +8 -20
- package/src/commands/nocturnal-review.ts +5 -7
- package/src/commands/nocturnal-rollout.ts +1 -12
- package/src/commands/nocturnal-train.ts +21 -47
- package/src/commands/pain.ts +10 -5
- package/src/commands/principle-rollback.ts +4 -2
- package/src/commands/promote-impl.ts +274 -0
- package/src/commands/rollback-impl.ts +234 -0
- package/src/commands/rollback.ts +6 -3
- package/src/commands/samples.ts +2 -0
- package/src/commands/thinking-os.ts +3 -4
- package/src/commands/workflow-debug.ts +2 -1
- package/src/config/errors.ts +1 -0
- package/src/core/AGENTS.md +34 -0
- package/src/core/adaptive-thresholds.ts +4 -3
- package/src/core/code-implementation-storage.ts +241 -0
- package/src/core/config.ts +5 -2
- package/src/core/control-ui-db.ts +29 -10
- package/src/core/detection-funnel.ts +12 -7
- package/src/core/diagnostician-task-store.ts +156 -0
- package/src/core/dictionary.ts +4 -4
- package/src/core/empathy-keyword-matcher.ts +7 -3
- package/src/core/empathy-types.ts +13 -2
- package/src/core/event-log.ts +14 -6
- package/src/core/evolution-engine.ts +27 -31
- package/src/core/evolution-logger.ts +3 -2
- package/src/core/evolution-reducer.ts +110 -31
- package/src/core/evolution-types.ts +10 -0
- package/src/core/external-training-contract.ts +1 -0
- package/src/core/focus-history.ts +38 -24
- package/src/core/hygiene/tracker.ts +10 -6
- package/src/core/init.ts +5 -2
- package/src/core/migration.ts +3 -3
- package/src/core/model-deployment-registry.ts +6 -4
- package/src/core/model-training-registry.ts +5 -3
- package/src/core/nocturnal-arbiter.ts +13 -14
- package/src/core/nocturnal-artifact-lineage.ts +117 -0
- package/src/core/nocturnal-artificer.ts +257 -0
- package/src/core/nocturnal-candidate-scoring.ts +4 -2
- package/src/core/nocturnal-compliance.ts +67 -19
- package/src/core/nocturnal-dataset.ts +95 -2
- package/src/core/nocturnal-executability.ts +2 -3
- package/src/core/nocturnal-export.ts +6 -3
- package/src/core/nocturnal-rule-implementation-validator.ts +245 -0
- package/src/core/nocturnal-trajectory-extractor.ts +10 -3
- package/src/core/nocturnal-trinity.ts +319 -61
- package/src/core/pain-context-extractor.ts +29 -15
- package/src/core/pain.ts +7 -5
- package/src/core/path-resolver.ts +16 -15
- package/src/core/paths.ts +2 -1
- package/src/core/pd-task-reconciler.ts +463 -0
- package/src/core/pd-task-service.ts +42 -0
- package/src/core/pd-task-store.ts +77 -0
- package/src/core/pd-task-types.ts +128 -0
- package/src/core/principle-internalization/deprecated-readiness.ts +91 -0
- package/src/core/principle-internalization/internalization-routing-policy.ts +208 -0
- package/src/core/principle-internalization/lifecycle-metrics.ts +149 -0
- package/src/core/principle-internalization/lifecycle-read-model.ts +243 -0
- package/src/core/principle-internalization/lifecycle-refresh.ts +11 -0
- package/src/core/principle-internalization/principle-lifecycle-service.ts +167 -0
- package/src/core/principle-training-state.ts +95 -370
- package/src/core/principle-tree-ledger.ts +733 -0
- package/src/core/principle-tree-migration.ts +195 -0
- package/src/core/profile.ts +3 -1
- package/src/core/promotion-gate.ts +14 -18
- package/src/core/replay-engine.ts +562 -0
- package/src/core/risk-calculator.ts +6 -4
- package/src/core/rule-host-helpers.ts +39 -0
- package/src/core/rule-host-types.ts +82 -0
- package/src/core/rule-host.ts +245 -0
- package/src/core/rule-implementation-runtime.ts +38 -0
- package/src/core/schema/db-types.ts +16 -0
- package/src/core/schema/index.ts +26 -0
- package/src/core/schema/migration-runner.ts +207 -0
- package/src/core/schema/migrations/001-init-trajectory.ts +211 -0
- package/src/core/schema/migrations/002-init-central.ts +122 -0
- package/src/core/schema/migrations/003-init-workflow.ts +55 -0
- package/src/core/schema/migrations/004-add-thinking-and-gfi.ts +74 -0
- package/src/core/schema/migrations/index.ts +31 -0
- package/src/core/schema/schema-definitions.ts +650 -0
- package/src/core/session-tracker.ts +6 -4
- package/src/core/shadow-observation-registry.ts +6 -3
- package/src/core/system-logger.ts +2 -2
- package/src/core/thinking-models.ts +182 -46
- package/src/core/thinking-os-parser.ts +156 -0
- package/src/core/training-program.ts +7 -7
- package/src/core/trajectory.ts +42 -36
- package/src/core/workspace-context.ts +77 -11
- package/src/core/workspace-dir-validation.ts +152 -0
- package/src/hooks/AGENTS.md +31 -0
- package/src/hooks/bash-risk.ts +3 -1
- package/src/hooks/edit-verification.ts +9 -5
- package/src/hooks/gate-block-helper.ts +5 -1
- package/src/hooks/gate.ts +152 -5
- package/src/hooks/gfi-gate.ts +9 -2
- package/src/hooks/lifecycle-routing.ts +124 -0
- package/src/hooks/lifecycle.ts +12 -12
- package/src/hooks/llm.ts +17 -109
- package/src/hooks/message-sanitize.ts +5 -3
- package/src/hooks/pain.ts +19 -15
- package/src/hooks/progressive-trust-gate.ts +7 -1
- package/src/hooks/prompt.ts +169 -60
- package/src/hooks/subagent.ts +5 -4
- package/src/hooks/thinking-checkpoint.ts +2 -0
- package/src/hooks/trajectory-collector.ts +15 -12
- package/src/http/principles-console-route.ts +31 -68
- package/src/i18n/commands.ts +2 -2
- package/src/index.ts +130 -40
- package/src/service/central-database.ts +131 -43
- package/src/service/central-health-service.ts +47 -0
- package/src/service/central-overview-service.ts +135 -0
- package/src/service/central-sync-service.ts +87 -0
- package/src/service/control-ui-query-service.ts +46 -36
- package/src/service/event-log-auditor.ts +261 -0
- package/src/service/evolution-query-service.ts +23 -22
- package/src/service/evolution-worker.ts +565 -261
- package/src/service/health-query-service.ts +213 -36
- package/src/service/nocturnal-runtime.ts +8 -4
- package/src/service/nocturnal-service.ts +503 -59
- package/src/service/nocturnal-target-selector.ts +5 -7
- package/src/service/runtime-summary-service.ts +2 -1
- package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +25 -336
- package/src/service/subagent-workflow/dynamic-timeout.ts +30 -0
- package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +48 -386
- package/src/service/subagent-workflow/index.ts +2 -0
- package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +169 -284
- package/src/service/subagent-workflow/runtime-direct-driver.ts +114 -16
- package/src/service/subagent-workflow/subagent-error-utils.ts +25 -0
- package/src/service/subagent-workflow/types.ts +9 -4
- package/src/service/subagent-workflow/workflow-manager-base.ts +573 -0
- package/src/service/subagent-workflow/workflow-store.ts +71 -11
- package/src/service/trajectory-service.ts +2 -1
- package/src/tools/critique-prompt.ts +1 -1
- package/src/tools/deep-reflect.ts +175 -209
- package/src/tools/model-index.ts +2 -1
- package/src/types/event-types.ts +2 -2
- package/src/types/principle-tree-schema.ts +29 -23
- package/src/utils/file-lock.ts +5 -3
- package/src/utils/io.ts +5 -2
- package/src/utils/nlp.ts +5 -46
- package/src/utils/node-vm-polyfill.ts +11 -0
- package/src/utils/plugin-logger.ts +2 -0
- package/src/utils/retry.ts +572 -0
- package/src/utils/subagent-probe.ts +1 -1
- package/templates/langs/en/core/AGENTS.md +0 -13
- package/templates/langs/en/core/SOUL.md +1 -31
- package/templates/langs/en/core/TOOLS.md +0 -4
- package/templates/langs/en/principles/THINKING_OS.md +77 -0
- package/templates/langs/en/skills/admin/SKILL.md +0 -1
- package/templates/langs/en/skills/evolution-framework-update/SKILL.md +1 -1
- package/templates/langs/en/skills/pd-diagnostician/SKILL.md +18 -5
- package/templates/langs/zh/core/AGENTS.md +0 -22
- package/templates/langs/zh/core/SOUL.md +1 -31
- package/templates/langs/zh/core/TOOLS.md +0 -4
- package/templates/langs/zh/principles/THINKING_OS.md +77 -0
- package/templates/langs/zh/skills/admin/SKILL.md +0 -1
- package/templates/langs/zh/skills/evolution-framework-update/SKILL.md +1 -1
- package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +25 -4
- package/tests/commands/evolution-status.test.ts +119 -0
- package/tests/commands/implementation-lifecycle.test.ts +362 -0
- package/tests/core/code-implementation-storage.test.ts +398 -0
- package/tests/core/evolution-reducer.detector-metadata.test.ts +28 -28
- package/tests/core/nocturnal-artifact-lineage.test.ts +53 -0
- package/tests/core/nocturnal-artificer.test.ts +241 -0
- package/tests/core/nocturnal-compliance-p-principles.test.ts +133 -0
- package/tests/core/nocturnal-rule-implementation-validator.test.ts +127 -0
- package/tests/core/pd-task-store.test.ts +126 -0
- package/tests/core/principle-internalization/deprecated-readiness.test.ts +193 -0
- package/tests/core/principle-internalization/internalization-routing-policy.test.ts +212 -0
- package/tests/core/principle-internalization/lifecycle-metrics.test.ts +350 -0
- package/tests/core/principle-internalization/principle-lifecycle-service.test.ts +211 -0
- package/tests/core/principle-training-state.test.ts +228 -1
- package/tests/core/principle-tree-ledger.test.ts +423 -0
- package/tests/core/regression-v1-9-1.test.ts +265 -0
- package/tests/core/replay-engine.test.ts +234 -0
- package/tests/core/rule-host-helpers.test.ts +120 -0
- package/tests/core/rule-host.test.ts +389 -0
- package/tests/core/rule-implementation-runtime.test.ts +64 -0
- package/tests/core/workspace-context.test.ts +53 -0
- package/tests/core/workspace-dir-validation.test.ts +272 -0
- package/tests/hooks/gate-rule-host-pipeline.test.ts +385 -0
- package/tests/hooks/pain.test.ts +74 -10
- package/tests/hooks/prompt.test.ts +63 -1
- package/tests/integration/principle-lifecycle.e2e.test.ts +197 -0
- package/tests/integration/tool-hooks-workspace-dir.e2e.test.ts +211 -0
- package/tests/service/data-endpoints-regression.test.ts +834 -0
- package/tests/service/evolution-worker.test.ts +0 -123
- package/tests/service/nocturnal-service-code-candidate.test.ts +330 -0
- package/tests/utils/nlp.test.ts +1 -19
- package/tests/utils/retry.test.ts +327 -0
- package/ui/src/App.tsx +1 -1
- package/ui/src/api.ts +4 -0
- package/ui/src/charts.tsx +366 -0
- package/ui/src/components/WorkspaceConfig.tsx +107 -75
- package/ui/src/i18n/ui.ts +89 -31
- package/ui/src/pages/EvolutionPage.tsx +1 -1
- package/ui/src/pages/OverviewPage.tsx +441 -81
- package/ui/src/pages/ThinkingModelsPage.tsx +287 -69
- package/ui/src/styles.css +43 -0
- package/ui/src/types.ts +17 -1
- package/src/agents/nocturnal-dreamer.md +0 -152
- package/src/agents/nocturnal-philosopher.md +0 -138
- package/src/agents/nocturnal-reflector.md +0 -126
- package/src/agents/nocturnal-scribe.md +0 -164
- package/templates/workspace/.principles/00-kernel.md +0 -51
- package/templates/workspace/.principles/DECISION_POLICY.json +0 -44
- package/templates/workspace/.principles/PRINCIPLES.md +0 -20
- package/templates/workspace/.principles/PROFILE.json +0 -54
- package/templates/workspace/.principles/PROFILE.schema.json +0 -56
- package/templates/workspace/.principles/THINKING_OS.md +0 -64
- package/templates/workspace/.principles/THINKING_OS_ARCHIVE.md +0 -7
- package/templates/workspace/.principles/THINKING_OS_CANDIDATES.md +0 -9
- package/templates/workspace/.principles/models/_INDEX.md +0 -27
- package/templates/workspace/.principles/models/first_principles.md +0 -62
- package/templates/workspace/.principles/models/marketing_4p.md +0 -52
- package/templates/workspace/.principles/models/porter_five.md +0 -63
- package/templates/workspace/.principles/models/swot.md +0 -60
- package/templates/workspace/.principles/models/user_story_map.md +0 -63
- package/templates/workspace/.state/WORKBOARD.json +0 -4
|
@@ -34,40 +34,40 @@ export interface ControlUiDatabaseOptions {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
export interface RecentThinkingContext {
|
|
37
|
-
toolCalls:
|
|
37
|
+
toolCalls: {
|
|
38
38
|
id: number;
|
|
39
39
|
toolName: string;
|
|
40
40
|
outcome: 'success' | 'failure' | 'blocked';
|
|
41
41
|
errorType: string | null;
|
|
42
42
|
errorMessage: string | null;
|
|
43
43
|
createdAt: string;
|
|
44
|
-
}
|
|
45
|
-
painEvents:
|
|
44
|
+
}[];
|
|
45
|
+
painEvents: {
|
|
46
46
|
id: number;
|
|
47
47
|
source: string;
|
|
48
48
|
score: number;
|
|
49
49
|
reason: string | null;
|
|
50
50
|
createdAt: string;
|
|
51
|
-
}
|
|
52
|
-
gateBlocks:
|
|
51
|
+
}[];
|
|
52
|
+
gateBlocks: {
|
|
53
53
|
id: number;
|
|
54
54
|
toolName: string;
|
|
55
55
|
reason: string;
|
|
56
56
|
filePath: string | null;
|
|
57
57
|
createdAt: string;
|
|
58
|
-
}
|
|
59
|
-
userCorrections:
|
|
58
|
+
}[];
|
|
59
|
+
userCorrections: {
|
|
60
60
|
id: number;
|
|
61
61
|
correctionCue: string | null;
|
|
62
62
|
rawExcerpt: string | null;
|
|
63
63
|
createdAt: string;
|
|
64
|
-
}
|
|
65
|
-
principleEvents:
|
|
64
|
+
}[];
|
|
65
|
+
principleEvents: {
|
|
66
66
|
id: number;
|
|
67
67
|
principleId: string | null;
|
|
68
68
|
eventType: string;
|
|
69
69
|
createdAt: string;
|
|
70
|
-
}
|
|
70
|
+
}[];
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
const DEFAULT_BUSY_TIMEOUT_MS = 5000;
|
|
@@ -274,6 +274,25 @@ export class ControlUiDatabase {
|
|
|
274
274
|
return this.db.prepare(sql).get(...params) as T | undefined;
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
+
/**
|
|
278
|
+
* Execute SQL statement that does not return rows (DDL, CREATE TABLE, etc.).
|
|
279
|
+
*
|
|
280
|
+
* Returns: void (executes directly)
|
|
281
|
+
* Not for: SELECT queries (use all() or get() instead)
|
|
282
|
+
*/
|
|
283
|
+
execute(sql: string): void {
|
|
284
|
+
this.db.exec(sql);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Execute a parameterized write statement (INSERT, UPDATE, DELETE).
|
|
289
|
+
*/
|
|
290
|
+
run(sql: string, ...params: unknown[]): void {
|
|
291
|
+
this.withWrite(() => {
|
|
292
|
+
this.db.prepare(sql).run(...params);
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
277
296
|
restoreRawText(inlineText?: string | null, blobRef?: string | null): string {
|
|
278
297
|
if (inlineText) return inlineText;
|
|
279
298
|
if (!blobRef) return '';
|
|
@@ -12,9 +12,11 @@ export interface DetectionResult {
|
|
|
12
12
|
* A simple LRU Cache implementation using Map.
|
|
13
13
|
*/
|
|
14
14
|
class SimpleLRU<K, V> {
|
|
15
|
-
private cache: Map<K, V>;
|
|
16
|
-
|
|
15
|
+
private readonly cache: Map<K, V>;
|
|
16
|
+
private readonly maxSize: number;
|
|
17
|
+
constructor(maxSize = 100) {
|
|
17
18
|
this.cache = new Map();
|
|
19
|
+
this.maxSize = maxSize;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
get(key: K): V | undefined {
|
|
@@ -45,10 +47,13 @@ class SimpleLRU<K, V> {
|
|
|
45
47
|
* Orchestrates the three-layer detection funnel for pain signals.
|
|
46
48
|
*/
|
|
47
49
|
export class DetectionFunnel {
|
|
48
|
-
private cache = new SimpleLRU<string, { detected: boolean; severity?: number }>(100);
|
|
50
|
+
private readonly cache = new SimpleLRU<string, { detected: boolean; severity?: number }>(100);
|
|
49
51
|
private asyncQueue: string[] = [];
|
|
52
|
+
private readonly dictionary: PainDictionary;
|
|
50
53
|
|
|
51
|
-
constructor(
|
|
54
|
+
constructor(dictionary: PainDictionary) {
|
|
55
|
+
this.dictionary = dictionary;
|
|
56
|
+
}
|
|
52
57
|
|
|
53
58
|
/**
|
|
54
59
|
* Detects pain in the given text using L1 (Exact), L2 (Cache), and L3 (Async).
|
|
@@ -70,7 +75,7 @@ export class DetectionFunnel {
|
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
// --- Layer 2: LRU Cache (Sync) ---
|
|
73
|
-
const hash =
|
|
78
|
+
const hash = DetectionFunnel.computeHash(text);
|
|
74
79
|
const cached = this.cache.get(hash);
|
|
75
80
|
if (cached) {
|
|
76
81
|
return {
|
|
@@ -89,7 +94,7 @@ export class DetectionFunnel {
|
|
|
89
94
|
};
|
|
90
95
|
}
|
|
91
96
|
|
|
92
|
-
private computeHash(text: string): string {
|
|
97
|
+
private static computeHash(text: string): string {
|
|
93
98
|
return createHash('sha256').update(text).digest('hex');
|
|
94
99
|
}
|
|
95
100
|
|
|
@@ -104,7 +109,7 @@ export class DetectionFunnel {
|
|
|
104
109
|
* Internal method for the worker to update the cache after a semantic hit.
|
|
105
110
|
*/
|
|
106
111
|
updateCache(text: string, result: { detected: boolean; severity?: number }): void {
|
|
107
|
-
const hash =
|
|
112
|
+
const hash = DetectionFunnel.computeHash(text);
|
|
108
113
|
this.cache.set(hash, result);
|
|
109
114
|
}
|
|
110
115
|
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diagnostician Task Store
|
|
3
|
+
*
|
|
4
|
+
* Manages diagnostician task prompts stored in `.state/diagnostician_tasks.json`.
|
|
5
|
+
* This replaces the previous HEARTBEAT.md approach which had a race condition:
|
|
6
|
+
* the main session heartbeat would overwrite HEARTBEAT.md while the diagnostician
|
|
7
|
+
* was still running, causing the task prompt to be lost.
|
|
8
|
+
*
|
|
9
|
+
* This store uses file-level locking via withLockAsync to prevent concurrent writes.
|
|
10
|
+
*
|
|
11
|
+
* Design:
|
|
12
|
+
* - Each task is written once by the evolution worker (when pain is detected)
|
|
13
|
+
* - The prompt hook reads tasks during heartbeat triggers and injects them
|
|
14
|
+
* - The diagnostician agent completes the task and writes marker files
|
|
15
|
+
* - The worker detects marker files and extracts principles
|
|
16
|
+
*
|
|
17
|
+
* File format:
|
|
18
|
+
* {
|
|
19
|
+
* "tasks": {
|
|
20
|
+
* "<task-id>": {
|
|
21
|
+
* "prompt": "...full diagnostician prompt...",
|
|
22
|
+
* "createdAt": "ISO timestamp",
|
|
23
|
+
* "status": "pending" | "completed"
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
* }
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import * as fs from 'fs';
|
|
30
|
+
import * as path from 'path';
|
|
31
|
+
import { withLockAsync } from '../utils/file-lock.js';
|
|
32
|
+
|
|
33
|
+
const DIAGNOSTICIAN_TASKS_FILE = 'diagnostician_tasks.json';
|
|
34
|
+
|
|
35
|
+
export interface DiagnosticianTask {
|
|
36
|
+
prompt: string;
|
|
37
|
+
createdAt: string;
|
|
38
|
+
status: 'pending' | 'completed';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface DiagnosticianTaskStore {
|
|
42
|
+
tasks: Record<string, DiagnosticianTask>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Resolve the diagnostician tasks file path.
|
|
47
|
+
*/
|
|
48
|
+
function resolveTasksPath(stateDir: string): string {
|
|
49
|
+
return path.join(stateDir, DIAGNOSTICIAN_TASKS_FILE);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Read the diagnostician task store from disk.
|
|
54
|
+
* Returns an empty store if the file doesn't exist or is malformed.
|
|
55
|
+
*/
|
|
56
|
+
function readTaskStore(stateDir: string): DiagnosticianTaskStore {
|
|
57
|
+
const filePath = resolveTasksPath(stateDir);
|
|
58
|
+
if (!fs.existsSync(filePath)) {
|
|
59
|
+
return { tasks: {} };
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
63
|
+
const parsed = JSON.parse(raw);
|
|
64
|
+
if (parsed && typeof parsed.tasks === 'object') {
|
|
65
|
+
return parsed as DiagnosticianTaskStore;
|
|
66
|
+
}
|
|
67
|
+
return { tasks: {} };
|
|
68
|
+
} catch {
|
|
69
|
+
return { tasks: {} };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Add a new diagnostician task to the store.
|
|
75
|
+
* Overwrites if a task with the same ID already exists.
|
|
76
|
+
* Read-modify-write is performed atomically inside the file lock.
|
|
77
|
+
*/
|
|
78
|
+
export async function addDiagnosticianTask(
|
|
79
|
+
stateDir: string,
|
|
80
|
+
taskId: string,
|
|
81
|
+
prompt: string,
|
|
82
|
+
): Promise<void> {
|
|
83
|
+
const filePath = resolveTasksPath(stateDir);
|
|
84
|
+
await withLockAsync(filePath, async () => {
|
|
85
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: function defined later in file, hoisting pattern
|
|
86
|
+
const store = readTaskStoreSync(filePath);
|
|
87
|
+
store.tasks[taskId] = {
|
|
88
|
+
prompt,
|
|
89
|
+
createdAt: new Date().toISOString(),
|
|
90
|
+
status: 'pending',
|
|
91
|
+
};
|
|
92
|
+
const tmpPath = filePath + '.tmp';
|
|
93
|
+
fs.writeFileSync(tmpPath, JSON.stringify(store, null, 2), 'utf8');
|
|
94
|
+
fs.renameSync(tmpPath, filePath);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Mark a task as completed and remove it from the store.
|
|
100
|
+
* Read-modify-write is performed atomically inside the file lock.
|
|
101
|
+
*/
|
|
102
|
+
export async function completeDiagnosticianTask(
|
|
103
|
+
stateDir: string,
|
|
104
|
+
taskId: string,
|
|
105
|
+
): Promise<void> {
|
|
106
|
+
const filePath = resolveTasksPath(stateDir);
|
|
107
|
+
await withLockAsync(filePath, async () => {
|
|
108
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: function defined later in file, hoisting pattern
|
|
109
|
+
const store = readTaskStoreSync(filePath);
|
|
110
|
+
delete store.tasks[taskId];
|
|
111
|
+
const tmpPath = filePath + '.tmp';
|
|
112
|
+
fs.writeFileSync(tmpPath, JSON.stringify(store, null, 2), 'utf8');
|
|
113
|
+
fs.renameSync(tmpPath, filePath);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Synchronous read without lock — for use INSIDE a lock context.
|
|
119
|
+
*/
|
|
120
|
+
|
|
121
|
+
function readTaskStoreSync(filePath: string): DiagnosticianTaskStore {
|
|
122
|
+
if (!fs.existsSync(filePath)) {
|
|
123
|
+
return { tasks: {} };
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
127
|
+
const parsed = JSON.parse(raw);
|
|
128
|
+
if (parsed && typeof parsed.tasks === 'object') {
|
|
129
|
+
return parsed as DiagnosticianTaskStore;
|
|
130
|
+
}
|
|
131
|
+
return { tasks: {} };
|
|
132
|
+
} catch {
|
|
133
|
+
return { tasks: {} };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Get all pending diagnostician tasks.
|
|
139
|
+
* Returns an array of [taskId, task] pairs.
|
|
140
|
+
*/
|
|
141
|
+
export function getPendingDiagnosticianTasks(
|
|
142
|
+
stateDir: string,
|
|
143
|
+
): { id: string; task: DiagnosticianTask }[] {
|
|
144
|
+
const store = readTaskStore(stateDir);
|
|
145
|
+
return Object.entries(store.tasks)
|
|
146
|
+
.filter(([, task]) => task.status === 'pending')
|
|
147
|
+
.map(([id, task]) => ({ id, task }));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Check if there are any pending diagnostician tasks.
|
|
152
|
+
*/
|
|
153
|
+
export function hasPendingDiagnosticianTasks(stateDir: string): boolean {
|
|
154
|
+
const store = readTaskStore(stateDir);
|
|
155
|
+
return Object.values(store.tasks).some(t => t.status === 'pending');
|
|
156
|
+
}
|
package/src/core/dictionary.ts
CHANGED
|
@@ -62,10 +62,10 @@ const DEFAULT_RULES: Record<string, PainRule> = {
|
|
|
62
62
|
|
|
63
63
|
export class PainDictionary {
|
|
64
64
|
private data: PainDictionaryData = { rules: {} };
|
|
65
|
-
private filePath: string;
|
|
66
|
-
private compiledRegex: Map<string, RegExp> = new Map();
|
|
65
|
+
private readonly filePath: string;
|
|
66
|
+
private readonly compiledRegex: Map<string, RegExp> = new Map();
|
|
67
67
|
|
|
68
|
-
constructor(private stateDir: string) {
|
|
68
|
+
constructor(private readonly stateDir: string) {
|
|
69
69
|
this.filePath = path.join(stateDir, 'pain_dictionary.json');
|
|
70
70
|
}
|
|
71
71
|
|
|
@@ -73,7 +73,7 @@ export class PainDictionary {
|
|
|
73
73
|
if (fs.existsSync(this.filePath)) {
|
|
74
74
|
try {
|
|
75
75
|
this.data = JSON.parse(fs.readFileSync(this.filePath, 'utf8'));
|
|
76
|
-
} catch
|
|
76
|
+
} catch {
|
|
77
77
|
console.error('[PD] Failed to parse pain_dictionary.json, using defaults.');
|
|
78
78
|
this.data = { rules: { ...DEFAULT_RULES } };
|
|
79
79
|
}
|
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
|
|
15
15
|
import * as fs from 'fs';
|
|
16
16
|
import * as path from 'path';
|
|
17
|
-
import {
|
|
17
|
+
import type {
|
|
18
18
|
EmpathyKeywordStore,
|
|
19
19
|
EmpathyKeywordEntry,
|
|
20
20
|
EmpathyKeywordStats,
|
|
21
21
|
EmpathyMatchResult,
|
|
22
|
-
EmpathyKeywordConfig
|
|
23
|
-
|
|
22
|
+
EmpathyKeywordConfig} from './empathy-types.js';
|
|
23
|
+
import {
|
|
24
24
|
EMPATHY_SEED_KEYWORDS,
|
|
25
25
|
DEFAULT_EMPATHY_KEYWORD_CONFIG,
|
|
26
26
|
scoreToSeverity,
|
|
@@ -80,6 +80,7 @@ export function loadKeywordStore(stateDir: string, language?: 'zh' | 'en'): Empa
|
|
|
80
80
|
try {
|
|
81
81
|
if (!fs.existsSync(filePath)) {
|
|
82
82
|
const store = createDefaultKeywordStore(language);
|
|
83
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: function defined later in file, hoisting pattern
|
|
83
84
|
saveKeywordStore(stateDir, store);
|
|
84
85
|
return store;
|
|
85
86
|
}
|
|
@@ -91,6 +92,7 @@ export function loadKeywordStore(stateDir: string, language?: 'zh' | 'en'): Empa
|
|
|
91
92
|
if (!parsed.terms || !parsed.stats || !parsed.version) {
|
|
92
93
|
console.warn('[PD:Empathy] Invalid keyword store format, creating default');
|
|
93
94
|
const store = createDefaultKeywordStore(language);
|
|
95
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: function defined later in file, hoisting pattern
|
|
94
96
|
saveKeywordStore(stateDir, store);
|
|
95
97
|
return store;
|
|
96
98
|
}
|
|
@@ -99,6 +101,7 @@ export function loadKeywordStore(stateDir: string, language?: 'zh' | 'en'): Empa
|
|
|
99
101
|
} catch (e) {
|
|
100
102
|
console.warn(`[PD:Empathy] Failed to load keyword store: ${e}`);
|
|
101
103
|
const store = createDefaultKeywordStore(language);
|
|
104
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define -- Reason: function defined later in file, hoisting pattern
|
|
102
105
|
saveKeywordStore(stateDir, store);
|
|
103
106
|
return store;
|
|
104
107
|
}
|
|
@@ -107,6 +110,7 @@ export function loadKeywordStore(stateDir: string, language?: 'zh' | 'en'): Empa
|
|
|
107
110
|
/**
|
|
108
111
|
* Saves the keyword store to disk.
|
|
109
112
|
*/
|
|
113
|
+
|
|
110
114
|
export function saveKeywordStore(stateDir: string, store: EmpathyKeywordStore): void {
|
|
111
115
|
const filePath = path.join(stateDir, KEYWORD_STORE_FILE);
|
|
112
116
|
const dir = path.dirname(filePath);
|
|
@@ -178,8 +178,8 @@ export interface EmpathyKeywordConfig {
|
|
|
178
178
|
export const DEFAULT_EMPATHY_KEYWORD_CONFIG: EmpathyKeywordConfig = {
|
|
179
179
|
matchThreshold: 0.3,
|
|
180
180
|
maxTermsPerMessage: 5,
|
|
181
|
-
optimizationIntervalTurns:
|
|
182
|
-
optimizationIntervalMs:
|
|
181
|
+
optimizationIntervalTurns: 50,
|
|
182
|
+
optimizationIntervalMs: 6 * 60 * 60 * 1000, // 6 hours
|
|
183
183
|
penaltyMild: 10,
|
|
184
184
|
penaltyModerate: 25,
|
|
185
185
|
penaltySevere: 40,
|
|
@@ -216,3 +216,14 @@ export function severityToPenalty(
|
|
|
216
216
|
case 'severe': return config.penaltySevere;
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Normalizes various severity string inputs to the canonical empathy severity type.
|
|
222
|
+
* Handles common aliases: 'high' → 'severe', 'medium' → 'moderate'.
|
|
223
|
+
*/
|
|
224
|
+
export function normalizeSeverity(input?: string): 'mild' | 'moderate' | 'severe' {
|
|
225
|
+
const normalized = (input || '').toLowerCase();
|
|
226
|
+
if (normalized === 'severe' || normalized === 'high') return 'severe';
|
|
227
|
+
if (normalized === 'moderate' || normalized === 'medium') return 'moderate';
|
|
228
|
+
return 'mild';
|
|
229
|
+
}
|
package/src/core/event-log.ts
CHANGED
|
@@ -29,7 +29,7 @@ export class EventLog {
|
|
|
29
29
|
private readonly statsFile: string;
|
|
30
30
|
private readonly logger?: PluginLogger;
|
|
31
31
|
|
|
32
|
-
private statsCache: Map<string, DailyStats> = new Map();
|
|
32
|
+
private readonly statsCache: Map<string, DailyStats> = new Map();
|
|
33
33
|
private eventBuffer: EventLogEntry[] = [];
|
|
34
34
|
private readonly maxBufferSize = 20;
|
|
35
35
|
private readonly flushIntervalMs = 30000;
|
|
@@ -66,9 +66,12 @@ export class EventLog {
|
|
|
66
66
|
this.record('rule_promotion', 'promoted', undefined, data);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
recordHookExecution(data: HookExecutionEventData): void {
|
|
69
|
+
recordHookExecution(data: HookExecutionEventData, opts?: { flushImmediately?: boolean }): void {
|
|
70
70
|
const category = data.error ? 'failure' : 'success';
|
|
71
71
|
this.record('hook_execution', category, undefined, data);
|
|
72
|
+
if (opts?.flushImmediately) {
|
|
73
|
+
this.flushEvents();
|
|
74
|
+
}
|
|
72
75
|
}
|
|
73
76
|
|
|
74
77
|
recordGateBlock(sessionId: string | undefined, data: GateBlockEventData): void {
|
|
@@ -104,6 +107,7 @@ export class EventLog {
|
|
|
104
107
|
this.record('warn', 'failure', sessionId, { message, ...context });
|
|
105
108
|
}
|
|
106
109
|
|
|
110
|
+
// eslint-disable-next-line @typescript-eslint/max-params -- Reason: event record requires type + category + session + data - refactoring would break internal API
|
|
107
111
|
private record(
|
|
108
112
|
type: EventType,
|
|
109
113
|
category: EventCategory,
|
|
@@ -130,6 +134,7 @@ export class EventLog {
|
|
|
130
134
|
}
|
|
131
135
|
}
|
|
132
136
|
|
|
137
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this -- Reason: utility method doesn't require this - pure function
|
|
133
138
|
private formatDate(date: Date): string {
|
|
134
139
|
return date.toISOString().split('T')[0];
|
|
135
140
|
}
|
|
@@ -155,7 +160,8 @@ export class EventLog {
|
|
|
155
160
|
}
|
|
156
161
|
|
|
157
162
|
if (entry.type === 'tool_call') {
|
|
158
|
-
|
|
163
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars -- Reason: data used for type narrowing only, actual fields accessed via stats
|
|
164
|
+
const _data = entry.data as unknown as ToolCallEventData;
|
|
159
165
|
stats.tools.total++;
|
|
160
166
|
if (entry.category === 'success') stats.tools.success++;
|
|
161
167
|
else stats.tools.failure++;
|
|
@@ -237,6 +243,7 @@ export class EventLog {
|
|
|
237
243
|
return this.eventBuffer.map((entry) => ({ ...entry, data: { ...entry.data } }));
|
|
238
244
|
}
|
|
239
245
|
|
|
246
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this -- Reason: getEventDedupKey is a pure key-generation function, no this reference needed
|
|
240
247
|
private getEventDedupKey(entry: EventLogEntry): string {
|
|
241
248
|
const eventId = typeof (entry.data as { eventId?: unknown } | undefined)?.eventId === 'string'
|
|
242
249
|
? String((entry.data as { eventId?: string }).eventId)
|
|
@@ -452,6 +459,7 @@ export class EventLog {
|
|
|
452
459
|
* Rollback an empathy event by ID.
|
|
453
460
|
* Returns the rolled back score, or 0 if event not found.
|
|
454
461
|
*/
|
|
462
|
+
// eslint-disable-next-line @typescript-eslint/max-params -- Reason: rollback requires eventId + sessionId + reason + triggeredBy - refactoring would break API
|
|
455
463
|
rollbackEmpathyEvent(eventId: string, sessionId: string | undefined, reason: string, triggeredBy: 'user_command' | 'natural_language' | 'system'): number {
|
|
456
464
|
const allEvents = this.getMergedEvents();
|
|
457
465
|
let foundEvent: { entry: EventLogEntry; data: PainSignalEventData } | null = null;
|
|
@@ -459,7 +467,7 @@ export class EventLog {
|
|
|
459
467
|
for (const entry of allEvents) {
|
|
460
468
|
if (entry.type === 'pain_signal') {
|
|
461
469
|
const data = entry.data as unknown as PainSignalEventData;
|
|
462
|
-
if (
|
|
470
|
+
if (data.eventId === eventId && data.source === 'user_empathy') {
|
|
463
471
|
foundEvent = { entry, data };
|
|
464
472
|
break;
|
|
465
473
|
}
|
|
@@ -494,7 +502,7 @@ export class EventLog {
|
|
|
494
502
|
if (entry.sessionId === sessionId && entry.type === 'pain_signal') {
|
|
495
503
|
const data = entry.data as unknown as PainSignalEventData;
|
|
496
504
|
if (data.source === 'user_empathy' && !data.deduped) {
|
|
497
|
-
return
|
|
505
|
+
return data.eventId || null;
|
|
498
506
|
}
|
|
499
507
|
}
|
|
500
508
|
}
|
|
@@ -518,7 +526,7 @@ export class EventLog {
|
|
|
518
526
|
* Service to manage multiple EventLog instances by stateDir.
|
|
519
527
|
*/
|
|
520
528
|
export class EventLogService {
|
|
521
|
-
private static instances: Map<string, EventLog> = new Map();
|
|
529
|
+
private static readonly instances: Map<string, EventLog> = new Map();
|
|
522
530
|
|
|
523
531
|
static get(stateDir: string, logger?: PluginLogger): EventLog {
|
|
524
532
|
let instance = this.instances.get(stateDir);
|
|
@@ -21,19 +21,17 @@ import {
|
|
|
21
21
|
HIGH_RISK_TOOLS,
|
|
22
22
|
} from '../constants/tools.js';
|
|
23
23
|
|
|
24
|
-
import {
|
|
25
|
-
EvolutionTier,
|
|
24
|
+
import type {
|
|
26
25
|
EvolutionEvent,
|
|
27
26
|
EvolutionScorecard,
|
|
28
27
|
EvolutionStats,
|
|
29
28
|
EvolutionConfig,
|
|
30
|
-
EvolutionStorage,
|
|
31
29
|
TaskDifficulty,
|
|
32
30
|
TierDefinition,
|
|
33
|
-
TierPermissions,
|
|
34
31
|
GateDecision,
|
|
35
|
-
ToolCallContext
|
|
36
|
-
|
|
32
|
+
ToolCallContext} from './evolution-types.js';
|
|
33
|
+
import {
|
|
34
|
+
EvolutionTier,
|
|
37
35
|
DEFAULT_EVOLUTION_CONFIG,
|
|
38
36
|
TIER_DEFINITIONS,
|
|
39
37
|
TASK_DIFFICULTY_CONFIG,
|
|
@@ -44,11 +42,11 @@ import {
|
|
|
44
42
|
// ===== 主引擎 =====
|
|
45
43
|
|
|
46
44
|
export class EvolutionEngine {
|
|
47
|
-
private scorecard: EvolutionScorecard;
|
|
48
|
-
private workspaceDir: string;
|
|
49
|
-
private stateDir: string;
|
|
50
|
-
private config: EvolutionConfig;
|
|
51
|
-
private storagePath: string;
|
|
45
|
+
private readonly scorecard: EvolutionScorecard;
|
|
46
|
+
private readonly workspaceDir: string;
|
|
47
|
+
private readonly stateDir: string;
|
|
48
|
+
private readonly config: EvolutionConfig;
|
|
49
|
+
private readonly storagePath: string;
|
|
52
50
|
|
|
53
51
|
constructor(workspaceDir: string, config?: Partial<EvolutionConfig>) {
|
|
54
52
|
this.workspaceDir = workspaceDir;
|
|
@@ -123,14 +121,14 @@ export class EvolutionEngine {
|
|
|
123
121
|
): { pointsAwarded: number; isDoubleReward: boolean; newTier?: EvolutionTier } {
|
|
124
122
|
// 探索性工具成功不给积分,只重置失败记录
|
|
125
123
|
if (EXPLORATORY_TOOLS.has(toolName)) {
|
|
126
|
-
const taskHash =
|
|
124
|
+
const taskHash = EvolutionEngine.computeTaskHash(toolName, options?.filePath);
|
|
127
125
|
this.scorecard.recentFailureHashes.delete(taskHash);
|
|
128
126
|
this.saveScorecard();
|
|
129
127
|
return { pointsAwarded: 0, isDoubleReward: false };
|
|
130
128
|
}
|
|
131
129
|
|
|
132
|
-
const difficulty = options?.difficulty ||
|
|
133
|
-
const taskHash =
|
|
130
|
+
const difficulty = options?.difficulty || EvolutionEngine.inferDifficulty(toolName);
|
|
131
|
+
const taskHash = EvolutionEngine.computeTaskHash(toolName, options?.filePath);
|
|
134
132
|
|
|
135
133
|
// 计算积分
|
|
136
134
|
let points = this.calculatePoints(difficulty, taskHash);
|
|
@@ -178,14 +176,14 @@ export class EvolutionEngine {
|
|
|
178
176
|
): { pointsAwarded: number; lessonRecorded: boolean } {
|
|
179
177
|
// 探索性工具失败:记录但几乎不影响
|
|
180
178
|
if (EXPLORATORY_TOOLS.has(toolName)) {
|
|
181
|
-
const event = this.createEvent('failure',
|
|
179
|
+
const event = this.createEvent('failure', EvolutionEngine.computeTaskHash(toolName, options?.filePath), 'trivial', toolName, options?.filePath, options?.reason, 0, false, options?.sessionId);
|
|
182
180
|
this.addEvent(event);
|
|
183
181
|
this.saveScorecard();
|
|
184
182
|
return { pointsAwarded: 0, lessonRecorded: true };
|
|
185
183
|
}
|
|
186
184
|
|
|
187
|
-
const difficulty = options?.difficulty ||
|
|
188
|
-
const taskHash =
|
|
185
|
+
const difficulty = options?.difficulty || EvolutionEngine.inferDifficulty(toolName);
|
|
186
|
+
const taskHash = EvolutionEngine.computeTaskHash(toolName, options?.filePath);
|
|
189
187
|
|
|
190
188
|
// 失败不扣分,但记录教训(用于后续双倍奖励)
|
|
191
189
|
this.scorecard.recentFailureHashes.set(taskHash, new Date().toISOString());
|
|
@@ -244,7 +242,7 @@ export class EvolutionEngine {
|
|
|
244
242
|
// ===== 积分计算 =====
|
|
245
243
|
|
|
246
244
|
private calculatePoints(difficulty: TaskDifficulty, taskHash: string): number {
|
|
247
|
-
const basePoints = TASK_DIFFICULTY_CONFIG[difficulty]
|
|
245
|
+
const {basePoints} = TASK_DIFFICULTY_CONFIG[difficulty];
|
|
248
246
|
|
|
249
247
|
// 难度衰减
|
|
250
248
|
const penalty = this.getDifficultyPenalty(difficulty);
|
|
@@ -300,15 +298,6 @@ export class EvolutionEngine {
|
|
|
300
298
|
this.scorecard.currentTier = newTier;
|
|
301
299
|
this.scorecard.stats.tierPromotions++;
|
|
302
300
|
|
|
303
|
-
// 记录升级事件
|
|
304
|
-
const promotionEvent: TierPromotionEvent = {
|
|
305
|
-
previousTier,
|
|
306
|
-
newTier,
|
|
307
|
-
totalPoints: this.scorecard.totalPoints,
|
|
308
|
-
timestamp: new Date().toISOString(),
|
|
309
|
-
newPermissions: getTierDefinition(newTier).permissions,
|
|
310
|
-
};
|
|
311
|
-
|
|
312
301
|
console.log(`[Evolution] 🎉 Tier promotion: ${previousTier} → ${newTier} (${getTierDefinition(newTier).name})`);
|
|
313
302
|
|
|
314
303
|
return newTier;
|
|
@@ -319,7 +308,7 @@ export class EvolutionEngine {
|
|
|
319
308
|
|
|
320
309
|
// ===== 任务难度推断 =====
|
|
321
310
|
|
|
322
|
-
private inferDifficulty(toolName: string): TaskDifficulty {
|
|
311
|
+
private static inferDifficulty(toolName: string): TaskDifficulty {
|
|
323
312
|
if (HIGH_RISK_TOOLS.has(toolName)) return 'hard';
|
|
324
313
|
if (CONSTRUCTIVE_TOOLS.has(toolName)) return 'normal';
|
|
325
314
|
return 'trivial';
|
|
@@ -327,13 +316,14 @@ export class EvolutionEngine {
|
|
|
327
316
|
|
|
328
317
|
// ===== 任务哈希 =====
|
|
329
318
|
|
|
330
|
-
private computeTaskHash(toolName: string, filePath?: string): string {
|
|
319
|
+
private static computeTaskHash(toolName: string, filePath?: string): string {
|
|
331
320
|
const normalizedPath = filePath ? path.normalize(filePath) : '_nofile';
|
|
332
321
|
return `${toolName}:${normalizedPath}`;
|
|
333
322
|
}
|
|
334
323
|
|
|
335
324
|
// ===== 事件管理 =====
|
|
336
325
|
|
|
326
|
+
// eslint-disable-next-line @typescript-eslint/max-params -- Reason: event creation requires all event fields - refactoring would break internal structure
|
|
337
327
|
private createEvent(
|
|
338
328
|
type: 'success' | 'failure',
|
|
339
329
|
taskHash: string,
|
|
@@ -395,6 +385,7 @@ export class EvolutionEngine {
|
|
|
395
385
|
return this.createNewScorecard();
|
|
396
386
|
}
|
|
397
387
|
|
|
388
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this -- Reason: createNewScorecard is a factory function, no this reference needed
|
|
398
389
|
private createNewScorecard(): EvolutionScorecard {
|
|
399
390
|
const now = new Date().toISOString();
|
|
400
391
|
return {
|
|
@@ -453,12 +444,15 @@ export class EvolutionEngine {
|
|
|
453
444
|
} catch (e) {
|
|
454
445
|
console.error(`[Evolution] Failed to save scorecard: ${String(e)}`);
|
|
455
446
|
this.scheduleRetrySave();
|
|
456
|
-
|
|
447
|
+
/* eslint-disable @typescript-eslint/no-unused-vars, no-unused-vars -- Reason: intentionally ignored - cleanup failure should not mask original error */
|
|
448
|
+
try { fs.unlinkSync(tempPath); } catch (_e) {
|
|
449
|
+
// Cleanup failure intentionally ignored - should not mask original error
|
|
450
|
+
}
|
|
457
451
|
}
|
|
458
452
|
}
|
|
459
453
|
|
|
460
454
|
/** Per-instance retry queue (P0 fix: was static, causing cross-instance race) */
|
|
461
|
-
private retryQueue:
|
|
455
|
+
private retryQueue: { engine: EvolutionEngine; data: Partial<EvolutionScorecard> }[] = [];
|
|
462
456
|
private retryTimer: ReturnType<typeof setTimeout> | null = null;
|
|
463
457
|
|
|
464
458
|
/** 调度重试保存 */
|
|
@@ -533,6 +527,7 @@ export class EvolutionEngine {
|
|
|
533
527
|
|
|
534
528
|
// ===== 工具方法 =====
|
|
535
529
|
|
|
530
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this -- Reason: generateId is a pure utility function, no this reference needed
|
|
536
531
|
private generateId(): string {
|
|
537
532
|
return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
538
533
|
}
|
|
@@ -557,6 +552,7 @@ export function getEvolutionEngine(workspaceDir: string): EvolutionEngine {
|
|
|
557
552
|
if (!_instances.has(resolved)) {
|
|
558
553
|
_instances.set(resolved, new EvolutionEngine(resolved));
|
|
559
554
|
}
|
|
555
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- Reason: set() above guarantees get() returns non-null
|
|
560
556
|
return _instances.get(resolved)!;
|
|
561
557
|
}
|
|
562
558
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* 2. evolution_events 表 (SQLite) - 结构化查询
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import { randomBytes } from 'crypto';
|
|
14
14
|
import type { TrajectoryDatabase } from './trajectory.js';
|
|
15
15
|
import { SystemLogger } from './system-logger.js';
|
|
16
16
|
|
|
@@ -131,7 +131,7 @@ export class EvolutionLogger {
|
|
|
131
131
|
});
|
|
132
132
|
} catch (err) {
|
|
133
133
|
// Database write failure doesn't affect main flow, but log for diagnostics
|
|
134
|
-
|
|
134
|
+
|
|
135
135
|
console.error(`[EvolutionLogger] Failed to write to trajectory: ${String(err)}`);
|
|
136
136
|
}
|
|
137
137
|
}
|
|
@@ -264,6 +264,7 @@ export class EvolutionLogger {
|
|
|
264
264
|
durationMs?: number;
|
|
265
265
|
principlesGenerated?: number;
|
|
266
266
|
}): void {
|
|
267
|
+
// eslint-disable-next-line @typescript-eslint/init-declarations -- assigned in all if/else branches
|
|
267
268
|
let summary: string;
|
|
268
269
|
if (params.resolution === 'marker_detected' || params.resolution === 'late_marker_principle_created') {
|
|
269
270
|
summary = `任务 ${params.taskId} 完成,已生成 ${params.principlesGenerated || 0} 条原则`;
|