@getpaseo/server 0.1.63 → 0.1.65

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 (265) hide show
  1. package/dist/server/client/daemon-client-transport-types.d.ts +2 -0
  2. package/dist/server/client/daemon-client-transport-types.d.ts.map +1 -1
  3. package/dist/server/client/daemon-client-websocket-transport.d.ts +2 -1
  4. package/dist/server/client/daemon-client-websocket-transport.d.ts.map +1 -1
  5. package/dist/server/client/daemon-client-websocket-transport.js +4 -4
  6. package/dist/server/client/daemon-client-websocket-transport.js.map +1 -1
  7. package/dist/server/client/daemon-client.d.ts +30 -20
  8. package/dist/server/client/daemon-client.d.ts.map +1 -1
  9. package/dist/server/client/daemon-client.js +206 -98
  10. package/dist/server/client/daemon-client.js.map +1 -1
  11. package/dist/server/client/terminal-stream-router.d.ts +24 -0
  12. package/dist/server/client/terminal-stream-router.d.ts.map +1 -0
  13. package/dist/server/client/terminal-stream-router.js +100 -0
  14. package/dist/server/client/terminal-stream-router.js.map +1 -0
  15. package/dist/server/server/agent/activity-curator.d.ts +6 -3
  16. package/dist/server/server/agent/activity-curator.d.ts.map +1 -1
  17. package/dist/server/server/agent/activity-curator.js +45 -138
  18. package/dist/server/server/agent/activity-curator.js.map +1 -1
  19. package/dist/server/server/agent/agent-manager.d.ts +3 -14
  20. package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
  21. package/dist/server/server/agent/agent-manager.js +75 -140
  22. package/dist/server/server/agent/agent-manager.js.map +1 -1
  23. package/dist/server/server/agent/agent-metadata-generator.d.ts +0 -5
  24. package/dist/server/server/agent/agent-metadata-generator.d.ts.map +1 -1
  25. package/dist/server/server/agent/agent-metadata-generator.js +3 -93
  26. package/dist/server/server/agent/agent-metadata-generator.js.map +1 -1
  27. package/dist/server/server/agent/agent-sdk-types.d.ts +15 -1
  28. package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
  29. package/dist/server/server/agent/agent-stream-coalescer.d.ts +1 -1
  30. package/dist/server/server/agent/agent-stream-coalescer.d.ts.map +1 -1
  31. package/dist/server/server/agent/agent-stream-coalescer.js +1 -1
  32. package/dist/server/server/agent/agent-stream-coalescer.js.map +1 -1
  33. package/dist/server/server/agent/agent-timeline-store.d.ts.map +1 -1
  34. package/dist/server/server/agent/agent-timeline-store.js +29 -32
  35. package/dist/server/server/agent/agent-timeline-store.js.map +1 -1
  36. package/dist/server/server/agent/foreground-run-state.d.ts +50 -0
  37. package/dist/server/server/agent/foreground-run-state.d.ts.map +1 -0
  38. package/dist/server/server/agent/foreground-run-state.js +162 -0
  39. package/dist/server/server/agent/foreground-run-state.js.map +1 -0
  40. package/dist/server/server/agent/mcp-server.d.ts +5 -3
  41. package/dist/server/server/agent/mcp-server.d.ts.map +1 -1
  42. package/dist/server/server/agent/mcp-server.js +110 -99
  43. package/dist/server/server/agent/mcp-server.js.map +1 -1
  44. package/dist/server/server/agent/mcp-shared.d.ts.map +1 -1
  45. package/dist/server/server/agent/mcp-shared.js +7 -1
  46. package/dist/server/server/agent/mcp-shared.js.map +1 -1
  47. package/dist/server/server/agent/prompt-attachments.d.ts +4 -3
  48. package/dist/server/server/agent/prompt-attachments.d.ts.map +1 -1
  49. package/dist/server/server/agent/prompt-attachments.js +43 -4
  50. package/dist/server/server/agent/prompt-attachments.js.map +1 -1
  51. package/dist/server/server/agent/provider-manifest.d.ts.map +1 -1
  52. package/dist/server/server/agent/provider-manifest.js +7 -0
  53. package/dist/server/server/agent/provider-manifest.js.map +1 -1
  54. package/dist/server/server/agent/providers/acp-agent.d.ts +38 -1
  55. package/dist/server/server/agent/providers/acp-agent.d.ts.map +1 -1
  56. package/dist/server/server/agent/providers/acp-agent.js +257 -140
  57. package/dist/server/server/agent/providers/acp-agent.js.map +1 -1
  58. package/dist/server/server/agent/providers/claude/sidechain-tracker.d.ts.map +1 -1
  59. package/dist/server/server/agent/providers/claude/sidechain-tracker.js +1 -1
  60. package/dist/server/server/agent/providers/claude/sidechain-tracker.js.map +1 -1
  61. package/dist/server/server/agent/providers/claude/tool-call-mapper.d.ts.map +1 -1
  62. package/dist/server/server/agent/providers/claude/tool-call-mapper.js +68 -198
  63. package/dist/server/server/agent/providers/claude/tool-call-mapper.js.map +1 -1
  64. package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
  65. package/dist/server/server/agent/providers/claude-agent.js +52 -102
  66. package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
  67. package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts.map +1 -1
  68. package/dist/server/server/agent/providers/codex/tool-call-mapper.js +125 -141
  69. package/dist/server/server/agent/providers/codex/tool-call-mapper.js.map +1 -1
  70. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +36 -6
  71. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
  72. package/dist/server/server/agent/providers/codex-app-server-agent.js +374 -219
  73. package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
  74. package/dist/server/server/agent/providers/mock-load-test-agent.d.ts +6 -2
  75. package/dist/server/server/agent/providers/mock-load-test-agent.d.ts.map +1 -1
  76. package/dist/server/server/agent/providers/mock-load-test-agent.js +294 -113
  77. package/dist/server/server/agent/providers/mock-load-test-agent.js.map +1 -1
  78. package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.d.ts +1 -1
  79. package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.d.ts.map +1 -1
  80. package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.js +94 -2
  81. package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.js.map +1 -1
  82. package/dist/server/server/agent/providers/opencode/tool-call-mapper.d.ts.map +1 -1
  83. package/dist/server/server/agent/providers/opencode/tool-call-mapper.js +24 -115
  84. package/dist/server/server/agent/providers/opencode/tool-call-mapper.js.map +1 -1
  85. package/dist/server/server/agent/providers/opencode-agent.d.ts +102 -1
  86. package/dist/server/server/agent/providers/opencode-agent.d.ts.map +1 -1
  87. package/dist/server/server/agent/providers/opencode-agent.js +416 -158
  88. package/dist/server/server/agent/providers/opencode-agent.js.map +1 -1
  89. package/dist/server/server/agent/providers/provider-runner.d.ts +27 -0
  90. package/dist/server/server/agent/providers/provider-runner.d.ts.map +1 -0
  91. package/dist/server/server/agent/providers/provider-runner.js +80 -0
  92. package/dist/server/server/agent/providers/provider-runner.js.map +1 -0
  93. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +6 -3
  94. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts.map +1 -1
  95. package/dist/server/server/agent/providers/tool-call-mapper-utils.d.ts +2 -0
  96. package/dist/server/server/agent/providers/tool-call-mapper-utils.d.ts.map +1 -1
  97. package/dist/server/server/agent/providers/tool-call-mapper-utils.js +31 -0
  98. package/dist/server/server/agent/providers/tool-call-mapper-utils.js.map +1 -1
  99. package/dist/server/server/agent/timeline-projection.d.ts +21 -5
  100. package/dist/server/server/agent/timeline-projection.d.ts.map +1 -1
  101. package/dist/server/server/agent/timeline-projection.js +59 -9
  102. package/dist/server/server/agent/timeline-projection.js.map +1 -1
  103. package/dist/server/server/auth.d.ts +25 -0
  104. package/dist/server/server/auth.d.ts.map +1 -0
  105. package/dist/server/server/auth.js +93 -0
  106. package/dist/server/server/auth.js.map +1 -0
  107. package/dist/server/server/bootstrap.d.ts +3 -1
  108. package/dist/server/server/bootstrap.d.ts.map +1 -1
  109. package/dist/server/server/bootstrap.js +87 -30
  110. package/dist/server/server/bootstrap.js.map +1 -1
  111. package/dist/server/server/config.d.ts.map +1 -1
  112. package/dist/server/server/config.js +11 -0
  113. package/dist/server/server/config.js.map +1 -1
  114. package/dist/server/server/exports.d.ts +7 -1
  115. package/dist/server/server/exports.d.ts.map +1 -1
  116. package/dist/server/server/exports.js +6 -1
  117. package/dist/server/server/exports.js.map +1 -1
  118. package/dist/server/server/file-explorer/service.d.ts +10 -0
  119. package/dist/server/server/file-explorer/service.d.ts.map +1 -1
  120. package/dist/server/server/file-explorer/service.js +38 -4
  121. package/dist/server/server/file-explorer/service.js.map +1 -1
  122. package/dist/server/server/index.js +9 -6
  123. package/dist/server/server/index.js.map +1 -1
  124. package/dist/server/server/logger.d.ts.map +1 -1
  125. package/dist/server/server/logger.js +15 -1
  126. package/dist/server/server/logger.js.map +1 -1
  127. package/dist/server/server/pagination/cursor.d.ts +16 -0
  128. package/dist/server/server/pagination/cursor.d.ts.map +1 -0
  129. package/dist/server/server/pagination/cursor.js +62 -0
  130. package/dist/server/server/pagination/cursor.js.map +1 -0
  131. package/dist/server/server/pagination/sortable-pager.d.ts +24 -0
  132. package/dist/server/server/pagination/sortable-pager.d.ts.map +1 -0
  133. package/dist/server/server/pagination/sortable-pager.js +68 -0
  134. package/dist/server/server/pagination/sortable-pager.js.map +1 -0
  135. package/dist/server/server/paseo-worktree-archive-service.d.ts +3 -1
  136. package/dist/server/server/paseo-worktree-archive-service.d.ts.map +1 -1
  137. package/dist/server/server/paseo-worktree-archive-service.js +61 -53
  138. package/dist/server/server/paseo-worktree-archive-service.js.map +1 -1
  139. package/dist/server/server/paseo-worktree-service.d.ts +13 -0
  140. package/dist/server/server/paseo-worktree-service.d.ts.map +1 -1
  141. package/dist/server/server/paseo-worktree-service.js +72 -3
  142. package/dist/server/server/paseo-worktree-service.js.map +1 -1
  143. package/dist/server/server/persisted-config.d.ts +25 -0
  144. package/dist/server/server/persisted-config.d.ts.map +1 -1
  145. package/dist/server/server/persisted-config.js +9 -0
  146. package/dist/server/server/persisted-config.js.map +1 -1
  147. package/dist/server/server/relay-transport.d.ts.map +1 -1
  148. package/dist/server/server/relay-transport.js +16 -4
  149. package/dist/server/server/relay-transport.js.map +1 -1
  150. package/dist/server/server/resolve-worktree-creation-intent.d.ts +0 -10
  151. package/dist/server/server/resolve-worktree-creation-intent.d.ts.map +1 -1
  152. package/dist/server/server/resolve-worktree-creation-intent.js +1 -45
  153. package/dist/server/server/resolve-worktree-creation-intent.js.map +1 -1
  154. package/dist/server/server/script-status-projection.d.ts +6 -1
  155. package/dist/server/server/script-status-projection.d.ts.map +1 -1
  156. package/dist/server/server/script-status-projection.js +12 -3
  157. package/dist/server/server/script-status-projection.js.map +1 -1
  158. package/dist/server/server/session.d.ts +19 -51
  159. package/dist/server/server/session.d.ts.map +1 -1
  160. package/dist/server/server/session.js +354 -1069
  161. package/dist/server/server/session.js.map +1 -1
  162. package/dist/server/server/websocket-server.d.ts +4 -2
  163. package/dist/server/server/websocket-server.d.ts.map +1 -1
  164. package/dist/server/server/websocket-server.js +65 -12
  165. package/dist/server/server/websocket-server.js.map +1 -1
  166. package/dist/server/server/workspace-directory.d.ts +69 -0
  167. package/dist/server/server/workspace-directory.d.ts.map +1 -0
  168. package/dist/server/server/workspace-directory.js +229 -0
  169. package/dist/server/server/workspace-directory.js.map +1 -0
  170. package/dist/server/server/worktree-bootstrap.d.ts.map +1 -1
  171. package/dist/server/server/worktree-bootstrap.js +6 -2
  172. package/dist/server/server/worktree-bootstrap.js.map +1 -1
  173. package/dist/server/server/worktree-core.d.ts +2 -0
  174. package/dist/server/server/worktree-core.d.ts.map +1 -1
  175. package/dist/server/server/worktree-core.js.map +1 -1
  176. package/dist/server/server/worktree-errors.d.ts +1 -1
  177. package/dist/server/server/worktree-errors.d.ts.map +1 -1
  178. package/dist/server/server/worktree-errors.js +1 -4
  179. package/dist/server/server/worktree-errors.js.map +1 -1
  180. package/dist/server/server/worktree-session.d.ts +47 -20
  181. package/dist/server/server/worktree-session.d.ts.map +1 -1
  182. package/dist/server/server/worktree-session.js +68 -25
  183. package/dist/server/server/worktree-session.js.map +1 -1
  184. package/dist/server/shared/binary-frames/file-transfer.d.ts +56 -0
  185. package/dist/server/shared/binary-frames/file-transfer.d.ts.map +1 -0
  186. package/dist/server/shared/binary-frames/file-transfer.js +108 -0
  187. package/dist/server/shared/binary-frames/file-transfer.js.map +1 -0
  188. package/dist/server/shared/binary-frames/index.d.ts +3 -0
  189. package/dist/server/shared/binary-frames/index.d.ts.map +1 -0
  190. package/dist/server/shared/binary-frames/index.js +3 -0
  191. package/dist/server/shared/binary-frames/index.js.map +1 -0
  192. package/dist/server/shared/{terminal-stream-protocol.d.ts → binary-frames/terminal.d.ts} +2 -2
  193. package/dist/server/shared/binary-frames/terminal.d.ts.map +1 -0
  194. package/dist/server/shared/{terminal-stream-protocol.js → binary-frames/terminal.js} +2 -2
  195. package/dist/server/shared/binary-frames/terminal.js.map +1 -0
  196. package/dist/server/shared/client-capabilities.d.ts +5 -0
  197. package/dist/server/shared/client-capabilities.d.ts.map +1 -0
  198. package/dist/server/shared/client-capabilities.js +4 -0
  199. package/dist/server/shared/client-capabilities.js.map +1 -0
  200. package/dist/server/shared/connection-offer.d.ts +8 -0
  201. package/dist/server/shared/connection-offer.d.ts.map +1 -1
  202. package/dist/server/shared/connection-offer.js +35 -0
  203. package/dist/server/shared/connection-offer.js.map +1 -1
  204. package/dist/server/shared/daemon-endpoints.d.ts +16 -1
  205. package/dist/server/shared/daemon-endpoints.d.ts.map +1 -1
  206. package/dist/server/shared/daemon-endpoints.js +65 -6
  207. package/dist/server/shared/daemon-endpoints.js.map +1 -1
  208. package/dist/server/shared/host-connection-schema.d.ts +23 -0
  209. package/dist/server/shared/host-connection-schema.d.ts.map +1 -0
  210. package/dist/server/shared/host-connection-schema.js +9 -0
  211. package/dist/server/shared/host-connection-schema.js.map +1 -0
  212. package/dist/server/shared/messages.d.ts +3242 -535
  213. package/dist/server/shared/messages.d.ts.map +1 -1
  214. package/dist/server/shared/messages.js +73 -34
  215. package/dist/server/shared/messages.js.map +1 -1
  216. package/dist/server/terminal/terminal-manager-factory.d.ts +7 -0
  217. package/dist/server/terminal/terminal-manager-factory.d.ts.map +1 -0
  218. package/dist/server/terminal/terminal-manager-factory.js +13 -0
  219. package/dist/server/terminal/terminal-manager-factory.js.map +1 -0
  220. package/dist/server/terminal/terminal-manager.d.ts +7 -1
  221. package/dist/server/terminal/terminal-manager.d.ts.map +1 -1
  222. package/dist/server/terminal/terminal-manager.js +14 -1
  223. package/dist/server/terminal/terminal-manager.js.map +1 -1
  224. package/dist/server/terminal/terminal-session-controller.d.ts +63 -0
  225. package/dist/server/terminal/terminal-session-controller.d.ts.map +1 -0
  226. package/dist/server/terminal/terminal-session-controller.js +615 -0
  227. package/dist/server/terminal/terminal-session-controller.js.map +1 -0
  228. package/dist/server/terminal/terminal-ts-loader.mjs +20 -0
  229. package/dist/server/terminal/terminal-worker-process.d.ts +2 -0
  230. package/dist/server/terminal/terminal-worker-process.d.ts.map +1 -0
  231. package/dist/server/terminal/terminal-worker-process.js +221 -0
  232. package/dist/server/terminal/terminal-worker-process.js.map +1 -0
  233. package/dist/server/terminal/terminal-worker-protocol.d.ts +113 -0
  234. package/dist/server/terminal/terminal-worker-protocol.d.ts.map +1 -0
  235. package/dist/server/terminal/terminal-worker-protocol.js +2 -0
  236. package/dist/server/terminal/terminal-worker-protocol.js.map +1 -0
  237. package/dist/server/terminal/terminal.d.ts +7 -0
  238. package/dist/server/terminal/terminal.d.ts.map +1 -1
  239. package/dist/server/terminal/terminal.js +22 -9
  240. package/dist/server/terminal/terminal.js.map +1 -1
  241. package/dist/server/terminal/worker-terminal-manager.d.ts +19 -0
  242. package/dist/server/terminal/worker-terminal-manager.d.ts.map +1 -0
  243. package/dist/server/terminal/worker-terminal-manager.js +466 -0
  244. package/dist/server/terminal/worker-terminal-manager.js.map +1 -0
  245. package/dist/server/utils/directory-suggestions.d.ts.map +1 -1
  246. package/dist/server/utils/directory-suggestions.js +10 -1
  247. package/dist/server/utils/directory-suggestions.js.map +1 -1
  248. package/dist/server/utils/process-tree.d.ts +25 -0
  249. package/dist/server/utils/process-tree.d.ts.map +1 -0
  250. package/dist/server/utils/process-tree.js +96 -0
  251. package/dist/server/utils/process-tree.js.map +1 -0
  252. package/dist/server/utils/windows-command.d.ts.map +1 -1
  253. package/dist/server/utils/windows-command.js +5 -1
  254. package/dist/server/utils/windows-command.js.map +1 -1
  255. package/dist/server/utils/worktree-metadata.d.ts +44 -0
  256. package/dist/server/utils/worktree-metadata.d.ts.map +1 -1
  257. package/dist/server/utils/worktree-metadata.js +58 -0
  258. package/dist/server/utils/worktree-metadata.js.map +1 -1
  259. package/dist/server/utils/worktree.d.ts +14 -2
  260. package/dist/server/utils/worktree.d.ts.map +1 -1
  261. package/dist/server/utils/worktree.js +22 -13
  262. package/dist/server/utils/worktree.js.map +1 -1
  263. package/package.json +5 -4
  264. package/dist/server/shared/terminal-stream-protocol.d.ts.map +0 -1
  265. package/dist/server/shared/terminal-stream-protocol.js.map +0 -1
