@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.
- package/dist/server/server/agent/activity-curator.d.ts.map +1 -1
- package/dist/server/server/agent/activity-curator.js +15 -1
- package/dist/server/server/agent/activity-curator.js.map +1 -1
- package/dist/server/server/agent/agent-management-mcp.d.ts.map +1 -1
- package/dist/server/server/agent/agent-management-mcp.js +2 -4
- package/dist/server/server/agent/agent-management-mcp.js.map +1 -1
- package/dist/server/server/agent/agent-manager.d.ts +25 -14
- package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
- package/dist/server/server/agent/agent-manager.js +383 -337
- package/dist/server/server/agent/agent-manager.js.map +1 -1
- package/dist/server/server/agent/agent-sdk-types.d.ts +16 -14
- package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
- package/dist/server/server/agent/mcp-server.d.ts.map +1 -1
- package/dist/server/server/agent/mcp-server.js +2 -4
- package/dist/server/server/agent/mcp-server.js.map +1 -1
- package/dist/server/server/agent/provider-launch-config.d.ts.map +1 -1
- package/dist/server/server/agent/provider-launch-config.js +15 -1
- package/dist/server/server/agent/provider-launch-config.js.map +1 -1
- package/dist/server/server/agent/provider-manifest.d.ts +1 -1
- package/dist/server/server/agent/provider-manifest.d.ts.map +1 -1
- package/dist/server/server/agent/provider-manifest.js +4 -4
- package/dist/server/server/agent/provider-manifest.js.map +1 -1
- package/dist/server/server/agent/providers/claude/partial-json.js +3 -3
- package/dist/server/server/agent/providers/claude/partial-json.js.map +1 -1
- package/dist/server/server/agent/providers/claude/sdk-model-resolver.d.ts +11 -0
- package/dist/server/server/agent/providers/claude/sdk-model-resolver.d.ts.map +1 -0
- package/dist/server/server/agent/providers/claude/sdk-model-resolver.js +104 -0
- package/dist/server/server/agent/providers/claude/sdk-model-resolver.js.map +1 -0
- package/dist/server/server/agent/providers/claude-agent.d.ts +4 -13
- package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/claude-agent.js +382 -453
- package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +5 -3
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/codex-app-server-agent.js +174 -151
- package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
- package/dist/server/server/agent/providers/opencode-agent.d.ts +3 -3
- package/dist/server/server/agent/providers/opencode-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/opencode-agent.js +106 -25
- package/dist/server/server/agent/providers/opencode-agent.js.map +1 -1
- package/dist/server/server/agent/providers/test-utils/session-stream-adapter.d.ts +3 -0
- package/dist/server/server/agent/providers/test-utils/session-stream-adapter.d.ts.map +1 -0
- package/dist/server/server/agent/providers/test-utils/session-stream-adapter.js +57 -0
- package/dist/server/server/agent/providers/test-utils/session-stream-adapter.js.map +1 -0
- package/dist/server/server/config.d.ts.map +1 -1
- package/dist/server/server/config.js +1 -5
- package/dist/server/server/config.js.map +1 -1
- package/dist/server/server/session.d.ts.map +1 -1
- package/dist/server/server/session.js +14 -12
- package/dist/server/server/session.js.map +1 -1
- package/dist/server/shared/tool-call-display.js +1 -1
- package/dist/server/shared/tool-call-display.js.map +1 -1
- package/dist/src/server/agent/activity-curator.js +15 -1
- package/dist/src/server/agent/activity-curator.js.map +1 -1
- package/dist/src/server/agent/agent-manager.js +383 -337
- package/dist/src/server/agent/agent-manager.js.map +1 -1
- package/dist/src/server/agent/mcp-server.js +2 -4
- package/dist/src/server/agent/mcp-server.js.map +1 -1
- package/dist/src/server/agent/provider-launch-config.js +15 -1
- package/dist/src/server/agent/provider-launch-config.js.map +1 -1
- package/dist/src/server/agent/provider-manifest.js +4 -4
- package/dist/src/server/agent/provider-manifest.js.map +1 -1
- package/dist/src/server/agent/providers/claude/partial-json.js +3 -3
- package/dist/src/server/agent/providers/claude/partial-json.js.map +1 -1
- package/dist/src/server/agent/providers/claude/sdk-model-resolver.js +104 -0
- package/dist/src/server/agent/providers/claude/sdk-model-resolver.js.map +1 -0
- package/dist/src/server/agent/providers/claude-agent.js +382 -453
- package/dist/src/server/agent/providers/claude-agent.js.map +1 -1
- package/dist/src/server/agent/providers/codex-app-server-agent.js +174 -151
- package/dist/src/server/agent/providers/codex-app-server-agent.js.map +1 -1
- package/dist/src/server/agent/providers/opencode-agent.js +106 -25
- package/dist/src/server/agent/providers/opencode-agent.js.map +1 -1
- package/dist/src/server/config.js +1 -5
- package/dist/src/server/config.js.map +1 -1
- package/dist/src/server/session.js +14 -12
- package/dist/src/server/session.js.map +1 -1
- package/dist/src/shared/tool-call-display.js +1 -1
- package/dist/src/shared/tool-call-display.js.map +1 -1
- package/package.json +4 -5
- package/agent-prompt.md +0 -339
- package/dist/server/server/agent/providers/claude/model-catalog.d.ts +0 -29
- package/dist/server/server/agent/providers/claude/model-catalog.d.ts.map +0 -1
- package/dist/server/server/agent/providers/claude/model-catalog.js +0 -70
- package/dist/server/server/agent/providers/claude/model-catalog.js.map +0 -1
- package/dist/server/server/agent/system-prompt.d.ts +0 -3
- package/dist/server/server/agent/system-prompt.d.ts.map +0 -1
- package/dist/server/server/agent/system-prompt.js +0 -19
- package/dist/server/server/agent/system-prompt.js.map +0 -1
- package/dist/server/server/terminal-mcp/index.d.ts +0 -4
- package/dist/server/server/terminal-mcp/index.d.ts.map +0 -1
- package/dist/server/server/terminal-mcp/index.js +0 -3
- package/dist/server/server/terminal-mcp/index.js.map +0 -1
- package/dist/server/server/terminal-mcp/server.d.ts +0 -10
- package/dist/server/server/terminal-mcp/server.d.ts.map +0 -1
- package/dist/server/server/terminal-mcp/server.js +0 -209
- package/dist/server/server/terminal-mcp/server.js.map +0 -1
- package/dist/server/server/terminal-mcp/terminal-manager.d.ts +0 -123
- package/dist/server/server/terminal-mcp/terminal-manager.d.ts.map +0 -1
- package/dist/server/server/terminal-mcp/terminal-manager.js +0 -339
- package/dist/server/server/terminal-mcp/terminal-manager.js.map +0 -1
- package/dist/server/server/terminal-mcp/tmux.d.ts +0 -207
- package/dist/server/server/terminal-mcp/tmux.d.ts.map +0 -1
- package/dist/server/server/terminal-mcp/tmux.js +0 -821
- package/dist/server/server/terminal-mcp/tmux.js.map +0 -1
- package/dist/src/server/agent/providers/claude/model-catalog.js +0 -70
- 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
|
-
|
|
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 (
|
|
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
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
652
|
+
if (existingAgent.activeForegroundTurnId || this.hasPendingForegroundRun(agentId)) {
|
|
631
653
|
this.logger.trace({
|
|
632
654
|
agentId,
|
|
633
655
|
lifecycle: existingAgent.lifecycle,
|
|
634
|
-
|
|
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
|
-
|
|
665
|
+
const pendingRun = self.createPendingForegroundRun();
|
|
666
|
+
self.pendingForegroundRuns.set(agentId, pendingRun);
|
|
667
|
+
let turnId;
|
|
668
|
+
let waiter = null;
|
|
699
669
|
try {
|
|
700
|
-
|
|
701
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
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" &&
|
|
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
|
-
|
|
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
|
-
|
|
814
|
+
const pendingRun = this.getPendingForegroundRun(agentId);
|
|
815
|
+
if ((snapshot.lifecycle === "running" || pendingRun?.started) &&
|
|
816
|
+
!snapshot.pendingReplacement) {
|
|
785
817
|
return;
|
|
786
818
|
}
|
|
787
|
-
if (
|
|
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
|
|
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
|
-
|
|
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 =
|
|
871
|
-
const
|
|
872
|
-
const
|
|
873
|
-
|
|
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
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
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
|
-
|
|
889
|
-
|
|
890
|
-
|
|
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
|
|
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) ||
|
|
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 && !
|
|
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 ||
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
1294
|
-
|
|
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
|
-
|
|
1476
|
+
activeForegroundTurnId: agent.activeForegroundTurnId,
|
|
1477
|
+
eventTurnId,
|
|
1307
1478
|
}, "handleStreamEvent: turn_completed");
|
|
1308
1479
|
agent.lastUsage = event.usage;
|
|
1309
1480
|
agent.lastError = undefined;
|
|
1310
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1524
|
+
activeForegroundTurnId: agent.activeForegroundTurnId,
|
|
1525
|
+
eventTurnId,
|
|
1338
1526
|
}, "handleStreamEvent: turn_canceled");
|
|
1339
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1552
|
+
activeForegroundTurnId: agent.activeForegroundTurnId,
|
|
1553
|
+
eventTurnId,
|
|
1361
1554
|
}, "handleStreamEvent: turn_started");
|
|
1362
|
-
|
|
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
|
|
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
|
-
|
|
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
|