tandem-editor 0.11.2 → 0.13.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 (40) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +3 -3
  3. package/CHANGELOG.md +201 -72
  4. package/README.md +141 -238
  5. package/dist/channel/index.js +211 -81
  6. package/dist/channel/index.js.map +1 -1
  7. package/dist/cli/index.js +749 -170
  8. package/dist/cli/index.js.map +1 -1
  9. package/dist/client/assets/CoworkSettings-BOYbyKul.js +3 -0
  10. package/dist/client/assets/event-CNdo2oXa.js +1 -0
  11. package/dist/client/assets/index-D8uS4cj7.css +1 -0
  12. package/dist/client/assets/index-Dm_QtxGQ.js +1 -0
  13. package/dist/client/assets/index-g-KwmRn9.js +271 -0
  14. package/dist/client/assets/webview-KiZyy_pC.js +1 -0
  15. package/dist/client/assets/window-DePn7tLG.js +1 -0
  16. package/dist/client/fonts/OFL-Hanuman.txt +93 -0
  17. package/dist/client/fonts/OFL-InterTight.txt +93 -0
  18. package/dist/client/fonts/OFL-JetBrainsMono.txt +93 -0
  19. package/dist/client/fonts/OFL-SNPro.txt +93 -0
  20. package/dist/client/fonts/OFL-Sono.txt +93 -0
  21. package/dist/client/fonts/OFL-SourceSerif4.txt +93 -0
  22. package/dist/client/fonts/hanuman-latin.woff2 +0 -0
  23. package/dist/client/fonts/jetbrains-mono-latin.woff2 +0 -0
  24. package/dist/client/fonts/sn-pro-latin.woff2 +0 -0
  25. package/dist/client/fonts/sono-latin.woff2 +0 -0
  26. package/dist/client/fonts/source-serif-4-latin.woff2 +0 -0
  27. package/dist/client/index.html +206 -17
  28. package/dist/client/logo.png +0 -0
  29. package/dist/monitor/index.js +241 -160
  30. package/dist/monitor/index.js.map +1 -1
  31. package/dist/server/index.js +22828 -19659
  32. package/dist/server/index.js.map +1 -1
  33. package/package.json +12 -4
  34. package/sample/welcome.md +6 -6
  35. package/skills/tandem/SKILL.md +15 -0
  36. package/dist/client/assets/CoworkSettings-DK3jjdwK.js +0 -3
  37. package/dist/client/assets/index-CfT503n4.js +0 -297
  38. package/dist/client/assets/index-DeJe09pn.css +0 -1
  39. package/dist/client/assets/webview-Ben21ZLJ.js +0 -1
  40. package/dist/client/assets/window-BxBvHL5k.js +0 -1
@@ -14,12 +14,16 @@ var DEFAULT_MCP_PORT = 3479;
14
14
  var TANDEM_REPO_URL = "https://github.com/bloknayrb/tandem";
15
15
  var TANDEM_ISSUES_NEW_URL = `${TANDEM_REPO_URL}/issues/new`;
16
16
  var MAX_FILE_SIZE = 50 * 1024 * 1024;
17
- var MAX_WS_PAYLOAD = 10 * 1024 * 1024;
18
- var IDLE_TIMEOUT = 30 * 60 * 1e3;
19
17
  var SESSION_MAX_AGE = 30 * 24 * 60 * 60 * 1e3;
20
18
  var TANDEM_MODE_DEFAULT = "tandem";
21
19
  var CHANNEL_MAX_RETRIES = 5;
22
20
  var CHANNEL_RETRY_DELAY_MS = 2e3;
21
+ var CHANNEL_CONNECT_FETCH_TIMEOUT_MS = 1e4;
22
+ var CHANNEL_SSE_INACTIVITY_TIMEOUT_MS = 6e4;
23
+ var CHANNEL_MODE_FETCH_TIMEOUT_MS = 2e3;
24
+ var CHANNEL_AWARENESS_FETCH_TIMEOUT_MS = 5e3;
25
+ var CHANNEL_ERROR_REPORT_TIMEOUT_MS = 3e3;
26
+ var CHANNEL_MAX_SSE_BUFFER_BYTES = 1e6;
23
27
 
24
28
  // src/shared/cli-runtime.ts
