@junctionpanel/server 0.1.54 → 0.1.56
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.
- package/dist/server/client/daemon-client.d.ts +13 -0
- package/dist/server/client/daemon-client.d.ts.map +1 -1
- package/dist/server/client/daemon-client.js +89 -18
- package/dist/server/client/daemon-client.js.map +1 -1
- package/dist/server/server/agent/activity-curator.d.ts.map +1 -1
- package/dist/server/server/agent/activity-curator.js +12 -0
- package/dist/server/server/agent/activity-curator.js.map +1 -1
- package/dist/server/server/agent/agent-manager.d.ts +8 -0
- package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
- package/dist/server/server/agent/agent-manager.js +175 -29
- package/dist/server/server/agent/agent-manager.js.map +1 -1
- package/dist/server/server/agent/agent-metadata-generator.d.ts.map +1 -1
- package/dist/server/server/agent/agent-metadata-generator.js +11 -2
- package/dist/server/server/agent/agent-metadata-generator.js.map +1 -1
- package/dist/server/server/agent/agent-projections.d.ts.map +1 -1
- package/dist/server/server/agent/agent-projections.js +0 -1
- package/dist/server/server/agent/agent-projections.js.map +1 -1
- package/dist/server/server/agent/agent-response-loop.d.ts +27 -0
- package/dist/server/server/agent/agent-response-loop.d.ts.map +1 -1
- package/dist/server/server/agent/agent-response-loop.js +78 -4
- package/dist/server/server/agent/agent-response-loop.js.map +1 -1
- package/dist/server/server/agent/agent-sdk-types.d.ts +17 -1
- package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
- package/dist/server/server/agent/agent-sdk-types.js.map +1 -1
- package/dist/server/server/agent/agent-storage.d.ts +0 -3
- package/dist/server/server/agent/agent-storage.d.ts.map +1 -1
- package/dist/server/server/agent/agent-storage.js +0 -1
- package/dist/server/server/agent/agent-storage.js.map +1 -1
- package/dist/server/server/agent/provider-model-cache.d.ts +14 -0
- package/dist/server/server/agent/provider-model-cache.d.ts.map +1 -0
- package/dist/server/server/agent/provider-model-cache.js +71 -0
- package/dist/server/server/agent/provider-model-cache.js.map +1 -0
- package/dist/server/server/agent/providers/claude/model-catalog.d.ts.map +1 -1
- package/dist/server/server/agent/providers/claude/model-catalog.js +15 -1
- package/dist/server/server/agent/providers/claude/model-catalog.js.map +1 -1
- package/dist/server/server/agent/providers/claude-agent.d.ts +1 -0
- package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/claude-agent.js +43 -12
- package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
- package/dist/server/server/agent/providers/gemini-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/gemini-agent.js +36 -1
- package/dist/server/server/agent/providers/gemini-agent.js.map +1 -1
- package/dist/server/server/agent/providers/opencode-agent.js +8 -2
- package/dist/server/server/agent/providers/opencode-agent.js.map +1 -1
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts.map +1 -1
- package/dist/server/server/persistence-hooks.js +11 -1
- package/dist/server/server/persistence-hooks.js.map +1 -1
- package/dist/server/server/session.d.ts +13 -0
- package/dist/server/server/session.d.ts.map +1 -1
- package/dist/server/server/session.js +411 -170
- package/dist/server/server/session.js.map +1 -1
- package/dist/server/shared/messages.d.ts +16 -0
- package/dist/server/shared/messages.d.ts.map +1 -1
- package/dist/server/shared/messages.js +19 -0
- package/dist/server/shared/messages.js.map +1 -1
- package/dist/server/shared/permission-questions.d.ts +43 -0
- package/dist/server/shared/permission-questions.d.ts.map +1 -0
- package/dist/server/shared/permission-questions.js +234 -0
- package/dist/server/shared/permission-questions.js.map +1 -0
- package/dist/server/utils/checkout-git.d.ts +1 -0
- package/dist/server/utils/checkout-git.d.ts.map +1 -1
- package/dist/server/utils/checkout-git.js +19 -6
- package/dist/server/utils/checkout-git.js.map +1 -1
- 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,
|
|
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
|
|
2355
|
-
const
|
|
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
|
|
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 =
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
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
|
|
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
|
|
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.
|
|
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,11 @@ export class Session {
|
|
|
3298
3354
|
});
|
|
3299
3355
|
}, WORKSPACE_STATUS_GIT_REFRESH_MS);
|
|
3300
3356
|
}
|
|
3357
|
+
resolveWorkspaceStatusPrRefreshIntervalMs(target) {
|
|
3358
|
+
return this.workspaceStatusHasActiveSubscribers(target)
|
|
3359
|
+
? WORKSPACE_STATUS_PR_ACTIVE_REFRESH_MS
|
|
3360
|
+
: WORKSPACE_STATUS_PR_PASSIVE_REFRESH_MS;
|
|
3361
|
+
}
|
|
3301
3362
|
removeWorkspaceStatusSubscription(subscriptionId) {
|
|
3302
3363
|
const subscription = this.workspaceStatusSubscriptions.get(subscriptionId);
|
|
3303
3364
|
if (!subscription) {
|
|
@@ -3432,6 +3493,7 @@ export class Session {
|
|
|
3432
3493
|
|| includePr
|
|
3433
3494
|
|| (previousHasLoadedPullRequest && !gitIdentityChanged);
|
|
3434
3495
|
target.latestPayloadHasFullGitData = !snapshot.git.isGit || fullGit;
|
|
3496
|
+
this.updateWorkspaceStatusPrPolling(target);
|
|
3435
3497
|
const fingerprint = this.workspaceStatusSnapshotFingerprint(snapshot);
|
|
3436
3498
|
if (fingerprint !== target.latestFingerprint) {
|
|
3437
3499
|
target.latestFingerprint = fingerprint;
|
|
@@ -3477,51 +3539,23 @@ export class Session {
|
|
|
3477
3539
|
if (gitDir) {
|
|
3478
3540
|
watchPaths.add(gitDir);
|
|
3479
3541
|
}
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
const createWatcher = (recursive) => watch(watchPath, { recursive }, () => {
|
|
3542
|
+
const watchSetup = this.startCheckoutWatchers({
|
|
3543
|
+
repoWatchPath,
|
|
3544
|
+
watchPaths,
|
|
3545
|
+
onChange: () => {
|
|
3485
3546
|
this.scheduleWorkspaceStatusTargetRefresh(target);
|
|
3486
|
-
}
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
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({
|
|
3547
|
+
},
|
|
3548
|
+
recursiveUnavailableMessage: 'Workspace status recursive watch unavailable; using non-recursive fallback',
|
|
3549
|
+
startFailedMessage: 'Failed to start workspace status watcher',
|
|
3550
|
+
watcherErrorMessage: 'Workspace status watcher error',
|
|
3551
|
+
context: { cwd, remoteName },
|
|
3552
|
+
});
|
|
3553
|
+
target.watchers = watchSetup.watchers;
|
|
3554
|
+
if (target.watchers.length === 0 || !watchSetup.hasRecursiveRepoCoverage) {
|
|
3555
|
+
const log = watchSetup.repoWatchPathMissing && watchSetup.failedWatcherCount === 0
|
|
3556
|
+
? this.sessionLogger.debug.bind(this.sessionLogger)
|
|
3557
|
+
: this.sessionLogger.warn.bind(this.sessionLogger);
|
|
3558
|
+
log({
|
|
3525
3559
|
cwd,
|
|
3526
3560
|
remoteName,
|
|
3527
3561
|
reason: target.watchers.length === 0 ? 'no_watchers' : 'missing_recursive_repo_root_coverage',
|
|
@@ -3665,7 +3699,107 @@ export class Session {
|
|
|
3665
3699
|
return null;
|
|
3666
3700
|
}
|
|
3667
3701
|
}
|
|
3702
|
+
isExpectedMissingWatchPathError(error) {
|
|
3703
|
+
if (!error || typeof error !== 'object') {
|
|
3704
|
+
return false;
|
|
3705
|
+
}
|
|
3706
|
+
const code = 'code' in error && typeof error.code === 'string'
|
|
3707
|
+
? error.code
|
|
3708
|
+
: null;
|
|
3709
|
+
return code === 'ENOENT' || code === 'ENOTDIR' || code === 'EPERM';
|
|
3710
|
+
}
|
|
3711
|
+
isWatchPathMissing(watchPath) {
|
|
3712
|
+
try {
|
|
3713
|
+
statSync(watchPath);
|
|
3714
|
+
return false;
|
|
3715
|
+
}
|
|
3716
|
+
catch (error) {
|
|
3717
|
+
return this.isExpectedMissingWatchPathError(error);
|
|
3718
|
+
}
|
|
3719
|
+
}
|
|
3720
|
+
startCheckoutWatchers(options) {
|
|
3721
|
+
const watchers = [];
|
|
3722
|
+
let hasRecursiveRepoCoverage = false;
|
|
3723
|
+
let repoWatchPathMissing = false;
|
|
3724
|
+
let failedWatcherCount = 0;
|
|
3725
|
+
const allowRecursiveRepoWatch = process.platform !== 'linux';
|
|
3726
|
+
for (const watchPath of options.watchPaths) {
|
|
3727
|
+
const shouldTryRecursive = watchPath === options.repoWatchPath && allowRecursiveRepoWatch;
|
|
3728
|
+
if (this.isWatchPathMissing(watchPath)) {
|
|
3729
|
+
if (watchPath === options.repoWatchPath) {
|
|
3730
|
+
repoWatchPathMissing = true;
|
|
3731
|
+
}
|
|
3732
|
+
continue;
|
|
3733
|
+
}
|
|
3734
|
+
const createWatcher = (recursive) => watch(watchPath, { recursive }, () => {
|
|
3735
|
+
options.onChange();
|
|
3736
|
+
});
|
|
3737
|
+
let watcher = null;
|
|
3738
|
+
let watcherIsRecursive = false;
|
|
3739
|
+
try {
|
|
3740
|
+
if (shouldTryRecursive) {
|
|
3741
|
+
watcher = createWatcher(true);
|
|
3742
|
+
watcherIsRecursive = true;
|
|
3743
|
+
}
|
|
3744
|
+
else {
|
|
3745
|
+
watcher = createWatcher(false);
|
|
3746
|
+
}
|
|
3747
|
+
}
|
|
3748
|
+
catch (error) {
|
|
3749
|
+
if (shouldTryRecursive) {
|
|
3750
|
+
try {
|
|
3751
|
+
watcher = createWatcher(false);
|
|
3752
|
+
this.sessionLogger.warn({ err: error, watchPath, ...options.context }, options.recursiveUnavailableMessage);
|
|
3753
|
+
}
|
|
3754
|
+
catch (fallbackError) {
|
|
3755
|
+
if (this.isExpectedMissingWatchPathError(fallbackError)
|
|
3756
|
+
&& this.isWatchPathMissing(watchPath)) {
|
|
3757
|
+
if (watchPath === options.repoWatchPath) {
|
|
3758
|
+
repoWatchPathMissing = true;
|
|
3759
|
+
}
|
|
3760
|
+
continue;
|
|
3761
|
+
}
|
|
3762
|
+
failedWatcherCount += 1;
|
|
3763
|
+
this.sessionLogger.warn({ err: fallbackError, watchPath, ...options.context }, options.startFailedMessage);
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
else {
|
|
3767
|
+
if (this.isExpectedMissingWatchPathError(error) && this.isWatchPathMissing(watchPath)) {
|
|
3768
|
+
if (watchPath === options.repoWatchPath) {
|
|
3769
|
+
repoWatchPathMissing = true;
|
|
3770
|
+
}
|
|
3771
|
+
continue;
|
|
3772
|
+
}
|
|
3773
|
+
failedWatcherCount += 1;
|
|
3774
|
+
this.sessionLogger.warn({ err: error, watchPath, ...options.context }, options.startFailedMessage);
|
|
3775
|
+
}
|
|
3776
|
+
}
|
|
3777
|
+
if (!watcher) {
|
|
3778
|
+
continue;
|
|
3779
|
+
}
|
|
3780
|
+
watcher.on('error', (error) => {
|
|
3781
|
+
if (this.isExpectedMissingWatchPathError(error) && this.isWatchPathMissing(watchPath)) {
|
|
3782
|
+
return;
|
|
3783
|
+
}
|
|
3784
|
+
this.sessionLogger.warn({ err: error, watchPath, ...options.context }, options.watcherErrorMessage);
|
|
3785
|
+
});
|
|
3786
|
+
watchers.push(watcher);
|
|
3787
|
+
if (watchPath === options.repoWatchPath && watcherIsRecursive) {
|
|
3788
|
+
hasRecursiveRepoCoverage = true;
|
|
3789
|
+
}
|
|
3790
|
+
}
|
|
3791
|
+
return {
|
|
3792
|
+
watchers,
|
|
3793
|
+
hasRecursiveRepoCoverage,
|
|
3794
|
+
repoWatchPathMissing,
|
|
3795
|
+
failedWatcherCount,
|
|
3796
|
+
};
|
|
3797
|
+
}
|
|
3668
3798
|
scheduleCheckoutDiffTargetRefresh(target) {
|
|
3799
|
+
if (this.isWorkspaceGitWritePending(target.workspaceGitKey)) {
|
|
3800
|
+
target.refreshQueuedAfterWrite = true;
|
|
3801
|
+
return;
|
|
3802
|
+
}
|
|
3669
3803
|
if (target.debounceTimer) {
|
|
3670
3804
|
clearTimeout(target.debounceTimer);
|
|
3671
3805
|
}
|
|
@@ -3695,11 +3829,11 @@ export class Session {
|
|
|
3695
3829
|
const diffCwd = options?.diffCwd ?? cwd;
|
|
3696
3830
|
const detail = options?.detail ?? 'full';
|
|
3697
3831
|
try {
|
|
3698
|
-
const diffResult = await getCheckoutDiff(diffCwd, {
|
|
3832
|
+
const diffResult = await this.runWorkspaceGitRead(diffCwd, () => getCheckoutDiff(diffCwd, {
|
|
3699
3833
|
mode: compare.mode,
|
|
3700
3834
|
baseRef: compare.baseRef,
|
|
3701
3835
|
includeStructured: true,
|
|
3702
|
-
}, { junctionHome: this.junctionHome });
|
|
3836
|
+
}, { junctionHome: this.junctionHome }));
|
|
3703
3837
|
const files = [...(diffResult.structured ?? [])].map((file) => detail === 'summary'
|
|
3704
3838
|
? {
|
|
3705
3839
|
...file,
|
|
@@ -3759,10 +3893,12 @@ export class Session {
|
|
|
3759
3893
|
return existing;
|
|
3760
3894
|
}
|
|
3761
3895
|
const watchRoot = await this.resolveCheckoutWatchRoot(cwd);
|
|
3896
|
+
const workspaceGitKey = watchRoot ?? cwd;
|
|
3762
3897
|
const target = {
|
|
3763
3898
|
key: targetKey,
|
|
3764
3899
|
cwd,
|
|
3765
3900
|
diffCwd: watchRoot ?? cwd,
|
|
3901
|
+
workspaceGitKey,
|
|
3766
3902
|
compare,
|
|
3767
3903
|
detail,
|
|
3768
3904
|
subscriptions: new Set(),
|
|
@@ -3771,6 +3907,7 @@ export class Session {
|
|
|
3771
3907
|
debounceTimer: null,
|
|
3772
3908
|
refreshPromise: null,
|
|
3773
3909
|
refreshQueued: false,
|
|
3910
|
+
refreshQueuedAfterWrite: false,
|
|
3774
3911
|
latestPayload: null,
|
|
3775
3912
|
latestFingerprint: null,
|
|
3776
3913
|
};
|
|
@@ -3780,55 +3917,27 @@ export class Session {
|
|
|
3780
3917
|
if (gitDir) {
|
|
3781
3918
|
watchPaths.add(gitDir);
|
|
3782
3919
|
}
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
const createWatcher = (recursive) => watch(watchPath, { recursive }, () => {
|
|
3920
|
+
const watchSetup = this.startCheckoutWatchers({
|
|
3921
|
+
repoWatchPath,
|
|
3922
|
+
watchPaths,
|
|
3923
|
+
onChange: () => {
|
|
3788
3924
|
this.scheduleCheckoutDiffTargetRefresh(target);
|
|
3789
|
-
}
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
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;
|
|
3925
|
+
},
|
|
3926
|
+
recursiveUnavailableMessage: 'Checkout diff recursive watch unavailable; using non-recursive fallback',
|
|
3927
|
+
startFailedMessage: 'Failed to start checkout diff watcher',
|
|
3928
|
+
watcherErrorMessage: 'Checkout diff watcher error',
|
|
3929
|
+
context: { cwd, compare },
|
|
3930
|
+
});
|
|
3931
|
+
target.watchers = watchSetup.watchers;
|
|
3932
|
+
const missingRepoCoverage = !watchSetup.hasRecursiveRepoCoverage;
|
|
3827
3933
|
if (target.watchers.length === 0 || missingRepoCoverage) {
|
|
3828
3934
|
target.fallbackRefreshInterval = setInterval(() => {
|
|
3829
3935
|
this.scheduleCheckoutDiffTargetRefresh(target);
|
|
3830
3936
|
}, CHECKOUT_DIFF_FALLBACK_REFRESH_MS);
|
|
3831
|
-
|
|
3937
|
+
const log = watchSetup.repoWatchPathMissing && watchSetup.failedWatcherCount === 0
|
|
3938
|
+
? this.sessionLogger.debug.bind(this.sessionLogger)
|
|
3939
|
+
: this.sessionLogger.warn.bind(this.sessionLogger);
|
|
3940
|
+
log({
|
|
3832
3941
|
cwd,
|
|
3833
3942
|
compare,
|
|
3834
3943
|
intervalMs: CHECKOUT_DIFF_FALLBACK_REFRESH_MS,
|
|
@@ -3848,13 +3957,26 @@ export class Session {
|
|
|
3848
3957
|
this.checkoutDiffSubscriptions.set(msg.subscriptionId, {
|
|
3849
3958
|
targetKey: target.key,
|
|
3850
3959
|
});
|
|
3851
|
-
|
|
3852
|
-
|
|
3960
|
+
if (target.refreshPromise) {
|
|
3961
|
+
await target.refreshPromise;
|
|
3962
|
+
}
|
|
3963
|
+
const canReuseLatestPayload = target.latestPayload !== null &&
|
|
3964
|
+
!target.refreshQueuedAfterWrite &&
|
|
3965
|
+
!this.isWorkspaceGitWritePending(target.workspaceGitKey);
|
|
3966
|
+
const shouldPreserveQueuedFingerprint = !canReuseLatestPayload && target.refreshQueuedAfterWrite;
|
|
3967
|
+
const snapshot = canReuseLatestPayload
|
|
3968
|
+
? target.latestPayload
|
|
3969
|
+
: await this.computeCheckoutDiffSnapshot(cwd, compare, {
|
|
3853
3970
|
diffCwd: target.diffCwd,
|
|
3854
3971
|
detail: target.detail,
|
|
3855
|
-
})
|
|
3972
|
+
});
|
|
3973
|
+
if (!snapshot) {
|
|
3974
|
+
throw new Error('Checkout diff snapshot was unavailable');
|
|
3975
|
+
}
|
|
3856
3976
|
target.latestPayload = snapshot;
|
|
3857
|
-
|
|
3977
|
+
if (!shouldPreserveQueuedFingerprint) {
|
|
3978
|
+
target.latestFingerprint = this.checkoutDiffSnapshotFingerprint(snapshot);
|
|
3979
|
+
}
|
|
3858
3980
|
this.emit({
|
|
3859
3981
|
type: 'subscribe_checkout_diff_response',
|
|
3860
3982
|
payload: {
|
|
@@ -3876,21 +3998,105 @@ export class Session {
|
|
|
3876
3998
|
this.scheduleCheckoutDiffTargetRefresh(target);
|
|
3877
3999
|
}
|
|
3878
4000
|
}
|
|
4001
|
+
getWorkspaceGitOperationState(workspaceGitKey) {
|
|
4002
|
+
const existing = this.workspaceGitOperationStates.get(workspaceGitKey);
|
|
4003
|
+
if (existing) {
|
|
4004
|
+
return existing;
|
|
4005
|
+
}
|
|
4006
|
+
const created = {
|
|
4007
|
+
writeBarrier: Promise.resolve(),
|
|
4008
|
+
pendingWrites: 0,
|
|
4009
|
+
activeReaders: 0,
|
|
4010
|
+
readerDrain: Promise.resolve(),
|
|
4011
|
+
resolveReaderDrain: null,
|
|
4012
|
+
};
|
|
4013
|
+
this.workspaceGitOperationStates.set(workspaceGitKey, created);
|
|
4014
|
+
return created;
|
|
4015
|
+
}
|
|
4016
|
+
isWorkspaceGitWritePending(workspaceGitKey) {
|
|
4017
|
+
return (this.workspaceGitOperationStates.get(workspaceGitKey)?.pendingWrites ?? 0) > 0;
|
|
4018
|
+
}
|
|
4019
|
+
async resolveWorkspaceGitOperationKey(cwd) {
|
|
4020
|
+
const resolvedCwd = expandTilde(cwd);
|
|
4021
|
+
return (await this.resolveCheckoutWatchRoot(resolvedCwd)) ?? resolvedCwd;
|
|
4022
|
+
}
|
|
4023
|
+
flushQueuedCheckoutDiffRefreshesForWorkspace(workspaceGitKey) {
|
|
4024
|
+
for (const target of this.checkoutDiffTargets.values()) {
|
|
4025
|
+
if (target.workspaceGitKey !== workspaceGitKey || !target.refreshQueuedAfterWrite) {
|
|
4026
|
+
continue;
|
|
4027
|
+
}
|
|
4028
|
+
target.refreshQueuedAfterWrite = false;
|
|
4029
|
+
this.scheduleCheckoutDiffTargetRefresh(target);
|
|
4030
|
+
}
|
|
4031
|
+
}
|
|
4032
|
+
async runWorkspaceGitRead(cwd, operation) {
|
|
4033
|
+
const workspaceGitKey = await this.resolveWorkspaceGitOperationKey(cwd);
|
|
4034
|
+
const state = this.getWorkspaceGitOperationState(workspaceGitKey);
|
|
4035
|
+
while (state.pendingWrites > 0) {
|
|
4036
|
+
await state.writeBarrier;
|
|
4037
|
+
}
|
|
4038
|
+
state.activeReaders += 1;
|
|
4039
|
+
if (state.activeReaders === 1) {
|
|
4040
|
+
state.readerDrain = new Promise((resolve) => {
|
|
4041
|
+
state.resolveReaderDrain = resolve;
|
|
4042
|
+
});
|
|
4043
|
+
}
|
|
4044
|
+
try {
|
|
4045
|
+
return await operation();
|
|
4046
|
+
}
|
|
4047
|
+
finally {
|
|
4048
|
+
state.activeReaders = Math.max(0, state.activeReaders - 1);
|
|
4049
|
+
if (state.activeReaders === 0) {
|
|
4050
|
+
state.resolveReaderDrain?.();
|
|
4051
|
+
state.resolveReaderDrain = null;
|
|
4052
|
+
state.readerDrain = Promise.resolve();
|
|
4053
|
+
}
|
|
4054
|
+
}
|
|
4055
|
+
}
|
|
4056
|
+
async runWorkspaceGitWrite(cwd, operation) {
|
|
4057
|
+
const workspaceGitKey = await this.resolveWorkspaceGitOperationKey(cwd);
|
|
4058
|
+
const state = this.getWorkspaceGitOperationState(workspaceGitKey);
|
|
4059
|
+
const previousBarrier = state.writeBarrier;
|
|
4060
|
+
let releaseCurrentBarrier = () => { };
|
|
4061
|
+
const currentBarrier = new Promise((resolve) => {
|
|
4062
|
+
releaseCurrentBarrier = resolve;
|
|
4063
|
+
});
|
|
4064
|
+
state.pendingWrites += 1;
|
|
4065
|
+
state.writeBarrier = previousBarrier.then(() => currentBarrier);
|
|
4066
|
+
try {
|
|
4067
|
+
await previousBarrier;
|
|
4068
|
+
while (state.activeReaders > 0) {
|
|
4069
|
+
await state.readerDrain;
|
|
4070
|
+
}
|
|
4071
|
+
return await operation();
|
|
4072
|
+
}
|
|
4073
|
+
finally {
|
|
4074
|
+
state.pendingWrites = Math.max(0, state.pendingWrites - 1);
|
|
4075
|
+
releaseCurrentBarrier();
|
|
4076
|
+
if (state.pendingWrites === 0) {
|
|
4077
|
+
state.writeBarrier = Promise.resolve();
|
|
4078
|
+
this.flushQueuedCheckoutDiffRefreshesForWorkspace(workspaceGitKey);
|
|
4079
|
+
}
|
|
4080
|
+
}
|
|
4081
|
+
}
|
|
3879
4082
|
async handleCheckoutCommitRequest(msg) {
|
|
3880
4083
|
const { cwd, requestId } = msg;
|
|
3881
4084
|
try {
|
|
3882
4085
|
let message = msg.message?.trim() ?? '';
|
|
3883
4086
|
if (!message) {
|
|
3884
|
-
|
|
4087
|
+
const diff = await this.runWorkspaceGitRead(cwd, () => getCheckoutDiff(cwd, { mode: 'uncommitted', includeStructured: true }, { junctionHome: this.junctionHome }));
|
|
4088
|
+
message = await this.generateCommitMessage(cwd, diff);
|
|
3885
4089
|
}
|
|
3886
4090
|
if (!message) {
|
|
3887
4091
|
throw new Error('Commit message is required');
|
|
3888
4092
|
}
|
|
3889
|
-
await
|
|
3890
|
-
|
|
3891
|
-
|
|
4093
|
+
await this.runWorkspaceGitWrite(cwd, async () => {
|
|
4094
|
+
await commitChanges(cwd, {
|
|
4095
|
+
message,
|
|
4096
|
+
addAll: msg.addAll ?? true,
|
|
4097
|
+
});
|
|
4098
|
+
this.scheduleCheckoutDiffRefreshForCwd(cwd);
|
|
3892
4099
|
});
|
|
3893
|
-
this.scheduleCheckoutDiffRefreshForCwd(cwd);
|
|
3894
4100
|
this.scheduleWorkspaceStatusRefreshForCwd(cwd);
|
|
3895
4101
|
this.emit({
|
|
3896
4102
|
type: 'checkout_commit_response',
|
|
@@ -3917,45 +4123,54 @@ export class Session {
|
|
|
3917
4123
|
async handleCheckoutMergeRequest(msg) {
|
|
3918
4124
|
const { cwd, requestId } = msg;
|
|
3919
4125
|
try {
|
|
3920
|
-
const
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
4126
|
+
const mergeOperationCwd = await resolveMergeToBaseOperationCwd(cwd, { baseRef: msg.baseRef }, { junctionHome: this.junctionHome });
|
|
4127
|
+
await this.runWorkspaceGitWrite(mergeOperationCwd, async () => {
|
|
4128
|
+
const status = await getCheckoutStatus(cwd, { junctionHome: this.junctionHome });
|
|
4129
|
+
if (!status.isGit) {
|
|
4130
|
+
try {
|
|
4131
|
+
await execAsync('git rev-parse --is-inside-work-tree', {
|
|
4132
|
+
cwd,
|
|
4133
|
+
env: READ_ONLY_GIT_ENV,
|
|
4134
|
+
});
|
|
4135
|
+
}
|
|
4136
|
+
catch (error) {
|
|
4137
|
+
const details = typeof error?.stderr === 'string'
|
|
4138
|
+
? String(error.stderr).trim()
|
|
4139
|
+
: error instanceof Error
|
|
4140
|
+
? error.message
|
|
4141
|
+
: String(error);
|
|
4142
|
+
throw new Error(`Not a git repository: ${cwd}\n${details}`.trim());
|
|
4143
|
+
}
|
|
4144
|
+
}
|
|
4145
|
+
if (msg.requireCleanTarget) {
|
|
4146
|
+
const { stdout } = await execAsync('git status --porcelain', {
|
|
3924
4147
|
cwd,
|
|
3925
4148
|
env: READ_ONLY_GIT_ENV,
|
|
3926
4149
|
});
|
|
4150
|
+
if (stdout.trim().length > 0) {
|
|
4151
|
+
throw new Error('Working directory has uncommitted changes.');
|
|
4152
|
+
}
|
|
3927
4153
|
}
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
: error instanceof Error
|
|
3932
|
-
? error.message
|
|
3933
|
-
: String(error);
|
|
3934
|
-
throw new Error(`Not a git repository: ${cwd}\n${details}`.trim());
|
|
4154
|
+
let baseRef = msg.baseRef ?? (status.isGit ? status.baseRef : null);
|
|
4155
|
+
if (!baseRef) {
|
|
4156
|
+
throw new Error('Base branch is required for merge');
|
|
3935
4157
|
}
|
|
3936
|
-
|
|
3937
|
-
|
|
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.');
|
|
4158
|
+
if (baseRef.startsWith('origin/')) {
|
|
4159
|
+
baseRef = baseRef.slice('origin/'.length);
|
|
3944
4160
|
}
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
baseRef,
|
|
3955
|
-
mode: msg.strategy === 'squash' ? 'squash' : 'merge',
|
|
3956
|
-
}, { junctionHome: this.junctionHome });
|
|
3957
|
-
this.scheduleCheckoutDiffRefreshForCwd(cwd);
|
|
4161
|
+
await mergeToBase(cwd, {
|
|
4162
|
+
baseRef,
|
|
4163
|
+
mode: msg.strategy === 'squash' ? 'squash' : 'merge',
|
|
4164
|
+
}, { junctionHome: this.junctionHome });
|
|
4165
|
+
this.scheduleCheckoutDiffRefreshForCwd(cwd);
|
|
4166
|
+
if (resolve(mergeOperationCwd) !== resolve(cwd)) {
|
|
4167
|
+
this.scheduleCheckoutDiffRefreshForCwd(mergeOperationCwd);
|
|
4168
|
+
}
|
|
4169
|
+
});
|
|
3958
4170
|
this.scheduleWorkspaceStatusRefreshForCwd(cwd, { includePr: true });
|
|
4171
|
+
if (resolve(mergeOperationCwd) !== resolve(cwd)) {
|
|
4172
|
+
this.scheduleWorkspaceStatusRefreshForCwd(mergeOperationCwd, { includePr: true });
|
|
4173
|
+
}
|
|
3959
4174
|
this.emit({
|
|
3960
4175
|
type: 'checkout_merge_response',
|
|
3961
4176
|
payload: {
|
|
@@ -3981,21 +4196,23 @@ export class Session {
|
|
|
3981
4196
|
async handleCheckoutMergeFromBaseRequest(msg) {
|
|
3982
4197
|
const { cwd, requestId } = msg;
|
|
3983
4198
|
try {
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
4199
|
+
await this.runWorkspaceGitWrite(cwd, async () => {
|
|
4200
|
+
if (msg.requireCleanTarget ?? true) {
|
|
4201
|
+
const { stdout } = await execAsync('git status --porcelain', {
|
|
4202
|
+
cwd,
|
|
4203
|
+
env: READ_ONLY_GIT_ENV,
|
|
4204
|
+
});
|
|
4205
|
+
if (stdout.trim().length > 0) {
|
|
4206
|
+
throw new Error('Working directory has uncommitted changes.');
|
|
4207
|
+
}
|
|
3991
4208
|
}
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
4209
|
+
await mergeFromBase(cwd, {
|
|
4210
|
+
baseRef: msg.baseRef,
|
|
4211
|
+
remoteName: msg.remoteName,
|
|
4212
|
+
requireCleanTarget: msg.requireCleanTarget ?? true,
|
|
4213
|
+
});
|
|
4214
|
+
this.scheduleCheckoutDiffRefreshForCwd(cwd);
|
|
3997
4215
|
});
|
|
3998
|
-
this.scheduleCheckoutDiffRefreshForCwd(cwd);
|
|
3999
4216
|
this.scheduleWorkspaceStatusRefreshForCwd(cwd, { includePr: true });
|
|
4000
4217
|
this.emit({
|
|
4001
4218
|
type: 'checkout_merge_from_base_response',
|
|
@@ -4022,7 +4239,7 @@ export class Session {
|
|
|
4022
4239
|
async handleCheckoutPushRequest(msg) {
|
|
4023
4240
|
const { cwd, requestId } = msg;
|
|
4024
4241
|
try {
|
|
4025
|
-
await pushCurrentBranch(cwd, { remoteName: msg.remoteName });
|
|
4242
|
+
await this.runWorkspaceGitWrite(cwd, () => pushCurrentBranch(cwd, { remoteName: msg.remoteName }));
|
|
4026
4243
|
this.scheduleWorkspaceStatusRefreshForCwd(cwd, { includePr: true });
|
|
4027
4244
|
this.emit({
|
|
4028
4245
|
type: 'checkout_push_response',
|
|
@@ -4052,18 +4269,23 @@ export class Session {
|
|
|
4052
4269
|
let title = msg.title?.trim() ?? '';
|
|
4053
4270
|
let body = msg.body?.trim() ?? '';
|
|
4054
4271
|
if (!title || !body) {
|
|
4055
|
-
const
|
|
4272
|
+
const diff = await this.runWorkspaceGitRead(cwd, () => getCheckoutDiff(cwd, {
|
|
4273
|
+
mode: 'base',
|
|
4274
|
+
baseRef: msg.baseRef,
|
|
4275
|
+
includeStructured: true,
|
|
4276
|
+
}, { junctionHome: this.junctionHome }));
|
|
4277
|
+
const generated = await this.generatePullRequestText(cwd, msg.baseRef, diff);
|
|
4056
4278
|
if (!title)
|
|
4057
4279
|
title = generated.title;
|
|
4058
4280
|
if (!body)
|
|
4059
4281
|
body = generated.body;
|
|
4060
4282
|
}
|
|
4061
|
-
const result = await createPullRequest(cwd, {
|
|
4283
|
+
const result = await this.runWorkspaceGitWrite(cwd, () => createPullRequest(cwd, {
|
|
4062
4284
|
title,
|
|
4063
4285
|
body,
|
|
4064
4286
|
base: msg.baseRef,
|
|
4065
4287
|
remoteName: msg.remoteName,
|
|
4066
|
-
});
|
|
4288
|
+
}));
|
|
4067
4289
|
this.scheduleWorkspaceStatusRefreshForCwd(cwd, { includePr: true });
|
|
4068
4290
|
this.emit({
|
|
4069
4291
|
type: 'checkout_pr_create_response',
|
|
@@ -4121,11 +4343,11 @@ export class Session {
|
|
|
4121
4343
|
const cwd = expandTilde(msg.cwd);
|
|
4122
4344
|
const { requestId } = msg;
|
|
4123
4345
|
try {
|
|
4124
|
-
const result = await searchPullRequests(cwd, {
|
|
4346
|
+
const result = await this.runWorkspaceGitRead(cwd, () => searchPullRequests(cwd, {
|
|
4125
4347
|
query: msg.query,
|
|
4126
4348
|
limit: msg.limit,
|
|
4127
4349
|
remoteName: msg.remoteName,
|
|
4128
|
-
});
|
|
4350
|
+
}));
|
|
4129
4351
|
this.emit({
|
|
4130
4352
|
type: 'checkout_pr_search_response',
|
|
4131
4353
|
payload: {
|
|
@@ -4153,7 +4375,7 @@ export class Session {
|
|
|
4153
4375
|
async handleCheckoutPrFailureLogsRequest(msg) {
|
|
4154
4376
|
const { cwd, requestId } = msg;
|
|
4155
4377
|
try {
|
|
4156
|
-
const result = await getPullRequestFailureLogs(cwd, { remoteName: msg.remoteName });
|
|
4378
|
+
const result = await this.runWorkspaceGitRead(cwd, () => getPullRequestFailureLogs(cwd, { remoteName: msg.remoteName }));
|
|
4157
4379
|
this.emit({
|
|
4158
4380
|
type: 'checkout_pr_failure_logs_response',
|
|
4159
4381
|
payload: {
|
|
@@ -4536,8 +4758,8 @@ export class Session {
|
|
|
4536
4758
|
*/
|
|
4537
4759
|
async handleWorkspaceFileExplorerRequest(request) {
|
|
4538
4760
|
const { cwd, path: requestedPath = '.', mode, requestId, ref } = request;
|
|
4761
|
+
const root = expandTilde(cwd);
|
|
4539
4762
|
try {
|
|
4540
|
-
const root = expandTilde(cwd);
|
|
4541
4763
|
if (ref) {
|
|
4542
4764
|
this.assertSafeGitRef(ref, 'workspace file ref');
|
|
4543
4765
|
}
|
|
@@ -4584,6 +4806,25 @@ export class Session {
|
|
|
4584
4806
|
}
|
|
4585
4807
|
}
|
|
4586
4808
|
catch (error) {
|
|
4809
|
+
if (mode === 'file'
|
|
4810
|
+
&& request.allowMissing
|
|
4811
|
+
&& isWorkspaceExplorerMissingPathError(error)
|
|
4812
|
+
&& (ref != null || existsSync(root))) {
|
|
4813
|
+
this.emit({
|
|
4814
|
+
type: 'workspace_file_explorer_response',
|
|
4815
|
+
payload: {
|
|
4816
|
+
cwd,
|
|
4817
|
+
path: requestedPath,
|
|
4818
|
+
ref: ref ?? null,
|
|
4819
|
+
mode,
|
|
4820
|
+
directory: null,
|
|
4821
|
+
file: null,
|
|
4822
|
+
error: null,
|
|
4823
|
+
requestId,
|
|
4824
|
+
},
|
|
4825
|
+
});
|
|
4826
|
+
return;
|
|
4827
|
+
}
|
|
4587
4828
|
const log = isWorkspaceExplorerMissingPathError(error)
|
|
4588
4829
|
? this.sessionLogger.debug.bind(this.sessionLogger)
|
|
4589
4830
|
: this.sessionLogger.error.bind(this.sessionLogger);
|