@getpaseo/server 0.1.4 → 0.1.7
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/daemon-runner.js +31 -7
- package/dist/scripts/daemon-runner.js.map +1 -1
- package/dist/server/client/daemon-client-terminal-stream-manager.d.ts.map +1 -1
- package/dist/server/client/daemon-client-terminal-stream-manager.js +4 -0
- package/dist/server/client/daemon-client-terminal-stream-manager.js.map +1 -1
- package/dist/server/client/daemon-client.d.ts +25 -15
- package/dist/server/client/daemon-client.d.ts.map +1 -1
- package/dist/server/client/daemon-client.js +47 -23
- 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 +3 -0
- package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
- package/dist/server/server/agent/agent-manager.js +27 -5
- 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/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/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/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/bootstrap.d.ts.map +1 -1
- package/dist/server/server/bootstrap.js +3 -0
- package/dist/server/server/bootstrap.js.map +1 -1
- package/dist/server/server/exports.d.ts +1 -0
- package/dist/server/server/exports.d.ts.map +1 -1
- package/dist/server/server/exports.js +1 -0
- package/dist/server/server/exports.js.map +1 -1
- package/dist/server/server/persisted-config.d.ts +8 -8
- package/dist/server/server/session.d.ts +20 -6
- package/dist/server/server/session.d.ts.map +1 -1
- package/dist/server/server/session.js +409 -173
- package/dist/server/server/session.js.map +1 -1
- package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.d.ts.map +1 -1
- package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js +85 -27
- package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js.map +1 -1
- package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.d.ts +18 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.d.ts.map +1 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.js +68 -0
- package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.js.map +1 -0
- package/dist/server/server/utils/syntax-highlighter.d.ts.map +1 -1
- package/dist/server/server/utils/syntax-highlighter.js +4 -0
- package/dist/server/server/utils/syntax-highlighter.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 +454 -0
- package/dist/server/server/worktree-bootstrap.js.map +1 -0
- package/dist/server/shared/binary-mux.d.ts.map +1 -1
- package/dist/server/shared/binary-mux.js +13 -0
- package/dist/server/shared/binary-mux.js.map +1 -1
- package/dist/server/shared/messages.d.ts +2279 -2493
- package/dist/server/shared/messages.d.ts.map +1 -1
- package/dist/server/shared/messages.js +71 -25
- package/dist/server/shared/messages.js.map +1 -1
- 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 +16 -0
- package/dist/server/terminal/terminal-manager.d.ts.map +1 -1
- package/dist/server/terminal/terminal-manager.js +105 -24
- package/dist/server/terminal/terminal-manager.js.map +1 -1
- package/dist/server/terminal/terminal.d.ts +1 -0
- package/dist/server/terminal/terminal.d.ts.map +1 -1
- package/dist/server/terminal/terminal.js +54 -6
- package/dist/server/terminal/terminal.js.map +1 -1
- package/dist/server/utils/directory-suggestions.d.ts +9 -0
- package/dist/server/utils/directory-suggestions.d.ts.map +1 -0
- package/dist/server/utils/directory-suggestions.js +328 -0
- package/dist/server/utils/directory-suggestions.js.map +1 -0
- package/dist/server/utils/worktree-metadata.d.ts +28 -2
- package/dist/server/utils/worktree-metadata.d.ts.map +1 -1
- package/dist/server/utils/worktree-metadata.js +43 -1
- package/dist/server/utils/worktree-metadata.js.map +1 -1
- package/dist/server/utils/worktree.d.ts +45 -0
- package/dist/server/utils/worktree.d.ts.map +1 -1
- package/dist/server/utils/worktree.js +183 -18
- package/dist/server/utils/worktree.js.map +1 -1
- package/package.json +3 -2
|
@@ -4,6 +4,7 @@ import { stat } from "fs/promises";
|
|
|
4
4
|
import { exec } from "child_process";
|
|
5
5
|
import { promisify } from "util";
|
|
6
6
|
import { join, resolve, sep } from "path";
|
|
7
|
+
import { homedir } from "node:os";
|
|
7
8
|
import { z } from "zod";
|
|
8
9
|
import { serializeAgentStreamEvent, } from "./messages.js";
|
|
9
10
|
import { BinaryMuxChannel, TerminalBinaryFlags, TerminalBinaryMessageType, } from "../shared/binary-mux.js";
|
|
@@ -17,16 +18,19 @@ import { experimental_createMCPClient } from "ai";
|
|
|
17
18
|
import { buildProviderRegistry } from "./agent/provider-registry.js";
|
|
18
19
|
import { scheduleAgentMetadataGeneration } from "./agent/agent-metadata-generator.js";
|
|
19
20
|
import { toAgentPayload } from "./agent/agent-projections.js";
|
|
21
|
+
import { appendTimelineItemIfAgentKnown, emitLiveTimelineItemIfAgentKnown, } from "./agent/timeline-append.js";
|
|
20
22
|
import { projectTimelineRows } from "./agent/timeline-projection.js";
|
|
21
23
|
import { StructuredAgentResponseError, generateStructuredAgentResponse, } from "./agent/agent-response-loop.js";
|
|
22
24
|
import { isValidAgentProvider, AGENT_PROVIDER_IDS } from "./agent/provider-manifest.js";
|
|
23
25
|
import { buildVoiceAgentMcpServerConfig, buildVoiceModeSystemPrompt, stripVoiceModeSystemPrompt, } from "./voice-config.js";
|
|
24
26
|
import { isVoicePermissionAllowed } from "./voice-permission-policy.js";
|
|
25
27
|
import { listDirectoryEntries, readExplorerFile, getDownloadableFileInfo, } from "./file-explorer/service.js";
|
|
26
|
-
import {
|
|
28
|
+
import { slugify, validateBranchSlug, listPaseoWorktrees, deletePaseoWorktree, isPaseoOwnedWorktreeCwd, resolvePaseoWorktreeRootForCwd, } from "../utils/worktree.js";
|
|
29
|
+
import { createAgentWorktree, runAsyncWorktreeBootstrap, } from "./worktree-bootstrap.js";
|
|
27
30
|
import { getCheckoutDiff, getCheckoutStatus, getCheckoutStatusLite, listBranchSuggestions, NotGitRepoError, MergeConflictError, MergeFromBaseConflictError, commitChanges, mergeToBase, mergeFromBase, pushCurrentBranch, createPullRequest, getPullRequestStatus, } from "../utils/checkout-git.js";
|
|
28
31
|
import { getProjectIcon } from "../utils/project-icon.js";
|
|
29
32
|
import { expandTilde } from "../utils/path.js";
|
|
33
|
+
import { searchHomeDirectories } from "../utils/directory-suggestions.js";
|
|
30
34
|
import { ensureLocalSpeechModels, getLocalSpeechModelDir, listLocalSpeechModels, } from "./speech/providers/local/models.js";
|
|
31
35
|
const execAsync = promisify(exec);
|
|
32
36
|
const READ_ONLY_GIT_ENV = {
|
|
@@ -37,8 +41,6 @@ const pendingAgentInitializations = new Map();
|
|
|
37
41
|
let restartRequested = false;
|
|
38
42
|
const DEFAULT_AGENT_PROVIDER = AGENT_PROVIDER_IDS[0];
|
|
39
43
|
const RESTART_EXIT_DELAY_MS = 250;
|
|
40
|
-
const PROJECT_PLACEMENT_CACHE_TTL_MS = 10000;
|
|
41
|
-
const MAX_AGENTS_PER_PROJECT = 5;
|
|
42
44
|
const CHECKOUT_DIFF_WATCH_DEBOUNCE_MS = 150;
|
|
43
45
|
const CHECKOUT_DIFF_FALLBACK_REFRESH_MS = 5000;
|
|
44
46
|
const TERMINAL_STREAM_WINDOW_BYTES = 256 * 1024;
|
|
@@ -90,17 +92,17 @@ function deriveRemoteProjectKey(remoteUrl) {
|
|
|
90
92
|
}
|
|
91
93
|
return `remote:${cleanedHost}/${cleanedPath}`;
|
|
92
94
|
}
|
|
93
|
-
function deriveProjectGroupingKey(
|
|
94
|
-
const remoteKey = deriveRemoteProjectKey(remoteUrl);
|
|
95
|
+
function deriveProjectGroupingKey(options) {
|
|
96
|
+
const remoteKey = deriveRemoteProjectKey(options.remoteUrl);
|
|
95
97
|
if (remoteKey) {
|
|
96
98
|
return remoteKey;
|
|
97
99
|
}
|
|
98
100
|
const worktreeMarker = ".paseo/worktrees/";
|
|
99
|
-
const idx = cwd.indexOf(worktreeMarker);
|
|
101
|
+
const idx = options.cwd.indexOf(worktreeMarker);
|
|
100
102
|
if (idx !== -1) {
|
|
101
|
-
return cwd.slice(0, idx).replace(/\/$/, "");
|
|
103
|
+
return options.cwd.slice(0, idx).replace(/\/$/, "");
|
|
102
104
|
}
|
|
103
|
-
return cwd;
|
|
105
|
+
return options.cwd;
|
|
104
106
|
}
|
|
105
107
|
function deriveProjectGroupingName(projectKey) {
|
|
106
108
|
const githubRemotePrefix = "remote:github.com/";
|
|
@@ -110,6 +112,13 @@ function deriveProjectGroupingName(projectKey) {
|
|
|
110
112
|
const segments = projectKey.split(/[\\/]/).filter(Boolean);
|
|
111
113
|
return segments[segments.length - 1] || projectKey;
|
|
112
114
|
}
|
|
115
|
+
class SessionRequestError extends Error {
|
|
116
|
+
constructor(code, message) {
|
|
117
|
+
super(message);
|
|
118
|
+
this.code = code;
|
|
119
|
+
this.name = "SessionRequestError";
|
|
120
|
+
}
|
|
121
|
+
}
|
|
113
122
|
const PCM_SAMPLE_RATE = 16000;
|
|
114
123
|
const PCM_CHANNELS = 1;
|
|
115
124
|
const PCM_BITS_PER_SAMPLE = 16;
|
|
@@ -209,10 +218,12 @@ export class Session {
|
|
|
209
218
|
this.agentTools = null;
|
|
210
219
|
this.unsubscribeAgentEvents = null;
|
|
211
220
|
this.agentUpdatesSubscription = null;
|
|
212
|
-
this.projectPlacementCache = new Map();
|
|
213
221
|
this.clientActivity = null;
|
|
214
222
|
this.MOBILE_BACKGROUND_STREAM_GRACE_MS = 60000;
|
|
223
|
+
this.subscribedTerminalDirectories = new Set();
|
|
224
|
+
this.unsubscribeTerminalsChanged = null;
|
|
215
225
|
this.terminalSubscriptions = new Map();
|
|
226
|
+
this.terminalExitSubscriptions = new Map();
|
|
216
227
|
this.terminalStreams = new Map();
|
|
217
228
|
this.terminalStreamByTerminalId = new Map();
|
|
218
229
|
this.nextTerminalStreamId = 1;
|
|
@@ -232,6 +243,9 @@ export class Session {
|
|
|
232
243
|
this.agentStorage = agentStorage;
|
|
233
244
|
this.createAgentMcpTransport = createAgentMcpTransport;
|
|
234
245
|
this.terminalManager = terminalManager;
|
|
246
|
+
if (this.terminalManager) {
|
|
247
|
+
this.unsubscribeTerminalsChanged = this.terminalManager.subscribeTerminalsChanged((event) => this.handleTerminalsChanged(event));
|
|
248
|
+
}
|
|
235
249
|
this.voiceAgentMcpStdio = voice?.voiceAgentMcpStdio ?? null;
|
|
236
250
|
const configuredModelsDir = dictation?.localModels?.modelsDir?.trim();
|
|
237
251
|
this.localSpeechModelsDir =
|
|
@@ -619,26 +633,16 @@ export class Session {
|
|
|
619
633
|
const checkout = await getCheckoutStatusLite(cwd, { paseoHome: this.paseoHome })
|
|
620
634
|
.then((status) => this.toProjectCheckoutLite(cwd, status))
|
|
621
635
|
.catch(() => this.buildFallbackProjectCheckout(cwd));
|
|
622
|
-
const projectKey = deriveProjectGroupingKey(
|
|
636
|
+
const projectKey = deriveProjectGroupingKey({
|
|
637
|
+
cwd,
|
|
638
|
+
remoteUrl: checkout.remoteUrl,
|
|
639
|
+
});
|
|
623
640
|
return {
|
|
624
641
|
projectKey,
|
|
625
642
|
projectName: deriveProjectGroupingName(projectKey),
|
|
626
643
|
checkout,
|
|
627
644
|
};
|
|
628
645
|
}
|
|
629
|
-
getProjectPlacement(cwd) {
|
|
630
|
-
const now = Date.now();
|
|
631
|
-
const cached = this.projectPlacementCache.get(cwd);
|
|
632
|
-
if (cached && cached.expiresAt > now) {
|
|
633
|
-
return cached.promise;
|
|
634
|
-
}
|
|
635
|
-
const promise = this.buildProjectPlacement(cwd);
|
|
636
|
-
this.projectPlacementCache.set(cwd, {
|
|
637
|
-
expiresAt: now + PROJECT_PLACEMENT_CACHE_TTL_MS,
|
|
638
|
-
promise,
|
|
639
|
-
});
|
|
640
|
-
return promise;
|
|
641
|
-
}
|
|
642
646
|
async forwardAgentUpdate(agent) {
|
|
643
647
|
try {
|
|
644
648
|
const subscription = this.agentUpdatesSubscription;
|
|
@@ -648,7 +652,7 @@ export class Session {
|
|
|
648
652
|
const payload = await this.buildAgentPayload(agent);
|
|
649
653
|
const matches = this.matchesAgentFilter(payload, subscription.filter);
|
|
650
654
|
if (matches) {
|
|
651
|
-
const project = await this.
|
|
655
|
+
const project = await this.buildProjectPlacement(payload.cwd);
|
|
652
656
|
this.emit({
|
|
653
657
|
type: "agent_update",
|
|
654
658
|
payload: { kind: "upsert", agent: payload, project },
|
|
@@ -680,10 +684,7 @@ export class Session {
|
|
|
680
684
|
this.handleAudioPlayed(msg.id);
|
|
681
685
|
break;
|
|
682
686
|
case "fetch_agents_request":
|
|
683
|
-
await this.handleFetchAgents(msg
|
|
684
|
-
break;
|
|
685
|
-
case "fetch_agents_grouped_by_project_request":
|
|
686
|
-
await this.handleFetchAgentsGroupedByProject(msg.requestId, msg.filter);
|
|
687
|
+
await this.handleFetchAgents(msg);
|
|
687
688
|
break;
|
|
688
689
|
case "fetch_agent_request":
|
|
689
690
|
await this.handleFetchAgent(msg.agentId, msg.requestId);
|
|
@@ -789,6 +790,9 @@ export class Session {
|
|
|
789
790
|
case "branch_suggestions_request":
|
|
790
791
|
await this.handleBranchSuggestionsRequest(msg);
|
|
791
792
|
break;
|
|
793
|
+
case "directory_suggestions_request":
|
|
794
|
+
await this.handleDirectorySuggestionsRequest(msg);
|
|
795
|
+
break;
|
|
792
796
|
case "subscribe_checkout_diff_request":
|
|
793
797
|
await this.handleSubscribeCheckoutDiffRequest(msg);
|
|
794
798
|
break;
|
|
@@ -868,6 +872,12 @@ export class Session {
|
|
|
868
872
|
case "register_push_token":
|
|
869
873
|
this.handleRegisterPushToken(msg.token);
|
|
870
874
|
break;
|
|
875
|
+
case "subscribe_terminals_request":
|
|
876
|
+
this.handleSubscribeTerminalsRequest(msg);
|
|
877
|
+
break;
|
|
878
|
+
case "unsubscribe_terminals_request":
|
|
879
|
+
this.handleUnsubscribeTerminalsRequest(msg);
|
|
880
|
+
break;
|
|
871
881
|
case "list_terminals_request":
|
|
872
882
|
await this.handleListTerminalsRequest(msg);
|
|
873
883
|
break;
|
|
@@ -1546,7 +1556,10 @@ export class Session {
|
|
|
1546
1556
|
}
|
|
1547
1557
|
const prompt = this.buildAgentPrompt(text, images);
|
|
1548
1558
|
try {
|
|
1549
|
-
this.agentManager.recordUserMessage(agentId, text, {
|
|
1559
|
+
this.agentManager.recordUserMessage(agentId, text, {
|
|
1560
|
+
messageId,
|
|
1561
|
+
emitState: false,
|
|
1562
|
+
});
|
|
1550
1563
|
}
|
|
1551
1564
|
catch (error) {
|
|
1552
1565
|
this.sessionLogger.error({ err: error, agentId }, `Failed to record user message for agent ${agentId}`);
|
|
@@ -1606,7 +1619,22 @@ export class Session {
|
|
|
1606
1619
|
});
|
|
1607
1620
|
}
|
|
1608
1621
|
if (worktreeConfig) {
|
|
1609
|
-
void
|
|
1622
|
+
void runAsyncWorktreeBootstrap({
|
|
1623
|
+
agentId: snapshot.id,
|
|
1624
|
+
worktree: worktreeConfig,
|
|
1625
|
+
terminalManager: this.terminalManager,
|
|
1626
|
+
appendTimelineItem: (item) => appendTimelineItemIfAgentKnown({
|
|
1627
|
+
agentManager: this.agentManager,
|
|
1628
|
+
agentId: snapshot.id,
|
|
1629
|
+
item,
|
|
1630
|
+
}),
|
|
1631
|
+
emitLiveTimelineItem: (item) => emitLiveTimelineItemIfAgentKnown({
|
|
1632
|
+
agentManager: this.agentManager,
|
|
1633
|
+
agentId: snapshot.id,
|
|
1634
|
+
item,
|
|
1635
|
+
}),
|
|
1636
|
+
logger: this.sessionLogger,
|
|
1637
|
+
});
|
|
1610
1638
|
}
|
|
1611
1639
|
this.sessionLogger.info({ agentId: snapshot.id, provider: snapshot.provider }, `Created agent ${snapshot.id} (${snapshot.provider})`);
|
|
1612
1640
|
}
|
|
@@ -1776,12 +1804,11 @@ export class Session {
|
|
|
1776
1804
|
throw new Error("A branch name is required when creating a worktree.");
|
|
1777
1805
|
}
|
|
1778
1806
|
this.sessionLogger.info({ worktreeSlug: normalized.worktreeSlug ?? targetBranch, branch: targetBranch }, `Creating worktree '${normalized.worktreeSlug ?? targetBranch}' for branch ${targetBranch}`);
|
|
1779
|
-
const createdWorktree = await
|
|
1807
|
+
const createdWorktree = await createAgentWorktree({
|
|
1780
1808
|
branchName: targetBranch,
|
|
1781
1809
|
cwd,
|
|
1782
1810
|
baseBranch: normalized.baseBranch,
|
|
1783
1811
|
worktreeSlug: normalized.worktreeSlug ?? targetBranch,
|
|
1784
|
-
runSetup: false,
|
|
1785
1812
|
paseoHome: this.paseoHome,
|
|
1786
1813
|
});
|
|
1787
1814
|
cwd = createdWorktree.worktreePath;
|
|
@@ -1805,100 +1832,6 @@ export class Session {
|
|
|
1805
1832
|
worktreeConfig,
|
|
1806
1833
|
};
|
|
1807
1834
|
}
|
|
1808
|
-
async runAsyncWorktreeSetup(agentId, worktree) {
|
|
1809
|
-
const callId = uuidv4();
|
|
1810
|
-
let results = [];
|
|
1811
|
-
try {
|
|
1812
|
-
const started = await this.safeAppendTimelineItem(agentId, {
|
|
1813
|
-
type: "tool_call",
|
|
1814
|
-
name: "paseo_worktree_setup",
|
|
1815
|
-
callId,
|
|
1816
|
-
status: "running",
|
|
1817
|
-
detail: {
|
|
1818
|
-
type: "unknown",
|
|
1819
|
-
input: {
|
|
1820
|
-
worktreePath: worktree.worktreePath,
|
|
1821
|
-
branchName: worktree.branchName,
|
|
1822
|
-
},
|
|
1823
|
-
output: null,
|
|
1824
|
-
},
|
|
1825
|
-
error: null,
|
|
1826
|
-
});
|
|
1827
|
-
if (!started) {
|
|
1828
|
-
return;
|
|
1829
|
-
}
|
|
1830
|
-
results = await runWorktreeSetupCommands({
|
|
1831
|
-
worktreePath: worktree.worktreePath,
|
|
1832
|
-
branchName: worktree.branchName,
|
|
1833
|
-
cleanupOnFailure: false,
|
|
1834
|
-
});
|
|
1835
|
-
await this.safeAppendTimelineItem(agentId, {
|
|
1836
|
-
type: "tool_call",
|
|
1837
|
-
name: "paseo_worktree_setup",
|
|
1838
|
-
callId,
|
|
1839
|
-
status: "completed",
|
|
1840
|
-
detail: {
|
|
1841
|
-
type: "unknown",
|
|
1842
|
-
input: {
|
|
1843
|
-
worktreePath: worktree.worktreePath,
|
|
1844
|
-
branchName: worktree.branchName,
|
|
1845
|
-
},
|
|
1846
|
-
output: {
|
|
1847
|
-
worktreePath: worktree.worktreePath,
|
|
1848
|
-
commands: results.map((result) => ({
|
|
1849
|
-
command: result.command,
|
|
1850
|
-
cwd: result.cwd,
|
|
1851
|
-
exitCode: result.exitCode,
|
|
1852
|
-
output: `${result.stdout ?? ""}${result.stderr ? `\n${result.stderr}` : ""}`.trim(),
|
|
1853
|
-
})),
|
|
1854
|
-
},
|
|
1855
|
-
},
|
|
1856
|
-
error: null,
|
|
1857
|
-
});
|
|
1858
|
-
}
|
|
1859
|
-
catch (error) {
|
|
1860
|
-
if (error instanceof WorktreeSetupError) {
|
|
1861
|
-
results = error.results;
|
|
1862
|
-
}
|
|
1863
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1864
|
-
await this.safeAppendTimelineItem(agentId, {
|
|
1865
|
-
type: "tool_call",
|
|
1866
|
-
name: "paseo_worktree_setup",
|
|
1867
|
-
callId,
|
|
1868
|
-
status: "failed",
|
|
1869
|
-
detail: {
|
|
1870
|
-
type: "unknown",
|
|
1871
|
-
input: {
|
|
1872
|
-
worktreePath: worktree.worktreePath,
|
|
1873
|
-
branchName: worktree.branchName,
|
|
1874
|
-
},
|
|
1875
|
-
output: {
|
|
1876
|
-
worktreePath: worktree.worktreePath,
|
|
1877
|
-
commands: results.map((result) => ({
|
|
1878
|
-
command: result.command,
|
|
1879
|
-
cwd: result.cwd,
|
|
1880
|
-
exitCode: result.exitCode,
|
|
1881
|
-
output: `${result.stdout ?? ""}${result.stderr ? `\n${result.stderr}` : ""}`.trim(),
|
|
1882
|
-
})),
|
|
1883
|
-
},
|
|
1884
|
-
},
|
|
1885
|
-
error: { message },
|
|
1886
|
-
});
|
|
1887
|
-
}
|
|
1888
|
-
}
|
|
1889
|
-
async safeAppendTimelineItem(agentId, item) {
|
|
1890
|
-
try {
|
|
1891
|
-
await this.agentManager.appendTimelineItem(agentId, item);
|
|
1892
|
-
return true;
|
|
1893
|
-
}
|
|
1894
|
-
catch (error) {
|
|
1895
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1896
|
-
if (message.includes("Unknown agent")) {
|
|
1897
|
-
return false;
|
|
1898
|
-
}
|
|
1899
|
-
throw error;
|
|
1900
|
-
}
|
|
1901
|
-
}
|
|
1902
1835
|
async handleListProviderModelsRequest(msg) {
|
|
1903
1836
|
const fetchedAt = new Date().toISOString();
|
|
1904
1837
|
try {
|
|
@@ -2583,8 +2516,9 @@ export class Session {
|
|
|
2583
2516
|
}
|
|
2584
2517
|
async handleCheckoutStatusRequest(msg) {
|
|
2585
2518
|
const { cwd, requestId } = msg;
|
|
2519
|
+
const resolvedCwd = expandTilde(cwd);
|
|
2586
2520
|
try {
|
|
2587
|
-
const status = await getCheckoutStatus(
|
|
2521
|
+
const status = await getCheckoutStatus(resolvedCwd, { paseoHome: this.paseoHome });
|
|
2588
2522
|
if (!status.isGit) {
|
|
2589
2523
|
this.emit({
|
|
2590
2524
|
type: "checkout_status_response",
|
|
@@ -2764,6 +2698,34 @@ export class Session {
|
|
|
2764
2698
|
});
|
|
2765
2699
|
}
|
|
2766
2700
|
}
|
|
2701
|
+
async handleDirectorySuggestionsRequest(msg) {
|
|
2702
|
+
const { query, limit, requestId } = msg;
|
|
2703
|
+
try {
|
|
2704
|
+
const directories = await searchHomeDirectories({
|
|
2705
|
+
homeDir: process.env.HOME ?? homedir(),
|
|
2706
|
+
query,
|
|
2707
|
+
limit,
|
|
2708
|
+
});
|
|
2709
|
+
this.emit({
|
|
2710
|
+
type: "directory_suggestions_response",
|
|
2711
|
+
payload: {
|
|
2712
|
+
directories,
|
|
2713
|
+
error: null,
|
|
2714
|
+
requestId,
|
|
2715
|
+
},
|
|
2716
|
+
});
|
|
2717
|
+
}
|
|
2718
|
+
catch (error) {
|
|
2719
|
+
this.emit({
|
|
2720
|
+
type: "directory_suggestions_response",
|
|
2721
|
+
payload: {
|
|
2722
|
+
directories: [],
|
|
2723
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2724
|
+
requestId,
|
|
2725
|
+
},
|
|
2726
|
+
});
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2767
2729
|
normalizeCheckoutDiffCompare(compare) {
|
|
2768
2730
|
if (compare.mode === "uncommitted") {
|
|
2769
2731
|
return { mode: "uncommitted" };
|
|
@@ -3672,64 +3634,230 @@ export class Session {
|
|
|
3672
3634
|
}
|
|
3673
3635
|
return this.buildStoredAgentPayload(record);
|
|
3674
3636
|
}
|
|
3675
|
-
|
|
3637
|
+
normalizeFetchAgentsSort(sort) {
|
|
3638
|
+
const fallback = [
|
|
3639
|
+
{ key: "updated_at", direction: "desc" },
|
|
3640
|
+
];
|
|
3641
|
+
if (!sort || sort.length === 0) {
|
|
3642
|
+
return fallback;
|
|
3643
|
+
}
|
|
3644
|
+
const deduped = [];
|
|
3645
|
+
const seen = new Set();
|
|
3646
|
+
for (const entry of sort) {
|
|
3647
|
+
if (seen.has(entry.key)) {
|
|
3648
|
+
continue;
|
|
3649
|
+
}
|
|
3650
|
+
seen.add(entry.key);
|
|
3651
|
+
deduped.push(entry);
|
|
3652
|
+
}
|
|
3653
|
+
return deduped.length > 0 ? deduped : fallback;
|
|
3654
|
+
}
|
|
3655
|
+
getStatusPriority(agent) {
|
|
3656
|
+
const requiresAttention = agent.requiresAttention ?? false;
|
|
3657
|
+
const attentionReason = agent.attentionReason ?? null;
|
|
3658
|
+
if (requiresAttention && attentionReason === "permission") {
|
|
3659
|
+
return 0;
|
|
3660
|
+
}
|
|
3661
|
+
if (agent.status === "error" || attentionReason === "error") {
|
|
3662
|
+
return 1;
|
|
3663
|
+
}
|
|
3664
|
+
if (agent.status === "running") {
|
|
3665
|
+
return 2;
|
|
3666
|
+
}
|
|
3667
|
+
if (agent.status === "initializing") {
|
|
3668
|
+
return 3;
|
|
3669
|
+
}
|
|
3670
|
+
return 4;
|
|
3671
|
+
}
|
|
3672
|
+
getFetchAgentsSortValue(entry, key) {
|
|
3673
|
+
switch (key) {
|
|
3674
|
+
case "status_priority":
|
|
3675
|
+
return this.getStatusPriority(entry.agent);
|
|
3676
|
+
case "created_at":
|
|
3677
|
+
return Date.parse(entry.agent.createdAt);
|
|
3678
|
+
case "updated_at":
|
|
3679
|
+
return Date.parse(entry.agent.updatedAt);
|
|
3680
|
+
case "title":
|
|
3681
|
+
return entry.agent.title?.toLocaleLowerCase() ?? "";
|
|
3682
|
+
}
|
|
3683
|
+
}
|
|
3684
|
+
compareSortValues(left, right) {
|
|
3685
|
+
if (left === right) {
|
|
3686
|
+
return 0;
|
|
3687
|
+
}
|
|
3688
|
+
if (left === null) {
|
|
3689
|
+
return -1;
|
|
3690
|
+
}
|
|
3691
|
+
if (right === null) {
|
|
3692
|
+
return 1;
|
|
3693
|
+
}
|
|
3694
|
+
if (typeof left === "number" && typeof right === "number") {
|
|
3695
|
+
return left < right ? -1 : 1;
|
|
3696
|
+
}
|
|
3697
|
+
return String(left).localeCompare(String(right));
|
|
3698
|
+
}
|
|
3699
|
+
compareFetchAgentsEntries(left, right, sort) {
|
|
3700
|
+
for (const spec of sort) {
|
|
3701
|
+
const leftValue = this.getFetchAgentsSortValue(left, spec.key);
|
|
3702
|
+
const rightValue = this.getFetchAgentsSortValue(right, spec.key);
|
|
3703
|
+
const base = this.compareSortValues(leftValue, rightValue);
|
|
3704
|
+
if (base === 0) {
|
|
3705
|
+
continue;
|
|
3706
|
+
}
|
|
3707
|
+
return spec.direction === "asc" ? base : -base;
|
|
3708
|
+
}
|
|
3709
|
+
return left.agent.id.localeCompare(right.agent.id);
|
|
3710
|
+
}
|
|
3711
|
+
encodeFetchAgentsCursor(entry, sort) {
|
|
3712
|
+
const values = {};
|
|
3713
|
+
for (const spec of sort) {
|
|
3714
|
+
values[spec.key] = this.getFetchAgentsSortValue(entry, spec.key);
|
|
3715
|
+
}
|
|
3716
|
+
return Buffer.from(JSON.stringify({
|
|
3717
|
+
sort,
|
|
3718
|
+
values,
|
|
3719
|
+
id: entry.agent.id,
|
|
3720
|
+
}), "utf8").toString("base64url");
|
|
3721
|
+
}
|
|
3722
|
+
decodeFetchAgentsCursor(cursor, sort) {
|
|
3723
|
+
let parsed;
|
|
3676
3724
|
try {
|
|
3677
|
-
|
|
3678
|
-
this.emit({
|
|
3679
|
-
type: "fetch_agents_response",
|
|
3680
|
-
payload: { requestId, agents },
|
|
3681
|
-
});
|
|
3725
|
+
parsed = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
3682
3726
|
}
|
|
3683
|
-
catch
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
const
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3727
|
+
catch {
|
|
3728
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
|
|
3729
|
+
}
|
|
3730
|
+
if (!parsed || typeof parsed !== "object") {
|
|
3731
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
|
|
3732
|
+
}
|
|
3733
|
+
const payload = parsed;
|
|
3734
|
+
if (!Array.isArray(payload.sort) || typeof payload.id !== "string") {
|
|
3735
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
|
|
3736
|
+
}
|
|
3737
|
+
if (!payload.values || typeof payload.values !== "object") {
|
|
3738
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
|
|
3739
|
+
}
|
|
3740
|
+
const cursorSort = [];
|
|
3741
|
+
for (const item of payload.sort) {
|
|
3742
|
+
if (!item ||
|
|
3743
|
+
typeof item !== "object" ||
|
|
3744
|
+
typeof item.key !== "string" ||
|
|
3745
|
+
typeof item.direction !== "string") {
|
|
3746
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
|
|
3747
|
+
}
|
|
3748
|
+
const key = item.key;
|
|
3749
|
+
const direction = item.direction;
|
|
3750
|
+
if ((key !== "status_priority" &&
|
|
3751
|
+
key !== "created_at" &&
|
|
3752
|
+
key !== "updated_at" &&
|
|
3753
|
+
key !== "title") ||
|
|
3754
|
+
(direction !== "asc" && direction !== "desc")) {
|
|
3755
|
+
throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
|
|
3712
3756
|
}
|
|
3713
|
-
|
|
3757
|
+
cursorSort.push({ key, direction });
|
|
3758
|
+
}
|
|
3759
|
+
if (cursorSort.length !== sort.length ||
|
|
3760
|
+
cursorSort.some((entry, index) => entry.key !== sort[index]?.key ||
|
|
3761
|
+
entry.direction !== sort[index]?.direction)) {
|
|
3762
|
+
throw new SessionRequestError("invalid_cursor", "fetch_agents cursor does not match current sort");
|
|
3763
|
+
}
|
|
3764
|
+
return {
|
|
3765
|
+
sort: cursorSort,
|
|
3766
|
+
values: payload.values,
|
|
3767
|
+
id: payload.id,
|
|
3768
|
+
};
|
|
3769
|
+
}
|
|
3770
|
+
compareEntryWithCursor(entry, cursor, sort) {
|
|
3771
|
+
for (const spec of sort) {
|
|
3772
|
+
const leftValue = this.getFetchAgentsSortValue(entry, spec.key);
|
|
3773
|
+
const rightValue = cursor.values[spec.key] !== undefined ? cursor.values[spec.key] ?? null : null;
|
|
3774
|
+
const base = this.compareSortValues(leftValue, rightValue);
|
|
3775
|
+
if (base === 0) {
|
|
3714
3776
|
continue;
|
|
3715
3777
|
}
|
|
3716
|
-
|
|
3778
|
+
return spec.direction === "asc" ? base : -base;
|
|
3717
3779
|
}
|
|
3718
|
-
return
|
|
3780
|
+
return entry.agent.id.localeCompare(cursor.id);
|
|
3719
3781
|
}
|
|
3720
|
-
async
|
|
3782
|
+
async listFetchAgentsEntries(request) {
|
|
3783
|
+
const filter = request.filter;
|
|
3784
|
+
const sort = this.normalizeFetchAgentsSort(request.sort);
|
|
3785
|
+
const includeArchived = filter?.includeArchived ?? false;
|
|
3786
|
+
let agents = await this.listAgentPayloads({
|
|
3787
|
+
labels: filter?.labels,
|
|
3788
|
+
});
|
|
3789
|
+
if (!includeArchived) {
|
|
3790
|
+
agents = agents.filter((agent) => !agent.archivedAt);
|
|
3791
|
+
}
|
|
3792
|
+
if (filter?.statuses && filter.statuses.length > 0) {
|
|
3793
|
+
const statuses = new Set(filter.statuses);
|
|
3794
|
+
agents = agents.filter((agent) => statuses.has(agent.status));
|
|
3795
|
+
}
|
|
3796
|
+
if (typeof filter?.requiresAttention === "boolean") {
|
|
3797
|
+
agents = agents.filter((agent) => (agent.requiresAttention ?? false) === filter.requiresAttention);
|
|
3798
|
+
}
|
|
3799
|
+
const placementByCwd = new Map();
|
|
3800
|
+
const getPlacement = (cwd) => {
|
|
3801
|
+
const existing = placementByCwd.get(cwd);
|
|
3802
|
+
if (existing) {
|
|
3803
|
+
return existing;
|
|
3804
|
+
}
|
|
3805
|
+
const placementPromise = this.buildProjectPlacement(cwd);
|
|
3806
|
+
placementByCwd.set(cwd, placementPromise);
|
|
3807
|
+
return placementPromise;
|
|
3808
|
+
};
|
|
3809
|
+
let entries = await Promise.all(agents.map(async (agent) => ({
|
|
3810
|
+
agent,
|
|
3811
|
+
project: await getPlacement(agent.cwd),
|
|
3812
|
+
})));
|
|
3813
|
+
if (filter?.projectKeys && filter.projectKeys.length > 0) {
|
|
3814
|
+
const projectKeys = new Set(filter.projectKeys.filter((item) => item.trim().length > 0));
|
|
3815
|
+
entries = entries.filter((entry) => projectKeys.has(entry.project.projectKey));
|
|
3816
|
+
}
|
|
3817
|
+
entries.sort((left, right) => this.compareFetchAgentsEntries(left, right, sort));
|
|
3818
|
+
const cursorToken = request.page?.cursor;
|
|
3819
|
+
if (cursorToken) {
|
|
3820
|
+
const cursor = this.decodeFetchAgentsCursor(cursorToken, sort);
|
|
3821
|
+
entries = entries.filter((entry) => this.compareEntryWithCursor(entry, cursor, sort) > 0);
|
|
3822
|
+
}
|
|
3823
|
+
const limit = request.page?.limit ?? entries.length;
|
|
3824
|
+
const pagedEntries = entries.slice(0, limit);
|
|
3825
|
+
const hasMore = entries.length > limit;
|
|
3826
|
+
const nextCursor = hasMore && pagedEntries.length > 0
|
|
3827
|
+
? this.encodeFetchAgentsCursor(pagedEntries[pagedEntries.length - 1], sort)
|
|
3828
|
+
: null;
|
|
3829
|
+
return {
|
|
3830
|
+
entries: pagedEntries,
|
|
3831
|
+
pageInfo: {
|
|
3832
|
+
nextCursor,
|
|
3833
|
+
prevCursor: request.page?.cursor ?? null,
|
|
3834
|
+
hasMore,
|
|
3835
|
+
},
|
|
3836
|
+
};
|
|
3837
|
+
}
|
|
3838
|
+
async handleFetchAgents(request) {
|
|
3721
3839
|
try {
|
|
3722
|
-
const
|
|
3840
|
+
const payload = await this.listFetchAgentsEntries(request);
|
|
3723
3841
|
this.emit({
|
|
3724
|
-
type: "
|
|
3725
|
-
payload: {
|
|
3842
|
+
type: "fetch_agents_response",
|
|
3843
|
+
payload: {
|
|
3844
|
+
requestId: request.requestId,
|
|
3845
|
+
...payload,
|
|
3846
|
+
},
|
|
3726
3847
|
});
|
|
3727
3848
|
}
|
|
3728
3849
|
catch (error) {
|
|
3729
|
-
|
|
3850
|
+
const code = error instanceof SessionRequestError ? error.code : "fetch_agents_failed";
|
|
3851
|
+
const message = error instanceof Error ? error.message : "Failed to fetch agents";
|
|
3852
|
+
this.sessionLogger.error({ err: error }, "Failed to handle fetch_agents_request");
|
|
3730
3853
|
this.emit({
|
|
3731
|
-
type: "
|
|
3732
|
-
payload: {
|
|
3854
|
+
type: "rpc_error",
|
|
3855
|
+
payload: {
|
|
3856
|
+
requestId: request.requestId,
|
|
3857
|
+
requestType: request.type,
|
|
3858
|
+
error: message,
|
|
3859
|
+
code,
|
|
3860
|
+
},
|
|
3733
3861
|
});
|
|
3734
3862
|
}
|
|
3735
3863
|
}
|
|
@@ -3845,7 +3973,10 @@ export class Session {
|
|
|
3845
3973
|
await this.ensureAgentLoaded(agentId);
|
|
3846
3974
|
await this.interruptAgentIfRunning(agentId);
|
|
3847
3975
|
try {
|
|
3848
|
-
this.agentManager.recordUserMessage(agentId, msg.text, {
|
|
3976
|
+
this.agentManager.recordUserMessage(agentId, msg.text, {
|
|
3977
|
+
messageId: msg.messageId,
|
|
3978
|
+
emitState: false,
|
|
3979
|
+
});
|
|
3849
3980
|
}
|
|
3850
3981
|
catch (error) {
|
|
3851
3982
|
this.sessionLogger.error({ err: error, agentId }, "Failed to record user message for send_agent_message_request");
|
|
@@ -4502,10 +4633,19 @@ export class Session {
|
|
|
4502
4633
|
await this.disableVoiceModeForActiveAgent(true);
|
|
4503
4634
|
this.isVoiceMode = false;
|
|
4504
4635
|
// Unsubscribe from all terminals
|
|
4636
|
+
if (this.unsubscribeTerminalsChanged) {
|
|
4637
|
+
this.unsubscribeTerminalsChanged();
|
|
4638
|
+
this.unsubscribeTerminalsChanged = null;
|
|
4639
|
+
}
|
|
4640
|
+
this.subscribedTerminalDirectories.clear();
|
|
4505
4641
|
for (const unsubscribe of this.terminalSubscriptions.values()) {
|
|
4506
4642
|
unsubscribe();
|
|
4507
4643
|
}
|
|
4508
4644
|
this.terminalSubscriptions.clear();
|
|
4645
|
+
for (const unsubscribeExit of this.terminalExitSubscriptions.values()) {
|
|
4646
|
+
unsubscribeExit();
|
|
4647
|
+
}
|
|
4648
|
+
this.terminalExitSubscriptions.clear();
|
|
4509
4649
|
this.detachAllTerminalStreams({ emitExit: false });
|
|
4510
4650
|
for (const target of this.checkoutDiffTargets.values()) {
|
|
4511
4651
|
this.closeCheckoutDiffWatchTarget(target);
|
|
@@ -4516,6 +4656,96 @@ export class Session {
|
|
|
4516
4656
|
// ============================================================================
|
|
4517
4657
|
// Terminal Handlers
|
|
4518
4658
|
// ============================================================================
|
|
4659
|
+
ensureTerminalExitSubscription(terminal) {
|
|
4660
|
+
if (this.terminalExitSubscriptions.has(terminal.id)) {
|
|
4661
|
+
return;
|
|
4662
|
+
}
|
|
4663
|
+
const unsubscribeExit = terminal.onExit(() => {
|
|
4664
|
+
this.handleTerminalExited(terminal.id);
|
|
4665
|
+
});
|
|
4666
|
+
this.terminalExitSubscriptions.set(terminal.id, unsubscribeExit);
|
|
4667
|
+
}
|
|
4668
|
+
handleTerminalExited(terminalId) {
|
|
4669
|
+
const unsubscribeExit = this.terminalExitSubscriptions.get(terminalId);
|
|
4670
|
+
if (unsubscribeExit) {
|
|
4671
|
+
unsubscribeExit();
|
|
4672
|
+
this.terminalExitSubscriptions.delete(terminalId);
|
|
4673
|
+
}
|
|
4674
|
+
const unsubscribe = this.terminalSubscriptions.get(terminalId);
|
|
4675
|
+
if (unsubscribe) {
|
|
4676
|
+
try {
|
|
4677
|
+
unsubscribe();
|
|
4678
|
+
}
|
|
4679
|
+
catch (error) {
|
|
4680
|
+
this.sessionLogger.warn({ err: error, terminalId }, "Failed to unsubscribe terminal after process exit");
|
|
4681
|
+
}
|
|
4682
|
+
this.terminalSubscriptions.delete(terminalId);
|
|
4683
|
+
}
|
|
4684
|
+
const streamId = this.terminalStreamByTerminalId.get(terminalId);
|
|
4685
|
+
if (typeof streamId === "number") {
|
|
4686
|
+
this.detachTerminalStream(streamId, { emitExit: true });
|
|
4687
|
+
}
|
|
4688
|
+
}
|
|
4689
|
+
emitTerminalsChangedSnapshot(input) {
|
|
4690
|
+
this.emit({
|
|
4691
|
+
type: "terminals_changed",
|
|
4692
|
+
payload: {
|
|
4693
|
+
cwd: input.cwd,
|
|
4694
|
+
terminals: input.terminals,
|
|
4695
|
+
},
|
|
4696
|
+
});
|
|
4697
|
+
}
|
|
4698
|
+
handleTerminalsChanged(event) {
|
|
4699
|
+
if (!this.subscribedTerminalDirectories.has(event.cwd)) {
|
|
4700
|
+
return;
|
|
4701
|
+
}
|
|
4702
|
+
this.emitTerminalsChangedSnapshot({
|
|
4703
|
+
cwd: event.cwd,
|
|
4704
|
+
terminals: event.terminals.map((terminal) => ({
|
|
4705
|
+
id: terminal.id,
|
|
4706
|
+
name: terminal.name,
|
|
4707
|
+
})),
|
|
4708
|
+
});
|
|
4709
|
+
}
|
|
4710
|
+
handleSubscribeTerminalsRequest(msg) {
|
|
4711
|
+
this.subscribedTerminalDirectories.add(msg.cwd);
|
|
4712
|
+
void this.emitInitialTerminalsChangedSnapshot(msg.cwd);
|
|
4713
|
+
}
|
|
4714
|
+
handleUnsubscribeTerminalsRequest(msg) {
|
|
4715
|
+
this.subscribedTerminalDirectories.delete(msg.cwd);
|
|
4716
|
+
}
|
|
4717
|
+
async emitInitialTerminalsChangedSnapshot(cwd) {
|
|
4718
|
+
if (!this.terminalManager || !this.subscribedTerminalDirectories.has(cwd)) {
|
|
4719
|
+
return;
|
|
4720
|
+
}
|
|
4721
|
+
const hadDirectoryBeforeSubscribe = this.terminalManager
|
|
4722
|
+
.listDirectories()
|
|
4723
|
+
.includes(cwd);
|
|
4724
|
+
try {
|
|
4725
|
+
const terminals = await this.terminalManager.getTerminals(cwd);
|
|
4726
|
+
for (const terminal of terminals) {
|
|
4727
|
+
this.ensureTerminalExitSubscription(terminal);
|
|
4728
|
+
}
|
|
4729
|
+
// New directories auto-create Terminal 1, which already emits through
|
|
4730
|
+
// terminal-manager change listeners.
|
|
4731
|
+
if (!hadDirectoryBeforeSubscribe) {
|
|
4732
|
+
return;
|
|
4733
|
+
}
|
|
4734
|
+
if (!this.subscribedTerminalDirectories.has(cwd)) {
|
|
4735
|
+
return;
|
|
4736
|
+
}
|
|
4737
|
+
this.emitTerminalsChangedSnapshot({
|
|
4738
|
+
cwd,
|
|
4739
|
+
terminals: terminals.map((terminal) => ({
|
|
4740
|
+
id: terminal.id,
|
|
4741
|
+
name: terminal.name,
|
|
4742
|
+
})),
|
|
4743
|
+
});
|
|
4744
|
+
}
|
|
4745
|
+
catch (error) {
|
|
4746
|
+
this.sessionLogger.warn({ err: error, cwd }, "Failed to emit initial terminal snapshot");
|
|
4747
|
+
}
|
|
4748
|
+
}
|
|
4519
4749
|
async handleListTerminalsRequest(msg) {
|
|
4520
4750
|
if (!this.terminalManager) {
|
|
4521
4751
|
this.emit({
|
|
@@ -4530,6 +4760,9 @@ export class Session {
|
|
|
4530
4760
|
}
|
|
4531
4761
|
try {
|
|
4532
4762
|
const terminals = await this.terminalManager.getTerminals(msg.cwd);
|
|
4763
|
+
for (const terminal of terminals) {
|
|
4764
|
+
this.ensureTerminalExitSubscription(terminal);
|
|
4765
|
+
}
|
|
4533
4766
|
this.emit({
|
|
4534
4767
|
type: "list_terminals_response",
|
|
4535
4768
|
payload: {
|
|
@@ -4568,6 +4801,7 @@ export class Session {
|
|
|
4568
4801
|
cwd: msg.cwd,
|
|
4569
4802
|
name: msg.name,
|
|
4570
4803
|
});
|
|
4804
|
+
this.ensureTerminalExitSubscription(session);
|
|
4571
4805
|
this.emit({
|
|
4572
4806
|
type: "create_terminal_response",
|
|
4573
4807
|
payload: {
|
|
@@ -4615,6 +4849,7 @@ export class Session {
|
|
|
4615
4849
|
});
|
|
4616
4850
|
return;
|
|
4617
4851
|
}
|
|
4852
|
+
this.ensureTerminalExitSubscription(session);
|
|
4618
4853
|
// Unsubscribe from previous subscription if any
|
|
4619
4854
|
const existing = this.terminalSubscriptions.get(msg.terminalId);
|
|
4620
4855
|
if (existing) {
|
|
@@ -4660,6 +4895,7 @@ export class Session {
|
|
|
4660
4895
|
this.sessionLogger.warn({ terminalId: msg.terminalId }, "Terminal not found for input");
|
|
4661
4896
|
return;
|
|
4662
4897
|
}
|
|
4898
|
+
this.ensureTerminalExitSubscription(session);
|
|
4663
4899
|
session.send(msg.message);
|
|
4664
4900
|
}
|
|
4665
4901
|
async handleKillTerminalRequest(msg) {
|