@junctionpanel/server 0.1.54 → 0.1.55

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 (64) hide show
  1. package/dist/server/client/daemon-client.d.ts +13 -0
  2. package/dist/server/client/daemon-client.d.ts.map +1 -1
  3. package/dist/server/client/daemon-client.js +89 -18
  4. package/dist/server/client/daemon-client.js.map +1 -1
  5. package/dist/server/server/agent/activity-curator.d.ts.map +1 -1
  6. package/dist/server/server/agent/activity-curator.js +12 -0
  7. package/dist/server/server/agent/activity-curator.js.map +1 -1
  8. package/dist/server/server/agent/agent-manager.d.ts +8 -0
  9. package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
  10. package/dist/server/server/agent/agent-manager.js +175 -29
  11. package/dist/server/server/agent/agent-manager.js.map +1 -1
  12. package/dist/server/server/agent/agent-metadata-generator.d.ts.map +1 -1
  13. package/dist/server/server/agent/agent-metadata-generator.js +11 -2
  14. package/dist/server/server/agent/agent-metadata-generator.js.map +1 -1
  15. package/dist/server/server/agent/agent-projections.d.ts.map +1 -1
  16. package/dist/server/server/agent/agent-projections.js +0 -1
  17. package/dist/server/server/agent/agent-projections.js.map +1 -1
  18. package/dist/server/server/agent/agent-response-loop.d.ts +27 -0
  19. package/dist/server/server/agent/agent-response-loop.d.ts.map +1 -1
  20. package/dist/server/server/agent/agent-response-loop.js +78 -4
  21. package/dist/server/server/agent/agent-response-loop.js.map +1 -1
  22. package/dist/server/server/agent/agent-sdk-types.d.ts +17 -1
  23. package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
  24. package/dist/server/server/agent/agent-sdk-types.js.map +1 -1
  25. package/dist/server/server/agent/agent-storage.d.ts +0 -3
  26. package/dist/server/server/agent/agent-storage.d.ts.map +1 -1
  27. package/dist/server/server/agent/agent-storage.js +0 -1
  28. package/dist/server/server/agent/agent-storage.js.map +1 -1
  29. package/dist/server/server/agent/provider-model-cache.d.ts +14 -0
  30. package/dist/server/server/agent/provider-model-cache.d.ts.map +1 -0
  31. package/dist/server/server/agent/provider-model-cache.js +71 -0
  32. package/dist/server/server/agent/provider-model-cache.js.map +1 -0
  33. package/dist/server/server/agent/providers/claude/model-catalog.d.ts.map +1 -1
  34. package/dist/server/server/agent/providers/claude/model-catalog.js +15 -1
  35. package/dist/server/server/agent/providers/claude/model-catalog.js.map +1 -1
  36. package/dist/server/server/agent/providers/claude-agent.d.ts +1 -0
  37. package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
  38. package/dist/server/server/agent/providers/claude-agent.js +43 -12
  39. package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
  40. package/dist/server/server/agent/providers/gemini-agent.d.ts.map +1 -1
  41. package/dist/server/server/agent/providers/gemini-agent.js +36 -1
  42. package/dist/server/server/agent/providers/gemini-agent.js.map +1 -1
  43. package/dist/server/server/agent/providers/opencode-agent.js +8 -2
  44. package/dist/server/server/agent/providers/opencode-agent.js.map +1 -1
  45. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts.map +1 -1
  46. package/dist/server/server/persistence-hooks.js +11 -1
  47. package/dist/server/server/persistence-hooks.js.map +1 -1
  48. package/dist/server/server/session.d.ts +13 -0
  49. package/dist/server/server/session.d.ts.map +1 -1
  50. package/dist/server/server/session.js +416 -170
  51. package/dist/server/server/session.js.map +1 -1
  52. package/dist/server/shared/messages.d.ts +16 -0
  53. package/dist/server/shared/messages.d.ts.map +1 -1
  54. package/dist/server/shared/messages.js +19 -0
  55. package/dist/server/shared/messages.js.map +1 -1
  56. package/dist/server/shared/permission-questions.d.ts +43 -0
  57. package/dist/server/shared/permission-questions.d.ts.map +1 -0
  58. package/dist/server/shared/permission-questions.js +234 -0
  59. package/dist/server/shared/permission-questions.js.map +1 -0
  60. package/dist/server/utils/checkout-git.d.ts +1 -0
  61. package/dist/server/utils/checkout-git.d.ts.map +1 -1
  62. package/dist/server/utils/checkout-git.js +19 -6
  63. package/dist/server/utils/checkout-git.js.map +1 -1
  64. package/package.json +2 -2
@@ -1,5 +1,5 @@
1
1
  import { v4 as uuidv4 } from 'uuid';
2
- import { existsSync, watch } from 'node:fs';
2
+ import { existsSync, statSync, watch } from 'node:fs';
3
3
  import { exec } from 'child_process';
4
4
  import { promisify } from 'util';
5
5
  import { resolve, sep, basename, dirname, parse as parsePath } from 'path';
@@ -17,13 +17,13 @@ import { sanitizeTimelineItemForTransport, } from './tool-call-preview.js';
17
17
  import { toAgentPayload, resolveEffectiveThinkingOptionId, toStoredAgentPayload, } from './agent/agent-projections.js';
18
18
  import { appendTimelineItemIfAgentKnown, emitLiveTimelineItemIfAgentKnown, } from './agent/timeline-append.js';
19
19
  import { projectTimelineRows, selectTimelineWindowByProjectedLimit, } from './agent/timeline-projection.js';
20
- import { DEFAULT_STRUCTURED_GENERATION_PROVIDERS, StructuredAgentFallbackError, StructuredAgentResponseError, generateStructuredAgentResponseWithFallback, } from './agent/agent-response-loop.js';
20
+ import { DEFAULT_STRUCTURED_GENERATION_PROVIDERS, StructuredAgentFallbackError, StructuredAgentResponseError, generateStructuredAgentResponseWithFallback, resolveStructuredGenerationPreferredProviderForCwd, resolveStructuredGenerationProviders, } from './agent/agent-response-loop.js';
21
21
  import { isValidAgentProvider, AGENT_PROVIDER_IDS } from './agent/provider-manifest.js';
22
22
  import { listDirectoryEntries, readExplorerFile, getDownloadableFileInfo, isWorkspaceExplorerMissingPathError, } from './file-explorer/service.js';
