@getpaseo/server 0.1.33 → 0.1.34

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 (106) hide show
  1. package/dist/server/server/agent/activity-curator.d.ts.map +1 -1
  2. package/dist/server/server/agent/activity-curator.js +15 -1
  3. package/dist/server/server/agent/activity-curator.js.map +1 -1
  4. package/dist/server/server/agent/agent-management-mcp.d.ts.map +1 -1
  5. package/dist/server/server/agent/agent-management-mcp.js +2 -4
  6. package/dist/server/server/agent/agent-management-mcp.js.map +1 -1
  7. package/dist/server/server/agent/agent-manager.d.ts +25 -14
  8. package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
  9. package/dist/server/server/agent/agent-manager.js +383 -337
  10. package/dist/server/server/agent/agent-manager.js.map +1 -1
  11. package/dist/server/server/agent/agent-sdk-types.d.ts +16 -14
  12. package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
  13. package/dist/server/server/agent/mcp-server.d.ts.map +1 -1
  14. package/dist/server/server/agent/mcp-server.js +2 -4
  15. package/dist/server/server/agent/mcp-server.js.map +1 -1
  16. package/dist/server/server/agent/provider-launch-config.d.ts.map +1 -1
  17. package/dist/server/server/agent/provider-launch-config.js +15 -1
  18. package/dist/server/server/agent/provider-launch-config.js.map +1 -1
  19. package/dist/server/server/agent/provider-manifest.d.ts +1 -1
  20. package/dist/server/server/agent/provider-manifest.d.ts.map +1 -1
  21. package/dist/server/server/agent/provider-manifest.js +4 -4
  22. package/dist/server/server/agent/provider-manifest.js.map +1 -1
  23. package/dist/server/server/agent/providers/claude/partial-json.js +3 -3
  24. package/dist/server/server/agent/providers/claude/partial-json.js.map +1 -1
  25. package/dist/server/server/agent/providers/claude/sdk-model-resolver.d.ts +11 -0
  26. package/dist/server/server/agent/providers/claude/sdk-model-resolver.d.ts.map +1 -0
  27. package/dist/server/server/agent/providers/claude/sdk-model-resolver.js +104 -0
  28. package/dist/server/server/agent/providers/claude/sdk-model-resolver.js.map +1 -0
  29. package/dist/server/server/agent/providers/claude-agent.d.ts +4 -13
  30. package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
  31. package/dist/server/server/agent/providers/claude-agent.js +382 -453
  32. package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
  33. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +5 -3
  34. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
  35. package/dist/server/server/agent/providers/codex-app-server-agent.js +174 -151
  36. package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
  37. package/dist/server/server/agent/providers/opencode-agent.d.ts +3 -3
  38. package/dist/server/server/agent/providers/opencode-agent.d.ts.map +1 -1
  39. package/dist/server/server/agent/providers/opencode-agent.js +106 -25
  40. package/dist/server/server/agent/providers/opencode-agent.js.map +1 -1
  41. package/dist/server/server/agent/providers/test-utils/session-stream-adapter.d.ts +3 -0
  42. package/dist/server/server/agent/providers/test-utils/session-stream-adapter.d.ts.map +1 -0
  43. package/dist/server/server/agent/providers/test-utils/session-stream-adapter.js +57 -0
  44. package/dist/server/server/agent/providers/test-utils/session-stream-adapter.js.map +1 -0
  45. package/dist/server/server/config.d.ts.map +1 -1
  46. package/dist/server/server/config.js +1 -5
  47. package/dist/server/server/config.js.map +1 -1
  48. package/dist/server/server/session.d.ts.map +1 -1
  49. package/dist/server/server/session.js +14 -12
  50. package/dist/server/server/session.js.map +1 -1
  51. package/dist/server/shared/tool-call-display.js +1 -1
  52. package/dist/server/shared/tool-call-display.js.map +1 -1
  53. package/dist/src/server/agent/activity-curator.js +15 -1
  54. package/dist/src/server/agent/activity-curator.js.map +1 -1
  55. package/dist/src/server/agent/agent-manager.js +383 -337
  56. package/dist/src/server/agent/agent-manager.js.map +1 -1
  57. package/dist/src/server/agent/mcp-server.js +2 -4
  58. package/dist/src/server/agent/mcp-server.js.map +1 -1
  59. package/dist/src/server/agent/provider-launch-config.js +15 -1
  60. package/dist/src/server/agent/provider-launch-config.js.map +1 -1
  61. package/dist/src/server/agent/provider-manifest.js +4 -4
  62. package/dist/src/server/agent/provider-manifest.js.map +1 -1
  63. package/dist/src/server/agent/providers/claude/partial-json.js +3 -3
  64. package/dist/src/server/agent/providers/claude/partial-json.js.map +1 -1
  65. package/dist/src/server/agent/providers/claude/sdk-model-resolver.js +104 -0
  66. package/dist/src/server/agent/providers/claude/sdk-model-resolver.js.map +1 -0
  67. package/dist/src/server/agent/providers/claude-agent.js +382 -453
  68. package/dist/src/server/agent/providers/claude-agent.js.map +1 -1
  69. package/dist/src/server/agent/providers/codex-app-server-agent.js +174 -151
  70. package/dist/src/server/agent/providers/codex-app-server-agent.js.map +1 -1
  71. package/dist/src/server/agent/providers/opencode-agent.js +106 -25
  72. package/dist/src/server/agent/providers/opencode-agent.js.map +1 -1
  73. package/dist/src/server/config.js +1 -5
  74. package/dist/src/server/config.js.map +1 -1
  75. package/dist/src/server/session.js +14 -12
  76. package/dist/src/server/session.js.map +1 -1
  77. package/dist/src/shared/tool-call-display.js +1 -1
  78. package/dist/src/shared/tool-call-display.js.map +1 -1
  79. package/package.json +4 -5
  80. package/agent-prompt.md +0 -339
  81. package/dist/server/server/agent/providers/claude/model-catalog.d.ts +0 -29
  82. package/dist/server/server/agent/providers/claude/model-catalog.d.ts.map +0 -1
  83. package/dist/server/server/agent/providers/claude/model-catalog.js +0 -70
  84. package/dist/server/server/agent/providers/claude/model-catalog.js.map +0 -1
  85. package/dist/server/server/agent/system-prompt.d.ts +0 -3
  86. package/dist/server/server/agent/system-prompt.d.ts.map +0 -1
  87. package/dist/server/server/agent/system-prompt.js +0 -19
  88. package/dist/server/server/agent/system-prompt.js.map +0 -1
  89. package/dist/server/server/terminal-mcp/index.d.ts +0 -4
  90. package/dist/server/server/terminal-mcp/index.d.ts.map +0 -1
  91. package/dist/server/server/terminal-mcp/index.js +0 -3
  92. package/dist/server/server/terminal-mcp/index.js.map +0 -1
  93. package/dist/server/server/terminal-mcp/server.d.ts +0 -10
  94. package/dist/server/server/terminal-mcp/server.d.ts.map +0 -1
  95. package/dist/server/server/terminal-mcp/server.js +0 -209
  96. package/dist/server/server/terminal-mcp/server.js.map +0 -1
  97. package/dist/server/server/terminal-mcp/terminal-manager.d.ts +0 -123
  98. package/dist/server/server/terminal-mcp/terminal-manager.d.ts.map +0 -1
  99. package/dist/server/server/terminal-mcp/terminal-manager.js +0 -339
  100. package/dist/server/server/terminal-mcp/terminal-manager.js.map +0 -1
  101. package/dist/server/server/terminal-mcp/tmux.d.ts +0 -207
  102. package/dist/server/server/terminal-mcp/tmux.d.ts.map +0 -1
  103. package/dist/server/server/terminal-mcp/tmux.js +0 -821
  104. package/dist/server/server/terminal-mcp/tmux.js.map +0 -1
  105. package/dist/src/server/agent/providers/claude/model-catalog.js +0 -70
  106. package/dist/src/server/agent/providers/claude/model-catalog.js.map +0 -1
