@getpaseo/server 0.1.35 → 0.1.37

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 (153) hide show
  1. package/dist/scripts/daemon-runner.js +31 -1
  2. package/dist/scripts/daemon-runner.js.map +1 -1
  3. package/dist/scripts/supervisor.js +16 -6
  4. package/dist/scripts/supervisor.js.map +1 -1
  5. package/dist/server/client/daemon-client.d.ts +175 -0
  6. package/dist/server/client/daemon-client.d.ts.map +1 -1
  7. package/dist/server/client/daemon-client.js +235 -0
  8. package/dist/server/client/daemon-client.js.map +1 -1
  9. package/dist/server/server/agent/agent-manager.d.ts +13 -0
  10. package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
  11. package/dist/server/server/agent/agent-manager.js +53 -0
  12. package/dist/server/server/agent/agent-manager.js.map +1 -1
  13. package/dist/server/server/agent/agent-storage.d.ts +10 -10
  14. package/dist/server/server/agent/agent-storage.js +9 -4
  15. package/dist/server/server/agent/agent-storage.js.map +1 -1
  16. package/dist/server/server/agent/provider-launch-config.d.ts.map +1 -1
  17. package/dist/server/server/agent/provider-launch-config.js +38 -1
  18. package/dist/server/server/agent/provider-launch-config.js.map +1 -1
  19. package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
  20. package/dist/server/server/agent/providers/claude-agent.js +24 -5
  21. package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
  22. package/dist/server/server/agent-attention-policy.d.ts +1 -1
  23. package/dist/server/server/agent-attention-policy.d.ts.map +1 -1
  24. package/dist/server/server/bootstrap.d.ts.map +1 -1
  25. package/dist/server/server/bootstrap.js +57 -18
  26. package/dist/server/server/bootstrap.js.map +1 -1
  27. package/dist/server/server/chat/chat-mentions.d.ts +31 -0
  28. package/dist/server/server/chat/chat-mentions.d.ts.map +1 -0
  29. package/dist/server/server/chat/chat-mentions.js +71 -0
  30. package/dist/server/server/chat/chat-mentions.js.map +1 -0
  31. package/dist/server/server/chat/chat-rpc-schemas.d.ts +728 -0
  32. package/dist/server/server/chat/chat-rpc-schemas.d.ts.map +1 -0
  33. package/dist/server/server/chat/chat-rpc-schemas.js +103 -0
  34. package/dist/server/server/chat/chat-rpc-schemas.js.map +1 -0
  35. package/dist/server/server/chat/chat-service.d.ts +74 -0
  36. package/dist/server/server/chat/chat-service.d.ts.map +1 -0
  37. package/dist/server/server/chat/chat-service.js +330 -0
  38. package/dist/server/server/chat/chat-service.js.map +1 -0
  39. package/dist/server/server/chat/chat-types.d.ts +75 -0
  40. package/dist/server/server/chat/chat-types.d.ts.map +1 -0
  41. package/dist/server/server/chat/chat-types.js +22 -0
  42. package/dist/server/server/chat/chat-types.js.map +1 -0
  43. package/dist/server/server/checkout-diff-manager.d.ts +41 -0
  44. package/dist/server/server/checkout-diff-manager.d.ts.map +1 -0
  45. package/dist/server/server/checkout-diff-manager.js +272 -0
  46. package/dist/server/server/checkout-diff-manager.js.map +1 -0
  47. package/dist/server/server/checkout-git-utils.d.ts +9 -0
  48. package/dist/server/server/checkout-git-utils.d.ts.map +1 -0
  49. package/dist/server/server/checkout-git-utils.js +37 -0
  50. package/dist/server/server/checkout-git-utils.js.map +1 -0
  51. package/dist/server/server/loop/rpc-schemas.d.ts +2937 -0
  52. package/dist/server/server/loop/rpc-schemas.d.ts.map +1 -0
  53. package/dist/server/server/loop/rpc-schemas.js +159 -0
  54. package/dist/server/server/loop/rpc-schemas.js.map +1 -0
  55. package/dist/server/server/loop-service.d.ts +520 -0
  56. package/dist/server/server/loop-service.d.ts.map +1 -0
  57. package/dist/server/server/loop-service.js +741 -0
  58. package/dist/server/server/loop-service.js.map +1 -0
  59. package/dist/server/server/persisted-config.d.ts +10 -10
  60. package/dist/server/server/schedule/cron.d.ts +4 -0
  61. package/dist/server/server/schedule/cron.d.ts.map +1 -0
  62. package/dist/server/server/schedule/cron.js +103 -0
  63. package/dist/server/server/schedule/cron.js.map +1 -0
  64. package/dist/server/server/schedule/rpc-schemas.d.ts +2773 -0
  65. package/dist/server/server/schedule/rpc-schemas.d.ts.map +1 -0
  66. package/dist/server/server/schedule/rpc-schemas.js +112 -0
  67. package/dist/server/server/schedule/rpc-schemas.js.map +1 -0
  68. package/dist/server/server/schedule/service.d.ts +39 -0
  69. package/dist/server/server/schedule/service.d.ts.map +1 -0
  70. package/dist/server/server/schedule/service.js +397 -0
  71. package/dist/server/server/schedule/service.js.map +1 -0
  72. package/dist/server/server/schedule/store.d.ts +13 -0
  73. package/dist/server/server/schedule/store.d.ts.map +1 -0
  74. package/dist/server/server/schedule/store.js +56 -0
  75. package/dist/server/server/schedule/store.js.map +1 -0
  76. package/dist/server/server/schedule/types.d.ts +710 -0
  77. package/dist/server/server/schedule/types.d.ts.map +1 -0
  78. package/dist/server/server/schedule/types.js +73 -0
  79. package/dist/server/server/schedule/types.js.map +1 -0
  80. package/dist/server/server/session.d.ts +40 -19
  81. package/dist/server/server/session.d.ts.map +1 -1
  82. package/dist/server/server/session.js +778 -567
  83. package/dist/server/server/session.js.map +1 -1
  84. package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.d.ts.map +1 -1
  85. package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.js +19 -3
  86. package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.js.map +1 -1
  87. package/dist/server/server/websocket-server.d.ts +12 -1
  88. package/dist/server/server/websocket-server.d.ts.map +1 -1
  89. package/dist/server/server/websocket-server.js +71 -14
  90. package/dist/server/server/websocket-server.js.map +1 -1
  91. package/dist/server/shared/messages.d.ts +37933 -16895
  92. package/dist/server/shared/messages.d.ts.map +1 -1
  93. package/dist/server/shared/messages.js +41 -0
  94. package/dist/server/shared/messages.js.map +1 -1
  95. package/dist/server/terminal/terminal-manager.js +2 -2
  96. package/dist/server/terminal/terminal-manager.js.map +1 -1
  97. package/dist/server/utils/checkout-git.d.ts +12 -0
  98. package/dist/server/utils/checkout-git.d.ts.map +1 -1
  99. package/dist/server/utils/checkout-git.js +73 -3
  100. package/dist/server/utils/checkout-git.js.map +1 -1
  101. package/dist/server/utils/directory-suggestions.js +20 -4
  102. package/dist/server/utils/directory-suggestions.js.map +1 -1
  103. package/dist/src/server/agent/agent-manager.js +53 -0
  104. package/dist/src/server/agent/agent-manager.js.map +1 -1
  105. package/dist/src/server/agent/agent-storage.js +9 -4
  106. package/dist/src/server/agent/agent-storage.js.map +1 -1
  107. package/dist/src/server/agent/provider-launch-config.js +38 -1
  108. package/dist/src/server/agent/provider-launch-config.js.map +1 -1
  109. package/dist/src/server/agent/providers/claude-agent.js +24 -5
  110. package/dist/src/server/agent/providers/claude-agent.js.map +1 -1
  111. package/dist/src/server/bootstrap.js +57 -18
  112. package/dist/src/server/bootstrap.js.map +1 -1
  113. package/dist/src/server/chat/chat-mentions.js +71 -0
  114. package/dist/src/server/chat/chat-mentions.js.map +1 -0
  115. package/dist/src/server/chat/chat-rpc-schemas.js +103 -0
  116. package/dist/src/server/chat/chat-rpc-schemas.js.map +1 -0
  117. package/dist/src/server/chat/chat-service.js +330 -0
  118. package/dist/src/server/chat/chat-service.js.map +1 -0
  119. package/dist/src/server/chat/chat-types.js +22 -0
  120. package/dist/src/server/chat/chat-types.js.map +1 -0
  121. package/dist/src/server/checkout-diff-manager.js +272 -0
  122. package/dist/src/server/checkout-diff-manager.js.map +1 -0
  123. package/dist/src/server/checkout-git-utils.js +37 -0
  124. package/dist/src/server/checkout-git-utils.js.map +1 -0
  125. package/dist/src/server/loop/rpc-schemas.js +159 -0
  126. package/dist/src/server/loop/rpc-schemas.js.map +1 -0
  127. package/dist/src/server/loop-service.js +741 -0
  128. package/dist/src/server/loop-service.js.map +1 -0
  129. package/dist/src/server/schedule/cron.js +103 -0
  130. package/dist/src/server/schedule/cron.js.map +1 -0
  131. package/dist/src/server/schedule/rpc-schemas.js +112 -0
  132. package/dist/src/server/schedule/rpc-schemas.js.map +1 -0
  133. package/dist/src/server/schedule/service.js +397 -0
  134. package/dist/src/server/schedule/service.js.map +1 -0
  135. package/dist/src/server/schedule/store.js +56 -0
  136. package/dist/src/server/schedule/store.js.map +1 -0
  137. package/dist/src/server/schedule/types.js +73 -0
  138. package/dist/src/server/schedule/types.js.map +1 -0
  139. package/dist/src/server/session.js +778 -567
  140. package/dist/src/server/session.js.map +1 -1
  141. package/dist/src/server/speech/providers/local/sherpa/sherpa-runtime-env.js +19 -3
  142. package/dist/src/server/speech/providers/local/sherpa/sherpa-runtime-env.js.map +1 -1
  143. package/dist/src/server/websocket-server.js +71 -14
  144. package/dist/src/server/websocket-server.js.map +1 -1
  145. package/dist/src/shared/messages.js +41 -0
  146. package/dist/src/shared/messages.js.map +1 -1
  147. package/dist/src/terminal/terminal-manager.js +2 -2
  148. package/dist/src/terminal/terminal-manager.js.map +1 -1
  149. package/dist/src/utils/checkout-git.js +73 -3
  150. package/dist/src/utils/checkout-git.js.map +1 -1
  151. package/dist/src/utils/directory-suggestions.js +20 -4
  152. package/dist/src/utils/directory-suggestions.js.map +1 -1
  153. package/package.json +4 -3
