agent-relay 1.3.0 → 1.3.2

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 (240) 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/TRAIL_GIT_AUTH_FIX.md +113 -0
  37. package/deploy/workspace/codex.config.toml +1 -1
  38. package/deploy/workspace/entrypoint.sh +20 -79
  39. package/deploy/workspace/gh-relay +156 -0
  40. package/deploy/workspace/git-credential-relay +5 -1
  41. package/dist/bridge/multi-project-client.js +13 -10
  42. package/dist/bridge/spawner.d.ts +2 -0
  43. package/dist/bridge/spawner.js +19 -1
  44. package/dist/bridge/types.d.ts +2 -0
  45. package/dist/cli/index.d.ts +1 -1
  46. package/dist/cli/index.js +115 -69
  47. package/dist/cloud/api/admin.js +16 -3
  48. package/dist/cloud/api/codex-auth-helper.js +28 -8
  49. package/dist/cloud/api/consensus.d.ts +13 -0
  50. package/dist/cloud/api/consensus.js +259 -0
  51. package/dist/cloud/api/daemons.js +205 -1
  52. package/dist/cloud/api/git.js +37 -7
  53. package/dist/cloud/api/onboarding.js +4 -1
  54. package/dist/cloud/api/provider-env.d.ts +5 -0
  55. package/dist/cloud/api/provider-env.js +27 -0
  56. package/dist/cloud/api/providers.js +2 -0
  57. package/dist/cloud/api/test-helpers.js +130 -0
  58. package/dist/cloud/api/workspaces.js +38 -3
  59. package/dist/cloud/db/bulk-ingest.d.ts +88 -0
  60. package/dist/cloud/db/bulk-ingest.js +268 -0
  61. package/dist/cloud/db/drizzle.d.ts +33 -0
  62. package/dist/cloud/db/drizzle.js +174 -2
  63. package/dist/cloud/db/index.d.ts +24 -5
  64. package/dist/cloud/db/index.js +19 -4
  65. package/dist/cloud/db/schema.d.ts +397 -3
  66. package/dist/cloud/db/schema.js +75 -1
  67. package/dist/cloud/provisioner/index.d.ts +8 -0
  68. package/dist/cloud/provisioner/index.js +256 -50
  69. package/dist/cloud/server.js +47 -3
  70. package/dist/cloud/services/index.d.ts +1 -0
  71. package/dist/cloud/services/index.js +2 -0
  72. package/dist/cloud/services/nango.d.ts +3 -4
  73. package/dist/cloud/services/nango.js +11 -33
  74. package/dist/cloud/services/workspace-keepalive.d.ts +76 -0
  75. package/dist/cloud/services/workspace-keepalive.js +234 -0
  76. package/dist/config/relay-config.d.ts +23 -0
  77. package/dist/config/relay-config.js +23 -0
  78. package/dist/daemon/agent-manager.d.ts +20 -1
  79. package/dist/daemon/agent-manager.js +47 -0
  80. package/dist/daemon/agent-registry.js +4 -4
  81. package/dist/daemon/agent-signing.d.ts +158 -0
  82. package/dist/daemon/agent-signing.js +523 -0
  83. package/dist/daemon/api.js +18 -1
  84. package/dist/daemon/cli-auth.d.ts +4 -1
  85. package/dist/daemon/cli-auth.js +55 -11
  86. package/dist/daemon/cloud-sync.d.ts +47 -1
  87. package/dist/daemon/cloud-sync.js +152 -3
  88. package/dist/daemon/connection.d.ts +28 -0
  89. package/dist/daemon/connection.js +98 -15
  90. package/dist/daemon/consensus-integration.d.ts +167 -0
  91. package/dist/daemon/consensus-integration.js +371 -0
  92. package/dist/daemon/consensus.d.ts +271 -0
  93. package/dist/daemon/consensus.js +632 -0
  94. package/dist/daemon/delivery-tracker.d.ts +34 -0
  95. package/dist/daemon/delivery-tracker.js +104 -0
  96. package/dist/daemon/enhanced-features.d.ts +118 -0
  97. package/dist/daemon/enhanced-features.js +178 -0
  98. package/dist/daemon/index.d.ts +4 -0
  99. package/dist/daemon/index.js +5 -0
  100. package/dist/daemon/rate-limiter.d.ts +68 -0
  101. package/dist/daemon/rate-limiter.js +130 -0
  102. package/dist/daemon/router.d.ts +18 -11
  103. package/dist/daemon/router.js +55 -111
  104. package/dist/daemon/server.d.ts +13 -1
  105. package/dist/daemon/server.js +71 -9
  106. package/dist/daemon/sync-queue.d.ts +116 -0
  107. package/dist/daemon/sync-queue.js +361 -0
  108. package/dist/health-worker-manager.d.ts +62 -0
  109. package/dist/health-worker-manager.js +144 -0
  110. package/dist/health-worker.d.ts +9 -0
  111. package/dist/health-worker.js +79 -0
  112. package/dist/index.d.ts +2 -1
  113. package/dist/index.js +5 -1
  114. package/dist/memory/context-compaction.d.ts +156 -0
  115. package/dist/memory/context-compaction.js +453 -0
  116. package/dist/memory/index.d.ts +1 -0
  117. package/dist/memory/index.js +1 -0
  118. package/dist/protocol/channels.js +4 -4
  119. package/dist/protocol/framing.d.ts +72 -10
  120. package/dist/protocol/framing.js +194 -25
  121. package/dist/storage/adapter.d.ts +8 -1
  122. package/dist/storage/adapter.js +11 -0
  123. package/dist/storage/batched-sqlite-adapter.d.ts +71 -0
  124. package/dist/storage/batched-sqlite-adapter.js +183 -0
  125. package/dist/storage/dead-letter-queue.d.ts +196 -0
  126. package/dist/storage/dead-letter-queue.js +427 -0
  127. package/dist/storage/dlq-adapter.d.ts +195 -0
  128. package/dist/storage/dlq-adapter.js +664 -0
  129. package/dist/trajectory/config.d.ts +32 -14
  130. package/dist/trajectory/config.js +38 -16
  131. package/dist/trajectory/integration.js +217 -64
  132. package/dist/utils/git-remote.d.ts +47 -0
  133. package/dist/utils/git-remote.js +125 -0
  134. package/dist/utils/id-generator.d.ts +35 -0
  135. package/dist/utils/id-generator.js +60 -0
  136. package/dist/utils/index.d.ts +1 -0
  137. package/dist/utils/index.js +1 -0
  138. package/dist/utils/precompiled-patterns.d.ts +110 -0
  139. package/dist/utils/precompiled-patterns.js +322 -0
  140. package/dist/wrapper/auth-detection.js +1 -1
  141. package/dist/wrapper/base-wrapper.d.ts +36 -0
  142. package/dist/wrapper/base-wrapper.js +48 -2
  143. package/dist/wrapper/client.d.ts +14 -4
  144. package/dist/wrapper/client.js +84 -31
  145. package/dist/wrapper/idle-detector.d.ts +102 -0
  146. package/dist/wrapper/idle-detector.js +279 -0
  147. package/dist/wrapper/parser.d.ts +4 -0
  148. package/dist/wrapper/parser.js +19 -1
  149. package/dist/wrapper/pty-wrapper.d.ts +7 -1
  150. package/dist/wrapper/pty-wrapper.js +51 -27
  151. package/dist/wrapper/tmux-wrapper.d.ts +12 -1
  152. package/dist/wrapper/tmux-wrapper.js +65 -17
  153. package/package.json +5 -5
  154. package/scripts/run-migrations.js +43 -0
  155. package/scripts/verify-schema.js +134 -0
  156. package/tests/benchmarks/protocol.bench.ts +310 -0
  157. package/dist/dashboard/out/404.html +0 -1
  158. package/dist/dashboard/out/_next/static/T1tgCqVWHFIkV7ClEtzD7/_buildManifest.js +0 -1
  159. package/dist/dashboard/out/_next/static/T1tgCqVWHFIkV7ClEtzD7/_ssgManifest.js +0 -1
  160. package/dist/dashboard/out/_next/static/chunks/116-2502180def231162.js +0 -1
  161. package/dist/dashboard/out/_next/static/chunks/117-f7b8ab0809342e77.js +0 -2
  162. package/dist/dashboard/out/_next/static/chunks/282-980c2eb8fff20123.js +0 -1
  163. package/dist/dashboard/out/_next/static/chunks/532-bace199897eeab37.js +0 -9
  164. package/dist/dashboard/out/_next/static/chunks/648-5cc6e1921389a58a.js +0 -1
  165. package/dist/dashboard/out/_next/static/chunks/766-b54f0853794b78c3.js +0 -1
  166. package/dist/dashboard/out/_next/static/chunks/83-b51836037078006c.js +0 -1
  167. package/dist/dashboard/out/_next/static/chunks/891-6cd50de1224f70bb.js +0 -1
  168. package/dist/dashboard/out/_next/static/chunks/899-bb19a9b3d9b39ea6.js +0 -1
  169. package/dist/dashboard/out/_next/static/chunks/app/_not-found/page-53b8a69f76db17d0.js +0 -1
  170. package/dist/dashboard/out/_next/static/chunks/app/app/onboarding/page-8939b0fc700f7eca.js +0 -1
  171. package/dist/dashboard/out/_next/static/chunks/app/app/page-5af1b6b439858aa6.js +0 -1
  172. package/dist/dashboard/out/_next/static/chunks/app/connect-repos/page-f45ecbc3e06134fc.js +0 -1
  173. package/dist/dashboard/out/_next/static/chunks/app/history/page-8c8bed33beb2bf1c.js +0 -1
  174. package/dist/dashboard/out/_next/static/chunks/app/layout-2433bb48965f4333.js +0 -1
  175. package/dist/dashboard/out/_next/static/chunks/app/login/page-16f3b49e55b1e0ed.js +0 -1
  176. package/dist/dashboard/out/_next/static/chunks/app/metrics/page-ac39dc0cc3c26fa7.js +0 -1
  177. package/dist/dashboard/out/_next/static/chunks/app/page-4a5938c18a11a654.js +0 -1
  178. package/dist/dashboard/out/_next/static/chunks/app/pricing/page-982a7000fee44014.js +0 -1
  179. package/dist/dashboard/out/_next/static/chunks/app/providers/page-ac3a6ac433fd6001.js +0 -1
  180. package/dist/dashboard/out/_next/static/chunks/app/providers/setup/[provider]/page-09f9caae98a18c09.js +0 -1
  181. package/dist/dashboard/out/_next/static/chunks/app/signup/page-547dd0ca55ecd0ba.js +0 -1
  182. package/dist/dashboard/out/_next/static/chunks/e868780c-48e5f147c90a3a41.js +0 -18
  183. package/dist/dashboard/out/_next/static/chunks/fd9d1056-609918ca7b6280bb.js +0 -1
  184. package/dist/dashboard/out/_next/static/chunks/framework-f66176bb897dc684.js +0 -1
  185. package/dist/dashboard/out/_next/static/chunks/main-2ee6beb2ae96d210.js +0 -1
  186. package/dist/dashboard/out/_next/static/chunks/main-app-5d692157a8eb1fd9.js +0 -1
  187. package/dist/dashboard/out/_next/static/chunks/pages/_app-72b849fbd24ac258.js +0 -1
  188. package/dist/dashboard/out/_next/static/chunks/pages/_error-7ba65e1336b92748.js +0 -1
  189. package/dist/dashboard/out/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -1
  190. package/dist/dashboard/out/_next/static/chunks/webpack-1cdd8ed57114d5e1.js +0 -1
  191. package/dist/dashboard/out/_next/static/css/85d2af9c7ac74d62.css +0 -1
  192. package/dist/dashboard/out/_next/static/css/fe4b28883eeff359.css +0 -1
  193. package/dist/dashboard/out/alt-logos/agent-relay-logo-128.png +0 -0
  194. package/dist/dashboard/out/alt-logos/agent-relay-logo-256.png +0 -0
  195. package/dist/dashboard/out/alt-logos/agent-relay-logo-32.png +0 -0
  196. package/dist/dashboard/out/alt-logos/agent-relay-logo-512.png +0 -0
  197. package/dist/dashboard/out/alt-logos/agent-relay-logo-64.png +0 -0
  198. package/dist/dashboard/out/alt-logos/agent-relay-logo.svg +0 -45
  199. package/dist/dashboard/out/alt-logos/logo.svg +0 -38
  200. package/dist/dashboard/out/alt-logos/monogram-logo-128.png +0 -0
  201. package/dist/dashboard/out/alt-logos/monogram-logo-256.png +0 -0
  202. package/dist/dashboard/out/alt-logos/monogram-logo-32.png +0 -0
  203. package/dist/dashboard/out/alt-logos/monogram-logo-512.png +0 -0
  204. package/dist/dashboard/out/alt-logos/monogram-logo-64.png +0 -0
  205. package/dist/dashboard/out/alt-logos/monogram-logo.svg +0 -38
  206. package/dist/dashboard/out/app/onboarding.html +0 -1
  207. package/dist/dashboard/out/app/onboarding.txt +0 -7
  208. package/dist/dashboard/out/app.html +0 -1
  209. package/dist/dashboard/out/app.txt +0 -7
  210. package/dist/dashboard/out/apple-icon.png +0 -0
  211. package/dist/dashboard/out/connect-repos.html +0 -1
  212. package/dist/dashboard/out/connect-repos.txt +0 -7
  213. package/dist/dashboard/out/history.html +0 -1
  214. package/dist/dashboard/out/history.txt +0 -7
  215. package/dist/dashboard/out/index.html +0 -1
  216. package/dist/dashboard/out/index.txt +0 -7
  217. package/dist/dashboard/out/login.html +0 -6
  218. package/dist/dashboard/out/login.txt +0 -7
  219. package/dist/dashboard/out/metrics.html +0 -1
  220. package/dist/dashboard/out/metrics.txt +0 -7
  221. package/dist/dashboard/out/pricing.html +0 -13
  222. package/dist/dashboard/out/pricing.txt +0 -7
  223. package/dist/dashboard/out/providers/setup/claude.html +0 -1
  224. package/dist/dashboard/out/providers/setup/claude.txt +0 -8
  225. package/dist/dashboard/out/providers/setup/codex.html +0 -1
  226. package/dist/dashboard/out/providers/setup/codex.txt +0 -8
  227. package/dist/dashboard/out/providers.html +0 -1
  228. package/dist/dashboard/out/providers.txt +0 -7
  229. package/dist/dashboard/out/signup.html +0 -6
  230. package/dist/dashboard/out/signup.txt +0 -7
  231. package/dist/dashboard-server/metrics.d.ts +0 -105
  232. package/dist/dashboard-server/metrics.js +0 -193
  233. package/dist/dashboard-server/needs-attention.d.ts +0 -24
  234. package/dist/dashboard-server/needs-attention.js +0 -78
  235. package/dist/dashboard-server/server.d.ts +0 -15
  236. package/dist/dashboard-server/server.js +0 -3776
  237. package/dist/dashboard-server/start.d.ts +0 -6
  238. package/dist/dashboard-server/start.js +0 -13
  239. package/dist/dashboard-server/user-bridge.d.ts +0 -103
  240. package/dist/dashboard-server/user-bridge.js +0 -189
