@aiwg/cli 0.0.0-bootstrap.0 → 2026.7.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -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 +1462 -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 +882 -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 +64 -9
|
@@ -0,0 +1,799 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket PTY Bridge
|
|
3
|
+
*
|
|
4
|
+
* Bridges browser WebSocket connections to PTY sessions. Three modes:
|
|
5
|
+
*
|
|
6
|
+
* Phase 1 (local exec): spawns commands using node-pty.
|
|
7
|
+
* Phase 2a (sandbox WS): bridges to agentic-sandbox management WebSocket.
|
|
8
|
+
* Connects to the sandbox's management WS server, subscribes to an agent,
|
|
9
|
+
* starts an interactive shell, and multicasts PTY output to all browser clients.
|
|
10
|
+
* Leverages the sandbox's tokio broadcast channel for zero-copy multicast.
|
|
11
|
+
* Phase 2b (sandbox REST, deprecated): polls /api/v1/tasks log endpoint.
|
|
12
|
+
*
|
|
13
|
+
* Browser ↔ serve protocol:
|
|
14
|
+
* Client → Server: { type: 'data', payload: string } — stdin to PTY
|
|
15
|
+
* Client → Server: { type: 'resize', cols: number, rows: number }
|
|
16
|
+
* Client → Server: { type: 'close' } — request graceful shutdown
|
|
17
|
+
* Server → Client: { type: 'data', payload: string } — stdout/stderr from PTY
|
|
18
|
+
* Server → Client: { type: 'exit', code: number } — PTY process exited
|
|
19
|
+
* Server → Client: { type: 'error', message: string } — error notification
|
|
20
|
+
*
|
|
21
|
+
* Sandbox management WS protocol (agentic-sandbox):
|
|
22
|
+
* → { type: 'subscribe', agent_id }
|
|
23
|
+
* → { type: 'start_shell', agent_id, cols, rows }
|
|
24
|
+
* → { type: 'list_sessions', agent_id } — resolves session_name (#901)
|
|
25
|
+
* → { type: 'send_input', agent_id, command_id, data }
|
|
26
|
+
* → { type: 'pty_resize', agent_id, command_id, cols, rows }
|
|
27
|
+
* → { type: 'kill_session', agent_id, session_name } — must use session_name, not command_id
|
|
28
|
+
* ← { type: 'shell_started', agent_id, command_id } — idempotent: same cmd_id on reconnect (#903)
|
|
29
|
+
* ← { type: 'session_list', agent_id, sessions[] } — provides session_name for kill (#901)
|
|
30
|
+
* ← { type: 'output', agent_id, command_id, stream, data, ts, seq? }
|
|
31
|
+
* ← { type: 'session_killed' | 'session_detached', agent_id, exit_code? }
|
|
32
|
+
*
|
|
33
|
+
* Replay-on-attach (#1144). After `shell_started`, AIWG sends:
|
|
34
|
+
* → { type: 'join_session', agent_id, command_id, replay_from }
|
|
35
|
+
* and the sandbox emits buffered output frames carrying `seq`. AIWG prepends
|
|
36
|
+
* `\x1bc` (full terminal reset) ahead of the first replay frame so xterm.js
|
|
37
|
+
* agrees with tmux on the starting cursor/screen state, eliminating the
|
|
38
|
+
* first-frame alignment glitch. On unexpected disconnect, the bridge resumes
|
|
39
|
+
* from the last observed seq instead of replaying from zero.
|
|
40
|
+
*
|
|
41
|
+
* Capability negotiation (operator review on #1144, 2026-05-07): the original
|
|
42
|
+
* implementation gated this strictly on the sandbox advertising `join_session`
|
|
43
|
+
* + `replay_buffer` in a server-hello banner (agentic-sandbox#190). That
|
|
44
|
+
* banner doesn't ship today, so the gate kept replay disabled even on
|
|
45
|
+
* sandboxes that fully support it. The bridge now uses an opportunistic
|
|
46
|
+
* probe: if the banner explicitly signals support, replay is enabled; if the
|
|
47
|
+
* banner is absent, the bridge sends `join_session` anyway and treats an
|
|
48
|
+
* `error` response with an "unknown" / "not supported" / "session not found"
|
|
49
|
+
* message as the negative signal. Either path is logged so an operator can
|
|
50
|
+
* tell from `aiwg serve` output which mode the bridge is running in.
|
|
51
|
+
*
|
|
52
|
+
* @issue #712
|
|
53
|
+
* @issue #1144 — replay session history when AIWG attaches to running session
|
|
54
|
+
* @see #657 — agentic-sandbox PTY transport
|
|
55
|
+
* @see #711 — HTTP server scaffold
|
|
56
|
+
*/
|
|
57
|
+
import { loadFeaturePackage } from '../features/runtime.js';
|
|
58
|
+
// ============================================================
|
|
59
|
+
// Constants
|
|
60
|
+
// ============================================================
|
|
61
|
+
const OUTPUT_BUFFER_MAX = 64 * 1024; // 64 KB replay buffer per session
|
|
62
|
+
const SESSION_TTL_MS = 30_000; // 30 s before orphaned session is cleaned up
|
|
63
|
+
// ============================================================
|
|
64
|
+
// Session Registry
|
|
65
|
+
// ============================================================
|
|
66
|
+
export class PtySessionRegistry {
|
|
67
|
+
sessions = new Map();
|
|
68
|
+
cleanupTimer = null;
|
|
69
|
+
constructor() {
|
|
70
|
+
// Periodically clean up orphaned sessions
|
|
71
|
+
this.cleanupTimer = setInterval(() => this.evictExpired(), SESSION_TTL_MS);
|
|
72
|
+
if (this.cleanupTimer.unref)
|
|
73
|
+
this.cleanupTimer.unref();
|
|
74
|
+
}
|
|
75
|
+
get(id) {
|
|
76
|
+
return this.sessions.get(id);
|
|
77
|
+
}
|
|
78
|
+
create(id) {
|
|
79
|
+
const session = {
|
|
80
|
+
id,
|
|
81
|
+
clients: new Map(),
|
|
82
|
+
outputBuffer: '',
|
|
83
|
+
pty: null,
|
|
84
|
+
lastDisconnect: 0,
|
|
85
|
+
exited: false,
|
|
86
|
+
};
|
|
87
|
+
this.sessions.set(id, session);
|
|
88
|
+
return session;
|
|
89
|
+
}
|
|
90
|
+
delete(id) {
|
|
91
|
+
const session = this.sessions.get(id);
|
|
92
|
+
if (session?.pty) {
|
|
93
|
+
try {
|
|
94
|
+
session.pty.kill();
|
|
95
|
+
}
|
|
96
|
+
catch { /* ignore */ }
|
|
97
|
+
}
|
|
98
|
+
this.sessions.delete(id);
|
|
99
|
+
}
|
|
100
|
+
addClient(sessionId, clientId, ws) {
|
|
101
|
+
const session = this.sessions.get(sessionId);
|
|
102
|
+
if (session)
|
|
103
|
+
session.clients.set(clientId, ws);
|
|
104
|
+
}
|
|
105
|
+
removeClient(sessionId, clientId) {
|
|
106
|
+
const session = this.sessions.get(sessionId);
|
|
107
|
+
if (!session)
|
|
108
|
+
return;
|
|
109
|
+
session.clients.delete(clientId);
|
|
110
|
+
if (session.clients.size === 0) {
|
|
111
|
+
session.lastDisconnect = Date.now();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
appendOutput(sessionId, data) {
|
|
115
|
+
const session = this.sessions.get(sessionId);
|
|
116
|
+
if (!session)
|
|
117
|
+
return;
|
|
118
|
+
session.outputBuffer += data;
|
|
119
|
+
if (session.outputBuffer.length > OUTPUT_BUFFER_MAX) {
|
|
120
|
+
session.outputBuffer = session.outputBuffer.slice(-OUTPUT_BUFFER_MAX);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
broadcast(sessionId, msg) {
|
|
124
|
+
const session = this.sessions.get(sessionId);
|
|
125
|
+
if (!session)
|
|
126
|
+
return;
|
|
127
|
+
const text = JSON.stringify(msg);
|
|
128
|
+
for (const ws of session.clients.values()) {
|
|
129
|
+
if (ws.readyState === 1 /* OPEN */) {
|
|
130
|
+
try {
|
|
131
|
+
ws.send(text);
|
|
132
|
+
}
|
|
133
|
+
catch { /* ignore closed sockets */ }
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
evictExpired() {
|
|
138
|
+
const cutoff = Date.now() - SESSION_TTL_MS;
|
|
139
|
+
for (const [id, session] of this.sessions) {
|
|
140
|
+
const orphaned = session.clients.size === 0 && session.lastDisconnect < cutoff;
|
|
141
|
+
if (orphaned || session.exited) {
|
|
142
|
+
this.delete(id);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
shutdown() {
|
|
147
|
+
if (this.cleanupTimer)
|
|
148
|
+
clearInterval(this.cleanupTimer);
|
|
149
|
+
for (const id of this.sessions.keys()) {
|
|
150
|
+
this.delete(id);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Singleton registry shared across WebSocket connections
|
|
155
|
+
export const registry = new PtySessionRegistry();
|
|
156
|
+
// ============================================================
|
|
157
|
+
// PTY Spawn
|
|
158
|
+
// ============================================================
|
|
159
|
+
/**
|
|
160
|
+
* Spawn a PTY process for the given session.
|
|
161
|
+
*
|
|
162
|
+
* Priority order:
|
|
163
|
+
* 1. Explicit wsEndpoint opt → sandbox management WebSocket bridge
|
|
164
|
+
* 2. Auto-detect: first connected sandbox in registry → sandbox WS bridge
|
|
165
|
+
* 3. AIWG_SANDBOX_ENDPOINT env var → legacy REST polling (deprecated)
|
|
166
|
+
* 4. Fallback → local node-pty
|
|
167
|
+
*
|
|
168
|
+
* @issue #657 — agentic-sandbox PTY transport
|
|
169
|
+
*/
|
|
170
|
+
export async function spawnPty(session, command, args, opts = {}) {
|
|
171
|
+
// Phase 2a: explicit management WS endpoint
|
|
172
|
+
if (opts.wsEndpoint) {
|
|
173
|
+
const agentId = opts.agentId || process.env.AIWG_SANDBOX_AGENT_ID || 'agent-01';
|
|
174
|
+
// Look up advertised capabilities for this endpoint so spawnSandboxWsPty
|
|
175
|
+
// can capability-gate the replay-on-attach handshake (#1144).
|
|
176
|
+
let capabilities;
|
|
177
|
+
try {
|
|
178
|
+
const { sandboxRegistry } = await import('./sandbox-registry.js');
|
|
179
|
+
const match = sandboxRegistry.list().find((s) => s.wsEndpoint === opts.wsEndpoint);
|
|
180
|
+
capabilities = match?.wsCapabilities;
|
|
181
|
+
}
|
|
182
|
+
catch { /* registry not available — proceed without capability gating */ }
|
|
183
|
+
await spawnSandboxWsPty(session, agentId, opts.wsEndpoint, { ...opts, capabilities });
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
// Phase 2b: auto-detect — use first connected sandbox from registry
|
|
187
|
+
try {
|
|
188
|
+
const { sandboxRegistry } = await import('./sandbox-registry.js');
|
|
189
|
+
const sandboxes = sandboxRegistry.list();
|
|
190
|
+
const connected = sandboxes.find((s) => s.connected && s.wsEndpoint);
|
|
191
|
+
if (connected) {
|
|
192
|
+
// #1146: refuse silent fallback when multiple ready agents exist and the
|
|
193
|
+
// caller has not chosen one. Single-agent case still auto-selects.
|
|
194
|
+
const explicit = opts.agentId || process.env.AIWG_SANDBOX_AGENT_ID;
|
|
195
|
+
const ready = connected.agents.filter((a) => a.status === 'ready');
|
|
196
|
+
if (!explicit && ready.length > 1) {
|
|
197
|
+
const ids = ready.map((a) => a.agentId).join(', ');
|
|
198
|
+
throw new Error(`Sandbox '${connected.name}' has ${ready.length} ready agents (${ids}); ` +
|
|
199
|
+
`pass ?agent=<agentId> on /ws/pty/:sessionId or set AIWG_SANDBOX_AGENT_ID.`);
|
|
200
|
+
}
|
|
201
|
+
const agentId = explicit
|
|
202
|
+
|| ready[0]?.agentId
|
|
203
|
+
|| connected.agents[0]?.agentId
|
|
204
|
+
|| 'agent-01';
|
|
205
|
+
await spawnSandboxWsPty(session, agentId, connected.wsEndpoint, {
|
|
206
|
+
...opts,
|
|
207
|
+
capabilities: connected.wsCapabilities,
|
|
208
|
+
});
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
// #1146: if we threw above to demand explicit selection, propagate it.
|
|
214
|
+
if (err instanceof Error && err.message.startsWith('Sandbox '))
|
|
215
|
+
throw err;
|
|
216
|
+
/* registry not available — fall through to local PTY */
|
|
217
|
+
}
|
|
218
|
+
// Phase 2c: legacy REST-based sandbox (AIWG_SANDBOX_ENDPOINT env var — deprecated)
|
|
219
|
+
const sandboxEndpoint = opts.sandboxEndpoint || process.env.AIWG_SANDBOX_ENDPOINT;
|
|
220
|
+
if (sandboxEndpoint) {
|
|
221
|
+
await spawnSandboxPty(session, command, args, {
|
|
222
|
+
...opts,
|
|
223
|
+
sandboxEndpoint,
|
|
224
|
+
agentId: opts.agentId || process.env.AIWG_SANDBOX_AGENT_ID || 'agent-01',
|
|
225
|
+
});
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
// Phase 1: local exec via node-pty
|
|
229
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
230
|
+
let ptyMod;
|
|
231
|
+
try {
|
|
232
|
+
const loaded = await loadFeaturePackage('node-pty');
|
|
233
|
+
ptyMod = loaded.default ?? loaded;
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
throw new Error('node-pty is required for local PTY sessions. Install it with scoped approval: aiwg features install pty');
|
|
237
|
+
}
|
|
238
|
+
const pty = ptyMod.spawn(command, args, {
|
|
239
|
+
name: 'xterm-256color',
|
|
240
|
+
cols: opts.cols ?? 120,
|
|
241
|
+
rows: opts.rows ?? 30,
|
|
242
|
+
cwd: opts.cwd ?? process.cwd(),
|
|
243
|
+
env: process.env,
|
|
244
|
+
});
|
|
245
|
+
session.pty = pty;
|
|
246
|
+
pty.onData((data) => {
|
|
247
|
+
registry.appendOutput(session.id, data);
|
|
248
|
+
registry.broadcast(session.id, { type: 'data', payload: data });
|
|
249
|
+
});
|
|
250
|
+
pty.onExit(({ exitCode }) => {
|
|
251
|
+
session.exited = true;
|
|
252
|
+
registry.broadcast(session.id, { type: 'exit', code: exitCode });
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Spawn a PTY session bridged to an agentic-sandbox management WebSocket.
|
|
257
|
+
*
|
|
258
|
+
* Connects to the sandbox's management WS server at wsEndpoint, subscribes to
|
|
259
|
+
* the target agent, starts an interactive shell, then multicasts PTY output to
|
|
260
|
+
* all browser clients via the existing PtySessionRegistry broadcast channel.
|
|
261
|
+
*
|
|
262
|
+
* The sandbox uses an OutputAggregator that broadcasts all output for an agent_id
|
|
263
|
+
* to every subscribed WS connection. Multiple aiwg bridges sharing the same PTY
|
|
264
|
+
* session receive identical output streams — no extra overhead on the sandbox side.
|
|
265
|
+
* (#903: secondary start_shell for an existing session returns the same command_id
|
|
266
|
+
* as the primary — the PTY is ref-counted and survives until the last subscriber
|
|
267
|
+
* disconnects. The output filter `command_id === commandId` remains correct in all
|
|
268
|
+
* cases whether this is the first or a subsequent attach.)
|
|
269
|
+
*
|
|
270
|
+
* After shell_started, list_sessions is issued to resolve the human-readable
|
|
271
|
+
* session_name (e.g. "main") needed for kill_session. (#901)
|
|
272
|
+
*
|
|
273
|
+
* If the WS connection drops after handshake, the bridge reconnects with exponential
|
|
274
|
+
* backoff rather than marking the session exited. The tmux session on the VM is
|
|
275
|
+
* preserved as long as at least one subscriber is attached. (#902)
|
|
276
|
+
*
|
|
277
|
+
* Future: once aiwg-serve needs operator control (pause/inspect/hand-off), use the
|
|
278
|
+
* formal session protocol keyed on session_id from list_sessions:
|
|
279
|
+
* JoinSession { session_id, role: "controller" | "observer" }
|
|
280
|
+
* SessionInput { session_id, data }
|
|
281
|
+
* SessionResize { session_id, cols, rows }
|
|
282
|
+
* RequestControl / ReleaseControl
|
|
283
|
+
* This enables role-gated stdin and replay from a sequence number. (#904)
|
|
284
|
+
*
|
|
285
|
+
* @issue #657
|
|
286
|
+
*/
|
|
287
|
+
async function spawnSandboxWsPty(session, agentId, wsEndpoint, opts) {
|
|
288
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
289
|
+
let wsMod;
|
|
290
|
+
try {
|
|
291
|
+
wsMod = await (new Function('m', 'return import(m)'))('ws');
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
try {
|
|
295
|
+
// @ts-expect-error Optional runtime dependency; this workspace does not ship @types/ws.
|
|
296
|
+
wsMod = await import('ws');
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
throw new Error('ws package required for sandbox PTY bridge. Run: npm install ws');
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
303
|
+
const WS = wsMod.WebSocket ?? wsMod.default?.WebSocket ?? wsMod.default;
|
|
304
|
+
if (typeof WS !== 'function') {
|
|
305
|
+
throw new Error('Could not resolve WebSocket constructor from ws package');
|
|
306
|
+
}
|
|
307
|
+
const cols = opts.cols ?? 120;
|
|
308
|
+
const rows = opts.rows ?? 30;
|
|
309
|
+
// Replay-on-attach negotiation (#1144). Three states:
|
|
310
|
+
// "advertised" — banner explicitly lists join_session + replay_buffer.
|
|
311
|
+
// "probe" — no banner present; we'll send join_session opportunistically
|
|
312
|
+
// and watch for an "unknown message" / "not supported" error
|
|
313
|
+
// to demote to "unsupported" if the sandbox can't handle it.
|
|
314
|
+
// "unsupported"— banner advertises capabilities but join_session is absent
|
|
315
|
+
// (rare — would mean a partial banner). Skip entirely.
|
|
316
|
+
//
|
|
317
|
+
// Default behavior is opportunistic: if the operator hasn't supplied a
|
|
318
|
+
// capability map, or it lists no client messages, we still try. This keeps
|
|
319
|
+
// the replay path useful on sandboxes that support join_session without
|
|
320
|
+
// shipping the server-hello banner from agentic-sandbox#190.
|
|
321
|
+
let replayMode;
|
|
322
|
+
if (!opts.capabilities) {
|
|
323
|
+
replayMode = 'probe';
|
|
324
|
+
}
|
|
325
|
+
else if (opts.capabilities.supported_client_messages.includes('join_session') &&
|
|
326
|
+
opts.capabilities.features.includes('replay_buffer')) {
|
|
327
|
+
replayMode = 'advertised';
|
|
328
|
+
}
|
|
329
|
+
else if (opts.capabilities.supported_client_messages.length === 0 &&
|
|
330
|
+
opts.capabilities.features.length === 0) {
|
|
331
|
+
// Sandbox supplied an empty banner — treat as no-banner-yet, probe.
|
|
332
|
+
replayMode = 'probe';
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
// Banner present but missing the bits we care about — don't probe; the
|
|
336
|
+
// sandbox has explicitly told us it doesn't support replay.
|
|
337
|
+
replayMode = 'unsupported';
|
|
338
|
+
}
|
|
339
|
+
console.info(`[pty-bridge] sandbox WS ${wsEndpoint} (agent=${agentId}): replay ${replayMode === 'advertised'
|
|
340
|
+
? 'enabled (banner)'
|
|
341
|
+
: replayMode === 'probe'
|
|
342
|
+
? 'enabled (opportunistic — no banner advertised)'
|
|
343
|
+
: 'disabled (banner explicitly omits join_session/replay_buffer)'}`);
|
|
344
|
+
return new Promise((resolve, reject) => {
|
|
345
|
+
let commandId = null;
|
|
346
|
+
// session_name resolved via list_sessions after shell_started (#901)
|
|
347
|
+
// The kill_session handler looks up by session_name, not command_id.
|
|
348
|
+
let sessionName = null;
|
|
349
|
+
let onDataCb = null;
|
|
350
|
+
let onExitCb = null;
|
|
351
|
+
let settled = false;
|
|
352
|
+
let reconnectAttempt = 0;
|
|
353
|
+
const MAX_RECONNECT = 8;
|
|
354
|
+
// #1144: persist last seen sequence number so reconnects resume from gap point
|
|
355
|
+
let lastSeq = 0;
|
|
356
|
+
// #1144: prepend a single xterm full-reset before the first replay frame so
|
|
357
|
+
// the renderer's cursor/screen state agrees with tmux on its starting point.
|
|
358
|
+
// Without the reset, mid-conversation bytes overlap whatever was on screen
|
|
359
|
+
// and produce the alignment glitch described in #1144.
|
|
360
|
+
let needsReplayReset = false;
|
|
361
|
+
// sock is reassigned on each reconnect; sendMsg always uses the current one
|
|
362
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
363
|
+
let sock = null;
|
|
364
|
+
const settle = (err) => {
|
|
365
|
+
if (settled)
|
|
366
|
+
return;
|
|
367
|
+
settled = true;
|
|
368
|
+
if (err)
|
|
369
|
+
reject(err);
|
|
370
|
+
else
|
|
371
|
+
resolve();
|
|
372
|
+
};
|
|
373
|
+
const sendMsg = (msg) => {
|
|
374
|
+
if (sock?.readyState === 1 /* OPEN */) {
|
|
375
|
+
sock.send(JSON.stringify(msg));
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
function connect() {
|
|
379
|
+
sock = new WS(wsEndpoint);
|
|
380
|
+
sock.on('open', () => {
|
|
381
|
+
// Subscribe then start (or re-attach to) the interactive shell.
|
|
382
|
+
// After sandbox#ce8e600, start_shell for an existing session returns
|
|
383
|
+
// the same command_id (idempotent attach) — no new PTY is spawned. (#903)
|
|
384
|
+
sendMsg({ type: 'subscribe', agent_id: agentId });
|
|
385
|
+
sendMsg({ type: 'start_shell', agent_id: agentId, cols, rows });
|
|
386
|
+
});
|
|
387
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
388
|
+
sock.on('message', (raw) => {
|
|
389
|
+
let msg;
|
|
390
|
+
try {
|
|
391
|
+
msg = JSON.parse(typeof raw === 'string' ? raw : raw.toString());
|
|
392
|
+
}
|
|
393
|
+
catch {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
// Shell handshake — record command_id for all subsequent I/O routing.
|
|
397
|
+
// On reconnect, the server returns the same command_id (idempotent). (#903)
|
|
398
|
+
if (msg['type'] === 'shell_started' && msg['agent_id'] === agentId) {
|
|
399
|
+
commandId = msg['command_id'];
|
|
400
|
+
// Resolve the outer promise on first shell_started only
|
|
401
|
+
settle();
|
|
402
|
+
// Request session list to resolve the session_name needed for kill (#901)
|
|
403
|
+
sendMsg({ type: 'list_sessions', agent_id: agentId });
|
|
404
|
+
// #1144: ask the sandbox to replay buffered output so a late attach
|
|
405
|
+
// sees existing session history. On the initial attach lastSeq is 0
|
|
406
|
+
// (full replay); on reconnect lastSeq is the last frame we saw, so
|
|
407
|
+
// we resume from the gap rather than re-rendering history.
|
|
408
|
+
if (replayMode !== 'unsupported') {
|
|
409
|
+
needsReplayReset = true;
|
|
410
|
+
sendMsg({
|
|
411
|
+
type: 'join_session',
|
|
412
|
+
agent_id: agentId,
|
|
413
|
+
command_id: commandId,
|
|
414
|
+
replay_from: lastSeq,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
// Probe-mode error handling (#1144): if the sandbox doesn't recognize
|
|
420
|
+
// the join_session message, demote to 'unsupported' so the renderer
|
|
421
|
+
// doesn't keep waiting for a replay reset that's never going to come,
|
|
422
|
+
// and log so the operator can see why replay didn't take effect.
|
|
423
|
+
if (msg['type'] === 'error') {
|
|
424
|
+
const errMsg = String(msg['message'] ?? '').toLowerCase();
|
|
425
|
+
const looksLikeUnsupported = errMsg.includes('unknown message') ||
|
|
426
|
+
errMsg.includes('unsupported') ||
|
|
427
|
+
errMsg.includes('not supported') ||
|
|
428
|
+
errMsg.includes('session not found') ||
|
|
429
|
+
errMsg.includes('unknown type');
|
|
430
|
+
if (looksLikeUnsupported &&
|
|
431
|
+
(replayMode === 'probe' || replayMode === 'advertised')) {
|
|
432
|
+
console.warn(`[pty-bridge] sandbox declined join_session for agent=${agentId}: "${msg['message']}" — demoting to unsupported, falling back to live-only`);
|
|
433
|
+
replayMode = 'unsupported';
|
|
434
|
+
// Cancel the pending reset — there will be no replay frame to
|
|
435
|
+
// prepend `\x1bc` to.
|
|
436
|
+
needsReplayReset = false;
|
|
437
|
+
}
|
|
438
|
+
// Other error types are forwarded as-is to the existing handler
|
|
439
|
+
// chain (no action here — pre-existing behavior).
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
// session_list response: find our session by command_id, store session_name (#901)
|
|
443
|
+
if (msg['type'] === 'session_list' && msg['agent_id'] === agentId) {
|
|
444
|
+
const sessions = msg['sessions'];
|
|
445
|
+
const match = sessions?.find((s) => s.command_id === commandId);
|
|
446
|
+
if (match)
|
|
447
|
+
sessionName = match.session_name;
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
// PTY output from the sandbox broadcast channel → forward to browser
|
|
451
|
+
if (msg['type'] === 'output' &&
|
|
452
|
+
msg['agent_id'] === agentId &&
|
|
453
|
+
msg['command_id'] === commandId &&
|
|
454
|
+
(msg['stream'] === 'stdout' || msg['stream'] === undefined) &&
|
|
455
|
+
msg['data']) {
|
|
456
|
+
let data = typeof msg['data'] === 'string' ? msg['data'] : String(msg['data']);
|
|
457
|
+
// #1144: track sandbox-supplied seq so reconnects can resume from
|
|
458
|
+
// the gap point rather than replaying from the buffer head.
|
|
459
|
+
const seq = msg['seq'];
|
|
460
|
+
if (typeof seq === 'number' && seq > lastSeq)
|
|
461
|
+
lastSeq = seq;
|
|
462
|
+
// #1144: emit a full xterm reset (`\x1bc`) ahead of the first frame
|
|
463
|
+
// following a replay request so the renderer agrees with tmux on
|
|
464
|
+
// the starting state. Eliminates the alignment glitch where
|
|
465
|
+
// mid-session bytes overlap the prior screen content.
|
|
466
|
+
if (needsReplayReset) {
|
|
467
|
+
data = '\x1bc' + data;
|
|
468
|
+
needsReplayReset = false;
|
|
469
|
+
}
|
|
470
|
+
onDataCb?.(data);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
// Session ended on sandbox side (explicit kill or process exit)
|
|
474
|
+
if ((msg['type'] === 'session_killed' || msg['type'] === 'session_detached') &&
|
|
475
|
+
msg['agent_id'] === agentId) {
|
|
476
|
+
onExitCb?.({ exitCode: msg['exit_code'] ?? 0 });
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
sock.on('error', (_err) => {
|
|
480
|
+
// If not yet settled, the 'close' event will follow and settle with an error.
|
|
481
|
+
// After settlement, let 'close' drive reconnection.
|
|
482
|
+
});
|
|
483
|
+
sock.on('close', () => {
|
|
484
|
+
if (!settled) {
|
|
485
|
+
// Closed before shell_started — fatal for the initial connect
|
|
486
|
+
settle(new Error(`Sandbox WS closed before shell_started (agent: ${agentId}, endpoint: ${wsEndpoint})`));
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
if (session.exited)
|
|
490
|
+
return; // already torn down intentionally
|
|
491
|
+
// Unexpected mid-session disconnect — reconnect with exponential backoff. (#902)
|
|
492
|
+
// The tmux session on the VM survives as long as we reconnect before the last
|
|
493
|
+
// subscriber drops (sandbox ref-counts WS subscribers per PTY).
|
|
494
|
+
if (reconnectAttempt < MAX_RECONNECT) {
|
|
495
|
+
reconnectAttempt++;
|
|
496
|
+
const delay = Math.min(1_000 * Math.pow(2, reconnectAttempt), 30_000);
|
|
497
|
+
setTimeout(connect, delay);
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
// Exhausted retries — treat as session exit
|
|
501
|
+
onExitCb?.({ exitCode: 1 });
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
// 15 s timeout for the initial shell handshake only (not applied on reconnects)
|
|
505
|
+
if (!settled) {
|
|
506
|
+
const timeout = setTimeout(() => settle(new Error(`Timed out waiting for shell_started from agent ${agentId} at ${wsEndpoint}`)), 15_000);
|
|
507
|
+
if (timeout.unref) {
|
|
508
|
+
timeout.unref();
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
// PtyLike wrapper: routes browser I/O back through the sandbox management WS
|
|
513
|
+
const sandboxPty = {
|
|
514
|
+
write(data) {
|
|
515
|
+
if (!commandId)
|
|
516
|
+
return;
|
|
517
|
+
sendMsg({ type: 'send_input', agent_id: agentId, command_id: commandId, data });
|
|
518
|
+
},
|
|
519
|
+
resize(c, r) {
|
|
520
|
+
if (!commandId)
|
|
521
|
+
return;
|
|
522
|
+
sendMsg({ type: 'pty_resize', agent_id: agentId, command_id: commandId, cols: c, rows: r });
|
|
523
|
+
},
|
|
524
|
+
kill(_signal) {
|
|
525
|
+
// Kill by session_name — the sandbox KillSession handler looks up by name,
|
|
526
|
+
// not by command_id. session_name is resolved from list_sessions. (#901)
|
|
527
|
+
if (sessionName) {
|
|
528
|
+
sendMsg({ type: 'kill_session', agent_id: agentId, session_name: sessionName });
|
|
529
|
+
}
|
|
530
|
+
sock?.close();
|
|
531
|
+
},
|
|
532
|
+
onData(cb) { onDataCb = cb; },
|
|
533
|
+
onExit(cb) { onExitCb = cb; },
|
|
534
|
+
};
|
|
535
|
+
session.pty = sandboxPty;
|
|
536
|
+
// Wire output → broadcast pipeline (mirrors the local PTY pattern)
|
|
537
|
+
sandboxPty.onData((data) => {
|
|
538
|
+
registry.appendOutput(session.id, data);
|
|
539
|
+
registry.broadcast(session.id, { type: 'data', payload: data });
|
|
540
|
+
});
|
|
541
|
+
sandboxPty.onExit(({ exitCode }) => {
|
|
542
|
+
session.exited = true;
|
|
543
|
+
registry.broadcast(session.id, { type: 'exit', code: exitCode });
|
|
544
|
+
});
|
|
545
|
+
connect();
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Spawn a PTY session on a remote agentic-sandbox instance.
|
|
550
|
+
* Submits a task and polls for log output via REST.
|
|
551
|
+
*
|
|
552
|
+
* @deprecated Use spawnSandboxWsPty (management WebSocket) instead.
|
|
553
|
+
* The REST polling approach requires a /api/v1/tasks endpoint that the
|
|
554
|
+
* agentic-sandbox management server does not expose. Left as fallback for
|
|
555
|
+
* custom sandbox implementations that implement the task REST API.
|
|
556
|
+
* @issue #657
|
|
557
|
+
*/
|
|
558
|
+
async function spawnSandboxPty(session, command, args, opts) {
|
|
559
|
+
const endpoint = opts.sandboxEndpoint.replace(/\/$/, '');
|
|
560
|
+
const cmdLine = [command, ...args].join(' ');
|
|
561
|
+
// Submit task manifest
|
|
562
|
+
const manifest = {
|
|
563
|
+
manifest_yaml: [
|
|
564
|
+
'version: "1"',
|
|
565
|
+
'kind: Task',
|
|
566
|
+
'metadata:',
|
|
567
|
+
` name: "pty-${session.id}"`,
|
|
568
|
+
' labels:',
|
|
569
|
+
' aiwg_transport: pty',
|
|
570
|
+
` aiwg_session: "${session.id}"`,
|
|
571
|
+
'claude:',
|
|
572
|
+
` prompt: "${cmdLine}"`,
|
|
573
|
+
' headless: true',
|
|
574
|
+
' skip_permissions: true',
|
|
575
|
+
'vm:',
|
|
576
|
+
' profile: agentic-dev',
|
|
577
|
+
].join('\n'),
|
|
578
|
+
};
|
|
579
|
+
const resp = await fetch(`${endpoint}/api/v1/tasks`, {
|
|
580
|
+
method: 'POST',
|
|
581
|
+
headers: { 'Content-Type': 'application/json' },
|
|
582
|
+
body: JSON.stringify(manifest),
|
|
583
|
+
});
|
|
584
|
+
if (!resp.ok) {
|
|
585
|
+
const body = await resp.text().catch(() => '');
|
|
586
|
+
throw new Error(`Sandbox task submission failed: ${resp.status} ${body}`);
|
|
587
|
+
}
|
|
588
|
+
const result = await resp.json();
|
|
589
|
+
const taskId = result.task_id;
|
|
590
|
+
// Create a PtyLike wrapper that routes I/O through the sandbox REST API
|
|
591
|
+
let logOffset = 0;
|
|
592
|
+
let stopped = false;
|
|
593
|
+
const sandboxPty = {
|
|
594
|
+
write(data) {
|
|
595
|
+
if (stopped)
|
|
596
|
+
return;
|
|
597
|
+
fetch(`${endpoint}/api/v1/tasks/${taskId}`, {
|
|
598
|
+
method: 'PATCH',
|
|
599
|
+
headers: { 'Content-Type': 'application/json' },
|
|
600
|
+
body: JSON.stringify({ stdin: data }),
|
|
601
|
+
}).catch(() => { });
|
|
602
|
+
},
|
|
603
|
+
resize(_cols, _rows) {
|
|
604
|
+
// Resize handled by browser WS direct connection to sandbox :8121
|
|
605
|
+
},
|
|
606
|
+
kill(_signal) {
|
|
607
|
+
if (stopped)
|
|
608
|
+
return;
|
|
609
|
+
stopped = true;
|
|
610
|
+
fetch(`${endpoint}/api/v1/tasks/${taskId}`, { method: 'DELETE' })
|
|
611
|
+
.catch(() => { });
|
|
612
|
+
},
|
|
613
|
+
onData(callback) {
|
|
614
|
+
// Poll for log output
|
|
615
|
+
const timer = setInterval(async () => {
|
|
616
|
+
if (stopped) {
|
|
617
|
+
clearInterval(timer);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
try {
|
|
621
|
+
const logResp = await fetch(`${endpoint}/api/v1/tasks/${taskId}/logs?offset=${logOffset}`);
|
|
622
|
+
if (!logResp.ok) {
|
|
623
|
+
if (logResp.status === 404) {
|
|
624
|
+
stopped = true;
|
|
625
|
+
clearInterval(timer);
|
|
626
|
+
}
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
const text = await logResp.text();
|
|
630
|
+
if (text.length > 0) {
|
|
631
|
+
logOffset += text.length;
|
|
632
|
+
callback(text);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
catch { /* retry next poll */ }
|
|
636
|
+
}, 500);
|
|
637
|
+
},
|
|
638
|
+
onExit(callback) {
|
|
639
|
+
// Poll for task completion
|
|
640
|
+
const timer = setInterval(async () => {
|
|
641
|
+
if (stopped) {
|
|
642
|
+
clearInterval(timer);
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
try {
|
|
646
|
+
const statusResp = await fetch(`${endpoint}/api/v1/tasks/${taskId}`);
|
|
647
|
+
if (!statusResp.ok)
|
|
648
|
+
return;
|
|
649
|
+
const task = await statusResp.json();
|
|
650
|
+
if (['completed', 'failed', 'cancelled'].includes(task.state)) {
|
|
651
|
+
stopped = true;
|
|
652
|
+
clearInterval(timer);
|
|
653
|
+
callback({ exitCode: task.state === 'completed' ? 0 : 1 });
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
catch { /* retry */ }
|
|
657
|
+
}, 2000);
|
|
658
|
+
},
|
|
659
|
+
};
|
|
660
|
+
session.pty = sandboxPty;
|
|
661
|
+
sandboxPty.onData((data) => {
|
|
662
|
+
registry.appendOutput(session.id, data);
|
|
663
|
+
registry.broadcast(session.id, { type: 'data', payload: data });
|
|
664
|
+
});
|
|
665
|
+
sandboxPty.onExit(({ exitCode }) => {
|
|
666
|
+
session.exited = true;
|
|
667
|
+
registry.broadcast(session.id, { type: 'exit', code: exitCode });
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
// ============================================================
|
|
671
|
+
// WebSocket Connection Handler
|
|
672
|
+
// ============================================================
|
|
673
|
+
let clientCounter = 0;
|
|
674
|
+
/**
|
|
675
|
+
* Handle a new WebSocket connection for a PTY session.
|
|
676
|
+
*
|
|
677
|
+
* @param sessionId - PTY session ID from the URL path
|
|
678
|
+
* @param ws - WebSocket-like interface
|
|
679
|
+
* @param command - Command to spawn (default: 'aiwg')
|
|
680
|
+
* @param args - Command arguments (default: ['mc', 'watch'])
|
|
681
|
+
* @param cwd - Working directory
|
|
682
|
+
* @param wsEndpoint - Optional: sandbox management WS URL for explicit agent targeting
|
|
683
|
+
* @param agentId - Optional: sandbox agent ID to target (requires wsEndpoint)
|
|
684
|
+
*/
|
|
685
|
+
export async function handlePtyConnection(sessionId, ws, command = 'aiwg', cmdArgs = ['mc', 'watch'], cwd, wsEndpoint, agentId) {
|
|
686
|
+
const clientId = `client-${++clientCounter}`;
|
|
687
|
+
let session = registry.get(sessionId);
|
|
688
|
+
if (!session) {
|
|
689
|
+
// New session — create and spawn PTY
|
|
690
|
+
session = registry.create(sessionId);
|
|
691
|
+
registry.addClient(sessionId, clientId, ws);
|
|
692
|
+
try {
|
|
693
|
+
await spawnPty(session, command, cmdArgs, { cwd, wsEndpoint, agentId });
|
|
694
|
+
}
|
|
695
|
+
catch (err) {
|
|
696
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
697
|
+
ws.send(JSON.stringify({ type: 'error', message: msg }));
|
|
698
|
+
ws.close(1011, 'PTY spawn failed');
|
|
699
|
+
registry.delete(sessionId);
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
else if (!session.exited) {
|
|
704
|
+
// Reconnect to existing session — replay buffer
|
|
705
|
+
registry.addClient(sessionId, clientId, ws);
|
|
706
|
+
if (session.outputBuffer) {
|
|
707
|
+
// Trim replay to start from the last full-screen erase so that tmux's
|
|
708
|
+
// screen-init sequences (cursor moves, status-bar paint) from before the
|
|
709
|
+
// erase don't render as literal garbage in a fresh xterm.js context.
|
|
710
|
+
// Everything before \x1b[2J would be cleared by the erase anyway;
|
|
711
|
+
// everything after is the session content tmux redrew (MOTD, history, etc).
|
|
712
|
+
// If no erase is found, replay the whole buffer unchanged.
|
|
713
|
+
const ERASE = '\x1b[2J';
|
|
714
|
+
const lastErase = session.outputBuffer.lastIndexOf(ERASE);
|
|
715
|
+
const replay = lastErase !== -1 ? session.outputBuffer.slice(lastErase) : session.outputBuffer;
|
|
716
|
+
ws.send(JSON.stringify({ type: 'data', payload: replay }));
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
else {
|
|
720
|
+
// Session exited — inform client
|
|
721
|
+
ws.send(JSON.stringify({ type: 'exit', code: 0 }));
|
|
722
|
+
ws.close(1000, 'Session already exited');
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
// Handle incoming messages from browser
|
|
726
|
+
const onMessage = (rawData) => {
|
|
727
|
+
let msg;
|
|
728
|
+
try {
|
|
729
|
+
msg = JSON.parse(rawData);
|
|
730
|
+
}
|
|
731
|
+
catch {
|
|
732
|
+
return; // ignore malformed frames
|
|
733
|
+
}
|
|
734
|
+
const s = registry.get(sessionId);
|
|
735
|
+
if (!s?.pty)
|
|
736
|
+
return;
|
|
737
|
+
if (msg.type === 'data' && msg.payload !== undefined) {
|
|
738
|
+
s.pty.write(msg.payload);
|
|
739
|
+
}
|
|
740
|
+
else if (msg.type === 'resize' && msg.cols && msg.rows) {
|
|
741
|
+
s.pty.resize(msg.cols, msg.rows);
|
|
742
|
+
}
|
|
743
|
+
else if (msg.type === 'close') {
|
|
744
|
+
s.pty.kill();
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
const onClose = () => {
|
|
748
|
+
registry.removeClient(sessionId, clientId);
|
|
749
|
+
};
|
|
750
|
+
return Promise.resolve().then(() => {
|
|
751
|
+
// Return handlers for integration with WebSocket framework
|
|
752
|
+
ws._onMessage = onMessage;
|
|
753
|
+
ws._onClose = onClose;
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
// ============================================================
|
|
757
|
+
// Hono WebSocket Factory
|
|
758
|
+
// ============================================================
|
|
759
|
+
/**
|
|
760
|
+
* Create a Hono-compatible WebSocket event object for the PTY route.
|
|
761
|
+
*
|
|
762
|
+
* Used with `upgradeWebSocket` from `@hono/node-server/ws`:
|
|
763
|
+
*
|
|
764
|
+
* ```ts
|
|
765
|
+
* app.get('/ws/pty/:sessionId', upgradeWebSocket((c) => createPtyWsHandler(c)));
|
|
766
|
+
* ```
|
|
767
|
+
*/
|
|
768
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
769
|
+
export function createPtyWsHandler(c) {
|
|
770
|
+
const sessionId = c.req.param('sessionId') ?? 'default';
|
|
771
|
+
const q = c.req.query();
|
|
772
|
+
const command = q.command ?? 'aiwg';
|
|
773
|
+
const cmdArgs = q.args ? q.args.split(',') : ['mc', 'watch'];
|
|
774
|
+
const cwd = q.cwd;
|
|
775
|
+
// Explicit sandbox targeting (optional — auto-detected from registry when absent)
|
|
776
|
+
const wsEndpoint = q.wsEndpoint;
|
|
777
|
+
const agentId = q.agentId;
|
|
778
|
+
let wsRef = null;
|
|
779
|
+
return {
|
|
780
|
+
onOpen(_evt, ws) {
|
|
781
|
+
wsRef = ws;
|
|
782
|
+
handlePtyConnection(sessionId, ws, command, cmdArgs, cwd, wsEndpoint, agentId).catch((err) => {
|
|
783
|
+
ws.send(JSON.stringify({ type: 'error', message: String(err) }));
|
|
784
|
+
ws.close(1011);
|
|
785
|
+
});
|
|
786
|
+
},
|
|
787
|
+
onMessage(evt) {
|
|
788
|
+
wsRef?._onMessage?.(evt.data);
|
|
789
|
+
},
|
|
790
|
+
onClose() {
|
|
791
|
+
wsRef?._onClose?.();
|
|
792
|
+
wsRef = null;
|
|
793
|
+
},
|
|
794
|
+
onError(err) {
|
|
795
|
+
console.error(`[pty-bridge] WebSocket error on session ${sessionId}:`, err);
|
|
796
|
+
},
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
//# sourceMappingURL=pty-bridge.js.map
|