@getpaseo/server 0.1.59 → 0.1.61
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/scripts/dev-runner.js +26 -7
- package/dist/scripts/dev-runner.js.map +1 -1
- package/dist/server/client/daemon-client-runtime-metrics.d.ts +39 -0
- package/dist/server/client/daemon-client-runtime-metrics.d.ts.map +1 -0
- package/dist/server/client/daemon-client-runtime-metrics.js +173 -0
- package/dist/server/client/daemon-client-runtime-metrics.js.map +1 -0
- package/dist/server/client/daemon-client.d.ts +58 -9
- package/dist/server/client/daemon-client.d.ts.map +1 -1
- package/dist/server/client/daemon-client.js +151 -10
- package/dist/server/client/daemon-client.js.map +1 -1
- package/dist/server/server/agent/agent-manager.d.ts +55 -48
- package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
- package/dist/server/server/agent/agent-manager.js +541 -331
- package/dist/server/server/agent/agent-manager.js.map +1 -1
- package/dist/server/server/agent/agent-metadata-generator.d.ts +3 -2
- package/dist/server/server/agent/agent-metadata-generator.d.ts.map +1 -1
- package/dist/server/server/agent/agent-metadata-generator.js +31 -16
- package/dist/server/server/agent/agent-metadata-generator.js.map +1 -1
- package/dist/server/server/agent/agent-projections.d.ts +2 -1
- package/dist/server/server/agent/agent-projections.d.ts.map +1 -1
- package/dist/server/server/agent/agent-projections.js +29 -1
- package/dist/server/server/agent/agent-projections.js.map +1 -1
- package/dist/server/server/agent/agent-sdk-types.d.ts +9 -5
- package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
- package/dist/server/server/agent/agent-sdk-types.js.map +1 -1
- package/dist/server/server/agent/agent-storage.d.ts +76 -69
- package/dist/server/server/agent/agent-storage.d.ts.map +1 -1
- package/dist/server/server/agent/agent-storage.js +13 -6
- package/dist/server/server/agent/agent-storage.js.map +1 -1
- package/dist/server/server/agent/agent-stream-coalescer.d.ts +41 -0
- package/dist/server/server/agent/agent-stream-coalescer.d.ts.map +1 -0
- package/dist/server/server/agent/agent-stream-coalescer.js +166 -0
- package/dist/server/server/agent/agent-stream-coalescer.js.map +1 -0
- package/dist/server/server/agent/agent-timeline-store-types.d.ts +54 -0
- package/dist/server/server/agent/agent-timeline-store-types.d.ts.map +1 -0
- package/dist/server/server/agent/agent-timeline-store-types.js +2 -0
- package/dist/server/server/agent/agent-timeline-store-types.js.map +1 -0
- package/dist/server/server/agent/agent-timeline-store.d.ts +32 -0
- package/dist/server/server/agent/agent-timeline-store.d.ts.map +1 -0
- package/dist/server/server/agent/agent-timeline-store.js +245 -0
- package/dist/server/server/agent/agent-timeline-store.js.map +1 -0
- package/dist/server/server/agent/mcp-server.d.ts +12 -1
- package/dist/server/server/agent/mcp-server.d.ts.map +1 -1
- package/dist/server/server/agent/mcp-server.js +276 -65
- package/dist/server/server/agent/mcp-server.js.map +1 -1
- package/dist/server/server/agent/mcp-shared.d.ts +196 -152
- package/dist/server/server/agent/mcp-shared.d.ts.map +1 -1
- package/dist/server/server/agent/mcp-shared.js +40 -42
- package/dist/server/server/agent/mcp-shared.js.map +1 -1
- package/dist/server/server/agent/model-resolver.d.ts.map +1 -1
- package/dist/server/server/agent/model-resolver.js +3 -1
- package/dist/server/server/agent/model-resolver.js.map +1 -1
- package/dist/server/server/agent/prompt-attachments.d.ts +6 -0
- package/dist/server/server/agent/prompt-attachments.d.ts.map +1 -0
- package/dist/server/server/agent/prompt-attachments.js +31 -0
- package/dist/server/server/agent/prompt-attachments.js.map +1 -0
- package/dist/server/server/agent/provider-launch-config.d.ts +78 -8
- package/dist/server/server/agent/provider-launch-config.d.ts.map +1 -1
- package/dist/server/server/agent/provider-launch-config.js +35 -0
- package/dist/server/server/agent/provider-launch-config.js.map +1 -1
- package/dist/server/server/agent/provider-manifest.d.ts +1 -0
- package/dist/server/server/agent/provider-manifest.d.ts.map +1 -1
- package/dist/server/server/agent/provider-manifest.js +22 -1
- package/dist/server/server/agent/provider-manifest.js.map +1 -1
- package/dist/server/server/agent/provider-registry.d.ts +5 -2
- package/dist/server/server/agent/provider-registry.d.ts.map +1 -1
- package/dist/server/server/agent/provider-registry.js +61 -17
- package/dist/server/server/agent/provider-registry.js.map +1 -1
- package/dist/server/server/agent/provider-snapshot-manager.d.ts +17 -5
- package/dist/server/server/agent/provider-snapshot-manager.d.ts.map +1 -1
- package/dist/server/server/agent/provider-snapshot-manager.js +150 -61
- package/dist/server/server/agent/provider-snapshot-manager.js.map +1 -1
- package/dist/server/server/agent/providers/acp-agent.d.ts +8 -4
- package/dist/server/server/agent/providers/acp-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/acp-agent.js +73 -8
- package/dist/server/server/agent/providers/acp-agent.js.map +1 -1
- package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +2 -2
- package/dist/server/server/agent/providers/claude-agent.d.ts +1 -1
- package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/claude-agent.js +8 -7
- package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +37 -4
- 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 +61 -31
- package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
- package/dist/server/server/agent/providers/copilot-acp-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/copilot-acp-agent.js +3 -2
- package/dist/server/server/agent/providers/copilot-acp-agent.js.map +1 -1
- package/dist/server/server/agent/providers/mock-load-test-agent.d.ts +64 -0
- package/dist/server/server/agent/providers/mock-load-test-agent.d.ts.map +1 -0
- package/dist/server/server/agent/providers/mock-load-test-agent.js +585 -0
- package/dist/server/server/agent/providers/mock-load-test-agent.js.map +1 -0
- package/dist/server/server/agent/providers/opencode-agent.d.ts +19 -4
- package/dist/server/server/agent/providers/opencode-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/opencode-agent.js +227 -118
- package/dist/server/server/agent/providers/opencode-agent.js.map +1 -1
- package/dist/server/server/agent/providers/pi-direct-agent.d.ts +69 -0
- package/dist/server/server/agent/providers/pi-direct-agent.d.ts.map +1 -0
- package/dist/server/server/agent/providers/pi-direct-agent.js +1171 -0
- package/dist/server/server/agent/providers/pi-direct-agent.js.map +1 -0
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +7 -4
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts.map +1 -1
- package/dist/server/server/agent-attention-policy.d.ts +13 -13
- package/dist/server/server/agent-attention-policy.d.ts.map +1 -1
- package/dist/server/server/agent-attention-policy.js +20 -36
- package/dist/server/server/agent-attention-policy.js.map +1 -1
- package/dist/server/server/bootstrap.d.ts +6 -0
- package/dist/server/server/bootstrap.d.ts.map +1 -1
- package/dist/server/server/bootstrap.js +113 -11
- package/dist/server/server/bootstrap.js.map +1 -1
- package/dist/server/server/chat/chat-rpc-schemas.d.ts +44 -44
- package/dist/server/server/chat/chat-types.d.ts +6 -6
- package/dist/server/server/checkout-diff-manager.d.ts +0 -1
- package/dist/server/server/checkout-diff-manager.d.ts.map +1 -1
- package/dist/server/server/checkout-diff-manager.js +6 -4
- package/dist/server/server/checkout-diff-manager.js.map +1 -1
- package/dist/server/server/config.d.ts.map +1 -1
- package/dist/server/server/config.js +1 -0
- package/dist/server/server/config.js.map +1 -1
- package/dist/server/server/file-explorer/service.d.ts.map +1 -1
- package/dist/server/server/file-explorer/service.js +2 -1
- package/dist/server/server/file-explorer/service.js.map +1 -1
- package/dist/server/server/loop/rpc-schemas.d.ts +392 -392
- package/dist/server/server/loop-service.d.ts +52 -52
- package/dist/server/server/paseo-worktree-archive-service.d.ts +41 -0
- package/dist/server/server/paseo-worktree-archive-service.d.ts.map +1 -0
- package/dist/server/server/paseo-worktree-archive-service.js +137 -0
- package/dist/server/server/paseo-worktree-archive-service.js.map +1 -0
- package/dist/server/server/paseo-worktree-service.d.ts +24 -0
- package/dist/server/server/paseo-worktree-service.d.ts.map +1 -0
- package/dist/server/server/paseo-worktree-service.js +94 -0
- package/dist/server/server/paseo-worktree-service.js.map +1 -0
- package/dist/server/server/path-utils.d.ts +1 -0
- package/dist/server/server/path-utils.d.ts.map +1 -1
- package/dist/server/server/path-utils.js +9 -0
- package/dist/server/server/path-utils.js.map +1 -1
- package/dist/server/server/persisted-config.d.ts +195 -67
- package/dist/server/server/persisted-config.d.ts.map +1 -1
- package/dist/server/server/persistence-hooks.d.ts.map +1 -1
- package/dist/server/server/persistence-hooks.js +3 -0
- package/dist/server/server/persistence-hooks.js.map +1 -1
- package/dist/server/server/resolve-worktree-creation-intent.d.ts +30 -0
- package/dist/server/server/resolve-worktree-creation-intent.d.ts.map +1 -0
- package/dist/server/server/resolve-worktree-creation-intent.js +163 -0
- package/dist/server/server/resolve-worktree-creation-intent.js.map +1 -0
- package/dist/server/server/schedule/rpc-schemas.d.ts +192 -192
- package/dist/server/server/schedule/service.d.ts +1 -1
- package/dist/server/server/schedule/service.d.ts.map +1 -1
- package/dist/server/server/schedule/types.d.ts +44 -44
- package/dist/server/server/script-health-monitor.d.ts +39 -0
- package/dist/server/server/script-health-monitor.d.ts.map +1 -0
- package/dist/server/server/script-health-monitor.js +158 -0
- package/dist/server/server/script-health-monitor.js.map +1 -0
- package/dist/server/server/script-proxy.d.ts +40 -0
- package/dist/server/server/script-proxy.d.ts.map +1 -0
- package/dist/server/server/script-proxy.js +245 -0
- package/dist/server/server/script-proxy.js.map +1 -0
- package/dist/server/server/script-route-branch-handler.d.ts +10 -0
- package/dist/server/server/script-route-branch-handler.d.ts.map +1 -0
- package/dist/server/server/script-route-branch-handler.js +45 -0
- package/dist/server/server/script-route-branch-handler.js.map +1 -0
- package/dist/server/server/script-status-projection.d.ts +29 -0
- package/dist/server/server/script-status-projection.d.ts.map +1 -0
- package/dist/server/server/script-status-projection.js +133 -0
- package/dist/server/server/script-status-projection.js.map +1 -0
- package/dist/server/server/session.d.ts +77 -13
- package/dist/server/server/session.d.ts.map +1 -1
- package/dist/server/server/session.js +1290 -548
- package/dist/server/server/session.js.map +1 -1
- package/dist/server/server/websocket-server.d.ts +27 -3
- package/dist/server/server/websocket-server.d.ts.map +1 -1
- package/dist/server/server/websocket-server.js +112 -29
- package/dist/server/server/websocket-server.js.map +1 -1
- package/dist/server/server/workspace-archive-service.d.ts +8 -0
- package/dist/server/server/workspace-archive-service.d.ts.map +1 -0
- package/dist/server/server/workspace-archive-service.js +17 -0
- package/dist/server/server/workspace-archive-service.js.map +1 -0
- package/dist/server/server/workspace-git-metadata.d.ts +24 -0
- package/dist/server/server/workspace-git-metadata.d.ts.map +1 -0
- package/dist/server/server/workspace-git-metadata.js +78 -0
- package/dist/server/server/workspace-git-metadata.js.map +1 -0
- package/dist/server/server/workspace-git-service.d.ts +104 -5
- package/dist/server/server/workspace-git-service.d.ts.map +1 -1
- package/dist/server/server/workspace-git-service.js +442 -56
- package/dist/server/server/workspace-git-service.js.map +1 -1
- package/dist/server/server/workspace-reconciliation-service.d.ts +54 -0
- package/dist/server/server/workspace-reconciliation-service.d.ts.map +1 -0
- package/dist/server/server/workspace-reconciliation-service.js +176 -0
- package/dist/server/server/workspace-reconciliation-service.js.map +1 -0
- package/dist/server/server/workspace-registry-bootstrap.d.ts.map +1 -1
- package/dist/server/server/workspace-registry-bootstrap.js +4 -3
- package/dist/server/server/workspace-registry-bootstrap.js.map +1 -1
- package/dist/server/server/workspace-registry.d.ts +8 -8
- package/dist/server/server/workspace-registry.test-helpers.d.ts +37 -0
- package/dist/server/server/workspace-registry.test-helpers.d.ts.map +1 -0
- package/dist/server/server/workspace-registry.test-helpers.js +121 -0
- package/dist/server/server/workspace-registry.test-helpers.js.map +1 -0
- package/dist/server/server/workspace-script-runtime-store.d.ts +28 -0
- package/dist/server/server/workspace-script-runtime-store.d.ts.map +1 -0
- package/dist/server/server/workspace-script-runtime-store.js +78 -0
- package/dist/server/server/workspace-script-runtime-store.js.map +1 -0
- package/dist/server/server/workspace-service-env.d.ts +17 -0
- package/dist/server/server/workspace-service-env.d.ts.map +1 -0
- package/dist/server/server/workspace-service-env.js +80 -0
- package/dist/server/server/workspace-service-env.js.map +1 -0
- package/dist/server/server/workspace-service-port-registry.d.ts +19 -0
- package/dist/server/server/workspace-service-port-registry.d.ts.map +1 -0
- package/dist/server/server/workspace-service-port-registry.js +59 -0
- package/dist/server/server/workspace-service-port-registry.js.map +1 -0
- package/dist/server/server/worktree-bootstrap.d.ts +55 -10
- package/dist/server/server/worktree-bootstrap.d.ts.map +1 -1
- package/dist/server/server/worktree-bootstrap.js +290 -112
- package/dist/server/server/worktree-bootstrap.js.map +1 -1
- package/dist/server/server/worktree-core.d.ts +25 -0
- package/dist/server/server/worktree-core.d.ts.map +1 -0
- package/dist/server/server/worktree-core.js +75 -0
- package/dist/server/server/worktree-core.js.map +1 -0
- package/dist/server/server/worktree-errors.d.ts +12 -0
- package/dist/server/server/worktree-errors.d.ts.map +1 -0
- package/dist/server/server/worktree-errors.js +31 -0
- package/dist/server/server/worktree-errors.js.map +1 -0
- package/dist/server/server/worktree-session.d.ts +56 -70
- package/dist/server/server/worktree-session.d.ts.map +1 -1
- package/dist/server/server/worktree-session.js +176 -251
- package/dist/server/server/worktree-session.js.map +1 -1
- package/dist/server/services/github-service.d.ts +225 -0
- package/dist/server/services/github-service.d.ts.map +1 -0
- package/dist/server/services/github-service.js +1381 -0
- package/dist/server/services/github-service.js.map +1 -0
- package/dist/server/shared/messages.d.ts +29408 -12268
- package/dist/server/shared/messages.d.ts.map +1 -1
- package/dist/server/shared/messages.js +391 -65
- package/dist/server/shared/messages.js.map +1 -1
- package/dist/server/terminal/shell-integration/zsh/.zshenv +17 -0
- package/dist/server/terminal/shell-integration/zsh/paseo-integration.zsh +32 -0
- package/dist/server/terminal/terminal-manager.d.ts +9 -0
- package/dist/server/terminal/terminal-manager.d.ts.map +1 -1
- package/dist/server/terminal/terminal-manager.js +27 -0
- package/dist/server/terminal/terminal-manager.js.map +1 -1
- package/dist/server/terminal/terminal-output-coalescer.d.ts +30 -0
- package/dist/server/terminal/terminal-output-coalescer.d.ts.map +1 -0
- package/dist/server/terminal/terminal-output-coalescer.js +55 -0
- package/dist/server/terminal/terminal-output-coalescer.js.map +1 -0
- package/dist/server/terminal/terminal.d.ts +32 -1
- package/dist/server/terminal/terminal.d.ts.map +1 -1
- package/dist/server/terminal/terminal.js +397 -17
- package/dist/server/terminal/terminal.js.map +1 -1
- package/dist/server/utils/checkout-git.d.ts +63 -10
- package/dist/server/utils/checkout-git.d.ts.map +1 -1
- package/dist/server/utils/checkout-git.js +321 -229
- package/dist/server/utils/checkout-git.js.map +1 -1
- package/dist/server/utils/promise-timeout.d.ts +9 -0
- package/dist/server/utils/promise-timeout.d.ts.map +1 -0
- package/dist/server/utils/promise-timeout.js +25 -0
- package/dist/server/utils/promise-timeout.js.map +1 -0
- package/dist/server/utils/script-hostname.d.ts +8 -0
- package/dist/server/utils/script-hostname.d.ts.map +1 -0
- package/dist/server/utils/script-hostname.js +14 -0
- package/dist/server/utils/script-hostname.js.map +1 -0
- package/dist/server/utils/string-command-shell.d.ts +10 -0
- package/dist/server/utils/string-command-shell.d.ts.map +1 -0
- package/dist/server/utils/string-command-shell.js +21 -0
- package/dist/server/utils/string-command-shell.js.map +1 -0
- package/dist/server/utils/worktree.d.ts +54 -7
- package/dist/server/utils/worktree.d.ts.map +1 -1
- package/dist/server/utils/worktree.js +434 -129
- package/dist/server/utils/worktree.js.map +1 -1
- package/dist/src/terminal/shell-integration/zsh/.zshenv +17 -0
- package/dist/src/terminal/shell-integration/zsh/paseo-integration.zsh +32 -0
- package/package.json +11 -14
- package/dist/server/server/agent/providers/pi-acp-agent.d.ts +0 -28
- package/dist/server/server/agent/providers/pi-acp-agent.d.ts.map +0 -1
- package/dist/server/server/agent/providers/pi-acp-agent.js +0 -302
- package/dist/server/server/agent/providers/pi-acp-agent.js.map +0 -1
|
@@ -3,7 +3,11 @@ import { resolve } from "node:path";
|
|
|
3
3
|
import { stat } from "node:fs/promises";
|
|
4
4
|
import { AGENT_LIFECYCLE_STATUSES, } from "../../shared/agent-lifecycle.js";
|
|
5
5
|
import { z } from "zod";
|
|
6
|
+
import { InMemoryAgentTimelineStore, } from "./agent-timeline-store.js";
|
|
7
|
+
import { AGENT_STREAM_COALESCE_DEFAULT_WINDOW_MS, AgentStreamCoalescer, } from "./agent-stream-coalescer.js";
|
|
6
8
|
import { getAgentProviderDefinition } from "./provider-manifest.js";
|
|
9
|
+
const RELOAD_SESSION_CLOSE_TIMEOUT_MS = 3000;
|
|
10
|
+
const INTERRUPT_SESSION_TIMEOUT_MS = 2000;
|
|
7
11
|
export { AGENT_LIFECYCLE_STATUSES };
|
|
8
12
|
const SYSTEM_ERROR_PREFIX = "[System Error]";
|
|
9
13
|
function attachPersistenceCwd(handle, cwd) {
|
|
@@ -18,7 +22,6 @@ function attachPersistenceCwd(handle, cwd) {
|
|
|
18
22
|
},
|
|
19
23
|
};
|
|
20
24
|
}
|
|
21
|
-
const DEFAULT_TIMELINE_FETCH_LIMIT = 200;
|
|
22
25
|
const BUSY_STATUSES = ["initializing", "running"];
|
|
23
26
|
const AgentIdSchema = z.string().uuid();
|
|
24
27
|
function isAgentBusy(status) {
|
|
@@ -56,22 +59,31 @@ export class AgentManager {
|
|
|
56
59
|
constructor(options) {
|
|
57
60
|
this.clients = new Map();
|
|
58
61
|
this.agents = new Map();
|
|
62
|
+
this.timelineStore = new InMemoryAgentTimelineStore();
|
|
63
|
+
this.agentsAwaitingInitialSnapshotPersist = new Set();
|
|
64
|
+
this.sessionEventTails = new Map();
|
|
59
65
|
this.pendingForegroundRuns = new Map();
|
|
60
66
|
this.subscribers = new Set();
|
|
61
67
|
this.previousStatuses = new Map();
|
|
62
68
|
this.backgroundTasks = new Set();
|
|
63
|
-
const maxTimelineItems = options?.maxTimelineItems;
|
|
64
|
-
this.maxTimelineItems =
|
|
65
|
-
typeof maxTimelineItems === "number" &&
|
|
66
|
-
Number.isFinite(maxTimelineItems) &&
|
|
67
|
-
maxTimelineItems >= 0
|
|
68
|
-
? Math.floor(maxTimelineItems)
|
|
69
|
-
: null;
|
|
70
69
|
this.idFactory = options?.idFactory ?? (() => randomUUID());
|
|
71
70
|
this.registry = options?.registry;
|
|
71
|
+
this.durableTimelineStore = options?.durableTimelineStore;
|
|
72
72
|
this.onAgentAttention = options?.onAgentAttention;
|
|
73
73
|
this.mcpBaseUrl = options?.mcpBaseUrl ?? null;
|
|
74
74
|
this.logger = options.logger.child({ module: "agent", component: "agent-manager" });
|
|
75
|
+
this.rescueTimeouts = {
|
|
76
|
+
reloadSessionCloseMs: options.rescueTimeouts?.reloadSessionCloseMs ?? RELOAD_SESSION_CLOSE_TIMEOUT_MS,
|
|
77
|
+
interruptSessionMs: options.rescueTimeouts?.interruptSessionMs ?? INTERRUPT_SESSION_TIMEOUT_MS,
|
|
78
|
+
};
|
|
79
|
+
this.agentStreamCoalescer = new AgentStreamCoalescer({
|
|
80
|
+
windowMs: options.agentStreamCoalesceWindowMs ?? AGENT_STREAM_COALESCE_DEFAULT_WINDOW_MS,
|
|
81
|
+
timers: { setTimeout, clearTimeout },
|
|
82
|
+
onFlush: ({ agentId, item, provider, turnId }) => {
|
|
83
|
+
const event = this.recordAndDispatchTimelineItem(agentId, item, provider, turnId);
|
|
84
|
+
this.notifyForegroundTurnWaiters(agentId, event);
|
|
85
|
+
},
|
|
86
|
+
});
|
|
75
87
|
if (options?.clients) {
|
|
76
88
|
for (const [provider, client] of Object.entries(options.clients)) {
|
|
77
89
|
if (client) {
|
|
@@ -102,7 +114,10 @@ export class AgentManager {
|
|
|
102
114
|
if (agent.activeForegroundTurnId !== null) {
|
|
103
115
|
withActiveForegroundTurn++;
|
|
104
116
|
}
|
|
105
|
-
|
|
117
|
+
if (!this.timelineStore.has(agent.id)) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const len = this.timelineStore.getItems(agent.id).length;
|
|
106
121
|
totalItems += len;
|
|
107
122
|
if (len > maxItemsPerAgent) {
|
|
108
123
|
maxItemsPerAgent = len;
|
|
@@ -282,130 +297,19 @@ export class AgentManager {
|
|
|
282
297
|
return agent ? { ...agent } : null;
|
|
283
298
|
}
|
|
284
299
|
getTimeline(id) {
|
|
285
|
-
|
|
286
|
-
return
|
|
300
|
+
this.requireAgent(id);
|
|
301
|
+
return this.timelineStore.getItems(id);
|
|
287
302
|
}
|
|
288
|
-
getTimelineRows(id) {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
303
|
+
async getTimelineRows(id) {
|
|
304
|
+
this.requireAgent(id);
|
|
305
|
+
if (this.durableTimelineStore) {
|
|
306
|
+
return await this.durableTimelineStore.getCommittedRows(id);
|
|
307
|
+
}
|
|
308
|
+
return this.timelineStore.getRows(id);
|
|
292
309
|
}
|
|
293
310
|
fetchTimeline(id, options) {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const direction = options?.direction ?? "tail";
|
|
297
|
-
const requestedLimit = options?.limit;
|
|
298
|
-
const limit = requestedLimit === undefined
|
|
299
|
-
? DEFAULT_TIMELINE_FETCH_LIMIT
|
|
300
|
-
: Math.max(0, Math.floor(requestedLimit));
|
|
301
|
-
const cursor = options?.cursor;
|
|
302
|
-
const window = { minSeq, maxSeq, nextSeq };
|
|
303
|
-
if (cursor && cursor.epoch !== epoch) {
|
|
304
|
-
return {
|
|
305
|
-
epoch,
|
|
306
|
-
direction,
|
|
307
|
-
reset: true,
|
|
308
|
-
staleCursor: true,
|
|
309
|
-
gap: false,
|
|
310
|
-
window,
|
|
311
|
-
hasOlder: false,
|
|
312
|
-
hasNewer: false,
|
|
313
|
-
rows: rows.map((row) => ({ ...row })),
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
const selectAll = limit === 0;
|
|
317
|
-
const cloneRows = (items) => items.map((row) => ({ ...row }));
|
|
318
|
-
if (direction === "after" && cursor && rows.length > 0 && cursor.seq < minSeq - 1) {
|
|
319
|
-
return {
|
|
320
|
-
epoch,
|
|
321
|
-
direction,
|
|
322
|
-
reset: true,
|
|
323
|
-
staleCursor: false,
|
|
324
|
-
gap: true,
|
|
325
|
-
window,
|
|
326
|
-
hasOlder: false,
|
|
327
|
-
hasNewer: false,
|
|
328
|
-
rows: cloneRows(rows),
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
if (rows.length === 0) {
|
|
332
|
-
return {
|
|
333
|
-
epoch,
|
|
334
|
-
direction,
|
|
335
|
-
reset: false,
|
|
336
|
-
staleCursor: false,
|
|
337
|
-
gap: false,
|
|
338
|
-
window,
|
|
339
|
-
hasOlder: false,
|
|
340
|
-
hasNewer: false,
|
|
341
|
-
rows: [],
|
|
342
|
-
};
|
|
343
|
-
}
|
|
344
|
-
if (direction === "tail") {
|
|
345
|
-
const selected = selectAll || limit >= rows.length ? rows : rows.slice(rows.length - limit);
|
|
346
|
-
const hasOlder = selected.length > 0 && selected[0].seq > minSeq;
|
|
347
|
-
return {
|
|
348
|
-
epoch,
|
|
349
|
-
direction,
|
|
350
|
-
reset: false,
|
|
351
|
-
staleCursor: false,
|
|
352
|
-
gap: false,
|
|
353
|
-
window,
|
|
354
|
-
hasOlder,
|
|
355
|
-
hasNewer: false,
|
|
356
|
-
rows: cloneRows(selected),
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
if (direction === "after") {
|
|
360
|
-
const baseSeq = cursor?.seq ?? 0;
|
|
361
|
-
const startIdx = rows.findIndex((row) => row.seq > baseSeq);
|
|
362
|
-
if (startIdx < 0) {
|
|
363
|
-
return {
|
|
364
|
-
epoch,
|
|
365
|
-
direction,
|
|
366
|
-
reset: false,
|
|
367
|
-
staleCursor: false,
|
|
368
|
-
gap: false,
|
|
369
|
-
window,
|
|
370
|
-
hasOlder: baseSeq >= minSeq,
|
|
371
|
-
hasNewer: false,
|
|
372
|
-
rows: [],
|
|
373
|
-
};
|
|
374
|
-
}
|
|
375
|
-
const selected = selectAll ? rows.slice(startIdx) : rows.slice(startIdx, startIdx + limit);
|
|
376
|
-
const lastSelected = selected[selected.length - 1];
|
|
377
|
-
return {
|
|
378
|
-
epoch,
|
|
379
|
-
direction,
|
|
380
|
-
reset: false,
|
|
381
|
-
staleCursor: false,
|
|
382
|
-
gap: false,
|
|
383
|
-
window,
|
|
384
|
-
hasOlder: selected[0].seq > minSeq,
|
|
385
|
-
hasNewer: Boolean(lastSelected && lastSelected.seq < maxSeq),
|
|
386
|
-
rows: cloneRows(selected),
|
|
387
|
-
};
|
|
388
|
-
}
|
|
389
|
-
// direction === "before"
|
|
390
|
-
const beforeSeq = cursor?.seq ?? nextSeq;
|
|
391
|
-
const endExclusive = rows.findIndex((row) => row.seq >= beforeSeq);
|
|
392
|
-
const boundedRows = endExclusive < 0 ? rows : rows.slice(0, endExclusive);
|
|
393
|
-
const selected = selectAll || limit >= boundedRows.length
|
|
394
|
-
? boundedRows
|
|
395
|
-
: boundedRows.slice(boundedRows.length - limit);
|
|
396
|
-
const hasOlder = selected.length > 0 && selected[0].seq > minSeq;
|
|
397
|
-
const hasNewer = endExclusive >= 0;
|
|
398
|
-
return {
|
|
399
|
-
epoch,
|
|
400
|
-
direction,
|
|
401
|
-
reset: false,
|
|
402
|
-
staleCursor: false,
|
|
403
|
-
gap: false,
|
|
404
|
-
window,
|
|
405
|
-
hasOlder,
|
|
406
|
-
hasNewer,
|
|
407
|
-
rows: cloneRows(selected),
|
|
408
|
-
};
|
|
311
|
+
this.requireAgent(id);
|
|
312
|
+
return this.timelineStore.fetch(id, options);
|
|
409
313
|
}
|
|
410
314
|
async createAgent(config, agentId, options) {
|
|
411
315
|
const resolvedAgentId = validateAgentId(agentId ?? this.idFactory(), "createAgent");
|
|
@@ -431,6 +335,7 @@ export class AgentManager {
|
|
|
431
335
|
const session = await client.createSession(normalizedConfig, launchContext);
|
|
432
336
|
return this.registerSession(session, normalizedConfig, resolvedAgentId, {
|
|
433
337
|
labels: options?.labels,
|
|
338
|
+
workspaceId: options?.workspaceId,
|
|
434
339
|
});
|
|
435
340
|
}
|
|
436
341
|
// Reconstruct an agent from provider persistence. Callers should explicitly
|
|
@@ -459,16 +364,11 @@ export class AgentManager {
|
|
|
459
364
|
// Hot-reload an active agent session with config overrides while preserving
|
|
460
365
|
// in-memory timeline state.
|
|
461
366
|
async reloadAgentSession(agentId, overrides) {
|
|
462
|
-
let existing = this.
|
|
367
|
+
let existing = this.requireSessionAgent(agentId);
|
|
463
368
|
if (this.hasInFlightRun(agentId)) {
|
|
464
369
|
await this.cancelAgentRun(agentId);
|
|
465
|
-
existing = this.
|
|
370
|
+
existing = this.requireSessionAgent(agentId);
|
|
466
371
|
}
|
|
467
|
-
const timelineState = this.ensureTimelineState(existing);
|
|
468
|
-
const preservedTimeline = [...existing.timeline];
|
|
469
|
-
const preservedTimelineRows = timelineState.rows.map((row) => ({ ...row }));
|
|
470
|
-
const preservedTimelineEpoch = timelineState.epoch;
|
|
471
|
-
const preservedTimelineNextSeq = timelineState.nextSeq;
|
|
472
372
|
const preservedHistoryPrimed = existing.historyPrimed;
|
|
473
373
|
const preservedLastUsage = existing.lastUsage;
|
|
474
374
|
const preservedLastError = existing.lastError;
|
|
@@ -486,6 +386,7 @@ export class AgentManager {
|
|
|
486
386
|
const session = handle
|
|
487
387
|
? await client.resumeSession(handle, normalizedConfig, launchContext)
|
|
488
388
|
: await client.createSession(normalizedConfig, launchContext);
|
|
389
|
+
this.agentStreamCoalescer.flushAndDiscard(agentId);
|
|
489
390
|
// Remove the existing agent entry before swapping sessions
|
|
490
391
|
this.agents.delete(agentId);
|
|
491
392
|
if (existing.unsubscribeSession) {
|
|
@@ -497,28 +398,65 @@ export class AgentManager {
|
|
|
497
398
|
}
|
|
498
399
|
existing.foregroundTurnWaiters.clear();
|
|
499
400
|
this.settlePendingForegroundRun(agentId);
|
|
500
|
-
|
|
501
|
-
await existing.session.close();
|
|
502
|
-
}
|
|
503
|
-
catch (error) {
|
|
504
|
-
this.logger.warn({ err: error, agentId }, "Failed to close previous session during refresh");
|
|
505
|
-
}
|
|
401
|
+
await this.closeReloadedSession(existing.session, agentId);
|
|
506
402
|
// Preserve existing labels and timeline during reload.
|
|
507
403
|
return this.registerSession(session, normalizedConfig, agentId, {
|
|
508
404
|
labels: existing.labels,
|
|
509
405
|
createdAt: existing.createdAt,
|
|
510
406
|
updatedAt: existing.updatedAt,
|
|
511
407
|
lastUserMessageAt: existing.lastUserMessageAt,
|
|
512
|
-
timeline: preservedTimeline,
|
|
513
|
-
timelineRows: preservedTimelineRows,
|
|
514
|
-
timelineEpoch: preservedTimelineEpoch,
|
|
515
|
-
timelineNextSeq: preservedTimelineNextSeq,
|
|
516
408
|
historyPrimed: preservedHistoryPrimed,
|
|
517
409
|
lastUsage: preservedLastUsage,
|
|
518
410
|
lastError: preservedLastError,
|
|
519
411
|
attention: preservedAttention,
|
|
520
412
|
});
|
|
521
413
|
}
|
|
414
|
+
async closeReloadedSession(session, agentId) {
|
|
415
|
+
try {
|
|
416
|
+
const result = await this.waitWithTimeout({
|
|
417
|
+
operation: session.close(),
|
|
418
|
+
timeoutMs: this.rescueTimeouts.reloadSessionCloseMs,
|
|
419
|
+
onLateError: (error) => {
|
|
420
|
+
this.logger.warn({ err: error, agentId }, "Previous session close failed after refresh timeout");
|
|
421
|
+
},
|
|
422
|
+
});
|
|
423
|
+
if (result === "timed_out") {
|
|
424
|
+
this.logger.warn({ agentId, timeoutMs: this.rescueTimeouts.reloadSessionCloseMs }, "Timed out closing previous session during refresh");
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
catch (error) {
|
|
428
|
+
this.logger.warn({ err: error, agentId }, "Failed to close previous session during refresh");
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
async waitWithTimeout(options) {
|
|
432
|
+
let didTimeOut = false;
|
|
433
|
+
let timer = null;
|
|
434
|
+
const operation = options.operation
|
|
435
|
+
.then(() => "completed")
|
|
436
|
+
.catch((error) => {
|
|
437
|
+
if (didTimeOut) {
|
|
438
|
+
options.onLateError?.(error);
|
|
439
|
+
return "timed_out";
|
|
440
|
+
}
|
|
441
|
+
throw error;
|
|
442
|
+
});
|
|
443
|
+
try {
|
|
444
|
+
return await Promise.race([
|
|
445
|
+
operation,
|
|
446
|
+
new Promise((resolve) => {
|
|
447
|
+
timer = setTimeout(() => {
|
|
448
|
+
didTimeOut = true;
|
|
449
|
+
resolve("timed_out");
|
|
450
|
+
}, options.timeoutMs);
|
|
451
|
+
}),
|
|
452
|
+
]);
|
|
453
|
+
}
|
|
454
|
+
finally {
|
|
455
|
+
if (timer) {
|
|
456
|
+
clearTimeout(timer);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
522
460
|
async closeAgent(agentId) {
|
|
523
461
|
const agent = this.requireAgent(agentId);
|
|
524
462
|
this.logger.trace({
|
|
@@ -527,34 +465,11 @@ export class AgentManager {
|
|
|
527
465
|
activeForegroundTurnId: agent.activeForegroundTurnId,
|
|
528
466
|
pendingPermissions: agent.pendingPermissions.size,
|
|
529
467
|
}, "closeAgent: start");
|
|
530
|
-
this.
|
|
531
|
-
|
|
532
|
-
this.
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
agent.unsubscribeSession = null;
|
|
536
|
-
}
|
|
537
|
-
for (const waiter of agent.foregroundTurnWaiters) {
|
|
538
|
-
// Wake up the generator so it can exit the await loop
|
|
539
|
-
waiter.callback({
|
|
540
|
-
type: "turn_canceled",
|
|
541
|
-
provider: agent.provider,
|
|
542
|
-
reason: "agent closed",
|
|
543
|
-
turnId: waiter.turnId,
|
|
544
|
-
});
|
|
545
|
-
this.settleForegroundTurnWaiter(waiter);
|
|
546
|
-
}
|
|
547
|
-
agent.foregroundTurnWaiters.clear();
|
|
548
|
-
this.settlePendingForegroundRun(agentId);
|
|
549
|
-
const session = agent.session;
|
|
550
|
-
const closedAgent = {
|
|
551
|
-
...agent,
|
|
552
|
-
lifecycle: "closed",
|
|
553
|
-
session: null,
|
|
554
|
-
activeForegroundTurnId: null,
|
|
555
|
-
};
|
|
556
|
-
await session.close();
|
|
557
|
-
this.emitState(closedAgent);
|
|
468
|
+
const closedAgent = this.prepareAgentForClosure(agent, "agent closed");
|
|
469
|
+
await agent.session.close();
|
|
470
|
+
this.timelineStore.delete(agentId);
|
|
471
|
+
await this.persistSnapshot(closedAgent);
|
|
472
|
+
this.emitClosedAgent(closedAgent, { persist: false });
|
|
558
473
|
this.logger.trace({ agentId }, "closeAgent: completed");
|
|
559
474
|
}
|
|
560
475
|
async archiveAgent(agentId) {
|
|
@@ -587,7 +502,7 @@ export class AgentManager {
|
|
|
587
502
|
return { archivedAt };
|
|
588
503
|
}
|
|
589
504
|
async setAgentMode(agentId, modeId) {
|
|
590
|
-
const agent = this.
|
|
505
|
+
const agent = this.requireSessionAgent(agentId);
|
|
591
506
|
await agent.session.setMode(modeId);
|
|
592
507
|
agent.config.modeId = modeId;
|
|
593
508
|
agent.currentModeId = modeId;
|
|
@@ -599,7 +514,7 @@ export class AgentManager {
|
|
|
599
514
|
this.emitState(agent);
|
|
600
515
|
}
|
|
601
516
|
async setAgentModel(agentId, modelId) {
|
|
602
|
-
const agent = this.
|
|
517
|
+
const agent = this.requireSessionAgent(agentId);
|
|
603
518
|
const normalizedModelId = typeof modelId === "string" && modelId.trim().length > 0 ? modelId : null;
|
|
604
519
|
if (agent.session.setModel) {
|
|
605
520
|
await agent.session.setModel(normalizedModelId);
|
|
@@ -612,7 +527,7 @@ export class AgentManager {
|
|
|
612
527
|
this.emitState(agent);
|
|
613
528
|
}
|
|
614
529
|
async setAgentThinkingOption(agentId, thinkingOptionId) {
|
|
615
|
-
const agent = this.
|
|
530
|
+
const agent = this.requireSessionAgent(agentId);
|
|
616
531
|
const normalizedThinkingOptionId = typeof thinkingOptionId === "string" && thinkingOptionId.trim().length > 0
|
|
617
532
|
? thinkingOptionId
|
|
618
533
|
: null;
|
|
@@ -620,6 +535,12 @@ export class AgentManager {
|
|
|
620
535
|
await agent.session.setThinkingOption(normalizedThinkingOptionId);
|
|
621
536
|
}
|
|
622
537
|
agent.config.thinkingOptionId = normalizedThinkingOptionId ?? undefined;
|
|
538
|
+
if (agent.runtimeInfo) {
|
|
539
|
+
agent.runtimeInfo = {
|
|
540
|
+
...agent.runtimeInfo,
|
|
541
|
+
thinkingOptionId: normalizedThinkingOptionId,
|
|
542
|
+
};
|
|
543
|
+
}
|
|
623
544
|
this.touchUpdatedAt(agent);
|
|
624
545
|
this.emitState(agent);
|
|
625
546
|
}
|
|
@@ -639,16 +560,21 @@ export class AgentManager {
|
|
|
639
560
|
if (!normalizedTitle) {
|
|
640
561
|
return;
|
|
641
562
|
}
|
|
563
|
+
if (this.agentsAwaitingInitialSnapshotPersist.has(agent.id) &&
|
|
564
|
+
this.registry &&
|
|
565
|
+
(await this.registry.get(agent.id)) === null) {
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
642
568
|
this.touchUpdatedAt(agent);
|
|
643
569
|
await this.persistSnapshot(agent, { title: normalizedTitle });
|
|
644
|
-
this.emitState(agent);
|
|
570
|
+
this.emitState(agent, { persist: false });
|
|
645
571
|
}
|
|
646
572
|
async setLabels(agentId, labels) {
|
|
647
573
|
const agent = this.requireAgent(agentId);
|
|
648
574
|
agent.labels = { ...agent.labels, ...labels };
|
|
649
|
-
await this.persistSnapshot(agent);
|
|
650
575
|
this.touchUpdatedAt(agent);
|
|
651
|
-
this.
|
|
576
|
+
await this.persistSnapshot(agent);
|
|
577
|
+
this.emitState(agent, { persist: false });
|
|
652
578
|
}
|
|
653
579
|
notifyAgentState(agentId) {
|
|
654
580
|
const agent = this.agents.get(agentId);
|
|
@@ -663,8 +589,81 @@ export class AgentManager {
|
|
|
663
589
|
if (agent.attention.requiresAttention) {
|
|
664
590
|
agent.attention = { requiresAttention: false };
|
|
665
591
|
await this.persistSnapshot(agent);
|
|
666
|
-
this.emitState(agent);
|
|
592
|
+
this.emitState(agent, { persist: false });
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
async archiveSnapshot(agentId, archivedAt) {
|
|
596
|
+
const registry = this.requireRegistry();
|
|
597
|
+
const liveAgent = this.getAgent(agentId);
|
|
598
|
+
if (liveAgent) {
|
|
599
|
+
await this.persistSnapshot(liveAgent, {
|
|
600
|
+
internal: liveAgent.internal,
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
const record = await registry.get(agentId);
|
|
604
|
+
if (!record) {
|
|
605
|
+
throw new Error(`Agent not found: ${agentId}`);
|
|
606
|
+
}
|
|
607
|
+
const normalizedStatus = record.lastStatus === "running" || record.lastStatus === "initializing"
|
|
608
|
+
? "idle"
|
|
609
|
+
: record.lastStatus;
|
|
610
|
+
const nextRecord = {
|
|
611
|
+
...record,
|
|
612
|
+
archivedAt,
|
|
613
|
+
lastStatus: normalizedStatus,
|
|
614
|
+
requiresAttention: false,
|
|
615
|
+
attentionReason: null,
|
|
616
|
+
attentionTimestamp: null,
|
|
617
|
+
};
|
|
618
|
+
await registry.upsert(nextRecord);
|
|
619
|
+
return nextRecord;
|
|
620
|
+
}
|
|
621
|
+
async unarchiveSnapshot(agentId) {
|
|
622
|
+
const registry = this.requireRegistry();
|
|
623
|
+
const record = await registry.get(agentId);
|
|
624
|
+
if (!record || !record.archivedAt) {
|
|
625
|
+
return false;
|
|
626
|
+
}
|
|
627
|
+
await registry.upsert({
|
|
628
|
+
...record,
|
|
629
|
+
archivedAt: null,
|
|
630
|
+
});
|
|
631
|
+
if (this.getAgent(agentId)) {
|
|
632
|
+
this.notifyAgentState(agentId);
|
|
633
|
+
}
|
|
634
|
+
return true;
|
|
635
|
+
}
|
|
636
|
+
async unarchiveSnapshotByHandle(handle) {
|
|
637
|
+
const registry = this.requireRegistry();
|
|
638
|
+
const records = await registry.list();
|
|
639
|
+
const matched = records.find((record) => record.persistence?.provider === handle.provider &&
|
|
640
|
+
record.persistence?.sessionId === handle.sessionId);
|
|
641
|
+
if (!matched) {
|
|
642
|
+
return;
|
|
667
643
|
}
|
|
644
|
+
await this.unarchiveSnapshot(matched.id);
|
|
645
|
+
}
|
|
646
|
+
async updateAgentMetadata(agentId, updates) {
|
|
647
|
+
const liveAgent = this.getAgent(agentId);
|
|
648
|
+
if (liveAgent) {
|
|
649
|
+
if (updates.title) {
|
|
650
|
+
await this.setTitle(agentId, updates.title);
|
|
651
|
+
}
|
|
652
|
+
if (updates.labels) {
|
|
653
|
+
await this.setLabels(agentId, updates.labels);
|
|
654
|
+
}
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
const registry = this.requireRegistry();
|
|
658
|
+
const existing = await registry.get(agentId);
|
|
659
|
+
if (!existing) {
|
|
660
|
+
throw new Error(`Agent not found: ${agentId}`);
|
|
661
|
+
}
|
|
662
|
+
await registry.upsert({
|
|
663
|
+
...existing,
|
|
664
|
+
...(updates.title ? { title: updates.title } : {}),
|
|
665
|
+
...(updates.labels ? { labels: { ...existing.labels, ...updates.labels } } : {}),
|
|
666
|
+
});
|
|
668
667
|
}
|
|
669
668
|
async runAgent(agentId, prompt, options) {
|
|
670
669
|
const events = this.streamAgent(agentId, prompt, options);
|
|
@@ -710,14 +709,14 @@ export class AgentManager {
|
|
|
710
709
|
};
|
|
711
710
|
const updatedAt = this.touchUpdatedAt(agent);
|
|
712
711
|
agent.lastUserMessageAt = updatedAt;
|
|
713
|
-
const row = this.recordTimeline(
|
|
712
|
+
const row = this.recordTimeline(agentId, item);
|
|
714
713
|
this.dispatchStream(agentId, {
|
|
715
714
|
type: "timeline",
|
|
716
715
|
item,
|
|
717
716
|
provider: agent.provider,
|
|
718
717
|
}, {
|
|
719
718
|
seq: row.seq,
|
|
720
|
-
epoch: this.
|
|
719
|
+
epoch: this.timelineStore.getEpoch(agentId),
|
|
721
720
|
});
|
|
722
721
|
if (options?.emitState !== false) {
|
|
723
722
|
this.emitState(agent);
|
|
@@ -726,14 +725,14 @@ export class AgentManager {
|
|
|
726
725
|
async appendTimelineItem(agentId, item) {
|
|
727
726
|
const agent = this.requireAgent(agentId);
|
|
728
727
|
this.touchUpdatedAt(agent);
|
|
729
|
-
const row = this.recordTimeline(
|
|
728
|
+
const row = this.recordTimeline(agentId, item);
|
|
730
729
|
this.dispatchStream(agentId, {
|
|
731
730
|
type: "timeline",
|
|
732
731
|
item,
|
|
733
732
|
provider: agent.provider,
|
|
734
733
|
}, {
|
|
735
734
|
seq: row.seq,
|
|
736
|
-
epoch: this.
|
|
735
|
+
epoch: this.timelineStore.getEpoch(agentId),
|
|
737
736
|
});
|
|
738
737
|
await this.persistSnapshot(agent);
|
|
739
738
|
}
|
|
@@ -747,7 +746,7 @@ export class AgentManager {
|
|
|
747
746
|
});
|
|
748
747
|
}
|
|
749
748
|
streamAgent(agentId, prompt, options) {
|
|
750
|
-
const existingAgent = this.
|
|
749
|
+
const existingAgent = this.requireSessionAgent(agentId);
|
|
751
750
|
this.logger.trace({
|
|
752
751
|
agentId,
|
|
753
752
|
lifecycle: existingAgent.lifecycle,
|
|
@@ -852,8 +851,11 @@ export class AgentManager {
|
|
|
852
851
|
})();
|
|
853
852
|
return streamForwarder;
|
|
854
853
|
}
|
|
855
|
-
finalizeForegroundTurn(agent) {
|
|
854
|
+
finalizeForegroundTurn(agent, turnId) {
|
|
856
855
|
const mutableAgent = agent;
|
|
856
|
+
if (turnId) {
|
|
857
|
+
this.rememberFinalizedForegroundTurn(mutableAgent, turnId);
|
|
858
|
+
}
|
|
857
859
|
mutableAgent.activeForegroundTurnId = null;
|
|
858
860
|
const terminalError = mutableAgent.lastError;
|
|
859
861
|
const shouldHoldBusyForReplacement = mutableAgent.pendingReplacement && !terminalError;
|
|
@@ -887,8 +889,11 @@ export class AgentManager {
|
|
|
887
889
|
!this.hasPendingForegroundRun(agentId)) {
|
|
888
890
|
return this.streamAgent(agentId, prompt, options);
|
|
889
891
|
}
|
|
890
|
-
const agent =
|
|
892
|
+
const agent = this.requireSessionAgent(agentId);
|
|
891
893
|
agent.pendingReplacement = true;
|
|
894
|
+
agent.lifecycle = "running";
|
|
895
|
+
this.touchUpdatedAt(agent);
|
|
896
|
+
this.emitState(agent);
|
|
892
897
|
const self = this;
|
|
893
898
|
return (async function* replaceRunForwarder() {
|
|
894
899
|
try {
|
|
@@ -1026,7 +1031,7 @@ export class AgentManager {
|
|
|
1026
1031
|
}
|
|
1027
1032
|
}
|
|
1028
1033
|
async cancelAgentRun(agentId) {
|
|
1029
|
-
const agent = this.
|
|
1034
|
+
const agent = this.requireSessionAgent(agentId);
|
|
1030
1035
|
const pendingRun = this.getPendingForegroundRun(agentId);
|
|
1031
1036
|
const foregroundTurnId = agent.activeForegroundTurnId;
|
|
1032
1037
|
const hasForegroundTurn = Boolean(foregroundTurnId);
|
|
@@ -1034,12 +1039,7 @@ export class AgentManager {
|
|
|
1034
1039
|
if (!hasForegroundTurn && !isAutonomousRunning && !pendingRun) {
|
|
1035
1040
|
return false;
|
|
1036
1041
|
}
|
|
1037
|
-
|
|
1038
|
-
await agent.session.interrupt();
|
|
1039
|
-
}
|
|
1040
|
-
catch (error) {
|
|
1041
|
-
this.logger.error({ err: error, agentId }, "Failed to interrupt session");
|
|
1042
|
-
}
|
|
1042
|
+
await this.interruptSession(agent.session, agentId);
|
|
1043
1043
|
// The interrupt will produce a turn_canceled/turn_failed event via subscribe(),
|
|
1044
1044
|
// which flows through the session event dispatcher and settles the foreground turn waiter.
|
|
1045
1045
|
// Wait briefly for the event to propagate if there's an active foreground turn.
|
|
@@ -1110,28 +1110,60 @@ export class AgentManager {
|
|
|
1110
1110
|
}
|
|
1111
1111
|
return true;
|
|
1112
1112
|
}
|
|
1113
|
+
async interruptSession(session, agentId) {
|
|
1114
|
+
try {
|
|
1115
|
+
const result = await this.waitWithTimeout({
|
|
1116
|
+
operation: session.interrupt(),
|
|
1117
|
+
timeoutMs: this.rescueTimeouts.interruptSessionMs,
|
|
1118
|
+
onLateError: (error) => {
|
|
1119
|
+
this.logger.warn({ err: error, agentId }, "Session interrupt failed after timeout during cancel");
|
|
1120
|
+
},
|
|
1121
|
+
});
|
|
1122
|
+
if (result === "timed_out") {
|
|
1123
|
+
this.logger.warn({ agentId, timeoutMs: this.rescueTimeouts.interruptSessionMs }, "Timed out interrupting session during cancel");
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
catch (error) {
|
|
1127
|
+
this.logger.error({ err: error, agentId }, "Failed to interrupt session");
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1113
1130
|
getPendingPermissions(agentId) {
|
|
1114
|
-
const agent = this.
|
|
1131
|
+
const agent = this.requireSessionAgent(agentId);
|
|
1115
1132
|
return Array.from(agent.pendingPermissions.values());
|
|
1116
1133
|
}
|
|
1117
1134
|
peekPendingPermission(agent) {
|
|
1118
1135
|
const iterator = agent.pendingPermissions.values().next();
|
|
1119
1136
|
return iterator.done ? null : iterator.value;
|
|
1120
1137
|
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Hydrates the timeline from provider history if the agent's durable
|
|
1140
|
+
* timeline is empty (e.g., imported agents that have provider history
|
|
1141
|
+
* on disk but no persisted timeline rows). No-ops if already hydrated.
|
|
1142
|
+
*/
|
|
1121
1143
|
async hydrateTimelineFromProvider(agentId) {
|
|
1122
|
-
const agent = this.
|
|
1123
|
-
await this.
|
|
1144
|
+
const agent = this.requireSessionAgent(agentId);
|
|
1145
|
+
await this.hydrateTimelineFromLegacyProviderHistory(agent);
|
|
1124
1146
|
}
|
|
1125
|
-
|
|
1147
|
+
async deleteCommittedTimeline(agentId) {
|
|
1148
|
+
if (!this.durableTimelineStore) {
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
await this.durableTimelineStore.deleteAgent(agentId);
|
|
1152
|
+
}
|
|
1153
|
+
async getLastAssistantMessage(agentId) {
|
|
1126
1154
|
const agent = this.agents.get(agentId);
|
|
1127
1155
|
if (!agent) {
|
|
1128
1156
|
return null;
|
|
1129
1157
|
}
|
|
1130
|
-
return this.
|
|
1158
|
+
return await this.getLastAssistantMessageFromStores(agentId);
|
|
1131
1159
|
}
|
|
1132
1160
|
getLastAssistantMessageFromTimeline(timeline) {
|
|
1161
|
+
return this.getLastAssistantMessageSegmentFromTimeline(timeline)?.text ?? null;
|
|
1162
|
+
}
|
|
1163
|
+
getLastAssistantMessageSegmentFromTimeline(timeline) {
|
|
1133
1164
|
// Collect the last contiguous assistant messages (Claude streams chunks)
|
|
1134
1165
|
const chunks = [];
|
|
1166
|
+
let startsAtBeginning = false;
|
|
1135
1167
|
for (let i = timeline.length - 1; i >= 0; i--) {
|
|
1136
1168
|
const item = timeline[i];
|
|
1137
1169
|
if (item.type !== "assistant_message") {
|
|
@@ -1141,11 +1173,53 @@ export class AgentManager {
|
|
|
1141
1173
|
continue;
|
|
1142
1174
|
}
|
|
1143
1175
|
chunks.push(item.text);
|
|
1176
|
+
startsAtBeginning = i === 0;
|
|
1144
1177
|
}
|
|
1145
1178
|
if (!chunks.length) {
|
|
1146
1179
|
return null;
|
|
1147
1180
|
}
|
|
1148
|
-
return
|
|
1181
|
+
return {
|
|
1182
|
+
text: chunks.reverse().join(""),
|
|
1183
|
+
startsAtBeginning,
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
async getLastAssistantMessageFromStores(agentId) {
|
|
1187
|
+
const liveTimeline = this.timelineStore.getItems(agentId);
|
|
1188
|
+
const liveSegment = this.getLastAssistantMessageSegmentFromTimeline(liveTimeline);
|
|
1189
|
+
if (!this.durableTimelineStore) {
|
|
1190
|
+
return liveSegment?.text ?? null;
|
|
1191
|
+
}
|
|
1192
|
+
if (!liveSegment) {
|
|
1193
|
+
return await this.durableTimelineStore.getLastAssistantMessage(agentId);
|
|
1194
|
+
}
|
|
1195
|
+
if (!liveSegment.startsAtBeginning) {
|
|
1196
|
+
return liveSegment.text;
|
|
1197
|
+
}
|
|
1198
|
+
const lastDurableItem = await this.durableTimelineStore.getLastItem(agentId);
|
|
1199
|
+
if (lastDurableItem?.type !== "assistant_message") {
|
|
1200
|
+
return liveSegment.text;
|
|
1201
|
+
}
|
|
1202
|
+
const durableMessage = await this.durableTimelineStore.getLastAssistantMessage(agentId);
|
|
1203
|
+
return durableMessage ? `${durableMessage}${liveSegment.text}` : liveSegment.text;
|
|
1204
|
+
}
|
|
1205
|
+
async getLastItemFromStores(agentId) {
|
|
1206
|
+
const lastLiveItem = this.timelineStore.getLastItem(agentId);
|
|
1207
|
+
if (lastLiveItem) {
|
|
1208
|
+
return lastLiveItem;
|
|
1209
|
+
}
|
|
1210
|
+
if (!this.durableTimelineStore) {
|
|
1211
|
+
return null;
|
|
1212
|
+
}
|
|
1213
|
+
return await this.durableTimelineStore.getLastItem(agentId);
|
|
1214
|
+
}
|
|
1215
|
+
async hasCommittedUserMessageFromStores(agentId, options) {
|
|
1216
|
+
if (this.timelineStore.hasCommittedUserMessage(agentId, options)) {
|
|
1217
|
+
return true;
|
|
1218
|
+
}
|
|
1219
|
+
if (!this.durableTimelineStore) {
|
|
1220
|
+
return false;
|
|
1221
|
+
}
|
|
1222
|
+
return await this.durableTimelineStore.hasCommittedUserMessage(agentId, options);
|
|
1149
1223
|
}
|
|
1150
1224
|
async waitForAgentEvent(agentId, options) {
|
|
1151
1225
|
const snapshot = this.getAgent(agentId);
|
|
@@ -1159,7 +1233,7 @@ export class AgentManager {
|
|
|
1159
1233
|
return {
|
|
1160
1234
|
status: snapshot.lifecycle,
|
|
1161
1235
|
permission: immediatePermission,
|
|
1162
|
-
lastMessage: this.getLastAssistantMessage(agentId),
|
|
1236
|
+
lastMessage: await this.getLastAssistantMessage(agentId),
|
|
1163
1237
|
};
|
|
1164
1238
|
}
|
|
1165
1239
|
const initialStatus = snapshot.lifecycle;
|
|
@@ -1169,14 +1243,14 @@ export class AgentManager {
|
|
|
1169
1243
|
return {
|
|
1170
1244
|
status: initialStatus,
|
|
1171
1245
|
permission: null,
|
|
1172
|
-
lastMessage: this.getLastAssistantMessage(agentId),
|
|
1246
|
+
lastMessage: await this.getLastAssistantMessage(agentId),
|
|
1173
1247
|
};
|
|
1174
1248
|
}
|
|
1175
1249
|
if (waitForActive && !initialBusy && !hasForegroundTurn) {
|
|
1176
1250
|
return {
|
|
1177
1251
|
status: initialStatus,
|
|
1178
1252
|
permission: null,
|
|
1179
|
-
lastMessage: this.getLastAssistantMessage(agentId),
|
|
1253
|
+
lastMessage: await this.getLastAssistantMessage(agentId),
|
|
1180
1254
|
};
|
|
1181
1255
|
}
|
|
1182
1256
|
if (options?.signal?.aborted) {
|
|
@@ -1194,6 +1268,7 @@ export class AgentManager {
|
|
|
1194
1268
|
Boolean(snapshot.activeForegroundTurnId) ||
|
|
1195
1269
|
Boolean(pendingForegroundRun?.started);
|
|
1196
1270
|
let terminalStatusOverride = null;
|
|
1271
|
+
let finished = false;
|
|
1197
1272
|
// Bug #3 Fix: Declare unsubscribe and abortHandler upfront so cleanup can reference them
|
|
1198
1273
|
let unsubscribe = null;
|
|
1199
1274
|
let abortHandler = null;
|
|
@@ -1220,12 +1295,20 @@ export class AgentManager {
|
|
|
1220
1295
|
}
|
|
1221
1296
|
};
|
|
1222
1297
|
const finish = (permission) => {
|
|
1298
|
+
if (finished) {
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
finished = true;
|
|
1223
1302
|
cleanup();
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1303
|
+
void this.getLastAssistantMessage(agentId)
|
|
1304
|
+
.then((lastMessage) => {
|
|
1305
|
+
resolve({
|
|
1306
|
+
status: currentStatus,
|
|
1307
|
+
permission,
|
|
1308
|
+
lastMessage,
|
|
1309
|
+
});
|
|
1310
|
+
})
|
|
1311
|
+
.catch(reject);
|
|
1229
1312
|
};
|
|
1230
1313
|
// Bug #3 Fix: Set up abort handler BEFORE subscription
|
|
1231
1314
|
// to ensure cleanup handlers exist before callback can fire
|
|
@@ -1285,14 +1368,30 @@ export class AgentManager {
|
|
|
1285
1368
|
}
|
|
1286
1369
|
const initialPersistedTitle = await this.resolveInitialPersistedTitle(resolvedAgentId, config);
|
|
1287
1370
|
const now = new Date();
|
|
1288
|
-
const
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
:
|
|
1371
|
+
const explicitTimelineSeed = options?.timeline?.length ||
|
|
1372
|
+
options?.timelineRows?.length ||
|
|
1373
|
+
options?.timelineNextSeq !== undefined
|
|
1374
|
+
? {
|
|
1375
|
+
items: options?.timeline,
|
|
1376
|
+
rows: options?.timelineRows,
|
|
1377
|
+
nextSeq: options?.timelineNextSeq,
|
|
1378
|
+
timestamp: (options?.updatedAt ?? options?.createdAt ?? now).toISOString(),
|
|
1379
|
+
}
|
|
1380
|
+
: null;
|
|
1381
|
+
const shouldSeedFromDurable = !explicitTimelineSeed &&
|
|
1382
|
+
!this.timelineStore.has(resolvedAgentId) &&
|
|
1383
|
+
this.durableTimelineStore !== undefined;
|
|
1384
|
+
const durableTimelineSeed = shouldSeedFromDurable
|
|
1385
|
+
? await this.loadCommittedTimelineSeed(resolvedAgentId, now)
|
|
1386
|
+
: null;
|
|
1387
|
+
const durableTimelineHasRows = durableTimelineSeed != null && (durableTimelineSeed.nextSeq ?? 1) > 1;
|
|
1388
|
+
const timelineSeed = explicitTimelineSeed ?? durableTimelineSeed;
|
|
1389
|
+
if (timelineSeed || !this.timelineStore.has(resolvedAgentId)) {
|
|
1390
|
+
this.timelineStore.initialize(resolvedAgentId, timelineSeed ?? { timestamp: now.toISOString() });
|
|
1391
|
+
}
|
|
1392
|
+
if (options?.timelineRows?.length) {
|
|
1393
|
+
this.enqueueDurableTimelineBulkInsert(resolvedAgentId, options.timelineRows);
|
|
1394
|
+
}
|
|
1296
1395
|
const managed = {
|
|
1297
1396
|
id: resolvedAgentId,
|
|
1298
1397
|
provider: config.provider,
|
|
@@ -1312,13 +1411,10 @@ export class AgentManager {
|
|
|
1312
1411
|
pendingReplacement: false,
|
|
1313
1412
|
activeForegroundTurnId: null,
|
|
1314
1413
|
foregroundTurnWaiters: new Set(),
|
|
1414
|
+
finalizedForegroundTurnIds: new Set(),
|
|
1315
1415
|
unsubscribeSession: null,
|
|
1316
|
-
timeline: initialTimeline,
|
|
1317
|
-
timelineRows: initialTimelineRows,
|
|
1318
|
-
timelineEpoch: options?.timelineEpoch ?? randomUUID(),
|
|
1319
|
-
timelineNextSeq: derivedNextSeq,
|
|
1320
1416
|
persistence: attachPersistenceCwd(session.describePersistence(), config.cwd),
|
|
1321
|
-
historyPrimed: options?.historyPrimed ??
|
|
1417
|
+
historyPrimed: options?.historyPrimed ?? durableTimelineHasRows,
|
|
1322
1418
|
lastUserMessageAt: options?.lastUserMessageAt ?? null,
|
|
1323
1419
|
lastUsage: options?.lastUsage,
|
|
1324
1420
|
lastError: options?.lastError,
|
|
@@ -1339,36 +1435,99 @@ export class AgentManager {
|
|
|
1339
1435
|
this.previousStatuses.set(resolvedAgentId, managed.lifecycle);
|
|
1340
1436
|
await this.refreshRuntimeInfo(managed);
|
|
1341
1437
|
await this.persistSnapshot(managed, {
|
|
1438
|
+
workspaceId: options?.workspaceId,
|
|
1342
1439
|
title: initialPersistedTitle,
|
|
1343
1440
|
});
|
|
1344
|
-
this.emitState(managed);
|
|
1441
|
+
this.emitState(managed, { persist: false });
|
|
1345
1442
|
await this.refreshSessionState(managed);
|
|
1346
1443
|
managed.lifecycle = "idle";
|
|
1347
|
-
await this.persistSnapshot(managed);
|
|
1348
|
-
this.emitState(managed);
|
|
1444
|
+
await this.persistSnapshot(managed, { workspaceId: options?.workspaceId });
|
|
1445
|
+
this.emitState(managed, { persist: false });
|
|
1349
1446
|
this.subscribeToSession(managed);
|
|
1350
1447
|
return { ...managed };
|
|
1351
1448
|
}
|
|
1449
|
+
async loadCommittedTimelineSeed(agentId, now) {
|
|
1450
|
+
if (!this.durableTimelineStore) {
|
|
1451
|
+
return { timestamp: now.toISOString() };
|
|
1452
|
+
}
|
|
1453
|
+
return {
|
|
1454
|
+
nextSeq: (await this.durableTimelineStore.getLatestCommittedSeq(agentId)) + 1,
|
|
1455
|
+
timestamp: now.toISOString(),
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1458
|
+
prepareAgentForClosure(agent, cancelReason) {
|
|
1459
|
+
this.agentStreamCoalescer.flushAndDiscard(agent.id);
|
|
1460
|
+
this.agents.delete(agent.id);
|
|
1461
|
+
this.previousStatuses.delete(agent.id);
|
|
1462
|
+
if (agent.unsubscribeSession) {
|
|
1463
|
+
agent.unsubscribeSession();
|
|
1464
|
+
agent.unsubscribeSession = null;
|
|
1465
|
+
}
|
|
1466
|
+
for (const waiter of agent.foregroundTurnWaiters) {
|
|
1467
|
+
waiter.callback({
|
|
1468
|
+
type: "turn_canceled",
|
|
1469
|
+
provider: agent.provider,
|
|
1470
|
+
reason: cancelReason,
|
|
1471
|
+
turnId: waiter.turnId,
|
|
1472
|
+
});
|
|
1473
|
+
this.settleForegroundTurnWaiter(waiter);
|
|
1474
|
+
}
|
|
1475
|
+
agent.foregroundTurnWaiters.clear();
|
|
1476
|
+
this.settlePendingForegroundRun(agent.id);
|
|
1477
|
+
return {
|
|
1478
|
+
...agent,
|
|
1479
|
+
lifecycle: "closed",
|
|
1480
|
+
session: null,
|
|
1481
|
+
activeForegroundTurnId: null,
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1484
|
+
emitClosedAgent(agent, options) {
|
|
1485
|
+
this.emitState(agent, options);
|
|
1486
|
+
}
|
|
1352
1487
|
subscribeToSession(agent) {
|
|
1353
1488
|
if (agent.unsubscribeSession) {
|
|
1354
1489
|
return;
|
|
1355
1490
|
}
|
|
1356
1491
|
const agentId = agent.id;
|
|
1357
1492
|
const unsubscribe = agent.session.subscribe((event) => {
|
|
1493
|
+
this.enqueueSessionEvent(agentId, event);
|
|
1494
|
+
});
|
|
1495
|
+
agent.unsubscribeSession = unsubscribe;
|
|
1496
|
+
}
|
|
1497
|
+
enqueueSessionEvent(agentId, event) {
|
|
1498
|
+
const previous = this.sessionEventTails.get(agentId) ?? Promise.resolve();
|
|
1499
|
+
const next = previous
|
|
1500
|
+
.catch(() => undefined)
|
|
1501
|
+
.then(async () => {
|
|
1358
1502
|
const current = this.agents.get(agentId);
|
|
1359
1503
|
if (!current) {
|
|
1360
1504
|
return;
|
|
1361
1505
|
}
|
|
1362
|
-
|
|
1506
|
+
if (current.session == null) {
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1509
|
+
await this.dispatchSessionEvent(current, event);
|
|
1510
|
+
})
|
|
1511
|
+
.catch((err) => {
|
|
1512
|
+
this.logger.error({ err, agentId, eventType: event.type }, "Failed to process session event");
|
|
1513
|
+
});
|
|
1514
|
+
this.sessionEventTails.set(agentId, next);
|
|
1515
|
+
this.trackBackgroundTask(next);
|
|
1516
|
+
void next.finally(() => {
|
|
1517
|
+
if (this.sessionEventTails.get(agentId) === next) {
|
|
1518
|
+
this.sessionEventTails.delete(agentId);
|
|
1519
|
+
}
|
|
1363
1520
|
});
|
|
1364
|
-
agent.unsubscribeSession = unsubscribe;
|
|
1365
1521
|
}
|
|
1366
|
-
dispatchSessionEvent(agent, event) {
|
|
1522
|
+
async dispatchSessionEvent(agent, event) {
|
|
1367
1523
|
const turnId = event.turnId;
|
|
1368
1524
|
const matchingWaiters = turnId == null
|
|
1369
1525
|
? []
|
|
1370
1526
|
: Array.from(agent.foregroundTurnWaiters).filter((waiter) => waiter.turnId === turnId && !waiter.settled);
|
|
1371
|
-
this.handleStreamEvent(agent, event);
|
|
1527
|
+
const shouldNotifyWaiters = await this.handleStreamEvent(agent, event);
|
|
1528
|
+
if (!shouldNotifyWaiters) {
|
|
1529
|
+
return;
|
|
1530
|
+
}
|
|
1372
1531
|
for (const waiter of matchingWaiters) {
|
|
1373
1532
|
waiter.callback(event);
|
|
1374
1533
|
if (isTurnTerminalEvent(event)) {
|
|
@@ -1383,6 +1542,16 @@ export class AgentManager {
|
|
|
1383
1542
|
waiter.settled = true;
|
|
1384
1543
|
waiter.resolveSettled();
|
|
1385
1544
|
}
|
|
1545
|
+
rememberFinalizedForegroundTurn(agent, turnId) {
|
|
1546
|
+
agent.finalizedForegroundTurnIds.add(turnId);
|
|
1547
|
+
if (agent.finalizedForegroundTurnIds.size <= 50) {
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
const oldest = agent.finalizedForegroundTurnIds.values().next().value;
|
|
1551
|
+
if (oldest) {
|
|
1552
|
+
agent.finalizedForegroundTurnIds.delete(oldest);
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1386
1555
|
createPendingForegroundRun() {
|
|
1387
1556
|
let resolveSettled;
|
|
1388
1557
|
const settledPromise = new Promise((resolve) => {
|
|
@@ -1427,31 +1596,6 @@ export class AgentManager {
|
|
|
1427
1596
|
}
|
|
1428
1597
|
return null;
|
|
1429
1598
|
}
|
|
1430
|
-
buildTimelineRowsFromItems(items, startSeq, timestamp) {
|
|
1431
|
-
let nextSeq = startSeq;
|
|
1432
|
-
return items.map((item) => {
|
|
1433
|
-
const row = {
|
|
1434
|
-
seq: nextSeq,
|
|
1435
|
-
timestamp,
|
|
1436
|
-
item,
|
|
1437
|
-
};
|
|
1438
|
-
nextSeq += 1;
|
|
1439
|
-
return row;
|
|
1440
|
-
});
|
|
1441
|
-
}
|
|
1442
|
-
ensureTimelineState(agent) {
|
|
1443
|
-
const minSeq = agent.timelineRows.length ? agent.timelineRows[0].seq : 0;
|
|
1444
|
-
const maxSeq = agent.timelineRows.length
|
|
1445
|
-
? agent.timelineRows[agent.timelineRows.length - 1].seq
|
|
1446
|
-
: 0;
|
|
1447
|
-
return {
|
|
1448
|
-
rows: agent.timelineRows,
|
|
1449
|
-
epoch: agent.timelineEpoch,
|
|
1450
|
-
nextSeq: agent.timelineNextSeq,
|
|
1451
|
-
minSeq,
|
|
1452
|
-
maxSeq,
|
|
1453
|
-
};
|
|
1454
|
-
}
|
|
1455
1599
|
async persistSnapshot(agent, options) {
|
|
1456
1600
|
if (!this.registry) {
|
|
1457
1601
|
return;
|
|
@@ -1460,8 +1604,18 @@ export class AgentManager {
|
|
|
1460
1604
|
if (agent.internal) {
|
|
1461
1605
|
return;
|
|
1462
1606
|
}
|
|
1607
|
+
if (options?.workspaceId !== undefined) {
|
|
1608
|
+
await this.registry.applySnapshot(agent, options.workspaceId, options);
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1463
1611
|
await this.registry.applySnapshot(agent, options);
|
|
1464
1612
|
}
|
|
1613
|
+
requireRegistry() {
|
|
1614
|
+
if (!this.registry) {
|
|
1615
|
+
throw new Error("Agent storage unavailable");
|
|
1616
|
+
}
|
|
1617
|
+
return this.registry;
|
|
1618
|
+
}
|
|
1465
1619
|
async refreshSessionState(agent) {
|
|
1466
1620
|
try {
|
|
1467
1621
|
const modes = await agent.session.getAvailableModes();
|
|
@@ -1506,42 +1660,66 @@ export class AgentManager {
|
|
|
1506
1660
|
// Keep existing runtimeInfo if refresh fails.
|
|
1507
1661
|
}
|
|
1508
1662
|
}
|
|
1509
|
-
async
|
|
1663
|
+
async hydrateTimelineFromLegacyProviderHistory(agent) {
|
|
1510
1664
|
if (agent.historyPrimed) {
|
|
1511
1665
|
return;
|
|
1512
1666
|
}
|
|
1513
1667
|
agent.historyPrimed = true;
|
|
1514
|
-
const canonicalUserMessagesById =
|
|
1515
|
-
if (row.item.type !== "user_message") {
|
|
1516
|
-
return [];
|
|
1517
|
-
}
|
|
1518
|
-
const messageId = normalizeMessageId(row.item.messageId);
|
|
1519
|
-
if (!messageId) {
|
|
1520
|
-
return [];
|
|
1521
|
-
}
|
|
1522
|
-
return [[messageId, row.item.text]];
|
|
1523
|
-
}));
|
|
1668
|
+
const canonicalUserMessagesById = this.timelineStore.getCanonicalUserMessagesById(agent.id);
|
|
1524
1669
|
try {
|
|
1525
1670
|
for await (const event of agent.session.streamHistory()) {
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1671
|
+
if (event.type !== "timeline") {
|
|
1672
|
+
continue;
|
|
1673
|
+
}
|
|
1674
|
+
if (event.item.type === "user_message") {
|
|
1675
|
+
const eventMessageId = normalizeMessageId(event.item.messageId);
|
|
1676
|
+
if (eventMessageId) {
|
|
1677
|
+
const canonicalText = canonicalUserMessagesById.get(eventMessageId);
|
|
1678
|
+
if (canonicalText === event.item.text) {
|
|
1679
|
+
continue;
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
this.recordTimeline(agent.id, event.item);
|
|
1530
1684
|
}
|
|
1531
1685
|
}
|
|
1532
1686
|
catch {
|
|
1533
1687
|
// ignore history failures
|
|
1534
1688
|
}
|
|
1535
1689
|
}
|
|
1536
|
-
|
|
1690
|
+
notifyForegroundTurnWaiters(agentId, event) {
|
|
1691
|
+
const turnId = event.turnId;
|
|
1692
|
+
if (turnId == null) {
|
|
1693
|
+
return;
|
|
1694
|
+
}
|
|
1695
|
+
const agent = this.agents.get(agentId);
|
|
1696
|
+
if (!agent) {
|
|
1697
|
+
return;
|
|
1698
|
+
}
|
|
1699
|
+
for (const waiter of agent.foregroundTurnWaiters) {
|
|
1700
|
+
if (waiter.turnId === turnId && !waiter.settled) {
|
|
1701
|
+
waiter.callback(event);
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
async handleStreamEvent(agent, event, options) {
|
|
1537
1706
|
const eventTurnId = event.turnId;
|
|
1538
1707
|
const isForegroundEvent = Boolean(eventTurnId && agent.activeForegroundTurnId === eventTurnId);
|
|
1708
|
+
if (eventTurnId &&
|
|
1709
|
+
isTurnTerminalEvent(event) &&
|
|
1710
|
+
agent.finalizedForegroundTurnIds.has(eventTurnId)) {
|
|
1711
|
+
return false;
|
|
1712
|
+
}
|
|
1539
1713
|
// Only update timestamp for live events, not history replay
|
|
1540
1714
|
if (!options?.fromHistory) {
|
|
1541
1715
|
this.touchUpdatedAt(agent);
|
|
1716
|
+
if (this.agentStreamCoalescer.handle(agent.id, event)) {
|
|
1717
|
+
return false;
|
|
1718
|
+
}
|
|
1719
|
+
this.agentStreamCoalescer.flushFor(agent.id);
|
|
1542
1720
|
}
|
|
1543
|
-
let timelineRow = null;
|
|
1544
1721
|
let shouldDispatchEvent = true;
|
|
1722
|
+
let shouldNotifyWaiters = true;
|
|
1545
1723
|
switch (event.type) {
|
|
1546
1724
|
case "thread_started":
|
|
1547
1725
|
{
|
|
@@ -1561,38 +1739,43 @@ export class AgentManager {
|
|
|
1561
1739
|
this.emitState(agent);
|
|
1562
1740
|
break;
|
|
1563
1741
|
case "timeline":
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1742
|
+
{
|
|
1743
|
+
// Skip provider-replayed user_message items during history hydration.
|
|
1744
|
+
if (options?.fromHistory && event.item.type === "user_message") {
|
|
1745
|
+
const eventMessageId = normalizeMessageId(event.item.messageId);
|
|
1746
|
+
if (eventMessageId) {
|
|
1747
|
+
const canonicalText = options?.canonicalUserMessagesById?.get(eventMessageId);
|
|
1748
|
+
if (canonicalText === event.item.text) {
|
|
1749
|
+
shouldDispatchEvent = false;
|
|
1750
|
+
shouldNotifyWaiters = false;
|
|
1751
|
+
break;
|
|
1752
|
+
}
|
|
1571
1753
|
}
|
|
1572
1754
|
}
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
if (row.item.type !== "user_message") {
|
|
1582
|
-
return false;
|
|
1583
|
-
}
|
|
1584
|
-
const rowMessageId = normalizeMessageId(row.item.messageId);
|
|
1585
|
-
return rowMessageId === eventMessageId && row.item.text === eventText;
|
|
1586
|
-
});
|
|
1587
|
-
if (alreadyRecorded) {
|
|
1755
|
+
// Suppress user_message echoes for the active foreground turn.
|
|
1756
|
+
if (!options?.fromHistory && event.item.type === "user_message" && isForegroundEvent) {
|
|
1757
|
+
const eventMessageId = normalizeMessageId(event.item.messageId);
|
|
1758
|
+
if (eventMessageId &&
|
|
1759
|
+
(await this.hasCommittedUserMessageFromStores(agent.id, {
|
|
1760
|
+
messageId: eventMessageId,
|
|
1761
|
+
text: event.item.text,
|
|
1762
|
+
}))) {
|
|
1588
1763
|
break;
|
|
1589
1764
|
}
|
|
1590
1765
|
}
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1766
|
+
if (options?.fromHistory) {
|
|
1767
|
+
this.recordTimeline(agent.id, event.item);
|
|
1768
|
+
shouldDispatchEvent = false;
|
|
1769
|
+
shouldNotifyWaiters = false;
|
|
1770
|
+
break;
|
|
1771
|
+
}
|
|
1772
|
+
this.recordAndDispatchTimelineItem(agent.id, event.item, event.provider, event.turnId);
|
|
1773
|
+
if (event.item.type === "user_message") {
|
|
1774
|
+
agent.lastUserMessageAt = new Date();
|
|
1775
|
+
this.emitState(agent);
|
|
1776
|
+
}
|
|
1777
|
+
shouldDispatchEvent = false;
|
|
1778
|
+
shouldNotifyWaiters = true;
|
|
1596
1779
|
}
|
|
1597
1780
|
break;
|
|
1598
1781
|
case "turn_completed":
|
|
@@ -1627,7 +1810,7 @@ export class AgentManager {
|
|
|
1627
1810
|
agent.lifecycle = "error";
|
|
1628
1811
|
}
|
|
1629
1812
|
agent.lastError = event.error;
|
|
1630
|
-
this.appendSystemErrorTimelineMessage(agent, event.provider, this.formatTurnFailedMessage(event), options);
|
|
1813
|
+
await this.appendSystemErrorTimelineMessage(agent, event.provider, this.formatTurnFailedMessage(event), options);
|
|
1631
1814
|
for (const [requestId] of agent.pendingPermissions) {
|
|
1632
1815
|
agent.pendingPermissions.delete(requestId);
|
|
1633
1816
|
if (!options?.fromHistory) {
|
|
@@ -1707,19 +1890,29 @@ export class AgentManager {
|
|
|
1707
1890
|
break;
|
|
1708
1891
|
}
|
|
1709
1892
|
if (!options?.fromHistory && isForegroundEvent && isTurnTerminalEvent(event)) {
|
|
1710
|
-
this.finalizeForegroundTurn(agent);
|
|
1893
|
+
this.finalizeForegroundTurn(agent, eventTurnId);
|
|
1711
1894
|
}
|
|
1712
1895
|
// Skip dispatching individual stream events during history replay.
|
|
1713
1896
|
if (!options?.fromHistory && shouldDispatchEvent) {
|
|
1714
|
-
this.dispatchStream(agent.id, event
|
|
1715
|
-
? {
|
|
1716
|
-
seq: timelineRow.seq,
|
|
1717
|
-
epoch: this.ensureTimelineState(agent).epoch,
|
|
1718
|
-
}
|
|
1719
|
-
: undefined);
|
|
1897
|
+
this.dispatchStream(agent.id, event);
|
|
1720
1898
|
}
|
|
1899
|
+
return shouldNotifyWaiters;
|
|
1900
|
+
}
|
|
1901
|
+
recordAndDispatchTimelineItem(agentId, item, provider, turnId) {
|
|
1902
|
+
const row = this.recordTimeline(agentId, item);
|
|
1903
|
+
const event = {
|
|
1904
|
+
type: "timeline",
|
|
1905
|
+
item,
|
|
1906
|
+
provider,
|
|
1907
|
+
...(turnId !== undefined ? { turnId } : {}),
|
|
1908
|
+
};
|
|
1909
|
+
this.dispatchStream(agentId, event, {
|
|
1910
|
+
seq: row.seq,
|
|
1911
|
+
epoch: this.timelineStore.getEpoch(agentId),
|
|
1912
|
+
});
|
|
1913
|
+
return event;
|
|
1721
1914
|
}
|
|
1722
|
-
appendSystemErrorTimelineMessage(agent, provider, message, options) {
|
|
1915
|
+
async appendSystemErrorTimelineMessage(agent, provider, message, options) {
|
|
1723
1916
|
if (options?.fromHistory) {
|
|
1724
1917
|
return;
|
|
1725
1918
|
}
|
|
@@ -1728,19 +1921,19 @@ export class AgentManager {
|
|
|
1728
1921
|
return;
|
|
1729
1922
|
}
|
|
1730
1923
|
const text = `${SYSTEM_ERROR_PREFIX} ${normalized}`;
|
|
1731
|
-
const lastItem =
|
|
1924
|
+
const lastItem = await this.getLastItemFromStores(agent.id);
|
|
1732
1925
|
if (lastItem?.type === "assistant_message" && lastItem.text === text) {
|
|
1733
1926
|
return;
|
|
1734
1927
|
}
|
|
1735
1928
|
const item = { type: "assistant_message", text };
|
|
1736
|
-
const row = this.recordTimeline(agent, item);
|
|
1929
|
+
const row = this.recordTimeline(agent.id, item);
|
|
1737
1930
|
this.dispatchStream(agent.id, {
|
|
1738
1931
|
type: "timeline",
|
|
1739
1932
|
item,
|
|
1740
1933
|
provider,
|
|
1741
1934
|
}, {
|
|
1742
1935
|
seq: row.seq,
|
|
1743
|
-
epoch: this.
|
|
1936
|
+
epoch: this.timelineStore.getEpoch(agent.id),
|
|
1744
1937
|
});
|
|
1745
1938
|
}
|
|
1746
1939
|
formatTurnFailedMessage(event) {
|
|
@@ -1756,27 +1949,17 @@ export class AgentManager {
|
|
|
1756
1949
|
}
|
|
1757
1950
|
return parts.join("\n\n");
|
|
1758
1951
|
}
|
|
1759
|
-
recordTimeline(
|
|
1760
|
-
const
|
|
1761
|
-
|
|
1762
|
-
seq: timelineState.nextSeq,
|
|
1763
|
-
timestamp: new Date().toISOString(),
|
|
1764
|
-
item,
|
|
1765
|
-
};
|
|
1766
|
-
agent.timelineNextSeq = timelineState.nextSeq + 1;
|
|
1767
|
-
agent.timeline.push(item);
|
|
1768
|
-
timelineState.rows.push(row);
|
|
1769
|
-
if (typeof this.maxTimelineItems === "number" &&
|
|
1770
|
-
agent.timeline.length > this.maxTimelineItems) {
|
|
1771
|
-
const removeCount = agent.timeline.length - this.maxTimelineItems;
|
|
1772
|
-
agent.timeline.splice(0, removeCount);
|
|
1773
|
-
timelineState.rows.splice(0, removeCount);
|
|
1774
|
-
}
|
|
1952
|
+
recordTimeline(agentId, item) {
|
|
1953
|
+
const row = this.timelineStore.append(agentId, item);
|
|
1954
|
+
this.enqueueDurableTimelineAppend(agentId, row);
|
|
1775
1955
|
return row;
|
|
1776
1956
|
}
|
|
1777
|
-
emitState(agent) {
|
|
1957
|
+
emitState(agent, options) {
|
|
1778
1958
|
// Keep attention as an edge-triggered unread signal, not a level signal.
|
|
1779
1959
|
this.checkAndSetAttention(agent);
|
|
1960
|
+
if (options?.persist !== false) {
|
|
1961
|
+
this.enqueueBackgroundPersist(agent);
|
|
1962
|
+
}
|
|
1780
1963
|
this.syncFeaturesFromSession(agent);
|
|
1781
1964
|
this.dispatch({
|
|
1782
1965
|
type: "agent_state",
|
|
@@ -1809,7 +1992,6 @@ export class AgentManager {
|
|
|
1809
1992
|
attentionTimestamp: new Date(),
|
|
1810
1993
|
};
|
|
1811
1994
|
this.broadcastAgentAttention(agent, "finished");
|
|
1812
|
-
this.enqueueBackgroundPersist(agent);
|
|
1813
1995
|
return;
|
|
1814
1996
|
}
|
|
1815
1997
|
// Check if agent entered error state
|
|
@@ -1820,7 +2002,6 @@ export class AgentManager {
|
|
|
1820
2002
|
attentionTimestamp: new Date(),
|
|
1821
2003
|
};
|
|
1822
2004
|
this.broadcastAgentAttention(agent, "error");
|
|
1823
|
-
this.enqueueBackgroundPersist(agent);
|
|
1824
2005
|
return;
|
|
1825
2006
|
}
|
|
1826
2007
|
}
|
|
@@ -1830,6 +2011,27 @@ export class AgentManager {
|
|
|
1830
2011
|
});
|
|
1831
2012
|
this.trackBackgroundTask(task);
|
|
1832
2013
|
}
|
|
2014
|
+
enqueueDurableTimelineAppend(agentId, row) {
|
|
2015
|
+
if (!this.durableTimelineStore) {
|
|
2016
|
+
return;
|
|
2017
|
+
}
|
|
2018
|
+
const task = this.durableTimelineStore
|
|
2019
|
+
.bulkInsert(agentId, [row])
|
|
2020
|
+
.then(() => undefined)
|
|
2021
|
+
.catch((err) => {
|
|
2022
|
+
this.logger.error({ err, agentId, seq: row.seq, itemType: row.item.type }, "Failed to append timeline row to durable store");
|
|
2023
|
+
});
|
|
2024
|
+
this.trackBackgroundTask(task);
|
|
2025
|
+
}
|
|
2026
|
+
enqueueDurableTimelineBulkInsert(agentId, rows) {
|
|
2027
|
+
if (!this.durableTimelineStore || rows.length === 0) {
|
|
2028
|
+
return;
|
|
2029
|
+
}
|
|
2030
|
+
const task = this.durableTimelineStore.bulkInsert(agentId, rows).catch((err) => {
|
|
2031
|
+
this.logger.error({ err, agentId, rowCount: rows.length }, "Failed to seed durable timeline store");
|
|
2032
|
+
});
|
|
2033
|
+
this.trackBackgroundTask(task);
|
|
2034
|
+
}
|
|
1833
2035
|
trackBackgroundTask(task) {
|
|
1834
2036
|
this.backgroundTasks.add(task);
|
|
1835
2037
|
void task.finally(() => {
|
|
@@ -1841,6 +2043,7 @@ export class AgentManager {
|
|
|
1841
2043
|
* Used by daemon shutdown paths to avoid unhandled rejections after cleanup.
|
|
1842
2044
|
*/
|
|
1843
2045
|
async flush() {
|
|
2046
|
+
this.agentStreamCoalescer.flushAll();
|
|
1844
2047
|
// Drain tasks, including tasks spawned while awaiting.
|
|
1845
2048
|
while (this.backgroundTasks.size > 0) {
|
|
1846
2049
|
const pending = Array.from(this.backgroundTasks);
|
|
@@ -1915,7 +2118,7 @@ export class AgentManager {
|
|
|
1915
2118
|
const client = this.clients.get(normalized.provider);
|
|
1916
2119
|
if (client) {
|
|
1917
2120
|
try {
|
|
1918
|
-
const models = await client.listModels();
|
|
2121
|
+
const models = await client.listModels({ cwd: normalized.cwd, force: false });
|
|
1919
2122
|
const defaultModel = models.find((model) => model.isDefault) ?? models[0];
|
|
1920
2123
|
if (defaultModel) {
|
|
1921
2124
|
normalized.model = defaultModel.id;
|
|
@@ -1959,5 +2162,12 @@ export class AgentManager {
|
|
|
1959
2162
|
}
|
|
1960
2163
|
return agent;
|
|
1961
2164
|
}
|
|
2165
|
+
requireSessionAgent(id) {
|
|
2166
|
+
const agent = this.requireAgent(id);
|
|
2167
|
+
if (agent.session === null) {
|
|
2168
|
+
throw new Error(`Agent '${agent.id}' has no managed session`);
|
|
2169
|
+
}
|
|
2170
|
+
return agent;
|
|
2171
|
+
}
|
|
1962
2172
|
}
|
|
1963
2173
|
//# sourceMappingURL=agent-manager.js.map
|