@sideboard-ai/core 0.1.74 → 0.1.79

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 (37) hide show
  1. package/dist/{agents-H7M5CQY2.js → agents-EWPHOM6Y.js} +6 -6
  2. package/dist/{agents-2S2JVLZ6.js → agents-GRAPUXWW.js} +5 -5
  3. package/dist/{app-settings-3TLA2EJ7.js → app-settings-6IOQYGBK.js} +7 -1
  4. package/dist/{app-settings-P42Z3VOB.js → app-settings-NILNWNNQ.js} +7 -1
  5. package/dist/{caffeinate-hold-KLC6SABD.js → caffeinate-hold-EAZNWJBE.js} +5 -1
  6. package/dist/caffeinate-hold-OHAUWOA6.js +20 -0
  7. package/dist/{chunk-77W62MTX.js → chunk-5LXWTU3J.js} +6 -6
  8. package/dist/{chunk-GJ2LZQJI.js → chunk-5VTWBI3I.js} +2 -2
  9. package/dist/{chunk-2NKSHIRI.js → chunk-6T2SKGCS.js} +3 -3
  10. package/dist/{chunk-OE7YBB5X.js → chunk-FUUVPN4A.js} +108 -3
  11. package/dist/{chunk-HCWAIEBU.js → chunk-HCGEFU3J.js} +8 -7
  12. package/dist/{chunk-XA2FQJTN.js → chunk-HSLQXL6M.js} +6 -5
  13. package/dist/{chunk-W7YOTDVO.js → chunk-IFPN6TER.js} +47 -2
  14. package/dist/chunk-MFF2RE3I.js +168 -0
  15. package/dist/{chunk-KBKPVKYZ.js → chunk-P33ZQ7ZC.js} +3 -3
  16. package/dist/{chunk-DG3S2UXP.js → chunk-PW5YHHP3.js} +2 -2
  17. package/dist/chunk-RV6DBEDQ.js +170 -0
  18. package/dist/{chunk-5KJMNNCB.js → chunk-SHCR6RPU.js} +3 -3
  19. package/dist/{chunk-PTJJIQK7.js → chunk-XKNBSYA7.js} +47 -2
  20. package/dist/{chunk-MF2YMEGD.js → chunk-YVVXWWCC.js} +110 -3
  21. package/dist/{coordinator-prompt-3XXSG7M2.js → coordinator-prompt-2OVON2LQ.js} +4 -4
  22. package/dist/{coordinator-prompt-RTUCCPCI.js → coordinator-prompt-K2R34A5T.js} +3 -3
  23. package/dist/{global-workspace-PJU6HISJ.js → global-workspace-RI367AM6.js} +4 -4
  24. package/dist/{global-workspace-CSIS62Z4.js → global-workspace-VG44RZUH.js} +5 -5
  25. package/dist/index.cjs +1548 -614
  26. package/dist/index.d.cts +226 -28
  27. package/dist/index.d.ts +226 -28
  28. package/dist/index.js +1277 -575
  29. package/dist/mcp/run-stdio.cjs +3153 -2509
  30. package/dist/mcp/run-stdio.js +690 -262
  31. package/dist/{workspaces-L4TXMUNM.js → workspaces-BQWQEZWE.js} +5 -5
  32. package/dist/{workspaces-W72KZL4B.js → workspaces-TYPJNUSM.js} +6 -6
  33. package/dist/{worktree-VOCH3WS3.js → worktree-5VLLFI5W.js} +5 -1
  34. package/dist/{worktree-KWXHMKRN.js → worktree-UNSOCYZU.js} +5 -1
  35. package/package.json +1 -1
  36. package/dist/caffeinate-hold-BEJIYPJ7.js +0 -107
  37. package/dist/chunk-JAWEDVVA.js +0 -106
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  listWorkspaces,
5
5
  removeWorkspace,
6
6
  syncWorkspacesFromThreads
