@sideboard-ai/core 0.1.45 → 0.1.49

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 (45) hide show
  1. package/dist/agents/cursor-runner.cjs +11 -4
  2. package/dist/agents/cursor-runner.js +11 -4
  3. package/dist/{agents-JSHCAZUZ.js → agents-3P4N6KHC.js} +12 -2
  4. package/dist/agents-LUB3Q773.js +82 -0
  5. package/dist/app-settings-7XVDQJ7F.js +80 -0
  6. package/dist/{app-settings-LYGVDGZY.js → app-settings-LZP632KI.js} +1 -1
  7. package/dist/{chunk-U3EQKJHA.js → chunk-3NAS4MWH.js} +1 -1
  8. package/dist/{chunk-ZNSM2DDD.js → chunk-5WYEJ3F3.js} +6 -2
  9. package/dist/chunk-7MV3RXSC.js +277 -0
  10. package/dist/chunk-DF5VQQKA.js +191 -0
  11. package/dist/chunk-E35APBHV.js +269 -0
  12. package/dist/chunk-EOYDCKQC.js +172 -0
  13. package/dist/chunk-GI7E5733.js +30 -0
  14. package/dist/chunk-GZXBYHJC.js +148 -0
  15. package/dist/chunk-HBJSHRY2.js +494 -0
  16. package/dist/chunk-ISY4EGF6.js +77 -0
  17. package/dist/chunk-KGZBYWZ3.js +23 -0
  18. package/dist/chunk-RQI4TOXK.js +2232 -0
  19. package/dist/chunk-S2LL5HA4.js +33 -0
  20. package/dist/{chunk-ANZ566Z5.js → chunk-SS34TVSY.js} +66 -7
  21. package/dist/{chunk-WYY3J7GR.js → chunk-T5QQVXK3.js} +6 -3
  22. package/dist/chunk-V4JRXYYU.js +122 -0
  23. package/dist/{chunk-6NAPN2N5.js → chunk-ZQCQIWIP.js} +1 -1
  24. package/dist/chunk-ZVV5EEQN.js +1892 -0
  25. package/dist/connected-teams-UBXHZGO4.js +24 -0
  26. package/dist/{coordinator-prompt-2XFYG3C5.js → coordinator-prompt-SPGDT7J5.js} +1 -1
  27. package/dist/coordinator-prompt-VG4BZ5JL.js +25 -0
  28. package/dist/cursor-recover-NNK7JPQM.js +44 -0
  29. package/dist/{global-workspace-YFOQUGWD.js → global-workspace-KSR3E63K.js} +3 -2
  30. package/dist/global-workspace-OJF4ENH4.js +40 -0
  31. package/dist/index.cjs +377 -30
  32. package/dist/index.d.cts +70 -11
  33. package/dist/index.d.ts +70 -11
  34. package/dist/index.js +5779 -193
  35. package/dist/mcp/run-stdio.cjs +331 -32
  36. package/dist/mcp/run-stdio.js +5618 -14
  37. package/dist/paths-2OFB7UJG.js +28 -0
  38. package/dist/run-HMRSRG3U.js +12 -0
  39. package/dist/thread-store-XICUWFNM.js +32 -0
  40. package/dist/title-4WAKWZRF.js +27 -0
  41. package/dist/workspaces-OZE7LRDO.js +24 -0
  42. package/dist/{workspaces-TIKLNDW3.js → workspaces-UJX7JIGH.js} +4 -3
  43. package/dist/worktree-XG5PCLZY.js +94 -0
  44. package/package.json +2 -2
  45. package/dist/chunk-I6QGZOOS.js +0 -5667
