@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.
- 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 +416 -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,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
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
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
|
-
|
|
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({
|
|
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
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
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
|
-
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
3852
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
3890
|
-
|
|
3891
|
-
|
|
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
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
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
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
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
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
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
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
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
|
|
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);
|