@getpaseo/server 0.1.88 → 0.1.90

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 (94) hide show
  1. package/dist/server/server/agent/agent-manager.js +4 -1
  2. package/dist/server/server/agent/agent-prompt.js +4 -1
  3. package/dist/server/server/agent/agent-sdk-types.d.ts +1 -0
  4. package/dist/server/server/agent/agent-storage.d.ts +22 -22
  5. package/dist/server/server/agent/agent-storage.js +2 -9
  6. package/dist/server/server/agent/create-agent/create.d.ts +2 -0
  7. package/dist/server/server/agent/create-agent/create.js +26 -7
  8. package/dist/server/server/agent/create-agent-lifecycle-dispatch.d.ts +1 -0
  9. package/dist/server/server/agent/create-agent-lifecycle-dispatch.js +4 -0
  10. package/dist/server/server/agent/create-agent-mode.d.ts +3 -8
  11. package/dist/server/server/agent/create-agent-mode.js +16 -2
  12. package/dist/server/server/agent/import-sessions.js +1 -1
  13. package/dist/server/server/agent/mcp-server.d.ts +1 -0
  14. package/dist/server/server/agent/mcp-server.js +113 -70
  15. package/dist/server/server/agent/provider-snapshot-manager.d.ts +2 -1
  16. package/dist/server/server/agent/provider-snapshot-manager.js +18 -2
  17. package/dist/server/server/agent/providers/acp-agent.d.ts +3 -3
  18. package/dist/server/server/agent/providers/acp-agent.js +18 -13
  19. package/dist/server/server/agent/providers/codex-app-server-agent.js +16 -22
  20. package/dist/server/server/agent/providers/mock-load-test-agent.d.ts +2 -0
  21. package/dist/server/server/agent/providers/mock-load-test-agent.js +69 -2
  22. package/dist/server/server/agent/providers/opencode-agent.js +19 -8
  23. package/dist/server/server/agent/providers/pi/agent.js +13 -0
  24. package/dist/server/server/agent/providers/pi/rpc-types.d.ts +3 -0
  25. package/dist/server/server/agent/timeline-projection.js +30 -1
  26. package/dist/server/server/atomic-file.d.ts +3 -0
  27. package/dist/server/server/atomic-file.js +19 -0
  28. package/dist/server/server/auto-archive-on-merge/archive-if-safe.d.ts +1 -0
  29. package/dist/server/server/auto-archive-on-merge/archive-if-safe.js +10 -2
  30. package/dist/server/server/bootstrap.d.ts +7 -2
  31. package/dist/server/server/bootstrap.js +154 -115
  32. package/dist/server/server/chat/chat-service.js +2 -4
  33. package/dist/server/server/config.js +41 -0
  34. package/dist/server/server/daemon-keypair.js +2 -2
  35. package/dist/server/server/loop-service.d.ts +26 -22
  36. package/dist/server/server/loop-service.js +27 -9
  37. package/dist/server/server/package-version.d.ts +2 -2
  38. package/dist/server/server/paseo-worktree-archive-service.d.ts +2 -0
  39. package/dist/server/server/paseo-worktree-archive-service.js +28 -9
  40. package/dist/server/server/persisted-config.d.ts +84 -28
  41. package/dist/server/server/persisted-config.js +20 -3
  42. package/dist/server/server/pid-lock.d.ts +2 -2
  43. package/dist/server/server/private-files.d.ts +0 -1
  44. package/dist/server/server/private-files.js +0 -5
  45. package/dist/server/server/schedule/service.d.ts +6 -0
  46. package/dist/server/server/schedule/service.js +41 -18
  47. package/dist/server/server/schedule/store.js +3 -2
  48. package/dist/server/server/script-health-monitor.d.ts +4 -4
  49. package/dist/server/server/script-health-monitor.js +6 -6
  50. package/dist/server/server/script-proxy.d.ts +2 -39
  51. package/dist/server/server/script-proxy.js +1 -244
  52. package/dist/server/server/script-route-branch-handler.d.ts +2 -2
  53. package/dist/server/server/script-route-branch-handler.js +3 -37
  54. package/dist/server/server/script-status-projection.d.ts +6 -4
  55. package/dist/server/server/script-status-projection.js +85 -37
  56. package/dist/server/server/server-id.js +3 -3
  57. package/dist/server/server/service-proxy.d.ts +237 -0
  58. package/dist/server/server/service-proxy.js +714 -0
  59. package/dist/server/server/session.d.ts +12 -18
  60. package/dist/server/server/session.js +206 -117
  61. package/dist/server/server/speech/providers/local/worker-client.js +1 -11
  62. package/dist/server/server/websocket-server.d.ts +7 -4
  63. package/dist/server/server/websocket-server.js +9 -4
  64. package/dist/server/server/workspace-bootstrap-dedupe.d.ts +34 -0
  65. package/dist/server/server/workspace-bootstrap-dedupe.js +23 -0
  66. package/dist/server/server/workspace-directory.d.ts +8 -0
  67. package/dist/server/server/workspace-directory.js +141 -11
  68. package/dist/server/server/workspace-git-service.d.ts +3 -0
  69. package/dist/server/server/workspace-git-service.js +53 -12
  70. package/dist/server/server/workspace-registry.d.ts +2 -2
  71. package/dist/server/server/workspace-registry.js +2 -6
  72. package/dist/server/server/workspace-service-env.d.ts +1 -0
  73. package/dist/server/server/workspace-service-env.js +23 -18
  74. package/dist/server/server/worktree/commands.d.ts +2 -0
  75. package/dist/server/server/worktree/commands.js +4 -1
  76. package/dist/server/server/worktree-bootstrap.d.ts +4 -3
  77. package/dist/server/server/worktree-bootstrap.js +14 -13
  78. package/dist/server/server/worktree-core.d.ts +1 -0
  79. package/dist/server/server/worktree-core.js +2 -0
  80. package/dist/server/server/worktree-session.d.ts +6 -2
  81. package/dist/server/server/worktree-session.js +3 -0
  82. package/dist/server/services/github-service.d.ts +1 -0
  83. package/dist/server/services/github-service.js +7 -1
  84. package/dist/server/utils/checkout-git.d.ts +6 -3
  85. package/dist/server/utils/checkout-git.js +40 -38
  86. package/dist/server/utils/worktree.d.ts +17 -12
  87. package/dist/server/utils/worktree.js +39 -22
  88. package/dist/src/server/persisted-config.js +20 -3
  89. package/dist/src/server/private-files.js +0 -5
  90. package/package.json +9 -7
  91. package/dist/server/server/editor-targets.d.ts +0 -18
  92. package/dist/server/server/editor-targets.js +0 -109
  93. package/dist/server/utils/script-hostname.d.ts +0 -8
  94. package/dist/server/utils/script-hostname.js +0 -14
