@getpaseo/server 0.1.17 → 0.1.20
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.d.ts +5 -1
- package/dist/server/client/daemon-client.d.ts.map +1 -1
- package/dist/server/client/daemon-client.js +22 -0
- package/dist/server/client/daemon-client.js.map +1 -1
- package/dist/server/server/agent/agent-management-mcp.d.ts.map +1 -1
- package/dist/server/server/agent/agent-management-mcp.js +11 -28
- package/dist/server/server/agent/agent-management-mcp.js.map +1 -1
- package/dist/server/server/agent/agent-manager.d.ts +2 -0
- package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
- package/dist/server/server/agent/agent-manager.js +50 -6
- package/dist/server/server/agent/agent-manager.js.map +1 -1
- package/dist/server/server/agent/mcp-server.d.ts.map +1 -1
- package/dist/server/server/agent/mcp-server.js +11 -34
- package/dist/server/server/agent/mcp-server.js.map +1 -1
- package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/claude-agent.js +169 -40
- package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
- package/dist/server/server/bootstrap.d.ts +13 -0
- package/dist/server/server/bootstrap.d.ts.map +1 -1
- package/dist/server/server/bootstrap.js +46 -32
- package/dist/server/server/bootstrap.js.map +1 -1
- package/dist/server/server/persisted-config.d.ts +22 -22
- package/dist/server/server/session.d.ts +10 -6
- package/dist/server/server/session.d.ts.map +1 -1
- package/dist/server/server/session.js +191 -223
- package/dist/server/server/session.js.map +1 -1
- package/dist/server/server/speech/speech-types.d.ts +2 -2
- package/dist/server/server/websocket-server.d.ts +4 -1
- package/dist/server/server/websocket-server.d.ts.map +1 -1
- package/dist/server/server/websocket-server.js +27 -1
- package/dist/server/server/websocket-server.js.map +1 -1
- package/dist/server/server/workspace-registry-bootstrap.d.ts +11 -0
- package/dist/server/server/workspace-registry-bootstrap.d.ts.map +1 -0
- package/dist/server/server/workspace-registry-bootstrap.js +98 -0
- package/dist/server/server/workspace-registry-bootstrap.js.map +1 -0
- package/dist/server/server/workspace-registry-model.d.ts +26 -0
- package/dist/server/server/workspace-registry-model.d.ts.map +1 -0
- package/dist/server/server/workspace-registry-model.js +150 -0
- package/dist/server/server/workspace-registry-model.js.map +1 -0
- package/dist/server/server/workspace-registry.d.ts +128 -0
- package/dist/server/server/workspace-registry.d.ts.map +1 -0
- package/dist/server/server/workspace-registry.js +141 -0
- package/dist/server/server/workspace-registry.js.map +1 -0
- package/dist/server/shared/messages.d.ts +1510 -0
- package/dist/server/shared/messages.d.ts.map +1 -1
- package/dist/server/shared/messages.js +39 -0
- package/dist/server/shared/messages.js.map +1 -1
- package/dist/server/utils/checkout-git.d.ts +5 -0
- package/dist/server/utils/checkout-git.d.ts.map +1 -1
- package/dist/server/utils/checkout-git.js +64 -4
- package/dist/server/utils/checkout-git.js.map +1 -1
- package/dist/server/utils/project-icon.d.ts +1 -1
- package/dist/server/utils/project-icon.d.ts.map +1 -1
- package/dist/server/utils/project-icon.js +9 -2
- package/dist/server/utils/project-icon.js.map +1 -1
- package/dist/src/server/agent/agent-manager.js +50 -6
- package/dist/src/server/agent/agent-manager.js.map +1 -1
- package/dist/src/server/agent/mcp-server.js +11 -34
- package/dist/src/server/agent/mcp-server.js.map +1 -1
- package/dist/src/server/agent/providers/claude-agent.js +169 -40
- package/dist/src/server/agent/providers/claude-agent.js.map +1 -1
- package/dist/src/server/bootstrap.js +46 -32
- package/dist/src/server/bootstrap.js.map +1 -1
- package/dist/src/server/session.js +191 -223
- package/dist/src/server/session.js.map +1 -1
- package/dist/src/server/websocket-server.js +27 -1
- package/dist/src/server/websocket-server.js.map +1 -1
- package/dist/src/server/workspace-registry-bootstrap.js +98 -0
- package/dist/src/server/workspace-registry-bootstrap.js.map +1 -0
- package/dist/src/server/workspace-registry-model.js +150 -0
- package/dist/src/server/workspace-registry-model.js.map +1 -0
- package/dist/src/server/workspace-registry.js +141 -0
- package/dist/src/server/workspace-registry.js.map +1 -0
- package/dist/src/shared/messages.js +39 -0
- package/dist/src/shared/messages.js.map +1 -1
- package/dist/src/utils/checkout-git.js +64 -4
- package/dist/src/utils/checkout-git.js.map +1 -1
- package/dist/src/utils/project-icon.js +9 -2
- package/dist/src/utils/project-icon.js.map +1 -1
- package/package.json +4 -3
|
@@ -23,12 +23,14 @@ import { appendTimelineItemIfAgentKnown, emitLiveTimelineItemIfAgentKnown, } fro
|
|
|
23
23
|
import { projectTimelineRows, selectTimelineWindowByProjectedLimit, } from './agent/timeline-projection.js';
|
|
24
24
|
import { DEFAULT_STRUCTURED_GENERATION_PROVIDERS, StructuredAgentFallbackError, StructuredAgentResponseError, generateStructuredAgentResponseWithFallback, } from './agent/agent-response-loop.js';
|
|
25
25
|
import { isValidAgentProvider, AGENT_PROVIDER_IDS } from './agent/provider-manifest.js';
|
|
26
|
+
import { buildProjectPlacementForCwd, deriveProjectKind, deriveProjectRootPath, deriveWorkspaceDisplayName, deriveWorkspaceKind, normalizeWorkspaceId as normalizePersistedWorkspaceId, } from './workspace-registry-model.js';
|
|
27
|
+
import { createPersistedProjectRecord, createPersistedWorkspaceRecord, } from './workspace-registry.js';
|
|
26
28
|
import { buildVoiceAgentMcpServerConfig, buildVoiceModeSystemPrompt, stripVoiceModeSystemPrompt, } from './voice-config.js';
|
|
27
29
|
import { isVoicePermissionAllowed } from './voice-permission-policy.js';
|
|
28
30
|
import { listDirectoryEntries, readExplorerFile, getDownloadableFileInfo, } from './file-explorer/service.js';
|
|
29
31
|
import { slugify, validateBranchSlug, listPaseoWorktrees, deletePaseoWorktree, isPaseoOwnedWorktreeCwd, resolvePaseoWorktreeRootForCwd, } from '../utils/worktree.js';
|
|
30
32
|
import { createAgentWorktree, runAsyncWorktreeBootstrap } from './worktree-bootstrap.js';
|
|
31
|
-
import { getCheckoutDiff,
|
|
33
|
+
import { getCheckoutDiff, getCheckoutShortstat, getCheckoutStatus, listBranchSuggestions, NotGitRepoError, MergeConflictError, MergeFromBaseConflictError, commitChanges, mergeToBase, mergeFromBase, pushCurrentBranch, createPullRequest, getPullRequestStatus, } from '../utils/checkout-git.js';
|
|
32
34
|
import { getProjectIcon } from '../utils/project-icon.js';
|
|
33
35
|
import { expandTilde } from '../utils/path.js';
|
|
34
36
|
import { searchHomeDirectories, searchWorkspaceEntries } from '../utils/directory-suggestions.js';
|
|
@@ -73,66 +75,6 @@ export function resolveCreateAgentTitles(options) {
|
|
|
73
75
|
provisionalTitle,
|
|
74
76
|
};
|
|
75
77
|
}
|
|
76
|
-
function deriveRemoteProjectKey(remoteUrl) {
|
|
77
|
-
if (!remoteUrl) {
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
const trimmed = remoteUrl.trim();
|
|
81
|
-
if (!trimmed) {
|
|
82
|
-
return null;
|
|
83
|
-
}
|
|
84
|
-
let host = null;
|
|
85
|
-
let path = null;
|
|
86
|
-
const scpLike = trimmed.match(/^[^@]+@([^:]+):(.+)$/);
|
|
87
|
-
if (scpLike) {
|
|
88
|
-
host = scpLike[1] ?? null;
|
|
89
|
-
path = scpLike[2] ?? null;
|
|
90
|
-
}
|
|
91
|
-
else if (trimmed.includes('://')) {
|
|
92
|
-
try {
|
|
93
|
-
const parsed = new URL(trimmed);
|
|
94
|
-
host = parsed.hostname || null;
|
|
95
|
-
path = parsed.pathname ? parsed.pathname.replace(/^\//, '') : null;
|
|
96
|
-
}
|
|
97
|
-
catch {
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
if (!host || !path) {
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
let cleanedPath = path.trim().replace(/^\/+/, '').replace(/\/+$/, '');
|
|
105
|
-
if (cleanedPath.endsWith('.git')) {
|
|
106
|
-
cleanedPath = cleanedPath.slice(0, -4);
|
|
107
|
-
}
|
|
108
|
-
if (!cleanedPath.includes('/')) {
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
111
|
-
const cleanedHost = host.toLowerCase();
|
|
112
|
-
if (cleanedHost === 'github.com') {
|
|
113
|
-
return `remote:github.com/${cleanedPath}`;
|
|
114
|
-
}
|
|
115
|
-
return `remote:${cleanedHost}/${cleanedPath}`;
|
|
116
|
-
}
|
|
117
|
-
function deriveProjectGroupingKey(options) {
|
|
118
|
-
const remoteKey = deriveRemoteProjectKey(options.remoteUrl);
|
|
119
|
-
if (remoteKey) {
|
|
120
|
-
return remoteKey;
|
|
121
|
-
}
|
|
122
|
-
const mainRepoRoot = options.mainRepoRoot?.trim();
|
|
123
|
-
if (options.isPaseoOwnedWorktree && mainRepoRoot) {
|
|
124
|
-
return mainRepoRoot;
|
|
125
|
-
}
|
|
126
|
-
return options.cwd;
|
|
127
|
-
}
|
|
128
|
-
function deriveProjectGroupingName(projectKey) {
|
|
129
|
-
const githubRemotePrefix = 'remote:github.com/';
|
|
130
|
-
if (projectKey.startsWith(githubRemotePrefix)) {
|
|
131
|
-
return projectKey.slice(githubRemotePrefix.length) || projectKey;
|
|
132
|
-
}
|
|
133
|
-
const segments = projectKey.split(/[\\/]/).filter(Boolean);
|
|
134
|
-
return segments[segments.length - 1] || projectKey;
|
|
135
|
-
}
|
|
136
78
|
class SessionRequestError extends Error {
|
|
137
79
|
constructor(code, message) {
|
|
138
80
|
super(message);
|
|
@@ -260,7 +202,7 @@ export class Session {
|
|
|
260
202
|
attention: 3,
|
|
261
203
|
done: 4,
|
|
262
204
|
};
|
|
263
|
-
const { clientId, onMessage, onBinaryMessage, onLifecycleIntent, logger, downloadTokenStore, pushTokenStore, paseoHome, agentManager, agentStorage, createAgentMcpTransport, stt, tts, terminalManager, voice, voiceBridge, dictation, agentProviderRuntimeSettings, } = options;
|
|
205
|
+
const { clientId, onMessage, onBinaryMessage, onLifecycleIntent, logger, downloadTokenStore, pushTokenStore, paseoHome, agentManager, agentStorage, projectRegistry, workspaceRegistry, createAgentMcpTransport, stt, tts, terminalManager, voice, voiceBridge, dictation, agentProviderRuntimeSettings, } = options;
|
|
264
206
|
this.clientId = clientId;
|
|
265
207
|
this.sessionId = uuidv4();
|
|
266
208
|
this.onMessage = onMessage;
|
|
@@ -271,6 +213,8 @@ export class Session {
|
|
|
271
213
|
this.paseoHome = paseoHome;
|
|
272
214
|
this.agentManager = agentManager;
|
|
273
215
|
this.agentStorage = agentStorage;
|
|
216
|
+
this.projectRegistry = projectRegistry;
|
|
217
|
+
this.workspaceRegistry = workspaceRegistry;
|
|
274
218
|
this.createAgentMcpTransport = createAgentMcpTransport;
|
|
275
219
|
this.terminalManager = terminalManager;
|
|
276
220
|
if (this.terminalManager) {
|
|
@@ -421,8 +365,12 @@ export class Session {
|
|
|
421
365
|
}, 'startAgentStream: requested');
|
|
422
366
|
let iterator;
|
|
423
367
|
try {
|
|
424
|
-
|
|
425
|
-
|
|
368
|
+
const snapshot = this.agentManager.getAgent(agentId);
|
|
369
|
+
const shouldReplace = Boolean(snapshot && (snapshot.lifecycle === 'running' || snapshot.pendingRun));
|
|
370
|
+
iterator = shouldReplace
|
|
371
|
+
? this.agentManager.replaceAgentRun(agentId, prompt, runOptions)
|
|
372
|
+
: this.agentManager.streamAgent(agentId, prompt, runOptions);
|
|
373
|
+
this.sessionLogger.trace({ agentId, shouldReplace }, 'startAgentStream: agent iterator returned');
|
|
426
374
|
}
|
|
427
375
|
catch (error) {
|
|
428
376
|
this.handleAgentRunError(agentId, error, 'Failed to start agent run');
|
|
@@ -745,57 +693,15 @@ export class Session {
|
|
|
745
693
|
});
|
|
746
694
|
}
|
|
747
695
|
}
|
|
748
|
-
buildFallbackProjectCheckout(cwd) {
|
|
749
|
-
return {
|
|
750
|
-
cwd,
|
|
751
|
-
isGit: false,
|
|
752
|
-
currentBranch: null,
|
|
753
|
-
remoteUrl: null,
|
|
754
|
-
isPaseoOwnedWorktree: false,
|
|
755
|
-
mainRepoRoot: null,
|
|
756
|
-
};
|
|
757
|
-
}
|
|
758
|
-
toProjectCheckoutLite(cwd, status) {
|
|
759
|
-
if (!status.isGit) {
|
|
760
|
-
return this.buildFallbackProjectCheckout(cwd);
|
|
761
|
-
}
|
|
762
|
-
if (status.isPaseoOwnedWorktree) {
|
|
763
|
-
return {
|
|
764
|
-
cwd,
|
|
765
|
-
isGit: true,
|
|
766
|
-
currentBranch: status.currentBranch,
|
|
767
|
-
remoteUrl: status.remoteUrl,
|
|
768
|
-
isPaseoOwnedWorktree: true,
|
|
769
|
-
mainRepoRoot: status.mainRepoRoot,
|
|
770
|
-
};
|
|
771
|
-
}
|
|
772
|
-
return {
|
|
773
|
-
cwd,
|
|
774
|
-
isGit: true,
|
|
775
|
-
currentBranch: status.currentBranch,
|
|
776
|
-
remoteUrl: status.remoteUrl,
|
|
777
|
-
isPaseoOwnedWorktree: false,
|
|
778
|
-
mainRepoRoot: null,
|
|
779
|
-
};
|
|
780
|
-
}
|
|
781
696
|
async buildProjectPlacement(cwd) {
|
|
782
|
-
|
|
783
|
-
.then((status) => this.toProjectCheckoutLite(cwd, status))
|
|
784
|
-
.catch(() => this.buildFallbackProjectCheckout(cwd));
|
|
785
|
-
const projectKey = deriveProjectGroupingKey({
|
|
697
|
+
return buildProjectPlacementForCwd({
|
|
786
698
|
cwd,
|
|
787
|
-
|
|
788
|
-
isPaseoOwnedWorktree: checkout.isPaseoOwnedWorktree,
|
|
789
|
-
mainRepoRoot: checkout.mainRepoRoot,
|
|
699
|
+
paseoHome: this.paseoHome,
|
|
790
700
|
});
|
|
791
|
-
return {
|
|
792
|
-
projectKey,
|
|
793
|
-
projectName: deriveProjectGroupingName(projectKey),
|
|
794
|
-
checkout,
|
|
795
|
-
};
|
|
796
701
|
}
|
|
797
702
|
async forwardAgentUpdate(agent) {
|
|
798
703
|
try {
|
|
704
|
+
await this.ensureWorkspaceRegistered(agent.cwd);
|
|
799
705
|
const subscription = this.agentUpdatesSubscription;
|
|
800
706
|
const payload = await this.buildAgentPayload(agent);
|
|
801
707
|
if (subscription) {
|
|
@@ -975,6 +881,12 @@ export class Session {
|
|
|
975
881
|
case 'paseo_worktree_archive_request':
|
|
976
882
|
await this.handlePaseoWorktreeArchiveRequest(msg);
|
|
977
883
|
break;
|
|
884
|
+
case 'open_project_request':
|
|
885
|
+
await this.handleOpenProjectRequest(msg);
|
|
886
|
+
break;
|
|
887
|
+
case 'archive_workspace_request':
|
|
888
|
+
await this.handleArchiveWorkspaceRequest(msg);
|
|
889
|
+
break;
|
|
978
890
|
case 'file_explorer_request':
|
|
979
891
|
await this.handleFileExplorerRequest(msg);
|
|
980
892
|
break;
|
|
@@ -1222,7 +1134,7 @@ export class Session {
|
|
|
1222
1134
|
}
|
|
1223
1135
|
async handleArchiveAgentRequest(agentId, requestId) {
|
|
1224
1136
|
this.sessionLogger.info({ agentId }, `Archiving agent ${agentId}`);
|
|
1225
|
-
const { archivedAt
|
|
1137
|
+
const { archivedAt } = await this.archiveAgentState(agentId);
|
|
1226
1138
|
this.emit({
|
|
1227
1139
|
type: 'agent_archived',
|
|
1228
1140
|
payload: {
|
|
@@ -1231,11 +1143,6 @@ export class Session {
|
|
|
1231
1143
|
requestId,
|
|
1232
1144
|
},
|
|
1233
1145
|
});
|
|
1234
|
-
await this.maybeArchiveWorktreeAfterLastAgentArchived({
|
|
1235
|
-
archivedAgentId: agentId,
|
|
1236
|
-
archivedAgentCwd: archivedRecord.cwd,
|
|
1237
|
-
requestId,
|
|
1238
|
-
});
|
|
1239
1146
|
}
|
|
1240
1147
|
async archiveAgentState(agentId) {
|
|
1241
1148
|
if (this.agentManager.getAgent(agentId)) {
|
|
@@ -1773,14 +1680,6 @@ export class Session {
|
|
|
1773
1680
|
this.handleAgentRunError(agentId, error, 'Failed to initialize agent before sending prompt');
|
|
1774
1681
|
return;
|
|
1775
1682
|
}
|
|
1776
|
-
try {
|
|
1777
|
-
await this.interruptAgentIfRunning(agentId);
|
|
1778
|
-
}
|
|
1779
|
-
catch (error) {
|
|
1780
|
-
this.handleAgentRunError(agentId, error, 'Failed to interrupt running agent before sending prompt');
|
|
1781
|
-
return;
|
|
1782
|
-
}
|
|
1783
|
-
const prompt = this.buildAgentPrompt(text, images);
|
|
1784
1683
|
try {
|
|
1785
1684
|
this.agentManager.recordUserMessage(agentId, text, {
|
|
1786
1685
|
messageId,
|
|
@@ -1790,6 +1689,7 @@ export class Session {
|
|
|
1790
1689
|
catch (error) {
|
|
1791
1690
|
this.sessionLogger.error({ err: error, agentId }, `Failed to record user message for agent ${agentId}`);
|
|
1792
1691
|
}
|
|
1692
|
+
const prompt = this.buildAgentPrompt(text, images);
|
|
1793
1693
|
this.startAgentStream(agentId, prompt, runOptions);
|
|
1794
1694
|
}
|
|
1795
1695
|
/**
|
|
@@ -1809,6 +1709,7 @@ export class Session {
|
|
|
1809
1709
|
...(provisionalTitle ? { title: provisionalTitle } : {}),
|
|
1810
1710
|
};
|
|
1811
1711
|
const { sessionConfig, worktreeConfig } = await this.buildAgentSessionConfig(resolvedConfig, git, worktreeName, labels);
|
|
1712
|
+
await this.ensureWorkspaceRegistered(sessionConfig.cwd);
|
|
1812
1713
|
const snapshot = await this.agentManager.createAgent(sessionConfig, undefined, { labels });
|
|
1813
1714
|
await this.forwardAgentUpdate(snapshot);
|
|
1814
1715
|
if (requestId) {
|
|
@@ -3469,59 +3370,6 @@ export class Session {
|
|
|
3469
3370
|
});
|
|
3470
3371
|
}
|
|
3471
3372
|
}
|
|
3472
|
-
async maybeArchiveWorktreeAfterLastAgentArchived(options) {
|
|
3473
|
-
try {
|
|
3474
|
-
const ownership = await isPaseoOwnedWorktreeCwd(options.archivedAgentCwd, {
|
|
3475
|
-
paseoHome: this.paseoHome,
|
|
3476
|
-
});
|
|
3477
|
-
if (!ownership.allowed) {
|
|
3478
|
-
return;
|
|
3479
|
-
}
|
|
3480
|
-
const resolvedWorktree = await resolvePaseoWorktreeRootForCwd(options.archivedAgentCwd, {
|
|
3481
|
-
paseoHome: this.paseoHome,
|
|
3482
|
-
});
|
|
3483
|
-
if (!resolvedWorktree) {
|
|
3484
|
-
return;
|
|
3485
|
-
}
|
|
3486
|
-
const records = await this.agentStorage.list();
|
|
3487
|
-
const recordsById = new Map(records.map((record) => [record.id, record]));
|
|
3488
|
-
const targetPath = resolvedWorktree.worktreePath;
|
|
3489
|
-
const hasRemainingNonArchivedRecord = records.some((record) => {
|
|
3490
|
-
if (record.id === options.archivedAgentId || record.archivedAt) {
|
|
3491
|
-
return false;
|
|
3492
|
-
}
|
|
3493
|
-
return this.isPathWithinRoot(targetPath, record.cwd);
|
|
3494
|
-
});
|
|
3495
|
-
if (hasRemainingNonArchivedRecord) {
|
|
3496
|
-
return;
|
|
3497
|
-
}
|
|
3498
|
-
const hasUnknownLiveAgent = this.agentManager.listAgents().some((agent) => {
|
|
3499
|
-
if (agent.id === options.archivedAgentId) {
|
|
3500
|
-
return false;
|
|
3501
|
-
}
|
|
3502
|
-
if (!this.isPathWithinRoot(targetPath, agent.cwd)) {
|
|
3503
|
-
return false;
|
|
3504
|
-
}
|
|
3505
|
-
return !recordsById.has(agent.id);
|
|
3506
|
-
});
|
|
3507
|
-
if (hasUnknownLiveAgent) {
|
|
3508
|
-
return;
|
|
3509
|
-
}
|
|
3510
|
-
const repoRoot = ownership.repoRoot;
|
|
3511
|
-
if (!repoRoot) {
|
|
3512
|
-
this.sessionLogger.warn({ agentId: options.archivedAgentId, worktreePath: targetPath }, 'Unable to resolve repo root for auto-archive after agent archive');
|
|
3513
|
-
return;
|
|
3514
|
-
}
|
|
3515
|
-
await this.archivePaseoWorktree({
|
|
3516
|
-
targetPath,
|
|
3517
|
-
repoRoot,
|
|
3518
|
-
requestId: options.requestId,
|
|
3519
|
-
});
|
|
3520
|
-
}
|
|
3521
|
-
catch (error) {
|
|
3522
|
-
this.sessionLogger.warn({ err: error, agentId: options.archivedAgentId, cwd: options.archivedAgentCwd }, 'Failed to auto-archive worktree after agent archive');
|
|
3523
|
-
}
|
|
3524
|
-
}
|
|
3525
3373
|
async archivePaseoWorktree(options) {
|
|
3526
3374
|
let targetPath = options.targetPath;
|
|
3527
3375
|
const resolvedWorktree = await resolvePaseoWorktreeRootForCwd(targetPath, {
|
|
@@ -3532,11 +3380,13 @@ export class Session {
|
|
|
3532
3380
|
}
|
|
3533
3381
|
const removedAgents = new Set();
|
|
3534
3382
|
const affectedWorkspaceCwds = new Set([targetPath]);
|
|
3383
|
+
const affectedWorkspaceIds = new Set([normalizePersistedWorkspaceId(targetPath)]);
|
|
3535
3384
|
const agents = this.agentManager.listAgents();
|
|
3536
3385
|
for (const agent of agents) {
|
|
3537
3386
|
if (this.isPathWithinRoot(targetPath, agent.cwd)) {
|
|
3538
3387
|
removedAgents.add(agent.id);
|
|
3539
3388
|
affectedWorkspaceCwds.add(agent.cwd);
|
|
3389
|
+
affectedWorkspaceIds.add(normalizePersistedWorkspaceId(agent.cwd));
|
|
3540
3390
|
try {
|
|
3541
3391
|
await this.agentManager.closeAgent(agent.id);
|
|
3542
3392
|
}
|
|
@@ -3556,6 +3406,7 @@ export class Session {
|
|
|
3556
3406
|
if (this.isPathWithinRoot(targetPath, record.cwd)) {
|
|
3557
3407
|
removedAgents.add(record.id);
|
|
3558
3408
|
affectedWorkspaceCwds.add(record.cwd);
|
|
3409
|
+
affectedWorkspaceIds.add(normalizePersistedWorkspaceId(record.cwd));
|
|
3559
3410
|
try {
|
|
3560
3411
|
await this.agentStorage.remove(record.id);
|
|
3561
3412
|
}
|
|
@@ -3570,6 +3421,9 @@ export class Session {
|
|
|
3570
3421
|
worktreePath: targetPath,
|
|
3571
3422
|
paseoHome: this.paseoHome,
|
|
3572
3423
|
});
|
|
3424
|
+
for (const workspaceId of affectedWorkspaceIds) {
|
|
3425
|
+
await this.archiveWorkspaceRecord(workspaceId);
|
|
3426
|
+
}
|
|
3573
3427
|
for (const agentId of removedAgents) {
|
|
3574
3428
|
this.emit({
|
|
3575
3429
|
type: 'agent_deleted',
|
|
@@ -4109,13 +3963,6 @@ export class Session {
|
|
|
4109
3963
|
},
|
|
4110
3964
|
};
|
|
4111
3965
|
}
|
|
4112
|
-
normalizeWorkspaceId(cwd) {
|
|
4113
|
-
const trimmed = cwd.trim();
|
|
4114
|
-
if (!trimmed) {
|
|
4115
|
-
return cwd;
|
|
4116
|
-
}
|
|
4117
|
-
return resolve(trimmed);
|
|
4118
|
-
}
|
|
4119
3966
|
deriveWorkspaceStateBucket(agent) {
|
|
4120
3967
|
const pendingPermissionCount = agent.pendingPermissions?.length ?? 0;
|
|
4121
3968
|
if (pendingPermissionCount > 0 || agent.attentionReason === 'permission') {
|
|
@@ -4132,18 +3979,6 @@ export class Session {
|
|
|
4132
3979
|
}
|
|
4133
3980
|
return 'done';
|
|
4134
3981
|
}
|
|
4135
|
-
deriveWorkspaceDirectoryName(cwd) {
|
|
4136
|
-
const normalized = cwd.replace(/\\/g, '/');
|
|
4137
|
-
const segments = normalized.split('/').filter(Boolean);
|
|
4138
|
-
return segments[segments.length - 1] ?? cwd;
|
|
4139
|
-
}
|
|
4140
|
-
deriveWorkspaceName(input) {
|
|
4141
|
-
const branch = input.checkout.currentBranch?.trim() ?? null;
|
|
4142
|
-
if (branch && branch.toUpperCase() !== 'HEAD') {
|
|
4143
|
-
return branch;
|
|
4144
|
-
}
|
|
4145
|
-
return this.deriveWorkspaceDirectoryName(input.cwd);
|
|
4146
|
-
}
|
|
4147
3982
|
accumulateLatestActivityAt(current, agent) {
|
|
4148
3983
|
const candidateRaw = agent.lastUserMessageAt ?? agent.updatedAt;
|
|
4149
3984
|
const candidateMs = Date.parse(candidateRaw);
|
|
@@ -4159,39 +3994,60 @@ export class Session {
|
|
|
4159
3994
|
}
|
|
4160
3995
|
return current;
|
|
4161
3996
|
}
|
|
3997
|
+
async describeWorkspaceRecord(workspace, projectRecord) {
|
|
3998
|
+
const resolvedProjectRecord = projectRecord ?? (await this.projectRegistry.get(workspace.projectId));
|
|
3999
|
+
let displayName = workspace.displayName;
|
|
4000
|
+
try {
|
|
4001
|
+
const placement = await this.buildProjectPlacement(workspace.cwd);
|
|
4002
|
+
displayName = deriveWorkspaceDisplayName({
|
|
4003
|
+
cwd: workspace.cwd,
|
|
4004
|
+
checkout: placement.checkout,
|
|
4005
|
+
});
|
|
4006
|
+
}
|
|
4007
|
+
catch {
|
|
4008
|
+
// Fall back to the persisted label if checkout metadata is unavailable.
|
|
4009
|
+
}
|
|
4010
|
+
let diffStat = null;
|
|
4011
|
+
try {
|
|
4012
|
+
diffStat = await getCheckoutShortstat(workspace.cwd);
|
|
4013
|
+
}
|
|
4014
|
+
catch {
|
|
4015
|
+
// Non-critical — leave null on failure.
|
|
4016
|
+
}
|
|
4017
|
+
return {
|
|
4018
|
+
id: workspace.workspaceId,
|
|
4019
|
+
projectId: workspace.projectId,
|
|
4020
|
+
projectDisplayName: resolvedProjectRecord?.displayName ?? workspace.projectId,
|
|
4021
|
+
projectRootPath: resolvedProjectRecord?.rootPath ?? workspace.cwd,
|
|
4022
|
+
projectKind: resolvedProjectRecord?.kind ?? 'non_git',
|
|
4023
|
+
workspaceKind: workspace.kind,
|
|
4024
|
+
name: displayName,
|
|
4025
|
+
status: 'done',
|
|
4026
|
+
activityAt: null,
|
|
4027
|
+
diffStat,
|
|
4028
|
+
};
|
|
4029
|
+
}
|
|
4162
4030
|
async listWorkspaceDescriptors() {
|
|
4163
|
-
const agents = await
|
|
4031
|
+
const [agents, persistedWorkspaces, persistedProjects] = await Promise.all([
|
|
4032
|
+
this.listAgentPayloads(),
|
|
4033
|
+
this.workspaceRegistry.list(),
|
|
4034
|
+
this.projectRegistry.list(),
|
|
4035
|
+
]);
|
|
4036
|
+
const activeRecords = persistedWorkspaces.filter((workspace) => !workspace.archivedAt);
|
|
4037
|
+
const activeProjects = new Map(persistedProjects
|
|
4038
|
+
.filter((project) => !project.archivedAt)
|
|
4039
|
+
.map((project) => [project.projectId, project]));
|
|
4164
4040
|
const descriptorsByWorkspaceId = new Map();
|
|
4165
|
-
const
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
const existing = placementByWorkspaceId.get(key);
|
|
4169
|
-
if (existing) {
|
|
4170
|
-
return existing;
|
|
4171
|
-
}
|
|
4172
|
-
const next = this.buildProjectPlacement(workspaceCwd);
|
|
4173
|
-
placementByWorkspaceId.set(key, next);
|
|
4174
|
-
return next;
|
|
4175
|
-
};
|
|
4041
|
+
for (const workspace of activeRecords) {
|
|
4042
|
+
descriptorsByWorkspaceId.set(workspace.workspaceId, await this.describeWorkspaceRecord(workspace, activeProjects.get(workspace.projectId) ?? null));
|
|
4043
|
+
}
|
|
4176
4044
|
for (const agent of agents) {
|
|
4177
4045
|
if (agent.archivedAt) {
|
|
4178
4046
|
continue;
|
|
4179
4047
|
}
|
|
4180
|
-
const workspaceId =
|
|
4181
|
-
const placement = await getPlacement(workspaceId);
|
|
4048
|
+
const workspaceId = normalizePersistedWorkspaceId(agent.cwd);
|
|
4182
4049
|
const existing = descriptorsByWorkspaceId.get(workspaceId);
|
|
4183
4050
|
if (!existing) {
|
|
4184
|
-
const bucket = this.deriveWorkspaceStateBucket(agent);
|
|
4185
|
-
descriptorsByWorkspaceId.set(workspaceId, {
|
|
4186
|
-
id: workspaceId,
|
|
4187
|
-
projectId: placement.projectKey,
|
|
4188
|
-
name: this.deriveWorkspaceName({
|
|
4189
|
-
cwd: workspaceId,
|
|
4190
|
-
checkout: placement.checkout,
|
|
4191
|
-
}),
|
|
4192
|
-
status: bucket,
|
|
4193
|
-
activityAt: this.accumulateLatestActivityAt(null, agent),
|
|
4194
|
-
});
|
|
4195
4051
|
continue;
|
|
4196
4052
|
}
|
|
4197
4053
|
const bucket = this.deriveWorkspaceStateBucket(agent);
|
|
@@ -4399,12 +4255,62 @@ export class Session {
|
|
|
4399
4255
|
});
|
|
4400
4256
|
}
|
|
4401
4257
|
}
|
|
4258
|
+
async ensureWorkspaceRegistered(cwd) {
|
|
4259
|
+
const workspaceId = normalizePersistedWorkspaceId(cwd);
|
|
4260
|
+
const existing = await this.workspaceRegistry.get(workspaceId);
|
|
4261
|
+
if (existing && !existing.archivedAt) {
|
|
4262
|
+
return existing;
|
|
4263
|
+
}
|
|
4264
|
+
const placement = await this.buildProjectPlacement(workspaceId);
|
|
4265
|
+
const now = new Date().toISOString();
|
|
4266
|
+
const projectExisting = await this.projectRegistry.get(placement.projectKey);
|
|
4267
|
+
const projectRecord = createPersistedProjectRecord({
|
|
4268
|
+
projectId: placement.projectKey,
|
|
4269
|
+
rootPath: deriveProjectRootPath({
|
|
4270
|
+
cwd: workspaceId,
|
|
4271
|
+
checkout: placement.checkout,
|
|
4272
|
+
}),
|
|
4273
|
+
kind: deriveProjectKind(placement.checkout),
|
|
4274
|
+
displayName: placement.projectName,
|
|
4275
|
+
createdAt: projectExisting?.createdAt ?? now,
|
|
4276
|
+
updatedAt: now,
|
|
4277
|
+
archivedAt: null,
|
|
4278
|
+
});
|
|
4279
|
+
await this.projectRegistry.upsert(projectRecord);
|
|
4280
|
+
const workspaceRecord = createPersistedWorkspaceRecord({
|
|
4281
|
+
workspaceId,
|
|
4282
|
+
projectId: placement.projectKey,
|
|
4283
|
+
cwd: workspaceId,
|
|
4284
|
+
kind: deriveWorkspaceKind(placement.checkout),
|
|
4285
|
+
displayName: deriveWorkspaceDisplayName({
|
|
4286
|
+
cwd: workspaceId,
|
|
4287
|
+
checkout: placement.checkout,
|
|
4288
|
+
}),
|
|
4289
|
+
createdAt: existing?.createdAt ?? now,
|
|
4290
|
+
updatedAt: now,
|
|
4291
|
+
archivedAt: null,
|
|
4292
|
+
});
|
|
4293
|
+
await this.workspaceRegistry.upsert(workspaceRecord);
|
|
4294
|
+
return workspaceRecord;
|
|
4295
|
+
}
|
|
4296
|
+
async archiveWorkspaceRecord(workspaceId, archivedAt) {
|
|
4297
|
+
const existing = await this.workspaceRegistry.get(workspaceId);
|
|
4298
|
+
if (!existing || existing.archivedAt) {
|
|
4299
|
+
return;
|
|
4300
|
+
}
|
|
4301
|
+
const nextArchivedAt = archivedAt ?? new Date().toISOString();
|
|
4302
|
+
await this.workspaceRegistry.archive(workspaceId, nextArchivedAt);
|
|
4303
|
+
const siblingWorkspaces = (await this.workspaceRegistry.list()).filter((workspace) => workspace.projectId === existing.projectId && !workspace.archivedAt);
|
|
4304
|
+
if (siblingWorkspaces.length === 0) {
|
|
4305
|
+
await this.projectRegistry.archive(existing.projectId, nextArchivedAt);
|
|
4306
|
+
}
|
|
4307
|
+
}
|
|
4402
4308
|
async emitWorkspaceUpdateForCwd(cwd) {
|
|
4403
4309
|
const subscription = this.workspaceUpdatesSubscription;
|
|
4404
4310
|
if (!subscription) {
|
|
4405
4311
|
return;
|
|
4406
4312
|
}
|
|
4407
|
-
const workspaceId =
|
|
4313
|
+
const workspaceId = normalizePersistedWorkspaceId(cwd);
|
|
4408
4314
|
const all = await this.listWorkspaceDescriptors();
|
|
4409
4315
|
const workspace = all.find((entry) => entry.id === workspaceId);
|
|
4410
4316
|
if (!workspace) {
|
|
@@ -4432,7 +4338,7 @@ export class Session {
|
|
|
4432
4338
|
}
|
|
4433
4339
|
const uniqueWorkspaceCwds = new Set();
|
|
4434
4340
|
for (const cwd of cwds) {
|
|
4435
|
-
const normalized =
|
|
4341
|
+
const normalized = normalizePersistedWorkspaceId(cwd);
|
|
4436
4342
|
if (!normalized) {
|
|
4437
4343
|
continue;
|
|
4438
4344
|
}
|
|
@@ -4552,6 +4458,69 @@ export class Session {
|
|
|
4552
4458
|
});
|
|
4553
4459
|
}
|
|
4554
4460
|
}
|
|
4461
|
+
async handleOpenProjectRequest(request) {
|
|
4462
|
+
try {
|
|
4463
|
+
const workspace = await this.ensureWorkspaceRegistered(request.cwd);
|
|
4464
|
+
await this.emitWorkspaceUpdateForCwd(workspace.cwd);
|
|
4465
|
+
const descriptor = await this.describeWorkspaceRecord(workspace);
|
|
4466
|
+
this.emit({
|
|
4467
|
+
type: 'open_project_response',
|
|
4468
|
+
payload: {
|
|
4469
|
+
requestId: request.requestId,
|
|
4470
|
+
workspace: descriptor,
|
|
4471
|
+
error: null,
|
|
4472
|
+
},
|
|
4473
|
+
});
|
|
4474
|
+
}
|
|
4475
|
+
catch (error) {
|
|
4476
|
+
const message = error instanceof Error ? error.message : 'Failed to open project';
|
|
4477
|
+
this.sessionLogger.error({ err: error, cwd: request.cwd }, 'Failed to open project');
|
|
4478
|
+
this.emit({
|
|
4479
|
+
type: 'open_project_response',
|
|
4480
|
+
payload: {
|
|
4481
|
+
requestId: request.requestId,
|
|
4482
|
+
workspace: null,
|
|
4483
|
+
error: message,
|
|
4484
|
+
},
|
|
4485
|
+
});
|
|
4486
|
+
}
|
|
4487
|
+
}
|
|
4488
|
+
async handleArchiveWorkspaceRequest(request) {
|
|
4489
|
+
try {
|
|
4490
|
+
const existing = await this.workspaceRegistry.get(request.workspaceId);
|
|
4491
|
+
if (!existing) {
|
|
4492
|
+
throw new Error(`Workspace not found: ${request.workspaceId}`);
|
|
4493
|
+
}
|
|
4494
|
+
if (existing.kind === 'worktree') {
|
|
4495
|
+
throw new Error('Use worktree archive for Paseo worktrees');
|
|
4496
|
+
}
|
|
4497
|
+
const archivedAt = new Date().toISOString();
|
|
4498
|
+
await this.archiveWorkspaceRecord(request.workspaceId, archivedAt);
|
|
4499
|
+
await this.emitWorkspaceUpdateForCwd(existing.cwd);
|
|
4500
|
+
this.emit({
|
|
4501
|
+
type: 'archive_workspace_response',
|
|
4502
|
+
payload: {
|
|
4503
|
+
requestId: request.requestId,
|
|
4504
|
+
workspaceId: request.workspaceId,
|
|
4505
|
+
archivedAt,
|
|
4506
|
+
error: null,
|
|
4507
|
+
},
|
|
4508
|
+
});
|
|
4509
|
+
}
|
|
4510
|
+
catch (error) {
|
|
4511
|
+
const message = error instanceof Error ? error.message : 'Failed to archive workspace';
|
|
4512
|
+
this.sessionLogger.error({ err: error, workspaceId: request.workspaceId }, 'Failed to archive workspace');
|
|
4513
|
+
this.emit({
|
|
4514
|
+
type: 'archive_workspace_response',
|
|
4515
|
+
payload: {
|
|
4516
|
+
requestId: request.requestId,
|
|
4517
|
+
workspaceId: request.workspaceId,
|
|
4518
|
+
archivedAt: null,
|
|
4519
|
+
error: message,
|
|
4520
|
+
},
|
|
4521
|
+
});
|
|
4522
|
+
}
|
|
4523
|
+
}
|
|
4555
4524
|
async handleFetchAgent(agentIdOrIdentifier, requestId) {
|
|
4556
4525
|
const resolved = await this.resolveAgentIdentifier(agentIdOrIdentifier);
|
|
4557
4526
|
if (!resolved.ok) {
|
|
@@ -4731,7 +4700,6 @@ export class Session {
|
|
|
4731
4700
|
const agentId = resolved.agentId;
|
|
4732
4701
|
await this.unarchiveAgentState(agentId);
|
|
4733
4702
|
await this.ensureAgentLoaded(agentId);
|
|
4734
|
-
await this.interruptAgentIfRunning(agentId);
|
|
4735
4703
|
try {
|
|
4736
4704
|
this.agentManager.recordUserMessage(agentId, msg.text, {
|
|
4737
4705
|
messageId: msg.messageId,
|