@sideboard-ai/core 0.1.88 → 0.1.89

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.
@@ -286,6 +286,49 @@ function cursorSdkMessageToEvents(msg) {
286
286
  return [];
287
287
  }
288
288
 
289
+ // src/agents/cursor-stream-coalesce.ts
290
+ function isTextEvent(event) {
291
+ return event.type === "stdout" || event.type === "thinking";
292
+ }
293
+ function createAgentStreamCoalescer(emit2, opts) {
294
+ const intervalMs = opts?.intervalMs ?? 32;
295
+ let pending = null;
296
+ let timer = null;
297
+ const flush = () => {
298
+ if (timer) {
299
+ clearTimeout(timer);
300
+ timer = null;
301
+ }
302
+ if (!pending) return;
303
+ const event = pending;
304
+ pending = null;
305
+ emit2({ type: event.type, data: event.data });
306
+ };
307
+ const schedule = () => {
308
+ if (timer) return;
309
+ timer = setTimeout(flush, intervalMs);
310
+ timer.unref?.();
311
+ };
312
+ return {
313
+ push(event) {
314
+ if (!isTextEvent(event)) {
315
+ flush();
316
+ emit2(event);
317
+ return;
318
+ }
319
+ if (!event.data) return;
320
+ if (pending && pending.type === event.type) {
321
+ pending.data += event.data;
322
+ } else {
323
+ flush();
324
+ pending = { type: event.type, data: event.data };
325
+ }
326
+ schedule();
327
+ },
328
+ flush
329
+ };
330
+ }
331
+
289
332
  // src/agents/cursor-runner.ts
290
333
  dropNestedElectronEnvFromProcess();
