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
|
@@ -10,6 +10,13 @@ import fs from 'node:fs';
|
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import { EventEmitter } from 'node:events';
|
|
12
12
|
import { RelayClient } from './client.js';
|
|
13
|
+
import { parseSummaryWithDetails, parseSessionEndFromOutput, isPlaceholderTarget } from './parser.js';
|
|
14
|
+
import { getProjectPaths } from '../utils/project-namespace.js';
|
|
15
|
+
import { getTrailEnvVars } from '../trajectory/integration.js';
|
|
16
|
+
import { findAgentConfig } from '../utils/agent-config.js';
|
|
17
|
+
import { HookRegistry, createTrajectoryHooks } from '../hooks/index.js';
|
|
18
|
+
import { getContinuityManager, parseContinuityCommand, hasContinuityCommand } from '../continuity/index.js';
|
|
19
|
+
import { INJECTION_CONSTANTS, stripAnsi, sleep, buildInjectionString, injectWithRetry as sharedInjectWithRetry, calculateSuccessRate, createInjectionMetrics, getDefaultRelayPrefix, detectCliType, CLI_QUIRKS, } from './shared.js';
|
|
13
20
|
/** Maximum lines to keep in output buffer */
|
|
14
21
|
const MAX_BUFFER_LINES = 10000;
|
|
15
22
|
export class PtyWrapper extends EventEmitter {
|
|
@@ -20,26 +27,93 @@ export class PtyWrapper extends EventEmitter {
|
|
|
20
27
|
outputBuffer = [];
|
|
21
28
|
rawBuffer = '';
|
|
22
29
|
relayPrefix;
|
|
30
|
+
cliType;
|
|
23
31
|
sentMessageHashes = new Set();
|
|
32
|
+
receivedMessageIds = new Set(); // Dedup incoming messages
|
|
24
33
|
processedSpawnCommands = new Set();
|
|
25
34
|
processedReleaseCommands = new Set();
|
|
35
|
+
pendingFencedSpawn = null;
|
|
26
36
|
messageQueue = [];
|
|
27
37
|
isInjecting = false;
|
|
28
38
|
readyForMessages = false;
|
|
39
|
+
lastOutputTime = 0;
|
|
40
|
+
injectionMetrics = createInjectionMetrics();
|
|
29
41
|
logFilePath;
|
|
30
42
|
logStream;
|
|
31
|
-
|
|
43
|
+
acceptedPrompts = new Set(); // Track which prompts have been accepted
|
|
44
|
+
hookRegistry;
|
|
45
|
+
sessionStartTime = Date.now();
|
|
46
|
+
continuity;
|
|
47
|
+
agentId;
|
|
48
|
+
processedContinuityCommands = new Set();
|
|
49
|
+
lastSummaryRawContent = ''; // Dedup summary event emissions
|
|
50
|
+
sessionEndProcessed = false; // Track if we've already emitted session-end
|
|
51
|
+
inThinkingBlock = false; // Track if inside <thinking>...</thinking>
|
|
52
|
+
lastSummaryTime = Date.now(); // Track when last summary was output
|
|
53
|
+
outputsSinceSummary = 0; // Count outputs since last summary
|
|
54
|
+
detectedTask; // Auto-detected task from agent config
|
|
55
|
+
sessionEndData; // Store SESSION_END data for handoff
|
|
56
|
+
instructionsInjected = false; // Track if init instructions have been injected
|
|
57
|
+
continuityInjected = false; // Track if continuity context has been injected
|
|
58
|
+
recentLogChunks = new Map(); // Dedup log streaming (hash -> timestamp)
|
|
59
|
+
static LOG_DEDUP_WINDOW_MS = 500; // Window for considering logs as duplicates
|
|
60
|
+
static LOG_DEDUP_MAX_SIZE = 100; // Max entries in dedup map
|
|
61
|
+
lastParsedLength = 0; // Track last parsed position to avoid re-parsing entire buffer
|
|
62
|
+
lastContinuityParsedLength = 0; // Same for continuity commands
|
|
32
63
|
constructor(config) {
|
|
33
64
|
super();
|
|
34
65
|
this.config = config;
|
|
35
|
-
this.relayPrefix = config.relayPrefix ??
|
|
66
|
+
this.relayPrefix = config.relayPrefix ?? getDefaultRelayPrefix();
|
|
67
|
+
// Detect CLI type from command for special handling
|
|
68
|
+
this.cliType = config.cliType ?? detectCliType(config.command);
|
|
69
|
+
// Auto-detect agent role from .claude/agents/ or .openagents/ if task not provided
|
|
70
|
+
let detectedTask = config.task;
|
|
71
|
+
if (!detectedTask) {
|
|
72
|
+
const agentConfig = findAgentConfig(config.name, config.cwd);
|
|
73
|
+
if (agentConfig?.description) {
|
|
74
|
+
detectedTask = agentConfig.description;
|
|
75
|
+
// Use stderr for consistency with TmuxWrapper's logStderr pattern
|
|
76
|
+
process.stderr.write(`[pty:${config.name}] Auto-detected role: ${detectedTask.substring(0, 60)}...\n`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Store detected task for use in hook registry
|
|
80
|
+
this.detectedTask = detectedTask;
|
|
36
81
|
this.client = new RelayClient({
|
|
37
82
|
agentName: config.name,
|
|
38
83
|
socketPath: config.socketPath,
|
|
39
|
-
cli:
|
|
84
|
+
cli: this.cliType,
|
|
85
|
+
task: detectedTask,
|
|
40
86
|
workingDirectory: config.cwd ?? process.cwd(),
|
|
41
87
|
quiet: true,
|
|
42
88
|
});
|
|
89
|
+
// Initialize hook registry
|
|
90
|
+
const projectPaths = getProjectPaths();
|
|
91
|
+
this.hookRegistry = new HookRegistry({
|
|
92
|
+
agentName: config.name,
|
|
93
|
+
workingDir: config.cwd ?? process.cwd(),
|
|
94
|
+
projectId: projectPaths.projectId,
|
|
95
|
+
task: this.detectedTask,
|
|
96
|
+
env: config.env,
|
|
97
|
+
inject: (text) => this.write(text + '\r'),
|
|
98
|
+
send: async (to, body) => {
|
|
99
|
+
this.client.sendMessage(to, body, 'message');
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
// Register trajectory hooks if enabled (default: true if task provided or auto-detected)
|
|
103
|
+
const enableTrajectory = config.trajectoryTracking ?? !!this.detectedTask;
|
|
104
|
+
if (enableTrajectory) {
|
|
105
|
+
const trajectoryHooks = createTrajectoryHooks({
|
|
106
|
+
projectId: projectPaths.projectId,
|
|
107
|
+
agentName: config.name,
|
|
108
|
+
});
|
|
109
|
+
this.hookRegistry.registerLifecycleHooks(trajectoryHooks);
|
|
110
|
+
}
|
|
111
|
+
// Register custom hooks if provided
|
|
112
|
+
if (config.hooks) {
|
|
113
|
+
this.hookRegistry.registerLifecycleHooks(config.hooks);
|
|
114
|
+
}
|
|
115
|
+
// Initialize continuity manager
|
|
116
|
+
this.continuity = getContinuityManager({ defaultCli: 'spawned' });
|
|
43
117
|
// Handle incoming messages
|
|
44
118
|
this.client.onMessage = (from, payload, messageId, meta, originalTo) => {
|
|
45
119
|
this.handleIncomingMessage(from, payload, messageId, meta, originalTo);
|
|
@@ -87,6 +161,9 @@ export class PtyWrapper extends EventEmitter {
|
|
|
87
161
|
// Log spawn details for debugging
|
|
88
162
|
console.log(`[pty:${this.config.name}] Spawning: ${this.config.command} ${args.join(' ')}`);
|
|
89
163
|
console.log(`[pty:${this.config.name}] CWD: ${cwd}`);
|
|
164
|
+
// Get trail environment variables
|
|
165
|
+
const projectPaths = getProjectPaths();
|
|
166
|
+
const trailEnvVars = getTrailEnvVars(projectPaths.projectId, this.config.name, projectPaths.dataDir);
|
|
90
167
|
// Spawn the process with error handling
|
|
91
168
|
try {
|
|
92
169
|
this.ptyProcess = pty.spawn(this.config.command, args, {
|
|
@@ -97,6 +174,7 @@ export class PtyWrapper extends EventEmitter {
|
|
|
97
174
|
env: {
|
|
98
175
|
...process.env,
|
|
99
176
|
...this.config.env,
|
|
177
|
+
...trailEnvVars,
|
|
100
178
|
AGENT_RELAY_NAME: this.config.name,
|
|
101
179
|
TERM: 'xterm-256color',
|
|
102
180
|
},
|
|
@@ -109,6 +187,17 @@ export class PtyWrapper extends EventEmitter {
|
|
|
109
187
|
throw spawnError;
|
|
110
188
|
}
|
|
111
189
|
this.running = true;
|
|
190
|
+
this.sessionStartTime = Date.now();
|
|
191
|
+
// Dispatch session start hook (handles trajectory initialization)
|
|
192
|
+
this.hookRegistry.dispatchSessionStart().catch(err => {
|
|
193
|
+
console.error(`[pty:${this.config.name}] Session start hook error:`, err);
|
|
194
|
+
});
|
|
195
|
+
// Initialize continuity and get agentId, then inject context
|
|
196
|
+
this.initializeAgentId()
|
|
197
|
+
.then(() => this.injectContinuityContext())
|
|
198
|
+
.catch(err => {
|
|
199
|
+
console.error(`[pty:${this.config.name}] Agent ID/continuity initialization error:`, err);
|
|
200
|
+
});
|
|
112
201
|
// Capture output
|
|
113
202
|
this.ptyProcess.onData((data) => {
|
|
114
203
|
this.handleOutput(data);
|
|
@@ -128,10 +217,139 @@ export class PtyWrapper extends EventEmitter {
|
|
|
128
217
|
this.processMessageQueue();
|
|
129
218
|
}, 2000);
|
|
130
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* Initialize agent ID for continuity/resume functionality
|
|
222
|
+
*/
|
|
223
|
+
async initializeAgentId() {
|
|
224
|
+
if (!this.continuity)
|
|
225
|
+
return;
|
|
226
|
+
try {
|
|
227
|
+
let ledger;
|
|
228
|
+
// If resuming from a previous agent ID, try to find that ledger
|
|
229
|
+
if (this.config.resumeAgentId) {
|
|
230
|
+
ledger = await this.continuity.findLedgerByAgentId(this.config.resumeAgentId);
|
|
231
|
+
if (ledger) {
|
|
232
|
+
console.log(`[pty:${this.config.name}] Resuming agent ID: ${ledger.agentId} (from previous session)`);
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
console.error(`[pty:${this.config.name}] Resume agent ID ${this.config.resumeAgentId} not found, creating new`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// If not resuming or resume ID not found, get or create ledger
|
|
239
|
+
if (!ledger) {
|
|
240
|
+
ledger = await this.continuity.getOrCreateLedger(this.config.name, 'spawned');
|
|
241
|
+
console.log(`[pty:${this.config.name}] Agent ID: ${ledger.agentId} (use this to resume if agent dies)`);
|
|
242
|
+
}
|
|
243
|
+
this.agentId = ledger.agentId;
|
|
244
|
+
}
|
|
245
|
+
catch (err) {
|
|
246
|
+
console.error(`[pty:${this.config.name}] Failed to initialize agent ID: ${err.message}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Get the current agent ID
|
|
251
|
+
*/
|
|
252
|
+
getAgentId() {
|
|
253
|
+
return this.agentId;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Inject continuity context from previous session.
|
|
257
|
+
* Called after agent ID initialization to restore state from ledger.
|
|
258
|
+
*/
|
|
259
|
+
async injectContinuityContext() {
|
|
260
|
+
if (!this.continuity || !this.running)
|
|
261
|
+
return;
|
|
262
|
+
// Guard: Only inject once per session
|
|
263
|
+
if (this.continuityInjected) {
|
|
264
|
+
console.log(`[pty:${this.config.name}] Continuity context already injected, skipping`);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
this.continuityInjected = true;
|
|
268
|
+
try {
|
|
269
|
+
const context = await this.continuity.getStartupContext(this.config.name);
|
|
270
|
+
// Skip if no meaningful context (empty ledger or just boilerplate)
|
|
271
|
+
if (!context?.formatted || context.formatted.length < 50) {
|
|
272
|
+
console.log(`[pty:${this.config.name}] Skipping continuity injection (no meaningful context)`);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (context?.formatted) {
|
|
276
|
+
// Build context notification similar to TmuxWrapper
|
|
277
|
+
const taskInfo = context.ledger?.currentTask
|
|
278
|
+
? `Task: ${context.ledger.currentTask.slice(0, 50)}`
|
|
279
|
+
: '';
|
|
280
|
+
const handoffInfo = context.handoff
|
|
281
|
+
? `Last handoff: ${context.handoff.createdAt.toISOString().split('T')[0]}`
|
|
282
|
+
: '';
|
|
283
|
+
const statusParts = [taskInfo, handoffInfo].filter(Boolean).join(' | ');
|
|
284
|
+
const notification = `[Continuity] Previous session context loaded.${statusParts ? ` ${statusParts}` : ''}\n\n${context.formatted}`;
|
|
285
|
+
// Queue continuity context directly to messageQueue with 'system' as sender
|
|
286
|
+
// This avoids creating confusing "Agent -> Agent" self-messages in the dashboard
|
|
287
|
+
// Fix for Lead communication issue: continuity checkpoints were creating self-messages
|
|
288
|
+
this.messageQueue.push({
|
|
289
|
+
from: 'system',
|
|
290
|
+
body: notification,
|
|
291
|
+
messageId: `continuity-startup-${Date.now()}`,
|
|
292
|
+
thread: 'continuity-context',
|
|
293
|
+
});
|
|
294
|
+
this.processMessageQueue();
|
|
295
|
+
const mode = context.handoff ? 'resume' : 'continue';
|
|
296
|
+
console.log(`[pty:${this.config.name}] Continuity context injected (${mode})`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
catch (err) {
|
|
300
|
+
console.error(`[pty:${this.config.name}] Failed to inject continuity context: ${err.message}`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Parse ->continuity: commands from output and handle them.
|
|
305
|
+
*
|
|
306
|
+
* Supported commands:
|
|
307
|
+
* ->continuity:save <<<...>>> - Save session state to ledger
|
|
308
|
+
* ->continuity:load - Request context injection
|
|
309
|
+
* ->continuity:search "query" - Search past handoffs
|
|
310
|
+
* ->continuity:uncertain "..." - Mark item as uncertain
|
|
311
|
+
* ->continuity:handoff <<<...>>> - Create explicit handoff
|
|
312
|
+
*/
|
|
313
|
+
async parseContinuityCommands(content) {
|
|
314
|
+
if (!this.continuity)
|
|
315
|
+
return;
|
|
316
|
+
if (!hasContinuityCommand(content))
|
|
317
|
+
return;
|
|
318
|
+
const command = parseContinuityCommand(content);
|
|
319
|
+
if (!command)
|
|
320
|
+
return;
|
|
321
|
+
// Deduplication: use type + content hash for all commands
|
|
322
|
+
// Fixed: content-less commands (like load) now use static hash to prevent infinite loops
|
|
323
|
+
const cmdHash = `${command.type}:${command.content || command.query || command.item || 'no-content'}`;
|
|
324
|
+
if (this.processedContinuityCommands.has(cmdHash))
|
|
325
|
+
return;
|
|
326
|
+
this.processedContinuityCommands.add(cmdHash);
|
|
327
|
+
// Limit dedup set size
|
|
328
|
+
if (this.processedContinuityCommands.size > 100) {
|
|
329
|
+
const oldest = this.processedContinuityCommands.values().next().value;
|
|
330
|
+
if (oldest)
|
|
331
|
+
this.processedContinuityCommands.delete(oldest);
|
|
332
|
+
}
|
|
333
|
+
try {
|
|
334
|
+
const response = await this.continuity.handleCommand(this.config.name, command);
|
|
335
|
+
if (response) {
|
|
336
|
+
// Inject response via relay message to self
|
|
337
|
+
this.client.sendMessage(this.config.name, response, 'message', {
|
|
338
|
+
thread: 'continuity-response',
|
|
339
|
+
});
|
|
340
|
+
console.log(`[pty:${this.config.name}] Continuity command handled: ${command.type}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
catch (err) {
|
|
344
|
+
console.error(`[pty:${this.config.name}] Continuity command error: ${err.message}`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
131
347
|
/**
|
|
132
348
|
* Handle output from the process
|
|
133
349
|
*/
|
|
134
350
|
handleOutput(data) {
|
|
351
|
+
// Track output timing for stability checks
|
|
352
|
+
this.lastOutputTime = Date.now();
|
|
135
353
|
// Append to raw buffer
|
|
136
354
|
this.rawBuffer += data;
|
|
137
355
|
// Write to log file if available
|
|
@@ -141,8 +359,13 @@ export class PtyWrapper extends EventEmitter {
|
|
|
141
359
|
// Emit for external listeners
|
|
142
360
|
this.emit('output', data);
|
|
143
361
|
// Stream to daemon for dashboard log viewing (if connected)
|
|
362
|
+
// Filter out Claude's extended thinking blocks before streaming
|
|
363
|
+
// Also deduplicate to prevent terminal redraws from causing duplicate log entries
|
|
144
364
|
if (this.config.streamLogs !== false && this.client.state === 'READY') {
|
|
145
|
-
this.
|
|
365
|
+
const filteredData = this.filterThinkingBlocks(data);
|
|
366
|
+
if (filteredData && !this.isDuplicateLogChunk(filteredData)) {
|
|
367
|
+
this.client.sendLog(filteredData);
|
|
368
|
+
}
|
|
146
369
|
}
|
|
147
370
|
// Auto-accept Claude's first-run prompt for --dangerously-skip-permissions
|
|
148
371
|
// The prompt shows: "2. Yes, I accept" - we send "2" to accept
|
|
@@ -162,28 +385,172 @@ export class PtyWrapper extends EventEmitter {
|
|
|
162
385
|
}
|
|
163
386
|
// Parse for relay commands
|
|
164
387
|
this.parseRelayCommands();
|
|
388
|
+
// Dispatch output hook (handles phase detection, etc.)
|
|
389
|
+
const cleanData = stripAnsi(data);
|
|
390
|
+
this.hookRegistry.dispatchOutput(cleanData, data).catch(err => {
|
|
391
|
+
console.error(`[pty:${this.config.name}] Output hook error:`, err);
|
|
392
|
+
});
|
|
393
|
+
// Check for [[SUMMARY]] and [[SESSION_END]] blocks and emit events
|
|
394
|
+
// This allows cloud services to handle persistence without hardcoding storage
|
|
395
|
+
const cleanContent = stripAnsi(this.rawBuffer);
|
|
396
|
+
this.checkForSummaryAndEmit(cleanContent);
|
|
397
|
+
this.checkForSessionEndAndEmit(cleanContent);
|
|
398
|
+
// Parse for continuity commands (->continuity:save, ->continuity:load, etc.)
|
|
399
|
+
// Use rawBuffer (accumulated content) not immediate chunk, since multi-line
|
|
400
|
+
// fenced commands like ->continuity:save <<<...>>> span multiple output events
|
|
401
|
+
// Optimization: Only parse new content with lookback for incomplete fenced commands
|
|
402
|
+
if (cleanContent.length > this.lastContinuityParsedLength) {
|
|
403
|
+
const lookbackStart = Math.max(0, this.lastContinuityParsedLength - 500);
|
|
404
|
+
const contentToParse = cleanContent.substring(lookbackStart);
|
|
405
|
+
this.parseContinuityCommands(contentToParse).catch(err => {
|
|
406
|
+
console.error(`[pty:${this.config.name}] Continuity command parsing error:`, err);
|
|
407
|
+
});
|
|
408
|
+
this.lastContinuityParsedLength = cleanContent.length;
|
|
409
|
+
}
|
|
410
|
+
// Track outputs and potentially remind about summaries
|
|
411
|
+
this.trackOutputAndRemind(data);
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Filter Claude's extended thinking blocks from output.
|
|
415
|
+
* Thinking blocks are wrapped in <thinking>...</thinking> tags and should
|
|
416
|
+
* not be streamed to the dashboard or stored in output buffers.
|
|
417
|
+
*
|
|
418
|
+
* This method tracks state across calls to handle multi-line thinking blocks.
|
|
419
|
+
*/
|
|
420
|
+
filterThinkingBlocks(data) {
|
|
421
|
+
const THINKING_START = /<thinking>/;
|
|
422
|
+
const THINKING_END = /<\/thinking>/;
|
|
423
|
+
const lines = data.split('\n');
|
|
424
|
+
const outputLines = [];
|
|
425
|
+
for (const line of lines) {
|
|
426
|
+
// If in thinking block, check for end
|
|
427
|
+
if (this.inThinkingBlock) {
|
|
428
|
+
if (THINKING_END.test(line)) {
|
|
429
|
+
this.inThinkingBlock = false;
|
|
430
|
+
// If there's content after </thinking> on the same line, keep it
|
|
431
|
+
const afterEnd = line.split('</thinking>')[1];
|
|
432
|
+
if (afterEnd && afterEnd.trim()) {
|
|
433
|
+
outputLines.push(afterEnd);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
// Skip this line - inside thinking block
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
// Check for thinking start
|
|
440
|
+
if (THINKING_START.test(line)) {
|
|
441
|
+
this.inThinkingBlock = true;
|
|
442
|
+
// Check if it ends on the same line
|
|
443
|
+
if (THINKING_END.test(line)) {
|
|
444
|
+
this.inThinkingBlock = false;
|
|
445
|
+
}
|
|
446
|
+
// Keep content before <thinking> if any
|
|
447
|
+
const beforeStart = line.split('<thinking>')[0];
|
|
448
|
+
if (beforeStart && beforeStart.trim()) {
|
|
449
|
+
outputLines.push(beforeStart);
|
|
450
|
+
}
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
// Normal line - keep it
|
|
454
|
+
outputLines.push(line);
|
|
455
|
+
}
|
|
456
|
+
return outputLines.join('\n');
|
|
165
457
|
}
|
|
166
458
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
459
|
+
* Check if a log chunk is a duplicate (recently streamed).
|
|
460
|
+
* Prevents terminal redraws from causing duplicate log entries in the dashboard.
|
|
461
|
+
*
|
|
462
|
+
* Uses content normalization and time-based deduplication:
|
|
463
|
+
* - Strips whitespace and normalizes content for comparison
|
|
464
|
+
* - Considers chunks with same normalized content within LOG_DEDUP_WINDOW_MS as duplicates
|
|
465
|
+
* - Cleans up old entries to prevent memory growth
|
|
466
|
+
*/
|
|
467
|
+
isDuplicateLogChunk(data) {
|
|
468
|
+
// Normalize: strip excessive whitespace, limit to first 200 chars for hash
|
|
469
|
+
// This helps catch redraws that might have slight formatting differences
|
|
470
|
+
const normalized = stripAnsi(data).replace(/\s+/g, ' ').trim().substring(0, 200);
|
|
471
|
+
// Very short chunks (likely control chars or partial output) - allow through
|
|
472
|
+
if (normalized.length < 10) {
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
// Simple hash using string as key
|
|
476
|
+
const hash = normalized;
|
|
477
|
+
const now = Date.now();
|
|
478
|
+
// Check if this chunk was recently streamed
|
|
479
|
+
const lastSeen = this.recentLogChunks.get(hash);
|
|
480
|
+
if (lastSeen && (now - lastSeen) < PtyWrapper.LOG_DEDUP_WINDOW_MS) {
|
|
481
|
+
return true; // Duplicate
|
|
482
|
+
}
|
|
483
|
+
// Record this chunk
|
|
484
|
+
this.recentLogChunks.set(hash, now);
|
|
485
|
+
// Cleanup: remove old entries if map is getting large
|
|
486
|
+
if (this.recentLogChunks.size > PtyWrapper.LOG_DEDUP_MAX_SIZE) {
|
|
487
|
+
const cutoff = now - PtyWrapper.LOG_DEDUP_WINDOW_MS * 2;
|
|
488
|
+
for (const [key, timestamp] of this.recentLogChunks) {
|
|
489
|
+
if (timestamp < cutoff) {
|
|
490
|
+
this.recentLogChunks.delete(key);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return false; // Not a duplicate
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Auto-accept Claude's first-run prompts
|
|
498
|
+
* Handles multiple prompts in sequence:
|
|
499
|
+
* 1. --dangerously-skip-permissions acceptance ("Yes, I accept")
|
|
500
|
+
* 2. Trust directory prompt ("Yes, I trust this folder")
|
|
501
|
+
* 3. "Ready to code here?" permission prompt ("Yes, continue")
|
|
502
|
+
*
|
|
503
|
+
* Uses a Set to track which prompts have been accepted, allowing
|
|
504
|
+
* multiple different prompts to be handled in sequence.
|
|
169
505
|
*/
|
|
170
506
|
handleAutoAcceptPrompts(data) {
|
|
171
|
-
if (this.hasAcceptedPrompt)
|
|
172
|
-
return;
|
|
173
507
|
if (!this.ptyProcess || !this.running)
|
|
174
508
|
return;
|
|
175
|
-
|
|
509
|
+
const cleanData = stripAnsi(data);
|
|
510
|
+
// Check for the permission acceptance prompt (--dangerously-skip-permissions)
|
|
176
511
|
// Pattern: "2. Yes, I accept" in the output
|
|
177
|
-
|
|
178
|
-
|
|
512
|
+
if (!this.acceptedPrompts.has('permission') &&
|
|
513
|
+
cleanData.includes('Yes, I accept') && cleanData.includes('No, exit')) {
|
|
179
514
|
console.log(`[pty:${this.config.name}] Detected permission prompt, auto-accepting...`);
|
|
180
|
-
this.
|
|
515
|
+
this.acceptedPrompts.add('permission');
|
|
181
516
|
// Send "2" to select "Yes, I accept" and Enter to confirm
|
|
182
517
|
setTimeout(() => {
|
|
183
518
|
if (this.ptyProcess && this.running) {
|
|
184
519
|
this.ptyProcess.write('2');
|
|
185
520
|
}
|
|
186
521
|
}, 100);
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
// Check for the trust directory prompt
|
|
525
|
+
// Pattern: "1. Yes, I trust this folder" with "No, exit"
|
|
526
|
+
if (!this.acceptedPrompts.has('trust') &&
|
|
527
|
+
(cleanData.includes('trust this folder') || cleanData.includes('safety check'))
|
|
528
|
+
&& cleanData.includes('No, exit')) {
|
|
529
|
+
console.log(`[pty:${this.config.name}] Detected trust directory prompt, auto-accepting...`);
|
|
530
|
+
this.acceptedPrompts.add('trust');
|
|
531
|
+
// Send Enter to accept first option (already selected)
|
|
532
|
+
setTimeout(() => {
|
|
533
|
+
if (this.ptyProcess && this.running) {
|
|
534
|
+
this.ptyProcess.write('\r');
|
|
535
|
+
}
|
|
536
|
+
}, 300);
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
// Check for "Ready to code here?" permission prompt
|
|
540
|
+
// Pattern: "Yes, continue" with "No, exit" and "Ready to code here?"
|
|
541
|
+
// This prompt asks for permission to work with files in the workspace
|
|
542
|
+
if (!this.acceptedPrompts.has('ready-to-code') &&
|
|
543
|
+
cleanData.includes('Yes, continue') && cleanData.includes('No, exit')
|
|
544
|
+
&& (cleanData.includes('Ready to code here') || cleanData.includes('permission to work with your files'))) {
|
|
545
|
+
console.log(`[pty:${this.config.name}] Detected "Ready to code here?" prompt, auto-accepting...`);
|
|
546
|
+
this.acceptedPrompts.add('ready-to-code');
|
|
547
|
+
// Send Enter to accept first option (already selected with ❯)
|
|
548
|
+
setTimeout(() => {
|
|
549
|
+
if (this.ptyProcess && this.running) {
|
|
550
|
+
this.ptyProcess.write('\r');
|
|
551
|
+
}
|
|
552
|
+
}, 300);
|
|
553
|
+
return;
|
|
187
554
|
}
|
|
188
555
|
}
|
|
189
556
|
/**
|
|
@@ -218,15 +585,27 @@ export class PtyWrapper extends EventEmitter {
|
|
|
218
585
|
* Parse relay commands from output.
|
|
219
586
|
* Handles both single-line and multi-line (fenced) formats.
|
|
220
587
|
* Deduplication via sentMessageHashes.
|
|
588
|
+
*
|
|
589
|
+
* Optimization: Only parses new content since last parse to avoid O(n²) behavior.
|
|
590
|
+
* Uses lookback buffer for incomplete fenced messages that span output chunks.
|
|
221
591
|
*/
|
|
222
592
|
parseRelayCommands() {
|
|
223
|
-
const cleanContent =
|
|
593
|
+
const cleanContent = stripAnsi(this.rawBuffer);
|
|
594
|
+
// Skip if no new content
|
|
595
|
+
if (cleanContent.length <= this.lastParsedLength)
|
|
596
|
+
return;
|
|
597
|
+
// For fenced messages, need some lookback for incomplete fences that span chunks
|
|
598
|
+
// 500 chars is enough to capture most relay message headers
|
|
599
|
+
const lookbackStart = Math.max(0, this.lastParsedLength - 500);
|
|
600
|
+
const contentToParse = cleanContent.substring(lookbackStart);
|
|
224
601
|
// First, try to find fenced multi-line messages: ->relay:Target <<<\n...\n>>>
|
|
225
|
-
this.parseFencedMessages(
|
|
602
|
+
this.parseFencedMessages(contentToParse);
|
|
226
603
|
// Then parse single-line messages
|
|
227
|
-
this.parseSingleLineMessages(
|
|
604
|
+
this.parseSingleLineMessages(contentToParse);
|
|
228
605
|
// Parse spawn/release commands
|
|
229
|
-
this.parseSpawnReleaseCommands(
|
|
606
|
+
this.parseSpawnReleaseCommands(contentToParse);
|
|
607
|
+
// Update parsed position
|
|
608
|
+
this.lastParsedLength = cleanContent.length;
|
|
230
609
|
}
|
|
231
610
|
/**
|
|
232
611
|
* Parse fenced multi-line messages: ->relay:Target [thread:xxx] <<<\n...\n>>>
|
|
@@ -242,6 +621,14 @@ export class PtyWrapper extends EventEmitter {
|
|
|
242
621
|
const threadProject = match[2]; // Optional: project part of thread
|
|
243
622
|
const threadId = match[3]; // Thread ID
|
|
244
623
|
const startIdx = match.index + match[0].length;
|
|
624
|
+
// Skip spawn/release commands - they are handled by parseSpawnReleaseCommands
|
|
625
|
+
if (/^spawn$/i.test(target) || /^release$/i.test(target)) {
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
// Skip placeholder targets (documentation examples like "AgentName", "Lead", etc.)
|
|
629
|
+
if (isPlaceholderTarget(target)) {
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
245
632
|
// Find the closing >>>
|
|
246
633
|
const endIdx = content.indexOf('>>>', startIdx);
|
|
247
634
|
if (endIdx === -1)
|
|
@@ -258,6 +645,10 @@ export class PtyWrapper extends EventEmitter {
|
|
|
258
645
|
project = target.substring(0, colonIdx);
|
|
259
646
|
to = target.substring(colonIdx + 1);
|
|
260
647
|
}
|
|
648
|
+
// Skip placeholder targets after parsing cross-project syntax
|
|
649
|
+
if (isPlaceholderTarget(to)) {
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
261
652
|
this.sendRelayCommand({
|
|
262
653
|
to,
|
|
263
654
|
kind: 'message',
|
|
@@ -283,6 +674,11 @@ export class PtyWrapper extends EventEmitter {
|
|
|
283
674
|
const prefixIdx = line.indexOf(this.relayPrefix);
|
|
284
675
|
if (prefixIdx === -1)
|
|
285
676
|
continue;
|
|
677
|
+
// Skip spawn/release commands - they are handled by parseSpawnReleaseCommands
|
|
678
|
+
const afterPrefixForCheck = line.substring(prefixIdx + this.relayPrefix.length);
|
|
679
|
+
if (/^spawn\s+/i.test(afterPrefixForCheck) || /^release\s+/i.test(afterPrefixForCheck)) {
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
286
682
|
// Extract everything after the prefix
|
|
287
683
|
const afterPrefix = line.substring(prefixIdx + this.relayPrefix.length);
|
|
288
684
|
// Pattern: Target [thread:project:id] body or Target [thread:id] body or Target body
|
|
@@ -296,6 +692,9 @@ export class PtyWrapper extends EventEmitter {
|
|
|
296
692
|
const [, target, body] = simpleMatch;
|
|
297
693
|
if (!body)
|
|
298
694
|
continue;
|
|
695
|
+
// Skip placeholder targets (documentation examples)
|
|
696
|
+
if (isPlaceholderTarget(target))
|
|
697
|
+
continue;
|
|
299
698
|
// Parse target for cross-project syntax
|
|
300
699
|
const colonIdx = target.indexOf(':');
|
|
301
700
|
let to = target;
|
|
@@ -304,6 +703,9 @@ export class PtyWrapper extends EventEmitter {
|
|
|
304
703
|
project = target.substring(0, colonIdx);
|
|
305
704
|
to = target.substring(colonIdx + 1);
|
|
306
705
|
}
|
|
706
|
+
// Skip placeholder targets after parsing cross-project syntax
|
|
707
|
+
if (isPlaceholderTarget(to))
|
|
708
|
+
continue;
|
|
307
709
|
this.sendRelayCommand({
|
|
308
710
|
to,
|
|
309
711
|
kind: 'message',
|
|
@@ -316,6 +718,9 @@ export class PtyWrapper extends EventEmitter {
|
|
|
316
718
|
const [, target, threadProject, threadId, body] = targetMatch;
|
|
317
719
|
if (!body)
|
|
318
720
|
continue;
|
|
721
|
+
// Skip placeholder targets (documentation examples)
|
|
722
|
+
if (isPlaceholderTarget(target))
|
|
723
|
+
continue;
|
|
319
724
|
// Parse target for cross-project syntax
|
|
320
725
|
const colonIdx = target.indexOf(':');
|
|
321
726
|
let to = target;
|
|
@@ -324,6 +729,9 @@ export class PtyWrapper extends EventEmitter {
|
|
|
324
729
|
project = target.substring(0, colonIdx);
|
|
325
730
|
to = target.substring(colonIdx + 1);
|
|
326
731
|
}
|
|
732
|
+
// Skip placeholder targets after parsing cross-project syntax
|
|
733
|
+
if (isPlaceholderTarget(to))
|
|
734
|
+
continue;
|
|
327
735
|
this.sendRelayCommand({
|
|
328
736
|
to,
|
|
329
737
|
kind: 'message',
|
|
@@ -335,77 +743,204 @@ export class PtyWrapper extends EventEmitter {
|
|
|
335
743
|
});
|
|
336
744
|
}
|
|
337
745
|
}
|
|
338
|
-
/**
|
|
339
|
-
* Strip ANSI escape codes from string.
|
|
340
|
-
* Converts cursor movements to spaces to preserve visual layout.
|
|
341
|
-
*/
|
|
342
|
-
stripAnsi(str) {
|
|
343
|
-
// Convert cursor forward movements to spaces (CSI n C)
|
|
344
|
-
// \x1B[nC means move cursor right n columns
|
|
345
|
-
// eslint-disable-next-line no-control-regex
|
|
346
|
-
str = str.replace(/\x1B\[(\d+)C/g, (_m, n) => ' '.repeat(parseInt(n, 10) || 1));
|
|
347
|
-
// Convert single cursor right (CSI C) to space
|
|
348
|
-
// eslint-disable-next-line no-control-regex
|
|
349
|
-
str = str.replace(/\x1B\[C/g, ' ');
|
|
350
|
-
// Remove carriage returns (causes text overwriting issues)
|
|
351
|
-
str = str.replace(/\r(?!\n)/g, '');
|
|
352
|
-
// Strip remaining ANSI escape sequences (with \x1B prefix)
|
|
353
|
-
// eslint-disable-next-line no-control-regex
|
|
354
|
-
str = str.replace(/\x1B(?:\[[0-9;?]*[A-Za-z]|\].*?(?:\x07|\x1B\\)|[@-Z\\-_])/g, '');
|
|
355
|
-
// Strip orphaned CSI sequences that lost their escape byte
|
|
356
|
-
// These look like [?25h, [?2026l, [0m, etc. at the start of content
|
|
357
|
-
str = str.replace(/^\s*(\[\??\d*[A-Za-z])+\s*/g, '');
|
|
358
|
-
return str;
|
|
359
|
-
}
|
|
360
746
|
/**
|
|
361
747
|
* Send relay command to daemon
|
|
362
748
|
*/
|
|
363
749
|
sendRelayCommand(cmd) {
|
|
364
750
|
const msgHash = `${cmd.to}:${cmd.body}`;
|
|
365
751
|
if (this.sentMessageHashes.has(msgHash)) {
|
|
752
|
+
console.log(`[pty:${this.config.name}] Skipping duplicate message to ${cmd.to}`);
|
|
366
753
|
return;
|
|
367
754
|
}
|
|
368
755
|
if (this.client.state !== 'READY') {
|
|
756
|
+
console.log(`[pty:${this.config.name}] Cannot send to ${cmd.to} - relay not ready (state: ${this.client.state})`);
|
|
369
757
|
return;
|
|
370
758
|
}
|
|
371
759
|
const success = this.client.sendMessage(cmd.to, cmd.body, cmd.kind, cmd.data, cmd.thread);
|
|
760
|
+
console.log(`[pty:${this.config.name}] Sent message to ${cmd.to}: ${success ? 'success' : 'failed'}`);
|
|
372
761
|
if (success) {
|
|
373
762
|
this.sentMessageHashes.add(msgHash);
|
|
763
|
+
// Dispatch message sent hook
|
|
764
|
+
this.hookRegistry.dispatchMessageSent(cmd.to, cmd.body, cmd.thread).catch(err => {
|
|
765
|
+
console.error(`[pty:${this.config.name}] Message sent hook error:`, err);
|
|
766
|
+
});
|
|
374
767
|
}
|
|
375
768
|
}
|
|
769
|
+
/** Valid CLI types for spawn commands */
|
|
770
|
+
static VALID_CLI_TYPES = new Set([
|
|
771
|
+
'claude', 'codex', 'gemini', 'droid', 'aider', 'cursor', 'cline', 'opencode',
|
|
772
|
+
]);
|
|
773
|
+
/** Validate agent name format (PascalCase, alphanumeric, 2-30 chars) */
|
|
774
|
+
isValidAgentName(name) {
|
|
775
|
+
// Must start with uppercase letter, contain only alphanumeric chars
|
|
776
|
+
// Length 2-30 characters
|
|
777
|
+
return /^[A-Z][a-zA-Z0-9]{1,29}$/.test(name);
|
|
778
|
+
}
|
|
779
|
+
/** Validate CLI type */
|
|
780
|
+
isValidCliType(cli) {
|
|
781
|
+
return PtyWrapper.VALID_CLI_TYPES.has(cli.toLowerCase());
|
|
782
|
+
}
|
|
376
783
|
/**
|
|
377
784
|
* Parse spawn/release commands from output
|
|
378
785
|
* Uses string-based parsing for robustness with PTY output.
|
|
786
|
+
* Supports two formats:
|
|
787
|
+
* Single-line: ->relay:spawn WorkerName cli "task description"
|
|
788
|
+
* Multi-line (fenced): ->relay:spawn WorkerName cli <<<
|
|
789
|
+
* task description here
|
|
790
|
+
* can span multiple lines>>>
|
|
379
791
|
* Delegates to dashboard API if dashboardPort is set (for nested spawns).
|
|
792
|
+
*
|
|
793
|
+
* STRICT VALIDATION:
|
|
794
|
+
* - Command must be at start of line (after whitespace)
|
|
795
|
+
* - Agent name must be PascalCase (e.g., Backend, Frontend, Worker1)
|
|
796
|
+
* - CLI must be a known type (claude, codex, gemini, etc.)
|
|
380
797
|
*/
|
|
381
798
|
parseSpawnReleaseCommands(content) {
|
|
382
799
|
// Need either API port or callbacks to handle spawn/release
|
|
383
|
-
|
|
800
|
+
// Also check allowSpawn config - spawned workers should not spawn other agents
|
|
801
|
+
const spawnAllowed = this.config.allowSpawn !== false;
|
|
802
|
+
const canSpawn = spawnAllowed && (this.config.dashboardPort || this.config.onSpawn);
|
|
384
803
|
const canRelease = this.config.dashboardPort || this.config.onRelease;
|
|
804
|
+
// Debug: always log spawn detection for debugging
|
|
805
|
+
if (content.includes('->relay:spawn')) {
|
|
806
|
+
console.log(`[pty:${this.config.name}] [SPAWN-DEBUG] Spawn pattern detected in content`);
|
|
807
|
+
console.log(`[pty:${this.config.name}] [SPAWN-DEBUG] canSpawn=${canSpawn} (allowSpawn=${spawnAllowed}, dashboardPort=${this.config.dashboardPort}, hasOnSpawn=${!!this.config.onSpawn})`);
|
|
808
|
+
// Log the actual lines containing spawn
|
|
809
|
+
const spawnLines = content.split('\n').filter(l => l.includes('->relay:spawn'));
|
|
810
|
+
spawnLines.forEach((line, i) => {
|
|
811
|
+
console.log(`[pty:${this.config.name}] [SPAWN-DEBUG] Line ${i}: "${line.substring(0, 100)}"`);
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
// Debug: always log release detection for debugging
|
|
815
|
+
if (content.includes('->relay:release')) {
|
|
816
|
+
console.log(`[pty:${this.config.name}] [RELEASE-DEBUG] Release pattern detected in content`);
|
|
817
|
+
console.log(`[pty:${this.config.name}] [RELEASE-DEBUG] canRelease=${canRelease} (dashboardPort=${this.config.dashboardPort}, hasOnRelease=${!!this.config.onRelease})`);
|
|
818
|
+
}
|
|
385
819
|
if (!canSpawn && !canRelease)
|
|
386
820
|
return;
|
|
387
821
|
const lines = content.split('\n');
|
|
388
822
|
const spawnPrefix = '->relay:spawn';
|
|
389
823
|
const releasePrefix = '->relay:release';
|
|
390
824
|
for (const line of lines) {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
825
|
+
let trimmed = line.trim();
|
|
826
|
+
// Strip bullet/prompt prefixes but PRESERVE the ->relay: pattern
|
|
827
|
+
// Look for ->relay: in the line and only strip what comes before it
|
|
828
|
+
const relayIdx = trimmed.indexOf('->relay:');
|
|
829
|
+
if (relayIdx > 0) {
|
|
830
|
+
// There's content before ->relay: - check if it's just prefix chars
|
|
831
|
+
const beforeRelay = trimmed.substring(0, relayIdx);
|
|
832
|
+
// Only strip if the prefix is just bullets/prompts/whitespace
|
|
833
|
+
if (/^[\s●•◦‣⁃⏺◆◇○□■│┃┆┇┊┋╎╏✦→➜›»$%#*]+$/.test(beforeRelay)) {
|
|
834
|
+
const originalTrimmed = trimmed;
|
|
835
|
+
trimmed = trimmed.substring(relayIdx);
|
|
836
|
+
console.log(`[pty:${this.config.name}] [SPAWN-DEBUG] Stripped prefix: "${originalTrimmed.substring(0, 60)}" -> "${trimmed.substring(0, 60)}"`);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
// Skip escaped commands: \->relay:spawn should not trigger
|
|
840
|
+
if (trimmed.includes('\\->relay:')) {
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
// If we're in fenced spawn mode, accumulate lines until we see >>>
|
|
844
|
+
if (this.pendingFencedSpawn) {
|
|
845
|
+
const closeIdx = trimmed.indexOf('>>>');
|
|
846
|
+
if (closeIdx !== -1) {
|
|
847
|
+
// Add content before >>> to task
|
|
848
|
+
const contentBeforeClose = trimmed.substring(0, closeIdx);
|
|
849
|
+
if (contentBeforeClose) {
|
|
850
|
+
this.pendingFencedSpawn.taskLines.push(contentBeforeClose);
|
|
851
|
+
}
|
|
852
|
+
// Execute the spawn with accumulated task
|
|
853
|
+
const { name, cli, taskLines } = this.pendingFencedSpawn;
|
|
854
|
+
const taskStr = taskLines.join('\n').trim();
|
|
855
|
+
const spawnKey = `${name}:${cli}`;
|
|
856
|
+
if (!this.processedSpawnCommands.has(spawnKey)) {
|
|
857
|
+
this.processedSpawnCommands.add(spawnKey);
|
|
858
|
+
console.log(`[pty:${this.config.name}] Spawn command (fenced): ${name} (${cli}) - "${taskStr.substring(0, 50)}..."`);
|
|
859
|
+
this.executeSpawn(name, cli, taskStr);
|
|
860
|
+
}
|
|
861
|
+
this.pendingFencedSpawn = null;
|
|
862
|
+
}
|
|
863
|
+
else {
|
|
864
|
+
// Accumulate line as part of task
|
|
865
|
+
this.pendingFencedSpawn.taskLines.push(line);
|
|
866
|
+
}
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
// Check for fenced spawn start: ->relay:spawn Name [cli] <<<
|
|
870
|
+
// STRICT: Must be at start of line (after whitespace)
|
|
871
|
+
if (canSpawn && trimmed.startsWith(spawnPrefix)) {
|
|
872
|
+
const afterSpawn = trimmed.substring(spawnPrefix.length).trim();
|
|
873
|
+
console.log(`[pty:${this.config.name}] [SPAWN-DEBUG] Detected spawn prefix, afterSpawn: "${afterSpawn.substring(0, 60)}"`);
|
|
874
|
+
// Check for fenced format: Name [cli] <<< (CLI optional, defaults to 'claude')
|
|
875
|
+
const fencedMatch = afterSpawn.match(/^(\S+)(?:\s+(\S+))?\s+<<<(.*)$/);
|
|
876
|
+
console.log(`[pty:${this.config.name}] [SPAWN-DEBUG] Fenced match result: ${fencedMatch ? 'MATCHED' : 'NO MATCH'}`);
|
|
877
|
+
if (fencedMatch) {
|
|
878
|
+
const [, name, cliOrUndefined, inlineContent] = fencedMatch;
|
|
879
|
+
let cli = cliOrUndefined || 'claude';
|
|
880
|
+
// STRICT: Validate agent name (PascalCase) and CLI type
|
|
881
|
+
if (!this.isValidAgentName(name)) {
|
|
882
|
+
console.warn(`[pty:${this.config.name}] Invalid agent name format, skipping: name=${name} (must be PascalCase)`);
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
885
|
+
if (!this.isValidCliType(cli)) {
|
|
886
|
+
console.warn(`[pty:${this.config.name}] Unknown CLI type, using default: cli=${cli}`);
|
|
887
|
+
cli = 'claude';
|
|
888
|
+
}
|
|
889
|
+
// Check if fence closes on same line
|
|
890
|
+
const inlineCloseIdx = inlineContent.indexOf('>>>');
|
|
891
|
+
if (inlineCloseIdx !== -1) {
|
|
892
|
+
// Single line fenced: extract task between <<< and >>>
|
|
893
|
+
const taskStr = inlineContent.substring(0, inlineCloseIdx).trim();
|
|
894
|
+
const spawnKey = `${name}:${cli}`;
|
|
895
|
+
if (!this.processedSpawnCommands.has(spawnKey)) {
|
|
896
|
+
this.processedSpawnCommands.add(spawnKey);
|
|
897
|
+
console.log(`[pty:${this.config.name}] Spawn command (fenced): ${name} (${cli}) - "${taskStr.substring(0, 50)}..."`);
|
|
898
|
+
this.executeSpawn(name, cli, taskStr);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
else {
|
|
902
|
+
// Start multi-line fenced mode - but only if not already processed
|
|
903
|
+
const spawnKey = `${name}:${cli}`;
|
|
904
|
+
if (this.processedSpawnCommands.has(spawnKey)) {
|
|
905
|
+
// Already processed this spawn, skip the fenced capture
|
|
906
|
+
continue;
|
|
907
|
+
}
|
|
908
|
+
this.pendingFencedSpawn = {
|
|
909
|
+
name,
|
|
910
|
+
cli,
|
|
911
|
+
taskLines: inlineContent.trim() ? [inlineContent.trim()] : [],
|
|
912
|
+
};
|
|
913
|
+
console.log(`[pty:${this.config.name}] Starting fenced spawn capture: ${name} (${cli})`);
|
|
914
|
+
}
|
|
915
|
+
continue;
|
|
916
|
+
}
|
|
917
|
+
// Parse single-line format: WorkerName [cli] [task]
|
|
918
|
+
// CLI defaults to 'claude' if not provided
|
|
396
919
|
const parts = afterSpawn.split(/\s+/);
|
|
397
|
-
if (parts.length >=
|
|
920
|
+
if (parts.length >= 1) {
|
|
398
921
|
const name = parts[0];
|
|
399
|
-
|
|
400
|
-
|
|
922
|
+
// CLI is optional - defaults to 'claude'
|
|
923
|
+
let cli = parts[1] || 'claude';
|
|
924
|
+
// Task is everything after cli (if cli was provided) or after name (if cli was omitted)
|
|
401
925
|
let task = '';
|
|
402
|
-
|
|
403
|
-
|
|
926
|
+
const taskStartIndex = parts[1] ? 2 : 1;
|
|
927
|
+
if (parts.length > taskStartIndex) {
|
|
928
|
+
const taskPart = parts.slice(taskStartIndex).join(' ');
|
|
404
929
|
// Remove surrounding quotes if present
|
|
405
930
|
const quoteMatch = taskPart.match(/^["'](.*)["']$/);
|
|
406
931
|
task = quoteMatch ? quoteMatch[1] : taskPart;
|
|
407
932
|
}
|
|
408
|
-
if (name
|
|
933
|
+
if (name) {
|
|
934
|
+
// STRICT: Validate agent name (PascalCase) and CLI type
|
|
935
|
+
if (!this.isValidAgentName(name)) {
|
|
936
|
+
// Don't log warning for documentation text - just silently skip
|
|
937
|
+
continue;
|
|
938
|
+
}
|
|
939
|
+
if (!this.isValidCliType(cli)) {
|
|
940
|
+
// Default CLI 'claude' should always be valid, but validate anyway
|
|
941
|
+
console.warn(`[pty:${this.config.name}] Unknown CLI type, using default: cli=${cli}, defaulting to 'claude'`);
|
|
942
|
+
cli = 'claude';
|
|
943
|
+
}
|
|
409
944
|
const spawnKey = `${name}:${cli}`;
|
|
410
945
|
if (!this.processedSpawnCommands.has(spawnKey)) {
|
|
411
946
|
this.processedSpawnCommands.add(spawnKey);
|
|
@@ -416,13 +951,18 @@ export class PtyWrapper extends EventEmitter {
|
|
|
416
951
|
continue;
|
|
417
952
|
}
|
|
418
953
|
// Check for release command
|
|
419
|
-
|
|
420
|
-
if (
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
this.
|
|
954
|
+
// STRICT: Must be at start of line (after whitespace)
|
|
955
|
+
if (trimmed.startsWith(releasePrefix)) {
|
|
956
|
+
console.log(`[pty:${this.config.name}] [RELEASE-DEBUG] Release prefix detected, canRelease=${canRelease}`);
|
|
957
|
+
if (canRelease) {
|
|
958
|
+
const afterRelease = trimmed.substring(releasePrefix.length).trim();
|
|
959
|
+
const name = afterRelease.split(/\s+/)[0];
|
|
960
|
+
console.log(`[pty:${this.config.name}] [RELEASE-DEBUG] Parsed name: ${name}, isValidName=${name ? this.isValidAgentName(name) : false}, alreadyProcessed=${this.processedReleaseCommands.has(name)}`);
|
|
961
|
+
// STRICT: Validate agent name format
|
|
962
|
+
if (name && this.isValidAgentName(name) && !this.processedReleaseCommands.has(name)) {
|
|
963
|
+
this.processedReleaseCommands.add(name);
|
|
964
|
+
this.executeRelease(name);
|
|
965
|
+
}
|
|
426
966
|
}
|
|
427
967
|
}
|
|
428
968
|
}
|
|
@@ -431,6 +971,8 @@ export class PtyWrapper extends EventEmitter {
|
|
|
431
971
|
* Execute spawn via API or callback
|
|
432
972
|
*/
|
|
433
973
|
async executeSpawn(name, cli, task) {
|
|
974
|
+
console.log(`[pty:${this.config.name}] [SPAWN-DEBUG] executeSpawn called: name=${name}, cli=${cli}, task="${task.substring(0, 50)}..."`);
|
|
975
|
+
console.log(`[pty:${this.config.name}] [SPAWN-DEBUG] dashboardPort=${this.config.dashboardPort}, hasOnSpawn=${!!this.config.onSpawn}`);
|
|
434
976
|
if (this.config.dashboardPort) {
|
|
435
977
|
// Use dashboard API for spawning (works from spawned agents)
|
|
436
978
|
try {
|
|
@@ -468,7 +1010,7 @@ export class PtyWrapper extends EventEmitter {
|
|
|
468
1010
|
if (this.config.dashboardPort) {
|
|
469
1011
|
// Use dashboard API for releasing
|
|
470
1012
|
try {
|
|
471
|
-
const response = await fetch(`http://localhost:${this.config.dashboardPort}/api/spawned/${name}`, {
|
|
1013
|
+
const response = await fetch(`http://localhost:${this.config.dashboardPort}/api/spawned/${encodeURIComponent(name)}`, {
|
|
472
1014
|
method: 'DELETE',
|
|
473
1015
|
});
|
|
474
1016
|
const result = await response.json();
|
|
@@ -498,11 +1040,67 @@ export class PtyWrapper extends EventEmitter {
|
|
|
498
1040
|
* @param originalTo - The original 'to' field from sender. '*' indicates this was a broadcast message.
|
|
499
1041
|
*/
|
|
500
1042
|
handleIncomingMessage(from, payload, messageId, meta, originalTo) {
|
|
1043
|
+
// Deduplicate: skip if we've already received this message
|
|
1044
|
+
if (this.receivedMessageIds.has(messageId)) {
|
|
1045
|
+
console.log(`[pty:${this.config.name}] Skipping duplicate message: ${messageId.substring(0, 8)}`);
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
this.receivedMessageIds.add(messageId);
|
|
1049
|
+
// Limit dedup set size to prevent memory leak
|
|
1050
|
+
if (this.receivedMessageIds.size > 1000) {
|
|
1051
|
+
const oldest = this.receivedMessageIds.values().next().value;
|
|
1052
|
+
if (oldest)
|
|
1053
|
+
this.receivedMessageIds.delete(oldest);
|
|
1054
|
+
}
|
|
501
1055
|
this.messageQueue.push({ from, body: payload.body, messageId, thread: payload.thread, importance: meta?.importance, data: payload.data, originalTo });
|
|
502
1056
|
this.processMessageQueue();
|
|
1057
|
+
// Dispatch message received hook
|
|
1058
|
+
this.hookRegistry.dispatchMessageReceived(from, payload.body, messageId).catch(err => {
|
|
1059
|
+
console.error(`[pty:${this.config.name}] Message received hook error:`, err);
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Wait for output to stabilize before injection.
|
|
1064
|
+
* Returns true if output has been stable for the required duration.
|
|
1065
|
+
*/
|
|
1066
|
+
async waitForOutputStable() {
|
|
1067
|
+
const startTime = Date.now();
|
|
1068
|
+
let stablePolls = 0;
|
|
1069
|
+
let lastBufferLength = this.rawBuffer.length;
|
|
1070
|
+
while (Date.now() - startTime < INJECTION_CONSTANTS.STABILITY_TIMEOUT_MS) {
|
|
1071
|
+
await sleep(INJECTION_CONSTANTS.STABILITY_POLL_MS);
|
|
1072
|
+
const timeSinceOutput = Date.now() - this.lastOutputTime;
|
|
1073
|
+
const bufferUnchanged = this.rawBuffer.length === lastBufferLength;
|
|
1074
|
+
// Consider stable if no output for at least one poll interval
|
|
1075
|
+
if (timeSinceOutput >= INJECTION_CONSTANTS.STABILITY_POLL_MS && bufferUnchanged) {
|
|
1076
|
+
stablePolls++;
|
|
1077
|
+
if (stablePolls >= INJECTION_CONSTANTS.REQUIRED_STABLE_POLLS) {
|
|
1078
|
+
return true;
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
else {
|
|
1082
|
+
stablePolls = 0;
|
|
1083
|
+
lastBufferLength = this.rawBuffer.length;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
// Timeout - return true anyway to avoid blocking forever
|
|
1087
|
+
console.warn(`[pty:${this.config.name}] Stability timeout, proceeding with injection`);
|
|
1088
|
+
return true;
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Check if the agent process is still alive and responsive.
|
|
1092
|
+
*/
|
|
1093
|
+
isAgentAlive() {
|
|
1094
|
+
return this.running && this.ptyProcess !== undefined;
|
|
503
1095
|
}
|
|
504
1096
|
/**
|
|
505
|
-
* Process queued messages
|
|
1097
|
+
* Process queued messages with reliability improvements:
|
|
1098
|
+
* 1. Wait for output stability before injection
|
|
1099
|
+
* 2. Verify injection appeared in output
|
|
1100
|
+
* 3. Retry with backoff on failure
|
|
1101
|
+
* 4. Fall back to logging on complete failure
|
|
1102
|
+
*
|
|
1103
|
+
* Uses shared injection logic with PTY-specific callbacks.
|
|
506
1104
|
*/
|
|
507
1105
|
async processMessageQueue() {
|
|
508
1106
|
// Wait until instructions have been injected and agent is ready
|
|
@@ -510,8 +1108,11 @@ export class PtyWrapper extends EventEmitter {
|
|
|
510
1108
|
return;
|
|
511
1109
|
if (this.isInjecting || this.messageQueue.length === 0)
|
|
512
1110
|
return;
|
|
513
|
-
|
|
1111
|
+
// Health check: is agent still alive?
|
|
1112
|
+
if (!this.isAgentAlive()) {
|
|
1113
|
+
console.error(`[pty:${this.config.name}] Agent not alive, cannot inject messages`);
|
|
514
1114
|
return;
|
|
1115
|
+
}
|
|
515
1116
|
this.isInjecting = true;
|
|
516
1117
|
const msg = this.messageQueue.shift();
|
|
517
1118
|
if (!msg) {
|
|
@@ -519,31 +1120,68 @@ export class PtyWrapper extends EventEmitter {
|
|
|
519
1120
|
return;
|
|
520
1121
|
}
|
|
521
1122
|
try {
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
1123
|
+
// Wait for output to stabilize before injecting
|
|
1124
|
+
await this.waitForOutputStable();
|
|
1125
|
+
// For Gemini: check if at shell prompt, skip injection to avoid shell execution
|
|
1126
|
+
if (this.cliType === 'gemini') {
|
|
1127
|
+
const recentOutput = this.rawBuffer.slice(-200);
|
|
1128
|
+
const lastLine = recentOutput.split('\n').filter(l => l.trim()).pop() || '';
|
|
1129
|
+
if (CLI_QUIRKS.isShellPrompt(lastLine)) {
|
|
1130
|
+
console.log(`[pty:${this.config.name}] Gemini at shell prompt, re-queuing message`);
|
|
1131
|
+
this.messageQueue.unshift(msg);
|
|
1132
|
+
this.isInjecting = false;
|
|
1133
|
+
setTimeout(() => this.processMessageQueue(), 2000);
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
// Build injection string using shared utility
|
|
1138
|
+
let injection = buildInjectionString(msg);
|
|
1139
|
+
// Gemini-specific: wrap in backticks to prevent shell keyword interpretation
|
|
1140
|
+
if (this.cliType === 'gemini') {
|
|
1141
|
+
// Extract the message body part and wrap it
|
|
1142
|
+
const colonIdx = injection.indexOf(': ');
|
|
1143
|
+
if (colonIdx > 0) {
|
|
1144
|
+
const prefix = injection.substring(0, colonIdx + 2);
|
|
1145
|
+
const body = injection.substring(colonIdx + 2);
|
|
1146
|
+
injection = prefix + CLI_QUIRKS.wrapForGemini(body);
|
|
539
1147
|
}
|
|
540
1148
|
}
|
|
541
|
-
const
|
|
542
|
-
//
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
1149
|
+
const shortId = msg.messageId.substring(0, 8);
|
|
1150
|
+
// Create callbacks for shared injection logic
|
|
1151
|
+
const callbacks = {
|
|
1152
|
+
getOutput: async () => {
|
|
1153
|
+
// Look at last 2000 chars to avoid scanning entire buffer
|
|
1154
|
+
return this.rawBuffer.slice(-2000);
|
|
1155
|
+
},
|
|
1156
|
+
performInjection: async (inj) => {
|
|
1157
|
+
if (!this.ptyProcess || !this.running) {
|
|
1158
|
+
throw new Error('PTY process not running');
|
|
1159
|
+
}
|
|
1160
|
+
// Write message to PTY, then send Enter separately after a small delay
|
|
1161
|
+
this.ptyProcess.write(inj);
|
|
1162
|
+
await sleep(INJECTION_CONSTANTS.ENTER_DELAY_MS);
|
|
1163
|
+
this.ptyProcess.write('\r');
|
|
1164
|
+
},
|
|
1165
|
+
log: (message) => console.log(`[pty:${this.config.name}] ${message}`),
|
|
1166
|
+
logError: (message) => console.error(`[pty:${this.config.name}] ${message}`),
|
|
1167
|
+
getMetrics: () => this.injectionMetrics,
|
|
1168
|
+
// Skip verification for PTY-based injection - CLIs don't echo input back
|
|
1169
|
+
// so verification will always fail. Trust that pty.write() succeeds.
|
|
1170
|
+
skipVerification: true,
|
|
1171
|
+
};
|
|
1172
|
+
// Inject with retry and verification using shared logic
|
|
1173
|
+
const result = await sharedInjectWithRetry(injection, shortId, msg.from, callbacks);
|
|
1174
|
+
if (!result.success) {
|
|
1175
|
+
// Log the failed message for debugging/recovery
|
|
1176
|
+
console.error(`[pty:${this.config.name}] Message delivery failed after ${result.attempts} attempts: ` +
|
|
1177
|
+
`from=${msg.from} id=${shortId}`);
|
|
1178
|
+
// Emit event for external monitoring (e.g., dashboard)
|
|
1179
|
+
this.emit('injection-failed', {
|
|
1180
|
+
messageId: msg.messageId,
|
|
1181
|
+
from: msg.from,
|
|
1182
|
+
attempts: result.attempts,
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
547
1185
|
}
|
|
548
1186
|
catch (err) {
|
|
549
1187
|
console.error(`[pty:${this.config.name}] Injection failed: ${err.message}`);
|
|
@@ -552,24 +1190,34 @@ export class PtyWrapper extends EventEmitter {
|
|
|
552
1190
|
this.isInjecting = false;
|
|
553
1191
|
// Process next message if any
|
|
554
1192
|
if (this.messageQueue.length > 0) {
|
|
555
|
-
setTimeout(() => this.processMessageQueue(),
|
|
1193
|
+
setTimeout(() => this.processMessageQueue(), INJECTION_CONSTANTS.QUEUE_PROCESS_DELAY_MS);
|
|
556
1194
|
}
|
|
557
1195
|
}
|
|
558
1196
|
}
|
|
559
1197
|
/**
|
|
560
|
-
*
|
|
1198
|
+
* Queue minimal agent identity notification as the first message.
|
|
1199
|
+
*
|
|
1200
|
+
* Full protocol instructions are in ~/.claude/CLAUDE.md (set up by entrypoint.sh).
|
|
1201
|
+
* We only inject a brief identity message here to let the agent know its name
|
|
1202
|
+
* and that it's connected to the relay.
|
|
561
1203
|
*/
|
|
562
1204
|
injectInstructions() {
|
|
563
|
-
if (!this.running
|
|
1205
|
+
if (!this.running)
|
|
1206
|
+
return;
|
|
1207
|
+
// Guard: Only inject once per session
|
|
1208
|
+
if (this.instructionsInjected) {
|
|
1209
|
+
console.log(`[pty:${this.config.name}] Init instructions already injected, skipping`);
|
|
564
1210
|
return;
|
|
565
|
-
const escapedPrefix = '\\' + this.relayPrefix;
|
|
566
|
-
const instructions = `[Agent Relay] You are "${this.config.name}" - connected for real-time messaging. SEND: ${escapedPrefix}AgentName message. PROTOCOL: (1) Wait for task via relay. (2) ACK receipt before starting. (3) Send "DONE: <summary>" when complete, then wait for next task.`;
|
|
567
|
-
try {
|
|
568
|
-
this.ptyProcess.write(instructions + '\r');
|
|
569
|
-
}
|
|
570
|
-
catch {
|
|
571
|
-
// Silent fail
|
|
572
1211
|
}
|
|
1212
|
+
this.instructionsInjected = true;
|
|
1213
|
+
// Minimal notification - full protocol is in ~/.claude/CLAUDE.md
|
|
1214
|
+
const notification = `You are agent "${this.config.name}" connected to Agent Relay. See CLAUDE.md for the messaging protocol. ACK messages, do work, send DONE when complete.`;
|
|
1215
|
+
// Queue as first message from "system" - will be injected when CLI is ready
|
|
1216
|
+
this.messageQueue.unshift({
|
|
1217
|
+
from: 'system',
|
|
1218
|
+
body: notification,
|
|
1219
|
+
messageId: `init-${Date.now()}`,
|
|
1220
|
+
});
|
|
573
1221
|
}
|
|
574
1222
|
/**
|
|
575
1223
|
* Write directly to the PTY
|
|
@@ -597,38 +1245,73 @@ export class PtyWrapper extends EventEmitter {
|
|
|
597
1245
|
/**
|
|
598
1246
|
* Stop the agent process
|
|
599
1247
|
*/
|
|
600
|
-
stop() {
|
|
1248
|
+
async stop() {
|
|
601
1249
|
if (!this.running)
|
|
602
1250
|
return;
|
|
603
1251
|
this.running = false;
|
|
1252
|
+
// Auto-save continuity state before stopping
|
|
1253
|
+
// Pass sessionEndData to populate handoff (fixes empty handoff issue)
|
|
1254
|
+
if (this.continuity) {
|
|
1255
|
+
try {
|
|
1256
|
+
await this.continuity.autoSave(this.config.name, 'session_end', this.sessionEndData);
|
|
1257
|
+
}
|
|
1258
|
+
catch (err) {
|
|
1259
|
+
console.error(`[pty:${this.config.name}] Continuity auto-save failed:`, err);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
// Dispatch session end hook (handles trajectory completion)
|
|
1263
|
+
try {
|
|
1264
|
+
await this.hookRegistry.dispatchSessionEnd(0, true);
|
|
1265
|
+
}
|
|
1266
|
+
catch (err) {
|
|
1267
|
+
console.error(`[pty:${this.config.name}] Session end hook error:`, err);
|
|
1268
|
+
}
|
|
604
1269
|
if (this.ptyProcess) {
|
|
605
1270
|
// Try graceful termination first
|
|
606
1271
|
this.ptyProcess.write('\x03'); // Ctrl+C
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
}, 1000);
|
|
1272
|
+
await sleep(1000);
|
|
1273
|
+
if (this.ptyProcess) {
|
|
1274
|
+
this.ptyProcess.kill();
|
|
1275
|
+
}
|
|
612
1276
|
}
|
|
613
1277
|
this.closeLogStream();
|
|
614
1278
|
this.client.destroy();
|
|
1279
|
+
this.hookRegistry.destroy();
|
|
615
1280
|
}
|
|
616
1281
|
/**
|
|
617
1282
|
* Kill the process immediately
|
|
618
1283
|
*/
|
|
619
|
-
kill() {
|
|
1284
|
+
async kill() {
|
|
620
1285
|
this.running = false;
|
|
1286
|
+
// Auto-save continuity state before killing (with timeout to avoid hanging)
|
|
1287
|
+
// Pass sessionEndData if available (may have been parsed before kill)
|
|
1288
|
+
if (this.continuity) {
|
|
1289
|
+
try {
|
|
1290
|
+
await Promise.race([
|
|
1291
|
+
this.continuity.autoSave(this.config.name, 'crash', this.sessionEndData),
|
|
1292
|
+
sleep(2000), // 2s timeout for crash saves
|
|
1293
|
+
]);
|
|
1294
|
+
}
|
|
1295
|
+
catch (err) {
|
|
1296
|
+
console.error(`[pty:${this.config.name}] Continuity auto-save failed:`, err);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
// Dispatch session end hook (forced termination, with timeout)
|
|
1300
|
+
try {
|
|
1301
|
+
await Promise.race([
|
|
1302
|
+
this.hookRegistry.dispatchSessionEnd(undefined, false),
|
|
1303
|
+
sleep(1000), // 1s timeout for hooks on kill
|
|
1304
|
+
]);
|
|
1305
|
+
}
|
|
1306
|
+
catch (err) {
|
|
1307
|
+
console.error(`[pty:${this.config.name}] Session end hook error:`, err);
|
|
1308
|
+
}
|
|
621
1309
|
if (this.ptyProcess) {
|
|
622
1310
|
this.ptyProcess.kill();
|
|
623
1311
|
}
|
|
624
1312
|
this.closeLogStream();
|
|
625
1313
|
this.client.destroy();
|
|
626
|
-
|
|
627
|
-
/**
|
|
628
|
-
* Sleep helper
|
|
629
|
-
*/
|
|
630
|
-
sleep(ms) {
|
|
631
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1314
|
+
this.hookRegistry.destroy();
|
|
632
1315
|
}
|
|
633
1316
|
/**
|
|
634
1317
|
* Close the log file stream
|
|
@@ -652,5 +1335,159 @@ export class PtyWrapper extends EventEmitter {
|
|
|
652
1335
|
get logPath() {
|
|
653
1336
|
return this.logFilePath;
|
|
654
1337
|
}
|
|
1338
|
+
/**
|
|
1339
|
+
* Track significant outputs and inject summary reminder if needed.
|
|
1340
|
+
* Works with any CLI (Claude, Gemini, Codex, etc.)
|
|
1341
|
+
*/
|
|
1342
|
+
trackOutputAndRemind(data) {
|
|
1343
|
+
// Disabled if config.summaryReminder === false or env RELAY_SUMMARY_REMINDER_ENABLED=false
|
|
1344
|
+
if (this.config.summaryReminder === false)
|
|
1345
|
+
return;
|
|
1346
|
+
if (process.env.RELAY_SUMMARY_REMINDER_ENABLED === 'false')
|
|
1347
|
+
return;
|
|
1348
|
+
const config = this.config.summaryReminder ?? {};
|
|
1349
|
+
// Env vars take precedence over config, config takes precedence over defaults
|
|
1350
|
+
const intervalMinutes = process.env.RELAY_SUMMARY_INTERVAL_MINUTES
|
|
1351
|
+
? parseInt(process.env.RELAY_SUMMARY_INTERVAL_MINUTES, 10)
|
|
1352
|
+
: (config.intervalMinutes ?? 15);
|
|
1353
|
+
const minOutputs = process.env.RELAY_SUMMARY_MIN_OUTPUTS
|
|
1354
|
+
? parseInt(process.env.RELAY_SUMMARY_MIN_OUTPUTS, 10)
|
|
1355
|
+
: (config.minOutputs ?? 50);
|
|
1356
|
+
// Only count "significant" outputs (more than just whitespace/control chars)
|
|
1357
|
+
const cleanData = stripAnsi(data).trim();
|
|
1358
|
+
if (cleanData.length > 20) {
|
|
1359
|
+
this.outputsSinceSummary++;
|
|
1360
|
+
}
|
|
1361
|
+
// Check if we should remind
|
|
1362
|
+
const minutesSinceSummary = (Date.now() - this.lastSummaryTime) / (1000 * 60);
|
|
1363
|
+
const shouldRemind = minutesSinceSummary >= intervalMinutes &&
|
|
1364
|
+
this.outputsSinceSummary >= minOutputs;
|
|
1365
|
+
if (shouldRemind && this.running && this.ptyProcess) {
|
|
1366
|
+
// Reset counters before injecting (prevent spam)
|
|
1367
|
+
this.lastSummaryTime = Date.now();
|
|
1368
|
+
this.outputsSinceSummary = 0;
|
|
1369
|
+
// Inject reminder as a relay-style message
|
|
1370
|
+
// IMPORTANT: Must be single-line - embedded newlines cause the message to span
|
|
1371
|
+
// multiple lines in the CLI input buffer, and the final Enter only submits
|
|
1372
|
+
// the last (empty) line. Regular relay messages are also single-line (see buildInjectionString).
|
|
1373
|
+
const reminder = `[Agent Relay] It's been ${Math.round(minutesSinceSummary)} minutes. Please output a [[SUMMARY]] block to checkpoint your progress: [[SUMMARY]]{"currentTask": "...", "completedTasks": [...], "context": "..."}[[/SUMMARY]]`;
|
|
1374
|
+
// Delay slightly to not interrupt current output, then write + Enter
|
|
1375
|
+
setTimeout(async () => {
|
|
1376
|
+
if (this.ptyProcess && this.running) {
|
|
1377
|
+
this.ptyProcess.write(reminder);
|
|
1378
|
+
await sleep(INJECTION_CONSTANTS.ENTER_DELAY_MS);
|
|
1379
|
+
this.ptyProcess.write('\r');
|
|
1380
|
+
}
|
|
1381
|
+
}, 1000);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
/**
|
|
1385
|
+
* Check for [[SUMMARY]] blocks and emit 'summary' event.
|
|
1386
|
+
* Allows cloud services to persist summaries without hardcoding storage.
|
|
1387
|
+
* Also updates the local continuity ledger for session recovery.
|
|
1388
|
+
*/
|
|
1389
|
+
checkForSummaryAndEmit(content) {
|
|
1390
|
+
const result = parseSummaryWithDetails(content);
|
|
1391
|
+
// No SUMMARY block found
|
|
1392
|
+
if (!result.found)
|
|
1393
|
+
return;
|
|
1394
|
+
// Dedup based on raw content - prevents repeated event emissions for same summary
|
|
1395
|
+
if (result.rawContent === this.lastSummaryRawContent)
|
|
1396
|
+
return;
|
|
1397
|
+
this.lastSummaryRawContent = result.rawContent || '';
|
|
1398
|
+
// Reset reminder counters on any summary (even invalid JSON)
|
|
1399
|
+
this.lastSummaryTime = Date.now();
|
|
1400
|
+
this.outputsSinceSummary = 0;
|
|
1401
|
+
// Invalid JSON - log warning
|
|
1402
|
+
if (!result.valid) {
|
|
1403
|
+
console.warn(`[pty:${this.config.name}] Invalid JSON in SUMMARY block`);
|
|
1404
|
+
return;
|
|
1405
|
+
}
|
|
1406
|
+
const summary = result.summary;
|
|
1407
|
+
// Save to local continuity ledger for session recovery
|
|
1408
|
+
// This ensures the ledger has actual data instead of placeholders
|
|
1409
|
+
if (this.continuity) {
|
|
1410
|
+
this.saveSummaryToLedger(summary).catch(err => {
|
|
1411
|
+
console.error(`[pty:${this.config.name}] Failed to save summary to ledger:`, err);
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
// Emit event for external handlers (cloud services, dashboard, etc.)
|
|
1415
|
+
this.emit('summary', {
|
|
1416
|
+
agentName: this.config.name,
|
|
1417
|
+
summary,
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Save a parsed summary to the continuity ledger.
|
|
1422
|
+
* Maps summary fields to ledger fields for session recovery.
|
|
1423
|
+
*/
|
|
1424
|
+
async saveSummaryToLedger(summary) {
|
|
1425
|
+
if (!this.continuity)
|
|
1426
|
+
return;
|
|
1427
|
+
const updates = {};
|
|
1428
|
+
// Map summary fields to ledger fields
|
|
1429
|
+
if (summary.currentTask) {
|
|
1430
|
+
updates.currentTask = summary.currentTask;
|
|
1431
|
+
}
|
|
1432
|
+
if (summary.completedTasks && summary.completedTasks.length > 0) {
|
|
1433
|
+
updates.completed = summary.completedTasks;
|
|
1434
|
+
}
|
|
1435
|
+
if (summary.context) {
|
|
1436
|
+
// Store context in inProgress as "next steps" hint
|
|
1437
|
+
updates.inProgress = [summary.context];
|
|
1438
|
+
}
|
|
1439
|
+
if (summary.files && summary.files.length > 0) {
|
|
1440
|
+
updates.fileContext = summary.files.map((f) => ({ path: f }));
|
|
1441
|
+
}
|
|
1442
|
+
// Only save if we have meaningful updates
|
|
1443
|
+
if (Object.keys(updates).length > 0) {
|
|
1444
|
+
await this.continuity.saveLedger(this.config.name, updates);
|
|
1445
|
+
console.log(`[pty:${this.config.name}] Saved summary to continuity ledger`);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
/**
|
|
1449
|
+
* Check for [[SESSION_END]] blocks and emit 'session-end' event.
|
|
1450
|
+
* Allows cloud services to handle session closure without hardcoding storage.
|
|
1451
|
+
* Also stores the data for use in autoSave to populate handoff.
|
|
1452
|
+
*/
|
|
1453
|
+
checkForSessionEndAndEmit(content) {
|
|
1454
|
+
if (this.sessionEndProcessed)
|
|
1455
|
+
return; // Only emit once per session
|
|
1456
|
+
const sessionEnd = parseSessionEndFromOutput(content);
|
|
1457
|
+
if (!sessionEnd)
|
|
1458
|
+
return;
|
|
1459
|
+
this.sessionEndProcessed = true;
|
|
1460
|
+
// Store SESSION_END data for use in autoSave (fixes empty handoff issue)
|
|
1461
|
+
this.sessionEndData = sessionEnd;
|
|
1462
|
+
// Emit event for external handlers
|
|
1463
|
+
this.emit('session-end', {
|
|
1464
|
+
agentName: this.config.name,
|
|
1465
|
+
marker: sessionEnd,
|
|
1466
|
+
});
|
|
1467
|
+
}
|
|
1468
|
+
/**
|
|
1469
|
+
* Reset session-specific state for wrapper reuse.
|
|
1470
|
+
* Call this when starting a new session with the same wrapper instance.
|
|
1471
|
+
*/
|
|
1472
|
+
resetSessionState() {
|
|
1473
|
+
this.sessionEndProcessed = false;
|
|
1474
|
+
this.lastSummaryRawContent = '';
|
|
1475
|
+
this.sessionEndData = undefined;
|
|
1476
|
+
}
|
|
1477
|
+
/**
|
|
1478
|
+
* Get injection reliability metrics
|
|
1479
|
+
*/
|
|
1480
|
+
getInjectionMetrics() {
|
|
1481
|
+
return {
|
|
1482
|
+
...this.injectionMetrics,
|
|
1483
|
+
successRate: calculateSuccessRate(this.injectionMetrics),
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
/**
|
|
1487
|
+
* Get count of pending messages in queue
|
|
1488
|
+
*/
|
|
1489
|
+
get pendingMessageCount() {
|
|
1490
|
+
return this.messageQueue.length;
|
|
1491
|
+
}
|
|
655
1492
|
}
|
|
656
1493
|
//# sourceMappingURL=pty-wrapper.js.map
|