@getpaseo/server 0.1.97-beta.3 → 0.1.98
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server/server/agent/agent-manager.d.ts +11 -3
- package/dist/server/server/agent/agent-manager.js +95 -23
- package/dist/server/server/agent/agent-prompt.d.ts +1 -1
- package/dist/server/server/agent/agent-prompt.js +3 -10
- package/dist/server/server/agent/agent-response-loop.js +9 -3
- package/dist/server/server/agent/agent-sdk-types.d.ts +9 -3
- package/dist/server/server/agent/agent-storage.d.ts +20 -240
- package/dist/server/server/agent/agent-storage.js +6 -6
- package/dist/server/server/agent/create-agent/create.d.ts +2 -0
- package/dist/server/server/agent/create-agent/create.js +8 -7
- package/dist/server/server/agent/lifecycle-command.d.ts +15 -1
- package/dist/server/server/agent/lifecycle-command.js +9 -2
- package/dist/server/server/agent/mcp-server.js +263 -119
- package/dist/server/server/agent/mcp-shared.d.ts +35 -179
- package/dist/server/server/agent/provider-notices.d.ts +3 -0
- package/dist/server/server/agent/provider-notices.js +5 -0
- package/dist/server/server/agent/provider-registry.d.ts +2 -0
- package/dist/server/server/agent/provider-registry.js +10 -3
- package/dist/server/server/agent/provider-snapshot-manager.d.ts +3 -0
- package/dist/server/server/agent/provider-snapshot-manager.js +11 -2
- package/dist/server/server/agent/providers/claude/agent.js +257 -143
- package/dist/server/server/agent/providers/claude/models.js +7 -3
- package/dist/server/server/agent/providers/claude/project-dir.js +9 -6
- package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +2 -22
- package/dist/server/server/agent/providers/codex/app-server-transport.d.ts +8 -118
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +4 -3
- package/dist/server/server/agent/providers/codex-app-server-agent.js +43 -1
- package/dist/server/server/agent/providers/copilot-acp-agent.js +4 -1
- package/dist/server/server/agent/providers/diagnostic-utils.d.ts +9 -0
- package/dist/server/server/agent/providers/diagnostic-utils.js +188 -0
- package/dist/server/server/agent/providers/generic-acp-agent.d.ts +1 -5
- package/dist/server/server/agent/providers/mock-slow-provider.js +1 -1
- package/dist/server/server/agent/providers/opencode/server-manager.d.ts +29 -2
- package/dist/server/server/agent/providers/opencode/server-manager.js +83 -17
- package/dist/server/server/agent/providers/opencode-agent.d.ts +2 -0
- package/dist/server/server/agent/providers/opencode-agent.js +14 -9
- package/dist/server/server/agent/providers/pi/agent.d.ts +1 -5
- package/dist/server/server/agent/providers/pi/agent.js +27 -14
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +391 -1261
- package/dist/server/server/agent/providers/tool-call-detail-primitives.js +26 -16
- package/dist/server/server/bootstrap.d.ts +2 -0
- package/dist/server/server/bootstrap.js +32 -2
- package/dist/server/server/loop-service.d.ts +60 -359
- package/dist/server/server/managed-processes/managed-processes.d.ts +76 -0
- package/dist/server/server/managed-processes/managed-processes.js +326 -0
- package/dist/server/server/migrations/backfill-workspace-id.migration.js +10 -6
- package/dist/server/server/package-version.d.ts +1 -7
- package/dist/server/server/paseo-worktree-service.js +15 -1
- package/dist/server/server/persisted-config.d.ts +138 -1009
- package/dist/server/server/persisted-config.js +1 -1
- package/dist/server/server/pid-lock.d.ts +1 -15
- package/dist/server/server/resolve-worktree-creation-intent.d.ts +3 -0
- package/dist/server/server/resolve-worktree-creation-intent.js +3 -3
- package/dist/server/server/session.d.ts +18 -1
- package/dist/server/server/session.js +424 -64
- package/dist/server/server/speech/providers/local/sherpa/model-catalog.d.ts +2 -2
- package/dist/server/server/speech/providers/openai/runtime.js +3 -4
- package/dist/server/server/speech/speech-types.d.ts +9 -11
- package/dist/server/server/websocket-server.d.ts +1 -0
- package/dist/server/server/websocket-server.js +15 -0
- package/dist/server/server/workspace-archive-service.js +2 -3
- package/dist/server/server/workspace-directory.js +5 -5
- package/dist/server/server/workspace-reconciliation-service.js +2 -2
- package/dist/server/server/workspace-registry.d.ts +17 -48
- package/dist/server/server/workspace-registry.js +9 -0
- package/dist/server/server/worktree-core.d.ts +1 -0
- package/dist/server/server/worktree-core.js +5 -1
- package/dist/server/services/quota-fetcher/manifest.d.ts +4 -0
- package/dist/server/services/quota-fetcher/manifest.js +47 -0
- package/dist/server/services/quota-fetcher/provider.d.ts +17 -0
- package/dist/server/services/quota-fetcher/provider.js +2 -0
- package/dist/server/services/quota-fetcher/providers/claude.d.ts +26 -0
- package/dist/server/services/quota-fetcher/providers/claude.js +217 -0
- package/dist/server/services/quota-fetcher/providers/codex.d.ts +23 -0
- package/dist/server/services/quota-fetcher/providers/codex.js +211 -0
- package/dist/server/services/quota-fetcher/providers/copilot.d.ts +17 -0
- package/dist/server/services/quota-fetcher/providers/copilot.js +75 -0
- package/dist/server/services/quota-fetcher/providers/cursor.d.ts +17 -0
- package/dist/server/services/quota-fetcher/providers/cursor.js +123 -0
- package/dist/server/services/quota-fetcher/providers/grok.d.ts +18 -0
- package/dist/server/services/quota-fetcher/providers/grok.js +89 -0
- package/dist/server/services/quota-fetcher/providers/kimi.d.ts +20 -0
- package/dist/server/services/quota-fetcher/providers/kimi.js +89 -0
- package/dist/server/services/quota-fetcher/providers/zai.d.ts +17 -0
- package/dist/server/services/quota-fetcher/providers/zai.js +58 -0
- package/dist/server/services/quota-fetcher/service.d.ts +28 -0
- package/dist/server/services/quota-fetcher/service.js +58 -0
- package/dist/server/services/quota-fetcher/usage.d.ts +22 -0
- package/dist/server/services/quota-fetcher/usage.js +49 -0
- package/dist/server/terminal/terminal-session-controller.d.ts +8 -0
- package/dist/server/terminal/terminal-session-controller.js +23 -3
- package/dist/server/utils/checkout-git.js +36 -76
- package/dist/server/utils/directory-suggestions.js +98 -2
- package/dist/server/utils/worktree-metadata.d.ts +7 -59
- package/dist/src/server/persisted-config.js +1 -1
- package/package.json +9 -9
|
@@ -2,7 +2,7 @@ import equal from "fast-deep-equal";
|
|
|
2
2
|
import { v4 as uuidv4 } from "uuid";
|
|
3
3
|
import { realpathSync } from "node:fs";
|
|
4
4
|
import { stat } from "node:fs/promises";
|
|
5
|
-
import { resolve, sep } from "path";
|
|
5
|
+
import { basename, normalize, resolve, sep } from "path";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
import { CLIENT_CAPS } from "@getpaseo/protocol/client-capabilities";
|
|
@@ -32,9 +32,10 @@ import { deriveProjectSlug } from "./workspace-git-metadata.js";
|
|
|
32
32
|
import { spawnWorkspaceScript } from "./worktree-bootstrap.js";
|
|
33
33
|
import { getErrorMessage, getErrorMessageOr } from "@getpaseo/protocol/error-utils";
|
|
34
34
|
import { getAgentStatusPriority } from "@getpaseo/protocol/agent-state-bucket";
|
|
35
|
+
import { getParentAgentIdFromLabels } from "@getpaseo/protocol/agent-labels";
|
|
35
36
|
import { resolveSnapshotCwd } from "./agent/provider-snapshot-manager.js";
|
|
36
37
|
import { createAgentCommand } from "./agent/create-agent/create.js";
|
|
37
|
-
import { archiveAgentCommand, cancelAgentRunCommand, closeAgentCommand, setAgentModeCommand, updateAgentCommand, } from "./agent/lifecycle-command.js";
|
|
38
|
+
import { archiveAgentCommand, cancelAgentRunCommand, closeAgentCommand, detachAgentCommand, setAgentModeCommand, updateAgentCommand, } from "./agent/lifecycle-command.js";
|
|
38
39
|
import { buildStoredAgentPayload, resolveEffectiveThinkingOptionId, resolveStoredAgentPayloadUpdatedAt, toAgentPayload, } from "./agent/agent-projections.js";
|
|
39
40
|
import { appendTimelineItemIfAgentKnown, emitLiveTimelineItemIfAgentKnown, } from "./agent/timeline-append.js";
|
|
40
41
|
import { projectTimelineRows, selectProjectedTimelinePage, } from "./agent/timeline-projection.js";
|
|
@@ -50,7 +51,7 @@ import { isVoicePermissionAllowed } from "./voice-permission-policy.js";
|
|
|
50
51
|
import { listDirectoryEntries, readExplorerFile, readExplorerFileBytes, getDownloadableFileInfo, } from "./file-explorer/service.js";
|
|
51
52
|
import { readPaseoConfigForEdit, writePaseoConfigForEdit, } from "../utils/paseo-config-file.js";
|
|
52
53
|
import { buildMetadataPrompt } from "../utils/build-metadata-prompt.js";
|
|
53
|
-
import { archivePersistedWorkspaceRecord } from "./workspace-archive-service.js";
|
|
54
|
+
import { archivePersistedWorkspaceRecord, archiveWorkspaceContents, } from "./workspace-archive-service.js";
|
|
54
55
|
import { WorkspaceReconciliationService } from "./workspace-reconciliation-service.js";
|
|
55
56
|
import { checkoutResolvedBranch, commitChanges, mergeToBase, mergeFromBase, pullCurrentBranch, pushCurrentBranch, createPullRequest, renameCurrentBranch as renameCurrentBranchDefault, } from "../utils/checkout-git.js";
|
|
56
57
|
import { validateBranchSlug } from "@getpaseo/protocol/branch-slug";
|
|
@@ -70,7 +71,9 @@ import { attemptFirstAgentBranchAutoName, createLocalCheckoutWorkspace, createPa
|
|
|
70
71
|
import { generateBranchNameFromFirstAgentContext, } from "./worktree-branch-name-generator.js";
|
|
71
72
|
import { assertSafeGitRef as assertWorktreeSafeGitRef, buildAgentSessionConfig as buildWorktreeAgentSessionConfig, createPaseoWorktreeWorkflow as createWorktreeWorkflow, handleCreatePaseoWorktreeRequest as handleCreateWorktreeRequest, handlePaseoWorktreeArchiveRequest as handleWorktreeArchiveRequest, handlePaseoWorktreeListRequest as handleWorktreeListRequest, handleWorkspaceSetupStatusRequest as handleWorkspaceSetupStatusRequestMessage, } from "./worktree-session.js";
|
|
72
73
|
import { archiveByScope } from "./workspace-archive-service.js";
|
|
73
|
-
import { toWorktreeWireError } from "./worktree-errors.js";
|
|
74
|
+
import { WorktreeRequestError, toWorktreeRequestError, toWorktreeWireError, } from "./worktree-errors.js";
|
|
75
|
+
import { createWorktree } from "../utils/worktree.js";
|
|
76
|
+
import { runGitCommand } from "../utils/run-git-command.js";
|
|
74
77
|
import { CreateAgentLifecycleDispatch } from "./agent/create-agent-lifecycle-dispatch.js";
|
|
75
78
|
const WORKSPACE_GIT_WATCH_REMOVED_STATE_KEY = "__removed__";
|
|
76
79
|
async function resolveKnownProjectRootForConfig(input) {
|
|
@@ -223,7 +226,7 @@ const PCM_BITS_PER_SAMPLE = 16;
|
|
|
223
226
|
const PCM_BYTES_PER_MS = (PCM_SAMPLE_RATE * PCM_CHANNELS * (PCM_BITS_PER_SAMPLE / 8)) / 1000;
|
|
224
227
|
const MIN_STREAMING_SEGMENT_DURATION_MS = 1000;
|
|
225
228
|
const MIN_STREAMING_SEGMENT_BYTES = Math.round(PCM_BYTES_PER_MS * MIN_STREAMING_SEGMENT_DURATION_MS);
|
|
226
|
-
const AgentIdSchema = z.
|
|
229
|
+
const AgentIdSchema = z.guid();
|
|
227
230
|
const nodeSessionFileSystem = {
|
|
228
231
|
async isDirectory(path) {
|
|
229
232
|
const stats = await stat(path).catch(() => null);
|
|
@@ -340,7 +343,7 @@ export class Session {
|
|
|
340
343
|
}
|
|
341
344
|
},
|
|
342
345
|
});
|
|
343
|
-
const { clientId, appVersion, clientCapabilities, onMessage, onBinaryMessage, getTransportBufferedAmount, onLifecycleIntent, logger, downloadTokenStore, pushTokenStore, paseoHome, worktreesRoot, agentManager, agentStorage, projectRegistry, workspaceRegistry, filesystem, chatService, scheduleService, loopService, checkoutDiffManager, github, renameCurrentBranch, generateWorkspaceName, workspaceGitService, daemonConfigStore, mcpBaseUrl, stt, sttLanguage, tts, terminalManager, providerSnapshotManager, serviceProxy, scriptRuntimeStore, workspaceSetupSnapshots, onBranchChanged, getDaemonTcpPort, getDaemonTcpHost, serviceProxyPublicBaseUrl, resolveScriptHealth, voice, voiceBridge, dictation, serverId, daemonVersion, daemonRuntimeConfig, } = options;
|
|
346
|
+
const { clientId, appVersion, clientCapabilities, onMessage, onBinaryMessage, getTransportBufferedAmount, onLifecycleIntent, logger, downloadTokenStore, pushTokenStore, paseoHome, worktreesRoot, agentManager, agentStorage, projectRegistry, workspaceRegistry, filesystem, chatService, scheduleService, loopService, checkoutDiffManager, github, renameCurrentBranch, generateWorkspaceName, workspaceGitService, daemonConfigStore, mcpBaseUrl, stt, sttLanguage, tts, terminalManager, providerSnapshotManager, providerUsageService, serviceProxy, scriptRuntimeStore, workspaceSetupSnapshots, onBranchChanged, getDaemonTcpPort, getDaemonTcpHost, serviceProxyPublicBaseUrl, resolveScriptHealth, voice, voiceBridge, dictation, serverId, daemonVersion, daemonRuntimeConfig, } = options;
|
|
344
347
|
this.clientId = clientId;
|
|
345
348
|
this.appVersion = appVersion ?? null;
|
|
346
349
|
this.clientCapabilities = parseClientCapabilities(clientCapabilities);
|
|
@@ -382,12 +385,7 @@ export class Session {
|
|
|
382
385
|
hasBinaryChannel: () => this.onBinaryMessage !== null,
|
|
383
386
|
isPathWithinRoot: (rootPath, candidatePath) => this.isPathWithinRoot(rootPath, candidatePath),
|
|
384
387
|
sessionLogger: this.sessionLogger,
|
|
385
|
-
|
|
386
|
-
const workspaces = await this.workspaceRegistry.list();
|
|
387
|
-
return workspaces
|
|
388
|
-
.filter((workspace) => !workspace.archivedAt)
|
|
389
|
-
.map((workspace) => workspace.cwd);
|
|
390
|
-
},
|
|
388
|
+
listTerminalWorkspaceRefs: () => this.listActiveWorkspaceRefs(),
|
|
391
389
|
clientSupportsWrapReflow: () => this.clientCapabilities.has(CLIENT_CAPS.terminalReflowableSnapshot),
|
|
392
390
|
getClientBufferedAmount: () => this.getTransportBufferedAmount(),
|
|
393
391
|
});
|
|
@@ -419,6 +417,7 @@ export class Session {
|
|
|
419
417
|
logger: this.sessionLogger,
|
|
420
418
|
});
|
|
421
419
|
this.providerSnapshotManager = providerSnapshotManager;
|
|
420
|
+
this.providerUsageService = providerUsageService;
|
|
422
421
|
this.serviceProxy = serviceProxy ?? null;
|
|
423
422
|
this.scriptRuntimeStore = scriptRuntimeStore ?? null;
|
|
424
423
|
this.workspaceSetupSnapshots = workspaceSetupSnapshots ?? new Map();
|
|
@@ -863,6 +862,39 @@ export class Session {
|
|
|
863
862
|
payload,
|
|
864
863
|
});
|
|
865
864
|
}
|
|
865
|
+
async emitStoredAgentUpdate(record) {
|
|
866
|
+
const payload = this.buildStoredAgentPayload(record);
|
|
867
|
+
const subscription = this.agentUpdatesSubscription;
|
|
868
|
+
if (!subscription) {
|
|
869
|
+
return payload;
|
|
870
|
+
}
|
|
871
|
+
const project = payload.workspaceId
|
|
872
|
+
? await this.buildProjectPlacementForWorkspaceId(payload.workspaceId)
|
|
873
|
+
: null;
|
|
874
|
+
if (!project) {
|
|
875
|
+
this.bufferOrEmitAgentUpdate(subscription, {
|
|
876
|
+
kind: "remove",
|
|
877
|
+
agentId: payload.id,
|
|
878
|
+
});
|
|
879
|
+
return payload;
|
|
880
|
+
}
|
|
881
|
+
const matches = this.matchesAgentFilter({
|
|
882
|
+
agent: payload,
|
|
883
|
+
project,
|
|
884
|
+
filter: subscription.filter,
|
|
885
|
+
});
|
|
886
|
+
this.bufferOrEmitAgentUpdate(subscription, matches
|
|
887
|
+
? {
|
|
888
|
+
kind: "upsert",
|
|
889
|
+
agent: payload,
|
|
890
|
+
project,
|
|
891
|
+
}
|
|
892
|
+
: {
|
|
893
|
+
kind: "remove",
|
|
894
|
+
agentId: payload.id,
|
|
895
|
+
});
|
|
896
|
+
return payload;
|
|
897
|
+
}
|
|
866
898
|
flushBootstrappedAgentUpdates(options) {
|
|
867
899
|
const subscription = this.agentUpdatesSubscription;
|
|
868
900
|
if (!subscription || !subscription.isBootstrapping) {
|
|
@@ -911,6 +943,7 @@ export class Session {
|
|
|
911
943
|
return {
|
|
912
944
|
projectKey: project.projectId,
|
|
913
945
|
projectName: resolveProjectDisplayName(project),
|
|
946
|
+
workspaceName: resolveWorkspaceDisplayName(workspace),
|
|
914
947
|
checkout,
|
|
915
948
|
};
|
|
916
949
|
}
|
|
@@ -920,6 +953,15 @@ export class Session {
|
|
|
920
953
|
return null;
|
|
921
954
|
return this.buildProjectPlacementForWorkspace(workspace);
|
|
922
955
|
}
|
|
956
|
+
async buildProjectPlacementForExistingWorkspaceProject(workspaceId) {
|
|
957
|
+
const workspace = await this.workspaceRegistry.get(workspaceId);
|
|
958
|
+
if (!workspace)
|
|
959
|
+
return null;
|
|
960
|
+
const project = await this.projectRegistry.get(workspace.projectId);
|
|
961
|
+
if (!project)
|
|
962
|
+
return null;
|
|
963
|
+
return this.buildProjectPlacementForWorkspace(workspace, project);
|
|
964
|
+
}
|
|
923
965
|
async forwardAgentUpdate(agent) {
|
|
924
966
|
try {
|
|
925
967
|
const subscription = this.agentUpdatesSubscription;
|
|
@@ -1019,6 +1061,7 @@ export class Session {
|
|
|
1019
1061
|
async dispatchInboundMessage(msg) {
|
|
1020
1062
|
const promise = this.dispatchVoiceAndControlMessage(msg) ??
|
|
1021
1063
|
this.dispatchAgentRewindMessage(msg) ??
|
|
1064
|
+
this.dispatchAgentRelationshipMessage(msg) ??
|
|
1022
1065
|
this.dispatchAgentLifecycleMessage(msg) ??
|
|
1023
1066
|
this.dispatchAgentConfigMessage(msg) ??
|
|
1024
1067
|
this.dispatchCheckoutMessage(msg) ??
|
|
@@ -1087,6 +1130,14 @@ export class Session {
|
|
|
1087
1130
|
return undefined;
|
|
1088
1131
|
}
|
|
1089
1132
|
}
|
|
1133
|
+
dispatchAgentRelationshipMessage(msg) {
|
|
1134
|
+
switch (msg.type) {
|
|
1135
|
+
case "agent.detach.request":
|
|
1136
|
+
return this.handleDetachAgentRequest(msg.agentId, msg.requestId);
|
|
1137
|
+
default:
|
|
1138
|
+
return undefined;
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1090
1141
|
async handleDictationStreamStart(msg) {
|
|
1091
1142
|
const unavailable = this.resolveVoiceFeatureUnavailableContext("dictation");
|
|
1092
1143
|
if (unavailable) {
|
|
@@ -1343,8 +1394,12 @@ export class Session {
|
|
|
1343
1394
|
return this.handleLegacyOpenInEditorRequest(msg);
|
|
1344
1395
|
case "open_project_request":
|
|
1345
1396
|
return this.handleOpenProjectRequest(msg);
|
|
1397
|
+
case "project.add.request":
|
|
1398
|
+
return this.handleProjectAddRequest(msg);
|
|
1346
1399
|
case "archive_workspace_request":
|
|
1347
1400
|
return this.handleArchiveWorkspaceRequest(msg);
|
|
1401
|
+
case "project.remove.request":
|
|
1402
|
+
return this.handleProjectRemoveRequest(msg);
|
|
1348
1403
|
case "workspace.create.request":
|
|
1349
1404
|
return this.handleWorkspaceCreateRequest(msg);
|
|
1350
1405
|
case "workspace.clear_attention.request":
|
|
@@ -1380,6 +1435,8 @@ export class Session {
|
|
|
1380
1435
|
return this.handleRefreshProvidersSnapshotRequest(msg);
|
|
1381
1436
|
case "provider_diagnostic_request":
|
|
1382
1437
|
return this.handleProviderDiagnosticRequest(msg);
|
|
1438
|
+
case "provider.usage.list.request":
|
|
1439
|
+
return this.handleProviderUsageListRequest(msg);
|
|
1383
1440
|
default:
|
|
1384
1441
|
return undefined;
|
|
1385
1442
|
}
|
|
@@ -1571,39 +1628,60 @@ export class Session {
|
|
|
1571
1628
|
logger: this.sessionLogger,
|
|
1572
1629
|
}, agentId);
|
|
1573
1630
|
if (this.agentUpdatesSubscription) {
|
|
1574
|
-
const payload = this.
|
|
1575
|
-
const project = payload.workspaceId
|
|
1576
|
-
? await this.buildProjectPlacementForWorkspaceId(payload.workspaceId)
|
|
1577
|
-
: null;
|
|
1578
|
-
if (project) {
|
|
1579
|
-
const matches = this.matchesAgentFilter({
|
|
1580
|
-
agent: payload,
|
|
1581
|
-
project,
|
|
1582
|
-
filter: this.agentUpdatesSubscription.filter,
|
|
1583
|
-
});
|
|
1584
|
-
this.bufferOrEmitAgentUpdate(this.agentUpdatesSubscription, matches
|
|
1585
|
-
? {
|
|
1586
|
-
kind: "upsert",
|
|
1587
|
-
agent: payload,
|
|
1588
|
-
project,
|
|
1589
|
-
}
|
|
1590
|
-
: {
|
|
1591
|
-
kind: "remove",
|
|
1592
|
-
agentId,
|
|
1593
|
-
});
|
|
1594
|
-
}
|
|
1595
|
-
else {
|
|
1596
|
-
this.bufferOrEmitAgentUpdate(this.agentUpdatesSubscription, {
|
|
1597
|
-
kind: "remove",
|
|
1598
|
-
agentId,
|
|
1599
|
-
});
|
|
1600
|
-
}
|
|
1631
|
+
const payload = await this.emitStoredAgentUpdate(archivedRecord);
|
|
1601
1632
|
if (payload.workspaceId) {
|
|
1602
1633
|
await this.emitWorkspaceUpdateForWorkspaceId(payload.workspaceId);
|
|
1603
1634
|
}
|
|
1604
1635
|
}
|
|
1605
1636
|
return { agentId, archivedAt };
|
|
1606
1637
|
}
|
|
1638
|
+
async handleDetachAgentRequest(agentId, requestId) {
|
|
1639
|
+
this.sessionLogger.info({ agentId, requestId }, "Detaching agent from parent");
|
|
1640
|
+
try {
|
|
1641
|
+
const result = await detachAgentCommand({ agentManager: this.agentManager }, agentId);
|
|
1642
|
+
const affectedWorkspaceIds = new Set();
|
|
1643
|
+
if (!result.live) {
|
|
1644
|
+
const payload = await this.emitStoredAgentUpdate(result.record);
|
|
1645
|
+
if (payload.workspaceId) {
|
|
1646
|
+
affectedWorkspaceIds.add(payload.workspaceId);
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
else if (result.record.workspaceId) {
|
|
1650
|
+
affectedWorkspaceIds.add(result.record.workspaceId);
|
|
1651
|
+
}
|
|
1652
|
+
if (result.previousParentAgentId) {
|
|
1653
|
+
const rootWorkspaceId = await this.resolveDelegationRootWorkspaceId(result.previousParentAgentId);
|
|
1654
|
+
if (rootWorkspaceId) {
|
|
1655
|
+
affectedWorkspaceIds.add(rootWorkspaceId);
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
await this.emitWorkspaceUpdatesForWorkspaceIds(affectedWorkspaceIds, {
|
|
1659
|
+
skipReconcile: true,
|
|
1660
|
+
});
|
|
1661
|
+
this.emit({
|
|
1662
|
+
type: "agent.detach.response",
|
|
1663
|
+
payload: {
|
|
1664
|
+
requestId,
|
|
1665
|
+
agentId,
|
|
1666
|
+
accepted: true,
|
|
1667
|
+
error: null,
|
|
1668
|
+
},
|
|
1669
|
+
});
|
|
1670
|
+
}
|
|
1671
|
+
catch (error) {
|
|
1672
|
+
const message = getErrorMessageOr(error, "Failed to detach agent");
|
|
1673
|
+
this.sessionLogger.error({ err: error, agentId, requestId }, "Failed to detach agent");
|
|
1674
|
+
this.emit({
|
|
1675
|
+
type: "agent.detach.response",
|
|
1676
|
+
payload: {
|
|
1677
|
+
requestId,
|
|
1678
|
+
agentId,
|
|
1679
|
+
accepted: false,
|
|
1680
|
+
error: message,
|
|
1681
|
+
},
|
|
1682
|
+
});
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1607
1685
|
async handleCloseItemsRequest(msg) {
|
|
1608
1686
|
const archiveResults = await Promise.allSettled(msg.agentIds.map((agentId) => this.archiveAgentForClose(agentId)));
|
|
1609
1687
|
const agents = [];
|
|
@@ -1764,6 +1842,80 @@ export class Session {
|
|
|
1764
1842
|
});
|
|
1765
1843
|
}
|
|
1766
1844
|
}
|
|
1845
|
+
async handleProjectRemoveRequest(request) {
|
|
1846
|
+
const { projectId, requestId } = request;
|
|
1847
|
+
this.sessionLogger.info({ projectId, requestId }, "session: project.remove.request");
|
|
1848
|
+
try {
|
|
1849
|
+
const projectWorkspaces = (await this.workspaceRegistry.list()).filter((workspace) => workspace.projectId === projectId);
|
|
1850
|
+
const activeWorkspaceIds = projectWorkspaces
|
|
1851
|
+
.filter((workspace) => !workspace.archivedAt)
|
|
1852
|
+
.map((workspace) => workspace.workspaceId);
|
|
1853
|
+
if (activeWorkspaceIds.length > 0) {
|
|
1854
|
+
this.markWorkspaceArchiving(activeWorkspaceIds, new Date().toISOString());
|
|
1855
|
+
await this.emitWorkspaceUpdatesForWorkspaceIds(activeWorkspaceIds, {
|
|
1856
|
+
skipReconcile: true,
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1859
|
+
const removedWorkspaceIds = [];
|
|
1860
|
+
try {
|
|
1861
|
+
for (const workspaceId of activeWorkspaceIds) {
|
|
1862
|
+
await archiveWorkspaceContents({
|
|
1863
|
+
agentManager: this.agentManager,
|
|
1864
|
+
agentStorage: this.agentStorage,
|
|
1865
|
+
killTerminalsForWorkspace: (id) => this.terminalController.killTerminalsForWorkspace(id),
|
|
1866
|
+
sessionLogger: this.sessionLogger,
|
|
1867
|
+
}, workspaceId);
|
|
1868
|
+
await this.archiveWorkspaceRecord(workspaceId);
|
|
1869
|
+
removedWorkspaceIds.push(workspaceId);
|
|
1870
|
+
}
|
|
1871
|
+
await this.projectRegistry.remove(projectId);
|
|
1872
|
+
}
|
|
1873
|
+
finally {
|
|
1874
|
+
if (activeWorkspaceIds.length > 0) {
|
|
1875
|
+
this.clearWorkspaceArchiving(activeWorkspaceIds);
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
const updateIds = removedWorkspaceIds.length > 0
|
|
1879
|
+
? removedWorkspaceIds
|
|
1880
|
+
: [projectWorkspaces[0]?.workspaceId ?? projectId];
|
|
1881
|
+
await this.emitWorkspaceUpdatesForWorkspaceIds(updateIds, {
|
|
1882
|
+
skipReconcile: true,
|
|
1883
|
+
removedProjectId: projectId,
|
|
1884
|
+
});
|
|
1885
|
+
this.emit({
|
|
1886
|
+
type: "project.remove.response",
|
|
1887
|
+
payload: {
|
|
1888
|
+
requestId,
|
|
1889
|
+
projectId,
|
|
1890
|
+
accepted: true,
|
|
1891
|
+
removedWorkspaceIds,
|
|
1892
|
+
error: null,
|
|
1893
|
+
},
|
|
1894
|
+
});
|
|
1895
|
+
}
|
|
1896
|
+
catch (error) {
|
|
1897
|
+
this.sessionLogger.error({ err: error, projectId, requestId }, "session: project.remove.request error");
|
|
1898
|
+
this.emit({
|
|
1899
|
+
type: "activity_log",
|
|
1900
|
+
payload: {
|
|
1901
|
+
id: uuidv4(),
|
|
1902
|
+
timestamp: new Date(),
|
|
1903
|
+
type: "error",
|
|
1904
|
+
content: `Failed to remove project: ${getErrorMessage(error)}`,
|
|
1905
|
+
},
|
|
1906
|
+
});
|
|
1907
|
+
this.emit({
|
|
1908
|
+
type: "project.remove.response",
|
|
1909
|
+
payload: {
|
|
1910
|
+
requestId,
|
|
1911
|
+
projectId,
|
|
1912
|
+
accepted: false,
|
|
1913
|
+
removedWorkspaceIds: [],
|
|
1914
|
+
error: getErrorMessageOr(error, "Failed to remove project"),
|
|
1915
|
+
},
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1767
1919
|
async handleWorkspaceTitleSetRequest(workspaceId, title, requestId) {
|
|
1768
1920
|
this.sessionLogger.info({ workspaceId, requestId, hasTitle: typeof title === "string" }, "session: workspace.title.set.request");
|
|
1769
1921
|
try {
|
|
@@ -2412,6 +2564,7 @@ export class Session {
|
|
|
2412
2564
|
this.sessionLogger.info({ agentId }, `Refreshing agent ${agentId} from persistence`);
|
|
2413
2565
|
try {
|
|
2414
2566
|
await unarchiveAgentState(this.agentStorage, this.agentManager, agentId);
|
|
2567
|
+
await this.unarchiveOwningWorkspaceForAgent(agentId);
|
|
2415
2568
|
let snapshot;
|
|
2416
2569
|
const existing = this.agentManager.getAgent(agentId);
|
|
2417
2570
|
if (existing) {
|
|
@@ -2460,7 +2613,7 @@ export class Session {
|
|
|
2460
2613
|
requestId,
|
|
2461
2614
|
requestType: msg.type,
|
|
2462
2615
|
error: message,
|
|
2463
|
-
code: "agent_refresh_failed",
|
|
2616
|
+
code: error instanceof WorktreeRequestError ? error.code : "agent_refresh_failed",
|
|
2464
2617
|
},
|
|
2465
2618
|
});
|
|
2466
2619
|
}
|
|
@@ -2991,6 +3144,32 @@ export class Session {
|
|
|
2991
3144
|
});
|
|
2992
3145
|
}
|
|
2993
3146
|
}
|
|
3147
|
+
async handleProviderUsageListRequest(msg) {
|
|
3148
|
+
try {
|
|
3149
|
+
const usage = await this.providerUsageService.listUsage();
|
|
3150
|
+
this.emit({
|
|
3151
|
+
type: "provider.usage.list.response",
|
|
3152
|
+
payload: {
|
|
3153
|
+
requestId: msg.requestId,
|
|
3154
|
+
fetchedAt: usage.fetchedAt,
|
|
3155
|
+
providers: usage.providers,
|
|
3156
|
+
},
|
|
3157
|
+
});
|
|
3158
|
+
}
|
|
3159
|
+
catch (error) {
|
|
3160
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
3161
|
+
this.sessionLogger.error({ err }, "Failed to list provider usage");
|
|
3162
|
+
this.emit({
|
|
3163
|
+
type: "rpc_error",
|
|
3164
|
+
payload: {
|
|
3165
|
+
requestId: msg.requestId,
|
|
3166
|
+
requestType: msg.type,
|
|
3167
|
+
error: `Failed to list provider usage: ${err.message}`,
|
|
3168
|
+
code: "provider_usage_list_failed",
|
|
3169
|
+
},
|
|
3170
|
+
});
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
2994
3173
|
assertSafeGitRef(ref, label) {
|
|
2995
3174
|
if (!/^[A-Za-z0-9._/-]+$/.test(ref)) {
|
|
2996
3175
|
throw new Error(`Invalid ${label}: ${ref}`);
|
|
@@ -3225,11 +3404,11 @@ export class Session {
|
|
|
3225
3404
|
async handleSetAgentModeRequest(agentId, modeId, requestId) {
|
|
3226
3405
|
this.sessionLogger.info({ agentId, modeId, requestId }, "session: set_agent_mode_request");
|
|
3227
3406
|
try {
|
|
3228
|
-
await setAgentModeCommand({ agentManager: this.agentManager }, { agentId, modeId });
|
|
3407
|
+
const result = await setAgentModeCommand({ agentManager: this.agentManager }, { agentId, modeId });
|
|
3229
3408
|
this.sessionLogger.info({ agentId, modeId, requestId }, "session: set_agent_mode_request success");
|
|
3230
3409
|
this.emit({
|
|
3231
3410
|
type: "set_agent_mode_response",
|
|
3232
|
-
payload: { requestId, agentId, accepted: true, error: null },
|
|
3411
|
+
payload: { requestId, agentId, accepted: true, error: null, notice: result.notice },
|
|
3233
3412
|
});
|
|
3234
3413
|
}
|
|
3235
3414
|
catch (error) {
|
|
@@ -3321,11 +3500,11 @@ export class Session {
|
|
|
3321
3500
|
async handleSetAgentThinkingRequest(agentId, thinkingOptionId, requestId) {
|
|
3322
3501
|
this.sessionLogger.info({ agentId, thinkingOptionId, requestId }, "session: set_agent_thinking_request");
|
|
3323
3502
|
try {
|
|
3324
|
-
await this.agentManager.setAgentThinkingOption(agentId, thinkingOptionId);
|
|
3503
|
+
const notice = await this.agentManager.setAgentThinkingOption(agentId, thinkingOptionId);
|
|
3325
3504
|
this.sessionLogger.info({ agentId, thinkingOptionId, requestId }, "session: set_agent_thinking_request success");
|
|
3326
3505
|
this.emit({
|
|
3327
3506
|
type: "set_agent_thinking_response",
|
|
3328
|
-
payload: { requestId, agentId, accepted: true, error: null },
|
|
3507
|
+
payload: { requestId, agentId, accepted: true, error: null, notice },
|
|
3329
3508
|
});
|
|
3330
3509
|
}
|
|
3331
3510
|
catch (error) {
|
|
@@ -4880,6 +5059,29 @@ export class Session {
|
|
|
4880
5059
|
const payload = this.buildStoredAgentPayload(record);
|
|
4881
5060
|
return this.isProviderVisibleToClient(payload.provider) ? payload : null;
|
|
4882
5061
|
}
|
|
5062
|
+
async resolveDelegationRootWorkspaceId(agentId) {
|
|
5063
|
+
const seen = new Set();
|
|
5064
|
+
let currentAgentId = agentId;
|
|
5065
|
+
while (true) {
|
|
5066
|
+
if (seen.has(currentAgentId)) {
|
|
5067
|
+
return null;
|
|
5068
|
+
}
|
|
5069
|
+
seen.add(currentAgentId);
|
|
5070
|
+
const live = this.agentManager.getAgent(currentAgentId);
|
|
5071
|
+
const source = live ?? (await this.agentStorage.get(currentAgentId));
|
|
5072
|
+
if (!source) {
|
|
5073
|
+
return null;
|
|
5074
|
+
}
|
|
5075
|
+
if ("archivedAt" in source && source.archivedAt) {
|
|
5076
|
+
return null;
|
|
5077
|
+
}
|
|
5078
|
+
const parentAgentId = getParentAgentIdFromLabels(source.labels);
|
|
5079
|
+
if (!parentAgentId) {
|
|
5080
|
+
return source.workspaceId ?? null;
|
|
5081
|
+
}
|
|
5082
|
+
currentAgentId = parentAgentId;
|
|
5083
|
+
}
|
|
5084
|
+
}
|
|
4883
5085
|
async buildActiveProjectPlacementsByWorkspaceId() {
|
|
4884
5086
|
const [persistedWorkspaces, persistedProjects] = await Promise.all([
|
|
4885
5087
|
this.workspaceRegistry.list(),
|
|
@@ -4962,7 +5164,9 @@ export class Session {
|
|
|
4962
5164
|
if (existing) {
|
|
4963
5165
|
return existing;
|
|
4964
5166
|
}
|
|
4965
|
-
const placementPromise =
|
|
5167
|
+
const placementPromise = request.type === "fetch_agent_history_request"
|
|
5168
|
+
? this.buildProjectPlacementForExistingWorkspaceProject(workspaceId)
|
|
5169
|
+
: this.buildProjectPlacementForWorkspaceId(workspaceId);
|
|
4966
5170
|
placementByWorkspaceId.set(workspaceId, placementPromise);
|
|
4967
5171
|
return placementPromise;
|
|
4968
5172
|
};
|
|
@@ -5285,6 +5489,26 @@ export class Session {
|
|
|
5285
5489
|
await this.workspaceRegistry.upsert(workspaceRecord);
|
|
5286
5490
|
return workspaceRecord;
|
|
5287
5491
|
}
|
|
5492
|
+
async findOrCreateProjectForDirectory(cwd) {
|
|
5493
|
+
const normalizedCwd = resolve(cwd);
|
|
5494
|
+
const checkout = await this.workspaceGitService.getCheckout(normalizedCwd);
|
|
5495
|
+
const membership = classifyDirectoryForProjectMembership({ cwd: normalizedCwd, checkout });
|
|
5496
|
+
const projectRecord = await this.resolveProjectRecordForPlacement({
|
|
5497
|
+
membership,
|
|
5498
|
+
timestamp: new Date().toISOString(),
|
|
5499
|
+
});
|
|
5500
|
+
await this.projectRegistry.upsert(projectRecord);
|
|
5501
|
+
return projectRecord;
|
|
5502
|
+
}
|
|
5503
|
+
buildProjectDescriptor(project) {
|
|
5504
|
+
return {
|
|
5505
|
+
projectId: project.projectId,
|
|
5506
|
+
projectDisplayName: resolveProjectDisplayName(project),
|
|
5507
|
+
projectCustomName: project.customName ?? null,
|
|
5508
|
+
projectRootPath: project.rootPath,
|
|
5509
|
+
projectKind: project.kind,
|
|
5510
|
+
};
|
|
5511
|
+
}
|
|
5288
5512
|
async reclassifyOrUnarchiveWorkspaceForDirectory(input) {
|
|
5289
5513
|
const checkout = await this.workspaceGitService.getCheckout(input.cwd);
|
|
5290
5514
|
const membership = classifyDirectoryForProjectMembership({ cwd: input.cwd, checkout });
|
|
@@ -5299,6 +5523,9 @@ export class Session {
|
|
|
5299
5523
|
if (input.workspace.projectId === projectId &&
|
|
5300
5524
|
input.workspace.kind === kind &&
|
|
5301
5525
|
input.workspace.displayName === displayName) {
|
|
5526
|
+
if (!input.project) {
|
|
5527
|
+
await this.projectRegistry.upsert(projectRecord);
|
|
5528
|
+
}
|
|
5302
5529
|
return this.ensureWorkspaceRecordUnarchived(input.workspace);
|
|
5303
5530
|
}
|
|
5304
5531
|
await this.projectRegistry.upsert(projectRecord);
|
|
@@ -5340,6 +5567,78 @@ export class Session {
|
|
|
5340
5567
|
updatedAt: input.timestamp,
|
|
5341
5568
|
};
|
|
5342
5569
|
}
|
|
5570
|
+
async unarchiveOwningWorkspaceForAgent(agentId) {
|
|
5571
|
+
const record = await this.agentStorage.get(agentId);
|
|
5572
|
+
if (!record?.workspaceId) {
|
|
5573
|
+
return;
|
|
5574
|
+
}
|
|
5575
|
+
const workspace = await this.workspaceRegistry.get(record.workspaceId);
|
|
5576
|
+
if (!workspace?.archivedAt) {
|
|
5577
|
+
return;
|
|
5578
|
+
}
|
|
5579
|
+
const directoryExists = await this.filesystem.isDirectory(record.cwd).catch(() => false);
|
|
5580
|
+
if (!directoryExists) {
|
|
5581
|
+
if (workspace.kind !== "worktree" || !workspace.branch) {
|
|
5582
|
+
return;
|
|
5583
|
+
}
|
|
5584
|
+
// Recreate the worktree directory from its kept branch BEFORE clearing
|
|
5585
|
+
// archivedAt — the reconciler re-archives workspaces whose directory is
|
|
5586
|
+
// missing, so the record must point at a real directory first.
|
|
5587
|
+
await this.recreateOwningWorktreeForRestore(workspace, workspace.branch);
|
|
5588
|
+
}
|
|
5589
|
+
await this.ensureWorkspaceRecordUnarchived(workspace);
|
|
5590
|
+
await this.emitWorkspaceUpdatesForWorkspaceIds([workspace.workspaceId]);
|
|
5591
|
+
}
|
|
5592
|
+
async recreateOwningWorktreeForRestore(workspace, branch) {
|
|
5593
|
+
const project = await this.projectRegistry.get(workspace.projectId);
|
|
5594
|
+
if (!project) {
|
|
5595
|
+
throw new WorktreeRequestError({
|
|
5596
|
+
code: "unknown",
|
|
5597
|
+
message: `Project ${workspace.projectId} not found for workspace ${workspace.workspaceId}`,
|
|
5598
|
+
});
|
|
5599
|
+
}
|
|
5600
|
+
const projectRootExists = await this.filesystem
|
|
5601
|
+
.isDirectory(project.rootPath)
|
|
5602
|
+
.catch(() => false);
|
|
5603
|
+
if (!projectRootExists) {
|
|
5604
|
+
throw new WorktreeRequestError({
|
|
5605
|
+
code: "unknown",
|
|
5606
|
+
message: `Project root is missing for ${workspace.projectId}: ${project.rootPath}`,
|
|
5607
|
+
});
|
|
5608
|
+
}
|
|
5609
|
+
// Archiving through the default path (scope "workspace", worktreePath only)
|
|
5610
|
+
// resolves repoRoot=null, so deletePaseoWorktree's `git worktree remove`/
|
|
5611
|
+
// `prune` is skipped and the admin registration survives — pinning the
|
|
5612
|
+
// branch as "already checked out". Prune here frees any stale registration
|
|
5613
|
+
// whose working dir is missing (a no-op for live worktrees) so the recreate
|
|
5614
|
+
// below succeeds regardless of how the worktree was archived.
|
|
5615
|
+
try {
|
|
5616
|
+
await runGitCommand(["worktree", "prune"], { cwd: project.rootPath, timeout: 30000 });
|
|
5617
|
+
}
|
|
5618
|
+
catch {
|
|
5619
|
+
// not critical; git will prune lazily
|
|
5620
|
+
}
|
|
5621
|
+
let result;
|
|
5622
|
+
try {
|
|
5623
|
+
result = await createWorktree({
|
|
5624
|
+
cwd: project.rootPath,
|
|
5625
|
+
worktreeSlug: basename(workspace.cwd),
|
|
5626
|
+
source: { kind: "checkout-branch", branchName: branch },
|
|
5627
|
+
runSetup: false,
|
|
5628
|
+
paseoHome: this.paseoHome,
|
|
5629
|
+
worktreesRoot: this.worktreesRoot,
|
|
5630
|
+
});
|
|
5631
|
+
}
|
|
5632
|
+
catch (error) {
|
|
5633
|
+
throw toWorktreeRequestError(error);
|
|
5634
|
+
}
|
|
5635
|
+
if (normalize(result.worktreePath) !== normalize(workspace.cwd)) {
|
|
5636
|
+
throw new WorktreeRequestError({
|
|
5637
|
+
code: "unknown",
|
|
5638
|
+
message: `Recreated worktree diverged from ${workspace.cwd}: ${result.worktreePath}`,
|
|
5639
|
+
});
|
|
5640
|
+
}
|
|
5641
|
+
}
|
|
5343
5642
|
async ensureWorkspaceRecordUnarchived(workspace) {
|
|
5344
5643
|
const project = await this.projectRegistry.get(workspace.projectId);
|
|
5345
5644
|
if (!workspace.archivedAt && (!project || !project.archivedAt)) {
|
|
@@ -5501,21 +5800,10 @@ export class Session {
|
|
|
5501
5800
|
this.shouldSkipWorkspaceGitWatchUpdate(workspaceId, nextWorkspace)) {
|
|
5502
5801
|
continue;
|
|
5503
5802
|
}
|
|
5504
|
-
|
|
5505
|
-
if (watchTarget && this.onBranchChanged) {
|
|
5506
|
-
const newBranchName = nextWorkspace?.name ?? null;
|
|
5507
|
-
if (newBranchName !== watchTarget.lastBranchName) {
|
|
5508
|
-
this.onBranchChanged(workspaceId, watchTarget.lastBranchName, newBranchName);
|
|
5509
|
-
}
|
|
5510
|
-
}
|
|
5511
|
-
this.rememberWorkspaceGitDescriptorState(workspaceId, nextWorkspace);
|
|
5803
|
+
this.recordWorkspaceGitDescriptorState(workspaceId, nextWorkspace);
|
|
5512
5804
|
if (!nextWorkspace) {
|
|
5513
5805
|
subscription.lastEmittedByWorkspaceId.delete(workspaceId);
|
|
5514
|
-
this.bufferOrEmitWorkspaceUpdate(subscription,
|
|
5515
|
-
kind: "remove",
|
|
5516
|
-
id: workspaceId,
|
|
5517
|
-
...(await this.resolveEmptyProjectForArchivedWorkspace(workspaceId)),
|
|
5518
|
-
});
|
|
5806
|
+
this.bufferOrEmitWorkspaceUpdate(subscription, await this.buildWorkspaceRemoveUpdatePayload(workspaceId, options?.removedProjectId));
|
|
5519
5807
|
continue;
|
|
5520
5808
|
}
|
|
5521
5809
|
const nextPayload = {
|
|
@@ -5534,16 +5822,36 @@ export class Session {
|
|
|
5534
5822
|
void this.reconcileAndEmitWorkspaceUpdates();
|
|
5535
5823
|
}
|
|
5536
5824
|
}
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5825
|
+
recordWorkspaceGitDescriptorState(workspaceId, nextWorkspace) {
|
|
5826
|
+
const watchTarget = this.resolveWorkspaceGitWatchTarget(workspaceId);
|
|
5827
|
+
if (watchTarget && this.onBranchChanged) {
|
|
5828
|
+
const newBranchName = nextWorkspace?.name ?? null;
|
|
5829
|
+
if (newBranchName !== watchTarget.lastBranchName) {
|
|
5830
|
+
this.onBranchChanged(workspaceId, watchTarget.lastBranchName, newBranchName);
|
|
5831
|
+
}
|
|
5832
|
+
}
|
|
5833
|
+
this.rememberWorkspaceGitDescriptorState(workspaceId, nextWorkspace);
|
|
5834
|
+
}
|
|
5835
|
+
async buildWorkspaceRemoveUpdatePayload(workspaceId, removedProjectId) {
|
|
5836
|
+
if (removedProjectId) {
|
|
5837
|
+
return { kind: "remove", id: workspaceId, removedProjectId };
|
|
5838
|
+
}
|
|
5839
|
+
return {
|
|
5840
|
+
kind: "remove",
|
|
5841
|
+
id: workspaceId,
|
|
5842
|
+
...(await this.resolveProjectWithoutActiveWorkspacesForArchivedWorkspace(workspaceId)),
|
|
5843
|
+
};
|
|
5844
|
+
}
|
|
5845
|
+
// When a workspace is archived its project may have no active workspaces left.
|
|
5846
|
+
// Resolve that project parent so the `remove` update can carry it, keeping the
|
|
5847
|
+
// sidebar in sync without a full re-hydration.
|
|
5848
|
+
async resolveProjectWithoutActiveWorkspacesForArchivedWorkspace(workspaceId) {
|
|
5541
5849
|
const archivedWorkspace = await this.workspaceRegistry.get(workspaceId);
|
|
5542
5850
|
if (!archivedWorkspace) {
|
|
5543
5851
|
return null;
|
|
5544
5852
|
}
|
|
5545
|
-
const
|
|
5546
|
-
return
|
|
5853
|
+
const projectWithoutActiveWorkspaces = (await this.workspaceDirectory.listEmptyProjects()).find((project) => project.projectId === archivedWorkspace.projectId);
|
|
5854
|
+
return projectWithoutActiveWorkspaces ? { emptyProject: projectWithoutActiveWorkspaces } : null;
|
|
5547
5855
|
}
|
|
5548
5856
|
async emitWorkspaceUpdateForTerminalContribution(event) {
|
|
5549
5857
|
// A terminal's activity contributes only to the workspace it carries. A
|
|
@@ -5989,6 +6297,58 @@ export class Session {
|
|
|
5989
6297
|
});
|
|
5990
6298
|
}
|
|
5991
6299
|
}
|
|
6300
|
+
async handleProjectAddRequest(request) {
|
|
6301
|
+
const requestedCwd = request.cwd;
|
|
6302
|
+
const cwd = expandTilde(requestedCwd);
|
|
6303
|
+
const directoryExists = await this.filesystem.isDirectory(cwd).catch(() => false);
|
|
6304
|
+
if (!directoryExists) {
|
|
6305
|
+
this.sessionLogger.info({ requestedCwd, resolvedCwd: cwd, reason: "directory_not_found" }, "Add project rejected");
|
|
6306
|
+
this.emit({
|
|
6307
|
+
type: "project.add.response",
|
|
6308
|
+
payload: {
|
|
6309
|
+
requestId: request.requestId,
|
|
6310
|
+
project: null,
|
|
6311
|
+
error: `Directory not found: ${cwd}`,
|
|
6312
|
+
errorCode: "directory_not_found",
|
|
6313
|
+
},
|
|
6314
|
+
});
|
|
6315
|
+
return;
|
|
6316
|
+
}
|
|
6317
|
+
try {
|
|
6318
|
+
const projectsBefore = new Map();
|
|
6319
|
+
for (const project of await this.projectRegistry.list()) {
|
|
6320
|
+
projectsBefore.set(project.projectId, project);
|
|
6321
|
+
}
|
|
6322
|
+
const project = await this.findOrCreateProjectForDirectory(cwd);
|
|
6323
|
+
this.sessionLogger.info({
|
|
6324
|
+
requestedCwd,
|
|
6325
|
+
resolvedCwd: cwd,
|
|
6326
|
+
projectId: project.projectId,
|
|
6327
|
+
projectKind: project.kind,
|
|
6328
|
+
projectTransition: describeRegistryTransition(projectsBefore.get(project.projectId) ?? null),
|
|
6329
|
+
}, "Project added");
|
|
6330
|
+
this.emit({
|
|
6331
|
+
type: "project.add.response",
|
|
6332
|
+
payload: {
|
|
6333
|
+
requestId: request.requestId,
|
|
6334
|
+
project: this.buildProjectDescriptor(project),
|
|
6335
|
+
error: null,
|
|
6336
|
+
},
|
|
6337
|
+
});
|
|
6338
|
+
}
|
|
6339
|
+
catch (error) {
|
|
6340
|
+
const message = error instanceof Error ? error.message : "Failed to add project";
|
|
6341
|
+
this.sessionLogger.error({ err: error, cwd }, "Failed to add project");
|
|
6342
|
+
this.emit({
|
|
6343
|
+
type: "project.add.response",
|
|
6344
|
+
payload: {
|
|
6345
|
+
requestId: request.requestId,
|
|
6346
|
+
project: null,
|
|
6347
|
+
error: message,
|
|
6348
|
+
},
|
|
6349
|
+
});
|
|
6350
|
+
}
|
|
6351
|
+
}
|
|
5992
6352
|
buildWorkspaceScriptPayloadSnapshot(workspaceId, workspaceDirectory) {
|
|
5993
6353
|
if (!this.serviceProxy || !this.scriptRuntimeStore) {
|
|
5994
6354
|
return [];
|