291
334
  function emit(event) {
@@ -442,12 +485,14 @@ async function main() {
442
485
  emit({ type: "session_id", data: agent.agentId });
443
486
  const run = await sendPrompt(agent);
444
487
  liveRun = run;
488
+ const stream = createAgentStreamCoalescer(emit);
445
489
  try {
446
490
  for await (const msg of run.stream()) {
447
491
  for (const event of cursorSdkMessageToEvents(msg)) {
448
- emit(event);
492
+ stream.push(event);
449
493
  }
450
494
  }
495
+ stream.flush();
451
496
  const result = await run.wait();
452
497
  if (result.status === "error") {
453
498
  const detail = formatUnknownDetail(result.error);
@@ -460,6 +505,7 @@ async function main() {
460
505
  if (result.status === "cancelled") return 0;
461
506
  return 0;
462
507
  } finally {
508
+ stream.flush();
463
509
  liveRun = null;
464
510
  }
465
511
  }
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ createAgentStreamCoalescer
4
+ } from "../chunk-MMI4RJ5I.js";
2
5
  import {
3
6
  cursorSdkMessageToEvents,
4
7
  formatUnknownDetail
@@ -208,12 +211,14 @@ async function main() {
208
211
  emit({ type: "session_id", data: agent.agentId });
209
212
  const run = await sendPrompt(agent);
210
213
  liveRun = run;
214
+ const stream = createAgentStreamCoalescer(emit);
211
215
  try {
212
216
  for await (const msg of run.stream()) {
213
217
  for (const event of cursorSdkMessageToEvents(msg)) {
214
- emit(event);
218
+ stream.push(event);
215
219
  }
216
220
  }
221
+ stream.flush();
217
222
  const result = await run.wait();
218
223
  if (result.status === "error") {
219
224
  const detail = formatUnknownDetail(result.error);
@@ -226,6 +231,7 @@ async function main() {
226
231
  if (result.status === "cancelled") return 0;
227
232
  return 0;
228
233
  } finally {
234
+ stream.flush();
229
235
  liveRun = null;
230
236
  }
231
237
  }
@@ -0,0 +1,46 @@
1
+ // src/agents/cursor-stream-coalesce.ts
2
+ function isTextEvent(event) {
3
+ return event.type === "stdout" || event.type === "thinking";
4
+ }
5
+ function createAgentStreamCoalescer(emit, opts) {
6
+ const intervalMs = opts?.intervalMs ?? 32;
7
+ let pending = null;
8
+ let timer = null;
9
+ const flush = () => {
10
+ if (timer) {
11
+ clearTimeout(timer);
12
+ timer = null;
13
+ }
14
+ if (!pending) return;
15
+ const event = pending;
16
+ pending = null;
17
+ emit({ type: event.type, data: event.data });
18
+ };
19
+ const schedule = () => {
20
+ if (timer) return;
21
+ timer = setTimeout(flush, intervalMs);
22
+ timer.unref?.();
23
+ };
24
+ return {
25
+ push(event) {
26
+ if (!isTextEvent(event)) {
27
+ flush();
28
+ emit(event);
29
+ return;
30
+ }
31
+ if (!event.data) return;
32
+ if (pending && pending.type === event.type) {
33
+ pending.data += event.data;
34
+ } else {
35
+ flush();
36
+ pending = { type: event.type, data: event.data };
37
+ }
38
+ schedule();
39
+ },
40
+ flush
41
+ };
42
+ }
43
+
44
+ export {
45
+ createAgentStreamCoalescer
46
+ };
package/dist/index.cjs CHANGED
@@ -10441,6 +10441,51 @@ function normalizeParseResult(parsed) {
10441
10441
  // src/agents/spawn.ts
10442
10442
  init_path();
10443
10443
  init_usage();
10444
+
10445
+ // src/agents/cursor-stream-coalesce.ts
10446
+ function isTextEvent(event) {
10447
+ return event.type === "stdout" || event.type === "thinking";
10448
+ }
10449
+ function createAgentStreamCoalescer(emit, opts) {
10450
+ const intervalMs = opts?.intervalMs ?? 32;
10451
+ let pending = null;
10452
+ let timer = null;
10453
+ const flush = () => {
10454
+ if (timer) {
10455
+ clearTimeout(timer);
10456
+ timer = null;
10457
+ }
10458
+ if (!pending) return;
10459
+ const event = pending;
10460
+ pending = null;
10461
+ emit({ type: event.type, data: event.data });
10462
+ };
10463
+ const schedule = () => {
10464
+ if (timer) return;
10465
+ timer = setTimeout(flush, intervalMs);
10466
+ timer.unref?.();
10467
+ };
10468
+ return {
10469
+ push(event) {
10470
+ if (!isTextEvent(event)) {
10471
+ flush();
10472
+ emit(event);
10473
+ return;
10474
+ }
10475
+ if (!event.data) return;
10476
+ if (pending && pending.type === event.type) {
10477
+ pending.data += event.data;
10478
+ } else {
10479
+ flush();
10480
+ pending = { type: event.type, data: event.data };
10481
+ }
10482
+ schedule();
10483
+ },
10484
+ flush
10485
+ };
10486
+ }
10487
+
10488
+ // src/agents/spawn.ts
10444
10489
  async function spawnAgentTurn(thread, input, onEvent) {
10445
10490
  ensureAgentPath();
10446
10491
  if (!thread.worktreePath?.trim()) {
@@ -10495,12 +10540,13 @@ async function spawnAgentTurn(thread, input, onEvent) {
10495
10540
  let assistantText = "";
10496
10541
  let parts = [];
10497
10542
  let usage = null;
10543
+ const outbound = createAgentStreamCoalescer(onEvent);
10498
10544
  const consume = (stream, kind) => {
10499
10545
  if (!stream) return;
10500
10546
  const rl = (0, import_node_readline2.createInterface)({ input: stream, crlfDelay: Infinity });
10501
10547
  rl.on("line", (line) => {
10502
10548
  if (kind === "stderr") {
10503
- onEvent({ type: "stderr", data: line });
10549
+ outbound.push({ type: "stderr", data: line });
10504
10550
  return;
10505
10551
  }
10506
10552
  let events = normalizeParseResult(adapter.parseEvent(line));
@@ -10515,12 +10561,12 @@ async function spawnAgentTurn(thread, input, onEvent) {
10515
10561
  for (const parsed of events) {
10516
10562
  if (parsed.type === "session_id") {
10517
10563
  sessionId = parsed.data;
10518
- onEvent(parsed);
10564
+ outbound.push(parsed);
10519
10565
  continue;
10520
10566
  }
10521
10567
  if (parsed.type === "usage") {
10522
10568
  usage = applyTurnUsage(usage, parsed.data, parsed.scope ?? "request");
10523
- onEvent(parsed);
10569
+ outbound.push(parsed);
10524
10570
  continue;
10525
10571
  }
10526
10572
  if (parsed.type === "stdout") {
@@ -10533,13 +10579,14 @@ async function spawnAgentTurn(thread, input, onEvent) {
10533
10579
  assistantText += parsed.data;
10534
10580
  }
10535
10581
  parts = applyAgentEvent(parts, parsed);
10536
- onEvent(parsed);
10582
+ outbound.push(parsed);
10537
10583
  }
10538
10584
  });
10539
10585
  };
10540
10586
  consume(child.stdout, "stdout");
10541
10587
  consume(child.stderr, "stderr");
10542
10588
  const done = child.then((result) => {
10589
+ outbound.flush();
10543
10590
  const exitCode = result.exitCode ?? null;
10544
10591
  onEvent({ type: "exit", data: exitCode });
10545
10592
  const finalized = finalizeParts(parts);
@@ -15560,10 +15607,12 @@ var Orchestrator = class {
15560
15607
  if (event.type === "stderr" && typeof event.data === "string") {
15561
15608
  pushTurnStderr(stderrTail, event.data);
15562
15609
  }
15563
- const live = readThread(threadId);
15564
- if (live?.lastError?.includes("reconciled on startup") && (this.activeTurns.has(threadId) || this.startingTurns.has(threadId))) {
15565
- setStatus(threadId, "running");
15566
- this.emit({ type: "status_changed", threadId, status: "running" });
15610
+ if (event.type !== "stdout" && event.type !== "thinking") {
15611
+ const live = readThread(threadId);
15612
+ if (live?.lastError?.includes("reconciled on startup") && (this.activeTurns.has(threadId) || this.startingTurns.has(threadId))) {
15613
+ setStatus(threadId, "running");
15614
+ this.emit({ type: "status_changed", threadId, status: "running" });
15615
+ }
15567
15616
  }
15568
15617
  }
15569
15618
  );
package/dist/index.js CHANGED
@@ -124,6 +124,9 @@ import {
124
124
  resolvePlanMarkdown,
125
125
  writePlanFile
126
126
  } from "./chunk-QTUESPAW.js";
127
+ import {
128
+ createAgentStreamCoalescer
129
+ } from "./chunk-MMI4RJ5I.js";
127
130
  import {
128
131
  cursorSdkMessageToEvents,
129
132
  fallbackTurnFailDetail,
@@ -1372,12 +1375,13 @@ async function spawnAgentTurn(thread, input, onEvent) {
1372
1375
  let assistantText = "";
1373
1376
  let parts = [];
1374
1377
  let usage = null;
1378
+ const outbound = createAgentStreamCoalescer(onEvent);
1375
1379
  const consume = (stream, kind) => {
1376
1380
  if (!stream) return;
1377
1381
  const rl = createInterface({ input: stream, crlfDelay: Infinity });
1378
1382
  rl.on("line", (line) => {
1379
1383
  if (kind === "stderr") {
1380
- onEvent({ type: "stderr", data: line });
1384
+ outbound.push({ type: "stderr", data: line });
1381
1385
  return;
1382
1386
  }
1383
1387
  let events = normalizeParseResult(adapter.parseEvent(line));
@@ -1392,12 +1396,12 @@ async function spawnAgentTurn(thread, input, onEvent) {
1392
1396
  for (const parsed of events) {
1393
1397
  if (parsed.type === "session_id") {
1394
1398
  sessionId = parsed.data;
1395
- onEvent(parsed);
1399
+ outbound.push(parsed);
1396
1400
  continue;
1397
1401
  }
1398
1402
  if (parsed.type === "usage") {
1399
1403
  usage = applyTurnUsage(usage, parsed.data, parsed.scope ?? "request");
1400
- onEvent(parsed);
1404
+ outbound.push(parsed);
1401
1405
  continue;
1402
1406
  }
1403
1407
  if (parsed.type === "stdout") {
@@ -1410,13 +1414,14 @@ async function spawnAgentTurn(thread, input, onEvent) {
1410
1414
  assistantText += parsed.data;
1411
1415
  }
1412
1416
  parts = applyAgentEvent(parts, parsed);
1413
- onEvent(parsed);
1417
+ outbound.push(parsed);
1414
1418
  }
1415
1419
  });
1416
1420
  };
1417
1421
  consume(child.stdout, "stdout");
1418
1422
  consume(child.stderr, "stderr");
1419
1423
  const done = child.then((result) => {
1424
+ outbound.flush();
1420
1425
  const exitCode = result.exitCode ?? null;
1421
1426
  onEvent({ type: "exit", data: exitCode });
1422
1427
  const finalized = finalizeParts(parts);
@@ -6354,10 +6359,12 @@ var Orchestrator = class {
6354
6359
  if (event.type === "stderr" && typeof event.data === "string") {
6355
6360
  pushTurnStderr(stderrTail, event.data);
6356
6361
  }
6357
- const live = readThread(threadId);
6358
- if (live?.lastError?.includes("reconciled on startup") && (this.activeTurns.has(threadId) || this.startingTurns.has(threadId))) {
6359
- setStatus(threadId, "running");
6360
- this.emit({ type: "status_changed", threadId, status: "running" });
6362
+ if (event.type !== "stdout" && event.type !== "thinking") {
6363
+ const live = readThread(threadId);
6364
+ if (live?.lastError?.includes("reconciled on startup") && (this.activeTurns.has(threadId) || this.startingTurns.has(threadId))) {
6365
+ setStatus(threadId, "running");
6366
+ this.emit({ type: "status_changed", threadId, status: "running" });
6367
+ }
6361
6368
  }
6362
6369
  }
6363
6370
  );
@@ -9312,6 +9312,51 @@ function normalizeParseResult(parsed) {
9312
9312
  // src/agents/spawn.ts
9313
9313
  init_path();
9314
9314
  init_usage();
9315
+
9316
+ // src/agents/cursor-stream-coalesce.ts
9317
+ function isTextEvent(event) {
9318
+ return event.type === "stdout" || event.type === "thinking";
9319
+ }
9320
+ function createAgentStreamCoalescer(emit, opts) {
9321
+ const intervalMs = opts?.intervalMs ?? 32;
9322
+ let pending = null;
9323
+ let timer = null;
9324
+ const flush = () => {
9325
+ if (timer) {
9326
+ clearTimeout(timer);
9327
+ timer = null;
9328
+ }
9329
+ if (!pending) return;
9330
+ const event = pending;
9331
+ pending = null;
9332
+ emit({ type: event.type, data: event.data });
9333
+ };
9334
+ const schedule = () => {
9335
+ if (timer) return;
9336
+ timer = setTimeout(flush, intervalMs);
9337
+ timer.unref?.();
9338
+ };
9339
+ return {
9340
+ push(event) {
9341
+ if (!isTextEvent(event)) {
9342
+ flush();
9343
+ emit(event);
9344
+ return;
9345
+ }
9346
+ if (!event.data) return;
9347
+ if (pending && pending.type === event.type) {
9348
+ pending.data += event.data;
9349
+ } else {
9350
+ flush();
9351
+ pending = { type: event.type, data: event.data };
9352
+ }
9353
+ schedule();
9354
+ },
9355
+ flush
9356
+ };
9357
+ }
9358
+
9359
+ // src/agents/spawn.ts
9315
9360
  async function spawnAgentTurn(thread, input, onEvent) {
9316
9361
  ensureAgentPath();
9317
9362
  if (!thread.worktreePath?.trim()) {
@@ -9366,12 +9411,13 @@ async function spawnAgentTurn(thread, input, onEvent) {
9366
9411
  let assistantText = "";
9367
9412
  let parts = [];
9368
9413
  let usage = null;
9414
+ const outbound = createAgentStreamCoalescer(onEvent);
9369
9415
  const consume = (stream, kind) => {
9370
9416
  if (!stream) return;
9371
9417
  const rl = (0, import_node_readline2.createInterface)({ input: stream, crlfDelay: Infinity });
9372
9418
  rl.on("line", (line) => {
9373
9419
  if (kind === "stderr") {
9374
- onEvent({ type: "stderr", data: line });
9420
+ outbound.push({ type: "stderr", data: line });
9375
9421
  return;
9376
9422
  }
9377
9423
  let events = normalizeParseResult(adapter.parseEvent(line));
@@ -9386,12 +9432,12 @@ async function spawnAgentTurn(thread, input, onEvent) {
9386
9432
  for (const parsed of events) {
9387
9433
  if (parsed.type === "session_id") {
9388
9434
  sessionId = parsed.data;
9389
- onEvent(parsed);
9435
+ outbound.push(parsed);
9390
9436
  continue;
9391
9437
  }
9392
9438
  if (parsed.type === "usage") {
9393
9439
  usage = applyTurnUsage(usage, parsed.data, parsed.scope ?? "request");
9394
- onEvent(parsed);
9440
+ outbound.push(parsed);
9395
9441
  continue;
9396
9442
  }
9397
9443
  if (parsed.type === "stdout") {
@@ -9404,13 +9450,14 @@ async function spawnAgentTurn(thread, input, onEvent) {
9404
9450
  assistantText += parsed.data;
9405
9451
  }
9406
9452
  parts = applyAgentEvent(parts, parsed);
9407
- onEvent(parsed);
9453
+ outbound.push(parsed);
9408
9454
  }
9409
9455
  });
9410
9456
  };
9411
9457
  consume(child.stdout, "stdout");
9412
9458
  consume(child.stderr, "stderr");
9413
9459
  const done = child.then((result) => {
9460
+ outbound.flush();
9414
9461
  const exitCode = result.exitCode ?? null;
9415
9462
  onEvent({ type: "exit", data: exitCode });
9416
9463
  const finalized = finalizeParts(parts);
@@ -13624,10 +13671,12 @@ var Orchestrator = class {
13624
13671
  if (event.type === "stderr" && typeof event.data === "string") {
13625
13672
  pushTurnStderr(stderrTail, event.data);
13626
13673
  }
13627
- const live = readThread(threadId);
13628
- if (live?.lastError?.includes("reconciled on startup") && (this.activeTurns.has(threadId) || this.startingTurns.has(threadId))) {
13629
- setStatus(threadId, "running");
13630
- this.emit({ type: "status_changed", threadId, status: "running" });
13674
+ if (event.type !== "stdout" && event.type !== "thinking") {
13675
+ const live = readThread(threadId);
13676
+ if (live?.lastError?.includes("reconciled on startup") && (this.activeTurns.has(threadId) || this.startingTurns.has(threadId))) {
13677
+ setStatus(threadId, "running");
13678
+ this.emit({ type: "status_changed", threadId, status: "running" });
13679
+ }
13631
13680
  }
13632
13681
  }
13633
13682
  );
@@ -888,6 +888,49 @@ function normalizeParseResult(parsed) {
888
888
  return Array.isArray(parsed) ? parsed : [parsed];
889
889
  }
890
890
 
891
+ // src/agents/cursor-stream-coalesce.ts
892
+ function isTextEvent(event) {
893
+ return event.type === "stdout" || event.type === "thinking";
894
+ }
895
+ function createAgentStreamCoalescer(emit, opts) {
896
+ const intervalMs = opts?.intervalMs ?? 32;
897
+ let pending = null;
898
+ let timer = null;
899
+ const flush = () => {
900
+ if (timer) {
901
+ clearTimeout(timer);
902
+ timer = null;
903
+ }
904
+ if (!pending) return;
905
+ const event = pending;
906
+ pending = null;
907
+ emit({ type: event.type, data: event.data });
908
+ };
909
+ const schedule = () => {
910
+ if (timer) return;
911
+ timer = setTimeout(flush, intervalMs);
912
+ timer.unref?.();
913
+ };
914
+ return {
915
+ push(event) {
916
+ if (!isTextEvent(event)) {
917
+ flush();
918
+ emit(event);
919
+ return;
920
+ }
921
+ if (!event.data) return;
922
+ if (pending && pending.type === event.type) {
923
+ pending.data += event.data;
924
+ } else {
925
+ flush();
926
+ pending = { type: event.type, data: event.data };
927
+ }
928
+ schedule();
929
+ },
930
+ flush
931
+ };
932
+ }
933
+
891
934
  // src/agents/spawn.ts
892
935
  async function spawnAgentTurn(thread, input, onEvent) {
893
936
  ensureAgentPath();
@@ -943,12 +986,13 @@ async function spawnAgentTurn(thread, input, onEvent) {
943
986
  let assistantText = "";
944
987
  let parts = [];
945
988
  let usage = null;
989
+ const outbound = createAgentStreamCoalescer(onEvent);
946
990
  const consume = (stream, kind) => {
947
991
  if (!stream) return;
948
992
  const rl = createInterface({ input: stream, crlfDelay: Infinity });
949
993
  rl.on("line", (line) => {
950
994
  if (kind === "stderr") {
951
- onEvent({ type: "stderr", data: line });
995
+ outbound.push({ type: "stderr", data: line });
952
996
  return;
953
997
  }
954
998
  let events = normalizeParseResult(adapter.parseEvent(line));
@@ -963,12 +1007,12 @@ async function spawnAgentTurn(thread, input, onEvent) {
963
1007
  for (const parsed of events) {
964
1008
  if (parsed.type === "session_id") {
965
1009
  sessionId = parsed.data;
966
- onEvent(parsed);
1010
+ outbound.push(parsed);
967
1011
  continue;
968
1012
  }
969
1013
  if (parsed.type === "usage") {
970
1014
  usage = applyTurnUsage(usage, parsed.data, parsed.scope ?? "request");
971
- onEvent(parsed);
1015
+ outbound.push(parsed);
972
1016
  continue;
973
1017
  }
974
1018
  if (parsed.type === "stdout") {
@@ -981,13 +1025,14 @@ async function spawnAgentTurn(thread, input, onEvent) {
981
1025
  assistantText += parsed.data;
982
1026
  }
983
1027
  parts = applyAgentEvent(parts, parsed);
984
- onEvent(parsed);
1028
+ outbound.push(parsed);
985
1029
  }
986
1030
  });
987
1031
  };
988
1032
  consume(child.stdout, "stdout");
989
1033
  consume(child.stderr, "stderr");
990
1034
  const done = child.then((result) => {
1035
+ outbound.flush();
991
1036
  const exitCode = result.exitCode ?? null;
992
1037
  onEvent({ type: "exit", data: exitCode });
993
1038
  const finalized = finalizeParts(parts);
@@ -5136,10 +5181,12 @@ var Orchestrator = class {
5136
5181
  if (event.type === "stderr" && typeof event.data === "string") {
5137
5182
  pushTurnStderr(stderrTail, event.data);
5138
5183
  }
5139
- const live = readThread(threadId);
5140
- if (live?.lastError?.includes("reconciled on startup") && (this.activeTurns.has(threadId) || this.startingTurns.has(threadId))) {
5141
- setStatus(threadId, "running");
5142
- this.emit({ type: "status_changed", threadId, status: "running" });
5184
+ if (event.type !== "stdout" && event.type !== "thinking") {
5185
+ const live = readThread(threadId);
5186
+ if (live?.lastError?.includes("reconciled on startup") && (this.activeTurns.has(threadId) || this.startingTurns.has(threadId))) {
5187
+ setStatus(threadId, "running");
5188
+ this.emit({ type: "status_changed", threadId, status: "running" });
5189
+ }
5143
5190
  }
5144
5191
  }
5145
5192
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sideboard-ai/core",
3
- "version": "0.1.88",
3
+ "version": "0.1.89",
4
4
  "description": "Sideboard core — orchestration, agents, git worktrees, MCP server",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",