@@ -20,6 +20,7 @@ import type { ParsedCommand, ParsedSummary } from './parser.js';
20
20
  import type { SendPayload, SendMeta, SpeakOnTrigger } from '../protocol/types.js';
21
21
  import { type QueuedMessage, type InjectionMetrics, type CliType } from './shared.js';
22
22
  import { type ContinuityManager } from '../continuity/index.js';
23
+ import { UniversalIdleDetector } from './idle-detector.js';
23
24
  /**
24
25
  * Base configuration shared by all wrapper types
25
26
  */
@@ -55,6 +56,10 @@ export interface BaseWrapperConfig {
55
56
  /** Shadow configuration */
56
57
  shadowOf?: string;
57
58
  shadowSpeakOn?: SpeakOnTrigger[];
59
+ /** Milliseconds of idle time before injection is allowed (default: 1500) */
60
+ idleBeforeInjectMs?: number;
61
+ /** Confidence threshold for idle detection (0-1, default: 0.7) */
62
+ idleConfidenceThreshold?: number;
58
63
  }
59
64
  /**
60
65
  * Abstract base class for agent wrappers
@@ -86,6 +91,7 @@ export declare abstract class BaseWrapper extends EventEmitter {
86
91
  completedTasks?: string[];
87
92
  };
88
93
  protected lastSummaryRawContent: string;
94
+ protected idleDetector: UniversalIdleDetector;
89
95
  constructor(config: BaseWrapperConfig);
90
96
  /** Start the agent process */