@@ -6,10 +6,12 @@ import { realpathSync } from "node:fs";
6
6
  import { basename, resolve, sep } from "path";
7
7
  import { homedir } from "node:os";
8
8
  import { z } from "zod";
9
+ import { CLIENT_CAPS } from "../shared/client-capabilities.js";
9
10
  import { isLegacyEditorTargetId, serializeAgentStreamEvent, } from "./messages.js";
10
- import { captureTerminalLines } from "../terminal/terminal.js";
11
- import { TerminalOutputCoalescer } from "../terminal/terminal-output-coalescer.js";
12
- import { TerminalStreamOpcode, encodeTerminalSnapshotPayload, encodeTerminalStreamFrame, decodeTerminalResizePayload, } from "../shared/terminal-stream-protocol.js";
11
+ import { TerminalSessionController } from "../terminal/terminal-session-controller.js";
12
+ import { encodeFileTransferFrame, FileTransferOpcode, } from "../shared/binary-frames/index.js";
13
+ import { CursorError } from "./pagination/cursor.js";
14
+ import { SortablePager } from "./pagination/sortable-pager.js";
13
15
  import { TTSManager } from "./agent/tts-manager.js";
14
16
  import { STTManager } from "./agent/stt-manager.js";
15
17
  import { maybePersistTtsDebugAudio } from "./agent/tts-debug.js";
@@ -22,7 +24,7 @@ import { ensureAgentLoaded } from "./agent/agent-loading.js";
22
24
  import { sendPromptToAgent, unarchiveAgentState } from "./agent/mcp-shared.js";
23
25
  import { experimental_createMCPClient } from "ai";
24
26
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
25
- import { buildWorkspaceScriptPayloads } from "./script-status-projection.js";
27
+ import { buildWorkspaceScriptPayloads, readPaseoConfigForProjection, } from "./script-status-projection.js";
26
28
  import { deriveProjectSlug } from "./workspace-git-metadata.js";
27
29
  import { spawnWorkspaceScript } from "./worktree-bootstrap.js";
28
30
  import { applyMutableProviderConfigToOverrides } from "./daemon-config-store.js";
