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
|
@@ -85,14 +85,63 @@ export class HealthQueryService {
|
|
|
85
85
|
this.eventLog = wctx.eventLog;
|
|
86
86
|
this.evolutionReducer = wctx.evolutionReducer;
|
|
87
87
|
this.uiDb = new ControlUiDatabase({ workspaceDir });
|
|
88
|
+
this.ensureTables();
|
|
89
|
+
this.syncGfiFromSession();
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
dispose(): void {
|
|
91
93
|
this.uiDb.dispose();
|
|
92
94
|
}
|
|
93
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Ensure all tables required by HealthQueryService exist.
|
|
98
|
+
* This is a safety net for workspaces created before TrajectoryDatabase
|
|
99
|
+
* had full schema initialization, or where initSchema() was never called.
|
|
100
|
+
* Uses CREATE TABLE IF NOT EXISTS so it's idempotent.
|
|
101
|
+
*/
|
|
102
|
+
private ensureTables(): void {
|
|
103
|
+
try {
|
|
104
|
+
this.uiDb.execute(`
|
|
105
|
+
CREATE TABLE IF NOT EXISTS pain_events (
|
|
106
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
107
|
+
session_id TEXT NOT NULL,
|
|
108
|
+
source TEXT NOT NULL,
|
|
109
|
+
score REAL NOT NULL DEFAULT 0,
|
|
110
|
+
reason TEXT DEFAULT '',
|
|
111
|
+
severity TEXT DEFAULT 'mild',
|
|
112
|
+
origin TEXT DEFAULT '',
|
|
113
|
+
confidence REAL DEFAULT 0,
|
|
114
|
+
text TEXT DEFAULT '',
|
|
115
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
116
|
+
)
|
|
117
|
+
`);
|
|
118
|
+
this.uiDb.execute(`
|
|
119
|
+
CREATE INDEX IF NOT EXISTS idx_pain_events_created_at ON pain_events(created_at)
|
|
120
|
+
`);
|
|
121
|
+
this.uiDb.execute(`
|
|
122
|
+
CREATE TABLE IF NOT EXISTS gate_blocks (
|
|
123
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
124
|
+
session_id TEXT NOT NULL,
|
|
125
|
+
tool_name TEXT NOT NULL,
|
|
126
|
+
file_path TEXT,
|
|
127
|
+
reason TEXT NOT NULL,
|
|
128
|
+
gfi REAL,
|
|
129
|
+
gfi_after REAL,
|
|
130
|
+
trust_stage INTEGER,
|
|
131
|
+
gate_type TEXT,
|
|
132
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
133
|
+
)
|
|
134
|
+
`);
|
|
135
|
+
this.uiDb.execute(`
|
|
136
|
+
CREATE INDEX IF NOT EXISTS idx_gate_blocks_created_at ON gate_blocks(created_at)
|
|
137
|
+
`);
|
|
138
|
+
} catch (err) {
|
|
139
|
+
console.warn('[HealthQueryService] Table ensure failed (non-fatal):', err);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
94
143
|
getOverviewHealth(): {
|
|
95
|
-
gfi: { current: number; peakToday: number; threshold: number };
|
|
144
|
+
gfi: { current: number; peakToday: number; threshold: number; trend: { hour: string; value: number }[] };
|
|
96
145
|
trust: { stage: number; stageLabel: string; score: number };
|
|
97
146
|
evolution: { tier: string; points: number };
|
|
98
147
|
painFlag: { active: boolean; source: string | null; score: number | null };
|
|
@@ -100,7 +149,6 @@ export class HealthQueryService {
|
|
|
100
149
|
queue: { pending: number; inProgress: number; completed: number };
|
|
101
150
|
activeStage: HealthStage;
|
|
102
151
|
} {
|
|
103
|
-
const session = this.getCurrentSession();
|
|
104
152
|
const threshold = this.getGfiThreshold();
|
|
105
153
|
const trust = this.readTrust();
|
|
106
154
|
const evolution = this.readEvolutionScore();
|
|
@@ -108,14 +156,21 @@ export class HealthQueryService {
|
|
|
108
156
|
const queue = this.readQueueStats();
|
|
109
157
|
const painFlag = this.readPainFlag();
|
|
110
158
|
|
|
111
|
-
|
|
112
|
-
const
|
|
159
|
+
// GFI: always read from SQLite (synced from session JSON at construction time)
|
|
160
|
+
const gfiData = this.readGfiFromDb();
|
|
161
|
+
const {currentGfi} = gfiData;
|
|
162
|
+
const peakToday = gfiData.dailyGfiPeak;
|
|
163
|
+
|
|
164
|
+
// GFI history trend: aggregate pain_events by hour
|
|
165
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
166
|
+
const gfiTrend = this.readGfiTrend(today);
|
|
113
167
|
|
|
114
168
|
return {
|
|
115
169
|
gfi: {
|
|
116
170
|
current: currentGfi,
|
|
117
171
|
peakToday,
|
|
118
172
|
threshold,
|
|
173
|
+
trend: gfiTrend,
|
|
119
174
|
},
|
|
120
175
|
trust,
|
|
121
176
|
evolution,
|
|
@@ -127,14 +182,14 @@ export class HealthQueryService {
|
|
|
127
182
|
deprecated: reducerStats.deprecatedCount,
|
|
128
183
|
},
|
|
129
184
|
queue,
|
|
130
|
-
activeStage:
|
|
185
|
+
activeStage: HealthQueryService.computeHealthStage(currentGfi, threshold, painFlag.active),
|
|
131
186
|
};
|
|
132
187
|
}
|
|
133
188
|
|
|
134
189
|
getEvolutionPrinciples(): {
|
|
135
190
|
principles: {
|
|
136
191
|
summary: { candidate: number; probation: number; active: number; deprecated: number };
|
|
137
|
-
recent:
|
|
192
|
+
recent: {
|
|
138
193
|
principleId: string;
|
|
139
194
|
status: string;
|
|
140
195
|
triggerPattern: string;
|
|
@@ -142,14 +197,14 @@ export class HealthQueryService {
|
|
|
142
197
|
fromStatus: string;
|
|
143
198
|
toStatus: string;
|
|
144
199
|
timestamp: string;
|
|
145
|
-
}
|
|
200
|
+
}[];
|
|
146
201
|
};
|
|
147
202
|
nocturnalTraining: {
|
|
148
203
|
queue: { pending: number; inProgress: number; completed: number };
|
|
149
|
-
trinityRecords:
|
|
204
|
+
trinityRecords: { artifactId: string; status: string; createdAt: string }[];
|
|
150
205
|
arbiterPassRate: number;
|
|
151
206
|
orpoSampleCount: number;
|
|
152
|
-
deployments:
|
|
207
|
+
deployments: { modelId: string; status: string; checkpointPath: string | null }[];
|
|
153
208
|
};
|
|
154
209
|
painSourceDistribution: Record<string, number>;
|
|
155
210
|
activeStage: string;
|
|
@@ -176,14 +231,33 @@ export class HealthQueryService {
|
|
|
176
231
|
};
|
|
177
232
|
}
|
|
178
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Read GFI trend for a specific day by aggregating pain_events by hour.
|
|
236
|
+
* Used by getOverviewHealth() to provide historical GFI context.
|
|
237
|
+
*/
|
|
238
|
+
private readGfiTrend(date: string): { hour: string; value: number }[] {
|
|
239
|
+
try {
|
|
240
|
+
const rows = this.uiDb.all<{ hour: string; value: number }>(`
|
|
241
|
+
SELECT substr(created_at, 1, 13) || ':00:00Z' AS hour, ROUND(SUM(score), 2) AS value
|
|
242
|
+
FROM pain_events
|
|
243
|
+
WHERE substr(created_at, 1, 10) = ?
|
|
244
|
+
GROUP BY substr(created_at, 1, 13)
|
|
245
|
+
ORDER BY hour ASC
|
|
246
|
+
`, date);
|
|
247
|
+
return rows.map(row => ({ hour: row.hour, value: this.asNumber(row.value, 0) }));
|
|
248
|
+
} catch {
|
|
249
|
+
// pain_events table may not exist in this workspace — return empty trend
|
|
250
|
+
return [];
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
179
254
|
getFeedbackGfi(): {
|
|
180
255
|
current: number;
|
|
181
256
|
peakToday: number;
|
|
182
257
|
threshold: number;
|
|
183
|
-
trend:
|
|
258
|
+
trend: { hour: string; value: number }[];
|
|
184
259
|
sources: Record<string, number>;
|
|
185
260
|
} {
|
|
186
|
-
const session = this.getCurrentSession();
|
|
187
261
|
const threshold = this.getGfiThreshold();
|
|
188
262
|
const today = new Date().toISOString().slice(0, 10);
|
|
189
263
|
|
|
@@ -203,23 +277,26 @@ export class HealthQueryService {
|
|
|
203
277
|
ORDER BY total DESC
|
|
204
278
|
`, today);
|
|
205
279
|
|
|
280
|
+
// GFI: read from SQLite (synced from session JSON at construction time)
|
|
281
|
+
const gfiData = this.readGfiFromDb();
|
|
282
|
+
|
|
206
283
|
return {
|
|
207
|
-
current:
|
|
208
|
-
peakToday:
|
|
284
|
+
current: gfiData.currentGfi,
|
|
285
|
+
peakToday: gfiData.dailyGfiPeak,
|
|
209
286
|
threshold,
|
|
210
287
|
trend: trendRows.map((row) => ({ hour: row.hour, value: this.asNumber(row.value, 0) })),
|
|
211
288
|
sources: Object.fromEntries(sourceRows.map((row) => [row.source, this.asNumber(row.total, 0)])),
|
|
212
289
|
};
|
|
213
290
|
}
|
|
214
291
|
|
|
215
|
-
getFeedbackEmpathyEvents(limit
|
|
292
|
+
getFeedbackEmpathyEvents(limit = 50): {
|
|
216
293
|
timestamp: string;
|
|
217
294
|
severity: string;
|
|
218
295
|
score: number;
|
|
219
296
|
reason: string;
|
|
220
297
|
origin: string;
|
|
221
298
|
gfiAfter: number;
|
|
222
|
-
}
|
|
299
|
+
}[] {
|
|
223
300
|
const safeLimit = Math.max(1, Math.min(500, Math.floor(limit)));
|
|
224
301
|
const events = this.readMergedEvents()
|
|
225
302
|
.filter((entry) => entry.type === 'pain_signal' && String(entry.data?.source ?? '') === 'user_empathy')
|
|
@@ -239,13 +316,13 @@ export class HealthQueryService {
|
|
|
239
316
|
});
|
|
240
317
|
}
|
|
241
318
|
|
|
242
|
-
getFeedbackGateBlocks(limit
|
|
319
|
+
getFeedbackGateBlocks(limit = 50): {
|
|
243
320
|
timestamp: string;
|
|
244
321
|
toolName: string;
|
|
245
322
|
reason: string;
|
|
246
323
|
gfi: number;
|
|
247
324
|
trustStage: number;
|
|
248
|
-
}
|
|
325
|
+
}[] {
|
|
249
326
|
const trust = this.readTrust();
|
|
250
327
|
const rows = this.readGateBlocksRaw(limit);
|
|
251
328
|
|
|
@@ -315,7 +392,7 @@ export class HealthQueryService {
|
|
|
315
392
|
};
|
|
316
393
|
}
|
|
317
394
|
|
|
318
|
-
getGateBlocks(limit
|
|
395
|
+
getGateBlocks(limit = 50): {
|
|
319
396
|
timestamp: string;
|
|
320
397
|
toolName: string;
|
|
321
398
|
filePath: string | null;
|
|
@@ -323,7 +400,7 @@ export class HealthQueryService {
|
|
|
323
400
|
gateType: string;
|
|
324
401
|
gfi: number;
|
|
325
402
|
trustStage: number;
|
|
326
|
-
}
|
|
403
|
+
}[] {
|
|
327
404
|
const trust = this.readTrust();
|
|
328
405
|
const rows = this.readGateBlocksRaw(limit);
|
|
329
406
|
|
|
@@ -347,13 +424,13 @@ export class HealthQueryService {
|
|
|
347
424
|
);
|
|
348
425
|
const rawStage = this.asNumber(
|
|
349
426
|
scorecard.trustStage ?? scorecard.trust_stage ?? scorecard.stage,
|
|
350
|
-
|
|
427
|
+
HealthQueryService.inferTrustStageFromScore(score),
|
|
351
428
|
);
|
|
352
429
|
const stage = Math.max(1, Math.min(4, Math.round(rawStage)));
|
|
353
430
|
|
|
354
431
|
return {
|
|
355
432
|
stage,
|
|
356
|
-
stageLabel:
|
|
433
|
+
stageLabel: HealthQueryService.getTrustStageLabel(stage),
|
|
357
434
|
score,
|
|
358
435
|
};
|
|
359
436
|
}
|
|
@@ -397,14 +474,20 @@ export class HealthQueryService {
|
|
|
397
474
|
}
|
|
398
475
|
|
|
399
476
|
private getCurrentSession(): SessionState | null {
|
|
477
|
+
// First, try in-memory sessions (live sessions from session-tracker)
|
|
400
478
|
const sessions = listSessions(this.workspaceDir);
|
|
401
|
-
if (sessions.length
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
479
|
+
if (sessions.length > 0) {
|
|
480
|
+
const sorted = [...sessions].sort((a, b) => {
|
|
481
|
+
const aTs = Number(a.lastControlActivityAt ?? a.lastActivityAt ?? 0);
|
|
482
|
+
const bTs = Number(b.lastControlActivityAt ?? b.lastActivityAt ?? 0);
|
|
483
|
+
return bTs - aTs;
|
|
484
|
+
});
|
|
485
|
+
return sorted[0] ?? null;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Fallback: read session JSON files directly (handles process restarts where
|
|
489
|
+
// loadAllSessions may not have reloaded all sessions, or workspaceDir mismatch)
|
|
490
|
+
return this.readLatestSessionFromFile();
|
|
408
491
|
}
|
|
409
492
|
|
|
410
493
|
private getGfiThreshold(): number {
|
|
@@ -418,7 +501,7 @@ export class HealthQueryService {
|
|
|
418
501
|
return fallback ?? 70;
|
|
419
502
|
}
|
|
420
503
|
|
|
421
|
-
private computeHealthStage(currentGfi: number, threshold: number, painFlagActive: boolean): HealthStage {
|
|
504
|
+
private static computeHealthStage(currentGfi: number, threshold: number, painFlagActive: boolean): HealthStage {
|
|
422
505
|
if (painFlagActive || currentGfi >= threshold) {
|
|
423
506
|
return 'critical';
|
|
424
507
|
}
|
|
@@ -428,7 +511,7 @@ export class HealthQueryService {
|
|
|
428
511
|
return 'healthy';
|
|
429
512
|
}
|
|
430
513
|
|
|
431
|
-
private getTrustStageLabel(stage: number): string {
|
|
514
|
+
private static getTrustStageLabel(stage: number): string {
|
|
432
515
|
switch (stage) {
|
|
433
516
|
case 1:
|
|
434
517
|
return 'Observer';
|
|
@@ -443,7 +526,7 @@ export class HealthQueryService {
|
|
|
443
526
|
}
|
|
444
527
|
}
|
|
445
528
|
|
|
446
|
-
private inferTrustStageFromScore(score: number): number {
|
|
529
|
+
private static inferTrustStageFromScore(score: number): number {
|
|
447
530
|
if (score >= 80) return 4;
|
|
448
531
|
if (score >= 60) return 3;
|
|
449
532
|
if (score >= 30) return 2;
|
|
@@ -468,6 +551,7 @@ export class HealthQueryService {
|
|
|
468
551
|
const streamPath = resolvePdPath(this.workspaceDir, 'EVOLUTION_STREAM');
|
|
469
552
|
if (!fs.existsSync(streamPath)) return [];
|
|
470
553
|
|
|
554
|
+
// eslint-disable-next-line no-useless-assignment -- Reason: initial value unused due to immediate reassignment
|
|
471
555
|
let lines: string[] = [];
|
|
472
556
|
try {
|
|
473
557
|
const raw = fs.readFileSync(streamPath, 'utf8').trim();
|
|
@@ -479,6 +563,7 @@ export class HealthQueryService {
|
|
|
479
563
|
|
|
480
564
|
const records: RecentPrincipleChange[] = [];
|
|
481
565
|
for (const line of lines) {
|
|
566
|
+
// eslint-disable-next-line no-useless-assignment -- Reason: initial value unused due to immediate reassignment in try/catch
|
|
482
567
|
let event: EvolutionStreamRecord | null = null;
|
|
483
568
|
try {
|
|
484
569
|
event = JSON.parse(line) as EvolutionStreamRecord;
|
|
@@ -572,10 +657,10 @@ export class HealthQueryService {
|
|
|
572
657
|
|
|
573
658
|
private readNocturnalTraining(): {
|
|
574
659
|
queue: { pending: number; inProgress: number; completed: number };
|
|
575
|
-
trinityRecords:
|
|
660
|
+
trinityRecords: { artifactId: string; status: string; createdAt: string }[];
|
|
576
661
|
arbiterPassRate: number;
|
|
577
662
|
orpoSampleCount: number;
|
|
578
|
-
deployments:
|
|
663
|
+
deployments: { modelId: string; status: string; checkpointPath: string | null }[];
|
|
579
664
|
} {
|
|
580
665
|
const sampleDir = resolvePdPath(this.workspaceDir, 'NOCTURNAL_SAMPLES_DIR');
|
|
581
666
|
const exportDir = resolvePdPath(this.workspaceDir, 'NOCTURNAL_EXPORTS_DIR');
|
|
@@ -696,6 +781,7 @@ export class HealthQueryService {
|
|
|
696
781
|
return [];
|
|
697
782
|
}
|
|
698
783
|
|
|
784
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this -- Reason: Private helper doesn't use instance state
|
|
699
785
|
private getEventDedupKey(entry: EventLogEntry): string {
|
|
700
786
|
const eventId = typeof entry.data?.eventId === 'string' ? entry.data.eventId : null;
|
|
701
787
|
if (eventId) {
|
|
@@ -743,7 +829,7 @@ export class HealthQueryService {
|
|
|
743
829
|
if (direct !== null) return direct;
|
|
744
830
|
|
|
745
831
|
const reason = String(row.reason ?? '');
|
|
746
|
-
const match =
|
|
832
|
+
const match = /gfi\s*[:=]\s*(-?\d+(?:\.\d+)?)/i.exec(reason);
|
|
747
833
|
if (match) {
|
|
748
834
|
return this.asNumber(Number(match[1]), 0);
|
|
749
835
|
}
|
|
@@ -757,7 +843,7 @@ export class HealthQueryService {
|
|
|
757
843
|
if (direct !== null) return Math.max(1, Math.min(4, Math.round(direct)));
|
|
758
844
|
|
|
759
845
|
const reason = String(row.reason ?? '').toLowerCase();
|
|
760
|
-
const match =
|
|
846
|
+
const match = /stage\s*(\d+)/i.exec(reason);
|
|
761
847
|
if (match) {
|
|
762
848
|
return Math.max(1, Math.min(4, Math.round(this.asNumber(Number(match[1]), fallbackStage))));
|
|
763
849
|
}
|
|
@@ -765,6 +851,7 @@ export class HealthQueryService {
|
|
|
765
851
|
return fallbackStage;
|
|
766
852
|
}
|
|
767
853
|
|
|
854
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this -- Reason: Private helper doesn't use instance state
|
|
768
855
|
private resolveGateType(row: GateBlockRow): string {
|
|
769
856
|
if (typeof row.gate_type === 'string' && row.gate_type.trim().length > 0) {
|
|
770
857
|
return row.gate_type;
|
|
@@ -788,12 +875,14 @@ export class HealthQueryService {
|
|
|
788
875
|
return cached.has(columnName);
|
|
789
876
|
}
|
|
790
877
|
|
|
878
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this -- Reason: Private helper doesn't use instance state
|
|
791
879
|
private scoreToStatus(score: number): string {
|
|
792
880
|
if (score >= 70) return 'healthy';
|
|
793
881
|
if (score >= 40) return 'warning';
|
|
794
882
|
return 'critical';
|
|
795
883
|
}
|
|
796
884
|
|
|
885
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this -- Reason: Private helper doesn't use instance state
|
|
797
886
|
private evolutionToStatus(tier: string, points: number): string {
|
|
798
887
|
const lower = tier.toLowerCase();
|
|
799
888
|
if (lower === 'forest' || lower === 'tree') return 'healthy';
|
|
@@ -801,7 +890,8 @@ export class HealthQueryService {
|
|
|
801
890
|
return 'critical';
|
|
802
891
|
}
|
|
803
892
|
|
|
804
|
-
|
|
893
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this, no-unused-vars -- Reason: Private helper doesn't use instance state
|
|
894
|
+
private safeListFiles(dirPath: string, predicate: (_name: string) => boolean): string[] {
|
|
805
895
|
if (!fs.existsSync(dirPath)) return [];
|
|
806
896
|
try {
|
|
807
897
|
return fs.readdirSync(dirPath)
|
|
@@ -812,6 +902,7 @@ export class HealthQueryService {
|
|
|
812
902
|
}
|
|
813
903
|
}
|
|
814
904
|
|
|
905
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this -- Reason: Private helper doesn't use instance state
|
|
815
906
|
private readJsonFile<T>(filePath: string, fallback: T): T {
|
|
816
907
|
if (!fs.existsSync(filePath)) return fallback;
|
|
817
908
|
try {
|
|
@@ -821,10 +912,12 @@ export class HealthQueryService {
|
|
|
821
912
|
}
|
|
822
913
|
}
|
|
823
914
|
|
|
915
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this -- Reason: Private helper doesn't use instance state
|
|
824
916
|
private asNumber(value: unknown, fallback: number): number {
|
|
825
917
|
return Number.isFinite(value) ? Number(value) : fallback;
|
|
826
918
|
}
|
|
827
919
|
|
|
920
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this -- Reason: Private helper doesn't use instance state
|
|
828
921
|
private asNullableNumber(value: unknown): number | null {
|
|
829
922
|
if (Number.isFinite(value)) return Number(value);
|
|
830
923
|
if (typeof value === 'string' && value.trim().length > 0) {
|
|
@@ -833,4 +926,88 @@ export class HealthQueryService {
|
|
|
833
926
|
}
|
|
834
927
|
return null;
|
|
835
928
|
}
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* Sync GFI from the latest session JSON file into SQLite.
|
|
932
|
+
* Called at construction time so all queries read from a single source (SQLite).
|
|
933
|
+
*/
|
|
934
|
+
private syncGfiFromSession(): void {
|
|
935
|
+
const session = this.readLatestSessionFromFile();
|
|
936
|
+
const currentGfi = this.asNumber(session?.currentGfi, 0);
|
|
937
|
+
const dailyGfiPeak = this.asNumber(session?.dailyGfiPeak, currentGfi);
|
|
938
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
939
|
+
|
|
940
|
+
try {
|
|
941
|
+
this.uiDb.execute(`
|
|
942
|
+
CREATE TABLE IF NOT EXISTS gfi_state (
|
|
943
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
944
|
+
current_gfi REAL NOT NULL DEFAULT 0,
|
|
945
|
+
daily_gfi_peak REAL NOT NULL DEFAULT 0,
|
|
946
|
+
gfi_date TEXT NOT NULL DEFAULT ''
|
|
947
|
+
)
|
|
948
|
+
`);
|
|
949
|
+
this.uiDb.run(
|
|
950
|
+
'INSERT OR REPLACE INTO gfi_state (id, current_gfi, daily_gfi_peak, gfi_date) VALUES (1, ?, ?, ?)',
|
|
951
|
+
currentGfi,
|
|
952
|
+
dailyGfiPeak,
|
|
953
|
+
today,
|
|
954
|
+
);
|
|
955
|
+
} catch (err) {
|
|
956
|
+
console.warn('[HealthQueryService] Failed to sync GFI from session:', err);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/**
|
|
961
|
+
* Read current GFI state from SQLite.
|
|
962
|
+
*/
|
|
963
|
+
private readGfiFromDb(): { currentGfi: number; dailyGfiPeak: number } {
|
|
964
|
+
try {
|
|
965
|
+
const row = this.uiDb.get<{ current_gfi: number; daily_gfi_peak: number }>(
|
|
966
|
+
'SELECT current_gfi, daily_gfi_peak FROM gfi_state WHERE id = 1',
|
|
967
|
+
);
|
|
968
|
+
if (!row) return { currentGfi: 0, dailyGfiPeak: 0 };
|
|
969
|
+
return {
|
|
970
|
+
currentGfi: row.current_gfi ?? 0,
|
|
971
|
+
dailyGfiPeak: row.daily_gfi_peak ?? 0,
|
|
972
|
+
};
|
|
973
|
+
} catch {
|
|
974
|
+
return { currentGfi: 0, dailyGfiPeak: 0 };
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
/**
|
|
979
|
+
* Read the most recent session JSON file from disk.
|
|
980
|
+
* Used to sync GFI from session-tracker's persistence into SQLite.
|
|
981
|
+
*/
|
|
982
|
+
private readLatestSessionFromFile(): SessionState | null {
|
|
983
|
+
const sessionsDir = path.join(this.stateDir, 'sessions');
|
|
984
|
+
if (!fs.existsSync(sessionsDir)) return null;
|
|
985
|
+
|
|
986
|
+
try {
|
|
987
|
+
const files = fs.readdirSync(sessionsDir).filter(f => f.endsWith('.json'));
|
|
988
|
+
if (files.length === 0) return null;
|
|
989
|
+
|
|
990
|
+
let latest: SessionState | null = null;
|
|
991
|
+
let latestTs = 0;
|
|
992
|
+
|
|
993
|
+
for (const file of files) {
|
|
994
|
+
try {
|
|
995
|
+
const content = fs.readFileSync(path.join(sessionsDir, file), 'utf-8');
|
|
996
|
+
const state = JSON.parse(content) as SessionState;
|
|
997
|
+
if (state.workspaceDir && state.workspaceDir !== this.workspaceDir) continue;
|
|
998
|
+
const ts = Number(state.lastControlActivityAt ?? state.lastActivityAt ?? 0);
|
|
999
|
+
if (ts > latestTs) {
|
|
1000
|
+
latestTs = ts;
|
|
1001
|
+
latest = state;
|
|
1002
|
+
}
|
|
1003
|
+
} catch {
|
|
1004
|
+
// Skip corrupted files
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
return latest;
|
|
1009
|
+
} catch {
|
|
1010
|
+
return null;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
836
1013
|
}
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
|
|
21
21
|
import * as fs from 'fs';
|
|
22
22
|
import * as path from 'path';
|
|
23
|
-
import {
|
|
23
|
+
import type { SessionState } from '../core/session-tracker.js';
|
|
24
|
+
import { listSessions } from '../core/session-tracker.js';
|
|
24
25
|
import { withLockAsync } from '../utils/file-lock.js';
|
|
25
26
|
|
|
26
27
|
// ---------------------------------------------------------------------------
|
|
@@ -303,6 +304,7 @@ export function checkWorkspaceIdle(
|
|
|
303
304
|
trajectoryGuardrailConfirmsIdle = trajectoryIdleFor > idleThresholdMs * 0.8;
|
|
304
305
|
}
|
|
305
306
|
|
|
307
|
+
// eslint-disable-next-line @typescript-eslint/init-declarations -- assigned in all if/else branches
|
|
306
308
|
let reason: string;
|
|
307
309
|
if (mostRecentActivityAt === 0) {
|
|
308
310
|
reason = 'No active sessions found — workspace is idle';
|
|
@@ -350,8 +352,9 @@ export function checkCooldown(
|
|
|
350
352
|
} = {}
|
|
351
353
|
): CooldownCheckResult {
|
|
352
354
|
const {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
+
/* eslint-disable @typescript-eslint/no-unused-vars, no-unused-vars -- Reason: Cooldown parameters reserved for future quota enforcement */
|
|
356
|
+
globalCooldownMs: _globalCooldownMs = DEFAULT_GLOBAL_COOLDOWN_MS,
|
|
357
|
+
principleCooldownMs: _principleCooldownMs = DEFAULT_PRINCIPLE_COOLDOWN_MS,
|
|
355
358
|
maxRunsPerWindow = DEFAULT_MAX_RUNS_PER_WINDOW,
|
|
356
359
|
quotaWindowMs = DEFAULT_QUOTA_WINDOW_MS,
|
|
357
360
|
} = options;
|
|
@@ -369,7 +372,7 @@ export function checkCooldown(
|
|
|
369
372
|
if (cooldownEnd > now) {
|
|
370
373
|
globalCooldownActive = true;
|
|
371
374
|
globalCooldownRemainingMs = cooldownEnd - now;
|
|
372
|
-
globalCooldownUntil = state.globalCooldownUntil;
|
|
375
|
+
globalCooldownUntil = state.globalCooldownUntil; // eslint-disable-line @typescript-eslint/prefer-destructuring -- Reason: globalCooldownUntil is reassignable outer let - destructuring would shadow
|
|
373
376
|
}
|
|
374
377
|
}
|
|
375
378
|
|
|
@@ -537,6 +540,7 @@ export interface PreflightCheckResult {
|
|
|
537
540
|
* @param trajectoryLastActivityAt - Optional trajectory timestamp as secondary guardrail
|
|
538
541
|
* @param idleCheckOverride - Optional override for idle check result (for testing)
|
|
539
542
|
*/
|
|
543
|
+
/* eslint-disable @typescript-eslint/max-params -- Reason: Preflight check requires workspace, state, principleId, trajectory timestamp, and idle override */
|
|
540
544
|
export function checkPreflight(
|
|
541
545
|
workspaceDir: string,
|
|
542
546
|
stateDir: string,
|