agent-relay 1.0.22 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/bridge/shadow-cli.d.ts +17 -0
- package/dist/bridge/shadow-cli.d.ts.map +1 -0
- package/dist/bridge/shadow-cli.js +75 -0
- package/dist/bridge/shadow-cli.js.map +1 -0
- package/dist/bridge/shadow-config.d.ts +87 -0
- package/dist/bridge/shadow-config.d.ts.map +1 -0
- package/dist/bridge/shadow-config.js +134 -0
- package/dist/bridge/shadow-config.js.map +1 -0
- package/dist/bridge/spawner.d.ts +68 -1
- package/dist/bridge/spawner.d.ts.map +1 -1
- package/dist/bridge/spawner.js +360 -16
- package/dist/bridge/spawner.js.map +1 -1
- package/dist/bridge/types.d.ts +67 -0
- package/dist/bridge/types.d.ts.map +1 -1
- package/dist/cli/index.js +1196 -15
- package/dist/cli/index.js.map +1 -1
- package/dist/cloud/api/auth.d.ts +20 -0
- package/dist/cloud/api/auth.d.ts.map +1 -0
- package/dist/cloud/api/auth.js +128 -0
- package/dist/cloud/api/auth.js.map +1 -0
- package/dist/cloud/api/billing.d.ts +17 -0
- package/dist/cloud/api/billing.d.ts.map +1 -0
- package/dist/cloud/api/billing.js +353 -0
- package/dist/cloud/api/billing.js.map +1 -0
- package/dist/cloud/api/cli-pty-runner.d.ts +54 -0
- package/dist/cloud/api/cli-pty-runner.d.ts.map +1 -0
- package/dist/cloud/api/cli-pty-runner.js +119 -0
- package/dist/cloud/api/cli-pty-runner.js.map +1 -0
- package/dist/cloud/api/coordinators.d.ts +8 -0
- package/dist/cloud/api/coordinators.d.ts.map +1 -0
- package/dist/cloud/api/coordinators.js +347 -0
- package/dist/cloud/api/coordinators.js.map +1 -0
- package/dist/cloud/api/daemons.d.ts +12 -0
- package/dist/cloud/api/daemons.d.ts.map +1 -0
- package/dist/cloud/api/daemons.js +320 -0
- package/dist/cloud/api/daemons.js.map +1 -0
- package/dist/cloud/api/generic-webhooks.d.ts +8 -0
- package/dist/cloud/api/generic-webhooks.d.ts.map +1 -0
- package/dist/cloud/api/generic-webhooks.js +129 -0
- package/dist/cloud/api/generic-webhooks.js.map +1 -0
- package/dist/cloud/api/git.d.ts +8 -0
- package/dist/cloud/api/git.d.ts.map +1 -0
- package/dist/cloud/api/git.js +131 -0
- package/dist/cloud/api/git.js.map +1 -0
- package/dist/cloud/api/github-app.d.ts +11 -0
- package/dist/cloud/api/github-app.d.ts.map +1 -0
- package/dist/cloud/api/github-app.js +189 -0
- package/dist/cloud/api/github-app.js.map +1 -0
- package/dist/cloud/api/middleware/planLimits.d.ts +43 -0
- package/dist/cloud/api/middleware/planLimits.d.ts.map +1 -0
- package/dist/cloud/api/middleware/planLimits.js +202 -0
- package/dist/cloud/api/middleware/planLimits.js.map +1 -0
- package/dist/cloud/api/monitoring.d.ts +11 -0
- package/dist/cloud/api/monitoring.d.ts.map +1 -0
- package/dist/cloud/api/monitoring.js +578 -0
- package/dist/cloud/api/monitoring.js.map +1 -0
- package/dist/cloud/api/nango-auth.d.ts +9 -0
- package/dist/cloud/api/nango-auth.d.ts.map +1 -0
- package/dist/cloud/api/nango-auth.js +377 -0
- package/dist/cloud/api/nango-auth.js.map +1 -0
- package/dist/cloud/api/onboarding.d.ts +15 -0
- package/dist/cloud/api/onboarding.d.ts.map +1 -0
- package/dist/cloud/api/onboarding.js +588 -0
- package/dist/cloud/api/onboarding.js.map +1 -0
- package/dist/cloud/api/policy.d.ts +8 -0
- package/dist/cloud/api/policy.d.ts.map +1 -0
- package/dist/cloud/api/policy.js +229 -0
- package/dist/cloud/api/policy.js.map +1 -0
- package/dist/cloud/api/providers.d.ts +7 -0
- package/dist/cloud/api/providers.d.ts.map +1 -0
- package/dist/cloud/api/providers.js +507 -0
- package/dist/cloud/api/providers.js.map +1 -0
- package/dist/cloud/api/repos.d.ts +7 -0
- package/dist/cloud/api/repos.d.ts.map +1 -0
- package/dist/cloud/api/repos.js +314 -0
- package/dist/cloud/api/repos.js.map +1 -0
- package/dist/cloud/api/teams.d.ts +7 -0
- package/dist/cloud/api/teams.d.ts.map +1 -0
- package/dist/cloud/api/teams.js +279 -0
- package/dist/cloud/api/teams.js.map +1 -0
- package/dist/cloud/api/test-helpers.d.ts +10 -0
- package/dist/cloud/api/test-helpers.d.ts.map +1 -0
- package/dist/cloud/api/test-helpers.js +575 -0
- package/dist/cloud/api/test-helpers.js.map +1 -0
- package/dist/cloud/api/usage.d.ts +7 -0
- package/dist/cloud/api/usage.d.ts.map +1 -0
- package/dist/cloud/api/usage.js +98 -0
- package/dist/cloud/api/usage.js.map +1 -0
- package/dist/cloud/api/webhooks.d.ts +7 -0
- package/dist/cloud/api/webhooks.d.ts.map +1 -0
- package/dist/cloud/api/webhooks.js +496 -0
- package/dist/cloud/api/webhooks.js.map +1 -0
- package/dist/cloud/api/workspaces.d.ts +7 -0
- package/dist/cloud/api/workspaces.d.ts.map +1 -0
- package/dist/cloud/api/workspaces.js +727 -0
- package/dist/cloud/api/workspaces.js.map +1 -0
- package/dist/cloud/billing/index.d.ts +9 -0
- package/dist/cloud/billing/index.d.ts.map +1 -0
- package/dist/cloud/billing/index.js +9 -0
- package/dist/cloud/billing/index.js.map +1 -0
- package/dist/cloud/billing/plans.d.ts +39 -0
- package/dist/cloud/billing/plans.d.ts.map +1 -0
- package/dist/cloud/billing/plans.js +245 -0
- package/dist/cloud/billing/plans.js.map +1 -0
- package/dist/cloud/billing/service.d.ts +80 -0
- package/dist/cloud/billing/service.d.ts.map +1 -0
- package/dist/cloud/billing/service.js +388 -0
- package/dist/cloud/billing/service.js.map +1 -0
- package/dist/cloud/billing/types.d.ts +141 -0
- package/dist/cloud/billing/types.d.ts.map +1 -0
- package/dist/cloud/billing/types.js +7 -0
- package/dist/cloud/billing/types.js.map +1 -0
- package/dist/cloud/config.d.ts +66 -0
- package/dist/cloud/config.d.ts.map +1 -0
- package/dist/cloud/config.js +92 -0
- package/dist/cloud/config.js.map +1 -0
- package/dist/cloud/db/drizzle.d.ts +215 -0
- package/dist/cloud/db/drizzle.d.ts.map +1 -0
- package/dist/cloud/db/drizzle.js +1083 -0
- package/dist/cloud/db/drizzle.js.map +1 -0
- package/dist/cloud/db/index.d.ts +35 -0
- package/dist/cloud/db/index.d.ts.map +1 -0
- package/dist/cloud/db/index.js +52 -0
- package/dist/cloud/db/index.js.map +1 -0
- package/dist/cloud/db/schema.d.ts +4519 -0
- package/dist/cloud/db/schema.d.ts.map +1 -0
- package/dist/cloud/db/schema.js +547 -0
- package/dist/cloud/db/schema.js.map +1 -0
- package/dist/cloud/index.d.ts +12 -0
- package/dist/cloud/index.d.ts.map +1 -0
- package/dist/cloud/index.js +39 -0
- package/dist/cloud/index.js.map +1 -0
- package/dist/cloud/provisioner/index.d.ts +75 -0
- package/dist/cloud/provisioner/index.d.ts.map +1 -0
- package/dist/cloud/provisioner/index.js +977 -0
- package/dist/cloud/provisioner/index.js.map +1 -0
- package/dist/cloud/server.d.ts +17 -0
- package/dist/cloud/server.d.ts.map +1 -0
- package/dist/cloud/server.js +534 -0
- package/dist/cloud/server.js.map +1 -0
- package/dist/cloud/services/auto-scaler.d.ts +152 -0
- package/dist/cloud/services/auto-scaler.d.ts.map +1 -0
- package/dist/cloud/services/auto-scaler.js +439 -0
- package/dist/cloud/services/auto-scaler.js.map +1 -0
- package/dist/cloud/services/capacity-manager.d.ts +148 -0
- package/dist/cloud/services/capacity-manager.d.ts.map +1 -0
- package/dist/cloud/services/capacity-manager.js +449 -0
- package/dist/cloud/services/capacity-manager.js.map +1 -0
- package/dist/cloud/services/ci-agent-spawner.d.ts +49 -0
- package/dist/cloud/services/ci-agent-spawner.d.ts.map +1 -0
- package/dist/cloud/services/ci-agent-spawner.js +373 -0
- package/dist/cloud/services/ci-agent-spawner.js.map +1 -0
- package/dist/cloud/services/coordinator.d.ts +62 -0
- package/dist/cloud/services/coordinator.d.ts.map +1 -0
- package/dist/cloud/services/coordinator.js +389 -0
- package/dist/cloud/services/coordinator.js.map +1 -0
- package/dist/cloud/services/index.d.ts +12 -0
- package/dist/cloud/services/index.d.ts.map +1 -0
- package/dist/cloud/services/index.js +15 -0
- package/dist/cloud/services/index.js.map +1 -0
- package/dist/cloud/services/mention-handler.d.ts +65 -0
- package/dist/cloud/services/mention-handler.d.ts.map +1 -0
- package/dist/cloud/services/mention-handler.js +405 -0
- package/dist/cloud/services/mention-handler.js.map +1 -0
- package/dist/cloud/services/nango.d.ts +126 -0
- package/dist/cloud/services/nango.d.ts.map +1 -0
- package/dist/cloud/services/nango.js +191 -0
- package/dist/cloud/services/nango.js.map +1 -0
- package/dist/cloud/services/persistence.d.ts +131 -0
- package/dist/cloud/services/persistence.d.ts.map +1 -0
- package/dist/cloud/services/persistence.js +200 -0
- package/dist/cloud/services/persistence.js.map +1 -0
- package/dist/cloud/services/planLimits.d.ts +125 -0
- package/dist/cloud/services/planLimits.d.ts.map +1 -0
- package/dist/cloud/services/planLimits.js +282 -0
- package/dist/cloud/services/planLimits.js.map +1 -0
- package/dist/cloud/services/scaling-orchestrator.d.ts +159 -0
- package/dist/cloud/services/scaling-orchestrator.d.ts.map +1 -0
- package/dist/cloud/services/scaling-orchestrator.js +502 -0
- package/dist/cloud/services/scaling-orchestrator.js.map +1 -0
- package/dist/cloud/services/scaling-policy.d.ts +121 -0
- package/dist/cloud/services/scaling-policy.d.ts.map +1 -0
- package/dist/cloud/services/scaling-policy.js +415 -0
- package/dist/cloud/services/scaling-policy.js.map +1 -0
- package/dist/cloud/vault/index.d.ts +76 -0
- package/dist/cloud/vault/index.d.ts.map +1 -0
- package/dist/cloud/vault/index.js +219 -0
- package/dist/cloud/vault/index.js.map +1 -0
- package/dist/cloud/webhooks/index.d.ts +24 -0
- package/dist/cloud/webhooks/index.d.ts.map +1 -0
- package/dist/cloud/webhooks/index.js +29 -0
- package/dist/cloud/webhooks/index.js.map +1 -0
- package/dist/cloud/webhooks/parsers/github.d.ts +8 -0
- package/dist/cloud/webhooks/parsers/github.d.ts.map +1 -0
- package/dist/cloud/webhooks/parsers/github.js +234 -0
- package/dist/cloud/webhooks/parsers/github.js.map +1 -0
- package/dist/cloud/webhooks/parsers/index.d.ts +23 -0
- package/dist/cloud/webhooks/parsers/index.d.ts.map +1 -0
- package/dist/cloud/webhooks/parsers/index.js +30 -0
- package/dist/cloud/webhooks/parsers/index.js.map +1 -0
- package/dist/cloud/webhooks/parsers/linear.d.ts +9 -0
- package/dist/cloud/webhooks/parsers/linear.d.ts.map +1 -0
- package/dist/cloud/webhooks/parsers/linear.js +258 -0
- package/dist/cloud/webhooks/parsers/linear.js.map +1 -0
- package/dist/cloud/webhooks/parsers/slack.d.ts +9 -0
- package/dist/cloud/webhooks/parsers/slack.d.ts.map +1 -0
- package/dist/cloud/webhooks/parsers/slack.js +214 -0
- package/dist/cloud/webhooks/parsers/slack.js.map +1 -0
- package/dist/cloud/webhooks/responders/github.d.ts +8 -0
- package/dist/cloud/webhooks/responders/github.d.ts.map +1 -0
- package/dist/cloud/webhooks/responders/github.js +73 -0
- package/dist/cloud/webhooks/responders/github.js.map +1 -0
- package/dist/cloud/webhooks/responders/index.d.ts +23 -0
- package/dist/cloud/webhooks/responders/index.d.ts.map +1 -0
- package/dist/cloud/webhooks/responders/index.js +30 -0
- package/dist/cloud/webhooks/responders/index.js.map +1 -0
- package/dist/cloud/webhooks/responders/linear.d.ts +9 -0
- package/dist/cloud/webhooks/responders/linear.d.ts.map +1 -0
- package/dist/cloud/webhooks/responders/linear.js +149 -0
- package/dist/cloud/webhooks/responders/linear.js.map +1 -0
- package/dist/cloud/webhooks/responders/slack.d.ts +20 -0
- package/dist/cloud/webhooks/responders/slack.d.ts.map +1 -0
- package/dist/cloud/webhooks/responders/slack.js +178 -0
- package/dist/cloud/webhooks/responders/slack.js.map +1 -0
- package/dist/cloud/webhooks/router.d.ts +25 -0
- package/dist/cloud/webhooks/router.d.ts.map +1 -0
- package/dist/cloud/webhooks/router.js +504 -0
- package/dist/cloud/webhooks/router.js.map +1 -0
- package/dist/cloud/webhooks/rules-engine.d.ts +24 -0
- package/dist/cloud/webhooks/rules-engine.d.ts.map +1 -0
- package/dist/cloud/webhooks/rules-engine.js +287 -0
- package/dist/cloud/webhooks/rules-engine.js.map +1 -0
- package/dist/cloud/webhooks/types.d.ts +186 -0
- package/dist/cloud/webhooks/types.d.ts.map +1 -0
- package/dist/cloud/webhooks/types.js +8 -0
- package/dist/cloud/webhooks/types.js.map +1 -0
- package/dist/continuity/formatter.d.ts +51 -0
- package/dist/continuity/formatter.d.ts.map +1 -0
- package/dist/continuity/formatter.js +313 -0
- package/dist/continuity/formatter.js.map +1 -0
- package/dist/continuity/handoff-store.d.ts +67 -0
- package/dist/continuity/handoff-store.d.ts.map +1 -0
- package/dist/continuity/handoff-store.js +472 -0
- package/dist/continuity/handoff-store.js.map +1 -0
- package/dist/continuity/index.d.ts +45 -0
- package/dist/continuity/index.d.ts.map +1 -0
- package/dist/continuity/index.js +48 -0
- package/dist/continuity/index.js.map +1 -0
- package/dist/continuity/ledger-store.d.ts +110 -0
- package/dist/continuity/ledger-store.d.ts.map +1 -0
- package/dist/continuity/ledger-store.js +500 -0
- package/dist/continuity/ledger-store.js.map +1 -0
- package/dist/continuity/manager.d.ts +178 -0
- package/dist/continuity/manager.d.ts.map +1 -0
- package/dist/continuity/manager.js +562 -0
- package/dist/continuity/manager.js.map +1 -0
- package/dist/continuity/parser.d.ts +76 -0
- package/dist/continuity/parser.d.ts.map +1 -0
- package/dist/continuity/parser.js +579 -0
- package/dist/continuity/parser.js.map +1 -0
- package/dist/continuity/types.d.ts +180 -0
- package/dist/continuity/types.d.ts.map +1 -0
- package/dist/continuity/types.js +9 -0
- package/dist/continuity/types.js.map +1 -0
- package/dist/daemon/agent-manager.d.ts +114 -0
- package/dist/daemon/agent-manager.d.ts.map +1 -0
- package/dist/daemon/agent-manager.js +513 -0
- package/dist/daemon/agent-manager.js.map +1 -0
- package/dist/daemon/agent-registry.d.ts +34 -0
- package/dist/daemon/agent-registry.d.ts.map +1 -1
- package/dist/daemon/agent-registry.js +45 -2
- package/dist/daemon/agent-registry.js.map +1 -1
- package/dist/daemon/api.d.ts +81 -0
- package/dist/daemon/api.d.ts.map +1 -0
- package/dist/daemon/api.js +554 -0
- package/dist/daemon/api.js.map +1 -0
- package/dist/daemon/cli-auth.d.ts +67 -0
- package/dist/daemon/cli-auth.d.ts.map +1 -0
- package/dist/daemon/cli-auth.js +537 -0
- package/dist/daemon/cli-auth.js.map +1 -0
- package/dist/daemon/cloud-sync.d.ts +101 -0
- package/dist/daemon/cloud-sync.d.ts.map +1 -0
- package/dist/daemon/cloud-sync.js +263 -0
- package/dist/daemon/cloud-sync.js.map +1 -0
- package/dist/daemon/index.d.ts +4 -0
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +6 -0
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/orchestrator.d.ts +155 -0
- package/dist/daemon/orchestrator.d.ts.map +1 -0
- package/dist/daemon/orchestrator.js +766 -0
- package/dist/daemon/orchestrator.js.map +1 -0
- package/dist/daemon/router.d.ts +29 -0
- package/dist/daemon/router.d.ts.map +1 -1
- package/dist/daemon/router.js +143 -21
- package/dist/daemon/router.js.map +1 -1
- package/dist/daemon/server.d.ts +42 -0
- package/dist/daemon/server.d.ts.map +1 -1
- package/dist/daemon/server.js +199 -16
- package/dist/daemon/server.js.map +1 -1
- package/dist/daemon/services/browser-testing.d.ts +88 -0
- package/dist/daemon/services/browser-testing.d.ts.map +1 -0
- package/dist/daemon/services/browser-testing.js +244 -0
- package/dist/daemon/services/browser-testing.js.map +1 -0
- package/dist/daemon/services/container-spawner.d.ts +135 -0
- package/dist/daemon/services/container-spawner.d.ts.map +1 -0
- package/dist/daemon/services/container-spawner.js +313 -0
- package/dist/daemon/services/container-spawner.js.map +1 -0
- package/dist/daemon/types.d.ts +131 -0
- package/dist/daemon/types.d.ts.map +1 -0
- package/dist/daemon/types.js +6 -0
- package/dist/daemon/types.js.map +1 -0
- package/dist/daemon/workspace-manager.d.ts +75 -0
- package/dist/daemon/workspace-manager.d.ts.map +1 -0
- package/dist/daemon/workspace-manager.js +289 -0
- package/dist/daemon/workspace-manager.js.map +1 -0
- package/dist/dashboard/out/404.html +1 -1
- package/dist/dashboard/out/_next/static/chunks/116-2502180def231162.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/282-980c2eb8fff20123.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/480-2d4111711d4e473c.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/724-73c1ee5f60abe860.js +9 -0
- package/dist/dashboard/out/_next/static/chunks/766-c3a14283c88d815b.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/app/page-7120be68bea622f3.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/connect-repos/page-dc2e3a1a22478efc.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/history/page-56a8b4616a90dc43.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/layout-2433bb48965f4333.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/login/page-3eac37ea6f5dd153.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/metrics/page-1081dd190a331a91.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/page-daf87e86f783f980.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/pricing/page-4d72d5a5d8a9b618.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/providers/page-b68a681526eb145e.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/signup/page-fee4ed1709070bcd.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/e868780c-48e5f147c90a3a41.js +18 -0
- package/dist/dashboard/out/_next/static/chunks/{main-e0a1f53fe0617a63.js → main-97850e03d723ea8c.js} +1 -1
- package/dist/dashboard/out/_next/static/chunks/main-app-5d692157a8eb1fd9.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/webpack-1cdd8ed57114d5e1.js +1 -0
- package/dist/dashboard/out/_next/static/css/29852f26181969a0.css +1 -0
- package/dist/dashboard/out/_next/static/css/411ce23ffeae9f76.css +1 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-128.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-256.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-32.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-512.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-64.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo.svg +45 -0
- package/dist/dashboard/out/alt-logos/logo.svg +38 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-128.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-256.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-32.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-512.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-64.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo.svg +38 -0
- package/dist/dashboard/out/app.html +1 -0
- package/dist/dashboard/out/app.txt +7 -0
- package/dist/dashboard/out/connect-repos.html +1 -0
- package/dist/dashboard/out/connect-repos.txt +7 -0
- package/dist/dashboard/out/history.html +1 -0
- package/dist/dashboard/out/history.txt +7 -0
- package/dist/dashboard/out/index.html +1 -1
- package/dist/dashboard/out/index.txt +2 -2
- package/dist/dashboard/out/login.html +6 -0
- package/dist/dashboard/out/login.txt +7 -0
- package/dist/dashboard/out/metrics.html +1 -515
- package/dist/dashboard/out/metrics.txt +2 -2
- package/dist/dashboard/out/pricing.html +13 -0
- package/dist/dashboard/out/pricing.txt +7 -0
- package/dist/dashboard/out/providers.html +1 -0
- package/dist/dashboard/out/providers.txt +7 -0
- package/dist/dashboard/out/signup.html +6 -0
- package/dist/dashboard/out/signup.txt +7 -0
- package/dist/dashboard-server/metrics.d.ts.map +1 -1
- package/dist/dashboard-server/metrics.js +3 -2
- package/dist/dashboard-server/metrics.js.map +1 -1
- package/dist/dashboard-server/server.d.ts.map +1 -1
- package/dist/dashboard-server/server.js +2653 -130
- package/dist/dashboard-server/server.js.map +1 -1
- package/dist/hooks/emitter.d.ts +40 -0
- package/dist/hooks/emitter.d.ts.map +1 -0
- package/dist/hooks/emitter.js +63 -0
- package/dist/hooks/emitter.js.map +1 -0
- package/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +3 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/registry.d.ts +173 -0
- package/dist/hooks/registry.d.ts.map +1 -0
- package/dist/hooks/registry.js +476 -0
- package/dist/hooks/registry.js.map +1 -0
- package/dist/hooks/trajectory-hooks.d.ts +52 -0
- package/dist/hooks/trajectory-hooks.d.ts.map +1 -0
- package/dist/hooks/trajectory-hooks.js +183 -0
- package/dist/hooks/trajectory-hooks.js.map +1 -0
- package/dist/hooks/types.d.ts +141 -0
- package/dist/hooks/types.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/memory/adapters/index.d.ts +8 -0
- package/dist/memory/adapters/index.d.ts.map +1 -0
- package/dist/memory/adapters/index.js +8 -0
- package/dist/memory/adapters/index.js.map +1 -0
- package/dist/memory/adapters/inmemory.d.ts +59 -0
- package/dist/memory/adapters/inmemory.d.ts.map +1 -0
- package/dist/memory/adapters/inmemory.js +195 -0
- package/dist/memory/adapters/inmemory.js.map +1 -0
- package/dist/memory/adapters/supermemory.d.ts +71 -0
- package/dist/memory/adapters/supermemory.d.ts.map +1 -0
- package/dist/memory/adapters/supermemory.js +338 -0
- package/dist/memory/adapters/supermemory.js.map +1 -0
- package/dist/memory/factory.d.ts +48 -0
- package/dist/memory/factory.d.ts.map +1 -0
- package/dist/memory/factory.js +143 -0
- package/dist/memory/factory.js.map +1 -0
- package/dist/memory/index.d.ts +32 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +32 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/memory-hooks.d.ts +60 -0
- package/dist/memory/memory-hooks.d.ts.map +1 -0
- package/dist/memory/memory-hooks.js +313 -0
- package/dist/memory/memory-hooks.js.map +1 -0
- package/dist/memory/service.d.ts +49 -0
- package/dist/memory/service.d.ts.map +1 -0
- package/dist/memory/service.js +146 -0
- package/dist/memory/service.js.map +1 -0
- package/dist/memory/types.d.ts +195 -0
- package/dist/memory/types.d.ts.map +1 -0
- package/dist/memory/types.js +8 -0
- package/dist/memory/types.js.map +1 -0
- package/dist/policy/agent-policy.d.ts +225 -0
- package/dist/policy/agent-policy.d.ts.map +1 -0
- package/dist/policy/agent-policy.js +665 -0
- package/dist/policy/agent-policy.js.map +1 -0
- package/dist/policy/cloud-policy-fetcher.d.ts +12 -0
- package/dist/policy/cloud-policy-fetcher.d.ts.map +1 -0
- package/dist/policy/cloud-policy-fetcher.js +64 -0
- package/dist/policy/cloud-policy-fetcher.js.map +1 -0
- package/dist/protocol/types.d.ts +10 -1
- package/dist/protocol/types.d.ts.map +1 -1
- package/dist/resiliency/context-persistence.d.ts +140 -0
- package/dist/resiliency/context-persistence.d.ts.map +1 -0
- package/dist/resiliency/context-persistence.js +397 -0
- package/dist/resiliency/context-persistence.js.map +1 -0
- package/dist/resiliency/crash-insights.d.ts +156 -0
- package/dist/resiliency/crash-insights.d.ts.map +1 -0
- package/dist/resiliency/crash-insights.js +492 -0
- package/dist/resiliency/crash-insights.js.map +1 -0
- package/dist/resiliency/gossip-health.d.ts +137 -0
- package/dist/resiliency/gossip-health.d.ts.map +1 -0
- package/dist/resiliency/gossip-health.js +241 -0
- package/dist/resiliency/gossip-health.js.map +1 -0
- package/dist/resiliency/health-monitor.d.ts +97 -0
- package/dist/resiliency/health-monitor.d.ts.map +1 -0
- package/dist/resiliency/health-monitor.js +291 -0
- package/dist/resiliency/health-monitor.js.map +1 -0
- package/dist/resiliency/index.d.ts +68 -0
- package/dist/resiliency/index.d.ts.map +1 -0
- package/dist/resiliency/index.js +68 -0
- package/dist/resiliency/index.js.map +1 -0
- package/dist/resiliency/leader-watchdog.d.ts +109 -0
- package/dist/resiliency/leader-watchdog.d.ts.map +1 -0
- package/dist/resiliency/leader-watchdog.js +189 -0
- package/dist/resiliency/leader-watchdog.js.map +1 -0
- package/dist/resiliency/logger.d.ts +114 -0
- package/dist/resiliency/logger.d.ts.map +1 -0
- package/dist/resiliency/logger.js +250 -0
- package/dist/resiliency/logger.js.map +1 -0
- package/dist/resiliency/memory-monitor.d.ts +172 -0
- package/dist/resiliency/memory-monitor.d.ts.map +1 -0
- package/dist/resiliency/memory-monitor.js +593 -0
- package/dist/resiliency/memory-monitor.js.map +1 -0
- package/dist/resiliency/metrics.d.ts +115 -0
- package/dist/resiliency/metrics.d.ts.map +1 -0
- package/dist/resiliency/metrics.js +239 -0
- package/dist/resiliency/metrics.js.map +1 -0
- package/dist/resiliency/provider-context.d.ts +100 -0
- package/dist/resiliency/provider-context.d.ts.map +1 -0
- package/dist/resiliency/provider-context.js +360 -0
- package/dist/resiliency/provider-context.js.map +1 -0
- package/dist/resiliency/stateless-lead.d.ts +149 -0
- package/dist/resiliency/stateless-lead.d.ts.map +1 -0
- package/dist/resiliency/stateless-lead.js +308 -0
- package/dist/resiliency/stateless-lead.js.map +1 -0
- package/dist/resiliency/supervisor.d.ts +147 -0
- package/dist/resiliency/supervisor.d.ts.map +1 -0
- package/dist/resiliency/supervisor.js +459 -0
- package/dist/resiliency/supervisor.js.map +1 -0
- package/dist/shared/cli-auth-config.d.ts +91 -0
- package/dist/shared/cli-auth-config.d.ts.map +1 -0
- package/dist/shared/cli-auth-config.js +264 -0
- package/dist/shared/cli-auth-config.js.map +1 -0
- package/dist/storage/adapter.d.ts +3 -1
- package/dist/storage/adapter.d.ts.map +1 -1
- package/dist/storage/adapter.js +12 -2
- package/dist/storage/adapter.js.map +1 -1
- package/dist/storage/sqlite-adapter.d.ts.map +1 -1
- package/dist/storage/sqlite-adapter.js +18 -14
- package/dist/storage/sqlite-adapter.js.map +1 -1
- package/dist/trajectory/config.d.ts +84 -0
- package/dist/trajectory/config.d.ts.map +1 -0
- package/dist/trajectory/config.js +163 -0
- package/dist/trajectory/config.js.map +1 -0
- package/dist/trajectory/index.d.ts +8 -0
- package/dist/trajectory/index.d.ts.map +1 -0
- package/dist/trajectory/index.js +8 -0
- package/dist/trajectory/index.js.map +1 -0
- package/dist/trajectory/integration.d.ts +292 -0
- package/dist/trajectory/integration.d.ts.map +1 -0
- package/dist/trajectory/integration.js +834 -0
- package/dist/trajectory/integration.js.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/logger.d.ts +40 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +84 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/project-namespace.d.ts +24 -0
- package/dist/utils/project-namespace.d.ts.map +1 -1
- package/dist/utils/project-namespace.js +84 -0
- package/dist/utils/project-namespace.js.map +1 -1
- package/dist/wrapper/client.d.ts +16 -1
- package/dist/wrapper/client.d.ts.map +1 -1
- package/dist/wrapper/client.js +32 -1
- package/dist/wrapper/client.js.map +1 -1
- package/dist/wrapper/parser.d.ts +13 -0
- package/dist/wrapper/parser.d.ts.map +1 -1
- package/dist/wrapper/parser.js +217 -47
- package/dist/wrapper/parser.js.map +1 -1
- package/dist/wrapper/pty-wrapper.d.ts +219 -17
- package/dist/wrapper/pty-wrapper.d.ts.map +1 -1
- package/dist/wrapper/pty-wrapper.js +1050 -104
- package/dist/wrapper/pty-wrapper.js.map +1 -1
- package/dist/wrapper/shared.d.ts +165 -0
- package/dist/wrapper/shared.d.ts.map +1 -0
- package/dist/wrapper/shared.js +270 -0
- package/dist/wrapper/shared.js.map +1 -0
- package/dist/wrapper/tmux-wrapper.d.ts +78 -11
- package/dist/wrapper/tmux-wrapper.d.ts.map +1 -1
- package/dist/wrapper/tmux-wrapper.js +567 -106
- package/dist/wrapper/tmux-wrapper.js.map +1 -1
- package/docs/CLOUD-ARCHITECTURE.md +804 -0
- package/docs/CLOUD-ONBOARDING-DESIGN.md +1983 -0
- package/docs/HOOKS_API.md +394 -0
- package/docs/WRAPPER_EVENTS.md +358 -0
- package/docs/agent-policy-snippet.md +40 -0
- package/docs/agent-relay-protocol.md +238 -0
- package/docs/agent-relay-snippet.md +115 -6
- package/docs/archive/EXECUTIVE_SUMMARY.md +358 -0
- package/docs/archive/ROADMAP.md +329 -0
- package/docs/archive/TESTING_PRESENCE_FEATURES.md +327 -0
- package/docs/competitive/GASTOWN.md +451 -0
- package/docs/{COMPETITIVE_ANALYSIS.md → competitive/OVERVIEW.md} +1 -0
- package/docs/competitive/README.md +34 -0
- package/docs/competitive/TMUX_ORCHESTRATOR.md +605 -0
- package/docs/dashboard.png +0 -0
- package/docs/design/ci-failure-webhooks.md +812 -0
- package/docs/design/comprehensive-integrations.md +238 -0
- package/docs/design/e2b-sandbox-integration.md +504 -0
- package/docs/design/github-app-permissions.md +264 -0
- package/docs/guides/CLOUD.md +236 -0
- package/docs/guides/LOCAL.md +535 -0
- package/docs/guides/SELF-HOSTED.md +494 -0
- package/docs/local-testing.md +428 -0
- package/docs/proposals/continuous-claude-integration.md +622 -0
- package/docs/proposals/custom-commands.md +368 -0
- package/docs/proposals/shadow-as-subagent.md +765 -0
- package/docs/proposals/slack-bot-integration.md +1457 -0
- package/docs/tasks/global-skills-system.tasks.md +230 -0
- package/docs/tasks/webhook-integrations.tasks.md +184 -0
- package/docs/tasks/workspace-capabilities.tasks.md +121 -0
- package/docs/testing/RESILIENCY-TEST-PLAN-2026-01-01.md +366 -0
- package/package.json +45 -7
- package/scripts/cloud-setup.sh +96 -0
- package/scripts/manual-qa.sh +293 -0
- package/scripts/postinstall.js +60 -0
- package/scripts/run-cloud-qa.sh +220 -0
- package/scripts/test-cli-auth/Dockerfile +44 -0
- package/scripts/test-cli-auth/Dockerfile.real +79 -0
- package/scripts/test-cli-auth/README.md +286 -0
- package/scripts/test-cli-auth/ci-test-real-clis.ts +251 -0
- package/scripts/test-cli-auth/ci-test-runner.ts +263 -0
- package/scripts/test-cli-auth/mock-cli.sh +147 -0
- package/scripts/test-cli-auth/package.json +14 -0
- package/scripts/test-cli-auth/test-oauth-flow.ts +220 -0
- package/scripts/test-pty-input-auto.js +222 -0
- package/scripts/test-pty-input.js +150 -0
- package/dist/dashboard/out/_next/static/chunks/app/layout-c9d8c5d95e48c6bf.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/metrics/page-8aa9936bc6c771ab.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/page-4498be09a5157759.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/main-app-bae2e535de00de50.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/webpack-c81f7fd28659d64f.js +0 -1
- package/dist/dashboard/out/_next/static/css/50ed6996e3df7bdd.css +0 -1
- /package/dist/dashboard/out/_next/static/{DXFA-jj8wb3PcY5DX2xcU → H5aWG0udPB4iOUIl_gytz}/_buildManifest.js +0 -0
- /package/dist/dashboard/out/_next/static/{DXFA-jj8wb3PcY5DX2xcU → H5aWG0udPB4iOUIl_gytz}/_ssgManifest.js +0 -0
- /package/dist/dashboard/out/_next/static/chunks/{117-3bef7b19f3e60751.js → 117-b100311aff8d5c61.js} +0 -0
- /package/dist/dashboard/out/_next/static/chunks/{648-6cf686106c891ad3.js → 648-a13d3c2b1be45466.js} +0 -0
- /package/dist/dashboard/out/_next/static/chunks/app/_not-found/{page-8ff6572bc7c9bc61.js → page-a4973f3e3c82fb67.js} +0 -0
- /package/dist/dashboard/out/_next/static/chunks/{fd9d1056-26bd8d656b496dba.js → fd9d1056-bf46c09eb57e019c.js} +0 -0
- /package/docs/{CHANGELOG.md → archive/CHANGELOG.md} +0 -0
- /package/docs/{CLI-SIMPLIFICATION-COMPLETE.md → archive/CLI-SIMPLIFICATION-COMPLETE.md} +0 -0
- /package/docs/{DESIGN_BRIDGE_STAFFING.md → archive/DESIGN_BRIDGE_STAFFING.md} +0 -0
- /package/docs/{DESIGN_V2.md → archive/DESIGN_V2.md} +0 -0
- /package/docs/{MONETIZATION.md → archive/MONETIZATION.md} +0 -0
- /package/docs/{PROPOSAL-trajectories.md → archive/PROPOSAL-trajectories.md} +0 -0
- /package/docs/{SCALING_ANALYSIS.md → archive/SCALING_ANALYSIS.md} +0 -0
- /package/docs/{TMUX_IMPLEMENTATION_NOTES.md → archive/TMUX_IMPLEMENTATION_NOTES.md} +0 -0
- /package/docs/{TMUX_IMPROVEMENTS.md → archive/TMUX_IMPROVEMENTS.md} +0 -0
- /package/docs/{dashboard-v2-plan.md → archive/dashboard-v2-plan.md} +0 -0
- /package/docs/{removable-code-analysis.md → archive/removable-code-analysis.md} +0 -0
- /package/docs/{competitive-analysis-mcp-agent-mail.md → competitive/MCP_AGENT_MAIL.md} +0 -0
|
@@ -20,6 +20,11 @@ import { InboxManager } from './inbox.js';
|
|
|
20
20
|
import { SqliteStorageAdapter } from '../storage/sqlite-adapter.js';
|
|
21
21
|
import { getProjectPaths } from '../utils/project-namespace.js';
|
|
22
22
|
import { getTmuxPath } from '../utils/tmux-resolver.js';
|
|
23
|
+
import { findAgentConfig } from '../utils/agent-config.js';
|
|
24
|
+
import { getTrajectoryIntegration, detectPhaseFromContent, detectToolCalls, detectErrors, getCompactTrailInstructions, getTrailEnvVars, } from '../trajectory/integration.js';
|
|
25
|
+
import { escapeForShell } from '../bridge/utils.js';
|
|
26
|
+
import { getContinuityManager, parseContinuityCommand, hasContinuityCommand, } from '../continuity/index.js';
|
|
27
|
+
import { stripAnsi, sleep, getDefaultRelayPrefix, createInjectionMetrics, buildInjectionString, injectWithRetry as sharedInjectWithRetry, INJECTION_CONSTANTS, CLI_QUIRKS, } from './shared.js';
|
|
23
28
|
const execAsync = promisify(exec);
|
|
24
29
|
const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
25
30
|
// Constants for cursor stability detection in waitForClearInput
|
|
@@ -34,10 +39,10 @@ const RELAY_LOG_TRUNCATE_LENGTH = 50;
|
|
|
34
39
|
/**
|
|
35
40
|
* Get the default relay prefix for a given CLI type.
|
|
36
41
|
* All agents now use '->relay:' as the unified prefix.
|
|
42
|
+
* @deprecated Use getDefaultRelayPrefix() from shared.js instead
|
|
37
43
|
*/
|
|
38
44
|
export function getDefaultPrefix(_cliType) {
|
|
39
|
-
|
|
40
|
-
return '->relay:';
|
|
45
|
+
return getDefaultRelayPrefix();
|
|
41
46
|
}
|
|
42
47
|
export class TmuxWrapper {
|
|
43
48
|
config;
|
|
@@ -60,21 +65,32 @@ export class TmuxWrapper {
|
|
|
60
65
|
isInjecting = false;
|
|
61
66
|
// Track processed output to avoid re-parsing
|
|
62
67
|
processedOutputLength = 0;
|
|
68
|
+
lastLoggedLength = 0; // Track length for incremental log streaming
|
|
63
69
|
lastDebugLog = 0;
|
|
64
70
|
cliType;
|
|
65
71
|
relayPrefix;
|
|
66
72
|
lastSummaryHash = ''; // Dedup summary saves
|
|
67
73
|
lastSummaryRawContent = ''; // Dedup invalid JSON error logging
|
|
68
74
|
sessionEndProcessed = false; // Track if we've already processed session end
|
|
75
|
+
sessionEndData; // Store SESSION_END data for handoff
|
|
69
76
|
pendingRelayCommands = [];
|
|
70
77
|
queuedMessageHashes = new Set(); // For offline queue dedup
|
|
71
78
|
MAX_PENDING_RELAY_COMMANDS = 50;
|
|
72
79
|
processedSpawnCommands = new Set(); // Dedup spawn commands
|
|
73
80
|
processedReleaseCommands = new Set(); // Dedup release commands
|
|
81
|
+
pendingFencedSpawn = null; // Track multi-line spawn task
|
|
74
82
|
receivedMessageIdSet = new Set();
|
|
75
83
|
receivedMessageIdOrder = [];
|
|
76
84
|
MAX_RECEIVED_MESSAGES = 2000;
|
|
77
85
|
tmuxPath; // Resolved path to tmux binary (system or bundled)
|
|
86
|
+
trajectory; // Trajectory tracking via trail
|
|
87
|
+
lastDetectedPhase; // Track last auto-detected PDERO phase
|
|
88
|
+
seenToolCalls = new Set(); // Dedup tool call trajectory events
|
|
89
|
+
seenErrors = new Set(); // Dedup error trajectory events
|
|
90
|
+
continuity; // Session continuity management
|
|
91
|
+
processedContinuityCommands = new Set(); // Dedup continuity commands
|
|
92
|
+
agentId; // Unique agent ID for resume functionality
|
|
93
|
+
injectionMetrics = createInjectionMetrics(); // Track injection reliability
|
|
78
94
|
constructor(config) {
|
|
79
95
|
this.config = {
|
|
80
96
|
cols: process.stdout.columns || 120,
|
|
@@ -88,6 +104,7 @@ export class TmuxWrapper {
|
|
|
88
104
|
activityIdleThresholdMs: 30_000, // Consider idle after 30s with no output
|
|
89
105
|
outputStabilityTimeoutMs: 2000,
|
|
90
106
|
outputStabilityPollMs: 200,
|
|
107
|
+
streamLogs: true, // Stream output to daemon for dashboard
|
|
91
108
|
...config,
|
|
92
109
|
};
|
|
93
110
|
// Detect CLI type from command for special handling
|
|
@@ -107,6 +124,9 @@ export class TmuxWrapper {
|
|
|
107
124
|
else if (cmdLower.includes('droid')) {
|
|
108
125
|
this.cliType = 'droid';
|
|
109
126
|
}
|
|
127
|
+
else if (cmdLower.includes('opencode')) {
|
|
128
|
+
this.cliType = 'opencode';
|
|
129
|
+
}
|
|
110
130
|
else {
|
|
111
131
|
this.cliType = 'other';
|
|
112
132
|
}
|
|
@@ -116,13 +136,22 @@ export class TmuxWrapper {
|
|
|
116
136
|
this.sessionName = `relay-${config.name}`;
|
|
117
137
|
// Resolve tmux path early so we fail fast if tmux isn't available
|
|
118
138
|
this.tmuxPath = getTmuxPath();
|
|
139
|
+
// Auto-detect agent role from .claude/agents/ or .openagents/ if task not provided
|
|
140
|
+
let detectedTask = this.config.task;
|
|
141
|
+
if (!detectedTask) {
|
|
142
|
+
const agentConfig = findAgentConfig(config.name, this.config.cwd);
|
|
143
|
+
if (agentConfig?.description) {
|
|
144
|
+
detectedTask = agentConfig.description;
|
|
145
|
+
this.logStderr(`Auto-detected role: ${detectedTask.substring(0, 60)}...`, true);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
119
148
|
this.client = new RelayClient({
|
|
120
149
|
agentName: config.name,
|
|
121
150
|
socketPath: config.socketPath,
|
|
122
151
|
cli: this.cliType,
|
|
123
152
|
program: this.config.program,
|
|
124
153
|
model: this.config.model,
|
|
125
|
-
task:
|
|
154
|
+
task: detectedTask,
|
|
126
155
|
workingDirectory: this.config.cwd ?? process.cwd(),
|
|
127
156
|
quiet: true, // Keep stdout clean; we log to stderr via wrapper
|
|
128
157
|
});
|
|
@@ -143,9 +172,13 @@ export class TmuxWrapper {
|
|
|
143
172
|
this.storage = undefined;
|
|
144
173
|
return false;
|
|
145
174
|
});
|
|
175
|
+
// Initialize trajectory tracking via trail CLI
|
|
176
|
+
this.trajectory = getTrajectoryIntegration(projectPaths.projectId, config.name);
|
|
177
|
+
// Initialize continuity manager for session persistence
|
|
178
|
+
this.continuity = getContinuityManager({ defaultCli: this.cliType });
|
|
146
179
|
// Handle incoming messages from relay
|
|
147
|
-
this.client.onMessage = (from, payload, messageId, meta) => {
|
|
148
|
-
this.handleIncomingMessage(from, payload, messageId, meta);
|
|
180
|
+
this.client.onMessage = (from, payload, messageId, meta, originalTo) => {
|
|
181
|
+
this.handleIncomingMessage(from, payload, messageId, meta, originalTo);
|
|
149
182
|
};
|
|
150
183
|
this.client.onStateChange = (state) => {
|
|
151
184
|
// Only log to stderr, never stdout (user is in tmux)
|
|
@@ -180,6 +213,46 @@ export class TmuxWrapper {
|
|
|
180
213
|
// Prefix with newline to avoid corrupting tmux status line
|
|
181
214
|
process.stderr.write(`\r[relay:${this.config.name}] ${msg}\n`);
|
|
182
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Detect PDERO phase from output content and auto-transition if needed.
|
|
218
|
+
* Also detects tool calls and errors, recording them to the trajectory.
|
|
219
|
+
*/
|
|
220
|
+
detectAndTransitionPhase(content) {
|
|
221
|
+
if (!this.trajectory)
|
|
222
|
+
return;
|
|
223
|
+
// Detect phase transitions
|
|
224
|
+
const detectedPhase = detectPhaseFromContent(content);
|
|
225
|
+
if (detectedPhase && detectedPhase !== this.lastDetectedPhase) {
|
|
226
|
+
const currentPhase = this.trajectory.getPhase();
|
|
227
|
+
if (detectedPhase !== currentPhase) {
|
|
228
|
+
this.trajectory.transition(detectedPhase, 'Auto-detected from output');
|
|
229
|
+
this.lastDetectedPhase = detectedPhase;
|
|
230
|
+
this.logStderr(`Phase transition: ${currentPhase || 'none'} → ${detectedPhase}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// Detect and record tool calls
|
|
234
|
+
// Note: We deduplicate by tool+status to record each unique tool type once per session
|
|
235
|
+
// (e.g., "Read" started, "Read" completed). This provides a summary of tools used
|
|
236
|
+
// without flooding the trajectory with every individual invocation.
|
|
237
|
+
const tools = detectToolCalls(content);
|
|
238
|
+
for (const tool of tools) {
|
|
239
|
+
const key = `${tool.tool}:${tool.status || 'started'}`;
|
|
240
|
+
if (!this.seenToolCalls.has(key)) {
|
|
241
|
+
this.seenToolCalls.add(key);
|
|
242
|
+
const statusLabel = tool.status === 'completed' ? ' (completed)' : '';
|
|
243
|
+
this.trajectory.event(`Tool: ${tool.tool}${statusLabel}`, 'tool_call');
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// Detect and record errors
|
|
247
|
+
const errors = detectErrors(content);
|
|
248
|
+
for (const error of errors) {
|
|
249
|
+
if (!this.seenErrors.has(error.message)) {
|
|
250
|
+
this.seenErrors.add(error.message);
|
|
251
|
+
const prefix = error.type === 'warning' ? 'Warning' : 'Error';
|
|
252
|
+
this.trajectory.event(`${prefix}: ${error.message}`, 'error');
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
183
256
|
/**
|
|
184
257
|
* Build the full command with proper quoting
|
|
185
258
|
* Args containing spaces need to be quoted
|
|
@@ -284,20 +357,24 @@ export class TmuxWrapper {
|
|
|
284
357
|
// Ignore on older tmux versions lacking these key tables
|
|
285
358
|
}
|
|
286
359
|
}
|
|
287
|
-
// Set environment variables
|
|
360
|
+
// Set environment variables including trail/trajectory vars
|
|
361
|
+
const projectPaths = getProjectPaths();
|
|
362
|
+
const trailEnvVars = getTrailEnvVars(projectPaths.projectId, this.config.name, projectPaths.dataDir);
|
|
288
363
|
for (const [key, value] of Object.entries({
|
|
289
364
|
...this.config.env,
|
|
365
|
+
...trailEnvVars,
|
|
290
366
|
AGENT_RELAY_NAME: this.config.name,
|
|
291
367
|
TERM: 'xterm-256color',
|
|
292
368
|
})) {
|
|
293
|
-
|
|
369
|
+
// Use proper shell escaping to prevent command injection via env var values
|
|
370
|
+
const escaped = escapeForShell(value);
|
|
294
371
|
execSync(`"${this.tmuxPath}" setenv -t ${this.sessionName} ${key} "${escaped}"`);
|
|
295
372
|
}
|
|
296
373
|
// Wait for shell to be ready (look for prompt)
|
|
297
374
|
await this.waitForShellReady();
|
|
298
375
|
// Send the command to run
|
|
299
376
|
await this.sendKeysLiteral(fullCommand);
|
|
300
|
-
await
|
|
377
|
+
await sleep(100);
|
|
301
378
|
await this.sendKeys('Enter');
|
|
302
379
|
}
|
|
303
380
|
catch (err) {
|
|
@@ -308,6 +385,10 @@ export class TmuxWrapper {
|
|
|
308
385
|
this.running = true;
|
|
309
386
|
this.lastActivityTime = Date.now();
|
|
310
387
|
this.activityState = 'active';
|
|
388
|
+
// Initialize trajectory tracking (auto-start if task provided)
|
|
389
|
+
this.initializeTrajectory();
|
|
390
|
+
// Initialize continuity and get/create agentId
|
|
391
|
+
this.initializeAgentId();
|
|
311
392
|
// Inject instructions for the agent (after a delay to let CLI initialize)
|
|
312
393
|
setTimeout(() => this.injectInstructions(), 3000);
|
|
313
394
|
// Start background polling (silent - no stdout writes)
|
|
@@ -317,28 +398,126 @@ export class TmuxWrapper {
|
|
|
317
398
|
this.attachToSession();
|
|
318
399
|
}
|
|
319
400
|
/**
|
|
320
|
-
*
|
|
401
|
+
* Initialize trajectory tracking
|
|
402
|
+
* Auto-starts a trajectory if task is provided in config
|
|
403
|
+
*/
|
|
404
|
+
async initializeTrajectory() {
|
|
405
|
+
if (!this.trajectory)
|
|
406
|
+
return;
|
|
407
|
+
// Auto-start trajectory if task is provided
|
|
408
|
+
if (this.config.task) {
|
|
409
|
+
const success = await this.trajectory.initialize(this.config.task);
|
|
410
|
+
if (success) {
|
|
411
|
+
this.logStderr(`Trajectory started for task: ${this.config.task}`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
// Just initialize without starting a trajectory
|
|
416
|
+
await this.trajectory.initialize();
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Initialize agent ID for continuity/resume functionality
|
|
421
|
+
*/
|
|
422
|
+
async initializeAgentId() {
|
|
423
|
+
if (!this.continuity)
|
|
424
|
+
return;
|
|
425
|
+
try {
|
|
426
|
+
let ledger;
|
|
427
|
+
// If resuming from a previous agent ID, try to find that ledger
|
|
428
|
+
if (this.config.resumeAgentId) {
|
|
429
|
+
ledger = await this.continuity.findLedgerByAgentId(this.config.resumeAgentId);
|
|
430
|
+
if (ledger) {
|
|
431
|
+
this.logStderr(`Resuming agent ID: ${ledger.agentId} (from previous session)`);
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
this.logStderr(`Resume agent ID ${this.config.resumeAgentId} not found, creating new`, true);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
// If not resuming or resume ID not found, get or create ledger
|
|
438
|
+
if (!ledger) {
|
|
439
|
+
ledger = await this.continuity.getOrCreateLedger(this.config.name, this.cliType);
|
|
440
|
+
this.logStderr(`Agent ID: ${ledger.agentId} (use this to resume if agent dies)`);
|
|
441
|
+
}
|
|
442
|
+
this.agentId = ledger.agentId;
|
|
443
|
+
}
|
|
444
|
+
catch (err) {
|
|
445
|
+
this.logStderr(`Failed to initialize agent ID: ${err.message}`, true);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Get the current agent ID
|
|
450
|
+
*/
|
|
451
|
+
getAgentId() {
|
|
452
|
+
return this.agentId;
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Inject usage instructions for the agent including persistence protocol
|
|
321
456
|
*/
|
|
322
457
|
async injectInstructions() {
|
|
323
458
|
if (!this.running)
|
|
324
459
|
return;
|
|
325
460
|
// Use escaped prefix (\->relay:) in examples to prevent parser from treating them as real commands
|
|
326
461
|
const escapedPrefix = '\\' + this.relayPrefix;
|
|
327
|
-
|
|
462
|
+
// Build instructions including relay and trail
|
|
463
|
+
const relayInstructions = [
|
|
328
464
|
`[Agent Relay] You are "${this.config.name}" - connected for real-time messaging.`,
|
|
329
465
|
`SEND: ${escapedPrefix}AgentName message`,
|
|
330
466
|
`MULTI-LINE: ${escapedPrefix}AgentName <<<(newline)content(newline)>>> - ALWAYS end with >>> on its own line!`,
|
|
331
|
-
`
|
|
467
|
+
`PERSIST: Output [[SUMMARY]]{"currentTask":"...","context":"..."}[[/SUMMARY]] after major work.`,
|
|
468
|
+
`END: Output [[SESSION_END]]{"summary":"..."}[[/SESSION_END]] when session complete.`,
|
|
332
469
|
].join(' | ');
|
|
470
|
+
// Add trail instructions if available
|
|
471
|
+
const trailInstructions = getCompactTrailInstructions();
|
|
333
472
|
try {
|
|
334
|
-
await this.sendKeysLiteral(
|
|
335
|
-
await
|
|
473
|
+
await this.sendKeysLiteral(relayInstructions);
|
|
474
|
+
await sleep(50);
|
|
336
475
|
await this.sendKeys('Enter');
|
|
476
|
+
// Inject trail instructions
|
|
477
|
+
if (this.trajectory?.isTrailInstalledSync()) {
|
|
478
|
+
await sleep(100);
|
|
479
|
+
await this.sendKeysLiteral(trailInstructions);
|
|
480
|
+
await sleep(50);
|
|
481
|
+
await this.sendKeys('Enter');
|
|
482
|
+
}
|
|
483
|
+
// Inject continuity context from previous session
|
|
484
|
+
await this.injectContinuityContext();
|
|
337
485
|
}
|
|
338
486
|
catch {
|
|
339
487
|
// Silent fail - instructions are nice-to-have
|
|
340
488
|
}
|
|
341
489
|
}
|
|
490
|
+
/**
|
|
491
|
+
* Inject continuity context from previous session
|
|
492
|
+
*/
|
|
493
|
+
async injectContinuityContext() {
|
|
494
|
+
if (!this.continuity || !this.running)
|
|
495
|
+
return;
|
|
496
|
+
try {
|
|
497
|
+
const context = await this.continuity.getStartupContext(this.config.name);
|
|
498
|
+
if (context && context.formatted) {
|
|
499
|
+
// Inject a brief notification about loaded context
|
|
500
|
+
const notification = `[Continuity] Previous session context loaded. ${context.ledger ? `Task: ${context.ledger.currentTask?.slice(0, 50) || 'unknown'}` : ''}${context.handoff ? ` | Last handoff: ${context.handoff.createdAt.toISOString().split('T')[0]}` : ''}`;
|
|
501
|
+
await sleep(200);
|
|
502
|
+
await this.sendKeysLiteral(notification);
|
|
503
|
+
await sleep(50);
|
|
504
|
+
await this.sendKeys('Enter');
|
|
505
|
+
// Queue the full context for injection when agent is ready
|
|
506
|
+
this.messageQueue.push({
|
|
507
|
+
from: 'system',
|
|
508
|
+
body: context.formatted,
|
|
509
|
+
messageId: `continuity-startup-${Date.now()}`,
|
|
510
|
+
});
|
|
511
|
+
this.checkForInjectionOpportunity();
|
|
512
|
+
if (this.config.debug) {
|
|
513
|
+
this.logStderr(`[CONTINUITY] Loaded context for ${this.config.name}`);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
catch (err) {
|
|
518
|
+
this.logStderr(`[CONTINUITY] Failed to load context: ${err.message}`, true);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
342
521
|
/**
|
|
343
522
|
* Wait for tmux session to be ready
|
|
344
523
|
*/
|
|
@@ -372,14 +551,14 @@ export class TmuxWrapper {
|
|
|
372
551
|
if (promptPatterns.test(lastLine)) {
|
|
373
552
|
this.logStderr('Shell ready');
|
|
374
553
|
// Extra delay to ensure shell is fully ready
|
|
375
|
-
await
|
|
554
|
+
await sleep(200);
|
|
376
555
|
return;
|
|
377
556
|
}
|
|
378
557
|
}
|
|
379
558
|
catch {
|
|
380
559
|
// Session might not be ready yet
|
|
381
560
|
}
|
|
382
|
-
await
|
|
561
|
+
await sleep(200);
|
|
383
562
|
}
|
|
384
563
|
// Fallback: proceed anyway after timeout
|
|
385
564
|
this.logStderr('Shell ready timeout, proceeding anyway');
|
|
@@ -433,10 +612,10 @@ export class TmuxWrapper {
|
|
|
433
612
|
`"${this.tmuxPath}" capture-pane -t ${this.sessionName} -p -J -S - 2>/dev/null`);
|
|
434
613
|
// Always parse the FULL capture for ->relay commands
|
|
435
614
|
// This handles terminal UIs that rewrite content in place
|
|
436
|
-
const cleanContent =
|
|
615
|
+
const cleanContent = stripAnsi(stdout);
|
|
437
616
|
// Join continuation lines that TUIs split across multiple lines
|
|
438
617
|
const joinedContent = this.joinContinuationLines(cleanContent);
|
|
439
|
-
const { commands } = this.parser.parse(joinedContent);
|
|
618
|
+
const { commands, output: filteredOutput } = this.parser.parse(joinedContent);
|
|
440
619
|
// Debug: log relay commands being parsed
|
|
441
620
|
if (commands.length > 0 && this.config.debug) {
|
|
442
621
|
for (const cmd of commands) {
|
|
@@ -449,6 +628,16 @@ export class TmuxWrapper {
|
|
|
449
628
|
this.lastOutputTime = Date.now();
|
|
450
629
|
this.markActivity();
|
|
451
630
|
this.processedOutputLength = stdout.length;
|
|
631
|
+
// Stream new output to daemon for dashboard log viewing
|
|
632
|
+
// Use filtered output to exclude thinking blocks and relay commands
|
|
633
|
+
if (this.config.streamLogs && this.client.state === 'READY') {
|
|
634
|
+
// Send incremental filtered output since last log
|
|
635
|
+
const newContent = filteredOutput.substring(this.lastLoggedLength);
|
|
636
|
+
if (newContent.length > 0) {
|
|
637
|
+
this.client.sendLog(newContent);
|
|
638
|
+
this.lastLoggedLength = filteredOutput.length;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
452
641
|
}
|
|
453
642
|
// Send any commands found (deduplication handles repeats)
|
|
454
643
|
for (const cmd of commands) {
|
|
@@ -456,6 +645,10 @@ export class TmuxWrapper {
|
|
|
456
645
|
}
|
|
457
646
|
// Check for [[SUMMARY]] blocks and save to storage
|
|
458
647
|
this.parseSummaryAndSave(cleanContent);
|
|
648
|
+
// Detect PDERO phase transitions from output content
|
|
649
|
+
this.detectAndTransitionPhase(cleanContent);
|
|
650
|
+
// Parse and handle continuity commands (->continuity:save, ->continuity:load, etc.)
|
|
651
|
+
await this.parseContinuityCommands(joinedContent);
|
|
459
652
|
// Check for [[SESSION_END]] blocks to explicitly close session
|
|
460
653
|
this.parseSessionEndAndClose(cleanContent);
|
|
461
654
|
// Check for ->relay:spawn and ->relay:release commands (any agent can spawn)
|
|
@@ -471,13 +664,6 @@ export class TmuxWrapper {
|
|
|
471
664
|
}
|
|
472
665
|
}
|
|
473
666
|
}
|
|
474
|
-
/**
|
|
475
|
-
* Strip ANSI escape codes
|
|
476
|
-
*/
|
|
477
|
-
stripAnsi(str) {
|
|
478
|
-
// eslint-disable-next-line no-control-regex
|
|
479
|
-
return str.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
|
|
480
|
-
}
|
|
481
667
|
/**
|
|
482
668
|
* Join continuation lines after ->relay commands.
|
|
483
669
|
* Claude Code and other TUIs insert real newlines in output, causing
|
|
@@ -600,6 +786,8 @@ export class TmuxWrapper {
|
|
|
600
786
|
this.queuedMessageHashes.delete(msgHash);
|
|
601
787
|
const truncatedBody = cmd.body.substring(0, Math.min(RELAY_LOG_TRUNCATE_LENGTH, cmd.body.length));
|
|
602
788
|
this.logStderr(`→ ${cmd.to}: ${truncatedBody}...`);
|
|
789
|
+
// Record in trajectory via trail
|
|
790
|
+
this.trajectory?.message('sent', this.config.name, cmd.to, cmd.body);
|
|
603
791
|
}
|
|
604
792
|
else if (this.client.state !== 'READY') {
|
|
605
793
|
// Only log failure once per state change
|
|
@@ -647,7 +835,14 @@ export class TmuxWrapper {
|
|
|
647
835
|
if (summaryHash === this.lastSummaryHash)
|
|
648
836
|
return;
|
|
649
837
|
this.lastSummaryHash = summaryHash;
|
|
650
|
-
//
|
|
838
|
+
// Save to continuity ledger for session recovery
|
|
839
|
+
// This ensures the ledger has actual data instead of placeholders
|
|
840
|
+
if (this.continuity) {
|
|
841
|
+
this.saveSummaryToLedger(summary).catch(err => {
|
|
842
|
+
this.logStderr(`Failed to save summary to ledger: ${err.message}`, true);
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
// Wait for storage to be ready before saving to project storage
|
|
651
846
|
this.storageReady.then(ready => {
|
|
652
847
|
if (!ready || !this.storage) {
|
|
653
848
|
this.logStderr('Cannot save summary: storage not initialized');
|
|
@@ -669,6 +864,86 @@ export class TmuxWrapper {
|
|
|
669
864
|
});
|
|
670
865
|
});
|
|
671
866
|
}
|
|
867
|
+
/**
|
|
868
|
+
* Save a parsed summary to the continuity ledger.
|
|
869
|
+
* Maps summary fields to ledger fields for session recovery.
|
|
870
|
+
*/
|
|
871
|
+
async saveSummaryToLedger(summary) {
|
|
872
|
+
if (!this.continuity)
|
|
873
|
+
return;
|
|
874
|
+
const updates = {};
|
|
875
|
+
// Map summary fields to ledger fields
|
|
876
|
+
if (summary.currentTask) {
|
|
877
|
+
updates.currentTask = summary.currentTask;
|
|
878
|
+
}
|
|
879
|
+
if (summary.completedTasks && summary.completedTasks.length > 0) {
|
|
880
|
+
updates.completed = summary.completedTasks;
|
|
881
|
+
}
|
|
882
|
+
if (summary.context) {
|
|
883
|
+
// Store context in inProgress as "next steps" hint
|
|
884
|
+
updates.inProgress = [summary.context];
|
|
885
|
+
}
|
|
886
|
+
if (summary.files && summary.files.length > 0) {
|
|
887
|
+
updates.fileContext = summary.files.map((f) => ({ path: f }));
|
|
888
|
+
}
|
|
889
|
+
// Only save if we have meaningful updates
|
|
890
|
+
if (Object.keys(updates).length > 0) {
|
|
891
|
+
await this.continuity.saveLedger(this.config.name, updates);
|
|
892
|
+
this.logStderr('Saved summary to continuity ledger');
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* Parse ->continuity: commands from output and handle them.
|
|
897
|
+
* Supported commands:
|
|
898
|
+
* ->continuity:save <<<...>>> - Save session state to ledger
|
|
899
|
+
* ->continuity:load - Request context injection
|
|
900
|
+
* ->continuity:search "query" - Search past handoffs
|
|
901
|
+
* ->continuity:uncertain "..." - Mark item as uncertain
|
|
902
|
+
* ->continuity:handoff <<<...>>> - Create explicit handoff
|
|
903
|
+
*/
|
|
904
|
+
async parseContinuityCommands(content) {
|
|
905
|
+
if (!this.continuity)
|
|
906
|
+
return;
|
|
907
|
+
if (!hasContinuityCommand(content))
|
|
908
|
+
return;
|
|
909
|
+
const command = parseContinuityCommand(content);
|
|
910
|
+
if (!command)
|
|
911
|
+
return;
|
|
912
|
+
// Create a hash for deduplication
|
|
913
|
+
// For commands with content (save, handoff, uncertain), use content hash
|
|
914
|
+
// For commands without content (load, search), allow each unique call
|
|
915
|
+
const hasContent = command.content || command.query || command.item;
|
|
916
|
+
const cmdHash = hasContent
|
|
917
|
+
? `${command.type}:${command.content || command.query || command.item}`
|
|
918
|
+
: `${command.type}:${Date.now()}`; // Allow load/search to run each time
|
|
919
|
+
if (hasContent && this.processedContinuityCommands.has(cmdHash))
|
|
920
|
+
return;
|
|
921
|
+
this.processedContinuityCommands.add(cmdHash);
|
|
922
|
+
// Limit dedup set size
|
|
923
|
+
if (this.processedContinuityCommands.size > 100) {
|
|
924
|
+
const oldest = this.processedContinuityCommands.values().next().value;
|
|
925
|
+
if (oldest)
|
|
926
|
+
this.processedContinuityCommands.delete(oldest);
|
|
927
|
+
}
|
|
928
|
+
try {
|
|
929
|
+
if (this.config.debug) {
|
|
930
|
+
this.logStderr(`[CONTINUITY] Processing ${command.type} command`);
|
|
931
|
+
}
|
|
932
|
+
const response = await this.continuity.handleCommand(this.config.name, command);
|
|
933
|
+
// If there's a response (e.g., from load or search), inject it
|
|
934
|
+
if (response) {
|
|
935
|
+
this.messageQueue.push({
|
|
936
|
+
from: 'system',
|
|
937
|
+
body: response,
|
|
938
|
+
messageId: `continuity-${Date.now()}`,
|
|
939
|
+
});
|
|
940
|
+
this.checkForInjectionOpportunity();
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
catch (err) {
|
|
944
|
+
this.logStderr(`[CONTINUITY] Error: ${err.message}`, true);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
672
947
|
/**
|
|
673
948
|
* Parse [[SESSION_END]] blocks from output and close session explicitly.
|
|
674
949
|
* Agents output this to mark their work session as complete:
|
|
@@ -676,6 +951,8 @@ export class TmuxWrapper {
|
|
|
676
951
|
* [[SESSION_END]]
|
|
677
952
|
* {"summary": "Completed auth module", "completedTasks": ["login", "logout"]}
|
|
678
953
|
* [[/SESSION_END]]
|
|
954
|
+
*
|
|
955
|
+
* Also stores the data for use in autoSave to populate handoff (fixes empty handoff issue).
|
|
679
956
|
*/
|
|
680
957
|
parseSessionEndAndClose(content) {
|
|
681
958
|
if (this.sessionEndProcessed)
|
|
@@ -683,6 +960,8 @@ export class TmuxWrapper {
|
|
|
683
960
|
const sessionEnd = parseSessionEndFromOutput(content);
|
|
684
961
|
if (!sessionEnd)
|
|
685
962
|
return;
|
|
963
|
+
// Store SESSION_END data for use in autoSave (fixes empty handoff issue)
|
|
964
|
+
this.sessionEndData = sessionEnd;
|
|
686
965
|
// Get session ID from client connection - if not available yet, don't set flag
|
|
687
966
|
// so we can retry when sessionId becomes available
|
|
688
967
|
const sessionId = this.client.currentSessionId;
|
|
@@ -707,64 +986,226 @@ export class TmuxWrapper {
|
|
|
707
986
|
});
|
|
708
987
|
});
|
|
709
988
|
}
|
|
989
|
+
/**
|
|
990
|
+
* Execute spawn via API (if dashboardPort set) or callback
|
|
991
|
+
*/
|
|
992
|
+
async executeSpawn(name, cli, task) {
|
|
993
|
+
if (this.config.dashboardPort) {
|
|
994
|
+
// Use dashboard API for spawning (works from any context, no terminal required)
|
|
995
|
+
try {
|
|
996
|
+
const response = await fetch(`http://localhost:${this.config.dashboardPort}/api/spawn`, {
|
|
997
|
+
method: 'POST',
|
|
998
|
+
headers: { 'Content-Type': 'application/json' },
|
|
999
|
+
body: JSON.stringify({ name, cli, task }),
|
|
1000
|
+
});
|
|
1001
|
+
const result = await response.json();
|
|
1002
|
+
if (result.success) {
|
|
1003
|
+
this.logStderr(`Spawned ${name} via API`);
|
|
1004
|
+
}
|
|
1005
|
+
else {
|
|
1006
|
+
this.logStderr(`Spawn failed: ${result.error}`, true);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
catch (err) {
|
|
1010
|
+
this.logStderr(`Spawn API call failed: ${err.message}`, true);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
else if (this.config.onSpawn) {
|
|
1014
|
+
// Fall back to callback
|
|
1015
|
+
try {
|
|
1016
|
+
await this.config.onSpawn(name, cli, task);
|
|
1017
|
+
}
|
|
1018
|
+
catch (err) {
|
|
1019
|
+
this.logStderr(`Spawn failed: ${err.message}`, true);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Execute release via API (if dashboardPort set) or callback
|
|
1025
|
+
*/
|
|
1026
|
+
async executeRelease(name) {
|
|
1027
|
+
if (this.config.dashboardPort) {
|
|
1028
|
+
// Use dashboard API for release (works from any context, no terminal required)
|
|
1029
|
+
try {
|
|
1030
|
+
const response = await fetch(`http://localhost:${this.config.dashboardPort}/api/spawned/${encodeURIComponent(name)}`, {
|
|
1031
|
+
method: 'DELETE',
|
|
1032
|
+
});
|
|
1033
|
+
const result = await response.json();
|
|
1034
|
+
if (result.success) {
|
|
1035
|
+
this.logStderr(`Released ${name} via API`);
|
|
1036
|
+
}
|
|
1037
|
+
else {
|
|
1038
|
+
this.logStderr(`Release failed: ${result.error}`, true);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
catch (err) {
|
|
1042
|
+
this.logStderr(`Release API call failed: ${err.message}`, true);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
else if (this.config.onRelease) {
|
|
1046
|
+
// Fall back to callback
|
|
1047
|
+
try {
|
|
1048
|
+
await this.config.onRelease(name);
|
|
1049
|
+
}
|
|
1050
|
+
catch (err) {
|
|
1051
|
+
this.logStderr(`Release failed: ${err.message}`, true);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
710
1055
|
/**
|
|
711
1056
|
* Parse ->relay:spawn and ->relay:release commands from output.
|
|
712
|
-
*
|
|
713
|
-
* ->relay:spawn WorkerName cli "task description"
|
|
1057
|
+
* Supports two formats:
|
|
1058
|
+
* Single-line: ->relay:spawn WorkerName cli "task description"
|
|
1059
|
+
* Multi-line (fenced): ->relay:spawn WorkerName cli <<<
|
|
1060
|
+
* task description here
|
|
1061
|
+
* can span multiple lines>>>
|
|
714
1062
|
* ->relay:release WorkerName
|
|
715
1063
|
*/
|
|
716
1064
|
parseSpawnReleaseCommands(content) {
|
|
717
|
-
// Only process if callbacks
|
|
718
|
-
|
|
1065
|
+
// Only process if we have API or callbacks configured
|
|
1066
|
+
const canSpawn = this.config.dashboardPort || this.config.onSpawn;
|
|
1067
|
+
const canRelease = this.config.dashboardPort || this.config.onRelease;
|
|
1068
|
+
if (!canSpawn && !canRelease)
|
|
719
1069
|
return;
|
|
720
1070
|
const lines = content.split('\n');
|
|
1071
|
+
// Pattern to strip common line prefixes (bullets, prompts, etc.)
|
|
1072
|
+
// Must include ● (U+25CF BLACK CIRCLE) used by Claude's TUI
|
|
1073
|
+
const linePrefixPattern = /^(?:[>$%#→➜›»●•◦‣⁃\-*⏺◆◇○□■│┃┆┇┊┋╎╏✦]\s*)+/;
|
|
721
1074
|
for (const line of lines) {
|
|
722
|
-
|
|
723
|
-
//
|
|
724
|
-
|
|
725
|
-
//
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
const
|
|
729
|
-
|
|
730
|
-
|
|
1075
|
+
let trimmed = line.trim();
|
|
1076
|
+
// Strip common line prefixes (bullets, prompts) before checking for commands
|
|
1077
|
+
trimmed = trimmed.replace(linePrefixPattern, '');
|
|
1078
|
+
// If we're in fenced spawn mode, accumulate lines until we see >>>
|
|
1079
|
+
if (this.pendingFencedSpawn) {
|
|
1080
|
+
// Check for fence close (>>> at end of line or on its own line)
|
|
1081
|
+
const closeIdx = trimmed.indexOf('>>>');
|
|
1082
|
+
if (closeIdx !== -1) {
|
|
1083
|
+
// Add content before >>> to task
|
|
1084
|
+
const contentBeforeClose = trimmed.substring(0, closeIdx);
|
|
1085
|
+
if (contentBeforeClose) {
|
|
1086
|
+
this.pendingFencedSpawn.taskLines.push(contentBeforeClose);
|
|
1087
|
+
}
|
|
1088
|
+
// Execute the spawn with accumulated task
|
|
1089
|
+
const { name, cli, taskLines } = this.pendingFencedSpawn;
|
|
1090
|
+
const taskStr = taskLines.join('\n').trim();
|
|
1091
|
+
const spawnKey = `${name}:${cli}`;
|
|
1092
|
+
if (!this.processedSpawnCommands.has(spawnKey)) {
|
|
1093
|
+
this.processedSpawnCommands.add(spawnKey);
|
|
1094
|
+
if (taskStr) {
|
|
1095
|
+
this.logStderr(`Spawn command (fenced): ${name} (${cli}) - "${taskStr.substring(0, 50)}..."`);
|
|
1096
|
+
}
|
|
1097
|
+
else {
|
|
1098
|
+
this.logStderr(`Spawn command (fenced): ${name} (${cli}) - no task`);
|
|
1099
|
+
}
|
|
1100
|
+
this.executeSpawn(name, cli, taskStr);
|
|
1101
|
+
}
|
|
1102
|
+
this.pendingFencedSpawn = null;
|
|
1103
|
+
}
|
|
1104
|
+
else {
|
|
1105
|
+
// Accumulate line as part of task
|
|
1106
|
+
this.pendingFencedSpawn.taskLines.push(line);
|
|
1107
|
+
}
|
|
1108
|
+
continue;
|
|
1109
|
+
}
|
|
1110
|
+
// Check for fenced spawn start: ->relay:spawn Name [cli] <<< (CLI optional, defaults to 'claude')
|
|
1111
|
+
// Prefixes are stripped above, so we just look for the command at start of line
|
|
1112
|
+
const fencedSpawnMatch = trimmed.match(/^->relay:spawn\s+(\S+)(?:\s+(\S+))?\s+<<<(.*)$/);
|
|
1113
|
+
if (fencedSpawnMatch && canSpawn) {
|
|
1114
|
+
const [, name, cliOrUndefined, inlineContent] = fencedSpawnMatch;
|
|
1115
|
+
const cli = cliOrUndefined || 'claude';
|
|
1116
|
+
// Validate name
|
|
1117
|
+
if (name.length < 2) {
|
|
1118
|
+
this.logStderr(`Fenced spawn has invalid name, skipping: name=${name}`);
|
|
1119
|
+
continue;
|
|
1120
|
+
}
|
|
1121
|
+
// Check if fence closes on same line (e.g., ->relay:spawn Worker cli <<<task>>>)
|
|
1122
|
+
const inlineCloseIdx = inlineContent.indexOf('>>>');
|
|
1123
|
+
if (inlineCloseIdx !== -1) {
|
|
1124
|
+
// Single line fenced: extract task between <<< and >>>
|
|
1125
|
+
const taskStr = inlineContent.substring(0, inlineCloseIdx).trim();
|
|
1126
|
+
const spawnKey = `${name}:${cli}`;
|
|
1127
|
+
if (!this.processedSpawnCommands.has(spawnKey)) {
|
|
1128
|
+
this.processedSpawnCommands.add(spawnKey);
|
|
1129
|
+
if (taskStr) {
|
|
1130
|
+
this.logStderr(`Spawn command: ${name} (${cli}) - "${taskStr.substring(0, 50)}..."`);
|
|
1131
|
+
}
|
|
1132
|
+
else {
|
|
1133
|
+
this.logStderr(`Spawn command: ${name} (${cli}) - no task`);
|
|
1134
|
+
}
|
|
1135
|
+
this.executeSpawn(name, cli, taskStr);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
else {
|
|
1139
|
+
// Start multi-line fenced mode
|
|
1140
|
+
this.pendingFencedSpawn = {
|
|
1141
|
+
name,
|
|
1142
|
+
cli,
|
|
1143
|
+
taskLines: inlineContent.trim() ? [inlineContent.trim()] : [],
|
|
1144
|
+
};
|
|
1145
|
+
this.logStderr(`Starting fenced spawn capture: ${name} (${cli})`);
|
|
1146
|
+
}
|
|
1147
|
+
continue;
|
|
1148
|
+
}
|
|
1149
|
+
// Match single-line spawn: ->relay:spawn WorkerName [cli] ["task"]
|
|
1150
|
+
// CLI is optional - defaults to 'claude'. Task is also optional.
|
|
1151
|
+
// Prefixes are stripped above, so we just look for the command at start of line
|
|
1152
|
+
const spawnMatch = trimmed.match(/^->relay:spawn\s+(\S+)(?:\s+(\S+))?(?:\s+["'](.+?)["'])?\s*$/);
|
|
1153
|
+
if (spawnMatch && canSpawn) {
|
|
1154
|
+
const [, name, cliOrUndefined, task] = spawnMatch;
|
|
1155
|
+
const cli = cliOrUndefined || 'claude';
|
|
1156
|
+
// Validate the parsed values
|
|
1157
|
+
if (cli === '<<<' || cli === '>>>' || name === '<<<' || name === '>>>') {
|
|
1158
|
+
this.logStderr(`Invalid spawn command (fence markers), skipping: name=${name}, cli=${cli}`);
|
|
1159
|
+
continue;
|
|
1160
|
+
}
|
|
1161
|
+
if (name.length < 2) {
|
|
1162
|
+
this.logStderr(`Spawn command has suspiciously short name, skipping: name=${name}`);
|
|
1163
|
+
continue;
|
|
1164
|
+
}
|
|
1165
|
+
const taskStr = task || '';
|
|
1166
|
+
const spawnKey = `${name}:${cli}`;
|
|
731
1167
|
if (!this.processedSpawnCommands.has(spawnKey)) {
|
|
732
1168
|
this.processedSpawnCommands.add(spawnKey);
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
1169
|
+
if (taskStr) {
|
|
1170
|
+
this.logStderr(`Spawn command: ${name} (${cli}) - "${taskStr.substring(0, 50)}..."`);
|
|
1171
|
+
}
|
|
1172
|
+
else {
|
|
1173
|
+
this.logStderr(`Spawn command: ${name} (${cli}) - no task`);
|
|
1174
|
+
}
|
|
1175
|
+
this.executeSpawn(name, cli, taskStr);
|
|
737
1176
|
}
|
|
738
1177
|
continue;
|
|
739
1178
|
}
|
|
740
1179
|
// Match ->relay:release WorkerName
|
|
741
|
-
//
|
|
742
|
-
const releaseMatch = trimmed.match(
|
|
743
|
-
if (releaseMatch &&
|
|
1180
|
+
// Prefixes are stripped above, so we just look for the command at start of line
|
|
1181
|
+
const releaseMatch = trimmed.match(/^->relay:release\s+(\S+)\s*$/);
|
|
1182
|
+
if (releaseMatch && canRelease) {
|
|
744
1183
|
const [, name] = releaseMatch;
|
|
745
|
-
// Dedup - only process each release once
|
|
746
1184
|
if (!this.processedReleaseCommands.has(name)) {
|
|
747
1185
|
this.processedReleaseCommands.add(name);
|
|
748
1186
|
this.logStderr(`Release command: ${name}`);
|
|
749
|
-
this.
|
|
750
|
-
this.logStderr(`Release failed: ${err.message}`, true);
|
|
751
|
-
});
|
|
1187
|
+
this.executeRelease(name);
|
|
752
1188
|
}
|
|
753
1189
|
}
|
|
754
1190
|
}
|
|
755
1191
|
}
|
|
756
1192
|
/**
|
|
757
1193
|
* Handle incoming message from relay
|
|
1194
|
+
* @param originalTo - The original 'to' field from sender. '*' indicates this was a broadcast message.
|
|
1195
|
+
* Agents should reply to originalTo to maintain channel routing (e.g., respond to #general, not DM).
|
|
758
1196
|
*/
|
|
759
|
-
handleIncomingMessage(from, payload, messageId, meta) {
|
|
1197
|
+
handleIncomingMessage(from, payload, messageId, meta, originalTo) {
|
|
760
1198
|
if (this.hasSeenIncoming(messageId)) {
|
|
761
1199
|
this.logStderr(`← ${from}: duplicate delivery (${messageId.substring(0, 8)})`);
|
|
762
1200
|
return;
|
|
763
1201
|
}
|
|
764
1202
|
const truncatedBody = payload.body.substring(0, Math.min(DEBUG_LOG_TRUNCATE_LENGTH, payload.body.length));
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
1203
|
+
const channelInfo = originalTo === '*' ? ' [broadcast]' : '';
|
|
1204
|
+
this.logStderr(`← ${from}${channelInfo}: ${truncatedBody}...`);
|
|
1205
|
+
// Record in trajectory via trail
|
|
1206
|
+
this.trajectory?.message('received', from, this.config.name, payload.body);
|
|
1207
|
+
// Queue for injection - include originalTo so we can inform the agent how to route responses
|
|
1208
|
+
this.messageQueue.push({ from, body: payload.body, messageId, thread: payload.thread, importance: meta?.importance, data: payload.data, originalTo });
|
|
768
1209
|
// Write to inbox if enabled
|
|
769
1210
|
if (this.inbox) {
|
|
770
1211
|
this.inbox.addMessage(from, payload.body);
|
|
@@ -792,7 +1233,8 @@ export class TmuxWrapper {
|
|
|
792
1233
|
this.injectNextMessage();
|
|
793
1234
|
}
|
|
794
1235
|
/**
|
|
795
|
-
* Inject message via tmux send-keys
|
|
1236
|
+
* Inject message via tmux send-keys.
|
|
1237
|
+
* Uses shared injection logic with tmux-specific callbacks.
|
|
796
1238
|
*/
|
|
797
1239
|
async injectNextMessage() {
|
|
798
1240
|
const msg = this.messageQueue.shift();
|
|
@@ -801,32 +1243,21 @@ export class TmuxWrapper {
|
|
|
801
1243
|
this.isInjecting = true;
|
|
802
1244
|
this.logStderr(`Injecting message from ${msg.from} (cli: ${this.cliType})`);
|
|
803
1245
|
try {
|
|
804
|
-
let sanitizedBody = msg.body.replace(/[\r\n]+/g, ' ').trim();
|
|
805
|
-
// Gemini interprets certain keywords (While, For, If, etc.) as shell commands
|
|
806
|
-
// Wrap in backticks to prevent shell keyword interpretation
|
|
807
|
-
if (this.cliType === 'gemini') {
|
|
808
|
-
sanitizedBody = `\`${sanitizedBody.replace(/`/g, "'")}\``;
|
|
809
|
-
}
|
|
810
|
-
// Short message ID for display (first 8 chars)
|
|
811
1246
|
const shortId = msg.messageId.substring(0, 8);
|
|
812
|
-
// Remove message truncation to allow full messages to pass through
|
|
813
|
-
const wasTruncated = false;
|
|
814
|
-
// Always include message ID; add lookup hint if truncated
|
|
815
|
-
const idTag = `[${shortId}]`;
|
|
816
|
-
const truncationHint = wasTruncated
|
|
817
|
-
? ` [TRUNCATED - run "agent-relay read ${msg.messageId}"]`
|
|
818
|
-
: '';
|
|
819
1247
|
// Wait for input to be clear before injecting
|
|
1248
|
+
// If input is not clear (human typing), re-queue and try later - never clear forcefully!
|
|
1249
|
+
// Fix for agent-relay-j9z: forceful clearing destroys human input in progress
|
|
820
1250
|
const waitTimeoutMs = this.config.inputWaitTimeoutMs ?? 5000;
|
|
821
1251
|
const waitPollMs = this.config.inputWaitPollMs ?? 200;
|
|
822
1252
|
const inputClear = await this.waitForClearInput(waitTimeoutMs, waitPollMs);
|
|
823
1253
|
if (!inputClear) {
|
|
824
|
-
// Input still has text after timeout - clear
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
1254
|
+
// Input still has text after timeout - DON'T clear forcefully, re-queue instead
|
|
1255
|
+
// This preserves any human input in progress
|
|
1256
|
+
this.logStderr('Input not clear after waiting, re-queuing injection to preserve human input');
|
|
1257
|
+
this.messageQueue.unshift(msg);
|
|
1258
|
+
this.isInjecting = false;
|
|
1259
|
+
setTimeout(() => this.checkForInjectionOpportunity(), this.config.injectRetryMs ?? 1000);
|
|
1260
|
+
return;
|
|
830
1261
|
}
|
|
831
1262
|
// Ensure pane output is stable to avoid interleaving with active generation
|
|
832
1263
|
const stablePane = await this.waitForStablePane(this.config.outputStabilityTimeoutMs ?? 2000, this.config.outputStabilityPollMs ?? 200);
|
|
@@ -841,8 +1272,8 @@ export class TmuxWrapper {
|
|
|
841
1272
|
// If at shell prompt, skip injection to avoid shell command execution
|
|
842
1273
|
if (this.cliType === 'gemini') {
|
|
843
1274
|
const lastLine = await this.getLastLine();
|
|
844
|
-
const cleanLine =
|
|
845
|
-
if (
|
|
1275
|
+
const cleanLine = stripAnsi(lastLine).trim();
|
|
1276
|
+
if (CLI_QUIRKS.isShellPrompt(cleanLine)) {
|
|
846
1277
|
this.logStderr('Gemini at shell prompt, skipping injection to avoid shell execution');
|
|
847
1278
|
// Re-queue the message for later
|
|
848
1279
|
this.messageQueue.unshift(msg);
|
|
@@ -851,20 +1282,51 @@ export class TmuxWrapper {
|
|
|
851
1282
|
return;
|
|
852
1283
|
}
|
|
853
1284
|
}
|
|
854
|
-
//
|
|
855
|
-
|
|
856
|
-
//
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
//
|
|
866
|
-
|
|
867
|
-
|
|
1285
|
+
// Build injection string using shared utility
|
|
1286
|
+
let injection = buildInjectionString(msg);
|
|
1287
|
+
// Gemini-specific: wrap body in backticks to prevent shell keyword interpretation
|
|
1288
|
+
if (this.cliType === 'gemini') {
|
|
1289
|
+
const colonIdx = injection.indexOf(': ');
|
|
1290
|
+
if (colonIdx > 0) {
|
|
1291
|
+
const prefix = injection.substring(0, colonIdx + 2);
|
|
1292
|
+
const body = injection.substring(colonIdx + 2);
|
|
1293
|
+
injection = prefix + CLI_QUIRKS.wrapForGemini(body);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
// Create callbacks for shared injection logic
|
|
1297
|
+
const callbacks = {
|
|
1298
|
+
getOutput: async () => {
|
|
1299
|
+
try {
|
|
1300
|
+
const { stdout } = await execAsync(`"${this.tmuxPath}" capture-pane -t ${this.sessionName} -p -S - 2>/dev/null`);
|
|
1301
|
+
return stdout;
|
|
1302
|
+
}
|
|
1303
|
+
catch {
|
|
1304
|
+
return '';
|
|
1305
|
+
}
|
|
1306
|
+
},
|
|
1307
|
+
performInjection: async (inj) => {
|
|
1308
|
+
await this.pasteLiteral(inj);
|
|
1309
|
+
await sleep(INJECTION_CONSTANTS.ENTER_DELAY_MS);
|
|
1310
|
+
await this.sendKeys('Enter');
|
|
1311
|
+
},
|
|
1312
|
+
log: (message) => this.logStderr(message),
|
|
1313
|
+
logError: (message) => this.logStderr(message, true),
|
|
1314
|
+
getMetrics: () => this.injectionMetrics,
|
|
1315
|
+
};
|
|
1316
|
+
// Inject with retry and verification using shared logic
|
|
1317
|
+
const result = await sharedInjectWithRetry(injection, shortId, msg.from, callbacks);
|
|
1318
|
+
if (result.success) {
|
|
1319
|
+
this.logStderr(`Injection complete (attempt ${result.attempts})`);
|
|
1320
|
+
}
|
|
1321
|
+
else {
|
|
1322
|
+
// All retries failed - log and optionally fall back to inbox
|
|
1323
|
+
this.logStderr(`Message delivery failed after ${result.attempts} attempts: from=${msg.from} id=${shortId}`, true);
|
|
1324
|
+
// Write to inbox as fallback if enabled
|
|
1325
|
+
if (this.inbox) {
|
|
1326
|
+
this.inbox.addMessage(msg.from, msg.body);
|
|
1327
|
+
this.logStderr('Wrote message to inbox as fallback');
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
868
1330
|
}
|
|
869
1331
|
catch (err) {
|
|
870
1332
|
this.logStderr(`Injection failed: ${err.message}`, true);
|
|
@@ -872,7 +1334,7 @@ export class TmuxWrapper {
|
|
|
872
1334
|
finally {
|
|
873
1335
|
this.isInjecting = false;
|
|
874
1336
|
if (this.messageQueue.length > 0) {
|
|
875
|
-
setTimeout(() => this.checkForInjectionOpportunity(),
|
|
1337
|
+
setTimeout(() => this.checkForInjectionOpportunity(), INJECTION_CONSTANTS.QUEUE_PROCESS_DELAY_MS);
|
|
876
1338
|
}
|
|
877
1339
|
}
|
|
878
1340
|
}
|
|
@@ -927,7 +1389,7 @@ export class TmuxWrapper {
|
|
|
927
1389
|
// Set tmux buffer then paste
|
|
928
1390
|
// Skip bracketed paste (-p) for CLIs that don't handle it properly (droid, other)
|
|
929
1391
|
await execAsync(`"${this.tmuxPath}" set-buffer -- "${escaped}"`);
|
|
930
|
-
const useBracketedPaste = this.cliType === 'claude' || this.cliType === 'codex' || this.cliType === 'gemini';
|
|
1392
|
+
const useBracketedPaste = this.cliType === 'claude' || this.cliType === 'codex' || this.cliType === 'gemini' || this.cliType === 'opencode';
|
|
931
1393
|
if (useBracketedPaste) {
|
|
932
1394
|
await execAsync(`"${this.tmuxPath}" paste-buffer -t ${this.sessionName} -p`);
|
|
933
1395
|
}
|
|
@@ -935,9 +1397,6 @@ export class TmuxWrapper {
|
|
|
935
1397
|
await execAsync(`"${this.tmuxPath}" paste-buffer -t ${this.sessionName}`);
|
|
936
1398
|
}
|
|
937
1399
|
}
|
|
938
|
-
sleep(ms) {
|
|
939
|
-
return new Promise(r => setTimeout(r, ms));
|
|
940
|
-
}
|
|
941
1400
|
/**
|
|
942
1401
|
* Reset session-specific state for wrapper reuse.
|
|
943
1402
|
* Call this when starting a new session with the same wrapper instance.
|
|
@@ -946,18 +1405,13 @@ export class TmuxWrapper {
|
|
|
946
1405
|
this.sessionEndProcessed = false;
|
|
947
1406
|
this.lastSummaryHash = '';
|
|
948
1407
|
this.lastSummaryRawContent = '';
|
|
1408
|
+
this.sessionEndData = undefined;
|
|
949
1409
|
}
|
|
950
1410
|
/**
|
|
951
1411
|
* Get the prompt pattern for the current CLI type.
|
|
952
1412
|
*/
|
|
953
1413
|
getPromptPattern() {
|
|
954
|
-
|
|
955
|
-
claude: /^[>›»]\s*$/, // Claude: "> " or similar
|
|
956
|
-
gemini: /^[>›»]\s*$/, // Gemini: "> "
|
|
957
|
-
codex: /^[>›»]\s*$/, // Codex: "> "
|
|
958
|
-
other: /^[>$%#➜›»]\s*$/, // Shell or other: "$ ", "> ", etc.
|
|
959
|
-
};
|
|
960
|
-
return promptPatterns[this.cliType] || promptPatterns.other;
|
|
1414
|
+
return CLI_QUIRKS.getPromptPattern(this.cliType);
|
|
961
1415
|
}
|
|
962
1416
|
/**
|
|
963
1417
|
* Capture the last non-empty line from the tmux pane.
|
|
@@ -976,7 +1430,7 @@ export class TmuxWrapper {
|
|
|
976
1430
|
* Detect if the provided line contains visible user input (beyond the prompt).
|
|
977
1431
|
*/
|
|
978
1432
|
hasVisibleInput(line) {
|
|
979
|
-
const cleanLine =
|
|
1433
|
+
const cleanLine = stripAnsi(line).trimEnd();
|
|
980
1434
|
if (cleanLine === '')
|
|
981
1435
|
return false;
|
|
982
1436
|
return !this.getPromptPattern().test(cleanLine);
|
|
@@ -988,7 +1442,7 @@ export class TmuxWrapper {
|
|
|
988
1442
|
async isInputClear(lastLine) {
|
|
989
1443
|
try {
|
|
990
1444
|
const lineToCheck = lastLine ?? await this.getLastLine();
|
|
991
|
-
const cleanLine =
|
|
1445
|
+
const cleanLine = stripAnsi(lineToCheck).trimEnd();
|
|
992
1446
|
const isClear = this.getPromptPattern().test(cleanLine);
|
|
993
1447
|
if (this.config.debug) {
|
|
994
1448
|
const truncatedLine = cleanLine.substring(0, Math.min(DEBUG_LOG_TRUNCATE_LENGTH, cleanLine.length));
|
|
@@ -1048,7 +1502,7 @@ export class TmuxWrapper {
|
|
|
1048
1502
|
stableCursorCount = 0;
|
|
1049
1503
|
lastCursorX = cursorX;
|
|
1050
1504
|
}
|
|
1051
|
-
await
|
|
1505
|
+
await sleep(pollIntervalMs);
|
|
1052
1506
|
}
|
|
1053
1507
|
this.logStderr(`waitForClearInput: timed out after ${maxWaitMs}ms`);
|
|
1054
1508
|
return false;
|
|
@@ -1077,7 +1531,7 @@ export class TmuxWrapper {
|
|
|
1077
1531
|
return false;
|
|
1078
1532
|
let stableCount = 0;
|
|
1079
1533
|
while (Date.now() - start < maxWaitMs) {
|
|
1080
|
-
await
|
|
1534
|
+
await sleep(pollIntervalMs);
|
|
1081
1535
|
const sig = await this.capturePaneSignature();
|
|
1082
1536
|
if (!sig)
|
|
1083
1537
|
continue;
|
|
@@ -1103,6 +1557,13 @@ export class TmuxWrapper {
|
|
|
1103
1557
|
return;
|
|
1104
1558
|
this.running = false;
|
|
1105
1559
|
this.activityState = 'disconnected';
|
|
1560
|
+
// Auto-save continuity state before shutdown (fire and forget)
|
|
1561
|
+
// Pass sessionEndData to populate handoff (fixes empty handoff issue)
|
|
1562
|
+
if (this.continuity) {
|
|
1563
|
+
this.continuity.autoSave(this.config.name, 'session_end', this.sessionEndData).catch((err) => {
|
|
1564
|
+
this.logStderr(`[CONTINUITY] Auto-save failed: ${err.message}`, true);
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1106
1567
|
// Reset session state for potential reuse
|
|
1107
1568
|
this.resetSessionState();
|
|
1108
1569
|
// Stop polling
|