@substrate-ai/factory 0.20.1 → 0.20.3
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 +39 -0
- package/package.json +6 -3
- package/dist/__tests__/config.test.d.ts +0 -11
- package/dist/__tests__/config.test.d.ts.map +0 -1
- package/dist/__tests__/config.test.js +0 -215
- package/dist/__tests__/config.test.js.map +0 -1
- package/dist/__tests__/factory-run-command.test.d.ts +0 -12
- package/dist/__tests__/factory-run-command.test.d.ts.map +0 -1
- package/dist/__tests__/factory-run-command.test.js +0 -454
- package/dist/__tests__/factory-run-command.test.js.map +0 -1
- package/dist/__tests__/factory-validate-command.test.d.ts +0 -15
- package/dist/__tests__/factory-validate-command.test.d.ts.map +0 -1
- package/dist/__tests__/factory-validate-command.test.js +0 -339
- package/dist/__tests__/factory-validate-command.test.js.map +0 -1
- package/dist/__tests__/fixtures/advanced-cross-project-validation.dot.d.ts +0 -72
- package/dist/__tests__/fixtures/advanced-cross-project-validation.dot.d.ts.map +0 -1
- package/dist/__tests__/fixtures/advanced-cross-project-validation.dot.js +0 -121
- package/dist/__tests__/fixtures/advanced-cross-project-validation.dot.js.map +0 -1
- package/dist/__tests__/fixtures/llm-edge-routing.dot.d.ts +0 -28
- package/dist/__tests__/fixtures/llm-edge-routing.dot.d.ts.map +0 -1
- package/dist/__tests__/fixtures/llm-edge-routing.dot.js +0 -55
- package/dist/__tests__/fixtures/llm-edge-routing.dot.js.map +0 -1
- package/dist/__tests__/fixtures/manager-loop.dot.d.ts +0 -34
- package/dist/__tests__/fixtures/manager-loop.dot.d.ts.map +0 -1
- package/dist/__tests__/fixtures/manager-loop.dot.js +0 -61
- package/dist/__tests__/fixtures/manager-loop.dot.js.map +0 -1
- package/dist/__tests__/fixtures/parallel-fan-out-fan-in.dot.d.ts +0 -42
- package/dist/__tests__/fixtures/parallel-fan-out-fan-in.dot.d.ts.map +0 -1
- package/dist/__tests__/fixtures/parallel-fan-out-fan-in.dot.js +0 -118
- package/dist/__tests__/fixtures/parallel-fan-out-fan-in.dot.js.map +0 -1
- package/dist/__tests__/fixtures/subgraph-parent.dot.d.ts +0 -35
- package/dist/__tests__/fixtures/subgraph-parent.dot.d.ts.map +0 -1
- package/dist/__tests__/fixtures/subgraph-parent.dot.js +0 -69
- package/dist/__tests__/fixtures/subgraph-parent.dot.js.map +0 -1
- package/dist/__tests__/integration/advanced-graph-events.test.d.ts +0 -19
- package/dist/__tests__/integration/advanced-graph-events.test.d.ts.map +0 -1
- package/dist/__tests__/integration/advanced-graph-events.test.js +0 -288
- package/dist/__tests__/integration/advanced-graph-events.test.js.map +0 -1
- package/dist/__tests__/integration/checkpoint-resume.test.d.ts +0 -10
- package/dist/__tests__/integration/checkpoint-resume.test.d.ts.map +0 -1
- package/dist/__tests__/integration/checkpoint-resume.test.js +0 -125
- package/dist/__tests__/integration/checkpoint-resume.test.js.map +0 -1
- package/dist/__tests__/integration/conditional-pipeline.test.d.ts +0 -10
- package/dist/__tests__/integration/conditional-pipeline.test.d.ts.map +0 -1
- package/dist/__tests__/integration/conditional-pipeline.test.js +0 -106
- package/dist/__tests__/integration/conditional-pipeline.test.js.map +0 -1
- package/dist/__tests__/integration/convergence-validation.test.d.ts +0 -14
- package/dist/__tests__/integration/convergence-validation.test.d.ts.map +0 -1
- package/dist/__tests__/integration/convergence-validation.test.js +0 -449
- package/dist/__tests__/integration/convergence-validation.test.js.map +0 -1
- package/dist/__tests__/integration/epic44-coverage-gate.test.d.ts +0 -12
- package/dist/__tests__/integration/epic44-coverage-gate.test.d.ts.map +0 -1
- package/dist/__tests__/integration/epic44-coverage-gate.test.js +0 -58
- package/dist/__tests__/integration/epic44-coverage-gate.test.js.map +0 -1
- package/dist/__tests__/integration/epic45-coverage-gate.test.d.ts +0 -11
- package/dist/__tests__/integration/epic45-coverage-gate.test.d.ts.map +0 -1
- package/dist/__tests__/integration/epic45-coverage-gate.test.js +0 -64
- package/dist/__tests__/integration/epic45-coverage-gate.test.js.map +0 -1
- package/dist/__tests__/integration/epic46-scenario-primary-executor.test.d.ts +0 -2
- package/dist/__tests__/integration/epic46-scenario-primary-executor.test.d.ts.map +0 -1
- package/dist/__tests__/integration/epic46-scenario-primary-executor.test.js +0 -285
- package/dist/__tests__/integration/epic46-scenario-primary-executor.test.js.map +0 -1
- package/dist/__tests__/integration/events.test.d.ts +0 -8
- package/dist/__tests__/integration/events.test.d.ts.map +0 -1
- package/dist/__tests__/integration/events.test.js +0 -194
- package/dist/__tests__/integration/events.test.js.map +0 -1
- package/dist/__tests__/integration/graphs.d.ts +0 -59
- package/dist/__tests__/integration/graphs.d.ts.map +0 -1
- package/dist/__tests__/integration/graphs.js +0 -164
- package/dist/__tests__/integration/graphs.js.map +0 -1
- package/dist/__tests__/integration/helpers.d.ts +0 -127
- package/dist/__tests__/integration/helpers.d.ts.map +0 -1
- package/dist/__tests__/integration/helpers.js +0 -167
- package/dist/__tests__/integration/helpers.js.map +0 -1
- package/dist/__tests__/integration/integrity.test.d.ts +0 -8
- package/dist/__tests__/integration/integrity.test.d.ts.map +0 -1
- package/dist/__tests__/integration/integrity.test.js +0 -198
- package/dist/__tests__/integration/integrity.test.js.map +0 -1
- package/dist/__tests__/integration/llm-edge-routing.test.d.ts +0 -21
- package/dist/__tests__/integration/llm-edge-routing.test.d.ts.map +0 -1
- package/dist/__tests__/integration/llm-edge-routing.test.js +0 -341
- package/dist/__tests__/integration/llm-edge-routing.test.js.map +0 -1
- package/dist/__tests__/integration/manager-loop.test.d.ts +0 -24
- package/dist/__tests__/integration/manager-loop.test.d.ts.map +0 -1
- package/dist/__tests__/integration/manager-loop.test.js +0 -276
- package/dist/__tests__/integration/manager-loop.test.js.map +0 -1
- package/dist/__tests__/integration/multi-type-graph.test.d.ts +0 -10
- package/dist/__tests__/integration/multi-type-graph.test.d.ts.map +0 -1
- package/dist/__tests__/integration/multi-type-graph.test.js +0 -100
- package/dist/__tests__/integration/multi-type-graph.test.js.map +0 -1
- package/dist/__tests__/integration/parallel-fan-out-fan-in.test.d.ts +0 -22
- package/dist/__tests__/integration/parallel-fan-out-fan-in.test.d.ts.map +0 -1
- package/dist/__tests__/integration/parallel-fan-out-fan-in.test.js +0 -515
- package/dist/__tests__/integration/parallel-fan-out-fan-in.test.js.map +0 -1
- package/dist/__tests__/integration/persistence.test.d.ts +0 -8
- package/dist/__tests__/integration/persistence.test.d.ts.map +0 -1
- package/dist/__tests__/integration/persistence.test.js +0 -129
- package/dist/__tests__/integration/persistence.test.js.map +0 -1
- package/dist/__tests__/integration/pipeline-templates-integration.test.d.ts +0 -16
- package/dist/__tests__/integration/pipeline-templates-integration.test.d.ts.map +0 -1
- package/dist/__tests__/integration/pipeline-templates-integration.test.js +0 -171
- package/dist/__tests__/integration/pipeline-templates-integration.test.js.map +0 -1
- package/dist/__tests__/integration/scenario-pipeline.test.d.ts +0 -11
- package/dist/__tests__/integration/scenario-pipeline.test.d.ts.map +0 -1
- package/dist/__tests__/integration/scenario-pipeline.test.js +0 -243
- package/dist/__tests__/integration/scenario-pipeline.test.js.map +0 -1
- package/dist/__tests__/integration/stylesheet-application.test.d.ts +0 -12
- package/dist/__tests__/integration/stylesheet-application.test.d.ts.map +0 -1
- package/dist/__tests__/integration/stylesheet-application.test.js +0 -119
- package/dist/__tests__/integration/stylesheet-application.test.js.map +0 -1
- package/dist/__tests__/integration/subgraph-execution.test.d.ts +0 -24
- package/dist/__tests__/integration/subgraph-execution.test.d.ts.map +0 -1
- package/dist/__tests__/integration/subgraph-execution.test.js +0 -291
- package/dist/__tests__/integration/subgraph-execution.test.js.map +0 -1
- package/dist/__tests__/integration/validation-errors.test.d.ts +0 -8
- package/dist/__tests__/integration/validation-errors.test.d.ts.map +0 -1
- package/dist/__tests__/integration/validation-errors.test.js +0 -150
- package/dist/__tests__/integration/validation-errors.test.js.map +0 -1
- package/dist/agent/__tests__/loop-detection.test.d.ts +0 -2
- package/dist/agent/__tests__/loop-detection.test.d.ts.map +0 -1
- package/dist/agent/__tests__/loop-detection.test.js +0 -236
- package/dist/agent/__tests__/loop-detection.test.js.map +0 -1
- package/dist/agent/__tests__/loop.test.d.ts +0 -2
- package/dist/agent/__tests__/loop.test.d.ts.map +0 -1
- package/dist/agent/__tests__/loop.test.js +0 -868
- package/dist/agent/__tests__/loop.test.js.map +0 -1
- package/dist/agent/__tests__/truncation.test.d.ts +0 -2
- package/dist/agent/__tests__/truncation.test.d.ts.map +0 -1
- package/dist/agent/__tests__/truncation.test.js +0 -276
- package/dist/agent/__tests__/truncation.test.js.map +0 -1
- package/dist/agent/tools/__tests__/anthropic-tools.test.d.ts +0 -6
- package/dist/agent/tools/__tests__/anthropic-tools.test.d.ts.map +0 -1
- package/dist/agent/tools/__tests__/anthropic-tools.test.js +0 -49
- package/dist/agent/tools/__tests__/anthropic-tools.test.js.map +0 -1
- package/dist/agent/tools/__tests__/environment.test.d.ts +0 -6
- package/dist/agent/tools/__tests__/environment.test.d.ts.map +0 -1
- package/dist/agent/tools/__tests__/environment.test.js +0 -33
- package/dist/agent/tools/__tests__/environment.test.js.map +0 -1
- package/dist/agent/tools/__tests__/gemini-tools.test.d.ts +0 -6
- package/dist/agent/tools/__tests__/gemini-tools.test.d.ts.map +0 -1
- package/dist/agent/tools/__tests__/gemini-tools.test.js +0 -98
- package/dist/agent/tools/__tests__/gemini-tools.test.js.map +0 -1
- package/dist/agent/tools/__tests__/openai-tools.test.d.ts +0 -6
- package/dist/agent/tools/__tests__/openai-tools.test.d.ts.map +0 -1
- package/dist/agent/tools/__tests__/openai-tools.test.js +0 -53
- package/dist/agent/tools/__tests__/openai-tools.test.js.map +0 -1
- package/dist/agent/tools/__tests__/patch.test.d.ts +0 -6
- package/dist/agent/tools/__tests__/patch.test.d.ts.map +0 -1
- package/dist/agent/tools/__tests__/patch.test.js +0 -116
- package/dist/agent/tools/__tests__/patch.test.js.map +0 -1
- package/dist/agent/tools/__tests__/profiles.test.d.ts +0 -6
- package/dist/agent/tools/__tests__/profiles.test.d.ts.map +0 -1
- package/dist/agent/tools/__tests__/profiles.test.js +0 -125
- package/dist/agent/tools/__tests__/profiles.test.js.map +0 -1
- package/dist/agent/tools/__tests__/registry.test.d.ts +0 -6
- package/dist/agent/tools/__tests__/registry.test.d.ts.map +0 -1
- package/dist/agent/tools/__tests__/registry.test.js +0 -94
- package/dist/agent/tools/__tests__/registry.test.js.map +0 -1
- package/dist/agent/tools/__tests__/shared.test.d.ts +0 -6
- package/dist/agent/tools/__tests__/shared.test.d.ts.map +0 -1
- package/dist/agent/tools/__tests__/shared.test.js +0 -131
- package/dist/agent/tools/__tests__/shared.test.js.map +0 -1
- package/dist/backend/__tests__/direct-backend.test.d.ts +0 -14
- package/dist/backend/__tests__/direct-backend.test.d.ts.map +0 -1
- package/dist/backend/__tests__/direct-backend.test.js +0 -393
- package/dist/backend/__tests__/direct-backend.test.js.map +0 -1
- package/dist/backend/__tests__/direct-bootstrap.test.d.ts +0 -7
- package/dist/backend/__tests__/direct-bootstrap.test.d.ts.map +0 -1
- package/dist/backend/__tests__/direct-bootstrap.test.js +0 -177
- package/dist/backend/__tests__/direct-bootstrap.test.js.map +0 -1
- package/dist/backend/__tests__/mock-backend.test.d.ts +0 -7
- package/dist/backend/__tests__/mock-backend.test.d.ts.map +0 -1
- package/dist/backend/__tests__/mock-backend.test.js +0 -273
- package/dist/backend/__tests__/mock-backend.test.js.map +0 -1
- package/dist/backend/__tests__/parity.test.d.ts +0 -17
- package/dist/backend/__tests__/parity.test.d.ts.map +0 -1
- package/dist/backend/__tests__/parity.test.js +0 -411
- package/dist/backend/__tests__/parity.test.js.map +0 -1
- package/dist/context/__tests__/auto-summarizer.test.d.ts +0 -14
- package/dist/context/__tests__/auto-summarizer.test.d.ts.map +0 -1
- package/dist/context/__tests__/auto-summarizer.test.js +0 -189
- package/dist/context/__tests__/auto-summarizer.test.js.map +0 -1
- package/dist/context/__tests__/context-cli-command.test.d.ts +0 -7
- package/dist/context/__tests__/context-cli-command.test.d.ts.map +0 -1
- package/dist/context/__tests__/context-cli-command.test.js +0 -331
- package/dist/context/__tests__/context-cli-command.test.js.map +0 -1
- package/dist/context/__tests__/pyramid-summary-integration.test.d.ts +0 -2
- package/dist/context/__tests__/pyramid-summary-integration.test.d.ts.map +0 -1
- package/dist/context/__tests__/pyramid-summary-integration.test.js +0 -533
- package/dist/context/__tests__/pyramid-summary-integration.test.js.map +0 -1
- package/dist/context/__tests__/summarizer.test.d.ts +0 -2
- package/dist/context/__tests__/summarizer.test.d.ts.map +0 -1
- package/dist/context/__tests__/summarizer.test.js +0 -189
- package/dist/context/__tests__/summarizer.test.js.map +0 -1
- package/dist/context/__tests__/summary-cache.test.d.ts +0 -2
- package/dist/context/__tests__/summary-cache.test.d.ts.map +0 -1
- package/dist/context/__tests__/summary-cache.test.js +0 -214
- package/dist/context/__tests__/summary-cache.test.js.map +0 -1
- package/dist/context/__tests__/summary-metrics.test.d.ts +0 -2
- package/dist/context/__tests__/summary-metrics.test.d.ts.map +0 -1
- package/dist/context/__tests__/summary-metrics.test.js +0 -172
- package/dist/context/__tests__/summary-metrics.test.js.map +0 -1
- package/dist/context/__tests__/summary-types.test.d.ts +0 -2
- package/dist/context/__tests__/summary-types.test.d.ts.map +0 -1
- package/dist/context/__tests__/summary-types.test.js +0 -130
- package/dist/context/__tests__/summary-types.test.js.map +0 -1
- package/dist/convergence/__tests__/budget.test.d.ts +0 -6
- package/dist/convergence/__tests__/budget.test.d.ts.map +0 -1
- package/dist/convergence/__tests__/budget.test.js +0 -187
- package/dist/convergence/__tests__/budget.test.js.map +0 -1
- package/dist/convergence/__tests__/controller.test.d.ts +0 -9
- package/dist/convergence/__tests__/controller.test.d.ts.map +0 -1
- package/dist/convergence/__tests__/controller.test.js +0 -585
- package/dist/convergence/__tests__/controller.test.js.map +0 -1
- package/dist/convergence/__tests__/dual-signal.test.d.ts +0 -14
- package/dist/convergence/__tests__/dual-signal.test.d.ts.map +0 -1
- package/dist/convergence/__tests__/dual-signal.test.js +0 -123
- package/dist/convergence/__tests__/dual-signal.test.js.map +0 -1
- package/dist/convergence/__tests__/epic46-integration.test.d.ts +0 -15
- package/dist/convergence/__tests__/epic46-integration.test.d.ts.map +0 -1
- package/dist/convergence/__tests__/epic46-integration.test.js +0 -522
- package/dist/convergence/__tests__/epic46-integration.test.js.map +0 -1
- package/dist/convergence/__tests__/plateau.test.d.ts +0 -6
- package/dist/convergence/__tests__/plateau.test.d.ts.map +0 -1
- package/dist/convergence/__tests__/plateau.test.js +0 -163
- package/dist/convergence/__tests__/plateau.test.js.map +0 -1
- package/dist/convergence/__tests__/remediation.test.d.ts +0 -11
- package/dist/convergence/__tests__/remediation.test.d.ts.map +0 -1
- package/dist/convergence/__tests__/remediation.test.js +0 -209
- package/dist/convergence/__tests__/remediation.test.js.map +0 -1
- package/dist/convergence/__tests__/scenario-primary.test.d.ts +0 -13
- package/dist/convergence/__tests__/scenario-primary.test.d.ts.map +0 -1
- package/dist/convergence/__tests__/scenario-primary.test.js +0 -183
- package/dist/convergence/__tests__/scenario-primary.test.js.map +0 -1
- package/dist/factory-command.test.d.ts +0 -8
- package/dist/factory-command.test.d.ts.map +0 -1
- package/dist/factory-command.test.js +0 -304
- package/dist/factory-command.test.js.map +0 -1
- package/dist/graph/__tests__/attractor-compliance.test.d.ts +0 -10
- package/dist/graph/__tests__/attractor-compliance.test.d.ts.map +0 -1
- package/dist/graph/__tests__/attractor-compliance.test.js +0 -766
- package/dist/graph/__tests__/attractor-compliance.test.js.map +0 -1
- package/dist/graph/__tests__/checkpoint.test.d.ts +0 -8
- package/dist/graph/__tests__/checkpoint.test.d.ts.map +0 -1
- package/dist/graph/__tests__/checkpoint.test.js +0 -329
- package/dist/graph/__tests__/checkpoint.test.js.map +0 -1
- package/dist/graph/__tests__/condition-parser.test.d.ts +0 -14
- package/dist/graph/__tests__/condition-parser.test.d.ts.map +0 -1
- package/dist/graph/__tests__/condition-parser.test.js +0 -406
- package/dist/graph/__tests__/condition-parser.test.js.map +0 -1
- package/dist/graph/__tests__/context.test.d.ts +0 -14
- package/dist/graph/__tests__/context.test.d.ts.map +0 -1
- package/dist/graph/__tests__/context.test.js +0 -276
- package/dist/graph/__tests__/context.test.js.map +0 -1
- package/dist/graph/__tests__/edge-selector-events.test.d.ts +0 -11
- package/dist/graph/__tests__/edge-selector-events.test.d.ts.map +0 -1
- package/dist/graph/__tests__/edge-selector-events.test.js +0 -184
- package/dist/graph/__tests__/edge-selector-events.test.js.map +0 -1
- package/dist/graph/__tests__/edge-selector.test.d.ts +0 -6
- package/dist/graph/__tests__/edge-selector.test.d.ts.map +0 -1
- package/dist/graph/__tests__/edge-selector.test.js +0 -452
- package/dist/graph/__tests__/edge-selector.test.js.map +0 -1
- package/dist/graph/__tests__/executor-convergence.test.d.ts +0 -12
- package/dist/graph/__tests__/executor-convergence.test.d.ts.map +0 -1
- package/dist/graph/__tests__/executor-convergence.test.js +0 -432
- package/dist/graph/__tests__/executor-convergence.test.js.map +0 -1
- package/dist/graph/__tests__/executor-fidelity.test.d.ts +0 -13
- package/dist/graph/__tests__/executor-fidelity.test.d.ts.map +0 -1
- package/dist/graph/__tests__/executor-fidelity.test.js +0 -335
- package/dist/graph/__tests__/executor-fidelity.test.js.map +0 -1
- package/dist/graph/__tests__/executor.test.d.ts +0 -14
- package/dist/graph/__tests__/executor.test.d.ts.map +0 -1
- package/dist/graph/__tests__/executor.test.js +0 -901
- package/dist/graph/__tests__/executor.test.js.map +0 -1
- package/dist/graph/__tests__/fidelity.test.d.ts +0 -8
- package/dist/graph/__tests__/fidelity.test.d.ts.map +0 -1
- package/dist/graph/__tests__/fidelity.test.js +0 -135
- package/dist/graph/__tests__/fidelity.test.js.map +0 -1
- package/dist/graph/__tests__/llm-evaluator.test.d.ts +0 -7
- package/dist/graph/__tests__/llm-evaluator.test.d.ts.map +0 -1
- package/dist/graph/__tests__/llm-evaluator.test.js +0 -106
- package/dist/graph/__tests__/llm-evaluator.test.js.map +0 -1
- package/dist/graph/__tests__/parser-chaining.test.d.ts +0 -13
- package/dist/graph/__tests__/parser-chaining.test.d.ts.map +0 -1
- package/dist/graph/__tests__/parser-chaining.test.js +0 -215
- package/dist/graph/__tests__/parser-chaining.test.js.map +0 -1
- package/dist/graph/__tests__/parser.test.d.ts +0 -22
- package/dist/graph/__tests__/parser.test.d.ts.map +0 -1
- package/dist/graph/__tests__/parser.test.js +0 -452
- package/dist/graph/__tests__/parser.test.js.map +0 -1
- package/dist/graph/__tests__/run-state.test.d.ts +0 -13
- package/dist/graph/__tests__/run-state.test.d.ts.map +0 -1
- package/dist/graph/__tests__/run-state.test.js +0 -189
- package/dist/graph/__tests__/run-state.test.js.map +0 -1
- package/dist/graph/__tests__/transformer.test.d.ts +0 -16
- package/dist/graph/__tests__/transformer.test.d.ts.map +0 -1
- package/dist/graph/__tests__/transformer.test.js +0 -350
- package/dist/graph/__tests__/transformer.test.js.map +0 -1
- package/dist/graph/__tests__/validator-errors.test.d.ts +0 -15
- package/dist/graph/__tests__/validator-errors.test.d.ts.map +0 -1
- package/dist/graph/__tests__/validator-errors.test.js +0 -572
- package/dist/graph/__tests__/validator-errors.test.js.map +0 -1
- package/dist/graph/__tests__/validator-warnings.test.d.ts +0 -15
- package/dist/graph/__tests__/validator-warnings.test.d.ts.map +0 -1
- package/dist/graph/__tests__/validator-warnings.test.js +0 -363
- package/dist/graph/__tests__/validator-warnings.test.js.map +0 -1
- package/dist/handlers/__tests__/codergen-handler.test.d.ts +0 -14
- package/dist/handlers/__tests__/codergen-handler.test.d.ts.map +0 -1
- package/dist/handlers/__tests__/codergen-handler.test.js +0 -442
- package/dist/handlers/__tests__/codergen-handler.test.js.map +0 -1
- package/dist/handlers/__tests__/fan-in.test.d.ts +0 -14
- package/dist/handlers/__tests__/fan-in.test.d.ts.map +0 -1
- package/dist/handlers/__tests__/fan-in.test.js +0 -399
- package/dist/handlers/__tests__/fan-in.test.js.map +0 -1
- package/dist/handlers/__tests__/join-policy.test.d.ts +0 -9
- package/dist/handlers/__tests__/join-policy.test.d.ts.map +0 -1
- package/dist/handlers/__tests__/join-policy.test.js +0 -201
- package/dist/handlers/__tests__/join-policy.test.js.map +0 -1
- package/dist/handlers/__tests__/manager-loop.test.d.ts +0 -14
- package/dist/handlers/__tests__/manager-loop.test.d.ts.map +0 -1
- package/dist/handlers/__tests__/manager-loop.test.js +0 -322
- package/dist/handlers/__tests__/manager-loop.test.js.map +0 -1
- package/dist/handlers/__tests__/parallel-events.test.d.ts +0 -12
- package/dist/handlers/__tests__/parallel-events.test.d.ts.map +0 -1
- package/dist/handlers/__tests__/parallel-events.test.js +0 -252
- package/dist/handlers/__tests__/parallel-events.test.js.map +0 -1
- package/dist/handlers/__tests__/parallel-handler.test.d.ts +0 -14
- package/dist/handlers/__tests__/parallel-handler.test.d.ts.map +0 -1
- package/dist/handlers/__tests__/parallel-handler.test.js +0 -337
- package/dist/handlers/__tests__/parallel-handler.test.js.map +0 -1
- package/dist/handlers/__tests__/parallel-join.test.d.ts +0 -9
- package/dist/handlers/__tests__/parallel-join.test.d.ts.map +0 -1
- package/dist/handlers/__tests__/parallel-join.test.js +0 -267
- package/dist/handlers/__tests__/parallel-join.test.js.map +0 -1
- package/dist/handlers/__tests__/registry.test.d.ts +0 -14
- package/dist/handlers/__tests__/registry.test.d.ts.map +0 -1
- package/dist/handlers/__tests__/registry.test.js +0 -315
- package/dist/handlers/__tests__/registry.test.js.map +0 -1
- package/dist/handlers/__tests__/subgraph-events.test.d.ts +0 -10
- package/dist/handlers/__tests__/subgraph-events.test.d.ts.map +0 -1
- package/dist/handlers/__tests__/subgraph-events.test.js +0 -189
- package/dist/handlers/__tests__/subgraph-events.test.js.map +0 -1
- package/dist/handlers/__tests__/subgraph-inheritance.test.d.ts +0 -14
- package/dist/handlers/__tests__/subgraph-inheritance.test.d.ts.map +0 -1
- package/dist/handlers/__tests__/subgraph-inheritance.test.js +0 -267
- package/dist/handlers/__tests__/subgraph-inheritance.test.js.map +0 -1
- package/dist/handlers/__tests__/subgraph.test.d.ts +0 -14
- package/dist/handlers/__tests__/subgraph.test.d.ts.map +0 -1
- package/dist/handlers/__tests__/subgraph.test.js +0 -369
- package/dist/handlers/__tests__/subgraph.test.js.map +0 -1
- package/dist/handlers/__tests__/tool-handler.test.d.ts +0 -11
- package/dist/handlers/__tests__/tool-handler.test.d.ts.map +0 -1
- package/dist/handlers/__tests__/tool-handler.test.js +0 -184
- package/dist/handlers/__tests__/tool-handler.test.js.map +0 -1
- package/dist/handlers/__tests__/tool-scenario.test.d.ts +0 -12
- package/dist/handlers/__tests__/tool-scenario.test.d.ts.map +0 -1
- package/dist/handlers/__tests__/tool-scenario.test.js +0 -222
- package/dist/handlers/__tests__/tool-scenario.test.js.map +0 -1
- package/dist/handlers/__tests__/wait-human-handler.test.d.ts +0 -11
- package/dist/handlers/__tests__/wait-human-handler.test.d.ts.map +0 -1
- package/dist/handlers/__tests__/wait-human-handler.test.js +0 -251
- package/dist/handlers/__tests__/wait-human-handler.test.js.map +0 -1
- package/dist/llm/__tests__/client.test.d.ts +0 -2
- package/dist/llm/__tests__/client.test.d.ts.map +0 -1
- package/dist/llm/__tests__/client.test.js +0 -198
- package/dist/llm/__tests__/client.test.js.map +0 -1
- package/dist/llm/__tests__/types.test.d.ts +0 -2
- package/dist/llm/__tests__/types.test.d.ts.map +0 -1
- package/dist/llm/__tests__/types.test.js +0 -289
- package/dist/llm/__tests__/types.test.js.map +0 -1
- package/dist/llm/middleware/__tests__/cost-tracking.test.d.ts +0 -2
- package/dist/llm/middleware/__tests__/cost-tracking.test.d.ts.map +0 -1
- package/dist/llm/middleware/__tests__/cost-tracking.test.js +0 -73
- package/dist/llm/middleware/__tests__/cost-tracking.test.js.map +0 -1
- package/dist/llm/middleware/__tests__/logging.test.d.ts +0 -2
- package/dist/llm/middleware/__tests__/logging.test.d.ts.map +0 -1
- package/dist/llm/middleware/__tests__/logging.test.js +0 -127
- package/dist/llm/middleware/__tests__/logging.test.js.map +0 -1
- package/dist/llm/middleware/__tests__/retry.test.d.ts +0 -2
- package/dist/llm/middleware/__tests__/retry.test.d.ts.map +0 -1
- package/dist/llm/middleware/__tests__/retry.test.js +0 -126
- package/dist/llm/middleware/__tests__/retry.test.js.map +0 -1
- package/dist/llm/providers/__tests__/anthropic.test.d.ts +0 -2
- package/dist/llm/providers/__tests__/anthropic.test.d.ts.map +0 -1
- package/dist/llm/providers/__tests__/anthropic.test.js +0 -412
- package/dist/llm/providers/__tests__/anthropic.test.js.map +0 -1
- package/dist/llm/providers/__tests__/gemini.test.d.ts +0 -2
- package/dist/llm/providers/__tests__/gemini.test.d.ts.map +0 -1
- package/dist/llm/providers/__tests__/gemini.test.js +0 -591
- package/dist/llm/providers/__tests__/gemini.test.js.map +0 -1
- package/dist/llm/providers/__tests__/openai.test.d.ts +0 -2
- package/dist/llm/providers/__tests__/openai.test.d.ts.map +0 -1
- package/dist/llm/providers/__tests__/openai.test.js +0 -546
- package/dist/llm/providers/__tests__/openai.test.js.map +0 -1
- package/dist/persistence/__tests__/factory-queries.test.d.ts +0 -9
- package/dist/persistence/__tests__/factory-queries.test.d.ts.map +0 -1
- package/dist/persistence/__tests__/factory-queries.test.js +0 -372
- package/dist/persistence/__tests__/factory-queries.test.js.map +0 -1
- package/dist/persistence/__tests__/factory-schema.test.d.ts +0 -6
- package/dist/persistence/__tests__/factory-schema.test.d.ts.map +0 -1
- package/dist/persistence/__tests__/factory-schema.test.js +0 -105
- package/dist/persistence/__tests__/factory-schema.test.js.map +0 -1
- package/dist/scenarios/__tests__/cli-command-list.test.d.ts +0 -7
- package/dist/scenarios/__tests__/cli-command-list.test.d.ts.map +0 -1
- package/dist/scenarios/__tests__/cli-command-list.test.js +0 -237
- package/dist/scenarios/__tests__/cli-command-list.test.js.map +0 -1
- package/dist/scenarios/__tests__/cli-command.test.d.ts +0 -11
- package/dist/scenarios/__tests__/cli-command.test.d.ts.map +0 -1
- package/dist/scenarios/__tests__/cli-command.test.js +0 -275
- package/dist/scenarios/__tests__/cli-command.test.js.map +0 -1
- package/dist/scenarios/__tests__/integrity-pipeline.test.d.ts +0 -15
- package/dist/scenarios/__tests__/integrity-pipeline.test.d.ts.map +0 -1
- package/dist/scenarios/__tests__/integrity-pipeline.test.js +0 -318
- package/dist/scenarios/__tests__/integrity-pipeline.test.js.map +0 -1
- package/dist/scenarios/__tests__/runner-twins.test.d.ts +0 -13
- package/dist/scenarios/__tests__/runner-twins.test.d.ts.map +0 -1
- package/dist/scenarios/__tests__/runner-twins.test.js +0 -205
- package/dist/scenarios/__tests__/runner-twins.test.js.map +0 -1
- package/dist/scenarios/__tests__/scorer.test.d.ts +0 -11
- package/dist/scenarios/__tests__/scorer.test.d.ts.map +0 -1
- package/dist/scenarios/__tests__/scorer.test.js +0 -225
- package/dist/scenarios/__tests__/scorer.test.js.map +0 -1
- package/dist/scenarios/__tests__/scoring-integration.test.d.ts +0 -8
- package/dist/scenarios/__tests__/scoring-integration.test.d.ts.map +0 -1
- package/dist/scenarios/__tests__/scoring-integration.test.js +0 -178
- package/dist/scenarios/__tests__/scoring-integration.test.js.map +0 -1
- package/dist/scenarios/__tests__/store.test.d.ts +0 -5
- package/dist/scenarios/__tests__/store.test.d.ts.map +0 -1
- package/dist/scenarios/__tests__/store.test.js +0 -169
- package/dist/scenarios/__tests__/store.test.js.map +0 -1
- package/dist/stylesheet/__tests__/stylesheet.test.d.ts +0 -17
- package/dist/stylesheet/__tests__/stylesheet.test.d.ts.map +0 -1
- package/dist/stylesheet/__tests__/stylesheet.test.js +0 -368
- package/dist/stylesheet/__tests__/stylesheet.test.js.map +0 -1
- package/dist/templates/__tests__/templates.test.d.ts +0 -7
- package/dist/templates/__tests__/templates.test.d.ts.map +0 -1
- package/dist/templates/__tests__/templates.test.js +0 -92
- package/dist/templates/__tests__/templates.test.js.map +0 -1
- package/dist/twins/__tests__/docker-compose.test.d.ts +0 -13
- package/dist/twins/__tests__/docker-compose.test.d.ts.map +0 -1
- package/dist/twins/__tests__/docker-compose.test.js +0 -247
- package/dist/twins/__tests__/docker-compose.test.js.map +0 -1
- package/dist/twins/__tests__/health-monitor.test.d.ts +0 -19
- package/dist/twins/__tests__/health-monitor.test.d.ts.map +0 -1
- package/dist/twins/__tests__/health-monitor.test.js +0 -301
- package/dist/twins/__tests__/health-monitor.test.js.map +0 -1
- package/dist/twins/__tests__/integration/e2e.test.d.ts +0 -18
- package/dist/twins/__tests__/integration/e2e.test.d.ts.map +0 -1
- package/dist/twins/__tests__/integration/e2e.test.js +0 -146
- package/dist/twins/__tests__/integration/e2e.test.js.map +0 -1
- package/dist/twins/__tests__/integration/helpers.d.ts +0 -32
- package/dist/twins/__tests__/integration/helpers.d.ts.map +0 -1
- package/dist/twins/__tests__/integration/helpers.js +0 -67
- package/dist/twins/__tests__/integration/helpers.js.map +0 -1
- package/dist/twins/__tests__/integration/lifecycle.test.d.ts +0 -14
- package/dist/twins/__tests__/integration/lifecycle.test.d.ts.map +0 -1
- package/dist/twins/__tests__/integration/lifecycle.test.js +0 -127
- package/dist/twins/__tests__/integration/lifecycle.test.js.map +0 -1
- package/dist/twins/__tests__/integration/persistence-integration.test.d.ts +0 -14
- package/dist/twins/__tests__/integration/persistence-integration.test.d.ts.map +0 -1
- package/dist/twins/__tests__/integration/persistence-integration.test.js +0 -132
- package/dist/twins/__tests__/integration/persistence-integration.test.js.map +0 -1
- package/dist/twins/__tests__/persistence.test.d.ts +0 -10
- package/dist/twins/__tests__/persistence.test.d.ts.map +0 -1
- package/dist/twins/__tests__/persistence.test.js +0 -300
- package/dist/twins/__tests__/persistence.test.js.map +0 -1
- package/dist/twins/__tests__/registry.test.d.ts +0 -7
- package/dist/twins/__tests__/registry.test.d.ts.map +0 -1
- package/dist/twins/__tests__/registry.test.js +0 -282
- package/dist/twins/__tests__/registry.test.js.map +0 -1
- package/dist/twins/__tests__/run-state.test.d.ts +0 -9
- package/dist/twins/__tests__/run-state.test.d.ts.map +0 -1
- package/dist/twins/__tests__/run-state.test.js +0 -112
- package/dist/twins/__tests__/run-state.test.js.map +0 -1
- package/dist/twins/__tests__/templates-cli.test.d.ts +0 -10
- package/dist/twins/__tests__/templates-cli.test.d.ts.map +0 -1
- package/dist/twins/__tests__/templates-cli.test.js +0 -187
- package/dist/twins/__tests__/templates-cli.test.js.map +0 -1
- package/dist/twins/__tests__/templates.test.d.ts +0 -7
- package/dist/twins/__tests__/templates.test.d.ts.map +0 -1
- package/dist/twins/__tests__/templates.test.js +0 -87
- package/dist/twins/__tests__/templates.test.js.map +0 -1
- package/dist/twins/__tests__/twins-cli.test.d.ts +0 -11
- package/dist/twins/__tests__/twins-cli.test.d.ts.map +0 -1
- package/dist/twins/__tests__/twins-cli.test.js +0 -365
- package/dist/twins/__tests__/twins-cli.test.js.map +0 -1
|
@@ -1,868 +0,0 @@
|
|
|
1
|
-
// packages/factory/src/agent/__tests__/loop.test.ts
|
|
2
|
-
// Tests for CodingAgentSession and the core agentic loop.
|
|
3
|
-
// Story 48-7: Coding Agent Loop — Core Agentic Loop
|
|
4
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
5
|
-
import { EventEmitter } from 'node:events';
|
|
6
|
-
import { createSession, CodingAgentSession, convertHistoryToMessages } from '../loop.js';
|
|
7
|
-
import { EventKind, SessionState } from '../types.js';
|
|
8
|
-
// ---------------------------------------------------------------------------
|
|
9
|
-
// Test helpers / shared mocks
|
|
10
|
-
// ---------------------------------------------------------------------------
|
|
11
|
-
function makeUsage(override = {}) {
|
|
12
|
-
return {
|
|
13
|
-
inputTokens: 10,
|
|
14
|
-
outputTokens: 5,
|
|
15
|
-
totalTokens: 15,
|
|
16
|
-
...override,
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
function makeTextResponse(content = 'Done!') {
|
|
20
|
-
return {
|
|
21
|
-
content,
|
|
22
|
-
toolCalls: [],
|
|
23
|
-
usage: makeUsage(),
|
|
24
|
-
model: 'test-model',
|
|
25
|
-
stopReason: 'stop',
|
|
26
|
-
providerMetadata: {},
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
function makeToolCallResponse(toolName, callId = 'call1', args = {}) {
|
|
30
|
-
return {
|
|
31
|
-
content: '',
|
|
32
|
-
toolCalls: [{ id: callId, name: toolName, arguments: args }],
|
|
33
|
-
usage: makeUsage(),
|
|
34
|
-
model: 'test-model',
|
|
35
|
-
stopReason: 'tool_calls',
|
|
36
|
-
providerMetadata: {},
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
40
|
-
function makeToolDefinition(name, executor) {
|
|
41
|
-
return {
|
|
42
|
-
name,
|
|
43
|
-
description: `Mock ${name} tool`,
|
|
44
|
-
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
45
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
46
|
-
executor: executor ?? vi.fn().mockResolvedValue(`${name} output`),
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
function makeProfile(tools = [], overrides = {}) {
|
|
50
|
-
return {
|
|
51
|
-
id: 'test',
|
|
52
|
-
model: 'test-model',
|
|
53
|
-
supports_streaming: false,
|
|
54
|
-
context_window_size: 100_000,
|
|
55
|
-
supports_parallel_tool_calls: true,
|
|
56
|
-
build_system_prompt: vi.fn().mockReturnValue('System prompt'),
|
|
57
|
-
tools: vi.fn().mockReturnValue(tools),
|
|
58
|
-
provider_options: vi.fn().mockReturnValue({}),
|
|
59
|
-
...overrides,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
function makeMockClient(completeFn) {
|
|
63
|
-
return { complete: completeFn };
|
|
64
|
-
}
|
|
65
|
-
function makeEnv() {
|
|
66
|
-
return {
|
|
67
|
-
workdir: '/tmp',
|
|
68
|
-
exec: vi.fn().mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 }),
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
function makeConfig(overrides = {}) {
|
|
72
|
-
return {
|
|
73
|
-
max_turns: 0,
|
|
74
|
-
max_tool_rounds_per_input: 0,
|
|
75
|
-
tool_output_limits: new Map(),
|
|
76
|
-
...overrides,
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
// Collect all events for specific kinds
|
|
80
|
-
function collectEvents(session, ...kinds) {
|
|
81
|
-
const events = [];
|
|
82
|
-
for (const kind of kinds) {
|
|
83
|
-
session.on(kind, e => events.push(e));
|
|
84
|
-
}
|
|
85
|
-
return events;
|
|
86
|
-
}
|
|
87
|
-
// Collect all events from a session
|
|
88
|
-
function collectAllEvents(session) {
|
|
89
|
-
const events = [];
|
|
90
|
-
for (const kind of Object.values(EventKind)) {
|
|
91
|
-
session.on(kind, e => events.push(e));
|
|
92
|
-
}
|
|
93
|
-
return events;
|
|
94
|
-
}
|
|
95
|
-
// ---------------------------------------------------------------------------
|
|
96
|
-
// Test Suite: createSession
|
|
97
|
-
// ---------------------------------------------------------------------------
|
|
98
|
-
describe('createSession', () => {
|
|
99
|
-
it('returns a CodingAgentSession instance in IDLE state with a UUID id', () => {
|
|
100
|
-
const session = createSession({
|
|
101
|
-
llmClient: makeMockClient(vi.fn()),
|
|
102
|
-
providerProfile: makeProfile(),
|
|
103
|
-
executionEnv: makeEnv(),
|
|
104
|
-
});
|
|
105
|
-
expect(session).toBeInstanceOf(CodingAgentSession);
|
|
106
|
-
expect(session.state).toBe(SessionState.IDLE);
|
|
107
|
-
expect(session.id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
|
|
108
|
-
});
|
|
109
|
-
it('emits SESSION_START event synchronously during creation', () => {
|
|
110
|
-
// Spy on EventEmitter.prototype.emit before construction
|
|
111
|
-
const emitSpy = vi.spyOn(EventEmitter.prototype, 'emit');
|
|
112
|
-
const session = createSession({
|
|
113
|
-
llmClient: makeMockClient(vi.fn()),
|
|
114
|
-
providerProfile: makeProfile(),
|
|
115
|
-
executionEnv: makeEnv(),
|
|
116
|
-
});
|
|
117
|
-
const startCall = emitSpy.mock.calls.find(args => args[0] === EventKind.SESSION_START);
|
|
118
|
-
expect(startCall).toBeDefined();
|
|
119
|
-
if (startCall) {
|
|
120
|
-
const event = startCall[1];
|
|
121
|
-
expect(event.kind).toBe(EventKind.SESSION_START);
|
|
122
|
-
expect(event.session_id).toBe(session.id);
|
|
123
|
-
}
|
|
124
|
-
emitSpy.mockRestore();
|
|
125
|
-
});
|
|
126
|
-
it('returns session with empty history', () => {
|
|
127
|
-
const session = createSession({
|
|
128
|
-
llmClient: makeMockClient(vi.fn()),
|
|
129
|
-
providerProfile: makeProfile(),
|
|
130
|
-
executionEnv: makeEnv(),
|
|
131
|
-
});
|
|
132
|
-
expect(session.history).toHaveLength(0);
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
// ---------------------------------------------------------------------------
|
|
136
|
-
// Test Suite: close()
|
|
137
|
-
// ---------------------------------------------------------------------------
|
|
138
|
-
describe('session.close()', () => {
|
|
139
|
-
it('transitions state to CLOSED and emits SESSION_END', () => {
|
|
140
|
-
const session = createSession({
|
|
141
|
-
llmClient: makeMockClient(vi.fn()),
|
|
142
|
-
providerProfile: makeProfile(),
|
|
143
|
-
executionEnv: makeEnv(),
|
|
144
|
-
});
|
|
145
|
-
const events = [];
|
|
146
|
-
session.on(EventKind.SESSION_END, e => events.push(e));
|
|
147
|
-
session.close();
|
|
148
|
-
expect(session.state).toBe(SessionState.CLOSED);
|
|
149
|
-
expect(events).toHaveLength(1);
|
|
150
|
-
expect(events[0].kind).toBe(EventKind.SESSION_END);
|
|
151
|
-
expect(events[0].session_id).toBe(session.id);
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
// ---------------------------------------------------------------------------
|
|
155
|
-
// Test Suite: processInput — natural completion
|
|
156
|
-
// ---------------------------------------------------------------------------
|
|
157
|
-
describe('processInput — natural completion (no tool calls)', () => {
|
|
158
|
-
it('emits USER_INPUT then ASSISTANT_TEXT_END then PROCESSING_END for text-only response', async () => {
|
|
159
|
-
const complete = vi.fn().mockResolvedValue(makeTextResponse('Hello, world!'));
|
|
160
|
-
const session = createSession({
|
|
161
|
-
llmClient: makeMockClient(complete),
|
|
162
|
-
providerProfile: makeProfile(),
|
|
163
|
-
executionEnv: makeEnv(),
|
|
164
|
-
});
|
|
165
|
-
const events = collectAllEvents(session);
|
|
166
|
-
await session.processInput('hi');
|
|
167
|
-
const kinds = events.map(e => e.kind);
|
|
168
|
-
expect(kinds).toContain(EventKind.USER_INPUT);
|
|
169
|
-
expect(kinds).toContain(EventKind.ASSISTANT_TEXT_END);
|
|
170
|
-
expect(kinds).toContain(EventKind.PROCESSING_END);
|
|
171
|
-
// Verify ordering
|
|
172
|
-
const uiIdx = kinds.indexOf(EventKind.USER_INPUT);
|
|
173
|
-
const ateIdx = kinds.indexOf(EventKind.ASSISTANT_TEXT_END);
|
|
174
|
-
const peIdx = kinds.indexOf(EventKind.PROCESSING_END);
|
|
175
|
-
expect(uiIdx).toBeLessThan(ateIdx);
|
|
176
|
-
expect(ateIdx).toBeLessThan(peIdx);
|
|
177
|
-
});
|
|
178
|
-
it('appends UserTurn and AssistantTurn to history', async () => {
|
|
179
|
-
const complete = vi.fn().mockResolvedValue(makeTextResponse('Response'));
|
|
180
|
-
const session = createSession({
|
|
181
|
-
llmClient: makeMockClient(complete),
|
|
182
|
-
providerProfile: makeProfile(),
|
|
183
|
-
executionEnv: makeEnv(),
|
|
184
|
-
});
|
|
185
|
-
await session.processInput('my question');
|
|
186
|
-
expect(session.history).toHaveLength(2);
|
|
187
|
-
expect(session.history[0].type).toBe('user');
|
|
188
|
-
expect(session.history[1].type).toBe('assistant');
|
|
189
|
-
});
|
|
190
|
-
it('returns to IDLE state after processing', async () => {
|
|
191
|
-
const complete = vi.fn().mockResolvedValue(makeTextResponse());
|
|
192
|
-
const session = createSession({
|
|
193
|
-
llmClient: makeMockClient(complete),
|
|
194
|
-
providerProfile: makeProfile(),
|
|
195
|
-
executionEnv: makeEnv(),
|
|
196
|
-
});
|
|
197
|
-
await session.processInput('test');
|
|
198
|
-
expect(session.state).toBe(SessionState.IDLE);
|
|
199
|
-
});
|
|
200
|
-
it('calls LLM with a request built from the session (model, systemPrompt, toolChoice)', async () => {
|
|
201
|
-
const complete = vi.fn().mockResolvedValue(makeTextResponse());
|
|
202
|
-
const profile = makeProfile();
|
|
203
|
-
const session = createSession({
|
|
204
|
-
llmClient: makeMockClient(complete),
|
|
205
|
-
providerProfile: profile,
|
|
206
|
-
executionEnv: makeEnv(),
|
|
207
|
-
});
|
|
208
|
-
await session.processInput('hello');
|
|
209
|
-
expect(complete).toHaveBeenCalledOnce();
|
|
210
|
-
const req = complete.mock.calls[0][0];
|
|
211
|
-
expect(req.model).toBe('test-model');
|
|
212
|
-
expect(req.systemPrompt).toBe('System prompt');
|
|
213
|
-
expect(req.toolChoice).toBe('auto');
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
// ---------------------------------------------------------------------------
|
|
217
|
-
// Test Suite: processInput — tool call loop
|
|
218
|
-
// ---------------------------------------------------------------------------
|
|
219
|
-
describe('processInput — one tool call then natural completion', () => {
|
|
220
|
-
it('emits TOOL_CALL_START, TOOL_CALL_END, final ASSISTANT_TEXT_END, PROCESSING_END', async () => {
|
|
221
|
-
const tool = makeToolDefinition('my_tool');
|
|
222
|
-
const complete = vi
|
|
223
|
-
.fn()
|
|
224
|
-
.mockResolvedValueOnce(makeToolCallResponse('my_tool'))
|
|
225
|
-
.mockResolvedValueOnce(makeTextResponse('All done'));
|
|
226
|
-
const session = createSession({
|
|
227
|
-
llmClient: makeMockClient(complete),
|
|
228
|
-
providerProfile: makeProfile([tool]),
|
|
229
|
-
executionEnv: makeEnv(),
|
|
230
|
-
});
|
|
231
|
-
const events = collectAllEvents(session);
|
|
232
|
-
await session.processInput('run my tool');
|
|
233
|
-
const kinds = events.map(e => e.kind);
|
|
234
|
-
expect(kinds).toContain(EventKind.TOOL_CALL_START);
|
|
235
|
-
expect(kinds).toContain(EventKind.TOOL_CALL_END);
|
|
236
|
-
expect(kinds).toContain(EventKind.PROCESSING_END);
|
|
237
|
-
// LLM was called twice (once returning tool call, once returning text)
|
|
238
|
-
expect(complete).toHaveBeenCalledTimes(2);
|
|
239
|
-
});
|
|
240
|
-
it('appends UserTurn, AssistantTurn, ToolResultsTurn, and final AssistantTurn to history', async () => {
|
|
241
|
-
const tool = makeToolDefinition('my_tool');
|
|
242
|
-
const complete = vi
|
|
243
|
-
.fn()
|
|
244
|
-
.mockResolvedValueOnce(makeToolCallResponse('my_tool'))
|
|
245
|
-
.mockResolvedValueOnce(makeTextResponse('All done'));
|
|
246
|
-
const session = createSession({
|
|
247
|
-
llmClient: makeMockClient(complete),
|
|
248
|
-
providerProfile: makeProfile([tool]),
|
|
249
|
-
executionEnv: makeEnv(),
|
|
250
|
-
});
|
|
251
|
-
await session.processInput('run my tool');
|
|
252
|
-
expect(session.history).toHaveLength(4);
|
|
253
|
-
expect(session.history[0].type).toBe('user');
|
|
254
|
-
expect(session.history[1].type).toBe('assistant');
|
|
255
|
-
expect(session.history[2].type).toBe('tool_results');
|
|
256
|
-
expect(session.history[3].type).toBe('assistant');
|
|
257
|
-
});
|
|
258
|
-
});
|
|
259
|
-
// ---------------------------------------------------------------------------
|
|
260
|
-
// Test Suite: Limit enforcement
|
|
261
|
-
// ---------------------------------------------------------------------------
|
|
262
|
-
describe('max_tool_rounds_per_input enforcement', () => {
|
|
263
|
-
it('stops after N rounds and emits TURN_LIMIT with correct data', async () => {
|
|
264
|
-
const tool = makeToolDefinition('looping_tool');
|
|
265
|
-
// LLM always returns a tool call (never natural completion)
|
|
266
|
-
const complete = vi.fn().mockResolvedValue(makeToolCallResponse('looping_tool'));
|
|
267
|
-
const session = createSession({
|
|
268
|
-
llmClient: makeMockClient(complete),
|
|
269
|
-
providerProfile: makeProfile([tool]),
|
|
270
|
-
executionEnv: makeEnv(),
|
|
271
|
-
config: makeConfig({ max_tool_rounds_per_input: 2 }),
|
|
272
|
-
});
|
|
273
|
-
const turnLimitEvents = [];
|
|
274
|
-
session.on(EventKind.TURN_LIMIT, e => turnLimitEvents.push(e));
|
|
275
|
-
const processingEndEvents = [];
|
|
276
|
-
session.on(EventKind.PROCESSING_END, e => processingEndEvents.push(e));
|
|
277
|
-
await session.processInput('loop me');
|
|
278
|
-
expect(turnLimitEvents).toHaveLength(1);
|
|
279
|
-
expect(turnLimitEvents[0].data.reason).toBe('max_tool_rounds_per_input');
|
|
280
|
-
expect(turnLimitEvents[0].data.round).toBe(2);
|
|
281
|
-
// PROCESSING_END emitted after TURN_LIMIT
|
|
282
|
-
expect(processingEndEvents).toHaveLength(1);
|
|
283
|
-
expect(session.state).toBe(SessionState.IDLE);
|
|
284
|
-
});
|
|
285
|
-
});
|
|
286
|
-
describe('max_turns enforcement', () => {
|
|
287
|
-
it('stops loop when total turn count reaches max_turns and emits TURN_LIMIT', async () => {
|
|
288
|
-
const tool = makeToolDefinition('looping_tool');
|
|
289
|
-
const complete = vi.fn().mockResolvedValue(makeToolCallResponse('looping_tool'));
|
|
290
|
-
// max_turns: 3 — after UserTurn(1) + AssistantTurn(2) + ToolResultsTurn(3) = 3 turns
|
|
291
|
-
// At next iteration start, history.length = 3 >= 3 → TURN_LIMIT
|
|
292
|
-
const session = createSession({
|
|
293
|
-
llmClient: makeMockClient(complete),
|
|
294
|
-
providerProfile: makeProfile([tool]),
|
|
295
|
-
executionEnv: makeEnv(),
|
|
296
|
-
config: makeConfig({ max_turns: 3 }),
|
|
297
|
-
});
|
|
298
|
-
const turnLimitEvents = [];
|
|
299
|
-
session.on(EventKind.TURN_LIMIT, e => turnLimitEvents.push(e));
|
|
300
|
-
await session.processInput('test');
|
|
301
|
-
expect(turnLimitEvents).toHaveLength(1);
|
|
302
|
-
expect(turnLimitEvents[0].data.reason).toBe('max_turns');
|
|
303
|
-
expect(session.state).toBe(SessionState.IDLE);
|
|
304
|
-
});
|
|
305
|
-
it('does not enforce limit when max_turns is 0 (unlimited)', async () => {
|
|
306
|
-
const complete = vi.fn().mockResolvedValue(makeTextResponse());
|
|
307
|
-
const session = createSession({
|
|
308
|
-
llmClient: makeMockClient(complete),
|
|
309
|
-
providerProfile: makeProfile(),
|
|
310
|
-
executionEnv: makeEnv(),
|
|
311
|
-
config: makeConfig({ max_turns: 0 }),
|
|
312
|
-
});
|
|
313
|
-
const turnLimitEvents = [];
|
|
314
|
-
session.on(EventKind.TURN_LIMIT, e => turnLimitEvents.push(e));
|
|
315
|
-
await session.processInput('test');
|
|
316
|
-
expect(turnLimitEvents).toHaveLength(0);
|
|
317
|
-
});
|
|
318
|
-
});
|
|
319
|
-
// ---------------------------------------------------------------------------
|
|
320
|
-
// Test Suite: Unknown tool handling
|
|
321
|
-
// ---------------------------------------------------------------------------
|
|
322
|
-
describe('unknown tool handling', () => {
|
|
323
|
-
it('returns is_error: true without throwing for unknown tool calls', async () => {
|
|
324
|
-
// Profile has NO tools registered
|
|
325
|
-
const complete = vi
|
|
326
|
-
.fn()
|
|
327
|
-
.mockResolvedValueOnce(makeToolCallResponse('nonexistent_tool', 'call99'))
|
|
328
|
-
.mockResolvedValueOnce(makeTextResponse());
|
|
329
|
-
const session = createSession({
|
|
330
|
-
llmClient: makeMockClient(complete),
|
|
331
|
-
providerProfile: makeProfile([]), // empty tools list
|
|
332
|
-
executionEnv: makeEnv(),
|
|
333
|
-
});
|
|
334
|
-
const toolEndEvents = [];
|
|
335
|
-
session.on(EventKind.TOOL_CALL_END, e => toolEndEvents.push(e));
|
|
336
|
-
// Should NOT throw
|
|
337
|
-
await expect(session.processInput('call unknown')).resolves.toBeUndefined();
|
|
338
|
-
expect(toolEndEvents).toHaveLength(1);
|
|
339
|
-
expect(toolEndEvents[0].data.is_error).toBe(true);
|
|
340
|
-
expect(toolEndEvents[0].data.output).toContain('Unknown tool: nonexistent_tool');
|
|
341
|
-
});
|
|
342
|
-
it('records the error in ToolResultsTurn history', async () => {
|
|
343
|
-
const complete = vi
|
|
344
|
-
.fn()
|
|
345
|
-
.mockResolvedValueOnce(makeToolCallResponse('ghost_tool', 'c1'))
|
|
346
|
-
.mockResolvedValueOnce(makeTextResponse());
|
|
347
|
-
const session = createSession({
|
|
348
|
-
llmClient: makeMockClient(complete),
|
|
349
|
-
providerProfile: makeProfile([]),
|
|
350
|
-
executionEnv: makeEnv(),
|
|
351
|
-
});
|
|
352
|
-
await session.processInput('call ghost');
|
|
353
|
-
const toolResultsTurn = session.history.find(t => t.type === 'tool_results');
|
|
354
|
-
expect(toolResultsTurn).toBeDefined();
|
|
355
|
-
if (toolResultsTurn?.type === 'tool_results') {
|
|
356
|
-
expect(toolResultsTurn.results[0].is_error).toBe(true);
|
|
357
|
-
expect(toolResultsTurn.results[0].content).toContain('Unknown tool: ghost_tool');
|
|
358
|
-
}
|
|
359
|
-
else {
|
|
360
|
-
throw new Error('Expected tool_results turn');
|
|
361
|
-
}
|
|
362
|
-
});
|
|
363
|
-
});
|
|
364
|
-
// ---------------------------------------------------------------------------
|
|
365
|
-
// Test Suite: TOOL_CALL_END carries full untruncated output
|
|
366
|
-
// ---------------------------------------------------------------------------
|
|
367
|
-
describe('TOOL_CALL_END full output in event', () => {
|
|
368
|
-
it('event carries full untruncated output when truncation is applied to LLM content', async () => {
|
|
369
|
-
// 'shell' has a 30,000 char default limit
|
|
370
|
-
const largeOutput = 'X'.repeat(35_000);
|
|
371
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
372
|
-
const shellTool = makeToolDefinition('shell', vi.fn().mockResolvedValue(largeOutput));
|
|
373
|
-
const complete = vi
|
|
374
|
-
.fn()
|
|
375
|
-
.mockResolvedValueOnce(makeToolCallResponse('shell', 'c1'))
|
|
376
|
-
.mockResolvedValueOnce(makeTextResponse());
|
|
377
|
-
const session = createSession({
|
|
378
|
-
llmClient: makeMockClient(complete),
|
|
379
|
-
providerProfile: makeProfile([shellTool]),
|
|
380
|
-
executionEnv: makeEnv(),
|
|
381
|
-
});
|
|
382
|
-
const toolEndEvents = [];
|
|
383
|
-
session.on(EventKind.TOOL_CALL_END, e => toolEndEvents.push(e));
|
|
384
|
-
await session.processInput('run shell');
|
|
385
|
-
// Event output = full untruncated
|
|
386
|
-
expect(toolEndEvents).toHaveLength(1);
|
|
387
|
-
expect(toolEndEvents[0].data.output).toBe(largeOutput);
|
|
388
|
-
expect(toolEndEvents[0].data.output.length).toBe(35_000);
|
|
389
|
-
// History ToolResultsTurn content should be truncated
|
|
390
|
-
const toolResultsTurn = session.history.find(t => t.type === 'tool_results');
|
|
391
|
-
expect(toolResultsTurn).toBeDefined();
|
|
392
|
-
if (toolResultsTurn?.type === 'tool_results') {
|
|
393
|
-
expect(toolResultsTurn.results[0].content).toContain('characters truncated from middle.');
|
|
394
|
-
expect(toolResultsTurn.results[0].content.length).toBeLessThan(35_000);
|
|
395
|
-
}
|
|
396
|
-
});
|
|
397
|
-
});
|
|
398
|
-
// ---------------------------------------------------------------------------
|
|
399
|
-
// Test Suite: Parallel vs sequential tool dispatch
|
|
400
|
-
// ---------------------------------------------------------------------------
|
|
401
|
-
describe('parallel tool call dispatch', () => {
|
|
402
|
-
it('dispatches multiple tool calls concurrently when supports_parallel_tool_calls=true', async () => {
|
|
403
|
-
let activeCount = 0;
|
|
404
|
-
let maxActiveCount = 0;
|
|
405
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
406
|
-
const makeParallelExecutor = () => vi.fn().mockImplementation(async () => {
|
|
407
|
-
activeCount++;
|
|
408
|
-
maxActiveCount = Math.max(maxActiveCount, activeCount);
|
|
409
|
-
await new Promise(r => setTimeout(r, 15));
|
|
410
|
-
activeCount--;
|
|
411
|
-
return 'output';
|
|
412
|
-
});
|
|
413
|
-
const tool1 = makeToolDefinition('tool1', makeParallelExecutor());
|
|
414
|
-
const tool2 = makeToolDefinition('tool2', makeParallelExecutor());
|
|
415
|
-
const complete = vi
|
|
416
|
-
.fn()
|
|
417
|
-
.mockResolvedValueOnce({
|
|
418
|
-
content: '',
|
|
419
|
-
toolCalls: [
|
|
420
|
-
{ id: 'c1', name: 'tool1', arguments: {} },
|
|
421
|
-
{ id: 'c2', name: 'tool2', arguments: {} },
|
|
422
|
-
],
|
|
423
|
-
usage: makeUsage(),
|
|
424
|
-
model: 'test-model',
|
|
425
|
-
stopReason: 'tool_calls',
|
|
426
|
-
providerMetadata: {},
|
|
427
|
-
})
|
|
428
|
-
.mockResolvedValueOnce(makeTextResponse());
|
|
429
|
-
const profile = makeProfile([tool1, tool2], { supports_parallel_tool_calls: true });
|
|
430
|
-
const session = createSession({
|
|
431
|
-
llmClient: makeMockClient(complete),
|
|
432
|
-
providerProfile: profile,
|
|
433
|
-
executionEnv: makeEnv(),
|
|
434
|
-
});
|
|
435
|
-
await session.processInput('run tools in parallel');
|
|
436
|
-
// Parallel: both tools run simultaneously → maxActiveCount = 2
|
|
437
|
-
expect(maxActiveCount).toBe(2);
|
|
438
|
-
});
|
|
439
|
-
it('dispatches tool calls sequentially when supports_parallel_tool_calls=false', async () => {
|
|
440
|
-
const callOrder = [];
|
|
441
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
442
|
-
const makeSeqExecutor = (name) => vi.fn().mockImplementation(async () => {
|
|
443
|
-
callOrder.push(`start_${name}`);
|
|
444
|
-
await new Promise(r => setTimeout(r, 10));
|
|
445
|
-
callOrder.push(`end_${name}`);
|
|
446
|
-
return 'output';
|
|
447
|
-
});
|
|
448
|
-
const tool1 = makeToolDefinition('tool1', makeSeqExecutor('tool1'));
|
|
449
|
-
const tool2 = makeToolDefinition('tool2', makeSeqExecutor('tool2'));
|
|
450
|
-
const complete = vi
|
|
451
|
-
.fn()
|
|
452
|
-
.mockResolvedValueOnce({
|
|
453
|
-
content: '',
|
|
454
|
-
toolCalls: [
|
|
455
|
-
{ id: 'c1', name: 'tool1', arguments: {} },
|
|
456
|
-
{ id: 'c2', name: 'tool2', arguments: {} },
|
|
457
|
-
],
|
|
458
|
-
usage: makeUsage(),
|
|
459
|
-
model: 'test-model',
|
|
460
|
-
stopReason: 'tool_calls',
|
|
461
|
-
providerMetadata: {},
|
|
462
|
-
})
|
|
463
|
-
.mockResolvedValueOnce(makeTextResponse());
|
|
464
|
-
const profile = makeProfile([tool1, tool2], { supports_parallel_tool_calls: false });
|
|
465
|
-
const session = createSession({
|
|
466
|
-
llmClient: makeMockClient(complete),
|
|
467
|
-
providerProfile: profile,
|
|
468
|
-
executionEnv: makeEnv(),
|
|
469
|
-
});
|
|
470
|
-
await session.processInput('run tools sequentially');
|
|
471
|
-
// Sequential: tool1 must complete before tool2 starts
|
|
472
|
-
expect(callOrder).toEqual(['start_tool1', 'end_tool1', 'start_tool2', 'end_tool2']);
|
|
473
|
-
});
|
|
474
|
-
});
|
|
475
|
-
// ---------------------------------------------------------------------------
|
|
476
|
-
// Test Suite: convertHistoryToMessages
|
|
477
|
-
// ---------------------------------------------------------------------------
|
|
478
|
-
describe('convertHistoryToMessages', () => {
|
|
479
|
-
it('converts UserTurn to user role message with text content', () => {
|
|
480
|
-
const history = [{ type: 'user', content: 'hello', timestamp: new Date() }];
|
|
481
|
-
const messages = convertHistoryToMessages(history);
|
|
482
|
-
expect(messages).toHaveLength(1);
|
|
483
|
-
expect(messages[0].role).toBe('user');
|
|
484
|
-
expect(messages[0].content[0]).toMatchObject({ kind: 'text', text: 'hello' });
|
|
485
|
-
});
|
|
486
|
-
it('converts SteeringTurn to user role message', () => {
|
|
487
|
-
const history = [{ type: 'steering', content: 'steer this', timestamp: new Date() }];
|
|
488
|
-
const messages = convertHistoryToMessages(history);
|
|
489
|
-
expect(messages).toHaveLength(1);
|
|
490
|
-
expect(messages[0].role).toBe('user');
|
|
491
|
-
expect(messages[0].content[0]).toMatchObject({ kind: 'text', text: 'steer this' });
|
|
492
|
-
});
|
|
493
|
-
it('skips SystemTurn (system prompt passed separately)', () => {
|
|
494
|
-
const history = [{ type: 'system', content: 'system msg', timestamp: new Date() }];
|
|
495
|
-
const messages = convertHistoryToMessages(history);
|
|
496
|
-
expect(messages).toHaveLength(0);
|
|
497
|
-
});
|
|
498
|
-
it('converts ToolResultsTurn to user role with tool_result content parts', () => {
|
|
499
|
-
const history = [{
|
|
500
|
-
type: 'tool_results',
|
|
501
|
-
results: [{ tool_call_id: 'c1', content: 'result', is_error: false }],
|
|
502
|
-
timestamp: new Date(),
|
|
503
|
-
}];
|
|
504
|
-
const messages = convertHistoryToMessages(history);
|
|
505
|
-
expect(messages).toHaveLength(1);
|
|
506
|
-
expect(messages[0].role).toBe('user');
|
|
507
|
-
expect(messages[0].content[0]).toMatchObject({
|
|
508
|
-
kind: 'tool_result',
|
|
509
|
-
toolResult: { toolCallId: 'c1', content: 'result', isError: false },
|
|
510
|
-
});
|
|
511
|
-
});
|
|
512
|
-
it('converts AssistantTurn with tool calls to assistant role with tool_call parts', () => {
|
|
513
|
-
const history = [{
|
|
514
|
-
type: 'assistant',
|
|
515
|
-
content: 'Using tool',
|
|
516
|
-
tool_calls: [{ id: 'tc1', name: 'my_tool', arguments: { x: 1 } }],
|
|
517
|
-
reasoning: null,
|
|
518
|
-
usage: makeUsage(),
|
|
519
|
-
response_id: null,
|
|
520
|
-
timestamp: new Date(),
|
|
521
|
-
}];
|
|
522
|
-
const messages = convertHistoryToMessages(history);
|
|
523
|
-
expect(messages).toHaveLength(1);
|
|
524
|
-
expect(messages[0].role).toBe('assistant');
|
|
525
|
-
const parts = messages[0].content;
|
|
526
|
-
expect(parts.some(p => p.kind === 'text')).toBe(true);
|
|
527
|
-
expect(parts.some(p => p.kind === 'tool_call')).toBe(true);
|
|
528
|
-
});
|
|
529
|
-
});
|
|
530
|
-
// ---------------------------------------------------------------------------
|
|
531
|
-
// Test Suite: error handling
|
|
532
|
-
// ---------------------------------------------------------------------------
|
|
533
|
-
describe('error handling', () => {
|
|
534
|
-
it('emits ERROR event and sets state to CLOSED when LLM throws', async () => {
|
|
535
|
-
const complete = vi.fn().mockRejectedValue(new Error('LLM unavailable'));
|
|
536
|
-
const session = createSession({
|
|
537
|
-
llmClient: makeMockClient(complete),
|
|
538
|
-
providerProfile: makeProfile(),
|
|
539
|
-
executionEnv: makeEnv(),
|
|
540
|
-
});
|
|
541
|
-
const errorEvents = [];
|
|
542
|
-
session.on(EventKind.ERROR, e => errorEvents.push(e));
|
|
543
|
-
await expect(session.processInput('test')).rejects.toThrow('LLM unavailable');
|
|
544
|
-
expect(errorEvents).toHaveLength(1);
|
|
545
|
-
expect(errorEvents[0].data.message).toBe('LLM unavailable');
|
|
546
|
-
expect(session.state).toBe(SessionState.CLOSED);
|
|
547
|
-
});
|
|
548
|
-
});
|
|
549
|
-
// ---------------------------------------------------------------------------
|
|
550
|
-
// Test Suite: steer() — steering injection (Story 48-8)
|
|
551
|
-
// ---------------------------------------------------------------------------
|
|
552
|
-
describe('steer() — steering injection', () => {
|
|
553
|
-
it('message queued via steer() appears as SteeringTurn before next LLM call', async () => {
|
|
554
|
-
const tool = makeToolDefinition('my_tool');
|
|
555
|
-
const complete = vi
|
|
556
|
-
.fn()
|
|
557
|
-
.mockResolvedValueOnce(makeToolCallResponse('my_tool'))
|
|
558
|
-
.mockResolvedValueOnce(makeTextResponse('Done'));
|
|
559
|
-
const session = createSession({
|
|
560
|
-
llmClient: makeMockClient(complete),
|
|
561
|
-
providerProfile: makeProfile([tool]),
|
|
562
|
-
executionEnv: makeEnv(),
|
|
563
|
-
});
|
|
564
|
-
// Queue a steering message before processing starts
|
|
565
|
-
session.steer('please use a different approach');
|
|
566
|
-
await session.processInput('do something');
|
|
567
|
-
// SteeringTurn should appear in history
|
|
568
|
-
const steeringTurns = session.history.filter(t => t.type === 'steering');
|
|
569
|
-
expect(steeringTurns.length).toBeGreaterThanOrEqual(1);
|
|
570
|
-
const turn = steeringTurns[0];
|
|
571
|
-
expect(turn?.type).toBe('steering');
|
|
572
|
-
if (turn?.type === 'steering') {
|
|
573
|
-
expect(turn.content).toBe('please use a different approach');
|
|
574
|
-
}
|
|
575
|
-
});
|
|
576
|
-
it('STEERING_INJECTED event is emitted with correct content', async () => {
|
|
577
|
-
const complete = vi.fn().mockResolvedValue(makeTextResponse('Done'));
|
|
578
|
-
const session = createSession({
|
|
579
|
-
llmClient: makeMockClient(complete),
|
|
580
|
-
providerProfile: makeProfile(),
|
|
581
|
-
executionEnv: makeEnv(),
|
|
582
|
-
});
|
|
583
|
-
const steeringEvents = [];
|
|
584
|
-
session.on(EventKind.STEERING_INJECTED, e => steeringEvents.push(e));
|
|
585
|
-
session.steer('redirect now');
|
|
586
|
-
await session.processInput('hello');
|
|
587
|
-
expect(steeringEvents).toHaveLength(1);
|
|
588
|
-
expect(steeringEvents[0].data.content).toBe('redirect now');
|
|
589
|
-
});
|
|
590
|
-
it('steering message is in history before the next LLM request (correct history position)', async () => {
|
|
591
|
-
const tool = makeToolDefinition('my_tool');
|
|
592
|
-
// Capture what history looks like at each LLM call
|
|
593
|
-
const historySnapshotsAtLLMCall = [];
|
|
594
|
-
const complete = vi.fn().mockImplementation(() => {
|
|
595
|
-
// Capture the types of turns in history at call time
|
|
596
|
-
historySnapshotsAtLLMCall.push(session.history.map(t => t.type));
|
|
597
|
-
if (complete.mock.calls.length === 1) {
|
|
598
|
-
return Promise.resolve(makeToolCallResponse('my_tool'));
|
|
599
|
-
}
|
|
600
|
-
return Promise.resolve(makeTextResponse('Done'));
|
|
601
|
-
});
|
|
602
|
-
const session = createSession({
|
|
603
|
-
llmClient: makeMockClient(complete),
|
|
604
|
-
providerProfile: makeProfile([tool]),
|
|
605
|
-
executionEnv: makeEnv(),
|
|
606
|
-
});
|
|
607
|
-
// Steer before the second LLM call (while the tool is executing)
|
|
608
|
-
// We'll queue it before processInput; it will be drained before the first LLM call
|
|
609
|
-
session.steer('steered!');
|
|
610
|
-
await session.processInput('use tool');
|
|
611
|
-
// First LLM call should see the steering turn in history
|
|
612
|
-
expect(historySnapshotsAtLLMCall[0]).toContain('steering');
|
|
613
|
-
});
|
|
614
|
-
it('steer() called while IDLE: message is buffered and injected on next processInput call', async () => {
|
|
615
|
-
const complete = vi.fn().mockResolvedValue(makeTextResponse('OK'));
|
|
616
|
-
const session = createSession({
|
|
617
|
-
llmClient: makeMockClient(complete),
|
|
618
|
-
providerProfile: makeProfile(),
|
|
619
|
-
executionEnv: makeEnv(),
|
|
620
|
-
});
|
|
621
|
-
// Session is IDLE; queue steering
|
|
622
|
-
session.steer('buffered-steer');
|
|
623
|
-
expect(session._steeringQueue).toHaveLength(1);
|
|
624
|
-
const steeringEvents = [];
|
|
625
|
-
session.on(EventKind.STEERING_INJECTED, e => steeringEvents.push(e));
|
|
626
|
-
await session.processInput('now process');
|
|
627
|
-
// Steering should have been drained
|
|
628
|
-
expect(steeringEvents).toHaveLength(1);
|
|
629
|
-
expect(steeringEvents[0].data.content).toBe('buffered-steer');
|
|
630
|
-
expect(session._steeringQueue).toHaveLength(0);
|
|
631
|
-
});
|
|
632
|
-
it('multiple steer() messages are drained FIFO', async () => {
|
|
633
|
-
const complete = vi.fn().mockResolvedValue(makeTextResponse('OK'));
|
|
634
|
-
const session = createSession({
|
|
635
|
-
llmClient: makeMockClient(complete),
|
|
636
|
-
providerProfile: makeProfile(),
|
|
637
|
-
executionEnv: makeEnv(),
|
|
638
|
-
});
|
|
639
|
-
session.steer('first');
|
|
640
|
-
session.steer('second');
|
|
641
|
-
session.steer('third');
|
|
642
|
-
const steeringEvents = [];
|
|
643
|
-
session.on(EventKind.STEERING_INJECTED, e => steeringEvents.push(e));
|
|
644
|
-
await session.processInput('go');
|
|
645
|
-
expect(steeringEvents).toHaveLength(3);
|
|
646
|
-
expect(steeringEvents[0].data.content).toBe('first');
|
|
647
|
-
expect(steeringEvents[1].data.content).toBe('second');
|
|
648
|
-
expect(steeringEvents[2].data.content).toBe('third');
|
|
649
|
-
});
|
|
650
|
-
});
|
|
651
|
-
// ---------------------------------------------------------------------------
|
|
652
|
-
// Test Suite: _drainSteering (Story 48-8)
|
|
653
|
-
// ---------------------------------------------------------------------------
|
|
654
|
-
describe('_drainSteering()', () => {
|
|
655
|
-
it('dequeues all messages in FIFO order, appends SteeringTurns, emits STEERING_INJECTED', () => {
|
|
656
|
-
const complete = vi.fn();
|
|
657
|
-
const session = createSession({
|
|
658
|
-
llmClient: makeMockClient(complete),
|
|
659
|
-
providerProfile: makeProfile(),
|
|
660
|
-
executionEnv: makeEnv(),
|
|
661
|
-
});
|
|
662
|
-
const steeringEvents = [];
|
|
663
|
-
session.on(EventKind.STEERING_INJECTED, e => steeringEvents.push(e));
|
|
664
|
-
session._steeringQueue.push('msg1', 'msg2', 'msg3');
|
|
665
|
-
session._drainSteering();
|
|
666
|
-
// All messages dequeued
|
|
667
|
-
expect(session._steeringQueue).toHaveLength(0);
|
|
668
|
-
// SteeringTurns appended to history
|
|
669
|
-
expect(session.history).toHaveLength(3);
|
|
670
|
-
expect(session.history.every(t => t.type === 'steering')).toBe(true);
|
|
671
|
-
expect(session.history[0].content).toBe('msg1');
|
|
672
|
-
expect(session.history[1].content).toBe('msg2');
|
|
673
|
-
expect(session.history[2].content).toBe('msg3');
|
|
674
|
-
// Events emitted per message
|
|
675
|
-
expect(steeringEvents).toHaveLength(3);
|
|
676
|
-
expect(steeringEvents[0].data.content).toBe('msg1');
|
|
677
|
-
expect(steeringEvents[1].data.content).toBe('msg2');
|
|
678
|
-
expect(steeringEvents[2].data.content).toBe('msg3');
|
|
679
|
-
});
|
|
680
|
-
it('does nothing when steering queue is empty', () => {
|
|
681
|
-
const session = createSession({
|
|
682
|
-
llmClient: makeMockClient(vi.fn()),
|
|
683
|
-
providerProfile: makeProfile(),
|
|
684
|
-
executionEnv: makeEnv(),
|
|
685
|
-
});
|
|
686
|
-
const steeringEvents = [];
|
|
687
|
-
session.on(EventKind.STEERING_INJECTED, e => steeringEvents.push(e));
|
|
688
|
-
session._drainSteering();
|
|
689
|
-
expect(session.history).toHaveLength(0);
|
|
690
|
-
expect(steeringEvents).toHaveLength(0);
|
|
691
|
-
});
|
|
692
|
-
it('SteeringTurn has a timestamp (Date instance)', () => {
|
|
693
|
-
const session = createSession({
|
|
694
|
-
llmClient: makeMockClient(vi.fn()),
|
|
695
|
-
providerProfile: makeProfile(),
|
|
696
|
-
executionEnv: makeEnv(),
|
|
697
|
-
});
|
|
698
|
-
session._steeringQueue.push('msg');
|
|
699
|
-
session._drainSteering();
|
|
700
|
-
const turn = session.history[0];
|
|
701
|
-
expect(turn?.type).toBe('steering');
|
|
702
|
-
if (turn?.type === 'steering') {
|
|
703
|
-
expect(turn.timestamp).toBeInstanceOf(Date);
|
|
704
|
-
}
|
|
705
|
-
});
|
|
706
|
-
});
|
|
707
|
-
// ---------------------------------------------------------------------------
|
|
708
|
-
// Test Suite: follow_up() — follow-up queue (Story 48-8)
|
|
709
|
-
// ---------------------------------------------------------------------------
|
|
710
|
-
describe('follow_up() — follow-up queue', () => {
|
|
711
|
-
it('queued follow-up triggers a new processing cycle after natural completion', async () => {
|
|
712
|
-
const complete = vi
|
|
713
|
-
.fn()
|
|
714
|
-
.mockResolvedValueOnce(makeTextResponse('First response'))
|
|
715
|
-
.mockResolvedValueOnce(makeTextResponse('Second response'));
|
|
716
|
-
const session = createSession({
|
|
717
|
-
llmClient: makeMockClient(complete),
|
|
718
|
-
providerProfile: makeProfile(),
|
|
719
|
-
executionEnv: makeEnv(),
|
|
720
|
-
});
|
|
721
|
-
session.follow_up('follow-up message');
|
|
722
|
-
const userInputEvents = [];
|
|
723
|
-
session.on(EventKind.USER_INPUT, e => userInputEvents.push(e));
|
|
724
|
-
const processingEndEvents = [];
|
|
725
|
-
session.on(EventKind.PROCESSING_END, e => processingEndEvents.push(e));
|
|
726
|
-
await session.processInput('initial message');
|
|
727
|
-
// LLM should have been called twice
|
|
728
|
-
expect(complete).toHaveBeenCalledTimes(2);
|
|
729
|
-
// USER_INPUT emitted twice (once per processInput call)
|
|
730
|
-
expect(userInputEvents).toHaveLength(2);
|
|
731
|
-
expect(userInputEvents[0].data.content).toBe('initial message');
|
|
732
|
-
expect(userInputEvents[1].data.content).toBe('follow-up message');
|
|
733
|
-
// PROCESSING_END emitted exactly once (after follow-up is fully exhausted)
|
|
734
|
-
expect(processingEndEvents).toHaveLength(1);
|
|
735
|
-
});
|
|
736
|
-
it('PROCESSING_END is NOT emitted until all follow-ups are exhausted', async () => {
|
|
737
|
-
const complete = vi
|
|
738
|
-
.fn()
|
|
739
|
-
.mockResolvedValueOnce(makeTextResponse('R1'))
|
|
740
|
-
.mockResolvedValueOnce(makeTextResponse('R2'))
|
|
741
|
-
.mockResolvedValueOnce(makeTextResponse('R3'));
|
|
742
|
-
const session = createSession({
|
|
743
|
-
llmClient: makeMockClient(complete),
|
|
744
|
-
providerProfile: makeProfile(),
|
|
745
|
-
executionEnv: makeEnv(),
|
|
746
|
-
});
|
|
747
|
-
session.follow_up('follow-up-1');
|
|
748
|
-
session.follow_up('follow-up-2');
|
|
749
|
-
const processingEndEvents = [];
|
|
750
|
-
session.on(EventKind.PROCESSING_END, e => processingEndEvents.push(e));
|
|
751
|
-
await session.processInput('initial');
|
|
752
|
-
expect(complete).toHaveBeenCalledTimes(3);
|
|
753
|
-
expect(processingEndEvents).toHaveLength(1);
|
|
754
|
-
});
|
|
755
|
-
it('follow-up messages are processed FIFO', async () => {
|
|
756
|
-
const complete = vi.fn()
|
|
757
|
-
.mockResolvedValueOnce(makeTextResponse('R1'))
|
|
758
|
-
.mockResolvedValueOnce(makeTextResponse('R2'))
|
|
759
|
-
.mockResolvedValueOnce(makeTextResponse('R3'));
|
|
760
|
-
const session = createSession({
|
|
761
|
-
llmClient: makeMockClient(complete),
|
|
762
|
-
providerProfile: makeProfile(),
|
|
763
|
-
executionEnv: makeEnv(),
|
|
764
|
-
});
|
|
765
|
-
session.follow_up('fu-first');
|
|
766
|
-
session.follow_up('fu-second');
|
|
767
|
-
const inputEvents = [];
|
|
768
|
-
session.on(EventKind.USER_INPUT, e => inputEvents.push(e));
|
|
769
|
-
await session.processInput('initial');
|
|
770
|
-
expect(inputEvents).toHaveLength(3);
|
|
771
|
-
expect(inputEvents[0].data.content).toBe('initial');
|
|
772
|
-
expect(inputEvents[1].data.content).toBe('fu-first');
|
|
773
|
-
expect(inputEvents[2].data.content).toBe('fu-second');
|
|
774
|
-
});
|
|
775
|
-
});
|
|
776
|
-
// ---------------------------------------------------------------------------
|
|
777
|
-
// Test Suite: Loop detection integration (Story 48-8)
|
|
778
|
-
// ---------------------------------------------------------------------------
|
|
779
|
-
describe('loop detection integration', () => {
|
|
780
|
-
it('LOOP_DETECTION event is emitted when the same tool is called 10+ times', async () => {
|
|
781
|
-
const tool = makeToolDefinition('looping_tool');
|
|
782
|
-
// LLM returns same tool call many times then completes
|
|
783
|
-
const complete = vi.fn();
|
|
784
|
-
// Return 'looping_tool' call 10 times, then text
|
|
785
|
-
for (let i = 0; i < 10; i++) {
|
|
786
|
-
complete.mockResolvedValueOnce(makeToolCallResponse('looping_tool', `call${i}`, {}));
|
|
787
|
-
}
|
|
788
|
-
complete.mockResolvedValueOnce(makeTextResponse('Done'));
|
|
789
|
-
const session = createSession({
|
|
790
|
-
llmClient: makeMockClient(complete),
|
|
791
|
-
providerProfile: makeProfile([tool]),
|
|
792
|
-
executionEnv: makeEnv(),
|
|
793
|
-
config: makeConfig({ enable_loop_detection: true, loop_detection_window: 10 }),
|
|
794
|
-
});
|
|
795
|
-
const loopEvents = [];
|
|
796
|
-
session.on(EventKind.LOOP_DETECTION, e => loopEvents.push(e));
|
|
797
|
-
await session.processInput('run loop');
|
|
798
|
-
expect(loopEvents).toHaveLength(1);
|
|
799
|
-
expect(loopEvents[0].data.message).toContain('Loop detected');
|
|
800
|
-
expect(loopEvents[0].data.message).toContain('10');
|
|
801
|
-
expect(loopEvents[0].data.message).toContain('repeating pattern');
|
|
802
|
-
});
|
|
803
|
-
it('LOOP_DETECTION injects SteeringTurn directly into history', async () => {
|
|
804
|
-
const tool = makeToolDefinition('looping_tool');
|
|
805
|
-
const complete = vi.fn();
|
|
806
|
-
for (let i = 0; i < 10; i++) {
|
|
807
|
-
complete.mockResolvedValueOnce(makeToolCallResponse('looping_tool', `call${i}`, {}));
|
|
808
|
-
}
|
|
809
|
-
complete.mockResolvedValueOnce(makeTextResponse('Done'));
|
|
810
|
-
const session = createSession({
|
|
811
|
-
llmClient: makeMockClient(complete),
|
|
812
|
-
providerProfile: makeProfile([tool]),
|
|
813
|
-
executionEnv: makeEnv(),
|
|
814
|
-
config: makeConfig({ enable_loop_detection: true, loop_detection_window: 10 }),
|
|
815
|
-
});
|
|
816
|
-
await session.processInput('run');
|
|
817
|
-
const steeringTurns = session.history.filter(t => t.type === 'steering');
|
|
818
|
-
const loopWarning = steeringTurns.find(t => t.type === 'steering' && t.content.includes('Loop detected'));
|
|
819
|
-
expect(loopWarning).toBeDefined();
|
|
820
|
-
if (loopWarning?.type === 'steering') {
|
|
821
|
-
expect(loopWarning.content).toContain('repeating pattern');
|
|
822
|
-
}
|
|
823
|
-
});
|
|
824
|
-
it('enable_loop_detection: false suppresses LOOP_DETECTION event even with repeating pattern', async () => {
|
|
825
|
-
const tool = makeToolDefinition('looping_tool');
|
|
826
|
-
const complete = vi.fn();
|
|
827
|
-
for (let i = 0; i < 10; i++) {
|
|
828
|
-
complete.mockResolvedValueOnce(makeToolCallResponse('looping_tool', `call${i}`, {}));
|
|
829
|
-
}
|
|
830
|
-
complete.mockResolvedValueOnce(makeTextResponse('Done'));
|
|
831
|
-
const session = createSession({
|
|
832
|
-
llmClient: makeMockClient(complete),
|
|
833
|
-
providerProfile: makeProfile([tool]),
|
|
834
|
-
executionEnv: makeEnv(),
|
|
835
|
-
config: makeConfig({
|
|
836
|
-
enable_loop_detection: false,
|
|
837
|
-
loop_detection_window: 10,
|
|
838
|
-
max_tool_rounds_per_input: 12, // allow enough rounds
|
|
839
|
-
}),
|
|
840
|
-
});
|
|
841
|
-
const loopEvents = [];
|
|
842
|
-
session.on(EventKind.LOOP_DETECTION, e => loopEvents.push(e));
|
|
843
|
-
await session.processInput('run no detection');
|
|
844
|
-
expect(loopEvents).toHaveLength(0);
|
|
845
|
-
});
|
|
846
|
-
it('loop detection warning message has correct format', async () => {
|
|
847
|
-
const tool = makeToolDefinition('repeat_tool');
|
|
848
|
-
const windowSize = 6;
|
|
849
|
-
const complete = vi.fn();
|
|
850
|
-
for (let i = 0; i < windowSize; i++) {
|
|
851
|
-
complete.mockResolvedValueOnce(makeToolCallResponse('repeat_tool', `c${i}`, {}));
|
|
852
|
-
}
|
|
853
|
-
complete.mockResolvedValueOnce(makeTextResponse('Done'));
|
|
854
|
-
const session = createSession({
|
|
855
|
-
llmClient: makeMockClient(complete),
|
|
856
|
-
providerProfile: makeProfile([tool]),
|
|
857
|
-
executionEnv: makeEnv(),
|
|
858
|
-
config: makeConfig({ enable_loop_detection: true, loop_detection_window: windowSize }),
|
|
859
|
-
});
|
|
860
|
-
const loopEvents = [];
|
|
861
|
-
session.on(EventKind.LOOP_DETECTION, e => loopEvents.push(e));
|
|
862
|
-
await session.processInput('run');
|
|
863
|
-
expect(loopEvents).toHaveLength(1);
|
|
864
|
-
const expectedMsg = `Loop detected: the last ${windowSize} tool calls follow a repeating pattern. Try a different approach.`;
|
|
865
|
-
expect(loopEvents[0].data.message).toBe(expectedMsg);
|
|
866
|
-
});
|
|
867
|
-
});
|
|
868
|
-
//# sourceMappingURL=loop.test.js.map
|