91
97
  abstract start(): Promise<void>;
@@ -102,6 +108,36 @@ export declare abstract class BaseWrapper extends EventEmitter {
102
108
  successRate: number;
103
109
  };
104
110
  get pendingMessageCount(): number;
111
+ /**
112
+ * Set the PID for process state inspection (Linux only).
113
+ * Call this after the agent process is started.
114
+ */
115
+ protected setIdleDetectorPid(pid: number): void;
116
+ /**
117
+ * Feed output to the idle detector.
118
+ * Call this whenever new output is received from the agent.
119
+ */
120
+ protected feedIdleDetectorOutput(output: string): void;
121
+ /**
122
+ * Check if the agent is idle and ready for injection.
123
+ * Returns idle state with confidence signals.
124
+ */
125
+ protected checkIdleForInjection(): {
126
+ isIdle: boolean;
127
+ confidence: number;
128
+ signals: Array<{
129
+ source: string;
130
+ confidence: number;
131
+ }>;
132
+ };
133
+ /**
134
+ * Wait for the agent to become idle.
135
+ * Returns when idle or after timeout.
136
+ */
137
+ protected waitForIdleState(timeoutMs?: number, pollMs?: number): Promise<{
138
+ isIdle: boolean;
139
+ confidence: number;
140
+ }>;
105
141
  /**
106
142
  * Handle incoming message from relay
107
143
  */