@@ -31,23 +31,20 @@ import { isVoicePermissionAllowed } from "./voice-permission-policy.js";
31
31
  import { listDirectoryEntries, readExplorerFile, getDownloadableFileInfo, } from "./file-explorer/service.js";
32
32
  import { computeWorktreePath, getWorktreeSetupCommands, resolveWorktreeRuntimeEnv, slugify, validateBranchSlug, listPaseoWorktrees, deletePaseoWorktree, isPaseoOwnedWorktreeCwd, resolvePaseoWorktreeRootForCwd, } from "../utils/worktree.js";
33
33
  import { createAgentWorktree, runAsyncWorktreeBootstrap } from "./worktree-bootstrap.js";
34
- import { getCheckoutDiff, getCheckoutShortstat, getCheckoutStatus, getCheckoutStatusLite, listBranchSuggestions, NotGitRepoError, MergeConflictError, MergeFromBaseConflictError, commitChanges, mergeToBase, mergeFromBase, pushCurrentBranch, createPullRequest, getPullRequestStatus, resolveRepositoryDefaultBranch, } from "../utils/checkout-git.js";
34
+ import { getCheckoutDiff, getCheckoutShortstat, getCheckoutStatus, getCheckoutStatusLite, listBranchSuggestions, commitChanges, mergeToBase, mergeFromBase, pushCurrentBranch, createPullRequest, getPullRequestStatus, resolveRepositoryDefaultBranch, } from "../utils/checkout-git.js";
35
35
  import { getProjectIcon } from "../utils/project-icon.js";
36
36
  import { expandTilde } from "../utils/path.js";
37
37
  import { searchHomeDirectories, searchWorkspaceEntries } from "../utils/directory-suggestions.js";
38
+ import { READ_ONLY_GIT_ENV, resolveCheckoutGitDir, toCheckoutError, } from "./checkout-git-utils.js";
38
39
  import { ensureLocalSpeechModels, getLocalSpeechModelDir, listLocalSpeechModels, } from "./speech/providers/local/models.js";
39
40
  import { toResolver } from "./speech/provider-resolver.js";
40
41
  import { resolveClientMessageId } from "./client-message-id.js";
42
+ import { ChatServiceError, } from "./chat/chat-service.js";
43
+ import { notifyChatMentions } from "./chat/chat-mentions.js";
41
44
  const execAsync = promisify(exec);
42
45
  const MAX_INITIAL_AGENT_TITLE_CHARS = Math.min(60, MAX_EXPLICIT_AGENT_TITLE_CHARS);
43
- const READ_ONLY_GIT_ENV = {
44
- ...process.env,
45
- GIT_OPTIONAL_LOCKS: "0",
46
- };
47
46
  const pendingAgentInitializations = new Map();
48
47
  const DEFAULT_AGENT_PROVIDER = AGENT_PROVIDER_IDS[0];
49
- const CHECKOUT_DIFF_WATCH_DEBOUNCE_MS = 150;
50
- const CHECKOUT_DIFF_FALLBACK_REFRESH_MS = 5000;
51
48
  const WORKSPACE_GIT_WATCH_DEBOUNCE_MS = 500;
52
49
  const WORKSPACE_GIT_WATCH_REMOVED_FINGERPRINT = "__removed__";
53
50
  const TERMINAL_STREAM_HIGH_WATER_BYTES = 256 * 1024;
