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
package/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,1245 @@
|
|
|
1
|
+
# Agent Relay: Architecture & Design Document
|
|
2
|
+
|
|
3
|
+
## Executive Summary
|
|
4
|
+
|
|
5
|
+
Agent Relay is a real-time messaging system that enables autonomous agent-to-agent communication. It allows AI coding assistants (Claude, Codex, Gemini, etc.) running in separate terminal sessions to discover each other and exchange messages without human intervention.
|
|
6
|
+
|
|
7
|
+
The system works by:
|
|
8
|
+
1. Wrapping agent CLI processes in monitored tmux sessions
|
|
9
|
+
2. Parsing agent output for `->relay:` commands
|
|
10
|
+
3. Routing messages through a central daemon via Unix domain sockets
|
|
11
|
+
4. Injecting incoming messages directly into agent terminal input
|
|
12
|
+
|
|
13
|
+
This document provides complete transparency into how the system works, its design decisions, limitations, and trade-offs.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Table of Contents
|
|
18
|
+
|
|
19
|
+
1. [System Overview](#1-system-overview)
|
|
20
|
+
2. [Architecture Layers](#2-architecture-layers)
|
|
21
|
+
3. [Component Deep Dive](#3-component-deep-dive)
|
|
22
|
+
4. [Protocol Specification](#4-protocol-specification)
|
|
23
|
+
5. [Message Flow](#5-message-flow)
|
|
24
|
+
6. [State Machines](#6-state-machines)
|
|
25
|
+
7. [Data Storage](#7-data-storage)
|
|
26
|
+
8. [Security Model](#8-security-model)
|
|
27
|
+
9. [Design Decisions & Trade-offs](#9-design-decisions--trade-offs)
|
|
28
|
+
10. [Known Limitations](#10-known-limitations)
|
|
29
|
+
11. [Future Considerations](#11-future-considerations)
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 1. System Overview
|
|
34
|
+
|
|
35
|
+
### 1.1 Problem Statement
|
|
36
|
+
|
|
37
|
+
Modern AI coding assistants operate in isolation. When you run multiple agents on different parts of a codebase, they cannot:
|
|
38
|
+
- Share discoveries or context
|
|
39
|
+
- Coordinate on interdependent tasks
|
|
40
|
+
- Request help from specialized agents
|
|
41
|
+
- Avoid duplicate work
|
|
42
|
+
|
|
43
|
+
Agent Relay solves this by providing a communication layer that requires **zero modification** to the underlying AI systems.
|
|
44
|
+
|
|
45
|
+
### 1.2 Core Principle: Output Parsing, Not API Integration
|
|
46
|
+
|
|
47
|
+
The fundamental insight is that AI agents already produce text output. By monitoring that output for specific patterns (`->relay:Target message`), we can extract communication intent without modifying the agent itself.
|
|
48
|
+
|
|
49
|
+
This approach:
|
|
50
|
+
- Works with any CLI-based agent
|
|
51
|
+
- Requires no agent-side code changes
|
|
52
|
+
- Preserves the user's normal terminal experience
|
|
53
|
+
- Allows agents to communicate using natural language
|
|
54
|
+
|
|
55
|
+
### 1.3 High-Level Architecture
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
59
|
+
│ User's Terminal │
|
|
60
|
+
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
|
61
|
+
│ │ agent-relay │ │ agent-relay │ │ agent-relay │ │
|
|
62
|
+
│ │ -n Alice │ │ -n Bob │ │ -n Carol │ │
|
|
63
|
+
│ │ claude │ │ codex │ │ gemini │ │
|
|
64
|
+
│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │
|
|
65
|
+
│ │ │ │ │
|
|
66
|
+
│ │ Unix Socket │ Unix Socket │ Unix Socket │
|
|
67
|
+
│ │ │ │ │
|
|
68
|
+
│ └────────────────────┼────────────────────┘ │
|
|
69
|
+
│ │ │
|
|
70
|
+
│ ┌───────────▼───────────┐ │
|
|
71
|
+
│ │ Relay Daemon │ │
|
|
72
|
+
│ │ (Message Router) │ │
|
|
73
|
+
│ └───────────┬───────────┘ │
|
|
74
|
+
│ │ │
|
|
75
|
+
│ ┌───────────▼───────────┐ │
|
|
76
|
+
│ │ SQLite Storage │ │
|
|
77
|
+
│ │ (Message History) │ │
|
|
78
|
+
│ └───────────────────────┘ │
|
|
79
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 2. Architecture Layers
|
|
85
|
+
|
|
86
|
+
The system is organized into six distinct layers:
|
|
87
|
+
|
|
88
|
+
### Layer 1: CLI Interface (`src/cli/`)
|
|
89
|
+
Entry point for users. Parses commands, manages daemon lifecycle, wraps agent processes.
|
|
90
|
+
|
|
91
|
+
### Layer 2: Agent Wrapper (`src/wrapper/`)
|
|
92
|
+
Monitors agent output, parses relay commands, injects incoming messages, maintains daemon connection.
|
|
93
|
+
|
|
94
|
+
### Layer 3: Daemon (`src/daemon/`)
|
|
95
|
+
Central message broker. Manages connections, routes messages, handles handshakes.
|
|
96
|
+
|
|
97
|
+
### Layer 4: Protocol (`src/protocol/`)
|
|
98
|
+
Wire format specification. Defines message types, envelope structure, framing.
|
|
99
|
+
|
|
100
|
+
### Layer 5: Storage (`src/storage/`)
|
|
101
|
+
Message persistence. SQLite for history, supports queries by sender/recipient/time.
|
|
102
|
+
|
|
103
|
+
### Layer 6: Dashboard (`src/dashboard/`)
|
|
104
|
+
Web UI for monitoring. Shows connected agents, message flow, real-time updates.
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
108
|
+
│ Layer 1: CLI │
|
|
109
|
+
│ ┌─────────────────────────────────────────────────────────────┐│
|
|
110
|
+
│ │ Commands: up, down, status, read, wrap ││
|
|
111
|
+
│ └─────────────────────────────────────────────────────────────┘│
|
|
112
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
113
|
+
│ Layer 2: Wrapper │
|
|
114
|
+
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
|
|
115
|
+
│ │ TmuxWrapper │ │ OutputParser │ │ RelayClient │ │
|
|
116
|
+
│ │ (PTY mgmt) │ │ (->relay:) │ │ (Socket I/O) │ │
|
|
117
|
+
│ └───────────────┘ └───────────────┘ └───────────────┘ │
|
|
118
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
119
|
+
│ Layer 3: Daemon │
|
|
120
|
+
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
|
|
121
|
+
│ │ Server │ │ Connection │ │ Router │ │
|
|
122
|
+
│ │ (Lifecycle) │ │ (State M/C) │ │ (Routing) │ │
|
|
123
|
+
│ └───────────────┘ └───────────────┘ └───────────────┘ │
|
|
124
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
125
|
+
│ Layer 4: Protocol │
|
|
126
|
+
│ ┌───────────────┐ ┌───────────────┐ │
|
|
127
|
+
│ │ Types │ │ Framing │ │
|
|
128
|
+
│ │ (Envelopes) │ │ (Wire format) │ │
|
|
129
|
+
│ └───────────────┘ └───────────────┘ │
|
|
130
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
131
|
+
│ Layer 5: Storage │
|
|
132
|
+
│ ┌───────────────┐ ┌───────────────┐ │
|
|
133
|
+
│ │ Adapter │ │ SQLite │ │
|
|
134
|
+
│ │ (Interface) │ │ (Persistence) │ │
|
|
135
|
+
│ └───────────────┘ └───────────────┘ │
|
|
136
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
137
|
+
│ Layer 6: Dashboard │
|
|
138
|
+
│ ┌───────────────┐ ┌───────────────┐ │
|
|
139
|
+
│ │ Express │ │ WebSocket │ │
|
|
140
|
+
│ │ (REST API) │ │ (Real-time) │ │
|
|
141
|
+
│ └───────────────┘ └───────────────┘ │
|
|
142
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## 3. Component Deep Dive
|
|
148
|
+
|
|
149
|
+
### 3.1 TmuxWrapper (`src/wrapper/tmux-wrapper.ts`)
|
|
150
|
+
|
|
151
|
+
The TmuxWrapper is the most complex component. It bridges the gap between agent output and the relay system.
|
|
152
|
+
|
|
153
|
+
#### Architecture
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
157
|
+
│ TmuxWrapper │
|
|
158
|
+
│ │
|
|
159
|
+
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
160
|
+
│ │ tmux session │ │
|
|
161
|
+
│ │ ┌────────────────────────────────────────────────────┐ │ │
|
|
162
|
+
│ │ │ Agent Process (claude, etc.) │ │ │
|
|
163
|
+
│ │ │ │ │ │
|
|
164
|
+
│ │ │ Output: "I'll send a message to Bob" │ │ │
|
|
165
|
+
│ │ │ Output: "->relay:Bob Can you review auth.ts?" │ │ │
|
|
166
|
+
│ │ │ │ │ │
|
|
167
|
+
│ │ └────────────────────────────────────────────────────┘ │ │
|
|
168
|
+
│ └──────────────────────────────────────────────────────────┘ │
|
|
169
|
+
│ │ │
|
|
170
|
+
│ │ capture-pane (every 200ms) │
|
|
171
|
+
│ ▼ │
|
|
172
|
+
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
173
|
+
│ │ OutputParser │ │
|
|
174
|
+
│ │ - Strip ANSI codes │ │
|
|
175
|
+
│ │ - Join continuation lines │ │
|
|
176
|
+
│ │ - Extract ->relay: commands │ │
|
|
177
|
+
│ │ - Deduplicate (hash-based) │ │
|
|
178
|
+
│ └──────────────────────────────────────────────────────────┘ │
|
|
179
|
+
│ │ │
|
|
180
|
+
│ ▼ │
|
|
181
|
+
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
182
|
+
│ │ RelayClient │ │
|
|
183
|
+
│ │ - Connect to daemon │ │
|
|
184
|
+
│ │ - Send SEND envelope │ │
|
|
185
|
+
│ │ - Receive DELIVER envelope │ │
|
|
186
|
+
│ └──────────────────────────────────────────────────────────┘ │
|
|
187
|
+
│ │ │
|
|
188
|
+
│ ▼ │
|
|
189
|
+
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
190
|
+
│ │ Message Injection │ │
|
|
191
|
+
│ │ - Wait for idle (1.5s no output) │ │
|
|
192
|
+
│ │ - tmux send-keys "Relay message from X: ..." │ │
|
|
193
|
+
│ │ - Press Enter │ │
|
|
194
|
+
│ └──────────────────────────────────────────────────────────┘ │
|
|
195
|
+
│ │
|
|
196
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
#### Key Implementation Details
|
|
200
|
+
|
|
201
|
+
**1. Silent Background Polling**
|
|
202
|
+
```typescript
|
|
203
|
+
// Poll every 200ms, capture scrollback
|
|
204
|
+
const { stdout } = await execAsync(
|
|
205
|
+
`tmux capture-pane -t ${sessionName} -p -J -S - 2>/dev/null`
|
|
206
|
+
);
|
|
207
|
+
```
|
|
208
|
+
The `-J` flag joins wrapped lines. The `-S -` captures full scrollback history.
|
|
209
|
+
|
|
210
|
+
**2. ANSI Stripping**
|
|
211
|
+
```typescript
|
|
212
|
+
// Remove escape codes for pattern matching
|
|
213
|
+
return str.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**3. Continuation Line Joining**
|
|
217
|
+
When TUIs wrap long lines, `->relay:` commands can span multiple lines:
|
|
218
|
+
```
|
|
219
|
+
->relay:Bob This is a very long message that gets
|
|
220
|
+
wrapped by the terminal and continues here
|
|
221
|
+
```
|
|
222
|
+
The wrapper joins these back together.
|
|
223
|
+
|
|
224
|
+
**4. Message Deduplication**
|
|
225
|
+
Uses a permanent hash set to prevent re-sending the same message:
|
|
226
|
+
```typescript
|
|
227
|
+
const msgHash = `${cmd.to}:${cmd.body}`;
|
|
228
|
+
if (this.sentMessageHashes.has(msgHash)) return;
|
|
229
|
+
this.sentMessageHashes.add(msgHash);
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
**5. Idle Detection for Injection**
|
|
233
|
+
Waits 1.5 seconds after last output before injecting to avoid interrupting the agent:
|
|
234
|
+
```typescript
|
|
235
|
+
const timeSinceOutput = Date.now() - this.lastOutputTime;
|
|
236
|
+
if (timeSinceOutput < 1500) {
|
|
237
|
+
setTimeout(() => this.checkForInjectionOpportunity(), 500);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**6. CLI-Specific Handling**
|
|
243
|
+
Different CLIs need different injection strategies:
|
|
244
|
+
- **Claude/Codex**: Direct `send-keys` with literal text
|
|
245
|
+
- **Gemini**: Uses `printf` because Gemini interprets input as shell commands
|
|
246
|
+
|
|
247
|
+
### 3.2 OutputParser (`src/wrapper/parser.ts`)
|
|
248
|
+
|
|
249
|
+
Extracts relay commands from agent output.
|
|
250
|
+
|
|
251
|
+
#### Supported Formats
|
|
252
|
+
|
|
253
|
+
**1. Inline Format (Primary)**
|
|
254
|
+
```
|
|
255
|
+
->relay:AgentName Your message here
|
|
256
|
+
->relay:* Broadcast to everyone
|
|
257
|
+
@thinking:AgentName Share reasoning (not displayed to user)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**2. Block Format (Structured)**
|
|
261
|
+
```
|
|
262
|
+
[[RELAY]]{"to":"Agent","type":"message","body":"content","data":{}}[[/RELAY]]
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
#### Pattern Matching
|
|
266
|
+
|
|
267
|
+
The parser handles real-world terminal output complexity:
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
// Allow common input prefixes: >, $, %, #, bullets, etc.
|
|
271
|
+
const INLINE_RELAY = /^(?:\s*(?:[>$%#→➜›»●•◦‣⁃\-*⏺◆◇○□■]\s*)*)?->relay:(\S+)\s+(.+)$/;
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
This matches:
|
|
275
|
+
- `->relay:Bob hello` (plain)
|
|
276
|
+
- ` ->relay:Bob hello` (indented)
|
|
277
|
+
- `> ->relay:Bob hello` (quoted)
|
|
278
|
+
- `- ->relay:Bob hello` (bullet point)
|
|
279
|
+
- `⏺ ->relay:Bob hello` (Claude's bullet)
|
|
280
|
+
|
|
281
|
+
#### Code Fence Awareness
|
|
282
|
+
|
|
283
|
+
The parser ignores content inside code fences to prevent false positives:
|
|
284
|
+
```typescript
|
|
285
|
+
if (CODE_FENCE.test(stripped)) {
|
|
286
|
+
this.inCodeFence = !this.inCodeFence;
|
|
287
|
+
}
|
|
288
|
+
if (this.inCodeFence) {
|
|
289
|
+
return { command: null, output: line };
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### 3.3 Daemon Server (`src/daemon/server.ts`)
|
|
294
|
+
|
|
295
|
+
The central message broker.
|
|
296
|
+
|
|
297
|
+
#### Lifecycle
|
|
298
|
+
|
|
299
|
+
```
|
|
300
|
+
1. Start
|
|
301
|
+
└── Clean up stale socket file
|
|
302
|
+
└── Create Unix domain socket
|
|
303
|
+
└── Set permissions (0o600 - owner only)
|
|
304
|
+
└── Write PID file
|
|
305
|
+
└── Initialize storage adapter
|
|
306
|
+
|
|
307
|
+
2. Accept Connection
|
|
308
|
+
└── Create Connection object
|
|
309
|
+
└── Wait for HELLO
|
|
310
|
+
└── Send WELCOME
|
|
311
|
+
└── Register with Router
|
|
312
|
+
|
|
313
|
+
3. Route Messages
|
|
314
|
+
└── Receive SEND from connection
|
|
315
|
+
└── Look up target in Router
|
|
316
|
+
└── Create DELIVER envelope
|
|
317
|
+
└── Send to target connection
|
|
318
|
+
└── Persist to storage
|
|
319
|
+
|
|
320
|
+
4. Stop
|
|
321
|
+
└── Close all connections
|
|
322
|
+
└── Remove socket file
|
|
323
|
+
└── Remove PID file
|
|
324
|
+
└── Close storage
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
#### agents.json Updates
|
|
328
|
+
|
|
329
|
+
The daemon maintains an `agents.json` file for dashboard consumption:
|
|
330
|
+
```typescript
|
|
331
|
+
private writeAgentsFile(): void {
|
|
332
|
+
const agents = this.router.getAgents().map(name => ({
|
|
333
|
+
name,
|
|
334
|
+
cli: connection?.cli,
|
|
335
|
+
connectedAt: new Date().toISOString(),
|
|
336
|
+
}));
|
|
337
|
+
fs.writeFileSync(agentsPath, JSON.stringify({ agents }, null, 2));
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### 3.4 Connection State Machine (`src/daemon/connection.ts`)
|
|
342
|
+
|
|
343
|
+
Each client connection follows a strict state machine:
|
|
344
|
+
|
|
345
|
+
```
|
|
346
|
+
┌─────────────┐
|
|
347
|
+
│ CONNECTING │
|
|
348
|
+
└──────┬──────┘
|
|
349
|
+
│
|
|
350
|
+
▼
|
|
351
|
+
┌─────────────┐
|
|
352
|
+
┌─────────│ HANDSHAKING │─────────┐
|
|
353
|
+
│ └──────┬──────┘ │
|
|
354
|
+
│ │ │
|
|
355
|
+
│ error │ HELLO/WELCOME │ error
|
|
356
|
+
│ ▼ │
|
|
357
|
+
│ ┌─────────────┐ │
|
|
358
|
+
│ │ ACTIVE │─────────┤
|
|
359
|
+
│ └──────┬──────┘ │
|
|
360
|
+
│ │ │
|
|
361
|
+
│ │ BYE/error │
|
|
362
|
+
│ ▼ │
|
|
363
|
+
│ ┌─────────────┐ │
|
|
364
|
+
└────────▶│ CLOSING │◀────────┘
|
|
365
|
+
└──────┬──────┘
|
|
366
|
+
│
|
|
367
|
+
▼
|
|
368
|
+
┌─────────────┐
|
|
369
|
+
│ CLOSED │
|
|
370
|
+
└─────────────┘
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
#### Heartbeat Mechanism
|
|
374
|
+
|
|
375
|
+
The daemon sends PING every 5 seconds. If no PONG within 10 seconds, connection is terminated:
|
|
376
|
+
```typescript
|
|
377
|
+
if (now - this.lastPongReceived > this.config.heartbeatMs * 2) {
|
|
378
|
+
this.handleError(new Error('Heartbeat timeout'));
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### 3.5 Router (`src/daemon/router.ts`)
|
|
383
|
+
|
|
384
|
+
Manages agent registry and message routing.
|
|
385
|
+
|
|
386
|
+
#### Routing Logic
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
route(from: RoutableConnection, envelope: Envelope<SendPayload>): void {
|
|
390
|
+
const to = envelope.to;
|
|
391
|
+
|
|
392
|
+
if (to === '*') {
|
|
393
|
+
// Broadcast to all (except sender)
|
|
394
|
+
this.broadcast(senderName, envelope, topic);
|
|
395
|
+
} else if (to) {
|
|
396
|
+
// Direct message
|
|
397
|
+
this.sendDirect(senderName, to, envelope);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
#### Topic Subscriptions
|
|
403
|
+
|
|
404
|
+
Agents can subscribe to topics for filtered broadcasts:
|
|
405
|
+
```typescript
|
|
406
|
+
// Agent subscribes
|
|
407
|
+
router.subscribe('Alice', 'code-review');
|
|
408
|
+
|
|
409
|
+
// Later, broadcast only reaches topic subscribers
|
|
410
|
+
envelope.topic = 'code-review';
|
|
411
|
+
router.route(connection, envelope); // Only goes to subscribed agents
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
#### Sequence Numbers
|
|
415
|
+
|
|
416
|
+
Each message gets a sequence number per (topic, peer) stream for ordering:
|
|
417
|
+
```typescript
|
|
418
|
+
getNextSeq(topic: string, peer: string): number {
|
|
419
|
+
const key = `${topic}:${peer}`;
|
|
420
|
+
const seq = (this.sequences.get(key) ?? 0) + 1;
|
|
421
|
+
this.sequences.set(key, seq);
|
|
422
|
+
return seq;
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### 3.6 RelayClient (`src/wrapper/client.ts`)
|
|
427
|
+
|
|
428
|
+
Client-side daemon connection with automatic reconnection.
|
|
429
|
+
|
|
430
|
+
#### State Machine
|
|
431
|
+
|
|
432
|
+
```
|
|
433
|
+
┌──────────────┐
|
|
434
|
+
│ DISCONNECTED │◀────────────────────────────────┐
|
|
435
|
+
└──────┬───────┘ │
|
|
436
|
+
│ connect() │
|
|
437
|
+
▼ │
|
|
438
|
+
┌──────────────┐ │
|
|
439
|
+
│ CONNECTING │─────error──────────────────────▶│
|
|
440
|
+
└──────┬───────┘ │
|
|
441
|
+
│ socket connected │
|
|
442
|
+
▼ │
|
|
443
|
+
┌──────────────┐ │
|
|
444
|
+
│ HANDSHAKING │─────error──────────────────────▶│
|
|
445
|
+
└──────┬───────┘ │
|
|
446
|
+
│ WELCOME received │
|
|
447
|
+
▼ │
|
|
448
|
+
┌──────────────┐ ┌─────────┐ │
|
|
449
|
+
│ READY │◀───────▶│ BACKOFF │────────────┘
|
|
450
|
+
└──────────────┘ └─────────┘
|
|
451
|
+
│ ▲
|
|
452
|
+
│ disconnect │ reconnect
|
|
453
|
+
└──────────────────────┘
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
#### Reconnection Strategy
|
|
457
|
+
|
|
458
|
+
Exponential backoff with jitter:
|
|
459
|
+
```typescript
|
|
460
|
+
const jitter = Math.random() * 0.3 + 0.85; // 0.85 - 1.15
|
|
461
|
+
const delay = Math.min(this.reconnectDelay * jitter, 30000);
|
|
462
|
+
this.reconnectDelay *= 2; // Exponential growth
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
Starting at 100ms, max 30 seconds, up to 10 attempts.
|
|
466
|
+
|
|
467
|
+
---
|
|
468
|
+
|
|
469
|
+
## 4. Protocol Specification
|
|
470
|
+
|
|
471
|
+
### 4.1 Wire Format
|
|
472
|
+
|
|
473
|
+
Messages use a simple length-prefixed framing:
|
|
474
|
+
|
|
475
|
+
```
|
|
476
|
+
┌─────────────────┬─────────────────────────────────┐
|
|
477
|
+
│ Length (4B) │ JSON Payload (UTF-8) │
|
|
478
|
+
│ Big-endian │ (up to 1 MiB) │
|
|
479
|
+
└─────────────────┴─────────────────────────────────┘
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
Example frame:
|
|
483
|
+
```
|
|
484
|
+
Header: 0x00 0x00 0x00 0x3A (58 bytes)
|
|
485
|
+
Payload: {"v":1,"type":"HELLO","id":"abc","ts":1234,"payload":{...}}
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### 4.2 Envelope Structure
|
|
489
|
+
|
|
490
|
+
Every message follows this structure:
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
interface Envelope<T = unknown> {
|
|
494
|
+
v: number; // Protocol version (always 1)
|
|
495
|
+
type: MessageType; // Message type
|
|
496
|
+
id: string; // UUID, unique per sender
|
|
497
|
+
ts: number; // Unix timestamp (milliseconds)
|
|
498
|
+
from?: string; // Sender name (set by daemon)
|
|
499
|
+
to?: string | '*'; // Recipient or broadcast
|
|
500
|
+
topic?: string; // Optional topic/channel
|
|
501
|
+
payload: T; // Type-specific payload
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### 4.3 Message Types
|
|
506
|
+
|
|
507
|
+
| Type | Direction | Purpose |
|
|
508
|
+
|------|-----------|---------|
|
|
509
|
+
| `HELLO` | Client → Daemon | Initiate handshake, identify agent |
|
|
510
|
+
| `WELCOME` | Daemon → Client | Confirm session, provide config |
|
|
511
|
+
| `SEND` | Client → Daemon | Send message to another agent |
|
|
512
|
+
| `DELIVER` | Daemon → Client | Deliver message from another agent |
|
|
513
|
+
| `ACK` | Both | Acknowledge message receipt |
|
|
514
|
+
| `NACK` | Both | Negative acknowledgment |
|
|
515
|
+
| `PING` | Daemon → Client | Heartbeat check |
|
|
516
|
+
| `PONG` | Client → Daemon | Heartbeat response |
|
|
517
|
+
| `ERROR` | Daemon → Client | Error notification |
|
|
518
|
+
| `BUSY` | Daemon → Client | Backpressure signal |
|
|
519
|
+
| `SUBSCRIBE` | Client → Daemon | Subscribe to topic |
|
|
520
|
+
| `UNSUBSCRIBE` | Client → Daemon | Unsubscribe from topic |
|
|
521
|
+
| `BYE` | Both | Graceful disconnect |
|
|
522
|
+
|
|
523
|
+
### 4.4 Handshake Flow
|
|
524
|
+
|
|
525
|
+
```
|
|
526
|
+
Client Daemon
|
|
527
|
+
│ │
|
|
528
|
+
│────────── HELLO ─────────────────▶│
|
|
529
|
+
│ { │
|
|
530
|
+
│ agent: "Alice", │
|
|
531
|
+
│ cli: "claude", │
|
|
532
|
+
│ capabilities: { │
|
|
533
|
+
│ ack: true, │
|
|
534
|
+
│ resume: true, │
|
|
535
|
+
│ max_inflight: 256, │
|
|
536
|
+
│ supports_topics: true │
|
|
537
|
+
│ } │
|
|
538
|
+
│ } │
|
|
539
|
+
│ │
|
|
540
|
+
│◀───────── WELCOME ────────────────│
|
|
541
|
+
│ { │
|
|
542
|
+
│ session_id: "...", │
|
|
543
|
+
│ resume_token: "...", │
|
|
544
|
+
│ server: { │
|
|
545
|
+
│ max_frame_bytes: 1048576, │
|
|
546
|
+
│ heartbeat_ms: 5000 │
|
|
547
|
+
│ } │
|
|
548
|
+
│ } │
|
|
549
|
+
│ │
|
|
550
|
+
│ [Connection ACTIVE] │
|
|
551
|
+
│ │
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
### 4.5 Message Delivery Flow
|
|
555
|
+
|
|
556
|
+
```
|
|
557
|
+
Alice Daemon Bob
|
|
558
|
+
│ │ │
|
|
559
|
+
│──── SEND ─────────────▶│ │
|
|
560
|
+
│ { │ │
|
|
561
|
+
│ to: "Bob", │ │
|
|
562
|
+
│ payload: { │ │
|
|
563
|
+
│ kind: "message", │ │
|
|
564
|
+
│ body: "Hello!" │ │
|
|
565
|
+
│ } │ │
|
|
566
|
+
│ } │ │
|
|
567
|
+
│ │ │
|
|
568
|
+
│ │────── DELIVER ────────▶│
|
|
569
|
+
│ │ { │
|
|
570
|
+
│ │ from: "Alice", │
|
|
571
|
+
│ │ payload: {...}, │
|
|
572
|
+
│ │ delivery: { │
|
|
573
|
+
│ │ seq: 1, │
|
|
574
|
+
│ │ session_id: "..." │
|
|
575
|
+
│ │ } │
|
|
576
|
+
│ │ } │
|
|
577
|
+
│ │ │
|
|
578
|
+
│ │◀─────── ACK ───────────│
|
|
579
|
+
│ │ { ack_id: "...", │
|
|
580
|
+
│ │ seq: 1 } │
|
|
581
|
+
│ │ │
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
### 4.6 Payload Kinds
|
|
585
|
+
|
|
586
|
+
Messages can have different semantic kinds:
|
|
587
|
+
|
|
588
|
+
| Kind | Purpose |
|
|
589
|
+
|------|---------|
|
|
590
|
+
| `message` | General communication |
|
|
591
|
+
| `action` | Request to perform a task |
|
|
592
|
+
| `state` | Status update |
|
|
593
|
+
| `thinking` | Shared reasoning (for transparency) |
|
|
594
|
+
|
|
595
|
+
---
|
|
596
|
+
|
|
597
|
+
## 5. Message Flow
|
|
598
|
+
|
|
599
|
+
### 5.1 Complete End-to-End Flow
|
|
600
|
+
|
|
601
|
+
```
|
|
602
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
603
|
+
│ 1. AGENT OUTPUT │
|
|
604
|
+
│ Agent (Claude) produces text: "->relay:Bob Can you review auth.ts?" │
|
|
605
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
606
|
+
│
|
|
607
|
+
▼
|
|
608
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
609
|
+
│ 2. TMUX CAPTURE │
|
|
610
|
+
│ TmuxWrapper polls: `tmux capture-pane -t session -p -J -S -` │
|
|
611
|
+
│ Retrieves full scrollback buffer │
|
|
612
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
613
|
+
│
|
|
614
|
+
▼
|
|
615
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
616
|
+
│ 3. OUTPUT PARSING │
|
|
617
|
+
│ OutputParser: │
|
|
618
|
+
│ - Strips ANSI escape codes │
|
|
619
|
+
│ - Joins continuation lines │
|
|
620
|
+
│ - Matches /^->relay:(\S+)\s+(.+)$/ │
|
|
621
|
+
│ - Returns: { to: "Bob", body: "Can you review auth.ts?" } │
|
|
622
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
623
|
+
│
|
|
624
|
+
▼
|
|
625
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
626
|
+
│ 4. DEDUPLICATION CHECK │
|
|
627
|
+
│ Hash: "Bob:Can you review auth.ts?" │
|
|
628
|
+
│ If seen before → skip │
|
|
629
|
+
│ If new → add to hash set, continue │
|
|
630
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
631
|
+
│
|
|
632
|
+
▼
|
|
633
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
634
|
+
│ 5. RELAY CLIENT SEND │
|
|
635
|
+
│ Creates SEND envelope: │
|
|
636
|
+
│ { │
|
|
637
|
+
│ v: 1, type: "SEND", id: "uuid", ts: 1234567890, │
|
|
638
|
+
│ to: "Bob", │
|
|
639
|
+
│ payload: { kind: "message", body: "Can you review auth.ts?" } │
|
|
640
|
+
│ } │
|
|
641
|
+
│ Encodes with 4-byte length prefix, writes to socket │
|
|
642
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
643
|
+
│
|
|
644
|
+
▼
|
|
645
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
646
|
+
│ 6. DAEMON RECEIVES │
|
|
647
|
+
│ Connection.handleData() → FrameParser.push() → processFrame() │
|
|
648
|
+
│ Validates state is ACTIVE, forwards to Router │
|
|
649
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
650
|
+
│
|
|
651
|
+
▼
|
|
652
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
653
|
+
│ 7. ROUTER PROCESSES │
|
|
654
|
+
│ router.route(connection, envelope): │
|
|
655
|
+
│ - Looks up "Bob" in agents map │
|
|
656
|
+
│ - Creates DELIVER envelope with sequence number │
|
|
657
|
+
│ - Sends to Bob's connection │
|
|
658
|
+
│ - Persists to SQLite storage │
|
|
659
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
660
|
+
│
|
|
661
|
+
▼
|
|
662
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
663
|
+
│ 8. BOB'S CLIENT RECEIVES │
|
|
664
|
+
│ RelayClient.handleDeliver(): │
|
|
665
|
+
│ - Sends ACK back to daemon │
|
|
666
|
+
│ - Calls onMessage callback │
|
|
667
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
668
|
+
│
|
|
669
|
+
▼
|
|
670
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
671
|
+
│ 9. MESSAGE QUEUED │
|
|
672
|
+
│ TmuxWrapper.handleIncomingMessage(): │
|
|
673
|
+
│ - Adds to messageQueue │
|
|
674
|
+
│ - Schedules injection check │
|
|
675
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
676
|
+
│
|
|
677
|
+
▼
|
|
678
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
679
|
+
│ 10. IDLE DETECTION │
|
|
680
|
+
│ Wait for 1.5 seconds of no output from Bob's agent │
|
|
681
|
+
│ If still active output → retry in 500ms │
|
|
682
|
+
│ If idle → proceed to injection │
|
|
683
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
684
|
+
│
|
|
685
|
+
▼
|
|
686
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
687
|
+
│ 11. MESSAGE INJECTION │
|
|
688
|
+
│ tmux send-keys -t session -l "Relay message from Alice [abc12345]: │
|
|
689
|
+
│ Can you review auth.ts?" │
|
|
690
|
+
│ tmux send-keys -t session Enter │
|
|
691
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
692
|
+
│
|
|
693
|
+
▼
|
|
694
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
695
|
+
│ 12. BOB'S AGENT RECEIVES │
|
|
696
|
+
│ The message appears as user input in Bob's terminal │
|
|
697
|
+
│ Bob's agent (Codex) processes it as a new message │
|
|
698
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
### 5.2 Broadcast Flow
|
|
702
|
+
|
|
703
|
+
When sending to `->relay:*`:
|
|
704
|
+
|
|
705
|
+
```
|
|
706
|
+
Alice Daemon Bob, Carol, Dave
|
|
707
|
+
│ │ │
|
|
708
|
+
│──── SEND ─────────────▶│ │
|
|
709
|
+
│ { to: "*", ... } │ │
|
|
710
|
+
│ │ │
|
|
711
|
+
│ │──── DELIVER ──────────▶│ Bob
|
|
712
|
+
│ │──── DELIVER ──────────▶│ Carol
|
|
713
|
+
│ │──── DELIVER ──────────▶│ Dave
|
|
714
|
+
│ │ │
|
|
715
|
+
│ │ (Alice excluded) │
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
---
|
|
719
|
+
|
|
720
|
+
## 6. State Machines
|
|
721
|
+
|
|
722
|
+
### 6.1 Connection State Machine (Daemon-side)
|
|
723
|
+
|
|
724
|
+
```
|
|
725
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
726
|
+
│ │
|
|
727
|
+
│ CONNECTING ─────────────▶ HANDSHAKING ─────────────▶ ACTIVE │
|
|
728
|
+
│ │ │ │ │
|
|
729
|
+
│ │ │ │ │
|
|
730
|
+
│ │ ┌───────────────┴────────────────────────┤ │
|
|
731
|
+
│ │ │ │ │
|
|
732
|
+
│ │ ▼ ▼ │
|
|
733
|
+
│ │ ERROR ──────────────────────────────────▶ CLOSED │
|
|
734
|
+
│ │ ▲ │
|
|
735
|
+
│ │ │ │
|
|
736
|
+
│ └──────────────────────────────────────────────────┘ │
|
|
737
|
+
│ │
|
|
738
|
+
│ Transitions: │
|
|
739
|
+
│ - CONNECTING → HANDSHAKING: Socket accepted │
|
|
740
|
+
│ - HANDSHAKING → ACTIVE: Valid HELLO received, WELCOME sent │
|
|
741
|
+
│ - HANDSHAKING → ERROR: Invalid HELLO or timeout │
|
|
742
|
+
│ - ACTIVE → CLOSING: BYE received or sent │
|
|
743
|
+
│ - ACTIVE → ERROR: Protocol error or heartbeat timeout │
|
|
744
|
+
│ - CLOSING → CLOSED: Socket closed │
|
|
745
|
+
│ - ERROR → CLOSED: Cleanup complete │
|
|
746
|
+
│ │
|
|
747
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
### 6.2 Client State Machine (Wrapper-side)
|
|
751
|
+
|
|
752
|
+
```
|
|
753
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
754
|
+
│ │
|
|
755
|
+
│ ┌────────────────┐ │
|
|
756
|
+
│ │ DISCONNECTED │◀──────────┐ │
|
|
757
|
+
│ └───────┬────────┘ │ │
|
|
758
|
+
│ │ connect() │ │
|
|
759
|
+
│ ▼ │ │
|
|
760
|
+
│ ┌────────────────┐ │ │
|
|
761
|
+
│ ┌─────────│ CONNECTING │───────────┤ │
|
|
762
|
+
│ │ └───────┬────────┘ │ │
|
|
763
|
+
│ │ │ connected │ │
|
|
764
|
+
│ │ ▼ │ │
|
|
765
|
+
│ │ ┌────────────────┐ │ │
|
|
766
|
+
│ │ error │ HANDSHAKING │───────────┤ │
|
|
767
|
+
│ │ └───────┬────────┘ │ │
|
|
768
|
+
│ │ │ WELCOME │ │
|
|
769
|
+
│ │ ▼ │ │
|
|
770
|
+
│ │ ┌────────────────┐ │ │
|
|
771
|
+
│ │ │ READY │ │ │
|
|
772
|
+
│ │ └───────┬────────┘ │ │
|
|
773
|
+
│ │ │ disconnect │ │
|
|
774
|
+
│ │ ▼ │ │
|
|
775
|
+
│ │ ┌────────────────┐ │ │
|
|
776
|
+
│ └────────▶│ BACKOFF │───────────┘ │
|
|
777
|
+
│ └────────────────┘ max attempts │
|
|
778
|
+
│ │ │
|
|
779
|
+
│ │ timer expires │
|
|
780
|
+
│ └──────▶ (retry connect()) │
|
|
781
|
+
│ │
|
|
782
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
### 6.3 Parser State Machine
|
|
786
|
+
|
|
787
|
+
```
|
|
788
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
789
|
+
│ │
|
|
790
|
+
│ ┌─────────────┐ ┌─────────────┐ │
|
|
791
|
+
│ │ NORMAL │◀────────▶│ IN_FENCE │ │
|
|
792
|
+
│ │ │ ``` │ │ │
|
|
793
|
+
│ └──────┬──────┘ └─────────────┘ │
|
|
794
|
+
│ │ │
|
|
795
|
+
│ │ [[RELAY]] │
|
|
796
|
+
│ ▼ │
|
|
797
|
+
│ ┌─────────────┐ │
|
|
798
|
+
│ │ IN_BLOCK │──── [[/RELAY]] ────▶ Parse JSON, emit command │
|
|
799
|
+
│ │ │ │
|
|
800
|
+
│ └─────────────┘ │
|
|
801
|
+
│ │
|
|
802
|
+
│ State Tracking: │
|
|
803
|
+
│ - inCodeFence: boolean - ignore ->relay inside code fences │
|
|
804
|
+
│ - inBlock: boolean - buffering [[RELAY]]...[[/RELAY]] │
|
|
805
|
+
│ - blockBuffer: string - accumulated block content │
|
|
806
|
+
│ │
|
|
807
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
---
|
|
811
|
+
|
|
812
|
+
## 7. Data Storage
|
|
813
|
+
|
|
814
|
+
### 7.1 Storage Architecture
|
|
815
|
+
|
|
816
|
+
```
|
|
817
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
818
|
+
│ StorageAdapter Interface │
|
|
819
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
820
|
+
│ init(): Promise<void> │
|
|
821
|
+
│ saveMessage(message: StoredMessage): Promise<void> │
|
|
822
|
+
│ getMessages(query: MessageQuery): Promise<StoredMessage[]> │
|
|
823
|
+
│ getMessageById(id: string): Promise<StoredMessage | null> │
|
|
824
|
+
│ close(): Promise<void> │
|
|
825
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
826
|
+
│
|
|
827
|
+
┌───────────────┼───────────────┐
|
|
828
|
+
│ │ │
|
|
829
|
+
▼ ▼ ▼
|
|
830
|
+
┌───────────┐ ┌───────────┐ ┌───────────┐
|
|
831
|
+
│ SQLite │ │ Memory │ │ PostgreSQL│
|
|
832
|
+
│ Adapter │ │ Adapter │ │ (Planned) │
|
|
833
|
+
└───────────┘ └───────────┘ └───────────┘
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
### 7.2 SQLite Schema
|
|
837
|
+
|
|
838
|
+
```sql
|
|
839
|
+
CREATE TABLE messages (
|
|
840
|
+
id TEXT PRIMARY KEY, -- UUID
|
|
841
|
+
ts INTEGER NOT NULL, -- Unix timestamp (ms)
|
|
842
|
+
sender TEXT NOT NULL, -- Sender agent name
|
|
843
|
+
recipient TEXT NOT NULL, -- Recipient agent name
|
|
844
|
+
topic TEXT, -- Optional topic
|
|
845
|
+
kind TEXT NOT NULL, -- message/action/state/thinking
|
|
846
|
+
body TEXT NOT NULL, -- Message content
|
|
847
|
+
data TEXT, -- JSON blob for structured data
|
|
848
|
+
delivery_seq INTEGER, -- Sequence number for ordering
|
|
849
|
+
delivery_session_id TEXT, -- Session that received it
|
|
850
|
+
session_id TEXT -- Sender's session
|
|
851
|
+
);
|
|
852
|
+
|
|
853
|
+
-- Performance indexes
|
|
854
|
+
CREATE INDEX idx_messages_ts ON messages(ts);
|
|
855
|
+
CREATE INDEX idx_messages_sender ON messages(sender);
|
|
856
|
+
CREATE INDEX idx_messages_recipient ON messages(recipient);
|
|
857
|
+
CREATE INDEX idx_messages_topic ON messages(topic);
|
|
858
|
+
```
|
|
859
|
+
|
|
860
|
+
**WAL Mode**: Enabled for better concurrent access.
|
|
861
|
+
|
|
862
|
+
### 7.3 Storage Locations
|
|
863
|
+
|
|
864
|
+
```
|
|
865
|
+
/tmp/agent-relay/{projectId}/
|
|
866
|
+
├── relay.sock # Unix domain socket
|
|
867
|
+
├── relay.sock.pid # Daemon PID file
|
|
868
|
+
├── messages.sqlite # Message database
|
|
869
|
+
├── agents.json # Connected agents (for dashboard)
|
|
870
|
+
└── team/
|
|
871
|
+
└── {agentName}/
|
|
872
|
+
└── inbox.md # File-based inbox (optional)
|
|
873
|
+
```
|
|
874
|
+
|
|
875
|
+
### 7.4 Project Namespace Isolation
|
|
876
|
+
|
|
877
|
+
Each project gets isolated storage based on project root hash:
|
|
878
|
+
|
|
879
|
+
```typescript
|
|
880
|
+
function getProjectId(projectRoot: string): string {
|
|
881
|
+
const hash = crypto.createHash('sha256');
|
|
882
|
+
hash.update(projectRoot);
|
|
883
|
+
return hash.digest('hex').substring(0, 12);
|
|
884
|
+
}
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
Project roots are detected by looking for markers:
|
|
888
|
+
- `.git`
|
|
889
|
+
- `package.json`
|
|
890
|
+
- `Cargo.toml`
|
|
891
|
+
- `go.mod`
|
|
892
|
+
- `pyproject.toml`
|
|
893
|
+
- `.agent-relay`
|
|
894
|
+
|
|
895
|
+
---
|
|
896
|
+
|
|
897
|
+
## 8. Security Model
|
|
898
|
+
|
|
899
|
+
### 8.1 Trust Boundaries
|
|
900
|
+
|
|
901
|
+
```
|
|
902
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
903
|
+
│ TRUST BOUNDARY: Local Machine │
|
|
904
|
+
│ │
|
|
905
|
+
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
906
|
+
│ │ User's Terminal Session │ │
|
|
907
|
+
│ │ │ │
|
|
908
|
+
│ │ Agents run with user's permissions │ │
|
|
909
|
+
│ │ Socket permissions: 0o600 (owner only) │ │
|
|
910
|
+
│ │ No network exposure │ │
|
|
911
|
+
│ │ │ │
|
|
912
|
+
│ └──────────────────────────────────────────────────────────┘ │
|
|
913
|
+
│ │
|
|
914
|
+
│ Assumptions: │
|
|
915
|
+
│ - All agents on the machine are trusted │
|
|
916
|
+
│ - No authentication between agents │
|
|
917
|
+
│ - Any process that can access the socket can send messages │
|
|
918
|
+
│ │
|
|
919
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
920
|
+
```
|
|
921
|
+
|
|
922
|
+
### 8.2 Current Security Properties
|
|
923
|
+
|
|
924
|
+
| Property | Status | Notes |
|
|
925
|
+
|----------|--------|-------|
|
|
926
|
+
| Local-only communication | ✅ | Unix socket, no network |
|
|
927
|
+
| Socket permissions | ✅ | 0o600 (owner read/write only) |
|
|
928
|
+
| No authentication | ⚠️ | Any local process can connect |
|
|
929
|
+
| No encryption | ⚠️ | Messages in plaintext on socket |
|
|
930
|
+
| No message signing | ⚠️ | Sender identity trusted |
|
|
931
|
+
| Rate limiting | ❌ | Not implemented |
|
|
932
|
+
| Message validation | ⚠️ | Basic field presence checks only |
|
|
933
|
+
|
|
934
|
+
### 8.3 Threat Model
|
|
935
|
+
|
|
936
|
+
**In Scope:**
|
|
937
|
+
- Local process isolation (handled by Unix socket permissions)
|
|
938
|
+
- Preventing accidental message loops (deduplication)
|
|
939
|
+
- Graceful handling of malformed messages
|
|
940
|
+
|
|
941
|
+
**Out of Scope (Explicitly Not Protected Against):**
|
|
942
|
+
- Malicious local processes with same user permissions
|
|
943
|
+
- Message spoofing by compromised agents
|
|
944
|
+
- Denial of service from local processes
|
|
945
|
+
- Data exfiltration through relay messages
|
|
946
|
+
|
|
947
|
+
### 8.4 Security Recommendations
|
|
948
|
+
|
|
949
|
+
For sensitive environments:
|
|
950
|
+
1. Run agents under separate user accounts
|
|
951
|
+
2. Use filesystem ACLs for additional socket protection
|
|
952
|
+
3. Monitor message logs for anomalies
|
|
953
|
+
4. Consider adding TLS for socket encryption (not currently implemented)
|
|
954
|
+
|
|
955
|
+
---
|
|
956
|
+
|
|
957
|
+
## 9. Design Decisions & Trade-offs
|
|
958
|
+
|
|
959
|
+
### 9.1 Why Output Parsing Instead of API Integration?
|
|
960
|
+
|
|
961
|
+
**Decision**: Parse agent stdout for `->relay:` patterns instead of modifying agent code.
|
|
962
|
+
|
|
963
|
+
**Rationale**:
|
|
964
|
+
- Works with any CLI agent without modification
|
|
965
|
+
- No vendor lock-in or API dependencies
|
|
966
|
+
- Agents naturally produce text - leverage this
|
|
967
|
+
- Users see exactly what agents communicate
|
|
968
|
+
|
|
969
|
+
**Trade-offs**:
|
|
970
|
+
- ❌ Parsing is inherently fragile (ANSI codes, line wrapping)
|
|
971
|
+
- ❌ Can miss messages in edge cases
|
|
972
|
+
- ❌ No structured validation of message content
|
|
973
|
+
- ✅ Zero changes to Claude, Codex, or other agents
|
|
974
|
+
- ✅ Transparent - users see `->relay:` in agent output
|
|
975
|
+
|
|
976
|
+
### 9.2 Why Tmux Instead of Direct PTY?
|
|
977
|
+
|
|
978
|
+
**Decision**: Wrap agents in tmux sessions rather than implementing our own PTY handling.
|
|
979
|
+
|
|
980
|
+
**Rationale**:
|
|
981
|
+
- Tmux provides mature terminal emulation
|
|
982
|
+
- Users can detach/reattach to sessions
|
|
983
|
+
- Scrollback buffer capture is reliable
|
|
984
|
+
- Mouse scroll and copy/paste work naturally
|
|
985
|
+
|
|
986
|
+
**Trade-offs**:
|
|
987
|
+
- ❌ Dependency on tmux installation
|
|
988
|
+
- ❌ Slightly more complex setup
|
|
989
|
+
- ❌ Platform limitations (tmux on Windows requires WSL)
|
|
990
|
+
- ✅ Battle-tested terminal handling
|
|
991
|
+
- ✅ User can interact with raw session if needed
|
|
992
|
+
|
|
993
|
+
### 9.3 Why Unix Sockets Instead of TCP?
|
|
994
|
+
|
|
995
|
+
**Decision**: Use Unix domain sockets for daemon communication.
|
|
996
|
+
|
|
997
|
+
**Rationale**:
|
|
998
|
+
- No network exposure by default
|
|
999
|
+
- Filesystem permissions for access control
|
|
1000
|
+
- Lower overhead than TCP
|
|
1001
|
+
- Natural fit for single-machine communication
|
|
1002
|
+
|
|
1003
|
+
**Trade-offs**:
|
|
1004
|
+
- ❌ No remote agent communication (by design)
|
|
1005
|
+
- ❌ Platform-specific (Windows support limited)
|
|
1006
|
+
- ✅ Security through file permissions
|
|
1007
|
+
- ✅ No port conflicts
|
|
1008
|
+
|
|
1009
|
+
### 9.4 Why Inject Messages as User Input?
|
|
1010
|
+
|
|
1011
|
+
**Decision**: Inject incoming messages by typing them into the agent's terminal.
|
|
1012
|
+
|
|
1013
|
+
**Rationale**:
|
|
1014
|
+
- Agents treat messages as natural user requests
|
|
1015
|
+
- No modification to agent input handling
|
|
1016
|
+
- Works with any CLI agent
|
|
1017
|
+
|
|
1018
|
+
**Trade-offs**:
|
|
1019
|
+
- ❌ Timing-sensitive (must wait for agent idle)
|
|
1020
|
+
- ❌ Can interrupt agent mid-thought
|
|
1021
|
+
- ❌ Long messages get truncated
|
|
1022
|
+
- ✅ Universal compatibility
|
|
1023
|
+
- ✅ Natural conversational flow
|
|
1024
|
+
|
|
1025
|
+
### 9.5 Why File-Based Inbox as Backup?
|
|
1026
|
+
|
|
1027
|
+
**Decision**: Optionally write messages to `inbox.md` file in addition to terminal injection.
|
|
1028
|
+
|
|
1029
|
+
**Rationale**:
|
|
1030
|
+
- Agents can read inbox at their convenience
|
|
1031
|
+
- Survives terminal buffer overflow
|
|
1032
|
+
- Provides message history
|
|
1033
|
+
- Some agents read files more reliably than terminal input
|
|
1034
|
+
|
|
1035
|
+
**Trade-offs**:
|
|
1036
|
+
- ❌ Agents must be instructed to check inbox
|
|
1037
|
+
- ❌ Delayed message processing
|
|
1038
|
+
- ✅ No message loss
|
|
1039
|
+
- ✅ Works even if injection fails
|
|
1040
|
+
|
|
1041
|
+
### 9.6 Why SQLite for Storage?
|
|
1042
|
+
|
|
1043
|
+
**Decision**: Use SQLite for message persistence.
|
|
1044
|
+
|
|
1045
|
+
**Rationale**:
|
|
1046
|
+
- Zero configuration
|
|
1047
|
+
- Single file, easy to back up
|
|
1048
|
+
- Fast enough for message volume
|
|
1049
|
+
- WAL mode handles concurrent access
|
|
1050
|
+
|
|
1051
|
+
**Trade-offs**:
|
|
1052
|
+
- ❌ Not suitable for distributed deployment
|
|
1053
|
+
- ❌ Limited query capabilities vs. full database
|
|
1054
|
+
- ✅ No external dependencies
|
|
1055
|
+
- ✅ Works offline
|
|
1056
|
+
|
|
1057
|
+
---
|
|
1058
|
+
|
|
1059
|
+
## 10. Known Limitations
|
|
1060
|
+
|
|
1061
|
+
### 10.1 Message Delivery Reliability
|
|
1062
|
+
|
|
1063
|
+
| Issue | Impact | Mitigation |
|
|
1064
|
+
|-------|--------|------------|
|
|
1065
|
+
| Messages can be lost if agent is busy | Medium | Idle detection, file inbox |
|
|
1066
|
+
| No delivery confirmation to sender | Medium | ACK exists but not surfaced |
|
|
1067
|
+
| Dedup memory grows unbounded | Low | Restart periodically |
|
|
1068
|
+
|
|
1069
|
+
### 10.2 Terminal Handling
|
|
1070
|
+
|
|
1071
|
+
| Issue | Impact | Mitigation |
|
|
1072
|
+
|-------|--------|------------|
|
|
1073
|
+
| ANSI codes can confuse parser | Low | Aggressive stripping |
|
|
1074
|
+
| Long messages truncated | Medium | Show truncation notice |
|
|
1075
|
+
| Line wrapping breaks patterns | Medium | Continuation line joining |
|
|
1076
|
+
| Code fences can hide commands | Low | Fence state tracking |
|
|
1077
|
+
|
|
1078
|
+
### 10.3 Timing Issues
|
|
1079
|
+
|
|
1080
|
+
| Issue | Impact | Mitigation |
|
|
1081
|
+
|-------|--------|------------|
|
|
1082
|
+
| Injection can interrupt agent | Medium | 1.5s idle wait |
|
|
1083
|
+
| Polling has 200ms latency | Low | Acceptable for most use cases |
|
|
1084
|
+
| Race between poll and injection | Low | Queue-based injection |
|
|
1085
|
+
|
|
1086
|
+
### 10.4 Platform Support
|
|
1087
|
+
|
|
1088
|
+
| Platform | Status | Notes |
|
|
1089
|
+
|----------|--------|-------|
|
|
1090
|
+
| Linux | ✅ Full | Primary development platform |
|
|
1091
|
+
| macOS | ✅ Full | Well tested |
|
|
1092
|
+
| Windows | ⚠️ Partial | Requires WSL for tmux |
|
|
1093
|
+
|
|
1094
|
+
### 10.5 Scalability
|
|
1095
|
+
|
|
1096
|
+
| Metric | Current Limit | Notes |
|
|
1097
|
+
|--------|---------------|-------|
|
|
1098
|
+
| Concurrent agents | ~50 | Limited by daemon resources |
|
|
1099
|
+
| Message rate | ~100/sec | SQLite bottleneck |
|
|
1100
|
+
| Message size | 1 MiB | Protocol limit |
|
|
1101
|
+
| Storage retention | Unbounded | No automatic cleanup |
|
|
1102
|
+
|
|
1103
|
+
---
|
|
1104
|
+
|
|
1105
|
+
## 11. Future Considerations
|
|
1106
|
+
|
|
1107
|
+
### 11.1 Potential Enhancements
|
|
1108
|
+
|
|
1109
|
+
**Reliability**:
|
|
1110
|
+
- Persistent session resume tokens
|
|
1111
|
+
- Guaranteed delivery with retry
|
|
1112
|
+
- Message acknowledgment surfacing
|
|
1113
|
+
|
|
1114
|
+
**Security**:
|
|
1115
|
+
- Agent authentication tokens
|
|
1116
|
+
- Message signing
|
|
1117
|
+
- Encrypted socket communication
|
|
1118
|
+
|
|
1119
|
+
**Scalability**:
|
|
1120
|
+
- PostgreSQL storage adapter
|
|
1121
|
+
- Multiple daemon instances
|
|
1122
|
+
- Message queue integration
|
|
1123
|
+
|
|
1124
|
+
**Features**:
|
|
1125
|
+
- Agent discovery protocol
|
|
1126
|
+
- Typed message schemas
|
|
1127
|
+
- Priority queues
|
|
1128
|
+
- Message threading
|
|
1129
|
+
|
|
1130
|
+
### 11.2 Architectural Evolution
|
|
1131
|
+
|
|
1132
|
+
The current architecture is intentionally simple. Future evolution might include:
|
|
1133
|
+
|
|
1134
|
+
```
|
|
1135
|
+
Current:
|
|
1136
|
+
Agent ──▶ Tmux ──▶ Parser ──▶ Socket ──▶ Daemon ──▶ Socket ──▶ Agent
|
|
1137
|
+
|
|
1138
|
+
Future (hypothetical):
|
|
1139
|
+
Agent ──▶ Native SDK ──▶ gRPC ──▶ Message Broker ──▶ gRPC ──▶ Agent
|
|
1140
|
+
│
|
|
1141
|
+
▼
|
|
1142
|
+
Distributed
|
|
1143
|
+
Storage
|
|
1144
|
+
```
|
|
1145
|
+
|
|
1146
|
+
However, the output-parsing approach has proven remarkably effective for the target use case of local multi-agent coordination.
|
|
1147
|
+
|
|
1148
|
+
---
|
|
1149
|
+
|
|
1150
|
+
## Appendix A: File Map
|
|
1151
|
+
|
|
1152
|
+
```
|
|
1153
|
+
agent-relay/
|
|
1154
|
+
├── src/
|
|
1155
|
+
│ ├── cli/
|
|
1156
|
+
│ │ └── index.ts # CLI entry point, command handling
|
|
1157
|
+
│ ├── daemon/
|
|
1158
|
+
│ │ ├── server.ts # Daemon lifecycle, socket listener
|
|
1159
|
+
│ │ ├── connection.ts # Connection state machine
|
|
1160
|
+
│ │ ├── router.ts # Message routing logic
|
|
1161
|
+
│ │ └── index.ts
|
|
1162
|
+
│ ├── wrapper/
|
|
1163
|
+
│ │ ├── tmux-wrapper.ts # Tmux session management
|
|
1164
|
+
│ │ ├── client.ts # Daemon client connection
|
|
1165
|
+
│ │ ├── parser.ts # Output parsing (->relay:)
|
|
1166
|
+
│ │ ├── inbox.ts # File-based inbox
|
|
1167
|
+
│ │ └── index.ts
|
|
1168
|
+
│ ├── protocol/
|
|
1169
|
+
│ │ ├── types.ts # Envelope and payload types
|
|
1170
|
+
│ │ ├── framing.ts # Wire format encoding
|
|
1171
|
+
│ │ └── index.ts
|
|
1172
|
+
│ ├── storage/
|
|
1173
|
+
│ │ ├── adapter.ts # Storage interface
|
|
1174
|
+
│ │ └── sqlite-adapter.ts # SQLite implementation
|
|
1175
|
+
│ ├── dashboard/
|
|
1176
|
+
│ │ ├── server.ts # Express + WebSocket server
|
|
1177
|
+
│ │ ├── start.ts # Dashboard startup
|
|
1178
|
+
│ │ └── public/ # Static assets
|
|
1179
|
+
│ ├── utils/
|
|
1180
|
+
│ │ ├── project-namespace.ts # Multi-project isolation
|
|
1181
|
+
│ │ └── name-generator.ts # Random agent names
|
|
1182
|
+
│ └── index.ts # Package exports
|
|
1183
|
+
├── package.json
|
|
1184
|
+
├── tsconfig.json
|
|
1185
|
+
├── CLAUDE.md # Agent instructions
|
|
1186
|
+
└── ARCHITECTURE.md # This document
|
|
1187
|
+
```
|
|
1188
|
+
|
|
1189
|
+
---
|
|
1190
|
+
|
|
1191
|
+
## Appendix B: Environment Variables
|
|
1192
|
+
|
|
1193
|
+
| Variable | Default | Description |
|
|
1194
|
+
|----------|---------|-------------|
|
|
1195
|
+
| `AGENT_RELAY_DASHBOARD_PORT` | 3888 | Dashboard HTTP port |
|
|
1196
|
+
| `AGENT_RELAY_STORAGE_TYPE` | sqlite | Storage backend (sqlite/memory) |
|
|
1197
|
+
| `AGENT_RELAY_STORAGE_PATH` | (auto) | SQLite database path |
|
|
1198
|
+
| `AGENT_RELAY_STORAGE_URL` | - | PostgreSQL URL (future) |
|
|
1199
|
+
| `AGENT_RELAY_DEBUG` | false | Enable debug logging |
|
|
1200
|
+
|
|
1201
|
+
---
|
|
1202
|
+
|
|
1203
|
+
## Appendix C: Quick Reference
|
|
1204
|
+
|
|
1205
|
+
### Starting the System
|
|
1206
|
+
|
|
1207
|
+
```bash
|
|
1208
|
+
# Start daemon (required first)
|
|
1209
|
+
agent-relay up
|
|
1210
|
+
|
|
1211
|
+
# Start agents
|
|
1212
|
+
agent-relay -n Alice claude
|
|
1213
|
+
agent-relay -n Bob claude
|
|
1214
|
+
```
|
|
1215
|
+
|
|
1216
|
+
### Agent Communication
|
|
1217
|
+
|
|
1218
|
+
```
|
|
1219
|
+
# Direct message
|
|
1220
|
+
->relay:Bob Please review the auth module
|
|
1221
|
+
|
|
1222
|
+
# Broadcast
|
|
1223
|
+
->relay:* I've finished the database migration
|
|
1224
|
+
|
|
1225
|
+
# Structured (block format)
|
|
1226
|
+
[[RELAY]]{"to":"Bob","type":"action","body":"Run tests"}[[/RELAY]]
|
|
1227
|
+
```
|
|
1228
|
+
|
|
1229
|
+
### Troubleshooting
|
|
1230
|
+
|
|
1231
|
+
```bash
|
|
1232
|
+
# Check daemon status
|
|
1233
|
+
agent-relay status
|
|
1234
|
+
|
|
1235
|
+
# Read truncated message
|
|
1236
|
+
agent-relay read <message-id>
|
|
1237
|
+
|
|
1238
|
+
# View logs
|
|
1239
|
+
# (Daemon logs to stdout, wrapper logs to stderr)
|
|
1240
|
+
```
|
|
1241
|
+
|
|
1242
|
+
---
|
|
1243
|
+
|
|
1244
|
+
*Document generated for agent-relay v1.0.7*
|
|
1245
|
+
*Last updated: 2025*
|