conductor-oss 0.3.1 → 0.3.3

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 (136) hide show
  1. package/node_modules/@conductor-oss/plugin-agent-amp/package.json +1 -1
  2. package/node_modules/@conductor-oss/plugin-agent-ccr/package.json +1 -1
  3. package/node_modules/@conductor-oss/plugin-agent-claude-code/package.json +1 -1
  4. package/node_modules/@conductor-oss/plugin-agent-codex/package.json +1 -1
  5. package/node_modules/@conductor-oss/plugin-agent-cursor-cli/package.json +1 -1
  6. package/node_modules/@conductor-oss/plugin-agent-droid/package.json +1 -1
  7. package/node_modules/@conductor-oss/plugin-agent-gemini/dist/index.d.ts +44 -2
  8. package/node_modules/@conductor-oss/plugin-agent-gemini/dist/index.js +203 -17
  9. package/node_modules/@conductor-oss/plugin-agent-gemini/package.json +1 -1
  10. package/node_modules/@conductor-oss/plugin-agent-github-copilot/package.json +1 -1
  11. package/node_modules/@conductor-oss/plugin-agent-opencode/package.json +1 -1
  12. package/node_modules/@conductor-oss/plugin-agent-qwen-code/package.json +1 -1
  13. package/node_modules/@conductor-oss/plugin-mcp-server/package.json +1 -1
  14. package/node_modules/@conductor-oss/plugin-notifier-desktop/package.json +1 -1
  15. package/node_modules/@conductor-oss/plugin-notifier-discord/package.json +1 -1
  16. package/node_modules/@conductor-oss/plugin-runtime-tmux/package.json +1 -1
  17. package/node_modules/@conductor-oss/plugin-scm-github/package.json +1 -1
  18. package/node_modules/@conductor-oss/plugin-terminal-web/package.json +1 -1
  19. package/node_modules/@conductor-oss/plugin-tracker-github/package.json +1 -1
  20. package/node_modules/@conductor-oss/plugin-workspace-worktree/package.json +1 -1
  21. package/package.json +24 -23
  22. package/web/.next/standalone/packages/web/.next/BUILD_ID +1 -1
  23. package/web/.next/standalone/packages/web/.next/build-manifest.json +2 -2
  24. package/web/.next/standalone/packages/web/.next/prerender-manifest.json +3 -3
  25. package/web/.next/standalone/packages/web/.next/server/app/_global-error.html +2 -2
  26. package/web/.next/standalone/packages/web/.next/server/app/_global-error.rsc +1 -1
  27. package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  28. package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  29. package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  30. package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  31. package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  32. package/web/.next/standalone/packages/web/.next/server/app/_not-found/page/server-reference-manifest.json +7 -7
  33. package/web/.next/standalone/packages/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  34. package/web/.next/standalone/packages/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  35. package/web/.next/standalone/packages/web/.next/server/app/_not-found.html +1 -1
  36. package/web/.next/standalone/packages/web/.next/server/app/_not-found.rsc +3 -3
  37. package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  38. package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  39. package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  40. package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  41. package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  42. package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  43. package/web/.next/standalone/packages/web/.next/server/app/index.html +1 -1
  44. package/web/.next/standalone/packages/web/.next/server/app/index.rsc +4 -4
  45. package/web/.next/standalone/packages/web/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  46. package/web/.next/standalone/packages/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
  47. package/web/.next/standalone/packages/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  48. package/web/.next/standalone/packages/web/.next/server/app/index.segments/_index.segment.rsc +3 -3
  49. package/web/.next/standalone/packages/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  50. package/web/.next/standalone/packages/web/.next/server/app/page/react-loadable-manifest.json +1 -1
  51. package/web/.next/standalone/packages/web/.next/server/app/page/server-reference-manifest.json +7 -7
  52. package/web/.next/standalone/packages/web/.next/server/app/page.js.nft.json +1 -1
  53. package/web/.next/standalone/packages/web/.next/server/app/page_client-reference-manifest.js +1 -1
  54. package/web/.next/standalone/packages/web/.next/server/app/sessions/[id]/page/server-reference-manifest.json +7 -7
  55. package/web/.next/standalone/packages/web/.next/server/app/sessions/[id]/page.js.nft.json +1 -1
  56. package/web/.next/standalone/packages/web/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
  57. package/web/.next/standalone/packages/web/.next/server/app/sign-in/[[...sign-in]]/page/server-reference-manifest.json +7 -7
  58. package/web/.next/standalone/packages/web/.next/server/app/sign-in/[[...sign-in]]/page.js.nft.json +1 -1
  59. package/web/.next/standalone/packages/web/.next/server/app/sign-in/[[...sign-in]]/page_client-reference-manifest.js +1 -1
  60. package/web/.next/standalone/packages/web/.next/server/app/unlock/page/server-reference-manifest.json +7 -7
  61. package/web/.next/standalone/packages/web/.next/server/app/unlock/page.js.nft.json +1 -1
  62. package/web/.next/standalone/packages/web/.next/server/app/unlock/page_client-reference-manifest.js +1 -1
  63. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__6622b514._.js +1 -1
  64. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__869d9ac0._.js +1 -1
  65. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__9dc23e5a._.js +1 -1
  66. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{[root-of-the-server]__c3eb4913._.js → [root-of-the-server]__a324b6db._.js} +2 -2
  67. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__b388693f._.js +1 -1
  68. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_0e1412de._.js +1 -1
  69. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_3acfb388._.js +1 -1
  70. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_668c9201._.js +1 -1
  71. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_69e05fca._.js +1 -1
  72. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_80efe193._.js +1 -1
  73. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_b6d31783._.js +1 -1
  74. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_c0f0e227._.js +1 -1
  75. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_f36ddaa9._.js +1 -1
  76. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{node_modules_@clerk_nextjs_dist_esm_app-router_129dde21._.js → node_modules_@clerk_nextjs_dist_esm_app-router_6ed7a74d._.js} +2 -2
  77. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_@clerk_nextjs_dist_esm_app-router_9e576054._.js +3 -0
  78. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_f2ebd7a9._.js +1 -1
  79. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/packages_web_src_79316445._.js +1 -1
  80. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/packages_web_src_a078c137._.js +1 -1
  81. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/packages_web_src_app_page_tsx_cd282e82._.js +1 -1
  82. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{packages_web_src_components_aad7de7c._.js → packages_web_src_components_80ce6eea._.js} +1 -1
  83. package/web/.next/standalone/packages/web/.next/server/pages/404.html +1 -1
  84. package/web/.next/standalone/packages/web/.next/server/pages/500.html +2 -2
  85. package/web/.next/standalone/packages/web/.next/server/server-reference-manifest.js +1 -1
  86. package/web/.next/standalone/packages/web/.next/server/server-reference-manifest.json +8 -8
  87. package/web/.next/standalone/packages/web/.next/static/chunks/25438e9094804f85.js +1 -0
  88. package/web/.next/standalone/packages/web/.next/static/chunks/{5c5637796e242130.js → 34d660195aaf8635.js} +2 -2
  89. package/web/.next/standalone/packages/web/.next/static/chunks/{07a5ac7389686572.js → 3814813ead38f9ae.js} +1 -1
  90. package/web/.next/standalone/packages/web/.next/static/chunks/524a7c2a8e85ea2f.js +1 -0
  91. package/web/.next/{static/chunks/e8d146cc2723147b.js → standalone/packages/web/.next/static/chunks/5672ef74a562c5dd.js} +1 -1
  92. package/web/.next/standalone/packages/web/.next/static/chunks/77d150e9b7dda43c.js +1 -0
  93. package/web/.next/standalone/packages/web/.next/static/chunks/8ec81b945f12169b.js +1 -0
  94. package/web/.next/standalone/packages/web/.next/static/chunks/aa158726d4a10331.js +1 -0
  95. package/web/.next/standalone/packages/web/.next/static/chunks/d3cd3cf58c908ec9.js +1 -0
  96. package/web/.next/standalone/packages/web/.next/static/chunks/df9658182f4f7d54.css +3 -0
  97. package/web/.next/standalone/packages/web/package.json +1 -0
  98. package/web/.next/standalone/packages/web/src/components/layout/Sidebar.tsx +20 -2
  99. package/web/.next/standalone/packages/web/src/components/layout/WorkspaceSidebarPanel.tsx +64 -74
  100. package/web/.next/standalone/packages/web/src/components/sessions/SessionOverview.tsx +114 -50
  101. package/web/.next/standalone/packages/web/src/features/dashboard/DashboardClient.tsx +459 -115
  102. package/web/.next/standalone/packages/web/src/features/dashboard/components/WorkspaceOverview.tsx +13 -31
  103. package/web/.next/standalone/packages/web/src/features/sessions/SessionPageClient.tsx +4 -1
  104. package/web/.next/standalone/packages/web/src/hooks/useSessions.ts +13 -0
  105. package/web/.next/standalone/packages/web/src/lib/types.ts +3 -0
  106. package/web/.next/static/chunks/25438e9094804f85.js +1 -0
  107. package/web/.next/static/chunks/{5c5637796e242130.js → 34d660195aaf8635.js} +2 -2
  108. package/web/.next/static/chunks/{07a5ac7389686572.js → 3814813ead38f9ae.js} +1 -1
  109. package/web/.next/static/chunks/524a7c2a8e85ea2f.js +1 -0
  110. package/web/.next/{standalone/packages/web/.next/static/chunks/e8d146cc2723147b.js → static/chunks/5672ef74a562c5dd.js} +1 -1
  111. package/web/.next/static/chunks/77d150e9b7dda43c.js +1 -0
  112. package/web/.next/static/chunks/8ec81b945f12169b.js +1 -0
  113. package/web/.next/static/chunks/aa158726d4a10331.js +1 -0
  114. package/web/.next/static/chunks/d3cd3cf58c908ec9.js +1 -0
  115. package/web/.next/static/chunks/df9658182f4f7d54.css +3 -0
  116. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_@clerk_nextjs_dist_esm_app-router_1776df7f._.js +0 -3
  117. package/web/.next/standalone/packages/web/.next/static/chunks/3603312f8407bd12.js +0 -1
  118. package/web/.next/standalone/packages/web/.next/static/chunks/382be8f0b30c5f08.js +0 -1
  119. package/web/.next/standalone/packages/web/.next/static/chunks/479b8cb58c95a8a0.js +0 -1
  120. package/web/.next/standalone/packages/web/.next/static/chunks/53e4f3b495e423c9.js +0 -1
  121. package/web/.next/standalone/packages/web/.next/static/chunks/5e05e67a26b7ecbc.js +0 -1
  122. package/web/.next/standalone/packages/web/.next/static/chunks/6d083840413686dd.css +0 -3
  123. package/web/.next/standalone/packages/web/.next/static/chunks/c7c1bd729f17c8bf.js +0 -1
  124. package/web/.next/static/chunks/3603312f8407bd12.js +0 -1
  125. package/web/.next/static/chunks/382be8f0b30c5f08.js +0 -1
  126. package/web/.next/static/chunks/479b8cb58c95a8a0.js +0 -1
  127. package/web/.next/static/chunks/53e4f3b495e423c9.js +0 -1
  128. package/web/.next/static/chunks/5e05e67a26b7ecbc.js +0 -1
  129. package/web/.next/static/chunks/6d083840413686dd.css +0 -3
  130. package/web/.next/static/chunks/c7c1bd729f17c8bf.js +0 -1
  131. /package/web/.next/standalone/packages/web/.next/static/{oFTStwDpHHac4pinj0rAJ → -Z2NIcYE-i0soCDJ1TgtS}/_buildManifest.js +0 -0
  132. /package/web/.next/standalone/packages/web/.next/static/{oFTStwDpHHac4pinj0rAJ → -Z2NIcYE-i0soCDJ1TgtS}/_clientMiddlewareManifest.json +0 -0
  133. /package/web/.next/standalone/packages/web/.next/static/{oFTStwDpHHac4pinj0rAJ → -Z2NIcYE-i0soCDJ1TgtS}/_ssgManifest.js +0 -0
  134. /package/web/.next/static/{oFTStwDpHHac4pinj0rAJ → -Z2NIcYE-i0soCDJ1TgtS}/_buildManifest.js +0 -0
  135. /package/web/.next/static/{oFTStwDpHHac4pinj0rAJ → -Z2NIcYE-i0soCDJ1TgtS}/_clientMiddlewareManifest.json +0 -0
  136. /package/web/.next/static/{oFTStwDpHHac4pinj0rAJ → -Z2NIcYE-i0soCDJ1TgtS}/_ssgManifest.js +0 -0
