swarm-engine 1.0.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/AGENTS.md +72 -0
- package/CLAUDE.md +89 -0
- package/LICENSE +21 -0
- package/README.md +235 -0
- package/agents/accessibility-reviewer.md +118 -0
- package/agents/api-contract-reviewer.md +99 -0
- package/agents/concurrency-reviewer.md +111 -0
- package/agents/data-integrity-reviewer.md +103 -0
- package/agents/debugger.md +99 -0
- package/agents/dependency-reviewer.md +115 -0
- package/agents/devils-advocate.md +94 -0
- package/agents/documentation-reviewer.md +114 -0
- package/agents/documenter.md +78 -0
- package/agents/error-handling-reviewer.md +113 -0
- package/agents/grounding.md +99 -0
- package/agents/guardian.md +87 -0
- package/agents/implementer.md +141 -0
- package/agents/integrator.md +95 -0
- package/agents/judge.md +79 -0
- package/agents/librarian.md +90 -0
- package/agents/orchestrator.md +331 -0
- package/agents/performance-reviewer.md +93 -0
- package/agents/planner.md +106 -0
- package/agents/refactorer.md +92 -0
- package/agents/researcher.md +97 -0
- package/agents/reviewer.md +117 -0
- package/agents/security-reviewer.md +107 -0
- package/agents/sentinel.md +92 -0
- package/agents/tester.md +93 -0
- package/agents/testing-reviewer.md +112 -0
- package/commands/diff-review.md +64 -0
- package/commands/fix-pr.md +78 -0
- package/commands/red-team.md +82 -0
- package/commands/research.md +59 -0
- package/commands/resume.md +80 -0
- package/commands/review-cycle.md +123 -0
- package/commands/swarm-setup.md +28 -0
- package/commands/swarm.md +126 -0
- package/commands/tdd.md +91 -0
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +3 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/commands/acp.d.ts +3 -0
- package/dist/cli/commands/acp.d.ts.map +1 -0
- package/dist/cli/commands/acp.js +29 -0
- package/dist/cli/commands/acp.js.map +1 -0
- package/dist/cli/commands/agents.d.ts +3 -0
- package/dist/cli/commands/agents.d.ts.map +1 -0
- package/dist/cli/commands/agents.js +344 -0
- package/dist/cli/commands/agents.js.map +1 -0
- package/dist/cli/commands/backends.d.ts +3 -0
- package/dist/cli/commands/backends.d.ts.map +1 -0
- package/dist/cli/commands/backends.js +22 -0
- package/dist/cli/commands/backends.js.map +1 -0
- package/dist/cli/commands/completions.d.ts +3 -0
- package/dist/cli/commands/completions.d.ts.map +1 -0
- package/dist/cli/commands/completions.js +85 -0
- package/dist/cli/commands/completions.js.map +1 -0
- package/dist/cli/commands/compound.d.ts +3 -0
- package/dist/cli/commands/compound.d.ts.map +1 -0
- package/dist/cli/commands/compound.js +119 -0
- package/dist/cli/commands/compound.js.map +1 -0
- package/dist/cli/commands/configure.d.ts +3 -0
- package/dist/cli/commands/configure.d.ts.map +1 -0
- package/dist/cli/commands/configure.js +151 -0
- package/dist/cli/commands/configure.js.map +1 -0
- package/dist/cli/commands/convert.d.ts +15 -0
- package/dist/cli/commands/convert.d.ts.map +1 -0
- package/dist/cli/commands/convert.js +218 -0
- package/dist/cli/commands/convert.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +3 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/doctor.js +96 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/init.d.ts +3 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +154 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/learn.d.ts +3 -0
- package/dist/cli/commands/learn.d.ts.map +1 -0
- package/dist/cli/commands/learn.js +163 -0
- package/dist/cli/commands/learn.js.map +1 -0
- package/dist/cli/commands/mcp.d.ts +3 -0
- package/dist/cli/commands/mcp.d.ts.map +1 -0
- package/dist/cli/commands/mcp.js +89 -0
- package/dist/cli/commands/mcp.js.map +1 -0
- package/dist/cli/commands/memory.d.ts +3 -0
- package/dist/cli/commands/memory.d.ts.map +1 -0
- package/dist/cli/commands/memory.js +134 -0
- package/dist/cli/commands/memory.js.map +1 -0
- package/dist/cli/commands/orchestrate.d.ts +3 -0
- package/dist/cli/commands/orchestrate.d.ts.map +1 -0
- package/dist/cli/commands/orchestrate.js +237 -0
- package/dist/cli/commands/orchestrate.js.map +1 -0
- package/dist/cli/commands/patterns.d.ts +3 -0
- package/dist/cli/commands/patterns.d.ts.map +1 -0
- package/dist/cli/commands/patterns.js +25 -0
- package/dist/cli/commands/patterns.js.map +1 -0
- package/dist/cli/commands/plan.d.ts +3 -0
- package/dist/cli/commands/plan.d.ts.map +1 -0
- package/dist/cli/commands/plan.js +77 -0
- package/dist/cli/commands/plan.js.map +1 -0
- package/dist/cli/commands/plugin.d.ts +3 -0
- package/dist/cli/commands/plugin.d.ts.map +1 -0
- package/dist/cli/commands/plugin.js +124 -0
- package/dist/cli/commands/plugin.js.map +1 -0
- package/dist/cli/commands/resume.d.ts +3 -0
- package/dist/cli/commands/resume.d.ts.map +1 -0
- package/dist/cli/commands/resume.js +55 -0
- package/dist/cli/commands/resume.js.map +1 -0
- package/dist/cli/commands/run.d.ts +3 -0
- package/dist/cli/commands/run.d.ts.map +1 -0
- package/dist/cli/commands/run.js +78 -0
- package/dist/cli/commands/run.js.map +1 -0
- package/dist/cli/commands/share.d.ts +3 -0
- package/dist/cli/commands/share.d.ts.map +1 -0
- package/dist/cli/commands/share.js +34 -0
- package/dist/cli/commands/share.js.map +1 -0
- package/dist/cli/commands/status.d.ts +3 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +148 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/template.d.ts +3 -0
- package/dist/cli/commands/template.d.ts.map +1 -0
- package/dist/cli/commands/template.js +213 -0
- package/dist/cli/commands/template.js.map +1 -0
- package/dist/cli/commands/vault.d.ts +3 -0
- package/dist/cli/commands/vault.d.ts.map +1 -0
- package/dist/cli/commands/vault.js +402 -0
- package/dist/cli/commands/vault.js.map +1 -0
- package/dist/cli/commands/verify.d.ts +3 -0
- package/dist/cli/commands/verify.d.ts.map +1 -0
- package/dist/cli/commands/verify.js +38 -0
- package/dist/cli/commands/verify.js.map +1 -0
- package/dist/cli/commands/version.d.ts +3 -0
- package/dist/cli/commands/version.d.ts.map +1 -0
- package/dist/cli/commands/version.js +19 -0
- package/dist/cli/commands/version.js.map +1 -0
- package/dist/cli/commands/watch.d.ts +3 -0
- package/dist/cli/commands/watch.d.ts.map +1 -0
- package/dist/cli/commands/watch.js +179 -0
- package/dist/cli/commands/watch.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +118 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/checkpoint.d.ts +35 -0
- package/dist/core/checkpoint.d.ts.map +1 -0
- package/dist/core/checkpoint.js +80 -0
- package/dist/core/checkpoint.js.map +1 -0
- package/dist/core/event-bus.d.ts +41 -0
- package/dist/core/event-bus.d.ts.map +1 -0
- package/dist/core/event-bus.js +115 -0
- package/dist/core/event-bus.js.map +1 -0
- package/dist/core/lifecycle.d.ts +30 -0
- package/dist/core/lifecycle.d.ts.map +1 -0
- package/dist/core/lifecycle.js +72 -0
- package/dist/core/lifecycle.js.map +1 -0
- package/dist/core/patterns.d.ts +43 -0
- package/dist/core/patterns.d.ts.map +1 -0
- package/dist/core/patterns.js +372 -0
- package/dist/core/patterns.js.map +1 -0
- package/dist/core/permissions.d.ts +40 -0
- package/dist/core/permissions.d.ts.map +1 -0
- package/dist/core/permissions.js +113 -0
- package/dist/core/permissions.js.map +1 -0
- package/dist/core/registry.d.ts +80 -0
- package/dist/core/registry.d.ts.map +1 -0
- package/dist/core/registry.js +308 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/core/snapshots.d.ts +20 -0
- package/dist/core/snapshots.d.ts.map +1 -0
- package/dist/core/snapshots.js +73 -0
- package/dist/core/snapshots.js.map +1 -0
- package/dist/core/types.d.ts +168 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +33 -0
- package/dist/core/types.js.map +1 -0
- package/dist/hooks/cli.d.ts +11 -0
- package/dist/hooks/cli.d.ts.map +1 -0
- package/dist/hooks/cli.js +32 -0
- package/dist/hooks/cli.js.map +1 -0
- package/dist/hooks/index.d.ts +23 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +58 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/index.d.ts +82 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/index.d.ts +105 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +298 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/schema.d.ts +6 -0
- package/dist/memory/schema.d.ts.map +1 -0
- package/dist/memory/schema.js +71 -0
- package/dist/memory/schema.js.map +1 -0
- package/dist/plugin/index.d.ts +6 -0
- package/dist/plugin/index.d.ts.map +1 -0
- package/dist/plugin/index.js +182 -0
- package/dist/plugin/index.js.map +1 -0
- package/dist/runtime/acp.d.ts +41 -0
- package/dist/runtime/acp.d.ts.map +1 -0
- package/dist/runtime/acp.js +137 -0
- package/dist/runtime/acp.js.map +1 -0
- package/dist/runtime/adaptive.d.ts +34 -0
- package/dist/runtime/adaptive.d.ts.map +1 -0
- package/dist/runtime/adaptive.js +229 -0
- package/dist/runtime/adaptive.js.map +1 -0
- package/dist/runtime/autonomy.d.ts +21 -0
- package/dist/runtime/autonomy.d.ts.map +1 -0
- package/dist/runtime/autonomy.js +74 -0
- package/dist/runtime/autonomy.js.map +1 -0
- package/dist/runtime/backends/claude.d.ts +9 -0
- package/dist/runtime/backends/claude.d.ts.map +1 -0
- package/dist/runtime/backends/claude.js +134 -0
- package/dist/runtime/backends/claude.js.map +1 -0
- package/dist/runtime/backends/codex.d.ts +9 -0
- package/dist/runtime/backends/codex.d.ts.map +1 -0
- package/dist/runtime/backends/codex.js +132 -0
- package/dist/runtime/backends/codex.js.map +1 -0
- package/dist/runtime/backends/gemini.d.ts +9 -0
- package/dist/runtime/backends/gemini.d.ts.map +1 -0
- package/dist/runtime/backends/gemini.js +103 -0
- package/dist/runtime/backends/gemini.js.map +1 -0
- package/dist/runtime/backends/index.d.ts +17 -0
- package/dist/runtime/backends/index.d.ts.map +1 -0
- package/dist/runtime/backends/index.js +39 -0
- package/dist/runtime/backends/index.js.map +1 -0
- package/dist/runtime/backends/mock.d.ts +21 -0
- package/dist/runtime/backends/mock.d.ts.map +1 -0
- package/dist/runtime/backends/mock.js +46 -0
- package/dist/runtime/backends/mock.js.map +1 -0
- package/dist/runtime/backends/types.d.ts +34 -0
- package/dist/runtime/backends/types.d.ts.map +1 -0
- package/dist/runtime/backends/types.js +2 -0
- package/dist/runtime/backends/types.js.map +1 -0
- package/dist/runtime/backends/vercel-ai.d.ts +14 -0
- package/dist/runtime/backends/vercel-ai.d.ts.map +1 -0
- package/dist/runtime/backends/vercel-ai.js +137 -0
- package/dist/runtime/backends/vercel-ai.js.map +1 -0
- package/dist/runtime/cache-optimizer.d.ts +37 -0
- package/dist/runtime/cache-optimizer.d.ts.map +1 -0
- package/dist/runtime/cache-optimizer.js +54 -0
- package/dist/runtime/cache-optimizer.js.map +1 -0
- package/dist/runtime/cascade.d.ts +26 -0
- package/dist/runtime/cascade.d.ts.map +1 -0
- package/dist/runtime/cascade.js +54 -0
- package/dist/runtime/cascade.js.map +1 -0
- package/dist/runtime/chunker.d.ts +36 -0
- package/dist/runtime/chunker.d.ts.map +1 -0
- package/dist/runtime/chunker.js +210 -0
- package/dist/runtime/chunker.js.map +1 -0
- package/dist/runtime/compaction.d.ts +22 -0
- package/dist/runtime/compaction.d.ts.map +1 -0
- package/dist/runtime/compaction.js +36 -0
- package/dist/runtime/compaction.js.map +1 -0
- package/dist/runtime/compounder.d.ts +66 -0
- package/dist/runtime/compounder.d.ts.map +1 -0
- package/dist/runtime/compounder.js +276 -0
- package/dist/runtime/compounder.js.map +1 -0
- package/dist/runtime/cost-model.d.ts +24 -0
- package/dist/runtime/cost-model.d.ts.map +1 -0
- package/dist/runtime/cost-model.js +120 -0
- package/dist/runtime/cost-model.js.map +1 -0
- package/dist/runtime/distiller.d.ts +21 -0
- package/dist/runtime/distiller.d.ts.map +1 -0
- package/dist/runtime/distiller.js +70 -0
- package/dist/runtime/distiller.js.map +1 -0
- package/dist/runtime/engine.d.ts +123 -0
- package/dist/runtime/engine.d.ts.map +1 -0
- package/dist/runtime/engine.js +969 -0
- package/dist/runtime/engine.js.map +1 -0
- package/dist/runtime/executor.d.ts +71 -0
- package/dist/runtime/executor.d.ts.map +1 -0
- package/dist/runtime/executor.js +283 -0
- package/dist/runtime/executor.js.map +1 -0
- package/dist/runtime/heuristics.d.ts +33 -0
- package/dist/runtime/heuristics.d.ts.map +1 -0
- package/dist/runtime/heuristics.js +188 -0
- package/dist/runtime/heuristics.js.map +1 -0
- package/dist/runtime/living-spec.d.ts +34 -0
- package/dist/runtime/living-spec.d.ts.map +1 -0
- package/dist/runtime/living-spec.js +91 -0
- package/dist/runtime/living-spec.js.map +1 -0
- package/dist/runtime/lsp.d.ts +50 -0
- package/dist/runtime/lsp.d.ts.map +1 -0
- package/dist/runtime/lsp.js +110 -0
- package/dist/runtime/lsp.js.map +1 -0
- package/dist/runtime/mcp.d.ts +27 -0
- package/dist/runtime/mcp.d.ts.map +1 -0
- package/dist/runtime/mcp.js +154 -0
- package/dist/runtime/mcp.js.map +1 -0
- package/dist/runtime/model-router.d.ts +22 -0
- package/dist/runtime/model-router.d.ts.map +1 -0
- package/dist/runtime/model-router.js +94 -0
- package/dist/runtime/model-router.js.map +1 -0
- package/dist/runtime/panes.d.ts +76 -0
- package/dist/runtime/panes.d.ts.map +1 -0
- package/dist/runtime/panes.js +279 -0
- package/dist/runtime/panes.js.map +1 -0
- package/dist/runtime/plan-search.d.ts +41 -0
- package/dist/runtime/plan-search.d.ts.map +1 -0
- package/dist/runtime/plan-search.js +140 -0
- package/dist/runtime/plan-search.js.map +1 -0
- package/dist/runtime/plugins.d.ts +59 -0
- package/dist/runtime/plugins.d.ts.map +1 -0
- package/dist/runtime/plugins.js +121 -0
- package/dist/runtime/plugins.js.map +1 -0
- package/dist/runtime/reflexion.d.ts +22 -0
- package/dist/runtime/reflexion.d.ts.map +1 -0
- package/dist/runtime/reflexion.js +85 -0
- package/dist/runtime/reflexion.js.map +1 -0
- package/dist/runtime/review-schema.d.ts +75 -0
- package/dist/runtime/review-schema.d.ts.map +1 -0
- package/dist/runtime/review-schema.js +223 -0
- package/dist/runtime/review-schema.js.map +1 -0
- package/dist/runtime/rewriter.d.ts +8 -0
- package/dist/runtime/rewriter.d.ts.map +1 -0
- package/dist/runtime/rewriter.js +81 -0
- package/dist/runtime/rewriter.js.map +1 -0
- package/dist/runtime/sharing.d.ts +15 -0
- package/dist/runtime/sharing.d.ts.map +1 -0
- package/dist/runtime/sharing.js +48 -0
- package/dist/runtime/sharing.js.map +1 -0
- package/dist/runtime/stats.d.ts +53 -0
- package/dist/runtime/stats.d.ts.map +1 -0
- package/dist/runtime/stats.js +160 -0
- package/dist/runtime/stats.js.map +1 -0
- package/dist/runtime/templates.d.ts +77 -0
- package/dist/runtime/templates.d.ts.map +1 -0
- package/dist/runtime/templates.js +221 -0
- package/dist/runtime/templates.js.map +1 -0
- package/dist/runtime/traces.d.ts +60 -0
- package/dist/runtime/traces.d.ts.map +1 -0
- package/dist/runtime/traces.js +166 -0
- package/dist/runtime/traces.js.map +1 -0
- package/dist/runtime/verifier.d.ts +54 -0
- package/dist/runtime/verifier.d.ts.map +1 -0
- package/dist/runtime/verifier.js +172 -0
- package/dist/runtime/verifier.js.map +1 -0
- package/dist/runtime/worktree.d.ts +24 -0
- package/dist/runtime/worktree.d.ts.map +1 -0
- package/dist/runtime/worktree.js +82 -0
- package/dist/runtime/worktree.js.map +1 -0
- package/dist/tui/dashboard.d.ts +65 -0
- package/dist/tui/dashboard.d.ts.map +1 -0
- package/dist/tui/dashboard.js +496 -0
- package/dist/tui/dashboard.js.map +1 -0
- package/dist/tui/progress.d.ts +32 -0
- package/dist/tui/progress.d.ts.map +1 -0
- package/dist/tui/progress.js +257 -0
- package/dist/tui/progress.js.map +1 -0
- package/dist/tui/renderer.d.ts +72 -0
- package/dist/tui/renderer.d.ts.map +1 -0
- package/dist/tui/renderer.js +205 -0
- package/dist/tui/renderer.js.map +1 -0
- package/dist/utils/compact-format.d.ts +35 -0
- package/dist/utils/compact-format.d.ts.map +1 -0
- package/dist/utils/compact-format.js +106 -0
- package/dist/utils/compact-format.js.map +1 -0
- package/dist/utils/config.d.ts +73 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +70 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/env.d.ts +6 -0
- package/dist/utils/env.d.ts.map +1 -0
- package/dist/utils/env.js +28 -0
- package/dist/utils/env.js.map +1 -0
- package/dist/utils/errors.d.ts +14 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +120 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/logger.d.ts +14 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +56 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/output.d.ts +10 -0
- package/dist/utils/output.d.ts.map +1 -0
- package/dist/utils/output.js +26 -0
- package/dist/utils/output.js.map +1 -0
- package/dist/utils/paths.d.ts +7 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +16 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/project-config.d.ts +18 -0
- package/dist/utils/project-config.d.ts.map +1 -0
- package/dist/utils/project-config.js +46 -0
- package/dist/utils/project-config.js.map +1 -0
- package/dist/utils/redact.d.ts +5 -0
- package/dist/utils/redact.d.ts.map +1 -0
- package/dist/utils/redact.js +25 -0
- package/dist/utils/redact.js.map +1 -0
- package/dist/utils/schemas.d.ts +109 -0
- package/dist/utils/schemas.d.ts.map +1 -0
- package/dist/utils/schemas.js +63 -0
- package/dist/utils/schemas.js.map +1 -0
- package/dist/utils/terminal.d.ts +33 -0
- package/dist/utils/terminal.d.ts.map +1 -0
- package/dist/utils/terminal.js +82 -0
- package/dist/utils/terminal.js.map +1 -0
- package/dist/utils/tokens.d.ts +9 -0
- package/dist/utils/tokens.d.ts.map +1 -0
- package/dist/utils/tokens.js +11 -0
- package/dist/utils/tokens.js.map +1 -0
- package/package.json +71 -0
- package/skills/design-system/SKILL.md +195 -0
- package/skills/orchestration-patterns/SKILL.md +81 -0
- package/skills/orchestration-patterns/examples/example-workflows.md +290 -0
- package/skills/orchestration-patterns/references/adversarial.md +51 -0
- package/skills/orchestration-patterns/references/batch.md +63 -0
- package/skills/orchestration-patterns/references/chaos.md +43 -0
- package/skills/orchestration-patterns/references/competitive.md +84 -0
- package/skills/orchestration-patterns/references/debate.md +44 -0
- package/skills/orchestration-patterns/references/emergence.md +50 -0
- package/skills/orchestration-patterns/references/fan-out-fan-in.md +74 -0
- package/skills/orchestration-patterns/references/iterative.md +99 -0
- package/skills/orchestration-patterns/references/pipeline.md +72 -0
- package/skills/swarm-output-style/SKILL.md +85 -0
- package/templates/add-endpoint.yml +44 -0
- package/templates/add-feature.yml +30 -0
- package/templates/agent-template.md +73 -0
- package/templates/bug-fix.yml +31 -0
- package/templates/explore.yml +21 -0
- package/templates/fix-pr.yml +30 -0
- package/templates/migration.yml +36 -0
- package/templates/refactor.yml +35 -0
- package/templates/security-audit.yml +27 -0
- package/templates/workflow-template.md +63 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
3
|
+
import { safeEnv } from '../utils/env.js';
|
|
4
|
+
export class MCPClient {
|
|
5
|
+
servers = new Map();
|
|
6
|
+
tools = new Map();
|
|
7
|
+
bus;
|
|
8
|
+
constructor(bus) { this.bus = bus ?? null; }
|
|
9
|
+
async connect(config) {
|
|
10
|
+
const conn = config.transport === 'stdio' ? new StdioConn(config) : new HttpConn(config);
|
|
11
|
+
await conn.initialize();
|
|
12
|
+
this.servers.set(config.name, conn);
|
|
13
|
+
const tools = await conn.listTools();
|
|
14
|
+
for (const t of tools)
|
|
15
|
+
this.tools.set(`${config.name}:${t.name}`, { ...t, serverName: config.name });
|
|
16
|
+
return tools.map((t) => ({ ...t, serverName: config.name }));
|
|
17
|
+
}
|
|
18
|
+
async callTool(server, tool, args) {
|
|
19
|
+
const conn = this.servers.get(server);
|
|
20
|
+
if (!conn)
|
|
21
|
+
throw new Error(`MCP not connected: ${server}`);
|
|
22
|
+
return conn.callTool(tool, args);
|
|
23
|
+
}
|
|
24
|
+
getAllTools() { return [...this.tools.values()]; }
|
|
25
|
+
async disconnectAll() {
|
|
26
|
+
for (const c of this.servers.values())
|
|
27
|
+
try {
|
|
28
|
+
await c.shutdown();
|
|
29
|
+
}
|
|
30
|
+
catch { }
|
|
31
|
+
this.servers.clear();
|
|
32
|
+
this.tools.clear();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
class StdioConn {
|
|
36
|
+
config;
|
|
37
|
+
proc = null;
|
|
38
|
+
pending = new Map();
|
|
39
|
+
buf = '';
|
|
40
|
+
constructor(config) {
|
|
41
|
+
this.config = config;
|
|
42
|
+
}
|
|
43
|
+
async initialize() {
|
|
44
|
+
this.proc = spawn(this.config.command, this.config.args ?? [], {
|
|
45
|
+
cwd: this.config.cwd, env: safeEnv(this.config.env), stdio: ['pipe', 'pipe', 'pipe'],
|
|
46
|
+
});
|
|
47
|
+
this.proc.stdout.on('data', (c) => { this.buf += c.toString(); this.flush(); });
|
|
48
|
+
await this.req('initialize', { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'swarm-engine', version: '1.0.0' } });
|
|
49
|
+
this.proc?.stdin?.write(JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized', params: {} }) + '\n');
|
|
50
|
+
}
|
|
51
|
+
async listTools() { return (await this.req('tools/list', {})).tools ?? []; }
|
|
52
|
+
async callTool(name, args) { return (await this.req('tools/call', { name, arguments: args })).content; }
|
|
53
|
+
async shutdown() {
|
|
54
|
+
// Clear all pending request timers to prevent leaks
|
|
55
|
+
for (const [, entry] of this.pending) {
|
|
56
|
+
clearTimeout(entry.timer);
|
|
57
|
+
entry.reject(new Error('Connection shutdown'));
|
|
58
|
+
}
|
|
59
|
+
this.pending.clear();
|
|
60
|
+
this.proc?.kill('SIGTERM');
|
|
61
|
+
}
|
|
62
|
+
req(method, params) {
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
const id = randomUUID();
|
|
65
|
+
const timer = setTimeout(() => { if (this.pending.has(id)) {
|
|
66
|
+
this.pending.delete(id);
|
|
67
|
+
reject(new Error(`Timeout: ${method}`));
|
|
68
|
+
} }, 30_000);
|
|
69
|
+
this.pending.set(id, { resolve, reject, timer });
|
|
70
|
+
this.proc?.stdin?.write(JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n');
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
flush() {
|
|
74
|
+
const lines = this.buf.split('\n');
|
|
75
|
+
this.buf = lines.pop() ?? '';
|
|
76
|
+
for (const l of lines) {
|
|
77
|
+
if (!l.trim())
|
|
78
|
+
continue;
|
|
79
|
+
try {
|
|
80
|
+
const m = JSON.parse(l);
|
|
81
|
+
if (m.id && this.pending.has(String(m.id))) {
|
|
82
|
+
const { resolve, reject, timer } = this.pending.get(String(m.id));
|
|
83
|
+
this.pending.delete(String(m.id));
|
|
84
|
+
clearTimeout(timer);
|
|
85
|
+
m.error ? reject(new Error(m.error.message)) : resolve(m.result);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch { }
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
class HttpConn {
|
|
93
|
+
config;
|
|
94
|
+
constructor(config) {
|
|
95
|
+
this.config = config;
|
|
96
|
+
}
|
|
97
|
+
async initialize() {
|
|
98
|
+
if (!this.config.url)
|
|
99
|
+
throw new Error('HTTP MCP requires url');
|
|
100
|
+
await this.req('initialize', { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'swarm-engine', version: '1.0.0' } });
|
|
101
|
+
}
|
|
102
|
+
async listTools() { return (await this.req('tools/list', {})).tools ?? []; }
|
|
103
|
+
async callTool(name, args) { return (await this.req('tools/call', { name, arguments: args })).content; }
|
|
104
|
+
async shutdown() { }
|
|
105
|
+
validateUrl(url) {
|
|
106
|
+
const parsed = new URL(url);
|
|
107
|
+
const host = parsed.hostname.replace(/^\[|\]$/g, ''); // Strip IPv6 brackets
|
|
108
|
+
// Block obvious patterns
|
|
109
|
+
const blocked = [
|
|
110
|
+
'169.254.169.254', // AWS metadata
|
|
111
|
+
'0.0.0.0',
|
|
112
|
+
'localhost',
|
|
113
|
+
'127.0.0.1',
|
|
114
|
+
'::1', // IPv6 loopback
|
|
115
|
+
'::ffff:127.0.0.1', // IPv6-mapped loopback
|
|
116
|
+
];
|
|
117
|
+
if (blocked.includes(host)) {
|
|
118
|
+
throw new Error(`SSRF blocked: ${host}`);
|
|
119
|
+
}
|
|
120
|
+
// Block private ranges (IPv4)
|
|
121
|
+
const ipv4Match = host.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
|
|
122
|
+
if (ipv4Match) {
|
|
123
|
+
const [, a, b] = ipv4Match.map(Number);
|
|
124
|
+
if (a === 10 || a === 127 || // 10.x, 127.x
|
|
125
|
+
(a === 172 && b >= 16 && b <= 31) || // 172.16-31.x
|
|
126
|
+
(a === 192 && b === 168) || // 192.168.x
|
|
127
|
+
(a === 169 && b === 254)) { // 169.254.x
|
|
128
|
+
throw new Error(`SSRF blocked: ${host} is a private address`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Block IPv6 private/loopback
|
|
132
|
+
if (host.startsWith('::') || host.startsWith('fe80') || host.startsWith('fc') || host.startsWith('fd')) {
|
|
133
|
+
throw new Error(`SSRF blocked: ${host} is a private IPv6 address`);
|
|
134
|
+
}
|
|
135
|
+
// Block decimal/octal/hex IP representations
|
|
136
|
+
if (/^\d{8,}$/.test(host) || /^0\d/.test(host) || /^0x/i.test(host)) {
|
|
137
|
+
throw new Error(`SSRF blocked: ${host} uses non-standard IP representation`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async req(method, params) {
|
|
141
|
+
this.validateUrl(this.config.url);
|
|
142
|
+
const r = await fetch(this.config.url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: randomUUID(), method, params }), redirect: 'manual' });
|
|
143
|
+
if (r.status >= 300 && r.status < 400) {
|
|
144
|
+
throw new Error(`SSRF blocked: MCP server returned redirect to ${r.headers.get('location')}`);
|
|
145
|
+
}
|
|
146
|
+
if (!r.ok)
|
|
147
|
+
throw new Error(`HTTP ${r.status}`);
|
|
148
|
+
const m = await r.json();
|
|
149
|
+
if (m.error)
|
|
150
|
+
throw new Error(m.error.message);
|
|
151
|
+
return m.result;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=mcp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../../src/runtime/mcp.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface ModelScore {
|
|
2
|
+
model: string;
|
|
3
|
+
taskType: string;
|
|
4
|
+
successes: number;
|
|
5
|
+
attempts: number;
|
|
6
|
+
avgReward: number;
|
|
7
|
+
ucbScore: number;
|
|
8
|
+
}
|
|
9
|
+
export declare class ModelRouter {
|
|
10
|
+
private db;
|
|
11
|
+
constructor(dbPath?: string);
|
|
12
|
+
private initSchema;
|
|
13
|
+
/** Record an outcome for a model on a task type */
|
|
14
|
+
record(model: string, taskType: string, success: boolean, reward?: number): void;
|
|
15
|
+
/** Select the best model for a task type using UCB1 */
|
|
16
|
+
recommend(taskType: string, candidates: string[]): string;
|
|
17
|
+
/** Get scores for all models for a task type */
|
|
18
|
+
getScores(taskType: string): ModelScore[];
|
|
19
|
+
prune(maxRows?: number): void;
|
|
20
|
+
close(): void;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=model-router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-router.d.ts","sourceRoot":"","sources":["../../src/runtime/model-router.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,EAAE,CAAoB;gBAElB,MAAM,CAAC,EAAE,MAAM;IAU3B,OAAO,CAAC,UAAU;IAclB,mDAAmD;IACnD,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAMhF,uDAAuD;IACvD,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM;IA0CzD,gDAAgD;IAChD,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,EAAE;IAoBzC,KAAK,CAAC,OAAO,GAAE,MAAc,GAAG,IAAI;IAIpC,KAAK,IAAI,IAAI;CACd"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import { existsSync, mkdirSync } from 'fs';
|
|
3
|
+
import { join, dirname } from 'path';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
export class ModelRouter {
|
|
6
|
+
db;
|
|
7
|
+
constructor(dbPath) {
|
|
8
|
+
const path = dbPath ?? join(homedir(), '.swarm', 'data', 'router.db');
|
|
9
|
+
const dir = dirname(path);
|
|
10
|
+
if (!existsSync(dir))
|
|
11
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
12
|
+
this.db = new Database(path);
|
|
13
|
+
this.db.pragma('journal_mode = WAL');
|
|
14
|
+
this.db.pragma('busy_timeout = 5000');
|
|
15
|
+
this.initSchema();
|
|
16
|
+
}
|
|
17
|
+
initSchema() {
|
|
18
|
+
this.db.exec(`
|
|
19
|
+
CREATE TABLE IF NOT EXISTS model_outcomes (
|
|
20
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
21
|
+
model TEXT NOT NULL,
|
|
22
|
+
task_type TEXT NOT NULL,
|
|
23
|
+
success INTEGER NOT NULL,
|
|
24
|
+
reward REAL DEFAULT 0,
|
|
25
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
26
|
+
);
|
|
27
|
+
CREATE INDEX IF NOT EXISTS idx_model_task ON model_outcomes(model, task_type);
|
|
28
|
+
`);
|
|
29
|
+
}
|
|
30
|
+
/** Record an outcome for a model on a task type */
|
|
31
|
+
record(model, taskType, success, reward) {
|
|
32
|
+
this.db.prepare('INSERT INTO model_outcomes (model, task_type, success, reward) VALUES (?, ?, ?, ?)').run(model, taskType, success ? 1 : 0, reward ?? (success ? 1.0 : 0.0));
|
|
33
|
+
}
|
|
34
|
+
/** Select the best model for a task type using UCB1 */
|
|
35
|
+
recommend(taskType, candidates) {
|
|
36
|
+
if (candidates.length === 0)
|
|
37
|
+
return 'sonnet';
|
|
38
|
+
// Single query for all models instead of N+1 per-candidate queries
|
|
39
|
+
const rows = this.db.prepare(`
|
|
40
|
+
SELECT model, COUNT(*) as attempts, AVG(reward) as avg_reward
|
|
41
|
+
FROM model_outcomes WHERE task_type = ?
|
|
42
|
+
GROUP BY model
|
|
43
|
+
`).all(taskType);
|
|
44
|
+
const statsMap = new Map();
|
|
45
|
+
let N = 0;
|
|
46
|
+
for (const row of rows) {
|
|
47
|
+
const attempts = row.attempts;
|
|
48
|
+
statsMap.set(row.model, { attempts, avgReward: row.avg_reward ?? 0.5 });
|
|
49
|
+
N += attempts;
|
|
50
|
+
}
|
|
51
|
+
if (N === 0)
|
|
52
|
+
N = 1;
|
|
53
|
+
let bestModel = candidates[0];
|
|
54
|
+
let bestScore = -Infinity;
|
|
55
|
+
for (const model of candidates) {
|
|
56
|
+
const stats = statsMap.get(model);
|
|
57
|
+
const attempts = stats?.attempts ?? 0;
|
|
58
|
+
const avgReward = stats?.avgReward ?? 0.5;
|
|
59
|
+
// UCB1 formula: avg_reward + sqrt(2 * ln(N) / n_i)
|
|
60
|
+
// For unexplored models, return Infinity (explore first)
|
|
61
|
+
const ucb = attempts === 0
|
|
62
|
+
? Infinity
|
|
63
|
+
: avgReward + Math.sqrt(2 * Math.log(N) / attempts);
|
|
64
|
+
if (ucb > bestScore) {
|
|
65
|
+
bestScore = ucb;
|
|
66
|
+
bestModel = model;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return bestModel;
|
|
70
|
+
}
|
|
71
|
+
/** Get scores for all models for a task type */
|
|
72
|
+
getScores(taskType) {
|
|
73
|
+
const rows = this.db.prepare(`
|
|
74
|
+
SELECT model, task_type, COUNT(*) as attempts, SUM(success) as successes, AVG(reward) as avg_reward
|
|
75
|
+
FROM model_outcomes
|
|
76
|
+
WHERE task_type = ?
|
|
77
|
+
GROUP BY model
|
|
78
|
+
`).all(taskType);
|
|
79
|
+
const N = rows.reduce((sum, r) => sum + r.attempts, 0) || 1;
|
|
80
|
+
return rows.map(r => ({
|
|
81
|
+
model: r.model,
|
|
82
|
+
taskType: r.task_type,
|
|
83
|
+
successes: r.successes,
|
|
84
|
+
attempts: r.attempts,
|
|
85
|
+
avgReward: r.avg_reward ?? 0,
|
|
86
|
+
ucbScore: r.attempts === 0 ? Infinity : (r.avg_reward ?? 0) + Math.sqrt(2 * Math.log(N) / r.attempts),
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
prune(maxRows = 10000) {
|
|
90
|
+
this.db.prepare('DELETE FROM model_outcomes WHERE id NOT IN (SELECT id FROM model_outcomes ORDER BY created_at DESC LIMIT ?)').run(maxRows);
|
|
91
|
+
}
|
|
92
|
+
close() { this.db.close(); }
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=model-router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-router.js","sourceRoot":"","sources":["../../src/runtime/model-router.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export interface PaneInfo {
|
|
2
|
+
paneId: string;
|
|
3
|
+
agentId: string;
|
|
4
|
+
agentName: string;
|
|
5
|
+
windowId: string;
|
|
6
|
+
status: 'running' | 'done' | 'error';
|
|
7
|
+
outputFile: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Tmux pane manager for visible agent execution.
|
|
11
|
+
*
|
|
12
|
+
* Creates a tmux session with split panes so each agent
|
|
13
|
+
* runs in its own visible terminal pane. Panes are titled
|
|
14
|
+
* by agent name and auto-close after completion.
|
|
15
|
+
*/
|
|
16
|
+
export declare class PaneManager {
|
|
17
|
+
private sessionName;
|
|
18
|
+
private panes;
|
|
19
|
+
private active;
|
|
20
|
+
constructor(sessionName?: string);
|
|
21
|
+
/** Check if tmux is available on this system. */
|
|
22
|
+
static isAvailable(): boolean;
|
|
23
|
+
/** Start the tmux session with an initial orchestrator pane. */
|
|
24
|
+
start(orchestrationName: string): void;
|
|
25
|
+
/**
|
|
26
|
+
* Create a new pane for an agent and run a command in it.
|
|
27
|
+
* Returns the tmux pane ID.
|
|
28
|
+
*/
|
|
29
|
+
createPane(agentId: string, agentName: string, command: string, env?: Record<string, string>): {
|
|
30
|
+
paneId: string;
|
|
31
|
+
outputFile: string;
|
|
32
|
+
};
|
|
33
|
+
/** Mark a pane as done -- show status and close after delay. */
|
|
34
|
+
markDone(agentId: string, success: boolean): void;
|
|
35
|
+
/** Print attachment instructions for the user. */
|
|
36
|
+
attach(): void;
|
|
37
|
+
/** Get the session name for external attachment. */
|
|
38
|
+
getSessionName(): string;
|
|
39
|
+
/** Kill the entire tmux session. */
|
|
40
|
+
stop(): void;
|
|
41
|
+
/** List active panes. */
|
|
42
|
+
list(): PaneInfo[];
|
|
43
|
+
/** Check if session is active. */
|
|
44
|
+
isActive(): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Wait for a pane's command to exit by polling tmux pane status.
|
|
47
|
+
* Resolves when the pane's process has finished.
|
|
48
|
+
*/
|
|
49
|
+
waitForPane(paneId: string): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Capture the output of a pane using tmux capture-pane.
|
|
52
|
+
*/
|
|
53
|
+
capturePane(paneId: string): string;
|
|
54
|
+
/**
|
|
55
|
+
* Read captured output from the temp file (reliable, not lossy like tmux capture-pane).
|
|
56
|
+
*/
|
|
57
|
+
captureFromFile(agentId: string): string;
|
|
58
|
+
/**
|
|
59
|
+
* Clean up the output file for an agent.
|
|
60
|
+
*/
|
|
61
|
+
cleanupFile(agentId: string): void;
|
|
62
|
+
private setTitle;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Shell-escape a string by replacing single quotes with '\'' pattern.
|
|
66
|
+
*/
|
|
67
|
+
export declare function shellEscape(s: string): string;
|
|
68
|
+
/**
|
|
69
|
+
* Build a CLI command string for a backend.
|
|
70
|
+
*
|
|
71
|
+
* For Codex: cd <cwd> && codex exec --full-auto --json --ephemeral '<prompt>'
|
|
72
|
+
* For Gemini: cd <cwd> && gemini -p '<prompt>' --output-format json -y
|
|
73
|
+
* For Claude: falls back to invisible execution (returns null)
|
|
74
|
+
*/
|
|
75
|
+
export declare function buildPaneCommand(backendName: string, prompt: string, cwd: string): string | null;
|
|
76
|
+
//# sourceMappingURL=panes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"panes.d.ts","sourceRoot":"","sources":["../../src/runtime/panes.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;IACrC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,KAAK,CAAoC;IACjD,OAAO,CAAC,MAAM,CAAS;gBAEX,WAAW,CAAC,EAAE,MAAM;IAIhC,iDAAiD;IACjD,MAAM,CAAC,WAAW,IAAI,OAAO;IAO7B,gEAAgE;IAChE,KAAK,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI;IAmCtC;;;OAGG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE;IAoDrI,gEAAgE;IAChE,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI;IAwBjD,kDAAkD;IAClD,MAAM,IAAI,IAAI;IAKd,oDAAoD;IACpD,cAAc,IAAI,MAAM;IAIxB,oCAAoC;IACpC,IAAI,IAAI,IAAI;IASZ,yBAAyB;IACzB,IAAI,IAAI,QAAQ,EAAE;IAIlB,kCAAkC;IAClC,QAAQ,IAAI,OAAO;IAInB;;;OAGG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoC1C;;OAEG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAUnC;;OAEG;IACH,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAWxC;;OAEG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAOlC,OAAO,CAAC,QAAQ;CAOjB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,GACV,MAAM,GAAG,IAAI,CAef"}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { execFileSync } from 'child_process';
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
3
|
+
import { readFileSync, unlinkSync } from 'fs';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
/**
|
|
7
|
+
* Tmux pane manager for visible agent execution.
|
|
8
|
+
*
|
|
9
|
+
* Creates a tmux session with split panes so each agent
|
|
10
|
+
* runs in its own visible terminal pane. Panes are titled
|
|
11
|
+
* by agent name and auto-close after completion.
|
|
12
|
+
*/
|
|
13
|
+
export class PaneManager {
|
|
14
|
+
sessionName;
|
|
15
|
+
panes = new Map();
|
|
16
|
+
active = false;
|
|
17
|
+
constructor(sessionName) {
|
|
18
|
+
this.sessionName = sessionName ?? `swarm-${randomUUID().slice(0, 8)}`;
|
|
19
|
+
}
|
|
20
|
+
/** Check if tmux is available on this system. */
|
|
21
|
+
static isAvailable() {
|
|
22
|
+
try {
|
|
23
|
+
execFileSync('tmux', ['-V'], { encoding: 'utf-8' });
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Start the tmux session with an initial orchestrator pane. */
|
|
31
|
+
start(orchestrationName) {
|
|
32
|
+
// Create a new detached session
|
|
33
|
+
execFileSync('tmux', [
|
|
34
|
+
'new-session', '-d',
|
|
35
|
+
'-s', this.sessionName,
|
|
36
|
+
'-n', 'swarm',
|
|
37
|
+
'-x', '200', '-y', '50',
|
|
38
|
+
]);
|
|
39
|
+
// Enable pane border titles
|
|
40
|
+
try {
|
|
41
|
+
execFileSync('tmux', [
|
|
42
|
+
'set', '-t', this.sessionName, 'pane-border-status', 'top',
|
|
43
|
+
]);
|
|
44
|
+
execFileSync('tmux', [
|
|
45
|
+
'set', '-t', this.sessionName, 'pane-border-format',
|
|
46
|
+
' #[bold]#{pane_title}#[default] ',
|
|
47
|
+
]);
|
|
48
|
+
// Keep pane visible after command exits so we can capture output
|
|
49
|
+
execFileSync('tmux', [
|
|
50
|
+
'set', '-t', this.sessionName, 'remain-on-exit', 'on',
|
|
51
|
+
]);
|
|
52
|
+
}
|
|
53
|
+
catch { /* older tmux versions may not support these options */ }
|
|
54
|
+
// Set initial pane title
|
|
55
|
+
try {
|
|
56
|
+
execFileSync('tmux', [
|
|
57
|
+
'select-pane', '-t', `${this.sessionName}:swarm`,
|
|
58
|
+
'-T', `swarm: ${orchestrationName.slice(0, 50)}`,
|
|
59
|
+
]);
|
|
60
|
+
}
|
|
61
|
+
catch { /* ignore */ }
|
|
62
|
+
this.active = true;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Create a new pane for an agent and run a command in it.
|
|
66
|
+
* Returns the tmux pane ID.
|
|
67
|
+
*/
|
|
68
|
+
createPane(agentId, agentName, command, env) {
|
|
69
|
+
if (!this.active)
|
|
70
|
+
throw new Error('Session not started');
|
|
71
|
+
// Build env prefix — validate env key names to prevent injection
|
|
72
|
+
const ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
73
|
+
const envStr = env
|
|
74
|
+
? Object.entries(env)
|
|
75
|
+
.filter(([k]) => ENV_KEY_RE.test(k))
|
|
76
|
+
.map(([k, v]) => `${k}=${shellEscape(v)}`).join(' ') + ' '
|
|
77
|
+
: '';
|
|
78
|
+
// Create a temp file to tee output for reliable parsing (tmux capture-pane is lossy)
|
|
79
|
+
const outputFile = join(tmpdir(), `swarm-pane-${agentId.replace(/[^a-zA-Z0-9-]/g, '')}.out`);
|
|
80
|
+
// The original command runs and output is tee'd to a file for reliable parsing
|
|
81
|
+
const wrappedCommand = `${envStr}${command} 2>&1 | tee ${shellEscape(outputFile)}; echo ""; echo "--- DONE ---"`;
|
|
82
|
+
// Split window and run the command directly via shell -c
|
|
83
|
+
// This way the pane's process IS the command, and pane_dead=1 when it exits
|
|
84
|
+
const result = execFileSync('tmux', [
|
|
85
|
+
'split-window', '-t', `${this.sessionName}:swarm`,
|
|
86
|
+
'-h', // horizontal split
|
|
87
|
+
'-P', '-F', '#{pane_id}', // print the new pane ID
|
|
88
|
+
'sh', '-c', wrappedCommand,
|
|
89
|
+
], { encoding: 'utf-8' }).trim();
|
|
90
|
+
const paneId = result;
|
|
91
|
+
// Rebalance the layout
|
|
92
|
+
try {
|
|
93
|
+
execFileSync('tmux', ['select-layout', '-t', `${this.sessionName}:swarm`, 'tiled']);
|
|
94
|
+
}
|
|
95
|
+
catch { /* layout may fail if only 1 pane */ }
|
|
96
|
+
// Set pane title
|
|
97
|
+
execFileSync('tmux', [
|
|
98
|
+
'select-pane', '-t', paneId,
|
|
99
|
+
'-T', agentName,
|
|
100
|
+
]);
|
|
101
|
+
const info = {
|
|
102
|
+
paneId,
|
|
103
|
+
agentId,
|
|
104
|
+
agentName,
|
|
105
|
+
windowId: 'swarm',
|
|
106
|
+
status: 'running',
|
|
107
|
+
outputFile,
|
|
108
|
+
};
|
|
109
|
+
this.panes.set(agentId, info);
|
|
110
|
+
return { paneId, outputFile };
|
|
111
|
+
}
|
|
112
|
+
/** Mark a pane as done -- show status and close after delay. */
|
|
113
|
+
markDone(agentId, success) {
|
|
114
|
+
const pane = this.panes.get(agentId);
|
|
115
|
+
if (!pane)
|
|
116
|
+
return;
|
|
117
|
+
pane.status = success ? 'done' : 'error';
|
|
118
|
+
const msg = success ? 'DONE' : 'FAILED';
|
|
119
|
+
try {
|
|
120
|
+
// Send a visible completion message
|
|
121
|
+
execFileSync('tmux', [
|
|
122
|
+
'send-keys', '-t', pane.paneId,
|
|
123
|
+
`echo "--- ${msg} ---"`, 'Enter',
|
|
124
|
+
]);
|
|
125
|
+
// Close the pane after a short delay (3 seconds)
|
|
126
|
+
setTimeout(() => {
|
|
127
|
+
try {
|
|
128
|
+
execFileSync('tmux', ['kill-pane', '-t', pane.paneId]);
|
|
129
|
+
}
|
|
130
|
+
catch { /* pane may already be gone */ }
|
|
131
|
+
this.panes.delete(agentId);
|
|
132
|
+
}, 3000);
|
|
133
|
+
}
|
|
134
|
+
catch { /* pane may already be gone */ }
|
|
135
|
+
}
|
|
136
|
+
/** Print attachment instructions for the user. */
|
|
137
|
+
attach() {
|
|
138
|
+
if (!this.active)
|
|
139
|
+
return;
|
|
140
|
+
console.log(`\nView agents: tmux attach -t ${this.sessionName}\n`);
|
|
141
|
+
}
|
|
142
|
+
/** Get the session name for external attachment. */
|
|
143
|
+
getSessionName() {
|
|
144
|
+
return this.sessionName;
|
|
145
|
+
}
|
|
146
|
+
/** Kill the entire tmux session. */
|
|
147
|
+
stop() {
|
|
148
|
+
if (!this.active)
|
|
149
|
+
return;
|
|
150
|
+
try {
|
|
151
|
+
execFileSync('tmux', ['kill-session', '-t', this.sessionName]);
|
|
152
|
+
}
|
|
153
|
+
catch { /* session may already be gone */ }
|
|
154
|
+
this.active = false;
|
|
155
|
+
this.panes.clear();
|
|
156
|
+
}
|
|
157
|
+
/** List active panes. */
|
|
158
|
+
list() {
|
|
159
|
+
return [...this.panes.values()];
|
|
160
|
+
}
|
|
161
|
+
/** Check if session is active. */
|
|
162
|
+
isActive() {
|
|
163
|
+
return this.active;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Wait for a pane's command to exit by polling tmux pane status.
|
|
167
|
+
* Resolves when the pane's process has finished.
|
|
168
|
+
*/
|
|
169
|
+
waitForPane(paneId) {
|
|
170
|
+
return new Promise((resolve) => {
|
|
171
|
+
const check = setInterval(() => {
|
|
172
|
+
try {
|
|
173
|
+
const dead = execFileSync('tmux', [
|
|
174
|
+
'list-panes', '-t', `${this.sessionName}:swarm`,
|
|
175
|
+
'-F', '#{pane_id} #{pane_dead}',
|
|
176
|
+
], { encoding: 'utf-8' });
|
|
177
|
+
// Parse output lines looking for our pane
|
|
178
|
+
const lines = dead.trim().split('\n');
|
|
179
|
+
for (const line of lines) {
|
|
180
|
+
const [id, isDead] = line.split(' ');
|
|
181
|
+
if (id === paneId && isDead === '1') {
|
|
182
|
+
clearInterval(check);
|
|
183
|
+
resolve();
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// If pane is no longer listed at all, it's gone
|
|
188
|
+
const paneIds = lines.map(l => l.split(' ')[0]);
|
|
189
|
+
if (!paneIds.includes(paneId)) {
|
|
190
|
+
clearInterval(check);
|
|
191
|
+
resolve();
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
// Session/pane gone
|
|
197
|
+
clearInterval(check);
|
|
198
|
+
resolve();
|
|
199
|
+
}
|
|
200
|
+
}, 2000); // poll every 2 seconds
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Capture the output of a pane using tmux capture-pane.
|
|
205
|
+
*/
|
|
206
|
+
capturePane(paneId) {
|
|
207
|
+
try {
|
|
208
|
+
return execFileSync('tmux', [
|
|
209
|
+
'capture-pane', '-t', paneId, '-p',
|
|
210
|
+
], { encoding: 'utf-8' });
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return '';
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Read captured output from the temp file (reliable, not lossy like tmux capture-pane).
|
|
218
|
+
*/
|
|
219
|
+
captureFromFile(agentId) {
|
|
220
|
+
const pane = this.panes.get(agentId);
|
|
221
|
+
if (!pane?.outputFile)
|
|
222
|
+
return this.capturePane(pane?.paneId ?? '');
|
|
223
|
+
try {
|
|
224
|
+
return readFileSync(pane.outputFile, 'utf-8');
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
return '';
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Clean up the output file for an agent.
|
|
232
|
+
*/
|
|
233
|
+
cleanupFile(agentId) {
|
|
234
|
+
const pane = this.panes.get(agentId);
|
|
235
|
+
if (pane?.outputFile) {
|
|
236
|
+
try {
|
|
237
|
+
unlinkSync(pane.outputFile);
|
|
238
|
+
}
|
|
239
|
+
catch { /* file may already be gone */ }
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
setTitle(title) {
|
|
243
|
+
try {
|
|
244
|
+
execFileSync('tmux', [
|
|
245
|
+
'rename-window', '-t', `${this.sessionName}:0`, title.slice(0, 40),
|
|
246
|
+
]);
|
|
247
|
+
}
|
|
248
|
+
catch { /* ignore */ }
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Shell-escape a string by replacing single quotes with '\'' pattern.
|
|
253
|
+
*/
|
|
254
|
+
export function shellEscape(s) {
|
|
255
|
+
return "'" + s.replace(/'/g, "'\\''") + "'";
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Build a CLI command string for a backend.
|
|
259
|
+
*
|
|
260
|
+
* For Codex: cd <cwd> && codex exec --full-auto --json --ephemeral '<prompt>'
|
|
261
|
+
* For Gemini: cd <cwd> && gemini -p '<prompt>' --output-format json -y
|
|
262
|
+
* For Claude: falls back to invisible execution (returns null)
|
|
263
|
+
*/
|
|
264
|
+
export function buildPaneCommand(backendName, prompt, cwd) {
|
|
265
|
+
const escaped = shellEscape(prompt);
|
|
266
|
+
switch (backendName) {
|
|
267
|
+
case 'codex':
|
|
268
|
+
return `cd ${shellEscape(cwd)} && codex exec --full-auto --json --ephemeral ${escaped}`;
|
|
269
|
+
case 'gemini':
|
|
270
|
+
return `cd ${shellEscape(cwd)} && gemini -p ${escaped} --output-format json -y`;
|
|
271
|
+
case 'claude':
|
|
272
|
+
// Claude uses the Agent SDK, not a CLI command for pane mode.
|
|
273
|
+
// Return null to signal fallback to invisible execution.
|
|
274
|
+
return null;
|
|
275
|
+
default:
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
//# sourceMappingURL=panes.js.map
|