@sideboard-ai/core 0.1.124 → 0.1.127

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 (39) hide show
  1. package/dist/{agents-DPR2TDNF.js → agents-7BUQY2WX.js} +6 -6
  2. package/dist/{agents-DCXXLTXF.js → agents-X2I3RNCY.js} +6 -6
  3. package/dist/{app-settings-YFWV7MOG.js → app-settings-6RPALX4J.js} +7 -1
  4. package/dist/{app-settings-IKFWZDUK.js → app-settings-E7NEQY7D.js} +7 -1
  5. package/dist/{chunk-SDCPQL27.js → chunk-DLM5LSQW.js} +484 -93
  6. package/dist/{chunk-RG737OWT.js → chunk-G6X6UFJO.js} +2 -0
  7. package/dist/{chunk-HPAWHIZ3.js → chunk-HLJUNJBF.js} +2 -0
  8. package/dist/{chunk-OHEFHIG3.js → chunk-HRSBZIXS.js} +14 -6
  9. package/dist/{chunk-KWGP6RZM.js → chunk-HULESWLI.js} +4 -4
  10. package/dist/{chunk-FXQPY2KU.js → chunk-HUYEU4GS.js} +26 -4
  11. package/dist/{chunk-2X7I6MDW.js → chunk-HZPO3SSZ.js} +11 -8
  12. package/dist/{chunk-O43IRQ2Y.js → chunk-MNL4FSKY.js} +4 -4
  13. package/dist/{chunk-7Y3AYQWT.js → chunk-NV73AO7Q.js} +4 -4
  14. package/dist/{chunk-TRTWH6C2.js → chunk-RSFCB23A.js} +11 -8
  15. package/dist/{chunk-RBVVWBVB.js → chunk-SYLTXDRF.js} +2 -2
  16. package/dist/{chunk-MZDAEI3Y.js → chunk-T7KJQJAX.js} +496 -109
  17. package/dist/{chunk-UAVZ2JSO.js → chunk-UDQI3N47.js} +14 -6
  18. package/dist/{chunk-HZLE6LJJ.js → chunk-VE22NWBD.js} +2 -2
  19. package/dist/{chunk-JNAIKICO.js → chunk-ZMUOPTKZ.js} +4 -4
  20. package/dist/{chunk-BMIRX64H.js → chunk-ZND6XV6J.js} +26 -4
  21. package/dist/{coordinator-prompt-AZ3WRDNC.js → coordinator-prompt-MEEUOEF2.js} +4 -4
  22. package/dist/{coordinator-prompt-LEU62W25.js → coordinator-prompt-SPJSGAQS.js} +4 -4
  23. package/dist/{global-workspace-U5UOVXKQ.js → global-workspace-BMWP7YZC.js} +5 -5
  24. package/dist/{global-workspace-4YNYDMLQ.js → global-workspace-KNESLWKG.js} +5 -5
  25. package/dist/index.cjs +1208 -236
  26. package/dist/index.d.cts +303 -103
  27. package/dist/index.d.ts +303 -103
  28. package/dist/index.js +671 -129
  29. package/dist/mcp/run-stdio.cjs +1054 -194
  30. package/dist/mcp/run-stdio.js +554 -114
  31. package/dist/{orchestrator-VJQ5EWZZ.js → orchestrator-3JLPPTCF.js} +8 -8
  32. package/dist/{orchestrator-JS3EHI4E.js → orchestrator-6GUCE4LY.js} +8 -8
  33. package/dist/{thread-store-JR235AWK.js → thread-store-5MLYY35S.js} +1 -1
  34. package/dist/{thread-store-PMH3BMB6.js → thread-store-F6BCARQG.js} +1 -1
  35. package/dist/{workspaces-ICK433ZV.js → workspaces-HSYRYJYV.js} +6 -6
  36. package/dist/{workspaces-POWKTH2D.js → workspaces-O4QZOASB.js} +6 -6
  37. package/dist/{worktree-CMGBTVN4.js → worktree-ERHC7Q4F.js} +5 -3
  38. package/dist/{worktree-T57RD7BQ.js → worktree-JVGSLSLJ.js} +5 -3
  39. package/package.json +1 -1
@@ -29,13 +29,13 @@ import {
29
29
  summarizeTurnStderr,
30
30
  toolDescription,
31
31
  turnFailChatText
32
- } from "./chunk-JNAIKICO.js";
32
+ } from "./chunk-ZMUOPTKZ.js";
33
33
  import {
34
34
  addWorkspace,
35
35
  ensureWorkspace,
36
36
  removeWorkspace,
37
37
  syncWorkspacesFromThreads
38
- } from "./chunk-RBVVWBVB.js";
38
+ } from "./chunk-SYLTXDRF.js";
39
39
  import {
40
40
  extractPresentedPlan,
41
41
  readPlanFile,
@@ -56,18 +56,19 @@ import {
56
56
  isOrchestratorThread,
57
57
  isSlackCoordinatorThread,
58
58
  orchestratorSessionPoisonedByBuiltins
59
- } from "./chunk-O43IRQ2Y.js";
59
+ } from "./chunk-MNL4FSKY.js";
60
60
  import {
61
61
  SLACK_REPLY_FORMATTING,
62
62
  coordinatorSystemPrompt,
63
63
  coordinatorTurnReminder,
64
64
  enrichWorkspacesWithGithub,
65
65
  ensureGlobalCoordinatorCwd
66
- } from "./chunk-TRTWH6C2.js";
66
+ } from "./chunk-RSFCB23A.js";
67
67
  import {
68
68
  addPrStackLayer,
69
69
  allocateTeamName,
70
70
  allocateTeamSlug,
71
+ canonicalizeRepoPath,
71
72
  commitAll,
72
73
  createExistingBranchWorktree,
73
74
  createOrUpdatePr,
@@ -105,8 +106,9 @@ import {
105
106
  takenSlugsFromThread,
106
107
  threadDisplayLabel,
107
108
  withRepoGitLock,
109
+ worktreeDisplayLabelForGroup,
108
110
  worktreeNameFromPath
109
- } from "./chunk-OHEFHIG3.js";
111
+ } from "./chunk-HRSBZIXS.js";
110
112
  import {
111
113
  ATTACHMENTS_DIR,
112
114
  LEGACY_ATTACHMENTS_DIR,
@@ -123,7 +125,7 @@ import {
123
125
  updateThread,
124
126
  withThreadLock,
125
127
  writeThread
126
- } from "./chunk-RG737OWT.js";
128
+ } from "./chunk-G6X6UFJO.js";
127
129
  import {
128
130
  clearStaleIndexLocks,
129
131
  enrichPathWithNpmGlobalBin,
@@ -145,7 +147,7 @@ import {
145
147
  stripNestedElectronEnv,
146
148
  updateAdvancedSettings,
147
149
  writeSecureJson
148
- } from "./chunk-BMIRX64H.js";
150
+ } from "./chunk-ZND6XV6J.js";
149
151
  import {
150
152
  writePrivateFile
151
153
  } from "./chunk-NSTQ6QKD.js";