@@ -19,7 +19,6 @@ function attachPersistenceCwd(handle, cwd) {
19
19
  };
20
20
  }
21
21
  const DEFAULT_TIMELINE_FETCH_LIMIT = 200;
22
- const LIVE_BACKLOG_TERMINAL_REPLAY_DELAY_MS = 300;
23
22
  const BUSY_STATUSES = ["initializing", "running"];
24
23
  const AgentIdSchema = z.string().uuid();
25
24
  function isAgentBusy(status) {
@@ -46,10 +45,6 @@ function validateAgentId(agentId, source) {
46
45
  }
47
46
  return result.data;
48
47
  }
49
- function supportsLiveEventStream(session) {
50
- return ("streamLiveEvents" in session &&
51
- typeof session.streamLiveEvents === "function");
52
- }
53
48
  function normalizeMessageId(messageId) {
54
49
  if (typeof messageId !== "string") {
55
50
  return undefined;
@@ -61,12 +56,10 @@ export class AgentManager {
61
56
  constructor(options) {
62
57
  this.clients = new Map();
63
58
  this.agents = new Map();
59
+ this.pendingForegroundRuns = new Map();
64
60
  this.subscribers = new Set();
65
61
  this.previousStatuses = new Map();
66
62
  this.backgroundTasks = new Set();
67
- this.liveEventPumps = new Map();
68
- this.liveEventBacklog = new Map();
69
- this.liveEventBacklogFlushTimers = new Map();
70
63
  const maxTimelineItems = options?.maxTimelineItems;
71
64
  this.maxTimelineItems =
72
65
  typeof maxTimelineItems === "number" &&
@@ -100,6 +93,15 @@ export class AgentManager {
100
93
  agent.updatedAt = next;
101
94
  return next;
102
95
  }
96
+ hasInFlightRun(agentId) {
97
+ const agent = this.agents.get(agentId);
98
+ if (!agent) {
99
+ return false;
100
+ }
101
+ return (agent.lifecycle === "running" ||
102
+ Boolean(agent.activeForegroundTurnId) ||
103
+ this.hasPendingForegroundRun(agentId));
104
+ }
103
105
  subscribe(callback, options) {
104
106
  const targetAgentId = options?.agentId == null ? null : validateAgentId(options.agentId, "subscribe");
105
107
  const record = {
@@ -356,16 +358,14 @@ export class AgentManager {
356
358
  async createAgent(config, agentId, options) {
357
359
  // Generate agent ID early so we can use it in MCP config
358
360
  const resolvedAgentId = validateAgentId(agentId ?? this.idFactory(), "createAgent");
359
- const normalizedConfig = await this.normalizeConfig(config, {
360
- labels: options?.labels,
361
- agentId: resolvedAgentId,
362
- });
361
+ const normalizedConfig = await this.normalizeConfig(config);
362
+ const launchContext = this.buildLaunchContext(resolvedAgentId);
363
363
  const client = this.requireClient(normalizedConfig.provider);
364
364
  const available = await client.isAvailable();
365
365
  if (!available) {
366
366
  throw new Error(`Provider '${normalizedConfig.provider}' is not available. Please ensure the CLI is installed.`);
367
367
  }
368
- const session = await client.createSession(normalizedConfig);
368
+ const session = await client.createSession(normalizedConfig, launchContext);
369
369
  return this.registerSession(session, normalizedConfig, resolvedAgentId, {
370
370
  labels: options?.labels,
371
371
  });
@@ -384,15 +384,16 @@ export class AgentManager {
384
384
  const resumeOverrides = normalizedConfig.model !== mergedConfig.model
385
385
  ? { ...overrides, model: normalizedConfig.model }
386
386
  : overrides;
387
+ const launchContext = this.buildLaunchContext(resolvedAgentId);
387
388
  const client = this.requireClient(handle.provider);
388
- const session = await client.resumeSession(handle, resumeOverrides);
389
+ const session = await client.resumeSession(handle, resumeOverrides, launchContext);
389
390
  return this.registerSession(session, normalizedConfig, resolvedAgentId, options);
390
391
  }
391
392
  // Hot-reload an active agent session with config overrides while preserving
392
393
  // in-memory timeline state.
393
394
  async reloadAgentSession(agentId, overrides) {
394
395
  let existing = this.requireAgent(agentId);
395
- if (existing.lifecycle === "running" || existing.pendingRun) {
396
+ if (this.hasInFlightRun(agentId)) {
396
397
  await this.cancelAgentRun(agentId);
397
398
  existing = this.requireAgent(agentId);
398
399
  }
@@ -414,14 +415,21 @@ export class AgentManager {
414
415
  provider,
415
416
  };
416
417
  const normalizedConfig = await this.normalizeConfig(refreshConfig);
418
+ const launchContext = this.buildLaunchContext(agentId);
417
419
  const session = handle
418
- ? await client.resumeSession(handle, normalizedConfig)
419
- : await client.createSession(normalizedConfig);
420
+ ? await client.resumeSession(handle, normalizedConfig, launchContext)
421
+ : await client.createSession(normalizedConfig, launchContext);
420
422
  // Remove the existing agent entry before swapping sessions
421
423
  this.agents.delete(agentId);
422
- this.liveEventPumps.delete(agentId);
423
- this.liveEventBacklog.delete(agentId);
424
- this.clearLiveEventBacklogFlushTimer(agentId);
424
+ if (existing.unsubscribeSession) {
425
+ existing.unsubscribeSession();
426
+ existing.unsubscribeSession = null;
427
+ }
428
+ for (const waiter of existing.foregroundTurnWaiters) {
429
+ this.settleForegroundTurnWaiter(waiter);
430
+ }
431
+ existing.foregroundTurnWaiters.clear();
432
+ this.settlePendingForegroundRun(agentId);
425
433
  try {
426
434
  await existing.session.close();
427
435
  }
@@ -449,21 +457,34 @@ export class AgentManager {
449
457
  this.logger.trace({
450
458
  agentId,
451
459
  lifecycle: agent.lifecycle,
452
- hasPendingRun: Boolean(agent.pendingRun),
460
+ activeForegroundTurnId: agent.activeForegroundTurnId,
453
461
  pendingPermissions: agent.pendingPermissions.size,
454
462
  }, "closeAgent: start");
455
463
  this.agents.delete(agentId);
456
- this.liveEventPumps.delete(agentId);
457
- this.liveEventBacklog.delete(agentId);
458
- this.clearLiveEventBacklogFlushTimer(agentId);
459
464
  // Clean up previousStatus to prevent memory leak
460
465
  this.previousStatuses.delete(agentId);
466
+ if (agent.unsubscribeSession) {
467
+ agent.unsubscribeSession();
468
+ agent.unsubscribeSession = null;
469
+ }
470
+ for (const waiter of agent.foregroundTurnWaiters) {
471
+ // Wake up the generator so it can exit the await loop
472
+ waiter.callback({
473
+ type: "turn_canceled",
474
+ provider: agent.provider,
475
+ reason: "agent closed",
476
+ turnId: waiter.turnId,
477
+ });
478
+ this.settleForegroundTurnWaiter(waiter);
479
+ }
480
+ agent.foregroundTurnWaiters.clear();
481
+ this.settlePendingForegroundRun(agentId);
461
482
  const session = agent.session;
462
483
  const closedAgent = {
463
484
  ...agent,
464
485
  lifecycle: "closed",
465
486
  session: null,
466
- pendingRun: null,
487
+ activeForegroundTurnId: null,
467
488
  };
468
489
  await session.close();
469
490
  this.emitState(closedAgent);
@@ -623,128 +644,140 @@ export class AgentManager {
623
644
  this.logger.trace({
624
645
  agentId,
625
646
  lifecycle: existingAgent.lifecycle,
626
- hasPendingRun: Boolean(existingAgent.pendingRun),
647
+ activeForegroundTurnId: existingAgent.activeForegroundTurnId,
648
+ hasPendingForegroundRun: this.hasPendingForegroundRun(agentId),
627
649
  promptType: typeof prompt === "string" ? "string" : "structured",
628
650
  hasRunOptions: Boolean(options),
629
651
  }, "streamAgent: requested");
630
- if (existingAgent.pendingRun) {
652
+ if (existingAgent.activeForegroundTurnId || this.hasPendingForegroundRun(agentId)) {
631
653
  this.logger.trace({
632
654
  agentId,
633
655
  lifecycle: existingAgent.lifecycle,
634
- }, "streamAgent: rejected because pendingRun already exists");
656
+ hasPendingForegroundRun: this.hasPendingForegroundRun(agentId),
657
+ }, "streamAgent: rejected because a foreground run is already in flight");
635
658
  throw new Error(`Agent ${agentId} already has an active run`);
636
659
  }
637
660
  const agent = existingAgent;
638
- const iterator = agent.session.stream(prompt, options);
639
661
  agent.pendingReplacement = false;
640
662
  agent.lastError = undefined;
641
- let finalized = false;
642
- const finalize = (error) => {
643
- this.logger.trace({
644
- agentId,
645
- error,
646
- alreadyFinalized: finalized,
647
- lifecycle: agent.lifecycle,
648
- hasPendingRun: Boolean(agent.pendingRun),
649
- }, "streamAgent.finalize: invoked");
650
- if (finalized) {
651
- return;
652
- }
653
- finalized = true;
654
- if (agent.pendingRun !== streamForwarder) {
655
- this.logger.trace({
656
- agentId,
657
- error,
658
- lifecycle: agent.lifecycle,
659
- hasPendingRun: Boolean(agent.pendingRun),
660
- }, "streamAgent.finalize: skipped because pendingRun no longer points to streamForwarder");
661
- if (error) {
662
- agent.lastError = error;
663
- }
664
- return;
665
- }
666
- const mutableAgent = agent;
667
- mutableAgent.pendingRun = null;
668
- const terminalError = error ?? mutableAgent.lastError;
669
- const shouldHoldBusyForReplacement = mutableAgent.pendingReplacement && !terminalError;
670
- mutableAgent.lifecycle = shouldHoldBusyForReplacement
671
- ? "running"
672
- : terminalError
673
- ? "error"
674
- : "idle";
675
- mutableAgent.lastError = terminalError;
676
- const persistenceHandle = mutableAgent.session.describePersistence() ??
677
- (mutableAgent.runtimeInfo?.sessionId
678
- ? { provider: mutableAgent.provider, sessionId: mutableAgent.runtimeInfo.sessionId }
679
- : null);
680
- if (persistenceHandle) {
681
- mutableAgent.persistence = attachPersistenceCwd(persistenceHandle, mutableAgent.cwd);
682
- }
683
- this.logger.trace({
684
- agentId,
685
- lifecycle: mutableAgent.lifecycle,
686
- hasPendingRun: Boolean(mutableAgent.pendingRun),
687
- terminalError,
688
- pendingReplacement: mutableAgent.pendingReplacement,
689
- }, "streamAgent.finalize: applying terminal state");
690
- if (!shouldHoldBusyForReplacement) {
691
- this.touchUpdatedAt(mutableAgent);
692
- this.emitState(mutableAgent);
693
- this.flushLiveEventBacklog(mutableAgent);
694
- }
695
- };
696
663
  const self = this;
697
664
  const streamForwarder = (async function* streamForwarder() {
698
- let finalizeError;
665
+ const pendingRun = self.createPendingForegroundRun();
666
+ self.pendingForegroundRuns.set(agentId, pendingRun);
667
+ let turnId;
668
+ let waiter = null;
699
669
  try {
700
- for await (const event of iterator) {
701
- self.handleStreamEvent(agent, event);
702
- if (event.type === "turn_started" ||
703
- event.type === "turn_completed" ||
704
- event.type === "turn_failed" ||
705
- event.type === "turn_canceled") {
706
- self.logger.trace({
707
- agentId,
708
- eventType: event.type,
709
- lifecycle: agent.lifecycle,
710
- hasPendingRun: Boolean(agent.pendingRun),
711
- }, "streamAgent: forwarded terminal/turn event");
712
- }
713
- yield event;
714
- }
670
+ const result = await agent.session.startTurn(prompt, options);
671
+ turnId = result.turnId;
715
672
  }
716
673
  catch (error) {
717
- finalizeError = error instanceof Error ? error.message : "Agent stream failed";
674
+ const errorMsg = error instanceof Error ? error.message : "Failed to start turn";
718
675
  self.handleStreamEvent(agent, {
719
676
  type: "turn_failed",
720
677
  provider: agent.provider,
721
- error: finalizeError,
678
+ error: errorMsg,
722
679
  });
680
+ self.finalizeForegroundTurn(agent);
723
681
  throw error;
724
682
  }
683
+ pendingRun.started = true;
684
+ agent.activeForegroundTurnId = turnId;
685
+ agent.lifecycle = "running";
686
+ self.touchUpdatedAt(agent);
687
+ self.emitState(agent);
688
+ self.logger.trace({
689
+ agentId,
690
+ lifecycle: agent.lifecycle,
691
+ activeForegroundTurnId: agent.activeForegroundTurnId,
692
+ }, "streamAgent: started");
693
+ // Create a pushable queue for this foreground turn
694
+ const queue = [];
695
+ let queueResolve = null;
696
+ let done = false;
697
+ let resolveSettled;
698
+ const settledPromise = new Promise((resolve) => {
699
+ resolveSettled = resolve;
700
+ });
701
+ waiter = {
702
+ turnId,
703
+ settled: false,
704
+ settledPromise,
705
+ resolveSettled,
706
+ callback: (event) => {
707
+ queue.push(event);
708
+ if (queueResolve) {
709
+ queueResolve();
710
+ queueResolve = null;
711
+ }
712
+ },
713
+ };
714
+ agent.foregroundTurnWaiters.add(waiter);
715
+ try {
716
+ while (!done) {
717
+ while (queue.length > 0) {
718
+ const event = queue.shift();
719
+ yield event;
720
+ if (isTurnTerminalEvent(event)) {
721
+ done = true;
722
+ break;
723
+ }
724
+ }
725
+ if (!done && queue.length === 0) {
726
+ if (waiter.settled) {
727
+ break;
728
+ }
729
+ await new Promise((resolve) => {
730
+ queueResolve = resolve;
731
+ });
732
+ }
733
+ }
734
+ }
725
735
  finally {
726
- await self.refreshRuntimeInfo(agent);
727
- // Ensure we always clear the pending run and emit state when the stream is
728
- // cancelled early (e.g., via .return()) so the UI can exit the cancelling state.
729
- finalize(finalizeError);
736
+ if (waiter) {
737
+ agent.foregroundTurnWaiters.delete(waiter);
738
+ self.settleForegroundTurnWaiter(waiter);
739
+ }
740
+ self.settlePendingForegroundRun(agentId, pendingRun.token);
741
+ if (!agent.activeForegroundTurnId) {
742
+ await self.refreshRuntimeInfo(agent);
743
+ }
730
744
  }
731
745
  })();
732
- agent.pendingRun = streamForwarder;
733
- agent.lifecycle = "running";
734
- // Bump updatedAt when lifecycle changes so downstream consumers can
735
- // deterministically order idle->running transitions.
736
- this.touchUpdatedAt(agent);
737
- self.emitState(agent);
738
- this.logger.trace({
739
- agentId,
740
- lifecycle: agent.lifecycle,
741
- hasPendingRun: Boolean(agent.pendingRun),
742
- }, "streamAgent: started");
743
746
  return streamForwarder;
744
747
  }
748
+ finalizeForegroundTurn(agent) {
749
+ const mutableAgent = agent;
750
+ mutableAgent.activeForegroundTurnId = null;
751
+ const terminalError = mutableAgent.lastError;
752
+ const shouldHoldBusyForReplacement = mutableAgent.pendingReplacement && !terminalError;
753
+ mutableAgent.lifecycle = shouldHoldBusyForReplacement
754
+ ? "running"
755
+ : terminalError
756
+ ? "error"
757
+ : "idle";
758
+ const persistenceHandle = mutableAgent.session.describePersistence() ??
759
+ (mutableAgent.runtimeInfo?.sessionId
760
+ ? { provider: mutableAgent.provider, sessionId: mutableAgent.runtimeInfo.sessionId }
761
+ : null);
762
+ if (persistenceHandle) {
763
+ mutableAgent.persistence = attachPersistenceCwd(persistenceHandle, mutableAgent.cwd);
764
+ }
765
+ this.logger.trace({
766
+ agentId: agent.id,
767
+ lifecycle: mutableAgent.lifecycle,
768
+ terminalError,
769
+ pendingReplacement: mutableAgent.pendingReplacement,
770
+ }, "finalizeForegroundTurn: applying terminal state");
771
+ if (!shouldHoldBusyForReplacement) {
772
+ this.touchUpdatedAt(mutableAgent);
773
+ this.emitState(mutableAgent);
774
+ }
775
+ }
745
776
  replaceAgentRun(agentId, prompt, options) {
746
777
  const snapshot = this.requireAgent(agentId);
747
- if (snapshot.lifecycle !== "running" && !snapshot.pendingRun) {
778
+ if (snapshot.lifecycle !== "running" &&
779
+ !snapshot.activeForegroundTurnId &&
780
+ !this.hasPendingForegroundRun(agentId)) {
748
781
  return this.streamAgent(agentId, prompt, options);
749
782
  }
750
783
  const agent = snapshot;
@@ -763,13 +796,10 @@ export class AgentManager {
763
796
  if (latest) {
764
797
  const latestActive = latest;
765
798
  latestActive.pendingReplacement = false;
766
- const hasForegroundRun = Boolean(latestActive.pendingRun);
767
- const lifecycle = latestActive.lifecycle;
768
- if (!hasForegroundRun && lifecycle === "running") {
799
+ if (!latestActive.activeForegroundTurnId && latestActive.lifecycle === "running") {
769
800
  latestActive.lifecycle = "idle";
770
801
  self.touchUpdatedAt(latestActive);
771
802
  self.emitState(latestActive);
772
- self.flushLiveEventBacklog(latestActive);
773
803
  }
774
804
  }
775
805
  throw error;
@@ -781,10 +811,12 @@ export class AgentManager {
781
811
  if (!snapshot) {
782
812
  throw new Error(`Agent ${agentId} not found`);
783
813
  }
784
- if (snapshot.lifecycle === "running" && !snapshot.pendingReplacement) {
814
+ const pendingRun = this.getPendingForegroundRun(agentId);
815
+ if ((snapshot.lifecycle === "running" || pendingRun?.started) &&
816
+ !snapshot.pendingReplacement) {
785
817
  return;
786
818
  }
787
- if ((!("pendingRun" in snapshot) || !snapshot.pendingRun) && !snapshot.pendingReplacement) {
819
+ if (!snapshot.activeForegroundTurnId && !pendingRun && !snapshot.pendingReplacement) {
788
820
  throw new Error(`Agent ${agentId} has no pending run`);
789
821
  }
790
822
  if (options?.signal?.aborted) {
@@ -829,26 +861,37 @@ export class AgentManager {
829
861
  abortHandler = () => finishErr(createAbortError(options.signal, "wait_for_agent_start aborted"));
830
862
  options.signal.addEventListener("abort", abortHandler, { once: true });
831
863
  }
864
+ const checkCurrentState = () => {
865
+ const current = this.getAgent(agentId);
866
+ if (!current) {
867
+ finishErr(new Error(`Agent ${agentId} not found`));
868
+ return true;
869
+ }
870
+ const currentPendingRun = this.getPendingForegroundRun(agentId);
871
+ if ((current.lifecycle === "running" || currentPendingRun?.started) &&
872
+ !current.pendingReplacement) {
873
+ finishOk();
874
+ return true;
875
+ }
876
+ if (current.lifecycle === "error" && !currentPendingRun?.started) {
877
+ finishErr(new Error(current.lastError ?? `Agent ${agentId} failed to start`));
878
+ return true;
879
+ }
880
+ if (!currentPendingRun &&
881
+ !current.activeForegroundTurnId &&
882
+ !current.pendingReplacement) {
883
+ finishErr(new Error(`Agent ${agentId} run finished before starting`));
884
+ return true;
885
+ }
886
+ return false;
887
+ };
832
888
  unsubscribe = this.subscribe((event) => {
833
- if (event.type === "agent_state") {
834
- if (event.agent.id !== agentId) {
835
- return;
836
- }
837
- if (event.agent.lifecycle === "running" && !event.agent.pendingReplacement) {
838
- finishOk();
839
- return;
840
- }
841
- if (event.agent.lifecycle === "error") {
842
- finishErr(new Error(event.agent.lastError ?? `Agent ${agentId} failed to start`));
843
- return;
844
- }
845
- if ("pendingRun" in event.agent && !event.agent.pendingRun) {
846
- finishErr(new Error(`Agent ${agentId} run finished before starting`));
847
- return;
848
- }
889
+ if (event.type !== "agent_state" || event.agent.id !== agentId) {
849
890
  return;
850
891
  }
851
- }, { agentId, replayState: true });
892
+ checkCurrentState();
893
+ }, { agentId, replayState: false });
894
+ checkCurrentState();
852
895
  });
853
896
  }
854
897
  async respondToPermission(agentId, requestId, response) {
@@ -867,10 +910,11 @@ export class AgentManager {
867
910
  }
868
911
  async cancelAgentRun(agentId) {
869
912
  const agent = this.requireAgent(agentId);
870
- const pendingRun = agent.pendingRun;
871
- const hasForegroundPendingRun = Boolean(pendingRun) && typeof pendingRun?.return === "function";
872
- const isAutonomousRunning = agent.lifecycle === "running" && !hasForegroundPendingRun;
873
- if (!hasForegroundPendingRun && !isAutonomousRunning) {
913
+ const pendingRun = this.getPendingForegroundRun(agentId);
914
+ const foregroundTurnId = agent.activeForegroundTurnId;
915
+ const hasForegroundTurn = Boolean(foregroundTurnId);
916
+ const isAutonomousRunning = agent.lifecycle === "running" && !hasForegroundTurn && !pendingRun;
917
+ if (!hasForegroundTurn && !isAutonomousRunning && !pendingRun) {
874
918
  return false;
875
919
  }
876
920
  try {
@@ -879,20 +923,61 @@ export class AgentManager {
879
923
  catch (error) {
880
924
  this.logger.error({ err: error, agentId }, "Failed to interrupt session");
881
925
  }
882
- if (hasForegroundPendingRun && pendingRun) {
883
- try {
884
- // Await the generator's .return() to ensure the finally block runs
885
- // and pendingRun is properly cleared before we return.
886
- await pendingRun.return(undefined);
926
+ // The interrupt will produce a turn_canceled/turn_failed event via subscribe(),
927
+ // which flows through the session event dispatcher and settles the foreground turn waiter.
928
+ // Wait briefly for the event to propagate if there's an active foreground turn.
929
+ if (foregroundTurnId) {
930
+ const waiter = Array.from(agent.foregroundTurnWaiters).find((candidate) => candidate.turnId === foregroundTurnId);
931
+ const timeout = new Promise((resolve) => setTimeout(resolve, 2000));
932
+ if (waiter) {
933
+ await Promise.race([waiter.settledPromise, timeout]);
887
934
  }
888
- catch (error) {
889
- this.logger.error({ err: error, agentId }, "Failed to cancel run");
890
- throw error;
935
+ else if (agent.activeForegroundTurnId === foregroundTurnId) {
936
+ await Promise.race([
937
+ new Promise((resolve) => {
938
+ const unsubscribe = this.subscribe((event) => {
939
+ if (event.type === "agent_state" &&
940
+ event.agent.id === agentId &&
941
+ !event.agent.activeForegroundTurnId) {
942
+ unsubscribe();
943
+ resolve();
944
+ }
945
+ }, { agentId, replayState: false });
946
+ }),
947
+ timeout,
948
+ ]);
949
+ }
950
+ // The waiter settling wakes up the streamForwarder generator, but its
951
+ // finally block (which deletes the pendingForegroundRun) runs asynchronously.
952
+ // Wait for the pending run to be fully cleaned up so the next streamAgent
953
+ // call doesn't see a stale entry and reject with "already has an active run".
954
+ if (pendingRun && !pendingRun.settled) {
955
+ await Promise.race([pendingRun.settledPromise, timeout]);
956
+ }
957
+ }
958
+ else if (pendingRun) {
959
+ const timeout = new Promise((resolve) => setTimeout(resolve, 2000));
960
+ await Promise.race([pendingRun.settledPromise, timeout]);
961
+ }
962
+ // If the foreground turn is still stuck after the timeout, force-dispatch a
963
+ // synthetic turn_canceled so the normal event pipeline cleans up
964
+ // activeForegroundTurnId, settles waiters, and unblocks the streamForwarder.
965
+ if (foregroundTurnId && agent.activeForegroundTurnId === foregroundTurnId) {
966
+ this.logger.warn({ agentId, foregroundTurnId }, "cancelAgentRun: foreground turn still active after timeout, force-canceling");
967
+ this.dispatchSessionEvent(agent, {
968
+ type: "turn_canceled",
969
+ provider: agent.provider,
970
+ reason: "interrupted",
971
+ turnId: foregroundTurnId,
972
+ });
973
+ // The synthetic event unblocks the streamForwarder generator, whose finally
974
+ // block settles the pending foreground run asynchronously. Wait for it.
975
+ const staleRun = this.getPendingForegroundRun(agentId);
976
+ if (staleRun && !staleRun.settled) {
977
+ await staleRun.settledPromise;
891
978
  }
892
979
  }
893
980
  // Clear any pending permissions that weren't cleaned up by handleStreamEvent.
894
- // Due to microtask ordering, .return() may force the generator to its finally
895
- // block before it consumes the turn_canceled event, skipping our cleanup code.
896
981
  if (agent.pendingPermissions.size > 0) {
897
982
  for (const [requestId] of agent.pendingPermissions) {
898
983
  this.dispatchStream(agent.id, {
@@ -950,7 +1035,7 @@ export class AgentManager {
950
1035
  if (!snapshot) {
951
1036
  throw new Error(`Agent ${agentId} not found`);
952
1037
  }
953
- const hasPendingRun = "pendingRun" in snapshot && Boolean(snapshot.pendingRun);
1038
+ const hasForegroundTurn = Boolean(snapshot.activeForegroundTurnId) || this.hasPendingForegroundRun(agentId);
954
1039
  const immediatePermission = this.peekPendingPermission(snapshot);
955
1040
  if (immediatePermission) {
956
1041
  return {
@@ -960,7 +1045,7 @@ export class AgentManager {
960
1045
  };
961
1046
  }
962
1047
  const initialStatus = snapshot.lifecycle;
963
- const initialBusy = isAgentBusy(initialStatus) || hasPendingRun;
1048
+ const initialBusy = isAgentBusy(initialStatus) || hasForegroundTurn;
964
1049
  const waitForActive = options?.waitForActive ?? false;
965
1050
  if (!waitForActive && !initialBusy) {
966
1051
  return {
@@ -969,7 +1054,7 @@ export class AgentManager {
969
1054
  lastMessage: this.getLastAssistantMessage(agentId),
970
1055
  };
971
1056
  }
972
- if (waitForActive && !initialBusy && !hasPendingRun) {
1057
+ if (waitForActive && !initialBusy && !hasForegroundTurn) {
973
1058
  return {
974
1059
  status: initialStatus,
975
1060
  permission: null,
@@ -987,7 +1072,7 @@ export class AgentManager {
987
1072
  return;
988
1073
  }
989
1074
  let currentStatus = initialStatus;
990
- let hasStarted = initialBusy || hasPendingRun;
1075
+ let hasStarted = initialBusy || hasForegroundTurn;
991
1076
  let terminalStatusOverride = null;
992
1077
  // Bug #3 Fix: Declare unsubscribe and abortHandler upfront so cleanup can reference them
993
1078
  let unsubscribe = null;
@@ -1103,7 +1188,9 @@ export class AgentManager {
1103
1188
  currentModeId: null,
1104
1189
  pendingPermissions: new Map(),
1105
1190
  pendingReplacement: false,
1106
- pendingRun: null,
1191
+ activeForegroundTurnId: null,
1192
+ foregroundTurnWaiters: new Set(),
1193
+ unsubscribeSession: null,
1107
1194
  timeline: initialTimeline,
1108
1195
  timelineRows: initialTimelineRows,
1109
1196
  timelineEpoch: options?.timelineEpoch ?? randomUUID(),
@@ -1137,9 +1224,77 @@ export class AgentManager {
1137
1224
  managed.lifecycle = "idle";
1138
1225
  await this.persistSnapshot(managed);
1139
1226
  this.emitState(managed);
1140
- this.startLiveEventPump(managed);
1227
+ this.subscribeToSession(managed);
1141
1228
  return { ...managed };
1142
1229
  }
1230
+ subscribeToSession(agent) {
1231
+ if (agent.unsubscribeSession) {
1232
+ return;
1233
+ }
1234
+ const agentId = agent.id;
1235
+ const unsubscribe = agent.session.subscribe((event) => {
1236
+ const current = this.agents.get(agentId);
1237
+ if (!current) {
1238
+ return;
1239
+ }
1240
+ this.dispatchSessionEvent(current, event);
1241
+ });
1242
+ agent.unsubscribeSession = unsubscribe;
1243
+ }
1244
+ dispatchSessionEvent(agent, event) {
1245
+ const turnId = event.turnId;
1246
+ const matchingWaiters = turnId == null
1247
+ ? []
1248
+ : Array.from(agent.foregroundTurnWaiters).filter((waiter) => waiter.turnId === turnId && !waiter.settled);
1249
+ this.handleStreamEvent(agent, event);
1250
+ for (const waiter of matchingWaiters) {
1251
+ waiter.callback(event);
1252
+ if (isTurnTerminalEvent(event)) {
1253
+ this.settleForegroundTurnWaiter(waiter);
1254
+ }
1255
+ }
1256
+ }
1257
+ settleForegroundTurnWaiter(waiter) {
1258
+ if (waiter.settled) {
1259
+ return;
1260
+ }
1261
+ waiter.settled = true;
1262
+ waiter.resolveSettled();
1263
+ }
1264
+ createPendingForegroundRun() {
1265
+ let resolveSettled;
1266
+ const settledPromise = new Promise((resolve) => {
1267
+ resolveSettled = resolve;
1268
+ });
1269
+ return {
1270
+ token: randomUUID(),
1271
+ started: false,
1272
+ settled: false,
1273
+ settledPromise,
1274
+ resolveSettled,
1275
+ };
1276
+ }
1277
+ getPendingForegroundRun(agentId) {
1278
+ return this.pendingForegroundRuns.get(agentId) ?? null;
1279
+ }
1280
+ hasPendingForegroundRun(agentId) {
1281
+ return this.pendingForegroundRuns.has(agentId);
1282
+ }
1283
+ settlePendingForegroundRun(agentId, token) {
1284
+ const pendingRun = this.pendingForegroundRuns.get(agentId);
1285
+ if (!pendingRun) {
1286
+ return;
1287
+ }
1288
+ if (token && pendingRun.token !== token) {
1289
+ return;
1290
+ }
1291
+ this.pendingForegroundRuns.delete(agentId);
1292
+ if (pendingRun.settled) {
1293
+ return;
1294
+ }
1295
+ pendingRun.settled = true;
1296
+ pendingRun.resolveSettled();
1297
+ }
1143
1298
  async resolveInitialPersistedTitle(agentId, config) {
1144
1299
  const existing = await this.registry?.get(agentId);
1145
1300
  if (existing) {
@@ -1256,6 +1411,8 @@ export class AgentManager {
1256
1411
  }
1257
1412
  }
1258
1413
  handleStreamEvent(agent, event, options) {
1414
+ const eventTurnId = event.turnId;
1415
+ const isForegroundEvent = Boolean(eventTurnId && agent.activeForegroundTurnId === eventTurnId);
1259
1416
  // Only update timestamp for live events, not history replay
1260
1417
  if (!options?.fromHistory) {
1261
1418
  this.touchUpdatedAt(agent);
@@ -1263,8 +1420,6 @@ export class AgentManager {
1263
1420
  let timelineRow = null;
1264
1421
  switch (event.type) {
1265
1422
  case "thread_started":
1266
- // Update persistence with the new session ID from the provider.
1267
- // persistence.sessionId is the single source of truth for session identity.
1268
1423
  {
1269
1424
  const previousSessionId = agent.persistence?.sessionId ?? null;
1270
1425
  const handle = agent.session.describePersistence();
@@ -1274,13 +1429,11 @@ export class AgentManager {
1274
1429
  this.emitState(agent);
1275
1430
  }
1276
1431
  }
1432
+ void this.refreshRuntimeInfo(agent);
1277
1433
  }
1278
1434
  break;
1279
1435
  case "timeline":
1280
1436
  // Skip provider-replayed user_message items during history hydration.
1281
- // These are already canonically recorded by recordUserMessage() and replaying them would
1282
- // create duplicates. Match by messageId (not text) to avoid dropping legitimate
1283
- // provider-origin messages that happen to reuse the same text.
1284
1437
  if (options?.fromHistory && event.item.type === "user_message") {
1285
1438
  const eventMessageId = normalizeMessageId(event.item.messageId);
1286
1439
  if (eventMessageId) {
@@ -1290,8 +1443,25 @@ export class AgentManager {
1290
1443
  }
1291
1444
  }
1292
1445
  }
1293
- if (this.shouldSuppressLiveUserMessageEcho(agent, event, options)) {
1294
- break;
1446
+ // Suppress user_message echoes for the active foreground turn —
1447
+ // these are already recorded by recordUserMessage().
1448
+ if (!options?.fromHistory &&
1449
+ event.item.type === "user_message" &&
1450
+ isForegroundEvent) {
1451
+ const eventMessageId = normalizeMessageId(event.item.messageId);
1452
+ const eventText = event.item.text;
1453
+ if (eventMessageId) {
1454
+ const alreadyRecorded = agent.timelineRows.some((row) => {
1455
+ if (row.item.type !== "user_message") {
1456
+ return false;
1457
+ }
1458
+ const rowMessageId = normalizeMessageId(row.item.messageId);
1459
+ return rowMessageId === eventMessageId && row.item.text === eventText;
1460
+ });
1461
+ if (alreadyRecorded) {
1462
+ break;
1463
+ }
1464
+ }
1295
1465
  }
1296
1466
  timelineRow = this.recordTimeline(agent, event.item);
1297
1467
  if (!options?.fromHistory && event.item.type === "user_message") {
@@ -1303,18 +1473,33 @@ export class AgentManager {
1303
1473
  this.logger.trace({
1304
1474
  agentId: agent.id,
1305
1475
  lifecycle: agent.lifecycle,
1306
- hasPendingRun: Boolean(agent.pendingRun),
1476
+ activeForegroundTurnId: agent.activeForegroundTurnId,
1477
+ eventTurnId,
1307
1478
  }, "handleStreamEvent: turn_completed");
1308
1479
  agent.lastUsage = event.usage;
1309
1480
  agent.lastError = undefined;
1310
- if (!agent.pendingRun && agent.lifecycle !== "idle") {
1481
+ // For autonomous turns (not foreground), transition to idle
1482
+ // unless a replacement is pending (avoid idle flash during replace)
1483
+ if (!isForegroundEvent && agent.lifecycle !== "idle" && !agent.pendingReplacement) {
1311
1484
  agent.lifecycle = "idle";
1312
1485
  this.emitState(agent);
1313
1486
  }
1314
1487
  void this.refreshRuntimeInfo(agent);
1315
1488
  break;
1316
1489
  case "turn_failed":
1317
- agent.lifecycle = "error";
1490
+ this.logger.trace({
1491
+ agentId: agent.id,
1492
+ lifecycle: agent.lifecycle,
1493
+ activeForegroundTurnId: agent.activeForegroundTurnId,
1494
+ eventTurnId,
1495
+ error: event.error,
1496
+ code: event.code,
1497
+ diagnostic: event.diagnostic,
1498
+ }, "handleStreamEvent: turn_failed");
1499
+ // For autonomous turns, set error state directly
1500
+ if (!isForegroundEvent) {
1501
+ agent.lifecycle = "error";
1502
+ }
1318
1503
  agent.lastError = event.error;
1319
1504
  this.appendSystemErrorTimelineMessage(agent, event.provider, this.formatTurnFailedMessage(event), options);
1320
1505
  for (const [requestId] of agent.pendingPermissions) {
@@ -1328,15 +1513,20 @@ export class AgentManager {
1328
1513
  });
1329
1514
  }
1330
1515
  }
1331
- this.emitState(agent);
1516
+ if (!isForegroundEvent) {
1517
+ this.emitState(agent);
1518
+ }
1332
1519
  break;
1333
1520
  case "turn_canceled":
1334
1521
  this.logger.trace({
1335
1522
  agentId: agent.id,
1336
1523
  lifecycle: agent.lifecycle,
1337
- hasPendingRun: Boolean(agent.pendingRun),
1524
+ activeForegroundTurnId: agent.activeForegroundTurnId,
1525
+ eventTurnId,
1338
1526
  }, "handleStreamEvent: turn_canceled");
1339
- if (!agent.pendingRun) {
1527
+ // For autonomous turns, transition to idle
1528
+ // unless a replacement is pending (avoid idle flash during replace)
1529
+ if (!isForegroundEvent && !agent.pendingReplacement) {
1340
1530
  agent.lifecycle = "idle";
1341
1531
  }
1342
1532
  agent.lastError = undefined;
@@ -1351,15 +1541,19 @@ export class AgentManager {
1351
1541
  });
1352
1542
  }
1353
1543
  }
1354
- this.emitState(agent);
1544
+ if (!isForegroundEvent) {
1545
+ this.emitState(agent);
1546
+ }
1355
1547
  break;
1356
1548
  case "turn_started":
1357
1549
  this.logger.trace({
1358
1550
  agentId: agent.id,
1359
1551
  lifecycle: agent.lifecycle,
1360
- hasPendingRun: Boolean(agent.pendingRun),
1552
+ activeForegroundTurnId: agent.activeForegroundTurnId,
1553
+ eventTurnId,
1361
1554
  }, "handleStreamEvent: turn_started");
1362
- if (!agent.pendingRun) {
1555
+ // For autonomous turn_started (no foreground match), set running
1556
+ if (!isForegroundEvent) {
1363
1557
  agent.lifecycle = "running";
1364
1558
  this.emitState(agent);
1365
1559
  }
@@ -1381,6 +1575,9 @@ export class AgentManager {
1381
1575
  default:
1382
1576
  break;
1383
1577
  }
1578
+ if (!options?.fromHistory && isForegroundEvent && isTurnTerminalEvent(event)) {
1579
+ this.finalizeForegroundTurn(agent);
1580
+ }
1384
1581
  // Skip dispatching individual stream events during history replay.
1385
1582
  if (!options?.fromHistory) {
1386
1583
  this.dispatchStream(agent.id, event, timelineRow
@@ -1428,27 +1625,6 @@ export class AgentManager {
1428
1625
  }
1429
1626
  return parts.join("\n\n");
1430
1627
  }
1431
- shouldSuppressLiveUserMessageEcho(agent, event, options) {
1432
- if (options?.fromHistory || event.type !== "timeline") {
1433
- return false;
1434
- }
1435
- if (event.item.type !== "user_message" || !agent.pendingRun) {
1436
- return false;
1437
- }
1438
- const eventMessageId = normalizeMessageId(event.item.messageId);
1439
- const eventText = event.item.text;
1440
- if (!eventMessageId) {
1441
- return false;
1442
- }
1443
- return agent.timelineRows.some((row) => {
1444
- const rowItem = row.item;
1445
- if (rowItem.type !== "user_message") {
1446
- return false;
1447
- }
1448
- const rowMessageId = normalizeMessageId(rowItem.messageId);
1449
- return rowMessageId === eventMessageId && rowItem.text === eventText;
1450
- });
1451
- }
1452
1628
  recordTimeline(agent, item) {
1453
1629
  const timelineState = this.ensureTimelineState(agent);
1454
1630
  const row = {
@@ -1571,7 +1747,7 @@ export class AgentManager {
1571
1747
  subscriber.callback(event);
1572
1748
  }
1573
1749
  }
1574
- async normalizeConfig(config, _options) {
1750
+ async normalizeConfig(config) {
1575
1751
  const normalized = { ...config };
1576
1752
  // Always resolve cwd to absolute path for consistent history file lookup
1577
1753
  if (normalized.cwd) {
@@ -1596,11 +1772,17 @@ export class AgentManager {
1596
1772
  }
1597
1773
  if (typeof normalized.model === "string") {
1598
1774
  const trimmed = normalized.model.trim();
1599
- const normalizedId = trimmed.toLowerCase();
1600
- normalized.model = trimmed.length > 0 && normalizedId !== "default" ? trimmed : undefined;
1775
+ normalized.model = trimmed.length > 0 ? trimmed : undefined;
1601
1776
  }
1602
1777
  return normalized;
1603
1778
  }
1779
+ buildLaunchContext(agentId) {
1780
+ return {
1781
+ env: {
1782
+ PASEO_AGENT_ID: agentId,
1783
+ },
1784
+ };
1785
+ }
1604
1786
  requireClient(provider) {
1605
1787
  const client = this.clients.get(provider);
1606
1788
  if (!client) {
@@ -1616,141 +1798,5 @@ export class AgentManager {
1616
1798
  }
1617
1799
  return agent;
1618
1800
  }
1619
- startLiveEventPump(agent) {
1620
- if (!supportsLiveEventStream(agent.session)) {
1621
- return;
1622
- }
1623
- if (this.liveEventPumps.has(agent.id)) {
1624
- return;
1625
- }
1626
- const pump = (async () => {
1627
- while (true) {
1628
- const current = this.agents.get(agent.id);
1629
- if (!current) {
1630
- return;
1631
- }
1632
- if (!supportsLiveEventStream(current.session)) {
1633
- return;
1634
- }
1635
- try {
1636
- for await (const event of current.session.streamLiveEvents()) {
1637
- const latest = this.agents.get(agent.id);
1638
- if (!latest) {
1639
- return;
1640
- }
1641
- // Keep consuming provider events even during an active foreground run,
1642
- // then replay them immediately once that run settles.
1643
- if (latest.pendingRun) {
1644
- this.logger.trace({
1645
- agentId: latest.id,
1646
- eventType: event.type,
1647
- backlogSize: (this.liveEventBacklog.get(latest.id)?.length ?? 0) + 1,
1648
- }, "Live event pump: queued event because pendingRun is active");
1649
- this.enqueueLiveEvent(latest.id, event);
1650
- continue;
1651
- }
1652
- this.flushLiveEventBacklog(latest);
1653
- this.handleStreamEvent(latest, event);
1654
- }
1655
- this.logger.warn({ agentId: agent.id }, "Live event pump stream ended; restarting");
1656
- }
1657
- catch (error) {
1658
- this.logger.warn({ err: error, agentId: agent.id }, "Live event pump failed");
1659
- }
1660
- // Keep pump alive unless the agent is gone.
1661
- await new Promise((resolve) => setTimeout(resolve, 250));
1662
- const latest = this.agents.get(agent.id);
1663
- if (!latest) {
1664
- return;
1665
- }
1666
- if (!latest.pendingRun) {
1667
- this.flushLiveEventBacklog(latest);
1668
- }
1669
- }
1670
- })();
1671
- this.liveEventPumps.set(agent.id, pump);
1672
- pump.finally(() => {
1673
- const current = this.liveEventPumps.get(agent.id);
1674
- if (current === pump) {
1675
- this.liveEventPumps.delete(agent.id);
1676
- }
1677
- });
1678
- }
1679
- enqueueLiveEvent(agentId, event) {
1680
- const existing = this.liveEventBacklog.get(agentId);
1681
- if (existing) {
1682
- existing.push(event);
1683
- return;
1684
- }
1685
- this.liveEventBacklog.set(agentId, [event]);
1686
- }
1687
- clearLiveEventBacklogFlushTimer(agentId) {
1688
- const timer = this.liveEventBacklogFlushTimers.get(agentId);
1689
- if (!timer) {
1690
- return;
1691
- }
1692
- clearTimeout(timer);
1693
- this.liveEventBacklogFlushTimers.delete(agentId);
1694
- }
1695
- scheduleLiveEventBacklogFlush(agentId, delayMs) {
1696
- if (this.liveEventBacklogFlushTimers.has(agentId)) {
1697
- return;
1698
- }
1699
- const timer = setTimeout(() => {
1700
- this.liveEventBacklogFlushTimers.delete(agentId);
1701
- const latest = this.agents.get(agentId);
1702
- if (!latest) {
1703
- return;
1704
- }
1705
- if (latest.pendingRun) {
1706
- this.scheduleLiveEventBacklogFlush(agentId, LIVE_BACKLOG_TERMINAL_REPLAY_DELAY_MS);
1707
- return;
1708
- }
1709
- this.flushLiveEventBacklog(latest);
1710
- }, delayMs);
1711
- this.liveEventBacklogFlushTimers.set(agentId, timer);
1712
- }
1713
- flushLiveEventBacklog(agent) {
1714
- if (agent.pendingRun) {
1715
- return;
1716
- }
1717
- const pending = this.liveEventBacklog.get(agent.id);
1718
- if (!pending || pending.length === 0) {
1719
- return;
1720
- }
1721
- this.clearLiveEventBacklogFlushTimer(agent.id);
1722
- this.liveEventBacklog.delete(agent.id);
1723
- const immediate = [];
1724
- const deferred = [];
1725
- let sawTurnStarted = false;
1726
- let deferRemainder = false;
1727
- for (const event of pending) {
1728
- if (!deferRemainder && sawTurnStarted && isTurnTerminalEvent(event)) {
1729
- deferRemainder = true;
1730
- }
1731
- if (deferRemainder) {
1732
- deferred.push(event);
1733
- continue;
1734
- }
1735
- immediate.push(event);
1736
- if (event.type === "turn_started") {
1737
- sawTurnStarted = true;
1738
- }
1739
- }
1740
- for (const event of immediate) {
1741
- this.handleStreamEvent(agent, event);
1742
- }
1743
- if (deferred.length === 0) {
1744
- return;
1745
- }
1746
- const existing = this.liveEventBacklog.get(agent.id);
1747
- if (existing && existing.length > 0) {
1748
- this.liveEventBacklog.set(agent.id, [...deferred, ...existing]);
1749
- }
1750
- else {
1751
- this.liveEventBacklog.set(agent.id, deferred);
1752
- }
1753
- this.scheduleLiveEventBacklogFlush(agent.id, LIVE_BACKLOG_TERMINAL_REPLAY_DELAY_MS);
1754
- }
1755
1801
  }
1756
1802
  //# sourceMappingURL=agent-manager.js.map