agent-relay 1.2.3 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/.trajectories/agent-relay-322-324.md +17 -0
  2. package/.trajectories/completed/2026-01/traj_03zupyv1s7b9.json +49 -0
  3. package/.trajectories/completed/2026-01/traj_03zupyv1s7b9.md +31 -0
  4. package/.trajectories/completed/2026-01/traj_0zacdjl1g4ht.json +125 -0
  5. package/.trajectories/completed/2026-01/traj_0zacdjl1g4ht.md +62 -0
  6. package/.trajectories/completed/2026-01/traj_33iuy72sezbk.json +49 -0
  7. package/.trajectories/completed/2026-01/traj_33iuy72sezbk.md +31 -0
  8. package/.trajectories/completed/2026-01/traj_5ammh5qtvklq.json +77 -0
  9. package/.trajectories/completed/2026-01/traj_5ammh5qtvklq.md +42 -0
  10. package/.trajectories/completed/2026-01/traj_6mieijqyvaag.json +77 -0
  11. package/.trajectories/completed/2026-01/traj_6mieijqyvaag.md +42 -0
  12. package/.trajectories/completed/2026-01/traj_78ffm31jn3uk.json +77 -0
  13. package/.trajectories/completed/2026-01/traj_78ffm31jn3uk.md +42 -0
  14. package/.trajectories/completed/2026-01/traj_94gnp3k30goq.json +66 -0
  15. package/.trajectories/completed/2026-01/traj_94gnp3k30goq.md +36 -0
  16. package/.trajectories/completed/2026-01/traj_avqeghu6pz5a.json +40 -0
  17. package/.trajectories/completed/2026-01/traj_avqeghu6pz5a.md +22 -0
  18. package/.trajectories/completed/2026-01/traj_dcsp9s8y01ra.json +121 -0
  19. package/.trajectories/completed/2026-01/traj_dcsp9s8y01ra.md +29 -0
  20. package/.trajectories/completed/2026-01/traj_fhx9irlckht6.json +53 -0
  21. package/.trajectories/completed/2026-01/traj_fhx9irlckht6.md +32 -0
  22. package/.trajectories/completed/2026-01/traj_fqduidx3xbtp.json +101 -0
  23. package/.trajectories/completed/2026-01/traj_fqduidx3xbtp.md +52 -0
  24. package/.trajectories/completed/2026-01/traj_hf81ey93uz6t.json +49 -0
  25. package/.trajectories/completed/2026-01/traj_hf81ey93uz6t.md +31 -0
  26. package/.trajectories/completed/2026-01/traj_hfmki2jr9d4r.json +65 -0
  27. package/.trajectories/completed/2026-01/traj_hfmki2jr9d4r.md +37 -0
  28. package/.trajectories/completed/2026-01/traj_lq450ly148uw.json +49 -0
  29. package/.trajectories/completed/2026-01/traj_lq450ly148uw.md +31 -0
  30. package/.trajectories/completed/2026-01/traj_multi_server_arch.md +101 -0
  31. package/.trajectories/completed/2026-01/traj_psd9ob0j2ru3.json +27 -0
  32. package/.trajectories/completed/2026-01/traj_psd9ob0j2ru3.md +14 -0
  33. package/.trajectories/completed/2026-01/traj_ub8csuv3lcv4.json +53 -0
  34. package/.trajectories/completed/2026-01/traj_ub8csuv3lcv4.md +32 -0
  35. package/.trajectories/completed/2026-01/traj_uc29tlso8i9s.json +186 -0
  36. package/.trajectories/completed/2026-01/traj_uc29tlso8i9s.md +86 -0
  37. package/.trajectories/completed/2026-01/traj_ui9b4tqxoa7j.json +77 -0
  38. package/.trajectories/completed/2026-01/traj_ui9b4tqxoa7j.md +42 -0
  39. package/.trajectories/completed/2026-01/traj_v9dkdoxylyid.json +89 -0
  40. package/.trajectories/completed/2026-01/traj_v9dkdoxylyid.md +47 -0
  41. package/.trajectories/completed/2026-01/traj_xy9vifpqet80.json +65 -0
  42. package/.trajectories/completed/2026-01/traj_xy9vifpqet80.md +37 -0
  43. package/.trajectories/completed/2026-01/traj_y7aiwijyfmmv.json +49 -0
  44. package/.trajectories/completed/2026-01/traj_y7aiwijyfmmv.md +31 -0
  45. package/.trajectories/consolidate-settings-panel.md +24 -0
  46. package/.trajectories/gh-cli-user-token.md +26 -0
  47. package/.trajectories/index.json +155 -1
  48. package/deploy/workspace/codex.config.toml +15 -0
  49. package/deploy/workspace/entrypoint.sh +167 -7
  50. package/deploy/workspace/git-credential-relay +17 -2
  51. package/dist/bridge/spawner.d.ts +7 -0
  52. package/dist/bridge/spawner.js +40 -9
  53. package/dist/bridge/types.d.ts +2 -0
  54. package/dist/cli/index.js +210 -168
  55. package/dist/cloud/api/admin.d.ts +8 -0
  56. package/dist/cloud/api/admin.js +212 -0
  57. package/dist/cloud/api/auth.js +8 -0
  58. package/dist/cloud/api/billing.d.ts +0 -10
  59. package/dist/cloud/api/billing.js +248 -58
  60. package/dist/cloud/api/codex-auth-helper.d.ts +10 -4
  61. package/dist/cloud/api/codex-auth-helper.js +215 -8
  62. package/dist/cloud/api/coordinators.js +402 -0
  63. package/dist/cloud/api/daemons.js +15 -11
  64. package/dist/cloud/api/git.js +104 -17
  65. package/dist/cloud/api/github-app.js +42 -8
  66. package/dist/cloud/api/nango-auth.js +297 -16
  67. package/dist/cloud/api/onboarding.js +97 -33
  68. package/dist/cloud/api/providers.js +12 -16
  69. package/dist/cloud/api/repos.js +200 -124
  70. package/dist/cloud/api/test-helpers.js +40 -0
  71. package/dist/cloud/api/usage.js +13 -0
  72. package/dist/cloud/api/webhooks.js +1 -1
  73. package/dist/cloud/api/workspaces.d.ts +18 -0
  74. package/dist/cloud/api/workspaces.js +945 -15
  75. package/dist/cloud/config.d.ts +8 -0
  76. package/dist/cloud/config.js +15 -0
  77. package/dist/cloud/db/drizzle.d.ts +5 -2
  78. package/dist/cloud/db/drizzle.js +27 -20
  79. package/dist/cloud/db/schema.d.ts +19 -51
  80. package/dist/cloud/db/schema.js +5 -4
  81. package/dist/cloud/index.d.ts +0 -1
  82. package/dist/cloud/index.js +0 -1
  83. package/dist/cloud/provisioner/index.d.ts +93 -1
  84. package/dist/cloud/provisioner/index.js +608 -63
  85. package/dist/cloud/server.js +156 -16
  86. package/dist/cloud/services/compute-enforcement.d.ts +57 -0
  87. package/dist/cloud/services/compute-enforcement.js +175 -0
  88. package/dist/cloud/services/index.d.ts +2 -0
  89. package/dist/cloud/services/index.js +4 -0
  90. package/dist/cloud/services/intro-expiration.d.ts +55 -0
  91. package/dist/cloud/services/intro-expiration.js +211 -0
  92. package/dist/cloud/services/nango.d.ts +14 -0
  93. package/dist/cloud/services/nango.js +74 -14
  94. package/dist/cloud/services/ssh-security.d.ts +31 -0
  95. package/dist/cloud/services/ssh-security.js +63 -0
  96. package/dist/continuity/manager.d.ts +5 -0
  97. package/dist/continuity/manager.js +56 -2
  98. package/dist/daemon/api.d.ts +2 -0
  99. package/dist/daemon/api.js +214 -5
  100. package/dist/daemon/cli-auth.d.ts +13 -1
  101. package/dist/daemon/cli-auth.js +166 -47
  102. package/dist/daemon/connection.d.ts +7 -1
  103. package/dist/daemon/connection.js +15 -0
  104. package/dist/daemon/orchestrator.d.ts +2 -0
  105. package/dist/daemon/orchestrator.js +26 -0
  106. package/dist/daemon/repo-manager.d.ts +116 -0
  107. package/dist/daemon/repo-manager.js +384 -0
  108. package/dist/daemon/router.d.ts +60 -1
  109. package/dist/daemon/router.js +281 -20
  110. package/dist/daemon/user-directory.d.ts +111 -0
  111. package/dist/daemon/user-directory.js +233 -0
  112. package/dist/dashboard/out/404.html +1 -1
  113. package/dist/dashboard/out/_next/static/T1tgCqVWHFIkV7ClEtzD7/_ssgManifest.js +1 -0
  114. package/dist/dashboard/out/_next/static/chunks/532-bace199897eeab37.js +9 -0
  115. package/dist/dashboard/out/_next/static/chunks/766-b54f0853794b78c3.js +1 -0
  116. package/dist/dashboard/out/_next/static/chunks/83-b51836037078006c.js +1 -0
  117. package/dist/dashboard/out/_next/static/chunks/891-6cd50de1224f70bb.js +1 -0
  118. package/dist/dashboard/out/_next/static/chunks/899-bb19a9b3d9b39ea6.js +1 -0
  119. package/dist/dashboard/out/_next/static/chunks/app/app/onboarding/page-8939b0fc700f7eca.js +1 -0
  120. package/dist/dashboard/out/_next/static/chunks/app/app/page-5af1b6b439858aa6.js +1 -0
  121. package/dist/dashboard/out/_next/static/chunks/app/connect-repos/page-f45ecbc3e06134fc.js +1 -0
  122. package/dist/dashboard/out/_next/static/chunks/app/history/{page-abb9ab2d329f56e9.js → page-8c8bed33beb2bf1c.js} +1 -1
  123. package/dist/dashboard/out/_next/static/chunks/app/layout-2433bb48965f4333.js +1 -0
  124. package/dist/dashboard/out/_next/static/chunks/app/login/{page-c22d080201cbd9fb.js → page-16f3b49e55b1e0ed.js} +1 -1
  125. package/dist/dashboard/out/_next/static/chunks/app/metrics/page-ac39dc0cc3c26fa7.js +1 -0
  126. package/dist/dashboard/out/_next/static/chunks/app/{page-77e9c65420a06cfb.js → page-4a5938c18a11a654.js} +1 -1
  127. package/dist/dashboard/out/_next/static/chunks/app/pricing/page-982a7000fee44014.js +1 -0
  128. package/dist/dashboard/out/_next/static/chunks/app/providers/page-ac3a6ac433fd6001.js +1 -0
  129. package/dist/dashboard/out/_next/static/chunks/app/providers/setup/[provider]/page-09f9caae98a18c09.js +1 -0
  130. package/dist/dashboard/out/_next/static/chunks/app/signup/{page-68d34f50baa8ab6b.js → page-547dd0ca55ecd0ba.js} +1 -1
  131. package/dist/dashboard/out/_next/static/chunks/{main-ed4e1fb6f29c34cf.js → main-2ee6beb2ae96d210.js} +1 -1
  132. package/dist/dashboard/out/_next/static/chunks/{main-app-6e8e8d3ef4e0192a.js → main-app-5d692157a8eb1fd9.js} +1 -1
  133. package/dist/dashboard/out/_next/static/css/85d2af9c7ac74d62.css +1 -0
  134. package/dist/dashboard/out/_next/static/css/fe4b28883eeff359.css +1 -0
  135. package/dist/dashboard/out/app/onboarding.html +1 -1
  136. package/dist/dashboard/out/app/onboarding.txt +3 -3
  137. package/dist/dashboard/out/app.html +1 -1
  138. package/dist/dashboard/out/app.txt +3 -3
  139. package/dist/dashboard/out/apple-icon.png +0 -0
  140. package/dist/dashboard/out/connect-repos.html +1 -1
  141. package/dist/dashboard/out/connect-repos.txt +3 -3
  142. package/dist/dashboard/out/history.html +1 -1
  143. package/dist/dashboard/out/history.txt +3 -3
  144. package/dist/dashboard/out/index.html +1 -1
  145. package/dist/dashboard/out/index.txt +3 -3
  146. package/dist/dashboard/out/login.html +2 -2
  147. package/dist/dashboard/out/login.txt +3 -3
  148. package/dist/dashboard/out/metrics.html +1 -1
  149. package/dist/dashboard/out/metrics.txt +3 -3
  150. package/dist/dashboard/out/pricing.html +2 -2
  151. package/dist/dashboard/out/pricing.txt +3 -3
  152. package/dist/dashboard/out/providers/setup/claude.html +1 -0
  153. package/dist/dashboard/out/providers/setup/claude.txt +8 -0
  154. package/dist/dashboard/out/providers/setup/codex.html +1 -0
  155. package/dist/dashboard/out/providers/setup/codex.txt +8 -0
  156. package/dist/dashboard/out/providers.html +1 -1
  157. package/dist/dashboard/out/providers.txt +3 -3
  158. package/dist/dashboard/out/signup.html +2 -2
  159. package/dist/dashboard/out/signup.txt +3 -3
  160. package/dist/dashboard-server/server.js +316 -12
  161. package/dist/dashboard-server/user-bridge.d.ts +103 -0
  162. package/dist/dashboard-server/user-bridge.js +189 -0
  163. package/dist/protocol/channels.d.ts +205 -0
  164. package/dist/protocol/channels.js +154 -0
  165. package/dist/protocol/types.d.ts +13 -1
  166. package/dist/resiliency/provider-context.js +2 -0
  167. package/dist/shared/cli-auth-config.d.ts +19 -0
  168. package/dist/shared/cli-auth-config.js +58 -2
  169. package/dist/utils/agent-config.js +1 -1
  170. package/dist/wrapper/auth-detection.d.ts +49 -0
  171. package/dist/wrapper/auth-detection.js +192 -0
  172. package/dist/wrapper/base-wrapper.d.ts +153 -0
  173. package/dist/wrapper/base-wrapper.js +393 -0
  174. package/dist/wrapper/client.d.ts +7 -1
  175. package/dist/wrapper/client.js +3 -0
  176. package/dist/wrapper/index.d.ts +1 -0
  177. package/dist/wrapper/index.js +4 -3
  178. package/dist/wrapper/pty-wrapper.d.ts +62 -84
  179. package/dist/wrapper/pty-wrapper.js +154 -180
  180. package/dist/wrapper/tmux-wrapper.d.ts +41 -66
  181. package/dist/wrapper/tmux-wrapper.js +90 -134
  182. package/package.json +4 -2
  183. package/scripts/postinstall.js +11 -155
  184. package/scripts/test-interactive-terminal.sh +248 -0
  185. package/dist/cloud/vault/index.d.ts +0 -76
  186. package/dist/cloud/vault/index.js +0 -219
  187. package/dist/dashboard/out/_next/static/chunks/699-3b1cd6618a45d259.js +0 -1
  188. package/dist/dashboard/out/_next/static/chunks/724-2dae7627550ab88f.js +0 -9
  189. package/dist/dashboard/out/_next/static/chunks/766-1f2dd8cb7f766b0b.js +0 -1
  190. package/dist/dashboard/out/_next/static/chunks/app/app/onboarding/page-3fdfa60e53f2810d.js +0 -1
  191. package/dist/dashboard/out/_next/static/chunks/app/app/page-e6381e5a6e1fbcfd.js +0 -1
  192. package/dist/dashboard/out/_next/static/chunks/app/connect-repos/page-3538dfe0ffe984b8.js +0 -1
  193. package/dist/dashboard/out/_next/static/chunks/app/layout-c0d118c0f92d969c.js +0 -1
  194. package/dist/dashboard/out/_next/static/chunks/app/metrics/page-67a3e98d9a43a6ed.js +0 -1
  195. package/dist/dashboard/out/_next/static/chunks/app/pricing/page-b08ed1c34d14434a.js +0 -1
  196. package/dist/dashboard/out/_next/static/chunks/app/providers/page-e88bc117ef7671c3.js +0 -1
  197. package/dist/dashboard/out/_next/static/css/29852f26181969a0.css +0 -1
  198. package/dist/dashboard/out/_next/static/css/7c3ae9e8617d42a5.css +0 -1
  199. package/dist/dashboard/out/_next/static/wPgKJtcOmTFLpUncDg16A/_ssgManifest.js +0 -1
  200. /package/dist/dashboard/out/_next/static/{wPgKJtcOmTFLpUncDg16A → T1tgCqVWHFIkV7ClEtzD7}/_buildManifest.js +0 -0
