quadwork 1.12.0 → 1.13.0

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 (70) hide show
  1. package/bin/quadwork.js +81 -89
  2. package/out/404.html +1 -1
  3. package/out/__next.__PAGE__.txt +1 -1
  4. package/out/__next._full.txt +1 -1
  5. package/out/__next._head.txt +1 -1
  6. package/out/__next._index.txt +1 -1
  7. package/out/__next._tree.txt +1 -1
  8. package/out/_next/static/chunks/0wuucfn72wx0t.js +1 -0
  9. package/out/_not-found/__next._full.txt +1 -1
  10. package/out/_not-found/__next._head.txt +1 -1
  11. package/out/_not-found/__next._index.txt +1 -1
  12. package/out/_not-found/__next._not-found.__PAGE__.txt +1 -1
  13. package/out/_not-found/__next._not-found.txt +1 -1
  14. package/out/_not-found/__next._tree.txt +1 -1
  15. package/out/_not-found.html +1 -1
  16. package/out/_not-found.txt +1 -1
  17. package/out/app-shell/__next._full.txt +1 -1
  18. package/out/app-shell/__next._head.txt +1 -1
  19. package/out/app-shell/__next._index.txt +1 -1
  20. package/out/app-shell/__next._tree.txt +1 -1
  21. package/out/app-shell/__next.app-shell.__PAGE__.txt +1 -1
  22. package/out/app-shell/__next.app-shell.txt +1 -1
  23. package/out/app-shell.html +1 -1
  24. package/out/app-shell.txt +1 -1
  25. package/out/index.html +1 -1
  26. package/out/index.txt +1 -1
  27. package/out/project/_/__next._full.txt +1 -1
  28. package/out/project/_/__next._head.txt +1 -1
  29. package/out/project/_/__next._index.txt +1 -1
  30. package/out/project/_/__next._tree.txt +1 -1
  31. package/out/project/_/__next.project.$d$id.__PAGE__.txt +1 -1
  32. package/out/project/_/__next.project.$d$id.txt +1 -1
  33. package/out/project/_/__next.project.txt +1 -1
  34. package/out/project/_/queue/__next._full.txt +1 -1
  35. package/out/project/_/queue/__next._head.txt +1 -1
  36. package/out/project/_/queue/__next._index.txt +1 -1
  37. package/out/project/_/queue/__next._tree.txt +1 -1
  38. package/out/project/_/queue/__next.project.$d$id.queue.__PAGE__.txt +1 -1
  39. package/out/project/_/queue/__next.project.$d$id.queue.txt +1 -1
  40. package/out/project/_/queue/__next.project.$d$id.txt +1 -1
  41. package/out/project/_/queue/__next.project.txt +1 -1
  42. package/out/project/_/queue.html +1 -1
  43. package/out/project/_/queue.txt +1 -1
  44. package/out/project/_.html +1 -1
  45. package/out/project/_.txt +1 -1
  46. package/out/settings/__next._full.txt +1 -1
  47. package/out/settings/__next._head.txt +1 -1
  48. package/out/settings/__next._index.txt +1 -1
  49. package/out/settings/__next._tree.txt +1 -1
  50. package/out/settings/__next.settings.__PAGE__.txt +1 -1
  51. package/out/settings/__next.settings.txt +1 -1
  52. package/out/settings.html +1 -1
  53. package/out/settings.txt +1 -1
  54. package/out/setup/__next._full.txt +2 -2
  55. package/out/setup/__next._head.txt +1 -1
  56. package/out/setup/__next._index.txt +1 -1
  57. package/out/setup/__next._tree.txt +1 -1
  58. package/out/setup/__next.setup.__PAGE__.txt +2 -2
  59. package/out/setup/__next.setup.txt +1 -1
  60. package/out/setup.html +1 -1
  61. package/out/setup.txt +2 -2
  62. package/package.json +1 -1
  63. package/server/agentchattr-registry.js +17 -0
  64. package/server/config.js +3 -1
  65. package/server/index.js +116 -25
  66. package/server/routes.js +24 -1
  67. package/out/_next/static/chunks/09h0i4gh79na..js +0 -1
  68. /package/out/_next/static/{nkNB54Q5aOvoEsUmAlro2 → 4nR7KJBgnDoDp9KRiNBWM}/_buildManifest.js +0 -0
  69. /package/out/_next/static/{nkNB54Q5aOvoEsUmAlro2 → 4nR7KJBgnDoDp9KRiNBWM}/_clientMiddlewareManifest.js +0 -0
  70. /package/out/_next/static/{nkNB54Q5aOvoEsUmAlro2 → 4nR7KJBgnDoDp9KRiNBWM}/_ssgManifest.js +0 -0
package/server/index.js CHANGED
@@ -13,7 +13,7 @@ const {
13
13
  patchAgentchattrConfigForTelegramBridge,
14
14
  projectAgentchattrConfigPath,
15
15
  } = routes;
16
- const { waitForAgentChattrReady, registerAgent, deregisterAgent, startHeartbeat, stopHeartbeat } = require("./agentchattr-registry");
16
+ const { waitForAgentChattrReady, registerAgent, registerAgentWithRetry, deregisterAgent, startHeartbeat, stopHeartbeat } = require("./agentchattr-registry");
17
17
  const { patchAgentchattrCss } = require("./install-agentchattr");
18
18
  const { startQueueWatcher, stopQueueWatcher } = require("./queue-watcher");
19
19
 