@@ -190,8 +187,9 @@ export class Session {
190
187
  this.activeTerminalStreams = new Map();
191
188
  this.terminalIdToSlot = new Map();
192
189
  this.nextTerminalSlot = 0;
190
+ this.inflightRequests = 0;
191
+ this.peakInflightRequests = 0;
193
192
  this.checkoutDiffSubscriptions = new Map();
194
- this.checkoutDiffTargets = new Map();
195
193
  this.workspaceGitWatchTargets = new Map();
196
194
  this.voiceModeAgentId = null;
197
195
  this.voiceModeBaseConfig = null;
@@ -202,7 +200,7 @@ export class Session {
202
200
  attention: 3,
203
201
  done: 4,
204
202
  };
205
- const { clientId, onMessage, onBinaryMessage, getBinaryBufferedAmount, onLifecycleIntent, logger, downloadTokenStore, pushTokenStore, paseoHome, agentManager, agentStorage, projectRegistry, workspaceRegistry, createAgentMcpTransport, stt, tts, terminalManager, voice, voiceBridge, dictation, agentProviderRuntimeSettings, } = options;
203
+ const { clientId, onMessage, onBinaryMessage, getBinaryBufferedAmount, onLifecycleIntent, logger, downloadTokenStore, pushTokenStore, paseoHome, agentManager, agentStorage, projectRegistry, workspaceRegistry, chatService, scheduleService, loopService, checkoutDiffManager, createAgentMcpTransport, stt, tts, terminalManager, voice, voiceBridge, dictation, agentProviderRuntimeSettings, } = options;
206
204
  this.clientId = clientId;
207
205
  this.sessionId = uuidv4();
208
206
  this.onMessage = onMessage;
@@ -216,6 +214,10 @@ export class Session {
216
214
  this.agentStorage = agentStorage;
217
215
  this.projectRegistry = projectRegistry;
218
216
  this.workspaceRegistry = workspaceRegistry;
217
+ this.chatService = chatService;
218
+ this.scheduleService = scheduleService;
219
+ this.loopService = loopService;
220
+ this.checkoutDiffManager = checkoutDiffManager;
219
221
  this.createAgentMcpTransport = createAgentMcpTransport;
220
222
  this.terminalManager = terminalManager;
221
223
  if (this.terminalManager) {
@@ -271,21 +273,11 @@ export class Session {
271
273
  return this.clientActivity;
272
274
  }
273
275
  getRuntimeMetrics() {
274
- let checkoutDiffWatcherCount = 0;
275
- let checkoutDiffFallbackRefreshTargetCount = 0;
276
- for (const target of this.checkoutDiffTargets.values()) {
277
- checkoutDiffWatcherCount += target.watchers.length;
278
- if (target.fallbackRefreshInterval) {
279
- checkoutDiffFallbackRefreshTargetCount += 1;
280
- }
281
- }
282
276
  return {
283
- checkoutDiffTargetCount: this.checkoutDiffTargets.size,
284
- checkoutDiffSubscriptionCount: this.checkoutDiffSubscriptions.size,
285
- checkoutDiffWatcherCount,
286
- checkoutDiffFallbackRefreshTargetCount,
287
277
  terminalDirectorySubscriptionCount: this.subscribedTerminalDirectories.size,
288
278
  terminalSubscriptionCount: this.activeTerminalStreams.size,
279
+ inflightRequests: this.inflightRequests,
280
+ peakInflightRequests: this.peakInflightRequests,
289
281
  };
290
282
  }
291
283
  /**
@@ -847,264 +839,333 @@ export class Session {
847
839
  * Main entry point for processing session messages
848
840
  */
849
841
  async handleMessage(msg) {
850
- this.sessionLogger.trace({ inbound: msg }, "inbound message");
842
+ this.inflightRequests++;
843
+ if (this.inflightRequests > this.peakInflightRequests) {
844
+ this.peakInflightRequests = this.inflightRequests;
845
+ }
851
846
  try {
852
- switch (msg.type) {
853
- case "voice_audio_chunk":
854
- await this.handleAudioChunk(msg);
855
- break;
856
- case "abort_request":
857
- await this.handleAbort();
858
- break;
859
- case "audio_played":
860
- this.handleAudioPlayed(msg.id);
861
- break;
862
- case "fetch_agents_request":
863
- await this.handleFetchAgents(msg);
864
- break;
865
- case "fetch_workspaces_request":
866
- await this.handleFetchWorkspacesRequest(msg);
867
- break;
868
- case "fetch_agent_request":
869
- await this.handleFetchAgent(msg.agentId, msg.requestId);
870
- break;
871
- case "delete_agent_request":
872
- await this.handleDeleteAgentRequest(msg.agentId, msg.requestId);
873
- break;
874
- case "archive_agent_request":
875
- await this.handleArchiveAgentRequest(msg.agentId, msg.requestId);
876
- break;
877
- case "update_agent_request":
878
- await this.handleUpdateAgentRequest(msg.agentId, msg.name, msg.labels, msg.requestId);
879
- break;
880
- case "set_voice_mode":
881
- await this.handleSetVoiceMode(msg.enabled, msg.agentId, msg.requestId);
882
- break;
883
- case "send_agent_message_request":
884
- await this.handleSendAgentMessageRequest(msg);
885
- break;
886
- case "wait_for_finish_request":
887
- await this.handleWaitForFinish(msg.agentId, msg.requestId, msg.timeoutMs);
888
- break;
889
- case "dictation_stream_start":
890
- {
891
- const unavailable = this.resolveVoiceFeatureUnavailableContext("dictation");
892
- if (unavailable) {
893
- this.emit({
894
- type: "dictation_stream_error",
895
- payload: {
896
- dictationId: msg.dictationId,
897
- error: unavailable.message,
898
- retryable: unavailable.retryable,
899
- reasonCode: unavailable.reasonCode,
900
- missingModelIds: unavailable.missingModelIds,
901
- },
902
- });
903
- break;
847
+ this.sessionLogger.trace({ inbound: msg }, "inbound message");
848
+ try {
849
+ switch (msg.type) {
850
+ case "voice_audio_chunk":
851
+ await this.handleAudioChunk(msg);
852
+ break;
853
+ case "abort_request":
854
+ await this.handleAbort();
855
+ break;
856
+ case "audio_played":
857
+ this.handleAudioPlayed(msg.id);
858
+ break;
859
+ case "fetch_agents_request":
860
+ await this.handleFetchAgents(msg);
861
+ break;
862
+ case "fetch_workspaces_request":
863
+ await this.handleFetchWorkspacesRequest(msg);
864
+ break;
865
+ case "fetch_agent_request":
866
+ await this.handleFetchAgent(msg.agentId, msg.requestId);
867
+ break;
868
+ case "delete_agent_request":
869
+ await this.handleDeleteAgentRequest(msg.agentId, msg.requestId);
870
+ break;
871
+ case "archive_agent_request":
872
+ await this.handleArchiveAgentRequest(msg.agentId, msg.requestId);
873
+ break;
874
+ case "update_agent_request":
875
+ await this.handleUpdateAgentRequest(msg.agentId, msg.name, msg.labels, msg.requestId);
876
+ break;
877
+ case "set_voice_mode":
878
+ await this.handleSetVoiceMode(msg.enabled, msg.agentId, msg.requestId);
879
+ break;
880
+ case "send_agent_message_request":
881
+ await this.handleSendAgentMessageRequest(msg);
882
+ break;
883
+ case "wait_for_finish_request":
884
+ await this.handleWaitForFinish(msg.agentId, msg.requestId, msg.timeoutMs);
885
+ break;
886
+ case "dictation_stream_start":
887
+ {
888
+ const unavailable = this.resolveVoiceFeatureUnavailableContext("dictation");
889
+ if (unavailable) {
890
+ this.emit({
891
+ type: "dictation_stream_error",
892
+ payload: {
893
+ dictationId: msg.dictationId,
894
+ error: unavailable.message,
895
+ retryable: unavailable.retryable,
896
+ reasonCode: unavailable.reasonCode,
897
+ missingModelIds: unavailable.missingModelIds,
898
+ },
899
+ });
900
+ break;
901
+ }
904
902
  }
903
+ await this.dictationStreamManager.handleStart(msg.dictationId, msg.format);
904
+ break;
905
+ case "dictation_stream_chunk":
906
+ await this.dictationStreamManager.handleChunk({
907
+ dictationId: msg.dictationId,
908
+ seq: msg.seq,
909
+ audioBase64: msg.audio,
910
+ format: msg.format,
911
+ });
912
+ break;
913
+ case "dictation_stream_finish":
914
+ await this.dictationStreamManager.handleFinish(msg.dictationId, msg.finalSeq);
915
+ break;
916
+ case "dictation_stream_cancel":
917
+ this.dictationStreamManager.handleCancel(msg.dictationId);
918
+ break;
919
+ case "create_agent_request":
920
+ await this.handleCreateAgentRequest(msg);
921
+ break;
922
+ case "resume_agent_request":
923
+ await this.handleResumeAgentRequest(msg);
924
+ break;
925
+ case "refresh_agent_request":
926
+ await this.handleRefreshAgentRequest(msg);
927
+ break;
928
+ case "cancel_agent_request":
929
+ await this.handleCancelAgentRequest(msg.agentId);
930
+ break;
931
+ case "restart_server_request":
932
+ await this.handleRestartServerRequest(msg.requestId, msg.reason);
933
+ break;
934
+ case "shutdown_server_request":
935
+ await this.handleShutdownServerRequest(msg.requestId);
936
+ break;
937
+ case "fetch_agent_timeline_request":
938
+ await this.handleFetchAgentTimelineRequest(msg);
939
+ break;
940
+ case "set_agent_mode_request":
941
+ await this.handleSetAgentModeRequest(msg.agentId, msg.modeId, msg.requestId);
942
+ break;
943
+ case "set_agent_model_request":
944
+ await this.handleSetAgentModelRequest(msg.agentId, msg.modelId, msg.requestId);
945
+ break;
946
+ case "set_agent_thinking_request":
947
+ await this.handleSetAgentThinkingRequest(msg.agentId, msg.thinkingOptionId, msg.requestId);
948
+ break;
949
+ case "agent_permission_response":
950
+ await this.handleAgentPermissionResponse(msg.agentId, msg.requestId, msg.response);
951
+ break;
952
+ case "checkout_status_request":
953
+ await this.handleCheckoutStatusRequest(msg);
954
+ break;
955
+ case "validate_branch_request":
956
+ await this.handleValidateBranchRequest(msg);
957
+ break;
958
+ case "branch_suggestions_request":
959
+ await this.handleBranchSuggestionsRequest(msg);
960
+ break;
961
+ case "directory_suggestions_request":
962
+ await this.handleDirectorySuggestionsRequest(msg);
963
+ break;
964
+ case "subscribe_checkout_diff_request":
965
+ await this.handleSubscribeCheckoutDiffRequest(msg);
966
+ break;
967
+ case "unsubscribe_checkout_diff_request":
968
+ this.handleUnsubscribeCheckoutDiffRequest(msg);
969
+ break;
970
+ case "checkout_commit_request":
971
+ await this.handleCheckoutCommitRequest(msg);
972
+ break;
973
+ case "checkout_merge_request":
974
+ await this.handleCheckoutMergeRequest(msg);
975
+ break;
976
+ case "checkout_merge_from_base_request":
977
+ await this.handleCheckoutMergeFromBaseRequest(msg);
978
+ break;
979
+ case "checkout_push_request":
980
+ await this.handleCheckoutPushRequest(msg);
981
+ break;
982
+ case "checkout_pr_create_request":
983
+ await this.handleCheckoutPrCreateRequest(msg);
984
+ break;
985
+ case "checkout_pr_status_request":
986
+ await this.handleCheckoutPrStatusRequest(msg);
987
+ break;
988
+ case "paseo_worktree_list_request":
989
+ await this.handlePaseoWorktreeListRequest(msg);
990
+ break;
991
+ case "paseo_worktree_archive_request":
992
+ await this.handlePaseoWorktreeArchiveRequest(msg);
993
+ break;
994
+ case "create_paseo_worktree_request":
995
+ await this.handleCreatePaseoWorktreeRequest(msg);
996
+ break;
997
+ case "open_project_request":
998
+ await this.handleOpenProjectRequest(msg);
999
+ break;
1000
+ case "archive_workspace_request":
1001
+ await this.handleArchiveWorkspaceRequest(msg);
1002
+ break;
1003
+ case "file_explorer_request":
1004
+ await this.handleFileExplorerRequest(msg);
1005
+ break;
1006
+ case "project_icon_request":
1007
+ await this.handleProjectIconRequest(msg);
1008
+ break;
1009
+ case "file_download_token_request":
1010
+ await this.handleFileDownloadTokenRequest(msg);
1011
+ break;
1012
+ case "list_provider_models_request":
1013
+ await this.handleListProviderModelsRequest(msg);
1014
+ break;
1015
+ case "list_available_providers_request":
1016
+ await this.handleListAvailableProvidersRequest(msg);
1017
+ break;
1018
+ case "speech_models_list_request":
1019
+ await this.handleSpeechModelsListRequest(msg);
1020
+ break;
1021
+ case "speech_models_download_request":
1022
+ await this.handleSpeechModelsDownloadRequest(msg);
1023
+ break;
1024
+ case "clear_agent_attention":
1025
+ await this.handleClearAgentAttention(msg.agentId);
1026
+ break;
1027
+ case "client_heartbeat":
1028
+ this.handleClientHeartbeat(msg);
1029
+ break;
1030
+ case "ping": {
1031
+ const now = Date.now();
1032
+ this.emit({
1033
+ type: "pong",
1034
+ payload: {
1035
+ requestId: msg.requestId,
1036
+ clientSentAt: msg.clientSentAt,
1037
+ serverReceivedAt: now,
1038
+ serverSentAt: now,
1039
+ },
1040
+ });
1041
+ break;
905
1042
  }
906
- await this.dictationStreamManager.handleStart(msg.dictationId, msg.format);
907
- break;
908
- case "dictation_stream_chunk":
909
- await this.dictationStreamManager.handleChunk({
910
- dictationId: msg.dictationId,
911
- seq: msg.seq,
912
- audioBase64: msg.audio,
913
- format: msg.format,
914
- });
915
- break;
916
- case "dictation_stream_finish":
917
- await this.dictationStreamManager.handleFinish(msg.dictationId, msg.finalSeq);
918
- break;
919
- case "dictation_stream_cancel":
920
- this.dictationStreamManager.handleCancel(msg.dictationId);
921
- break;
922
- case "create_agent_request":
923
- await this.handleCreateAgentRequest(msg);
924
- break;
925
- case "resume_agent_request":
926
- await this.handleResumeAgentRequest(msg);
927
- break;
928
- case "refresh_agent_request":
929
- await this.handleRefreshAgentRequest(msg);
930
- break;
931
- case "cancel_agent_request":
932
- await this.handleCancelAgentRequest(msg.agentId);
933
- break;
934
- case "restart_server_request":
935
- await this.handleRestartServerRequest(msg.requestId, msg.reason);
936
- break;
937
- case "shutdown_server_request":
938
- await this.handleShutdownServerRequest(msg.requestId);
939
- break;
940
- case "fetch_agent_timeline_request":
941
- await this.handleFetchAgentTimelineRequest(msg);
942
- break;
943
- case "set_agent_mode_request":
944
- await this.handleSetAgentModeRequest(msg.agentId, msg.modeId, msg.requestId);
945
- break;
946
- case "set_agent_model_request":
947
- await this.handleSetAgentModelRequest(msg.agentId, msg.modelId, msg.requestId);
948
- break;
949
- case "set_agent_thinking_request":
950
- await this.handleSetAgentThinkingRequest(msg.agentId, msg.thinkingOptionId, msg.requestId);
951
- break;
952
- case "agent_permission_response":
953
- await this.handleAgentPermissionResponse(msg.agentId, msg.requestId, msg.response);
954
- break;
955
- case "checkout_status_request":
956
- await this.handleCheckoutStatusRequest(msg);
957
- break;
958
- case "validate_branch_request":
959
- await this.handleValidateBranchRequest(msg);
960
- break;
961
- case "branch_suggestions_request":
962
- await this.handleBranchSuggestionsRequest(msg);
963
- break;
964
- case "directory_suggestions_request":
965
- await this.handleDirectorySuggestionsRequest(msg);
966
- break;
967
- case "subscribe_checkout_diff_request":
968
- await this.handleSubscribeCheckoutDiffRequest(msg);
969
- break;
970
- case "unsubscribe_checkout_diff_request":
971
- this.handleUnsubscribeCheckoutDiffRequest(msg);
972
- break;
973
- case "checkout_commit_request":
974
- await this.handleCheckoutCommitRequest(msg);
975
- break;
976
- case "checkout_merge_request":
977
- await this.handleCheckoutMergeRequest(msg);
978
- break;
979
- case "checkout_merge_from_base_request":
980
- await this.handleCheckoutMergeFromBaseRequest(msg);
981
- break;
982
- case "checkout_push_request":
983
- await this.handleCheckoutPushRequest(msg);
984
- break;
985
- case "checkout_pr_create_request":
986
- await this.handleCheckoutPrCreateRequest(msg);
987
- break;
988
- case "checkout_pr_status_request":
989
- await this.handleCheckoutPrStatusRequest(msg);
990
- break;
991
- case "paseo_worktree_list_request":
992
- await this.handlePaseoWorktreeListRequest(msg);
993
- break;
994
- case "paseo_worktree_archive_request":
995
- await this.handlePaseoWorktreeArchiveRequest(msg);
996
- break;
997
- case "create_paseo_worktree_request":
998
- await this.handleCreatePaseoWorktreeRequest(msg);
999
- break;
1000
- case "open_project_request":
1001
- await this.handleOpenProjectRequest(msg);
1002
- break;
1003
- case "archive_workspace_request":
1004
- await this.handleArchiveWorkspaceRequest(msg);
1005
- break;
1006
- case "file_explorer_request":
1007
- await this.handleFileExplorerRequest(msg);
1008
- break;
1009
- case "project_icon_request":
1010
- await this.handleProjectIconRequest(msg);
1011
- break;
1012
- case "file_download_token_request":
1013
- await this.handleFileDownloadTokenRequest(msg);
1014
- break;
1015
- case "list_provider_models_request":
1016
- await this.handleListProviderModelsRequest(msg);
1017
- break;
1018
- case "list_available_providers_request":
1019
- await this.handleListAvailableProvidersRequest(msg);
1020
- break;
1021
- case "speech_models_list_request":
1022
- await this.handleSpeechModelsListRequest(msg);
1023
- break;
1024
- case "speech_models_download_request":
1025
- await this.handleSpeechModelsDownloadRequest(msg);
1026
- break;
1027
- case "clear_agent_attention":
1028
- await this.handleClearAgentAttention(msg.agentId);
1029
- break;
1030
- case "client_heartbeat":
1031
- this.handleClientHeartbeat(msg);
1032
- break;
1033
- case "ping": {
1034
- const now = Date.now();
1035
- this.emit({
1036
- type: "pong",
1037
- payload: {
1038
- requestId: msg.requestId,
1039
- clientSentAt: msg.clientSentAt,
1040
- serverReceivedAt: now,
1041
- serverSentAt: now,
1042
- },
1043
- });
1044
- break;
1043
+ case "list_commands_request":
1044
+ await this.handleListCommandsRequest(msg);
1045
+ break;
1046
+ case "register_push_token":
1047
+ this.handleRegisterPushToken(msg.token);
1048
+ break;
1049
+ case "subscribe_terminals_request":
1050
+ this.handleSubscribeTerminalsRequest(msg);
1051
+ break;
1052
+ case "unsubscribe_terminals_request":
1053
+ this.handleUnsubscribeTerminalsRequest(msg);
1054
+ break;
1055
+ case "list_terminals_request":
1056
+ await this.handleListTerminalsRequest(msg);
1057
+ break;
1058
+ case "create_terminal_request":
1059
+ await this.handleCreateTerminalRequest(msg);
1060
+ break;
1061
+ case "subscribe_terminal_request":
1062
+ await this.handleSubscribeTerminalRequest(msg);
1063
+ break;
1064
+ case "unsubscribe_terminal_request":
1065
+ this.handleUnsubscribeTerminalRequest(msg);
1066
+ break;
1067
+ case "terminal_input":
1068
+ this.handleTerminalInput(msg);
1069
+ break;
1070
+ case "kill_terminal_request":
1071
+ await this.handleKillTerminalRequest(msg);
1072
+ break;
1073
+ case "chat/create":
1074
+ await this.handleChatCreateRequest(msg);
1075
+ break;
1076
+ case "chat/list":
1077
+ await this.handleChatListRequest(msg);
1078
+ break;
1079
+ case "chat/inspect":
1080
+ await this.handleChatInspectRequest(msg);
1081
+ break;
1082
+ case "chat/delete":
1083
+ await this.handleChatDeleteRequest(msg);
1084
+ break;
1085
+ case "chat/post":
1086
+ await this.handleChatPostRequest(msg);
1087
+ break;
1088
+ case "chat/read":
1089
+ await this.handleChatReadRequest(msg);
1090
+ break;
1091
+ case "chat/wait":
1092
+ await this.handleChatWaitRequest(msg);
1093
+ break;
1094
+ case "schedule/create":
1095
+ await this.handleScheduleCreateRequest(msg);
1096
+ break;
1097
+ case "schedule/list":
1098
+ await this.handleScheduleListRequest(msg);
1099
+ break;
1100
+ case "schedule/inspect":
1101
+ await this.handleScheduleInspectRequest(msg);
1102
+ break;
1103
+ case "schedule/logs":
1104
+ await this.handleScheduleLogsRequest(msg);
1105
+ break;
1106
+ case "schedule/pause":
1107
+ await this.handleSchedulePauseRequest(msg);
1108
+ break;
1109
+ case "schedule/resume":
1110
+ await this.handleScheduleResumeRequest(msg);
1111
+ break;
1112
+ case "schedule/delete":
1113
+ await this.handleScheduleDeleteRequest(msg);
1114
+ break;
1115
+ case "loop/run":
1116
+ await this.handleLoopRunRequest(msg);
1117
+ break;
1118
+ case "loop/list":
1119
+ await this.handleLoopListRequest(msg);
1120
+ break;
1121
+ case "loop/inspect":
1122
+ await this.handleLoopInspectRequest(msg);
1123
+ break;
1124
+ case "loop/logs":
1125
+ await this.handleLoopLogsRequest(msg);
1126
+ break;
1127
+ case "loop/stop":
1128
+ await this.handleLoopStopRequest(msg);
1129
+ break;
1045
1130
  }
1046
- case "list_commands_request":
1047
- await this.handleListCommandsRequest(msg);
1048
- break;
1049
- case "register_push_token":
1050
- this.handleRegisterPushToken(msg.token);
1051
- break;
1052
- case "subscribe_terminals_request":
1053
- this.handleSubscribeTerminalsRequest(msg);
1054
- break;
1055
- case "unsubscribe_terminals_request":
1056
- this.handleUnsubscribeTerminalsRequest(msg);
1057
- break;
1058
- case "list_terminals_request":
1059
- await this.handleListTerminalsRequest(msg);
1060
- break;
1061
- case "create_terminal_request":
1062
- await this.handleCreateTerminalRequest(msg);
1063
- break;
1064
- case "subscribe_terminal_request":
1065
- await this.handleSubscribeTerminalRequest(msg);
1066
- break;
1067
- case "unsubscribe_terminal_request":
1068
- this.handleUnsubscribeTerminalRequest(msg);
1069
- break;
1070
- case "terminal_input":
1071
- this.handleTerminalInput(msg);
1072
- break;
1073
- case "kill_terminal_request":
1074
- await this.handleKillTerminalRequest(msg);
1075
- break;
1076
1131
  }
1077
- }
1078
- catch (error) {
1079
- const err = error instanceof Error ? error : new Error(String(error));
1080
- this.sessionLogger.error({ err }, "Error handling message");
1081
- const requestId = msg.requestId;
1082
- if (typeof requestId === "string") {
1083
- try {
1084
- this.emit({
1085
- type: "rpc_error",
1086
- payload: {
1087
- requestId,
1088
- requestType: msg.type,
1089
- error: `Request failed: ${err.message}`,
1090
- code: "handler_error",
1091
- },
1092
- });
1093
- }
1094
- catch (emitError) {
1095
- this.sessionLogger.error({ err: emitError }, "Failed to emit rpc_error");
1132
+ catch (error) {
1133
+ const err = error instanceof Error ? error : new Error(String(error));
1134
+ this.sessionLogger.error({ err }, "Error handling message");
1135
+ const requestId = msg.requestId;
1136
+ if (typeof requestId === "string") {
1137
+ try {
1138
+ this.emit({
1139
+ type: "rpc_error",
1140
+ payload: {
1141
+ requestId,
1142
+ requestType: msg.type,
1143
+ error: `Request failed: ${err.message}`,
1144
+ code: "handler_error",
1145
+ },
1146
+ });
1147
+ }
1148
+ catch (emitError) {
1149
+ this.sessionLogger.error({ err: emitError }, "Failed to emit rpc_error");
1150
+ }
1096
1151
  }
1152
+ this.emit({
1153
+ type: "activity_log",
1154
+ payload: {
1155
+ id: uuidv4(),
1156
+ timestamp: new Date(),
1157
+ type: "error",
1158
+ content: `Error: ${err.message}`,
1159
+ },
1160
+ });
1097
1161
  }
1098
- this.emit({
1099
- type: "activity_log",
1100
- payload: {
1101
- id: uuidv4(),
1102
- timestamp: new Date(),
1103
- type: "error",
1104
- content: `Error: ${err.message}`,
1105
- },
1106
- });
1107
1162
  }
1163
+ finally {
1164
+ this.inflightRequests--;
1165
+ }
1166
+ }
1167
+ resetPeakInflight() {
1168
+ this.peakInflightRequests = this.inflightRequests;
1108
1169
  }
1109
1170
  handleBinaryFrame(frame) {
1110
1171
  const activeStream = this.activeTerminalStreams.get(frame.slot);
@@ -2210,21 +2271,6 @@ export class Session {
2210
2271
  }
2211
2272
  return baseBranch;
2212
2273
  }
2213
- toCheckoutError(error) {
2214
- if (error instanceof NotGitRepoError) {
2215
- return { code: "NOT_GIT_REPO", message: error.message };
2216
- }
2217
- if (error instanceof MergeConflictError) {
2218
- return { code: "MERGE_CONFLICT", message: error.message };
2219
- }
2220
- if (error instanceof MergeFromBaseConflictError) {
2221
- return { code: "MERGE_CONFLICT", message: error.message };
2222
- }
2223
- if (error instanceof Error) {
2224
- return { code: "UNKNOWN", message: error.message };
2225
- }
2226
- return { code: "UNKNOWN", message: String(error) };
2227
- }
2228
2274
  isPathWithinRoot(rootPath, candidatePath) {
2229
2275
  const resolvedRoot = resolve(rootPath);
2230
2276
  const resolvedCandidate = resolve(candidatePath);
@@ -2721,7 +2767,7 @@ export class Session {
2721
2767
  hasRemote: false,
2722
2768
  remoteUrl: null,
2723
2769
  isPaseoOwnedWorktree: false,
2724
- error: this.toCheckoutError(error),
2770
+ error: toCheckoutError(error),
2725
2771
  requestId,
2726
2772
  },
2727
2773
  });
@@ -2865,63 +2911,6 @@ export class Session {
2865
2911
  });
2866
2912
  }
2867
2913
  }
2868
- normalizeCheckoutDiffCompare(compare) {
2869
- if (compare.mode === "uncommitted") {
2870
- return { mode: "uncommitted" };
2871
- }
2872
- const trimmedBaseRef = compare.baseRef?.trim();
2873
- return trimmedBaseRef ? { mode: "base", baseRef: trimmedBaseRef } : { mode: "base" };
2874
- }
2875
- buildCheckoutDiffTargetKey(cwd, compare) {
2876
- return JSON.stringify([
2877
- cwd,
2878
- compare.mode,
2879
- compare.mode === "base" ? (compare.baseRef ?? "") : "",
2880
- ]);
2881
- }
2882
- closeCheckoutDiffWatchTarget(target) {
2883
- if (target.debounceTimer) {
2884
- clearTimeout(target.debounceTimer);
2885
- target.debounceTimer = null;
2886
- }
2887
- if (target.fallbackRefreshInterval) {
2888
- clearInterval(target.fallbackRefreshInterval);
2889
- target.fallbackRefreshInterval = null;
2890
- }
2891
- for (const watcher of target.watchers) {
2892
- watcher.close();
2893
- }
2894
- target.watchers = [];
2895
- }
2896
- removeCheckoutDiffSubscription(subscriptionId) {
2897
- const subscription = this.checkoutDiffSubscriptions.get(subscriptionId);
2898
- if (!subscription) {
2899
- return;
2900
- }
2901
- this.checkoutDiffSubscriptions.delete(subscriptionId);
2902
- const target = this.checkoutDiffTargets.get(subscription.targetKey);
2903
- if (!target) {
2904
- return;
2905
- }
2906
- target.subscriptions.delete(subscriptionId);
2907
- if (target.subscriptions.size === 0) {
2908
- this.closeCheckoutDiffWatchTarget(target);
2909
- this.checkoutDiffTargets.delete(subscription.targetKey);
2910
- }
2911
- }
2912
- async resolveCheckoutGitDir(cwd) {
2913
- try {
2914
- const { stdout } = await execAsync("git rev-parse --absolute-git-dir", {
2915
- cwd,
2916
- env: READ_ONLY_GIT_ENV,
2917
- });
2918
- const gitDir = stdout.trim();
2919
- return gitDir.length > 0 ? gitDir : null;
2920
- }
2921
- catch {
2922
- return null;
2923
- }
2924
- }
2925
2914
  async resolveWorkspaceGitRefsRoot(gitDir) {
2926
2915
  try {
2927
2916
  const commonDir = (await readFile(join(gitDir, "commondir"), "utf8")).trim();
@@ -3020,7 +3009,7 @@ export class Session {
3020
3009
  if (this.workspaceGitWatchTargets.has(workspaceId)) {
3021
3010
  return;
3022
3011
  }
3023
- const gitDir = await this.resolveCheckoutGitDir(cwd);
3012
+ const gitDir = await resolveCheckoutGitDir(cwd);
3024
3013
  if (!gitDir) {
3025
3014
  return;
3026
3015
  }
@@ -3054,228 +3043,41 @@ export class Session {
3054
3043
  if (target.watchers.length === 0) {
3055
3044
  return;
3056
3045
  }
3057
- this.workspaceGitWatchTargets.set(workspaceId, target);
3058
- }
3059
- async syncWorkspaceGitWatchTarget(cwd, options) {
3060
- if (!options.isGit) {
3061
- this.removeWorkspaceGitWatchTarget(cwd);
3062
- return;
3063
- }
3064
- await this.ensureWorkspaceGitWatchTarget(cwd);
3065
- }
3066
- async resolveCheckoutWatchRoot(cwd) {
3067
- try {
3068
- const { stdout } = await execAsync("git rev-parse --path-format=absolute --show-toplevel", {
3069
- cwd,
3070
- env: READ_ONLY_GIT_ENV,
3071
- });
3072
- const root = stdout.trim();
3073
- return root.length > 0 ? root : null;
3074
- }
3075
- catch {
3076
- return null;
3077
- }
3078
- }
3079
- scheduleCheckoutDiffTargetRefresh(target) {
3080
- if (target.debounceTimer) {
3081
- clearTimeout(target.debounceTimer);
3082
- }
3083
- target.debounceTimer = setTimeout(() => {
3084
- target.debounceTimer = null;
3085
- void this.refreshCheckoutDiffTarget(target);
3086
- }, CHECKOUT_DIFF_WATCH_DEBOUNCE_MS);
3087
- }
3088
- emitCheckoutDiffUpdate(target, snapshot) {
3089
- if (target.subscriptions.size === 0) {
3090
- return;
3091
- }
3092
- for (const subscriptionId of target.subscriptions) {
3093
- this.emit({
3094
- type: "checkout_diff_update",
3095
- payload: {
3096
- subscriptionId,
3097
- ...snapshot,
3098
- },
3099
- });
3100
- }
3101
- }
3102
- checkoutDiffSnapshotFingerprint(snapshot) {
3103
- return JSON.stringify(snapshot);
3104
- }
3105
- async computeCheckoutDiffSnapshot(cwd, compare, options) {
3106
- const diffCwd = options?.diffCwd ?? cwd;
3107
- try {
3108
- const diffResult = await getCheckoutDiff(diffCwd, {
3109
- mode: compare.mode,
3110
- baseRef: compare.baseRef,
3111
- includeStructured: true,
3112
- }, { paseoHome: this.paseoHome });
3113
- const files = [...(diffResult.structured ?? [])];
3114
- files.sort((a, b) => {
3115
- if (a.path === b.path)
3116
- return 0;
3117
- return a.path < b.path ? -1 : 1;
3118
- });
3119
- return {
3120
- cwd,
3121
- files,
3122
- error: null,
3123
- };
3124
- }
3125
- catch (error) {
3126
- return {
3127
- cwd,
3128
- files: [],
3129
- error: this.toCheckoutError(error),
3130
- };
3131
- }
3132
- }
3133
- async refreshCheckoutDiffTarget(target) {
3134
- if (target.refreshPromise) {
3135
- target.refreshQueued = true;
3136
- return;
3137
- }
3138
- target.refreshPromise = (async () => {
3139
- do {
3140
- target.refreshQueued = false;
3141
- const snapshot = await this.computeCheckoutDiffSnapshot(target.cwd, target.compare, {
3142
- diffCwd: target.diffCwd,
3143
- });
3144
- target.latestPayload = snapshot;
3145
- const fingerprint = this.checkoutDiffSnapshotFingerprint(snapshot);
3146
- if (fingerprint !== target.latestFingerprint) {
3147
- target.latestFingerprint = fingerprint;
3148
- this.emitCheckoutDiffUpdate(target, snapshot);
3149
- }
3150
- } while (target.refreshQueued);
3151
- })();
3152
- try {
3153
- await target.refreshPromise;
3154
- }
3155
- finally {
3156
- target.refreshPromise = null;
3157
- }
3158
- }
3159
- async ensureCheckoutDiffWatchTarget(cwd, compare) {
3160
- const targetKey = this.buildCheckoutDiffTargetKey(cwd, compare);
3161
- const existing = this.checkoutDiffTargets.get(targetKey);
3162
- if (existing) {
3163
- return existing;
3164
- }
3165
- const watchRoot = await this.resolveCheckoutWatchRoot(cwd);
3166
- const target = {
3167
- key: targetKey,
3168
- cwd,
3169
- diffCwd: watchRoot ?? cwd,
3170
- compare,
3171
- subscriptions: new Set(),
3172
- watchers: [],
3173
- fallbackRefreshInterval: null,
3174
- debounceTimer: null,
3175
- refreshPromise: null,
3176
- refreshQueued: false,
3177
- latestPayload: null,
3178
- latestFingerprint: null,
3179
- };
3180
- const repoWatchPath = watchRoot ?? cwd;
3181
- const watchPaths = new Set([repoWatchPath]);
3182
- const gitDir = await this.resolveCheckoutGitDir(cwd);
3183
- if (gitDir) {
3184
- watchPaths.add(gitDir);
3185
- }
3186
- let hasRecursiveRepoCoverage = false;
3187
- const allowRecursiveRepoWatch = process.platform !== "linux";
3188
- for (const watchPath of watchPaths) {
3189
- const shouldTryRecursive = watchPath === repoWatchPath && allowRecursiveRepoWatch;
3190
- const createWatcher = (recursive) => watch(watchPath, { recursive }, () => {
3191
- this.scheduleCheckoutDiffTargetRefresh(target);
3192
- });
3193
- let watcher = null;
3194
- let watcherIsRecursive = false;
3195
- try {
3196
- if (shouldTryRecursive) {
3197
- watcher = createWatcher(true);
3198
- watcherIsRecursive = true;
3199
- }
3200
- else {
3201
- watcher = createWatcher(false);
3202
- }
3203
- }
3204
- catch (error) {
3205
- if (shouldTryRecursive) {
3206
- try {
3207
- watcher = createWatcher(false);
3208
- this.sessionLogger.warn({ err: error, watchPath, cwd, compare }, "Checkout diff recursive watch unavailable; using non-recursive fallback");
3209
- }
3210
- catch (fallbackError) {
3211
- this.sessionLogger.warn({ err: fallbackError, watchPath, cwd, compare }, "Failed to start checkout diff watcher");
3212
- }
3213
- }
3214
- else {
3215
- this.sessionLogger.warn({ err: error, watchPath, cwd, compare }, "Failed to start checkout diff watcher");
3216
- }
3217
- }
3218
- if (!watcher) {
3219
- continue;
3220
- }
3221
- watcher.on("error", (error) => {
3222
- this.sessionLogger.warn({ err: error, watchPath, cwd, compare }, "Checkout diff watcher error");
3223
- });
3224
- target.watchers.push(watcher);
3225
- if (watchPath === repoWatchPath && watcherIsRecursive) {
3226
- hasRecursiveRepoCoverage = true;
3227
- }
3228
- }
3229
- const missingRepoCoverage = !hasRecursiveRepoCoverage;
3230
- if (target.watchers.length === 0 || missingRepoCoverage) {
3231
- target.fallbackRefreshInterval = setInterval(() => {
3232
- this.scheduleCheckoutDiffTargetRefresh(target);
3233
- }, CHECKOUT_DIFF_FALLBACK_REFRESH_MS);
3234
- this.sessionLogger.warn({
3235
- cwd,
3236
- compare,
3237
- intervalMs: CHECKOUT_DIFF_FALLBACK_REFRESH_MS,
3238
- reason: target.watchers.length === 0 ? "no_watchers" : "missing_recursive_repo_root_coverage",
3239
- }, "Checkout diff watchers unavailable; using timed refresh fallback");
3046
+ this.workspaceGitWatchTargets.set(workspaceId, target);
3047
+ }
3048
+ async syncWorkspaceGitWatchTarget(cwd, options) {
3049
+ if (!options.isGit) {
3050
+ this.removeWorkspaceGitWatchTarget(cwd);
3051
+ return;
3240
3052
  }
3241
- this.checkoutDiffTargets.set(targetKey, target);
3242
- return target;
3053
+ await this.ensureWorkspaceGitWatchTarget(cwd);
3243
3054
  }
3244
3055
  async handleSubscribeCheckoutDiffRequest(msg) {
3245
3056
  const cwd = expandTilde(msg.cwd);
3246
- const compare = this.normalizeCheckoutDiffCompare(msg.compare);
3247
- this.removeCheckoutDiffSubscription(msg.subscriptionId);
3248
- const target = await this.ensureCheckoutDiffWatchTarget(cwd, compare);
3249
- target.subscriptions.add(msg.subscriptionId);
3250
- this.checkoutDiffSubscriptions.set(msg.subscriptionId, {
3251
- targetKey: target.key,
3057
+ this.checkoutDiffSubscriptions.get(msg.subscriptionId)?.();
3058
+ this.checkoutDiffSubscriptions.delete(msg.subscriptionId);
3059
+ const subscription = await this.checkoutDiffManager.subscribe({ cwd, compare: msg.compare }, (snapshot) => {
3060
+ this.emit({
3061
+ type: "checkout_diff_update",
3062
+ payload: {
3063
+ subscriptionId: msg.subscriptionId,
3064
+ ...snapshot,
3065
+ },
3066
+ });
3252
3067
  });
3253
- const snapshot = target.latestPayload ??
3254
- (await this.computeCheckoutDiffSnapshot(cwd, compare, {
3255
- diffCwd: target.diffCwd,
3256
- }));
3257
- target.latestPayload = snapshot;
3258
- target.latestFingerprint = this.checkoutDiffSnapshotFingerprint(snapshot);
3068
+ this.checkoutDiffSubscriptions.set(msg.subscriptionId, subscription.unsubscribe);
3259
3069
  this.emit({
3260
3070
  type: "subscribe_checkout_diff_response",
3261
3071
  payload: {
3262
3072
  subscriptionId: msg.subscriptionId,
3263
- ...snapshot,
3073
+ ...subscription.initial,
3264
3074
  requestId: msg.requestId,
3265
3075
  },
3266
3076
  });
3267
3077
  }
3268
3078
  handleUnsubscribeCheckoutDiffRequest(msg) {
3269
- this.removeCheckoutDiffSubscription(msg.subscriptionId);
3270
- }
3271
- scheduleCheckoutDiffRefreshForCwd(cwd) {
3272
- const resolvedCwd = expandTilde(cwd);
3273
- for (const target of this.checkoutDiffTargets.values()) {
3274
- if (target.cwd !== resolvedCwd && target.diffCwd !== resolvedCwd) {
3275
- continue;
3276
- }
3277
- this.scheduleCheckoutDiffTargetRefresh(target);
3278
- }
3079
+ this.checkoutDiffSubscriptions.get(msg.subscriptionId)?.();
3080
+ this.checkoutDiffSubscriptions.delete(msg.subscriptionId);
3279
3081
  }
3280
3082
  async handleCheckoutCommitRequest(msg) {
3281
3083
  const { cwd, requestId } = msg;
@@ -3291,7 +3093,7 @@ export class Session {
3291
3093
  message,
3292
3094
  addAll: msg.addAll ?? true,
3293
3095
  });
3294
- this.scheduleCheckoutDiffRefreshForCwd(cwd);
3096
+ this.checkoutDiffManager.scheduleRefreshForCwd(cwd);
3295
3097
  this.emit({
3296
3098
  type: "checkout_commit_response",
3297
3099
  payload: {
@@ -3308,7 +3110,7 @@ export class Session {
3308
3110
  payload: {
3309
3111
  cwd,
3310
3112
  success: false,
3311
- error: this.toCheckoutError(error),
3113
+ error: toCheckoutError(error),
3312
3114
  requestId,
3313
3115
  },
3314
3116
  });
@@ -3354,7 +3156,7 @@ export class Session {
3354
3156
  baseRef,
3355
3157
  mode: msg.strategy === "squash" ? "squash" : "merge",
3356
3158
  }, { paseoHome: this.paseoHome });
3357
- this.scheduleCheckoutDiffRefreshForCwd(cwd);
3159
+ this.checkoutDiffManager.scheduleRefreshForCwd(cwd);
3358
3160
  this.emit({
3359
3161
  type: "checkout_merge_response",
3360
3162
  payload: {
@@ -3371,7 +3173,7 @@ export class Session {
3371
3173
  payload: {
3372
3174
  cwd,
3373
3175
  success: false,
3374
- error: this.toCheckoutError(error),
3176
+ error: toCheckoutError(error),
3375
3177
  requestId,
3376
3178
  },
3377
3179
  });
@@ -3393,7 +3195,7 @@ export class Session {
3393
3195
  baseRef: msg.baseRef,
3394
3196
  requireCleanTarget: msg.requireCleanTarget ?? true,
3395
3197
  });
3396
- this.scheduleCheckoutDiffRefreshForCwd(cwd);
3198
+ this.checkoutDiffManager.scheduleRefreshForCwd(cwd);
3397
3199
  this.emit({
3398
3200
  type: "checkout_merge_from_base_response",
3399
3201
  payload: {
@@ -3410,7 +3212,7 @@ export class Session {
3410
3212
  payload: {
3411
3213
  cwd,
3412
3214
  success: false,
3413
- error: this.toCheckoutError(error),
3215
+ error: toCheckoutError(error),
3414
3216
  requestId,
3415
3217
  },
3416
3218
  });
@@ -3436,7 +3238,7 @@ export class Session {
3436
3238
  payload: {
3437
3239
  cwd,
3438
3240
  success: false,
3439
- error: this.toCheckoutError(error),
3241
+ error: toCheckoutError(error),
3440
3242
  requestId,
3441
3243
  },
3442
3244
  });
@@ -3477,7 +3279,7 @@ export class Session {
3477
3279
  cwd,
3478
3280
  url: null,
3479
3281
  number: null,
3480
- error: this.toCheckoutError(error),
3282
+ error: toCheckoutError(error),
3481
3283
  requestId,
3482
3284
  },
3483
3285
  });
@@ -3505,7 +3307,7 @@ export class Session {
3505
3307
  cwd,
3506
3308
  status: null,
3507
3309
  githubFeaturesEnabled: true,
3508
- error: this.toCheckoutError(error),
3310
+ error: toCheckoutError(error),
3509
3311
  requestId,
3510
3312
  },
3511
3313
  });
@@ -3546,7 +3348,7 @@ export class Session {
3546
3348
  type: "paseo_worktree_list_response",
3547
3349
  payload: {
3548
3350
  worktrees: [],
3549
- error: this.toCheckoutError(error),
3351
+ error: toCheckoutError(error),
3550
3352
  requestId,
3551
3353
  },
3552
3354
  });
@@ -3677,7 +3479,7 @@ export class Session {
3677
3479
  payload: {
3678
3480
  success: false,
3679
3481
  removedAgents: [],
3680
- error: this.toCheckoutError(error),
3482
+ error: toCheckoutError(error),
3681
3483
  requestId,
3682
3484
  },
3683
3485
  });
@@ -5735,10 +5537,9 @@ export class Session {
5735
5537
  }
5736
5538
  this.terminalExitSubscriptions.clear();
5737
5539
  this.disposeTerminalSubscriptions();
5738
- for (const target of this.checkoutDiffTargets.values()) {
5739
- this.closeCheckoutDiffWatchTarget(target);
5540
+ for (const unsubscribe of this.checkoutDiffSubscriptions.values()) {
5541
+ unsubscribe();
5740
5542
  }
5741
- this.checkoutDiffTargets.clear();
5742
5543
  this.checkoutDiffSubscriptions.clear();
5743
5544
  for (const target of this.workspaceGitWatchTargets.values()) {
5744
5545
  this.closeWorkspaceGitWatchTarget(target);
@@ -5765,6 +5566,416 @@ export class Session {
5765
5566
  }
5766
5567
  this.detachTerminalStream(terminalId, { emitExit: true });
5767
5568
  }
5569
+ emitChatRpcError(request, error) {
5570
+ const message = error instanceof Error ? error.message : "Chat request failed";
5571
+ const code = error instanceof ChatServiceError ? error.code : "chat_request_failed";
5572
+ this.sessionLogger.error({ err: error, requestType: request.type }, "Chat request failed");
5573
+ this.emit({
5574
+ type: "rpc_error",
5575
+ payload: {
5576
+ requestId: request.requestId,
5577
+ requestType: request.type,
5578
+ error: message,
5579
+ code,
5580
+ },
5581
+ });
5582
+ }
5583
+ async handleChatCreateRequest(request) {
5584
+ try {
5585
+ const room = await this.chatService.createRoom({
5586
+ name: request.name,
5587
+ purpose: request.purpose,
5588
+ });
5589
+ this.emit({
5590
+ type: "chat/create/response",
5591
+ payload: {
5592
+ requestId: request.requestId,
5593
+ room,
5594
+ error: null,
5595
+ },
5596
+ });
5597
+ }
5598
+ catch (error) {
5599
+ this.emitChatRpcError(request, error);
5600
+ }
5601
+ }
5602
+ async handleChatListRequest(request) {
5603
+ try {
5604
+ const rooms = await this.chatService.listRooms();
5605
+ this.emit({
5606
+ type: "chat/list/response",
5607
+ payload: {
5608
+ requestId: request.requestId,
5609
+ rooms,
5610
+ error: null,
5611
+ },
5612
+ });
5613
+ }
5614
+ catch (error) {
5615
+ this.emitChatRpcError(request, error);
5616
+ }
5617
+ }
5618
+ async handleChatInspectRequest(request) {
5619
+ try {
5620
+ const result = await this.chatService.inspectRoom({
5621
+ room: request.room,
5622
+ });
5623
+ this.emit({
5624
+ type: "chat/inspect/response",
5625
+ payload: {
5626
+ requestId: request.requestId,
5627
+ room: result.room,
5628
+ error: null,
5629
+ },
5630
+ });
5631
+ }
5632
+ catch (error) {
5633
+ this.emitChatRpcError(request, error);
5634
+ }
5635
+ }
5636
+ async handleChatDeleteRequest(request) {
5637
+ try {
5638
+ const result = await this.chatService.deleteRoom({
5639
+ room: request.room,
5640
+ });
5641
+ this.emit({
5642
+ type: "chat/delete/response",
5643
+ payload: {
5644
+ requestId: request.requestId,
5645
+ room: result.room,
5646
+ error: null,
5647
+ },
5648
+ });
5649
+ }
5650
+ catch (error) {
5651
+ this.emitChatRpcError(request, error);
5652
+ }
5653
+ }
5654
+ async handleChatPostRequest(request) {
5655
+ try {
5656
+ const authorAgentId = request.authorAgentId?.trim() || this.clientId;
5657
+ const message = await this.chatService.postMessage({
5658
+ room: request.room,
5659
+ authorAgentId,
5660
+ body: request.body,
5661
+ replyToMessageId: request.replyToMessageId,
5662
+ });
5663
+ this.emit({
5664
+ type: "chat/post/response",
5665
+ payload: {
5666
+ requestId: request.requestId,
5667
+ message,
5668
+ error: null,
5669
+ },
5670
+ });
5671
+ void notifyChatMentions({
5672
+ room: request.room,
5673
+ authorAgentId,
5674
+ body: request.body,
5675
+ mentionAgentIds: message.mentionAgentIds,
5676
+ logger: this.sessionLogger,
5677
+ listStoredAgents: () => this.agentStorage.list(),
5678
+ listLiveAgents: () => this.agentManager.listAgents(),
5679
+ resolveAgentIdentifier: (identifier) => this.resolveAgentIdentifier(identifier),
5680
+ sendAgentMessage: (agentId, text) => this.handleSendAgentMessage(agentId, text),
5681
+ });
5682
+ }
5683
+ catch (error) {
5684
+ this.emitChatRpcError(request, error);
5685
+ }
5686
+ }
5687
+ async handleChatReadRequest(request) {
5688
+ try {
5689
+ const messages = await this.chatService.readMessages({
5690
+ room: request.room,
5691
+ limit: request.limit,
5692
+ since: request.since,
5693
+ authorAgentId: request.authorAgentId,
5694
+ });
5695
+ this.emit({
5696
+ type: "chat/read/response",
5697
+ payload: {
5698
+ requestId: request.requestId,
5699
+ messages,
5700
+ error: null,
5701
+ },
5702
+ });
5703
+ }
5704
+ catch (error) {
5705
+ this.emitChatRpcError(request, error);
5706
+ }
5707
+ }
5708
+ async handleChatWaitRequest(request) {
5709
+ try {
5710
+ const messages = await this.chatService.waitForMessages({
5711
+ room: request.room,
5712
+ afterMessageId: request.afterMessageId,
5713
+ timeoutMs: request.timeoutMs,
5714
+ });
5715
+ this.emit({
5716
+ type: "chat/wait/response",
5717
+ payload: {
5718
+ requestId: request.requestId,
5719
+ messages,
5720
+ timedOut: messages.length === 0,
5721
+ error: null,
5722
+ },
5723
+ });
5724
+ }
5725
+ catch (error) {
5726
+ this.emitChatRpcError(request, error);
5727
+ }
5728
+ }
5729
+ toScheduleSummary(schedule) {
5730
+ const { runs: _runs, ...summary } = schedule;
5731
+ return summary;
5732
+ }
5733
+ emitScheduleRpcError(request, error) {
5734
+ const message = error instanceof Error ? error.message : String(error);
5735
+ this.sessionLogger.error({ err: error, requestType: request.type }, "Schedule request failed");
5736
+ this.emit({
5737
+ type: "rpc_error",
5738
+ payload: {
5739
+ requestId: request.requestId,
5740
+ requestType: request.type,
5741
+ error: message,
5742
+ code: "schedule_request_failed",
5743
+ },
5744
+ });
5745
+ }
5746
+ async handleScheduleCreateRequest(request) {
5747
+ try {
5748
+ const target = request.target.type === "self"
5749
+ ? { type: "agent", agentId: request.target.agentId }
5750
+ : request.target;
5751
+ const schedule = await this.scheduleService.create({
5752
+ prompt: request.prompt,
5753
+ name: request.name,
5754
+ cadence: request.cadence,
5755
+ target,
5756
+ maxRuns: request.maxRuns,
5757
+ expiresAt: request.expiresAt,
5758
+ });
5759
+ this.emit({
5760
+ type: "schedule/create/response",
5761
+ payload: {
5762
+ requestId: request.requestId,
5763
+ schedule: this.toScheduleSummary(schedule),
5764
+ error: null,
5765
+ },
5766
+ });
5767
+ }
5768
+ catch (error) {
5769
+ this.emitScheduleRpcError(request, error);
5770
+ }
5771
+ }
5772
+ async handleScheduleListRequest(request) {
5773
+ try {
5774
+ const schedules = await this.scheduleService.list();
5775
+ this.emit({
5776
+ type: "schedule/list/response",
5777
+ payload: {
5778
+ requestId: request.requestId,
5779
+ schedules: schedules.map((schedule) => this.toScheduleSummary(schedule)),
5780
+ error: null,
5781
+ },
5782
+ });
5783
+ }
5784
+ catch (error) {
5785
+ this.emitScheduleRpcError(request, error);
5786
+ }
5787
+ }
5788
+ async handleScheduleInspectRequest(request) {
5789
+ try {
5790
+ const schedule = await this.scheduleService.inspect(request.scheduleId);
5791
+ this.emit({
5792
+ type: "schedule/inspect/response",
5793
+ payload: {
5794
+ requestId: request.requestId,
5795
+ schedule,
5796
+ error: null,
5797
+ },
5798
+ });
5799
+ }
5800
+ catch (error) {
5801
+ this.emitScheduleRpcError(request, error);
5802
+ }
5803
+ }
5804
+ async handleScheduleLogsRequest(request) {
5805
+ try {
5806
+ const runs = await this.scheduleService.logs(request.scheduleId);
5807
+ this.emit({
5808
+ type: "schedule/logs/response",
5809
+ payload: {
5810
+ requestId: request.requestId,
5811
+ runs,
5812
+ error: null,
5813
+ },
5814
+ });
5815
+ }
5816
+ catch (error) {
5817
+ this.emitScheduleRpcError(request, error);
5818
+ }
5819
+ }
5820
+ async handleSchedulePauseRequest(request) {
5821
+ try {
5822
+ const schedule = await this.scheduleService.pause(request.scheduleId);
5823
+ this.emit({
5824
+ type: "schedule/pause/response",
5825
+ payload: {
5826
+ requestId: request.requestId,
5827
+ schedule: this.toScheduleSummary(schedule),
5828
+ error: null,
5829
+ },
5830
+ });
5831
+ }
5832
+ catch (error) {
5833
+ this.emitScheduleRpcError(request, error);
5834
+ }
5835
+ }
5836
+ async handleScheduleResumeRequest(request) {
5837
+ try {
5838
+ const schedule = await this.scheduleService.resume(request.scheduleId);
5839
+ this.emit({
5840
+ type: "schedule/resume/response",
5841
+ payload: {
5842
+ requestId: request.requestId,
5843
+ schedule: this.toScheduleSummary(schedule),
5844
+ error: null,
5845
+ },
5846
+ });
5847
+ }
5848
+ catch (error) {
5849
+ this.emitScheduleRpcError(request, error);
5850
+ }
5851
+ }
5852
+ async handleScheduleDeleteRequest(request) {
5853
+ try {
5854
+ await this.scheduleService.delete(request.scheduleId);
5855
+ this.emit({
5856
+ type: "schedule/delete/response",
5857
+ payload: {
5858
+ requestId: request.requestId,
5859
+ scheduleId: request.scheduleId,
5860
+ error: null,
5861
+ },
5862
+ });
5863
+ }
5864
+ catch (error) {
5865
+ this.emitScheduleRpcError(request, error);
5866
+ }
5867
+ }
5868
+ emitLoopRpcError(request, error) {
5869
+ const message = error instanceof Error ? error.message : String(error);
5870
+ this.sessionLogger.error({ err: error, requestType: request.type }, "Loop request failed");
5871
+ this.emit({
5872
+ type: "rpc_error",
5873
+ payload: {
5874
+ requestId: request.requestId,
5875
+ requestType: request.type,
5876
+ error: message,
5877
+ code: "loop_request_failed",
5878
+ },
5879
+ });
5880
+ }
5881
+ async handleLoopRunRequest(request) {
5882
+ try {
5883
+ const loop = await this.loopService.runLoop({
5884
+ prompt: request.prompt,
5885
+ cwd: request.cwd,
5886
+ provider: request.provider,
5887
+ model: request.model,
5888
+ workerProvider: request.workerProvider,
5889
+ workerModel: request.workerModel,
5890
+ verifierProvider: request.verifierProvider,
5891
+ verifierModel: request.verifierModel,
5892
+ verifyPrompt: request.verifyPrompt,
5893
+ verifyChecks: request.verifyChecks,
5894
+ archive: request.archive,
5895
+ name: request.name,
5896
+ sleepMs: request.sleepMs,
5897
+ maxIterations: request.maxIterations,
5898
+ maxTimeMs: request.maxTimeMs,
5899
+ });
5900
+ this.emit({
5901
+ type: "loop/run/response",
5902
+ payload: {
5903
+ requestId: request.requestId,
5904
+ loop,
5905
+ error: null,
5906
+ },
5907
+ });
5908
+ }
5909
+ catch (error) {
5910
+ this.emitLoopRpcError(request, error);
5911
+ }
5912
+ }
5913
+ async handleLoopListRequest(request) {
5914
+ try {
5915
+ const loops = await this.loopService.listLoops();
5916
+ this.emit({
5917
+ type: "loop/list/response",
5918
+ payload: {
5919
+ requestId: request.requestId,
5920
+ loops,
5921
+ error: null,
5922
+ },
5923
+ });
5924
+ }
5925
+ catch (error) {
5926
+ this.emitLoopRpcError(request, error);
5927
+ }
5928
+ }
5929
+ async handleLoopInspectRequest(request) {
5930
+ try {
5931
+ const loop = await this.loopService.inspectLoop(request.id);
5932
+ this.emit({
5933
+ type: "loop/inspect/response",
5934
+ payload: {
5935
+ requestId: request.requestId,
5936
+ loop,
5937
+ error: null,
5938
+ },
5939
+ });
5940
+ }
5941
+ catch (error) {
5942
+ this.emitLoopRpcError(request, error);
5943
+ }
5944
+ }
5945
+ async handleLoopLogsRequest(request) {
5946
+ try {
5947
+ const result = await this.loopService.getLoopLogs(request.id, request.afterSeq ?? 0);
5948
+ this.emit({
5949
+ type: "loop/logs/response",
5950
+ payload: {
5951
+ requestId: request.requestId,
5952
+ loop: result.loop,
5953
+ entries: result.entries,
5954
+ nextCursor: result.nextCursor,
5955
+ error: null,
5956
+ },
5957
+ });
5958
+ }
5959
+ catch (error) {
5960
+ this.emitLoopRpcError(request, error);
5961
+ }
5962
+ }
5963
+ async handleLoopStopRequest(request) {
5964
+ try {
5965
+ const loop = await this.loopService.stopLoop(request.id);
5966
+ this.emit({
5967
+ type: "loop/stop/response",
5968
+ payload: {
5969
+ requestId: request.requestId,
5970
+ loop,
5971
+ error: null,
5972
+ },
5973
+ });
5974
+ }
5975
+ catch (error) {
5976
+ this.emitLoopRpcError(request, error);
5977
+ }
5978
+ }
5768
5979
  emitTerminalsChangedSnapshot(input) {
5769
5980
  this.emit({
5770
5981
  type: "terminals_changed",