@sideboard-ai/core 0.1.262 → 0.1.264

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 (55) hide show
  1. package/dist/{abletime-OQ4PC52L.js → abletime-6AOJI2YZ.js} +4 -4
  2. package/dist/{abletime-CDGINDXG.js → abletime-U75ZGDIX.js} +5 -5
  3. package/dist/{abletime-oauth-KX2TDFTE.js → abletime-oauth-LOAHGG4X.js} +3 -3
  4. package/dist/{abletime-oauth-5A5EOH2A.js → abletime-oauth-TA2YDCYX.js} +4 -4
  5. package/dist/{abletime-rest-UEHOBLXD.js → abletime-rest-CTA5WV26.js} +4 -4
  6. package/dist/{abletime-rest-PUGKYTRR.js → abletime-rest-YZPFM6IY.js} +3 -3
  7. package/dist/agents/claude-runner.cjs +463 -0
  8. package/dist/agents/claude-runner.d.cts +1 -0
  9. package/dist/agents/claude-runner.d.ts +1 -0
  10. package/dist/agents/claude-runner.js +134 -0
  11. package/dist/agents/cursor-runner.cjs +393 -8
  12. package/dist/agents/cursor-runner.js +39 -8
  13. package/dist/{agents-J2P4KNEY.js → agents-3TUMFSMH.js} +10 -9
  14. package/dist/{agents-IKVUGW5H.js → agents-6FWDRVA2.js} +4 -4
  15. package/dist/{app-settings-OM7H64YM.js → app-settings-MVZPM6WW.js} +1 -1
  16. package/dist/{app-settings-VQEUCONM.js → app-settings-RKFFUIFQ.js} +2 -2
  17. package/dist/{chunk-6PFZ6XO7.js → chunk-2QUQH2SZ.js} +81 -6
  18. package/dist/{chunk-VIC6TDYT.js → chunk-36FOBJBF.js} +76 -3
  19. package/dist/{chunk-ABPNIYRS.js → chunk-3H7O27X2.js} +389 -102
  20. package/dist/{chunk-FSZ7YYFQ.js → chunk-4ALDX5IO.js} +5 -3
  21. package/dist/{chunk-5OKJDCBK.js → chunk-5PK53SAP.js} +5 -5
  22. package/dist/{chunk-U3AOJLH3.js → chunk-5ZPT5YAV.js} +8 -6
  23. package/dist/{chunk-YFTO7U2A.js → chunk-6YOXQVJ7.js} +3 -3
  24. package/dist/{chunk-J6ONZP74.js → chunk-6YY7XPPL.js} +5 -5
  25. package/dist/{chunk-WV7KJH2E.js → chunk-A5SAYFJ4.js} +1 -0
  26. package/dist/{chunk-B6XFBNYM.js → chunk-BOMSJUXW.js} +2 -2
  27. package/dist/{chunk-S7VDDJ34.js → chunk-CEOTK5GZ.js} +3 -3
  28. package/dist/{chunk-4F7J6UWD.js → chunk-EK3KVT7I.js} +515 -139
  29. package/dist/{chunk-HHEKVR6C.js → chunk-F5MFNUAA.js} +40 -2
  30. package/dist/{chunk-KVQX62XI.js → chunk-K7B25HO4.js} +3 -3
  31. package/dist/{chunk-JTNY5CK4.js → chunk-L5WGAIL2.js} +46 -46
  32. package/dist/chunk-NYBFYFK7.js +415 -0
  33. package/dist/{chunk-GIZQAOHZ.js → chunk-QX6QUZVD.js} +121 -91
  34. package/dist/{chunk-X2OTZ36G.js → chunk-RKPBIB2U.js} +52 -52
  35. package/dist/{chunk-W5PWGEKS.js → chunk-T3AYRQJZ.js} +107 -77
  36. package/dist/{chunk-D554YCHG.js → chunk-YKYEHDMX.js} +2 -2
  37. package/dist/connected-teams-RU6VATCM.js +1 -1
  38. package/dist/{coordinator-prompt-VQHQ3RIX.js → coordinator-prompt-HZCQZ6C5.js} +6 -6
  39. package/dist/{coordinator-prompt-TU6656OK.js → coordinator-prompt-J7DKHOCH.js} +3 -3
  40. package/dist/{global-workspace-PHF2MOGW.js → global-workspace-6LDUZ2NF.js} +3 -3
  41. package/dist/{global-workspace-47NZVA44.js → global-workspace-TI26K25J.js} +6 -6
  42. package/dist/index.cjs +1712 -721
  43. package/dist/index.d.cts +181 -41
  44. package/dist/index.d.ts +181 -41
  45. package/dist/index.js +693 -285
  46. package/dist/mcp/run-stdio.cjs +10469 -9896
  47. package/dist/mcp/run-stdio.js +193 -168
  48. package/dist/{orchestrator-VLTO5JBR.js → orchestrator-4JSG7BWC.js} +5 -5
  49. package/dist/{orchestrator-OCG4QVA5.js → orchestrator-5YU46YNS.js} +11 -10
  50. package/dist/{workspaces-I7TWFTJM.js → workspaces-QJ3XIPUJ.js} +6 -6
  51. package/dist/{workspaces-2XTDS7N2.js → workspaces-WOQHJFRQ.js} +3 -3
  52. package/dist/{worktree-DDDF3WHV.js → worktree-6U2FA5LO.js} +2 -2
  53. package/dist/{worktree-YASFE65H.js → worktree-KQA4ON2G.js} +5 -5
  54. package/package.json +4 -3
  55. /package/dist/{chunk-LDXSAVWO.js → chunk-GWFVCCJZ.js} +0 -0
package/dist/index.js CHANGED
@@ -1,66 +1,3 @@
1
- import {
2
- ABLETIME_OAUTH_CALLBACK_PATH,
3
- ABLETIME_OAUTH_CANCELLED,
4
- ABLETIME_OAUTH_LOCAL_CALLBACK,
5
- ABLETIME_OAUTH_PORT,
6
- ABLETIME_OAUTH_REDIRECT,
7
- ABLETIME_OAUTH_RESOURCE,
8
- AbleTimeOAuthCancelledError,
9
- BAKED_ABLETIME_OAUTH_CLIENT_ID,
10
- ableTimeOAuthAuthorizeUrl,
11
- ableTimeOAuthBouncePage,
12
- ableTimeOAuthClientId,
13
- ableTimeOAuthLocalBounceUrl,
14
- ableTimeOAuthRedirectUri,
15
- ableTimeOAuthResource,
16
- createAbleTimePkce,
17
- ensureAbleTimeAccessToken,
18
- hasBakedAbleTimeOAuth,
19
- isAbleTimeOAuthCancelled,
20
- parseAbleTimeOAuthCallbackUrl,
21
- resolveAbleTimeOAuthHost,
22
- startAbleTimeOAuth
23
- } from "./chunk-YFTO7U2A.js";
24
- import {
25
- ableTimeDependencyTarget,
26
- commentAbleTimeTask,
27
- createAbleTimeTask,
28
- ensureAbleTimeTask,
29
- getAbleTimeOrientation,
30
- getAbleTimeTask,
31
- issueAttachmentForAbleTimeTask,
32
- listAbleTimeAssignedIssues,
33
- listAbleTimeProjects,
34
- listAbleTimeTasks,
35
- mapAbleTimeTask,
36
- searchAbleTimeTasks,
37
- taskUrl,
38
- toAbleTimeIssueInfo,
39
- updateAbleTimeTask,
40
- verifyAbleTimeConnection
41
- } from "./chunk-KVQX62XI.js";
42
- import "./chunk-D554YCHG.js";
43
- import {
44
- ABLETIME_MCP_PATH,
45
- ABLETIME_MCP_PM_PATH,
46
- ABLETIME_OAUTH_TOKEN_PREFIX,
47
- ABLETIME_ORG_KEY_PREFIX,
48
- ABLETIME_PAT_PREFIX,
49
- ABLETIME_PM_TOOL_NAMES,
50
- DEFAULT_ABLETIME_HOST,
51
- ableTimeCredentialKind,
52
- ableTimeUsesRest,
53
- abletimeMcpRequest,
54
- abletimeMcpUrl,
55
- abletimeToolUsesPm,
56
- assertAbleTimeCredential,
57
- assertAbleTimeMcpCredential,
58
- callAbleTimeTool,
59
- isAbleTimeRejectedCredential,
60
- normalizeAbleTimeCredential,
61
- normalizeAbleTimeHost,
62
- rewriteAbleTimeError
63
- } from "./chunk-J6ONZP74.js";
64
1
  import {
65
2
  AGENT_GIT_ACTIONS,
66
3
  BOARD_COLUMN_DEFS,
@@ -335,15 +272,19 @@ import {
335
272
  verifyOptionalService,
336
273
  waitForDetachedJob,
337
274
  waitForPidExit,
275
+ waitForTurnToolResult,
338
276
  worktreeBoardStatus,
339
277
  worktreeCleanupSettings,
340
278
  worktreeRunPortsPath,
341
279
  wrapReviewSkillMarkdown,
342
280
  writeWorktreeFile
343
- } from "./chunk-GIZQAOHZ.js";
281
+ } from "./chunk-QX6QUZVD.js";
344
282
  import {
283
+ BASE_ALLOWED_TOOLS,
345
284
  BRIGHTSY_MCP_ALLOWED_TOOLS,
285
+ CLAUDE_CHROME_ALLOWED_TOOLS,
346
286
  CLAUDE_MODEL_CATALOG,
287
+ CLAUDE_PRINT_BG_WAIT_CEILING_MS,
347
288
  MAX_ANTHROPIC_CACHE_CONTROL_BLOCKS,
348
289
  PLAN_MODE_INSTRUCTION,
349
290
  PLAN_MODE_OFF_INSTRUCTION,
@@ -445,6 +386,7 @@ import {
445
386
  resolveQuotaFallbackAgent,
446
387
  sanitizeMcpServerName,
447
388
  shouldInjectBrightsyMcp,
389
+ shouldRegisterMcpWaitForTurn,
448
390
  sideboardMcpProfile,
449
391
  skillCommandFromBody,
450
392
  skillCommandFromInput,
@@ -464,9 +406,83 @@ import {
464
406
  whichOnPath,
465
407
  withEventParentId,
466
408
  withEventsParentId,
409
+ worktreeMcpToolNames,
467
410
  writeInjectedMcpConfig
468
- } from "./chunk-ABPNIYRS.js";
411
+ } from "./chunk-3H7O27X2.js";
412
+ import {
413
+ brightsyMcpServerName,
414
+ connectBrightsyTeam,
415
+ disconnectBrightsyTeam,
416
+ ensureConnectedBrightsyTeamTokens,
417
+ getBrightsySession,
418
+ listBrightsyAccounts,
419
+ listConnectedBrightsyTeams,
420
+ switchBrightsyAccount
421
+ } from "./chunk-GWFVCCJZ.js";
469
422
  import "./chunk-7H655NEW.js";
423
+ import {
424
+ ABLETIME_OAUTH_CALLBACK_PATH,
425
+ ABLETIME_OAUTH_CANCELLED,
426
+ ABLETIME_OAUTH_LOCAL_CALLBACK,
427
+ ABLETIME_OAUTH_PORT,
428
+ ABLETIME_OAUTH_REDIRECT,
429
+ ABLETIME_OAUTH_RESOURCE,
430
+ AbleTimeOAuthCancelledError,
431
+ BAKED_ABLETIME_OAUTH_CLIENT_ID,
432
+ ableTimeOAuthAuthorizeUrl,
433
+ ableTimeOAuthBouncePage,
434
+ ableTimeOAuthClientId,
435
+ ableTimeOAuthLocalBounceUrl,
436
+ ableTimeOAuthRedirectUri,
437
+ ableTimeOAuthResource,
438
+ createAbleTimePkce,
439
+ ensureAbleTimeAccessToken,
440
+ hasBakedAbleTimeOAuth,
441
+ isAbleTimeOAuthCancelled,
442
+ parseAbleTimeOAuthCallbackUrl,
443
+ resolveAbleTimeOAuthHost,
444
+ startAbleTimeOAuth
445
+ } from "./chunk-6YOXQVJ7.js";
446
+ import {
447
+ ableTimeDependencyTarget,
448
+ commentAbleTimeTask,
449
+ createAbleTimeTask,
450
+ ensureAbleTimeTask,
451
+ getAbleTimeOrientation,
452
+ getAbleTimeTask,
453
+ issueAttachmentForAbleTimeTask,
454
+ listAbleTimeAssignedIssues,
455
+ listAbleTimeProjects,
456
+ listAbleTimeTasks,
457
+ mapAbleTimeTask,
458
+ searchAbleTimeTasks,
459
+ taskUrl,
460
+ toAbleTimeIssueInfo,
461
+ updateAbleTimeTask,
462
+ verifyAbleTimeConnection
463
+ } from "./chunk-K7B25HO4.js";
464
+ import "./chunk-YKYEHDMX.js";
465
+ import {
466
+ ABLETIME_MCP_PATH,
467
+ ABLETIME_MCP_PM_PATH,
468
+ ABLETIME_OAUTH_TOKEN_PREFIX,
469
+ ABLETIME_ORG_KEY_PREFIX,
470
+ ABLETIME_PAT_PREFIX,
471
+ ABLETIME_PM_TOOL_NAMES,
472
+ DEFAULT_ABLETIME_HOST,
473
+ ableTimeCredentialKind,
474
+ ableTimeUsesRest,
475
+ abletimeMcpRequest,
476
+ abletimeMcpUrl,
477
+ abletimeToolUsesPm,
478
+ assertAbleTimeCredential,
479
+ assertAbleTimeMcpCredential,
480
+ callAbleTimeTool,
481
+ isAbleTimeRejectedCredential,
482
+ normalizeAbleTimeCredential,
483
+ normalizeAbleTimeHost,
484
+ rewriteAbleTimeError
485
+ } from "./chunk-6YY7XPPL.js";
470
486
  import {
471
487
  LEGACY_PLAN_FILE_REL,
472
488
  PLAN_FILE_NAME,
@@ -527,7 +543,7 @@ import {
527
543
  stageBuffersAsAttachments,
528
544
  syncWorkspacesFromThreads,
529
545
  takenTeamSlugsForOrchestration
530
- } from "./chunk-X2OTZ36G.js";
546
+ } from "./chunk-RKPBIB2U.js";
531
547
  import {
532
548
  FAMOUS_SOCCER_TEAMS,
533
549
  GITHUB_PR_BODY_MAX_CHARS,
@@ -627,18 +643,26 @@ import {
627
643
  worktreeDisplayLabelForGroup,
628
644
  worktreeNameFromPath,
629
645
  worktreeSlugForTicket
630
- } from "./chunk-U3AOJLH3.js";
631
- import "./chunk-2P4XKWV2.js";
646
+ } from "./chunk-5ZPT5YAV.js";
632
647
  import {
633
- brightsyMcpServerName,
634
- connectBrightsyTeam,
635
- disconnectBrightsyTeam,
636
- ensureConnectedBrightsyTeamTokens,
637
- getBrightsySession,
638
- listBrightsyAccounts,
639
- listConnectedBrightsyTeams,
640
- switchBrightsyAccount
641
- } from "./chunk-LDXSAVWO.js";
648
+ appendMessage,
649
+ createEmptyThread,
650
+ deleteThreadRecord,
651
+ findThreadByRef,
652
+ invalidateThreadListCache,
653
+ invalidateThreadRecord,
654
+ isSelfWrittenRecord,
655
+ isThreadRecordFile,
656
+ listThreads,
657
+ normalizeThread,
658
+ readThread,
659
+ resolveThreadEffort,
660
+ setStatus,
661
+ updateThread,
662
+ withThreadLock,
663
+ writeThread
664
+ } from "./chunk-FWNL62VA.js";
665
+ import "./chunk-2P4XKWV2.js";
642
666
  import {
643
667
  BRIGHTSY_OAUTH_CLIENT_ID,
644
668
  BRIGHTSY_OAUTH_REDIRECT,
@@ -659,7 +683,7 @@ import {
659
683
  } from "./chunk-N7WZ6NIN.js";
660
684
  import {
661
685
  clipMcpToolResult
662
- } from "./chunk-HHEKVR6C.js";
686
+ } from "./chunk-F5MFNUAA.js";
663
687
  import "./chunk-AXQ4ZWHK.js";
