@oxagen/cli 0.5.0 → 0.6.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/README.md +66 -3
- package/dist/agent/__tests__/code-graph.test.d.ts +2 -0
- package/dist/agent/__tests__/code-graph.test.d.ts.map +1 -0
- package/dist/agent/__tests__/code-graph.test.js +83 -0
- package/dist/agent/__tests__/code-graph.test.js.map +1 -0
- package/dist/agent/__tests__/evaluator.test.d.ts +2 -0
- package/dist/agent/__tests__/evaluator.test.d.ts.map +1 -0
- package/dist/agent/__tests__/evaluator.test.js +96 -0
- package/dist/agent/__tests__/evaluator.test.js.map +1 -0
- package/dist/agent/__tests__/fleet-memory.test.d.ts +2 -0
- package/dist/agent/__tests__/fleet-memory.test.d.ts.map +1 -0
- package/dist/agent/__tests__/fleet-memory.test.js +107 -0
- package/dist/agent/__tests__/fleet-memory.test.js.map +1 -0
- package/dist/agent/__tests__/fleet-store.test.d.ts +2 -0
- package/dist/agent/__tests__/fleet-store.test.d.ts.map +1 -0
- package/dist/agent/__tests__/fleet-store.test.js +93 -0
- package/dist/agent/__tests__/fleet-store.test.js.map +1 -0
- package/dist/agent/__tests__/git-isolation.test.d.ts +2 -0
- package/dist/agent/__tests__/git-isolation.test.d.ts.map +1 -0
- package/dist/agent/__tests__/git-isolation.test.js +119 -0
- package/dist/agent/__tests__/git-isolation.test.js.map +1 -0
- package/dist/agent/__tests__/judge.test.d.ts +2 -0
- package/dist/agent/__tests__/judge.test.d.ts.map +1 -0
- package/dist/agent/__tests__/judge.test.js +135 -0
- package/dist/agent/__tests__/judge.test.js.map +1 -0
- package/dist/agent/__tests__/loop-errors.test.d.ts +2 -0
- package/dist/agent/__tests__/loop-errors.test.d.ts.map +1 -0
- package/dist/agent/__tests__/loop-errors.test.js +44 -0
- package/dist/agent/__tests__/loop-errors.test.js.map +1 -0
- package/dist/agent/__tests__/model-router.test.d.ts +2 -0
- package/dist/agent/__tests__/model-router.test.d.ts.map +1 -0
- package/dist/agent/__tests__/model-router.test.js +122 -0
- package/dist/agent/__tests__/model-router.test.js.map +1 -0
- package/dist/agent/__tests__/orchestrator-isolation.test.d.ts +2 -0
- package/dist/agent/__tests__/orchestrator-isolation.test.d.ts.map +1 -0
- package/dist/agent/__tests__/orchestrator-isolation.test.js +134 -0
- package/dist/agent/__tests__/orchestrator-isolation.test.js.map +1 -0
- package/dist/agent/__tests__/orchestrator.test.d.ts +2 -0
- package/dist/agent/__tests__/orchestrator.test.d.ts.map +1 -0
- package/dist/agent/__tests__/orchestrator.test.js +201 -0
- package/dist/agent/__tests__/orchestrator.test.js.map +1 -0
- package/dist/agent/__tests__/pipeline.test.d.ts +2 -0
- package/dist/agent/__tests__/pipeline.test.d.ts.map +1 -0
- package/dist/agent/__tests__/pipeline.test.js +226 -0
- package/dist/agent/__tests__/pipeline.test.js.map +1 -0
- package/dist/agent/__tests__/planner.test.d.ts +2 -0
- package/dist/agent/__tests__/planner.test.d.ts.map +1 -0
- package/dist/agent/__tests__/planner.test.js +98 -0
- package/dist/agent/__tests__/planner.test.js.map +1 -0
- package/dist/agent/__tests__/prompt-enhancer.test.d.ts +2 -0
- package/dist/agent/__tests__/prompt-enhancer.test.d.ts.map +1 -0
- package/dist/agent/__tests__/prompt-enhancer.test.js +107 -0
- package/dist/agent/__tests__/prompt-enhancer.test.js.map +1 -0
- package/dist/agent/__tests__/trace-format.test.d.ts +2 -0
- package/dist/agent/__tests__/trace-format.test.d.ts.map +1 -0
- package/dist/agent/__tests__/trace-format.test.js +104 -0
- package/dist/agent/__tests__/trace-format.test.js.map +1 -0
- package/dist/agent/__tests__/trace-store.test.d.ts +2 -0
- package/dist/agent/__tests__/trace-store.test.d.ts.map +1 -0
- package/dist/agent/__tests__/trace-store.test.js +113 -0
- package/dist/agent/__tests__/trace-store.test.js.map +1 -0
- package/dist/agent/code-graph.d.ts +18 -0
- package/dist/agent/code-graph.d.ts.map +1 -0
- package/dist/agent/code-graph.js +119 -0
- package/dist/agent/code-graph.js.map +1 -0
- package/dist/agent/env.d.ts +11 -0
- package/dist/agent/env.d.ts.map +1 -0
- package/dist/agent/env.js +82 -0
- package/dist/agent/env.js.map +1 -0
- package/dist/agent/evaluator.d.ts +13 -0
- package/dist/agent/evaluator.d.ts.map +1 -0
- package/dist/agent/evaluator.js +146 -0
- package/dist/agent/evaluator.js.map +1 -0
- package/dist/agent/fleet/git-isolation.d.ts +142 -0
- package/dist/agent/fleet/git-isolation.d.ts.map +1 -0
- package/dist/agent/fleet/git-isolation.js +290 -0
- package/dist/agent/fleet/git-isolation.js.map +1 -0
- package/dist/agent/fleet/memory.d.ts +21 -0
- package/dist/agent/fleet/memory.d.ts.map +1 -0
- package/dist/agent/fleet/memory.js +129 -0
- package/dist/agent/fleet/memory.js.map +1 -0
- package/dist/agent/fleet/orchestrator.d.ts +103 -0
- package/dist/agent/fleet/orchestrator.d.ts.map +1 -0
- package/dist/agent/fleet/orchestrator.js +355 -0
- package/dist/agent/fleet/orchestrator.js.map +1 -0
- package/dist/agent/fleet/store.d.ts +13 -0
- package/dist/agent/fleet/store.d.ts.map +1 -0
- package/dist/agent/fleet/store.js +79 -0
- package/dist/agent/fleet/store.js.map +1 -0
- package/dist/agent/fleet/types.d.ts +105 -0
- package/dist/agent/fleet/types.d.ts.map +1 -0
- package/dist/agent/fleet/types.js +17 -0
- package/dist/agent/fleet/types.js.map +1 -0
- package/dist/agent/judge.d.ts +38 -0
- package/dist/agent/judge.d.ts.map +1 -0
- package/dist/agent/judge.js +170 -0
- package/dist/agent/judge.js.map +1 -0
- package/dist/agent/loop.d.ts +61 -0
- package/dist/agent/loop.d.ts.map +1 -0
- package/dist/agent/loop.js +134 -0
- package/dist/agent/loop.js.map +1 -0
- package/dist/agent/memory.d.ts +14 -0
- package/dist/agent/memory.d.ts.map +1 -0
- package/dist/agent/memory.js +118 -0
- package/dist/agent/memory.js.map +1 -0
- package/dist/agent/model-router.d.ts +79 -0
- package/dist/agent/model-router.d.ts.map +1 -0
- package/dist/agent/model-router.js +141 -0
- package/dist/agent/model-router.js.map +1 -0
- package/dist/agent/model.d.ts +9 -0
- package/dist/agent/model.d.ts.map +1 -0
- package/dist/agent/model.js +24 -0
- package/dist/agent/model.js.map +1 -0
- package/dist/agent/pipeline.d.ts +82 -0
- package/dist/agent/pipeline.d.ts.map +1 -0
- package/dist/agent/pipeline.js +320 -0
- package/dist/agent/pipeline.js.map +1 -0
- package/dist/agent/planner.d.ts +16 -0
- package/dist/agent/planner.d.ts.map +1 -0
- package/dist/agent/planner.js +126 -0
- package/dist/agent/planner.js.map +1 -0
- package/dist/agent/project-context.d.ts +13 -0
- package/dist/agent/project-context.d.ts.map +1 -0
- package/dist/agent/project-context.js +66 -0
- package/dist/agent/project-context.js.map +1 -0
- package/dist/agent/prompt-enhancer.d.ts +37 -0
- package/dist/agent/prompt-enhancer.d.ts.map +1 -0
- package/dist/agent/prompt-enhancer.js +115 -0
- package/dist/agent/prompt-enhancer.js.map +1 -0
- package/dist/agent/system-prompt.d.ts +9 -0
- package/dist/agent/system-prompt.d.ts.map +1 -0
- package/dist/agent/system-prompt.js +38 -0
- package/dist/agent/system-prompt.js.map +1 -0
- package/dist/agent/tools.d.ts +19 -0
- package/dist/agent/tools.d.ts.map +1 -0
- package/dist/agent/tools.js +323 -0
- package/dist/agent/tools.js.map +1 -0
- package/dist/agent/trace-format.d.ts +6 -0
- package/dist/agent/trace-format.d.ts.map +1 -0
- package/dist/agent/trace-format.js +74 -0
- package/dist/agent/trace-format.js.map +1 -0
- package/dist/agent/trace-store.d.ts +19 -0
- package/dist/agent/trace-store.d.ts.map +1 -0
- package/dist/agent/trace-store.js +82 -0
- package/dist/agent/trace-store.js.map +1 -0
- package/dist/agent/trace.d.ts +121 -0
- package/dist/agent/trace.d.ts.map +1 -0
- package/dist/agent/trace.js +2 -0
- package/dist/agent/trace.js.map +1 -0
- package/dist/commands/__tests__/replay.test.d.ts +2 -0
- package/dist/commands/__tests__/replay.test.d.ts.map +1 -0
- package/dist/commands/__tests__/replay.test.js +76 -0
- package/dist/commands/__tests__/replay.test.js.map +1 -0
- package/dist/commands/agent.skill.load.d.ts +3 -0
- package/dist/commands/agent.skill.load.d.ts.map +1 -0
- package/dist/commands/agent.skill.load.js +57 -0
- package/dist/commands/agent.skill.load.js.map +1 -0
- package/dist/commands/code.d.ts +14 -0
- package/dist/commands/code.d.ts.map +1 -0
- package/dist/commands/code.js +100 -0
- package/dist/commands/code.js.map +1 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +66 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/env.d.ts +19 -0
- package/dist/commands/env.d.ts.map +1 -0
- package/dist/commands/env.js +64 -0
- package/dist/commands/env.js.map +1 -0
- package/dist/commands/graph.search.d.ts +10 -0
- package/dist/commands/graph.search.d.ts.map +1 -0
- package/dist/commands/graph.search.js +25 -0
- package/dist/commands/graph.search.js.map +1 -0
- package/dist/commands/mcp.add.d.ts +13 -0
- package/dist/commands/mcp.add.d.ts.map +1 -0
- package/dist/commands/mcp.add.js +110 -0
- package/dist/commands/mcp.add.js.map +1 -0
- package/dist/commands/mcp.auth.d.ts +10 -0
- package/dist/commands/mcp.auth.d.ts.map +1 -0
- package/dist/commands/mcp.auth.js +132 -0
- package/dist/commands/mcp.auth.js.map +1 -0
- package/dist/commands/mcp.check.d.ts +10 -0
- package/dist/commands/mcp.check.d.ts.map +1 -0
- package/dist/commands/mcp.check.js +114 -0
- package/dist/commands/mcp.check.js.map +1 -0
- package/dist/commands/mcp.list.d.ts +9 -0
- package/dist/commands/mcp.list.d.ts.map +1 -0
- package/dist/commands/mcp.list.js +93 -0
- package/dist/commands/mcp.list.js.map +1 -0
- package/dist/commands/mcp.permit.d.ts +12 -0
- package/dist/commands/mcp.permit.d.ts.map +1 -0
- package/dist/commands/mcp.permit.js +117 -0
- package/dist/commands/mcp.permit.js.map +1 -0
- package/dist/commands/mcp.remove.d.ts +9 -0
- package/dist/commands/mcp.remove.d.ts.map +1 -0
- package/dist/commands/mcp.remove.js +65 -0
- package/dist/commands/mcp.remove.js.map +1 -0
- package/dist/commands/plugin.org.install_bulk.js +1 -1
- package/dist/commands/plugin.org.install_bulk.js.map +1 -1
- package/dist/commands/privacy.erase.test.js +7 -0
- package/dist/commands/privacy.erase.test.js.map +1 -1
- package/dist/commands/replay.d.ts +5 -0
- package/dist/commands/replay.d.ts.map +1 -0
- package/dist/commands/replay.js +28 -0
- package/dist/commands/replay.js.map +1 -0
- package/dist/commands/schema/schema.config.d.ts +3 -0
- package/dist/commands/schema/schema.config.d.ts.map +1 -0
- package/dist/commands/schema/schema.config.js +34 -0
- package/dist/commands/schema/schema.config.js.map +1 -0
- package/dist/commands/schema/schema.disable.d.ts +3 -0
- package/dist/commands/schema/schema.disable.d.ts.map +1 -0
- package/dist/commands/schema/schema.disable.js +22 -0
- package/dist/commands/schema/schema.disable.js.map +1 -0
- package/dist/commands/schema/schema.enable.d.ts +3 -0
- package/dist/commands/schema/schema.enable.d.ts.map +1 -0
- package/dist/commands/schema/schema.enable.js +22 -0
- package/dist/commands/schema/schema.enable.js.map +1 -0
- package/dist/commands/schema/schema.export.d.ts +3 -0
- package/dist/commands/schema/schema.export.d.ts.map +1 -0
- package/dist/commands/schema/schema.export.js +31 -0
- package/dist/commands/schema/schema.export.js.map +1 -0
- package/dist/commands/schema/schema.get.d.ts +3 -0
- package/dist/commands/schema/schema.get.d.ts.map +1 -0
- package/dist/commands/schema/schema.get.js +23 -0
- package/dist/commands/schema/schema.get.js.map +1 -0
- package/dist/commands/schema/schema.label.d.ts +5 -0
- package/dist/commands/schema/schema.label.d.ts.map +1 -0
- package/dist/commands/schema/schema.label.js +60 -0
- package/dist/commands/schema/schema.label.js.map +1 -0
- package/dist/commands/schema/schema.list.d.ts +3 -0
- package/dist/commands/schema/schema.list.d.ts.map +1 -0
- package/dist/commands/schema/schema.list.js +30 -0
- package/dist/commands/schema/schema.list.js.map +1 -0
- package/dist/commands/schema/schema.prop.d.ts +5 -0
- package/dist/commands/schema/schema.prop.d.ts.map +1 -0
- package/dist/commands/schema/schema.prop.js +72 -0
- package/dist/commands/schema/schema.prop.js.map +1 -0
- package/dist/commands/schema/schema.reconcile.d.ts +10 -0
- package/dist/commands/schema/schema.reconcile.d.ts.map +1 -0
- package/dist/commands/schema/schema.reconcile.js +65 -0
- package/dist/commands/schema/schema.reconcile.js.map +1 -0
- package/dist/commands/schema/schema.rel.d.ts +5 -0
- package/dist/commands/schema/schema.rel.d.ts.map +1 -0
- package/dist/commands/schema/schema.rel.js +65 -0
- package/dist/commands/schema/schema.rel.js.map +1 -0
- package/dist/commands/schema/schema.version.d.ts +7 -0
- package/dist/commands/schema/schema.version.d.ts.map +1 -0
- package/dist/commands/schema/schema.version.js +96 -0
- package/dist/commands/schema/schema.version.js.map +1 -0
- package/dist/commands/secret.d.ts +23 -0
- package/dist/commands/secret.d.ts.map +1 -0
- package/dist/commands/secret.js +90 -0
- package/dist/commands/secret.js.map +1 -0
- package/dist/commands/skill.create.d.ts +3 -0
- package/dist/commands/skill.create.d.ts.map +1 -0
- package/dist/commands/skill.create.js +52 -0
- package/dist/commands/skill.create.js.map +1 -0
- package/dist/commands/skill.enable.d.ts +3 -0
- package/dist/commands/skill.enable.d.ts.map +1 -0
- package/dist/commands/skill.enable.js +31 -0
- package/dist/commands/skill.enable.js.map +1 -0
- package/dist/commands.test.js +1291 -281
- package/dist/commands.test.js.map +1 -1
- package/dist/components/DevStatus.d.ts.map +1 -1
- package/dist/components/DevStatus.js +3 -2
- package/dist/components/DevStatus.js.map +1 -1
- package/dist/daemon/client.d.ts +30 -0
- package/dist/daemon/client.d.ts.map +1 -0
- package/dist/daemon/client.js +97 -0
- package/dist/daemon/client.js.map +1 -0
- package/dist/daemon/code-graph/builder.d.ts +6 -0
- package/dist/daemon/code-graph/builder.d.ts.map +1 -0
- package/dist/daemon/code-graph/builder.js +215 -0
- package/dist/daemon/code-graph/builder.js.map +1 -0
- package/dist/daemon/code-graph/query.d.ts +29 -0
- package/dist/daemon/code-graph/query.d.ts.map +1 -0
- package/dist/daemon/code-graph/query.js +98 -0
- package/dist/daemon/code-graph/query.js.map +1 -0
- package/dist/daemon/code-graph/types.d.ts +37 -0
- package/dist/daemon/code-graph/types.d.ts.map +1 -0
- package/dist/daemon/code-graph/types.js +5 -0
- package/dist/daemon/code-graph/types.js.map +1 -0
- package/dist/daemon/code-graph/watcher.d.ts +37 -0
- package/dist/daemon/code-graph/watcher.d.ts.map +1 -0
- package/dist/daemon/code-graph/watcher.js +79 -0
- package/dist/daemon/code-graph/watcher.js.map +1 -0
- package/dist/daemon/lifecycle.d.ts +6 -0
- package/dist/daemon/lifecycle.d.ts.map +1 -0
- package/dist/daemon/lifecycle.js +132 -0
- package/dist/daemon/lifecycle.js.map +1 -0
- package/dist/daemon/protocol.d.ts +113 -0
- package/dist/daemon/protocol.d.ts.map +1 -0
- package/dist/daemon/protocol.js +16 -0
- package/dist/daemon/protocol.js.map +1 -0
- package/dist/daemon/server.d.ts +26 -0
- package/dist/daemon/server.d.ts.map +1 -0
- package/dist/daemon/server.js +168 -0
- package/dist/daemon/server.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +262 -356
- package/dist/index.js.map +1 -1
- package/dist/lib/api.d.ts +5 -0
- package/dist/lib/api.d.ts.map +1 -0
- package/dist/lib/api.js +52 -0
- package/dist/lib/api.js.map +1 -0
- package/dist/lib/config.d.ts +3 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +8 -2
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/differential-context.d.ts +46 -0
- package/dist/lib/differential-context.d.ts.map +1 -0
- package/dist/lib/differential-context.js +89 -0
- package/dist/lib/differential-context.js.map +1 -0
- package/dist/lib/resolve.d.ts +3 -0
- package/dist/lib/resolve.d.ts.map +1 -0
- package/dist/lib/resolve.js +29 -0
- package/dist/lib/resolve.js.map +1 -0
- package/dist/lib/structured-tool-io.d.ts +31 -0
- package/dist/lib/structured-tool-io.d.ts.map +1 -0
- package/dist/lib/structured-tool-io.js +56 -0
- package/dist/lib/structured-tool-io.js.map +1 -0
- package/dist/repl/__tests__/_queue_demo.test.d.ts +2 -0
- package/dist/repl/__tests__/_queue_demo.test.d.ts.map +1 -0
- package/dist/repl/__tests__/_queue_demo.test.js +40 -0
- package/dist/repl/__tests__/_queue_demo.test.js.map +1 -0
- package/dist/repl/__tests__/components.test.d.ts +2 -0
- package/dist/repl/__tests__/components.test.d.ts.map +1 -0
- package/dist/repl/__tests__/components.test.js +38 -0
- package/dist/repl/__tests__/components.test.js.map +1 -0
- package/dist/repl/__tests__/interactive.queue.test.d.ts +2 -0
- package/dist/repl/__tests__/interactive.queue.test.d.ts.map +1 -0
- package/dist/repl/__tests__/interactive.queue.test.js +124 -0
- package/dist/repl/__tests__/interactive.queue.test.js.map +1 -0
- package/dist/repl/components.d.ts +59 -0
- package/dist/repl/components.d.ts.map +1 -0
- package/dist/repl/components.js +152 -0
- package/dist/repl/components.js.map +1 -0
- package/dist/repl/interactive.d.ts +12 -0
- package/dist/repl/interactive.d.ts.map +1 -0
- package/dist/repl/interactive.js +326 -0
- package/dist/repl/interactive.js.map +1 -0
- package/dist/repl/one-shot.d.ts +9 -0
- package/dist/repl/one-shot.d.ts.map +1 -0
- package/dist/repl/one-shot.js +83 -0
- package/dist/repl/one-shot.js.map +1 -0
- package/dist/tui/__tests__/app.test.d.ts +2 -0
- package/dist/tui/__tests__/app.test.d.ts.map +1 -0
- package/dist/tui/__tests__/app.test.js +136 -0
- package/dist/tui/__tests__/app.test.js.map +1 -0
- package/dist/tui/__tests__/banner.test.d.ts +2 -0
- package/dist/tui/__tests__/banner.test.d.ts.map +1 -0
- package/dist/tui/__tests__/banner.test.js +15 -0
- package/dist/tui/__tests__/banner.test.js.map +1 -0
- package/dist/tui/__tests__/command-form.test.d.ts +2 -0
- package/dist/tui/__tests__/command-form.test.d.ts.map +1 -0
- package/dist/tui/__tests__/command-form.test.js +96 -0
- package/dist/tui/__tests__/command-form.test.js.map +1 -0
- package/dist/tui/__tests__/command-tree.test.d.ts +2 -0
- package/dist/tui/__tests__/command-tree.test.d.ts.map +1 -0
- package/dist/tui/__tests__/command-tree.test.js +55 -0
- package/dist/tui/__tests__/command-tree.test.js.map +1 -0
- package/dist/tui/__tests__/runner.test.d.ts +2 -0
- package/dist/tui/__tests__/runner.test.d.ts.map +1 -0
- package/dist/tui/__tests__/runner.test.js +38 -0
- package/dist/tui/__tests__/runner.test.js.map +1 -0
- package/dist/tui/__tests__/theme.test.d.ts +2 -0
- package/dist/tui/__tests__/theme.test.d.ts.map +1 -0
- package/dist/tui/__tests__/theme.test.js +11 -0
- package/dist/tui/__tests__/theme.test.js.map +1 -0
- package/dist/tui/agent-view/activity-feed.d.ts +3 -0
- package/dist/tui/agent-view/activity-feed.d.ts.map +1 -0
- package/dist/tui/agent-view/activity-feed.js +34 -0
- package/dist/tui/agent-view/activity-feed.js.map +1 -0
- package/dist/tui/agent-view/budget-bar.d.ts +3 -0
- package/dist/tui/agent-view/budget-bar.d.ts.map +1 -0
- package/dist/tui/agent-view/budget-bar.js +53 -0
- package/dist/tui/agent-view/budget-bar.js.map +1 -0
- package/dist/tui/agent-view/compile-panel.d.ts +3 -0
- package/dist/tui/agent-view/compile-panel.d.ts.map +1 -0
- package/dist/tui/agent-view/compile-panel.js +34 -0
- package/dist/tui/agent-view/compile-panel.js.map +1 -0
- package/dist/tui/agent-view/index.d.ts +4 -0
- package/dist/tui/agent-view/index.d.ts.map +1 -0
- package/dist/tui/agent-view/index.js +31 -0
- package/dist/tui/agent-view/index.js.map +1 -0
- package/dist/tui/agent-view/memory-panel.d.ts +3 -0
- package/dist/tui/agent-view/memory-panel.d.ts.map +1 -0
- package/dist/tui/agent-view/memory-panel.js +80 -0
- package/dist/tui/agent-view/memory-panel.js.map +1 -0
- package/dist/tui/agent-view/session-panel.d.ts +3 -0
- package/dist/tui/agent-view/session-panel.d.ts.map +1 -0
- package/dist/tui/agent-view/session-panel.js +44 -0
- package/dist/tui/agent-view/session-panel.js.map +1 -0
- package/dist/tui/agent-view/status-bar.d.ts +7 -0
- package/dist/tui/agent-view/status-bar.d.ts.map +1 -0
- package/dist/tui/agent-view/status-bar.js +22 -0
- package/dist/tui/agent-view/status-bar.js.map +1 -0
- package/dist/tui/app.d.ts +9 -0
- package/dist/tui/app.d.ts.map +1 -0
- package/dist/tui/app.js +115 -0
- package/dist/tui/app.js.map +1 -0
- package/dist/tui/banner.d.ts +5 -0
- package/dist/tui/banner.d.ts.map +1 -0
- package/dist/tui/banner.js +17 -0
- package/dist/tui/banner.js.map +1 -0
- package/dist/tui/command-form.d.ts +9 -0
- package/dist/tui/command-form.d.ts.map +1 -0
- package/dist/tui/command-form.js +76 -0
- package/dist/tui/command-form.js.map +1 -0
- package/dist/tui/command-tree.d.ts +30 -0
- package/dist/tui/command-tree.d.ts.map +1 -0
- package/dist/tui/command-tree.js +43 -0
- package/dist/tui/command-tree.js.map +1 -0
- package/dist/tui/fleet-view/agent-row.d.ts +10 -0
- package/dist/tui/fleet-view/agent-row.d.ts.map +1 -0
- package/dist/tui/fleet-view/agent-row.js +80 -0
- package/dist/tui/fleet-view/agent-row.js.map +1 -0
- package/dist/tui/fleet-view/dispatch-input.d.ts +5 -0
- package/dist/tui/fleet-view/dispatch-input.d.ts.map +1 -0
- package/dist/tui/fleet-view/dispatch-input.js +36 -0
- package/dist/tui/fleet-view/dispatch-input.js.map +1 -0
- package/dist/tui/fleet-view/fleet-app.d.ts +11 -0
- package/dist/tui/fleet-view/fleet-app.d.ts.map +1 -0
- package/dist/tui/fleet-view/fleet-app.js +95 -0
- package/dist/tui/fleet-view/fleet-app.js.map +1 -0
- package/dist/tui/fleet-view/fleet-summary.d.ts +6 -0
- package/dist/tui/fleet-view/fleet-summary.d.ts.map +1 -0
- package/dist/tui/fleet-view/fleet-summary.js +19 -0
- package/dist/tui/fleet-view/fleet-summary.js.map +1 -0
- package/dist/tui/fleet-view/index.d.ts +16 -0
- package/dist/tui/fleet-view/index.d.ts.map +1 -0
- package/dist/tui/fleet-view/index.js +61 -0
- package/dist/tui/fleet-view/index.js.map +1 -0
- package/dist/tui/runner.d.ts +7 -0
- package/dist/tui/runner.d.ts.map +1 -0
- package/dist/tui/runner.js +36 -0
- package/dist/tui/runner.js.map +1 -0
- package/dist/tui/theme.d.ts +8 -0
- package/dist/tui/theme.d.ts.map +1 -0
- package/dist/tui/theme.js +10 -0
- package/dist/tui/theme.js.map +1 -0
- package/package.json +12 -7
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git isolation protocol — proven against a *real* temp git repo, because the
|
|
3
|
+
* whole point is the actual git behavior, not a mock of it. We assert the four
|
|
4
|
+
* guarantees the fleet relies on:
|
|
5
|
+
*
|
|
6
|
+
* 1. No clobber — two agents editing the same file in their own worktrees
|
|
7
|
+
* never see each other's writes.
|
|
8
|
+
* 2. Atomic + pinned — every checkpoint is one commit, pinned under
|
|
9
|
+
* `refs/oxagen/agents/*` so it survives branch deletion and gc.
|
|
10
|
+
* 3. Conflicts surface, never corrupt — an overlapping merge is aborted, the
|
|
11
|
+
* integration tree stays clean, and the losing branch is still recoverable.
|
|
12
|
+
* 4. Durable log — cleanup removes worktrees but keeps the pins.
|
|
13
|
+
*/
|
|
14
|
+
import { execFileSync } from "node:child_process";
|
|
15
|
+
import { existsSync, mkdtempSync, rmSync, writeFileSync, readFileSync } from "node:fs";
|
|
16
|
+
import { tmpdir } from "node:os";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
19
|
+
import { WorktreeManager } from "../fleet/git-isolation.js";
|
|
20
|
+
function git(cwd, ...args) {
|
|
21
|
+
return execFileSync("git", args, { cwd, encoding: "utf8" }).trim();
|
|
22
|
+
}
|
|
23
|
+
let repo;
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
repo = mkdtempSync(join(tmpdir(), "oxa-iso-"));
|
|
26
|
+
git(repo, "init", "-b", "main");
|
|
27
|
+
git(repo, "config", "user.email", "t@oxagen.dev");
|
|
28
|
+
git(repo, "config", "user.name", "Isolation Test");
|
|
29
|
+
git(repo, "config", "commit.gpgsign", "false");
|
|
30
|
+
writeFileSync(join(repo, "base.txt"), "original\n");
|
|
31
|
+
git(repo, "add", "-A");
|
|
32
|
+
git(repo, "commit", "-m", "init");
|
|
33
|
+
});
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
rmSync(repo, { recursive: true, force: true });
|
|
36
|
+
});
|
|
37
|
+
describe("WorktreeManager", () => {
|
|
38
|
+
it("isolates parallel agents — same-file edits never clobber, both pinned", async () => {
|
|
39
|
+
const mgr = new WorktreeManager({ repoRoot: repo, namespace: "p1" });
|
|
40
|
+
const a = await mgr.spawn("task-a");
|
|
41
|
+
const b = await mgr.spawn("task-b");
|
|
42
|
+
expect(a).not.toBe(b);
|
|
43
|
+
// Both agents edit the SAME path — but in their own trees.
|
|
44
|
+
writeFileSync(join(a, "shared.txt"), "from A\n");
|
|
45
|
+
writeFileSync(join(b, "shared.txt"), "from B\n");
|
|
46
|
+
const cpA = await mgr.checkpoint("task-a", "a writes shared");
|
|
47
|
+
const cpB = await mgr.checkpoint("task-b", "b writes shared");
|
|
48
|
+
// No clobber: each worktree kept its own content.
|
|
49
|
+
expect(readFileSync(join(a, "shared.txt"), "utf8")).toBe("from A\n");
|
|
50
|
+
expect(readFileSync(join(b, "shared.txt"), "utf8")).toBe("from B\n");
|
|
51
|
+
// Atomic + pinned: one commit each, both reachable under refs/oxagen/agents.
|
|
52
|
+
expect(cpA?.hash).toMatch(/^[0-9a-f]{40}$/);
|
|
53
|
+
expect(cpB?.hash).toMatch(/^[0-9a-f]{40}$/);
|
|
54
|
+
expect(cpA.hash).not.toBe(cpB.hash);
|
|
55
|
+
const pins = git(repo, "for-each-ref", "--format=%(objectname)", "refs/oxagen/agents");
|
|
56
|
+
expect(pins).toContain(cpA.hash);
|
|
57
|
+
expect(pins).toContain(cpB.hash);
|
|
58
|
+
});
|
|
59
|
+
it("checkpoint returns null when the agent changed nothing", async () => {
|
|
60
|
+
const mgr = new WorktreeManager({ repoRoot: repo, namespace: "p2" });
|
|
61
|
+
await mgr.spawn("noop");
|
|
62
|
+
expect(await mgr.checkpoint("noop", "nothing")).toBeNull();
|
|
63
|
+
});
|
|
64
|
+
it("integrates non-overlapping work cleanly", async () => {
|
|
65
|
+
const mgr = new WorktreeManager({ repoRoot: repo, namespace: "p3" });
|
|
66
|
+
const a = await mgr.spawn("a");
|
|
67
|
+
writeFileSync(join(a, "a.txt"), "a\n");
|
|
68
|
+
await mgr.checkpoint("a", "add a.txt");
|
|
69
|
+
const rA = await mgr.integrate("a");
|
|
70
|
+
expect(rA.ok).toBe(true);
|
|
71
|
+
const b = await mgr.spawn("b");
|
|
72
|
+
writeFileSync(join(b, "b.txt"), "b\n");
|
|
73
|
+
await mgr.checkpoint("b", "add b.txt");
|
|
74
|
+
const rB = await mgr.integrate("b");
|
|
75
|
+
expect(rB.ok).toBe(true);
|
|
76
|
+
// The integration tree has both agents' files.
|
|
77
|
+
const int = mgr.integrationRef();
|
|
78
|
+
expect(existsSync(join(int.path, "a.txt"))).toBe(true);
|
|
79
|
+
expect(existsSync(join(int.path, "b.txt"))).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
it("surfaces a conflict, aborts clean, and keeps the losing work recoverable", async () => {
|
|
82
|
+
const mgr = new WorktreeManager({ repoRoot: repo, namespace: "p4" });
|
|
83
|
+
// A and B both rewrite base.txt from the same frozen base → real conflict.
|
|
84
|
+
const a = await mgr.spawn("a");
|
|
85
|
+
writeFileSync(join(a, "base.txt"), "A version\n");
|
|
86
|
+
await mgr.checkpoint("a", "a edits base");
|
|
87
|
+
const rA = await mgr.integrate("a");
|
|
88
|
+
expect(rA.ok).toBe(true);
|
|
89
|
+
const intTipAfterA = git(repo, "rev-parse", mgr.integrationRef().branch);
|
|
90
|
+
const b = await mgr.spawn("b");
|
|
91
|
+
writeFileSync(join(b, "base.txt"), "B version\n");
|
|
92
|
+
const cpB = await mgr.checkpoint("b", "b edits base");
|
|
93
|
+
const rB = await mgr.integrate("b");
|
|
94
|
+
// Conflict surfaced, not silently merged.
|
|
95
|
+
expect(rB.ok).toBe(false);
|
|
96
|
+
expect(rB.conflicts).toContain("base.txt");
|
|
97
|
+
// Integration tree stayed clean (merge --abort) and branch is unchanged.
|
|
98
|
+
const int = mgr.integrationRef();
|
|
99
|
+
expect(git(int.path, "status", "--porcelain")).toBe("");
|
|
100
|
+
expect(git(repo, "rev-parse", int.branch)).toBe(intTipAfterA);
|
|
101
|
+
// The losing work is NOT lost — recoverable by its pinned tip.
|
|
102
|
+
expect(rB.taskTip).toBe(cpB.hash);
|
|
103
|
+
expect(git(repo, "cat-file", "-t", rB.taskTip)).toBe("commit");
|
|
104
|
+
expect(git(repo, "show", `${rB.taskTip}:base.txt`)).toBe("B version");
|
|
105
|
+
});
|
|
106
|
+
it("cleanupAll removes worktrees but keeps the durability pins", async () => {
|
|
107
|
+
const mgr = new WorktreeManager({ repoRoot: repo, namespace: "p5" });
|
|
108
|
+
const a = await mgr.spawn("a");
|
|
109
|
+
writeFileSync(join(a, "a.txt"), "a\n");
|
|
110
|
+
const cp = await mgr.checkpoint("a", "add a.txt");
|
|
111
|
+
await mgr.cleanupAll();
|
|
112
|
+
expect(existsSync(a)).toBe(false); // worktree gone
|
|
113
|
+
// Pin survives — the commit is still reachable by hash.
|
|
114
|
+
const pins = git(repo, "for-each-ref", "--format=%(objectname)", "refs/oxagen/agents");
|
|
115
|
+
expect(pins).toContain(cp.hash);
|
|
116
|
+
expect(git(repo, "cat-file", "-t", cp.hash)).toBe("commit");
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
//# sourceMappingURL=git-isolation.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-isolation.test.js","sourceRoot":"","sources":["../../../src/agent/__tests__/git-isolation.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvF,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,SAAS,GAAG,CAAC,GAAW,EAAE,GAAG,IAAc;IACzC,OAAO,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;AACrE,CAAC;AAED,IAAI,IAAY,CAAC;AAEjB,UAAU,CAAC,GAAG,EAAE;IACd,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,UAAU,CAAC,CAAC,CAAC;IAC/C,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IAChC,GAAG,CAAC,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,cAAc,CAAC,CAAC;IAClD,GAAG,CAAC,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,gBAAgB,CAAC,CAAC;IACnD,GAAG,CAAC,IAAI,EAAE,QAAQ,EAAE,gBAAgB,EAAE,OAAO,CAAC,CAAC;IAC/C,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,EAAE,YAAY,CAAC,CAAC;IACpD,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IACvB,GAAG,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;AACpC,CAAC,CAAC,CAAC;AAEH,SAAS,CAAC,GAAG,EAAE;IACb,MAAM,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AACjD,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,uEAAuE,EAAE,KAAK,IAAI,EAAE;QACrF,MAAM,GAAG,GAAG,IAAI,eAAe,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACrE,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACpC,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACpC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAEtB,2DAA2D;QAC3D,aAAa,CAAC,IAAI,CAAC,CAAC,EAAE,YAAY,CAAC,EAAE,UAAU,CAAC,CAAC;QACjD,aAAa,CAAC,IAAI,CAAC,CAAC,EAAE,YAAY,CAAC,EAAE,UAAU,CAAC,CAAC;QAEjD,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,UAAU,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAC;QAC9D,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,UAAU,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAC;QAE9D,kDAAkD;QAClD,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,EAAE,YAAY,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACrE,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,EAAE,YAAY,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAErE,6EAA6E;QAC7E,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;QAC5C,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;QAC5C,MAAM,CAAC,GAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,GAAI,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,cAAc,EAAE,wBAAwB,EAAE,oBAAoB,CAAC,CAAC;QACvF,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,GAAI,CAAC,IAAI,CAAC,CAAC;QAClC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,GAAI,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,KAAK,IAAI,EAAE;QACtE,MAAM,GAAG,GAAG,IAAI,eAAe,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACrE,MAAM,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACxB,MAAM,CAAC,MAAM,GAAG,CAAC,UAAU,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;QACvD,MAAM,GAAG,GAAG,IAAI,eAAe,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAErE,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,aAAa,CAAC,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC;QACvC,MAAM,GAAG,CAAC,UAAU,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;QACvC,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QACpC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEzB,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,aAAa,CAAC,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC;QACvC,MAAM,GAAG,CAAC,UAAU,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;QACvC,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QACpC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEzB,+CAA+C;QAC/C,MAAM,GAAG,GAAG,GAAG,CAAC,cAAc,EAAG,CAAC;QAClC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvD,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0EAA0E,EAAE,KAAK,IAAI,EAAE;QACxF,MAAM,GAAG,GAAG,IAAI,eAAe,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAErE,2EAA2E;QAC3E,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,aAAa,CAAC,IAAI,CAAC,CAAC,EAAE,UAAU,CAAC,EAAE,aAAa,CAAC,CAAC;QAClD,MAAM,GAAG,CAAC,UAAU,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;QAC1C,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QACpC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzB,MAAM,YAAY,GAAG,GAAG,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,CAAC,cAAc,EAAG,CAAC,MAAM,CAAC,CAAC;QAE1E,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,aAAa,CAAC,IAAI,CAAC,CAAC,EAAE,UAAU,CAAC,EAAE,aAAa,CAAC,CAAC;QAClD,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,UAAU,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;QACtD,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QAEpC,0CAA0C;QAC1C,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1B,MAAM,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QAE3C,yEAAyE;QACzE,MAAM,GAAG,GAAG,GAAG,CAAC,cAAc,EAAG,CAAC;QAClC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACxD,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAE9D,+DAA+D;QAC/D,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAI,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,CAAC,OAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAChE,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4DAA4D,EAAE,KAAK,IAAI,EAAE;QAC1E,MAAM,GAAG,GAAG,IAAI,eAAe,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACrE,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,aAAa,CAAC,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC;QACvC,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,UAAU,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;QAElD,MAAM,GAAG,CAAC,UAAU,EAAE,CAAC;QAEvB,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,gBAAgB;QACnD,wDAAwD;QACxD,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,cAAc,EAAE,wBAAwB,EAAE,oBAAoB,CAAC,CAAC;QACvF,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,EAAG,CAAC,IAAI,CAAC,CAAC;QACjC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,EAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"judge.test.d.ts","sourceRoot":"","sources":["../../../src/agent/__tests__/judge.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Completeness judge — proves the advisor is always a DIFFERENT model than the
|
|
3
|
+
* executor, that a model verdict is returned faithfully, that the heuristic
|
|
4
|
+
* fallback flags "claimed a change but touched nothing", and that the revision
|
|
5
|
+
* prompt is built from the verdict. The model call is mocked.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
8
|
+
vi.mock("ai", () => ({ generateObject: vi.fn() }));
|
|
9
|
+
vi.mock("../../lib/config.js", () => ({ readConfig: () => ({}) }));
|
|
10
|
+
import { generateObject } from "ai";
|
|
11
|
+
import { judgeCompleteness, pickAdvisorModel, buildRevisionPrompt } from "../judge.js";
|
|
12
|
+
const mockGen = generateObject;
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
delete process.env["OXAGEN_LLM_ADVISOR"];
|
|
15
|
+
delete process.env["OXAGEN_LLM_FAST"];
|
|
16
|
+
delete process.env["OXAGEN_LLM_BALANCED"];
|
|
17
|
+
delete process.env["OXAGEN_LLM_PRECISE"];
|
|
18
|
+
});
|
|
19
|
+
const baseJudge = {
|
|
20
|
+
request: "add a route",
|
|
21
|
+
response: "Done, added the route.",
|
|
22
|
+
filesTouched: ["src/route.ts"],
|
|
23
|
+
commandsRun: [],
|
|
24
|
+
steps: 3,
|
|
25
|
+
};
|
|
26
|
+
describe("pickAdvisorModel", () => {
|
|
27
|
+
it("defaults to the precise tier when the executor is not precise", () => {
|
|
28
|
+
expect(pickAdvisorModel("anthropic/claude-sonnet-4.6")).toContain("opus");
|
|
29
|
+
});
|
|
30
|
+
it("falls back to a distinct tier when the executor is already precise", () => {
|
|
31
|
+
const advisor = pickAdvisorModel("anthropic/claude-opus-4.8");
|
|
32
|
+
expect(advisor).not.toBe("anthropic/claude-opus-4.8");
|
|
33
|
+
expect(advisor).toContain("sonnet");
|
|
34
|
+
});
|
|
35
|
+
it("honours an explicit override that differs from the executor", () => {
|
|
36
|
+
process.env["OXAGEN_LLM_ADVISOR"] = "openai/gpt-5";
|
|
37
|
+
expect(pickAdvisorModel("anthropic/claude-sonnet-4.6")).toBe("openai/gpt-5");
|
|
38
|
+
});
|
|
39
|
+
it("ignores an override equal to the executor (must stay distinct)", () => {
|
|
40
|
+
process.env["OXAGEN_LLM_ADVISOR"] = "anthropic/claude-sonnet-4.6";
|
|
41
|
+
const advisor = pickAdvisorModel("anthropic/claude-sonnet-4.6");
|
|
42
|
+
expect(advisor).not.toBe("anthropic/claude-sonnet-4.6");
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
describe("judgeCompleteness", () => {
|
|
46
|
+
it("returns the advisor's verdict, judged by a different model", async () => {
|
|
47
|
+
mockGen.mockResolvedValueOnce({
|
|
48
|
+
object: {
|
|
49
|
+
complete: false,
|
|
50
|
+
confidence: 90,
|
|
51
|
+
findings: ["no test added"],
|
|
52
|
+
remainingWork: ["add a test"],
|
|
53
|
+
reasoning: "tests were promised but absent",
|
|
54
|
+
},
|
|
55
|
+
usage: { inputTokens: 4, outputTokens: 2 },
|
|
56
|
+
});
|
|
57
|
+
const v = await judgeCompleteness({ ...baseJudge, executorModel: "anthropic/claude-sonnet-4.6" });
|
|
58
|
+
expect(v.complete).toBe(false);
|
|
59
|
+
expect(v.findings).toEqual(["no test added"]);
|
|
60
|
+
expect(v.model).not.toBe("anthropic/claude-sonnet-4.6");
|
|
61
|
+
expect(v.fallback).toBe(false);
|
|
62
|
+
expect(v.usage.inputTokens).toBe(4);
|
|
63
|
+
});
|
|
64
|
+
it("clamps confidence into range", async () => {
|
|
65
|
+
mockGen.mockResolvedValueOnce({
|
|
66
|
+
object: { complete: true, confidence: 200, findings: [], remainingWork: [], reasoning: "ok" },
|
|
67
|
+
usage: {},
|
|
68
|
+
});
|
|
69
|
+
const v = await judgeCompleteness({ ...baseJudge, executorModel: "anthropic/claude-haiku-4.5" });
|
|
70
|
+
expect(v.confidence).toBe(100);
|
|
71
|
+
});
|
|
72
|
+
it("heuristic flags claimed-change-with-no-activity when the advisor is unavailable", async () => {
|
|
73
|
+
mockGen.mockRejectedValueOnce(new Error("down"));
|
|
74
|
+
const v = await judgeCompleteness({
|
|
75
|
+
request: "add a route",
|
|
76
|
+
response: "I added the route and updated the tests.",
|
|
77
|
+
filesTouched: [],
|
|
78
|
+
commandsRun: [],
|
|
79
|
+
steps: 1,
|
|
80
|
+
executorModel: "anthropic/claude-sonnet-4.6",
|
|
81
|
+
});
|
|
82
|
+
expect(v.fallback).toBe(true);
|
|
83
|
+
expect(v.complete).toBe(false);
|
|
84
|
+
expect(v.findings.length).toBeGreaterThan(0);
|
|
85
|
+
});
|
|
86
|
+
it("heuristic abstains to low-confidence complete when there is no red flag", async () => {
|
|
87
|
+
mockGen.mockRejectedValueOnce(new Error("down"));
|
|
88
|
+
const v = await judgeCompleteness({
|
|
89
|
+
request: "explain how auth works",
|
|
90
|
+
response: "Auth uses sessions stored in Postgres.",
|
|
91
|
+
filesTouched: [],
|
|
92
|
+
commandsRun: [],
|
|
93
|
+
steps: 1,
|
|
94
|
+
executorModel: "anthropic/claude-sonnet-4.6",
|
|
95
|
+
});
|
|
96
|
+
expect(v.fallback).toBe(true);
|
|
97
|
+
expect(v.complete).toBe(true);
|
|
98
|
+
expect(v.confidence).toBe(30);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
describe("buildRevisionPrompt", () => {
|
|
102
|
+
it("renders findings and remaining work into actionable instructions", () => {
|
|
103
|
+
const verdict = {
|
|
104
|
+
complete: false,
|
|
105
|
+
confidence: 80,
|
|
106
|
+
findings: ["missing migration"],
|
|
107
|
+
remainingWork: ["write the migration"],
|
|
108
|
+
reasoning: "r",
|
|
109
|
+
model: "anthropic/claude-opus-4.8",
|
|
110
|
+
fallback: false,
|
|
111
|
+
usage: { inputTokens: 0, outputTokens: 0, costUsd: 0 },
|
|
112
|
+
};
|
|
113
|
+
const p = buildRevisionPrompt(verdict);
|
|
114
|
+
expect(p).toContain("NOT done");
|
|
115
|
+
expect(p).toContain("missing migration");
|
|
116
|
+
expect(p).toContain("write the migration");
|
|
117
|
+
});
|
|
118
|
+
it("omits empty sections cleanly", () => {
|
|
119
|
+
const verdict = {
|
|
120
|
+
complete: false,
|
|
121
|
+
confidence: 50,
|
|
122
|
+
findings: [],
|
|
123
|
+
remainingWork: [],
|
|
124
|
+
reasoning: "",
|
|
125
|
+
model: "m",
|
|
126
|
+
fallback: false,
|
|
127
|
+
usage: { inputTokens: 0, outputTokens: 0, costUsd: 0 },
|
|
128
|
+
};
|
|
129
|
+
const p = buildRevisionPrompt(verdict);
|
|
130
|
+
expect(p).not.toContain("Gaps found:");
|
|
131
|
+
expect(p).not.toContain("Remaining work:");
|
|
132
|
+
expect(p).toContain("Make the actual changes now");
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
//# sourceMappingURL=judge.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"judge.test.js","sourceRoot":"","sources":["../../../src/agent/__tests__/judge.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loop-errors.test.d.ts","sourceRoot":"","sources":["../../../src/agent/__tests__/loop-errors.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* normalizeAgentError turns raw AI Gateway / stream errors into one clean,
|
|
3
|
+
* actionable line — so dogfooding failures (no credits, bad key, rate limit)
|
|
4
|
+
* are legible instead of dumping the SDK's internal error object.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect } from "vitest";
|
|
7
|
+
import { normalizeAgentError } from "../loop.js";
|
|
8
|
+
/** Shape of an AI SDK gateway error: provider JSON lives on `responseBody`. */
|
|
9
|
+
function gatewayError(message, type = "error") {
|
|
10
|
+
const err = new Error("No output generated. Check the stream for errors.");
|
|
11
|
+
err.responseBody = JSON.stringify({
|
|
12
|
+
error: { message, type },
|
|
13
|
+
});
|
|
14
|
+
return err;
|
|
15
|
+
}
|
|
16
|
+
describe("normalizeAgentError", () => {
|
|
17
|
+
it("translates insufficient-funds into an add-credits message", () => {
|
|
18
|
+
const out = normalizeAgentError(gatewayError("A positive credit balance is required for all requests, including BYOK.", "insufficient_funds"));
|
|
19
|
+
expect(out.message).toContain("no credit balance");
|
|
20
|
+
expect(out.message).toContain("Vercel AI Gateway");
|
|
21
|
+
// The opaque SDK wrapper message must not leak through.
|
|
22
|
+
expect(out.message).not.toContain("No output generated");
|
|
23
|
+
});
|
|
24
|
+
it("translates a 401 into a key-check message", () => {
|
|
25
|
+
const out = normalizeAgentError(gatewayError("401 Unauthorized: invalid api key"));
|
|
26
|
+
expect(out.message).toContain("401");
|
|
27
|
+
expect(out.message).toContain("AI_GATEWAY_API_KEY");
|
|
28
|
+
});
|
|
29
|
+
it("translates a 429 into a rate-limit message", () => {
|
|
30
|
+
const out = normalizeAgentError(gatewayError("429 rate limit exceeded"));
|
|
31
|
+
expect(out.message).toContain("429");
|
|
32
|
+
expect(out.message.toLowerCase()).toContain("rate");
|
|
33
|
+
});
|
|
34
|
+
it("passes through an ordinary Error unchanged", () => {
|
|
35
|
+
const original = new Error("something specific broke");
|
|
36
|
+
expect(normalizeAgentError(original)).toBe(original);
|
|
37
|
+
});
|
|
38
|
+
it("wraps a non-Error value into an Error", () => {
|
|
39
|
+
const out = normalizeAgentError("plain string failure");
|
|
40
|
+
expect(out).toBeInstanceOf(Error);
|
|
41
|
+
expect(out.message).toContain("plain string failure");
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
//# sourceMappingURL=loop-errors.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loop-errors.test.js","sourceRoot":"","sources":["../../../src/agent/__tests__/loop-errors.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEjD,+EAA+E;AAC/E,SAAS,YAAY,CAAC,OAAe,EAAE,IAAI,GAAG,OAAO;IACnD,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;IAC1E,GAA2C,CAAC,YAAY,GAAG,IAAI,CAAC,SAAS,CAAC;QACzE,KAAK,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;KACzB,CAAC,CAAC;IACH,OAAO,GAAG,CAAC;AACb,CAAC;AAED,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACnE,MAAM,GAAG,GAAG,mBAAmB,CAC7B,YAAY,CACV,yEAAyE,EACzE,oBAAoB,CACrB,CACF,CAAC;QACF,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;QACnD,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;QACnD,wDAAwD;QACxD,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,GAAG,GAAG,mBAAmB,CAAC,YAAY,CAAC,mCAAmC,CAAC,CAAC,CAAC;QACnF,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACrC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,GAAG,GAAG,mBAAmB,CAAC,YAAY,CAAC,yBAAyB,CAAC,CAAC,CAAC;QACzE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACrC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,QAAQ,GAAG,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;QACvD,MAAM,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,GAAG,GAAG,mBAAmB,CAAC,sBAAsB,CAAC,CAAC;QACxD,MAAM,CAAC,GAAG,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;QAClC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-router.test.d.ts","sourceRoot":"","sources":["../../../src/agent/__tests__/model-router.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cost-aware model router — proves the classifier picks the cheapest sufficient
|
|
3
|
+
* tier, prices token usage off the vendored rate card, and honours manual pins.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
6
|
+
// Pin readConfig to empty so routeModel's "auto" path is deterministic and never
|
|
7
|
+
// picks up a developer's real ~/.config/oxagen/config.json `model`.
|
|
8
|
+
vi.mock("../../lib/config.js", () => ({ readConfig: () => ({}) }));
|
|
9
|
+
import { classifyTier, routeModel, rateFor, estimateCostUsd, formatUsd, accumulateUsage, tierForSlug, tierLabel, modelForTier, } from "../model-router.js";
|
|
10
|
+
import { emptyUsage } from "../fleet/types.js";
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
delete process.env["OXAGEN_MODEL"];
|
|
13
|
+
delete process.env["OXAGEN_LLM_FAST"];
|
|
14
|
+
delete process.env["OXAGEN_LLM_BALANCED"];
|
|
15
|
+
delete process.env["OXAGEN_LLM_PRECISE"];
|
|
16
|
+
});
|
|
17
|
+
describe("classifyTier", () => {
|
|
18
|
+
it("escalates high-stakes domains to precise regardless of size", () => {
|
|
19
|
+
for (const text of [
|
|
20
|
+
"fix the login session token bug",
|
|
21
|
+
"add a stripe refund to billing",
|
|
22
|
+
"write the RLS tenant migration",
|
|
23
|
+
"rework the auth architecture",
|
|
24
|
+
]) {
|
|
25
|
+
expect(classifyTier({ text }).tier).toBe("precise");
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
it("pins trivial, mechanical single-file work to fast", () => {
|
|
29
|
+
expect(classifyTier({ text: "rename the variable foo to bar" }).tier).toBe("fast");
|
|
30
|
+
expect(classifyTier({ text: "fix a typo in the readme" }).tier).toBe("fast");
|
|
31
|
+
expect(classifyTier({ text: "update the changelog" }).tier).toBe("fast");
|
|
32
|
+
});
|
|
33
|
+
it("uses balanced for non-trivial design/debugging language", () => {
|
|
34
|
+
expect(classifyTier({ text: "refactor the streaming pipeline" }).tier).toBe("balanced");
|
|
35
|
+
expect(classifyTier({ text: "debug why the parser drops the last token" }).tier).toBe("balanced");
|
|
36
|
+
});
|
|
37
|
+
it("escalates on breadth: many files or cross-package", () => {
|
|
38
|
+
expect(classifyTier({ text: "touch up styles", fileCount: 10 }).tier).toBe("precise");
|
|
39
|
+
expect(classifyTier({ text: "wire it up", fileCount: 4, crossPackage: true }).tier).toBe("precise");
|
|
40
|
+
expect(classifyTier({ text: "wire it up", fileCount: 4 }).tier).toBe("balanced");
|
|
41
|
+
expect(classifyTier({ text: "small thing", crossPackage: true }).tier).toBe("balanced");
|
|
42
|
+
});
|
|
43
|
+
it("treats a short unqualified ask as fast and a long one as balanced", () => {
|
|
44
|
+
expect(classifyTier({ text: "add a helper" }).tier).toBe("fast");
|
|
45
|
+
expect(classifyTier({
|
|
46
|
+
text: "go through the module and make the surrounding code more readable overall please",
|
|
47
|
+
}).tier).toBe("balanced");
|
|
48
|
+
});
|
|
49
|
+
it("always returns a concrete model slug and a rationale", () => {
|
|
50
|
+
const d = classifyTier({ text: "rename foo" });
|
|
51
|
+
expect(d.model).toContain("haiku");
|
|
52
|
+
expect(d.rationale.length).toBeGreaterThan(0);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe("rate card + cost", () => {
|
|
56
|
+
it("matches the model family by prefix", () => {
|
|
57
|
+
expect(rateFor("anthropic/claude-opus-4.8").inputPer1M).toBe(15);
|
|
58
|
+
expect(rateFor("anthropic/claude-sonnet-4.6").inputPer1M).toBe(3);
|
|
59
|
+
expect(rateFor("anthropic/claude-haiku-4.5").outputPer1M).toBe(5);
|
|
60
|
+
expect(rateFor("openai/gpt-5.2").inputPer1M).toBe(1.25);
|
|
61
|
+
});
|
|
62
|
+
it("falls back to sonnet pricing for an unknown model (never zero-charge)", () => {
|
|
63
|
+
expect(rateFor("acme/mystery-1").inputPer1M).toBe(3);
|
|
64
|
+
});
|
|
65
|
+
it("estimates cost from token usage", () => {
|
|
66
|
+
// 1M in + 1M out on opus = $15 + $75.
|
|
67
|
+
expect(estimateCostUsd("anthropic/claude-opus-4.8", {
|
|
68
|
+
inputTokens: 1_000_000,
|
|
69
|
+
outputTokens: 1_000_000,
|
|
70
|
+
})).toBeCloseTo(90, 6);
|
|
71
|
+
});
|
|
72
|
+
it("formats USD compactly across magnitudes", () => {
|
|
73
|
+
expect(formatUsd(0)).toBe("$0");
|
|
74
|
+
expect(formatUsd(0.00005)).toBe("<$0.0001");
|
|
75
|
+
expect(formatUsd(0.0042)).toBe("$0.0042");
|
|
76
|
+
expect(formatUsd(1.2)).toBe("$1.20");
|
|
77
|
+
});
|
|
78
|
+
it("accumulates usage with per-call pricing", () => {
|
|
79
|
+
let total = emptyUsage();
|
|
80
|
+
total = accumulateUsage(total, "anthropic/claude-haiku-4.5", {
|
|
81
|
+
inputTokens: 1_000_000,
|
|
82
|
+
outputTokens: 0,
|
|
83
|
+
});
|
|
84
|
+
expect(total.inputTokens).toBe(1_000_000);
|
|
85
|
+
expect(total.costUsd).toBeCloseTo(1, 6);
|
|
86
|
+
total = accumulateUsage(total, "anthropic/claude-opus-4.8", {
|
|
87
|
+
outputTokens: 1_000_000,
|
|
88
|
+
});
|
|
89
|
+
expect(total.outputTokens).toBe(1_000_000);
|
|
90
|
+
expect(total.costUsd).toBeCloseTo(1 + 75, 6);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
describe("routeModel + tier helpers", () => {
|
|
94
|
+
it("honours an explicit override and reads its tier from the slug", () => {
|
|
95
|
+
const d = routeModel({ text: "rename foo" }, "anthropic/claude-opus-4.8");
|
|
96
|
+
expect(d.model).toBe("anthropic/claude-opus-4.8");
|
|
97
|
+
expect(d.tier).toBe("precise");
|
|
98
|
+
expect(d.rationale).toBe("pinned model");
|
|
99
|
+
});
|
|
100
|
+
it("honours OXAGEN_MODEL as a pin", () => {
|
|
101
|
+
process.env["OXAGEN_MODEL"] = "anthropic/claude-haiku-4.5";
|
|
102
|
+
expect(routeModel({ text: "rework the auth layer" }).model).toBe("anthropic/claude-haiku-4.5");
|
|
103
|
+
});
|
|
104
|
+
it("auto-routes when nothing is pinned", () => {
|
|
105
|
+
expect(routeModel({ text: "fix the billing webhook signature" }).tier).toBe("precise");
|
|
106
|
+
});
|
|
107
|
+
it("maps slugs to tiers and tiers to labels/slugs", () => {
|
|
108
|
+
expect(tierForSlug("anthropic/claude-opus-4.8")).toBe("precise");
|
|
109
|
+
expect(tierForSlug("anthropic/claude-haiku-4.5")).toBe("fast");
|
|
110
|
+
expect(tierForSlug("openai/gpt-4o-mini")).toBe("fast");
|
|
111
|
+
expect(tierForSlug("anthropic/claude-sonnet-4.6")).toBe("balanced");
|
|
112
|
+
expect(tierLabel("fast")).toBe("Haiku");
|
|
113
|
+
expect(tierLabel("balanced")).toBe("Sonnet");
|
|
114
|
+
expect(tierLabel("precise")).toBe("Opus");
|
|
115
|
+
expect(modelForTier("precise")).toContain("opus");
|
|
116
|
+
});
|
|
117
|
+
it("respects env overrides for tier slugs", () => {
|
|
118
|
+
process.env["OXAGEN_LLM_FAST"] = "anthropic/claude-haiku-9";
|
|
119
|
+
expect(modelForTier("fast")).toBe("anthropic/claude-haiku-9");
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
//# sourceMappingURL=model-router.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-router.test.js","sourceRoot":"","sources":["../../../src/agent/__tests__/model-router.test.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9D,iFAAiF;AACjF,oEAAoE;AACpE,EAAE,CAAC,IAAI,CAAC,qBAAqB,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAEnE,OAAO,EACL,YAAY,EACZ,UAAU,EACV,OAAO,EACP,eAAe,EACf,SAAS,EACT,eAAe,EACf,WAAW,EACX,SAAS,EACT,YAAY,GACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/C,UAAU,CAAC,GAAG,EAAE;IACd,OAAO,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IACnC,OAAO,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACtC,OAAO,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;IAC1C,OAAO,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;AAC3C,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACrE,KAAK,MAAM,IAAI,IAAI;YACjB,iCAAiC;YACjC,gCAAgC;YAChC,gCAAgC;YAChC,8BAA8B;SAC/B,EAAE,CAAC;YACF,MAAM,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACtD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,gCAAgC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnF,MAAM,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,0BAA0B,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC7E,MAAM,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,sBAAsB,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACjE,MAAM,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,iCAAiC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACxF,MAAM,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,2CAA2C,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CACnF,UAAU,CACX,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACtF,MAAM,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CACtF,SAAS,CACV,CAAC;QACF,MAAM,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACjF,MAAM,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC1F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mEAAmE,EAAE,GAAG,EAAE;QAC3E,MAAM,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACjE,MAAM,CACJ,YAAY,CAAC;YACX,IAAI,EAAE,kFAAkF;SACzF,CAAC,CAAC,IAAI,CACR,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,MAAM,CAAC,GAAG,YAAY,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC;QAC/C,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACnC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,CAAC,OAAO,CAAC,2BAA2B,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjE,MAAM,CAAC,OAAO,CAAC,6BAA6B,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClE,MAAM,CAAC,OAAO,CAAC,4BAA4B,CAAC,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClE,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uEAAuE,EAAE,GAAG,EAAE;QAC/E,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,sCAAsC;QACtC,MAAM,CACJ,eAAe,CAAC,2BAA2B,EAAE;YAC3C,WAAW,EAAE,SAAS;YACtB,YAAY,EAAE,SAAS;SACxB,CAAC,CACH,CAAC,WAAW,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC5C,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC1C,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,IAAI,KAAK,GAAG,UAAU,EAAE,CAAC;QACzB,KAAK,GAAG,eAAe,CAAC,KAAK,EAAE,4BAA4B,EAAE;YAC3D,WAAW,EAAE,SAAS;YACtB,YAAY,EAAE,CAAC;SAChB,CAAC,CAAC;QACH,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC1C,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACxC,KAAK,GAAG,eAAe,CAAC,KAAK,EAAE,2BAA2B,EAAE;YAC1D,YAAY,EAAE,SAAS;SACxB,CAAC,CAAC;QACH,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC3C,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,WAAW,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,2BAA2B,EAAE,GAAG,EAAE;IACzC,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACvE,MAAM,CAAC,GAAG,UAAU,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,2BAA2B,CAAC,CAAC;QAC1E,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;QAClD,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC/B,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,GAAG,4BAA4B,CAAC;QAC3D,MAAM,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,uBAAuB,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAC9D,4BAA4B,CAC7B,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,mCAAmC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACzF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,CAAC,WAAW,CAAC,2BAA2B,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACjE,MAAM,CAAC,WAAW,CAAC,4BAA4B,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC/D,MAAM,CAAC,WAAW,CAAC,oBAAoB,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACvD,MAAM,CAAC,WAAW,CAAC,6BAA6B,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACpE,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACxC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC7C,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1C,MAAM,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,GAAG,0BAA0B,CAAC;QAC5D,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orchestrator-isolation.test.d.ts","sourceRoot":"","sources":["../../../src/agent/__tests__/orchestrator-isolation.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fleet ↔ isolation wiring — proves the orchestrator drives the isolation
|
|
3
|
+
* protocol correctly without touching git: the agent runs in the spawned
|
|
4
|
+
* worktree (not the shared tree), finished work is checkpointed then integrated,
|
|
5
|
+
* conflicts are surfaced and the worktree kept, and — crucially — tasks that
|
|
6
|
+
* predict the *same* files run in parallel under isolation instead of being
|
|
7
|
+
* serialized. The git layer itself is covered in git-isolation.test.ts.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, it, expect, vi } from "vitest";
|
|
10
|
+
vi.mock("../prompt-enhancer.js", () => ({
|
|
11
|
+
enhancePrompt: async ({ prompt }) => ({
|
|
12
|
+
prompt,
|
|
13
|
+
context: "",
|
|
14
|
+
resolved: [],
|
|
15
|
+
lessons: [],
|
|
16
|
+
}),
|
|
17
|
+
}));
|
|
18
|
+
import { Fleet } from "../fleet/orchestrator.js";
|
|
19
|
+
function task(id, over = {}) {
|
|
20
|
+
return {
|
|
21
|
+
id,
|
|
22
|
+
title: id,
|
|
23
|
+
description: id,
|
|
24
|
+
status: "queued",
|
|
25
|
+
dependsOn: [],
|
|
26
|
+
files: [],
|
|
27
|
+
tier: "fast",
|
|
28
|
+
model: "anthropic/claude-haiku-4.5",
|
|
29
|
+
createdAt: 1,
|
|
30
|
+
usage: { inputTokens: 0, outputTokens: 0, costUsd: 0 },
|
|
31
|
+
...over,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function plan(tasks) {
|
|
35
|
+
return { id: "p1", goal: "g", createdAt: 1, tasks, status: "draft" };
|
|
36
|
+
}
|
|
37
|
+
function deferred() {
|
|
38
|
+
let resolve;
|
|
39
|
+
const promise = new Promise((res) => {
|
|
40
|
+
resolve = res;
|
|
41
|
+
});
|
|
42
|
+
return { promise, resolve };
|
|
43
|
+
}
|
|
44
|
+
/** Records the protocol calls and returns canned integration results. */
|
|
45
|
+
function fakeIsolation(integrate) {
|
|
46
|
+
const calls = [];
|
|
47
|
+
const cp = (id) => ({ taskId: id, hash: "h", ref: "r", message: "m" });
|
|
48
|
+
return {
|
|
49
|
+
calls,
|
|
50
|
+
init: async () => void calls.push("init"),
|
|
51
|
+
spawn: async (id) => {
|
|
52
|
+
calls.push(`spawn:${id}`);
|
|
53
|
+
return `/wt/${id}`;
|
|
54
|
+
},
|
|
55
|
+
checkpoint: async (id) => {
|
|
56
|
+
calls.push(`checkpoint:${id}`);
|
|
57
|
+
return cp(id);
|
|
58
|
+
},
|
|
59
|
+
recordedCommits: () => [],
|
|
60
|
+
integrate: async (id) => {
|
|
61
|
+
calls.push(`integrate:${id}`);
|
|
62
|
+
return integrate?.(id) ?? { ok: true, commit: "c", taskBranch: `b/${id}` };
|
|
63
|
+
},
|
|
64
|
+
integrationRef: () => null,
|
|
65
|
+
dispose: async (id, opts) => void calls.push(`dispose:${id}:keep=${opts?.keep ?? false}`),
|
|
66
|
+
cleanupAll: async () => void calls.push("cleanupAll"),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
describe("Fleet with git isolation", () => {
|
|
70
|
+
it("runs the agent in its worktree, then checkpoints, integrates, disposes", async () => {
|
|
71
|
+
const iso = fakeIsolation();
|
|
72
|
+
let seenCwd = "";
|
|
73
|
+
const runner = async (o) => {
|
|
74
|
+
seenCwd = o.cwd;
|
|
75
|
+
return { text: "done", steps: 1, usage: { inputTokens: 1, outputTokens: 1 } };
|
|
76
|
+
};
|
|
77
|
+
const fleet = new Fleet({ cwd: "/repo", isolation: iso, runner, memory: null });
|
|
78
|
+
fleet.loadPlan(plan([task("t1")]));
|
|
79
|
+
await fleet.start();
|
|
80
|
+
expect(seenCwd).toBe("/wt/t1"); // ran in the worktree, not /repo
|
|
81
|
+
expect(iso.calls).toEqual([
|
|
82
|
+
"spawn:t1",
|
|
83
|
+
"checkpoint:t1",
|
|
84
|
+
"integrate:t1",
|
|
85
|
+
"dispose:t1:keep=false",
|
|
86
|
+
]);
|
|
87
|
+
expect(fleet.snapshot().agents[0]?.status).toBe("done");
|
|
88
|
+
});
|
|
89
|
+
it("surfaces an integration conflict and keeps the worktree", async () => {
|
|
90
|
+
const iso = fakeIsolation(() => ({
|
|
91
|
+
ok: false,
|
|
92
|
+
conflicts: ["base.txt"],
|
|
93
|
+
taskBranch: "b/t1",
|
|
94
|
+
taskTip: "tip",
|
|
95
|
+
}));
|
|
96
|
+
const events = [];
|
|
97
|
+
const runner = async () => ({ text: "x", steps: 1, usage: {} });
|
|
98
|
+
const fleet = new Fleet({ cwd: "/repo", isolation: iso, runner, memory: null });
|
|
99
|
+
fleet.on("conflict", (e) => events.push(e));
|
|
100
|
+
fleet.loadPlan(plan([task("t1")]));
|
|
101
|
+
await fleet.start();
|
|
102
|
+
expect(events).toHaveLength(1);
|
|
103
|
+
expect(events[0]?.conflicts).toContain("base.txt");
|
|
104
|
+
expect(iso.calls).toContain("dispose:t1:keep=true"); // kept for resolution
|
|
105
|
+
const snap = fleet.snapshot().agents[0];
|
|
106
|
+
expect(snap?.status).toBe("done");
|
|
107
|
+
expect(snap?.error).toMatch(/integration conflict/);
|
|
108
|
+
});
|
|
109
|
+
it("runs same-file tasks in parallel under isolation (no file-lock serialization)", async () => {
|
|
110
|
+
const iso = fakeIsolation();
|
|
111
|
+
let active = 0;
|
|
112
|
+
let maxActive = 0;
|
|
113
|
+
const release = deferred();
|
|
114
|
+
const bothRunning = deferred();
|
|
115
|
+
const runner = async () => {
|
|
116
|
+
active++;
|
|
117
|
+
maxActive = Math.max(maxActive, active);
|
|
118
|
+
if (active === 2)
|
|
119
|
+
bothRunning.resolve();
|
|
120
|
+
await release.promise;
|
|
121
|
+
active--;
|
|
122
|
+
return { text: "x", steps: 1, usage: {} };
|
|
123
|
+
};
|
|
124
|
+
const fleet = new Fleet({ cwd: "/repo", concurrency: 4, isolation: iso, runner, memory: null });
|
|
125
|
+
// Both predict the SAME file — without isolation this would serialize.
|
|
126
|
+
fleet.loadPlan(plan([task("t1", { files: ["x.ts"] }), task("t2", { files: ["x.ts"] })]));
|
|
127
|
+
const done = fleet.start();
|
|
128
|
+
await bothRunning.promise;
|
|
129
|
+
expect(maxActive).toBe(2);
|
|
130
|
+
release.resolve();
|
|
131
|
+
await done;
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
//# sourceMappingURL=orchestrator-isolation.test.js.map
|