@@ -303,26 +305,9 @@ function storePath3() {
303
305
  function watchId(teamId, channelId, ts) {
304
306
  return `${teamId}:${channelId}:${ts}`;
305
307
  }
306
- function badgeId(teamId, userId) {
307
- return `${teamId}:${userId}`;
308
- }
309
308
  function slackArchiveUrl(channelId, ts) {
310
309
  return `https://slack.com/archives/${channelId}/p${ts.replace(".", "")}`;
311
310
  }
312
- function initialsFromName(name) {
313
- const parts = name.trim().split(/\s+/).filter(Boolean);
314
- if (parts.length === 0) return "?";
315
- if (parts.length === 1) {
316
- const w = parts[0];
317
- return (w.slice(0, 2) || "?").toUpperCase();
318
- }
319
- return `${parts[0][0] ?? ""}${parts[parts.length - 1][0] ?? ""}`.toUpperCase();
320
- }
321
- function hueFromId(id) {
322
- let h = 0;
323
- for (const c of id) h = h * 31 + c.charCodeAt(0) >>> 0;
324
- return h % 360;
325
- }
326
311
  function tsNewer(a, b) {
327
312
  return Number(a) > Number(b);
328
313
  }
@@ -395,7 +380,7 @@ async function continueSourceThread(threadId, prompt) {
395
380
  await continueOnReply(threadId, prompt);
396
381
  return;
397
382
  }
398
- const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-VJQ5EWZZ.js");
383
+ const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-3JLPPTCF.js");
399
384
  await getOrchestrator2().send(threadId, prompt);
400
385
  } catch {
401
386
  }
@@ -479,7 +464,6 @@ function recordSlackOutboundWatch(input) {
479
464
  sourceThreadId: input.sourceThreadId?.trim() || void 0,
480
465
  postedAt: (/* @__PURE__ */ new Date()).toISOString(),
481
466
  lastSeenTs: ts,
482
- unread: false,
483
467
  permalink: slackArchiveUrl(channelId, ts),
484
468
  injectedReplyTs: [],
485
469
  replies: []
@@ -566,36 +550,9 @@ async function fetchMessages(token, watch, fetchImpl) {
566
550
  }
567
551
  return out;
568
552
  }
