@getpaseo/server 0.1.59 → 0.1.60

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 (273) hide show
  1. package/dist/scripts/dev-runner.js +26 -7
  2. package/dist/scripts/dev-runner.js.map +1 -1
  3. package/dist/server/client/daemon-client-runtime-metrics.d.ts +39 -0
  4. package/dist/server/client/daemon-client-runtime-metrics.d.ts.map +1 -0
  5. package/dist/server/client/daemon-client-runtime-metrics.js +173 -0
  6. package/dist/server/client/daemon-client-runtime-metrics.js.map +1 -0
  7. package/dist/server/client/daemon-client.d.ts +58 -9
  8. package/dist/server/client/daemon-client.d.ts.map +1 -1
  9. package/dist/server/client/daemon-client.js +151 -10
  10. package/dist/server/client/daemon-client.js.map +1 -1
  11. package/dist/server/server/agent/agent-manager.d.ts +55 -48
  12. package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
  13. package/dist/server/server/agent/agent-manager.js +541 -331
  14. package/dist/server/server/agent/agent-manager.js.map +1 -1
  15. package/dist/server/server/agent/agent-metadata-generator.d.ts +3 -2
  16. package/dist/server/server/agent/agent-metadata-generator.d.ts.map +1 -1
  17. package/dist/server/server/agent/agent-metadata-generator.js +31 -16
  18. package/dist/server/server/agent/agent-metadata-generator.js.map +1 -1
  19. package/dist/server/server/agent/agent-projections.d.ts +2 -1
  20. package/dist/server/server/agent/agent-projections.d.ts.map +1 -1
  21. package/dist/server/server/agent/agent-projections.js +29 -1
  22. package/dist/server/server/agent/agent-projections.js.map +1 -1
  23. package/dist/server/server/agent/agent-sdk-types.d.ts +9 -5
  24. package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
  25. package/dist/server/server/agent/agent-sdk-types.js.map +1 -1
  26. package/dist/server/server/agent/agent-storage.d.ts +76 -69
  27. package/dist/server/server/agent/agent-storage.d.ts.map +1 -1
  28. package/dist/server/server/agent/agent-storage.js +13 -6
  29. package/dist/server/server/agent/agent-storage.js.map +1 -1
  30. package/dist/server/server/agent/agent-stream-coalescer.d.ts +41 -0
  31. package/dist/server/server/agent/agent-stream-coalescer.d.ts.map +1 -0
  32. package/dist/server/server/agent/agent-stream-coalescer.js +166 -0
  33. package/dist/server/server/agent/agent-stream-coalescer.js.map +1 -0
  34. package/dist/server/server/agent/agent-timeline-store-types.d.ts +54 -0
  35. package/dist/server/server/agent/agent-timeline-store-types.d.ts.map +1 -0
  36. package/dist/server/server/agent/agent-timeline-store-types.js +2 -0
  37. package/dist/server/server/agent/agent-timeline-store-types.js.map +1 -0
  38. package/dist/server/server/agent/agent-timeline-store.d.ts +32 -0
  39. package/dist/server/server/agent/agent-timeline-store.d.ts.map +1 -0
  40. package/dist/server/server/agent/agent-timeline-store.js +245 -0
  41. package/dist/server/server/agent/agent-timeline-store.js.map +1 -0
  42. package/dist/server/server/agent/mcp-server.d.ts +12 -1
  43. package/dist/server/server/agent/mcp-server.d.ts.map +1 -1
  44. package/dist/server/server/agent/mcp-server.js +276 -65
  45. package/dist/server/server/agent/mcp-server.js.map +1 -1
  46. package/dist/server/server/agent/mcp-shared.d.ts +196 -152
  47. package/dist/server/server/agent/mcp-shared.d.ts.map +1 -1
  48. package/dist/server/server/agent/mcp-shared.js +40 -42
  49. package/dist/server/server/agent/mcp-shared.js.map +1 -1
  50. package/dist/server/server/agent/model-resolver.d.ts.map +1 -1
  51. package/dist/server/server/agent/model-resolver.js +3 -1
  52. package/dist/server/server/agent/model-resolver.js.map +1 -1
  53. package/dist/server/server/agent/prompt-attachments.d.ts +6 -0
  54. package/dist/server/server/agent/prompt-attachments.d.ts.map +1 -0
  55. package/dist/server/server/agent/prompt-attachments.js +31 -0
  56. package/dist/server/server/agent/prompt-attachments.js.map +1 -0
  57. package/dist/server/server/agent/provider-launch-config.d.ts +12 -10
  58. package/dist/server/server/agent/provider-launch-config.d.ts.map +1 -1
  59. package/dist/server/server/agent/provider-launch-config.js +34 -0
  60. package/dist/server/server/agent/provider-launch-config.js.map +1 -1
  61. package/dist/server/server/agent/provider-manifest.d.ts +1 -0
  62. package/dist/server/server/agent/provider-manifest.d.ts.map +1 -1
  63. package/dist/server/server/agent/provider-manifest.js +22 -1
  64. package/dist/server/server/agent/provider-manifest.js.map +1 -1
  65. package/dist/server/server/agent/provider-registry.d.ts +5 -2
  66. package/dist/server/server/agent/provider-registry.d.ts.map +1 -1
  67. package/dist/server/server/agent/provider-registry.js +20 -9
  68. package/dist/server/server/agent/provider-registry.js.map +1 -1
  69. package/dist/server/server/agent/provider-snapshot-manager.d.ts +17 -5
  70. package/dist/server/server/agent/provider-snapshot-manager.d.ts.map +1 -1
  71. package/dist/server/server/agent/provider-snapshot-manager.js +150 -61
  72. package/dist/server/server/agent/provider-snapshot-manager.js.map +1 -1
  73. package/dist/server/server/agent/providers/acp-agent.d.ts +8 -4
  74. package/dist/server/server/agent/providers/acp-agent.d.ts.map +1 -1
  75. package/dist/server/server/agent/providers/acp-agent.js +73 -8
  76. package/dist/server/server/agent/providers/acp-agent.js.map +1 -1
  77. package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +2 -2
  78. package/dist/server/server/agent/providers/claude-agent.d.ts +1 -1
  79. package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
  80. package/dist/server/server/agent/providers/claude-agent.js +8 -7
  81. package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
  82. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +37 -4
  83. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
  84. package/dist/server/server/agent/providers/codex-app-server-agent.js +61 -31
  85. package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
  86. package/dist/server/server/agent/providers/copilot-acp-agent.d.ts.map +1 -1
  87. package/dist/server/server/agent/providers/copilot-acp-agent.js +3 -2
  88. package/dist/server/server/agent/providers/copilot-acp-agent.js.map +1 -1
  89. package/dist/server/server/agent/providers/mock-load-test-agent.d.ts +64 -0
  90. package/dist/server/server/agent/providers/mock-load-test-agent.d.ts.map +1 -0
  91. package/dist/server/server/agent/providers/mock-load-test-agent.js +585 -0
  92. package/dist/server/server/agent/providers/mock-load-test-agent.js.map +1 -0
  93. package/dist/server/server/agent/providers/opencode-agent.d.ts +19 -4
  94. package/dist/server/server/agent/providers/opencode-agent.d.ts.map +1 -1
  95. package/dist/server/server/agent/providers/opencode-agent.js +227 -118
  96. package/dist/server/server/agent/providers/opencode-agent.js.map +1 -1
  97. package/dist/server/server/agent/providers/pi-direct-agent.d.ts +69 -0
  98. package/dist/server/server/agent/providers/pi-direct-agent.d.ts.map +1 -0
  99. package/dist/server/server/agent/providers/pi-direct-agent.js +1177 -0
  100. package/dist/server/server/agent/providers/pi-direct-agent.js.map +1 -0
  101. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +7 -4
  102. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts.map +1 -1
  103. package/dist/server/server/agent-attention-policy.d.ts +13 -13
  104. package/dist/server/server/agent-attention-policy.d.ts.map +1 -1
  105. package/dist/server/server/agent-attention-policy.js +20 -36
  106. package/dist/server/server/agent-attention-policy.js.map +1 -1
  107. package/dist/server/server/bootstrap.d.ts +6 -0
  108. package/dist/server/server/bootstrap.d.ts.map +1 -1
  109. package/dist/server/server/bootstrap.js +113 -11
  110. package/dist/server/server/bootstrap.js.map +1 -1
  111. package/dist/server/server/chat/chat-rpc-schemas.d.ts +44 -44
  112. package/dist/server/server/chat/chat-types.d.ts +6 -6
  113. package/dist/server/server/checkout-diff-manager.d.ts +0 -1
  114. package/dist/server/server/checkout-diff-manager.d.ts.map +1 -1
  115. package/dist/server/server/checkout-diff-manager.js +6 -4
  116. package/dist/server/server/checkout-diff-manager.js.map +1 -1
  117. package/dist/server/server/config.d.ts.map +1 -1
  118. package/dist/server/server/config.js +1 -0
  119. package/dist/server/server/config.js.map +1 -1
  120. package/dist/server/server/file-explorer/service.d.ts.map +1 -1
  121. package/dist/server/server/file-explorer/service.js +2 -1
  122. package/dist/server/server/file-explorer/service.js.map +1 -1
  123. package/dist/server/server/loop/rpc-schemas.d.ts +392 -392
  124. package/dist/server/server/loop-service.d.ts +52 -52
  125. package/dist/server/server/paseo-worktree-archive-service.d.ts +41 -0
  126. package/dist/server/server/paseo-worktree-archive-service.d.ts.map +1 -0
  127. package/dist/server/server/paseo-worktree-archive-service.js +137 -0
  128. package/dist/server/server/paseo-worktree-archive-service.js.map +1 -0
  129. package/dist/server/server/paseo-worktree-service.d.ts +24 -0
  130. package/dist/server/server/paseo-worktree-service.d.ts.map +1 -0
  131. package/dist/server/server/paseo-worktree-service.js +94 -0
  132. package/dist/server/server/paseo-worktree-service.js.map +1 -0
  133. package/dist/server/server/path-utils.d.ts +1 -0
  134. package/dist/server/server/path-utils.d.ts.map +1 -1
  135. package/dist/server/server/path-utils.js +9 -0
  136. package/dist/server/server/path-utils.js.map +1 -1
  137. package/dist/server/server/persisted-config.d.ts +73 -73
  138. package/dist/server/server/persistence-hooks.d.ts.map +1 -1
  139. package/dist/server/server/persistence-hooks.js +3 -0
  140. package/dist/server/server/persistence-hooks.js.map +1 -1
  141. package/dist/server/server/resolve-worktree-creation-intent.d.ts +30 -0
  142. package/dist/server/server/resolve-worktree-creation-intent.d.ts.map +1 -0
  143. package/dist/server/server/resolve-worktree-creation-intent.js +163 -0
  144. package/dist/server/server/resolve-worktree-creation-intent.js.map +1 -0
  145. package/dist/server/server/schedule/rpc-schemas.d.ts +192 -192
  146. package/dist/server/server/schedule/service.d.ts +1 -1
  147. package/dist/server/server/schedule/service.d.ts.map +1 -1
  148. package/dist/server/server/schedule/types.d.ts +44 -44
  149. package/dist/server/server/script-health-monitor.d.ts +39 -0
  150. package/dist/server/server/script-health-monitor.d.ts.map +1 -0
  151. package/dist/server/server/script-health-monitor.js +158 -0
  152. package/dist/server/server/script-health-monitor.js.map +1 -0
  153. package/dist/server/server/script-proxy.d.ts +40 -0
  154. package/dist/server/server/script-proxy.d.ts.map +1 -0
  155. package/dist/server/server/script-proxy.js +245 -0
  156. package/dist/server/server/script-proxy.js.map +1 -0
  157. package/dist/server/server/script-route-branch-handler.d.ts +10 -0
  158. package/dist/server/server/script-route-branch-handler.d.ts.map +1 -0
  159. package/dist/server/server/script-route-branch-handler.js +45 -0
  160. package/dist/server/server/script-route-branch-handler.js.map +1 -0
  161. package/dist/server/server/script-status-projection.d.ts +29 -0
  162. package/dist/server/server/script-status-projection.d.ts.map +1 -0
  163. package/dist/server/server/script-status-projection.js +133 -0
  164. package/dist/server/server/script-status-projection.js.map +1 -0
  165. package/dist/server/server/session.d.ts +77 -13
  166. package/dist/server/server/session.d.ts.map +1 -1
  167. package/dist/server/server/session.js +1290 -548
  168. package/dist/server/server/session.js.map +1 -1
  169. package/dist/server/server/websocket-server.d.ts +27 -3
  170. package/dist/server/server/websocket-server.d.ts.map +1 -1
  171. package/dist/server/server/websocket-server.js +112 -29
  172. package/dist/server/server/websocket-server.js.map +1 -1
  173. package/dist/server/server/workspace-archive-service.d.ts +8 -0
  174. package/dist/server/server/workspace-archive-service.d.ts.map +1 -0
  175. package/dist/server/server/workspace-archive-service.js +17 -0
  176. package/dist/server/server/workspace-archive-service.js.map +1 -0
  177. package/dist/server/server/workspace-git-metadata.d.ts +24 -0
  178. package/dist/server/server/workspace-git-metadata.d.ts.map +1 -0
  179. package/dist/server/server/workspace-git-metadata.js +78 -0
  180. package/dist/server/server/workspace-git-metadata.js.map +1 -0
  181. package/dist/server/server/workspace-git-service.d.ts +104 -5
  182. package/dist/server/server/workspace-git-service.d.ts.map +1 -1
  183. package/dist/server/server/workspace-git-service.js +442 -56
  184. package/dist/server/server/workspace-git-service.js.map +1 -1
  185. package/dist/server/server/workspace-reconciliation-service.d.ts +54 -0
  186. package/dist/server/server/workspace-reconciliation-service.d.ts.map +1 -0
  187. package/dist/server/server/workspace-reconciliation-service.js +176 -0
  188. package/dist/server/server/workspace-reconciliation-service.js.map +1 -0
  189. package/dist/server/server/workspace-registry-bootstrap.d.ts.map +1 -1
  190. package/dist/server/server/workspace-registry-bootstrap.js +4 -3
  191. package/dist/server/server/workspace-registry-bootstrap.js.map +1 -1
  192. package/dist/server/server/workspace-registry.d.ts +8 -8
  193. package/dist/server/server/workspace-registry.test-helpers.d.ts +37 -0
  194. package/dist/server/server/workspace-registry.test-helpers.d.ts.map +1 -0
  195. package/dist/server/server/workspace-registry.test-helpers.js +121 -0
  196. package/dist/server/server/workspace-registry.test-helpers.js.map +1 -0
  197. package/dist/server/server/workspace-script-runtime-store.d.ts +28 -0
  198. package/dist/server/server/workspace-script-runtime-store.d.ts.map +1 -0
  199. package/dist/server/server/workspace-script-runtime-store.js +78 -0
  200. package/dist/server/server/workspace-script-runtime-store.js.map +1 -0
  201. package/dist/server/server/workspace-service-env.d.ts +17 -0
  202. package/dist/server/server/workspace-service-env.d.ts.map +1 -0
  203. package/dist/server/server/workspace-service-env.js +80 -0
  204. package/dist/server/server/workspace-service-env.js.map +1 -0
  205. package/dist/server/server/workspace-service-port-registry.d.ts +19 -0
  206. package/dist/server/server/workspace-service-port-registry.d.ts.map +1 -0
  207. package/dist/server/server/workspace-service-port-registry.js +59 -0
  208. package/dist/server/server/workspace-service-port-registry.js.map +1 -0
  209. package/dist/server/server/worktree-bootstrap.d.ts +55 -10
  210. package/dist/server/server/worktree-bootstrap.d.ts.map +1 -1
  211. package/dist/server/server/worktree-bootstrap.js +290 -112
  212. package/dist/server/server/worktree-bootstrap.js.map +1 -1
  213. package/dist/server/server/worktree-core.d.ts +25 -0
  214. package/dist/server/server/worktree-core.d.ts.map +1 -0
  215. package/dist/server/server/worktree-core.js +75 -0
  216. package/dist/server/server/worktree-core.js.map +1 -0
  217. package/dist/server/server/worktree-errors.d.ts +12 -0
  218. package/dist/server/server/worktree-errors.d.ts.map +1 -0
  219. package/dist/server/server/worktree-errors.js +31 -0
  220. package/dist/server/server/worktree-errors.js.map +1 -0
  221. package/dist/server/server/worktree-session.d.ts +56 -70
  222. package/dist/server/server/worktree-session.d.ts.map +1 -1
  223. package/dist/server/server/worktree-session.js +176 -251
  224. package/dist/server/server/worktree-session.js.map +1 -1
  225. package/dist/server/services/github-service.d.ts +225 -0
  226. package/dist/server/services/github-service.d.ts.map +1 -0
  227. package/dist/server/services/github-service.js +1381 -0
  228. package/dist/server/services/github-service.js.map +1 -0
  229. package/dist/server/shared/messages.d.ts +29408 -12268
  230. package/dist/server/shared/messages.d.ts.map +1 -1
  231. package/dist/server/shared/messages.js +391 -65
  232. package/dist/server/shared/messages.js.map +1 -1
  233. package/dist/server/terminal/shell-integration/zsh/.zshenv +17 -0
  234. package/dist/server/terminal/shell-integration/zsh/paseo-integration.zsh +32 -0
  235. package/dist/server/terminal/terminal-manager.d.ts +9 -0
  236. package/dist/server/terminal/terminal-manager.d.ts.map +1 -1
  237. package/dist/server/terminal/terminal-manager.js +27 -0
  238. package/dist/server/terminal/terminal-manager.js.map +1 -1
  239. package/dist/server/terminal/terminal-output-coalescer.d.ts +30 -0
  240. package/dist/server/terminal/terminal-output-coalescer.d.ts.map +1 -0
  241. package/dist/server/terminal/terminal-output-coalescer.js +55 -0
  242. package/dist/server/terminal/terminal-output-coalescer.js.map +1 -0
  243. package/dist/server/terminal/terminal.d.ts +32 -1
  244. package/dist/server/terminal/terminal.d.ts.map +1 -1
  245. package/dist/server/terminal/terminal.js +397 -17
  246. package/dist/server/terminal/terminal.js.map +1 -1
  247. package/dist/server/utils/checkout-git.d.ts +63 -10
  248. package/dist/server/utils/checkout-git.d.ts.map +1 -1
  249. package/dist/server/utils/checkout-git.js +321 -229
  250. package/dist/server/utils/checkout-git.js.map +1 -1
  251. package/dist/server/utils/promise-timeout.d.ts +9 -0
  252. package/dist/server/utils/promise-timeout.d.ts.map +1 -0
  253. package/dist/server/utils/promise-timeout.js +25 -0
  254. package/dist/server/utils/promise-timeout.js.map +1 -0
  255. package/dist/server/utils/script-hostname.d.ts +8 -0
  256. package/dist/server/utils/script-hostname.d.ts.map +1 -0
  257. package/dist/server/utils/script-hostname.js +14 -0
  258. package/dist/server/utils/script-hostname.js.map +1 -0
  259. package/dist/server/utils/string-command-shell.d.ts +10 -0
  260. package/dist/server/utils/string-command-shell.d.ts.map +1 -0
  261. package/dist/server/utils/string-command-shell.js +21 -0
  262. package/dist/server/utils/string-command-shell.js.map +1 -0
  263. package/dist/server/utils/worktree.d.ts +54 -7
  264. package/dist/server/utils/worktree.d.ts.map +1 -1
  265. package/dist/server/utils/worktree.js +434 -129
  266. package/dist/server/utils/worktree.js.map +1 -1
  267. package/dist/src/terminal/shell-integration/zsh/.zshenv +17 -0
  268. package/dist/src/terminal/shell-integration/zsh/paseo-integration.zsh +32 -0
  269. package/package.json +11 -14
  270. package/dist/server/server/agent/providers/pi-acp-agent.d.ts +0 -28
  271. package/dist/server/server/agent/providers/pi-acp-agent.d.ts.map +0 -1
  272. package/dist/server/server/agent/providers/pi-acp-agent.js +0 -302
  273. package/dist/server/server/agent/providers/pi-acp-agent.js.map +0 -1