@@ -1,13 +1,11 @@
1
1
  import equal from "fast-deep-equal";
2
2
  import { v4 as uuidv4 } from "uuid";
3
- import { TTLCache } from "@isaacs/ttlcache";
4
- import pMemoize from "p-memoize";
5
3
  import { realpathSync } from "node:fs";
6
4
  import { basename, resolve, sep } from "path";
7
5
  import { homedir } from "node:os";
8
6
  import { z } from "zod";
9
7
  import { CLIENT_CAPS } from "@getpaseo/protocol/client-capabilities";
10
- import { isLegacyEditorTargetId, serializeAgentStreamEvent, } from "./messages.js";
8
+ import { serializeAgentStreamEvent, } from "./messages.js";
11
9
  import { TerminalSessionController } from "../terminal/terminal-session-controller.js";
12
10
  import { encodeFileTransferFrame, FileTransferOpcode, } from "@getpaseo/protocol/binary-frames/index";
13
11
  import { CursorError } from "./pagination/cursor.js";
@@ -16,7 +14,6 @@ import { TTSManager } from "./agent/tts-manager.js";
16
14
  import { STTManager } from "./agent/stt-manager.js";
17
15
  import { maybePersistTtsDebugAudio } from "./agent/tts-debug.js";
18
16
  import { isPaseoDictationDebugEnabled } from "./agent/recordings-debug.js";
19
- import { listAvailableEditorTargets, openInEditorTarget } from "./editor-targets.js";
20
17
  import { getPidLockInfo } from "./pid-lock.js";
21
18
  import { generateLocalPairingOffer } from "./pairing-offer.js";
22
19
  import { DictationStreamManager, } from "./dictation/dictation-stream-manager.js";
@@ -65,6 +62,7 @@ import { notifyChatMentions, prepareChatMentionFanout } from "./chat/chat-mentio
65
62
  import { execCommand } from "../utils/spawn.js";
66
63
  import { assertPullRequestAutoMergeDisableReady, assertPullRequestAutoMergeEnableReady, createGitHubService, } from "../services/github-service.js";
67
64
  import { summarizeFetchWorkspacesEntries, WorkspaceDirectory, } from "./workspace-directory.js";
65
+ import { shouldEmitPendingBootstrapUpdate } from "./workspace-bootstrap-dedupe.js";
68
66
  import { attemptFirstAgentBranchAutoName, createPaseoWorktree, } from "./paseo-worktree-service.js";
69
67
  import { generateBranchNameFromFirstAgentContext } from "./worktree-branch-name-generator.js";
70
68
  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";
@@ -114,7 +112,6 @@ const LEGACY_MODE_ICONS = new Set([
114
112
  "ShieldQuestionMark",
115
113
  ]);
116
114
  const MIN_VERSION_ALL_PROVIDERS = "0.1.45";
