@getpaseo/server 0.1.3 → 0.1.6
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/client/daemon-client-relay-e2ee-transport.d.ts +8 -0
- package/dist/server/client/daemon-client-relay-e2ee-transport.d.ts.map +1 -0
- package/dist/server/client/daemon-client-relay-e2ee-transport.js +161 -0
- package/dist/server/client/daemon-client-relay-e2ee-transport.js.map +1 -0
- package/dist/server/client/daemon-client-terminal-stream-manager.d.ts +43 -0
- package/dist/server/client/daemon-client-terminal-stream-manager.d.ts.map +1 -0
- package/dist/server/client/daemon-client-terminal-stream-manager.js +134 -0
- package/dist/server/client/daemon-client-terminal-stream-manager.js.map +1 -0
- package/dist/server/client/daemon-client-transport-types.d.ts +34 -0
- package/dist/server/client/daemon-client-transport-types.d.ts.map +1 -0
- package/dist/server/client/daemon-client-transport-types.js +2 -0
- package/dist/server/client/daemon-client-transport-types.js.map +1 -0
- package/dist/server/client/daemon-client-transport-utils.d.ts +9 -0
- package/dist/server/client/daemon-client-transport-utils.d.ts.map +1 -0
- package/dist/server/client/daemon-client-transport-utils.js +121 -0
- package/dist/server/client/daemon-client-transport-utils.js.map +1 -0
- package/dist/server/client/daemon-client-transport.d.ts +5 -0
- package/dist/server/client/daemon-client-transport.d.ts.map +1 -0
- package/dist/server/client/daemon-client-transport.js +4 -0
- package/dist/server/client/daemon-client-transport.js.map +1 -0
- package/dist/server/client/daemon-client-websocket-transport.d.ts +7 -0
- package/dist/server/client/daemon-client-websocket-transport.d.ts.map +1 -0
- package/dist/server/client/daemon-client-websocket-transport.js +64 -0
- package/dist/server/client/daemon-client-websocket-transport.js.map +1 -0
- package/dist/server/client/daemon-client.d.ts +63 -46
- package/dist/server/client/daemon-client.d.ts.map +1 -1
- package/dist/server/client/daemon-client.js +497 -796
- package/dist/server/client/daemon-client.js.map +1 -1
- package/dist/server/server/agent/agent-management-mcp.d.ts +2 -0
- package/dist/server/server/agent/agent-management-mcp.d.ts.map +1 -1
- package/dist/server/server/agent/agent-management-mcp.js +29 -4
- package/dist/server/server/agent/agent-management-mcp.js.map +1 -1
- package/dist/server/server/agent/agent-manager.d.ts +48 -0
- package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
- package/dist/server/server/agent/agent-manager.js +224 -14
- package/dist/server/server/agent/agent-manager.js.map +1 -1
- package/dist/server/server/agent/agent-sdk-types.d.ts +14 -0
- package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
- package/dist/server/server/agent/agent-storage.d.ts +4 -4
- package/dist/server/server/agent/mcp-server.d.ts +2 -0
- package/dist/server/server/agent/mcp-server.d.ts.map +1 -1
- package/dist/server/server/agent/mcp-server.js +30 -5
- package/dist/server/server/agent/mcp-server.js.map +1 -1
- package/dist/server/server/agent/providers/claude/tool-call-mapper.d.ts.map +1 -1
- package/dist/server/server/agent/providers/claude/tool-call-mapper.js +120 -6
- package/dist/server/server/agent/providers/claude/tool-call-mapper.js.map +1 -1
- package/dist/server/server/agent/providers/claude-agent.d.ts +7 -0
- package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/claude-agent.js +83 -9
- package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
- 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 +25 -0
- package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +42 -0
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts.map +1 -1
- package/dist/server/server/agent/stt-manager.d.ts +3 -2
- package/dist/server/server/agent/stt-manager.d.ts.map +1 -1
- package/dist/server/server/agent/stt-manager.js +5 -3
- package/dist/server/server/agent/stt-manager.js.map +1 -1
- package/dist/server/server/agent/timeline-append.d.ts +10 -0
- package/dist/server/server/agent/timeline-append.d.ts.map +1 -0
- package/dist/server/server/agent/timeline-append.js +27 -0
- package/dist/server/server/agent/timeline-append.js.map +1 -0
- package/dist/server/server/agent/timeline-projection.d.ts +19 -0
- package/dist/server/server/agent/timeline-projection.d.ts.map +1 -0
- package/dist/server/server/agent/timeline-projection.js +142 -0
- package/dist/server/server/agent/timeline-projection.js.map +1 -0
- package/dist/server/server/agent/tts-manager.d.ts +3 -2
- package/dist/server/server/agent/tts-manager.d.ts.map +1 -1
- package/dist/server/server/agent/tts-manager.js +5 -3
- package/dist/server/server/agent/tts-manager.js.map +1 -1
- package/dist/server/server/agent-attention-policy.d.ts +20 -0
- package/dist/server/server/agent-attention-policy.d.ts.map +1 -0
- package/dist/server/server/agent-attention-policy.js +40 -0
- package/dist/server/server/agent-attention-policy.js.map +1 -0
- package/dist/server/server/bootstrap.d.ts.map +1 -1
- package/dist/server/server/bootstrap.js +16 -18
- package/dist/server/server/bootstrap.js.map +1 -1
- package/dist/server/server/dictation/dictation-stream-manager.d.ts +10 -2
- package/dist/server/server/dictation/dictation-stream-manager.d.ts.map +1 -1
- package/dist/server/server/dictation/dictation-stream-manager.js +81 -14
- package/dist/server/server/dictation/dictation-stream-manager.js.map +1 -1
- package/dist/server/server/exports.d.ts +1 -1
- package/dist/server/server/exports.d.ts.map +1 -1
- package/dist/server/server/persisted-config.d.ts +12 -12
- package/dist/server/server/relay-transport.d.ts +3 -2
- package/dist/server/server/relay-transport.d.ts.map +1 -1
- package/dist/server/server/relay-transport.js +21 -5
- package/dist/server/server/relay-transport.js.map +1 -1
- package/dist/server/server/session.d.ts +51 -14
- package/dist/server/server/session.d.ts.map +1 -1
- package/dist/server/server/session.js +872 -250
- package/dist/server/server/session.js.map +1 -1
- package/dist/server/server/speech/provider-resolver.d.ts +3 -0
- package/dist/server/server/speech/provider-resolver.d.ts.map +1 -0
- package/dist/server/server/speech/provider-resolver.js +7 -0
- package/dist/server/server/speech/provider-resolver.js.map +1 -0
- package/dist/server/server/speech/providers/local/runtime.d.ts +1 -0
- package/dist/server/server/speech/providers/local/runtime.d.ts.map +1 -1
- package/dist/server/server/speech/providers/local/runtime.js +4 -3
- package/dist/server/server/speech/providers/local/runtime.js.map +1 -1
- package/dist/server/server/speech/providers/local/sherpa/model-downloader.d.ts.map +1 -1
- package/dist/server/server/speech/providers/local/sherpa/model-downloader.js +3 -66
- package/dist/server/server/speech/providers/local/sherpa/model-downloader.js.map +1 -1
- package/dist/server/server/speech/speech-runtime.d.ts +26 -3
- package/dist/server/server/speech/speech-runtime.d.ts.map +1 -1
- package/dist/server/server/speech/speech-runtime.js +466 -112
- package/dist/server/server/speech/speech-runtime.js.map +1 -1
- package/dist/server/server/websocket-server.d.ts +23 -7
- package/dist/server/server/websocket-server.d.ts.map +1 -1
- package/dist/server/server/websocket-server.js +288 -64
- package/dist/server/server/websocket-server.js.map +1 -1
- package/dist/server/server/worktree-bootstrap.d.ts +29 -0
- package/dist/server/server/worktree-bootstrap.d.ts.map +1 -0
- package/dist/server/server/worktree-bootstrap.js +407 -0
- package/dist/server/server/worktree-bootstrap.js.map +1 -0
- package/dist/server/shared/binary-mux.d.ts +31 -0
- package/dist/server/shared/binary-mux.d.ts.map +1 -0
- package/dist/server/shared/binary-mux.js +114 -0
- package/dist/server/shared/binary-mux.js.map +1 -0
- package/dist/server/shared/messages.d.ts +6437 -5839
- package/dist/server/shared/messages.d.ts.map +1 -1
- package/dist/server/shared/messages.js +238 -50
- package/dist/server/shared/messages.js.map +1 -1
- package/dist/server/shared/terminal-key-input.d.ts +9 -0
- package/dist/server/shared/terminal-key-input.d.ts.map +1 -0
- package/dist/server/shared/terminal-key-input.js +132 -0
- package/dist/server/shared/terminal-key-input.js.map +1 -0
- package/dist/server/shared/tool-call-display.d.ts.map +1 -1
- package/dist/server/shared/tool-call-display.js +4 -0
- package/dist/server/shared/tool-call-display.js.map +1 -1
- package/dist/server/terminal/terminal-manager.d.ts +11 -0
- package/dist/server/terminal/terminal-manager.d.ts.map +1 -1
- package/dist/server/terminal/terminal-manager.js +75 -24
- package/dist/server/terminal/terminal-manager.js.map +1 -1
- package/dist/server/terminal/terminal.d.ts +18 -0
- package/dist/server/terminal/terminal.d.ts.map +1 -1
- package/dist/server/terminal/terminal.js +142 -5
- package/dist/server/terminal/terminal.js.map +1 -1
- package/dist/server/utils/checkout-git.d.ts +4 -0
- package/dist/server/utils/checkout-git.d.ts.map +1 -1
- package/dist/server/utils/checkout-git.js +92 -0
- package/dist/server/utils/checkout-git.js.map +1 -1
- package/dist/server/utils/worktree.d.ts +32 -0
- package/dist/server/utils/worktree.d.ts.map +1 -1
- package/dist/server/utils/worktree.js +160 -10
- package/dist/server/utils/worktree.js.map +1 -1
- package/package.json +2 -2
|
@@ -6,6 +6,7 @@ import { promisify } from "util";
|
|
|
6
6
|
import { join, resolve, sep } from "path";
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
import { serializeAgentStreamEvent, } from "./messages.js";
|
|
9
|
+
import { BinaryMuxChannel, TerminalBinaryFlags, TerminalBinaryMessageType, } from "../shared/binary-mux.js";
|
|
9
10
|
import { TTSManager } from "./agent/tts-manager.js";
|
|
10
11
|
import { STTManager } from "./agent/stt-manager.js";
|
|
11
12
|
import { maybePersistTtsDebugAudio } from "./agent/tts-debug.js";
|
|
@@ -16,13 +17,16 @@ import { experimental_createMCPClient } from "ai";
|
|
|
16
17
|
import { buildProviderRegistry } from "./agent/provider-registry.js";
|
|
17
18
|
import { scheduleAgentMetadataGeneration } from "./agent/agent-metadata-generator.js";
|
|
18
19
|
import { toAgentPayload } from "./agent/agent-projections.js";
|
|
20
|
+
import { appendTimelineItemIfAgentKnown, emitLiveTimelineItemIfAgentKnown, } from "./agent/timeline-append.js";
|
|
21
|
+
import { projectTimelineRows } from "./agent/timeline-projection.js";
|
|
19
22
|
import { StructuredAgentResponseError, generateStructuredAgentResponse, } from "./agent/agent-response-loop.js";
|
|
20
23
|
import { isValidAgentProvider, AGENT_PROVIDER_IDS } from "./agent/provider-manifest.js";
|
|
21
24
|
import { buildVoiceAgentMcpServerConfig, buildVoiceModeSystemPrompt, stripVoiceModeSystemPrompt, } from "./voice-config.js";
|
|
22
25
|
import { isVoicePermissionAllowed } from "./voice-permission-policy.js";
|
|
23
26
|
import { listDirectoryEntries, readExplorerFile, getDownloadableFileInfo, } from "./file-explorer/service.js";
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
27
|
+
import { slugify, validateBranchSlug, listPaseoWorktrees, deletePaseoWorktree, isPaseoOwnedWorktreeCwd, resolvePaseoWorktreeRootForCwd, } from "../utils/worktree.js";
|
|
28
|
+
import { createAgentWorktree, runAsyncWorktreeBootstrap, } from "./worktree-bootstrap.js";
|
|
29
|
+
import { getCheckoutDiff, getCheckoutStatus, getCheckoutStatusLite, listBranchSuggestions, NotGitRepoError, MergeConflictError, MergeFromBaseConflictError, commitChanges, mergeToBase, mergeFromBase, pushCurrentBranch, createPullRequest, getPullRequestStatus, } from "../utils/checkout-git.js";
|
|
26
30
|
import { getProjectIcon } from "../utils/project-icon.js";
|
|
27
31
|
import { expandTilde } from "../utils/path.js";
|
|
28
32
|
import { ensureLocalSpeechModels, getLocalSpeechModelDir, listLocalSpeechModels, } from "./speech/providers/local/models.js";
|
|
@@ -35,10 +39,11 @@ const pendingAgentInitializations = new Map();
|
|
|
35
39
|
let restartRequested = false;
|
|
36
40
|
const DEFAULT_AGENT_PROVIDER = AGENT_PROVIDER_IDS[0];
|
|
37
41
|
const RESTART_EXIT_DELAY_MS = 250;
|
|
38
|
-
const PROJECT_PLACEMENT_CACHE_TTL_MS = 10000;
|
|
39
|
-
const MAX_AGENTS_PER_PROJECT = 5;
|
|
40
42
|
const CHECKOUT_DIFF_WATCH_DEBOUNCE_MS = 150;
|
|
41
43
|
const CHECKOUT_DIFF_FALLBACK_REFRESH_MS = 5000;
|
|
44
|
+
const TERMINAL_STREAM_WINDOW_BYTES = 256 * 1024;
|
|
45
|
+
const TERMINAL_STREAM_MAX_PENDING_BYTES = 2 * 1024 * 1024;
|
|
46
|
+
const TERMINAL_STREAM_MAX_PENDING_CHUNKS = 2048;
|
|
42
47
|
/**
|
|
43
48
|
* Default model used for auto-generating commit messages and PR descriptions.
|
|
44
49
|
* Uses Claude Haiku for speed and cost efficiency.
|
|
@@ -85,17 +90,17 @@ function deriveRemoteProjectKey(remoteUrl) {
|
|
|
85
90
|
}
|
|
86
91
|
return `remote:${cleanedHost}/${cleanedPath}`;
|
|
87
92
|
}
|
|
88
|
-
function deriveProjectGroupingKey(
|
|
89
|
-
const remoteKey = deriveRemoteProjectKey(remoteUrl);
|
|
93
|
+
function deriveProjectGroupingKey(options) {
|
|
94
|
+
const remoteKey = deriveRemoteProjectKey(options.remoteUrl);
|
|
90
95
|
if (remoteKey) {
|
|
91
96
|
return remoteKey;
|
|
92
97
|
}
|
|
93
98
|
const worktreeMarker = ".paseo/worktrees/";
|
|
94
|
-
const idx = cwd.indexOf(worktreeMarker);
|
|
99
|
+
const idx = options.cwd.indexOf(worktreeMarker);
|
|
95
100
|
if (idx !== -1) {
|
|
96
|
-
return cwd.slice(0, idx).replace(/\/$/, "");
|
|
101
|
+
return options.cwd.slice(0, idx).replace(/\/$/, "");
|
|
97
102
|
}
|
|
98
|
-
return cwd;
|
|
103
|
+
return options.cwd;
|
|
99
104
|
}
|
|
100
105
|
function deriveProjectGroupingName(projectKey) {
|
|
101
106
|
const githubRemotePrefix = "remote:github.com/";
|
|
@@ -105,6 +110,13 @@ function deriveProjectGroupingName(projectKey) {
|
|
|
105
110
|
const segments = projectKey.split(/[\\/]/).filter(Boolean);
|
|
106
111
|
return segments[segments.length - 1] || projectKey;
|
|
107
112
|
}
|
|
113
|
+
class SessionRequestError extends Error {
|
|
114
|
+
constructor(code, message) {
|
|
115
|
+
super(message);
|
|
116
|
+
this.code = code;
|
|
117
|
+
this.name = "SessionRequestError";
|
|
118
|
+
}
|
|
119
|
+
}
|
|
108
120
|
const PCM_SAMPLE_RATE = 16000;
|
|
109
121
|
const PCM_CHANNELS = 1;
|
|
110
122
|
const PCM_BITS_PER_SAMPLE = 16;
|
|
@@ -116,6 +128,15 @@ const VOICE_INTERNAL_DICTATION_ID_PREFIX = "__voice_turn__:";
|
|
|
116
128
|
const SAFE_GIT_REF_PATTERN = /^[A-Za-z0-9._\/-]+$/;
|
|
117
129
|
const AgentIdSchema = z.string().uuid();
|
|
118
130
|
const VOICE_MCP_SERVER_NAME = "paseo_voice";
|
|
131
|
+
class VoiceFeatureUnavailableError extends Error {
|
|
132
|
+
constructor(context) {
|
|
133
|
+
super(context.message);
|
|
134
|
+
this.name = "VoiceFeatureUnavailableError";
|
|
135
|
+
this.reasonCode = context.reasonCode;
|
|
136
|
+
this.retryable = context.retryable;
|
|
137
|
+
this.missingModelIds = [...context.missingModelIds];
|
|
138
|
+
}
|
|
139
|
+
}
|
|
119
140
|
function convertPCMToWavBuffer(pcmBuffer, sampleRate, channels, bitsPerSample) {
|
|
120
141
|
const headerSize = 44;
|
|
121
142
|
const wavBuffer = Buffer.alloc(headerSize + pcmBuffer.length);
|
|
@@ -195,18 +216,24 @@ export class Session {
|
|
|
195
216
|
this.agentTools = null;
|
|
196
217
|
this.unsubscribeAgentEvents = null;
|
|
197
218
|
this.agentUpdatesSubscription = null;
|
|
198
|
-
this.projectPlacementCache = new Map();
|
|
199
219
|
this.clientActivity = null;
|
|
200
220
|
this.MOBILE_BACKGROUND_STREAM_GRACE_MS = 60000;
|
|
221
|
+
this.subscribedTerminalDirectories = new Set();
|
|
222
|
+
this.unsubscribeTerminalsChanged = null;
|
|
201
223
|
this.terminalSubscriptions = new Map();
|
|
224
|
+
this.terminalExitSubscriptions = new Map();
|
|
225
|
+
this.terminalStreams = new Map();
|
|
226
|
+
this.terminalStreamByTerminalId = new Map();
|
|
227
|
+
this.nextTerminalStreamId = 1;
|
|
202
228
|
this.checkoutDiffSubscriptions = new Map();
|
|
203
229
|
this.checkoutDiffTargets = new Map();
|
|
204
230
|
this.voiceModeAgentId = null;
|
|
205
231
|
this.voiceModeBaseConfig = null;
|
|
206
|
-
const { clientId, onMessage, logger, downloadTokenStore, pushTokenStore, paseoHome, agentManager, agentStorage, createAgentMcpTransport, stt, tts, terminalManager, voice, voiceBridge, dictation, agentProviderRuntimeSettings, } = options;
|
|
232
|
+
const { clientId, onMessage, onBinaryMessage, logger, downloadTokenStore, pushTokenStore, paseoHome, agentManager, agentStorage, createAgentMcpTransport, stt, tts, terminalManager, voice, voiceBridge, dictation, agentProviderRuntimeSettings, } = options;
|
|
207
233
|
this.clientId = clientId;
|
|
208
234
|
this.sessionId = uuidv4();
|
|
209
235
|
this.onMessage = onMessage;
|
|
236
|
+
this.onBinaryMessage = onBinaryMessage ?? null;
|
|
210
237
|
this.downloadTokenStore = downloadTokenStore;
|
|
211
238
|
this.pushTokenStore = pushTokenStore;
|
|
212
239
|
this.paseoHome = paseoHome;
|
|
@@ -214,6 +241,9 @@ export class Session {
|
|
|
214
241
|
this.agentStorage = agentStorage;
|
|
215
242
|
this.createAgentMcpTransport = createAgentMcpTransport;
|
|
216
243
|
this.terminalManager = terminalManager;
|
|
244
|
+
if (this.terminalManager) {
|
|
245
|
+
this.unsubscribeTerminalsChanged = this.terminalManager.subscribeTerminalsChanged((event) => this.handleTerminalsChanged(event));
|
|
246
|
+
}
|
|
217
247
|
this.voiceAgentMcpStdio = voice?.voiceAgentMcpStdio ?? null;
|
|
218
248
|
const configuredModelsDir = dictation?.localModels?.modelsDir?.trim();
|
|
219
249
|
this.localSpeechModelsDir =
|
|
@@ -230,6 +260,7 @@ export class Session {
|
|
|
230
260
|
this.unregisterVoiceCallerContext = voiceBridge?.unregisterVoiceCallerContext;
|
|
231
261
|
this.ensureVoiceMcpSocketForAgent = voiceBridge?.ensureVoiceMcpSocketForAgent;
|
|
232
262
|
this.removeVoiceMcpSocketForAgent = voiceBridge?.removeVoiceMcpSocketForAgent;
|
|
263
|
+
this.getSpeechReadiness = dictation?.getSpeechReadiness;
|
|
233
264
|
this.agentProviderRuntimeSettings = agentProviderRuntimeSettings;
|
|
234
265
|
this.abortController = new AbortController();
|
|
235
266
|
this.sessionLogger = logger.child({
|
|
@@ -415,9 +446,7 @@ export class Session {
|
|
|
415
446
|
}
|
|
416
447
|
// Reduce bandwidth/CPU on mobile: only forward high-frequency agent stream events
|
|
417
448
|
// for the focused agent, with a short grace window while backgrounded.
|
|
418
|
-
//
|
|
419
|
-
// History catch-up is handled via explicit `initialize_agent_request` which emits a
|
|
420
|
-
// batched `agent_stream_snapshot`.
|
|
449
|
+
// History catch-up is handled via pull-based `fetch_agent_timeline_request`.
|
|
421
450
|
const activity = this.clientActivity;
|
|
422
451
|
if (activity?.deviceType === "mobile") {
|
|
423
452
|
if (!activity.focusedAgentId) {
|
|
@@ -441,6 +470,8 @@ export class Session {
|
|
|
441
470
|
agentId: event.agentId,
|
|
442
471
|
event: serializedEvent,
|
|
443
472
|
timestamp: new Date().toISOString(),
|
|
473
|
+
...(typeof event.seq === "number" ? { seq: event.seq } : {}),
|
|
474
|
+
...(typeof event.epoch === "string" ? { epoch: event.epoch } : {}),
|
|
444
475
|
};
|
|
445
476
|
this.emit({
|
|
446
477
|
type: "agent_stream",
|
|
@@ -600,26 +631,16 @@ export class Session {
|
|
|
600
631
|
const checkout = await getCheckoutStatusLite(cwd, { paseoHome: this.paseoHome })
|
|
601
632
|
.then((status) => this.toProjectCheckoutLite(cwd, status))
|
|
602
633
|
.catch(() => this.buildFallbackProjectCheckout(cwd));
|
|
603
|
-
const projectKey = deriveProjectGroupingKey(
|
|
634
|
+
const projectKey = deriveProjectGroupingKey({
|
|
635
|
+
cwd,
|
|
636
|
+
remoteUrl: checkout.remoteUrl,
|
|
637
|
+
});
|
|
604
638
|
return {
|
|
605
639
|
projectKey,
|
|
606
640
|
projectName: deriveProjectGroupingName(projectKey),
|
|
607
641
|
checkout,
|
|
608
642
|
};
|
|
609
643
|
}
|
|
610
|
-
getProjectPlacement(cwd) {
|
|
611
|
-
const now = Date.now();
|
|
612
|
-
const cached = this.projectPlacementCache.get(cwd);
|
|
613
|
-
if (cached && cached.expiresAt > now) {
|
|
614
|
-
return cached.promise;
|
|
615
|
-
}
|
|
616
|
-
const promise = this.buildProjectPlacement(cwd);
|
|
617
|
-
this.projectPlacementCache.set(cwd, {
|
|
618
|
-
expiresAt: now + PROJECT_PLACEMENT_CACHE_TTL_MS,
|
|
619
|
-
promise,
|
|
620
|
-
});
|
|
621
|
-
return promise;
|
|
622
|
-
}
|
|
623
644
|
async forwardAgentUpdate(agent) {
|
|
624
645
|
try {
|
|
625
646
|
const subscription = this.agentUpdatesSubscription;
|
|
@@ -629,7 +650,7 @@ export class Session {
|
|
|
629
650
|
const payload = await this.buildAgentPayload(agent);
|
|
630
651
|
const matches = this.matchesAgentFilter(payload, subscription.filter);
|
|
631
652
|
if (matches) {
|
|
632
|
-
const project = await this.
|
|
653
|
+
const project = await this.buildProjectPlacement(payload.cwd);
|
|
633
654
|
this.emit({
|
|
634
655
|
type: "agent_update",
|
|
635
656
|
payload: { kind: "upsert", agent: payload, project },
|
|
@@ -661,10 +682,7 @@ export class Session {
|
|
|
661
682
|
this.handleAudioPlayed(msg.id);
|
|
662
683
|
break;
|
|
663
684
|
case "fetch_agents_request":
|
|
664
|
-
await this.handleFetchAgents(msg
|
|
665
|
-
break;
|
|
666
|
-
case "fetch_agents_grouped_by_project_request":
|
|
667
|
-
await this.handleFetchAgentsGroupedByProject(msg.requestId, msg.filter);
|
|
685
|
+
await this.handleFetchAgents(msg);
|
|
668
686
|
break;
|
|
669
687
|
case "fetch_agent_request":
|
|
670
688
|
await this.handleFetchAgent(msg.agentId, msg.requestId);
|
|
@@ -699,6 +717,22 @@ export class Session {
|
|
|
699
717
|
await this.handleWaitForFinish(msg.agentId, msg.requestId, msg.timeoutMs);
|
|
700
718
|
break;
|
|
701
719
|
case "dictation_stream_start":
|
|
720
|
+
{
|
|
721
|
+
const unavailable = this.resolveVoiceFeatureUnavailableContext("dictation");
|
|
722
|
+
if (unavailable) {
|
|
723
|
+
this.emit({
|
|
724
|
+
type: "dictation_stream_error",
|
|
725
|
+
payload: {
|
|
726
|
+
dictationId: msg.dictationId,
|
|
727
|
+
error: unavailable.message,
|
|
728
|
+
retryable: unavailable.retryable,
|
|
729
|
+
reasonCode: unavailable.reasonCode,
|
|
730
|
+
missingModelIds: unavailable.missingModelIds,
|
|
731
|
+
},
|
|
732
|
+
});
|
|
733
|
+
break;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
702
736
|
await this.dictationStreamManager.handleStart(msg.dictationId, msg.format);
|
|
703
737
|
break;
|
|
704
738
|
case "dictation_stream_chunk":
|
|
@@ -730,8 +764,8 @@ export class Session {
|
|
|
730
764
|
case "restart_server_request":
|
|
731
765
|
await this.handleRestartServerRequest(msg.requestId, msg.reason);
|
|
732
766
|
break;
|
|
733
|
-
case "
|
|
734
|
-
await this.
|
|
767
|
+
case "fetch_agent_timeline_request":
|
|
768
|
+
await this.handleFetchAgentTimelineRequest(msg);
|
|
735
769
|
break;
|
|
736
770
|
case "set_agent_mode_request":
|
|
737
771
|
await this.handleSetAgentModeRequest(msg.agentId, msg.modeId, msg.requestId);
|
|
@@ -751,6 +785,9 @@ export class Session {
|
|
|
751
785
|
case "validate_branch_request":
|
|
752
786
|
await this.handleValidateBranchRequest(msg);
|
|
753
787
|
break;
|
|
788
|
+
case "branch_suggestions_request":
|
|
789
|
+
await this.handleBranchSuggestionsRequest(msg);
|
|
790
|
+
break;
|
|
754
791
|
case "subscribe_checkout_diff_request":
|
|
755
792
|
await this.handleSubscribeCheckoutDiffRequest(msg);
|
|
756
793
|
break;
|
|
@@ -830,6 +867,12 @@ export class Session {
|
|
|
830
867
|
case "register_push_token":
|
|
831
868
|
this.handleRegisterPushToken(msg.token);
|
|
832
869
|
break;
|
|
870
|
+
case "subscribe_terminals_request":
|
|
871
|
+
this.handleSubscribeTerminalsRequest(msg);
|
|
872
|
+
break;
|
|
873
|
+
case "unsubscribe_terminals_request":
|
|
874
|
+
this.handleUnsubscribeTerminalsRequest(msg);
|
|
875
|
+
break;
|
|
833
876
|
case "list_terminals_request":
|
|
834
877
|
await this.handleListTerminalsRequest(msg);
|
|
835
878
|
break;
|
|
@@ -848,6 +891,12 @@ export class Session {
|
|
|
848
891
|
case "kill_terminal_request":
|
|
849
892
|
await this.handleKillTerminalRequest(msg);
|
|
850
893
|
break;
|
|
894
|
+
case "attach_terminal_stream_request":
|
|
895
|
+
await this.handleAttachTerminalStreamRequest(msg);
|
|
896
|
+
break;
|
|
897
|
+
case "detach_terminal_stream_request":
|
|
898
|
+
this.handleDetachTerminalStreamRequest(msg);
|
|
899
|
+
break;
|
|
851
900
|
}
|
|
852
901
|
}
|
|
853
902
|
catch (error) {
|
|
@@ -881,6 +930,58 @@ export class Session {
|
|
|
881
930
|
});
|
|
882
931
|
}
|
|
883
932
|
}
|
|
933
|
+
handleBinaryFrame(frame) {
|
|
934
|
+
switch (frame.channel) {
|
|
935
|
+
case BinaryMuxChannel.Terminal:
|
|
936
|
+
this.handleTerminalBinaryFrame(frame);
|
|
937
|
+
break;
|
|
938
|
+
default:
|
|
939
|
+
this.sessionLogger.warn({ channel: frame.channel, messageType: frame.messageType }, "Unhandled binary mux channel");
|
|
940
|
+
break;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
handleTerminalBinaryFrame(frame) {
|
|
944
|
+
if (frame.messageType === TerminalBinaryMessageType.InputUtf8) {
|
|
945
|
+
const binding = this.terminalStreams.get(frame.streamId);
|
|
946
|
+
if (!binding) {
|
|
947
|
+
this.sessionLogger.warn({ streamId: frame.streamId }, "Terminal stream not found for input");
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
if (!this.terminalManager) {
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
const session = this.terminalManager.getTerminal(binding.terminalId);
|
|
954
|
+
if (!session) {
|
|
955
|
+
this.detachTerminalStream(frame.streamId, { emitExit: true });
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
const payload = frame.payload ?? new Uint8Array(0);
|
|
959
|
+
if (payload.byteLength === 0) {
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
const text = Buffer.from(payload).toString("utf8");
|
|
963
|
+
if (!text) {
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
session.send({ type: "input", data: text });
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
if (frame.messageType === TerminalBinaryMessageType.Ack) {
|
|
970
|
+
const binding = this.terminalStreams.get(frame.streamId);
|
|
971
|
+
if (binding) {
|
|
972
|
+
if (!Number.isFinite(frame.offset) || frame.offset < 0) {
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
const nextAckOffset = Math.max(binding.lastAckOffset, Math.min(Math.floor(frame.offset), binding.lastOutputOffset));
|
|
976
|
+
if (nextAckOffset > binding.lastAckOffset) {
|
|
977
|
+
binding.lastAckOffset = nextAckOffset;
|
|
978
|
+
this.flushPendingTerminalStreamChunks(frame.streamId, binding);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
this.sessionLogger.warn({ streamId: frame.streamId, messageType: frame.messageType }, "Unhandled terminal binary frame");
|
|
984
|
+
}
|
|
884
985
|
async handleRestartServerRequest(requestId, reason) {
|
|
885
986
|
if (restartRequested) {
|
|
886
987
|
this.sessionLogger.debug("Restart already requested, ignoring duplicate");
|
|
@@ -1032,12 +1133,57 @@ export class Session {
|
|
|
1032
1133
|
});
|
|
1033
1134
|
}
|
|
1034
1135
|
}
|
|
1136
|
+
toVoiceFeatureUnavailableContext(state) {
|
|
1137
|
+
return {
|
|
1138
|
+
reasonCode: state.reasonCode,
|
|
1139
|
+
message: state.message,
|
|
1140
|
+
retryable: state.retryable,
|
|
1141
|
+
missingModelIds: [...state.missingModelIds],
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
resolveModeReadinessState(readiness, mode) {
|
|
1145
|
+
if (mode === "voice_mode") {
|
|
1146
|
+
return readiness.realtimeVoice;
|
|
1147
|
+
}
|
|
1148
|
+
return readiness.dictation;
|
|
1149
|
+
}
|
|
1150
|
+
getVoiceFeatureUnavailableResponseMetadata(error) {
|
|
1151
|
+
if (!(error instanceof VoiceFeatureUnavailableError)) {
|
|
1152
|
+
return {};
|
|
1153
|
+
}
|
|
1154
|
+
return {
|
|
1155
|
+
reasonCode: error.reasonCode,
|
|
1156
|
+
retryable: error.retryable,
|
|
1157
|
+
missingModelIds: error.missingModelIds,
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
resolveVoiceFeatureUnavailableContext(mode) {
|
|
1161
|
+
const readiness = this.getSpeechReadiness?.();
|
|
1162
|
+
if (!readiness) {
|
|
1163
|
+
return null;
|
|
1164
|
+
}
|
|
1165
|
+
const modeReadiness = this.resolveModeReadinessState(readiness, mode);
|
|
1166
|
+
if (!modeReadiness.enabled) {
|
|
1167
|
+
return this.toVoiceFeatureUnavailableContext(modeReadiness);
|
|
1168
|
+
}
|
|
1169
|
+
if (!readiness.voiceFeature.available) {
|
|
1170
|
+
return this.toVoiceFeatureUnavailableContext(readiness.voiceFeature);
|
|
1171
|
+
}
|
|
1172
|
+
if (!modeReadiness.available) {
|
|
1173
|
+
return this.toVoiceFeatureUnavailableContext(modeReadiness);
|
|
1174
|
+
}
|
|
1175
|
+
return null;
|
|
1176
|
+
}
|
|
1035
1177
|
/**
|
|
1036
1178
|
* Handle voice mode toggle
|
|
1037
1179
|
*/
|
|
1038
1180
|
async handleSetVoiceMode(enabled, agentId, requestId) {
|
|
1039
1181
|
try {
|
|
1040
1182
|
if (enabled) {
|
|
1183
|
+
const unavailable = this.resolveVoiceFeatureUnavailableContext("voice_mode");
|
|
1184
|
+
if (unavailable) {
|
|
1185
|
+
throw new VoiceFeatureUnavailableError(unavailable);
|
|
1186
|
+
}
|
|
1041
1187
|
const normalizedAgentId = this.parseVoiceTargetAgentId(agentId ?? "", "set_voice_mode");
|
|
1042
1188
|
if (this.isVoiceMode &&
|
|
1043
1189
|
this.voiceModeAgentId &&
|
|
@@ -1084,6 +1230,7 @@ export class Session {
|
|
|
1084
1230
|
}
|
|
1085
1231
|
catch (error) {
|
|
1086
1232
|
const errorMessage = error instanceof Error ? error.message : "Failed to set voice mode";
|
|
1233
|
+
const unavailable = this.getVoiceFeatureUnavailableResponseMetadata(error);
|
|
1087
1234
|
this.sessionLogger.error({
|
|
1088
1235
|
err: error,
|
|
1089
1236
|
enabled,
|
|
@@ -1098,6 +1245,7 @@ export class Session {
|
|
|
1098
1245
|
agentId: this.voiceModeAgentId,
|
|
1099
1246
|
accepted: false,
|
|
1100
1247
|
error: errorMessage,
|
|
1248
|
+
...unavailable,
|
|
1101
1249
|
},
|
|
1102
1250
|
});
|
|
1103
1251
|
return;
|
|
@@ -1403,44 +1551,16 @@ export class Session {
|
|
|
1403
1551
|
}
|
|
1404
1552
|
const prompt = this.buildAgentPrompt(text, images);
|
|
1405
1553
|
try {
|
|
1406
|
-
this.agentManager.recordUserMessage(agentId, text, {
|
|
1554
|
+
this.agentManager.recordUserMessage(agentId, text, {
|
|
1555
|
+
messageId,
|
|
1556
|
+
emitState: false,
|
|
1557
|
+
});
|
|
1407
1558
|
}
|
|
1408
1559
|
catch (error) {
|
|
1409
1560
|
this.sessionLogger.error({ err: error, agentId }, `Failed to record user message for agent ${agentId}`);
|
|
1410
1561
|
}
|
|
1411
1562
|
this.startAgentStream(agentId, prompt, runOptions);
|
|
1412
1563
|
}
|
|
1413
|
-
/**
|
|
1414
|
-
* Handle on-demand agent initialization request from client
|
|
1415
|
-
*/
|
|
1416
|
-
async handleInitializeAgentRequest(agentId, requestId) {
|
|
1417
|
-
try {
|
|
1418
|
-
const snapshot = await this.ensureAgentLoaded(agentId);
|
|
1419
|
-
await this.forwardAgentUpdate(snapshot);
|
|
1420
|
-
// Send timeline snapshot after hydration (if any)
|
|
1421
|
-
const timelineSize = this.emitAgentTimelineSnapshot(snapshot);
|
|
1422
|
-
this.emit({
|
|
1423
|
-
type: "initialize_agent_request",
|
|
1424
|
-
payload: {
|
|
1425
|
-
agentId,
|
|
1426
|
-
agentStatus: snapshot.lifecycle,
|
|
1427
|
-
timelineSize,
|
|
1428
|
-
requestId,
|
|
1429
|
-
},
|
|
1430
|
-
});
|
|
1431
|
-
}
|
|
1432
|
-
catch (error) {
|
|
1433
|
-
this.sessionLogger.error({ err: error, agentId }, `Failed to initialize agent ${agentId}`);
|
|
1434
|
-
this.emit({
|
|
1435
|
-
type: "initialize_agent_request",
|
|
1436
|
-
payload: {
|
|
1437
|
-
agentId,
|
|
1438
|
-
requestId,
|
|
1439
|
-
error: error?.message ?? "Failed to initialize agent",
|
|
1440
|
-
},
|
|
1441
|
-
});
|
|
1442
|
-
}
|
|
1443
|
-
}
|
|
1444
1564
|
/**
|
|
1445
1565
|
* Handle create agent request
|
|
1446
1566
|
*/
|
|
@@ -1494,7 +1614,22 @@ export class Session {
|
|
|
1494
1614
|
});
|
|
1495
1615
|
}
|
|
1496
1616
|
if (worktreeConfig) {
|
|
1497
|
-
void
|
|
1617
|
+
void runAsyncWorktreeBootstrap({
|
|
1618
|
+
agentId: snapshot.id,
|
|
1619
|
+
worktree: worktreeConfig,
|
|
1620
|
+
terminalManager: this.terminalManager,
|
|
1621
|
+
appendTimelineItem: (item) => appendTimelineItemIfAgentKnown({
|
|
1622
|
+
agentManager: this.agentManager,
|
|
1623
|
+
agentId: snapshot.id,
|
|
1624
|
+
item,
|
|
1625
|
+
}),
|
|
1626
|
+
emitLiveTimelineItem: (item) => emitLiveTimelineItemIfAgentKnown({
|
|
1627
|
+
agentManager: this.agentManager,
|
|
1628
|
+
agentId: snapshot.id,
|
|
1629
|
+
item,
|
|
1630
|
+
}),
|
|
1631
|
+
logger: this.sessionLogger,
|
|
1632
|
+
});
|
|
1498
1633
|
}
|
|
1499
1634
|
this.sessionLogger.info({ agentId: snapshot.id, provider: snapshot.provider }, `Created agent ${snapshot.id} (${snapshot.provider})`);
|
|
1500
1635
|
}
|
|
@@ -1541,7 +1676,7 @@ export class Session {
|
|
|
1541
1676
|
const snapshot = await this.agentManager.resumeAgentFromPersistence(handle, overrides);
|
|
1542
1677
|
await this.agentManager.hydrateTimelineFromProvider(snapshot.id);
|
|
1543
1678
|
await this.forwardAgentUpdate(snapshot);
|
|
1544
|
-
const timelineSize = this.
|
|
1679
|
+
const timelineSize = this.agentManager.getTimeline(snapshot.id).length;
|
|
1545
1680
|
if (requestId) {
|
|
1546
1681
|
const agentPayload = await this.getAgentPayloadById(snapshot.id);
|
|
1547
1682
|
if (!agentPayload) {
|
|
@@ -1600,7 +1735,7 @@ export class Session {
|
|
|
1600
1735
|
}
|
|
1601
1736
|
await this.agentManager.hydrateTimelineFromProvider(agentId);
|
|
1602
1737
|
await this.forwardAgentUpdate(snapshot);
|
|
1603
|
-
const timelineSize = this.
|
|
1738
|
+
const timelineSize = this.agentManager.getTimeline(agentId).length;
|
|
1604
1739
|
if (requestId) {
|
|
1605
1740
|
this.emit({
|
|
1606
1741
|
type: "status",
|
|
@@ -1664,12 +1799,11 @@ export class Session {
|
|
|
1664
1799
|
throw new Error("A branch name is required when creating a worktree.");
|
|
1665
1800
|
}
|
|
1666
1801
|
this.sessionLogger.info({ worktreeSlug: normalized.worktreeSlug ?? targetBranch, branch: targetBranch }, `Creating worktree '${normalized.worktreeSlug ?? targetBranch}' for branch ${targetBranch}`);
|
|
1667
|
-
const createdWorktree = await
|
|
1802
|
+
const createdWorktree = await createAgentWorktree({
|
|
1668
1803
|
branchName: targetBranch,
|
|
1669
1804
|
cwd,
|
|
1670
1805
|
baseBranch: normalized.baseBranch,
|
|
1671
1806
|
worktreeSlug: normalized.worktreeSlug ?? targetBranch,
|
|
1672
|
-
runSetup: false,
|
|
1673
1807
|
paseoHome: this.paseoHome,
|
|
1674
1808
|
});
|
|
1675
1809
|
cwd = createdWorktree.worktreePath;
|
|
@@ -1693,100 +1827,6 @@ export class Session {
|
|
|
1693
1827
|
worktreeConfig,
|
|
1694
1828
|
};
|
|
1695
1829
|
}
|
|
1696
|
-
async runAsyncWorktreeSetup(agentId, worktree) {
|
|
1697
|
-
const callId = uuidv4();
|
|
1698
|
-
let results = [];
|
|
1699
|
-
try {
|
|
1700
|
-
const started = await this.safeAppendTimelineItem(agentId, {
|
|
1701
|
-
type: "tool_call",
|
|
1702
|
-
name: "paseo_worktree_setup",
|
|
1703
|
-
callId,
|
|
1704
|
-
status: "running",
|
|
1705
|
-
detail: {
|
|
1706
|
-
type: "unknown",
|
|
1707
|
-
input: {
|
|
1708
|
-
worktreePath: worktree.worktreePath,
|
|
1709
|
-
branchName: worktree.branchName,
|
|
1710
|
-
},
|
|
1711
|
-
output: null,
|
|
1712
|
-
},
|
|
1713
|
-
error: null,
|
|
1714
|
-
});
|
|
1715
|
-
if (!started) {
|
|
1716
|
-
return;
|
|
1717
|
-
}
|
|
1718
|
-
results = await runWorktreeSetupCommands({
|
|
1719
|
-
worktreePath: worktree.worktreePath,
|
|
1720
|
-
branchName: worktree.branchName,
|
|
1721
|
-
cleanupOnFailure: false,
|
|
1722
|
-
});
|
|
1723
|
-
await this.safeAppendTimelineItem(agentId, {
|
|
1724
|
-
type: "tool_call",
|
|
1725
|
-
name: "paseo_worktree_setup",
|
|
1726
|
-
callId,
|
|
1727
|
-
status: "completed",
|
|
1728
|
-
detail: {
|
|
1729
|
-
type: "unknown",
|
|
1730
|
-
input: {
|
|
1731
|
-
worktreePath: worktree.worktreePath,
|
|
1732
|
-
branchName: worktree.branchName,
|
|
1733
|
-
},
|
|
1734
|
-
output: {
|
|
1735
|
-
worktreePath: worktree.worktreePath,
|
|
1736
|
-
commands: results.map((result) => ({
|
|
1737
|
-
command: result.command,
|
|
1738
|
-
cwd: result.cwd,
|
|
1739
|
-
exitCode: result.exitCode,
|
|
1740
|
-
output: `${result.stdout ?? ""}${result.stderr ? `\n${result.stderr}` : ""}`.trim(),
|
|
1741
|
-
})),
|
|
1742
|
-
},
|
|
1743
|
-
},
|
|
1744
|
-
error: null,
|
|
1745
|
-
});
|
|
1746
|
-
}
|
|
1747
|
-
catch (error) {
|
|
1748
|
-
if (error instanceof WorktreeSetupError) {
|
|
1749
|
-
results = error.results;
|
|
1750
|
-
}
|
|
1751
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1752
|
-
await this.safeAppendTimelineItem(agentId, {
|
|
1753
|
-
type: "tool_call",
|
|
1754
|
-
name: "paseo_worktree_setup",
|
|
1755
|
-
callId,
|
|
1756
|
-
status: "failed",
|
|
1757
|
-
detail: {
|
|
1758
|
-
type: "unknown",
|
|
1759
|
-
input: {
|
|
1760
|
-
worktreePath: worktree.worktreePath,
|
|
1761
|
-
branchName: worktree.branchName,
|
|
1762
|
-
},
|
|
1763
|
-
output: {
|
|
1764
|
-
worktreePath: worktree.worktreePath,
|
|
1765
|
-
commands: results.map((result) => ({
|
|
1766
|
-
command: result.command,
|
|
1767
|
-
cwd: result.cwd,
|
|
1768
|
-
exitCode: result.exitCode,
|
|
1769
|
-
output: `${result.stdout ?? ""}${result.stderr ? `\n${result.stderr}` : ""}`.trim(),
|
|
1770
|
-
})),
|
|
1771
|
-
},
|
|
1772
|
-
},
|
|
1773
|
-
error: { message },
|
|
1774
|
-
});
|
|
1775
|
-
}
|
|
1776
|
-
}
|
|
1777
|
-
async safeAppendTimelineItem(agentId, item) {
|
|
1778
|
-
try {
|
|
1779
|
-
await this.agentManager.appendTimelineItem(agentId, item);
|
|
1780
|
-
return true;
|
|
1781
|
-
}
|
|
1782
|
-
catch (error) {
|
|
1783
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1784
|
-
if (message.includes("Unknown agent")) {
|
|
1785
|
-
return false;
|
|
1786
|
-
}
|
|
1787
|
-
throw error;
|
|
1788
|
-
}
|
|
1789
|
-
}
|
|
1790
1830
|
async handleListProviderModelsRequest(msg) {
|
|
1791
1831
|
const fetchedAt = new Date().toISOString();
|
|
1792
1832
|
try {
|
|
@@ -2627,6 +2667,31 @@ export class Session {
|
|
|
2627
2667
|
});
|
|
2628
2668
|
}
|
|
2629
2669
|
}
|
|
2670
|
+
async handleBranchSuggestionsRequest(msg) {
|
|
2671
|
+
const { cwd, query, limit, requestId } = msg;
|
|
2672
|
+
try {
|
|
2673
|
+
const resolvedCwd = expandTilde(cwd);
|
|
2674
|
+
const branches = await listBranchSuggestions(resolvedCwd, { query, limit });
|
|
2675
|
+
this.emit({
|
|
2676
|
+
type: "branch_suggestions_response",
|
|
2677
|
+
payload: {
|
|
2678
|
+
branches,
|
|
2679
|
+
error: null,
|
|
2680
|
+
requestId,
|
|
2681
|
+
},
|
|
2682
|
+
});
|
|
2683
|
+
}
|
|
2684
|
+
catch (error) {
|
|
2685
|
+
this.emit({
|
|
2686
|
+
type: "branch_suggestions_response",
|
|
2687
|
+
payload: {
|
|
2688
|
+
branches: [],
|
|
2689
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2690
|
+
requestId,
|
|
2691
|
+
},
|
|
2692
|
+
});
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2630
2695
|
normalizeCheckoutDiffCompare(compare) {
|
|
2631
2696
|
if (compare.mode === "uncommitted") {
|
|
2632
2697
|
return { mode: "uncommitted" };
|
|
@@ -2798,30 +2863,42 @@ export class Session {
|
|
|
2798
2863
|
latestPayload: null,
|
|
2799
2864
|
latestFingerprint: null,
|
|
2800
2865
|
};
|
|
2801
|
-
const
|
|
2802
|
-
|
|
2803
|
-
watchPaths.add(watchRoot);
|
|
2804
|
-
}
|
|
2866
|
+
const repoWatchPath = watchRoot ?? cwd;
|
|
2867
|
+
const watchPaths = new Set([repoWatchPath]);
|
|
2805
2868
|
const gitDir = await this.resolveCheckoutGitDir(cwd);
|
|
2806
2869
|
if (gitDir) {
|
|
2807
2870
|
watchPaths.add(gitDir);
|
|
2808
2871
|
}
|
|
2809
|
-
let
|
|
2872
|
+
let hasRecursiveRepoCoverage = false;
|
|
2873
|
+
const allowRecursiveRepoWatch = process.platform !== "linux";
|
|
2810
2874
|
for (const watchPath of watchPaths) {
|
|
2875
|
+
const shouldTryRecursive = watchPath === repoWatchPath && allowRecursiveRepoWatch;
|
|
2811
2876
|
const createWatcher = (recursive) => watch(watchPath, { recursive }, () => {
|
|
2812
2877
|
this.scheduleCheckoutDiffTargetRefresh(target);
|
|
2813
2878
|
});
|
|
2814
2879
|
let watcher = null;
|
|
2880
|
+
let watcherIsRecursive = false;
|
|
2815
2881
|
try {
|
|
2816
|
-
|
|
2882
|
+
if (shouldTryRecursive) {
|
|
2883
|
+
watcher = createWatcher(true);
|
|
2884
|
+
watcherIsRecursive = true;
|
|
2885
|
+
}
|
|
2886
|
+
else {
|
|
2887
|
+
watcher = createWatcher(false);
|
|
2888
|
+
}
|
|
2817
2889
|
}
|
|
2818
2890
|
catch (error) {
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2891
|
+
if (shouldTryRecursive) {
|
|
2892
|
+
try {
|
|
2893
|
+
watcher = createWatcher(false);
|
|
2894
|
+
this.sessionLogger.warn({ err: error, watchPath, cwd, compare }, "Checkout diff recursive watch unavailable; using non-recursive fallback");
|
|
2895
|
+
}
|
|
2896
|
+
catch (fallbackError) {
|
|
2897
|
+
this.sessionLogger.warn({ err: fallbackError, watchPath, cwd, compare }, "Failed to start checkout diff watcher");
|
|
2898
|
+
}
|
|
2822
2899
|
}
|
|
2823
|
-
|
|
2824
|
-
this.sessionLogger.warn({ err:
|
|
2900
|
+
else {
|
|
2901
|
+
this.sessionLogger.warn({ err: error, watchPath, cwd, compare }, "Failed to start checkout diff watcher");
|
|
2825
2902
|
}
|
|
2826
2903
|
}
|
|
2827
2904
|
if (!watcher) {
|
|
@@ -2831,11 +2908,11 @@ export class Session {
|
|
|
2831
2908
|
this.sessionLogger.warn({ err: error, watchPath, cwd, compare }, "Checkout diff watcher error");
|
|
2832
2909
|
});
|
|
2833
2910
|
target.watchers.push(watcher);
|
|
2834
|
-
if (
|
|
2835
|
-
|
|
2911
|
+
if (watchPath === repoWatchPath && watcherIsRecursive) {
|
|
2912
|
+
hasRecursiveRepoCoverage = true;
|
|
2836
2913
|
}
|
|
2837
2914
|
}
|
|
2838
|
-
const missingRepoCoverage =
|
|
2915
|
+
const missingRepoCoverage = !hasRecursiveRepoCoverage;
|
|
2839
2916
|
if (target.watchers.length === 0 || missingRepoCoverage) {
|
|
2840
2917
|
target.fallbackRefreshInterval = setInterval(() => {
|
|
2841
2918
|
this.scheduleCheckoutDiffTargetRefresh(target);
|
|
@@ -2846,7 +2923,7 @@ export class Session {
|
|
|
2846
2923
|
intervalMs: CHECKOUT_DIFF_FALLBACK_REFRESH_MS,
|
|
2847
2924
|
reason: target.watchers.length === 0
|
|
2848
2925
|
? "no_watchers"
|
|
2849
|
-
: "
|
|
2926
|
+
: "missing_recursive_repo_root_coverage",
|
|
2850
2927
|
}, "Checkout diff watchers unavailable; using timed refresh fallback");
|
|
2851
2928
|
}
|
|
2852
2929
|
this.checkoutDiffTargets.set(targetKey, target);
|
|
@@ -3523,64 +3600,230 @@ export class Session {
|
|
|
3523
3600
|
}
|
|
3524
3601
|
return this.buildStoredAgentPayload(record);
|
|
3525
3602
|
}
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3603
|
+
normalizeFetchAgentsSort(sort) {
|
|
3604
|
+
const fallback = [
|
|
3605
|
+
{ key: "updated_at", direction: "desc" },
|
|
3606
|
+
];
|
|
3607
|
+
if (!sort || sort.length === 0) {
|
|
3608
|
+
return fallback;
|
|
3609
|
+
}
|
|
3610
|
+
const deduped = [];
|
|
3611
|
+
const seen = new Set();
|
|
3612
|
+
for (const entry of sort) {
|
|
3613
|
+
if (seen.has(entry.key)) {
|
|
3614
|
+
continue;
|
|
3615
|
+
}
|
|
3616
|
+
seen.add(entry.key);
|
|
3617
|
+
deduped.push(entry);
|
|
3533
3618
|
}
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3619
|
+
return deduped.length > 0 ? deduped : fallback;
|
|
3620
|
+
}
|
|
3621
|
+
getStatusPriority(agent) {
|
|
3622
|
+
const requiresAttention = agent.requiresAttention ?? false;
|
|
3623
|
+
const attentionReason = agent.attentionReason ?? null;
|
|
3624
|
+
if (requiresAttention && attentionReason === "permission") {
|
|
3625
|
+
return 0;
|
|
3540
3626
|
}
|
|
3627
|
+
if (agent.status === "error" || attentionReason === "error") {
|
|
3628
|
+
return 1;
|
|
3629
|
+
}
|
|
3630
|
+
if (agent.status === "running") {
|
|
3631
|
+
return 2;
|
|
3632
|
+
}
|
|
3633
|
+
if (agent.status === "initializing") {
|
|
3634
|
+
return 3;
|
|
3635
|
+
}
|
|
3636
|
+
return 4;
|
|
3541
3637
|
}
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3638
|
+
getFetchAgentsSortValue(entry, key) {
|
|
3639
|
+
switch (key) {
|
|
3640
|
+
case "status_priority":
|
|
3641
|
+
return this.getStatusPriority(entry.agent);
|
|
3642
|
+
case "created_at":
|
|
3643
|
+
return Date.parse(entry.agent.createdAt);
|
|
3644
|
+
case "updated_at":
|
|
3645
|
+
return Date.parse(entry.agent.updatedAt);
|
|
3646
|
+
case "title":
|
|
3647
|
+
return entry.agent.title?.toLocaleLowerCase() ?? "";
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
compareSortValues(left, right) {
|
|
3651
|
+
if (left === right) {
|
|
3652
|
+
return 0;
|
|
3653
|
+
}
|
|
3654
|
+
if (left === null) {
|
|
3655
|
+
return -1;
|
|
3656
|
+
}
|
|
3657
|
+
if (right === null) {
|
|
3658
|
+
return 1;
|
|
3659
|
+
}
|
|
3660
|
+
if (typeof left === "number" && typeof right === "number") {
|
|
3661
|
+
return left < right ? -1 : 1;
|
|
3662
|
+
}
|
|
3663
|
+
return String(left).localeCompare(String(right));
|
|
3664
|
+
}
|
|
3665
|
+
compareFetchAgentsEntries(left, right, sort) {
|
|
3666
|
+
for (const spec of sort) {
|
|
3667
|
+
const leftValue = this.getFetchAgentsSortValue(left, spec.key);
|
|
3668
|
+
const rightValue = this.getFetchAgentsSortValue(right, spec.key);
|
|
3669
|
+
const base = this.compareSortValues(leftValue, rightValue);
|
|
3670
|
+
if (base === 0) {
|
|
3671
|
+
continue;
|
|
3672
|
+
}
|
|
3673
|
+
return spec.direction === "asc" ? base : -base;
|
|
3674
|
+
}
|
|
3675
|
+
return left.agent.id.localeCompare(right.agent.id);
|
|
3676
|
+
}
|
|
3677
|
+
encodeFetchAgentsCursor(entry, sort) {
|
|
3678
|
+
const values = {};
|
|
3679
|
+
for (const spec of sort) {
|
|
3680
|
+
values[spec.key] = this.getFetchAgentsSortValue(entry, spec.key);
|
|
3681
|
+
}
|
|
3682
|
+
return Buffer.from(JSON.stringify({
|
|
3683
|
+
sort,
|
|
3684
|
+
values,
|
|
3685
|
+
id: entry.agent.id,
|
|
3686
|
+
}), "utf8").toString("base64url");
|
|
3687
|
+
}
|
|
3688
|
+
decodeFetchAgentsCursor(cursor, sort) {
|
|
3689
|
+
let parsed;
|
|
3690
|
+
try {
|
|
3691
|
+
parsed = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
3692
|
+
}
|
|
3693
|
+
catch {
|
|
3694
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
|
|
3695
|
+
}
|
|
3696
|
+
if (!parsed || typeof parsed !== "object") {
|
|
3697
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
|
|
3698
|
+
}
|
|
3699
|
+
const payload = parsed;
|
|
3700
|
+
if (!Array.isArray(payload.sort) || typeof payload.id !== "string") {
|
|
3701
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
|
|
3702
|
+
}
|
|
3703
|
+
if (!payload.values || typeof payload.values !== "object") {
|
|
3704
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
|
|
3705
|
+
}
|
|
3706
|
+
const cursorSort = [];
|
|
3707
|
+
for (const item of payload.sort) {
|
|
3708
|
+
if (!item ||
|
|
3709
|
+
typeof item !== "object" ||
|
|
3710
|
+
typeof item.key !== "string" ||
|
|
3711
|
+
typeof item.direction !== "string") {
|
|
3712
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
|
|
3713
|
+
}
|
|
3714
|
+
const key = item.key;
|
|
3715
|
+
const direction = item.direction;
|
|
3716
|
+
if ((key !== "status_priority" &&
|
|
3717
|
+
key !== "created_at" &&
|
|
3718
|
+
key !== "updated_at" &&
|
|
3719
|
+
key !== "title") ||
|
|
3720
|
+
(direction !== "asc" && direction !== "desc")) {
|
|
3721
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
|
|
3563
3722
|
}
|
|
3564
|
-
|
|
3723
|
+
cursorSort.push({ key, direction });
|
|
3724
|
+
}
|
|
3725
|
+
if (cursorSort.length !== sort.length ||
|
|
3726
|
+
cursorSort.some((entry, index) => entry.key !== sort[index]?.key ||
|
|
3727
|
+
entry.direction !== sort[index]?.direction)) {
|
|
3728
|
+
throw new SessionRequestError("invalid_cursor", "fetch_agents cursor does not match current sort");
|
|
3729
|
+
}
|
|
3730
|
+
return {
|
|
3731
|
+
sort: cursorSort,
|
|
3732
|
+
values: payload.values,
|
|
3733
|
+
id: payload.id,
|
|
3734
|
+
};
|
|
3735
|
+
}
|
|
3736
|
+
compareEntryWithCursor(entry, cursor, sort) {
|
|
3737
|
+
for (const spec of sort) {
|
|
3738
|
+
const leftValue = this.getFetchAgentsSortValue(entry, spec.key);
|
|
3739
|
+
const rightValue = cursor.values[spec.key] !== undefined ? cursor.values[spec.key] ?? null : null;
|
|
3740
|
+
const base = this.compareSortValues(leftValue, rightValue);
|
|
3741
|
+
if (base === 0) {
|
|
3565
3742
|
continue;
|
|
3566
3743
|
}
|
|
3567
|
-
|
|
3744
|
+
return spec.direction === "asc" ? base : -base;
|
|
3568
3745
|
}
|
|
3569
|
-
return
|
|
3746
|
+
return entry.agent.id.localeCompare(cursor.id);
|
|
3570
3747
|
}
|
|
3571
|
-
async
|
|
3748
|
+
async listFetchAgentsEntries(request) {
|
|
3749
|
+
const filter = request.filter;
|
|
3750
|
+
const sort = this.normalizeFetchAgentsSort(request.sort);
|
|
3751
|
+
const includeArchived = filter?.includeArchived ?? false;
|
|
3752
|
+
let agents = await this.listAgentPayloads({
|
|
3753
|
+
labels: filter?.labels,
|
|
3754
|
+
});
|
|
3755
|
+
if (!includeArchived) {
|
|
3756
|
+
agents = agents.filter((agent) => !agent.archivedAt);
|
|
3757
|
+
}
|
|
3758
|
+
if (filter?.statuses && filter.statuses.length > 0) {
|
|
3759
|
+
const statuses = new Set(filter.statuses);
|
|
3760
|
+
agents = agents.filter((agent) => statuses.has(agent.status));
|
|
3761
|
+
}
|
|
3762
|
+
if (typeof filter?.requiresAttention === "boolean") {
|
|
3763
|
+
agents = agents.filter((agent) => (agent.requiresAttention ?? false) === filter.requiresAttention);
|
|
3764
|
+
}
|
|
3765
|
+
const placementByCwd = new Map();
|
|
3766
|
+
const getPlacement = (cwd) => {
|
|
3767
|
+
const existing = placementByCwd.get(cwd);
|
|
3768
|
+
if (existing) {
|
|
3769
|
+
return existing;
|
|
3770
|
+
}
|
|
3771
|
+
const placementPromise = this.buildProjectPlacement(cwd);
|
|
3772
|
+
placementByCwd.set(cwd, placementPromise);
|
|
3773
|
+
return placementPromise;
|
|
3774
|
+
};
|
|
3775
|
+
let entries = await Promise.all(agents.map(async (agent) => ({
|
|
3776
|
+
agent,
|
|
3777
|
+
project: await getPlacement(agent.cwd),
|
|
3778
|
+
})));
|
|
3779
|
+
if (filter?.projectKeys && filter.projectKeys.length > 0) {
|
|
3780
|
+
const projectKeys = new Set(filter.projectKeys.filter((item) => item.trim().length > 0));
|
|
3781
|
+
entries = entries.filter((entry) => projectKeys.has(entry.project.projectKey));
|
|
3782
|
+
}
|
|
3783
|
+
entries.sort((left, right) => this.compareFetchAgentsEntries(left, right, sort));
|
|
3784
|
+
const cursorToken = request.page?.cursor;
|
|
3785
|
+
if (cursorToken) {
|
|
3786
|
+
const cursor = this.decodeFetchAgentsCursor(cursorToken, sort);
|
|
3787
|
+
entries = entries.filter((entry) => this.compareEntryWithCursor(entry, cursor, sort) > 0);
|
|
3788
|
+
}
|
|
3789
|
+
const limit = request.page?.limit ?? entries.length;
|
|
3790
|
+
const pagedEntries = entries.slice(0, limit);
|
|
3791
|
+
const hasMore = entries.length > limit;
|
|
3792
|
+
const nextCursor = hasMore && pagedEntries.length > 0
|
|
3793
|
+
? this.encodeFetchAgentsCursor(pagedEntries[pagedEntries.length - 1], sort)
|
|
3794
|
+
: null;
|
|
3795
|
+
return {
|
|
3796
|
+
entries: pagedEntries,
|
|
3797
|
+
pageInfo: {
|
|
3798
|
+
nextCursor,
|
|
3799
|
+
prevCursor: request.page?.cursor ?? null,
|
|
3800
|
+
hasMore,
|
|
3801
|
+
},
|
|
3802
|
+
};
|
|
3803
|
+
}
|
|
3804
|
+
async handleFetchAgents(request) {
|
|
3572
3805
|
try {
|
|
3573
|
-
const
|
|
3806
|
+
const payload = await this.listFetchAgentsEntries(request);
|
|
3574
3807
|
this.emit({
|
|
3575
|
-
type: "
|
|
3576
|
-
payload: {
|
|
3808
|
+
type: "fetch_agents_response",
|
|
3809
|
+
payload: {
|
|
3810
|
+
requestId: request.requestId,
|
|
3811
|
+
...payload,
|
|
3812
|
+
},
|
|
3577
3813
|
});
|
|
3578
3814
|
}
|
|
3579
3815
|
catch (error) {
|
|
3580
|
-
|
|
3816
|
+
const code = error instanceof SessionRequestError ? error.code : "fetch_agents_failed";
|
|
3817
|
+
const message = error instanceof Error ? error.message : "Failed to fetch agents";
|
|
3818
|
+
this.sessionLogger.error({ err: error }, "Failed to handle fetch_agents_request");
|
|
3581
3819
|
this.emit({
|
|
3582
|
-
type: "
|
|
3583
|
-
payload: {
|
|
3820
|
+
type: "rpc_error",
|
|
3821
|
+
payload: {
|
|
3822
|
+
requestId: request.requestId,
|
|
3823
|
+
requestType: request.type,
|
|
3824
|
+
error: message,
|
|
3825
|
+
code,
|
|
3826
|
+
},
|
|
3584
3827
|
});
|
|
3585
3828
|
}
|
|
3586
3829
|
}
|
|
@@ -3606,6 +3849,77 @@ export class Session {
|
|
|
3606
3849
|
payload: { requestId, agent, error: null },
|
|
3607
3850
|
});
|
|
3608
3851
|
}
|
|
3852
|
+
async handleFetchAgentTimelineRequest(msg) {
|
|
3853
|
+
const direction = msg.direction ?? (msg.cursor ? "after" : "tail");
|
|
3854
|
+
const projection = msg.projection ?? "projected";
|
|
3855
|
+
const limit = msg.limit ?? (direction === "after" ? 0 : undefined);
|
|
3856
|
+
const cursor = msg.cursor
|
|
3857
|
+
? {
|
|
3858
|
+
epoch: msg.cursor.epoch,
|
|
3859
|
+
seq: msg.cursor.seq,
|
|
3860
|
+
}
|
|
3861
|
+
: undefined;
|
|
3862
|
+
try {
|
|
3863
|
+
const snapshot = await this.ensureAgentLoaded(msg.agentId);
|
|
3864
|
+
const timeline = this.agentManager.fetchTimeline(msg.agentId, {
|
|
3865
|
+
direction,
|
|
3866
|
+
cursor,
|
|
3867
|
+
limit,
|
|
3868
|
+
});
|
|
3869
|
+
const projected = projectTimelineRows(timeline.rows, snapshot.provider, projection);
|
|
3870
|
+
const firstRow = timeline.rows[0];
|
|
3871
|
+
const lastRow = timeline.rows[timeline.rows.length - 1];
|
|
3872
|
+
const startCursor = firstRow
|
|
3873
|
+
? { epoch: timeline.epoch, seq: firstRow.seq }
|
|
3874
|
+
: null;
|
|
3875
|
+
const endCursor = lastRow
|
|
3876
|
+
? { epoch: timeline.epoch, seq: lastRow.seq }
|
|
3877
|
+
: null;
|
|
3878
|
+
this.emit({
|
|
3879
|
+
type: "fetch_agent_timeline_response",
|
|
3880
|
+
payload: {
|
|
3881
|
+
requestId: msg.requestId,
|
|
3882
|
+
agentId: msg.agentId,
|
|
3883
|
+
direction,
|
|
3884
|
+
projection,
|
|
3885
|
+
epoch: timeline.epoch,
|
|
3886
|
+
reset: timeline.reset,
|
|
3887
|
+
staleCursor: timeline.staleCursor,
|
|
3888
|
+
gap: timeline.gap,
|
|
3889
|
+
window: timeline.window,
|
|
3890
|
+
startCursor,
|
|
3891
|
+
endCursor,
|
|
3892
|
+
hasOlder: timeline.hasOlder,
|
|
3893
|
+
hasNewer: timeline.hasNewer,
|
|
3894
|
+
entries: projected,
|
|
3895
|
+
error: null,
|
|
3896
|
+
},
|
|
3897
|
+
});
|
|
3898
|
+
}
|
|
3899
|
+
catch (error) {
|
|
3900
|
+
this.sessionLogger.error({ err: error, agentId: msg.agentId }, "Failed to handle fetch_agent_timeline_request");
|
|
3901
|
+
this.emit({
|
|
3902
|
+
type: "fetch_agent_timeline_response",
|
|
3903
|
+
payload: {
|
|
3904
|
+
requestId: msg.requestId,
|
|
3905
|
+
agentId: msg.agentId,
|
|
3906
|
+
direction,
|
|
3907
|
+
projection,
|
|
3908
|
+
epoch: "",
|
|
3909
|
+
reset: false,
|
|
3910
|
+
staleCursor: false,
|
|
3911
|
+
gap: false,
|
|
3912
|
+
window: { minSeq: 0, maxSeq: 0, nextSeq: 0 },
|
|
3913
|
+
startCursor: null,
|
|
3914
|
+
endCursor: null,
|
|
3915
|
+
hasOlder: false,
|
|
3916
|
+
hasNewer: false,
|
|
3917
|
+
entries: [],
|
|
3918
|
+
error: error instanceof Error ? error.message : String(error),
|
|
3919
|
+
},
|
|
3920
|
+
});
|
|
3921
|
+
}
|
|
3922
|
+
}
|
|
3609
3923
|
async handleSendAgentMessageRequest(msg) {
|
|
3610
3924
|
const resolved = await this.resolveAgentIdentifier(msg.agentId);
|
|
3611
3925
|
if (!resolved.ok) {
|
|
@@ -3625,7 +3939,10 @@ export class Session {
|
|
|
3625
3939
|
await this.ensureAgentLoaded(agentId);
|
|
3626
3940
|
await this.interruptAgentIfRunning(agentId);
|
|
3627
3941
|
try {
|
|
3628
|
-
this.agentManager.recordUserMessage(agentId, msg.text, {
|
|
3942
|
+
this.agentManager.recordUserMessage(agentId, msg.text, {
|
|
3943
|
+
messageId: msg.messageId,
|
|
3944
|
+
emitState: false,
|
|
3945
|
+
});
|
|
3629
3946
|
}
|
|
3630
3947
|
catch (error) {
|
|
3631
3948
|
this.sessionLogger.error({ err: error, agentId }, "Failed to record user message for send_agent_message_request");
|
|
@@ -3782,30 +4099,6 @@ export class Session {
|
|
|
3782
4099
|
clearTimeout(timeoutHandle);
|
|
3783
4100
|
}
|
|
3784
4101
|
}
|
|
3785
|
-
emitAgentTimelineSnapshot(agent) {
|
|
3786
|
-
const timeline = this.agentManager.getTimeline(agent.id);
|
|
3787
|
-
const events = timeline.flatMap((item) => {
|
|
3788
|
-
const serializedEvent = serializeAgentStreamEvent({
|
|
3789
|
-
type: "timeline",
|
|
3790
|
-
provider: agent.provider,
|
|
3791
|
-
item,
|
|
3792
|
-
});
|
|
3793
|
-
if (!serializedEvent) {
|
|
3794
|
-
return [];
|
|
3795
|
-
}
|
|
3796
|
-
return [
|
|
3797
|
-
{
|
|
3798
|
-
event: serializedEvent,
|
|
3799
|
-
timestamp: new Date().toISOString(),
|
|
3800
|
-
},
|
|
3801
|
-
];
|
|
3802
|
-
});
|
|
3803
|
-
this.emit({
|
|
3804
|
-
type: "agent_stream_snapshot",
|
|
3805
|
-
payload: { agentId: agent.id, events },
|
|
3806
|
-
});
|
|
3807
|
-
return timeline.length;
|
|
3808
|
-
}
|
|
3809
4102
|
/**
|
|
3810
4103
|
* Handle audio chunk for buffering and transcription
|
|
3811
4104
|
*/
|
|
@@ -4257,6 +4550,17 @@ export class Session {
|
|
|
4257
4550
|
}
|
|
4258
4551
|
this.onMessage(msg);
|
|
4259
4552
|
}
|
|
4553
|
+
emitBinary(frame) {
|
|
4554
|
+
if (!this.onBinaryMessage) {
|
|
4555
|
+
return;
|
|
4556
|
+
}
|
|
4557
|
+
try {
|
|
4558
|
+
this.onBinaryMessage(frame);
|
|
4559
|
+
}
|
|
4560
|
+
catch (error) {
|
|
4561
|
+
this.sessionLogger.error({ err: error }, "Failed to emit binary frame");
|
|
4562
|
+
}
|
|
4563
|
+
}
|
|
4260
4564
|
/**
|
|
4261
4565
|
* Clean up session resources
|
|
4262
4566
|
*/
|
|
@@ -4295,10 +4599,20 @@ export class Session {
|
|
|
4295
4599
|
await this.disableVoiceModeForActiveAgent(true);
|
|
4296
4600
|
this.isVoiceMode = false;
|
|
4297
4601
|
// Unsubscribe from all terminals
|
|
4602
|
+
if (this.unsubscribeTerminalsChanged) {
|
|
4603
|
+
this.unsubscribeTerminalsChanged();
|
|
4604
|
+
this.unsubscribeTerminalsChanged = null;
|
|
4605
|
+
}
|
|
4606
|
+
this.subscribedTerminalDirectories.clear();
|
|
4298
4607
|
for (const unsubscribe of this.terminalSubscriptions.values()) {
|
|
4299
4608
|
unsubscribe();
|
|
4300
4609
|
}
|
|
4301
4610
|
this.terminalSubscriptions.clear();
|
|
4611
|
+
for (const unsubscribeExit of this.terminalExitSubscriptions.values()) {
|
|
4612
|
+
unsubscribeExit();
|
|
4613
|
+
}
|
|
4614
|
+
this.terminalExitSubscriptions.clear();
|
|
4615
|
+
this.detachAllTerminalStreams({ emitExit: false });
|
|
4302
4616
|
for (const target of this.checkoutDiffTargets.values()) {
|
|
4303
4617
|
this.closeCheckoutDiffWatchTarget(target);
|
|
4304
4618
|
}
|
|
@@ -4308,6 +4622,96 @@ export class Session {
|
|
|
4308
4622
|
// ============================================================================
|
|
4309
4623
|
// Terminal Handlers
|
|
4310
4624
|
// ============================================================================
|
|
4625
|
+
ensureTerminalExitSubscription(terminal) {
|
|
4626
|
+
if (this.terminalExitSubscriptions.has(terminal.id)) {
|
|
4627
|
+
return;
|
|
4628
|
+
}
|
|
4629
|
+
const unsubscribeExit = terminal.onExit(() => {
|
|
4630
|
+
this.handleTerminalExited(terminal.id);
|
|
4631
|
+
});
|
|
4632
|
+
this.terminalExitSubscriptions.set(terminal.id, unsubscribeExit);
|
|
4633
|
+
}
|
|
4634
|
+
handleTerminalExited(terminalId) {
|
|
4635
|
+
const unsubscribeExit = this.terminalExitSubscriptions.get(terminalId);
|
|
4636
|
+
if (unsubscribeExit) {
|
|
4637
|
+
unsubscribeExit();
|
|
4638
|
+
this.terminalExitSubscriptions.delete(terminalId);
|
|
4639
|
+
}
|
|
4640
|
+
const unsubscribe = this.terminalSubscriptions.get(terminalId);
|
|
4641
|
+
if (unsubscribe) {
|
|
4642
|
+
try {
|
|
4643
|
+
unsubscribe();
|
|
4644
|
+
}
|
|
4645
|
+
catch (error) {
|
|
4646
|
+
this.sessionLogger.warn({ err: error, terminalId }, "Failed to unsubscribe terminal after process exit");
|
|
4647
|
+
}
|
|
4648
|
+
this.terminalSubscriptions.delete(terminalId);
|
|
4649
|
+
}
|
|
4650
|
+
const streamId = this.terminalStreamByTerminalId.get(terminalId);
|
|
4651
|
+
if (typeof streamId === "number") {
|
|
4652
|
+
this.detachTerminalStream(streamId, { emitExit: true });
|
|
4653
|
+
}
|
|
4654
|
+
}
|
|
4655
|
+
emitTerminalsChangedSnapshot(input) {
|
|
4656
|
+
this.emit({
|
|
4657
|
+
type: "terminals_changed",
|
|
4658
|
+
payload: {
|
|
4659
|
+
cwd: input.cwd,
|
|
4660
|
+
terminals: input.terminals,
|
|
4661
|
+
},
|
|
4662
|
+
});
|
|
4663
|
+
}
|
|
4664
|
+
handleTerminalsChanged(event) {
|
|
4665
|
+
if (!this.subscribedTerminalDirectories.has(event.cwd)) {
|
|
4666
|
+
return;
|
|
4667
|
+
}
|
|
4668
|
+
this.emitTerminalsChangedSnapshot({
|
|
4669
|
+
cwd: event.cwd,
|
|
4670
|
+
terminals: event.terminals.map((terminal) => ({
|
|
4671
|
+
id: terminal.id,
|
|
4672
|
+
name: terminal.name,
|
|
4673
|
+
})),
|
|
4674
|
+
});
|
|
4675
|
+
}
|
|
4676
|
+
handleSubscribeTerminalsRequest(msg) {
|
|
4677
|
+
this.subscribedTerminalDirectories.add(msg.cwd);
|
|
4678
|
+
void this.emitInitialTerminalsChangedSnapshot(msg.cwd);
|
|
4679
|
+
}
|
|
4680
|
+
handleUnsubscribeTerminalsRequest(msg) {
|
|
4681
|
+
this.subscribedTerminalDirectories.delete(msg.cwd);
|
|
4682
|
+
}
|
|
4683
|
+
async emitInitialTerminalsChangedSnapshot(cwd) {
|
|
4684
|
+
if (!this.terminalManager || !this.subscribedTerminalDirectories.has(cwd)) {
|
|
4685
|
+
return;
|
|
4686
|
+
}
|
|
4687
|
+
const hadDirectoryBeforeSubscribe = this.terminalManager
|
|
4688
|
+
.listDirectories()
|
|
4689
|
+
.includes(cwd);
|
|
4690
|
+
try {
|
|
4691
|
+
const terminals = await this.terminalManager.getTerminals(cwd);
|
|
4692
|
+
for (const terminal of terminals) {
|
|
4693
|
+
this.ensureTerminalExitSubscription(terminal);
|
|
4694
|
+
}
|
|
4695
|
+
// New directories auto-create Terminal 1, which already emits through
|
|
4696
|
+
// terminal-manager change listeners.
|
|
4697
|
+
if (!hadDirectoryBeforeSubscribe) {
|
|
4698
|
+
return;
|
|
4699
|
+
}
|
|
4700
|
+
if (!this.subscribedTerminalDirectories.has(cwd)) {
|
|
4701
|
+
return;
|
|
4702
|
+
}
|
|
4703
|
+
this.emitTerminalsChangedSnapshot({
|
|
4704
|
+
cwd,
|
|
4705
|
+
terminals: terminals.map((terminal) => ({
|
|
4706
|
+
id: terminal.id,
|
|
4707
|
+
name: terminal.name,
|
|
4708
|
+
})),
|
|
4709
|
+
});
|
|
4710
|
+
}
|
|
4711
|
+
catch (error) {
|
|
4712
|
+
this.sessionLogger.warn({ err: error, cwd }, "Failed to emit initial terminal snapshot");
|
|
4713
|
+
}
|
|
4714
|
+
}
|
|
4311
4715
|
async handleListTerminalsRequest(msg) {
|
|
4312
4716
|
if (!this.terminalManager) {
|
|
4313
4717
|
this.emit({
|
|
@@ -4322,6 +4726,9 @@ export class Session {
|
|
|
4322
4726
|
}
|
|
4323
4727
|
try {
|
|
4324
4728
|
const terminals = await this.terminalManager.getTerminals(msg.cwd);
|
|
4729
|
+
for (const terminal of terminals) {
|
|
4730
|
+
this.ensureTerminalExitSubscription(terminal);
|
|
4731
|
+
}
|
|
4325
4732
|
this.emit({
|
|
4326
4733
|
type: "list_terminals_response",
|
|
4327
4734
|
payload: {
|
|
@@ -4360,6 +4767,7 @@ export class Session {
|
|
|
4360
4767
|
cwd: msg.cwd,
|
|
4361
4768
|
name: msg.name,
|
|
4362
4769
|
});
|
|
4770
|
+
this.ensureTerminalExitSubscription(session);
|
|
4363
4771
|
this.emit({
|
|
4364
4772
|
type: "create_terminal_response",
|
|
4365
4773
|
payload: {
|
|
@@ -4407,6 +4815,7 @@ export class Session {
|
|
|
4407
4815
|
});
|
|
4408
4816
|
return;
|
|
4409
4817
|
}
|
|
4818
|
+
this.ensureTerminalExitSubscription(session);
|
|
4410
4819
|
// Unsubscribe from previous subscription if any
|
|
4411
4820
|
const existing = this.terminalSubscriptions.get(msg.terminalId);
|
|
4412
4821
|
if (existing) {
|
|
@@ -4452,6 +4861,7 @@ export class Session {
|
|
|
4452
4861
|
this.sessionLogger.warn({ terminalId: msg.terminalId }, "Terminal not found for input");
|
|
4453
4862
|
return;
|
|
4454
4863
|
}
|
|
4864
|
+
this.ensureTerminalExitSubscription(session);
|
|
4455
4865
|
session.send(msg.message);
|
|
4456
4866
|
}
|
|
4457
4867
|
async handleKillTerminalRequest(msg) {
|
|
@@ -4472,6 +4882,10 @@ export class Session {
|
|
|
4472
4882
|
unsubscribe();
|
|
4473
4883
|
this.terminalSubscriptions.delete(msg.terminalId);
|
|
4474
4884
|
}
|
|
4885
|
+
const streamId = this.terminalStreamByTerminalId.get(msg.terminalId);
|
|
4886
|
+
if (typeof streamId === "number") {
|
|
4887
|
+
this.detachTerminalStream(streamId, { emitExit: true });
|
|
4888
|
+
}
|
|
4475
4889
|
this.terminalManager.killTerminal(msg.terminalId);
|
|
4476
4890
|
this.emit({
|
|
4477
4891
|
type: "kill_terminal_response",
|
|
@@ -4482,5 +4896,213 @@ export class Session {
|
|
|
4482
4896
|
},
|
|
4483
4897
|
});
|
|
4484
4898
|
}
|
|
4899
|
+
async handleAttachTerminalStreamRequest(msg) {
|
|
4900
|
+
if (!this.terminalManager || !this.onBinaryMessage) {
|
|
4901
|
+
this.emit({
|
|
4902
|
+
type: "attach_terminal_stream_response",
|
|
4903
|
+
payload: {
|
|
4904
|
+
terminalId: msg.terminalId,
|
|
4905
|
+
streamId: null,
|
|
4906
|
+
replayedFrom: 0,
|
|
4907
|
+
currentOffset: 0,
|
|
4908
|
+
earliestAvailableOffset: 0,
|
|
4909
|
+
reset: true,
|
|
4910
|
+
error: "Terminal streaming not available",
|
|
4911
|
+
requestId: msg.requestId,
|
|
4912
|
+
},
|
|
4913
|
+
});
|
|
4914
|
+
return;
|
|
4915
|
+
}
|
|
4916
|
+
const session = this.terminalManager.getTerminal(msg.terminalId);
|
|
4917
|
+
if (!session) {
|
|
4918
|
+
this.emit({
|
|
4919
|
+
type: "attach_terminal_stream_response",
|
|
4920
|
+
payload: {
|
|
4921
|
+
terminalId: msg.terminalId,
|
|
4922
|
+
streamId: null,
|
|
4923
|
+
replayedFrom: 0,
|
|
4924
|
+
currentOffset: 0,
|
|
4925
|
+
earliestAvailableOffset: 0,
|
|
4926
|
+
reset: true,
|
|
4927
|
+
error: "Terminal not found",
|
|
4928
|
+
requestId: msg.requestId,
|
|
4929
|
+
},
|
|
4930
|
+
});
|
|
4931
|
+
return;
|
|
4932
|
+
}
|
|
4933
|
+
if (msg.rows || msg.cols) {
|
|
4934
|
+
const state = session.getState();
|
|
4935
|
+
session.send({
|
|
4936
|
+
type: "resize",
|
|
4937
|
+
rows: msg.rows ?? state.rows,
|
|
4938
|
+
cols: msg.cols ?? state.cols,
|
|
4939
|
+
});
|
|
4940
|
+
}
|
|
4941
|
+
const existingStreamId = this.terminalStreamByTerminalId.get(msg.terminalId);
|
|
4942
|
+
if (typeof existingStreamId === "number") {
|
|
4943
|
+
this.detachTerminalStream(existingStreamId, { emitExit: false });
|
|
4944
|
+
}
|
|
4945
|
+
const streamId = this.allocateTerminalStreamId();
|
|
4946
|
+
const initialOffset = Math.max(0, Math.floor(msg.resumeOffset ?? 0));
|
|
4947
|
+
const binding = {
|
|
4948
|
+
terminalId: msg.terminalId,
|
|
4949
|
+
unsubscribe: () => { },
|
|
4950
|
+
lastOutputOffset: initialOffset,
|
|
4951
|
+
lastAckOffset: initialOffset,
|
|
4952
|
+
pendingChunks: [],
|
|
4953
|
+
pendingBytes: 0,
|
|
4954
|
+
};
|
|
4955
|
+
this.terminalStreams.set(streamId, binding);
|
|
4956
|
+
this.terminalStreamByTerminalId.set(msg.terminalId, streamId);
|
|
4957
|
+
let rawSub;
|
|
4958
|
+
try {
|
|
4959
|
+
rawSub = session.subscribeRaw((chunk) => {
|
|
4960
|
+
const currentBinding = this.terminalStreams.get(streamId);
|
|
4961
|
+
if (!currentBinding) {
|
|
4962
|
+
return;
|
|
4963
|
+
}
|
|
4964
|
+
this.enqueueOrEmitTerminalStreamChunk(streamId, currentBinding, {
|
|
4965
|
+
data: chunk.data,
|
|
4966
|
+
startOffset: chunk.startOffset,
|
|
4967
|
+
endOffset: chunk.endOffset,
|
|
4968
|
+
replay: chunk.replay,
|
|
4969
|
+
});
|
|
4970
|
+
}, { fromOffset: msg.resumeOffset ?? 0 });
|
|
4971
|
+
}
|
|
4972
|
+
catch (error) {
|
|
4973
|
+
this.terminalStreams.delete(streamId);
|
|
4974
|
+
this.terminalStreamByTerminalId.delete(msg.terminalId);
|
|
4975
|
+
throw error;
|
|
4976
|
+
}
|
|
4977
|
+
binding.unsubscribe = rawSub.unsubscribe;
|
|
4978
|
+
binding.lastAckOffset = rawSub.replayedFrom;
|
|
4979
|
+
if (binding.lastOutputOffset < rawSub.replayedFrom) {
|
|
4980
|
+
binding.lastOutputOffset = rawSub.replayedFrom;
|
|
4981
|
+
}
|
|
4982
|
+
this.flushPendingTerminalStreamChunks(streamId, binding);
|
|
4983
|
+
this.emit({
|
|
4984
|
+
type: "attach_terminal_stream_response",
|
|
4985
|
+
payload: {
|
|
4986
|
+
terminalId: msg.terminalId,
|
|
4987
|
+
streamId,
|
|
4988
|
+
replayedFrom: rawSub.replayedFrom,
|
|
4989
|
+
currentOffset: rawSub.currentOffset,
|
|
4990
|
+
earliestAvailableOffset: rawSub.earliestAvailableOffset,
|
|
4991
|
+
reset: rawSub.reset,
|
|
4992
|
+
error: null,
|
|
4993
|
+
requestId: msg.requestId,
|
|
4994
|
+
},
|
|
4995
|
+
});
|
|
4996
|
+
}
|
|
4997
|
+
getTerminalStreamChunkByteLength(chunk) {
|
|
4998
|
+
return Math.max(0, chunk.endOffset - chunk.startOffset);
|
|
4999
|
+
}
|
|
5000
|
+
canEmitTerminalStreamChunk(binding, chunk) {
|
|
5001
|
+
return chunk.startOffset < binding.lastAckOffset + TERMINAL_STREAM_WINDOW_BYTES;
|
|
5002
|
+
}
|
|
5003
|
+
emitTerminalStreamChunk(streamId, binding, chunk) {
|
|
5004
|
+
const payload = new Uint8Array(Buffer.from(chunk.data, "utf8"));
|
|
5005
|
+
this.emitBinary({
|
|
5006
|
+
channel: BinaryMuxChannel.Terminal,
|
|
5007
|
+
messageType: TerminalBinaryMessageType.OutputUtf8,
|
|
5008
|
+
streamId,
|
|
5009
|
+
offset: chunk.startOffset,
|
|
5010
|
+
flags: chunk.replay ? TerminalBinaryFlags.Replay : 0,
|
|
5011
|
+
payload,
|
|
5012
|
+
});
|
|
5013
|
+
binding.lastOutputOffset = chunk.endOffset;
|
|
5014
|
+
}
|
|
5015
|
+
enqueueOrEmitTerminalStreamChunk(streamId, binding, chunk) {
|
|
5016
|
+
const chunkBytes = this.getTerminalStreamChunkByteLength(chunk);
|
|
5017
|
+
if (binding.pendingChunks.length > 0 || !this.canEmitTerminalStreamChunk(binding, chunk)) {
|
|
5018
|
+
if (binding.pendingChunks.length >= TERMINAL_STREAM_MAX_PENDING_CHUNKS ||
|
|
5019
|
+
binding.pendingBytes + chunkBytes > TERMINAL_STREAM_MAX_PENDING_BYTES) {
|
|
5020
|
+
this.sessionLogger.warn({
|
|
5021
|
+
streamId,
|
|
5022
|
+
pendingChunks: binding.pendingChunks.length,
|
|
5023
|
+
pendingBytes: binding.pendingBytes,
|
|
5024
|
+
chunkBytes,
|
|
5025
|
+
}, "Terminal stream pending buffer overflow; closing stream");
|
|
5026
|
+
this.detachTerminalStream(streamId, { emitExit: true });
|
|
5027
|
+
return;
|
|
5028
|
+
}
|
|
5029
|
+
binding.pendingChunks.push(chunk);
|
|
5030
|
+
binding.pendingBytes += chunkBytes;
|
|
5031
|
+
return;
|
|
5032
|
+
}
|
|
5033
|
+
this.emitTerminalStreamChunk(streamId, binding, chunk);
|
|
5034
|
+
}
|
|
5035
|
+
flushPendingTerminalStreamChunks(streamId, binding) {
|
|
5036
|
+
while (binding.pendingChunks.length > 0) {
|
|
5037
|
+
const next = binding.pendingChunks[0];
|
|
5038
|
+
if (!next || !this.canEmitTerminalStreamChunk(binding, next)) {
|
|
5039
|
+
break;
|
|
5040
|
+
}
|
|
5041
|
+
binding.pendingChunks.shift();
|
|
5042
|
+
binding.pendingBytes -= this.getTerminalStreamChunkByteLength(next);
|
|
5043
|
+
if (binding.pendingBytes < 0) {
|
|
5044
|
+
binding.pendingBytes = 0;
|
|
5045
|
+
}
|
|
5046
|
+
this.emitTerminalStreamChunk(streamId, binding, next);
|
|
5047
|
+
}
|
|
5048
|
+
}
|
|
5049
|
+
handleDetachTerminalStreamRequest(msg) {
|
|
5050
|
+
const success = this.detachTerminalStream(msg.streamId, { emitExit: false });
|
|
5051
|
+
this.emit({
|
|
5052
|
+
type: "detach_terminal_stream_response",
|
|
5053
|
+
payload: {
|
|
5054
|
+
streamId: msg.streamId,
|
|
5055
|
+
success,
|
|
5056
|
+
requestId: msg.requestId,
|
|
5057
|
+
},
|
|
5058
|
+
});
|
|
5059
|
+
}
|
|
5060
|
+
detachAllTerminalStreams(options) {
|
|
5061
|
+
for (const streamId of Array.from(this.terminalStreams.keys())) {
|
|
5062
|
+
this.detachTerminalStream(streamId, options);
|
|
5063
|
+
}
|
|
5064
|
+
}
|
|
5065
|
+
detachTerminalStream(streamId, options) {
|
|
5066
|
+
const binding = this.terminalStreams.get(streamId);
|
|
5067
|
+
if (!binding) {
|
|
5068
|
+
return false;
|
|
5069
|
+
}
|
|
5070
|
+
try {
|
|
5071
|
+
binding.unsubscribe();
|
|
5072
|
+
}
|
|
5073
|
+
catch (error) {
|
|
5074
|
+
this.sessionLogger.warn({ err: error, streamId }, "Failed to unsubscribe terminal stream");
|
|
5075
|
+
}
|
|
5076
|
+
this.terminalStreams.delete(streamId);
|
|
5077
|
+
if (this.terminalStreamByTerminalId.get(binding.terminalId) === streamId) {
|
|
5078
|
+
this.terminalStreamByTerminalId.delete(binding.terminalId);
|
|
5079
|
+
}
|
|
5080
|
+
if (options?.emitExit) {
|
|
5081
|
+
this.emit({
|
|
5082
|
+
type: "terminal_stream_exit",
|
|
5083
|
+
payload: {
|
|
5084
|
+
streamId,
|
|
5085
|
+
terminalId: binding.terminalId,
|
|
5086
|
+
},
|
|
5087
|
+
});
|
|
5088
|
+
}
|
|
5089
|
+
return true;
|
|
5090
|
+
}
|
|
5091
|
+
allocateTerminalStreamId() {
|
|
5092
|
+
let attempts = 0;
|
|
5093
|
+
while (attempts < 0xffffffff) {
|
|
5094
|
+
const candidate = this.nextTerminalStreamId >>> 0;
|
|
5095
|
+
this.nextTerminalStreamId = ((this.nextTerminalStreamId + 1) & 0xffffffff) >>> 0;
|
|
5096
|
+
if (candidate === 0) {
|
|
5097
|
+
attempts += 1;
|
|
5098
|
+
continue;
|
|
5099
|
+
}
|
|
5100
|
+
if (!this.terminalStreams.has(candidate)) {
|
|
5101
|
+
return candidate;
|
|
5102
|
+
}
|
|
5103
|
+
attempts += 1;
|
|
5104
|
+
}
|
|
5105
|
+
throw new Error("Unable to allocate terminal stream id");
|
|
5106
|
+
}
|
|
4485
5107
|
}
|
|
4486
5108
|
//# sourceMappingURL=session.js.map
|