agent-relay 1.1.0 → 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/spawner.d.ts +53 -0
- package/dist/bridge/spawner.d.ts.map +1 -1
- package/dist/bridge/spawner.js +203 -19
- package/dist/bridge/spawner.js.map +1 -1
- package/dist/bridge/types.d.ts +12 -0
- package/dist/bridge/types.d.ts.map +1 -1
- package/dist/cli/index.js +401 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/cloud/api/auth.d.ts +3 -2
- package/dist/cloud/api/auth.d.ts.map +1 -1
- package/dist/cloud/api/auth.js +10 -98
- package/dist/cloud/api/auth.js.map +1 -1
- 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/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 +7 -0
- package/dist/cloud/api/middleware/planLimits.d.ts.map +1 -1
- package/dist/cloud/api/middleware/planLimits.js +39 -1
- package/dist/cloud/api/middleware/planLimits.js.map +1 -1
- 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 +8 -1
- package/dist/cloud/api/onboarding.d.ts.map +1 -1
- package/dist/cloud/api/onboarding.js +300 -119
- package/dist/cloud/api/onboarding.js.map +1 -1
- 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.js +114 -42
- package/dist/cloud/api/providers.js.map +1 -1
- 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/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.js +225 -8
- package/dist/cloud/api/workspaces.js.map +1 -1
- package/dist/cloud/billing/plans.d.ts.map +1 -1
- package/dist/cloud/billing/plans.js +13 -0
- package/dist/cloud/billing/plans.js.map +1 -1
- package/dist/cloud/billing/types.d.ts +9 -3
- package/dist/cloud/billing/types.d.ts.map +1 -1
- package/dist/cloud/config.d.ts +9 -2
- package/dist/cloud/config.d.ts.map +1 -1
- package/dist/cloud/config.js +13 -4
- package/dist/cloud/config.js.map +1 -1
- package/dist/cloud/db/drizzle.d.ts +84 -1
- package/dist/cloud/db/drizzle.d.ts.map +1 -1
- package/dist/cloud/db/drizzle.js +470 -0
- package/dist/cloud/db/drizzle.js.map +1 -1
- package/dist/cloud/db/index.d.ts +9 -4
- package/dist/cloud/db/index.d.ts.map +1 -1
- package/dist/cloud/db/index.js +11 -3
- package/dist/cloud/db/index.js.map +1 -1
- package/dist/cloud/db/schema.d.ts +3283 -556
- package/dist/cloud/db/schema.d.ts.map +1 -1
- package/dist/cloud/db/schema.js +314 -1
- package/dist/cloud/db/schema.js.map +1 -1
- package/dist/cloud/index.d.ts +1 -0
- package/dist/cloud/index.d.ts.map +1 -1
- package/dist/cloud/index.js +2 -0
- package/dist/cloud/index.js.map +1 -1
- package/dist/cloud/provisioner/index.d.ts +24 -0
- package/dist/cloud/provisioner/index.d.ts.map +1 -1
- package/dist/cloud/provisioner/index.js +319 -18
- package/dist/cloud/provisioner/index.js.map +1 -1
- package/dist/cloud/server.d.ts +1 -0
- package/dist/cloud/server.d.ts.map +1 -1
- package/dist/cloud/server.js +357 -13
- package/dist/cloud/server.js.map +1 -1
- 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/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 +15 -0
- package/dist/cloud/services/planLimits.d.ts.map +1 -1
- package/dist/cloud/services/planLimits.js +28 -0
- package/dist/cloud/services/planLimits.js.map +1 -1
- 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.js +1 -1
- package/dist/cloud/vault/index.js.map +1 -1
- 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 +27 -0
- package/dist/daemon/agent-manager.d.ts.map +1 -1
- package/dist/daemon/agent-manager.js +107 -6
- package/dist/daemon/agent-manager.js.map +1 -1
- package/dist/daemon/agent-registry.d.ts +32 -0
- package/dist/daemon/agent-registry.d.ts.map +1 -1
- package/dist/daemon/agent-registry.js +42 -2
- package/dist/daemon/agent-registry.js.map +1 -1
- package/dist/daemon/api.d.ts +12 -0
- package/dist/daemon/api.d.ts.map +1 -1
- package/dist/daemon/api.js +131 -2
- package/dist/daemon/api.js.map +1 -1
- 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.map +1 -1
- package/dist/daemon/cloud-sync.js +9 -7
- package/dist/daemon/cloud-sync.js.map +1 -1
- package/dist/daemon/orchestrator.d.ts.map +1 -1
- package/dist/daemon/orchestrator.js +30 -0
- package/dist/daemon/orchestrator.js.map +1 -1
- package/dist/daemon/router.d.ts +5 -0
- package/dist/daemon/router.d.ts.map +1 -1
- package/dist/daemon/router.js +78 -26
- package/dist/daemon/router.js.map +1 -1
- package/dist/daemon/server.d.ts +5 -0
- package/dist/daemon/server.d.ts.map +1 -1
- package/dist/daemon/server.js +9 -1
- 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 +5 -1
- package/dist/daemon/types.d.ts.map +1 -1
- 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-b6edd4dde8d08194.js → page-56a8b4616a90dc43.js} +1 -1
- 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/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-c2f423b9c9f4591b.js → main-97850e03d723ea8c.js} +1 -1
- 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/app.html +1 -14
- package/dist/dashboard/out/app.txt +2 -2
- 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 -1
- package/dist/dashboard/out/history.txt +2 -2
- 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 -1
- package/dist/dashboard/out/metrics.txt +2 -2
- package/dist/dashboard/out/pricing.html +2 -2
- package/dist/dashboard/out/pricing.txt +2 -2
- 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/server.d.ts.map +1 -1
- package/dist/dashboard-server/server.js +1308 -8
- 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/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/index.d.ts +5 -0
- package/dist/resiliency/index.d.ts.map +1 -1
- package/dist/resiliency/index.js +5 -0
- package/dist/resiliency/index.js.map +1 -1
- 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/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/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 +38 -0
- package/dist/resiliency/supervisor.d.ts.map +1 -1
- package/dist/resiliency/supervisor.js +122 -0
- package/dist/resiliency/supervisor.js.map +1 -1
- 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 +1 -1
- package/dist/storage/adapter.d.ts.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/logger.js +1 -1
- package/dist/utils/logger.js.map +1 -1
- 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/parser.d.ts +10 -0
- package/dist/wrapper/parser.d.ts.map +1 -1
- package/dist/wrapper/parser.js +100 -33
- package/dist/wrapper/parser.js.map +1 -1
- package/dist/wrapper/pty-wrapper.d.ts +197 -16
- package/dist/wrapper/pty-wrapper.d.ts.map +1 -1
- package/dist/wrapper/pty-wrapper.js +943 -106
- 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 +73 -11
- package/dist/wrapper/tmux-wrapper.d.ts.map +1 -1
- package/dist/wrapper/tmux-wrapper.js +541 -120
- package/dist/wrapper/tmux-wrapper.js.map +1 -1
- package/docs/CLOUD-ARCHITECTURE.md +152 -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 +53 -47
- package/docs/archive/EXECUTIVE_SUMMARY.md +358 -0
- package/docs/archive/ROADMAP.md +329 -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/local-testing.md +428 -0
- package/docs/proposals/continuous-claude-integration.md +622 -0
- package/docs/proposals/custom-commands.md +368 -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 +16 -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/693-7b3301d8f6bc5014.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/713-f78477eb185f1f4d.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/766-e53e1cfe39b0b5b5.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/900-037c64bfd797fb2a.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/app/page-e3d9e1f4466b9bae.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/metrics/page-e68825a81db67ba1.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/page-cc108bf68c8a657f.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/webpack-a5acc2831d094776.js +0 -1
- package/dist/dashboard/out/_next/static/css/79b80143647a07d7.css +0 -1
- package/dist/dashboard/out/_next/static/css/8cf277370ad48cfe.css +0 -1
- /package/dist/dashboard/out/_next/static/{6HHWb2ZmnJ4OSm0zUP7h4 → H5aWG0udPB4iOUIl_gytz}/_buildManifest.js +0 -0
- /package/dist/dashboard/out/_next/static/{6HHWb2ZmnJ4OSm0zUP7h4 → H5aWG0udPB4iOUIl_gytz}/_ssgManifest.js +0 -0
- /package/dist/dashboard/out/_next/static/chunks/{117-b2cd8d6485aacf2b.js → 117-b100311aff8d5c61.js} +0 -0
- /package/dist/dashboard/out/_next/static/chunks/{648-8f3f26864ce515e5.js → 648-a13d3c2b1be45466.js} +0 -0
- /package/dist/dashboard/out/_next/static/chunks/app/_not-found/{page-0b990dbb71d72a98.js → page-a4973f3e3c82fb67.js} +0 -0
- /package/dist/dashboard/out/_next/static/chunks/app/pricing/{page-d80e03a5297f95b6.js → page-4d72d5a5d8a9b618.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/{TESTING_PRESENCE_FEATURES.md → archive/TESTING_PRESENCE_FEATURES.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;
|
|
@@ -67,15 +72,25 @@ export class TmuxWrapper {
|
|
|
67
72
|
lastSummaryHash = ''; // Dedup summary saves
|
|
68
73
|
lastSummaryRawContent = ''; // Dedup invalid JSON error logging
|
|
69
74
|
sessionEndProcessed = false; // Track if we've already processed session end
|
|
75
|
+
sessionEndData; // Store SESSION_END data for handoff
|
|
70
76
|
pendingRelayCommands = [];
|
|
71
77
|
queuedMessageHashes = new Set(); // For offline queue dedup
|
|
72
78
|
MAX_PENDING_RELAY_COMMANDS = 50;
|
|
73
79
|
processedSpawnCommands = new Set(); // Dedup spawn commands
|
|
74
80
|
processedReleaseCommands = new Set(); // Dedup release commands
|
|
81
|
+
pendingFencedSpawn = null; // Track multi-line spawn task
|
|
75
82
|
receivedMessageIdSet = new Set();
|
|
76
83
|
receivedMessageIdOrder = [];
|
|
77
84
|
MAX_RECEIVED_MESSAGES = 2000;
|
|
78
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
|
|
79
94
|
constructor(config) {
|
|
80
95
|
this.config = {
|
|
81
96
|
cols: process.stdout.columns || 120,
|
|
@@ -109,6 +124,9 @@ export class TmuxWrapper {
|
|
|
109
124
|
else if (cmdLower.includes('droid')) {
|
|
110
125
|
this.cliType = 'droid';
|
|
111
126
|
}
|
|
127
|
+
else if (cmdLower.includes('opencode')) {
|
|
128
|
+
this.cliType = 'opencode';
|
|
129
|
+
}
|
|
112
130
|
else {
|
|
113
131
|
this.cliType = 'other';
|
|
114
132
|
}
|
|
@@ -118,13 +136,22 @@ export class TmuxWrapper {
|
|
|
118
136
|
this.sessionName = `relay-${config.name}`;
|
|
119
137
|
// Resolve tmux path early so we fail fast if tmux isn't available
|
|
120
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
|
+
}
|
|
121
148
|
this.client = new RelayClient({
|
|
122
149
|
agentName: config.name,
|
|
123
150
|
socketPath: config.socketPath,
|
|
124
151
|
cli: this.cliType,
|
|
125
152
|
program: this.config.program,
|
|
126
153
|
model: this.config.model,
|
|
127
|
-
task:
|
|
154
|
+
task: detectedTask,
|
|
128
155
|
workingDirectory: this.config.cwd ?? process.cwd(),
|
|
129
156
|
quiet: true, // Keep stdout clean; we log to stderr via wrapper
|
|
130
157
|
});
|
|
@@ -145,6 +172,10 @@ export class TmuxWrapper {
|
|
|
145
172
|
this.storage = undefined;
|
|
146
173
|
return false;
|
|
147
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 });
|
|
148
179
|
// Handle incoming messages from relay
|
|
149
180
|
this.client.onMessage = (from, payload, messageId, meta, originalTo) => {
|
|
150
181
|
this.handleIncomingMessage(from, payload, messageId, meta, originalTo);
|
|
@@ -182,6 +213,46 @@ export class TmuxWrapper {
|
|
|
182
213
|
// Prefix with newline to avoid corrupting tmux status line
|
|
183
214
|
process.stderr.write(`\r[relay:${this.config.name}] ${msg}\n`);
|
|
184
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
|
+
}
|
|
185
256
|
/**
|
|
186
257
|
* Build the full command with proper quoting
|
|
187
258
|
* Args containing spaces need to be quoted
|
|
@@ -286,20 +357,24 @@ export class TmuxWrapper {
|
|
|
286
357
|
// Ignore on older tmux versions lacking these key tables
|
|
287
358
|
}
|
|
288
359
|
}
|
|
289
|
-
// 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);
|
|
290
363
|
for (const [key, value] of Object.entries({
|
|
291
364
|
...this.config.env,
|
|
365
|
+
...trailEnvVars,
|
|
292
366
|
AGENT_RELAY_NAME: this.config.name,
|
|
293
367
|
TERM: 'xterm-256color',
|
|
294
368
|
})) {
|
|
295
|
-
|
|
369
|
+
// Use proper shell escaping to prevent command injection via env var values
|
|
370
|
+
const escaped = escapeForShell(value);
|
|
296
371
|
execSync(`"${this.tmuxPath}" setenv -t ${this.sessionName} ${key} "${escaped}"`);
|
|
297
372
|
}
|
|
298
373
|
// Wait for shell to be ready (look for prompt)
|
|
299
374
|
await this.waitForShellReady();
|
|
300
375
|
// Send the command to run
|
|
301
376
|
await this.sendKeysLiteral(fullCommand);
|
|
302
|
-
await
|
|
377
|
+
await sleep(100);
|
|
303
378
|
await this.sendKeys('Enter');
|
|
304
379
|
}
|
|
305
380
|
catch (err) {
|
|
@@ -310,6 +385,10 @@ export class TmuxWrapper {
|
|
|
310
385
|
this.running = true;
|
|
311
386
|
this.lastActivityTime = Date.now();
|
|
312
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();
|
|
313
392
|
// Inject instructions for the agent (after a delay to let CLI initialize)
|
|
314
393
|
setTimeout(() => this.injectInstructions(), 3000);
|
|
315
394
|
// Start background polling (silent - no stdout writes)
|
|
@@ -319,28 +398,126 @@ export class TmuxWrapper {
|
|
|
319
398
|
this.attachToSession();
|
|
320
399
|
}
|
|
321
400
|
/**
|
|
322
|
-
*
|
|
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
|
|
323
456
|
*/
|
|
324
457
|
async injectInstructions() {
|
|
325
458
|
if (!this.running)
|
|
326
459
|
return;
|
|
327
460
|
// Use escaped prefix (\->relay:) in examples to prevent parser from treating them as real commands
|
|
328
461
|
const escapedPrefix = '\\' + this.relayPrefix;
|
|
329
|
-
|
|
462
|
+
// Build instructions including relay and trail
|
|
463
|
+
const relayInstructions = [
|
|
330
464
|
`[Agent Relay] You are "${this.config.name}" - connected for real-time messaging.`,
|
|
331
465
|
`SEND: ${escapedPrefix}AgentName message`,
|
|
332
466
|
`MULTI-LINE: ${escapedPrefix}AgentName <<<(newline)content(newline)>>> - ALWAYS end with >>> on its own line!`,
|
|
333
|
-
`
|
|
467
|
+
`PERSIST: Output [[SUMMARY]]{"currentTask":"...","context":"..."}[[/SUMMARY]] after major work.`,
|
|
468
|
+
`END: Output [[SESSION_END]]{"summary":"..."}[[/SESSION_END]] when session complete.`,
|
|
334
469
|
].join(' | ');
|
|
470
|
+
// Add trail instructions if available
|
|
471
|
+
const trailInstructions = getCompactTrailInstructions();
|
|
335
472
|
try {
|
|
336
|
-
await this.sendKeysLiteral(
|
|
337
|
-
await
|
|
473
|
+
await this.sendKeysLiteral(relayInstructions);
|
|
474
|
+
await sleep(50);
|
|
338
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();
|
|
339
485
|
}
|
|
340
486
|
catch {
|
|
341
487
|
// Silent fail - instructions are nice-to-have
|
|
342
488
|
}
|
|
343
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
|
+
}
|
|
344
521
|
/**
|
|
345
522
|
* Wait for tmux session to be ready
|
|
346
523
|
*/
|
|
@@ -374,14 +551,14 @@ export class TmuxWrapper {
|
|
|
374
551
|
if (promptPatterns.test(lastLine)) {
|
|
375
552
|
this.logStderr('Shell ready');
|
|
376
553
|
// Extra delay to ensure shell is fully ready
|
|
377
|
-
await
|
|
554
|
+
await sleep(200);
|
|
378
555
|
return;
|
|
379
556
|
}
|
|
380
557
|
}
|
|
381
558
|
catch {
|
|
382
559
|
// Session might not be ready yet
|
|
383
560
|
}
|
|
384
|
-
await
|
|
561
|
+
await sleep(200);
|
|
385
562
|
}
|
|
386
563
|
// Fallback: proceed anyway after timeout
|
|
387
564
|
this.logStderr('Shell ready timeout, proceeding anyway');
|
|
@@ -435,10 +612,10 @@ export class TmuxWrapper {
|
|
|
435
612
|
`"${this.tmuxPath}" capture-pane -t ${this.sessionName} -p -J -S - 2>/dev/null`);
|
|
436
613
|
// Always parse the FULL capture for ->relay commands
|
|
437
614
|
// This handles terminal UIs that rewrite content in place
|
|
438
|
-
const cleanContent =
|
|
615
|
+
const cleanContent = stripAnsi(stdout);
|
|
439
616
|
// Join continuation lines that TUIs split across multiple lines
|
|
440
617
|
const joinedContent = this.joinContinuationLines(cleanContent);
|
|
441
|
-
const { commands } = this.parser.parse(joinedContent);
|
|
618
|
+
const { commands, output: filteredOutput } = this.parser.parse(joinedContent);
|
|
442
619
|
// Debug: log relay commands being parsed
|
|
443
620
|
if (commands.length > 0 && this.config.debug) {
|
|
444
621
|
for (const cmd of commands) {
|
|
@@ -452,12 +629,13 @@ export class TmuxWrapper {
|
|
|
452
629
|
this.markActivity();
|
|
453
630
|
this.processedOutputLength = stdout.length;
|
|
454
631
|
// Stream new output to daemon for dashboard log viewing
|
|
632
|
+
// Use filtered output to exclude thinking blocks and relay commands
|
|
455
633
|
if (this.config.streamLogs && this.client.state === 'READY') {
|
|
456
|
-
// Send incremental output since last log
|
|
457
|
-
const newContent =
|
|
634
|
+
// Send incremental filtered output since last log
|
|
635
|
+
const newContent = filteredOutput.substring(this.lastLoggedLength);
|
|
458
636
|
if (newContent.length > 0) {
|
|
459
637
|
this.client.sendLog(newContent);
|
|
460
|
-
this.lastLoggedLength =
|
|
638
|
+
this.lastLoggedLength = filteredOutput.length;
|
|
461
639
|
}
|
|
462
640
|
}
|
|
463
641
|
}
|
|
@@ -467,6 +645,10 @@ export class TmuxWrapper {
|
|
|
467
645
|
}
|
|
468
646
|
// Check for [[SUMMARY]] blocks and save to storage
|
|
469
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);
|
|
470
652
|
// Check for [[SESSION_END]] blocks to explicitly close session
|
|
471
653
|
this.parseSessionEndAndClose(cleanContent);
|
|
472
654
|
// Check for ->relay:spawn and ->relay:release commands (any agent can spawn)
|
|
@@ -482,18 +664,6 @@ export class TmuxWrapper {
|
|
|
482
664
|
}
|
|
483
665
|
}
|
|
484
666
|
}
|
|
485
|
-
/**
|
|
486
|
-
* Strip ANSI escape codes
|
|
487
|
-
*/
|
|
488
|
-
stripAnsi(str) {
|
|
489
|
-
// Strip ANSI escape sequences (with \x1B prefix)
|
|
490
|
-
// eslint-disable-next-line no-control-regex
|
|
491
|
-
let result = str.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
|
|
492
|
-
// Strip orphaned CSI sequences that lost their escape byte
|
|
493
|
-
// These look like [?25h, [?2026l, [0m, etc. at the start of content
|
|
494
|
-
result = result.replace(/^\s*(\[\??\d*[A-Za-z])+\s*/g, '');
|
|
495
|
-
return result;
|
|
496
|
-
}
|
|
497
667
|
/**
|
|
498
668
|
* Join continuation lines after ->relay commands.
|
|
499
669
|
* Claude Code and other TUIs insert real newlines in output, causing
|
|
@@ -616,6 +786,8 @@ export class TmuxWrapper {
|
|
|
616
786
|
this.queuedMessageHashes.delete(msgHash);
|
|
617
787
|
const truncatedBody = cmd.body.substring(0, Math.min(RELAY_LOG_TRUNCATE_LENGTH, cmd.body.length));
|
|
618
788
|
this.logStderr(`→ ${cmd.to}: ${truncatedBody}...`);
|
|
789
|
+
// Record in trajectory via trail
|
|
790
|
+
this.trajectory?.message('sent', this.config.name, cmd.to, cmd.body);
|
|
619
791
|
}
|
|
620
792
|
else if (this.client.state !== 'READY') {
|
|
621
793
|
// Only log failure once per state change
|
|
@@ -663,7 +835,14 @@ export class TmuxWrapper {
|
|
|
663
835
|
if (summaryHash === this.lastSummaryHash)
|
|
664
836
|
return;
|
|
665
837
|
this.lastSummaryHash = summaryHash;
|
|
666
|
-
//
|
|
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
|
|
667
846
|
this.storageReady.then(ready => {
|
|
668
847
|
if (!ready || !this.storage) {
|
|
669
848
|
this.logStderr('Cannot save summary: storage not initialized');
|
|
@@ -685,6 +864,86 @@ export class TmuxWrapper {
|
|
|
685
864
|
});
|
|
686
865
|
});
|
|
687
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
|
+
}
|
|
688
947
|
/**
|
|
689
948
|
* Parse [[SESSION_END]] blocks from output and close session explicitly.
|
|
690
949
|
* Agents output this to mark their work session as complete:
|
|
@@ -692,6 +951,8 @@ export class TmuxWrapper {
|
|
|
692
951
|
* [[SESSION_END]]
|
|
693
952
|
* {"summary": "Completed auth module", "completedTasks": ["login", "logout"]}
|
|
694
953
|
* [[/SESSION_END]]
|
|
954
|
+
*
|
|
955
|
+
* Also stores the data for use in autoSave to populate handoff (fixes empty handoff issue).
|
|
695
956
|
*/
|
|
696
957
|
parseSessionEndAndClose(content) {
|
|
697
958
|
if (this.sessionEndProcessed)
|
|
@@ -699,6 +960,8 @@ export class TmuxWrapper {
|
|
|
699
960
|
const sessionEnd = parseSessionEndFromOutput(content);
|
|
700
961
|
if (!sessionEnd)
|
|
701
962
|
return;
|
|
963
|
+
// Store SESSION_END data for use in autoSave (fixes empty handoff issue)
|
|
964
|
+
this.sessionEndData = sessionEnd;
|
|
702
965
|
// Get session ID from client connection - if not available yet, don't set flag
|
|
703
966
|
// so we can retry when sessionId becomes available
|
|
704
967
|
const sessionId = this.client.currentSessionId;
|
|
@@ -723,29 +986,184 @@ export class TmuxWrapper {
|
|
|
723
986
|
});
|
|
724
987
|
});
|
|
725
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
|
+
}
|
|
726
1055
|
/**
|
|
727
1056
|
* Parse ->relay:spawn and ->relay:release commands from output.
|
|
728
|
-
*
|
|
729
|
-
* ->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>>>
|
|
730
1062
|
* ->relay:release WorkerName
|
|
731
1063
|
*/
|
|
732
1064
|
parseSpawnReleaseCommands(content) {
|
|
733
|
-
// Only process if callbacks
|
|
734
|
-
|
|
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)
|
|
735
1069
|
return;
|
|
736
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*)+/;
|
|
737
1074
|
for (const line of lines) {
|
|
738
|
-
|
|
739
|
-
//
|
|
740
|
-
|
|
741
|
-
//
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
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 || '';
|
|
747
1166
|
const spawnKey = `${name}:${cli}`;
|
|
748
|
-
// Dedup - only process each spawn once (keyed by name:cli, not including task)
|
|
749
1167
|
if (!this.processedSpawnCommands.has(spawnKey)) {
|
|
750
1168
|
this.processedSpawnCommands.add(spawnKey);
|
|
751
1169
|
if (taskStr) {
|
|
@@ -754,24 +1172,19 @@ export class TmuxWrapper {
|
|
|
754
1172
|
else {
|
|
755
1173
|
this.logStderr(`Spawn command: ${name} (${cli}) - no task`);
|
|
756
1174
|
}
|
|
757
|
-
this.
|
|
758
|
-
this.logStderr(`Spawn failed: ${err.message}`, true);
|
|
759
|
-
});
|
|
1175
|
+
this.executeSpawn(name, cli, taskStr);
|
|
760
1176
|
}
|
|
761
1177
|
continue;
|
|
762
1178
|
}
|
|
763
1179
|
// Match ->relay:release WorkerName
|
|
764
|
-
//
|
|
765
|
-
const releaseMatch = trimmed.match(
|
|
766
|
-
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) {
|
|
767
1183
|
const [, name] = releaseMatch;
|
|
768
|
-
// Dedup - only process each release once
|
|
769
1184
|
if (!this.processedReleaseCommands.has(name)) {
|
|
770
1185
|
this.processedReleaseCommands.add(name);
|
|
771
1186
|
this.logStderr(`Release command: ${name}`);
|
|
772
|
-
this.
|
|
773
|
-
this.logStderr(`Release failed: ${err.message}`, true);
|
|
774
|
-
});
|
|
1187
|
+
this.executeRelease(name);
|
|
775
1188
|
}
|
|
776
1189
|
}
|
|
777
1190
|
}
|
|
@@ -789,6 +1202,8 @@ export class TmuxWrapper {
|
|
|
789
1202
|
const truncatedBody = payload.body.substring(0, Math.min(DEBUG_LOG_TRUNCATE_LENGTH, payload.body.length));
|
|
790
1203
|
const channelInfo = originalTo === '*' ? ' [broadcast]' : '';
|
|
791
1204
|
this.logStderr(`← ${from}${channelInfo}: ${truncatedBody}...`);
|
|
1205
|
+
// Record in trajectory via trail
|
|
1206
|
+
this.trajectory?.message('received', from, this.config.name, payload.body);
|
|
792
1207
|
// Queue for injection - include originalTo so we can inform the agent how to route responses
|
|
793
1208
|
this.messageQueue.push({ from, body: payload.body, messageId, thread: payload.thread, importance: meta?.importance, data: payload.data, originalTo });
|
|
794
1209
|
// Write to inbox if enabled
|
|
@@ -818,7 +1233,8 @@ export class TmuxWrapper {
|
|
|
818
1233
|
this.injectNextMessage();
|
|
819
1234
|
}
|
|
820
1235
|
/**
|
|
821
|
-
* Inject message via tmux send-keys
|
|
1236
|
+
* Inject message via tmux send-keys.
|
|
1237
|
+
* Uses shared injection logic with tmux-specific callbacks.
|
|
822
1238
|
*/
|
|
823
1239
|
async injectNextMessage() {
|
|
824
1240
|
const msg = this.messageQueue.shift();
|
|
@@ -827,33 +1243,21 @@ export class TmuxWrapper {
|
|
|
827
1243
|
this.isInjecting = true;
|
|
828
1244
|
this.logStderr(`Injecting message from ${msg.from} (cli: ${this.cliType})`);
|
|
829
1245
|
try {
|
|
830
|
-
// Strip ANSI escape sequences and orphaned control sequences from message body
|
|
831
|
-
let sanitizedBody = this.stripAnsi(msg.body).replace(/[\r\n]+/g, ' ').trim();
|
|
832
|
-
// Gemini interprets certain keywords (While, For, If, etc.) as shell commands
|
|
833
|
-
// Wrap in backticks to prevent shell keyword interpretation
|
|
834
|
-
if (this.cliType === 'gemini') {
|
|
835
|
-
sanitizedBody = `\`${sanitizedBody.replace(/`/g, "'")}\``;
|
|
836
|
-
}
|
|
837
|
-
// Short message ID for display (first 8 chars)
|
|
838
1246
|
const shortId = msg.messageId.substring(0, 8);
|
|
839
|
-
// Remove message truncation to allow full messages to pass through
|
|
840
|
-
const wasTruncated = false;
|
|
841
|
-
// Always include message ID; add lookup hint if truncated
|
|
842
|
-
const idTag = `[${shortId}]`;
|
|
843
|
-
const truncationHint = wasTruncated
|
|
844
|
-
? ` [TRUNCATED - run "agent-relay read ${msg.messageId}"]`
|
|
845
|
-
: '';
|
|
846
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
|
|
847
1250
|
const waitTimeoutMs = this.config.inputWaitTimeoutMs ?? 5000;
|
|
848
1251
|
const waitPollMs = this.config.inputWaitPollMs ?? 200;
|
|
849
1252
|
const inputClear = await this.waitForClearInput(waitTimeoutMs, waitPollMs);
|
|
850
1253
|
if (!inputClear) {
|
|
851
|
-
// Input still has text after timeout - clear
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
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;
|
|
857
1261
|
}
|
|
858
1262
|
// Ensure pane output is stable to avoid interleaving with active generation
|
|
859
1263
|
const stablePane = await this.waitForStablePane(this.config.outputStabilityTimeoutMs ?? 2000, this.config.outputStabilityPollMs ?? 200);
|
|
@@ -868,8 +1272,8 @@ export class TmuxWrapper {
|
|
|
868
1272
|
// If at shell prompt, skip injection to avoid shell command execution
|
|
869
1273
|
if (this.cliType === 'gemini') {
|
|
870
1274
|
const lastLine = await this.getLastLine();
|
|
871
|
-
const cleanLine =
|
|
872
|
-
if (
|
|
1275
|
+
const cleanLine = stripAnsi(lastLine).trim();
|
|
1276
|
+
if (CLI_QUIRKS.isShellPrompt(cleanLine)) {
|
|
873
1277
|
this.logStderr('Gemini at shell prompt, skipping injection to avoid shell execution');
|
|
874
1278
|
// Re-queue the message for later
|
|
875
1279
|
this.messageQueue.unshift(msg);
|
|
@@ -878,33 +1282,51 @@ export class TmuxWrapper {
|
|
|
878
1282
|
return;
|
|
879
1283
|
}
|
|
880
1284
|
}
|
|
881
|
-
//
|
|
882
|
-
|
|
883
|
-
//
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
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');
|
|
899
1328
|
}
|
|
900
1329
|
}
|
|
901
|
-
const injection = `Relay message from ${msg.from} ${idTag}${threadHint}${importanceHint}${channelHint}${attachmentHint}: ${sanitizedBody}${truncationHint}`;
|
|
902
|
-
// Paste message as a bracketed paste to avoid interleaving with active output
|
|
903
|
-
await this.pasteLiteral(injection);
|
|
904
|
-
await this.sleep(30);
|
|
905
|
-
// Submit
|
|
906
|
-
await this.sendKeys('Enter');
|
|
907
|
-
this.logStderr(`Injection complete`);
|
|
908
1330
|
}
|
|
909
1331
|
catch (err) {
|
|
910
1332
|
this.logStderr(`Injection failed: ${err.message}`, true);
|
|
@@ -912,7 +1334,7 @@ export class TmuxWrapper {
|
|
|
912
1334
|
finally {
|
|
913
1335
|
this.isInjecting = false;
|
|
914
1336
|
if (this.messageQueue.length > 0) {
|
|
915
|
-
setTimeout(() => this.checkForInjectionOpportunity(),
|
|
1337
|
+
setTimeout(() => this.checkForInjectionOpportunity(), INJECTION_CONSTANTS.QUEUE_PROCESS_DELAY_MS);
|
|
916
1338
|
}
|
|
917
1339
|
}
|
|
918
1340
|
}
|
|
@@ -967,7 +1389,7 @@ export class TmuxWrapper {
|
|
|
967
1389
|
// Set tmux buffer then paste
|
|
968
1390
|
// Skip bracketed paste (-p) for CLIs that don't handle it properly (droid, other)
|
|
969
1391
|
await execAsync(`"${this.tmuxPath}" set-buffer -- "${escaped}"`);
|
|
970
|
-
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';
|
|
971
1393
|
if (useBracketedPaste) {
|
|
972
1394
|
await execAsync(`"${this.tmuxPath}" paste-buffer -t ${this.sessionName} -p`);
|
|
973
1395
|
}
|
|
@@ -975,9 +1397,6 @@ export class TmuxWrapper {
|
|
|
975
1397
|
await execAsync(`"${this.tmuxPath}" paste-buffer -t ${this.sessionName}`);
|
|
976
1398
|
}
|
|
977
1399
|
}
|
|
978
|
-
sleep(ms) {
|
|
979
|
-
return new Promise(r => setTimeout(r, ms));
|
|
980
|
-
}
|
|
981
1400
|
/**
|
|
982
1401
|
* Reset session-specific state for wrapper reuse.
|
|
983
1402
|
* Call this when starting a new session with the same wrapper instance.
|
|
@@ -986,18 +1405,13 @@ export class TmuxWrapper {
|
|
|
986
1405
|
this.sessionEndProcessed = false;
|
|
987
1406
|
this.lastSummaryHash = '';
|
|
988
1407
|
this.lastSummaryRawContent = '';
|
|
1408
|
+
this.sessionEndData = undefined;
|
|
989
1409
|
}
|
|
990
1410
|
/**
|
|
991
1411
|
* Get the prompt pattern for the current CLI type.
|
|
992
1412
|
*/
|
|
993
1413
|
getPromptPattern() {
|
|
994
|
-
|
|
995
|
-
claude: /^[>›»]\s*$/, // Claude: "> " or similar
|
|
996
|
-
gemini: /^[>›»]\s*$/, // Gemini: "> "
|
|
997
|
-
codex: /^[>›»]\s*$/, // Codex: "> "
|
|
998
|
-
other: /^[>$%#➜›»]\s*$/, // Shell or other: "$ ", "> ", etc.
|
|
999
|
-
};
|
|
1000
|
-
return promptPatterns[this.cliType] || promptPatterns.other;
|
|
1414
|
+
return CLI_QUIRKS.getPromptPattern(this.cliType);
|
|
1001
1415
|
}
|
|
1002
1416
|
/**
|
|
1003
1417
|
* Capture the last non-empty line from the tmux pane.
|
|
@@ -1016,7 +1430,7 @@ export class TmuxWrapper {
|
|
|
1016
1430
|
* Detect if the provided line contains visible user input (beyond the prompt).
|
|
1017
1431
|
*/
|
|
1018
1432
|
hasVisibleInput(line) {
|
|
1019
|
-
const cleanLine =
|
|
1433
|
+
const cleanLine = stripAnsi(line).trimEnd();
|
|
1020
1434
|
if (cleanLine === '')
|
|
1021
1435
|
return false;
|
|
1022
1436
|
return !this.getPromptPattern().test(cleanLine);
|
|
@@ -1028,7 +1442,7 @@ export class TmuxWrapper {
|
|
|
1028
1442
|
async isInputClear(lastLine) {
|
|
1029
1443
|
try {
|
|
1030
1444
|
const lineToCheck = lastLine ?? await this.getLastLine();
|
|
1031
|
-
const cleanLine =
|
|
1445
|
+
const cleanLine = stripAnsi(lineToCheck).trimEnd();
|
|
1032
1446
|
const isClear = this.getPromptPattern().test(cleanLine);
|
|
1033
1447
|
if (this.config.debug) {
|
|
1034
1448
|
const truncatedLine = cleanLine.substring(0, Math.min(DEBUG_LOG_TRUNCATE_LENGTH, cleanLine.length));
|
|
@@ -1088,7 +1502,7 @@ export class TmuxWrapper {
|
|
|
1088
1502
|
stableCursorCount = 0;
|
|
1089
1503
|
lastCursorX = cursorX;
|
|
1090
1504
|
}
|
|
1091
|
-
await
|
|
1505
|
+
await sleep(pollIntervalMs);
|
|
1092
1506
|
}
|
|
1093
1507
|
this.logStderr(`waitForClearInput: timed out after ${maxWaitMs}ms`);
|
|
1094
1508
|
return false;
|
|
@@ -1117,7 +1531,7 @@ export class TmuxWrapper {
|
|
|
1117
1531
|
return false;
|
|
1118
1532
|
let stableCount = 0;
|
|
1119
1533
|
while (Date.now() - start < maxWaitMs) {
|
|
1120
|
-
await
|
|
1534
|
+
await sleep(pollIntervalMs);
|
|
1121
1535
|
const sig = await this.capturePaneSignature();
|
|
1122
1536
|
if (!sig)
|
|
1123
1537
|
continue;
|
|
@@ -1143,6 +1557,13 @@ export class TmuxWrapper {
|
|
|
1143
1557
|
return;
|
|
1144
1558
|
this.running = false;
|
|
1145
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
|
+
}
|
|
1146
1567
|
// Reset session state for potential reuse
|
|
1147
1568
|
this.resetSessionState();
|
|
1148
1569
|
// Stop polling
|