principles-disciple 1.71.0 → 1.73.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/openclaw.plugin.json +10 -5
- package/package.json +17 -19
- package/scripts/acceptance-test.mjs +16 -73
- package/scripts/sync-plugin.mjs +382 -77
- package/src/commands/archive-impl.ts +2 -1
- package/src/commands/capabilities.ts +2 -2
- package/src/commands/context.ts +2 -2
- package/src/commands/disable-impl.ts +2 -1
- package/src/commands/evolution-status.ts +16 -16
- package/src/commands/export.ts +12 -67
- package/src/commands/pain.ts +91 -1
- package/src/commands/principle-rollback.ts +2 -1
- package/src/commands/promote-impl.ts +7 -43
- package/src/commands/rollback-impl.ts +2 -1
- package/src/commands/rollback.ts +2 -1
- package/src/commands/samples.ts +2 -1
- package/src/commands/thinking-os.ts +2 -1
- package/src/config/errors.ts +18 -2
- package/src/constants/diagnostician.ts +2 -2
- package/src/constants/tools.ts +2 -1
- package/src/core/__tests__/focus-history.test.ts +210 -0
- package/src/core/config.ts +1 -1
- package/src/core/confirm-first-gate.ts +255 -0
- package/src/core/correction-cue-learner.ts +2 -136
- package/src/core/correction-types.ts +16 -88
- package/src/core/dictionary.ts +19 -20
- package/src/core/empathy-keyword-matcher.ts +17 -289
- package/src/core/empathy-types.ts +18 -229
- package/src/core/event-log.ts +38 -132
- package/src/core/evolution-reducer.ts +21 -2
- package/src/core/evolution-types.ts +76 -464
- package/src/core/file-store.ts +80 -0
- package/src/core/focus-history.ts +228 -955
- package/src/core/local-worker-routing.ts +34 -314
- package/src/core/merge-gate-audit.ts +0 -195
- package/src/core/pain-diagnostic-gate.ts +154 -0
- package/src/core/pain-signal.ts +21 -138
- package/src/core/pain.ts +15 -88
- package/src/core/pd-task-reconciler.ts +26 -115
- package/src/core/pd-task-service.ts +9 -9
- package/src/core/pd-task-types.ts +23 -127
- package/src/core/principle-compiler/__tests__/compiler-replay-gate.test.ts +174 -0
- package/src/core/principle-compiler/code-validator.ts +15 -42
- package/src/core/principle-compiler/compiler.ts +100 -15
- package/src/core/principle-compiler/index.ts +5 -2
- package/src/core/principle-compiler/template-generator.ts +4 -104
- package/src/core/principle-injection.ts +10 -202
- package/src/core/principle-internalization/filesystem-lifecycle-datasource.ts +42 -0
- package/src/core/principle-internalization/lifecycle-read-model.ts +39 -242
- package/src/core/principle-internalization/principle-lifecycle-service.ts +12 -10
- package/src/core/principle-tree-ledger-adapter.ts +145 -0
- package/src/core/principle-tree-ledger.ts +8 -6
- package/src/core/reflection/reflection-context.ts +14 -109
- package/src/core/replay-engine.ts +8 -500
- package/src/core/rule-host-helpers.ts +5 -35
- package/src/core/rule-host-types.ts +10 -82
- package/src/core/rule-host.ts +6 -63
- package/src/core/runtime-v2-prompt-activation-reader.ts +231 -0
- package/src/core/session-tracker.ts +87 -101
- package/src/core/shadow-observation-registry.ts +19 -48
- package/src/core/trajectory.ts +3 -1
- package/src/core/workflow-funnel-loader.ts +62 -68
- package/src/core/workspace-context.ts +46 -0
- package/src/core/workspace-dir-service.ts +1 -1
- package/src/core/workspace-dir-validation.ts +18 -9
- package/src/hooks/AGENTS.md +1 -1
- package/src/hooks/gate-block-helper.ts +46 -44
- package/src/hooks/gate.ts +207 -7
- package/src/hooks/lifecycle.ts +30 -32
- package/src/hooks/llm.ts +60 -32
- package/src/hooks/pain.ts +297 -103
- package/src/hooks/prompt.ts +469 -339
- package/src/hooks/subagent.ts +2 -29
- package/src/i18n/commands.ts +2 -10
- package/src/index.ts +95 -85
- package/src/openclaw-sdk.ts +311 -0
- package/src/service/central-database.ts +8 -4
- package/src/service/evolution-queue-migration.ts +2 -1
- package/src/service/evolution-worker.ts +163 -1786
- package/src/service/internalization-trigger-adapter.ts +302 -0
- package/src/service/keyword-optimization-service.ts +4 -4
- package/src/service/monitoring-query-service.ts +1 -215
- package/src/service/queue-io.ts +60 -331
- package/src/service/runtime-summary-service.ts +115 -18
- package/src/service/subagent-workflow/index.ts +0 -41
- package/src/service/subagent-workflow/types.ts +9 -120
- package/src/service/subagent-workflow/workflow-store.ts +2 -119
- package/src/service/workflow-watchdog.ts +0 -43
- package/src/types/event-payload.ts +16 -74
- package/src/types/event-types.ts +39 -547
- package/src/types/hygiene-types.ts +7 -30
- package/src/types/principle-tree-schema.ts +20 -222
- package/src/types/queue.ts +15 -70
- package/src/types/runtime-summary.ts +5 -49
- package/src/utils/io.ts +10 -0
- package/src/utils/retry.ts +1 -1
- package/src/utils/shadow-fingerprint.ts +2 -2
- package/src/utils/workspace-resolver.ts +50 -0
- package/templates/langs/en/core/AGENTS.md +2 -2
- package/templates/langs/en/core/BOOT.md +1 -1
- package/templates/langs/en/core/HEARTBEAT.md +2 -2
- package/templates/langs/en/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
- package/templates/langs/en/skills/ai-sprint-orchestration/runtime/.gitignore +2 -2
- package/templates/langs/en/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
- package/templates/langs/en/skills/evolve-task/SKILL.md +1 -1
- package/templates/langs/en/skills/pd-cli-operator/SKILL.md +67 -0
- package/templates/langs/en/skills/pd-diagnostician/SKILL.md +1 -1
- package/templates/langs/en/skills/pd-mentor/SKILL.md +1 -1
- package/templates/langs/en/skills/pd-pain-signal/SKILL.md +17 -39
- package/templates/langs/en/skills/pd-runtime-v2/SKILL.md +61 -0
- package/templates/langs/zh/core/AGENTS.md +2 -2
- package/templates/langs/zh/core/BOOT.md +1 -1
- package/templates/langs/zh/core/HEARTBEAT.md +2 -2
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/nocturnal-trinity-quality-enhancement.json +8 -8
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
- package/templates/langs/zh/skills/ai-sprint-orchestration/runtime/.gitignore +2 -2
- package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
- package/templates/langs/zh/skills/ai-sprint-orchestration/test/run.test.mjs +21 -5
- package/templates/langs/zh/skills/evolve-task/SKILL.md +2 -2
- package/templates/langs/zh/skills/pd-cli-operator/SKILL.md +67 -0
- package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +1 -1
- package/templates/langs/zh/skills/pd-mentor/SKILL.md +1 -1
- package/templates/langs/zh/skills/pd-pain-signal/SKILL.md +17 -38
- package/templates/langs/zh/skills/pd-runtime-v2/SKILL.md +61 -0
- package/tests/build-artifacts.test.ts +1 -3
- package/tests/commands/evolution-status.test.ts +0 -118
- package/tests/core/bootstrap-rules.test.ts +1 -1
- package/tests/core/config.test.ts +1 -1
- package/tests/core/event-log.test.ts +35 -0
- package/tests/core/evolution-engine.test.ts +610 -0
- package/tests/core/file-store.test.ts +102 -0
- package/tests/core/focus-history.test.ts +203 -11
- package/tests/core/merge-gate-audit.test.ts +2 -169
- package/tests/core/model-deployment-registry.test.ts +7 -1
- package/tests/core/model-training-registry.test.ts +19 -0
- package/tests/core/observability.test.ts +0 -1
- package/tests/core/pain-diagnostic-gate.test.ts +498 -0
- package/tests/core/pain.test.ts +0 -1
- package/tests/core/principle-internalization/deprecated-readiness.test.ts +2 -2
- package/tests/core/principle-internalization/lifecycle-metrics.test.ts +2 -2
- package/tests/core/principle-internalization/{internalization-routing-policy.test.ts → lifecycle-routing-policy.test.ts} +6 -6
- package/tests/core/principle-internalization/lineage-source-retired.test.ts +56 -0
- package/tests/core/principle-internalization/principle-lifecycle-service.test.ts +1 -23
- package/tests/core/principle-tree-ledger-adapter.test.ts +253 -0
- package/tests/core/reflection-context.test.ts +0 -14
- package/tests/core/replay-engine.test.ts +127 -215
- package/tests/core/rule-host-helpers.test.ts +2 -2
- package/tests/core/rule-implementation-runtime.test.ts +0 -27
- package/tests/core/workflow-funnel-loader.test.ts +162 -0
- package/tests/core/workspace-dir-validation.test.ts +8 -1
- package/tests/core-anti-growth.test.ts +192 -0
- package/tests/hook-workspace-nextaction-contract.test.ts +42 -0
- package/tests/hooks/confirm-first-gate.test.ts +333 -0
- package/tests/hooks/gate-auto-correct-shadow.test.ts +310 -0
- package/tests/hooks/gate-auto-correct.test.ts +665 -0
- package/tests/hooks/gate-rule-host-pipeline.test.ts +2 -1
- package/tests/hooks/pain.test.ts +269 -12
- package/tests/hooks/prompt-characterization.test.ts +500 -0
- package/tests/hooks/prompt-size-guard.test.ts +329 -0
- package/tests/hooks/runtime-v2-prompt-activation.test.ts +869 -0
- package/tests/index.test.ts +94 -1
- package/tests/integration/auto-entry-gate.test.ts +248 -0
- package/tests/integration/internalization-trigger-guard.test.ts +69 -0
- package/tests/integration/m8-legacy-paths.test.ts +63 -0
- package/tests/integration/runtime-v2-pain-guard.test.ts +125 -0
- package/tests/plugin-config-resolution-cutover.test.ts +359 -0
- package/tests/runtime-v2-discovery-guard.test.ts +154 -0
- package/tests/service/central-database.test.ts +457 -0
- package/tests/service/evolution-worker.correction-observer.test.ts +173 -0
- package/tests/service/evolution-worker.timeout.test.ts +11 -129
- package/tests/service/internalization-trigger-adapter.test.ts +251 -0
- package/tests/service/monitoring-query-service.test.ts +1 -47
- package/tests/service/queue-io.test.ts +1 -62
- package/tests/service/runtime-summary-service.test.ts +184 -3
- package/tests/service/workflow-watchdog.test.ts +0 -91
- package/tests/utils/file-lock.test.ts +5 -3
- package/tests/utils/session-key.test.ts +52 -0
- package/tests/utils/subagent-probe.test.ts +48 -1
- package/vitest.config.ts +4 -11
- package/.planning/codebase/ARCHITECTURE.md +0 -157
- package/.planning/codebase/CONCERNS.md +0 -145
- package/.planning/codebase/CONVENTIONS.md +0 -148
- package/.planning/codebase/INTEGRATIONS.md +0 -81
- package/.planning/codebase/STACK.md +0 -87
- package/.planning/codebase/STRUCTURE.md +0 -193
- package/.planning/codebase/TESTING.md +0 -243
- package/.planning/phases/01-basic-visualization/01-GAP-CLOSURE-VERIFICATION.md +0 -113
- package/docs/COMMAND_REFERENCE.md +0 -76
- package/docs/COMMAND_REFERENCE_EN.md +0 -79
- package/scripts/build-web.mjs +0 -46
- package/scripts/diagnose-nocturnal.mjs +0 -537
- package/scripts/seed-nocturnal-scenarios.mjs +0 -384
- package/src/commands/nocturnal-review.ts +0 -322
- package/src/commands/nocturnal-rollout.ts +0 -790
- package/src/commands/nocturnal-train.ts +0 -986
- package/src/commands/pd-reflect.ts +0 -88
- package/src/core/adaptive-thresholds.ts +0 -478
- package/src/core/diagnostician-task-store.ts +0 -192
- package/src/core/nocturnal-arbiter.ts +0 -715
- package/src/core/nocturnal-artifact-lineage.ts +0 -116
- package/src/core/nocturnal-artificer.ts +0 -257
- package/src/core/nocturnal-candidate-scoring.ts +0 -530
- package/src/core/nocturnal-compliance.ts +0 -1146
- package/src/core/nocturnal-dataset.ts +0 -763
- package/src/core/nocturnal-executability.ts +0 -428
- package/src/core/nocturnal-export.ts +0 -499
- package/src/core/nocturnal-paths.ts +0 -240
- package/src/core/nocturnal-reasoning-deriver.ts +0 -343
- package/src/core/nocturnal-rule-implementation-validator.ts +0 -246
- package/src/core/nocturnal-snapshot-contract.ts +0 -99
- package/src/core/nocturnal-trajectory-extractor.ts +0 -512
- package/src/core/nocturnal-trinity-types.ts +0 -218
- package/src/core/nocturnal-trinity.ts +0 -2680
- package/src/core/principle-internalization/deprecated-readiness.ts +0 -93
- package/src/core/principle-internalization/internalization-routing-policy.ts +0 -208
- package/src/core/principle-internalization/lifecycle-metrics.ts +0 -152
- package/src/http/principles-console-route.ts +0 -709
- package/src/service/central-health-service.ts +0 -49
- package/src/service/central-overview-service.ts +0 -138
- package/src/service/control-ui-query-service.ts +0 -900
- package/src/service/cooldown-strategy.ts +0 -97
- package/src/service/evolution-pain-context.ts +0 -79
- package/src/service/evolution-query-service.ts +0 -407
- package/src/service/health-query-service.ts +0 -1038
- package/src/service/nocturnal-config.ts +0 -214
- package/src/service/nocturnal-runtime.ts +0 -734
- package/src/service/nocturnal-service.ts +0 -1605
- package/src/service/nocturnal-target-selector.ts +0 -545
- package/src/service/sleep-cycle.ts +0 -157
- package/src/service/startup-reconciler.ts +0 -112
- package/src/service/subagent-workflow/correction-observer-types.ts +0 -82
- package/src/service/subagent-workflow/correction-observer-workflow-manager.ts +0 -250
- package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +0 -1
- package/src/service/subagent-workflow/dynamic-timeout.ts +0 -30
- package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +0 -268
- package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +0 -795
- package/src/service/subagent-workflow/runtime-direct-driver.ts +0 -268
- package/src/service/subagent-workflow/workflow-manager-base.ts +0 -580
- package/src/tools/write-pain-flag.ts +0 -215
- package/tests/commands/nocturnal-review.test.ts +0 -448
- package/tests/commands/nocturnal-train.test.ts +0 -97
- package/tests/commands/pd-reflect.test.ts +0 -49
- package/tests/core/adaptive-thresholds.test.ts +0 -261
- package/tests/core/nocturnal-arbiter.test.ts +0 -559
- package/tests/core/nocturnal-artifact-lineage.test.ts +0 -53
- package/tests/core/nocturnal-artificer.test.ts +0 -241
- package/tests/core/nocturnal-candidate-scoring.test.ts +0 -532
- package/tests/core/nocturnal-compliance-p-principles.test.ts +0 -133
- package/tests/core/nocturnal-compliance.test.ts +0 -646
- package/tests/core/nocturnal-dataset.test.ts +0 -892
- package/tests/core/nocturnal-e2e.test.ts +0 -234
- package/tests/core/nocturnal-executability.test.ts +0 -357
- package/tests/core/nocturnal-export.test.ts +0 -517
- package/tests/core/nocturnal-reasoning-deriver.test.ts +0 -372
- package/tests/core/nocturnal-reviewed-subset-comparison.test.ts +0 -428
- package/tests/core/nocturnal-rule-implementation-validator.test.ts +0 -127
- package/tests/core/nocturnal-snapshot-contract.test.ts +0 -121
- package/tests/core/nocturnal-trajectory-extractor.test.ts +0 -634
- package/tests/core/nocturnal-trinity.test.ts +0 -2053
- package/tests/core/pain-auto-repair.test.ts +0 -96
- package/tests/core/pain-integration.test.ts +0 -510
- package/tests/fixtures/nocturnal-reviewed-subset.json +0 -183
- package/tests/http/principles-console-route.test.ts +0 -162
- package/tests/integration/chaos-resilience.test.ts +0 -348
- package/tests/integration/empathy-workflow-integration.test.ts +0 -626
- package/tests/integration/pain-diagnostician-loop.e2e.test.ts +0 -380
- package/tests/service/control-ui-query-service.test.ts +0 -121
- package/tests/service/cooldown-strategy.test.ts +0 -164
- package/tests/service/data-endpoints-regression.test.ts +0 -834
- package/tests/service/empathy-observer-workflow-manager.test.ts +0 -175
- package/tests/service/evolution-worker.nocturnal.test.ts +0 -601
- package/tests/service/nocturnal-runtime-hardening.test.ts +0 -118
- package/tests/service/nocturnal-runtime.test.ts +0 -473
- package/tests/service/nocturnal-service-code-candidate.test.ts +0 -330
- package/tests/service/nocturnal-target-selector.test.ts +0 -615
- package/tests/service/startup-reconciler.test.ts +0 -148
- package/tests/tools/write-pain-flag.test.ts +0 -358
- package/ui/src/App.tsx +0 -45
- package/ui/src/api.ts +0 -220
- package/ui/src/charts.tsx +0 -955
- package/ui/src/components/ErrorState.tsx +0 -6
- package/ui/src/components/Loading.tsx +0 -13
- package/ui/src/components/ProtectedRoute.tsx +0 -12
- package/ui/src/components/Shell.tsx +0 -91
- package/ui/src/components/WorkspaceConfig.tsx +0 -178
- package/ui/src/components/index.ts +0 -5
- package/ui/src/context/auth.tsx +0 -80
- package/ui/src/context/theme.tsx +0 -66
- package/ui/src/hooks/useAutoRefresh.ts +0 -39
- package/ui/src/i18n/ui.ts +0 -473
- package/ui/src/main.tsx +0 -16
- package/ui/src/pages/EvolutionPage.tsx +0 -333
- package/ui/src/pages/FeedbackPage.tsx +0 -138
- package/ui/src/pages/GateMonitorPage.tsx +0 -136
- package/ui/src/pages/LoginPage.tsx +0 -89
- package/ui/src/pages/OverviewPage.tsx +0 -599
- package/ui/src/pages/SamplesPage.tsx +0 -174
- package/ui/src/pages/ThinkingModelsPage.tsx +0 -702
- package/ui/src/styles.css +0 -2020
- package/ui/src/types.ts +0 -384
- package/ui/src/utils/format.ts +0 -15
|
@@ -1,1038 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
import { readPainFlagData } from '../core/pain.js';
|
|
5
|
-
import { resolvePdPath } from '../core/paths.js';
|
|
6
|
-
import { listSessions, type SessionState } from '../core/session-tracker.js';
|
|
7
|
-
import { listDeployments } from '../core/model-deployment-registry.js';
|
|
8
|
-
import { ControlUiDatabase } from '../core/control-ui-db.js';
|
|
9
|
-
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
10
|
-
import type { EventLogEntry } from '../types/event-types.js';
|
|
11
|
-
|
|
12
|
-
type HealthStage = 'healthy' | 'warning' | 'critical';
|
|
13
|
-
|
|
14
|
-
interface QueueItem {
|
|
15
|
-
status?: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface AgentScorecard {
|
|
19
|
-
trustStage?: number;
|
|
20
|
-
trust_stage?: number;
|
|
21
|
-
stage?: number;
|
|
22
|
-
trustScore?: number;
|
|
23
|
-
trust_score?: number;
|
|
24
|
-
score?: number;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
interface EvolutionScorecard {
|
|
28
|
-
totalPoints?: number;
|
|
29
|
-
total_points?: number;
|
|
30
|
-
currentTier?: number | string;
|
|
31
|
-
current_tier?: number | string;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
interface EvolutionStreamRecord {
|
|
35
|
-
ts?: string;
|
|
36
|
-
type?: string;
|
|
37
|
-
data?: Record<string, unknown>;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
interface GateBlockRow {
|
|
41
|
-
created_at: string;
|
|
42
|
-
tool_name: string;
|
|
43
|
-
file_path?: string | null;
|
|
44
|
-
reason: string;
|
|
45
|
-
gfi?: number | null;
|
|
46
|
-
gfi_after?: number | null;
|
|
47
|
-
trust_stage?: number | null;
|
|
48
|
-
gate_type?: string | null;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
interface NocturnalSampleRecord {
|
|
52
|
-
artifactId?: string;
|
|
53
|
-
status?: string;
|
|
54
|
-
createdAt?: string;
|
|
55
|
-
arbiter?: {
|
|
56
|
-
passed?: boolean;
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
interface RecentPrincipleChange {
|
|
61
|
-
principleId: string;
|
|
62
|
-
status: string;
|
|
63
|
-
triggerPattern: string;
|
|
64
|
-
action: string;
|
|
65
|
-
fromStatus: string;
|
|
66
|
-
toStatus: string;
|
|
67
|
-
timestamp: string;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export class HealthQueryService {
|
|
71
|
-
private readonly workspaceDir: string;
|
|
72
|
-
private readonly stateDir: string;
|
|
73
|
-
private readonly trajectory;
|
|
74
|
-
private readonly config;
|
|
75
|
-
private readonly eventLog;
|
|
76
|
-
private readonly evolutionReducer;
|
|
77
|
-
private readonly uiDb: ControlUiDatabase;
|
|
78
|
-
private readonly tableColumnCache = new Map<string, Set<string>>();
|
|
79
|
-
|
|
80
|
-
constructor(workspaceDir: string) {
|
|
81
|
-
this.workspaceDir = workspaceDir;
|
|
82
|
-
const wctx = WorkspaceContext.fromHookContext({ workspaceDir });
|
|
83
|
-
this.stateDir = wctx.stateDir;
|
|
84
|
-
this.trajectory = wctx.trajectory;
|
|
85
|
-
this.config = wctx.config;
|
|
86
|
-
this.eventLog = wctx.eventLog;
|
|
87
|
-
this.evolutionReducer = wctx.evolutionReducer;
|
|
88
|
-
this.uiDb = new ControlUiDatabase({ workspaceDir });
|
|
89
|
-
this.ensureTables();
|
|
90
|
-
this.syncGfiFromSession();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
dispose(): void {
|
|
94
|
-
this.uiDb.dispose();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Ensure all tables required by HealthQueryService exist.
|
|
99
|
-
* This is a safety net for workspaces created before TrajectoryDatabase
|
|
100
|
-
* had full schema initialization, or where initSchema() was never called.
|
|
101
|
-
* Uses CREATE TABLE IF NOT EXISTS so it's idempotent.
|
|
102
|
-
*/
|
|
103
|
-
private ensureTables(): void {
|
|
104
|
-
try {
|
|
105
|
-
this.uiDb.execute(`
|
|
106
|
-
CREATE TABLE IF NOT EXISTS pain_events (
|
|
107
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
108
|
-
session_id TEXT NOT NULL,
|
|
109
|
-
source TEXT NOT NULL,
|
|
110
|
-
score REAL NOT NULL DEFAULT 0,
|
|
111
|
-
reason TEXT DEFAULT '',
|
|
112
|
-
severity TEXT DEFAULT 'mild',
|
|
113
|
-
origin TEXT DEFAULT '',
|
|
114
|
-
confidence REAL DEFAULT 0,
|
|
115
|
-
text TEXT DEFAULT '',
|
|
116
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
117
|
-
)
|
|
118
|
-
`);
|
|
119
|
-
this.uiDb.execute(`
|
|
120
|
-
CREATE INDEX IF NOT EXISTS idx_pain_events_created_at ON pain_events(created_at)
|
|
121
|
-
`);
|
|
122
|
-
this.uiDb.execute(`
|
|
123
|
-
CREATE TABLE IF NOT EXISTS gate_blocks (
|
|
124
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
125
|
-
session_id TEXT NOT NULL,
|
|
126
|
-
tool_name TEXT NOT NULL,
|
|
127
|
-
file_path TEXT,
|
|
128
|
-
reason TEXT NOT NULL,
|
|
129
|
-
gfi REAL,
|
|
130
|
-
gfi_after REAL,
|
|
131
|
-
trust_stage INTEGER,
|
|
132
|
-
gate_type TEXT,
|
|
133
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
134
|
-
)
|
|
135
|
-
`);
|
|
136
|
-
this.uiDb.execute(`
|
|
137
|
-
CREATE INDEX IF NOT EXISTS idx_gate_blocks_created_at ON gate_blocks(created_at)
|
|
138
|
-
`);
|
|
139
|
-
} catch (err) {
|
|
140
|
-
console.warn('[HealthQueryService] Table ensure failed (non-fatal):', err);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
getOverviewHealth(): {
|
|
145
|
-
gfi: { current: number; peakToday: number; threshold: number; trend: { hour: string; value: number }[] };
|
|
146
|
-
trust: { stage: number; stageLabel: string; score: number };
|
|
147
|
-
evolution: { tier: string; points: number };
|
|
148
|
-
painFlag: { active: boolean; source: string | null; score: number | null };
|
|
149
|
-
principles: { candidate: number; probation: number; active: number; deprecated: number };
|
|
150
|
-
queue: { pending: number; inProgress: number; completed: number };
|
|
151
|
-
activeStage: HealthStage;
|
|
152
|
-
} {
|
|
153
|
-
const threshold = this.getGfiThreshold();
|
|
154
|
-
const trust = this.readTrust();
|
|
155
|
-
const evolution = this.readEvolutionScore();
|
|
156
|
-
const reducerStats = this.evolutionReducer.getStats();
|
|
157
|
-
const queue = this.readQueueStats();
|
|
158
|
-
const painFlag = this.readPainFlag();
|
|
159
|
-
|
|
160
|
-
// GFI: Re-sync from session JSON on every request for real-time data
|
|
161
|
-
this.syncGfiFromSession();
|
|
162
|
-
const gfiData = this.readGfiFromDb();
|
|
163
|
-
const {currentGfi} = gfiData;
|
|
164
|
-
const peakToday = gfiData.dailyGfiPeak;
|
|
165
|
-
|
|
166
|
-
// GFI history trend: aggregate pain_events by hour
|
|
167
|
-
const today = new Date().toISOString().slice(0, 10);
|
|
168
|
-
const gfiTrend = this.readGfiTrend(today);
|
|
169
|
-
|
|
170
|
-
return {
|
|
171
|
-
gfi: {
|
|
172
|
-
current: currentGfi,
|
|
173
|
-
peakToday,
|
|
174
|
-
threshold,
|
|
175
|
-
trend: gfiTrend,
|
|
176
|
-
},
|
|
177
|
-
trust,
|
|
178
|
-
evolution,
|
|
179
|
-
painFlag,
|
|
180
|
-
principles: {
|
|
181
|
-
candidate: reducerStats.candidateCount,
|
|
182
|
-
probation: reducerStats.probationCount,
|
|
183
|
-
active: reducerStats.activeCount,
|
|
184
|
-
deprecated: reducerStats.deprecatedCount,
|
|
185
|
-
},
|
|
186
|
-
queue,
|
|
187
|
-
activeStage: HealthQueryService.computeHealthStage(currentGfi, threshold, painFlag.active),
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
getEvolutionPrinciples(): {
|
|
192
|
-
principles: {
|
|
193
|
-
summary: { candidate: number; probation: number; active: number; deprecated: number };
|
|
194
|
-
recent: {
|
|
195
|
-
principleId: string;
|
|
196
|
-
status: string;
|
|
197
|
-
triggerPattern: string;
|
|
198
|
-
action: string;
|
|
199
|
-
fromStatus: string;
|
|
200
|
-
toStatus: string;
|
|
201
|
-
timestamp: string;
|
|
202
|
-
}[];
|
|
203
|
-
};
|
|
204
|
-
nocturnalTraining: {
|
|
205
|
-
queue: { pending: number; inProgress: number; completed: number };
|
|
206
|
-
trinityRecords: { artifactId: string; status: string; createdAt: string }[];
|
|
207
|
-
arbiterPassRate: number;
|
|
208
|
-
orpoSampleCount: number;
|
|
209
|
-
deployments: { modelId: string; status: string; checkpointPath: string | null }[];
|
|
210
|
-
};
|
|
211
|
-
painSourceDistribution: Record<string, number>;
|
|
212
|
-
activeStage: string;
|
|
213
|
-
} {
|
|
214
|
-
const stats = this.evolutionReducer.getStats();
|
|
215
|
-
const recent = this.readRecentPrincipleChanges(30);
|
|
216
|
-
const nocturnal = this.readNocturnalTraining();
|
|
217
|
-
const painSourceDistribution = this.readPainSourceDistribution();
|
|
218
|
-
const activeStage = this.readEvolutionActiveStage(nocturnal.queue);
|
|
219
|
-
|
|
220
|
-
return {
|
|
221
|
-
principles: {
|
|
222
|
-
summary: {
|
|
223
|
-
candidate: stats.candidateCount,
|
|
224
|
-
probation: stats.probationCount,
|
|
225
|
-
active: stats.activeCount,
|
|
226
|
-
deprecated: stats.deprecatedCount,
|
|
227
|
-
},
|
|
228
|
-
recent,
|
|
229
|
-
},
|
|
230
|
-
nocturnalTraining: nocturnal,
|
|
231
|
-
painSourceDistribution,
|
|
232
|
-
activeStage,
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Read GFI trend for a specific day by aggregating pain_events by hour.
|
|
238
|
-
* Used by getOverviewHealth() to provide historical GFI context.
|
|
239
|
-
*/
|
|
240
|
-
private readGfiTrend(date: string): { hour: string; value: number }[] {
|
|
241
|
-
try {
|
|
242
|
-
const rows = this.uiDb.all<{ hour: string; value: number }>(`
|
|
243
|
-
SELECT substr(created_at, 1, 13) || ':00:00Z' AS hour, ROUND(SUM(score), 2) AS value
|
|
244
|
-
FROM pain_events
|
|
245
|
-
WHERE substr(created_at, 1, 10) = ?
|
|
246
|
-
GROUP BY substr(created_at, 1, 13)
|
|
247
|
-
ORDER BY hour ASC
|
|
248
|
-
`, date);
|
|
249
|
-
return rows.map(row => ({ hour: row.hour, value: this.asNumber(row.value, 0) }));
|
|
250
|
-
} catch {
|
|
251
|
-
// pain_events table may not exist in this workspace — return empty trend
|
|
252
|
-
return [];
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
getFeedbackGfi(): {
|
|
257
|
-
current: number;
|
|
258
|
-
peakToday: number;
|
|
259
|
-
threshold: number;
|
|
260
|
-
trend: { hour: string; value: number }[];
|
|
261
|
-
sources: Record<string, number>;
|
|
262
|
-
} {
|
|
263
|
-
const threshold = this.getGfiThreshold();
|
|
264
|
-
const today = new Date().toISOString().slice(0, 10);
|
|
265
|
-
|
|
266
|
-
const trendRows = this.uiDb.all<{ hour: string; value: number }>(`
|
|
267
|
-
SELECT substr(created_at, 1, 13) || ':00:00Z' AS hour, ROUND(SUM(score), 2) AS value
|
|
268
|
-
FROM pain_events
|
|
269
|
-
WHERE substr(created_at, 1, 10) = ?
|
|
270
|
-
GROUP BY substr(created_at, 1, 13)
|
|
271
|
-
ORDER BY hour ASC
|
|
272
|
-
`, today);
|
|
273
|
-
|
|
274
|
-
const sourceRows = this.uiDb.all<{ source: string; total: number }>(`
|
|
275
|
-
SELECT source, COUNT(*) AS total
|
|
276
|
-
FROM pain_events
|
|
277
|
-
WHERE substr(created_at, 1, 10) = ?
|
|
278
|
-
GROUP BY source
|
|
279
|
-
ORDER BY total DESC
|
|
280
|
-
`, today);
|
|
281
|
-
|
|
282
|
-
// GFI: Re-sync from session JSON for real-time data
|
|
283
|
-
this.syncGfiFromSession();
|
|
284
|
-
const gfiData = this.readGfiFromDb();
|
|
285
|
-
|
|
286
|
-
return {
|
|
287
|
-
current: gfiData.currentGfi,
|
|
288
|
-
peakToday: gfiData.dailyGfiPeak,
|
|
289
|
-
threshold,
|
|
290
|
-
trend: trendRows.map((row) => ({ hour: row.hour, value: this.asNumber(row.value, 0) })),
|
|
291
|
-
sources: Object.fromEntries(sourceRows.map((row) => [row.source, this.asNumber(row.total, 0)])),
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
getFeedbackEmpathyEvents(limit = 50): {
|
|
296
|
-
timestamp: string;
|
|
297
|
-
severity: string;
|
|
298
|
-
score: number;
|
|
299
|
-
reason: string;
|
|
300
|
-
origin: string;
|
|
301
|
-
gfiAfter: number;
|
|
302
|
-
}[] {
|
|
303
|
-
const safeLimit = Math.max(1, Math.min(500, Math.floor(limit)));
|
|
304
|
-
const events = this.readMergedEvents()
|
|
305
|
-
.filter((entry) => entry.type === 'pain_signal' && String(entry.data?.source ?? '') === 'user_empathy')
|
|
306
|
-
.sort((a, b) => (b.ts || '').localeCompare(a.ts || ''))
|
|
307
|
-
.slice(0, safeLimit);
|
|
308
|
-
|
|
309
|
-
return events.map((entry) => {
|
|
310
|
-
const data = entry.data ?? {};
|
|
311
|
-
return {
|
|
312
|
-
timestamp: String(entry.ts ?? ''),
|
|
313
|
-
severity: typeof data.severity === 'string' ? data.severity : 'mild',
|
|
314
|
-
score: this.asNumber(data.score, 0),
|
|
315
|
-
reason: typeof data.reason === 'string' ? data.reason : '',
|
|
316
|
-
origin: typeof data.origin === 'string' ? data.origin : 'unknown',
|
|
317
|
-
gfiAfter: this.asNumber(data.gfiAfter ?? data.gfi_after ?? data.gfi, 0),
|
|
318
|
-
};
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
getFeedbackGateBlocks(limit = 50): {
|
|
323
|
-
timestamp: string;
|
|
324
|
-
toolName: string;
|
|
325
|
-
reason: string;
|
|
326
|
-
gfi: number;
|
|
327
|
-
trustStage: number;
|
|
328
|
-
}[] {
|
|
329
|
-
const trust = this.readTrust();
|
|
330
|
-
const rows = this.readGateBlocksRaw(limit);
|
|
331
|
-
|
|
332
|
-
return rows.map((row) => ({
|
|
333
|
-
timestamp: row.created_at,
|
|
334
|
-
toolName: row.tool_name,
|
|
335
|
-
reason: row.reason,
|
|
336
|
-
gfi: this.resolveGateBlockGfi(row),
|
|
337
|
-
trustStage: this.resolveGateBlockTrustStage(row, trust.stage),
|
|
338
|
-
}));
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
getGateStats(): {
|
|
343
|
-
today: {
|
|
344
|
-
gfiBlocks: number;
|
|
345
|
-
stageBlocks: number;
|
|
346
|
-
p03Blocks: number;
|
|
347
|
-
bypassAttempts: number;
|
|
348
|
-
p16Exemptions: number;
|
|
349
|
-
};
|
|
350
|
-
trust: { stage: number; score: number; status: string };
|
|
351
|
-
evolution: { tier: string; points: number; status: string };
|
|
352
|
-
} {
|
|
353
|
-
const today = new Date().toISOString().slice(0, 10);
|
|
354
|
-
const rows = this.uiDb.all<{ reason: string }>(`
|
|
355
|
-
SELECT reason
|
|
356
|
-
FROM gate_blocks
|
|
357
|
-
WHERE substr(created_at, 1, 10) = ?
|
|
358
|
-
`, today);
|
|
359
|
-
|
|
360
|
-
let gfiBlocks = 0;
|
|
361
|
-
let stageBlocks = 0;
|
|
362
|
-
let p03Blocks = 0;
|
|
363
|
-
let bypassAttempts = 0;
|
|
364
|
-
let p16Exemptions = 0;
|
|
365
|
-
|
|
366
|
-
for (const row of rows) {
|
|
367
|
-
const reason = String(row.reason || '').toLowerCase();
|
|
368
|
-
if (reason.includes('gfi')) gfiBlocks++;
|
|
369
|
-
if (reason.includes('tier') || reason.includes('stage') || reason.includes('trust')) stageBlocks++;
|
|
370
|
-
if (reason.includes('p-03') || reason.includes('edit verification') || reason.includes('oldtext')) p03Blocks++;
|
|
371
|
-
if (reason.includes('bypass')) bypassAttempts++;
|
|
372
|
-
if (reason.includes('p-16') || reason.includes('exemption')) p16Exemptions++;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
const trust = this.readTrust();
|
|
376
|
-
const evolution = this.readEvolutionScore();
|
|
377
|
-
|
|
378
|
-
return {
|
|
379
|
-
today: {
|
|
380
|
-
gfiBlocks,
|
|
381
|
-
stageBlocks,
|
|
382
|
-
p03Blocks,
|
|
383
|
-
bypassAttempts,
|
|
384
|
-
p16Exemptions,
|
|
385
|
-
},
|
|
386
|
-
trust: {
|
|
387
|
-
stage: trust.stage,
|
|
388
|
-
score: trust.score,
|
|
389
|
-
status: this.scoreToStatus(trust.score),
|
|
390
|
-
},
|
|
391
|
-
evolution: {
|
|
392
|
-
tier: evolution.tier,
|
|
393
|
-
points: evolution.points,
|
|
394
|
-
status: this.evolutionToStatus(evolution.tier, evolution.points),
|
|
395
|
-
},
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
getGateBlocks(limit = 50): {
|
|
400
|
-
timestamp: string;
|
|
401
|
-
toolName: string;
|
|
402
|
-
filePath: string | null;
|
|
403
|
-
reason: string;
|
|
404
|
-
gateType: string;
|
|
405
|
-
gfi: number;
|
|
406
|
-
trustStage: number;
|
|
407
|
-
}[] {
|
|
408
|
-
const trust = this.readTrust();
|
|
409
|
-
const rows = this.readGateBlocksRaw(limit);
|
|
410
|
-
|
|
411
|
-
return rows.map((row) => ({
|
|
412
|
-
timestamp: row.created_at,
|
|
413
|
-
toolName: row.tool_name,
|
|
414
|
-
filePath: row.file_path ?? null,
|
|
415
|
-
reason: row.reason,
|
|
416
|
-
gateType: this.resolveGateType(row),
|
|
417
|
-
gfi: this.resolveGateBlockGfi(row),
|
|
418
|
-
trustStage: this.resolveGateBlockTrustStage(row, trust.stage),
|
|
419
|
-
}));
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
private readTrust(): { stage: number; stageLabel: string; score: number } {
|
|
423
|
-
const scorecardPath = resolvePdPath(this.workspaceDir, 'AGENT_SCORECARD');
|
|
424
|
-
const scorecard = this.readJsonFile<AgentScorecard>(scorecardPath, {});
|
|
425
|
-
const score = this.asNumber(
|
|
426
|
-
scorecard.trustScore ?? scorecard.trust_score ?? scorecard.score,
|
|
427
|
-
0,
|
|
428
|
-
);
|
|
429
|
-
const rawStage = this.asNumber(
|
|
430
|
-
scorecard.trustStage ?? scorecard.trust_stage ?? scorecard.stage,
|
|
431
|
-
HealthQueryService.inferTrustStageFromScore(score),
|
|
432
|
-
);
|
|
433
|
-
const stage = Math.max(1, Math.min(4, Math.round(rawStage)));
|
|
434
|
-
|
|
435
|
-
return {
|
|
436
|
-
stage,
|
|
437
|
-
stageLabel: HealthQueryService.getTrustStageLabel(stage),
|
|
438
|
-
score,
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
private readEvolutionScore(): { tier: string; points: number } {
|
|
443
|
-
const scorecardPath = path.join(this.stateDir, 'evolution-scorecard.json');
|
|
444
|
-
const scorecard = this.readJsonFile<EvolutionScorecard>(scorecardPath, {});
|
|
445
|
-
const points = this.asNumber(scorecard.totalPoints ?? scorecard.total_points, 0);
|
|
446
|
-
const tierRaw = scorecard.currentTier ?? scorecard.current_tier ?? 1;
|
|
447
|
-
return {
|
|
448
|
-
tier: this.normalizeTierName(tierRaw),
|
|
449
|
-
points,
|
|
450
|
-
};
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
private readQueueStats(): { pending: number; inProgress: number; completed: number } {
|
|
454
|
-
const queuePath = resolvePdPath(this.workspaceDir, 'EVOLUTION_QUEUE');
|
|
455
|
-
const queue = this.readJsonFile<QueueItem[]>(queuePath, []);
|
|
456
|
-
const stats = { pending: 0, inProgress: 0, completed: 0 };
|
|
457
|
-
for (const item of queue) {
|
|
458
|
-
const status = String(item?.status ?? 'pending');
|
|
459
|
-
if (status === 'completed') stats.completed++;
|
|
460
|
-
else if (status === 'in_progress') stats.inProgress++;
|
|
461
|
-
else stats.pending++;
|
|
462
|
-
}
|
|
463
|
-
return stats;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
private readPainFlag(): { active: boolean; source: string | null; score: number | null } {
|
|
467
|
-
const painFlagPath = resolvePdPath(this.workspaceDir, 'PAIN_FLAG');
|
|
468
|
-
if (!fs.existsSync(painFlagPath)) {
|
|
469
|
-
return { active: false, source: null, score: null };
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
const data = readPainFlagData(this.workspaceDir);
|
|
473
|
-
return {
|
|
474
|
-
active: true,
|
|
475
|
-
source: typeof data.source === 'string' ? data.source : null,
|
|
476
|
-
score: this.asNullableNumber(data.score),
|
|
477
|
-
};
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
private getCurrentSession(): SessionState | null {
|
|
481
|
-
// First, try in-memory sessions (live sessions from session-tracker)
|
|
482
|
-
const sessions = listSessions(this.workspaceDir);
|
|
483
|
-
if (sessions.length > 0) {
|
|
484
|
-
const sorted = [...sessions].sort((a, b) => {
|
|
485
|
-
const aTs = Number(a.lastControlActivityAt ?? a.lastActivityAt ?? 0);
|
|
486
|
-
const bTs = Number(b.lastControlActivityAt ?? b.lastActivityAt ?? 0);
|
|
487
|
-
return bTs - aTs;
|
|
488
|
-
});
|
|
489
|
-
return sorted[0] ?? null;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
// Fallback: read session JSON files directly (handles process restarts where
|
|
493
|
-
// loadAllSessions may not have reloaded all sessions, or workspaceDir mismatch)
|
|
494
|
-
return this.readLatestSessionFromFile();
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
private getGfiThreshold(): number {
|
|
498
|
-
const fromConfig = this.asNullableNumber(this.config.get('gfi_gate.thresholds.low_risk_block'));
|
|
499
|
-
if (fromConfig !== null) return fromConfig;
|
|
500
|
-
const fallbackPath = resolvePdPath(this.workspaceDir, 'PAIN_SETTINGS');
|
|
501
|
-
const raw = this.readJsonFile<Record<string, unknown>>(fallbackPath, {});
|
|
502
|
-
const fallback = this.asNullableNumber(
|
|
503
|
-
((raw.gfi_gate as { thresholds?: { low_risk_block?: unknown } } | undefined)?.thresholds?.low_risk_block),
|
|
504
|
-
);
|
|
505
|
-
return fallback ?? 70;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
private static computeHealthStage(currentGfi: number, threshold: number, painFlagActive: boolean): HealthStage {
|
|
509
|
-
if (painFlagActive || currentGfi >= threshold) {
|
|
510
|
-
return 'critical';
|
|
511
|
-
}
|
|
512
|
-
if (currentGfi >= threshold * 0.7) {
|
|
513
|
-
return 'warning';
|
|
514
|
-
}
|
|
515
|
-
return 'healthy';
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
private static getTrustStageLabel(stage: number): string {
|
|
519
|
-
switch (stage) {
|
|
520
|
-
case 1:
|
|
521
|
-
return 'Observer';
|
|
522
|
-
case 2:
|
|
523
|
-
return 'Editor';
|
|
524
|
-
case 3:
|
|
525
|
-
return 'Developer';
|
|
526
|
-
case 4:
|
|
527
|
-
return 'Architect';
|
|
528
|
-
default:
|
|
529
|
-
return 'Observer';
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
private static inferTrustStageFromScore(score: number): number {
|
|
534
|
-
if (score >= 80) return 4;
|
|
535
|
-
if (score >= 60) return 3;
|
|
536
|
-
if (score >= 30) return 2;
|
|
537
|
-
return 1;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
private normalizeTierName(rawTier: number | string): string {
|
|
541
|
-
if (typeof rawTier === 'string') {
|
|
542
|
-
const t = rawTier.trim();
|
|
543
|
-
if (t.length > 0) return t;
|
|
544
|
-
}
|
|
545
|
-
const n = this.asNumber(rawTier, 1);
|
|
546
|
-
if (n === 1) return 'Seed';
|
|
547
|
-
if (n === 2) return 'Sprout';
|
|
548
|
-
if (n === 3) return 'Sapling';
|
|
549
|
-
if (n === 4) return 'Tree';
|
|
550
|
-
if (n === 5) return 'Forest';
|
|
551
|
-
return `Tier-${n}`;
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
private readRecentPrincipleChanges(limit: number): RecentPrincipleChange[] {
|
|
555
|
-
const streamPath = resolvePdPath(this.workspaceDir, 'EVOLUTION_STREAM');
|
|
556
|
-
if (!fs.existsSync(streamPath)) return [];
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
let lines: string[];
|
|
560
|
-
try {
|
|
561
|
-
const raw = fs.readFileSync(streamPath, 'utf8').trim();
|
|
562
|
-
if (!raw) return [];
|
|
563
|
-
lines = raw.split('\n');
|
|
564
|
-
} catch {
|
|
565
|
-
return [];
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
const records: RecentPrincipleChange[] = [];
|
|
569
|
-
for (const line of lines) {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
let event: EvolutionStreamRecord | null;
|
|
573
|
-
try {
|
|
574
|
-
event = JSON.parse(line) as EvolutionStreamRecord;
|
|
575
|
-
} catch {
|
|
576
|
-
event = null;
|
|
577
|
-
}
|
|
578
|
-
if (!event?.type || !event.data) continue;
|
|
579
|
-
|
|
580
|
-
const mapped = this.mapPrincipleEvent(event);
|
|
581
|
-
if (mapped) records.push(mapped);
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
return records
|
|
585
|
-
.sort((a, b) => b.timestamp.localeCompare(a.timestamp))
|
|
586
|
-
.slice(0, Math.max(1, Math.min(200, limit)));
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
private mapPrincipleEvent(event: EvolutionStreamRecord): RecentPrincipleChange | null {
|
|
590
|
-
const data = event.data ?? {};
|
|
591
|
-
const type = String(event.type);
|
|
592
|
-
const principleId = typeof data.principleId === 'string' ? data.principleId : '';
|
|
593
|
-
if (!principleId) return null;
|
|
594
|
-
|
|
595
|
-
const principle = this.evolutionReducer.getPrincipleById(principleId);
|
|
596
|
-
const triggerPattern =
|
|
597
|
-
typeof data.trigger === 'string'
|
|
598
|
-
? data.trigger
|
|
599
|
-
: typeof principle?.trigger === 'string'
|
|
600
|
-
? principle.trigger
|
|
601
|
-
: '';
|
|
602
|
-
const action =
|
|
603
|
-
typeof data.action === 'string'
|
|
604
|
-
? data.action
|
|
605
|
-
: typeof principle?.action === 'string'
|
|
606
|
-
? principle.action
|
|
607
|
-
: '';
|
|
608
|
-
|
|
609
|
-
if (type === 'candidate_created') {
|
|
610
|
-
const toStatus = typeof data.status === 'string' ? data.status : 'candidate';
|
|
611
|
-
return {
|
|
612
|
-
principleId,
|
|
613
|
-
status: toStatus,
|
|
614
|
-
triggerPattern,
|
|
615
|
-
action,
|
|
616
|
-
fromStatus: 'none',
|
|
617
|
-
toStatus,
|
|
618
|
-
timestamp: String(event.ts ?? ''),
|
|
619
|
-
};
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
if (type === 'principle_promoted') {
|
|
623
|
-
const fromStatus = typeof data.from === 'string' ? data.from : 'candidate';
|
|
624
|
-
const toStatus = typeof data.to === 'string' ? data.to : 'probation';
|
|
625
|
-
return {
|
|
626
|
-
principleId,
|
|
627
|
-
status: toStatus,
|
|
628
|
-
triggerPattern,
|
|
629
|
-
action,
|
|
630
|
-
fromStatus,
|
|
631
|
-
toStatus,
|
|
632
|
-
timestamp: String(event.ts ?? ''),
|
|
633
|
-
};
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
if (type === 'principle_deprecated') {
|
|
637
|
-
return {
|
|
638
|
-
principleId,
|
|
639
|
-
status: 'deprecated',
|
|
640
|
-
triggerPattern,
|
|
641
|
-
action,
|
|
642
|
-
fromStatus: 'active',
|
|
643
|
-
toStatus: 'deprecated',
|
|
644
|
-
timestamp: String(event.ts ?? ''),
|
|
645
|
-
};
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
if (type === 'principle_rolled_back') {
|
|
649
|
-
return {
|
|
650
|
-
principleId,
|
|
651
|
-
status: 'deprecated',
|
|
652
|
-
triggerPattern,
|
|
653
|
-
action,
|
|
654
|
-
fromStatus: 'probation',
|
|
655
|
-
toStatus: 'deprecated',
|
|
656
|
-
timestamp: String(event.ts ?? ''),
|
|
657
|
-
};
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
return null;
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
private readNocturnalTraining(): {
|
|
665
|
-
queue: { pending: number; inProgress: number; completed: number };
|
|
666
|
-
trinityRecords: { artifactId: string; status: string; createdAt: string }[];
|
|
667
|
-
arbiterPassRate: number;
|
|
668
|
-
orpoSampleCount: number;
|
|
669
|
-
deployments: { modelId: string; status: string; checkpointPath: string | null }[];
|
|
670
|
-
} {
|
|
671
|
-
const sampleDir = resolvePdPath(this.workspaceDir, 'NOCTURNAL_SAMPLES_DIR');
|
|
672
|
-
const exportDir = resolvePdPath(this.workspaceDir, 'NOCTURNAL_EXPORTS_DIR');
|
|
673
|
-
const reviewQueuePath = path.join(this.stateDir, 'nocturnal', 'review-queue.json');
|
|
674
|
-
|
|
675
|
-
const sampleFiles = this.safeListFiles(sampleDir, (name) => name.endsWith('.json') && name !== 'lineage-index.json');
|
|
676
|
-
const samples: NocturnalSampleRecord[] = sampleFiles.map((filePath) =>
|
|
677
|
-
this.readJsonFile<NocturnalSampleRecord>(filePath, {}),
|
|
678
|
-
);
|
|
679
|
-
|
|
680
|
-
const queue = { pending: 0, inProgress: 0, completed: 0 };
|
|
681
|
-
for (const sample of samples) {
|
|
682
|
-
const status = String(sample.status ?? '').toLowerCase();
|
|
683
|
-
if (status === 'in_progress' || status === 'running') queue.inProgress++;
|
|
684
|
-
else if (status === 'pending' || status === 'pending_review') queue.pending++;
|
|
685
|
-
else queue.completed++;
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
const reviewQueue = this.readJsonFile<unknown[]>(reviewQueuePath, []);
|
|
689
|
-
queue.pending += reviewQueue.length;
|
|
690
|
-
|
|
691
|
-
const trinityRecords = samples
|
|
692
|
-
.map((sample) => ({
|
|
693
|
-
artifactId: String(sample.artifactId ?? ''),
|
|
694
|
-
status: String(sample.status ?? 'unknown'),
|
|
695
|
-
createdAt: String(sample.createdAt ?? ''),
|
|
696
|
-
}))
|
|
697
|
-
.filter((row) => row.artifactId.length > 0)
|
|
698
|
-
.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
|
|
699
|
-
.slice(0, 20);
|
|
700
|
-
|
|
701
|
-
let passed = 0;
|
|
702
|
-
for (const sample of samples) {
|
|
703
|
-
const status = String(sample.status ?? '').toLowerCase();
|
|
704
|
-
const arbiterPassed = sample.arbiter?.passed === true;
|
|
705
|
-
if (arbiterPassed || status === 'approved' || status === 'approved_for_training') {
|
|
706
|
-
passed++;
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
const total = samples.length;
|
|
710
|
-
const arbiterPassRate = total > 0 ? Number((passed / total).toFixed(4)) : 0;
|
|
711
|
-
|
|
712
|
-
const exportFiles = this.safeListFiles(exportDir, (name) => name.endsWith('.jsonl'));
|
|
713
|
-
const orpoSampleCount = exportFiles.length;
|
|
714
|
-
|
|
715
|
-
const deployments = listDeployments(this.stateDir).map((deployment) => ({
|
|
716
|
-
modelId: deployment.workerProfile,
|
|
717
|
-
status: deployment.routingEnabled ? 'active' : 'inactive',
|
|
718
|
-
checkpointPath: deployment.activeCheckpointId,
|
|
719
|
-
}));
|
|
720
|
-
|
|
721
|
-
return {
|
|
722
|
-
queue,
|
|
723
|
-
trinityRecords,
|
|
724
|
-
arbiterPassRate,
|
|
725
|
-
orpoSampleCount,
|
|
726
|
-
deployments,
|
|
727
|
-
};
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
private readPainSourceDistribution(): Record<string, number> {
|
|
731
|
-
const rows = this.uiDb.all<{ source: string; total: number }>(`
|
|
732
|
-
SELECT source, COUNT(*) AS total
|
|
733
|
-
FROM pain_events
|
|
734
|
-
GROUP BY source
|
|
735
|
-
ORDER BY total DESC
|
|
736
|
-
`);
|
|
737
|
-
return Object.fromEntries(rows.map((row) => [row.source, this.asNumber(row.total, 0)]));
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
private readEvolutionActiveStage(queue: { pending: number; inProgress: number; completed: number }): string {
|
|
741
|
-
const events = this.trajectory.listEvolutionEvents(undefined, { limit: 1, offset: 0 });
|
|
742
|
-
if (events.length > 0) {
|
|
743
|
-
return events[0].stage;
|
|
744
|
-
}
|
|
745
|
-
if (queue.inProgress > 0) return 'in_progress';
|
|
746
|
-
if (queue.pending > 0) return 'pending';
|
|
747
|
-
if (queue.completed > 0) return 'completed';
|
|
748
|
-
return 'idle';
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
private readMergedEvents(): EventLogEntry[] {
|
|
752
|
-
const persistedEvents = this.readPersistedEvents();
|
|
753
|
-
const bufferedEvents = this.getBufferedEvents();
|
|
754
|
-
const merged = new Map<string, EventLogEntry>();
|
|
755
|
-
for (const entry of [...persistedEvents, ...bufferedEvents]) {
|
|
756
|
-
merged.set(this.getEventDedupKey(entry), entry);
|
|
757
|
-
}
|
|
758
|
-
return [...merged.values()].sort((a, b) => (a.ts || '').localeCompare(b.ts || ''));
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
private readPersistedEvents(): EventLogEntry[] {
|
|
762
|
-
const eventsPath = path.join(this.stateDir, 'logs', 'events.jsonl');
|
|
763
|
-
if (!fs.existsSync(eventsPath)) return [];
|
|
764
|
-
try {
|
|
765
|
-
const raw = fs.readFileSync(eventsPath, 'utf8').trim();
|
|
766
|
-
if (!raw) return [];
|
|
767
|
-
return raw
|
|
768
|
-
.split('\n')
|
|
769
|
-
.map((line) => {
|
|
770
|
-
try {
|
|
771
|
-
return JSON.parse(line) as EventLogEntry;
|
|
772
|
-
} catch {
|
|
773
|
-
return null;
|
|
774
|
-
}
|
|
775
|
-
})
|
|
776
|
-
.filter((entry): entry is EventLogEntry => entry !== null);
|
|
777
|
-
} catch {
|
|
778
|
-
return [];
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
private getBufferedEvents(): EventLogEntry[] {
|
|
783
|
-
const candidate = this.eventLog as { getBufferedEvents?: () => EventLogEntry[] };
|
|
784
|
-
if (typeof candidate.getBufferedEvents === 'function') {
|
|
785
|
-
return candidate.getBufferedEvents();
|
|
786
|
-
}
|
|
787
|
-
return [];
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
private getEventDedupKey(entry: EventLogEntry): string {
|
|
794
|
-
const eventId = typeof entry.data?.eventId === 'string' ? entry.data.eventId : null;
|
|
795
|
-
if (eventId) {
|
|
796
|
-
return `${entry.type}:${entry.sessionId ?? 'none'}:${eventId}`;
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
return [
|
|
800
|
-
entry.ts ?? 'no-ts',
|
|
801
|
-
entry.type ?? 'no-type',
|
|
802
|
-
entry.category ?? 'no-category',
|
|
803
|
-
entry.sessionId ?? 'no-session',
|
|
804
|
-
typeof entry.data?.source === 'string' ? entry.data.source : 'no-source',
|
|
805
|
-
typeof entry.data?.toolName === 'string' ? entry.data.toolName : 'no-tool',
|
|
806
|
-
typeof entry.data?.reason === 'string' ? entry.data.reason : 'no-reason',
|
|
807
|
-
].join('::');
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
private readGateBlocksRaw(limit: number): GateBlockRow[] {
|
|
811
|
-
const safeLimit = Math.max(1, Math.min(1000, Math.floor(limit)));
|
|
812
|
-
const hasGfi = this.hasTableColumn('gate_blocks', 'gfi');
|
|
813
|
-
const hasGfiAfter = this.hasTableColumn('gate_blocks', 'gfi_after');
|
|
814
|
-
const hasTrustStage = this.hasTableColumn('gate_blocks', 'trust_stage');
|
|
815
|
-
const hasGateType = this.hasTableColumn('gate_blocks', 'gate_type');
|
|
816
|
-
|
|
817
|
-
const sql = `
|
|
818
|
-
SELECT
|
|
819
|
-
created_at,
|
|
820
|
-
tool_name,
|
|
821
|
-
file_path,
|
|
822
|
-
reason,
|
|
823
|
-
${hasGfi ? 'gfi' : 'NULL'} AS gfi,
|
|
824
|
-
${hasGfiAfter ? 'gfi_after' : 'NULL'} AS gfi_after,
|
|
825
|
-
${hasTrustStage ? 'trust_stage' : 'NULL'} AS trust_stage,
|
|
826
|
-
${hasGateType ? 'gate_type' : 'NULL'} AS gate_type
|
|
827
|
-
FROM gate_blocks
|
|
828
|
-
ORDER BY created_at DESC
|
|
829
|
-
LIMIT ?
|
|
830
|
-
`;
|
|
831
|
-
|
|
832
|
-
return this.uiDb.all<GateBlockRow>(sql, safeLimit);
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
private resolveGateBlockGfi(row: GateBlockRow): number {
|
|
836
|
-
const direct = this.asNullableNumber(row.gfi ?? row.gfi_after);
|
|
837
|
-
if (direct !== null) return direct;
|
|
838
|
-
|
|
839
|
-
const reason = String(row.reason ?? '');
|
|
840
|
-
const match = /gfi\s*[:=]\s*(-?\d+(?:\.\d+)?)/i.exec(reason);
|
|
841
|
-
if (match) {
|
|
842
|
-
return this.asNumber(Number(match[1]), 0);
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
const session = this.getCurrentSession();
|
|
846
|
-
return this.asNumber(session?.currentGfi, 0);
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
private resolveGateBlockTrustStage(row: GateBlockRow, fallbackStage: number): number {
|
|
850
|
-
const direct = this.asNullableNumber(row.trust_stage);
|
|
851
|
-
if (direct !== null) return Math.max(1, Math.min(4, Math.round(direct)));
|
|
852
|
-
|
|
853
|
-
const reason = String(row.reason ?? '').toLowerCase();
|
|
854
|
-
const match = /stage\s*(\d+)/i.exec(reason);
|
|
855
|
-
if (match) {
|
|
856
|
-
return Math.max(1, Math.min(4, Math.round(this.asNumber(Number(match[1]), fallbackStage))));
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
return fallbackStage;
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
private resolveGateType(row: GateBlockRow): string {
|
|
866
|
-
if (typeof row.gate_type === 'string' && row.gate_type.trim().length > 0) {
|
|
867
|
-
return row.gate_type;
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
const reason = String(row.reason ?? '').toLowerCase();
|
|
871
|
-
if (reason.includes('gfi')) return 'gfi';
|
|
872
|
-
if (reason.includes('tier') || reason.includes('stage') || reason.includes('trust')) return 'stage';
|
|
873
|
-
if (reason.includes('p-03') || reason.includes('edit verification') || reason.includes('oldtext')) return 'p03';
|
|
874
|
-
if (reason.includes('p-16') || reason.includes('exemption')) return 'p16';
|
|
875
|
-
return 'general';
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
private hasTableColumn(tableName: string, columnName: string): boolean {
|
|
879
|
-
let cached = this.tableColumnCache.get(tableName);
|
|
880
|
-
if (!cached) {
|
|
881
|
-
const rows = this.uiDb.all<{ name: string }>(`PRAGMA table_info(${tableName})`);
|
|
882
|
-
cached = new Set(rows.map((row) => row.name));
|
|
883
|
-
this.tableColumnCache.set(tableName, cached);
|
|
884
|
-
}
|
|
885
|
-
return cached.has(columnName);
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
private scoreToStatus(score: number): string {
|
|
891
|
-
if (score >= 70) return 'healthy';
|
|
892
|
-
if (score >= 40) return 'warning';
|
|
893
|
-
return 'critical';
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
private evolutionToStatus(tier: string, points: number): string {
|
|
899
|
-
const lower = tier.toLowerCase();
|
|
900
|
-
if (lower === 'forest' || lower === 'tree') return 'healthy';
|
|
901
|
-
if (lower === 'sapling' || points >= 200) return 'warning';
|
|
902
|
-
return 'critical';
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
private safeListFiles(dirPath: string, predicate: (_name: string) => boolean): string[] {
|
|
908
|
-
if (!fs.existsSync(dirPath)) return [];
|
|
909
|
-
try {
|
|
910
|
-
return fs.readdirSync(dirPath)
|
|
911
|
-
.filter((name) => predicate(name))
|
|
912
|
-
.map((name) => path.join(dirPath, name));
|
|
913
|
-
} catch {
|
|
914
|
-
return [];
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
private readJsonFile<T>(filePath: string, fallback: T): T {
|
|
921
|
-
if (!fs.existsSync(filePath)) return fallback;
|
|
922
|
-
try {
|
|
923
|
-
return JSON.parse(fs.readFileSync(filePath, 'utf8')) as T;
|
|
924
|
-
} catch {
|
|
925
|
-
return fallback;
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
private asNumber(value: unknown, fallback: number): number {
|
|
932
|
-
return Number.isFinite(value) ? Number(value) : fallback;
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
private asNullableNumber(value: unknown): number | null {
|
|
938
|
-
if (Number.isFinite(value)) return Number(value);
|
|
939
|
-
if (typeof value === 'string' && value.trim().length > 0) {
|
|
940
|
-
const n = Number(value);
|
|
941
|
-
return Number.isFinite(n) ? n : null;
|
|
942
|
-
}
|
|
943
|
-
return null;
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
/**
|
|
947
|
-
* Sync GFI from the latest session JSON file into SQLite.
|
|
948
|
-
* Called at construction time so all queries read from a single source (SQLite).
|
|
949
|
-
*/
|
|
950
|
-
private syncGfiFromSession(): void {
|
|
951
|
-
const session = this.readLatestSessionFromFile();
|
|
952
|
-
const currentGfi = this.asNumber(session?.currentGfi, 0);
|
|
953
|
-
const dailyGfiPeak = this.asNumber(session?.dailyGfiPeak, currentGfi);
|
|
954
|
-
const today = new Date().toISOString().slice(0, 10);
|
|
955
|
-
|
|
956
|
-
try {
|
|
957
|
-
this.uiDb.execute(`
|
|
958
|
-
CREATE TABLE IF NOT EXISTS gfi_state (
|
|
959
|
-
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
960
|
-
current_gfi REAL NOT NULL DEFAULT 0,
|
|
961
|
-
daily_gfi_peak REAL NOT NULL DEFAULT 0,
|
|
962
|
-
gfi_date TEXT NOT NULL DEFAULT ''
|
|
963
|
-
)
|
|
964
|
-
`);
|
|
965
|
-
this.uiDb.run(
|
|
966
|
-
'INSERT OR REPLACE INTO gfi_state (id, current_gfi, daily_gfi_peak, gfi_date) VALUES (1, ?, ?, ?)',
|
|
967
|
-
currentGfi,
|
|
968
|
-
dailyGfiPeak,
|
|
969
|
-
today,
|
|
970
|
-
);
|
|
971
|
-
} catch {
|
|
972
|
-
// Non-critical: GFI sync failure should not block queries
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
/**
|
|
977
|
-
* Read current GFI state from SQLite.
|
|
978
|
-
*/
|
|
979
|
-
private readGfiFromDb(): { currentGfi: number; dailyGfiPeak: number } {
|
|
980
|
-
try {
|
|
981
|
-
const row = this.uiDb.get<{ current_gfi: number; daily_gfi_peak: number }>(
|
|
982
|
-
'SELECT current_gfi, daily_gfi_peak FROM gfi_state WHERE id = 1',
|
|
983
|
-
);
|
|
984
|
-
if (!row) return { currentGfi: 0, dailyGfiPeak: 0 };
|
|
985
|
-
return {
|
|
986
|
-
currentGfi: row.current_gfi ?? 0,
|
|
987
|
-
dailyGfiPeak: row.daily_gfi_peak ?? 0,
|
|
988
|
-
};
|
|
989
|
-
} catch {
|
|
990
|
-
return { currentGfi: 0, dailyGfiPeak: 0 };
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
|
|
994
|
-
/**
|
|
995
|
-
* Read the most recent session JSON file from disk.
|
|
996
|
-
* Used to sync GFI from session-tracker's persistence into SQLite.
|
|
997
|
-
*/
|
|
998
|
-
|
|
999
|
-
private readLatestSessionFromFile(): SessionState | null {
|
|
1000
|
-
const sessionsDir = path.join(this.stateDir, 'sessions');
|
|
1001
|
-
if (!fs.existsSync(sessionsDir)) {
|
|
1002
|
-
return null;
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
try {
|
|
1006
|
-
const files = fs.readdirSync(sessionsDir).filter(f => f.endsWith('.json'));
|
|
1007
|
-
if (files.length === 0) {
|
|
1008
|
-
return null;
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
let latest: SessionState | null = null;
|
|
1012
|
-
let latestTs = 0;
|
|
1013
|
-
|
|
1014
|
-
for (const file of files) {
|
|
1015
|
-
try {
|
|
1016
|
-
const content = fs.readFileSync(path.join(sessionsDir, file), 'utf-8');
|
|
1017
|
-
const state = JSON.parse(content) as SessionState;
|
|
1018
|
-
// Skip sessions from different workspaces
|
|
1019
|
-
if (state.workspaceDir && state.workspaceDir !== this.workspaceDir) {
|
|
1020
|
-
continue;
|
|
1021
|
-
}
|
|
1022
|
-
const ts = Number(state.lastControlActivityAt ?? state.lastActivityAt ?? 0);
|
|
1023
|
-
if (ts > latestTs) {
|
|
1024
|
-
latestTs = ts;
|
|
1025
|
-
latest = state;
|
|
1026
|
-
}
|
|
1027
|
-
} catch {
|
|
1028
|
-
// Skip corrupted files
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
return latest;
|
|
1033
|
-
} catch {
|
|
1034
|
-
// Non-critical: failure to read session files should not crash the service
|
|
1035
|
-
return null;
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
}
|