@@ -11,22 +11,17 @@
11
11
  * The key insight: user sees the REAL tmux session, not a proxy.
12
12
  * We just do background parsing and injection.
13
13
  */
14
+ import { BaseWrapper, type BaseWrapperConfig } from './base-wrapper.js';
15
+ import { type ParsedCommand, type ParsedSummary } from './parser.js';
16
+ import type { SendPayload, SendMeta } from '../protocol/types.js';
14
17
  import { type CliType } from './shared.js';
15
- export interface TmuxWrapperConfig {
16
- name: string;
17
- command: string;
18
- args?: string[];
19
- socketPath?: string;
18
+ export interface TmuxWrapperConfig extends BaseWrapperConfig {
20
19
  cols?: number;
21
20
  rows?: number;
22
- cwd?: string;
23
- env?: Record<string, string>;
24
21
  /** Optional program identifier (e.g., 'claude', 'gpt-4o') */
25
22
  program?: string;
26
23
  /** Optional model identifier (e.g., 'claude-3-opus') */
27
24
  model?: string;
28
- /** Optional task/role description for dashboard/registry */
29
- task?: string;
30
25
  /** Use file-based inbox in addition to injection */
31
26
  useInbox?: boolean;
32
27
  /** Custom inbox directory */
@@ -47,26 +42,12 @@ export interface TmuxWrapperConfig {
47
42
  inputWaitTimeoutMs?: number;
48
43
  /** Polling interval when waiting for clear input (ms) */
49
44
  inputWaitPollMs?: number;
50
- /** CLI type for special handling (auto-detected from command if not set) */
51
- cliType?: 'claude' | 'codex' | 'gemini' | 'droid' | 'opencode' | 'other';
52
45
  /** Enable tmux mouse mode for scroll passthrough (default: true) */
53
46
  mouseMode?: boolean;
54
- /** Relay prefix pattern (default: '->relay:') */
55
- relayPrefix?: string;
56
- /** Callback for spawn commands (@relay:spawn WorkerName cli "task") */
57
- onSpawn?: (name: string, cli: string, task: string) => Promise<void>;
58
- /** Callback for release commands (@relay:release WorkerName) */
59
- onRelease?: (name: string) => Promise<void>;
60
47
  /** Max time to wait for stable pane output before injection (ms) */
61
48
  outputStabilityTimeoutMs?: number;
62
49
  /** Poll interval when checking pane stability before injection (ms) */
63
50
  outputStabilityPollMs?: number;
64
- /** Stream output to daemon for dashboard log viewing (default: true) */
65
- streamLogs?: boolean;
66
- /** Resume from a previous agent ID (for crash recovery) */
67
- resumeAgentId?: string;
68
- /** Dashboard port for API-based spawn/release (preferred over callbacks) */
69
- dashboardPort?: number;
70
51
  }
71
52
  /**
72
53
  * Get the default relay prefix for a given CLI type.
@@ -74,15 +55,13 @@ export interface TmuxWrapperConfig {
74
55
  * @deprecated Use getDefaultRelayPrefix() from shared.js instead
75
56
  */
76
57
  export declare function getDefaultPrefix(_cliType: CliType): string;
77
- export declare class TmuxWrapper {
78
- private config;
58
+ export declare class TmuxWrapper extends BaseWrapper {
59
+ protected config: TmuxWrapperConfig;
79
60
  private sessionName;
80
- private client;
81
61
  private parser;
82
62
  private inbox?;
83
63
  private storage?;
84
64
  private storageReady;
85
- private running;
86
65
  private pollTimer?;
87
66
  private attachProcess?;
88
67
  private lastCapturedOutput;
@@ -90,24 +69,13 @@ export declare class TmuxWrapper {
90
69
  private lastActivityTime;
91
70
  private activityState;
92
71
  private recentlySentMessages;
93
- private sentMessageHashes;
94
- private messageQueue;
95
- private isInjecting;
96
72
  private processedOutputLength;
97
73
  private lastLoggedLength;
98
74
  private lastDebugLog;
99
- private cliType;
100
- private relayPrefix;
101
75
  private lastSummaryHash;
102
- private lastSummaryRawContent;
103
- private sessionEndProcessed;
104
- private sessionEndData?;
105
76
  private pendingRelayCommands;
106
77
  private queuedMessageHashes;
107
78
  private readonly MAX_PENDING_RELAY_COMMANDS;
108
- private processedSpawnCommands;
109
- private processedReleaseCommands;
110
- private pendingFencedSpawn;
111
79
  private receivedMessageIdSet;
112
80
  private receivedMessageIdOrder;
113
81
  private readonly MAX_RECEIVED_MESSAGES;
@@ -116,11 +84,18 @@ export declare class TmuxWrapper {
116
84
  private lastDetectedPhase?;
117
85
  private seenToolCalls;
118
86
  private seenErrors;
119
- private continuity?;
120
- private processedContinuityCommands;
121
- private agentId?;
122
- private injectionMetrics;
87
+ private authRevoked;
88
+ private lastAuthCheck;
89
+ private readonly AUTH_CHECK_INTERVAL;
123
90
  constructor(config: TmuxWrapperConfig);
91
+ /**
92
+ * Inject content into the tmux session via paste
93
+ */
94
+ protected performInjection(content: string): Promise<void>;
95
+ /**
96
+ * Get cleaned output for parsing (strip ANSI codes)
97
+ */
98
+ protected getCleanOutput(): string;
124
99
  /**
125
100
  * Log to stderr (safe - doesn't interfere with tmux display)
126
101
  */
@@ -149,13 +124,9 @@ export declare class TmuxWrapper {
149
124
  */
150
125
  private initializeTrajectory;
151
126
  /**
152
- * Initialize agent ID for continuity/resume functionality
127
+ * Initialize agent ID for continuity/resume functionality (uses logStderr for tmux)
153
128
  */
154
- private initializeAgentId;
155
- /**
156
- * Get the current agent ID
157
- */
158
- getAgentId(): string | undefined;
129
+ protected initializeAgentId(): Promise<void>;
159
130
  /**
160
131
  * Inject usage instructions for the agent including persistence protocol
161
132
  */
@@ -186,13 +157,6 @@ export declare class TmuxWrapper {
186
157
  * Poll for ->relay commands in output (silent)
187
158
  */
188
159
  private pollForRelayCommands;
189
- /**
190
- * Join continuation lines after ->relay commands.
191
- * Claude Code and other TUIs insert real newlines in output, causing
192
- * ->relay messages to span multiple lines. This joins indented
193
- * continuation lines back to the ->relay line.
194
- */
195
- private joinContinuationLines;
196
160
  /**
197
161
  * Record recent activity and transition back to active if needed.
198
162
  */
@@ -202,9 +166,22 @@ export declare class TmuxWrapper {
202
166
  */
203
167
  private updateActivityState;
204
168
  /**
205
- * Send relay command to daemon
169
+ * Check if the CLI output indicates auth has been revoked.
170
+ * This can happen when the user authenticates elsewhere (limited sessions).
171
+ */
172
+ private checkAuthRevocation;
173
+ /**
174
+ * Reset auth revocation state (called after successful re-authentication)
175
+ */
176
+ resetAuthState(): void;
177
+ /**
178
+ * Check if auth has been revoked
179
+ */
180
+ isAuthRevoked(): boolean;
181
+ /**
182
+ * Send relay command to daemon (overrides BaseWrapper for offline queue support)
206
183
  */
207
- private sendRelayCommand;
184
+ protected sendRelayCommand(cmd: ParsedCommand): void;
208
185
  /**
209
186
  * Flush any queued relay commands when the client reconnects.
210
187
  */
@@ -219,10 +196,10 @@ export declare class TmuxWrapper {
219
196
  */
220
197
  private parseSummaryAndSave;
221
198
  /**
222
- * Save a parsed summary to the continuity ledger.
199
+ * Save a parsed summary to the continuity ledger (uses logStderr for tmux).
223
200
  * Maps summary fields to ledger fields for session recovery.
224
201
  */
225
- private saveSummaryToLedger;
202
+ protected saveSummaryToLedger(summary: ParsedSummary): Promise<void>;
226
203
  /**
227
204
  * Parse ->continuity: commands from output and handle them.
228
205
  * Supported commands:
@@ -232,7 +209,7 @@ export declare class TmuxWrapper {
232
209
  * ->continuity:uncertain "..." - Mark item as uncertain
233
210
  * ->continuity:handoff <<<...>>> - Create explicit handoff
234
211
  */
235
- private parseContinuityCommands;
212
+ protected parseContinuityCommands(content: string): Promise<void>;
236
213
  /**
237
214
  * Parse [[SESSION_END]] blocks from output and close session explicitly.
238
215
  * Agents output this to mark their work session as complete:
@@ -247,11 +224,11 @@ export declare class TmuxWrapper {
247
224
  /**
248
225
  * Execute spawn via API (if dashboardPort set) or callback
249
226
  */
250
- private executeSpawn;
227
+ protected executeSpawn(name: string, cli: string, task: string): Promise<void>;
251
228
  /**
252
229
  * Execute release via API (if dashboardPort set) or callback
253
230
  */
254
- private executeRelease;
231
+ protected executeRelease(name: string): Promise<void>;
255
232
  /**
256
233
  * Parse ->relay:spawn and ->relay:release commands from output.
257
234
  * Supports two formats:
@@ -261,13 +238,13 @@ export declare class TmuxWrapper {
261
238
  * can span multiple lines>>>
262
239
  * ->relay:release WorkerName
263
240
  */
264
- private parseSpawnReleaseCommands;
241
+ protected parseSpawnReleaseCommands(content: string): void;
265
242
  /**
266
243
  * Handle incoming message from relay
267
244
  * @param originalTo - The original 'to' field from sender. '*' indicates this was a broadcast message.
268
245
  * Agents should reply to originalTo to maintain channel routing (e.g., respond to #general, not DM).
269
246
  */
270
- private handleIncomingMessage;
247
+ protected handleIncomingMessage(from: string, payload: SendPayload, messageId: string, meta?: SendMeta, originalTo?: string): void;
271
248
  /**
272
249
  * Check if we should inject a message
273
250
  */
@@ -340,7 +317,5 @@ export declare class TmuxWrapper {
340
317
  * Stop and cleanup
341
318
  */
342
319
  stop(): void;
343
- get isRunning(): boolean;
344
- get name(): string;
345
320
  }
346
321
  //# sourceMappingURL=tmux-wrapper.d.ts.map
@@ -14,8 +14,9 @@
14
14
  import { exec, execSync, spawn } from 'node:child_process';
15
15
  import crypto from 'node:crypto';
16
16
  import { promisify } from 'node:util';
17
- import { RelayClient } from './client.js';
17
+ import { BaseWrapper } from './base-wrapper.js';
18
18
  import { OutputParser, parseSummaryWithDetails, parseSessionEndFromOutput } from './parser.js';
19
+ import { hasContinuityCommand, parseContinuityCommand, } from '../continuity/index.js';
19
20
  import { InboxManager } from './inbox.js';
20
21
  import { SqliteStorageAdapter } from '../storage/sqlite-adapter.js';
21
22
  import { getProjectPaths } from '../utils/project-namespace.js';
@@ -23,10 +24,9 @@ import { getTmuxPath } from '../utils/tmux-resolver.js';
23
24
  import { findAgentConfig } from '../utils/agent-config.js';
24
25
  import { getTrajectoryIntegration, detectPhaseFromContent, detectToolCalls, detectErrors, getCompactTrailInstructions, getTrailEnvVars, } from '../trajectory/integration.js';
25
26
  import { escapeForShell } from '../bridge/utils.js';
26
- import { getContinuityManager, parseContinuityCommand, hasContinuityCommand, } from '../continuity/index.js';
27
- import { stripAnsi, sleep, getDefaultRelayPrefix, createInjectionMetrics, buildInjectionString, injectWithRetry as sharedInjectWithRetry, INJECTION_CONSTANTS, CLI_QUIRKS, } from './shared.js';
27
+ import { detectProviderAuthRevocation } from './auth-detection.js';
28
+ import { stripAnsi, sleep, getDefaultRelayPrefix, buildInjectionString, injectWithRetry as sharedInjectWithRetry, INJECTION_CONSTANTS, CLI_QUIRKS, } from './shared.js';
28
29
  const execAsync = promisify(exec);
29
- const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
30
30
  // Constants for cursor stability detection in waitForClearInput
31
31
  /** Number of consecutive polls with stable cursor before assuming input is clear */
32
32
  const STABLE_CURSOR_THRESHOLD = 3;
@@ -44,15 +44,13 @@ const RELAY_LOG_TRUNCATE_LENGTH = 50;
44
44
  export function getDefaultPrefix(_cliType) {
45
45
  return getDefaultRelayPrefix();
46
46
  }
47
- export class TmuxWrapper {
47
+ export class TmuxWrapper extends BaseWrapper {
48
48
  config;
49
49
  sessionName;
50
- client;
51
50
  parser;
52
51
  inbox;
53
52
  storage;
54
53
  storageReady; // Resolves true if storage initialized, false if failed
55
- running = false;
56
54
  pollTimer;
57
55
  attachProcess;
58
56
  lastCapturedOutput = '';
@@ -60,25 +58,14 @@ export class TmuxWrapper {
60
58
  lastActivityTime = Date.now();
61
59
  activityState = 'disconnected';
62
60
  recentlySentMessages = new Map();
63
- sentMessageHashes = new Set(); // Permanent dedup
64
- messageQueue = [];
65
- isInjecting = false;
66
61
  // Track processed output to avoid re-parsing
67
62
  processedOutputLength = 0;
68
63
  lastLoggedLength = 0; // Track length for incremental log streaming
69
64
  lastDebugLog = 0;
70
- cliType;
71
- relayPrefix;
72
65
  lastSummaryHash = ''; // Dedup summary saves
73
- lastSummaryRawContent = ''; // Dedup invalid JSON error logging
74
- sessionEndProcessed = false; // Track if we've already processed session end
75
- sessionEndData; // Store SESSION_END data for handoff
76
66
  pendingRelayCommands = [];
77
67
  queuedMessageHashes = new Set(); // For offline queue dedup
78
68
  MAX_PENDING_RELAY_COMMANDS = 50;
79
- processedSpawnCommands = new Set(); // Dedup spawn commands
80
- processedReleaseCommands = new Set(); // Dedup release commands
81
- pendingFencedSpawn = null; // Track multi-line spawn task
82
69
  receivedMessageIdSet = new Set();
83
70
  receivedMessageIdOrder = [];
84
71
  MAX_RECEIVED_MESSAGES = 2000;
@@ -87,12 +74,12 @@ export class TmuxWrapper {
87
74
  lastDetectedPhase; // Track last auto-detected PDERO phase
88
75
  seenToolCalls = new Set(); // Dedup tool call trajectory events
89
76
  seenErrors = new Set(); // Dedup error trajectory events
90
- continuity; // Session continuity management
91
- processedContinuityCommands = new Set(); // Dedup continuity commands
92
- agentId; // Unique agent ID for resume functionality
93
- injectionMetrics = createInjectionMetrics(); // Track injection reliability
77
+ authRevoked = false; // Track if auth has been revoked
78
+ lastAuthCheck = 0; // Timestamp of last auth check (throttle)
79
+ AUTH_CHECK_INTERVAL = 5000; // Check auth status every 5 seconds max
94
80
  constructor(config) {
95
- this.config = {
81
+ // Merge defaults with config
82
+ const mergedConfig = {
96
83
  cols: process.stdout.columns || 120,
97
84
  rows: process.stdout.rows || 40,
98
85
  pollInterval: 200, // Slightly slower polling since we're not displaying
@@ -107,31 +94,9 @@ export class TmuxWrapper {
107
94
  streamLogs: true, // Stream output to daemon for dashboard
108
95
  ...config,
109
96
  };
110
- // Detect CLI type from command for special handling
111
- const cmdLower = config.command.toLowerCase();
112
- if (config.cliType) {
113
- this.cliType = config.cliType;
114
- }
115
- else if (cmdLower.includes('gemini')) {
116
- this.cliType = 'gemini';
117
- }
118
- else if (cmdLower.includes('codex')) {
119
- this.cliType = 'codex';
120
- }
121
- else if (cmdLower.includes('claude')) {
122
- this.cliType = 'claude';
123
- }
124
- else if (cmdLower.includes('droid')) {
125
- this.cliType = 'droid';
126
- }
127
- else if (cmdLower.includes('opencode')) {
128
- this.cliType = 'opencode';
129
- }
130
- else {
131
- this.cliType = 'other';
132
- }
133
- // Determine relay prefix: explicit config > auto-detect from CLI type
134
- this.relayPrefix = config.relayPrefix ?? getDefaultPrefix(this.cliType);
97
+ // Call parent constructor (initializes client, cliType, relayPrefix, continuity)
98
+ super(mergedConfig);
99
+ this.config = mergedConfig;
135
100
  // Session name (one agent per name - starting a duplicate kills the existing one)
136
101
  this.sessionName = `relay-${config.name}`;
137
102
  // Resolve tmux path early so we fail fast if tmux isn't available
@@ -145,16 +110,6 @@ export class TmuxWrapper {
145
110
  this.logStderr(`Auto-detected role: ${detectedTask.substring(0, 60)}...`, true);
146
111
  }
147
112
  }
148
- this.client = new RelayClient({
149
- agentName: config.name,
150
- socketPath: config.socketPath,
151
- cli: this.cliType,
152
- program: this.config.program,
153
- model: this.config.model,
154
- task: detectedTask,
155
- workingDirectory: this.config.cwd ?? process.cwd(),
156
- quiet: true, // Keep stdout clean; we log to stderr via wrapper
157
- });
158
113
  this.parser = new OutputParser({ prefix: this.relayPrefix });
159
114
  // Initialize inbox if using file-based messaging
160
115
  if (config.useInbox) {
@@ -174,12 +129,6 @@ export class TmuxWrapper {
174
129
  });
175
130
  // Initialize trajectory tracking via trail CLI
176
131
  this.trajectory = getTrajectoryIntegration(projectPaths.projectId, config.name);
177
- // Initialize continuity manager for session persistence
178
- this.continuity = getContinuityManager({ defaultCli: this.cliType });
179
- // Handle incoming messages from relay
180
- this.client.onMessage = (from, payload, messageId, meta, originalTo) => {
181
- this.handleIncomingMessage(from, payload, messageId, meta, originalTo);
182
- };
183
132
  this.client.onStateChange = (state) => {
184
133
  // Only log to stderr, never stdout (user is in tmux)
185
134
  if (state === 'READY') {
@@ -197,6 +146,21 @@ export class TmuxWrapper {
197
146
  }
198
147
  };
199
148
  }
149
+ // =========================================================================
150
+ // Abstract method implementations
151
+ // =========================================================================
152
+ /**
153
+ * Inject content into the tmux session via paste
154
+ */
155
+ async performInjection(content) {
156
+ await this.pasteLiteral(content);
157
+ }
158
+ /**
159
+ * Get cleaned output for parsing (strip ANSI codes)
160
+ */
161
+ getCleanOutput() {
162
+ return stripAnsi(this.lastCapturedOutput);
163
+ }
200
164
  /**
201
165
  * Log to stderr (safe - doesn't interfere with tmux display)
202
166
  */
@@ -417,7 +381,7 @@ export class TmuxWrapper {
417
381
  }
418
382
  }
419
383
  /**
420
- * Initialize agent ID for continuity/resume functionality
384
+ * Initialize agent ID for continuity/resume functionality (uses logStderr for tmux)
421
385
  */
422
386
  async initializeAgentId() {
423
387
  if (!this.continuity)
@@ -445,12 +409,6 @@ export class TmuxWrapper {
445
409
  this.logStderr(`Failed to initialize agent ID: ${err.message}`, true);
446
410
  }
447
411
  }
448
- /**
449
- * Get the current agent ID
450
- */
451
- getAgentId() {
452
- return this.agentId;
453
- }
454
412
  /**
455
413
  * Inject usage instructions for the agent including persistence protocol
456
414
  */
@@ -464,6 +422,7 @@ export class TmuxWrapper {
464
422
  `[Agent Relay] You are "${this.config.name}" - connected for real-time messaging.`,
465
423
  `SEND: ${escapedPrefix}AgentName message`,
466
424
  `MULTI-LINE: ${escapedPrefix}AgentName <<<(newline)content(newline)>>> - ALWAYS end with >>> on its own line!`,
425
+ `IMPORTANT: Do NOT include self-identification or preamble in messages. Start with your actual response content.`,
467
426
  `PERSIST: Output [[SUMMARY]]{"currentTask":"...","context":"..."}[[/SUMMARY]] after major work.`,
468
427
  `END: Output [[SESSION_END]]{"summary":"..."}[[/SESSION_END]] when session complete.`,
469
428
  ].join(' | ');
@@ -654,6 +613,8 @@ export class TmuxWrapper {
654
613
  // Check for ->relay:spawn and ->relay:release commands (any agent can spawn)
655
614
  // Use joinedContent to handle multi-line output from TUIs like Claude Code
656
615
  this.parseSpawnReleaseCommands(joinedContent);
616
+ // Check for auth revocation (limited sessions scenario)
617
+ this.checkAuthRevocation(cleanContent);
657
618
  this.updateActivityState();
658
619
  // Also check for injection opportunity
659
620
  this.checkForInjectionOpportunity();
@@ -664,58 +625,6 @@ export class TmuxWrapper {
664
625
  }
665
626
  }
666
627
  }
667
- /**
668
- * Join continuation lines after ->relay commands.
669
- * Claude Code and other TUIs insert real newlines in output, causing
670
- * ->relay messages to span multiple lines. This joins indented
671
- * continuation lines back to the ->relay line.
672
- */
673
- joinContinuationLines(content) {
674
- const lines = content.split('\n');
675
- const result = [];
676
- // Pattern to detect relay command line (with optional bullet prefix)
677
- const escapedPrefix = escapeRegex(this.relayPrefix);
678
- const relayPattern = new RegExp(`^(?:\\s*(?:[>$%#→➜›»●•◦‣⁃\\-*⏺◆◇○□■]\\s*)*)?${escapedPrefix}`);
679
- // Pattern to detect a continuation line (starts with spaces, no bullet/command)
680
- const continuationPattern = /^[ \t]+[^>$%#→➜›»●•◦‣⁃\-*⏺◆◇○□■\s]/;
681
- // Pattern to detect a new block/bullet (stops continuation)
682
- const newBlockPattern = /^(?:\s*)?[>$%#→➜›»●•◦‣⁃\-*⏺◆◇○□■]/;
683
- let i = 0;
684
- while (i < lines.length) {
685
- const line = lines[i];
686
- // Check if this is a ->relay line
687
- if (relayPattern.test(line)) {
688
- let joined = line;
689
- let j = i + 1;
690
- // Look ahead for continuation lines
691
- while (j < lines.length) {
692
- const nextLine = lines[j];
693
- // Empty line stops continuation
694
- if (nextLine.trim() === '')
695
- break;
696
- // New bullet/block stops continuation
697
- if (newBlockPattern.test(nextLine))
698
- break;
699
- // Check if it looks like a continuation (indented text)
700
- if (continuationPattern.test(nextLine)) {
701
- // Join with newline to preserve multi-line message content
702
- joined += '\n' + nextLine.trim();
703
- j++;
704
- }
705
- else {
706
- break;
707
- }
708
- }
709
- result.push(joined);
710
- i = j; // Skip the lines we joined
711
- }
712
- else {
713
- result.push(line);
714
- i++;
715
- }
716
- }
717
- return result.join('\n');
718
- }
719
628
  /**
720
629
  * Record recent activity and transition back to active if needed.
721
630
  */
@@ -746,7 +655,61 @@ export class TmuxWrapper {
746
655
  }
747
656
  }
748
657
  /**
749
- * Send relay command to daemon
658
+ * Check if the CLI output indicates auth has been revoked.
659
+ * This can happen when the user authenticates elsewhere (limited sessions).
660
+ */
661
+ checkAuthRevocation(output) {
662
+ // Don't check if already revoked or if we checked recently
663
+ if (this.authRevoked)
664
+ return;
665
+ const now = Date.now();
666
+ if (now - this.lastAuthCheck < this.AUTH_CHECK_INTERVAL)
667
+ return;
668
+ this.lastAuthCheck = now;
669
+ // Get the CLI type/provider from config
670
+ const provider = this.config.program || this.cliType || 'claude';
671
+ // Check for auth revocation patterns in recent output
672
+ const result = detectProviderAuthRevocation(output, provider);
673
+ if (result.detected && result.confidence !== 'low') {
674
+ this.authRevoked = true;
675
+ this.logStderr(`[AUTH] Auth revocation detected (${result.confidence} confidence): ${result.message}`);
676
+ // Send auth status message to daemon
677
+ if (this.client.state === 'READY') {
678
+ const authPayload = JSON.stringify({
679
+ type: 'auth_revoked',
680
+ agent: this.config.name,
681
+ provider,
682
+ message: result.message,
683
+ confidence: result.confidence,
684
+ timestamp: new Date().toISOString(),
685
+ });
686
+ this.client.sendMessage('#system', authPayload, 'message');
687
+ }
688
+ // Emit event for listeners
689
+ this.emit('auth_revoked', {
690
+ agent: this.config.name,
691
+ provider,
692
+ message: result.message,
693
+ confidence: result.confidence,
694
+ });
695
+ }
696
+ }
697
+ /**
698
+ * Reset auth revocation state (called after successful re-authentication)
699
+ */
700
+ resetAuthState() {
701
+ this.authRevoked = false;
702
+ this.lastAuthCheck = 0;
703
+ this.logStderr('[AUTH] Auth state reset');
704
+ }
705
+ /**
706
+ * Check if auth has been revoked
707
+ */
708
+ isAuthRevoked() {
709
+ return this.authRevoked;
710
+ }
711
+ /**
712
+ * Send relay command to daemon (overrides BaseWrapper for offline queue support)
750
713
  */
751
714
  sendRelayCommand(cmd) {
752
715
  const msgHash = `${cmd.to}:${cmd.body}`;
@@ -865,7 +828,7 @@ export class TmuxWrapper {
865
828
  });
866
829
  }
867
830
  /**
868
- * Save a parsed summary to the continuity ledger.
831
+ * Save a parsed summary to the continuity ledger (uses logStderr for tmux).
869
832
  * Maps summary fields to ledger fields for session recovery.
870
833
  */
871
834
  async saveSummaryToLedger(summary) {
@@ -1402,10 +1365,9 @@ export class TmuxWrapper {
1402
1365
  * Call this when starting a new session with the same wrapper instance.
1403
1366
  */
1404
1367
  resetSessionState() {
1405
- this.sessionEndProcessed = false;
1368
+ super.resetSessionState();
1369
+ // TmuxWrapper-specific state
1406
1370
  this.lastSummaryHash = '';
1407
- this.lastSummaryRawContent = '';
1408
- this.sessionEndData = undefined;
1409
1371
  }
1410
1372
  /**
1411
1373
  * Get the prompt pattern for the current CLI type.
@@ -1581,11 +1543,5 @@ export class TmuxWrapper {
1581
1543
  // Disconnect relay
1582
1544
  this.client.destroy();
1583
1545
  }
1584
- get isRunning() {
1585
- return this.running;
1586
- }
1587
- get name() {
1588
- return this.config.name;
1589
- }
1590
1546
  }
1591
1547
  //# sourceMappingURL=tmux-wrapper.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-relay",
3
- "version": "1.2.3",
3
+ "version": "1.3.0",
4
4
  "description": "Real-time agent-to-agent communication system",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -91,6 +91,7 @@
91
91
  "node-pty": "1.0.0",
92
92
  "pg": "^8.16.3",
93
93
  "redis": "^5.10.0",
94
+ "ssh2": "^1.17.0",
94
95
  "stripe": "^20.1.0",
95
96
  "uuid": "^10.0.0",
96
97
  "ws": "^8.18.3"
@@ -103,6 +104,7 @@
103
104
  "@types/helmet": "^0.0.48",
104
105
  "@types/node": "^22.19.3",
105
106
  "@types/pg": "^8.16.0",
107
+ "@types/ssh2": "^1.15.5",
106
108
  "@types/uuid": "^10.0.0",
107
109
  "@types/ws": "^8.18.1",
108
110
  "@typescript-eslint/eslint-plugin": "^8.18.2",
@@ -113,7 +115,7 @@
113
115
  "esbuild": "^0.24.0",
114
116
  "eslint": "^8.57.1",
115
117
  "jsdom": "^25.0.1",
116
- "typescript": "^5.7.2",
118
+ "typescript": "^5.9.3",
117
119
  "vitest": "^2.1.8"
118
120
  },
119
121
  "engines": {