25
29
  function resolveTandemUrl(override) {
@@ -34,7 +38,7 @@ function resolveTandemUrlCandidate(override) {
34
38
  for (const url of candidates) {
35
39
  if (url !== void 0 && url.trim() !== "") return url.trim();
36
40
  }
37
- return `http://localhost:${DEFAULT_MCP_PORT}`;
41
+ return `http://127.0.0.1:${DEFAULT_MCP_PORT}`;
38
42
  }
39
43
  function resolveAuthTokenCandidate(override) {
40
44
  const candidates = [
@@ -139,6 +143,12 @@ function formatEventContent(event) {
139
143
  }
140
144
  }
141
145
 
146
+ // src/shared/api-paths.ts
147
+ var API_EVENTS = "/api/events";
148
+ var API_CHANNEL_AWARENESS = "/api/channel-awareness";
149
+ var API_CHANNEL_ERROR = "/api/channel-error";
150
+ var API_MODE = "/api/mode";
151
+
142
152
  // src/shared/fetch-with-timeout.ts
143
153
  async function fetchWithTimeout(url, init, timeoutMs) {
144
154
  const timeoutSignal = AbortSignal.timeout(timeoutMs);
@@ -4211,107 +4221,104 @@ var ToolErrorCodeSchema = external_exports.enum([
4211
4221
  "FILE_NOT_FOUND",
4212
4222
  "NO_DOCUMENT",
4213
4223
  "INVALID_RANGE",
4224
+ "INVALID_ARGUMENT",
4225
+ "NOT_FOUND",
4226
+ "ANNOTATION_RESOLVED",
4214
4227
  "FORMAT_ERROR",
4215
4228
  "PERMISSION_DENIED"
4216
4229
  ]);
4230
+ var ChannelErrorCodeSchema = external_exports.enum(["CHANNEL_CONNECT_FAILED", "MONITOR_CONNECT_FAILED"]);
4231
+ var MONITOR_CONNECT_FAILED = "MONITOR_CONNECT_FAILED";
4217
4232
 
4218
- // src/monitor/index.ts
4219
- var IS_VITEST = process.env.VITEST === "true";
4220
- if (!IS_VITEST) {
4221
- console.log = console.error;
4222
- console.warn = console.error;
4223
- console.info = console.error;
4224
- }
4225
- var TANDEM_URL = resolveTandemUrl();
4233
+ // src/shared/sse-consumer.ts
4226
4234
  var AWARENESS_DEBOUNCE_MS = 500;
4227
4235
  var AWARENESS_CLEAR_MS = 3e3;
4228
4236
  var MODE_CACHE_TTL_MS = 2e3;
4229
- var MAX_SSE_BUFFER_BYTES = 1e6;
4230
- var CONNECT_FETCH_TIMEOUT_MS = 1e4;
4231
- var SSE_INACTIVITY_TIMEOUT_MS = 6e4;
4232
- var MODE_FETCH_TIMEOUT_MS = 2e3;
4233
- var AWARENESS_FETCH_TIMEOUT_MS = 5e3;
4234
- var ERROR_REPORT_TIMEOUT_MS = 3e3;
4235
4237
  var STABLE_CONNECTION_MS = 6e4;
4236
- var CHANNEL_RETRY_MAX_DELAY_MS = 3e4;
4237
- async function main() {
4238
- installShutdownHandlers();
4239
- installStdoutErrorHandler();
4240
- console.error(`[Monitor] Tandem monitor starting (server: ${TANDEM_URL})`);
4241
- await getCachedMode().catch(() => {
4238
+ var RETRY_MAX_DELAY_MS = 3e4;
4239
+ var shutdownTimers = { awarenessTimer: null, clearAwarenessTimer: null, lastDocumentId: null };
4240
+ var outstandingAwareness = /* @__PURE__ */ new Set();
4241
+ function trackAwareness(p) {
4242
+ outstandingAwareness.add(p);
4243
+ p.finally(() => outstandingAwareness.delete(p));
4244
+ }
4245
+ var cachedMode = TANDEM_MODE_DEFAULT;
4246
+ var cachedModeAt = 0;
4247
+ var cachedModeFailedAt = 0;
4248
+ var _modeRefreshInFlight = null;
4249
+ async function runEventConsumer(opts) {
4250
+ await getCachedMode(opts.tandemUrl, opts.logPrefix).catch(() => {
4242
4251
  });
4243
4252
  let retries = 0;
4244
4253
  let lastEventId;
4245
4254
  while (retries < CHANNEL_MAX_RETRIES) {
4246
4255
  try {
4247
- await connectAndStream(
4248
- lastEventId,
4249
- (id) => {
4256
+ await connectAndStreamOnce(opts, lastEventId, {
4257
+ onEventId: (id) => {
4250
4258
  lastEventId = id;
4251
4259
  },
4252
- () => {
4260
+ onStable: () => {
4253
4261
  retries = 0;
4254
4262
  }
4255
- );
4263
+ });
4256
4264
  } catch (err) {
4257
4265
  retries++;
4258
4266
  console.error(
4259
- `[Monitor] SSE connection failed (${retries}/${CHANNEL_MAX_RETRIES}):`,
4267
+ `${opts.logPrefix} SSE connection failed (${retries}/${CHANNEL_MAX_RETRIES}):`,
4260
4268
  err instanceof Error ? err.message : err
4261
4269
  );
4262
4270
  if (retries >= CHANNEL_MAX_RETRIES) {
4263
- console.error("[Monitor] SSE connection exhausted, exiting");
4271
+ console.error(`${opts.logPrefix} SSE connection exhausted, reporting error and exiting`);
4264
4272
  try {
4265
4273
  await fetchWithTimeout(
4266
- `${TANDEM_URL}/api/channel-error`,
4274
+ `${opts.tandemUrl}${API_CHANNEL_ERROR}`,
4267
4275
  {
4268
4276
  method: "POST",
4269
4277
  headers: { "Content-Type": "application/json" },
4270
4278
  body: JSON.stringify({
4271
- error: "MONITOR_CONNECT_FAILED",
4272
- message: `Monitor lost connection after ${CHANNEL_MAX_RETRIES} retries.`
4279
+ error: opts.errorCode,
4280
+ message: `${opts.logPrefix} lost connection after ${CHANNEL_MAX_RETRIES} retries.`
4273
4281
  })
4274
4282
  },
4275
- ERROR_REPORT_TIMEOUT_MS
4283
+ CHANNEL_ERROR_REPORT_TIMEOUT_MS
4276
4284
  );
4277
4285
  } catch (reportErr) {
4278
4286
  console.error(
4279
- "[Monitor] Could not report failure to server:",
4280
- reportErr instanceof Error ? reportErr.message : reportErr
4287
+ `${opts.logPrefix} Could not report failure to server:`,
4288
+ describeFetchError(reportErr, API_CHANNEL_ERROR, CHANNEL_ERROR_REPORT_TIMEOUT_MS)
4281
4289
  );
4282
4290
  }
4283
- process.stdout.write(
4284
- "Tandem monitor disconnected \u2014 restart Tandem to restore real-time events\n"
4285
- );
4291
+ opts.onExhaustion?.();
4286
4292
  process.exit(1);
4287
4293
  }
4288
- const delay = Math.min(
4289
- CHANNEL_RETRY_DELAY_MS * 2 ** (retries - 1),
4290
- CHANNEL_RETRY_MAX_DELAY_MS
4291
- );
4294
+ const delay = Math.min(CHANNEL_RETRY_DELAY_MS * 2 ** (retries - 1), RETRY_MAX_DELAY_MS);
4292
4295
  console.error(
4293
- `[Monitor] Retrying in ${delay}ms (attempt ${retries}/${CHANNEL_MAX_RETRIES})...`
4296
+ `${opts.logPrefix} Retrying in ${delay}ms (attempt ${retries}/${CHANNEL_MAX_RETRIES})...`
4294
4297
  );
4295
4298
  await new Promise((r) => setTimeout(r, delay));
4296
4299
  }
4297
4300
  }
4298
4301
  console.error(
4299
- `[Monitor] Retry loop exited unexpectedly (retries=${retries}/${CHANNEL_MAX_RETRIES})`
4302
+ `${opts.logPrefix} Retry loop exited unexpectedly (retries=${retries}/${CHANNEL_MAX_RETRIES})`
4300
4303
  );
4301
4304
  process.exit(1);
4302
4305
  }
4303
- async function connectAndStream(lastEventId, onEventId, onStable = () => {
4304
- }) {
4306
+ async function connectAndStreamOnce(opts, lastEventId, cb) {
4307
+ const onStable = cb.onStable ?? (() => {
4308
+ });
4305
4309
  const headers = { Accept: "text/event-stream" };
4306
4310
  if (lastEventId) headers["Last-Event-ID"] = lastEventId;
4307
4311
  const connectCtrl = new AbortController();
4308
4312
  const connectTimer = setTimeout(
4309
4313
  () => connectCtrl.abort(new Error("handshake timeout")),
4310
- CONNECT_FETCH_TIMEOUT_MS
4314
+ CHANNEL_CONNECT_FETCH_TIMEOUT_MS
4311
4315
  );
4312
4316
  let res;
4313
4317
  try {
4314
- res = await authFetch(`${TANDEM_URL}/api/events`, { headers, signal: connectCtrl.signal });
4318
+ res = await authFetch(`${opts.tandemUrl}${API_EVENTS}`, {
4319
+ headers,
4320
+ signal: connectCtrl.signal
4321
+ });
4315
4322
  } finally {
4316
4323
  clearTimeout(connectTimer);
4317
4324
  }
@@ -4324,16 +4331,16 @@ async function connectAndStream(lastEventId, onEventId, onStable = () => {
4324
4331
  let lastActivityAt = Date.now();
4325
4332
  let inactivityTimedOut = false;
4326
4333
  const watchdog = setInterval(() => {
4327
- if (Date.now() - lastActivityAt > SSE_INACTIVITY_TIMEOUT_MS) {
4334
+ if (Date.now() - lastActivityAt > CHANNEL_SSE_INACTIVITY_TIMEOUT_MS) {
4328
4335
  inactivityTimedOut = true;
4329
4336
  reader.cancel(new Error("SSE inactivity timeout")).catch(() => {
4330
4337
  });
4331
4338
  }
4332
- }, SSE_INACTIVITY_TIMEOUT_MS / 4);
4339
+ }, CHANNEL_SSE_INACTIVITY_TIMEOUT_MS / 4);
4333
4340
  let pendingAwareness = null;
4334
- function clearAwareness(documentId) {
4341
+ function clearAwarenessNow(documentId) {
4335
4342
  const p = fetchWithTimeout(
4336
- `${TANDEM_URL}/api/channel-awareness`,
4343
+ `${opts.tandemUrl}${API_CHANNEL_AWARENESS}`,
4337
4344
  {
4338
4345
  method: "POST",
4339
4346
  headers: { "Content-Type": "application/json" },
@@ -4343,11 +4350,15 @@ async function connectAndStream(lastEventId, onEventId, onStable = () => {
4343
4350
  active: false
4344
4351
  })
4345
4352
  },
4346
- AWARENESS_FETCH_TIMEOUT_MS
4353
+ CHANNEL_AWARENESS_FETCH_TIMEOUT_MS
4347
4354
  ).catch((err) => {
4348
4355
  console.error(
4349
- "[Monitor] Awareness clear failed:",
4350
- describeFetchError(err, "/api/channel-awareness clear", AWARENESS_FETCH_TIMEOUT_MS)
4356
+ `${opts.logPrefix} Awareness clear failed:`,
4357
+ describeFetchError(
4358
+ err,
4359
+ `${API_CHANNEL_AWARENESS} clear`,
4360
+ CHANNEL_AWARENESS_FETCH_TIMEOUT_MS
4361
+ )
4351
4362
  );
4352
4363
  });
4353
4364
  trackAwareness(p);
@@ -4358,7 +4369,7 @@ async function connectAndStream(lastEventId, onEventId, onStable = () => {
4358
4369
  pendingAwareness = null;
4359
4370
  if (event.documentId) shutdownTimers.lastDocumentId = event.documentId;
4360
4371
  const p = fetchWithTimeout(
4361
- `${TANDEM_URL}/api/channel-awareness`,
4372
+ `${opts.tandemUrl}${API_CHANNEL_AWARENESS}`,
4362
4373
  {
4363
4374
  method: "POST",
4364
4375
  headers: { "Content-Type": "application/json" },
@@ -4368,17 +4379,21 @@ async function connectAndStream(lastEventId, onEventId, onStable = () => {
4368
4379
  active: true
4369
4380
  })
4370
4381
  },
4371
- AWARENESS_FETCH_TIMEOUT_MS
4382
+ CHANNEL_AWARENESS_FETCH_TIMEOUT_MS
4372
4383
  ).catch((err) => {
4373
4384
  console.error(
4374
- "[Monitor] Awareness update failed:",
4375
- describeFetchError(err, "/api/channel-awareness update", AWARENESS_FETCH_TIMEOUT_MS)
4385
+ `${opts.logPrefix} Awareness update failed:`,
4386
+ describeFetchError(
4387
+ err,
4388
+ `${API_CHANNEL_AWARENESS} update`,
4389
+ CHANNEL_AWARENESS_FETCH_TIMEOUT_MS
4390
+ )
4376
4391
  );
4377
4392
  });
4378
4393
  trackAwareness(p);
4379
4394
  if (shutdownTimers.clearAwarenessTimer) clearTimeout(shutdownTimers.clearAwarenessTimer);
4380
4395
  shutdownTimers.clearAwarenessTimer = setTimeout(
4381
- () => clearAwareness(event.documentId),
4396
+ () => clearAwarenessNow(event.documentId),
4382
4397
  AWARENESS_CLEAR_MS
4383
4398
  );
4384
4399
  }
@@ -4396,9 +4411,9 @@ async function connectAndStream(lastEventId, onEventId, onStable = () => {
4396
4411
  }
4397
4412
  lastActivityAt = Date.now();
4398
4413
  buffer += decoder.decode(value, { stream: true });
4399
- if (buffer.length > MAX_SSE_BUFFER_BYTES) {
4414
+ if (buffer.length > CHANNEL_MAX_SSE_BUFFER_BYTES) {
4400
4415
  throw new Error(
4401
- `SSE buffer exceeded ${MAX_SSE_BUFFER_BYTES} bytes without a frame boundary`
4416
+ `SSE buffer exceeded ${CHANNEL_MAX_SSE_BUFFER_BYTES} bytes without a frame boundary`
4402
4417
  );
4403
4418
  }
4404
4419
  let boundary;
@@ -4418,29 +4433,35 @@ async function connectAndStream(lastEventId, onEventId, onStable = () => {
4418
4433
  raw = JSON.parse(data);
4419
4434
  } catch (err) {
4420
4435
  console.error(
4421
- `[Monitor] SSE JSON parse failed (eventId=${eventId ?? "none"}, len=${data.length}): ${err instanceof Error ? err.message : err}. Tail:`,
4436
+ `${opts.logPrefix} SSE JSON parse failed (eventId=${eventId ?? "none"}, len=${data.length}): ${err instanceof Error ? err.message : err}. Tail:`,
4422
4437
  data.slice(Math.max(0, data.length - 200))
4423
4438
  );
4439
+ if (eventId) cb.onEventId(eventId);
4424
4440
  continue;
4425
4441
  }
4426
4442
  const event = parseTandemEvent(raw);
4427
4443
  if (!event) {
4428
4444
  console.error(
4429
- `[Monitor] SSE event failed validation (eventId=${eventId ?? "none"}): shape mismatch`
4445
+ `${opts.logPrefix} SSE event failed validation (eventId=${eventId ?? "none"}): shape mismatch`
4430
4446
  );
4447
+ if (eventId) cb.onEventId(eventId);
4431
4448
  continue;
4432
4449
  }
4433
4450
  if (event.type !== "chat:message") {
4434
- refreshMode();
4451
+ refreshMode(opts.tandemUrl, opts.logPrefix);
4435
4452
  if (getModeSync() === "solo") {
4436
- console.error(`[Monitor] Solo mode: suppressed ${event.type} event`);
4437
- if (eventId) onEventId(eventId);
4453
+ console.error(`${opts.logPrefix} Solo mode: suppressed ${event.type} event`);
4454
+ if (eventId) cb.onEventId(eventId);
4438
4455
  continue;
4439
4456
  }
4440
4457
  }
4441
- const content = formatEventContent(event).replace(/\n/g, " ");
4442
- process.stdout.write(content + "\n");
4443
- if (eventId) onEventId(eventId);
4458
+ try {
4459
+ await opts.onEvent(event, eventId);
4460
+ } catch (err) {
4461
+ console.error(`${opts.logPrefix} onEvent failed (transport broken?):`, err);
4462
+ throw err;
4463
+ }
4464
+ if (eventId) cb.onEventId(eventId);
4444
4465
  scheduleAwareness(event);
4445
4466
  }
4446
4467
  }
@@ -4454,14 +4475,73 @@ async function connectAndStream(lastEventId, onEventId, onStable = () => {
4454
4475
  pendingAwareness = null;
4455
4476
  }
4456
4477
  }
4457
- var shutdownTimers = { awarenessTimer: null, clearAwarenessTimer: null, lastDocumentId: null };
4458
- var _modeRefreshInFlight = null;
4459
- var outstandingAwareness = /* @__PURE__ */ new Set();
4460
- function trackAwareness(p) {
4461
- outstandingAwareness.add(p);
4462
- p.finally(() => outstandingAwareness.delete(p));
4478
+ async function fetchMode(tandemUrl) {
4479
+ try {
4480
+ const res = await fetchWithTimeout(
4481
+ `${tandemUrl}${API_MODE}`,
4482
+ {},
4483
+ CHANNEL_MODE_FETCH_TIMEOUT_MS
4484
+ );
4485
+ if (!res.ok) return { ok: false, reason: `status ${res.status}` };
4486
+ const body = await res.json();
4487
+ const parsed = TandemModeSchema.safeParse(body.mode);
4488
+ if (!parsed.success) return { ok: false, reason: `invalid mode ${JSON.stringify(body.mode)}` };
4489
+ return { ok: true, mode: parsed.data };
4490
+ } catch (err) {
4491
+ return { ok: false, reason: describeFetchError(err, API_MODE, CHANNEL_MODE_FETCH_TIMEOUT_MS) };
4492
+ }
4493
+ }
4494
+ async function getCachedMode(tandemUrl, logPrefix = "[Tandem]") {
4495
+ const now = Date.now();
4496
+ if (now - cachedModeAt < MODE_CACHE_TTL_MS && cachedModeAt !== 0) return cachedMode;
4497
+ const result = await fetchMode(tandemUrl);
4498
+ if (!result.ok) {
4499
+ if (cachedModeAt !== 0) {
4500
+ console.error(
4501
+ `${logPrefix} Mode check failed (${result.reason}), preserving last known mode '${cachedMode}'`
4502
+ );
4503
+ return cachedMode;
4504
+ }
4505
+ console.error(
4506
+ `${logPrefix} Mode check failed (${result.reason}), no prior mode \u2014 using cold-start default '${TANDEM_MODE_DEFAULT}'`
4507
+ );
4508
+ cachedMode = TANDEM_MODE_DEFAULT;
4509
+ return TANDEM_MODE_DEFAULT;
4510
+ }
4511
+ cachedMode = result.mode;
4512
+ cachedModeAt = now;
4513
+ return cachedMode;
4463
4514
  }
4464
- async function finalClearAwareness() {
4515
+ function getModeSync() {
4516
+ return cachedMode;
4517
+ }
4518
+ function refreshMode(tandemUrl, logPrefix) {
4519
+ if (_modeRefreshInFlight) return;
4520
+ const now = Date.now();
4521
+ if (now - cachedModeAt < MODE_CACHE_TTL_MS) return;
4522
+ if (now - cachedModeFailedAt < MODE_CACHE_TTL_MS) return;
4523
+ _modeRefreshInFlight = (async () => {
4524
+ try {
4525
+ const result = await fetchMode(tandemUrl);
4526
+ if (result.ok) {
4527
+ cachedMode = result.mode;
4528
+ cachedModeAt = Date.now();
4529
+ cachedModeFailedAt = 0;
4530
+ } else {
4531
+ cachedModeFailedAt = Date.now();
4532
+ console.error(
4533
+ `${logPrefix} Background mode refresh failed (${result.reason}), keeping cached`
4534
+ );
4535
+ }
4536
+ } finally {
4537
+ _modeRefreshInFlight = null;
4538
+ }
4539
+ })().catch((err) => {
4540
+ console.error(`${logPrefix} refreshMode unexpected error:`, err);
4541
+ cachedModeFailedAt = Date.now();
4542
+ });
4543
+ }
4544
+ async function flushFinalAwareness(tandemUrl, logPrefix = "[Tandem]") {
4465
4545
  if (shutdownTimers.awarenessTimer) clearTimeout(shutdownTimers.awarenessTimer);
4466
4546
  if (shutdownTimers.clearAwarenessTimer) clearTimeout(shutdownTimers.clearAwarenessTimer);
4467
4547
  if (outstandingAwareness.size > 0) {
@@ -4470,7 +4550,7 @@ async function finalClearAwareness() {
4470
4550
  if (shutdownTimers.lastDocumentId === null) return true;
4471
4551
  try {
4472
4552
  const res = await fetchWithTimeout(
4473
- `${TANDEM_URL}/api/channel-awareness`,
4553
+ `${tandemUrl}${API_CHANNEL_AWARENESS}`,
4474
4554
  {
4475
4555
  method: "POST",
4476
4556
  headers: { "Content-Type": "application/json" },
@@ -4480,27 +4560,35 @@ async function finalClearAwareness() {
4480
4560
  active: false
4481
4561
  })
4482
4562
  },
4483
- AWARENESS_FETCH_TIMEOUT_MS
4563
+ CHANNEL_AWARENESS_FETCH_TIMEOUT_MS
4484
4564
  );
4485
4565
  if (!res.ok) {
4486
- console.error(`[Monitor] Shutdown awareness clear returned ${res.status}`);
4566
+ console.error(`${logPrefix} Shutdown awareness clear returned ${res.status}`);
4487
4567
  return false;
4488
4568
  }
4489
4569
  return true;
4490
4570
  } catch (err) {
4491
4571
  console.error(
4492
- "[Monitor] Shutdown awareness clear failed:",
4493
- describeFetchError(err, "/api/channel-awareness shutdown", AWARENESS_FETCH_TIMEOUT_MS)
4572
+ `${logPrefix} Shutdown awareness clear failed:`,
4573
+ describeFetchError(
4574
+ err,
4575
+ `${API_CHANNEL_AWARENESS} shutdown`,
4576
+ CHANNEL_AWARENESS_FETCH_TIMEOUT_MS
4577
+ )
4494
4578
  );
4495
4579
  return false;
4496
4580
  }
4497
4581
  }
4498
- async function shutdownMonitor(signal) {
4499
- console.error(`[Monitor] Received ${signal}, clearing awareness and exiting`);
4500
- const ok = await finalClearAwareness();
4501
- process.exit(ok ? 0 : 1);
4582
+ function _resetSseConsumerStateForTests() {
4583
+ cachedMode = TANDEM_MODE_DEFAULT;
4584
+ cachedModeAt = 0;
4585
+ cachedModeFailedAt = 0;
4586
+ _modeRefreshInFlight = null;
4587
+ shutdownTimers.awarenessTimer = null;
4588
+ shutdownTimers.clearAwarenessTimer = null;
4589
+ shutdownTimers.lastDocumentId = null;
4590
+ outstandingAwareness.clear();
4502
4591
  }
4503
- var shutdownForTests = shutdownMonitor;
4504
4592
  function _setLastDocumentIdForTests(id) {
4505
4593
  shutdownTimers.lastDocumentId = id;
4506
4594
  }
@@ -4510,11 +4598,63 @@ function _getLastDocumentIdForTests() {
4510
4598
  function _addOutstandingAwarenessForTests(p) {
4511
4599
  trackAwareness(p);
4512
4600
  }
4601
+
4602
+ // src/monitor/index.ts
4603
+ var IS_VITEST = process.env.VITEST === "true";
4604
+ if (!IS_VITEST) {
4605
+ console.log = console.error;
4606
+ console.warn = console.error;
4607
+ console.info = console.error;
4608
+ }
4609
+ var TANDEM_URL = resolveTandemUrl();
4610
+ var LOG_PREFIX = "[Monitor]";
4611
+ function buildOptions() {
4612
+ return {
4613
+ tandemUrl: TANDEM_URL,
4614
+ logPrefix: LOG_PREFIX,
4615
+ errorCode: MONITOR_CONNECT_FAILED,
4616
+ onEvent: (event) => {
4617
+ const content = formatEventContent(event).replace(/\n/g, " ");
4618
+ process.stdout.write(content + "\n");
4619
+ },
4620
+ onExhaustion: () => {
4621
+ process.stdout.write(
4622
+ "Tandem monitor disconnected \u2014 restart Tandem to restore real-time events\n"
4623
+ );
4624
+ }
4625
+ };
4626
+ }
4627
+ async function main() {
4628
+ installShutdownHandlers();
4629
+ installStdoutErrorHandler();
4630
+ console.error(`${LOG_PREFIX} Tandem monitor starting (server: ${TANDEM_URL})`);
4631
+ await getCachedMode2().catch(() => {
4632
+ });
4633
+ await runEventConsumer(buildOptions());
4634
+ }
4635
+ async function connectAndStream(lastEventId, onEventId, onStable = () => {
4636
+ }) {
4637
+ return connectAndStreamOnce(buildOptions(), lastEventId, { onEventId, onStable });
4638
+ }
4639
+ async function shutdownMonitor(signal) {
4640
+ console.error(`${LOG_PREFIX} Received ${signal}, clearing awareness and exiting`);
4641
+ const ok = await flushFinalAwareness(TANDEM_URL, LOG_PREFIX);
4642
+ process.exit(ok ? 0 : 1);
4643
+ }
4644
+ function _setLastDocumentIdForTests2(id) {
4645
+ _setLastDocumentIdForTests(id);
4646
+ }
4647
+ function _getLastDocumentIdForTests2() {
4648
+ return _getLastDocumentIdForTests();
4649
+ }
4650
+ function _addOutstandingAwarenessForTests2(p) {
4651
+ _addOutstandingAwarenessForTests(p);
4652
+ }
4513
4653
  function installShutdownHandlers() {
4514
4654
  if (IS_VITEST) return;
4515
4655
  const handler = (signal) => {
4516
4656
  shutdownMonitor(signal).catch((err) => {
4517
- console.error("[Monitor] Shutdown handler failed:", err);
4657
+ console.error(`${LOG_PREFIX} Shutdown handler failed:`, err);
4518
4658
  process.exit(1);
4519
4659
  });
4520
4660
  };
@@ -4522,79 +4662,21 @@ function installShutdownHandlers() {
4522
4662
  process.on("SIGTERM", () => handler("SIGTERM"));
4523
4663
  }
4524
4664
  function onStdoutError(err) {
4525
- console.error("[Monitor] stdout error (plugin-host pipe likely closed):", err);
4665
+ console.error(`${LOG_PREFIX} stdout error (plugin-host pipe likely closed):`, err);
4526
4666
  process.exit(1);
4527
4667
  }
4528
4668
  function installStdoutErrorHandler() {
4529
4669
  if (IS_VITEST) return;
4530
4670
  process.stdout.on("error", onStdoutError);
4531
4671
  }
4532
- var cachedMode = TANDEM_MODE_DEFAULT;
4533
- var cachedModeAt = 0;
4534
- var cachedModeFailedAt = 0;
4535
- async function fetchMode() {
4536
- try {
4537
- const res = await fetchWithTimeout(`${TANDEM_URL}/api/mode`, {}, MODE_FETCH_TIMEOUT_MS);
4538
- if (!res.ok) return { ok: false, reason: `status ${res.status}` };
4539
- const body = await res.json();
4540
- const parsed = TandemModeSchema.safeParse(body.mode);
4541
- if (!parsed.success) return { ok: false, reason: `invalid mode ${JSON.stringify(body.mode)}` };
4542
- return { ok: true, mode: parsed.data };
4543
- } catch (err) {
4544
- return { ok: false, reason: describeFetchError(err, "/api/mode", MODE_FETCH_TIMEOUT_MS) };
4545
- }
4546
- }
4547
- async function getCachedMode() {
4548
- const now = Date.now();
4549
- if (now - cachedModeAt < MODE_CACHE_TTL_MS) return cachedMode;
4550
- const result = await fetchMode();
4551
- if (!result.ok) {
4552
- console.error(`[Monitor] Mode check failed (${result.reason}), failing closed to 'solo'`);
4553
- cachedMode = "solo";
4554
- return "solo";
4555
- }
4556
- cachedMode = result.mode;
4557
- cachedModeAt = now;
4558
- return cachedMode;
4672
+ async function getCachedMode2() {
4673
+ return getCachedMode(TANDEM_URL, LOG_PREFIX);
4559
4674
  }
4560
- function getModeSync() {
4561
- return cachedMode;
4562
- }
4563
- function refreshMode() {
4564
- if (_modeRefreshInFlight) return;
4565
- const now = Date.now();
4566
- if (now - cachedModeAt < MODE_CACHE_TTL_MS) return;
4567
- if (now - cachedModeFailedAt < MODE_CACHE_TTL_MS) return;
4568
- _modeRefreshInFlight = (async () => {
4569
- try {
4570
- const result = await fetchMode();
4571
- if (result.ok) {
4572
- cachedMode = result.mode;
4573
- cachedModeAt = Date.now();
4574
- cachedModeFailedAt = 0;
4575
- } else {
4576
- cachedModeFailedAt = Date.now();
4577
- console.error(
4578
- `[Monitor] Background mode refresh failed (${result.reason}), keeping cached`
4579
- );
4580
- }
4581
- } finally {
4582
- _modeRefreshInFlight = null;
4583
- }
4584
- })().catch((err) => {
4585
- console.error("[Monitor] refreshMode unexpected error:", err);
4586
- cachedModeFailedAt = Date.now();
4587
- });
4675
+ function getModeSync2() {
4676
+ return getModeSync();
4588
4677
  }
4589
4678
  function _resetMonitorStateForTests() {
4590
- cachedMode = TANDEM_MODE_DEFAULT;
4591
- cachedModeAt = 0;
4592
- cachedModeFailedAt = 0;
4593
- _modeRefreshInFlight = null;
4594
- shutdownTimers.awarenessTimer = null;
4595
- shutdownTimers.clearAwarenessTimer = null;
4596
- shutdownTimers.lastDocumentId = null;
4597
- outstandingAwareness.clear();
4679
+ _resetSseConsumerStateForTests();
4598
4680
  process.removeAllListeners("SIGINT");
4599
4681
  process.removeAllListeners("SIGTERM");
4600
4682
  }
@@ -4609,21 +4691,20 @@ var __thisFileNormalized = normalizeForCompare(fileURLToPath(import.meta.url));
4609
4691
  var isDirectRun = typeof process.argv[1] === "string" && normalizeForCompare(process.argv[1]) === __thisFileNormalized;
4610
4692
  if (isDirectRun && !IS_VITEST) {
4611
4693
  main().catch((err) => {
4612
- console.error("[Monitor] Fatal error:", err);
4694
+ console.error(`${LOG_PREFIX} Fatal error:`, err);
4613
4695
  process.exit(1);
4614
4696
  });
4615
4697
  }
4616
4698
  export {
4617
- _addOutstandingAwarenessForTests,
4618
- _getLastDocumentIdForTests,
4699
+ _addOutstandingAwarenessForTests2 as _addOutstandingAwarenessForTests,
4700
+ _getLastDocumentIdForTests2 as _getLastDocumentIdForTests,
4619
4701
  _monitorTestExports,
4620
4702
  _resetMonitorStateForTests,
4621
- _setLastDocumentIdForTests,
4703
+ _setLastDocumentIdForTests2 as _setLastDocumentIdForTests,
4622
4704
  connectAndStream,
4623
- getCachedMode,
4624
- getModeSync,
4705
+ getCachedMode2 as getCachedMode,
4706
+ getModeSync2 as getModeSync,
4625
4707
  main,
4626
- shutdownForTests,
4627
4708
  shutdownMonitor
4628
4709
  };
4629
4710
  //# sourceMappingURL=index.js.map