@getpaseo/server 0.1.100 → 0.1.102-beta.2

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 (172) hide show
  1. package/dist/scripts/supervisor.js +26 -8
  2. package/dist/server/executable-resolution/windows.js +3 -0
  3. package/dist/server/server/agent/activity-curator.d.ts +17 -0
  4. package/dist/server/server/agent/activity-curator.js +101 -24
  5. package/dist/server/server/agent/agent-manager.d.ts +10 -0
  6. package/dist/server/server/agent/agent-manager.js +69 -27
  7. package/dist/server/server/agent/agent-sdk-types.d.ts +15 -2
  8. package/dist/server/server/agent/mcp-server.d.ts +2 -45
  9. package/dist/server/server/agent/mcp-server.js +45 -1985
  10. package/dist/server/server/agent/prompt-attachments.js +6 -2
  11. package/dist/server/server/agent/provider-snapshot-manager.d.ts +12 -1
  12. package/dist/server/server/agent/provider-snapshot-manager.js +132 -42
  13. package/dist/server/server/agent/providers/acp-agent.d.ts +27 -1
  14. package/dist/server/server/agent/providers/acp-agent.js +178 -27
  15. package/dist/server/server/agent/providers/claude/agent.js +111 -24
  16. package/dist/server/server/agent/providers/claude/query.d.ts +3 -0
  17. package/dist/server/server/agent/providers/claude/query.js +4 -2
  18. package/dist/server/server/agent/providers/codex-app-server-agent.js +6 -57
  19. package/dist/server/server/agent/providers/diagnostic-utils.d.ts +1 -0
  20. package/dist/server/server/agent/providers/diagnostic-utils.js +1 -1
  21. package/dist/server/server/agent/providers/generic-acp-agent.d.ts +3 -0
  22. package/dist/server/server/agent/providers/generic-acp-agent.js +41 -23
  23. package/dist/server/server/agent/providers/mock-load-test-agent.js +12 -2
  24. package/dist/server/server/agent/providers/opencode/paths.d.ts +2 -0
  25. package/dist/server/server/agent/providers/opencode/paths.js +7 -0
  26. package/dist/server/server/agent/providers/opencode/server-manager.d.ts +2 -0
  27. package/dist/server/server/agent/providers/opencode/server-manager.js +34 -5
  28. package/dist/server/server/agent/providers/opencode-agent.d.ts +4 -0
  29. package/dist/server/server/agent/providers/opencode-agent.js +14 -2
  30. package/dist/server/server/agent/providers/pi/agent.d.ts +5 -1
  31. package/dist/server/server/agent/providers/pi/agent.js +12 -3
  32. package/dist/server/server/agent/providers/provider-image-output.d.ts +5 -0
  33. package/dist/server/server/agent/providers/provider-image-output.js +61 -1
  34. package/dist/server/server/agent/tools/paseo-tools.d.ts +48 -0
  35. package/dist/server/server/agent/tools/paseo-tools.js +2119 -0
  36. package/dist/server/server/agent/tools/types.d.ts +36 -0
  37. package/dist/server/server/agent/tools/types.js +2 -0
  38. package/dist/server/server/bootstrap.d.ts +7 -1
  39. package/dist/server/server/bootstrap.js +89 -62
  40. package/dist/server/server/config.d.ts +2 -0
  41. package/dist/server/server/config.js +57 -1
  42. package/dist/server/server/daemon-worker.js +19 -7
  43. package/dist/server/server/lifecycle-reasons.d.ts +4 -0
  44. package/dist/server/server/lifecycle-reasons.js +6 -0
  45. package/dist/server/server/persisted-config.d.ts +12 -0
  46. package/dist/server/server/persisted-config.js +18 -2
  47. package/dist/server/server/process-diagnostics.d.ts +17 -0
  48. package/dist/server/server/process-diagnostics.js +22 -0
  49. package/dist/server/server/relay-transport.js +1 -0
  50. package/dist/server/server/resolve-worktree-creation-intent.js +3 -1
  51. package/dist/server/server/session/agent-updates/agent-updates-service.d.ts +59 -0
  52. package/dist/server/server/session/agent-updates/agent-updates-service.js +220 -0
  53. package/dist/server/server/session/checkout/checkout-session.d.ts +13 -15
  54. package/dist/server/server/session/checkout/checkout-session.js +18 -16
  55. package/dist/server/server/session/checkout/git-metadata-generator.d.ts +53 -0
  56. package/dist/server/server/session/checkout/git-metadata-generator.js +159 -0
  57. package/dist/server/server/session/daemon/daemon-self-update-session-controller.d.ts +32 -0
  58. package/dist/server/server/session/daemon/daemon-self-update-session-controller.js +88 -0
  59. package/dist/server/server/session/daemon/daemon-self-updater.d.ts +32 -0
  60. package/dist/server/server/session/daemon/daemon-self-updater.js +56 -0
  61. package/dist/server/server/session/daemon/daemon-session.d.ts +26 -0
  62. package/dist/server/server/session/daemon/daemon-session.js +50 -0
  63. package/dist/server/server/session/daemon/diagnostics.d.ts +41 -0
  64. package/dist/server/server/session/daemon/diagnostics.js +431 -0
  65. package/dist/server/server/session/daemon/install-origin.d.ts +7 -0
  66. package/dist/server/server/session/daemon/install-origin.js +64 -0
  67. package/dist/server/server/session/daemon/npm-global-cli.d.ts +29 -0
  68. package/dist/server/server/session/daemon/npm-global-cli.js +98 -0
  69. package/dist/server/server/session/git-mutation/git-mutation-service.d.ts +34 -0
  70. package/dist/server/server/session/git-mutation/git-mutation-service.js +71 -0
  71. package/dist/server/server/session/provider/provider-catalog-session.js +8 -4
  72. package/dist/server/server/session/workspace-git-observer/workspace-git-observer-service.d.ts +36 -0
  73. package/dist/server/server/session/workspace-git-observer/workspace-git-observer-service.js +134 -0
  74. package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.d.ts +34 -0
  75. package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.js +190 -0
  76. package/dist/server/server/session/workspace-scripts/workspace-scripts-service.d.ts +41 -0
  77. package/dist/server/server/session/workspace-scripts/workspace-scripts-service.js +100 -0
  78. package/dist/server/server/session.d.ts +12 -54
  79. package/dist/server/server/session.js +187 -970
  80. package/dist/server/server/speech/providers/openai/config.d.ts +1 -2
  81. package/dist/server/server/speech/providers/openai/config.js +13 -9
  82. package/dist/server/server/speech/providers/openai/runtime.js +2 -16
  83. package/dist/server/server/speech/providers/openai/stt.d.ts +1 -0
  84. package/dist/server/server/speech/providers/openai/stt.js +4 -2
  85. package/dist/server/server/speech/providers/openai/tts.d.ts +1 -0
  86. package/dist/server/server/speech/providers/openai/tts.js +1 -0
  87. package/dist/server/server/web-ui.d.ts +10 -0
  88. package/dist/server/server/web-ui.js +205 -0
  89. package/dist/server/server/websocket/runtime-metrics.d.ts +23 -0
  90. package/dist/server/server/websocket-server.d.ts +4 -2
  91. package/dist/server/server/websocket-server.js +215 -52
  92. package/dist/server/server/worktree-bootstrap.d.ts +1 -1
  93. package/dist/server/server/worktree-branch-name-generator.js +3 -1
  94. package/dist/server/services/quota-fetcher/manifest.js +5 -0
  95. package/dist/server/services/quota-fetcher/providers/minimax.d.ts +29 -0
  96. package/dist/server/services/quota-fetcher/providers/minimax.js +227 -0
  97. package/dist/server/terminal/agent-hooks/agent-hook-installer.js +2 -2
  98. package/dist/server/utils/checkout-git.js +203 -25
  99. package/dist/server/utils/directory-suggestions.js +1 -4
  100. package/dist/server/utils/path.d.ts +2 -0
  101. package/dist/server/utils/path.js +13 -0
  102. package/dist/server/utils/worktree.d.ts +1 -0
  103. package/dist/server/utils/worktree.js +92 -11
  104. package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css +1 -0
  105. package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css.br +0 -0
  106. package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css.gz +0 -0
  107. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-b01555c9b42665a03988c0a0032ef528.js +1 -0
  108. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-b01555c9b42665a03988c0a0032ef528.js.br +0 -0
  109. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-b01555c9b42665a03988c0a0032ef528.js.gz +0 -0
  110. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-648388eca5c510b496e1eddf523f70ff.js +1 -0
  111. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-648388eca5c510b496e1eddf523f70ff.js.br +0 -0
  112. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-648388eca5c510b496e1eddf523f70ff.js.gz +0 -0
  113. package/dist/server/web-ui/_expo/static/js/web/index-0ebbea2cd337f0c0680fdb3f8d4d5af3.js +16157 -0
  114. package/dist/server/web-ui/_expo/static/js/web/index-0ebbea2cd337f0c0680fdb3f8d4d5af3.js.br +0 -0
  115. package/dist/server/web-ui/_expo/static/js/web/index-0ebbea2cd337f0c0680fdb3f8d4d5af3.js.gz +0 -0
  116. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-c64fa2416284927857a39087fd8d1332.js +1 -0
  117. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-c64fa2416284927857a39087fd8d1332.js.br +0 -0
  118. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-c64fa2416284927857a39087fd8d1332.js.gz +0 -0
  119. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-a9784226715772edf87ef36c596599c2.js +3 -0
  120. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-a9784226715772edf87ef36c596599c2.js.br +0 -0
  121. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-a9784226715772edf87ef36c596599c2.js.gz +0 -0
  122. package/dist/server/web-ui/apple-touch-icon.png +0 -0
  123. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/back-icon-mask.0a328cd9c1afd0afe8e3b1ec5165b1b4.png +0 -0
  124. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/back-icon.35ba0eaec5a4f5ed12ca16fabeae451d.png +0 -0
  125. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55.png +0 -0
  126. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@2x.png +0 -0
  127. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@3x.png +0 -0
  128. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@4x.png +0 -0
  129. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7.png +0 -0
  130. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@2x.png +0 -0
  131. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@3x.png +0 -0
  132. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@4x.png +0 -0
  133. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/search-icon.286d67d3f74808a60a78d3ebf1a5fb57.png +0 -0
  134. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/arrow_down.017bc6ba3fc25503e5eb5e53826d48a8.png +0 -0
  135. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/error.d1ea1496f9057eb392d5bbf3732a61b7.png +0 -0
  136. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/file.19eeb73b9593a38f8e9f418337fc7d10.png +0 -0
  137. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/forward.d8b800c443b8972542883e0b9de2bdc6.png +0 -0
  138. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/pkg.ab19f4cbc543357183a20571f68380a3.png +0 -0
  139. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/sitemap.412dd9275b6b48ad28f5e3d81bb1f626.png +0 -0
  140. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/unmatched.20e71bdf79e3a97bf55fd9e164041578.png +0 -0
  141. package/dist/server/web-ui/assets/assets/images/editor-apps/antigravity.6e91a685c33435e0b466a56db86cf141.png +0 -0
  142. package/dist/server/web-ui/assets/assets/images/editor-apps/cursor.c31d6bce4fe9aadc3fe59962f4c4fcf3.png +0 -0
  143. package/dist/server/web-ui/assets/assets/images/editor-apps/file-explorer.3e15e8f72c825c85ce336bcb0cdef776.png +0 -0
  144. package/dist/server/web-ui/assets/assets/images/editor-apps/finder.7f68fc2c475621a672e1be09309d5567.png +0 -0
  145. package/dist/server/web-ui/assets/assets/images/editor-apps/vscode.832bdb4c685d930f1c864c793703600b.png +0 -0
  146. package/dist/server/web-ui/assets/assets/images/editor-apps/webstorm.aa5dc2cd8c20cc0a155c4c5c5ab3c5f5.png +0 -0
  147. package/dist/server/web-ui/assets/assets/images/editor-apps/zed.f3a670b7f9aa226da4fe53fb86f1abbd.png +0 -0
  148. package/dist/server/web-ui/assets/assets/images/favicon-dark-attention.882b3a27dcb2073e9e31b334f9ed9728.png +0 -0
  149. package/dist/server/web-ui/assets/assets/images/favicon-dark-running.8112342ff0d39e047a7f8d4fad9402f3.png +0 -0
  150. package/dist/server/web-ui/assets/assets/images/favicon-dark.8005ed36ac07a5a7c60de25780897bd4.png +0 -0
  151. package/dist/server/web-ui/assets/assets/images/favicon-light-attention.882b3a27dcb2073e9e31b334f9ed9728.png +0 -0
  152. package/dist/server/web-ui/assets/assets/images/favicon-light-running.8112342ff0d39e047a7f8d4fad9402f3.png +0 -0
  153. package/dist/server/web-ui/assets/assets/images/favicon-light.8005ed36ac07a5a7c60de25780897bd4.png +0 -0
  154. package/dist/server/web-ui/assets/assets/images/notification-icon.3bf81d33ddbf380606bdd248ba83e158.png +0 -0
  155. package/dist/server/web-ui/favicon.ico +0 -0
  156. package/dist/server/web-ui/index.html +90 -0
  157. package/dist/server/web-ui/index.html.br +0 -0
  158. package/dist/server/web-ui/index.html.gz +0 -0
  159. package/dist/server/web-ui/manifest.json +27 -0
  160. package/dist/server/web-ui/manifest.json.br +0 -0
  161. package/dist/server/web-ui/manifest.json.gz +0 -0
  162. package/dist/server/web-ui/metadata.json +1 -0
  163. package/dist/server/web-ui/metadata.json.br +1 -0
  164. package/dist/server/web-ui/metadata.json.gz +0 -0
  165. package/dist/server/web-ui/pwa-icon-192.png +0 -0
  166. package/dist/server/web-ui/pwa-icon-512.png +0 -0
  167. package/dist/server/web-ui/robots.txt +2 -0
  168. package/dist/src/executable-resolution/windows.js +3 -0
  169. package/dist/src/server/persisted-config.js +18 -2
  170. package/package.json +7 -7
  171. package/dist/server/server/speech/providers/openai/realtime-transcription-session.d.ts +0 -42
  172. package/dist/server/server/speech/providers/openai/realtime-transcription-session.js +0 -168