23
23
  import { slugify, validateBranchSlug, listJunctionWorktrees, deleteJunctionWorktree, isJunctionOwnedWorktreeCwd, resolveJunctionWorktreeRootForCwd, createInRepoWorktree, attachInRepoWorktree, restoreInRepoWorktree, } from '../utils/worktree.js';
24
24
  import { readJunctionWorktreeMetadata } from '../utils/worktree-metadata.js';
25
25
  import { runAsyncWorktreeBootstrap } from './worktree-bootstrap.js';
26
- import { getCheckoutDiff, getCheckoutStatus, getCheckoutStatusLite, listBranchSuggestions, NotGitRepoError, MergeConflictError, MergeFromBaseConflictError, commitChanges, mergeToBase, mergeFromBase, pushCurrentBranch, createPullRequest, getPullRequestFailureLogs, getPullRequestStatus, searchPullRequests, listGitRemotes, mergePullRequest, resolveBaseRefWithSource, } from '../utils/checkout-git.js';
26
+ import { getCheckoutDiff, getCheckoutStatus, getCheckoutStatusLite, listBranchSuggestions, NotGitRepoError, MergeConflictError, MergeFromBaseConflictError, commitChanges, mergeToBase, mergeFromBase, pushCurrentBranch, createPullRequest, getPullRequestFailureLogs, getPullRequestStatus, searchPullRequests, listGitRemotes, mergePullRequest, resolveMergeToBaseOperationCwd, resolveBaseRefWithSource, } from '../utils/checkout-git.js';
27
27
  import { getProjectIcon } from '../utils/project-icon.js';
28
28
  import { expandTilde } from '../utils/path.js';
29
29
  import { searchHomeDirectories, searchWorkspaceEntries, searchWorkspaceEntriesAtGitRef, searchGitRepositories, checkIsGitRepo, } from '../utils/directory-suggestions.js';