@@ -356,7 +356,15 @@ async function buildAgentArgs(projectId, agentId) {
356
356
  // write that into the per-agent MCP config file.
357
357
  const chattrInfo = resolveProjectChattr(projectId);
358
358
  acServerPort = Number(new URL(chattrInfo.url).port) || 8300;
359
- await waitForAgentChattrReady(acServerPort);
359
+ // #565: extend timeout to 30s — first setup may need AC to install
360
+ // (git clone + venv + pip install) before it can bind a port.
361
+ const acReady = await waitForAgentChattrReady(acServerPort, 30000);
362
+ if (!acReady) {
363
+ console.warn(`[#565] Agent ${agentId}: AC not reachable on port ${acServerPort} after 30s. Spawning without chat integration.`);
364
+ // #565: preserve acServerPort and acInjectMode so deferred
365
+ // recovery in spawnAgentPty can retry registration later.
366
+ return { args, acRegistrationName: null, acServerPort, acRegistrationToken: null, acInjectMode: injectMode, acMcpHttpPort: mcpHttpPort || null };
367
+ }
360
368
  // #242: best-effort deregister any stale registration of the
361
369
  // canonical name (left over by a crashed previous QuadWork
362
370
  // session) so the fresh register lands at slot 1 instead of
@@ -370,16 +378,18 @@ async function buildAgentArgs(projectId, agentId) {
370
378
  clearPersistedAgentToken(projectId, agentId);
371
379
  }
372
380
  // #478: force-replace so AC expires any ghost slots for this base
373
- const registration = await registerAgent(acServerPort, agentId, agentCfg.display_name || null, { force: true });
381
+ // #565: retry with backoff and degrade gracefully if AC is not ready
382
+ const registration = await registerAgentWithRetry(acServerPort, agentId, agentCfg.display_name || null, { force: true });
374
383
  if (!registration) {
375
- throw new Error(`Failed to register ${agentId}: ${registerAgent.lastError}`);
384
+ console.warn(`[#565] Agent ${agentId}: AC registration failed after retries (${registerAgent.lastError}). Spawning without chat integration.`);
385
+ } else {
386
+ acRegistrationName = registration.name;
387
+ acRegistrationToken = registration.token;
388
+ writePersistedAgentToken(projectId, agentId, registration.token);
389
+ const mcpConfigPath = writeMcpConfigFile(projectId, agentId, mcpHttpPort, registration.token);
390
+ const flag = agentCfg.mcp_flag || "--mcp-config";
391
+ args.push(flag, mcpConfigPath);
376
392
  }
377
- acRegistrationName = registration.name;
378
- acRegistrationToken = registration.token;
379
- writePersistedAgentToken(projectId, agentId, registration.token);
380
- const mcpConfigPath = writeMcpConfigFile(projectId, agentId, mcpHttpPort, registration.token);
381
- const flag = agentCfg.mcp_flag || "--mcp-config";
382
- args.push(flag, mcpConfigPath);
383
393
  } else if (injectMode === "proxy_flag") {
384
394
  // Codex: register with AgentChattr first (#240) so the proxy
385
395
  // injects a real per-agent token, not the global session token.
@@ -387,7 +397,14 @@ async function buildAgentArgs(projectId, agentId) {
387
397
  // projects without a per-project agentchattr_url still work.
388
398
  const chattrInfo = resolveProjectChattr(projectId);
389
399
  acServerPort = Number(new URL(chattrInfo.url).port) || 8300;
390
- await waitForAgentChattrReady(acServerPort);
400
+ // #565: extend timeout to 30s for first-setup scenario
401
+ const acReady = await waitForAgentChattrReady(acServerPort, 30000);
402
+ if (!acReady) {
403
+ console.warn(`[#565] Agent ${agentId}: AC not reachable on port ${acServerPort} after 30s. Spawning without chat integration.`);
404
+ // #565: preserve acServerPort and acInjectMode so deferred
405
+ // recovery in spawnAgentPty can retry registration later.
406
+ return { args, acRegistrationName: null, acServerPort, acRegistrationToken: null, acInjectMode: injectMode, acMcpHttpPort: mcpHttpPort || null };
407
+ }
391
408
  // #242: best-effort deregister stale canonical name first using
392
409
  // the persisted bearer token from a previous session.
393
410
  const stalePersistedToken = readPersistedAgentToken(projectId, agentId);
@@ -396,17 +413,19 @@ async function buildAgentArgs(projectId, agentId) {
396
413
  clearPersistedAgentToken(projectId, agentId);
397
414
  }
398
415
  // #478: force-replace so AC expires any ghost slots for this base
399
- const registration = await registerAgent(acServerPort, agentId, agentCfg.display_name || null, { force: true });
416
+ // #565: retry with backoff and degrade gracefully if AC is not ready
417
+ const registration = await registerAgentWithRetry(acServerPort, agentId, agentCfg.display_name || null, { force: true });
400
418
  if (!registration) {
401
- throw new Error(`Failed to register ${agentId}: ${registerAgent.lastError}`);
402
- }
403
- acRegistrationName = registration.name;
404
- acRegistrationToken = registration.token;
405
- writePersistedAgentToken(projectId, agentId, registration.token);
406
- const upstreamUrl = `http://127.0.0.1:${mcpHttpPort}`;
407
- const proxyUrl = await startMcpProxy(projectId, agentId, upstreamUrl, registration.token);
408
- if (proxyUrl) {
409
- args.push("-c", `mcp_servers.agentchattr.url="${proxyUrl}"`);
419
+ console.warn(`[#565] Agent ${agentId}: AC registration failed after retries (${registerAgent.lastError}). Spawning without chat integration.`);
420
+ } else {
421
+ acRegistrationName = registration.name;
422
+ acRegistrationToken = registration.token;
423
+ writePersistedAgentToken(projectId, agentId, registration.token);
424
+ const upstreamUrl = `http://127.0.0.1:${mcpHttpPort}`;
425
+ const proxyUrl = await startMcpProxy(projectId, agentId, upstreamUrl, registration.token);
426
+ if (proxyUrl) {
427
+ args.push("-c", `mcp_servers.agentchattr.url="${proxyUrl}"`);
428
+ }
410
429
  }
411
430
  }
412
431
  }
