@getpaseo/server 0.1.97-beta.3 → 0.1.97

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.
Files changed (33) hide show
  1. package/dist/server/server/agent/agent-manager.js +1 -1
  2. package/dist/server/server/agent/agent-response-loop.js +9 -3
  3. package/dist/server/server/agent/agent-storage.d.ts +20 -240
  4. package/dist/server/server/agent/agent-storage.js +6 -6
  5. package/dist/server/server/agent/mcp-server.js +9 -4
  6. package/dist/server/server/agent/mcp-shared.d.ts +35 -179
  7. package/dist/server/server/agent/providers/claude/project-dir.js +9 -6
  8. package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +2 -22
  9. package/dist/server/server/agent/providers/codex/app-server-transport.d.ts +8 -118
  10. package/dist/server/server/agent/providers/generic-acp-agent.d.ts +1 -5
  11. package/dist/server/server/agent/providers/pi/agent.d.ts +1 -5
  12. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +391 -1261
  13. package/dist/server/server/agent/providers/tool-call-detail-primitives.js +26 -16
  14. package/dist/server/server/loop-service.d.ts +60 -359
  15. package/dist/server/server/migrations/backfill-workspace-id.migration.js +10 -6
  16. package/dist/server/server/package-version.d.ts +1 -7
  17. package/dist/server/server/paseo-worktree-service.js +15 -1
  18. package/dist/server/server/persisted-config.d.ts +138 -1009
  19. package/dist/server/server/persisted-config.js +1 -1
  20. package/dist/server/server/pid-lock.d.ts +1 -15
  21. package/dist/server/server/session.d.ts +6 -0
  22. package/dist/server/server/session.js +195 -25
  23. package/dist/server/server/speech/providers/local/sherpa/model-catalog.d.ts +2 -2
  24. package/dist/server/server/speech/speech-types.d.ts +9 -11
  25. package/dist/server/server/websocket-server.js +4 -0
  26. package/dist/server/server/workspace-registry.d.ts +17 -48
  27. package/dist/server/server/workspace-registry.js +9 -0
  28. package/dist/server/terminal/terminal-session-controller.d.ts +8 -0
  29. package/dist/server/terminal/terminal-session-controller.js +23 -3
  30. package/dist/server/utils/checkout-git.js +36 -76
  31. package/dist/server/utils/worktree-metadata.d.ts +7 -59
  32. package/dist/src/server/persisted-config.js +1 -1
  33. package/package.json +9 -9
@@ -213,7 +213,7 @@ export const PersistedConfigSchema = z
213
213
  // localhost service proxying remains always enabled.
214
214
  enabled: z.boolean().optional(),
215
215
  listen: z.string().optional(),
216
- publicBaseUrl: z.string().url().optional(),
216
+ publicBaseUrl: z.url().optional(),
217
217
  })
218
218
  .strict()
219
219
  .optional(),
@@ -6,21 +6,7 @@ export declare const pidLockInfoSchema: z.ZodObject<{
6
6
  uid: z.ZodNumber;
7
7
  listen: z.ZodNullable<z.ZodString>;
8
8
  desktopManaged: z.ZodOptional<z.ZodBoolean>;
9
- }, "strip", z.ZodTypeAny, {
10
- hostname: string;
11
- uid: number;
12
- startedAt: string;
13
- listen: string | null;
14
- pid: number;
15
- desktopManaged?: boolean | undefined;
16
- }, {
17
- hostname: string;
18
- uid: number;
19
- startedAt: string;
20
- listen: string | null;
21
- pid: number;
22
- desktopManaged?: boolean | undefined;
23
- }>;
9
+ }, z.core.$strip>;
24
10
  export interface PidLockInfo extends z.infer<typeof pidLockInfoSchema> {
25
11
  }