@@ -48,6 +48,7 @@ const READ_ONLY_GIT_ENV = {
48
48
  const DEFAULT_STORED_TIMELINE_FETCH_LIMIT = 200;
49
49
  const pendingAgentInitializations = new Map();
50
50
  const pendingAgentMessageExecutions = new Map();
51
+ const sharedWorkspaceGitOperationStates = new Map();
51
52
  const DEFAULT_AGENT_PROVIDER = AGENT_PROVIDER_IDS[0];
52
53
  const CHECKOUT_DIFF_WATCH_DEBOUNCE_MS = 150;
53
54
  const CHECKOUT_DIFF_FALLBACK_REFRESH_MS = 5000;
@@ -133,6 +134,7 @@ export class Session {
133
134
  this.nextTerminalStreamId = 1;
134
135
  this.checkoutDiffSubscriptions = new Map();
135
136
  this.checkoutDiffTargets = new Map();
137
+ this.workspaceGitOperationStates = sharedWorkspaceGitOperationStates;
136
138
  const { clientId, userId, onMessage, onBinaryMessage, onLifecycleIntent, logger, downloadTokenStore, junctionHome, agentManager, agentStorage, createAgentMcpTransport, terminalManager, agentProviderRuntimeSettings, } = options;
137
139
  this.clientId = clientId;
138
140
  this.userId = userId;
@@ -309,7 +311,10 @@ export class Session {
309
311
  this.sessionLogger.error({ err: error, agentId: input.agentId }, `Failed to record user message for agent ${input.agentId}`);
310
312
  }
311
313
  const prompt = this.buildAgentPrompt(input.text, input.images);
312
- const started = this.startAgentStream(input.agentId, prompt, input.runOptions);
314
+ const started = this.startAgentStream(input.agentId, prompt, {
315
+ ...input.runOptions,
316
+ ...(input.messageId ? { messageId: input.messageId } : {}),
317
+ });
313
318
  if (!started.ok) {
314
319
  throw new Error(started.error);
315
320
  }
@@ -2351,8 +2356,53 @@ export class Session {
2351
2356
  }
2352
2357
  return resolvedCandidate.startsWith(resolvedRoot + sep);
2353
2358
  }
2354
- async generateCommitMessage(cwd) {
2355
- const diff = await getCheckoutDiff(cwd, { mode: 'uncommitted', includeStructured: true }, { junctionHome: this.junctionHome });
2359
+ async resolvePreferredStructuredProviderForCwd(cwd) {
2360
+ const resolvedCwd = expandTilde(cwd);
2361
+ const storedRecords = new Map((await this.agentStorage.list()).map((record) => [record.id, record]));
2362
+ const liveCandidates = this.agentManager.listAgents().map((agent) => {
2363
+ const storedRecord = storedRecords.get(agent.id);
2364
+ return {
2365
+ provider: agent.provider,
2366
+ cwd: agent.cwd,
2367
+ lastActivityAt: agent.lastUserMessageAt ?? agent.updatedAt,
2368
+ updatedAt: agent.updatedAt,
2369
+ archivedAt: storedRecord?.archivedAt ?? null,
2370
+ internal: agent.internal ?? storedRecord?.internal ?? false,
2371
+ };
2372
+ });
2373
+ const storedCandidates = Array.from(storedRecords.values()).map((record) => ({
2374
+ provider: record.provider,
2375
+ cwd: record.cwd,
2376
+ lastActivityAt: record.lastActivityAt ?? record.lastUserMessageAt ?? record.updatedAt,
2377
+ updatedAt: record.updatedAt,
2378
+ archivedAt: record.archivedAt ?? null,
2379
+ internal: record.internal ?? false,
2380
+ }));
2381
+ return resolveStructuredGenerationPreferredProviderForCwd({
2382
+ cwd: resolvedCwd,
2383
+ candidates: [...liveCandidates, ...storedCandidates],
2384
+ });
2385
+ }
2386
+ async resolveStructuredHelperProviders(cwd, reason) {
2387
+ let preferredProvider = null;
2388
+ try {
2389
+ preferredProvider = await this.resolvePreferredStructuredProviderForCwd(cwd);
2390
+ }
2391
+ catch (error) {
2392
+ this.sessionLogger.warn({ err: error, cwd, reason }, 'Failed to resolve preferred provider for structured helper generation');
2393
+ }
2394
+ return {
2395
+ preferredProvider,
2396
+ providers: resolveStructuredGenerationProviders({
2397
+ preferredProvider,
2398
+ reason,
2399
+ providers: DEFAULT_STRUCTURED_GENERATION_PROVIDERS,
2400
+ }),
2401
+ };
2402
+ }
2403
+ async generateCommitMessage(cwd, diffOverride) {
2404
+ const diff = diffOverride ??
2405
+ (await getCheckoutDiff(cwd, { mode: 'uncommitted', includeStructured: true }, { junctionHome: this.junctionHome }));
2356
2406
  const schema = z.object({
2357
2407
  message: z
2358
2408
  .string()
@@ -2383,6 +2433,7 @@ export class Session {
2383
2433
  patch.length > 0 ? patch : '(No diff available)',
2384
2434
  ].join('\n');
2385
2435
  try {
2436
+ const { preferredProvider, providers } = await this.resolveStructuredHelperProviders(cwd, 'commit');
2386
2437
  const result = await generateStructuredAgentResponseWithFallback({
2387
2438
  manager: this.agentManager,
2388
2439
  cwd,
@@ -2390,7 +2441,9 @@ export class Session {
2390
2441
  schema,
2391
2442
  schemaName: 'CommitMessage',
2392
2443
  maxRetries: 2,
2393
- providers: DEFAULT_STRUCTURED_GENERATION_PROVIDERS,
2444
+ providers,
2445
+ preferredProvider,
2446
+ reason: 'commit',
2394
2447
  agentConfigOverrides: {
2395
2448
  title: 'Commit generator',
2396
2449
  internal: true,
@@ -2406,12 +2459,13 @@ export class Session {
2406
2459
  throw error;
2407
2460
  }
2408
2461
  }
2409
- async generatePullRequestText(cwd, baseRef) {
2410
- const diff = await getCheckoutDiff(cwd, {
2411
- mode: 'base',
2412
- baseRef,
2413
- includeStructured: true,
2414
- }, { junctionHome: this.junctionHome });
2462
+ async generatePullRequestText(cwd, baseRef, diffOverride) {
2463
+ const diff = diffOverride ??
2464
+ (await getCheckoutDiff(cwd, {
2465
+ mode: 'base',
2466
+ baseRef,
2467
+ includeStructured: true,
2468
+ }, { junctionHome: this.junctionHome }));
2415
2469
  const schema = z.object({
2416
2470
  title: z.string().min(1).max(72),
2417
2471
  body: z.string().min(1),
@@ -2439,6 +2493,7 @@ export class Session {
2439
2493
  patch.length > 0 ? patch : '(No diff available)',
2440
2494
  ].join('\n');
2441
2495
  try {
2496
+ const { preferredProvider, providers } = await this.resolveStructuredHelperProviders(cwd, 'pr');
2442
2497
  return await generateStructuredAgentResponseWithFallback({
2443
2498
  manager: this.agentManager,
2444
2499
  cwd,
@@ -2446,7 +2501,9 @@ export class Session {
2446
2501
  schema,
2447
2502
  schemaName: 'PullRequest',
2448
2503
  maxRetries: 2,
2449
- providers: DEFAULT_STRUCTURED_GENERATION_PROVIDERS,
2504
+ providers,
2505
+ preferredProvider,
2506
+ reason: 'pr',
2450
2507
  agentConfigOverrides: {
2451
2508
  title: 'PR generator',
2452
2509
  internal: true,
@@ -2884,7 +2941,8 @@ export class Session {
2884
2941
  async handleCheckoutStatusRequest(msg) {
2885
2942
  const { cwd, requestId } = msg;
2886
2943
  try {
2887
- const status = await getCheckoutStatus(expandTilde(cwd), { junctionHome: this.junctionHome });
2944
+ const resolvedCwd = expandTilde(cwd);
2945
+ const status = await this.runWorkspaceGitRead(resolvedCwd, () => getCheckoutStatus(resolvedCwd, { junctionHome: this.junctionHome }));
2888
2946
  this.emit({
2889
2947
  type: 'checkout_status_response',
2890
2948
  payload: {
@@ -3273,9 +3331,7 @@ export class Session {
3273
3331
  if (target.subscriptions.size === 0) {
3274
3332
  return;
3275
3333
  }
3276
- const intervalMs = this.workspaceStatusHasActiveSubscribers(target)
3277
- ? WORKSPACE_STATUS_PR_ACTIVE_REFRESH_MS
3278
- : WORKSPACE_STATUS_PR_PASSIVE_REFRESH_MS;
3334
+ const intervalMs = this.resolveWorkspaceStatusPrRefreshIntervalMs(target);
3279
3335
  target.prRefreshInterval = setInterval(() => {
3280
3336
  this.scheduleWorkspaceStatusTargetRefresh(target, {
3281
3337
  includePr: true,
@@ -3298,6 +3354,16 @@ export class Session {
3298
3354
  });
3299
3355
  }, WORKSPACE_STATUS_GIT_REFRESH_MS);
3300
3356
  }
3357
+ resolveWorkspaceStatusPrRefreshIntervalMs(target) {
3358
+ if (this.workspaceStatusHasActiveSubscribers(target)) {
3359
+ return WORKSPACE_STATUS_PR_ACTIVE_REFRESH_MS;
3360
+ }
3361
+ const pullRequest = target.latestPayload?.pullRequest;
3362
+ const hasOpenPullRequest = pullRequest != null && !pullRequest.isMerged && pullRequest.state.toLowerCase() === 'open';
3363
+ return hasOpenPullRequest
3364
+ ? WORKSPACE_STATUS_PR_ACTIVE_REFRESH_MS
3365
+ : WORKSPACE_STATUS_PR_PASSIVE_REFRESH_MS;
3366
+ }
3301
3367
  removeWorkspaceStatusSubscription(subscriptionId) {
3302
3368
  const subscription = this.workspaceStatusSubscriptions.get(subscriptionId);
3303
3369
  if (!subscription) {
@@ -3432,6 +3498,7 @@ export class Session {
3432
3498
  || includePr
3433
3499
  || (previousHasLoadedPullRequest && !gitIdentityChanged);
3434
3500
  target.latestPayloadHasFullGitData = !snapshot.git.isGit || fullGit;
3501
+ this.updateWorkspaceStatusPrPolling(target);
3435
3502
  const fingerprint = this.workspaceStatusSnapshotFingerprint(snapshot);
3436
3503
  if (fingerprint !== target.latestFingerprint) {
3437
3504
  target.latestFingerprint = fingerprint;
@@ -3477,51 +3544,23 @@ export class Session {
3477
3544
  if (gitDir) {
3478
3545
  watchPaths.add(gitDir);
3479
3546
  }
3480
- let hasRecursiveRepoCoverage = false;
3481
- const allowRecursiveRepoWatch = process.platform !== 'linux';
3482
- for (const watchPath of watchPaths) {
3483
- const shouldTryRecursive = watchPath === repoWatchPath && allowRecursiveRepoWatch;
3484
- const createWatcher = (recursive) => watch(watchPath, { recursive }, () => {
3547
+ const watchSetup = this.startCheckoutWatchers({
3548
+ repoWatchPath,
3549
+ watchPaths,
3550
+ onChange: () => {
3485
3551
  this.scheduleWorkspaceStatusTargetRefresh(target);
3486
- });
3487
- let watcher = null;
3488
- let watcherIsRecursive = false;
3489
- try {
3490
- if (shouldTryRecursive) {
3491
- watcher = createWatcher(true);
3492
- watcherIsRecursive = true;
3493
- }
3494
- else {
3495
- watcher = createWatcher(false);
3496
- }
3497
- }
3498
- catch (error) {
3499
- if (shouldTryRecursive) {
3500
- try {
3501
- watcher = createWatcher(false);
3502
- this.sessionLogger.warn({ err: error, watchPath, cwd, remoteName }, 'Workspace status recursive watch unavailable; using non-recursive fallback');
3503
- }
3504
- catch (fallbackError) {
3505
- this.sessionLogger.warn({ err: fallbackError, watchPath, cwd, remoteName }, 'Failed to start workspace status watcher');
3506
- }
3507
- }
3508
- else {
3509
- this.sessionLogger.warn({ err: error, watchPath, cwd, remoteName }, 'Failed to start workspace status watcher');
3510
- }
3511
- }
3512
- if (!watcher) {
3513
- continue;
3514
- }
3515
- watcher.on('error', (error) => {
3516
- this.sessionLogger.warn({ err: error, watchPath, cwd, remoteName }, 'Workspace status watcher error');
3517
- });
3518
- target.watchers.push(watcher);
3519
- if (watchPath === repoWatchPath && watcherIsRecursive) {
3520
- hasRecursiveRepoCoverage = true;
3521
- }
3522
- }
3523
- if (target.watchers.length === 0 || !hasRecursiveRepoCoverage) {
3524
- this.sessionLogger.warn({
3552
+ },
3553
+ recursiveUnavailableMessage: 'Workspace status recursive watch unavailable; using non-recursive fallback',
3554
+ startFailedMessage: 'Failed to start workspace status watcher',
3555
+ watcherErrorMessage: 'Workspace status watcher error',
3556
+ context: { cwd, remoteName },
3557
+ });
3558
+ target.watchers = watchSetup.watchers;
3559
+ if (target.watchers.length === 0 || !watchSetup.hasRecursiveRepoCoverage) {
3560
+ const log = watchSetup.repoWatchPathMissing && watchSetup.failedWatcherCount === 0
3561
+ ? this.sessionLogger.debug.bind(this.sessionLogger)
3562
+ : this.sessionLogger.warn.bind(this.sessionLogger);
3563
+ log({
3525
3564
  cwd,
3526
3565
  remoteName,
3527
3566
  reason: target.watchers.length === 0 ? 'no_watchers' : 'missing_recursive_repo_root_coverage',
@@ -3665,7 +3704,107 @@ export class Session {
3665
3704
  return null;
3666
3705
  }
3667
3706
  }
3707
+ isExpectedMissingWatchPathError(error) {
3708
+ if (!error || typeof error !== 'object') {
3709
+ return false;
3710
+ }
3711
+ const code = 'code' in error && typeof error.code === 'string'
3712
+ ? error.code
3713
+ : null;
3714
+ return code === 'ENOENT' || code === 'ENOTDIR' || code === 'EPERM';
3715
+ }
3716
+ isWatchPathMissing(watchPath) {
3717
+ try {
3718
+ statSync(watchPath);
3719
+ return false;
3720
+ }
3721
+ catch (error) {
3722
+ return this.isExpectedMissingWatchPathError(error);
3723
+ }
3724
+ }
3725
+ startCheckoutWatchers(options) {
3726
+ const watchers = [];
3727
+ let hasRecursiveRepoCoverage = false;
3728
+ let repoWatchPathMissing = false;
3729
+ let failedWatcherCount = 0;
3730
+ const allowRecursiveRepoWatch = process.platform !== 'linux';
3731
+ for (const watchPath of options.watchPaths) {
3732
+ const shouldTryRecursive = watchPath === options.repoWatchPath && allowRecursiveRepoWatch;
3733
+ if (this.isWatchPathMissing(watchPath)) {
3734
+ if (watchPath === options.repoWatchPath) {
3735
+ repoWatchPathMissing = true;
3736
+ }
3737
+ continue;
3738
+ }
3739
+ const createWatcher = (recursive) => watch(watchPath, { recursive }, () => {
3740
+ options.onChange();
3741
+ });
3742
+ let watcher = null;
3743
+ let watcherIsRecursive = false;
3744
+ try {
3745
+ if (shouldTryRecursive) {
3746
+ watcher = createWatcher(true);
3747
+ watcherIsRecursive = true;
3748
+ }
3749
+ else {
3750
+ watcher = createWatcher(false);
3751
+ }
3752
+ }
3753
+ catch (error) {
3754
+ if (shouldTryRecursive) {
3755
+ try {
3756
+ watcher = createWatcher(false);
3757
+ this.sessionLogger.warn({ err: error, watchPath, ...options.context }, options.recursiveUnavailableMessage);
3758
+ }
3759
+ catch (fallbackError) {
3760
+ if (this.isExpectedMissingWatchPathError(fallbackError)
3761
+ && this.isWatchPathMissing(watchPath)) {
3762
+ if (watchPath === options.repoWatchPath) {
3763
+ repoWatchPathMissing = true;
3764
+ }
3765
+ continue;
3766
+ }
3767
+ failedWatcherCount += 1;
3768
+ this.sessionLogger.warn({ err: fallbackError, watchPath, ...options.context }, options.startFailedMessage);
3769
+ }
3770
+ }
3771
+ else {
3772
+ if (this.isExpectedMissingWatchPathError(error) && this.isWatchPathMissing(watchPath)) {
3773
+ if (watchPath === options.repoWatchPath) {
3774
+ repoWatchPathMissing = true;
3775
+ }
3776
+ continue;
3777
+ }
3778
+ failedWatcherCount += 1;
3779
+ this.sessionLogger.warn({ err: error, watchPath, ...options.context }, options.startFailedMessage);
3780
+ }
3781
+ }
3782
+ if (!watcher) {
3783
+ continue;
3784
+ }
3785
+ watcher.on('error', (error) => {
3786
+ if (this.isExpectedMissingWatchPathError(error) && this.isWatchPathMissing(watchPath)) {
3787
+ return;
3788
+ }
3789
+ this.sessionLogger.warn({ err: error, watchPath, ...options.context }, options.watcherErrorMessage);
3790
+ });
3791
+ watchers.push(watcher);
3792
+ if (watchPath === options.repoWatchPath && watcherIsRecursive) {
3793
+ hasRecursiveRepoCoverage = true;
3794
+ }
3795
+ }
3796
+ return {
3797
+ watchers,
3798
+ hasRecursiveRepoCoverage,
3799
+ repoWatchPathMissing,
3800
+ failedWatcherCount,
3801
+ };
3802
+ }
3668
3803
  scheduleCheckoutDiffTargetRefresh(target) {
3804
+ if (this.isWorkspaceGitWritePending(target.workspaceGitKey)) {
3805
+ target.refreshQueuedAfterWrite = true;
3806
+ return;
3807
+ }
3669
3808
  if (target.debounceTimer) {
3670
3809
  clearTimeout(target.debounceTimer);
3671
3810
  }
@@ -3695,11 +3834,11 @@ export class Session {
3695
3834
  const diffCwd = options?.diffCwd ?? cwd;
3696
3835
  const detail = options?.detail ?? 'full';
3697
3836
  try {
3698
- const diffResult = await getCheckoutDiff(diffCwd, {
3837
+ const diffResult = await this.runWorkspaceGitRead(diffCwd, () => getCheckoutDiff(diffCwd, {
3699
3838
  mode: compare.mode,
3700
3839
  baseRef: compare.baseRef,
3701
3840
  includeStructured: true,
3702
- }, { junctionHome: this.junctionHome });
3841
+ }, { junctionHome: this.junctionHome }));
3703
3842
  const files = [...(diffResult.structured ?? [])].map((file) => detail === 'summary'
3704
3843
  ? {
3705
3844
  ...file,
@@ -3759,10 +3898,12 @@ export class Session {
3759
3898
  return existing;
3760
3899
  }
3761
3900
  const watchRoot = await this.resolveCheckoutWatchRoot(cwd);
3901
+ const workspaceGitKey = watchRoot ?? cwd;
3762
3902
  const target = {
3763
3903
  key: targetKey,
3764
3904
  cwd,
3765
3905
  diffCwd: watchRoot ?? cwd,
3906
+ workspaceGitKey,
3766
3907
  compare,
3767
3908
  detail,
3768
3909
  subscriptions: new Set(),
@@ -3771,6 +3912,7 @@ export class Session {
3771
3912
  debounceTimer: null,
3772
3913
  refreshPromise: null,
3773
3914
  refreshQueued: false,
3915
+ refreshQueuedAfterWrite: false,
3774
3916
  latestPayload: null,
3775
3917
  latestFingerprint: null,
3776
3918
  };
@@ -3780,55 +3922,27 @@ export class Session {
3780
3922
  if (gitDir) {
3781
3923
  watchPaths.add(gitDir);
3782
3924
  }
3783
- let hasRecursiveRepoCoverage = false;
3784
- const allowRecursiveRepoWatch = process.platform !== 'linux';
3785
- for (const watchPath of watchPaths) {
3786
- const shouldTryRecursive = watchPath === repoWatchPath && allowRecursiveRepoWatch;
3787
- const createWatcher = (recursive) => watch(watchPath, { recursive }, () => {
3925
+ const watchSetup = this.startCheckoutWatchers({
3926
+ repoWatchPath,
3927
+ watchPaths,
3928
+ onChange: () => {
3788
3929
  this.scheduleCheckoutDiffTargetRefresh(target);
3789
- });
3790
- let watcher = null;
3791
- let watcherIsRecursive = false;
3792
- try {
3793
- if (shouldTryRecursive) {
3794
- watcher = createWatcher(true);
3795
- watcherIsRecursive = true;
3796
- }
3797
- else {
3798
- watcher = createWatcher(false);
3799
- }
3800
- }
3801
- catch (error) {
3802
- if (shouldTryRecursive) {
3803
- try {
3804
- watcher = createWatcher(false);
3805
- this.sessionLogger.warn({ err: error, watchPath, cwd, compare }, 'Checkout diff recursive watch unavailable; using non-recursive fallback');
3806
- }
3807
- catch (fallbackError) {
3808
- this.sessionLogger.warn({ err: fallbackError, watchPath, cwd, compare }, 'Failed to start checkout diff watcher');
3809
- }
3810
- }
3811
- else {
3812
- this.sessionLogger.warn({ err: error, watchPath, cwd, compare }, 'Failed to start checkout diff watcher');
3813
- }
3814
- }
3815
- if (!watcher) {
3816
- continue;
3817
- }
3818
- watcher.on('error', (error) => {
3819
- this.sessionLogger.warn({ err: error, watchPath, cwd, compare }, 'Checkout diff watcher error');
3820
- });
3821
- target.watchers.push(watcher);
3822
- if (watchPath === repoWatchPath && watcherIsRecursive) {
3823
- hasRecursiveRepoCoverage = true;
3824
- }
3825
- }
3826
- const missingRepoCoverage = !hasRecursiveRepoCoverage;
3930
+ },
3931
+ recursiveUnavailableMessage: 'Checkout diff recursive watch unavailable; using non-recursive fallback',
3932
+ startFailedMessage: 'Failed to start checkout diff watcher',
3933
+ watcherErrorMessage: 'Checkout diff watcher error',
3934
+ context: { cwd, compare },
3935
+ });
3936
+ target.watchers = watchSetup.watchers;
3937
+ const missingRepoCoverage = !watchSetup.hasRecursiveRepoCoverage;
3827
3938
  if (target.watchers.length === 0 || missingRepoCoverage) {
3828
3939
  target.fallbackRefreshInterval = setInterval(() => {
3829
3940
  this.scheduleCheckoutDiffTargetRefresh(target);
3830
3941
  }, CHECKOUT_DIFF_FALLBACK_REFRESH_MS);
3831
- this.sessionLogger.warn({
3942
+ const log = watchSetup.repoWatchPathMissing && watchSetup.failedWatcherCount === 0
3943
+ ? this.sessionLogger.debug.bind(this.sessionLogger)
3944
+ : this.sessionLogger.warn.bind(this.sessionLogger);
3945
+ log({
3832
3946
  cwd,
3833
3947
  compare,
3834
3948
  intervalMs: CHECKOUT_DIFF_FALLBACK_REFRESH_MS,
@@ -3848,13 +3962,26 @@ export class Session {
3848
3962
  this.checkoutDiffSubscriptions.set(msg.subscriptionId, {
3849
3963
  targetKey: target.key,
3850
3964
  });
3851
- const snapshot = target.latestPayload ??
3852
- (await this.computeCheckoutDiffSnapshot(cwd, compare, {
3965
+ if (target.refreshPromise) {
3966
+ await target.refreshPromise;
3967
+ }
3968
+ const canReuseLatestPayload = target.latestPayload !== null &&
3969
+ !target.refreshQueuedAfterWrite &&
3970
+ !this.isWorkspaceGitWritePending(target.workspaceGitKey);
3971
+ const shouldPreserveQueuedFingerprint = !canReuseLatestPayload && target.refreshQueuedAfterWrite;
3972
+ const snapshot = canReuseLatestPayload
3973
+ ? target.latestPayload
3974
+ : await this.computeCheckoutDiffSnapshot(cwd, compare, {
3853
3975
  diffCwd: target.diffCwd,
3854
3976
  detail: target.detail,
3855
- }));
3977
+ });
3978
+ if (!snapshot) {
3979
+ throw new Error('Checkout diff snapshot was unavailable');
3980
+ }
3856
3981
  target.latestPayload = snapshot;
3857
- target.latestFingerprint = this.checkoutDiffSnapshotFingerprint(snapshot);
3982
+ if (!shouldPreserveQueuedFingerprint) {
3983
+ target.latestFingerprint = this.checkoutDiffSnapshotFingerprint(snapshot);
3984
+ }
3858
3985
  this.emit({
3859
3986
  type: 'subscribe_checkout_diff_response',
3860
3987
  payload: {
@@ -3876,21 +4003,105 @@ export class Session {
3876
4003
  this.scheduleCheckoutDiffTargetRefresh(target);
3877
4004
  }
3878
4005
  }
4006
+ getWorkspaceGitOperationState(workspaceGitKey) {
4007
+ const existing = this.workspaceGitOperationStates.get(workspaceGitKey);
4008
+ if (existing) {
4009
+ return existing;
4010
+ }
4011
+ const created = {
4012
+ writeBarrier: Promise.resolve(),
4013
+ pendingWrites: 0,
4014
+ activeReaders: 0,
4015
+ readerDrain: Promise.resolve(),
4016
+ resolveReaderDrain: null,
4017
+ };
4018
+ this.workspaceGitOperationStates.set(workspaceGitKey, created);
4019
+ return created;
4020
+ }
4021
+ isWorkspaceGitWritePending(workspaceGitKey) {
4022
+ return (this.workspaceGitOperationStates.get(workspaceGitKey)?.pendingWrites ?? 0) > 0;
4023
+ }
4024
+ async resolveWorkspaceGitOperationKey(cwd) {
4025
+ const resolvedCwd = expandTilde(cwd);
4026
+ return (await this.resolveCheckoutWatchRoot(resolvedCwd)) ?? resolvedCwd;
4027
+ }
4028
+ flushQueuedCheckoutDiffRefreshesForWorkspace(workspaceGitKey) {
4029
+ for (const target of this.checkoutDiffTargets.values()) {
4030
+ if (target.workspaceGitKey !== workspaceGitKey || !target.refreshQueuedAfterWrite) {
4031
+ continue;
4032
+ }
4033
+ target.refreshQueuedAfterWrite = false;
4034
+ this.scheduleCheckoutDiffTargetRefresh(target);
4035
+ }
4036
+ }
4037
+ async runWorkspaceGitRead(cwd, operation) {
4038
+ const workspaceGitKey = await this.resolveWorkspaceGitOperationKey(cwd);
4039
+ const state = this.getWorkspaceGitOperationState(workspaceGitKey);
4040
+ while (state.pendingWrites > 0) {
4041
+ await state.writeBarrier;
4042
+ }
4043
+ state.activeReaders += 1;
4044
+ if (state.activeReaders === 1) {
4045
+ state.readerDrain = new Promise((resolve) => {
4046
+ state.resolveReaderDrain = resolve;
4047
+ });
4048
+ }
4049
+ try {
4050
+ return await operation();
4051
+ }
4052
+ finally {
4053
+ state.activeReaders = Math.max(0, state.activeReaders - 1);
4054
+ if (state.activeReaders === 0) {
4055
+ state.resolveReaderDrain?.();
4056
+ state.resolveReaderDrain = null;
4057
+ state.readerDrain = Promise.resolve();
4058
+ }
4059
+ }
4060
+ }
4061
+ async runWorkspaceGitWrite(cwd, operation) {
4062
+ const workspaceGitKey = await this.resolveWorkspaceGitOperationKey(cwd);
4063
+ const state = this.getWorkspaceGitOperationState(workspaceGitKey);
4064
+ const previousBarrier = state.writeBarrier;
4065
+ let releaseCurrentBarrier = () => { };
4066
+ const currentBarrier = new Promise((resolve) => {
4067
+ releaseCurrentBarrier = resolve;
4068
+ });
4069
+ state.pendingWrites += 1;
4070
+ state.writeBarrier = previousBarrier.then(() => currentBarrier);
4071
+ try {
4072
+ await previousBarrier;
4073
+ while (state.activeReaders > 0) {
4074
+ await state.readerDrain;
4075
+ }
4076
+ return await operation();
4077
+ }
4078
+ finally {
4079
+ state.pendingWrites = Math.max(0, state.pendingWrites - 1);
4080
+ releaseCurrentBarrier();
4081
+ if (state.pendingWrites === 0) {
4082
+ state.writeBarrier = Promise.resolve();
4083
+ this.flushQueuedCheckoutDiffRefreshesForWorkspace(workspaceGitKey);
4084
+ }
4085
+ }
4086
+ }
3879
4087
  async handleCheckoutCommitRequest(msg) {
3880
4088
  const { cwd, requestId } = msg;
3881
4089
  try {
3882
4090
  let message = msg.message?.trim() ?? '';
3883
4091
  if (!message) {
3884
- message = await this.generateCommitMessage(cwd);
4092
+ const diff = await this.runWorkspaceGitRead(cwd, () => getCheckoutDiff(cwd, { mode: 'uncommitted', includeStructured: true }, { junctionHome: this.junctionHome }));
4093
+ message = await this.generateCommitMessage(cwd, diff);
3885
4094
  }
3886
4095
  if (!message) {
3887
4096
  throw new Error('Commit message is required');
3888
4097
  }
3889
- await commitChanges(cwd, {
3890
- message,
3891
- addAll: msg.addAll ?? true,
4098
+ await this.runWorkspaceGitWrite(cwd, async () => {
4099
+ await commitChanges(cwd, {
4100
+ message,
4101
+ addAll: msg.addAll ?? true,
4102
+ });
4103
+ this.scheduleCheckoutDiffRefreshForCwd(cwd);
3892
4104
  });
3893
- this.scheduleCheckoutDiffRefreshForCwd(cwd);
3894
4105
  this.scheduleWorkspaceStatusRefreshForCwd(cwd);
3895
4106
  this.emit({
3896
4107
  type: 'checkout_commit_response',
@@ -3917,45 +4128,54 @@ export class Session {
3917
4128
  async handleCheckoutMergeRequest(msg) {
3918
4129
  const { cwd, requestId } = msg;
3919
4130
  try {
3920
- const status = await getCheckoutStatus(cwd, { junctionHome: this.junctionHome });
3921
- if (!status.isGit) {
3922
- try {
3923
- await execAsync('git rev-parse --is-inside-work-tree', {
4131
+ const mergeOperationCwd = await resolveMergeToBaseOperationCwd(cwd, { baseRef: msg.baseRef }, { junctionHome: this.junctionHome });
4132
+ await this.runWorkspaceGitWrite(mergeOperationCwd, async () => {
4133
+ const status = await getCheckoutStatus(cwd, { junctionHome: this.junctionHome });
4134
+ if (!status.isGit) {
4135
+ try {
4136
+ await execAsync('git rev-parse --is-inside-work-tree', {
4137
+ cwd,
4138
+ env: READ_ONLY_GIT_ENV,
4139
+ });
4140
+ }
4141
+ catch (error) {
4142
+ const details = typeof error?.stderr === 'string'
4143
+ ? String(error.stderr).trim()
4144
+ : error instanceof Error
4145
+ ? error.message
4146
+ : String(error);
4147
+ throw new Error(`Not a git repository: ${cwd}\n${details}`.trim());
4148
+ }
4149
+ }
4150
+ if (msg.requireCleanTarget) {
4151
+ const { stdout } = await execAsync('git status --porcelain', {
3924
4152
  cwd,
3925
4153
  env: READ_ONLY_GIT_ENV,
3926
4154
  });
4155
+ if (stdout.trim().length > 0) {
4156
+ throw new Error('Working directory has uncommitted changes.');
4157
+ }
3927
4158
  }
3928
- catch (error) {
3929
- const details = typeof error?.stderr === 'string'
3930
- ? String(error.stderr).trim()
3931
- : error instanceof Error
3932
- ? error.message
3933
- : String(error);
3934
- throw new Error(`Not a git repository: ${cwd}\n${details}`.trim());
4159
+ let baseRef = msg.baseRef ?? (status.isGit ? status.baseRef : null);
4160
+ if (!baseRef) {
4161
+ throw new Error('Base branch is required for merge');
3935
4162
  }
3936
- }
3937
- if (msg.requireCleanTarget) {
3938
- const { stdout } = await execAsync('git status --porcelain', {
3939
- cwd,
3940
- env: READ_ONLY_GIT_ENV,
3941
- });
3942
- if (stdout.trim().length > 0) {
3943
- throw new Error('Working directory has uncommitted changes.');
4163
+ if (baseRef.startsWith('origin/')) {
4164
+ baseRef = baseRef.slice('origin/'.length);
3944
4165
  }
3945
- }
3946
- let baseRef = msg.baseRef ?? (status.isGit ? status.baseRef : null);
3947
- if (!baseRef) {
3948
- throw new Error('Base branch is required for merge');
3949
- }
3950
- if (baseRef.startsWith('origin/')) {
3951
- baseRef = baseRef.slice('origin/'.length);
3952
- }
3953
- await mergeToBase(cwd, {
3954
- baseRef,
3955
- mode: msg.strategy === 'squash' ? 'squash' : 'merge',
3956
- }, { junctionHome: this.junctionHome });
3957
- this.scheduleCheckoutDiffRefreshForCwd(cwd);
4166
+ await mergeToBase(cwd, {
4167
+ baseRef,
4168
+ mode: msg.strategy === 'squash' ? 'squash' : 'merge',
4169
+ }, { junctionHome: this.junctionHome });
4170
+ this.scheduleCheckoutDiffRefreshForCwd(cwd);
4171
+ if (resolve(mergeOperationCwd) !== resolve(cwd)) {
4172
+ this.scheduleCheckoutDiffRefreshForCwd(mergeOperationCwd);
4173
+ }
4174
+ });
3958
4175
  this.scheduleWorkspaceStatusRefreshForCwd(cwd, { includePr: true });
4176
+ if (resolve(mergeOperationCwd) !== resolve(cwd)) {
4177
+ this.scheduleWorkspaceStatusRefreshForCwd(mergeOperationCwd, { includePr: true });
4178
+ }
3959
4179
  this.emit({
3960
4180
  type: 'checkout_merge_response',
3961
4181
  payload: {
@@ -3981,21 +4201,23 @@ export class Session {
3981
4201
  async handleCheckoutMergeFromBaseRequest(msg) {
3982
4202
  const { cwd, requestId } = msg;
3983
4203
  try {
3984
- if (msg.requireCleanTarget ?? true) {
3985
- const { stdout } = await execAsync('git status --porcelain', {
3986
- cwd,
3987
- env: READ_ONLY_GIT_ENV,
3988
- });
3989
- if (stdout.trim().length > 0) {
3990
- throw new Error('Working directory has uncommitted changes.');
4204
+ await this.runWorkspaceGitWrite(cwd, async () => {
4205
+ if (msg.requireCleanTarget ?? true) {
4206
+ const { stdout } = await execAsync('git status --porcelain', {
4207
+ cwd,
4208
+ env: READ_ONLY_GIT_ENV,
4209
+ });
4210
+ if (stdout.trim().length > 0) {
4211
+ throw new Error('Working directory has uncommitted changes.');
4212
+ }
3991
4213
  }
3992
- }
3993
- await mergeFromBase(cwd, {
3994
- baseRef: msg.baseRef,
3995
- remoteName: msg.remoteName,
3996
- requireCleanTarget: msg.requireCleanTarget ?? true,
4214
+ await mergeFromBase(cwd, {
4215
+ baseRef: msg.baseRef,
4216
+ remoteName: msg.remoteName,
4217
+ requireCleanTarget: msg.requireCleanTarget ?? true,
4218
+ });
4219
+ this.scheduleCheckoutDiffRefreshForCwd(cwd);
3997
4220
  });
3998
- this.scheduleCheckoutDiffRefreshForCwd(cwd);
3999
4221
  this.scheduleWorkspaceStatusRefreshForCwd(cwd, { includePr: true });
4000
4222
  this.emit({
4001
4223
  type: 'checkout_merge_from_base_response',
@@ -4022,7 +4244,7 @@ export class Session {
4022
4244
  async handleCheckoutPushRequest(msg) {
4023
4245
  const { cwd, requestId } = msg;
4024
4246
  try {
4025
- await pushCurrentBranch(cwd, { remoteName: msg.remoteName });
4247
+ await this.runWorkspaceGitWrite(cwd, () => pushCurrentBranch(cwd, { remoteName: msg.remoteName }));
4026
4248
  this.scheduleWorkspaceStatusRefreshForCwd(cwd, { includePr: true });
4027
4249
  this.emit({
4028
4250
  type: 'checkout_push_response',
@@ -4052,18 +4274,23 @@ export class Session {
4052
4274
  let title = msg.title?.trim() ?? '';
4053
4275
  let body = msg.body?.trim() ?? '';
4054
4276
  if (!title || !body) {
4055
- const generated = await this.generatePullRequestText(cwd, msg.baseRef);
4277
+ const diff = await this.runWorkspaceGitRead(cwd, () => getCheckoutDiff(cwd, {
4278
+ mode: 'base',
4279
+ baseRef: msg.baseRef,
4280
+ includeStructured: true,
4281
+ }, { junctionHome: this.junctionHome }));
4282
+ const generated = await this.generatePullRequestText(cwd, msg.baseRef, diff);
4056
4283
  if (!title)
4057
4284
  title = generated.title;
4058
4285
  if (!body)
4059
4286
  body = generated.body;
4060
4287
  }
4061
- const result = await createPullRequest(cwd, {
4288
+ const result = await this.runWorkspaceGitWrite(cwd, () => createPullRequest(cwd, {
4062
4289
  title,
4063
4290
  body,
4064
4291
  base: msg.baseRef,
4065
4292
  remoteName: msg.remoteName,
4066
- });
4293
+ }));
4067
4294
  this.scheduleWorkspaceStatusRefreshForCwd(cwd, { includePr: true });
4068
4295
  this.emit({
4069
4296
  type: 'checkout_pr_create_response',
@@ -4121,11 +4348,11 @@ export class Session {
4121
4348
  const cwd = expandTilde(msg.cwd);
4122
4349
  const { requestId } = msg;
4123
4350
  try {
4124
- const result = await searchPullRequests(cwd, {
4351
+ const result = await this.runWorkspaceGitRead(cwd, () => searchPullRequests(cwd, {
4125
4352
  query: msg.query,
4126
4353
  limit: msg.limit,
4127
4354
  remoteName: msg.remoteName,
4128
- });
4355
+ }));
4129
4356
  this.emit({
4130
4357
  type: 'checkout_pr_search_response',
4131
4358
  payload: {
@@ -4153,7 +4380,7 @@ export class Session {
4153
4380
  async handleCheckoutPrFailureLogsRequest(msg) {
4154
4381
  const { cwd, requestId } = msg;
4155
4382
  try {
4156
- const result = await getPullRequestFailureLogs(cwd, { remoteName: msg.remoteName });
4383
+ const result = await this.runWorkspaceGitRead(cwd, () => getPullRequestFailureLogs(cwd, { remoteName: msg.remoteName }));
4157
4384
  this.emit({
4158
4385
  type: 'checkout_pr_failure_logs_response',
4159
4386
  payload: {
@@ -4536,8 +4763,8 @@ export class Session {
4536
4763
  */
4537
4764
  async handleWorkspaceFileExplorerRequest(request) {
4538
4765
  const { cwd, path: requestedPath = '.', mode, requestId, ref } = request;
4766
+ const root = expandTilde(cwd);
4539
4767
  try {
4540
- const root = expandTilde(cwd);
4541
4768
  if (ref) {
4542
4769
  this.assertSafeGitRef(ref, 'workspace file ref');
4543
4770
  }
@@ -4584,6 +4811,25 @@ export class Session {
4584
4811
  }
4585
4812
  }
4586
4813
  catch (error) {
4814
+ if (mode === 'file'
4815
+ && request.allowMissing
4816
+ && isWorkspaceExplorerMissingPathError(error)
4817
+ && (ref != null || existsSync(root))) {
4818
+ this.emit({
4819
+ type: 'workspace_file_explorer_response',
4820
+ payload: {
4821
+ cwd,
4822
+ path: requestedPath,
4823
+ ref: ref ?? null,
4824
+ mode,
4825
+ directory: null,
4826
+ file: null,
4827
+ error: null,
4828
+ requestId,
4829
+ },
4830
+ });
4831
+ return;
4832
+ }
4587
4833
  const log = isWorkspaceExplorerMissingPathError(error)
4588
4834
  ? this.sessionLogger.debug.bind(this.sessionLogger)
4589
4835
  : this.sessionLogger.error.bind(this.sessionLogger);