agent-relay 1.3.1 → 1.3.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.
Files changed (202) hide show
  1. package/.trajectories/active/traj_3yx9dy148mge.json +42 -0
  2. package/.trajectories/completed/2026-01/traj_1g7yx6qtg4ai.json +49 -0
  3. package/.trajectories/completed/2026-01/traj_1g7yx6qtg4ai.md +31 -0
  4. package/.trajectories/completed/2026-01/traj_4qwd4zmhfwp4.json +49 -0
  5. package/.trajectories/completed/2026-01/traj_4qwd4zmhfwp4.md +31 -0
  6. package/.trajectories/completed/2026-01/traj_6unwwmgyj5sq.json +109 -0
  7. package/.trajectories/completed/2026-01/traj_a0tqx8biw9c4.json +49 -0
  8. package/.trajectories/completed/2026-01/traj_a0tqx8biw9c4.md +31 -0
  9. package/.trajectories/completed/2026-01/traj_ax8uungxz2qh.json +66 -0
  10. package/.trajectories/completed/2026-01/traj_ax8uungxz2qh.md +36 -0
  11. package/.trajectories/completed/2026-01/traj_c9izbh2snpzf.json +49 -0
  12. package/.trajectories/completed/2026-01/traj_c9izbh2snpzf.md +31 -0
  13. package/.trajectories/completed/2026-01/traj_cpn70dw066nt.json +65 -0
  14. package/.trajectories/completed/2026-01/traj_cpn70dw066nt.md +37 -0
  15. package/.trajectories/completed/2026-01/traj_erglv2f8t9eh.json +36 -0
  16. package/.trajectories/completed/2026-01/traj_erglv2f8t9eh.md +21 -0
  17. package/.trajectories/completed/2026-01/traj_he75f24d1xfm.json +101 -0
  18. package/.trajectories/completed/2026-01/traj_he75f24d1xfm.md +52 -0
  19. package/.trajectories/completed/2026-01/traj_lgtodco7dp1n.json +61 -0
  20. package/.trajectories/completed/2026-01/traj_lgtodco7dp1n.md +36 -0
  21. package/.trajectories/completed/2026-01/traj_oszg9flv74pk.json +73 -0
  22. package/.trajectories/completed/2026-01/traj_oszg9flv74pk.md +41 -0
  23. package/.trajectories/completed/2026-01/traj_pulomd3y8cvj.json +77 -0
  24. package/.trajectories/completed/2026-01/traj_pulomd3y8cvj.md +42 -0
  25. package/.trajectories/completed/2026-01/traj_rsavt0jipi3c.json +109 -0
  26. package/.trajectories/completed/2026-01/traj_rsavt0jipi3c.md +56 -0
  27. package/.trajectories/completed/2026-01/traj_x721m1j9rzup.json +113 -0
  28. package/.trajectories/completed/2026-01/traj_x721m1j9rzup.md +57 -0
  29. package/.trajectories/completed/2026-01/traj_xjqvmep5ed3h.json +61 -0
  30. package/.trajectories/completed/2026-01/traj_xjqvmep5ed3h.md +36 -0
  31. package/.trajectories/completed/2026-01/traj_y7n6hfbf7dmg.json +49 -0
  32. package/.trajectories/completed/2026-01/traj_y7n6hfbf7dmg.md +31 -0
  33. package/.trajectories/completed/2026-01/traj_yvfkwnkdiso2.json +49 -0
  34. package/.trajectories/completed/2026-01/traj_yvfkwnkdiso2.md +31 -0
  35. package/.trajectories/index.json +140 -1
  36. package/README.md +23 -9
  37. package/TRAIL_GIT_AUTH_FIX.md +113 -0
  38. package/deploy/workspace/codex.config.toml +1 -1
  39. package/deploy/workspace/entrypoint.sh +20 -79
  40. package/deploy/workspace/gh-relay +156 -0
  41. package/deploy/workspace/git-credential-relay +5 -1
  42. package/dist/bridge/multi-project-client.js +13 -10
  43. package/dist/bridge/spawner.d.ts +2 -0
  44. package/dist/bridge/spawner.js +58 -76
  45. package/dist/bridge/types.d.ts +2 -0
  46. package/dist/cli/index.d.ts +8 -6
  47. package/dist/cli/index.js +297 -30
  48. package/dist/cloud/api/admin.js +16 -3
  49. package/dist/cloud/api/codex-auth-helper.js +28 -8
  50. package/dist/cloud/api/consensus.d.ts +13 -0
  51. package/dist/cloud/api/consensus.js +259 -0
  52. package/dist/cloud/api/daemons.js +205 -1
  53. package/dist/cloud/api/git.js +37 -7
  54. package/dist/cloud/api/onboarding.js +4 -1
  55. package/dist/cloud/api/provider-env.d.ts +5 -0
  56. package/dist/cloud/api/provider-env.js +27 -0
  57. package/dist/cloud/api/providers.js +2 -0
  58. package/dist/cloud/api/test-helpers.js +130 -0
  59. package/dist/cloud/api/workspaces.js +38 -3
  60. package/dist/cloud/db/bulk-ingest.d.ts +88 -0
  61. package/dist/cloud/db/bulk-ingest.js +268 -0
  62. package/dist/cloud/db/drizzle.d.ts +33 -0
  63. package/dist/cloud/db/drizzle.js +174 -2
  64. package/dist/cloud/db/index.d.ts +24 -5
  65. package/dist/cloud/db/index.js +19 -4
  66. package/dist/cloud/db/schema.d.ts +397 -3
  67. package/dist/cloud/db/schema.js +75 -1
  68. package/dist/cloud/provisioner/index.d.ts +8 -0
  69. package/dist/cloud/provisioner/index.js +256 -50
  70. package/dist/cloud/server.js +47 -3
  71. package/dist/cloud/services/index.d.ts +1 -0
  72. package/dist/cloud/services/index.js +2 -0
  73. package/dist/cloud/services/nango.d.ts +3 -4
  74. package/dist/cloud/services/nango.js +11 -33
  75. package/dist/cloud/services/workspace-keepalive.d.ts +76 -0
  76. package/dist/cloud/services/workspace-keepalive.js +234 -0
  77. package/dist/config/relay-config.d.ts +23 -0
  78. package/dist/config/relay-config.js +23 -0
  79. package/dist/daemon/agent-manager.d.ts +20 -1
  80. package/dist/daemon/agent-manager.js +51 -0
  81. package/dist/daemon/agent-registry.js +4 -4
  82. package/dist/daemon/agent-signing.d.ts +158 -0
  83. package/dist/daemon/agent-signing.js +523 -0
  84. package/dist/daemon/api.js +18 -1
  85. package/dist/daemon/cli-auth.d.ts +4 -1
  86. package/dist/daemon/cli-auth.js +55 -11
  87. package/dist/daemon/cloud-sync.d.ts +47 -1
  88. package/dist/daemon/cloud-sync.js +152 -3
  89. package/dist/daemon/connection.d.ts +28 -0
  90. package/dist/daemon/connection.js +113 -22
  91. package/dist/daemon/consensus-integration.d.ts +167 -0
  92. package/dist/daemon/consensus-integration.js +371 -0
  93. package/dist/daemon/consensus.d.ts +271 -0
  94. package/dist/daemon/consensus.js +632 -0
  95. package/dist/daemon/delivery-tracker.d.ts +34 -0
  96. package/dist/daemon/delivery-tracker.js +104 -0
  97. package/dist/daemon/enhanced-features.d.ts +118 -0
  98. package/dist/daemon/enhanced-features.js +178 -0
  99. package/dist/daemon/index.d.ts +4 -0
  100. package/dist/daemon/index.js +5 -0
  101. package/dist/daemon/rate-limiter.d.ts +68 -0
  102. package/dist/daemon/rate-limiter.js +130 -0
  103. package/dist/daemon/router.d.ts +18 -11
  104. package/dist/daemon/router.js +57 -113
  105. package/dist/daemon/server.d.ts +13 -1
  106. package/dist/daemon/server.js +71 -9
  107. package/dist/daemon/sync-queue.d.ts +116 -0
  108. package/dist/daemon/sync-queue.js +361 -0
  109. package/dist/dashboard/out/404.html +1 -1
  110. package/dist/dashboard/out/_next/static/chunks/116-de2a4ac06e5000dc.js +1 -0
  111. package/dist/dashboard/out/_next/static/chunks/847-f1f467060f32afff.js +1 -0
  112. package/dist/dashboard/out/_next/static/chunks/919-87d604a5d76c1fbd.js +1 -0
  113. package/dist/dashboard/out/_next/static/chunks/app/app/{page-c617745b81344f4f.js → page-7f64824ae7d06707.js} +1 -1
  114. package/dist/dashboard/out/_next/static/chunks/app/cloud/link/page-3f559d393902aad2.js +1 -0
  115. package/dist/dashboard/out/_next/static/chunks/app/login/page-16d1715ddaa874ee.js +1 -0
  116. package/dist/dashboard/out/_next/static/chunks/app/{page-dc786c183425c2ac.js → page-814efc4d77b4191d.js} +1 -1
  117. package/dist/dashboard/out/_next/static/chunks/{main-2ee6beb2ae96d210.js → main-5a40a5ae29646e1b.js} +1 -1
  118. package/dist/dashboard/out/_next/static/css/44d2b52637b511bc.css +1 -0
  119. package/dist/dashboard/out/app/onboarding.html +1 -1
  120. package/dist/dashboard/out/app/onboarding.txt +1 -1
  121. package/dist/dashboard/out/app.html +1 -1
  122. package/dist/dashboard/out/app.txt +2 -2
  123. package/dist/dashboard/out/cloud/link.html +1 -0
  124. package/dist/dashboard/out/cloud/link.txt +7 -0
  125. package/dist/dashboard/out/connect-repos.html +1 -1
  126. package/dist/dashboard/out/connect-repos.txt +1 -1
  127. package/dist/dashboard/out/history.html +1 -1
  128. package/dist/dashboard/out/history.txt +2 -2
  129. package/dist/dashboard/out/index.html +1 -1
  130. package/dist/dashboard/out/index.txt +2 -2
  131. package/dist/dashboard/out/login.html +2 -3
  132. package/dist/dashboard/out/login.txt +2 -2
  133. package/dist/dashboard/out/metrics.html +1 -1
  134. package/dist/dashboard/out/metrics.txt +2 -2
  135. package/dist/dashboard/out/pricing.html +2 -2
  136. package/dist/dashboard/out/pricing.txt +1 -1
  137. package/dist/dashboard/out/providers/setup/claude.html +1 -1
  138. package/dist/dashboard/out/providers/setup/claude.txt +1 -1
  139. package/dist/dashboard/out/providers/setup/codex.html +1 -1
  140. package/dist/dashboard/out/providers/setup/codex.txt +1 -1
  141. package/dist/dashboard/out/providers.html +1 -1
  142. package/dist/dashboard/out/providers.txt +1 -1
  143. package/dist/dashboard/out/signup.html +2 -2
  144. package/dist/dashboard/out/signup.txt +1 -1
  145. package/dist/dashboard-server/server.js +244 -28
  146. package/dist/health-worker-manager.d.ts +62 -0
  147. package/dist/health-worker-manager.js +144 -0
  148. package/dist/health-worker.d.ts +9 -0
  149. package/dist/health-worker.js +79 -0
  150. package/dist/index.d.ts +2 -1
  151. package/dist/index.js +5 -1
  152. package/dist/memory/context-compaction.d.ts +156 -0
  153. package/dist/memory/context-compaction.js +453 -0
  154. package/dist/memory/index.d.ts +1 -0
  155. package/dist/memory/index.js +1 -0
  156. package/dist/protocol/channels.js +4 -4
  157. package/dist/protocol/framing.d.ts +72 -10
  158. package/dist/protocol/framing.js +194 -25
  159. package/dist/storage/adapter.d.ts +8 -1
  160. package/dist/storage/adapter.js +11 -0
  161. package/dist/storage/batched-sqlite-adapter.d.ts +71 -0
  162. package/dist/storage/batched-sqlite-adapter.js +183 -0
  163. package/dist/storage/dead-letter-queue.d.ts +196 -0
  164. package/dist/storage/dead-letter-queue.js +427 -0
  165. package/dist/storage/dlq-adapter.d.ts +195 -0
  166. package/dist/storage/dlq-adapter.js +664 -0
  167. package/dist/trajectory/config.d.ts +32 -14
  168. package/dist/trajectory/config.js +38 -16
  169. package/dist/trajectory/integration.js +217 -64
  170. package/dist/utils/git-remote.d.ts +47 -0
  171. package/dist/utils/git-remote.js +125 -0
  172. package/dist/utils/id-generator.d.ts +35 -0
  173. package/dist/utils/id-generator.js +60 -0
  174. package/dist/utils/index.d.ts +1 -0
  175. package/dist/utils/index.js +1 -0
  176. package/dist/utils/precompiled-patterns.d.ts +110 -0
  177. package/dist/utils/precompiled-patterns.js +322 -0
  178. package/dist/wrapper/auth-detection.js +1 -1
  179. package/dist/wrapper/base-wrapper.d.ts +40 -0
  180. package/dist/wrapper/base-wrapper.js +60 -6
  181. package/dist/wrapper/client.d.ts +14 -4
  182. package/dist/wrapper/client.js +89 -31
  183. package/dist/wrapper/idle-detector.d.ts +102 -0
  184. package/dist/wrapper/idle-detector.js +279 -0
  185. package/dist/wrapper/parser.d.ts +4 -0
  186. package/dist/wrapper/parser.js +19 -1
  187. package/dist/wrapper/pty-wrapper.d.ts +14 -2
  188. package/dist/wrapper/pty-wrapper.js +132 -32
  189. package/dist/wrapper/shared.d.ts +1 -1
  190. package/dist/wrapper/shared.js +1 -1
  191. package/dist/wrapper/tmux-wrapper.d.ts +20 -2
  192. package/dist/wrapper/tmux-wrapper.js +163 -40
  193. package/package.json +3 -1
  194. package/scripts/run-migrations.js +43 -0
  195. package/scripts/verify-schema.js +134 -0
  196. package/tests/benchmarks/protocol.bench.ts +310 -0
  197. package/dist/dashboard/out/_next/static/chunks/116-2502180def231162.js +0 -1
  198. package/dist/dashboard/out/_next/static/chunks/899-fc02ed79e3de4302.js +0 -1
  199. package/dist/dashboard/out/_next/static/chunks/app/login/page-c22d080201cbd9fb.js +0 -1
  200. package/dist/dashboard/out/_next/static/css/48a8fbe3e659080e.css +0 -1
  201. /package/dist/dashboard/out/_next/static/{sDcbGRTYLcpPvyTs_rsNb → R-uQOUcOLINtsp6ACeZa9}/_buildManifest.js +0 -0
  202. /package/dist/dashboard/out/_next/static/{sDcbGRTYLcpPvyTs_rsNb → R-uQOUcOLINtsp6ACeZa9}/_ssgManifest.js +0 -0