@@ -3,7 +3,6 @@ import { v4 as uuidv4 } from "uuid";
3
3
  import { stat } from "node:fs/promises";
4
4
  import { basename, normalize, resolve, sep } from "path";
5
5
  import { homedir } from "node:os";
6
- import { z } from "zod";
7
6
  import { CLIENT_CAPS } from "@getpaseo/protocol/client-capabilities";
8
7
  import { serializeAgentStreamEvent, } from "./messages.js";
9
8
  import { TerminalSessionController } from "../terminal/terminal-session-controller.js";
@@ -14,55 +13,54 @@ import { ensureAgentLoaded } from "./agent/agent-loading.js";
14
13
  import { formatSystemNotificationPrompt, sendPromptToAgent, waitForAgentRunStartWithTimeout, unarchiveAgentState, } from "./agent/agent-prompt.js";
15
14
  import { resolveCreateAgentTitles, resolveFirstAgentPromptTitle, } from "./agent/create-agent-title.js";
16
15
  import { respondToAgentPermission } from "./agent/permission-response.js";
17
- import { experimental_createMCPClient } from "ai";
18
- import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
19
- import { buildWorkspaceScriptPayloads, readPaseoConfigForProjection, } from "./script-status-projection.js";
20
- import { deriveProjectSlug } from "./workspace-git-metadata.js";
21
16
  import { spawnWorkspaceScript } from "./worktree-bootstrap.js";
17
+ import { createWorkspaceScriptsService, } from "./session/workspace-scripts/workspace-scripts-service.js";
22
18
  import { getErrorMessage, getErrorMessageOr } from "@getpaseo/protocol/error-utils";
23
19
  import { getAgentStatusPriority } from "@getpaseo/protocol/agent-state-bucket";
24
20
  import { getParentAgentIdFromLabels } from "@getpaseo/protocol/agent-labels";
21
+ import { CLIENT_SHUTDOWN_RPC_REASON, normalizeClientRestartRpcReason, } from "./lifecycle-reasons.js";
25
22
  import { createAgentCommand } from "./agent/create-agent/create.js";
26
23
  import { archiveAgentCommand, cancelAgentRunCommand, closeAgentCommand, detachAgentCommand, setAgentModeCommand, updateAgentCommand, } from "./agent/lifecycle-command.js";
27
- import { buildStoredAgentPayload, resolveEffectiveThinkingOptionId, resolveStoredAgentPayloadUpdatedAt, toAgentPayload, } from "./agent/agent-projections.js";
24
+ import { buildStoredAgentPayload, resolveStoredAgentPayloadUpdatedAt, toAgentPayload, } from "./agent/agent-projections.js";
28
25
  import { appendTimelineItemIfAgentKnown, emitLiveTimelineItemIfAgentKnown, } from "./agent/timeline-append.js";
29
26
  import { projectTimelineRows, selectProjectedTimelinePage, } from "./agent/timeline-projection.js";
30
- import { StructuredAgentFallbackError, StructuredAgentResponseError, generateStructuredAgentResponseWithFallback, } from "./agent/agent-response-loop.js";
31
- import { resolveStructuredGenerationProviders, } from "./agent/structured-generation-providers.js";
27
+ import { buildAgentForkContextAttachment } from "./agent/activity-curator.js";
32
28
  import { getAgentStreamEventTurnId, } from "./agent/agent-sdk-types.js";
33
29
  import { ImportSessionsRequestError, importProviderSession, listImportableProviderSessions, normalizeImportAgentRequest, } from "./agent/import-sessions.js";
34
- import { checkoutLiteFromGitSnapshot, classifyDirectoryForProjectMembership, deriveWorkspaceDisplayName, generateWorkspaceId, } from "./workspace-registry-model.js";
30
+ import { checkoutLiteFromGitSnapshot, deriveWorkspaceDisplayName, } from "./workspace-registry-model.js";
35
31
  import { resolveWorkspaceIdForPath } from "./resolve-workspace-id-for-path.js";
36
- import { createPersistedProjectRecord, createPersistedWorkspaceRecord, resolveProjectDisplayName, resolveWorkspaceDisplayName, resolveWorkspaceName, } from "./workspace-registry.js";
32
+ import { resolveProjectDisplayName, resolveWorkspaceDisplayName, resolveWorkspaceName, } from "./workspace-registry.js";
37
33
  import { wrapSpokenInput } from "./voice-config.js";
38
34
  import { isVoicePermissionAllowed } from "./voice-permission-policy.js";
39
35
  import { VoiceSession } from "./session/voice/voice-session.js";
40
36
  import { CheckoutSession } from "./session/checkout/checkout-session.js";
37
+ import { createWorkspaceGitObserverService, } from "./session/workspace-git-observer/workspace-git-observer-service.js";
38
+ import { createAgentStructuredTextGeneration, createGitMetadataGenerator, } from "./session/checkout/git-metadata-generator.js";
41
39
  import { ChatScheduleLoopSession } from "./session/chat/chat-schedule-loop-session.js";
42
40
  import { ProviderCatalogSession } from "./session/provider/provider-catalog-session.js";
43
41
  import { WorkspaceFilesSession } from "./session/files/workspace-files-session.js";
44
42
  import { AgentConfigSession } from "./session/agent-config/agent-config-session.js";
45
43
  import { ProjectConfigSession } from "./session/project-config/project-config-session.js";
46
44
  import { DaemonSession } from "./session/daemon/daemon-session.js";
47
- import { buildMetadataPrompt } from "../utils/build-metadata-prompt.js";
48
45
  import { archivePersistedWorkspaceRecord, archiveWorkspaceContents, } from "./workspace-archive-service.js";
49
46
  import { WorkspaceReconciliationService } from "./workspace-reconciliation-service.js";
50
- import { checkoutResolvedBranch, renameCurrentBranch as renameCurrentBranchDefault, } from "../utils/checkout-git.js";
47
+ import { renameCurrentBranch as renameCurrentBranchDefault } from "../utils/checkout-git.js";
48
+ import { createGitMutationService, } from "./session/git-mutation/git-mutation-service.js";
49
+ import { createWorkspaceProvisioningService, } from "./session/workspace-provisioning/workspace-provisioning-service.js";
50
+ import { createAgentUpdatesService, matchesAgentUpdatesFilter, } from "./session/agent-updates/agent-updates-service.js";
51
51
  import { expandTilde } from "../utils/path.js";
52
52
  import { searchHomeDirectories, searchWorkspaceEntries } from "../utils/directory-suggestions.js";
53
- import { execCommand } from "../utils/spawn.js";
54
53
  import { createGitHubService } from "../services/github-service.js";
55
54
  import { summarizeFetchWorkspacesEntries, workspaceIdsOnCheckout, WorkspaceDirectory, } from "./workspace-directory.js";
56
55
  import { shouldEmitPendingBootstrapUpdate } from "./workspace-bootstrap-dedupe.js";
57
56
  import { attemptFirstAgentBranchAutoName, createLocalCheckoutWorkspace, createPaseoWorktree, } from "./paseo-worktree-service.js";
58
57
  import { generateBranchNameFromFirstAgentContext, } from "./worktree-branch-name-generator.js";
59
- 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";
58
+ import { buildAgentSessionConfig as buildWorktreeAgentSessionConfig, createPaseoWorktreeWorkflow as createWorktreeWorkflow, handleCreatePaseoWorktreeRequest as handleCreateWorktreeRequest, handlePaseoWorktreeArchiveRequest as handleWorktreeArchiveRequest, handlePaseoWorktreeListRequest as handleWorktreeListRequest, handleWorkspaceSetupStatusRequest as handleWorkspaceSetupStatusRequestMessage, } from "./worktree-session.js";
60
59
  import { archiveByScope } from "./workspace-archive-service.js";
61
60
  import { WorktreeRequestError, toWorktreeRequestError, toWorktreeWireError, } from "./worktree-errors.js";
62
61
  import { createWorktree } from "../utils/worktree.js";
63
62
  import { runGitCommand } from "../utils/run-git-command.js";
64
63
  import { CreateAgentLifecycleDispatch } from "./agent/create-agent-lifecycle-dispatch.js";
65
- const WORKSPACE_GIT_WATCH_REMOVED_STATE_KEY = "__removed__";
66
64
  // TODO: Remove once all app store clients are on >=0.1.45 and understand arbitrary provider strings.
67
65
  // Clients before 0.1.45 validate providers with z.enum(["claude", "codex", "opencode"]) and reject
68
66
  // the entire session message if they encounter an unknown provider.
@@ -83,13 +81,6 @@ function resolveSubscriptionId(subscribe, requestedSubscriptionId) {
83
81
  }
84
82
  return uuidv4();
85
83
  }
