agent-relay 1.1.0 → 1.2.3
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/.gitattributes +3 -0
- package/.nvmrc +1 -0
- package/.trajectories/completed/2026-01/traj_1dviorhnkcb5.json +65 -0
- package/.trajectories/completed/2026-01/traj_1dviorhnkcb5.md +37 -0
- package/.trajectories/completed/2026-01/traj_1k5if5snst2e.json +65 -0
- package/.trajectories/completed/2026-01/traj_1k5if5snst2e.md +37 -0
- package/.trajectories/completed/2026-01/traj_1rp3rges5811.json +49 -0
- package/.trajectories/completed/2026-01/traj_1rp3rges5811.md +31 -0
- package/.trajectories/completed/2026-01/traj_22bhyulruouw.json +113 -0
- package/.trajectories/completed/2026-01/traj_22bhyulruouw.md +57 -0
- package/.trajectories/completed/2026-01/traj_2dao7ddgnta0.json +53 -0
- package/.trajectories/completed/2026-01/traj_2dao7ddgnta0.md +32 -0
- package/.trajectories/completed/2026-01/traj_3t0440mjeunc.json +26 -0
- package/.trajectories/completed/2026-01/traj_3t0440mjeunc.md +6 -0
- package/.trajectories/completed/2026-01/traj_45x9494d9xnr.json +47 -0
- package/.trajectories/completed/2026-01/traj_45x9494d9xnr.md +32 -0
- package/.trajectories/completed/2026-01/traj_4aa0bb77s4nh.json +53 -0
- package/.trajectories/completed/2026-01/traj_4aa0bb77s4nh.md +32 -0
- package/.trajectories/completed/2026-01/traj_5lhmzq8rxpqv.json +59 -0
- package/.trajectories/completed/2026-01/traj_5lhmzq8rxpqv.md +33 -0
- package/.trajectories/completed/2026-01/traj_5vr4e9erb1fs.json +53 -0
- package/.trajectories/completed/2026-01/traj_5vr4e9erb1fs.md +32 -0
- package/.trajectories/completed/2026-01/traj_6fgiwdoklvym.json +48 -0
- package/.trajectories/completed/2026-01/traj_6fgiwdoklvym.md +24 -0
- package/.trajectories/completed/2026-01/traj_7ludwvz45veh.json +209 -0
- package/.trajectories/completed/2026-01/traj_7ludwvz45veh.md +97 -0
- package/.trajectories/completed/2026-01/traj_9921cuhel0pj.json +48 -0
- package/.trajectories/completed/2026-01/traj_9921cuhel0pj.md +24 -0
- package/.trajectories/completed/2026-01/traj_ajs7zqfux4wc.json +49 -0
- package/.trajectories/completed/2026-01/traj_ajs7zqfux4wc.md +23 -0
- package/.trajectories/completed/2026-01/traj_cvtqhlwcq9s0.json +53 -0
- package/.trajectories/completed/2026-01/traj_cvtqhlwcq9s0.md +32 -0
- package/.trajectories/completed/2026-01/traj_cxofprm2m2en.json +49 -0
- package/.trajectories/completed/2026-01/traj_cxofprm2m2en.md +31 -0
- package/.trajectories/completed/2026-01/traj_d2hhz3k0vrhn.json +26 -0
- package/.trajectories/completed/2026-01/traj_d2hhz3k0vrhn.md +6 -0
- package/.trajectories/completed/2026-01/traj_dfuvww9pege5.json +59 -0
- package/.trajectories/completed/2026-01/traj_dfuvww9pege5.md +37 -0
- package/.trajectories/completed/2026-01/traj_g0fisy9h51mf.json +77 -0
- package/.trajectories/completed/2026-01/traj_g0fisy9h51mf.md +42 -0
- package/.trajectories/completed/2026-01/traj_gjdre5voouod.json +53 -0
- package/.trajectories/completed/2026-01/traj_gjdre5voouod.md +32 -0
- package/.trajectories/completed/2026-01/traj_gtlyqtta3x8l.json +25 -0
- package/.trajectories/completed/2026-01/traj_gtlyqtta3x8l.md +15 -0
- package/.trajectories/completed/2026-01/traj_h4xijiuip3w4.json +101 -0
- package/.trajectories/completed/2026-01/traj_h4xijiuip3w4.md +44 -0
- package/.trajectories/completed/2026-01/traj_hhxte7w4gjjx.json +22 -0
- package/.trajectories/completed/2026-01/traj_hhxte7w4gjjx.md +5 -0
- package/.trajectories/completed/2026-01/traj_hpungyhoj6v5.json +53 -0
- package/.trajectories/completed/2026-01/traj_hpungyhoj6v5.md +32 -0
- package/.trajectories/completed/2026-01/traj_m2xkjv0w2sq7.json +25 -0
- package/.trajectories/completed/2026-01/traj_m2xkjv0w2sq7.md +15 -0
- package/.trajectories/completed/2026-01/traj_noq5zbvnrdvz.json +53 -0
- package/.trajectories/completed/2026-01/traj_noq5zbvnrdvz.md +32 -0
- package/.trajectories/completed/2026-01/traj_ntbs6ppopf46.json +53 -0
- package/.trajectories/completed/2026-01/traj_ntbs6ppopf46.md +32 -0
- package/.trajectories/completed/2026-01/traj_ozd98si6a7ns.json +48 -0
- package/.trajectories/completed/2026-01/traj_ozd98si6a7ns.md +24 -0
- package/.trajectories/completed/2026-01/traj_prdza7a5cxp5.json +53 -0
- package/.trajectories/completed/2026-01/traj_prdza7a5cxp5.md +32 -0
- package/.trajectories/completed/2026-01/traj_qb3twvvywfwi.json +77 -0
- package/.trajectories/completed/2026-01/traj_qb3twvvywfwi.md +42 -0
- package/.trajectories/completed/2026-01/traj_qft54mi7nfor.json +53 -0
- package/.trajectories/completed/2026-01/traj_qft54mi7nfor.md +32 -0
- package/.trajectories/completed/2026-01/traj_qx9uhf8whhxo.json +83 -0
- package/.trajectories/completed/2026-01/traj_qx9uhf8whhxo.md +47 -0
- package/.trajectories/completed/2026-01/traj_rd9toccj18a0.json +59 -0
- package/.trajectories/completed/2026-01/traj_rd9toccj18a0.md +37 -0
- package/.trajectories/completed/2026-01/traj_rt4fiw3ecp50.json +48 -0
- package/.trajectories/completed/2026-01/traj_rt4fiw3ecp50.md +16 -0
- package/.trajectories/completed/2026-01/traj_st8j35b0hrlc.json +59 -0
- package/.trajectories/completed/2026-01/traj_st8j35b0hrlc.md +37 -0
- package/.trajectories/completed/2026-01/traj_t1yy8m7hbuxp.json +53 -0
- package/.trajectories/completed/2026-01/traj_t1yy8m7hbuxp.md +32 -0
- package/.trajectories/completed/2026-01/traj_tmux_orchestrator_analysis.json +84 -0
- package/.trajectories/completed/2026-01/traj_tmux_orchestrator_analysis.md +109 -0
- package/.trajectories/completed/2026-01/traj_u9n9eqasw16k.json +53 -0
- package/.trajectories/completed/2026-01/traj_u9n9eqasw16k.md +32 -0
- package/.trajectories/completed/2026-01/traj_v87hypnongqx.json +71 -0
- package/.trajectories/completed/2026-01/traj_v87hypnongqx.md +42 -0
- package/.trajectories/completed/2026-01/traj_wkp2fgzdyinb.json +53 -0
- package/.trajectories/completed/2026-01/traj_wkp2fgzdyinb.md +32 -0
- package/.trajectories/completed/2026-01/traj_x14t8w8rn7xg.json +20 -0
- package/.trajectories/completed/2026-01/traj_x14t8w8rn7xg.md +6 -0
- package/.trajectories/completed/2026-01/traj_xnwbznkvv8ua.json +175 -0
- package/.trajectories/completed/2026-01/traj_xnwbznkvv8ua.md +82 -0
- package/.trajectories/completed/2026-01/traj_ysjc8zaeqtd3.json +47 -0
- package/.trajectories/completed/2026-01/traj_ysjc8zaeqtd3.md +32 -0
- package/.trajectories/completed/2026-01/traj_yvdadtvdgnz3.json +59 -0
- package/.trajectories/completed/2026-01/traj_yvdadtvdgnz3.md +37 -0
- package/.trajectories/completed/2026-01/traj_z0vcw1wrzide.json +53 -0
- package/.trajectories/completed/2026-01/traj_z0vcw1wrzide.md +32 -0
- package/.trajectories/index.json +314 -0
- package/ARCHITECTURE.md +1245 -0
- package/README.md +1 -1
- package/TESTING.md +278 -0
- package/deploy/init-db.sql +5 -0
- package/deploy/scripts/setup-fly-workspaces.sh +69 -0
- package/deploy/scripts/setup-railway.sh +75 -0
- package/deploy/workspace/entrypoint-browser.sh +118 -0
- package/deploy/workspace/entrypoint.sh +348 -0
- package/deploy/workspace/git-credential-relay +111 -0
- package/dist/bridge/spawner.d.ts +53 -0
- package/dist/bridge/spawner.js +203 -19
- package/dist/bridge/types.d.ts +12 -0
- package/dist/cli/index.js +618 -5
- package/dist/cloud/api/auth.d.ts +3 -2
- package/dist/cloud/api/auth.js +10 -98
- package/dist/cloud/api/billing.js +30 -9
- package/dist/cloud/api/cli-pty-runner.d.ts +54 -0
- package/dist/cloud/api/cli-pty-runner.js +119 -0
- package/dist/cloud/api/codex-auth-helper.d.ts +15 -0
- package/dist/cloud/api/codex-auth-helper.js +100 -0
- package/dist/cloud/api/generic-webhooks.d.ts +8 -0
- package/dist/cloud/api/generic-webhooks.js +129 -0
- package/dist/cloud/api/git.d.ts +8 -0
- package/dist/cloud/api/git.js +152 -0
- package/dist/cloud/api/github-app.d.ts +11 -0
- package/dist/cloud/api/github-app.js +189 -0
- package/dist/cloud/api/middleware/planLimits.d.ts +7 -0
- package/dist/cloud/api/middleware/planLimits.js +39 -1
- package/dist/cloud/api/monitoring.d.ts +11 -0
- package/dist/cloud/api/monitoring.js +578 -0
- package/dist/cloud/api/nango-auth.d.ts +9 -0
- package/dist/cloud/api/nango-auth.js +377 -0
- package/dist/cloud/api/onboarding.d.ts +8 -1
- package/dist/cloud/api/onboarding.js +313 -119
- package/dist/cloud/api/policy.d.ts +8 -0
- package/dist/cloud/api/policy.js +229 -0
- package/dist/cloud/api/providers.js +114 -42
- package/dist/cloud/api/repos.d.ts +1 -0
- package/dist/cloud/api/repos.js +186 -0
- package/dist/cloud/api/test-helpers.d.ts +10 -0
- package/dist/cloud/api/test-helpers.js +575 -0
- package/dist/cloud/api/webhooks.d.ts +8 -0
- package/dist/cloud/api/webhooks.js +645 -0
- package/dist/cloud/api/workspaces.js +320 -12
- package/dist/cloud/billing/plans.js +32 -19
- package/dist/cloud/billing/types.d.ts +9 -3
- package/dist/cloud/config.d.ts +9 -2
- package/dist/cloud/config.js +13 -4
- package/dist/cloud/db/drizzle.d.ts +84 -1
- package/dist/cloud/db/drizzle.js +470 -0
- package/dist/cloud/db/index.d.ts +9 -4
- package/dist/cloud/db/index.js +11 -3
- package/dist/cloud/db/schema.d.ts +3283 -556
- package/dist/cloud/db/schema.js +314 -1
- package/dist/cloud/index.d.ts +1 -0
- package/dist/cloud/index.js +2 -0
- package/dist/cloud/provisioner/index.d.ts +56 -0
- package/dist/cloud/provisioner/index.js +676 -34
- package/dist/cloud/server.d.ts +1 -0
- package/dist/cloud/server.js +362 -13
- package/dist/cloud/services/auto-scaler.d.ts +152 -0
- package/dist/cloud/services/auto-scaler.js +439 -0
- package/dist/cloud/services/capacity-manager.d.ts +148 -0
- package/dist/cloud/services/capacity-manager.js +449 -0
- package/dist/cloud/services/ci-agent-spawner.d.ts +49 -0
- package/dist/cloud/services/ci-agent-spawner.js +373 -0
- package/dist/cloud/services/index.d.ts +12 -0
- package/dist/cloud/services/index.js +15 -0
- package/dist/cloud/services/mention-handler.d.ts +65 -0
- package/dist/cloud/services/mention-handler.js +405 -0
- package/dist/cloud/services/nango.d.ts +186 -0
- package/dist/cloud/services/nango.js +344 -0
- package/dist/cloud/services/persistence.d.ts +131 -0
- package/dist/cloud/services/persistence.js +200 -0
- package/dist/cloud/services/planLimits.d.ts +37 -0
- package/dist/cloud/services/planLimits.js +86 -5
- package/dist/cloud/services/scaling-orchestrator.d.ts +159 -0
- package/dist/cloud/services/scaling-orchestrator.js +502 -0
- package/dist/cloud/services/scaling-policy.d.ts +121 -0
- package/dist/cloud/services/scaling-policy.js +415 -0
- package/dist/cloud/vault/index.js +1 -1
- package/dist/cloud/webhooks/index.d.ts +24 -0
- package/dist/cloud/webhooks/index.js +29 -0
- package/dist/cloud/webhooks/parsers/github.d.ts +8 -0
- package/dist/cloud/webhooks/parsers/github.js +234 -0
- package/dist/cloud/webhooks/parsers/index.d.ts +23 -0
- package/dist/cloud/webhooks/parsers/index.js +30 -0
- package/dist/cloud/webhooks/parsers/linear.d.ts +9 -0
- package/dist/cloud/webhooks/parsers/linear.js +258 -0
- package/dist/cloud/webhooks/parsers/slack.d.ts +9 -0
- package/dist/cloud/webhooks/parsers/slack.js +214 -0
- package/dist/cloud/webhooks/responders/github.d.ts +8 -0
- package/dist/cloud/webhooks/responders/github.js +73 -0
- package/dist/cloud/webhooks/responders/index.d.ts +23 -0
- package/dist/cloud/webhooks/responders/index.js +30 -0
- package/dist/cloud/webhooks/responders/linear.d.ts +9 -0
- package/dist/cloud/webhooks/responders/linear.js +149 -0
- package/dist/cloud/webhooks/responders/slack.d.ts +20 -0
- package/dist/cloud/webhooks/responders/slack.js +178 -0
- package/dist/cloud/webhooks/router.d.ts +25 -0
- package/dist/cloud/webhooks/router.js +504 -0
- package/dist/cloud/webhooks/rules-engine.d.ts +24 -0
- package/dist/cloud/webhooks/rules-engine.js +287 -0
- package/dist/cloud/webhooks/types.d.ts +186 -0
- package/dist/cloud/webhooks/types.js +8 -0
- package/dist/continuity/formatter.d.ts +51 -0
- package/dist/continuity/formatter.js +313 -0
- package/dist/continuity/handoff-store.d.ts +67 -0
- package/dist/continuity/handoff-store.js +472 -0
- package/dist/continuity/index.d.ts +45 -0
- package/dist/continuity/index.js +48 -0
- package/dist/continuity/ledger-store.d.ts +110 -0
- package/dist/continuity/ledger-store.js +500 -0
- package/dist/continuity/manager.d.ts +178 -0
- package/dist/continuity/manager.js +562 -0
- package/dist/continuity/parser.d.ts +76 -0
- package/dist/continuity/parser.js +579 -0
- package/dist/continuity/types.d.ts +180 -0
- package/dist/continuity/types.js +9 -0
- package/dist/daemon/agent-manager.d.ts +27 -0
- package/dist/daemon/agent-manager.js +107 -6
- package/dist/daemon/agent-registry.d.ts +32 -0
- package/dist/daemon/agent-registry.js +42 -2
- package/dist/daemon/api.d.ts +12 -0
- package/dist/daemon/api.js +131 -2
- package/dist/daemon/cli-auth.d.ts +67 -0
- package/dist/daemon/cli-auth.js +537 -0
- package/dist/daemon/cloud-sync.js +9 -7
- package/dist/daemon/orchestrator.js +30 -0
- package/dist/daemon/router.d.ts +5 -0
- package/dist/daemon/router.js +78 -26
- package/dist/daemon/server.d.ts +5 -0
- package/dist/daemon/server.js +9 -1
- package/dist/daemon/services/browser-testing.d.ts +88 -0
- package/dist/daemon/services/browser-testing.js +244 -0
- package/dist/daemon/services/container-spawner.d.ts +135 -0
- package/dist/daemon/services/container-spawner.js +313 -0
- package/dist/daemon/types.d.ts +5 -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/699-3b1cd6618a45d259.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/724-2dae7627550ab88f.js +9 -0
- package/dist/dashboard/out/_next/static/chunks/766-1f2dd8cb7f766b0b.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/app/onboarding/page-3fdfa60e53f2810d.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/app/page-e6381e5a6e1fbcfd.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/connect-repos/page-3538dfe0ffe984b8.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/history/{page-b6edd4dde8d08194.js → page-abb9ab2d329f56e9.js} +1 -1
- package/dist/dashboard/out/_next/static/chunks/app/layout-c0d118c0f92d969c.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/login/page-c22d080201cbd9fb.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/metrics/page-67a3e98d9a43a6ed.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/page-77e9c65420a06cfb.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/pricing/page-b08ed1c34d14434a.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/providers/page-e88bc117ef7671c3.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/signup/page-68d34f50baa8ab6b.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/e868780c-48e5f147c90a3a41.js +18 -0
- package/dist/dashboard/out/_next/static/chunks/{main-app-5d692157a8eb1fd9.js → main-app-6e8e8d3ef4e0192a.js} +1 -1
- package/dist/dashboard/out/_next/static/chunks/{main-c2f423b9c9f4591b.js → main-ed4e1fb6f29c34cf.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/7c3ae9e8617d42a5.css +1 -0
- package/dist/dashboard/out/app/onboarding.html +1 -0
- package/dist/dashboard/out/app/onboarding.txt +7 -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 +3 -3
- 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.js +1308 -8
- package/dist/hooks/emitter.d.ts +40 -0
- package/dist/hooks/emitter.js +63 -0
- package/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/index.js +3 -0
- package/dist/hooks/registry.d.ts +173 -0
- package/dist/hooks/registry.js +476 -0
- package/dist/hooks/trajectory-hooks.d.ts +52 -0
- package/dist/hooks/trajectory-hooks.js +183 -0
- package/dist/hooks/types.d.ts +141 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/memory/adapters/index.d.ts +8 -0
- package/dist/memory/adapters/index.js +8 -0
- package/dist/memory/adapters/inmemory.d.ts +59 -0
- package/dist/memory/adapters/inmemory.js +195 -0
- package/dist/memory/adapters/supermemory.d.ts +71 -0
- package/dist/memory/adapters/supermemory.js +338 -0
- package/dist/memory/factory.d.ts +48 -0
- package/dist/memory/factory.js +143 -0
- package/dist/memory/index.d.ts +32 -0
- package/dist/memory/index.js +32 -0
- package/dist/memory/memory-hooks.d.ts +60 -0
- package/dist/memory/memory-hooks.js +313 -0
- package/dist/memory/service.d.ts +49 -0
- package/dist/memory/service.js +146 -0
- package/dist/memory/types.d.ts +195 -0
- package/dist/memory/types.js +8 -0
- package/dist/policy/agent-policy.d.ts +225 -0
- package/dist/policy/agent-policy.js +665 -0
- package/dist/policy/cloud-policy-fetcher.d.ts +12 -0
- package/dist/policy/cloud-policy-fetcher.js +64 -0
- package/dist/resiliency/crash-insights.d.ts +156 -0
- package/dist/resiliency/crash-insights.js +492 -0
- package/dist/resiliency/gossip-health.d.ts +137 -0
- package/dist/resiliency/gossip-health.js +241 -0
- package/dist/resiliency/index.d.ts +5 -0
- package/dist/resiliency/index.js +5 -0
- package/dist/resiliency/leader-watchdog.d.ts +109 -0
- package/dist/resiliency/leader-watchdog.js +189 -0
- package/dist/resiliency/memory-monitor.d.ts +172 -0
- package/dist/resiliency/memory-monitor.js +593 -0
- package/dist/resiliency/stateless-lead.d.ts +149 -0
- package/dist/resiliency/stateless-lead.js +308 -0
- package/dist/resiliency/supervisor.d.ts +38 -0
- package/dist/resiliency/supervisor.js +122 -0
- package/dist/shared/cli-auth-config.d.ts +91 -0
- package/dist/shared/cli-auth-config.js +264 -0
- package/dist/storage/adapter.d.ts +1 -1
- package/dist/trajectory/config.d.ts +84 -0
- package/dist/trajectory/config.js +163 -0
- package/dist/trajectory/index.d.ts +8 -0
- package/dist/trajectory/index.js +8 -0
- package/dist/trajectory/integration.d.ts +292 -0
- package/dist/trajectory/integration.js +834 -0
- package/dist/utils/logger.js +1 -1
- package/dist/utils/project-namespace.d.ts +24 -0
- package/dist/utils/project-namespace.js +84 -0
- package/dist/wrapper/parser.d.ts +10 -0
- package/dist/wrapper/parser.js +100 -33
- package/dist/wrapper/pty-wrapper.d.ts +197 -16
- package/dist/wrapper/pty-wrapper.js +943 -106
- package/dist/wrapper/shared.d.ts +165 -0
- package/dist/wrapper/shared.js +270 -0
- package/dist/wrapper/tmux-wrapper.d.ts +73 -11
- package/dist/wrapper/tmux-wrapper.js +541 -120
- package/package.json +16 -16
- package/scripts/postinstall.js +60 -0
- package/test-push.txt +1 -0
- package/bin/tmux +0 -0
- package/dist/bridge/config.d.ts.map +0 -1
- package/dist/bridge/config.js.map +0 -1
- package/dist/bridge/index.d.ts.map +0 -1
- package/dist/bridge/index.js.map +0 -1
- package/dist/bridge/multi-project-client.d.ts.map +0 -1
- package/dist/bridge/multi-project-client.js.map +0 -1
- package/dist/bridge/shadow-cli.d.ts.map +0 -1
- package/dist/bridge/shadow-cli.js.map +0 -1
- package/dist/bridge/shadow-config.d.ts.map +0 -1
- package/dist/bridge/shadow-config.js.map +0 -1
- package/dist/bridge/spawner.d.ts.map +0 -1
- package/dist/bridge/spawner.js.map +0 -1
- package/dist/bridge/teams-config.d.ts.map +0 -1
- package/dist/bridge/teams-config.js.map +0 -1
- package/dist/bridge/types.d.ts.map +0 -1
- package/dist/bridge/types.js.map +0 -1
- package/dist/bridge/utils.d.ts.map +0 -1
- package/dist/bridge/utils.js.map +0 -1
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js.map +0 -1
- package/dist/cloud/api/auth.d.ts.map +0 -1
- package/dist/cloud/api/auth.js.map +0 -1
- package/dist/cloud/api/billing.d.ts.map +0 -1
- package/dist/cloud/api/billing.js.map +0 -1
- package/dist/cloud/api/coordinators.d.ts.map +0 -1
- package/dist/cloud/api/coordinators.js.map +0 -1
- package/dist/cloud/api/daemons.d.ts.map +0 -1
- package/dist/cloud/api/daemons.js.map +0 -1
- package/dist/cloud/api/middleware/planLimits.d.ts.map +0 -1
- package/dist/cloud/api/middleware/planLimits.js.map +0 -1
- package/dist/cloud/api/onboarding.d.ts.map +0 -1
- package/dist/cloud/api/onboarding.js.map +0 -1
- package/dist/cloud/api/providers.d.ts.map +0 -1
- package/dist/cloud/api/providers.js.map +0 -1
- package/dist/cloud/api/repos.d.ts.map +0 -1
- package/dist/cloud/api/repos.js.map +0 -1
- package/dist/cloud/api/teams.d.ts.map +0 -1
- package/dist/cloud/api/teams.js.map +0 -1
- package/dist/cloud/api/usage.d.ts.map +0 -1
- package/dist/cloud/api/usage.js.map +0 -1
- package/dist/cloud/api/workspaces.d.ts.map +0 -1
- package/dist/cloud/api/workspaces.js.map +0 -1
- package/dist/cloud/billing/index.d.ts.map +0 -1
- package/dist/cloud/billing/index.js.map +0 -1
- package/dist/cloud/billing/plans.d.ts.map +0 -1
- package/dist/cloud/billing/plans.js.map +0 -1
- package/dist/cloud/billing/service.d.ts.map +0 -1
- package/dist/cloud/billing/service.js.map +0 -1
- package/dist/cloud/billing/types.d.ts.map +0 -1
- package/dist/cloud/billing/types.js.map +0 -1
- package/dist/cloud/config.d.ts.map +0 -1
- package/dist/cloud/config.js.map +0 -1
- package/dist/cloud/db/drizzle.d.ts.map +0 -1
- package/dist/cloud/db/drizzle.js.map +0 -1
- package/dist/cloud/db/index.d.ts.map +0 -1
- package/dist/cloud/db/index.js.map +0 -1
- package/dist/cloud/db/schema.d.ts.map +0 -1
- package/dist/cloud/db/schema.js.map +0 -1
- package/dist/cloud/index.d.ts.map +0 -1
- package/dist/cloud/index.js.map +0 -1
- package/dist/cloud/provisioner/index.d.ts.map +0 -1
- package/dist/cloud/provisioner/index.js.map +0 -1
- package/dist/cloud/server.d.ts.map +0 -1
- package/dist/cloud/server.js.map +0 -1
- package/dist/cloud/services/coordinator.d.ts.map +0 -1
- package/dist/cloud/services/coordinator.js.map +0 -1
- package/dist/cloud/services/planLimits.d.ts.map +0 -1
- package/dist/cloud/services/planLimits.js.map +0 -1
- package/dist/cloud/vault/index.d.ts.map +0 -1
- package/dist/cloud/vault/index.js.map +0 -1
- package/dist/daemon/agent-manager.d.ts.map +0 -1
- package/dist/daemon/agent-manager.js.map +0 -1
- package/dist/daemon/agent-registry.d.ts.map +0 -1
- package/dist/daemon/agent-registry.js.map +0 -1
- package/dist/daemon/api.d.ts.map +0 -1
- package/dist/daemon/api.js.map +0 -1
- package/dist/daemon/auth.d.ts.map +0 -1
- package/dist/daemon/auth.js.map +0 -1
- package/dist/daemon/cloud-sync.d.ts.map +0 -1
- package/dist/daemon/cloud-sync.js.map +0 -1
- package/dist/daemon/connection.d.ts.map +0 -1
- package/dist/daemon/connection.js.map +0 -1
- package/dist/daemon/index.d.ts.map +0 -1
- package/dist/daemon/index.js.map +0 -1
- package/dist/daemon/orchestrator.d.ts.map +0 -1
- package/dist/daemon/orchestrator.js.map +0 -1
- package/dist/daemon/registry.d.ts.map +0 -1
- package/dist/daemon/registry.js.map +0 -1
- package/dist/daemon/router.d.ts.map +0 -1
- package/dist/daemon/router.js.map +0 -1
- package/dist/daemon/server.d.ts.map +0 -1
- package/dist/daemon/server.js.map +0 -1
- package/dist/daemon/types.d.ts.map +0 -1
- package/dist/daemon/types.js.map +0 -1
- package/dist/daemon/workspace-manager.d.ts.map +0 -1
- package/dist/daemon/workspace-manager.js.map +0 -1
- 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/layout-2433bb48965f4333.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/app/pricing/page-d80e03a5297f95b6.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-server/metrics.d.ts.map +0 -1
- package/dist/dashboard-server/metrics.js.map +0 -1
- package/dist/dashboard-server/needs-attention.d.ts.map +0 -1
- package/dist/dashboard-server/needs-attention.js.map +0 -1
- package/dist/dashboard-server/server.d.ts.map +0 -1
- package/dist/dashboard-server/server.js.map +0 -1
- package/dist/dashboard-server/start.d.ts.map +0 -1
- package/dist/dashboard-server/start.js.map +0 -1
- package/dist/hooks/inbox-check/hook.d.ts.map +0 -1
- package/dist/hooks/inbox-check/hook.js.map +0 -1
- package/dist/hooks/inbox-check/index.d.ts.map +0 -1
- package/dist/hooks/inbox-check/index.js.map +0 -1
- package/dist/hooks/inbox-check/types.d.ts.map +0 -1
- package/dist/hooks/inbox-check/types.js.map +0 -1
- package/dist/hooks/inbox-check/utils.d.ts.map +0 -1
- package/dist/hooks/inbox-check/utils.js.map +0 -1
- package/dist/hooks/index.d.ts.map +0 -1
- package/dist/hooks/index.js.map +0 -1
- package/dist/hooks/types.d.ts.map +0 -1
- package/dist/hooks/types.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/protocol/framing.d.ts.map +0 -1
- package/dist/protocol/framing.js.map +0 -1
- package/dist/protocol/index.d.ts.map +0 -1
- package/dist/protocol/index.js.map +0 -1
- package/dist/protocol/types.d.ts.map +0 -1
- package/dist/protocol/types.js.map +0 -1
- package/dist/resiliency/context-persistence.d.ts.map +0 -1
- package/dist/resiliency/context-persistence.js.map +0 -1
- package/dist/resiliency/health-monitor.d.ts.map +0 -1
- package/dist/resiliency/health-monitor.js.map +0 -1
- package/dist/resiliency/index.d.ts.map +0 -1
- package/dist/resiliency/index.js.map +0 -1
- package/dist/resiliency/logger.d.ts.map +0 -1
- package/dist/resiliency/logger.js.map +0 -1
- package/dist/resiliency/metrics.d.ts.map +0 -1
- package/dist/resiliency/metrics.js.map +0 -1
- package/dist/resiliency/provider-context.d.ts.map +0 -1
- package/dist/resiliency/provider-context.js.map +0 -1
- package/dist/resiliency/supervisor.d.ts.map +0 -1
- package/dist/resiliency/supervisor.js.map +0 -1
- package/dist/state/agent-state.d.ts.map +0 -1
- package/dist/state/agent-state.js.map +0 -1
- package/dist/storage/adapter.d.ts.map +0 -1
- package/dist/storage/adapter.js.map +0 -1
- package/dist/storage/sqlite-adapter.d.ts.map +0 -1
- package/dist/storage/sqlite-adapter.js.map +0 -1
- package/dist/utils/agent-config.d.ts.map +0 -1
- package/dist/utils/agent-config.js.map +0 -1
- package/dist/utils/command-resolver.d.ts.map +0 -1
- package/dist/utils/command-resolver.js.map +0 -1
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/logger.js.map +0 -1
- package/dist/utils/name-generator.d.ts.map +0 -1
- package/dist/utils/name-generator.js.map +0 -1
- package/dist/utils/project-namespace.d.ts.map +0 -1
- package/dist/utils/project-namespace.js.map +0 -1
- package/dist/utils/tmux-resolver.d.ts.map +0 -1
- package/dist/utils/tmux-resolver.js.map +0 -1
- package/dist/utils/update-checker.d.ts.map +0 -1
- package/dist/utils/update-checker.js.map +0 -1
- package/dist/wrapper/client.d.ts.map +0 -1
- package/dist/wrapper/client.js.map +0 -1
- package/dist/wrapper/inbox.d.ts.map +0 -1
- package/dist/wrapper/inbox.js.map +0 -1
- package/dist/wrapper/index.d.ts.map +0 -1
- package/dist/wrapper/index.js.map +0 -1
- package/dist/wrapper/parser.d.ts.map +0 -1
- package/dist/wrapper/parser.js.map +0 -1
- package/dist/wrapper/pty-wrapper.d.ts.map +0 -1
- package/dist/wrapper/pty-wrapper.js.map +0 -1
- package/dist/wrapper/tmux-wrapper.d.ts.map +0 -1
- package/dist/wrapper/tmux-wrapper.js.map +0 -1
- package/docs/AGENTS.md +0 -513
- package/docs/ARCHITECTURE_DECISIONS.md +0 -175
- package/docs/CHANGELOG.md +0 -11
- package/docs/CLI-SIMPLIFICATION-COMPLETE.md +0 -48
- package/docs/CLOUD-ARCHITECTURE.md +0 -652
- package/docs/CLOUD-ONBOARDING-DESIGN.md +0 -1983
- package/docs/COMPETITIVE_ANALYSIS.md +0 -897
- package/docs/CONTRIBUTING.md +0 -151
- package/docs/DESIGN_BRIDGE_STAFFING.md +0 -878
- package/docs/DESIGN_V2.md +0 -1079
- package/docs/INTEGRATION-GUIDE.md +0 -926
- package/docs/MONETIZATION.md +0 -1679
- package/docs/PROPOSAL-trajectories.md +0 -1582
- package/docs/PROTOCOL.md +0 -325
- package/docs/SCALING_ANALYSIS.md +0 -280
- package/docs/TESTING_PRESENCE_FEATURES.md +0 -327
- package/docs/TMUX_IMPLEMENTATION_NOTES.md +0 -364
- package/docs/TMUX_IMPROVEMENTS.md +0 -968
- package/docs/agent-relay-snippet.md +0 -168
- package/docs/competitive-analysis-mcp-agent-mail.md +0 -389
- package/docs/dashboard-v2-plan.md +0 -179
- package/docs/guides/CLOUD.md +0 -236
- package/docs/guides/LOCAL.md +0 -535
- package/docs/guides/SELF-HOSTED.md +0 -494
- package/docs/proposals/shadow-as-subagent.md +0 -765
- package/docs/proposals/slack-bot-integration.md +0 -1457
- package/docs/removable-code-analysis.md +0 -24
- package/scripts/dev/PUBLIC_RELEASE_PLAN.md +0 -88
- package/scripts/dev/dev-team-setup.sh +0 -431
- package/scripts/e2e-test.sh +0 -119
- package/scripts/games/game-protocol.md +0 -79
- package/scripts/games/hearts-setup.sh +0 -264
- package/scripts/tictactoe-setup.sh +0 -181
- /package/dist/dashboard/out/_next/static/chunks/{117-b2cd8d6485aacf2b.js → 117-f7b8ab0809342e77.js} +0 -0
- /package/dist/dashboard/out/_next/static/chunks/{648-8f3f26864ce515e5.js → 648-5cc6e1921389a58a.js} +0 -0
- /package/dist/dashboard/out/_next/static/chunks/app/_not-found/{page-0b990dbb71d72a98.js → page-53b8a69f76db17d0.js} +0 -0
- /package/dist/dashboard/out/_next/static/chunks/{fd9d1056-bf46c09eb57e019c.js → fd9d1056-609918ca7b6280bb.js} +0 -0
- /package/dist/dashboard/out/_next/static/{6HHWb2ZmnJ4OSm0zUP7h4 → wPgKJtcOmTFLpUncDg16A}/_buildManifest.js +0 -0
- /package/dist/dashboard/out/_next/static/{6HHWb2ZmnJ4OSm0zUP7h4 → wPgKJtcOmTFLpUncDg16A}/_ssgManifest.js +0 -0
|
@@ -1,968 +0,0 @@
|
|
|
1
|
-
# tmux Implementation: Technical Comparison & Analysis
|
|
2
|
-
|
|
3
|
-
## Head-to-Head: Ours vs Alternative Approach
|
|
4
|
-
|
|
5
|
-
### Architecture Overview
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
┌─────────────────────────────────────────────────────────────────────┐
|
|
9
|
-
│ OUR APPROACH (agent-relay) │
|
|
10
|
-
├─────────────────────────────────────────────────────────────────────┤
|
|
11
|
-
│ │
|
|
12
|
-
│ User Terminal │
|
|
13
|
-
│ │ │
|
|
14
|
-
│ ▼ │
|
|
15
|
-
│ ┌─────────────────────────────────────────┐ │
|
|
16
|
-
│ │ spawn('tmux', ['attach-session', '-t'])│ ◄── stdio: 'inherit' │
|
|
17
|
-
│ │ User sees REAL tmux session │ │
|
|
18
|
-
│ └─────────────────────────────────────────┘ │
|
|
19
|
-
│ │
|
|
20
|
-
│ Background Process (same Node.js process) │
|
|
21
|
-
│ │ │
|
|
22
|
-
│ ├─► setInterval(200ms) │
|
|
23
|
-
│ │ └─► execAsync('tmux capture-pane -p -J -S -') │
|
|
24
|
-
│ │ └─► parser.parse(output) │
|
|
25
|
-
│ │ └─► detect ->relay: patterns │
|
|
26
|
-
│ │ └─► send to daemon │
|
|
27
|
-
│ │ │
|
|
28
|
-
│ └─► onMessage from daemon │
|
|
29
|
-
│ └─► wait for idle (1.5s no output) │
|
|
30
|
-
│ └─► execAsync('tmux send-keys -l "msg"') │
|
|
31
|
-
│ │
|
|
32
|
-
│ Dependencies: NONE (just tmux + node child_process) │
|
|
33
|
-
│ │
|
|
34
|
-
└─────────────────────────────────────────────────────────────────────┘
|
|
35
|
-
|
|
36
|
-
┌─────────────────────────────────────────────────────────────────────┐
|
|
37
|
-
│ ALTERNATIVE APPROACH (streaming) │
|
|
38
|
-
├─────────────────────────────────────────────────────────────────────┤
|
|
39
|
-
│ │
|
|
40
|
-
│ Browser (xterm.js) │
|
|
41
|
-
│ │ │
|
|
42
|
-
│ │ WebSocket │
|
|
43
|
-
│ ▼ │
|
|
44
|
-
│ ┌─────────────────────────────────────────┐ │
|
|
45
|
-
│ │ WebSocket Server (server.mjs) │ │
|
|
46
|
-
│ │ │ │ │
|
|
47
|
-
│ │ ▼ │ │
|
|
48
|
-
│ │ node-pty.spawn('tmux', ['attach']) │ ◄── PTY pseudo-tty │
|
|
49
|
-
│ │ │ │ │
|
|
50
|
-
│ │ ├─► pty.onData(data) │ │
|
|
51
|
-
│ │ │ └─► ws.send(data) │ Real-time to browser │
|
|
52
|
-
│ │ │ │ │
|
|
53
|
-
│ │ └─► ws.onMessage(input) │ │
|
|
54
|
-
│ │ └─► pty.write(input) │ Real-time from user │
|
|
55
|
-
│ └─────────────────────────────────────────┘ │
|
|
56
|
-
│ │
|
|
57
|
-
│ Messages: Separate system (filesystem + HTTP API) │
|
|
58
|
-
│ └─► ~/.relay/messages/inbox/{agent}/ │
|
|
59
|
-
│ └─► Agent polls for new files or gets tmux notification │
|
|
60
|
-
│ │
|
|
61
|
-
│ Dependencies: node-pty (native), ws, xterm.js │
|
|
62
|
-
│ │
|
|
63
|
-
└─────────────────────────────────────────────────────────────────────┘
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
---
|
|
67
|
-
|
|
68
|
-
## Technical Deep Dive
|
|
69
|
-
|
|
70
|
-
### 1. Terminal I/O Method
|
|
71
|
-
|
|
72
|
-
#### Ours: Polling with `capture-pane`
|
|
73
|
-
|
|
74
|
-
```typescript
|
|
75
|
-
// Every 200ms, capture terminal buffer
|
|
76
|
-
private async pollForRelayCommands(): Promise<void> {
|
|
77
|
-
const { stdout } = await execAsync(
|
|
78
|
-
`tmux capture-pane -t ${this.sessionName} -p -J -S - 2>/dev/null`
|
|
79
|
-
);
|
|
80
|
-
// -p = print to stdout (not to buffer)
|
|
81
|
-
// -J = join wrapped lines
|
|
82
|
-
// -S - = start from beginning of scrollback
|
|
83
|
-
|
|
84
|
-
const cleanContent = this.stripAnsi(stdout);
|
|
85
|
-
const { commands } = this.parser.parse(cleanContent);
|
|
86
|
-
// ...
|
|
87
|
-
}
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
#### Alternative: Streaming with node-pty
|
|
91
|
-
|
|
92
|
-
```javascript
|
|
93
|
-
// Real-time event-driven
|
|
94
|
-
const pty = spawn('tmux', ['attach-session', '-t', session]);
|
|
95
|
-
|
|
96
|
-
pty.onData((data) => {
|
|
97
|
-
// Fires immediately on ANY output
|
|
98
|
-
ws.send(data); // Forward to browser
|
|
99
|
-
filterAndLog(data);
|
|
100
|
-
});
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
#### Comparison
|
|
104
|
-
|
|
105
|
-
| Aspect | Polling (Ours) | Streaming (Theirs) |
|
|
106
|
-
|--------|----------------|-------------------|
|
|
107
|
-
| **Latency** | 0-200ms (poll interval) | <10ms (event-driven) |
|
|
108
|
-
| **CPU idle** | Constant ~1-2% (polling) | Near 0% (event-driven) |
|
|
109
|
-
| **CPU active** | Same | Same |
|
|
110
|
-
| **Missed output** | Possible if buffer wraps | Never (stream-based) |
|
|
111
|
-
| **Complexity** | ~20 lines | ~50 lines + native dep |
|
|
112
|
-
| **Build issues** | None | node-pty compilation |
|
|
113
|
-
|
|
114
|
-
**Robustness verdict:** Streaming is technically more robust (no missed output), but polling is simpler and "good enough" for text-based agents that don't produce massive output bursts.
|
|
115
|
-
|
|
116
|
-
---
|
|
117
|
-
|
|
118
|
-
### 2. User Terminal Experience
|
|
119
|
-
|
|
120
|
-
#### Ours: Native tmux attach
|
|
121
|
-
|
|
122
|
-
```typescript
|
|
123
|
-
// User's terminal is directly attached to tmux
|
|
124
|
-
this.attachProcess = spawn('tmux', ['attach-session', '-t', this.sessionName], {
|
|
125
|
-
stdio: 'inherit', // <-- KEY: User's stdin/stdout/stderr ARE the tmux session
|
|
126
|
-
});
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
**What this means:**
|
|
130
|
-
- User's terminal emulator renders tmux directly
|
|
131
|
-
- All keybindings work natively (Ctrl+B, mouse, etc.)
|
|
132
|
-
- Scrollback, copy/paste work as expected
|
|
133
|
-
- No intermediate rendering layer
|
|
134
|
-
|
|
135
|
-
#### Alternative: Browser-based xterm.js
|
|
136
|
-
|
|
137
|
-
```javascript
|
|
138
|
-
// Terminal rendered in browser via WebGL
|
|
139
|
-
const term = new Terminal({
|
|
140
|
-
rendererType: 'webgl',
|
|
141
|
-
convertEol: false, // PTY handles line endings
|
|
142
|
-
});
|
|
143
|
-
term.loadAddon(new FitAddon());
|
|
144
|
-
term.loadAddon(new WebglAddon());
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
**What this means:**
|
|
148
|
-
- Terminal is *emulated* in browser
|
|
149
|
-
- Some keybindings may differ
|
|
150
|
-
- Scrollback limited by xterm.js buffer
|
|
151
|
-
- Copy/paste goes through browser
|
|
152
|
-
|
|
153
|
-
#### Comparison
|
|
154
|
-
|
|
155
|
-
| Aspect | Native tmux (Ours) | xterm.js (Theirs) |
|
|
156
|
-
|--------|-------------------|-------------------|
|
|
157
|
-
| **Keybindings** | 100% native | ~95% (some edge cases) |
|
|
158
|
-
| **Scrollback** | tmux buffer (configurable) | xterm.js buffer |
|
|
159
|
-
| **Performance** | Native | WebGL (good, but more overhead) |
|
|
160
|
-
| **Accessibility** | Terminal emulator's | Browser-based |
|
|
161
|
-
| **Remote access** | SSH | Browser (Tailscale) |
|
|
162
|
-
|
|
163
|
-
**Robustness verdict:** Native is more robust for power users. Browser is more accessible for teams/remote.
|
|
164
|
-
|
|
165
|
-
---
|
|
166
|
-
|
|
167
|
-
### 3. Message Injection
|
|
168
|
-
|
|
169
|
-
#### Ours: Idle detection + send-keys
|
|
170
|
-
|
|
171
|
-
```typescript
|
|
172
|
-
private async injectNextMessage(): Promise<void> {
|
|
173
|
-
// Wait for output to settle
|
|
174
|
-
const timeSinceOutput = Date.now() - this.lastOutputTime;
|
|
175
|
-
if (timeSinceOutput < 1500) { // 1.5 seconds
|
|
176
|
-
setTimeout(() => this.checkForInjectionOpportunity(), 500);
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Clear any partial input
|
|
181
|
-
await this.sendKeys('Escape');
|
|
182
|
-
await this.sleep(30);
|
|
183
|
-
await this.sendKeys('C-u'); // Clear line
|
|
184
|
-
await this.sleep(30);
|
|
185
|
-
|
|
186
|
-
// Type the message
|
|
187
|
-
await this.sendKeysLiteral(message);
|
|
188
|
-
await this.sleep(50);
|
|
189
|
-
await this.sendKeys('Enter');
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
private async sendKeysLiteral(text: string): Promise<void> {
|
|
193
|
-
const escaped = text
|
|
194
|
-
.replace(/[\r\n]+/g, ' ')
|
|
195
|
-
.replace(/\\/g, '\\\\')
|
|
196
|
-
.replace(/"/g, '\\"')
|
|
197
|
-
.replace(/\$/g, '\\$')
|
|
198
|
-
.replace(/`/g, '\\`')
|
|
199
|
-
.replace(/!/g, '\\!');
|
|
200
|
-
await execAsync(`tmux send-keys -t ${this.sessionName} -l "${escaped}"`);
|
|
201
|
-
}
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
#### Alternative: Multiple injection methods
|
|
205
|
-
|
|
206
|
-
```bash
|
|
207
|
-
# Method 1: display-message (non-intrusive popup)
|
|
208
|
-
tmux display-message -t $SESSION "Message from $FROM: $MSG"
|
|
209
|
-
|
|
210
|
-
# Method 2: send-keys with echo (injects shell command)
|
|
211
|
-
tmux send-keys -t $SESSION "echo '📬 MESSAGE: $MSG'" Enter
|
|
212
|
-
|
|
213
|
-
# Method 3: send-keys literal (injects text)
|
|
214
|
-
tmux send-keys -t $SESSION -l "Message: $MSG"
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
#### Comparison
|
|
218
|
-
|
|
219
|
-
| Aspect | Our Approach | Their Approach |
|
|
220
|
-
|--------|--------------|----------------|
|
|
221
|
-
| **Idle detection** | Time-based (1.5s) | None (fire and forget) |
|
|
222
|
-
| **Input clearing** | Yes (Esc + Ctrl-U) | No |
|
|
223
|
-
| **Race conditions** | Reduced | Possible |
|
|
224
|
-
| **CLI-specific** | Yes (Gemini printf) | Partial |
|
|
225
|
-
| **Intrusive** | Yes (types into prompt) | display-message is not |
|
|
226
|
-
|
|
227
|
-
**Robustness verdict:** Our approach is more robust because of idle detection and input clearing. Their `display-message` is less intrusive but also less reliable for LLM consumption.
|
|
228
|
-
|
|
229
|
-
---
|
|
230
|
-
|
|
231
|
-
### 4. Message Detection/Parsing
|
|
232
|
-
|
|
233
|
-
#### Ours: Pattern matching on terminal output
|
|
234
|
-
|
|
235
|
-
```typescript
|
|
236
|
-
// Parser handles real-world terminal mess
|
|
237
|
-
const INLINE_RELAY = /^(?:\s*(?:[>$%#→➜›»●•◦‣⁃\-*⏺◆◇○□■]\s*)*)?->relay:(\S+)\s+(.+)$/;
|
|
238
|
-
|
|
239
|
-
// Strip ANSI codes
|
|
240
|
-
const ANSI_PATTERN = /\x1b\[[0-9;?]*[a-zA-Z]|\x1b\].*?(?:\x07|\x1b\\)|\r/g;
|
|
241
|
-
|
|
242
|
-
// Handle continuation lines (TUI wrapping)
|
|
243
|
-
private joinContinuationLines(content: string): string {
|
|
244
|
-
// Claude Code and TUIs insert real newlines...
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Track what we've already processed
|
|
248
|
-
private sentMessageHashes: Set<string> = new Set();
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
#### Alternative: No parsing needed
|
|
252
|
-
|
|
253
|
-
```javascript
|
|
254
|
-
// Messages are sent via API/CLI, not terminal output
|
|
255
|
-
send-relay-message.sh Bob "Subject" "Body"
|
|
256
|
-
|
|
257
|
-
// Creates file in ~/.relay/messages/inbox/Bob/
|
|
258
|
-
// Agent's "subconscious" polls filesystem for new files
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
#### Comparison
|
|
262
|
-
|
|
263
|
-
| Aspect | Pattern Parsing (Ours) | API-based (Theirs) |
|
|
264
|
-
|--------|------------------------|-------------------|
|
|
265
|
-
| **Agent effort** | Just output text | Call external script |
|
|
266
|
-
| **Natural** | Yes (`->relay:Bob hi`) | No (shell command) |
|
|
267
|
-
| **Reliable** | ~95% (edge cases) | 100% (structured) |
|
|
268
|
-
| **Multi-line** | Complex (continuation) | Easy (JSON body) |
|
|
269
|
-
| **ANSI codes** | Must strip | N/A |
|
|
270
|
-
|
|
271
|
-
**Robustness verdict:** API-based is technically more robust (no parsing edge cases). But pattern-based is more natural for agents - they just "speak" instead of calling tools.
|
|
272
|
-
|
|
273
|
-
---
|
|
274
|
-
|
|
275
|
-
### 5. Session Management
|
|
276
|
-
|
|
277
|
-
#### Ours: One wrapper = one session
|
|
278
|
-
|
|
279
|
-
```typescript
|
|
280
|
-
// Generate unique session name
|
|
281
|
-
this.sessionName = `relay-${config.name}-${process.pid}`;
|
|
282
|
-
|
|
283
|
-
// Create session
|
|
284
|
-
execSync(`tmux new-session -d -s ${this.sessionName} -x ${cols} -y ${rows}`);
|
|
285
|
-
|
|
286
|
-
// Set environment
|
|
287
|
-
execSync(`tmux setenv -t ${this.sessionName} AGENT_RELAY_NAME ${name}`);
|
|
288
|
-
|
|
289
|
-
// When wrapper exits, session is killed
|
|
290
|
-
stop(): void {
|
|
291
|
-
execSync(`tmux kill-session -t ${this.sessionName} 2>/dev/null`);
|
|
292
|
-
}
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
#### Alternative: Discovery-based
|
|
296
|
-
|
|
297
|
-
```typescript
|
|
298
|
-
// Discover existing sessions
|
|
299
|
-
async function discoverLocalSessions(): Promise<Session[]> {
|
|
300
|
-
const { stdout } = await execAsync('tmux list-sessions -F "#{session_name}"');
|
|
301
|
-
return stdout.trim().split('\n').map(name => ({
|
|
302
|
-
name,
|
|
303
|
-
// Fetch metadata
|
|
304
|
-
cwd: await execAsync(`tmux display-message -t ${name} -p '#{pane_current_path}'`)
|
|
305
|
-
}));
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// Sessions can exist without agents
|
|
309
|
-
// Agents can exist without sessions
|
|
310
|
-
// Linking is optional
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
#### Comparison
|
|
314
|
-
|
|
315
|
-
| Aspect | Wrapper-owns-session (Ours) | Discovery-based (Theirs) |
|
|
316
|
-
|--------|----------------------------|-------------------------|
|
|
317
|
-
| **Lifecycle** | Coupled (wrapper=session) | Decoupled |
|
|
318
|
-
| **Pre-existing** | No | Yes |
|
|
319
|
-
| **Orphan sessions** | No (killed on exit) | Possible |
|
|
320
|
-
| **Flexibility** | Lower | Higher |
|
|
321
|
-
|
|
322
|
-
**Robustness verdict:** Their approach is more flexible (can attach to existing sessions). Our approach is simpler and prevents orphan sessions.
|
|
323
|
-
|
|
324
|
-
---
|
|
325
|
-
|
|
326
|
-
## Which is More Robust?
|
|
327
|
-
|
|
328
|
-
### Our Strengths
|
|
329
|
-
|
|
330
|
-
| Area | Why We're Stronger |
|
|
331
|
-
|------|-------------------|
|
|
332
|
-
| **Native experience** | Direct tmux attach, no emulation layer |
|
|
333
|
-
| **Simplicity** | No native dependencies, no WebSocket complexity |
|
|
334
|
-
| **Injection** | Idle detection prevents race conditions |
|
|
335
|
-
| **CLI support** | Special handling for Gemini, etc. |
|
|
336
|
-
| **Deduplication** | Won't send same message twice |
|
|
337
|
-
|
|
338
|
-
### Their Strengths
|
|
339
|
-
|
|
340
|
-
| Area | Why They're Stronger |
|
|
341
|
-
|------|---------------------|
|
|
342
|
-
| **Real-time** | Event-driven, no polling latency |
|
|
343
|
-
| **Visibility** | Browser dashboard shows all agents |
|
|
344
|
-
| **Message reliability** | Filesystem-based, never lost |
|
|
345
|
-
| **Remote access** | Browser-based, works via Tailscale |
|
|
346
|
-
| **Agent decoupling** | Agents exist independent of sessions |
|
|
347
|
-
|
|
348
|
-
---
|
|
349
|
-
|
|
350
|
-
## Recommended Improvements for Robustness
|
|
351
|
-
|
|
352
|
-
### 1. Add Activity State Tracking (from their approach)
|
|
353
|
-
|
|
354
|
-
```typescript
|
|
355
|
-
// Track active/idle/disconnected state
|
|
356
|
-
private activityState: 'active' | 'idle' | 'disconnected' = 'active';
|
|
357
|
-
private lastActivityTime = Date.now();
|
|
358
|
-
|
|
359
|
-
private updateActivityState(): void {
|
|
360
|
-
const elapsed = Date.now() - this.lastActivityTime;
|
|
361
|
-
|
|
362
|
-
if (elapsed > 30_000) {
|
|
363
|
-
this.activityState = 'idle';
|
|
364
|
-
// Idle is the BEST time to inject
|
|
365
|
-
this.flushMessageQueue();
|
|
366
|
-
} else if (elapsed > 5_000) {
|
|
367
|
-
this.activityState = 'idle';
|
|
368
|
-
} else {
|
|
369
|
-
this.activityState = 'active';
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
### 2. Add Exponential Backoff for Reconnection
|
|
375
|
-
|
|
376
|
-
```typescript
|
|
377
|
-
private readonly RECONNECT_DELAYS = [100, 500, 1000, 2000, 5000];
|
|
378
|
-
private reconnectAttempt = 0;
|
|
379
|
-
|
|
380
|
-
private reconnect(): void {
|
|
381
|
-
if (this.reconnectAttempt >= this.RECONNECT_DELAYS.length) {
|
|
382
|
-
this.logStderr('Max reconnection attempts, operating offline');
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
const delay = this.RECONNECT_DELAYS[this.reconnectAttempt++];
|
|
387
|
-
setTimeout(() => this.client.connect(), delay);
|
|
388
|
-
}
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
### 3. Consider Hybrid: Streaming + Pattern Parsing
|
|
392
|
-
|
|
393
|
-
```typescript
|
|
394
|
-
// Best of both worlds (optional mode)
|
|
395
|
-
import { spawn } from 'node-pty';
|
|
396
|
-
|
|
397
|
-
// Read-only PTY attach for real-time output
|
|
398
|
-
const pty = spawn('tmux', ['attach-session', '-t', session, '-r']);
|
|
399
|
-
|
|
400
|
-
pty.onData((data) => {
|
|
401
|
-
// Real-time pattern detection
|
|
402
|
-
const { commands } = this.parser.parse(data);
|
|
403
|
-
for (const cmd of commands) {
|
|
404
|
-
this.sendRelayCommand(cmd);
|
|
405
|
-
}
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
// Still use send-keys for injection (works on attached session)
|
|
409
|
-
```
|
|
410
|
-
|
|
411
|
-
### 4. Add Bracketed Paste for Safer Injection
|
|
412
|
-
|
|
413
|
-
```typescript
|
|
414
|
-
const PASTE_START = '\x1b[200~';
|
|
415
|
-
const PASTE_END = '\x1b[201~';
|
|
416
|
-
|
|
417
|
-
async function injectSafe(text: string): Promise<void> {
|
|
418
|
-
// Bracketed paste prevents shell interpretation
|
|
419
|
-
await sendKeysLiteral(PASTE_START + text + PASTE_END);
|
|
420
|
-
await sendKeys('Enter');
|
|
421
|
-
}
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
---
|
|
425
|
-
|
|
426
|
-
## Known Issue: `@` Symbol Conflicts with Gemini
|
|
427
|
-
|
|
428
|
-
### The Problem
|
|
429
|
-
|
|
430
|
-
Gemini CLI uses `@` for file references:
|
|
431
|
-
```bash
|
|
432
|
-
gemini> @src/main.ts # References a file
|
|
433
|
-
gemini> ->relay:Bob Hi # Gemini might try to open file "relay:Bob"!
|
|
434
|
-
```
|
|
435
|
-
|
|
436
|
-
This could explain why Gemini agents have trouble sending relay messages - the CLI intercepts `@` before it reaches the terminal output.
|
|
437
|
-
|
|
438
|
-
We've standardized on `->relay:` for all CLIs to keep a single mental model. Keep the prefix configurable so Gemini users can fall back to an alternative if their CLI mangles the arrow sequence.
|
|
439
|
-
|
|
440
|
-
### Optional Alternative Prefixes
|
|
441
|
-
|
|
442
|
-
| Prefix | Example | Pros | Cons |
|
|
443
|
-
|--------|---------|------|------|
|
|
444
|
-
| `>>` | `>>Bob: Hello` | Simple, intuitive | Could conflict with shell redirect |
|
|
445
|
-
| `->` | `->Bob: Hello` | Clear direction | Might look like code |
|
|
446
|
-
| `#relay:` | `#relay:Bob Hello` | Hashtag is common | Could conflict with comments |
|
|
447
|
-
| `!relay:` | `!relay:Bob Hello` | Bang is distinct | Could trigger shell history |
|
|
448
|
-
| `/relay` | `/relay Bob Hello` | Slash command style | Familiar pattern |
|
|
449
|
-
| `[[relay]]` | `[[relay:Bob]] Hello` | Very distinct | Verbose |
|
|
450
|
-
| `@>` | `@>Bob: Hello` | Keeps @ but distinct | Still has @ |
|
|
451
|
-
| `relay::` | `relay::Bob Hello` | No special prefix char | Plain text |
|
|
452
|
-
|
|
453
|
-
### Recommended: Configurable Prefix
|
|
454
|
-
|
|
455
|
-
Support multiple prefixes with a default that works everywhere:
|
|
456
|
-
|
|
457
|
-
```typescript
|
|
458
|
-
// In config
|
|
459
|
-
{
|
|
460
|
-
"relayPrefix": "->relay:", // Default (works across Claude/Codex/Gemini)
|
|
461
|
-
// Alternatives:
|
|
462
|
-
// "relayPrefix": ">>", // For Gemini
|
|
463
|
-
// "relayPrefix": "/relay", // Slash command style
|
|
464
|
-
// "relayPrefix": "relay::", // Plain text
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// In parser.ts
|
|
468
|
-
const prefix = config.relayPrefix || '->relay:';
|
|
469
|
-
const pattern = new RegExp(`^(?:\\s*)?${escapeRegex(prefix)}(\\S+)\\s+(.+)$`);
|
|
470
|
-
```
|
|
471
|
-
|
|
472
|
-
### Testing Gemini with Alternative Prefix
|
|
473
|
-
|
|
474
|
-
```bash
|
|
475
|
-
# Default: unified arrow prefix
|
|
476
|
-
agent-relay -n GeminiAgent gemini
|
|
477
|
-
|
|
478
|
-
# If Gemini mangles ->relay:, try an alternative prefix
|
|
479
|
-
agent-relay -n GeminiAgent --prefix=">>" gemini
|
|
480
|
-
|
|
481
|
-
# Agent outputs:
|
|
482
|
-
>>Bob: Can you review this code?
|
|
483
|
-
|
|
484
|
-
# Instead of:
|
|
485
|
-
->relay:Bob Can you review this code?
|
|
486
|
-
```
|
|
487
|
-
|
|
488
|
-
### Implementation: CLI Flag
|
|
489
|
-
|
|
490
|
-
```typescript
|
|
491
|
-
// In cli/index.ts
|
|
492
|
-
.option('--prefix <pattern>', 'Relay pattern prefix (default: ->relay:)')
|
|
493
|
-
|
|
494
|
-
// In wrapper config
|
|
495
|
-
const wrapperConfig: TmuxWrapperConfig = {
|
|
496
|
-
name: options.name,
|
|
497
|
-
command: mainCommand,
|
|
498
|
-
args: commandArgs,
|
|
499
|
-
relayPrefix: options.prefix || '->relay:',
|
|
500
|
-
// ...
|
|
501
|
-
};
|
|
502
|
-
```
|
|
503
|
-
|
|
504
|
-
### Parser Update
|
|
505
|
-
|
|
506
|
-
```typescript
|
|
507
|
-
// In parser.ts
|
|
508
|
-
export class OutputParser {
|
|
509
|
-
private prefix: string;
|
|
510
|
-
private inlinePattern: RegExp;
|
|
511
|
-
|
|
512
|
-
constructor(options: ParserOptions = {}) {
|
|
513
|
-
this.prefix = options.prefix || '->relay:';
|
|
514
|
-
|
|
515
|
-
// Build pattern dynamically
|
|
516
|
-
const escaped = this.escapeRegex(this.prefix);
|
|
517
|
-
this.inlinePattern = new RegExp(
|
|
518
|
-
`^(?:\\s*(?:[>$%#→➜›»●•◦‣⁃\\-*⏺◆◇○□■]\\s*)*)?${escaped}(\\S+)\\s+(.+)$`
|
|
519
|
-
);
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
private escapeRegex(str: string): string {
|
|
523
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
```
|
|
527
|
-
|
|
528
|
-
### Recommendation
|
|
529
|
-
|
|
530
|
-
1. **Short term:** Add `--prefix` flag to test with Gemini
|
|
531
|
-
2. **Default for all CLIs:** Keep unified `->relay:` prefix; override only when needed
|
|
532
|
-
3. **Long term:** Document recommended fallbacks per CLI if issues appear
|
|
533
|
-
|
|
534
|
-
```typescript
|
|
535
|
-
// Auto-detect best prefix for CLI type
|
|
536
|
-
function getDefaultPrefix(cliType: string): string {
|
|
537
|
-
// Unified default; users can override via --prefix when necessary
|
|
538
|
-
return '->relay:';
|
|
539
|
-
}
|
|
540
|
-
```
|
|
541
|
-
|
|
542
|
-
---
|
|
543
|
-
|
|
544
|
-
## Verdict: Overall Robustness
|
|
545
|
-
|
|
546
|
-
| Category | Winner | Reason |
|
|
547
|
-
|----------|--------|--------|
|
|
548
|
-
| **Message detection** | Tie | Ours is natural, theirs is reliable |
|
|
549
|
-
| **Message delivery** | Ours | Idle detection prevents corruption |
|
|
550
|
-
| **Terminal fidelity** | Ours | Native > emulated |
|
|
551
|
-
| **Real-time** | Theirs | Streaming > polling |
|
|
552
|
-
| **Simplicity** | Ours | No native deps, no browser |
|
|
553
|
-
| **Visibility** | Theirs | Dashboard > logs |
|
|
554
|
-
| **Multi-agent** | Theirs | Built for teams |
|
|
555
|
-
|
|
556
|
-
**Overall:** For 2-3 agents, **ours is more robust** (simpler, fewer failure modes). For 5-10 agents, **theirs scales better** (visibility, discovery). The recommended improvements above would close the gap.
|
|
557
|
-
|
|
558
|
-
---
|
|
559
|
-
|
|
560
|
-
## Current Implementation Summary
|
|
561
|
-
|
|
562
|
-
Our tmux wrapper uses an **attach-based polling architecture**:
|
|
563
|
-
|
|
564
|
-
```
|
|
565
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
566
|
-
│ User Terminal │
|
|
567
|
-
│ └─ tmux attach-session (stdio: 'inherit') │
|
|
568
|
-
│ └─ User sees real tmux session │
|
|
569
|
-
│ │
|
|
570
|
-
│ Background (every 200ms) │
|
|
571
|
-
│ └─ tmux capture-pane -p -J -S - │
|
|
572
|
-
│ └─ Parse for ->relay: patterns │
|
|
573
|
-
│ └─ Send detected commands to daemon │
|
|
574
|
-
│ │
|
|
575
|
-
│ Message Injection │
|
|
576
|
-
│ └─ Wait for 1.5s idle │
|
|
577
|
-
│ └─ tmux send-keys (Escape, C-u, message, Enter) │
|
|
578
|
-
└─────────────────────────────────────────────────────────────┘
|
|
579
|
-
```
|
|
580
|
-
|
|
581
|
-
---
|
|
582
|
-
|
|
583
|
-
## Alternative Approach: WebSocket + node-pty
|
|
584
|
-
|
|
585
|
-
A different approach uses **real-time PTY streaming** instead of polling:
|
|
586
|
-
|
|
587
|
-
```
|
|
588
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
589
|
-
│ Browser/Client │
|
|
590
|
-
│ └─ xterm.js terminal │
|
|
591
|
-
│ └─ WebSocket connection │
|
|
592
|
-
│ │
|
|
593
|
-
│ Server │
|
|
594
|
-
│ └─ node-pty spawns: tmux attach -t session │
|
|
595
|
-
│ └─ pty.onData → ws.send (real-time streaming) │
|
|
596
|
-
│ └─ ws.onMessage → pty.write (real-time input) │
|
|
597
|
-
│ │
|
|
598
|
-
│ No polling needed - events are instant │
|
|
599
|
-
└─────────────────────────────────────────────────────────────┘
|
|
600
|
-
```
|
|
601
|
-
|
|
602
|
-
---
|
|
603
|
-
|
|
604
|
-
## Key Differences
|
|
605
|
-
|
|
606
|
-
| Aspect | Our Approach (Polling) | Alternative (Streaming) |
|
|
607
|
-
|--------|------------------------|-------------------------|
|
|
608
|
-
| **Terminal location** | User's actual terminal | Browser (xterm.js) |
|
|
609
|
-
| **Data flow** | Periodic capture-pane | Real-time PTY events |
|
|
610
|
-
| **Latency** | 0-200ms | ~1-10ms |
|
|
611
|
-
| **CPU usage** | Constant (polling) | Event-driven (lower) |
|
|
612
|
-
| **Complexity** | Simple shell commands | node-pty + WebSocket |
|
|
613
|
-
| **Dependencies** | None (just tmux) | node-pty, ws, xterm.js |
|
|
614
|
-
| **User experience** | Native terminal feel | Browser-based |
|
|
615
|
-
|
|
616
|
-
---
|
|
617
|
-
|
|
618
|
-
## What We Do Better
|
|
619
|
-
|
|
620
|
-
### 1. Native Terminal Experience
|
|
621
|
-
|
|
622
|
-
Users stay in their actual terminal. No browser, no xterm.js emulation quirks.
|
|
623
|
-
|
|
624
|
-
```bash
|
|
625
|
-
# Our approach - user is IN the tmux
|
|
626
|
-
agent-relay -n Alice claude
|
|
627
|
-
# User types directly, sees real output
|
|
628
|
-
|
|
629
|
-
# Alternative - user is in browser
|
|
630
|
-
# Terminal is rendered in xterm.js WebGL
|
|
631
|
-
# Subtle differences in keybindings, scrolling, copy/paste
|
|
632
|
-
```
|
|
633
|
-
|
|
634
|
-
**Keep this.** The native feel is valuable.
|
|
635
|
-
|
|
636
|
-
### 2. Simpler Dependencies
|
|
637
|
-
|
|
638
|
-
We only need tmux and Node.js. No native compilation (node-pty), no browser components.
|
|
639
|
-
|
|
640
|
-
```json
|
|
641
|
-
// Our package.json - no native deps
|
|
642
|
-
{
|
|
643
|
-
"dependencies": {
|
|
644
|
-
"commander": "^12.0.0",
|
|
645
|
-
"better-sqlite3": "^9.0.0"
|
|
646
|
-
// That's it for core functionality
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
// Alternative needs
|
|
651
|
-
{
|
|
652
|
-
"dependencies": {
|
|
653
|
-
"node-pty": "^1.0.0", // Native compilation required
|
|
654
|
-
"xterm": "^5.0.0", // Browser terminal
|
|
655
|
-
"xterm-addon-fit": "...",
|
|
656
|
-
"xterm-addon-webgl": "...",
|
|
657
|
-
"ws": "^8.0.0"
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
```
|
|
661
|
-
|
|
662
|
-
**Keep this.** Simpler install, fewer build issues.
|
|
663
|
-
|
|
664
|
-
### 3. Pattern-Based Communication
|
|
665
|
-
|
|
666
|
-
Agents just output `->relay:Name message`. No API calls, no special handling.
|
|
667
|
-
|
|
668
|
-
```
|
|
669
|
-
# Our approach - agent outputs text naturally
|
|
670
|
-
Claude: I'll ask Bob for help.
|
|
671
|
-
->relay:Bob Can you review the auth module?
|
|
672
|
-
|
|
673
|
-
# Alternative - agent calls external script
|
|
674
|
-
Claude: I'll ask Bob for help.
|
|
675
|
-
!send-message Bob "Can you review the auth module?"
|
|
676
|
-
```
|
|
677
|
-
|
|
678
|
-
**Keep this.** It's our killer feature.
|
|
679
|
-
|
|
680
|
-
---
|
|
681
|
-
|
|
682
|
-
## What We Can Improve
|
|
683
|
-
|
|
684
|
-
### 1. Activity Tracking
|
|
685
|
-
|
|
686
|
-
The alternative tracks session activity state (active/idle/disconnected) with timestamps:
|
|
687
|
-
|
|
688
|
-
```typescript
|
|
689
|
-
// Their approach
|
|
690
|
-
const sessionActivity: Map<string, number> = new Map();
|
|
691
|
-
|
|
692
|
-
// On any output
|
|
693
|
-
sessionActivity.set(sessionName, Date.now());
|
|
694
|
-
|
|
695
|
-
// Idle detection
|
|
696
|
-
const IDLE_THRESHOLD = 30_000; // 30 seconds
|
|
697
|
-
function getSessionStatus(name: string): 'active' | 'idle' | 'disconnected' {
|
|
698
|
-
const lastActivity = sessionActivity.get(name);
|
|
699
|
-
if (!lastActivity) return 'disconnected';
|
|
700
|
-
return Date.now() - lastActivity > IDLE_THRESHOLD ? 'idle' : 'active';
|
|
701
|
-
}
|
|
702
|
-
```
|
|
703
|
-
|
|
704
|
-
**Improvement:** Add activity tracking for better injection timing:
|
|
705
|
-
|
|
706
|
-
```typescript
|
|
707
|
-
// In tmux-wrapper.ts
|
|
708
|
-
private lastActivityTime = Date.now();
|
|
709
|
-
private activityState: 'active' | 'idle' = 'active';
|
|
710
|
-
|
|
711
|
-
private updateActivityState(): void {
|
|
712
|
-
const now = Date.now();
|
|
713
|
-
const wasActive = this.activityState === 'active';
|
|
714
|
-
|
|
715
|
-
if (now - this.lastActivityTime > 30_000) {
|
|
716
|
-
this.activityState = 'idle';
|
|
717
|
-
if (wasActive) {
|
|
718
|
-
this.logStderr('Session went idle');
|
|
719
|
-
// Good time to check for messages
|
|
720
|
-
this.checkForInjectionOpportunity();
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
```
|
|
725
|
-
|
|
726
|
-
### 2. Graceful Reconnection
|
|
727
|
-
|
|
728
|
-
The alternative implements exponential backoff for WebSocket reconnection:
|
|
729
|
-
|
|
730
|
-
```typescript
|
|
731
|
-
// Their approach
|
|
732
|
-
const RECONNECT_DELAYS = [100, 500, 1000, 2000, 5000];
|
|
733
|
-
let reconnectAttempt = 0;
|
|
734
|
-
|
|
735
|
-
function reconnect() {
|
|
736
|
-
if (reconnectAttempt >= RECONNECT_DELAYS.length) {
|
|
737
|
-
console.error('Max reconnection attempts reached');
|
|
738
|
-
return;
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
setTimeout(() => {
|
|
742
|
-
connect();
|
|
743
|
-
reconnectAttempt++;
|
|
744
|
-
}, RECONNECT_DELAYS[reconnectAttempt]);
|
|
745
|
-
}
|
|
746
|
-
```
|
|
747
|
-
|
|
748
|
-
**Improvement:** Add to our RelayClient:
|
|
749
|
-
|
|
750
|
-
```typescript
|
|
751
|
-
// In client.ts
|
|
752
|
-
private reconnectAttempts = 0;
|
|
753
|
-
private readonly MAX_RECONNECT_ATTEMPTS = 5;
|
|
754
|
-
private readonly RECONNECT_DELAYS = [100, 500, 1000, 2000, 5000];
|
|
755
|
-
|
|
756
|
-
private scheduleReconnect(): void {
|
|
757
|
-
if (this.reconnectAttempts >= this.MAX_RECONNECT_ATTEMPTS) {
|
|
758
|
-
this.logStderr('Relay connection failed, operating offline');
|
|
759
|
-
return;
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
const delay = this.RECONNECT_DELAYS[this.reconnectAttempts];
|
|
763
|
-
this.reconnectAttempts++;
|
|
764
|
-
|
|
765
|
-
setTimeout(() => {
|
|
766
|
-
this.connect().catch(() => this.scheduleReconnect());
|
|
767
|
-
}, delay);
|
|
768
|
-
}
|
|
769
|
-
```
|
|
770
|
-
|
|
771
|
-
### 3. Agent Registry Persistence
|
|
772
|
-
|
|
773
|
-
The alternative stores agent metadata in a persistent registry:
|
|
774
|
-
|
|
775
|
-
```typescript
|
|
776
|
-
// Alternative approach - ~/.relay/agents/registry.json
|
|
777
|
-
{
|
|
778
|
-
"agents": {
|
|
779
|
-
"agent-abc123": {
|
|
780
|
-
"id": "agent-abc123",
|
|
781
|
-
"name": "Alice",
|
|
782
|
-
"aliases": ["alice", "dev-alice"],
|
|
783
|
-
"workingDirectory": "/home/user/project",
|
|
784
|
-
"cli": "claude",
|
|
785
|
-
"createdAt": "2025-12-20T10:00:00Z",
|
|
786
|
-
"lastSeen": "2025-12-20T14:30:00Z"
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
```
|
|
791
|
-
|
|
792
|
-
**Improvement:** Add agent registry:
|
|
793
|
-
|
|
794
|
-
```typescript
|
|
795
|
-
// New file: src/daemon/registry.ts
|
|
796
|
-
interface AgentRecord {
|
|
797
|
-
id: string;
|
|
798
|
-
name: string;
|
|
799
|
-
cli: string;
|
|
800
|
-
workingDirectory: string;
|
|
801
|
-
firstSeen: string;
|
|
802
|
-
lastSeen: string;
|
|
803
|
-
messagesSent: number;
|
|
804
|
-
messagesReceived: number;
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
class AgentRegistry {
|
|
808
|
-
private registryPath: string;
|
|
809
|
-
private agents: Map<string, AgentRecord> = new Map();
|
|
810
|
-
|
|
811
|
-
constructor(dataDir: string) {
|
|
812
|
-
this.registryPath = path.join(dataDir, 'agents.json');
|
|
813
|
-
this.load();
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
register(name: string, cli: string, cwd: string): AgentRecord {
|
|
817
|
-
const existing = this.agents.get(name);
|
|
818
|
-
if (existing) {
|
|
819
|
-
existing.lastSeen = new Date().toISOString();
|
|
820
|
-
this.save();
|
|
821
|
-
return existing;
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
const record: AgentRecord = {
|
|
825
|
-
id: `agent-${randomId()}`,
|
|
826
|
-
name,
|
|
827
|
-
cli,
|
|
828
|
-
workingDirectory: cwd,
|
|
829
|
-
firstSeen: new Date().toISOString(),
|
|
830
|
-
lastSeen: new Date().toISOString(),
|
|
831
|
-
messagesSent: 0,
|
|
832
|
-
messagesReceived: 0,
|
|
833
|
-
};
|
|
834
|
-
|
|
835
|
-
this.agents.set(name, record);
|
|
836
|
-
this.save();
|
|
837
|
-
return record;
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
```
|
|
841
|
-
|
|
842
|
-
### 4. Session Discovery
|
|
843
|
-
|
|
844
|
-
The alternative auto-discovers tmux sessions:
|
|
845
|
-
|
|
846
|
-
```typescript
|
|
847
|
-
// Their approach
|
|
848
|
-
async function discoverLocalSessions(): Promise<Session[]> {
|
|
849
|
-
const { stdout } = await execAsync('tmux list-sessions -F "#{session_name}"');
|
|
850
|
-
const sessionNames = stdout.trim().split('\n').filter(Boolean);
|
|
851
|
-
|
|
852
|
-
return Promise.all(sessionNames.map(async (name) => {
|
|
853
|
-
const { stdout: cwd } = await execAsync(
|
|
854
|
-
`tmux display-message -t ${name} -p '#{pane_current_path}'`
|
|
855
|
-
);
|
|
856
|
-
return { name, workingDirectory: cwd.trim() };
|
|
857
|
-
}));
|
|
858
|
-
}
|
|
859
|
-
```
|
|
860
|
-
|
|
861
|
-
**Improvement:** Add discovery for better `agent-relay status`:
|
|
862
|
-
|
|
863
|
-
```typescript
|
|
864
|
-
// In cli/index.ts - enhance status command
|
|
865
|
-
async function discoverRelaySessions(): Promise<SessionInfo[]> {
|
|
866
|
-
try {
|
|
867
|
-
const { stdout } = await execAsync('tmux list-sessions -F "#{session_name}"');
|
|
868
|
-
const sessions = stdout.trim().split('\n').filter(Boolean);
|
|
869
|
-
|
|
870
|
-
// Filter to relay sessions only
|
|
871
|
-
return sessions
|
|
872
|
-
.filter(name => name.startsWith('relay-'))
|
|
873
|
-
.map(name => {
|
|
874
|
-
const match = name.match(/^relay-(.+)-\d+$/);
|
|
875
|
-
return match ? { sessionName: name, agentName: match[1] } : null;
|
|
876
|
-
})
|
|
877
|
-
.filter(Boolean);
|
|
878
|
-
} catch {
|
|
879
|
-
return [];
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
```
|
|
883
|
-
|
|
884
|
-
### 5. Output Filtering
|
|
885
|
-
|
|
886
|
-
The alternative filters noisy patterns from logs:
|
|
887
|
-
|
|
888
|
-
```typescript
|
|
889
|
-
// Their approach - filter thinking indicators, escape sequences
|
|
890
|
-
const NOISE_PATTERNS = [
|
|
891
|
-
/\[\d+\/\d+\]/, // [1/418] thinking steps
|
|
892
|
-
/\x1b\[[0-9;]*[mK]/, // ANSI escape sequences
|
|
893
|
-
/^Thinking\.{1,3}$/, // "Thinking..." lines
|
|
894
|
-
];
|
|
895
|
-
|
|
896
|
-
function filterNoise(output: string): string {
|
|
897
|
-
return output.split('\n')
|
|
898
|
-
.filter(line => !NOISE_PATTERNS.some(p => p.test(line)))
|
|
899
|
-
.join('\n');
|
|
900
|
-
}
|
|
901
|
-
```
|
|
902
|
-
|
|
903
|
-
**Improvement:** Add optional output filtering for cleaner logs:
|
|
904
|
-
|
|
905
|
-
```typescript
|
|
906
|
-
// In tmux-wrapper.ts
|
|
907
|
-
private filterForLogging(output: string): string {
|
|
908
|
-
if (!this.config.filterLogs) return output;
|
|
909
|
-
|
|
910
|
-
return output
|
|
911
|
-
.split('\n')
|
|
912
|
-
.filter(line => {
|
|
913
|
-
// Skip thinking indicators
|
|
914
|
-
if (/^\[[\d/]+\]/.test(line)) return false;
|
|
915
|
-
// Skip empty ANSI-only lines
|
|
916
|
-
if (this.stripAnsi(line).trim() === '') return false;
|
|
917
|
-
return true;
|
|
918
|
-
})
|
|
919
|
-
.join('\n');
|
|
920
|
-
}
|
|
921
|
-
```
|
|
922
|
-
|
|
923
|
-
---
|
|
924
|
-
|
|
925
|
-
## Rejected Ideas
|
|
926
|
-
|
|
927
|
-
### 1. Browser-Based Terminal
|
|
928
|
-
|
|
929
|
-
Moving to xterm.js would lose the native terminal feel. Users expect to use their own terminal with their own keybindings, themes, and muscle memory.
|
|
930
|
-
|
|
931
|
-
**Decision:** Keep native tmux attach.
|
|
932
|
-
|
|
933
|
-
### 2. Full node-pty Integration
|
|
934
|
-
|
|
935
|
-
Using node-pty for output streaming would add native dependencies and build complexity. The polling approach works well enough.
|
|
936
|
-
|
|
937
|
-
**Decision:** Keep capture-pane polling. Consider optional streaming as future enhancement.
|
|
938
|
-
|
|
939
|
-
### 3. Complex Agent Lifecycle
|
|
940
|
-
|
|
941
|
-
The alternative supports agents without sessions, complex metadata, and persistent memory. This adds significant complexity.
|
|
942
|
-
|
|
943
|
-
**Decision:** Keep it simple. Agent = wrapper process. When wrapper exits, agent is gone.
|
|
944
|
-
|
|
945
|
-
---
|
|
946
|
-
|
|
947
|
-
## Implementation Priority
|
|
948
|
-
|
|
949
|
-
| Improvement | Effort | Impact | Priority |
|
|
950
|
-
|-------------|--------|--------|----------|
|
|
951
|
-
| Activity tracking | Low | Medium | P1 |
|
|
952
|
-
| Reconnection backoff | Low | Medium | P1 |
|
|
953
|
-
| Session discovery | Low | Low | P2 |
|
|
954
|
-
| Agent registry | Medium | Medium | P2 |
|
|
955
|
-
| Output filtering | Low | Low | P3 |
|
|
956
|
-
|
|
957
|
-
---
|
|
958
|
-
|
|
959
|
-
## Summary
|
|
960
|
-
|
|
961
|
-
Our tmux implementation is **simpler and more native** than alternatives. The key improvements to adopt:
|
|
962
|
-
|
|
963
|
-
1. **Activity state tracking** - Better injection timing
|
|
964
|
-
2. **Exponential backoff** - Graceful daemon reconnection
|
|
965
|
-
3. **Session discovery** - Better status output
|
|
966
|
-
4. **Agent registry** - Persistence across restarts
|
|
967
|
-
|
|
968
|
-
These add minimal complexity while improving reliability. The core architecture (polling + pattern parsing + injection) remains unchanged.
|