@@ -38,9 +40,8 @@ import { checkoutLiteFromGitSnapshot, normalizeWorkspaceId as normalizePersisted
38
40
  import { createPersistedProjectRecord, createPersistedWorkspaceRecord, } from "./workspace-registry.js";
39
41
  import { buildVoiceModeSystemPrompt, stripVoiceModeSystemPrompt, wrapSpokenInput, } from "./voice-config.js";
40
42
  import { isVoicePermissionAllowed } from "./voice-permission-policy.js";
41
- import { listDirectoryEntries, readExplorerFile, getDownloadableFileInfo, } from "./file-explorer/service.js";
43
+ import { listDirectoryEntries, readExplorerFile, readExplorerFileBytes, getDownloadableFileInfo, } from "./file-explorer/service.js";
42
44
  import { readPaseoConfigForEdit, writePaseoConfigForEdit, } from "../utils/paseo-config-file.js";
43
- import { runAsyncWorktreeBootstrap } from "./worktree-bootstrap.js";
44
45
  import { archivePersistedWorkspaceRecord } from "./workspace-archive-service.js";
45
46
  import { WorkspaceReconciliationService } from "./workspace-reconciliation-service.js";
46
47
  import { checkoutResolvedBranch, commitChanges, mergeToBase, mergeFromBase, pullCurrentBranch, pushCurrentBranch, createPullRequest, } from "../utils/checkout-git.js";
@@ -54,10 +55,10 @@ import { ChatServiceError } from "./chat/chat-service.js";
54
55
  import { notifyChatMentions } from "./chat/chat-mentions.js";
55
56
  import { execCommand } from "../utils/spawn.js";
56
57
  import { createGitHubService, } from "../services/github-service.js";
57
- import { createPaseoWorktree, } from "./paseo-worktree-service.js";
58
+ import { summarizeFetchWorkspacesEntries, WorkspaceDirectory, } from "./workspace-directory.js";
59
+ import { attemptFirstAgentBranchAutoName, createPaseoWorktree, } from "./paseo-worktree-service.js";
58
60
  import { createWorktreeCoreDeps } from "./worktree-core.js";
59
- import { assertSafeGitRef as assertWorktreeSafeGitRef, buildAgentSessionConfig as buildWorktreeAgentSessionConfig, runWorktreeSetupInBackground as runWorktreeSetupInBackgroundSession, handleCreatePaseoWorktreeRequest as handleCreateWorktreeRequest, handlePaseoWorktreeArchiveRequest as handleWorktreeArchiveRequest, handlePaseoWorktreeListRequest as handleWorktreeListRequest, handleWorkspaceSetupStatusRequest as handleWorkspaceSetupStatusRequestMessage, } from "./worktree-session.js";
60
- import { killTerminalsUnderPath as killWorktreeTerminalsUnderPath } from "./paseo-worktree-archive-service.js";
61
+ 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";
61
62
  import { toWorktreeWireError } from "./worktree-errors.js";
62
63
  const MAX_INITIAL_AGENT_TITLE_CHARS = Math.min(60, MAX_EXPLICIT_AGENT_TITLE_CHARS);
63
64
  const WORKSPACE_GIT_WATCH_REMOVED_STATE_KEY = "__removed__";
@@ -175,7 +176,6 @@ function clientSupportsAllProviders(appVersion) {
175
176
  function clientSupportsFlexibleEditorIds(appVersion) {
176
177
  return isAppVersionAtLeast(appVersion, MIN_VERSION_FLEXIBLE_EDITOR_IDS);
177
178
  }
178
- const MAX_TERMINAL_STREAM_SLOTS = 256;
179
179
  function beginAgentDeleteIfSupported(agentStorage, agentId) {
180
180
  if ("beginDelete" in agentStorage && typeof agentStorage.beginDelete === "function") {
181
181
  agentStorage.beginDelete(agentId);
@@ -207,50 +207,19 @@ export function resolveCreateAgentTitles(options) {
207
207
  provisionalTitle,
208
208
  };
209
209
  }
210
- function parseFetchWorkspacesCursorSort(raw) {
211
- const cursorSort = [];
212
- for (const item of raw) {
213
- if (!item ||
214
- typeof item !== "object" ||
215
- typeof item.key !== "string" ||
216
- typeof item.direction !== "string") {
217
- throw new SessionRequestError("invalid_cursor", "Invalid fetch_workspaces cursor");
218
- }
219
- const key = item.key;
220
- const direction = item.direction;
221
- if ((key !== "status_priority" &&
222
- key !== "activity_at" &&
223
- key !== "name" &&
224
- key !== "project_id") ||
225
- (direction !== "asc" && direction !== "desc")) {
226
- throw new SessionRequestError("invalid_cursor", "Invalid fetch_workspaces cursor");
227
- }
228
- cursorSort.push({ key, direction });
229
- }
230
- return cursorSort;
231
- }
232
- function parseFetchAgentsCursorSort(raw) {
233
- const cursorSort = [];
234
- for (const item of raw) {
235
- if (!item ||
236
- typeof item !== "object" ||
237
- typeof item.key !== "string" ||
238
- typeof item.direction !== "string") {
239
- throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
240
- }
241
- const key = item.key;
242
- const direction = item.direction;
243
- if ((key !== "status_priority" &&
244
- key !== "created_at" &&
245
- key !== "updated_at" &&
246
- key !== "title") ||
247
- (direction !== "asc" && direction !== "desc")) {
248
- throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
249
- }
250
- cursorSort.push({ key, direction });
251
- }
252
- return cursorSort;
210
+ function getFirstUserMessageText(timeline) {
211
+ for (const item of timeline) {
212
+ if (item.type !== "user_message") {
213
+ continue;
214
+ }
215
+ const text = item.text.trim();
216
+ if (text) {
217
+ return text;
218
+ }
219
+ }
220
+ return null;
253
221
  }
222
+ const FETCH_AGENTS_SORT_KEYS = ["status_priority", "created_at", "updated_at", "title"];
254
223
  export function resolveWaitForFinishError(options) {
255
224
  if (options.status !== "error") {
256
225
  return null;
@@ -258,27 +227,6 @@ export function resolveWaitForFinishError(options) {
258
227
  const message = options.final?.lastError;
259
228
  return typeof message === "string" && message.trim().length > 0 ? message : "Agent failed";
260
229
  }
261
- function summarizeFetchWorkspacesEntries(entries) {
262
- const workspaces = Array.from(entries, (entry) => ({
263
- id: entry.id,
264
- projectId: entry.projectId,
265
- projectDisplayName: entry.projectDisplayName,
266
- name: entry.name,
267
- status: entry.status,
268
- workspaceKind: entry.workspaceKind,
269
- activityAt: entry.activityAt,
270
- }));
271
- const statusCounts = new Map();
272
- for (const workspace of workspaces) {
273
- statusCounts.set(workspace.status, (statusCounts.get(workspace.status) ?? 0) + 1);
274
- }
275
- return {
276
- count: workspaces.length,
277
- projectIds: [...new Set(workspaces.map((workspace) => workspace.projectId))],
278
- statusCounts: Object.fromEntries(statusCounts),
279
- workspaces,
280
- };
281
- }
282
230
  class SessionRequestError extends Error {
283
231
  constructor(code, message) {
284
232
  super(message);
@@ -305,6 +253,31 @@ class VoiceFeatureUnavailableError extends Error {
305
253
  this.missingModelIds = [...context.missingModelIds];
306
254
  }
307
255
  }
256
+ function buildImportPersistenceHandle(input) {
257
+ const cwd = input.cwd ?? process.cwd();
258
+ return {
259
+ provider: input.provider,
260
+ sessionId: input.sessionId,
261
+ nativeHandle: input.sessionId,
262
+ metadata: {
263
+ provider: input.provider,
264
+ cwd,
265
+ },
266
+ };
267
+ }
268
+ function applyImportCwdOverride(handle, cwd) {
269
+ if (!cwd) {
270
+ return handle;
271
+ }
272
+ return {
273
+ ...handle,
274
+ metadata: {
275
+ ...handle.metadata,
276
+ provider: handle.provider,
277
+ cwd,
278
+ },
279
+ };
280
+ }
308
281
  function convertPCMToWavBuffer(pcmBuffer, sampleRate, channels, bitsPerSample) {
309
282
  const headerSize = 44;
310
283
  const wavBuffer = Buffer.alloc(headerSize + pcmBuffer.length);
@@ -326,6 +299,13 @@ function convertPCMToWavBuffer(pcmBuffer, sampleRate, channels, bitsPerSample) {
326
299
  pcmBuffer.copy(wavBuffer, 44);
327
300
  return wavBuffer;
328
301
  }
302
+ function parseClientCapabilities(capabilities) {
303
+ if (!capabilities) {
304
+ return new Set();
305
+ }
306
+ const known = new Set(Object.values(CLIENT_CAPS));
307
+ return new Set(Object.entries(capabilities).flatMap(([key, value]) => value === true && known.has(key) ? [key] : []));
308
+ }
329
309
  /**
330
310
  * Session represents a single connected client session.
331
311
  * It owns all state management, orchestration logic, and message processing.
@@ -358,12 +338,6 @@ export class Session {
358
338
  this.clientActivity = null;
359
339
  this.MOBILE_BACKGROUND_STREAM_GRACE_MS = 60000;
360
340
  this.unsubscribeProviderSnapshotEvents = null;
361
- this.subscribedTerminalDirectories = new Set();
362
- this.unsubscribeTerminalsChanged = null;
363
- this.terminalExitSubscriptions = new Map();
364
- this.activeTerminalStreams = new Map();
365
- this.terminalIdToSlot = new Map();
366
- this.nextTerminalSlot = 0;
367
341
  this.inflightRequests = 0;
368
342
  this.peakInflightRequests = 0;
369
343
  this.availableEditorTargetsCache = new TTLCache({
@@ -381,16 +355,28 @@ export class Session {
381
355
  this.workspaceGitSubscriptions = new Map();
382
356
  this.voiceModeAgentId = null;
383
357
  this.voiceModeBaseConfig = null;
384
- this.workspaceStatePriority = {
385
- needs_input: 0,
386
- failed: 1,
387
- running: 2,
388
- attention: 3,
389
- done: 4,
390
- };
391
- const { clientId, appVersion, onMessage, onBinaryMessage, onLifecycleIntent, logger, downloadTokenStore, pushTokenStore, paseoHome, agentManager, agentStorage, projectRegistry, workspaceRegistry, chatService, scheduleService, loopService, checkoutDiffManager, github, workspaceGitService, daemonConfigStore, mcpBaseUrl, stt, tts, terminalManager, providerSnapshotManager, scriptRouteStore, scriptRuntimeStore, workspaceSetupSnapshots, onBranchChanged, getDaemonTcpPort, getDaemonTcpHost, resolveScriptHealth, voice, voiceBridge, dictation, agentProviderRuntimeSettings, providerOverrides, isDev, } = options;
358
+ this.agentsPager = new SortablePager({
359
+ validKeys: FETCH_AGENTS_SORT_KEYS,
360
+ defaultSort: [{ key: "updated_at", direction: "desc" }],
361
+ label: "fetch_agents",
362
+ getId: (agent) => agent.id,
363
+ getSortValue: (agent, key) => {
364
+ switch (key) {
365
+ case "status_priority":
366
+ return this.getStatusPriority(agent);
367
+ case "created_at":
368
+ return Date.parse(agent.createdAt);
369
+ case "updated_at":
370
+ return Date.parse(agent.updatedAt);
371
+ case "title":
372
+ return agent.title?.toLocaleLowerCase() ?? "";
373
+ }
374
+ },
375
+ });
376
+ const { clientId, appVersion, clientCapabilities, onMessage, onBinaryMessage, onLifecycleIntent, logger, downloadTokenStore, pushTokenStore, paseoHome, agentManager, agentStorage, projectRegistry, workspaceRegistry, chatService, scheduleService, loopService, checkoutDiffManager, github, workspaceGitService, daemonConfigStore, mcpBaseUrl, stt, tts, terminalManager, providerSnapshotManager, scriptRouteStore, scriptRuntimeStore, workspaceSetupSnapshots, onBranchChanged, getDaemonTcpPort, getDaemonTcpHost, resolveScriptHealth, voice, voiceBridge, dictation, agentProviderRuntimeSettings, providerOverrides, isDev, } = options;
392
377
  this.clientId = clientId;
393
378
  this.appVersion = appVersion ?? null;
379
+ this.clientCapabilities = parseClientCapabilities(clientCapabilities);
394
380
  this.sessionId = uuidv4();
395
381
  this.onMessage = onMessage;
396
382
  this.onBinaryMessage = onBinaryMessage ?? null;
@@ -416,6 +402,14 @@ export class Session {
416
402
  this.daemonConfigStore = daemonConfigStore;
417
403
  this.mcpBaseUrl = mcpBaseUrl ?? null;
418
404
  this.terminalManager = terminalManager;
405
+ this.terminalController = new TerminalSessionController({
406
+ terminalManager,
407
+ emit: (msg) => this.emit(msg),
408
+ emitBinary: (frame) => this.emitBinary(frame),
409
+ hasBinaryChannel: () => this.onBinaryMessage !== null,
410
+ isPathWithinRoot: (rootPath, candidatePath) => this.isPathWithinRoot(rootPath, candidatePath),
411
+ sessionLogger: this.sessionLogger,
412
+ });
419
413
  this.providerSnapshotManager = providerSnapshotManager ?? null;
420
414
  this.scriptRouteStore = scriptRouteStore ?? null;
421
415
  this.scriptRuntimeStore = scriptRuntimeStore ?? null;
@@ -430,6 +424,14 @@ export class Session {
430
424
  this.providerOverrides = providerOverrides;
431
425
  this.isDev = isDev === true;
432
426
  this.abortController = new AbortController();
427
+ this.workspaceDirectory = new WorkspaceDirectory({
428
+ logger: this.sessionLogger,
429
+ projectRegistry: this.projectRegistry,
430
+ workspaceRegistry: this.workspaceRegistry,
431
+ listAgentPayloads: () => this.listAgentPayloads(),
432
+ isProviderVisibleToClient: (provider) => this.isProviderVisibleToClient(provider),
433
+ buildWorkspaceDescriptor: (input) => this.buildWorkspaceDescriptor(input),
434
+ });
433
435
  this.initializePerSessionManagers({ tts, stt, dictation });
434
436
  // Initialize agent MCP client asynchronously
435
437
  void this.initializeAgentMcp();
@@ -441,6 +443,12 @@ export class Session {
441
443
  this.appVersion = appVersion;
442
444
  }
443
445
  }
446
+ updateClientCapabilities(capabilities) {
447
+ this.clientCapabilities = parseClientCapabilities(capabilities);
448
+ }
449
+ supports(capability) {
450
+ return this.clientCapabilities.has(capability);
451
+ }
444
452
  async syncWorkspaceGitObserverForWorkspace(workspace) {
445
453
  const descriptor = await this.describeWorkspaceRecordWithGitData(workspace);
446
454
  this.syncWorkspaceGitObservers([descriptor]);
@@ -451,8 +459,17 @@ export class Session {
451
459
  async archiveWorkspaceRecordForExternalMutation(workspaceId) {
452
460
  await this.archiveWorkspaceRecord(workspaceId);
453
461
  }
462
+ markWorkspaceArchivingForExternalMutation(workspaceIds, archivingAt) {
463
+ this.markWorkspaceArchiving(workspaceIds, archivingAt);
464
+ }
465
+ clearWorkspaceArchivingForExternalMutation(workspaceIds) {
466
+ this.clearWorkspaceArchiving(workspaceIds);
467
+ }
468
+ async emitWorkspaceUpdatesForExternalWorkspaceIds(workspaceIds) {
469
+ await this.emitWorkspaceUpdatesForWorkspaceIds(workspaceIds);
470
+ }
454
471
  async emitWorkspaceUpdatesForExternalCwds(cwds) {
455
- await this.emitWorkspaceUpdatesForCwds(cwds);
472
+ await Promise.all(Array.from(cwds, (cwd) => this.emitWorkspaceUpdateForCwd(cwd)));
456
473
  }
457
474
  async warmWorkspaceGitDataForWorkspace(workspace) {
458
475
  await this.syncWorkspaceGitObserverForWorkspace(workspace);
@@ -465,9 +482,10 @@ export class Session {
465
482
  return this.clientActivity;
466
483
  }
467
484
  getRuntimeMetrics() {
485
+ const terminalMetrics = this.terminalController.getMetrics();
468
486
  return {
469
- terminalDirectorySubscriptionCount: this.subscribedTerminalDirectories.size,
470
- terminalSubscriptionCount: this.activeTerminalStreams.size,
487
+ terminalDirectorySubscriptionCount: terminalMetrics.directorySubscriptionCount,
488
+ terminalSubscriptionCount: terminalMetrics.streamSubscriptionCount,
471
489
  inflightRequests: this.inflightRequests,
472
490
  peakInflightRequests: this.peakInflightRequests,
473
491
  };
@@ -605,9 +623,7 @@ export class Session {
605
623
  * Subscribe to AgentManager events and forward them to the client
606
624
  */
607
625
  subscribeToOptionalManagers() {
608
- if (this.terminalManager) {
609
- this.unsubscribeTerminalsChanged = this.terminalManager.subscribeTerminalsChanged((event) => this.handleTerminalsChanged(event));
610
- }
626
+ this.terminalController.start();
611
627
  if (this.providerSnapshotManager) {
612
628
  const handleProviderSnapshotChange = (entries, cwd) => {
613
629
  // COMPAT(providersSnapshot): keep provider visibility gating for older clients.
@@ -1110,6 +1126,8 @@ export class Session {
1110
1126
  return this.handleCreateAgentRequest(msg);
1111
1127
  case "resume_agent_request":
1112
1128
  return this.handleResumeAgentRequest(msg);
1129
+ case "import_agent_request":
1130
+ return this.handleImportAgentRequest(msg);
1113
1131
  case "refresh_agent_request":
1114
1132
  return this.handleRefreshAgentRequest(msg);
1115
1133
  case "cancel_agent_request":
@@ -1336,34 +1354,10 @@ export class Session {
1336
1354
  }
1337
1355
  }
1338
1356
  dispatchTerminalMessage(msg) {
1339
- switch (msg.type) {
1340
- case "subscribe_terminals_request":
1341
- this.handleSubscribeTerminalsRequest(msg);
1342
- return undefined;
1343
- case "unsubscribe_terminals_request":
1344
- this.handleUnsubscribeTerminalsRequest(msg);
1345
- return undefined;
1346
- case "list_terminals_request":
1347
- return this.handleListTerminalsRequest(msg);
1348
- case "create_terminal_request":
1349
- return this.handleCreateTerminalRequest(msg);
1350
- case "start_workspace_script_request":
1351
- return this.handleStartWorkspaceScriptRequest(msg);
1352
- case "subscribe_terminal_request":
1353
- return this.handleSubscribeTerminalRequest(msg);
1354
- case "unsubscribe_terminal_request":
1355
- this.handleUnsubscribeTerminalRequest(msg);
1356
- return undefined;
1357
- case "terminal_input":
1358
- this.handleTerminalInput(msg);
1359
- return undefined;
1360
- case "kill_terminal_request":
1361
- return this.handleKillTerminalRequest(msg);
1362
- case "capture_terminal_request":
1363
- return this.handleCaptureTerminalRequest(msg);
1364
- default:
1365
- return undefined;
1357
+ if (msg.type === "start_workspace_script_request") {
1358
+ return this.handleStartWorkspaceScriptRequest(msg);
1366
1359
  }
1360
+ return this.terminalController.dispatch(msg);
1367
1361
  }
1368
1362
  dispatchChatScheduleLoopMessage(msg) {
1369
1363
  switch (msg.type) {
@@ -1423,38 +1417,7 @@ export class Session {
1423
1417
  this.peakInflightRequests = this.inflightRequests;
1424
1418
  }
1425
1419
  handleBinaryFrame(frame) {
1426
- const activeStream = this.activeTerminalStreams.get(frame.slot);
1427
- if (!activeStream || !this.terminalManager) {
1428
- return;
1429
- }
1430
- const terminal = this.terminalManager.getTerminal(activeStream.terminalId);
1431
- if (!terminal) {
1432
- this.detachTerminalStream(activeStream.terminalId, { emitExit: true });
1433
- return;
1434
- }
1435
- switch (frame.opcode) {
1436
- case TerminalStreamOpcode.Input: {
1437
- if (frame.payload.byteLength === 0) {
1438
- return;
1439
- }
1440
- const text = Buffer.from(frame.payload).toString("utf8");
1441
- if (!text) {
1442
- return;
1443
- }
1444
- terminal.send({ type: "input", data: text });
1445
- return;
1446
- }
1447
- case TerminalStreamOpcode.Resize: {
1448
- const resize = decodeTerminalResizePayload(frame.payload);
1449
- if (!resize) {
1450
- return;
1451
- }
1452
- terminal.send({ type: "resize", rows: resize.rows, cols: resize.cols });
1453
- return;
1454
- }
1455
- default:
1456
- return;
1457
- }
1420
+ this.terminalController.handleBinaryFrame(frame);
1458
1421
  }
1459
1422
  async handleRestartServerRequest(requestId, reason) {
1460
1423
  const payload = {
@@ -1633,7 +1596,7 @@ export class Session {
1633
1596
  const terminals = [];
1634
1597
  for (const terminalId of msg.terminalIds) {
1635
1598
  try {
1636
- terminals.push(this.killTerminalForClose(terminalId));
1599
+ terminals.push(this.terminalController.killTerminalForClose(terminalId));
1637
1600
  }
1638
1601
  catch (error) {
1639
1602
  this.sessionLogger.warn({ err: error, terminalId, requestId: msg.requestId }, "Failed to kill terminal during close_items batch");
@@ -2064,14 +2027,22 @@ export class Session {
2064
2027
  ...config,
2065
2028
  ...(provisionalTitle ? { title: provisionalTitle } : {}),
2066
2029
  };
2067
- const { sessionConfig, worktreeBootstrap } = await this.buildAgentSessionConfig(resolvedConfig, git, worktreeName, attachments);
2068
- const resolvedWorkspace = msg.workspaceId
2030
+ const firstAgentContext = {
2031
+ ...(trimmedPrompt ? { prompt: trimmedPrompt } : {}),
2032
+ ...(attachments && attachments.length > 0 ? { attachments } : {}),
2033
+ };
2034
+ const { sessionConfig, setupContinuation } = await this.buildAgentSessionConfig(resolvedConfig, git, worktreeName, firstAgentContext);
2035
+ let resolvedWorkspace = msg.workspaceId
2069
2036
  ? await this.workspaceRegistry.get(msg.workspaceId)
2070
2037
  : ((await this.findWorkspaceByDirectory(sessionConfig.cwd)) ??
2071
2038
  (await this.findOrCreateWorkspaceForDirectory(sessionConfig.cwd)));
2072
2039
  if (!resolvedWorkspace) {
2073
2040
  throw new Error(`Workspace not found: ${msg.workspaceId}`);
2074
2041
  }
2042
+ resolvedWorkspace = await this.maybeAutoNameWorkspaceBranchForFirstAgent({
2043
+ workspace: resolvedWorkspace,
2044
+ firstAgentContext,
2045
+ });
2075
2046
  const snapshot = await this.agentManager.createAgent({
2076
2047
  ...sessionConfig,
2077
2048
  cwd: resolvedWorkspace.cwd,
@@ -2102,25 +2073,9 @@ export class Session {
2102
2073
  },
2103
2074
  });
2104
2075
  }
2105
- if (worktreeBootstrap) {
2106
- void runAsyncWorktreeBootstrap({
2107
- agentId: snapshot.id,
2108
- worktree: worktreeBootstrap.worktree,
2109
- shouldBootstrap: worktreeBootstrap.shouldBootstrap,
2110
- terminalManager: this.terminalManager,
2111
- appendTimelineItem: (item) => appendTimelineItemIfAgentKnown({
2112
- agentManager: this.agentManager,
2113
- agentId: snapshot.id,
2114
- item,
2115
- }),
2116
- emitLiveTimelineItem: (item) => emitLiveTimelineItemIfAgentKnown({
2117
- agentManager: this.agentManager,
2118
- agentId: snapshot.id,
2119
- item,
2120
- }),
2121
- logger: this.sessionLogger,
2122
- });
2123
- }
2076
+ setupContinuation?.startAfterAgentCreate({
2077
+ agentId: snapshot.id,
2078
+ });
2124
2079
  this.sessionLogger.info({ agentId: snapshot.id, provider: snapshot.provider }, `Created agent ${snapshot.id} (${snapshot.provider})`);
2125
2080
  }
2126
2081
  catch (error) {
@@ -2164,9 +2119,6 @@ export class Session {
2164
2119
  explicitTitle: params.explicitTitle,
2165
2120
  paseoHome: this.paseoHome,
2166
2121
  logger: this.sessionLogger,
2167
- deps: {
2168
- workspaceGitService: this.workspaceGitService,
2169
- },
2170
2122
  });
2171
2123
  const started = await this.handleSendAgentMessage(snapshot.id, trimmedPrompt || "", resolveClientMessageId(clientMessageId), images, attachments, outputSchema ? { outputSchema } : undefined);
2172
2124
  if (!started.ok) {
@@ -2223,6 +2175,83 @@ export class Session {
2223
2175
  });
2224
2176
  }
2225
2177
  }
2178
+ async handleImportAgentRequest(msg) {
2179
+ const { provider, sessionId, cwd, labels, requestId } = msg;
2180
+ this.sessionLogger.info({ sessionId, provider }, `Importing agent ${sessionId} (${provider})`);
2181
+ try {
2182
+ const descriptor = await this.agentManager.findPersistedAgent(provider, sessionId);
2183
+ if (!descriptor && provider === "opencode" && !cwd) {
2184
+ throw new Error("OpenCode sessions require --cwd when the session cannot be found in persisted agents");
2185
+ }
2186
+ const handle = descriptor
2187
+ ? applyImportCwdOverride(descriptor.persistence, cwd)
2188
+ : buildImportPersistenceHandle({ provider, sessionId, cwd });
2189
+ const overrides = cwd ? { cwd } : undefined;
2190
+ await this.unarchiveAgentByHandle(handle);
2191
+ const snapshot = await this.agentManager.resumeAgentFromPersistence(handle, overrides, undefined, {
2192
+ labels,
2193
+ });
2194
+ await unarchiveAgentState(this.agentStorage, this.agentManager, snapshot.id);
2195
+ await this.agentManager.hydrateTimelineFromProvider(snapshot.id);
2196
+ await this.applyImportedAgentTitle(snapshot);
2197
+ await this.forwardAgentUpdate(snapshot);
2198
+ const timelineSize = this.agentManager.getTimeline(snapshot.id).length;
2199
+ const agentPayload = await this.buildAgentPayload(snapshot);
2200
+ this.emit({
2201
+ type: "status",
2202
+ payload: {
2203
+ status: "agent_resumed",
2204
+ agentId: snapshot.id,
2205
+ requestId,
2206
+ timelineSize,
2207
+ agent: agentPayload,
2208
+ },
2209
+ });
2210
+ }
2211
+ catch (error) {
2212
+ const message = error instanceof Error ? error.message : String(error);
2213
+ this.sessionLogger.error({ err: error }, "Failed to import agent");
2214
+ this.emit({
2215
+ type: "status",
2216
+ payload: {
2217
+ status: "agent_create_failed",
2218
+ requestId,
2219
+ error: message,
2220
+ },
2221
+ });
2222
+ this.emit({
2223
+ type: "activity_log",
2224
+ payload: {
2225
+ id: uuidv4(),
2226
+ timestamp: new Date(),
2227
+ type: "error",
2228
+ content: `Failed to import agent: ${message}`,
2229
+ },
2230
+ });
2231
+ }
2232
+ }
2233
+ async applyImportedAgentTitle(snapshot) {
2234
+ const initialPrompt = getFirstUserMessageText(this.agentManager.getTimeline(snapshot.id));
2235
+ if (!initialPrompt) {
2236
+ return;
2237
+ }
2238
+ const { explicitTitle, provisionalTitle } = resolveCreateAgentTitles({
2239
+ configTitle: snapshot.config.title,
2240
+ initialPrompt,
2241
+ });
2242
+ if (!explicitTitle && provisionalTitle) {
2243
+ await this.agentManager.setTitle(snapshot.id, provisionalTitle);
2244
+ }
2245
+ scheduleAgentMetadataGeneration({
2246
+ agentManager: this.agentManager,
2247
+ agentId: snapshot.id,
2248
+ cwd: snapshot.cwd,
2249
+ initialPrompt,
2250
+ explicitTitle,
2251
+ paseoHome: this.paseoHome,
2252
+ logger: this.sessionLogger,
2253
+ });
2254
+ }
2226
2255
  async handleRefreshAgentRequest(msg) {
2227
2256
  const { agentId, requestId } = msg;
2228
2257
  this.sessionLogger.info({ agentId }, `Refreshing agent ${agentId} from persistence`);
@@ -2298,16 +2327,53 @@ export class Session {
2298
2327
  this.handleAgentRunError(agentId, error, "Failed to cancel running agent on request");
2299
2328
  }
2300
2329
  }
2301
- async buildAgentSessionConfig(config, gitOptions, legacyWorktreeName, attachments) {
2330
+ async buildAgentSessionConfig(config, gitOptions, legacyWorktreeName, firstAgentContext) {
2302
2331
  return buildWorktreeAgentSessionConfig({
2303
2332
  paseoHome: this.paseoHome,
2304
2333
  sessionLogger: this.sessionLogger,
2305
2334
  workspaceGitService: this.workspaceGitService,
2306
- createPaseoWorktree: (input, serviceOptions) => this.createPaseoWorktree(input, serviceOptions),
2335
+ createPaseoWorktree: (input, serviceOptions) => this.createPaseoWorktreeWorkflow(input, {
2336
+ ...serviceOptions,
2337
+ setupContinuation: {
2338
+ kind: "agent",
2339
+ terminalManager: this.terminalManager,
2340
+ appendTimelineItem: ({ agentId, item }) => appendTimelineItemIfAgentKnown({
2341
+ agentManager: this.agentManager,
2342
+ agentId,
2343
+ item,
2344
+ }),
2345
+ emitLiveTimelineItem: ({ agentId, item }) => emitLiveTimelineItemIfAgentKnown({
2346
+ agentManager: this.agentManager,
2347
+ agentId,
2348
+ item,
2349
+ }),
2350
+ logger: this.sessionLogger,
2351
+ },
2352
+ }),
2307
2353
  checkoutExistingBranch: (cwd, branch) => this.checkoutExistingBranch(cwd, branch),
2308
2354
  createBranchFromBase: (params) => this.createBranchFromBase(params),
2309
2355
  github: this.github,
2310
- }, config, gitOptions, legacyWorktreeName, attachments);
2356
+ }, config, gitOptions, legacyWorktreeName, firstAgentContext);
2357
+ }
2358
+ async maybeAutoNameWorkspaceBranchForFirstAgent(input) {
2359
+ const coreDeps = createWorktreeCoreDeps(this.github);
2360
+ const result = await attemptFirstAgentBranchAutoName({
2361
+ cwd: input.workspace.cwd,
2362
+ firstAgentContext: input.firstAgentContext,
2363
+ generateBranchName: coreDeps.generateBranchName,
2364
+ });
2365
+ if (!result.renamed || !result.branchName) {
2366
+ return input.workspace;
2367
+ }
2368
+ const updatedWorkspace = {
2369
+ ...input.workspace,
2370
+ displayName: result.branchName,
2371
+ updatedAt: new Date().toISOString(),
2372
+ };
2373
+ await this.workspaceRegistry.upsert(updatedWorkspace);
2374
+ await this.notifyGitMutation(input.workspace.cwd, "rename-branch");
2375
+ await this.emitWorkspaceUpdateForCwd(input.workspace.cwd);
2376
+ return updatedWorkspace;
2311
2377
  }
2312
2378
  emitProviderDisabledResponse(kind, provider, requestId, fetchedAt) {
2313
2379
  const payload = {
@@ -3970,16 +4036,13 @@ export class Session {
3970
4036
  workspaceGitService: this.workspaceGitService,
3971
4037
  agentManager: this.agentManager,
3972
4038
  agentStorage: this.agentStorage,
3973
- archiveWorkspaceRecord: async (workspaceDirectory) => {
3974
- const workspace = await this.findWorkspaceByDirectory(workspaceDirectory);
3975
- if (workspace) {
3976
- await this.archiveWorkspaceRecord(workspace.workspaceId);
3977
- }
3978
- },
4039
+ archiveWorkspaceRecord: (workspaceId) => this.archiveWorkspaceRecord(workspaceId),
3979
4040
  emit: (message) => this.emit(message),
3980
- emitWorkspaceUpdatesForCwds: (cwds) => this.emitWorkspaceUpdatesForCwds(cwds),
4041
+ emitWorkspaceUpdatesForWorkspaceIds: (workspaceIds) => this.emitWorkspaceUpdatesForWorkspaceIds(workspaceIds),
4042
+ markWorkspaceArchiving: (workspaceIds, archivingAt) => this.markWorkspaceArchiving(workspaceIds, archivingAt),
4043
+ clearWorkspaceArchiving: (workspaceIds) => this.clearWorkspaceArchiving(workspaceIds),
3981
4044
  isPathWithinRoot: (rootPath, candidatePath) => this.isPathWithinRoot(rootPath, candidatePath),
3982
- killTerminalsUnderPath: (rootPath) => this.killTerminalsUnderPath(rootPath),
4045
+ killTerminalsUnderPath: (rootPath) => this.terminalController.killTerminalsUnderPath(rootPath),
3983
4046
  sessionLogger: this.sessionLogger,
3984
4047
  }, msg);
3985
4048
  }
@@ -4024,22 +4087,49 @@ export class Session {
4024
4087
  });
4025
4088
  }
4026
4089
  else {
4027
- const file = await readExplorerFile({
4028
- root: cwd,
4029
- relativePath: requestedPath,
4030
- });
4031
- this.emit({
4032
- type: "file_explorer_response",
4033
- payload: {
4034
- cwd,
4035
- path: file.path,
4036
- mode,
4037
- directory: null,
4038
- file,
4039
- error: null,
4090
+ if (request.acceptBinary && this.onBinaryMessage) {
4091
+ const file = await readExplorerFileBytes({
4092
+ root: cwd,
4093
+ relativePath: requestedPath,
4094
+ });
4095
+ this.emitBinary(encodeFileTransferFrame({
4096
+ opcode: FileTransferOpcode.FileBegin,
4040
4097
  requestId,
4041
- },
4042
- });
4098
+ metadata: {
4099
+ mime: file.mimeType,
4100
+ size: file.size,
4101
+ encoding: file.encoding,
4102
+ modifiedAt: file.modifiedAt,
4103
+ },
4104
+ }));
4105
+ this.emitBinary(encodeFileTransferFrame({
4106
+ opcode: FileTransferOpcode.FileChunk,
4107
+ requestId,
4108
+ payload: file.bytes,
4109
+ }));
4110
+ this.emitBinary(encodeFileTransferFrame({
4111
+ opcode: FileTransferOpcode.FileEnd,
4112
+ requestId,
4113
+ }));
4114
+ }
4115
+ else {
4116
+ const file = await readExplorerFile({
4117
+ root: cwd,
4118
+ relativePath: requestedPath,
4119
+ });
4120
+ this.emit({
4121
+ type: "file_explorer_response",
4122
+ payload: {
4123
+ cwd,
4124
+ path: file.path,
4125
+ mode,
4126
+ directory: null,
4127
+ file,
4128
+ error: null,
4129
+ requestId,
4130
+ },
4131
+ });
4132
+ }
4043
4133
  }
4044
4134
  }
4045
4135
  catch (error) {
@@ -4237,22 +4327,6 @@ export class Session {
4237
4327
  const payload = this.buildStoredAgentPayload(record);
4238
4328
  return this.isProviderVisibleToClient(payload.provider) ? payload : null;
4239
4329
  }
4240
- normalizeFetchAgentsSort(sort) {
4241
- const fallback = [{ key: "updated_at", direction: "desc" }];
4242
- if (!sort || sort.length === 0) {
4243
- return fallback;
4244
- }
4245
- const deduped = [];
4246
- const seen = new Set();
4247
- for (const entry of sort) {
4248
- if (seen.has(entry.key)) {
4249
- continue;
4250
- }
4251
- seen.add(entry.key);
4252
- deduped.push(entry);
4253
- }
4254
- return deduped.length > 0 ? deduped : fallback;
4255
- }
4256
4330
  getStatusPriority(agent) {
4257
4331
  const attentionReason = agent.attentionReason ?? null;
4258
4332
  const hasPendingPermission = (agent.pendingPermissions?.length ?? 0) > 0;
@@ -4270,109 +4344,6 @@ export class Session {
4270
4344
  }
4271
4345
  return 4;
4272
4346
  }
4273
- getFetchAgentsSortValue(entry, key) {
4274
- switch (key) {
4275
- case "status_priority":
4276
- return this.getStatusPriority(entry.agent);
4277
- case "created_at":
4278
- return Date.parse(entry.agent.createdAt);
4279
- case "updated_at":
4280
- return Date.parse(entry.agent.updatedAt);
4281
- case "title":
4282
- return entry.agent.title?.toLocaleLowerCase() ?? "";
4283
- }
4284
- }
4285
- getFetchAgentsSortValueFromAgent(agent, key) {
4286
- switch (key) {
4287
- case "status_priority":
4288
- return this.getStatusPriority(agent);
4289
- case "created_at":
4290
- return Date.parse(agent.createdAt);
4291
- case "updated_at":
4292
- return Date.parse(agent.updatedAt);
4293
- case "title":
4294
- return agent.title?.toLocaleLowerCase() ?? "";
4295
- }
4296
- }
4297
- compareSortValues(left, right) {
4298
- if (left === right) {
4299
- return 0;
4300
- }
4301
- if (left === null) {
4302
- return -1;
4303
- }
4304
- if (right === null) {
4305
- return 1;
4306
- }
4307
- if (typeof left === "number" && typeof right === "number") {
4308
- return left < right ? -1 : 1;
4309
- }
4310
- return String(left).localeCompare(String(right));
4311
- }
4312
- compareFetchAgentsAgents(left, right, sort) {
4313
- for (const spec of sort) {
4314
- const leftValue = this.getFetchAgentsSortValueFromAgent(left, spec.key);
4315
- const rightValue = this.getFetchAgentsSortValueFromAgent(right, spec.key);
4316
- const base = this.compareSortValues(leftValue, rightValue);
4317
- if (base === 0) {
4318
- continue;
4319
- }
4320
- return spec.direction === "asc" ? base : -base;
4321
- }
4322
- return left.id.localeCompare(right.id);
4323
- }
4324
- encodeFetchAgentsCursor(entry, sort) {
4325
- const values = {};
4326
- for (const spec of sort) {
4327
- values[spec.key] = this.getFetchAgentsSortValue(entry, spec.key);
4328
- }
4329
- return Buffer.from(JSON.stringify({
4330
- sort,
4331
- values,
4332
- id: entry.agent.id,
4333
- }), "utf8").toString("base64url");
4334
- }
4335
- decodeFetchAgentsCursor(cursor, sort) {
4336
- let parsed;
4337
- try {
4338
- parsed = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
4339
- }
4340
- catch {
4341
- throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
4342
- }
4343
- if (!parsed || typeof parsed !== "object") {
4344
- throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
4345
- }
4346
- const payload = parsed;
4347
- if (!Array.isArray(payload.sort) || typeof payload.id !== "string") {
4348
- throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
4349
- }
4350
- if (!payload.values || typeof payload.values !== "object") {
4351
- throw new SessionRequestError("invalid_cursor", "Invalid fetch_agents cursor");
4352
- }
4353
- const cursorSort = parseFetchAgentsCursorSort(payload.sort);
4354
- if (cursorSort.length !== sort.length ||
4355
- cursorSort.some((entry, index) => entry.key !== sort[index]?.key || entry.direction !== sort[index]?.direction)) {
4356
- throw new SessionRequestError("invalid_cursor", "fetch_agents cursor does not match current sort");
4357
- }
4358
- return {
4359
- sort: cursorSort,
4360
- values: payload.values,
4361
- id: payload.id,
4362
- };
4363
- }
4364
- compareAgentWithCursor(agent, cursor, sort) {
4365
- for (const spec of sort) {
4366
- const leftValue = this.getFetchAgentsSortValueFromAgent(agent, spec.key);
4367
- const rightValue = cursor.values[spec.key] !== undefined ? (cursor.values[spec.key] ?? null) : null;
4368
- const base = this.compareSortValues(leftValue, rightValue);
4369
- if (base === 0) {
4370
- continue;
4371
- }
4372
- return spec.direction === "asc" ? base : -base;
4373
- }
4374
- return agent.id.localeCompare(cursor.id);
4375
- }
4376
4347
  async buildActiveProjectPlacementsByWorkspaceCwd() {
4377
4348
  const [persistedWorkspaces, persistedProjects] = await Promise.all([
4378
4349
  this.workspaceRegistry.list(),
@@ -4431,7 +4402,7 @@ export class Session {
4431
4402
  ? { ...request.filter, includeArchived: true }
4432
4403
  : request.filter;
4433
4404
  const scope = request.type === "fetch_agents_request" ? request.scope : undefined;
4434
- const sort = this.normalizeFetchAgentsSort(request.sort);
4405
+ const sort = this.agentsPager.normalizeSort(request.sort);
4435
4406
  let agents = await this.listAgentPayloads({
4436
4407
  labels: filter?.labels,
4437
4408
  includeUnavailablePersisted: request.type === "fetch_agent_history_request",
@@ -4454,11 +4425,11 @@ export class Session {
4454
4425
  return placementPromise;
4455
4426
  };
4456
4427
  let candidates = [...agents];
4457
- candidates.sort((left, right) => this.compareFetchAgentsAgents(left, right, sort));
4428
+ candidates.sort((left, right) => this.agentsPager.compare(left, right, sort));
4458
4429
  const cursorToken = request.page?.cursor;
4459
4430
  if (cursorToken) {
4460
- const cursor = this.decodeFetchAgentsCursor(cursorToken, sort);
4461
- candidates = candidates.filter((agent) => this.compareAgentWithCursor(agent, cursor, sort) > 0);
4431
+ const cursor = this.decodeAgentCursor(cursorToken, sort);
4432
+ candidates = candidates.filter((agent) => this.agentsPager.compareWithCursor(agent, cursor, sort) > 0);
4462
4433
  }
4463
4434
  const limit = request.page?.limit ?? 200;
4464
4435
  const matchedEntries = await this.collectFetchAgentsEntries({
@@ -4470,7 +4441,7 @@ export class Session {
4470
4441
  const pagedEntries = matchedEntries.slice(0, limit);
4471
4442
  const hasMore = matchedEntries.length > limit;
4472
4443
  const nextCursor = hasMore && pagedEntries.length > 0
4473
- ? this.encodeFetchAgentsCursor(pagedEntries[pagedEntries.length - 1], sort)
4444
+ ? this.agentsPager.encode(pagedEntries[pagedEntries.length - 1].agent, sort)
4474
4445
  : null;
4475
4446
  return {
4476
4447
  entries: pagedEntries,
@@ -4481,21 +4452,16 @@ export class Session {
4481
4452
  },
4482
4453
  };
4483
4454
  }
4484
- deriveWorkspaceStateBucket(agent) {
4485
- const pendingPermissionCount = agent.pendingPermissions?.length ?? 0;
4486
- if (pendingPermissionCount > 0 || agent.attentionReason === "permission") {
4487
- return "needs_input";
4488
- }
4489
- if (agent.status === "error" || agent.attentionReason === "error") {
4490
- return "failed";
4491
- }
4492
- if (agent.status === "running") {
4493
- return "running";
4455
+ decodeAgentCursor(token, sort) {
4456
+ try {
4457
+ return this.agentsPager.decode(token, sort);
4494
4458
  }
4495
- if (agent.requiresAttention) {
4496
- return "attention";
4459
+ catch (error) {
4460
+ if (error instanceof CursorError) {
4461
+ throw new SessionRequestError("invalid_cursor", error.message);
4462
+ }
4463
+ throw error;
4497
4464
  }
4498
- return "done";
4499
4465
  }
4500
4466
  async describeWorkspaceRecord(workspace, projectRecord) {
4501
4467
  const resolvedProjectRecord = projectRecord ?? (await this.projectRegistry.get(workspace.projectId));
@@ -4513,6 +4479,7 @@ export class Session {
4513
4479
  projectKind: (resolvedProjectRecord?.kind ?? "directory") === "git" ? "git" : "non_git",
4514
4480
  workspaceKind: workspace.kind,
4515
4481
  name: workspace.displayName,
4482
+ archivingAt: null,
4516
4483
  status: "done",
4517
4484
  activityAt: null,
4518
4485
  diffStat,
@@ -4520,6 +4487,7 @@ export class Session {
4520
4487
  ? buildWorkspaceScriptPayloads({
4521
4488
  workspaceId: workspace.workspaceId,
4522
4489
  workspaceDirectory: workspace.cwd,
4490
+ paseoConfig: readPaseoConfigForProjection(workspace.cwd, this.sessionLogger),
4523
4491
  routeStore: this.scriptRouteStore,
4524
4492
  runtimeStore: this.scriptRuntimeStore,
4525
4493
  daemonPort: this.getDaemonTcpPort?.() ?? null,
@@ -4582,6 +4550,7 @@ export class Session {
4582
4550
  projectKind: "git",
4583
4551
  workspaceKind: result.workspace.kind,
4584
4552
  name: result.worktree.branchName || result.workspace.displayName,
4553
+ archivingAt: null,
4585
4554
  status: "done",
4586
4555
  activityAt: null,
4587
4556
  diffStat: { additions: 0, deletions: 0 },
@@ -4604,239 +4573,37 @@ export class Session {
4604
4573
  }
4605
4574
  return this.describeWorkspaceRecord(input.workspace, input.projectRecord);
4606
4575
  }
4576
+ markWorkspaceArchiving(workspaceIds, archivingAt) {
4577
+ this.workspaceDirectory.markArchiving(workspaceIds, archivingAt);
4578
+ }
4579
+ clearWorkspaceArchiving(workspaceIds) {
4580
+ this.workspaceDirectory.clearArchiving(workspaceIds);
4581
+ }
4607
4582
  async buildWorkspaceDescriptorMap(options) {
4608
- const [agents, persistedWorkspaces, persistedProjects] = await Promise.all([
4609
- this.listAgentPayloads(),
4610
- this.workspaceRegistry.list(),
4611
- this.projectRegistry.list(),
4612
- ]);
4613
- const activeProjects = new Map(persistedProjects
4614
- .filter((project) => !project.archivedAt)
4615
- .map((project) => [project.projectId, project]));
4616
- const archivedProjectIds = new Set(persistedProjects.filter((project) => project.archivedAt).map((project) => project.projectId));
4617
- const activeRecords = persistedWorkspaces.filter((workspace) => !workspace.archivedAt && !archivedProjectIds.has(workspace.projectId));
4618
- const descriptorsByWorkspaceId = new Map();
4619
- const workspaceIds = options.workspaceIds ? new Set(options.workspaceIds) : null;
4620
- const workspaceIdsByDirectory = new Map(activeRecords.map((workspace) => [normalizePersistedWorkspaceId(workspace.cwd), workspace.workspaceId]));
4621
- const includedWorkspaces = activeRecords.filter((workspace) => !workspaceIds || workspaceIds.has(workspace.workspaceId));
4622
- const workspaceDescriptors = await Promise.all(includedWorkspaces.map((workspace) => this.buildWorkspaceDescriptor({
4623
- workspace,
4624
- projectRecord: activeProjects.get(workspace.projectId) ?? null,
4625
- includeGitData: options.includeGitData,
4626
- })));
4627
- for (let i = 0; i < includedWorkspaces.length; i += 1) {
4628
- descriptorsByWorkspaceId.set(includedWorkspaces[i].workspaceId, workspaceDescriptors[i]);
4629
- }
4630
- for (const agent of agents) {
4631
- if (agent.archivedAt) {
4632
- continue;
4633
- }
4634
- if (!this.isProviderVisibleToClient(agent.provider)) {
4635
- continue;
4636
- }
4637
- const workspaceId = workspaceIdsByDirectory.get(normalizePersistedWorkspaceId(agent.cwd));
4638
- if (workspaceId === undefined) {
4639
- continue;
4640
- }
4641
- const existing = descriptorsByWorkspaceId.get(workspaceId);
4642
- if (!existing) {
4643
- continue;
4644
- }
4645
- const bucket = this.deriveWorkspaceStateBucket(agent);
4646
- if (this.workspaceStatePriority[bucket] < this.workspaceStatePriority[existing.status]) {
4647
- existing.status = bucket;
4648
- }
4649
- }
4650
- return descriptorsByWorkspaceId;
4583
+ return this.workspaceDirectory.buildDescriptorMap(options);
4651
4584
  }
4652
4585
  resolveRegisteredWorkspaceIdForCwd(cwd, workspaces) {
4653
- const normalizedCwd = normalizePersistedWorkspaceId(cwd);
4654
- const exact = workspaces.find((workspace) => workspace.cwd === normalizedCwd);
4655
- if (exact) {
4656
- return exact.workspaceId;
4657
- }
4658
- const userHome = homedir();
4659
- let bestMatch = null;
4660
- for (const workspace of workspaces) {
4661
- if (workspace.cwd === userHome)
4662
- continue;
4663
- if (workspace.archivedAt)
4664
- continue;
4665
- const prefix = workspace.cwd.endsWith(sep) ? workspace.cwd : `${workspace.cwd}${sep}`;
4666
- if (!normalizedCwd.startsWith(prefix)) {
4667
- continue;
4668
- }
4669
- if (!bestMatch || workspace.cwd.length > bestMatch.cwd.length) {
4670
- bestMatch = workspace;
4671
- }
4672
- }
4673
- return bestMatch?.workspaceId ?? normalizedCwd;
4586
+ return this.workspaceDirectory.resolveRegisteredWorkspaceIdForCwd(cwd, workspaces);
4674
4587
  }
4675
- async listWorkspaceDescriptors() {
4676
- return Array.from((await this.buildWorkspaceDescriptorMap({
4677
- includeGitData: true,
4678
- })).values());
4588
+ matchesWorkspaceFilter(input) {
4589
+ return this.workspaceDirectory.matchesFilter(input);
4679
4590
  }
4680
- normalizeFetchWorkspacesSort(sort) {
4681
- const fallback = [{ key: "activity_at", direction: "desc" }];
4682
- if (!sort || sort.length === 0) {
4683
- return fallback;
4591
+ async listFetchWorkspacesEntries(request) {
4592
+ try {
4593
+ return await this.workspaceDirectory.listFetchEntries(request);
4684
4594
  }
4685
- const deduped = [];
4686
- const seen = new Set();
4687
- for (const entry of sort) {
4688
- if (seen.has(entry.key)) {
4689
- continue;
4690
- }
4691
- seen.add(entry.key);
4692
- deduped.push(entry);
4693
- }
4694
- return deduped.length > 0 ? deduped : fallback;
4695
- }
4696
- getFetchWorkspacesSortValue(workspace, key) {
4697
- switch (key) {
4698
- case "status_priority":
4699
- return this.workspaceStatePriority[workspace.status];
4700
- case "activity_at":
4701
- return workspace.activityAt ? Date.parse(workspace.activityAt) : null;
4702
- case "name":
4703
- return workspace.name.toLocaleLowerCase();
4704
- case "project_id":
4705
- return workspace.projectId.toLocaleLowerCase();
4706
- }
4707
- }
4708
- compareFetchWorkspacesEntries(left, right, sort) {
4709
- for (const spec of sort) {
4710
- const leftValue = this.getFetchWorkspacesSortValue(left, spec.key);
4711
- const rightValue = this.getFetchWorkspacesSortValue(right, spec.key);
4712
- const base = this.compareSortValues(leftValue, rightValue);
4713
- if (base === 0) {
4714
- continue;
4595
+ catch (error) {
4596
+ if (error instanceof CursorError) {
4597
+ throw new SessionRequestError("invalid_cursor", error.message);
4715
4598
  }
4716
- return spec.direction === "asc" ? base : -base;
4599
+ throw error;
4717
4600
  }
4718
- return left.id.localeCompare(right.id);
4719
4601
  }
4720
- encodeFetchWorkspacesCursor(entry, sort) {
4721
- const values = {};
4722
- for (const spec of sort) {
4723
- values[spec.key] = this.getFetchWorkspacesSortValue(entry, spec.key);
4724
- }
4725
- return Buffer.from(JSON.stringify({
4726
- sort,
4727
- values,
4728
- id: entry.id,
4729
- }), "utf8").toString("base64url");
4730
- }
4731
- decodeFetchWorkspacesCursor(cursor, sort) {
4732
- let parsed;
4733
- try {
4734
- parsed = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
4735
- }
4736
- catch {
4737
- throw new SessionRequestError("invalid_cursor", "Invalid fetch_workspaces cursor");
4738
- }
4739
- if (!parsed || typeof parsed !== "object") {
4740
- throw new SessionRequestError("invalid_cursor", "Invalid fetch_workspaces cursor");
4741
- }
4742
- const payload = parsed;
4743
- if (!Array.isArray(payload.sort) || typeof payload.id !== "string") {
4744
- throw new SessionRequestError("invalid_cursor", "Invalid fetch_workspaces cursor");
4745
- }
4746
- if (!payload.values || typeof payload.values !== "object") {
4747
- throw new SessionRequestError("invalid_cursor", "Invalid fetch_workspaces cursor");
4748
- }
4749
- const cursorSort = parseFetchWorkspacesCursorSort(payload.sort);
4750
- if (cursorSort.length !== sort.length ||
4751
- cursorSort.some((entry, index) => entry.key !== sort[index]?.key || entry.direction !== sort[index]?.direction)) {
4752
- throw new SessionRequestError("invalid_cursor", "fetch_workspaces cursor does not match current sort");
4753
- }
4754
- return {
4755
- sort: cursorSort,
4756
- values: payload.values,
4757
- id: String(payload.id),
4758
- };
4759
- }
4760
- compareWorkspaceWithCursor(workspace, cursor, sort) {
4761
- for (const spec of sort) {
4762
- const leftValue = this.getFetchWorkspacesSortValue(workspace, spec.key);
4763
- const rightValue = cursor.values[spec.key] !== undefined ? (cursor.values[spec.key] ?? null) : null;
4764
- const base = this.compareSortValues(leftValue, rightValue);
4765
- if (base === 0) {
4766
- continue;
4767
- }
4768
- return spec.direction === "asc" ? base : -base;
4769
- }
4770
- return workspace.id.localeCompare(cursor.id);
4771
- }
4772
- matchesWorkspaceFilter(input) {
4773
- const { workspace, filter } = input;
4774
- if (!filter) {
4775
- return true;
4776
- }
4777
- if (filter.projectId && filter.projectId.trim().length > 0) {
4778
- if (workspace.projectId !== filter.projectId.trim()) {
4779
- return false;
4780
- }
4781
- }
4782
- if (filter.idPrefix && filter.idPrefix.trim().length > 0) {
4783
- if (!String(workspace.id).startsWith(filter.idPrefix.trim())) {
4784
- return false;
4785
- }
4786
- }
4787
- if (filter.query && filter.query.trim().length > 0) {
4788
- const query = filter.query.trim().toLocaleLowerCase();
4789
- const haystacks = [workspace.name, String(workspace.projectId), String(workspace.id)];
4790
- if (!haystacks.some((value) => value.toLocaleLowerCase().includes(query))) {
4791
- return false;
4792
- }
4793
- }
4794
- return true;
4795
- }
4796
- async listFetchWorkspacesEntries(request) {
4797
- const filter = request.filter;
4798
- const sort = this.normalizeFetchWorkspacesSort(request.sort);
4799
- let entries = await this.listWorkspaceDescriptors();
4800
- const listedCount = entries.length;
4801
- entries = entries.filter((workspace) => this.matchesWorkspaceFilter({ workspace, filter }));
4802
- const filteredCount = entries.length;
4803
- entries.sort((left, right) => this.compareFetchWorkspacesEntries(left, right, sort));
4804
- const cursorToken = request.page?.cursor;
4805
- if (cursorToken) {
4806
- const cursor = this.decodeFetchWorkspacesCursor(cursorToken, sort);
4807
- entries = entries.filter((workspace) => this.compareWorkspaceWithCursor(workspace, cursor, sort) > 0);
4808
- }
4809
- const limit = request.page?.limit ?? 200;
4810
- const pagedEntries = entries.slice(0, limit);
4811
- const hasMore = entries.length > limit;
4812
- const nextCursor = hasMore && pagedEntries.length > 0
4813
- ? this.encodeFetchWorkspacesCursor(pagedEntries[pagedEntries.length - 1], sort)
4814
- : null;
4815
- this.sessionLogger.debug({
4816
- requestId: request.requestId,
4817
- filter: request.filter ?? null,
4818
- sort,
4819
- page: request.page ?? null,
4820
- listedCount,
4821
- filteredCount,
4822
- returnedCount: pagedEntries.length,
4823
- hasMore,
4824
- nextCursor,
4825
- }, "fetch_workspaces_entries_listed");
4826
- return {
4827
- entries: pagedEntries,
4828
- pageInfo: {
4829
- nextCursor,
4830
- prevCursor: request.page?.cursor ?? null,
4831
- hasMore,
4832
- },
4833
- };
4834
- }
4835
- bufferOrEmitWorkspaceUpdate(subscription, payload) {
4836
- if (subscription.isBootstrapping) {
4837
- const workspaceId = payload.kind === "upsert" ? payload.workspace.id : payload.id;
4838
- subscription.pendingUpdatesByWorkspaceId.set(workspaceId, payload);
4839
- return;
4602
+ bufferOrEmitWorkspaceUpdate(subscription, payload) {
4603
+ if (subscription.isBootstrapping) {
4604
+ const workspaceId = payload.kind === "upsert" ? payload.workspace.id : payload.id;
4605
+ subscription.pendingUpdatesByWorkspaceId.set(workspaceId, payload);
4606
+ return;
4840
4607
  }
4841
4608
  const workspaceId = payload.kind === "upsert" ? payload.workspace.id : payload.id;
4842
4609
  subscription.lastEmittedByWorkspaceId.set(workspaceId, payload);
@@ -5154,14 +4921,6 @@ export class Session {
5154
4921
  const workspaceId = this.resolveRegisteredWorkspaceIdForCwd(cwd, workspaces);
5155
4922
  await this.emitWorkspaceUpdatesForWorkspaceIds([workspaceId], options);
5156
4923
  }
5157
- async emitWorkspaceUpdatesForCwds(cwds) {
5158
- const workspaces = await this.workspaceRegistry.list();
5159
- const uniqueWorkspaceIds = new Set();
5160
- for (const cwd of cwds) {
5161
- uniqueWorkspaceIds.add(this.resolveRegisteredWorkspaceIdForCwd(cwd, workspaces));
5162
- }
5163
- await this.emitWorkspaceUpdatesForWorkspaceIds(uniqueWorkspaceIds);
5164
- }
5165
4924
  async handleFetchAgents(request) {
5166
4925
  const requestedSubscriptionId = request.subscribe?.subscriptionId?.trim();
5167
4926
  const subscriptionId = resolveSubscriptionId(request.subscribe, requestedSubscriptionId);
@@ -5350,6 +5109,7 @@ export class Session {
5350
5109
  return buildWorkspaceScriptPayloads({
5351
5110
  workspaceId,
5352
5111
  workspaceDirectory,
5112
+ paseoConfig: readPaseoConfigForProjection(workspaceDirectory, this.sessionLogger),
5353
5113
  routeStore: this.scriptRouteStore,
5354
5114
  runtimeStore: this.scriptRuntimeStore,
5355
5115
  daemonPort: this.getDaemonTcpPort?.() ?? null,
@@ -5500,15 +5260,15 @@ export class Session {
5500
5260
  paseoHome: this.paseoHome,
5501
5261
  describeWorkspaceRecord: (result) => this.describeCreatedWorktreeWorkspace(result),
5502
5262
  emit: (message) => this.emit(message),
5503
- createPaseoWorktree: (input) => this.createPaseoWorktree(input),
5504
- warmWorkspaceGitData: (workspace) => this.warmWorkspaceGitDataForWorkspace(workspace),
5505
5263
  sessionLogger: this.sessionLogger,
5506
- runWorktreeSetupInBackground: (options) => this.runWorktreeSetupInBackground(options),
5264
+ createPaseoWorktreeWorkflow: (input) => this.createPaseoWorktreeWorkflow(input),
5507
5265
  }, request);
5508
5266
  }
5509
- async runWorktreeSetupInBackground(options) {
5510
- return runWorktreeSetupInBackgroundSession({
5267
+ async createPaseoWorktreeWorkflow(input, options) {
5268
+ return createWorktreeWorkflow({
5511
5269
  paseoHome: this.paseoHome,
5270
+ createPaseoWorktree: (workflowInput, serviceOptions) => this.createPaseoWorktree(workflowInput, serviceOptions),
5271
+ warmWorkspaceGitData: (workspace) => this.warmWorkspaceGitDataForWorkspace(workspace),
5512
5272
  emitWorkspaceUpdateForCwd: (cwd, emitOptions) => this.emitWorkspaceUpdateForCwd(cwd, emitOptions),
5513
5273
  cacheWorkspaceSetupSnapshot: (workspaceId, snapshot) => {
5514
5274
  this.workspaceSetupSnapshots.set(workspaceId, snapshot);
@@ -5524,7 +5284,7 @@ export class Session {
5524
5284
  onScriptsChanged: (workspaceId, workspaceDirectory) => {
5525
5285
  this.emitWorkspaceScriptStatusUpdate(workspaceId, workspaceDirectory);
5526
5286
  },
5527
- }, options);
5287
+ }, input, options);
5528
5288
  }
5529
5289
  async handleWorkspaceSetupStatusRequest(request) {
5530
5290
  return handleWorkspaceSetupStatusRequestMessage({
@@ -5597,13 +5357,12 @@ export class Session {
5597
5357
  });
5598
5358
  }
5599
5359
  loadProjectedTimelineWindow(params) {
5600
- const { agentId, direction, cursor, requestedLimit, provider } = params;
5360
+ const { agentId, direction, cursor, requestedLimit } = params;
5601
5361
  let timeline = params.timeline;
5602
5362
  const projectedLimit = Math.max(1, Math.floor(requestedLimit));
5603
5363
  let fetchLimit = projectedLimit;
5604
5364
  let projectedWindow = selectTimelineWindowByProjectedLimit({
5605
5365
  rows: timeline.rows,
5606
- provider,
5607
5366
  direction,
5608
5367
  limit: projectedLimit,
5609
5368
  collapseToolLifecycle: false,
@@ -5632,7 +5391,6 @@ export class Session {
5632
5391
  });
5633
5392
  projectedWindow = selectTimelineWindowByProjectedLimit({
5634
5393
  rows: timeline.rows,
5635
- provider,
5636
5394
  direction,
5637
5395
  limit: projectedLimit,
5638
5396
  collapseToolLifecycle: false,
@@ -5685,11 +5443,10 @@ export class Session {
5685
5443
  direction,
5686
5444
  cursor,
5687
5445
  requestedLimit,
5688
- provider: snapshot.provider,
5689
5446
  timeline,
5690
5447
  });
5691
5448
  timeline = projectedResult.timeline;
5692
- entries = projectTimelineRows(projectedResult.selectedRows, snapshot.provider, projection);
5449
+ entries = projectTimelineRows({ rows: projectedResult.selectedRows, mode: projection });
5693
5450
  if (projectedResult.minSeq !== null && projectedResult.maxSeq !== null) {
5694
5451
  startCursor = { epoch: timeline.epoch, seq: projectedResult.minSeq };
5695
5452
  endCursor = { epoch: timeline.epoch, seq: projectedResult.maxSeq };
@@ -5702,7 +5459,7 @@ export class Session {
5702
5459
  const lastRow = timeline.rows[timeline.rows.length - 1];
5703
5460
  startCursor = firstRow ? { epoch: timeline.epoch, seq: firstRow.seq } : null;
5704
5461
  endCursor = lastRow ? { epoch: timeline.epoch, seq: lastRow.seq } : null;
5705
- entries = projectTimelineRows(timeline.rows, snapshot.provider, projection);
5462
+ entries = projectTimelineRows({ rows: timeline.rows, mode: projection });
5706
5463
  }
5707
5464
  this.emit({
5708
5465
  type: "fetch_agent_timeline_response",
@@ -5721,7 +5478,17 @@ export class Session {
5721
5478
  endCursor,
5722
5479
  hasOlder,
5723
5480
  hasNewer,
5724
- entries,
5481
+ entries: entries.map((entry) => ({
5482
+ provider: snapshot.provider,
5483
+ item: entry.item,
5484
+ timestamp: entry.timestamp,
5485
+ seqStart: entry.seqStart,
5486
+ seqEnd: entry.seqEnd,
5487
+ sourceSeqRanges: entry.sourceSeqRanges,
5488
+ collapsed: this.supports(CLIENT_CAPS.reasoningMergeEnum)
5489
+ ? entry.collapsed
5490
+ : entry.collapsed.filter((value) => value !== "reasoning_merge"),
5491
+ })),
5725
5492
  error: null,
5726
5493
  },
5727
5494
  });
@@ -6474,17 +6241,7 @@ export class Session {
6474
6241
  }
6475
6242
  await this.disableVoiceModeForActiveAgent(true);
6476
6243
  this.isVoiceMode = false;
6477
- // Unsubscribe from all terminals
6478
- if (this.unsubscribeTerminalsChanged) {
6479
- this.unsubscribeTerminalsChanged();
6480
- this.unsubscribeTerminalsChanged = null;
6481
- }
6482
- this.subscribedTerminalDirectories.clear();
6483
- for (const unsubscribeExit of this.terminalExitSubscriptions.values()) {
6484
- unsubscribeExit();
6485
- }
6486
- this.terminalExitSubscriptions.clear();
6487
- this.disposeTerminalSubscriptions();
6244
+ this.terminalController.dispose();
6488
6245
  for (const unsubscribe of this.checkoutDiffSubscriptions.values()) {
6489
6246
  unsubscribe();
6490
6247
  }
@@ -6494,26 +6251,6 @@ export class Session {
6494
6251
  }
6495
6252
  this.workspaceGitSubscriptions.clear();
6496
6253
  }
6497
- // ----------------------------------------------------------------------------
6498
- // Terminal Handlers
6499
- // ----------------------------------------------------------------------------
6500
- ensureTerminalExitSubscription(terminal) {
6501
- if (this.terminalExitSubscriptions.has(terminal.id)) {
6502
- return;
6503
- }
6504
- const unsubscribeExit = terminal.onExit(() => {
6505
- this.handleTerminalExited(terminal.id);
6506
- });
6507
- this.terminalExitSubscriptions.set(terminal.id, unsubscribeExit);
6508
- }
6509
- handleTerminalExited(terminalId) {
6510
- const unsubscribeExit = this.terminalExitSubscriptions.get(terminalId);
6511
- if (unsubscribeExit) {
6512
- unsubscribeExit();
6513
- this.terminalExitSubscriptions.delete(terminalId);
6514
- }
6515
- this.detachTerminalStream(terminalId, { emitExit: true });
6516
- }
6517
6254
  emitChatRpcError(request, error) {
6518
6255
  const message = error instanceof Error ? error.message : "Chat request failed";
6519
6256
  const code = error instanceof ChatServiceError ? error.code : "chat_request_failed";
@@ -6926,458 +6663,6 @@ export class Session {
6926
6663
  this.emitLoopRpcError(request, error);
6927
6664
  }
6928
6665
  }
6929
- emitTerminalsChangedSnapshot(input) {
6930
- this.emit({
6931
- type: "terminals_changed",
6932
- payload: {
6933
- cwd: input.cwd,
6934
- terminals: input.terminals,
6935
- },
6936
- });
6937
- }
6938
- filterStandaloneTerminals(terminals) {
6939
- return terminals;
6940
- }
6941
- toTerminalInfo(terminal) {
6942
- const title = terminal.getTitle();
6943
- return {
6944
- id: terminal.id,
6945
- name: terminal.name,
6946
- ...(title ? { title } : {}),
6947
- };
6948
- }
6949
- handleTerminalsChanged(event) {
6950
- if (!this.subscribedTerminalDirectories.has(event.cwd)) {
6951
- return;
6952
- }
6953
- this.emitTerminalsChangedSnapshot({
6954
- cwd: event.cwd,
6955
- terminals: this.filterStandaloneTerminals(event.terminals).map((terminal) => Object.assign({ id: terminal.id, name: terminal.name }, terminal.title ? { title: terminal.title } : {})),
6956
- });
6957
- }
6958
- handleSubscribeTerminalsRequest(msg) {
6959
- this.subscribedTerminalDirectories.add(msg.cwd);
6960
- void this.emitInitialTerminalsChangedSnapshot(msg.cwd);
6961
- }
6962
- handleUnsubscribeTerminalsRequest(msg) {
6963
- this.subscribedTerminalDirectories.delete(msg.cwd);
6964
- }
6965
- async emitInitialTerminalsChangedSnapshot(cwd) {
6966
- if (!this.terminalManager || !this.subscribedTerminalDirectories.has(cwd)) {
6967
- return;
6968
- }
6969
- try {
6970
- const terminals = this.filterStandaloneTerminals(await this.terminalManager.getTerminals(cwd));
6971
- for (const terminal of terminals) {
6972
- this.ensureTerminalExitSubscription(terminal);
6973
- }
6974
- if (!this.subscribedTerminalDirectories.has(cwd)) {
6975
- return;
6976
- }
6977
- this.emitTerminalsChangedSnapshot({
6978
- cwd,
6979
- terminals: terminals.map((terminal) => this.toTerminalInfo(terminal)),
6980
- });
6981
- }
6982
- catch (error) {
6983
- this.sessionLogger.warn({ err: error, cwd }, "Failed to emit initial terminal snapshot");
6984
- }
6985
- }
6986
- async handleListTerminalsRequest(msg) {
6987
- if (!this.terminalManager) {
6988
- this.emit({
6989
- type: "list_terminals_response",
6990
- payload: {
6991
- ...(msg.cwd ? { cwd: msg.cwd } : {}),
6992
- terminals: [],
6993
- requestId: msg.requestId,
6994
- },
6995
- });
6996
- return;
6997
- }
6998
- try {
6999
- const terminals = this.filterStandaloneTerminals(typeof msg.cwd === "string"
7000
- ? await this.terminalManager.getTerminals(msg.cwd)
7001
- : await this.getAllTerminalSessions());
7002
- for (const terminal of terminals) {
7003
- this.ensureTerminalExitSubscription(terminal);
7004
- }
7005
- this.emit({
7006
- type: "list_terminals_response",
7007
- payload: {
7008
- ...(msg.cwd ? { cwd: msg.cwd } : {}),
7009
- terminals: terminals.map((terminal) => this.toTerminalInfo(terminal)),
7010
- requestId: msg.requestId,
7011
- },
7012
- });
7013
- }
7014
- catch (error) {
7015
- this.sessionLogger.error({ err: error, cwd: msg.cwd }, "Failed to list terminals");
7016
- this.emit({
7017
- type: "list_terminals_response",
7018
- payload: {
7019
- ...(msg.cwd ? { cwd: msg.cwd } : {}),
7020
- terminals: [],
7021
- requestId: msg.requestId,
7022
- },
7023
- });
7024
- }
7025
- }
7026
- async getAllTerminalSessions() {
7027
- if (!this.terminalManager) {
7028
- return [];
7029
- }
7030
- const directories = this.terminalManager.listDirectories();
7031
- const terminalsByDirectory = await Promise.all(directories.map((cwd) => this.terminalManager.getTerminals(cwd)));
7032
- return terminalsByDirectory.flat();
7033
- }
7034
- async handleCreateTerminalRequest(msg) {
7035
- if (!this.terminalManager) {
7036
- this.emit({
7037
- type: "create_terminal_response",
7038
- payload: {
7039
- terminal: null,
7040
- error: "Terminal manager not available",
7041
- requestId: msg.requestId,
7042
- },
7043
- });
7044
- return;
7045
- }
7046
- try {
7047
- if (msg.agentId) {
7048
- this.emit({
7049
- type: "create_terminal_response",
7050
- payload: {
7051
- terminal: null,
7052
- error: `Agent-backed terminals are no longer supported for agent ${msg.agentId}`,
7053
- requestId: msg.requestId,
7054
- },
7055
- });
7056
- return;
7057
- }
7058
- const session = await this.terminalManager.createTerminal({
7059
- cwd: msg.cwd,
7060
- name: msg.name,
7061
- command: msg.command,
7062
- args: msg.args,
7063
- });
7064
- this.ensureTerminalExitSubscription(session);
7065
- this.emit({
7066
- type: "create_terminal_response",
7067
- payload: {
7068
- terminal: {
7069
- id: session.id,
7070
- name: session.name,
7071
- cwd: session.cwd,
7072
- ...(session.getTitle() ? { title: session.getTitle() } : {}),
7073
- },
7074
- error: null,
7075
- requestId: msg.requestId,
7076
- },
7077
- });
7078
- }
7079
- catch (error) {
7080
- this.sessionLogger.error({ err: error, cwd: msg.cwd }, "Failed to create terminal");
7081
- this.emit({
7082
- type: "create_terminal_response",
7083
- payload: {
7084
- terminal: null,
7085
- error: error.message,
7086
- requestId: msg.requestId,
7087
- },
7088
- });
7089
- }
7090
- }
7091
- async handleSubscribeTerminalRequest(msg) {
7092
- if (!this.terminalManager) {
7093
- this.emit({
7094
- type: "subscribe_terminal_response",
7095
- payload: {
7096
- terminalId: msg.terminalId,
7097
- error: "Terminal manager not available",
7098
- requestId: msg.requestId,
7099
- },
7100
- });
7101
- return;
7102
- }
7103
- const session = this.terminalManager.getTerminal(msg.terminalId);
7104
- if (!session) {
7105
- this.emit({
7106
- type: "subscribe_terminal_response",
7107
- payload: {
7108
- terminalId: msg.terminalId,
7109
- error: "Terminal not found",
7110
- requestId: msg.requestId,
7111
- },
7112
- });
7113
- return;
7114
- }
7115
- this.ensureTerminalExitSubscription(session);
7116
- const slot = this.bindActiveTerminalStream(session);
7117
- if (slot === null) {
7118
- this.sessionLogger.warn({
7119
- terminalId: msg.terminalId,
7120
- activeTerminalStreamCount: this.activeTerminalStreams.size,
7121
- }, "Terminal stream slot exhaustion");
7122
- this.emit({
7123
- type: "subscribe_terminal_response",
7124
- payload: {
7125
- terminalId: msg.terminalId,
7126
- error: "No terminal stream slots available",
7127
- requestId: msg.requestId,
7128
- },
7129
- });
7130
- return;
7131
- }
7132
- this.emit({
7133
- type: "subscribe_terminal_response",
7134
- payload: {
7135
- terminalId: msg.terminalId,
7136
- slot,
7137
- error: null,
7138
- requestId: msg.requestId,
7139
- },
7140
- });
7141
- const activeStream = this.activeTerminalStreams.get(slot);
7142
- if (activeStream) {
7143
- this.trySendTerminalSnapshot(activeStream);
7144
- }
7145
- }
7146
- handleUnsubscribeTerminalRequest(msg) {
7147
- this.detachTerminalStream(msg.terminalId, { emitExit: false });
7148
- }
7149
- handleTerminalInput(msg) {
7150
- if (!this.terminalManager) {
7151
- return;
7152
- }
7153
- const session = this.terminalManager.getTerminal(msg.terminalId);
7154
- if (!session) {
7155
- this.sessionLogger.warn({ terminalId: msg.terminalId }, "Terminal not found for input");
7156
- return;
7157
- }
7158
- this.ensureTerminalExitSubscription(session);
7159
- if (msg.message.type === "resize") {
7160
- const currentSize = session.getSize();
7161
- if (currentSize.rows === msg.message.rows && currentSize.cols === msg.message.cols) {
7162
- return;
7163
- }
7164
- }
7165
- session.send(msg.message);
7166
- }
7167
- killTrackedTerminal(terminalId, options) {
7168
- this.detachTerminalStream(terminalId, { emitExit: options?.emitExit ?? true });
7169
- this.terminalManager?.killTerminal(terminalId);
7170
- }
7171
- async killTerminalsUnderPath(rootPath) {
7172
- return killWorktreeTerminalsUnderPath({
7173
- isPathWithinRoot: (pathRoot, candidatePath) => this.isPathWithinRoot(pathRoot, candidatePath),
7174
- killTrackedTerminal: (terminalId, options) => this.killTrackedTerminal(terminalId, options),
7175
- detachTerminalStream: (terminalId, options) => void this.detachTerminalStream(terminalId, options),
7176
- sessionLogger: this.sessionLogger,
7177
- terminalManager: this.terminalManager,
7178
- }, rootPath);
7179
- }
7180
- async handleKillTerminalRequest(msg) {
7181
- const result = this.killTerminalForClose(msg.terminalId);
7182
- this.emit({
7183
- type: "kill_terminal_response",
7184
- payload: {
7185
- terminalId: result.terminalId,
7186
- success: result.success,
7187
- requestId: msg.requestId,
7188
- },
7189
- });
7190
- }
7191
- killTerminalForClose(terminalId) {
7192
- if (!this.terminalManager) {
7193
- return {
7194
- terminalId,
7195
- success: false,
7196
- };
7197
- }
7198
- this.killTrackedTerminal(terminalId, { emitExit: true });
7199
- return {
7200
- terminalId,
7201
- success: true,
7202
- };
7203
- }
7204
- async handleCaptureTerminalRequest(msg) {
7205
- if (!this.terminalManager) {
7206
- this.emit({
7207
- type: "capture_terminal_response",
7208
- payload: {
7209
- terminalId: msg.terminalId,
7210
- lines: [],
7211
- totalLines: 0,
7212
- requestId: msg.requestId,
7213
- },
7214
- });
7215
- return;
7216
- }
7217
- const session = this.terminalManager.getTerminal(msg.terminalId);
7218
- if (!session) {
7219
- this.emit({
7220
- type: "capture_terminal_response",
7221
- payload: {
7222
- terminalId: msg.terminalId,
7223
- lines: [],
7224
- totalLines: 0,
7225
- requestId: msg.requestId,
7226
- },
7227
- });
7228
- return;
7229
- }
7230
- this.ensureTerminalExitSubscription(session);
7231
- try {
7232
- const capture = captureTerminalLines(session, {
7233
- start: msg.start,
7234
- end: msg.end,
7235
- stripAnsi: msg.stripAnsi,
7236
- });
7237
- this.emit({
7238
- type: "capture_terminal_response",
7239
- payload: {
7240
- terminalId: msg.terminalId,
7241
- lines: capture.lines,
7242
- totalLines: capture.totalLines,
7243
- requestId: msg.requestId,
7244
- },
7245
- });
7246
- }
7247
- catch (error) {
7248
- this.sessionLogger.error({ err: error, terminalId: msg.terminalId }, "Failed to capture terminal");
7249
- this.emit({
7250
- type: "capture_terminal_response",
7251
- payload: {
7252
- terminalId: msg.terminalId,
7253
- lines: [],
7254
- totalLines: 0,
7255
- requestId: msg.requestId,
7256
- },
7257
- });
7258
- }
7259
- }
7260
- bindActiveTerminalStream(terminal) {
7261
- if (!this.onBinaryMessage) {
7262
- return null;
7263
- }
7264
- const existingSlot = this.terminalIdToSlot.get(terminal.id);
7265
- if (typeof existingSlot === "number") {
7266
- const existingStream = this.activeTerminalStreams.get(existingSlot);
7267
- if (existingStream) {
7268
- existingStream.needsSnapshot = true;
7269
- return existingSlot;
7270
- }
7271
- this.terminalIdToSlot.delete(terminal.id);
7272
- }
7273
- const slot = this.allocateTerminalSlot();
7274
- if (slot === null) {
7275
- return null;
7276
- }
7277
- const activeStream = {
7278
- terminalId: terminal.id,
7279
- slot,
7280
- unsubscribe: () => { },
7281
- needsSnapshot: true,
7282
- outputCoalescer: new TerminalOutputCoalescer({
7283
- timers: { setTimeout, clearTimeout },
7284
- onFlush: ({ payload }) => {
7285
- if (this.activeTerminalStreams.get(slot) !== activeStream) {
7286
- return;
7287
- }
7288
- this.emitBinary(encodeTerminalStreamFrame({
7289
- opcode: TerminalStreamOpcode.Output,
7290
- slot,
7291
- payload,
7292
- }));
7293
- },
7294
- }),
7295
- };
7296
- this.activeTerminalStreams.set(slot, activeStream);
7297
- this.terminalIdToSlot.set(terminal.id, slot);
7298
- activeStream.unsubscribe = terminal.subscribe((message) => {
7299
- if (this.activeTerminalStreams.get(slot) !== activeStream) {
7300
- return;
7301
- }
7302
- if (message.type === "snapshot") {
7303
- activeStream.outputCoalescer.flush();
7304
- activeStream.needsSnapshot = true;
7305
- this.trySendTerminalSnapshot(activeStream);
7306
- return;
7307
- }
7308
- if (message.type === "titleChange") {
7309
- return;
7310
- }
7311
- if (activeStream.needsSnapshot || message.data.length === 0) {
7312
- return;
7313
- }
7314
- activeStream.outputCoalescer.handle(message.data);
7315
- });
7316
- return slot;
7317
- }
7318
- trySendTerminalSnapshot(activeStream) {
7319
- if (this.activeTerminalStreams.get(activeStream.slot) !== activeStream ||
7320
- !activeStream.needsSnapshot) {
7321
- return;
7322
- }
7323
- const terminal = this.terminalManager?.getTerminal(activeStream.terminalId);
7324
- if (!terminal) {
7325
- this.detachTerminalStream(activeStream.terminalId, { emitExit: true });
7326
- return;
7327
- }
7328
- activeStream.outputCoalescer.flush();
7329
- activeStream.needsSnapshot = false;
7330
- this.emitBinary(encodeTerminalStreamFrame({
7331
- opcode: TerminalStreamOpcode.Snapshot,
7332
- slot: activeStream.slot,
7333
- payload: encodeTerminalSnapshotPayload(terminal.getState()),
7334
- }));
7335
- }
7336
- allocateTerminalSlot() {
7337
- for (let attempt = 0; attempt < MAX_TERMINAL_STREAM_SLOTS; attempt += 1) {
7338
- const slot = (this.nextTerminalSlot + attempt) % MAX_TERMINAL_STREAM_SLOTS;
7339
- if (this.activeTerminalStreams.has(slot)) {
7340
- continue;
7341
- }
7342
- this.nextTerminalSlot = (slot + 1) % MAX_TERMINAL_STREAM_SLOTS;
7343
- return slot;
7344
- }
7345
- return null;
7346
- }
7347
- detachTerminalStream(terminalId, options) {
7348
- const slot = this.terminalIdToSlot.get(terminalId);
7349
- if (typeof slot !== "number") {
7350
- return false;
7351
- }
7352
- const activeStream = this.activeTerminalStreams.get(slot);
7353
- if (!activeStream) {
7354
- this.terminalIdToSlot.delete(terminalId);
7355
- return false;
7356
- }
7357
- activeStream.outputCoalescer.flush();
7358
- this.activeTerminalStreams.delete(slot);
7359
- this.terminalIdToSlot.delete(terminalId);
7360
- try {
7361
- activeStream.unsubscribe();
7362
- }
7363
- catch (error) {
7364
- this.sessionLogger.warn({ err: error }, "Failed to unsubscribe terminal stream");
7365
- }
7366
- if (options?.emitExit) {
7367
- this.emit({
7368
- type: "terminal_stream_exit",
7369
- payload: {
7370
- terminalId: activeStream.terminalId,
7371
- },
7372
- });
7373
- }
7374
- return true;
7375
- }
7376
- disposeTerminalSubscriptions() {
7377
- for (const terminalId of Array.from(this.terminalIdToSlot.keys())) {
7378
- this.detachTerminalStream(terminalId, { emitExit: false });
7379
- }
7380
- }
7381
6666
  }
7382
6667
  // ---------------------------------------------------------------------------
7383
6668
  // Stash handlers