26
12
  export declare class PidLockError extends Error {
@@ -278,6 +278,7 @@ export declare class Session {
278
278
  private resolveWorkspaceDirectory;
279
279
  private buildProjectPlacementForWorkspace;
280
280
  private buildProjectPlacementForWorkspaceId;
281
+ private buildProjectPlacementForExistingWorkspaceProject;
281
282
  private forwardAgentUpdate;
282
283
  /**
283
284
  * Main entry point for processing session messages
@@ -312,6 +313,7 @@ export declare class Session {
312
313
  private unarchiveAgentByHandle;
313
314
  private handleUpdateAgentRequest;
314
315
  private handleProjectRenameRequest;
316
+ private handleProjectRemoveRequest;
315
317
  private handleWorkspaceTitleSetRequest;
316
318
  private toVoiceFeatureUnavailableContext;
317
319
  private resolveModeReadinessState;
@@ -484,6 +486,8 @@ export declare class Session {
484
486
  private createWorkspaceForDirectory;
485
487
  private reclassifyOrUnarchiveWorkspaceForDirectory;
486
488
  private resolveProjectRecordForPlacement;
489
+ private unarchiveOwningWorkspaceForAgent;
490
+ private recreateOwningWorktreeForRestore;
487
491
  private ensureWorkspaceRecordUnarchived;
488
492
  private createPaseoWorktree;
489
493
  private listActiveWorkspaceRefs;
@@ -492,6 +496,8 @@ export declare class Session {
492
496
  private reconcileAndEmitWorkspaceUpdates;
493
497
  private reconcileActiveWorkspaceRecords;
494
498
  private emitWorkspaceUpdatesForWorkspaceIds;
499
+ private recordWorkspaceGitDescriptorState;
500
+ private buildWorkspaceRemoveUpdatePayload;
495
501
  private resolveEmptyProjectForArchivedWorkspace;
496
502
  private emitWorkspaceUpdateForTerminalContribution;
497
503
  private emitWorkspaceUpdateForCwd;
@@ -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";
@@ -50,7 +50,7 @@ import { isVoicePermissionAllowed } from "./voice-permission-policy.js";
50
50
  import { listDirectoryEntries, readExplorerFile, readExplorerFileBytes, getDownloadableFileInfo, } from "./file-explorer/service.js";
51
51
  import { readPaseoConfigForEdit, writePaseoConfigForEdit, } from "../utils/paseo-config-file.js";
52
52
  import { buildMetadataPrompt } from "../utils/build-metadata-prompt.js";
53
- import { archivePersistedWorkspaceRecord } from "./workspace-archive-service.js";
53
+ import { archivePersistedWorkspaceRecord, archiveWorkspaceContents, } from "./workspace-archive-service.js";
54
54
  import { WorkspaceReconciliationService } from "./workspace-reconciliation-service.js";
55
55
  import { checkoutResolvedBranch, commitChanges, mergeToBase, mergeFromBase, pullCurrentBranch, pushCurrentBranch, createPullRequest, renameCurrentBranch as renameCurrentBranchDefault, } from "../utils/checkout-git.js";
56
56
  import { validateBranchSlug } from "@getpaseo/protocol/branch-slug";
@@ -70,7 +70,9 @@ import { attemptFirstAgentBranchAutoName, createLocalCheckoutWorkspace, createPa
70
70
  import { generateBranchNameFromFirstAgentContext, } from "./worktree-branch-name-generator.js";
71
71
  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
72
  import { archiveByScope } from "./workspace-archive-service.js";
73
- import { toWorktreeWireError } from "./worktree-errors.js";
73
+ import { WorktreeRequestError, toWorktreeRequestError, toWorktreeWireError, } from "./worktree-errors.js";
74
+ import { createWorktree } from "../utils/worktree.js";
75
+ import { runGitCommand } from "../utils/run-git-command.js";
74
76
  import { CreateAgentLifecycleDispatch } from "./agent/create-agent-lifecycle-dispatch.js";
75
77
  const WORKSPACE_GIT_WATCH_REMOVED_STATE_KEY = "__removed__";
76
78
  async function resolveKnownProjectRootForConfig(input) {
@@ -223,7 +225,7 @@ const PCM_BITS_PER_SAMPLE = 16;
223
225
  const PCM_BYTES_PER_MS = (PCM_SAMPLE_RATE * PCM_CHANNELS * (PCM_BITS_PER_SAMPLE / 8)) / 1000;
224
226
  const MIN_STREAMING_SEGMENT_DURATION_MS = 1000;
225
227
  const MIN_STREAMING_SEGMENT_BYTES = Math.round(PCM_BYTES_PER_MS * MIN_STREAMING_SEGMENT_DURATION_MS);
226
- const AgentIdSchema = z.string().uuid();
228
+ const AgentIdSchema = z.guid();
227
229
  const nodeSessionFileSystem = {
228
230
  async isDirectory(path) {
229
231
  const stats = await stat(path).catch(() => null);
@@ -382,12 +384,7 @@ export class Session {
382
384
  hasBinaryChannel: () => this.onBinaryMessage !== null,
383
385
  isPathWithinRoot: (rootPath, candidatePath) => this.isPathWithinRoot(rootPath, candidatePath),
384
386
  sessionLogger: this.sessionLogger,
385
- listTerminalWorkspaceRoots: async () => {
386
- const workspaces = await this.workspaceRegistry.list();
387
- return workspaces
388
- .filter((workspace) => !workspace.archivedAt)
389
- .map((workspace) => workspace.cwd);
390
- },
387
+ listTerminalWorkspaceRefs: () => this.listActiveWorkspaceRefs(),
391
388
  clientSupportsWrapReflow: () => this.clientCapabilities.has(CLIENT_CAPS.terminalReflowableSnapshot),
392
389
  getClientBufferedAmount: () => this.getTransportBufferedAmount(),
393
390
  });
@@ -911,6 +908,7 @@ export class Session {
911
908
  return {
912
909
  projectKey: project.projectId,
913
910
  projectName: resolveProjectDisplayName(project),
911
+ workspaceName: resolveWorkspaceDisplayName(workspace),
914
912
  checkout,
915
913
  };
916
914
  }
@@ -920,6 +918,15 @@ export class Session {
920
918
  return null;
921
919
  return this.buildProjectPlacementForWorkspace(workspace);
922
920
  }
921
+ async buildProjectPlacementForExistingWorkspaceProject(workspaceId) {
922
+ const workspace = await this.workspaceRegistry.get(workspaceId);
923
+ if (!workspace)
924
+ return null;
925
+ const project = await this.projectRegistry.get(workspace.projectId);
926
+ if (!project)
927
+ return null;
928
+ return this.buildProjectPlacementForWorkspace(workspace, project);
929
+ }
923
930
  async forwardAgentUpdate(agent) {
924
931
  try {
925
932
  const subscription = this.agentUpdatesSubscription;
@@ -1345,6 +1352,8 @@ export class Session {
1345
1352
  return this.handleOpenProjectRequest(msg);
1346
1353
  case "archive_workspace_request":
1347
1354
  return this.handleArchiveWorkspaceRequest(msg);
1355
+ case "project.remove.request":
1356
+ return this.handleProjectRemoveRequest(msg);
1348
1357
  case "workspace.create.request":
1349
1358
  return this.handleWorkspaceCreateRequest(msg);
1350
1359
  case "workspace.clear_attention.request":
@@ -1764,6 +1773,80 @@ export class Session {
1764
1773
  });
1765
1774
  }
1766
1775
  }
1776
+ async handleProjectRemoveRequest(request) {
1777
+ const { projectId, requestId } = request;
1778
+ this.sessionLogger.info({ projectId, requestId }, "session: project.remove.request");
1779
+ try {
1780
+ const projectWorkspaces = (await this.workspaceRegistry.list()).filter((workspace) => workspace.projectId === projectId);
1781
+ const activeWorkspaceIds = projectWorkspaces
1782
+ .filter((workspace) => !workspace.archivedAt)
1783
+ .map((workspace) => workspace.workspaceId);
1784
+ if (activeWorkspaceIds.length > 0) {
1785
+ this.markWorkspaceArchiving(activeWorkspaceIds, new Date().toISOString());
1786
+ await this.emitWorkspaceUpdatesForWorkspaceIds(activeWorkspaceIds, {
1787
+ skipReconcile: true,
1788
+ });
1789
+ }
1790
+ const removedWorkspaceIds = [];
1791
+ try {
1792
+ for (const workspaceId of activeWorkspaceIds) {
1793
+ await archiveWorkspaceContents({
1794
+ agentManager: this.agentManager,
1795
+ agentStorage: this.agentStorage,
1796
+ killTerminalsForWorkspace: (id) => this.terminalController.killTerminalsForWorkspace(id),
1797
+ sessionLogger: this.sessionLogger,
1798
+ }, workspaceId);
1799
+ await this.archiveWorkspaceRecord(workspaceId);
1800
+ removedWorkspaceIds.push(workspaceId);
1801
+ }
1802
+ await this.projectRegistry.remove(projectId);
1803
+ }
1804
+ finally {
1805
+ if (activeWorkspaceIds.length > 0) {
1806
+ this.clearWorkspaceArchiving(activeWorkspaceIds);
1807
+ }
1808
+ }
1809
+ const updateIds = removedWorkspaceIds.length > 0
1810
+ ? removedWorkspaceIds
1811
+ : [projectWorkspaces[0]?.workspaceId ?? projectId];
1812
+ await this.emitWorkspaceUpdatesForWorkspaceIds(updateIds, {
1813
+ skipReconcile: true,
1814
+ removedProjectId: projectId,
1815
+ });
1816
+ this.emit({
1817
+ type: "project.remove.response",
1818
+ payload: {
1819
+ requestId,
1820
+ projectId,
1821
+ accepted: true,
1822
+ removedWorkspaceIds,
1823
+ error: null,
1824
+ },
1825
+ });
1826
+ }
1827
+ catch (error) {
1828
+ this.sessionLogger.error({ err: error, projectId, requestId }, "session: project.remove.request error");
1829
+ this.emit({
1830
+ type: "activity_log",
1831
+ payload: {
1832
+ id: uuidv4(),
1833
+ timestamp: new Date(),
1834
+ type: "error",
1835
+ content: `Failed to remove project: ${getErrorMessage(error)}`,
1836
+ },
1837
+ });
1838
+ this.emit({
1839
+ type: "project.remove.response",
1840
+ payload: {
1841
+ requestId,
1842
+ projectId,
1843
+ accepted: false,
1844
+ removedWorkspaceIds: [],
1845
+ error: getErrorMessageOr(error, "Failed to remove project"),
1846
+ },
1847
+ });
1848
+ }
1849
+ }
1767
1850
  async handleWorkspaceTitleSetRequest(workspaceId, title, requestId) {
1768
1851
  this.sessionLogger.info({ workspaceId, requestId, hasTitle: typeof title === "string" }, "session: workspace.title.set.request");
1769
1852
  try {
@@ -2412,6 +2495,7 @@ export class Session {
2412
2495
  this.sessionLogger.info({ agentId }, `Refreshing agent ${agentId} from persistence`);
2413
2496
  try {
2414
2497
  await unarchiveAgentState(this.agentStorage, this.agentManager, agentId);
2498
+ await this.unarchiveOwningWorkspaceForAgent(agentId);
2415
2499
  let snapshot;
2416
2500
  const existing = this.agentManager.getAgent(agentId);
2417
2501
  if (existing) {
@@ -2460,7 +2544,7 @@ export class Session {
2460
2544
  requestId,
2461
2545
  requestType: msg.type,
2462
2546
  error: message,
2463
- code: "agent_refresh_failed",
2547
+ code: error instanceof WorktreeRequestError ? error.code : "agent_refresh_failed",
2464
2548
  },
2465
2549
  });
2466
2550
  }
@@ -4962,7 +5046,9 @@ export class Session {
4962
5046
  if (existing) {
4963
5047
  return existing;
4964
5048
  }
4965
- const placementPromise = this.buildProjectPlacementForWorkspaceId(workspaceId);
5049
+ const placementPromise = request.type === "fetch_agent_history_request"
5050
+ ? this.buildProjectPlacementForExistingWorkspaceProject(workspaceId)
5051
+ : this.buildProjectPlacementForWorkspaceId(workspaceId);
4966
5052
  placementByWorkspaceId.set(workspaceId, placementPromise);
4967
5053
  return placementPromise;
4968
5054
  };
@@ -5299,6 +5385,9 @@ export class Session {
5299
5385
  if (input.workspace.projectId === projectId &&
5300
5386
  input.workspace.kind === kind &&
5301
5387
  input.workspace.displayName === displayName) {
5388
+ if (!input.project) {
5389
+ await this.projectRegistry.upsert(projectRecord);
5390
+ }
5302
5391
  return this.ensureWorkspaceRecordUnarchived(input.workspace);
5303
5392
  }
5304
5393
  await this.projectRegistry.upsert(projectRecord);
@@ -5340,6 +5429,78 @@ export class Session {
5340
5429
  updatedAt: input.timestamp,
5341
5430
  };
5342
5431
  }
5432
+ async unarchiveOwningWorkspaceForAgent(agentId) {
5433
+ const record = await this.agentStorage.get(agentId);
5434
+ if (!record?.workspaceId) {
5435
+ return;
5436
+ }
5437
+ const workspace = await this.workspaceRegistry.get(record.workspaceId);
5438
+ if (!workspace?.archivedAt) {
5439
+ return;
5440
+ }
5441
+ const directoryExists = await this.filesystem.isDirectory(record.cwd).catch(() => false);
5442
+ if (!directoryExists) {
5443
+ if (workspace.kind !== "worktree" || !workspace.branch) {
5444
+ return;
5445
+ }
5446
+ // Recreate the worktree directory from its kept branch BEFORE clearing
5447
+ // archivedAt — the reconciler re-archives workspaces whose directory is
5448
+ // missing, so the record must point at a real directory first.
5449
+ await this.recreateOwningWorktreeForRestore(workspace, workspace.branch);
5450
+ }
5451
+ await this.ensureWorkspaceRecordUnarchived(workspace);
5452
+ await this.emitWorkspaceUpdatesForWorkspaceIds([workspace.workspaceId]);
5453
+ }
5454
+ async recreateOwningWorktreeForRestore(workspace, branch) {
5455
+ const project = await this.projectRegistry.get(workspace.projectId);
5456
+ if (!project) {
5457
+ throw new WorktreeRequestError({
5458
+ code: "unknown",
5459
+ message: `Project ${workspace.projectId} not found for workspace ${workspace.workspaceId}`,
5460
+ });
5461
+ }
5462
+ const projectRootExists = await this.filesystem
5463
+ .isDirectory(project.rootPath)
5464
+ .catch(() => false);
5465
+ if (!projectRootExists) {
5466
+ throw new WorktreeRequestError({
5467
+ code: "unknown",
5468
+ message: `Project root is missing for ${workspace.projectId}: ${project.rootPath}`,
5469
+ });
5470
+ }
5471
+ // Archiving through the default path (scope "workspace", worktreePath only)
5472
+ // resolves repoRoot=null, so deletePaseoWorktree's `git worktree remove`/
5473
+ // `prune` is skipped and the admin registration survives — pinning the
5474
+ // branch as "already checked out". Prune here frees any stale registration
5475
+ // whose working dir is missing (a no-op for live worktrees) so the recreate
5476
+ // below succeeds regardless of how the worktree was archived.
5477
+ try {
5478
+ await runGitCommand(["worktree", "prune"], { cwd: project.rootPath, timeout: 30000 });
5479
+ }
5480
+ catch {
5481
+ // not critical; git will prune lazily
5482
+ }
5483
+ let result;
5484
+ try {
5485
+ result = await createWorktree({
5486
+ cwd: project.rootPath,
5487
+ worktreeSlug: basename(workspace.cwd),
5488
+ source: { kind: "checkout-branch", branchName: branch },
5489
+ runSetup: false,
5490
+ paseoHome: this.paseoHome,
5491
+ worktreesRoot: this.worktreesRoot,
5492
+ });
5493
+ }
5494
+ catch (error) {
5495
+ throw toWorktreeRequestError(error);
5496
+ }
5497
+ if (normalize(result.worktreePath) !== normalize(workspace.cwd)) {
5498
+ throw new WorktreeRequestError({
5499
+ code: "unknown",
5500
+ message: `Recreated worktree diverged from ${workspace.cwd}: ${result.worktreePath}`,
5501
+ });
5502
+ }
5503
+ }
5343
5504
  async ensureWorkspaceRecordUnarchived(workspace) {
5344
5505
  const project = await this.projectRegistry.get(workspace.projectId);
5345
5506
  if (!workspace.archivedAt && (!project || !project.archivedAt)) {
@@ -5501,21 +5662,10 @@ export class Session {
5501
5662
  this.shouldSkipWorkspaceGitWatchUpdate(workspaceId, nextWorkspace)) {
5502
5663
  continue;
5503
5664
  }
5504
- const watchTarget = this.resolveWorkspaceGitWatchTarget(workspaceId);
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);
5665
+ this.recordWorkspaceGitDescriptorState(workspaceId, nextWorkspace);
5512
5666
  if (!nextWorkspace) {
5513
5667
  subscription.lastEmittedByWorkspaceId.delete(workspaceId);
5514
- this.bufferOrEmitWorkspaceUpdate(subscription, {
5515
- kind: "remove",
5516
- id: workspaceId,
5517
- ...(await this.resolveEmptyProjectForArchivedWorkspace(workspaceId)),
5518
- });
5668
+ this.bufferOrEmitWorkspaceUpdate(subscription, await this.buildWorkspaceRemoveUpdatePayload(workspaceId, options?.removedProjectId));
5519
5669
  continue;
5520
5670
  }
5521
5671
  const nextPayload = {
@@ -5534,6 +5684,26 @@ export class Session {
5534
5684
  void this.reconcileAndEmitWorkspaceUpdates();
5535
5685
  }
5536
5686
  }
5687
+ recordWorkspaceGitDescriptorState(workspaceId, nextWorkspace) {
5688
+ const watchTarget = this.resolveWorkspaceGitWatchTarget(workspaceId);
5689
+ if (watchTarget && this.onBranchChanged) {
5690
+ const newBranchName = nextWorkspace?.name ?? null;
5691
+ if (newBranchName !== watchTarget.lastBranchName) {
5692
+ this.onBranchChanged(workspaceId, watchTarget.lastBranchName, newBranchName);
5693
+ }
5694
+ }
5695
+ this.rememberWorkspaceGitDescriptorState(workspaceId, nextWorkspace);
5696
+ }
5697
+ async buildWorkspaceRemoveUpdatePayload(workspaceId, removedProjectId) {
5698
+ if (removedProjectId) {
5699
+ return { kind: "remove", id: workspaceId, removedProjectId };
5700
+ }
5701
+ return {
5702
+ kind: "remove",
5703
+ id: workspaceId,
5704
+ ...(await this.resolveEmptyProjectForArchivedWorkspace(workspaceId)),
5705
+ };
5706
+ }
5537
5707
  // When a workspace is archived its project may become empty. Resolve the
5538
5708
  // now-empty project parent so the `remove` update can carry it, keeping the
5539
5709
  // sidebar's empty project row in sync without a full re-hydration.
@@ -45,8 +45,8 @@ export declare const LOCAL_STT_MODEL_IDS: LocalSttModelId[];
45
45
  export declare const LOCAL_TTS_MODEL_IDS: LocalTtsModelId[];
46
46
  export declare const DEFAULT_LOCAL_STT_MODEL: LocalSttModelId;
47
47
  export declare const DEFAULT_LOCAL_TTS_MODEL: "kokoro-en-v0_19";
48
- export declare const LocalSttModelIdSchema: z.ZodType<LocalSttModelId, z.ZodTypeDef, string>;
49
- export declare const LocalTtsModelIdSchema: z.ZodType<"kokoro-en-v0_19", z.ZodTypeDef, string>;
48
+ export declare const LocalSttModelIdSchema: z.ZodType<LocalSttModelId, string, z.core.$ZodTypeInternals<LocalSttModelId, string>>;
49
+ export declare const LocalTtsModelIdSchema: z.ZodType<"kokoro-en-v0_19", string, z.core.$ZodTypeInternals<"kokoro-en-v0_19", string>>;
50
50
  export type SherpaOnnxModelSpec = SherpaOnnxCatalogEntry & {
51
51
  id: SherpaOnnxModelId;
52
52
  };
@@ -1,19 +1,17 @@
1
1
  import { z } from "zod";
2
- export declare const SpeechProviderIdSchema: z.ZodEnum<["openai", "local"]>;
2
+ export declare const SpeechProviderIdSchema: z.ZodEnum<{
3
+ local: "local";
4
+ openai: "openai";
5
+ }>;
3
6
  export type SpeechProviderId = z.infer<typeof SpeechProviderIdSchema>;
4
7
  export declare const RequestedSpeechProviderSchema: z.ZodObject<{
5
- provider: z.ZodEnum<["openai", "local"]>;
8
+ provider: z.ZodEnum<{
9
+ local: "local";
10
+ openai: "openai";
11
+ }>;
6
12
  explicit: z.ZodBoolean;
7
13
  enabled: z.ZodOptional<z.ZodBoolean>;
8
- }, "strip", z.ZodTypeAny, {
9
- provider: "local" | "openai";
10
- explicit: boolean;
11
- enabled?: boolean | undefined;
12
- }, {
13
- provider: "local" | "openai";
14
- explicit: boolean;
15
- enabled?: boolean | undefined;
16
- }>;
14
+ }, z.core.$strip>;
17
15
  export type RequestedSpeechProvider = z.infer<typeof RequestedSpeechProviderSchema>;
18
16
  export interface RequestedSpeechProviders {
19
17
  dictationStt: RequestedSpeechProvider;
@@ -807,6 +807,10 @@ export class VoiceAssistantWebSocketServer {
807
807
  checkoutRefresh: true,
808
808
  // COMPAT(workspaceMultiplicity): added in v0.1.97, drop the gate when floor >= v0.1.97
809
809
  workspaceMultiplicity: true,
810
+ // COMPAT(projectRemove): added in v0.1.97, drop the gate when floor >= v0.1.97.
811
+ projectRemove: true,
812
+ // COMPAT(worktreeRestore): added in v0.1.97, drop the gate when floor >= v0.1.97
813
+ worktreeRestore: true,
810
814
  },
811
815
  };
812
816
  }
@@ -4,65 +4,33 @@ import type { PersistedProjectKind, PersistedWorkspaceKind } from "./workspace-r
4
4
  declare const PersistedProjectRecordSchema: z.ZodObject<{
5
5
  projectId: z.ZodString;
6
6
  rootPath: z.ZodString;
7
- kind: z.ZodEnum<["git", "non_git"]>;
7
+ kind: z.ZodEnum<{
8
+ git: "git";
9
+ non_git: "non_git";
10
+ }>;
8
11
  displayName: z.ZodString;
9
- customName: z.ZodEffects<z.ZodOptional<z.ZodNullable<z.ZodString>>, string | null, string | null | undefined>;
12
+ customName: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
10
13
  createdAt: z.ZodString;
11
14
  updatedAt: z.ZodString;
12
15
  archivedAt: z.ZodNullable<z.ZodString>;
13
- }, "strip", z.ZodTypeAny, {
14
- kind: "git" | "non_git";
15
- createdAt: string;
16
- updatedAt: string;
17
- archivedAt: string | null;
18
- projectId: string;
19
- displayName: string;
20
- rootPath: string;
21
- customName: string | null;
22
- }, {
23
- kind: "git" | "non_git";
24
- createdAt: string;
25
- updatedAt: string;
26
- archivedAt: string | null;
27
- projectId: string;
28
- displayName: string;
29
- rootPath: string;
30
- customName?: string | null | undefined;
31
- }>;
16
+ }, z.core.$strip>;
32
17
  declare const PersistedWorkspaceRecordSchema: z.ZodObject<{
33
18
  workspaceId: z.ZodString;
34
19
  projectId: z.ZodString;
35
20
  cwd: z.ZodString;
36
- kind: z.ZodEnum<["local_checkout", "worktree", "directory"]>;
21
+ kind: z.ZodEnum<{
22
+ local_checkout: "local_checkout";
23
+ worktree: "worktree";
24
+ directory: "directory";
25
+ }>;
37
26
  displayName: z.ZodString;
38
- title: z.ZodEffects<z.ZodOptional<z.ZodNullable<z.ZodString>>, string | null, string | null | undefined>;
39
- branch: z.ZodEffects<z.ZodOptional<z.ZodNullable<z.ZodString>>, string | null, string | null | undefined>;
27
+ title: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
28
+ branch: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
29
+ baseBranch: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
40
30
  createdAt: z.ZodString;
41
31
  updatedAt: z.ZodString;
42
32
  archivedAt: z.ZodNullable<z.ZodString>;
43
- }, "strip", z.ZodTypeAny, {
44
- kind: "local_checkout" | "worktree" | "directory";
45
- workspaceId: string;
46
- cwd: string;
47
- title: string | null;
48
- createdAt: string;
49
- updatedAt: string;
50
- archivedAt: string | null;
51
- projectId: string;
52
- displayName: string;
53
- branch: string | null;
54
- }, {
55
- kind: "local_checkout" | "worktree" | "directory";
56
- workspaceId: string;
57
- cwd: string;
58
- createdAt: string;
59
- updatedAt: string;
60
- archivedAt: string | null;
61
- projectId: string;
62
- displayName: string;
63
- title?: string | null | undefined;
64
- branch?: string | null | undefined;
65
- }>;
33
+ }, z.core.$strip>;
66
34
  export type PersistedProjectRecord = z.infer<typeof PersistedProjectRecordSchema>;
67
35
  export type PersistedWorkspaceRecord = z.infer<typeof PersistedWorkspaceRecordSchema>;
68
36
  export interface ProjectRegistry {
@@ -95,7 +63,7 @@ declare class FileBackedRegistry<TRecord extends RegistryRecord> {
95
63
  constructor(options: {
96
64
  filePath: string;
97
65
  logger: Logger;
98
- schema: z.ZodType<TRecord, z.ZodTypeDef, unknown>;
66
+ schema: z.ZodType<TRecord, unknown>;
99
67
  getId: (record: TRecord) => string;
100
68
  component: string;
101
69
  });
@@ -135,6 +103,7 @@ export declare function createPersistedWorkspaceRecord(input: {
135
103
  displayName: string;
136
104
  title?: string | null;
137
105
  branch?: string | null;
106
+ baseBranch?: string | null;
138
107
  createdAt: string;
139
108
  updatedAt: string;
140
109
  archivedAt?: string | null;
@@ -39,6 +39,14 @@ const PersistedWorkspaceRecordSchema = z.object({
39
39
  .nullable()
40
40
  .optional()
41
41
  .transform((value) => value ?? null),
42
+ // The base branch the worktree was created from (normalized like worktree.json's
43
+ // baseRefName). Only worktree workspaces carry a base branch; checkout-branch
44
+ // worktrees and directory/local_checkout workspaces leave it null.
45
+ baseBranch: z
46
+ .string()
47
+ .nullable()
48
+ .optional()
49
+ .transform((value) => value ?? null),
42
50
  createdAt: z.string(),
43
51
  updatedAt: z.string(),
44
52
  archivedAt: z.string().nullable(),
@@ -170,6 +178,7 @@ export function createPersistedWorkspaceRecord(input) {
170
178
  ...input,
171
179
  title: input.title ?? null,
172
180
  branch: input.branch ?? null,
181
+ baseBranch: input.baseBranch ?? null,
173
182
  archivedAt: input.archivedAt ?? null,
174
183
  });
175
184
  }
@@ -9,10 +9,15 @@ export interface TerminalSessionControllerOptions {
9
9
  hasBinaryChannel: () => boolean;
10
10
  isPathWithinRoot: (rootPath: string, candidatePath: string) => boolean;
11
11
  sessionLogger: pino.Logger;
12
+ listTerminalWorkspaceRefs?: () => Promise<readonly TerminalWorkspaceRef[]>;
12
13
  listTerminalWorkspaceRoots?: () => Promise<readonly string[]>;
13
14
  clientSupportsWrapReflow?: () => boolean;
14
15
  getClientBufferedAmount?: () => number | null;
15
16
  }
17
+ interface TerminalWorkspaceRef {
18
+ workspaceId: string;
19
+ cwd: string;
20
+ }
16
21
  export interface TerminalSessionControllerMetrics {
17
22
  directorySubscriptionCount: number;
18
23
  streamSubscriptionCount: number;
@@ -24,6 +29,7 @@ export declare class TerminalSessionController {
24
29
  private readonly hasBinaryChannel;
25
30
  private readonly isPathWithinRoot;
26
31
  private readonly sessionLogger;
32
+ private readonly listTerminalWorkspaceRefs;
27
33
  private readonly listTerminalWorkspaceRoots;
28
34
  private readonly clientSupportsWrapReflow;
29
35
  private readonly getClientBufferedAmount;
@@ -59,6 +65,7 @@ export declare class TerminalSessionController {
59
65
  private resolveTerminalOwnerRoot;
60
66
  private isSamePath;
61
67
  private handleCreateTerminalRequest;
68
+ private resolveLegacyTerminalWorkspaceId;
62
69
  private handleRenameTerminalRequest;
63
70
  private handleSubscribeTerminalRequest;
64
71
  private handleUnsubscribeTerminalRequest;
@@ -74,4 +81,5 @@ export declare class TerminalSessionController {
74
81
  private allocateSlot;
75
82
  private detachStream;
76
83
  }
84
+ export {};
77
85
  //# sourceMappingURL=terminal-session-controller.d.ts.map
@@ -34,7 +34,10 @@ export class TerminalSessionController {
34
34
  this.hasBinaryChannel = options.hasBinaryChannel;
35
35
  this.isPathWithinRoot = options.isPathWithinRoot;
36
36
  this.sessionLogger = options.sessionLogger;
37
- this.listTerminalWorkspaceRoots = options.listTerminalWorkspaceRoots ?? (async () => []);
37
+ this.listTerminalWorkspaceRefs = options.listTerminalWorkspaceRefs ?? (async () => []);
38
+ this.listTerminalWorkspaceRoots =
39
+ options.listTerminalWorkspaceRoots ??
40
+ (async () => (await this.listTerminalWorkspaceRefs()).map((workspace) => workspace.cwd));
38
41
  this.clientSupportsWrapReflow = options.clientSupportsWrapReflow ?? (() => false);
39
42
  this.getClientBufferedAmount = options.getClientBufferedAmount ?? (() => 0);
40
43
  }
@@ -331,7 +334,8 @@ export class TerminalSessionController {
331
334
  });
332
335
  return;
333
336
  }
334
- if (!msg.workspaceId) {
337
+ const workspaceId = msg.workspaceId ?? (await this.resolveLegacyTerminalWorkspaceId(msg.cwd));
338
+ if (!workspaceId) {
335
339
  this.emit({
336
340
  type: "create_terminal_response",
337
341
  payload: {
@@ -344,7 +348,7 @@ export class TerminalSessionController {
344
348
  }
345
349
  const session = await this.terminalManager.createTerminal({
346
350
  cwd: msg.cwd,
347
- workspaceId: msg.workspaceId,
351
+ workspaceId,
348
352
  name: msg.name,
349
353
  command: msg.command,
350
354
  args: msg.args,
@@ -378,6 +382,22 @@ export class TerminalSessionController {
378
382
  });
379
383
  }
380
384
  }
385
+ async resolveLegacyTerminalWorkspaceId(cwd) {
386
+ const workspaceRefs = await this.listTerminalWorkspaceRefs();
387
+ if (workspaceRefs.length === 0) {
388
+ return null;
389
+ }
390
+ const exactMatch = workspaceRefs.find((workspace) => this.isSamePath(workspace.cwd, cwd));
391
+ if (exactMatch) {
392
+ return exactMatch.workspaceId;
393
+ }
394
+ const ownerRoot = this.resolveTerminalOwnerRoot(cwd, workspaceRefs.map((workspace) => workspace.cwd));
395
+ if (!ownerRoot) {
396
+ return null;
397
+ }
398
+ return (workspaceRefs.find((workspace) => this.isSamePath(workspace.cwd, ownerRoot))?.workspaceId ??
399
+ null);
400
+ }
381
401
  async handleRenameTerminalRequest(msg) {
382
402
  const respond = (success, error) => {
383
403
  this.emit({