principles-disciple 1.8.0 → 1.8.2
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/ADVANCED_CONFIG_ZH.md +97 -0
- package/AGENT_INSTALL.md +173 -0
- package/AGENT_INSTALL_EN.md +173 -0
- package/INSTALL.md +256 -0
- package/SKILL.md +63 -0
- package/docs/COMMAND_REFERENCE.md +76 -0
- package/docs/COMMAND_REFERENCE_EN.md +79 -0
- package/esbuild.config.js +75 -0
- package/openclaw.plugin.json +6 -1
- package/package.json +13 -15
- package/scripts/build-web.mjs +46 -0
- package/scripts/install-dependencies.cjs +47 -0
- package/scripts/sync-plugin.mjs +802 -0
- package/scripts/verify-build.mjs +109 -0
- package/src/agents/nocturnal-dreamer.md +152 -0
- package/src/agents/nocturnal-philosopher.md +138 -0
- package/src/agents/nocturnal-reflector.md +126 -0
- package/src/agents/nocturnal-scribe.md +164 -0
- package/src/commands/capabilities.ts +85 -0
- package/{dist/commands/context.js → src/commands/context.ts} +78 -38
- package/src/commands/evolution-status.ts +146 -0
- package/src/commands/export.ts +111 -0
- package/src/commands/focus.ts +533 -0
- package/src/commands/nocturnal-review.ts +311 -0
- package/src/commands/nocturnal-rollout.ts +763 -0
- package/src/commands/nocturnal-train.ts +1002 -0
- package/{dist/commands/pain.js → src/commands/pain.ts} +68 -49
- package/src/commands/principle-rollback.ts +27 -0
- package/{dist/commands/rollback.js → src/commands/rollback.ts} +44 -12
- package/src/commands/samples.ts +60 -0
- package/src/commands/strategy.ts +38 -0
- package/{dist/commands/thinking-os.js → src/commands/thinking-os.ts} +59 -36
- package/src/commands/workflow-debug.ts +128 -0
- package/{dist/config/defaults/runtime.js → src/config/defaults/runtime.ts} +12 -5
- package/src/config/errors.ts +163 -0
- package/{dist/config/index.d.ts → src/config/index.ts} +2 -1
- package/src/constants/diagnostician.ts +66 -0
- package/src/constants/tools.ts +62 -0
- package/src/core/adaptive-thresholds.ts +476 -0
- package/{dist/core/config-service.js → src/core/config-service.ts} +7 -4
- package/{dist/core/config.js → src/core/config.ts} +158 -46
- package/src/core/control-ui-db.ts +435 -0
- package/{dist/core/detection-funnel.js → src/core/detection-funnel.ts} +36 -21
- package/{dist/core/detection-service.js → src/core/detection-service.ts} +7 -4
- package/{dist/core/dictionary-service.js → src/core/dictionary-service.ts} +7 -4
- package/{dist/core/dictionary.js → src/core/dictionary.ts} +57 -34
- package/src/core/empathy-keyword-matcher.ts +327 -0
- package/src/core/empathy-types.ts +218 -0
- package/src/core/event-log.ts +544 -0
- package/src/core/evolution-engine.ts +612 -0
- package/src/core/evolution-logger.ts +353 -0
- package/src/core/evolution-migration.ts +77 -0
- package/src/core/evolution-reducer.ts +731 -0
- package/src/core/evolution-types.ts +456 -0
- package/src/core/external-training-contract.ts +527 -0
- package/src/core/focus-history.ts +1458 -0
- package/src/core/hygiene/tracker.ts +117 -0
- package/{dist/core/init.js → src/core/init.ts} +39 -26
- package/src/core/local-worker-routing.ts +617 -0
- package/{dist/core/migration.js → src/core/migration.ts} +18 -11
- package/src/core/model-deployment-registry.ts +722 -0
- package/src/core/model-training-registry.ts +813 -0
- package/src/core/nocturnal-arbiter.ts +706 -0
- package/src/core/nocturnal-candidate-scoring.ts +392 -0
- package/src/core/nocturnal-compliance.ts +1075 -0
- package/src/core/nocturnal-dataset.ts +668 -0
- package/src/core/nocturnal-executability.ts +428 -0
- package/src/core/nocturnal-export.ts +390 -0
- package/{dist/core/nocturnal-paths.js → src/core/nocturnal-paths.ts} +49 -23
- package/src/core/nocturnal-trajectory-extractor.ts +484 -0
- package/src/core/nocturnal-trinity.ts +1384 -0
- package/src/core/pain.ts +122 -0
- package/{dist/core/path-resolver.js → src/core/path-resolver.ts} +157 -36
- package/{dist/core/paths.js → src/core/paths.ts} +13 -4
- package/src/core/principle-training-state.ts +450 -0
- package/src/core/profile.ts +226 -0
- package/src/core/promotion-gate.ts +822 -0
- package/{dist/core/risk-calculator.js → src/core/risk-calculator.ts} +42 -16
- package/{dist/core/session-tracker.js → src/core/session-tracker.ts} +185 -63
- package/src/core/shadow-observation-registry.ts +534 -0
- package/{dist/core/system-logger.js → src/core/system-logger.ts} +9 -5
- package/src/core/thinking-models.ts +217 -0
- package/src/core/training-program.ts +630 -0
- package/src/core/trajectory-types.ts +243 -0
- package/src/core/trajectory.ts +1673 -0
- package/{dist/core/workspace-context.js → src/core/workspace-context.ts} +57 -32
- package/src/hooks/bash-risk.ts +171 -0
- package/src/hooks/edit-verification.ts +295 -0
- package/src/hooks/gate-block-helper.ts +160 -0
- package/src/hooks/gate.ts +210 -0
- package/src/hooks/gfi-gate.ts +177 -0
- package/src/hooks/lifecycle.ts +326 -0
- package/{dist/hooks/llm.js → src/hooks/llm.ts} +166 -139
- package/src/hooks/message-sanitize.ts +45 -0
- package/src/hooks/pain.ts +384 -0
- package/src/hooks/progressive-trust-gate.ts +174 -0
- package/src/hooks/prompt.ts +920 -0
- package/src/hooks/subagent.ts +207 -0
- package/src/hooks/thinking-checkpoint.ts +73 -0
- package/src/hooks/trajectory-collector.ts +290 -0
- package/src/http/principles-console-route.ts +716 -0
- package/src/i18n/commands.ts +117 -0
- package/src/index.ts +694 -0
- package/src/service/central-database.ts +831 -0
- package/src/service/control-ui-query-service.ts +888 -0
- package/src/service/evolution-query-service.ts +405 -0
- package/src/service/evolution-worker.ts +1646 -0
- package/src/service/health-query-service.ts +836 -0
- package/{dist/service/nocturnal-runtime.js → src/service/nocturnal-runtime.ts} +263 -36
- package/src/service/nocturnal-service.ts +1015 -0
- package/src/service/nocturnal-target-selector.ts +532 -0
- package/src/service/phase3-input-filter.ts +237 -0
- package/src/service/runtime-summary-service.ts +757 -0
- package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +513 -0
- package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +603 -0
- package/src/service/subagent-workflow/index.ts +51 -0
- package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +856 -0
- package/src/service/subagent-workflow/runtime-direct-driver.ts +166 -0
- package/src/service/subagent-workflow/types.ts +378 -0
- package/src/service/subagent-workflow/workflow-store.ts +328 -0
- package/src/service/trajectory-service.ts +15 -0
- package/{dist/tools/critique-prompt.js → src/tools/critique-prompt.ts} +25 -8
- package/src/tools/deep-reflect.ts +349 -0
- package/{dist/tools/model-index.js → src/tools/model-index.ts} +33 -17
- package/src/types/event-types.ts +453 -0
- package/src/types/hygiene-types.ts +31 -0
- package/src/types/principle-tree-schema.ts +244 -0
- package/src/types/runtime-summary.ts +49 -0
- package/src/types.ts +74 -0
- package/src/utils/file-lock.ts +391 -0
- package/{dist/utils/glob-match.js → src/utils/glob-match.ts} +21 -20
- package/{dist/utils/hashing.js → src/utils/hashing.ts} +6 -4
- package/src/utils/io.ts +110 -0
- package/{dist/utils/nlp.js → src/utils/nlp.ts} +19 -12
- package/{dist/utils/plugin-logger.js → src/utils/plugin-logger.ts} +33 -8
- package/src/utils/subagent-probe.ts +94 -0
- package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +70 -1
- package/templates/pain_settings.json +2 -1
- package/tests/README.md +120 -0
- package/tests/build-artifacts.test.ts +111 -0
- package/tests/commands/evolution-status.test.ts +222 -0
- package/tests/commands/evolver.test.ts +22 -0
- package/tests/commands/export.test.ts +78 -0
- package/tests/commands/nocturnal-review.test.ts +448 -0
- package/tests/commands/nocturnal-train.test.ts +97 -0
- package/tests/commands/pain.test.ts +108 -0
- package/tests/commands/samples.test.ts +65 -0
- package/tests/commands/strategy.test.ts +34 -0
- package/tests/commands/thinking-os.test.ts +88 -0
- package/tests/core/adaptive-thresholds.test.ts +261 -0
- package/tests/core/config-service.test.ts +89 -0
- package/tests/core/config.test.ts +90 -0
- package/tests/core/control-ui-db.test.ts +75 -0
- package/tests/core/core-template-guidance.test.ts +21 -0
- package/tests/core/detection-funnel.test.ts +63 -0
- package/tests/core/detection-service.test.ts +50 -0
- package/tests/core/dictionary-service.test.ts +116 -0
- package/tests/core/dictionary.test.ts +168 -0
- package/tests/core/empathy-keyword-matcher.test.ts +209 -0
- package/tests/core/event-log.test.ts +181 -0
- package/tests/core/evolution-e2e.test.ts +58 -0
- package/tests/core/evolution-engine-gate-integration.test.ts +543 -0
- package/tests/core/evolution-engine.test.ts +562 -0
- package/tests/core/evolution-logger.test.ts +148 -0
- package/tests/core/evolution-migration.test.ts +50 -0
- package/tests/core/evolution-paths.test.ts +21 -0
- package/tests/core/evolution-reducer.detector-metadata.test.ts +602 -0
- package/tests/core/evolution-reducer.test.ts +180 -0
- package/tests/core/evolution-types-loop.test.ts +48 -0
- package/tests/core/evolution-user-stories.e2e.test.ts +249 -0
- package/tests/core/external-training-contract.test.ts +463 -0
- package/tests/core/focus-history.test.ts +682 -0
- package/tests/core/init-flatten.test.ts +69 -0
- package/tests/core/init-refactor.test.ts +87 -0
- package/tests/core/init-v1.3.test.ts +46 -0
- package/tests/core/init.test.ts +190 -0
- package/tests/core/local-worker-routing.test.ts +757 -0
- package/tests/core/migration.test.ts +84 -0
- package/tests/core/model-deployment-registry.test.ts +845 -0
- package/tests/core/model-training-registry.test.ts +889 -0
- package/tests/core/nocturnal-arbiter.test.ts +494 -0
- package/tests/core/nocturnal-candidate-scoring.test.ts +400 -0
- package/tests/core/nocturnal-compliance.test.ts +646 -0
- package/tests/core/nocturnal-dataset.test.ts +892 -0
- package/tests/core/nocturnal-executability.test.ts +357 -0
- package/tests/core/nocturnal-export.test.ts +462 -0
- package/tests/core/nocturnal-reviewed-subset-comparison.test.ts +428 -0
- package/tests/core/nocturnal-trajectory-extractor.test.ts +634 -0
- package/tests/core/nocturnal-trinity.test.ts +953 -0
- package/tests/core/pain.test.ts +33 -0
- package/tests/core/path-resolver.test.ts +57 -0
- package/tests/core/paths-refactor.test.ts +42 -0
- package/tests/core/phase7-rollout-integration.test.ts +477 -0
- package/tests/core/principle-training-state.test.ts +712 -0
- package/tests/core/profile.test.ts +56 -0
- package/tests/core/promotion-gate.test.ts +556 -0
- package/tests/core/risk-calculator.test.ts +168 -0
- package/tests/core/session-tracker.test.ts +191 -0
- package/tests/core/training-program.test.ts +472 -0
- package/tests/core/trajectory.test.ts +265 -0
- package/tests/core/workspace-context-factory.test.ts +18 -0
- package/tests/core/workspace-context.test.ts +134 -0
- package/tests/fixtures/nocturnal-reviewed-subset.json +183 -0
- package/tests/fixtures/production-compatibility.test.ts +147 -0
- package/tests/fixtures/production-mock-generator.ts +282 -0
- package/tests/hooks/bash-risk-integration.test.ts +137 -0
- package/tests/hooks/bash-risk.test.ts +81 -0
- package/tests/hooks/edit-verification.test.ts +678 -0
- package/tests/hooks/gate-edit-verification-p1.test.ts +632 -0
- package/tests/hooks/gate-edit-verification.test.ts +435 -0
- package/tests/hooks/gate-pipeline-integration.test.ts +404 -0
- package/tests/hooks/gate.test.ts +271 -0
- package/tests/hooks/gfi-gate-unit.test.ts +422 -0
- package/tests/hooks/gfi-gate.test.ts +669 -0
- package/tests/hooks/lifecycle.test.ts +248 -0
- package/tests/hooks/llm.test.ts +308 -0
- package/tests/hooks/message-sanitize.test.ts +36 -0
- package/tests/hooks/pain.test.ts +141 -0
- package/tests/hooks/progressive-trust-gate.test.ts +277 -0
- package/tests/hooks/prompt.test.ts +1411 -0
- package/tests/hooks/subagent.test.ts +467 -0
- package/tests/hooks/thinking-gate.test.ts +313 -0
- package/tests/http/principles-console-route.test.ts +140 -0
- package/tests/hygiene-tracker.test.ts +77 -0
- package/tests/index.integration.test.ts +179 -0
- package/tests/index.shadow-routing.integration.test.ts +140 -0
- package/tests/index.test.ts +9 -0
- package/tests/integration/empathy-workflow-integration.test.ts +627 -0
- package/tests/service/control-ui-query-service.test.ts +121 -0
- package/tests/service/empathy-observer-workflow-manager.test.ts +176 -0
- package/tests/service/evolution-worker.test.ts +585 -0
- package/tests/service/nocturnal-runtime.test.ts +470 -0
- package/tests/service/nocturnal-service.test.ts +577 -0
- package/tests/service/nocturnal-target-selector.test.ts +615 -0
- package/tests/service/nocturnal-workflow-manager.test.ts +439 -0
- package/tests/service/phase3-input-filter.test.ts +289 -0
- package/tests/service/runtime-summary-service.test.ts +919 -0
- package/tests/task-compliance.test.ts +166 -0
- package/tests/test-utils.ts +48 -0
- package/tests/tools/critique-prompt.test.ts +260 -0
- package/tests/tools/deep-reflect.test.ts +232 -0
- package/tests/tools/model-index.test.ts +246 -0
- package/tests/ui/app.test.tsx +114 -0
- package/tests/utils/file-lock.test.ts +407 -0
- package/tests/utils/hashing.test.ts +32 -0
- package/tests/utils/io.test.ts +39 -0
- package/tests/utils/nlp.test.ts +53 -0
- package/tests/utils/plugin-logger.test.ts +156 -0
- package/tsconfig.json +16 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/ui/src/App.tsx +45 -0
- package/ui/src/api.ts +216 -0
- package/ui/src/charts.tsx +586 -0
- package/ui/src/components/ErrorState.tsx +6 -0
- package/ui/src/components/Loading.tsx +13 -0
- package/ui/src/components/ProtectedRoute.tsx +12 -0
- package/ui/src/components/Shell.tsx +91 -0
- package/ui/src/components/WorkspaceConfig.tsx +146 -0
- package/ui/src/components/index.ts +5 -0
- package/ui/src/context/auth.tsx +80 -0
- package/ui/src/context/theme.tsx +66 -0
- package/ui/src/hooks/useAutoRefresh.ts +39 -0
- package/ui/src/i18n/ui.ts +363 -0
- package/ui/src/main.tsx +16 -0
- package/ui/src/pages/EvolutionPage.tsx +352 -0
- package/ui/src/pages/FeedbackPage.tsx +140 -0
- package/ui/src/pages/GateMonitorPage.tsx +136 -0
- package/ui/src/pages/LoginPage.tsx +88 -0
- package/ui/src/pages/OverviewPage.tsx +238 -0
- package/ui/src/pages/SamplesPage.tsx +174 -0
- package/ui/src/pages/ThinkingModelsPage.tsx +127 -0
- package/ui/src/styles.css +1661 -0
- package/ui/src/types.ts +368 -0
- package/ui/src/utils/format.ts +15 -0
- package/vitest.config.ts +23 -0
- package/dist/commands/capabilities.d.ts +0 -3
- package/dist/commands/capabilities.js +0 -73
- package/dist/commands/context.d.ts +0 -5
- package/dist/commands/evolution-status.d.ts +0 -4
- package/dist/commands/evolution-status.js +0 -117
- package/dist/commands/evolver.d.ts +0 -9
- package/dist/commands/evolver.js +0 -26
- package/dist/commands/export.d.ts +0 -2
- package/dist/commands/export.js +0 -98
- package/dist/commands/focus.d.ts +0 -14
- package/dist/commands/focus.js +0 -457
- package/dist/commands/nocturnal-review.d.ts +0 -24
- package/dist/commands/nocturnal-review.js +0 -265
- package/dist/commands/nocturnal-rollout.d.ts +0 -27
- package/dist/commands/nocturnal-rollout.js +0 -671
- package/dist/commands/nocturnal-train.d.ts +0 -25
- package/dist/commands/nocturnal-train.js +0 -919
- package/dist/commands/pain.d.ts +0 -5
- package/dist/commands/principle-rollback.d.ts +0 -4
- package/dist/commands/principle-rollback.js +0 -22
- package/dist/commands/rollback.d.ts +0 -19
- package/dist/commands/samples.d.ts +0 -2
- package/dist/commands/samples.js +0 -55
- package/dist/commands/strategy.d.ts +0 -3
- package/dist/commands/strategy.js +0 -29
- package/dist/commands/thinking-os.d.ts +0 -2
- package/dist/config/defaults/runtime.d.ts +0 -40
- package/dist/config/errors.d.ts +0 -84
- package/dist/config/errors.js +0 -94
- package/dist/config/index.js +0 -7
- package/dist/constants/diagnostician.d.ts +0 -12
- package/dist/constants/diagnostician.js +0 -56
- package/dist/constants/tools.d.ts +0 -17
- package/dist/constants/tools.js +0 -54
- package/dist/core/adaptive-thresholds.d.ts +0 -186
- package/dist/core/adaptive-thresholds.js +0 -300
- package/dist/core/config-service.d.ts +0 -15
- package/dist/core/config.d.ts +0 -127
- package/dist/core/control-ui-db.d.ts +0 -95
- package/dist/core/control-ui-db.js +0 -292
- package/dist/core/detection-funnel.d.ts +0 -33
- package/dist/core/detection-service.d.ts +0 -15
- package/dist/core/dictionary-service.d.ts +0 -15
- package/dist/core/dictionary.d.ts +0 -38
- package/dist/core/event-log.d.ts +0 -82
- package/dist/core/event-log.js +0 -463
- package/dist/core/evolution-engine.d.ts +0 -118
- package/dist/core/evolution-engine.js +0 -464
- package/dist/core/evolution-logger.d.ts +0 -137
- package/dist/core/evolution-logger.js +0 -256
- package/dist/core/evolution-migration.d.ts +0 -5
- package/dist/core/evolution-migration.js +0 -65
- package/dist/core/evolution-reducer.d.ts +0 -98
- package/dist/core/evolution-reducer.js +0 -465
- package/dist/core/evolution-types.d.ts +0 -287
- package/dist/core/evolution-types.js +0 -78
- package/dist/core/external-training-contract.d.ts +0 -276
- package/dist/core/external-training-contract.js +0 -269
- package/dist/core/focus-history.d.ts +0 -210
- package/dist/core/focus-history.js +0 -1185
- package/dist/core/hygiene/tracker.d.ts +0 -22
- package/dist/core/hygiene/tracker.js +0 -106
- package/dist/core/init.d.ts +0 -12
- package/dist/core/local-worker-routing.d.ts +0 -175
- package/dist/core/local-worker-routing.js +0 -525
- package/dist/core/migration.d.ts +0 -6
- package/dist/core/model-deployment-registry.d.ts +0 -218
- package/dist/core/model-deployment-registry.js +0 -503
- package/dist/core/model-training-registry.d.ts +0 -295
- package/dist/core/model-training-registry.js +0 -475
- package/dist/core/nocturnal-arbiter.d.ts +0 -159
- package/dist/core/nocturnal-arbiter.js +0 -534
- package/dist/core/nocturnal-candidate-scoring.d.ts +0 -137
- package/dist/core/nocturnal-candidate-scoring.js +0 -266
- package/dist/core/nocturnal-compliance.d.ts +0 -175
- package/dist/core/nocturnal-compliance.js +0 -824
- package/dist/core/nocturnal-dataset.d.ts +0 -224
- package/dist/core/nocturnal-dataset.js +0 -443
- package/dist/core/nocturnal-executability.d.ts +0 -85
- package/dist/core/nocturnal-executability.js +0 -331
- package/dist/core/nocturnal-export.d.ts +0 -124
- package/dist/core/nocturnal-export.js +0 -275
- package/dist/core/nocturnal-paths.d.ts +0 -124
- package/dist/core/nocturnal-trajectory-extractor.d.ts +0 -242
- package/dist/core/nocturnal-trajectory-extractor.js +0 -307
- package/dist/core/nocturnal-trinity.d.ts +0 -311
- package/dist/core/nocturnal-trinity.js +0 -880
- package/dist/core/pain.d.ts +0 -4
- package/dist/core/pain.js +0 -70
- package/dist/core/path-resolver.d.ts +0 -46
- package/dist/core/paths.d.ts +0 -65
- package/dist/core/principle-training-state.d.ts +0 -121
- package/dist/core/principle-training-state.js +0 -321
- package/dist/core/profile.d.ts +0 -62
- package/dist/core/profile.js +0 -210
- package/dist/core/promotion-gate.d.ts +0 -238
- package/dist/core/promotion-gate.js +0 -529
- package/dist/core/risk-calculator.d.ts +0 -22
- package/dist/core/session-tracker.d.ts +0 -99
- package/dist/core/shadow-observation-registry.d.ts +0 -217
- package/dist/core/shadow-observation-registry.js +0 -308
- package/dist/core/system-logger.d.ts +0 -8
- package/dist/core/thinking-models.d.ts +0 -38
- package/dist/core/thinking-models.js +0 -170
- package/dist/core/training-program.d.ts +0 -233
- package/dist/core/training-program.js +0 -433
- package/dist/core/trajectory.d.ts +0 -411
- package/dist/core/trajectory.js +0 -1307
- package/dist/core/workspace-context.d.ts +0 -71
- package/dist/hooks/bash-risk.d.ts +0 -57
- package/dist/hooks/bash-risk.js +0 -137
- package/dist/hooks/edit-verification.d.ts +0 -62
- package/dist/hooks/edit-verification.js +0 -256
- package/dist/hooks/gate-block-helper.d.ts +0 -44
- package/dist/hooks/gate-block-helper.js +0 -119
- package/dist/hooks/gate.d.ts +0 -24
- package/dist/hooks/gate.js +0 -173
- package/dist/hooks/gfi-gate.d.ts +0 -40
- package/dist/hooks/gfi-gate.js +0 -113
- package/dist/hooks/lifecycle.d.ts +0 -5
- package/dist/hooks/lifecycle.js +0 -284
- package/dist/hooks/llm.d.ts +0 -12
- package/dist/hooks/message-sanitize.d.ts +0 -3
- package/dist/hooks/message-sanitize.js +0 -37
- package/dist/hooks/pain.d.ts +0 -5
- package/dist/hooks/pain.js +0 -301
- package/dist/hooks/progressive-trust-gate.d.ts +0 -51
- package/dist/hooks/progressive-trust-gate.js +0 -89
- package/dist/hooks/prompt.d.ts +0 -47
- package/dist/hooks/prompt.js +0 -884
- package/dist/hooks/subagent.d.ts +0 -10
- package/dist/hooks/subagent.js +0 -387
- package/dist/hooks/thinking-checkpoint.d.ts +0 -37
- package/dist/hooks/thinking-checkpoint.js +0 -51
- package/dist/hooks/trajectory-collector.d.ts +0 -32
- package/dist/hooks/trajectory-collector.js +0 -256
- package/dist/http/principles-console-route.d.ts +0 -9
- package/dist/http/principles-console-route.js +0 -567
- package/dist/i18n/commands.d.ts +0 -26
- package/dist/i18n/commands.js +0 -116
- package/dist/index.d.ts +0 -7
- package/dist/index.js +0 -581
- package/dist/service/central-database.d.ts +0 -104
- package/dist/service/central-database.js +0 -649
- package/dist/service/control-ui-query-service.d.ts +0 -221
- package/dist/service/control-ui-query-service.js +0 -543
- package/dist/service/empathy-observer-manager.d.ts +0 -52
- package/dist/service/empathy-observer-manager.js +0 -229
- package/dist/service/evolution-query-service.d.ts +0 -155
- package/dist/service/evolution-query-service.js +0 -258
- package/dist/service/evolution-worker.d.ts +0 -101
- package/dist/service/evolution-worker.js +0 -974
- package/dist/service/nocturnal-runtime.d.ts +0 -183
- package/dist/service/nocturnal-service.d.ts +0 -163
- package/dist/service/nocturnal-service.js +0 -787
- package/dist/service/nocturnal-target-selector.d.ts +0 -145
- package/dist/service/nocturnal-target-selector.js +0 -315
- package/dist/service/phase3-input-filter.d.ts +0 -73
- package/dist/service/phase3-input-filter.js +0 -172
- package/dist/service/runtime-summary-service.d.ts +0 -122
- package/dist/service/runtime-summary-service.js +0 -485
- package/dist/service/trajectory-service.d.ts +0 -2
- package/dist/service/trajectory-service.js +0 -15
- package/dist/tools/critique-prompt.d.ts +0 -14
- package/dist/tools/deep-reflect.d.ts +0 -39
- package/dist/tools/deep-reflect.js +0 -350
- package/dist/tools/model-index.d.ts +0 -9
- package/dist/types/event-types.d.ts +0 -306
- package/dist/types/event-types.js +0 -106
- package/dist/types/hygiene-types.d.ts +0 -20
- package/dist/types/hygiene-types.js +0 -12
- package/dist/types/runtime-summary.d.ts +0 -47
- package/dist/types/runtime-summary.js +0 -1
- package/dist/types.d.ts +0 -50
- package/dist/types.js +0 -22
- package/dist/utils/file-lock.d.ts +0 -71
- package/dist/utils/file-lock.js +0 -309
- package/dist/utils/glob-match.d.ts +0 -28
- package/dist/utils/hashing.d.ts +0 -9
- package/dist/utils/io.d.ts +0 -6
- package/dist/utils/io.js +0 -106
- package/dist/utils/nlp.d.ts +0 -9
- package/dist/utils/plugin-logger.d.ts +0 -39
- package/dist/utils/subagent-probe.d.ts +0 -34
- package/dist/utils/subagent-probe.js +0 -81
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import {
|
|
6
|
+
checkWorkspaceIdle,
|
|
7
|
+
checkCooldown,
|
|
8
|
+
checkPreflight,
|
|
9
|
+
recordRunStart,
|
|
10
|
+
recordRunEnd,
|
|
11
|
+
clearAllCooldowns,
|
|
12
|
+
getRuntimeState,
|
|
13
|
+
DEFAULT_IDLE_THRESHOLD_MS,
|
|
14
|
+
DEFAULT_GLOBAL_COOLDOWN_MS,
|
|
15
|
+
DEFAULT_PRINCIPLE_COOLDOWN_MS,
|
|
16
|
+
DEFAULT_ABANDONED_THRESHOLD_MS,
|
|
17
|
+
NOCTURNAL_RUNTIME_FILE,
|
|
18
|
+
} from '../../src/service/nocturnal-runtime.js';
|
|
19
|
+
import { initPersistence, trackToolRead, clearSession, listSessions } from '../../src/core/session-tracker.js';
|
|
20
|
+
import { safeRmDir } from '../test-utils.js';
|
|
21
|
+
|
|
22
|
+
describe('NocturnalRuntime', () => {
|
|
23
|
+
let tempDir: string;
|
|
24
|
+
let workspaceDir: string;
|
|
25
|
+
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
vi.useFakeTimers();
|
|
28
|
+
// Use a fixed "now" for deterministic testing
|
|
29
|
+
vi.setSystemTime(new Date('2026-03-27T12:00:00.000Z'));
|
|
30
|
+
|
|
31
|
+
workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-nocturnal-ws-'));
|
|
32
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-nocturnal-'));
|
|
33
|
+
|
|
34
|
+
// Initialize session tracker persistence for the temp workspace
|
|
35
|
+
initPersistence(tempDir);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
vi.useRealTimers();
|
|
40
|
+
vi.clearAllMocks();
|
|
41
|
+
safeRmDir(workspaceDir);
|
|
42
|
+
safeRmDir(tempDir);
|
|
43
|
+
clearSession('session-active');
|
|
44
|
+
clearSession('session-stale');
|
|
45
|
+
clearSession('session-abandoned');
|
|
46
|
+
clearSession('session-ancient');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// -------------------------------------------------------------------------
|
|
50
|
+
// Idle Detection Tests
|
|
51
|
+
// -------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
describe('checkWorkspaceIdle', () => {
|
|
54
|
+
it('should return isIdle=true when no sessions exist', () => {
|
|
55
|
+
const result = checkWorkspaceIdle(workspaceDir);
|
|
56
|
+
expect(result.isIdle).toBe(true);
|
|
57
|
+
expect(result.userActiveSessions).toBe(0);
|
|
58
|
+
expect(result.abandonedSessionIds).toEqual([]);
|
|
59
|
+
expect(result.reason).toContain('No active sessions');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should return isIdle=false when a session is recent (within threshold)', () => {
|
|
63
|
+
// Create an active session with recent activity
|
|
64
|
+
trackToolRead('session-active', 'src/main.ts', workspaceDir);
|
|
65
|
+
|
|
66
|
+
const result = checkWorkspaceIdle(workspaceDir, { idleThresholdMs: 30 * 60 * 1000 });
|
|
67
|
+
expect(result.isIdle).toBe(false);
|
|
68
|
+
expect(result.userActiveSessions).toBe(1);
|
|
69
|
+
expect(result.abandonedSessionIds).toEqual([]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should return isIdle=true when session is older than idle threshold', () => {
|
|
73
|
+
// Create a stale session (activity 45 min ago)
|
|
74
|
+
vi.setSystemTime(new Date('2026-03-27T11:15:00.000Z')); // 45 min before "now"
|
|
75
|
+
trackToolRead('session-stale', 'src/main.ts', workspaceDir);
|
|
76
|
+
vi.setSystemTime(new Date('2026-03-27T12:00:00.000Z')); // reset to "now"
|
|
77
|
+
|
|
78
|
+
const result = checkWorkspaceIdle(workspaceDir, { idleThresholdMs: 30 * 60 * 1000 });
|
|
79
|
+
expect(result.isIdle).toBe(true);
|
|
80
|
+
expect(result.idleForMs).toBeGreaterThan(30 * 60 * 1000);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should treat abandoned sessions as not contributing to idle check', () => {
|
|
84
|
+
// Session active 3 hours ago — should be treated as abandoned
|
|
85
|
+
vi.setSystemTime(new Date('2026-03-27T09:00:00.000Z')); // 3 hours before "now"
|
|
86
|
+
trackToolRead('session-abandoned', 'src/main.ts', workspaceDir);
|
|
87
|
+
vi.setSystemTime(new Date('2026-03-27T12:00:00.000Z')); // reset to "now"
|
|
88
|
+
|
|
89
|
+
const result = checkWorkspaceIdle(workspaceDir, {
|
|
90
|
+
idleThresholdMs: 30 * 60 * 1000,
|
|
91
|
+
abandonedThresholdMs: 2 * 60 * 60 * 1000,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
expect(result.isIdle).toBe(true); // No active sessions, so idle
|
|
95
|
+
expect(result.abandonedSessionIds).toContain('session-abandoned');
|
|
96
|
+
expect(result.userActiveSessions).toBe(0);
|
|
97
|
+
expect(result.reason).toContain('abandoned session(s) ignored');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should ignore ancient sessions but still detect recent activity from other sessions', () => {
|
|
101
|
+
// Ancient session (4 hours ago — abandoned)
|
|
102
|
+
vi.setSystemTime(new Date('2026-03-27T08:00:00.000Z'));
|
|
103
|
+
trackToolRead('session-ancient', 'src/main.ts', workspaceDir);
|
|
104
|
+
vi.setSystemTime(new Date('2026-03-27T12:00:00.000Z'));
|
|
105
|
+
|
|
106
|
+
// Recent session (5 minutes ago — still active)
|
|
107
|
+
vi.setSystemTime(new Date('2026-03-27T11:55:00.000Z'));
|
|
108
|
+
trackToolRead('session-active', 'src/main.ts', workspaceDir);
|
|
109
|
+
vi.setSystemTime(new Date('2026-03-27T12:00:00.000Z'));
|
|
110
|
+
|
|
111
|
+
const result = checkWorkspaceIdle(workspaceDir, {
|
|
112
|
+
idleThresholdMs: 30 * 60 * 1000,
|
|
113
|
+
abandonedThresholdMs: 2 * 60 * 60 * 1000,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
expect(result.isIdle).toBe(false); // Recent activity 5 min ago
|
|
117
|
+
expect(result.abandonedSessionIds).toContain('session-ancient');
|
|
118
|
+
expect(result.userActiveSessions).toBe(1);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should use trajectory timestamp as secondary guardrail', () => {
|
|
122
|
+
// No sessions, trajectory shows recent activity
|
|
123
|
+
const trajectoryRecent = Date.now() - 5 * 60 * 1000; // 5 min ago
|
|
124
|
+
|
|
125
|
+
const result = checkWorkspaceIdle(workspaceDir, {}, trajectoryRecent);
|
|
126
|
+
expect(result.isIdle).toBe(true); // Still idle (no sessions is primary)
|
|
127
|
+
expect(result.trajectoryGuardrailConfirmsIdle).toBe(false); // But trajectory disagrees
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should confirm idle when both session state and trajectory agree', () => {
|
|
131
|
+
// No sessions, trajectory also shows idle (>80% of threshold)
|
|
132
|
+
const trajectoryOld = Date.now() - 40 * 60 * 1000; // 40 min ago (>24 min = 80% of 30min)
|
|
133
|
+
|
|
134
|
+
const result = checkWorkspaceIdle(workspaceDir, {}, trajectoryOld);
|
|
135
|
+
expect(result.isIdle).toBe(true);
|
|
136
|
+
expect(result.trajectoryGuardrailConfirmsIdle).toBe(true);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should report idleForMs correctly', () => {
|
|
140
|
+
// Session active 15 min ago
|
|
141
|
+
vi.setSystemTime(new Date('2026-03-27T11:45:00.000Z'));
|
|
142
|
+
trackToolRead('session-active', 'src/main.ts', workspaceDir);
|
|
143
|
+
vi.setSystemTime(new Date('2026-03-27T12:00:00.000Z'));
|
|
144
|
+
|
|
145
|
+
const result = checkWorkspaceIdle(workspaceDir, { idleThresholdMs: 30 * 60 * 1000 });
|
|
146
|
+
expect(result.idleForMs).toBe(15 * 60 * 1000);
|
|
147
|
+
expect(result.isIdle).toBe(false);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// -------------------------------------------------------------------------
|
|
152
|
+
// Cooldown Management Tests
|
|
153
|
+
// -------------------------------------------------------------------------
|
|
154
|
+
|
|
155
|
+
describe('checkCooldown', () => {
|
|
156
|
+
it('should return no active cooldowns when state is empty', () => {
|
|
157
|
+
const result = checkCooldown(tempDir);
|
|
158
|
+
expect(result.globalCooldownActive).toBe(false);
|
|
159
|
+
expect(result.principleCooldownActive).toBe(false);
|
|
160
|
+
expect(result.quotaExhausted).toBe(false);
|
|
161
|
+
expect(result.runsRemaining).toBe(3); // DEFAULT_MAX_RUNS_PER_WINDOW
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should detect active global cooldown', async () => {
|
|
165
|
+
await recordRunStart(tempDir, 'T-01');
|
|
166
|
+
|
|
167
|
+
const result = checkCooldown(tempDir);
|
|
168
|
+
expect(result.globalCooldownActive).toBe(true);
|
|
169
|
+
expect(result.globalCooldownRemainingMs).toBe(DEFAULT_GLOBAL_COOLDOWN_MS);
|
|
170
|
+
expect(result.globalCooldownUntil).toBeTruthy();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should detect expired global cooldown', async () => {
|
|
174
|
+
await recordRunStart(tempDir, 'T-01');
|
|
175
|
+
|
|
176
|
+
// Advance time past the global cooldown
|
|
177
|
+
vi.advanceTimersByTime(DEFAULT_GLOBAL_COOLDOWN_MS + 1000);
|
|
178
|
+
|
|
179
|
+
const result = checkCooldown(tempDir);
|
|
180
|
+
expect(result.globalCooldownActive).toBe(false);
|
|
181
|
+
expect(result.globalCooldownRemainingMs).toBe(0);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should detect principle-specific cooldown after successful run', async () => {
|
|
185
|
+
await recordRunStart(tempDir, 'T-01');
|
|
186
|
+
await recordRunEnd(tempDir, 'success', { sampleCount: 5 });
|
|
187
|
+
|
|
188
|
+
const result = checkCooldown(tempDir, 'T-01');
|
|
189
|
+
expect(result.principleCooldownActive).toBe(true);
|
|
190
|
+
expect(result.principleCooldownRemainingMs).toBe(DEFAULT_PRINCIPLE_COOLDOWN_MS);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should not trigger principle cooldown on failed run', async () => {
|
|
194
|
+
await recordRunStart(tempDir, 'T-01');
|
|
195
|
+
await recordRunEnd(tempDir, 'failed', { reason: 'No violating sessions' });
|
|
196
|
+
|
|
197
|
+
// Global cooldown still active, but no principle cooldown
|
|
198
|
+
const result = checkCooldown(tempDir, 'T-01');
|
|
199
|
+
expect(result.globalCooldownActive).toBe(true);
|
|
200
|
+
expect(result.principleCooldownActive).toBe(false);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('should not trigger principle cooldown on skipped run', async () => {
|
|
204
|
+
await recordRunStart(tempDir, 'T-01');
|
|
205
|
+
await recordRunEnd(tempDir, 'skipped', { reason: 'Idle check failed' });
|
|
206
|
+
|
|
207
|
+
const result = checkCooldown(tempDir, 'T-01');
|
|
208
|
+
expect(result.principleCooldownActive).toBe(false);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('should enforce quota limit', async () => {
|
|
212
|
+
// Run max number of times
|
|
213
|
+
for (let i = 0; i < 3; i++) {
|
|
214
|
+
await recordRunStart(tempDir, 'T-01');
|
|
215
|
+
await recordRunEnd(tempDir, 'success', { sampleCount: 1 });
|
|
216
|
+
// Advance past global cooldown for each run
|
|
217
|
+
vi.advanceTimersByTime(DEFAULT_GLOBAL_COOLDOWN_MS + 1000);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const result = checkCooldown(tempDir);
|
|
221
|
+
expect(result.quotaExhausted).toBe(true);
|
|
222
|
+
expect(result.runsRemaining).toBe(0);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('should reset quota after window expires', async () => {
|
|
226
|
+
// Run max times
|
|
227
|
+
for (let i = 0; i < 3; i++) {
|
|
228
|
+
await recordRunStart(tempDir, 'T-01');
|
|
229
|
+
await recordRunEnd(tempDir, 'success', { sampleCount: 1 });
|
|
230
|
+
vi.advanceTimersByTime(DEFAULT_GLOBAL_COOLDOWN_MS + 1000);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Advance past the quota window
|
|
234
|
+
vi.advanceTimersByTime(24 * 60 * 60 * 1000 + 1000);
|
|
235
|
+
|
|
236
|
+
const result = checkCooldown(tempDir);
|
|
237
|
+
expect(result.quotaExhausted).toBe(false);
|
|
238
|
+
expect(result.runsRemaining).toBe(3);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('should only cooldown specific principles, not others', async () => {
|
|
242
|
+
await recordRunStart(tempDir, 'T-01');
|
|
243
|
+
await recordRunEnd(tempDir, 'success');
|
|
244
|
+
|
|
245
|
+
const resultT01 = checkCooldown(tempDir, 'T-01');
|
|
246
|
+
const resultT02 = checkCooldown(tempDir, 'T-02');
|
|
247
|
+
|
|
248
|
+
expect(resultT01.principleCooldownActive).toBe(true);
|
|
249
|
+
expect(resultT02.principleCooldownActive).toBe(false);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// -------------------------------------------------------------------------
|
|
254
|
+
// Run Recording Tests
|
|
255
|
+
// -------------------------------------------------------------------------
|
|
256
|
+
|
|
257
|
+
describe('recordRunStart / recordRunEnd', () => {
|
|
258
|
+
it('should record run start timestamp', async () => {
|
|
259
|
+
await recordRunStart(tempDir, 'T-01');
|
|
260
|
+
const state = await getRuntimeState(tempDir);
|
|
261
|
+
|
|
262
|
+
expect(state.lastRunAt).toBeTruthy();
|
|
263
|
+
expect(state.lastRunMeta?.targetPrincipleId).toBe('T-01');
|
|
264
|
+
expect(state.lastRunMeta?.status).toBe('skipped');
|
|
265
|
+
expect(state.globalCooldownUntil).toBeTruthy();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('should record successful run with sample count', async () => {
|
|
269
|
+
await recordRunStart(tempDir, 'T-01');
|
|
270
|
+
await recordRunEnd(tempDir, 'success', { sampleCount: 7 });
|
|
271
|
+
|
|
272
|
+
const state = await getRuntimeState(tempDir);
|
|
273
|
+
expect(state.lastSuccessfulRunAt).toBeTruthy();
|
|
274
|
+
expect(state.lastRunMeta?.status).toBe('success');
|
|
275
|
+
expect(state.lastRunMeta?.sampleCount).toBe(7);
|
|
276
|
+
expect(state.principleCooldowns['T-01']).toBeTruthy();
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('should preserve failed run reason without setting successful timestamp', async () => {
|
|
280
|
+
await recordRunStart(tempDir, 'T-02');
|
|
281
|
+
await recordRunEnd(tempDir, 'failed', { reason: 'No violating sessions found' });
|
|
282
|
+
|
|
283
|
+
const state = await getRuntimeState(tempDir);
|
|
284
|
+
expect(state.lastSuccessfulRunAt).toBeUndefined();
|
|
285
|
+
expect(state.lastRunMeta?.status).toBe('failed');
|
|
286
|
+
expect(state.lastRunMeta?.reason).toBe('No violating sessions found');
|
|
287
|
+
expect(state.principleCooldowns['T-02']).toBeUndefined(); // No principle cooldown on failure
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('should add timestamp to recentRunTimestamps for quota tracking', async () => {
|
|
291
|
+
await recordRunStart(tempDir, 'T-01');
|
|
292
|
+
const state = await getRuntimeState(tempDir);
|
|
293
|
+
|
|
294
|
+
expect(state.recentRunTimestamps.length).toBe(1);
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// -------------------------------------------------------------------------
|
|
299
|
+
// clearAllCooldowns Tests
|
|
300
|
+
// -------------------------------------------------------------------------
|
|
301
|
+
|
|
302
|
+
describe('clearAllCooldowns', () => {
|
|
303
|
+
it('should clear all cooldown state', async () => {
|
|
304
|
+
await recordRunStart(tempDir, 'T-01');
|
|
305
|
+
await recordRunEnd(tempDir, 'success');
|
|
306
|
+
|
|
307
|
+
await clearAllCooldowns(tempDir);
|
|
308
|
+
|
|
309
|
+
const state = await getRuntimeState(tempDir);
|
|
310
|
+
expect(state.globalCooldownUntil).toBeUndefined();
|
|
311
|
+
expect(state.principleCooldowns).toEqual({});
|
|
312
|
+
expect(state.recentRunTimestamps).toEqual([]);
|
|
313
|
+
expect(state.lastRunMeta).toBeUndefined();
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// -------------------------------------------------------------------------
|
|
318
|
+
// Preflight Check Tests
|
|
319
|
+
// -------------------------------------------------------------------------
|
|
320
|
+
|
|
321
|
+
describe('checkPreflight', () => {
|
|
322
|
+
it('should return canRun=true when workspace is idle and no cooldowns', () => {
|
|
323
|
+
// No sessions = idle
|
|
324
|
+
const result = checkPreflight(workspaceDir, tempDir, 'T-01');
|
|
325
|
+
expect(result.canRun).toBe(true);
|
|
326
|
+
expect(result.blockers).toEqual([]);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('should block when workspace is not idle', () => {
|
|
330
|
+
// Create recent session
|
|
331
|
+
trackToolRead('session-active', 'src/main.ts', workspaceDir);
|
|
332
|
+
|
|
333
|
+
const result = checkPreflight(workspaceDir, tempDir, 'T-01');
|
|
334
|
+
expect(result.canRun).toBe(false);
|
|
335
|
+
expect(result.blockers.some(b => b.includes('not idle'))).toBe(true);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('should block when global cooldown is active', async () => {
|
|
339
|
+
await recordRunStart(tempDir, 'T-01');
|
|
340
|
+
|
|
341
|
+
const result = checkPreflight(workspaceDir, tempDir, 'T-01');
|
|
342
|
+
expect(result.canRun).toBe(false);
|
|
343
|
+
expect(result.blockers.some(b => b.includes('Global cooldown'))).toBe(true);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it('should block when quota is exhausted', async () => {
|
|
347
|
+
// Exhaust quota
|
|
348
|
+
for (let i = 0; i < 3; i++) {
|
|
349
|
+
await recordRunStart(tempDir, 'T-01');
|
|
350
|
+
await recordRunEnd(tempDir, 'success', { sampleCount: 1 });
|
|
351
|
+
vi.advanceTimersByTime(DEFAULT_GLOBAL_COOLDOWN_MS + 1000);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const result = checkPreflight(workspaceDir, tempDir, 'T-01');
|
|
355
|
+
expect(result.canRun).toBe(false);
|
|
356
|
+
expect(result.blockers.some(b => b.includes('Quota exhausted'))).toBe(true);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it('should report all blockers when multiple conditions block', async () => {
|
|
360
|
+
// Create recent session AND set global cooldown
|
|
361
|
+
trackToolRead('session-active', 'src/main.ts', workspaceDir);
|
|
362
|
+
await recordRunStart(tempDir, 'T-01');
|
|
363
|
+
|
|
364
|
+
const result = checkPreflight(workspaceDir, tempDir, 'T-01');
|
|
365
|
+
expect(result.canRun).toBe(false);
|
|
366
|
+
expect(result.blockers.length).toBeGreaterThanOrEqual(2);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it('should include idle info in preflight result', () => {
|
|
370
|
+
const result = checkPreflight(workspaceDir, tempDir, 'T-01');
|
|
371
|
+
expect(result.idle).toBeDefined();
|
|
372
|
+
expect(result.idle.isIdle).toBe(true); // No sessions
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it('should include cooldown info in preflight result', () => {
|
|
376
|
+
const result = checkPreflight(workspaceDir, tempDir, 'T-01');
|
|
377
|
+
expect(result.cooldown).toBeDefined();
|
|
378
|
+
expect(result.cooldown.globalCooldownActive).toBe(false);
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
// -------------------------------------------------------------------------
|
|
383
|
+
// Abandoned Session Tests (dedicated)
|
|
384
|
+
// -------------------------------------------------------------------------
|
|
385
|
+
|
|
386
|
+
describe('abandoned sessions', () => {
|
|
387
|
+
it('should not block nocturnal flow when all sessions are abandoned but workspace otherwise empty', () => {
|
|
388
|
+
// Create only abandoned sessions (no recent activity)
|
|
389
|
+
vi.setSystemTime(new Date('2026-03-27T09:00:00.000Z')); // 3 hours ago
|
|
390
|
+
trackToolRead('session-abandoned', 'src/main.ts', workspaceDir);
|
|
391
|
+
vi.setSystemTime(new Date('2026-03-27T12:00:00.000Z'));
|
|
392
|
+
|
|
393
|
+
const result = checkWorkspaceIdle(workspaceDir, {
|
|
394
|
+
idleThresholdMs: 30 * 60 * 1000,
|
|
395
|
+
abandonedThresholdMs: 2 * 60 * 60 * 1000,
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
// Workspace should be considered idle (all sessions abandoned = effectively no sessions)
|
|
399
|
+
expect(result.isIdle).toBe(true);
|
|
400
|
+
expect(result.userActiveSessions).toBe(0);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it('should not incorrectly block when there are abandoned AND active sessions', () => {
|
|
404
|
+
// Abandoned session (3 hours ago)
|
|
405
|
+
vi.setSystemTime(new Date('2026-03-27T09:00:00.000Z'));
|
|
406
|
+
trackToolRead('session-abandoned', 'src/main.ts', workspaceDir);
|
|
407
|
+
|
|
408
|
+
// Recent session (5 min ago)
|
|
409
|
+
vi.setSystemTime(new Date('2026-03-27T11:55:00.000Z'));
|
|
410
|
+
trackToolRead('session-active', 'src/main.ts', workspaceDir);
|
|
411
|
+
vi.setSystemTime(new Date('2026-03-27T12:00:00.000Z'));
|
|
412
|
+
|
|
413
|
+
const idleResult = checkWorkspaceIdle(workspaceDir, {
|
|
414
|
+
idleThresholdMs: 30 * 60 * 1000,
|
|
415
|
+
abandonedThresholdMs: 2 * 60 * 60 * 1000,
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
// Should NOT be idle because there's a recent active session
|
|
419
|
+
expect(idleResult.isIdle).toBe(false);
|
|
420
|
+
expect(idleResult.userActiveSessions).toBe(1);
|
|
421
|
+
expect(idleResult.abandonedSessionIds).toContain('session-abandoned');
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it('should persist abandoned sessions do not create cooldown state', async () => {
|
|
425
|
+
// Create abandoned session
|
|
426
|
+
vi.setSystemTime(new Date('2026-03-27T09:00:00.000Z'));
|
|
427
|
+
trackToolRead('session-abandoned', 'src/main.ts', workspaceDir);
|
|
428
|
+
vi.setSystemTime(new Date('2026-03-27T12:00:00.000Z'));
|
|
429
|
+
|
|
430
|
+
// Workspace is idle, preflight should pass
|
|
431
|
+
const result = checkPreflight(workspaceDir, tempDir, 'T-01');
|
|
432
|
+
expect(result.canRun).toBe(true);
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
// -------------------------------------------------------------------------
|
|
437
|
+
// File Persistence Tests
|
|
438
|
+
// -------------------------------------------------------------------------
|
|
439
|
+
|
|
440
|
+
describe('file persistence', () => {
|
|
441
|
+
it('should create nocturnal-runtime.json on first write', async () => {
|
|
442
|
+
const filePath = path.join(tempDir, NOCTURNAL_RUNTIME_FILE);
|
|
443
|
+
expect(fs.existsSync(filePath)).toBe(false);
|
|
444
|
+
|
|
445
|
+
await recordRunStart(tempDir, 'T-01');
|
|
446
|
+
expect(fs.existsSync(filePath)).toBe(true);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
it('should survive corrupted JSON (start fresh)', async () => {
|
|
450
|
+
const filePath = path.join(tempDir, NOCTURNAL_RUNTIME_FILE);
|
|
451
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
452
|
+
fs.writeFileSync(filePath, '{ corrupted json }', 'utf-8');
|
|
453
|
+
|
|
454
|
+
const state = await getRuntimeState(tempDir);
|
|
455
|
+
// Should return default state, not throw
|
|
456
|
+
expect(state.principleCooldowns).toEqual({});
|
|
457
|
+
expect(state.recentRunTimestamps).toEqual([]);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it('should read persisted cooldown on restart', async () => {
|
|
461
|
+
await recordRunStart(tempDir, 'T-01');
|
|
462
|
+
await recordRunEnd(tempDir, 'success');
|
|
463
|
+
|
|
464
|
+
// Simulate restart by re-reading
|
|
465
|
+
const state = await getRuntimeState(tempDir);
|
|
466
|
+
expect(state.lastSuccessfulRunAt).toBeTruthy();
|
|
467
|
+
expect(state.principleCooldowns['T-01']).toBeTruthy();
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
});
|