@substrate-ai/core 0.19.54
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/README.md +55 -0
- package/dist/__tests__/adapter.test.d.ts +12 -0
- package/dist/__tests__/adapter.test.d.ts.map +1 -0
- package/dist/__tests__/adapter.test.js +259 -0
- package/dist/__tests__/adapter.test.js.map +1 -0
- package/dist/__tests__/event-bus.test.d.ts +14 -0
- package/dist/__tests__/event-bus.test.d.ts.map +1 -0
- package/dist/__tests__/event-bus.test.js +199 -0
- package/dist/__tests__/event-bus.test.js.map +1 -0
- package/dist/__tests__/output-quality.test.d.ts +8 -0
- package/dist/__tests__/output-quality.test.d.ts.map +1 -0
- package/dist/__tests__/output-quality.test.js +166 -0
- package/dist/__tests__/output-quality.test.js.map +1 -0
- package/dist/__tests__/schema-suffix.test.d.ts +9 -0
- package/dist/__tests__/schema-suffix.test.d.ts.map +1 -0
- package/dist/__tests__/schema-suffix.test.js +126 -0
- package/dist/__tests__/schema-suffix.test.js.map +1 -0
- package/dist/__tests__/yaml-parser.test.d.ts +18 -0
- package/dist/__tests__/yaml-parser.test.d.ts.map +1 -0
- package/dist/__tests__/yaml-parser.test.js +475 -0
- package/dist/__tests__/yaml-parser.test.js.map +1 -0
- package/dist/__type-checks__.d.ts +11 -0
- package/dist/__type-checks__.d.ts.map +1 -0
- package/dist/__type-checks__.js +19 -0
- package/dist/__type-checks__.js.map +1 -0
- package/dist/adapters/__tests__/adapter-output-normalizer.test.d.ts +12 -0
- package/dist/adapters/__tests__/adapter-output-normalizer.test.d.ts.map +1 -0
- package/dist/adapters/__tests__/adapter-output-normalizer.test.js +193 -0
- package/dist/adapters/__tests__/adapter-output-normalizer.test.js.map +1 -0
- package/dist/adapters/adapter-format-error.d.ts +35 -0
- package/dist/adapters/adapter-format-error.d.ts.map +1 -0
- package/dist/adapters/adapter-format-error.js +38 -0
- package/dist/adapters/adapter-format-error.js.map +1 -0
- package/dist/adapters/adapter-output-normalizer.d.ts +50 -0
- package/dist/adapters/adapter-output-normalizer.d.ts.map +1 -0
- package/dist/adapters/adapter-output-normalizer.js +233 -0
- package/dist/adapters/adapter-output-normalizer.js.map +1 -0
- package/dist/adapters/adapter-registry.d.ts +50 -0
- package/dist/adapters/adapter-registry.d.ts.map +1 -0
- package/dist/adapters/adapter-registry.js +101 -0
- package/dist/adapters/adapter-registry.js.map +1 -0
- package/dist/adapters/claude-adapter.d.ts +59 -0
- package/dist/adapters/claude-adapter.d.ts.map +1 -0
- package/dist/adapters/claude-adapter.js +367 -0
- package/dist/adapters/claude-adapter.js.map +1 -0
- package/dist/adapters/codex-adapter.d.ts +64 -0
- package/dist/adapters/codex-adapter.d.ts.map +1 -0
- package/dist/adapters/codex-adapter.js +263 -0
- package/dist/adapters/codex-adapter.js.map +1 -0
- package/dist/adapters/gemini-adapter.d.ts +57 -0
- package/dist/adapters/gemini-adapter.d.ts.map +1 -0
- package/dist/adapters/gemini-adapter.js +311 -0
- package/dist/adapters/gemini-adapter.js.map +1 -0
- package/dist/adapters/index.d.ts +10 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +14 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/schemas.d.ts +137 -0
- package/dist/adapters/schemas.d.ts.map +1 -0
- package/dist/adapters/schemas.js +140 -0
- package/dist/adapters/schemas.js.map +1 -0
- package/dist/adapters/types.d.ts +245 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +6 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/adapters/worker-adapter.d.ts +188 -0
- package/dist/adapters/worker-adapter.d.ts.map +1 -0
- package/dist/adapters/worker-adapter.js +19 -0
- package/dist/adapters/worker-adapter.js.map +1 -0
- package/dist/budget/budget-tracker.d.ts +22 -0
- package/dist/budget/budget-tracker.d.ts.map +1 -0
- package/dist/budget/budget-tracker.js +39 -0
- package/dist/budget/budget-tracker.js.map +1 -0
- package/dist/budget/index.d.ts +6 -0
- package/dist/budget/index.d.ts.map +1 -0
- package/dist/budget/index.js +5 -0
- package/dist/budget/index.js.map +1 -0
- package/dist/config/config-migrator.d.ts +58 -0
- package/dist/config/config-migrator.d.ts.map +1 -0
- package/dist/config/config-migrator.js +158 -0
- package/dist/config/config-migrator.js.map +1 -0
- package/dist/config/config-system-impl.d.ts +63 -0
- package/dist/config/config-system-impl.d.ts.map +1 -0
- package/dist/config/config-system-impl.js +364 -0
- package/dist/config/config-system-impl.js.map +1 -0
- package/dist/config/config-watcher.d.ts +59 -0
- package/dist/config/config-watcher.d.ts.map +1 -0
- package/dist/config/config-watcher.js +137 -0
- package/dist/config/config-watcher.js.map +1 -0
- package/dist/config/defaults.d.ts +13 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +62 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/errors.d.ts +25 -0
- package/dist/config/errors.d.ts.map +1 -0
- package/dist/config/errors.js +49 -0
- package/dist/config/errors.js.map +1 -0
- package/dist/config/index.d.ts +19 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +39 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/types.d.ts +456 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +174 -0
- package/dist/config/types.js.map +1 -0
- package/dist/config/version-utils.d.ts +39 -0
- package/dist/config/version-utils.d.ts.map +1 -0
- package/dist/config/version-utils.js +66 -0
- package/dist/config/version-utils.js.map +1 -0
- package/dist/context/index.d.ts +2 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +2 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/types.d.ts +113 -0
- package/dist/context/types.d.ts.map +1 -0
- package/dist/context/types.js +59 -0
- package/dist/context/types.js.map +1 -0
- package/dist/cost-tracker/cost-tracker-impl.d.ts +51 -0
- package/dist/cost-tracker/cost-tracker-impl.d.ts.map +1 -0
- package/dist/cost-tracker/cost-tracker-impl.js +85 -0
- package/dist/cost-tracker/cost-tracker-impl.js.map +1 -0
- package/dist/cost-tracker/cost-tracker-subscriber.d.ts +31 -0
- package/dist/cost-tracker/cost-tracker-subscriber.d.ts.map +1 -0
- package/dist/cost-tracker/cost-tracker-subscriber.js +116 -0
- package/dist/cost-tracker/cost-tracker-subscriber.js.map +1 -0
- package/dist/cost-tracker/index.d.ts +11 -0
- package/dist/cost-tracker/index.d.ts.map +1 -0
- package/dist/cost-tracker/index.js +7 -0
- package/dist/cost-tracker/index.js.map +1 -0
- package/dist/cost-tracker/token-rates.d.ts +25 -0
- package/dist/cost-tracker/token-rates.d.ts.map +1 -0
- package/dist/cost-tracker/token-rates.js +99 -0
- package/dist/cost-tracker/token-rates.js.map +1 -0
- package/dist/cost-tracker/types.d.ts +6 -0
- package/dist/cost-tracker/types.d.ts.map +1 -0
- package/dist/cost-tracker/types.js +2 -0
- package/dist/cost-tracker/types.js.map +1 -0
- package/dist/dispatch/dispatcher-impl.d.ts +92 -0
- package/dist/dispatch/dispatcher-impl.d.ts.map +1 -0
- package/dist/dispatch/dispatcher-impl.js +847 -0
- package/dist/dispatch/dispatcher-impl.js.map +1 -0
- package/dist/dispatch/index.d.ts +15 -0
- package/dist/dispatch/index.d.ts.map +1 -0
- package/dist/dispatch/index.js +14 -0
- package/dist/dispatch/index.js.map +1 -0
- package/dist/dispatch/interface-change-detector.d.ts +46 -0
- package/dist/dispatch/interface-change-detector.d.ts.map +1 -0
- package/dist/dispatch/interface-change-detector.js +135 -0
- package/dist/dispatch/interface-change-detector.js.map +1 -0
- package/dist/dispatch/output-quality.d.ts +43 -0
- package/dist/dispatch/output-quality.d.ts.map +1 -0
- package/dist/dispatch/output-quality.js +148 -0
- package/dist/dispatch/output-quality.js.map +1 -0
- package/dist/dispatch/types.d.ts +271 -0
- package/dist/dispatch/types.d.ts.map +1 -0
- package/dist/dispatch/types.js +76 -0
- package/dist/dispatch/types.js.map +1 -0
- package/dist/dispatch/yaml-parser.d.ts +40 -0
- package/dist/dispatch/yaml-parser.d.ts.map +1 -0
- package/dist/dispatch/yaml-parser.js +323 -0
- package/dist/dispatch/yaml-parser.js.map +1 -0
- package/dist/events/core-events.d.ts +288 -0
- package/dist/events/core-events.d.ts.map +1 -0
- package/dist/events/core-events.js +10 -0
- package/dist/events/core-events.js.map +1 -0
- package/dist/events/event-bus.d.ts +55 -0
- package/dist/events/event-bus.d.ts.map +1 -0
- package/dist/events/event-bus.js +52 -0
- package/dist/events/event-bus.js.map +1 -0
- package/dist/events/index.d.ts +9 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/index.js +6 -0
- package/dist/events/index.js.map +1 -0
- package/dist/events/types.d.ts +21 -0
- package/dist/events/types.d.ts.map +1 -0
- package/dist/events/types.js +8 -0
- package/dist/events/types.js.map +1 -0
- package/dist/git/git-manager.d.ts +31 -0
- package/dist/git/git-manager.d.ts.map +1 -0
- package/dist/git/git-manager.js +46 -0
- package/dist/git/git-manager.js.map +1 -0
- package/dist/git/git-utils.d.ts +166 -0
- package/dist/git/git-utils.d.ts.map +1 -0
- package/dist/git/git-utils.js +347 -0
- package/dist/git/git-utils.js.map +1 -0
- package/dist/git/git-worktree-manager-impl.d.ts +58 -0
- package/dist/git/git-worktree-manager-impl.d.ts.map +1 -0
- package/dist/git/git-worktree-manager-impl.js +336 -0
- package/dist/git/git-worktree-manager-impl.js.map +1 -0
- package/dist/git/git-worktree-manager.d.ts +122 -0
- package/dist/git/git-worktree-manager.d.ts.map +1 -0
- package/dist/git/git-worktree-manager.js +14 -0
- package/dist/git/git-worktree-manager.js.map +1 -0
- package/dist/git/index.d.ts +11 -0
- package/dist/git/index.d.ts.map +1 -0
- package/dist/git/index.js +8 -0
- package/dist/git/index.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +47 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/client.d.ts +42 -0
- package/dist/llm/client.d.ts.map +1 -0
- package/dist/llm/client.js +27 -0
- package/dist/llm/client.js.map +1 -0
- package/dist/monitor/index.d.ts +15 -0
- package/dist/monitor/index.d.ts.map +1 -0
- package/dist/monitor/index.js +9 -0
- package/dist/monitor/index.js.map +1 -0
- package/dist/monitor/monitor-agent-impl.d.ts +56 -0
- package/dist/monitor/monitor-agent-impl.d.ts.map +1 -0
- package/dist/monitor/monitor-agent-impl.js +178 -0
- package/dist/monitor/monitor-agent-impl.js.map +1 -0
- package/dist/monitor/monitor-agent.d.ts +36 -0
- package/dist/monitor/monitor-agent.d.ts.map +1 -0
- package/dist/monitor/monitor-agent.js +6 -0
- package/dist/monitor/monitor-agent.js.map +1 -0
- package/dist/monitor/performance-aggregates.d.ts +41 -0
- package/dist/monitor/performance-aggregates.d.ts.map +1 -0
- package/dist/monitor/performance-aggregates.js +6 -0
- package/dist/monitor/performance-aggregates.js.map +1 -0
- package/dist/monitor/recommendation-engine.d.ts +27 -0
- package/dist/monitor/recommendation-engine.d.ts.map +1 -0
- package/dist/monitor/recommendation-engine.js +140 -0
- package/dist/monitor/recommendation-engine.js.map +1 -0
- package/dist/monitor/recommendation-types.d.ts +30 -0
- package/dist/monitor/recommendation-types.d.ts.map +1 -0
- package/dist/monitor/recommendation-types.js +43 -0
- package/dist/monitor/recommendation-types.js.map +1 -0
- package/dist/monitor/report-generator.d.ts +48 -0
- package/dist/monitor/report-generator.d.ts.map +1 -0
- package/dist/monitor/report-generator.js +123 -0
- package/dist/monitor/report-generator.js.map +1 -0
- package/dist/monitor/task-type-classifier.d.ts +19 -0
- package/dist/monitor/task-type-classifier.d.ts.map +1 -0
- package/dist/monitor/task-type-classifier.js +68 -0
- package/dist/monitor/task-type-classifier.js.map +1 -0
- package/dist/persistence/adapter.d.ts +4 -0
- package/dist/persistence/adapter.d.ts.map +1 -0
- package/dist/persistence/adapter.js +56 -0
- package/dist/persistence/adapter.js.map +1 -0
- package/dist/persistence/cost-types.d.ts +87 -0
- package/dist/persistence/cost-types.d.ts.map +1 -0
- package/dist/persistence/cost-types.js +14 -0
- package/dist/persistence/cost-types.js.map +1 -0
- package/dist/persistence/dolt-adapter-transaction.test.d.ts +10 -0
- package/dist/persistence/dolt-adapter-transaction.test.d.ts.map +1 -0
- package/dist/persistence/dolt-adapter-transaction.test.js +359 -0
- package/dist/persistence/dolt-adapter-transaction.test.js.map +1 -0
- package/dist/persistence/dolt-adapter.d.ts +77 -0
- package/dist/persistence/dolt-adapter.d.ts.map +1 -0
- package/dist/persistence/dolt-adapter.js +98 -0
- package/dist/persistence/dolt-adapter.js.map +1 -0
- package/dist/persistence/dolt-client.d.ts +69 -0
- package/dist/persistence/dolt-client.d.ts.map +1 -0
- package/dist/persistence/dolt-client.js +278 -0
- package/dist/persistence/dolt-client.js.map +1 -0
- package/dist/persistence/dolt-errors.d.ts +10 -0
- package/dist/persistence/dolt-errors.d.ts.map +1 -0
- package/dist/persistence/dolt-errors.js +15 -0
- package/dist/persistence/dolt-errors.js.map +1 -0
- package/dist/persistence/dolt-init.d.ts +64 -0
- package/dist/persistence/dolt-init.d.ts.map +1 -0
- package/dist/persistence/dolt-init.js +233 -0
- package/dist/persistence/dolt-init.js.map +1 -0
- package/dist/persistence/index.d.ts +23 -0
- package/dist/persistence/index.d.ts.map +1 -0
- package/dist/persistence/index.js +23 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/persistence/memory-adapter.d.ts +156 -0
- package/dist/persistence/memory-adapter.d.ts.map +1 -0
- package/dist/persistence/memory-adapter.js +1167 -0
- package/dist/persistence/memory-adapter.js.map +1 -0
- package/dist/persistence/monitor-database.d.ts +113 -0
- package/dist/persistence/monitor-database.d.ts.map +1 -0
- package/dist/persistence/monitor-database.js +345 -0
- package/dist/persistence/monitor-database.js.map +1 -0
- package/dist/persistence/queries/amendments.d.ts +91 -0
- package/dist/persistence/queries/amendments.d.ts.map +1 -0
- package/dist/persistence/queries/amendments.js +185 -0
- package/dist/persistence/queries/amendments.js.map +1 -0
- package/dist/persistence/queries/cost.d.ts +130 -0
- package/dist/persistence/queries/cost.d.ts.map +1 -0
- package/dist/persistence/queries/cost.js +384 -0
- package/dist/persistence/queries/cost.js.map +1 -0
- package/dist/persistence/queries/decisions.d.ts +131 -0
- package/dist/persistence/queries/decisions.d.ts.map +1 -0
- package/dist/persistence/queries/decisions.js +339 -0
- package/dist/persistence/queries/decisions.js.map +1 -0
- package/dist/persistence/queries/metrics.d.ts +155 -0
- package/dist/persistence/queries/metrics.d.ts.map +1 -0
- package/dist/persistence/queries/metrics.js +237 -0
- package/dist/persistence/queries/metrics.js.map +1 -0
- package/dist/persistence/queries/retry-escalated.d.ts +35 -0
- package/dist/persistence/queries/retry-escalated.d.ts.map +1 -0
- package/dist/persistence/queries/retry-escalated.js +83 -0
- package/dist/persistence/queries/retry-escalated.js.map +1 -0
- package/dist/persistence/schema-version.d.ts +89 -0
- package/dist/persistence/schema-version.d.ts.map +1 -0
- package/dist/persistence/schema-version.js +67 -0
- package/dist/persistence/schema-version.js.map +1 -0
- package/dist/persistence/schema.d.ts +26 -0
- package/dist/persistence/schema.d.ts.map +1 -0
- package/dist/persistence/schema.js +509 -0
- package/dist/persistence/schema.js.map +1 -0
- package/dist/persistence/schemas/decisions.d.ts +176 -0
- package/dist/persistence/schemas/decisions.d.ts.map +1 -0
- package/dist/persistence/schemas/decisions.js +139 -0
- package/dist/persistence/schemas/decisions.js.map +1 -0
- package/dist/persistence/schemas/operational.d.ts +194 -0
- package/dist/persistence/schemas/operational.d.ts.map +1 -0
- package/dist/persistence/schemas/operational.js +197 -0
- package/dist/persistence/schemas/operational.js.map +1 -0
- package/dist/persistence/types.d.ts +98 -0
- package/dist/persistence/types.d.ts.map +1 -0
- package/dist/persistence/types.js +22 -0
- package/dist/persistence/types.js.map +1 -0
- package/dist/quality-gates/index.d.ts +2 -0
- package/dist/quality-gates/index.d.ts.map +1 -0
- package/dist/quality-gates/index.js +2 -0
- package/dist/quality-gates/index.js.map +1 -0
- package/dist/quality-gates/types.d.ts +106 -0
- package/dist/quality-gates/types.d.ts.map +1 -0
- package/dist/quality-gates/types.js +5 -0
- package/dist/quality-gates/types.js.map +1 -0
- package/dist/routing/index.d.ts +19 -0
- package/dist/routing/index.d.ts.map +1 -0
- package/dist/routing/index.js +32 -0
- package/dist/routing/index.js.map +1 -0
- package/dist/routing/model-routing-config.d.ts +75 -0
- package/dist/routing/model-routing-config.d.ts.map +1 -0
- package/dist/routing/model-routing-config.js +110 -0
- package/dist/routing/model-routing-config.js.map +1 -0
- package/dist/routing/model-routing-resolver.d.ts +48 -0
- package/dist/routing/model-routing-resolver.d.ts.map +1 -0
- package/dist/routing/model-routing-resolver.js +105 -0
- package/dist/routing/model-routing-resolver.js.map +1 -0
- package/dist/routing/model-tier.d.ts +21 -0
- package/dist/routing/model-tier.d.ts.map +1 -0
- package/dist/routing/model-tier.js +34 -0
- package/dist/routing/model-tier.js.map +1 -0
- package/dist/routing/provider-status.d.ts +99 -0
- package/dist/routing/provider-status.d.ts.map +1 -0
- package/dist/routing/provider-status.js +163 -0
- package/dist/routing/provider-status.js.map +1 -0
- package/dist/routing/routing-decision.d.ts +127 -0
- package/dist/routing/routing-decision.d.ts.map +1 -0
- package/dist/routing/routing-decision.js +111 -0
- package/dist/routing/routing-decision.js.map +1 -0
- package/dist/routing/routing-engine-impl.d.ts +132 -0
- package/dist/routing/routing-engine-impl.d.ts.map +1 -0
- package/dist/routing/routing-engine-impl.js +450 -0
- package/dist/routing/routing-engine-impl.js.map +1 -0
- package/dist/routing/routing-engine.d.ts +83 -0
- package/dist/routing/routing-engine.d.ts.map +1 -0
- package/dist/routing/routing-engine.js +24 -0
- package/dist/routing/routing-engine.js.map +1 -0
- package/dist/routing/routing-policy.d.ts +138 -0
- package/dist/routing/routing-policy.d.ts.map +1 -0
- package/dist/routing/routing-policy.js +159 -0
- package/dist/routing/routing-policy.js.map +1 -0
- package/dist/routing/routing-recommender.d.ts +60 -0
- package/dist/routing/routing-recommender.d.ts.map +1 -0
- package/dist/routing/routing-recommender.js +209 -0
- package/dist/routing/routing-recommender.js.map +1 -0
- package/dist/routing/routing-telemetry.d.ts +36 -0
- package/dist/routing/routing-telemetry.d.ts.map +1 -0
- package/dist/routing/routing-telemetry.js +39 -0
- package/dist/routing/routing-telemetry.js.map +1 -0
- package/dist/routing/routing-token-accumulator.d.ts +68 -0
- package/dist/routing/routing-token-accumulator.d.ts.map +1 -0
- package/dist/routing/routing-token-accumulator.js +111 -0
- package/dist/routing/routing-token-accumulator.js.map +1 -0
- package/dist/routing/routing-tuner.d.ts +69 -0
- package/dist/routing/routing-tuner.d.ts.map +1 -0
- package/dist/routing/routing-tuner.js +217 -0
- package/dist/routing/routing-tuner.js.map +1 -0
- package/dist/routing/types.d.ts +152 -0
- package/dist/routing/types.d.ts.map +1 -0
- package/dist/routing/types.js +13 -0
- package/dist/routing/types.js.map +1 -0
- package/dist/supervisor/analysis.d.ts +178 -0
- package/dist/supervisor/analysis.d.ts.map +1 -0
- package/dist/supervisor/analysis.js +420 -0
- package/dist/supervisor/analysis.js.map +1 -0
- package/dist/supervisor/experimenter.d.ts +118 -0
- package/dist/supervisor/experimenter.d.ts.map +1 -0
- package/dist/supervisor/experimenter.js +493 -0
- package/dist/supervisor/experimenter.js.map +1 -0
- package/dist/supervisor/index.d.ts +13 -0
- package/dist/supervisor/index.d.ts.map +1 -0
- package/dist/supervisor/index.js +11 -0
- package/dist/supervisor/index.js.map +1 -0
- package/dist/telemetry/batch-buffer.d.ts +53 -0
- package/dist/telemetry/batch-buffer.d.ts.map +1 -0
- package/dist/telemetry/batch-buffer.js +83 -0
- package/dist/telemetry/batch-buffer.js.map +1 -0
- package/dist/telemetry/categorizer.d.ts +65 -0
- package/dist/telemetry/categorizer.d.ts.map +1 -0
- package/dist/telemetry/categorizer.js +338 -0
- package/dist/telemetry/categorizer.js.map +1 -0
- package/dist/telemetry/consumer-analyzer.d.ts +53 -0
- package/dist/telemetry/consumer-analyzer.d.ts.map +1 -0
- package/dist/telemetry/consumer-analyzer.js +182 -0
- package/dist/telemetry/consumer-analyzer.js.map +1 -0
- package/dist/telemetry/cost-table.d.ts +36 -0
- package/dist/telemetry/cost-table.d.ts.map +1 -0
- package/dist/telemetry/cost-table.js +127 -0
- package/dist/telemetry/cost-table.js.map +1 -0
- package/dist/telemetry/efficiency-scorer.d.ts +103 -0
- package/dist/telemetry/efficiency-scorer.d.ts.map +1 -0
- package/dist/telemetry/efficiency-scorer.js +311 -0
- package/dist/telemetry/efficiency-scorer.js.map +1 -0
- package/dist/telemetry/index.d.ts +28 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +37 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/telemetry/ingestion-server.d.ts +99 -0
- package/dist/telemetry/ingestion-server.d.ts.map +1 -0
- package/dist/telemetry/ingestion-server.js +315 -0
- package/dist/telemetry/ingestion-server.js.map +1 -0
- package/dist/telemetry/log-turn-analyzer.d.ts +35 -0
- package/dist/telemetry/log-turn-analyzer.d.ts.map +1 -0
- package/dist/telemetry/log-turn-analyzer.js +132 -0
- package/dist/telemetry/log-turn-analyzer.js.map +1 -0
- package/dist/telemetry/normalizer.d.ts +43 -0
- package/dist/telemetry/normalizer.d.ts.map +1 -0
- package/dist/telemetry/normalizer.js +320 -0
- package/dist/telemetry/normalizer.js.map +1 -0
- package/dist/telemetry/recommender.d.ts +116 -0
- package/dist/telemetry/recommender.d.ts.map +1 -0
- package/dist/telemetry/recommender.js +532 -0
- package/dist/telemetry/recommender.js.map +1 -0
- package/dist/telemetry/source-detector.d.ts +19 -0
- package/dist/telemetry/source-detector.d.ts.map +1 -0
- package/dist/telemetry/source-detector.js +73 -0
- package/dist/telemetry/source-detector.js.map +1 -0
- package/dist/telemetry/task-baselines.d.ts +30 -0
- package/dist/telemetry/task-baselines.d.ts.map +1 -0
- package/dist/telemetry/task-baselines.js +44 -0
- package/dist/telemetry/task-baselines.js.map +1 -0
- package/dist/telemetry/telemetry-pipeline.d.ts +122 -0
- package/dist/telemetry/telemetry-pipeline.d.ts.map +1 -0
- package/dist/telemetry/telemetry-pipeline.js +349 -0
- package/dist/telemetry/telemetry-pipeline.js.map +1 -0
- package/dist/telemetry/timestamp-normalizer.d.ts +32 -0
- package/dist/telemetry/timestamp-normalizer.d.ts.map +1 -0
- package/dist/telemetry/timestamp-normalizer.js +133 -0
- package/dist/telemetry/timestamp-normalizer.js.map +1 -0
- package/dist/telemetry/token-extractor.d.ts +57 -0
- package/dist/telemetry/token-extractor.d.ts.map +1 -0
- package/dist/telemetry/token-extractor.js +200 -0
- package/dist/telemetry/token-extractor.js.map +1 -0
- package/dist/telemetry/turn-analyzer.d.ts +34 -0
- package/dist/telemetry/turn-analyzer.d.ts.map +1 -0
- package/dist/telemetry/turn-analyzer.js +101 -0
- package/dist/telemetry/turn-analyzer.js.map +1 -0
- package/dist/telemetry/types.d.ts +456 -0
- package/dist/telemetry/types.d.ts.map +1 -0
- package/dist/telemetry/types.js +186 -0
- package/dist/telemetry/types.js.map +1 -0
- package/dist/types.d.ts +80 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/version-manager/index.d.ts +11 -0
- package/dist/version-manager/index.d.ts.map +1 -0
- package/dist/version-manager/index.js +8 -0
- package/dist/version-manager/index.js.map +1 -0
- package/dist/version-manager/update-checker.d.ts +44 -0
- package/dist/version-manager/update-checker.d.ts.map +1 -0
- package/dist/version-manager/update-checker.js +171 -0
- package/dist/version-manager/update-checker.js.map +1 -0
- package/dist/version-manager/version-cache.d.ts +42 -0
- package/dist/version-manager/version-cache.d.ts.map +1 -0
- package/dist/version-manager/version-cache.js +69 -0
- package/dist/version-manager/version-cache.js.map +1 -0
- package/dist/version-manager/version-manager-impl.d.ts +81 -0
- package/dist/version-manager/version-manager-impl.d.ts.map +1 -0
- package/dist/version-manager/version-manager-impl.js +223 -0
- package/dist/version-manager/version-manager-impl.js.map +1 -0
- package/dist/version-manager/version-manager.d.ts +70 -0
- package/dist/version-manager/version-manager.d.ts.map +1 -0
- package/dist/version-manager/version-manager.js +8 -0
- package/dist/version-manager/version-manager.js.map +1 -0
- package/package.json +27 -0
|
@@ -0,0 +1,847 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DispatcherImpl — concrete implementation of the Dispatcher interface.
|
|
3
|
+
*
|
|
4
|
+
* Spawns autonomous coding agents (Claude, Codex, Gemini) as subprocesses,
|
|
5
|
+
* tracks their lifecycle, enforces concurrency limits, parses their YAML
|
|
6
|
+
* output, and emits events through the event bus.
|
|
7
|
+
*
|
|
8
|
+
* Architecture:
|
|
9
|
+
* - Uses child_process.spawn (ADR-005) — NOT exec
|
|
10
|
+
* - Communicates via event bus (ADR-001: modular monolith)
|
|
11
|
+
* - Prompt delivered via stdin
|
|
12
|
+
* - YAML output parsed from stdout
|
|
13
|
+
* - Concurrency limited with FIFO queue
|
|
14
|
+
*/
|
|
15
|
+
import { spawn, execSync } from 'node:child_process';
|
|
16
|
+
import { freemem, platform } from 'node:os';
|
|
17
|
+
import { randomUUID } from 'node:crypto';
|
|
18
|
+
import { DispatcherShuttingDownError, DEFAULT_TIMEOUTS, DEFAULT_MAX_TURNS } from './types.js';
|
|
19
|
+
import { parseYamlResult } from './yaml-parser.js';
|
|
20
|
+
import { estimateOutputQuality } from './output-quality.js';
|
|
21
|
+
import { AdapterOutputNormalizer } from '../adapters/adapter-output-normalizer.js';
|
|
22
|
+
import { AdapterFormatError } from '../adapters/adapter-format-error.js';
|
|
23
|
+
// Grace period (ms) between SIGTERM and SIGKILL during shutdown()
|
|
24
|
+
const SHUTDOWN_GRACE_MS = 10_000;
|
|
25
|
+
// Maximum time (ms) to wait for processes to exit after SIGKILL during shutdown()
|
|
26
|
+
const SHUTDOWN_MAX_WAIT_MS = 30_000;
|
|
27
|
+
// Characters per token for estimation heuristic
|
|
28
|
+
const CHARS_PER_TOKEN = 4;
|
|
29
|
+
// YAML output format reminder appended to prompts for non-Claude agents.
|
|
30
|
+
// Claude Code follows the methodology pack's embedded YAML instructions reliably;
|
|
31
|
+
// other agents (Codex, Gemini) sometimes need an explicit final reminder.
|
|
32
|
+
// When an outputSchema is provided, the suffix includes actual field names
|
|
33
|
+
// so the agent knows exactly what to emit.
|
|
34
|
+
/**
|
|
35
|
+
* Extract top-level field names from a Zod schema for prompt injection.
|
|
36
|
+
* Returns field names with type hints (e.g., "result: <string>", "files_modified: <list>").
|
|
37
|
+
* Exported for testing.
|
|
38
|
+
*/
|
|
39
|
+
export function extractSchemaFields(schema) {
|
|
40
|
+
// Zod object schemas have a .shape property with field definitions.
|
|
41
|
+
// Navigate through .innerType() for transformed/preprocessed schemas.
|
|
42
|
+
let current = schema;
|
|
43
|
+
// Unwrap ZodEffects / pipe (transform/preprocess/refine)
|
|
44
|
+
// Zod 3.x: typeName === 'ZodEffects', inner at _def.schema
|
|
45
|
+
// Zod 4.x: type === 'pipe', inner at _def.in
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
47
|
+
let def = current?._def;
|
|
48
|
+
while (true) {
|
|
49
|
+
if (def?.typeName === 'ZodEffects' && def?.schema != null) {
|
|
50
|
+
current = def.schema;
|
|
51
|
+
}
|
|
52
|
+
else if (def?.type === 'pipe' && def?.in != null) {
|
|
53
|
+
current = def.in;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
59
|
+
def = current?._def;
|
|
60
|
+
}
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
62
|
+
const shape = current?.shape;
|
|
63
|
+
if (shape == null || typeof shape !== 'object')
|
|
64
|
+
return [];
|
|
65
|
+
const fields = [];
|
|
66
|
+
for (const [key, fieldDef] of Object.entries(shape)) {
|
|
67
|
+
const typeName = resolveZodTypeName(fieldDef);
|
|
68
|
+
fields.push(`${key}: ${typeName}`);
|
|
69
|
+
}
|
|
70
|
+
return fields;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Resolve a human-readable type hint from a Zod field definition.
|
|
74
|
+
*/
|
|
75
|
+
function resolveZodTypeName(fieldDef) {
|
|
76
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
77
|
+
const d = fieldDef;
|
|
78
|
+
// Support both Zod 3.x (_def.typeName) and Zod 4.x (_def.type)
|
|
79
|
+
const typeName = (d?._def?.typeName ?? d?._def?.type);
|
|
80
|
+
// Unwrap wrappers: optional, default, preprocess
|
|
81
|
+
if (typeName === 'ZodOptional' || typeName === 'optional' || typeName === 'ZodDefault' || typeName === 'default') {
|
|
82
|
+
return d._def?.innerType != null ? resolveZodTypeName(d._def.innerType) : '<value>';
|
|
83
|
+
}
|
|
84
|
+
if (typeName === 'ZodEffects') {
|
|
85
|
+
return d._def?.schema != null ? resolveZodTypeName(d._def.schema) : '<value>';
|
|
86
|
+
}
|
|
87
|
+
if (typeName === 'transform')
|
|
88
|
+
return '<value>';
|
|
89
|
+
if (typeName === 'pipe') {
|
|
90
|
+
// Zod 4.x pipe: _def.out is the output schema (for preprocess/transform)
|
|
91
|
+
return d._def?.out != null ? resolveZodTypeName(d._def.out) : '<value>';
|
|
92
|
+
}
|
|
93
|
+
if (typeName === 'ZodString' || typeName === 'string')
|
|
94
|
+
return '<string>';
|
|
95
|
+
if (typeName === 'ZodNumber' || typeName === 'number')
|
|
96
|
+
return '<number>';
|
|
97
|
+
if (typeName === 'ZodBoolean' || typeName === 'boolean')
|
|
98
|
+
return '<boolean>';
|
|
99
|
+
if (typeName === 'ZodEnum' || typeName === 'enum') {
|
|
100
|
+
// Zod 3.x: _def.values is string[]; Zod 4.x: _def.entries is Record<string, string>
|
|
101
|
+
const values = d._def?.values;
|
|
102
|
+
const entries = d._def?.entries;
|
|
103
|
+
if (values != null)
|
|
104
|
+
return values.join(' | ');
|
|
105
|
+
if (entries != null)
|
|
106
|
+
return Object.keys(entries).join(' | ');
|
|
107
|
+
return '<enum>';
|
|
108
|
+
}
|
|
109
|
+
if (typeName === 'ZodArray' || typeName === 'array')
|
|
110
|
+
return '<list>';
|
|
111
|
+
if (typeName === 'ZodObject' || typeName === 'object')
|
|
112
|
+
return '<object>';
|
|
113
|
+
return '<value>';
|
|
114
|
+
}
|
|
115
|
+
/** Exported for testing. */
|
|
116
|
+
export function buildYamlOutputSuffix(outputSchema) {
|
|
117
|
+
const fields = outputSchema != null ? extractSchemaFields(outputSchema) : [];
|
|
118
|
+
const fieldLines = fields.length > 0
|
|
119
|
+
? fields.map((f) => ` ${f}`).join('\n')
|
|
120
|
+
: ' result: success\n # ... additional fields as specified in the task above';
|
|
121
|
+
return `
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
IMPORTANT: When you have completed the task, output your structured result as a fenced YAML block at the END of your response. Use this exact format:
|
|
125
|
+
|
|
126
|
+
\`\`\`yaml
|
|
127
|
+
${fieldLines}
|
|
128
|
+
\`\`\`
|
|
129
|
+
|
|
130
|
+
The YAML block MUST be the last thing in your output. Do not add any text after the closing fence.`;
|
|
131
|
+
}
|
|
132
|
+
// Minimum free system memory (bytes) required before spawning a new agent.
|
|
133
|
+
// When free memory is below this threshold, dispatches are held in the queue
|
|
134
|
+
// and retried periodically until memory recovers.
|
|
135
|
+
// Override with SUBSTRATE_MEMORY_THRESHOLD_MB env var (e.g. "128" for 128 MB).
|
|
136
|
+
//
|
|
137
|
+
// History: originally 256MB when concurrent test suite runs caused OOM.
|
|
138
|
+
// Lowered to 128MB in v0.8.5 — concurrent dispatch is now controlled by
|
|
139
|
+
// concurrency slots, and the test suite has been optimized. Only level 4
|
|
140
|
+
// (critical) kernel pressure hard-gates to 0.
|
|
141
|
+
const MIN_FREE_MEMORY_BYTES = (() => {
|
|
142
|
+
const envMB = process.env['SUBSTRATE_MEMORY_THRESHOLD_MB'];
|
|
143
|
+
if (envMB) {
|
|
144
|
+
const parsed = parseInt(envMB, 10);
|
|
145
|
+
if (!isNaN(parsed) && parsed >= 0)
|
|
146
|
+
return parsed * 1024 * 1024;
|
|
147
|
+
}
|
|
148
|
+
return 128 * 1024 * 1024; // 128 MB default
|
|
149
|
+
})();
|
|
150
|
+
// How often (ms) to re-check memory when the queue is held due to pressure
|
|
151
|
+
const MEMORY_PRESSURE_POLL_MS = 10_000;
|
|
152
|
+
// Maximum time (ms) to hold the dispatch queue due to memory pressure before
|
|
153
|
+
// forcing dispatch with a warning. Prevents indefinite stalls when the system
|
|
154
|
+
// is persistently pressured (e.g., macOS with many concurrent processes).
|
|
155
|
+
const MEMORY_PRESSURE_MAX_HOLD_MS = 300_000; // 5 minutes
|
|
156
|
+
// Tracks the most recently observed macOS kernel pressure level (1=normal,
|
|
157
|
+
// 2=warn, 4=critical). Updated inside getAvailableMemory() on darwin.
|
|
158
|
+
// Read by _isMemoryPressured() to include in the warn log (Story 23-8, AC3).
|
|
159
|
+
let _lastKnownPressureLevel = 0;
|
|
160
|
+
/**
|
|
161
|
+
* Get available system memory in bytes, accounting for platform differences.
|
|
162
|
+
*
|
|
163
|
+
* On macOS, the previous approach (free + inactive pages) dramatically
|
|
164
|
+
* overestimates availability because inactive pages may be compressed,
|
|
165
|
+
* swapped, or require I/O to reclaim. With heavy agent concurrency, this
|
|
166
|
+
* leads to spawning too many processes and triggering real memory pressure.
|
|
167
|
+
*
|
|
168
|
+
* New approach:
|
|
169
|
+
* 1. Check kern.memorystatus_vm_pressure_level — the kernel's own assessment.
|
|
170
|
+
* Level 4 (critical) = hard gate, return 0.
|
|
171
|
+
* Level 2 (warn) = halve the vm_stat estimate as a conservative signal.
|
|
172
|
+
* Level 1 (normal) = trust vm_stat as-is.
|
|
173
|
+
* Note: level 2 fires frequently on macOS when the compressor is active,
|
|
174
|
+
* even with gigabytes of reclaimable memory. Hard-gating at 2 caused
|
|
175
|
+
* false stalls on 24GB+ machines with >50% free RAM.
|
|
176
|
+
* 2. Use page calculation: free + inactive + purgeable + speculative.
|
|
177
|
+
* These categories are reclaimable by the OS — matching macOS Activity
|
|
178
|
+
* Monitor's definition of available memory (Physical - Used).
|
|
179
|
+
* Inactive pages ("Cached Files") are included because macOS reclaims
|
|
180
|
+
* them transparently. Excluding them caused substrate to see ~3 GB
|
|
181
|
+
* available when Activity Monitor showed ~8 GB on a 24 GB machine.
|
|
182
|
+
*/
|
|
183
|
+
function getAvailableMemory(logger) {
|
|
184
|
+
if (platform() === 'darwin') {
|
|
185
|
+
let pressureLevel = 0;
|
|
186
|
+
try {
|
|
187
|
+
// Kernel memory pressure level (1=normal, 2=warn, 4=critical)
|
|
188
|
+
pressureLevel = parseInt(execSync('sysctl -n kern.memorystatus_vm_pressure_level', {
|
|
189
|
+
timeout: 1000,
|
|
190
|
+
encoding: 'utf-8',
|
|
191
|
+
}).trim(), 10);
|
|
192
|
+
_lastKnownPressureLevel = pressureLevel;
|
|
193
|
+
if (pressureLevel >= 4) {
|
|
194
|
+
logger.warn({ pressureLevel }, 'macOS kernel reports critical memory pressure');
|
|
195
|
+
return 0;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
// sysctl not available — fall through to vm_stat
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
const vmstat = execSync('vm_stat', { timeout: 2000, encoding: 'utf-8' });
|
|
203
|
+
const pageSize = parseInt(vmstat.match(/page size of (\d+)/)?.[1] ?? '4096', 10);
|
|
204
|
+
const free = parseInt(vmstat.match(/Pages free:\s+(\d+)/)?.[1] ?? '0', 10);
|
|
205
|
+
const inactive = parseInt(vmstat.match(/Pages inactive:\s+(\d+)/)?.[1] ?? '0', 10);
|
|
206
|
+
const purgeable = parseInt(vmstat.match(/Pages purgeable:\s+(\d+)/)?.[1] ?? '0', 10);
|
|
207
|
+
const speculative = parseInt(vmstat.match(/Pages speculative:\s+(\d+)/)?.[1] ?? '0', 10);
|
|
208
|
+
const available = (free + inactive + purgeable + speculative) * pageSize;
|
|
209
|
+
// At warn level (2), log but trust the vm_stat estimate as-is.
|
|
210
|
+
// Level 2 fires frequently on macOS when the compressor is active,
|
|
211
|
+
// even with gigabytes of reclaimable memory. Halving caused false
|
|
212
|
+
// stalls on 24GB machines during single-story runs.
|
|
213
|
+
// Only level 4 (critical) hard-gates to 0 (handled above).
|
|
214
|
+
if (pressureLevel >= 2) {
|
|
215
|
+
logger.debug({ pressureLevel, available }, 'macOS kernel reports memory pressure level 2 (warn) — using raw estimate');
|
|
216
|
+
}
|
|
217
|
+
return available;
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return freemem();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// Non-darwin: pressure level is always 0
|
|
224
|
+
_lastKnownPressureLevel = 0;
|
|
225
|
+
return freemem();
|
|
226
|
+
}
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
// Mutable handle implementation
|
|
229
|
+
// ---------------------------------------------------------------------------
|
|
230
|
+
class MutableDispatchHandle {
|
|
231
|
+
id;
|
|
232
|
+
status;
|
|
233
|
+
_cancelFn;
|
|
234
|
+
constructor(id, initialStatus, cancelFn) {
|
|
235
|
+
this.id = id;
|
|
236
|
+
this.status = initialStatus;
|
|
237
|
+
this._cancelFn = cancelFn;
|
|
238
|
+
}
|
|
239
|
+
cancel() {
|
|
240
|
+
return this._cancelFn();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
// DispatcherImpl
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
export class DispatcherImpl {
|
|
247
|
+
_eventBus;
|
|
248
|
+
_adapterRegistry;
|
|
249
|
+
_config;
|
|
250
|
+
_logger;
|
|
251
|
+
_normalizer;
|
|
252
|
+
_running = new Map();
|
|
253
|
+
_queue = [];
|
|
254
|
+
_shuttingDown = false;
|
|
255
|
+
_memoryPressureTimer = null;
|
|
256
|
+
_memoryPressureHoldStart = null;
|
|
257
|
+
constructor(eventBus, adapterRegistry, config, logger = console, normalizer = new AdapterOutputNormalizer()) {
|
|
258
|
+
this._eventBus = eventBus;
|
|
259
|
+
this._adapterRegistry = adapterRegistry;
|
|
260
|
+
this._config = config;
|
|
261
|
+
this._logger = logger;
|
|
262
|
+
this._normalizer = normalizer;
|
|
263
|
+
}
|
|
264
|
+
// ---------------------------------------------------------------------------
|
|
265
|
+
// Dispatcher interface
|
|
266
|
+
// ---------------------------------------------------------------------------
|
|
267
|
+
dispatch(request) {
|
|
268
|
+
if (this._shuttingDown) {
|
|
269
|
+
const handle = new MutableDispatchHandle(randomUUID(), 'queued', async () => { });
|
|
270
|
+
handle.status = 'failed';
|
|
271
|
+
return Object.assign(handle, {
|
|
272
|
+
result: Promise.reject(new DispatcherShuttingDownError()),
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
const id = randomUUID();
|
|
276
|
+
const resultPromise = new Promise((resolve, reject) => {
|
|
277
|
+
const typedResolve = resolve;
|
|
278
|
+
if (this._running.size < this._config.maxConcurrency && !this._isMemoryPressured()) {
|
|
279
|
+
// Reserve the running slot synchronously before async work begins
|
|
280
|
+
// This ensures getRunning() returns the correct count immediately
|
|
281
|
+
this._reserveSlot(id);
|
|
282
|
+
// Start the actual dispatch asynchronously
|
|
283
|
+
this._startDispatch(id, request, typedResolve).catch((err) => {
|
|
284
|
+
// If _startDispatch throws unexpectedly, clean up the slot and drain the queue
|
|
285
|
+
this._running.delete(id);
|
|
286
|
+
this._drainQueue();
|
|
287
|
+
reject(err);
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
// Queue it
|
|
292
|
+
const queueHandle = new MutableDispatchHandle(id, 'queued', async () => {
|
|
293
|
+
this._removeFromQueue(id);
|
|
294
|
+
resolve({
|
|
295
|
+
id,
|
|
296
|
+
status: 'failed',
|
|
297
|
+
exitCode: -1,
|
|
298
|
+
output: '',
|
|
299
|
+
parsed: null,
|
|
300
|
+
parseError: 'Cancelled while queued',
|
|
301
|
+
durationMs: 0,
|
|
302
|
+
tokenEstimate: { input: 0, output: 0 },
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
this._queue.push({
|
|
306
|
+
id,
|
|
307
|
+
request: request,
|
|
308
|
+
handle: queueHandle,
|
|
309
|
+
resolve: typedResolve,
|
|
310
|
+
reject,
|
|
311
|
+
});
|
|
312
|
+
this._logger.debug({ id, queueLength: this._queue.length }, 'Dispatch queued');
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
// Determine initial status by checking whether the id was placed in the
|
|
316
|
+
// running map (via _reserveSlot) or in the queue. Reading both maps
|
|
317
|
+
// directly avoids any reliance on side-effect ordering from the async
|
|
318
|
+
// _startDispatch call above.
|
|
319
|
+
const initialStatus = this._running.has(id) ? 'running' : 'queued';
|
|
320
|
+
const cancelFn = async () => {
|
|
321
|
+
// If queued, remove from queue and reject the pending promise so the caller is not left hanging
|
|
322
|
+
const queueIdx = this._queue.findIndex((q) => q.id === id);
|
|
323
|
+
if (queueIdx !== -1) {
|
|
324
|
+
const [queued] = this._queue.splice(queueIdx, 1);
|
|
325
|
+
queued?.reject(new Error(`Dispatch ${id} was cancelled while queued`));
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
// If running, kill the process
|
|
329
|
+
const entry = this._running.get(id);
|
|
330
|
+
if (entry !== undefined && entry.proc != null) {
|
|
331
|
+
try {
|
|
332
|
+
entry.proc.kill('SIGTERM');
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
// Process may already be dead
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
const handle = new MutableDispatchHandle(id, initialStatus, cancelFn);
|
|
340
|
+
return Object.assign(handle, { result: resultPromise });
|
|
341
|
+
}
|
|
342
|
+
getPending() {
|
|
343
|
+
return this._queue.length;
|
|
344
|
+
}
|
|
345
|
+
getRunning() {
|
|
346
|
+
return this._running.size;
|
|
347
|
+
}
|
|
348
|
+
async shutdown() {
|
|
349
|
+
this._shuttingDown = true;
|
|
350
|
+
this._stopMemoryPressureTimer();
|
|
351
|
+
this._logger.info({ running: this._running.size, queued: this._queue.length }, 'Dispatcher shutting down');
|
|
352
|
+
// Reject all queued dispatches
|
|
353
|
+
const queued = this._queue.splice(0, this._queue.length);
|
|
354
|
+
for (const entry of queued) {
|
|
355
|
+
entry.reject(new DispatcherShuttingDownError());
|
|
356
|
+
}
|
|
357
|
+
if (this._running.size === 0) {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
// Snapshot running dispatches
|
|
361
|
+
const runningEntries = Array.from(this._running.values());
|
|
362
|
+
// Send SIGTERM to all running processes, mark as terminated
|
|
363
|
+
for (const entry of runningEntries) {
|
|
364
|
+
entry.terminated = true;
|
|
365
|
+
if (entry.timeoutHandle !== null) {
|
|
366
|
+
clearTimeout(entry.timeoutHandle);
|
|
367
|
+
entry.timeoutHandle = null;
|
|
368
|
+
}
|
|
369
|
+
// Guard: proc may be null for placeholder slots reserved before spawn completes
|
|
370
|
+
if (entry.proc !== null && entry.proc !== undefined) {
|
|
371
|
+
try {
|
|
372
|
+
entry.proc.kill('SIGTERM');
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
// Process may already be dead
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
// Wait grace period
|
|
380
|
+
await new Promise((resolve) => setTimeout(resolve, SHUTDOWN_GRACE_MS));
|
|
381
|
+
// SIGKILL any still remaining
|
|
382
|
+
for (const entry of runningEntries) {
|
|
383
|
+
if (this._running.has(entry.id)) {
|
|
384
|
+
// Guard: proc may be null for placeholder slots
|
|
385
|
+
if (entry.proc !== null && entry.proc !== undefined) {
|
|
386
|
+
try {
|
|
387
|
+
entry.proc.kill('SIGKILL');
|
|
388
|
+
}
|
|
389
|
+
catch {
|
|
390
|
+
// Process may already be dead
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
// Wait for all to exit (they should have after SIGKILL)
|
|
396
|
+
// A maximum wait of 30 seconds is enforced to avoid hanging forever if
|
|
397
|
+
// SIGKILL fails (e.g., on Windows or in edge cases).
|
|
398
|
+
if (this._running.size > 0) {
|
|
399
|
+
await new Promise((resolve) => {
|
|
400
|
+
const startWait = Date.now();
|
|
401
|
+
const checkInterval = setInterval(() => {
|
|
402
|
+
if (this._running.size === 0 || Date.now() - startWait >= SHUTDOWN_MAX_WAIT_MS) {
|
|
403
|
+
clearInterval(checkInterval);
|
|
404
|
+
resolve();
|
|
405
|
+
}
|
|
406
|
+
}, 50);
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
this._logger.info('Dispatcher shutdown complete');
|
|
410
|
+
}
|
|
411
|
+
// ---------------------------------------------------------------------------
|
|
412
|
+
// Internal dispatch lifecycle
|
|
413
|
+
// ---------------------------------------------------------------------------
|
|
414
|
+
async _startDispatch(id, request, resolve) {
|
|
415
|
+
const { prompt, agent, taskType, timeout, outputSchema, workingDirectory, model, maxTurns, maxContextTokens, otlpEndpoint, storyKey, optimizationDirectives } = request;
|
|
416
|
+
// Resolve effective model: explicit request.model wins; then routing resolver; then undefined (adapter default)
|
|
417
|
+
let effectiveModel = model;
|
|
418
|
+
if (effectiveModel === undefined && this._config.routingResolver !== undefined) {
|
|
419
|
+
const resolution = this._config.routingResolver.resolveModel(taskType);
|
|
420
|
+
if (resolution !== null) {
|
|
421
|
+
effectiveModel = resolution.model;
|
|
422
|
+
// Emit routing:model-selected before agent:spawned
|
|
423
|
+
this._eventBus.emit('routing:model-selected', {
|
|
424
|
+
dispatchId: id,
|
|
425
|
+
taskType,
|
|
426
|
+
model: resolution.model,
|
|
427
|
+
phase: resolution.phase,
|
|
428
|
+
source: resolution.source,
|
|
429
|
+
});
|
|
430
|
+
this._logger.debug({ id, taskType, model: resolution.model, routingSource: resolution.source }, 'Routing resolved model');
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
this._logger.debug({ id, taskType, routingSource: 'fallback' }, 'Routing returned null — using adapter default');
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
// Look up adapter
|
|
437
|
+
const adapter = this._adapterRegistry.get(agent);
|
|
438
|
+
if (adapter === undefined) {
|
|
439
|
+
this._logger.warn({ id, agent }, 'No adapter found for agent');
|
|
440
|
+
this._running.delete(id);
|
|
441
|
+
this._drainQueue();
|
|
442
|
+
resolve({
|
|
443
|
+
id,
|
|
444
|
+
status: 'failed',
|
|
445
|
+
exitCode: -1,
|
|
446
|
+
output: '',
|
|
447
|
+
parsed: null,
|
|
448
|
+
parseError: `No adapter registered for agent "${agent}"`,
|
|
449
|
+
durationMs: 0,
|
|
450
|
+
tokenEstimate: {
|
|
451
|
+
input: Math.ceil(prompt.length / CHARS_PER_TOKEN),
|
|
452
|
+
output: 0,
|
|
453
|
+
},
|
|
454
|
+
});
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
// Build spawn command from adapter, using workingDirectory from request or process.cwd() as fallback
|
|
458
|
+
const worktreePath = workingDirectory ?? process.cwd();
|
|
459
|
+
const resolvedMaxTurns = maxTurns ?? DEFAULT_MAX_TURNS[taskType];
|
|
460
|
+
// For agents that require it (declared via capabilities), append a YAML output
|
|
461
|
+
// format reminder to the prompt. Claude Code follows methodology pack format
|
|
462
|
+
// instructions reliably; other agents need an explicit final nudge.
|
|
463
|
+
const capabilities = adapter.getCapabilities();
|
|
464
|
+
const effectivePrompt = capabilities.requiresYamlSuffix === true
|
|
465
|
+
? prompt + buildYamlOutputSuffix(outputSchema)
|
|
466
|
+
: prompt;
|
|
467
|
+
const cmd = adapter.buildCommand(effectivePrompt, {
|
|
468
|
+
worktreePath,
|
|
469
|
+
billingMode: 'subscription',
|
|
470
|
+
...(effectiveModel !== undefined ? { model: effectiveModel } : {}),
|
|
471
|
+
...(resolvedMaxTurns !== undefined ? { maxTurns: resolvedMaxTurns } : {}),
|
|
472
|
+
...(maxContextTokens !== undefined ? { maxContextTokens } : {}),
|
|
473
|
+
...(otlpEndpoint !== undefined ? { otlpEndpoint } : {}),
|
|
474
|
+
...(storyKey !== undefined ? { storyKey } : {}),
|
|
475
|
+
...(optimizationDirectives !== undefined ? { optimizationDirectives } : {}),
|
|
476
|
+
taskType,
|
|
477
|
+
dispatchId: id,
|
|
478
|
+
});
|
|
479
|
+
// Resolve timeout, applying per-agent multiplier from adapter capabilities.
|
|
480
|
+
// Agents that are slower (e.g., Codex) declare timeoutMultiplier > 1.0
|
|
481
|
+
// so all timeouts scale automatically without per-project config overrides.
|
|
482
|
+
const baseTimeoutMs = timeout ??
|
|
483
|
+
this._config.defaultTimeouts[taskType] ??
|
|
484
|
+
DEFAULT_TIMEOUTS[taskType] ??
|
|
485
|
+
300_000;
|
|
486
|
+
const timeoutMultiplier = capabilities.timeoutMultiplier ?? 1.0;
|
|
487
|
+
const timeoutMs = Math.round(baseTimeoutMs * timeoutMultiplier);
|
|
488
|
+
// Spawn the process
|
|
489
|
+
const env = { ...process.env };
|
|
490
|
+
// Cap Node.js heap per spawned agent to prevent memory exhaustion when
|
|
491
|
+
// multiple agents run vitest concurrently (each vitest fork inherits this).
|
|
492
|
+
const parentNodeOpts = env['NODE_OPTIONS'] ?? '';
|
|
493
|
+
if (!parentNodeOpts.includes('--max-old-space-size')) {
|
|
494
|
+
env['NODE_OPTIONS'] = `${parentNodeOpts} --max-old-space-size=512`.trim();
|
|
495
|
+
}
|
|
496
|
+
if (cmd.env !== undefined) {
|
|
497
|
+
Object.assign(env, cmd.env);
|
|
498
|
+
}
|
|
499
|
+
if (cmd.unsetEnvKeys !== undefined) {
|
|
500
|
+
for (const key of cmd.unsetEnvKeys) {
|
|
501
|
+
delete env[key];
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
const proc = spawn(cmd.binary, cmd.args, {
|
|
505
|
+
cwd: cmd.cwd,
|
|
506
|
+
env,
|
|
507
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
508
|
+
});
|
|
509
|
+
const startedAt = Date.now();
|
|
510
|
+
// Handle spawn errors — if the binary doesn't exist or can't be executed,
|
|
511
|
+
// the 'error' event fires. Without this handler, spawn failures are silent.
|
|
512
|
+
proc.on('error', (err) => {
|
|
513
|
+
this._logger.error({ id, binary: cmd.binary, error: err.message }, 'Process spawn failed');
|
|
514
|
+
});
|
|
515
|
+
// Write prompt to stdin and close — guard against EPIPE if process exits early
|
|
516
|
+
if (proc.stdin !== null) {
|
|
517
|
+
proc.stdin.on('error', (err) => {
|
|
518
|
+
// Suppress EPIPE errors that occur when the process exits before stdin is consumed
|
|
519
|
+
if (err.code !== 'EPIPE') {
|
|
520
|
+
this._logger.warn({ id, error: err.message }, 'stdin write error');
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
try {
|
|
524
|
+
proc.stdin.write(prompt);
|
|
525
|
+
proc.stdin.end();
|
|
526
|
+
}
|
|
527
|
+
catch {
|
|
528
|
+
// Process may have already exited — stdin write failures are non-fatal
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
// Set up output collection
|
|
532
|
+
const stdoutChunks = [];
|
|
533
|
+
const stderrChunks = [];
|
|
534
|
+
if (proc.stdout !== null) {
|
|
535
|
+
proc.stdout.on('data', (chunk) => {
|
|
536
|
+
stdoutChunks.push(chunk);
|
|
537
|
+
const dataStr = chunk.toString('utf-8');
|
|
538
|
+
this._eventBus.emit('agent:output', {
|
|
539
|
+
dispatchId: id,
|
|
540
|
+
data: dataStr,
|
|
541
|
+
});
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
if (proc.stderr !== null) {
|
|
545
|
+
proc.stderr.on('data', (chunk) => {
|
|
546
|
+
stderrChunks.push(chunk);
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
// Create the active dispatch entry
|
|
550
|
+
const activeDispatch = {
|
|
551
|
+
id,
|
|
552
|
+
agent,
|
|
553
|
+
taskType,
|
|
554
|
+
proc,
|
|
555
|
+
startedAt,
|
|
556
|
+
timeoutHandle: null,
|
|
557
|
+
stdoutChunks,
|
|
558
|
+
stderrChunks,
|
|
559
|
+
resolve,
|
|
560
|
+
timedOut: false,
|
|
561
|
+
terminated: false,
|
|
562
|
+
};
|
|
563
|
+
this._running.set(id, activeDispatch);
|
|
564
|
+
// Emit spawned event
|
|
565
|
+
this._eventBus.emit('agent:spawned', {
|
|
566
|
+
dispatchId: id,
|
|
567
|
+
agent,
|
|
568
|
+
taskType,
|
|
569
|
+
});
|
|
570
|
+
this._logger.debug({ id, agent, taskType, timeoutMs }, 'Agent dispatched');
|
|
571
|
+
// Set up timeout
|
|
572
|
+
activeDispatch.timeoutHandle = setTimeout(() => {
|
|
573
|
+
activeDispatch.timedOut = true;
|
|
574
|
+
proc.kill('SIGTERM');
|
|
575
|
+
const durationMs = Date.now() - startedAt;
|
|
576
|
+
const output = Buffer.concat(stdoutChunks).toString('utf-8');
|
|
577
|
+
const inputTokens = Math.ceil(prompt.length / CHARS_PER_TOKEN);
|
|
578
|
+
const outputTokens = Math.ceil(output.length / CHARS_PER_TOKEN);
|
|
579
|
+
this._eventBus.emit('agent:timeout', {
|
|
580
|
+
dispatchId: id,
|
|
581
|
+
timeoutMs,
|
|
582
|
+
});
|
|
583
|
+
this._logger.warn({ id, agent, taskType, timeoutMs }, 'Agent timed out');
|
|
584
|
+
this._running.delete(id);
|
|
585
|
+
this._drainQueue();
|
|
586
|
+
resolve({
|
|
587
|
+
id,
|
|
588
|
+
status: 'timeout',
|
|
589
|
+
exitCode: -1,
|
|
590
|
+
output,
|
|
591
|
+
parsed: null,
|
|
592
|
+
parseError: `Agent timed out after ${String(timeoutMs)}ms`,
|
|
593
|
+
durationMs,
|
|
594
|
+
tokenEstimate: { input: inputTokens, output: outputTokens },
|
|
595
|
+
});
|
|
596
|
+
}, timeoutMs);
|
|
597
|
+
// Set up close handler
|
|
598
|
+
proc.on('close', (exitCode) => {
|
|
599
|
+
const entry = this._running.get(id);
|
|
600
|
+
if (entry === undefined || entry.timedOut) {
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
// If terminated (shutdown in progress), clean up the running slot so
|
|
604
|
+
// shutdown() can detect that all processes have exited.
|
|
605
|
+
if (entry.terminated) {
|
|
606
|
+
this._running.delete(id);
|
|
607
|
+
this._drainQueue();
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
if (entry.timeoutHandle !== null) {
|
|
611
|
+
clearTimeout(entry.timeoutHandle);
|
|
612
|
+
entry.timeoutHandle = null;
|
|
613
|
+
}
|
|
614
|
+
const durationMs = Date.now() - startedAt;
|
|
615
|
+
const stdout = Buffer.concat(stdoutChunks).toString('utf-8');
|
|
616
|
+
const code = exitCode ?? 1;
|
|
617
|
+
const inputTokens = Math.ceil(prompt.length / CHARS_PER_TOKEN);
|
|
618
|
+
this._running.delete(id);
|
|
619
|
+
this._drainQueue();
|
|
620
|
+
if (code === 0) {
|
|
621
|
+
// Parse YAML output via AdapterOutputNormalizer (multi-strategy extraction)
|
|
622
|
+
const normalizeResult = this._normalizer.normalize(stdout, agent);
|
|
623
|
+
let parsed = null;
|
|
624
|
+
let parseError = null;
|
|
625
|
+
if (normalizeResult instanceof AdapterFormatError) {
|
|
626
|
+
// All normalization strategies exhausted — return an adapter format error result
|
|
627
|
+
const errMsg = [
|
|
628
|
+
normalizeResult.adapter_id,
|
|
629
|
+
normalizeResult.tried_strategies.join(','),
|
|
630
|
+
normalizeResult.raw_output_snippet,
|
|
631
|
+
].join(' | ');
|
|
632
|
+
// Estimate output quality for observability (especially non-Claude backends)
|
|
633
|
+
const quality = estimateOutputQuality(stdout);
|
|
634
|
+
this._eventBus.emit('agent:completed', {
|
|
635
|
+
dispatchId: id,
|
|
636
|
+
exitCode: code,
|
|
637
|
+
output: stdout,
|
|
638
|
+
inputTokens,
|
|
639
|
+
outputTokens: Math.ceil(stdout.length / CHARS_PER_TOKEN),
|
|
640
|
+
qualityScore: quality.qualityScore,
|
|
641
|
+
agent,
|
|
642
|
+
...(effectiveModel !== undefined && { model: effectiveModel }),
|
|
643
|
+
});
|
|
644
|
+
this._logger.warn({ id, agent, taskType, adapterError: true, snippet: normalizeResult.raw_output_snippet }, 'Adapter format error — all normalization strategies exhausted');
|
|
645
|
+
resolve({
|
|
646
|
+
id,
|
|
647
|
+
status: 'completed',
|
|
648
|
+
exitCode: code,
|
|
649
|
+
output: stdout,
|
|
650
|
+
parsed: null,
|
|
651
|
+
parseError: errMsg,
|
|
652
|
+
adapterError: true,
|
|
653
|
+
verdict: 'error',
|
|
654
|
+
errorMessage: errMsg,
|
|
655
|
+
durationMs,
|
|
656
|
+
tokenEstimate: { input: inputTokens, output: Math.ceil(stdout.length / CHARS_PER_TOKEN) },
|
|
657
|
+
});
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
// Successful normalization — parse the extracted YAML
|
|
661
|
+
const parseResult = parseYamlResult(normalizeResult.yaml, outputSchema);
|
|
662
|
+
parsed = parseResult.parsed;
|
|
663
|
+
parseError = parseResult.error;
|
|
664
|
+
// Estimate output quality for observability (especially non-Claude backends)
|
|
665
|
+
const quality = estimateOutputQuality(stdout);
|
|
666
|
+
if (quality.hedgingCount > 0 || quality.qualityScore < 40) {
|
|
667
|
+
this._logger.warn({ id, agent, taskType, qualityScore: quality.qualityScore, hedging: quality.hedgingPhrases }, 'Low output quality detected');
|
|
668
|
+
}
|
|
669
|
+
this._eventBus.emit('agent:completed', {
|
|
670
|
+
dispatchId: id,
|
|
671
|
+
exitCode: code,
|
|
672
|
+
output: stdout,
|
|
673
|
+
inputTokens,
|
|
674
|
+
outputTokens: Math.ceil(stdout.length / CHARS_PER_TOKEN),
|
|
675
|
+
qualityScore: quality.qualityScore,
|
|
676
|
+
agent,
|
|
677
|
+
...(effectiveModel !== undefined && { model: effectiveModel }),
|
|
678
|
+
});
|
|
679
|
+
this._logger.debug({ id, agent, taskType, durationMs, qualityScore: quality.qualityScore }, 'Agent completed');
|
|
680
|
+
resolve({
|
|
681
|
+
id,
|
|
682
|
+
status: 'completed',
|
|
683
|
+
exitCode: code,
|
|
684
|
+
output: stdout,
|
|
685
|
+
parsed,
|
|
686
|
+
parseError,
|
|
687
|
+
durationMs,
|
|
688
|
+
tokenEstimate: { input: inputTokens, output: Math.ceil(stdout.length / CHARS_PER_TOKEN) },
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
else {
|
|
692
|
+
const stderr = Buffer.concat(stderrChunks).toString('utf-8');
|
|
693
|
+
this._eventBus.emit('agent:failed', {
|
|
694
|
+
dispatchId: id,
|
|
695
|
+
error: stderr || `Process exited with code ${String(code)}`,
|
|
696
|
+
exitCode: code,
|
|
697
|
+
});
|
|
698
|
+
this._logger.warn({ id, agent, taskType, exitCode: code, durationMs, stderr: stderr.slice(0, 500) }, 'Agent failed');
|
|
699
|
+
// Combine stdout and stderr so callers have full context for failures
|
|
700
|
+
const combinedOutput = stderr ? `${stdout}\n--- stderr ---\n${stderr}` : stdout;
|
|
701
|
+
resolve({
|
|
702
|
+
id,
|
|
703
|
+
status: 'failed',
|
|
704
|
+
exitCode: code,
|
|
705
|
+
output: combinedOutput,
|
|
706
|
+
parsed: null,
|
|
707
|
+
parseError: `Agent exited with code ${String(code)}`,
|
|
708
|
+
durationMs,
|
|
709
|
+
tokenEstimate: { input: inputTokens, output: Math.ceil(combinedOutput.length / CHARS_PER_TOKEN) },
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
// ---------------------------------------------------------------------------
|
|
715
|
+
// Slot reservation
|
|
716
|
+
// ---------------------------------------------------------------------------
|
|
717
|
+
/**
|
|
718
|
+
* Synchronously reserve a slot in the running map with a placeholder entry.
|
|
719
|
+
* This ensures getRunning() reflects the correct count immediately, before
|
|
720
|
+
* the async _startDispatch has a chance to set up the real ActiveDispatch entry.
|
|
721
|
+
*/
|
|
722
|
+
_reserveSlot(id) {
|
|
723
|
+
const placeholder = {
|
|
724
|
+
id,
|
|
725
|
+
agent: '',
|
|
726
|
+
taskType: '',
|
|
727
|
+
proc: null,
|
|
728
|
+
startedAt: Date.now(),
|
|
729
|
+
timeoutHandle: null,
|
|
730
|
+
stdoutChunks: [],
|
|
731
|
+
stderrChunks: [],
|
|
732
|
+
resolve: () => undefined,
|
|
733
|
+
timedOut: false,
|
|
734
|
+
terminated: false,
|
|
735
|
+
};
|
|
736
|
+
this._running.set(id, placeholder);
|
|
737
|
+
}
|
|
738
|
+
// ---------------------------------------------------------------------------
|
|
739
|
+
// Queue management
|
|
740
|
+
// ---------------------------------------------------------------------------
|
|
741
|
+
_drainQueue() {
|
|
742
|
+
if (this._queue.length === 0) {
|
|
743
|
+
this._stopMemoryPressureTimer();
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
if (this._running.size >= this._config.maxConcurrency) {
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
if (this._shuttingDown) {
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
if (this._isMemoryPressured()) {
|
|
753
|
+
this._startMemoryPressureTimer();
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
const next = this._queue.shift();
|
|
757
|
+
if (next === undefined) {
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
next.handle.status = 'running';
|
|
761
|
+
this._logger.debug({ id: next.id, queueLength: this._queue.length }, 'Dequeued dispatch');
|
|
762
|
+
this._startDispatch(next.id, next.request, next.resolve).catch(next.reject);
|
|
763
|
+
}
|
|
764
|
+
_removeFromQueue(id) {
|
|
765
|
+
const idx = this._queue.findIndex((q) => q.id === id);
|
|
766
|
+
if (idx !== -1) {
|
|
767
|
+
this._queue.splice(idx, 1);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
// ---------------------------------------------------------------------------
|
|
771
|
+
// Memory pressure management
|
|
772
|
+
// ---------------------------------------------------------------------------
|
|
773
|
+
_isMemoryPressured() {
|
|
774
|
+
const free = getAvailableMemory(this._logger);
|
|
775
|
+
if (free < MIN_FREE_MEMORY_BYTES) {
|
|
776
|
+
const now = Date.now();
|
|
777
|
+
if (this._memoryPressureHoldStart === null) {
|
|
778
|
+
this._memoryPressureHoldStart = now;
|
|
779
|
+
}
|
|
780
|
+
const holdDurationMs = now - this._memoryPressureHoldStart;
|
|
781
|
+
// If we've been holding for too long, force dispatch with a warning
|
|
782
|
+
if (holdDurationMs >= MEMORY_PRESSURE_MAX_HOLD_MS) {
|
|
783
|
+
this._logger.warn({
|
|
784
|
+
freeMB: Math.round(free / 1024 / 1024),
|
|
785
|
+
thresholdMB: Math.round(MIN_FREE_MEMORY_BYTES / 1024 / 1024),
|
|
786
|
+
pressureLevel: _lastKnownPressureLevel,
|
|
787
|
+
holdDurationMs,
|
|
788
|
+
}, 'Memory pressure hold exceeded max duration — forcing dispatch');
|
|
789
|
+
this._memoryPressureHoldStart = null;
|
|
790
|
+
return false;
|
|
791
|
+
}
|
|
792
|
+
// AC3 (Story 23-8): log freeMB, thresholdMB, and pressureLevel at warn
|
|
793
|
+
this._logger.warn({
|
|
794
|
+
freeMB: Math.round(free / 1024 / 1024),
|
|
795
|
+
thresholdMB: Math.round(MIN_FREE_MEMORY_BYTES / 1024 / 1024),
|
|
796
|
+
pressureLevel: _lastKnownPressureLevel,
|
|
797
|
+
holdDurationMs,
|
|
798
|
+
}, 'Memory pressure detected — holding dispatch queue');
|
|
799
|
+
return true;
|
|
800
|
+
}
|
|
801
|
+
// Memory cleared — reset hold timer
|
|
802
|
+
this._memoryPressureHoldStart = null;
|
|
803
|
+
return false;
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Return current memory pressure state (Story 23-8, AC1).
|
|
807
|
+
*
|
|
808
|
+
* Used by the orchestrator before dispatching a story phase so it can
|
|
809
|
+
* implement backoff-retry without waiting on the dispatcher's internal queue.
|
|
810
|
+
*/
|
|
811
|
+
getMemoryState() {
|
|
812
|
+
const free = getAvailableMemory(this._logger);
|
|
813
|
+
return {
|
|
814
|
+
freeMB: Math.round(free / 1024 / 1024),
|
|
815
|
+
thresholdMB: Math.round(MIN_FREE_MEMORY_BYTES / 1024 / 1024),
|
|
816
|
+
pressureLevel: _lastKnownPressureLevel,
|
|
817
|
+
isPressured: free < MIN_FREE_MEMORY_BYTES,
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
_startMemoryPressureTimer() {
|
|
821
|
+
if (this._memoryPressureTimer !== null)
|
|
822
|
+
return;
|
|
823
|
+
this._memoryPressureTimer = setInterval(() => {
|
|
824
|
+
this._drainQueue();
|
|
825
|
+
}, MEMORY_PRESSURE_POLL_MS);
|
|
826
|
+
this._memoryPressureTimer.unref();
|
|
827
|
+
}
|
|
828
|
+
_stopMemoryPressureTimer() {
|
|
829
|
+
if (this._memoryPressureTimer !== null) {
|
|
830
|
+
clearInterval(this._memoryPressureTimer);
|
|
831
|
+
this._memoryPressureTimer = null;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
// ---------------------------------------------------------------------------
|
|
836
|
+
// createDispatcher factory
|
|
837
|
+
// ---------------------------------------------------------------------------
|
|
838
|
+
/**
|
|
839
|
+
* Create a new DispatcherImpl instance.
|
|
840
|
+
*
|
|
841
|
+
* @param options - Configuration options for the dispatcher
|
|
842
|
+
* @returns A new Dispatcher instance
|
|
843
|
+
*/
|
|
844
|
+
export function createDispatcher(options) {
|
|
845
|
+
return new DispatcherImpl(options.eventBus, options.adapterRegistry, options.config, options.logger ?? console, options.normalizer ?? new AdapterOutputNormalizer());
|
|
846
|
+
}
|
|
847
|
+
//# sourceMappingURL=dispatcher-impl.js.map
|