@sideboard-ai/core 0.1.125 → 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-GKY2GR2J.js → chunk-DLM5LSQW.js} +481 -35
  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-ED27HCPV.js → chunk-T7KJQJAX.js} +493 -35
  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 +1203 -157
  26. package/dist/index.d.cts +233 -10
  27. package/dist/index.d.ts +233 -10
  28. package/dist/index.js +668 -120
  29. package/dist/mcp/run-stdio.cjs +1051 -136
  30. package/dist/mcp/run-stdio.js +552 -112
  31. package/dist/{orchestrator-FZYM7I42.js → orchestrator-3JLPPTCF.js} +8 -8
  32. package/dist/{orchestrator-HN3LYHFN.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
package/dist/index.js CHANGED
@@ -1,9 +1,12 @@
1
1
  import {
2
2
  AGENT_GIT_ACTIONS,
3
+ BOARD_COLUMN_DEFS,
3
4
  CONTEXT_COMPACT_CHARS,
4
5
  CONTEXT_KEEP_RECENT_CHARS,
5
6
  CONTEXT_KEEP_RECENT_MESSAGES,
6
7
  CONTEXT_MIN_MESSAGES,
8
+ HOME_BOARD_AGENT_HINT,
9
+ HOME_BOARD_CACHE_TTL_MS,
7
10
  LEGACY_REVIEW_REQUEST_PATH,
8
11
  Orchestrator,
9
12
  REPO_REVIEW_NAME,
@@ -25,9 +28,11 @@ import {
25
28
  applyPromptCacheTtlEnv,
26
29
  applyThreadIntoMain,
27
30
  armSchedules,
31
+ assembleHomeBoard,
28
32
  attachmentFromAbsolutePath,
29
33
  attachmentsFromBuffers,
30
34
  attachmentsFromWorktreePaths,
35
+ boardPinIdentity,
31
36
  buildForkTranscriptAttachment,
32
37
  buildReviewRequestAttachment,
33
38
  buildSessionSeed,
@@ -35,6 +40,7 @@ import {
35
40
  captureLoginEnv,
36
41
  captureTurnBaseline,
37
42
  claimDesktopHost,
43
+ classifyWorktreeColumn,
38
44
  cleanupOrphanWorktrees,
39
45
  cloneRepoIntoSideboard,
40
46
  computeNextRunAt,
@@ -46,6 +52,8 @@ import {
46
52
  createPrStack,
47
53
  createSchedule,
48
54
  createThread,
55
+ dedupeBoardIssues,
56
+ dedupeBoardPrs,
49
57
  defaultScheduleName,
50
58
  deleteSchedule,
51
59
  desktopHostPidPath,
@@ -58,6 +66,10 @@ import {
58
66
  estimateThreadChars,
59
67
  expandComposerPrompt,
60
68
  extractiveSummary,
69
+ findBoardIssue,
70
+ findBoardPin,
71
+ findBoardPr,
72
+ findLiveThreadForCreate,
61
73
  findOrphanWorktrees,
62
74
  findThreadForStackLayer,
63
75
  fireSchedule,
@@ -87,6 +99,7 @@ import {
87
99
  getSchedule,
88
100
  getSlackReplyTarget,
89
101
  getSlackWorkspace,
102
+ groupHomeBoardWorktrees,
90
103
  hasEnabledSchedules,
91
104
  importConductorWorkspace,
92
105
  importConductorWorkspaceAsync,
@@ -95,11 +108,13 @@ import {
95
108
  inspectGitWorktree,
96
109
  isCowboyThread,
97
110
  isDesktopHostAlive,
111
+ isHomeBoardThread,
98
112
  isImageFilePath,
99
113
  isPidAlive,
100
114
  isPrimaryCheckoutThread,
101
115
  isSlackExternalReplyPrompt,
102
116
  isThisProcessDesktopHost,
117
+ issueNeedsWorkspacePick,
103
118
  lastRequestOccupancy,
104
119
  listBranchCommits,
105
120
  listConductorWorkspaces,
@@ -158,6 +173,7 @@ import {
158
173
  startDevServer,
159
174
  startOrchestration,
160
175
  summarizeConversation,
176
+ syncBoardPins,
161
177
  takenTeamSlugsForChatTab,
162
178
  thisProcessShouldDrainAgentQueues,
163
179
  threadsSharingWorktree,
@@ -165,10 +181,11 @@ import {
165
181
  upsertSlackWorkspace,
166
182
  waitForPidExit,
167
183
  withAgentInstructions,
184
+ worktreeBoardStatus,
168
185
  worktreeCleanupSettings,
169
186
  wrapReviewSkillMarkdown,
170
187
  writeWorktreeFile
171
- } from "./chunk-ED27HCPV.js";
188
+ } from "./chunk-T7KJQJAX.js";
172
189
  import {
173
190
  BRIGHTSY_MCP_ALLOWED_TOOLS,
174
191
  CLAUDE_MODEL_CATALOG,
@@ -247,7 +264,7 @@ import {
247
264
  withEventParentId,
248
265
  withEventsParentId,
249
266
  writeInjectedMcpConfig
250
- } from "./chunk-7Y3AYQWT.js";
267
+ } from "./chunk-NV73AO7Q.js";
251
268
  import {
252
269
  brightsyConfigPath,
253
270
  brightsyMcpServerName,
@@ -266,7 +283,7 @@ import {
266
283
  listWorkspaces,
267
284
  removeWorkspace,
268
285
  syncWorkspacesFromThreads
269
- } from "./chunk-HZLE6LJJ.js";
286
+ } from "./chunk-VE22NWBD.js";
270
287
  import {
271
288
  CLOUD_COORDINATOR_BUSY_REPLY,
272
289
  CLOUD_COORDINATOR_STOPPED_REPLY,
@@ -294,7 +311,7 @@ import {
294
311
  parseForceStopMessage,
295
312
  slackCoordinatorSourceRef,
296
313
  takenTeamSlugsForOrchestration
297
- } from "./chunk-KWGP6RZM.js";
314
+ } from "./chunk-HULESWLI.js";
298
315
  import {
299
316
  COORDINATOR_TOOL_PLAYBOOK,
300
317
  SLACK_REPLY_FORMATTING,
@@ -303,7 +320,7 @@ import {
303
320
  enrichWorkspacesWithGithub,
304
321
  ensureGlobalCoordinatorCwd,
305
322
  formatWorkspaceInventory
306
- } from "./chunk-2X7I6MDW.js";
323
+ } from "./chunk-HZPO3SSZ.js";
307
324
  import {
308
325
  LEGACY_PLAN_FILE_REL,
309
326
  PLAN_FILE_NAME,
@@ -340,6 +357,7 @@ import {
340
357
  appendIndexedGitConfig,
341
358
  applyGithubGitAuthEnv,
342
359
  branchDisplayLabel,
360
+ canonicalizeRepoPath,
343
361
  checkoutPrStackLayer,
344
362
  codexSandboxWritableRootsArgs,
345
363
  codexUnattendedGitConfigArgs,
@@ -412,7 +430,7 @@ import {
412
430
  worktreeDisplayLabel,
413
431
  worktreeDisplayLabelForGroup,
414
432
  worktreeNameFromPath
415
- } from "./chunk-UAVZ2JSO.js";
433
+ } from "./chunk-UDQI3N47.js";
416
434
  import {
417
435
  ATTACHMENTS_DIR,
418
436
  LEGACY_ATTACHMENTS_DIR,
@@ -422,6 +440,7 @@ import {
422
440
  import {
423
441
  GITHUB_GIT_AUTH_MODES,
424
442
  HARNESS_ENV_KEYS,
443
+ ISSUE_SOURCE_LABELS,
425
444
  appSettingsPath,
426
445
  applyAppEnvironment,
427
446
  autoArchiveOnMergeEnabled,
@@ -451,7 +470,9 @@ import {
451
470
  getIssueSource,
452
471
  getLinearApiKey,
453
472
  harnessEnvKey,
473
+ isIssueSourceConnected,
454
474
  isLinearConnected,
475
+ issueSourceLabel,
455
476
  loadAppSettings,
456
477
  maxConcurrentAgents,
457
478
  orchestrationQuotaFallbackAgent,
@@ -481,7 +502,7 @@ import {
481
502
  updateDefaultsSettings,
482
503
  updateIntegrationsSettings,
483
504
  updateOpencodeSettings
484
- } from "./chunk-FXQPY2KU.js";
505
+ } from "./chunk-HUYEU4GS.js";
485
506
  import {
486
507
  stripNestedElectronEnv
487
508
  } from "./chunk-4TR3HZFT.js";
@@ -500,7 +521,7 @@ import {
500
521
  updateThread,
501
522
  withThreadLock,
502
523
  writeThread
503
- } from "./chunk-HPAWHIZ3.js";
524
+ } from "./chunk-HLJUNJBF.js";
504
525
  import {
505
526
  THINKING_EFFORTS,
506
527
  isThinkingEffort,
@@ -909,10 +930,13 @@ var ISSUE_FIELDS = `
909
930
  assignee { id name }
910
931
  team { id key name states { nodes { id name type } } }
911
932
  labels { nodes { name } }
933
+ cycle { id name number startsAt endsAt completedAt }
912
934
  `;
913
935
  var ASSIGNED_ISSUES_QUERY = `
914
936
  query SideboardAssignedIssues($first: Int!) {
915
937
  viewer {
938
+ id
939
+ name
916
940
  assignedIssues(
917
941
  first: $first
918
942
  orderBy: updatedAt
@@ -1015,6 +1039,25 @@ function mapState(node) {
1015
1039
  type: String(node.type ?? "")
1016
1040
  };
1017
1041
  }
1042
+ function linearCycleIsActive(cycle, now = Date.now()) {
1043
+ if (!cycle) return false;
1044
+ if (cycle.completedAt) return false;
1045
+ const start = cycle.startsAt ? Date.parse(cycle.startsAt) : Number.NaN;
1046
+ const end = cycle.endsAt ? Date.parse(cycle.endsAt) : Number.NaN;
1047
+ if (Number.isFinite(start) && now < start) return false;
1048
+ if (Number.isFinite(end) && now > end) return false;
1049
+ return true;
1050
+ }
1051
+ function mapCycle(node) {
1052
+ if (!node?.name && node?.number == null) return null;
1053
+ const name = String(node.name ?? (node.number != null ? `Cycle ${node.number}` : "")).trim();
1054
+ if (!name) return null;
1055
+ return {
1056
+ name,
1057
+ number: typeof node.number === "number" ? node.number : void 0,
1058
+ isActive: linearCycleIsActive(node)
1059
+ };
1060
+ }
1018
1061
  function mapIssue(node) {
1019
1062
  const team = node.team;
1020
1063
  return {
@@ -1032,7 +1075,8 @@ function mapIssue(node) {
1032
1075
  name: String(team.name ?? ""),
1033
1076
  states: (team.states?.nodes ?? []).map((s) => mapState(s)).filter((s) => Boolean(s))
1034
1077
  } : void 0,
1035
- labels: (node.labels?.nodes ?? []).map((l) => l.name).filter((n) => Boolean(n))
1078
+ labels: (node.labels?.nodes ?? []).map((l) => l.name).filter((n) => Boolean(n)),
1079
+ cycle: mapCycle(node.cycle)
1036
1080
  };
1037
1081
  }
1038
1082
  function toIssueInfo(issue) {
@@ -1042,7 +1086,11 @@ function toIssueInfo(issue) {
1042
1086
  title: issue.title,
1043
1087
  url: issue.url,
1044
1088
  labels: issue.labels,
1045
- provider: "linear"
1089
+ provider: "linear",
1090
+ assignee: issue.assignee?.name,
1091
+ assignees: issue.assignee?.name ? [issue.assignee.name] : void 0,
1092
+ cycle: issue.cycle ?? null,
1093
+ teamKey: issue.team?.key || void 0
1046
1094
  };
1047
1095
  }
1048
1096
  function mapTeam(node) {
@@ -1084,10 +1132,21 @@ function normalizePriority(priority) {
1084
1132
  }
1085
1133
  return priority;
1086
1134
  }
1087
- async function listLinearIssuesDirect(opts) {
1088
- const first = Math.max(1, Math.min(100, opts?.limit ?? 50));
1135
+ async function listLinearAssignedIssues(opts) {
1136
+ const first = Math.max(1, Math.min(250, opts?.limit ?? 200));
1089
1137
  const json = await linearGraphql(ASSIGNED_ISSUES_QUERY, { first }, opts);
1090
- return (json.viewer?.assignedIssues?.nodes ?? []).map((node) => toIssueInfo(mapIssue(node)));
1138
+ return {
1139
+ viewer: {
1140
+ id: String(json.viewer?.id ?? ""),
1141
+ name: String(json.viewer?.name ?? "")
1142
+ },
1143
+ issues: (json.viewer?.assignedIssues?.nodes ?? []).map(
1144
+ (node) => toIssueInfo(mapIssue(node))
1145
+ )
1146
+ };
1147
+ }
1148
+ async function listLinearIssuesDirect(opts) {
1149
+ return (await listLinearAssignedIssues(opts)).issues;
1091
1150
  }
1092
1151
  async function listLinearTeams(opts) {
1093
1152
  const json = await linearGraphql(TEAMS_QUERY, void 0, opts);
@@ -1206,13 +1265,13 @@ async function validateLinearApiKey(apiKey) {
1206
1265
 
1207
1266
  // src/integrations/issues.ts
1208
1267
  async function listGitHubIssues(repoPath, opts) {
1209
- const limit = Math.max(1, Math.min(100, opts?.limit ?? 50));
1268
+ const limit = Math.max(1, Math.min(1e3, opts?.limit ?? 200));
1210
1269
  const slug = await resolveGithubRepoSlug(repoPath);
1211
1270
  const args = [
1212
1271
  "issue",
1213
1272
  "list",
1214
1273
  "--json",
1215
- "number,title,url,labels",
1274
+ "number,title,url,labels,assignees",
1216
1275
  "--limit",
1217
1276
  String(limit),
1218
1277
  "--state",
@@ -1235,27 +1294,55 @@ async function listGitHubIssues(repoPath, opts) {
1235
1294
  const labels = (item.labels ?? []).map(
1236
1295
  (l) => typeof l === "string" ? l : String(l?.name ?? "")
1237
1296
  ).filter(Boolean);
1297
+ const assignees = (item.assignees ?? []).map((a) => typeof a === "string" ? a : String(a?.login ?? "")).map((login) => login.trim()).filter(Boolean);
1238
1298
  return {
1239
1299
  id: Number.isFinite(number) ? `gh-${number}` : identifier,
1240
1300
  identifier,
1241
1301
  title: String(item.title ?? ""),
1242
1302
  url: String(item.url ?? ""),
1243
1303
  labels,
1244
- provider: "github"
1304
+ provider: "github",
1305
+ assignee: assignees[0],
1306
+ assignees
1245
1307
  };
1246
1308
  });
1247
1309
  }
1310
+ async function githubViewerLogin(repoPath) {
1311
+ const { stdout, exitCode } = await gh(["api", "user", "--jq", ".login"], repoPath, {
1312
+ reject: false
1313
+ });
1314
+ if (exitCode !== 0) return "";
1315
+ return stdout.trim();
1316
+ }
1248
1317
  async function listIssues(repoPath) {
1249
1318
  const settings = loadAppSettings();
1250
1319
  const preferredSource = settings.integrations.issueSource ?? "github";
1251
1320
  const linearConnected = isLinearConnected(settings);
1252
1321
  const source = resolveEffectiveIssueSource(settings);
1253
1322
  if (source === "linear") {
1254
- const issues2 = await listLinearIssuesDirect();
1255
- return { source, preferredSource, linearConnected, issues: issues2 };
1323
+ const listed = await listLinearAssignedIssues();
1324
+ return {
1325
+ source,
1326
+ preferredSource,
1327
+ linearConnected,
1328
+ issues: listed.issues,
1329
+ viewer: {
1330
+ login: listed.viewer.name,
1331
+ name: listed.viewer.name
1332
+ }
1333
+ };
1256
1334
  }
1257
- const issues = await listGitHubIssues(repoPath);
1258
- return { source, preferredSource, linearConnected, issues };
1335
+ const [issues, login] = await Promise.all([
1336
+ listGitHubIssues(repoPath),
1337
+ githubViewerLogin(repoPath)
1338
+ ]);
1339
+ return {
1340
+ source,
1341
+ preferredSource,
1342
+ linearConnected,
1343
+ issues,
1344
+ viewer: login ? { login } : void 0
1345
+ };
1259
1346
  }
1260
1347
 
1261
1348
  // src/composer/diff-comment.ts
@@ -1343,6 +1430,12 @@ function buildPastedTextAttachment(text4, opts) {
1343
1430
  }
1344
1431
 
1345
1432
  // src/plan/ask-user.ts
1433
+ function planQuestionsSignature(questions) {
1434
+ return questions.map((q) => {
1435
+ const opts = q.options.map((o) => o.label).join("");
1436
+ return `${q.question}${opts}`;
1437
+ }).join("");
1438
+ }
1346
1439
  function asRecord(v) {
1347
1440
  return v != null && typeof v === "object" && !Array.isArray(v) ? v : null;
1348
1441
  }
@@ -1400,12 +1493,22 @@ function extractPendingPlanQuestions(parts) {
1400
1493
  if (!questions.length) continue;
1401
1494
  return {
1402
1495
  id: p.id || `ask-${i}`,
1496
+ signature: planQuestionsSignature(questions),
1403
1497
  questions,
1404
1498
  source: p.name || "ask_user"
1405
1499
  };
1406
1500
  }
1407
1501
  return null;
1408
1502
  }
1503
+ function latestPendingPlanQuestions(input) {
1504
+ if (input.userReplied) return null;
1505
+ const fromLive = extractPendingPlanQuestions(input.liveParts);
1506
+ if (fromLive) return fromLive;
1507
+ const last = input.messages[input.messages.length - 1];
1508
+ if (last?.role === "user") return null;
1509
+ if (last?.role === "agent") return extractPendingPlanQuestions(last.parts);
1510
+ return null;
1511
+ }
1409
1512
  function formatPlanQuestionAnswers(questions, answers) {
1410
1513
  const lines = ["Answers to your questions:"];
1411
1514
  for (let i = 0; i < questions.length; i++) {
@@ -1444,6 +1547,7 @@ function formatPlanQuestionsForChat(questions) {
1444
1547
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1445
1548
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1446
1549
  import { z as z4 } from "zod";
1550
+ import { randomUUID as randomUUID3 } from "crypto";
1447
1551
  import { basename } from "path";
1448
1552
 
1449
1553
  // src/mcp/archive-guard.ts
@@ -2226,6 +2330,283 @@ function registerScheduleTools(server) {
2226
2330
  );
2227
2331
  }
2228
2332
 
2333
+ // src/board/load-home-board.ts
2334
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
2335
+ import { join as join2 } from "path";
2336
+
2337
+ // src/board/board-pins.ts
2338
+ import { randomUUID as randomUUID2 } from "crypto";
2339
+ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
2340
+ import { join } from "path";
2341
+ var FILE = "home-board-pins.json";
2342
+ var VERSION = 1;
2343
+ function pinsFile() {
2344
+ return join(appDataDir(), FILE);
2345
+ }
2346
+ function readDisk() {
2347
+ const path2 = pinsFile();
2348
+ if (!existsSync(path2)) return [];
2349
+ try {
2350
+ const raw = JSON.parse(readFileSync(path2, "utf8"));
2351
+ if (raw?.version !== VERSION || !Array.isArray(raw.items)) return [];
2352
+ return raw.items.filter((item) => item?.id && item.kind && item.ref);
2353
+ } catch {
2354
+ return [];
2355
+ }
2356
+ }
2357
+ function writeDisk(items) {
2358
+ mkdirSync(appDataDir(), { recursive: true });
2359
+ writeFileSync(pinsFile(), JSON.stringify({ version: VERSION, items }, null, 2), "utf8");
2360
+ }
2361
+ function listBoardPins() {
2362
+ return readDisk();
2363
+ }
2364
+ function addBoardPin(input) {
2365
+ const ref = input.ref.trim();
2366
+ if (!ref) throw new Error("Board item needs a ref");
2367
+ const pin = {
2368
+ id: randomUUID2(),
2369
+ kind: input.kind,
2370
+ ref,
2371
+ repoPath: input.repoPath.trim(),
2372
+ addedAt: (/* @__PURE__ */ new Date()).toISOString(),
2373
+ title: input.title?.trim() || ref,
2374
+ url: input.url,
2375
+ labels: input.labels,
2376
+ provider: input.provider,
2377
+ assignee: input.assignee,
2378
+ cycle: input.cycle,
2379
+ teamKey: input.teamKey,
2380
+ headRefName: input.headRefName,
2381
+ author: input.author,
2382
+ remoteState: "open",
2383
+ needsWorkspacePick: issueNeedsWorkspacePick(
2384
+ input.provider,
2385
+ input.workspaceCount ?? 1
2386
+ )
2387
+ };
2388
+ const items = listBoardPins();
2389
+ const identity = boardPinIdentity(pin);
2390
+ const existing = items.find((item) => boardPinIdentity(item) === identity);
2391
+ if (existing) {
2392
+ const merged = { ...existing, ...pin, id: existing.id, addedAt: existing.addedAt };
2393
+ writeDisk(items.map((item) => item.id === existing.id ? merged : item));
2394
+ return merged;
2395
+ }
2396
+ writeDisk([...items, pin]);
2397
+ return pin;
2398
+ }
2399
+ function removeBoardPin(id) {
2400
+ const items = listBoardPins();
2401
+ const next = items.filter((item) => item.id !== id);
2402
+ if (next.length === items.length) return false;
2403
+ writeDisk(next);
2404
+ return true;
2405
+ }
2406
+ function replaceBoardPins(items) {
2407
+ writeDisk(items);
2408
+ }
2409
+ function clearBoardPins() {
2410
+ try {
2411
+ unlinkSync(pinsFile());
2412
+ } catch {
2413
+ }
2414
+ }
2415
+
2416
+ // src/board/load-home-board.ts
2417
+ var CACHE_VERSION = 1;
2418
+ var memory = null;
2419
+ var inflight = null;
2420
+ function errText(err) {
2421
+ return err instanceof Error ? err.message : String(err);
2422
+ }
2423
+ function homeBoardWorkspaceKey(workspaces) {
2424
+ return workspaces.map((w) => w.path).filter(Boolean).sort().join("\n");
2425
+ }
2426
+ function cacheFile() {
2427
+ return join2(appDataDir(), "home-board-cache.json");
2428
+ }
2429
+ function emptyInputs() {
2430
+ return {
2431
+ issues: [],
2432
+ prs: [],
2433
+ issueSource: "github",
2434
+ viewerLogin: void 0,
2435
+ issueErrors: [],
2436
+ prErrors: []
2437
+ };
2438
+ }
2439
+ function asLoaded(entry, fromCache) {
2440
+ const pins = syncBoardPins(listBoardPins(), entry.inputs.issues, entry.inputs.prs);
2441
+ return { ...entry.inputs, fetchedAt: entry.fetchedAt, fromCache, pins };
2442
+ }
2443
+ function cacheStillFresh(entry, key, now) {
2444
+ return entry.workspaceKey === key && now - entry.fetchedAt < HOME_BOARD_CACHE_TTL_MS;
2445
+ }
2446
+ function shouldCacheHomeBoardInputs(inputs) {
2447
+ const issuesFailed = inputs.issues.length === 0 && inputs.issueErrors.length > 0;
2448
+ const prsFailed = inputs.prs.length === 0 && inputs.prErrors.length > 0;
2449
+ return !(issuesFailed && prsFailed);
2450
+ }
2451
+ function readDiskCache() {
2452
+ const path2 = cacheFile();
2453
+ if (!existsSync2(path2)) return null;
2454
+ try {
2455
+ const raw = JSON.parse(readFileSync2(path2, "utf8"));
2456
+ if (raw?.version !== CACHE_VERSION || typeof raw.fetchedAt !== "number") {
2457
+ return null;
2458
+ }
2459
+ if (!raw.inputs || !Array.isArray(raw.inputs.issues) || !Array.isArray(raw.inputs.prs)) {
2460
+ return null;
2461
+ }
2462
+ return raw;
2463
+ } catch {
2464
+ return null;
2465
+ }
2466
+ }
2467
+ function writeDiskCache(entry) {
2468
+ try {
2469
+ mkdirSync2(appDataDir(), { recursive: true });
2470
+ writeFileSync2(cacheFile(), JSON.stringify(entry), "utf8");
2471
+ } catch {
2472
+ }
2473
+ }
2474
+ function deleteDiskCache() {
2475
+ try {
2476
+ unlinkSync2(cacheFile());
2477
+ } catch {
2478
+ }
2479
+ }
2480
+ function resetHomeBoardMemory() {
2481
+ memory = null;
2482
+ inflight = null;
2483
+ }
2484
+ function clearHomeBoardCache() {
2485
+ resetHomeBoardMemory();
2486
+ deleteDiskCache();
2487
+ }
2488
+ async function loadHomeBoardInputs(workspaces) {
2489
+ const paths = workspaces.map((w) => w.path).filter(Boolean);
2490
+ if (paths.length === 0) return emptyInputs();
2491
+ const issueErrors = [];
2492
+ const prErrors = [];
2493
+ let issueSource = "github";
2494
+ let viewerLogin;
2495
+ let issues = [];
2496
+ try {
2497
+ const first = await listIssues(paths[0]);
2498
+ issueSource = first.source;
2499
+ viewerLogin = first.viewer?.login || first.viewer?.name || void 0;
2500
+ if (first.source === "linear") {
2501
+ const repoPath = paths[0] ?? "";
2502
+ issues = first.issues.map((issue) => ({
2503
+ ...issue,
2504
+ repoPath,
2505
+ needsWorkspacePick: issueNeedsWorkspacePick(
2506
+ issue.provider ?? first.source,
2507
+ paths.length
2508
+ )
2509
+ }));
2510
+ } else {
2511
+ const settled = await Promise.allSettled(
2512
+ paths.map(async (path2) => {
2513
+ const result = path2 === paths[0] ? first : await listIssues(path2);
2514
+ return result.issues.map((issue) => ({
2515
+ ...issue,
2516
+ repoPath: path2,
2517
+ needsWorkspacePick: issueNeedsWorkspacePick(
2518
+ issue.provider ?? result.source,
2519
+ 1
2520
+ )
2521
+ }));
2522
+ })
2523
+ );
2524
+ const collected = [];
2525
+ for (const item of settled) {
2526
+ if (item.status === "fulfilled") collected.push(...item.value);
2527
+ else issueErrors.push(errText(item.reason));
2528
+ }
2529
+ issues = collected;
2530
+ if (collected.length === 0 && issueErrors[0]) {
2531
+ throw new Error(issueErrors[0]);
2532
+ }
2533
+ }
2534
+ issues = dedupeBoardIssues(issues);
2535
+ } catch (err) {
2536
+ issueErrors.push(errText(err));
2537
+ issues = [];
2538
+ }
2539
+ let prs = [];
2540
+ try {
2541
+ const settled = await Promise.allSettled(
2542
+ paths.map(async (path2) => {
2543
+ const list = await listPrs(path2);
2544
+ return list.map((pr) => ({ ...pr, repoPath: path2 }));
2545
+ })
2546
+ );
2547
+ const collected = [];
2548
+ for (const item of settled) {
2549
+ if (item.status === "fulfilled") collected.push(...item.value);
2550
+ else prErrors.push(errText(item.reason));
2551
+ }
2552
+ if (collected.length === 0 && prErrors[0]) {
2553
+ throw new Error(prErrors[0]);
2554
+ }
2555
+ prs = dedupeBoardPrs(collected);
2556
+ } catch (err) {
2557
+ prErrors.push(errText(err));
2558
+ prs = [];
2559
+ }
2560
+ return { issues, prs, issueSource, viewerLogin, issueErrors, prErrors };
2561
+ }
2562
+ async function getHomeBoardInputs(workspaces, opts) {
2563
+ const key = homeBoardWorkspaceKey(workspaces);
2564
+ const now = opts?.now ?? Date.now();
2565
+ if (!key) {
2566
+ return {
2567
+ ...emptyInputs(),
2568
+ fetchedAt: now,
2569
+ fromCache: false,
2570
+ pins: listBoardPins()
2571
+ };
2572
+ }
2573
+ if (!opts?.refresh) {
2574
+ const disk = readDiskCache();
2575
+ const best = memory && disk ? disk.fetchedAt >= memory.fetchedAt ? disk : memory : memory ?? disk;
2576
+ if (best && cacheStillFresh(best, key, now)) {
2577
+ memory = best;
2578
+ return asLoaded(best, true);
2579
+ }
2580
+ }
2581
+ if (inflight && inflight.key === key) {
2582
+ return inflight.promise;
2583
+ }
2584
+ const promise = (async () => {
2585
+ const inputs = await loadHomeBoardInputs(workspaces);
2586
+ const fetchedAt = opts?.now ?? Date.now();
2587
+ const pins = syncBoardPins(listBoardPins(), inputs.issues, inputs.prs);
2588
+ replaceBoardPins(pins);
2589
+ const loaded = { ...inputs, fetchedAt, fromCache: false, pins };
2590
+ if (shouldCacheHomeBoardInputs(inputs)) {
2591
+ const entry = {
2592
+ version: CACHE_VERSION,
2593
+ workspaceKey: key,
2594
+ fetchedAt,
2595
+ inputs
2596
+ };
2597
+ memory = entry;
2598
+ writeDiskCache(entry);
2599
+ }
2600
+ return loaded;
2601
+ })();
2602
+ inflight = { key, promise };
2603
+ try {
2604
+ return await promise;
2605
+ } finally {
2606
+ if (inflight?.promise === promise) inflight = null;
2607
+ }
2608
+ }
2609
+
2229
2610
  // src/mcp/server.ts
2230
2611
  var MAX_ORCH_THREADS = 5;
2231
2612
  var CREATE_THREAD_TIMEOUT_MS = 9e4;
@@ -2246,6 +2627,93 @@ function withTimeout(promise, ms, label) {
2246
2627
  );
2247
2628
  });
2248
2629
  }
2630
+ async function createOrchChildThread(orch, args, resolveNewThreadOptions2) {
2631
+ const envParentId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || "";
2632
+ let parentId = args.parentThreadId?.trim() || "";
2633
+ let parent = parentId ? orch.getThread(parentId) : null;
2634
+ let parentCorrectedFrom;
2635
+ if (envParentId) {
2636
+ const envParent = orch.getThread(envParentId);
2637
+ if (envParent) {
2638
+ if (parentId && parentId !== envParentId) parentCorrectedFrom = parentId;
2639
+ else if (!parentId) parentCorrectedFrom = void 0;
2640
+ parentId = envParentId;
2641
+ parent = envParent;
2642
+ }
2643
+ }
2644
+ if (parentId && !parent) {
2645
+ parentCorrectedFrom = parentId;
2646
+ parentId = "";
2647
+ parent = null;
2648
+ }
2649
+ if (parentId) {
2650
+ const children = orch.getThreads(false).filter((t) => t.parentThreadId === parentId);
2651
+ if (children.length >= MAX_ORCH_THREADS) {
2652
+ return {
2653
+ ok: false,
2654
+ text: `Thread-creation cap (${MAX_ORCH_THREADS}) reached for this orchestration session`
2655
+ };
2656
+ }
2657
+ }
2658
+ let agentArg = args.agent;
2659
+ let agentCoercedFrom;
2660
+ const resolvedProbe = resolveNewThreadOptions2({ agent: agentArg }).agent;
2661
+ if (resolvedProbe === "codex") {
2662
+ agentCoercedFrom = agentArg ?? "codex";
2663
+ const accountAgent = resolveNewThreadOptions2({}).agent;
2664
+ agentArg = accountAgent !== "codex" ? accountAgent : "cursor";
2665
+ }
2666
+ const opts = resolveNewThreadOptions2({
2667
+ agent: agentArg,
2668
+ model: args.model
2669
+ });
2670
+ try {
2671
+ const priorIds = new Set(orch.getThreads(false).map((t) => t.id));
2672
+ const thread = await withTimeout(
2673
+ orch.createThread({
2674
+ sourceType: args.sourceType,
2675
+ sourceRef: args.sourceRef,
2676
+ agent: opts.agent,
2677
+ model: opts.model,
2678
+ effort: opts.effort,
2679
+ fast: opts.fast,
2680
+ repoPath: args.repoPath,
2681
+ title: args.title,
2682
+ parentThreadId: parentId || null,
2683
+ cowboy: args.cowboy || void 0,
2684
+ attachments: args.attachments
2685
+ }),
2686
+ CREATE_THREAD_TIMEOUT_MS,
2687
+ "create_thread"
2688
+ );
2689
+ const alreadyStarted = priorIds.has(thread.id);
2690
+ const parentNote = parentCorrectedFrom ? parentId ? `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; nested under ${parentId}` : `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; created without parent` : void 0;
2691
+ return {
2692
+ ok: true,
2693
+ text: JSON.stringify({
2694
+ id: thread.id,
2695
+ title: thread.title,
2696
+ branchName: thread.branchName,
2697
+ worktreePath: thread.worktreePath,
2698
+ agent: thread.agent,
2699
+ model: thread.model,
2700
+ status: thread.status,
2701
+ cowboy: Boolean(thread.cowboy),
2702
+ link: `sideboard://thread/${thread.id}`,
2703
+ parentThreadId: thread.parentThreadId,
2704
+ ...alreadyStarted ? { alreadyStarted: true } : {},
2705
+ ...agentCoercedFrom ? {
2706
+ agentCoercedFrom,
2707
+ note: `Avoid nested Codex under a Codex orchestrator \u2014 used Account default agent=${thread.agent}`
2708
+ } : {},
2709
+ ...parentCorrectedFrom ? { parentCorrectedFrom, parentNote } : {}
2710
+ })
2711
+ };
2712
+ } catch (err) {
2713
+ const message = err instanceof Error ? err.message : String(err);
2714
+ return { ok: false, text: `create_thread failed: ${message}` };
2715
+ }
2716
+ }
2249
2717
  async function startMcpServer() {
2250
2718
  const orch = getOrchestrator();
2251
2719
  try {
@@ -2257,7 +2725,7 @@ async function startMcpServer() {
2257
2725
  );
2258
2726
  }
2259
2727
  try {
2260
- const { maxConcurrentAgents: maxConcurrentAgents2 } = await import("./app-settings-YFWV7MOG.js");
2728
+ const { maxConcurrentAgents: maxConcurrentAgents2 } = await import("./app-settings-6RPALX4J.js");
2261
2729
  orch.setMaxConcurrent(maxConcurrentAgents2());
2262
2730
  } catch {
2263
2731
  }
@@ -2277,7 +2745,7 @@ async function startMcpServer() {
2277
2745
  if (!worktreeProfile) {
2278
2746
  server.tool(
2279
2747
  "list_workspaces",
2280
- "List registered Sideboard workspaces (repos). Each line is name, path, and github:owner/repo when resolvable \u2014 use path as repoPath for list_branches/list_prs/list_issues/create_thread.",
2748
+ "List registered Sideboard workspaces (repos). Each line is name, path, and github:owner/repo when resolvable \u2014 use path as repoPath for list_board/list_branches/list_prs/list_issues/create_thread.",
2281
2749
  {},
2282
2750
  async () => {
2283
2751
  const workspaces = orch.listWorkspaces();
@@ -2311,6 +2779,51 @@ async function startMcpServer() {
2311
2779
  };
2312
2780
  }
2313
2781
  );
2782
+ server.tool(
2783
+ "list_board",
2784
+ "Home Kanban of worktrees (New, Draft, Review, Merged) \u2014 one card per checkout; sibling chat tabs nest as inner cards. Same cards as desktop Home. Path to merge: no PR \u2192 draft PR \u2192 open PR \u2192 merged. Archive removes the card to Settings \u2192 History. Queued/running are activity on the card, not columns. Orchestration chats are not on the board. Filters: query, repoPath, kind (ticket/PR/branch source), column, limit (default 40). create_thread adds a worktree (and a Home card), or returns the live one if that ticket/PR/named branch is already checked out.",
2785
+ {
2786
+ query: z4.string().optional().describe("Case-insensitive token search across title, id, labels, repo"),
2787
+ repoPath: z4.string().optional().describe("Limit to one workspace path from list_workspaces"),
2788
+ kind: z4.enum(["all", "tickets", "prs", "branches", "threads"]).optional().describe("Filter by worktree source (default all)"),
2789
+ column: z4.enum(["new", "draft", "review", "done", "needs_you"]).optional().describe(
2790
+ "Return cards for this column only (totals still include the rest). needs_you is a legacy alias for new."
2791
+ ),
2792
+ limit: z4.number().int().positive().optional().describe("Max cards per column (default 40). hidden counts the remainder.")
2793
+ },
2794
+ async ({ query, repoPath, kind, column, limit }) => {
2795
+ const workspaces = orch.listWorkspaces();
2796
+ const all = orch.getThreads(true);
2797
+ const names = new Map(workspaces.map((w) => [w.path, w.name]));
2798
+ const snap = assembleHomeBoard({
2799
+ threads: all.filter((t) => t.status !== "archived"),
2800
+ query,
2801
+ repoPath,
2802
+ kind: kind ?? "all",
2803
+ column: column === "needs_you" ? "new" : column,
2804
+ limit,
2805
+ workspaceName: (path2) => names.get(path2) ?? ""
2806
+ });
2807
+ return {
2808
+ content: [
2809
+ {
2810
+ type: "text",
2811
+ text: JSON.stringify(
2812
+ {
2813
+ columns: snap.columns,
2814
+ hidden: snap.hidden,
2815
+ totals: snap.totals,
2816
+ columnDefs: BOARD_COLUMN_DEFS,
2817
+ hint: HOME_BOARD_AGENT_HINT
2818
+ },
2819
+ null,
2820
+ 2
2821
+ )
2822
+ }
2823
+ ]
2824
+ };
2825
+ }
2826
+ );
2314
2827
  server.tool(
2315
2828
  "get_thread",
2316
2829
  "Get a compact thread summary by id/ref. While running, includes progress (last tool/thinking) and lastActivityAt. Includes usage (thread billed token + costUsd totals when providers reported cost) and lastTurnUsage.",
@@ -2480,7 +2993,7 @@ async function startMcpServer() {
2480
2993
  registerLinearTools(server);
2481
2994
  registerScheduleTools(server);
2482
2995
  const { getCaffeinateHold: getCaffeinateHold2, setCaffeinateHold: setCaffeinateHold2 } = await import("./caffeinate-hold-X3JNPR42.js");
2483
- const { resolveNewThreadOptions: resolveNewThreadOptions2, resolveThreadDefaults: resolveThreadDefaults2 } = await import("./app-settings-YFWV7MOG.js");
2996
+ const { resolveNewThreadOptions: resolveNewThreadOptions2, resolveThreadDefaults: resolveThreadDefaults2 } = await import("./app-settings-6RPALX4J.js");
2484
2997
  const accountDefaults = resolveThreadDefaults2();
2485
2998
  const accountDefaultsHint = `Account defaults: agent=${accountDefaults.agent}, model=${accountDefaults.model?.trim() || "Auto"}, effort=${accountDefaults.effort}`;
2486
2999
  server.tool(
@@ -2523,7 +3036,7 @@ async function startMcpServer() {
2523
3036
  );
2524
3037
  server.tool(
2525
3038
  "create_thread",
2526
- `Create a new worktree thread (chat) from branch, pr, or ticket. Pass repoPath from list_workspaces. cowboy=true uses the project folder on the default branch (no isolated worktree; land is commit+push). From an orchestration chat, omit parentThreadId (Sideboard binds the child to this chat) or pass the exact id from the turn reminder \u2014 never invent a uuid. Prefer omitting agent/model so Sideboard applies ${accountDefaultsHint}. Setup (settings.toml, .cursor/worktrees.json, or script/setup) runs in the background in parallel with the first turn (skipped for cowboy). Then use send_to_thread to chat.`,
3039
+ `Create a worktree thread (chat) from branch, pr, or ticket. If a live worktree already matches that ticket, PR, or named branch, returns it (alreadyStarted=true) instead of a second checkout. Creating from the default branch still opens a new isolated worktree. Pass repoPath from list_workspaces. cowboy=true uses the project folder on the default branch (no isolated worktree; land is commit+push). From an orchestration chat, omit parentThreadId (Sideboard binds the child to this chat) or pass the exact id from the turn reminder \u2014 never invent a uuid. Prefer omitting agent/model so Sideboard applies ${accountDefaultsHint}. Setup (settings.toml, .cursor/worktrees.json, or script/setup) runs in the background in parallel with the first turn (skipped for cowboy). Then use send_to_thread to chat.`,
2527
3040
  {
2528
3041
  sourceType: z4.enum(["branch", "pr", "ticket"]),
2529
3042
  sourceRef: z4.string(),
@@ -2541,104 +3054,118 @@ async function startMcpServer() {
2541
3054
  )
2542
3055
  },
2543
3056
  async (args) => {
2544
- const envParentId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || "";
2545
- let parentId = args.parentThreadId?.trim() || "";
2546
- let parent = parentId ? orch.getThread(parentId) : null;
2547
- let parentCorrectedFrom;
2548
- if (envParentId) {
2549
- const envParent = orch.getThread(envParentId);
2550
- if (envParent) {
2551
- if (parentId && parentId !== envParentId) parentCorrectedFrom = parentId;
2552
- else if (!parentId) parentCorrectedFrom = void 0;
2553
- parentId = envParentId;
2554
- parent = envParent;
2555
- }
2556
- }
2557
- if (parentId && !parent) {
2558
- parentCorrectedFrom = parentId;
2559
- parentId = "";
2560
- parent = null;
2561
- }
2562
- if (parentId) {
2563
- const children = orch.getThreads(false).filter((t) => t.parentThreadId === parentId);
2564
- if (children.length >= MAX_ORCH_THREADS) {
2565
- return {
2566
- content: [
2567
- {
2568
- type: "text",
2569
- text: `Thread-creation cap (${MAX_ORCH_THREADS}) reached for this orchestration session`
2570
- }
2571
- ],
2572
- isError: true
2573
- };
2574
- }
2575
- }
2576
- let agentArg = args.agent;
2577
- let agentCoercedFrom;
2578
- const resolvedProbe = resolveNewThreadOptions2({ agent: agentArg }).agent;
2579
- if (resolvedProbe === "codex") {
2580
- agentCoercedFrom = agentArg ?? "codex";
2581
- const accountAgent = resolveNewThreadOptions2({}).agent;
2582
- agentArg = accountAgent !== "codex" ? accountAgent : "cursor";
2583
- }
2584
- const opts = resolveNewThreadOptions2({
2585
- agent: agentArg,
2586
- model: args.model
2587
- });
2588
- try {
2589
- const thread = await withTimeout(
2590
- orch.createThread({
2591
- sourceType: args.sourceType,
2592
- sourceRef: args.sourceRef,
2593
- agent: opts.agent,
2594
- model: opts.model,
2595
- effort: opts.effort,
2596
- fast: opts.fast,
2597
- repoPath: args.repoPath,
2598
- title: args.title,
2599
- parentThreadId: parentId || null,
2600
- cowboy: args.cowboy || void 0
2601
- }),
2602
- CREATE_THREAD_TIMEOUT_MS,
2603
- "create_thread"
2604
- );
2605
- const parentNote = parentCorrectedFrom ? parentId ? `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; nested under ${parentId}` : `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; created without parent` : void 0;
3057
+ const result = await createOrchChildThread(orch, args, resolveNewThreadOptions2);
3058
+ return {
3059
+ content: [{ type: "text", text: result.text }],
3060
+ ...result.ok ? {} : { isError: true }
3061
+ };
3062
+ }
3063
+ );
3064
+ server.tool(
3065
+ "start_board_card",
3066
+ "Same as create_thread for a ticket, PR, or named branch (attaches issue text when Sideboard can resolve it). Reuses the live worktree if one already matches. Then send_to_thread.",
3067
+ {
3068
+ kind: z4.enum(["ticket", "pr", "branch"]),
3069
+ ref: z4.string().describe("Ticket identifier (ENG-12), PR number (44), or branch name from list_board"),
3070
+ repoPath: z4.string().describe("Workspace path from list_workspaces / list_board"),
3071
+ title: z4.string().optional()
3072
+ },
3073
+ async ({ kind, ref, repoPath, title }) => {
3074
+ const root = await resolveRepoRoot(repoPath);
3075
+ const existing = findLiveThreadForCreate(
3076
+ {
3077
+ sourceType: kind,
3078
+ sourceRef: ref.trim(),
3079
+ repoPath: canonicalizeRepoPath(root),
3080
+ title: title?.trim()
3081
+ },
3082
+ orch.getThreads(false).map((t) => ({
3083
+ ...t,
3084
+ repoPath: canonicalizeRepoPath(t.repoPath)
3085
+ }))
3086
+ );
3087
+ if (existing) {
2606
3088
  return {
2607
3089
  content: [
2608
3090
  {
2609
3091
  type: "text",
2610
3092
  text: JSON.stringify({
2611
- id: thread.id,
2612
- title: thread.title,
2613
- branchName: thread.branchName,
2614
- worktreePath: thread.worktreePath,
2615
- agent: thread.agent,
2616
- model: thread.model,
2617
- status: thread.status,
2618
- cowboy: Boolean(thread.cowboy),
2619
- link: `sideboard://thread/${thread.id}`,
2620
- parentThreadId: thread.parentThreadId,
2621
- ...agentCoercedFrom ? {
2622
- agentCoercedFrom,
2623
- note: `Avoid nested Codex under a Codex orchestrator \u2014 used Account default agent=${thread.agent}`
2624
- } : {},
2625
- ...parentCorrectedFrom ? { parentCorrectedFrom, parentNote } : {}
3093
+ alreadyStarted: true,
3094
+ id: existing.id,
3095
+ title: existing.title,
3096
+ status: existing.status,
3097
+ link: `sideboard://thread/${existing.id}`
2626
3098
  })
2627
3099
  }
2628
3100
  ]
2629
3101
  };
2630
- } catch (err) {
2631
- const message = err instanceof Error ? err.message : String(err);
3102
+ }
3103
+ const workspaces = orch.listWorkspaces();
3104
+ const loaded = await getHomeBoardInputs(workspaces);
3105
+ const pin = findBoardPin(loaded.pins, kind, ref, root);
3106
+ if (kind === "branch") {
3107
+ const sourceRef = pin?.ref === "default" ? "default" : pin?.ref ?? ref.trim();
3108
+ const result2 = await createOrchChildThread(
3109
+ orch,
3110
+ {
3111
+ sourceType: "branch",
3112
+ sourceRef,
3113
+ repoPath: root,
3114
+ title: title?.trim() || pin?.title || (sourceRef === "default" ? void 0 : sourceRef)
3115
+ },
3116
+ resolveNewThreadOptions2
3117
+ );
2632
3118
  return {
2633
- content: [
2634
- {
2635
- type: "text",
2636
- text: `create_thread failed: ${message}`
2637
- }
2638
- ],
2639
- isError: true
3119
+ content: [{ type: "text", text: result2.text }],
3120
+ ...result2.ok ? {} : { isError: true }
2640
3121
  };
2641
3122
  }
3123
+ if (kind === "ticket") {
3124
+ const issue = findBoardIssue(loaded.issues, ref, root);
3125
+ const ident = issue?.identifier ?? ref.trim();
3126
+ const attachments = issue ? [
3127
+ {
3128
+ id: randomUUID3(),
3129
+ name: issue.identifier,
3130
+ kind: "issue",
3131
+ content: [
3132
+ `Linked issue: ${issue.identifier} \u2014 ${issue.title}`,
3133
+ issue.url ? `URL: ${issue.url}` : null
3134
+ ].filter(Boolean).join("\n")
3135
+ }
3136
+ ] : void 0;
3137
+ const result2 = await createOrchChildThread(
3138
+ orch,
3139
+ {
3140
+ sourceType: "ticket",
3141
+ sourceRef: ident,
3142
+ repoPath: root,
3143
+ title: title?.trim() || issue?.title,
3144
+ attachments
3145
+ },
3146
+ resolveNewThreadOptions2
3147
+ );
3148
+ return {
3149
+ content: [{ type: "text", text: result2.text }],
3150
+ ...result2.ok ? {} : { isError: true }
3151
+ };
3152
+ }
3153
+ const pr = findBoardPr(loaded.prs, ref, root);
3154
+ const number = pr ? String(pr.number) : ref.trim().replace(/^#/, "");
3155
+ const result = await createOrchChildThread(
3156
+ orch,
3157
+ {
3158
+ sourceType: "pr",
3159
+ sourceRef: number,
3160
+ repoPath: root,
3161
+ title: title?.trim() || pr?.title
3162
+ },
3163
+ resolveNewThreadOptions2
3164
+ );
3165
+ return {
3166
+ content: [{ type: "text", text: result.text }],
3167
+ ...result.ok ? {} : { isError: true }
3168
+ };
2642
3169
  }
2643
3170
  );
2644
3171
  server.tool(
@@ -3285,7 +3812,7 @@ async function startMcpServer() {
3285
3812
  );
3286
3813
  server.tool(
3287
3814
  "list_issues",
3288
- "List issues from Sideboard Account connections (Linear API or GitHub Issues; Linear\u2192GitHub fallback when Linear is not connected). Pass repoPath from list_workspaces \u2014 GitHub Issues are scoped to that repo. Then create_thread with sourceType=ticket.",
3815
+ "List issues from Sideboard Account connections (Linear API or GitHub Issues; Linear\u2192GitHub fallback when Linear is not connected). Linear returns issues assigned to you (includes cycle). Pass repoPath from list_workspaces \u2014 GitHub Issues are scoped to that repo. Prefer list_board for Home columns + current-cycle filter. Then create_thread with sourceType=ticket.",
3289
3816
  { repoPath: z4.string() },
3290
3817
  async ({ repoPath }) => {
3291
3818
  const root = await resolveRepoRoot(repoPath);
@@ -3627,14 +4154,14 @@ async function runCloudConnect(opts) {
3627
4154
  }
3628
4155
 
3629
4156
  // src/agents/user-mcp-config.ts
3630
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
4157
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
3631
4158
  import { homedir } from "os";
3632
- import { dirname, join } from "path";
4159
+ import { dirname, join as join3 } from "path";
3633
4160
  function userCursorMcpConfigPath() {
3634
- return join(homedir(), ".cursor", "mcp.json");
4161
+ return join3(homedir(), ".cursor", "mcp.json");
3635
4162
  }
3636
4163
  function userClaudeMcpConfigPath() {
3637
- return join(homedir(), ".claude.json");
4164
+ return join3(homedir(), ".claude.json");
3638
4165
  }
3639
4166
  function asObject(value) {
3640
4167
  if (!value || typeof value !== "object" || Array.isArray(value)) return {};
@@ -3661,16 +4188,16 @@ function mergeSideboardIntoMcpServersJson(existing, sideboard) {
3661
4188
  }
3662
4189
  function writeMergedMcpServersJson(configPath, sideboard) {
3663
4190
  let existing = {};
3664
- if (existsSync(configPath)) {
4191
+ if (existsSync3(configPath)) {
3665
4192
  try {
3666
- existing = JSON.parse(readFileSync(configPath, "utf8"));
4193
+ existing = JSON.parse(readFileSync3(configPath, "utf8"));
3667
4194
  } catch {
3668
4195
  existing = {};
3669
4196
  }
3670
4197
  }
3671
4198
  const next = mergeSideboardIntoMcpServersJson(existing, sideboard);
3672
- mkdirSync(dirname(configPath), { recursive: true });
3673
- writeFileSync(configPath, `${JSON.stringify(next, null, 2)}
4199
+ mkdirSync3(dirname(configPath), { recursive: true });
4200
+ writeFileSync3(configPath, `${JSON.stringify(next, null, 2)}
3674
4201
  `);
3675
4202
  }
3676
4203
  function launchFromResolved(server) {
@@ -3688,7 +4215,7 @@ async function registerPackagedUserMcpClients() {
3688
4215
  const cursor = userCursorMcpConfigPath();
3689
4216
  writeMergedMcpServersJson(cursor, launch);
3690
4217
  const claude = userClaudeMcpConfigPath();
3691
- if (existsSync(claude)) {
4218
+ if (existsSync3(claude)) {
3692
4219
  writeMergedMcpServersJson(claude, launch);
3693
4220
  return { cursor, claude };
3694
4221
  }
@@ -5617,6 +6144,8 @@ export {
5617
6144
  GITHUB_GIT_AUTH_MODES,
5618
6145
  GLOBAL_WORKSPACE_ID,
5619
6146
  HARNESS_ENV_KEYS,
6147
+ HOME_BOARD_CACHE_TTL_MS,
6148
+ ISSUE_SOURCE_LABELS,
5620
6149
  LEGACY_ATTACHMENTS_DIR,
5621
6150
  LEGACY_PLAN_FILE_REL,
5622
6151
  LEGACY_REVIEW_REQUEST_PATH,
@@ -5658,6 +6187,7 @@ export {
5658
6187
  THINKING_EFFORTS,
5659
6188
  WORKTREE_MCP_TOOLS,
5660
6189
  ackSlackInboundSeen,
6190
+ addBoardPin,
5661
6191
  addPrStackLayer,
5662
6192
  addStackLayerFromThread,
5663
6193
  addWorkspace,
@@ -5711,15 +6241,19 @@ export {
5711
6241
  caffeinateWhileRunningEnabled,
5712
6242
  caffeinateWhileSchedulesEnabled,
5713
6243
  caffeinateWhileSlackListenEnabled,
6244
+ canonicalizeRepoPath,
5714
6245
  captureLoginEnv,
5715
6246
  captureTurnBaseline,
5716
6247
  checkoutPrStackLayer,
5717
6248
  childEnvWithAppSettings,
5718
6249
  claimDesktopHost,
6250
+ classifyWorktreeColumn,
5719
6251
  claudeAdapter,
5720
6252
  claudeChromeEnabled,
5721
6253
  claudeUserSettingsPath,
5722
6254
  cleanupOrphanWorktrees,
6255
+ clearBoardPins,
6256
+ clearHomeBoardCache,
5723
6257
  cloneRepoIntoSideboard,
5724
6258
  codexAdapter,
5725
6259
  codexSandboxWritableRootsArgs,
@@ -5792,6 +6326,7 @@ export {
5792
6326
  finalizeParts,
5793
6327
  findConventionSetup,
5794
6328
  findInvalidCacheControlTtlOrder,
6329
+ findLiveThreadForCreate,
5795
6330
  findOrphanWorktrees,
5796
6331
  findSlackCoordinator,
5797
6332
  findThreadByRef,
@@ -5843,6 +6378,7 @@ export {
5843
6378
  getGitHubStatus,
5844
6379
  getGithubGitAuthMode,
5845
6380
  getGithubPat,
6381
+ getHomeBoardInputs,
5846
6382
  getIssueSource,
5847
6383
  getLinearApiKey,
5848
6384
  getLinearAuthToken,
@@ -5865,6 +6401,7 @@ export {
5865
6401
  git,
5866
6402
  githubAgentGitEnv,
5867
6403
  globalAgentCwd,
6404
+ groupHomeBoardWorktrees,
5868
6405
  handleSlackInbound,
5869
6406
  harnessEnvKey,
5870
6407
  hasBakedLinearOAuth,
@@ -5898,9 +6435,11 @@ export {
5898
6435
  isGhRateLimitError,
5899
6436
  isGlobalRepoPath,
5900
6437
  isGlobalThread,
6438
+ isHomeBoardThread,
5901
6439
  isImageFilePath,
5902
6440
  isInPrStack,
5903
6441
  isInboundForThisDesktop,
6442
+ isIssueSourceConnected,
5904
6443
  isLinearConnected,
5905
6444
  isLinearOAuthCancelled,
5906
6445
  isOrchestratorCapableAgent,
@@ -5923,12 +6462,16 @@ export {
5923
6462
  isThreadCaffeinated,
5924
6463
  isThreadRecordFile,
5925
6464
  isWorkspaceScratchPath,
6465
+ issueSourceLabel,
5926
6466
  lastRequestOccupancy,
6467
+ latestPendingPlanQuestions,
5927
6468
  linearAuthorizationHeader,
6469
+ linearCycleIsActive,
5928
6470
  linearGraphql,
5929
6471
  linearOAuthAuthorizeUrl,
5930
6472
  linearOAuthCredentials,
5931
6473
  listAgentSetupInfo,
6474
+ listBoardPins,
5932
6475
  listBranchCommits,
5933
6476
  listBranches,
5934
6477
  listBrightsyAccounts,
@@ -5940,6 +6483,7 @@ export {
5940
6483
  listGitHubIssues,
5941
6484
  listGlobalThreads,
5942
6485
  listIssues,
6486
+ listLinearAssignedIssues,
5943
6487
  listLinearIssues,
5944
6488
  listLinearIssuesDirect,
5945
6489
  listLinearTeams,
@@ -5959,6 +6503,7 @@ export {
5959
6503
  loadAppSettings,
5960
6504
  loadBrightsyConfig,
5961
6505
  loadConductorSettings,
6506
+ loadHomeBoardInputs,
5962
6507
  loadRepoSettings,
5963
6508
  loadWorkspaceSettings,
5964
6509
  locksDir,
@@ -6007,6 +6552,7 @@ export {
6007
6552
  permissionMode,
6008
6553
  persistVaultKeyInKeychain,
6009
6554
  planFileAbs,
6555
+ planQuestionsSignature,
6010
6556
  pollSlackOutboundWatches,
6011
6557
  posixShellSingleQuote,
6012
6558
  preferredCursorCostCents,
@@ -6028,6 +6574,7 @@ export {
6028
6574
  registerPackagedUserMcpClients,
6029
6575
  releaseCaffeinateHoldForThread,
6030
6576
  releaseDesktopHost,
6577
+ removeBoardPin,
6031
6578
  removeWorkspace,
6032
6579
  removeWorktree,
6033
6580
  repoSlug,
@@ -6174,6 +6721,7 @@ export {
6174
6721
  withMaxOldSpaceSize,
6175
6722
  withThreadLock,
6176
6723
  workspaceSettingsSourceLabel,
6724
+ worktreeBoardStatus,
6177
6725
  worktreeCleanupSettings,
6178
6726
  worktreeDisplayLabel,
6179
6727
  worktreeDisplayLabelForGroup,