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
package/src/service/queue-io.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Full persistence layer encapsulating queue file locking, atomic writes,
|
|
5
5
|
* queue format, and enqueue orchestration. Depends on file-lock.ts, io.ts,
|
|
6
|
-
* queue-migration.ts,
|
|
6
|
+
* queue-migration.ts, and pain.ts.
|
|
7
7
|
* Zero imports from evolution-worker.ts.
|
|
8
8
|
*/
|
|
9
9
|
|
|
@@ -16,364 +16,93 @@ import { migrateQueueToV2 } from './queue-migration.js';
|
|
|
16
16
|
import type { EvolutionQueueItem } from '../core/evolution-types.js';
|
|
17
17
|
import type { RawQueueItem } from './queue-migration.js';
|
|
18
18
|
import type { PluginLogger } from '../openclaw-sdk.js';
|
|
19
|
-
import type { WorkspaceContext } from '../core/workspace-context.js';
|
|
20
|
-
import { CorrectionCueLearner } from '../core/correction-cue-learner.js';
|
|
21
|
-
import { readPainFlagContract } from '../core/pain.js';
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Extended EvolutionQueueItem that includes the recentPainContext field.
|
|
25
|
-
* This field is added inline in evolution-worker.ts but needs to be available
|
|
26
|
-
* in queue-io.ts for the enqueue functions.
|
|
27
|
-
*/
|
|
28
|
-
interface EvolutionQueueItemWithPain extends EvolutionQueueItem {
|
|
29
|
-
recentPainContext?: RecentPainContext;
|
|
30
|
-
}
|
|
31
19
|
|
|
32
20
|
export const EVOLUTION_QUEUE_LOCK_SUFFIX = '.lock';
|
|
33
21
|
export const LOCK_MAX_RETRIES = 50;
|
|
34
22
|
export const LOCK_RETRY_DELAY_MS = 50;
|
|
35
23
|
export const LOCK_STALE_MS = 30_000;
|
|
36
24
|
|
|
37
|
-
export const SLEEP_REFLECTION_DEDUP_WINDOW_MS = 4 * 60 * 60 * 1000; // 4 hours
|
|
38
|
-
|
|
39
|
-
// ---------------------------------------------------------------------------
|
|
40
|
-
// requireQueueLock — thin wrapper that adds LockUnavailableError
|
|
41
|
-
// ---------------------------------------------------------------------------
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Acquire a queue lock, throwing LockUnavailableError on failure.
|
|
45
|
-
* This is the standard lock used across all queue operations.
|
|
46
|
-
*/
|
|
47
25
|
export async function requireQueueLock(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
26
|
+
resourcePath: string,
|
|
27
|
+
logger: PluginLogger | { warn?: (message: string) => void; info?: (message: string) => void } | undefined,
|
|
28
|
+
scope: string,
|
|
29
|
+
lockSuffix: string = EVOLUTION_QUEUE_LOCK_SUFFIX,
|
|
52
30
|
): Promise<() => void> {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
31
|
+
try {
|
|
32
|
+
return await acquireQueueLock(resourcePath, logger, lockSuffix);
|
|
33
|
+
} catch (err) {
|
|
34
|
+
throw new LockUnavailableError(resourcePath, scope, { cause: err });
|
|
35
|
+
}
|
|
58
36
|
}
|
|
59
37
|
|
|
60
|
-
// ---------------------------------------------------------------------------
|
|
61
|
-
// RecentPainContext
|
|
62
|
-
// ---------------------------------------------------------------------------
|
|
63
|
-
|
|
64
|
-
export interface RecentPainContext {
|
|
65
|
-
mostRecent: {
|
|
66
|
-
score: number;
|
|
67
|
-
source: string;
|
|
68
|
-
reason: string;
|
|
69
|
-
timestamp: string;
|
|
70
|
-
sessionId: string;
|
|
71
|
-
/** Trajectory pain_events row ID — set when pain flag includes pain_event_id */
|
|
72
|
-
painEventId?: number;
|
|
73
|
-
} | null;
|
|
74
|
-
recentPainCount: number;
|
|
75
|
-
recentMaxPainScore: number;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// ---------------------------------------------------------------------------
|
|
79
|
-
// Task ID creation
|
|
80
|
-
// ---------------------------------------------------------------------------
|
|
81
|
-
|
|
82
38
|
export function createEvolutionTaskId(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
39
|
+
source: string,
|
|
40
|
+
score: number,
|
|
41
|
+
preview: string,
|
|
42
|
+
reason: string,
|
|
43
|
+
now: number,
|
|
88
44
|
): string {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
45
|
+
return createHash('md5')
|
|
46
|
+
.update(`${source}:${score}:${preview}:${reason}:${now}`)
|
|
47
|
+
.digest('hex')
|
|
48
|
+
.substring(0, 8);
|
|
93
49
|
}
|
|
94
50
|
|
|
95
|
-
// ---------------------------------------------------------------------------
|
|
96
|
-
// Queue helpers
|
|
97
|
-
// ---------------------------------------------------------------------------
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Check whether a specific task kind has a pending or in-progress entry.
|
|
101
|
-
*/
|
|
102
51
|
export function hasPendingTask(queue: EvolutionQueueItem[], taskKind: string): boolean {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Build a dedup key from pain context.
|
|
110
|
-
* Returns null when no pain context is available (bypasses dedup).
|
|
111
|
-
*/
|
|
112
|
-
function buildPainSourceKey(
|
|
113
|
-
painCtx: ReturnType<typeof readRecentPainContext>,
|
|
114
|
-
): string | null {
|
|
115
|
-
if (!painCtx.mostRecent) return null;
|
|
116
|
-
return `${painCtx.mostRecent.source}::${painCtx.mostRecent.reason?.slice(0, 50) ?? ''}`;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Check whether a similar sleep_reflection task completed recently.
|
|
121
|
-
*/
|
|
122
|
-
function hasRecentSimilarReflection(
|
|
123
|
-
queue: EvolutionQueueItemWithPain[],
|
|
124
|
-
painSourceKey: string,
|
|
125
|
-
now: number,
|
|
126
|
-
): EvolutionQueueItem | null {
|
|
127
|
-
return queue.find((t) => {
|
|
128
|
-
if (t.taskKind !== 'sleep_reflection') return false;
|
|
129
|
-
if (t.status !== 'completed') return false;
|
|
130
|
-
if (!t.completed_at) return false;
|
|
131
|
-
const age = now - new Date(t.completed_at).getTime();
|
|
132
|
-
if (age > SLEEP_REFLECTION_DEDUP_WINDOW_MS) return false;
|
|
133
|
-
const taskPainKey = buildPainSourceKey(t.recentPainContext ?? { mostRecent: null, recentPainCount: 0, recentMaxPainScore: 0 });
|
|
134
|
-
if (!taskPainKey) return false;
|
|
135
|
-
return taskPainKey === painSourceKey;
|
|
136
|
-
}) ?? null;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// ---------------------------------------------------------------------------
|
|
140
|
-
// Pain context
|
|
141
|
-
// ---------------------------------------------------------------------------
|
|
142
|
-
|
|
143
|
-
export function readRecentPainContext(wctx: WorkspaceContext): RecentPainContext {
|
|
144
|
-
const contract = readPainFlagContract(wctx.workspaceDir);
|
|
145
|
-
if (contract.status !== 'valid') {
|
|
146
|
-
return { mostRecent: null, recentPainCount: 0, recentMaxPainScore: 0 };
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
try {
|
|
150
|
-
const score = parseInt(contract.data.score ?? '0', 10) || 0;
|
|
151
|
-
const source = contract.data.source ?? '';
|
|
152
|
-
const reason = contract.data.reason ?? '';
|
|
153
|
-
const timestamp = contract.data.time ?? '';
|
|
154
|
-
const sessionId = contract.data.session_id ?? '';
|
|
155
|
-
const painEventIdRaw = contract.data.pain_event_id;
|
|
156
|
-
const painEventId = painEventIdRaw ? parseInt(painEventIdRaw, 10) : undefined;
|
|
157
|
-
|
|
158
|
-
if (score > 0) {
|
|
159
|
-
return {
|
|
160
|
-
mostRecent: { score, source, reason, timestamp, sessionId, painEventId },
|
|
161
|
-
recentPainCount: 1,
|
|
162
|
-
recentMaxPainScore: score,
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
} catch (err) {
|
|
166
|
-
// Best effort — non-fatal, but surface unexpected errors
|
|
167
|
-
|
|
168
|
-
console.warn(`[queue-io] Failed to read pain context (non-fatal): ${String(err)}`);
|
|
169
|
-
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return { mostRecent: null, recentPainCount: 0, recentMaxPainScore: 0 };
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Decide whether to skip enqueuing due to a recent similar reflection.
|
|
177
|
-
*/
|
|
178
|
-
export function shouldSkipForDedup(
|
|
179
|
-
queue: EvolutionQueueItemWithPain[],
|
|
180
|
-
wctx: WorkspaceContext,
|
|
181
|
-
logger: PluginLogger | undefined,
|
|
182
|
-
): boolean {
|
|
183
|
-
const recentPainContext = readRecentPainContext(wctx);
|
|
184
|
-
const painSourceKey = buildPainSourceKey(recentPainContext);
|
|
185
|
-
|
|
186
|
-
if (!painSourceKey) return false;
|
|
187
|
-
|
|
188
|
-
const now = Date.now();
|
|
189
|
-
const recentSimilarReflection = hasRecentSimilarReflection(queue, painSourceKey, now);
|
|
190
|
-
|
|
191
|
-
if (recentSimilarReflection) {
|
|
192
|
-
const completedTime = new Date(recentSimilarReflection.completed_at!).getTime();
|
|
193
|
-
logger?.debug?.(`[PD:EvolutionWorker] Skipping sleep_reflection — similar reflection completed ${Math.round((now - completedTime) / 60000)}min ago (same pain pattern: ${painSourceKey})`);
|
|
194
|
-
return true;
|
|
195
|
-
}
|
|
196
|
-
return false;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// ---------------------------------------------------------------------------
|
|
200
|
-
// Enqueue functions
|
|
201
|
-
// ---------------------------------------------------------------------------
|
|
202
|
-
|
|
203
|
-
function enqueueNewSleepReflectionTask(
|
|
204
|
-
queue: EvolutionQueueItemWithPain[],
|
|
205
|
-
recentPainContext: ReturnType<typeof readRecentPainContext>,
|
|
206
|
-
queuePath: string,
|
|
207
|
-
logger: PluginLogger | undefined,
|
|
208
|
-
): void {
|
|
209
|
-
const taskId = createEvolutionTaskId('nocturnal', 50, 'idle workspace', 'Sleep-mode reflection', Date.now());
|
|
210
|
-
const nowIso = new Date().toISOString();
|
|
211
|
-
|
|
212
|
-
queue.push({
|
|
213
|
-
id: taskId,
|
|
214
|
-
taskKind: 'sleep_reflection',
|
|
215
|
-
priority: 'medium',
|
|
216
|
-
score: 50,
|
|
217
|
-
source: 'nocturnal',
|
|
218
|
-
reason: 'Sleep-mode reflection triggered by idle workspace',
|
|
219
|
-
trigger_text_preview: 'Idle workspace detected',
|
|
220
|
-
timestamp: nowIso,
|
|
221
|
-
enqueued_at: nowIso,
|
|
222
|
-
status: 'pending',
|
|
223
|
-
traceId: taskId,
|
|
224
|
-
retryCount: 0,
|
|
225
|
-
maxRetries: 1,
|
|
226
|
-
recentPainContext,
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
// Cast to EvolutionQueueItem[] because saveEvolutionQueue expects the base type
|
|
230
|
-
// but the queue may contain extended fields (recentPainContext) that are
|
|
231
|
-
// serialized as part of the JSON - this is safe at runtime.
|
|
232
|
-
saveEvolutionQueue(queuePath, queue as unknown as EvolutionQueueItem[]);
|
|
233
|
-
logger?.info?.(`[PD:EvolutionWorker] Enqueued sleep_reflection task ${taskId}`);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Enqueue a sleep_reflection task if one is not already pending.
|
|
238
|
-
*/
|
|
239
|
-
export async function enqueueSleepReflectionTask(
|
|
240
|
-
wctx: WorkspaceContext,
|
|
241
|
-
logger: PluginLogger | undefined,
|
|
242
|
-
): Promise<void> {
|
|
243
|
-
const queuePath = wctx.resolve('EVOLUTION_QUEUE');
|
|
244
|
-
const releaseLock = await requireQueueLock(queuePath, logger, 'enqueueSleepReflection', EVOLUTION_QUEUE_LOCK_SUFFIX);
|
|
245
|
-
|
|
246
|
-
try {
|
|
247
|
-
const queue = loadEvolutionQueue(queuePath);
|
|
248
|
-
|
|
249
|
-
if (hasPendingTask(queue, 'sleep_reflection')) {
|
|
250
|
-
logger?.debug?.('[PD:EvolutionWorker] sleep_reflection task already pending/in-progress, skipping');
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (shouldSkipForDedup(queue, wctx, logger)) {
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
const recentPainContext = readRecentPainContext(wctx);
|
|
259
|
-
enqueueNewSleepReflectionTask(queue, recentPainContext, queuePath, logger);
|
|
260
|
-
} finally {
|
|
261
|
-
releaseLock();
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Enqueue a keyword_optimization task if one is not already pending/in-progress.
|
|
267
|
-
*/
|
|
268
|
-
export async function enqueueKeywordOptimizationTask(
|
|
269
|
-
wctx: WorkspaceContext,
|
|
270
|
-
logger: PluginLogger | undefined,
|
|
271
|
-
): Promise<void> {
|
|
272
|
-
const queuePath = wctx.resolve('EVOLUTION_QUEUE');
|
|
273
|
-
const releaseLock = await requireQueueLock(queuePath, logger, 'enqueueKeywordOpt', EVOLUTION_QUEUE_LOCK_SUFFIX);
|
|
274
|
-
|
|
275
|
-
try {
|
|
276
|
-
const queue = loadEvolutionQueue(queuePath);
|
|
277
|
-
|
|
278
|
-
if (hasPendingTask(queue, 'keyword_optimization')) {
|
|
279
|
-
logger?.debug?.('[PD:EvolutionWorker] keyword_optimization task already pending/in-progress, skipping');
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const learner = CorrectionCueLearner.get(wctx.stateDir);
|
|
284
|
-
if (!learner.canRunKeywordOptimization()) {
|
|
285
|
-
logger?.debug?.('[PD:EvolutionWorker] keyword_optimization throttle exhausted, skipping');
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const taskId = createEvolutionTaskId('keyword_optimization', 50, 'keyword optimization', 'Keyword optimization via LLM', Date.now());
|
|
290
|
-
const nowIso = new Date().toISOString();
|
|
291
|
-
|
|
292
|
-
queue.push({
|
|
293
|
-
id: taskId,
|
|
294
|
-
taskKind: 'keyword_optimization',
|
|
295
|
-
priority: 'medium',
|
|
296
|
-
score: 50,
|
|
297
|
-
source: 'correction',
|
|
298
|
-
reason: 'Keyword optimization triggered by heartbeat',
|
|
299
|
-
trigger_text_preview: 'Keyword optimization via LLM',
|
|
300
|
-
timestamp: nowIso,
|
|
301
|
-
enqueued_at: nowIso,
|
|
302
|
-
status: 'pending',
|
|
303
|
-
traceId: taskId,
|
|
304
|
-
retryCount: 0,
|
|
305
|
-
maxRetries: 1,
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
saveEvolutionQueue(queuePath, queue);
|
|
309
|
-
logger?.info?.(`[PD:EvolutionWorker] Enqueued keyword_optimization task ${taskId}`);
|
|
310
|
-
} finally {
|
|
311
|
-
releaseLock();
|
|
312
|
-
}
|
|
52
|
+
return queue.some(
|
|
53
|
+
(t) => t.taskKind === taskKind && (t.status === 'pending' || t.status === 'in_progress'),
|
|
54
|
+
);
|
|
313
55
|
}
|
|
314
56
|
|
|
315
57
|
export async function acquireQueueLock(
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
58
|
+
resourcePath: string,
|
|
59
|
+
logger: PluginLogger | { warn?: (message: string) => void; info?: (message: string) => void } | undefined,
|
|
60
|
+
lockSuffix: string = EVOLUTION_QUEUE_LOCK_SUFFIX,
|
|
319
61
|
): Promise<() => void> {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
62
|
+
try {
|
|
63
|
+
const ctx: LockContext = await acquireLockAsync(resourcePath, {
|
|
64
|
+
lockSuffix,
|
|
65
|
+
maxRetries: LOCK_MAX_RETRIES,
|
|
66
|
+
baseRetryDelayMs: LOCK_RETRY_DELAY_MS,
|
|
67
|
+
lockStaleMs: LOCK_STALE_MS,
|
|
68
|
+
});
|
|
69
|
+
return () => releaseImportedLock(ctx);
|
|
70
|
+
} catch (error: unknown) {
|
|
71
|
+
const warn = logger?.warn;
|
|
72
|
+
warn?.(`[PD:EvolutionWorker] Failed to acquire lock for ${resourcePath}: ${String(error)}`);
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
333
75
|
}
|
|
334
76
|
|
|
335
|
-
/**
|
|
336
|
-
* RAII-style lock guard — always releases the lock on exceptions.
|
|
337
|
-
*/
|
|
338
77
|
export async function withQueueLock<T>(
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
78
|
+
resourcePath: string,
|
|
79
|
+
logger: PluginLogger | { warn?: (message: string) => void; info?: (message: string) => void } | undefined,
|
|
80
|
+
scope: string,
|
|
81
|
+
fn: () => Promise<T>,
|
|
343
82
|
): Promise<T> {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
83
|
+
const releaseLock = await acquireQueueLock(resourcePath, logger, EVOLUTION_QUEUE_LOCK_SUFFIX);
|
|
84
|
+
try {
|
|
85
|
+
return await fn();
|
|
86
|
+
} finally {
|
|
87
|
+
releaseLock();
|
|
88
|
+
}
|
|
350
89
|
}
|
|
351
90
|
|
|
352
|
-
/**
|
|
353
|
-
* Load and migrate the evolution queue. Returns empty array if file doesn't exist.
|
|
354
|
-
*/
|
|
355
91
|
export function loadEvolutionQueue(queuePath: string): EvolutionQueueItem[] {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
console.warn(`[queue-io] Failed to load evolution queue (recovering with empty): ${String(err)}`);
|
|
367
|
-
|
|
368
|
-
rawQueue = [];
|
|
369
|
-
}
|
|
92
|
+
let rawQueue: RawQueueItem[] = [];
|
|
93
|
+
try {
|
|
94
|
+
rawQueue = JSON.parse(fs.readFileSync(queuePath, 'utf8'));
|
|
95
|
+
} catch (err) {
|
|
96
|
+
if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
97
|
+
rawQueue = [];
|
|
98
|
+
} else {
|
|
99
|
+
console.warn(`[queue-io] Failed to load evolution queue (recovering with empty): ${String(err)}`);
|
|
100
|
+
rawQueue = [];
|
|
370
101
|
}
|
|
371
|
-
|
|
102
|
+
}
|
|
103
|
+
return migrateQueueToV2(rawQueue) as unknown as EvolutionQueueItem[];
|
|
372
104
|
}
|
|
373
105
|
|
|
374
|
-
/**
|
|
375
|
-
* Atomically write the queue to disk.
|
|
376
|
-
*/
|
|
377
106
|
export function saveEvolutionQueue(queuePath: string, queue: EvolutionQueueItem[]): void {
|
|
378
|
-
|
|
107
|
+
atomicWriteFileSync(queuePath, JSON.stringify(queue, null, 2));
|
|
379
108
|
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
|
+
import Database from 'better-sqlite3';
|
|
2
3
|
import * as path from 'path';
|
|
3
4
|
import { readPainFlagData } from '../core/pain.js';
|
|
4
5
|
import { listSessions } from '../core/session-tracker.js';
|
|
5
6
|
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
6
7
|
import { evaluatePhase3Inputs } from './phase3-input-filter.js';
|
|
7
8
|
import { TrajectoryRegistry } from '../core/trajectory.js';
|
|
8
|
-
import { getPendingDiagnosticianTasks } from '../core/diagnostician-task-store.js';
|
|
9
9
|
import type { WorkflowStage } from '../core/workflow-funnel-loader.js';
|
|
10
10
|
import type { RuntimeTruth, AnalyticsTruth } from '../types/runtime-summary.js';
|
|
11
|
+
import { buildGfiWorkspaceSnapshot } from '@principles/core/runtime-v2';
|
|
12
|
+
import type { GfiWorkspaceSnapshot } from '@principles/core/runtime-v2';
|
|
11
13
|
|
|
12
14
|
export type RuntimeDataQuality = 'authoritative' | 'partial';
|
|
13
15
|
export type RuntimeRewardPolicy =
|
|
@@ -47,6 +49,7 @@ export interface RuntimeSummary {
|
|
|
47
49
|
peak: number | null;
|
|
48
50
|
sources: RuntimeSummarySource[];
|
|
49
51
|
dataQuality: RuntimeDataQuality;
|
|
52
|
+
workspaceSnapshot?: GfiWorkspaceSnapshot;
|
|
50
53
|
};
|
|
51
54
|
evolution: {
|
|
52
55
|
queue: {
|
|
@@ -62,8 +65,8 @@ export interface RuntimeSummary {
|
|
|
62
65
|
};
|
|
63
66
|
dataQuality: RuntimeDataQuality;
|
|
64
67
|
};
|
|
65
|
-
// D:
|
|
66
|
-
|
|
68
|
+
// D: Runtime Diagnostician chain (M8) — replaced legacy heartbeat path
|
|
69
|
+
runtimeDiagnosis: {
|
|
67
70
|
/** Tasks pending in diagnostician_tasks.json (not yet processed by heartbeat) */
|
|
68
71
|
pendingTasks: number;
|
|
69
72
|
/** Total diagnosis tasks written by evolution worker (today from event log) */
|
|
@@ -123,6 +126,10 @@ export interface RuntimeSummary {
|
|
|
123
126
|
interface PersistedSessionState {
|
|
124
127
|
sessionId: string;
|
|
125
128
|
currentGfi?: number;
|
|
129
|
+
gfiBySource?: Record<string, number>;
|
|
130
|
+
lastErrorSource?: string;
|
|
131
|
+
consecutiveErrors?: number;
|
|
132
|
+
lastGfiDecayAt?: number;
|
|
126
133
|
dailyGfiPeak?: number;
|
|
127
134
|
lastActivityAt?: number;
|
|
128
135
|
lastControlActivityAt?: number;
|
|
@@ -222,7 +229,7 @@ export class RuntimeSummaryService {
|
|
|
222
229
|
const selectedSession = this.selectSession(sessions, options?.sessionId ?? null);
|
|
223
230
|
const selectedSessionId = selectedSession.session?.sessionId ?? null;
|
|
224
231
|
|
|
225
|
-
const persistedEvents = this.readEvents(path.join(wctx.stateDir, 'logs'
|
|
232
|
+
const persistedEvents = this.readEvents(path.join(wctx.stateDir, 'logs'), warnings);
|
|
226
233
|
const hasBufferedEventAccess =
|
|
227
234
|
typeof (wctx.eventLog as { getBufferedEvents?: () => EventLogEntry[] }).getBufferedEvents === 'function';
|
|
228
235
|
const bufferedEvents = hasBufferedEventAccess
|
|
@@ -311,6 +318,23 @@ export class RuntimeSummaryService {
|
|
|
311
318
|
const gfiPeak =
|
|
312
319
|
sessionPeak ?? (Number.isFinite(dailyGfiPeak) ? Number(dailyGfiPeak) : null);
|
|
313
320
|
|
|
321
|
+
// PRI-78/PRI-82: Build authoritative GFI workspace snapshot (active vs stale)
|
|
322
|
+
// Uses real persisted GFI fields: gfiBySource, consecutiveErrors, lastErrorSource,
|
|
323
|
+
// lastGfiDecayAt, dailyGfiPeak from session-tracker persistence.
|
|
324
|
+
const gfiWorkspaceSnapshot: GfiWorkspaceSnapshot = buildGfiWorkspaceSnapshot({
|
|
325
|
+
sessions: sessions.map((s) => ({
|
|
326
|
+
sessionId: s.sessionId,
|
|
327
|
+
currentGfi: s.currentGfi ?? 0,
|
|
328
|
+
gfiBySource: s.gfiBySource ?? {},
|
|
329
|
+
consecutiveErrors: s.consecutiveErrors ?? 0,
|
|
330
|
+
lastErrorSource: s.lastErrorSource,
|
|
331
|
+
lastGfiDecayAt: s.lastGfiDecayAt,
|
|
332
|
+
dailyGfiPeak: s.dailyGfiPeak,
|
|
333
|
+
lastActivityAt: s.lastControlActivityAt ?? s.lastActivityAt ?? 0,
|
|
334
|
+
})),
|
|
335
|
+
nowMs: Date.now(),
|
|
336
|
+
});
|
|
337
|
+
|
|
314
338
|
pushWarning(warnings, GFI_PARTIAL_WARNING);
|
|
315
339
|
if (sessionPeak === null && Number.isFinite(dailyGfiPeak)) {
|
|
316
340
|
pushWarning(warnings, DAILY_GFI_WARNING);
|
|
@@ -343,13 +367,25 @@ export class RuntimeSummaryService {
|
|
|
343
367
|
const gfiSources = this.buildGfiSources(events, selectedSessionId);
|
|
344
368
|
const gateStats = this.buildGateStats(events, selectedSessionId, warnings);
|
|
345
369
|
|
|
346
|
-
// D:
|
|
347
|
-
// Read pending tasks from the diagnostician task store
|
|
348
|
-
const pendingDiagTasks = getPendingDiagnosticianTasks(wctx.stateDir);
|
|
370
|
+
// D: Runtime Diagnostician chain (M8) — queries runtime-v2 SQLite task store
|
|
349
371
|
// Read heartbeat diagnosis stats from daily event log
|
|
350
372
|
const diagDailyStats = dailyStats?.[todayStr]?.evolution;
|
|
351
|
-
|
|
352
|
-
|
|
373
|
+
let pendingRuntimeDiagTasks = 0;
|
|
374
|
+
try {
|
|
375
|
+
const taskStoreDbPath = path.join(wctx.stateDir, '.principles', 'db', 'task-store.db');
|
|
376
|
+
if (fs.existsSync(taskStoreDbPath)) {
|
|
377
|
+
const db = new Database(taskStoreDbPath, { readonly: true });
|
|
378
|
+
const row = db.prepare(`
|
|
379
|
+
SELECT COUNT(*) as count FROM tasks
|
|
380
|
+
WHERE task_kind = 'diagnostician' AND status = 'pending'
|
|
381
|
+
`).get() as { count: number };
|
|
382
|
+
pendingRuntimeDiagTasks = row?.count ?? 0;
|
|
383
|
+
db.close();
|
|
384
|
+
}
|
|
385
|
+
} catch { /* task store not yet initialized — 0 pending */ }
|
|
386
|
+
// TODO(PRI-XXX): distinguish "not initialized" from permission/corruption/query errors
|
|
387
|
+
const runtimeDiagnosis = {
|
|
388
|
+
pendingTasks: pendingRuntimeDiagTasks,
|
|
353
389
|
tasksWrittenToday: diagDailyStats?.diagnosisTasksWritten ?? 0,
|
|
354
390
|
reportsWrittenToday: diagDailyStats?.diagnosticianReportsWritten ?? 0,
|
|
355
391
|
reportsMissingJsonToday: diagDailyStats?.reportsMissingJson ?? 0,
|
|
@@ -358,6 +394,22 @@ export class RuntimeSummaryService {
|
|
|
358
394
|
heartbeatsInjectedToday: diagDailyStats?.heartbeatsInjected ?? 0,
|
|
359
395
|
};
|
|
360
396
|
|
|
397
|
+
// D: Stall detection — high-signal warning when the diagnostician loop appears broken.
|
|
398
|
+
// Conditions: tasks are being injected (heartbeats > 0) but no reports are being written.
|
|
399
|
+
if (
|
|
400
|
+
runtimeDiagnosis.heartbeatsInjectedToday > 0 &&
|
|
401
|
+
runtimeDiagnosis.reportsWrittenToday === 0 &&
|
|
402
|
+
runtimeDiagnosis.pendingTasks > 0
|
|
403
|
+
) {
|
|
404
|
+
pushWarning(
|
|
405
|
+
warnings,
|
|
406
|
+
'Diagnostician appears stalled: heartbeats are injecting tasks ' +
|
|
407
|
+
`(${runtimeDiagnosis.heartbeatsInjectedToday}) but no reports are being written. ` +
|
|
408
|
+
`${runtimeDiagnosis.pendingTasks} task(s) remain pending. ` +
|
|
409
|
+
'Check prompt injection size limits and diagnostician task processing.'
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
|
|
361
413
|
// Read trajectory analytics data (historical data, NOT runtime truth)
|
|
362
414
|
const trajectoryStats = this.readTrajectoryStats(workspaceDir, warnings);
|
|
363
415
|
|
|
@@ -406,6 +458,7 @@ export class RuntimeSummaryService {
|
|
|
406
458
|
peak: gfiPeak,
|
|
407
459
|
sources: gfiSources,
|
|
408
460
|
dataQuality: 'partial',
|
|
461
|
+
workspaceSnapshot: gfiWorkspaceSnapshot,
|
|
409
462
|
},
|
|
410
463
|
evolution: {
|
|
411
464
|
queue: queueStats,
|
|
@@ -435,8 +488,8 @@ export class RuntimeSummaryService {
|
|
|
435
488
|
lastSignal: lastPainSignal,
|
|
436
489
|
},
|
|
437
490
|
gate: gateStats,
|
|
438
|
-
// D: Heartbeat Diagnostician chain
|
|
439
|
-
|
|
491
|
+
// D: Heartbeat Diagnostician chain
|
|
492
|
+
runtimeDiagnosis,
|
|
440
493
|
...(workflowFunnelsOutput && { workflowFunnels: workflowFunnelsOutput }),
|
|
441
494
|
metadata: {
|
|
442
495
|
generatedAt,
|
|
@@ -508,6 +561,12 @@ export class RuntimeSummaryService {
|
|
|
508
561
|
sessionId: live.sessionId,
|
|
509
562
|
currentGfi:
|
|
510
563
|
Number.isFinite(live.currentGfi) ? Number(live.currentGfi) : persisted?.currentGfi,
|
|
564
|
+
gfiBySource:
|
|
565
|
+
live.gfiBySource ? { ...live.gfiBySource } : persisted?.gfiBySource,
|
|
566
|
+
lastErrorSource: live.lastErrorSource || persisted?.lastErrorSource,
|
|
567
|
+
consecutiveErrors:
|
|
568
|
+
Number.isFinite(live.consecutiveErrors) ? Number(live.consecutiveErrors) : persisted?.consecutiveErrors,
|
|
569
|
+
lastGfiDecayAt: live.lastGfiDecayAt || persisted?.lastGfiDecayAt,
|
|
511
570
|
dailyGfiPeak:
|
|
512
571
|
Number.isFinite(live.dailyGfiPeak) ? Number(live.dailyGfiPeak) : persisted?.dailyGfiPeak,
|
|
513
572
|
lastActivityAt:
|
|
@@ -596,14 +655,49 @@ export class RuntimeSummaryService {
|
|
|
596
655
|
};
|
|
597
656
|
}
|
|
598
657
|
|
|
599
|
-
private static readEvents(
|
|
600
|
-
|
|
601
|
-
|
|
658
|
+
private static readEvents(logsDir: string, warnings: string[]): EventLogEntry[] {
|
|
659
|
+
// The event log is stored as daily files: events_YYYY-MM-DD.jsonl.
|
|
660
|
+
// Prefer today's file; fall back to the most recent daily file so that
|
|
661
|
+
// gate/pain stats are still populated when the day rolled over.
|
|
662
|
+
const dir = logsDir;
|
|
663
|
+
|
|
664
|
+
let bestFile: string | null = null;
|
|
665
|
+
|
|
666
|
+
if (fs.existsSync(dir)) {
|
|
667
|
+
const today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
|
|
668
|
+
// Prefer exact match on today's file
|
|
669
|
+
const todayFile = path.join(dir, `events_${today}.jsonl`);
|
|
670
|
+
if (fs.existsSync(todayFile)) {
|
|
671
|
+
bestFile = todayFile;
|
|
672
|
+
} else {
|
|
673
|
+
// Fallback: pick the most recent file by date embedded in the filename
|
|
674
|
+
// (lexical comparison works for ISO dates YYYY-MM-DD).
|
|
675
|
+
let newestDate = '';
|
|
676
|
+
try {
|
|
677
|
+
for (const file of fs.readdirSync(dir)) {
|
|
678
|
+
const m = file.match(/^events_(\d{4}-\d{2}-\d{2})\.jsonl$/);
|
|
679
|
+
if (!m) continue;
|
|
680
|
+
const fileDate = m[1];
|
|
681
|
+
if (fileDate > newestDate) {
|
|
682
|
+
newestDate = fileDate;
|
|
683
|
+
bestFile = path.join(dir, file);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
} catch { /* ignore scan errors */ }
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
if (!bestFile) {
|
|
691
|
+
pushWarning(
|
|
692
|
+
warnings,
|
|
693
|
+
'No event log file found; recent pain and gate summaries are partial. ' +
|
|
694
|
+
'Expected format: events_YYYY-MM-DD.jsonl in the logs directory.'
|
|
695
|
+
);
|
|
602
696
|
return [];
|
|
603
697
|
}
|
|
604
698
|
|
|
605
699
|
try {
|
|
606
|
-
const raw = fs.readFileSync(
|
|
700
|
+
const raw = fs.readFileSync(bestFile, 'utf8').trim();
|
|
607
701
|
if (!raw) return [];
|
|
608
702
|
let parseFailures = 0;
|
|
609
703
|
const entries = raw
|
|
@@ -620,12 +714,15 @@ export class RuntimeSummaryService {
|
|
|
620
714
|
if (parseFailures > 0) {
|
|
621
715
|
pushWarning(
|
|
622
716
|
warnings,
|
|
623
|
-
`Skipped ${parseFailures} malformed event line${parseFailures === 1 ? '' : 's'} while reading
|
|
717
|
+
`Skipped ${parseFailures} malformed event line${parseFailures === 1 ? '' : 's'} while reading ${path.basename(bestFile!)}.`
|
|
624
718
|
);
|
|
625
719
|
}
|
|
626
720
|
return entries;
|
|
627
721
|
} catch {
|
|
628
|
-
pushWarning(
|
|
722
|
+
pushWarning(
|
|
723
|
+
warnings,
|
|
724
|
+
`Failed to read ${path.basename(bestFile!)}; recent pain and gate summaries are partial.`
|
|
725
|
+
);
|
|
629
726
|
return [];
|
|
630
727
|
}
|
|
631
728
|
}
|
|
@@ -657,7 +754,7 @@ export class RuntimeSummaryService {
|
|
|
657
754
|
|
|
658
755
|
return {
|
|
659
756
|
source: `tool_failure:${String(entry.data?.toolName ?? 'unknown')}`,
|
|
660
|
-
score: this.asFiniteNumber(entry.data?.gfi),
|
|
757
|
+
score: this.asFiniteNumber(entry.data?.gfiAfter ?? entry.data?.gfi),
|
|
661
758
|
ts: entry.ts,
|
|
662
759
|
};
|
|
663
760
|
});
|