@@ -0,0 +1,279 @@
1
+ /**
2
+ * UniversalIdleDetector - Detect when an agent is waiting for input
3
+ *
4
+ * Works across all CLI tools (Claude, Codex, Gemini, Aider, etc.) by combining:
5
+ * 1. Process state inspection via /proc/{pid}/stat (Linux, 95% confidence)
6
+ * 2. Output silence analysis (cross-platform, 60-80% confidence)
7
+ * 3. Natural ending detection (heuristic, 60% confidence)
8
+ *
9
+ * The hybrid approach ensures reliable idle detection regardless of CLI type.
10
+ */
11
+ import fs from 'node:fs';
12
+ const DEFAULT_CONFIG = {
13
+ minSilenceMs: 500,
14
+ bufferLimit: 10000,
15
+ confidenceThreshold: 0.7,
16
+ };
17
+ /**
18
+ * Universal idle detector for any CLI-based agent.
19
+ */
20
+ export class UniversalIdleDetector {
21
+ lastOutputTime = 0;
22
+ outputBuffer = '';
23
+ pid = null;
24
+ config;
25
+ constructor(config = {}) {
26
+ this.config = { ...DEFAULT_CONFIG, ...config };
27
+ // Initialize lastOutputTime to now to avoid immediate false positives
28
+ this.lastOutputTime = Date.now();
29
+ }
30
+ /**
31
+ * Set the PID of the agent process to monitor.
32
+ * Required for Linux process state inspection.
33
+ */
34
+ setPid(pid) {
35
+ this.pid = pid;
36
+ }
37
+ /**
38
+ * Get the current PID being monitored.
39
+ */
40
+ getPid() {
41
+ return this.pid;
42
+ }
43
+ /**
44
+ * Process output chunk from the agent.
45
+ * Call this for every output received from the agent process.
46
+ */
47
+ onOutput(chunk) {
48
+ this.lastOutputTime = Date.now();
49
+ this.outputBuffer += chunk;
50
+ // Keep buffer bounded
51
+ if (this.outputBuffer.length > this.config.bufferLimit) {
52
+ this.outputBuffer = this.outputBuffer.slice(-Math.floor(this.config.bufferLimit / 2));
53
+ }
54
+ }
55
+ /**
56
+ * Check if the agent process is blocked on read (waiting for input).
57
+ * This is the most reliable signal - the OS knows when a process is waiting.
58
+ *
59
+ * Linux-only; returns null on other platforms.
60
+ */
61
+ isProcessWaitingForInput() {
62
+ if (process.platform !== 'linux' || !this.pid) {
63
+ return null; // Can't determine on non-Linux
64
+ }
65
+ try {
66
+ // Check process state from /proc/{pid}/stat
67
+ // State codes: R=running, S=sleeping, D=disk sleep, Z=zombie, T=stopped
68
+ const statPath = `/proc/${this.pid}/stat`;
69
+ const stat = fs.readFileSync(statPath, 'utf-8');
70
+ const fields = stat.split(' ');
71
+ const state = fields[2]; // Third field is state
72
+ // S (sleeping) often means waiting for I/O
73
+ if (state !== 'S') {
74
+ return { waiting: false }; // Running or other state = not waiting
75
+ }
76
+ // More precise: check what the process is blocked on
77
+ const wchanPath = `/proc/${this.pid}/wchan`;
78
+ const wchan = fs.readFileSync(wchanPath, 'utf-8').trim();
79
+ // Common wait channels for terminal input
80
+ const inputWaitChannels = [
81
+ 'wait_woken',
82
+ 'poll_schedule_timeout',
83
+ 'do_select',
84
+ 'n_tty_read',
85
+ 'unix_stream_read_generic',
86
+ 'pipe_read',
87
+ 'ep_poll', // epoll wait
88
+ 'futex_wait_queue', // sometimes seen
89
+ ];
90
+ const isWaiting = inputWaitChannels.some(ch => wchan.includes(ch));
91
+ return { waiting: isWaiting, wchan };
92
+ }
93
+ catch {
94
+ return null; // Process may have exited or permission denied
95
+ }
96
+ }
97
+ /**
98
+ * Get milliseconds since last output.
99
+ */
100
+ getOutputSilenceMs() {
101
+ return Date.now() - this.lastOutputTime;
102
+ }
103
+ /**
104
+ * Check if the last output ends "naturally" (complete thought vs mid-sentence).
105
+ * Helps distinguish between pauses in output and waiting for input.
106
+ */
107
+ hasNaturalEnding() {
108
+ const lastChars = this.outputBuffer.slice(-100).trim();
109
+ if (lastChars.length === 0)
110
+ return true;
111
+ // Positive signals: output ended cleanly
112
+ const naturalEndings = [
113
+ /[.!?]\s*$/, // Sentence ended
114
+ /```\s*$/, // Code block closed
115
+ /\n\n$/, // Paragraph break
116
+ />\s*$/, // Prompt character
117
+ /\$\s*$/, // Shell prompt
118
+ />>>\s*$/, // Python/Aider prompt
119
+ /❯\s*$/, // Fancy prompts
120
+ /λ\s*$/, // Lambda prompts
121
+ /→\s*$/, // Arrow prompts
122
+ ];
123
+ // Negative signals: output mid-thought
124
+ const midThought = [
125
+ /[,;:]\s*$/, // Comma, semicolon = more coming
126
+ /\w$/, // Ended mid-word (no trailing space)
127
+ /[-–—]\s*$/, // Dash = continuation
128
+ /\(\s*$/, // Open paren
129
+ /\[\s*$/, // Open bracket
130
+ /\{\s*$/, // Open brace
131
+ /\\\s*$/, // Line continuation
132
+ ];
133
+ for (const pattern of midThought) {
134
+ if (pattern.test(lastChars)) {
135
+ return false;
136
+ }
137
+ }
138
+ for (const pattern of naturalEndings) {
139
+ if (pattern.test(lastChars)) {
140
+ return true;
141
+ }
142
+ }
143
+ // Default: assume natural if no negative signals and some silence
144
+ return true;
145
+ }
146
+ /**
147
+ * Determine if the agent is idle and ready for input.
148
+ * Combines multiple signals for reliability across all CLI types.
149
+ */
150
+ checkIdle(options = {}) {
151
+ const minSilence = options.minSilenceMs ?? this.config.minSilenceMs;
152
+ const signals = [];
153
+ // Signal 1: Process state (most reliable on Linux)
154
+ const processState = this.isProcessWaitingForInput();
155
+ if (processState !== null) {
156
+ if (processState.waiting) {
157
+ signals.push({
158
+ source: 'process_state',
159
+ confidence: 0.95, // Very high - OS-level truth
160
+ timestamp: Date.now(),
161
+ details: processState.wchan,
162
+ });
163
+ }
164
+ else {
165
+ // Process is actively running - definitely not idle
166
+ return {
167
+ isIdle: false,
168
+ confidence: 0.95,
169
+ signals: [{
170
+ source: 'process_state',
171
+ confidence: 0.95,
172
+ timestamp: Date.now(),
173
+ details: 'process running',
174
+ }],
175
+ };
176
+ }
177
+ }
178
+ // processState === null means we can't determine (non-Linux)
179
+ // Signal 2: Output silence
180
+ const silenceMs = this.getOutputSilenceMs();
181
+ if (silenceMs > minSilence) {
182
+ // Confidence scales with silence duration (up to 0.8)
183
+ // 500ms = 0.13, 1000ms = 0.27, 2000ms = 0.53, 3000ms = 0.8
184
+ const silenceConfidence = Math.min(silenceMs / 3000, 0.8);
185
+ signals.push({
186
+ source: 'output_silence',
187
+ confidence: silenceConfidence,
188
+ timestamp: Date.now(),
189
+ details: `${silenceMs}ms`,
190
+ });
191
+ }
192
+ // Signal 3: Natural ending (only if some silence)
193
+ if (silenceMs > 200 && this.hasNaturalEnding()) {
194
+ signals.push({
195
+ source: 'natural_ending',
196
+ confidence: 0.6,
197
+ timestamp: Date.now(),
198
+ });
199
+ }
200
+ // No signals = not idle
201
+ if (signals.length === 0) {
202
+ return { isIdle: false, confidence: 0, signals: [] };
203
+ }
204
+ // Combine signals
205
+ // Use max confidence, boosted if multiple signals agree
206
+ const maxConfidence = Math.max(...signals.map(s => s.confidence));
207
+ const boost = signals.length > 1 ? 0.1 : 0;
208
+ const combinedConfidence = Math.min(maxConfidence + boost, 1.0);
209
+ return {
210
+ isIdle: combinedConfidence >= this.config.confidenceThreshold,
211
+ confidence: combinedConfidence,
212
+ signals,
213
+ };
214
+ }
215
+ /**
216
+ * Wait for idle state with timeout.
217
+ * Returns the idle result when achieved or after timeout.
218
+ */
219
+ async waitForIdle(timeoutMs = 30000, pollMs = 200) {
220
+ const startTime = Date.now();
221
+ while (Date.now() - startTime < timeoutMs) {
222
+ const result = this.checkIdle();
223
+ if (result.isIdle) {
224
+ return result;
225
+ }
226
+ await new Promise(r => setTimeout(r, pollMs));
227
+ }
228
+ // Timeout - return current state
229
+ return this.checkIdle();
230
+ }
231
+ /**
232
+ * Reset state (call when agent starts new response).
233
+ */
234
+ reset() {
235
+ this.outputBuffer = '';
236
+ this.lastOutputTime = Date.now();
237
+ }
238
+ /**
239
+ * Get time since last output in milliseconds.
240
+ */
241
+ getTimeSinceLastOutput() {
242
+ return this.getOutputSilenceMs();
243
+ }
244
+ }
245
+ /**
246
+ * Get the PID of a process running in a tmux pane.
247
+ * Uses tmux list-panes with format specifier.
248
+ */
249
+ export async function getTmuxPanePid(tmuxPath, sessionName) {
250
+ const { exec } = await import('node:child_process');
251
+ const { promisify } = await import('node:util');
252
+ const execAsync = promisify(exec);
253
+ try {
254
+ // Get the PID of the command running in the pane
255
+ const { stdout } = await execAsync(`"${tmuxPath}" list-panes -t ${sessionName} -F "#{pane_pid}" 2>/dev/null`);
256
+ const pid = parseInt(stdout.trim(), 10);
257
+ return isNaN(pid) ? null : pid;
258
+ }
259
+ catch {
260
+ return null;
261
+ }
262
+ }
263
+ /**
264
+ * Create an idle detector configured for the current platform.
265
+ * Logs a warning on non-Linux platforms where process state inspection isn't available.
266
+ */
267
+ export function createIdleDetector(config = {}, options = {}) {
268
+ const detector = new UniversalIdleDetector(config);
269
+ if (!options.quiet) {
270
+ if (process.platform === 'darwin') {
271
+ console.warn('[idle-detector] macOS: using output analysis only (less reliable)');
272
+ }
273
+ else if (process.platform === 'win32') {
274
+ console.warn('[idle-detector] Windows: using output analysis only (less reliable)');
275
+ }
276
+ }
277
+ return detector;
278
+ }
279
+ //# sourceMappingURL=idle-detector.js.map
@@ -104,6 +104,10 @@ export declare class OutputParser {
104
104
  *
105
105
  * IMPORTANT: We strip ANSI codes for pattern matching, but preserve
106
106
  * the original line for output to maintain terminal rendering.
107
+ *
108
+ * OPTIMIZATION: Early exit for lines that can't possibly be relay commands.
109
+ * Most lines don't contain relay patterns, so we avoid expensive regex/ANSI
110
+ * stripping for the common case.
107
111
  */
108
112
  private processLine;
109
113
  /**
@@ -640,9 +640,23 @@ export class OutputParser {
640
640
  *
641
641
  * IMPORTANT: We strip ANSI codes for pattern matching, but preserve
642
642
  * the original line for output to maintain terminal rendering.
643
+ *
644
+ * OPTIMIZATION: Early exit for lines that can't possibly be relay commands.
645
+ * Most lines don't contain relay patterns, so we avoid expensive regex/ANSI
646
+ * stripping for the common case.
643
647
  */
644
648
  processLine(line) {
645
- // Strip ANSI codes for pattern matching
649
+ // FAST PATH: Quick string check before any expensive operations
650
+ // Most lines don't contain relay commands, so early exit is a big win
651
+ // Check for prefix bases (without the colon to handle custom prefixes like '>>')
652
+ const relayBase = this.options.prefix.replace(/:$/, '');
653
+ const thinkingBase = this.options.thinkingPrefix.replace(/:$/, '');
654
+ const hasRelayPattern = line.includes(relayBase) || line.includes(thinkingBase);
655
+ const hasBlockPattern = line.includes('[[') || line.includes('```');
656
+ if (!hasRelayPattern && !hasBlockPattern) {
657
+ return { command: null, output: line };
658
+ }
659
+ // Strip ANSI codes for pattern matching (only when potentially needed)
646
660
  const stripped = stripAnsi(line);
647
661
  // Handle code fences
648
662
  if (CODE_FENCE.test(stripped)) {
@@ -672,11 +686,13 @@ export class OutputParser {
672
686
  const [raw, target, threadProject, threadId, body] = relayMatch;
673
687
  // Skip instructional/example text (common in system prompts)
674
688
  if (isInstructionalText(body)) {
689
+ console.error(`[parser] Filtered inline message to ${target} - instructional text. Body: ${body.substring(0, 100)}`);
675
690
  return { command: null, output: line };
676
691
  }
677
692
  const { to, project } = parseTarget(target);
678
693
  // Skip placeholder target names (common in documentation/examples)
679
694
  if (isPlaceholderTarget(to)) {
695
+ console.error(`[parser] Filtered inline message - placeholder target: ${to}`);
680
696
  return { command: null, output: line };
681
697
  }
682
698
  return {
@@ -795,10 +811,12 @@ export class OutputParser {
795
811
  shouldFilterFencedInline(target, body) {
796
812
  // Check for placeholder target names
797
813
  if (isPlaceholderTarget(target)) {
814
+ // Silently filter placeholder targets (common in documentation)
798
815
  return true;
799
816
  }
800
817
  // Check for instructional body content
801
818
  if (isInstructionalText(body)) {
819
+ // Silently filter instructional text (common in system prompts)
802
820
  return true;
803
821
  }
804
822
  return false;
@@ -112,6 +112,11 @@ export declare class PtyWrapper extends BaseWrapper {
112
112
  * Start the agent process
113
113
  */
114
114
  start(): Promise<void>;
115
+ /**
116
+ * Wait for the agent to be ready for input.
117
+ * Uses idle detection instead of a fixed delay.
118
+ */
119
+ private waitForAgentReady;
115
120
  /**
116
121
  * Inject continuity context from previous session.
117
122
  * Called after agent ID initialization to restore state from ledger.
@@ -225,9 +230,15 @@ export declare class PtyWrapper extends BaseWrapper {
225
230
  protected parseSpawnReleaseCommands(content: string): void;
226
231
  /**
227
232
  * Execute spawn via API or callback.
228
- * Overrides BaseWrapper to add PTY-specific logging and API path.
233
+ * After spawning, waits for the agent to come online and sends the task via relay.
229
234
  */
230
235
  protected executeSpawn(name: string, cli: string, task: string): Promise<void>;
236
+ /**
237
+ * Wait for a spawned agent to come online, then send the task via relay.
238
+ * Uses the wrapper's own relay client so the message comes "from" this agent,
239
+ * not from the dashboard's relay client.
240
+ */
241
+ private waitAndSendTask;
231
242
  /**
232
243
  * Execute release via API or callback.
233
244
  * Overrides BaseWrapper to add PTY-specific logging and API path.
@@ -240,7 +251,8 @@ export declare class PtyWrapper extends BaseWrapper {
240
251
  protected handleIncomingMessage(from: string, payload: SendPayload, messageId: string, meta?: SendMeta, originalTo?: string): void;
241
252
  /**
242
253
  * Wait for output to stabilize before injection.
243
- * Returns true if output has been stable for the required duration.
254
+ * Uses UniversalIdleDetector (from BaseWrapper) for robust cross-CLI idle detection.
255
+ * Returns true if agent is idle and ready for input.
244
256
  */
245
257
  private waitForOutputStable;
246
258
  /**