flaio-cli 1.0.12 → 1.0.14

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 (42) hide show
  1. package/dist/agents/agent-session.d.ts +4 -0
  2. package/dist/agents/agent-session.d.ts.map +1 -1
  3. package/dist/agents/agent-session.js +45 -1
  4. package/dist/agents/agent-session.js.map +1 -1
  5. package/dist/agents/drivers/base-driver.d.ts.map +1 -1
  6. package/dist/agents/drivers/base-driver.js +2 -2
  7. package/dist/agents/drivers/base-driver.js.map +1 -1
  8. package/dist/analytics/index.d.ts +1 -1
  9. package/dist/analytics/index.d.ts.map +1 -1
  10. package/dist/analytics/index.js +1 -1
  11. package/dist/analytics/index.js.map +1 -1
  12. package/dist/analytics/posthog.d.ts.map +1 -1
  13. package/dist/analytics/posthog.js +18 -0
  14. package/dist/analytics/posthog.js.map +1 -1
  15. package/dist/analytics/resource-monitor.d.ts.map +1 -1
  16. package/dist/analytics/resource-monitor.js +15 -0
  17. package/dist/analytics/resource-monitor.js.map +1 -1
  18. package/dist/analytics/sentry.d.ts +5 -0
  19. package/dist/analytics/sentry.d.ts.map +1 -1
  20. package/dist/analytics/sentry.js +27 -1
  21. package/dist/analytics/sentry.js.map +1 -1
  22. package/dist/cli.js +8 -0
  23. package/dist/cli.js.map +1 -1
  24. package/dist/config/config.d.ts +40 -40
  25. package/dist/relay/relay-client.d.ts +3 -0
  26. package/dist/relay/relay-client.d.ts.map +1 -1
  27. package/dist/relay/relay-client.js +155 -30
  28. package/dist/relay/relay-client.js.map +1 -1
  29. package/dist/store/app-store.d.ts.map +1 -1
  30. package/dist/store/app-store.js +29 -2
  31. package/dist/store/app-store.js.map +1 -1
  32. package/dist/store/connector-store.js +19 -2
  33. package/dist/store/connector-store.js.map +1 -1
  34. package/dist/strip-ansi.d.ts +2 -0
  35. package/dist/strip-ansi.d.ts.map +1 -0
  36. package/dist/strip-ansi.js +9 -0
  37. package/dist/strip-ansi.js.map +1 -0
  38. package/dist/terminal/screen-buffer.d.ts +3 -0
  39. package/dist/terminal/screen-buffer.d.ts.map +1 -1
  40. package/dist/terminal/screen-buffer.js +19 -0
  41. package/dist/terminal/screen-buffer.js.map +1 -1
  42. package/package.json +2 -1
@@ -7,19 +7,19 @@ declare const SlackConfigSchema: z.ZodObject<{
7
7
  pollInterval: z.ZodDefault<z.ZodNumber>;
8
8
  timeout: z.ZodDefault<z.ZodNumber>;
9
9
  }, "strip", z.ZodTypeAny, {
10
- timeout: number;
11
10
  enabled: boolean;
12
11
  pollInterval: number;
12
+ timeout: number;
13
13
  botToken?: string | undefined;
14
14
  appToken?: string | undefined;
15
15
  channelId?: string | undefined;
16
16
  }, {
17
- timeout?: number | undefined;
18
17
  enabled?: boolean | undefined;
19
18
  botToken?: string | undefined;
20
19
  appToken?: string | undefined;
21
20
  channelId?: string | undefined;
22
21
  pollInterval?: number | undefined;
22
+ timeout?: number | undefined;
23
23
  }>;