569
- function listSlackReplyBadges() {
570
- const unread = readStore3().filter((w) => w.unread && w.replyUserId && w.permalink);
571
- const byUser = /* @__PURE__ */ new Map();
572
- for (const w of unread) {
573
- const id = badgeId(w.teamId, w.replyUserId);
574
- const prev = byUser.get(id);
575
- if (!prev || tsNewer(w.replyTs || "", prev.replyTs || "")) {
576
- byUser.set(id, w);
577
- }
578
- }
579
- return [...byUser.entries()].map(([id, w]) => {
580
- const userName = w.replyUserName || w.toLabel || "Slack";
581
- return {
582
- id,
583
- userId: w.replyUserId,
584
- userName,
585
- initials: initialsFromName(userName),
586
- hue: hueFromId(w.replyUserId),
587
- permalink: w.permalink || slackArchiveUrl(w.channelId, w.replyTs || w.ts),
588
- label: w.toLabel,
589
- preview: w.replyPreview,
590
- repliedAt: w.replyTs || w.postedAt
591
- };
592
- }).sort((a, b) => b.repliedAt.localeCompare(a.repliedAt));
593
- }
594
- async function refreshSlackReplyBadges(opts) {
553
+ async function pollSlackOutboundWatches(opts) {
595
554
  const now = opts?.now ?? Date.now();
596
- if (!opts?.force && now - lastPollMs < POLL_INTERVAL_MS) {
597
- return listSlackReplyBadges();
598
- }
555
+ if (!opts?.force && now - lastPollMs < POLL_INTERVAL_MS) return;
599
556
  lastPollMs = now;
600
557
  const existing = readStore3();
601
558
  let watches = pruneWatches(existing, now);
@@ -616,9 +573,7 @@ async function refreshSlackReplyBadges(opts) {
616
573
  const injected = new Set(watch.injectedReplyTs ?? []);
617
574
  const collected = [...watch.replies ?? []];
618
575
  let lastSeenTs = watch.lastSeenTs;
619
- let latestUser;
620
576
  let latestName;
621
- let latestText = "";
622
577
  let latestPermalink = watch.permalink;
623
578
  let newlyInjected = 0;
624
579
  for (const msg of replies) {
@@ -644,9 +599,7 @@ async function refreshSlackReplyBadges(opts) {
644
599
  text: msg.text ?? ""
645
600
  };
646
601
  if (!collected.some((r) => r.ts === ts)) collected.push(reply);
647
- latestUser = user;
648
602
  latestName = replyUserName;
649
- latestText = reply.text;
650
603
  latestPermalink = permalink;
651
604
  if (injected.has(ts)) {
652
605
  lastSeenTs = ts;
@@ -677,11 +630,6 @@ async function refreshSlackReplyBadges(opts) {
677
630
  watches[i] = {
678
631
  ...watch,
679
632
  lastSeenTs,
680
- unread: true,
681
- replyUserId: latestUser,
682
- replyUserName: latestName,
683
- replyTs: lastSeenTs,
684
- replyPreview: latestText.slice(0, 140),
685
633
  permalink: latestPermalink,
686
634
  injectedReplyTs: [...injected],
687
635
  replies: collected.slice(-MAX_REPLIES_PER_WATCH)
@@ -689,7 +637,6 @@ async function refreshSlackReplyBadges(opts) {
689
637
  changed = true;
690
638
  }
691
639
  if (changed) writeStore(watches);
692
- return listSlackReplyBadges();
693
640
  }
694
641
 
695
642
  // src/orchestrator/orchestrator.ts
@@ -774,9 +721,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
774
721
  `Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
775
722
  );
776
723
  }
777
- const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-4YNYDMLQ.js");
724
+ const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-KNESLWKG.js");
778
725
  if (isGlobalThread2(thread)) {
779
- const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-LEU62W25.js");
726
+ const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-SPJSGAQS.js");
780
727
  ensureGlobalCoordinatorCwd2(
781
728
  isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
782
729
  );
@@ -1342,6 +1289,7 @@ function worktreeBindingFrom(from) {
1342
1289
  prUrl: from.prUrl,
1343
1290
  prTitle: from.prTitle,
1344
1291
  prState: from.prState,
1292
+ prIsDraft: from.prIsDraft,
1345
1293
  skipAutoArchiveOnMerge: from.skipAutoArchiveOnMerge,
1346
1294
  stackId: from.stackId,
1347
1295
  stackLayer: from.stackLayer,
@@ -2317,8 +2265,418 @@ async function requireAgent(agent, opts) {
2317
2265
  return status;
2318
2266
  }
2319
2267
 
2268
+ // src/board/home-board.ts
2269
+ var GLOBAL_WORKSPACE_ID = "__global__";
2270
+ function isHomeBoardThread(thread) {
2271
+ if (thread.sourceType === "orchestration") return false;
2272
+ if (thread.repoPath === GLOBAL_WORKSPACE_ID) return false;
2273
+ return true;
2274
+ }
2275
+ var BOARD_COLUMN_DEFS = [
2276
+ { id: "new", title: "New" },
2277
+ { id: "draft", title: "Draft" },
2278
+ { id: "review", title: "Review" },
2279
+ { id: "done", title: "Merged" }
2280
+ ];
2281
+ var HOME_BOARD_CACHE_TTL_MS = 15 * 60 * 1e3;
2282
+ function isOpenPrState(prUrl, prState) {
2283
+ if (!prUrl?.trim()) return false;
2284
+ const state = (prState ?? "OPEN").trim().toUpperCase();
2285
+ return state !== "MERGED" && state !== "CLOSED";
2286
+ }
2287
+ function isMergedPrState(prUrl, prState) {
2288
+ if (!prUrl?.trim()) return false;
2289
+ return (prState ?? "").trim().toUpperCase() === "MERGED";
2290
+ }
2291
+ function groupHomeBoardWorktrees(threads) {
2292
+ const map = /* @__PURE__ */ new Map();
2293
+ for (const t of threads) {
2294
+ const key = normalizeWorktreePath(t.worktreePath);
2295
+ const list = map.get(key) ?? [];
2296
+ list.push(t);
2297
+ map.set(key, list);
2298
+ }
2299
+ return [...map.values()].map((list) => list.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))).sort((a, b) => (b[0]?.updatedAt ?? "").localeCompare(a[0]?.updatedAt ?? ""));
2300
+ }
2301
+ function classifyWorktreeColumn(group) {
2302
+ if (group.some((t) => isMergedPrState(t.prUrl, t.prState))) return "done";
2303
+ if (group.some((t) => isOpenPrState(t.prUrl, t.prState) && !t.prIsDraft)) {
2304
+ return "review";
2305
+ }
2306
+ if (group.some((t) => isOpenPrState(t.prUrl, t.prState) && t.prIsDraft)) {
2307
+ return "draft";
2308
+ }
2309
+ return "new";
2310
+ }
2311
+ function worktreeBoardStatus(group) {
2312
+ const order = ["running", "queued", "error", "broken"];
2313
+ for (const status of order) {
2314
+ if (group.some((t) => t.status === status)) return status;
2315
+ }
2316
+ return group[0]?.status ?? "idle";
2317
+ }
2318
+ function normalizeIssueKey(value) {
2319
+ return value.trim().toLowerCase().replace(/^#/, "");
2320
+ }
2321
+ function threadMatchesIssue(thread, issue) {
2322
+ const identRaw = issue.identifier.trim().toLowerCase();
2323
+ const identKey = normalizeIssueKey(issue.identifier);
2324
+ const title = issue.title.trim().toLowerCase();
2325
+ const refRaw = (thread.sourceRef ?? "").trim().toLowerCase();
2326
+ const refKey = normalizeIssueKey(thread.sourceRef ?? "");
2327
+ const threadTitle = (thread.title ?? "").trim().toLowerCase();
2328
+ if (thread.sourceType === "ticket" && identKey && refKey === identKey) return true;
2329
+ if (identRaw && (refRaw === identRaw || threadTitle.includes(identRaw))) return true;
2330
+ if (title && threadTitle === title) return true;
2331
+ return false;
2332
+ }
2333
+ function dedupeBoardIssues(issues) {
2334
+ const seen = /* @__PURE__ */ new Set();
2335
+ const out = [];
2336
+ for (const issue of issues) {
2337
+ const key = [
2338
+ issue.repoPath,
2339
+ issue.provider ?? "",
2340
+ issue.id || issue.identifier
2341
+ ].join(":");
2342
+ if (seen.has(key)) continue;
2343
+ seen.add(key);
2344
+ out.push(issue);
2345
+ }
2346
+ return out;
2347
+ }
2348
+ function issueNeedsWorkspacePick(provider, workspaceCount) {
2349
+ if (workspaceCount <= 1) return false;
2350
+ return provider !== "github";
2351
+ }
2352
+ function boardPrKey(pr) {
2353
+ return pr.url?.trim() || `${pr.repoPath}::${pr.number}`;
2354
+ }
2355
+ function urlsMatch(a, b) {
2356
+ const norm = (u) => u.trim().replace(/\/+$/, "").toLowerCase();
2357
+ return Boolean(a && b && norm(a) === norm(b));
2358
+ }
2359
+ function threadMatchesPr(thread, pr) {
2360
+ const num = String(pr.number);
2361
+ if (thread.sourceType === "pr" && normalizeIssueKey(thread.sourceRef ?? "") === num) {
2362
+ return true;
2363
+ }
2364
+ if (thread.prUrl && pr.url && urlsMatch(thread.prUrl, pr.url)) return true;
2365
+ const head = pr.headRefName.trim().toLowerCase();
2366
+ if (!head) return false;
2367
+ const branch = (thread.branchName ?? "").trim().toLowerCase();
2368
+ const ref = (thread.sourceRef ?? "").trim().replace(/^refs\/heads\//, "").replace(/^origin\//, "").toLowerCase();
2369
+ if (branch === head) return true;
2370
+ if (!ref || /^(main|master|develop|development|trunk|default|head)$/.test(ref)) {
2371
+ return false;
2372
+ }
2373
+ return ref === head;
2374
+ }
2375
+ function normalizeBranchRef(value) {
2376
+ return value.trim().replace(/^refs\/heads\//, "").replace(/^origin\//, "").toLowerCase();
2377
+ }
2378
+ function threadMatchesBranch(thread, pin) {
2379
+ if (pin.repoPath && thread.repoPath && pin.repoPath !== thread.repoPath) return false;
2380
+ const head = normalizeBranchRef(pin.ref === "default" ? "" : pin.ref);
2381
+ if (!head) {
2382
+ return thread.sourceType === "branch" && pin.repoPath === thread.repoPath;
2383
+ }
2384
+ const branch = normalizeBranchRef(thread.branchName ?? "");
2385
+ const ref = normalizeBranchRef(thread.sourceRef ?? "");
2386
+ return branch === head || ref === head;
2387
+ }
2388
+ function sameRepoPath(a, b) {
2389
+ return a.replace(/\/+$/, "") === b.replace(/\/+$/, "");
2390
+ }
2391
+ var DEFAULTISH_BRANCH = /^(main|master|develop|development|trunk|default|head)$/;
2392
+ function isDefaultBranchCreateRef(ref) {
2393
+ const head = normalizeBranchRef(ref);
2394
+ return !head || DEFAULTISH_BRANCH.test(head);
2395
+ }
2396
+ function findLiveThreadForCreate(input, threads) {
2397
+ const live = threads.filter(
2398
+ (t) => t.status !== "archived" && sameRepoPath(t.repoPath, input.repoPath)
2399
+ );
2400
+ if (input.cowboy) {
2401
+ return live.find((t) => t.cowboy);
2402
+ }
2403
+ if (input.sourceType === "ticket") {
2404
+ return live.find(
2405
+ (t) => threadMatchesIssue(t, {
2406
+ identifier: input.sourceRef,
2407
+ title: input.title ?? ""
2408
+ })
2409
+ );
2410
+ }
2411
+ if (input.sourceType === "pr") {
2412
+ const number = Number(normalizeIssueKey(input.sourceRef));
2413
+ return live.find(
2414
+ (t) => threadMatchesPr(t, {
2415
+ number: Number.isFinite(number) ? number : -1,
2416
+ title: input.title ?? "",
2417
+ url: "",
2418
+ headRefName: ""
2419
+ })
2420
+ );
2421
+ }
2422
+ if (input.sourceType === "branch") {
2423
+ if (isDefaultBranchCreateRef(input.sourceRef)) return void 0;
2424
+ return live.find(
2425
+ (t) => threadMatchesBranch(t, {
2426
+ ref: input.sourceRef,
2427
+ repoPath: input.repoPath
2428
+ })
2429
+ );
2430
+ }
2431
+ return void 0;
2432
+ }
2433
+ function findBoardPin(pins, kind, ref, repoPath = "") {
2434
+ const key = normalizeIssueKey(ref);
2435
+ const matches = pins.filter(
2436
+ (pin) => pin.kind === kind && normalizeIssueKey(pin.ref) === key
2437
+ );
2438
+ if (repoPath) {
2439
+ const scoped = matches.filter((pin) => pin.repoPath === repoPath);
2440
+ if (scoped[0]) return scoped[0];
2441
+ }
2442
+ return matches[0];
2443
+ }
2444
+ function syncBoardPins(pins, issues, prs) {
2445
+ return pins.map((pin) => {
2446
+ if (pin.kind === "ticket") {
2447
+ const issue = findBoardIssue(issues, pin.ref, pin.repoPath);
2448
+ if (!issue) {
2449
+ return { ...pin, remoteState: pin.remoteState || "stale" };
2450
+ }
2451
+ return {
2452
+ ...pin,
2453
+ title: issue.title || pin.title,
2454
+ url: issue.url || pin.url,
2455
+ labels: issue.labels,
2456
+ provider: issue.provider ?? pin.provider,
2457
+ assignee: issue.assignee ?? pin.assignee,
2458
+ cycle: issue.cycle?.name ?? pin.cycle,
2459
+ teamKey: issue.teamKey ?? pin.teamKey,
2460
+ remoteState: "open",
2461
+ needsWorkspacePick: issue.needsWorkspacePick
2462
+ };
2463
+ }
2464
+ if (pin.kind === "pr") {
2465
+ const pr = findBoardPr(prs, pin.ref, pin.repoPath);
2466
+ if (!pr) {
2467
+ return { ...pin, remoteState: pin.remoteState === "open" ? "stale" : pin.remoteState || "stale" };
2468
+ }
2469
+ return {
2470
+ ...pin,
2471
+ title: pr.title || pin.title,
2472
+ url: pr.url || pin.url,
2473
+ headRefName: pr.headRefName || pin.headRefName,
2474
+ author: prAuthorLogin(pr) || pin.author,
2475
+ remoteState: "open"
2476
+ };
2477
+ }
2478
+ return pin;
2479
+ });
2480
+ }
2481
+ function dedupeBoardPrs(prs) {
2482
+ const seen = /* @__PURE__ */ new Set();
2483
+ const out = [];
2484
+ for (const pr of prs) {
2485
+ const key = boardPrKey(pr);
2486
+ if (seen.has(key)) continue;
2487
+ seen.add(key);
2488
+ out.push(pr);
2489
+ }
2490
+ return out.sort((a, b) => b.number - a.number);
2491
+ }
2492
+ var BOARD_PAGE_SIZE = 40;
2493
+ function tokenizeQuery(query) {
2494
+ return query.trim().toLowerCase().split(/\s+/).filter(Boolean);
2495
+ }
2496
+ function haystackMatches(haystack, tokens) {
2497
+ if (tokens.length === 0) return true;
2498
+ const h = haystack.toLowerCase();
2499
+ return tokens.every((token) => h.includes(token));
2500
+ }
2501
+ function prAuthorLogin(pr) {
2502
+ return pr.author?.login?.trim() ?? "";
2503
+ }
2504
+ function threadSearchText(thread, workspaceName = "") {
2505
+ return [
2506
+ thread.title,
2507
+ thread.sourceRef,
2508
+ thread.sourceType,
2509
+ thread.agent,
2510
+ thread.status,
2511
+ thread.branchName,
2512
+ thread.prUrl ?? "",
2513
+ workspaceName,
2514
+ thread.repoPath
2515
+ ].join(" ");
2516
+ }
2517
+ function inWorkspace(repoPath, filterRepoPath) {
2518
+ if (!filterRepoPath) return true;
2519
+ return repoPath === filterRepoPath;
2520
+ }
2521
+ function visiblePage(items, shown) {
2522
+ const n = Math.max(0, shown);
2523
+ return {
2524
+ visible: items.slice(0, n),
2525
+ hidden: Math.max(0, items.length - n)
2526
+ };
2527
+ }
2528
+ function emptyColumns() {
2529
+ return {
2530
+ backlog: [],
2531
+ queued: [],
2532
+ running: [],
2533
+ new: [],
2534
+ draft: [],
2535
+ review: [],
2536
+ done: []
2537
+ };
2538
+ }
2539
+ function emptyHidden() {
2540
+ return {
2541
+ backlog: 0,
2542
+ queued: 0,
2543
+ running: 0,
2544
+ new: 0,
2545
+ draft: 0,
2546
+ review: 0,
2547
+ done: 0
2548
+ };
2549
+ }
2550
+ function findBoardIssue(issues, ref, repoPath = "") {
2551
+ const key = normalizeIssueKey(ref);
2552
+ if (!key) return void 0;
2553
+ const matches = issues.filter((issue) => normalizeIssueKey(issue.identifier) === key);
2554
+ if (repoPath) {
2555
+ const scoped = matches.filter((issue) => issue.repoPath === repoPath);
2556
+ if (scoped[0]) return scoped[0];
2557
+ }
2558
+ return matches[0];
2559
+ }
2560
+ function findBoardPr(prs, ref, repoPath = "") {
2561
+ const num = Number(normalizeIssueKey(ref));
2562
+ if (!Number.isFinite(num)) return void 0;
2563
+ const matches = prs.filter((pr) => pr.number === num);
2564
+ if (repoPath) {
2565
+ const scoped = matches.filter((pr) => pr.repoPath === repoPath);
2566
+ if (scoped[0]) return scoped[0];
2567
+ }
2568
+ return matches[0];
2569
+ }
2570
+ function toThreadCard(group) {
2571
+ const thread = group[0];
2572
+ const withPr = group.find((t) => t.prUrl?.trim()) ?? thread;
2573
+ return {
2574
+ kind: "thread",
2575
+ id: thread.id,
2576
+ title: worktreeDisplayLabelForGroup(group),
2577
+ status: worktreeBoardStatus(group),
2578
+ agent: thread.agent,
2579
+ sourceType: thread.sourceType,
2580
+ sourceRef: thread.sourceRef,
2581
+ repoPath: thread.repoPath,
2582
+ prUrl: withPr.prUrl,
2583
+ link: `sideboard://thread/${thread.id}`,
2584
+ ...group.length > 1 ? { chatCount: group.length } : {}
2585
+ };
2586
+ }
2587
+ function threadMatchesKind(thread, kind) {
2588
+ if (kind === "all" || kind === "threads") return true;
2589
+ if (kind === "tickets") return thread.sourceType === "ticket";
2590
+ if (kind === "prs") return thread.sourceType === "pr";
2591
+ if (kind === "branches") return thread.sourceType === "branch" || thread.sourceType === "adopt";
2592
+ return true;
2593
+ }
2594
+ function assembleHomeBoard(input) {
2595
+ const tokens = tokenizeQuery(input.query ?? "");
2596
+ const repo = input.repoPath?.trim() ?? "";
2597
+ const kind = input.kind ?? "all";
2598
+ const limit = Math.max(1, input.limit ?? BOARD_PAGE_SIZE);
2599
+ const wsName = input.workspaceName ?? (() => "");
2600
+ const byUpdated = (a, b) => b.updatedAt.localeCompare(a.updatedAt);
2601
+ const live = input.threads.filter((t) => t.status !== "archived" && isHomeBoardThread(t)).sort(byUpdated);
2602
+ const columns = emptyColumns();
2603
+ const hidden = emptyHidden();
2604
+ const totals = {
2605
+ backlog: 0,
2606
+ queued: 0,
2607
+ running: 0,
2608
+ new: 0,
2609
+ draft: 0,
2610
+ review: 0,
2611
+ done: 0,
2612
+ tickets: 0,
2613
+ prs: 0,
2614
+ branches: 0,
2615
+ threads: 0
2616
+ };
2617
+ const byCol = {
2618
+ queued: [],
2619
+ running: [],
2620
+ new: [],
2621
+ draft: [],
2622
+ review: [],
2623
+ done: []
2624
+ };
2625
+ for (const group of groupHomeBoardWorktrees(live)) {
2626
+ if (!group.some((t) => threadMatchesKind(t, kind))) continue;
2627
+ const col = classifyWorktreeColumn(group);
2628
+ if (col === "backlog") continue;
2629
+ const primary = group[0];
2630
+ if (!inWorkspace(primary.repoPath, repo)) continue;
2631
+ const hay = group.map((t) => threadSearchText(t, wsName(t.repoPath))).join(" ");
2632
+ if (!haystackMatches(hay, tokens)) continue;
2633
+ byCol[col].push(group);
2634
+ }
2635
+ const flat = [...Object.values(byCol)].flatMap((groups) => groups.map((g) => g[0]));
2636
+ totals.tickets = flat.filter((t) => t.sourceType === "ticket").length;
2637
+ totals.prs = flat.filter((t) => t.sourceType === "pr").length;
2638
+ totals.branches = flat.filter((t) => t.sourceType === "branch" || t.sourceType === "adopt").length;
2639
+ totals.threads = Object.values(byCol).reduce((n, list) => n + list.length, 0);
2640
+ for (const col of Object.keys(byCol)) {
2641
+ const cards = byCol[col].map(toThreadCard);
2642
+ totals[col] = cards.length;
2643
+ const page = visiblePage(cards, limit);
2644
+ columns[col] = page.visible;
2645
+ hidden[col] = page.hidden;
2646
+ }
2647
+ if (input.column) {
2648
+ for (const col of Object.keys(columns)) {
2649
+ if (col === input.column) continue;
2650
+ columns[col] = [];
2651
+ hidden[col] = 0;
2652
+ }
2653
+ }
2654
+ return { columns, hidden, totals };
2655
+ }
2656
+ var HOME_BOARD_AGENT_HINT = "Home is a Kanban of worktrees (one card per checkout; sibling chat tabs nest as inner cards). create_thread reuses a live worktree for the same ticket, PR, or named branch \u2014 do not recreate it. Columns are the path to merge: New (no PR) \u2192 Draft (draft PR) \u2192 Review (open PR) \u2192 Merged. Archive removes the card to Settings \u2192 History. Queued/running are activity on the card, not columns. Orchestration chats stay in the sidebar. Do not invent status.";
2657
+
2320
2658
  // src/threads/create.ts
2321
2659
  async function createThread(input, _onSetupLine) {
2660
+ const repoPath = await resolveRepoRoot(input.repoPath);
2661
+ if (!existsSync7(repoPath)) {
2662
+ throw new Error(`Repo not found: ${repoPath}`);
2663
+ }
2664
+ if (input.reuseExisting !== false) {
2665
+ const existing = findLiveThreadForCreate(
2666
+ {
2667
+ sourceType: input.sourceType,
2668
+ sourceRef: input.sourceRef,
2669
+ repoPath,
2670
+ title: input.title,
2671
+ cowboy: input.cowboy
2672
+ },
2673
+ listThreads({ includeArchived: false }).map((t) => ({
2674
+ ...t,
2675
+ repoPath: canonicalizeRepoPath(t.repoPath)
2676
+ }))
2677
+ );
2678
+ if (existing) return readThread(existing.id) ?? existing;
2679
+ }
2322
2680
  const resolved = resolveNewThreadOptions({
2323
2681
  agent: input.agent,
2324
2682
  model: input.model,
@@ -2326,12 +2684,8 @@ async function createThread(input, _onSetupLine) {
2326
2684
  fast: input.fast
2327
2685
  });
2328
2686
  await requireAgent(resolved.agent);
2329
- const repoPath = await resolveRepoRoot(input.repoPath);
2330
- if (!existsSync7(repoPath)) {
2331
- throw new Error(`Repo not found: ${repoPath}`);
2332
- }
2333
2687
  if (input.cowboy) {
2334
- const { cowboyModeEnabled } = await import("./app-settings-IKFWZDUK.js");
2688
+ const { cowboyModeEnabled } = await import("./app-settings-E7NEQY7D.js");
2335
2689
  if (!cowboyModeEnabled()) {
2336
2690
  throw new Error(
2337
2691
  "Cowboy mode is off. Enable it in Settings \u2192 Advanced."
@@ -2442,7 +2796,7 @@ async function createThread(input, _onSetupLine) {
2442
2796
  return readThread(thread.id) ?? thread;
2443
2797
  }
2444
2798
  async function listLinearIssues(agent, repoPath) {
2445
- const { getAdapter: getAdapter2 } = await import("./agents-DPR2TDNF.js");
2799
+ const { getAdapter: getAdapter2 } = await import("./agents-7BUQY2WX.js");
2446
2800
  await requireAgent(agent, { requireLinear: true });
2447
2801
  const adapter = getAdapter2(agent);
2448
2802
  if (!adapter.listLinearIssues) {
@@ -2628,7 +2982,8 @@ async function forkThreadWorktree(input, onSetupLine) {
2628
2982
  planMode: from.planMode,
2629
2983
  title: input.title?.trim() || void 0,
2630
2984
  parentThreadId: from.id,
2631
- attachments: [attachment]
2985
+ attachments: [attachment],
2986
+ reuseExisting: false
2632
2987
  },
2633
2988
  onSetupLine
2634
2989
  );
@@ -2708,7 +3063,7 @@ function buildQuotaHandoffAttachment(from, limitText, fallbackAgent) {
2708
3063
  "",
2709
3064
  `## Instructions`,
2710
3065
  `- Continue fleet orchestration from this handoff.`,
2711
- `- Prefer Sideboard MCP (list_threads, get_thread, send_to_thread, \u2026) for live status.`,
3066
+ `- Prefer Sideboard MCP (list_board, list_threads, get_thread, send_to_thread, \u2026) for live status.`,
2712
3067
  `- Leave model Auto unless there is a specific reason to pin one.`,
2713
3068
  `- Do not wait on the limited ${from.agent} account; keep going on ${fallbackAgent}.`
2714
3069
  ].join("\n");
@@ -2721,9 +3076,9 @@ function buildQuotaHandoffAttachment(from, limitText, fallbackAgent) {
2721
3076
  }
2722
3077
  var QUOTA_CONTINUE_PROMPT = (fromAgent, fallback) => [
2723
3078
  `${fromAgent} hit a session/usage limit. Continue this orchestration on ${fallback} using the attached handoff.`,
2724
- "Call list_threads for live fleet status, then proceed with the goal. Leave model Auto unless needed."
3079
+ "Call list_board or list_threads for live fleet status, then proceed with the goal. Leave model Auto unless needed."
2725
3080
  ].join(" ");
2726
- var QUOTA_RESUME_PROMPT = "Session/usage limit window should have reset. Continue the orchestration from where you left off. Use list_threads for fleet status.";
3081
+ var QUOTA_RESUME_PROMPT = "Session/usage limit window should have reset. Continue the orchestration from where you left off. Use list_board or list_threads for fleet status.";
2727
3082
  function createQuotaFailoverChat(from, fallbackAgent, limitText) {
2728
3083
  const handoff = buildQuotaHandoffAttachment(from, limitText, fallbackAgent);
2729
3084
  const tab = createChatTab({
@@ -2838,7 +3193,7 @@ async function adoptThread(input) {
2838
3193
  messages: input.messages ?? []
2839
3194
  });
2840
3195
  writeThread(thread);
2841
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-ICK433ZV.js");
3196
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-HSYRYJYV.js");
2842
3197
  await ensureWorkspace2(repoPath);
2843
3198
  return thread;
2844
3199
  }
@@ -4985,7 +5340,7 @@ function formatScheduledPrompt(name, prompt) {
4985
5340
  ${prompt}`;
4986
5341
  }
4987
5342
  async function defaultDeps() {
4988
- const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-VJQ5EWZZ.js");
5343
+ const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-3JLPPTCF.js");
4989
5344
  const orch = getOrchestrator2();
4990
5345
  return {
4991
5346
  findThread: (id) => findThreadByRef(id),
@@ -5139,7 +5494,7 @@ var Orchestrator = class {
5139
5494
  maxConcurrent;
5140
5495
  runningCount = 0;
5141
5496
  constructor(opts) {
5142
- this.maxConcurrent = opts?.maxConcurrent ?? 3;
5497
+ this.maxConcurrent = opts?.maxConcurrent ?? 5;
5143
5498
  }
5144
5499
  on(listener) {
5145
5500
  this.events.on("event", listener);
@@ -5205,7 +5560,7 @@ var Orchestrator = class {
5205
5560
  orphans: orphans.map((o) => ({ path: o.path, repoPath: o.repoPath }))
5206
5561
  });
5207
5562
  }
5208
- const { autoCleanupOrphansEnabled } = await import("./app-settings-IKFWZDUK.js");
5563
+ const { autoCleanupOrphansEnabled } = await import("./app-settings-E7NEQY7D.js");
5209
5564
  if (autoCleanupOrphansEnabled() && shouldRunWorktreeCleanup() && orphans.length > 0) {
5210
5565
  await cleanupOrphanWorktrees({ repoPaths });
5211
5566
  }
@@ -5356,7 +5711,23 @@ var Orchestrator = class {
5356
5711
  return findThreadByRef(idOrRef) ?? readThread(idOrRef);
5357
5712
  }
5358
5713
  async createThread(input) {
5714
+ const prior = new Set(
5715
+ listThreads({ includeArchived: false }).map((t) => t.id)
5716
+ );
5359
5717
  const thread = await createThread(input);
5718
+ if (prior.has(thread.id)) {
5719
+ if (input.prompt?.trim()) {
5720
+ try {
5721
+ await this.send(thread.id, input.prompt.trim());
5722
+ } catch (err) {
5723
+ const message = err instanceof Error ? err.message : String(err);
5724
+ updateThread(thread.id, {
5725
+ lastError: `First prompt failed: ${message}`
5726
+ });
5727
+ }
5728
+ }
5729
+ return thread;
5730
+ }
5360
5731
  this.emit({ type: "status_changed", threadId: thread.id, status: thread.status });
5361
5732
  void this.finishCreateThread(thread.id, input.prompt?.trim() || void 0);
5362
5733
  return thread;
@@ -5377,7 +5748,7 @@ var Orchestrator = class {
5377
5748
  }
5378
5749
  await setup;
5379
5750
  if (skipSetup) return;
5380
- const { autoRunAfterSetupEnabled } = await import("./app-settings-IKFWZDUK.js");
5751
+ const { autoRunAfterSetupEnabled } = await import("./app-settings-E7NEQY7D.js");
5381
5752
  if (autoRunAfterSetupEnabled()) {
5382
5753
  try {
5383
5754
  await this.startDev(threadId);
@@ -5662,7 +6033,7 @@ var Orchestrator = class {
5662
6033
  }
5663
6034
  const isBrightsy = fresh.agent === "brightsy";
5664
6035
  const isOrchestration = isOrchestratorThread(fresh);
5665
- const { autoRenameBranchEnabled, getGithubGitAuthMode } = await import("./app-settings-IKFWZDUK.js");
6036
+ const { autoRenameBranchEnabled, getGithubGitAuthMode } = await import("./app-settings-E7NEQY7D.js");
5666
6037
  const gitAuthMode = getGithubGitAuthMode();
5667
6038
  const worktreeDirective = isBrightsy || isOrchestration ? null : formatWorktreeDirective(fresh, {
5668
6039
  githubSlug: await resolveGithubRepoSlug(fresh.worktreePath).catch(
@@ -6199,7 +6570,8 @@ var Orchestrator = class {
6199
6570
  agent,
6200
6571
  repoPath: opts.repoPath,
6201
6572
  title: opts.title ? `${opts.title} (${agent})` : `best-of-n: ${opts.prompt.slice(0, 48)} (${agent})`,
6202
- prompt: opts.prompt
6573
+ prompt: opts.prompt,
6574
+ reuseExisting: false
6203
6575
  });
6204
6576
  created.push(thread);
6205
6577
  }
@@ -6436,6 +6808,8 @@ var Orchestrator = class {
6436
6808
  if (meta.url && meta.url !== thread.prUrl) patch.prUrl = meta.url;
6437
6809
  if (meta.title && meta.title !== thread.prTitle) patch.prTitle = meta.title;
6438
6810
  if (nextState && nextState !== prevState) patch.prState = nextState;
6811
+ const nextDraft = Boolean(meta.isDraft) && nextState !== "MERGED" && nextState !== "CLOSED";
6812
+ if (nextDraft !== Boolean(thread.prIsDraft)) patch.prIsDraft = nextDraft;
6439
6813
  if (thread.skipAutoArchiveOnMerge && nextState && nextState !== "MERGED" && nextState !== "CLOSED") {
6440
6814
  patch.skipAutoArchiveOnMerge = false;
6441
6815
  }
@@ -6446,7 +6820,7 @@ var Orchestrator = class {
6446
6820
  updateThread(thread.id, { title: meta.title });
6447
6821
  }
6448
6822
  }
6449
- const { autoArchiveOnMergeEnabled } = await import("./app-settings-IKFWZDUK.js");
6823
+ const { autoArchiveOnMergeEnabled } = await import("./app-settings-E7NEQY7D.js");
6450
6824
  const latest = this.requireThread(thread.id);
6451
6825
  if (!shouldAutoArchiveOnPrMerge({
6452
6826
  previousPrState: prevState || null,
@@ -6460,7 +6834,7 @@ var Orchestrator = class {
6460
6834
  }
6461
6835
  const siblings = threadsSharingWorktree(latest.worktreePath);
6462
6836
  for (const t of siblings) {
6463
- const sibPatch = { prState: "MERGED" };
6837
+ const sibPatch = { prState: "MERGED", prIsDraft: false };
6464
6838
  if (meta.url && meta.url !== t.prUrl) sibPatch.prUrl = meta.url;
6465
6839
  if (meta.title && meta.title !== t.prTitle) sibPatch.prTitle = meta.title;
6466
6840
  if (Object.keys(sibPatch).length > 0) updateThread(t.id, sibPatch);
@@ -6634,10 +7008,15 @@ var Orchestrator = class {
6634
7008
  web: action === "create-web"
6635
7009
  });
6636
7010
  if (!url) return;
6637
- const patch = { prUrl: url, prTitle: meta.title };
7011
+ const patch = {
7012
+ prUrl: url,
7013
+ prTitle: meta.title,
7014
+ prIsDraft: action === "create-draft"
7015
+ };
6638
7016
  try {
6639
7017
  const fetched = await getPrMeta(cwd, url);
6640
7018
  if (fetched?.title) patch.prTitle = fetched.title;
7019
+ if (fetched) patch.prIsDraft = Boolean(fetched.isDraft);
6641
7020
  } catch {
6642
7021
  }
6643
7022
  updateThread(thread.id, patch);
@@ -6751,7 +7130,7 @@ var Orchestrator = class {
6751
7130
  this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
6752
7131
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
6753
7132
  try {
6754
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-ICK433ZV.js");
7133
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-HSYRYJYV.js");
6755
7134
  await ensureWorkspace2(thread.repoPath);
6756
7135
  } catch {
6757
7136
  }
@@ -6778,7 +7157,7 @@ var Orchestrator = class {
6778
7157
  await runArchiveScript(thread.repoPath, thread.worktreePath);
6779
7158
  } catch {
6780
7159
  }
6781
- const { deleteBranchOnPurgeEnabled } = await import("./app-settings-IKFWZDUK.js");
7160
+ const { deleteBranchOnPurgeEnabled } = await import("./app-settings-E7NEQY7D.js");
6782
7161
  const deleteBranch = opts?.deleteBranch ?? deleteBranchOnPurgeEnabled();
6783
7162
  await removeWorktree(thread.repoPath, thread.worktreePath, {
6784
7163
  deleteBranch: deleteBranch ? thread.branchName : void 0
@@ -6805,7 +7184,7 @@ var Orchestrator = class {
6805
7184
  `Cowboy checkout missing: ${thread.worktreePath}. Re-add the project folder, then restore.`
6806
7185
  );
6807
7186
  }
6808
- const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-CMGBTVN4.js");
7187
+ const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-ERHC7Q4F.js");
6809
7188
  const slug = thread.worktreePath.split("/").pop();
6810
7189
  const dest = thread.worktreePath;
6811
7190
  await withRepoGitLock(thread.repoPath, async () => {
@@ -6913,7 +7292,7 @@ async function startOrchestration(opts) {
6913
7292
  sourceRef: "default",
6914
7293
  ...createOpts
6915
7294
  }).catch(async () => {
6916
- const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-CMGBTVN4.js");
7295
+ const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-ERHC7Q4F.js");
6917
7296
  const repo = await resolveRepoRoot2(repoPath);
6918
7297
  const def = await resolveDefaultBranch2(repo);
6919
7298
  return orch.createThread({
@@ -6923,7 +7302,7 @@ async function startOrchestration(opts) {
6923
7302
  repoPath: repo
6924
7303
  });
6925
7304
  });
6926
- const { updateThread: upd } = await import("./thread-store-PMH3BMB6.js");
7305
+ const { updateThread: upd } = await import("./thread-store-F6BCARQG.js");
6927
7306
  const updated = upd(thread.id, {
6928
7307
  sourceType: "orchestration",
6929
7308
  sourceRef: goal
@@ -6941,8 +7320,20 @@ export {
6941
7320
  requireSlackWorkspace,
6942
7321
  listSlackOutboundWatches,
6943
7322
  recordSlackOutboundWatch,
6944
- refreshSlackReplyBadges,
7323
+ pollSlackOutboundWatches,
6945
7324
  AGENT_GIT_ACTIONS,
7325
+ BOARD_COLUMN_DEFS,
7326
+ HOME_BOARD_CACHE_TTL_MS,
7327
+ dedupeBoardIssues,
7328
+ issueNeedsWorkspacePick,
7329
+ findLiveThreadForCreate,
7330
+ findBoardPin,
7331
+ syncBoardPins,
7332
+ dedupeBoardPrs,
7333
+ findBoardIssue,
7334
+ findBoardPr,
7335
+ assembleHomeBoard,
7336
+ HOME_BOARD_AGENT_HINT,
6946
7337
  listLinearIssues,
6947
7338
  readTurnLive,
6948
7339
  resolveScheduleThreadId,