@getpaseo/server 0.1.98 → 0.1.100

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 (59) hide show
  1. package/dist/server/server/agent/agent-manager.js +2 -2
  2. package/dist/server/server/agent/agent-sdk-types.d.ts +11 -6
  3. package/dist/server/server/agent/provider-registry.d.ts +6 -3
  4. package/dist/server/server/agent/provider-registry.js +49 -22
  5. package/dist/server/server/agent/provider-snapshot-manager.js +26 -14
  6. package/dist/server/server/agent/providers/acp-agent.d.ts +23 -3
  7. package/dist/server/server/agent/providers/acp-agent.js +139 -9
  8. package/dist/server/server/agent/providers/claude/agent.d.ts +2 -2
  9. package/dist/server/server/agent/providers/claude/agent.js +41 -77
  10. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +3 -2
  11. package/dist/server/server/agent/providers/codex-app-server-agent.js +6 -25
  12. package/dist/server/server/agent/providers/copilot-acp-agent.d.ts +2 -1
  13. package/dist/server/server/agent/providers/copilot-acp-agent.js +11 -31
  14. package/dist/server/server/agent/providers/generic-acp-agent.d.ts +0 -1
  15. package/dist/server/server/agent/providers/generic-acp-agent.js +2 -108
  16. package/dist/server/server/agent/providers/mock-load-test-agent.d.ts +2 -3
  17. package/dist/server/server/agent/providers/mock-load-test-agent.js +5 -5
  18. package/dist/server/server/agent/providers/mock-slow-provider.d.ts +2 -3
  19. package/dist/server/server/agent/providers/mock-slow-provider.js +2 -5
  20. package/dist/server/server/agent/providers/opencode/server-manager.d.ts +14 -11
  21. package/dist/server/server/agent/providers/opencode/server-manager.js +149 -91
  22. package/dist/server/server/agent/providers/opencode/test-server-manager.d.ts +6 -5
  23. package/dist/server/server/agent/providers/opencode/test-server-manager.js +13 -3
  24. package/dist/server/server/agent/providers/opencode/test-utils/{test-opencode-runtime.d.ts → test-opencode-harness.d.ts} +11 -11
  25. package/dist/server/server/agent/providers/opencode/test-utils/{test-opencode-runtime.js → test-opencode-harness.js} +23 -10
  26. package/dist/server/server/agent/providers/opencode-agent.d.ts +13 -6
  27. package/dist/server/server/agent/providers/opencode-agent.js +74 -137
  28. package/dist/server/server/agent/providers/pi/agent.d.ts +4 -4
  29. package/dist/server/server/agent/providers/pi/agent.js +13 -76
  30. package/dist/server/server/agent/providers/pi/cli-runtime.d.ts +3 -0
  31. package/dist/server/server/agent/providers/pi/cli-runtime.js +8 -5
  32. package/dist/server/server/agent/providers/pi/rpc-types.d.ts +2 -1
  33. package/dist/server/server/agent/providers/pi/runtime.d.ts +1 -1
  34. package/dist/server/server/agent/providers/pi/test-utils/fake-pi.d.ts +1 -1
  35. package/dist/server/server/agent/providers/pi/test-utils/fake-pi.js +1 -1
  36. package/dist/server/server/session/agent-config/agent-config-session.d.ts +50 -0
  37. package/dist/server/server/session/agent-config/agent-config-session.js +98 -0
  38. package/dist/server/server/session/chat/chat-schedule-loop-session.d.ts +120 -0
  39. package/dist/server/server/session/chat/chat-schedule-loop-session.js +489 -0
  40. package/dist/server/server/session/checkout/checkout-session.d.ts +142 -0
  41. package/dist/server/server/session/checkout/checkout-session.js +925 -0
  42. package/dist/server/server/session/daemon/daemon-session.d.ts +50 -0
  43. package/dist/server/server/session/daemon/daemon-session.js +98 -0
  44. package/dist/server/server/session/files/workspace-files-session.d.ts +43 -0
  45. package/dist/server/server/session/files/workspace-files-session.js +218 -0
  46. package/dist/server/server/session/project-config/project-config-session.d.ts +34 -0
  47. package/dist/server/server/session/project-config/project-config-session.js +125 -0
  48. package/dist/server/server/session/provider/provider-catalog-session.d.ts +74 -0
  49. package/dist/server/server/session/provider/provider-catalog-session.js +339 -0
  50. package/dist/server/server/session/voice/voice-session.d.ts +166 -0
  51. package/dist/server/server/session/voice/voice-session.js +893 -0
  52. package/dist/server/server/{voice → session/voice}/voice-turn-controller.d.ts +2 -2
  53. package/dist/server/server/{voice → session/voice}/voice-turn-controller.js +2 -2
  54. package/dist/server/server/session.d.ts +13 -208
  55. package/dist/server/server/session.js +2132 -5105
  56. package/dist/server/utils/checkout-git.d.ts +6 -0
  57. package/package.json +5 -5
  58. package/dist/server/server/agent/providers/opencode/runtime.d.ts +0 -28
  59. package/dist/server/server/agent/providers/opencode/runtime.js +0 -5