@@ -3,6 +3,7 @@
3
3
  import dynamic from "next/dynamic";
4
4
  import { type FormEvent, memo, useCallback, useEffect, useMemo, useState } from "react";
5
5
  import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
6
+ import { GitBranchIcon, LockIcon, MarkGithubIcon, RepoIcon } from "@primer/octicons-react";
6
7
  import {
7
8
  getAvailableAgentModels,
8
9
  getAvailableAgentReasoningEfforts,
@@ -32,7 +33,6 @@ import {
32
33
  FolderOpen,
33
34
  FolderGit2,
34
35
  FolderKanban,
35
- Github,
36
36
  Hand,
37
37
  List,
38
38
  Loader2,
@@ -163,6 +163,77 @@ function formatCurrentModelLabel(agentName: string, modelId: string): string {
163
163
  .join("-");
164
164
  }
165
165
 
166
+ function suggestWorkspaceId(value: string): string {
167
+ return value
168
+ .trim()
169
+ .toLowerCase()
170
+ .replace(/[^a-z0-9]+/g, "-")
171
+ .replace(/^-+|-+$/g, "")
172
+ .slice(0, 64);
173
+ }
174
+
175
+ function extractNameFromPath(value: string): string | null {
176
+ const trimmed = value.trim();
177
+ if (!trimmed) return null;
178
+ const segments = trimmed.split(/[\\/]+/).filter(Boolean);
179
+ return segments.at(-1) ?? null;
180
+ }
181
+
182
+ function extractRepositoryNameFromGitUrl(value: string): string | null {
183
+ const trimmed = value.trim();
184
+ if (!trimmed) return null;
185
+
186
+ const sshMatch = trimmed.match(/^git@[^:]+:([^/]+)\/([^/\s]+?)(?:\.git)?$/i);
187
+ if (sshMatch) {
188
+ return sshMatch[2] ?? null;
189
+ }
190
+
191
+ try {
192
+ const parsed = new URL(trimmed);
193
+ const segments = parsed.pathname.split("/").filter(Boolean);
194
+ const last = segments.at(-1);
195
+ return last ? last.replace(/\.git$/i, "") : null;
196
+ } catch {
197
+ return null;
198
+ }
199
+ }
200
+
201
+ function normalizeGitHubRepositoryUrl(value: string): string | null {
202
+ const trimmed = value.trim();
203
+ if (!trimmed) return null;
204
+
205
+ const httpsMatch = trimmed.match(/^https?:\/\/github\.com\/([^/\s]+)\/([^/\s?#]+?)(?:\.git)?\/?$/i);
206
+ if (httpsMatch) {
207
+ return `https://github.com/${httpsMatch[1]}/${httpsMatch[2]}.git`;
208
+ }
209
+
210
+ const sshMatch = trimmed.match(/^git@github\.com:([^/\s]+)\/([^/\s]+?)(?:\.git)?$/i);
211
+ if (sshMatch) {
212
+ return `https://github.com/${sshMatch[1]}/${sshMatch[2]}.git`;
213
+ }
214
+
215
+ return null;
216
+ }
217
+
218
+ function normalizeManualGitUrl(value: string): string | null {
219
+ const trimmed = value.trim();
220
+ if (!trimmed) return null;
221
+ if (/^(https?:\/\/|git@)/i.test(trimmed)) {
222
+ return trimmed;
223
+ }
224
+ return null;
225
+ }
226
+
227
+ function formatRepoUpdatedLabel(value: string | null | undefined): string | null {
228
+ if (!value) return null;
229
+ const timestamp = Date.parse(value);
230
+ if (Number.isNaN(timestamp)) return null;
231
+ return `Updated ${new Intl.DateTimeFormat(undefined, {
232
+ month: "short",
233
+ day: "numeric",
234
+ }).format(new Date(timestamp))}`;
235
+ }
236
+
166
237
  type NewWorkspacePayload = {
167
238
  mode: "git" | "local";
168
239
  projectId?: string;
@@ -206,6 +277,10 @@ type GitHubRepo = {
206
277
  sshUrl: string;
207
278
  defaultBranch: string;
208
279
  private: boolean;
280
+ description?: string | null;
281
+ updatedAt?: string | null;
282
+ ownerLogin?: string | null;
283
+ permission?: string | null;
209
284
  };
210
285
 
211
286
  type DirectoryEntry = {
@@ -937,7 +1012,7 @@ export default function DashboardClient() {
937
1012
  return;
938
1013
  }
939
1014
 
940
- if (!selectedProjectId || !projects.some((project) => project.id === selectedProjectId)) {
1015
+ if (selectedProjectId !== null && !projects.some((project) => project.id === selectedProjectId)) {
941
1016
  setSelectedProjectId(projects[0]?.id ?? null);
942
1017
  }
943
1018
  }, [projects, selectedProjectId]);
@@ -1380,7 +1455,6 @@ export default function DashboardClient() {
1380
1455
 
1381
1456
  const sidebarContent = useMemo(() => (
1382
1457
  <WorkspaceSidebarPanel
1383
- orgLabel="conductor-oss"
1384
1458
  projects={projects}
1385
1459
  selectedProjectId={selectedProjectId}
1386
1460
  onSelectProject={handleSelectProject}
@@ -1457,6 +1531,56 @@ export default function DashboardClient() {
1457
1531
  workspaceView,
1458
1532
  ]);
1459
1533
 
1534
+ const projectWorkspaceContent = useMemo(() => {
1535
+ if (!selectedProject) return workspaceMainPanel;
1536
+
1537
+ return (
1538
+ <div className="flex min-h-0 flex-1 flex-col overflow-hidden">
1539
+ <div className="border-b border-[var(--vk-border)] bg-[var(--vk-bg-panel)]/70 px-3 py-3 sm:px-4">
1540
+ <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
1541
+ <div className="min-w-0">
1542
+ <p className="text-[11px] uppercase tracking-[0.12em] text-[var(--vk-text-muted)]">
1543
+ Project Workspace
1544
+ </p>
1545
+ <p className="mt-1 truncate text-[14px] text-[var(--vk-text-strong)]">
1546
+ {selectedProject.id} · {selectedProject.defaultBranch || "main"}
1547
+ </p>
1548
+ </div>
1549
+
1550
+ <div className="inline-flex w-fit rounded-[6px] border border-[var(--vk-border)] p-1">
1551
+ <button
1552
+ type="button"
1553
+ onClick={() => setWorkspaceView("chat")}
1554
+ className={`min-h-[32px] rounded-[4px] px-3 text-[13px] ${
1555
+ workspaceView === "chat"
1556
+ ? "bg-[var(--vk-bg-active)] text-[var(--vk-text-strong)]"
1557
+ : "text-[var(--vk-text-muted)] hover:bg-[var(--vk-bg-hover)]"
1558
+ }`}
1559
+ >
1560
+ Chat launchpad
1561
+ </button>
1562
+ <button
1563
+ type="button"
1564
+ onClick={() => setWorkspaceView("board")}
1565
+ className={`min-h-[32px] rounded-[4px] px-3 text-[13px] ${
1566
+ workspaceView === "board"
1567
+ ? "bg-[var(--vk-bg-active)] text-[var(--vk-text-strong)]"
1568
+ : "text-[var(--vk-text-muted)] hover:bg-[var(--vk-bg-hover)]"
1569
+ }`}
1570
+ >
1571
+ Board view
1572
+ </button>
1573
+ </div>
1574
+ </div>
1575
+ </div>
1576
+
1577
+ <div className="min-h-0 flex-1 overflow-hidden">
1578
+ {workspaceMainPanel}
1579
+ </div>
1580
+ </div>
1581
+ );
1582
+ }, [selectedProject, workspaceMainPanel, workspaceView]);
1583
+
1460
1584
  const workspaceContent = useMemo(() => {
1461
1585
  if (selectedSessionId) {
1462
1586
  return <SessionDetail sessionId={selectedSessionId} />;
@@ -1465,7 +1589,7 @@ export default function DashboardClient() {
1465
1589
  if (selectedProjectId !== null) {
1466
1590
  return (
1467
1591
  <div className="min-h-0 flex-1 overflow-hidden">
1468
- {workspaceMainPanel}
1592
+ {projectWorkspaceContent}
1469
1593
  </div>
1470
1594
  );
1471
1595
  }
@@ -1476,25 +1600,22 @@ export default function DashboardClient() {
1476
1600
  projects={projects}
1477
1601
  sessions={dashboardSessions}
1478
1602
  selectedProjectId={selectedProjectId}
1479
- workspaceView={workspaceView}
1480
1603
  agentCount={agentOptions.length}
1481
1604
  onCreateWorkspace={openWorkspaceDialog}
1482
1605
  onSelectProject={handleSelectProject}
1483
1606
  onSelectSession={handleSelectSession}
1484
- onShowView={setWorkspaceView}
1485
1607
  />
1486
1608
  </div>
1487
1609
  );
1488
1610
  }, [
1489
1611
  dashboardSessions,
1612
+ projectWorkspaceContent,
1490
1613
  selectedSessionId,
1491
- workspaceMainPanel,
1492
1614
  handleSelectProject,
1493
1615
  handleSelectSession,
1494
1616
  openWorkspaceDialog,
1495
1617
  projects,
1496
1618
  selectedProjectId,
1497
- workspaceView,
1498
1619
  agentOptions.length,
1499
1620
  ]);
1500
1621
 
@@ -1570,6 +1691,7 @@ function NewWorkspaceDialog({
1570
1691
  }) {
1571
1692
  const [mode, setMode] = useState<"git" | "local">("git");
1572
1693
  const [projectId, setProjectId] = useState("");
1694
+ const [projectIdTouched, setProjectIdTouched] = useState(false);
1573
1695
  const [gitUrl, setGitUrl] = useState("");
1574
1696
  const [path, setPath] = useState("");
1575
1697
  const [defaultBranch, setDefaultBranch] = useState("main");
@@ -1578,6 +1700,7 @@ function NewWorkspaceDialog({
1578
1700
  const [initializeGit, setInitializeGit] = useState(true);
1579
1701
  const [githubRepos, setGithubRepos] = useState<GitHubRepo[]>([]);
1580
1702
  const [githubReposLoading, setGithubReposLoading] = useState(false);
1703
+ const [githubReposLoaded, setGithubReposLoaded] = useState(false);
1581
1704
  const [githubReposError, setGithubReposError] = useState<string | null>(null);
1582
1705
  const [githubRepoSearch, setGithubRepoSearch] = useState("");
1583
1706
  const [selectedGithubRepo, setSelectedGithubRepo] = useState("");
@@ -1591,6 +1714,7 @@ function NewWorkspaceDialog({
1591
1714
  if (!open) return;
1592
1715
  setMode("git");
1593
1716
  setProjectId("");
1717
+ setProjectIdTouched(false);
1594
1718
  setGitUrl("");
1595
1719
  setPath("");
1596
1720
  setDefaultBranch("main");
@@ -1598,6 +1722,7 @@ function NewWorkspaceDialog({
1598
1722
  setUseWorktree(true);
1599
1723
  setAgent(defaultAgent);
1600
1724
  setGithubRepos([]);
1725
+ setGithubReposLoaded(false);
1601
1726
  setGithubReposError(null);
1602
1727
  setGithubRepoSearch("");
1603
1728
  setSelectedGithubRepo("");
@@ -1617,15 +1742,42 @@ function NewWorkspaceDialog({
1617
1742
  }, [mode, open, path]);
1618
1743
 
1619
1744
  const filteredGitHubRepos = useMemo(() => {
1620
- if (githubRepoSearch.trim().length === 0) return githubRepos;
1621
1745
  const query = githubRepoSearch.trim().toLowerCase();
1622
- return githubRepos.filter((repo) => {
1746
+ const filtered = query.length === 0 ? githubRepos : githubRepos.filter((repo) => {
1623
1747
  return repo.fullName.toLowerCase().includes(query)
1624
1748
  || repo.name.toLowerCase().includes(query)
1749
+ || (repo.ownerLogin ?? "").toLowerCase().includes(query)
1750
+ || (repo.description ?? "").toLowerCase().includes(query)
1625
1751
  || repo.defaultBranch.toLowerCase().includes(query);
1626
1752
  });
1753
+ return query.length === 0 ? filtered.slice(0, 10) : filtered.slice(0, 14);
1627
1754
  }, [githubRepoSearch, githubRepos]);
1628
1755
 
1756
+ const selectedGitHubRepoData = useMemo(() => {
1757
+ const selected = githubRepos.find((repo) => repo.httpsUrl === selectedGithubRepo);
1758
+ if (selected) return selected;
1759
+ if (!gitUrl.trim()) return null;
1760
+ return githubRepos.find((repo) => repo.httpsUrl === gitUrl.trim()) ?? null;
1761
+ }, [gitUrl, githubRepos, selectedGithubRepo]);
1762
+
1763
+ const normalizedGitHubSearchUrl = useMemo(
1764
+ () => normalizeGitHubRepositoryUrl(githubRepoSearch),
1765
+ [githubRepoSearch],
1766
+ );
1767
+ const normalizedManualGitUrl = useMemo(
1768
+ () => normalizeManualGitUrl(githubRepoSearch),
1769
+ [githubRepoSearch],
1770
+ );
1771
+ const selectedRepoUpdatedLabel = useMemo(
1772
+ () => formatRepoUpdatedLabel(selectedGitHubRepoData?.updatedAt),
1773
+ [selectedGitHubRepoData],
1774
+ );
1775
+ const showUseSearchValueAction = useMemo(() => {
1776
+ const normalizedUrl = normalizedGitHubSearchUrl ?? normalizedManualGitUrl;
1777
+ if (!normalizedUrl) return false;
1778
+ return normalizedUrl.toLowerCase() !== gitUrl.trim().toLowerCase();
1779
+ }, [gitUrl, normalizedGitHubSearchUrl, normalizedManualGitUrl]);
1780
+
1629
1781
  const orderedAgentOptions = useMemo(() => {
1630
1782
  const opts = [...new Set(agentOptions)];
1631
1783
  if (opts.length === 0) {
@@ -1647,13 +1799,12 @@ function NewWorkspaceDialog({
1647
1799
  }
1648
1800
  }, [agent, orderedAgentOptions]);
1649
1801
 
1650
- const handleFetchGitHubRepos = async () => {
1802
+ const handleFetchGitHubRepos = async (forceRefresh = false) => {
1651
1803
  setGithubReposLoading(true);
1652
1804
  setGithubReposError(null);
1653
1805
  try {
1654
- const query = githubRepoSearch.trim();
1655
- const queryParam = query.length > 0 ? `?q=${encodeURIComponent(query)}` : "";
1656
- const res = await fetch(`/api/github/repos${queryParam}`);
1806
+ const query = forceRefresh ? "?refresh=true" : "";
1807
+ const res = await fetch(`/api/github/repos${query}`);
1657
1808
  const data = (await res.json().catch(() => null)) as
1658
1809
  | { repos?: GitHubRepo[]; error?: string }
1659
1810
  | null;
@@ -1667,10 +1818,17 @@ function NewWorkspaceDialog({
1667
1818
  err instanceof Error ? err.message : "Failed to load GitHub repositories",
1668
1819
  );
1669
1820
  } finally {
1821
+ setGithubReposLoaded(true);
1670
1822
  setGithubReposLoading(false);
1671
1823
  }
1672
1824
  };
1673
1825
 
1826
+ useEffect(() => {
1827
+ if (!open || mode !== "git") return;
1828
+ if (githubReposLoading || githubReposLoaded) return;
1829
+ void handleFetchGitHubRepos();
1830
+ }, [githubReposLoaded, githubReposLoading, mode, open]);
1831
+
1674
1832
  const handleDetectBranches = async (
1675
1833
  sourceOverride?: { gitUrl?: string; path?: string },
1676
1834
  ) => {
@@ -1680,7 +1838,7 @@ function NewWorkspaceDialog({
1680
1838
  if (effectiveGitUrl.length === 0 && effectivePath.length === 0) {
1681
1839
  setBranchesError(
1682
1840
  mode === "git"
1683
- ? "Enter a Git URL first."
1841
+ ? "Choose or paste a repository first."
1684
1842
  : "Select a local repository path first.",
1685
1843
  );
1686
1844
  return;
@@ -1732,19 +1890,41 @@ function NewWorkspaceDialog({
1732
1890
  if (!selected) return;
1733
1891
 
1734
1892
  setGitUrl(selected.httpsUrl);
1893
+ setGithubRepoSearch(selected.fullName);
1735
1894
  setDefaultBranch(selected.defaultBranch || "main");
1736
- if (projectId.trim().length === 0) {
1737
- const suggestedProjectId = selected.name
1738
- .toLowerCase()
1739
- .replace(/[^a-z0-9]+/g, "-")
1740
- .replace(/^-+|-+$/g, "")
1741
- .slice(0, 64);
1895
+ if (!projectIdTouched) {
1896
+ const suggestedProjectId = suggestWorkspaceId(selected.name);
1742
1897
  setProjectId(suggestedProjectId || projectId);
1743
1898
  }
1744
1899
 
1745
1900
  await handleDetectBranches({ gitUrl: selected.httpsUrl });
1746
1901
  };
1747
1902
 
1903
+ const handleUseSearchValueAsRepository = async () => {
1904
+ const normalizedUrl = normalizedGitHubSearchUrl ?? normalizedManualGitUrl;
1905
+ if (!normalizedUrl) return;
1906
+
1907
+ const matchingRepo = githubRepos.find((repo) => repo.httpsUrl.toLowerCase() === normalizedUrl.toLowerCase()) ?? null;
1908
+ if (matchingRepo) {
1909
+ await handleSelectGitHubRepo(matchingRepo.httpsUrl);
1910
+ return;
1911
+ }
1912
+
1913
+ setSelectedGithubRepo("");
1914
+ setGitUrl(normalizedUrl);
1915
+ setDefaultBranch("main");
1916
+ setBranchOptions([]);
1917
+ setBranchesError(null);
1918
+ if (!projectIdTouched) {
1919
+ const repoName = extractRepositoryNameFromGitUrl(normalizedUrl);
1920
+ if (repoName) {
1921
+ setProjectId(suggestWorkspaceId(repoName));
1922
+ }
1923
+ }
1924
+
1925
+ await handleDetectBranches({ gitUrl: normalizedUrl });
1926
+ };
1927
+
1748
1928
  const openFolderPicker = (target: "clone" | "local") => {
1749
1929
  setFolderPickerTarget(target);
1750
1930
  setFolderPickerOpen(true);
@@ -1803,7 +1983,7 @@ function NewWorkspaceDialog({
1803
1983
  <div>
1804
1984
  <h2 className="text-[18px] leading-[22px] text-[var(--vk-text-strong)]">Add Workspace</h2>
1805
1985
  <p className="pt-1 text-[12px] text-[var(--vk-text-muted)]">
1806
- Select a repository with a folder picker, then choose the target branch.
1986
+ Pick a GitHub repository or local folder, then confirm the branch.
1807
1987
  </p>
1808
1988
  </div>
1809
1989
  <button
@@ -1828,7 +2008,7 @@ function NewWorkspaceDialog({
1828
2008
  : "text-[var(--vk-text-muted)] hover:bg-[var(--vk-bg-hover)]"
1829
2009
  }`}
1830
2010
  >
1831
- Git Repository
2011
+ GitHub
1832
2012
  </button>
1833
2013
  <button
1834
2014
  type="button"
@@ -1843,103 +2023,198 @@ function NewWorkspaceDialog({
1843
2023
  </button>
1844
2024
  </div>
1845
2025
 
1846
- <label className="block">
1847
- <span className="mb-1.5 block text-[12px] text-[var(--vk-text-muted)]">Project ID (optional)</span>
1848
- <input
1849
- value={projectId}
1850
- onChange={(event) => setProjectId(event.target.value)}
1851
- placeholder="auto-derived from repo/folder"
1852
- className="h-9 w-full rounded-[4px] border border-[var(--vk-border)] bg-transparent px-2 text-[14px] text-[var(--vk-text-normal)] outline-none focus:border-[var(--vk-orange)]"
1853
- />
1854
- </label>
1855
-
1856
2026
  {mode === "git" ? (
1857
2027
  <>
1858
- <div className="rounded-[4px] border border-[var(--vk-border)] p-3">
1859
- <div className="flex items-center gap-2">
1860
- <Github className="h-4 w-4 text-[var(--vk-text-muted)]" />
1861
- <span className="text-[12px] font-medium text-[var(--vk-text-normal)]">GitHub Integration</span>
1862
- </div>
1863
- <div className="mt-2 flex flex-wrap items-center gap-2">
2028
+ <div className="rounded-[6px] border border-[var(--vk-border)] bg-[var(--vk-bg-main)] p-3">
2029
+ <div className="flex items-start gap-3">
2030
+ <span className="inline-flex h-9 w-9 items-center justify-center rounded-[5px] border border-[var(--vk-border)] bg-[var(--vk-bg-panel)] text-[var(--vk-text-strong)]">
2031
+ <MarkGithubIcon className="h-[18px] w-[18px]" />
2032
+ </span>
2033
+ <div className="min-w-0 flex-1">
2034
+ <p className="text-[13px] font-medium text-[var(--vk-text-strong)]">GitHub Repository</p>
2035
+ <p className="pt-0.5 text-[12px] text-[var(--vk-text-muted)]">
2036
+ Search accessible repositories or paste a repository URL. Conductor fills the branch and clone URL after selection.
2037
+ </p>
2038
+ </div>
1864
2039
  <button
1865
2040
  type="button"
1866
- onClick={handleFetchGitHubRepos}
2041
+ onClick={() => {
2042
+ setGithubReposLoaded(false);
2043
+ void handleFetchGitHubRepos(true);
2044
+ }}
1867
2045
  disabled={githubReposLoading}
1868
2046
  className="inline-flex h-8 items-center rounded-[4px] border border-[var(--vk-border)] px-2 text-[12px] text-[var(--vk-text-normal)] hover:bg-[var(--vk-bg-hover)] disabled:opacity-50"
1869
2047
  >
1870
2048
  {githubReposLoading ? (
2049
+ <Loader2 className="h-3.5 w-3.5 animate-spin" />
2050
+ ) : (
1871
2051
  <>
1872
- <Loader2 className="mr-1.5 h-3.5 w-3.5 animate-spin" />
1873
- Loading repos...
2052
+ <RefreshCcw className="mr-1.5 h-3.5 w-3.5" />
2053
+ Refresh
1874
2054
  </>
1875
- ) : "Load My GitHub Repositories"}
2055
+ )}
1876
2056
  </button>
1877
- <div className="relative min-w-[220px] flex-1">
1878
- <Search className="pointer-events-none absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-[var(--vk-text-muted)]" />
1879
- <input
1880
- value={githubRepoSearch}
1881
- onChange={(event) => setGithubRepoSearch(event.target.value)}
1882
- placeholder="Filter repos..."
1883
- className="h-8 w-full rounded-[4px] border border-[var(--vk-border)] bg-transparent pl-7 pr-2 text-[12px] text-[var(--vk-text-normal)] outline-none focus:border-[var(--vk-orange)]"
1884
- />
1885
- </div>
1886
2057
  </div>
1887
- {filteredGitHubRepos.length > 0 && (
1888
- <label className="mt-2 block">
1889
- <span className="mb-1 block text-[11px] text-[var(--vk-text-muted)]">Choose repository</span>
1890
- <select
1891
- value={selectedGithubRepo}
1892
- onChange={(event) => {
1893
- void handleSelectGitHubRepo(event.target.value);
1894
- }}
1895
- className="h-8 w-full rounded-[4px] border border-[var(--vk-border)] bg-[var(--vk-bg-panel)] px-2 text-[12px] text-[var(--vk-text-normal)] outline-none focus:border-[var(--vk-orange)]"
1896
- >
1897
- <option value="">Select a GitHub repo...</option>
1898
- {filteredGitHubRepos.map((repo) => (
1899
- <option key={repo.httpsUrl} value={repo.httpsUrl}>
1900
- {repo.fullName} ({repo.defaultBranch})
1901
- </option>
1902
- ))}
1903
- </select>
1904
- </label>
1905
- )}
1906
- {githubReposError && (
1907
- <p className="mt-2 text-[11px] text-[var(--vk-red)]">{githubReposError}</p>
1908
- )}
1909
- </div>
1910
2058
 
1911
- <label className="block">
1912
- <span className="mb-1.5 block text-[12px] text-[var(--vk-text-muted)]">Git URL</span>
1913
- <input
1914
- value={gitUrl}
1915
- onChange={(event) => setGitUrl(event.target.value)}
1916
- placeholder="https://github.com/org/repo.git"
1917
- className="h-9 w-full rounded-[4px] border border-[var(--vk-border)] bg-transparent px-2 text-[14px] text-[var(--vk-text-normal)] outline-none focus:border-[var(--vk-orange)]"
1918
- />
1919
- </label>
1920
-
1921
- <label className="block">
1922
- <span className="mb-1.5 block text-[12px] text-[var(--vk-text-muted)]">
1923
- Local Path (optional, clone target)
1924
- </span>
1925
- <div className="flex items-center gap-2">
2059
+ <div className="relative mt-3">
2060
+ <MarkGithubIcon className="pointer-events-none absolute left-3 top-1/2 h-[16px] w-[16px] -translate-y-1/2 text-[var(--vk-text-muted)]" />
1926
2061
  <input
1927
- value={path}
1928
- readOnly
1929
- onClick={() => openFolderPicker("clone")}
1930
- placeholder="Use Browse to choose a clone target folder"
1931
- className="h-9 w-full cursor-pointer rounded-[4px] border border-[var(--vk-border)] bg-transparent px-2 text-[14px] text-[var(--vk-text-normal)] outline-none focus:border-[var(--vk-orange)]"
2062
+ value={githubRepoSearch}
2063
+ onChange={(event) => setGithubRepoSearch(event.target.value)}
2064
+ onKeyDown={(event) => {
2065
+ if (event.key === "Enter" && showUseSearchValueAction) {
2066
+ event.preventDefault();
2067
+ void handleUseSearchValueAsRepository();
2068
+ }
2069
+ }}
2070
+ placeholder="Search GitHub repos or paste a repository URL"
2071
+ className="h-10 w-full rounded-[5px] border border-[var(--vk-border)] bg-transparent pl-10 pr-3 text-[14px] text-[var(--vk-text-normal)] outline-none focus:border-[var(--vk-orange)]"
1932
2072
  />
2073
+ </div>
2074
+
2075
+ {showUseSearchValueAction ? (
1933
2076
  <button
1934
2077
  type="button"
1935
- onClick={() => openFolderPicker("clone")}
1936
- className="inline-flex h-9 items-center rounded-[4px] border border-[var(--vk-border)] px-2 text-[12px] text-[var(--vk-text-normal)] hover:bg-[var(--vk-bg-hover)]"
1937
- title="Browse folders"
2078
+ onClick={() => {
2079
+ void handleUseSearchValueAsRepository();
2080
+ }}
2081
+ className="mt-3 inline-flex items-center gap-2 rounded-[5px] border border-[var(--vk-border)] bg-[var(--vk-bg-panel)] px-3 py-2 text-[12px] text-[var(--vk-text-normal)] hover:bg-[var(--vk-bg-hover)]"
1938
2082
  >
1939
- <FolderOpen className="h-4 w-4" />
2083
+ <RepoIcon className="h-4 w-4 text-[var(--vk-text-strong)]" />
2084
+ <span className="truncate">
2085
+ Use {normalizedGitHubSearchUrl ? "this GitHub repository" : "pasted repository URL"}
2086
+ </span>
1940
2087
  </button>
2088
+ ) : null}
2089
+
2090
+ {githubReposLoading && githubRepos.length === 0 ? (
2091
+ <div className="mt-3 rounded-[5px] border border-[var(--vk-border)] px-3 py-3 text-[12px] text-[var(--vk-text-muted)]">
2092
+ Loading accessible GitHub repositories...
2093
+ </div>
2094
+ ) : null}
2095
+
2096
+ {githubReposError ? (
2097
+ <div className="mt-3 rounded-[5px] border border-[var(--vk-red)]/40 bg-[var(--vk-bg-panel)] px-3 py-3 text-[12px] text-[var(--vk-red)]">
2098
+ <p>{githubReposError}</p>
2099
+ <button
2100
+ type="button"
2101
+ onClick={() => {
2102
+ setGithubReposLoaded(false);
2103
+ void handleFetchGitHubRepos(true);
2104
+ }}
2105
+ className="mt-2 inline-flex items-center rounded-[4px] border border-[var(--vk-border)] px-2 py-1 text-[12px] text-[var(--vk-text-normal)] hover:bg-[var(--vk-bg-hover)]"
2106
+ >
2107
+ Retry
2108
+ </button>
2109
+ </div>
2110
+ ) : null}
2111
+
2112
+ {!githubReposError && filteredGitHubRepos.length > 0 ? (
2113
+ <div className="mt-3 max-h-[260px] space-y-2 overflow-y-auto pr-1">
2114
+ {filteredGitHubRepos.map((repo) => {
2115
+ const repoUpdatedLabel = formatRepoUpdatedLabel(repo.updatedAt);
2116
+ const selected = selectedGitHubRepoData?.httpsUrl === repo.httpsUrl;
2117
+ return (
2118
+ <button
2119
+ key={repo.httpsUrl}
2120
+ type="button"
2121
+ onClick={() => {
2122
+ void handleSelectGitHubRepo(repo.httpsUrl);
2123
+ }}
2124
+ className={`flex w-full items-start gap-3 rounded-[5px] border px-3 py-3 text-left transition ${
2125
+ selected
2126
+ ? "border-[var(--vk-orange)] bg-[var(--vk-bg-panel)]"
2127
+ : "border-[var(--vk-border)] bg-[var(--vk-bg-panel)] hover:bg-[var(--vk-bg-hover)]"
2128
+ }`}
2129
+ >
2130
+ <span className="mt-0.5 inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-[5px] border border-[var(--vk-border)] bg-[var(--vk-bg-main)] text-[var(--vk-text-strong)]">
2131
+ <RepoIcon className="h-4 w-4" />
2132
+ </span>
2133
+ <span className="min-w-0 flex-1">
2134
+ <span className="flex flex-wrap items-center gap-2">
2135
+ <span className="truncate text-[13px] font-medium text-[var(--vk-text-strong)]">
2136
+ {repo.fullName}
2137
+ </span>
2138
+ <span className="inline-flex items-center gap-1 rounded-full border border-[var(--vk-border)] px-2 py-0.5 text-[11px] text-[var(--vk-text-muted)]">
2139
+ {repo.private ? <LockIcon className="h-3 w-3" /> : <MarkGithubIcon className="h-3 w-3" />}
2140
+ {repo.private ? "Private" : "Public"}
2141
+ </span>
2142
+ <span className="inline-flex items-center gap-1 rounded-full border border-[var(--vk-border)] px-2 py-0.5 text-[11px] text-[var(--vk-text-muted)]">
2143
+ <GitBranchIcon className="h-3 w-3" />
2144
+ {repo.defaultBranch}
2145
+ </span>
2146
+ </span>
2147
+ {repo.description ? (
2148
+ <span className="mt-1 block line-clamp-2 text-[12px] leading-[17px] text-[var(--vk-text-muted)]">
2149
+ {repo.description}
2150
+ </span>
2151
+ ) : null}
2152
+ <span className="mt-1 flex flex-wrap gap-x-3 gap-y-1 text-[11px] text-[var(--vk-text-muted)]">
2153
+ {repo.ownerLogin ? <span>{repo.ownerLogin}</span> : null}
2154
+ {repoUpdatedLabel ? <span>{repoUpdatedLabel}</span> : null}
2155
+ {repo.permission ? <span>{repo.permission.toLowerCase()}</span> : null}
2156
+ </span>
2157
+ </span>
2158
+ <span className="ml-auto inline-flex h-4 w-4 shrink-0 items-center justify-center text-[var(--vk-text-strong)]">
2159
+ {selected ? <Check className="h-4 w-4" /> : null}
2160
+ </span>
2161
+ </button>
2162
+ );
2163
+ })}
2164
+ </div>
2165
+ ) : null}
2166
+
2167
+ {!githubReposLoading && !githubReposError && filteredGitHubRepos.length === 0 ? (
2168
+ <p className="mt-3 text-[12px] text-[var(--vk-text-muted)]">
2169
+ {githubRepoSearch.trim().length > 0
2170
+ ? "No matching repositories. Try another search or paste a repository URL."
2171
+ : "No accessible GitHub repositories were found for this machine yet."}
2172
+ </p>
2173
+ ) : null}
2174
+ </div>
2175
+
2176
+ {gitUrl.trim().length > 0 ? (
2177
+ <div className="rounded-[6px] border border-[var(--vk-border)] bg-[var(--vk-bg-main)] p-3">
2178
+ <div className="flex items-start gap-3">
2179
+ <span className="inline-flex h-9 w-9 items-center justify-center rounded-[5px] border border-[var(--vk-border)] bg-[var(--vk-bg-panel)] text-[var(--vk-text-strong)]">
2180
+ <RepoIcon className="h-[18px] w-[18px]" />
2181
+ </span>
2182
+ <div className="min-w-0 flex-1">
2183
+ <div className="flex flex-wrap items-center gap-2">
2184
+ <span className="truncate text-[14px] font-medium text-[var(--vk-text-strong)]">
2185
+ {selectedGitHubRepoData?.fullName ?? gitUrl}
2186
+ </span>
2187
+ <span className="inline-flex items-center gap-1 rounded-full border border-[var(--vk-border)] px-2 py-0.5 text-[11px] text-[var(--vk-text-muted)]">
2188
+ {selectedGitHubRepoData ? (
2189
+ selectedGitHubRepoData.private ? <LockIcon className="h-3 w-3" /> : <MarkGithubIcon className="h-3 w-3" />
2190
+ ) : (
2191
+ <MarkGithubIcon className="h-3 w-3" />
2192
+ )}
2193
+ {selectedGitHubRepoData ? (selectedGitHubRepoData.private ? "Private" : "Public") : "Manual URL"}
2194
+ </span>
2195
+ <span className="inline-flex items-center gap-1 rounded-full border border-[var(--vk-border)] px-2 py-0.5 text-[11px] text-[var(--vk-text-muted)]">
2196
+ <GitBranchIcon className="h-3 w-3" />
2197
+ {defaultBranch || "main"}
2198
+ </span>
2199
+ </div>
2200
+ {selectedGitHubRepoData?.description ? (
2201
+ <p className="mt-1 line-clamp-2 text-[12px] leading-[17px] text-[var(--vk-text-muted)]">
2202
+ {selectedGitHubRepoData.description}
2203
+ </p>
2204
+ ) : (
2205
+ <p className="mt-1 truncate text-[12px] text-[var(--vk-text-muted)]">{gitUrl}</p>
2206
+ )}
2207
+ <div className="mt-1 flex flex-wrap gap-x-3 gap-y-1 text-[11px] text-[var(--vk-text-muted)]">
2208
+ {selectedGitHubRepoData?.ownerLogin ? <span>{selectedGitHubRepoData.ownerLogin}</span> : null}
2209
+ {selectedRepoUpdatedLabel ? <span>{selectedRepoUpdatedLabel}</span> : null}
2210
+ {selectedGitHubRepoData?.permission ? (
2211
+ <span>{selectedGitHubRepoData.permission.toLowerCase()}</span>
2212
+ ) : null}
2213
+ </div>
2214
+ </div>
2215
+ </div>
1941
2216
  </div>
1942
- </label>
2217
+ ) : null}
1943
2218
  </>
1944
2219
  ) : (
1945
2220
  <>
@@ -1975,14 +2250,27 @@ function NewWorkspaceDialog({
1975
2250
  </>
1976
2251
  )}
1977
2252
 
2253
+ <label className="block">
2254
+ <span className="mb-1.5 block text-[12px] text-[var(--vk-text-muted)]">Workspace Name (optional)</span>
2255
+ <input
2256
+ value={projectId}
2257
+ onChange={(event) => {
2258
+ setProjectId(event.target.value);
2259
+ setProjectIdTouched(true);
2260
+ }}
2261
+ placeholder="auto-derived from the selected repository or folder"
2262
+ className="h-9 w-full rounded-[4px] border border-[var(--vk-border)] bg-transparent px-2 text-[14px] text-[var(--vk-text-normal)] outline-none focus:border-[var(--vk-orange)]"
2263
+ />
2264
+ </label>
2265
+
1978
2266
  <div className="grid grid-cols-1 gap-3 md:grid-cols-2">
1979
2267
  <label className="block">
1980
- <span className="mb-1.5 block text-[12px] text-[var(--vk-text-muted)]">Default Branch</span>
2268
+ <span className="mb-1.5 block text-[12px] text-[var(--vk-text-muted)]">Branch</span>
1981
2269
  <div className="flex items-center gap-2">
1982
2270
  <input
1983
2271
  value={defaultBranch}
1984
2272
  onChange={(event) => setDefaultBranch(event.target.value)}
1985
- placeholder="main"
2273
+ placeholder="Uses the repository default branch"
1986
2274
  className="h-9 w-full rounded-[4px] border border-[var(--vk-border)] bg-transparent px-2 text-[14px] text-[var(--vk-text-normal)] outline-none focus:border-[var(--vk-orange)]"
1987
2275
  />
1988
2276
  <button
@@ -2035,20 +2323,70 @@ function NewWorkspaceDialog({
2035
2323
  </label>
2036
2324
  </div>
2037
2325
 
2038
- <label className="flex items-start gap-2 rounded-[4px] border border-[var(--vk-border)] px-2 py-2 text-[13px] text-[var(--vk-text-normal)]">
2039
- <input
2040
- type="checkbox"
2041
- checked={useWorktree}
2042
- onChange={(event) => setUseWorktree(event.target.checked)}
2043
- className="mt-0.5 h-4 w-4 rounded border border-[var(--vk-border)] bg-transparent accent-[var(--vk-orange)]"
2044
- />
2045
- <span>
2046
- Use worktree isolation
2047
- <span className="block text-[11px] text-[var(--vk-text-muted)]">
2048
- If unchecked, sessions run directly on the selected branch in the local repo.
2326
+ {mode === "git" ? (
2327
+ <details className="rounded-[4px] border border-[var(--vk-border)] bg-[var(--vk-bg-main)]">
2328
+ <summary className="cursor-pointer list-none px-3 py-2 text-[13px] text-[var(--vk-text-normal)] marker:hidden">
2329
+ <span className="inline-flex items-center gap-2">
2330
+ <ChevronDown className="h-3.5 w-3.5 text-[var(--vk-text-muted)]" />
2331
+ Advanced options
2332
+ </span>
2333
+ </summary>
2334
+ <div className="space-y-3 border-t border-[var(--vk-border)] px-3 py-3">
2335
+ <label className="block">
2336
+ <span className="mb-1.5 block text-[12px] text-[var(--vk-text-muted)]">
2337
+ Local Copy Location (optional)
2338
+ </span>
2339
+ <div className="flex items-center gap-2">
2340
+ <input
2341
+ value={path}
2342
+ readOnly
2343
+ onClick={() => openFolderPicker("clone")}
2344
+ placeholder="Choose a folder only if you want a specific clone location"
2345
+ className="h-9 w-full cursor-pointer rounded-[4px] border border-[var(--vk-border)] bg-transparent px-2 text-[14px] text-[var(--vk-text-normal)] outline-none focus:border-[var(--vk-orange)]"
2346
+ />
2347
+ <button
2348
+ type="button"
2349
+ onClick={() => openFolderPicker("clone")}
2350
+ className="inline-flex h-9 items-center rounded-[4px] border border-[var(--vk-border)] px-2 text-[12px] text-[var(--vk-text-normal)] hover:bg-[var(--vk-bg-hover)]"
2351
+ title="Browse folders"
2352
+ >
2353
+ <FolderOpen className="h-4 w-4" />
2354
+ </button>
2355
+ </div>
2356
+ </label>
2357
+
2358
+ <label className="flex items-start gap-2 rounded-[4px] border border-[var(--vk-border)] px-2 py-2 text-[13px] text-[var(--vk-text-normal)]">
2359
+ <input
2360
+ type="checkbox"
2361
+ checked={useWorktree}
2362
+ onChange={(event) => setUseWorktree(event.target.checked)}
2363
+ className="mt-0.5 h-4 w-4 rounded border border-[var(--vk-border)] bg-transparent accent-[var(--vk-orange)]"
2364
+ />
2365
+ <span>
2366
+ Keep work isolated in a new worktree
2367
+ <span className="block text-[11px] text-[var(--vk-text-muted)]">
2368
+ Turn this off only if you want sessions to run directly in the selected branch.
2369
+ </span>
2370
+ </span>
2371
+ </label>
2372
+ </div>
2373
+ </details>
2374
+ ) : (
2375
+ <label className="flex items-start gap-2 rounded-[4px] border border-[var(--vk-border)] px-2 py-2 text-[13px] text-[var(--vk-text-normal)]">
2376
+ <input
2377
+ type="checkbox"
2378
+ checked={useWorktree}
2379
+ onChange={(event) => setUseWorktree(event.target.checked)}
2380
+ className="mt-0.5 h-4 w-4 rounded border border-[var(--vk-border)] bg-transparent accent-[var(--vk-orange)]"
2381
+ />
2382
+ <span>
2383
+ Keep work isolated in a new worktree
2384
+ <span className="block text-[11px] text-[var(--vk-text-muted)]">
2385
+ Turn this off only if you want sessions to run directly in the selected branch.
2386
+ </span>
2049
2387
  </span>
2050
- </span>
2051
- </label>
2388
+ </label>
2389
+ )}
2052
2390
 
2053
2391
  {error && <p className="text-[12px] text-[var(--vk-red)]">{error}</p>}
2054
2392
  </div>
@@ -2090,6 +2428,12 @@ function NewWorkspaceDialog({
2090
2428
  setFolderPickerOpen(false);
2091
2429
  if (!selectedPath) return;
2092
2430
  setPath(selectedPath);
2431
+ if ((mode === "local" || folderPickerTarget === "local") && !projectIdTouched) {
2432
+ const folderName = extractNameFromPath(selectedPath);
2433
+ if (folderName) {
2434
+ setProjectId(suggestWorkspaceId(folderName));
2435
+ }
2436
+ }
2093
2437
  if (mode === "local" || folderPickerTarget === "local") {
2094
2438
  void handleDetectBranches({ path: selectedPath });
2095
2439
  }