@@ -0,0 +1,2232 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {
4
+ applyConnectedTeamToCli,
5
+ brightsyMcpServerName,
6
+ ensureCliTeamTracked,
7
+ ensureConnectedBrightsyTeamTokens,
8
+ loadBrightsyConfig
9
+ } from "./chunk-E35APBHV.js";
10
+ import {
11
+ claudeChromeEnabled,
12
+ loadAppSettings,
13
+ resolveClaudeExecutable
14
+ } from "./chunk-HBJSHRY2.js";
15
+ import {
16
+ ensureAgentPath,
17
+ run
18
+ } from "./chunk-ISY4EGF6.js";
19
+
20
+ // src/agents/brightsy-targets.ts
21
+ function encodeBrightsyTarget(type, id, accountId) {
22
+ if (accountId) return `team:${accountId}:${type}:${id}`;
23
+ return `${type}:${id}`;
24
+ }
25
+ function decodeBrightsyTarget(model) {
26
+ const raw = model?.trim();
27
+ if (!raw || raw === "default" || raw === "agent:default") {
28
+ return { type: "agent", id: "default" };
29
+ }
30
+ const teamMatch = raw.match(/^team:([^:]+):(agent|model):(.+)$/);
31
+ if (teamMatch) {
32
+ return {
33
+ type: teamMatch[2],
34
+ id: teamMatch[3] || "default",
35
+ accountId: teamMatch[1]
36
+ };
37
+ }
38
+ if (raw.startsWith("agent:")) {
39
+ return { type: "agent", id: raw.slice("agent:".length) || "default" };
40
+ }
41
+ if (raw.startsWith("model:")) {
42
+ return { type: "model", id: raw.slice("model:".length) };
43
+ }
44
+ if (/^[0-9a-f]{8}-[0-9a-f-]{27,}$/i.test(raw) || raw === "default") {
45
+ return { type: "agent", id: raw };
46
+ }
47
+ return { type: "model", id: raw };
48
+ }
49
+
50
+ // src/agents/error-detail.ts
51
+ function formatUnknownDetail(err) {
52
+ if (err == null) return "";
53
+ if (typeof err === "string") return err.trim();
54
+ if (err instanceof Error) {
55
+ const base = err.message.trim() || err.name;
56
+ const code = "code" in err && typeof err.code === "string" ? err.code.trim() : "";
57
+ return code && !base.includes(code) ? `${base} (${code})` : base;
58
+ }
59
+ if (typeof err === "object") {
60
+ const o = err;
61
+ const nested = o.error != null && typeof o.error === "object" ? formatUnknownDetail(o.error) : "";
62
+ const message = typeof o.message === "string" ? o.message.trim() : typeof o.error === "string" ? o.error.trim() : typeof o.result === "string" ? o.result.trim() : nested;
63
+ const code = typeof o.code === "string" ? o.code.trim() : "";
64
+ if (message) return code && !message.includes(code) ? `${message} (${code})` : message;
65
+ try {
66
+ const json = JSON.stringify(err);
67
+ if (json && json !== "{}" && json !== "null") return json;
68
+ } catch {
69
+ }
70
+ }
71
+ const fallback = String(err);
72
+ return fallback === "[object Object]" ? "" : fallback;
73
+ }
74
+ function extractJsonErrorMessage(obj) {
75
+ const nested = obj.error != null && typeof obj.error === "object" ? obj.error : null;
76
+ const candidates = [
77
+ typeof obj.message === "string" ? obj.message : null,
78
+ typeof obj.error === "string" ? obj.error : null,
79
+ nested && typeof nested.message === "string" ? nested.message : null,
80
+ typeof obj.result === "string" ? obj.result : null,
81
+ typeof obj.detail === "string" ? obj.detail : null
82
+ ];
83
+ for (const c of candidates) {
84
+ const t = c?.trim();
85
+ if (t) return t;
86
+ }
87
+ if (Array.isArray(obj.errors)) {
88
+ const parts = obj.errors.map((e) => formatUnknownDetail(e)).map((s) => s.trim()).filter(Boolean);
89
+ if (parts.length) return parts.join("; ");
90
+ }
91
+ return null;
92
+ }
93
+ var NODE_VERSION_FOOTER = /^Node\.js v\d+/i;
94
+ function pushTurnStderr(tail, line, maxLines = 12) {
95
+ const trimmed = line.trim();
96
+ if (!trimmed) return;
97
+ if (NODE_VERSION_FOOTER.test(trimmed)) return;
98
+ if (/^reconnecting\.\.\./i.test(trimmed)) return;
99
+ tail.push(trimmed);
100
+ while (tail.length > maxLines) tail.shift();
101
+ }
102
+ function summarizeTurnStderr(tail, maxChars = 500) {
103
+ if (tail.length === 0) return "";
104
+ const moduleMissing = [...tail].reverse().find((line) => /cannot find module/i.test(line));
105
+ if (moduleMissing) {
106
+ return moduleMissing.length <= maxChars ? moduleMissing : moduleMissing.slice(0, maxChars);
107
+ }
108
+ const joined = tail.slice(-6).join("\n").trim();
109
+ if (joined.length <= maxChars) return joined;
110
+ return joined.slice(joined.length - maxChars);
111
+ }
112
+ function looksLikeAgentFailureMessage(text) {
113
+ const lower = text.trim().toLowerCase();
114
+ if (!lower) return false;
115
+ return /you've hit your|hit your (session|weekly|opus) limit|usage limit/.test(lower) || /credit balance is too low|out of credits|insufficient.?quota|quota.?exceeded/.test(lower) || /invalid user api key|invalid api key|not logged in|not authenticated|unauthorized/.test(
116
+ lower
117
+ ) || /\b429\b|too many requests|rate.?limit/.test(lower) || /prompt is too long|context.*(too long|exceed)|conversation too long/.test(lower);
118
+ }
119
+ function fallbackTurnFailDetail(assistantText) {
120
+ const t = assistantText.trim();
121
+ if (!t) return "";
122
+ if (looksLikeAgentFailureMessage(t)) return t;
123
+ if (t.length <= 400 && !/\n\n/.test(t)) return t;
124
+ return "";
125
+ }
126
+ function humanizeAgentFailDetail(detail) {
127
+ const raw = detail.trim();
128
+ if (!raw) return raw;
129
+ const lower = raw.toLowerCase();
130
+ if (/credit balance is too low|out of credits|insufficient.?quota|quota.?exceeded|billing/.test(lower)) {
131
+ return `${raw} \u2014 add credits or switch auth, then retry.`;
132
+ }
133
+ if (/hit your (session|weekly|opus) limit|usage limit|you've hit your/.test(lower)) {
134
+ return raw.includes("reset") ? raw : `${raw} \u2014 wait for the limit window to reset, then retry.`;
135
+ }
136
+ if (/\b429\b|rate.?limit|too many requests/.test(lower)) {
137
+ return `${raw} \u2014 wait a moment and retry.`;
138
+ }
139
+ if (/invalid user api key|invalid api key|not logged in|not authenticated|unauthorized|authentication|please run.*login|codex login|claude auth|cursor api/.test(
140
+ lower
141
+ )) {
142
+ return `${raw} \u2014 check agent login / API key in Settings.`;
143
+ }
144
+ if (/model .{0,80}(not found|unavailable|unknown|invalid)/.test(lower)) {
145
+ return `${raw} \u2014 pick another model in the agent options.`;
146
+ }
147
+ if (/context.*(too long|exceed)|prompt is too long|conversation too long/.test(lower)) {
148
+ return `${raw} \u2014 start a new chat or compact context, then retry.`;
149
+ }
150
+ return raw;
151
+ }
152
+ function formatTurnExitError(exitCode, stderrSummary) {
153
+ const code = exitCode ?? 1;
154
+ const raw = stderrSummary.trim();
155
+ if (/^exit\s*\d+$/i.test(raw)) {
156
+ return `exit ${code}: agent exited without details (credits, auth, rate limits, or a CLI error)`;
157
+ }
158
+ const detail = humanizeAgentFailDetail(raw);
159
+ if (!detail) {
160
+ return `exit ${code}: agent exited without details (credits, auth, rate limits, or a CLI error)`;
161
+ }
162
+ if (looksLikeAgentFailureMessage(raw)) return detail;
163
+ return `exit ${code}: ${detail}`;
164
+ }
165
+
166
+ // src/agents/turn-input.ts
167
+ function normalizeTurnInput(input) {
168
+ if (typeof input === "string") return { prompt: input };
169
+ return { prompt: input.prompt, cachedPrefix: input.cachedPrefix?.trim() || void 0 };
170
+ }
171
+ function flattenTurnInput(input) {
172
+ const { cachedPrefix, prompt } = normalizeTurnInput(input);
173
+ if (!cachedPrefix) return prompt;
174
+ return `${cachedPrefix}
175
+
176
+ ---
177
+
178
+ Current request:
179
+ ${prompt}`;
180
+ }
181
+
182
+ // src/agents/brightsy.ts
183
+ function usageFromBrightsy(usage) {
184
+ if (!usage) return null;
185
+ const inputTokens = Number(usage.prompt_tokens ?? 0);
186
+ const outputTokens = Number(usage.completion_tokens ?? 0);
187
+ if (!inputTokens && !outputTokens) return null;
188
+ const cached = Number(usage.prompt_tokens_details?.cached_tokens ?? 0);
189
+ return {
190
+ inputTokens,
191
+ outputTokens,
192
+ cacheReadTokens: cached || void 0
193
+ };
194
+ }
195
+ function parseBrightsyCliLine(line) {
196
+ const trimmed = line.trim();
197
+ if (!trimmed) return null;
198
+ try {
199
+ const obj = JSON.parse(trimmed);
200
+ if (obj.type === "text" && typeof obj.text === "string") {
201
+ return { type: "stdout", data: obj.text };
202
+ }
203
+ if (obj.type === "thinking" && typeof obj.text === "string") {
204
+ return { type: "thinking", data: obj.text };
205
+ }
206
+ if (obj.type === "error") {
207
+ const msg = extractJsonErrorMessage(obj) || formatUnknownDetail(obj.error) || formatUnknownDetail(obj.message) || trimmed;
208
+ return [
209
+ { type: "stderr", data: msg },
210
+ { type: "stdout", data: `Error: ${msg}` }
211
+ ];
212
+ }
213
+ if (obj.type === "usage") {
214
+ const usage = usageFromBrightsy(obj.usage);
215
+ return usage ? { type: "usage", data: usage } : null;
216
+ }
217
+ if (obj.type === "tool_use") {
218
+ const id = typeof obj.id === "string" && obj.id || `brightsy-tool-${Date.now()}`;
219
+ const name = typeof obj.name === "string" && obj.name || "brightsy_tool";
220
+ const input = obj.input && typeof obj.input === "object" && !Array.isArray(obj.input) ? obj.input : void 0;
221
+ return { type: "tool_use", id, name, input };
222
+ }
223
+ if (obj.type === "tool_result" || obj.type === "tool") {
224
+ const id = typeof obj.id === "string" && obj.id || typeof obj.tool_call_id === "string" && obj.tool_call_id || `brightsy-tool-${Date.now()}`;
225
+ const content = typeof obj.content === "string" ? obj.content : obj.content != null ? JSON.stringify(obj.content) : void 0;
226
+ return {
227
+ type: "tool_result",
228
+ id,
229
+ content,
230
+ isError: obj.isError === true
231
+ };
232
+ }
233
+ if (obj.type === "done") return null;
234
+ if (typeof obj.type === "string" && ["tool_use", "tool_result", "tool", "thinking", "usage", "error", "done", "text"].includes(
235
+ obj.type
236
+ )) {
237
+ return null;
238
+ }
239
+ return { type: "stdout", data: trimmed };
240
+ } catch {
241
+ if (trimmed.startsWith("{") && /"type"\s*:\s*"(tool_use|tool_result|tool|text|thinking|usage|done|error)"/.test(trimmed)) {
242
+ return null;
243
+ }
244
+ if (/error|failed|unauthorized|quota|limit|not logged in/i.test(trimmed)) {
245
+ return [
246
+ { type: "stderr", data: trimmed },
247
+ { type: "stdout", data: `Error: ${trimmed}` }
248
+ ];
249
+ }
250
+ return { type: "stdout", data: line };
251
+ }
252
+ }
253
+ async function fetchTeamChatTargets(team) {
254
+ const endpoint = (team.endpoint || "https://brightsy.ai").replace(/\/$/, "");
255
+ let agents = [
256
+ {
257
+ type: "agent",
258
+ id: "default",
259
+ name: "Default Agent",
260
+ description: "Account default agent (tools + memory)",
261
+ accountId: team.id,
262
+ accountSlug: team.slug,
263
+ accountName: team.name
264
+ }
265
+ ];
266
+ let models = [];
267
+ try {
268
+ const res = await fetch(`${endpoint}/api/v1beta/${team.id}/agents`, {
269
+ headers: { Authorization: `Bearer ${team.access_token}` }
270
+ });
271
+ if (res.ok) {
272
+ const json = await res.json();
273
+ const listed = (json.data || []).filter(
274
+ (a) => Boolean(a?.id && a?.name)
275
+ ).filter((a) => a.id !== "default").map((a) => ({
276
+ type: "agent",
277
+ id: a.id,
278
+ name: a.name,
279
+ description: a.description,
280
+ accountId: team.id,
281
+ accountSlug: team.slug,
282
+ accountName: team.name
283
+ }));
284
+ agents = [...agents, ...listed];
285
+ if (Array.isArray(json.models)) {
286
+ models = json.models.filter(
287
+ (m) => Boolean(m?.id && m?.name)
288
+ ).slice(0, 12).map((m) => ({
289
+ type: "model",
290
+ id: m.id,
291
+ name: m.name,
292
+ description: m.description,
293
+ accountId: team.id,
294
+ accountSlug: team.slug,
295
+ accountName: team.name
296
+ }));
297
+ }
298
+ }
299
+ } catch {
300
+ }
301
+ return {
302
+ accountId: team.id,
303
+ accountSlug: team.slug,
304
+ accountName: team.name,
305
+ agents,
306
+ models
307
+ };
308
+ }
309
+ async function listBrightsyChatTargetsViaCli() {
310
+ const listed = await run("brightsy", ["chat", "--list-targets", "--json"], {
311
+ reject: false
312
+ });
313
+ if (listed.exitCode !== 0 || !listed.stdout.trim()) {
314
+ throw new Error(
315
+ listed.stderr.trim() || "Failed to list Brightsy chat targets \u2014 is `brightsy` installed and logged in?"
316
+ );
317
+ }
318
+ try {
319
+ const parsed = JSON.parse(listed.stdout);
320
+ let accountId = null;
321
+ let accountSlug = "team";
322
+ let accountName = "Brightsy";
323
+ try {
324
+ const cfg = loadBrightsyConfig();
325
+ accountId = cfg.account_id;
326
+ accountSlug = cfg.account_slug || accountSlug;
327
+ accountName = cfg.account_slug || accountName;
328
+ } catch {
329
+ }
330
+ const agents = (Array.isArray(parsed.agents) ? parsed.agents : []).map((a) => ({
331
+ ...a,
332
+ accountId: accountId ?? void 0,
333
+ accountSlug,
334
+ accountName
335
+ }));
336
+ const models = (Array.isArray(parsed.models) ? parsed.models : []).map((m) => ({
337
+ ...m,
338
+ accountId: accountId ?? void 0,
339
+ accountSlug,
340
+ accountName
341
+ }));
342
+ const teams = accountId ? [
343
+ {
344
+ accountId,
345
+ accountSlug,
346
+ accountName,
347
+ agents,
348
+ models
349
+ }
350
+ ] : [];
351
+ return { teams, agents, models, activeAccountId: accountId };
352
+ } catch {
353
+ throw new Error("Brightsy --list-targets returned invalid JSON");
354
+ }
355
+ }
356
+ async function listBrightsyChatTargets() {
357
+ ensureCliTeamTracked();
358
+ const teamsRaw = await ensureConnectedBrightsyTeamTokens();
359
+ if (teamsRaw.length === 0) {
360
+ return listBrightsyChatTargetsViaCli();
361
+ }
362
+ let activeAccountId = null;
363
+ try {
364
+ activeAccountId = loadBrightsyConfig().account_id;
365
+ } catch {
366
+ activeAccountId = teamsRaw[0]?.id ?? null;
367
+ }
368
+ const teams = await Promise.all(teamsRaw.map((t) => fetchTeamChatTargets(t)));
369
+ teams.sort((a, b) => {
370
+ if (a.accountId === activeAccountId) return -1;
371
+ if (b.accountId === activeAccountId) return 1;
372
+ return a.accountSlug.localeCompare(b.accountSlug);
373
+ });
374
+ const active = teams.find((t) => t.accountId === activeAccountId) ?? teams[0] ?? null;
375
+ return {
376
+ teams,
377
+ agents: active?.agents ?? [],
378
+ models: active?.models ?? [],
379
+ activeAccountId
380
+ };
381
+ }
382
+ async function syncCliForTarget(accountId) {
383
+ if (!accountId) return;
384
+ const teams = await ensureConnectedBrightsyTeamTokens();
385
+ const team = teams.find((t) => t.id === accountId);
386
+ if (!team) return;
387
+ try {
388
+ const cfg = loadBrightsyConfig();
389
+ if (cfg.account_id === team.id && cfg.access_token === team.access_token) {
390
+ return;
391
+ }
392
+ } catch {
393
+ }
394
+ applyConnectedTeamToCli(team);
395
+ }
396
+ var brightsyAdapter = {
397
+ kind: "brightsy",
398
+ async detect() {
399
+ const which = await run("which", ["brightsy"], { reject: false });
400
+ if (which.exitCode !== 0) {
401
+ return {
402
+ agent: "brightsy",
403
+ installed: false,
404
+ authenticated: false,
405
+ linearMcp: false,
406
+ warnings: [],
407
+ reason: "brightsy CLI not found on PATH \u2014 npm i -g @brightsy/cli"
408
+ };
409
+ }
410
+ const who = await run("brightsy", ["whoami"], { reject: false });
411
+ const authenticated = who.exitCode === 0 && /logged in as/i.test(who.stdout);
412
+ return {
413
+ agent: "brightsy",
414
+ installed: true,
415
+ authenticated,
416
+ linearMcp: false,
417
+ warnings: [],
418
+ reason: authenticated ? void 0 : "not logged in \u2014 run `brightsy login`"
419
+ };
420
+ },
421
+ async buildTurn(thread, input) {
422
+ const prompt = flattenTurnInput(input);
423
+ const target = decodeBrightsyTarget(thread.model);
424
+ await syncCliForTarget(target.accountId);
425
+ const mode = thread.planMode ? "plan" : target.type === "model" ? "ask" : "agent";
426
+ const args = [
427
+ "chat",
428
+ "--json",
429
+ "--mode",
430
+ mode,
431
+ target.type === "model" ? "--model" : "--agent",
432
+ target.id
433
+ ];
434
+ return {
435
+ file: "brightsy",
436
+ args,
437
+ cwd: thread.worktreePath,
438
+ // Piped stdin becomes the message body (avoids ARG_MAX for long seeds).
439
+ stdin: prompt
440
+ };
441
+ },
442
+ parseEvent(line) {
443
+ return parseBrightsyCliLine(line);
444
+ },
445
+ async resolveSessionId() {
446
+ return null;
447
+ },
448
+ async buildAttach(thread) {
449
+ const target = decodeBrightsyTarget(thread.model);
450
+ await syncCliForTarget(target.accountId);
451
+ const args = target.type === "model" ? ["chat", "--model", target.id] : ["chat", "--agent", target.id];
452
+ return { file: "brightsy", args, cwd: thread.worktreePath };
453
+ }
454
+ };
455
+
456
+ // src/agents/claude.ts
457
+ import { existsSync as existsSync2 } from "fs";
458
+
459
+ // src/agents/claude-mcp.ts
460
+ function parseMcpList(text) {
461
+ const servers = [];
462
+ const seen = /* @__PURE__ */ new Set();
463
+ for (const raw of text.split("\n")) {
464
+ const line = raw.trim();
465
+ if (!line || /^Checking MCP/i.test(line)) continue;
466
+ const m = line.match(/^(.+?):\s+\S+/);
467
+ if (!m) continue;
468
+ const name = m[1].trim();
469
+ if (!name || seen.has(name)) continue;
470
+ const needsAuth = /Needs authentication/i.test(line);
471
+ const connected = !needsAuth && /Connected/i.test(line);
472
+ if (!needsAuth && !connected) continue;
473
+ seen.add(name);
474
+ servers.push({ name, connected, needsAuth });
475
+ }
476
+ return servers;
477
+ }
478
+ function sanitizeMcpServerName(name) {
479
+ return name.replace(/[^A-Za-z0-9_-]/g, "_");
480
+ }
481
+ function mcpAllowTools(servers) {
482
+ const out = [];
483
+ for (const s of servers) {
484
+ if (!s.connected) continue;
485
+ const id = sanitizeMcpServerName(s.name);
486
+ if (!id) continue;
487
+ out.push(`mcp__${id}`);
488
+ out.push(`mcp__${id}__*`);
489
+ }
490
+ return out;
491
+ }
492
+ function mcpAuthWarnings(servers) {
493
+ const needing = servers.filter((s) => s.needsAuth).map((s) => s.name);
494
+ if (needing.length === 0) return [];
495
+ return [
496
+ `MCP needs login: ${needing.join(", ")}. Run: claude mcp login "<name>"`
497
+ ];
498
+ }
499
+
500
+ // src/agents/injected-mcp.ts
501
+ import { existsSync, mkdtempSync, writeFileSync } from "fs";
502
+ import { createRequire } from "module";
503
+ import { tmpdir } from "os";
504
+ import { dirname, join } from "path";
505
+ import { fileURLToPath } from "url";
506
+
507
+ // src/agents/node-launch.ts
508
+ function isAsarPath(filePath) {
509
+ return /\.asar([/\\]|$)/.test(filePath);
510
+ }
511
+ async function resolveNodeLaunch(scriptPath) {
512
+ if (isAsarPath(scriptPath)) {
513
+ return {
514
+ file: process.execPath,
515
+ env: { ELECTRON_RUN_AS_NODE: "1" }
516
+ };
517
+ }
518
+ const whichNode = await run("which", ["node"], { reject: false });
519
+ const nodeBin = whichNode.exitCode === 0 && whichNode.stdout.trim() ? whichNode.stdout.trim() : null;
520
+ if (nodeBin) {
521
+ return { file: nodeBin, env: {} };
522
+ }
523
+ return {
524
+ file: process.execPath,
525
+ env: { ELECTRON_RUN_AS_NODE: "1" }
526
+ };
527
+ }
528
+
529
+ // src/agents/injected-mcp.ts
530
+ var SIDEBOARD_MCP_ALLOWED_TOOLS = [
531
+ "mcp__sideboard",
532
+ "mcp__sideboard__*"
533
+ ];
534
+ var SIDEBOARD_ARTIFACT_MCP_ALLOWED_TOOLS = [
535
+ "mcp__sideboard__present_artifact",
536
+ "mcp__sideboard__present_schema",
537
+ "mcp__sideboard__present_files"
538
+ ];
539
+ var brightsyMcpCommandCache = null;
540
+ async function resolveBrightsyMcpCommand() {
541
+ const now = Date.now();
542
+ if (brightsyMcpCommandCache && now - brightsyMcpCommandCache.at < 6e4 && brightsyMcpCommandCache.command) {
543
+ return brightsyMcpCommandCache.command;
544
+ }
545
+ const which = await run("which", ["brightsy-mcp"], { reject: false });
546
+ const command = which.exitCode === 0 && which.stdout.trim() ? "brightsy-mcp" : "npx";
547
+ brightsyMcpCommandCache = { at: now, command };
548
+ return command;
549
+ }
550
+ function isBrightsyConnected() {
551
+ try {
552
+ loadBrightsyConfig();
553
+ return true;
554
+ } catch {
555
+ return false;
556
+ }
557
+ }
558
+ function mcpLaunch(cmd, name, env) {
559
+ if (cmd === "brightsy-mcp") {
560
+ return { name, command: "brightsy-mcp", ...env ? { env } : {} };
561
+ }
562
+ return {
563
+ name,
564
+ command: "npx",
565
+ args: ["-y", "@brightsy/mcp-server"],
566
+ ...env ? { env } : {}
567
+ };
568
+ }
569
+ function teamEnv(team) {
570
+ const endpoint = (team.endpoint || "https://brightsy.ai").replace(/\/$/, "");
571
+ return {
572
+ BRIGHTSY_API_TOKEN: team.access_token,
573
+ BRIGHTSY_ACCOUNT_ID: team.id,
574
+ BRIGHTSY_API_URL: endpoint
575
+ };
576
+ }
577
+ function brightsyMcpAllowedTools(serverNames) {
578
+ const out = [];
579
+ for (const name of serverNames) {
580
+ out.push(`mcp__${name}`, `mcp__${name}__*`);
581
+ }
582
+ return out;
583
+ }
584
+ function corePackageDir() {
585
+ const cjsDir = typeof __dirname !== "undefined" ? __dirname : "";
586
+ if (cjsDir) return cjsDir;
587
+ try {
588
+ const url = import.meta.url;
589
+ if (typeof url === "string" && url.length > 0) {
590
+ return dirname(fileURLToPath(url));
591
+ }
592
+ } catch {
593
+ }
594
+ try {
595
+ const req = createRequire(join(process.cwd(), "package.json"));
596
+ return dirname(req.resolve("@sideboard-ai/core"));
597
+ } catch {
598
+ return process.cwd();
599
+ }
600
+ }
601
+ function findSideboardMcpJsEntry() {
602
+ const override = process.env.SIDEBOARD_MCP_ENTRY?.trim() || process.env.SIDEBOARD_CLI?.trim();
603
+ if (override && existsSync(override)) return override;
604
+ let dir = corePackageDir();
605
+ for (let i = 0; i < 10; i++) {
606
+ const candidates = [
607
+ join(dir, "mcp/run-stdio.js"),
608
+ join(dir, "mcp/run-stdio.cjs"),
609
+ join(dir, "dist/mcp/run-stdio.js"),
610
+ join(dir, "dist/mcp/run-stdio.cjs"),
611
+ join(dir, "packages/core/dist/mcp/run-stdio.js"),
612
+ join(dir, "packages/cli/dist/index.js"),
613
+ join(dir, "cli/dist/index.js")
614
+ ];
615
+ for (const p of candidates) {
616
+ if (existsSync(p)) return p;
617
+ }
618
+ const parent = dirname(dir);
619
+ if (parent === dir) break;
620
+ dir = parent;
621
+ }
622
+ return null;
623
+ }
624
+ async function resolveSideboardMcpServer() {
625
+ const entry = findSideboardMcpJsEntry();
626
+ if (entry) {
627
+ const isCli = /[/\\]cli[/\\]dist[/\\]index\.js$/.test(entry);
628
+ const scriptArgs = isCli ? [entry, "mcp"] : [entry];
629
+ const launch = await resolveNodeLaunch(entry);
630
+ return {
631
+ name: "sideboard",
632
+ command: launch.file,
633
+ args: scriptArgs,
634
+ ...Object.keys(launch.env).length > 0 ? { env: launch.env } : {}
635
+ };
636
+ }
637
+ const which = await run("which", ["sideboard"], { reject: false });
638
+ if (which.exitCode === 0 && which.stdout.trim()) {
639
+ return { name: "sideboard", command: which.stdout.trim(), args: ["mcp"] };
640
+ }
641
+ return { name: "sideboard", command: "sideboard", args: ["mcp"] };
642
+ }
643
+ async function buildInjectedMcpServers(opts) {
644
+ const servers = [];
645
+ if (opts.includeSideboard) {
646
+ servers.push(await resolveSideboardMcpServer());
647
+ }
648
+ if (opts.includeBrightsy && isBrightsyConnected()) {
649
+ const cmd = await resolveBrightsyMcpCommand();
650
+ const teams = await ensureConnectedBrightsyTeamTokens();
651
+ if (teams.length > 0) {
652
+ const used = /* @__PURE__ */ new Set();
653
+ for (const team of teams) {
654
+ let name = brightsyMcpServerName(team.slug);
655
+ if (used.has(name)) name = `${name}_${team.id.slice(0, 8)}`;
656
+ used.add(name);
657
+ servers.push(mcpLaunch(cmd, name, teamEnv(team)));
658
+ }
659
+ } else {
660
+ servers.push(mcpLaunch(cmd, "brightsy"));
661
+ }
662
+ }
663
+ return servers;
664
+ }
665
+ function toCursorMcpServers(servers) {
666
+ const out = {};
667
+ for (const s of servers) {
668
+ out[s.name] = {
669
+ command: s.command,
670
+ ...s.args ? { args: s.args } : {},
671
+ ...s.env ? { env: s.env } : {}
672
+ };
673
+ }
674
+ return out;
675
+ }
676
+ function toCodexMcpConfigArgs(servers) {
677
+ const args = [];
678
+ for (const s of servers) {
679
+ const prefix = `mcp_servers.${s.name}`;
680
+ args.push("-c", `${prefix}.command=${JSON.stringify(s.command)}`);
681
+ if (s.args?.length) {
682
+ args.push("-c", `${prefix}.args=${JSON.stringify(s.args)}`);
683
+ }
684
+ if (s.env) {
685
+ for (const [key, value] of Object.entries(s.env)) {
686
+ args.push("-c", `${prefix}.env.${key}=${JSON.stringify(value)}`);
687
+ }
688
+ }
689
+ }
690
+ return args;
691
+ }
692
+ function toOpencodeMcpConfigContent(servers) {
693
+ const mcp = {};
694
+ for (const s of servers) {
695
+ mcp[s.name] = {
696
+ type: "local",
697
+ command: [s.command, ...s.args ?? []],
698
+ enabled: true,
699
+ ...s.env && Object.keys(s.env).length > 0 ? { environment: s.env } : {}
700
+ };
701
+ }
702
+ return JSON.stringify({ mcp });
703
+ }
704
+ function writeMcpServersConfig(servers) {
705
+ if (servers.length === 0) return null;
706
+ const mcpServers = {};
707
+ for (const s of servers) {
708
+ mcpServers[s.name] = {
709
+ command: s.command,
710
+ ...s.args ? { args: s.args } : {},
711
+ ...s.env ? { env: s.env } : {}
712
+ };
713
+ }
714
+ const dir = mkdtempSync(join(tmpdir(), "sideboard-mcp-"));
715
+ const cfgPath = join(dir, "mcp.json");
716
+ writeFileSync(cfgPath, JSON.stringify({ mcpServers }, null, 2));
717
+ return cfgPath;
718
+ }
719
+
720
+ // src/agents/types.ts
721
+ var PLAN_MODE_INSTRUCTION = "Plan mode is active and must remain active until the user turns Plan mode off in the UI (or explicitly asks you to implement). Analyze the codebase, search and read files as needed, and produce or refine a clear implementation plan. Do not modify, create, or delete any files. Do not exit plan mode on your own.";
722
+ function permissionMode(thread) {
723
+ if (thread.planMode) {
724
+ return {
725
+ claude: "plan",
726
+ opencodePermission: JSON.stringify({
727
+ edit: "deny",
728
+ write: "deny",
729
+ bash: { "*": "deny" }
730
+ }),
731
+ codexSandbox: "read-only"
732
+ };
733
+ }
734
+ if (thread.autonomy === "full") {
735
+ return {
736
+ claude: "bypassPermissions",
737
+ opencodePermission: JSON.stringify({ "*": "allow" }),
738
+ codexSandbox: "workspace-write"
739
+ };
740
+ }
741
+ return {
742
+ claude: "acceptEdits",
743
+ opencodePermission: JSON.stringify({
744
+ edit: "allow",
745
+ bash: { "*": "allow", "rm -rf *": "deny" }
746
+ }),
747
+ codexSandbox: "workspace-write"
748
+ };
749
+ }
750
+
751
+ // src/agents/claude.ts
752
+ var BASE_ALLOWED_TOOLS = [
753
+ "Edit",
754
+ "Write",
755
+ "Bash",
756
+ "Read",
757
+ "Glob",
758
+ "Grep",
759
+ "WebFetch",
760
+ "WebSearch"
761
+ ];
762
+ var CLAUDE_CHROME_ALLOWED_TOOLS = [
763
+ "mcp__claude-in-chrome",
764
+ "mcp__claude-in-chrome__*",
765
+ "Skill(claude-in-chrome)"
766
+ ];
767
+ var CLAUDE_PROMPT_ARG_MAX = 2e5;
768
+ async function loadMcpServers() {
769
+ const claude = resolveClaudeExecutable();
770
+ const mcpText = await run(claude, ["mcp", "list"], { reject: false });
771
+ return parseMcpList(`${mcpText.stdout}
772
+ ${mcpText.stderr}`);
773
+ }
774
+ function usageFromClaude(usage) {
775
+ if (!usage) return null;
776
+ const inputTokens = Number(usage.input_tokens ?? 0);
777
+ const outputTokens = Number(usage.output_tokens ?? 0);
778
+ if (!inputTokens && !outputTokens) return null;
779
+ return {
780
+ inputTokens,
781
+ outputTokens,
782
+ cacheReadTokens: usage.cache_read_input_tokens ? Number(usage.cache_read_input_tokens) : void 0,
783
+ cacheWriteTokens: usage.cache_creation_input_tokens ? Number(usage.cache_creation_input_tokens) : void 0
784
+ };
785
+ }
786
+ function claudeResultErrorDetail(obj) {
787
+ const isError = Boolean(obj.is_error) || typeof obj.subtype === "string" && /^error/i.test(obj.subtype);
788
+ const fromResult = typeof obj.result === "string" ? obj.result.trim() : "";
789
+ if (fromResult && (isError || looksLikeAgentFailureMessage(fromResult))) {
790
+ return fromResult;
791
+ }
792
+ if (!isError) return null;
793
+ const errors = obj.errors;
794
+ if (Array.isArray(errors)) {
795
+ const parts = errors.map((e) => {
796
+ if (typeof e === "string") return e.trim();
797
+ if (e && typeof e === "object" && typeof e.message === "string") {
798
+ return e.message.trim();
799
+ }
800
+ return "";
801
+ }).filter(Boolean);
802
+ if (parts.length) return parts.join("; ");
803
+ }
804
+ if (typeof obj.error === "string" && obj.error.trim()) return obj.error.trim();
805
+ if (typeof obj.subtype === "string" && obj.subtype) {
806
+ return obj.subtype.replace(/^error[_-]?/i, "").replace(/_/g, " ") || "Claude turn failed";
807
+ }
808
+ return "Claude turn failed";
809
+ }
810
+ function eventsFromContentBlocks(blocks) {
811
+ if (!blocks?.length) return [];
812
+ const out = [];
813
+ for (const block of blocks) {
814
+ if (!block?.type) continue;
815
+ if (block.type === "text" && block.text) {
816
+ out.push({ type: "stdout", data: block.text });
817
+ continue;
818
+ }
819
+ if ((block.type === "thinking" || block.type === "redacted_thinking") && block.thinking) {
820
+ out.push({ type: "thinking", data: block.thinking });
821
+ continue;
822
+ }
823
+ if (block.type === "tool_use" && block.id && block.name) {
824
+ out.push({
825
+ type: "tool_use",
826
+ id: block.id,
827
+ name: block.name,
828
+ input: block.input
829
+ });
830
+ continue;
831
+ }
832
+ if (block.type === "tool_result" && block.tool_use_id) {
833
+ const content = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map(
834
+ (c) => typeof c === "string" ? c : c && typeof c === "object" && "text" in c ? String(c.text ?? "") : ""
835
+ ).join("") : block.content != null ? JSON.stringify(block.content) : void 0;
836
+ out.push({
837
+ type: "tool_result",
838
+ id: block.tool_use_id,
839
+ content,
840
+ isError: Boolean(block.is_error)
841
+ });
842
+ }
843
+ }
844
+ return out;
845
+ }
846
+ var claudeAdapter = {
847
+ kind: "claude",
848
+ async detect() {
849
+ const claude = resolveClaudeExecutable();
850
+ if (claude !== "claude") {
851
+ if (!existsSync2(claude)) {
852
+ return {
853
+ agent: "claude",
854
+ installed: false,
855
+ authenticated: false,
856
+ linearMcp: false,
857
+ warnings: [],
858
+ reason: `Claude Code executable not found: ${claude}`
859
+ };
860
+ }
861
+ } else {
862
+ const which = await run("which", ["claude"], { reject: false });
863
+ if (which.exitCode !== 0) {
864
+ return {
865
+ agent: "claude",
866
+ installed: false,
867
+ authenticated: false,
868
+ linearMcp: false,
869
+ warnings: [],
870
+ reason: "claude CLI not found on PATH"
871
+ };
872
+ }
873
+ }
874
+ const auth = await run(claude, ["auth", "status"], { reject: false });
875
+ const authenticated = auth.exitCode === 0;
876
+ const servers = await loadMcpServers();
877
+ const linearMcp = servers.some(
878
+ (s) => s.connected && /linear/i.test(s.name)
879
+ );
880
+ return {
881
+ agent: "claude",
882
+ installed: true,
883
+ authenticated,
884
+ linearMcp,
885
+ warnings: mcpAuthWarnings(servers),
886
+ reason: authenticated ? void 0 : "claude auth status failed \u2014 run `claude auth login`"
887
+ };
888
+ },
889
+ async buildTurn(thread, input) {
890
+ const claude = resolveClaudeExecutable();
891
+ const turn = normalizeTurnInput(input);
892
+ const sessionId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
893
+ const effective = sessionId ? { prompt: turn.prompt } : turn;
894
+ const promptText = flattenTurnInput(effective);
895
+ const useStdin = promptText.length > CLAUDE_PROMPT_ARG_MAX;
896
+ if (process.env.SIDEBOARD_DEBUG_CLAUDE_TURN === "1") {
897
+ console.error(
898
+ `[sideboard/claude] promptChars=${promptText.length} resumed=${Boolean(sessionId)} stdin=${useStdin}`
899
+ );
900
+ }
901
+ const mode = permissionMode(thread);
902
+ const { isOrchestratorThread } = await import("./global-workspace-OJF4ENH4.js");
903
+ const isOrchestrator = isOrchestratorThread(thread);
904
+ const injectedServers = await buildInjectedMcpServers({
905
+ includeSideboard: true,
906
+ includeBrightsy: isBrightsyConnected()
907
+ });
908
+ const injectedBrightsyNames = injectedServers.filter((s) => s.name === "brightsy" || s.name.startsWith("brightsy_")).map((s) => s.name);
909
+ const chromeOn = claudeChromeEnabled();
910
+ let allowedTools;
911
+ if (isOrchestrator) {
912
+ allowedTools = [
913
+ ...BASE_ALLOWED_TOOLS,
914
+ ...SIDEBOARD_MCP_ALLOWED_TOOLS,
915
+ ...brightsyMcpAllowedTools(injectedBrightsyNames)
916
+ ];
917
+ } else {
918
+ const servers = await loadMcpServers();
919
+ allowedTools = [
920
+ ...BASE_ALLOWED_TOOLS,
921
+ ...mcpAllowTools(servers),
922
+ ...SIDEBOARD_ARTIFACT_MCP_ALLOWED_TOOLS,
923
+ ...brightsyMcpAllowedTools(injectedBrightsyNames)
924
+ ];
925
+ }
926
+ if (chromeOn) {
927
+ allowedTools = [...allowedTools, ...CLAUDE_CHROME_ALLOWED_TOOLS];
928
+ }
929
+ const args = [
930
+ "-p",
931
+ ...useStdin ? [] : [promptText],
932
+ "--output-format",
933
+ "stream-json",
934
+ "--verbose",
935
+ "--include-partial-messages",
936
+ "--permission-mode",
937
+ mode.claude
938
+ ];
939
+ const mcpConfigPath = writeMcpServersConfig(injectedServers);
940
+ if (mcpConfigPath) {
941
+ args.push("--mcp-config", mcpConfigPath);
942
+ }
943
+ if (chromeOn) {
944
+ args.push("--chrome");
945
+ }
946
+ if (useStdin) {
947
+ args.push("--input-format", "text");
948
+ }
949
+ for (const tool of allowedTools) {
950
+ args.push("--allowedTools", tool);
951
+ }
952
+ if (thread.model) {
953
+ args.push("--model", thread.model);
954
+ }
955
+ const effort = thread.effort ?? (thread.fast ? "low" : "high");
956
+ args.push("--effort", effort);
957
+ if (sessionId) {
958
+ args.push("--resume", sessionId);
959
+ }
960
+ return {
961
+ file: claude,
962
+ args,
963
+ cwd: thread.worktreePath,
964
+ stdin: useStdin ? `${promptText}
965
+ ` : void 0
966
+ };
967
+ },
968
+ parseEvent(line) {
969
+ const trimmed = line.trim();
970
+ if (!trimmed) return null;
971
+ try {
972
+ const obj = JSON.parse(trimmed);
973
+ if (obj.type === "system" && obj.subtype === "init") {
974
+ const sid = obj.session_id;
975
+ if (typeof sid === "string") return { type: "session_id", data: sid };
976
+ return null;
977
+ }
978
+ if (obj.type === "system" && typeof obj.session_id === "string") {
979
+ return { type: "session_id", data: obj.session_id };
980
+ }
981
+ if (obj.type === "assistant" || obj.type === "user") {
982
+ const content = obj.message?.content;
983
+ const events = eventsFromContentBlocks(content);
984
+ if (events.length === 0) return null;
985
+ return events.length === 1 ? events[0] : events;
986
+ }
987
+ if (obj.type === "stream_event") {
988
+ const event = obj.event;
989
+ if (!event) return null;
990
+ if (event.type === "content_block_start" && event.content_block) {
991
+ const block = event.content_block;
992
+ if (block.type === "tool_use" && block.id && block.name) {
993
+ return {
994
+ type: "tool_use",
995
+ id: block.id,
996
+ name: block.name,
997
+ input: block.input
998
+ };
999
+ }
1000
+ if (block.type === "thinking" && block.thinking) {
1001
+ return { type: "thinking", data: block.thinking };
1002
+ }
1003
+ return null;
1004
+ }
1005
+ if (event.type === "content_block_delta" && event.delta) {
1006
+ if (event.delta.text) return { type: "stdout", data: event.delta.text };
1007
+ if (event.delta.thinking) return { type: "thinking", data: event.delta.thinking };
1008
+ return null;
1009
+ }
1010
+ return null;
1011
+ }
1012
+ if (obj.type === "content_block_delta") {
1013
+ const delta = obj.delta;
1014
+ if (delta?.text) return { type: "stdout", data: delta.text };
1015
+ if (delta?.thinking) return { type: "thinking", data: delta.thinking };
1016
+ return null;
1017
+ }
1018
+ if (obj.type === "result") {
1019
+ const events = [];
1020
+ const errorDetail = claudeResultErrorDetail(obj);
1021
+ if (errorDetail) {
1022
+ events.push({ type: "stderr", data: errorDetail });
1023
+ } else {
1024
+ const text = obj.result;
1025
+ if (typeof text === "string" && text) events.push({ type: "stdout", data: text });
1026
+ }
1027
+ const usage = usageFromClaude(obj.usage);
1028
+ if (usage) events.push({ type: "usage", data: usage });
1029
+ if (events.length === 0) return null;
1030
+ return events.length === 1 ? events[0] : events;
1031
+ }
1032
+ return null;
1033
+ } catch {
1034
+ return { type: "stdout", data: line };
1035
+ }
1036
+ },
1037
+ async resolveSessionId(_worktreePath, cached) {
1038
+ return cached;
1039
+ },
1040
+ async buildAttach(thread) {
1041
+ const claude = resolveClaudeExecutable();
1042
+ const sessionId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
1043
+ const args = sessionId ? ["--resume", sessionId] : [];
1044
+ if (claudeChromeEnabled()) {
1045
+ args.push("--chrome");
1046
+ }
1047
+ return { file: claude, args, cwd: thread.worktreePath };
1048
+ },
1049
+ async listLinearIssues(_repoPath) {
1050
+ const claude = resolveClaudeExecutable();
1051
+ const prompt = "List my assigned Linear issues as JSON array only, no markdown. Each item: id, identifier, title, url, labels (string[]).";
1052
+ const { stdout, exitCode } = await run(
1053
+ claude,
1054
+ [
1055
+ "-p",
1056
+ prompt,
1057
+ "--output-format",
1058
+ "json",
1059
+ "--permission-mode",
1060
+ "bypassPermissions",
1061
+ "--allowedTools",
1062
+ "mcp__linear__*"
1063
+ ],
1064
+ { reject: false }
1065
+ );
1066
+ if (exitCode !== 0) return [];
1067
+ return parseIssuesJson(stdout);
1068
+ }
1069
+ };
1070
+ function parseIssuesJson(raw) {
1071
+ const text = raw.trim();
1072
+ const candidates = [text];
1073
+ const match = text.match(/\[[\s\S]*\]/);
1074
+ if (match) candidates.push(match[0]);
1075
+ for (const c of candidates) {
1076
+ try {
1077
+ const parsed = JSON.parse(c);
1078
+ if (Array.isArray(parsed)) {
1079
+ return parsed.map((item) => ({
1080
+ id: String(item.id ?? item.identifier ?? ""),
1081
+ identifier: String(item.identifier ?? item.id ?? ""),
1082
+ title: String(item.title ?? ""),
1083
+ url: String(item.url ?? ""),
1084
+ labels: Array.isArray(item.labels) ? item.labels.map(String) : []
1085
+ }));
1086
+ }
1087
+ if (parsed && typeof parsed === "object" && typeof parsed.result === "string") {
1088
+ return parseIssuesJson(parsed.result);
1089
+ }
1090
+ } catch {
1091
+ }
1092
+ }
1093
+ return [];
1094
+ }
1095
+
1096
+ // src/agents/codex.ts
1097
+ import { existsSync as existsSync3, readFileSync } from "fs";
1098
+ import { homedir } from "os";
1099
+ import { join as join2 } from "path";
1100
+ var CODEX_PROMPT_ARG_MAX = 2e5;
1101
+ var FALLBACK_CODEX_MODELS = [
1102
+ { id: "gpt-5.6-sol", displayName: "GPT-5.6 Sol" },
1103
+ { id: "gpt-5.6-terra", displayName: "GPT-5.6 Terra" },
1104
+ { id: "gpt-5.6-luna", displayName: "GPT-5.6 Luna" },
1105
+ { id: "gpt-5.5", displayName: "GPT-5.5" },
1106
+ { id: "gpt-5.2", displayName: "GPT-5.2" }
1107
+ ];
1108
+ var cachedCodexModels = null;
1109
+ var CODEX_MODEL_CACHE_MS = 5 * 60 * 1e3;
1110
+ async function listCodexModels() {
1111
+ const now = Date.now();
1112
+ if (cachedCodexModels && now - cachedCodexModels.at < CODEX_MODEL_CACHE_MS) {
1113
+ return cachedCodexModels.models;
1114
+ }
1115
+ const which = await run("which", ["codex"], { reject: false });
1116
+ if (which.exitCode !== 0) return FALLBACK_CODEX_MODELS;
1117
+ const listed = await run("codex", ["debug", "models"], { reject: false });
1118
+ if (listed.exitCode !== 0 || !listed.stdout.trim()) {
1119
+ return cachedCodexModels?.models ?? FALLBACK_CODEX_MODELS;
1120
+ }
1121
+ try {
1122
+ const parsed = JSON.parse(listed.stdout);
1123
+ const rows = Array.isArray(parsed.models) ? parsed.models : [];
1124
+ const preferred = rows.filter((m) => (m.visibility ?? "list") === "list");
1125
+ const source = preferred.length > 0 ? preferred : rows;
1126
+ const models = source.map((m) => ({
1127
+ id: (m.slug || "").trim(),
1128
+ displayName: (m.display_name || m.slug || "").trim(),
1129
+ description: m.description,
1130
+ priority: typeof m.priority === "number" ? m.priority : 999
1131
+ })).filter((m) => m.id).sort((a, b) => a.priority - b.priority).map(({ id, displayName, description }) => ({ id, displayName, description }));
1132
+ if (models.length === 0) return FALLBACK_CODEX_MODELS;
1133
+ cachedCodexModels = { at: now, models };
1134
+ return models;
1135
+ } catch {
1136
+ return cachedCodexModels?.models ?? FALLBACK_CODEX_MODELS;
1137
+ }
1138
+ }
1139
+ function usageFromCodex(usage) {
1140
+ if (!usage) return null;
1141
+ const inputTokens = Number(usage.input_tokens ?? 0);
1142
+ const outputTokens = Number(usage.output_tokens ?? 0) + Number(usage.reasoning_output_tokens ?? 0);
1143
+ if (!inputTokens && !outputTokens) return null;
1144
+ return {
1145
+ inputTokens,
1146
+ outputTokens,
1147
+ cacheReadTokens: usage.cached_input_tokens ? Number(usage.cached_input_tokens) : void 0
1148
+ };
1149
+ }
1150
+ function codexConfigHasNetworkAccess() {
1151
+ const candidates = [
1152
+ join2(homedir(), ".codex", "config.toml"),
1153
+ join2(homedir(), ".config", "codex", "config.toml")
1154
+ ];
1155
+ for (const path of candidates) {
1156
+ if (!existsSync3(path)) continue;
1157
+ const text = readFileSync(path, "utf8");
1158
+ if (/network_access\s*=\s*true/.test(text)) return true;
1159
+ }
1160
+ return false;
1161
+ }
1162
+ var codexAdapter = {
1163
+ kind: "codex",
1164
+ async detect() {
1165
+ const which = await run("which", ["codex"], { reject: false });
1166
+ if (which.exitCode !== 0) {
1167
+ return {
1168
+ agent: "codex",
1169
+ installed: false,
1170
+ authenticated: false,
1171
+ linearMcp: false,
1172
+ warnings: [],
1173
+ reason: "codex CLI not found on PATH"
1174
+ };
1175
+ }
1176
+ const auth = await run("codex", ["login", "status"], { reject: false });
1177
+ const authenticated = auth.exitCode === 0;
1178
+ const mcp = await run("codex", ["mcp", "list", "--json"], { reject: false });
1179
+ const linearMcp = /linear/i.test(mcp.stdout + mcp.stderr);
1180
+ const warnings = [];
1181
+ if (!codexConfigHasNetworkAccess()) {
1182
+ warnings.push(
1183
+ "Codex workspace-write blocks network by default \u2014 set [sandbox_workspace_write] network_access = true in ~/.codex/config.toml if agents need npm install etc."
1184
+ );
1185
+ }
1186
+ return {
1187
+ agent: "codex",
1188
+ installed: true,
1189
+ authenticated,
1190
+ linearMcp,
1191
+ warnings,
1192
+ reason: authenticated ? void 0 : "codex login status failed \u2014 run `codex login`"
1193
+ };
1194
+ },
1195
+ async buildTurn(thread, input) {
1196
+ const prompt = flattenTurnInput(input);
1197
+ const sessionId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
1198
+ const useStdin = prompt.length > CODEX_PROMPT_ARG_MAX;
1199
+ const promptArg = useStdin ? "-" : prompt;
1200
+ if (process.env.SIDEBOARD_DEBUG_CODEX_TURN === "1") {
1201
+ const { cachedPrefix } = normalizeTurnInput(input);
1202
+ console.error(
1203
+ `[sideboard/codex] promptChars=${prompt.length} resumed=${Boolean(sessionId)} stdin=${useStdin} hasPrefix=${Boolean(cachedPrefix)}`
1204
+ );
1205
+ }
1206
+ const mode = permissionMode(thread);
1207
+ const model = thread.model?.trim();
1208
+ const injected = await buildInjectedMcpServers({
1209
+ includeSideboard: true,
1210
+ includeBrightsy: isBrightsyConnected()
1211
+ });
1212
+ const mcpOverrides = toCodexMcpConfigArgs(injected);
1213
+ const args = [
1214
+ "exec",
1215
+ ...sessionId ? ["resume", sessionId] : [],
1216
+ promptArg,
1217
+ "--cd",
1218
+ thread.worktreePath,
1219
+ "--json",
1220
+ "--sandbox",
1221
+ mode.codexSandbox,
1222
+ "--ask-for-approval",
1223
+ "never",
1224
+ ...model ? ["--model", model] : [],
1225
+ ...mcpOverrides
1226
+ ];
1227
+ return {
1228
+ file: "codex",
1229
+ args,
1230
+ cwd: thread.worktreePath,
1231
+ stdin: useStdin ? `${prompt}
1232
+ ` : void 0
1233
+ };
1234
+ },
1235
+ parseEvent(line) {
1236
+ const trimmed = line.trim();
1237
+ if (!trimmed) return null;
1238
+ try {
1239
+ const obj = JSON.parse(trimmed);
1240
+ const type = typeof obj.type === "string" ? obj.type : "";
1241
+ if (type === "turn.failed" || type === "turn_failed") {
1242
+ const detail = extractJsonErrorMessage(obj) || extractJsonErrorMessage(obj.error ?? {}) || "Codex turn failed";
1243
+ return { type: "stderr", data: detail };
1244
+ }
1245
+ if (type === "error") {
1246
+ const detail = extractJsonErrorMessage(obj) || trimmed;
1247
+ if (/^reconnecting\.\.\./i.test(detail)) return null;
1248
+ return { type: "stderr", data: detail };
1249
+ }
1250
+ if (typeof obj.item === "object" && obj.item !== null) {
1251
+ const item = obj.item;
1252
+ if (item.type === "error") {
1253
+ const detail = item.message?.trim() || extractJsonErrorMessage(obj) || "Codex item error";
1254
+ return { type: "stderr", data: detail };
1255
+ }
1256
+ if (item.type === "agent_message" && item.text) {
1257
+ return { type: "stdout", data: item.text };
1258
+ }
1259
+ if (item.status === "failed") {
1260
+ const detail = item.message?.trim() || extractJsonErrorMessage(item) || `Codex ${item.type ?? "item"} failed`;
1261
+ return { type: "stderr", data: detail };
1262
+ }
1263
+ }
1264
+ const sid = typeof obj.session_id === "string" && obj.session_id || typeof obj.thread_id === "string" && obj.thread_id || typeof obj.session?.id === "string" && obj.session.id;
1265
+ if (sid && (type === "thread.started" || type === "session" || !type)) {
1266
+ return { type: "session_id", data: sid };
1267
+ }
1268
+ if (sid && type.endsWith(".started")) {
1269
+ return { type: "session_id", data: sid };
1270
+ }
1271
+ if (type === "turn.completed" || type === "turn_completed") {
1272
+ const usage = usageFromCodex(obj.usage);
1273
+ return usage ? { type: "usage", data: usage } : null;
1274
+ }
1275
+ if (typeof obj.content === "string" && obj.content.trim()) {
1276
+ return { type: "stdout", data: obj.content };
1277
+ }
1278
+ return null;
1279
+ } catch {
1280
+ if (/error|failed|unauthorized|quota|limit/i.test(trimmed)) {
1281
+ return { type: "stderr", data: trimmed };
1282
+ }
1283
+ return { type: "stdout", data: line };
1284
+ }
1285
+ },
1286
+ async resolveSessionId(_worktreePath, cached) {
1287
+ return cached;
1288
+ },
1289
+ async buildAttach(thread) {
1290
+ const sessionId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
1291
+ if (sessionId) {
1292
+ return {
1293
+ file: "codex",
1294
+ args: ["exec", "resume", sessionId, "--cd", thread.worktreePath],
1295
+ cwd: thread.worktreePath
1296
+ };
1297
+ }
1298
+ return {
1299
+ file: "codex",
1300
+ args: ["--cd", thread.worktreePath],
1301
+ cwd: thread.worktreePath
1302
+ };
1303
+ },
1304
+ async listLinearIssues(_repoPath) {
1305
+ const prompt = "List my assigned Linear issues as JSON array only: id, identifier, title, url, labels.";
1306
+ const { stdout, exitCode } = await run(
1307
+ "codex",
1308
+ [
1309
+ "exec",
1310
+ prompt,
1311
+ "--json",
1312
+ "--sandbox",
1313
+ "read-only",
1314
+ "--ask-for-approval",
1315
+ "never"
1316
+ ],
1317
+ { reject: false }
1318
+ );
1319
+ if (exitCode !== 0) return [];
1320
+ const match = stdout.match(/\[[\s\S]*\]/);
1321
+ if (!match) return [];
1322
+ try {
1323
+ const parsed = JSON.parse(match[0]);
1324
+ return Array.isArray(parsed) ? parsed : [];
1325
+ } catch {
1326
+ return [];
1327
+ }
1328
+ }
1329
+ };
1330
+
1331
+ // src/agents/cursor.ts
1332
+ import { existsSync as existsSync4 } from "fs";
1333
+ import { createRequire as createRequire2 } from "module";
1334
+ import { dirname as dirname2, join as join3 } from "path";
1335
+ import { fileURLToPath as fileURLToPath2 } from "url";
1336
+ import { Cursor } from "@cursor/sdk";
1337
+
1338
+ // src/agents/cursor-events.ts
1339
+ function usageFromCursor(usage) {
1340
+ if (!usage) return null;
1341
+ const inputTokens = Number(usage.inputTokens ?? 0);
1342
+ const outputTokens = Number(usage.outputTokens ?? 0);
1343
+ if (!inputTokens && !outputTokens) return null;
1344
+ return {
1345
+ inputTokens,
1346
+ outputTokens,
1347
+ cacheReadTokens: usage.cacheReadTokens ? Number(usage.cacheReadTokens) : void 0,
1348
+ cacheWriteTokens: usage.cacheWriteTokens ? Number(usage.cacheWriteTokens) : void 0
1349
+ };
1350
+ }
1351
+ function cursorSdkMessageToEvents(msg) {
1352
+ if (!msg?.type) return [];
1353
+ if (msg.type === "system" && msg.agent_id) {
1354
+ return [{ type: "session_id", data: msg.agent_id }];
1355
+ }
1356
+ if (msg.type === "thinking" && msg.text) {
1357
+ return [{ type: "thinking", data: msg.text }];
1358
+ }
1359
+ if (msg.type === "assistant" && msg.message?.content?.length) {
1360
+ const out = [];
1361
+ for (const block of msg.message.content) {
1362
+ if (block?.type === "text" && block.text) {
1363
+ out.push({ type: "stdout", data: block.text });
1364
+ } else if (block?.type === "tool_use" && block.id && block.name) {
1365
+ out.push({
1366
+ type: "tool_use",
1367
+ id: block.id,
1368
+ name: block.name,
1369
+ input: block.input && typeof block.input === "object" ? block.input : void 0
1370
+ });
1371
+ }
1372
+ }
1373
+ return out;
1374
+ }
1375
+ if (msg.type === "tool_call" && msg.call_id && msg.name) {
1376
+ if (msg.status === "running") {
1377
+ return [
1378
+ {
1379
+ type: "tool_use",
1380
+ id: msg.call_id,
1381
+ name: msg.name,
1382
+ input: msg.args && typeof msg.args === "object" ? msg.args : void 0
1383
+ }
1384
+ ];
1385
+ }
1386
+ if (msg.status === "completed" || msg.status === "error") {
1387
+ const content = typeof msg.result === "string" ? msg.result : msg.result != null ? JSON.stringify(msg.result) : void 0;
1388
+ return [
1389
+ {
1390
+ type: "tool_result",
1391
+ id: msg.call_id,
1392
+ content,
1393
+ isError: msg.status === "error"
1394
+ }
1395
+ ];
1396
+ }
1397
+ }
1398
+ if (msg.type === "usage") {
1399
+ const usage = usageFromCursor(msg.usage);
1400
+ if (usage) return [{ type: "usage", data: usage }];
1401
+ }
1402
+ if (msg.type === "status" && msg.status === "ERROR") {
1403
+ const rawMessage = msg.message;
1404
+ const detail = (typeof rawMessage === "string" ? rawMessage.trim() : "") || extractJsonErrorMessage(msg) || formatUnknownDetail(msg.error) || msg.text || "Cursor run entered ERROR status";
1405
+ return [{ type: "stderr", data: detail }];
1406
+ }
1407
+ if (msg.type === "error") {
1408
+ const detail = extractJsonErrorMessage(msg) || formatUnknownDetail(msg.error) || msg.text || "Cursor run error";
1409
+ return [{ type: "stderr", data: detail }];
1410
+ }
1411
+ return [];
1412
+ }
1413
+ function parseCursorRunnerLine(line) {
1414
+ const trimmed = line.trim();
1415
+ if (!trimmed) return null;
1416
+ try {
1417
+ const obj = JSON.parse(trimmed);
1418
+ if (Array.isArray(obj)) return obj;
1419
+ if (obj && typeof obj === "object" && "events" in obj && Array.isArray(obj.events)) {
1420
+ return obj.events;
1421
+ }
1422
+ if (obj && typeof obj === "object" && "type" in obj) {
1423
+ return obj;
1424
+ }
1425
+ return null;
1426
+ } catch {
1427
+ return { type: "stdout", data: line };
1428
+ }
1429
+ }
1430
+
1431
+ // src/agents/cursor.ts
1432
+ var FALLBACK_CURSOR_MODELS = [
1433
+ { id: "default", displayName: "Auto" },
1434
+ { id: "composer-2.5", displayName: "Composer 2.5" },
1435
+ { id: "composer-2", displayName: "Composer 2" }
1436
+ ];
1437
+ var cachedModels = null;
1438
+ var MODEL_CACHE_MS = 5 * 60 * 1e3;
1439
+ function resolveCursorApiKey() {
1440
+ const fromEnv = (process.env.CURSOR_API_KEY || "").trim();
1441
+ if (fromEnv) return fromEnv;
1442
+ return (loadAppSettings().environment.CURSOR_API_KEY || "").trim();
1443
+ }
1444
+ function isCursorAutoModel(model) {
1445
+ const id = (model ?? "").trim().toLowerCase();
1446
+ return !id || id === "default" || id === "auto";
1447
+ }
1448
+ function resolveCursorModelId(model) {
1449
+ if (isCursorAutoModel(model)) return "default";
1450
+ return model.trim();
1451
+ }
1452
+ async function listCursorModels() {
1453
+ const now = Date.now();
1454
+ if (cachedModels && now - cachedModels.at < MODEL_CACHE_MS) {
1455
+ return cachedModels.models;
1456
+ }
1457
+ const apiKey = resolveCursorApiKey();
1458
+ if (!apiKey) return FALLBACK_CURSOR_MODELS;
1459
+ try {
1460
+ const listed = await Cursor.models.list({ apiKey });
1461
+ const models = listed.map((m) => ({
1462
+ id: m.id,
1463
+ displayName: m.displayName || m.id,
1464
+ description: m.description
1465
+ })).filter((m) => Boolean(m.id));
1466
+ if (models.length === 0) return FALLBACK_CURSOR_MODELS;
1467
+ cachedModels = { at: now, models };
1468
+ return models;
1469
+ } catch {
1470
+ return cachedModels?.models ?? FALLBACK_CURSOR_MODELS;
1471
+ }
1472
+ }
1473
+ function entryDir() {
1474
+ const cjsDir = typeof __dirname !== "undefined" ? __dirname : "";
1475
+ if (cjsDir) return cjsDir;
1476
+ try {
1477
+ return dirname2(fileURLToPath2(import.meta.url));
1478
+ } catch {
1479
+ try {
1480
+ const req = createRequire2(process.cwd() + "/");
1481
+ return dirname2(req.resolve("@sideboard-ai/core"));
1482
+ } catch {
1483
+ return process.cwd();
1484
+ }
1485
+ }
1486
+ }
1487
+ function cursorRunnerPath() {
1488
+ const root = entryDir();
1489
+ const candidates = [
1490
+ join3(root, "agents", "cursor-runner.js"),
1491
+ join3(root, "agents", "cursor-runner.cjs"),
1492
+ // If somehow resolved from package root instead of dist/
1493
+ join3(root, "dist", "agents", "cursor-runner.js"),
1494
+ join3(root, "dist", "agents", "cursor-runner.cjs"),
1495
+ // Source tree (dev): packages/core/src/agents/cursor-runner.ts
1496
+ join3(root, "cursor-runner.ts"),
1497
+ join3(root, "src", "agents", "cursor-runner.ts")
1498
+ ];
1499
+ for (const candidate of candidates) {
1500
+ if (existsSync4(candidate)) return candidate;
1501
+ }
1502
+ return candidates[0];
1503
+ }
1504
+ var cursorAdapter = {
1505
+ kind: "cursor",
1506
+ async detect() {
1507
+ const apiKey = resolveCursorApiKey();
1508
+ if (!apiKey) {
1509
+ return {
1510
+ agent: "cursor",
1511
+ installed: true,
1512
+ authenticated: false,
1513
+ linearMcp: false,
1514
+ warnings: [],
1515
+ reason: "CURSOR_API_KEY not set \u2014 add it in Settings \u2192 Agents \u2192 Cursor, or Settings \u2192 Environment"
1516
+ };
1517
+ }
1518
+ try {
1519
+ await Cursor.models.list({ apiKey });
1520
+ return {
1521
+ agent: "cursor",
1522
+ installed: true,
1523
+ authenticated: true,
1524
+ linearMcp: false,
1525
+ warnings: []
1526
+ };
1527
+ } catch (err) {
1528
+ const message = err instanceof Error ? err.message : String(err);
1529
+ return {
1530
+ agent: "cursor",
1531
+ installed: true,
1532
+ authenticated: false,
1533
+ linearMcp: false,
1534
+ warnings: [],
1535
+ reason: `Cursor API auth failed: ${message}`
1536
+ };
1537
+ }
1538
+ },
1539
+ async buildTurn(thread, input) {
1540
+ const prompt = flattenTurnInput(input);
1541
+ const agentId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
1542
+ const apiKey = resolveCursorApiKey() || void 0;
1543
+ const injected = await buildInjectedMcpServers({
1544
+ includeSideboard: true,
1545
+ includeBrightsy: isBrightsyConnected()
1546
+ });
1547
+ const mcpServers = toCursorMcpServers(injected);
1548
+ const req = {
1549
+ prompt,
1550
+ cwd: thread.worktreePath,
1551
+ agentId,
1552
+ model: thread.model,
1553
+ effort: thread.effort,
1554
+ fast: thread.fast,
1555
+ planMode: thread.planMode,
1556
+ apiKey,
1557
+ ...Object.keys(mcpServers).length > 0 ? { mcpServers } : {}
1558
+ };
1559
+ const runner = cursorRunnerPath();
1560
+ const isTs = runner.endsWith(".ts");
1561
+ const launch = await resolveNodeLaunch(runner);
1562
+ return {
1563
+ file: launch.file,
1564
+ args: isTs ? ["--import", "tsx", runner] : [runner],
1565
+ cwd: thread.worktreePath,
1566
+ stdin: JSON.stringify(req),
1567
+ env: {
1568
+ ...launch.env,
1569
+ ...apiKey ? { CURSOR_API_KEY: apiKey } : {}
1570
+ }
1571
+ };
1572
+ },
1573
+ parseEvent(line) {
1574
+ return parseCursorRunnerLine(line);
1575
+ },
1576
+ async resolveSessionId(_worktreePath, cached) {
1577
+ return cached;
1578
+ },
1579
+ async buildAttach(thread) {
1580
+ const which = await run("which", ["cursor"], { reject: false });
1581
+ if (which.exitCode === 0) {
1582
+ return { file: "cursor", args: [thread.worktreePath], cwd: thread.worktreePath };
1583
+ }
1584
+ throw new Error(
1585
+ "Cursor agents have no interactive CLI attach. Install the Cursor shell command (`cursor`) to open the worktree, or continue the thread in Sideboard."
1586
+ );
1587
+ }
1588
+ };
1589
+
1590
+ // src/agents/opencode.ts
1591
+ var FALLBACK_OPENCODE_MODELS = [
1592
+ { id: "opencode/big-pickle", displayName: "opencode \xB7 big-pickle" },
1593
+ {
1594
+ id: "openrouter/~anthropic/claude-sonnet-latest",
1595
+ displayName: "openrouter \xB7 claude-sonnet-latest"
1596
+ },
1597
+ {
1598
+ id: "openrouter/~openai/gpt-latest",
1599
+ displayName: "openrouter \xB7 gpt-latest"
1600
+ }
1601
+ ];
1602
+ var cachedOpencodeModels = null;
1603
+ var OPENCODE_MODEL_CACHE_MS = 5 * 60 * 1e3;
1604
+ function displayNameFromOpencodeId(id) {
1605
+ const slash = id.indexOf("/");
1606
+ if (slash <= 0) return id;
1607
+ const provider = id.slice(0, slash);
1608
+ const name = id.slice(slash + 1);
1609
+ return `${provider} \xB7 ${name}`;
1610
+ }
1611
+ function sortOpencodeModelIds(ids) {
1612
+ return [...ids].sort((a, b) => {
1613
+ const aLatest = /latest|~/.test(a) ? 0 : 1;
1614
+ const bLatest = /latest|~/.test(b) ? 0 : 1;
1615
+ if (aLatest !== bLatest) return aLatest - bLatest;
1616
+ const aOc = a.startsWith("opencode/") ? 0 : 1;
1617
+ const bOc = b.startsWith("opencode/") ? 0 : 1;
1618
+ if (aOc !== bOc) return aOc - bOc;
1619
+ return a.localeCompare(b);
1620
+ });
1621
+ }
1622
+ async function listOpencodeModels() {
1623
+ const now = Date.now();
1624
+ if (cachedOpencodeModels && now - cachedOpencodeModels.at < OPENCODE_MODEL_CACHE_MS) {
1625
+ return cachedOpencodeModels.models;
1626
+ }
1627
+ const which = await run("which", ["opencode"], { reject: false });
1628
+ if (which.exitCode !== 0) return FALLBACK_OPENCODE_MODELS;
1629
+ const listed = await run("opencode", ["models"], { reject: false });
1630
+ if (listed.exitCode !== 0 || !listed.stdout.trim()) {
1631
+ return cachedOpencodeModels?.models ?? FALLBACK_OPENCODE_MODELS;
1632
+ }
1633
+ const ids = listed.stdout.split("\n").map((l) => l.trim()).filter((l) => /^[\w.~@+-]+\/[\w.~@+/-]+$/.test(l));
1634
+ const unique = [...new Set(ids)];
1635
+ if (unique.length === 0) return FALLBACK_OPENCODE_MODELS;
1636
+ const OPENCODE_PICKER_LIMIT = 60;
1637
+ const models = sortOpencodeModelIds(unique).slice(0, OPENCODE_PICKER_LIMIT).map((id) => ({
1638
+ id,
1639
+ displayName: displayNameFromOpencodeId(id)
1640
+ }));
1641
+ cachedOpencodeModels = { at: now, models };
1642
+ return models;
1643
+ }
1644
+ function usageFromOpencode(tokens) {
1645
+ if (!tokens) return null;
1646
+ const inputTokens = Number(tokens.input ?? 0);
1647
+ const outputTokens = Number(tokens.output ?? 0) + Number(tokens.reasoning ?? 0);
1648
+ if (!inputTokens && !outputTokens) return null;
1649
+ return {
1650
+ inputTokens,
1651
+ outputTokens,
1652
+ cacheReadTokens: tokens.cache?.read ? Number(tokens.cache.read) : void 0,
1653
+ cacheWriteTokens: tokens.cache?.write ? Number(tokens.cache.write) : void 0
1654
+ };
1655
+ }
1656
+ var opencodeAdapter = {
1657
+ kind: "opencode",
1658
+ async detect() {
1659
+ const which = await run("which", ["opencode"], { reject: false });
1660
+ if (which.exitCode !== 0) {
1661
+ return {
1662
+ agent: "opencode",
1663
+ installed: false,
1664
+ authenticated: false,
1665
+ linearMcp: false,
1666
+ warnings: [],
1667
+ reason: "opencode CLI not found on PATH"
1668
+ };
1669
+ }
1670
+ const auth = await run("opencode", ["auth", "list"], { reject: false });
1671
+ const authenticated = auth.exitCode === 0 && auth.stdout.trim().length > 0;
1672
+ const mcp = await run("opencode", ["mcp", "list"], { reject: false });
1673
+ const linearMcp = /linear/i.test(mcp.stdout + mcp.stderr);
1674
+ return {
1675
+ agent: "opencode",
1676
+ installed: true,
1677
+ authenticated,
1678
+ linearMcp,
1679
+ warnings: [],
1680
+ reason: authenticated ? void 0 : "opencode auth list empty \u2014 run `opencode auth login`"
1681
+ };
1682
+ },
1683
+ async buildTurn(thread, input) {
1684
+ const prompt = flattenTurnInput(input);
1685
+ const sessionId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
1686
+ const mode = permissionMode(thread);
1687
+ const model = thread.model?.trim();
1688
+ const args = [
1689
+ "run",
1690
+ "--dir",
1691
+ thread.worktreePath,
1692
+ "--format",
1693
+ "json"
1694
+ ];
1695
+ if (sessionId) {
1696
+ args.push("--session", sessionId);
1697
+ }
1698
+ if (model) {
1699
+ args.push("--model", model);
1700
+ }
1701
+ const injected = await buildInjectedMcpServers({
1702
+ includeSideboard: true,
1703
+ includeBrightsy: isBrightsyConnected()
1704
+ });
1705
+ const mcpContent = injected.length > 0 ? toOpencodeMcpConfigContent(injected) : null;
1706
+ return {
1707
+ file: "opencode",
1708
+ args,
1709
+ cwd: thread.worktreePath,
1710
+ // `opencode run` treats non-TTY stdin as the message body when no positional
1711
+ // message is given (see resolveRunInput in opencode's run.ts).
1712
+ stdin: prompt,
1713
+ env: {
1714
+ OPENCODE_PERMISSION: mode.opencodePermission,
1715
+ ...mcpContent ? { OPENCODE_CONFIG_CONTENT: mcpContent } : {}
1716
+ }
1717
+ };
1718
+ },
1719
+ parseEvent(line) {
1720
+ const trimmed = line.trim();
1721
+ if (!trimmed) return null;
1722
+ try {
1723
+ const obj = JSON.parse(trimmed);
1724
+ if (obj.type === "error") {
1725
+ const detail = extractJsonErrorMessage(obj) || formatUnknownDetail(obj.error) || formatUnknownDetail(obj.message) || trimmed;
1726
+ return { type: "stderr", data: detail };
1727
+ }
1728
+ const sid = typeof obj.sessionID === "string" && obj.sessionID || typeof obj.sessionId === "string" && obj.sessionId || typeof obj.session_id === "string" && obj.session_id;
1729
+ if (sid) return { type: "session_id", data: sid };
1730
+ if (obj.type === "text") {
1731
+ const text = obj.part?.text ?? obj.text;
1732
+ if (text) return { type: "stdout", data: text };
1733
+ }
1734
+ if (obj.type === "tool_use") {
1735
+ const part = obj.part;
1736
+ const id = part?.id ?? obj.id ?? `tool-${Date.now()}`;
1737
+ const name = part?.name ?? part?.tool ?? obj.name ?? obj.tool ?? "tool";
1738
+ const input = part?.input ?? obj.input;
1739
+ return { type: "tool_use", id, name, input };
1740
+ }
1741
+ if (obj.type === "tool_result") {
1742
+ const part = obj.part;
1743
+ const id = part?.tool_use_id ?? part?.id ?? obj.tool_use_id ?? obj.id;
1744
+ if (!id) return null;
1745
+ return {
1746
+ type: "tool_result",
1747
+ id,
1748
+ content: part?.output ?? part?.content ?? obj.output ?? obj.content
1749
+ };
1750
+ }
1751
+ if (obj.type === "step_finish" || obj.type === "step-finish") {
1752
+ const part = obj.part;
1753
+ const usage = usageFromOpencode(
1754
+ part?.tokens ?? obj.tokens
1755
+ );
1756
+ return usage ? { type: "usage", data: usage } : null;
1757
+ }
1758
+ return null;
1759
+ } catch {
1760
+ if (/error|failed|unauthorized|quota|limit/i.test(trimmed)) {
1761
+ return { type: "stderr", data: trimmed };
1762
+ }
1763
+ return { type: "stdout", data: line };
1764
+ }
1765
+ },
1766
+ async resolveSessionId(worktreePath, cached) {
1767
+ const listed = await run(
1768
+ "opencode",
1769
+ ["session", "list", "--format", "json"],
1770
+ { cwd: worktreePath, reject: false }
1771
+ );
1772
+ if (listed.exitCode === 0 && listed.stdout.trim()) {
1773
+ try {
1774
+ const sessions = JSON.parse(listed.stdout);
1775
+ if (Array.isArray(sessions) && sessions.length > 0) {
1776
+ const norm = (p) => p.replace(/\/+$/, "");
1777
+ const wt = norm(worktreePath);
1778
+ const match = sessions.find(
1779
+ (s) => s.directory && norm(s.directory) === wt || s.path && norm(s.path) === wt
1780
+ );
1781
+ if (match?.id) return match.id;
1782
+ }
1783
+ } catch {
1784
+ }
1785
+ }
1786
+ return cached;
1787
+ },
1788
+ async buildAttach(thread) {
1789
+ const sessionId = await this.resolveSessionId(thread.worktreePath, thread.sessionId);
1790
+ const args = ["--dir", thread.worktreePath];
1791
+ if (sessionId) args.push("--session", sessionId);
1792
+ return {
1793
+ file: "opencode",
1794
+ args,
1795
+ cwd: thread.worktreePath,
1796
+ env: {
1797
+ OPENCODE_PERMISSION: permissionMode(thread).opencodePermission
1798
+ }
1799
+ };
1800
+ },
1801
+ async listLinearIssues(_repoPath) {
1802
+ const prompt = "List my assigned Linear issues as JSON array only: id, identifier, title, url, labels.";
1803
+ const { stdout, exitCode } = await run(
1804
+ "opencode",
1805
+ ["run", prompt, "--format", "json"],
1806
+ {
1807
+ reject: false,
1808
+ env: { OPENCODE_PERMISSION: JSON.stringify({ "*": "allow" }) }
1809
+ }
1810
+ );
1811
+ if (exitCode !== 0) return [];
1812
+ const texts = [];
1813
+ for (const line of stdout.split("\n")) {
1814
+ try {
1815
+ const obj = JSON.parse(line);
1816
+ if (obj.type === "text") {
1817
+ texts.push(obj.part?.text ?? obj.text ?? "");
1818
+ }
1819
+ } catch {
1820
+ }
1821
+ }
1822
+ const joined = texts.join("");
1823
+ const match = joined.match(/\[[\s\S]*\]/);
1824
+ if (!match) return [];
1825
+ try {
1826
+ return JSON.parse(match[0]);
1827
+ } catch {
1828
+ return [];
1829
+ }
1830
+ }
1831
+ };
1832
+
1833
+ // src/agents/list-models.ts
1834
+ var CLAUDE_MODEL_CATALOG = [
1835
+ { id: "fable", displayName: "Fable" },
1836
+ { id: "opus", displayName: "Opus" },
1837
+ { id: "sonnet", displayName: "Sonnet" },
1838
+ { id: "haiku", displayName: "Haiku" }
1839
+ ];
1840
+ async function listBrightsyModels() {
1841
+ try {
1842
+ const targets = await listBrightsyChatTargets();
1843
+ const accountId = targets.activeAccountId;
1844
+ const models = (targets.models ?? []).map((m) => ({
1845
+ id: encodeBrightsyTarget("model", m.id, accountId),
1846
+ displayName: m.name || m.id,
1847
+ description: m.description ?? void 0
1848
+ }));
1849
+ const agents = (targets.agents ?? []).map((a) => ({
1850
+ id: encodeBrightsyTarget("agent", a.id, accountId),
1851
+ displayName: a.name || a.id,
1852
+ description: a.description ?? "Brightsy agent target"
1853
+ }));
1854
+ return [...models, ...agents].slice(0, 80);
1855
+ } catch {
1856
+ return [];
1857
+ }
1858
+ }
1859
+ async function listModelsForAgent(agent) {
1860
+ const kinds = agent ? [agent] : ["claude", "codex", "opencode", "cursor", "brightsy"];
1861
+ const out = [];
1862
+ for (const kind of kinds) {
1863
+ if (kind === "claude") {
1864
+ out.push({
1865
+ agent: kind,
1866
+ auto: true,
1867
+ models: CLAUDE_MODEL_CATALOG,
1868
+ note: "Default Auto \u2014 only pass a model id when you have a reason."
1869
+ });
1870
+ continue;
1871
+ }
1872
+ if (kind === "codex") {
1873
+ out.push({
1874
+ agent: kind,
1875
+ auto: true,
1876
+ models: await listCodexModels(),
1877
+ note: "Default Auto \u2014 only pass a model slug when you have a reason."
1878
+ });
1879
+ continue;
1880
+ }
1881
+ if (kind === "opencode") {
1882
+ out.push({
1883
+ agent: kind,
1884
+ auto: true,
1885
+ models: await listOpencodeModels(),
1886
+ note: "Default Auto \u2014 only pass a provider/model id when you have a reason."
1887
+ });
1888
+ continue;
1889
+ }
1890
+ if (kind === "cursor") {
1891
+ out.push({
1892
+ agent: kind,
1893
+ auto: true,
1894
+ models: await listCursorModels(),
1895
+ note: 'Default Auto \u2014 only pass a model id when you have a reason (or use "default").'
1896
+ });
1897
+ continue;
1898
+ }
1899
+ if (kind === "brightsy") {
1900
+ const models = await listBrightsyModels();
1901
+ out.push({
1902
+ agent: kind,
1903
+ auto: true,
1904
+ models,
1905
+ note: models.length ? "Default Auto / Default agent \u2014 only pass a model/agent id when you have a reason." : "Brightsy not logged in or no targets \u2014 leave model unset for Default."
1906
+ });
1907
+ }
1908
+ }
1909
+ return out;
1910
+ }
1911
+
1912
+ // src/agents/session-quota.ts
1913
+ function isSessionQuotaLimit(text) {
1914
+ const lower = text.trim().toLowerCase();
1915
+ if (!lower) return false;
1916
+ if (/credit balance is too low|out of credits|insufficient.?quota|billing/.test(lower)) {
1917
+ return false;
1918
+ }
1919
+ if (/prompt is too long|context.*(too long|exceed)|conversation too long/.test(lower)) {
1920
+ return false;
1921
+ }
1922
+ return /you've hit your/.test(lower) || /hit your (session|weekly|opus) limit/.test(lower) || /usage limit/.test(lower) || /rate.?limit|too many requests|\b429\b/.test(lower) && /reset/i.test(text);
1923
+ }
1924
+ function parseSessionQuotaResetAt(text, now = /* @__PURE__ */ new Date()) {
1925
+ const absolute = text.match(
1926
+ /resets\s+(?:at\s+)?(\d{1,2}):(\d{2})\s*(am|pm)(?:\s*\(([^)]+)\))?/i
1927
+ );
1928
+ if (absolute) {
1929
+ const hour12 = Number(absolute[1]);
1930
+ const minute = Number(absolute[2]);
1931
+ const ampm = absolute[3].toLowerCase();
1932
+ const timeZone = absolute[4]?.trim() || Intl.DateTimeFormat().resolvedOptions().timeZone;
1933
+ let hour = hour12 % 12;
1934
+ if (ampm === "pm") hour += 12;
1935
+ const at = zonedWallTimeToUtc(now, hour, minute, timeZone);
1936
+ if (!at) return null;
1937
+ if (at.getTime() <= now.getTime() + 3e4) {
1938
+ const tomorrow = new Date(now.getTime() + 24 * 60 * 60 * 1e3);
1939
+ return zonedWallTimeToUtc(tomorrow, hour, minute, timeZone);
1940
+ }
1941
+ return at;
1942
+ }
1943
+ const relative = text.match(
1944
+ /resets\s+in\s+(\d+)\s*(minutes?|hours?|days?)/i
1945
+ );
1946
+ if (relative) {
1947
+ const n = Number(relative[1]);
1948
+ const unit = relative[2].toLowerCase();
1949
+ const ms = unit.startsWith("day") ? n * 24 * 60 * 60 * 1e3 : unit.startsWith("hour") ? n * 60 * 60 * 1e3 : n * 60 * 1e3;
1950
+ return new Date(now.getTime() + ms);
1951
+ }
1952
+ return null;
1953
+ }
1954
+ function zonedWallTimeToUtc(day, hour, minute, timeZone) {
1955
+ try {
1956
+ const cal = new Intl.DateTimeFormat("en-US", {
1957
+ timeZone,
1958
+ year: "numeric",
1959
+ month: "2-digit",
1960
+ day: "2-digit"
1961
+ });
1962
+ const parts = Object.fromEntries(
1963
+ cal.formatToParts(day).filter((p) => p.type !== "literal").map((p) => [p.type, p.value])
1964
+ );
1965
+ const year = Number(parts.year);
1966
+ const month = Number(parts.month);
1967
+ const date = Number(parts.day);
1968
+ if (![year, month, date].every((n) => Number.isFinite(n))) return null;
1969
+ const utcGuess = Date.UTC(year, month - 1, date, hour, minute, 0);
1970
+ const dtf = new Intl.DateTimeFormat("en-US", {
1971
+ timeZone,
1972
+ year: "numeric",
1973
+ month: "2-digit",
1974
+ day: "2-digit",
1975
+ hour: "2-digit",
1976
+ minute: "2-digit",
1977
+ second: "2-digit",
1978
+ hourCycle: "h23"
1979
+ });
1980
+ const asParts = Object.fromEntries(
1981
+ dtf.formatToParts(new Date(utcGuess)).filter((p) => p.type !== "literal").map((p) => [p.type, p.value])
1982
+ );
1983
+ const asUtc = Date.UTC(
1984
+ Number(asParts.year),
1985
+ Number(asParts.month) - 1,
1986
+ Number(asParts.day),
1987
+ Number(asParts.hour),
1988
+ Number(asParts.minute),
1989
+ Number(asParts.second || "0")
1990
+ );
1991
+ const offset = asUtc - utcGuess;
1992
+ return new Date(utcGuess - offset);
1993
+ } catch {
1994
+ return null;
1995
+ }
1996
+ }
1997
+ var FALLBACK_ORDER = [
1998
+ "cursor",
1999
+ "codex",
2000
+ "opencode",
2001
+ "claude"
2002
+ ];
2003
+ function resolveQuotaFallbackAgent(current, preferred) {
2004
+ const ordered = [
2005
+ ...preferred && preferred !== "brightsy" ? [preferred] : [],
2006
+ ...FALLBACK_ORDER.filter((a) => a !== preferred)
2007
+ ];
2008
+ return ordered.find((a) => a !== current) ?? (current === "cursor" ? "codex" : "cursor");
2009
+ }
2010
+
2011
+ // src/agents/install.ts
2012
+ var SETUP = {
2013
+ claude: {
2014
+ agent: "claude",
2015
+ kind: "cli",
2016
+ summary: "Install the Claude Code CLI, then complete login in a terminal.",
2017
+ docsUrl: "https://code.claude.com/docs/en/install",
2018
+ installCommand: "npm install -g @anthropic-ai/claude-code",
2019
+ loginCommand: "claude auth login",
2020
+ npmPackage: "@anthropic-ai/claude-code"
2021
+ },
2022
+ codex: {
2023
+ agent: "codex",
2024
+ kind: "cli",
2025
+ summary: "Install the Codex CLI, then run login in a terminal.",
2026
+ docsUrl: "https://github.com/openai/codex",
2027
+ installCommand: "npm install -g @openai/codex",
2028
+ loginCommand: "codex login",
2029
+ npmPackage: "@openai/codex"
2030
+ },
2031
+ opencode: {
2032
+ agent: "opencode",
2033
+ kind: "cli",
2034
+ summary: "Install OpenCode (curl installer or npm), then authenticate providers.",
2035
+ docsUrl: "https://opencode.ai/docs",
2036
+ // Prefer the official installer; npm package name is opencode-ai.
2037
+ installCommand: "curl -fsSL https://opencode.ai/install | bash",
2038
+ loginCommand: "opencode auth login",
2039
+ npmPackage: "opencode-ai@latest"
2040
+ },
2041
+ cursor: {
2042
+ agent: "cursor",
2043
+ kind: "bundled-sdk",
2044
+ summary: "No CLI install \u2014 Sideboard ships the Cursor SDK. Add a CURSOR_API_KEY from the Cursor dashboard.",
2045
+ docsUrl: "https://cursor.com/dashboard/integrations",
2046
+ installCommand: null,
2047
+ loginCommand: null
2048
+ },
2049
+ brightsy: {
2050
+ agent: "brightsy",
2051
+ kind: "cli",
2052
+ summary: "Install the Brightsy CLI, then run `brightsy login`.",
2053
+ docsUrl: "https://www.npmjs.com/package/@brightsy/cli",
2054
+ installCommand: "npm install -g @brightsy/cli",
2055
+ loginCommand: "brightsy login",
2056
+ npmPackage: "@brightsy/cli"
2057
+ }
2058
+ };
2059
+ function getAgentSetupInfo(agent) {
2060
+ return SETUP[agent];
2061
+ }
2062
+ function listAgentSetupInfo() {
2063
+ return Object.values(SETUP);
2064
+ }
2065
+ async function openInSystemTerminal(command) {
2066
+ ensureAgentPath();
2067
+ const trimmed = command.trim();
2068
+ if (!trimmed) throw new Error("Command is empty");
2069
+ if (process.platform === "darwin") {
2070
+ const escaped = trimmed.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
2071
+ const script = `tell application "Terminal" to do script "${escaped}"`;
2072
+ const result = await run("osascript", ["-e", script], { reject: false });
2073
+ if (result.exitCode !== 0) {
2074
+ throw new Error(result.stderr.trim() || result.stdout.trim() || "Failed to open Terminal");
2075
+ }
2076
+ await run("osascript", ["-e", 'tell application "Terminal" to activate'], {
2077
+ reject: false
2078
+ });
2079
+ return;
2080
+ }
2081
+ if (process.platform === "win32") {
2082
+ const result = await run(
2083
+ "cmd.exe",
2084
+ ["/c", "start", "cmd.exe", "/k", trimmed],
2085
+ { reject: false }
2086
+ );
2087
+ if (result.exitCode !== 0) {
2088
+ throw new Error(result.stderr.trim() || result.stdout.trim() || "Failed to open cmd");
2089
+ }
2090
+ return;
2091
+ }
2092
+ const candidates = [
2093
+ { file: "gnome-terminal", args: ["--", "bash", "-lc", `${trimmed}; exec bash`] },
2094
+ { file: "x-terminal-emulator", args: ["-e", "bash", "-lc", `${trimmed}; exec bash`] },
2095
+ { file: "konsole", args: ["-e", "bash", "-lc", `${trimmed}; exec bash`] },
2096
+ { file: "xterm", args: ["-e", "bash", "-lc", `${trimmed}; exec bash`] }
2097
+ ];
2098
+ for (const c of candidates) {
2099
+ const which = await run("which", [c.file], { reject: false });
2100
+ if (which.exitCode !== 0 || !which.stdout.trim()) continue;
2101
+ const result = await run(c.file, c.args, { reject: false });
2102
+ if (result.exitCode === 0) return;
2103
+ }
2104
+ throw new Error(
2105
+ `No terminal found to run: ${trimmed}. Install gnome-terminal (or similar), or run the command manually.`
2106
+ );
2107
+ }
2108
+ async function installAgent(agent) {
2109
+ const info = getAgentSetupInfo(agent);
2110
+ if (info.kind === "bundled-sdk" || info.kind === "api-key") {
2111
+ return {
2112
+ ok: true,
2113
+ message: info.summary
2114
+ };
2115
+ }
2116
+ if (!info.installCommand) {
2117
+ return { ok: false, message: `No install command for ${agent}` };
2118
+ }
2119
+ if (/curl\s| \|\s*bash/.test(info.installCommand) || !info.npmPackage) {
2120
+ await openInSystemTerminal(info.installCommand);
2121
+ return {
2122
+ ok: true,
2123
+ openedTerminal: true,
2124
+ command: info.installCommand,
2125
+ message: `Opened Terminal to run: ${info.installCommand}`
2126
+ };
2127
+ }
2128
+ ensureAgentPath();
2129
+ const result = await run("npm", ["install", "-g", info.npmPackage], { reject: false });
2130
+ const ok = result.exitCode === 0;
2131
+ if (!ok) {
2132
+ try {
2133
+ await openInSystemTerminal(info.installCommand);
2134
+ return {
2135
+ ok: false,
2136
+ openedTerminal: true,
2137
+ command: info.installCommand,
2138
+ exitCode: result.exitCode,
2139
+ stdout: result.stdout,
2140
+ stderr: result.stderr,
2141
+ message: `npm install failed (exit ${result.exitCode}). Opened Terminal with: ${info.installCommand}`
2142
+ };
2143
+ } catch {
2144
+ return {
2145
+ ok: false,
2146
+ command: info.installCommand,
2147
+ exitCode: result.exitCode,
2148
+ stdout: result.stdout,
2149
+ stderr: result.stderr,
2150
+ message: result.stderr.trim() || result.stdout.trim() || `npm install failed (exit ${result.exitCode})`
2151
+ };
2152
+ }
2153
+ }
2154
+ return {
2155
+ ok: true,
2156
+ command: info.installCommand,
2157
+ exitCode: 0,
2158
+ stdout: result.stdout,
2159
+ stderr: result.stderr,
2160
+ message: `Installed ${info.npmPackage}`
2161
+ };
2162
+ }
2163
+ async function loginAgent(agent) {
2164
+ const info = getAgentSetupInfo(agent);
2165
+ if (!info.loginCommand) {
2166
+ return {
2167
+ ok: true,
2168
+ message: info.kind === "bundled-sdk" || info.kind === "api-key" ? info.summary : `No login command for ${agent}`
2169
+ };
2170
+ }
2171
+ await openInSystemTerminal(info.loginCommand);
2172
+ return {
2173
+ ok: true,
2174
+ openedTerminal: true,
2175
+ command: info.loginCommand,
2176
+ message: `Opened Terminal to run: ${info.loginCommand}`
2177
+ };
2178
+ }
2179
+
2180
+ // src/agents/index.ts
2181
+ var adapters = {
2182
+ claude: claudeAdapter,
2183
+ codex: codexAdapter,
2184
+ opencode: opencodeAdapter,
2185
+ brightsy: brightsyAdapter,
2186
+ cursor: cursorAdapter
2187
+ };
2188
+ function getAdapter(kind) {
2189
+ return adapters[kind];
2190
+ }
2191
+ function allAdapters() {
2192
+ return Object.values(adapters);
2193
+ }
2194
+
2195
+ export {
2196
+ pushTurnStderr,
2197
+ summarizeTurnStderr,
2198
+ looksLikeAgentFailureMessage,
2199
+ fallbackTurnFailDetail,
2200
+ humanizeAgentFailDetail,
2201
+ formatTurnExitError,
2202
+ encodeBrightsyTarget,
2203
+ decodeBrightsyTarget,
2204
+ parseBrightsyCliLine,
2205
+ listBrightsyChatTargets,
2206
+ brightsyAdapter,
2207
+ PLAN_MODE_INSTRUCTION,
2208
+ permissionMode,
2209
+ claudeAdapter,
2210
+ listCodexModels,
2211
+ codexAdapter,
2212
+ cursorSdkMessageToEvents,
2213
+ parseCursorRunnerLine,
2214
+ isCursorAutoModel,
2215
+ resolveCursorModelId,
2216
+ listCursorModels,
2217
+ cursorAdapter,
2218
+ listOpencodeModels,
2219
+ opencodeAdapter,
2220
+ CLAUDE_MODEL_CATALOG,
2221
+ listModelsForAgent,
2222
+ isSessionQuotaLimit,
2223
+ parseSessionQuotaResetAt,
2224
+ resolveQuotaFallbackAgent,
2225
+ getAgentSetupInfo,
2226
+ listAgentSetupInfo,
2227
+ openInSystemTerminal,
2228
+ installAgent,
2229
+ loginAgent,
2230
+ getAdapter,
2231
+ allAdapters
2232
+ };