principles-disciple 1.72.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 +459 -439
- 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 +59 -16
- 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 +32 -17
- 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 +3 -1
- 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
|
@@ -15,42 +15,6 @@ vi.mock('../../src/core/session-tracker.js', () => ({
|
|
|
15
15
|
listSessions: vi.fn(() => []),
|
|
16
16
|
}));
|
|
17
17
|
|
|
18
|
-
const { mockStartWorkflow, mockGetWorkflowDebugSummary } = vi.hoisted(() => ({
|
|
19
|
-
mockStartWorkflow: vi.fn(),
|
|
20
|
-
mockGetWorkflowDebugSummary: vi.fn(),
|
|
21
|
-
}));
|
|
22
|
-
|
|
23
|
-
vi.mock('../../src/service/subagent-workflow/nocturnal-workflow-manager.js', () => ({
|
|
24
|
-
NocturnalWorkflowManager: class {
|
|
25
|
-
startWorkflow = mockStartWorkflow;
|
|
26
|
-
getWorkflowDebugSummary = mockGetWorkflowDebugSummary;
|
|
27
|
-
},
|
|
28
|
-
nocturnalWorkflowSpec: {
|
|
29
|
-
workflowType: 'nocturnal',
|
|
30
|
-
transport: 'runtime_direct',
|
|
31
|
-
timeoutMs: 15 * 60 * 1000,
|
|
32
|
-
ttlMs: 30 * 60 * 1000,
|
|
33
|
-
},
|
|
34
|
-
}));
|
|
35
|
-
|
|
36
|
-
const { mockGetNocturnalSessionSnapshot, mockListRecentNocturnalCandidateSessions } = vi.hoisted(() => ({
|
|
37
|
-
mockGetNocturnalSessionSnapshot: vi.fn(),
|
|
38
|
-
mockListRecentNocturnalCandidateSessions: vi.fn(() => []),
|
|
39
|
-
}));
|
|
40
|
-
|
|
41
|
-
vi.mock('../../src/core/nocturnal-trajectory-extractor.js', async () => {
|
|
42
|
-
const actual = await vi.importActual<typeof import('../../src/core/nocturnal-trajectory-extractor.js')>(
|
|
43
|
-
'../../src/core/nocturnal-trajectory-extractor.js'
|
|
44
|
-
);
|
|
45
|
-
return {
|
|
46
|
-
...actual,
|
|
47
|
-
createNocturnalTrajectoryExtractor: vi.fn(() => ({
|
|
48
|
-
getNocturnalSessionSnapshot: mockGetNocturnalSessionSnapshot,
|
|
49
|
-
listRecentNocturnalCandidateSessions: mockListRecentNocturnalCandidateSessions,
|
|
50
|
-
})),
|
|
51
|
-
};
|
|
52
|
-
});
|
|
53
|
-
|
|
54
18
|
import { EvolutionWorkerService } from '../../src/service/evolution-worker.js';
|
|
55
19
|
import { safeRmDir } from '../test-utils.js';
|
|
56
20
|
|
|
@@ -88,8 +52,7 @@ describe('EvolutionWorkerService timeout mechanisms', () => {
|
|
|
88
52
|
|
|
89
53
|
// ── Pain diagnosis timeout (30 min) ──
|
|
90
54
|
|
|
91
|
-
|
|
92
|
-
it.skip('times out pain_diagnosis task after 30 minutes → resolution = diagnostician_timeout', async () => { const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-timeout-pain-'));
|
|
55
|
+
it('drops legacy pain_diagnosis tasks correctly on startup', async () => { const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-timeout-pain-'));
|
|
93
56
|
const stateDir = path.join(workspaceDir, '.state');
|
|
94
57
|
fs.mkdirSync(stateDir, { recursive: true });
|
|
95
58
|
|
|
@@ -138,21 +101,20 @@ describe('EvolutionWorkerService timeout mechanisms', () => {
|
|
|
138
101
|
const queue = readQueue(stateDir);
|
|
139
102
|
const task = queue.find((t: any) => t.id === 'timeout-test-30min');
|
|
140
103
|
|
|
141
|
-
expect(task
|
|
142
|
-
expect(task.resolution).toBe('diagnostician_timeout');
|
|
143
|
-
expect(task.completed_at).toBeDefined();
|
|
104
|
+
expect(task).toBeUndefined();
|
|
144
105
|
} finally {
|
|
145
106
|
EvolutionWorkerService.stop!({ workspaceDir, stateDir, logger: console } as any);
|
|
146
107
|
safeRmDir(workspaceDir);
|
|
147
108
|
}
|
|
148
109
|
});
|
|
149
110
|
|
|
150
|
-
it('
|
|
111
|
+
it('drops legacy pain_diagnosis tasks instead of timing them out', async () => {
|
|
151
112
|
const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-no-timeout-'));
|
|
152
113
|
const stateDir = path.join(workspaceDir, '.state');
|
|
153
114
|
fs.mkdirSync(stateDir, { recursive: true });
|
|
154
115
|
|
|
155
|
-
//
|
|
116
|
+
// Runtime v2 owns pain diagnosis. EvolutionWorker should not keep or
|
|
117
|
+
// timeout legacy pain_diagnosis queue items.
|
|
156
118
|
const startedAt = new Date(Date.now() - 10 * 60 * 1000).toISOString();
|
|
157
119
|
fs.writeFileSync(
|
|
158
120
|
path.join(stateDir, 'evolution_queue.json'),
|
|
@@ -196,85 +158,7 @@ describe('EvolutionWorkerService timeout mechanisms', () => {
|
|
|
196
158
|
const queue = readQueue(stateDir);
|
|
197
159
|
const task = queue.find((t: any) => t.id === 'no-timeout-10min');
|
|
198
160
|
|
|
199
|
-
|
|
200
|
-
expect(task.status).toBe('in_progress');
|
|
201
|
-
expect(task.resolution).toBeUndefined();
|
|
202
|
-
} finally {
|
|
203
|
-
EvolutionWorkerService.stop!({ workspaceDir, stateDir, logger: console } as any);
|
|
204
|
-
safeRmDir(workspaceDir);
|
|
205
|
-
}
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
// ── Sleep reflection timeout (60 min default) ──
|
|
209
|
-
|
|
210
|
-
it('times out sleep_reflection task after 60 minutes → resolution = failed_max_retries', async () => {
|
|
211
|
-
const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-timeout-sleep-'));
|
|
212
|
-
const stateDir = path.join(workspaceDir, '.state');
|
|
213
|
-
fs.mkdirSync(path.join(stateDir, 'sessions'), { recursive: true });
|
|
214
|
-
fs.mkdirSync(path.join(stateDir, 'logs'), { recursive: true });
|
|
215
|
-
|
|
216
|
-
// Create an in_progress sleep_reflection task that started 61 minutes ago
|
|
217
|
-
const startedAt = new Date(Date.now() - 61 * 60 * 1000).toISOString();
|
|
218
|
-
fs.writeFileSync(
|
|
219
|
-
path.join(stateDir, 'evolution_queue.json'),
|
|
220
|
-
JSON.stringify([
|
|
221
|
-
{
|
|
222
|
-
id: 'sleep-timeout-60min',
|
|
223
|
-
taskKind: 'sleep_reflection',
|
|
224
|
-
priority: 'medium',
|
|
225
|
-
score: 50,
|
|
226
|
-
source: 'nocturnal',
|
|
227
|
-
reason: 'Test sleep reflection timeout',
|
|
228
|
-
timestamp: startedAt,
|
|
229
|
-
enqueued_at: startedAt,
|
|
230
|
-
status: 'in_progress',
|
|
231
|
-
session_id: 'test',
|
|
232
|
-
agent_id: 'main',
|
|
233
|
-
started_at: startedAt,
|
|
234
|
-
resultRef: 'wf-sleep-timeout',
|
|
235
|
-
retryCount: 0,
|
|
236
|
-
maxRetries: 1,
|
|
237
|
-
recentPainContext: {
|
|
238
|
-
mostRecent: null,
|
|
239
|
-
recentPainCount: 0,
|
|
240
|
-
recentMaxPainScore: 0,
|
|
241
|
-
},
|
|
242
|
-
},
|
|
243
|
-
], null, 2),
|
|
244
|
-
'utf8'
|
|
245
|
-
);
|
|
246
|
-
|
|
247
|
-
mockStartWorkflow.mockResolvedValue({
|
|
248
|
-
workflowId: 'wf-sleep-timeout',
|
|
249
|
-
childSessionKey: 'child-sleep',
|
|
250
|
-
state: 'terminal_error',
|
|
251
|
-
});
|
|
252
|
-
mockGetWorkflowDebugSummary.mockResolvedValue({
|
|
253
|
-
state: 'terminal_error',
|
|
254
|
-
metadata: {},
|
|
255
|
-
recentEvents: [{ reason: 'Test: simulating stuck sleep reflection', payload: {} }],
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
const mockApi = createMockApi();
|
|
259
|
-
EvolutionWorkerService.api = mockApi;
|
|
260
|
-
|
|
261
|
-
try {
|
|
262
|
-
EvolutionWorkerService.start({
|
|
263
|
-
workspaceDir,
|
|
264
|
-
stateDir,
|
|
265
|
-
logger: mockApi.logger,
|
|
266
|
-
config: fastPollConfig,
|
|
267
|
-
api: mockApi,
|
|
268
|
-
} as any);
|
|
269
|
-
|
|
270
|
-
await vi.advanceTimersByTimeAsync(5000);
|
|
271
|
-
|
|
272
|
-
const queue = readQueue(stateDir);
|
|
273
|
-
const task = queue.find((t: any) => t.id === 'sleep-timeout-60min');
|
|
274
|
-
|
|
275
|
-
expect(task.status).toBe('failed');
|
|
276
|
-
expect(task.resolution).toBe('failed_max_retries');
|
|
277
|
-
expect(task.completed_at).toBeDefined();
|
|
161
|
+
expect(task).toBeUndefined();
|
|
278
162
|
} finally {
|
|
279
163
|
EvolutionWorkerService.stop!({ workspaceDir, stateDir, logger: console } as any);
|
|
280
164
|
safeRmDir(workspaceDir);
|
|
@@ -283,8 +167,7 @@ describe('EvolutionWorkerService timeout mechanisms', () => {
|
|
|
283
167
|
|
|
284
168
|
// ── Report file cleanup on timeout ──
|
|
285
169
|
|
|
286
|
-
|
|
287
|
-
it.skip('cleans up .diagnostician_report_*.json file on pain_diagnosis timeout', async () => {
|
|
170
|
+
it('drops legacy pain_diagnosis tasks and ignores stale diagnostician reports', async () => {
|
|
288
171
|
const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-timeout-cleanup-'));
|
|
289
172
|
const stateDir = path.join(workspaceDir, '.state');
|
|
290
173
|
fs.mkdirSync(stateDir, { recursive: true });
|
|
@@ -335,14 +218,13 @@ describe('EvolutionWorkerService timeout mechanisms', () => {
|
|
|
335
218
|
|
|
336
219
|
await vi.advanceTimersByTimeAsync(5000);
|
|
337
220
|
|
|
338
|
-
// Verify the report file
|
|
339
|
-
expect(fs.existsSync(reportPath)).toBe(
|
|
221
|
+
// Verify the report file is ignored because legacy pain_diagnosis tasks are dropped immediately
|
|
222
|
+
expect(fs.existsSync(reportPath)).toBe(true);
|
|
340
223
|
|
|
341
|
-
// Verify the task was
|
|
224
|
+
// Verify the task was dropped
|
|
342
225
|
const queue = readQueue(stateDir);
|
|
343
226
|
const task = queue.find((t: any) => t.id === 'timeout-cleanup');
|
|
344
|
-
expect(task
|
|
345
|
-
expect(task.resolution).toBe('diagnostician_timeout');
|
|
227
|
+
expect(task).toBeUndefined();
|
|
346
228
|
} finally {
|
|
347
229
|
EvolutionWorkerService.stop!({ workspaceDir, stateDir, logger: console } as any);
|
|
348
230
|
safeRmDir(workspaceDir);
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internalization Trigger Adapter - Unit Tests (PRI-63/65)
|
|
3
|
+
*
|
|
4
|
+
* PRI-65: Updated to use diagnosticJson for PI metadata, simulating
|
|
5
|
+
* real SqliteTaskStore behavior where PI fields live inside diagnosticJson.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
9
|
+
import { readFileSync } from 'fs';
|
|
10
|
+
import { resolve } from 'path';
|
|
11
|
+
import type { TaskRecord } from '@principles/core/runtime-v2';
|
|
12
|
+
import { createPITaskDiagnosticJson } from '@principles/core/runtime-v2';
|
|
13
|
+
import type { InternalizationChannel, ArtifactRef } from '@principles/core/runtime-v2';
|
|
14
|
+
|
|
15
|
+
type MockLogger = {
|
|
16
|
+
debug?: (msg: string, meta?: Record<string, unknown>) => void;
|
|
17
|
+
info?: (msg: string, meta?: Record<string, unknown>) => void;
|
|
18
|
+
warn?: (msg: string, meta?: Record<string, unknown>) => void;
|
|
19
|
+
error?: (msg: string, meta?: Record<string, unknown>) => void;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
interface MockProvider {
|
|
23
|
+
listTasks: ReturnType<typeof vi.fn>;
|
|
24
|
+
getTask: ReturnType<typeof vi.fn>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Create a base TaskRecord (no PI metadata). Provider returns this from SQLite. */
|
|
28
|
+
function makeTask(overrides: Partial<TaskRecord> & { taskId: string; taskKind: string }): TaskRecord {
|
|
29
|
+
return {
|
|
30
|
+
taskId: overrides.taskId,
|
|
31
|
+
taskKind: overrides.taskKind,
|
|
32
|
+
status: overrides.status ?? 'pending',
|
|
33
|
+
createdAt: overrides.createdAt ?? new Date().toISOString(),
|
|
34
|
+
updatedAt: overrides.updatedAt ?? new Date().toISOString(),
|
|
35
|
+
attemptCount: 0,
|
|
36
|
+
maxAttempts: 3,
|
|
37
|
+
leaseOwner: undefined,
|
|
38
|
+
leaseExpiresAt: undefined,
|
|
39
|
+
lastError: undefined,
|
|
40
|
+
inputRef: undefined,
|
|
41
|
+
resultRef: undefined,
|
|
42
|
+
...overrides,
|
|
43
|
+
} as TaskRecord;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Create a TaskRecord with PI metadata stored in diagnosticJson (simulates SqliteTaskStore). */
|
|
47
|
+
function makePITask(
|
|
48
|
+
taskId: string,
|
|
49
|
+
taskKind: string,
|
|
50
|
+
channel: InternalizationChannel,
|
|
51
|
+
options?: {
|
|
52
|
+
status?: TaskRecord['status'];
|
|
53
|
+
dependencyTaskIds?: string[];
|
|
54
|
+
parentTaskId?: string;
|
|
55
|
+
correlationId?: string;
|
|
56
|
+
timeoutMs?: number;
|
|
57
|
+
inputArtifactRefs?: ArtifactRef[];
|
|
58
|
+
outputArtifactRefs?: ArtifactRef[];
|
|
59
|
+
},
|
|
60
|
+
): TaskRecord {
|
|
61
|
+
const meta = {
|
|
62
|
+
dependencyTaskIds: options?.dependencyTaskIds ?? [],
|
|
63
|
+
channel,
|
|
64
|
+
timeoutMs: options?.timeoutMs ?? 300000,
|
|
65
|
+
inputArtifactRefs: options?.inputArtifactRefs ?? [],
|
|
66
|
+
outputArtifactRefs: options?.outputArtifactRefs ?? [],
|
|
67
|
+
parentTaskId: options?.parentTaskId,
|
|
68
|
+
correlationId: options?.correlationId,
|
|
69
|
+
};
|
|
70
|
+
return {
|
|
71
|
+
...makeTask({ taskId, taskKind, status: options?.status ?? 'pending' }),
|
|
72
|
+
diagnosticJson: createPITaskDiagnosticJson(meta),
|
|
73
|
+
} as TaskRecord;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function readAdapterSource(): string {
|
|
77
|
+
return readFileSync(
|
|
78
|
+
resolve(__dirname, '../../src/service/internalization-trigger-adapter.ts'),
|
|
79
|
+
'utf8',
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
describe('Internalization Trigger Adapter', () => {
|
|
84
|
+
let mockProvider: MockProvider;
|
|
85
|
+
let mockLogger: MockLogger;
|
|
86
|
+
let adapter: {
|
|
87
|
+
wake: (ctx: { workspaceDir: string; stateDir: string }) => Promise<void>;
|
|
88
|
+
start: (ctx: { workspaceDir: string; stateDir: string }, intervalMs?: number) => () => void;
|
|
89
|
+
stop: () => void;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
beforeEach(async () => {
|
|
93
|
+
mockLogger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
|
|
94
|
+
mockProvider = { listTasks: vi.fn(), getTask: vi.fn() };
|
|
95
|
+
const mod = await import('../../src/service/internalization-trigger-adapter.js');
|
|
96
|
+
adapter = mod.createInternalizationTrigger(
|
|
97
|
+
mockProvider as import('../../src/service/internalization-trigger-adapter.js').InternalizationTaskProvider,
|
|
98
|
+
mockLogger,
|
|
99
|
+
) as typeof adapter;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('wake — basic behavior', () => {
|
|
103
|
+
it('no pending tasks → returns without error', async () => {
|
|
104
|
+
mockProvider.listTasks.mockResolvedValue([]);
|
|
105
|
+
await expect(adapter.wake({ workspaceDir: '/test', stateDir: '/test/.state' })).resolves.not.toThrow();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('pending task with no deps → logs INTERNALIZATION_TRIGGER_WAKE with correct payload', async () => {
|
|
109
|
+
const task = makePITask('task-1', 'dreamer', 'prompt', { status: 'pending', dependencyTaskIds: [] });
|
|
110
|
+
mockProvider.listTasks.mockResolvedValue([task]);
|
|
111
|
+
await adapter.wake({ workspaceDir: '/test', stateDir: '/test/.state' });
|
|
112
|
+
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
113
|
+
'[PD:InternalizationTrigger] INTERNALIZATION_TRIGGER_WAKE',
|
|
114
|
+
expect.objectContaining({ taskId: 'task-1', taskKind: 'dreamer', gateDecision: 'proceed' }),
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('retry_wait task → logs INTERNALIZATION_TRIGGER_WAKE', async () => {
|
|
119
|
+
const task = makePITask('task-retry', 'philosopher', 'prompt', { status: 'retry_wait', dependencyTaskIds: [] });
|
|
120
|
+
mockProvider.listTasks.mockResolvedValue([task]);
|
|
121
|
+
await adapter.wake({ workspaceDir: '/test', stateDir: '/test/.state' });
|
|
122
|
+
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
123
|
+
'[PD:InternalizationTrigger] INTERNALIZATION_TRIGGER_WAKE',
|
|
124
|
+
expect.objectContaining({ taskId: 'task-retry', taskKind: 'philosopher' }),
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('task with unmet deps → logs INTERNALIZATION_TRIGGER_BLOCKED with gateDecision blocked', async () => {
|
|
129
|
+
const depTask = makePITask('dep-1', 'scribe', 'prompt', { status: 'pending', dependencyTaskIds: [] });
|
|
130
|
+
const task = makePITask('task-blocked', 'dreamer', 'prompt', { status: 'pending', dependencyTaskIds: ['dep-1'] });
|
|
131
|
+
mockProvider.listTasks.mockResolvedValue([task]);
|
|
132
|
+
mockProvider.getTask.mockResolvedValue(depTask);
|
|
133
|
+
await adapter.wake({ workspaceDir: '/test', stateDir: '/test/.state' });
|
|
134
|
+
expect(mockLogger.debug).toHaveBeenCalledWith(
|
|
135
|
+
'[PD:InternalizationTrigger] INTERNALIZATION_TRIGGER_BLOCKED',
|
|
136
|
+
expect.objectContaining({ taskId: 'task-blocked', gateDecision: 'blocked', blockedBy: expect.any(Array) }),
|
|
137
|
+
);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('task with failed dep → logs INTERNALIZATION_TRIGGER_BLOCKED with dependency_failed', async () => {
|
|
141
|
+
const depTask = makePITask('dep-failed', 'scribe', 'prompt', { status: 'failed', dependencyTaskIds: [] });
|
|
142
|
+
const task = makePITask('task-dep-failed', 'dreamer', 'prompt', { status: 'pending', dependencyTaskIds: ['dep-failed'] });
|
|
143
|
+
mockProvider.listTasks.mockResolvedValue([task]);
|
|
144
|
+
mockProvider.getTask.mockResolvedValue(depTask);
|
|
145
|
+
await adapter.wake({ workspaceDir: '/test', stateDir: '/test/.state' });
|
|
146
|
+
expect(mockLogger.debug).toHaveBeenCalledWith(
|
|
147
|
+
'[PD:InternalizationTrigger] INTERNALIZATION_TRIGGER_BLOCKED',
|
|
148
|
+
expect.objectContaining({ taskId: 'task-dep-failed', gateDecision: 'dependency_failed' }),
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('task with missing dep → logs INTERNALIZATION_TRIGGER_BLOCKED (fail closed)', async () => {
|
|
153
|
+
const task = makePITask('task-missing-dep', 'dreamer', 'prompt', { status: 'pending', dependencyTaskIds: ['nonexistent'] });
|
|
154
|
+
mockProvider.listTasks.mockResolvedValue([task]);
|
|
155
|
+
mockProvider.getTask.mockResolvedValue(null);
|
|
156
|
+
await adapter.wake({ workspaceDir: '/test', stateDir: '/test/.state' });
|
|
157
|
+
expect(mockLogger.debug).toHaveBeenCalledWith(
|
|
158
|
+
'[PD:InternalizationTrigger] INTERNALIZATION_TRIGGER_BLOCKED',
|
|
159
|
+
expect.objectContaining({ taskId: 'task-missing-dep', gateDecision: 'blocked' }),
|
|
160
|
+
);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe('wake — fail closed', () => {
|
|
165
|
+
it('missing workspaceDir → does not throw', async () => {
|
|
166
|
+
await expect(adapter.wake({ workspaceDir: '', stateDir: '/test/.state' })).resolves.not.toThrow();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('missing stateDir → does not throw', async () => {
|
|
170
|
+
await expect(adapter.wake({ workspaceDir: '/test', stateDir: '' })).resolves.not.toThrow();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('provider throws → does not throw', async () => {
|
|
174
|
+
mockProvider.listTasks.mockRejectedValue(new Error('SQLite error'));
|
|
175
|
+
await expect(adapter.wake({ workspaceDir: '/test', stateDir: '/test/.state' })).resolves.not.toThrow();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('task with unknown taskKind → filtered out silently', async () => {
|
|
179
|
+
const unknownTask = makeTask({ taskId: 'task-unknown', taskKind: 'diagnostician', status: 'pending', dependencyTaskIds: [] });
|
|
180
|
+
mockProvider.listTasks.mockResolvedValue([unknownTask]);
|
|
181
|
+
await expect(adapter.wake({ workspaceDir: '/test', stateDir: '/test/.state' })).resolves.not.toThrow();
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe('wake — fire and forget', () => {
|
|
186
|
+
it('wake() returns in < 200ms even with many tasks', async () => {
|
|
187
|
+
const manyTasks = Array.from({ length: 20 }, (_, i) =>
|
|
188
|
+
makePITask(`task-${i}`, 'dreamer', 'prompt', { status: 'pending', dependencyTaskIds: [] }),
|
|
189
|
+
);
|
|
190
|
+
mockProvider.listTasks.mockResolvedValue(manyTasks);
|
|
191
|
+
const start = Date.now();
|
|
192
|
+
await adapter.wake({ workspaceDir: '/test', stateDir: '/test/.state' });
|
|
193
|
+
expect(Date.now() - start).toBeLessThan(200);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('wake() calls only read methods', async () => {
|
|
197
|
+
const task = makePITask('task-readonly', 'scribe', 'prompt', { status: 'pending', dependencyTaskIds: [] });
|
|
198
|
+
mockProvider.listTasks.mockResolvedValue([task]);
|
|
199
|
+
await adapter.wake({ workspaceDir: '/test', stateDir: '/test/.state' });
|
|
200
|
+
expect(mockProvider.listTasks).toHaveBeenCalled();
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
describe('start / stop', () => {
|
|
205
|
+
beforeEach(() => { vi.useFakeTimers(); });
|
|
206
|
+
afterEach(() => { vi.useRealTimers(); });
|
|
207
|
+
|
|
208
|
+
it('start() returns a stop function', () => {
|
|
209
|
+
mockProvider.listTasks.mockResolvedValue([]);
|
|
210
|
+
const stop = adapter.start({ workspaceDir: '/test', stateDir: '/test/.state' });
|
|
211
|
+
expect(typeof stop).toBe('function');
|
|
212
|
+
stop();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('stop() is idempotent', () => {
|
|
216
|
+
mockProvider.listTasks.mockResolvedValue([]);
|
|
217
|
+
const stop = adapter.start({ workspaceDir: '/test', stateDir: '/test/.state' });
|
|
218
|
+
expect(() => stop()).not.toThrow();
|
|
219
|
+
expect(() => stop()).not.toThrow();
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe('architecture guards (source-level)', () => {
|
|
224
|
+
it('does not import nocturnal-trinity', () => {
|
|
225
|
+
expect(readAdapterSource()).not.toContain('nocturnal-trinity');
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('does not import runTrinity', () => {
|
|
229
|
+
expect(readAdapterSource()).not.toContain('runTrinity');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('does import @principles/core/runtime-v2', () => {
|
|
233
|
+
expect(readAdapterSource()).toContain('@principles/core/runtime-v2');
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('imports hydratePITaskRecord from core (not own JSON.parse)', () => {
|
|
237
|
+
const src = readAdapterSource();
|
|
238
|
+
expect(src).toContain('hydratePITaskRecord');
|
|
239
|
+
// Must not contain ad-hoc JSON.parse for PI metadata
|
|
240
|
+
expect(src).not.toMatch(/JSON\.parse.*diagnosticJson/);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('does NOT use isValidPITaskRecord directly on raw provider tasks', () => {
|
|
244
|
+
// The adapter now uses hydratePITaskRecord instead of isValidPITaskRecord
|
|
245
|
+
// to determine if a task is a valid PI task.
|
|
246
|
+
// isValidPITaskRecord checks top-level fields which don't exist on persisted tasks.
|
|
247
|
+
const src = readAdapterSource();
|
|
248
|
+
expect(src).not.toMatch(/isValidPITaskRecord\s*\(\s*t\s*\)/);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
});
|
|
@@ -22,7 +22,7 @@ import { MonitoringQueryService } from '../../src/service/monitoring-query-servi
|
|
|
22
22
|
function createWorkflow(overrides: Partial<WorkflowRow> = {}): WorkflowRow {
|
|
23
23
|
return {
|
|
24
24
|
workflow_id: overrides.workflow_id ?? 'wf-1',
|
|
25
|
-
workflow_type: overrides.workflow_type ?? '
|
|
25
|
+
workflow_type: overrides.workflow_type ?? 'rulehost',
|
|
26
26
|
transport: overrides.transport ?? 'runtime_direct',
|
|
27
27
|
parent_session_id: overrides.parent_session_id ?? 'parent-1',
|
|
28
28
|
child_session_key: overrides.child_session_key ?? 'child-1',
|
|
@@ -37,23 +37,6 @@ function createWorkflow(overrides: Partial<WorkflowRow> = {}): WorkflowRow {
|
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
function createEvent(
|
|
41
|
-
workflowId: string,
|
|
42
|
-
eventType: string,
|
|
43
|
-
createdAt: number,
|
|
44
|
-
reason = ''
|
|
45
|
-
): WorkflowEventRow {
|
|
46
|
-
return {
|
|
47
|
-
workflow_id: workflowId,
|
|
48
|
-
event_type: eventType,
|
|
49
|
-
from_state: null,
|
|
50
|
-
to_state: 'completed',
|
|
51
|
-
reason,
|
|
52
|
-
payload_json: '{}',
|
|
53
|
-
created_at: createdAt,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
40
|
describe('MonitoringQueryService', () => {
|
|
58
41
|
beforeEach(() => {
|
|
59
42
|
vi.clearAllMocks();
|
|
@@ -81,33 +64,4 @@ describe('MonitoringQueryService', () => {
|
|
|
81
64
|
stuckDuration: null,
|
|
82
65
|
});
|
|
83
66
|
});
|
|
84
|
-
|
|
85
|
-
it('computes failure rate per terminal workflow instead of per failed stage', () => {
|
|
86
|
-
mockListWorkflows.mockReturnValue([
|
|
87
|
-
createWorkflow({ workflow_id: 'wf-complete', state: 'completed', duration_ms: 500 }),
|
|
88
|
-
createWorkflow({ workflow_id: 'wf-failed', state: 'terminal_error', duration_ms: 750 }),
|
|
89
|
-
]);
|
|
90
|
-
mockGetEvents.mockImplementation((workflowId: string) => {
|
|
91
|
-
if (workflowId === 'wf-failed') {
|
|
92
|
-
return [
|
|
93
|
-
createEvent(workflowId, 'trinity_dreamer_start', 1),
|
|
94
|
-
createEvent(workflowId, 'trinity_dreamer_failed', 2, 'dreamer failed'),
|
|
95
|
-
createEvent(workflowId, 'trinity_philosopher_start', 3),
|
|
96
|
-
createEvent(workflowId, 'trinity_philosopher_failed', 4, 'philosopher failed'),
|
|
97
|
-
];
|
|
98
|
-
}
|
|
99
|
-
return [
|
|
100
|
-
createEvent(workflowId, 'trinity_dreamer_start', 1),
|
|
101
|
-
createEvent(workflowId, 'trinity_dreamer_complete', 2),
|
|
102
|
-
];
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const service = new MonitoringQueryService('/workspace');
|
|
106
|
-
const result = service.getTrinityHealth();
|
|
107
|
-
|
|
108
|
-
expect(result.totalCalls).toBe(2);
|
|
109
|
-
expect(result.failureRate).toBe(0.5);
|
|
110
|
-
expect(result.stageStats.dreamer.failed).toBe(1);
|
|
111
|
-
expect(result.stageStats.philosopher.failed).toBe(1);
|
|
112
|
-
});
|
|
113
67
|
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Unit tests for queue-io.ts — queue persistence layer.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { describe, expect, it,
|
|
5
|
+
import { describe, expect, it, beforeEach, afterEach } from 'vitest';
|
|
6
6
|
import * as fs from 'fs';
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
import * as os from 'os';
|
|
@@ -15,14 +15,7 @@ import {
|
|
|
15
15
|
LOCK_MAX_RETRIES,
|
|
16
16
|
LOCK_RETRY_DELAY_MS,
|
|
17
17
|
LOCK_STALE_MS,
|
|
18
|
-
readRecentPainContext,
|
|
19
18
|
} from '../../src/service/queue-io.js';
|
|
20
|
-
import { readPainFlagContract } from '../../src/core/pain.js';
|
|
21
|
-
|
|
22
|
-
// Mock readPainFlagContract for readRecentPainContext tests
|
|
23
|
-
vi.mock('../../src/core/pain.js', () => ({
|
|
24
|
-
readPainFlagContract: vi.fn(),
|
|
25
|
-
}));
|
|
26
19
|
|
|
27
20
|
let tmpDir: string;
|
|
28
21
|
beforeEach(() => {
|
|
@@ -173,57 +166,3 @@ describe('withQueueLock RAII', () => {
|
|
|
173
166
|
release();
|
|
174
167
|
});
|
|
175
168
|
});
|
|
176
|
-
|
|
177
|
-
describe('readRecentPainContext', () => {
|
|
178
|
-
// readPainFlagContract is mocked via vi.mock above
|
|
179
|
-
const mockWctx = { workspaceDir: '/fake/workspace' } as any;
|
|
180
|
-
|
|
181
|
-
it('returns null context when contract status is not valid', () => {
|
|
182
|
-
vi.mocked(readPainFlagContract).mockReturnValueOnce({
|
|
183
|
-
status: 'missing',
|
|
184
|
-
format: 'missing',
|
|
185
|
-
data: {},
|
|
186
|
-
missingFields: [],
|
|
187
|
-
} as any);
|
|
188
|
-
const result = readRecentPainContext(mockWctx);
|
|
189
|
-
expect(result.mostRecent).toBeNull();
|
|
190
|
-
expect(result.recentPainCount).toBe(0);
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it('returns null context when score parses to 0', () => {
|
|
194
|
-
vi.mocked(readPainFlagContract).mockReturnValueOnce({
|
|
195
|
-
status: 'valid',
|
|
196
|
-
format: 'kv',
|
|
197
|
-
data: { score: '0', source: 'tool_failure', reason: 'err', time: '2026-04-14T00:00:00Z', session_id: 's1' },
|
|
198
|
-
missingFields: [],
|
|
199
|
-
} as any);
|
|
200
|
-
const result = readRecentPainContext(mockWctx);
|
|
201
|
-
expect(result.mostRecent).toBeNull();
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it('returns mostRecent with valid score > 0', () => {
|
|
205
|
-
vi.mocked(readPainFlagContract).mockReturnValueOnce({
|
|
206
|
-
status: 'valid',
|
|
207
|
-
format: 'kv',
|
|
208
|
-
data: { score: '75', source: 'tool_failure', reason: 'File write failed', time: '2026-04-14T10:00:00Z', session_id: 'sess-001' },
|
|
209
|
-
missingFields: [],
|
|
210
|
-
} as any);
|
|
211
|
-
const result = readRecentPainContext(mockWctx);
|
|
212
|
-
expect(result.mostRecent).not.toBeNull();
|
|
213
|
-
expect(result.mostRecent!.score).toBe(75);
|
|
214
|
-
expect(result.mostRecent!.source).toBe('tool_failure');
|
|
215
|
-
expect(result.recentPainCount).toBe(1);
|
|
216
|
-
expect(result.recentMaxPainScore).toBe(75);
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
it('returns null context when contract data is empty object', () => {
|
|
220
|
-
vi.mocked(readPainFlagContract).mockReturnValueOnce({
|
|
221
|
-
status: 'valid',
|
|
222
|
-
format: 'empty',
|
|
223
|
-
data: {},
|
|
224
|
-
missingFields: [],
|
|
225
|
-
} as any);
|
|
226
|
-
const result = readRecentPainContext(mockWctx);
|
|
227
|
-
expect(result.mostRecent).toBeNull();
|
|
228
|
-
});
|
|
229
|
-
});
|
|
@@ -1000,7 +1000,9 @@ describe('RuntimeSummaryService', () => {
|
|
|
1000
1000
|
});
|
|
1001
1001
|
});
|
|
1002
1002
|
|
|
1003
|
-
|
|
1003
|
+
// M8: Runtime v2 uses SQLite task store for pending tasks; legacy diagnostician_tasks.json
|
|
1004
|
+
// is no longer read for pendingTasks count. This test verified stale behavior.
|
|
1005
|
+
describe.skip('Stalled diagnostician warning', () => {
|
|
1004
1006
|
it('raises a high-signal warning when tasks are injected but no reports are written', () => {
|
|
1005
1007
|
const workspace = makeWorkspace();
|
|
1006
1008
|
writeJson(path.join(workspace, '.state', 'AGENT_SCORECARD.json'), {
|