117
- const MIN_VERSION_FLEXIBLE_EDITOR_IDS = "0.1.50";
118
115
  function errorToFriendlyMessage(error) {
119
116
  if (error instanceof Error)
120
117
  return error.message;
@@ -190,9 +187,6 @@ function isAppVersionAtLeast(appVersion, minVersion) {
190
187
  function clientSupportsAllProviders(appVersion) {
191
188
  return isAppVersionAtLeast(appVersion, MIN_VERSION_ALL_PROVIDERS);
192
189
  }
193
- function clientSupportsFlexibleEditorIds(appVersion) {
194
- return isAppVersionAtLeast(appVersion, MIN_VERSION_FLEXIBLE_EDITOR_IDS);
195
- }
196
190
  function beginAgentDeleteIfSupported(agentStorage, agentId) {
197
191
  if ("beginDelete" in agentStorage && typeof agentStorage.beginDelete === "function") {
198
192
  agentStorage.beginDelete(agentId);
@@ -220,8 +214,6 @@ const PCM_BYTES_PER_MS = (PCM_SAMPLE_RATE * PCM_CHANNELS * (PCM_BITS_PER_SAMPLE
220
214
  const MIN_STREAMING_SEGMENT_DURATION_MS = 1000;
221
215
  const MIN_STREAMING_SEGMENT_BYTES = Math.round(PCM_BYTES_PER_MS * MIN_STREAMING_SEGMENT_DURATION_MS);
222
216
  const AgentIdSchema = z.string().uuid();
223
- const AVAILABLE_EDITOR_TARGETS_CACHE_TTL_MS = 60000;
224
- const AVAILABLE_EDITOR_TARGETS_CACHE_KEY = "available";
225
217
  class VoiceFeatureUnavailableError extends Error {
226
218
  constructor(context) {
227
219
  super(context.message);
@@ -296,15 +288,6 @@ export class Session {
296
288
  this.unsubscribeProviderSnapshotEvents = null;
297
289
  this.inflightRequests = 0;
298
290
  this.peakInflightRequests = 0;
299
- this.availableEditorTargetsCache = new TTLCache({
300
- ttl: AVAILABLE_EDITOR_TARGETS_CACHE_TTL_MS,
301
- max: 1,
302
- checkAgeOnGet: true,
303
- });
304
- this.getMemoizedAvailableEditorTargets = pMemoize(async () => this.resolveAvailableEditorTargets(), {
305
- cache: this.availableEditorTargetsCache,
306
- cacheKey: () => AVAILABLE_EDITOR_TARGETS_CACHE_KEY,
307
- });
308
291
  this.checkoutDiffSubscriptions = new Map();
309
292
  this.workspaceGitWatchTargets = new Map();
310
293
  this.workspaceGitFetchSubscriptions = new Map();
@@ -334,7 +317,7 @@ export class Session {
334
317
  }
335
318
  },
336
319
  });
337
- const { clientId, appVersion, clientCapabilities, onMessage, onBinaryMessage, onLifecycleIntent, logger, downloadTokenStore, pushTokenStore, paseoHome, agentManager, agentStorage, projectRegistry, workspaceRegistry, chatService, scheduleService, loopService, checkoutDiffManager, github, workspaceGitService, daemonConfigStore, mcpBaseUrl, stt, sttLanguage, tts, terminalManager, providerSnapshotManager, scriptRouteStore, scriptRuntimeStore, workspaceSetupSnapshots, onBranchChanged, getDaemonTcpPort, getDaemonTcpHost, resolveScriptHealth, voice, voiceBridge, dictation, serverId, daemonVersion, daemonRuntimeConfig, } = options;
320
+ const { clientId, appVersion, clientCapabilities, onMessage, onBinaryMessage, onLifecycleIntent, logger, downloadTokenStore, pushTokenStore, paseoHome, worktreesRoot, agentManager, agentStorage, projectRegistry, workspaceRegistry, chatService, scheduleService, loopService, checkoutDiffManager, github, workspaceGitService, daemonConfigStore, mcpBaseUrl, stt, sttLanguage, tts, terminalManager, providerSnapshotManager, serviceProxy, scriptRuntimeStore, workspaceSetupSnapshots, onBranchChanged, getDaemonTcpPort, getDaemonTcpHost, serviceProxyPublicBaseUrl, resolveScriptHealth, voice, voiceBridge, dictation, serverId, daemonVersion, daemonRuntimeConfig, } = options;
338
321
  this.clientId = clientId;
339
322
  this.appVersion = appVersion ?? null;
340
323
  this.clientCapabilities = parseClientCapabilities(clientCapabilities);
@@ -345,6 +328,7 @@ export class Session {
345
328
  this.downloadTokenStore = downloadTokenStore;
346
329
  this.pushTokenStore = pushTokenStore;
347
330
  this.paseoHome = paseoHome;
331
+ this.worktreesRoot = worktreesRoot;
348
332
  this.sessionLogger = logger.child({
349
333
  module: "session",
350
334
  clientId: this.clientId,
@@ -374,6 +358,7 @@ export class Session {
374
358
  });
375
359
  this.createAgentLifecycleDispatch = new CreateAgentLifecycleDispatch({
376
360
  paseoHome: this.paseoHome,
361
+ worktreesRoot: this.worktreesRoot,
377
362
  agentManager: this.agentManager,
378
363
  agentStorage: this.agentStorage,
379
364
  github: this.github,
@@ -398,12 +383,13 @@ export class Session {
398
383
  logger: this.sessionLogger,
399
384
  });
400
385
  this.providerSnapshotManager = providerSnapshotManager;
401
- this.scriptRouteStore = scriptRouteStore ?? null;
386
+ this.serviceProxy = serviceProxy ?? null;
402
387
  this.scriptRuntimeStore = scriptRuntimeStore ?? null;
403
388
  this.workspaceSetupSnapshots = workspaceSetupSnapshots ?? new Map();
404
389
  this.onBranchChanged = onBranchChanged;
405
390
  this.getDaemonTcpPort = getDaemonTcpPort ?? null;
406
391
  this.getDaemonTcpHost = getDaemonTcpHost ?? null;
392
+ this.serviceProxyPublicBaseUrl = serviceProxyPublicBaseUrl ?? null;
407
393
  this.resolveScriptHealth = resolveScriptHealth ?? null;
408
394
  this.sttLanguage = sttLanguage ?? "en";
409
395
  this.subscribeToOptionalManagers();
@@ -761,12 +747,6 @@ export class Session {
761
747
  }
762
748
  return LEGACY_PROVIDER_IDS.has(provider);
763
749
  }
764
- filterEditorsForClient(editors) {
765
- if (clientSupportsFlexibleEditorIds(this.appVersion)) {
766
- return editors;
767
- }
768
- return editors.filter((editor) => isLegacyEditorTargetId(editor.id));
769
- }
770
750
  agentThinkingOptionMatchesFilter(agent, filter) {
771
751
  if (filter.thinkingOptionId === undefined) {
772
752
  return true;
@@ -1325,14 +1305,17 @@ export class Session {
1325
1305
  return this.handleCreatePaseoWorktreeRequest(msg);
1326
1306
  case "workspace_setup_status_request":
1327
1307
  return this.handleWorkspaceSetupStatusRequest(msg);
1308
+ // COMPAT(desktopEditorBridge): added in v0.1.88, remove after 2026-12-03 once old clients no longer call daemon editor RPCs.
1328
1309
  case "list_available_editors_request":
1329
- return this.handleListAvailableEditorsRequest(msg);
1310
+ return this.handleLegacyListAvailableEditorsRequest(msg);
1330
1311
  case "open_in_editor_request":
1331
- return this.handleOpenInEditorRequest(msg);
1312
+ return this.handleLegacyOpenInEditorRequest(msg);
1332
1313
  case "open_project_request":
1333
1314
  return this.handleOpenProjectRequest(msg);
1334
1315
  case "archive_workspace_request":
1335
1316
  return this.handleArchiveWorkspaceRequest(msg);
1317
+ case "workspace.clear_attention.request":
1318
+ return this.handleWorkspaceClearAttentionRequest(msg);
1336
1319
  case "file_explorer_request":
1337
1320
  return this.handleFileExplorerRequest(msg);
1338
1321
  case "project_icon_request":
@@ -2094,6 +2077,7 @@ export class Session {
2094
2077
  agentStorage: this.agentStorage,
2095
2078
  logger: this.sessionLogger,
2096
2079
  paseoHome: this.paseoHome,
2080
+ worktreesRoot: this.worktreesRoot,
2097
2081
  workspaceGitService: this.workspaceGitService,
2098
2082
  providerSnapshotManager: this.providerSnapshotManager,
2099
2083
  daemonConfig: this.readStructuredGenerationDaemonConfig(),
@@ -2428,6 +2412,7 @@ export class Session {
2428
2412
  async buildAgentSessionConfig(config, gitOptions, legacyWorktreeName, firstAgentContext) {
2429
2413
  return buildWorktreeAgentSessionConfig({
2430
2414
  paseoHome: this.paseoHome,
2415
+ worktreesRoot: this.worktreesRoot,
2431
2416
  sessionLogger: this.sessionLogger,
2432
2417
  workspaceGitService: this.workspaceGitService,
2433
2418
  createPaseoWorktree: (input, serviceOptions) => this.createPaseoWorktreeWorkflow(input, {
@@ -3873,7 +3858,7 @@ export class Session {
3873
3858
  const mutatedCwd = await mergeToBase(cwd, {
3874
3859
  baseRef,
3875
3860
  mode: msg.strategy === "squash" ? "squash" : "merge",
3876
- }, { paseoHome: this.paseoHome });
3861
+ }, { paseoHome: this.paseoHome, worktreesRoot: this.worktreesRoot });
3877
3862
  await Promise.all([
3878
3863
  this.notifyGitMutation(mutatedCwd, "merge-to-base", { invalidateGithub: true }),
3879
3864
  ...(mutatedCwd !== cwd ? [this.notifyGitMutation(cwd, "merge-to-base")] : []),
@@ -4293,6 +4278,7 @@ export class Session {
4293
4278
  async handlePaseoWorktreeArchiveRequest(msg) {
4294
4279
  return handleWorktreeArchiveRequest({
4295
4280
  paseoHome: this.paseoHome,
4281
+ worktreesRoot: this.worktreesRoot,
4296
4282
  github: this.github,
4297
4283
  workspaceGitService: this.workspaceGitService,
4298
4284
  agentManager: this.agentManager,
@@ -4526,7 +4512,7 @@ export class Session {
4526
4512
  // Filter by labels if filter provided
4527
4513
  if (filter?.labels) {
4528
4514
  const filterLabels = filter.labels;
4529
- agents = agents.filter((agent) => Object.entries(filterLabels).every(([key, value]) => agent.labels[key] === value));
4515
+ agents = agents.filter((agent) => Object.entries(filterLabels).every(([key, _value]) => agent.labels[key] === filterLabels[key]));
4530
4516
  }
4531
4517
  return agents;
4532
4518
  }
@@ -4728,16 +4714,18 @@ export class Session {
4728
4714
  name: workspace.displayName,
4729
4715
  archivingAt: null,
4730
4716
  status: "done",
4717
+ statusEnteredAt: null,
4731
4718
  activityAt: null,
4732
4719
  diffStat,
4733
- scripts: this.scriptRouteStore && this.scriptRuntimeStore
4720
+ scripts: this.serviceProxy && this.scriptRuntimeStore
4734
4721
  ? buildWorkspaceScriptPayloads({
4735
4722
  workspaceId: workspace.workspaceId,
4736
4723
  workspaceDirectory: workspace.cwd,
4737
4724
  paseoConfig: readPaseoConfigForProjection(workspace.cwd, this.sessionLogger),
4738
- routeStore: this.scriptRouteStore,
4725
+ serviceProxy: this.serviceProxy,
4739
4726
  runtimeStore: this.scriptRuntimeStore,
4740
4727
  daemonPort: this.getDaemonTcpPort?.() ?? null,
4728
+ serviceProxyPublicBaseUrl: this.serviceProxyPublicBaseUrl,
4741
4729
  gitMetadata: this.resolveWorkspaceScriptGitMetadata(workspace.cwd),
4742
4730
  resolveHealth: this.resolveScriptHealth ?? undefined,
4743
4731
  })
@@ -4802,6 +4790,7 @@ export class Session {
4802
4790
  name: result.worktree.branchName || result.workspace.displayName,
4803
4791
  archivingAt: null,
4804
4792
  status: "done",
4793
+ statusEnteredAt: null,
4805
4794
  activityAt: null,
4806
4795
  diffStat: { additions: 0, deletions: 0 },
4807
4796
  scripts: [],
@@ -4872,15 +4861,26 @@ export class Session {
4872
4861
  subscription.pendingUpdatesByWorkspaceId.clear();
4873
4862
  for (const payload of pending) {
4874
4863
  if (payload.kind === "upsert") {
4875
- const snapshotLatestActivity = options?.snapshotLatestActivityByWorkspaceId?.get(payload.workspace.id);
4876
- if (typeof snapshotLatestActivity === "number") {
4877
- const updateLatestActivity = payload.workspace.activityAt
4878
- ? Date.parse(payload.workspace.activityAt)
4879
- : Number.NEGATIVE_INFINITY;
4880
- if (!Number.isNaN(updateLatestActivity) &&
4881
- updateLatestActivity <= snapshotLatestActivity) {
4882
- continue;
4883
- }
4864
+ const snapshot = options?.snapshotByWorkspaceId?.get(payload.workspace.id);
4865
+ const updateActivityAtMs = payload.workspace.activityAt
4866
+ ? Date.parse(payload.workspace.activityAt)
4867
+ : null;
4868
+ const shouldEmit = shouldEmitPendingBootstrapUpdate({
4869
+ snapshot: snapshot
4870
+ ? {
4871
+ status: snapshot.status,
4872
+ statusEnteredAt: snapshot.statusEnteredAt,
4873
+ activityAtMs: snapshot.activityAtMs,
4874
+ }
4875
+ : null,
4876
+ update: {
4877
+ status: payload.workspace.status,
4878
+ statusEnteredAt: payload.workspace.statusEnteredAt ?? null,
4879
+ activityAtMs: Number.isNaN(updateActivityAtMs) ? null : updateActivityAtMs,
4880
+ },
4881
+ });
4882
+ if (!shouldEmit) {
4883
+ continue;
4884
4884
  }
4885
4885
  }
4886
4886
  this.emit({
@@ -5310,15 +5310,7 @@ export class Session {
5310
5310
  pageInfo: payload.pageInfo,
5311
5311
  payload: summarizeFetchWorkspacesEntries(payload.entries),
5312
5312
  }, "fetch_workspaces_response_ready");
5313
- const snapshotLatestActivityByWorkspaceId = new Map();
5314
- for (const entry of payload.entries) {
5315
- const parsedLatestActivity = entry.activityAt
5316
- ? Date.parse(entry.activityAt)
5317
- : Number.NEGATIVE_INFINITY;
5318
- if (!Number.isNaN(parsedLatestActivity)) {
5319
- snapshotLatestActivityByWorkspaceId.set(entry.id, parsedLatestActivity);
5320
- }
5321
- }
5313
+ const snapshot = this.buildBootstrapSnapshot(payload.entries);
5322
5314
  this.emit({
5323
5315
  type: "fetch_workspaces_response",
5324
5316
  payload: {
@@ -5328,7 +5320,7 @@ export class Session {
5328
5320
  },
5329
5321
  });
5330
5322
  if (subscriptionId && this.workspaceUpdatesSubscription?.subscriptionId === subscriptionId) {
5331
- this.flushBootstrappedWorkspaceUpdates({ snapshotLatestActivityByWorkspaceId });
5323
+ this.flushBootstrappedWorkspaceUpdates(snapshot);
5332
5324
  void this.reconcileAndEmitWorkspaceUpdates();
5333
5325
  }
5334
5326
  }
@@ -5350,6 +5342,24 @@ export class Session {
5350
5342
  });
5351
5343
  }
5352
5344
  }
5345
+ // Build the bootstrap snapshot used by `flushBootstrappedWorkspaceUpdates`
5346
+ // to decide which pending updates to drop. Captures the status,
5347
+ // statusEnteredAt, and activityAt (parsed to ms) for each workspace entry
5348
+ // so a status-only change (e.g. the unmask case), a statusEnteredAt-only
5349
+ // change (e.g. a fresh unmask time), AND a fresher activity all still
5350
+ // ship to the client.
5351
+ buildBootstrapSnapshot(entries) {
5352
+ const snapshotByWorkspaceId = new Map();
5353
+ for (const entry of entries) {
5354
+ const parsedActivity = entry.activityAt ? Date.parse(entry.activityAt) : null;
5355
+ snapshotByWorkspaceId.set(entry.id, {
5356
+ status: entry.status,
5357
+ statusEnteredAt: entry.statusEnteredAt ?? null,
5358
+ activityAtMs: Number.isNaN(parsedActivity) ? null : parsedActivity,
5359
+ });
5360
+ }
5361
+ return { snapshotByWorkspaceId };
5362
+ }
5353
5363
  async registerWorkspaceForImportedAgent(cwd) {
5354
5364
  try {
5355
5365
  const workspace = await this.findOrCreateWorkspaceForDirectory(cwd);
@@ -5399,16 +5409,17 @@ export class Session {
5399
5409
  }
5400
5410
  }
5401
5411
  buildWorkspaceScriptPayloadSnapshot(workspaceId, workspaceDirectory) {
5402
- if (!this.scriptRouteStore || !this.scriptRuntimeStore) {
5412
+ if (!this.serviceProxy || !this.scriptRuntimeStore) {
5403
5413
  return [];
5404
5414
  }
5405
5415
  return buildWorkspaceScriptPayloads({
5406
5416
  workspaceId,
5407
5417
  workspaceDirectory,
5408
5418
  paseoConfig: readPaseoConfigForProjection(workspaceDirectory, this.sessionLogger),
5409
- routeStore: this.scriptRouteStore,
5419
+ serviceProxy: this.serviceProxy,
5410
5420
  runtimeStore: this.scriptRuntimeStore,
5411
5421
  daemonPort: this.getDaemonTcpPort?.() ?? null,
5422
+ serviceProxyPublicBaseUrl: this.serviceProxyPublicBaseUrl,
5412
5423
  gitMetadata: this.resolveWorkspaceScriptGitMetadata(workspaceDirectory),
5413
5424
  resolveHealth: this.resolveScriptHealth ?? undefined,
5414
5425
  });
@@ -5432,18 +5443,9 @@ export class Session {
5432
5443
  },
5433
5444
  });
5434
5445
  }
5435
- async resolveAvailableEditorTargets() {
5436
- return listAvailableEditorTargets();
5437
- }
5438
- async getAvailableEditorTargets() {
5439
- return this.filterEditorsForClient(await this.getMemoizedAvailableEditorTargets());
5440
- }
5441
- async openEditorTarget(options) {
5442
- await openInEditorTarget(options);
5443
- }
5444
5446
  async handleStartWorkspaceScriptRequest(request) {
5445
5447
  try {
5446
- if (!this.terminalManager || !this.scriptRouteStore || !this.scriptRuntimeStore) {
5448
+ if (!this.terminalManager || !this.serviceProxy || !this.scriptRuntimeStore) {
5447
5449
  throw new Error("Workspace scripts are not available on this daemon");
5448
5450
  }
5449
5451
  const workspace = await this.workspaceRegistry.get(request.workspaceId);
@@ -5459,7 +5461,8 @@ export class Session {
5459
5461
  scriptName: request.scriptName,
5460
5462
  daemonPort: this.getDaemonTcpPort?.() ?? null,
5461
5463
  daemonListenHost: this.getDaemonTcpHost?.() ?? null,
5462
- routeStore: this.scriptRouteStore,
5464
+ serviceProxyPublicBaseUrl: this.serviceProxyPublicBaseUrl,
5465
+ serviceProxy: this.serviceProxy,
5463
5466
  runtimeStore: this.scriptRuntimeStore,
5464
5467
  terminalManager: this.terminalManager,
5465
5468
  logger: this.sessionLogger,
@@ -5498,62 +5501,30 @@ export class Session {
5498
5501
  });
5499
5502
  }
5500
5503
  }
5501
- async handleListAvailableEditorsRequest(request) {
5502
- try {
5503
- const editors = await this.getAvailableEditorTargets();
5504
- this.emit({
5505
- type: "list_available_editors_response",
5506
- payload: {
5507
- requestId: request.requestId,
5508
- editors,
5509
- error: null,
5510
- },
5511
- });
5512
- }
5513
- catch (error) {
5514
- const message = error instanceof Error ? error.message : "Failed to list available editors";
5515
- this.sessionLogger.error({ err: error, requestType: request.type }, "Failed to list available editors");
5516
- this.emit({
5517
- type: "list_available_editors_response",
5518
- payload: {
5519
- requestId: request.requestId,
5520
- editors: [],
5521
- error: message,
5522
- },
5523
- });
5524
- }
5504
+ // COMPAT(desktopEditorBridge): added in v0.1.88, remove after 2026-12-03 once old clients no longer call daemon editor RPCs.
5505
+ async handleLegacyListAvailableEditorsRequest(request) {
5506
+ this.emit({
5507
+ type: "list_available_editors_response",
5508
+ payload: {
5509
+ requestId: request.requestId,
5510
+ editors: [],
5511
+ error: "Editor opening moved to the desktop app and is no longer supported by the daemon",
5512
+ },
5513
+ });
5525
5514
  }
5526
- async handleOpenInEditorRequest(request) {
5527
- try {
5528
- await this.openEditorTarget({ editorId: request.editorId, path: request.path });
5529
- this.emit({
5530
- type: "open_in_editor_response",
5531
- payload: {
5532
- requestId: request.requestId,
5533
- error: null,
5534
- },
5535
- });
5536
- }
5537
- catch (error) {
5538
- const message = error instanceof Error ? error.message : "Failed to open in editor";
5539
- this.sessionLogger.error({
5540
- err: error,
5541
- editorId: request.editorId,
5542
- path: request.path,
5543
- requestType: request.type,
5544
- }, "Failed to open in editor");
5545
- this.emit({
5546
- type: "open_in_editor_response",
5547
- payload: {
5548
- requestId: request.requestId,
5549
- error: message,
5550
- },
5551
- });
5552
- }
5515
+ async handleLegacyOpenInEditorRequest(request) {
5516
+ this.emit({
5517
+ type: "open_in_editor_response",
5518
+ payload: {
5519
+ requestId: request.requestId,
5520
+ error: "Editor opening moved to the desktop app and is no longer supported by the daemon",
5521
+ },
5522
+ });
5553
5523
  }
5554
5524
  async handleCreatePaseoWorktreeRequest(request) {
5555
5525
  return handleCreateWorktreeRequest({
5556
5526
  paseoHome: this.paseoHome,
5527
+ worktreesRoot: this.worktreesRoot,
5557
5528
  describeWorkspaceRecord: (result) => this.describeCreatedWorktreeWorkspace(result),
5558
5529
  emit: (message) => this.emit(message),
5559
5530
  sessionLogger: this.sessionLogger,
@@ -5563,6 +5534,7 @@ export class Session {
5563
5534
  async createPaseoWorktreeWorkflow(input, options) {
5564
5535
  return createWorktreeWorkflow({
5565
5536
  paseoHome: this.paseoHome,
5537
+ worktreesRoot: this.worktreesRoot,
5566
5538
  createPaseoWorktree: (workflowInput, serviceOptions) => this.createPaseoWorktree(workflowInput, serviceOptions),
5567
5539
  warmWorkspaceGitData: (workspace) => this.warmWorkspaceGitDataForWorkspace(workspace),
5568
5540
  autoNameWorkspaceBranchForFirstAgent: (autoNameInput) => this.scheduleAutoNameWorkspaceBranchForFirstAgent(autoNameInput),
@@ -5574,10 +5546,11 @@ export class Session {
5574
5546
  sessionLogger: this.sessionLogger,
5575
5547
  terminalManager: this.terminalManager,
5576
5548
  archiveWorkspaceRecord: (workspaceId) => this.archiveWorkspaceRecord(workspaceId),
5577
- scriptRouteStore: this.scriptRouteStore,
5549
+ serviceProxy: this.serviceProxy,
5578
5550
  scriptRuntimeStore: this.scriptRuntimeStore,
5579
5551
  getDaemonTcpPort: this.getDaemonTcpPort,
5580
5552
  getDaemonTcpHost: this.getDaemonTcpHost,
5553
+ serviceProxyPublicBaseUrl: this.serviceProxyPublicBaseUrl,
5581
5554
  onScriptsChanged: (workspaceId, workspaceDirectory) => {
5582
5555
  this.emitWorkspaceScriptStatusUpdate(workspaceId, workspaceDirectory);
5583
5556
  },
@@ -5625,6 +5598,122 @@ export class Session {
5625
5598
  });
5626
5599
  }
5627
5600
  }
5601
+ async handleWorkspaceClearAttentionRequest(request) {
5602
+ const { requestId, workspaceId } = request;
5603
+ const requestedWorkspaceIds = Array.isArray(workspaceId) ? workspaceId : [workspaceId];
5604
+ let agents;
5605
+ try {
5606
+ agents = await this.listAgentPayloads();
5607
+ }
5608
+ catch (error) {
5609
+ const message = getErrorMessage(error);
5610
+ const results = requestedWorkspaceIds.map((requestedWorkspaceId) => ({
5611
+ workspaceId: requestedWorkspaceId,
5612
+ clearedAgentIds: [],
5613
+ success: false,
5614
+ error: message,
5615
+ }));
5616
+ this.emit({
5617
+ type: "workspace.clear_attention.response",
5618
+ payload: {
5619
+ requestId,
5620
+ workspaceId,
5621
+ clearedAgentIds: [],
5622
+ results,
5623
+ success: false,
5624
+ error: message,
5625
+ },
5626
+ });
5627
+ return;
5628
+ }
5629
+ const results = [];
5630
+ for (const requestedWorkspaceId of requestedWorkspaceIds) {
5631
+ const clearedAgentIds = [];
5632
+ try {
5633
+ const workspace = await this.workspaceRegistry.get(requestedWorkspaceId);
5634
+ if (!workspace || workspace.archivedAt) {
5635
+ throw new Error(`Workspace not found: ${requestedWorkspaceId}`);
5636
+ }
5637
+ const workspaceCwd = normalizePersistedWorkspaceId(workspace.cwd);
5638
+ const clearableAgentIds = agents
5639
+ .filter((agent) => !agent.archivedAt)
5640
+ .filter((agent) => normalizePersistedWorkspaceId(agent.cwd) === workspaceCwd)
5641
+ .filter((agent) => agent.requiresAttention === true)
5642
+ .filter((agent) => (agent.pendingPermissions?.length ?? 0) === 0)
5643
+ .filter((agent) => agent.attentionReason !== "permission")
5644
+ .map((agent) => agent.id);
5645
+ for (const agentId of clearableAgentIds) {
5646
+ const liveAgent = this.agentManager.getAgent(agentId);
5647
+ if (liveAgent) {
5648
+ await this.agentManager.clearAgentAttention(agentId);
5649
+ clearedAgentIds.push(agentId);
5650
+ continue;
5651
+ }
5652
+ const record = await this.agentStorage.get(agentId);
5653
+ if (!record ||
5654
+ record.internal ||
5655
+ record.archivedAt ||
5656
+ record.requiresAttention !== true) {
5657
+ continue;
5658
+ }
5659
+ const nextRecord = {
5660
+ ...record,
5661
+ updatedAt: new Date().toISOString(),
5662
+ requiresAttention: false,
5663
+ attentionReason: null,
5664
+ attentionTimestamp: null,
5665
+ };
5666
+ await this.agentStorage.upsert(nextRecord);
5667
+ const agent = this.buildStoredAgentPayload(nextRecord);
5668
+ const project = await this.buildProjectPlacementForCwd(agent.cwd);
5669
+ this.emit({
5670
+ type: "agent_update",
5671
+ payload: {
5672
+ kind: "upsert",
5673
+ agent,
5674
+ project,
5675
+ },
5676
+ });
5677
+ clearedAgentIds.push(agentId);
5678
+ }
5679
+ await this.emitWorkspaceUpdateForWorkspaceId(workspace.workspaceId);
5680
+ results.push({
5681
+ workspaceId: requestedWorkspaceId,
5682
+ clearedAgentIds,
5683
+ success: true,
5684
+ error: null,
5685
+ });
5686
+ }
5687
+ catch (error) {
5688
+ const message = getErrorMessage(error);
5689
+ this.sessionLogger.error({ err: error, workspaceId: requestedWorkspaceId }, "Failed to clear workspace attention");
5690
+ results.push({
5691
+ workspaceId: requestedWorkspaceId,
5692
+ clearedAgentIds,
5693
+ success: false,
5694
+ error: message,
5695
+ });
5696
+ }
5697
+ }
5698
+ const clearedAgentIds = results.flatMap((result) => result.clearedAgentIds);
5699
+ const failedResults = results.filter((result) => !result.success);
5700
+ this.emit({
5701
+ type: "workspace.clear_attention.response",
5702
+ payload: {
5703
+ requestId,
5704
+ workspaceId,
5705
+ clearedAgentIds,
5706
+ results,
5707
+ success: failedResults.length === 0,
5708
+ error: failedResults.length === 0
5709
+ ? null
5710
+ : failedResults
5711
+ .map((result) => result.error)
5712
+ .filter((error) => error !== null)
5713
+ .join("; "),
5714
+ },
5715
+ });
5716
+ }
5628
5717
  async handleFetchAgent(agentIdOrIdentifier, requestId) {
5629
5718
  const resolved = await this.resolveAgentIdentifier(agentIdOrIdentifier);
5630
5719
  if (!resolved.ok) {
@@ -174,7 +174,7 @@ export class LocalSpeechWorkerClient {
174
174
  reject,
175
175
  timeout,
176
176
  });
177
- const sent = worker.send(message, (error) => {
177
+ worker.send(message, (error) => {
178
178
  if (!error) {
179
179
  return;
180
180
  }
@@ -188,16 +188,6 @@ export class LocalSpeechWorkerClient {
188
188
  this.scheduleIdleShutdownIfReady();
189
189
  pending.reject(error);
190
190
  });
191
- if (!sent) {
192
- const pending = this.pendingRequests.get(requestId);
193
- if (pending) {
194
- clearTimeout(pending.timeout);
195
- this.pendingRequests.delete(requestId);
196
- this.inFlightRequests = Math.max(0, this.inFlightRequests - 1);
197
- this.scheduleIdleShutdownIfReady();
198
- pending.reject(new Error("Local speech worker IPC channel is not writable"));
199
- }
200
- }
201
191
  });
202
192
  }
203
193
  ensureWorker() {
@@ -17,7 +17,7 @@ import { ProviderSnapshotManager } from "./agent/provider-snapshot-manager.js";
17
17
  import type { WorkspaceGitService } from "./workspace-git-service.js";
18
18
  import { type PushNotificationSender } from "./push/notifications.js";
19
19
  import type { ScriptHealthState } from "./script-health-monitor.js";
20
- import type { ScriptRouteStore } from "./script-proxy.js";
20
+ import type { ServiceProxySubsystem } from "./service-proxy.js";
21
21
  import type { WorkspaceScriptRuntimeStore } from "./workspace-script-runtime-store.js";
22
22
  import type { SpeechReadinessSnapshot, SpeechService } from "./speech/speech-runtime.js";
23
23
  import type { VoiceCallerContext, VoiceSpeakHandler } from "./voice-types.js";
@@ -66,16 +66,18 @@ export declare class VoiceAssistantWebSocketServer {
66
66
  private readonly workspaceGitService;
67
67
  private readonly downloadTokenStore;
68
68
  private readonly paseoHome;
69
+ private readonly worktreesRoot;
69
70
  private readonly daemonConfigStore;
70
71
  private readonly pushTokenStore;
71
72
  private readonly pushNotificationSender;
72
73
  private readonly mcpBaseUrl;
73
74
  private speech;
74
75
  private terminalManager;
75
- private scriptRouteStore;
76
+ private serviceProxy;
76
77
  private scriptRuntimeStore;
77
78
  private getDaemonTcpPort;
78
79
  private getDaemonTcpHost;
80
+ private serviceProxyPublicBaseUrl;
79
81
  private resolveScriptHealth;
80
82
  private dictation;
81
83
  private readonly voiceSpeakHandlers;
@@ -91,8 +93,9 @@ export declare class VoiceAssistantWebSocketServer {
91
93
  private unsubscribeDaemonConfigChange;
92
94
  constructor(server: HTTPServer, logger: pino.Logger, serverId: string, agentManager: AgentManager, agentStorage: AgentStorage, downloadTokenStore: DownloadTokenStore, paseoHome: string, daemonConfigStore: DaemonConfigStore, mcpBaseUrl: string | null, wsConfig: WebSocketServerConfig, auth?: DaemonAuthConfig, speech?: SpeechService | null, terminalManager?: TerminalManager | null, dictation?: {
93
95
  finalTimeoutMs?: number;
94
- }, daemonVersion?: string, onLifecycleIntent?: (intent: SessionLifecycleIntent) => void, projectRegistry?: ProjectRegistry, workspaceRegistry?: WorkspaceRegistry, chatService?: FileBackedChatService, loopService?: LoopService, scheduleService?: ScheduleService, checkoutDiffManager?: CheckoutDiffManager, scriptRouteStore?: ScriptRouteStore | null, scriptRuntimeStore?: WorkspaceScriptRuntimeStore | null, onBranchChanged?: (workspaceId: string, oldBranch: string | null, newBranch: string | null) => void, getDaemonTcpPort?: () => number | null, getDaemonTcpHost?: () => string | null, resolveScriptHealth?: (hostname: string) => ScriptHealthState | null, workspaceGitService?: WorkspaceGitService, github?: GitHubService, pushNotificationSender?: PushNotificationSender, providerSnapshotManager?: ProviderSnapshotManager, daemonRuntimeConfig?: {
96
+ }, daemonVersion?: string, onLifecycleIntent?: (intent: SessionLifecycleIntent) => void, projectRegistry?: ProjectRegistry, workspaceRegistry?: WorkspaceRegistry, chatService?: FileBackedChatService, loopService?: LoopService, scheduleService?: ScheduleService, checkoutDiffManager?: CheckoutDiffManager, serviceProxy?: ServiceProxySubsystem | null, scriptRuntimeStore?: WorkspaceScriptRuntimeStore | null, onBranchChanged?: (workspaceId: string, oldBranch: string | null, newBranch: string | null) => void, getDaemonTcpPort?: () => number | null, getDaemonTcpHost?: () => string | null, resolveScriptHealth?: (hostname: string) => ScriptHealthState | null, workspaceGitService?: WorkspaceGitService, github?: GitHubService, pushNotificationSender?: PushNotificationSender, providerSnapshotManager?: ProviderSnapshotManager, daemonRuntimeConfig?: {
95
97
  listen: string | null;
98
+ worktreesRoot?: string;
96
99
  relay: {
97
100
  enabled: boolean;
98
101
  endpoint: string;
@@ -100,7 +103,7 @@ export declare class VoiceAssistantWebSocketServer {
100
103
  useTls: boolean;
101
104
  publicUseTls: boolean;
102
105
  };
103
- });
106
+ }, serviceProxyPublicBaseUrl?: string | null);
104
107
  private assignOptionalServices;
105
108
  private createWebSocketServer;
106
109
  private startRuntimeMetricsInterval;