@junctionpanel/server 0.1.39 → 0.1.40
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 +8 -1
- package/dist/server/client/daemon-client.d.ts.map +1 -1
- package/dist/server/client/daemon-client.js +14 -0
- package/dist/server/client/daemon-client.js.map +1 -1
- package/dist/server/server/session.d.ts +2 -0
- package/dist/server/server/session.d.ts.map +1 -1
- package/dist/server/server/session.js +182 -4
- package/dist/server/server/session.js.map +1 -1
- package/dist/server/shared/messages.d.ts +746 -0
- package/dist/server/shared/messages.d.ts.map +1 -1
- package/dist/server/shared/messages.js +32 -0
- package/dist/server/shared/messages.js.map +1 -1
- package/dist/server/utils/checkout-git.d.ts +19 -0
- package/dist/server/utils/checkout-git.d.ts.map +1 -1
- package/dist/server/utils/checkout-git.js +85 -0
- package/dist/server/utils/checkout-git.js.map +1 -1
- package/dist/server/utils/worktree.d.ts +23 -0
- package/dist/server/utils/worktree.d.ts.map +1 -1
- package/dist/server/utils/worktree.js +120 -1
- package/dist/server/utils/worktree.js.map +1 -1
- package/package.json +2 -2
|
@@ -19,10 +19,10 @@ import { projectTimelineRows, selectTimelineWindowByProjectedLimit, } from './ag
|
|
|
19
19
|
import { DEFAULT_STRUCTURED_GENERATION_PROVIDERS, StructuredAgentFallbackError, StructuredAgentResponseError, generateStructuredAgentResponseWithFallback, } from './agent/agent-response-loop.js';
|
|
20
20
|
import { isValidAgentProvider, AGENT_PROVIDER_IDS } from './agent/provider-manifest.js';
|
|
21
21
|
import { listDirectoryEntries, readExplorerFile, getDownloadableFileInfo, isWorkspaceExplorerMissingPathError, } from './file-explorer/service.js';
|
|
22
|
-
import { slugify, validateBranchSlug, listJunctionWorktrees, deleteJunctionWorktree, isJunctionOwnedWorktreeCwd, resolveJunctionWorktreeRootForCwd, createInRepoWorktree, restoreInRepoWorktree, } from '../utils/worktree.js';
|
|
22
|
+
import { slugify, validateBranchSlug, listJunctionWorktrees, deleteJunctionWorktree, isJunctionOwnedWorktreeCwd, resolveJunctionWorktreeRootForCwd, createInRepoWorktree, attachInRepoWorktree, restoreInRepoWorktree, } from '../utils/worktree.js';
|
|
23
23
|
import { readJunctionWorktreeMetadata } from '../utils/worktree-metadata.js';
|
|
24
24
|
import { runAsyncWorktreeBootstrap } from './worktree-bootstrap.js';
|
|
25
|
-
import { getCheckoutDiff, getCheckoutStatus, getCheckoutStatusLite, listBranchSuggestions, NotGitRepoError, MergeConflictError, MergeFromBaseConflictError, commitChanges, mergeToBase, mergeFromBase, pushCurrentBranch, createPullRequest, getPullRequestFailureLogs, getPullRequestStatus, listGitRemotes, mergePullRequest, resolveBaseRefWithSource, } from '../utils/checkout-git.js';
|
|
25
|
+
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
26
|
import { getProjectIcon } from '../utils/project-icon.js';
|
|
27
27
|
import { expandTilde } from '../utils/path.js';
|
|
28
28
|
import { searchHomeDirectories, searchWorkspaceEntries, searchWorkspaceEntriesAtGitRef, searchGitRepositories, checkIsGitRepo, } from '../utils/directory-suggestions.js';
|
|
@@ -906,6 +906,9 @@ export class Session {
|
|
|
906
906
|
case 'checkout_pr_status_request':
|
|
907
907
|
await this.handleCheckoutPrStatusRequest(msg);
|
|
908
908
|
break;
|
|
909
|
+
case 'checkout_pr_search_request':
|
|
910
|
+
await this.handleCheckoutPrSearchRequest(msg);
|
|
911
|
+
break;
|
|
909
912
|
case 'checkout_pr_failure_logs_request':
|
|
910
913
|
await this.handleCheckoutPrFailureLogsRequest(msg);
|
|
911
914
|
break;
|
|
@@ -1730,6 +1733,7 @@ export class Session {
|
|
|
1730
1733
|
async buildAgentSessionConfig(config, gitOptions, legacyWorktreeName, labels) {
|
|
1731
1734
|
const cwd = expandTilde(config.cwd);
|
|
1732
1735
|
const normalized = this.normalizeGitOptions(gitOptions, legacyWorktreeName);
|
|
1736
|
+
const requestedAttachBranch = normalized?.attachBranchName;
|
|
1733
1737
|
let repoRoot;
|
|
1734
1738
|
try {
|
|
1735
1739
|
const { stdout } = await execAsync('git rev-parse --path-format=absolute --git-common-dir', {
|
|
@@ -1767,6 +1771,64 @@ export class Session {
|
|
|
1767
1771
|
};
|
|
1768
1772
|
}
|
|
1769
1773
|
}
|
|
1774
|
+
if (requestedAttachBranch) {
|
|
1775
|
+
const attachBranchSource = normalized?.attachBranchSource ?? 'remote';
|
|
1776
|
+
const liveWorktrees = await listJunctionWorktrees({ cwd: repoRoot, junctionHome: this.junctionHome });
|
|
1777
|
+
const localMatchingWorktree = attachBranchSource === 'local'
|
|
1778
|
+
? liveWorktrees.find((entry) => entry.branchName?.trim() === requestedAttachBranch)
|
|
1779
|
+
: null;
|
|
1780
|
+
if (localMatchingWorktree) {
|
|
1781
|
+
this.sessionLogger.info({
|
|
1782
|
+
repoRoot,
|
|
1783
|
+
attachBranchName: requestedAttachBranch,
|
|
1784
|
+
attachBranchRef: `refs/heads/${requestedAttachBranch}`,
|
|
1785
|
+
worktreePath: localMatchingWorktree.path,
|
|
1786
|
+
}, 'Reusing existing Junction worktree for agent');
|
|
1787
|
+
return {
|
|
1788
|
+
sessionConfig: {
|
|
1789
|
+
...config,
|
|
1790
|
+
cwd: localMatchingWorktree.path,
|
|
1791
|
+
},
|
|
1792
|
+
autoWorkspaceName: labels?.['junction:workspace'] ?? basename(localMatchingWorktree.path),
|
|
1793
|
+
};
|
|
1794
|
+
}
|
|
1795
|
+
let requestedAttachTarget = null;
|
|
1796
|
+
try {
|
|
1797
|
+
requestedAttachTarget = await this.resolveAttachBranchTarget({
|
|
1798
|
+
repoRoot,
|
|
1799
|
+
branchName: requestedAttachBranch,
|
|
1800
|
+
branchSource: attachBranchSource,
|
|
1801
|
+
remoteName: normalized?.remoteName,
|
|
1802
|
+
});
|
|
1803
|
+
}
|
|
1804
|
+
catch (error) {
|
|
1805
|
+
this.sessionLogger.warn({
|
|
1806
|
+
err: error,
|
|
1807
|
+
repoRoot,
|
|
1808
|
+
attachBranchName: requestedAttachBranch,
|
|
1809
|
+
attachBranchSource,
|
|
1810
|
+
}, 'Failed to resolve attach target before reuse lookup');
|
|
1811
|
+
}
|
|
1812
|
+
const matchingWorktree = requestedAttachTarget?.head
|
|
1813
|
+
? liveWorktrees.find((entry) => entry.branchName?.trim() === requestedAttachBranch
|
|
1814
|
+
&& entry.head === requestedAttachTarget?.head)
|
|
1815
|
+
: null;
|
|
1816
|
+
if (matchingWorktree) {
|
|
1817
|
+
this.sessionLogger.info({
|
|
1818
|
+
repoRoot,
|
|
1819
|
+
attachBranchName: requestedAttachBranch,
|
|
1820
|
+
attachBranchRef: requestedAttachTarget?.ref ?? null,
|
|
1821
|
+
worktreePath: matchingWorktree.path,
|
|
1822
|
+
}, 'Reusing existing Junction worktree for agent');
|
|
1823
|
+
return {
|
|
1824
|
+
sessionConfig: {
|
|
1825
|
+
...config,
|
|
1826
|
+
cwd: matchingWorktree.path,
|
|
1827
|
+
},
|
|
1828
|
+
autoWorkspaceName: labels?.['junction:workspace'] ?? basename(matchingWorktree.path),
|
|
1829
|
+
};
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1770
1832
|
const resolvedBase = normalized?.baseBranch
|
|
1771
1833
|
? {
|
|
1772
1834
|
ref: normalized.baseBranch,
|
|
@@ -1781,6 +1843,32 @@ export class Session {
|
|
|
1781
1843
|
if (!baseBranch) {
|
|
1782
1844
|
throw new Error('Unable to determine a base branch for worktree creation');
|
|
1783
1845
|
}
|
|
1846
|
+
if (requestedAttachBranch) {
|
|
1847
|
+
this.sessionLogger.info({
|
|
1848
|
+
repoRoot,
|
|
1849
|
+
baseBranch,
|
|
1850
|
+
baseBranchSource: resolvedBase.source,
|
|
1851
|
+
attachBranchName: requestedAttachBranch,
|
|
1852
|
+
attachBranchSource: normalized?.attachBranchSource ?? 'remote',
|
|
1853
|
+
}, 'Attaching in-repo worktree for new agent');
|
|
1854
|
+
const attachedWorktree = await attachInRepoWorktree({
|
|
1855
|
+
repoRoot,
|
|
1856
|
+
baseBranch,
|
|
1857
|
+
baseBranchSource: resolvedBase.source,
|
|
1858
|
+
remoteName: resolvedBase.remoteName ?? normalized?.remoteName,
|
|
1859
|
+
branchName: requestedAttachBranch,
|
|
1860
|
+
branchSource: normalized?.attachBranchSource ?? 'remote',
|
|
1861
|
+
runSetup: false,
|
|
1862
|
+
});
|
|
1863
|
+
return {
|
|
1864
|
+
sessionConfig: {
|
|
1865
|
+
...config,
|
|
1866
|
+
cwd: attachedWorktree.worktreePath,
|
|
1867
|
+
},
|
|
1868
|
+
worktreeConfig: attachedWorktree,
|
|
1869
|
+
autoWorkspaceName: attachedWorktree.slug,
|
|
1870
|
+
};
|
|
1871
|
+
}
|
|
1784
1872
|
this.sessionLogger.info({ repoRoot, baseBranch, baseBranchSource: resolvedBase.source }, 'Creating in-repo worktree for new agent');
|
|
1785
1873
|
const createdWorktree = await createInRepoWorktree({
|
|
1786
1874
|
repoRoot,
|
|
@@ -2091,10 +2179,12 @@ export class Session {
|
|
|
2091
2179
|
const createWorktree = Boolean(merged.createWorktree);
|
|
2092
2180
|
const createNewBranch = Boolean(merged.createNewBranch);
|
|
2093
2181
|
const normalizedBranchName = merged.newBranchName ? slugify(merged.newBranchName) : undefined;
|
|
2182
|
+
const attachBranchName = merged.attachBranchName?.trim() || undefined;
|
|
2183
|
+
const attachBranchSource = merged.attachBranchSource ?? 'remote';
|
|
2094
2184
|
const normalizedWorktreeSlug = merged.worktreeSlug
|
|
2095
2185
|
? slugify(merged.worktreeSlug)
|
|
2096
|
-
: normalizedBranchName;
|
|
2097
|
-
if (!createWorktree && !createNewBranch && !baseBranch) {
|
|
2186
|
+
: normalizedBranchName ?? (attachBranchName ? slugify(attachBranchName) : undefined);
|
|
2187
|
+
if (!createWorktree && !createNewBranch && !attachBranchName && !baseBranch) {
|
|
2098
2188
|
return null;
|
|
2099
2189
|
}
|
|
2100
2190
|
if (baseBranch) {
|
|
@@ -2112,6 +2202,12 @@ export class Session {
|
|
|
2112
2202
|
throw new Error(`Invalid branch name: ${validation.error}`);
|
|
2113
2203
|
}
|
|
2114
2204
|
}
|
|
2205
|
+
if (attachBranchName) {
|
|
2206
|
+
this.assertSafeGitRef(attachBranchName, 'attach branch');
|
|
2207
|
+
}
|
|
2208
|
+
if (attachBranchName && createNewBranch) {
|
|
2209
|
+
throw new Error('Cannot both attach to an existing branch and create a new branch');
|
|
2210
|
+
}
|
|
2115
2211
|
if (normalizedWorktreeSlug) {
|
|
2116
2212
|
const validation = validateBranchSlug(normalizedWorktreeSlug);
|
|
2117
2213
|
if (!validation.valid) {
|
|
@@ -2126,7 +2222,56 @@ export class Session {
|
|
|
2126
2222
|
newBranchName: normalizedBranchName,
|
|
2127
2223
|
createWorktree,
|
|
2128
2224
|
worktreeSlug: normalizedWorktreeSlug,
|
|
2225
|
+
attachBranchName,
|
|
2226
|
+
attachBranchSource,
|
|
2227
|
+
};
|
|
2228
|
+
}
|
|
2229
|
+
async resolveAttachBranchTarget(options) {
|
|
2230
|
+
const resolveLocalRef = async () => {
|
|
2231
|
+
try {
|
|
2232
|
+
const { stdout } = await execAsync(`git rev-parse refs/heads/${options.branchName}`, {
|
|
2233
|
+
cwd: options.repoRoot,
|
|
2234
|
+
env: READ_ONLY_GIT_ENV,
|
|
2235
|
+
});
|
|
2236
|
+
return {
|
|
2237
|
+
head: stdout.trim(),
|
|
2238
|
+
ref: `refs/heads/${options.branchName}`,
|
|
2239
|
+
};
|
|
2240
|
+
}
|
|
2241
|
+
catch {
|
|
2242
|
+
return null;
|
|
2243
|
+
}
|
|
2129
2244
|
};
|
|
2245
|
+
if (options.branchSource === 'local') {
|
|
2246
|
+
return resolveLocalRef();
|
|
2247
|
+
}
|
|
2248
|
+
const remotes = await listGitRemotes(options.repoRoot);
|
|
2249
|
+
const effectiveRemoteName = options.remoteName && remotes.some((remote) => remote.name === options.remoteName)
|
|
2250
|
+
? options.remoteName
|
|
2251
|
+
: remotes.find((remote) => remote.name === 'origin')?.name ?? remotes[0]?.name;
|
|
2252
|
+
if (!effectiveRemoteName) {
|
|
2253
|
+
return null;
|
|
2254
|
+
}
|
|
2255
|
+
this.assertSafeRemoteName(effectiveRemoteName);
|
|
2256
|
+
try {
|
|
2257
|
+
await execAsync(`git fetch ${effectiveRemoteName} --prune`, { cwd: options.repoRoot });
|
|
2258
|
+
}
|
|
2259
|
+
catch {
|
|
2260
|
+
return null;
|
|
2261
|
+
}
|
|
2262
|
+
try {
|
|
2263
|
+
const { stdout } = await execAsync(`git rev-parse refs/remotes/${effectiveRemoteName}/${options.branchName}`, {
|
|
2264
|
+
cwd: options.repoRoot,
|
|
2265
|
+
env: READ_ONLY_GIT_ENV,
|
|
2266
|
+
});
|
|
2267
|
+
return {
|
|
2268
|
+
head: stdout.trim(),
|
|
2269
|
+
ref: `refs/remotes/${effectiveRemoteName}/${options.branchName}`,
|
|
2270
|
+
};
|
|
2271
|
+
}
|
|
2272
|
+
catch {
|
|
2273
|
+
return null;
|
|
2274
|
+
}
|
|
2130
2275
|
}
|
|
2131
2276
|
assertSafeGitRef(ref, label) {
|
|
2132
2277
|
if (!SAFE_GIT_REF_PATTERN.test(ref) || ref.includes('..') || ref.includes('@{')) {
|
|
@@ -3442,6 +3587,39 @@ export class Session {
|
|
|
3442
3587
|
});
|
|
3443
3588
|
}
|
|
3444
3589
|
}
|
|
3590
|
+
async handleCheckoutPrSearchRequest(msg) {
|
|
3591
|
+
const cwd = expandTilde(msg.cwd);
|
|
3592
|
+
const { requestId } = msg;
|
|
3593
|
+
try {
|
|
3594
|
+
const result = await searchPullRequests(cwd, {
|
|
3595
|
+
query: msg.query,
|
|
3596
|
+
limit: msg.limit,
|
|
3597
|
+
remoteName: msg.remoteName,
|
|
3598
|
+
});
|
|
3599
|
+
this.emit({
|
|
3600
|
+
type: 'checkout_pr_search_response',
|
|
3601
|
+
payload: {
|
|
3602
|
+
cwd,
|
|
3603
|
+
pullRequests: result.pullRequests,
|
|
3604
|
+
githubFeaturesEnabled: result.githubFeaturesEnabled,
|
|
3605
|
+
error: null,
|
|
3606
|
+
requestId,
|
|
3607
|
+
},
|
|
3608
|
+
});
|
|
3609
|
+
}
|
|
3610
|
+
catch (error) {
|
|
3611
|
+
this.emit({
|
|
3612
|
+
type: 'checkout_pr_search_response',
|
|
3613
|
+
payload: {
|
|
3614
|
+
cwd,
|
|
3615
|
+
pullRequests: [],
|
|
3616
|
+
githubFeaturesEnabled: true,
|
|
3617
|
+
error: this.toCheckoutError(error),
|
|
3618
|
+
requestId,
|
|
3619
|
+
},
|
|
3620
|
+
});
|
|
3621
|
+
}
|
|
3622
|
+
}
|
|
3445
3623
|
async handleCheckoutPrFailureLogsRequest(msg) {
|
|
3446
3624
|
const { cwd, requestId } = msg;
|
|
3447
3625
|
try {
|