agent-relay 1.3.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +130 -158
- package/bin/relay-pty +0 -0
- package/bin/relay-pty-darwin-arm64 +0 -0
- package/bin/relay-pty-darwin-x64 +0 -0
- package/bin/relay-pty-linux-x64 +0 -0
- package/deploy/workspace/entrypoint.sh +9 -0
- package/dist/bridge/spawner.d.ts +4 -4
- package/dist/bridge/spawner.js +58 -92
- package/dist/cli/index.d.ts +8 -6
- package/dist/cli/index.js +282 -47
- package/dist/cloud/api/daemons.js +13 -32
- package/dist/cloud/api/onboarding.js +2 -4
- package/dist/cloud/api/providers.js +6 -0
- package/dist/cloud/config.d.ts +1 -0
- package/dist/cloud/config.js +2 -0
- package/dist/cloud/db/bulk-ingest.d.ts +2 -1
- package/dist/cloud/db/drizzle.d.ts +21 -26
- package/dist/cloud/db/drizzle.js +87 -100
- package/dist/cloud/db/index.d.ts +6 -5
- package/dist/cloud/db/index.js +9 -8
- package/dist/cloud/db/schema.d.ts +1049 -1076
- package/dist/cloud/db/schema.js +59 -71
- package/dist/cloud/server.js +854 -18
- package/dist/cloud/services/persistence.d.ts +15 -15
- package/dist/cloud/services/persistence.js +14 -14
- package/dist/daemon/agent-manager.d.ts +6 -5
- package/dist/daemon/agent-manager.js +12 -8
- package/dist/daemon/channel-membership-store.d.ts +48 -0
- package/dist/daemon/channel-membership-store.js +149 -0
- package/dist/daemon/cloud-sync.d.ts +2 -0
- package/dist/daemon/cloud-sync.js +4 -0
- package/dist/daemon/connection.js +17 -9
- package/dist/daemon/router.d.ts +37 -0
- package/dist/daemon/router.js +318 -79
- package/dist/daemon/server.d.ts +15 -0
- package/dist/daemon/server.js +141 -3
- package/dist/dashboard/out/404.html +1 -0
- package/dist/dashboard/out/_next/static/IxxVRv94L1w3ReRGAiI-k/_buildManifest.js +1 -0
- package/dist/dashboard/out/_next/static/IxxVRv94L1w3ReRGAiI-k/_ssgManifest.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/116-eacf84a131b80db9.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/117-c8afed19e821a35d.js +2 -0
- package/dist/dashboard/out/_next/static/chunks/282-980c2eb8fff20123.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/532-bace199897eeab37.js +9 -0
- package/dist/dashboard/out/_next/static/chunks/64-87ab9cd6bcf2f737.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/648-acb2ff9f77cbfbd3.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/766-aa7c8c9900ff5f53.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/83-4f08122d4e7e79a6.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/847-f1f467060f32afff.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/891-a024fbe4b619cf6f.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/_not-found/page-60501fddbafba9dc.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/app/onboarding/page-f746f29e01fffc43.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/app/page-ffad986adfcc8b31.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/cloud/link/page-cfeb437f08a12ed9.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/connect-repos/page-03ac6f35a6654ea6.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/history/page-240f91e8b06ba8ac.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/layout-c0d118c0f92d969c.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/login/page-6ec54eee75877971.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/metrics/page-82938ab8fcf44694.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/page-671037943b2f2e43.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/pricing/page-0efa024c28ba4597.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/providers/page-57cbd738c6a73859.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/providers/setup/[provider]/page-5ab0854472b402b0.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/signup/page-18a4665665f6be11.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/e868780c-48e5f147c90a3a41.js +18 -0
- package/dist/dashboard/out/_next/static/chunks/fd9d1056-609918ca7b6280bb.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/framework-f66176bb897dc684.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/main-5a40a5ae29646e1b.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/main-app-6e8e8d3ef4e0192a.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/pages/_app-72b849fbd24ac258.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/pages/_error-7ba65e1336b92748.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/webpack-1cdd8ed57114d5e1.js +1 -0
- package/dist/dashboard/out/_next/static/css/4034f236dd1a3178.css +1 -0
- package/dist/dashboard/out/_next/static/css/8f9ed310f454e5a5.css +1 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-128.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-256.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-32.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-512.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-64.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo.svg +45 -0
- package/dist/dashboard/out/alt-logos/logo.svg +38 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-128.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-256.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-32.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-512.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-64.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo.svg +38 -0
- package/dist/dashboard/out/app/onboarding.html +1 -0
- package/dist/dashboard/out/app/onboarding.txt +7 -0
- package/dist/dashboard/out/app.html +1 -0
- package/dist/dashboard/out/app.txt +7 -0
- package/dist/dashboard/out/apple-icon.png +0 -0
- package/dist/dashboard/out/cloud/link.html +1 -0
- package/dist/dashboard/out/cloud/link.txt +7 -0
- package/dist/dashboard/out/connect-repos.html +1 -0
- package/dist/dashboard/out/connect-repos.txt +7 -0
- package/dist/dashboard/out/history.html +1 -0
- package/dist/dashboard/out/history.txt +7 -0
- package/dist/dashboard/out/index.html +1 -0
- package/dist/dashboard/out/index.txt +7 -0
- package/dist/dashboard/out/login.html +5 -0
- package/dist/dashboard/out/login.txt +7 -0
- package/dist/dashboard/out/metrics.html +1 -0
- package/dist/dashboard/out/metrics.txt +7 -0
- package/dist/dashboard/out/pricing.html +13 -0
- package/dist/dashboard/out/pricing.txt +7 -0
- package/dist/dashboard/out/providers/setup/claude.html +1 -0
- package/dist/dashboard/out/providers/setup/claude.txt +8 -0
- package/dist/dashboard/out/providers/setup/codex.html +1 -0
- package/dist/dashboard/out/providers/setup/codex.txt +8 -0
- package/dist/dashboard/out/providers.html +1 -0
- package/dist/dashboard/out/providers.txt +7 -0
- package/dist/dashboard/out/signup.html +6 -0
- package/dist/dashboard/out/signup.txt +7 -0
- package/dist/dashboard-server/metrics.d.ts +105 -0
- package/dist/dashboard-server/metrics.js +193 -0
- package/dist/dashboard-server/needs-attention.d.ts +24 -0
- package/dist/dashboard-server/needs-attention.js +78 -0
- package/dist/dashboard-server/server.d.ts +15 -0
- package/dist/dashboard-server/server.js +4753 -0
- package/dist/dashboard-server/start.d.ts +6 -0
- package/dist/dashboard-server/start.js +13 -0
- package/dist/dashboard-server/user-bridge.d.ts +132 -0
- package/dist/dashboard-server/user-bridge.js +317 -0
- package/dist/protocol/channels.d.ts +14 -8
- package/dist/protocol/channels.js +1 -1
- package/dist/protocol/index.d.ts +1 -0
- package/dist/protocol/index.js +1 -0
- package/dist/protocol/relay-pty-schemas.d.ts +209 -0
- package/dist/protocol/relay-pty-schemas.js +60 -0
- package/dist/wrapper/auth-detection.js +8 -1
- package/dist/wrapper/base-wrapper.d.ts +11 -1
- package/dist/wrapper/base-wrapper.js +67 -6
- package/dist/wrapper/client.d.ts +49 -1
- package/dist/wrapper/client.js +167 -0
- package/dist/wrapper/parser.d.ts +0 -4
- package/dist/wrapper/parser.js +38 -10
- package/dist/wrapper/pty-wrapper.d.ts +12 -1
- package/dist/wrapper/pty-wrapper.js +104 -5
- package/dist/wrapper/relay-pty-orchestrator.d.ts +270 -0
- package/dist/wrapper/relay-pty-orchestrator.js +970 -0
- package/dist/wrapper/shared.d.ts +1 -1
- package/dist/wrapper/shared.js +14 -4
- package/dist/wrapper/tmux-wrapper.d.ts +13 -1
- package/dist/wrapper/tmux-wrapper.js +143 -29
- package/package.json +9 -4
- package/scripts/postinstall.js +101 -11
- package/.trajectories/active/traj_3yx9dy148mge.json +0 -42
- package/.trajectories/agent-relay-322-324.md +0 -17
- package/.trajectories/completed/2026-01/traj_03zupyv1s7b9.json +0 -49
- package/.trajectories/completed/2026-01/traj_03zupyv1s7b9.md +0 -31
- package/.trajectories/completed/2026-01/traj_0zacdjl1g4ht.json +0 -125
- package/.trajectories/completed/2026-01/traj_0zacdjl1g4ht.md +0 -62
- package/.trajectories/completed/2026-01/traj_1dviorhnkcb5.json +0 -65
- package/.trajectories/completed/2026-01/traj_1dviorhnkcb5.md +0 -37
- package/.trajectories/completed/2026-01/traj_1g7yx6qtg4ai.json +0 -49
- package/.trajectories/completed/2026-01/traj_1g7yx6qtg4ai.md +0 -31
- package/.trajectories/completed/2026-01/traj_1k5if5snst2e.json +0 -65
- package/.trajectories/completed/2026-01/traj_1k5if5snst2e.md +0 -37
- package/.trajectories/completed/2026-01/traj_1rp3rges5811.json +0 -49
- package/.trajectories/completed/2026-01/traj_1rp3rges5811.md +0 -31
- package/.trajectories/completed/2026-01/traj_22bhyulruouw.json +0 -113
- package/.trajectories/completed/2026-01/traj_22bhyulruouw.md +0 -57
- package/.trajectories/completed/2026-01/traj_2dao7ddgnta0.json +0 -53
- package/.trajectories/completed/2026-01/traj_2dao7ddgnta0.md +0 -32
- package/.trajectories/completed/2026-01/traj_33iuy72sezbk.json +0 -49
- package/.trajectories/completed/2026-01/traj_33iuy72sezbk.md +0 -31
- package/.trajectories/completed/2026-01/traj_3t0440mjeunc.json +0 -26
- package/.trajectories/completed/2026-01/traj_3t0440mjeunc.md +0 -6
- package/.trajectories/completed/2026-01/traj_45x9494d9xnr.json +0 -47
- package/.trajectories/completed/2026-01/traj_45x9494d9xnr.md +0 -32
- package/.trajectories/completed/2026-01/traj_4aa0bb77s4nh.json +0 -53
- package/.trajectories/completed/2026-01/traj_4aa0bb77s4nh.md +0 -32
- package/.trajectories/completed/2026-01/traj_4qwd4zmhfwp4.json +0 -49
- package/.trajectories/completed/2026-01/traj_4qwd4zmhfwp4.md +0 -31
- package/.trajectories/completed/2026-01/traj_5ammh5qtvklq.json +0 -77
- package/.trajectories/completed/2026-01/traj_5ammh5qtvklq.md +0 -42
- package/.trajectories/completed/2026-01/traj_5lhmzq8rxpqv.json +0 -59
- package/.trajectories/completed/2026-01/traj_5lhmzq8rxpqv.md +0 -33
- package/.trajectories/completed/2026-01/traj_5vr4e9erb1fs.json +0 -53
- package/.trajectories/completed/2026-01/traj_5vr4e9erb1fs.md +0 -32
- package/.trajectories/completed/2026-01/traj_6fgiwdoklvym.json +0 -48
- package/.trajectories/completed/2026-01/traj_6fgiwdoklvym.md +0 -24
- package/.trajectories/completed/2026-01/traj_6mieijqyvaag.json +0 -77
- package/.trajectories/completed/2026-01/traj_6mieijqyvaag.md +0 -42
- package/.trajectories/completed/2026-01/traj_6unwwmgyj5sq.json +0 -109
- package/.trajectories/completed/2026-01/traj_78ffm31jn3uk.json +0 -77
- package/.trajectories/completed/2026-01/traj_78ffm31jn3uk.md +0 -42
- package/.trajectories/completed/2026-01/traj_7ludwvz45veh.json +0 -209
- package/.trajectories/completed/2026-01/traj_7ludwvz45veh.md +0 -97
- package/.trajectories/completed/2026-01/traj_94gnp3k30goq.json +0 -66
- package/.trajectories/completed/2026-01/traj_94gnp3k30goq.md +0 -36
- package/.trajectories/completed/2026-01/traj_9921cuhel0pj.json +0 -48
- package/.trajectories/completed/2026-01/traj_9921cuhel0pj.md +0 -24
- package/.trajectories/completed/2026-01/traj_a0tqx8biw9c4.json +0 -49
- package/.trajectories/completed/2026-01/traj_a0tqx8biw9c4.md +0 -31
- package/.trajectories/completed/2026-01/traj_ajs7zqfux4wc.json +0 -49
- package/.trajectories/completed/2026-01/traj_ajs7zqfux4wc.md +0 -23
- package/.trajectories/completed/2026-01/traj_avqeghu6pz5a.json +0 -40
- package/.trajectories/completed/2026-01/traj_avqeghu6pz5a.md +0 -22
- package/.trajectories/completed/2026-01/traj_ax8uungxz2qh.json +0 -66
- package/.trajectories/completed/2026-01/traj_ax8uungxz2qh.md +0 -36
- package/.trajectories/completed/2026-01/traj_c9izbh2snpzf.json +0 -49
- package/.trajectories/completed/2026-01/traj_c9izbh2snpzf.md +0 -31
- package/.trajectories/completed/2026-01/traj_cpn70dw066nt.json +0 -65
- package/.trajectories/completed/2026-01/traj_cpn70dw066nt.md +0 -37
- package/.trajectories/completed/2026-01/traj_cvtqhlwcq9s0.json +0 -53
- package/.trajectories/completed/2026-01/traj_cvtqhlwcq9s0.md +0 -32
- package/.trajectories/completed/2026-01/traj_cxofprm2m2en.json +0 -49
- package/.trajectories/completed/2026-01/traj_cxofprm2m2en.md +0 -31
- package/.trajectories/completed/2026-01/traj_d2hhz3k0vrhn.json +0 -26
- package/.trajectories/completed/2026-01/traj_d2hhz3k0vrhn.md +0 -6
- package/.trajectories/completed/2026-01/traj_dcsp9s8y01ra.json +0 -121
- package/.trajectories/completed/2026-01/traj_dcsp9s8y01ra.md +0 -29
- package/.trajectories/completed/2026-01/traj_dfuvww9pege5.json +0 -59
- package/.trajectories/completed/2026-01/traj_dfuvww9pege5.md +0 -37
- package/.trajectories/completed/2026-01/traj_erglv2f8t9eh.json +0 -36
- package/.trajectories/completed/2026-01/traj_erglv2f8t9eh.md +0 -21
- package/.trajectories/completed/2026-01/traj_fhx9irlckht6.json +0 -53
- package/.trajectories/completed/2026-01/traj_fhx9irlckht6.md +0 -32
- package/.trajectories/completed/2026-01/traj_fqduidx3xbtp.json +0 -101
- package/.trajectories/completed/2026-01/traj_fqduidx3xbtp.md +0 -52
- package/.trajectories/completed/2026-01/traj_g0fisy9h51mf.json +0 -77
- package/.trajectories/completed/2026-01/traj_g0fisy9h51mf.md +0 -42
- package/.trajectories/completed/2026-01/traj_gjdre5voouod.json +0 -53
- package/.trajectories/completed/2026-01/traj_gjdre5voouod.md +0 -32
- package/.trajectories/completed/2026-01/traj_gtlyqtta3x8l.json +0 -25
- package/.trajectories/completed/2026-01/traj_gtlyqtta3x8l.md +0 -15
- package/.trajectories/completed/2026-01/traj_h4xijiuip3w4.json +0 -101
- package/.trajectories/completed/2026-01/traj_h4xijiuip3w4.md +0 -44
- package/.trajectories/completed/2026-01/traj_he75f24d1xfm.json +0 -101
- package/.trajectories/completed/2026-01/traj_he75f24d1xfm.md +0 -52
- package/.trajectories/completed/2026-01/traj_hf81ey93uz6t.json +0 -49
- package/.trajectories/completed/2026-01/traj_hf81ey93uz6t.md +0 -31
- package/.trajectories/completed/2026-01/traj_hfmki2jr9d4r.json +0 -65
- package/.trajectories/completed/2026-01/traj_hfmki2jr9d4r.md +0 -37
- package/.trajectories/completed/2026-01/traj_hhxte7w4gjjx.json +0 -22
- package/.trajectories/completed/2026-01/traj_hhxte7w4gjjx.md +0 -5
- package/.trajectories/completed/2026-01/traj_hpungyhoj6v5.json +0 -53
- package/.trajectories/completed/2026-01/traj_hpungyhoj6v5.md +0 -32
- package/.trajectories/completed/2026-01/traj_lgtodco7dp1n.json +0 -61
- package/.trajectories/completed/2026-01/traj_lgtodco7dp1n.md +0 -36
- package/.trajectories/completed/2026-01/traj_lq450ly148uw.json +0 -49
- package/.trajectories/completed/2026-01/traj_lq450ly148uw.md +0 -31
- package/.trajectories/completed/2026-01/traj_m2xkjv0w2sq7.json +0 -25
- package/.trajectories/completed/2026-01/traj_m2xkjv0w2sq7.md +0 -15
- package/.trajectories/completed/2026-01/traj_multi_server_arch.md +0 -101
- package/.trajectories/completed/2026-01/traj_noq5zbvnrdvz.json +0 -53
- package/.trajectories/completed/2026-01/traj_noq5zbvnrdvz.md +0 -32
- package/.trajectories/completed/2026-01/traj_ntbs6ppopf46.json +0 -53
- package/.trajectories/completed/2026-01/traj_ntbs6ppopf46.md +0 -32
- package/.trajectories/completed/2026-01/traj_oszg9flv74pk.json +0 -73
- package/.trajectories/completed/2026-01/traj_oszg9flv74pk.md +0 -41
- package/.trajectories/completed/2026-01/traj_ozd98si6a7ns.json +0 -48
- package/.trajectories/completed/2026-01/traj_ozd98si6a7ns.md +0 -24
- package/.trajectories/completed/2026-01/traj_prdza7a5cxp5.json +0 -53
- package/.trajectories/completed/2026-01/traj_prdza7a5cxp5.md +0 -32
- package/.trajectories/completed/2026-01/traj_psd9ob0j2ru3.json +0 -27
- package/.trajectories/completed/2026-01/traj_psd9ob0j2ru3.md +0 -14
- package/.trajectories/completed/2026-01/traj_pulomd3y8cvj.json +0 -77
- package/.trajectories/completed/2026-01/traj_pulomd3y8cvj.md +0 -42
- package/.trajectories/completed/2026-01/traj_qb3twvvywfwi.json +0 -77
- package/.trajectories/completed/2026-01/traj_qb3twvvywfwi.md +0 -42
- package/.trajectories/completed/2026-01/traj_qft54mi7nfor.json +0 -53
- package/.trajectories/completed/2026-01/traj_qft54mi7nfor.md +0 -32
- package/.trajectories/completed/2026-01/traj_qx9uhf8whhxo.json +0 -83
- package/.trajectories/completed/2026-01/traj_qx9uhf8whhxo.md +0 -47
- package/.trajectories/completed/2026-01/traj_rd9toccj18a0.json +0 -59
- package/.trajectories/completed/2026-01/traj_rd9toccj18a0.md +0 -37
- package/.trajectories/completed/2026-01/traj_rsavt0jipi3c.json +0 -109
- package/.trajectories/completed/2026-01/traj_rsavt0jipi3c.md +0 -56
- package/.trajectories/completed/2026-01/traj_rt4fiw3ecp50.json +0 -48
- package/.trajectories/completed/2026-01/traj_rt4fiw3ecp50.md +0 -16
- package/.trajectories/completed/2026-01/traj_st8j35b0hrlc.json +0 -59
- package/.trajectories/completed/2026-01/traj_st8j35b0hrlc.md +0 -37
- package/.trajectories/completed/2026-01/traj_t1yy8m7hbuxp.json +0 -53
- package/.trajectories/completed/2026-01/traj_t1yy8m7hbuxp.md +0 -32
- package/.trajectories/completed/2026-01/traj_tmux_orchestrator_analysis.json +0 -84
- package/.trajectories/completed/2026-01/traj_tmux_orchestrator_analysis.md +0 -109
- package/.trajectories/completed/2026-01/traj_u9n9eqasw16k.json +0 -53
- package/.trajectories/completed/2026-01/traj_u9n9eqasw16k.md +0 -32
- package/.trajectories/completed/2026-01/traj_ub8csuv3lcv4.json +0 -53
- package/.trajectories/completed/2026-01/traj_ub8csuv3lcv4.md +0 -32
- package/.trajectories/completed/2026-01/traj_uc29tlso8i9s.json +0 -186
- package/.trajectories/completed/2026-01/traj_uc29tlso8i9s.md +0 -86
- package/.trajectories/completed/2026-01/traj_ui9b4tqxoa7j.json +0 -77
- package/.trajectories/completed/2026-01/traj_ui9b4tqxoa7j.md +0 -42
- package/.trajectories/completed/2026-01/traj_v87hypnongqx.json +0 -71
- package/.trajectories/completed/2026-01/traj_v87hypnongqx.md +0 -42
- package/.trajectories/completed/2026-01/traj_v9dkdoxylyid.json +0 -89
- package/.trajectories/completed/2026-01/traj_v9dkdoxylyid.md +0 -47
- package/.trajectories/completed/2026-01/traj_wkp2fgzdyinb.json +0 -53
- package/.trajectories/completed/2026-01/traj_wkp2fgzdyinb.md +0 -32
- package/.trajectories/completed/2026-01/traj_x14t8w8rn7xg.json +0 -20
- package/.trajectories/completed/2026-01/traj_x14t8w8rn7xg.md +0 -6
- package/.trajectories/completed/2026-01/traj_x721m1j9rzup.json +0 -113
- package/.trajectories/completed/2026-01/traj_x721m1j9rzup.md +0 -57
- package/.trajectories/completed/2026-01/traj_xjqvmep5ed3h.json +0 -61
- package/.trajectories/completed/2026-01/traj_xjqvmep5ed3h.md +0 -36
- package/.trajectories/completed/2026-01/traj_xnwbznkvv8ua.json +0 -175
- package/.trajectories/completed/2026-01/traj_xnwbznkvv8ua.md +0 -82
- package/.trajectories/completed/2026-01/traj_xy9vifpqet80.json +0 -65
- package/.trajectories/completed/2026-01/traj_xy9vifpqet80.md +0 -37
- package/.trajectories/completed/2026-01/traj_y7aiwijyfmmv.json +0 -49
- package/.trajectories/completed/2026-01/traj_y7aiwijyfmmv.md +0 -31
- package/.trajectories/completed/2026-01/traj_y7n6hfbf7dmg.json +0 -49
- package/.trajectories/completed/2026-01/traj_y7n6hfbf7dmg.md +0 -31
- package/.trajectories/completed/2026-01/traj_ysjc8zaeqtd3.json +0 -47
- package/.trajectories/completed/2026-01/traj_ysjc8zaeqtd3.md +0 -32
- package/.trajectories/completed/2026-01/traj_yvdadtvdgnz3.json +0 -59
- package/.trajectories/completed/2026-01/traj_yvdadtvdgnz3.md +0 -37
- package/.trajectories/completed/2026-01/traj_yvfkwnkdiso2.json +0 -49
- package/.trajectories/completed/2026-01/traj_yvfkwnkdiso2.md +0 -31
- package/.trajectories/completed/2026-01/traj_z0vcw1wrzide.json +0 -53
- package/.trajectories/completed/2026-01/traj_z0vcw1wrzide.md +0 -32
- package/.trajectories/consolidate-settings-panel.md +0 -24
- package/.trajectories/gh-cli-user-token.md +0 -26
- package/.trajectories/index.json +0 -607
|
@@ -0,0 +1,970 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RelayPtyOrchestrator - Orchestrates the relay-pty Rust binary
|
|
3
|
+
*
|
|
4
|
+
* This wrapper spawns the relay-pty binary and communicates via Unix socket.
|
|
5
|
+
* It provides the same interface as PtyWrapper but with improved latency
|
|
6
|
+
* (~550ms vs ~1700ms) by using direct PTY writes instead of tmux send-keys.
|
|
7
|
+
*
|
|
8
|
+
* Architecture:
|
|
9
|
+
* 1. Spawn relay-pty --name {agentName} -- {command} as child process
|
|
10
|
+
* 2. Connect to /tmp/relay-pty-{agentName}.sock for injection
|
|
11
|
+
* 3. Parse stdout for relay commands (relay-pty echoes all output)
|
|
12
|
+
* 4. Translate SEND envelopes → inject messages via socket
|
|
13
|
+
*
|
|
14
|
+
* @see docs/RUST_WRAPPER_DESIGN.md for protocol details
|
|
15
|
+
*/
|
|
16
|
+
import { spawn } from 'node:child_process';
|
|
17
|
+
import { createConnection } from 'node:net';
|
|
18
|
+
import { join, dirname } from 'node:path';
|
|
19
|
+
import { existsSync, unlinkSync } from 'node:fs';
|
|
20
|
+
import { getProjectPaths } from '../utils/project-namespace.js';
|
|
21
|
+
import { fileURLToPath } from 'node:url';
|
|
22
|
+
// Get the directory where this module is located
|
|
23
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
24
|
+
const __dirname = dirname(__filename);
|
|
25
|
+
import { BaseWrapper } from './base-wrapper.js';
|
|
26
|
+
import { parseSummaryWithDetails, parseSessionEndFromOutput } from './parser.js';
|
|
27
|
+
import { stripAnsi, sleep, buildInjectionString, INJECTION_CONSTANTS, } from './shared.js';
|
|
28
|
+
/**
|
|
29
|
+
* Orchestrator for relay-pty Rust binary
|
|
30
|
+
*
|
|
31
|
+
* Extends BaseWrapper to provide the same interface as PtyWrapper
|
|
32
|
+
* but uses the relay-pty binary for improved injection reliability.
|
|
33
|
+
*/
|
|
34
|
+
export class RelayPtyOrchestrator extends BaseWrapper {
|
|
35
|
+
config;
|
|
36
|
+
// Process management
|
|
37
|
+
relayPtyProcess;
|
|
38
|
+
socketPath;
|
|
39
|
+
_logPath;
|
|
40
|
+
_outboxPath;
|
|
41
|
+
socket;
|
|
42
|
+
socketConnected = false;
|
|
43
|
+
// Output buffering
|
|
44
|
+
outputBuffer = '';
|
|
45
|
+
rawBuffer = '';
|
|
46
|
+
lastParsedLength = 0;
|
|
47
|
+
// Interactive mode (show output to terminal)
|
|
48
|
+
isInteractive = false;
|
|
49
|
+
// Injection state
|
|
50
|
+
pendingInjections = new Map();
|
|
51
|
+
backpressureActive = false;
|
|
52
|
+
readyForMessages = false;
|
|
53
|
+
// Unread message indicator state
|
|
54
|
+
lastUnreadIndicatorTime = 0;
|
|
55
|
+
UNREAD_INDICATOR_COOLDOWN_MS = 5000; // Don't spam indicators
|
|
56
|
+
// Note: sessionEndProcessed and lastSummaryRawContent are inherited from BaseWrapper
|
|
57
|
+
constructor(config) {
|
|
58
|
+
super(config);
|
|
59
|
+
this.config = config;
|
|
60
|
+
this.socketPath = `/tmp/relay-pty-${config.name}.sock`;
|
|
61
|
+
// Generate log path using same project paths as daemon
|
|
62
|
+
// Use cwd from config if specified, otherwise detect from current directory
|
|
63
|
+
const paths = getProjectPaths(config.cwd);
|
|
64
|
+
this._logPath = join(paths.teamDir, 'worker-logs', `${config.name}.log`);
|
|
65
|
+
// Outbox for file-based relay messages (agent writes here)
|
|
66
|
+
// Use fixed /tmp path so agents can find it without configuration
|
|
67
|
+
this._outboxPath = `/tmp/relay-outbox/${config.name}`;
|
|
68
|
+
// Check if we're running interactively (stdin is a TTY)
|
|
69
|
+
this.isInteractive = process.stdin.isTTY === true;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Debug log - only outputs when debug is enabled
|
|
73
|
+
*/
|
|
74
|
+
log(message) {
|
|
75
|
+
if (this.config.debug) {
|
|
76
|
+
console.log(`[relay-pty-orchestrator:${this.config.name}] ${message}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Error log - always outputs (errors are important)
|
|
81
|
+
*/
|
|
82
|
+
logError(message) {
|
|
83
|
+
console.error(`[relay-pty-orchestrator:${this.config.name}] ERROR: ${message}`);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get the outbox path for this agent (for documentation purposes)
|
|
87
|
+
*/
|
|
88
|
+
get outboxPath() {
|
|
89
|
+
return this._outboxPath;
|
|
90
|
+
}
|
|
91
|
+
// =========================================================================
|
|
92
|
+
// Abstract method implementations (required by BaseWrapper)
|
|
93
|
+
// =========================================================================
|
|
94
|
+
/**
|
|
95
|
+
* Start the relay-pty process and connect to socket
|
|
96
|
+
*/
|
|
97
|
+
async start() {
|
|
98
|
+
if (this.running)
|
|
99
|
+
return;
|
|
100
|
+
this.log(` Starting...`);
|
|
101
|
+
// Clean up any stale socket from previous crashed process
|
|
102
|
+
try {
|
|
103
|
+
if (existsSync(this.socketPath)) {
|
|
104
|
+
this.log(` Removing stale socket: ${this.socketPath}`);
|
|
105
|
+
unlinkSync(this.socketPath);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
this.logError(` Failed to clean up socket: ${err.message}`);
|
|
110
|
+
}
|
|
111
|
+
// Find relay-pty binary
|
|
112
|
+
const binaryPath = this.findRelayPtyBinary();
|
|
113
|
+
if (!binaryPath) {
|
|
114
|
+
throw new Error('relay-pty binary not found. Build with: cd relay-pty && cargo build --release');
|
|
115
|
+
}
|
|
116
|
+
this.log(` Using binary: ${binaryPath}`);
|
|
117
|
+
// Connect to relay daemon first
|
|
118
|
+
try {
|
|
119
|
+
await this.client.connect();
|
|
120
|
+
this.log(` Relay daemon connected`);
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
this.logError(` Relay connect failed: ${err.message}`);
|
|
124
|
+
}
|
|
125
|
+
// Spawn relay-pty process
|
|
126
|
+
await this.spawnRelayPty(binaryPath);
|
|
127
|
+
// Wait for socket to become available and connect
|
|
128
|
+
await this.connectToSocket();
|
|
129
|
+
this.running = true;
|
|
130
|
+
this.readyForMessages = true;
|
|
131
|
+
this.log(` Ready for messages`);
|
|
132
|
+
this.log(` Socket connected: ${this.socketConnected}`);
|
|
133
|
+
this.log(` Relay client state: ${this.client.state}`);
|
|
134
|
+
// Process any queued messages
|
|
135
|
+
this.processMessageQueue();
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Stop the relay-pty process gracefully
|
|
139
|
+
*/
|
|
140
|
+
async stop() {
|
|
141
|
+
if (!this.running)
|
|
142
|
+
return;
|
|
143
|
+
this.running = false;
|
|
144
|
+
this.log(` Stopping...`);
|
|
145
|
+
// Send shutdown command via socket
|
|
146
|
+
if (this.socket && this.socketConnected) {
|
|
147
|
+
try {
|
|
148
|
+
await this.sendSocketRequest({ type: 'shutdown' });
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// Ignore errors during shutdown
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Close socket
|
|
155
|
+
this.disconnectSocket();
|
|
156
|
+
// Kill process if still running
|
|
157
|
+
if (this.relayPtyProcess && !this.relayPtyProcess.killed) {
|
|
158
|
+
this.relayPtyProcess.kill('SIGTERM');
|
|
159
|
+
// Force kill after timeout
|
|
160
|
+
await Promise.race([
|
|
161
|
+
new Promise((resolve) => {
|
|
162
|
+
this.relayPtyProcess?.on('exit', () => resolve());
|
|
163
|
+
}),
|
|
164
|
+
sleep(5000).then(() => {
|
|
165
|
+
if (this.relayPtyProcess && !this.relayPtyProcess.killed) {
|
|
166
|
+
this.relayPtyProcess.kill('SIGKILL');
|
|
167
|
+
}
|
|
168
|
+
}),
|
|
169
|
+
]);
|
|
170
|
+
}
|
|
171
|
+
// Cleanup relay client
|
|
172
|
+
this.destroyClient();
|
|
173
|
+
// Clean up socket file
|
|
174
|
+
try {
|
|
175
|
+
if (existsSync(this.socketPath)) {
|
|
176
|
+
unlinkSync(this.socketPath);
|
|
177
|
+
this.log(` Cleaned up socket: ${this.socketPath}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
this.logError(` Failed to clean up socket: ${err.message}`);
|
|
182
|
+
}
|
|
183
|
+
this.log(` Stopped`);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Inject content into the agent via socket
|
|
187
|
+
*/
|
|
188
|
+
async performInjection(content) {
|
|
189
|
+
// This is called by BaseWrapper but we handle injection differently
|
|
190
|
+
// via the socket protocol in processMessageQueue
|
|
191
|
+
throw new Error('Use injectMessage() instead of performInjection()');
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Get cleaned output for parsing
|
|
195
|
+
*/
|
|
196
|
+
getCleanOutput() {
|
|
197
|
+
return stripAnsi(this.rawBuffer);
|
|
198
|
+
}
|
|
199
|
+
// =========================================================================
|
|
200
|
+
// Process management
|
|
201
|
+
// =========================================================================
|
|
202
|
+
/**
|
|
203
|
+
* Find the relay-pty binary
|
|
204
|
+
*/
|
|
205
|
+
findRelayPtyBinary() {
|
|
206
|
+
// Check config path first
|
|
207
|
+
if (this.config.relayPtyPath && existsSync(this.config.relayPtyPath)) {
|
|
208
|
+
return this.config.relayPtyPath;
|
|
209
|
+
}
|
|
210
|
+
// Get the package root (two levels up from dist/wrapper/)
|
|
211
|
+
const packageRoot = join(__dirname, '..', '..');
|
|
212
|
+
// Check common locations (ordered by priority)
|
|
213
|
+
const candidates = [
|
|
214
|
+
// Primary: installed by postinstall from platform-specific binary
|
|
215
|
+
join(packageRoot, 'bin', 'relay-pty'),
|
|
216
|
+
// Development: local Rust build
|
|
217
|
+
join(packageRoot, 'relay-pty', 'target', 'release', 'relay-pty'),
|
|
218
|
+
join(packageRoot, 'relay-pty', 'target', 'debug', 'relay-pty'),
|
|
219
|
+
// Local build in cwd (for development)
|
|
220
|
+
join(process.cwd(), 'relay-pty', 'target', 'release', 'relay-pty'),
|
|
221
|
+
join(process.cwd(), 'relay-pty', 'target', 'debug', 'relay-pty'),
|
|
222
|
+
// Installed globally
|
|
223
|
+
'/usr/local/bin/relay-pty',
|
|
224
|
+
// In node_modules (when installed as dependency)
|
|
225
|
+
join(process.cwd(), 'node_modules', 'agent-relay', 'bin', 'relay-pty'),
|
|
226
|
+
join(process.cwd(), 'node_modules', '.bin', 'relay-pty'),
|
|
227
|
+
];
|
|
228
|
+
for (const candidate of candidates) {
|
|
229
|
+
if (existsSync(candidate)) {
|
|
230
|
+
return candidate;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Spawn the relay-pty process
|
|
237
|
+
*/
|
|
238
|
+
async spawnRelayPty(binaryPath) {
|
|
239
|
+
// Get terminal dimensions for proper rendering
|
|
240
|
+
const rows = process.stdout.rows || 24;
|
|
241
|
+
const cols = process.stdout.columns || 80;
|
|
242
|
+
const args = [
|
|
243
|
+
'--name', this.config.name,
|
|
244
|
+
'--socket', this.socketPath,
|
|
245
|
+
'--idle-timeout', String(this.config.idleBeforeInjectMs ?? 500),
|
|
246
|
+
'--json-output', // Enable Rust parsing output
|
|
247
|
+
'--rows', String(rows),
|
|
248
|
+
'--cols', String(cols),
|
|
249
|
+
'--log-level', 'warn', // Only show warnings and errors
|
|
250
|
+
'--log-file', this._logPath, // Enable output logging
|
|
251
|
+
'--outbox', this._outboxPath, // Enable file-based relay messages
|
|
252
|
+
'--', this.config.command,
|
|
253
|
+
...(this.config.args ?? []),
|
|
254
|
+
];
|
|
255
|
+
this.log(` Spawning: ${binaryPath} ${args.join(' ')}`);
|
|
256
|
+
// For interactive mode, let Rust directly inherit stdin/stdout from the terminal
|
|
257
|
+
// This is more robust than manual forwarding through pipes
|
|
258
|
+
// We still pipe stderr to capture JSON parsed commands
|
|
259
|
+
const stdio = this.isInteractive
|
|
260
|
+
? ['inherit', 'inherit', 'pipe'] // Rust handles terminal directly
|
|
261
|
+
: ['pipe', 'pipe', 'pipe']; // Headless mode - we handle I/O
|
|
262
|
+
const proc = spawn(binaryPath, args, {
|
|
263
|
+
cwd: this.config.cwd ?? process.cwd(),
|
|
264
|
+
env: {
|
|
265
|
+
...process.env,
|
|
266
|
+
...this.config.env,
|
|
267
|
+
AGENT_RELAY_NAME: this.config.name,
|
|
268
|
+
TERM: 'xterm-256color',
|
|
269
|
+
},
|
|
270
|
+
stdio,
|
|
271
|
+
});
|
|
272
|
+
this.relayPtyProcess = proc;
|
|
273
|
+
// Handle stdout (agent output) - only in headless mode
|
|
274
|
+
if (!this.isInteractive && proc.stdout) {
|
|
275
|
+
proc.stdout.on('data', (data) => {
|
|
276
|
+
const text = data.toString();
|
|
277
|
+
this.handleOutput(text);
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
// Handle stderr (relay-pty logs and JSON output) - always needed
|
|
281
|
+
if (proc.stderr) {
|
|
282
|
+
proc.stderr.on('data', (data) => {
|
|
283
|
+
const text = data.toString();
|
|
284
|
+
this.handleStderr(text);
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
// Handle exit
|
|
288
|
+
proc.on('exit', (code, signal) => {
|
|
289
|
+
const exitCode = code ?? (signal === 'SIGKILL' ? 137 : 1);
|
|
290
|
+
this.log(` Process exited: code=${exitCode} signal=${signal}`);
|
|
291
|
+
this.running = false;
|
|
292
|
+
this.emit('exit', exitCode);
|
|
293
|
+
this.config.onExit?.(exitCode);
|
|
294
|
+
});
|
|
295
|
+
// Handle error
|
|
296
|
+
proc.on('error', (err) => {
|
|
297
|
+
this.logError(` Process error: ${err.message}`);
|
|
298
|
+
this.emit('error', err);
|
|
299
|
+
});
|
|
300
|
+
// Wait for process to start
|
|
301
|
+
await sleep(500);
|
|
302
|
+
if (proc.exitCode !== null) {
|
|
303
|
+
throw new Error(`relay-pty exited immediately with code ${proc.exitCode}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Handle output from relay-pty stdout (headless mode only)
|
|
308
|
+
* In interactive mode, stdout goes directly to terminal via inherited stdio
|
|
309
|
+
*/
|
|
310
|
+
handleOutput(data) {
|
|
311
|
+
this.rawBuffer += data;
|
|
312
|
+
this.outputBuffer += data;
|
|
313
|
+
// Feed to idle detector
|
|
314
|
+
this.feedIdleDetectorOutput(data);
|
|
315
|
+
// Check for unread messages and append indicator if needed
|
|
316
|
+
const indicator = this.formatUnreadIndicator();
|
|
317
|
+
const outputWithIndicator = indicator ? data + indicator : data;
|
|
318
|
+
// Emit output event (with indicator if present)
|
|
319
|
+
this.emit('output', outputWithIndicator);
|
|
320
|
+
// Stream to daemon if configured
|
|
321
|
+
if (this.config.streamLogs !== false && this.client.state === 'READY') {
|
|
322
|
+
this.client.sendLog(outputWithIndicator);
|
|
323
|
+
}
|
|
324
|
+
// Parse for relay commands
|
|
325
|
+
this.parseRelayCommands();
|
|
326
|
+
// Check for summary and session end
|
|
327
|
+
const cleanContent = stripAnsi(this.rawBuffer);
|
|
328
|
+
this.checkForSummary(cleanContent);
|
|
329
|
+
this.checkForSessionEnd(cleanContent);
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Format an unread message indicator if there are pending messages.
|
|
333
|
+
* Returns empty string if no pending messages or within cooldown period.
|
|
334
|
+
*
|
|
335
|
+
* Example output:
|
|
336
|
+
* ───────────────────────────
|
|
337
|
+
* 📬 2 unread messages (from: Alice, Bob)
|
|
338
|
+
*/
|
|
339
|
+
formatUnreadIndicator() {
|
|
340
|
+
const queueLength = this.messageQueue.length;
|
|
341
|
+
if (queueLength === 0) {
|
|
342
|
+
return '';
|
|
343
|
+
}
|
|
344
|
+
// Check cooldown to avoid spamming
|
|
345
|
+
const now = Date.now();
|
|
346
|
+
if (now - this.lastUnreadIndicatorTime < this.UNREAD_INDICATOR_COOLDOWN_MS) {
|
|
347
|
+
return '';
|
|
348
|
+
}
|
|
349
|
+
this.lastUnreadIndicatorTime = now;
|
|
350
|
+
// Collect unique sender names
|
|
351
|
+
const senders = [...new Set(this.messageQueue.map(m => m.from))];
|
|
352
|
+
const senderList = senders.slice(0, 3).join(', ');
|
|
353
|
+
const moreCount = senders.length > 3 ? ` +${senders.length - 3} more` : '';
|
|
354
|
+
const line = '─'.repeat(27);
|
|
355
|
+
const messageWord = queueLength === 1 ? 'message' : 'messages';
|
|
356
|
+
return `\n${line}\n📬 ${queueLength} unread ${messageWord} (from: ${senderList}${moreCount})\n`;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Handle stderr from relay-pty (logs and JSON parsed commands)
|
|
360
|
+
*/
|
|
361
|
+
handleStderr(data) {
|
|
362
|
+
// relay-pty outputs JSON parsed commands to stderr with --json-output
|
|
363
|
+
const lines = data.split('\n').filter(l => l.trim());
|
|
364
|
+
for (const line of lines) {
|
|
365
|
+
if (line.startsWith('{')) {
|
|
366
|
+
// JSON output - parsed relay command from Rust
|
|
367
|
+
try {
|
|
368
|
+
const parsed = JSON.parse(line);
|
|
369
|
+
if (parsed.type === 'relay_command' && parsed.kind) {
|
|
370
|
+
this.log(`Rust parsed [${parsed.kind}]: ${parsed.from} -> ${parsed.to}`);
|
|
371
|
+
this.handleRustParsedCommand(parsed);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
// Not JSON, just log (only in debug mode)
|
|
376
|
+
if (this.config.debug) {
|
|
377
|
+
console.error(`[relay-pty:${this.config.name}] ${line}`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
// Non-JSON stderr - only show in debug mode (logs, info messages)
|
|
383
|
+
if (this.config.debug) {
|
|
384
|
+
console.error(`[relay-pty:${this.config.name}] ${line}`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Handle a parsed command from Rust relay-pty
|
|
391
|
+
* Rust outputs structured JSON with 'kind' field: "message", "spawn", "release"
|
|
392
|
+
*/
|
|
393
|
+
handleRustParsedCommand(parsed) {
|
|
394
|
+
switch (parsed.kind) {
|
|
395
|
+
case 'spawn':
|
|
396
|
+
if (parsed.spawn_name && parsed.spawn_cli) {
|
|
397
|
+
this.log(` Spawn detected: ${parsed.spawn_name} (${parsed.spawn_cli})`);
|
|
398
|
+
this.handleSpawnCommand(parsed.spawn_name, parsed.spawn_cli, parsed.spawn_task || '');
|
|
399
|
+
}
|
|
400
|
+
break;
|
|
401
|
+
case 'release':
|
|
402
|
+
if (parsed.release_name) {
|
|
403
|
+
this.log(`Release: ${parsed.release_name}`);
|
|
404
|
+
this.handleReleaseCommand(parsed.release_name);
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
this.logError(`Missing release_name in parsed command: ${JSON.stringify(parsed)}`);
|
|
408
|
+
}
|
|
409
|
+
break;
|
|
410
|
+
case 'message':
|
|
411
|
+
default:
|
|
412
|
+
this.sendRelayCommand({
|
|
413
|
+
to: parsed.to,
|
|
414
|
+
kind: 'message',
|
|
415
|
+
body: parsed.body,
|
|
416
|
+
thread: parsed.thread,
|
|
417
|
+
raw: parsed.raw,
|
|
418
|
+
});
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Handle spawn command
|
|
424
|
+
*/
|
|
425
|
+
handleSpawnCommand(name, cli, task) {
|
|
426
|
+
const key = `spawn:${name}:${cli}`;
|
|
427
|
+
if (this.processedSpawnCommands.has(key)) {
|
|
428
|
+
this.log(` Spawn already processed: ${key}`);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
this.processedSpawnCommands.add(key);
|
|
432
|
+
this.log(` Spawn: ${name} (${cli})`);
|
|
433
|
+
this.log(` dashboardPort=${this.config.dashboardPort}, onSpawn=${!!this.config.onSpawn}`);
|
|
434
|
+
// Try dashboard API first, fall back to callback
|
|
435
|
+
if (this.config.dashboardPort) {
|
|
436
|
+
this.log(` Calling dashboard API at port ${this.config.dashboardPort}`);
|
|
437
|
+
this.spawnViaDashboardApi(name, cli, task)
|
|
438
|
+
.then(() => {
|
|
439
|
+
this.log(` Dashboard spawn succeeded for ${name}`);
|
|
440
|
+
})
|
|
441
|
+
.catch(err => {
|
|
442
|
+
this.logError(` Dashboard spawn failed: ${err.message}`);
|
|
443
|
+
if (this.config.onSpawn) {
|
|
444
|
+
this.log(` Falling back to onSpawn callback`);
|
|
445
|
+
this.config.onSpawn(name, cli, task);
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
else if (this.config.onSpawn) {
|
|
450
|
+
this.log(` Using onSpawn callback directly`);
|
|
451
|
+
this.config.onSpawn(name, cli, task);
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
this.logError(` No spawn mechanism available!`);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Handle release command
|
|
459
|
+
*/
|
|
460
|
+
handleReleaseCommand(name) {
|
|
461
|
+
const key = `release:${name}`;
|
|
462
|
+
if (this.processedReleaseCommands.has(key)) {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
this.processedReleaseCommands.add(key);
|
|
466
|
+
this.log(` Release: ${name}`);
|
|
467
|
+
// Try dashboard API first, fall back to callback
|
|
468
|
+
if (this.config.dashboardPort) {
|
|
469
|
+
this.releaseViaDashboardApi(name).catch(err => {
|
|
470
|
+
this.logError(` Dashboard release failed: ${err.message}`);
|
|
471
|
+
this.config.onRelease?.(name);
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
else if (this.config.onRelease) {
|
|
475
|
+
this.config.onRelease(name);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Spawn agent via dashboard API
|
|
480
|
+
*/
|
|
481
|
+
async spawnViaDashboardApi(name, cli, task) {
|
|
482
|
+
const response = await fetch(`http://localhost:${this.config.dashboardPort}/api/spawn`, {
|
|
483
|
+
method: 'POST',
|
|
484
|
+
headers: { 'Content-Type': 'application/json' },
|
|
485
|
+
body: JSON.stringify({ name, cli, task }),
|
|
486
|
+
});
|
|
487
|
+
if (!response.ok) {
|
|
488
|
+
throw new Error(`HTTP ${response.status}`);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Release agent via dashboard API
|
|
493
|
+
*/
|
|
494
|
+
async releaseViaDashboardApi(name) {
|
|
495
|
+
const response = await fetch(`http://localhost:${this.config.dashboardPort}/api/spawned/${encodeURIComponent(name)}`, {
|
|
496
|
+
method: 'DELETE',
|
|
497
|
+
});
|
|
498
|
+
if (!response.ok) {
|
|
499
|
+
const body = await response.json().catch(() => ({ error: 'Unknown' }));
|
|
500
|
+
throw new Error(`HTTP ${response.status}: ${body.error || 'Unknown error'}`);
|
|
501
|
+
}
|
|
502
|
+
this.log(`Released ${name} via dashboard API`);
|
|
503
|
+
}
|
|
504
|
+
// =========================================================================
|
|
505
|
+
// Socket communication
|
|
506
|
+
// =========================================================================
|
|
507
|
+
/**
|
|
508
|
+
* Connect to the relay-pty socket
|
|
509
|
+
*/
|
|
510
|
+
async connectToSocket() {
|
|
511
|
+
const timeout = this.config.socketConnectTimeoutMs ?? 5000;
|
|
512
|
+
const maxAttempts = this.config.socketReconnectAttempts ?? 3;
|
|
513
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
514
|
+
try {
|
|
515
|
+
await this.attemptSocketConnection(timeout);
|
|
516
|
+
this.log(` Socket connected`);
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
catch (err) {
|
|
520
|
+
this.logError(` Socket connect attempt ${attempt}/${maxAttempts} failed: ${err.message}`);
|
|
521
|
+
if (attempt < maxAttempts) {
|
|
522
|
+
await sleep(1000 * attempt); // Exponential backoff
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
throw new Error(`Failed to connect to socket after ${maxAttempts} attempts`);
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Attempt a single socket connection
|
|
530
|
+
*/
|
|
531
|
+
attemptSocketConnection(timeout) {
|
|
532
|
+
return new Promise((resolve, reject) => {
|
|
533
|
+
const timer = setTimeout(() => {
|
|
534
|
+
reject(new Error('Socket connection timeout'));
|
|
535
|
+
}, timeout);
|
|
536
|
+
this.socket = createConnection(this.socketPath, () => {
|
|
537
|
+
clearTimeout(timer);
|
|
538
|
+
this.socketConnected = true;
|
|
539
|
+
resolve();
|
|
540
|
+
});
|
|
541
|
+
this.socket.on('error', (err) => {
|
|
542
|
+
clearTimeout(timer);
|
|
543
|
+
this.socketConnected = false;
|
|
544
|
+
reject(err);
|
|
545
|
+
});
|
|
546
|
+
this.socket.on('close', () => {
|
|
547
|
+
this.socketConnected = false;
|
|
548
|
+
this.log(` Socket closed`);
|
|
549
|
+
});
|
|
550
|
+
// Handle incoming data (responses)
|
|
551
|
+
let buffer = '';
|
|
552
|
+
this.socket.on('data', (data) => {
|
|
553
|
+
buffer += data.toString();
|
|
554
|
+
// Process complete lines
|
|
555
|
+
const lines = buffer.split('\n');
|
|
556
|
+
buffer = lines.pop() ?? ''; // Keep incomplete line in buffer
|
|
557
|
+
for (const line of lines) {
|
|
558
|
+
if (line.trim()) {
|
|
559
|
+
this.handleSocketResponse(line);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Disconnect from socket
|
|
567
|
+
*/
|
|
568
|
+
disconnectSocket() {
|
|
569
|
+
if (this.socket) {
|
|
570
|
+
this.socket.destroy();
|
|
571
|
+
this.socket = undefined;
|
|
572
|
+
this.socketConnected = false;
|
|
573
|
+
}
|
|
574
|
+
// Reject all pending injections
|
|
575
|
+
for (const [id, pending] of this.pendingInjections) {
|
|
576
|
+
clearTimeout(pending.timeout);
|
|
577
|
+
pending.reject(new Error('Socket disconnected'));
|
|
578
|
+
}
|
|
579
|
+
this.pendingInjections.clear();
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Send a request to the socket and optionally wait for response
|
|
583
|
+
*/
|
|
584
|
+
sendSocketRequest(request) {
|
|
585
|
+
return new Promise((resolve, reject) => {
|
|
586
|
+
if (!this.socket || !this.socketConnected) {
|
|
587
|
+
reject(new Error('Socket not connected'));
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
const json = JSON.stringify(request) + '\n';
|
|
591
|
+
this.socket.write(json, (err) => {
|
|
592
|
+
if (err) {
|
|
593
|
+
reject(err);
|
|
594
|
+
}
|
|
595
|
+
else {
|
|
596
|
+
resolve();
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Handle a response from the socket
|
|
603
|
+
*/
|
|
604
|
+
handleSocketResponse(line) {
|
|
605
|
+
try {
|
|
606
|
+
const response = JSON.parse(line);
|
|
607
|
+
switch (response.type) {
|
|
608
|
+
case 'inject_result':
|
|
609
|
+
this.handleInjectResult(response);
|
|
610
|
+
break;
|
|
611
|
+
case 'status':
|
|
612
|
+
// Status responses are typically requested explicitly
|
|
613
|
+
this.log(` Status: idle=${response.agent_idle} queue=${response.queue_length}`);
|
|
614
|
+
break;
|
|
615
|
+
case 'backpressure':
|
|
616
|
+
this.handleBackpressure(response);
|
|
617
|
+
break;
|
|
618
|
+
case 'error':
|
|
619
|
+
this.logError(` Socket error: ${response.message}`);
|
|
620
|
+
break;
|
|
621
|
+
case 'shutdown_ack':
|
|
622
|
+
this.log(` Shutdown acknowledged`);
|
|
623
|
+
break;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
catch (err) {
|
|
627
|
+
this.logError(` Failed to parse socket response: ${err.message}`);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Handle injection result response
|
|
632
|
+
*/
|
|
633
|
+
handleInjectResult(response) {
|
|
634
|
+
const pending = this.pendingInjections.get(response.id);
|
|
635
|
+
if (!pending) {
|
|
636
|
+
// Response for unknown message - might be from a previous session
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
if (response.status === 'delivered') {
|
|
640
|
+
clearTimeout(pending.timeout);
|
|
641
|
+
this.pendingInjections.delete(response.id);
|
|
642
|
+
pending.resolve(true);
|
|
643
|
+
this.log(` Message ${response.id.substring(0, 8)} delivered`);
|
|
644
|
+
}
|
|
645
|
+
else if (response.status === 'failed') {
|
|
646
|
+
clearTimeout(pending.timeout);
|
|
647
|
+
this.pendingInjections.delete(response.id);
|
|
648
|
+
pending.resolve(false);
|
|
649
|
+
this.logError(` Message ${response.id.substring(0, 8)} failed: ${response.error}`);
|
|
650
|
+
this.emit('injection-failed', {
|
|
651
|
+
messageId: response.id,
|
|
652
|
+
from: 'unknown',
|
|
653
|
+
error: response.error ?? 'Unknown error',
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
// queued/injecting are intermediate states - wait for final status
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Handle backpressure notification
|
|
660
|
+
*/
|
|
661
|
+
handleBackpressure(response) {
|
|
662
|
+
const wasActive = this.backpressureActive;
|
|
663
|
+
this.backpressureActive = !response.accept;
|
|
664
|
+
if (this.backpressureActive !== wasActive) {
|
|
665
|
+
this.log(` Backpressure: ${this.backpressureActive ? 'ACTIVE' : 'cleared'} (queue=${response.queue_length})`);
|
|
666
|
+
this.emit('backpressure', { queueLength: response.queue_length, accept: response.accept });
|
|
667
|
+
// Resume processing if backpressure cleared
|
|
668
|
+
if (!this.backpressureActive) {
|
|
669
|
+
this.processMessageQueue();
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
// =========================================================================
|
|
674
|
+
// Message handling
|
|
675
|
+
// =========================================================================
|
|
676
|
+
/**
|
|
677
|
+
* Inject a message into the agent via socket
|
|
678
|
+
*/
|
|
679
|
+
async injectMessage(msg) {
|
|
680
|
+
this.log(` === INJECT START: ${msg.messageId.substring(0, 8)} from ${msg.from} ===`);
|
|
681
|
+
if (!this.socket || !this.socketConnected) {
|
|
682
|
+
this.logError(` Cannot inject - socket not connected`);
|
|
683
|
+
return false;
|
|
684
|
+
}
|
|
685
|
+
// Build injection content
|
|
686
|
+
const content = buildInjectionString(msg);
|
|
687
|
+
this.log(` Injection content (${content.length} bytes): ${content.substring(0, 100)}...`);
|
|
688
|
+
// Create request
|
|
689
|
+
const request = {
|
|
690
|
+
type: 'inject',
|
|
691
|
+
id: msg.messageId,
|
|
692
|
+
from: msg.from,
|
|
693
|
+
body: content,
|
|
694
|
+
priority: msg.importance ?? 0,
|
|
695
|
+
};
|
|
696
|
+
this.log(` Sending inject request to socket...`);
|
|
697
|
+
// Create promise for result
|
|
698
|
+
return new Promise((resolve, reject) => {
|
|
699
|
+
const timeout = setTimeout(() => {
|
|
700
|
+
this.logError(` Inject timeout for ${msg.messageId.substring(0, 8)}`);
|
|
701
|
+
this.pendingInjections.delete(msg.messageId);
|
|
702
|
+
resolve(false); // Timeout = failure
|
|
703
|
+
}, 30000); // 30 second timeout for injection
|
|
704
|
+
this.pendingInjections.set(msg.messageId, { resolve, reject, timeout });
|
|
705
|
+
// Send request
|
|
706
|
+
this.sendSocketRequest(request)
|
|
707
|
+
.then(() => {
|
|
708
|
+
this.log(` Socket request sent successfully`);
|
|
709
|
+
})
|
|
710
|
+
.catch((err) => {
|
|
711
|
+
this.logError(` Socket request failed: ${err.message}`);
|
|
712
|
+
clearTimeout(timeout);
|
|
713
|
+
this.pendingInjections.delete(msg.messageId);
|
|
714
|
+
resolve(false);
|
|
715
|
+
});
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Process queued messages
|
|
720
|
+
*/
|
|
721
|
+
async processMessageQueue() {
|
|
722
|
+
if (!this.readyForMessages || this.backpressureActive || this.isInjecting) {
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
if (this.messageQueue.length === 0) {
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
this.isInjecting = true;
|
|
729
|
+
const msg = this.messageQueue.shift();
|
|
730
|
+
const bodyPreview = msg.body.substring(0, 50).replace(/\n/g, '\\n');
|
|
731
|
+
this.log(` Processing message from ${msg.from}: "${bodyPreview}..." (remaining=${this.messageQueue.length})`);
|
|
732
|
+
try {
|
|
733
|
+
const success = await this.injectMessage(msg);
|
|
734
|
+
if (!success) {
|
|
735
|
+
this.logError(` Injection failed for message ${msg.messageId.substring(0, 8)}`);
|
|
736
|
+
this.injectionMetrics.failed++;
|
|
737
|
+
this.config.onInjectionFailed?.(msg.messageId, 'Injection failed');
|
|
738
|
+
}
|
|
739
|
+
else {
|
|
740
|
+
this.injectionMetrics.successFirstTry++;
|
|
741
|
+
}
|
|
742
|
+
this.injectionMetrics.total++;
|
|
743
|
+
}
|
|
744
|
+
catch (err) {
|
|
745
|
+
this.logError(` Injection error: ${err.message}`);
|
|
746
|
+
this.injectionMetrics.failed++;
|
|
747
|
+
this.injectionMetrics.total++;
|
|
748
|
+
}
|
|
749
|
+
finally {
|
|
750
|
+
this.isInjecting = false;
|
|
751
|
+
// Process next message after delay
|
|
752
|
+
if (this.messageQueue.length > 0 && !this.backpressureActive) {
|
|
753
|
+
setTimeout(() => this.processMessageQueue(), INJECTION_CONSTANTS.QUEUE_PROCESS_DELAY_MS);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Override handleIncomingMessage to trigger queue processing
|
|
759
|
+
*/
|
|
760
|
+
handleIncomingMessage(from, payload, messageId, meta, originalTo) {
|
|
761
|
+
this.log(` === MESSAGE RECEIVED: ${messageId.substring(0, 8)} from ${from} ===`);
|
|
762
|
+
this.log(` Body preview: ${payload.body?.substring(0, 100) ?? '(no body)'}...`);
|
|
763
|
+
super.handleIncomingMessage(from, payload, messageId, meta, originalTo);
|
|
764
|
+
this.log(` Queue length after add: ${this.messageQueue.length}`);
|
|
765
|
+
this.processMessageQueue();
|
|
766
|
+
}
|
|
767
|
+
// =========================================================================
|
|
768
|
+
// Output parsing
|
|
769
|
+
// =========================================================================
|
|
770
|
+
/**
|
|
771
|
+
* Parse relay commands from output
|
|
772
|
+
*/
|
|
773
|
+
parseRelayCommands() {
|
|
774
|
+
const cleanContent = stripAnsi(this.rawBuffer);
|
|
775
|
+
if (cleanContent.length <= this.lastParsedLength) {
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
// Parse new content with lookback for fenced messages
|
|
779
|
+
const lookbackStart = Math.max(0, this.lastParsedLength - 500);
|
|
780
|
+
const contentToParse = cleanContent.substring(lookbackStart);
|
|
781
|
+
// Parse fenced messages
|
|
782
|
+
this.parseFencedMessages(contentToParse);
|
|
783
|
+
// Parse single-line messages
|
|
784
|
+
this.parseSingleLineMessages(contentToParse);
|
|
785
|
+
// Parse spawn/release commands
|
|
786
|
+
this.parseSpawnReleaseCommands(contentToParse);
|
|
787
|
+
this.lastParsedLength = cleanContent.length;
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Parse fenced multi-line messages
|
|
791
|
+
*/
|
|
792
|
+
parseFencedMessages(content) {
|
|
793
|
+
const escapedPrefix = this.relayPrefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
794
|
+
const fencePattern = new RegExp(`${escapedPrefix}(\\S+)(?:\\s+\\[thread:([\\w-]+)\\])?\\s*<<<([\\s\\S]*?)>>>`, 'g');
|
|
795
|
+
let match;
|
|
796
|
+
while ((match = fencePattern.exec(content)) !== null) {
|
|
797
|
+
const target = match[1];
|
|
798
|
+
const thread = match[2];
|
|
799
|
+
const body = match[3].trim();
|
|
800
|
+
if (!body || target === 'spawn' || target === 'release') {
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
803
|
+
this.sendRelayCommand({
|
|
804
|
+
to: target,
|
|
805
|
+
kind: 'message',
|
|
806
|
+
body,
|
|
807
|
+
thread,
|
|
808
|
+
raw: match[0],
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Parse single-line messages
|
|
814
|
+
*/
|
|
815
|
+
parseSingleLineMessages(content) {
|
|
816
|
+
const lines = content.split('\n');
|
|
817
|
+
const escapedPrefix = this.relayPrefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
818
|
+
const pattern = new RegExp(`${escapedPrefix}(\\S+)(?:\\s+\\[thread:([\\w-]+)\\])?\\s+(.+)$`);
|
|
819
|
+
for (const line of lines) {
|
|
820
|
+
// Skip fenced messages
|
|
821
|
+
if (line.includes('<<<') || line.includes('>>>')) {
|
|
822
|
+
continue;
|
|
823
|
+
}
|
|
824
|
+
const match = line.match(pattern);
|
|
825
|
+
if (!match) {
|
|
826
|
+
continue;
|
|
827
|
+
}
|
|
828
|
+
const target = match[1];
|
|
829
|
+
const thread = match[2];
|
|
830
|
+
const body = match[3].trim();
|
|
831
|
+
if (!body || target === 'spawn' || target === 'release') {
|
|
832
|
+
continue;
|
|
833
|
+
}
|
|
834
|
+
this.sendRelayCommand({
|
|
835
|
+
to: target,
|
|
836
|
+
kind: 'message',
|
|
837
|
+
body,
|
|
838
|
+
thread,
|
|
839
|
+
raw: line,
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
// =========================================================================
|
|
844
|
+
// Summary and session end detection
|
|
845
|
+
// =========================================================================
|
|
846
|
+
/**
|
|
847
|
+
* Check for [[SUMMARY]] blocks
|
|
848
|
+
*/
|
|
849
|
+
checkForSummary(content) {
|
|
850
|
+
const result = parseSummaryWithDetails(content);
|
|
851
|
+
if (!result.found || !result.valid) {
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
if (result.rawContent === this.lastSummaryRawContent) {
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
this.lastSummaryRawContent = result.rawContent ?? '';
|
|
858
|
+
this.emit('summary', {
|
|
859
|
+
agentName: this.config.name,
|
|
860
|
+
summary: result.summary,
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* Check for [[SESSION_END]] blocks
|
|
865
|
+
*/
|
|
866
|
+
checkForSessionEnd(content) {
|
|
867
|
+
if (this.sessionEndProcessed) {
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
const sessionEnd = parseSessionEndFromOutput(content);
|
|
871
|
+
if (!sessionEnd) {
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
this.sessionEndProcessed = true;
|
|
875
|
+
this.emit('session-end', {
|
|
876
|
+
agentName: this.config.name,
|
|
877
|
+
marker: sessionEnd,
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
// =========================================================================
|
|
881
|
+
// Public API
|
|
882
|
+
// =========================================================================
|
|
883
|
+
/**
|
|
884
|
+
* Query status from relay-pty
|
|
885
|
+
*/
|
|
886
|
+
async queryStatus() {
|
|
887
|
+
if (!this.socket || !this.socketConnected) {
|
|
888
|
+
return null;
|
|
889
|
+
}
|
|
890
|
+
try {
|
|
891
|
+
await this.sendSocketRequest({ type: 'status' });
|
|
892
|
+
// Response will come asynchronously via handleSocketResponse
|
|
893
|
+
// For now, return null - could implement request/response matching
|
|
894
|
+
return null;
|
|
895
|
+
}
|
|
896
|
+
catch {
|
|
897
|
+
return null;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Get raw output buffer
|
|
902
|
+
*/
|
|
903
|
+
getRawOutput() {
|
|
904
|
+
return this.rawBuffer;
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* Check if backpressure is active
|
|
908
|
+
*/
|
|
909
|
+
isBackpressureActive() {
|
|
910
|
+
return this.backpressureActive;
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Get the socket path
|
|
914
|
+
*/
|
|
915
|
+
getSocketPath() {
|
|
916
|
+
return this.socketPath;
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Get the relay-pty process PID
|
|
920
|
+
*/
|
|
921
|
+
get pid() {
|
|
922
|
+
return this.relayPtyProcess?.pid;
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* Get the log file path (not used by relay-pty, returns undefined)
|
|
926
|
+
*/
|
|
927
|
+
get logPath() {
|
|
928
|
+
return this._logPath;
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Kill the process forcefully
|
|
932
|
+
*/
|
|
933
|
+
async kill() {
|
|
934
|
+
if (this.relayPtyProcess && !this.relayPtyProcess.killed) {
|
|
935
|
+
this.relayPtyProcess.kill('SIGKILL');
|
|
936
|
+
}
|
|
937
|
+
this.running = false;
|
|
938
|
+
this.disconnectSocket();
|
|
939
|
+
this.destroyClient();
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* Get output lines (for compatibility with PtyWrapper)
|
|
943
|
+
* @param limit Maximum number of lines to return
|
|
944
|
+
*/
|
|
945
|
+
getOutput(limit) {
|
|
946
|
+
const lines = this.rawBuffer.split('\n');
|
|
947
|
+
if (limit && limit > 0) {
|
|
948
|
+
return lines.slice(-limit);
|
|
949
|
+
}
|
|
950
|
+
return lines;
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Write data directly to the process stdin
|
|
954
|
+
* @param data Data to write
|
|
955
|
+
*/
|
|
956
|
+
async write(data) {
|
|
957
|
+
if (!this.relayPtyProcess || !this.relayPtyProcess.stdin) {
|
|
958
|
+
throw new Error('Process not running');
|
|
959
|
+
}
|
|
960
|
+
const buffer = typeof data === 'string' ? Buffer.from(data) : data;
|
|
961
|
+
this.relayPtyProcess.stdin.write(buffer);
|
|
962
|
+
}
|
|
963
|
+
/**
|
|
964
|
+
* Get the agent ID (from continuity if available)
|
|
965
|
+
*/
|
|
966
|
+
getAgentId() {
|
|
967
|
+
return this.agentId;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
//# sourceMappingURL=relay-pty-orchestrator.js.map
|