24
24
  declare const DiscordConfigSchema: z.ZodObject<{
25
25
  enabled: z.ZodDefault<z.ZodBoolean>;
@@ -27,15 +27,15 @@ declare const DiscordConfigSchema: z.ZodObject<{
27
27
  channelId: z.ZodOptional<z.ZodString>;
28
28
  timeout: z.ZodDefault<z.ZodNumber>;
29
29
  }, "strip", z.ZodTypeAny, {
30
- timeout: number;
31
30
  enabled: boolean;
31
+ timeout: number;
32
32
  botToken?: string | undefined;
33
33
  channelId?: string | undefined;
34
34
  }, {
35
- timeout?: number | undefined;
36
35
  enabled?: boolean | undefined;
37
36
  botToken?: string | undefined;
38
37
  channelId?: string | undefined;
38
+ timeout?: number | undefined;
39
39
  }>;
40
40
  declare const TelegramConfigSchema: z.ZodObject<{
41
41
  enabled: z.ZodDefault<z.ZodBoolean>;
@@ -43,14 +43,14 @@ declare const TelegramConfigSchema: z.ZodObject<{
43
43
  chatId: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
44
44
  timeout: z.ZodDefault<z.ZodNumber>;
45
45
  }, "strip", z.ZodTypeAny, {
46
- timeout: number;
47
46
  enabled: boolean;
47
+ timeout: number;
48
48
  botToken?: string | undefined;
49
49
  chatId?: string | number | undefined;
50
50
  }, {
51
- timeout?: number | undefined;
52
51
  enabled?: boolean | undefined;
53
52
  botToken?: string | undefined;
53
+ timeout?: number | undefined;
54
54
  chatId?: string | number | undefined;
55
55
  }>;
56
56
  declare const RelayConfigSchema: z.ZodObject<{
@@ -66,7 +66,7 @@ declare const RelayConfigSchema: z.ZodObject<{
66
66
  }, "strip", z.ZodTypeAny, {
67
67
  enabled: boolean;
68
68
  autoConnect: boolean;
69
- defaultShareMode: "read-only" | "read-write";
69
+ defaultShareMode: "read-write" | "read-only";
70
70
  maxReplayBufferKB: number;
71
71
  e2eEncryption: boolean;
72
72
  relayUrl: string;
@@ -78,7 +78,7 @@ declare const RelayConfigSchema: z.ZodObject<{
78
78
  authToken?: string | undefined;
79
79
  refreshToken?: string | undefined;
80
80
  autoConnect?: boolean | undefined;
81
- defaultShareMode?: "read-only" | "read-write" | undefined;
81
+ defaultShareMode?: "read-write" | "read-only" | undefined;
82
82
  maxReplayBufferKB?: number | undefined;
83
83
  e2eEncryption?: boolean | undefined;
84
84
  relayUrl?: string | undefined;
@@ -123,19 +123,19 @@ declare const AppConfigSchema: z.ZodObject<{
123
123
  pollInterval: z.ZodDefault<z.ZodNumber>;
124
124
  timeout: z.ZodDefault<z.ZodNumber>;
125
125
  }, "strip", z.ZodTypeAny, {
126
- timeout: number;
127
126
  enabled: boolean;
128
127
  pollInterval: number;
128
+ timeout: number;
129
129
  botToken?: string | undefined;
130
130
  appToken?: string | undefined;
131
131
  channelId?: string | undefined;
132
132
  }, {
133
- timeout?: number | undefined;
134
133
  enabled?: boolean | undefined;
135
134
  botToken?: string | undefined;
136
135
  appToken?: string | undefined;
137
136
  channelId?: string | undefined;
138
137
  pollInterval?: number | undefined;
138
+ timeout?: number | undefined;
139
139
  }>>;
140
140
  discord: z.ZodDefault<z.ZodObject<{
141
141
  enabled: z.ZodDefault<z.ZodBoolean>;
@@ -143,15 +143,15 @@ declare const AppConfigSchema: z.ZodObject<{
143
143
  channelId: z.ZodOptional<z.ZodString>;
144
144
  timeout: z.ZodDefault<z.ZodNumber>;
145
145
  }, "strip", z.ZodTypeAny, {
146
- timeout: number;
147
146
  enabled: boolean;
147
+ timeout: number;
148
148
  botToken?: string | undefined;
149
149
  channelId?: string | undefined;
150
150
  }, {
151
- timeout?: number | undefined;
152
151
  enabled?: boolean | undefined;
153
152
  botToken?: string | undefined;
154
153
  channelId?: string | undefined;
154
+ timeout?: number | undefined;
155
155
  }>>;
156
156
  telegram: z.ZodDefault<z.ZodObject<{
157
157
  enabled: z.ZodDefault<z.ZodBoolean>;
@@ -159,56 +159,56 @@ declare const AppConfigSchema: z.ZodObject<{
159
159
  chatId: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
160
160
  timeout: z.ZodDefault<z.ZodNumber>;
161
161
  }, "strip", z.ZodTypeAny, {
162
- timeout: number;
163
162
  enabled: boolean;
163
+ timeout: number;
164
164
  botToken?: string | undefined;
165
165
  chatId?: string | number | undefined;
166
166
  }, {
167
- timeout?: number | undefined;
168
167
  enabled?: boolean | undefined;
169
168
  botToken?: string | undefined;
169
+ timeout?: number | undefined;
170
170
  chatId?: string | number | undefined;
171
171
  }>>;
172
172
  }, "strip", z.ZodTypeAny, {
173
173
  slack: {
174
- timeout: number;
175
174
  enabled: boolean;
176
175
  pollInterval: number;
176
+ timeout: number;
177
177
  botToken?: string | undefined;
178
178
  appToken?: string | undefined;
179
179
  channelId?: string | undefined;
180
180
  };
181
181
  discord: {
182
- timeout: number;
183
182
  enabled: boolean;
183
+ timeout: number;
184
184
  botToken?: string | undefined;
185
185
  channelId?: string | undefined;
186
186
  };
187
187
  telegram: {
188
- timeout: number;
189
188
  enabled: boolean;
189
+ timeout: number;
190
190
  botToken?: string | undefined;
191
191
  chatId?: string | number | undefined;
192
192
  };
193
193
  }, {
194
194
  slack?: {
195
- timeout?: number | undefined;
196
195
  enabled?: boolean | undefined;
197
196
  botToken?: string | undefined;
198
197
  appToken?: string | undefined;
199
198
  channelId?: string | undefined;
200
199
  pollInterval?: number | undefined;
200
+ timeout?: number | undefined;
201
201
  } | undefined;
202
202
  discord?: {
203
- timeout?: number | undefined;
204
203
  enabled?: boolean | undefined;
205
204
  botToken?: string | undefined;
206
205
  channelId?: string | undefined;
206
+ timeout?: number | undefined;
207
207
  } | undefined;
208
208
  telegram?: {
209
- timeout?: number | undefined;
210
209
  enabled?: boolean | undefined;
211
210
  botToken?: string | undefined;
211
+ timeout?: number | undefined;
212
212
  chatId?: string | number | undefined;
213
213
  } | undefined;
214
214
  }>>;
@@ -235,7 +235,7 @@ declare const AppConfigSchema: z.ZodObject<{
235
235
  }, "strip", z.ZodTypeAny, {
236
236
  enabled: boolean;
237
237
  autoConnect: boolean;
238
- defaultShareMode: "read-only" | "read-write";
238
+ defaultShareMode: "read-write" | "read-only";
239
239
  maxReplayBufferKB: number;
240
240
  e2eEncryption: boolean;
241
241
  relayUrl: string;
@@ -247,7 +247,7 @@ declare const AppConfigSchema: z.ZodObject<{
247
247
  authToken?: string | undefined;
248
248
  refreshToken?: string | undefined;
249
249
  autoConnect?: boolean | undefined;
250
- defaultShareMode?: "read-only" | "read-write" | undefined;
250
+ defaultShareMode?: "read-write" | "read-only" | undefined;
251
251
  maxReplayBufferKB?: number | undefined;
252
252
  e2eEncryption?: boolean | undefined;
253
253
  relayUrl?: string | undefined;
@@ -281,11 +281,6 @@ declare const AppConfigSchema: z.ZodObject<{
281
281
  statusCheckInterval: number;
282
282
  detectorInterval: number;
283
283
  };
284
- worktree: {
285
- planning: boolean;
286
- interactivePlanning: boolean;
287
- implementation: boolean;
288
- };
289
284
  ui: {
290
285
  sidebarWidth: number;
291
286
  narrowBreakpoint: number;
@@ -294,22 +289,22 @@ declare const AppConfigSchema: z.ZodObject<{
294
289
  };
295
290
  connectors: {
296
291
  slack: {
297
- timeout: number;
298
292
  enabled: boolean;
299
293
  pollInterval: number;
294
+ timeout: number;
300
295
  botToken?: string | undefined;
301
296
  appToken?: string | undefined;
302
297
  channelId?: string | undefined;
303
298
  };
304
299
  discord: {
305
- timeout: number;
306
300
  enabled: boolean;
301
+ timeout: number;
307
302
  botToken?: string | undefined;
308
303
  channelId?: string | undefined;
309
304
  };
310
305
  telegram: {
311
- timeout: number;
312
306
  enabled: boolean;
307
+ timeout: number;
313
308
  botToken?: string | undefined;
314
309
  chatId?: string | number | undefined;
315
310
  };
@@ -317,7 +312,7 @@ declare const AppConfigSchema: z.ZodObject<{
317
312
  relay: {
318
313
  enabled: boolean;
319
314
  autoConnect: boolean;
320
- defaultShareMode: "read-only" | "read-write";
315
+ defaultShareMode: "read-write" | "read-only";
321
316
  maxReplayBufferKB: number;
322
317
  e2eEncryption: boolean;
323
318
  relayUrl: string;
@@ -325,6 +320,11 @@ declare const AppConfigSchema: z.ZodObject<{
325
320
  authToken?: string | undefined;
326
321
  refreshToken?: string | undefined;
327
322
  };
323
+ worktree: {
324
+ planning: boolean;
325
+ interactivePlanning: boolean;
326
+ implementation: boolean;
327
+ };
328
328
  telemetry: {
329
329
  enabled: boolean;
330
330
  crashReports: boolean;
@@ -334,11 +334,6 @@ declare const AppConfigSchema: z.ZodObject<{
334
334
  statusCheckInterval?: number | undefined;
335
335
  detectorInterval?: number | undefined;
336
336
  } | undefined;
337
- worktree?: {
338
- planning?: boolean | undefined;
339
- interactivePlanning?: boolean | undefined;
340
- implementation?: boolean | undefined;
341
- } | undefined;
342
337
  ui?: {
343
338
  sidebarWidth?: number | undefined;
344
339
  narrowBreakpoint?: number | undefined;
@@ -347,23 +342,23 @@ declare const AppConfigSchema: z.ZodObject<{
347
342
  } | undefined;
348
343
  connectors?: {
349
344
  slack?: {
350
- timeout?: number | undefined;
351
345
  enabled?: boolean | undefined;
352
346
  botToken?: string | undefined;
353
347
  appToken?: string | undefined;
354
348
  channelId?: string | undefined;
355
349
  pollInterval?: number | undefined;
350
+ timeout?: number | undefined;
356
351
  } | undefined;
357
352
  discord?: {
358
- timeout?: number | undefined;
359
353
  enabled?: boolean | undefined;
360
354
  botToken?: string | undefined;
361
355
  channelId?: string | undefined;
356
+ timeout?: number | undefined;
362
357
  } | undefined;
363
358
  telegram?: {
364
- timeout?: number | undefined;
365
359
  enabled?: boolean | undefined;
366
360
  botToken?: string | undefined;
361
+ timeout?: number | undefined;
367
362
  chatId?: string | number | undefined;
368
363
  } | undefined;
369
364
  } | undefined;
@@ -372,12 +367,17 @@ declare const AppConfigSchema: z.ZodObject<{
372
367
  authToken?: string | undefined;
373
368
  refreshToken?: string | undefined;
374
369
  autoConnect?: boolean | undefined;
375
- defaultShareMode?: "read-only" | "read-write" | undefined;
370
+ defaultShareMode?: "read-write" | "read-only" | undefined;
376
371
  maxReplayBufferKB?: number | undefined;
377
372
  e2eEncryption?: boolean | undefined;
378
373
  relayUrl?: string | undefined;
379
374
  authUrl?: string | undefined;
380
375
  } | undefined;
376
+ worktree?: {
377
+ planning?: boolean | undefined;
378
+ interactivePlanning?: boolean | undefined;
379
+ implementation?: boolean | undefined;
380
+ } | undefined;
381
381
  telemetry?: {
382
382
  enabled?: boolean | undefined;
383
383
  crashReports?: boolean | undefined;
@@ -10,9 +10,12 @@ export declare class RelayClient extends EventEmitter {
10
10
  private pingTimer;
11
11
  private pongTimer;
12
12
  private started;
13
+ private lastPingTime;
14
+ private connectSpan;
13
15
  private inputLimiter;
14
16
  private browseLimiter;
15
17
  private createSessionLimiter;
18
+ private cachedDriversResult;
16
19
  constructor();
17
20
  private get e2eEnabled();
18
21
  start(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"relay-client.d.ts","sourceRoot":"","sources":["../../src/relay/relay-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAgI3C,qBAAa,WAAY,SAAQ,YAAY;IAC3C,OAAO,CAAC,EAAE,CAAuC;IACjD,OAAO,CAAC,eAAe,CAAqC;IAC5D,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,UAAU,CAA6B;IAG/C,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,eAAe,CAAS;IAGhC,OAAO,CAAC,SAAS,CAA+C;IAChE,OAAO,CAAC,SAAS,CAA8C;IAE/D,OAAO,CAAC,OAAO,CAAS;IAGxB,OAAO,CAAC,YAAY,CAA2B;IAC/C,OAAO,CAAC,aAAa,CAA2B;IAChD,OAAO,CAAC,oBAAoB,CAA2B;;IAQvD,OAAO,KAAK,UAAU,GAErB;IAMK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAUtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YA4Cb,OAAO;IAmFrB,OAAO,CAAC,aAAa;YAsGP,iBAAiB;IAa/B,OAAO,CAAC,gBAAgB;IAoBxB,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,UAAU;IAUlB,OAAO,CAAC,wBAAwB;IAsBhC,OAAO,CAAC,kBAAkB;IAyB1B,OAAO,CAAC,kBAAkB;YAUZ,iBAAiB;YAajB,eAAe;YA6Cf,iBAAiB;YAoDjB,oBAAoB;YA6EpB,mBAAmB;YAwJnB,8BAA8B;YA4E9B,yBAAyB;IA6HvC,OAAO,CAAC,oBAAoB;YAsBd,qBAAqB;YA+CrB,oBAAoB;IAoClC,OAAO,CAAC,mBAAmB;YAWb,WAAW;YAiCX,mBAAmB;YAyBnB,aAAa;YA+Cb,YAAY;IA2G1B,OAAO,CAAC,cAAc;IAuBtB,OAAO,CAAC,mBAAmB;IAM3B,OAAO,CAAC,eAAe;IA4CvB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,cAAc;IAetB,OAAO,CAAC,iBAAiB;IAkBzB,OAAO,CAAC,mBAAmB;IAWpB,gBAAgB,CAAC,gBAAgB,EAAE,OAAO,CAAC;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,mBAAmB,EAAE,OAAO,CAAC;QAAC,cAAc,EAAE,OAAO,CAAA;KAAE,CAAC,GAAG,IAAI;IAOtI,OAAO,CAAC,IAAI;CAiBb"}
1
+ {"version":3,"file":"relay-client.d.ts","sourceRoot":"","sources":["../../src/relay/relay-client.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AA2K3C,qBAAa,WAAY,SAAQ,YAAY;IAC3C,OAAO,CAAC,EAAE,CAAuC;IACjD,OAAO,CAAC,eAAe,CAAqC;IAC5D,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,UAAU,CAA6B;IAG/C,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,eAAe,CAAS;IAGhC,OAAO,CAAC,SAAS,CAA+C;IAChE,OAAO,CAAC,SAAS,CAA8C;IAE/D,OAAO,CAAC,OAAO,CAAS;IAGxB,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,WAAW,CAAqB;IAGxC,OAAO,CAAC,YAAY,CAA2B;IAC/C,OAAO,CAAC,aAAa,CAA2B;IAChD,OAAO,CAAC,oBAAoB,CAA2B;IAGvD,OAAO,CAAC,mBAAmB,CAAgG;;IAQ3H,OAAO,KAAK,UAAU,GAErB;IAMK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAUtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YA6Cb,OAAO;IA8FrB,OAAO,CAAC,aAAa;YA4GP,iBAAiB;IAa/B,OAAO,CAAC,gBAAgB;IAoBxB,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,UAAU;IAUlB,OAAO,CAAC,wBAAwB;IAsBhC,OAAO,CAAC,kBAAkB;IAyB1B,OAAO,CAAC,kBAAkB;YAYZ,iBAAiB;YAejB,eAAe;YA6Cf,iBAAiB;YAoDjB,oBAAoB;YA6EpB,mBAAmB;YAoKnB,8BAA8B;YA4E9B,yBAAyB;IA0JvC,OAAO,CAAC,oBAAoB;YAsBd,qBAAqB;YA+CrB,oBAAoB;IAoClC,OAAO,CAAC,mBAAmB;YAWb,WAAW;YAwDX,mBAAmB;YAyBnB,aAAa;YA+Cb,YAAY;IA2G1B,OAAO,CAAC,cAAc;IAuBtB,OAAO,CAAC,mBAAmB;IAM3B,OAAO,CAAC,eAAe;IA4CvB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,cAAc;IAetB,OAAO,CAAC,iBAAiB;IAsBzB,OAAO,CAAC,mBAAmB;IAWpB,gBAAgB,CAAC,gBAAgB,EAAE,OAAO,CAAC;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,mBAAmB,EAAE,OAAO,CAAC;QAAC,cAAc,EAAE,OAAO,CAAA;KAAE,CAAC,GAAG,IAAI;IAOtI,OAAO,CAAC,IAAI;CAiBb"}
@@ -1,3 +1,4 @@
1
+ import { stripAnsi } from "../strip-ansi.js";
1
2
  import { EventEmitter } from "node:events";
2
3
  import fs from "node:fs/promises";
3
4
  import os from "node:os";
@@ -15,43 +16,82 @@ import { makeDebugLog } from "../connectors/debug.js";
15
16
  import { RelayToCliMsgSchema } from "./relay-message-schemas.js";
16
17
  import { RateLimiter } from "./rate-limiter.js";
17
18
  import { sessionMetadataStore } from "../agents/session-metadata.js";
19
+ import { trackCliEvent, startTransaction } from "../analytics/index.js";
20
+ import * as Sentry from "@sentry/node";
18
21
  const debugLog = makeDebugLog("relay");
19
22
  // ---------------------------------------------------------------------------
20
23
  // Replay buffer — ring buffer of recent PTY output per session
21
24
  // ---------------------------------------------------------------------------
25
+ class SessionRingBuffer {
26
+ chunks;
27
+ head = 0;
28
+ tail = 0;
29
+ count = 0;
30
+ size = 0;
31
+ capacity;
32
+ maxBytes;
33
+ constructor(maxBytes, initialCapacity = 256) {
34
+ this.maxBytes = maxBytes;
35
+ this.capacity = initialCapacity;
36
+ this.chunks = new Array(initialCapacity).fill(null);
37
+ }
38
+ push(data) {
39
+ while (this.size + data.length > this.maxBytes && this.count > 0) {
40
+ const removed = this.chunks[this.head];
41
+ this.chunks[this.head] = null;
42
+ this.head = (this.head + 1) % this.capacity;
43
+ this.count--;
44
+ this.size -= removed.length;
45
+ }
46
+ if (this.count === this.capacity) {
47
+ this.grow();
48
+ }
49
+ this.chunks[this.tail] = data;
50
+ this.tail = (this.tail + 1) % this.capacity;
51
+ this.count++;
52
+ this.size += data.length;
53
+ }
54
+ toArray() {
55
+ const result = [];
56
+ for (let i = 0; i < this.count; i++) {
57
+ result.push(this.chunks[(this.head + i) % this.capacity]);
58
+ }
59
+ return result;
60
+ }
61
+ grow() {
62
+ const newCapacity = this.capacity * 2;
63
+ const newChunks = new Array(newCapacity).fill(null);
64
+ for (let i = 0; i < this.count; i++) {
65
+ newChunks[i] = this.chunks[(this.head + i) % this.capacity];
66
+ }
67
+ this.chunks = newChunks;
68
+ this.head = 0;
69
+ this.tail = this.count;
70
+ this.capacity = newCapacity;
71
+ }
72
+ }
22
73
  class ReplayBuffer {
23
74
  buffers = new Map();
24
- sizes = new Map();
25
75
  maxBytes;
26
76
  constructor(maxKB) {
27
77
  this.maxBytes = maxKB * 1024;
28
78
  }
29
79
  push(sessionId, data) {
30
- let chunks = this.buffers.get(sessionId);
31
- let size = this.sizes.get(sessionId) ?? 0;
32
- if (!chunks) {
33
- chunks = [];
34
- this.buffers.set(sessionId, chunks);
35
- }
36
- chunks.push(data);
37
- size += data.length;
38
- // Evict oldest chunks when over budget
39
- while (size > this.maxBytes && chunks.length > 1) {
40
- const removed = chunks.shift();
41
- size -= removed.length;
42
- }
43
- this.sizes.set(sessionId, size);
80
+ let ring = this.buffers.get(sessionId);
81
+ if (!ring) {
82
+ ring = new SessionRingBuffer(this.maxBytes);
83
+ this.buffers.set(sessionId, ring);
84
+ }
85
+ ring.push(data);
44
86
  }
45
87
  get(sessionId) {
46
- return this.buffers.get(sessionId) ?? [];
88
+ return this.buffers.get(sessionId)?.toArray() ?? [];
47
89
  }
48
90
  remove(sessionId) {
49
91
  this.buffers.delete(sessionId);
50
- this.sizes.delete(sessionId);
51
92
  }
52
93
  clear() {
53
94
  this.buffers.clear();
54
- this.sizes.clear();
55
95
  }
56
96
  }
57
97
  // ---------------------------------------------------------------------------
@@ -70,10 +110,15 @@ export class RelayClient extends EventEmitter {
70
110
  pingTimer = null;
71
111
  pongTimer = null;
72
112
  started = false;
113
+ // Analytics
114
+ lastPingTime = 0;
115
+ connectSpan = null;
73
116
  // Rate limiters
74
117
  inputLimiter = new RateLimiter(30, 30); // 30 inputs/sec per key
75
118
  browseLimiter = new RateLimiter(10, 10); // 10 browse requests/sec
76
119
  createSessionLimiter = new RateLimiter(1, 0.2); // 1 per 5 seconds
120
+ // Cached driver list (avoids re-checking installed status on every poll)
121
+ cachedDriversResult = null;
77
122
  constructor() {
78
123
  super();
79
124
  const maxKB = settingsStore.getState().config.relay.maxReplayBufferKB;
@@ -116,6 +161,7 @@ export class RelayClient extends EventEmitter {
116
161
  }
117
162
  this.trackedSessions.clear();
118
163
  this.replayBuffer.clear();
164
+ this.cachedDriversResult = null;
119
165
  clearViewerCounts();
120
166
  this.inputLimiter.clear();
121
167
  this.browseLimiter.clear();
@@ -137,6 +183,7 @@ export class RelayClient extends EventEmitter {
137
183
  return;
138
184
  }
139
185
  setRelayConnectionStatus("connecting");
186
+ this.connectSpan = startTransaction("relay.connect", "websocket.connect");
140
187
  try {
141
188
  // Clean up previous WebSocket if it exists (e.g. on reconnect)
142
189
  if (this.ws) {
@@ -152,9 +199,14 @@ export class RelayClient extends EventEmitter {
152
199
  this.ws = new WebSocket(relayUrl);
153
200
  this.ws.on("open", () => {
154
201
  debugLog("relay: connected to relay server");
202
+ this.connectSpan?.end();
203
+ this.connectSpan = null;
155
204
  setRelayConnectionStatus("authenticating");
156
205
  this.send({ type: "cli_auth", token: token });
157
206
  this.startHeartbeat();
207
+ trackCliEvent("cli_relay_connected", {
208
+ attempt: this.reconnectAttempt,
209
+ });
158
210
  });
159
211
  this.ws.on("message", (raw) => {
160
212
  try {
@@ -186,6 +238,11 @@ export class RelayClient extends EventEmitter {
186
238
  });
187
239
  this.ws.on("error", (err) => {
188
240
  debugLog(`relay: WebSocket error: ${err.message}`);
241
+ if (this.connectSpan) {
242
+ this.connectSpan.setStatus({ code: 2, message: err.message });
243
+ this.connectSpan.end();
244
+ this.connectSpan = null;
245
+ }
189
246
  setRelayConnectionStatus("error", err.message);
190
247
  });
191
248
  }
@@ -243,6 +300,12 @@ export class RelayClient extends EventEmitter {
243
300
  this.handleBrowseFiles(msg.requestId, msg.viewerId, msg.path);
244
301
  break;
245
302
  case "relay_ping":
303
+ if (this.lastPingTime > 0) {
304
+ const latencyMs = Date.now() - this.lastPingTime;
305
+ trackCliEvent("cli_relay_latency", { latencyMs });
306
+ Sentry.setMeasurement("relay.ping_latency", latencyMs, "millisecond");
307
+ }
308
+ this.lastPingTime = Date.now();
246
309
  this.send({ type: "cli_pong" });
247
310
  this.resetPongTimer();
248
311
  break;
@@ -376,15 +439,19 @@ export class RelayClient extends EventEmitter {
376
439
  worktree: msg.worktreeDefaults,
377
440
  });
378
441
  }
442
+ // TODO: Web app polls this every 5s (code-relay-server epics.ts refreshSessionsPollingEpic).
443
+ // Consider increasing interval to 30-60s or switching to push-based updates.
379
444
  async handleListDrivers(viewerId) {
380
- const allDrivers = getAllDrivers();
381
- const drivers = await Promise.all(allDrivers.map(async (d) => ({
382
- name: d.name,
383
- displayName: d.displayName,
384
- installed: await d.checkInstalled(),
385
- models: d.listModels(),
386
- })));
387
- this.send({ type: "cli_drivers_result", viewerId, drivers });
445
+ if (!this.cachedDriversResult) {
446
+ const allDrivers = getAllDrivers();
447
+ this.cachedDriversResult = await Promise.all(allDrivers.map(async (d) => ({
448
+ name: d.name,
449
+ displayName: d.displayName,
450
+ installed: await d.checkInstalled(),
451
+ models: d.listModels(),
452
+ })));
453
+ }
454
+ this.send({ type: "cli_drivers_result", viewerId, drivers: this.cachedDriversResult });
388
455
  }
389
456
  async handleBrowseDir(requestId, viewerId, dirPath) {
390
457
  if (!this.browseLimiter.allow(viewerId)) {
@@ -594,8 +661,8 @@ export class RelayClient extends EventEmitter {
594
661
  const driverName = msg.driverName || DEFAULT_DRIVER_NAME;
595
662
  debugLog(`relay: start planning ticket=${msg.ticketId} iteration=${iteration} cwd=${effectiveCwd} driver=${driverName}${branchName ? ` branch=${branchName}` : ""}`);
596
663
  try {
597
- // Non-interactive: reduced scrollback to save memory
598
- const session = appStore.getState().createSession(driverName, effectiveCwd, undefined, undefined, 500);
664
+ // Non-interactive: minimal scrollback to save memory (raw output is captured separately)
665
+ const session = appStore.getState().createSession(driverName, effectiveCwd, undefined, undefined, 1);
599
666
  if (!session) {
600
667
  debugLog("relay: failed to create planning session");
601
668
  return;
@@ -632,11 +699,22 @@ export class RelayClient extends EventEmitter {
632
699
  const command = `${driverName} -p "<planning prompt>"`;
633
700
  appStore.getState().setSessionMeta(session.id, { interactive: false, command });
634
701
  this.registerSession(session.id);
702
+ // Safety timeout: force-close planning sessions that run too long
703
+ const PLANNING_TIMEOUT_MS = 10 * 60 * 1000;
704
+ const safetyTimer = setTimeout(() => {
705
+ rawUnsub();
706
+ rawOutput = "";
707
+ if (getSessionInstance(session.id)) {
708
+ debugLog(`relay: planning session ${session.id} timed out after 10min, force closing`);
709
+ appStore.getState().closeSession(session.id);
710
+ }
711
+ }, PLANNING_TIMEOUT_MS);
635
712
  // In print mode, claude -p exits when done — listen for exit
636
713
  const onExit = () => {
714
+ clearTimeout(safetyTimer);
637
715
  rawUnsub();
638
716
  // Strip ANSI escape sequences from raw PTY output
639
- const plainText = rawOutput.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").trim();
717
+ const plainText = stripAnsi(rawOutput).trim();
640
718
  ticketTracker.updateStatus(msg.ticketId, "plan_ready");
641
719
  this.send({
642
720
  type: "cli_plan_ready",
@@ -803,9 +881,13 @@ export class RelayClient extends EventEmitter {
803
881
  });
804
882
  this.registerSession(session.id);
805
883
  // Monitor session status for implementation completion
884
+ const cleanup = () => {
885
+ session.removeListener("status", onStatus);
886
+ session.removeListener("exit", onExitFallback);
887
+ };
806
888
  const onStatus = (status) => {
807
889
  if (status === "waiting_input" || status === "exited") {
808
- session.removeListener("status", onStatus);
890
+ cleanup();
809
891
  const plainText = session.getPlainText(500).join("\n");
810
892
  ticketTracker.updateStatus(msg.ticketId, "done");
811
893
  this.send({
@@ -824,7 +906,26 @@ export class RelayClient extends EventEmitter {
824
906
  });
825
907
  }
826
908
  };
909
+ const onExitFallback = () => {
910
+ cleanup();
911
+ ticketTracker.updateStatus(msg.ticketId, "done");
912
+ this.send({
913
+ type: "cli_implementation_done",
914
+ ticketId: msg.ticketId,
915
+ sessionId: session.id,
916
+ summary: "(session exited)",
917
+ gitContext: { branch: branchName ?? undefined },
918
+ });
919
+ this.send({
920
+ type: "cli_ticket_status",
921
+ ticketId: msg.ticketId,
922
+ sessionId: session.id,
923
+ status: "done",
924
+ branchName: branchName ?? undefined,
925
+ });
926
+ };
827
927
  session.on("status", onStatus);
928
+ session.once("exit", onExitFallback);
828
929
  }
829
930
  catch (err) {
830
931
  const message = err instanceof Error ? err.message : String(err);
@@ -918,6 +1019,7 @@ export class RelayClient extends EventEmitter {
918
1019
  // Encrypted PTY data sending
919
1020
  // -------------------------------------------------------------------------
920
1021
  async sendPtyData(tracked, rawData) {
1022
+ const encryptStart = performance.now();
921
1023
  if (tracked.sck) {
922
1024
  // E2E enabled — encrypt with SCK
923
1025
  try {
@@ -930,6 +1032,16 @@ export class RelayClient extends EventEmitter {
930
1032
  data: encrypted,
931
1033
  seq,
932
1034
  });
1035
+ // Sample 1 in 100 to avoid event flood
1036
+ if (Math.random() < 0.01) {
1037
+ const encryptMs = performance.now() - encryptStart;
1038
+ trackCliEvent("cli_pty_data_sent", {
1039
+ sessionId: tracked.sessionId,
1040
+ dataLenBytes: rawData.length,
1041
+ encrypted: true,
1042
+ encryptMs: Math.round(encryptMs * 100) / 100,
1043
+ });
1044
+ }
933
1045
  }
934
1046
  catch (err) {
935
1047
  const message = err instanceof Error ? err.message : String(err);
@@ -950,6 +1062,15 @@ export class RelayClient extends EventEmitter {
950
1062
  sessionId: tracked.sessionId,
951
1063
  data: base64,
952
1064
  });
1065
+ // Sample 1 in 100 to avoid event flood
1066
+ if (Math.random() < 0.01) {
1067
+ trackCliEvent("cli_pty_data_sent", {
1068
+ sessionId: tracked.sessionId,
1069
+ dataLenBytes: rawData.length,
1070
+ encrypted: false,
1071
+ encryptMs: 0,
1072
+ });
1073
+ }
953
1074
  }
954
1075
  }
955
1076
  async sendEncryptedReplay(tracked) {
@@ -1222,6 +1343,10 @@ export class RelayClient extends EventEmitter {
1222
1343
  this.reconnectAttempt++;
1223
1344
  debugLog(`relay: reconnecting in ${delay}ms (attempt ${this.reconnectAttempt})`);
1224
1345
  setRelayConnectionStatus("connecting", undefined, this.reconnectAttempt);
1346
+ trackCliEvent("cli_relay_reconnect", {
1347
+ attempt: this.reconnectAttempt,
1348
+ delayMs: delay,
1349
+ });
1225
1350
  this.reconnectTimer = setTimeout(() => {
1226
1351
  this.reconnectTimer = null;
1227
1352
  if (this.shouldReconnect) {