86
- function diffChangeTypeFor(file) {
87
- if (file.isNew)
88
- return "A";
89
- if (file.isDeleted)
90
- return "D";
91
- return "M";
92
- }
93
84
  function buildWorkspaceCheckout(workspace, project,
94
85
  // The persisted `branch` field is the source of truth, but it is null for
95
86
  // records created before branch was lifted to its own field (no migrations,
@@ -201,19 +192,12 @@ function describeRegistryTransition(record) {
201
192
  */
202
193
  export class Session {
203
194
  constructor(options) {
204
- // Per-session MCP client and tools
205
- this.agentMcpClient = null;
206
- this.agentTools = null;
207
195
  this.unsubscribeAgentEvents = null;
208
196
  this.unsubscribeTerminalWorkspaceContributionEvents = null;
209
- this.agentUpdatesSubscription = null;
210
197
  this.workspaceUpdatesSubscription = null;
211
198
  this.clientActivity = null;
212
199
  this.inflightRequests = 0;
213
200
  this.peakInflightRequests = 0;
214
- this.workspaceGitWatchTargets = new Map();
215
- this.workspaceGitFetchSubscriptions = new Map();
216
- this.workspaceGitSubscriptions = new Map();
217
201
  this.agentsPager = new SortablePager({
218
202
  validKeys: FETCH_AGENTS_SORT_KEYS,
219
203
  defaultSort: [{ key: "updated_at", direction: "desc" }],
@@ -237,7 +221,7 @@ export class Session {
237
221
  }
238
222
  },
239
223
  });
240
- 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;
224
+ 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, stt, sttLanguage, tts, terminalManager, providerSnapshotManager, providerUsageService, serviceProxy, scriptRuntimeStore, workspaceSetupSnapshots, onBranchChanged, getDaemonTcpPort, getDaemonTcpHost, serviceProxyPublicBaseUrl, resolveScriptHealth, voice, voiceBridge, dictation, serverId, daemonVersion, daemonRuntimeConfig, getWebSocketRuntimeMetrics, } = options;
241
225
  this.clientId = clientId;
242
226
  this.appVersion = appVersion ?? null;
243
227
  this.clientCapabilities = parseClientCapabilities(clientCapabilities);
@@ -273,24 +257,49 @@ export class Session {
273
257
  this.renameCurrentBranch = renameCurrentBranch ?? renameCurrentBranchDefault;
274
258
  this.generateWorkspaceName = generateWorkspaceName ?? generateBranchNameFromFirstAgentContext;
275
259
  this.workspaceGitService = workspaceGitService;
260
+ this.gitMutation = createGitMutationService({
261
+ workspaceGitService: this.workspaceGitService,
262
+ github: this.github,
263
+ logger: this.sessionLogger,
264
+ });
265
+ this.workspaceProvisioning = createWorkspaceProvisioningService({
266
+ workspaceRegistry: this.workspaceRegistry,
267
+ projectRegistry: this.projectRegistry,
268
+ workspaceGitService: this.workspaceGitService,
269
+ });
276
270
  this.checkoutSession = new CheckoutSession({
277
271
  host: {
278
272
  emit: (msg) => this.emit(msg),
279
- notifyGitMutation: (cwd, reason, mutationOptions) => this.notifyGitMutation(cwd, reason, mutationOptions),
280
273
  emitWorkspaceUpdateForCwd: (cwd) => this.emitWorkspaceUpdateForCwd(cwd),
281
- handleWorkspaceGitBranchSnapshot: (cwd, branchName) => this.handleWorkspaceGitBranchSnapshot(cwd, branchName),
274
+ handleWorkspaceGitBranchSnapshot: (cwd, branchName) => this.workspaceGitObserver.handleBranchSnapshot(cwd, branchName),
282
275
  renameCurrentBranch: (cwd, branch) => this.renameCurrentBranch(cwd, branch),
283
- checkoutExistingBranch: (cwd, branch) => this.checkoutExistingBranch(cwd, branch),
284
- generateCommitMessage: (cwd) => this.generateCommitMessage(cwd),
285
- generatePullRequestText: (cwd, baseRef) => this.generatePullRequestText(cwd, baseRef),
286
276
  },
277
+ gitMutation: this.gitMutation,
287
278
  workspaceGitService: this.workspaceGitService,
288
279
  github: this.github,
289
280
  checkoutDiffManager,
281
+ gitMetadataGenerator: createGitMetadataGenerator({
282
+ workspaceGitService: this.workspaceGitService,
283
+ generation: createAgentStructuredTextGeneration({
284
+ agentManager: this.agentManager,
285
+ providerSnapshotManager,
286
+ readDaemonConfig: () => this.readStructuredGenerationDaemonConfig(),
287
+ getFocusedSelection: (cwd) => this.getFocusedAgentSelectionForCwd(cwd),
288
+ }),
289
+ }),
290
290
  paseoHome: this.paseoHome,
291
291
  worktreesRoot: this.worktreesRoot,
292
292
  logger: this.sessionLogger,
293
293
  });
294
+ this.workspaceGitObserver = createWorkspaceGitObserverService({
295
+ workspaceGitService: this.workspaceGitService,
296
+ describeWorkspaceRecordWithGitData: (workspace) => this.describeWorkspaceRecordWithGitData(workspace),
297
+ emitWorkspaceUpdateForCwd: (cwd) => this.emitWorkspaceUpdateForCwd(cwd),
298
+ emitWorkspaceUpdateForWorkspaceId: (workspaceId) => this.emitWorkspaceUpdateForWorkspaceId(workspaceId),
299
+ emitStatusUpdate: (cwd, snapshot) => this.checkoutSession.emitStatusUpdate(cwd, snapshot),
300
+ onBranchChanged,
301
+ logger: this.sessionLogger,
302
+ });
294
303
  this.chatScheduleLoopSession = new ChatScheduleLoopSession({
295
304
  host: {
296
305
  emit: (msg) => this.emit(msg),
@@ -348,16 +357,21 @@ export class Session {
348
357
  this.daemonSession = new DaemonSession({
349
358
  host: {
350
359
  emit: (msg) => this.emit(msg),
360
+ emitLifecycleIntent: (intent) => this.emitLifecycleIntent(intent),
351
361
  },
362
+ clientId: this.clientId,
352
363
  paseoHome: this.paseoHome,
353
364
  serverId,
354
365
  daemonVersion,
355
366
  daemonRuntimeConfig,
367
+ getWebSocketRuntimeMetrics,
356
368
  listProviderAvailability: () => this.agentManager.listProviderAvailability(),
369
+ listAgents: () => this.agentManager.listAgents(),
370
+ listProjects: () => this.projectRegistry.list(),
371
+ listWorkspaces: () => this.workspaceRegistry.list(),
357
372
  logger: this.sessionLogger,
358
373
  });
359
374
  this.daemonConfigStore = daemonConfigStore;
360
- this.mcpBaseUrl = mcpBaseUrl ?? null;
361
375
  this.terminalManager = terminalManager;
362
376
  this.terminalController = new TerminalSessionController({
363
377
  terminalManager,
@@ -370,6 +384,15 @@ export class Session {
370
384
  clientSupportsWrapReflow: () => this.clientCapabilities.has(CLIENT_CAPS.terminalReflowableSnapshot),
371
385
  getClientBufferedAmount: () => this.getTransportBufferedAmount(),
372
386
  });
387
+ this.agentUpdates = createAgentUpdatesService({
388
+ emit: (message) => this.emit(message),
389
+ buildAgentPayload: (agent) => this.buildAgentPayload(agent),
390
+ buildStoredAgentPayload: (record) => this.buildStoredAgentPayload(record),
391
+ isProviderVisibleToClient: (provider) => this.isProviderVisibleToClient(provider),
392
+ buildProjectPlacementForWorkspaceId: (workspaceId) => this.buildProjectPlacementForWorkspaceId(workspaceId),
393
+ emitWorkspaceUpdateForWorkspaceId: (workspaceId) => this.emitWorkspaceUpdateForWorkspaceId(workspaceId),
394
+ logger: this.sessionLogger,
395
+ });
373
396
  this.createAgentLifecycleDispatch = new CreateAgentLifecycleDispatch({
374
397
  paseoHome: this.paseoHome,
375
398
  worktreesRoot: this.worktreesRoot,
@@ -383,14 +406,7 @@ export class Session {
383
406
  listActiveWorkspaces: () => this.listActiveWorkspaceRefs(),
384
407
  archiveWorkspaceRecord: (workspaceId) => this.archiveWorkspaceRecord(workspaceId),
385
408
  emit: (message) => this.emit(message),
386
- emitAgentRemove: (agentId) => {
387
- if (this.agentUpdatesSubscription) {
388
- this.bufferOrEmitAgentUpdate(this.agentUpdatesSubscription, {
389
- kind: "remove",
390
- agentId,
391
- });
392
- }
393
- },
409
+ emitAgentRemove: (agentId) => this.agentUpdates.removeAgent(agentId),
394
410
  emitWorkspaceUpdatesForWorkspaceIds: (workspaceIds) => this.emitWorkspaceUpdatesForWorkspaceIds(workspaceIds),
395
411
  markWorkspaceArchiving: (workspaceIds, archivingAt) => this.markWorkspaceArchiving(workspaceIds, archivingAt),
396
412
  clearWorkspaceArchiving: (workspaceIds) => this.clearWorkspaceArchiving(workspaceIds),
@@ -401,11 +417,24 @@ export class Session {
401
417
  this.serviceProxy = serviceProxy ?? null;
402
418
  this.scriptRuntimeStore = scriptRuntimeStore ?? null;
403
419
  this.workspaceSetupSnapshots = workspaceSetupSnapshots ?? new Map();
404
- this.onBranchChanged = onBranchChanged;
405
420
  this.getDaemonTcpPort = getDaemonTcpPort ?? null;
406
421
  this.getDaemonTcpHost = getDaemonTcpHost ?? null;
407
422
  this.serviceProxyPublicBaseUrl = serviceProxyPublicBaseUrl ?? null;
408
423
  this.resolveScriptHealth = resolveScriptHealth ?? null;
424
+ this.workspaceScripts = createWorkspaceScriptsService({
425
+ serviceProxy: this.serviceProxy,
426
+ scriptRuntimeStore: this.scriptRuntimeStore,
427
+ terminalManager: this.terminalManager,
428
+ workspaceRegistry: this.workspaceRegistry,
429
+ workspaceGitService: this.workspaceGitService,
430
+ getDaemonTcpPort: this.getDaemonTcpPort,
431
+ getDaemonTcpHost: this.getDaemonTcpHost,
432
+ serviceProxyPublicBaseUrl: this.serviceProxyPublicBaseUrl,
433
+ resolveScriptHealth: this.resolveScriptHealth,
434
+ logger: this.sessionLogger,
435
+ emit: (message) => this.emit(message),
436
+ spawnWorkspaceScript,
437
+ });
409
438
  this.subscribeToOptionalManagers();
410
439
  this.workspaceDirectory = new WorkspaceDirectory({
411
440
  logger: this.sessionLogger,
@@ -440,8 +469,6 @@ export class Session {
440
469
  voiceBridge,
441
470
  dictation,
442
471
  });
443
- // Initialize agent MCP client asynchronously
444
- void this.initializeAgentMcp();
445
472
  this.subscribeToAgentEvents();
446
473
  this.sessionLogger.trace({}, "agent.session.lifecycle.created");
447
474
  }
@@ -457,8 +484,7 @@ export class Session {
457
484
  return this.clientCapabilities.has(capability);
458
485
  }
459
486
  async syncWorkspaceGitObserverForWorkspace(workspace) {
460
- const descriptor = await this.describeWorkspaceRecordWithGitData(workspace);
461
- this.syncWorkspaceGitObservers([descriptor]);
487
+ await this.workspaceGitObserver.syncObserverForWorkspace(workspace);
462
488
  }
463
489
  async emitWorkspaceUpdateForWorkspaceId(workspaceId) {
464
490
  await this.emitWorkspaceUpdatesForWorkspaceIds([workspaceId], { skipReconcile: true });
@@ -476,8 +502,7 @@ export class Session {
476
502
  await this.emitWorkspaceUpdatesForWorkspaceIds(workspaceIds);
477
503
  }
478
504
  async warmWorkspaceGitDataForWorkspace(workspace) {
479
- await this.syncWorkspaceGitObserverForWorkspace(workspace);
480
- await this.emitWorkspaceUpdateForWorkspaceId(workspace.workspaceId);
505
+ await this.workspaceGitObserver.warmGitData(workspace);
481
506
  }
482
507
  /**
483
508
  * Get the client's current activity state
@@ -592,30 +617,6 @@ export class Session {
592
617
  },
593
618
  });
594
619
  }
595
- /**
596
- * Initialize Agent MCP client for this session using the daemon's HTTP MCP endpoint.
597
- */
598
- async initializeAgentMcp() {
599
- try {
600
- if (!this.mcpBaseUrl) {
601
- this.sessionLogger.info("Skipping Agent MCP initialization because no MCP base URL is configured");
602
- return;
603
- }
604
- const authToken = this.agentManager.getMcpAuthToken();
605
- const transport = new StreamableHTTPClientTransport(new URL(this.mcpBaseUrl), authToken
606
- ? { requestInit: { headers: { Authorization: `Bearer ${authToken}` } } }
607
- : undefined);
608
- this.agentMcpClient = await experimental_createMCPClient({
609
- transport,
610
- });
611
- this.agentTools = (await this.agentMcpClient.tools());
612
- const agentToolCount = Object.keys(this.agentTools ?? {}).length;
613
- this.sessionLogger.trace({ agentToolCount }, "agent.session.mcp_init");
614
- }
615
- catch (error) {
616
- this.sessionLogger.error({ err: error }, "Failed to initialize Agent MCP");
617
- }
618
- }
619
620
  /**
620
621
  * Subscribe to AgentManager events and forward them to the client
621
622
  */
@@ -644,7 +645,7 @@ export class Session {
644
645
  turnId: event.agent.activeForegroundTurnId ?? undefined,
645
646
  lifecycle: event.agent.lifecycle,
646
647
  }, "agent.session.forward_update");
647
- void this.forwardAgentUpdate(event.agent);
648
+ void this.agentUpdates.forwardLiveAgent(event.agent);
648
649
  return;
649
650
  }
650
651
  if (this.voiceSession.isActiveForAgent(event.agentId) &&
@@ -734,148 +735,6 @@ export class Session {
734
735
  }
735
736
  return LEGACY_PROVIDER_IDS.has(provider);
736
737
  }
737
- agentThinkingOptionMatchesFilter(agent, filter) {
738
- if (filter.thinkingOptionId === undefined) {
739
- return true;
740
- }
741
- const expectedThinkingOptionId = resolveEffectiveThinkingOptionId({
742
- configuredThinkingOptionId: filter.thinkingOptionId ?? null,
743
- });
744
- const resolvedThinkingOptionId = agent.effectiveThinkingOptionId ??
745
- resolveEffectiveThinkingOptionId({
746
- runtimeInfo: agent.runtimeInfo,
747
- configuredThinkingOptionId: agent.thinkingOptionId ?? null,
748
- });
749
- return resolvedThinkingOptionId === expectedThinkingOptionId;
750
- }
751
- matchesAgentStructuralFilter(agent, project, filter) {
752
- if (filter.statuses && filter.statuses.length > 0) {
753
- const statuses = new Set(filter.statuses);
754
- if (!statuses.has(agent.status)) {
755
- return false;
756
- }
757
- }
758
- if (typeof filter.requiresAttention === "boolean") {
759
- const requiresAttention = agent.requiresAttention ?? false;
760
- if (requiresAttention !== filter.requiresAttention) {
761
- return false;
762
- }
763
- }
764
- if (filter.projectKeys && filter.projectKeys.length > 0) {
765
- const projectKeys = new Set(filter.projectKeys.filter((item) => item.trim().length > 0));
766
- if (projectKeys.size > 0 && !projectKeys.has(project.projectKey)) {
767
- return false;
768
- }
769
- }
770
- return true;
771
- }
772
- matchesAgentFilter(options) {
773
- const { agent, project, filter } = options;
774
- if (filter?.labels) {
775
- const matchesLabels = Object.entries(filter.labels).every(([key, value]) => agent.labels[key] === value);
776
- if (!matchesLabels) {
777
- return false;
778
- }
779
- }
780
- const includeArchived = filter?.includeArchived ?? false;
781
- if (!includeArchived && agent.archivedAt) {
782
- return false;
783
- }
784
- if (filter && !this.agentThinkingOptionMatchesFilter(agent, filter)) {
785
- return false;
786
- }
787
- if (filter && !this.matchesAgentStructuralFilter(agent, project, filter)) {
788
- return false;
789
- }
790
- return true;
791
- }
792
- getAgentUpdateTargetId(update) {
793
- return update.kind === "remove" ? update.agentId : update.agent.id;
794
- }
795
- bufferOrEmitAgentUpdate(subscription, payload) {
796
- if (payload.kind === "upsert" && !this.isProviderVisibleToClient(payload.agent.provider)) {
797
- return;
798
- }
799
- if (subscription.isBootstrapping) {
800
- subscription.pendingUpdatesByAgentId.set(this.getAgentUpdateTargetId(payload), payload);
801
- return;
802
- }
803
- this.emit({
804
- type: "agent_update",
805
- payload,
806
- });
807
- }
808
- async emitStoredAgentUpdate(record) {
809
- const payload = this.buildStoredAgentPayload(record);
810
- const subscription = this.agentUpdatesSubscription;
811
- if (!subscription) {
812
- return payload;
813
- }
814
- const project = payload.workspaceId
815
- ? await this.buildProjectPlacementForWorkspaceId(payload.workspaceId)
816
- : null;
817
- if (!project) {
818
- this.bufferOrEmitAgentUpdate(subscription, {
819
- kind: "remove",
820
- agentId: payload.id,
821
- });
822
- return payload;
823
- }
824
- const matches = this.matchesAgentFilter({
825
- agent: payload,
826
- project,
827
- filter: subscription.filter,
828
- });
829
- this.bufferOrEmitAgentUpdate(subscription, matches
830
- ? {
831
- kind: "upsert",
832
- agent: payload,
833
- project,
834
- }
835
- : {
836
- kind: "remove",
837
- agentId: payload.id,
838
- });
839
- return payload;
840
- }
841
- flushBootstrappedAgentUpdates(options) {
842
- const subscription = this.agentUpdatesSubscription;
843
- if (!subscription || !subscription.isBootstrapping) {
844
- return;
845
- }
846
- subscription.isBootstrapping = false;
847
- const pending = Array.from(subscription.pendingUpdatesByAgentId.values());
848
- subscription.pendingUpdatesByAgentId.clear();
849
- for (const payload of pending) {
850
- if (payload.kind === "upsert") {
851
- const snapshotUpdatedAt = options?.snapshotUpdatedAtByAgentId?.get(payload.agent.id);
852
- if (typeof snapshotUpdatedAt === "number") {
853
- const updateUpdatedAt = Date.parse(payload.agent.updatedAt);
854
- if (!Number.isNaN(updateUpdatedAt) && updateUpdatedAt <= snapshotUpdatedAt) {
855
- continue;
856
- }
857
- }
858
- }
859
- this.emit({
860
- type: "agent_update",
861
- payload,
862
- });
863
- }
864
- }
865
- async findExactWorkspaceByDirectory(cwd, options) {
866
- const normalizedCwd = await this.resolveWorkspaceDirectory(cwd, options);
867
- const workspaces = await this.workspaceRegistry.list();
868
- return workspaces.find((workspace) => workspace.cwd === normalizedCwd) ?? null;
869
- }
870
- async resolveWorkspaceDirectory(cwd, options) {
871
- const normalizedCwd = resolve(cwd);
872
- if (options?.refreshGit === false) {
873
- const snapshot = this.workspaceGitService.peekSnapshot(normalizedCwd);
874
- return resolve(snapshot?.git.repoRoot ?? normalizedCwd);
875
- }
876
- const checkout = await this.workspaceGitService.getCheckout(normalizedCwd);
877
- return resolve(checkout.worktreeRoot ?? normalizedCwd);
878
- }
879
738
  async buildProjectPlacementForWorkspace(workspace, projectRecord) {
880
739
  const project = projectRecord ?? (await this.projectRegistry.get(workspace.projectId));
881
740
  if (!project) {
@@ -891,12 +750,6 @@ export class Session {
891
750
  };
892
751
  }
893
752
  async buildProjectPlacementForWorkspaceId(workspaceId) {
894
- const workspace = await this.workspaceRegistry.get(workspaceId);
895
- if (!workspace)
896
- return null;
897
- return this.buildProjectPlacementForWorkspace(workspace);
898
- }
899
- async buildProjectPlacementForExistingWorkspaceProject(workspaceId) {
900
753
  const workspace = await this.workspaceRegistry.get(workspaceId);
901
754
  if (!workspace)
902
755
  return null;
@@ -905,51 +758,6 @@ export class Session {
905
758
  return null;
906
759
  return this.buildProjectPlacementForWorkspace(workspace, project);
907
760
  }
908
- async forwardAgentUpdate(agent) {
909
- try {
910
- const subscription = this.agentUpdatesSubscription;
911
- const payload = await this.buildAgentPayload(agent);
912
- if (subscription) {
913
- const project = payload.workspaceId
914
- ? await this.buildProjectPlacementForWorkspaceId(payload.workspaceId)
915
- : null;
916
- if (!project) {
917
- this.bufferOrEmitAgentUpdate(subscription, {
918
- kind: "remove",
919
- agentId: payload.id,
920
- });
921
- }
922
- else {
923
- const matches = this.matchesAgentFilter({
924
- agent: payload,
925
- project,
926
- filter: subscription.filter,
927
- });
928
- if (matches) {
929
- this.bufferOrEmitAgentUpdate(subscription, {
930
- kind: "upsert",
931
- agent: payload,
932
- project,
933
- });
934
- }
935
- else {
936
- this.bufferOrEmitAgentUpdate(subscription, {
937
- kind: "remove",
938
- agentId: payload.id,
939
- });
940
- }
941
- }
942
- }
943
- // A lifecycle change updates exactly the agent's owning workspace, never
944
- // every workspace sharing its cwd. Ownership is the agent's workspaceId.
945
- if (payload.workspaceId) {
946
- await this.emitWorkspaceUpdateForWorkspaceId(payload.workspaceId);
947
- }
948
- }
949
- catch (error) {
950
- this.sessionLogger.error({ err: error }, "Failed to emit agent update");
951
- }
952
- }
953
761
  /**
954
762
  * Main entry point for processing session messages
955
763
  */
@@ -1005,6 +813,7 @@ export class Session {
1005
813
  const promise = this.dispatchVoiceAndControlMessage(msg) ??
1006
814
  this.dispatchAgentRewindMessage(msg) ??
1007
815
  this.dispatchAgentRelationshipMessage(msg) ??
816
+ this.dispatchAgentTimelineMessage(msg) ??
1008
817
  this.dispatchAgentLifecycleMessage(msg) ??
1009
818
  this.dispatchAgentConfigMessage(msg) ??
1010
819
  this.dispatchCheckoutMessage(msg) ??
@@ -1081,6 +890,16 @@ export class Session {
1081
890
  return undefined;
1082
891
  }
1083
892
  }
893
+ dispatchAgentTimelineMessage(msg) {
894
+ switch (msg.type) {
895
+ case "fetch_agent_timeline_request":
896
+ return this.handleFetchAgentTimelineRequest(msg);
897
+ case "agent.fork_context.request":
898
+ return this.handleAgentForkContextRequest(msg);
899
+ default:
900
+ return undefined;
901
+ }
902
+ }
1084
903
  dispatchAgentLifecycleMessage(msg) {
1085
904
  switch (msg.type) {
1086
905
  case "fetch_agents_request":
@@ -1115,8 +934,6 @@ export class Session {
1115
934
  return this.handleRefreshAgentRequest(msg);
1116
935
  case "cancel_agent_request":
1117
936
  return this.handleCancelAgentRequest(msg.agentId, msg.requestId);
1118
- case "fetch_agent_timeline_request":
1119
- return this.handleFetchAgentTimelineRequest(msg);
1120
937
  case "agent_permission_response":
1121
938
  return this.handleAgentPermissionResponse(msg.agentId, msg.requestId, msg.response);
1122
939
  case "clear_agent_attention":
@@ -1145,6 +962,10 @@ export class Session {
1145
962
  return this.daemonSession.handleGetStatusRequest(msg);
1146
963
  case "daemon.get_pairing_offer.request":
1147
964
  return this.daemonSession.handleGetPairingOfferRequest(msg);
965
+ case "diagnostics.request":
966
+ return this.daemonSession.handleDiagnosticsRequest(msg);
967
+ case "daemon.update.request":
968
+ return this.daemonSession.handleUpdateRequest(msg);
1148
969
  case "set_daemon_config_request":
1149
970
  this.emit({
1150
971
  type: "set_daemon_config_response",
@@ -1352,6 +1173,9 @@ export class Session {
1352
1173
  resetPeakInflight() {
1353
1174
  this.peakInflightRequests = this.inflightRequests;
1354
1175
  }
1176
+ getSessionId() {
1177
+ return this.sessionId;
1178
+ }
1355
1179
  async handleBinaryFrame(binaryFrame) {
1356
1180
  if (binaryFrame.kind === "file_transfer") {
1357
1181
  await this.workspaceFilesSession.handleFileTransferFrame(binaryFrame.frame);
@@ -1360,6 +1184,7 @@ export class Session {
1360
1184
  this.terminalController.handleBinaryFrame(binaryFrame.frame);
1361
1185
  }
1362
1186
  async handleRestartServerRequest(requestId, reason) {
1187
+ const lifecycleReason = normalizeClientRestartRpcReason(reason);
1363
1188
  const payload = {
1364
1189
  status: "restart_requested",
1365
1190
  clientId: this.clientId,
@@ -1368,7 +1193,7 @@ export class Session {
1368
1193
  payload.reason = reason;
1369
1194
  }
1370
1195
  payload.requestId = requestId;
1371
- this.sessionLogger.warn({ reason }, "Restart requested via websocket");
1196
+ this.sessionLogger.warn({ reason: lifecycleReason }, "Restart requested via websocket");
1372
1197
  this.emit({
1373
1198
  type: "status",
1374
1199
  payload,
@@ -1377,11 +1202,12 @@ export class Session {
1377
1202
  type: "restart",
1378
1203
  clientId: this.clientId,
1379
1204
  requestId,
1380
- ...(reason ? { reason } : {}),
1205
+ reason: lifecycleReason,
1381
1206
  });
1382
1207
  }
1383
1208
  async handleShutdownServerRequest(requestId) {
1384
- this.sessionLogger.warn("Shutdown requested via websocket");
1209
+ const reason = CLIENT_SHUTDOWN_RPC_REASON;
1210
+ this.sessionLogger.warn({ reason }, "Shutdown requested via websocket");
1385
1211
  this.emit({
1386
1212
  type: "status",
1387
1213
  payload: {
@@ -1394,6 +1220,7 @@ export class Session {
1394
1220
  type: "shutdown",
1395
1221
  clientId: this.clientId,
1396
1222
  requestId,
1223
+ reason,
1397
1224
  });
1398
1225
  }
1399
1226
  emitLifecycleIntent(intent) {
@@ -1437,12 +1264,7 @@ export class Session {
1437
1264
  requestId,
1438
1265
  },
1439
1266
  });
1440
- if (this.agentUpdatesSubscription) {
1441
- this.bufferOrEmitAgentUpdate(this.agentUpdatesSubscription, {
1442
- kind: "remove",
1443
- agentId,
1444
- });
1445
- }
1267
+ this.agentUpdates.removeAgent(agentId);
1446
1268
  if (knownWorkspaceId) {
1447
1269
  await this.emitWorkspaceUpdateForWorkspaceId(knownWorkspaceId);
1448
1270
  }
@@ -1465,8 +1287,8 @@ export class Session {
1465
1287
  agentStorage: this.agentStorage,
1466
1288
  logger: this.sessionLogger,
1467
1289
  }, agentId);
1468
- if (this.agentUpdatesSubscription) {
1469
- const payload = await this.emitStoredAgentUpdate(archivedRecord);
1290
+ if (this.agentUpdates.hasSubscription()) {
1291
+ const payload = await this.agentUpdates.emitStoredRecord(archivedRecord);
1470
1292
  if (payload.workspaceId) {
1471
1293
  await this.emitWorkspaceUpdateForWorkspaceId(payload.workspaceId);
1472
1294
  }
@@ -1479,7 +1301,7 @@ export class Session {
1479
1301
  const result = await detachAgentCommand({ agentManager: this.agentManager }, agentId);
1480
1302
  const affectedWorkspaceIds = new Set();
1481
1303
  if (!result.live) {
1482
- const payload = await this.emitStoredAgentUpdate(result.record);
1304
+ const payload = await this.agentUpdates.emitStoredRecord(result.record);
1483
1305
  if (payload.workspaceId) {
1484
1306
  affectedWorkspaceIds.add(payload.workspaceId);
1485
1307
  }
@@ -1878,12 +1700,13 @@ export class Session {
1878
1700
  const createAgentConfig = createdWorktree
1879
1701
  ? { ...config, cwd: createdWorktree.worktree.worktreePath }
1880
1702
  : config;
1881
- const workspaceId = await this.resolveOrCreateWorkspaceIdForCreateAgent({
1703
+ const workspaceId = await this.workspaceProvisioning.resolveOrCreateWorkspaceIdForCreateAgent({
1882
1704
  createdWorktree,
1883
1705
  requestedWorkspaceId: msg.workspaceId,
1884
1706
  cwd: createAgentConfig.cwd,
1885
1707
  initialTitle: workspacePromptTitle,
1886
1708
  });
1709
+ const createdDirectoryWorkspaceForAgent = !createdWorktree && !msg.workspaceId;
1887
1710
  const { snapshot, liveSnapshot } = await createAgentCommand({
1888
1711
  agentManager: this.agentManager,
1889
1712
  agentStorage: this.agentStorage,
@@ -1909,11 +1732,8 @@ export class Session {
1909
1732
  buildSessionConfig: (sessionConfig, gitOptions, legacyWorktreeName, ctx) => this.buildAgentSessionConfig(sessionConfig, gitOptions, legacyWorktreeName, ctx),
1910
1733
  });
1911
1734
  createdAgentId = snapshot.id;
1912
- if (!createdWorktree && msg.workspaceId) {
1913
- await this.writeInitialWorkspaceTitleIfUntitled(workspaceId, workspacePromptTitle);
1914
- }
1915
- await this.forwardAgentUpdate(snapshot);
1916
- if (!createdWorktree && trimmedPrompt) {
1735
+ await this.agentUpdates.forwardLiveAgent(snapshot);
1736
+ if (createdDirectoryWorkspaceForAgent && trimmedPrompt) {
1917
1737
  await this.scheduleAutoNameLocalWorkspaceTitleForFirstAgent({
1918
1738
  workspaceId,
1919
1739
  cwd: createAgentConfig.cwd,
@@ -2000,7 +1820,7 @@ export class Session {
2000
1820
  const snapshot = await this.agentManager.resumeAgentFromPersistence(handle, overrides);
2001
1821
  await unarchiveAgentState(this.agentStorage, this.agentManager, snapshot.id);
2002
1822
  await this.agentManager.hydrateTimelineFromProvider(snapshot.id);
2003
- await this.forwardAgentUpdate(snapshot);
1823
+ await this.agentUpdates.forwardLiveAgent(snapshot);
2004
1824
  const timelineSize = this.agentManager.getTimeline(snapshot.id).length;
2005
1825
  if (requestId) {
2006
1826
  const agentPayload = await this.buildAgentPayload(snapshot);
@@ -2062,7 +1882,7 @@ export class Session {
2062
1882
  }
2063
1883
  // An imported agent mints its own workspace; ownership is its workspaceId,
2064
1884
  // never an existing same-cwd workspace resolved by path.
2065
- const workspace = await this.createWorkspaceForDirectory(normalized.cwd);
1885
+ const workspace = await this.workspaceProvisioning.createWorkspaceForDirectory(normalized.cwd);
2066
1886
  const { snapshot, timelineSize } = await importProviderSession({
2067
1887
  request: normalized,
2068
1888
  workspaceId: workspace.workspaceId,
@@ -2135,7 +1955,7 @@ export class Session {
2135
1955
  snapshot = await this.agentManager.resumeAgentFromPersistence(handle, buildConfigOverrides(record), agentId, extractTimestamps(record));
2136
1956
  }
2137
1957
  await this.agentManager.hydrateTimelineFromProvider(agentId);
2138
- await this.forwardAgentUpdate(snapshot);
1958
+ await this.agentUpdates.forwardLiveAgent(snapshot);
2139
1959
  const timelineSize = this.agentManager.getTimeline(agentId).length;
2140
1960
  if (requestId) {
2141
1961
  this.emit({
@@ -2244,8 +2064,8 @@ export class Session {
2244
2064
  logger: this.sessionLogger,
2245
2065
  },
2246
2066
  }),
2247
- checkoutExistingBranch: (cwd, branch) => this.checkoutExistingBranch(cwd, branch),
2248
- createBranchFromBase: (params) => this.createBranchFromBase(params),
2067
+ checkoutExistingBranch: (cwd, branch) => this.gitMutation.checkoutExistingBranch(cwd, branch),
2068
+ createBranchFromBase: (params) => this.gitMutation.createBranchFromBase(params),
2249
2069
  github: this.github,
2250
2070
  }, config, gitOptions, legacyWorktreeName, firstAgentContext);
2251
2071
  }
@@ -2290,7 +2110,7 @@ export class Session {
2290
2110
  branch: result.branchName,
2291
2111
  promptTitle: resolveFirstAgentPromptTitle(input.firstAgentContext),
2292
2112
  });
2293
- await this.notifyGitMutation(input.workspace.cwd, "rename-branch");
2113
+ await this.gitMutation.notifyGitMutation(input.workspace.cwd, "rename-branch");
2294
2114
  await this.emitWorkspaceUpdateForCwd(input.workspace.cwd);
2295
2115
  }
2296
2116
  // Generated names may replace the prompt title set at creation, but not a user
@@ -2311,20 +2131,6 @@ export class Session {
2311
2131
  updatedAt: new Date().toISOString(),
2312
2132
  });
2313
2133
  }
2314
- async writeInitialWorkspaceTitleIfUntitled(workspaceId, title) {
2315
- if (!title) {
2316
- return;
2317
- }
2318
- const current = await this.workspaceRegistry.get(workspaceId);
2319
- if (!current || current.title) {
2320
- return;
2321
- }
2322
- await this.workspaceRegistry.upsert({
2323
- ...current,
2324
- title,
2325
- updatedAt: new Date().toISOString(),
2326
- });
2327
- }
2328
2134
  // Wraps the injected workspace-name generator for a directory workspace.
2329
2135
  async generateWorkspaceTitleFromContext(input) {
2330
2136
  return this.generateWorkspaceName({
@@ -2366,12 +2172,6 @@ export class Session {
2366
2172
  firstAgentContext: input.firstAgentContext,
2367
2173
  }), { cwd: input.cwd, message: "Failed to auto-name local workspace title" });
2368
2174
  }
2369
- assertSafeGitRef(ref, label) {
2370
- if (!/^[A-Za-z0-9._/-]+$/.test(ref)) {
2371
- throw new Error(`Invalid ${label}: ${ref}`);
2372
- }
2373
- assertWorktreeSafeGitRef(ref, label);
2374
- }
2375
2175
  isPathWithinRoot(rootPath, candidatePath) {
2376
2176
  const resolvedRoot = resolve(rootPath);
2377
2177
  const resolvedCandidate = resolve(candidatePath);
@@ -2380,220 +2180,6 @@ export class Session {
2380
2180
  }
2381
2181
  return resolvedCandidate.startsWith(resolvedRoot + sep);
2382
2182
  }
2383
- async generateCommitMessage(cwd) {
2384
- const diff = await this.workspaceGitService.getCheckoutDiff(cwd, {
2385
- mode: "uncommitted",
2386
- includeStructured: true,
2387
- });
2388
- const schema = z.object({
2389
- message: z
2390
- .string()
2391
- .min(1)
2392
- .max(72)
2393
- .describe("Concise git commit message, imperative mood, no trailing period."),
2394
- });
2395
- const fileList = diff.structured && diff.structured.length > 0
2396
- ? [
2397
- "Files changed:",
2398
- ...diff.structured.map((file) => {
2399
- const changeType = diffChangeTypeFor(file);
2400
- const status = file.status && file.status !== "ok" ? ` [${file.status}]` : "";
2401
- return `${changeType}\t${file.path}\t(+${file.additions} -${file.deletions})${status}`;
2402
- }),
2403
- ].join("\n")
2404
- : "Files changed: (unknown)";
2405
- const maxPatchChars = 120000;
2406
- const patch = diff.diff.length > maxPatchChars
2407
- ? `${diff.diff.slice(0, maxPatchChars)}\n\n... (diff truncated to ${maxPatchChars} chars)\n`
2408
- : diff.diff;
2409
- const prompt = await buildMetadataPrompt({
2410
- cwd,
2411
- workspaceGitService: this.workspaceGitService,
2412
- contract: "Write a concise git commit message for the changes below.",
2413
- styles: [
2414
- {
2415
- configKey: "commitMessage",
2416
- default: "Concise, imperative mood, no trailing period.",
2417
- },
2418
- ],
2419
- after: [
2420
- "Return JSON only with a single field 'message'.",
2421
- "",
2422
- fileList,
2423
- "",
2424
- patch.length > 0 ? patch : "(No diff available)",
2425
- ].join("\n"),
2426
- });
2427
- const providers = await resolveStructuredGenerationProviders({
2428
- cwd,
2429
- providerSnapshotManager: this.providerSnapshotManager,
2430
- daemonConfig: this.readStructuredGenerationDaemonConfig(),
2431
- currentSelection: this.getFocusedAgentSelectionForCwd(cwd),
2432
- });
2433
- try {
2434
- const result = await generateStructuredAgentResponseWithFallback({
2435
- manager: this.agentManager,
2436
- cwd,
2437
- prompt,
2438
- schema,
2439
- schemaName: "CommitMessage",
2440
- maxRetries: 2,
2441
- providers,
2442
- persistSession: false,
2443
- agentConfigOverrides: {
2444
- title: "Commit generator",
2445
- internal: true,
2446
- },
2447
- });
2448
- return result.message;
2449
- }
2450
- catch (error) {
2451
- if (error instanceof StructuredAgentResponseError ||
2452
- error instanceof StructuredAgentFallbackError) {
2453
- return "Update files";
2454
- }
2455
- throw error;
2456
- }
2457
- }
2458
- async generatePullRequestText(cwd, baseRef) {
2459
- const diff = await this.workspaceGitService.getCheckoutDiff(cwd, {
2460
- mode: "base",
2461
- baseRef,
2462
- includeStructured: true,
2463
- });
2464
- const schema = z.object({
2465
- title: z.string().min(1).max(72),
2466
- body: z.string().min(1),
2467
- });
2468
- const fileList = diff.structured && diff.structured.length > 0
2469
- ? [
2470
- "Files changed:",
2471
- ...diff.structured.map((file) => {
2472
- const changeType = diffChangeTypeFor(file);
2473
- const status = file.status && file.status !== "ok" ? ` [${file.status}]` : "";
2474
- return `${changeType}\t${file.path}\t(+${file.additions} -${file.deletions})${status}`;
2475
- }),
2476
- ].join("\n")
2477
- : "Files changed: (unknown)";
2478
- const maxPatchChars = 200000;
2479
- const patch = diff.diff.length > maxPatchChars
2480
- ? `${diff.diff.slice(0, maxPatchChars)}\n\n... (diff truncated to ${maxPatchChars} chars)\n`
2481
- : diff.diff;
2482
- const prompt = await buildMetadataPrompt({
2483
- cwd,
2484
- workspaceGitService: this.workspaceGitService,
2485
- contract: "Write a pull request title and body for the changes below.",
2486
- styles: [
2487
- {
2488
- configKey: "pullRequest",
2489
- default: "Clear, descriptive title; body explaining what changed and why.",
2490
- },
2491
- ],
2492
- after: [
2493
- "Return JSON only with fields 'title' and 'body'.",
2494
- "",
2495
- fileList,
2496
- "",
2497
- patch.length > 0 ? patch : "(No diff available)",
2498
- ].join("\n"),
2499
- });
2500
- const providers = await resolveStructuredGenerationProviders({
2501
- cwd,
2502
- providerSnapshotManager: this.providerSnapshotManager,
2503
- daemonConfig: this.readStructuredGenerationDaemonConfig(),
2504
- currentSelection: this.getFocusedAgentSelectionForCwd(cwd),
2505
- });
2506
- try {
2507
- return await generateStructuredAgentResponseWithFallback({
2508
- manager: this.agentManager,
2509
- cwd,
2510
- prompt,
2511
- schema,
2512
- schemaName: "PullRequest",
2513
- maxRetries: 2,
2514
- providers,
2515
- persistSession: false,
2516
- agentConfigOverrides: {
2517
- title: "PR generator",
2518
- internal: true,
2519
- },
2520
- });
2521
- }
2522
- catch (error) {
2523
- if (error instanceof StructuredAgentResponseError ||
2524
- error instanceof StructuredAgentFallbackError) {
2525
- return {
2526
- title: "Update changes",
2527
- body: "Automated PR generated by Paseo.",
2528
- };
2529
- }
2530
- throw error;
2531
- }
2532
- }
2533
- async ensureCleanWorkingTree(cwd) {
2534
- const dirty = await this.isWorkingTreeDirty(cwd);
2535
- if (dirty) {
2536
- throw new Error("Working directory has uncommitted changes. Commit or stash before switching branches.");
2537
- }
2538
- }
2539
- async isWorkingTreeDirty(cwd) {
2540
- try {
2541
- const snapshot = await this.workspaceGitService.getSnapshot(cwd);
2542
- return snapshot.git.isDirty === true;
2543
- }
2544
- catch (error) {
2545
- throw new Error(`Unable to inspect git status for ${cwd}: ${getErrorMessage(error)}`, {
2546
- cause: error,
2547
- });
2548
- }
2549
- }
2550
- async checkoutExistingBranch(cwd, branch) {
2551
- this.assertSafeGitRef(branch, "branch");
2552
- const resolution = await this.workspaceGitService.validateBranchRef(cwd, branch);
2553
- if (resolution.kind === "not-found") {
2554
- throw new Error(`Branch not found: ${branch}`);
2555
- }
2556
- await this.ensureCleanWorkingTree(cwd);
2557
- const result = await checkoutResolvedBranch({
2558
- cwd,
2559
- resolution,
2560
- });
2561
- await this.notifyGitMutation(cwd, "switch-branch", { invalidateGithub: true });
2562
- return result;
2563
- }
2564
- async createBranchFromBase(params) {
2565
- const { cwd, baseBranch, newBranchName } = params;
2566
- this.assertSafeGitRef(baseBranch, "base branch");
2567
- this.assertSafeGitRef(newBranchName, "new branch");
2568
- const baseResolution = await this.workspaceGitService.validateBranchRef(cwd, baseBranch);
2569
- if (baseResolution.kind === "not-found") {
2570
- throw new Error(`Base branch not found: ${baseBranch}`);
2571
- }
2572
- const exists = await this.doesLocalBranchExist(cwd, newBranchName);
2573
- if (exists) {
2574
- throw new Error(`Branch already exists: ${newBranchName}`);
2575
- }
2576
- await this.ensureCleanWorkingTree(cwd);
2577
- await execCommand("git", ["checkout", "-b", newBranchName, baseBranch], {
2578
- cwd,
2579
- });
2580
- await this.notifyGitMutation(cwd, "create-branch");
2581
- }
2582
- async doesLocalBranchExist(cwd, branch) {
2583
- this.assertSafeGitRef(branch, "branch");
2584
- return this.workspaceGitService.hasLocalBranch(cwd, branch);
2585
- }
2586
- async notifyGitMutation(cwd, reason, options) {
2587
- if (options?.invalidateGithub) {
2588
- this.github.invalidate({ cwd });
2589
- }
2590
- try {
2591
- await this.workspaceGitService.getSnapshot(cwd, { force: true, reason });
2592
- }
2593
- catch (error) {
2594
- this.sessionLogger.warn({ err: error, cwd, reason }, "Failed to force-refresh workspace git snapshot after mutation");
2595
- }
2596
- }
2597
2183
  /**
2598
2184
  * Handle clearing agent attention flag
2599
2185
  */
@@ -2797,129 +2383,6 @@ export class Session {
2797
2383
  });
2798
2384
  }
2799
2385
  }
2800
- closeWorkspaceGitWatchTarget(target) {
2801
- if (target.debounceTimer) {
2802
- clearTimeout(target.debounceTimer);
2803
- target.debounceTimer = null;
2804
- }
2805
- for (const watcher of target.watchers) {
2806
- try {
2807
- watcher.close();
2808
- }
2809
- catch {
2810
- // Ignore watcher close errors
2811
- }
2812
- }
2813
- target.watchers.length = 0;
2814
- }
2815
- async removeWorkspaceGitWatchTarget(cwd) {
2816
- const normalizedCwd = resolve(cwd);
2817
- const target = this.workspaceGitWatchTargets.get(normalizedCwd);
2818
- if (target) {
2819
- this.closeWorkspaceGitWatchTarget(target);
2820
- this.workspaceGitWatchTargets.delete(normalizedCwd);
2821
- }
2822
- }
2823
- removeWorkspaceGitSubscription(cwd) {
2824
- const normalizedCwd = resolve(cwd);
2825
- const target = this.workspaceGitWatchTargets.get(normalizedCwd);
2826
- if (target) {
2827
- const unsubscribeFetch = this.workspaceGitFetchSubscriptions.get(normalizedCwd);
2828
- unsubscribeFetch?.();
2829
- this.workspaceGitFetchSubscriptions.delete(normalizedCwd);
2830
- this.closeWorkspaceGitWatchTarget(target);
2831
- this.workspaceGitWatchTargets.delete(normalizedCwd);
2832
- }
2833
- this.workspaceGitSubscriptions.get(normalizedCwd)?.();
2834
- this.workspaceGitSubscriptions.delete(normalizedCwd);
2835
- }
2836
- workspaceGitDescriptorStateKey(workspace) {
2837
- if (!workspace) {
2838
- return WORKSPACE_GIT_WATCH_REMOVED_STATE_KEY;
2839
- }
2840
- return JSON.stringify([
2841
- workspace.name,
2842
- workspace.diffStat ? [workspace.diffStat.additions, workspace.diffStat.deletions] : null,
2843
- ]);
2844
- }
2845
- resolveWorkspaceGitWatchTarget(workspaceId) {
2846
- for (const target of this.workspaceGitWatchTargets.values()) {
2847
- if (target.workspaceId === workspaceId) {
2848
- return target;
2849
- }
2850
- }
2851
- return null;
2852
- }
2853
- shouldSkipWorkspaceGitWatchUpdate(workspaceId, workspace) {
2854
- const target = this.resolveWorkspaceGitWatchTarget(workspaceId);
2855
- if (!target) {
2856
- return false;
2857
- }
2858
- const nextStateKey = this.workspaceGitDescriptorStateKey(workspace);
2859
- if (target.latestDescriptorStateKey === nextStateKey) {
2860
- return true;
2861
- }
2862
- target.latestDescriptorStateKey = nextStateKey;
2863
- return false;
2864
- }
2865
- rememberWorkspaceGitDescriptorState(workspaceId, workspace) {
2866
- const target = this.resolveWorkspaceGitWatchTarget(workspaceId);
2867
- if (!target) {
2868
- return;
2869
- }
2870
- target.latestDescriptorStateKey = this.workspaceGitDescriptorStateKey(workspace);
2871
- target.lastBranchName = workspace?.name ?? null;
2872
- }
2873
- handleWorkspaceGitBranchSnapshot(cwd, branchName) {
2874
- const target = this.workspaceGitWatchTargets.get(resolve(cwd));
2875
- if (!target) {
2876
- return;
2877
- }
2878
- const previousBranchName = target.lastBranchName;
2879
- if (branchName === previousBranchName) {
2880
- return;
2881
- }
2882
- target.lastBranchName = branchName;
2883
- this.onBranchChanged?.(target.workspaceId, previousBranchName, branchName);
2884
- }
2885
- syncWorkspaceGitObservers(workspaces) {
2886
- for (const workspace of workspaces) {
2887
- this.syncWorkspaceGitObserver(workspace.workspaceDirectory, {
2888
- isGit: workspace.projectKind === "git",
2889
- workspaceId: workspace.id,
2890
- });
2891
- this.rememberWorkspaceGitDescriptorState(workspace.workspaceDirectory, workspace);
2892
- }
2893
- }
2894
- syncWorkspaceGitObserver(cwd, options) {
2895
- const normalizedCwd = resolve(cwd);
2896
- if (!options.isGit) {
2897
- this.removeWorkspaceGitSubscription(normalizedCwd);
2898
- return;
2899
- }
2900
- if (this.workspaceGitSubscriptions.has(normalizedCwd)) {
2901
- return;
2902
- }
2903
- const target = {
2904
- cwd: normalizedCwd,
2905
- workspaceId: options.workspaceId,
2906
- watchers: [],
2907
- debounceTimer: null,
2908
- refreshPromise: null,
2909
- refreshQueued: false,
2910
- latestDescriptorStateKey: null,
2911
- lastBranchName: null,
2912
- };
2913
- this.workspaceGitWatchTargets.set(normalizedCwd, target);
2914
- const subscription = this.workspaceGitService.registerWorkspace({ cwd: normalizedCwd }, (snapshot) => {
2915
- this.handleWorkspaceGitBranchSnapshot(normalizedCwd, snapshot.git.currentBranch ?? null);
2916
- void this.emitWorkspaceUpdateForCwd(normalizedCwd).catch((error) => {
2917
- this.sessionLogger.warn({ err: error, cwd: normalizedCwd }, "Failed to emit workspace update after git branch snapshot");
2918
- });
2919
- this.checkoutSession.emitStatusUpdate(normalizedCwd, snapshot);
2920
- });
2921
- this.workspaceGitSubscriptions.set(normalizedCwd, subscription.unsubscribe);
2922
- }
2923
2386
  async handlePaseoWorktreeListRequest(msg) {
2924
2387
  return handleWorktreeListRequest({
2925
2388
  emit: (message) => this.emit(message),
@@ -3115,7 +2578,7 @@ export class Session {
3115
2578
  if (!entry) {
3116
2579
  continue;
3117
2580
  }
3118
- if (!this.matchesAgentFilter({
2581
+ if (!matchesAgentUpdatesFilter({
3119
2582
  agent: entry.agent,
3120
2583
  project: entry.project,
3121
2584
  filter,
@@ -3160,9 +2623,7 @@ export class Session {
3160
2623
  if (existing) {
3161
2624
  return existing;
3162
2625
  }
3163
- const placementPromise = request.type === "fetch_agent_history_request"
3164
- ? this.buildProjectPlacementForExistingWorkspaceProject(workspaceId)
3165
- : this.buildProjectPlacementForWorkspaceId(workspaceId);
2626
+ const placementPromise = this.buildProjectPlacementForWorkspaceId(workspaceId);
3166
2627
  placementByWorkspaceId.set(workspaceId, placementPromise);
3167
2628
  return placementPromise;
3168
2629
  };
@@ -3230,19 +2691,7 @@ export class Session {
3230
2691
  statusEnteredAt: null,
3231
2692
  activityAt: null,
3232
2693
  diffStat,
3233
- scripts: this.serviceProxy && this.scriptRuntimeStore
3234
- ? buildWorkspaceScriptPayloads({
3235
- workspaceId: workspace.workspaceId,
3236
- workspaceDirectory: workspace.cwd,
3237
- paseoConfig: readPaseoConfigForProjection(workspace.cwd, this.sessionLogger),
3238
- serviceProxy: this.serviceProxy,
3239
- runtimeStore: this.scriptRuntimeStore,
3240
- daemonPort: this.getDaemonTcpPort?.() ?? null,
3241
- serviceProxyPublicBaseUrl: this.serviceProxyPublicBaseUrl,
3242
- gitMetadata: this.resolveWorkspaceScriptGitMetadata(workspace.cwd),
3243
- resolveHealth: this.resolveScriptHealth ?? undefined,
3244
- })
3245
- : [],
2694
+ scripts: this.buildWorkspaceScriptPayloadSnapshot(workspace.workspaceId, workspace.cwd),
3246
2695
  ...(resolvedProjectRecord
3247
2696
  ? {
3248
2697
  project: await this.buildProjectPlacementForWorkspace(workspace, resolvedProjectRecord),
@@ -3411,91 +2860,6 @@ export class Session {
3411
2860
  });
3412
2861
  }
3413
2862
  }
3414
- async findOrCreateWorkspaceForDirectory(cwd) {
3415
- const inputCwd = resolve(cwd);
3416
- const normalizedCwd = await this.resolveWorkspaceDirectory(cwd);
3417
- const existingWorkspace = await this.findExactWorkspaceByDirectory(normalizedCwd, {
3418
- refreshGit: false,
3419
- });
3420
- if (existingWorkspace) {
3421
- if (existingWorkspace.archivedAt && inputCwd !== normalizedCwd) {
3422
- const timestamp = new Date().toISOString();
3423
- const checkout = checkoutLiteFromGitSnapshot(inputCwd, {
3424
- isGit: false,
3425
- currentBranch: null,
3426
- remoteUrl: null,
3427
- repoRoot: null,
3428
- isPaseoOwnedWorktree: false,
3429
- mainRepoRoot: null,
3430
- });
3431
- const membership = classifyDirectoryForProjectMembership({ cwd: inputCwd, checkout });
3432
- const projectRecord = await this.resolveProjectRecordForPlacement({
3433
- membership,
3434
- timestamp,
3435
- });
3436
- await this.projectRegistry.upsert(projectRecord);
3437
- const workspaceRecord = createPersistedWorkspaceRecord({
3438
- workspaceId: generateWorkspaceId(),
3439
- projectId: projectRecord.projectId,
3440
- cwd: inputCwd,
3441
- kind: membership.workspaceKind,
3442
- displayName: membership.workspaceDisplayName,
3443
- createdAt: timestamp,
3444
- updatedAt: timestamp,
3445
- });
3446
- await this.workspaceRegistry.upsert(workspaceRecord);
3447
- return workspaceRecord;
3448
- }
3449
- return this.reclassifyOrUnarchiveWorkspaceForDirectory({
3450
- workspace: existingWorkspace,
3451
- project: await this.projectRegistry.get(existingWorkspace.projectId),
3452
- cwd: normalizedCwd,
3453
- });
3454
- }
3455
- return this.createWorkspaceForDirectory(normalizedCwd);
3456
- }
3457
- async resolveOrCreateWorkspaceIdForCreateAgent(input) {
3458
- if (input.createdWorktree) {
3459
- return input.createdWorktree.workspace.workspaceId;
3460
- }
3461
- if (input.requestedWorkspaceId) {
3462
- return input.requestedWorkspaceId;
3463
- }
3464
- return (await this.createWorkspaceForDirectory(input.cwd, input.initialTitle)).workspaceId;
3465
- }
3466
- async createWorkspaceForDirectory(cwd, title) {
3467
- const checkout = await this.workspaceGitService.getCheckout(cwd);
3468
- const membership = classifyDirectoryForProjectMembership({ cwd, checkout });
3469
- const timestamp = new Date().toISOString();
3470
- const projectRecord = await this.resolveProjectRecordForPlacement({
3471
- membership,
3472
- timestamp,
3473
- });
3474
- await this.projectRegistry.upsert(projectRecord);
3475
- const workspaceRecord = createPersistedWorkspaceRecord({
3476
- workspaceId: generateWorkspaceId(),
3477
- projectId: projectRecord.projectId,
3478
- cwd,
3479
- kind: membership.workspaceKind,
3480
- displayName: membership.workspaceDisplayName,
3481
- title: title ?? null,
3482
- createdAt: timestamp,
3483
- updatedAt: timestamp,
3484
- });
3485
- await this.workspaceRegistry.upsert(workspaceRecord);
3486
- return workspaceRecord;
3487
- }
3488
- async findOrCreateProjectForDirectory(cwd) {
3489
- const normalizedCwd = resolve(cwd);
3490
- const checkout = await this.workspaceGitService.getCheckout(normalizedCwd);
3491
- const membership = classifyDirectoryForProjectMembership({ cwd: normalizedCwd, checkout });
3492
- const projectRecord = await this.resolveProjectRecordForPlacement({
3493
- membership,
3494
- timestamp: new Date().toISOString(),
3495
- });
3496
- await this.projectRegistry.upsert(projectRecord);
3497
- return projectRecord;
3498
- }
3499
2863
  buildProjectDescriptor(project) {
3500
2864
  return {
3501
2865
  projectId: project.projectId,
@@ -3505,64 +2869,6 @@ export class Session {
3505
2869
  projectKind: project.kind,
3506
2870
  };
3507
2871
  }
3508
- async reclassifyOrUnarchiveWorkspaceForDirectory(input) {
3509
- const checkout = await this.workspaceGitService.getCheckout(input.cwd);
3510
- const membership = classifyDirectoryForProjectMembership({ cwd: input.cwd, checkout });
3511
- const timestamp = new Date().toISOString();
3512
- const projectRecord = await this.resolveProjectRecordForPlacement({
3513
- membership,
3514
- timestamp,
3515
- });
3516
- const projectId = projectRecord.projectId;
3517
- const kind = membership.workspaceKind;
3518
- const displayName = membership.workspaceDisplayName;
3519
- if (input.workspace.projectId === projectId &&
3520
- input.workspace.kind === kind &&
3521
- input.workspace.displayName === displayName) {
3522
- if (!input.project) {
3523
- await this.projectRegistry.upsert(projectRecord);
3524
- }
3525
- return this.ensureWorkspaceRecordUnarchived(input.workspace);
3526
- }
3527
- await this.projectRegistry.upsert(projectRecord);
3528
- const nextWorkspace = {
3529
- ...input.workspace,
3530
- workspaceId: input.workspace.workspaceId,
3531
- projectId,
3532
- cwd: input.cwd,
3533
- kind,
3534
- displayName,
3535
- archivedAt: null,
3536
- updatedAt: timestamp,
3537
- };
3538
- await this.workspaceRegistry.upsert(nextWorkspace);
3539
- return nextWorkspace;
3540
- }
3541
- async resolveProjectRecordForPlacement(input) {
3542
- const rootPath = input.membership.projectRootPath;
3543
- const kind = input.membership.projectKind;
3544
- const projects = await this.projectRegistry.list();
3545
- const existingProject = projects.find((project) => !project.archivedAt && project.rootPath === rootPath) ??
3546
- projects.find((project) => project.rootPath === rootPath) ??
3547
- null;
3548
- if (!existingProject) {
3549
- return createPersistedProjectRecord({
3550
- projectId: input.membership.projectKey,
3551
- rootPath,
3552
- kind,
3553
- displayName: input.membership.projectName,
3554
- createdAt: input.timestamp,
3555
- updatedAt: input.timestamp,
3556
- });
3557
- }
3558
- return {
3559
- ...existingProject,
3560
- rootPath,
3561
- kind,
3562
- archivedAt: null,
3563
- updatedAt: input.timestamp,
3564
- };
3565
- }
3566
2872
  async unarchiveOwningWorkspaceForAgent(agentId) {
3567
2873
  const record = await this.agentStorage.get(agentId);
3568
2874
  if (!record?.workspaceId) {
@@ -3582,7 +2888,7 @@ export class Session {
3582
2888
  // missing, so the record must point at a real directory first.
3583
2889
  await this.recreateOwningWorktreeForRestore(workspace, workspace.branch);
3584
2890
  }
3585
- await this.ensureWorkspaceRecordUnarchived(workspace);
2891
+ await this.workspaceProvisioning.ensureWorkspaceRecordUnarchived(workspace);
3586
2892
  await this.emitWorkspaceUpdatesForWorkspaceIds([workspace.workspaceId]);
3587
2893
  }
3588
2894
  async recreateOwningWorktreeForRestore(workspace, branch) {
@@ -3635,26 +2941,6 @@ export class Session {
3635
2941
  });
3636
2942
  }
3637
2943
  }
3638
- async ensureWorkspaceRecordUnarchived(workspace) {
3639
- const project = await this.projectRegistry.get(workspace.projectId);
3640
- if (!workspace.archivedAt && (!project || !project.archivedAt)) {
3641
- return workspace;
3642
- }
3643
- const timestamp = new Date().toISOString();
3644
- let unarchivedWorkspace = workspace;
3645
- if (workspace.archivedAt) {
3646
- unarchivedWorkspace = { ...workspace, archivedAt: null, updatedAt: timestamp };
3647
- await this.workspaceRegistry.upsert(unarchivedWorkspace);
3648
- }
3649
- if (project?.archivedAt) {
3650
- await this.projectRegistry.upsert({
3651
- ...project,
3652
- archivedAt: null,
3653
- updatedAt: timestamp,
3654
- });
3655
- }
3656
- return unarchivedWorkspace;
3657
- }
3658
2944
  async createPaseoWorktree(input, options) {
3659
2945
  const result = await createPaseoWorktree(input, {
3660
2946
  github: this.github,
@@ -3666,8 +2952,8 @@ export class Session {
3666
2952
  workspaceGitService: this.workspaceGitService,
3667
2953
  });
3668
2954
  void Promise.all([
3669
- this.notifyGitMutation(input.cwd, "create-worktree"),
3670
- this.notifyGitMutation(result.worktree.worktreePath, "create-worktree"),
2955
+ this.gitMutation.notifyGitMutation(input.cwd, "create-worktree"),
2956
+ this.gitMutation.notifyGitMutation(result.worktree.worktreePath, "create-worktree"),
3671
2957
  ]).catch((error) => {
3672
2958
  this.sessionLogger.warn({ err: error, cwd: input.cwd, worktreePath: result.worktree.worktreePath }, "Failed to warm git snapshots after creating worktree");
3673
2959
  });
@@ -3691,10 +2977,7 @@ export class Session {
3691
2977
  workspaceRegistry: this.workspaceRegistry,
3692
2978
  });
3693
2979
  if (!existingWorkspace) {
3694
- const watchTarget = this.resolveWorkspaceGitWatchTarget(workspaceId);
3695
- if (watchTarget) {
3696
- this.removeWorkspaceGitSubscription(watchTarget.cwd);
3697
- }
2980
+ this.workspaceGitObserver.removeForWorkspaceId(workspaceId);
3698
2981
  return;
3699
2982
  }
3700
2983
  if (!existingWorkspace.archivedAt) {
@@ -3716,9 +2999,8 @@ export class Session {
3716
2999
  // store is keyed by the opaque workspace id. Each cleanup uses its own key so an
3717
3000
  // opaque id is never resolved as a filesystem path.
3718
3001
  async teardownArchivedWorkspace(input) {
3719
- await this.removeWorkspaceGitWatchTarget(input.cwd);
3002
+ this.workspaceGitObserver.removeForCwd(input.cwd);
3720
3003
  this.scriptRuntimeStore?.removeForWorkspace(input.workspaceId);
3721
- this.removeWorkspaceGitSubscription(input.cwd);
3722
3004
  }
3723
3005
  async reconcileAndEmitWorkspaceUpdates() {
3724
3006
  if (!this.workspaceUpdatesSubscription) {
@@ -3793,10 +3075,10 @@ export class Session {
3793
3075
  ? workspace
3794
3076
  : null;
3795
3077
  if (options?.dedupeGitState &&
3796
- this.shouldSkipWorkspaceGitWatchUpdate(workspaceId, nextWorkspace)) {
3078
+ this.workspaceGitObserver.shouldSkipUpdate(workspaceId, nextWorkspace)) {
3797
3079
  continue;
3798
3080
  }
3799
- this.recordWorkspaceGitDescriptorState(workspaceId, nextWorkspace);
3081
+ this.workspaceGitObserver.recordDescriptorState(workspaceId, nextWorkspace);
3800
3082
  if (!nextWorkspace) {
3801
3083
  subscription.lastEmittedByWorkspaceId.delete(workspaceId);
3802
3084
  this.bufferOrEmitWorkspaceUpdate(subscription, await this.buildWorkspaceRemoveUpdatePayload(workspaceId, options?.removedProjectId));
@@ -3818,16 +3100,6 @@ export class Session {
3818
3100
  void this.reconcileAndEmitWorkspaceUpdates();
3819
3101
  }
3820
3102
  }
3821
- recordWorkspaceGitDescriptorState(workspaceId, nextWorkspace) {
3822
- const watchTarget = this.resolveWorkspaceGitWatchTarget(workspaceId);
3823
- if (watchTarget && this.onBranchChanged) {
3824
- const newBranchName = nextWorkspace?.name ?? null;
3825
- if (newBranchName !== watchTarget.lastBranchName) {
3826
- this.onBranchChanged(workspaceId, watchTarget.lastBranchName, newBranchName);
3827
- }
3828
- }
3829
- this.rememberWorkspaceGitDescriptorState(workspaceId, nextWorkspace);
3830
- }
3831
3103
  async buildWorkspaceRemoveUpdatePayload(workspaceId, removedProjectId) {
3832
3104
  if (removedProjectId) {
3833
3105
  return { kind: "remove", id: workspaceId, removedProjectId };
@@ -3876,12 +3148,10 @@ export class Session {
3876
3148
  const subscriptionId = resolveSubscriptionId(request.subscribe, requestedSubscriptionId);
3877
3149
  try {
3878
3150
  if (subscriptionId) {
3879
- this.agentUpdatesSubscription = {
3151
+ this.agentUpdates.beginSubscription({
3880
3152
  subscriptionId,
3881
3153
  filter: request.filter,
3882
- isBootstrapping: true,
3883
- pendingUpdatesByAgentId: new Map(),
3884
- };
3154
+ });
3885
3155
  }
3886
3156
  const payload = await this.listFetchAgentsEntries(request);
3887
3157
  const snapshotUpdatedAtByAgentId = new Map();
@@ -3899,13 +3169,13 @@ export class Session {
3899
3169
  ...payload,
3900
3170
  },
3901
3171
  });
3902
- if (subscriptionId && this.agentUpdatesSubscription?.subscriptionId === subscriptionId) {
3903
- this.flushBootstrappedAgentUpdates({ snapshotUpdatedAtByAgentId });
3172
+ if (subscriptionId) {
3173
+ this.agentUpdates.flushBootstrapped(subscriptionId, { snapshotUpdatedAtByAgentId });
3904
3174
  }
3905
3175
  }
3906
3176
  catch (error) {
3907
- if (subscriptionId && this.agentUpdatesSubscription?.subscriptionId === subscriptionId) {
3908
- this.agentUpdatesSubscription = null;
3177
+ if (subscriptionId) {
3178
+ this.agentUpdates.clearSubscription(subscriptionId);
3909
3179
  }
3910
3180
  const code = error instanceof SessionRequestError ? error.code : "fetch_agents_failed";
3911
3181
  const message = error instanceof Error ? error.message : "Failed to fetch agents";
@@ -4004,7 +3274,7 @@ export class Session {
4004
3274
  };
4005
3275
  }
4006
3276
  const payload = await this.listFetchWorkspacesEntries(request);
4007
- this.syncWorkspaceGitObservers(payload.entries);
3277
+ this.workspaceGitObserver.syncObservers(payload.entries);
4008
3278
  this.sessionLogger.debug({
4009
3279
  requestId: request.requestId,
4010
3280
  subscriptionId,
@@ -4246,7 +3516,7 @@ export class Session {
4246
3516
  for (const workspaceRecord of await this.workspaceRegistry.list()) {
4247
3517
  workspacesBefore.set(workspaceRecord.workspaceId, workspaceRecord);
4248
3518
  }
4249
- const workspace = await this.findOrCreateWorkspaceForDirectory(cwd);
3519
+ const workspace = await this.workspaceProvisioning.findOrCreateWorkspaceForDirectory(cwd);
4250
3520
  const project = await this.projectRegistry.get(workspace.projectId);
4251
3521
  await this.syncWorkspaceGitObserverForWorkspace(workspace);
4252
3522
  const descriptor = await this.describeWorkspaceRecord(workspace);
@@ -4315,7 +3585,7 @@ export class Session {
4315
3585
  for (const project of await this.projectRegistry.list()) {
4316
3586
  projectsBefore.set(project.projectId, project);
4317
3587
  }
4318
- const project = await this.findOrCreateProjectForDirectory(cwd);
3588
+ const project = await this.workspaceProvisioning.findOrCreateProjectForDirectory(cwd);
4319
3589
  this.sessionLogger.info({
4320
3590
  requestedCwd,
4321
3591
  resolvedCwd: cwd,
@@ -4345,98 +3615,13 @@ export class Session {
4345
3615
  });
4346
3616
  }
4347
3617
  }
3618
+ // Named accessor: the workspace descriptor builder and the git-watch test both read a workspace's
3619
+ // scripts snapshot through here; the workspace-scripts module owns the payload assembly.
4348
3620
  buildWorkspaceScriptPayloadSnapshot(workspaceId, workspaceDirectory) {
4349
- if (!this.serviceProxy || !this.scriptRuntimeStore) {
4350
- return [];
4351
- }
4352
- return buildWorkspaceScriptPayloads({
4353
- workspaceId,
4354
- workspaceDirectory,
4355
- paseoConfig: readPaseoConfigForProjection(workspaceDirectory, this.sessionLogger),
4356
- serviceProxy: this.serviceProxy,
4357
- runtimeStore: this.scriptRuntimeStore,
4358
- daemonPort: this.getDaemonTcpPort?.() ?? null,
4359
- serviceProxyPublicBaseUrl: this.serviceProxyPublicBaseUrl,
4360
- gitMetadata: this.resolveWorkspaceScriptGitMetadata(workspaceDirectory),
4361
- resolveHealth: this.resolveScriptHealth ?? undefined,
4362
- });
4363
- }
4364
- resolveWorkspaceScriptGitMetadata(workspaceDirectory) {
4365
- const snapshot = this.workspaceGitService.peekSnapshot(workspaceDirectory);
4366
- if (!snapshot) {
4367
- return undefined;
4368
- }
4369
- return {
4370
- projectSlug: deriveProjectSlug(workspaceDirectory, snapshot.git.isGit ? snapshot.git.remoteUrl : null),
4371
- currentBranch: snapshot.git.currentBranch,
4372
- };
3621
+ return this.workspaceScripts.buildSnapshot(workspaceId, workspaceDirectory);
4373
3622
  }
4374
- emitWorkspaceScriptStatusUpdate(workspaceId, workspaceDirectory) {
4375
- this.emit({
4376
- type: "script_status_update",
4377
- payload: {
4378
- workspaceId,
4379
- scripts: this.buildWorkspaceScriptPayloadSnapshot(workspaceId, workspaceDirectory),
4380
- },
4381
- });
4382
- }
4383
- async handleStartWorkspaceScriptRequest(request) {
4384
- try {
4385
- if (!this.terminalManager || !this.serviceProxy || !this.scriptRuntimeStore) {
4386
- throw new Error("Workspace scripts are not available on this daemon");
4387
- }
4388
- const workspace = await this.workspaceRegistry.get(request.workspaceId);
4389
- if (!workspace) {
4390
- throw new Error(`Workspace not found: ${request.workspaceId}`);
4391
- }
4392
- const gitMetadata = await this.workspaceGitService.getWorkspaceGitMetadata(workspace.cwd);
4393
- const serviceResult = await spawnWorkspaceScript({
4394
- repoRoot: workspace.cwd,
4395
- workspaceId: workspace.workspaceId,
4396
- projectSlug: gitMetadata.projectSlug,
4397
- branchName: gitMetadata.currentBranch,
4398
- scriptName: request.scriptName,
4399
- daemonPort: this.getDaemonTcpPort?.() ?? null,
4400
- daemonListenHost: this.getDaemonTcpHost?.() ?? null,
4401
- serviceProxyPublicBaseUrl: this.serviceProxyPublicBaseUrl,
4402
- serviceProxy: this.serviceProxy,
4403
- runtimeStore: this.scriptRuntimeStore,
4404
- terminalManager: this.terminalManager,
4405
- logger: this.sessionLogger,
4406
- onLifecycleChanged: () => {
4407
- this.emitWorkspaceScriptStatusUpdate(workspace.workspaceId, workspace.cwd);
4408
- },
4409
- });
4410
- this.emitWorkspaceScriptStatusUpdate(workspace.workspaceId, workspace.cwd);
4411
- this.emit({
4412
- type: "start_workspace_script_response",
4413
- payload: {
4414
- requestId: request.requestId,
4415
- workspaceId: request.workspaceId,
4416
- scriptName: request.scriptName,
4417
- terminalId: serviceResult.terminalId,
4418
- error: null,
4419
- },
4420
- });
4421
- }
4422
- catch (error) {
4423
- const message = error instanceof Error ? error.message : "Failed to start workspace script";
4424
- this.sessionLogger.error({
4425
- err: error,
4426
- workspaceId: request.workspaceId,
4427
- scriptName: request.scriptName,
4428
- }, "Failed to start workspace script");
4429
- this.emit({
4430
- type: "start_workspace_script_response",
4431
- payload: {
4432
- requestId: request.requestId,
4433
- workspaceId: request.workspaceId,
4434
- scriptName: request.scriptName,
4435
- terminalId: null,
4436
- error: message,
4437
- },
4438
- });
4439
- }
3623
+ handleStartWorkspaceScriptRequest(request) {
3624
+ return this.workspaceScripts.start(request);
4440
3625
  }
4441
3626
  // COMPAT(desktopEditorBridge): added in v0.1.88, remove after 2026-12-03 once old clients no longer call daemon editor RPCs.
4442
3627
  async handleLegacyListAvailableEditorsRequest(request) {
@@ -4489,7 +3674,7 @@ export class Session {
4489
3674
  getDaemonTcpHost: this.getDaemonTcpHost,
4490
3675
  serviceProxyPublicBaseUrl: this.serviceProxyPublicBaseUrl,
4491
3676
  onScriptsChanged: (workspaceId, workspaceDirectory) => {
4492
- this.emitWorkspaceScriptStatusUpdate(workspaceId, workspaceDirectory);
3677
+ this.workspaceScripts.emitStatusUpdate(workspaceId, workspaceDirectory);
4493
3678
  },
4494
3679
  }, input, options);
4495
3680
  }
@@ -4850,6 +4035,51 @@ export class Session {
4850
4035
  });
4851
4036
  }
4852
4037
  }
4038
+ async handleAgentForkContextRequest(msg) {
4039
+ try {
4040
+ const snapshot = await ensureAgentLoaded(msg.agentId, {
4041
+ agentManager: this.agentManager,
4042
+ agentStorage: this.agentStorage,
4043
+ logger: this.sessionLogger,
4044
+ });
4045
+ const agentPayload = await this.buildAgentPayload(snapshot);
4046
+ const rows = this.agentManager.fetchTimeline(msg.agentId, {
4047
+ direction: "tail",
4048
+ limit: 0,
4049
+ }).rows;
4050
+ const forkContext = buildAgentForkContextAttachment({
4051
+ rows,
4052
+ boundaryMessageId: msg.boundaryMessageId,
4053
+ agentTitle: agentPayload.title,
4054
+ cwd: snapshot.cwd,
4055
+ });
4056
+ this.emit({
4057
+ type: "agent.fork_context.response",
4058
+ payload: {
4059
+ requestId: msg.requestId,
4060
+ agentId: msg.agentId,
4061
+ attachment: forkContext.attachment,
4062
+ itemCount: forkContext.itemCount,
4063
+ boundaryMessageId: forkContext.boundaryMessageId,
4064
+ error: null,
4065
+ },
4066
+ });
4067
+ }
4068
+ catch (error) {
4069
+ this.sessionLogger.error({ err: error, agentId: msg.agentId }, "Failed to handle agent.fork_context.request");
4070
+ this.emit({
4071
+ type: "agent.fork_context.response",
4072
+ payload: {
4073
+ requestId: msg.requestId,
4074
+ agentId: msg.agentId,
4075
+ attachment: null,
4076
+ itemCount: 0,
4077
+ boundaryMessageId: msg.boundaryMessageId ?? null,
4078
+ error: error instanceof Error ? error.message : String(error),
4079
+ },
4080
+ });
4081
+ }
4082
+ }
4853
4083
  async handleSendAgentMessageRequest(msg) {
4854
4084
  const resolved = await this.resolveAgentIdentifier(msg.agentId);
4855
4085
  if (!resolved.ok) {
@@ -5097,29 +4327,16 @@ export class Session {
5097
4327
  this.unsubscribeAgentEvents();
5098
4328
  this.unsubscribeAgentEvents = null;
5099
4329
  }
4330
+ this.agentUpdates.dispose();
5100
4331
  if (this.unsubscribeTerminalWorkspaceContributionEvents) {
5101
4332
  this.unsubscribeTerminalWorkspaceContributionEvents();
5102
4333
  this.unsubscribeTerminalWorkspaceContributionEvents = null;
5103
4334
  }
5104
4335
  this.providerCatalogSession.dispose();
5105
4336
  await this.voiceSession.cleanup();
5106
- // Close MCP clients
5107
- if (this.agentMcpClient) {
5108
- try {
5109
- await this.agentMcpClient.close();
5110
- }
5111
- catch (error) {
5112
- this.sessionLogger.error({ err: error }, "Failed to close Agent MCP client");
5113
- }
5114
- this.agentMcpClient = null;
5115
- this.agentTools = null;
5116
- }
5117
4337
  this.terminalController.dispose();
5118
4338
  this.checkoutSession.cleanup();
5119
- for (const unsubscribe of this.workspaceGitSubscriptions.values()) {
5120
- unsubscribe();
5121
- }
5122
- this.workspaceGitSubscriptions.clear();
4339
+ this.workspaceGitObserver.dispose();
5123
4340
  }
5124
4341
  }
5125
4342
  //# sourceMappingURL=session.js.map