@@ -526,11 +545,14 @@ async function spawnAgentPty(project, agent) {
526
545
  if (!cwd) return { ok: false, error: `Unknown agent: ${key}` };
527
546
 
528
547
  const command = resolveAgentCommand(project, agent) || (process.env.SHELL || "/bin/zsh");
529
- const built = await buildAgentArgs(project, agent);
530
- const args = built.args;
531
548
  const extraEnv = buildAgentEnv(project, agent);
532
549
 
533
550
  try {
551
+ // #565: buildAgentArgs is inside try-catch so registration failures
552
+ // cannot crash the server as an unhandled rejection.
553
+ const built = await buildAgentArgs(project, agent);
554
+ const args = built.args;
555
+
534
556
  const term = pty.spawn(command, args, {
535
557
  name: "xterm-256color",
536
558
  cols: 120,
@@ -611,6 +633,30 @@ async function spawnAgentPty(project, agent) {
611
633
  }
612
634
  }
613
635
 
636
+ // #565: deferred restart — if the agent spawned without AC
637
+ // registration (AC wasn't ready or registration failed), wait for
638
+ // AC to come up then stop + respawn the agent so it gets the full
639
+ // MCP CLI args (--mcp-config / -c mcp_servers...url) that can only
640
+ // be set at process launch time.
641
+ if (!session.acRegistrationName && session.acServerPort && session.acInjectMode) {
642
+ const deferredRestart = async () => {
643
+ const ready = await waitForAgentChattrReady(session.acServerPort, 60000);
644
+ if (!ready) {
645
+ // #572: log timeout so operators know the health monitor will
646
+ // handle recovery when AC eventually comes up.
647
+ console.log(`[#565] Agent ${agent}: AC not reachable after 60s — health monitor will restart agent when AC recovers.`);
648
+ return;
649
+ }
650
+ // Guard: agent may have been stopped manually while we waited.
651
+ const current = agentSessions.get(key);
652
+ if (!current || !current.term || current.state !== "running") return;
653
+ console.log(`[#565] Agent ${agent}: AC is now reachable — restarting agent to gain chat integration.`);
654
+ await stopAgentSession(key);
655
+ await spawnAgentPty(project, agent);
656
+ };
657
+ deferredRestart().catch(() => {});
658
+ }
659
+
614
660
  term.onExit(({ exitCode }) => {
615
661
  const current = agentSessions.get(key);
616
662
  if (current && current.term === term) {
@@ -834,16 +880,25 @@ async function handleAgentChattr(req, res) {
834
880
  return null;
835
881
  }
836
882
 
883
+ // #569: redirect AC stdout/stderr to a log file so operators can
884
+ // diagnose startup failures. Append mode preserves restart history.
885
+ const acLogDir = path.join(os.homedir(), ".quadwork", projectId);
886
+ try { fs.mkdirSync(acLogDir, { recursive: true, mode: 0o700 }); } catch {}
887
+ const acLogPath = path.join(acLogDir, "agentchattr.log");
888
+ const acLogFd = fs.openSync(acLogPath, "a");
837
889
  const child = spawn(acSpawn.command, [...acSpawn.args, ...extraArgs], {
838
890
  cwd: acSpawn.cwd,
839
891
  env: process.env,
840
- stdio: "ignore",
892
+ stdio: ["ignore", acLogFd, acLogFd],
841
893
  detached: true,
842
894
  });
843
895
 
896
+ // Close our copy of the log fd — child inherits its own copy.
897
+ fs.closeSync(acLogFd);
898
+
844
899
  // If pid is undefined, spawn failed
845
900
  if (!child.pid) {
846
- setProc({ process: null, state: "error", error: "Failed to start AgentChattr — check that Python venv is set up in " + acDir });
901
+ setProc({ process: null, state: "error", error: "Failed to start AgentChattr — check that Python venv is set up in " + acDir + ". Log: " + acLogPath });
847
902
  child.on("error", () => {});
848
903
  return null;
849
904
  }
@@ -2038,6 +2093,35 @@ setInterval(runLoopGuardPollingTick, LOOP_GUARD_POLL_INTERVAL_MS);
2038
2093
  // logic). Rate-limited to one restart per 60s per project; gives up after
2039
2094
  // 3 consecutive failures and surfaces a persistent error.
2040
2095
  // ---------------------------------------------------------------------------
2096
+ // #572: restart agents that are running without AC registration after AC
2097
+ // recovers from a crash. Scans agentSessions for the given project,
2098
+ // finds agents missing acRegistrationName, and stop+respawns them so
2099
+ // they get MCP CLI flags at launch time.
2100
+ async function restartUnregisteredAgents(projectId) {
2101
+ const toRestart = [];
2102
+ for (const [key, session] of agentSessions) {
2103
+ if (session.projectId !== projectId) continue;
2104
+ if (session.acRegistrationName) continue; // already registered
2105
+ if (session.state !== "running") continue;
2106
+ if (!session.acServerPort || !session.acInjectMode) continue;
2107
+ toRestart.push({ key, agentId: session.agentId });
2108
+ }
2109
+
2110
+ if (toRestart.length === 0) return;
2111
+ const samplePort = agentSessions.get(toRestart[0].key)?.acServerPort || "?";
2112
+ console.log(`[health] AC recovered on port ${samplePort} — restarting ${toRestart.length} agent(s) for chat integration`);
2113
+
2114
+ for (const { key, agentId } of toRestart) {
2115
+ try {
2116
+ console.log(`[health] Restarting agent ${agentId} for project ${projectId} to gain chat integration`);
2117
+ await stopAgentSession(key);
2118
+ await spawnAgentPty(projectId, agentId);
2119
+ } catch (err) {
2120
+ console.error(`[health] Failed to restart agent ${agentId}: ${err.message}`);
2121
+ }
2122
+ }
2123
+ }
2124
+
2041
2125
  const _acHealth = {
2042
2126
  // Per-project: { lastRestart: timestamp, consecutiveFailures: number }
2043
2127
  state: new Map(),
@@ -2074,6 +2158,13 @@ async function acHealthCheck() {
2074
2158
  // Healthy — reset failure counter
2075
2159
  if (health.consecutiveFailures > 0) {
2076
2160
  console.log(`[health] AC for ${project.id} recovered (port ${port} alive)`);
2161
+ // #572: restart agents that are running without chat integration.
2162
+ // These are agents where the #565 deferred restart timed out, or
2163
+ // agents spawned while AC was down. MCP flags are set at process
2164
+ // launch, so a full stop+respawn is required.
2165
+ restartUnregisteredAgents(project.id).catch((err) => {
2166
+ console.error(`[health] Failed to restart unregistered agents for ${project.id}:`, err.message);
2167
+ });
2077
2168
  }
2078
2169
  health.consecutiveFailures = 0;
2079
2170
  _acHealth.state.set(project.id, health);
package/server/routes.js CHANGED
@@ -623,6 +623,8 @@ const RESERVED_HISTORY_SENDERS = new Set([
623
623
  "dev",
624
624
  "re1",
625
625
  "re2",
626
+ // Legacy agent slugs — kept for backward compat so old config
627
+ // imports are still blocked. New projects use head/dev/re1/re2.
626
628
  "reviewer1",
627
629
  "reviewer2",
628
630
  "t1",
@@ -1148,7 +1150,17 @@ router.get("/api/uploads/:project/:filename", (req, res) => {
1148
1150
  }
1149
1151
  const filePath = path.join(CONFIG_DIR, project, "uploads", filename);
1150
1152
  if (!fs.existsSync(filePath)) return res.status(404).json({ error: "Not found" });
1151
- res.sendFile(filePath);
1153
+ // #560: pass error callback so Express/send NotFoundError (race between
1154
+ // existsSync and sendFile, or stricter file resolution in Express 5's
1155
+ // send module) is handled gracefully instead of spamming the server log.
1156
+ res.sendFile(filePath, (err) => {
1157
+ if (!err || res.headersSent) return;
1158
+ if (err.status === 404 || err.code === "ENOENT") {
1159
+ res.status(404).json({ error: "Not found" });
1160
+ } else {
1161
+ res.status(500).json({ error: "Internal server error" });
1162
+ }
1163
+ });
1152
1164
  });
1153
1165
 
1154
1166
  // ─── Projects (dashboard aggregation) ──────────────────────────────────────
@@ -1895,6 +1907,17 @@ router.get("/api/github/user", (_req, res) => {
1895
1907
  }
1896
1908
  });
1897
1909
 
1910
+ // GitHub orgs the authenticated user belongs to
1911
+ router.get("/api/github/orgs", (_req, res) => {
1912
+ try {
1913
+ const out = execFileSync("gh", ["api", "user/orgs", "--jq", "[.[].login]"], { encoding: "utf-8", timeout: 10000 });
1914
+ const orgs = JSON.parse(out);
1915
+ res.json(Array.isArray(orgs) ? orgs : []);
1916
+ } catch {
1917
+ res.json([]);
1918
+ }
1919
+ });
1920
+
1898
1921
  // GitHub repo list for an owner (only repos with push access)
1899
1922
  router.get("/api/github/repos", (req, res) => {
1900
1923
  const owner = req.query.owner;
@@ -1 +0,0 @@
1
- (globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,94810,e=>{"use strict";var t=e.i(47167),s=e.i(43476),a=e.i(71645),r=e.i(18566);let c=[{id:"name",label:"Project Name",subtitle:"Name your project",status:"active"},{id:"repo",label:"GitHub Repo",subtitle:"Connect a repository",status:"pending"},{id:"models",label:"Agent Models",subtitle:"Configure CLI backends",status:"pending"},{id:"workdir",label:"Working Directory",subtitle:"Set the local path",status:"pending"},{id:"workspaces",label:"Create Workspaces",subtitle:"Worktrees + seed files",status:"pending"},{id:"launch",label:"Ready to Launch",subtitle:"Review & start",status:"pending"}],n=[{value:"claude",label:"Claude Code"},{value:"codex",label:"Codex"}],o=[{key:"head",label:"T1 — Head",role:"Owner / Final Guard",desc:"Merges PRs, makes final calls"},{key:"re1",label:"RE1 — Reviewer 1",role:"Design Reviewer",desc:"Reviews architecture & design"},{key:"re2",label:"RE2 — Reviewer 2",role:"Code Reviewer",desc:"Reviews implementation quality"},{key:"dev",label:"T3 — Developer",role:"Full-Stack Builder",desc:"Implements features & fixes"}];function l({repo:e,workingDir:t,setWorkingDir:r,error:c,onNext:n}){let[o,i]=(0,a.useState)(!0),[x,d]=(0,a.useState)(null),[p,m]=(0,a.useState)(!1),u=e?e.split("/")[1]:"project";return(0,a.useEffect)(()=>{e?fetch(`/api/setup/detect-clone?repo=${encodeURIComponent(e)}`).then(e=>e.ok?e.json():null).then(e=>{d(e),e?.found&&e.path?r(e.path):e?.suggested&&r(e.suggested),i(!1)}).catch(()=>i(!1)):i(!1)},[e,r]),(0,s.jsxs)("div",{children:[(0,s.jsx)("h2",{className:"text-sm font-semibold text-text mb-1",children:"Where is your project?"}),(0,s.jsx)("p",{className:"text-[11px] text-text-muted mb-3",children:"Your project's git repository on your local machine. QuadWork will create 4 agent workspaces next to this directory."}),o&&(0,s.jsx)("p",{className:"text-[11px] text-text-muted mb-3",children:"Scanning for existing clone..."}),!o&&x?.found&&(0,s.jsxs)("div",{className:"border border-accent/30 bg-accent/5 p-3 mb-4 text-[11px]",children:[(0,s.jsx)("p",{className:"text-accent font-semibold mb-1",children:"Found existing clone"}),(0,s.jsx)("p",{className:"text-text font-mono",children:x.path}),(0,s.jsxs)("div",{className:"flex gap-2 mt-2",children:[(0,s.jsx)("button",{onClick:n,className:"px-3 py-1 bg-accent text-bg text-[11px] font-semibold hover:bg-accent-dim transition-colors",children:"Use this"}),(0,s.jsx)("button",{onClick:()=>{m(!0),r("")},className:"px-3 py-1 text-[11px] text-text-muted border border-border hover:text-text transition-colors",children:"Choose different path"})]})]}),!o&&!x?.found&&!p&&(0,s.jsxs)("div",{className:"border border-border bg-bg-surface p-3 mb-4 text-[11px]",children:[(0,s.jsxs)("p",{className:"text-text-muted mb-1",children:["No local clone found for ",(0,s.jsx)("span",{className:"text-accent",children:e})]}),(0,s.jsx)("p",{className:"text-text-muted mb-2",children:"Setup will clone it to:"}),(0,s.jsx)("p",{className:"text-text font-mono mb-2",children:x?.suggested||`~/Projects/${u}`}),(0,s.jsxs)("div",{className:"flex gap-2",children:[(0,s.jsx)("button",{onClick:n,disabled:!t.trim(),className:"px-3 py-1 bg-accent text-bg text-[11px] font-semibold hover:bg-accent-dim transition-colors disabled:opacity-50",children:"Clone here & continue"}),(0,s.jsx)("button",{onClick:()=>m(!0),className:"px-3 py-1 text-[11px] text-text-muted border border-border hover:text-text transition-colors",children:"Choose different path"})]})]}),(p||!o&&!x)&&(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)("input",{value:t,onChange:e=>r(e.target.value),placeholder:`~/Projects/${u}`,className:"w-full bg-transparent border border-border px-2 py-1.5 text-[12px] text-text outline-none focus:border-accent mb-2"}),(0,s.jsx)("button",{onClick:n,disabled:!t.trim(),className:"px-4 py-1.5 bg-accent text-bg text-[12px] font-semibold hover:bg-accent-dim transition-colors disabled:opacity-50",children:"Next"})]}),c&&(0,s.jsx)("p",{className:"text-[11px] text-error mt-2",children:c}),(0,s.jsxs)("div",{className:"border border-border bg-bg-surface p-3 mt-4 text-[11px] text-text-muted font-mono space-y-0.5",children:[(0,s.jsx)("p",{className:"text-[10px] uppercase tracking-wider text-text-muted mb-1 font-sans",children:"Workspace layout"}),(0,s.jsxs)("p",{className:"text-accent",children:[u,"/ ← your repo"]}),(0,s.jsxs)("p",{children:[u,"-head/ ← Head agent"]}),(0,s.jsxs)("p",{children:[u,"-dev/ ← Dev agent"]}),(0,s.jsxs)("p",{children:[u,"-re1/ ← RE1"]}),(0,s.jsxs)("p",{children:[u,"-re2/ ← RE2"]})]})]})}e.s(["default",0,function(){let e=(0,r.useRouter)(),[i,x]=(0,a.useState)(c),[d,p]=(0,a.useState)(0),[m,u]=(0,a.useState)(""),[h,b]=(0,a.useState)(""),[g,j]=(0,a.useState)(""),[f,N]=(0,a.useState)([]),[v,k]=(0,a.useState)(!1),[y,w]=(0,a.useState)(!1),[C,S]=(0,a.useState)(""),[_,R]=(0,a.useState)(!1),[T,H]=(0,a.useState)({head:"claude",re1:"claude",re2:"claude",dev:"claude"}),[P,$]=(0,a.useState)(!0),[q,E]=(0,a.useState)(!1),[I,A]=(0,a.useState)(""),[L,F]=(0,a.useState)("paste"),[D,U]=(0,a.useState)(""),[W,G]=(0,a.useState)("~/.quadwork/reviewer-token"),[M,O]=(0,a.useState)(""),[B,Y]=(0,a.useState)({}),[K,z]=(0,a.useState)(!1),[J,V]=(0,a.useState)([]),[X,Q]=(0,a.useState)("idle"),[Z,ee]=(0,a.useState)(!1),[et,es]=(0,a.useState)({chattr:0,mcpHttp:0,mcpSse:0}),[ea,er]=(0,a.useState)({chattr:"",mcpHttp:"",mcpSse:""}),ec=e=>{let t=parseInt(ea[e],10),s=Number.isFinite(t)&&t>0&&t<=65535?t:0;es(t=>({...t,[e]:s})),er(t=>({...t,[e]:s?String(s):""}))},[en,eo]=(0,a.useState)({chattr:0,mcpHttp:0,mcpSse:0}),[el,ei]=(0,a.useState)(null);(0,a.useEffect)(()=>{fetch("/api/cli-status").then(e=>e.json()).then(e=>{ei(e);let t=e.claude&&!e.codex?"claude":!e.claude&&e.codex?"codex":null;t?H({head:t,re1:t,re2:t,dev:t}):e.claude&&e.codex&&H({head:"codex",dev:"claude",re1:"codex",re2:"claude"})}).catch(()=>{})},[]),(0,a.useEffect)(()=>{fetch("/api/github/user").then(e=>e.json()).then(e=>{e.login&&S(e.login)}).catch(()=>{})},[]),(0,a.useEffect)(()=>{C&&(k(!0),fetch(`/api/github/repos?owner=${encodeURIComponent(C)}`).then(e=>e.json()).then(e=>{Array.isArray(e)&&N(e)}).catch(()=>{}).finally(()=>k(!1)))},[C]);let ex=(0,a.useCallback)((e,t)=>{x(s=>s.map((s,a)=>a===e?{...s,...t}:s))},[]),[ed,ep]=(0,a.useState)(0),em=(0,a.useCallback)(()=>{let e=d<ed?ed:d+1;x(t=>t.map((t,s)=>s===d?{...t,status:"done"}:s===e?{...t,status:"active"}:t)),p(e),ep(t=>Math.max(t,e))},[d,ed]),eu=(0,a.useCallback)(e=>{x(t=>t.map((t,s)=>s===e?{...t,status:"active"}:s===d?"done"===t.status?t:{...t,status:"pending"}:t)),p(e)},[d]),eh=async(e,t)=>{z(!0);try{let s=await fetch(`/api/setup?step=${e}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)}),a=await s.json();return z(!1),a}catch{return z(!1),{ok:!1,error:"Request failed"}}},eb=async()=>{let e=await eh("verify-repo",{repo:h});e.ok?em():ex(d,{status:"error",error:e.error})},eg=async()=>{if(z(!0),V([]),q&&"paste"===L&&D){V(e=>[...e,"Saving reviewer token..."]);try{let e=await fetch("/api/setup/save-token",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:D})}),t=await e.json();t.ok&&V(e=>[...e,`Token saved to ${t.path}`])}catch{}}V(e=>[...e,"Creating worktrees..."]);let e=await eh("create-worktrees",{workingDir:M,repo:h});if(!e.ok){V(t=>[...t,`Error: ${e.errors?.join(", ")||e.error}`]),ex(d,{status:"error",error:e.errors?.join(", ")||e.error}),z(!1);return}V(e=>[...e,"Worktrees created."]),V(e=>[...e,"Writing seed files..."]);let t=q?"file"===L?W:"~/.quadwork/reviewer-token":"",s=await eh("seed-files",{workingDir:M,projectName:m,repo:h,reviewerUser:q?I:"",reviewerTokenPath:t});if(!s.ok){V(e=>[...e,`Error: ${s.error}`]),ex(d,{status:"error",error:s.error}),z(!1);return}V(e=>[...e,"Seed files written."]),V(e=>[...e,"Done."]),z(!1),em()},ej=async()=>{let t,s,a;ec("chattr"),ec("mcpHttp"),ec("mcpSse");let r=e=>{let t=ea[e];if(""!==t){let e=parseInt(t,10);return Number.isFinite(e)&&e>0&&e<=65535?e:0}return et[e]},c={chattr:r("chattr"),mcpHttp:r("mcpHttp"),mcpSse:r("mcpSse")};if(Q("running"),Z&&c.chattr>0){t=c.chattr,s=c.mcpHttp||c.chattr-100,a=c.mcpSse||s+1;let e=[t,s,a];try{let t=(await Promise.all(e.map(e=>fetch(`/api/port-check?port=${e}`).then(e=>e.json())))).filter(e=>!e.free).map(e=>e.port);if(t.length>0){Q("error"),ex(d,{status:"error",error:`Port${t.length>1?"s":""} ${t.join(", ")} already in use`});return}}catch{}}else if(en.chattr)t=en.chattr,s=en.mcpHttp,a=en.mcpSse;else try{let e=await fetch("/api/port-check/auto?start=8300&count=1"),r=await e.json(),c=await fetch("/api/port-check/auto?start=8200&count=2"),n=await c.json();t=r.ports?.[0]||8300,s=n.ports?.[0]||8200,a=n.ports?.[1]||8201}catch{t=8300,s=8200,a=8201}let n=await eh("agentchattr-config",{workingDir:M,projectName:m,repo:h,backends:T,agentchattr_port:t,mcp_http_port:s,mcp_sse_port:a});n.ok&&Y({agentchattr_token:n.agentchattr_token,agentchattr_port:n.agentchattr_port,mcp_http_port:n.mcp_http_port,mcp_sse_port:n.mcp_sse_port});let o=M.split("/").pop()||m.toLowerCase().replace(/\s+/g,"-").replace(/[^a-z0-9-]/g,""),l=await eh("add-config",{id:o,name:m,repo:h,workingDir:M,backends:T,auto_approve:P,...n.ok?{agentchattr_token:n.agentchattr_token,agentchattr_port:n.agentchattr_port,mcp_http_port:n.mcp_http_port,mcp_sse_port:n.mcp_sse_port}:B});l.ok?(Q("done"),ex(d,{status:"done"}),setTimeout(()=>e.push(`/project/${o}`),1200)):(Q("error"),ex(d,{status:"error",error:l.error}))};(0,a.useEffect)(()=>{i[d]?.id==="launch"&&(async()=>{try{let e=await fetch("/api/port-check/auto?start=8300&count=1"),t=await e.json(),s=await fetch("/api/port-check/auto?start=8200&count=2"),a=await s.json(),r={chattr:t.ports?.[0]||8300,mcpHttp:a.ports?.[0]||8200,mcpSse:a.ports?.[1]||8201};eo(r),et.chattr||(es(r),er({chattr:String(r.chattr),mcpHttp:String(r.mcpHttp),mcpSse:String(r.mcpSse)}))}catch{}})()},[d,i]);let ef=f.filter(e=>e.name.toLowerCase().includes(g.toLowerCase())),eN=i[d];return(0,s.jsxs)("div",{className:"h-full overflow-y-auto",children:[(0,s.jsxs)("div",{className:"px-6 pt-6 pb-4 border-b border-border",children:[(0,s.jsx)("h1",{className:"text-lg font-semibold text-text tracking-tight",children:"Set Up Your AI Dev Team"}),(0,s.jsx)("p",{className:"text-[11px] text-text-muted mt-1",children:"Configure agents, connect your repo, and launch a multi-agent development workflow in minutes."})]}),(0,s.jsxs)("div",{className:"flex h-[calc(100%-80px)]",children:[(0,s.jsxs)("div",{className:"flex-1 flex gap-6 p-6 overflow-y-auto",children:[(0,s.jsx)("div",{className:"w-44 shrink-0",children:i.map((e,t)=>{let a="done"===e.status;return(0,s.jsxs)("div",{className:`flex items-start gap-2 py-2 ${a?"cursor-pointer group":""}`,onClick:a?()=>eu(t):void 0,role:a?"button":void 0,tabIndex:a?0:void 0,onKeyDown:a?e=>{("Enter"===e.key||" "===e.key)&&eu(t)}:void 0,children:[(0,s.jsx)("span",{className:`w-5 h-5 flex items-center justify-center text-[10px] border shrink-0 mt-0.5 ${"done"===e.status?"border-accent text-accent":"error"===e.status?"border-error text-error":"active"===e.status?"border-accent text-accent bg-accent/10":"skipped"===e.status?"border-border text-text-muted line-through":"border-border text-text-muted"}`,children:"done"===e.status?"✓":"error"===e.status?"!":t+1}),(0,s.jsxs)("div",{children:[(0,s.jsx)("span",{className:`text-[11px] block leading-tight ${"active"===e.status?"text-text font-semibold":"done"===e.status?"text-accent group-hover:text-text":"text-text-muted"}`,children:e.label}),(0,s.jsx)("span",{className:"text-[10px] text-text-muted block",children:e.subtitle})]})]},e.id)})}),(0,s.jsxs)("div",{className:"flex-1 border border-border p-5 min-h-0",children:[eN?.id==="name"&&(0,s.jsxs)("div",{children:[(0,s.jsx)("h2",{className:"text-sm font-semibold text-text mb-1",children:"Name your project"}),(0,s.jsx)("p",{className:"text-[11px] text-text-muted mb-4",children:"This name identifies your project in the dashboard and agent configs."}),(0,s.jsx)("input",{value:m,onChange:e=>u(e.target.value),placeholder:"e.g. My DeFi App",className:"w-full bg-transparent border border-border px-2 py-1.5 text-[12px] text-text outline-none focus:border-accent mb-4",autoFocus:!0}),(0,s.jsx)("button",{onClick:em,disabled:!m.trim(),className:"px-4 py-1.5 bg-accent text-bg text-[12px] font-semibold hover:bg-accent-dim transition-colors disabled:opacity-50",children:"Next"})]}),eN?.id==="repo"&&(0,s.jsxs)("div",{children:[(0,s.jsx)("h2",{className:"text-sm font-semibold text-text mb-1",children:"Connect a GitHub repository"}),(0,s.jsx)("p",{className:"text-[11px] text-text-muted mb-4",children:"Select an existing repo or enter one manually. Agents will work within this repo."}),!y&&(0,s.jsxs)(s.Fragment,{children:[C&&(0,s.jsxs)("p",{className:"text-[11px] text-text-muted mb-2",children:["Showing repos for ",(0,s.jsx)("span",{className:"text-accent",children:C})]}),(0,s.jsx)("input",{value:g,onChange:e=>j(e.target.value),placeholder:"Search repos...",className:"w-full bg-transparent border border-border px-2 py-1.5 text-[12px] text-text outline-none focus:border-accent mb-2"}),v&&(0,s.jsx)("p",{className:"text-[11px] text-text-muted mb-2",children:"Loading..."}),(0,s.jsxs)("div",{className:"max-h-40 overflow-y-auto border border-border mb-3",children:[ef.map(e=>(0,s.jsxs)("button",{onClick:()=>b(`${C}/${e.name}`),className:`w-full text-left px-3 py-1.5 text-[11px] border-b border-border/50 last:border-b-0 hover:bg-accent/5 transition-colors ${h===`${C}/${e.name}`?"bg-accent/10 text-accent":"text-text"}`,children:[(0,s.jsx)("span",{className:"font-semibold",children:e.name}),e.isPrivate&&(0,s.jsx)("span",{className:"text-[10px] text-text-muted ml-2",children:"private"}),e.description&&(0,s.jsx)("span",{className:"text-[10px] text-text-muted ml-2",children:e.description})]},e.name)),!v&&0===ef.length&&(0,s.jsx)("p",{className:"px-3 py-2 text-[11px] text-text-muted",children:"No repos found."})]}),(0,s.jsx)("button",{onClick:()=>w(!0),className:"text-[11px] text-text-muted hover:text-accent transition-colors mb-3 block",children:"Enter manually instead"})]}),y&&(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)("input",{value:h,onChange:e=>b(e.target.value),placeholder:"owner/repo",className:"w-full bg-transparent border border-border px-2 py-1.5 text-[12px] text-text outline-none focus:border-accent mb-2"}),(0,s.jsx)("button",{onClick:()=>w(!1),className:"text-[11px] text-text-muted hover:text-accent transition-colors mb-3 block",children:"Back to repo list"})]}),(0,s.jsxs)("label",{className:"flex items-center gap-2 mb-4 cursor-pointer",children:[(0,s.jsx)("input",{type:"checkbox",checked:_,onChange:e=>R(e.target.checked),className:"accent-accent"}),(0,s.jsxs)("span",{className:"text-[11px] text-text-muted",children:["Enable branch protection on ",(0,s.jsx)("code",{className:"text-accent",children:"main"})]})]}),_&&(0,s.jsxs)("div",{className:"border border-border bg-bg-surface p-3 mb-4 text-[11px] space-y-2",children:[(0,s.jsx)("p",{className:"text-text-muted",children:"Run this after setup, or configure in GitHub UI:"}),(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)("code",{className:"text-accent flex-1 select-all text-[10px] break-all",children:`gh api repos/${h||"owner/repo"}/branches/main/protection -X PUT -f "required_pull_request_reviews[required_approving_review_count]=1" -f "enforce_admins=false" -f "required_status_checks=null" -f "restrictions=null"`}),(0,s.jsx)("button",{onClick:()=>navigator.clipboard.writeText(`gh api repos/${h}/branches/main/protection -X PUT -f "required_pull_request_reviews[required_approving_review_count]=1" -f "enforce_admins=false" -f "required_status_checks=null" -f "restrictions=null"`),className:"text-[10px] text-text-muted hover:text-accent shrink-0",children:"copy"})]})]}),eN.error&&(0,s.jsx)("p",{className:"text-[11px] text-error mb-2",children:eN.error}),(0,s.jsx)("button",{onClick:eb,disabled:!h||K,className:"px-4 py-1.5 bg-accent text-bg text-[12px] font-semibold hover:bg-accent-dim transition-colors disabled:opacity-50",children:K?"Verifying...":"Verify & Continue"})]}),eN?.id==="models"&&(0,s.jsxs)("div",{children:[(0,s.jsx)("h2",{className:"text-sm font-semibold text-text mb-1",children:"Configure agent CLI backends"}),(0,s.jsx)("p",{className:"text-[11px] text-text-muted mb-4",children:"Each agent runs its own CLI instance. Pick the backend for each role."}),el&&!el.claude&&el.codex&&(0,s.jsxs)("div",{className:"border border-accent/20 bg-accent/5 p-3 mb-4 text-[11px]",children:[(0,s.jsx)("p",{className:"text-text",children:"You have Codex CLI installed — great! All 4 agents will use Codex."}),(0,s.jsx)("p",{className:"text-text-muted mt-1.5",children:"Tip: Installing Claude Code too gives your team different AI perspectives, which can improve code review quality. You can add it anytime:"}),(0,s.jsx)("p",{className:"text-accent mt-1 font-mono text-[10px]",children:"npm install -g @anthropic-ai/claude-code"}),(0,s.jsx)("p",{className:"text-text-muted mt-1.5",children:"For now, Codex CLI handles everything perfectly. Let's continue!"})]}),el&&el.claude&&!el.codex&&(0,s.jsxs)("div",{className:"border border-accent/20 bg-accent/5 p-3 mb-4 text-[11px]",children:[(0,s.jsx)("p",{className:"text-text",children:"You have Claude Code installed — great! All 4 agents will use Claude."}),(0,s.jsx)("p",{className:"text-text-muted mt-1.5",children:"Tip: Installing Codex CLI too gives your team different AI perspectives, which can improve code review quality. You can add it anytime:"}),(0,s.jsx)("p",{className:"text-accent mt-1 font-mono text-[10px]",children:"npm install -g codex"}),(0,s.jsx)("p",{className:"text-text-muted mt-1.5",children:"For now, Claude Code handles everything perfectly. Let's continue!"})]}),(0,s.jsx)("div",{className:"border border-border mb-4",children:o.map(e=>(0,s.jsxs)("div",{className:"flex items-center justify-between px-3 py-2 border-b border-border/50 last:border-b-0",children:[(0,s.jsxs)("div",{className:"flex-1 min-w-0",children:[(0,s.jsx)("span",{className:"text-[11px] text-text font-semibold block",children:e.label}),(0,s.jsx)("span",{className:"text-[10px] text-text-muted",children:e.desc})]}),(0,s.jsx)("select",{value:T[e.key],onChange:t=>H({...T,[e.key]:t.target.value}),className:"bg-transparent border border-border px-2 py-0.5 text-[11px] text-text outline-none focus:border-accent cursor-pointer ml-3",children:n.map(e=>(0,s.jsxs)("option",{value:e.value,className:"bg-bg-surface",disabled:!!el&&!el[e.value],children:[e.label,el&&!el[e.value]?" (not installed)":""]},e.value))})]},e.key))}),(0,s.jsxs)("label",{className:"flex items-center gap-2 mb-3 cursor-pointer",title:"Enable permission bypass flags so agents can work autonomously without prompting for approval on every action",children:[(0,s.jsx)("input",{type:"checkbox",checked:P,onChange:e=>$(e.target.checked),className:"accent-accent"}),(0,s.jsx)("span",{className:"text-[11px] text-text",children:"Auto-approve agent actions"}),(0,s.jsx)("span",{className:"text-[10px] text-text-muted",children:"(required for autonomous work)"})]}),(0,s.jsxs)("label",{className:"flex items-center gap-2 mb-3 cursor-pointer",children:[(0,s.jsx)("input",{type:"checkbox",checked:q,onChange:e=>E(e.target.checked),className:"accent-accent"}),(0,s.jsx)("span",{className:"text-[11px] text-text-muted",children:"Configure reviewer credentials (for GitHub PR reviews)"})]}),q&&(0,s.jsxs)("div",{className:"border border-border p-3 mb-4 space-y-3",children:[(0,s.jsxs)("div",{children:[(0,s.jsx)("label",{className:"text-[11px] text-text-muted block mb-1",children:"Reviewer GitHub username"}),(0,s.jsx)("input",{value:I,onChange:e=>A(e.target.value),placeholder:"github-username",className:"w-full bg-transparent border border-border px-2 py-1.5 text-[12px] text-text outline-none focus:border-accent"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)("label",{className:"text-[11px] text-text-muted block mb-2",children:"Token source"}),(0,s.jsxs)("div",{className:"flex gap-4 mb-2",children:[(0,s.jsxs)("label",{className:"flex items-center gap-1.5 cursor-pointer",children:[(0,s.jsx)("input",{type:"radio",name:"tokenMode",checked:"paste"===L,onChange:()=>F("paste"),className:"accent-accent"}),(0,s.jsx)("span",{className:"text-[11px] text-text",children:"Paste token"})]}),(0,s.jsxs)("label",{className:"flex items-center gap-1.5 cursor-pointer",children:[(0,s.jsx)("input",{type:"radio",name:"tokenMode",checked:"file"===L,onChange:()=>F("file"),className:"accent-accent"}),(0,s.jsx)("span",{className:"text-[11px] text-text",children:"Use existing file"})]})]}),"paste"===L?(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)("input",{value:D,onChange:e=>U(e.target.value),placeholder:"ghp_xxxxxxxxxxxxxxxxxxxx",type:"password",className:"w-full bg-transparent border border-border px-2 py-1.5 text-[12px] text-text outline-none focus:border-accent"}),(0,s.jsxs)("div",{className:"mt-2 text-[10px] text-text-muted leading-relaxed",children:[(0,s.jsxs)("p",{children:["Paste a GitHub ",(0,s.jsx)("span",{className:"text-text",children:"Personal Access Token (classic)"}),"."]}),(0,s.jsxs)("p",{className:"mt-1",children:["Create one at"," ",(0,s.jsx)("a",{href:"https://github.com/settings/tokens",target:"_blank",rel:"noopener noreferrer",className:"text-accent hover:underline",children:"github.com/settings/tokens"})," ","→ Generate new token (classic)"]}),(0,s.jsxs)("p",{className:"mt-1",children:["Required permission: ",(0,s.jsx)("span",{className:"text-accent",children:"repo"})," (Full control of private repositories)",(0,s.jsx)("br",{}),(0,s.jsx)("span",{className:"text-text-muted",children:"Needed for reading PRs, posting reviews, and approving/requesting changes"})]})]})]}):(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)("input",{value:W,onChange:e=>G(e.target.value),placeholder:"~/.quadwork/reviewer-token",className:"w-full bg-transparent border border-border px-2 py-1.5 text-[12px] text-text outline-none focus:border-accent"}),W&&!W.startsWith("~/.quadwork")&&!W.startsWith(String.raw`${t.default.env.HOME}/.quadwork`)&&(0,s.jsx)("p",{className:"text-[10px] text-[#ffcc00] mt-1",children:"This path may be inside a git repository. Consider using the default ~/.quadwork/ location to avoid accidentally committing tokens."})]})]})]}),(0,s.jsx)("button",{onClick:em,className:"px-4 py-1.5 bg-accent text-bg text-[12px] font-semibold hover:bg-accent-dim transition-colors",children:"Next"})]}),eN?.id==="workdir"&&(0,s.jsx)(l,{repo:h,workingDir:M,setWorkingDir:O,error:eN.error,onNext:em}),eN?.id==="workspaces"&&(0,s.jsxs)("div",{children:[(0,s.jsx)("h2",{className:"text-sm font-semibold text-text mb-1",children:"Create workspaces"}),(0,s.jsx)("p",{className:"text-[11px] text-text-muted mb-4",children:"This creates git worktrees for each agent and writes seed configuration files (AGENTS.md, CLAUDE.md) into each workspace."}),eN.error&&(0,s.jsx)("p",{className:"text-[11px] text-error mb-2",children:eN.error}),J.length>0&&(0,s.jsx)("div",{className:"border border-border bg-bg-surface p-3 mb-4 text-[11px] text-text-muted space-y-0.5 font-mono",children:J.map((e,t)=>(0,s.jsx)("p",{children:e},t))}),(0,s.jsx)("button",{onClick:eg,disabled:K,className:"px-4 py-1.5 bg-accent text-bg text-[12px] font-semibold hover:bg-accent-dim transition-colors disabled:opacity-50",children:K?"Creating...":"Create Worktrees & Seed Files"})]}),eN?.id==="launch"&&(0,s.jsxs)("div",{children:[(0,s.jsx)("h2",{className:"text-sm font-semibold text-text mb-1",children:"Ready to launch"}),(0,s.jsx)("p",{className:"text-[11px] text-text-muted mb-4",children:"Everything is configured. Review the summary and launch your AI dev team."}),(0,s.jsxs)("div",{className:"border border-border mb-4",children:[(0,s.jsx)("div",{className:"px-3 py-1.5 border-b border-border bg-bg-surface",children:(0,s.jsx)("span",{className:"text-[11px] text-text font-semibold",children:"Team Roster"})}),o.map(e=>(0,s.jsxs)("div",{className:"flex items-center justify-between px-3 py-1.5 border-b border-border/50 last:border-b-0",children:[(0,s.jsx)("span",{className:"text-[11px] text-text font-semibold",children:e.label}),(0,s.jsx)("span",{className:"text-[10px] text-text-muted",children:e.role}),(0,s.jsx)("span",{className:"text-[11px] text-accent",children:"claude"===T[e.key]?"Claude Code":"Codex"})]},e.key))]}),(0,s.jsxs)("div",{className:"mb-4",children:[(0,s.jsxs)("label",{className:"flex items-center gap-2 cursor-pointer mb-2",children:[(0,s.jsx)("input",{type:"checkbox",checked:Z,onChange:e=>ee(e.target.checked),className:"accent-accent"}),(0,s.jsx)("span",{className:"text-[11px] text-text-muted",children:"Custom ports"})]}),Z&&(0,s.jsx)("div",{className:"border border-border p-3 space-y-2",children:(0,s.jsxs)("div",{className:"grid grid-cols-3 gap-3",children:[(0,s.jsxs)("div",{className:"flex flex-col gap-1",children:[(0,s.jsx)("label",{className:"text-[10px] text-text-muted uppercase tracking-wider",children:"AgentChattr port"}),(0,s.jsx)("input",{type:"number",value:ea.chattr,onChange:e=>er({...ea,chattr:e.target.value}),onBlur:()=>ec("chattr"),placeholder:String(en.chattr||8300),className:"bg-transparent border border-border px-2 py-1 text-[11px] text-text outline-none focus:border-accent"}),en.chattr>0&&(0,s.jsxs)("span",{className:"text-[10px] text-text-muted",children:["auto-detected: ",en.chattr]})]}),(0,s.jsxs)("div",{className:"flex flex-col gap-1",children:[(0,s.jsx)("label",{className:"text-[10px] text-text-muted uppercase tracking-wider",children:"MCP HTTP port"}),(0,s.jsx)("input",{type:"number",value:ea.mcpHttp,onChange:e=>er({...ea,mcpHttp:e.target.value}),onBlur:()=>ec("mcpHttp"),placeholder:String(en.mcpHttp||8200),className:"bg-transparent border border-border px-2 py-1 text-[11px] text-text outline-none focus:border-accent"}),en.mcpHttp>0&&(0,s.jsxs)("span",{className:"text-[10px] text-text-muted",children:["auto-detected: ",en.mcpHttp]})]}),(0,s.jsxs)("div",{className:"flex flex-col gap-1",children:[(0,s.jsx)("label",{className:"text-[10px] text-text-muted uppercase tracking-wider",children:"MCP SSE port"}),(0,s.jsx)("input",{type:"number",value:ea.mcpSse,onChange:e=>er({...ea,mcpSse:e.target.value}),onBlur:()=>ec("mcpSse"),placeholder:String(en.mcpSse||8201),className:"bg-transparent border border-border px-2 py-1 text-[11px] text-text outline-none focus:border-accent"}),en.mcpSse>0&&(0,s.jsxs)("span",{className:"text-[10px] text-text-muted",children:["auto-detected: ",en.mcpSse]})]})]})})]}),eN.error&&(0,s.jsx)("p",{className:"text-[11px] text-error mb-2",children:eN.error}),"done"===X&&(0,s.jsx)("p",{className:"text-[11px] text-accent mb-2",children:"Project saved. Redirecting to dashboard..."}),(0,s.jsx)("button",{onClick:ej,disabled:"running"===X||"done"===X,className:"px-5 py-2 bg-accent text-bg text-[12px] font-semibold hover:bg-accent-dim transition-colors disabled:opacity-50",children:"running"===X?"Launching...":"done"===X?"Launched!":"Launch Project"})]}),d>=i.length&&(0,s.jsxs)("div",{className:"text-center py-8",children:[(0,s.jsx)("p",{className:"text-accent text-sm font-semibold",children:"Setup complete!"}),(0,s.jsx)("p",{className:"text-[11px] text-text-muted mt-2",children:"Redirecting to project dashboard..."})]})]})]}),(0,s.jsxs)("div",{className:"w-64 shrink-0 border-l border-border p-4 overflow-y-auto bg-bg-surface/50",children:[(0,s.jsx)("h3",{className:"text-[11px] font-semibold text-text-muted uppercase tracking-wider mb-3",children:"Configuration Preview"}),(0,s.jsxs)("div",{className:"space-y-3 text-[11px]",children:[(0,s.jsxs)("div",{children:[(0,s.jsx)("span",{className:"text-text-muted block mb-0.5",children:"Project"}),(0,s.jsx)("span",{className:"text-text",children:m||"—"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)("span",{className:"text-text-muted block mb-0.5",children:"Repository"}),(0,s.jsx)("span",{className:"text-text",children:h||"—"}),_&&(0,s.jsx)("span",{className:"text-[10px] text-accent block",children:"+ branch protection"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)("span",{className:"text-text-muted block mb-0.5",children:"Backends"}),Object.entries(T).map(([e,t])=>(0,s.jsxs)("div",{className:"flex justify-between",children:[(0,s.jsx)("span",{className:"text-text capitalize",children:e}),(0,s.jsx)("span",{className:"text-accent",children:t})]},e))]}),q&&I&&(0,s.jsxs)("div",{children:[(0,s.jsx)("span",{className:"text-text-muted block mb-0.5",children:"Reviewer"}),(0,s.jsxs)("span",{className:"text-text",children:["@",I]})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)("span",{className:"text-text-muted block mb-0.5",children:"Directory"}),(0,s.jsx)("span",{className:"text-text font-mono text-[10px]",children:M||"—"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)("span",{className:"text-text-muted block mb-0.5",children:"Status"}),(0,s.jsx)("div",{className:"space-y-0.5",children:i.map(e=>(0,s.jsxs)("div",{className:"flex items-center gap-1.5",children:[(0,s.jsx)("span",{className:`text-[10px] ${"done"===e.status?"text-accent":"error"===e.status?"text-error":"active"===e.status?"text-text":"text-text-muted"}`,children:"done"===e.status?"✓":"error"===e.status?"✗":"active"===e.status?"●":"○"}),(0,s.jsx)("span",{className:`text-[10px] ${"active"===e.status?"text-text":"text-text-muted"}`,children:e.label})]},e.id))})]})]})]})]})]})}])}]);