@@ -0,0 +1,925 @@
1
+ import { getErrorMessage } from "@getpaseo/protocol/error-utils";
2
+ import { validateBranchSlug } from "@getpaseo/protocol/branch-slug";
3
+ import { toCheckoutError } from "../../checkout-git-utils.js";
4
+ import { buildCheckoutPrStatusPayloadFromSnapshot, buildCheckoutStatusPayloadFromSnapshot, } from "../../checkout/status-projection.js";
5
+ import { assertSafeGitRef } from "../../worktree-session.js";
6
+ import { assertPullRequestAutoMergeDisableReady, assertPullRequestAutoMergeEnableReady, } from "../../../services/github-service.js";
7
+ import { commitChanges, createPullRequest, mergeFromBase, mergeToBase, pullCurrentBranch, pushCurrentBranch, } from "../../../utils/checkout-git.js";
8
+ import { execCommand } from "../../../utils/spawn.js";
9
+ import { expandTilde } from "../../../utils/path.js";
10
+ /**
11
+ * A client's checkout view, both sides: the read & live-stream side (status
12
+ * queries, branch validation/suggestions, manual refresh, live git-diff and
13
+ * checkout-status subscriptions) and the command side (switch/rename/commit/
14
+ * merge/pull/push/stash and the GitHub-PR operations).
15
+ *
16
+ * Command operations keep the live diff in sync by calling scheduleDiffRefresh()
17
+ * and refresh the workspace git snapshot through host.notifyGitMutation(); the
18
+ * workspace git observer streams branch changes through emitStatusUpdate().
19
+ */
20
+ export class CheckoutSession {
21
+ constructor(options) {
22
+ this.diffSubscriptions = new Map();
23
+ this.host = options.host;
24
+ this.workspaceGitService = options.workspaceGitService;
25
+ this.github = options.github;
26
+ this.checkoutDiffManager = options.checkoutDiffManager;
27
+ this.paseoHome = options.paseoHome;
28
+ this.worktreesRoot = options.worktreesRoot;
29
+ this.logger = options.logger;
30
+ }
31
+ async handleStatusRequest(msg) {
32
+ const { cwd, requestId } = msg;
33
+ const resolvedCwd = expandTilde(cwd);
34
+ try {
35
+ const snapshot = await this.workspaceGitService.getSnapshot(resolvedCwd);
36
+ this.host.emit({
37
+ type: "checkout_status_response",
38
+ payload: buildCheckoutStatusPayloadFromSnapshot({
39
+ cwd,
40
+ requestId,
41
+ snapshot,
42
+ }),
43
+ });
44
+ }
45
+ catch (error) {
46
+ this.host.emit({
47
+ type: "checkout_status_response",
48
+ payload: {
49
+ cwd,
50
+ isGit: false,
51
+ repoRoot: null,
52
+ currentBranch: null,
53
+ isDirty: null,
54
+ baseRef: null,
55
+ aheadBehind: null,
56
+ aheadOfOrigin: null,
57
+ behindOfOrigin: null,
58
+ hasRemote: false,
59
+ remoteUrl: null,
60
+ isPaseoOwnedWorktree: false,
61
+ error: toCheckoutError(error),
62
+ requestId,
63
+ },
64
+ });
65
+ }
66
+ }
67
+ async handleValidateBranchRequest(msg) {
68
+ const { cwd, branchName, requestId } = msg;
69
+ try {
70
+ const resolvedCwd = expandTilde(cwd);
71
+ assertSafeGitRef(branchName, "branch");
72
+ const resolution = await this.workspaceGitService.validateBranchRef(resolvedCwd, branchName);
73
+ switch (resolution.kind) {
74
+ case "local":
75
+ this.host.emit({
76
+ type: "validate_branch_response",
77
+ payload: {
78
+ exists: true,
79
+ resolvedRef: resolution.name,
80
+ isRemote: false,
81
+ error: null,
82
+ requestId,
83
+ },
84
+ });
85
+ return;
86
+ case "remote-only":
87
+ this.host.emit({
88
+ type: "validate_branch_response",
89
+ payload: {
90
+ exists: true,
91
+ resolvedRef: resolution.remoteRef,
92
+ isRemote: true,
93
+ error: null,
94
+ requestId,
95
+ },
96
+ });
97
+ return;
98
+ case "not-found":
99
+ this.host.emit({
100
+ type: "validate_branch_response",
101
+ payload: {
102
+ exists: false,
103
+ resolvedRef: null,
104
+ isRemote: false,
105
+ error: null,
106
+ requestId,
107
+ },
108
+ });
109
+ return;
110
+ default: {
111
+ const exhaustiveCheck = resolution;
112
+ throw new Error(`Unhandled branch resolution: ${getErrorMessage(exhaustiveCheck)}`);
113
+ }
114
+ }
115
+ }
116
+ catch (error) {
117
+ this.host.emit({
118
+ type: "validate_branch_response",
119
+ payload: {
120
+ exists: false,
121
+ resolvedRef: null,
122
+ isRemote: false,
123
+ error: error instanceof Error ? error.message : String(error),
124
+ requestId,
125
+ },
126
+ });
127
+ }
128
+ }
129
+ async handleBranchSuggestionsRequest(msg) {
130
+ const { cwd, query, limit, requestId } = msg;
131
+ try {
132
+ const resolvedCwd = expandTilde(cwd);
133
+ const branchDetails = await this.workspaceGitService.suggestBranchesForCwd(resolvedCwd, {
134
+ query,
135
+ limit,
136
+ });
137
+ this.host.emit({
138
+ type: "branch_suggestions_response",
139
+ payload: {
140
+ branches: branchDetails.map((branch) => branch.name),
141
+ branchDetails,
142
+ error: null,
143
+ requestId,
144
+ },
145
+ });
146
+ }
147
+ catch (error) {
148
+ this.host.emit({
149
+ type: "branch_suggestions_response",
150
+ payload: {
151
+ branches: [],
152
+ branchDetails: [],
153
+ error: error instanceof Error ? error.message : String(error),
154
+ requestId,
155
+ },
156
+ });
157
+ }
158
+ }
159
+ async handleSubscribeDiffRequest(msg) {
160
+ const cwd = expandTilde(msg.cwd);
161
+ this.diffSubscriptions.get(msg.subscriptionId)?.();
162
+ this.diffSubscriptions.delete(msg.subscriptionId);
163
+ const subscription = await this.checkoutDiffManager.subscribe({ cwd, compare: msg.compare }, (snapshot) => {
164
+ this.host.emit({
165
+ type: "checkout_diff_update",
166
+ payload: {
167
+ subscriptionId: msg.subscriptionId,
168
+ ...snapshot,
169
+ },
170
+ });
171
+ });
172
+ this.diffSubscriptions.set(msg.subscriptionId, subscription.unsubscribe);
173
+ this.host.emit({
174
+ type: "subscribe_checkout_diff_response",
175
+ payload: {
176
+ subscriptionId: msg.subscriptionId,
177
+ ...subscription.initial,
178
+ requestId: msg.requestId,
179
+ },
180
+ });
181
+ }
182
+ handleUnsubscribeDiffRequest(msg) {
183
+ this.diffSubscriptions.get(msg.subscriptionId)?.();
184
+ this.diffSubscriptions.delete(msg.subscriptionId);
185
+ }
186
+ async handleRefreshRequest(msg) {
187
+ const { cwd, requestId } = msg;
188
+ const resolvedCwd = expandTilde(cwd);
189
+ try {
190
+ this.github.invalidate({ cwd: resolvedCwd });
191
+ await this.workspaceGitService.getSnapshot(resolvedCwd, {
192
+ force: true,
193
+ includeGitHub: true,
194
+ reason: "manual-refresh",
195
+ });
196
+ this.checkoutDiffManager.scheduleRefreshForCwd(resolvedCwd);
197
+ this.host.emit({
198
+ type: "checkout.refresh.response",
199
+ payload: {
200
+ cwd,
201
+ success: true,
202
+ error: null,
203
+ requestId,
204
+ },
205
+ });
206
+ }
207
+ catch (error) {
208
+ this.host.emit({
209
+ type: "checkout.refresh.response",
210
+ payload: {
211
+ cwd,
212
+ success: false,
213
+ error: toCheckoutError(error),
214
+ requestId,
215
+ },
216
+ });
217
+ }
218
+ }
219
+ emitStatusUpdate(cwd, snapshot) {
220
+ try {
221
+ const requestId = `subscription:${cwd}`;
222
+ this.host.emit({
223
+ type: "checkout_status_update",
224
+ payload: {
225
+ ...buildCheckoutStatusPayloadFromSnapshot({
226
+ cwd,
227
+ requestId,
228
+ snapshot,
229
+ }),
230
+ prStatus: buildCheckoutPrStatusPayloadFromSnapshot({
231
+ cwd,
232
+ requestId,
233
+ snapshot,
234
+ }),
235
+ },
236
+ });
237
+ }
238
+ catch (error) {
239
+ this.logger.warn({ err: error, cwd }, "Failed to emit workspace checkout status update");
240
+ }
241
+ }
242
+ /**
243
+ * Notify the live diff subscriptions that the working tree at `cwd` changed.
244
+ * Called by the command handlers below after they mutate the repository.
245
+ */
246
+ scheduleDiffRefresh(cwd) {
247
+ this.checkoutDiffManager.scheduleRefreshForCwd(cwd);
248
+ }
249
+ // ---------------------------------------------------------------------------
250
+ // Command operations (writes) and GitHub-PR operations
251
+ // ---------------------------------------------------------------------------
252
+ async handleCheckoutSwitchBranchRequest(msg) {
253
+ const { cwd, branch, requestId } = msg;
254
+ try {
255
+ const checkoutResult = await this.host.checkoutExistingBranch(cwd, branch);
256
+ this.scheduleDiffRefresh(cwd);
257
+ // Push a workspace_update immediately so the sidebar/header reflect
258
+ // the new branch name without waiting for the background git watcher.
259
+ await this.host.emitWorkspaceUpdateForCwd(cwd);
260
+ this.host.emit({
261
+ type: "checkout_switch_branch_response",
262
+ payload: {
263
+ cwd,
264
+ success: true,
265
+ branch,
266
+ source: checkoutResult.source,
267
+ error: null,
268
+ requestId,
269
+ },
270
+ });
271
+ }
272
+ catch (error) {
273
+ this.host.emit({
274
+ type: "checkout_switch_branch_response",
275
+ payload: {
276
+ cwd,
277
+ success: false,
278
+ branch,
279
+ error: toCheckoutError(error),
280
+ requestId,
281
+ },
282
+ });
283
+ }
284
+ }
285
+ async handleCheckoutRenameBranchRequest(msg) {
286
+ const { cwd, branch, requestId } = msg;
287
+ const validation = validateBranchSlug(branch);
288
+ if (!validation.valid) {
289
+ this.host.emit({
290
+ type: "checkout.rename_branch.response",
291
+ payload: {
292
+ cwd,
293
+ success: false,
294
+ currentBranch: null,
295
+ error: toCheckoutError(new Error(validation.error ?? "Invalid branch name")),
296
+ requestId,
297
+ },
298
+ });
299
+ return;
300
+ }
301
+ try {
302
+ const result = await this.host.renameCurrentBranch(cwd, branch);
303
+ await this.host.notifyGitMutation(cwd, "rename-branch", { invalidateGithub: true });
304
+ this.scheduleDiffRefresh(cwd);
305
+ this.host.handleWorkspaceGitBranchSnapshot(cwd, result.currentBranch);
306
+ // Branch is a git fact derived per-descriptor from each workspace's own
307
+ // live git snapshot (id → cwd); the reconciliation pass re-persists the
308
+ // `branch` field per workspace from its own cwd. No cwd → ids fan-out here.
309
+ // TODO(K10): PR-binding on branch rename is deferred — see plan K10.
310
+ // Push a workspace_update immediately so the sidebar/header reflect
311
+ // the new branch name without waiting for the background git watcher.
312
+ await this.host.emitWorkspaceUpdateForCwd(cwd);
313
+ this.host.emit({
314
+ type: "checkout.rename_branch.response",
315
+ payload: {
316
+ cwd,
317
+ success: true,
318
+ currentBranch: result.currentBranch,
319
+ error: null,
320
+ requestId,
321
+ },
322
+ });
323
+ }
324
+ catch (error) {
325
+ this.host.emit({
326
+ type: "checkout.rename_branch.response",
327
+ payload: {
328
+ cwd,
329
+ success: false,
330
+ currentBranch: null,
331
+ error: toCheckoutError(error),
332
+ requestId,
333
+ },
334
+ });
335
+ }
336
+ }
337
+ async handleStashSaveRequest(msg) {
338
+ const { cwd, requestId } = msg;
339
+ try {
340
+ const branchLabel = msg.branch?.trim() ?? "";
341
+ const message = branchLabel
342
+ ? `${CheckoutSession.PASEO_STASH_PREFIX} ${branchLabel}`
343
+ : `${CheckoutSession.PASEO_STASH_PREFIX} unnamed`;
344
+ await execCommand("git", ["stash", "push", "--include-untracked", "-m", message], {
345
+ cwd,
346
+ });
347
+ await this.host.notifyGitMutation(cwd, "stash-push");
348
+ this.scheduleDiffRefresh(cwd);
349
+ this.host.emit({
350
+ type: "stash_save_response",
351
+ payload: { cwd, success: true, error: null, requestId },
352
+ });
353
+ }
354
+ catch (error) {
355
+ this.host.emit({
356
+ type: "stash_save_response",
357
+ payload: { cwd, success: false, error: toCheckoutError(error), requestId },
358
+ });
359
+ }
360
+ }
361
+ async handleStashPopRequest(msg) {
362
+ const { cwd, stashIndex, requestId } = msg;
363
+ try {
364
+ await execCommand("git", ["stash", "pop", `stash@{${stashIndex}}`], {
365
+ cwd,
366
+ });
367
+ await this.host.notifyGitMutation(cwd, "stash-pop");
368
+ this.scheduleDiffRefresh(cwd);
369
+ this.host.emit({
370
+ type: "stash_pop_response",
371
+ payload: { cwd, success: true, error: null, requestId },
372
+ });
373
+ }
374
+ catch (error) {
375
+ this.host.emit({
376
+ type: "stash_pop_response",
377
+ payload: { cwd, success: false, error: toCheckoutError(error), requestId },
378
+ });
379
+ }
380
+ }
381
+ async handleStashListRequest(msg) {
382
+ const { cwd, requestId } = msg;
383
+ const paseoOnly = msg.paseoOnly !== false;
384
+ try {
385
+ const entries = await this.workspaceGitService.listStashes(cwd, { paseoOnly });
386
+ this.host.emit({
387
+ type: "stash_list_response",
388
+ payload: { cwd, entries, error: null, requestId },
389
+ });
390
+ }
391
+ catch (error) {
392
+ this.host.emit({
393
+ type: "stash_list_response",
394
+ payload: { cwd, entries: [], error: toCheckoutError(error), requestId },
395
+ });
396
+ }
397
+ }
398
+ async handleCheckoutCommitRequest(msg) {
399
+ const { cwd, requestId } = msg;
400
+ try {
401
+ let message = msg.message?.trim() ?? "";
402
+ if (!message) {
403
+ message = await this.host.generateCommitMessage(cwd);
404
+ }
405
+ if (!message) {
406
+ throw new Error("Commit message is required");
407
+ }
408
+ await commitChanges(cwd, {
409
+ message,
410
+ addAll: msg.addAll ?? true,
411
+ });
412
+ await this.host.notifyGitMutation(cwd, "commit-changes");
413
+ this.scheduleDiffRefresh(cwd);
414
+ this.host.emit({
415
+ type: "checkout_commit_response",
416
+ payload: {
417
+ cwd,
418
+ success: true,
419
+ error: null,
420
+ requestId,
421
+ },
422
+ });
423
+ }
424
+ catch (error) {
425
+ this.host.emit({
426
+ type: "checkout_commit_response",
427
+ payload: {
428
+ cwd,
429
+ success: false,
430
+ error: toCheckoutError(error),
431
+ requestId,
432
+ },
433
+ });
434
+ }
435
+ }
436
+ async handleCheckoutMergeRequest(msg) {
437
+ const { cwd, requestId } = msg;
438
+ try {
439
+ const snapshot = await this.workspaceGitService.getSnapshot(cwd);
440
+ if (!snapshot.git.isGit) {
441
+ throw new Error(`Not a git repository: ${cwd}`);
442
+ }
443
+ if (msg.requireCleanTarget) {
444
+ if (snapshot.git.isDirty) {
445
+ throw new Error("Working directory has uncommitted changes.");
446
+ }
447
+ }
448
+ let baseRef = msg.baseRef ?? snapshot.git.baseRef;
449
+ if (!baseRef) {
450
+ throw new Error("Base branch is required for merge");
451
+ }
452
+ if (baseRef.startsWith("origin/")) {
453
+ baseRef = baseRef.slice("origin/".length);
454
+ }
455
+ const mutatedCwd = await mergeToBase(cwd, {
456
+ baseRef,
457
+ mode: msg.strategy === "squash" ? "squash" : "merge",
458
+ }, { paseoHome: this.paseoHome, worktreesRoot: this.worktreesRoot });
459
+ await Promise.all([
460
+ this.host.notifyGitMutation(mutatedCwd, "merge-to-base", { invalidateGithub: true }),
461
+ ...(mutatedCwd !== cwd ? [this.host.notifyGitMutation(cwd, "merge-to-base")] : []),
462
+ ]);
463
+ this.scheduleDiffRefresh(cwd);
464
+ this.host.emit({
465
+ type: "checkout_merge_response",
466
+ payload: {
467
+ cwd,
468
+ success: true,
469
+ error: null,
470
+ requestId,
471
+ },
472
+ });
473
+ }
474
+ catch (error) {
475
+ this.host.emit({
476
+ type: "checkout_merge_response",
477
+ payload: {
478
+ cwd,
479
+ success: false,
480
+ error: toCheckoutError(error),
481
+ requestId,
482
+ },
483
+ });
484
+ }
485
+ }
486
+ async handleCheckoutMergeFromBaseRequest(msg) {
487
+ const { cwd, requestId } = msg;
488
+ try {
489
+ if (msg.requireCleanTarget ?? true) {
490
+ const snapshot = await this.workspaceGitService.getSnapshot(cwd);
491
+ if (snapshot.git.isDirty) {
492
+ throw new Error("Working directory has uncommitted changes.");
493
+ }
494
+ }
495
+ await mergeFromBase(cwd, {
496
+ baseRef: msg.baseRef,
497
+ requireCleanTarget: msg.requireCleanTarget ?? true,
498
+ });
499
+ await this.host.notifyGitMutation(cwd, "merge-from-base", { invalidateGithub: true });
500
+ this.scheduleDiffRefresh(cwd);
501
+ this.host.emit({
502
+ type: "checkout_merge_from_base_response",
503
+ payload: {
504
+ cwd,
505
+ success: true,
506
+ error: null,
507
+ requestId,
508
+ },
509
+ });
510
+ }
511
+ catch (error) {
512
+ this.host.emit({
513
+ type: "checkout_merge_from_base_response",
514
+ payload: {
515
+ cwd,
516
+ success: false,
517
+ error: toCheckoutError(error),
518
+ requestId,
519
+ },
520
+ });
521
+ }
522
+ }
523
+ async handleCheckoutPullRequest(msg) {
524
+ const { cwd, requestId } = msg;
525
+ try {
526
+ await pullCurrentBranch(cwd);
527
+ await this.host.notifyGitMutation(cwd, "pull", { invalidateGithub: true });
528
+ this.scheduleDiffRefresh(cwd);
529
+ this.host.emit({
530
+ type: "checkout_pull_response",
531
+ payload: {
532
+ cwd,
533
+ success: true,
534
+ error: null,
535
+ requestId,
536
+ },
537
+ });
538
+ }
539
+ catch (error) {
540
+ this.host.emit({
541
+ type: "checkout_pull_response",
542
+ payload: {
543
+ cwd,
544
+ success: false,
545
+ error: toCheckoutError(error),
546
+ requestId,
547
+ },
548
+ });
549
+ }
550
+ }
551
+ async handleCheckoutPushRequest(msg) {
552
+ const { cwd, requestId } = msg;
553
+ try {
554
+ await pushCurrentBranch(cwd);
555
+ await this.host.notifyGitMutation(cwd, "push", { invalidateGithub: true });
556
+ this.host.emit({
557
+ type: "checkout_push_response",
558
+ payload: {
559
+ cwd,
560
+ success: true,
561
+ error: null,
562
+ requestId,
563
+ },
564
+ });
565
+ }
566
+ catch (error) {
567
+ this.host.emit({
568
+ type: "checkout_push_response",
569
+ payload: {
570
+ cwd,
571
+ success: false,
572
+ error: toCheckoutError(error),
573
+ requestId,
574
+ },
575
+ });
576
+ }
577
+ }
578
+ async handleCheckoutPrCreateRequest(msg) {
579
+ const { cwd, requestId } = msg;
580
+ try {
581
+ let title = msg.title?.trim() ?? "";
582
+ let body = msg.body?.trim() ?? "";
583
+ if (!title || !body) {
584
+ const generated = await this.host.generatePullRequestText(cwd, msg.baseRef);
585
+ if (!title)
586
+ title = generated.title;
587
+ if (!body)
588
+ body = generated.body;
589
+ }
590
+ const result = await createPullRequest(cwd, {
591
+ title,
592
+ body,
593
+ base: msg.baseRef,
594
+ }, this.github);
595
+ await this.host.notifyGitMutation(cwd, "create-pr", { invalidateGithub: true });
596
+ this.host.emit({
597
+ type: "checkout_pr_create_response",
598
+ payload: {
599
+ cwd,
600
+ url: result.url ?? null,
601
+ number: result.number ?? null,
602
+ error: null,
603
+ requestId,
604
+ },
605
+ });
606
+ }
607
+ catch (error) {
608
+ this.host.emit({
609
+ type: "checkout_pr_create_response",
610
+ payload: {
611
+ cwd,
612
+ url: null,
613
+ number: null,
614
+ error: toCheckoutError(error),
615
+ requestId,
616
+ },
617
+ });
618
+ }
619
+ }
620
+ async handleCheckoutPrMergeRequest(msg) {
621
+ const { cwd, requestId } = msg;
622
+ try {
623
+ const pullRequest = await this.resolveCurrentPullRequest(cwd, "merge", {
624
+ force: true,
625
+ includeGitHub: true,
626
+ reason: "merge-pr-validation",
627
+ });
628
+ this.assertCurrentPullRequestHasGithubMergeFacts(pullRequest);
629
+ await this.github.mergePullRequest({
630
+ cwd,
631
+ prNumber: pullRequest.number,
632
+ mergeMethod: msg.mergeMethod,
633
+ status: pullRequest,
634
+ });
635
+ await this.host.notifyGitMutation(cwd, "merge-pr", { invalidateGithub: true });
636
+ this.host.emit({
637
+ type: "checkout_pr_merge_response",
638
+ payload: {
639
+ cwd,
640
+ success: true,
641
+ error: null,
642
+ requestId,
643
+ },
644
+ });
645
+ }
646
+ catch (error) {
647
+ this.host.emit({
648
+ type: "checkout_pr_merge_response",
649
+ payload: {
650
+ cwd,
651
+ success: false,
652
+ error: toCheckoutError(error),
653
+ requestId,
654
+ },
655
+ });
656
+ }
657
+ }
658
+ assertCurrentPullRequestHasGithubMergeFacts(pullRequest) {
659
+ if (!pullRequest.github) {
660
+ throw new Error("GitHub merge facts are unavailable for this pull request");
661
+ }
662
+ }
663
+ async handleCheckoutGithubSetAutoMergeRequest(msg) {
664
+ const { cwd, requestId } = msg;
665
+ try {
666
+ const pullRequest = await this.resolveCurrentPullRequest(cwd, "auto-merge", {
667
+ force: true,
668
+ includeGitHub: true,
669
+ reason: "auto-merge-validation",
670
+ });
671
+ if (msg.enabled) {
672
+ const mergeMethod = msg.mergeMethod;
673
+ if (!mergeMethod) {
674
+ throw new Error("mergeMethod is required when enabling auto-merge");
675
+ }
676
+ assertPullRequestAutoMergeEnableReady({
677
+ mergeMethod,
678
+ status: pullRequest,
679
+ });
680
+ await this.github.enablePullRequestAutoMerge({
681
+ cwd,
682
+ prNumber: pullRequest.number,
683
+ mergeMethod,
684
+ status: pullRequest,
685
+ });
686
+ }
687
+ else {
688
+ if (msg.mergeMethod) {
689
+ throw new Error("mergeMethod is not allowed when disabling auto-merge");
690
+ }
691
+ assertPullRequestAutoMergeDisableReady({ status: pullRequest });
692
+ await this.github.disablePullRequestAutoMerge({
693
+ cwd,
694
+ prNumber: pullRequest.number,
695
+ status: pullRequest,
696
+ });
697
+ }
698
+ await this.host.notifyGitMutation(cwd, msg.enabled ? "enable-pr-auto-merge" : "disable-pr-auto-merge", {
699
+ invalidateGithub: true,
700
+ });
701
+ this.host.emit({
702
+ type: "checkout.github.set_auto_merge.response",
703
+ payload: {
704
+ cwd,
705
+ enabled: msg.enabled,
706
+ success: true,
707
+ error: null,
708
+ requestId,
709
+ },
710
+ });
711
+ }
712
+ catch (error) {
713
+ this.host.emit({
714
+ type: "checkout.github.set_auto_merge.response",
715
+ payload: {
716
+ cwd,
717
+ enabled: msg.enabled,
718
+ success: false,
719
+ error: toCheckoutError(error),
720
+ requestId,
721
+ },
722
+ });
723
+ }
724
+ }
725
+ async resolveCurrentPullRequest(cwd, operation, options) {
726
+ const snapshot = await this.workspaceGitService.getSnapshot(cwd, options);
727
+ const pullRequest = snapshot.github.pullRequest;
728
+ if (!pullRequest || typeof pullRequest.number !== "number") {
729
+ throw new Error(`Unable to determine GitHub pull request number for ${operation}`);
730
+ }
731
+ return { ...pullRequest, number: pullRequest.number };
732
+ }
733
+ async handleCheckoutPrStatusRequest(msg) {
734
+ const { cwd, requestId } = msg;
735
+ try {
736
+ const snapshot = await this.workspaceGitService.getSnapshot(cwd);
737
+ this.host.emit({
738
+ type: "checkout_pr_status_response",
739
+ payload: buildCheckoutPrStatusPayloadFromSnapshot({
740
+ cwd,
741
+ requestId,
742
+ snapshot,
743
+ }),
744
+ });
745
+ }
746
+ catch (error) {
747
+ this.host.emit({
748
+ type: "checkout_pr_status_response",
749
+ payload: {
750
+ cwd,
751
+ status: null,
752
+ githubFeaturesEnabled: true,
753
+ error: toCheckoutError(error),
754
+ requestId,
755
+ },
756
+ });
757
+ }
758
+ }
759
+ async handlePullRequestTimelineRequest(msg) {
760
+ const { cwd, prNumber, repoOwner, repoName, requestId } = msg;
761
+ if (!isValidPullRequestTimelineIdentity({ prNumber, repoOwner, repoName })) {
762
+ this.host.emit({
763
+ type: "pull_request_timeline_response",
764
+ payload: {
765
+ cwd,
766
+ prNumber,
767
+ items: [],
768
+ truncated: false,
769
+ error: {
770
+ kind: "unknown",
771
+ message: "Pull request timeline request has invalid PR identity",
772
+ },
773
+ requestId,
774
+ githubFeaturesEnabled: true,
775
+ },
776
+ });
777
+ return;
778
+ }
779
+ const githubFeaturesEnabled = await this.github.isAuthenticated({ cwd });
780
+ if (!githubFeaturesEnabled) {
781
+ this.host.emit({
782
+ type: "pull_request_timeline_response",
783
+ payload: {
784
+ cwd,
785
+ prNumber,
786
+ items: [],
787
+ truncated: false,
788
+ error: {
789
+ kind: "unknown",
790
+ message: "GitHub CLI is unavailable or not authenticated",
791
+ },
792
+ requestId,
793
+ githubFeaturesEnabled: false,
794
+ },
795
+ });
796
+ return;
797
+ }
798
+ try {
799
+ const timeline = await this.github.getPullRequestTimeline({
800
+ cwd,
801
+ prNumber,
802
+ repoOwner,
803
+ repoName,
804
+ });
805
+ this.host.emit({
806
+ type: "pull_request_timeline_response",
807
+ payload: {
808
+ cwd,
809
+ prNumber: timeline.prNumber,
810
+ items: timeline.items.map(toPullRequestTimelinePayloadItem),
811
+ truncated: timeline.truncated,
812
+ error: timeline.error,
813
+ requestId,
814
+ githubFeaturesEnabled: true,
815
+ },
816
+ });
817
+ }
818
+ catch (error) {
819
+ this.host.emit({
820
+ type: "pull_request_timeline_response",
821
+ payload: {
822
+ cwd,
823
+ prNumber,
824
+ items: [],
825
+ truncated: false,
826
+ error: {
827
+ kind: "unknown",
828
+ message: error instanceof Error ? error.message : String(error),
829
+ },
830
+ requestId,
831
+ githubFeaturesEnabled: true,
832
+ },
833
+ });
834
+ }
835
+ }
836
+ async handleCheckoutGithubGetCheckDetailsRequest(msg) {
837
+ const { cwd, repoOwner, repoName, checkRunId, workflowRunId, requestId } = msg;
838
+ try {
839
+ const details = await this.github.getGitHubCheckDetails({
840
+ cwd,
841
+ repoOwner,
842
+ repoName,
843
+ checkRunId,
844
+ workflowRunId,
845
+ });
846
+ this.host.emit({
847
+ type: "checkout.github.get_check_details.response",
848
+ payload: {
849
+ cwd,
850
+ success: true,
851
+ details,
852
+ error: null,
853
+ requestId,
854
+ },
855
+ });
856
+ }
857
+ catch (error) {
858
+ this.host.emit({
859
+ type: "checkout.github.get_check_details.response",
860
+ payload: {
861
+ cwd,
862
+ success: false,
863
+ details: null,
864
+ error: {
865
+ code: "UNKNOWN",
866
+ message: error instanceof Error ? error.message : String(error),
867
+ },
868
+ requestId,
869
+ },
870
+ });
871
+ }
872
+ }
873
+ async handleGitHubSearchRequest(msg) {
874
+ const { cwd, query, limit, kinds, requestId } = msg;
875
+ try {
876
+ const resolvedCwd = expandTilde(cwd);
877
+ const result = await this.github.searchIssuesAndPrs({
878
+ cwd: resolvedCwd,
879
+ query,
880
+ limit,
881
+ kinds,
882
+ });
883
+ this.host.emit({
884
+ type: "github_search_response",
885
+ payload: {
886
+ items: result.items,
887
+ githubFeaturesEnabled: result.githubFeaturesEnabled,
888
+ error: null,
889
+ requestId,
890
+ },
891
+ });
892
+ }
893
+ catch (error) {
894
+ this.host.emit({
895
+ type: "github_search_response",
896
+ payload: {
897
+ items: [],
898
+ githubFeaturesEnabled: true,
899
+ error: error instanceof Error ? error.message : String(error),
900
+ requestId,
901
+ },
902
+ });
903
+ }
904
+ }
905
+ cleanup() {
906
+ for (const unsubscribe of this.diffSubscriptions.values()) {
907
+ unsubscribe();
908
+ }
909
+ this.diffSubscriptions.clear();
910
+ }
911
+ }
912
+ CheckoutSession.PASEO_STASH_PREFIX = "paseo-auto-stash:";
913
+ function isValidPullRequestTimelineIdentity(options) {
914
+ if (!Number.isInteger(options.prNumber) || options.prNumber <= 0) {
915
+ return false;
916
+ }
917
+ return isValidGitHubRepoSegment(options.repoOwner) && isValidGitHubRepoSegment(options.repoName);
918
+ }
919
+ function isValidGitHubRepoSegment(value) {
920
+ return /^[A-Za-z0-9._-]+$/.test(value);
921
+ }
922
+ function toPullRequestTimelinePayloadItem(item) {
923
+ return item;
924
+ }
925
+ //# sourceMappingURL=checkout-session.js.map