664
688
  import {
665
689
  AGENT_RUNNER_MAX_OLD_SPACE_MB,
@@ -675,7 +699,7 @@ import {
675
699
  trustSystemCertificates,
676
700
  turnCostUsdFromCursorUsage,
677
701
  withMaxOldSpaceSize
678
- } from "./chunk-WV7KJH2E.js";
702
+ } from "./chunk-A5SAYFJ4.js";
679
703
  import {
680
704
  ATTACHMENTS_DIR,
681
705
  CLI_DIR,
@@ -685,6 +709,7 @@ import {
685
709
  attachmentsGitignoreBody,
686
710
  isWorkspaceScratchPath
687
711
  } from "./chunk-6K64FJDD.js";
712
+ import "./chunk-UWQ6UCIB.js";
688
713
  import {
689
714
  conductorBundledBinDir,
690
715
  enrichPathWithNpmGlobalBin,
@@ -701,6 +726,28 @@ import {
701
726
  run,
702
727
  withExportedPath
703
728
  } from "./chunk-QVN2WOKJ.js";
729
+ import {
730
+ AGENT_RPC_JOB_HOLD_MAX_MS,
731
+ AGENT_RPC_JOB_SLICE_MS,
732
+ AGENT_RPC_NATIVE_ENV,
733
+ AGENT_RPC_PATH,
734
+ AGENT_RPC_PROTOCOL_VERSION,
735
+ AGENT_RPC_TOKEN_QUERY,
736
+ RPC_INTERNAL_ERROR,
737
+ RPC_INVALID_REQUEST,
738
+ RPC_METHOD_NOT_FOUND,
739
+ RPC_PARSE_ERROR,
740
+ RPC_UNAUTHORIZED,
741
+ agentRpcConnectFromMetadata,
742
+ clearAgentRuntimeMetadataIfOwned,
743
+ isJsonRpcRequest,
744
+ parseRpcFrame,
745
+ readAgentRuntimeMetadata,
746
+ rpcError,
747
+ rpcNotify,
748
+ rpcResult,
749
+ writeAgentRuntimeMetadata
750
+ } from "./chunk-NYBFYFK7.js";
704
751
  import {
705
752
  AGENT_DONE_SOUNDS,
706
753
  CHAT_TEXT_SCALES,
@@ -794,6 +841,7 @@ import {
794
841
  persistVaultKeyInKeychain,
795
842
  planHistoryAgePurge,
796
843
  planHistoryRetention,
844
+ previousCostBaseline,
797
845
  readKeychainVaultKey,
798
846
  readSecureJson,
799
847
  readViewerContext,
@@ -826,6 +874,7 @@ import {
826
874
  sumUsageList,
827
875
  toPublicAppSettings,
828
876
  totalTokens,
877
+ turnCostUsdFromProviderSnapshot,
829
878
  updateAdvancedSettings,
830
879
  updateAgentExecutable,
831
880
  updateAppEnvironment,
@@ -837,9 +886,18 @@ import {
837
886
  updateOpencodeSettings,
838
887
  updateProjectProfileSettings,
839
888
  usageOnLimit,
889
+ withTurnScopedCost,
840
890
  writeSecureJson,
841
891
  writeViewerContext
842
- } from "./chunk-6PFZ6XO7.js";
892
+ } from "./chunk-2QUQH2SZ.js";
893
+ import {
894
+ THINKING_EFFORTS,
895
+ isThinkingEffort,
896
+ nextThinkingEffort,
897
+ normalizeThinkingEffort,
898
+ thinkingEffortBars,
899
+ thinkingEffortLabel
900
+ } from "./chunk-77WWLBCI.js";
843
901
  import {
844
902
  stripNestedElectronEnv
845
903
  } from "./chunk-4TR3HZFT.js";
@@ -877,33 +935,6 @@ import {
877
935
  import {
878
936
  writePrivateFile
879
937
  } from "./chunk-JT7R45JB.js";
880
- import {
881
- appendMessage,
882
- createEmptyThread,
883
- deleteThreadRecord,
884
- findThreadByRef,
885
- invalidateThreadListCache,
886
- invalidateThreadRecord,
887
- isSelfWrittenRecord,
888
- isThreadRecordFile,
889
- listThreads,
890
- normalizeThread,
891
- readThread,
892
- resolveThreadEffort,
893
- setStatus,
894
- updateThread,
895
- withThreadLock,
896
- writeThread
897
- } from "./chunk-FWNL62VA.js";
898
- import "./chunk-UWQ6UCIB.js";
899
- import {
900
- THINKING_EFFORTS,
901
- isThinkingEffort,
902
- nextThinkingEffort,
903
- normalizeThinkingEffort,
904
- thinkingEffortBars,
905
- thinkingEffortLabel
906
- } from "./chunk-77WWLBCI.js";
907
938
  import {
908
939
  CONVENTION_SETUP_RELPATHS,
909
940
  activeRunPreview,
@@ -3235,6 +3266,31 @@ function buildPastedTextAttachment(text7, opts) {
3235
3266
  };
3236
3267
  }
3237
3268
 
3269
+ // src/threads/chat-link.ts
3270
+ var CHAT_LINK_PREFIX = "sideboard://chat/";
3271
+ var LEGACY_THREAD_LINK_PREFIX = "sideboard://thread/";
3272
+ var ID = "[A-Za-z0-9_-]+";
3273
+ var CHAT_LINK_RE = new RegExp(`^sideboard://(?:chat|thread)/(${ID})/?$`, "i");
3274
+ var BARE_CHAT_LINK_RE = new RegExp(
3275
+ `(?<!\\]\\()(?<!href=["'])sideboard://(?:chat|thread)/${ID}/?`,
3276
+ "gi"
3277
+ );
3278
+ function chatLinkUrl(chatId) {
3279
+ return `${CHAT_LINK_PREFIX}${chatId.trim()}`;
3280
+ }
3281
+ function parseChatLink(href) {
3282
+ const match = CHAT_LINK_RE.exec(href.trim());
3283
+ return match?.[1] ?? null;
3284
+ }
3285
+ function linkifyChatUrls(text7) {
3286
+ return text7.replace(BARE_CHAT_LINK_RE, (url) => {
3287
+ const id = parseChatLink(url);
3288
+ if (!id) return url;
3289
+ const label = id.length > 8 ? id.slice(0, 8) : id;
3290
+ return `[${label}](${chatLinkUrl(id)})`;
3291
+ });
3292
+ }
3293
+
3238
3294
  // src/orchestrator/outbound-turn-reply.ts
3239
3295
  function outboundReplyFromTurn(result, copy) {
3240
3296
  switch (result.taskState) {
@@ -3271,10 +3327,10 @@ function formatAskUserNotifyMessage(questions) {
3271
3327
  }
3272
3328
  function formatNotifyOrchestratorPrompt(input) {
3273
3329
  const title = input.child.title?.trim() || "Untitled";
3274
- const link = `[${title}](sideboard://thread/${input.child.id})`;
3330
+ const link = `[${title}](sideboard://chat/${input.child.id})`;
3275
3331
  const body = clampMessage(input.message) || "(no details)";
3276
3332
  const state = input.reason === "input-required" ? "is input-required (waiting for the user in that chat)" : "is blocked";
3277
- const next = input.reason === "input-required" ? "Wait for the user in that chat. Do not send_to_thread a check-in." : "Help if this is your child; otherwise briefly tell the user. Do not send_to_thread a check-in.";
3333
+ const next = input.reason === "input-required" ? "Wait for the user in that chat. Do not send_to_chat a check-in." : "Help if this is your child; otherwise briefly tell the user. Do not send_to_chat a check-in.";
3278
3334
  return [
3279
3335
  `Sideboard: child worktree ${link} ${state}.`,
3280
3336
  body,
@@ -3299,13 +3355,13 @@ function resolveNotifyCallerThread(opts) {
3299
3355
  return thread;
3300
3356
  }
3301
3357
  const siblings = threadsSharingWorktree(cwd);
3302
- const running = siblings.find((t) => t.status === "running") ?? siblings[0];
3303
- if (!running) {
3358
+ const running2 = siblings.find((t) => t.status === "running") ?? siblings[0];
3359
+ if (!running2) {
3304
3360
  throw new Error(
3305
3361
  "No Sideboard thread for this cwd. Call notify_orchestrator from a worktree chat."
3306
3362
  );
3307
3363
  }
3308
- return running;
3364
+ return running2;
3309
3365
  }
3310
3366
  function skipReason(opts) {
3311
3367
  if (!opts.child.parentThreadId) {
@@ -3508,7 +3564,7 @@ function createClippedMcpServer() {
3508
3564
 
3509
3565
  // src/mcp/server.ts
3510
3566
  import { z as z9 } from "zod";
3511
- import { randomUUID as randomUUID3 } from "crypto";
3567
+ import { randomUUID as randomUUID4 } from "crypto";
3512
3568
  import { basename as basename2 } from "path";
3513
3569
 
3514
3570
  // src/mcp/archive-guard.ts
@@ -4039,7 +4095,7 @@ async function continueSourceThread(threadId, prompt) {
4039
4095
  await continueOnReply(threadId, prompt);
4040
4096
  return;
4041
4097
  }
4042
- const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-OCG4QVA5.js");
4098
+ const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-5YU46YNS.js");
4043
4099
  await getOrchestrator2().send(threadId, prompt);
4044
4100
  } catch {
4045
4101
  }
@@ -5128,7 +5184,7 @@ function registerAbleTimeTools(server) {
5128
5184
  );
5129
5185
  server.tool(
5130
5186
  "abletime_ensure_task",
5131
- "Find or create an AbleTime task for this work, then create_thread with sourceType=ticket.",
5187
+ "Find or create an AbleTime task for this work, then create_workspace with sourceType=ticket.",
5132
5188
  {
5133
5189
  title: z4.string(),
5134
5190
  description: z4.string().optional(),
@@ -5452,11 +5508,11 @@ async function resolveProjectRepoPath(repoPath) {
5452
5508
  }
5453
5509
  return matchRegisteredWorkspace(root) ?? matchRegisteredWorkspace(hint);
5454
5510
  }
5455
- var PROJECT_PATH_HINT = "Pass repoPath from list_workspaces. Orchestration cwd is synthetic and empty \u2014 do not omit repoPath for project context.";
5511
+ var PROJECT_PATH_HINT = "Pass repoPath from list_projects. Orchestration cwd is synthetic and empty \u2014 do not omit repoPath for project context.";
5456
5512
  function registerViewerContextTools(server) {
5457
5513
  server.tool(
5458
5514
  "get_viewer_context",
5459
- "Read account and project user context (Settings \u2192 Agents / Projects): roles, tickets, and review queues as freeform text. Orchestration must pass repoPath from list_workspaces for project context. Worktree turns may omit repoPath (uses cwd).",
5515
+ "Read account and project user context (Settings \u2192 Agents / Projects): roles, tickets, and review queues as freeform text. Orchestration must pass repoPath from list_projects for project context. Worktree turns may omit repoPath (uses cwd).",
5460
5516
  {
5461
5517
  repoPath: z7.string().optional().describe(
5462
5518
  "Registered workspace or worktree path. Required from orchestration for a project. Omit on a worktree turn (uses cwd)."
@@ -5482,7 +5538,7 @@ function registerViewerContextTools(server) {
5482
5538
  context: z7.string().describe("Full replacement text (roles, tickets, review queues). Empty clears."),
5483
5539
  confirmed: z7.boolean().optional().describe("Must be true after the user confirmed via ask_user. False/omit is rejected."),
5484
5540
  repoPath: z7.string().optional().describe(
5485
- "Required for scope=project from orchestration (path from list_workspaces). Worktree turns may omit it (cwd)."
5541
+ "Required for scope=project from orchestration (path from list_projects). Worktree turns may omit it (cwd)."
5486
5542
  )
5487
5543
  },
5488
5544
  async ({ scope, context, confirmed, repoPath }) => {
@@ -5596,7 +5652,7 @@ function registerScheduleTools(server) {
5596
5652
  );
5597
5653
  server.tool(
5598
5654
  "create_schedule",
5599
- "Create a local schedule that, when due, sends a prompt to an existing chat (threadId) or starts a new Global orchestration chat (omit threadId). Pass threadId=self to continue this chat. If that chat is missing or archived when the job fires, Sideboard starts a new Global chat (recurring jobs then continue that chat). Exactly one of at (ISO datetime), every (15m/1h/6h/1d), or cron (5-field). Recurring jobs without threadId open a new Global chat each run. Creating or enabling a future job turns on Settings \u2192 Advanced \u2192 Caffeinate while schedules are enabled so the Mac stays awake until due. Sideboard.app must be running for the job to fire.",
5655
+ "Create a local schedule that, when due, sends a prompt to an existing chat (chatId) or starts a new Global orchestration chat (omit chatId). Pass chatId=self to continue this chat. If that chat is missing or archived when the job fires, Sideboard starts a new Global chat (recurring jobs then continue that chat). Exactly one of at (ISO datetime), every (15m/1h/6h/1d), or cron (5-field). Recurring jobs without chatId open a new Global chat each run. Creating or enabling a future job turns on Settings \u2192 Advanced \u2192 Caffeinate while schedules are enabled so the Mac stays awake until due. Sideboard.app must be running for the job to fire.",
5600
5656
  {
5601
5657
  prompt: z8.string().describe("User message / goal queued when the schedule fires"),
5602
5658
  name: z8.string().optional(),
@@ -5604,20 +5660,22 @@ function registerScheduleTools(server) {
5604
5660
  every: z8.string().optional().describe("Interval such as 15m, 1h, 6h, 1d"),
5605
5661
  cron: z8.string().optional().describe("5-field cron expression"),
5606
5662
  tz: z8.string().optional().describe("IANA timezone for cron (default: system)"),
5607
- threadId: z8.string().optional().describe(
5608
- 'Existing chat id (orchestration or worktree), or "self" for this chat. Omit to create a new Global chat on fire.'
5663
+ chatId: z8.string().optional().describe(
5664
+ 'Existing chat id (orchestration or workspace), or "self" for this chat. Omit to create a new Global chat on fire.'
5609
5665
  ),
5666
+ threadId: z8.string().optional().describe("Legacy alias for chatId."),
5610
5667
  agent: z8.enum(["claude", "cursor", "codex", "opencode"]).optional().describe("Agent for a new Global chat (omit for Account default)"),
5611
5668
  model: z8.string().optional()
5612
5669
  },
5613
5670
  async (args) => {
5614
5671
  try {
5615
5672
  const when = parseWhen(args);
5616
- const threadId = resolveScheduleThreadId(args.threadId);
5617
- if (args.threadId?.trim().toLowerCase() === "self" && !threadId) {
5673
+ const rawChatId = args.chatId ?? args.threadId;
5674
+ const threadId = resolveScheduleThreadId(rawChatId);
5675
+ if (rawChatId?.trim().toLowerCase() === "self" && !threadId) {
5618
5676
  return fail5(
5619
5677
  new Error(
5620
- "threadId=self needs this chat id (SIDEBOARD_THREAD_ID or SIDEBOARD_ORCHESTRATOR_THREAD_ID)"
5678
+ "chatId=self needs this chat id (SIDEBOARD_THREAD_ID or SIDEBOARD_ORCHESTRATOR_THREAD_ID)"
5621
5679
  )
5622
5680
  );
5623
5681
  }
@@ -5632,7 +5690,7 @@ function registerScheduleTools(server) {
5632
5690
  });
5633
5691
  return text6({
5634
5692
  schedule,
5635
- hint: scheduleNeedsCaffeinate(schedule) ? "Fires while Sideboard.app is running. Recurring jobs without threadId create a new orchestration chat each run. Caffeinate while schedules are enabled is now on so the Mac can stay awake until this job is due." : "Fires while Sideboard.app is running. Recurring jobs without threadId create a new orchestration chat each run."
5693
+ hint: scheduleNeedsCaffeinate(schedule) ? "Fires while Sideboard.app is running. Recurring jobs without chatId create a new orchestration chat each run. Caffeinate while schedules are enabled is now on so the Mac can stay awake until this job is due." : "Fires while Sideboard.app is running. Recurring jobs without chatId create a new orchestration chat each run."
5636
5694
  });
5637
5695
  } catch (err) {
5638
5696
  return fail5(err);
@@ -5641,7 +5699,7 @@ function registerScheduleTools(server) {
5641
5699
  );
5642
5700
  server.tool(
5643
5701
  "update_schedule",
5644
- "Update a local schedule (prompt, cadence, target thread, enabled). Pass id from list_schedules.",
5702
+ "Update a local schedule (prompt, cadence, target chat, enabled). Pass id from list_schedules.",
5645
5703
  {
5646
5704
  id: z8.string(),
5647
5705
  prompt: z8.string().optional(),
@@ -5650,9 +5708,10 @@ function registerScheduleTools(server) {
5650
5708
  every: z8.string().optional(),
5651
5709
  cron: z8.string().optional(),
5652
5710
  tz: z8.string().optional(),
5653
- threadId: z8.string().optional().describe(
5654
- 'Existing chat (orchestration or worktree), "self", or empty string to create a new Global chat each run'
5711
+ chatId: z8.string().optional().describe(
5712
+ 'Existing chat (orchestration or workspace), "self", or empty string to create a new Global chat each run'
5655
5713
  ),
5714
+ threadId: z8.string().optional().describe("Legacy alias for chatId."),
5656
5715
  agent: z8.enum(["claude", "cursor", "codex", "opencode"]).optional(),
5657
5716
  model: z8.string().optional(),
5658
5717
  enabled: z8.boolean().optional()
@@ -5667,12 +5726,13 @@ function registerScheduleTools(server) {
5667
5726
  tz: args.tz
5668
5727
  }) : void 0;
5669
5728
  let threadId;
5670
- if (args.threadId !== void 0) {
5671
- threadId = resolveScheduleThreadId(args.threadId);
5672
- if (args.threadId.trim().toLowerCase() === "self" && !threadId) {
5729
+ const rawChatId = args.chatId ?? args.threadId;
5730
+ if (rawChatId !== void 0) {
5731
+ threadId = resolveScheduleThreadId(rawChatId);
5732
+ if (rawChatId.trim().toLowerCase() === "self" && !threadId) {
5673
5733
  return fail5(
5674
5734
  new Error(
5675
- "threadId=self needs this chat id (SIDEBOARD_THREAD_ID or SIDEBOARD_ORCHESTRATOR_THREAD_ID)"
5735
+ "chatId=self needs this chat id (SIDEBOARD_THREAD_ID or SIDEBOARD_ORCHESTRATOR_THREAD_ID)"
5676
5736
  )
5677
5737
  );
5678
5738
  }
@@ -5723,12 +5783,163 @@ function registerScheduleTools(server) {
5723
5783
  );
5724
5784
  }
5725
5785
 
5786
+ // src/agent-rpc/handlers.ts
5787
+ import { randomUUID as randomUUID2 } from "crypto";
5788
+ import { isAbsolute } from "path";
5789
+ function presentArtifactResult(input) {
5790
+ const id = input.artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${randomUUID2().slice(0, 8)}`;
5791
+ return {
5792
+ ok: true,
5793
+ artifact_id: id,
5794
+ title: input.title,
5795
+ type: input.type,
5796
+ status: input.status,
5797
+ phase: input.phase,
5798
+ mode: input.type === "log" ? input.mode ?? "append" : void 0,
5799
+ message: input.type === "log" ? "Log accepted. Same artifact_id appends; send only new lines next time." : "Artifact accepted. Sideboard desktop opens it in the side column beside chat."
5800
+ };
5801
+ }
5802
+ function asRecord2(value) {
5803
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
5804
+ return value;
5805
+ }
5806
+ function str(value) {
5807
+ return typeof value === "string" ? value : "";
5808
+ }
5809
+ function invalidParams(message) {
5810
+ return Object.assign(new Error(message), { rpcCode: -32602 });
5811
+ }
5812
+ function jobCwd(rec) {
5813
+ const raw = str(rec.cwd).trim();
5814
+ if (!raw) throw invalidParams("cwd is required");
5815
+ if (!isAbsolute(raw)) throw invalidParams("cwd must be an absolute path");
5816
+ return raw;
5817
+ }
5818
+ async function handleRuntimePing(_params, _ctx) {
5819
+ return {
5820
+ ok: true,
5821
+ protocolVersion: AGENT_RPC_PROTOCOL_VERSION,
5822
+ pid: process.pid
5823
+ };
5824
+ }
5825
+ async function handlePresentArtifact(params, _ctx) {
5826
+ const rec = asRecord2(params);
5827
+ const title = str(rec.title).trim();
5828
+ const type = str(rec.type).trim();
5829
+ const allowed = ["html", "svg", "markdown", "react", "log"];
5830
+ if (!title) throw invalidParams("title is required");
5831
+ if (!allowed.includes(type)) {
5832
+ throw invalidParams("type must be html, svg, markdown, react, or log");
5833
+ }
5834
+ const statusRaw = str(rec.status).trim();
5835
+ const status = ["running", "ok", "failed", "idle"].includes(
5836
+ statusRaw
5837
+ ) ? statusRaw : void 0;
5838
+ const modeRaw = str(rec.mode).trim();
5839
+ const mode = modeRaw === "append" || modeRaw === "replace" ? modeRaw : void 0;
5840
+ return presentArtifactResult({
5841
+ title,
5842
+ type,
5843
+ artifact_id: str(rec.artifact_id) || void 0,
5844
+ status,
5845
+ phase: str(rec.phase) || void 0,
5846
+ mode
5847
+ });
5848
+ }
5849
+ async function handleJobWait(params, ctx, waitFn = waitForDetachedJob) {
5850
+ const rec = asRecord2(params);
5851
+ const id = str(rec.id).trim();
5852
+ if (!id) throw invalidParams("id is required");
5853
+ const cwd = jobCwd(rec);
5854
+ const deadline = Date.now() + AGENT_RPC_JOB_HOLD_MAX_MS;
5855
+ let last;
5856
+ while (Date.now() < deadline) {
5857
+ const slice = Math.min(AGENT_RPC_JOB_SLICE_MS, Math.max(50, deadline - Date.now()));
5858
+ last = await waitFn(cwd, id, { timeoutMs: slice });
5859
+ if (!last.stillRunning) return last;
5860
+ ctx.notify("runtime.progress", last);
5861
+ }
5862
+ return last ?? {
5863
+ stillRunning: true,
5864
+ ok: false,
5865
+ failed: false,
5866
+ status: "running",
5867
+ id,
5868
+ delta: "",
5869
+ progress: "Job still running after max hold. Call wait_for_job again.",
5870
+ hint: "The RPC hold cap was reached. Call wait_for_job again \u2014 do not assume success."
5871
+ };
5872
+ }
5873
+ async function handleJobStop(params, _ctx) {
5874
+ const rec = asRecord2(params);
5875
+ const id = str(rec.id).trim();
5876
+ if (!id) throw invalidParams("id is required");
5877
+ const reason = str(rec.reason).trim() || void 0;
5878
+ return stopDetachedJob(jobCwd(rec), id, reason ? { reason } : void 0);
5879
+ }
5880
+ async function defaultTurnWait(ref, timeoutMs) {
5881
+ const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-5YU46YNS.js");
5882
+ const orch = getOrchestrator2();
5883
+ const thread = await orch.waitForTurn(ref, timeoutMs, {
5884
+ resolveIfStillRunning: true
5885
+ });
5886
+ const result = orch.getTurnResult(thread.id);
5887
+ return waitForTurnToolResult({
5888
+ id: thread.id,
5889
+ status: result.status,
5890
+ taskState: result.taskState,
5891
+ text: result.text,
5892
+ lastError: result.lastError,
5893
+ stillRunning: result.stillRunning,
5894
+ progress: result.progress,
5895
+ lastActivityAt: result.lastActivityAt,
5896
+ usage: result.usage
5897
+ });
5898
+ }
5899
+ async function handleTurnWait(params, ctx, waitFn = defaultTurnWait) {
5900
+ const rec = asRecord2(params);
5901
+ const ref = str(rec.ref).trim();
5902
+ if (!ref) throw invalidParams("ref is required");
5903
+ const requested = typeof rec.timeoutMs === "number" && Number.isFinite(rec.timeoutMs) ? rec.timeoutMs : void 0;
5904
+ const holdMax = requested !== void 0 ? Math.min(Math.max(1e3, Math.floor(requested)), AGENT_RPC_JOB_HOLD_MAX_MS) : AGENT_RPC_JOB_HOLD_MAX_MS;
5905
+ const deadline = Date.now() + holdMax;
5906
+ let last;
5907
+ while (Date.now() < deadline) {
5908
+ const slice = Math.min(AGENT_RPC_JOB_SLICE_MS, Math.max(50, deadline - Date.now()));
5909
+ last = await waitFn(ref, slice);
5910
+ if (!last.stillRunning) return last;
5911
+ ctx.notify("runtime.progress", last);
5912
+ }
5913
+ return last ?? {
5914
+ id: ref,
5915
+ status: "running",
5916
+ taskState: "working",
5917
+ text: "",
5918
+ lastError: null,
5919
+ stillRunning: true,
5920
+ progress: null,
5921
+ lastActivityAt: null,
5922
+ usage: null,
5923
+ hint: "Child is still working after max hold. Call wait_for_turn again. Do not send_to_chat a check-in (that steers / interrupts).",
5924
+ incomplete: false
5925
+ };
5926
+ }
5927
+ function createAgentRpcDispatcher() {
5928
+ return {
5929
+ "runtime.ping": handleRuntimePing,
5930
+ "ui.presentArtifact": handlePresentArtifact,
5931
+ "job.wait": handleJobWait,
5932
+ "job.stop": handleJobStop,
5933
+ "turn.wait": handleTurnWait
5934
+ };
5935
+ }
5936
+
5726
5937
  // src/board/load-home-board.ts
5727
5938
  import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync5, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "fs";
5728
5939
  import { join as join6 } from "path";
5729
5940
 
5730
5941
  // src/board/board-pins.ts
5731
- import { randomUUID as randomUUID2 } from "crypto";
5942
+ import { randomUUID as randomUUID3 } from "crypto";
5732
5943
  import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, unlinkSync as unlinkSync2, writeFileSync } from "fs";
5733
5944
  import { join as join5 } from "path";
5734
5945
  var FILE = "home-board-pins.json";
@@ -5758,7 +5969,7 @@ function addBoardPin(input) {
5758
5969
  const ref = input.ref.trim();
5759
5970
  if (!ref) throw new Error("Board item needs a ref");
5760
5971
  const pin = {
5761
- id: randomUUID2(),
5972
+ id: randomUUID3(),
5762
5973
  kind: input.kind,
5763
5974
  ref,
5764
5975
  repoPath: input.repoPath.trim(),
@@ -6067,7 +6278,7 @@ function withTimeout(promise, ms, label) {
6067
6278
  }
6068
6279
  async function createOrchChildThread(orch, args, resolveNewThreadOptions2) {
6069
6280
  const envParentId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || "";
6070
- let parentId = args.parentThreadId?.trim() || "";
6281
+ let parentId = args.parentChatId?.trim() || args.parentThreadId?.trim() || "";
6071
6282
  let parent = parentId ? orch.getThread(parentId) : null;
6072
6283
  let parentCorrectedFrom;
6073
6284
  if (envParentId) {
@@ -6117,7 +6328,7 @@ async function createOrchChildThread(orch, args, resolveNewThreadOptions2) {
6117
6328
  attachments: args.attachments
6118
6329
  }),
6119
6330
  CREATE_THREAD_TIMEOUT_MS,
6120
- "create_thread"
6331
+ "create_workspace"
6121
6332
  );
6122
6333
  const alreadyStarted = priorIds.has(thread.id);
6123
6334
  const parentNote = parentCorrectedFrom ? parentId ? `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; nested under ${parentId}` : `Ignored unknown/stale parentThreadId ${parentCorrectedFrom}; created without parent` : void 0;
@@ -6132,7 +6343,8 @@ async function createOrchChildThread(orch, args, resolveNewThreadOptions2) {
6132
6343
  model: thread.model,
6133
6344
  status: thread.status,
6134
6345
  cowboy: Boolean(thread.cowboy),
6135
- link: `sideboard://thread/${thread.id}`,
6346
+ link: `sideboard://chat/${thread.id}`,
6347
+ parentChatId: thread.parentThreadId,
6136
6348
  parentThreadId: thread.parentThreadId,
6137
6349
  ...alreadyStarted ? { alreadyStarted: true } : {},
6138
6350
  ...opts.coercedFrom ? { agentCoercedFrom: opts.coercedFrom } : {},
@@ -6143,7 +6355,7 @@ async function createOrchChildThread(orch, args, resolveNewThreadOptions2) {
6143
6355
  };
6144
6356
  } catch (err) {
6145
6357
  const message = err instanceof Error ? err.message : String(err);
6146
- return { ok: false, text: `create_thread failed: ${message}` };
6358
+ return { ok: false, text: `create_workspace failed: ${message}` };
6147
6359
  }
6148
6360
  }
6149
6361
  function previewHint(preview) {
@@ -6160,7 +6372,7 @@ async function startMcpServer() {
6160
6372
  );
6161
6373
  }
6162
6374
  try {
6163
- const { maxConcurrentAgents: maxConcurrentAgents2 } = await import("./app-settings-VQEUCONM.js");
6375
+ const { maxConcurrentAgents: maxConcurrentAgents2 } = await import("./app-settings-RKFFUIFQ.js");
6164
6376
  orch.setMaxConcurrent(maxConcurrentAgents2());
6165
6377
  } catch {
6166
6378
  }
@@ -6193,8 +6405,8 @@ async function startMcpServer() {
6193
6405
  }
6194
6406
  if (!worktreeProfile) {
6195
6407
  server.tool(
6196
- "list_workspaces",
6197
- "List registered Sideboard workspaces (repos). Each line is name, path, github:owner/repo when resolvable, and project context when set \u2014 use path as repoPath for list_board/list_branches/list_prs/list_issues/create_thread. Account / project context (Settings \u2192 Agents / Projects) says which tickets and review PRs belong to the user.",
6408
+ "list_projects",
6409
+ "List registered projects (git repos). Each line is name, path, github:owner/repo when resolvable, and project context when set \u2014 use path as repoPath for list_board/list_branches/list_prs/list_issues/create_workspace. Account / project context (Settings \u2192 Agents / Projects) says which tickets and review PRs belong to the user.",
6198
6410
  {},
6199
6411
  async () => {
6200
6412
  const workspaces = orch.listWorkspaces();
@@ -6213,14 +6425,14 @@ async function startMcpServer() {
6213
6425
  );
6214
6426
  return {
6215
6427
  content: [
6216
- { type: "text", text: lines.join("\n") || "(no workspaces)" }
6428
+ { type: "text", text: lines.join("\n") || "(no projects)" }
6217
6429
  ]
6218
6430
  };
6219
6431
  }
6220
6432
  );
6221
6433
  server.tool(
6222
- "list_threads",
6223
- "List Sideboard threads across all workspaces (one summary line each \u2014 token-frugal). Includes parent id, last message preview, and live progress so you can see worktree children. Each line ends with sideboard://thread/<id> \u2014 use that URL in markdown links so the UI can open the chat.",
6434
+ "list_chats",
6435
+ "List chats across all projects (one summary line each \u2014 token-frugal). Includes parent id, last message preview, and live progress so you can see workspace children. Each line ends with sideboard://chat/<id> \u2014 use that URL in markdown links so the UI can open the chat.",
6224
6436
  {},
6225
6437
  async () => {
6226
6438
  const threads = orch.getThreads(true);
@@ -6232,19 +6444,19 @@ async function startMcpServer() {
6232
6444
  const previewBit = preview ? ` ${preview}` : "";
6233
6445
  const err = t.lastError ? ` error:${t.lastError.replace(/\s+/g, " ").slice(0, 60)}` : "";
6234
6446
  const progress = live?.summary && !isInternalAgentStatusText(live.summary) ? ` ${live.summary}` : "";
6235
- return `${t.id.slice(0, 8)} ${t.status.padEnd(9)} ${t.agent.padEnd(8)} ${repo} ${t.sourceType}:${t.sourceRef} ${t.title}${parent}${previewBit}${err} sideboard://thread/${t.id}${t.devPort ? ` http://localhost:${t.devPort}` : ""}${progress}`;
6447
+ return `${t.id.slice(0, 8)} ${t.status.padEnd(9)} ${t.agent.padEnd(8)} ${repo} ${t.sourceType}:${t.sourceRef} ${t.title}${parent}${previewBit}${err} sideboard://chat/${t.id}${t.devPort ? ` http://localhost:${t.devPort}` : ""}${progress}`;
6236
6448
  });
6237
6449
  return {
6238
- content: [{ type: "text", text: lines.join("\n") || "(no threads)" }]
6450
+ content: [{ type: "text", text: lines.join("\n") || "(no chats)" }]
6239
6451
  };
6240
6452
  }
6241
6453
  );
6242
6454
  server.tool(
6243
6455
  "list_board",
6244
- "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), ownership (mine = your PRs / WIP; reviewing = someone else's PR), 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.",
6456
+ "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), ownership (mine = your PRs / WIP; reviewing = someone else's PR), column, limit (default 40). create_workspace adds a workspace (and a Home card), or returns the live one if that ticket/PR/named branch is already checked out.",
6245
6457
  {
6246
6458
  query: z9.string().optional().describe("Case-insensitive token search across title, id, labels, repo"),
6247
- repoPath: z9.string().optional().describe("Limit to one workspace path from list_workspaces"),
6459
+ repoPath: z9.string().optional().describe("Limit to one project path from list_projects"),
6248
6460
  kind: z9.enum(["all", "tickets", "prs", "branches", "threads"]).optional().describe("Filter by worktree source (default all)"),
6249
6461
  ownership: z9.enum(["all", "mine", "reviewing"]).optional().describe(
6250
6462
  "Mine = PRs you authored or local WIP; reviewing = someone else's PR. Uses the gh login."
@@ -6280,13 +6492,13 @@ async function startMcpServer() {
6280
6492
  }
6281
6493
  );
6282
6494
  server.tool(
6283
- "get_thread",
6284
- "Get a compact thread summary by id/ref. Includes last message preview, parentThreadId, and child worktree threads (status + lastText). While running, includes progress (last tool/thinking) and lastActivityAt. Includes usage (thread billed token + costUsd totals when providers reported cost) and lastTurnUsage.",
6495
+ "get_chat",
6496
+ "Get a compact chat summary by id/ref. Includes last message preview, parentChatId, and child workspace chats (status + lastText). While running, includes progress (last tool/thinking) and lastActivityAt. Includes usage (billed token + costUsd totals when providers reported cost) and lastTurnUsage.",
6285
6497
  { ref: z9.string() },
6286
6498
  async ({ ref }) => {
6287
6499
  const t = orch.getThread(ref);
6288
6500
  if (!t) {
6289
- return { content: [{ type: "text", text: `Thread not found: ${ref}` }], isError: true };
6501
+ return { content: [{ type: "text", text: `Chat not found: ${ref}` }], isError: true };
6290
6502
  }
6291
6503
  const stillRunning = orch.threadLooksLive(t);
6292
6504
  const live = stillRunning ? readTurnLive(t.id) : null;
@@ -6302,6 +6514,7 @@ async function startMcpServer() {
6302
6514
  branchName: t.branchName,
6303
6515
  worktreePath: t.worktreePath,
6304
6516
  sessionId: t.sessionId,
6517
+ parentChatId: t.parentThreadId,
6305
6518
  parentThreadId: t.parentThreadId,
6306
6519
  children: childThreadRefs(t.id, orch.getThreads(false)),
6307
6520
  queueLength: t.queue.length,
@@ -6320,37 +6533,31 @@ async function startMcpServer() {
6320
6533
  }
6321
6534
  );
6322
6535
  }
6323
- server.tool(
6324
- "present_artifact",
6325
- "Show a document or live log in Sideboard\u2019s side column. For html/svg/markdown/react, pass the FULL document and do not also fence that same body in chat. For type=log, pass only NEW lines (same artifact_id appends). Prefer type=log for long-running job output \u2014 do not resend HTML. type=react is a single default-export component (JSX/TSX); only react/react-dom imports.",
6326
- {
6327
- title: z9.string().describe("Short title shown in the artifact pane header"),
6328
- type: z9.enum(["html", "svg", "markdown", "react", "log"]).describe(
6329
- "html/svg/markdown/react replace the pane. log appends content to the same artifact_id (new lines only)."
6330
- ),
6331
- content: z9.string().describe(
6332
- "html/svg/markdown/react: full document. log: only the new lines since the last call (empty is ok for a status-only update)."
6333
- ),
6334
- artifact_id: z9.string().optional().describe("Stable id. Required for type=log so later calls append to the same pane."),
6335
- status: z9.enum(["running", "ok", "failed", "idle"]).optional().describe("Log header pill: running (working), ok (done), failed, idle"),
6336
- phase: z9.string().optional().describe("Log subtitle (Signing, Notarizing, \u2026)"),
6337
- mode: z9.enum(["append", "replace"]).optional().describe("log only: append (default) or replace the buffer")
6338
- },
6339
- async ({ title, type, artifact_id, status, phase, mode }) => {
6340
- const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
6341
- const payload = {
6342
- ok: true,
6343
- artifact_id: id,
6344
- title,
6345
- type,
6346
- status,
6347
- phase,
6348
- mode: type === "log" ? mode ?? "append" : void 0,
6349
- message: type === "log" ? "Log accepted. Same artifact_id appends; send only new lines next time." : "Artifact accepted. Sideboard desktop opens it in the side column beside chat."
6350
- };
6351
- return mcpJson(payload);
6352
- }
6353
- );
6536
+ const mcpCatalog = new Set(worktreeMcpToolNames());
6537
+ if (mcpCatalog.has("present_artifact")) {
6538
+ server.tool(
6539
+ "present_artifact",
6540
+ "Show a document or live log in Sideboard\u2019s side column. For html/svg/markdown/react, pass the FULL document and do not also fence that same body in chat. For type=log, pass only NEW lines (same artifact_id appends). Prefer type=log for long-running job output \u2014 do not resend HTML. type=react is a single default-export component (JSX/TSX); only react/react-dom imports.",
6541
+ {
6542
+ title: z9.string().describe("Short title shown in the artifact pane header"),
6543
+ type: z9.enum(["html", "svg", "markdown", "react", "log"]).describe(
6544
+ "html/svg/markdown/react replace the pane. log appends content to the same artifact_id (new lines only)."
6545
+ ),
6546
+ content: z9.string().describe(
6547
+ "html/svg/markdown/react: full document. log: only the new lines since the last call (empty is ok for a status-only update)."
6548
+ ),
6549
+ artifact_id: z9.string().optional().describe("Stable id. Required for type=log so later calls append to the same pane."),
6550
+ status: z9.enum(["running", "ok", "failed", "idle"]).optional().describe("Log header pill: running (working), ok (done), failed, idle"),
6551
+ phase: z9.string().optional().describe("Log subtitle (Signing, Notarizing, \u2026)"),
6552
+ mode: z9.enum(["append", "replace"]).optional().describe("log only: append (default) or replace the buffer")
6553
+ },
6554
+ async ({ title, type, artifact_id, status, phase, mode }) => {
6555
+ return mcpJson(
6556
+ presentArtifactResult({ title, type, artifact_id, status, phase, mode })
6557
+ );
6558
+ }
6559
+ );
6560
+ }
6354
6561
  server.tool(
6355
6562
  "ask_user",
6356
6563
  "Ask the user a clarifying multiple-choice question in Sideboard\u2019s composer. Call only when work is blocked on choosing among a few concrete options (approach fork, which API, auth vs cookies). Do not call for greetings, check-ins, \u201Chello\u201D, open-ended how-can-I-help, or to invent a menu of possible next tasks \u2014 reply in chat instead. If one option is the obvious default, proceed without asking. Before calling, write a short chat message explaining the decision and what each option means. Include a description on every option. After calling, stop and wait for answers. Not for \u201Cis the plan ready?\u201D.",
@@ -6467,32 +6674,34 @@ async function startMcpServer() {
6467
6674
  return mcpJson(payload);
6468
6675
  }
6469
6676
  );
6470
- server.tool(
6471
- "wait_for_job",
6472
- "Wait on a detached long job (tests, pack, deploy, connector CLI) started with detached-job.cjs. MCP clients kill tools around 60s, so this returns within 45s. Sideboard opens a type=log pane from this result (artifact_id = job id, content = delta). stillRunning is the source of truth \u2014 if true, call wait_for_job again. If the job is hanging, producing no useful output, or doing the wrong thing, call stop_job instead of looping forever. Do not end the turn or tell the user you will let them know later. If false, ok/failed is the result.",
6473
- {
6474
- id: z9.string().describe("Detached job id (same kebab-case id passed to detached-job.cjs start)"),
6475
- timeoutMs: z9.number().optional()
6476
- },
6477
- async ({ id, timeoutMs }) => {
6478
- const result = await waitForDetachedJob(process.cwd(), id, {
6479
- timeoutMs: mcpWaitForJobTimeoutMs(timeoutMs)
6480
- });
6481
- return mcpJson(result);
6482
- }
6483
- );
6484
- server.tool(
6485
- "stop_job",
6486
- "Stop a detached job you started with detached-job.cjs when it is hanging, producing no useful output, buffering forever, or doing the wrong thing (wrong project, infinite watch, huge dump). Do not stop a pack/test/deploy that is clearly making progress. The type=log pane updates to status=failed with the last delta. Then decide the next step.",
6487
- {
6488
- id: z9.string().describe("Detached job id (same kebab-case id passed to detached-job.cjs start)"),
6489
- reason: z9.string().optional().describe("Why you are stopping (hanging, no progress, wrong output, already have the answer)")
6490
- },
6491
- async ({ id, reason }) => {
6492
- const result = await stopDetachedJob(process.cwd(), id, { reason });
6493
- return mcpJson(result);
6494
- }
6495
- );
6677
+ if (mcpCatalog.has("wait_for_job")) {
6678
+ server.tool(
6679
+ "wait_for_job",
6680
+ "Wait on a detached long job (tests, pack, deploy, connector CLI) started with detached-job.cjs. MCP clients kill tools around 60s, so this returns within 45s. Sideboard opens a type=log pane from this result (artifact_id = job id, content = delta). stillRunning is the source of truth \u2014 if true, call wait_for_job again. If the job is hanging, producing no useful output, or doing the wrong thing, call stop_job instead of looping forever. Do not end the turn or tell the user you will let them know later. If false, ok/failed is the result.",
6681
+ {
6682
+ id: z9.string().describe("Detached job id (same kebab-case id passed to detached-job.cjs start)"),
6683
+ timeoutMs: z9.number().optional()
6684
+ },
6685
+ async ({ id, timeoutMs }) => {
6686
+ const result = await waitForDetachedJob(process.cwd(), id, {
6687
+ timeoutMs: mcpWaitForJobTimeoutMs(timeoutMs)
6688
+ });
6689
+ return mcpJson(result);
6690
+ }
6691
+ );
6692
+ server.tool(
6693
+ "stop_job",
6694
+ "Stop a detached job you started with detached-job.cjs when it is hanging, producing no useful output, buffering forever, or doing the wrong thing (wrong project, infinite watch, huge dump). Do not stop a pack/test/deploy that is clearly making progress. The type=log pane updates to status=failed with the last delta. Then decide the next step.",
6695
+ {
6696
+ id: z9.string().describe("Detached job id (same kebab-case id passed to detached-job.cjs start)"),
6697
+ reason: z9.string().optional().describe("Why you are stopping (hanging, no progress, wrong output, already have the answer)")
6698
+ },
6699
+ async ({ id, reason }) => {
6700
+ const result = await stopDetachedJob(process.cwd(), id, { reason });
6701
+ return mcpJson(result);
6702
+ }
6703
+ );
6704
+ }
6496
6705
  server.tool(
6497
6706
  "list_run_scripts",
6498
6707
  "List .sideboard/.conductor run scripts for this thread (same menu as the desktop Dev button) and which are active. Each script and active run includes preview: url (open http://localhost:<SIDEBOARD_PORT>) or window (Electron: native window is the app; url is renderer/HMR only). Never guess :3000. Worktree turns may omit ref (uses cwd).",
@@ -6652,7 +6861,7 @@ async function startMcpServer() {
6652
6861
  registerSlackTools(server);
6653
6862
  registerConnectedIssueVendorTools(server);
6654
6863
  const { getCaffeinateHold: getCaffeinateHold2, setCaffeinateHold: setCaffeinateHold2 } = await import("./caffeinate-hold-DYJGWK6Y.js");
6655
- const { resolveNewThreadOptions: resolveNewThreadOptions2, resolveThreadDefaults: resolveThreadDefaults2 } = await import("./app-settings-VQEUCONM.js");
6864
+ const { resolveNewThreadOptions: resolveNewThreadOptions2, resolveThreadDefaults: resolveThreadDefaults2 } = await import("./app-settings-RKFFUIFQ.js");
6656
6865
  const accountDefaults = resolveThreadDefaults2();
6657
6866
  const accountDefaultsHint = `Account defaults: agent=${accountDefaults.agent}, model=${accountDefaults.model?.trim() || "Auto"}, effort=${accountDefaults.effort}`;
6658
6867
  server.tool(
@@ -6682,8 +6891,8 @@ async function startMcpServer() {
6682
6891
  }
6683
6892
  );
6684
6893
  server.tool(
6685
- "create_thread",
6686
- `Create a worktree thread (chat) from branch, pr, or ticket. A ticket, PR, or named branch may have only one live worktree \u2014 if one already matches, 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. Omit agent/model \u2014 Sideboard applies ${accountDefaultsHint}. Do not pass your own agent or agent=cursor; those are ignored. 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.`,
6894
+ "create_workspace",
6895
+ `Create a workspace (isolated git worktree + first chat) from branch, pr, or ticket. A ticket, PR, or named branch may have only one live worktree \u2014 if one already matches, returns it (alreadyStarted=true) instead of a second checkout. Creating from the default branch still opens a new isolated worktree. Pass repoPath from list_projects. cowboy=true uses the project folder on the default branch (no isolated worktree; land is commit+push). From an orchestration chat, omit parentChatId (Sideboard binds the child to this chat) or pass the exact id from the turn reminder \u2014 never invent a uuid. Omit agent/model \u2014 Sideboard applies ${accountDefaultsHint}. Do not pass your own agent or agent=cursor; those are ignored. 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_chat to chat.`,
6687
6896
  {
6688
6897
  sourceType: z9.enum(["branch", "pr", "ticket"]),
6689
6898
  sourceRef: z9.string(),
@@ -6698,9 +6907,10 @@ async function startMcpServer() {
6698
6907
  cowboy: z9.boolean().optional().describe(
6699
6908
  "If true, work in the project folder on the default branch (no thread/* worktree). Requires Settings \u2192 Advanced \u2192 Cowboy mode. Land is commit+push to that branch. Archive does not delete the folder."
6700
6909
  ),
6701
- parentThreadId: z9.string().optional().describe(
6910
+ parentChatId: z9.string().optional().describe(
6702
6911
  "Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
6703
- )
6912
+ ),
6913
+ parentThreadId: z9.string().optional().describe("Legacy alias for parentChatId.")
6704
6914
  },
6705
6915
  async (args) => {
6706
6916
  const result = await createOrchChildThread(orch, args, resolveNewThreadOptions2);
@@ -6712,11 +6922,11 @@ async function startMcpServer() {
6712
6922
  );
6713
6923
  server.tool(
6714
6924
  "start_board_card",
6715
- "Same as create_thread for a ticket, PR, or named branch (attaches issue text when Sideboard can resolve it). Does not create a second worktree when one already matches \u2014 returns that thread (alreadyStarted). Then send_to_thread.",
6925
+ "Same as create_workspace for a ticket, PR, or named branch (attaches issue text when Sideboard can resolve it). Does not create a second worktree when one already matches \u2014 returns that thread (alreadyStarted). Then send_to_chat.",
6716
6926
  {
6717
6927
  kind: z9.enum(["ticket", "pr", "branch"]),
6718
6928
  ref: z9.string().describe("Ticket identifier (ENG-12), PR number (44), or branch name from list_board"),
6719
- repoPath: z9.string().describe("Workspace path from list_workspaces / list_board"),
6929
+ repoPath: z9.string().describe("Project path from list_projects / list_board"),
6720
6930
  title: z9.string().optional()
6721
6931
  },
6722
6932
  async ({ kind, ref, repoPath, title }) => {
@@ -6739,7 +6949,7 @@ async function startMcpServer() {
6739
6949
  id: existing.id,
6740
6950
  title: existing.title,
6741
6951
  status: existing.status,
6742
- link: `sideboard://thread/${existing.id}`
6952
+ link: `sideboard://chat/${existing.id}`
6743
6953
  });
6744
6954
  }
6745
6955
  const workspaces = orch.listWorkspaces();
@@ -6767,7 +6977,7 @@ async function startMcpServer() {
6767
6977
  const ident = issue?.identifier ?? ref.trim();
6768
6978
  const attachments = issue ? [
6769
6979
  {
6770
- id: randomUUID3(),
6980
+ id: randomUUID4(),
6771
6981
  name: issue.identifier,
6772
6982
  kind: "issue",
6773
6983
  content: [
@@ -6811,8 +7021,8 @@ async function startMcpServer() {
6811
7021
  }
6812
7022
  );
6813
7023
  server.tool(
6814
- "send_to_thread",
6815
- 'Steer a prompt on a worktree thread chat (Settings \u2192 Follow-up, default steer: front of the queue, skips the inbox; while the desktop board is running it starts as soon as the in-flight turn yields). Use after create_thread to start or continue a conversation. For commit/push/PR, prefer ask_git (canonical desktop-button phrases). Send "Merge PR." / ask_git merge only when the user explicitly asked to merge. force_stop=true kills the in-flight turn and clears the queue before this prompt \u2014 only when the current request is wrong and must be replaced. Do not send_to_thread to check in, resume after a halt notice, or because wait_for_turn returned stillRunning; that interrupts the child mid-thought. Call wait_for_turn again instead.',
7024
+ "send_to_chat",
7025
+ 'Steer a prompt on a workspace chat (Settings \u2192 Follow-up, default steer: front of the queue, skips the inbox; while the desktop board is running it starts as soon as the in-flight turn yields). Use after create_workspace to start or continue a conversation. For commit/push/PR, prefer ask_git (canonical desktop-button phrases). Send "Merge PR." / ask_git merge only when the user explicitly asked to merge. force_stop=true kills the in-flight turn and clears the queue before this prompt \u2014 only when the current request is wrong and must be replaced. Do not send_to_chat to check in, resume after a halt notice, or because wait_for_turn returned stillRunning; that interrupts the child mid-thought. Call wait_for_turn again instead.',
6816
7026
  {
6817
7027
  ref: z9.string(),
6818
7028
  prompt: z9.string(),
@@ -6836,36 +7046,38 @@ async function startMcpServer() {
6836
7046
  });
6837
7047
  }
6838
7048
  );
6839
- server.tool(
6840
- "wait_for_turn",
6841
- "Wait until the thread finishes its current/queued turn, or return early with a live progress snapshot. MCP clients often kill tools around 60s, so this returns within 45s even while the child is still working. taskState is the A2A-style lifecycle: submitted (queued, not started), working, input-required (ask_user), completed, failed, canceled. stillRunning is true only for submitted/working. If stillRunning, call wait_for_turn again \u2014 do not send_to_thread a check-in (that steers / interrupts). On failed, lastError/text is the failure. On canceled, the child did not finish \u2014 resume with send_to_thread or tell the user. On input-required, wait for the user in that chat. When finished, usage is the last agent turn\u2019s tokens + costUsd (when the provider reported cost).",
6842
- {
6843
- ref: z9.string(),
6844
- timeoutMs: z9.number().optional()
6845
- },
6846
- async ({ ref, timeoutMs }) => {
6847
- const thread = await orch.waitForTurn(ref, mcpWaitForTurnTimeoutMs(timeoutMs), {
6848
- resolveIfStillRunning: true
6849
- });
6850
- const result = orch.getTurnResult(thread.id);
6851
- const hint = mcpWaitTaskHint(result.taskState, result.status);
6852
- return mcpJson({
6853
- id: thread.id,
6854
- status: result.status,
6855
- taskState: result.taskState,
6856
- text: result.text,
6857
- lastError: result.lastError,
6858
- stillRunning: result.stillRunning,
6859
- progress: result.progress,
6860
- lastActivityAt: result.lastActivityAt,
6861
- hint,
6862
- incomplete: needsCoordinatorAction(result.taskState)
6863
- });
6864
- }
6865
- );
7049
+ if (shouldRegisterMcpWaitForTurn()) {
7050
+ server.tool(
7051
+ "wait_for_turn",
7052
+ "Wait until the chat finishes its current/queued turn, or return early with a live progress snapshot. MCP clients often kill tools around 60s, so this returns within 45s even while the child is still working. taskState is the A2A-style lifecycle: submitted (queued, not started), working, input-required (ask_user), completed, failed, canceled. stillRunning is true only for submitted/working. If stillRunning, text and usage are empty (they would be the previous turn) \u2014 read progress and call wait_for_turn again. Do not send_to_chat a check-in (that steers / interrupts). On failed, lastError/text is the failure. On canceled, the child did not finish \u2014 resume with send_to_chat or tell the user. On input-required, wait for the user in that chat. When finished, text is this turn\u2019s assistant reply and usage is that turn\u2019s tokens + costUsd (this turn, not the Claude session total; sessionCostUsd is the provider session total when present).",
7053
+ {
7054
+ ref: z9.string(),
7055
+ timeoutMs: z9.number().optional()
7056
+ },
7057
+ async ({ ref, timeoutMs }) => {
7058
+ const thread = await orch.waitForTurn(ref, mcpWaitForTurnTimeoutMs(timeoutMs), {
7059
+ resolveIfStillRunning: true
7060
+ });
7061
+ const result = orch.getTurnResult(thread.id);
7062
+ return mcpJson(
7063
+ waitForTurnToolResult({
7064
+ id: thread.id,
7065
+ status: result.status,
7066
+ taskState: result.taskState,
7067
+ text: result.text,
7068
+ lastError: result.lastError,
7069
+ stillRunning: result.stillRunning,
7070
+ progress: result.progress,
7071
+ lastActivityAt: result.lastActivityAt,
7072
+ usage: result.usage
7073
+ })
7074
+ );
7075
+ }
7076
+ );
7077
+ }
6866
7078
  server.tool(
6867
7079
  "get_turn_result",
6868
- "Assistant message when the turn finished, or live progress while stillRunning. Not the full transcript. Includes taskState (A2A lifecycle) and usage for the last agent turn (tokens + costUsd when reported).",
7080
+ "Assistant message and last-turn usage when the turn finished. While stillRunning (submitted/working), text and usage are empty so a previous reply cannot be mistaken for this turn \u2014 use progress for tools/thinking. Not the full transcript. Includes taskState (A2A lifecycle).",
6869
7081
  { ref: z9.string() },
6870
7082
  async ({ ref }) => {
6871
7083
  const result = orch.getTurnResult(ref);
@@ -6878,7 +7090,7 @@ async function startMcpServer() {
6878
7090
  }
6879
7091
  );
6880
7092
  server.tool(
6881
- "stop_thread",
7093
+ "stop_chat",
6882
7094
  "Force-stop a thread: kill any in-flight agent turn AND clear queued prompts so drainQueue cannot continue. Does not archive the worktree. Optional force defaults to true.",
6883
7095
  {
6884
7096
  ref: z9.string(),
@@ -6888,7 +7100,7 @@ async function startMcpServer() {
6888
7100
  const t = orch.getThread(ref);
6889
7101
  if (!t) {
6890
7102
  return {
6891
- content: [{ type: "text", text: `Thread not found: ${ref}` }],
7103
+ content: [{ type: "text", text: `Chat not found: ${ref}` }],
6892
7104
  isError: true
6893
7105
  };
6894
7106
  }
@@ -6903,14 +7115,14 @@ async function startMcpServer() {
6903
7115
  }
6904
7116
  );
6905
7117
  server.tool(
6906
- "archive_thread",
7118
+ "archive_chat",
6907
7119
  "Archive a thread (stops agent/dev, runs archive script, removes worktree when last chat tab). Coordinators commit, push, and open PRs by asking the worktree agent (ask_git). Merge only when the user explicitly asked.",
6908
7120
  { ref: z9.string() },
6909
7121
  async ({ ref }) => {
6910
7122
  const t = orch.getThread(ref);
6911
7123
  if (!t) {
6912
7124
  return {
6913
- content: [{ type: "text", text: `Thread not found: ${ref}` }],
7125
+ content: [{ type: "text", text: `Chat not found: ${ref}` }],
6914
7126
  isError: true
6915
7127
  };
6916
7128
  }
@@ -6929,7 +7141,7 @@ async function startMcpServer() {
6929
7141
  }
6930
7142
  );
6931
7143
  server.tool(
6932
- "restore_thread",
7144
+ "restore_chat",
6933
7145
  "Restore an archived thread (recreates worktree from branch when needed)",
6934
7146
  { ref: z9.string() },
6935
7147
  async ({ ref }) => {
@@ -6988,7 +7200,7 @@ async function startMcpServer() {
6988
7200
  title: tab.title,
6989
7201
  status: tab.status,
6990
7202
  fromThreadId: from?.id ?? ref,
6991
- link: `sideboard://thread/${tab.id}`
7203
+ link: `sideboard://chat/${tab.id}`
6992
7204
  });
6993
7205
  } catch (err) {
6994
7206
  const message = err instanceof Error ? err.message : String(err);
@@ -7013,7 +7225,7 @@ async function startMcpServer() {
7013
7225
  status: thread.status,
7014
7226
  queueLength: thread.queue.length,
7015
7227
  action,
7016
- link: `sideboard://thread/${thread.id}`
7228
+ link: `sideboard://chat/${thread.id}`
7017
7229
  });
7018
7230
  } catch (err) {
7019
7231
  const raw = err instanceof Error ? err.message : String(err);
@@ -7023,7 +7235,7 @@ async function startMcpServer() {
7023
7235
  error: true,
7024
7236
  action,
7025
7237
  lastError: message,
7026
- hint: /body is too long/i.test(message) ? "Branch is already pushed. send_to_thread so the worktree agent runs gh pr create --draft --assignee @me -R <origin> --body-file <short.md>. Keep the description short." : void 0
7238
+ hint: /body is too long/i.test(message) ? "Branch is already pushed. send_to_chat so the worktree agent runs gh pr create --draft --assignee @me -R <origin> --body-file <short.md>. Keep the description short." : void 0
7027
7239
  },
7028
7240
  true
7029
7241
  );
@@ -7048,8 +7260,8 @@ async function startMcpServer() {
7048
7260
  }
7049
7261
  );
7050
7262
  server.tool(
7051
- "fork_worktree",
7052
- "Fork a worktree agent chat into a NEW git worktree + chat (desktop \u201CFork to new workspace\u201D). Seeds a transcript (through through_index, default all). Optional agent override. Leave model unset for Auto (default) \u2014 only pass model when you have a reason. Not for the orchestrator. Then send_to_thread / wait_for_turn (loop while stillRunning) on the returned id.",
7263
+ "fork_workspace",
7264
+ "Fork a worktree agent chat into a NEW git worktree + chat (desktop \u201CFork to new workspace\u201D). Seeds a transcript (through through_index, default all). Optional agent override. Leave model unset for Auto (default) \u2014 only pass model when you have a reason. Not for the orchestrator. Then send_to_chat / wait_for_turn (loop while stillRunning) on the returned id.",
7053
7265
  {
7054
7266
  ref: z9.string().describe("Worktree thread id/ref to fork"),
7055
7267
  through_index: z9.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
@@ -7077,7 +7289,7 @@ async function startMcpServer() {
7077
7289
  branchName: thread.branchName,
7078
7290
  worktreePath: thread.worktreePath,
7079
7291
  fromThreadId: source?.id ?? ref,
7080
- link: `sideboard://thread/${thread.id}`
7292
+ link: `sideboard://chat/${thread.id}`
7081
7293
  });
7082
7294
  } catch (err) {
7083
7295
  const message = err instanceof Error ? err.message : String(err);
@@ -7087,7 +7299,7 @@ async function startMcpServer() {
7087
7299
  );
7088
7300
  server.tool(
7089
7301
  "fork_chat",
7090
- "Fork a chat into a NEW tab on the SAME workspace: worktree agent \u2192 same worktree tab; Global orchestration chat \u2192 new orchestration chat (same synthetic home). Seeds a transcript; optional agent override. Leave model unset for Auto unless you have a reason. Orchestration forks require an MCP-capable agent (claude, cursor, codex, opencode \u2014 not brightsy). Slack / Global orchestrators use this to continue an orchestration chat on another agent after session limits. Then send_to_thread / wait_for_turn (loop while stillRunning) on the returned id. Use fork_worktree only for worktree agents that need a new git worktree.",
7302
+ "Fork a chat into a NEW tab on the SAME workspace: worktree agent \u2192 same worktree tab; Global orchestration chat \u2192 new orchestration chat (same synthetic home). Seeds a transcript; optional agent override. Leave model unset for Auto unless you have a reason. Orchestration forks require an MCP-capable agent (claude, cursor, codex, opencode \u2014 not brightsy). Slack / Global orchestrators use this to continue an orchestration chat on another agent after session limits. Then send_to_chat / wait_for_turn (loop while stillRunning) on the returned id. Use fork_workspace only for worktree agents that need a new git worktree.",
7091
7303
  {
7092
7304
  ref: z9.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
7093
7305
  through_index: z9.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
@@ -7100,7 +7312,7 @@ async function startMcpServer() {
7100
7312
  const source = orch.getThread(ref);
7101
7313
  if (!source) {
7102
7314
  return {
7103
- content: [{ type: "text", text: `Thread not found: ${ref}` }],
7315
+ content: [{ type: "text", text: `Chat not found: ${ref}` }],
7104
7316
  isError: true
7105
7317
  };
7106
7318
  }
@@ -7120,7 +7332,7 @@ async function startMcpServer() {
7120
7332
  sourceType: tab.sourceType,
7121
7333
  worktreePath: tab.worktreePath,
7122
7334
  fromThreadId: source.id,
7123
- link: `sideboard://thread/${tab.id}`
7335
+ link: `sideboard://chat/${tab.id}`
7124
7336
  });
7125
7337
  } catch (err) {
7126
7338
  const message = err instanceof Error ? err.message : String(err);
@@ -7138,7 +7350,7 @@ async function startMcpServer() {
7138
7350
  }
7139
7351
  );
7140
7352
  server.tool(
7141
- "add_workspace",
7353
+ "add_project",
7142
7354
  "Register a git repo as a Sideboard workspace",
7143
7355
  { repoPath: z9.string() },
7144
7356
  async ({ repoPath }) => {
@@ -7147,7 +7359,7 @@ async function startMcpServer() {
7147
7359
  }
7148
7360
  );
7149
7361
  server.tool(
7150
- "remove_workspace",
7362
+ "remove_project",
7151
7363
  "Unregister a Sideboard workspace (does not archive threads)",
7152
7364
  { repoPath: z9.string() },
7153
7365
  async ({ repoPath }) => {
@@ -7180,7 +7392,7 @@ async function startMcpServer() {
7180
7392
  );
7181
7393
  server.tool(
7182
7394
  "list_branches",
7183
- "List git branches in a registered workspace. Pass repoPath from list_workspaces (unmerged into the default branch by default \u2014 for create_thread sourceType=branch).",
7395
+ "List git branches in a registered workspace. Pass repoPath from list_projects (unmerged into the default branch by default \u2014 for create_workspace sourceType=branch).",
7184
7396
  {
7185
7397
  repoPath: z9.string(),
7186
7398
  unmergedOnly: z9.boolean().optional()
@@ -7202,7 +7414,7 @@ async function startMcpServer() {
7202
7414
  );
7203
7415
  server.tool(
7204
7416
  "list_prs",
7205
- `List GitHub PRs for a registered workspace (the review surface for assigned ticket work \u2014 not the tickets). Pass repoPath from list_workspaces. "Get me N tickets to review" \u2192 queue=review and limit=N: open non-draft PRs labeled with this project's ready-for-review GitHub label (Settings \u2192 Projects; default eng-review) with no individual user reviewer. Prefer teams that match Settings \u2192 Agents / Projects roles for this repo. A team like engineering-team is not a claim (you are on that team). Also: state (open|closed|merged|all|review), label, reviewer (me|unassigned|login), query, limit (default 40, max 250). Then create_thread with sourceType=pr.`,
7417
+ `List GitHub PRs for a registered workspace (the review surface for assigned ticket work \u2014 not the tickets). Pass repoPath from list_projects. "Get me N tickets to review" \u2192 queue=review and limit=N: open non-draft PRs labeled with this project's ready-for-review GitHub label (Settings \u2192 Projects; default eng-review) with no individual user reviewer. Prefer teams that match Settings \u2192 Agents / Projects roles for this repo. A team like engineering-team is not a claim (you are on that team). Also: state (open|closed|merged|all|review), label, reviewer (me|unassigned|login), query, limit (default 40, max 250). Then create_workspace with sourceType=pr.`,
7206
7418
  {
7207
7419
  repoPath: z9.string(),
7208
7420
  query: z9.string().optional().describe("GitHub search tokens (title, draft:true, \u2026)"),
@@ -7274,7 +7486,7 @@ async function startMcpServer() {
7274
7486
  stackLayer: t.stackLayer,
7275
7487
  worktreePath: t.worktreePath,
7276
7488
  prUrl: t.prUrl,
7277
- link: `sideboard://thread/${t.id}`
7489
+ link: `sideboard://chat/${t.id}`
7278
7490
  }))
7279
7491
  });
7280
7492
  }
@@ -7295,7 +7507,7 @@ async function startMcpServer() {
7295
7507
  branchName: result.thread.branchName,
7296
7508
  stackLayer: result.thread.stackLayer,
7297
7509
  worktreePath: result.thread.worktreePath,
7298
- link: `sideboard://thread/${result.thread.id}`
7510
+ link: `sideboard://chat/${result.thread.id}`
7299
7511
  });
7300
7512
  }
7301
7513
  );
@@ -7326,14 +7538,14 @@ async function startMcpServer() {
7326
7538
  branchName: t.branchName,
7327
7539
  stackLayer: t.stackLayer,
7328
7540
  worktreePath: t.worktreePath,
7329
- link: `sideboard://thread/${t.id}`
7541
+ link: `sideboard://chat/${t.id}`
7330
7542
  }))
7331
7543
  });
7332
7544
  }
7333
7545
  );
7334
7546
  server.tool(
7335
7547
  "list_issues",
7336
- "List or search issues (Linear, AbleTime, or GitHub; falls back to GitHub). Use Settings \u2192 Agents / Projects notes and roles to pick tickets relevant to the viewer (query + assignee=me or unassigned as the notes say). Default 40; pass query and/or limit (max 250) when truncated. assignee: me (Linear default), unassigned, all, or a user id. Pass updatedSince for new/updated tickets and new comments (\u201Cany updates since yesterday?\u201D). Then create_thread with sourceType=ticket.",
7548
+ "List or search issues (Linear, AbleTime, or GitHub; falls back to GitHub). Use Settings \u2192 Agents / Projects notes and roles to pick tickets relevant to the viewer (query + assignee=me or unassigned as the notes say). Default 40; pass query and/or limit (max 250) when truncated. assignee: me (Linear default), unassigned, all, or a user id. Pass updatedSince for new/updated tickets and new comments (\u201Cany updates since yesterday?\u201D). Then create_workspace with sourceType=ticket.",
7337
7549
  {
7338
7550
  repoPath: z9.string(),
7339
7551
  query: z9.string().optional().describe("Search title, identifier, or description"),
@@ -7358,6 +7570,184 @@ async function startMcpServer() {
7358
7570
  await server.connect(transport);
7359
7571
  }
7360
7572
 
7573
+ // src/agent-rpc/server.ts
7574
+ import { randomBytes as randomBytes2, randomUUID as randomUUID5, timingSafeEqual } from "crypto";
7575
+ import { createServer as createServer2 } from "http";
7576
+ import { WebSocketServer } from "ws";
7577
+ function tokensEqual(a, b) {
7578
+ const left = Buffer.from(a);
7579
+ const right = Buffer.from(b);
7580
+ if (left.length !== right.length) return false;
7581
+ return timingSafeEqual(left, right);
7582
+ }
7583
+ function authTokenFromUpgrade(req) {
7584
+ try {
7585
+ const url = new URL(req.url ?? "", "http://127.0.0.1");
7586
+ return url.searchParams.get(AGENT_RPC_TOKEN_QUERY) ?? "";
7587
+ } catch {
7588
+ return "";
7589
+ }
7590
+ }
7591
+ function sendJson(ws, value) {
7592
+ if (ws.readyState !== ws.OPEN) return;
7593
+ ws.send(JSON.stringify(value));
7594
+ }
7595
+ var AgentRpcServer = class {
7596
+ http = null;
7597
+ wss = null;
7598
+ meta = null;
7599
+ handlers;
7600
+ cwd;
7601
+ constructor(opts = {}) {
7602
+ this.handlers = opts.handlers ?? createAgentRpcDispatcher();
7603
+ this.cwd = opts.cwd ?? (() => process.cwd());
7604
+ }
7605
+ get metadata() {
7606
+ return this.meta;
7607
+ }
7608
+ get url() {
7609
+ return this.meta?.transports[0]?.url ?? null;
7610
+ }
7611
+ async listen(opts = {}) {
7612
+ const host = opts.host ?? "127.0.0.1";
7613
+ const port = opts.port ?? 0;
7614
+ const authToken = randomBytes2(24).toString("base64url");
7615
+ this.meta = {
7616
+ runtimeId: randomUUID5(),
7617
+ pid: process.pid,
7618
+ protocolVersion: AGENT_RPC_PROTOCOL_VERSION,
7619
+ authToken,
7620
+ startedAt: Date.now(),
7621
+ transports: []
7622
+ };
7623
+ const http = createServer2((_req, res) => {
7624
+ res.statusCode = 404;
7625
+ res.end();
7626
+ });
7627
+ const wss = new WebSocketServer({
7628
+ server: http,
7629
+ path: AGENT_RPC_PATH,
7630
+ verifyClient: (info) => this.authorizeUpgrade(info.req)
7631
+ });
7632
+ wss.on("connection", (ws) => this.attach(ws));
7633
+ try {
7634
+ await new Promise((resolve, reject) => {
7635
+ http.once("error", reject);
7636
+ http.listen(port, host, () => {
7637
+ http.off("error", reject);
7638
+ resolve();
7639
+ });
7640
+ });
7641
+ } catch (err) {
7642
+ this.meta = null;
7643
+ throw err;
7644
+ }
7645
+ const addr = http.address();
7646
+ if (!addr || typeof addr === "string") {
7647
+ this.meta = null;
7648
+ http.close();
7649
+ throw new Error("Agent RPC failed to bind a TCP port");
7650
+ }
7651
+ this.http = http;
7652
+ this.wss = wss;
7653
+ const url = `ws://${host}:${addr.port}${AGENT_RPC_PATH}`;
7654
+ this.meta = {
7655
+ ...this.meta,
7656
+ transports: [{ kind: "websocket", url }]
7657
+ };
7658
+ writeAgentRuntimeMetadata(this.meta);
7659
+ return this.meta;
7660
+ }
7661
+ authorizeUpgrade(req) {
7662
+ const expected = this.meta?.authToken ?? "";
7663
+ const got = authTokenFromUpgrade(req);
7664
+ return Boolean(expected && got && tokensEqual(got, expected));
7665
+ }
7666
+ attach(ws) {
7667
+ ws.on("message", (data) => {
7668
+ void this.onMessage(ws, data.toString());
7669
+ });
7670
+ ws.on("error", () => {
7671
+ });
7672
+ }
7673
+ async onMessage(ws, raw) {
7674
+ const parsed = parseRpcFrame(raw);
7675
+ if (parsed === void 0) {
7676
+ sendJson(ws, rpcError(null, RPC_PARSE_ERROR, "Parse error"));
7677
+ return;
7678
+ }
7679
+ if (!isJsonRpcRequest(parsed)) {
7680
+ sendJson(ws, rpcError(null, RPC_INVALID_REQUEST, "Invalid request"));
7681
+ return;
7682
+ }
7683
+ const id = parsed.id;
7684
+ const expected = this.meta?.authToken ?? "";
7685
+ const got = typeof parsed.authToken === "string" ? parsed.authToken : "";
7686
+ if (!expected || !got || !tokensEqual(got, expected)) {
7687
+ sendJson(ws, rpcError(id, RPC_UNAUTHORIZED, "Unauthorized"));
7688
+ return;
7689
+ }
7690
+ const handler = this.handlers[parsed.method];
7691
+ if (!handler) {
7692
+ sendJson(ws, rpcError(id, RPC_METHOD_NOT_FOUND, `Method not found: ${parsed.method}`));
7693
+ return;
7694
+ }
7695
+ const ctx = {
7696
+ cwd: this.cwd(),
7697
+ notify: (method, params) => sendJson(ws, rpcNotify(method, params))
7698
+ };
7699
+ try {
7700
+ const result = await handler(parsed.params, ctx);
7701
+ sendJson(ws, rpcResult(id, result));
7702
+ } catch (err) {
7703
+ const message = err instanceof Error ? err.message : String(err);
7704
+ const code = err && typeof err === "object" && "rpcCode" in err && typeof err.rpcCode === "number" ? err.rpcCode : RPC_INTERNAL_ERROR;
7705
+ sendJson(ws, rpcError(id, code, message));
7706
+ }
7707
+ }
7708
+ async close() {
7709
+ clearAgentRuntimeMetadataIfOwned();
7710
+ this.meta = null;
7711
+ const wss = this.wss;
7712
+ this.wss = null;
7713
+ if (wss) {
7714
+ for (const client of wss.clients) {
7715
+ try {
7716
+ client.close();
7717
+ } catch {
7718
+ }
7719
+ }
7720
+ await new Promise((resolve) => wss.close(() => resolve()));
7721
+ }
7722
+ const http = this.http;
7723
+ this.http = null;
7724
+ if (http) {
7725
+ await new Promise((resolve) => http.close(() => resolve()));
7726
+ }
7727
+ }
7728
+ };
7729
+ var running = null;
7730
+ async function startAgentRpcServer(opts = {}) {
7731
+ if (running) return running;
7732
+ const server = new AgentRpcServer(opts);
7733
+ try {
7734
+ await server.listen({ host: opts.host, port: opts.port });
7735
+ } catch (err) {
7736
+ await server.close().catch(() => void 0);
7737
+ throw err;
7738
+ }
7739
+ running = server;
7740
+ return server;
7741
+ }
7742
+ async function stopAgentRpcServer() {
7743
+ const server = running;
7744
+ running = null;
7745
+ if (server) await server.close();
7746
+ }
7747
+ function getAgentRpcServer() {
7748
+ return running;
7749
+ }
7750
+
7361
7751
  // src/brightsy/api.ts
7362
7752
  var formatBrightsyFetchError = formatFetchError;
7363
7753
  var BrightsySideboardApi = class {
@@ -7636,14 +8026,14 @@ async function runCloudConnect(opts) {
7636
8026
  while (!opts.signal?.aborted) {
7637
8027
  try {
7638
8028
  const hadFailures = consecutivePollFailures > 0;
7639
- const [pending, awaiting, running] = await Promise.all([
8029
+ const [pending, awaiting, running2] = await Promise.all([
7640
8030
  api.getTasks("pending"),
7641
8031
  api.getTasks("awaiting_confirmation"),
7642
8032
  api.getTasks("running")
7643
8033
  ]);
7644
8034
  consecutivePollFailures = 0;
7645
8035
  if (hadFailures) log("poll recovered");
7646
- const tasks = [...awaiting, ...pending, ...running];
8036
+ const tasks = [...awaiting, ...pending, ...running2];
7647
8037
  for (const task of tasks) {
7648
8038
  if (inFlight.has(task.id)) continue;
7649
8039
  if (task.task_status === "running") continue;
@@ -7689,7 +8079,7 @@ async function runCloudConnect(opts) {
7689
8079
  }
7690
8080
 
7691
8081
  // src/slack/oauth.ts
7692
- import { randomBytes as randomBytes2 } from "crypto";
8082
+ import { randomBytes as randomBytes3 } from "crypto";
7693
8083
 
7694
8084
  // src/slack/baked-app.ts
7695
8085
  var BAKED_SLACK_CLIENT_ID = "7592788819232.11813536272562";
@@ -7911,7 +8301,7 @@ async function startSlackOAuth(opts) {
7911
8301
  throw new SlackOAuthCancelledError();
7912
8302
  }
7913
8303
  const { clientId } = slackOAuthCredentials();
7914
- const state = randomBytes2(16).toString("hex");
8304
+ const state = randomBytes3(16).toString("hex");
7915
8305
  const authorizeUrl = slackOAuthAuthorizeUrl(clientId, state);
7916
8306
  const timeoutMs = opts?.timeoutMs ?? 5 * 6e4;
7917
8307
  const pollIntervalMs = opts?.pollIntervalMs ?? 400;
@@ -8178,7 +8568,7 @@ function connectSession(ws, opts, log) {
8178
8568
  }
8179
8569
 
8180
8570
  // src/slack/relay-protocol.ts
8181
- import { randomBytes as randomBytes3 } from "crypto";
8571
+ import { randomBytes as randomBytes4 } from "crypto";
8182
8572
  var SLACK_RELAY_PING_INTERVAL_MS = 2e4;
8183
8573
  var SLACK_RELAY_PONG_TIMEOUT_MS = 15e3;
8184
8574
  function slackRelaySessionKey(teamId, userId, deviceId) {
@@ -8188,7 +8578,7 @@ function slackRelayUserKey(teamId, userId) {
8188
8578
  return `${teamId.trim()}:${userId.trim()}`;
8189
8579
  }
8190
8580
  function newSlackRelayEventId() {
8191
- return randomBytes3(12).toString("hex");
8581
+ return randomBytes4(12).toString("hex");
8192
8582
  }
8193
8583
  function parseSlackRelayClientMessage(raw) {
8194
8584
  try {
@@ -9317,8 +9707,8 @@ var SlackRelayHub = class {
9317
9707
  };
9318
9708
 
9319
9709
  // src/slack/relay-server.ts
9320
- import { createServer as createServer2 } from "http";
9321
- import { WebSocketServer } from "ws";
9710
+ import { createServer as createServer3 } from "http";
9711
+ import { WebSocketServer as WebSocketServer2 } from "ws";
9322
9712
 
9323
9713
  // src/slack/relay-static.ts
9324
9714
  import { createReadStream } from "fs";
@@ -9400,7 +9790,7 @@ function sendHtml(res, status, title, body) {
9400
9790
  res.writeHead(status, { "Content-Type": "text/html; charset=utf-8" });
9401
9791
  res.end(slackOAuthHtmlPage(title, body));
9402
9792
  }
9403
- function sendJson(res, status, body) {
9793
+ function sendJson2(res, status, body) {
9404
9794
  res.writeHead(status, { "Content-Type": "application/json" });
9405
9795
  res.end(JSON.stringify(body));
9406
9796
  }
@@ -9479,17 +9869,17 @@ async function startSlackRelayServer(opts) {
9479
9869
  const state = url2.searchParams.get("state")?.trim() ?? "";
9480
9870
  const value = pending.take(state);
9481
9871
  if (!value) {
9482
- sendJson(res, 404, { ok: false, error: "pending" });
9872
+ sendJson2(res, 404, { ok: false, error: "pending" });
9483
9873
  return true;
9484
9874
  }
9485
9875
  if (!value.ok) {
9486
- sendJson(res, 400, { ok: false, error: value.error });
9876
+ sendJson2(res, 400, { ok: false, error: value.error });
9487
9877
  return true;
9488
9878
  }
9489
- sendJson(res, 200, { ok: true, ...value.payload });
9879
+ sendJson2(res, 200, { ok: true, ...value.payload });
9490
9880
  return true;
9491
9881
  };
9492
- const httpServer = createServer2((req, res) => {
9882
+ const httpServer = createServer3((req, res) => {
9493
9883
  void (async () => {
9494
9884
  const reqUrl = req.url || "/";
9495
9885
  if (await handleCallback(reqUrl, res)) return;
@@ -9505,7 +9895,7 @@ async function startSlackRelayServer(opts) {
9505
9895
  return;
9506
9896
  }
9507
9897
  if (req.url === "/health") {
9508
- sendJson(res, 200, {
9898
+ sendJson2(res, 200, {
9509
9899
  ok: true,
9510
9900
  service: "sideboard-slack-relay",
9511
9901
  sessions: hub.listSessions().length,
@@ -9524,7 +9914,7 @@ async function startSlackRelayServer(opts) {
9524
9914
  return;
9525
9915
  }
9526
9916
  if (req.url === "/") {
9527
- sendJson(res, 200, {
9917
+ sendJson2(res, 200, {
9528
9918
  ok: true,
9529
9919
  service: "sideboard-slack-relay",
9530
9920
  sessions: hub.listSessions().length,
@@ -9542,7 +9932,7 @@ async function startSlackRelayServer(opts) {
9542
9932
  }
9543
9933
  });
9544
9934
  });
9545
- const wss = new WebSocketServer({ server: httpServer, path: SLACK_RELAY_DESKTOP_PATH });
9935
+ const wss = new WebSocketServer2({ server: httpServer, path: SLACK_RELAY_DESKTOP_PATH });
9546
9936
  wss.on("connection", (ws, _req) => {
9547
9937
  const socket = {
9548
9938
  send: (data) => {
@@ -9646,11 +10036,14 @@ export {
9646
10036
  AGENT_DONE_CUSTOM_SOUND_MAX_BYTES,
9647
10037
  AGENT_DONE_SOUNDS,
9648
10038
  AGENT_GIT_ACTIONS,
10039
+ AGENT_RPC_NATIVE_ENV,
10040
+ AGENT_RPC_PROTOCOL_VERSION,
9649
10041
  AGENT_RUNNER_MAX_OLD_SPACE_MB,
9650
10042
  ATTACHMENTS_DIR,
9651
10043
  AbleTimeOAuthCancelledError,
9652
10044
  BAKED_ABLETIME_OAUTH_CLIENT_ID,
9653
10045
  BAKED_SLACK_RELAY_URL,
10046
+ BASE_ALLOWED_TOOLS,
9654
10047
  BRIGHTSY_MCP_ALLOWED_TOOLS,
9655
10048
  BRIGHTSY_OAUTH_CLIENT_ID,
9656
10049
  BRIGHTSY_OAUTH_REDIRECT,
@@ -9659,9 +10052,12 @@ export {
9659
10052
  BUNDLED_SKILL_PREFIX,
9660
10053
  BrightsySideboardApi,
9661
10054
  CHARS_PER_CONTEXT_TOKEN,
10055
+ CHAT_LINK_PREFIX,
9662
10056
  CHAT_TEXT_SCALES,
9663
10057
  CHAT_TEXT_SCALE_DEFAULT,
10058
+ CLAUDE_CHROME_ALLOWED_TOOLS,
9664
10059
  CLAUDE_MODEL_CATALOG,
10060
+ CLAUDE_PRINT_BG_WAIT_CEILING_MS,
9665
10061
  CLAUDE_SUMMARY_TIMEOUT_MS,
9666
10062
  CLI_DIR,
9667
10063
  CLOUD_COORDINATOR_BUSY_REPLY,
@@ -9703,6 +10099,7 @@ export {
9703
10099
  LEGACY_DETACHED_JOBS_DIR,
9704
10100
  LEGACY_PLAN_FILE_REL,
9705
10101
  LEGACY_REVIEW_REQUEST_PATH,
10102
+ LEGACY_THREAD_LINK_PREFIX,
9706
10103
  LIGHT_RECONCILE_THROTTLE_MS,
9707
10104
  LINEAR_OAUTH_CANCELLED,
9708
10105
  LINEAR_OAUTH_PORT,
@@ -9791,6 +10188,7 @@ export {
9791
10188
  agentDoneSound,
9792
10189
  agentDoneSoundsDir,
9793
10190
  agentGitPrompt,
10191
+ agentRpcConnectFromMetadata,
9794
10192
  alignQueueAttachments,
9795
10193
  allAdapters,
9796
10194
  allocatePort,
@@ -9859,6 +10257,7 @@ export {
9859
10257
  canonicalizeRepoPath,
9860
10258
  captureLoginEnv,
9861
10259
  captureTurnBaseline,
10260
+ chatLinkUrl,
9862
10261
  chatTextScale,
9863
10262
  checkoutPrStackLayer,
9864
10263
  childEnvWithAppSettings,
@@ -10069,6 +10468,7 @@ export {
10069
10468
  getAbleTimeOrientation,
10070
10469
  getAbleTimeTask,
10071
10470
  getAdapter,
10471
+ getAgentRpcServer,
10072
10472
  getAgentSetupInfo,
10073
10473
  getBrightsySession,
10074
10474
  getCaffeinateHold,
@@ -10233,6 +10633,7 @@ export {
10233
10633
  linearOAuthCredentials,
10234
10634
  linearRelationCreateInput,
10235
10635
  linearTicketIdFromThread,
10636
+ linkifyChatUrls,
10236
10637
  listAbleTimeAssignedIssues,
10237
10638
  listAbleTimeProjects,
10238
10639
  listAbleTimeTasks,
@@ -10345,6 +10746,7 @@ export {
10345
10746
  outboundReplyFromTurn,
10346
10747
  packagedDetachedJobPath,
10347
10748
  parseAbleTimeOAuthCallbackUrl,
10749
+ parseChatLink,
10348
10750
  parseClaudeUsagePayload,
10349
10751
  parseCursorRunnerLine,
10350
10752
  parseDevPreview,
@@ -10378,10 +10780,12 @@ export {
10378
10780
  prepareTerminalCommand,
10379
10781
  prependQueuedItem,
10380
10782
  previewLand,
10783
+ previousCostBaseline,
10381
10784
  primaryCheckoutFromLinkedWorktree,
10382
10785
  promptMentionsBrightsy,
10383
10786
  pushBranch,
10384
10787
  readAgentDoneCustomSound,
10788
+ readAgentRuntimeMetadata,
10385
10789
  readExistingReviewRequestFile,
10386
10790
  readKeychainVaultKey,
10387
10791
  readPlanFile,
@@ -10538,6 +10942,7 @@ export {
10538
10942
  stageAbsolutePathsAsAttachments,
10539
10943
  stageBuffersAsAttachments,
10540
10944
  startAbleTimeOAuth,
10945
+ startAgentRpcServer,
10541
10946
  startDevServer,
10542
10947
  startLinearOAuth,
10543
10948
  startMcpServer,
@@ -10545,6 +10950,7 @@ export {
10545
10950
  startSlackOAuth,
10546
10951
  startSlackRelayServer,
10547
10952
  statWorktreePath,
10953
+ stopAgentRpcServer,
10548
10954
  stripBrightsyNdjsonNoise,
10549
10955
  stripNestedElectronEnv,
10550
10956
  submitPrStack,
@@ -10585,6 +10991,7 @@ export {
10585
10991
  tryReservePort,
10586
10992
  tryReservePorts,
10587
10993
  turnCostUsdFromCursorUsage,
10994
+ turnCostUsdFromProviderSnapshot,
10588
10995
  updateAbleTimeTask,
10589
10996
  updateAdvancedSettings,
10590
10997
  updateAgentExecutable,
@@ -10620,6 +11027,7 @@ export {
10620
11027
  withRemovedAccount,
10621
11028
  withSelectedAccount,
10622
11029
  withThreadLock,
11030
+ withTurnScopedCost,
10623
11031
  workspaceSettingsSourceLabel,
10624
11032
  worktreeBoardStatus,
10625
11033
  worktreeCleanupSettings,