@methodts/runtime 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__fixtures__/executor-fixtures.d.ts +10 -0
- package/dist/__fixtures__/executor-fixtures.d.ts.map +1 -0
- package/dist/__fixtures__/executor-fixtures.js +36 -0
- package/dist/__fixtures__/executor-fixtures.js.map +1 -0
- package/dist/architecture.test.d.ts +2 -0
- package/dist/architecture.test.d.ts.map +1 -0
- package/dist/architecture.test.js +143 -0
- package/dist/architecture.test.js.map +1 -0
- package/dist/config/cost-governor-config.d.ts +40 -0
- package/dist/config/cost-governor-config.d.ts.map +1 -0
- package/dist/config/cost-governor-config.js +48 -0
- package/dist/config/cost-governor-config.js.map +1 -0
- package/dist/config/index.d.ts +7 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +6 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/sessions-config.d.ts +29 -0
- package/dist/config/sessions-config.d.ts.map +1 -0
- package/dist/config/sessions-config.js +37 -0
- package/dist/config/sessions-config.js.map +1 -0
- package/dist/config/strategies-config.d.ts +38 -0
- package/dist/config/strategies-config.d.ts.map +1 -0
- package/dist/config/strategies-config.js +41 -0
- package/dist/config/strategies-config.js.map +1 -0
- package/dist/cost-governor/backpressure-queue.d.ts +20 -0
- package/dist/cost-governor/backpressure-queue.d.ts.map +1 -0
- package/dist/cost-governor/backpressure-queue.js +78 -0
- package/dist/cost-governor/backpressure-queue.js.map +1 -0
- package/dist/cost-governor/backpressure-queue.test.d.ts +2 -0
- package/dist/cost-governor/backpressure-queue.test.d.ts.map +1 -0
- package/dist/cost-governor/backpressure-queue.test.js +48 -0
- package/dist/cost-governor/backpressure-queue.test.js.map +1 -0
- package/dist/cost-governor/cost-events.d.ts +65 -0
- package/dist/cost-governor/cost-events.d.ts.map +1 -0
- package/dist/cost-governor/cost-events.js +48 -0
- package/dist/cost-governor/cost-events.js.map +1 -0
- package/dist/cost-governor/cost-governor-app-id.test.d.ts +19 -0
- package/dist/cost-governor/cost-governor-app-id.test.d.ts.map +1 -0
- package/dist/cost-governor/cost-governor-app-id.test.js +201 -0
- package/dist/cost-governor/cost-governor-app-id.test.js.map +1 -0
- package/dist/cost-governor/cost-oracle-impl.d.ts +19 -0
- package/dist/cost-governor/cost-oracle-impl.d.ts.map +1 -0
- package/dist/cost-governor/cost-oracle-impl.js +52 -0
- package/dist/cost-governor/cost-oracle-impl.js.map +1 -0
- package/dist/cost-governor/estimator.d.ts +22 -0
- package/dist/cost-governor/estimator.d.ts.map +1 -0
- package/dist/cost-governor/estimator.js +119 -0
- package/dist/cost-governor/estimator.js.map +1 -0
- package/dist/cost-governor/estimator.test.d.ts +2 -0
- package/dist/cost-governor/estimator.test.d.ts.map +1 -0
- package/dist/cost-governor/estimator.test.js +141 -0
- package/dist/cost-governor/estimator.test.js.map +1 -0
- package/dist/cost-governor/index.d.ts +75 -0
- package/dist/cost-governor/index.d.ts.map +1 -0
- package/dist/cost-governor/index.js +120 -0
- package/dist/cost-governor/index.js.map +1 -0
- package/dist/cost-governor/observations-store.d.ts +49 -0
- package/dist/cost-governor/observations-store.d.ts.map +1 -0
- package/dist/cost-governor/observations-store.js +179 -0
- package/dist/cost-governor/observations-store.js.map +1 -0
- package/dist/cost-governor/observations-store.test.d.ts +2 -0
- package/dist/cost-governor/observations-store.test.d.ts.map +1 -0
- package/dist/cost-governor/observations-store.test.js +191 -0
- package/dist/cost-governor/observations-store.test.js.map +1 -0
- package/dist/cost-governor/percentile.d.ts +17 -0
- package/dist/cost-governor/percentile.d.ts.map +1 -0
- package/dist/cost-governor/percentile.js +33 -0
- package/dist/cost-governor/percentile.js.map +1 -0
- package/dist/cost-governor/percentile.test.d.ts +2 -0
- package/dist/cost-governor/percentile.test.d.ts.map +1 -0
- package/dist/cost-governor/percentile.test.js +46 -0
- package/dist/cost-governor/percentile.test.js.map +1 -0
- package/dist/cost-governor/rate-governor-impl.d.ts +73 -0
- package/dist/cost-governor/rate-governor-impl.d.ts.map +1 -0
- package/dist/cost-governor/rate-governor-impl.js +148 -0
- package/dist/cost-governor/rate-governor-impl.js.map +1 -0
- package/dist/cost-governor/signature-builder.d.ts +22 -0
- package/dist/cost-governor/signature-builder.d.ts.map +1 -0
- package/dist/cost-governor/signature-builder.js +43 -0
- package/dist/cost-governor/signature-builder.js.map +1 -0
- package/dist/cost-governor/signature-builder.test.d.ts +2 -0
- package/dist/cost-governor/signature-builder.test.d.ts.map +1 -0
- package/dist/cost-governor/signature-builder.test.js +58 -0
- package/dist/cost-governor/signature-builder.test.js.map +1 -0
- package/dist/cost-governor/token-bucket.d.ts +57 -0
- package/dist/cost-governor/token-bucket.d.ts.map +1 -0
- package/dist/cost-governor/token-bucket.js +109 -0
- package/dist/cost-governor/token-bucket.js.map +1 -0
- package/dist/cost-governor/token-bucket.test.d.ts +2 -0
- package/dist/cost-governor/token-bucket.test.d.ts.map +1 -0
- package/dist/cost-governor/token-bucket.test.js +67 -0
- package/dist/cost-governor/token-bucket.test.js.map +1 -0
- package/dist/dlq/cortex-dlq-observer.d.ts +22 -0
- package/dist/dlq/cortex-dlq-observer.d.ts.map +1 -0
- package/dist/dlq/cortex-dlq-observer.js +29 -0
- package/dist/dlq/cortex-dlq-observer.js.map +1 -0
- package/dist/dlq/dlq-observer.test.d.ts +8 -0
- package/dist/dlq/dlq-observer.test.d.ts.map +1 -0
- package/dist/dlq/dlq-observer.test.js +103 -0
- package/dist/dlq/dlq-observer.test.js.map +1 -0
- package/dist/dlq/index.d.ts +6 -0
- package/dist/dlq/index.d.ts.map +1 -0
- package/dist/dlq/index.js +6 -0
- package/dist/dlq/index.js.map +1 -0
- package/dist/event-bus/adapters.d.ts +50 -0
- package/dist/event-bus/adapters.d.ts.map +1 -0
- package/dist/event-bus/adapters.js +51 -0
- package/dist/event-bus/adapters.js.map +1 -0
- package/dist/event-bus/adapters.test.d.ts +5 -0
- package/dist/event-bus/adapters.test.d.ts.map +1 -0
- package/dist/event-bus/adapters.test.js +73 -0
- package/dist/event-bus/adapters.test.js.map +1 -0
- package/dist/event-bus/agent-event-adapter.d.ts +22 -0
- package/dist/event-bus/agent-event-adapter.d.ts.map +1 -0
- package/dist/event-bus/agent-event-adapter.js +49 -0
- package/dist/event-bus/agent-event-adapter.js.map +1 -0
- package/dist/event-bus/agent-event-adapter.test.d.ts +5 -0
- package/dist/event-bus/agent-event-adapter.test.d.ts.map +1 -0
- package/dist/event-bus/agent-event-adapter.test.js +170 -0
- package/dist/event-bus/agent-event-adapter.test.js.map +1 -0
- package/dist/event-bus/channel-sink.d.ts +71 -0
- package/dist/event-bus/channel-sink.d.ts.map +1 -0
- package/dist/event-bus/channel-sink.js +159 -0
- package/dist/event-bus/channel-sink.js.map +1 -0
- package/dist/event-bus/channel-sink.test.d.ts +5 -0
- package/dist/event-bus/channel-sink.test.d.ts.map +1 -0
- package/dist/event-bus/channel-sink.test.js +234 -0
- package/dist/event-bus/channel-sink.test.js.map +1 -0
- package/dist/event-bus/event-types.snapshot.test.d.ts +27 -0
- package/dist/event-bus/event-types.snapshot.test.d.ts.map +1 -0
- package/dist/event-bus/event-types.snapshot.test.js +165 -0
- package/dist/event-bus/event-types.snapshot.test.js.map +1 -0
- package/dist/event-bus/genesis-sink.d.ts +55 -0
- package/dist/event-bus/genesis-sink.d.ts.map +1 -0
- package/dist/event-bus/genesis-sink.js +141 -0
- package/dist/event-bus/genesis-sink.js.map +1 -0
- package/dist/event-bus/genesis-sink.test.d.ts +5 -0
- package/dist/event-bus/genesis-sink.test.d.ts.map +1 -0
- package/dist/event-bus/genesis-sink.test.js +160 -0
- package/dist/event-bus/genesis-sink.test.js.map +1 -0
- package/dist/event-bus/in-memory-event-bus.d.ts +60 -0
- package/dist/event-bus/in-memory-event-bus.d.ts.map +1 -0
- package/dist/event-bus/in-memory-event-bus.js +274 -0
- package/dist/event-bus/in-memory-event-bus.js.map +1 -0
- package/dist/event-bus/in-memory-event-bus.test.d.ts +5 -0
- package/dist/event-bus/in-memory-event-bus.test.d.ts.map +1 -0
- package/dist/event-bus/in-memory-event-bus.test.js +457 -0
- package/dist/event-bus/in-memory-event-bus.test.js.map +1 -0
- package/dist/event-bus/index.d.ts +22 -0
- package/dist/event-bus/index.d.ts.map +1 -0
- package/dist/event-bus/index.js +17 -0
- package/dist/event-bus/index.js.map +1 -0
- package/dist/event-bus/persistence-sink.d.ts +74 -0
- package/dist/event-bus/persistence-sink.d.ts.map +1 -0
- package/dist/event-bus/persistence-sink.js +193 -0
- package/dist/event-bus/persistence-sink.js.map +1 -0
- package/dist/event-bus/persistence-sink.test.d.ts +6 -0
- package/dist/event-bus/persistence-sink.test.d.ts.map +1 -0
- package/dist/event-bus/persistence-sink.test.js +319 -0
- package/dist/event-bus/persistence-sink.test.js.map +1 -0
- package/dist/event-bus/session-checkpoint-sink.d.ts +91 -0
- package/dist/event-bus/session-checkpoint-sink.d.ts.map +1 -0
- package/dist/event-bus/session-checkpoint-sink.js +107 -0
- package/dist/event-bus/session-checkpoint-sink.js.map +1 -0
- package/dist/event-bus/session-checkpoint-sink.test.d.ts +5 -0
- package/dist/event-bus/session-checkpoint-sink.test.d.ts.map +1 -0
- package/dist/event-bus/session-checkpoint-sink.test.js +215 -0
- package/dist/event-bus/session-checkpoint-sink.test.js.map +1 -0
- package/dist/event-bus/webhook-connector.d.ts +59 -0
- package/dist/event-bus/webhook-connector.d.ts.map +1 -0
- package/dist/event-bus/webhook-connector.js +191 -0
- package/dist/event-bus/webhook-connector.js.map +1 -0
- package/dist/event-bus/webhook-connector.test.d.ts +5 -0
- package/dist/event-bus/webhook-connector.test.d.ts.map +1 -0
- package/dist/event-bus/webhook-connector.test.js +214 -0
- package/dist/event-bus/webhook-connector.test.js.map +1 -0
- package/dist/executors/cortex-job-backed-executor.d.ts +137 -0
- package/dist/executors/cortex-job-backed-executor.d.ts.map +1 -0
- package/dist/executors/cortex-job-backed-executor.js +441 -0
- package/dist/executors/cortex-job-backed-executor.js.map +1 -0
- package/dist/executors/cortex-job-backed-executor.test.d.ts +13 -0
- package/dist/executors/cortex-job-backed-executor.test.d.ts.map +1 -0
- package/dist/executors/cortex-job-backed-executor.test.js +303 -0
- package/dist/executors/cortex-job-backed-executor.test.js.map +1 -0
- package/dist/executors/index.d.ts +9 -0
- package/dist/executors/index.d.ts.map +1 -0
- package/dist/executors/index.js +9 -0
- package/dist/executors/index.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/ports/checkpoint-sink.d.ts +69 -0
- package/dist/ports/checkpoint-sink.d.ts.map +1 -0
- package/dist/ports/checkpoint-sink.js +16 -0
- package/dist/ports/checkpoint-sink.js.map +1 -0
- package/dist/ports/checkpoint.d.ts +68 -0
- package/dist/ports/checkpoint.d.ts.map +1 -0
- package/dist/ports/checkpoint.js +14 -0
- package/dist/ports/checkpoint.js.map +1 -0
- package/dist/ports/continuation-envelope-cross-app.test.d.ts +10 -0
- package/dist/ports/continuation-envelope-cross-app.test.d.ts.map +1 -0
- package/dist/ports/continuation-envelope-cross-app.test.js +95 -0
- package/dist/ports/continuation-envelope-cross-app.test.js.map +1 -0
- package/dist/ports/continuation-envelope.d.ts +199 -0
- package/dist/ports/continuation-envelope.d.ts.map +1 -0
- package/dist/ports/continuation-envelope.js +69 -0
- package/dist/ports/continuation-envelope.js.map +1 -0
- package/dist/ports/conversation.d.ts +64 -0
- package/dist/ports/conversation.d.ts.map +1 -0
- package/dist/ports/conversation.js +19 -0
- package/dist/ports/conversation.js.map +1 -0
- package/dist/ports/cost-oracle.d.ts +26 -0
- package/dist/ports/cost-oracle.d.ts.map +1 -0
- package/dist/ports/cost-oracle.js +9 -0
- package/dist/ports/cost-oracle.js.map +1 -0
- package/dist/ports/cross-app-invoker.d.ts +198 -0
- package/dist/ports/cross-app-invoker.d.ts.map +1 -0
- package/dist/ports/cross-app-invoker.js +157 -0
- package/dist/ports/cross-app-invoker.js.map +1 -0
- package/dist/ports/dlq-observer.d.ts +40 -0
- package/dist/ports/dlq-observer.d.ts.map +1 -0
- package/dist/ports/dlq-observer.js +20 -0
- package/dist/ports/dlq-observer.js.map +1 -0
- package/dist/ports/event-bus.d.ts +169 -0
- package/dist/ports/event-bus.d.ts.map +1 -0
- package/dist/ports/event-bus.js +20 -0
- package/dist/ports/event-bus.js.map +1 -0
- package/dist/ports/event-reader.d.ts +21 -0
- package/dist/ports/event-reader.d.ts.map +1 -0
- package/dist/ports/event-reader.js +12 -0
- package/dist/ports/event-reader.js.map +1 -0
- package/dist/ports/event-rotator.d.ts +39 -0
- package/dist/ports/event-rotator.d.ts.map +1 -0
- package/dist/ports/event-rotator.js +15 -0
- package/dist/ports/event-rotator.js.map +1 -0
- package/dist/ports/file-system.d.ts +48 -0
- package/dist/ports/file-system.d.ts.map +1 -0
- package/dist/ports/file-system.js +10 -0
- package/dist/ports/file-system.js.map +1 -0
- package/dist/ports/historical-observations.d.ts +39 -0
- package/dist/ports/historical-observations.d.ts.map +1 -0
- package/dist/ports/historical-observations.js +12 -0
- package/dist/ports/historical-observations.js.map +1 -0
- package/dist/ports/in-memory-source.d.ts +46 -0
- package/dist/ports/in-memory-source.d.ts.map +1 -0
- package/dist/ports/in-memory-source.js +69 -0
- package/dist/ports/in-memory-source.js.map +1 -0
- package/dist/ports/index.d.ts +32 -0
- package/dist/ports/index.d.ts.map +1 -0
- package/dist/ports/index.js +10 -0
- package/dist/ports/index.js.map +1 -0
- package/dist/ports/job-backed-executor.d.ts +139 -0
- package/dist/ports/job-backed-executor.d.ts.map +1 -0
- package/dist/ports/job-backed-executor.js +56 -0
- package/dist/ports/job-backed-executor.js.map +1 -0
- package/dist/ports/methodology-source.d.ts +95 -0
- package/dist/ports/methodology-source.d.ts.map +1 -0
- package/dist/ports/methodology-source.js +26 -0
- package/dist/ports/methodology-source.js.map +1 -0
- package/dist/ports/native-session-discovery.d.ts +20 -0
- package/dist/ports/native-session-discovery.d.ts.map +1 -0
- package/dist/ports/native-session-discovery.js +13 -0
- package/dist/ports/native-session-discovery.js.map +1 -0
- package/dist/ports/projection-store.d.ts +48 -0
- package/dist/ports/projection-store.d.ts.map +1 -0
- package/dist/ports/projection-store.js +17 -0
- package/dist/ports/projection-store.js.map +1 -0
- package/dist/ports/projection.d.ts +29 -0
- package/dist/ports/projection.d.ts.map +1 -0
- package/dist/ports/projection.js +13 -0
- package/dist/ports/projection.js.map +1 -0
- package/dist/ports/rate-governor.d.ts +17 -0
- package/dist/ports/rate-governor.d.ts.map +1 -0
- package/dist/ports/rate-governor.js +11 -0
- package/dist/ports/rate-governor.js.map +1 -0
- package/dist/ports/schedule-client.d.ts +29 -0
- package/dist/ports/schedule-client.d.ts.map +1 -0
- package/dist/ports/schedule-client.js +2 -0
- package/dist/ports/schedule-client.js.map +1 -0
- package/dist/ports/session-pool.d.ts +162 -0
- package/dist/ports/session-pool.d.ts.map +1 -0
- package/dist/ports/session-pool.js +21 -0
- package/dist/ports/session-pool.js.map +1 -0
- package/dist/ports/session-store-errors.d.ts +22 -0
- package/dist/ports/session-store-errors.d.ts.map +1 -0
- package/dist/ports/session-store-errors.js +29 -0
- package/dist/ports/session-store-errors.js.map +1 -0
- package/dist/ports/session-store-types.d.ts +132 -0
- package/dist/ports/session-store-types.d.ts.map +1 -0
- package/dist/ports/session-store-types.js +14 -0
- package/dist/ports/session-store-types.js.map +1 -0
- package/dist/ports/session-store.d.ts +69 -0
- package/dist/ports/session-store.d.ts.map +1 -0
- package/dist/ports/session-store.js +23 -0
- package/dist/ports/session-store.js.map +1 -0
- package/dist/ports/yaml-loader.d.ts +15 -0
- package/dist/ports/yaml-loader.d.ts.map +1 -0
- package/dist/ports/yaml-loader.js +13 -0
- package/dist/ports/yaml-loader.js.map +1 -0
- package/dist/scheduling/index.d.ts +6 -0
- package/dist/scheduling/index.d.ts.map +1 -0
- package/dist/scheduling/index.js +6 -0
- package/dist/scheduling/index.js.map +1 -0
- package/dist/scheduling/scheduled-pact.d.ts +97 -0
- package/dist/scheduling/scheduled-pact.d.ts.map +1 -0
- package/dist/scheduling/scheduled-pact.js +89 -0
- package/dist/scheduling/scheduled-pact.js.map +1 -0
- package/dist/sessions/__tests__/cognitive-modules.test.d.ts +10 -0
- package/dist/sessions/__tests__/cognitive-modules.test.d.ts.map +1 -0
- package/dist/sessions/__tests__/cognitive-modules.test.js +535 -0
- package/dist/sessions/__tests__/cognitive-modules.test.js.map +1 -0
- package/dist/sessions/__tests__/cognitive-provider.test.d.ts +13 -0
- package/dist/sessions/__tests__/cognitive-provider.test.d.ts.map +1 -0
- package/dist/sessions/__tests__/cognitive-provider.test.js +331 -0
- package/dist/sessions/__tests__/cognitive-provider.test.js.map +1 -0
- package/dist/sessions/__tests__/cognitive-sink.test.d.ts +19 -0
- package/dist/sessions/__tests__/cognitive-sink.test.d.ts.map +1 -0
- package/dist/sessions/__tests__/cognitive-sink.test.js +334 -0
- package/dist/sessions/__tests__/cognitive-sink.test.js.map +1 -0
- package/dist/sessions/__tests__/runtime-tools.test.d.ts +2 -0
- package/dist/sessions/__tests__/runtime-tools.test.d.ts.map +1 -0
- package/dist/sessions/__tests__/runtime-tools.test.js +83 -0
- package/dist/sessions/__tests__/runtime-tools.test.js.map +1 -0
- package/dist/sessions/auto-retro.d.ts +29 -0
- package/dist/sessions/auto-retro.d.ts.map +1 -0
- package/dist/sessions/auto-retro.js +181 -0
- package/dist/sessions/auto-retro.js.map +1 -0
- package/dist/sessions/auto-retro.test.d.ts +2 -0
- package/dist/sessions/auto-retro.test.d.ts.map +1 -0
- package/dist/sessions/auto-retro.test.js +361 -0
- package/dist/sessions/auto-retro.test.js.map +1 -0
- package/dist/sessions/channels.d.ts +55 -0
- package/dist/sessions/channels.d.ts.map +1 -0
- package/dist/sessions/channels.js +118 -0
- package/dist/sessions/channels.js.map +1 -0
- package/dist/sessions/channels.test.d.ts +2 -0
- package/dist/sessions/channels.test.d.ts.map +1 -0
- package/dist/sessions/channels.test.js +285 -0
- package/dist/sessions/channels.test.js.map +1 -0
- package/dist/sessions/cognitive-modules.d.ts +100 -0
- package/dist/sessions/cognitive-modules.d.ts.map +1 -0
- package/dist/sessions/cognitive-modules.js +458 -0
- package/dist/sessions/cognitive-modules.js.map +1 -0
- package/dist/sessions/cognitive-provider.d.ts +42 -0
- package/dist/sessions/cognitive-provider.d.ts.map +1 -0
- package/dist/sessions/cognitive-provider.js +208 -0
- package/dist/sessions/cognitive-provider.js.map +1 -0
- package/dist/sessions/cognitive-sink.d.ts +73 -0
- package/dist/sessions/cognitive-sink.d.ts.map +1 -0
- package/dist/sessions/cognitive-sink.js +154 -0
- package/dist/sessions/cognitive-sink.js.map +1 -0
- package/dist/sessions/diagnostics.d.ts +70 -0
- package/dist/sessions/diagnostics.d.ts.map +1 -0
- package/dist/sessions/diagnostics.js +129 -0
- package/dist/sessions/diagnostics.js.map +1 -0
- package/dist/sessions/diagnostics.test.d.ts +2 -0
- package/dist/sessions/diagnostics.test.d.ts.map +1 -0
- package/dist/sessions/diagnostics.test.js +135 -0
- package/dist/sessions/diagnostics.test.js.map +1 -0
- package/dist/sessions/index.d.ts +32 -0
- package/dist/sessions/index.d.ts.map +1 -0
- package/dist/sessions/index.js +33 -0
- package/dist/sessions/index.js.map +1 -0
- package/dist/sessions/pool.d.ts +218 -0
- package/dist/sessions/pool.d.ts.map +1 -0
- package/dist/sessions/pool.js +991 -0
- package/dist/sessions/pool.js.map +1 -0
- package/dist/sessions/pool.test.d.ts +2 -0
- package/dist/sessions/pool.test.d.ts.map +1 -0
- package/dist/sessions/pool.test.js +633 -0
- package/dist/sessions/pool.test.js.map +1 -0
- package/dist/sessions/print-session.d.ts +142 -0
- package/dist/sessions/print-session.d.ts.map +1 -0
- package/dist/sessions/print-session.js +325 -0
- package/dist/sessions/print-session.js.map +1 -0
- package/dist/sessions/print-session.test.d.ts +2 -0
- package/dist/sessions/print-session.test.d.ts.map +1 -0
- package/dist/sessions/print-session.test.js +418 -0
- package/dist/sessions/print-session.test.js.map +1 -0
- package/dist/sessions/runtime-tools.d.ts +22 -0
- package/dist/sessions/runtime-tools.d.ts.map +1 -0
- package/dist/sessions/runtime-tools.js +162 -0
- package/dist/sessions/runtime-tools.js.map +1 -0
- package/dist/sessions/scope-hook.d.ts +77 -0
- package/dist/sessions/scope-hook.d.ts.map +1 -0
- package/dist/sessions/scope-hook.js +323 -0
- package/dist/sessions/scope-hook.js.map +1 -0
- package/dist/sessions/scope-hook.test.d.ts +2 -0
- package/dist/sessions/scope-hook.test.d.ts.map +1 -0
- package/dist/sessions/scope-hook.test.js +249 -0
- package/dist/sessions/scope-hook.test.js.map +1 -0
- package/dist/sessions/session-store/checkpoint-sink-impl.d.ts +16 -0
- package/dist/sessions/session-store/checkpoint-sink-impl.d.ts.map +1 -0
- package/dist/sessions/session-store/checkpoint-sink-impl.js +191 -0
- package/dist/sessions/session-store/checkpoint-sink-impl.js.map +1 -0
- package/dist/sessions/session-store/checkpoint-sink-impl.test.d.ts +5 -0
- package/dist/sessions/session-store/checkpoint-sink-impl.test.d.ts.map +1 -0
- package/dist/sessions/session-store/checkpoint-sink-impl.test.js +137 -0
- package/dist/sessions/session-store/checkpoint-sink-impl.test.js.map +1 -0
- package/dist/sessions/session-store/conformance.d.ts +59 -0
- package/dist/sessions/session-store/conformance.d.ts.map +1 -0
- package/dist/sessions/session-store/conformance.js +172 -0
- package/dist/sessions/session-store/conformance.js.map +1 -0
- package/dist/sessions/session-store/conformance.test.d.ts +7 -0
- package/dist/sessions/session-store/conformance.test.d.ts.map +1 -0
- package/dist/sessions/session-store/conformance.test.js +22 -0
- package/dist/sessions/session-store/conformance.test.js.map +1 -0
- package/dist/sessions/session-store/in-memory-session-store.d.ts +23 -0
- package/dist/sessions/session-store/in-memory-session-store.d.ts.map +1 -0
- package/dist/sessions/session-store/in-memory-session-store.js +197 -0
- package/dist/sessions/session-store/in-memory-session-store.js.map +1 -0
- package/dist/sessions/session-store/in-memory-session-store.test.d.ts +6 -0
- package/dist/sessions/session-store/in-memory-session-store.test.d.ts.map +1 -0
- package/dist/sessions/session-store/in-memory-session-store.test.js +183 -0
- package/dist/sessions/session-store/in-memory-session-store.test.js.map +1 -0
- package/dist/sessions/session-store/index.d.ts +20 -0
- package/dist/sessions/session-store/index.d.ts.map +1 -0
- package/dist/sessions/session-store/index.js +15 -0
- package/dist/sessions/session-store/index.js.map +1 -0
- package/dist/sessions/session-store/resume.d.ts +88 -0
- package/dist/sessions/session-store/resume.d.ts.map +1 -0
- package/dist/sessions/session-store/resume.js +96 -0
- package/dist/sessions/session-store/resume.js.map +1 -0
- package/dist/sessions/session-store/resume.test.d.ts +5 -0
- package/dist/sessions/session-store/resume.test.d.ts.map +1 -0
- package/dist/sessions/session-store/resume.test.js +119 -0
- package/dist/sessions/session-store/resume.test.js.map +1 -0
- package/dist/sessions/spawn-queue.d.ts +28 -0
- package/dist/sessions/spawn-queue.d.ts.map +1 -0
- package/dist/sessions/spawn-queue.js +63 -0
- package/dist/sessions/spawn-queue.js.map +1 -0
- package/dist/sessions/spawn-queue.test.d.ts +2 -0
- package/dist/sessions/spawn-queue.test.d.ts.map +1 -0
- package/dist/sessions/spawn-queue.test.js +65 -0
- package/dist/sessions/spawn-queue.test.js.map +1 -0
- package/dist/sessions/types.d.ts +16 -0
- package/dist/sessions/types.d.ts.map +1 -0
- package/dist/sessions/types.js +11 -0
- package/dist/sessions/types.js.map +1 -0
- package/dist/sessions/worktree-stale.test.d.ts +2 -0
- package/dist/sessions/worktree-stale.test.d.ts.map +1 -0
- package/dist/sessions/worktree-stale.test.js +468 -0
- package/dist/sessions/worktree-stale.test.js.map +1 -0
- package/dist/strategy/artifact-store.d.ts +12 -0
- package/dist/strategy/artifact-store.d.ts.map +1 -0
- package/dist/strategy/artifact-store.js +12 -0
- package/dist/strategy/artifact-store.js.map +1 -0
- package/dist/strategy/artifact-store.test.d.ts +2 -0
- package/dist/strategy/artifact-store.test.d.ts.map +1 -0
- package/dist/strategy/artifact-store.test.js +170 -0
- package/dist/strategy/artifact-store.test.js.map +1 -0
- package/dist/strategy/context-load-executor.d.ts +23 -0
- package/dist/strategy/context-load-executor.d.ts.map +1 -0
- package/dist/strategy/context-load-executor.js +87 -0
- package/dist/strategy/context-load-executor.js.map +1 -0
- package/dist/strategy/context-load-executor.test.d.ts +16 -0
- package/dist/strategy/context-load-executor.test.d.ts.map +1 -0
- package/dist/strategy/context-load-executor.test.js +158 -0
- package/dist/strategy/context-load-executor.test.js.map +1 -0
- package/dist/strategy/cortex-cross-app-invoker.stub.d.ts +65 -0
- package/dist/strategy/cortex-cross-app-invoker.stub.d.ts.map +1 -0
- package/dist/strategy/cortex-cross-app-invoker.stub.js +68 -0
- package/dist/strategy/cortex-cross-app-invoker.stub.js.map +1 -0
- package/dist/strategy/cross-app-node-executor.d.ts +54 -0
- package/dist/strategy/cross-app-node-executor.d.ts.map +1 -0
- package/dist/strategy/cross-app-node-executor.js +98 -0
- package/dist/strategy/cross-app-node-executor.js.map +1 -0
- package/dist/strategy/cross-app-node-executor.test.d.ts +13 -0
- package/dist/strategy/cross-app-node-executor.test.d.ts.map +1 -0
- package/dist/strategy/cross-app-node-executor.test.js +160 -0
- package/dist/strategy/cross-app-node-executor.test.js.map +1 -0
- package/dist/strategy/gates.d.ts +13 -0
- package/dist/strategy/gates.d.ts.map +1 -0
- package/dist/strategy/gates.js +13 -0
- package/dist/strategy/gates.js.map +1 -0
- package/dist/strategy/gates.test.d.ts +2 -0
- package/dist/strategy/gates.test.d.ts.map +1 -0
- package/dist/strategy/gates.test.js +299 -0
- package/dist/strategy/gates.test.js.map +1 -0
- package/dist/strategy/human-approval-resolver.d.ts +23 -0
- package/dist/strategy/human-approval-resolver.d.ts.map +1 -0
- package/dist/strategy/human-approval-resolver.js +94 -0
- package/dist/strategy/human-approval-resolver.js.map +1 -0
- package/dist/strategy/human-approval-resolver.test.d.ts +16 -0
- package/dist/strategy/human-approval-resolver.test.d.ts.map +1 -0
- package/dist/strategy/human-approval-resolver.test.js +200 -0
- package/dist/strategy/human-approval-resolver.test.js.map +1 -0
- package/dist/strategy/in-process-cross-app-invoker.d.ts +105 -0
- package/dist/strategy/in-process-cross-app-invoker.d.ts.map +1 -0
- package/dist/strategy/in-process-cross-app-invoker.js +206 -0
- package/dist/strategy/in-process-cross-app-invoker.js.map +1 -0
- package/dist/strategy/in-process-cross-app-invoker.test.d.ts +15 -0
- package/dist/strategy/in-process-cross-app-invoker.test.d.ts.map +1 -0
- package/dist/strategy/in-process-cross-app-invoker.test.js +190 -0
- package/dist/strategy/in-process-cross-app-invoker.test.js.map +1 -0
- package/dist/strategy/index.d.ts +29 -0
- package/dist/strategy/index.d.ts.map +1 -0
- package/dist/strategy/index.js +29 -0
- package/dist/strategy/index.js.map +1 -0
- package/dist/strategy/pacta-strategy.d.ts +97 -0
- package/dist/strategy/pacta-strategy.d.ts.map +1 -0
- package/dist/strategy/pacta-strategy.js +117 -0
- package/dist/strategy/pacta-strategy.js.map +1 -0
- package/dist/strategy/pacta-strategy.test.d.ts +2 -0
- package/dist/strategy/pacta-strategy.test.d.ts.map +1 -0
- package/dist/strategy/pacta-strategy.test.js +234 -0
- package/dist/strategy/pacta-strategy.test.js.map +1 -0
- package/dist/strategy/retro-generator.d.ts +18 -0
- package/dist/strategy/retro-generator.d.ts.map +1 -0
- package/dist/strategy/retro-generator.js +22 -0
- package/dist/strategy/retro-generator.js.map +1 -0
- package/dist/strategy/retro-writer.d.ts +25 -0
- package/dist/strategy/retro-writer.d.ts.map +1 -0
- package/dist/strategy/retro-writer.js +65 -0
- package/dist/strategy/retro-writer.js.map +1 -0
- package/dist/strategy/strategy-executor.d.ts +39 -0
- package/dist/strategy/strategy-executor.d.ts.map +1 -0
- package/dist/strategy/strategy-executor.js +253 -0
- package/dist/strategy/strategy-executor.js.map +1 -0
- package/dist/strategy/strategy-executor.test.d.ts +8 -0
- package/dist/strategy/strategy-executor.test.d.ts.map +1 -0
- package/dist/strategy/strategy-executor.test.js +1301 -0
- package/dist/strategy/strategy-executor.test.js.map +1 -0
- package/dist/strategy/strategy-parser.d.ts +30 -0
- package/dist/strategy/strategy-parser.d.ts.map +1 -0
- package/dist/strategy/strategy-parser.js +30 -0
- package/dist/strategy/strategy-parser.js.map +1 -0
- package/dist/strategy/sub-strategy-source.d.ts +27 -0
- package/dist/strategy/sub-strategy-source.d.ts.map +1 -0
- package/dist/strategy/sub-strategy-source.js +77 -0
- package/dist/strategy/sub-strategy-source.js.map +1 -0
- package/dist/strategy/types.d.ts +12 -0
- package/dist/strategy/types.d.ts.map +1 -0
- package/dist/strategy/types.js +9 -0
- package/dist/strategy/types.js.map +1 -0
- package/package.json +87 -0
|
@@ -0,0 +1,1301 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* PRD 017: Strategy Pipelines — DAG Executor Tests (Phase 1c)
|
|
4
|
+
*
|
|
5
|
+
* Tests for Strategy YAML parsing, DAG validation, topological sort,
|
|
6
|
+
* and end-to-end Strategy execution with mock LLM provider.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it } from 'node:test';
|
|
9
|
+
import assert from 'node:assert/strict';
|
|
10
|
+
import { parseStrategyYaml, parseStrategyObject, validateStrategyDAG, topologicalSort, } from './strategy-parser.js';
|
|
11
|
+
import { StrategyExecutor } from './strategy-executor.js';
|
|
12
|
+
import { setStrategyParserYaml } from './strategy-parser.js';
|
|
13
|
+
import { setRetroGeneratorYaml } from './retro-generator.js';
|
|
14
|
+
import yaml from 'js-yaml';
|
|
15
|
+
// PRD 024 / PRD-057 C2: Configure YAML port for tests with an inline
|
|
16
|
+
// js-yaml-backed loader. The runtime stays transport-free — the bridge's
|
|
17
|
+
// JsYamlLoader implementation lives in @methodts/bridge.
|
|
18
|
+
const testYaml = {
|
|
19
|
+
load: (text) => yaml.load(text),
|
|
20
|
+
dump: (obj) => yaml.dump(obj),
|
|
21
|
+
};
|
|
22
|
+
setStrategyParserYaml(testYaml);
|
|
23
|
+
setRetroGeneratorYaml(testYaml);
|
|
24
|
+
// ── Test Fixtures ───────────────────────────────────────────────
|
|
25
|
+
const TEST_STRATEGY_YAML = `
|
|
26
|
+
strategy:
|
|
27
|
+
id: S-TEST-3NODE
|
|
28
|
+
name: "Test 3-Node Strategy"
|
|
29
|
+
version: "1.0"
|
|
30
|
+
context:
|
|
31
|
+
inputs:
|
|
32
|
+
- { name: task_description, type: string }
|
|
33
|
+
capabilities:
|
|
34
|
+
read_only: [Read, Glob, Grep]
|
|
35
|
+
implementation: [Read, Write, Edit, Bash, Glob, Grep]
|
|
36
|
+
dag:
|
|
37
|
+
nodes:
|
|
38
|
+
- id: analyze
|
|
39
|
+
type: methodology
|
|
40
|
+
methodology: P2-SD
|
|
41
|
+
method_hint: M7-PRDS
|
|
42
|
+
capabilities: [read_only]
|
|
43
|
+
inputs: [task_description]
|
|
44
|
+
outputs: [analysis]
|
|
45
|
+
gates:
|
|
46
|
+
- type: algorithmic
|
|
47
|
+
check: "output.analysis !== undefined"
|
|
48
|
+
- id: implement
|
|
49
|
+
type: methodology
|
|
50
|
+
methodology: P2-SD
|
|
51
|
+
method_hint: M1-IMPL
|
|
52
|
+
capabilities: [implementation]
|
|
53
|
+
inputs: [analysis]
|
|
54
|
+
outputs: [code_changes]
|
|
55
|
+
depends_on: [analyze]
|
|
56
|
+
gates:
|
|
57
|
+
- type: algorithmic
|
|
58
|
+
check: "output.tests_passed === true"
|
|
59
|
+
max_retries: 2
|
|
60
|
+
- id: summarize
|
|
61
|
+
type: script
|
|
62
|
+
script: "return { summary: 'Completed: ' + JSON.stringify(inputs.code_changes) };"
|
|
63
|
+
inputs: [code_changes]
|
|
64
|
+
outputs: [summary]
|
|
65
|
+
depends_on: [implement]
|
|
66
|
+
oversight:
|
|
67
|
+
rules:
|
|
68
|
+
- { condition: "total_cost_usd > 5.00", action: warn_human }
|
|
69
|
+
`;
|
|
70
|
+
// ── Mock Agent Provider ───────────────────────────────────────
|
|
71
|
+
function makeMockResult(overrides = {}) {
|
|
72
|
+
return {
|
|
73
|
+
output: '```json\n{"status": "ok"}\n```',
|
|
74
|
+
sessionId: 'mock-session',
|
|
75
|
+
completed: true,
|
|
76
|
+
stopReason: 'complete',
|
|
77
|
+
usage: {
|
|
78
|
+
inputTokens: 100,
|
|
79
|
+
outputTokens: 50,
|
|
80
|
+
cacheReadTokens: 0,
|
|
81
|
+
cacheWriteTokens: 0,
|
|
82
|
+
totalTokens: 150,
|
|
83
|
+
},
|
|
84
|
+
cost: { totalUsd: 0.05, perModel: {} },
|
|
85
|
+
durationMs: 100,
|
|
86
|
+
turns: 1,
|
|
87
|
+
...overrides,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
class MockAgentProvider {
|
|
91
|
+
name = 'mock';
|
|
92
|
+
responses = new Map();
|
|
93
|
+
invocations = [];
|
|
94
|
+
setResponse(promptContains, result) {
|
|
95
|
+
this.responses.set(promptContains, result);
|
|
96
|
+
}
|
|
97
|
+
capabilities() {
|
|
98
|
+
return {
|
|
99
|
+
modes: ['oneshot'],
|
|
100
|
+
streaming: false,
|
|
101
|
+
resumable: false,
|
|
102
|
+
budgetEnforcement: 'none',
|
|
103
|
+
outputValidation: 'client',
|
|
104
|
+
toolModel: 'builtin',
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
async invoke(_pact, request) {
|
|
108
|
+
this.invocations.push(request);
|
|
109
|
+
// Find matching result by checking if prompt contains any key
|
|
110
|
+
for (const [key, result] of this.responses) {
|
|
111
|
+
if (request.prompt.includes(key))
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
// Default result
|
|
115
|
+
return makeMockResult();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function makeExecutorConfig(overrides = {}) {
|
|
119
|
+
return {
|
|
120
|
+
maxParallel: 3,
|
|
121
|
+
defaultGateRetries: 3,
|
|
122
|
+
defaultTimeoutMs: 600000,
|
|
123
|
+
retroDir: '.method/retros',
|
|
124
|
+
...overrides,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
// ── Helper: build a StrategyYaml object programmatically ────────
|
|
128
|
+
function makeStrategyYaml(overrides = {}) {
|
|
129
|
+
return {
|
|
130
|
+
strategy: {
|
|
131
|
+
id: 'S-TEST',
|
|
132
|
+
name: 'Test Strategy',
|
|
133
|
+
version: '1.0',
|
|
134
|
+
capabilities: {
|
|
135
|
+
read_only: ['Read', 'Glob'],
|
|
136
|
+
impl: ['Read', 'Write', 'Edit'],
|
|
137
|
+
},
|
|
138
|
+
dag: {
|
|
139
|
+
nodes: [
|
|
140
|
+
{
|
|
141
|
+
id: 'node-a',
|
|
142
|
+
type: 'methodology',
|
|
143
|
+
methodology: 'P2-SD',
|
|
144
|
+
method_hint: 'M1-IMPL',
|
|
145
|
+
capabilities: ['read_only'],
|
|
146
|
+
inputs: ['task'],
|
|
147
|
+
outputs: ['result_a'],
|
|
148
|
+
gates: [
|
|
149
|
+
{
|
|
150
|
+
type: 'algorithmic',
|
|
151
|
+
check: 'output.result_a !== undefined',
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
},
|
|
157
|
+
...overrides,
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
// ── Parser Tests ───────────────────────────────────────────────
|
|
162
|
+
describe('parseStrategyYaml', () => {
|
|
163
|
+
it('parses a valid Strategy YAML string into StrategyDAG', () => {
|
|
164
|
+
const dag = parseStrategyYaml(TEST_STRATEGY_YAML);
|
|
165
|
+
assert.equal(dag.id, 'S-TEST-3NODE');
|
|
166
|
+
assert.equal(dag.name, 'Test 3-Node Strategy');
|
|
167
|
+
assert.equal(dag.version, '1.0');
|
|
168
|
+
assert.equal(dag.nodes.length, 3);
|
|
169
|
+
assert.equal(dag.context_inputs.length, 1);
|
|
170
|
+
assert.equal(dag.context_inputs[0].name, 'task_description');
|
|
171
|
+
});
|
|
172
|
+
it('parses nodes with correct types and dependencies', () => {
|
|
173
|
+
const dag = parseStrategyYaml(TEST_STRATEGY_YAML);
|
|
174
|
+
const analyze = dag.nodes.find((n) => n.id === 'analyze');
|
|
175
|
+
assert.equal(analyze.type, 'methodology');
|
|
176
|
+
assert.deepEqual(analyze.depends_on, []);
|
|
177
|
+
assert.deepEqual(analyze.inputs, ['task_description']);
|
|
178
|
+
assert.deepEqual(analyze.outputs, ['analysis']);
|
|
179
|
+
assert.equal(analyze.config.type, 'methodology');
|
|
180
|
+
if (analyze.config.type === 'methodology') {
|
|
181
|
+
assert.equal(analyze.config.methodology, 'P2-SD');
|
|
182
|
+
assert.equal(analyze.config.method_hint, 'M7-PRDS');
|
|
183
|
+
assert.deepEqual(analyze.config.capabilities, ['read_only']);
|
|
184
|
+
}
|
|
185
|
+
const implement = dag.nodes.find((n) => n.id === 'implement');
|
|
186
|
+
assert.deepEqual(implement.depends_on, ['analyze']);
|
|
187
|
+
assert.deepEqual(implement.inputs, ['analysis']);
|
|
188
|
+
assert.deepEqual(implement.outputs, ['code_changes']);
|
|
189
|
+
const summarize = dag.nodes.find((n) => n.id === 'summarize');
|
|
190
|
+
assert.equal(summarize.type, 'script');
|
|
191
|
+
assert.deepEqual(summarize.depends_on, ['implement']);
|
|
192
|
+
assert.equal(summarize.config.type, 'script');
|
|
193
|
+
if (summarize.config.type === 'script') {
|
|
194
|
+
assert.ok(summarize.config.script.includes('JSON.stringify'));
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
it('applies default retries and timeout to gates', () => {
|
|
198
|
+
const dag = parseStrategyYaml(TEST_STRATEGY_YAML);
|
|
199
|
+
// analyze gate: no explicit retries/timeout → defaults
|
|
200
|
+
const analyzeGate = dag.nodes.find((n) => n.id === 'analyze').gates[0];
|
|
201
|
+
assert.equal(analyzeGate.max_retries, 3); // default for algorithmic
|
|
202
|
+
assert.equal(analyzeGate.timeout_ms, 5000); // default timeout
|
|
203
|
+
// implement gate: explicit max_retries=2
|
|
204
|
+
const implementGate = dag.nodes.find((n) => n.id === 'implement').gates[0];
|
|
205
|
+
assert.equal(implementGate.max_retries, 2);
|
|
206
|
+
assert.equal(implementGate.timeout_ms, 5000);
|
|
207
|
+
});
|
|
208
|
+
it('parses capabilities and oversight rules', () => {
|
|
209
|
+
const dag = parseStrategyYaml(TEST_STRATEGY_YAML);
|
|
210
|
+
assert.deepEqual(dag.capabilities['read_only'], ['Read', 'Glob', 'Grep']);
|
|
211
|
+
assert.deepEqual(dag.capabilities['implementation'], ['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep']);
|
|
212
|
+
assert.equal(dag.oversight_rules.length, 1);
|
|
213
|
+
assert.equal(dag.oversight_rules[0].condition, 'total_cost_usd > 5.00');
|
|
214
|
+
assert.equal(dag.oversight_rules[0].action, 'warn_human');
|
|
215
|
+
});
|
|
216
|
+
it('parses a Strategy with script nodes', () => {
|
|
217
|
+
const yamlStr = `
|
|
218
|
+
strategy:
|
|
219
|
+
id: S-SCRIPT
|
|
220
|
+
name: "Script Strategy"
|
|
221
|
+
version: "1.0"
|
|
222
|
+
dag:
|
|
223
|
+
nodes:
|
|
224
|
+
- id: compute
|
|
225
|
+
type: script
|
|
226
|
+
script: "return { total: inputs.a + inputs.b };"
|
|
227
|
+
inputs: [a, b]
|
|
228
|
+
outputs: [total]
|
|
229
|
+
`;
|
|
230
|
+
const dag = parseStrategyYaml(yamlStr);
|
|
231
|
+
assert.equal(dag.nodes.length, 1);
|
|
232
|
+
assert.equal(dag.nodes[0].type, 'script');
|
|
233
|
+
assert.equal(dag.nodes[0].config.type, 'script');
|
|
234
|
+
if (dag.nodes[0].config.type === 'script') {
|
|
235
|
+
assert.ok(dag.nodes[0].config.script.includes('inputs.a + inputs.b'));
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
it('parses strategy_gates', () => {
|
|
239
|
+
const yamlStr = `
|
|
240
|
+
strategy:
|
|
241
|
+
id: S-SGATES
|
|
242
|
+
name: "Strategy Gates Test"
|
|
243
|
+
version: "1.0"
|
|
244
|
+
dag:
|
|
245
|
+
nodes:
|
|
246
|
+
- id: work
|
|
247
|
+
type: methodology
|
|
248
|
+
methodology: P2-SD
|
|
249
|
+
outputs: [result]
|
|
250
|
+
strategy_gates:
|
|
251
|
+
- id: final_check
|
|
252
|
+
depends_on: [work]
|
|
253
|
+
type: algorithmic
|
|
254
|
+
check: "artifacts.result !== undefined"
|
|
255
|
+
`;
|
|
256
|
+
const dag = parseStrategyYaml(yamlStr);
|
|
257
|
+
assert.equal(dag.strategy_gates.length, 1);
|
|
258
|
+
assert.equal(dag.strategy_gates[0].id, 'final_check');
|
|
259
|
+
assert.deepEqual(dag.strategy_gates[0].depends_on, ['work']);
|
|
260
|
+
assert.equal(dag.strategy_gates[0].gate.type, 'algorithmic');
|
|
261
|
+
assert.equal(dag.strategy_gates[0].gate.check, 'artifacts.result !== undefined');
|
|
262
|
+
assert.equal(dag.strategy_gates[0].gate.max_retries, 0); // forced to 0: strategy gates are single-shot
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
describe('parseStrategyObject', () => {
|
|
266
|
+
it('transforms a pre-parsed YAML object into StrategyDAG', () => {
|
|
267
|
+
const obj = makeStrategyYaml();
|
|
268
|
+
const dag = parseStrategyObject(obj);
|
|
269
|
+
assert.equal(dag.id, 'S-TEST');
|
|
270
|
+
assert.equal(dag.name, 'Test Strategy');
|
|
271
|
+
assert.equal(dag.nodes.length, 1);
|
|
272
|
+
assert.deepEqual(dag.capabilities['read_only'], ['Read', 'Glob']);
|
|
273
|
+
});
|
|
274
|
+
it('handles empty optional fields gracefully', () => {
|
|
275
|
+
const obj = {
|
|
276
|
+
strategy: {
|
|
277
|
+
id: 'S-MINIMAL',
|
|
278
|
+
name: 'Minimal',
|
|
279
|
+
version: '0.1',
|
|
280
|
+
dag: {
|
|
281
|
+
nodes: [
|
|
282
|
+
{ id: 'a', type: 'script', script: 'return {};' },
|
|
283
|
+
],
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
const dag = parseStrategyObject(obj);
|
|
288
|
+
assert.equal(dag.nodes.length, 1);
|
|
289
|
+
assert.deepEqual(dag.strategy_gates, []);
|
|
290
|
+
assert.deepEqual(dag.capabilities, {});
|
|
291
|
+
assert.deepEqual(dag.oversight_rules, []);
|
|
292
|
+
assert.deepEqual(dag.context_inputs, []);
|
|
293
|
+
assert.deepEqual(dag.nodes[0].depends_on, []);
|
|
294
|
+
assert.deepEqual(dag.nodes[0].inputs, []);
|
|
295
|
+
assert.deepEqual(dag.nodes[0].outputs, []);
|
|
296
|
+
assert.deepEqual(dag.nodes[0].gates, []);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
// ── Validation Tests ───────────────────────────────────────────
|
|
300
|
+
describe('validateStrategyDAG', () => {
|
|
301
|
+
it('valid DAG passes validation', () => {
|
|
302
|
+
const dag = parseStrategyYaml(TEST_STRATEGY_YAML);
|
|
303
|
+
const result = validateStrategyDAG(dag);
|
|
304
|
+
assert.equal(result.valid, true);
|
|
305
|
+
assert.deepEqual(result.errors, []);
|
|
306
|
+
});
|
|
307
|
+
it('detects cyclic dependency (A -> B -> A)', () => {
|
|
308
|
+
const obj = {
|
|
309
|
+
strategy: {
|
|
310
|
+
id: 'S-CYCLE',
|
|
311
|
+
name: 'Cycle',
|
|
312
|
+
version: '1.0',
|
|
313
|
+
dag: {
|
|
314
|
+
nodes: [
|
|
315
|
+
{ id: 'a', type: 'script', script: 'return {};', depends_on: ['b'] },
|
|
316
|
+
{ id: 'b', type: 'script', script: 'return {};', depends_on: ['a'] },
|
|
317
|
+
],
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
const dag = parseStrategyObject(obj);
|
|
322
|
+
const result = validateStrategyDAG(dag);
|
|
323
|
+
assert.equal(result.valid, false);
|
|
324
|
+
assert.ok(result.errors.some((e) => e.toLowerCase().includes('cyclic')));
|
|
325
|
+
});
|
|
326
|
+
it('detects missing depends_on reference', () => {
|
|
327
|
+
const obj = {
|
|
328
|
+
strategy: {
|
|
329
|
+
id: 'S-BAD-REF',
|
|
330
|
+
name: 'Bad Ref',
|
|
331
|
+
version: '1.0',
|
|
332
|
+
dag: {
|
|
333
|
+
nodes: [
|
|
334
|
+
{ id: 'a', type: 'script', script: 'return {};', depends_on: ['nonexistent'] },
|
|
335
|
+
],
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
};
|
|
339
|
+
const dag = parseStrategyObject(obj);
|
|
340
|
+
const result = validateStrategyDAG(dag);
|
|
341
|
+
assert.equal(result.valid, false);
|
|
342
|
+
assert.ok(result.errors.some((e) => e.includes('unknown node "nonexistent"')));
|
|
343
|
+
});
|
|
344
|
+
it('detects duplicate node IDs', () => {
|
|
345
|
+
const obj = {
|
|
346
|
+
strategy: {
|
|
347
|
+
id: 'S-DUP',
|
|
348
|
+
name: 'Dup',
|
|
349
|
+
version: '1.0',
|
|
350
|
+
dag: {
|
|
351
|
+
nodes: [
|
|
352
|
+
{ id: 'a', type: 'script', script: 'return {};' },
|
|
353
|
+
{ id: 'a', type: 'script', script: 'return {};' },
|
|
354
|
+
],
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
const dag = parseStrategyObject(obj);
|
|
359
|
+
const result = validateStrategyDAG(dag);
|
|
360
|
+
assert.equal(result.valid, false);
|
|
361
|
+
assert.ok(result.errors.some((e) => e.includes('Duplicate node ID')));
|
|
362
|
+
});
|
|
363
|
+
it('detects invalid capability reference', () => {
|
|
364
|
+
const obj = {
|
|
365
|
+
strategy: {
|
|
366
|
+
id: 'S-BAD-CAP',
|
|
367
|
+
name: 'Bad Cap',
|
|
368
|
+
version: '1.0',
|
|
369
|
+
capabilities: {
|
|
370
|
+
read_only: ['Read'],
|
|
371
|
+
},
|
|
372
|
+
dag: {
|
|
373
|
+
nodes: [
|
|
374
|
+
{
|
|
375
|
+
id: 'a',
|
|
376
|
+
type: 'methodology',
|
|
377
|
+
methodology: 'P2-SD',
|
|
378
|
+
capabilities: ['nonexistent_cap'],
|
|
379
|
+
},
|
|
380
|
+
],
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
};
|
|
384
|
+
const dag = parseStrategyObject(obj);
|
|
385
|
+
const result = validateStrategyDAG(dag);
|
|
386
|
+
assert.equal(result.valid, false);
|
|
387
|
+
assert.ok(result.errors.some((e) => e.includes('undefined capability set "nonexistent_cap"')));
|
|
388
|
+
});
|
|
389
|
+
it('detects gate expression syntax error', () => {
|
|
390
|
+
const obj = {
|
|
391
|
+
strategy: {
|
|
392
|
+
id: 'S-BAD-GATE',
|
|
393
|
+
name: 'Bad Gate',
|
|
394
|
+
version: '1.0',
|
|
395
|
+
dag: {
|
|
396
|
+
nodes: [
|
|
397
|
+
{
|
|
398
|
+
id: 'a',
|
|
399
|
+
type: 'methodology',
|
|
400
|
+
methodology: 'P2-SD',
|
|
401
|
+
gates: [
|
|
402
|
+
{ type: 'algorithmic', check: 'output.result ===' }, // syntax error
|
|
403
|
+
],
|
|
404
|
+
},
|
|
405
|
+
],
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
};
|
|
409
|
+
const dag = parseStrategyObject(obj);
|
|
410
|
+
const result = validateStrategyDAG(dag);
|
|
411
|
+
assert.equal(result.valid, false);
|
|
412
|
+
assert.ok(result.errors.some((e) => e.includes('invalid check expression')));
|
|
413
|
+
});
|
|
414
|
+
it('returns multiple errors at once', () => {
|
|
415
|
+
const obj = {
|
|
416
|
+
strategy: {
|
|
417
|
+
id: 'S-MULTI-ERR',
|
|
418
|
+
name: 'Multi Err',
|
|
419
|
+
version: '1.0',
|
|
420
|
+
capabilities: {},
|
|
421
|
+
dag: {
|
|
422
|
+
nodes: [
|
|
423
|
+
{
|
|
424
|
+
id: 'a',
|
|
425
|
+
type: 'methodology',
|
|
426
|
+
methodology: 'P2-SD',
|
|
427
|
+
capabilities: ['nonexistent'],
|
|
428
|
+
depends_on: ['ghost'],
|
|
429
|
+
gates: [
|
|
430
|
+
{ type: 'algorithmic', check: '!!!' }, // syntax error
|
|
431
|
+
],
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
id: 'a', // duplicate ID
|
|
435
|
+
type: 'script',
|
|
436
|
+
script: 'return {};',
|
|
437
|
+
},
|
|
438
|
+
],
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
};
|
|
442
|
+
const dag = parseStrategyObject(obj);
|
|
443
|
+
const result = validateStrategyDAG(dag);
|
|
444
|
+
assert.equal(result.valid, false);
|
|
445
|
+
// Should have at least 3 errors: duplicate ID, missing depends_on, bad capability
|
|
446
|
+
assert.ok(result.errors.length >= 3, `Expected >= 3 errors, got ${result.errors.length}: ${result.errors.join('; ')}`);
|
|
447
|
+
});
|
|
448
|
+
it('detects invalid strategy_gates depends_on reference', () => {
|
|
449
|
+
const obj = {
|
|
450
|
+
strategy: {
|
|
451
|
+
id: 'S-BAD-SG',
|
|
452
|
+
name: 'Bad Strategy Gate',
|
|
453
|
+
version: '1.0',
|
|
454
|
+
dag: {
|
|
455
|
+
nodes: [
|
|
456
|
+
{ id: 'a', type: 'script', script: 'return {};' },
|
|
457
|
+
],
|
|
458
|
+
strategy_gates: [
|
|
459
|
+
{ id: 'sg1', depends_on: ['nonexistent'], type: 'algorithmic', check: 'true' },
|
|
460
|
+
],
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
};
|
|
464
|
+
const dag = parseStrategyObject(obj);
|
|
465
|
+
const result = validateStrategyDAG(dag);
|
|
466
|
+
assert.equal(result.valid, false);
|
|
467
|
+
assert.ok(result.errors.some((e) => e.includes('Strategy gate "sg1"') && e.includes('unknown node')));
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
// ── Topological Sort Tests ──────────────────────────────────────
|
|
471
|
+
describe('topologicalSort', () => {
|
|
472
|
+
it('linear chain: A -> B -> C produces [[A], [B], [C]]', () => {
|
|
473
|
+
const obj = {
|
|
474
|
+
strategy: {
|
|
475
|
+
id: 'S-LINEAR',
|
|
476
|
+
name: 'Linear',
|
|
477
|
+
version: '1.0',
|
|
478
|
+
dag: {
|
|
479
|
+
nodes: [
|
|
480
|
+
{ id: 'A', type: 'script', script: 'return {};' },
|
|
481
|
+
{ id: 'B', type: 'script', script: 'return {};', depends_on: ['A'] },
|
|
482
|
+
{ id: 'C', type: 'script', script: 'return {};', depends_on: ['B'] },
|
|
483
|
+
],
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
};
|
|
487
|
+
const dag = parseStrategyObject(obj);
|
|
488
|
+
const levels = topologicalSort(dag);
|
|
489
|
+
assert.equal(levels.length, 3);
|
|
490
|
+
assert.deepEqual(levels[0], ['A']);
|
|
491
|
+
assert.deepEqual(levels[1], ['B']);
|
|
492
|
+
assert.deepEqual(levels[2], ['C']);
|
|
493
|
+
});
|
|
494
|
+
it('diamond: A -> B, A -> C, B -> D, C -> D produces [[A], [B, C], [D]]', () => {
|
|
495
|
+
const obj = {
|
|
496
|
+
strategy: {
|
|
497
|
+
id: 'S-DIAMOND',
|
|
498
|
+
name: 'Diamond',
|
|
499
|
+
version: '1.0',
|
|
500
|
+
dag: {
|
|
501
|
+
nodes: [
|
|
502
|
+
{ id: 'A', type: 'script', script: 'return {};' },
|
|
503
|
+
{ id: 'B', type: 'script', script: 'return {};', depends_on: ['A'] },
|
|
504
|
+
{ id: 'C', type: 'script', script: 'return {};', depends_on: ['A'] },
|
|
505
|
+
{ id: 'D', type: 'script', script: 'return {};', depends_on: ['B', 'C'] },
|
|
506
|
+
],
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
};
|
|
510
|
+
const dag = parseStrategyObject(obj);
|
|
511
|
+
const levels = topologicalSort(dag);
|
|
512
|
+
assert.equal(levels.length, 3);
|
|
513
|
+
assert.deepEqual(levels[0], ['A']);
|
|
514
|
+
// B and C should be at the same level (order may vary)
|
|
515
|
+
assert.equal(levels[1].length, 2);
|
|
516
|
+
assert.ok(levels[1].includes('B'));
|
|
517
|
+
assert.ok(levels[1].includes('C'));
|
|
518
|
+
assert.deepEqual(levels[2], ['D']);
|
|
519
|
+
});
|
|
520
|
+
it('independent nodes at same level', () => {
|
|
521
|
+
const obj = {
|
|
522
|
+
strategy: {
|
|
523
|
+
id: 'S-INDEP',
|
|
524
|
+
name: 'Independent',
|
|
525
|
+
version: '1.0',
|
|
526
|
+
dag: {
|
|
527
|
+
nodes: [
|
|
528
|
+
{ id: 'X', type: 'script', script: 'return {};' },
|
|
529
|
+
{ id: 'Y', type: 'script', script: 'return {};' },
|
|
530
|
+
{ id: 'Z', type: 'script', script: 'return {};' },
|
|
531
|
+
],
|
|
532
|
+
},
|
|
533
|
+
},
|
|
534
|
+
};
|
|
535
|
+
const dag = parseStrategyObject(obj);
|
|
536
|
+
const levels = topologicalSort(dag);
|
|
537
|
+
assert.equal(levels.length, 1);
|
|
538
|
+
assert.equal(levels[0].length, 3);
|
|
539
|
+
assert.ok(levels[0].includes('X'));
|
|
540
|
+
assert.ok(levels[0].includes('Y'));
|
|
541
|
+
assert.ok(levels[0].includes('Z'));
|
|
542
|
+
});
|
|
543
|
+
it('single node produces [[node]]', () => {
|
|
544
|
+
const obj = {
|
|
545
|
+
strategy: {
|
|
546
|
+
id: 'S-SINGLE',
|
|
547
|
+
name: 'Single',
|
|
548
|
+
version: '1.0',
|
|
549
|
+
dag: {
|
|
550
|
+
nodes: [
|
|
551
|
+
{ id: 'only', type: 'script', script: 'return {};' },
|
|
552
|
+
],
|
|
553
|
+
},
|
|
554
|
+
},
|
|
555
|
+
};
|
|
556
|
+
const dag = parseStrategyObject(obj);
|
|
557
|
+
const levels = topologicalSort(dag);
|
|
558
|
+
assert.equal(levels.length, 1);
|
|
559
|
+
assert.deepEqual(levels[0], ['only']);
|
|
560
|
+
});
|
|
561
|
+
it('throws on cyclic DAG', () => {
|
|
562
|
+
const dag = {
|
|
563
|
+
id: 'S-CYCLE',
|
|
564
|
+
name: 'Cycle',
|
|
565
|
+
version: '1.0',
|
|
566
|
+
nodes: [
|
|
567
|
+
{
|
|
568
|
+
id: 'a',
|
|
569
|
+
type: 'script',
|
|
570
|
+
depends_on: ['b'],
|
|
571
|
+
inputs: [],
|
|
572
|
+
outputs: [],
|
|
573
|
+
gates: [],
|
|
574
|
+
config: { type: 'script', script: 'return {};' },
|
|
575
|
+
},
|
|
576
|
+
{
|
|
577
|
+
id: 'b',
|
|
578
|
+
type: 'script',
|
|
579
|
+
depends_on: ['a'],
|
|
580
|
+
inputs: [],
|
|
581
|
+
outputs: [],
|
|
582
|
+
gates: [],
|
|
583
|
+
config: { type: 'script', script: 'return {};' },
|
|
584
|
+
},
|
|
585
|
+
],
|
|
586
|
+
strategy_gates: [],
|
|
587
|
+
capabilities: {},
|
|
588
|
+
oversight_rules: [],
|
|
589
|
+
context_inputs: [],
|
|
590
|
+
};
|
|
591
|
+
assert.throws(() => topologicalSort(dag), /cycle/i);
|
|
592
|
+
});
|
|
593
|
+
});
|
|
594
|
+
// ── Executor Integration Tests ──────────────────────────────────
|
|
595
|
+
describe('StrategyExecutor', () => {
|
|
596
|
+
it('3-node Strategy end-to-end: methodology -> methodology -> script', async () => {
|
|
597
|
+
const provider = new MockAgentProvider();
|
|
598
|
+
// Set up mock results for each node
|
|
599
|
+
provider.setResponse('node "analyze"', makeMockResult({
|
|
600
|
+
output: '```json\n{"analysis": "code needs refactoring"}\n```',
|
|
601
|
+
cost: { totalUsd: 0.10, perModel: {} },
|
|
602
|
+
turns: 3,
|
|
603
|
+
durationMs: 2000,
|
|
604
|
+
}));
|
|
605
|
+
provider.setResponse('node "implement"', makeMockResult({
|
|
606
|
+
output: '```json\n{"tests_passed": true, "code_changes": {"files": ["a.ts"]}}\n```',
|
|
607
|
+
cost: { totalUsd: 0.25, perModel: {} },
|
|
608
|
+
turns: 5,
|
|
609
|
+
durationMs: 5000,
|
|
610
|
+
}));
|
|
611
|
+
const config = makeExecutorConfig();
|
|
612
|
+
const executor = new StrategyExecutor(provider, config);
|
|
613
|
+
const dag = parseStrategyYaml(TEST_STRATEGY_YAML);
|
|
614
|
+
const result = await executor.execute(dag, { task_description: 'refactor module X' });
|
|
615
|
+
assert.equal(result.strategy_id, 'S-TEST-3NODE');
|
|
616
|
+
assert.equal(result.status, 'completed');
|
|
617
|
+
// All 3 nodes completed
|
|
618
|
+
assert.equal(Object.keys(result.node_results).length, 3);
|
|
619
|
+
assert.equal(result.node_results['analyze'].status, 'completed');
|
|
620
|
+
assert.equal(result.node_results['implement'].status, 'completed');
|
|
621
|
+
assert.equal(result.node_results['summarize'].status, 'completed');
|
|
622
|
+
// Cost tracked
|
|
623
|
+
assert.ok(result.cost_usd > 0, 'cost should be tracked');
|
|
624
|
+
// Artifacts flow correctly
|
|
625
|
+
assert.ok(result.artifacts['analysis'], 'analysis artifact should exist');
|
|
626
|
+
assert.ok(result.artifacts['code_changes'], 'code_changes artifact should exist');
|
|
627
|
+
assert.ok(result.artifacts['summary'], 'summary artifact should exist');
|
|
628
|
+
// Script node output: the 'summary' output key maps to the string value
|
|
629
|
+
// from { summary: 'Completed: ...' } returned by the script
|
|
630
|
+
const summaryContent = result.artifacts['summary'].content;
|
|
631
|
+
assert.ok(typeof summaryContent === 'string' &&
|
|
632
|
+
summaryContent.includes('Completed:'), 'summary artifact should contain the script output string');
|
|
633
|
+
});
|
|
634
|
+
it('parallel execution: two independent nodes', async () => {
|
|
635
|
+
// Create a provider with built-in delay
|
|
636
|
+
class DelayProvider {
|
|
637
|
+
name = 'delay';
|
|
638
|
+
invocationOrder = [];
|
|
639
|
+
capabilities() {
|
|
640
|
+
return { modes: ['oneshot'], streaming: false, resumable: false, budgetEnforcement: 'none', outputValidation: 'client', toolModel: 'builtin' };
|
|
641
|
+
}
|
|
642
|
+
async invoke(_pact, request) {
|
|
643
|
+
// Extract node name from prompt
|
|
644
|
+
const match = request.prompt.match(/node "(\w+)"/);
|
|
645
|
+
const nodeName = match ? match[1] : 'unknown';
|
|
646
|
+
this.invocationOrder.push(nodeName);
|
|
647
|
+
// Simulate 50ms work
|
|
648
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
649
|
+
return makeMockResult({
|
|
650
|
+
output: `\`\`\`json\n{"output_${nodeName}": true}\n\`\`\``,
|
|
651
|
+
cost: { totalUsd: 0.05, perModel: {} },
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
const delayProvider = new DelayProvider();
|
|
656
|
+
const config = makeExecutorConfig({ maxParallel: 5 });
|
|
657
|
+
const executor = new StrategyExecutor(delayProvider, config);
|
|
658
|
+
// Two independent nodes + one dependent
|
|
659
|
+
const yamlStr = `
|
|
660
|
+
strategy:
|
|
661
|
+
id: S-PARALLEL
|
|
662
|
+
name: "Parallel Test"
|
|
663
|
+
version: "1.0"
|
|
664
|
+
dag:
|
|
665
|
+
nodes:
|
|
666
|
+
- id: alpha
|
|
667
|
+
type: methodology
|
|
668
|
+
methodology: P2-SD
|
|
669
|
+
outputs: [out_alpha]
|
|
670
|
+
- id: beta
|
|
671
|
+
type: methodology
|
|
672
|
+
methodology: P2-SD
|
|
673
|
+
outputs: [out_beta]
|
|
674
|
+
- id: merge
|
|
675
|
+
type: script
|
|
676
|
+
script: "return { merged: true };"
|
|
677
|
+
inputs: [out_alpha, out_beta]
|
|
678
|
+
outputs: [merged]
|
|
679
|
+
depends_on: [alpha, beta]
|
|
680
|
+
`;
|
|
681
|
+
const dag = parseStrategyYaml(yamlStr);
|
|
682
|
+
const startTime = Date.now();
|
|
683
|
+
const result = await executor.execute(dag, {});
|
|
684
|
+
const wallTime = Date.now() - startTime;
|
|
685
|
+
assert.equal(result.status, 'completed');
|
|
686
|
+
assert.equal(Object.keys(result.node_results).length, 3);
|
|
687
|
+
// Both alpha and beta should have been invoked (order may vary since they're parallel)
|
|
688
|
+
assert.ok(delayProvider.invocationOrder.includes('alpha'), 'alpha should have been invoked');
|
|
689
|
+
assert.ok(delayProvider.invocationOrder.includes('beta'), 'beta should have been invoked');
|
|
690
|
+
// Wall time should be less than 50ms * 3 (sequential would be ~150ms+)
|
|
691
|
+
// With parallel alpha+beta, it should be ~100ms+ (50ms for alpha|beta parallel + 0ms for script)
|
|
692
|
+
// Using a generous threshold to avoid flaky tests
|
|
693
|
+
assert.ok(wallTime < 300, `Wall time ${wallTime}ms should be less than sequential (300ms threshold)`);
|
|
694
|
+
});
|
|
695
|
+
it('gate failure with retry: first attempt fails, second succeeds', async () => {
|
|
696
|
+
let callCount = 0;
|
|
697
|
+
class RetryProvider {
|
|
698
|
+
name = 'retry';
|
|
699
|
+
capabilities() {
|
|
700
|
+
return { modes: ['oneshot'], streaming: false, resumable: false, budgetEnforcement: 'none', outputValidation: 'client', toolModel: 'builtin' };
|
|
701
|
+
}
|
|
702
|
+
async invoke(_pact, _request) {
|
|
703
|
+
callCount++;
|
|
704
|
+
if (callCount === 1) {
|
|
705
|
+
// First attempt: gate will fail (tests_passed is false)
|
|
706
|
+
return makeMockResult({
|
|
707
|
+
output: '```json\n{"tests_passed": false, "analysis": "needs work"}\n```',
|
|
708
|
+
cost: { totalUsd: 0.05, perModel: {} },
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
// Second attempt: gate will pass
|
|
712
|
+
return makeMockResult({
|
|
713
|
+
output: '```json\n{"tests_passed": true, "analysis": "looks good"}\n```',
|
|
714
|
+
cost: { totalUsd: 0.05, perModel: {} },
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
const config = makeExecutorConfig();
|
|
719
|
+
const executor = new StrategyExecutor(new RetryProvider(), config);
|
|
720
|
+
const yamlStr = `
|
|
721
|
+
strategy:
|
|
722
|
+
id: S-RETRY
|
|
723
|
+
name: "Retry Test"
|
|
724
|
+
version: "1.0"
|
|
725
|
+
dag:
|
|
726
|
+
nodes:
|
|
727
|
+
- id: work
|
|
728
|
+
type: methodology
|
|
729
|
+
methodology: P2-SD
|
|
730
|
+
outputs: [result]
|
|
731
|
+
gates:
|
|
732
|
+
- type: algorithmic
|
|
733
|
+
check: "output.tests_passed === true"
|
|
734
|
+
max_retries: 3
|
|
735
|
+
`;
|
|
736
|
+
const dag = parseStrategyYaml(yamlStr);
|
|
737
|
+
const result = await executor.execute(dag, {});
|
|
738
|
+
assert.equal(result.status, 'completed');
|
|
739
|
+
assert.equal(result.node_results['work'].status, 'completed');
|
|
740
|
+
assert.equal(result.node_results['work'].retries, 1);
|
|
741
|
+
assert.equal(callCount, 2, 'Provider should have been called twice (1 initial + 1 retry)');
|
|
742
|
+
});
|
|
743
|
+
it('gate failure exhausts retries: node status is gate_failed', async () => {
|
|
744
|
+
class AlwaysFailProvider {
|
|
745
|
+
name = 'always-fail';
|
|
746
|
+
capabilities() {
|
|
747
|
+
return { modes: ['oneshot'], streaming: false, resumable: false, budgetEnforcement: 'none', outputValidation: 'client', toolModel: 'builtin' };
|
|
748
|
+
}
|
|
749
|
+
async invoke() {
|
|
750
|
+
return makeMockResult({
|
|
751
|
+
output: '```json\n{"tests_passed": false}\n```',
|
|
752
|
+
cost: { totalUsd: 0.02, perModel: {} },
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
const config = makeExecutorConfig();
|
|
757
|
+
const executor = new StrategyExecutor(new AlwaysFailProvider(), config);
|
|
758
|
+
const yamlStr = `
|
|
759
|
+
strategy:
|
|
760
|
+
id: S-EXHAUST
|
|
761
|
+
name: "Exhaust Retries"
|
|
762
|
+
version: "1.0"
|
|
763
|
+
dag:
|
|
764
|
+
nodes:
|
|
765
|
+
- id: flaky
|
|
766
|
+
type: methodology
|
|
767
|
+
methodology: P2-SD
|
|
768
|
+
outputs: [result]
|
|
769
|
+
gates:
|
|
770
|
+
- type: algorithmic
|
|
771
|
+
check: "output.tests_passed === true"
|
|
772
|
+
max_retries: 2
|
|
773
|
+
`;
|
|
774
|
+
const dag = parseStrategyYaml(yamlStr);
|
|
775
|
+
const result = await executor.execute(dag, {});
|
|
776
|
+
assert.equal(result.status, 'failed');
|
|
777
|
+
assert.equal(result.node_results['flaky'].status, 'gate_failed');
|
|
778
|
+
assert.equal(result.node_results['flaky'].retries, 2);
|
|
779
|
+
});
|
|
780
|
+
it('script node executes correctly: inputs flow in, output stored', async () => {
|
|
781
|
+
const config = makeExecutorConfig();
|
|
782
|
+
const executor = new StrategyExecutor(new MockAgentProvider(), config);
|
|
783
|
+
const yamlStr = `
|
|
784
|
+
strategy:
|
|
785
|
+
id: S-SCRIPT-EXEC
|
|
786
|
+
name: "Script Execution"
|
|
787
|
+
version: "1.0"
|
|
788
|
+
context:
|
|
789
|
+
inputs:
|
|
790
|
+
- { name: count, type: number }
|
|
791
|
+
dag:
|
|
792
|
+
nodes:
|
|
793
|
+
- id: compute
|
|
794
|
+
type: script
|
|
795
|
+
script: "return { doubled: inputs.count * 2, label: 'result' };"
|
|
796
|
+
inputs: [count]
|
|
797
|
+
outputs: [doubled, label]
|
|
798
|
+
`;
|
|
799
|
+
const dag = parseStrategyYaml(yamlStr);
|
|
800
|
+
const result = await executor.execute(dag, { count: 21 });
|
|
801
|
+
assert.equal(result.status, 'completed');
|
|
802
|
+
assert.equal(result.node_results['compute'].status, 'completed');
|
|
803
|
+
// The output is stored as artifacts
|
|
804
|
+
const doubledArtifact = result.artifacts['doubled'];
|
|
805
|
+
assert.ok(doubledArtifact, 'doubled artifact should exist');
|
|
806
|
+
assert.equal(doubledArtifact.content, 42);
|
|
807
|
+
const labelArtifact = result.artifacts['label'];
|
|
808
|
+
assert.ok(labelArtifact, 'label artifact should exist');
|
|
809
|
+
assert.equal(labelArtifact.content, 'result');
|
|
810
|
+
});
|
|
811
|
+
it('artifact dependency filtering: node receives only declared inputs', async () => {
|
|
812
|
+
// Track what the second node receives
|
|
813
|
+
let secondNodePrompt = '';
|
|
814
|
+
class TrackingProvider {
|
|
815
|
+
name = 'tracking';
|
|
816
|
+
capabilities() {
|
|
817
|
+
return { modes: ['oneshot'], streaming: false, resumable: false, budgetEnforcement: 'none', outputValidation: 'client', toolModel: 'builtin' };
|
|
818
|
+
}
|
|
819
|
+
async invoke(_pact, request) {
|
|
820
|
+
if (request.prompt.includes('node "second"')) {
|
|
821
|
+
secondNodePrompt = request.prompt;
|
|
822
|
+
}
|
|
823
|
+
return makeMockResult({
|
|
824
|
+
output: '```json\n{"data": "from_node"}\n```',
|
|
825
|
+
cost: { totalUsd: 0.01, perModel: {} },
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
const config = makeExecutorConfig();
|
|
830
|
+
const executor = new StrategyExecutor(new TrackingProvider(), config);
|
|
831
|
+
const yamlStr = `
|
|
832
|
+
strategy:
|
|
833
|
+
id: S-FILTER
|
|
834
|
+
name: "Filter Test"
|
|
835
|
+
version: "1.0"
|
|
836
|
+
context:
|
|
837
|
+
inputs:
|
|
838
|
+
- { name: secret, type: string }
|
|
839
|
+
- { name: public_data, type: string }
|
|
840
|
+
dag:
|
|
841
|
+
nodes:
|
|
842
|
+
- id: first
|
|
843
|
+
type: methodology
|
|
844
|
+
methodology: P2-SD
|
|
845
|
+
inputs: [secret]
|
|
846
|
+
outputs: [processed]
|
|
847
|
+
- id: second
|
|
848
|
+
type: methodology
|
|
849
|
+
methodology: P2-SD
|
|
850
|
+
inputs: [public_data]
|
|
851
|
+
outputs: [result]
|
|
852
|
+
depends_on: [first]
|
|
853
|
+
`;
|
|
854
|
+
const dag = parseStrategyYaml(yamlStr);
|
|
855
|
+
const result = await executor.execute(dag, {
|
|
856
|
+
secret: 'top-secret-value',
|
|
857
|
+
public_data: 'public-info',
|
|
858
|
+
});
|
|
859
|
+
assert.equal(result.status, 'completed');
|
|
860
|
+
// The second node's prompt should contain public_data but NOT secret
|
|
861
|
+
assert.ok(secondNodePrompt.includes('public_data'), 'should receive public_data');
|
|
862
|
+
assert.ok(secondNodePrompt.includes('public-info'), 'should receive public_data value');
|
|
863
|
+
assert.ok(!secondNodePrompt.includes('top-secret-value'), 'should NOT receive secret value');
|
|
864
|
+
});
|
|
865
|
+
it('oversight rule triggers on high cost', async () => {
|
|
866
|
+
class ExpensiveProvider {
|
|
867
|
+
name = 'expensive';
|
|
868
|
+
capabilities() {
|
|
869
|
+
return { modes: ['oneshot'], streaming: false, resumable: false, budgetEnforcement: 'none', outputValidation: 'client', toolModel: 'builtin' };
|
|
870
|
+
}
|
|
871
|
+
async invoke() {
|
|
872
|
+
return makeMockResult({
|
|
873
|
+
output: '```json\n{"done": true}\n```',
|
|
874
|
+
cost: { totalUsd: 3.00, perModel: {} }, // Expensive!
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
const config = makeExecutorConfig();
|
|
879
|
+
const executor = new StrategyExecutor(new ExpensiveProvider(), config);
|
|
880
|
+
const yamlStr = `
|
|
881
|
+
strategy:
|
|
882
|
+
id: S-COSTLY
|
|
883
|
+
name: "Costly Strategy"
|
|
884
|
+
version: "1.0"
|
|
885
|
+
dag:
|
|
886
|
+
nodes:
|
|
887
|
+
- id: expensive_a
|
|
888
|
+
type: methodology
|
|
889
|
+
methodology: P2-SD
|
|
890
|
+
outputs: [out_a]
|
|
891
|
+
- id: expensive_b
|
|
892
|
+
type: methodology
|
|
893
|
+
methodology: P2-SD
|
|
894
|
+
outputs: [out_b]
|
|
895
|
+
- id: final
|
|
896
|
+
type: script
|
|
897
|
+
script: "return { done: true };"
|
|
898
|
+
depends_on: [expensive_a, expensive_b]
|
|
899
|
+
outputs: [result]
|
|
900
|
+
oversight:
|
|
901
|
+
rules:
|
|
902
|
+
- { condition: "total_cost_usd > 5.00", action: warn_human }
|
|
903
|
+
`;
|
|
904
|
+
const dag = parseStrategyYaml(yamlStr);
|
|
905
|
+
const result = await executor.execute(dag, {});
|
|
906
|
+
// Both expensive nodes cost $3 each = $6 total > $5 threshold
|
|
907
|
+
assert.equal(result.status, 'completed'); // warn_human doesn't suspend
|
|
908
|
+
assert.ok(result.oversight_events.length > 0, 'should have triggered oversight event');
|
|
909
|
+
assert.equal(result.oversight_events[0].rule.action, 'warn_human');
|
|
910
|
+
assert.equal(result.oversight_events[0].rule.condition, 'total_cost_usd > 5.00');
|
|
911
|
+
});
|
|
912
|
+
it('context inputs stored as initial artifacts', async () => {
|
|
913
|
+
const config = makeExecutorConfig();
|
|
914
|
+
const executor = new StrategyExecutor(new MockAgentProvider(), config);
|
|
915
|
+
const yamlStr = `
|
|
916
|
+
strategy:
|
|
917
|
+
id: S-CTX
|
|
918
|
+
name: "Context Test"
|
|
919
|
+
version: "1.0"
|
|
920
|
+
context:
|
|
921
|
+
inputs:
|
|
922
|
+
- { name: project_name, type: string }
|
|
923
|
+
- { name: target_version, type: string }
|
|
924
|
+
dag:
|
|
925
|
+
nodes:
|
|
926
|
+
- id: use_ctx
|
|
927
|
+
type: script
|
|
928
|
+
script: "return { msg: inputs.project_name + ' v' + inputs.target_version };"
|
|
929
|
+
inputs: [project_name, target_version]
|
|
930
|
+
outputs: [msg]
|
|
931
|
+
`;
|
|
932
|
+
const dag = parseStrategyYaml(yamlStr);
|
|
933
|
+
const result = await executor.execute(dag, {
|
|
934
|
+
project_name: 'method',
|
|
935
|
+
target_version: '2.0',
|
|
936
|
+
});
|
|
937
|
+
assert.equal(result.status, 'completed');
|
|
938
|
+
// Context inputs should be in artifacts
|
|
939
|
+
assert.ok(result.artifacts['project_name'], 'project_name should be in artifacts');
|
|
940
|
+
assert.equal(result.artifacts['project_name'].content, 'method');
|
|
941
|
+
assert.equal(result.artifacts['project_name'].producer_node_id, '__context__');
|
|
942
|
+
// Script output should have used the context inputs
|
|
943
|
+
const msg = result.artifacts['msg'];
|
|
944
|
+
assert.ok(msg, 'msg artifact should exist');
|
|
945
|
+
assert.equal(msg.content, 'method v2.0');
|
|
946
|
+
});
|
|
947
|
+
it('escalate_to_human oversight rule suspends execution', async () => {
|
|
948
|
+
class FailingProvider {
|
|
949
|
+
name = 'failing';
|
|
950
|
+
callCount = 0;
|
|
951
|
+
capabilities() {
|
|
952
|
+
return { modes: ['oneshot'], streaming: false, resumable: false, budgetEnforcement: 'none', outputValidation: 'client', toolModel: 'builtin' };
|
|
953
|
+
}
|
|
954
|
+
async invoke() {
|
|
955
|
+
this.callCount++;
|
|
956
|
+
return makeMockResult({
|
|
957
|
+
output: '```json\n{"fail": true}\n```',
|
|
958
|
+
cost: { totalUsd: 0.01, perModel: {} },
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
const config = makeExecutorConfig();
|
|
963
|
+
const executor = new StrategyExecutor(new FailingProvider(), config);
|
|
964
|
+
const yamlStr = `
|
|
965
|
+
strategy:
|
|
966
|
+
id: S-ESCALATE
|
|
967
|
+
name: "Escalate Test"
|
|
968
|
+
version: "1.0"
|
|
969
|
+
dag:
|
|
970
|
+
nodes:
|
|
971
|
+
- id: retry_node
|
|
972
|
+
type: methodology
|
|
973
|
+
methodology: P2-SD
|
|
974
|
+
outputs: [result]
|
|
975
|
+
gates:
|
|
976
|
+
- type: algorithmic
|
|
977
|
+
check: "output.success === true"
|
|
978
|
+
max_retries: 5
|
|
979
|
+
- id: after_node
|
|
980
|
+
type: script
|
|
981
|
+
script: "return { final: true };"
|
|
982
|
+
depends_on: [retry_node]
|
|
983
|
+
outputs: [final]
|
|
984
|
+
oversight:
|
|
985
|
+
rules:
|
|
986
|
+
- { condition: "gate_failures >= 3 on same step", action: escalate_to_human }
|
|
987
|
+
`;
|
|
988
|
+
const dag = parseStrategyYaml(yamlStr);
|
|
989
|
+
const result = await executor.execute(dag, {});
|
|
990
|
+
assert.equal(result.status, 'suspended');
|
|
991
|
+
assert.ok(result.oversight_events.length > 0);
|
|
992
|
+
assert.equal(result.oversight_events[0].rule.action, 'escalate_to_human');
|
|
993
|
+
// The after_node should NOT have executed
|
|
994
|
+
assert.ok(!result.node_results['after_node'] ||
|
|
995
|
+
result.node_results['after_node'].status === 'pending', 'after_node should not have executed');
|
|
996
|
+
});
|
|
997
|
+
it('getState() returns null before execution', () => {
|
|
998
|
+
const config = makeExecutorConfig();
|
|
999
|
+
const executor = new StrategyExecutor(new MockAgentProvider(), config);
|
|
1000
|
+
assert.equal(executor.getState(), null);
|
|
1001
|
+
});
|
|
1002
|
+
it('getState() returns state during/after execution', async () => {
|
|
1003
|
+
const config = makeExecutorConfig();
|
|
1004
|
+
const executor = new StrategyExecutor(new MockAgentProvider(), config);
|
|
1005
|
+
const yamlStr = `
|
|
1006
|
+
strategy:
|
|
1007
|
+
id: S-STATE
|
|
1008
|
+
name: "State Test"
|
|
1009
|
+
version: "1.0"
|
|
1010
|
+
dag:
|
|
1011
|
+
nodes:
|
|
1012
|
+
- id: simple
|
|
1013
|
+
type: script
|
|
1014
|
+
script: "return { done: true };"
|
|
1015
|
+
outputs: [result]
|
|
1016
|
+
`;
|
|
1017
|
+
const dag = parseStrategyYaml(yamlStr);
|
|
1018
|
+
await executor.execute(dag, {});
|
|
1019
|
+
const state = executor.getState();
|
|
1020
|
+
assert.ok(state, 'state should exist after execution');
|
|
1021
|
+
assert.equal(state.strategy_id, 'S-STATE');
|
|
1022
|
+
assert.ok(state.started_at);
|
|
1023
|
+
assert.ok(state.completed_at);
|
|
1024
|
+
assert.ok(state.levels.length > 0);
|
|
1025
|
+
});
|
|
1026
|
+
it('invalid DAG throws during execution', async () => {
|
|
1027
|
+
const config = makeExecutorConfig();
|
|
1028
|
+
const executor = new StrategyExecutor(new MockAgentProvider(), config);
|
|
1029
|
+
const dag = {
|
|
1030
|
+
id: 'S-INVALID',
|
|
1031
|
+
name: 'Invalid',
|
|
1032
|
+
version: '1.0',
|
|
1033
|
+
nodes: [
|
|
1034
|
+
{
|
|
1035
|
+
id: 'a',
|
|
1036
|
+
type: 'script',
|
|
1037
|
+
depends_on: ['nonexistent'],
|
|
1038
|
+
inputs: [],
|
|
1039
|
+
outputs: [],
|
|
1040
|
+
gates: [],
|
|
1041
|
+
config: { type: 'script', script: 'return {};' },
|
|
1042
|
+
},
|
|
1043
|
+
],
|
|
1044
|
+
strategy_gates: [],
|
|
1045
|
+
capabilities: {},
|
|
1046
|
+
oversight_rules: [],
|
|
1047
|
+
context_inputs: [],
|
|
1048
|
+
};
|
|
1049
|
+
await assert.rejects(() => executor.execute(dag, {}), /Invalid Strategy DAG/);
|
|
1050
|
+
});
|
|
1051
|
+
it('strategy gate failure causes failed status', async () => {
|
|
1052
|
+
const config = makeExecutorConfig();
|
|
1053
|
+
const executor = new StrategyExecutor(new MockAgentProvider(), config);
|
|
1054
|
+
const yamlStr = `
|
|
1055
|
+
strategy:
|
|
1056
|
+
id: S-SG-FAIL
|
|
1057
|
+
name: "Strategy Gate Fail"
|
|
1058
|
+
version: "1.0"
|
|
1059
|
+
dag:
|
|
1060
|
+
nodes:
|
|
1061
|
+
- id: work
|
|
1062
|
+
type: script
|
|
1063
|
+
script: "return { count: 5 };"
|
|
1064
|
+
outputs: [count]
|
|
1065
|
+
strategy_gates:
|
|
1066
|
+
- id: minimum_count
|
|
1067
|
+
depends_on: [work]
|
|
1068
|
+
type: algorithmic
|
|
1069
|
+
check: "artifacts.count > 100"
|
|
1070
|
+
`;
|
|
1071
|
+
const dag = parseStrategyYaml(yamlStr);
|
|
1072
|
+
const result = await executor.execute(dag, {});
|
|
1073
|
+
// Node completed but strategy gate failed
|
|
1074
|
+
assert.equal(result.node_results['work'].status, 'completed');
|
|
1075
|
+
assert.equal(result.status, 'failed');
|
|
1076
|
+
assert.ok(result.gate_results.some((gr) => gr.gate_id === 'strategy:minimum_count' && !gr.passed), 'strategy gate should have failed');
|
|
1077
|
+
});
|
|
1078
|
+
it('methodology node with empty methodology field fails validation', () => {
|
|
1079
|
+
const obj = {
|
|
1080
|
+
strategy: {
|
|
1081
|
+
id: 'S-EMPTY-METH',
|
|
1082
|
+
name: 'Empty Methodology',
|
|
1083
|
+
version: '1.0',
|
|
1084
|
+
dag: {
|
|
1085
|
+
nodes: [
|
|
1086
|
+
{
|
|
1087
|
+
id: 'bad_node',
|
|
1088
|
+
type: 'methodology',
|
|
1089
|
+
methodology: '',
|
|
1090
|
+
outputs: ['result'],
|
|
1091
|
+
},
|
|
1092
|
+
],
|
|
1093
|
+
},
|
|
1094
|
+
},
|
|
1095
|
+
};
|
|
1096
|
+
const dag = parseStrategyObject(obj);
|
|
1097
|
+
const validation = validateStrategyDAG(dag);
|
|
1098
|
+
assert.equal(validation.valid, false);
|
|
1099
|
+
assert.ok(validation.errors.some((e) => e.includes('non-empty "methodology" field')), `Expected methodology validation error, got: ${validation.errors.join('; ')}`);
|
|
1100
|
+
});
|
|
1101
|
+
it('methodology node with missing methodology field fails validation', () => {
|
|
1102
|
+
const obj = {
|
|
1103
|
+
strategy: {
|
|
1104
|
+
id: 'S-MISSING-METH',
|
|
1105
|
+
name: 'Missing Methodology',
|
|
1106
|
+
version: '1.0',
|
|
1107
|
+
dag: {
|
|
1108
|
+
nodes: [
|
|
1109
|
+
{
|
|
1110
|
+
id: 'bad_node',
|
|
1111
|
+
type: 'methodology',
|
|
1112
|
+
// methodology field omitted — parser defaults to ''
|
|
1113
|
+
outputs: ['result'],
|
|
1114
|
+
},
|
|
1115
|
+
],
|
|
1116
|
+
},
|
|
1117
|
+
},
|
|
1118
|
+
};
|
|
1119
|
+
const dag = parseStrategyObject(obj);
|
|
1120
|
+
const validation = validateStrategyDAG(dag);
|
|
1121
|
+
assert.equal(validation.valid, false);
|
|
1122
|
+
assert.ok(validation.errors.some((e) => e.includes('non-empty "methodology" field')), `Expected methodology validation error, got: ${validation.errors.join('; ')}`);
|
|
1123
|
+
});
|
|
1124
|
+
it('LLM response without JSON code block is handled gracefully', async () => {
|
|
1125
|
+
class PlainTextProvider {
|
|
1126
|
+
name = 'plain-text';
|
|
1127
|
+
capabilities() {
|
|
1128
|
+
return { modes: ['oneshot'], streaming: false, resumable: false, budgetEnforcement: 'none', outputValidation: 'client', toolModel: 'builtin' };
|
|
1129
|
+
}
|
|
1130
|
+
async invoke() {
|
|
1131
|
+
return makeMockResult({
|
|
1132
|
+
output: 'I completed the task successfully. Everything looks good.',
|
|
1133
|
+
cost: { totalUsd: 0.03, perModel: {} },
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
const config = makeExecutorConfig();
|
|
1138
|
+
const executor = new StrategyExecutor(new PlainTextProvider(), config);
|
|
1139
|
+
const yamlStr = `
|
|
1140
|
+
strategy:
|
|
1141
|
+
id: S-PLAIN
|
|
1142
|
+
name: "Plain Response"
|
|
1143
|
+
version: "1.0"
|
|
1144
|
+
dag:
|
|
1145
|
+
nodes:
|
|
1146
|
+
- id: work
|
|
1147
|
+
type: methodology
|
|
1148
|
+
methodology: P2-SD
|
|
1149
|
+
outputs: [output]
|
|
1150
|
+
`;
|
|
1151
|
+
const dag = parseStrategyYaml(yamlStr);
|
|
1152
|
+
const result = await executor.execute(dag, {});
|
|
1153
|
+
// Should complete — output is the raw text
|
|
1154
|
+
assert.equal(result.status, 'completed');
|
|
1155
|
+
assert.equal(result.node_results['work'].status, 'completed');
|
|
1156
|
+
assert.ok(result.node_results['work'].output.result, 'should have raw result');
|
|
1157
|
+
});
|
|
1158
|
+
it('context continuity with refresh_context flag', async () => {
|
|
1159
|
+
// Test that:
|
|
1160
|
+
// 1. Without refresh_context: nodes 1 & 2 use the SAME session (continuous context)
|
|
1161
|
+
// 2. With refresh_context=true on node 2: node 3 gets a FRESH session
|
|
1162
|
+
//
|
|
1163
|
+
// Track sessionId and refreshSessionId in invocations to verify behavior
|
|
1164
|
+
class SessionTrackingProvider {
|
|
1165
|
+
name = 'session-tracking';
|
|
1166
|
+
invocations = [];
|
|
1167
|
+
capabilities() {
|
|
1168
|
+
return { modes: ['oneshot'], streaming: false, resumable: false, budgetEnforcement: 'none', outputValidation: 'client', toolModel: 'builtin' };
|
|
1169
|
+
}
|
|
1170
|
+
async invoke(_pact, request) {
|
|
1171
|
+
this.invocations.push(request);
|
|
1172
|
+
// Extract node name from prompt for debugging
|
|
1173
|
+
const match = request.prompt.match(/node "(\w+)"/);
|
|
1174
|
+
const nodeName = match ? match[1] : 'unknown';
|
|
1175
|
+
return makeMockResult({
|
|
1176
|
+
output: `\`\`\`json\n{"node": "${nodeName}", "done": true}\n\`\`\``,
|
|
1177
|
+
cost: { totalUsd: 0.02, perModel: {} },
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
const provider = new SessionTrackingProvider();
|
|
1182
|
+
const config = makeExecutorConfig();
|
|
1183
|
+
const executor = new StrategyExecutor(provider, config);
|
|
1184
|
+
const yamlStr = `
|
|
1185
|
+
strategy:
|
|
1186
|
+
id: S-CONTEXT-TEST
|
|
1187
|
+
name: "Context Continuity Test"
|
|
1188
|
+
version: "1.0"
|
|
1189
|
+
dag:
|
|
1190
|
+
nodes:
|
|
1191
|
+
- id: analyze
|
|
1192
|
+
type: methodology
|
|
1193
|
+
methodology: P2-SD
|
|
1194
|
+
outputs: [analysis]
|
|
1195
|
+
- id: design
|
|
1196
|
+
type: methodology
|
|
1197
|
+
methodology: P2-SD
|
|
1198
|
+
depends_on: [analyze]
|
|
1199
|
+
inputs: [analysis]
|
|
1200
|
+
outputs: [design]
|
|
1201
|
+
refresh_context: false
|
|
1202
|
+
- id: validate
|
|
1203
|
+
type: methodology
|
|
1204
|
+
methodology: P2-SD
|
|
1205
|
+
depends_on: [design]
|
|
1206
|
+
inputs: [design]
|
|
1207
|
+
outputs: [validation]
|
|
1208
|
+
refresh_context: true
|
|
1209
|
+
`;
|
|
1210
|
+
const dag = parseStrategyYaml(yamlStr);
|
|
1211
|
+
// Verify refresh_context fields were parsed correctly
|
|
1212
|
+
const analyzeNode = dag.nodes.find((n) => n.id === 'analyze');
|
|
1213
|
+
const designNode = dag.nodes.find((n) => n.id === 'design');
|
|
1214
|
+
const validateNode = dag.nodes.find((n) => n.id === 'validate');
|
|
1215
|
+
assert.equal(analyzeNode.refresh_context, false, 'analyze node should default to false');
|
|
1216
|
+
assert.equal(designNode.refresh_context, false, 'design node explicitly set to false');
|
|
1217
|
+
assert.equal(validateNode.refresh_context, true, 'validate node explicitly set to true');
|
|
1218
|
+
const result = await executor.execute(dag, {});
|
|
1219
|
+
// Verify execution completed successfully
|
|
1220
|
+
assert.equal(result.status, 'completed');
|
|
1221
|
+
assert.equal(result.node_results['analyze'].status, 'completed');
|
|
1222
|
+
assert.equal(result.node_results['design'].status, 'completed');
|
|
1223
|
+
assert.equal(result.node_results['validate'].status, 'completed');
|
|
1224
|
+
// Verify invocation tracking
|
|
1225
|
+
assert.equal(provider.invocations.length, 3, 'should have 3 invocations (one per node)');
|
|
1226
|
+
// Verify that each invocation has a prompt mentioning the correct node
|
|
1227
|
+
assert.ok(provider.invocations[0].prompt.includes('node "analyze"'), 'first invocation should be for analyze node');
|
|
1228
|
+
assert.ok(provider.invocations[1].prompt.includes('node "design"'), 'second invocation should be for design node');
|
|
1229
|
+
assert.ok(provider.invocations[2].prompt.includes('node "validate"'), 'third invocation should be for validate node');
|
|
1230
|
+
});
|
|
1231
|
+
});
|
|
1232
|
+
// ── Timeout Tests (isolated to avoid cascading cancellations) ──
|
|
1233
|
+
describe('StrategyExecutor — timeout enforcement', () => {
|
|
1234
|
+
it('provider that completes within timeout succeeds', async () => {
|
|
1235
|
+
// Verify that the timeout race doesn't interfere with normal execution.
|
|
1236
|
+
// Provider resolves in ~10ms, timeout is 5000ms — provider wins the race.
|
|
1237
|
+
class QuickProvider {
|
|
1238
|
+
name = 'quick';
|
|
1239
|
+
capabilities() {
|
|
1240
|
+
return { modes: ['oneshot'], streaming: false, resumable: false, budgetEnforcement: 'none', outputValidation: 'client', toolModel: 'builtin' };
|
|
1241
|
+
}
|
|
1242
|
+
async invoke(_pact, _request) {
|
|
1243
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
1244
|
+
return makeMockResult({
|
|
1245
|
+
output: '```json\n{"fast": true}\n```',
|
|
1246
|
+
cost: { totalUsd: 0.01, perModel: {} },
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
const config = makeExecutorConfig({ defaultTimeoutMs: 5000 });
|
|
1251
|
+
const executor = new StrategyExecutor(new QuickProvider(), config);
|
|
1252
|
+
const yamlStr = `
|
|
1253
|
+
strategy:
|
|
1254
|
+
id: S-FAST
|
|
1255
|
+
name: "Fast Test"
|
|
1256
|
+
version: "1.0"
|
|
1257
|
+
dag:
|
|
1258
|
+
nodes:
|
|
1259
|
+
- id: quick_node
|
|
1260
|
+
type: methodology
|
|
1261
|
+
methodology: P2-SD
|
|
1262
|
+
outputs: [result]
|
|
1263
|
+
`;
|
|
1264
|
+
const dag = parseStrategyYaml(yamlStr);
|
|
1265
|
+
const result = await executor.execute(dag, {});
|
|
1266
|
+
assert.equal(result.status, 'completed');
|
|
1267
|
+
assert.equal(result.node_results['quick_node'].status, 'completed');
|
|
1268
|
+
});
|
|
1269
|
+
it('provider that rejects immediately is caught as node failure', async () => {
|
|
1270
|
+
// Verify that provider errors (not timeouts) are also properly caught.
|
|
1271
|
+
class FailingProvider {
|
|
1272
|
+
name = 'failing';
|
|
1273
|
+
capabilities() {
|
|
1274
|
+
return { modes: ['oneshot'], streaming: false, resumable: false, budgetEnforcement: 'none', outputValidation: 'client', toolModel: 'builtin' };
|
|
1275
|
+
}
|
|
1276
|
+
async invoke(_pact, _request) {
|
|
1277
|
+
throw new Error('Connection refused');
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
const config = makeExecutorConfig({ defaultTimeoutMs: 5000 });
|
|
1281
|
+
const executor = new StrategyExecutor(new FailingProvider(), config);
|
|
1282
|
+
const yamlStr = `
|
|
1283
|
+
strategy:
|
|
1284
|
+
id: S-FAIL
|
|
1285
|
+
name: "Fail Test"
|
|
1286
|
+
version: "1.0"
|
|
1287
|
+
dag:
|
|
1288
|
+
nodes:
|
|
1289
|
+
- id: fail_node
|
|
1290
|
+
type: methodology
|
|
1291
|
+
methodology: P2-SD
|
|
1292
|
+
outputs: [result]
|
|
1293
|
+
`;
|
|
1294
|
+
const dag = parseStrategyYaml(yamlStr);
|
|
1295
|
+
const result = await executor.execute(dag, {});
|
|
1296
|
+
assert.equal(result.status, 'failed');
|
|
1297
|
+
assert.equal(result.node_results['fail_node'].status, 'failed');
|
|
1298
|
+
assert.ok(result.node_results['fail_node'].error?.includes('Connection refused'), `Expected connection error, got: ${result.node_results['fail_node'].error}`);
|
|
1299
|
+
});
|
|
1300
|
+
});
|
|
1301
|
+
//# sourceMappingURL=strategy-executor.test.js.map
|