@@ -1,14 +1,22 @@
1
1
  import { watch } from "node:fs";
2
2
  import { readFile, readdir } from "node:fs/promises";
3
3
  import { join, resolve } from "node:path";
4
- import { getCheckoutShortstat, getCheckoutStatus, getPullRequestStatus, hasOriginRemote, resolveGhPath, resolveAbsoluteGitDir, } from "../utils/checkout-git.js";
4
+ import { getCheckoutDiff, getCheckoutShortstat, getCheckoutStatus, getPullRequestStatus, hasOriginRemote, listBranchSuggestions, resolveRepositoryDefaultBranch, resolveBranchCheckout, resolveAbsoluteGitDir, } from "../utils/checkout-git.js";
5
+ import { createGitHubService } from "../services/github-service.js";
5
6
  import { parseGitRevParsePath } from "../utils/git-rev-parse-path.js";
6
7
  import { runGitCommand } from "../utils/run-git-command.js";
8
+ import { listPaseoWorktrees } from "../utils/worktree.js";
7
9
  import { READ_ONLY_GIT_ENV } from "./checkout-git-utils.js";
10
+ import { buildWorkspaceGitMetadataFromSnapshot, } from "./workspace-git-metadata.js";
8
11
  import { normalizeWorkspaceId } from "./workspace-registry-model.js";