@@ -18,7 +18,9 @@ import { EventEmitter } from 'node:events';
18
18
  import { RelayClient } from './client.js';
19
19
  import { isPlaceholderTarget } from './parser.js';
20
20
  import { getDefaultRelayPrefix, detectCliType, createInjectionMetrics, } from './shared.js';
21
+ import { DEFAULT_IDLE_BEFORE_INJECT_MS, DEFAULT_IDLE_CONFIDENCE_THRESHOLD, } from '../config/relay-config.js';
21
22
  import { getContinuityManager, parseContinuityCommand, hasContinuityCommand, } from '../continuity/index.js';
23
+ import { UniversalIdleDetector } from './idle-detector.js';
22
24
  /**
23
25
  * Abstract base class for agent wrappers
24
26
  */
@@ -45,6 +47,8 @@ export class BaseWrapper extends EventEmitter {
45
47
  sessionEndProcessed = false;
46
48
  sessionEndData;
47
49
  lastSummaryRawContent = '';
50
+ // Universal idle detection (shared across all wrapper types)
51
+ idleDetector;
48
52
  constructor(config) {
49
53
  super();
50
54
  this.config = config;
@@ -61,6 +65,11 @@ export class BaseWrapper extends EventEmitter {
61
65
  });
62
66
  // Initialize continuity manager
63
67
  this.continuity = getContinuityManager({ defaultCli: this.cliType });
68
+ // Initialize universal idle detector for robust injection timing
69
+ this.idleDetector = new UniversalIdleDetector({
70
+ minSilenceMs: config.idleBeforeInjectMs ?? DEFAULT_IDLE_BEFORE_INJECT_MS,
71
+ confidenceThreshold: config.idleConfidenceThreshold ?? DEFAULT_IDLE_CONFIDENCE_THRESHOLD,
72
+ });
64
73
  // Set up message handler
65
74
  this.client.onMessage = (from, payload, messageId, meta, originalTo) => {
66
75
  this.handleIncomingMessage(from, payload, messageId, meta, originalTo);
@@ -93,6 +102,39 @@ export class BaseWrapper extends EventEmitter {
93
102
  return this.messageQueue.length;
94
103
  }
95
104
  // =========================================================================
105
+ // Idle detection (shared across all wrapper types)
106
+ // =========================================================================
107
+ /**
108
+ * Set the PID for process state inspection (Linux only).
109
+ * Call this after the agent process is started.
110
+ */
111
+ setIdleDetectorPid(pid) {
112
+ this.idleDetector.setPid(pid);
113
+ }
114
+ /**
115
+ * Feed output to the idle detector.
116
+ * Call this whenever new output is received from the agent.
117
+ */
118
+ feedIdleDetectorOutput(output) {
119
+ this.idleDetector.onOutput(output);
120
+ }
121
+ /**
122
+ * Check if the agent is idle and ready for injection.
123
+ * Returns idle state with confidence signals.
124
+ */
125
+ checkIdleForInjection() {
126
+ return this.idleDetector.checkIdle({
127
+ minSilenceMs: this.config.idleBeforeInjectMs ?? 1500,
128
+ });
129
+ }
130
+ /**
131
+ * Wait for the agent to become idle.
132
+ * Returns when idle or after timeout.
133
+ */
134
+ async waitForIdleState(timeoutMs = 30000, pollMs = 200) {
135
+ return this.idleDetector.waitForIdle(timeoutMs, pollMs);
136
+ }
137
+ // =========================================================================
96
138
  // Message handling
97
139
  // =========================================================================
98
140
  /**
@@ -126,12 +168,16 @@ export class BaseWrapper extends EventEmitter {
126
168
  */
127
169
  sendRelayCommand(cmd) {
128
170
  // Validate target
129
- if (isPlaceholderTarget(cmd.to))
171
+ if (isPlaceholderTarget(cmd.to)) {
172
+ console.error(`[base-wrapper] Skipped message - placeholder target: ${cmd.to}`);
130
173
  return;
174
+ }
131
175
  // Create hash for deduplication (use first 100 chars of body)
132
176
  const hash = `${cmd.to}:${cmd.body.substring(0, 100)}`;
133
- if (this.sentMessageHashes.has(hash))
177
+ if (this.sentMessageHashes.has(hash)) {
178
+ console.error(`[base-wrapper] Skipped duplicate message to ${cmd.to}`);
134
179
  return;
180
+ }
135
181
  this.sentMessageHashes.add(hash);
136
182
  // Limit hash set size
137
183
  if (this.sentMessageHashes.size > 500) {
@@ -1,6 +1,11 @@
1
1
  /**
2
2
  * Relay Client
3
3
  * Connects to the daemon and handles message sending/receiving.
4
+ *
5
+ * Optimizations:
6
+ * - Monotonic ID generation (faster than UUID)
7
+ * - Write coalescing (batch socket writes)
8
+ * - Circular dedup cache (O(1) eviction)
4
9
  */
5
10
  import { type SendPayload, type SendMeta, type PayloadKind, type SpeakOnTrigger, type EntityType } from '../protocol/types.js';
6
11
  export type ClientState = 'DISCONNECTED' | 'CONNECTING' | 'HANDSHAKING' | 'READY' | 'BACKOFF';
@@ -41,9 +46,9 @@ export declare class RelayClient {
41
46
  private reconnectDelay;
42
47
  private reconnectTimer?;
43
48
  private _destroyed;
44
- private deliveredIds;
45
- private deliveredOrder;
46
- private readonly deliveredCacheLimit;
49
+ private dedupeCache;
50
+ private writeQueue;
51
+ private writeScheduled;
47
52
  /**
48
53
  * Handler for incoming messages.
49
54
  * @param from - The sender agent name
@@ -123,6 +128,10 @@ export declare class RelayClient {
123
128
  private setState;
124
129
  private sendHello;
125
130
  private send;
131
+ /**
132
+ * Flush all queued writes in a single syscall.
133
+ */
134
+ private flushWrites;
126
135
  private handleData;
127
136
  private processFrame;
128
137
  private handleWelcome;
@@ -133,7 +142,8 @@ export declare class RelayClient {
133
142
  private handleError;
134
143
  private scheduleReconnect;
135
144
  /**
136
- * Track delivered message IDs to provide deterministic deduplication when messages are replayed.
145
+ * Check if message was already delivered (deduplication).
146
+ * Uses circular buffer for O(1) eviction.
137
147
  * @returns true if the message has already been seen.
138
148
  */
139
149
  private markDelivered;
@@ -1,11 +1,16 @@
1
1
  /**
2
2
  * Relay Client
3
3
  * Connects to the daemon and handles message sending/receiving.
4
+ *
5
+ * Optimizations:
6
+ * - Monotonic ID generation (faster than UUID)
7
+ * - Write coalescing (batch socket writes)
8
+ * - Circular dedup cache (O(1) eviction)
4
9
  */
5
10
  import net from 'node:net';
6
- import { v4 as uuid } from 'uuid';
11
+ import { generateId } from '../utils/id-generator.js';
7
12
  import { PROTOCOL_VERSION, } from '../protocol/types.js';
8
- import { encodeFrame, FrameParser } from '../protocol/framing.js';
13
+ import { encodeFrameLegacy, FrameParser } from '../protocol/framing.js';
9
14
  import { DEFAULT_SOCKET_PATH } from '../daemon/server.js';
10
15
  const DEFAULT_CLIENT_CONFIG = {
11
16
  socketPath: DEFAULT_SOCKET_PATH,
@@ -17,6 +22,40 @@ const DEFAULT_CLIENT_CONFIG = {
17
22
  reconnectDelayMs: 100,
18
23
  reconnectMaxDelayMs: 30000,
19
24
  };
25
+ /**
26
+ * Circular buffer for O(1) deduplication with bounded memory.
27
+ */
28
+ class CircularDedupeCache {
29
+ ids = new Set();
30
+ ring;
31
+ head = 0;
32
+ capacity;
33
+ constructor(capacity = 2000) {
34
+ this.capacity = capacity;
35
+ this.ring = new Array(capacity);
36
+ }
37
+ /** Returns true if duplicate (already seen) */
38
+ check(id) {
39
+ if (this.ids.has(id))
40
+ return true;
41
+ // Evict oldest if at capacity
42
+ if (this.ids.size >= this.capacity) {
43
+ const oldest = this.ring[this.head];
44
+ if (oldest)
45
+ this.ids.delete(oldest);
46
+ }
47
+ // Add new ID
48
+ this.ring[this.head] = id;
49
+ this.ids.add(id);
50
+ this.head = (this.head + 1) % this.capacity;
51
+ return false;
52
+ }
53
+ clear() {
54
+ this.ids.clear();
55
+ this.ring = new Array(this.capacity);
56
+ this.head = 0;
57
+ }
58
+ }
20
59
  export class RelayClient {
21
60
  config;
22
61
  socket;
@@ -28,9 +67,11 @@ export class RelayClient {
28
67
  reconnectDelay;
29
68
  reconnectTimer;
30
69
  _destroyed = false;
31
- deliveredIds = new Set();
32
- deliveredOrder = [];
33
- deliveredCacheLimit = 2000;
70
+ // Circular dedup cache (O(1) eviction vs O(n) array shift)
71
+ dedupeCache = new CircularDedupeCache(2000);
72
+ // Write coalescing: batch multiple writes into single syscall
73
+ writeQueue = [];
74
+ writeScheduled = false;
34
75
  // Event handlers
35
76
  /**
36
77
  * Handler for incoming messages.
@@ -46,6 +87,7 @@ export class RelayClient {
46
87
  constructor(config = {}) {
47
88
  this.config = { ...DEFAULT_CLIENT_CONFIG, ...config };
48
89
  this.parser = new FrameParser();
90
+ this.parser.setLegacyMode(true); // Use 4-byte header for backwards compatibility
49
91
  this.reconnectDelay = this.config.reconnectDelayMs;
50
92
  }
51
93
  get state() {
@@ -124,7 +166,7 @@ export class RelayClient {
124
166
  this.send({
125
167
  v: PROTOCOL_VERSION,
126
168
  type: 'BYE',
127
- id: uuid(),
169
+ id: generateId(),
128
170
  ts: Date.now(),
129
171
  payload: {},
130
172
  });
@@ -156,7 +198,7 @@ export class RelayClient {
156
198
  const envelope = {
157
199
  v: PROTOCOL_VERSION,
158
200
  type: 'SEND',
159
- id: uuid(),
201
+ id: generateId(),
160
202
  ts: Date.now(),
161
203
  to,
162
204
  payload: {
@@ -184,7 +226,7 @@ export class RelayClient {
184
226
  return this.send({
185
227
  v: PROTOCOL_VERSION,
186
228
  type: 'SUBSCRIBE',
187
- id: uuid(),
229
+ id: generateId(),
188
230
  ts: Date.now(),
189
231
  topic,
190
232
  payload: {},
@@ -199,7 +241,7 @@ export class RelayClient {
199
241
  return this.send({
200
242
  v: PROTOCOL_VERSION,
201
243
  type: 'UNSUBSCRIBE',
202
- id: uuid(),
244
+ id: generateId(),
203
245
  ts: Date.now(),
204
246
  topic,
205
247
  payload: {},
@@ -217,7 +259,7 @@ export class RelayClient {
217
259
  return this.send({
218
260
  v: PROTOCOL_VERSION,
219
261
  type: 'SHADOW_BIND',
220
- id: uuid(),
262
+ id: generateId(),
221
263
  ts: Date.now(),
222
264
  payload: {
223
265
  primaryAgent,
@@ -237,7 +279,7 @@ export class RelayClient {
237
279
  return this.send({
238
280
  v: PROTOCOL_VERSION,
239
281
  type: 'SHADOW_UNBIND',
240
- id: uuid(),
282
+ id: generateId(),
241
283
  ts: Date.now(),
242
284
  payload: {
243
285
  primaryAgent,
@@ -257,7 +299,7 @@ export class RelayClient {
257
299
  const envelope = {
258
300
  v: PROTOCOL_VERSION,
259
301
  type: 'LOG',
260
- id: uuid(),
302
+ id: generateId(),
261
303
  ts: Date.now(),
262
304
  payload: {
263
305
  data,
@@ -276,7 +318,7 @@ export class RelayClient {
276
318
  const hello = {
277
319
  v: PROTOCOL_VERSION,
278
320
  type: 'HELLO',
279
- id: uuid(),
321
+ id: generateId(),
280
322
  ts: Date.now(),
281
323
  payload: {
282
324
  agent: this.config.agentName,
@@ -303,8 +345,13 @@ export class RelayClient {
303
345
  if (!this.socket)
304
346
  return false;
305
347
  try {
306
- const frame = encodeFrame(envelope);
307
- this.socket.write(frame);
348
+ const frame = encodeFrameLegacy(envelope);
349
+ this.writeQueue.push(frame);
350
+ // Coalesce writes: schedule flush on next tick if not already scheduled
351
+ if (!this.writeScheduled) {
352
+ this.writeScheduled = true;
353
+ setImmediate(() => this.flushWrites());
354
+ }
308
355
  return true;
309
356
  }
310
357
  catch (err) {
@@ -312,6 +359,23 @@ export class RelayClient {
312
359
  return false;
313
360
  }
314
361
  }
362
+ /**
363
+ * Flush all queued writes in a single syscall.
364
+ */
365
+ flushWrites() {
366
+ this.writeScheduled = false;
367
+ if (this.writeQueue.length === 0 || !this.socket)
368
+ return;
369
+ if (this.writeQueue.length === 1) {
370
+ // Single frame - write directly (no concat needed)
371
+ this.socket.write(this.writeQueue[0]);
372
+ }
373
+ else {
374
+ // Multiple frames - batch into single write
375
+ this.socket.write(Buffer.concat(this.writeQueue));
376
+ }
377
+ this.writeQueue = [];
378
+ }
315
379
  handleData(data) {
316
380
  try {
317
381
  const frames = this.parser.push(data);
@@ -357,7 +421,7 @@ export class RelayClient {
357
421
  this.send({
358
422
  v: PROTOCOL_VERSION,
359
423
  type: 'ACK',
360
- id: uuid(),
424
+ id: generateId(),
361
425
  ts: Date.now(),
362
426
  payload: {
363
427
  ack_id: envelope.id,
@@ -378,7 +442,7 @@ export class RelayClient {
378
442
  this.send({
379
443
  v: PROTOCOL_VERSION,
380
444
  type: 'PONG',
381
- id: uuid(),
445
+ id: generateId(),
382
446
  ts: Date.now(),
383
447
  payload: envelope.payload ?? {},
384
448
  });
@@ -435,23 +499,12 @@ export class RelayClient {
435
499
  }, delay);
436
500
  }
437
501
  /**
438
- * Track delivered message IDs to provide deterministic deduplication when messages are replayed.
502
+ * Check if message was already delivered (deduplication).
503
+ * Uses circular buffer for O(1) eviction.
439
504
  * @returns true if the message has already been seen.
440
505
  */
441
506
  markDelivered(id) {
442
- if (this.deliveredIds.has(id)) {
443
- return true;
444
- }
445
- this.deliveredIds.add(id);
446
- this.deliveredOrder.push(id);
447
- // Simple FIFO eviction to keep memory bounded
448
- if (this.deliveredOrder.length > this.deliveredCacheLimit) {
449
- const oldest = this.deliveredOrder.shift();
450
- if (oldest) {
451
- this.deliveredIds.delete(oldest);
452
- }
453
- }
454
- return false;
507
+ return this.dedupeCache.check(id);
455
508
  }
456
509
  }
457
510
  //# sourceMappingURL=client.js.map
@@ -0,0 +1,102 @@
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
+ export interface IdleSignal {
12
+ source: 'process_state' | 'output_silence' | 'natural_ending';
13
+ confidence: number;
14
+ timestamp: number;
15
+ details?: string;
16
+ }
17
+ export interface IdleResult {
18
+ isIdle: boolean;
19
+ confidence: number;
20
+ signals: IdleSignal[];
21
+ }
22
+ export interface IdleDetectorConfig {
23
+ /** Minimum silence duration to consider for idle (ms) */
24
+ minSilenceMs?: number;
25
+ /** Output buffer size limit */
26
+ bufferLimit?: number;
27
+ /** Confidence threshold for idle detection (0-1) */
28
+ confidenceThreshold?: number;
29
+ }
30
+ /**
31
+ * Universal idle detector for any CLI-based agent.
32
+ */
33
+ export declare class UniversalIdleDetector {
34
+ private lastOutputTime;
35
+ private outputBuffer;
36
+ private pid;
37
+ private config;
38
+ constructor(config?: IdleDetectorConfig);
39
+ /**
40
+ * Set the PID of the agent process to monitor.
41
+ * Required for Linux process state inspection.
42
+ */
43
+ setPid(pid: number): void;
44
+ /**
45
+ * Get the current PID being monitored.
46
+ */
47
+ getPid(): number | null;
48
+ /**
49
+ * Process output chunk from the agent.
50
+ * Call this for every output received from the agent process.
51
+ */
52
+ onOutput(chunk: string): void;
53
+ /**
54
+ * Check if the agent process is blocked on read (waiting for input).
55
+ * This is the most reliable signal - the OS knows when a process is waiting.
56
+ *
57
+ * Linux-only; returns null on other platforms.
58
+ */
59
+ private isProcessWaitingForInput;
60
+ /**
61
+ * Get milliseconds since last output.
62
+ */
63
+ private getOutputSilenceMs;
64
+ /**
65
+ * Check if the last output ends "naturally" (complete thought vs mid-sentence).
66
+ * Helps distinguish between pauses in output and waiting for input.
67
+ */
68
+ private hasNaturalEnding;
69
+ /**
70
+ * Determine if the agent is idle and ready for input.
71
+ * Combines multiple signals for reliability across all CLI types.
72
+ */
73
+ checkIdle(options?: {
74
+ minSilenceMs?: number;
75
+ }): IdleResult;
76
+ /**
77
+ * Wait for idle state with timeout.
78
+ * Returns the idle result when achieved or after timeout.
79
+ */
80
+ waitForIdle(timeoutMs?: number, pollMs?: number): Promise<IdleResult>;
81
+ /**
82
+ * Reset state (call when agent starts new response).
83
+ */
84
+ reset(): void;
85
+ /**
86
+ * Get time since last output in milliseconds.
87
+ */
88
+ getTimeSinceLastOutput(): number;
89
+ }
90
+ /**
91
+ * Get the PID of a process running in a tmux pane.
92
+ * Uses tmux list-panes with format specifier.
93
+ */
94
+ export declare function getTmuxPanePid(tmuxPath: string, sessionName: string): Promise<number | null>;
95
+ /**
96
+ * Create an idle detector configured for the current platform.
97
+ * Logs a warning on non-Linux platforms where process state inspection isn't available.
98
+ */
99
+ export declare function createIdleDetector(config?: IdleDetectorConfig, options?: {
100
+ quiet?: boolean;
101
+ }): UniversalIdleDetector;
102
+ //# sourceMappingURL=idle-detector.d.ts.map