@aiwg/cli 0.0.0-bootstrap.0 → 2026.7.18
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/LICENSE +21 -0
- package/README.md +22 -1
- package/bin/aiwg.mjs +349 -0
- package/dist/src/a2a/agent-card.js +145 -0
- package/dist/src/a2a/client.js +411 -0
- package/dist/src/a2a/hitl-cli.js +171 -0
- package/dist/src/a2a/hitl-driver.js +370 -0
- package/dist/src/a2a/hitl.js +181 -0
- package/dist/src/a2a/http.js +193 -0
- package/dist/src/a2a/jcs.js +128 -0
- package/dist/src/a2a/jws.js +187 -0
- package/dist/src/a2a/types.js +24 -0
- package/dist/src/a2a/webhook.js +240 -0
- package/dist/src/activity-log/cli.js +348 -0
- package/dist/src/activity-log/parser.js +81 -0
- package/dist/src/activity-log/types.js +50 -0
- package/dist/src/agents/agent-deployer.js +367 -0
- package/dist/src/agents/agent-packager.js +271 -0
- package/dist/src/agents/agent-validator.js +271 -0
- package/dist/src/agents/grounding/knowledge-base.js +161 -0
- package/dist/src/agents/grounding/types.js +12 -0
- package/dist/src/agents/index.js +10 -0
- package/dist/src/agents/packaged-agent-inventory.js +75 -0
- package/dist/src/agents/types.js +7 -0
- package/dist/src/api/index.d.ts +10 -0
- package/dist/src/api/index.js +10 -0
- package/dist/src/artifacts/address-parser.js +73 -0
- package/dist/src/artifacts/audit/cli.js +183 -0
- package/dist/src/artifacts/audit/drift.js +122 -0
- package/dist/src/artifacts/audit/types.js +12 -0
- package/dist/src/artifacts/backends/graphology-backend.js +160 -0
- package/dist/src/artifacts/backends/json-backend.js +129 -0
- package/dist/src/artifacts/backends/sqlite-backend.js +199 -0
- package/dist/src/artifacts/browser-export.js +622 -0
- package/dist/src/artifacts/capability-resolver.js +94 -0
- package/dist/src/artifacts/checksum-manifest.js +91 -0
- package/dist/src/artifacts/citation-parser.js +162 -0
- package/dist/src/artifacts/cli.js +1486 -0
- package/dist/src/artifacts/corpus-tools/citation-densify.js +311 -0
- package/dist/src/artifacts/corpus-tools/cli.js +393 -0
- package/dist/src/artifacts/corpus-tools/corpus-graph.js +225 -0
- package/dist/src/artifacts/corpus-tools/curator.js +186 -0
- package/dist/src/artifacts/corpus-tools/discovery-log.js +70 -0
- package/dist/src/artifacts/corpus-tools/funder-network.js +141 -0
- package/dist/src/artifacts/corpus-tools/induction-audit.js +233 -0
- package/dist/src/artifacts/corpus-tools/integrity-scan.js +202 -0
- package/dist/src/artifacts/corpus-tools/profile-communities.js +166 -0
- package/dist/src/artifacts/corpus-tools/profile-edges.js +72 -0
- package/dist/src/artifacts/corpus-tools/profile-embed.js +113 -0
- package/dist/src/artifacts/corpus-tools/profile-generate-fm.js +167 -0
- package/dist/src/artifacts/corpus-tools/profile-generate.js +181 -0
- package/dist/src/artifacts/corpus-tools/profile-metrics.js +98 -0
- package/dist/src/artifacts/corpus-tools/profile-status.js +86 -0
- package/dist/src/artifacts/corpus-tools/profile-temporal.js +139 -0
- package/dist/src/artifacts/corpus-tools/radar-init.js +93 -0
- package/dist/src/artifacts/corpus-tools/radar-report.js +118 -0
- package/dist/src/artifacts/corpus-tools/radar-shared.js +189 -0
- package/dist/src/artifacts/corpus-tools/radar-status.js +68 -0
- package/dist/src/artifacts/corpus-tools/sidecar-lint.js +233 -0
- package/dist/src/artifacts/corpus-tools/sidecar-repair.js +277 -0
- package/dist/src/artifacts/corpus-tools/snapshot.js +591 -0
- package/dist/src/artifacts/corpus-tools/source-types.js +164 -0
- package/dist/src/artifacts/corpus-tools/vision-extract.js +243 -0
- package/dist/src/artifacts/corpus-views/build.js +126 -0
- package/dist/src/artifacts/corpus-views/corpus-config.js +124 -0
- package/dist/src/artifacts/corpus-views/ref-parser.js +437 -0
- package/dist/src/artifacts/corpus-views/renderers.js +353 -0
- package/dist/src/artifacts/corpus-views/taxonomies.js +131 -0
- package/dist/src/artifacts/dep-graph.js +173 -0
- package/dist/src/artifacts/discover-facets.js +375 -0
- package/dist/src/artifacts/embedding-index.js +354 -0
- package/dist/src/artifacts/enrichment/cli.js +240 -0
- package/dist/src/artifacts/enrichment/prompt.js +47 -0
- package/dist/src/artifacts/enrichment/store.js +90 -0
- package/dist/src/artifacts/enrichment/types.js +12 -0
- package/dist/src/artifacts/fortemi-core-query-adapter.js +475 -0
- package/dist/src/artifacts/fortemi-core-sync.js +251 -0
- package/dist/src/artifacts/fortemi-shard-export.js +44 -0
- package/dist/src/artifacts/fulltext.js +108 -0
- package/dist/src/artifacts/graph-backend.js +51 -0
- package/dist/src/artifacts/graph-query.js +247 -0
- package/dist/src/artifacts/hybrid-query.js +225 -0
- package/dist/src/artifacts/index-builder.js +1089 -0
- package/dist/src/artifacts/index-reader.js +112 -0
- package/dist/src/artifacts/index-status.js +193 -0
- package/dist/src/artifacts/legacy-index-migration.js +169 -0
- package/dist/src/artifacts/move.js +154 -0
- package/dist/src/artifacts/operational-state.js +239 -0
- package/dist/src/artifacts/prebuilt-build-lock.js +49 -0
- package/dist/src/artifacts/query-engine.js +1955 -0
- package/dist/src/artifacts/source-graph.js +529 -0
- package/dist/src/artifacts/stats.js +169 -0
- package/dist/src/artifacts/types.js +606 -0
- package/dist/src/artifacts/views/cli.js +293 -0
- package/dist/src/artifacts/views/definition.js +170 -0
- package/dist/src/artifacts/views/store.js +149 -0
- package/dist/src/artifacts/views/types.js +8 -0
- package/dist/src/artifacts/watcher.js +255 -0
- package/dist/src/catalog/builtin-models.json +636 -0
- package/dist/src/catalog/cli.js +228 -0
- package/dist/src/catalog/cli.mjs +289 -0
- package/dist/src/catalog/index.js +15 -0
- package/dist/src/catalog/loader.js +217 -0
- package/dist/src/catalog/loader.mjs +251 -0
- package/dist/src/catalog/sources.json +41 -0
- package/dist/src/catalog/types.js +8 -0
- package/dist/src/channel/manager.mjs +504 -0
- package/dist/src/cli/agent-spawn.js +178 -0
- package/dist/src/cli/cli-extension-loader.js +245 -0
- package/dist/src/cli/command-log.js +275 -0
- package/dist/src/cli/config-loader.js +232 -0
- package/dist/src/cli/env.js +82 -0
- package/dist/src/cli/errors.js +150 -0
- package/dist/src/cli/find-package-root.js +40 -0
- package/dist/src/cli/git-hooks.js +250 -0
- package/dist/src/cli/handlers/agentcard.js +206 -0
- package/dist/src/cli/handlers/artifacts.js +76 -0
- package/dist/src/cli/handlers/best-practices-audit.js +280 -0
- package/dist/src/cli/handlers/cockpit.js +137 -0
- package/dist/src/cli/handlers/command-log.js +37 -0
- package/dist/src/cli/handlers/daemon.js +59 -0
- package/dist/src/cli/handlers/diagnose.js +300 -0
- package/dist/src/cli/handlers/execution-mode.js +105 -0
- package/dist/src/cli/handlers/feedback.js +341 -0
- package/dist/src/cli/handlers/help.js +147 -0
- package/dist/src/cli/handlers/index.js +302 -0
- package/dist/src/cli/handlers/init.js +179 -0
- package/dist/src/cli/handlers/install.js +129 -0
- package/dist/src/cli/handlers/issues.js +46 -0
- package/dist/src/cli/handlers/lint.js +42 -0
- package/dist/src/cli/handlers/local-executor.js +315 -0
- package/dist/src/cli/handlers/marketplace.js +168 -0
- package/dist/src/cli/handlers/mc.js +1010 -0
- package/dist/src/cli/handlers/models.js +352 -0
- package/dist/src/cli/handlers/packages.js +155 -0
- package/dist/src/cli/handlers/ralph-launcher.js +717 -0
- package/dist/src/cli/handlers/ralph.js +575 -0
- package/dist/src/cli/handlers/refresh.js +432 -0
- package/dist/src/cli/handlers/regenerate.js +336 -0
- package/dist/src/cli/handlers/repo-access.js +109 -0
- package/dist/src/cli/handlers/run.js +165 -0
- package/dist/src/cli/handlers/runtime-info.js +301 -0
- package/dist/src/cli/handlers/sandbox.js +197 -0
- package/dist/src/cli/handlers/scaffolding.js +149 -0
- package/dist/src/cli/handlers/script-runner.js +121 -0
- package/dist/src/cli/handlers/sdlc-accelerate.js +148 -0
- package/dist/src/cli/handlers/serve.js +1835 -0
- package/dist/src/cli/handlers/session.js +348 -0
- package/dist/src/cli/handlers/setup.js +440 -0
- package/dist/src/cli/handlers/skill-lint.js +367 -0
- package/dist/src/cli/handlers/skill-usage.js +74 -0
- package/dist/src/cli/handlers/steward.js +567 -0
- package/dist/src/cli/handlers/subcommands.js +1702 -0
- package/dist/src/cli/handlers/team.js +337 -0
- package/dist/src/cli/handlers/types.js +11 -0
- package/dist/src/cli/handlers/use.js +2772 -0
- package/dist/src/cli/handlers/utilities.js +703 -0
- package/dist/src/cli/handlers/version.js +134 -0
- package/dist/src/cli/handlers/workspace-context.js +93 -0
- package/dist/src/cli/handlers/workspace.js +126 -0
- package/dist/src/cli/help-generator.js +178 -0
- package/dist/src/cli/hooks/builtin/activity-log-hook.js +126 -0
- package/dist/src/cli/hooks/executor.js +96 -0
- package/dist/src/cli/hooks/index.js +15 -0
- package/dist/src/cli/hooks/registry.js +143 -0
- package/dist/src/cli/hooks/types.js +13 -0
- package/dist/src/cli/log.js +492 -0
- package/dist/src/cli/project-isolation/detect.js +70 -0
- package/dist/src/cli/project-isolation/index.js +7 -0
- package/dist/src/cli/project-isolation/signals.js +17 -0
- package/dist/src/cli/project-isolation/warning.js +102 -0
- package/dist/src/cli/prompt-utils.js +155 -0
- package/dist/src/cli/provider-deployment-plan.js +126 -0
- package/dist/src/cli/provider-resolution.js +119 -0
- package/dist/src/cli/router.js +305 -0
- package/dist/src/cli/scope-resolver.js +392 -0
- package/dist/src/cli/skill-usage.js +616 -0
- package/dist/src/cli/ui.js +224 -0
- package/dist/src/cli/watch-service.js +203 -0
- package/dist/src/cli/workflow-orchestrator.js +478 -0
- package/dist/src/cli/workspace-signals.js +321 -0
- package/dist/src/community/footer.js +14 -0
- package/dist/src/community/links.js +123 -0
- package/dist/src/community/milestones.js +46 -0
- package/dist/src/community/nudge-policy.js +93 -0
- package/dist/src/config/aiwg-config.js +883 -0
- package/dist/src/config/cli.js +703 -0
- package/dist/src/config/gitignore.js +156 -0
- package/dist/src/config/project-artifacts.js +76 -0
- package/dist/src/config/user-config.js +428 -0
- package/dist/src/config/user-registry.js +127 -0
- package/dist/src/config/workspace.js +276 -0
- package/dist/src/contributors/detect.js +39 -0
- package/dist/src/contributors/discover.js +172 -0
- package/dist/src/contributors/heuristic.js +186 -0
- package/dist/src/contributors/index.js +11 -0
- package/dist/src/contributors/types.js +13 -0
- package/dist/src/contributors/validation.js +116 -0
- package/dist/src/data/community.schema.json +43 -0
- package/dist/src/data/community.yaml +16 -0
- package/dist/src/extensions/capability-index.js +270 -0
- package/dist/src/extensions/claude-hooks-installer.js +268 -0
- package/dist/src/extensions/commands/definitions.js +3485 -0
- package/dist/src/extensions/deployment-registration.js +463 -0
- package/dist/src/extensions/index.js +22 -0
- package/dist/src/extensions/loader.js +132 -0
- package/dist/src/extensions/managed-marker.js +100 -0
- package/dist/src/extensions/manifest.js +196 -0
- package/dist/src/extensions/project-local-activity.js +114 -0
- package/dist/src/extensions/project-local-discovery.js +261 -0
- package/dist/src/extensions/project-local-doctor.js +281 -0
- package/dist/src/extensions/project-local-gitignore.js +176 -0
- package/dist/src/extensions/project-local-paths.js +121 -0
- package/dist/src/extensions/project-local-promote.js +218 -0
- package/dist/src/extensions/project-local-remove.js +373 -0
- package/dist/src/extensions/project-local-scaffold.js +252 -0
- package/dist/src/extensions/project-quickref.js +291 -0
- package/dist/src/extensions/registry.js +368 -0
- package/dist/src/extensions/shadow-resolver.js +271 -0
- package/dist/src/extensions/types.js +86 -0
- package/dist/src/extensions/upstream-registry.js +195 -0
- package/dist/src/extensions/validation.js +631 -0
- package/dist/src/features/catalog.js +88 -0
- package/dist/src/features/cli.js +197 -0
- package/dist/src/features/installer.js +57 -0
- package/dist/src/features/paths.js +14 -0
- package/dist/src/features/runtime.js +21 -0
- package/dist/src/features/status.js +134 -0
- package/dist/src/hooks/laziness-detection.js +532 -0
- package/dist/src/intake/git-history-analyzer.js +486 -0
- package/dist/src/intake/index.js +9 -0
- package/dist/src/issues/cli.js +327 -0
- package/dist/src/issues/index.js +5 -0
- package/dist/src/issues/live.js +154 -0
- package/dist/src/issues/local.js +666 -0
- package/dist/src/issues/sync.js +143 -0
- package/dist/src/issues/types.js +2 -0
- package/dist/src/issues/workflows.js +164 -0
- package/dist/src/kb/cli.js +173 -0
- package/dist/src/lint/cli.js +171 -0
- package/dist/src/lint/loader.js +225 -0
- package/dist/src/lint/reporters.js +141 -0
- package/dist/src/lint/runner.js +364 -0
- package/dist/src/lint/types.js +10 -0
- package/dist/src/marketplace/cache.js +110 -0
- package/dist/src/marketplace/identifier.js +63 -0
- package/dist/src/marketplace/registry.js +36 -0
- package/dist/src/marketplace/sources/clawhub.js +53 -0
- package/dist/src/marketplace/sources/git.js +45 -0
- package/dist/src/marketplace/types.js +9 -0
- package/dist/src/mcp/adapters/codex-runtime.js +224 -0
- package/dist/src/mcp/cli.mjs +1197 -0
- package/dist/src/mcp/elicitation.mjs +149 -0
- package/dist/src/mcp/helpers.mjs +287 -0
- package/dist/src/mcp/index.mjs +11 -0
- package/dist/src/mcp/profiles.js +263 -0
- package/dist/src/mcp/profiles.mjs +360 -0
- package/dist/src/mcp/registry.js +376 -0
- package/dist/src/mcp/registry.mjs +387 -0
- package/dist/src/mcp/server.mjs +633 -0
- package/dist/src/mcp/test-simple.mjs +271 -0
- package/dist/src/mcp/tools/command-run.mjs +101 -0
- package/dist/src/mcp/tools/discovery.mjs +291 -0
- package/dist/src/mcp/tools/interaction.mjs +87 -0
- package/dist/src/mcp/tools/orchestration.mjs +320 -0
- package/dist/src/mcp/tools/subsystems.mjs +640 -0
- package/dist/src/memory/cli.js +166 -0
- package/dist/src/memory/project-registry.js +282 -0
- package/dist/src/metrics/artifact-metrics.js +184 -0
- package/dist/src/metrics/context-budget.js +174 -0
- package/dist/src/metrics/token-counter.js +133 -0
- package/dist/src/models/config-loader.js +228 -0
- package/dist/src/models/index.js +22 -0
- package/dist/src/models/model-capabilities.v1.json +120 -0
- package/dist/src/models/model-catalog.v1.json +96 -0
- package/dist/src/models/model-discovery.js +521 -0
- package/dist/src/models/provider-models.js +73 -0
- package/dist/src/models/provider-policy.js +273 -0
- package/dist/src/models/resolver.js +245 -0
- package/dist/src/models/router.js +70 -0
- package/dist/src/models/types.js +8 -0
- package/dist/src/models/wrapper-deployment.js +178 -0
- package/dist/src/models/wrapper-route.js +54 -0
- package/dist/src/ops/cli.js +268 -0
- package/dist/src/ops/registry.js +623 -0
- package/dist/src/packages/adapters/clawhub.js +113 -0
- package/dist/src/packages/adapters/git.js +153 -0
- package/dist/src/packages/adapters/gitea.js +93 -0
- package/dist/src/packages/adapters/github.js +69 -0
- package/dist/src/packages/adapters/local-cache.js +89 -0
- package/dist/src/packages/package-registry.js +143 -0
- package/dist/src/packages/registry.js +238 -0
- package/dist/src/packages/types.js +11 -0
- package/dist/src/plugin/cross-framework-ops.js +475 -0
- package/dist/src/plugin/framework-config-loader.js +235 -0
- package/dist/src/plugin/framework-detector.js +186 -0
- package/dist/src/plugin/framework-isolator.js +341 -0
- package/dist/src/plugin/framework-migration.js +371 -0
- package/dist/src/plugin/metadata-validator.js +906 -0
- package/dist/src/plugin/plugin-installer.js +436 -0
- package/dist/src/plugin/plugin-status.js +369 -0
- package/dist/src/plugin/plugin-uninstaller.js +473 -0
- package/dist/src/plugin/registry-validator.js +344 -0
- package/dist/src/plugin/skill-command-translator.js +415 -0
- package/dist/src/plugin/workspace-creator.js +189 -0
- package/dist/src/plugin/workspace-migrator.js +821 -0
- package/dist/src/policy/authorization.js +390 -0
- package/dist/src/policy/repo-access.js +215 -0
- package/dist/src/provenance/cli.js +16 -0
- package/dist/src/providers/capability-matrix.js +300 -0
- package/dist/src/providers/capability-matrix.yaml +514 -0
- package/dist/src/providers/provider-definitions.js +977 -0
- package/dist/src/providers/provider-definitions.mjs +159 -0
- package/dist/src/providers/provider-inventory.js +169 -0
- package/dist/src/quality/feedback-ab.js +174 -0
- package/dist/src/quality/feedback-collector.js +176 -0
- package/dist/src/quality/feedback-tracker.js +123 -0
- package/dist/src/quality/patterns/adr.json +24 -0
- package/dist/src/quality/patterns/sad.json +24 -0
- package/dist/src/quality/patterns/test-plan.json +24 -0
- package/dist/src/quality/patterns/use-case.json +24 -0
- package/dist/src/quality/scoring.js +157 -0
- package/dist/src/reflections/cli.js +15 -0
- package/dist/src/release/plan.js +139 -0
- package/dist/src/research/cache/manager.js +227 -0
- package/dist/src/research/clients/arxiv.js +156 -0
- package/dist/src/research/clients/base.js +197 -0
- package/dist/src/research/clients/crossref.js +112 -0
- package/dist/src/research/clients/semantic-scholar.js +126 -0
- package/dist/src/research/clients/unpaywall.js +87 -0
- package/dist/src/research/index.js +14 -0
- package/dist/src/research/query-cli.js +336 -0
- package/dist/src/research/services/acquisition.js +313 -0
- package/dist/src/research/services/archival.js +225 -0
- package/dist/src/research/services/citation.js +279 -0
- package/dist/src/research/services/discovery.js +243 -0
- package/dist/src/research/services/documentation.js +269 -0
- package/dist/src/research/services/index.js +14 -0
- package/dist/src/research/services/provenance.js +279 -0
- package/dist/src/research/services/quality.js +370 -0
- package/dist/src/research/services/types.js +7 -0
- package/dist/src/research/storage-cli.js +20 -0
- package/dist/src/research/types.js +47 -0
- package/dist/src/resources/index.d.ts +2 -0
- package/dist/src/resources/index.js +2 -0
- package/dist/src/resources/web-release.d.ts +89 -0
- package/dist/src/resources/web-release.js +886 -0
- package/dist/src/rlm/cache/cli.js +162 -0
- package/dist/src/rlm/cache/hash.js +35 -0
- package/dist/src/rlm/cache/store.js +170 -0
- package/dist/src/rlm/cache/types.js +8 -0
- package/dist/src/rlm/cli.js +650 -0
- package/dist/src/serve/a2a-terminal-observer.js +120 -0
- package/dist/src/serve/agent-router.js +205 -0
- package/dist/src/serve/dispatch-router.js +171 -0
- package/dist/src/serve/executor-registry.js +715 -0
- package/dist/src/serve/mission-conductor.js +177 -0
- package/dist/src/serve/orchestrator-adapter.js +113 -0
- package/dist/src/serve/orchestrator-override.js +94 -0
- package/dist/src/serve/orchestrator-pty.js +133 -0
- package/dist/src/serve/pty-bridge.js +799 -0
- package/dist/src/serve/sandbox-registry.js +832 -0
- package/dist/src/serve/screen-reader.js +228 -0
- package/dist/src/serve/stack-adapters.js +68 -0
- package/dist/src/serve/telemetry.js +143 -0
- package/dist/src/skills/adapters/clawhub.js +250 -0
- package/dist/src/skills/adapters/local.js +335 -0
- package/dist/src/skills/adapters/openclaw.js +316 -0
- package/dist/src/skills/cli.js +334 -0
- package/dist/src/skills/registry.js +117 -0
- package/dist/src/skills/run.js +259 -0
- package/dist/src/skills/runtime.js +94 -0
- package/dist/src/skills/types.js +10 -0
- package/dist/src/smiths/agentsmith/demo.js +116 -0
- package/dist/src/smiths/agentsmith/examples.js +300 -0
- package/dist/src/smiths/agentsmith/generator.js +503 -0
- package/dist/src/smiths/agentsmith/index.js +11 -0
- package/dist/src/smiths/agentsmith/types.js +8 -0
- package/dist/src/smiths/commandsmith/example.js +220 -0
- package/dist/src/smiths/commandsmith/generator.js +288 -0
- package/dist/src/smiths/commandsmith/index.js +41 -0
- package/dist/src/smiths/commandsmith/templates.js +296 -0
- package/dist/src/smiths/commandsmith/types.js +7 -0
- package/dist/src/smiths/context-pipeline/aiwg-md.js +104 -0
- package/dist/src/smiths/context-pipeline/allowlist.js +195 -0
- package/dist/src/smiths/context-pipeline/claude-hook.js +127 -0
- package/dist/src/smiths/context-pipeline/discovery.js +187 -0
- package/dist/src/smiths/context-pipeline/external-links-section.js +49 -0
- package/dist/src/smiths/context-pipeline/finalization.js +147 -0
- package/dist/src/smiths/context-pipeline/generator.js +477 -0
- package/dist/src/smiths/context-pipeline/index.js +30 -0
- package/dist/src/smiths/context-pipeline/legacy-inject.js +103 -0
- package/dist/src/smiths/context-pipeline/managed-hook.js +95 -0
- package/dist/src/smiths/context-pipeline/overflow.js +212 -0
- package/dist/src/smiths/context-pipeline/parallelism-section.js +94 -0
- package/dist/src/smiths/context-pipeline/provider-policy.js +64 -0
- package/dist/src/smiths/context-pipeline/sanitizer.js +93 -0
- package/dist/src/smiths/context-pipeline/types.js +23 -0
- package/dist/src/smiths/context-pipeline/workspace-context.js +988 -0
- package/dist/src/smiths/hook-bridge/codex-translator.js +129 -0
- package/dist/src/smiths/hook-bridge/copilot-translator.js +64 -0
- package/dist/src/smiths/hook-bridge/factory-translator.js +43 -0
- package/dist/src/smiths/hook-bridge/hermes-translator.js +88 -0
- package/dist/src/smiths/hook-bridge/index.js +79 -0
- package/dist/src/smiths/hook-bridge/loader.js +116 -0
- package/dist/src/smiths/hook-bridge/shim.js +89 -0
- package/dist/src/smiths/hook-bridge/types.js +31 -0
- package/dist/src/smiths/index.js +39 -0
- package/dist/src/smiths/mcpsmith/analyzers/cli-analyzer.js +390 -0
- package/dist/src/smiths/mcpsmith/example.js +119 -0
- package/dist/src/smiths/mcpsmith/generator.js +236 -0
- package/dist/src/smiths/mcpsmith/index.js +17 -0
- package/dist/src/smiths/mcpsmith/types.js +10 -0
- package/dist/src/smiths/platform-paths.js +101 -0
- package/dist/src/smiths/skillsmith/codex-sidecar.js +85 -0
- package/dist/src/smiths/skillsmith/collision-detector.js +249 -0
- package/dist/src/smiths/skillsmith/demo.js +83 -0
- package/dist/src/smiths/skillsmith/examples.js +161 -0
- package/dist/src/smiths/skillsmith/generator.js +316 -0
- package/dist/src/smiths/skillsmith/index.js +12 -0
- package/dist/src/smiths/skillsmith/namespace-adapter.js +180 -0
- package/dist/src/smiths/skillsmith/platform-resolver.js +157 -0
- package/dist/src/smiths/skillsmith/types.js +9 -0
- package/dist/src/smiths/toolsmith/index.js +10 -0
- package/dist/src/smiths/toolsmith/runtime-discovery.mjs +710 -0
- package/dist/src/smiths/toolsmith/types.js +8 -0
- package/dist/src/storage/backends/fortemi.js +227 -0
- package/dist/src/storage/backends/fs.js +134 -0
- package/dist/src/storage/backends/logseq.js +309 -0
- package/dist/src/storage/backends/obsidian.js +233 -0
- package/dist/src/storage/cli.js +468 -0
- package/dist/src/storage/config.js +272 -0
- package/dist/src/storage/index.js +108 -0
- package/dist/src/storage/subsystem-cli.js +201 -0
- package/dist/src/storage/types.js +33 -0
- package/dist/src/testing/corpus/corpus-builder.js +383 -0
- package/dist/src/testing/corpus/ground-truth-manager.js +460 -0
- package/dist/src/testing/corpus/index.js +10 -0
- package/dist/src/testing/fixtures/index.js +9 -0
- package/dist/src/testing/fixtures/test-data-factory.js +472 -0
- package/dist/src/testing/generators/index.js +11 -0
- package/dist/src/testing/generators/test-case-generator.js +393 -0
- package/dist/src/testing/generators/test-code-generator.js +384 -0
- package/dist/src/testing/generators/use-case-parser.js +325 -0
- package/dist/src/testing/index.js +11 -0
- package/dist/src/tracker/capability-protocol.js +118 -0
- package/dist/src/update/checker.mjs +396 -0
- package/dist/src/update/notifier.mjs +235 -0
- package/dist/src/writing/content-diversifier.js +804 -0
- package/dist/src/writing/example-generator.js +959 -0
- package/dist/src/writing/example-templates.js +99 -0
- package/dist/src/writing/pattern-library.js +486 -0
- package/dist/src/writing/patterns/banned-phrases.json +4413 -0
- package/dist/src/writing/patterns/formulaic-structures.json +1300 -0
- package/dist/src/writing/patterns/generic-adjectives.json +1510 -0
- package/dist/src/writing/patterns/hedging-language.json +2096 -0
- package/dist/src/writing/patterns/transition-words.json +1285 -0
- package/dist/src/writing/patterns/weak-verbs.json +1112 -0
- package/dist/src/writing/prompt-optimizer.js +660 -0
- package/dist/src/writing/prompt-templates.js +583 -0
- package/dist/src/writing/scoring-config-loader.js +168 -0
- package/dist/src/writing/validation-engine.js +595 -0
- package/dist/src/writing/validation-rules.js +380 -0
- package/dist/src/writing/voice-analyzer.js +439 -0
- package/dist/src/writing/voice-calibration.js +661 -0
- package/dist/src/writing/voice-profiles.json +588 -0
- package/package.json +65 -9
|
@@ -0,0 +1,1010 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mission Control Command Handler
|
|
3
|
+
*
|
|
4
|
+
* Multi-loop background orchestration dashboard. Lets an orchestrator
|
|
5
|
+
* spawn multiple long-running agent loops, monitor all simultaneously,
|
|
6
|
+
* and react to completions or failures without blocking the primary session.
|
|
7
|
+
*
|
|
8
|
+
* Subcommands: start, dispatch, status, watch, abort, pause, resume, stop, list
|
|
9
|
+
*
|
|
10
|
+
* @implements @agentic/code/frameworks/sdlc-complete/rules/self-maintenance.md
|
|
11
|
+
* @source @src/cli/router.ts
|
|
12
|
+
* @issue #483
|
|
13
|
+
*/
|
|
14
|
+
import * as ui from '../ui.js';
|
|
15
|
+
import { promises as fs } from 'node:fs';
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
import { randomBytes } from 'node:crypto';
|
|
18
|
+
// ── Constants ────────────────────────────────────────────────
|
|
19
|
+
const MC_ROOT = '.aiwg/ralph-external/mc';
|
|
20
|
+
const SESSIONS_DIR = join(MC_ROOT, 'sessions');
|
|
21
|
+
// ── Helpers ──────────────────────────────────────────────────
|
|
22
|
+
function genId(prefix) {
|
|
23
|
+
return `${prefix}-${Date.now().toString(36)}-${randomBytes(3).toString('hex')}`;
|
|
24
|
+
}
|
|
25
|
+
async function ensureDir(dir) {
|
|
26
|
+
await fs.mkdir(dir, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
async function readSession(sessionId) {
|
|
29
|
+
const path = join(SESSIONS_DIR, sessionId, 'session.json');
|
|
30
|
+
try {
|
|
31
|
+
const raw = await fs.readFile(path, 'utf-8');
|
|
32
|
+
return JSON.parse(raw);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function writeSession(session) {
|
|
39
|
+
const dir = join(SESSIONS_DIR, session.id);
|
|
40
|
+
await ensureDir(dir);
|
|
41
|
+
session.updatedAt = new Date().toISOString();
|
|
42
|
+
await fs.writeFile(join(dir, 'session.json'), JSON.stringify(session, null, 2));
|
|
43
|
+
}
|
|
44
|
+
async function appendLog(sessionId, event) {
|
|
45
|
+
const logPath = join(SESSIONS_DIR, sessionId, 'log.jsonl');
|
|
46
|
+
const entry = JSON.stringify({ ...event, ts: new Date().toISOString() });
|
|
47
|
+
await fs.appendFile(logPath, entry + '\n');
|
|
48
|
+
}
|
|
49
|
+
async function listSessions() {
|
|
50
|
+
try {
|
|
51
|
+
const entries = await fs.readdir(SESSIONS_DIR, { withFileTypes: true });
|
|
52
|
+
const sessions = [];
|
|
53
|
+
for (const entry of entries) {
|
|
54
|
+
if (entry.isDirectory()) {
|
|
55
|
+
const s = await readSession(entry.name);
|
|
56
|
+
if (s)
|
|
57
|
+
sessions.push(s);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return sessions;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async function findActiveSession(sessionIdArg) {
|
|
67
|
+
if (sessionIdArg)
|
|
68
|
+
return readSession(sessionIdArg);
|
|
69
|
+
// Find latest active session
|
|
70
|
+
const sessions = await listSessions();
|
|
71
|
+
const active = sessions
|
|
72
|
+
.filter(s => s.state === 'active' || s.state === 'paused')
|
|
73
|
+
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
74
|
+
return active[0] || null;
|
|
75
|
+
}
|
|
76
|
+
function parseFlag(args, flag) {
|
|
77
|
+
for (let i = 0; i < args.length; i++) {
|
|
78
|
+
if (args[i] === flag) {
|
|
79
|
+
return i + 1 < args.length ? args[i + 1] : undefined;
|
|
80
|
+
}
|
|
81
|
+
// Support --flag=value — previously silently ignored, which for budget
|
|
82
|
+
// flags meant an unbounded loop the operator believed was capped (#1770)
|
|
83
|
+
if (args[i].startsWith(`${flag}=`)) {
|
|
84
|
+
return args[i].slice(flag.length + 1);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
function hasFlag(args, flag) {
|
|
90
|
+
return args.includes(flag);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Parse a numeric flag. A flag that is PRESENT but not a positive number is a
|
|
94
|
+
* usage error collected into `invalidSink` — silently dropping it used to
|
|
95
|
+
* dispatch missions with no ceiling while the operator believed one applied
|
|
96
|
+
* (#1770).
|
|
97
|
+
*/
|
|
98
|
+
function parseNumberFlag(args, flag, invalidSink) {
|
|
99
|
+
const raw = parseFlag(args, flag);
|
|
100
|
+
if (raw === undefined)
|
|
101
|
+
return undefined;
|
|
102
|
+
const value = Number(raw);
|
|
103
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
104
|
+
invalidSink?.push(`${flag} (got '${raw}')`);
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
return value;
|
|
108
|
+
}
|
|
109
|
+
function wantsHelp(args) {
|
|
110
|
+
return args.includes('--help') || args.includes('-h');
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Per-subcommand usage strings (#1440).
|
|
114
|
+
*
|
|
115
|
+
* `aiwg mc <subcommand> --help` MUST print this text and exit before any
|
|
116
|
+
* side-effecting logic. The previous behavior — running `aiwg mc start --help`
|
|
117
|
+
* actually created a session — violated the universal `--help` convention.
|
|
118
|
+
*/
|
|
119
|
+
const subcommandUsage = {
|
|
120
|
+
start: `Usage: aiwg mc start [--name "<label>"] [--max-missions N]
|
|
121
|
+
|
|
122
|
+
Create a new Mission Control session for tracking background missions.
|
|
123
|
+
|
|
124
|
+
--name <label> Human-readable session name (default: "Mission YYYY-MM-DD")
|
|
125
|
+
--max-missions N Capacity limit for queued missions (default: 10)
|
|
126
|
+
|
|
127
|
+
Note: 'start' only creates the session. Use 'dispatch' to queue missions and
|
|
128
|
+
'run' to drain the queue.`,
|
|
129
|
+
dispatch: `Usage: aiwg mc dispatch <session-id> "<objective>" [options]
|
|
130
|
+
|
|
131
|
+
Queue a mission onto a session. Does NOT execute — use 'aiwg mc run' to launch.
|
|
132
|
+
|
|
133
|
+
--completion "<criteria>" Verifiable completion criteria (required for 'mc run')
|
|
134
|
+
--priority <level> Priority hint (default: normal)
|
|
135
|
+
--max-iterations N Ralph iteration cap when launched (default: 10)
|
|
136
|
+
--max-total-tokens N Hard cumulative token ceiling when observable
|
|
137
|
+
--max-output-tokens N Hard cumulative output-token ceiling
|
|
138
|
+
--max-tool-calls N Hard cumulative tool-call ceiling
|
|
139
|
+
--max-total-cost USD Hard cumulative provider-reported spend ceiling
|
|
140
|
+
--max-wall-clock-minutes N Hard cumulative runtime ceiling
|
|
141
|
+
--exploration-quota K Require structural variant after K flat cycles
|
|
142
|
+
(off unless declared; no default K)
|
|
143
|
+
--budget-stop-policy P completion-wins (default) | budget-wins
|
|
144
|
+
--mode pty-orchestrator PTY-orchestrator mode (requires --target-agent)
|
|
145
|
+
--target-agent <id> Required for --mode pty-orchestrator`,
|
|
146
|
+
run: `Usage: aiwg mc run [<session-id>] [--accept-cost]
|
|
147
|
+
|
|
148
|
+
Drain queued missions in a session by launching each as a ralph loop. Missions
|
|
149
|
+
without --completion criteria are skipped with a warning.
|
|
150
|
+
|
|
151
|
+
--accept-cost Skip the cost-warning gate (required for non-TTY contexts
|
|
152
|
+
when estimated cumulative cost exceeds $5). See #1450.`,
|
|
153
|
+
status: `Usage: aiwg mc status [<session-id>] [--json]
|
|
154
|
+
|
|
155
|
+
Show mission status for a session. Auto-syncs from ralph loop state files.
|
|
156
|
+
|
|
157
|
+
--json Emit JSON instead of human-readable table`,
|
|
158
|
+
watch: `Usage: aiwg mc watch [<session-id>]
|
|
159
|
+
|
|
160
|
+
Live-monitor mission progress (non-interactive context prints status once).`,
|
|
161
|
+
abort: `Usage: aiwg mc abort <session-id> <mission-id>
|
|
162
|
+
|
|
163
|
+
Mark a specific mission as aborted.`,
|
|
164
|
+
pause: `Usage: aiwg mc pause [<session-id>]
|
|
165
|
+
|
|
166
|
+
Pause an active session; running missions transition to 'paused' status.`,
|
|
167
|
+
resume: `Usage: aiwg mc resume [<session-id>]
|
|
168
|
+
|
|
169
|
+
Resume a paused session; paused missions transition back to 'running'.`,
|
|
170
|
+
stop: `Usage: aiwg mc stop [<session-id>] [--drain]
|
|
171
|
+
|
|
172
|
+
Shut down a session.
|
|
173
|
+
|
|
174
|
+
--drain Cancel queued, let running finish (default: abort all non-completed)`,
|
|
175
|
+
list: `Usage: aiwg mc list [--json]
|
|
176
|
+
|
|
177
|
+
List all Mission Control sessions.`,
|
|
178
|
+
agents: `Usage: aiwg mc agents [filters] [--json]
|
|
179
|
+
|
|
180
|
+
Query routable agents from a local 'aiwg serve' instance.
|
|
181
|
+
|
|
182
|
+
--framework <name> Require an AIWG framework (repeatable)
|
|
183
|
+
--sandbox <id> Restrict to a sandbox
|
|
184
|
+
--agent <id> Restrict to a specific agent ID
|
|
185
|
+
--name <n> Match by logical name
|
|
186
|
+
--max-cpu <pct> Reject agents above this CPU %
|
|
187
|
+
--min-memory <gb> Reject agents below this memory threshold
|
|
188
|
+
--json Output raw JSON`,
|
|
189
|
+
};
|
|
190
|
+
function printSubcommandHelp(name) {
|
|
191
|
+
const text = subcommandUsage[name];
|
|
192
|
+
ui.blank();
|
|
193
|
+
if (text) {
|
|
194
|
+
console.log(` ${text}`);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
console.log(` No detailed help for 'aiwg mc ${name}'. Run 'aiwg mc --help' for the full reference.`);
|
|
198
|
+
}
|
|
199
|
+
ui.blank();
|
|
200
|
+
}
|
|
201
|
+
function getPositionalArgs(args) {
|
|
202
|
+
const positional = [];
|
|
203
|
+
for (let i = 0; i < args.length; i++) {
|
|
204
|
+
if (args[i].startsWith('--')) {
|
|
205
|
+
// Skip flag and its value if it has one
|
|
206
|
+
if (i + 1 < args.length && !args[i + 1].startsWith('--'))
|
|
207
|
+
i++;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
positional.push(args[i]);
|
|
211
|
+
}
|
|
212
|
+
return positional;
|
|
213
|
+
}
|
|
214
|
+
// ── Subcommand handlers ──────────────────────────────────────
|
|
215
|
+
async function mcStart(ctx) {
|
|
216
|
+
const name = parseFlag(ctx.args, '--name') || `Mission ${new Date().toISOString().slice(0, 10)}`;
|
|
217
|
+
const maxMissions = parseInt(parseFlag(ctx.args, '--max-missions') || '10', 10);
|
|
218
|
+
const session = {
|
|
219
|
+
id: genId('mc'),
|
|
220
|
+
name,
|
|
221
|
+
state: 'active',
|
|
222
|
+
maxMissions,
|
|
223
|
+
createdAt: new Date().toISOString(),
|
|
224
|
+
updatedAt: new Date().toISOString(),
|
|
225
|
+
missions: [],
|
|
226
|
+
};
|
|
227
|
+
await writeSession(session);
|
|
228
|
+
await appendLog(session.id, { event: 'session_started', name, maxMissions });
|
|
229
|
+
ui.blank();
|
|
230
|
+
console.log(` ${ui.brandMark()} ${ui.bold('Mission Control')} — ${ui.accent(name)}`);
|
|
231
|
+
ui.rule();
|
|
232
|
+
ui.success(`Session started: ${session.id}`);
|
|
233
|
+
ui.info(`Max missions: ${maxMissions}`);
|
|
234
|
+
// #1439: the lifecycle is start → dispatch → run → status. State this
|
|
235
|
+
// up-front so users don't get stuck wondering why missions never execute.
|
|
236
|
+
ui.info(`Next: \`aiwg mc dispatch ${session.id} "<objective>" --completion "<criteria>"\``);
|
|
237
|
+
ui.info(`Then: \`aiwg mc run ${session.id}\` to drain the queue (missions stay 'queued' until you do).`);
|
|
238
|
+
ui.blank();
|
|
239
|
+
return { exitCode: 0, message: session.id };
|
|
240
|
+
}
|
|
241
|
+
async function mcDispatch(ctx) {
|
|
242
|
+
const positional = getPositionalArgs(ctx.args);
|
|
243
|
+
const sessionId = positional[0];
|
|
244
|
+
const objective = positional.slice(1).join(' ') || parseFlag(ctx.args, '--objective');
|
|
245
|
+
const completion = parseFlag(ctx.args, '--completion');
|
|
246
|
+
const priority = parseFlag(ctx.args, '--priority') || 'normal';
|
|
247
|
+
const invalidFlags = [];
|
|
248
|
+
const maxIterationsRaw = parseFlag(ctx.args, '--max-iterations');
|
|
249
|
+
let maxIterations = 10;
|
|
250
|
+
if (maxIterationsRaw !== undefined) {
|
|
251
|
+
const parsedIterations = parseInt(maxIterationsRaw, 10);
|
|
252
|
+
if (!Number.isFinite(parsedIterations) || parsedIterations <= 0) {
|
|
253
|
+
invalidFlags.push(`--max-iterations (got '${maxIterationsRaw}')`);
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
maxIterations = parsedIterations;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const maxTotalTokens = parseNumberFlag(ctx.args, '--max-total-tokens', invalidFlags);
|
|
260
|
+
const maxOutputTokens = parseNumberFlag(ctx.args, '--max-output-tokens', invalidFlags);
|
|
261
|
+
const maxToolCalls = parseNumberFlag(ctx.args, '--max-tool-calls', invalidFlags);
|
|
262
|
+
const maxTotalCost = parseNumberFlag(ctx.args, '--max-total-cost', invalidFlags);
|
|
263
|
+
const maxWallClockMinutes = parseNumberFlag(ctx.args, '--max-wall-clock-minutes', invalidFlags);
|
|
264
|
+
const explorationQuota = parseNumberFlag(ctx.args, '--exploration-quota', invalidFlags);
|
|
265
|
+
const budgetStopPolicyRaw = parseFlag(ctx.args, '--budget-stop-policy');
|
|
266
|
+
let budgetStopPolicy;
|
|
267
|
+
if (budgetStopPolicyRaw !== undefined) {
|
|
268
|
+
if (budgetStopPolicyRaw === 'completion-wins' || budgetStopPolicyRaw === 'budget-wins') {
|
|
269
|
+
budgetStopPolicy = budgetStopPolicyRaw;
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
invalidFlags.push(`--budget-stop-policy (got '${budgetStopPolicyRaw}', expected completion-wins|budget-wins)`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
const modeRaw = parseFlag(ctx.args, '--mode') || 'direct';
|
|
276
|
+
const mode = modeRaw === 'pty-orchestrator' ? 'pty-orchestrator' : 'direct';
|
|
277
|
+
const targetAgent = parseFlag(ctx.args, '--target-agent');
|
|
278
|
+
if (!objective) {
|
|
279
|
+
// #1438: keep this in sync with subcommandUsage.dispatch above so 'mc
|
|
280
|
+
// dispatch' with bad args and 'mc dispatch --help' agree on flags.
|
|
281
|
+
ui.error('Usage: aiwg mc dispatch <session-id> "<objective>" [--completion "<criteria>"] [--max-iterations N] [--priority <level>] [--mode pty-orchestrator] [--target-agent <agent-id>]');
|
|
282
|
+
return { exitCode: 1 };
|
|
283
|
+
}
|
|
284
|
+
if (invalidFlags.length > 0) {
|
|
285
|
+
ui.error(`Invalid numeric flag value(s): ${invalidFlags.join(', ')}. Budget/quota flags require positive numbers. Mission not dispatched.`);
|
|
286
|
+
return { exitCode: 1 };
|
|
287
|
+
}
|
|
288
|
+
if (mode === 'pty-orchestrator' && !targetAgent) {
|
|
289
|
+
ui.error('--mode pty-orchestrator requires --target-agent <agent-id>');
|
|
290
|
+
return { exitCode: 1 };
|
|
291
|
+
}
|
|
292
|
+
const session = await findActiveSession(sessionId);
|
|
293
|
+
if (!session) {
|
|
294
|
+
ui.error(sessionId ? `Session not found: ${sessionId}` : 'No active session. Run `aiwg mc start` first.');
|
|
295
|
+
return { exitCode: 1 };
|
|
296
|
+
}
|
|
297
|
+
if (session.missions.length >= session.maxMissions) {
|
|
298
|
+
ui.error(`Session at capacity (${session.maxMissions} missions). Increase with --max-missions or stop completed missions.`);
|
|
299
|
+
return { exitCode: 1 };
|
|
300
|
+
}
|
|
301
|
+
// #1361: Check project-level parallelism cap. Active missions = running or
|
|
302
|
+
// queued; if at cap, warn but still queue (FIFO behavior — the mission goes
|
|
303
|
+
// into the session as 'queued' and will run when a slot frees up).
|
|
304
|
+
let capWarning;
|
|
305
|
+
try {
|
|
306
|
+
const { readAiwgConfig, resolveParallelism } = await import('../../config/aiwg-config.js');
|
|
307
|
+
const cfg = await readAiwgConfig(ctx.cwd || process.cwd());
|
|
308
|
+
if (cfg) {
|
|
309
|
+
const resolved = resolveParallelism(cfg.parallelism, cfg.providers[0]);
|
|
310
|
+
const activeCount = session.missions.filter(m => m.status === 'running' || m.status === 'queued' || m.status === 'paused').length;
|
|
311
|
+
if (activeCount >= resolved.max_parallel_mc_missions) {
|
|
312
|
+
capWarning = `Active missions (${activeCount}) at or above project parallelism cap (${resolved.max_parallel_mc_missions}). Mission will queue; bump via 'aiwg config set --project parallelism.max_parallel_mc_missions N'.`;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
// Non-fatal — config read failure doesn't block dispatch
|
|
318
|
+
}
|
|
319
|
+
const mission = {
|
|
320
|
+
id: genId('m'),
|
|
321
|
+
objective,
|
|
322
|
+
completion,
|
|
323
|
+
status: 'queued',
|
|
324
|
+
loop: 0,
|
|
325
|
+
maxIterations,
|
|
326
|
+
maxTotalTokens,
|
|
327
|
+
maxOutputTokens,
|
|
328
|
+
maxToolCalls,
|
|
329
|
+
maxTotalCost,
|
|
330
|
+
maxWallClockMinutes,
|
|
331
|
+
explorationQuota,
|
|
332
|
+
budgetStopPolicy,
|
|
333
|
+
priority,
|
|
334
|
+
mode,
|
|
335
|
+
targetAgent: targetAgent || undefined,
|
|
336
|
+
};
|
|
337
|
+
session.missions.push(mission);
|
|
338
|
+
await writeSession(session);
|
|
339
|
+
await appendLog(session.id, {
|
|
340
|
+
event: 'mission_dispatched',
|
|
341
|
+
missionId: mission.id,
|
|
342
|
+
objective,
|
|
343
|
+
priority,
|
|
344
|
+
mode,
|
|
345
|
+
targetAgent,
|
|
346
|
+
lfdBudgets: {
|
|
347
|
+
maxTotalTokens,
|
|
348
|
+
maxOutputTokens,
|
|
349
|
+
maxToolCalls,
|
|
350
|
+
maxTotalCost,
|
|
351
|
+
maxWallClockMinutes,
|
|
352
|
+
explorationQuota,
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
if (capWarning)
|
|
356
|
+
ui.warn(capWarning);
|
|
357
|
+
ui.success(`Dispatched mission ${mission.id}: ${objective}`);
|
|
358
|
+
const modeLabel = mode === 'pty-orchestrator' ? ` | Mode: PTY orchestrator → ${targetAgent}` : '';
|
|
359
|
+
ui.info(`Priority: ${priority} | Max iterations: ${maxIterations}${modeLabel}`);
|
|
360
|
+
const lfdLimits = [
|
|
361
|
+
maxTotalTokens ? `total tokens ${maxTotalTokens}` : null,
|
|
362
|
+
maxOutputTokens ? `output tokens ${maxOutputTokens}` : null,
|
|
363
|
+
maxToolCalls ? `tool calls ${maxToolCalls}` : null,
|
|
364
|
+
maxTotalCost ? `total cost $${maxTotalCost}` : null,
|
|
365
|
+
maxWallClockMinutes ? `wall clock ${maxWallClockMinutes}m` : null,
|
|
366
|
+
explorationQuota ? `exploration quota ${explorationQuota}` : null,
|
|
367
|
+
].filter(Boolean);
|
|
368
|
+
if (lfdLimits.length > 0) {
|
|
369
|
+
ui.info(`LFD limits: ${lfdLimits.join(' | ')}`);
|
|
370
|
+
}
|
|
371
|
+
// #1439: dispatch alone does NOT execute the mission. Surface the next step
|
|
372
|
+
// so the user knows the queue won't drain on its own.
|
|
373
|
+
ui.info(`Next: run \`aiwg mc run ${session.id}\` to launch queued missions as ralph loops.`);
|
|
374
|
+
return { exitCode: 0, message: mission.id };
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Drain queued missions in a session by launching each as a ralph loop (#1439).
|
|
378
|
+
*
|
|
379
|
+
* Before this command existed, missions sat in `queued` status forever because
|
|
380
|
+
* no supervisor process drained the queue. `mc run` is the explicit supervisor
|
|
381
|
+
* step: for every queued direct-mode mission, spawn a detached ralph process
|
|
382
|
+
* via launchExternalRalph(), record the resulting loopId + pid on the mission,
|
|
383
|
+
* and flip the mission to `running`.
|
|
384
|
+
*
|
|
385
|
+
* Status sync back from ralph to mc happens lazily via syncMissionsFromRalph()
|
|
386
|
+
* called from `mc status` and `mc watch` — no separate daemon needed.
|
|
387
|
+
*
|
|
388
|
+
* Limitations of cycle 1:
|
|
389
|
+
* - pty-orchestrator missions are skipped with a warning (separate codepath)
|
|
390
|
+
* - missions launch sequentially (each call to launchExternalRalph spawns a
|
|
391
|
+
* detached process, so they DO run in parallel after launch — the loop here
|
|
392
|
+
* is just for orderly dispatch, not for parallel scheduling)
|
|
393
|
+
*/
|
|
394
|
+
async function mcRun(ctx) {
|
|
395
|
+
const positional = getPositionalArgs(ctx.args);
|
|
396
|
+
const sessionId = positional[0];
|
|
397
|
+
const acceptCost = hasFlag(ctx.args, '--accept-cost');
|
|
398
|
+
const session = await findActiveSession(sessionId);
|
|
399
|
+
if (!session) {
|
|
400
|
+
ui.error(sessionId ? `Session not found: ${sessionId}` : 'No active session. Run `aiwg mc start` first.');
|
|
401
|
+
return { exitCode: 1 };
|
|
402
|
+
}
|
|
403
|
+
const queued = session.missions.filter((m) => m.status === 'queued');
|
|
404
|
+
if (queued.length === 0) {
|
|
405
|
+
ui.info(`No queued missions in session ${session.id}. Run \`aiwg mc dispatch ${session.id} "<objective>"\` to add one.`);
|
|
406
|
+
return { exitCode: 0 };
|
|
407
|
+
}
|
|
408
|
+
// #1450 P0: cost warning gate.
|
|
409
|
+
//
|
|
410
|
+
// Each headless claude session pays a ~$1.60 cache-creation cost on iteration
|
|
411
|
+
// 1 before any user-meaningful work (sonnet baseline; opus is ~$3.90). Across
|
|
412
|
+
// N missions × M iterations the floor compounds quickly. Warn before launch
|
|
413
|
+
// and refuse in non-TTY contexts unless --accept-cost is set.
|
|
414
|
+
//
|
|
415
|
+
// Estimate is intentionally conservative: cumulative iteration floor =
|
|
416
|
+
// missions × max_iterations × sonnet_cache_cost. Real spend may be lower if
|
|
417
|
+
// missions complete in fewer iterations.
|
|
418
|
+
const eligible = queued.filter(m => m.mode !== 'pty-orchestrator' && !!m.completion);
|
|
419
|
+
const SONNET_CACHE_USD = 1.60;
|
|
420
|
+
const iterFloor = eligible.reduce((sum, m) => sum + m.maxIterations, 0);
|
|
421
|
+
const estimateUsd = iterFloor * SONNET_CACHE_USD;
|
|
422
|
+
const COST_WARNING_THRESHOLD_USD = 5.0;
|
|
423
|
+
if (estimateUsd >= COST_WARNING_THRESHOLD_USD && !acceptCost) {
|
|
424
|
+
ui.blank();
|
|
425
|
+
ui.warn(`Cost estimate: ~$${estimateUsd.toFixed(2)} (${eligible.length} missions × iteration floors × ~$${SONNET_CACHE_USD.toFixed(2)} cache cost per claude headless iter).`);
|
|
426
|
+
ui.warn('Actual spend may be lower if missions complete early, higher if model is opus or context grows.');
|
|
427
|
+
if (!process.stdout.isTTY) {
|
|
428
|
+
ui.error('Refusing to launch in non-interactive context. Re-run with `--accept-cost` to proceed.');
|
|
429
|
+
return { exitCode: 1 };
|
|
430
|
+
}
|
|
431
|
+
// TTY path: surface the warning and continue. A future revision should
|
|
432
|
+
// prompt y/N here; for now the warning is informational and the operator
|
|
433
|
+
// can Ctrl+C before launches actually begin.
|
|
434
|
+
ui.info('Continuing (TTY). Use Ctrl+C to abort within the next 2 seconds.');
|
|
435
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
436
|
+
}
|
|
437
|
+
ui.blank();
|
|
438
|
+
console.log(` ${ui.brandMark()} ${ui.bold('Mission Control')} — running ${queued.length} queued mission(s) in ${ui.accent(session.id)}`);
|
|
439
|
+
ui.rule();
|
|
440
|
+
// Lazy import — ralph-launcher pulls in node:child_process and other heavy
|
|
441
|
+
// deps we don't want to pay for on every mc subcommand.
|
|
442
|
+
const { launchExternalRalph } = await import('./ralph-launcher.js');
|
|
443
|
+
let launched = 0;
|
|
444
|
+
let skipped = 0;
|
|
445
|
+
let failed = 0;
|
|
446
|
+
const projectRoot = ctx.cwd || process.cwd();
|
|
447
|
+
const frameworkRoot = ctx.frameworkRoot;
|
|
448
|
+
for (const mission of queued) {
|
|
449
|
+
if (mission.mode === 'pty-orchestrator') {
|
|
450
|
+
ui.warn(`Mission ${mission.id} mode=pty-orchestrator is not yet wired to mc run; skipping. Use 'aiwg ralph' directly for PTY-orchestrator workflows.`);
|
|
451
|
+
skipped += 1;
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
if (!mission.completion) {
|
|
455
|
+
ui.warn(`Mission ${mission.id} has no --completion criteria; ralph requires one. Skipping. Re-dispatch with --completion "<criteria>" to include this mission.`);
|
|
456
|
+
skipped += 1;
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
try {
|
|
460
|
+
const result = await launchExternalRalph(frameworkRoot, projectRoot, {
|
|
461
|
+
objective: mission.objective,
|
|
462
|
+
completionCriteria: mission.completion,
|
|
463
|
+
maxIterations: mission.maxIterations,
|
|
464
|
+
maxTotalTokens: mission.maxTotalTokens,
|
|
465
|
+
maxOutputTokens: mission.maxOutputTokens,
|
|
466
|
+
maxToolCalls: mission.maxToolCalls,
|
|
467
|
+
maxTotalCost: mission.maxTotalCost,
|
|
468
|
+
maxWallClockMinutes: mission.maxWallClockMinutes,
|
|
469
|
+
explorationQuota: mission.explorationQuota,
|
|
470
|
+
budgetStopPolicy: mission.budgetStopPolicy,
|
|
471
|
+
// Defaults for cycle 1; advanced options can be added per-mission later.
|
|
472
|
+
verbose: false,
|
|
473
|
+
});
|
|
474
|
+
mission.status = 'running';
|
|
475
|
+
mission.startedAt = new Date().toISOString();
|
|
476
|
+
mission.ralphLoopId = result.loopId;
|
|
477
|
+
mission.ralphPid = result.pid;
|
|
478
|
+
await writeSession(session);
|
|
479
|
+
await appendLog(session.id, {
|
|
480
|
+
event: 'mission_started',
|
|
481
|
+
missionId: mission.id,
|
|
482
|
+
loopId: result.loopId,
|
|
483
|
+
pid: result.pid,
|
|
484
|
+
});
|
|
485
|
+
ui.success(`Started ${mission.id} → ralph loop ${result.loopId} (PID ${result.pid})`);
|
|
486
|
+
launched += 1;
|
|
487
|
+
}
|
|
488
|
+
catch (err) {
|
|
489
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
490
|
+
mission.status = 'failed';
|
|
491
|
+
mission.error = msg;
|
|
492
|
+
mission.completedAt = new Date().toISOString();
|
|
493
|
+
await writeSession(session);
|
|
494
|
+
await appendLog(session.id, {
|
|
495
|
+
event: 'mission_launch_failed',
|
|
496
|
+
missionId: mission.id,
|
|
497
|
+
error: msg,
|
|
498
|
+
});
|
|
499
|
+
ui.error(`Failed to start ${mission.id}: ${msg}`);
|
|
500
|
+
failed += 1;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
ui.blank();
|
|
504
|
+
ui.info(`Launched: ${launched} | Skipped: ${skipped} | Failed: ${failed}`);
|
|
505
|
+
if (launched > 0) {
|
|
506
|
+
ui.info(`Monitor: \`aiwg mc status ${session.id}\` or \`aiwg mc watch ${session.id}\``);
|
|
507
|
+
}
|
|
508
|
+
return { exitCode: failed > 0 && launched === 0 ? 1 : 0 };
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Sync mission statuses from their backing ralph loop state files (#1439).
|
|
512
|
+
*
|
|
513
|
+
* Each mission launched by `mc run` carries a `ralphLoopId`. The ralph loop
|
|
514
|
+
* writes its own session-state.json with a `status` field
|
|
515
|
+
* (running|completed|failed|paused|aborted). This helper polls those files
|
|
516
|
+
* and reflects the truth back into the mc session.json so `mc status` and
|
|
517
|
+
* `mc watch` show actual progress without manual intervention.
|
|
518
|
+
*
|
|
519
|
+
* Sync is best-effort — a missing or unreadable ralph state file leaves the
|
|
520
|
+
* mission status unchanged (typically 'running' until the ralph process
|
|
521
|
+
* actually writes state).
|
|
522
|
+
*/
|
|
523
|
+
async function syncMissionsFromRalph(session, projectRoot) {
|
|
524
|
+
let mutated = false;
|
|
525
|
+
for (const mission of session.missions) {
|
|
526
|
+
if (mission.status !== 'running')
|
|
527
|
+
continue;
|
|
528
|
+
if (!mission.ralphLoopId)
|
|
529
|
+
continue;
|
|
530
|
+
const ralphStatePath = join(projectRoot, '.aiwg', 'ralph-external', 'loops', mission.ralphLoopId, 'session-state.json');
|
|
531
|
+
try {
|
|
532
|
+
const raw = await fs.readFile(ralphStatePath, 'utf-8');
|
|
533
|
+
const state = JSON.parse(raw);
|
|
534
|
+
const ralphStatus = String(state.status || '').toLowerCase();
|
|
535
|
+
// Map ralph status → mc mission status
|
|
536
|
+
let nextStatus = null;
|
|
537
|
+
if (ralphStatus === 'completed')
|
|
538
|
+
nextStatus = 'done';
|
|
539
|
+
else if (ralphStatus === 'failed' || ralphStatus === 'crashed')
|
|
540
|
+
nextStatus = 'failed';
|
|
541
|
+
else if (ralphStatus === 'aborted')
|
|
542
|
+
nextStatus = 'aborted';
|
|
543
|
+
else if (ralphStatus === 'paused')
|
|
544
|
+
nextStatus = 'paused';
|
|
545
|
+
// 'running' stays running
|
|
546
|
+
// Reflect iteration count if available
|
|
547
|
+
const iter = typeof state.iteration === 'number' ? state.iteration : (typeof state.currentIteration === 'number' ? state.currentIteration : null);
|
|
548
|
+
if (iter !== null && iter !== mission.loop) {
|
|
549
|
+
mission.loop = iter;
|
|
550
|
+
mutated = true;
|
|
551
|
+
}
|
|
552
|
+
// mission.status is narrowed to 'running' by the early-continue above,
|
|
553
|
+
// and nextStatus is one of {done, failed, aborted, paused}. So the
|
|
554
|
+
// transition is always real when nextStatus is set — no equality
|
|
555
|
+
// check needed.
|
|
556
|
+
if (nextStatus) {
|
|
557
|
+
mission.status = nextStatus;
|
|
558
|
+
mission.completedAt = new Date().toISOString();
|
|
559
|
+
if (nextStatus === 'failed' && typeof state.error === 'string') {
|
|
560
|
+
mission.error = state.error;
|
|
561
|
+
}
|
|
562
|
+
mutated = true;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
catch {
|
|
566
|
+
// State file missing/unreadable — leave mission status unchanged.
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
if (mutated) {
|
|
570
|
+
session.updatedAt = new Date().toISOString();
|
|
571
|
+
await writeSession(session);
|
|
572
|
+
}
|
|
573
|
+
return mutated;
|
|
574
|
+
}
|
|
575
|
+
async function mcStatus(ctx) {
|
|
576
|
+
const positional = getPositionalArgs(ctx.args);
|
|
577
|
+
const sessionId = positional[0];
|
|
578
|
+
const json = hasFlag(ctx.args, '--json');
|
|
579
|
+
const session = await findActiveSession(sessionId);
|
|
580
|
+
if (!session) {
|
|
581
|
+
if (json) {
|
|
582
|
+
console.log(JSON.stringify({ error: 'no_active_session' }));
|
|
583
|
+
}
|
|
584
|
+
else {
|
|
585
|
+
ui.error(sessionId ? `Session not found: ${sessionId}` : 'No active session.');
|
|
586
|
+
}
|
|
587
|
+
return { exitCode: 1 };
|
|
588
|
+
}
|
|
589
|
+
// #1439: Sync mission statuses from ralph loop state files BEFORE display
|
|
590
|
+
// so 'mc status' reflects the true state of any in-flight ralph processes
|
|
591
|
+
// (otherwise missions launched by 'mc run' stay 'running' in mc.session.json
|
|
592
|
+
// even after the ralph loop has completed).
|
|
593
|
+
await syncMissionsFromRalph(session, ctx.cwd || process.cwd());
|
|
594
|
+
if (json) {
|
|
595
|
+
console.log(JSON.stringify(session, null, 2));
|
|
596
|
+
return { exitCode: 0 };
|
|
597
|
+
}
|
|
598
|
+
const statusIcons = {
|
|
599
|
+
done: '✓',
|
|
600
|
+
running: '⏳',
|
|
601
|
+
queued: '⏺',
|
|
602
|
+
failed: '✗',
|
|
603
|
+
aborted: '⊘',
|
|
604
|
+
paused: '⏸',
|
|
605
|
+
};
|
|
606
|
+
ui.blank();
|
|
607
|
+
console.log(` ${ui.brandMark()} ${ui.bold('MISSION CONTROL')} — ${ui.accent(session.name)} [${session.id}]`);
|
|
608
|
+
ui.rule(60);
|
|
609
|
+
// Header
|
|
610
|
+
// #1441: ui.dim() returns void (it prints) — wrapping in console.log emitted
|
|
611
|
+
// a literal "undefined" between the header and the separator. Use dimText()
|
|
612
|
+
// (returns a string) for inline styling.
|
|
613
|
+
const header = ` ${'#'.padEnd(4)} ${'Mission'.padEnd(32)} ${'Mode'.padEnd(6)} ${'Status'.padEnd(12)} ${'Loop'.padEnd(8)} ${'Started'.padEnd(8)}`;
|
|
614
|
+
console.log(ui.dimText(header));
|
|
615
|
+
ui.rule(68);
|
|
616
|
+
for (let i = 0; i < session.missions.length; i++) {
|
|
617
|
+
const m = session.missions[i];
|
|
618
|
+
const icon = statusIcons[m.status] || '?';
|
|
619
|
+
const num = String(i + 1).padEnd(4);
|
|
620
|
+
const obj = m.objective.length > 30 ? m.objective.slice(0, 27) + '...' : m.objective.padEnd(32);
|
|
621
|
+
const modeTag = m.mode === 'pty-orchestrator' ? 'PTY'.padEnd(6) : '—'.padEnd(6);
|
|
622
|
+
const status = `${icon} ${m.status.toUpperCase()}`.padEnd(12);
|
|
623
|
+
const loop = m.status === 'queued' ? '—'.padEnd(8) : `${m.loop}/${m.maxIterations}`.padEnd(8);
|
|
624
|
+
const started = m.startedAt ? new Date(m.startedAt).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false }) : '—';
|
|
625
|
+
console.log(` ${num} ${obj} ${modeTag} ${status} ${loop} ${started}`);
|
|
626
|
+
// Show last action for PTY-orchestrated missions
|
|
627
|
+
if (m.mode === 'pty-orchestrator' && m.lastAction && m.status === 'running') {
|
|
628
|
+
console.log(ui.dimText(` └─ Last: ${m.lastAction}`));
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
ui.rule(68);
|
|
632
|
+
const counts = {
|
|
633
|
+
done: session.missions.filter(m => m.status === 'done').length,
|
|
634
|
+
running: session.missions.filter(m => m.status === 'running').length,
|
|
635
|
+
queued: session.missions.filter(m => m.status === 'queued').length,
|
|
636
|
+
failed: session.missions.filter(m => m.status === 'failed').length,
|
|
637
|
+
};
|
|
638
|
+
console.log(` ${session.missions.length} missions | ${counts.done} done | ${counts.running} running | ${counts.queued} queued | ${counts.failed} failed`);
|
|
639
|
+
// #1361: Show project parallelism cap when one is configured.
|
|
640
|
+
try {
|
|
641
|
+
const { readAiwgConfig, resolveParallelism } = await import('../../config/aiwg-config.js');
|
|
642
|
+
const cfg = await readAiwgConfig(ctx.cwd || process.cwd());
|
|
643
|
+
if (cfg) {
|
|
644
|
+
const resolved = resolveParallelism(cfg.parallelism, cfg.providers[0]);
|
|
645
|
+
const active = counts.running + counts.queued;
|
|
646
|
+
const cap = resolved.max_parallel_mc_missions;
|
|
647
|
+
const overCap = active > cap ? ` ${ui.dim(`(${active - cap} over cap — queued)`)}` : '';
|
|
648
|
+
console.log(` Parallelism cap: ${active}/${cap} active${overCap}`);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
catch {
|
|
652
|
+
// Non-fatal
|
|
653
|
+
}
|
|
654
|
+
ui.blank();
|
|
655
|
+
return { exitCode: 0 };
|
|
656
|
+
}
|
|
657
|
+
async function mcAbort(ctx) {
|
|
658
|
+
const positional = getPositionalArgs(ctx.args);
|
|
659
|
+
const sessionId = positional[0];
|
|
660
|
+
const missionId = positional[1];
|
|
661
|
+
if (!sessionId || !missionId) {
|
|
662
|
+
ui.error('Usage: aiwg mc abort <session-id> <mission-id>');
|
|
663
|
+
return { exitCode: 1 };
|
|
664
|
+
}
|
|
665
|
+
const session = await readSession(sessionId);
|
|
666
|
+
if (!session) {
|
|
667
|
+
ui.error(`Session not found: ${sessionId}`);
|
|
668
|
+
return { exitCode: 1 };
|
|
669
|
+
}
|
|
670
|
+
const mission = session.missions.find(m => m.id === missionId);
|
|
671
|
+
if (!mission) {
|
|
672
|
+
ui.error(`Mission not found: ${missionId}`);
|
|
673
|
+
return { exitCode: 1 };
|
|
674
|
+
}
|
|
675
|
+
mission.status = 'aborted';
|
|
676
|
+
mission.completedAt = new Date().toISOString();
|
|
677
|
+
await writeSession(session);
|
|
678
|
+
await appendLog(session.id, { event: 'mission_aborted', missionId });
|
|
679
|
+
ui.success(`Aborted mission: ${missionId}`);
|
|
680
|
+
return { exitCode: 0 };
|
|
681
|
+
}
|
|
682
|
+
async function mcPause(ctx) {
|
|
683
|
+
const positional = getPositionalArgs(ctx.args);
|
|
684
|
+
const sessionId = positional[0];
|
|
685
|
+
const session = await findActiveSession(sessionId);
|
|
686
|
+
if (!session) {
|
|
687
|
+
ui.error('No active session to pause.');
|
|
688
|
+
return { exitCode: 1 };
|
|
689
|
+
}
|
|
690
|
+
session.state = 'paused';
|
|
691
|
+
for (const m of session.missions) {
|
|
692
|
+
if (m.status === 'running')
|
|
693
|
+
m.status = 'paused';
|
|
694
|
+
}
|
|
695
|
+
await writeSession(session);
|
|
696
|
+
await appendLog(session.id, { event: 'session_paused' });
|
|
697
|
+
ui.success(`Paused session: ${session.id}`);
|
|
698
|
+
return { exitCode: 0 };
|
|
699
|
+
}
|
|
700
|
+
async function mcResume(ctx) {
|
|
701
|
+
const positional = getPositionalArgs(ctx.args);
|
|
702
|
+
const sessionId = positional[0];
|
|
703
|
+
const session = await findActiveSession(sessionId);
|
|
704
|
+
if (!session || session.state !== 'paused') {
|
|
705
|
+
ui.error('No paused session to resume.');
|
|
706
|
+
return { exitCode: 1 };
|
|
707
|
+
}
|
|
708
|
+
session.state = 'active';
|
|
709
|
+
for (const m of session.missions) {
|
|
710
|
+
if (m.status === 'paused')
|
|
711
|
+
m.status = 'running';
|
|
712
|
+
}
|
|
713
|
+
await writeSession(session);
|
|
714
|
+
await appendLog(session.id, { event: 'session_resumed' });
|
|
715
|
+
ui.success(`Resumed session: ${session.id}`);
|
|
716
|
+
return { exitCode: 0 };
|
|
717
|
+
}
|
|
718
|
+
async function mcStop(ctx) {
|
|
719
|
+
const positional = getPositionalArgs(ctx.args);
|
|
720
|
+
const sessionId = positional[0];
|
|
721
|
+
const drain = hasFlag(ctx.args, '--drain');
|
|
722
|
+
const session = await findActiveSession(sessionId);
|
|
723
|
+
if (!session) {
|
|
724
|
+
ui.error('No active session to stop.');
|
|
725
|
+
return { exitCode: 1 };
|
|
726
|
+
}
|
|
727
|
+
if (drain) {
|
|
728
|
+
// Mark queued missions as aborted, let running finish
|
|
729
|
+
for (const m of session.missions) {
|
|
730
|
+
if (m.status === 'queued') {
|
|
731
|
+
m.status = 'aborted';
|
|
732
|
+
m.completedAt = new Date().toISOString();
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
ui.info('Draining: queued missions cancelled, running missions will complete.');
|
|
736
|
+
}
|
|
737
|
+
else {
|
|
738
|
+
// Abort all non-completed missions
|
|
739
|
+
for (const m of session.missions) {
|
|
740
|
+
if (m.status === 'running' || m.status === 'queued' || m.status === 'paused') {
|
|
741
|
+
m.status = 'aborted';
|
|
742
|
+
m.completedAt = new Date().toISOString();
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
session.state = 'stopped';
|
|
747
|
+
await writeSession(session);
|
|
748
|
+
await appendLog(session.id, { event: 'session_stopped', drain });
|
|
749
|
+
ui.success(`Stopped session: ${session.id}`);
|
|
750
|
+
return { exitCode: 0 };
|
|
751
|
+
}
|
|
752
|
+
async function mcList(ctx) {
|
|
753
|
+
const json = hasFlag(ctx.args, '--json');
|
|
754
|
+
const sessions = await listSessions();
|
|
755
|
+
if (json) {
|
|
756
|
+
console.log(JSON.stringify(sessions.map(s => ({
|
|
757
|
+
id: s.id,
|
|
758
|
+
name: s.name,
|
|
759
|
+
state: s.state,
|
|
760
|
+
missions: s.missions.length,
|
|
761
|
+
created: s.createdAt,
|
|
762
|
+
updated: s.updatedAt,
|
|
763
|
+
})), null, 2));
|
|
764
|
+
return { exitCode: 0 };
|
|
765
|
+
}
|
|
766
|
+
if (sessions.length === 0) {
|
|
767
|
+
ui.info('No Mission Control sessions. Run `aiwg mc start` to create one.');
|
|
768
|
+
return { exitCode: 0 };
|
|
769
|
+
}
|
|
770
|
+
ui.blank();
|
|
771
|
+
console.log(` ${ui.brandMark()} ${ui.bold('Mission Control Sessions')}`);
|
|
772
|
+
ui.rule();
|
|
773
|
+
for (const s of sessions) {
|
|
774
|
+
const stateIcon = s.state === 'active' ? '●' : s.state === 'paused' ? '⏸' : '○';
|
|
775
|
+
const missionCount = s.missions.length;
|
|
776
|
+
const done = s.missions.filter(m => m.status === 'done').length;
|
|
777
|
+
console.log(` ${stateIcon} ${s.id} ${ui.accent(s.name)} (${done}/${missionCount} done) [${s.state}]`);
|
|
778
|
+
}
|
|
779
|
+
ui.blank();
|
|
780
|
+
return { exitCode: 0 };
|
|
781
|
+
}
|
|
782
|
+
async function mcWatch(ctx) {
|
|
783
|
+
const positional = getPositionalArgs(ctx.args);
|
|
784
|
+
const sessionId = positional[0];
|
|
785
|
+
const session = await findActiveSession(sessionId);
|
|
786
|
+
if (!session) {
|
|
787
|
+
ui.error('No active session to watch.');
|
|
788
|
+
return { exitCode: 1 };
|
|
789
|
+
}
|
|
790
|
+
// For non-interactive contexts, show status once with a note
|
|
791
|
+
// Real streaming would use fs.watch on the session file
|
|
792
|
+
ui.info(`Watch mode: polling session ${session.id}`);
|
|
793
|
+
ui.info('Press Ctrl+C to stop watching.');
|
|
794
|
+
ui.blank();
|
|
795
|
+
// Show current status
|
|
796
|
+
ctx.args = [session.id];
|
|
797
|
+
return mcStatus(ctx);
|
|
798
|
+
}
|
|
799
|
+
// ── Agent routing query ──────────────────────────────────────
|
|
800
|
+
/**
|
|
801
|
+
* aiwg mc agents [--filter key=value...] [--json]
|
|
802
|
+
*
|
|
803
|
+
* Queries GET /api/agents/candidates on the local aiwg serve instance and
|
|
804
|
+
* prints a table of agents that match the given routing filter. This is a thin
|
|
805
|
+
* CLI wrapper over the #916 routing endpoint so operators can check routing
|
|
806
|
+
* from a terminal without opening the dashboard.
|
|
807
|
+
*
|
|
808
|
+
* Filter flags:
|
|
809
|
+
* --framework <name> Require a specific AIWG framework (repeatable)
|
|
810
|
+
* --sandbox <id> Restrict to a specific sandbox
|
|
811
|
+
* --agent <id> Restrict to a specific agent ID
|
|
812
|
+
* --name <n> Match by logical name
|
|
813
|
+
* --max-cpu <pct> Reject agents above this CPU %
|
|
814
|
+
* --min-memory <gb> Reject agents below this memory threshold
|
|
815
|
+
* --json Output raw JSON
|
|
816
|
+
*/
|
|
817
|
+
async function mcAgents(ctx) {
|
|
818
|
+
const args = ctx.args;
|
|
819
|
+
const json = hasFlag(args, '--json');
|
|
820
|
+
const port = process.env['AIWG_SERVE_PORT'] ?? '7337';
|
|
821
|
+
const base = `http://127.0.0.1:${port}`;
|
|
822
|
+
const params = new URLSearchParams();
|
|
823
|
+
for (let i = 0; i < args.length; i++) {
|
|
824
|
+
const a = args[i];
|
|
825
|
+
if (a === '--framework' && args[i + 1]) {
|
|
826
|
+
params.append('frameworks', args[++i]);
|
|
827
|
+
}
|
|
828
|
+
else if (a === '--sandbox' && args[i + 1]) {
|
|
829
|
+
params.set('sandbox_id', args[++i]);
|
|
830
|
+
}
|
|
831
|
+
else if (a === '--agent' && args[i + 1]) {
|
|
832
|
+
params.set('agent_id', args[++i]);
|
|
833
|
+
}
|
|
834
|
+
else if (a === '--name' && args[i + 1]) {
|
|
835
|
+
params.set('agent_name', args[++i]);
|
|
836
|
+
}
|
|
837
|
+
else if (a === '--max-cpu' && args[i + 1]) {
|
|
838
|
+
params.set('max_cpu_percent', args[++i]);
|
|
839
|
+
}
|
|
840
|
+
else if (a === '--min-memory' && args[i + 1]) {
|
|
841
|
+
params.set('min_memory_gb', args[++i]);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
// 5s timeout so a wedged serve cannot hang the CLI. Override with
|
|
845
|
+
// AIWG_FETCH_TIMEOUT_MS for slow local environments or integration tests.
|
|
846
|
+
const fetchTimeoutMs = (() => {
|
|
847
|
+
const raw = process.env['AIWG_FETCH_TIMEOUT_MS'];
|
|
848
|
+
const n = raw ? parseInt(raw, 10) : NaN;
|
|
849
|
+
return Number.isFinite(n) && n > 0 ? n : 5_000;
|
|
850
|
+
})();
|
|
851
|
+
let result;
|
|
852
|
+
try {
|
|
853
|
+
// Combine user-cancel (Ctrl-C) with the per-call timeout so the fetch
|
|
854
|
+
// aborts on either. `AbortSignal.any` requires Node 20+.
|
|
855
|
+
const signal = ctx.signal
|
|
856
|
+
? AbortSignal.any([ctx.signal, AbortSignal.timeout(fetchTimeoutMs)])
|
|
857
|
+
: AbortSignal.timeout(fetchTimeoutMs);
|
|
858
|
+
const resp = await fetch(`${base}/api/agents/candidates?${params.toString()}`, {
|
|
859
|
+
signal,
|
|
860
|
+
});
|
|
861
|
+
if (!resp.ok) {
|
|
862
|
+
ui.error(`aiwg serve returned ${resp.status} — is it running on port ${port}?`);
|
|
863
|
+
return { exitCode: 1 };
|
|
864
|
+
}
|
|
865
|
+
result = await resp.json();
|
|
866
|
+
}
|
|
867
|
+
catch (err) {
|
|
868
|
+
if (err instanceof Error && err.name === 'TimeoutError') {
|
|
869
|
+
ui.error(`aiwg serve on port ${port} timed out after ${fetchTimeoutMs}ms. Is it wedged?`);
|
|
870
|
+
}
|
|
871
|
+
else {
|
|
872
|
+
ui.error(`Cannot reach aiwg serve on port ${port}. Start it with: aiwg serve`);
|
|
873
|
+
}
|
|
874
|
+
return { exitCode: 1 };
|
|
875
|
+
}
|
|
876
|
+
if (json) {
|
|
877
|
+
console.log(JSON.stringify(result, null, 2));
|
|
878
|
+
return { exitCode: 0 };
|
|
879
|
+
}
|
|
880
|
+
ui.blank();
|
|
881
|
+
console.log(` ${ui.brandMark()} ${ui.bold('Agent Routing Candidates')}`);
|
|
882
|
+
ui.rule();
|
|
883
|
+
if (result.candidates.length === 0) {
|
|
884
|
+
ui.info('No matching agents found for the given filter.');
|
|
885
|
+
return { exitCode: 0 };
|
|
886
|
+
}
|
|
887
|
+
for (const c of result.candidates) {
|
|
888
|
+
const agent = c['agent'];
|
|
889
|
+
const agentId = agent?.['agentId'] ?? '?';
|
|
890
|
+
const logicalName = agent?.['logicalName'];
|
|
891
|
+
const sandboxName = c['sandboxName'] ?? '';
|
|
892
|
+
const cpu = agent?.['latestMetrics']?.['cpu_percent'];
|
|
893
|
+
const status = agent?.['status'] ?? '';
|
|
894
|
+
const isSelected = result.selected?.['sandboxName'] === sandboxName &&
|
|
895
|
+
result.selected?.['agent']?.['agentId'] === agentId;
|
|
896
|
+
const label = logicalName ? `${logicalName} (${agentId})` : agentId;
|
|
897
|
+
const cpuStr = cpu !== undefined ? ` cpu:${cpu.toFixed(0)}%` : '';
|
|
898
|
+
const selectedMark = isSelected ? ' ← selected' : '';
|
|
899
|
+
console.log(` • ${ui.bold(label)} sandbox:${sandboxName} status:${status}${cpuStr}${selectedMark}`);
|
|
900
|
+
const reason = c['matchReason'];
|
|
901
|
+
if (reason)
|
|
902
|
+
console.log(` reason: ${reason}`);
|
|
903
|
+
}
|
|
904
|
+
ui.blank();
|
|
905
|
+
return { exitCode: 0 };
|
|
906
|
+
}
|
|
907
|
+
// ── Subcommand router ────────────────────────────────────────
|
|
908
|
+
const subcommands = {
|
|
909
|
+
start: mcStart,
|
|
910
|
+
dispatch: mcDispatch,
|
|
911
|
+
run: mcRun, // #1439
|
|
912
|
+
status: mcStatus,
|
|
913
|
+
watch: mcWatch,
|
|
914
|
+
abort: mcAbort,
|
|
915
|
+
pause: mcPause,
|
|
916
|
+
resume: mcResume,
|
|
917
|
+
stop: mcStop,
|
|
918
|
+
list: mcList,
|
|
919
|
+
agents: mcAgents,
|
|
920
|
+
};
|
|
921
|
+
function showMcHelp() {
|
|
922
|
+
ui.blank();
|
|
923
|
+
console.log(` ${ui.brandMark()} ${ui.bold('Mission Control')} — multi-loop background orchestration`);
|
|
924
|
+
ui.rule();
|
|
925
|
+
console.log(`
|
|
926
|
+
${ui.bold('Usage:')} aiwg mc <subcommand> [options]
|
|
927
|
+
|
|
928
|
+
${ui.bold('Lifecycle:')}
|
|
929
|
+
1. start — create a session (state-tracking only)
|
|
930
|
+
2. dispatch — queue one or more missions onto a session
|
|
931
|
+
3. run — drain the queue by launching each mission as a ralph loop
|
|
932
|
+
4. status — view progress (auto-syncs from ralph loop state)
|
|
933
|
+
5. watch — live tail of progress
|
|
934
|
+
6. stop — shut down the session
|
|
935
|
+
|
|
936
|
+
${ui.bold('Subcommands:')}
|
|
937
|
+
start Start a new Mission Control session
|
|
938
|
+
dispatch <id> "<objective>" Queue a mission on the session (does NOT execute)
|
|
939
|
+
[--completion "<criteria>"] [--max-iterations N]
|
|
940
|
+
[--max-total-tokens N] [--max-output-tokens N]
|
|
941
|
+
[--max-tool-calls N] [--max-total-cost USD]
|
|
942
|
+
[--max-wall-clock-minutes N] [--exploration-quota N]
|
|
943
|
+
[--mode pty-orchestrator] [--target-agent <id>]
|
|
944
|
+
run <id> [--accept-cost] Launch queued missions as ralph loops (#1439)
|
|
945
|
+
Cost gate warns/refuses above ~$5 estimate
|
|
946
|
+
status [<id>] [--json] View mission status dashboard
|
|
947
|
+
watch [<id>] Live monitor (streaming)
|
|
948
|
+
abort <session> <mission> Abort a specific mission
|
|
949
|
+
pause [<id>] Pause active session
|
|
950
|
+
resume [<id>] Resume paused session
|
|
951
|
+
stop [<id>] [--drain] Shut down session
|
|
952
|
+
list [--json] List all sessions
|
|
953
|
+
agents [--filter] [--json] Query routable agents from aiwg serve (#916)
|
|
954
|
+
${ui.bold('Examples:')}
|
|
955
|
+
aiwg mc start --name "Sprint 4"
|
|
956
|
+
aiwg mc dispatch mc-abc123 "Fix auth" --completion "tests pass"
|
|
957
|
+
aiwg mc dispatch mc-abc123 "Fix auth" --completion "tests pass" --max-total-tokens 5000 --exploration-quota 2
|
|
958
|
+
aiwg mc dispatch mc-abc123 "Refactor users" --completion "npm test passes"
|
|
959
|
+
aiwg mc run mc-abc123 # launches queued missions
|
|
960
|
+
aiwg mc status mc-abc123 # syncs progress from ralph loops
|
|
961
|
+
aiwg mc dispatch mc-abc123 "Supervise agent-01" --mode pty-orchestrator --target-agent agent-01 --completion "migration complete"
|
|
962
|
+
aiwg mc stop mc-abc123 --drain
|
|
963
|
+
aiwg mc agents --framework sdlc-complete --max-cpu 80
|
|
964
|
+
|
|
965
|
+
${ui.bold('A2A route for new sandbox work:')}
|
|
966
|
+
GET /api/v2/admin/instances
|
|
967
|
+
GET /agents/{instance_id}/.well-known/agent-card.json
|
|
968
|
+
GET /agents/{instance_id}/v1/extendedAgentCard
|
|
969
|
+
POST /agents/{instance_id}/v1/messages:send
|
|
970
|
+
`);
|
|
971
|
+
}
|
|
972
|
+
// ── Exported handler ─────────────────────────────────────────
|
|
973
|
+
export const mcHandler = {
|
|
974
|
+
id: 'mc',
|
|
975
|
+
name: 'Mission Control',
|
|
976
|
+
description: 'Multi-loop background orchestration (start, dispatch, status, watch, stop)',
|
|
977
|
+
category: 'orchestration',
|
|
978
|
+
aliases: ['mission-control'],
|
|
979
|
+
async execute(ctx) {
|
|
980
|
+
const subcmd = ctx.args[0];
|
|
981
|
+
if (!subcmd || subcmd === '--help' || subcmd === '-h') {
|
|
982
|
+
showMcHelp();
|
|
983
|
+
return { exitCode: 0 };
|
|
984
|
+
}
|
|
985
|
+
const handler = subcommands[subcmd];
|
|
986
|
+
if (!handler) {
|
|
987
|
+
ui.error(`Unknown subcommand: ${subcmd}. Run 'aiwg mc --help' for usage.`);
|
|
988
|
+
return { exitCode: 1 };
|
|
989
|
+
}
|
|
990
|
+
// #1440: '--help' / '-h' on any subcommand prints usage and exits BEFORE
|
|
991
|
+
// any side-effecting logic. The previous behavior — e.g. 'mc start --help'
|
|
992
|
+
// actually creating a session — violated the universal --help convention.
|
|
993
|
+
const subArgs = ctx.args.slice(1);
|
|
994
|
+
if (wantsHelp(subArgs)) {
|
|
995
|
+
printSubcommandHelp(subcmd);
|
|
996
|
+
return { exitCode: 0 };
|
|
997
|
+
}
|
|
998
|
+
// Pass remaining args to subcommand
|
|
999
|
+
const subCtx = {
|
|
1000
|
+
...ctx,
|
|
1001
|
+
args: subArgs,
|
|
1002
|
+
};
|
|
1003
|
+
return handler(subCtx);
|
|
1004
|
+
},
|
|
1005
|
+
};
|
|
1006
|
+
/**
|
|
1007
|
+
* All MC-related handlers for bulk registration
|
|
1008
|
+
*/
|
|
1009
|
+
export const mcHandlers = [mcHandler];
|
|
1010
|
+
//# sourceMappingURL=mc.js.map
|