@junctionpanel/server 0.1.39 → 0.1.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/dist/server/client/daemon-client.d.ts +8 -1
  2. package/dist/server/client/daemon-client.d.ts.map +1 -1
  3. package/dist/server/client/daemon-client.js +14 -0
  4. package/dist/server/client/daemon-client.js.map +1 -1
  5. package/dist/server/server/agent/agent-manager.d.ts +1 -0
  6. package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
  7. package/dist/server/server/agent/agent-manager.js +28 -6
  8. package/dist/server/server/agent/agent-manager.js.map +1 -1
  9. package/dist/server/server/agent/agent-projections.d.ts.map +1 -1
  10. package/dist/server/server/agent/agent-projections.js +3 -0
  11. package/dist/server/server/agent/agent-projections.js.map +1 -1
  12. package/dist/server/server/agent/agent-sdk-types.d.ts +2 -0
  13. package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
  14. package/dist/server/server/session.d.ts +2 -0
  15. package/dist/server/server/session.d.ts.map +1 -1
  16. package/dist/server/server/session.js +182 -4
  17. package/dist/server/server/session.js.map +1 -1
  18. package/dist/server/shared/messages.d.ts +979 -0
  19. package/dist/server/shared/messages.d.ts.map +1 -1
  20. package/dist/server/shared/messages.js +35 -0
  21. package/dist/server/shared/messages.js.map +1 -1
  22. package/dist/server/utils/checkout-git.d.ts +19 -0
  23. package/dist/server/utils/checkout-git.d.ts.map +1 -1
  24. package/dist/server/utils/checkout-git.js +85 -0
  25. package/dist/server/utils/checkout-git.js.map +1 -1
  26. package/dist/server/utils/worktree.d.ts +23 -0
  27. package/dist/server/utils/worktree.d.ts.map +1 -1
  28. package/dist/server/utils/worktree.js +120 -1
  29. package/dist/server/utils/worktree.js.map +1 -1
  30. 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 {