9
12
  const WORKSPACE_GIT_WATCH_DEBOUNCE_MS = 500;
10
13
  const BACKGROUND_GIT_FETCH_INTERVAL_MS = 180000;
14
+ export const WORKSPACE_GIT_SELF_HEAL_INTERVAL_MS = 60000;
11
15
  const WORKING_TREE_WATCH_FALLBACK_REFRESH_MS = 5000;
16
+ // Consumer reads may reuse cached values within this window; older peeks cold-load through the service.
17
+ const WORKSPACE_GIT_CONSUMER_TTL_MS = 15000;
18
+ // Non-forced refresh triggers share this minimum gap to absorb watcher/self-heal bursts; force bypasses it.
19
+ const WORKSPACE_GIT_INTERNAL_MIN_GAP_MS = 2000;
12
20
  export class WorkspaceGitServiceImpl {
13
21
  constructor(options) {
14
22
  this.workspaceTargets = new Map();
@@ -16,6 +24,13 @@ export class WorkspaceGitServiceImpl {
16
24
  this.workspaceTargetSetups = new Map();
17
25
  this.workingTreeWatchTargets = new Map();
18
26
  this.workingTreeWatchSetups = new Map();
27
+ this.branchValidationCache = new Map();
28
+ this.localBranchCache = new Map();
29
+ this.branchSuggestionsCache = new Map();
30
+ this.stashListCache = new Map();
31
+ this.worktreeListCache = new Map();
32
+ this.defaultBranchCache = new Map();
33
+ this.checkoutDiffCache = new Map();
19
34
  this.logger = options.logger.child({ module: "workspace-git-service" });
20
35
  this.paseoHome = options.paseoHome;
21
36
  this.deps = {
@@ -23,8 +38,13 @@ export class WorkspaceGitServiceImpl {
23
38
  readdir: options.deps?.readdir ?? readdir,
24
39
  getCheckoutStatus: options.deps?.getCheckoutStatus ?? getCheckoutStatus,
25
40
  getCheckoutShortstat: options.deps?.getCheckoutShortstat ?? getCheckoutShortstat,
41
+ getCheckoutDiff: options.deps?.getCheckoutDiff ?? getCheckoutDiff,
26
42
  getPullRequestStatus: options.deps?.getPullRequestStatus ?? getPullRequestStatus,
27
- resolveGhPath: options.deps?.resolveGhPath ?? resolveGhPath,
43
+ resolveBranchCheckout: options.deps?.resolveBranchCheckout ?? resolveBranchCheckout,
44
+ resolveRepositoryDefaultBranch: options.deps?.resolveRepositoryDefaultBranch ?? resolveRepositoryDefaultBranch,
45
+ listBranchSuggestions: options.deps?.listBranchSuggestions ?? listBranchSuggestions,
46
+ listPaseoWorktrees: options.deps?.listPaseoWorktrees ?? listPaseoWorktrees,
47
+ github: options.deps?.github ?? createGitHubService(),
28
48
  resolveAbsoluteGitDir: options.deps?.resolveAbsoluteGitDir ?? resolveAbsoluteGitDir,
29
49
  hasOriginRemote: options.deps?.hasOriginRemote ?? hasOriginRemote,
30
50
  runGitFetch: options.deps?.runGitFetch ?? runGitFetch,
@@ -36,6 +56,9 @@ export class WorkspaceGitServiceImpl {
36
56
  const cwd = normalizeWorkspaceId(params.cwd);
37
57
  const target = await this.ensureWorkspaceTarget(cwd);
38
58
  target.listeners.add(listener);
59
+ if (target.listeners.size === 1) {
60
+ this.startWorkspaceSubscriptionTimers(target);
61
+ }
39
62
  return {
40
63
  initial: target.latestSnapshot ?? (await this.getSnapshot(cwd)),
41
64
  unsubscribe: () => {
@@ -43,24 +66,143 @@ export class WorkspaceGitServiceImpl {
43
66
  },
44
67
  };
45
68
  }
46
- async getSnapshot(cwd) {
69
+ async getSnapshot(cwd, options) {
47
70
  cwd = normalizeWorkspaceId(cwd);
48
- const target = this.workspaceTargets.get(cwd);
49
- if (target?.latestSnapshot) {
71
+ const request = this.normalizeRefreshRequest(options, "getSnapshot", true);
72
+ const target = await this.ensureWorkspaceTarget(cwd);
73
+ if (!request.force && this.isSnapshotWarm(target)) {
50
74
  return target.latestSnapshot;
51
75
  }
52
- const ensuredTarget = await this.ensureWorkspaceTarget(cwd);
53
- return ensuredTarget.latestSnapshot ?? (await this.refreshSnapshot(cwd));
76
+ return this.requestWorkspaceSnapshot(target, request);
54
77
  }
55
78
  peekSnapshot(cwd) {
56
79
  cwd = normalizeWorkspaceId(cwd);
57
80
  return this.workspaceTargets.get(cwd)?.latestSnapshot ?? null;
58
81
  }
82
+ getCheckoutDiff(cwd, options, readOptions) {
83
+ const normalizedCwd = normalizeWorkspaceId(cwd);
84
+ const normalizedOptions = this.normalizeCheckoutDiffOptions(options);
85
+ const key = this.buildCheckoutDiffCacheKey(normalizedCwd, normalizedOptions);
86
+ return this.readAuxiliaryCache(this.checkoutDiffCache, key, readOptions, () => this.deps.getCheckoutDiff(normalizedCwd, normalizedOptions, { paseoHome: this.paseoHome }));
87
+ }
88
+ normalizeCheckoutDiffOptions(options) {
89
+ return {
90
+ mode: options.mode,
91
+ ...(options.mode === "base" && options.baseRef !== undefined
92
+ ? { baseRef: options.baseRef }
93
+ : {}),
94
+ ...(options.ignoreWhitespace === true ? { ignoreWhitespace: true } : {}),
95
+ ...(options.includeStructured === true ? { includeStructured: true } : {}),
96
+ };
97
+ }
98
+ buildCheckoutDiffCacheKey(cwd, options) {
99
+ // Diff content varies by compare signature. Keep the cache per exact diff read shape so
100
+ // hot diff panes coalesce while base refs and rendering options never share stale patches.
101
+ return JSON.stringify([
102
+ "checkout-diff",
103
+ cwd,
104
+ options.mode,
105
+ options.mode === "base" ? (options.baseRef ?? null) : null,
106
+ options.ignoreWhitespace === true,
107
+ options.includeStructured === true,
108
+ ]);
109
+ }
110
+ validateBranchRef(cwd, ref, options) {
111
+ const normalizedCwd = normalizeWorkspaceId(cwd);
112
+ const normalizedRef = ref.trim();
113
+ const key = JSON.stringify(["branch-validation", normalizedCwd, normalizedRef]);
114
+ return this.readAuxiliaryCache(this.branchValidationCache, key, options, () => this.deps.resolveBranchCheckout(normalizedCwd, normalizedRef));
115
+ }
116
+ hasLocalBranch(cwd, branch, options) {
117
+ const normalizedCwd = normalizeWorkspaceId(cwd);
118
+ const normalizedBranch = branch.trim();
119
+ const ref = `refs/heads/${normalizedBranch}`;
120
+ const key = JSON.stringify(["local-branch", normalizedCwd, ref]);
121
+ return this.readAuxiliaryCache(this.localBranchCache, key, options, async () => {
122
+ const result = await this.deps.runGitCommand(["rev-parse", "--verify", "--quiet", ref], {
123
+ cwd: normalizedCwd,
124
+ env: READ_ONLY_GIT_ENV,
125
+ acceptExitCodes: [0, 1],
126
+ });
127
+ return result.exitCode === 0;
128
+ });
129
+ }
130
+ suggestBranchesForCwd(cwd, options, readOptions) {
131
+ const normalizedCwd = normalizeWorkspaceId(cwd);
132
+ const query = options?.query ?? "";
133
+ const limit = options?.limit;
134
+ const key = JSON.stringify(["branch-suggestions", normalizedCwd, query, limit ?? null]);
135
+ return this.readAuxiliaryCache(this.branchSuggestionsCache, key, readOptions, () => this.deps.listBranchSuggestions(normalizedCwd, options));
136
+ }
137
+ listStashes(cwd, options, readOptions) {
138
+ const normalizedCwd = normalizeWorkspaceId(cwd);
139
+ const paseoOnly = options?.paseoOnly !== false;
140
+ const key = JSON.stringify(["stashes", normalizedCwd, paseoOnly]);
141
+ return this.readAuxiliaryCache(this.stashListCache, key, readOptions, async () => {
142
+ const { stdout } = await this.deps.runGitCommand(["stash", "list", "--format=%gd%x00%s"], {
143
+ cwd: normalizedCwd,
144
+ env: READ_ONLY_GIT_ENV,
145
+ });
146
+ return parseWorkspaceGitStashList(stdout, { paseoOnly });
147
+ });
148
+ }
149
+ async listWorktrees(cwdOrRepoRoot, options) {
150
+ const repoRoot = await this.resolveRepoRoot(cwdOrRepoRoot, options);
151
+ const key = JSON.stringify(["worktrees", repoRoot]);
152
+ return this.readAuxiliaryCache(this.worktreeListCache, key, options, () => this.deps.listPaseoWorktrees({
153
+ cwd: repoRoot,
154
+ paseoHome: this.paseoHome,
155
+ }));
156
+ }
157
+ async resolveRepoRoot(cwd, options) {
158
+ const snapshot = await this.getSnapshot(cwd, options);
159
+ if (!snapshot.git.isGit) {
160
+ throw new Error("Create worktree requires a git repository");
161
+ }
162
+ return snapshot.git.isPaseoOwnedWorktree
163
+ ? (snapshot.git.mainRepoRoot ?? snapshot.git.repoRoot ?? normalizeWorkspaceId(cwd))
164
+ : (snapshot.git.repoRoot ?? normalizeWorkspaceId(cwd));
165
+ }
166
+ async resolveDefaultBranch(cwdOrRepoRoot, options) {
167
+ const cwd = normalizeWorkspaceId(cwdOrRepoRoot);
168
+ const key = JSON.stringify(["default-branch", cwd]);
169
+ return this.readAuxiliaryCache(this.defaultBranchCache, key, options, async () => {
170
+ const defaultBranch = await this.deps.resolveRepositoryDefaultBranch(cwd);
171
+ if (!defaultBranch) {
172
+ throw new Error("Unable to resolve repository default branch");
173
+ }
174
+ return defaultBranch;
175
+ });
176
+ }
177
+ async getWorkspaceGitMetadata(cwd, options) {
178
+ const snapshot = await this.getSnapshot(cwd, options);
179
+ const directoryName = options?.directoryName ??
180
+ normalizeWorkspaceId(cwd).split(/[\\/]/).filter(Boolean).at(-1) ??
181
+ cwd;
182
+ return buildWorkspaceGitMetadataFromSnapshot({
183
+ cwd: normalizeWorkspaceId(cwd),
184
+ directoryName,
185
+ isGit: snapshot.git.isGit,
186
+ repoRoot: snapshot.git.repoRoot,
187
+ mainRepoRoot: snapshot.git.mainRepoRoot,
188
+ currentBranch: snapshot.git.currentBranch,
189
+ remoteUrl: snapshot.git.remoteUrl,
190
+ });
191
+ }
192
+ async resolveRepoRemoteUrl(cwd, options) {
193
+ const snapshot = await this.getSnapshot(cwd, options);
194
+ return snapshot.git.remoteUrl;
195
+ }
59
196
  async refresh(cwd, _options) {
60
197
  cwd = normalizeWorkspaceId(cwd);
61
198
  const target = this.workspaceTargets.get(cwd);
62
199
  if (target) {
63
- await this.refreshWorkspaceTarget(target);
200
+ await this.refreshWorkspaceTarget(target, {
201
+ force: false,
202
+ includeGitHub: false,
203
+ reason: "refresh",
204
+ notify: true,
205
+ });
64
206
  return;
65
207
  }
66
208
  await this.ensureWorkspaceTarget(cwd);
@@ -114,6 +256,51 @@ export class WorkspaceGitServiceImpl {
114
256
  this.workspaceTargetSetups.set(cwd, setup);
115
257
  return setup;
116
258
  }
259
+ readAuxiliaryCache(cache, key, options, load) {
260
+ if (options?.force && !options.reason) {
261
+ throw new Error("WorkspaceGitService forced read requires a reason");
262
+ }
263
+ const entry = this.ensureAuxiliaryCacheEntry(cache, key);
264
+ const nowMs = this.deps.now().getTime();
265
+ if (!options?.force && entry.value !== null && entry.loadedAtMs !== null) {
266
+ const ageMs = nowMs - entry.loadedAtMs;
267
+ if (ageMs <= WORKSPACE_GIT_CONSUMER_TTL_MS) {
268
+ return Promise.resolve(entry.value);
269
+ }
270
+ if (entry.lastShellOutAtMs !== null &&
271
+ nowMs - entry.lastShellOutAtMs < WORKSPACE_GIT_INTERNAL_MIN_GAP_MS) {
272
+ return Promise.resolve(entry.value);
273
+ }
274
+ }
275
+ if (entry.inFlight) {
276
+ return entry.inFlight;
277
+ }
278
+ entry.lastShellOutAtMs = nowMs;
279
+ entry.inFlight = load()
280
+ .then((value) => {
281
+ entry.value = value;
282
+ entry.loadedAtMs = this.deps.now().getTime();
283
+ return value;
284
+ })
285
+ .finally(() => {
286
+ entry.inFlight = null;
287
+ });
288
+ return entry.inFlight;
289
+ }
290
+ ensureAuxiliaryCacheEntry(cache, key) {
291
+ const existing = cache.get(key);
292
+ if (existing) {
293
+ return existing;
294
+ }
295
+ const entry = {
296
+ value: null,
297
+ loadedAtMs: null,
298
+ lastShellOutAtMs: null,
299
+ inFlight: null,
300
+ };
301
+ cache.set(key, entry);
302
+ return entry;
303
+ }
117
304
  async ensureWorkingTreeWatchTarget(cwd) {
118
305
  const existingTarget = this.workingTreeWatchTargets.get(cwd);
119
306
  if (existingTarget) {
@@ -135,24 +322,39 @@ export class WorkspaceGitServiceImpl {
135
322
  listeners: new Set(),
136
323
  watchers: [],
137
324
  debounceTimer: null,
138
- refreshPromise: null,
139
- refreshQueued: false,
325
+ selfHealTimer: null,
326
+ githubPollSubscription: null,
327
+ githubPollHeadRef: null,
328
+ refreshState: { status: "idle" },
140
329
  latestSnapshot: null,
330
+ latestSnapshotLoadedAtMs: null,
141
331
  latestFingerprint: null,
332
+ lastShellOutAtMs: null,
142
333
  repoGitRoot: null,
143
334
  };
144
- const initial = await this.refreshSnapshot(cwd);
145
- this.rememberSnapshot(target, initial);
146
335
  this.workspaceTargets.set(cwd, target);
147
- const gitDir = await this.deps.resolveAbsoluteGitDir(cwd);
148
- if (!gitDir) {
336
+ try {
337
+ await this.requestWorkspaceSnapshot(target, {
338
+ force: false,
339
+ includeGitHub: true,
340
+ reason: "initial",
341
+ notify: false,
342
+ });
343
+ const gitDir = await this.deps.resolveAbsoluteGitDir(cwd);
344
+ if (!gitDir) {
345
+ return target;
346
+ }
347
+ const repoGitRoot = await this.resolveWorkspaceGitRefsRoot(gitDir);
348
+ target.repoGitRoot = repoGitRoot;
349
+ this.startWorkspaceWatchers(target, gitDir, repoGitRoot);
350
+ await this.ensureRepoTarget(target);
149
351
  return target;
150
352
  }
151
- const repoGitRoot = await this.resolveWorkspaceGitRefsRoot(gitDir);
152
- target.repoGitRoot = repoGitRoot;
153
- this.startWorkspaceWatchers(target, gitDir, repoGitRoot);
154
- await this.ensureRepoTarget(target);
155
- return target;
353
+ catch (error) {
354
+ this.closeWorkspaceTarget(target);
355
+ this.workspaceTargets.delete(cwd);
356
+ throw error;
357
+ }
156
358
  }
157
359
  async createWorkingTreeWatchTarget(cwd) {
158
360
  const repoRoot = await this.resolveCheckoutWatchRoot(cwd);
@@ -192,7 +394,10 @@ export class WorkspaceGitServiceImpl {
192
394
  const missingRepoCoverage = repoRoot === null || !hasRecursiveRepoCoverage;
193
395
  if (target.watchers.length === 0 || missingRepoCoverage) {
194
396
  target.fallbackRefreshInterval = setInterval(() => {
195
- this.scheduleWorkspaceRefresh(cwd);
397
+ this.scheduleWorkspaceRefresh(cwd, {
398
+ force: true,
399
+ reason: "working-tree-watch-fallback",
400
+ });
196
401
  for (const listener of target.listeners) {
197
402
  listener();
198
403
  }
@@ -281,7 +486,7 @@ export class WorkspaceGitServiceImpl {
281
486
  this.repoTargets.set(repoGitRoot, repoTarget);
282
487
  void this.runRepoFetch(repoTarget);
283
488
  }
284
- scheduleWorkspaceRefresh(targetOrCwd) {
489
+ scheduleWorkspaceRefresh(targetOrCwd, options) {
285
490
  const target = typeof targetOrCwd === "string"
286
491
  ? this.workspaceTargets.get(normalizeWorkspaceId(targetOrCwd))
287
492
  : targetOrCwd;
@@ -293,9 +498,65 @@ export class WorkspaceGitServiceImpl {
293
498
  }
294
499
  target.debounceTimer = setTimeout(() => {
295
500
  target.debounceTimer = null;
296
- void this.refreshWorkspaceTarget(target);
501
+ void this.refreshWorkspaceTarget(target, {
502
+ force: options?.force === true,
503
+ includeGitHub: false,
504
+ reason: options?.reason ?? "watch",
505
+ notify: true,
506
+ });
297
507
  }, WORKSPACE_GIT_WATCH_DEBOUNCE_MS);
298
508
  }
509
+ startWorkspaceSubscriptionTimers(target) {
510
+ if (!target.selfHealTimer) {
511
+ target.selfHealTimer = setInterval(() => {
512
+ this.getSnapshot(target.cwd, { reason: "self-heal-git" }).catch((error) => {
513
+ this.logger.warn({ err: error, cwd: target.cwd, reason: "self-heal-git" }, "Failed to run workspace git self-heal refresh");
514
+ });
515
+ }, WORKSPACE_GIT_SELF_HEAL_INTERVAL_MS);
516
+ }
517
+ this.updateGitHubPollForTarget(target);
518
+ }
519
+ updateGitHubPollForTarget(target) {
520
+ if (target.listeners.size === 0) {
521
+ this.stopGitHubPollForTarget(target);
522
+ return;
523
+ }
524
+ const snapshot = target.latestSnapshot;
525
+ if (!snapshot || !this.deps.github.retainCurrentPullRequestStatusPoll) {
526
+ this.stopGitHubPollForTarget(target);
527
+ return;
528
+ }
529
+ const headRef = snapshot.git.currentBranch;
530
+ if (!headRef || !hasGitHubRemoteUrl(snapshot.git.remoteUrl)) {
531
+ this.stopGitHubPollForTarget(target);
532
+ return;
533
+ }
534
+ if (target.githubPollHeadRef === headRef && target.githubPollSubscription) {
535
+ return;
536
+ }
537
+ this.stopGitHubPollForTarget(target);
538
+ target.githubPollHeadRef = headRef;
539
+ target.githubPollSubscription = this.deps.github.retainCurrentPullRequestStatusPoll({
540
+ cwd: target.cwd,
541
+ headRef,
542
+ onStatus: () => {
543
+ void this.refreshWorkspaceTarget(target, {
544
+ force: false,
545
+ includeGitHub: true,
546
+ reason: "self-heal-github",
547
+ notify: true,
548
+ });
549
+ },
550
+ onError: (error) => {
551
+ this.logger.warn({ err: error, cwd: target.cwd, headRef, reason: "self-heal-github" }, "Failed to run GitHub self-heal refresh");
552
+ },
553
+ });
554
+ }
555
+ stopGitHubPollForTarget(target) {
556
+ target.githubPollSubscription?.unsubscribe();
557
+ target.githubPollSubscription = null;
558
+ target.githubPollHeadRef = null;
559
+ }
299
560
  addWorkingTreeWatcher(target, watchPath, shouldTryRecursive) {
300
561
  if (target.watchedPaths.has(watchPath)) {
301
562
  return false;
@@ -305,7 +566,10 @@ export class WorkspaceGitServiceImpl {
305
566
  if (process.platform === "linux" && target.repoWatchPath) {
306
567
  void this.refreshLinuxRepoTreeWatchers(target);
307
568
  }
308
- this.scheduleWorkspaceRefresh(cwd);
569
+ this.scheduleWorkspaceRefresh(cwd, {
570
+ force: true,
571
+ reason: "working-tree-watch",
572
+ });
309
573
  for (const listener of target.listeners) {
310
574
  listener();
311
575
  }
@@ -415,37 +679,119 @@ export class WorkspaceGitServiceImpl {
415
679
  }
416
680
  return directories;
417
681
  }
418
- async refreshWorkspaceTarget(target) {
419
- if (target.refreshPromise) {
420
- target.refreshQueued = true;
421
- return;
422
- }
423
- target.refreshPromise = (async () => {
424
- do {
425
- target.refreshQueued = false;
426
- try {
427
- const snapshot = await this.refreshSnapshot(target.cwd);
428
- this.rememberSnapshot(target, snapshot, { notify: true });
429
- }
430
- catch (error) {
431
- this.logger.warn({ err: error, cwd: target.cwd }, "Failed to refresh workspace git snapshot");
432
- }
433
- } while (target.refreshQueued);
434
- })();
682
+ async refreshWorkspaceTarget(target, request) {
435
683
  try {
436
- await target.refreshPromise;
684
+ await this.requestWorkspaceSnapshot(target, request);
437
685
  }
438
- finally {
439
- target.refreshPromise = null;
686
+ catch (error) {
687
+ this.logger.warn({ err: error, cwd: target.cwd, reason: request.reason }, "Failed to refresh workspace git snapshot");
688
+ }
689
+ }
690
+ requestWorkspaceSnapshot(target, request) {
691
+ if (target.refreshState.status === "in-flight") {
692
+ if (request.force && !target.refreshState.force) {
693
+ target.refreshState.queued = this.mergeQueuedRefresh(target.refreshState.queued, request);
694
+ }
695
+ return target.refreshState.promise;
696
+ }
697
+ if (!request.force && this.shouldThrottleNonForcedRefresh(target)) {
698
+ return Promise.resolve(target.latestSnapshot);
699
+ }
700
+ const promise = this.runWorkspaceRefreshLoop(target, request).finally(() => {
701
+ const state = target.refreshState;
702
+ if (state.status === "in-flight" && state.promise === promise) {
703
+ target.refreshState = { status: "idle" };
704
+ }
705
+ });
706
+ target.refreshState = {
707
+ status: "in-flight",
708
+ promise,
709
+ force: request.force,
710
+ includeGitHub: request.includeGitHub,
711
+ queued: null,
712
+ };
713
+ return promise;
714
+ }
715
+ normalizeRefreshRequest(options, defaultReason, notify) {
716
+ if (options?.force && !options.reason) {
717
+ throw new Error("WorkspaceGitService.getSnapshot force refresh requires a reason");
718
+ }
719
+ const force = options?.force === true;
720
+ return {
721
+ force,
722
+ includeGitHub: options?.includeGitHub ?? force,
723
+ reason: options?.reason ?? defaultReason,
724
+ notify,
725
+ };
726
+ }
727
+ isSnapshotWarm(target) {
728
+ if (!target.latestSnapshot || target.latestSnapshotLoadedAtMs === null) {
729
+ return false;
730
+ }
731
+ return (this.deps.now().getTime() - target.latestSnapshotLoadedAtMs <= WORKSPACE_GIT_CONSUMER_TTL_MS);
732
+ }
733
+ shouldThrottleNonForcedRefresh(target) {
734
+ if (!target.latestSnapshot || target.lastShellOutAtMs === null) {
735
+ return false;
736
+ }
737
+ return this.deps.now().getTime() - target.lastShellOutAtMs < WORKSPACE_GIT_INTERNAL_MIN_GAP_MS;
738
+ }
739
+ mergeQueuedRefresh(queued, request) {
740
+ if (!queued) {
741
+ return {
742
+ force: request.force,
743
+ includeGitHub: request.includeGitHub,
744
+ reason: request.reason,
745
+ notify: request.notify,
746
+ };
747
+ }
748
+ const force = queued.force || request.force;
749
+ return {
750
+ force,
751
+ includeGitHub: queued.includeGitHub || request.includeGitHub,
752
+ reason: request.force && !queued.force ? request.reason : queued.reason,
753
+ notify: queued.notify || request.notify,
754
+ };
755
+ }
756
+ async runWorkspaceRefreshLoop(target, initialRequest) {
757
+ let request = initialRequest;
758
+ let snapshot;
759
+ while (true) {
760
+ snapshot = await this.refreshSnapshot(target, request);
761
+ this.rememberSnapshot(target, snapshot, {
762
+ notify: request.notify,
763
+ forceEmit: request.force,
764
+ });
765
+ const state = target.refreshState;
766
+ if (state.status !== "in-flight" || !state.queued) {
767
+ break;
768
+ }
769
+ request = state.queued;
770
+ state.queued = null;
771
+ state.force = request.force;
772
+ state.includeGitHub = request.includeGitHub;
440
773
  }
774
+ return snapshot;
441
775
  }
442
- async refreshSnapshot(cwd) {
443
- return loadWorkspaceGitRuntimeSnapshot(cwd, { paseoHome: this.paseoHome }, this.deps.now(), this.deps);
776
+ async refreshSnapshot(target, request) {
777
+ const now = this.deps.now();
778
+ target.lastShellOutAtMs = now.getTime();
779
+ const forceGitHub = request.force && request.includeGitHub;
780
+ if (forceGitHub) {
781
+ this.deps.github.invalidate({ cwd: target.cwd });
782
+ }
783
+ const snapshot = await loadWorkspaceGitRuntimeSnapshot(target.cwd, { paseoHome: this.paseoHome }, now, this.deps, { force: request.force, forceGitHub, reason: request.reason });
784
+ target.latestSnapshotLoadedAtMs = now.getTime();
785
+ return snapshot;
444
786
  }
445
787
  rememberSnapshot(target, snapshot, options) {
446
788
  target.latestSnapshot = snapshot;
789
+ if (target.listeners.size > 0) {
790
+ this.updateGitHubPollForTarget(target);
791
+ }
447
792
  const fingerprint = JSON.stringify(snapshot);
448
- if (target.latestFingerprint === fingerprint) {
793
+ const fingerprintMatches = target.latestFingerprint === fingerprint;
794
+ if (fingerprintMatches && !options?.forceEmit) {
449
795
  return;
450
796
  }
451
797
  target.latestFingerprint = fingerprint;
@@ -475,7 +821,12 @@ export class WorkspaceGitServiceImpl {
475
821
  if (!workspaceTarget) {
476
822
  return;
477
823
  }
478
- await this.refreshWorkspaceTarget(workspaceTarget);
824
+ await this.refreshWorkspaceTarget(workspaceTarget, {
825
+ force: false,
826
+ includeGitHub: false,
827
+ reason: "repo-fetch",
828
+ notify: true,
829
+ });
479
830
  }));
480
831
  }
481
832
  }
@@ -519,6 +870,11 @@ export class WorkspaceGitServiceImpl {
519
870
  clearTimeout(target.debounceTimer);
520
871
  target.debounceTimer = null;
521
872
  }
873
+ if (target.selfHealTimer) {
874
+ clearInterval(target.selfHealTimer);
875
+ target.selfHealTimer = null;
876
+ }
877
+ this.stopGitHubPollForTarget(target);
522
878
  for (const watcher of target.watchers) {
523
879
  watcher.close();
524
880
  }
@@ -545,18 +901,20 @@ export class WorkspaceGitServiceImpl {
545
901
  target.workspaceKeys.clear();
546
902
  }
547
903
  }
548
- async function loadWorkspaceGitRuntimeSnapshot(cwd, context, now, deps) {
904
+ async function loadWorkspaceGitRuntimeSnapshot(cwd, context, now, deps, options) {
549
905
  const checkoutStatus = await deps.getCheckoutStatus(cwd, context);
550
906
  if (!checkoutStatus.isGit) {
551
907
  return buildNotGitSnapshot(cwd);
552
908
  }
553
909
  const [diffStat, github] = await Promise.all([
554
- deps.getCheckoutShortstat(cwd, context),
910
+ deps.getCheckoutShortstat(cwd, context, { force: options?.force }),
555
911
  loadGitHubSnapshot({
556
912
  cwd,
557
913
  remoteUrl: checkoutStatus.remoteUrl,
558
914
  now,
559
915
  deps,
916
+ force: options?.forceGitHub,
917
+ reason: options?.reason,
560
918
  }),
561
919
  ]);
562
920
  return {
@@ -569,9 +927,11 @@ async function loadWorkspaceGitRuntimeSnapshot(cwd, context, now, deps) {
569
927
  remoteUrl: checkoutStatus.remoteUrl,
570
928
  isPaseoOwnedWorktree: checkoutStatus.isPaseoOwnedWorktree,
571
929
  isDirty: checkoutStatus.isDirty,
930
+ baseRef: checkoutStatus.baseRef,
572
931
  aheadBehind: checkoutStatus.aheadBehind,
573
932
  aheadOfOrigin: checkoutStatus.aheadOfOrigin,
574
933
  behindOfOrigin: checkoutStatus.behindOfOrigin,
934
+ hasRemote: checkoutStatus.hasRemote,
575
935
  diffStat,
576
936
  },
577
937
  github,
@@ -583,27 +943,27 @@ async function loadGitHubSnapshot(options) {
583
943
  featuresEnabled: false,
584
944
  pullRequest: null,
585
945
  error: null,
586
- refreshedAt: null,
587
946
  };
588
947
  }
589
948
  try {
590
- await options.deps.resolveGhPath();
949
+ await options.deps.github.isAuthenticated({ cwd: options.cwd });
591
950
  }
592
951
  catch {
593
952
  return {
594
953
  featuresEnabled: false,
595
954
  pullRequest: null,
596
955
  error: null,
597
- refreshedAt: null,
598
956
  };
599
957
  }
600
958
  try {
601
- const result = await options.deps.getPullRequestStatus(options.cwd);
959
+ const result = await options.deps.getPullRequestStatus(options.cwd, options.deps.github, {
960
+ force: options.force,
961
+ reason: options.reason,
962
+ });
602
963
  return {
603
964
  featuresEnabled: true,
604
965
  pullRequest: result.status,
605
966
  error: null,
606
- refreshedAt: options.now.toISOString(),
607
967
  };
608
968
  }
609
969
  catch (error) {
@@ -613,7 +973,6 @@ async function loadGitHubSnapshot(options) {
613
973
  error: {
614
974
  message: error instanceof Error ? error.message : String(error),
615
975
  },
616
- refreshedAt: options.now.toISOString(),
617
976
  };
618
977
  }
619
978
  }
@@ -625,6 +984,32 @@ function hasGitHubRemoteUrl(remoteUrl) {
625
984
  remoteUrl.startsWith("git@github.com:") ||
626
985
  remoteUrl.startsWith("ssh://git@github.com/"));
627
986
  }
987
+ function parseWorkspaceGitStashList(stdout, options) {
988
+ const entries = [];
989
+ const lines = stdout.trim().split("\n").filter(Boolean);
990
+ for (const line of lines) {
991
+ const sepIdx = line.indexOf("\0");
992
+ if (sepIdx < 0) {
993
+ continue;
994
+ }
995
+ const refPart = line.slice(0, sepIdx);
996
+ const subject = line.slice(sepIdx + 1);
997
+ const indexMatch = refPart.match(/\{(\d+)\}/);
998
+ if (!indexMatch) {
999
+ continue;
1000
+ }
1001
+ const index = Number(indexMatch[1]);
1002
+ const prefix = "paseo-auto-stash:";
1003
+ const prefixIdx = subject.indexOf(prefix);
1004
+ const isPaseo = prefixIdx >= 0;
1005
+ const branch = isPaseo ? subject.slice(prefixIdx + prefix.length).trim() || null : null;
1006
+ if (options.paseoOnly && !isPaseo) {
1007
+ continue;
1008
+ }
1009
+ entries.push({ index, message: subject, branch, isPaseo });
1010
+ }
1011
+ return entries;
1012
+ }
628
1013
  function buildNotGitSnapshot(cwd) {
629
1014
  return {
630
1015
  cwd,
@@ -636,16 +1021,17 @@ function buildNotGitSnapshot(cwd) {
636
1021
  remoteUrl: null,
637
1022
  isPaseoOwnedWorktree: false,
638
1023
  isDirty: null,
1024
+ baseRef: null,
639
1025
  aheadBehind: null,
640
1026
  aheadOfOrigin: null,
641
1027
  behindOfOrigin: null,
1028
+ hasRemote: false,
642
1029
  diffStat: null,
643
1030
  },
644
1031
  github: {
645
1032
  featuresEnabled: false,
646
1033
  pullRequest: null,
647
1034
  error: null,
648
- refreshedAt: null,
649
1035
  },
650
1036
  };
651
1037
  }