7
- } from "./chunk-DG3S2UXP.js";
7
+ } from "./chunk-PW5YHHP3.js";
8
8
  import {
9
9
  BRIGHTSY_MCP_ALLOWED_TOOLS,
10
10
  CLAUDE_MODEL_CATALOG,
@@ -58,7 +58,7 @@ import {
58
58
  sideboardMcpProfile,
59
59
  threadRequestsBrightsyMcp,
60
60
  writeInjectedMcpConfig
61
- } from "./chunk-5KJMNNCB.js";
61
+ } from "./chunk-SHCR6RPU.js";
62
62
  import {
63
63
  CLOUD_COORDINATOR_BUSY_REPLY,
64
64
  CLOUD_COORDINATOR_STOPPED_REPLY,
@@ -85,7 +85,7 @@ import {
85
85
  parseForceStopMessage,
86
86
  slackCoordinatorSourceRef,
87
87
  takenTeamSlugsForOrchestration
88
- } from "./chunk-77W62MTX.js";
88
+ } from "./chunk-5LXWTU3J.js";
89
89
  import {
90
90
  COORDINATOR_TOOL_PLAYBOOK,
91
91
  SLACK_REPLY_FORMATTING,
@@ -94,7 +94,7 @@ import {
94
94
  enrichWorkspacesWithGithub,
95
95
  ensureGlobalCoordinatorCwd,
96
96
  formatWorkspaceInventory
97
- } from "./chunk-HCWAIEBU.js";
97
+ } from "./chunk-HCGEFU3J.js";
98
98
  import {
99
99
  brightsyConfigPath,
100
100
  brightsyMcpServerName,
@@ -129,79 +129,20 @@ import {
129
129
  pushTurnStderr,
130
130
  summarizeTurnStderr
131
131
  } from "./chunk-SEOICVGB.js";
132
- import {
133
- HARNESS_ENV_KEYS,
134
- appSettingsPath,
135
- applyAppEnvironment,
136
- autoArchiveOnMergeEnabled,
137
- autoCleanupOrphansEnabled,
138
- autoRenameBranchEnabled,
139
- autoRunAfterSetupEnabled,
140
- brightsyCloudConnectAgent,
141
- brightsyCloudConnectEnabled,
142
- brightsyInjectWorktreeMcpEnabled,
143
- caffeinateWhileCloudConnectEnabled,
144
- caffeinateWhileRunningEnabled,
145
- caffeinateWhileSlackListenEnabled,
146
- childEnvWithAppSettings,
147
- claudeChromeEnabled,
148
- claudeUserSettingsPath,
149
- deleteBranchOnPurgeEnabled,
150
- disconnectLinearConnection,
151
- ensureSlackDeviceIdentity,
152
- getDefaultAgent,
153
- getDefaultEffort,
154
- getDefaultFast,
155
- getDefaultModel,
156
- getIssueSource,
157
- getLinearApiKey,
158
- harnessEnvKey,
159
- isLinearConnected,
160
- isSecureFileEncrypted,
161
- loadAppSettings,
162
- maxConcurrentAgents,
163
- orchestrationQuotaFallbackAgent,
164
- orchestrationQuotaOnLimit,
165
- persistVaultKeyInKeychain,
166
- readKeychainVaultKey,
167
- readSecureJson,
168
- resolveAgentExecutable,
169
- resolveClaudeExecutable,
170
- resolveEffectiveIssueSource,
171
- resolveNewThreadOptions,
172
- resolveThreadDefaults,
173
- resolveVaultKey,
174
- saveAppSettings,
175
- saveLinearOAuth,
176
- secureFileUnlocksWith,
177
- setVaultMasterKey,
178
- slackAppLevelToken,
179
- slackListenEnabled,
180
- toPublicAppSettings,
181
- updateAdvancedSettings,
182
- updateAgentExecutable,
183
- updateAppEnvironment,
184
- updateBrightsySettings,
185
- updateClaudeSettings,
186
- updateCodexSettings,
187
- updateDefaultsSettings,
188
- updateIntegrationsSettings,
189
- updateOpencodeSettings,
190
- writeSecureJson
191
- } from "./chunk-W7YOTDVO.js";
192
132
  import {
193
133
  caffeinateHoldPath,
194
134
  getCaffeinateHold,
135
+ isThreadCaffeinated,
136
+ releaseCaffeinateHoldForThread,
195
137
  setCaffeinateHold
196
- } from "./chunk-JAWEDVVA.js";
197
- import {
198
- writePrivateFile
199
- } from "./chunk-JT7R45JB.js";
138
+ } from "./chunk-MFF2RE3I.js";
200
139
  import {
201
140
  FAMOUS_SOCCER_TEAMS,
202
141
  addPrStackLayer,
203
142
  allocateTeamName,
204
143
  allocateTeamSlug,
144
+ appendIndexedGitConfig,
145
+ applyGithubGitAuthEnv,
205
146
  branchDisplayLabel,
206
147
  checkoutPrStackLayer,
207
148
  collectTakenTeamSlugs,
@@ -216,6 +157,7 @@ import {
216
157
  extractGhErrorDetail,
217
158
  fetchPrHead,
218
159
  formatGhLandError,
160
+ formatGitAuthModeDirective,
219
161
  formatIpcInvokeError,
220
162
  formatRateLimitResetHint,
221
163
  getPr,
@@ -225,6 +167,7 @@ import {
225
167
  getPrStack,
226
168
  ghHeadRef,
227
169
  ghRepoSelectArgs,
170
+ githubAgentGitEnv,
228
171
  initPrStack,
229
172
  isDirty,
230
173
  isGhRateLimitError,
@@ -259,13 +202,79 @@ import {
259
202
  worktreeDisplayLabel,
260
203
  worktreeDisplayLabelForGroup,
261
204
  worktreeNameFromPath
262
- } from "./chunk-MF2YMEGD.js";
205
+ } from "./chunk-YVVXWWCC.js";
263
206
  import {
264
207
  ATTACHMENTS_DIR,
265
208
  LEGACY_ATTACHMENTS_DIR,
266
209
  attachmentsGitignoreBody,
267
210
  isWorkspaceScratchPath
268
211
  } from "./chunk-FKOIHGKV.js";
212
+ import {
213
+ GITHUB_GIT_AUTH_MODES,
214
+ HARNESS_ENV_KEYS,
215
+ appSettingsPath,
216
+ applyAppEnvironment,
217
+ autoArchiveOnMergeEnabled,
218
+ autoCleanupOrphansEnabled,
219
+ autoRenameBranchEnabled,
220
+ autoRunAfterSetupEnabled,
221
+ brightsyCloudConnectAgent,
222
+ brightsyCloudConnectEnabled,
223
+ brightsyInjectWorktreeMcpEnabled,
224
+ caffeinateWhileCloudConnectEnabled,
225
+ caffeinateWhileRunningEnabled,
226
+ caffeinateWhileSlackListenEnabled,
227
+ childEnvWithAppSettings,
228
+ claudeChromeEnabled,
229
+ claudeUserSettingsPath,
230
+ deleteBranchOnPurgeEnabled,
231
+ disconnectLinearConnection,
232
+ ensureSlackDeviceIdentity,
233
+ getDefaultAgent,
234
+ getDefaultEffort,
235
+ getDefaultFast,
236
+ getDefaultModel,
237
+ getGithubGitAuthMode,
238
+ getGithubPat,
239
+ getIssueSource,
240
+ getLinearApiKey,
241
+ harnessEnvKey,
242
+ isLinearConnected,
243
+ isSecureFileEncrypted,
244
+ loadAppSettings,
245
+ maxConcurrentAgents,
246
+ orchestrationQuotaFallbackAgent,
247
+ orchestrationQuotaOnLimit,
248
+ persistVaultKeyInKeychain,
249
+ readKeychainVaultKey,
250
+ readSecureJson,
251
+ resolveAgentExecutable,
252
+ resolveClaudeExecutable,
253
+ resolveEffectiveIssueSource,
254
+ resolveNewThreadOptions,
255
+ resolveThreadDefaults,
256
+ resolveVaultKey,
257
+ saveAppSettings,
258
+ saveLinearOAuth,
259
+ secureFileUnlocksWith,
260
+ setVaultMasterKey,
261
+ slackAppLevelToken,
262
+ slackListenEnabled,
263
+ toPublicAppSettings,
264
+ updateAdvancedSettings,
265
+ updateAgentExecutable,
266
+ updateAppEnvironment,
267
+ updateBrightsySettings,
268
+ updateClaudeSettings,
269
+ updateCodexSettings,
270
+ updateDefaultsSettings,
271
+ updateIntegrationsSettings,
272
+ updateOpencodeSettings,
273
+ writeSecureJson
274
+ } from "./chunk-IFPN6TER.js";
275
+ import {
276
+ writePrivateFile
277
+ } from "./chunk-JT7R45JB.js";
269
278
  import {
270
279
  appendMessage,
271
280
  createEmptyThread,
@@ -374,9 +383,9 @@ async function getGitHubStatus() {
374
383
  };
375
384
  }
376
385
  const status = await run("gh", ["auth", "status"], { reject: false });
377
- const text2 = `${status.stdout}
386
+ const text3 = `${status.stdout}
378
387
  ${status.stderr}`;
379
- const loginMatch = text2.match(/Logged in to ([^\s]+) account (\S+)/i) ?? text2.match(/Logged in to ([^\s]+) as (\S+)/i);
388
+ const loginMatch = text3.match(/Logged in to ([^\s]+) account (\S+)/i) ?? text3.match(/Logged in to ([^\s]+) as (\S+)/i);
380
389
  if (loginMatch) {
381
390
  return {
382
391
  connected: true,
@@ -396,13 +405,39 @@ async function refreshGitHubAuth() {
396
405
  return getGitHubStatus();
397
406
  }
398
407
 
408
+ // src/http/fetch.ts
409
+ var injected = null;
410
+ function setHttpFetchImpl(fetchImpl) {
411
+ injected = fetchImpl;
412
+ }
413
+ function formatFetchError(err, url) {
414
+ if (!(err instanceof Error)) return `${String(err)} (${url})`;
415
+ const cause = err.cause;
416
+ let detail = "";
417
+ if (cause instanceof Error) {
418
+ const code = typeof cause.code === "string" ? cause.code : void 0;
419
+ detail = code ? ` [${code}: ${cause.message}]` : ` [${cause.message}]`;
420
+ } else if (cause != null) {
421
+ detail = ` [${String(cause)}]`;
422
+ }
423
+ return `${err.message}${detail} (${url})`;
424
+ }
425
+ async function httpFetch(input, init) {
426
+ const url = typeof input === "string" ? input : input.href;
427
+ const fn = injected ?? globalThis.fetch.bind(globalThis);
428
+ try {
429
+ return await fn(url, init);
430
+ } catch (err) {
431
+ throw new Error(formatFetchError(err, url));
432
+ }
433
+ }
434
+
399
435
  // src/integrations/linear-oauth.ts
400
436
  import { createHash, randomBytes } from "crypto";
401
437
  import { createServer } from "http";
402
438
 
403
439
  // src/integrations/linear-app.ts
404
440
  var BAKED_LINEAR_CLIENT_ID = "39a15abc7ea5bea17c47f47f85ff5fd0";
405
- var BAKED_LINEAR_CLIENT_SECRET = "";
406
441
  function hasBakedLinearOAuth() {
407
442
  return Boolean(BAKED_LINEAR_CLIENT_ID.trim());
408
443
  }
@@ -414,12 +449,12 @@ var LINEAR_AUTHORIZE = "https://linear.app/oauth/authorize";
414
449
  var LINEAR_TOKEN = "https://api.linear.app/oauth/token";
415
450
  var LINEAR_REVOKE = "https://api.linear.app/oauth/revoke";
416
451
  var LINEAR_GRAPHQL = "https://api.linear.app/graphql";
417
- var LINEAR_OAUTH_SCOPES = "read";
452
+ var LINEAR_OAUTH_SCOPES = "read,write";
418
453
  var REFRESH_SKEW_MS = 5 * 6e4;
419
454
  function linearOAuthCredentials() {
420
455
  const settings = loadAppSettings();
421
456
  const clientId = process.env.SIDEBOARD_LINEAR_CLIENT_ID?.trim() || settings.integrations.linearClientId?.trim() || BAKED_LINEAR_CLIENT_ID.trim();
422
- const clientSecret = process.env.SIDEBOARD_LINEAR_CLIENT_SECRET?.trim() || settings.integrations.linearClientSecret?.trim() || BAKED_LINEAR_CLIENT_SECRET.trim();
457
+ const clientSecret = process.env.SIDEBOARD_LINEAR_CLIENT_SECRET?.trim() || settings.integrations.linearClientSecret?.trim() || "";
423
458
  if (!clientId) {
424
459
  throw new Error(
425
460
  `Linear browser sign-in needs a Linear OAuth app Client ID. Create one at linear.app/settings/api/applications/new with callback ${LINEAR_OAUTH_REDIRECT}, then set SIDEBOARD_LINEAR_CLIENT_ID (PKCE does not require a secret). You can still paste a personal API key.`
@@ -459,7 +494,7 @@ function htmlPage(title, body) {
459
494
  h1{font-size:1.25rem}p{line-height:1.5;color:#444}</style></head><body>${body}</body></html>`;
460
495
  }
461
496
  async function exchangeLinearToken(body) {
462
- const res = await fetch(LINEAR_TOKEN, {
497
+ const res = await httpFetch(LINEAR_TOKEN, {
463
498
  method: "POST",
464
499
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
465
500
  body
@@ -481,7 +516,7 @@ async function persistLinearTokens(data, viewer) {
481
516
  });
482
517
  }
483
518
  async function fetchLinearViewer(accessToken) {
484
- const res = await fetch(LINEAR_GRAPHQL, {
519
+ const res = await httpFetch(LINEAR_GRAPHQL, {
485
520
  method: "POST",
486
521
  headers: {
487
522
  "Content-Type": "application/json",
@@ -652,7 +687,7 @@ async function revokeLinearToken(token, tokenTypeHint) {
652
687
  token,
653
688
  token_type_hint: tokenTypeHint
654
689
  });
655
- await fetch(LINEAR_REVOKE, {
690
+ await httpFetch(LINEAR_REVOKE, {
656
691
  method: "POST",
657
692
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
658
693
  body
@@ -669,6 +704,18 @@ async function disconnectLinear() {
669
704
 
670
705
  // src/integrations/linear.ts
671
706
  var LINEAR_GRAPHQL2 = "https://api.linear.app/graphql";
707
+ var ISSUE_FIELDS = `
708
+ id
709
+ identifier
710
+ title
711
+ description
712
+ url
713
+ priority
714
+ state { id name type }
715
+ assignee { id name }
716
+ team { id key name states { nodes { id name type } } }
717
+ labels { nodes { name } }
718
+ `;
672
719
  var ASSIGNED_ISSUES_QUERY = `
673
720
  query SideboardAssignedIssues($first: Int!) {
674
721
  viewer {
@@ -677,53 +724,280 @@ query SideboardAssignedIssues($first: Int!) {
677
724
  orderBy: updatedAt
678
725
  filter: { state: { type: { nin: ["completed", "canceled"] } } }
679
726
  ) {
680
- nodes {
681
- id
682
- identifier
683
- title
684
- url
685
- labels { nodes { name } }
686
- }
727
+ nodes { ${ISSUE_FIELDS} }
687
728
  }
688
729
  }
689
730
  }
690
731
  `;
691
- async function listLinearIssuesDirect(opts) {
692
- const apiKey = (opts?.apiKey ?? await getLinearAuthToken())?.trim();
693
- if (!apiKey) {
732
+ var TEAMS_QUERY = `
733
+ query SideboardTeams {
734
+ viewer { id name }
735
+ teams(first: 50) {
736
+ nodes {
737
+ id
738
+ key
739
+ name
740
+ states { nodes { id name type } }
741
+ }
742
+ }
743
+ }
744
+ `;
745
+ var ISSUE_QUERY = `
746
+ query SideboardIssue($id: String!) {
747
+ issue(id: $id) { ${ISSUE_FIELDS} }
748
+ }
749
+ `;
750
+ var ISSUE_CREATE = `
751
+ mutation SideboardIssueCreate($input: IssueCreateInput!) {
752
+ issueCreate(input: $input) {
753
+ success
754
+ issue { ${ISSUE_FIELDS} }
755
+ }
756
+ }
757
+ `;
758
+ var ISSUE_UPDATE = `
759
+ mutation SideboardIssueUpdate($id: String!, $input: IssueUpdateInput!) {
760
+ issueUpdate(id: $id, input: $input) {
761
+ success
762
+ issue { ${ISSUE_FIELDS} }
763
+ }
764
+ }
765
+ `;
766
+ var COMMENT_CREATE = `
767
+ mutation SideboardCommentCreate($input: CommentCreateInput!) {
768
+ commentCreate(input: $input) {
769
+ success
770
+ comment { id body url }
771
+ }
772
+ }
773
+ `;
774
+ function rewriteLinearError(message) {
775
+ if (/scope|permission|not (authorized|allowed)|unauthorized|insufficient/i.test(message)) {
776
+ return `${message} \u2014 Disconnect and Connect Linear in Account settings to grant write access.`;
777
+ }
778
+ return message;
779
+ }
780
+ async function requireLinearToken(apiKey) {
781
+ const token = (apiKey ?? await getLinearAuthToken())?.trim();
782
+ if (!token) {
694
783
  throw new Error("Linear is not connected \u2014 sign in from Account settings");
695
784
  }
696
- const first = Math.max(1, Math.min(100, opts?.limit ?? 50));
697
- const res = await fetch(LINEAR_GRAPHQL2, {
785
+ return token;
786
+ }
787
+ async function linearGraphql(query, variables, opts) {
788
+ const apiKey = await requireLinearToken(opts?.apiKey);
789
+ const res = await httpFetch(LINEAR_GRAPHQL2, {
698
790
  method: "POST",
699
791
  headers: {
700
792
  "Content-Type": "application/json",
701
793
  Authorization: linearAuthorizationHeader(apiKey)
702
794
  },
703
- body: JSON.stringify({
704
- query: ASSIGNED_ISSUES_QUERY,
705
- variables: { first }
706
- })
795
+ body: JSON.stringify({ query, variables })
707
796
  });
708
797
  if (!res.ok) {
709
798
  const body = await res.text().catch(() => "");
710
799
  throw new Error(
711
- `Linear API error ${res.status}${body ? `: ${body.slice(0, 200)}` : ""}`
800
+ rewriteLinearError(
801
+ `Linear API error ${res.status}${body ? `: ${body.slice(0, 200)}` : ""}`
802
+ )
712
803
  );
713
804
  }
714
805
  const json = await res.json();
715
806
  if (json.errors?.length) {
716
- throw new Error(json.errors.map((e) => e.message ?? "Linear error").join("; "));
807
+ throw new Error(
808
+ rewriteLinearError(json.errors.map((e) => e.message ?? "Linear error").join("; "))
809
+ );
717
810
  }
718
- const nodes = json.data?.viewer?.assignedIssues?.nodes ?? [];
719
- return nodes.map((node) => ({
811
+ if (json.data == null) {
812
+ throw new Error("Linear API returned no data");
813
+ }
814
+ return json.data;
815
+ }
816
+ function mapState(node) {
817
+ if (!node?.id) return void 0;
818
+ return {
819
+ id: String(node.id),
820
+ name: String(node.name ?? ""),
821
+ type: String(node.type ?? "")
822
+ };
823
+ }
824
+ function mapIssue(node) {
825
+ const team = node.team;
826
+ return {
720
827
  id: String(node.id ?? node.identifier ?? ""),
721
828
  identifier: String(node.identifier ?? node.id ?? ""),
722
829
  title: String(node.title ?? ""),
723
830
  url: String(node.url ?? ""),
724
- labels: (node.labels?.nodes ?? []).map((l) => l.name).filter((n) => Boolean(n)),
831
+ description: node.description?.trim() || void 0,
832
+ priority: typeof node.priority === "number" ? node.priority : void 0,
833
+ state: mapState(node.state),
834
+ assignee: node.assignee?.id ? { id: String(node.assignee.id), name: String(node.assignee.name ?? "") } : void 0,
835
+ team: team?.id ? {
836
+ id: String(team.id),
837
+ key: String(team.key ?? ""),
838
+ name: String(team.name ?? ""),
839
+ states: (team.states?.nodes ?? []).map((s) => mapState(s)).filter((s) => Boolean(s))
840
+ } : void 0,
841
+ labels: (node.labels?.nodes ?? []).map((l) => l.name).filter((n) => Boolean(n))
842
+ };
843
+ }
844
+ function toIssueInfo(issue) {
845
+ return {
846
+ id: issue.id,
847
+ identifier: issue.identifier,
848
+ title: issue.title,
849
+ url: issue.url,
850
+ labels: issue.labels,
725
851
  provider: "linear"
726
- }));
852
+ };
853
+ }
854
+ function mapTeam(node) {
855
+ return {
856
+ id: String(node.id ?? ""),
857
+ key: String(node.key ?? ""),
858
+ name: String(node.name ?? ""),
859
+ states: (node.states?.nodes ?? []).map((s) => mapState(s)).filter((s) => Boolean(s))
860
+ };
861
+ }
862
+ function resolveLinearTeam(teams, team) {
863
+ const t = team.trim();
864
+ if (!t) throw new Error("Linear team is required (id, key, or name from linear_list_teams)");
865
+ const lower = t.toLowerCase();
866
+ const found = teams.find(
867
+ (x) => x.id === t || x.key.toLowerCase() === lower || x.name.toLowerCase() === lower
868
+ );
869
+ if (!found) {
870
+ const keys = teams.map((x) => x.key).filter(Boolean).join(", ") || "(none)";
871
+ throw new Error(`Linear team not found: ${team}. Known keys: ${keys}`);
872
+ }
873
+ return found;
874
+ }
875
+ function resolveLinearState(team, state) {
876
+ const s = state.trim();
877
+ if (!s) throw new Error("Linear state is empty");
878
+ const lower = s.toLowerCase();
879
+ const found = team.states.find((x) => x.id === s) || team.states.find((x) => x.name.toLowerCase() === lower) || team.states.find((x) => x.type.toLowerCase() === lower);
880
+ if (!found) {
881
+ const available = team.states.map((x) => `${x.name} (${x.type})`).join(", ") || "(none)";
882
+ throw new Error(`Linear state not found on ${team.key}: ${state}. Available: ${available}`);
883
+ }
884
+ return found;
885
+ }
886
+ function normalizePriority(priority) {
887
+ if (priority == null) return void 0;
888
+ if (!Number.isInteger(priority) || priority < 0 || priority > 4) {
889
+ throw new Error("Linear priority must be 0 (none), 1 (urgent), 2 (high), 3 (medium), or 4 (low)");
890
+ }
891
+ return priority;
892
+ }
893
+ async function listLinearIssuesDirect(opts) {
894
+ const first = Math.max(1, Math.min(100, opts?.limit ?? 50));
895
+ const json = await linearGraphql(ASSIGNED_ISSUES_QUERY, { first }, opts);
896
+ return (json.viewer?.assignedIssues?.nodes ?? []).map((node) => toIssueInfo(mapIssue(node)));
897
+ }
898
+ async function listLinearTeams(opts) {
899
+ const json = await linearGraphql(TEAMS_QUERY, void 0, opts);
900
+ return {
901
+ viewer: {
902
+ id: String(json.viewer?.id ?? ""),
903
+ name: String(json.viewer?.name ?? "")
904
+ },
905
+ teams: (json.teams?.nodes ?? []).map(mapTeam)
906
+ };
907
+ }
908
+ async function getLinearIssue(id, opts) {
909
+ const issueId = id.trim();
910
+ if (!issueId) throw new Error("Linear issue id is required (uuid or ENG-123)");
911
+ const json = await linearGraphql(
912
+ ISSUE_QUERY,
913
+ { id: issueId },
914
+ opts
915
+ );
916
+ if (!json.issue) {
917
+ throw new Error(`Linear issue not found: ${issueId}`);
918
+ }
919
+ return mapIssue(json.issue);
920
+ }
921
+ async function createLinearIssue(input, opts) {
922
+ const title = input.title.trim();
923
+ if (!title) throw new Error("Linear issue title is required");
924
+ const { viewer, teams } = await listLinearTeams(opts);
925
+ const team = resolveLinearTeam(teams, input.team);
926
+ const mutationInput = {
927
+ teamId: team.id,
928
+ title
929
+ };
930
+ const description = input.description?.trim();
931
+ if (description) mutationInput.description = description;
932
+ if (input.state?.trim()) {
933
+ mutationInput.stateId = resolveLinearState(team, input.state).id;
934
+ }
935
+ const assignee = input.assignee === void 0 ? void 0 : input.assignee?.trim() || null;
936
+ if (assignee === "me") mutationInput.assigneeId = viewer.id;
937
+ else if (assignee) mutationInput.assigneeId = assignee;
938
+ const priority = normalizePriority(input.priority);
939
+ if (priority != null) mutationInput.priority = priority;
940
+ const json = await linearGraphql(ISSUE_CREATE, { input: mutationInput }, opts);
941
+ if (!json.issueCreate?.success || !json.issueCreate.issue) {
942
+ throw new Error("Linear issueCreate failed");
943
+ }
944
+ return mapIssue(json.issueCreate.issue);
945
+ }
946
+ async function updateLinearIssue(input, opts) {
947
+ const issueId = input.id.trim();
948
+ if (!issueId) throw new Error("Linear issue id is required (uuid or ENG-123)");
949
+ const mutationInput = {};
950
+ if (input.title !== void 0) {
951
+ const title = input.title.trim();
952
+ if (!title) throw new Error("Linear issue title cannot be empty");
953
+ mutationInput.title = title;
954
+ }
955
+ if (input.description !== void 0) {
956
+ mutationInput.description = input.description;
957
+ }
958
+ if (input.state?.trim()) {
959
+ const existing = await getLinearIssue(issueId, opts);
960
+ const team = existing.team;
961
+ if (!team?.states.length) {
962
+ throw new Error(`Linear issue ${existing.identifier} has no workflow states to resolve "${input.state}"`);
963
+ }
964
+ mutationInput.stateId = resolveLinearState(team, input.state).id;
965
+ }
966
+ if (input.assignee !== void 0) {
967
+ const assignee = input.assignee?.trim() || null;
968
+ if (assignee === "me") {
969
+ const { viewer } = await listLinearTeams(opts);
970
+ mutationInput.assigneeId = viewer.id;
971
+ } else {
972
+ mutationInput.assigneeId = assignee;
973
+ }
974
+ }
975
+ if (input.priority !== void 0) {
976
+ mutationInput.priority = normalizePriority(input.priority);
977
+ }
978
+ if (Object.keys(mutationInput).length === 0) {
979
+ throw new Error("linear_update_issue needs at least one of title, description, state, assignee, priority");
980
+ }
981
+ const json = await linearGraphql(ISSUE_UPDATE, { id: issueId, input: mutationInput }, opts);
982
+ if (!json.issueUpdate?.success || !json.issueUpdate.issue) {
983
+ throw new Error("Linear issueUpdate failed");
984
+ }
985
+ return mapIssue(json.issueUpdate.issue);
986
+ }
987
+ async function commentLinearIssue(input, opts) {
988
+ const issueId = input.id.trim();
989
+ const body = input.body.trim();
990
+ if (!issueId) throw new Error("Linear issue id is required (uuid or ENG-123)");
991
+ if (!body) throw new Error("Linear comment body is required");
992
+ const json = await linearGraphql(COMMENT_CREATE, { input: { issueId, body } }, opts);
993
+ if (!json.commentCreate?.success || !json.commentCreate.comment?.id) {
994
+ throw new Error("Linear commentCreate failed");
995
+ }
996
+ return {
997
+ id: String(json.commentCreate.comment.id),
998
+ body: String(json.commentCreate.comment.body ?? body),
999
+ url: json.commentCreate.comment.url?.trim() || void 0
1000
+ };
727
1001
  }
728
1002
  async function validateLinearApiKey(apiKey) {
729
1003
  const key = apiKey.trim();
@@ -808,8 +1082,8 @@ function toolDetail(name, input) {
808
1082
  if (!input) return void 0;
809
1083
  const command = str(input.command) ?? str(input.cmd);
810
1084
  if (command) return command;
811
- const path = str(input.file_path) ?? str(input.path) ?? str(input.filePath) ?? str(input.filename);
812
- if (path) return path;
1085
+ const path2 = str(input.file_path) ?? str(input.path) ?? str(input.filePath) ?? str(input.filename);
1086
+ if (path2) return path2;
813
1087
  const pattern = str(input.pattern) ?? str(input.glob) ?? str(input.glob_pattern);
814
1088
  if (pattern) return pattern;
815
1089
  const query = str(input.query) ?? str(input.prompt);
@@ -853,12 +1127,12 @@ function toolDescription(name, input) {
853
1127
  return "Run shell command";
854
1128
  }
855
1129
  if (/edit|write|apply/i.test(name)) {
856
- const path = str(input.file_path) ?? str(input.path);
857
- return path ? `Edit ${path.split("/").pop()}` : "Edit file";
1130
+ const path2 = str(input.file_path) ?? str(input.path);
1131
+ return path2 ? `Edit ${path2.split("/").pop()}` : "Edit file";
858
1132
  }
859
1133
  if (/read/i.test(name)) {
860
- const path = str(input.file_path) ?? str(input.path);
861
- return path ? `Read ${path.split("/").pop()}` : "Read file";
1134
+ const path2 = str(input.file_path) ?? str(input.path);
1135
+ return path2 ? `Read ${path2.split("/").pop()}` : "Read file";
862
1136
  }
863
1137
  if (/grep/i.test(name)) return "Search files";
864
1138
  if (/glob/i.test(name)) return "Find files";
@@ -868,9 +1142,9 @@ function toolFilePath(input) {
868
1142
  if (!input) return void 0;
869
1143
  return str(input.file_path) ?? str(input.path) ?? str(input.filePath) ?? str(input.filename);
870
1144
  }
871
- function countLines(text2) {
872
- if (!text2) return 0;
873
- return text2.split("\n").length;
1145
+ function countLines(text3) {
1146
+ if (!text3) return 0;
1147
+ return text3.split("\n").length;
874
1148
  }
875
1149
  function diffFromInput(input) {
876
1150
  if (!input) return {};
@@ -997,9 +1271,9 @@ function applyAgentEvent(parts, event) {
997
1271
  function partsToAssistantText(parts) {
998
1272
  return parts.filter((p) => p.type === "text").map((p) => p.text).join("").trim();
999
1273
  }
1000
- function stripBrightsyNdjsonNoise(text2) {
1001
- if (!text2 || !text2.includes('"type"')) return text2;
1002
- let out = text2;
1274
+ function stripBrightsyNdjsonNoise(text3) {
1275
+ if (!text3 || !text3.includes('"type"')) return text3;
1276
+ let out = text3;
1003
1277
  out = out.replace(
1004
1278
  /\{"type":"(?:tool_use|tool_result|tool|thinking|usage|done|error)"[\s\S]*?\}\s*(?=\{"type":"|$|(?=[A-Za-z*#]))/g,
1005
1279
  ""
@@ -1065,9 +1339,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
1065
1339
  `Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
1066
1340
  );
1067
1341
  }
1068
- const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-CSIS62Z4.js");
1342
+ const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-VG44RZUH.js");
1069
1343
  if (isGlobalThread2(thread)) {
1070
- const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-3XXSG7M2.js");
1344
+ const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-2OVON2LQ.js");
1071
1345
  ensureGlobalCoordinatorCwd2(
1072
1346
  isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
1073
1347
  );
@@ -1084,8 +1358,13 @@ async function spawnAgentTurn(thread, input, onEvent) {
1084
1358
  }
1085
1359
  const env = childEnvWithAppSettings(cmd.env);
1086
1360
  if (!isOrchestratorThread(thread)) {
1087
- const originEnv = await originGhRepoEnv(thread.worktreePath).catch(() => ({}));
1088
- Object.assign(env, originEnv);
1361
+ try {
1362
+ const originEnv = await originGhRepoEnv(thread.worktreePath, { env });
1363
+ Object.assign(env, originEnv);
1364
+ } catch (err) {
1365
+ const detail = err instanceof Error ? err.message : String(err);
1366
+ console.warn(`[sideboard] originGhRepoEnv failed (${thread.worktreePath}): ${detail}`);
1367
+ }
1089
1368
  }
1090
1369
  const child = execa(cmd.file, cmd.args, {
1091
1370
  cwd: cmd.cwd,
@@ -1154,8 +1433,8 @@ async function spawnAgentTurn(thread, input, onEvent) {
1154
1433
  onEvent({ type: "exit", data: exitCode });
1155
1434
  const finalized = finalizeParts(parts);
1156
1435
  const rawText = assistantText.trim() || partsToAssistantText(finalized);
1157
- const text2 = thread.agent === "brightsy" ? stripBrightsyNdjsonNoise(rawText) : rawText;
1158
- return { exitCode, sessionId, assistantText: text2, parts: finalized, usage };
1436
+ const text3 = thread.agent === "brightsy" ? stripBrightsyNdjsonNoise(rawText) : rawText;
1437
+ return { exitCode, sessionId, assistantText: text3, parts: finalized, usage };
1159
1438
  });
1160
1439
  return {
1161
1440
  pid: child.pid,
@@ -1220,6 +1499,8 @@ function formatWorktreeDirective(thread, opts) {
1220
1499
  lines.push(
1221
1500
  "- Push with `git push -u origin HEAD` (or the current branch name). Do not `git push upstream`."
1222
1501
  );
1502
+ lines.push("");
1503
+ lines.push(formatGitAuthModeDirective(opts?.gitAuthMode ?? "auto"));
1223
1504
  lines.push(
1224
1505
  "- Derive the PR title and body from what the changes actually do and why \u2014 inspect the diff/commits and the user request. Do not use the soccer-team worktree nickname or placeholder branch as the PR title."
1225
1506
  );
@@ -1405,9 +1686,9 @@ function matchSimpleGlob(pattern, name) {
1405
1686
  return new RegExp(`^${escaped}$`).test(name);
1406
1687
  }
1407
1688
  function readWorktreeInclude(repoPath) {
1408
- const path = join2(repoPath, ".worktreeinclude");
1409
- if (!existsSync2(path)) return [];
1410
- return readFileSync2(path, "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
1689
+ const path2 = join2(repoPath, ".worktreeinclude");
1690
+ if (!existsSync2(path2)) return [];
1691
+ return readFileSync2(path2, "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
1411
1692
  }
1412
1693
  function resolveFilesToCopy(repoPath) {
1413
1694
  const fromInclude = readWorktreeInclude(repoPath);
@@ -1482,8 +1763,20 @@ async function captureLoginEnv() {
1482
1763
  cachedLoginEnv = { ...process.env };
1483
1764
  return { ...cachedLoginEnv };
1484
1765
  }
1766
+ var NESTED_ELECTRON_ENV_PREFIXES = ["ELECTRON_", "CHROME_"];
1767
+ function stripNestedElectronEnv(env) {
1768
+ const out = { ...env };
1769
+ for (const key of Object.keys(out)) {
1770
+ if (NESTED_ELECTRON_ENV_PREFIXES.some((prefix) => key.startsWith(prefix))) {
1771
+ delete out[key];
1772
+ }
1773
+ }
1774
+ return out;
1775
+ }
1485
1776
  function buildWorkspaceScriptEnv(opts, baseEnv) {
1486
- const env = { ...baseEnv ?? process.env };
1777
+ const env = stripNestedElectronEnv({
1778
+ ...baseEnv ?? process.env
1779
+ });
1487
1780
  const name = opts.workspaceName ?? basename(opts.worktreePath);
1488
1781
  const ports = opts.ports ?? [];
1489
1782
  const primary = ports[0];
@@ -1708,10 +2001,10 @@ import { join as join3 } from "path";
1708
2001
  import { execa as execa3 } from "execa";
1709
2002
  import { createInterface as createInterface3 } from "readline";
1710
2003
  function loadCursorWorktreesJson(rootPath) {
1711
- const path = join3(rootPath, ".cursor", "worktrees.json");
1712
- if (!existsSync3(path)) return null;
2004
+ const path2 = join3(rootPath, ".cursor", "worktrees.json");
2005
+ if (!existsSync3(path2)) return null;
1713
2006
  try {
1714
- return JSON.parse(readFileSync3(path, "utf8"));
2007
+ return JSON.parse(readFileSync3(path2, "utf8"));
1715
2008
  } catch {
1716
2009
  return null;
1717
2010
  }
@@ -1812,9 +2105,9 @@ function parseNameStatus(stdout) {
1812
2105
  const out = [];
1813
2106
  for (const line of stdout.split("\n").filter(Boolean)) {
1814
2107
  const [status, ...pathParts] = line.split(" ");
1815
- const path = pathParts[pathParts.length - 1];
1816
- if (!status || !path) continue;
1817
- out.push({ status: status[0] ?? status, path });
2108
+ const path2 = pathParts[pathParts.length - 1];
2109
+ if (!status || !path2) continue;
2110
+ out.push({ status: status[0] ?? status, path: path2 });
1818
2111
  }
1819
2112
  return out;
1820
2113
  }
@@ -1822,12 +2115,12 @@ function parseNumstat(stdout) {
1822
2115
  const map = /* @__PURE__ */ new Map();
1823
2116
  for (const line of stdout.split("\n").filter(Boolean)) {
1824
2117
  const [addRaw, delRaw, ...pathParts] = line.split(" ");
1825
- const path = pathParts[pathParts.length - 1];
1826
- if (!path) continue;
2118
+ const path2 = pathParts[pathParts.length - 1];
2119
+ if (!path2) continue;
1827
2120
  const additions = addRaw === "-" ? 0 : Number(addRaw);
1828
2121
  const deletions = delRaw === "-" ? 0 : Number(delRaw);
1829
2122
  if (!Number.isFinite(additions) && !Number.isFinite(deletions)) continue;
1830
- map.set(path, {
2123
+ map.set(path2, {
1831
2124
  additions: Number.isFinite(additions) ? additions : 0,
1832
2125
  deletions: Number.isFinite(deletions) ? deletions : 0
1833
2126
  });
@@ -1904,70 +2197,70 @@ function splitCombinedDiff(stdout) {
1904
2197
  }
1905
2198
  return map;
1906
2199
  }
1907
- function syntheticAddPatch(path, content, maxHunk) {
2200
+ function syntheticAddPatch(path2, content, maxHunk) {
1908
2201
  const lines = content.split("\n");
1909
2202
  const body = lines.map((l) => `+${l}`).join("\n");
1910
- const header = `diff --git a/${path} b/${path}
2203
+ const header = `diff --git a/${path2} b/${path2}
1911
2204
  new file mode 100644
1912
2205
  --- /dev/null
1913
- +++ b/${path}
2206
+ +++ b/${path2}
1914
2207
  @@ -0,0 +1,${lines.length} @@
1915
2208
  `;
1916
2209
  return {
1917
- path,
2210
+ path: path2,
1918
2211
  status: "A",
1919
2212
  patch: capPatch(`${header}${body}`, maxHunk),
1920
2213
  additions: lines.length,
1921
2214
  deletions: 0
1922
2215
  };
1923
2216
  }
1924
- async function untrackedPatch(worktreePath, path, maxHunk) {
1925
- const abs = join4(worktreePath, path);
2217
+ async function untrackedPatch(worktreePath, path2, maxHunk) {
2218
+ const abs = join4(worktreePath, path2);
1926
2219
  try {
1927
2220
  const st = statSync2(abs);
1928
2221
  if (st.isFile() && st.size > maxHunk) {
1929
2222
  const buf = readFileSync4(abs).subarray(0, maxHunk);
1930
- return syntheticAddPatch(path, buf.toString("utf8"), maxHunk);
2223
+ return syntheticAddPatch(path2, buf.toString("utf8"), maxHunk);
1931
2224
  }
1932
2225
  } catch {
1933
2226
  }
1934
2227
  const { stdout: patch } = await git(
1935
- ["diff", "--no-ext-diff", "--no-color", "--no-index", "--", "/dev/null", path],
2228
+ ["diff", "--no-ext-diff", "--no-color", "--no-index", "--", "/dev/null", path2],
1936
2229
  worktreePath,
1937
2230
  { reject: false, timeoutMs: 4e3 }
1938
2231
  );
1939
- const normalized = patch.replace(/^diff --git a\/.+? b\/.+?$/m, `diff --git a/${path} b/${path}`).replace(/^--- .*$/m, "--- /dev/null").replace(/^\+\+\+ .*$/m, `+++ b/${path}`);
2232
+ const normalized = patch.replace(/^diff --git a\/.+? b\/.+?$/m, `diff --git a/${path2} b/${path2}`).replace(/^--- .*$/m, "--- /dev/null").replace(/^\+\+\+ .*$/m, `+++ b/${path2}`);
1940
2233
  const addLines = normalized.split("\n").filter((l) => l.startsWith("+") && !l.startsWith("+++")).length;
1941
2234
  return {
1942
- path,
2235
+ path: path2,
1943
2236
  status: "A",
1944
2237
  patch: capPatch(normalized, maxHunk),
1945
2238
  additions: addLines,
1946
2239
  deletions: 0
1947
2240
  };
1948
2241
  }
1949
- async function listUntrackedPaths(worktreePath, path) {
2242
+ async function listUntrackedPaths(worktreePath, path2) {
1950
2243
  const args = ["ls-files", "-z", "--others", "--exclude-standard"];
1951
- if (path) args.push("--", path);
2244
+ if (path2) args.push("--", path2);
1952
2245
  const { stdout } = await git(args, worktreePath, { reject: false });
1953
2246
  return stdout.split("\0").filter(Boolean).filter((p) => !isSideboardScratchPath(p));
1954
2247
  }
1955
2248
  async function listUntrackedFiles(worktreePath, maxHunk, paths) {
1956
2249
  const list = paths ?? await listUntrackedPaths(worktreePath);
1957
2250
  if (list.length === 0) return [];
1958
- return Promise.all(list.map((path) => untrackedPatch(worktreePath, path, maxHunk)));
2251
+ return Promise.all(list.map((path2) => untrackedPatch(worktreePath, path2, maxHunk)));
1959
2252
  }
1960
2253
  function untrackedStubs(paths) {
1961
- return paths.map((path) => ({
1962
- path,
2254
+ return paths.map((path2) => ({
2255
+ path: path2,
1963
2256
  status: "A",
1964
2257
  patch: "",
1965
2258
  additions: 0,
1966
2259
  deletions: 0
1967
2260
  }));
1968
2261
  }
1969
- function pathspec(path) {
1970
- return path ? ["--", path] : [];
2262
+ function pathspec(path2) {
2263
+ return path2 ? ["--", path2] : [];
1971
2264
  }
1972
2265
  function gitDiff(args, cwd, opts) {
1973
2266
  return git(["diff", "--no-ext-diff", "--no-color", ...args], cwd, {
@@ -1975,7 +2268,7 @@ function gitDiff(args, cwd, opts) {
1975
2268
  timeoutMs: opts?.timeoutMs
1976
2269
  });
1977
2270
  }
1978
- function fileFromPatch(path, patch, maxHunk) {
2271
+ function fileFromPatch(path2, patch, maxHunk) {
1979
2272
  let status = "M";
1980
2273
  if (/^new file mode /m.test(patch) || /^--- \/dev\/null/m.test(patch)) {
1981
2274
  status = "A";
@@ -1987,15 +2280,15 @@ function fileFromPatch(path, patch, maxHunk) {
1987
2280
  const additions = patch.split("\n").filter((l) => l.startsWith("+") && !l.startsWith("+++")).length;
1988
2281
  const deletions = patch.split("\n").filter((l) => l.startsWith("-") && !l.startsWith("---")).length;
1989
2282
  return {
1990
- path,
2283
+ path: path2,
1991
2284
  status,
1992
2285
  patch: capPatch(patch, maxHunk),
1993
2286
  additions,
1994
2287
  deletions
1995
2288
  };
1996
2289
  }
1997
- async function gitDiffParts(worktreePath, diffArgs, includePatches, path) {
1998
- const extra = pathspec(path);
2290
+ async function gitDiffParts(worktreePath, diffArgs, includePatches, path2) {
2291
+ const extra = pathspec(path2);
1999
2292
  if (includePatches) {
2000
2293
  const diff = await gitDiff([...diffArgs, ...extra], worktreePath, {
2001
2294
  timeoutMs: 4e3
@@ -2020,30 +2313,30 @@ function formatStat(files) {
2020
2313
  return `${n} file${n === 1 ? "" : "s"} changed, ${additions} insertions(+), ${deletions} deletions(-)`;
2021
2314
  }
2022
2315
  function emptyScopeStats() {
2023
- const z3 = emptyStat();
2316
+ const z4 = emptyStat();
2024
2317
  return {
2025
- commits: z3,
2026
- uncommitted: z3,
2027
- staged: z3,
2028
- unstaged: z3,
2029
- last_turn: z3
2318
+ commits: z4,
2319
+ uncommitted: z4,
2320
+ staged: z4,
2321
+ unstaged: z4,
2322
+ last_turn: z4
2030
2323
  };
2031
2324
  }
2032
2325
  function filesFromDiff(nameStatus, numstat, combinedDiff, maxHunk) {
2033
2326
  const patches = splitCombinedDiff(combinedDiff);
2034
2327
  if (!nameStatus.trim() && patches.size > 0) {
2035
2328
  return [...patches.entries()].map(
2036
- ([path, patch]) => fileFromPatch(path, patch, maxHunk)
2329
+ ([path2, patch]) => fileFromPatch(path2, patch, maxHunk)
2037
2330
  );
2038
2331
  }
2039
2332
  const counts = parseNumstat(numstat);
2040
2333
  const files = [];
2041
- for (const { status, path } of parseNameStatus(nameStatus)) {
2042
- const n = counts.get(path);
2334
+ for (const { status, path: path2 } of parseNameStatus(nameStatus)) {
2335
+ const n = counts.get(path2);
2043
2336
  files.push({
2044
- path,
2337
+ path: path2,
2045
2338
  status,
2046
- patch: capPatch(patches.get(path) ?? "", maxHunk),
2339
+ patch: capPatch(patches.get(path2) ?? "", maxHunk),
2047
2340
  additions: n?.additions,
2048
2341
  deletions: n?.deletions
2049
2342
  });
@@ -2127,7 +2420,7 @@ function emptyPathDiff(scope, base, hasLastTurnBase, commitSha = null) {
2127
2420
  };
2128
2421
  }
2129
2422
  async function getPathDiff(worktreePath, repoPath, opts) {
2130
- const { path, scope, commitSha, lastTurnBase, hasLastTurnBase, includePatches, maxHunk } = opts;
2423
+ const { path: path2, scope, commitSha, lastTurnBase, hasLastTurnBase, includePatches, maxHunk } = opts;
2131
2424
  if (scope === "last_turn" && !lastTurnBase) {
2132
2425
  return emptyPathDiff(scope, "last turn", false);
2133
2426
  }
@@ -2166,7 +2459,7 @@ async function getPathDiff(worktreePath, repoPath, opts) {
2166
2459
  labelBase = "HEAD";
2167
2460
  }
2168
2461
  const diff = await gitDiff(
2169
- includePatches ? [...diffArgs, "--", path] : ["--name-status", ...diffArgs, "--", path],
2462
+ includePatches ? [...diffArgs, "--", path2] : ["--name-status", ...diffArgs, "--", path2],
2170
2463
  worktreePath,
2171
2464
  { timeoutMs: 4e3 }
2172
2465
  );
@@ -2174,8 +2467,8 @@ async function getPathDiff(worktreePath, repoPath, opts) {
2174
2467
  if (includePatches) {
2175
2468
  const parsed = filesFromDiff("", "", diff.stdout, maxHunk);
2176
2469
  for (const file of parsed) filesMap.set(file.path, file);
2177
- if (diff.stdout.trim() && !filesMap.has(path)) {
2178
- filesMap.set(path, fileFromPatch(path, diff.stdout, maxHunk));
2470
+ if (diff.stdout.trim() && !filesMap.has(path2)) {
2471
+ filesMap.set(path2, fileFromPatch(path2, diff.stdout, maxHunk));
2179
2472
  }
2180
2473
  } else {
2181
2474
  for (const file of filesFromDiff(diff.stdout, "", "", maxHunk)) {
@@ -2183,7 +2476,7 @@ async function getPathDiff(worktreePath, repoPath, opts) {
2183
2476
  }
2184
2477
  }
2185
2478
  if (filesMap.size === 0) {
2186
- const maybe = await listUntrackedPaths(worktreePath, path);
2479
+ const maybe = await listUntrackedPaths(worktreePath, path2);
2187
2480
  if (maybe.length > 0) {
2188
2481
  const extra = includePatches ? await listUntrackedFiles(worktreePath, maxHunk, maybe) : untrackedStubs(maybe);
2189
2482
  for (const file of extra) filesMap.set(file.path, file);
@@ -3016,16 +3309,16 @@ var PASTE_ATTACH_MIN_CHARS = 1200;
3016
3309
  var PASTE_ATTACH_MIN_LINES = 15;
3017
3310
  var PASTED_NAME_RE = /^Pasted text #(\d+)\.txt$/i;
3018
3311
  var PASTED_NAME_ALT_RE = /^pasted-(\d+)\.txt$/i;
3019
- function pastedTextStats(text2) {
3020
- const chars = text2.length;
3312
+ function pastedTextStats(text3) {
3313
+ const chars = text3.length;
3021
3314
  if (chars === 0) return { chars: 0, lines: 0 };
3022
- const lines = text2.split(/\r\n|\r|\n/).length;
3315
+ const lines = text3.split(/\r\n|\r|\n/).length;
3023
3316
  return { chars, lines };
3024
3317
  }
3025
- function shouldAttachPastedText(text2) {
3026
- const trimmed = text2.trim();
3318
+ function shouldAttachPastedText(text3) {
3319
+ const trimmed = text3.trim();
3027
3320
  if (!trimmed) return false;
3028
- const { chars, lines } = pastedTextStats(text2);
3321
+ const { chars, lines } = pastedTextStats(text3);
3029
3322
  return chars >= PASTE_ATTACH_MIN_CHARS || lines >= PASTE_ATTACH_MIN_LINES;
3030
3323
  }
3031
3324
  function nextPastedTextName(existing) {
@@ -3036,13 +3329,13 @@ function nextPastedTextName(existing) {
3036
3329
  }
3037
3330
  return `Pasted text #${max + 1}.txt`;
3038
3331
  }
3039
- function buildPastedTextAttachment(text2, opts) {
3332
+ function buildPastedTextAttachment(text3, opts) {
3040
3333
  return {
3041
3334
  id: opts?.id ?? randomUUID2(),
3042
3335
  name: opts?.name ?? "Pasted text #1.txt",
3043
3336
  kind: "file",
3044
3337
  path: opts?.path,
3045
- content: text2
3338
+ content: text3
3046
3339
  };
3047
3340
  }
3048
3341
 
@@ -3090,9 +3383,9 @@ async function tryClaudeSummary(transcript, opts) {
3090
3383
  { cwd: opts?.cwd, reject: false }
3091
3384
  );
3092
3385
  if (exitCode !== 0) return null;
3093
- const text2 = stdout.trim();
3094
- if (text2.length < 40) return null;
3095
- return text2;
3386
+ const text3 = stdout.trim();
3387
+ if (text3.length < 40) return null;
3388
+ return text3;
3096
3389
  } catch {
3097
3390
  return null;
3098
3391
  }
@@ -3141,9 +3434,9 @@ function extractiveSummary(transcript) {
3141
3434
  );
3142
3435
  return parts.join("\n");
3143
3436
  }
3144
- function clipSummary(text2) {
3145
- if (text2.length <= MAX_SUMMARY_CHARS) return text2;
3146
- return `${text2.slice(0, MAX_SUMMARY_CHARS)}
3437
+ function clipSummary(text3) {
3438
+ if (text3.length <= MAX_SUMMARY_CHARS) return text3;
3439
+ return `${text3.slice(0, MAX_SUMMARY_CHARS)}
3147
3440
 
3148
3441
  [\u2026summary truncated\u2026]`;
3149
3442
  }
@@ -3206,8 +3499,8 @@ function splitForCompaction(messages, thresholds = {}) {
3206
3499
  function formatToolPart(t, detail) {
3207
3500
  if (detail === "summary") {
3208
3501
  const label = t.description || t.detail || t.name;
3209
- const path = t.filePath ? ` (${t.filePath})` : "";
3210
- return `- ${t.name}: ${label}${path}`;
3502
+ const path2 = t.filePath ? ` (${t.filePath})` : "";
3503
+ return `- ${t.name}: ${label}${path2}`;
3211
3504
  }
3212
3505
  const lines = [`#### Tool: ${t.name}`, `Status: ${t.status}`];
3213
3506
  if (t.description) lines.push(`Description: ${t.description}`);
@@ -3545,7 +3838,7 @@ async function createThread(input, _onSetupLine) {
3545
3838
  return readThread(thread.id) ?? thread;
3546
3839
  }
3547
3840
  async function listLinearIssues(agent, repoPath) {
3548
- const { getAdapter: getAdapter2 } = await import("./agents-H7M5CQY2.js");
3841
+ const { getAdapter: getAdapter2 } = await import("./agents-EWPHOM6Y.js");
3549
3842
  await requireAgent(agent, { requireLinear: true });
3550
3843
  const adapter = getAdapter2(agent);
3551
3844
  if (!adapter.listLinearIssues) {
@@ -4043,13 +4336,13 @@ function resolveConductorCursorAgentId(workspacePath) {
4043
4336
  for (const hash of hashes) {
4044
4337
  const agentsFile = join7(CURSOR_SDK_STORE, hash, "agents.ndjson");
4045
4338
  if (!existsSync9(agentsFile)) continue;
4046
- let text2;
4339
+ let text3;
4047
4340
  try {
4048
- text2 = readFileSync7(agentsFile, "utf8");
4341
+ text3 = readFileSync7(agentsFile, "utf8");
4049
4342
  } catch {
4050
4343
  continue;
4051
4344
  }
4052
- for (const line of text2.split("\n")) {
4345
+ for (const line of text3.split("\n")) {
4053
4346
  const trimmed = line.trim();
4054
4347
  if (!trimmed) continue;
4055
4348
  try {
@@ -4088,7 +4381,7 @@ async function adoptThread(input) {
4088
4381
  messages: input.messages ?? []
4089
4382
  });
4090
4383
  writeThread(thread);
4091
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-W72KZL4B.js");
4384
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-TYPJNUSM.js");
4092
4385
  await ensureWorkspace2(repoPath);
4093
4386
  return thread;
4094
4387
  }
@@ -4339,10 +4632,10 @@ function storePath() {
4339
4632
  return join8(appDataDir(), "slack-reply-to.json");
4340
4633
  }
4341
4634
  function readStore() {
4342
- const path = storePath();
4343
- if (!existsSync10(path)) return {};
4635
+ const path2 = storePath();
4636
+ if (!existsSync10(path2)) return {};
4344
4637
  try {
4345
- const parsed = isSecureFileEncrypted(path) ? readSecureJson(path) : JSON.parse(readFileSync8(path, "utf8"));
4638
+ const parsed = isSecureFileEncrypted(path2) ? readSecureJson(path2) : JSON.parse(readFileSync8(path2, "utf8"));
4346
4639
  return parsed?.targets && typeof parsed.targets === "object" ? parsed.targets : {};
4347
4640
  } catch {
4348
4641
  return {};
@@ -4365,12 +4658,12 @@ function storePath2() {
4365
4658
  }
4366
4659
  function readStore2() {
4367
4660
  try {
4368
- const path = storePath2();
4369
- const wasEncrypted = isSecureFileEncrypted(path);
4370
- const parsed = readSecureJson(path);
4661
+ const path2 = storePath2();
4662
+ const wasEncrypted = isSecureFileEncrypted(path2);
4663
+ const parsed = readSecureJson(path2);
4371
4664
  const workspaces = Array.isArray(parsed?.workspaces) ? parsed.workspaces : [];
4372
4665
  if (workspaces.length > 0 && !wasEncrypted && resolveVaultKey()) {
4373
- writeSecureJson(path, { workspaces });
4666
+ writeSecureJson(path2, { workspaces });
4374
4667
  }
4375
4668
  return workspaces;
4376
4669
  } catch {
@@ -4506,10 +4799,10 @@ function tsNewer(a, b) {
4506
4799
  return Number(a) > Number(b);
4507
4800
  }
4508
4801
  function readStore3() {
4509
- const path = storePath3();
4510
- if (!existsSync11(path)) return [];
4802
+ const path2 = storePath3();
4803
+ if (!existsSync11(path2)) return [];
4511
4804
  try {
4512
- const parsed = isSecureFileEncrypted(path) ? readSecureJson(path) : JSON.parse(readFileSync9(path, "utf8"));
4805
+ const parsed = isSecureFileEncrypted(path2) ? readSecureJson(path2) : JSON.parse(readFileSync9(path2, "utf8"));
4513
4806
  return Array.isArray(parsed?.watches) ? parsed.watches : [];
4514
4807
  } catch {
4515
4808
  return [];
@@ -4539,8 +4832,8 @@ ${input.permalink}` : "";
4539
4832
 
4540
4833
  ${body}${link}`;
4541
4834
  }
4542
- function isSlackExternalReplyPrompt(text2) {
4543
- return text2.startsWith("Slack reply from ") && text2.includes("not a command");
4835
+ function isSlackExternalReplyPrompt(text3) {
4836
+ return text3.startsWith("Slack reply from ") && text3.includes("not a command");
4544
4837
  }
4545
4838
  function pendingSlackExternalReplies(messages) {
4546
4839
  let i = messages.length - 1;
@@ -4564,9 +4857,9 @@ function formatSlackRepliesForTurn(replies) {
4564
4857
  function listSlackOutboundWatches() {
4565
4858
  return pruneWatches(readStore3());
4566
4859
  }
4567
- function formatOwnerSlackFyi(userName, text2) {
4860
+ function formatOwnerSlackFyi(userName, text3) {
4568
4861
  const who = userName.trim() || "Someone";
4569
- const body = text2.trim() || "(no text)";
4862
+ const body = text3.trim() || "(no text)";
4570
4863
  return `${who} replied in Slack:
4571
4864
  ${body}`;
4572
4865
  }
@@ -4581,7 +4874,7 @@ async function relayExternalReply(opts) {
4581
4874
  const thread = readThread(threadId);
4582
4875
  if (!thread || thread.status === "archived") return true;
4583
4876
  try {
4584
- const text2 = formatSlackExternalReplyPrompt({
4877
+ const text3 = formatSlackExternalReplyPrompt({
4585
4878
  userName: opts.reply.userName,
4586
4879
  kind: opts.watch.kind,
4587
4880
  toLabel: opts.watch.toLabel,
@@ -4590,7 +4883,7 @@ async function relayExternalReply(opts) {
4590
4883
  });
4591
4884
  appendMessage(threadId, {
4592
4885
  role: "agent",
4593
- text: text2,
4886
+ text: text3,
4594
4887
  ts: (/* @__PURE__ */ new Date()).toISOString()
4595
4888
  });
4596
4889
  } catch {
@@ -4787,7 +5080,7 @@ async function refreshSlackReplyBadges(opts) {
4787
5080
  const messages = await fetchMessages(token, watch, opts?.fetchImpl);
4788
5081
  const replies = messages.filter((m) => isHumanReply(m, watch)).sort((a, b) => Number(a.ts) - Number(b.ts));
4789
5082
  if (replies.length === 0) continue;
4790
- const injected = new Set(watch.injectedReplyTs ?? []);
5083
+ const injected2 = new Set(watch.injectedReplyTs ?? []);
4791
5084
  const collected = [...watch.replies ?? []];
4792
5085
  let lastSeenTs = watch.lastSeenTs;
4793
5086
  let latestUser;
@@ -4821,7 +5114,7 @@ async function refreshSlackReplyBadges(opts) {
4821
5114
  latestName = replyUserName;
4822
5115
  latestText = reply.text;
4823
5116
  latestPermalink = permalink;
4824
- if (injected.has(ts)) {
5117
+ if (injected2.has(ts)) {
4825
5118
  lastSeenTs = ts;
4826
5119
  continue;
4827
5120
  }
@@ -4832,7 +5125,7 @@ async function refreshSlackReplyBadges(opts) {
4832
5125
  fetchImpl: opts?.fetchImpl
4833
5126
  });
4834
5127
  if (!delivered) break;
4835
- injected.add(ts);
5128
+ injected2.add(ts);
4836
5129
  lastSeenTs = ts;
4837
5130
  }
4838
5131
  watches[i] = {
@@ -4844,7 +5137,7 @@ async function refreshSlackReplyBadges(opts) {
4844
5137
  replyTs: lastSeenTs,
4845
5138
  replyPreview: latestText.slice(0, 140),
4846
5139
  permalink: latestPermalink,
4847
- injectedReplyTs: [...injected],
5140
+ injectedReplyTs: [...injected2],
4848
5141
  replies: collected.slice(-MAX_REPLIES_PER_WATCH)
4849
5142
  };
4850
5143
  changed = true;
@@ -4873,8 +5166,8 @@ function shouldAutoArchiveOnPrMerge(opts) {
4873
5166
  // src/git/orphan-cleanup.ts
4874
5167
  import { existsSync as existsSync12, readdirSync as readdirSync4, statSync as statSync5 } from "fs";
4875
5168
  import { join as join11 } from "path";
4876
- function isSideboardWorktreePath(path) {
4877
- return path.includes("/.sideboard/worktrees/") || path.includes("/sideboard/workspaces/");
5169
+ function isSideboardWorktreePath(path2) {
5170
+ return path2.includes("/.sideboard/worktrees/") || path2.includes("/sideboard/workspaces/");
4878
5171
  }
4879
5172
  async function findOrphanWorktrees(repoPaths) {
4880
5173
  const threads = listThreads({ includeArchived: true });
@@ -4899,18 +5192,18 @@ async function findOrphanWorktrees(repoPaths) {
4899
5192
  try {
4900
5193
  const wts = await listWorktrees(repoPath);
4901
5194
  for (const wt of wts) {
4902
- const path = wt.path.replace(/\/$/, "");
4903
- if (!isSideboardWorktreePath(path)) continue;
4904
- if (known.has(path)) continue;
4905
- if (seen.has(path)) continue;
4906
- seen.add(path);
5195
+ const path2 = wt.path.replace(/\/$/, "");
5196
+ if (!isSideboardWorktreePath(path2)) continue;
5197
+ if (known.has(path2)) continue;
5198
+ if (seen.has(path2)) continue;
5199
+ seen.add(path2);
4907
5200
  let mtimeMs = 0;
4908
5201
  try {
4909
- mtimeMs = statSync5(path).mtimeMs;
5202
+ mtimeMs = statSync5(path2).mtimeMs;
4910
5203
  } catch {
4911
5204
  mtimeMs = 0;
4912
5205
  }
4913
- orphans.push({ path, repoPath, mtimeMs });
5206
+ orphans.push({ path: path2, repoPath, mtimeMs });
4914
5207
  }
4915
5208
  } catch {
4916
5209
  }
@@ -4919,17 +5212,17 @@ async function findOrphanWorktrees(repoPaths) {
4919
5212
  if (existsSync12(root)) {
4920
5213
  for (const entry of readdirSync4(root, { withFileTypes: true })) {
4921
5214
  if (!entry.isDirectory()) continue;
4922
- const path = join11(root, entry.name).replace(/\/$/, "");
4923
- if (known.has(path) || seen.has(path)) continue;
4924
- if (!existsSync12(join11(path, ".git"))) continue;
4925
- seen.add(path);
5215
+ const path2 = join11(root, entry.name).replace(/\/$/, "");
5216
+ if (known.has(path2) || seen.has(path2)) continue;
5217
+ if (!existsSync12(join11(path2, ".git"))) continue;
5218
+ seen.add(path2);
4926
5219
  let mtimeMs = 0;
4927
5220
  try {
4928
- mtimeMs = statSync5(path).mtimeMs;
5221
+ mtimeMs = statSync5(path2).mtimeMs;
4929
5222
  } catch {
4930
5223
  mtimeMs = Date.now();
4931
5224
  }
4932
- orphans.push({ path, repoPath, mtimeMs });
5225
+ orphans.push({ path: path2, repoPath, mtimeMs });
4933
5226
  }
4934
5227
  }
4935
5228
  } catch {
@@ -5312,9 +5605,9 @@ function ensureReviewRequestFile(worktreePath) {
5312
5605
  const localAbs = join13(worktreePath, REVIEW_REQUEST_PATH);
5313
5606
  const localContent = readTextIfPresent(localAbs) ?? readTextIfPresent(join13(worktreePath, LEGACY_REVIEW_REQUEST_PATH));
5314
5607
  if (localContent && !shouldRefreshReviewRequestTemplate(localContent)) {
5315
- const path = existsSync14(localAbs) ? REVIEW_REQUEST_PATH : LEGACY_REVIEW_REQUEST_PATH;
5608
+ const path2 = existsSync14(localAbs) ? REVIEW_REQUEST_PATH : LEGACY_REVIEW_REQUEST_PATH;
5316
5609
  return {
5317
- path,
5610
+ path: path2,
5318
5611
  name: REVIEW_REQUEST_NAME,
5319
5612
  content: localContent,
5320
5613
  source: "local"
@@ -5330,13 +5623,13 @@ function ensureReviewRequestFile(worktreePath) {
5330
5623
  };
5331
5624
  }
5332
5625
  function buildReviewRequestAttachment(content, opts) {
5333
- const path = opts?.path ?? REVIEW_REQUEST_PATH;
5334
- const name = opts?.name ?? (path === REPO_REVIEW_PATH ? REPO_REVIEW_NAME : REVIEW_REQUEST_NAME);
5626
+ const path2 = opts?.path ?? REVIEW_REQUEST_PATH;
5627
+ const name = opts?.name ?? (path2 === REPO_REVIEW_PATH ? REPO_REVIEW_NAME : REVIEW_REQUEST_NAME);
5335
5628
  return {
5336
5629
  id: randomUUID4(),
5337
5630
  name,
5338
5631
  kind: "file",
5339
- path,
5632
+ path: path2,
5340
5633
  content
5341
5634
  };
5342
5635
  }
@@ -5421,8 +5714,8 @@ function buildQuotaHandoffAttachment(from, limitText, fallbackAgent) {
5421
5714
  );
5422
5715
  const recent = from.messages.slice(-8).map((m) => {
5423
5716
  const role = m.role === "user" ? "User" : m.role === "agent" ? "Agent" : "Summary";
5424
- const text2 = m.text.trim().replace(/\s+/g, " ").slice(0, 280);
5425
- return text2 ? `- ${role}: ${text2}` : null;
5717
+ const text3 = m.text.trim().replace(/\s+/g, " ").slice(0, 280);
5718
+ return text3 ? `- ${role}: ${text3}` : null;
5426
5719
  }).filter(Boolean);
5427
5720
  const body = [
5428
5721
  `# Orchestration handoff`,
@@ -5605,7 +5898,7 @@ var Orchestrator = class {
5605
5898
  orphans: orphans.map((o) => ({ path: o.path, repoPath: o.repoPath }))
5606
5899
  });
5607
5900
  }
5608
- const { autoCleanupOrphansEnabled: autoCleanupOrphansEnabled2 } = await import("./app-settings-3TLA2EJ7.js");
5901
+ const { autoCleanupOrphansEnabled: autoCleanupOrphansEnabled2 } = await import("./app-settings-6IOQYGBK.js");
5609
5902
  if (autoCleanupOrphansEnabled2() && shouldRunWorktreeCleanup() && orphans.length > 0) {
5610
5903
  await cleanupOrphanWorktrees({ repoPaths });
5611
5904
  }
@@ -5762,7 +6055,7 @@ var Orchestrator = class {
5762
6055
  }
5763
6056
  async finishCreateThread(threadId, prompt) {
5764
6057
  await this.runSetupAfterCreate(threadId);
5765
- const { autoRunAfterSetupEnabled: autoRunAfterSetupEnabled2 } = await import("./app-settings-3TLA2EJ7.js");
6058
+ const { autoRunAfterSetupEnabled: autoRunAfterSetupEnabled2 } = await import("./app-settings-6IOQYGBK.js");
5766
6059
  if (autoRunAfterSetupEnabled2()) {
5767
6060
  try {
5768
6061
  await this.startDev(threadId);
@@ -5842,11 +6135,11 @@ var Orchestrator = class {
5842
6135
  return results;
5843
6136
  }
5844
6137
  /** Edit the text of a not-yet-started queued message. */
5845
- async editQueuedMessage(threadRef, index, text2) {
6138
+ async editQueuedMessage(threadRef, index, text3) {
5846
6139
  const thread = this.requireThread(threadRef);
5847
6140
  return withThreadLock(thread.id, async () => {
5848
6141
  const current = this.requireThread(thread.id);
5849
- const trimmed = text2.trim();
6142
+ const trimmed = text3.trim();
5850
6143
  if (!trimmed || index < 0 || index >= current.queue.length) {
5851
6144
  return current;
5852
6145
  }
@@ -6040,14 +6333,16 @@ var Orchestrator = class {
6040
6333
  }
6041
6334
  const isBrightsy = fresh.agent === "brightsy";
6042
6335
  const isOrchestration = isOrchestratorThread(fresh);
6336
+ const { autoRenameBranchEnabled: autoRenameBranchEnabled2, getGithubGitAuthMode: getGithubGitAuthMode2 } = await import("./app-settings-6IOQYGBK.js");
6337
+ const gitAuthMode = getGithubGitAuthMode2();
6043
6338
  const worktreeDirective = isBrightsy || isOrchestration ? null : formatWorktreeDirective(fresh, {
6044
6339
  githubSlug: await resolveGithubRepoSlug(fresh.worktreePath).catch(
6045
6340
  () => null
6046
- )
6341
+ ),
6342
+ gitAuthMode
6047
6343
  });
6048
6344
  const artifactDirective = isBrightsy ? null : formatArtifactDirective();
6049
6345
  const settings = loadWorkspaceSettings(fresh.worktreePath, fresh.repoPath);
6050
- const { autoRenameBranchEnabled: autoRenameBranchEnabled2 } = await import("./app-settings-3TLA2EJ7.js");
6051
6346
  const renameBranchDirective = !isBrightsy && !isOrchestration && autoRenameBranchEnabled2() ? formatRenameBranchDirective(fresh, {
6052
6347
  customPrompt: settings?.prompts?.renameBranch
6053
6348
  }) : null;
@@ -6606,6 +6901,14 @@ var Orchestrator = class {
6606
6901
  throw new Error(`${action} is not available on the global coordinator`);
6607
6902
  }
6608
6903
  }
6904
+ /** MCP set_caffeinate is a detached hold — closing the chat must not leave the Mac awake. */
6905
+ releaseOrchestratorCaffeinate(thread) {
6906
+ if (!isOrchestratorThread(thread)) return;
6907
+ try {
6908
+ releaseCaffeinateHoldForThread(thread.id);
6909
+ } catch {
6910
+ }
6911
+ }
6609
6912
  async diff(threadRef, opts) {
6610
6913
  const thread = this.requireThread(threadRef);
6611
6914
  this.assertNotGlobal(thread, "Diff");
@@ -6745,7 +7048,7 @@ var Orchestrator = class {
6745
7048
  updateThread(thread.id, { title: meta.title });
6746
7049
  }
6747
7050
  }
6748
- const { autoArchiveOnMergeEnabled: autoArchiveOnMergeEnabled2 } = await import("./app-settings-3TLA2EJ7.js");
7051
+ const { autoArchiveOnMergeEnabled: autoArchiveOnMergeEnabled2 } = await import("./app-settings-6IOQYGBK.js");
6749
7052
  const latest = this.requireThread(thread.id);
6750
7053
  if (!shouldAutoArchiveOnPrMerge({
6751
7054
  previousPrState: prevState || null,
@@ -6876,8 +7179,9 @@ var Orchestrator = class {
6876
7179
  return tab;
6877
7180
  }
6878
7181
  /**
6879
- * Queue a desktop-git-button prompt on a worktree agent (commit/push/PR/merge).
6880
- * Orchestrators use this instead of running git/gh from the synthetic home.
7182
+ * Desktop git buttons + MCP `ask_git`.
7183
+ * When the worktree is clean, push / open the PR here (HTTPS via `gh` if SSH
7184
+ * is missing). When dirty, queue the worktree agent to commit first.
6881
7185
  */
6882
7186
  async askGit(threadRef, action) {
6883
7187
  if (!AGENT_GIT_ACTIONS.includes(action)) {
@@ -6895,6 +7199,10 @@ var Orchestrator = class {
6895
7199
  "No pull request linked. Ask the worktree agent to open a draft PR first (ask_git create-draft)."
6896
7200
  );
6897
7201
  }
7202
+ if ((action === "commit-push" || action === "create-draft" || action === "create-web") && thread.worktreePath?.trim() && !await isDirty(thread.worktreePath)) {
7203
+ await this.pushAndMaybeOpenPr(thread, action);
7204
+ return this.requireThread(threadRef);
7205
+ }
6898
7206
  let prBase;
6899
7207
  if (action === "resolve-conflicts") {
6900
7208
  try {
@@ -6905,6 +7213,34 @@ var Orchestrator = class {
6905
7213
  }
6906
7214
  return this.send(threadRef, agentGitPrompt(action, { prBase }));
6907
7215
  }
7216
+ /** Push origin (gh HTTPS fallback) and create/update the PR when requested. */
7217
+ async pushAndMaybeOpenPr(thread, action) {
7218
+ const cwd = thread.worktreePath;
7219
+ const branch = await currentBranch(cwd);
7220
+ await pushBranch(cwd, branch);
7221
+ if (action === "commit-push") return;
7222
+ const base = await resolveDefaultBranch(thread.repoPath);
7223
+ const meta = await suggestPrMetadata(cwd, {
7224
+ base,
7225
+ fallbackTitle: thread.prTitle ?? thread.title
7226
+ });
7227
+ const url = await createOrUpdatePr(cwd, {
7228
+ title: meta.title,
7229
+ body: meta.body,
7230
+ base,
7231
+ head: branch,
7232
+ draft: action === "create-draft",
7233
+ web: action === "create-web"
7234
+ });
7235
+ if (!url) return;
7236
+ const patch = { prUrl: url, prTitle: meta.title };
7237
+ try {
7238
+ const fetched = await getPrMeta(cwd, url);
7239
+ if (fetched?.title) patch.prTitle = fetched.title;
7240
+ } catch {
7241
+ }
7242
+ updateThread(thread.id, patch);
7243
+ }
6908
7244
  setThreadOptions(threadRef, patch) {
6909
7245
  const thread = this.requireThread(threadRef);
6910
7246
  const next = {};
@@ -6975,6 +7311,7 @@ var Orchestrator = class {
6975
7311
  async archive(threadRef) {
6976
7312
  const thread = this.requireThread(threadRef);
6977
7313
  this.stop(thread.id);
7314
+ this.releaseOrchestratorCaffeinate(thread);
6978
7315
  if (isGlobalThread(thread)) {
6979
7316
  const archived2 = setStatus(thread.id, "archived");
6980
7317
  this.emit({ type: "status_changed", threadId: archived2.id, status: "archived" });
@@ -6999,7 +7336,7 @@ var Orchestrator = class {
6999
7336
  this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
7000
7337
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
7001
7338
  try {
7002
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-W72KZL4B.js");
7339
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-TYPJNUSM.js");
7003
7340
  await ensureWorkspace2(thread.repoPath);
7004
7341
  } catch {
7005
7342
  }
@@ -7009,6 +7346,7 @@ var Orchestrator = class {
7009
7346
  async purge(threadRef, opts) {
7010
7347
  const thread = this.requireThread(threadRef);
7011
7348
  this.stop(thread.id);
7349
+ this.releaseOrchestratorCaffeinate(thread);
7012
7350
  if (isGlobalThread(thread)) {
7013
7351
  deleteThreadRecord(thread.id);
7014
7352
  return;
@@ -7020,7 +7358,7 @@ var Orchestrator = class {
7020
7358
  await runArchiveScript(thread.repoPath, thread.worktreePath);
7021
7359
  } catch {
7022
7360
  }
7023
- const { deleteBranchOnPurgeEnabled: deleteBranchOnPurgeEnabled2 } = await import("./app-settings-3TLA2EJ7.js");
7361
+ const { deleteBranchOnPurgeEnabled: deleteBranchOnPurgeEnabled2 } = await import("./app-settings-6IOQYGBK.js");
7024
7362
  const deleteBranch = opts?.deleteBranch ?? deleteBranchOnPurgeEnabled2();
7025
7363
  await removeWorktree(thread.repoPath, thread.worktreePath, {
7026
7364
  deleteBranch: deleteBranch ? thread.branchName : void 0
@@ -7041,7 +7379,7 @@ var Orchestrator = class {
7041
7379
  return restored2;
7042
7380
  }
7043
7381
  if (!existsSync15(thread.worktreePath)) {
7044
- const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-VOCH3WS3.js");
7382
+ const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-5VLLFI5W.js");
7045
7383
  const { execa: execa6 } = await import("execa");
7046
7384
  const slug = thread.worktreePath.split("/").pop();
7047
7385
  const dest = thread.worktreePath;
@@ -7150,7 +7488,7 @@ async function startOrchestration(opts) {
7150
7488
  sourceRef: "default",
7151
7489
  ...createOpts
7152
7490
  }).catch(async () => {
7153
- const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-VOCH3WS3.js");
7491
+ const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-5VLLFI5W.js");
7154
7492
  const repo = await resolveRepoRoot2(repoPath);
7155
7493
  const def = await resolveDefaultBranch2(repo);
7156
7494
  return createThread({
@@ -7272,7 +7610,7 @@ function formatPlanQuestionsForChat(questions) {
7272
7610
  // src/mcp/server.ts
7273
7611
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7274
7612
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7275
- import { z as z2 } from "zod";
7613
+ import { z as z3 } from "zod";
7276
7614
  import { basename as basename4 } from "path";
7277
7615
 
7278
7616
  // src/mcp/archive-guard.ts
@@ -7468,9 +7806,9 @@ function labelGithubUrl(url) {
7468
7806
  return null;
7469
7807
  }
7470
7808
  if (!/(^|\.)github\.com$/i.test(parsed.hostname)) return null;
7471
- const path = parsed.pathname;
7809
+ const path2 = parsed.pathname;
7472
7810
  const hash = parsed.hash || "";
7473
- const pr = path.match(/\/([^/]+)\/([^/]+)\/pull\/(\d+)/i);
7811
+ const pr = path2.match(/\/([^/]+)\/([^/]+)\/pull\/(\d+)/i);
7474
7812
  if (pr) {
7475
7813
  const n = pr[3];
7476
7814
  if (/#discussion_r\d+/i.test(hash) || /#pullrequestreview-\d+/i.test(hash)) {
@@ -7481,11 +7819,11 @@ function labelGithubUrl(url) {
7481
7819
  }
7482
7820
  return { kind: "pr", label: `PR #${n}`, url: raw };
7483
7821
  }
7484
- const issueComment = path.match(/\/([^/]+)\/([^/]+)\/issues\/(\d+)/i);
7822
+ const issueComment = path2.match(/\/([^/]+)\/([^/]+)\/issues\/(\d+)/i);
7485
7823
  if (issueComment && /#issuecomment-\d+/i.test(hash)) {
7486
7824
  return { kind: "comment", label: `Issue #${issueComment[3]} comment`, url: raw };
7487
7825
  }
7488
- const blob = path.match(/\/([^/]+)\/([^/]+)\/blob\/[^/]+\/(.+)$/i);
7826
+ const blob = path2.match(/\/([^/]+)\/([^/]+)\/blob\/[^/]+\/(.+)$/i);
7489
7827
  if (blob) {
7490
7828
  const filePath = decodeURIComponent(blob[3]);
7491
7829
  const fileName = filePath.split("/").pop() || filePath;
@@ -7500,8 +7838,8 @@ function labelGithubUrl(url) {
7500
7838
  }
7501
7839
  return { kind: "other", label: "GitHub", url: raw };
7502
7840
  }
7503
- function appendGithubLink(text2, githubUrl) {
7504
- const body = text2.trimEnd();
7841
+ function appendGithubLink(text3, githubUrl) {
7842
+ const body = text3.trimEnd();
7505
7843
  if (!githubUrl?.trim()) return body;
7506
7844
  const labeled = labelGithubUrl(githubUrl);
7507
7845
  if (!labeled) {
@@ -7784,30 +8122,125 @@ function registerSlackTools(server) {
7784
8122
  );
7785
8123
  }
7786
8124
 
7787
- // src/mcp/server.ts
7788
- var MAX_ORCH_THREADS = 5;
7789
- var CREATE_THREAD_TIMEOUT_MS = 9e4;
7790
- function withTimeout(promise, ms, label) {
7791
- return new Promise((resolve, reject) => {
7792
- const timer = setTimeout(() => {
7793
- reject(new Error(`${label} timed out after ${Math.round(ms / 1e3)}s`));
7794
- }, ms);
7795
- promise.then(
7796
- (value) => {
7797
- clearTimeout(timer);
7798
- resolve(value);
7799
- },
7800
- (err) => {
7801
- clearTimeout(timer);
7802
- reject(err);
7803
- }
8125
+ // src/mcp/linear-tools.ts
8126
+ import { z as z2 } from "zod";
8127
+ function text2(payload, isError = false) {
8128
+ return {
8129
+ content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
8130
+ ...isError ? { isError: true } : {}
8131
+ };
8132
+ }
8133
+ function fail2(err) {
8134
+ return text2(
8135
+ { error: err instanceof Error ? err.message : String(err) },
8136
+ true
8137
+ );
8138
+ }
8139
+ var prioritySchema = z2.number().int().min(0).max(4).optional().describe("0 none, 1 urgent, 2 high, 3 medium, 4 low");
8140
+ function registerLinearTools(server) {
8141
+ server.tool(
8142
+ "linear_list_teams",
8143
+ "List Linear teams (id, key, name) and workflow states for the connected Account. Use team key (e.g. ENG) on linear_create_issue; use state name or type (started, completed) on create/update. Viewer id is for assignee=me.",
8144
+ {},
8145
+ async () => {
8146
+ try {
8147
+ return text2(await listLinearTeams());
8148
+ } catch (err) {
8149
+ return fail2(err);
8150
+ }
8151
+ }
8152
+ );
8153
+ server.tool(
8154
+ "linear_get_issue",
8155
+ "Get a Linear issue by uuid or identifier (ENG-123).",
8156
+ { id: z2.string() },
8157
+ async ({ id }) => {
8158
+ try {
8159
+ return text2(await getLinearIssue(id));
8160
+ } catch (err) {
8161
+ return fail2(err);
8162
+ }
8163
+ }
8164
+ );
8165
+ server.tool(
8166
+ "linear_create_issue",
8167
+ 'Create a Linear issue. Call linear_list_teams first. team is id, key, or name. state is name, type (unstarted/started/completed/canceled/backlog), or id. assignee is "me" or a user id.',
8168
+ {
8169
+ team: z2.string(),
8170
+ title: z2.string(),
8171
+ description: z2.string().optional(),
8172
+ state: z2.string().optional(),
8173
+ assignee: z2.string().nullable().optional(),
8174
+ priority: prioritySchema
8175
+ },
8176
+ async (args) => {
8177
+ try {
8178
+ return text2(await createLinearIssue(args));
8179
+ } catch (err) {
8180
+ return fail2(err);
8181
+ }
8182
+ }
8183
+ );
8184
+ server.tool(
8185
+ "linear_update_issue",
8186
+ 'Update a Linear issue (uuid or ENG-123). Pass at least one of title, description, state, assignee, priority. state is name, type, or id. assignee is "me", a user id, or null to unassign.',
8187
+ {
8188
+ id: z2.string(),
8189
+ title: z2.string().optional(),
8190
+ description: z2.string().optional(),
8191
+ state: z2.string().optional(),
8192
+ assignee: z2.string().nullable().optional(),
8193
+ priority: prioritySchema
8194
+ },
8195
+ async (args) => {
8196
+ try {
8197
+ return text2(await updateLinearIssue(args));
8198
+ } catch (err) {
8199
+ return fail2(err);
8200
+ }
8201
+ }
8202
+ );
8203
+ server.tool(
8204
+ "linear_comment",
8205
+ "Add a markdown comment on a Linear issue (uuid or ENG-123).",
8206
+ {
8207
+ id: z2.string(),
8208
+ body: z2.string()
8209
+ },
8210
+ async (args) => {
8211
+ try {
8212
+ return text2(await commentLinearIssue(args));
8213
+ } catch (err) {
8214
+ return fail2(err);
8215
+ }
8216
+ }
8217
+ );
8218
+ }
8219
+
8220
+ // src/mcp/server.ts
8221
+ var MAX_ORCH_THREADS = 5;
8222
+ var CREATE_THREAD_TIMEOUT_MS = 9e4;
8223
+ function withTimeout(promise, ms, label) {
8224
+ return new Promise((resolve, reject) => {
8225
+ const timer = setTimeout(() => {
8226
+ reject(new Error(`${label} timed out after ${Math.round(ms / 1e3)}s`));
8227
+ }, ms);
8228
+ promise.then(
8229
+ (value) => {
8230
+ clearTimeout(timer);
8231
+ resolve(value);
8232
+ },
8233
+ (err) => {
8234
+ clearTimeout(timer);
8235
+ reject(err);
8236
+ }
7804
8237
  );
7805
8238
  });
7806
8239
  }
7807
8240
  async function startMcpServer() {
7808
8241
  const orch = getOrchestrator();
7809
8242
  try {
7810
- const { maxConcurrentAgents: maxConcurrentAgents2 } = await import("./app-settings-3TLA2EJ7.js");
8243
+ const { maxConcurrentAgents: maxConcurrentAgents2 } = await import("./app-settings-6IOQYGBK.js");
7811
8244
  orch.setMaxConcurrent(maxConcurrentAgents2());
7812
8245
  } catch {
7813
8246
  }
@@ -7860,7 +8293,7 @@ async function startMcpServer() {
7860
8293
  server.tool(
7861
8294
  "get_thread",
7862
8295
  "Get a compact thread summary by id/ref",
7863
- { ref: z2.string() },
8296
+ { ref: z3.string() },
7864
8297
  async ({ ref }) => {
7865
8298
  const t = orch.getThread(ref);
7866
8299
  if (!t) {
@@ -7889,14 +8322,14 @@ async function startMcpServer() {
7889
8322
  "present_artifact",
7890
8323
  "Show an HTML, SVG, markdown, or React document in Sideboard\u2019s Claude-style side column (desktop). Use instead of claude.ai\u2019s artifact tool \u2014 pass the full document content. Prefer type=html for interactive pages. For type=react, pass a single component module that `export default`s a component (JSX/TSX ok) \u2014 Sideboard bootstraps React/ReactDOM/Babel and renders it; only `react`/`react-dom` imports are available, no other npm packages.",
7891
8324
  {
7892
- title: z2.string().describe("Short title shown in the artifact pane header"),
7893
- type: z2.enum(["html", "svg", "markdown", "react"]).describe(
8325
+ title: z3.string().describe("Short title shown in the artifact pane header"),
8326
+ type: z3.enum(["html", "svg", "markdown", "react"]).describe(
7894
8327
  "Artifact kind \u2014 html opens an iframe preview; react transpiles and renders a default-exported component in a sandboxed iframe"
7895
8328
  ),
7896
- content: z2.string().describe(
8329
+ content: z3.string().describe(
7897
8330
  "Full document body (complete HTML page, SVG markup, markdown, or a React component module with a default export)"
7898
8331
  ),
7899
- artifact_id: z2.string().optional().describe("Stable id when updating the same artifact across turns")
8332
+ artifact_id: z3.string().optional().describe("Stable id when updating the same artifact across turns")
7900
8333
  },
7901
8334
  async ({ title, type, artifact_id }) => {
7902
8335
  const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
@@ -7914,15 +8347,15 @@ async function startMcpServer() {
7914
8347
  "ask_user",
7915
8348
  "Ask the user clarifying multiple-choice questions in Sideboard\u2019s composer (plan mode). Before calling, write a short chat message explaining the decision and what each option means. Include a description on every option. Use for approach forks and requirements \u2014 not for \u201Cis the plan ready?\u201D. After calling, stop and wait for their next message with answers.",
7916
8349
  {
7917
- questions: z2.array(
7918
- z2.object({
7919
- question: z2.string().describe("Full question text ending with ?"),
7920
- header: z2.string().max(24).optional().describe("Short label shown above the question"),
7921
- multiSelect: z2.boolean().optional().describe("Allow selecting multiple options"),
7922
- options: z2.array(
7923
- z2.object({
7924
- label: z2.string(),
7925
- description: z2.string().optional().describe("What this option means / when to choose it (strongly preferred)")
8350
+ questions: z3.array(
8351
+ z3.object({
8352
+ question: z3.string().describe("Full question text ending with ?"),
8353
+ header: z3.string().max(24).optional().describe("Short label shown above the question"),
8354
+ multiSelect: z3.boolean().optional().describe("Allow selecting multiple options"),
8355
+ options: z3.array(
8356
+ z3.object({
8357
+ label: z3.string(),
8358
+ description: z3.string().optional().describe("What this option means / when to choose it (strongly preferred)")
7926
8359
  })
7927
8360
  ).min(2).max(6).describe("2\u20136 choices (Sideboard also offers Other)")
7928
8361
  })
@@ -7941,9 +8374,9 @@ async function startMcpServer() {
7941
8374
  "present_plan",
7942
8375
  "Save the implementation plan as markdown to .context/attachments/plan.md and show it in Sideboard chat for user approval (Copy / Hand off / Approve). Call this when the plan is ready \u2014 required in plan mode. Pass the full plan body in content. Then Claude should call ExitPlanMode.",
7943
8376
  {
7944
- title: z2.string().optional().describe("Short plan title (defaults to Plan)"),
7945
- content: z2.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
7946
- thread_id: z2.string().optional().describe("Sideboard thread id when cwd is not the worktree")
8377
+ title: z3.string().optional().describe("Short plan title (defaults to Plan)"),
8378
+ content: z3.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
8379
+ thread_id: z3.string().optional().describe("Sideboard thread id when cwd is not the worktree")
7947
8380
  },
7948
8381
  async ({ title, content, thread_id }) => {
7949
8382
  const { writePlanFile: writePlanFile2 } = await import("./plan-file-6O7G4VPQ.js");
@@ -7952,10 +8385,10 @@ async function startMcpServer() {
7952
8385
  const t = orch.getThread(thread_id.trim());
7953
8386
  if (t?.worktreePath?.trim()) root = t.worktreePath;
7954
8387
  }
7955
- const path = writePlanFile2(root, content);
8388
+ const path2 = writePlanFile2(root, content);
7956
8389
  const payload = {
7957
8390
  ok: true,
7958
- path,
8391
+ path: path2,
7959
8392
  title: title?.trim() || "Plan",
7960
8393
  message: "Plan saved to .context/attachments/plan.md and shown in Sideboard chat for approval."
7961
8394
  };
@@ -7966,15 +8399,15 @@ async function startMcpServer() {
7966
8399
  "present_schema",
7967
8400
  "Open Sideboard\u2019s schema-driven side column (filterable table and/or form). Pass JSON Schema + optional schemaUi. Prefer datasource=inline with embedded resource/records. Use datasource=brightsy with resource_id only when the user is logged into Brightsy.",
7968
8401
  {
7969
- title: z2.string().describe("Pane title"),
7970
- mode: z2.enum(["table", "form"]).optional().describe("table = list/filter records; form = edit one record"),
7971
- datasource: z2.enum(["brightsy", "inline"]).optional().describe("brightsy resolves via login; inline uses embedded resource/records"),
7972
- resource_id: z2.string().optional().describe("Brightsy record type UUID (or generic resource id)"),
7973
- record_id: z2.string().optional().describe("Record id when opening form mode"),
7974
- resource: z2.record(z2.unknown()).optional().describe("Inline { id, title, schema, schemaUi?, slug? }"),
7975
- record: z2.record(z2.unknown()).optional().describe("Inline record { id, data, published_at? }"),
7976
- records: z2.array(z2.record(z2.unknown())).optional().describe("Inline records for table mode"),
7977
- pane_id: z2.string().optional().describe("Stable pane id across updates")
8402
+ title: z3.string().describe("Pane title"),
8403
+ mode: z3.enum(["table", "form"]).optional().describe("table = list/filter records; form = edit one record"),
8404
+ datasource: z3.enum(["brightsy", "inline"]).optional().describe("brightsy resolves via login; inline uses embedded resource/records"),
8405
+ resource_id: z3.string().optional().describe("Brightsy record type UUID (or generic resource id)"),
8406
+ record_id: z3.string().optional().describe("Record id when opening form mode"),
8407
+ resource: z3.record(z3.unknown()).optional().describe("Inline { id, title, schema, schemaUi?, slug? }"),
8408
+ record: z3.record(z3.unknown()).optional().describe("Inline record { id, data, published_at? }"),
8409
+ records: z3.array(z3.record(z3.unknown())).optional().describe("Inline records for table mode"),
8410
+ pane_id: z3.string().optional().describe("Stable pane id across updates")
7978
8411
  },
7979
8412
  async (args) => {
7980
8413
  const id = args.pane_id?.trim() || `schema_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
@@ -7995,10 +8428,10 @@ async function startMcpServer() {
7995
8428
  "present_files",
7996
8429
  "Open Sideboard\u2019s Files column (CMS-style file manager: browse, upload, pick). Use datasource=brightsy when the user is logged into Brightsy account storage; datasource=memory for a session-local demo store. Prefer this over claiming a file manager UI is unavailable. Pair with present_schema when editing records that need media.",
7997
8430
  {
7998
- title: z2.string().optional().describe("Pane title (default: Files)"),
7999
- datasource: z2.enum(["brightsy", "memory"]).optional().describe("brightsy = account storage via login; memory = session demo store"),
8000
- path: z2.string().optional().describe("Initial folder path (e.g. public)"),
8001
- pane_id: z2.string().optional().describe("Stable pane id across updates")
8431
+ title: z3.string().optional().describe("Pane title (default: Files)"),
8432
+ datasource: z3.enum(["brightsy", "memory"]).optional().describe("brightsy = account storage via login; memory = session demo store"),
8433
+ path: z3.string().optional().describe("Initial folder path (e.g. public)"),
8434
+ pane_id: z3.string().optional().describe("Stable pane id across updates")
8002
8435
  },
8003
8436
  async (args) => {
8004
8437
  const id = args.pane_id?.trim() || `files_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
@@ -8014,19 +8447,21 @@ async function startMcpServer() {
8014
8447
  }
8015
8448
  );
8016
8449
  registerSlackTools(server);
8450
+ registerLinearTools(server);
8017
8451
  if (sideboardMcpProfile() !== "worktree") {
8018
- const { getCaffeinateHold: getCaffeinateHold2, setCaffeinateHold: setCaffeinateHold2 } = await import("./caffeinate-hold-KLC6SABD.js");
8019
- const { resolveNewThreadOptions: resolveNewThreadOptions2, resolveThreadDefaults: resolveThreadDefaults2 } = await import("./app-settings-3TLA2EJ7.js");
8452
+ const { getCaffeinateHold: getCaffeinateHold2, setCaffeinateHold: setCaffeinateHold2 } = await import("./caffeinate-hold-EAZNWJBE.js");
8453
+ const { resolveNewThreadOptions: resolveNewThreadOptions2, resolveThreadDefaults: resolveThreadDefaults2 } = await import("./app-settings-6IOQYGBK.js");
8020
8454
  const accountDefaults = resolveThreadDefaults2();
8021
8455
  const accountDefaultsHint = `Account defaults: agent=${accountDefaults.agent}, model=${accountDefaults.model?.trim() || "Auto"}, effort=${accountDefaults.effort}`;
8022
8456
  server.tool(
8023
8457
  "set_caffeinate",
8024
- "Keep this Mac awake with caffeinate (like Claude Code) across turns \u2014 independent of Settings toggles. Turn ON when the user will be away from the keyboard, is driving work from Slack, or asks you to keep the machine awake. Turn OFF when they say they are done, wrapping up, going to sleep, or no longer need the Mac awake. macOS only.",
8458
+ "Keep this Mac awake with caffeinate (like Claude Code) across turns \u2014 independent of Settings toggles. Turn ON when the user will be away from the keyboard, is driving work from Slack, or asks you to keep the machine awake. Turn OFF when they say they are done, wrapping up, going to sleep, or no longer need the Mac awake. Closing or archiving this orchestration chat also releases the hold. macOS only.",
8025
8459
  {
8026
- enabled: z2.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
8460
+ enabled: z3.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
8027
8461
  },
8028
8462
  async ({ enabled }) => {
8029
- const state = setCaffeinateHold2(enabled);
8463
+ const threadId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || null;
8464
+ const state = setCaffeinateHold2(enabled, { threadId });
8030
8465
  if (enabled && !state.held) {
8031
8466
  return {
8032
8467
  content: [
@@ -8049,7 +8484,7 @@ async function startMcpServer() {
8049
8484
  text: JSON.stringify({
8050
8485
  ...getCaffeinateHold2(),
8051
8486
  ok: true,
8052
- message: state.held ? "Mac will stay awake until you call set_caffeinate with enabled=false (or the user says they are done)." : "Caffeinate hold released. The Mac can sleep (unless Settings caffeinate toggles are on)."
8487
+ message: state.held ? "Mac will stay awake until you call set_caffeinate with enabled=false, the user says they are done, or this orchestration chat is closed." : "Caffeinate hold released. The Mac can sleep (unless Settings caffeinate toggles are on)."
8053
8488
  })
8054
8489
  }
8055
8490
  ]
@@ -8060,15 +8495,15 @@ async function startMcpServer() {
8060
8495
  "create_thread",
8061
8496
  `Create a new worktree thread (chat) from branch, pr, or ticket. Pass repoPath from list_workspaces. 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}. Then use send_to_thread to chat.`,
8062
8497
  {
8063
- sourceType: z2.enum(["branch", "pr", "ticket"]),
8064
- sourceRef: z2.string(),
8065
- agent: z2.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
8066
- model: z2.string().nullable().optional().describe(
8498
+ sourceType: z3.enum(["branch", "pr", "ticket"]),
8499
+ sourceRef: z3.string(),
8500
+ agent: z3.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
8501
+ model: z3.string().nullable().optional().describe(
8067
8502
  `Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
8068
8503
  ),
8069
- repoPath: z2.string(),
8070
- title: z2.string().optional(),
8071
- parentThreadId: z2.string().optional().describe(
8504
+ repoPath: z3.string(),
8505
+ title: z3.string().optional(),
8506
+ parentThreadId: z3.string().optional().describe(
8072
8507
  "Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
8073
8508
  )
8074
8509
  },
@@ -8175,9 +8610,9 @@ async function startMcpServer() {
8175
8610
  "send_to_thread",
8176
8611
  "Queue a prompt on a worktree thread chat (runs under concurrency cap). Use after create_thread to start or continue a conversation. For commit/push/PR/merge, prefer ask_git (canonical desktop-button phrases). Set force_stop=true to interrupt an in-flight/queued turn (kill + clear queue) before queueing this prompt \u2014 use when the thread is mid-turn or has stale queued prompts you need to replace.",
8177
8612
  {
8178
- ref: z2.string(),
8179
- prompt: z2.string(),
8180
- force_stop: z2.boolean().optional()
8613
+ ref: z3.string(),
8614
+ prompt: z3.string(),
8615
+ force_stop: z3.boolean().optional()
8181
8616
  },
8182
8617
  async ({ ref, prompt, force_stop }) => {
8183
8618
  if (force_stop) {
@@ -8206,8 +8641,8 @@ async function startMcpServer() {
8206
8641
  "wait_for_turn",
8207
8642
  "Block until the thread finishes its current/queued turn (avoids polling). Use after send_to_thread or ask_git to read the agent reply.",
8208
8643
  {
8209
- ref: z2.string(),
8210
- timeoutMs: z2.number().optional()
8644
+ ref: z3.string(),
8645
+ timeoutMs: z3.number().optional()
8211
8646
  },
8212
8647
  async ({ ref, timeoutMs }) => {
8213
8648
  const thread = await orch.waitForTurn(ref, timeoutMs ?? 6e5);
@@ -8229,7 +8664,7 @@ async function startMcpServer() {
8229
8664
  server.tool(
8230
8665
  "get_turn_result",
8231
8666
  "Final assistant message only (not full transcript)",
8232
- { ref: z2.string() },
8667
+ { ref: z3.string() },
8233
8668
  async ({ ref }) => {
8234
8669
  const result = orch.getTurnResult(ref);
8235
8670
  return { content: [{ type: "text", text: JSON.stringify(result) }] };
@@ -8239,8 +8674,8 @@ async function startMcpServer() {
8239
8674
  "stop_thread",
8240
8675
  "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.",
8241
8676
  {
8242
- ref: z2.string(),
8243
- force: z2.boolean().optional()
8677
+ ref: z3.string(),
8678
+ force: z3.boolean().optional()
8244
8679
  },
8245
8680
  async ({ ref, force }) => {
8246
8681
  const t = orch.getThread(ref);
@@ -8270,7 +8705,7 @@ async function startMcpServer() {
8270
8705
  server.tool(
8271
8706
  "archive_thread",
8272
8707
  "Archive a thread (stops agent/dev, runs archive script, removes worktree when last chat tab). Coordinators commit, push, open PRs, and merge only by asking the worktree agent (ask_git).",
8273
- { ref: z2.string() },
8708
+ { ref: z3.string() },
8274
8709
  async ({ ref }) => {
8275
8710
  const t = orch.getThread(ref);
8276
8711
  if (!t) {
@@ -8303,7 +8738,7 @@ async function startMcpServer() {
8303
8738
  server.tool(
8304
8739
  "restore_thread",
8305
8740
  "Restore an archived thread (recreates worktree from branch when needed)",
8306
- { ref: z2.string() },
8741
+ { ref: z3.string() },
8307
8742
  async ({ ref }) => {
8308
8743
  try {
8309
8744
  const restored = await orch.restore(ref);
@@ -8329,8 +8764,8 @@ async function startMcpServer() {
8329
8764
  "get_diff",
8330
8765
  "Compact diff summary (capped hunks, paginated)",
8331
8766
  {
8332
- ref: z2.string(),
8333
- maxFiles: z2.number().optional()
8767
+ ref: z3.string(),
8768
+ maxFiles: z3.number().optional()
8334
8769
  },
8335
8770
  async ({ ref, maxFiles }) => {
8336
8771
  const summary = await orch.diffSummary(ref);
@@ -8350,7 +8785,7 @@ async function startMcpServer() {
8350
8785
  server.tool(
8351
8786
  "request_review",
8352
8787
  'Start a merge-readiness Review on a worktree agent thread (same as the desktop Review button). Opens a new Review chat tab, attaches .sideboard/review.md when present (else local Review request.md / stock template), and sends "Review changes in this workspace." Expect Approve / Approve with nits / Request changes / Needs more information. Pass a worktree thread ref \u2014 not the orchestrator. Then wait_for_turn / get_turn_result on the returned review tab id.',
8353
- { ref: z2.string().describe("Worktree thread id/ref to review") },
8788
+ { ref: z3.string().describe("Worktree thread id/ref to review") },
8354
8789
  async ({ ref }) => {
8355
8790
  try {
8356
8791
  const tab = await orch.requestReview(ref);
@@ -8377,10 +8812,10 @@ async function startMcpServer() {
8377
8812
  );
8378
8813
  server.tool(
8379
8814
  "ask_git",
8380
- "Tell a worktree agent to commit & push, open a draft PR, resolve conflicts, or merge the linked PR \u2014 same short prompts as the desktop git buttons. The worktree agent runs git/gh (`gh pr merge`); this only queues the prompt. Pass a worktree thread ref (not the orchestrator). Then wait_for_turn / get_turn_result. Do not run git or gh from the orchestration cwd.",
8815
+ "Commit & push, open a draft PR, resolve conflicts, or merge \u2014 same actions as the desktop git buttons. When the worktree is clean, Sideboard pushes / opens the PR itself (HTTPS via `gh` if SSH is missing). When dirty, queues the worktree agent to commit; then wait_for_turn. Pass a worktree thread ref (not the orchestrator). Do not run git or gh from the orchestration cwd.",
8381
8816
  {
8382
- ref: z2.string().describe("Worktree thread id/ref"),
8383
- action: z2.enum(AGENT_GIT_ACTIONS).describe(
8817
+ ref: z3.string().describe("Worktree thread id/ref"),
8818
+ action: z3.enum(AGENT_GIT_ACTIONS).describe(
8384
8819
  "commit-push | create-draft | create-web | resolve-conflicts | merge"
8385
8820
  )
8386
8821
  },
@@ -8407,7 +8842,7 @@ async function startMcpServer() {
8407
8842
  }
8408
8843
  }
8409
8844
  );
8410
- const agentEnum = z2.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
8845
+ const agentEnum = z3.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
8411
8846
  server.tool(
8412
8847
  "list_models",
8413
8848
  "List models for an agent. Prefer Auto: do not call this unless you have a reason to pin a specific model (user request, cost/latency, capability). Omit agent to list all.",
@@ -8430,11 +8865,11 @@ async function startMcpServer() {
8430
8865
  "fork_worktree",
8431
8866
  "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 on the returned id.",
8432
8867
  {
8433
- ref: z2.string().describe("Worktree thread id/ref to fork"),
8434
- through_index: z2.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
8868
+ ref: z3.string().describe("Worktree thread id/ref to fork"),
8869
+ through_index: z3.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
8435
8870
  agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
8436
- model: z2.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
8437
- title: z2.string().optional()
8871
+ model: z3.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
8872
+ title: z3.string().optional()
8438
8873
  },
8439
8874
  async ({ ref, through_index, agent, model, title }) => {
8440
8875
  try {
@@ -8475,11 +8910,11 @@ async function startMcpServer() {
8475
8910
  "fork_chat",
8476
8911
  "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 on the returned id. Use fork_worktree only for worktree agents that need a new git worktree.",
8477
8912
  {
8478
- ref: z2.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
8479
- through_index: z2.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
8913
+ ref: z3.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
8914
+ through_index: z3.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
8480
8915
  agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
8481
- model: z2.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
8482
- title: z2.string().optional()
8916
+ model: z3.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
8917
+ title: z3.string().optional()
8483
8918
  },
8484
8919
  async ({ ref, through_index, agent, model, title }) => {
8485
8920
  try {
@@ -8525,8 +8960,8 @@ async function startMcpServer() {
8525
8960
  "run_dev_script",
8526
8961
  "Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
8527
8962
  {
8528
- ref: z2.string(),
8529
- name: z2.string().optional()
8963
+ ref: z3.string(),
8964
+ name: z3.string().optional()
8530
8965
  },
8531
8966
  async ({ ref, name }) => {
8532
8967
  const result = await orch.startDev(ref, name);
@@ -8548,7 +8983,7 @@ async function startMcpServer() {
8548
8983
  server.tool(
8549
8984
  "list_run_scripts",
8550
8985
  "List named run scripts available for a thread",
8551
- { ref: z2.string() },
8986
+ { ref: z3.string() },
8552
8987
  async ({ ref }) => {
8553
8988
  const scripts = orch.listThreadRunScripts(ref);
8554
8989
  const active = orch.getActiveRuns(ref);
@@ -8566,8 +9001,8 @@ async function startMcpServer() {
8566
9001
  "stop_dev_script",
8567
9002
  "Stop a running script for a thread (all scripts if name omitted)",
8568
9003
  {
8569
- ref: z2.string(),
8570
- name: z2.string().optional()
9004
+ ref: z3.string(),
9005
+ name: z3.string().optional()
8571
9006
  },
8572
9007
  async ({ ref, name }) => {
8573
9008
  orch.stopDev(ref, name);
@@ -8577,7 +9012,7 @@ async function startMcpServer() {
8577
9012
  server.tool(
8578
9013
  "run_setup",
8579
9014
  "Re-run workspace setup (Sideboard/Conductor settings or .cursor/worktrees.json)",
8580
- { ref: z2.string() },
9015
+ { ref: z3.string() },
8581
9016
  async ({ ref }) => {
8582
9017
  const result = await orch.runSetup(ref);
8583
9018
  return {
@@ -8588,7 +9023,7 @@ async function startMcpServer() {
8588
9023
  server.tool(
8589
9024
  "add_workspace",
8590
9025
  "Register a git repo as a Sideboard workspace",
8591
- { repoPath: z2.string() },
9026
+ { repoPath: z3.string() },
8592
9027
  async ({ repoPath }) => {
8593
9028
  const ws = await orch.addWorkspace(repoPath);
8594
9029
  return { content: [{ type: "text", text: JSON.stringify(ws) }] };
@@ -8597,7 +9032,7 @@ async function startMcpServer() {
8597
9032
  server.tool(
8598
9033
  "remove_workspace",
8599
9034
  "Unregister a Sideboard workspace (does not archive threads)",
8600
- { repoPath: z2.string() },
9035
+ { repoPath: z3.string() },
8601
9036
  async ({ repoPath }) => {
8602
9037
  orch.removeWorkspace(repoPath);
8603
9038
  return { content: [{ type: "text", text: "ok" }] };
@@ -8607,12 +9042,12 @@ async function startMcpServer() {
8607
9042
  "fanout",
8608
9043
  "Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
8609
9044
  {
8610
- prompt: z2.string(),
8611
- agents: z2.array(z2.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
8612
- repoPath: z2.string(),
8613
- sourceType: z2.enum(["branch", "pr", "ticket"]).optional(),
8614
- sourceRef: z2.string().optional(),
8615
- title: z2.string().optional()
9045
+ prompt: z3.string(),
9046
+ agents: z3.array(z3.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
9047
+ repoPath: z3.string(),
9048
+ sourceType: z3.enum(["branch", "pr", "ticket"]).optional(),
9049
+ sourceRef: z3.string().optional(),
9050
+ title: z3.string().optional()
8616
9051
  },
8617
9052
  async (args) => {
8618
9053
  const threads = await orch.bestOfN(args);
@@ -8639,8 +9074,8 @@ async function startMcpServer() {
8639
9074
  "list_branches",
8640
9075
  "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).",
8641
9076
  {
8642
- repoPath: z2.string(),
8643
- unmergedOnly: z2.boolean().optional()
9077
+ repoPath: z3.string(),
9078
+ unmergedOnly: z3.boolean().optional()
8644
9079
  },
8645
9080
  async ({ repoPath, unmergedOnly }) => {
8646
9081
  const root = await resolveRepoRoot(repoPath);
@@ -8660,7 +9095,7 @@ async function startMcpServer() {
8660
9095
  server.tool(
8661
9096
  "list_prs",
8662
9097
  "List open GitHub PRs for a registered workspace. Pass repoPath from list_workspaces (uses gh against that repo remote). Then create_thread with sourceType=pr.",
8663
- { repoPath: z2.string() },
9098
+ { repoPath: z3.string() },
8664
9099
  async ({ repoPath }) => {
8665
9100
  const root = await resolveRepoRoot(repoPath);
8666
9101
  const prs = await listPrs(root);
@@ -8679,7 +9114,7 @@ async function startMcpServer() {
8679
9114
  server.tool(
8680
9115
  "get_pr_stack",
8681
9116
  "Load the GitHub PR stack for a thread worktree (`gh stack view --json`). Returns null JSON when the branch is not stacked. Prefer this before ask_git merge on stacked PRs.",
8682
- { ref: z2.string() },
9117
+ { ref: z3.string() },
8683
9118
  async ({ ref }) => {
8684
9119
  const stack = await orch.getPrStack(ref);
8685
9120
  return {
@@ -8691,8 +9126,8 @@ async function startMcpServer() {
8691
9126
  "open_pr_stack_layers",
8692
9127
  "Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
8693
9128
  {
8694
- ref: z2.string(),
8695
- layer: z2.number().int().positive().optional()
9129
+ ref: z3.string(),
9130
+ layer: z3.number().int().positive().optional()
8696
9131
  },
8697
9132
  async ({ ref, layer }) => {
8698
9133
  const result = await orch.openPrStackLayers(ref, { layer });
@@ -8726,9 +9161,9 @@ async function startMcpServer() {
8726
9161
  "add_stack_layer",
8727
9162
  "Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
8728
9163
  {
8729
- ref: z2.string(),
8730
- branchName: z2.string(),
8731
- title: z2.string().optional()
9164
+ ref: z3.string(),
9165
+ branchName: z3.string(),
9166
+ title: z3.string().optional()
8732
9167
  },
8733
9168
  async ({ ref, branchName, title }) => {
8734
9169
  const result = await orch.addStackLayer(ref, branchName, { title });
@@ -8757,11 +9192,11 @@ async function startMcpServer() {
8757
9192
  "create_pr_stack",
8758
9193
  "Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
8759
9194
  {
8760
- repoPath: z2.string(),
8761
- branches: z2.array(z2.string()).min(1),
8762
- agent: z2.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
8763
- base: z2.string().optional(),
8764
- title: z2.string().optional()
9195
+ repoPath: z3.string(),
9196
+ branches: z3.array(z3.string()).min(1),
9197
+ agent: z3.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
9198
+ base: z3.string().optional(),
9199
+ title: z3.string().optional()
8765
9200
  },
8766
9201
  async (args) => {
8767
9202
  const result = await orch.createPrStack({
@@ -8799,7 +9234,7 @@ async function startMcpServer() {
8799
9234
  server.tool(
8800
9235
  "list_issues",
8801
9236
  "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.",
8802
- { repoPath: z2.string() },
9237
+ { repoPath: z3.string() },
8803
9238
  async ({ repoPath }) => {
8804
9239
  const root = await resolveRepoRoot(repoPath);
8805
9240
  const result = await listIssues(root);
@@ -8810,8 +9245,8 @@ async function startMcpServer() {
8810
9245
  "list_linear_issues",
8811
9246
  "Deprecated: prefer list_issues. Lists assigned Linear issues via the chosen agent MCP connector",
8812
9247
  {
8813
- agent: z2.enum(["claude", "codex", "opencode"]),
8814
- repoPath: z2.string()
9248
+ agent: z3.enum(["claude", "codex", "opencode"]),
9249
+ repoPath: z3.string()
8815
9250
  },
8816
9251
  async ({ agent, repoPath }) => {
8817
9252
  const issues = await listLinearIssues(agent, repoPath);
@@ -8824,18 +9259,7 @@ async function startMcpServer() {
8824
9259
  }
8825
9260
 
8826
9261
  // src/brightsy/api.ts
8827
- function formatBrightsyFetchError(err, url) {
8828
- if (!(err instanceof Error)) return `${String(err)} (${url})`;
8829
- const cause = err.cause;
8830
- let detail = "";
8831
- if (cause instanceof Error) {
8832
- const code = typeof cause.code === "string" ? cause.code : void 0;
8833
- detail = code ? ` [${code}: ${cause.message}]` : ` [${cause.message}]`;
8834
- } else if (cause != null) {
8835
- detail = ` [${String(cause)}]`;
8836
- }
8837
- return `${err.message}${detail} (${url})`;
8838
- }
9262
+ var formatBrightsyFetchError = formatFetchError;
8839
9263
  var BrightsySideboardApi = class {
8840
9264
  cfg;
8841
9265
  fetchImpl;
@@ -8856,9 +9280,9 @@ var BrightsySideboardApi = class {
8856
9280
  get endpoint() {
8857
9281
  return (this.cfg.endpoint || "https://brightsy.ai").replace(/\/$/, "");
8858
9282
  }
8859
- async request(path, init = {}) {
9283
+ async request(path2, init = {}) {
8860
9284
  await this.refreshIfNeeded();
8861
- const url = `${this.endpoint}${path}`;
9285
+ const url = `${this.endpoint}${path2}`;
8862
9286
  const headers = {
8863
9287
  Authorization: `Bearer ${this.cfg.access_token}`,
8864
9288
  Accept: "application/json"
@@ -8955,8 +9379,8 @@ var BrightsySideboardApi = class {
8955
9379
  };
8956
9380
  function taskMessageText(task) {
8957
9381
  const parts = task.message?.parts ?? [];
8958
- const text2 = parts.find((p) => p.kind === "text")?.text ?? "";
8959
- return text2.trim();
9382
+ const text3 = parts.find((p) => p.kind === "text")?.text ?? "";
9383
+ return text3.trim();
8960
9384
  }
8961
9385
 
8962
9386
  // src/brightsy/cloud-connect.ts
@@ -9152,63 +9576,128 @@ async function runCloudConnect(opts) {
9152
9576
 
9153
9577
  // src/slack/oauth.ts
9154
9578
  import { randomBytes as randomBytes2 } from "crypto";
9155
- import { createServer as createServer3 } from "http";
9156
9579
 
9157
9580
  // src/slack/baked-app.ts
9158
9581
  var BAKED_SLACK_CLIENT_ID = "7592788819232.11813536272562";
9159
- var BAKED_SLACK_CLIENT_SECRET = "10290abf6ad0dc11fe6fc321a6215299";
9160
- var BAKED_SLACK_RELAY_URL = "wss://slack-relay.sideboard.cloud/desktop";
9582
+ var BAKED_SLACK_RELAY_URL = "wss://relay.sideboard.cloud/slack/desktop";
9161
9583
  function hasBakedSlackOAuth() {
9162
- return Boolean(BAKED_SLACK_CLIENT_ID.trim() && BAKED_SLACK_CLIENT_SECRET.trim());
9584
+ return Boolean(BAKED_SLACK_CLIENT_ID.trim());
9163
9585
  }
9164
9586
  function slackRelayUrl() {
9165
9587
  return process.env.SIDEBOARD_SLACK_RELAY_URL?.trim() || BAKED_SLACK_RELAY_URL.trim();
9166
9588
  }
9167
9589
 
9168
9590
  // src/slack/oauth-redirect.ts
9169
- var SLACK_OAUTH_PORT = 19847;
9170
- var SLACK_OAUTH_LOCAL_CALLBACK = `http://127.0.0.1:${SLACK_OAUTH_PORT}/callback`;
9171
- var SLACK_OAUTH_REDIRECT = "https://slack-relay.sideboard.cloud/callback";
9172
- var SLACK_OAUTH_BOUNCE_PATH = "/callback";
9173
- var BOUNCE_PARAMS = ["code", "state", "error", "error_description"];
9591
+ var SLACK_OAUTH_CALLBACK_PATH = "/slack/callback";
9592
+ var SLACK_OAUTH_RESULT_PATH = "/slack/oauth/result";
9593
+ var SLACK_RELAY_DESKTOP_PATH = "/slack/desktop";
9594
+ var SLACK_OAUTH_LOCAL_PORT = 19847;
9595
+ var SLACK_OAUTH_LOCAL_CALLBACK = `http://127.0.0.1:${SLACK_OAUTH_LOCAL_PORT}${SLACK_OAUTH_CALLBACK_PATH}`;
9596
+ var SLACK_OAUTH_REDIRECT = `https://relay.sideboard.cloud${SLACK_OAUTH_CALLBACK_PATH}`;
9174
9597
  function slackOAuthRedirectUri() {
9175
9598
  return process.env.SIDEBOARD_SLACK_OAUTH_REDIRECT?.trim() || SLACK_OAUTH_REDIRECT;
9176
9599
  }
9177
- function slackOAuthLocalBounceTarget(params) {
9178
- const dest = new URL(SLACK_OAUTH_LOCAL_CALLBACK);
9179
- for (const key of BOUNCE_PARAMS) {
9180
- const value = params.get(key);
9181
- if (value) dest.searchParams.set(key, value);
9182
- }
9183
- return dest.toString();
9184
- }
9185
9600
  function escapeHtml(value) {
9186
9601
  return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
9187
9602
  }
9188
- function slackOAuthBounceHtml(target) {
9189
- const safe = escapeHtml(target);
9190
- return `<!doctype html><html><head><meta charset="utf-8"><meta http-equiv="refresh" content="0;url=${safe}"><title>Connecting Slack</title>
9603
+ function slackOAuthHtmlPage(title, body) {
9604
+ return `<!doctype html><html><head><meta charset="utf-8"><title>${escapeHtml(title)}</title>
9191
9605
  <style>body{font-family:ui-sans-serif,system-ui,sans-serif;padding:48px 24px;max-width:36rem;margin:0 auto;color:#1a1a1a}
9192
- h1{font-size:1.25rem}p{line-height:1.5;color:#444}a{color:#111}</style>
9193
- <script>location.replace(${JSON.stringify(target)})</script>
9194
- </head><body><h1>Connecting Slack</h1><p>Returning to Sideboard\u2026 <a href="${safe}">Continue</a></p></body></html>`;
9606
+ h1{font-size:1.25rem}p{line-height:1.5;color:#444}</style></head><body>${body}</body></html>`;
9195
9607
  }
9196
- function slackOAuthBounceResponse(reqUrl) {
9608
+ function parseSlackOAuthCallbackUrl(reqUrl) {
9197
9609
  let url;
9198
9610
  try {
9199
9611
  url = new URL(reqUrl, SLACK_OAUTH_REDIRECT);
9200
9612
  } catch {
9201
9613
  return null;
9202
9614
  }
9203
- if (url.pathname !== SLACK_OAUTH_BOUNCE_PATH) return null;
9204
- const target = slackOAuthLocalBounceTarget(url.searchParams);
9615
+ if (url.pathname !== SLACK_OAUTH_CALLBACK_PATH) return null;
9616
+ return url;
9617
+ }
9618
+ function parseSlackOAuthResultUrl(reqUrl) {
9619
+ let url;
9620
+ try {
9621
+ url = new URL(reqUrl, SLACK_OAUTH_REDIRECT);
9622
+ } catch {
9623
+ return null;
9624
+ }
9625
+ if (url.pathname !== SLACK_OAUTH_RESULT_PATH) return null;
9626
+ return url;
9627
+ }
9628
+
9629
+ // src/slack/oauth-exchange.ts
9630
+ var PENDING_TTL_MS = 5 * 6e4;
9631
+ var SlackOAuthPendingStore = class {
9632
+ items = /* @__PURE__ */ new Map();
9633
+ put(state, value) {
9634
+ const id = state.trim();
9635
+ if (!id) return;
9636
+ this.sweep();
9637
+ const existing = this.items.get(id);
9638
+ if (existing && existing.value.ok && !value.ok) return;
9639
+ this.items.set(id, { expiresAt: Date.now() + PENDING_TTL_MS, value });
9640
+ }
9641
+ /** Consume a pending result. Missing/expired → null (desktop should keep polling). */
9642
+ take(state) {
9643
+ const id = state.trim();
9644
+ if (!id) return null;
9645
+ this.sweep();
9646
+ const row = this.items.get(id);
9647
+ if (!row) return null;
9648
+ this.items.delete(id);
9649
+ return row.value;
9650
+ }
9651
+ sweep() {
9652
+ const now = Date.now();
9653
+ for (const [key, row] of this.items) {
9654
+ if (row.expiresAt <= now) this.items.delete(key);
9655
+ }
9656
+ }
9657
+ };
9658
+ function slackRelayHttpOrigin(relayWsUrl) {
9659
+ const raw = (relayWsUrl ?? slackRelayUrl()).trim();
9660
+ const http = raw.replace(/^ws/i, "http");
9661
+ const u = new URL(http);
9662
+ return `${u.protocol}//${u.host}`;
9663
+ }
9664
+ function slackOAuthResultUrl(state, relayWsUrl) {
9665
+ const u = new URL(SLACK_OAUTH_RESULT_PATH, slackRelayHttpOrigin(relayWsUrl));
9666
+ u.searchParams.set("state", state);
9667
+ return u.toString();
9668
+ }
9669
+ function slackOAuthRelayClientId() {
9670
+ return process.env.SIDEBOARD_SLACK_CLIENT_ID?.trim() || BAKED_SLACK_CLIENT_ID.trim();
9671
+ }
9672
+ function slackOAuthRelayRedirectUri() {
9673
+ return slackOAuthRedirectUri() || SLACK_OAUTH_REDIRECT;
9674
+ }
9675
+ async function exchangeSlackOAuthCode(opts) {
9676
+ const body = new URLSearchParams({
9677
+ client_id: opts.clientId,
9678
+ client_secret: opts.clientSecret,
9679
+ code: opts.code,
9680
+ redirect_uri: opts.redirectUri
9681
+ });
9682
+ const fetchImpl = opts.fetchImpl ?? fetch;
9683
+ const res = await fetchImpl("https://slack.com/api/oauth.v2.access", {
9684
+ method: "POST",
9685
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
9686
+ body
9687
+ });
9688
+ const data = await res.json();
9689
+ if (!data.ok) {
9690
+ throw new Error(`Slack OAuth exchange failed: ${data.error || res.status}`);
9691
+ }
9692
+ const teamId = data.team?.id?.trim();
9693
+ if (!teamId) throw new Error("Slack OAuth did not return a team id");
9205
9694
  return {
9206
- status: 302,
9207
- headers: {
9208
- Location: target,
9209
- "Content-Type": "text/html; charset=utf-8"
9210
- },
9211
- body: slackOAuthBounceHtml(target)
9695
+ team_id: teamId,
9696
+ team_name: data.team?.name?.trim() || teamId,
9697
+ user_id: data.authed_user?.id?.trim() || void 0,
9698
+ bot_token: data.access_token?.trim() || void 0,
9699
+ user_token: data.authed_user?.access_token?.trim() || void 0,
9700
+ scopes: [data.scope, data.authed_user?.scope].filter(Boolean).join(",")
9212
9701
  };
9213
9702
  }
9214
9703
 
@@ -9244,13 +9733,13 @@ var SLACK_USER_SCOPES = [
9244
9733
  function slackOAuthCredentials() {
9245
9734
  const settings = loadAppSettings();
9246
9735
  const clientId = process.env.SIDEBOARD_SLACK_CLIENT_ID?.trim() || settings.integrations.slackClientId?.trim() || BAKED_SLACK_CLIENT_ID.trim();
9247
- const clientSecret = process.env.SIDEBOARD_SLACK_CLIENT_SECRET?.trim() || settings.integrations.slackClientSecret?.trim() || BAKED_SLACK_CLIENT_SECRET.trim();
9248
- if (!clientId || !clientSecret) {
9736
+ const clientSecret = process.env.SIDEBOARD_SLACK_CLIENT_SECRET?.trim() || settings.integrations.slackClientSecret?.trim() || "";
9737
+ if (!clientId) {
9249
9738
  throw new Error(
9250
- "Slack browser sign-in needs a Slack app Client ID and Secret (Account \u2192 Slack, or SIDEBOARD_SLACK_CLIENT_ID / SIDEBOARD_SLACK_CLIENT_SECRET). You can still paste an xoxb- or xoxp- token."
9739
+ "Slack browser sign-in needs a Slack app Client ID (Account \u2192 Slack, or SIDEBOARD_SLACK_CLIENT_ID). You can still paste an xoxb- or xoxp- token."
9251
9740
  );
9252
9741
  }
9253
- return { clientId, clientSecret };
9742
+ return { clientId, clientSecret: clientSecret || null };
9254
9743
  }
9255
9744
  var SLACK_OAUTH_CANCELLED = "Slack sign-in cancelled";
9256
9745
  var SlackOAuthCancelledError = class extends Error {
@@ -9275,127 +9764,100 @@ function slackOAuthAuthorizeUrl(clientId, state) {
9275
9764
  });
9276
9765
  return `https://slack.com/oauth/v2/authorize?${params.toString()}`;
9277
9766
  }
9278
- function htmlPage2(title, body) {
9279
- return `<!doctype html><html><head><meta charset="utf-8"><title>${title}</title>
9280
- <style>body{font-family:ui-sans-serif,system-ui,sans-serif;padding:48px 24px;max-width:36rem;margin:0 auto;color:#1a1a1a}
9281
- h1{font-size:1.25rem}p{line-height:1.5;color:#444}</style></head><body>${body}</body></html>`;
9767
+ function sleep(ms, signal) {
9768
+ return new Promise((resolve, reject) => {
9769
+ if (signal?.aborted) {
9770
+ reject(new SlackOAuthCancelledError());
9771
+ return;
9772
+ }
9773
+ const timer = setTimeout(() => {
9774
+ signal?.removeEventListener("abort", onAbort);
9775
+ resolve();
9776
+ }, ms);
9777
+ const onAbort = () => {
9778
+ clearTimeout(timer);
9779
+ reject(new SlackOAuthCancelledError());
9780
+ };
9781
+ signal?.addEventListener("abort", onAbort, { once: true });
9782
+ });
9783
+ }
9784
+ function upsertFromPayload(payload) {
9785
+ return upsertSlackWorkspace({
9786
+ team_id: payload.team_id,
9787
+ team_name: payload.team_name,
9788
+ user_id: payload.user_id,
9789
+ bot_token: payload.bot_token,
9790
+ user_token: payload.user_token,
9791
+ scopes: payload.scopes,
9792
+ connected_at: (/* @__PURE__ */ new Date()).toISOString()
9793
+ });
9282
9794
  }
9283
9795
  async function startSlackOAuth(opts) {
9284
9796
  if (opts?.signal?.aborted) {
9285
9797
  throw new SlackOAuthCancelledError();
9286
9798
  }
9287
- const { clientId, clientSecret } = slackOAuthCredentials();
9799
+ const { clientId } = slackOAuthCredentials();
9288
9800
  const state = randomBytes2(16).toString("hex");
9289
9801
  const authorizeUrl = slackOAuthAuthorizeUrl(clientId, state);
9290
9802
  const timeoutMs = opts?.timeoutMs ?? 5 * 6e4;
9291
- const code = await new Promise((resolve, reject) => {
9292
- let settled = false;
9293
- const finish = (err, value) => {
9294
- if (settled) return;
9295
- settled = true;
9296
- cleanup();
9297
- if (err) reject(err);
9298
- else resolve(value);
9299
- };
9300
- const server = createServer3((req, res2) => {
9301
- try {
9302
- const url = new URL(req.url || "/", `http://127.0.0.1:${SLACK_OAUTH_PORT}`);
9303
- if (url.pathname !== "/callback") {
9304
- res2.writeHead(404);
9305
- res2.end("Not found");
9306
- return;
9307
- }
9308
- const err = url.searchParams.get("error");
9309
- const gotState = url.searchParams.get("state");
9310
- const gotCode = url.searchParams.get("code");
9311
- if (err) {
9312
- res2.writeHead(400, { "Content-Type": "text/html" });
9313
- res2.end(htmlPage2("Slack", `<h1>Authorization cancelled</h1><p>${err}</p>`));
9314
- finish(new SlackOAuthCancelledError(`Slack OAuth: ${err}`));
9315
- return;
9316
- }
9317
- if (gotState !== state || !gotCode) {
9318
- res2.writeHead(400, { "Content-Type": "text/html" });
9319
- res2.end(htmlPage2("Slack", "<h1>Invalid callback</h1><p>State or code missing.</p>"));
9320
- finish(new Error("Slack OAuth callback was invalid"));
9321
- return;
9322
- }
9323
- res2.writeHead(200, { "Content-Type": "text/html" });
9324
- res2.end(
9325
- htmlPage2(
9326
- "Slack connected",
9327
- "<h1>Slack workspace connected</h1><p>You can close this tab and return to Sideboard.</p>"
9328
- )
9329
- );
9330
- finish(void 0, gotCode);
9331
- } catch (e) {
9332
- finish(e instanceof Error ? e : new Error(String(e)));
9333
- }
9334
- });
9335
- const timer = setTimeout(() => {
9336
- finish(new Error("Slack sign-in timed out \u2014 try again"));
9337
- }, timeoutMs);
9338
- const onAbort = () => finish(new SlackOAuthCancelledError());
9339
- const cleanup = () => {
9340
- clearTimeout(timer);
9341
- opts?.signal?.removeEventListener("abort", onAbort);
9342
- server.close();
9343
- };
9344
- opts?.signal?.addEventListener("abort", onAbort, { once: true });
9803
+ const pollIntervalMs = opts?.pollIntervalMs ?? 400;
9804
+ const fetchImpl = opts?.fetchImpl ?? fetch;
9805
+ const resultUrl = opts?.resultUrlForState?.(state) ?? slackOAuthResultUrl(state);
9806
+ await Promise.resolve(opts?.openUrl?.(authorizeUrl));
9807
+ if (opts?.signal?.aborted) {
9808
+ throw new SlackOAuthCancelledError();
9809
+ }
9810
+ const deadline = Date.now() + timeoutMs;
9811
+ while (Date.now() < deadline) {
9345
9812
  if (opts?.signal?.aborted) {
9346
- onAbort();
9347
- return;
9813
+ throw new SlackOAuthCancelledError();
9348
9814
  }
9349
- server.on("error", (e) => {
9350
- finish(
9351
- e instanceof Error && e.code === "EADDRINUSE" ? new Error(
9352
- `Port ${SLACK_OAUTH_PORT} is in use. Close whatever is bound there, or paste a Slack token instead.`
9353
- ) : e instanceof Error ? e : new Error(String(e))
9354
- );
9355
- });
9356
- server.listen(SLACK_OAUTH_PORT, "127.0.0.1", () => {
9357
- void Promise.resolve(opts?.openUrl?.(authorizeUrl)).catch((e) => {
9358
- finish(e instanceof Error ? e : new Error(String(e)));
9815
+ let res;
9816
+ try {
9817
+ res = await fetchImpl(resultUrl);
9818
+ } catch {
9819
+ if (opts?.signal?.aborted) throw new SlackOAuthCancelledError();
9820
+ await sleep(pollIntervalMs, opts?.signal);
9821
+ continue;
9822
+ }
9823
+ let data;
9824
+ try {
9825
+ data = await res.json();
9826
+ } catch {
9827
+ await sleep(pollIntervalMs, opts?.signal);
9828
+ continue;
9829
+ }
9830
+ if (data.ok && data.team_id?.trim()) {
9831
+ return upsertFromPayload({
9832
+ team_id: data.team_id.trim(),
9833
+ team_name: data.team_name?.trim() || data.team_id.trim(),
9834
+ user_id: data.user_id?.trim() || void 0,
9835
+ bot_token: data.bot_token?.trim() || void 0,
9836
+ user_token: data.user_token?.trim() || void 0,
9837
+ scopes: data.scopes?.trim() || ""
9359
9838
  });
9360
- });
9361
- });
9362
- const body = new URLSearchParams({
9363
- client_id: clientId,
9364
- client_secret: clientSecret,
9365
- code,
9366
- redirect_uri: slackOAuthRedirectUri()
9367
- });
9368
- const res = await fetch("https://slack.com/api/oauth.v2.access", {
9369
- method: "POST",
9370
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
9371
- body
9372
- });
9373
- const data = await res.json();
9374
- if (!data.ok) {
9375
- throw new Error(`Slack OAuth exchange failed: ${data.error || res.status}`);
9839
+ }
9840
+ const err = data.error?.trim();
9841
+ if (err && err !== "pending") {
9842
+ if (/cancel/i.test(err) || /access_denied/i.test(err)) {
9843
+ throw new SlackOAuthCancelledError(`Slack OAuth: ${err}`);
9844
+ }
9845
+ throw new Error(err.startsWith("Slack") ? err : `Slack OAuth: ${err}`);
9846
+ }
9847
+ await sleep(pollIntervalMs, opts?.signal);
9376
9848
  }
9377
- const teamId = data.team?.id?.trim();
9378
- if (!teamId) throw new Error("Slack OAuth did not return a team id");
9379
- return upsertSlackWorkspace({
9380
- team_id: teamId,
9381
- team_name: data.team?.name?.trim() || teamId,
9382
- user_id: data.authed_user?.id,
9383
- bot_token: data.access_token,
9384
- user_token: data.authed_user?.access_token,
9385
- scopes: [data.scope, data.authed_user?.scope].filter(Boolean).join(","),
9386
- connected_at: (/* @__PURE__ */ new Date()).toISOString()
9387
- });
9849
+ throw new Error("Slack sign-in timed out \u2014 try again");
9388
9850
  }
9389
9851
 
9390
9852
  // src/slack/socket-mode.ts
9391
9853
  import { WebSocket as WsWebSocket } from "ws";
9392
9854
  var BACKOFF_START_MS = 1e3;
9393
9855
  var BACKOFF_MAX_MS = 3e4;
9394
- function stripSlackMentions(text2) {
9395
- return text2.replace(/<@[^>]+>/g, "").replace(/\s+/g, " ").trim();
9856
+ function stripSlackMentions(text3) {
9857
+ return text3.replace(/<@[^>]+>/g, "").replace(/\s+/g, " ").trim();
9396
9858
  }
9397
- function isSlackStopCommand(text2) {
9398
- const t = stripSlackMentions(text2).toLowerCase();
9859
+ function isSlackStopCommand(text3) {
9860
+ const t = stripSlackMentions(text3).toLowerCase();
9399
9861
  return t === "stop" || t === "sideboard_force_stop";
9400
9862
  }
9401
9863
  function parseSlackSocketFrame(raw) {
@@ -9417,8 +9879,8 @@ function inboundFromSocketFrame(frame) {
9417
9879
  const ts = event.ts?.trim();
9418
9880
  if (!channelId || !ts) return null;
9419
9881
  const rawText = event.text?.trim() ?? "";
9420
- const text2 = stripSlackMentions(rawText);
9421
- if (!text2) return null;
9882
+ const text3 = stripSlackMentions(rawText);
9883
+ if (!text3) return null;
9422
9884
  const teamId = (frame.payload?.team_id || event.team || "").trim();
9423
9885
  if (!teamId) return null;
9424
9886
  const isDm = event.type === "message" && (event.channel_type === "im" || event.channel_type === "mpim" || !event.channel_type && channelId.startsWith("D"));
@@ -9430,7 +9892,7 @@ function inboundFromSocketFrame(frame) {
9430
9892
  ts,
9431
9893
  threadTs: event.thread_ts?.trim() || void 0,
9432
9894
  userId: event.user?.trim(),
9433
- text: text2,
9895
+ text: text3,
9434
9896
  kind: isMention ? "mention" : "dm"
9435
9897
  };
9436
9898
  }
@@ -9681,7 +10143,40 @@ function parseSlackRelayServerMessage(raw) {
9681
10143
  }
9682
10144
  }
9683
10145
 
10146
+ // src/slack/public-dns.ts
10147
+ import { lookup as systemLookup, Resolver } from "dns";
10148
+ import { request as httpRequest } from "http";
10149
+ import { request as httpsRequest } from "https";
10150
+ var PUBLIC_DNS = ["1.1.1.1", "8.8.8.8"];
10151
+ function finishLookup(options, callback, address, family) {
10152
+ if (options.all) {
10153
+ callback(null, [{ address, family }]);
10154
+ return;
10155
+ }
10156
+ callback(null, address, family);
10157
+ }
10158
+ var lookupPreferPublicDns = (hostname, options, callback) => {
10159
+ if (options.family === 6) {
10160
+ systemLookup(hostname, options, (err, address, family) => {
10161
+ callback(err, address, family);
10162
+ });
10163
+ return;
10164
+ }
10165
+ const resolver = new Resolver();
10166
+ resolver.setServers(PUBLIC_DNS);
10167
+ resolver.resolve4(hostname, (err, addresses) => {
10168
+ if (!err && addresses[0]) {
10169
+ finishLookup(options, callback, addresses[0], 4);
10170
+ return;
10171
+ }
10172
+ systemLookup(hostname, options, (sysErr, address, family) => {
10173
+ callback(sysErr, address, family);
10174
+ });
10175
+ });
10176
+ };
10177
+
9684
10178
  // src/slack/relay-client.ts
10179
+ import { WebSocket as WsWebSocket2 } from "ws";
9685
10180
  var BACKOFF_START_MS2 = 1e3;
9686
10181
  var BACKOFF_MAX_MS2 = 3e4;
9687
10182
  function wait2(ms, signal) {
@@ -9706,7 +10201,11 @@ function send(ws, msg) {
9706
10201
  }
9707
10202
  async function runSlackRelayClient(opts) {
9708
10203
  const log = opts.onLog ?? (() => void 0);
9709
- const Ws = resolveWebSocket(opts.WebSocketImpl);
10204
+ const Ws = opts.WebSocketImpl ? resolveWebSocket(opts.WebSocketImpl) : class {
10205
+ constructor(url2) {
10206
+ return new WsWebSocket2(url2, { lookup: lookupPreferPublicDns });
10207
+ }
10208
+ };
9710
10209
  const url = opts.url.trim();
9711
10210
  const deviceId = opts.deviceId.trim();
9712
10211
  if (!url) throw new Error("Slack relay URL is empty");
@@ -9869,12 +10368,12 @@ function formatSlackInboundPrompt(msg) {
9869
10368
 
9870
10369
  ${msg.text}`;
9871
10370
  }
9872
- function isSlackInboundUserPrompt(text2) {
9873
- return text2.startsWith("Slack DM\n") || text2.startsWith("Slack @mention\n");
10371
+ function isSlackInboundUserPrompt(text3) {
10372
+ return text3.startsWith("Slack DM\n") || text3.startsWith("Slack @mention\n");
9874
10373
  }
9875
- function formatSlackSignedReply(deviceLabel, text2) {
10374
+ function formatSlackSignedReply(deviceLabel, text3) {
9876
10375
  const label = deviceLabel.trim();
9877
- const body = text2.trim();
10376
+ const body = text3.trim();
9878
10377
  if (!body) return body;
9879
10378
  if (!label) return body;
9880
10379
  const head = `${label}:`;
@@ -9883,8 +10382,8 @@ function formatSlackSignedReply(deviceLabel, text2) {
9883
10382
  }
9884
10383
  return `${label}: ${body}`;
9885
10384
  }
9886
- function signForThisMac(text2) {
9887
- return formatSlackSignedReply(ensureSlackDeviceIdentity().deviceLabel, text2);
10385
+ function signForThisMac(text3) {
10386
+ return formatSlackSignedReply(ensureSlackDeviceIdentity().deviceLabel, text3);
9888
10387
  }
9889
10388
  function slackReplyThreadTs(msg) {
9890
10389
  if (msg.threadTs && msg.threadTs !== msg.ts) return msg.threadTs;
@@ -9927,7 +10426,7 @@ async function ackSlackInboundSeen(msg, opts) {
9927
10426
  log(`react error: ${errMsg}${hint}`);
9928
10427
  }
9929
10428
  }
9930
- async function postSlackText(target, text2, opts) {
10429
+ async function postSlackText(target, text3, opts) {
9931
10430
  const stub = {
9932
10431
  teamId: target.teamId,
9933
10432
  channelId: target.channelId,
@@ -9937,7 +10436,7 @@ async function postSlackText(target, text2, opts) {
9937
10436
  kind: "dm"
9938
10437
  };
9939
10438
  if (opts.postReply) {
9940
- await opts.postReply(stub, text2);
10439
+ await opts.postReply(stub, text3);
9941
10440
  return;
9942
10441
  }
9943
10442
  const token = writeTokenForTeam(target.teamId);
@@ -9946,29 +10445,29 @@ async function postSlackText(target, text2, opts) {
9946
10445
  "chat.postMessage",
9947
10446
  {
9948
10447
  channel: target.channelId,
9949
- text: text2,
10448
+ text: text3,
9950
10449
  thread_ts: target.threadTs
9951
10450
  },
9952
10451
  opts.fetchImpl
9953
10452
  );
9954
10453
  }
9955
- async function postSlackReply(msg, text2, opts) {
10454
+ async function postSlackReply(msg, text3, opts) {
9956
10455
  await postSlackText(
9957
10456
  {
9958
10457
  teamId: msg.teamId,
9959
10458
  channelId: msg.channelId,
9960
10459
  threadTs: slackReplyThreadTs(msg)
9961
10460
  },
9962
- signForThisMac(text2),
10461
+ signForThisMac(text3),
9963
10462
  opts
9964
10463
  );
9965
10464
  }
9966
10465
  var lastRelayed = /* @__PURE__ */ new Map();
9967
- function markRelayed(threadId, text2) {
9968
- lastRelayed.set(threadId, `${threadId}:${text2}`);
10466
+ function markRelayed(threadId, text3) {
10467
+ lastRelayed.set(threadId, `${threadId}:${text3}`);
9969
10468
  }
9970
- function alreadyRelayed(threadId, text2) {
9971
- return lastRelayed.get(threadId) === `${threadId}:${text2}`;
10469
+ function alreadyRelayed(threadId, text3) {
10470
+ return lastRelayed.get(threadId) === `${threadId}:${text3}`;
9972
10471
  }
9973
10472
  async function relayCoordinatorReplyToSlack(threadId, opts) {
9974
10473
  const target = getSlackReplyTarget(threadId);
@@ -9977,14 +10476,14 @@ async function relayCoordinatorReplyToSlack(threadId, opts) {
9977
10476
  const lastUser = thread ? [...thread.messages].reverse().find((m) => m.role === "user") : void 0;
9978
10477
  if (lastUser && isSlackInboundUserPrompt(lastUser.text)) return;
9979
10478
  const result = getOrchestrator().getTurnResult(threadId);
9980
- const text2 = result.text.trim();
9981
- if (!text2) return;
9982
- if (alreadyRelayed(threadId, text2)) return;
9983
- markRelayed(threadId, text2);
10479
+ const text3 = result.text.trim();
10480
+ if (!text3) return;
10481
+ if (alreadyRelayed(threadId, text3)) return;
10482
+ markRelayed(threadId, text3);
9984
10483
  const log = opts.onLog ?? (() => void 0);
9985
10484
  try {
9986
- await postSlackText(target, signForThisMac(text2), opts);
9987
- log(`replied ${target.threadTs ?? target.channelId} (${text2.length} chars)`);
10485
+ await postSlackText(target, signForThisMac(text3), opts);
10486
+ log(`replied ${target.threadTs ?? target.channelId} (${text3.length} chars)`);
9988
10487
  } catch (err) {
9989
10488
  lastRelayed.delete(threadId);
9990
10489
  const errMsg = err instanceof Error ? err.message : String(err);
@@ -10148,10 +10647,10 @@ function resolveSlackListenMode(opts) {
10148
10647
  }
10149
10648
 
10150
10649
  // src/slack/relay-hub.ts
10151
- function parseSlackDeviceDestination(text2) {
10152
- const m = text2.match(/^\s*(?:to\s+)?(?:@|#)?([A-Za-z][\w-]{0,63})\s*[::]\s*/);
10153
- if (!m) return { label: null, rest: text2 };
10154
- return { label: m[1], rest: text2.slice(m[0].length) };
10650
+ function parseSlackDeviceDestination(text3) {
10651
+ const m = text3.match(/^\s*(?:to\s+)?(?:@|#)?([A-Za-z][\w-]{0,63})\s*[::]\s*/);
10652
+ if (!m) return { label: null, rest: text3 };
10653
+ return { label: m[1], rest: text3.slice(m[0].length) };
10155
10654
  }
10156
10655
  var SlackRelayHub = class {
10157
10656
  sessions = /* @__PURE__ */ new Map();
@@ -10369,40 +10868,222 @@ var SlackRelayHub = class {
10369
10868
  };
10370
10869
 
10371
10870
  // src/slack/relay-server.ts
10372
- import { createServer as createServer4 } from "http";
10871
+ import { createServer as createServer3 } from "http";
10373
10872
  import { WebSocketServer } from "ws";
10873
+
10874
+ // src/slack/relay-static.ts
10875
+ import { createReadStream } from "fs";
10876
+ import { stat } from "fs/promises";
10877
+ import path from "path";
10878
+ var TYPES = {
10879
+ ".css": "text/css; charset=utf-8",
10880
+ ".html": "text/html; charset=utf-8",
10881
+ ".ico": "image/x-icon",
10882
+ ".jpeg": "image/jpeg",
10883
+ ".jpg": "image/jpeg",
10884
+ ".js": "text/javascript; charset=utf-8",
10885
+ ".json": "application/json; charset=utf-8",
10886
+ ".png": "image/png",
10887
+ ".svg": "image/svg+xml",
10888
+ ".txt": "text/plain; charset=utf-8",
10889
+ ".webp": "image/webp",
10890
+ ".woff2": "font/woff2"
10891
+ };
10892
+ function requestHostname(req) {
10893
+ const raw = req.headers.host ?? "";
10894
+ return raw.split(":")[0]?.toLowerCase() ?? "";
10895
+ }
10896
+ function hostnameAllowed(host, allowed) {
10897
+ if (!allowed || allowed.length === 0) return true;
10898
+ return allowed.includes(host);
10899
+ }
10900
+ function resolveStaticPath(root, requestUrl) {
10901
+ let pathname;
10902
+ try {
10903
+ pathname = decodeURIComponent(new URL(requestUrl, "http://relay.local").pathname);
10904
+ } catch {
10905
+ return null;
10906
+ }
10907
+ if (!pathname.startsWith("/") || pathname.includes("\0")) return null;
10908
+ const rootResolved = path.resolve(root);
10909
+ const candidate = path.resolve(rootResolved, `.${pathname}`);
10910
+ if (candidate !== rootResolved && !candidate.startsWith(rootResolved + path.sep)) {
10911
+ return null;
10912
+ }
10913
+ return candidate;
10914
+ }
10915
+ async function fileSize(file) {
10916
+ try {
10917
+ const st = await stat(file);
10918
+ return st.isFile() ? st.size : null;
10919
+ } catch {
10920
+ return null;
10921
+ }
10922
+ }
10923
+ function sendFile(req, res, file, size) {
10924
+ const ext = path.extname(file).toLowerCase();
10925
+ res.writeHead(200, {
10926
+ "Content-Type": TYPES[ext] ?? "application/octet-stream",
10927
+ "Content-Length": size,
10928
+ "Cache-Control": ext === ".html" ? "no-cache" : "public, max-age=3600"
10929
+ });
10930
+ if (req.method === "HEAD") {
10931
+ res.end();
10932
+ return true;
10933
+ }
10934
+ createReadStream(file).pipe(res);
10935
+ return true;
10936
+ }
10937
+ async function tryServeStatic(req, res, root) {
10938
+ if (req.method !== "GET" && req.method !== "HEAD") return false;
10939
+ const candidate = resolveStaticPath(root, req.url || "/");
10940
+ if (!candidate) return false;
10941
+ const direct = await fileSize(candidate);
10942
+ if (direct != null) return sendFile(req, res, candidate, direct);
10943
+ const asIndex = path.join(candidate, "index.html");
10944
+ const indexSize = await fileSize(asIndex);
10945
+ if (indexSize != null) return sendFile(req, res, asIndex, indexSize);
10946
+ return false;
10947
+ }
10948
+
10949
+ // src/slack/relay-server.ts
10950
+ function sendHtml(res, status, title, body) {
10951
+ res.writeHead(status, { "Content-Type": "text/html; charset=utf-8" });
10952
+ res.end(slackOAuthHtmlPage(title, body));
10953
+ }
10954
+ function sendJson(res, status, body) {
10955
+ res.writeHead(status, { "Content-Type": "application/json" });
10956
+ res.end(JSON.stringify(body));
10957
+ }
10374
10958
  async function startSlackRelayServer(opts) {
10375
10959
  const log = opts.onLog ?? console.log;
10376
10960
  const appToken = opts.appToken.trim();
10377
10961
  if (!appToken.startsWith("xapp-")) {
10378
10962
  throw new Error("SIDEBOARD_SLACK_APP_TOKEN must be an xapp-\u2026 app-level token");
10379
10963
  }
10964
+ const clientId = (opts.clientId ?? slackOAuthRelayClientId()).trim();
10965
+ const clientSecret = (opts.clientSecret ?? process.env.SIDEBOARD_SLACK_CLIENT_SECRET ?? "").trim();
10966
+ const redirectUri = (opts.oauthRedirectUri ?? slackOAuthRelayRedirectUri()).trim();
10967
+ const pending = new SlackOAuthPendingStore();
10380
10968
  const hub = opts.hub ?? new SlackRelayHub({
10381
10969
  fetchImpl: opts.fetchImpl,
10382
10970
  onLog: log
10383
10971
  });
10384
- const httpServer = createServer4((req, res) => {
10385
- const bounce = slackOAuthBounceResponse(req.url || "/");
10386
- if (bounce) {
10387
- res.writeHead(bounce.status, bounce.headers);
10388
- res.end(bounce.body);
10389
- return;
10972
+ const handleCallback = async (reqUrl, res) => {
10973
+ const url2 = parseSlackOAuthCallbackUrl(reqUrl);
10974
+ if (!url2) return false;
10975
+ const error = url2.searchParams.get("error")?.trim();
10976
+ const state = url2.searchParams.get("state")?.trim() ?? "";
10977
+ const code = url2.searchParams.get("code")?.trim() ?? "";
10978
+ if (error) {
10979
+ if (state) pending.put(state, { ok: false, error: `Slack OAuth: ${error}` });
10980
+ sendHtml(
10981
+ res,
10982
+ 400,
10983
+ "Slack",
10984
+ `<h1>Authorization cancelled</h1><p>${escapeHtml(error)}</p>`
10985
+ );
10986
+ return true;
10390
10987
  }
10391
- if (req.url === "/health" || req.url === "/") {
10392
- res.writeHead(200, { "Content-Type": "application/json" });
10393
- res.end(
10394
- JSON.stringify({
10395
- ok: true,
10396
- service: "sideboard-slack-relay",
10397
- sessions: hub.listSessions().length
10398
- })
10988
+ if (!state || !code) {
10989
+ sendHtml(res, 400, "Slack", "<h1>Invalid callback</h1><p>State or code missing.</p>");
10990
+ return true;
10991
+ }
10992
+ if (!clientSecret) {
10993
+ pending.put(state, {
10994
+ ok: false,
10995
+ error: "Relay is missing SIDEBOARD_SLACK_CLIENT_SECRET"
10996
+ });
10997
+ sendHtml(
10998
+ res,
10999
+ 500,
11000
+ "Slack",
11001
+ "<h1>Relay misconfigured</h1><p>OAuth client secret is not set on the relay.</p>"
10399
11002
  );
10400
- return;
11003
+ return true;
11004
+ }
11005
+ try {
11006
+ const payload = await exchangeSlackOAuthCode({
11007
+ clientId,
11008
+ clientSecret,
11009
+ code,
11010
+ redirectUri,
11011
+ fetchImpl: opts.fetchImpl
11012
+ });
11013
+ pending.put(state, { ok: true, payload });
11014
+ sendHtml(
11015
+ res,
11016
+ 200,
11017
+ "Slack connected",
11018
+ "<h1>Slack workspace connected</h1><p>You can close this tab and return to Sideboard.</p>"
11019
+ );
11020
+ } catch (err) {
11021
+ const message = err instanceof Error ? err.message : String(err);
11022
+ pending.put(state, { ok: false, error: message });
11023
+ sendHtml(res, 400, "Slack", `<h1>Could not connect Slack</h1><p>${escapeHtml(message)}</p>`);
11024
+ }
11025
+ return true;
11026
+ };
11027
+ const handleResult = (reqUrl, res) => {
11028
+ const url2 = parseSlackOAuthResultUrl(reqUrl);
11029
+ if (!url2) return false;
11030
+ const state = url2.searchParams.get("state")?.trim() ?? "";
11031
+ const value = pending.take(state);
11032
+ if (!value) {
11033
+ sendJson(res, 404, { ok: false, error: "pending" });
11034
+ return true;
11035
+ }
11036
+ if (!value.ok) {
11037
+ sendJson(res, 400, { ok: false, error: value.error });
11038
+ return true;
10401
11039
  }
10402
- res.writeHead(404);
10403
- res.end("Not found");
11040
+ sendJson(res, 200, { ok: true, ...value.payload });
11041
+ return true;
11042
+ };
11043
+ const httpServer = createServer3((req, res) => {
11044
+ void (async () => {
11045
+ const reqUrl = req.url || "/";
11046
+ if (await handleCallback(reqUrl, res)) return;
11047
+ if (handleResult(reqUrl, res)) return;
11048
+ if (req.url === "/health") {
11049
+ sendJson(res, 200, {
11050
+ ok: true,
11051
+ service: "sideboard-slack-relay",
11052
+ sessions: hub.listSessions().length,
11053
+ oauth: Boolean(clientSecret)
11054
+ });
11055
+ return;
11056
+ }
11057
+ const hostname = requestHostname(req);
11058
+ const canonical = (opts.canonicalSiteHost ?? "www.sideboard.cloud").toLowerCase();
11059
+ if (opts.redirectHosts?.includes(hostname) && hostname !== canonical) {
11060
+ res.writeHead(301, { Location: `https://${canonical}${reqUrl}` });
11061
+ res.end();
11062
+ return;
11063
+ }
11064
+ if (opts.staticRoot && hostnameAllowed(hostname, opts.staticHosts) && await tryServeStatic(req, res, opts.staticRoot)) {
11065
+ return;
11066
+ }
11067
+ if (req.url === "/") {
11068
+ sendJson(res, 200, {
11069
+ ok: true,
11070
+ service: "sideboard-slack-relay",
11071
+ sessions: hub.listSessions().length,
11072
+ oauth: Boolean(clientSecret)
11073
+ });
11074
+ return;
11075
+ }
11076
+ res.writeHead(404);
11077
+ res.end("Not found");
11078
+ })().catch((err) => {
11079
+ log(`http error: ${err instanceof Error ? err.message : String(err)}`);
11080
+ if (!res.headersSent) {
11081
+ res.writeHead(500);
11082
+ res.end("Internal error");
11083
+ }
11084
+ });
10404
11085
  });
10405
- const wss = new WebSocketServer({ server: httpServer, path: "/desktop" });
11086
+ const wss = new WebSocketServer({ server: httpServer, path: SLACK_RELAY_DESKTOP_PATH });
10406
11087
  wss.on("connection", (ws, _req) => {
10407
11088
  const socket = {
10408
11089
  send: (data) => {
@@ -10440,12 +11121,12 @@ async function startSlackRelayServer(opts) {
10440
11121
  });
10441
11122
  const address = httpServer.address();
10442
11123
  const boundPort = typeof address === "object" && address ? address.port : typeof port === "number" ? port : 0;
10443
- const url = `ws://${host === "0.0.0.0" ? "127.0.0.1" : host}:${boundPort}/desktop`;
11124
+ const url = `ws://${host === "0.0.0.0" ? "127.0.0.1" : host}:${boundPort}${SLACK_RELAY_DESKTOP_PATH}`;
10444
11125
  log(`relay listening on ${url}`);
10445
11126
  const ac = new AbortController();
10446
11127
  const onAbort = () => ac.abort();
10447
11128
  opts.signal?.addEventListener("abort", onAbort, { once: true });
10448
- const socketModeDone = runSlackSocketMode({
11129
+ const socketModeDone = opts.skipSocketMode ? Promise.resolve() : runSlackSocketMode({
10449
11130
  appToken,
10450
11131
  signal: ac.signal,
10451
11132
  fetchImpl: opts.fetchImpl,
@@ -10492,6 +11173,7 @@ export {
10492
11173
  CONTEXT_MIN_MESSAGES,
10493
11174
  COORDINATOR_TOOL_PLAYBOOK,
10494
11175
  FAMOUS_SOCCER_TEAMS,
11176
+ GITHUB_GIT_AUTH_MODES,
10495
11177
  GLOBAL_WORKSPACE_ID,
10496
11178
  HARNESS_ENV_KEYS,
10497
11179
  LEGACY_ATTACHMENTS_DIR,
@@ -10522,8 +11204,6 @@ export {
10522
11204
  SLACK_LISTEN_STOPPED_REPLY,
10523
11205
  SLACK_LISTEN_TIMEOUT_REPLY,
10524
11206
  SLACK_OAUTH_CANCELLED,
10525
- SLACK_OAUTH_LOCAL_CALLBACK,
10526
- SLACK_OAUTH_PORT,
10527
11207
  SLACK_OAUTH_REDIRECT,
10528
11208
  SLACK_REPLY_FORMATTING,
10529
11209
  SLACK_SEEN_REACTION,
@@ -10543,10 +11223,12 @@ export {
10543
11223
  allocateTeamSlug,
10544
11224
  appDataDir,
10545
11225
  appSettingsPath,
11226
+ appendIndexedGitConfig,
10546
11227
  appendMessage,
10547
11228
  applyAgentEvent,
10548
11229
  applyAppEnvironment,
10549
11230
  applyCompaction,
11231
+ applyGithubGitAuthEnv,
10550
11232
  applyThreadIntoMain,
10551
11233
  applyTurnUsage,
10552
11234
  assertOrchestratorCapableAgent,
@@ -10590,6 +11272,7 @@ export {
10590
11272
  codexAdapter,
10591
11273
  coerceOrchestratorAgent,
10592
11274
  collectTakenTeamSlugs,
11275
+ commentLinearIssue,
10593
11276
  commitAll,
10594
11277
  conductorBundledBinDir,
10595
11278
  conductorDbPath,
@@ -10605,6 +11288,7 @@ export {
10605
11288
  createEmptyThread,
10606
11289
  createExistingBranchWorktree,
10607
11290
  createGlobalChat,
11291
+ createLinearIssue,
10608
11292
  createLinearPkce,
10609
11293
  createOrUpdatePr,
10610
11294
  createPrStack,
@@ -10657,7 +11341,9 @@ export {
10657
11341
  formatAgentInstructions,
10658
11342
  formatArtifactDirective,
10659
11343
  formatBrightsyFetchError,
11344
+ formatFetchError,
10660
11345
  formatGhLandError,
11346
+ formatGitAuthModeDirective,
10661
11347
  formatIpcInvokeError,
10662
11348
  formatMessagesAsTranscript,
10663
11349
  formatPlanQuestionAnswers,
@@ -10684,9 +11370,12 @@ export {
10684
11370
  getDiff,
10685
11371
  getDiffSummary,
10686
11372
  getGitHubStatus,
11373
+ getGithubGitAuthMode,
11374
+ getGithubPat,
10687
11375
  getIssueSource,
10688
11376
  getLinearApiKey,
10689
11377
  getLinearAuthToken,
11378
+ getLinearIssue,
10690
11379
  getOrchestrator,
10691
11380
  getPr,
10692
11381
  getPrChecks,
@@ -10701,6 +11390,7 @@ export {
10701
11390
  ghHeadRef,
10702
11391
  ghRepoSelectArgs,
10703
11392
  git,
11393
+ githubAgentGitEnv,
10704
11394
  globalAgentCwd,
10705
11395
  handleSlackInbound,
10706
11396
  harnessEnvKey,
@@ -10711,6 +11401,7 @@ export {
10711
11401
  hasRepoHook,
10712
11402
  hasWorkspaceHook,
10713
11403
  healOrchestrationSoccerTitles,
11404
+ httpFetch,
10714
11405
  importConductorWorkspace,
10715
11406
  importConductorWorkspaceAsync,
10716
11407
  initPrStack,
@@ -10744,8 +11435,10 @@ export {
10744
11435
  isSlackExternalReplyPrompt,
10745
11436
  isSlackOAuthCancelled,
10746
11437
  isThinkingEffort,
11438
+ isThreadCaffeinated,
10747
11439
  isWorkspaceScratchPath,
10748
11440
  linearAuthorizationHeader,
11441
+ linearGraphql,
10749
11442
  linearOAuthAuthorizeUrl,
10750
11443
  linearOAuthCredentials,
10751
11444
  listAgentSetupInfo,
@@ -10762,6 +11455,7 @@ export {
10762
11455
  listIssues,
10763
11456
  listLinearIssues,
10764
11457
  listLinearIssuesDirect,
11458
+ listLinearTeams,
10765
11459
  listModelsForAgent,
10766
11460
  listOpencodeModels,
10767
11461
  listPrs,
@@ -10837,6 +11531,7 @@ export {
10837
11531
  recordSlackOutboundWatch,
10838
11532
  refreshGitHubAuth,
10839
11533
  refreshSlackReplyBadges,
11534
+ releaseCaffeinateHoldForThread,
10840
11535
  removeWorkspace,
10841
11536
  removeWorktree,
10842
11537
  repoSlug,
@@ -10855,6 +11550,8 @@ export {
10855
11550
  resolveFilesToCopy,
10856
11551
  resolveGhAuthToken,
10857
11552
  resolveGithubRepoSlug,
11553
+ resolveLinearState,
11554
+ resolveLinearTeam,
10858
11555
  resolveLoginCommand,
10859
11556
  resolveNewThreadOptions,
10860
11557
  resolvePlanMarkdown,
@@ -10867,6 +11564,7 @@ export {
10867
11564
  resolveThreadEffort,
10868
11565
  resolveVaultKey,
10869
11566
  resolveWorktreeStartPoint,
11567
+ rewriteLinearError,
10870
11568
  run,
10871
11569
  runArchiveScript,
10872
11570
  runCloudConnect,
@@ -10880,6 +11578,7 @@ export {
10880
11578
  saveLinearOAuth,
10881
11579
  secureFileUnlocksWith,
10882
11580
  setCaffeinateHold,
11581
+ setHttpFetchImpl,
10883
11582
  setStatus,
10884
11583
  setVaultMasterKey,
10885
11584
  settingsSourceLabel,
@@ -10897,6 +11596,7 @@ export {
10897
11596
  slackCoordinatorSourceRef,
10898
11597
  slackListenEnabled,
10899
11598
  slackOAuthCredentials,
11599
+ slackOAuthResultUrl,
10900
11600
  slackRelayUrl,
10901
11601
  slugify,
10902
11602
  spawnAgentTurn,
@@ -10913,6 +11613,7 @@ export {
10913
11613
  startSlackOAuth,
10914
11614
  startSlackRelayServer,
10915
11615
  stripBrightsyNdjsonNoise,
11616
+ stripNestedElectronEnv,
10916
11617
  submitPrStack,
10917
11618
  suggestSlug,
10918
11619
  summarizeConversation,
@@ -10942,6 +11643,7 @@ export {
10942
11643
  updateCodexSettings,
10943
11644
  updateDefaultsSettings,
10944
11645
  updateIntegrationsSettings,
11646
+ updateLinearIssue,
10945
11647
  updateOpencodeSettings,
10946
11648
  updateThread,
10947
11649
  validateLinearApiKey,