@sideboard-ai/core 0.1.43 → 0.1.45
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.
- package/dist/agents/cursor-runner.js +1 -1
- package/dist/{agents-T3EA7GZV.js → agents-JSHCAZUZ.js} +15 -5
- package/dist/{app-settings-ZKVZHJPQ.js → app-settings-LYGVDGZY.js} +5 -1
- package/dist/{chunk-Z2BQMXVM.js → chunk-6NAPN2N5.js} +6 -2
- package/dist/{chunk-VC7NORFX.js → chunk-ANZ566Z5.js} +184 -3
- package/dist/{chunk-7PCTK4WO.js → chunk-FV6FN6V5.js} +1 -1
- package/dist/{chunk-AGU52GTV.js → chunk-I6QGZOOS.js} +488 -32
- package/dist/{chunk-PU27NUO4.js → chunk-J5JTEJ5O.js} +7 -2
- package/dist/{chunk-ENSD62HW.js → chunk-O6W3P7V3.js} +3 -1
- package/dist/{chunk-FUCEOJFO.js → chunk-U3EQKJHA.js} +2 -2
- package/dist/{chunk-FSIK442J.js → chunk-WYY3J7GR.js} +21 -0
- package/dist/{chunk-X6P2QVRJ.js → chunk-ZNSM2DDD.js} +3 -3
- package/dist/{coordinator-prompt-BHXBQRPM.js → coordinator-prompt-2XFYG3C5.js} +3 -3
- package/dist/{global-workspace-SNN45OCN.js → global-workspace-YFOQUGWD.js} +4 -4
- package/dist/index.cjs +735 -25
- package/dist/index.d.cts +107 -2
- package/dist/index.d.ts +107 -2
- package/dist/index.js +35 -9
- package/dist/mcp/run-stdio.cjs +805 -123
- package/dist/mcp/run-stdio.js +9 -9
- package/dist/{thread-store-EHROA3VZ.js → thread-store-OV2X6PYO.js} +1 -1
- package/dist/{workspaces-AUYIJ64Z.js → workspaces-TIKLNDW3.js} +5 -5
- package/dist/{worktree-N4PRV4V3.js → worktree-TEDAJ57S.js} +2 -2
- package/package.json +1 -1
package/dist/mcp/run-stdio.cjs
CHANGED
|
@@ -133,11 +133,15 @@ function humanizeAgentFailDetail(detail) {
|
|
|
133
133
|
}
|
|
134
134
|
function formatTurnExitError(exitCode, stderrSummary) {
|
|
135
135
|
const code = exitCode ?? 1;
|
|
136
|
-
const
|
|
136
|
+
const raw = stderrSummary.trim();
|
|
137
|
+
if (/^exit\s*\d+$/i.test(raw)) {
|
|
138
|
+
return `exit ${code}: agent exited without details (credits, auth, rate limits, or a CLI error)`;
|
|
139
|
+
}
|
|
140
|
+
const detail = humanizeAgentFailDetail(raw);
|
|
137
141
|
if (!detail) {
|
|
138
142
|
return `exit ${code}: agent exited without details (credits, auth, rate limits, or a CLI error)`;
|
|
139
143
|
}
|
|
140
|
-
if (looksLikeAgentFailureMessage(
|
|
144
|
+
if (looksLikeAgentFailureMessage(raw)) return detail;
|
|
141
145
|
return `exit ${code}: ${detail}`;
|
|
142
146
|
}
|
|
143
147
|
var NODE_VERSION_FOOTER;
|
|
@@ -477,7 +481,9 @@ function normalizeThread(raw) {
|
|
|
477
481
|
attachments: Array.isArray(raw.attachments) ? raw.attachments : [],
|
|
478
482
|
prTitle: raw.prTitle ?? null,
|
|
479
483
|
userSetTitle: Boolean(raw.userSetTitle),
|
|
480
|
-
activeRuns: Array.isArray(raw.activeRuns) ? raw.activeRuns : []
|
|
484
|
+
activeRuns: Array.isArray(raw.activeRuns) ? raw.activeRuns : [],
|
|
485
|
+
quotaResumeAt: raw.quotaResumeAt ?? null,
|
|
486
|
+
quotaContinuedFromId: raw.quotaContinuedFromId ?? null
|
|
481
487
|
};
|
|
482
488
|
}
|
|
483
489
|
function createEmptyThread(partial) {
|
|
@@ -2631,6 +2637,8 @@ __export(app_settings_exports, {
|
|
|
2631
2637
|
isLinearConnected: () => isLinearConnected,
|
|
2632
2638
|
loadAppSettings: () => loadAppSettings,
|
|
2633
2639
|
maxConcurrentAgents: () => maxConcurrentAgents,
|
|
2640
|
+
orchestrationQuotaFallbackAgent: () => orchestrationQuotaFallbackAgent,
|
|
2641
|
+
orchestrationQuotaOnLimit: () => orchestrationQuotaOnLimit,
|
|
2634
2642
|
resolveClaudeExecutable: () => resolveClaudeExecutable,
|
|
2635
2643
|
resolveEffectiveIssueSource: () => resolveEffectiveIssueSource,
|
|
2636
2644
|
resolveThreadDefaults: () => resolveThreadDefaults,
|
|
@@ -2742,6 +2750,12 @@ function normalizeAdvanced(raw) {
|
|
|
2742
2750
|
if (typeof source.autoCleanupOrphans === "boolean") {
|
|
2743
2751
|
out.autoCleanupOrphans = source.autoCleanupOrphans;
|
|
2744
2752
|
}
|
|
2753
|
+
if (source.orchestrationQuotaOnLimit === "switch_agent" || source.orchestrationQuotaOnLimit === "wait_reset") {
|
|
2754
|
+
out.orchestrationQuotaOnLimit = source.orchestrationQuotaOnLimit;
|
|
2755
|
+
}
|
|
2756
|
+
if (typeof source.orchestrationQuotaFallbackAgent === "string" && DEFAULT_AGENTS.has(source.orchestrationQuotaFallbackAgent)) {
|
|
2757
|
+
out.orchestrationQuotaFallbackAgent = source.orchestrationQuotaFallbackAgent;
|
|
2758
|
+
}
|
|
2745
2759
|
return out;
|
|
2746
2760
|
}
|
|
2747
2761
|
function normalizeSettings(raw) {
|
|
@@ -2976,6 +2990,12 @@ function updateAdvancedSettings(patch) {
|
|
|
2976
2990
|
if (typeof patch.autoCleanupOrphans === "boolean") {
|
|
2977
2991
|
advanced.autoCleanupOrphans = patch.autoCleanupOrphans;
|
|
2978
2992
|
}
|
|
2993
|
+
if (patch.orchestrationQuotaOnLimit === "switch_agent" || patch.orchestrationQuotaOnLimit === "wait_reset") {
|
|
2994
|
+
advanced.orchestrationQuotaOnLimit = patch.orchestrationQuotaOnLimit;
|
|
2995
|
+
}
|
|
2996
|
+
if (typeof patch.orchestrationQuotaFallbackAgent === "string" && DEFAULT_AGENTS.has(patch.orchestrationQuotaFallbackAgent)) {
|
|
2997
|
+
advanced.orchestrationQuotaFallbackAgent = patch.orchestrationQuotaFallbackAgent;
|
|
2998
|
+
}
|
|
2979
2999
|
return saveAppSettings({ ...current, advanced });
|
|
2980
3000
|
}
|
|
2981
3001
|
function autoRenameBranchEnabled(settings = loadAppSettings()) {
|
|
@@ -2996,6 +3016,13 @@ function deleteBranchOnPurgeEnabled(settings = loadAppSettings()) {
|
|
|
2996
3016
|
function autoCleanupOrphansEnabled(settings = loadAppSettings()) {
|
|
2997
3017
|
return Boolean(settings.advanced.autoCleanupOrphans);
|
|
2998
3018
|
}
|
|
3019
|
+
function orchestrationQuotaOnLimit(settings = loadAppSettings()) {
|
|
3020
|
+
return settings.advanced.orchestrationQuotaOnLimit ?? "switch_agent";
|
|
3021
|
+
}
|
|
3022
|
+
function orchestrationQuotaFallbackAgent(settings = loadAppSettings()) {
|
|
3023
|
+
const preferred = settings.advanced.orchestrationQuotaFallbackAgent;
|
|
3024
|
+
return preferred && DEFAULT_AGENTS.has(preferred) ? preferred : "cursor";
|
|
3025
|
+
}
|
|
2999
3026
|
function maxConcurrentAgents(settings = loadAppSettings()) {
|
|
3000
3027
|
const n = settings.advanced.maxConcurrent;
|
|
3001
3028
|
if (typeof n === "number" && Number.isFinite(n)) {
|
|
@@ -3223,11 +3250,14 @@ var init_coordinator_prompt = __esm({
|
|
|
3223
3250
|
"Discover:",
|
|
3224
3251
|
"- list_workspaces \u2014 registered repos (path + github slug when known)",
|
|
3225
3252
|
"- list_branches / list_prs / list_issues \u2014 pass repoPath from list_workspaces (issues: Linear API or GitHub Issues)",
|
|
3253
|
+
"- list_models \u2014 only when you need a specific model (rare); otherwise leave model unset = Auto",
|
|
3226
3254
|
"- list_threads / get_thread \u2014 fleet status (what is going on)",
|
|
3227
3255
|
"Workspaces:",
|
|
3228
3256
|
"- add_workspace / remove_workspace \u2014 register or unregister a git repo",
|
|
3229
3257
|
"Worktree threads (chats):",
|
|
3230
3258
|
"- create_thread \u2014 create a worktree + chat from branch | pr | ticket; pass repoPath + parentThreadId",
|
|
3259
|
+
"- fork_worktree \u2014 fork a worktree chat into a NEW git worktree + chat (transcript attached); optional agent; leave model unset (Auto) unless you have a reason. Not for orchestration chats.",
|
|
3260
|
+
"- fork_chat \u2014 fork a worktree chat (same worktree tab) OR a Global orchestration chat (new orchestration tab); optional agent; leave model unset (Auto) unless you have a reason. Remote coordinators: use this to continue another orchestration chat on a different agent after session limits.",
|
|
3231
3261
|
"- send_to_thread \u2014 queue a prompt (start/continue a chat turn); pass force_stop: true to interrupt mid-turn / clear stale queued prompts before replacing with a new request",
|
|
3232
3262
|
"- wait_for_turn / get_turn_result \u2014 wait for and read the agent reply",
|
|
3233
3263
|
"- stop_thread \u2014 force-stop: kill in-flight turn AND clear queued prompts (do not leave stale queue after an interrupt)",
|
|
@@ -3235,8 +3265,9 @@ var init_coordinator_prompt = __esm({
|
|
|
3235
3265
|
"Setup / run:",
|
|
3236
3266
|
"- run_setup \u2014 re-run worktree setup",
|
|
3237
3267
|
"- list_run_scripts / run_dev_script / stop_dev_script \u2014 start/stop named run scripts",
|
|
3238
|
-
"Inspect / PRs:",
|
|
3268
|
+
"Inspect / review / PRs:",
|
|
3239
3269
|
"- get_diff \u2014 compact diff summary",
|
|
3270
|
+
"- request_review \u2014 open a Review chat tab on a worktree thread (merge-readiness recommendation); then wait_for_turn / get_turn_result on the returned id",
|
|
3240
3271
|
"- Ask the worktree agent via send_to_thread to open a draft PR with `gh pr create --draft -R <origin-owner/name>` (workspace `github:` slug / that worktree's origin \u2014 never upstream). Do not open PRs from the orchestrator yourself.",
|
|
3241
3272
|
"Human-only (do not attempt): merge, ready-for-review land, purge_thread.",
|
|
3242
3273
|
"Thread links in replies: when mentioning a chat/thread for the user, include a markdown link `[Title](sideboard://thread/<id>)` using the full id (or the link field from create_thread / list_threads). Sideboard renders these as clickable opens.",
|
|
@@ -5321,6 +5352,199 @@ var init_opencode = __esm({
|
|
|
5321
5352
|
}
|
|
5322
5353
|
});
|
|
5323
5354
|
|
|
5355
|
+
// src/agents/list-models.ts
|
|
5356
|
+
async function listBrightsyModels() {
|
|
5357
|
+
try {
|
|
5358
|
+
const targets = await listBrightsyChatTargets();
|
|
5359
|
+
const accountId = targets.activeAccountId;
|
|
5360
|
+
const models = (targets.models ?? []).map((m) => ({
|
|
5361
|
+
id: encodeBrightsyTarget("model", m.id, accountId),
|
|
5362
|
+
displayName: m.name || m.id,
|
|
5363
|
+
description: m.description ?? void 0
|
|
5364
|
+
}));
|
|
5365
|
+
const agents = (targets.agents ?? []).map((a) => ({
|
|
5366
|
+
id: encodeBrightsyTarget("agent", a.id, accountId),
|
|
5367
|
+
displayName: a.name || a.id,
|
|
5368
|
+
description: a.description ?? "Brightsy agent target"
|
|
5369
|
+
}));
|
|
5370
|
+
return [...models, ...agents].slice(0, 80);
|
|
5371
|
+
} catch {
|
|
5372
|
+
return [];
|
|
5373
|
+
}
|
|
5374
|
+
}
|
|
5375
|
+
async function listModelsForAgent(agent) {
|
|
5376
|
+
const kinds = agent ? [agent] : ["claude", "codex", "opencode", "cursor", "brightsy"];
|
|
5377
|
+
const out = [];
|
|
5378
|
+
for (const kind of kinds) {
|
|
5379
|
+
if (kind === "claude") {
|
|
5380
|
+
out.push({
|
|
5381
|
+
agent: kind,
|
|
5382
|
+
auto: true,
|
|
5383
|
+
models: CLAUDE_MODEL_CATALOG,
|
|
5384
|
+
note: "Default Auto \u2014 only pass a model id when you have a reason."
|
|
5385
|
+
});
|
|
5386
|
+
continue;
|
|
5387
|
+
}
|
|
5388
|
+
if (kind === "codex") {
|
|
5389
|
+
out.push({
|
|
5390
|
+
agent: kind,
|
|
5391
|
+
auto: true,
|
|
5392
|
+
models: await listCodexModels(),
|
|
5393
|
+
note: "Default Auto \u2014 only pass a model slug when you have a reason."
|
|
5394
|
+
});
|
|
5395
|
+
continue;
|
|
5396
|
+
}
|
|
5397
|
+
if (kind === "opencode") {
|
|
5398
|
+
out.push({
|
|
5399
|
+
agent: kind,
|
|
5400
|
+
auto: true,
|
|
5401
|
+
models: await listOpencodeModels(),
|
|
5402
|
+
note: "Default Auto \u2014 only pass a provider/model id when you have a reason."
|
|
5403
|
+
});
|
|
5404
|
+
continue;
|
|
5405
|
+
}
|
|
5406
|
+
if (kind === "cursor") {
|
|
5407
|
+
out.push({
|
|
5408
|
+
agent: kind,
|
|
5409
|
+
auto: true,
|
|
5410
|
+
models: await listCursorModels(),
|
|
5411
|
+
note: 'Default Auto \u2014 only pass a model id when you have a reason (or use "default").'
|
|
5412
|
+
});
|
|
5413
|
+
continue;
|
|
5414
|
+
}
|
|
5415
|
+
if (kind === "brightsy") {
|
|
5416
|
+
const models = await listBrightsyModels();
|
|
5417
|
+
out.push({
|
|
5418
|
+
agent: kind,
|
|
5419
|
+
auto: true,
|
|
5420
|
+
models,
|
|
5421
|
+
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."
|
|
5422
|
+
});
|
|
5423
|
+
}
|
|
5424
|
+
}
|
|
5425
|
+
return out;
|
|
5426
|
+
}
|
|
5427
|
+
var CLAUDE_MODEL_CATALOG;
|
|
5428
|
+
var init_list_models = __esm({
|
|
5429
|
+
"src/agents/list-models.ts"() {
|
|
5430
|
+
"use strict";
|
|
5431
|
+
init_brightsy();
|
|
5432
|
+
init_brightsy_targets();
|
|
5433
|
+
init_codex();
|
|
5434
|
+
init_cursor();
|
|
5435
|
+
init_opencode();
|
|
5436
|
+
CLAUDE_MODEL_CATALOG = [
|
|
5437
|
+
{ id: "fable", displayName: "Fable" },
|
|
5438
|
+
{ id: "opus", displayName: "Opus" },
|
|
5439
|
+
{ id: "sonnet", displayName: "Sonnet" },
|
|
5440
|
+
{ id: "haiku", displayName: "Haiku" }
|
|
5441
|
+
];
|
|
5442
|
+
}
|
|
5443
|
+
});
|
|
5444
|
+
|
|
5445
|
+
// src/agents/session-quota.ts
|
|
5446
|
+
function isSessionQuotaLimit(text) {
|
|
5447
|
+
const lower = text.trim().toLowerCase();
|
|
5448
|
+
if (!lower) return false;
|
|
5449
|
+
if (/credit balance is too low|out of credits|insufficient.?quota|billing/.test(lower)) {
|
|
5450
|
+
return false;
|
|
5451
|
+
}
|
|
5452
|
+
if (/prompt is too long|context.*(too long|exceed)|conversation too long/.test(lower)) {
|
|
5453
|
+
return false;
|
|
5454
|
+
}
|
|
5455
|
+
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);
|
|
5456
|
+
}
|
|
5457
|
+
function parseSessionQuotaResetAt(text, now = /* @__PURE__ */ new Date()) {
|
|
5458
|
+
const absolute = text.match(
|
|
5459
|
+
/resets\s+(?:at\s+)?(\d{1,2}):(\d{2})\s*(am|pm)(?:\s*\(([^)]+)\))?/i
|
|
5460
|
+
);
|
|
5461
|
+
if (absolute) {
|
|
5462
|
+
const hour12 = Number(absolute[1]);
|
|
5463
|
+
const minute = Number(absolute[2]);
|
|
5464
|
+
const ampm = absolute[3].toLowerCase();
|
|
5465
|
+
const timeZone = absolute[4]?.trim() || Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
5466
|
+
let hour = hour12 % 12;
|
|
5467
|
+
if (ampm === "pm") hour += 12;
|
|
5468
|
+
const at = zonedWallTimeToUtc(now, hour, minute, timeZone);
|
|
5469
|
+
if (!at) return null;
|
|
5470
|
+
if (at.getTime() <= now.getTime() + 3e4) {
|
|
5471
|
+
const tomorrow = new Date(now.getTime() + 24 * 60 * 60 * 1e3);
|
|
5472
|
+
return zonedWallTimeToUtc(tomorrow, hour, minute, timeZone);
|
|
5473
|
+
}
|
|
5474
|
+
return at;
|
|
5475
|
+
}
|
|
5476
|
+
const relative = text.match(
|
|
5477
|
+
/resets\s+in\s+(\d+)\s*(minutes?|hours?|days?)/i
|
|
5478
|
+
);
|
|
5479
|
+
if (relative) {
|
|
5480
|
+
const n = Number(relative[1]);
|
|
5481
|
+
const unit = relative[2].toLowerCase();
|
|
5482
|
+
const ms = unit.startsWith("day") ? n * 24 * 60 * 60 * 1e3 : unit.startsWith("hour") ? n * 60 * 60 * 1e3 : n * 60 * 1e3;
|
|
5483
|
+
return new Date(now.getTime() + ms);
|
|
5484
|
+
}
|
|
5485
|
+
return null;
|
|
5486
|
+
}
|
|
5487
|
+
function zonedWallTimeToUtc(day, hour, minute, timeZone) {
|
|
5488
|
+
try {
|
|
5489
|
+
const cal = new Intl.DateTimeFormat("en-US", {
|
|
5490
|
+
timeZone,
|
|
5491
|
+
year: "numeric",
|
|
5492
|
+
month: "2-digit",
|
|
5493
|
+
day: "2-digit"
|
|
5494
|
+
});
|
|
5495
|
+
const parts = Object.fromEntries(
|
|
5496
|
+
cal.formatToParts(day).filter((p) => p.type !== "literal").map((p) => [p.type, p.value])
|
|
5497
|
+
);
|
|
5498
|
+
const year = Number(parts.year);
|
|
5499
|
+
const month = Number(parts.month);
|
|
5500
|
+
const date = Number(parts.day);
|
|
5501
|
+
if (![year, month, date].every((n) => Number.isFinite(n))) return null;
|
|
5502
|
+
const utcGuess = Date.UTC(year, month - 1, date, hour, minute, 0);
|
|
5503
|
+
const dtf = new Intl.DateTimeFormat("en-US", {
|
|
5504
|
+
timeZone,
|
|
5505
|
+
year: "numeric",
|
|
5506
|
+
month: "2-digit",
|
|
5507
|
+
day: "2-digit",
|
|
5508
|
+
hour: "2-digit",
|
|
5509
|
+
minute: "2-digit",
|
|
5510
|
+
second: "2-digit",
|
|
5511
|
+
hourCycle: "h23"
|
|
5512
|
+
});
|
|
5513
|
+
const asParts = Object.fromEntries(
|
|
5514
|
+
dtf.formatToParts(new Date(utcGuess)).filter((p) => p.type !== "literal").map((p) => [p.type, p.value])
|
|
5515
|
+
);
|
|
5516
|
+
const asUtc = Date.UTC(
|
|
5517
|
+
Number(asParts.year),
|
|
5518
|
+
Number(asParts.month) - 1,
|
|
5519
|
+
Number(asParts.day),
|
|
5520
|
+
Number(asParts.hour),
|
|
5521
|
+
Number(asParts.minute),
|
|
5522
|
+
Number(asParts.second || "0")
|
|
5523
|
+
);
|
|
5524
|
+
const offset = asUtc - utcGuess;
|
|
5525
|
+
return new Date(utcGuess - offset);
|
|
5526
|
+
} catch {
|
|
5527
|
+
return null;
|
|
5528
|
+
}
|
|
5529
|
+
}
|
|
5530
|
+
function resolveQuotaFallbackAgent(current, preferred) {
|
|
5531
|
+
const ordered = preferred ? [preferred, ...FALLBACK_ORDER.filter((a) => a !== preferred)] : FALLBACK_ORDER;
|
|
5532
|
+
return ordered.find((a) => a !== current) ?? (current === "cursor" ? "codex" : "cursor");
|
|
5533
|
+
}
|
|
5534
|
+
var FALLBACK_ORDER;
|
|
5535
|
+
var init_session_quota = __esm({
|
|
5536
|
+
"src/agents/session-quota.ts"() {
|
|
5537
|
+
"use strict";
|
|
5538
|
+
FALLBACK_ORDER = [
|
|
5539
|
+
"cursor",
|
|
5540
|
+
"codex",
|
|
5541
|
+
"opencode",
|
|
5542
|
+
"brightsy",
|
|
5543
|
+
"claude"
|
|
5544
|
+
];
|
|
5545
|
+
}
|
|
5546
|
+
});
|
|
5547
|
+
|
|
5324
5548
|
// src/agents/install.ts
|
|
5325
5549
|
function getAgentSetupInfo(agent) {
|
|
5326
5550
|
return SETUP[agent];
|
|
@@ -5501,6 +5725,7 @@ var init_install = __esm({
|
|
|
5501
5725
|
// src/agents/index.ts
|
|
5502
5726
|
var agents_exports = {};
|
|
5503
5727
|
__export(agents_exports, {
|
|
5728
|
+
CLAUDE_MODEL_CATALOG: () => CLAUDE_MODEL_CATALOG,
|
|
5504
5729
|
PLAN_MODE_INSTRUCTION: () => PLAN_MODE_INSTRUCTION,
|
|
5505
5730
|
allAdapters: () => allAdapters,
|
|
5506
5731
|
brightsyAdapter: () => brightsyAdapter,
|
|
@@ -5515,17 +5740,21 @@ __export(agents_exports, {
|
|
|
5515
5740
|
getAgentSetupInfo: () => getAgentSetupInfo,
|
|
5516
5741
|
installAgent: () => installAgent,
|
|
5517
5742
|
isCursorAutoModel: () => isCursorAutoModel,
|
|
5743
|
+
isSessionQuotaLimit: () => isSessionQuotaLimit,
|
|
5518
5744
|
listAgentSetupInfo: () => listAgentSetupInfo,
|
|
5519
5745
|
listBrightsyChatTargets: () => listBrightsyChatTargets,
|
|
5520
5746
|
listCodexModels: () => listCodexModels,
|
|
5521
5747
|
listCursorModels: () => listCursorModels,
|
|
5748
|
+
listModelsForAgent: () => listModelsForAgent,
|
|
5522
5749
|
listOpencodeModels: () => listOpencodeModels,
|
|
5523
5750
|
loginAgent: () => loginAgent,
|
|
5524
5751
|
openInSystemTerminal: () => openInSystemTerminal,
|
|
5525
5752
|
opencodeAdapter: () => opencodeAdapter,
|
|
5526
5753
|
parseCursorRunnerLine: () => parseCursorRunnerLine,
|
|
5754
|
+
parseSessionQuotaResetAt: () => parseSessionQuotaResetAt,
|
|
5527
5755
|
permissionMode: () => permissionMode,
|
|
5528
|
-
resolveCursorModelId: () => resolveCursorModelId
|
|
5756
|
+
resolveCursorModelId: () => resolveCursorModelId,
|
|
5757
|
+
resolveQuotaFallbackAgent: () => resolveQuotaFallbackAgent
|
|
5529
5758
|
});
|
|
5530
5759
|
function getAdapter(kind) {
|
|
5531
5760
|
return adapters[kind];
|
|
@@ -5551,6 +5780,8 @@ var init_agents = __esm({
|
|
|
5551
5780
|
init_cursor_events();
|
|
5552
5781
|
init_cursor();
|
|
5553
5782
|
init_opencode();
|
|
5783
|
+
init_list_models();
|
|
5784
|
+
init_session_quota();
|
|
5554
5785
|
init_path();
|
|
5555
5786
|
init_install();
|
|
5556
5787
|
adapters = {
|
|
@@ -5690,10 +5921,10 @@ __export(cursor_recover_exports, {
|
|
|
5690
5921
|
function recoverFinishedCursorRun(opts) {
|
|
5691
5922
|
const agentId = opts.agentId.trim();
|
|
5692
5923
|
if (!agentId) return null;
|
|
5693
|
-
const runsPath = (0,
|
|
5694
|
-
if (!(0,
|
|
5924
|
+
const runsPath = (0, import_node_path24.join)(appDataDir(), "cursor-sdk-store", "runs.ndjson");
|
|
5925
|
+
if (!(0, import_node_fs25.existsSync)(runsPath)) return null;
|
|
5695
5926
|
try {
|
|
5696
|
-
const lines = (0,
|
|
5927
|
+
const lines = (0, import_node_fs25.readFileSync)(runsPath, "utf8").split("\n");
|
|
5697
5928
|
let best = null;
|
|
5698
5929
|
for (const line of lines) {
|
|
5699
5930
|
const trimmed = line.trim();
|
|
@@ -5719,12 +5950,12 @@ function recoverFinishedCursorRun(opts) {
|
|
|
5719
5950
|
return null;
|
|
5720
5951
|
}
|
|
5721
5952
|
}
|
|
5722
|
-
var
|
|
5953
|
+
var import_node_fs25, import_node_path24;
|
|
5723
5954
|
var init_cursor_recover = __esm({
|
|
5724
5955
|
"src/agents/cursor-recover.ts"() {
|
|
5725
5956
|
"use strict";
|
|
5726
|
-
|
|
5727
|
-
|
|
5957
|
+
import_node_fs25 = require("fs");
|
|
5958
|
+
import_node_path24 = require("path");
|
|
5728
5959
|
init_paths();
|
|
5729
5960
|
}
|
|
5730
5961
|
});
|
|
@@ -5733,11 +5964,11 @@ var init_cursor_recover = __esm({
|
|
|
5733
5964
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
5734
5965
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
5735
5966
|
var import_zod = require("zod");
|
|
5736
|
-
var
|
|
5967
|
+
var import_node_path25 = require("path");
|
|
5737
5968
|
|
|
5738
5969
|
// src/orchestrator/orchestrator.ts
|
|
5739
5970
|
var import_node_events = require("events");
|
|
5740
|
-
var
|
|
5971
|
+
var import_node_fs26 = require("fs");
|
|
5741
5972
|
init_error_detail();
|
|
5742
5973
|
|
|
5743
5974
|
// src/agents/spawn.ts
|
|
@@ -7174,16 +7405,76 @@ function forkChatTab(input) {
|
|
|
7174
7405
|
const from = requireThread(input.threadId);
|
|
7175
7406
|
const slice = forkMessageSlice(from, input.throughIndex);
|
|
7176
7407
|
const attachment = buildForkTranscriptAttachment(from.title || "Chat", slice);
|
|
7177
|
-
|
|
7408
|
+
const tab = createChatTab({
|
|
7178
7409
|
fromThreadId: input.threadId,
|
|
7179
7410
|
agent: input.agent ?? from.agent,
|
|
7411
|
+
model: input.model,
|
|
7180
7412
|
title: input.title?.trim() || void 0,
|
|
7181
7413
|
attachments: [attachment]
|
|
7182
7414
|
});
|
|
7415
|
+
if (isOrchestratorThread(from) && tab.parentThreadId !== from.id) {
|
|
7416
|
+
const next = { ...tab, parentThreadId: from.id };
|
|
7417
|
+
writeThread(next);
|
|
7418
|
+
return next;
|
|
7419
|
+
}
|
|
7420
|
+
return tab;
|
|
7421
|
+
}
|
|
7422
|
+
|
|
7423
|
+
// src/review/request-review.ts
|
|
7424
|
+
var import_node_crypto3 = require("crypto");
|
|
7425
|
+
var import_node_fs19 = require("fs");
|
|
7426
|
+
var import_node_path18 = require("path");
|
|
7427
|
+
init_global_workspace();
|
|
7428
|
+
init_thread_store();
|
|
7429
|
+
var REVIEW_REQUEST_PATH = ".sideboard/attachments/Review request.md";
|
|
7430
|
+
var REVIEW_REQUEST_NAME = "Review request.md";
|
|
7431
|
+
var REVIEW_REQUEST_PREFILL = `Please review the changes in this workspace and recommend whether they are ready to merge.
|
|
7432
|
+
|
|
7433
|
+
Start with a **Recommendation**: Approve, Approve with nits, Request changes, or Needs more information \u2014 and say why in 1\u20133 sentences. Then list blocking findings vs nits (findings may be empty).`;
|
|
7434
|
+
function buildReviewRequestAttachment(content) {
|
|
7435
|
+
return {
|
|
7436
|
+
id: (0, import_node_crypto3.randomUUID)(),
|
|
7437
|
+
name: REVIEW_REQUEST_NAME,
|
|
7438
|
+
kind: "file",
|
|
7439
|
+
path: REVIEW_REQUEST_PATH,
|
|
7440
|
+
content
|
|
7441
|
+
};
|
|
7442
|
+
}
|
|
7443
|
+
function readExistingReviewRequestFile(worktreePath) {
|
|
7444
|
+
const abs = (0, import_node_path18.join)(worktreePath, REVIEW_REQUEST_PATH);
|
|
7445
|
+
if (!(0, import_node_fs19.existsSync)(abs)) return null;
|
|
7446
|
+
try {
|
|
7447
|
+
const content = (0, import_node_fs19.readFileSync)(abs, "utf8");
|
|
7448
|
+
return content.trim() ? content : null;
|
|
7449
|
+
} catch {
|
|
7450
|
+
return null;
|
|
7451
|
+
}
|
|
7452
|
+
}
|
|
7453
|
+
async function requestReview(threadRef, send) {
|
|
7454
|
+
const from = findThreadByRef(threadRef);
|
|
7455
|
+
if (!from) throw new Error(`Thread not found: ${threadRef}`);
|
|
7456
|
+
if (isOrchestratorThread(from)) {
|
|
7457
|
+
throw new Error(
|
|
7458
|
+
"request_review targets a worktree agent thread (not the orchestrator). Pass a child/worktree thread ref."
|
|
7459
|
+
);
|
|
7460
|
+
}
|
|
7461
|
+
if (from.status === "archived") {
|
|
7462
|
+
throw new Error(`Thread is archived: ${from.id}`);
|
|
7463
|
+
}
|
|
7464
|
+
const existing = readExistingReviewRequestFile(from.worktreePath);
|
|
7465
|
+
const attachments = existing ? [buildReviewRequestAttachment(existing)] : [];
|
|
7466
|
+
const tab = createChatTab({
|
|
7467
|
+
fromThreadId: from.id,
|
|
7468
|
+
title: "Review",
|
|
7469
|
+
attachments
|
|
7470
|
+
});
|
|
7471
|
+
const started = await send(tab.id, REVIEW_REQUEST_PREFILL);
|
|
7472
|
+
return { tab: started, from };
|
|
7183
7473
|
}
|
|
7184
7474
|
|
|
7185
7475
|
// src/threads/fork-worktree.ts
|
|
7186
7476
|
init_thread_store();
|
|
7477
|
+
init_global_workspace();
|
|
7187
7478
|
function requireThread2(idOrRef) {
|
|
7188
7479
|
const thread = findThreadByRef(idOrRef) ?? null;
|
|
7189
7480
|
if (!thread) throw new Error(`Thread not found: ${idOrRef}`);
|
|
@@ -7191,16 +7482,28 @@ function requireThread2(idOrRef) {
|
|
|
7191
7482
|
}
|
|
7192
7483
|
async function forkThreadWorktree(input, onSetupLine) {
|
|
7193
7484
|
const from = requireThread2(input.threadId);
|
|
7485
|
+
if (isOrchestratorThread(from)) {
|
|
7486
|
+
throw new Error(
|
|
7487
|
+
"fork_worktree targets a worktree agent thread (not the orchestrator). Pass a child/worktree thread ref."
|
|
7488
|
+
);
|
|
7489
|
+
}
|
|
7490
|
+
if (!from.branchName?.trim() || !from.repoPath?.trim()) {
|
|
7491
|
+
throw new Error(
|
|
7492
|
+
`Cannot fork worktree: thread ${from.id} has no branch/repo (need a real worktree chat).`
|
|
7493
|
+
);
|
|
7494
|
+
}
|
|
7194
7495
|
const slice = forkMessageSlice(from, input.throughIndex);
|
|
7195
7496
|
const attachment = buildForkTranscriptAttachment(from.title || "Chat", slice);
|
|
7497
|
+
const nextAgent = input.agent ?? from.agent;
|
|
7498
|
+
const nextModel = input.model !== void 0 ? input.model : input.agent && input.agent !== from.agent ? null : from.model;
|
|
7196
7499
|
const thread = await createThread(
|
|
7197
7500
|
{
|
|
7198
7501
|
sourceType: "branch",
|
|
7199
7502
|
sourceRef: from.branchName,
|
|
7200
7503
|
repoPath: from.repoPath,
|
|
7201
|
-
agent:
|
|
7504
|
+
agent: nextAgent,
|
|
7202
7505
|
autonomy: from.autonomy,
|
|
7203
|
-
model:
|
|
7506
|
+
model: nextModel,
|
|
7204
7507
|
effort: from.effort,
|
|
7205
7508
|
fast: from.fast,
|
|
7206
7509
|
planMode: from.planMode,
|
|
@@ -7213,22 +7516,131 @@ async function forkThreadWorktree(input, onSetupLine) {
|
|
|
7213
7516
|
return thread;
|
|
7214
7517
|
}
|
|
7215
7518
|
|
|
7519
|
+
// src/orchestrator/quota-failover.ts
|
|
7520
|
+
var import_node_crypto4 = require("crypto");
|
|
7521
|
+
init_session_quota();
|
|
7522
|
+
init_app_settings();
|
|
7523
|
+
init_global_workspace();
|
|
7524
|
+
init_thread_store();
|
|
7525
|
+
function planOrchestrationQuotaFailover(thread, limitText, opts) {
|
|
7526
|
+
if (!isOrchestratorThread(thread)) return null;
|
|
7527
|
+
if (!isSessionQuotaLimit(limitText)) return null;
|
|
7528
|
+
const onLimit = opts?.onLimit ?? orchestrationQuotaOnLimit();
|
|
7529
|
+
const resumeAt = parseSessionQuotaResetAt(limitText, opts?.now);
|
|
7530
|
+
if (thread.quotaContinuedFromId) {
|
|
7531
|
+
if (resumeAt) {
|
|
7532
|
+
return {
|
|
7533
|
+
action: "wait_reset",
|
|
7534
|
+
reason: "Already continued once; waiting for quota reset instead.",
|
|
7535
|
+
limitText,
|
|
7536
|
+
resumeAt
|
|
7537
|
+
};
|
|
7538
|
+
}
|
|
7539
|
+
return {
|
|
7540
|
+
action: "none",
|
|
7541
|
+
reason: "Already continued once; no parseable reset time.",
|
|
7542
|
+
limitText
|
|
7543
|
+
};
|
|
7544
|
+
}
|
|
7545
|
+
if (onLimit === "wait_reset") {
|
|
7546
|
+
if (!resumeAt) {
|
|
7547
|
+
return {
|
|
7548
|
+
action: "none",
|
|
7549
|
+
reason: "wait_reset configured but reset time could not be parsed.",
|
|
7550
|
+
limitText
|
|
7551
|
+
};
|
|
7552
|
+
}
|
|
7553
|
+
return {
|
|
7554
|
+
action: "wait_reset",
|
|
7555
|
+
reason: "Settings: wait for quota reset.",
|
|
7556
|
+
limitText,
|
|
7557
|
+
resumeAt
|
|
7558
|
+
};
|
|
7559
|
+
}
|
|
7560
|
+
const preferred = opts?.fallbackAgent ?? orchestrationQuotaFallbackAgent();
|
|
7561
|
+
const fallbackAgent = resolveQuotaFallbackAgent(thread.agent, preferred);
|
|
7562
|
+
return {
|
|
7563
|
+
action: "switch_agent",
|
|
7564
|
+
reason: `Continue on ${fallbackAgent} (Auto) after ${thread.agent} session limit.`,
|
|
7565
|
+
limitText,
|
|
7566
|
+
fallbackAgent
|
|
7567
|
+
};
|
|
7568
|
+
}
|
|
7569
|
+
function buildQuotaHandoffAttachment(from, limitText, fallbackAgent) {
|
|
7570
|
+
const children = listThreads({ includeArchived: false }).filter((t) => t.parentThreadId === from.id && t.status !== "archived").slice(0, 40).map(
|
|
7571
|
+
(t) => `- ${t.title} \xB7 ${t.status} \xB7 ${t.agent} \xB7 sideboard://thread/${t.id}`
|
|
7572
|
+
);
|
|
7573
|
+
const recent = from.messages.slice(-8).map((m) => {
|
|
7574
|
+
const role = m.role === "user" ? "User" : m.role === "agent" ? "Agent" : "Summary";
|
|
7575
|
+
const text = m.text.trim().replace(/\s+/g, " ").slice(0, 280);
|
|
7576
|
+
return text ? `- ${role}: ${text}` : null;
|
|
7577
|
+
}).filter(Boolean);
|
|
7578
|
+
const body = [
|
|
7579
|
+
`# Orchestration handoff`,
|
|
7580
|
+
"",
|
|
7581
|
+
`Previous chat: ${from.title} (\`${from.id}\`) on **${from.agent}** hit a session/usage limit.`,
|
|
7582
|
+
`Limit: ${limitText.trim()}`,
|
|
7583
|
+
`Continuing on **${fallbackAgent}** with Auto model.`,
|
|
7584
|
+
"",
|
|
7585
|
+
`## Goal`,
|
|
7586
|
+
from.sourceRef?.trim() || "(none)",
|
|
7587
|
+
"",
|
|
7588
|
+
`## Child threads`,
|
|
7589
|
+
children.length ? children.join("\n") : "(none listed \u2014 call list_threads)",
|
|
7590
|
+
"",
|
|
7591
|
+
`## Recent turns (truncated)`,
|
|
7592
|
+
recent.length ? recent.join("\n") : "(none)",
|
|
7593
|
+
"",
|
|
7594
|
+
`## Instructions`,
|
|
7595
|
+
`- Continue fleet orchestration from this handoff.`,
|
|
7596
|
+
`- Prefer Sideboard MCP (list_threads, get_thread, send_to_thread, \u2026) for live status.`,
|
|
7597
|
+
`- Leave model Auto unless there is a specific reason to pin one.`,
|
|
7598
|
+
`- Do not wait on the limited ${from.agent} account; keep going on ${fallbackAgent}.`
|
|
7599
|
+
].join("\n");
|
|
7600
|
+
return {
|
|
7601
|
+
id: (0, import_node_crypto4.randomUUID)(),
|
|
7602
|
+
name: "Orchestration quota handoff.md",
|
|
7603
|
+
kind: "transcript",
|
|
7604
|
+
content: body
|
|
7605
|
+
};
|
|
7606
|
+
}
|
|
7607
|
+
var QUOTA_CONTINUE_PROMPT = (fromAgent, fallback) => [
|
|
7608
|
+
`${fromAgent} hit a session/usage limit. Continue this orchestration on ${fallback} using the attached handoff.`,
|
|
7609
|
+
"Call list_threads for live fleet status, then proceed with the goal. Leave model Auto unless needed."
|
|
7610
|
+
].join(" ");
|
|
7611
|
+
var QUOTA_RESUME_PROMPT = "Session/usage limit window should have reset. Continue the orchestration from where you left off. Use list_threads for fleet status.";
|
|
7612
|
+
function createQuotaFailoverChat(from, fallbackAgent, limitText) {
|
|
7613
|
+
const handoff = buildQuotaHandoffAttachment(from, limitText, fallbackAgent);
|
|
7614
|
+
const tab = createChatTab({
|
|
7615
|
+
fromThreadId: from.id,
|
|
7616
|
+
agent: fallbackAgent,
|
|
7617
|
+
model: null,
|
|
7618
|
+
attachments: [handoff]
|
|
7619
|
+
});
|
|
7620
|
+
return updateThread(tab.id, {
|
|
7621
|
+
parentThreadId: from.id,
|
|
7622
|
+
quotaContinuedFromId: from.id,
|
|
7623
|
+
sourceRef: from.sourceRef,
|
|
7624
|
+
sourceType: "orchestration"
|
|
7625
|
+
});
|
|
7626
|
+
}
|
|
7627
|
+
|
|
7216
7628
|
// src/threads/adopt.ts
|
|
7217
7629
|
var import_node_child_process = require("child_process");
|
|
7218
|
-
var
|
|
7630
|
+
var import_node_fs20 = require("fs");
|
|
7219
7631
|
var import_node_os8 = require("os");
|
|
7220
|
-
var
|
|
7632
|
+
var import_node_path19 = require("path");
|
|
7221
7633
|
var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
|
|
7222
7634
|
init_worktree();
|
|
7223
7635
|
init_thread_store();
|
|
7224
|
-
var CONDUCTOR_APP_SUPPORT = (0,
|
|
7636
|
+
var CONDUCTOR_APP_SUPPORT = (0, import_node_path19.join)(
|
|
7225
7637
|
process.env.HOME ?? "",
|
|
7226
7638
|
"Library",
|
|
7227
7639
|
"Application Support",
|
|
7228
7640
|
"com.conductor.app"
|
|
7229
7641
|
);
|
|
7230
|
-
var CONDUCTOR_DB = (0,
|
|
7231
|
-
var CURSOR_SDK_STORE = (0,
|
|
7642
|
+
var CONDUCTOR_DB = (0, import_node_path19.join)(CONDUCTOR_APP_SUPPORT, "conductor.db");
|
|
7643
|
+
var CURSOR_SDK_STORE = (0, import_node_path19.join)(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
|
|
7232
7644
|
function mapAgentType(raw) {
|
|
7233
7645
|
if (!raw) return null;
|
|
7234
7646
|
const v = raw.toLowerCase();
|
|
@@ -7240,21 +7652,21 @@ function mapAgentType(raw) {
|
|
|
7240
7652
|
return null;
|
|
7241
7653
|
}
|
|
7242
7654
|
function resolveConductorCursorAgentId(workspacePath) {
|
|
7243
|
-
if (!workspacePath || !(0,
|
|
7655
|
+
if (!workspacePath || !(0, import_node_fs20.existsSync)(CURSOR_SDK_STORE)) return null;
|
|
7244
7656
|
const normalized = workspacePath.replace(/\/$/, "");
|
|
7245
7657
|
let best = null;
|
|
7246
7658
|
let hashes;
|
|
7247
7659
|
try {
|
|
7248
|
-
hashes = (0,
|
|
7660
|
+
hashes = (0, import_node_fs20.readdirSync)(CURSOR_SDK_STORE);
|
|
7249
7661
|
} catch {
|
|
7250
7662
|
return null;
|
|
7251
7663
|
}
|
|
7252
7664
|
for (const hash of hashes) {
|
|
7253
|
-
const agentsFile = (0,
|
|
7254
|
-
if (!(0,
|
|
7665
|
+
const agentsFile = (0, import_node_path19.join)(CURSOR_SDK_STORE, hash, "agents.ndjson");
|
|
7666
|
+
if (!(0, import_node_fs20.existsSync)(agentsFile)) continue;
|
|
7255
7667
|
let text;
|
|
7256
7668
|
try {
|
|
7257
|
-
text = (0,
|
|
7669
|
+
text = (0, import_node_fs20.readFileSync)(agentsFile, "utf8");
|
|
7258
7670
|
} catch {
|
|
7259
7671
|
continue;
|
|
7260
7672
|
}
|
|
@@ -7278,7 +7690,7 @@ function resolveConductorCursorAgentId(workspacePath) {
|
|
|
7278
7690
|
return best?.agentId ?? null;
|
|
7279
7691
|
}
|
|
7280
7692
|
async function adoptThread(input) {
|
|
7281
|
-
if (!(0,
|
|
7693
|
+
if (!(0, import_node_fs20.existsSync)(input.worktreePath)) {
|
|
7282
7694
|
throw new Error(`Worktree not found: ${input.worktreePath}`);
|
|
7283
7695
|
}
|
|
7284
7696
|
const repoPath = await resolveRepoRoot(input.worktreePath);
|
|
@@ -7302,18 +7714,18 @@ async function adoptThread(input) {
|
|
|
7302
7714
|
return thread;
|
|
7303
7715
|
}
|
|
7304
7716
|
function listConductorWorkspaces() {
|
|
7305
|
-
if (!(0,
|
|
7717
|
+
if (!(0, import_node_fs20.existsSync)(CONDUCTOR_DB)) {
|
|
7306
7718
|
throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
|
|
7307
7719
|
}
|
|
7308
|
-
const tmp = (0,
|
|
7309
|
-
const snapshot = (0,
|
|
7720
|
+
const tmp = (0, import_node_fs20.mkdtempSync)((0, import_node_path19.join)((0, import_node_os8.tmpdir)(), "sideboard-conductor-"));
|
|
7721
|
+
const snapshot = (0, import_node_path19.join)(tmp, "conductor.db");
|
|
7310
7722
|
try {
|
|
7311
|
-
(0,
|
|
7723
|
+
(0, import_node_fs20.copyFileSync)(CONDUCTOR_DB, snapshot);
|
|
7312
7724
|
for (const suffix of ["-wal", "-shm"]) {
|
|
7313
7725
|
const src = `${CONDUCTOR_DB}${suffix}`;
|
|
7314
|
-
if ((0,
|
|
7726
|
+
if ((0, import_node_fs20.existsSync)(src)) {
|
|
7315
7727
|
try {
|
|
7316
|
-
(0,
|
|
7728
|
+
(0, import_node_fs20.copyFileSync)(src, `${snapshot}${suffix}`);
|
|
7317
7729
|
} catch {
|
|
7318
7730
|
}
|
|
7319
7731
|
}
|
|
@@ -7389,22 +7801,22 @@ function listConductorWorkspaces() {
|
|
|
7389
7801
|
db.close();
|
|
7390
7802
|
}
|
|
7391
7803
|
} finally {
|
|
7392
|
-
(0,
|
|
7804
|
+
(0, import_node_fs20.rmSync)(tmp, { recursive: true, force: true });
|
|
7393
7805
|
}
|
|
7394
7806
|
}
|
|
7395
7807
|
function importConductorWorkspace(workspaceId) {
|
|
7396
|
-
if (!(0,
|
|
7808
|
+
if (!(0, import_node_fs20.existsSync)(CONDUCTOR_DB)) {
|
|
7397
7809
|
throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
|
|
7398
7810
|
}
|
|
7399
|
-
const tmp = (0,
|
|
7400
|
-
const snapshot = (0,
|
|
7811
|
+
const tmp = (0, import_node_fs20.mkdtempSync)((0, import_node_path19.join)((0, import_node_os8.tmpdir)(), "sideboard-conductor-"));
|
|
7812
|
+
const snapshot = (0, import_node_path19.join)(tmp, "conductor.db");
|
|
7401
7813
|
try {
|
|
7402
|
-
(0,
|
|
7814
|
+
(0, import_node_fs20.copyFileSync)(CONDUCTOR_DB, snapshot);
|
|
7403
7815
|
for (const suffix of ["-wal", "-shm"]) {
|
|
7404
7816
|
const src = `${CONDUCTOR_DB}${suffix}`;
|
|
7405
|
-
if ((0,
|
|
7817
|
+
if ((0, import_node_fs20.existsSync)(src)) {
|
|
7406
7818
|
try {
|
|
7407
|
-
(0,
|
|
7819
|
+
(0, import_node_fs20.copyFileSync)(src, `${snapshot}${suffix}`);
|
|
7408
7820
|
} catch {
|
|
7409
7821
|
}
|
|
7410
7822
|
}
|
|
@@ -7422,7 +7834,7 @@ function importConductorWorkspace(workspaceId) {
|
|
|
7422
7834
|
).get(workspaceId);
|
|
7423
7835
|
if (!row) throw new Error(`Conductor workspace not found: ${workspaceId}`);
|
|
7424
7836
|
const worktreePath = String(row.workspacePath);
|
|
7425
|
-
if (!(0,
|
|
7837
|
+
if (!(0, import_node_fs20.existsSync)(worktreePath)) {
|
|
7426
7838
|
throw new Error(`Conductor worktree missing on disk: ${worktreePath}`);
|
|
7427
7839
|
}
|
|
7428
7840
|
let sessionId = null;
|
|
@@ -7485,7 +7897,7 @@ function importConductorWorkspace(workspaceId) {
|
|
|
7485
7897
|
db.close();
|
|
7486
7898
|
}
|
|
7487
7899
|
} finally {
|
|
7488
|
-
(0,
|
|
7900
|
+
(0, import_node_fs20.rmSync)(tmp, { recursive: true, force: true });
|
|
7489
7901
|
}
|
|
7490
7902
|
}
|
|
7491
7903
|
async function importConductorWorkspaceAsync(workspaceId) {
|
|
@@ -7496,12 +7908,12 @@ async function importConductorWorkspaceAsync(workspaceId) {
|
|
|
7496
7908
|
init_worktree();
|
|
7497
7909
|
|
|
7498
7910
|
// src/diff/diff.ts
|
|
7499
|
-
var
|
|
7500
|
-
var
|
|
7911
|
+
var import_node_fs21 = require("fs");
|
|
7912
|
+
var import_node_path20 = require("path");
|
|
7501
7913
|
init_run();
|
|
7502
7914
|
init_worktree();
|
|
7503
7915
|
async function inspectGitWorktree(worktreePath) {
|
|
7504
|
-
if (!worktreePath || !(0,
|
|
7916
|
+
if (!worktreePath || !(0, import_node_fs21.existsSync)(worktreePath)) return "missing_worktree";
|
|
7505
7917
|
const check = await git(["rev-parse", "--is-inside-work-tree"], worktreePath, {
|
|
7506
7918
|
reject: false
|
|
7507
7919
|
});
|
|
@@ -7509,7 +7921,7 @@ async function inspectGitWorktree(worktreePath) {
|
|
|
7509
7921
|
return "ok";
|
|
7510
7922
|
}
|
|
7511
7923
|
async function initializeGitRepository(worktreePath) {
|
|
7512
|
-
if (!worktreePath || !(0,
|
|
7924
|
+
if (!worktreePath || !(0, import_node_fs21.existsSync)(worktreePath)) {
|
|
7513
7925
|
throw new Error("Worktree not found");
|
|
7514
7926
|
}
|
|
7515
7927
|
const status = await inspectGitWorktree(worktreePath);
|
|
@@ -7941,8 +8353,8 @@ var DEFAULT_UPLOAD_MAX_BYTES = 5e7;
|
|
|
7941
8353
|
function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
|
|
7942
8354
|
assertSafeRelativePath(relativePath);
|
|
7943
8355
|
const maxBytes = opts?.maxBytes ?? DEFAULT_UPLOAD_MAX_BYTES;
|
|
7944
|
-
const abs = (0,
|
|
7945
|
-
const st = (0,
|
|
8356
|
+
const abs = (0, import_node_path20.join)(worktreePath, relativePath);
|
|
8357
|
+
const st = (0, import_node_fs21.statSync)(abs);
|
|
7946
8358
|
if (!st.isFile()) {
|
|
7947
8359
|
throw new Error(`Not a file: ${relativePath}`);
|
|
7948
8360
|
}
|
|
@@ -7951,7 +8363,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
|
|
|
7951
8363
|
`File too large to upload (${st.size} bytes; max ${maxBytes})`
|
|
7952
8364
|
);
|
|
7953
8365
|
}
|
|
7954
|
-
const buf = (0,
|
|
8366
|
+
const buf = (0, import_node_fs21.readFileSync)(abs);
|
|
7955
8367
|
return {
|
|
7956
8368
|
path: relativePath,
|
|
7957
8369
|
contentBase64: buf.toString("base64"),
|
|
@@ -7961,12 +8373,12 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
|
|
|
7961
8373
|
function readWorktreeFile(worktreePath, relativePath, opts) {
|
|
7962
8374
|
assertSafeRelativePath(relativePath);
|
|
7963
8375
|
const maxBytes = opts?.maxBytes ?? 2e5;
|
|
7964
|
-
const abs = (0,
|
|
7965
|
-
const st = (0,
|
|
8376
|
+
const abs = (0, import_node_path20.join)(worktreePath, relativePath);
|
|
8377
|
+
const st = (0, import_node_fs21.statSync)(abs);
|
|
7966
8378
|
if (!st.isFile()) {
|
|
7967
8379
|
throw new Error(`Not a file: ${relativePath}`);
|
|
7968
8380
|
}
|
|
7969
|
-
const buf = (0,
|
|
8381
|
+
const buf = (0, import_node_fs21.readFileSync)(abs);
|
|
7970
8382
|
if (isImageRelativePath(relativePath)) {
|
|
7971
8383
|
const maxImageBytes = Math.max(maxBytes, 15e6);
|
|
7972
8384
|
const truncated2 = buf.length > maxImageBytes;
|
|
@@ -8009,9 +8421,9 @@ function assertSafeRelativePath(relativePath) {
|
|
|
8009
8421
|
}
|
|
8010
8422
|
function writeWorktreeFile(worktreePath, relativePath, content) {
|
|
8011
8423
|
assertSafeRelativePath(relativePath);
|
|
8012
|
-
const abs = (0,
|
|
8013
|
-
(0,
|
|
8014
|
-
(0,
|
|
8424
|
+
const abs = (0, import_node_path20.join)(worktreePath, relativePath);
|
|
8425
|
+
(0, import_node_fs21.mkdirSync)((0, import_node_path20.dirname)(abs), { recursive: true });
|
|
8426
|
+
(0, import_node_fs21.writeFileSync)(abs, content, "utf8");
|
|
8015
8427
|
return { path: relativePath };
|
|
8016
8428
|
}
|
|
8017
8429
|
async function getDiffSummary(worktreePath, repoPath, opts) {
|
|
@@ -8190,9 +8602,9 @@ async function confirmLand(thread, opts) {
|
|
|
8190
8602
|
}
|
|
8191
8603
|
|
|
8192
8604
|
// src/skills/discover.ts
|
|
8193
|
-
var
|
|
8605
|
+
var import_node_fs22 = require("fs");
|
|
8194
8606
|
var import_node_os9 = require("os");
|
|
8195
|
-
var
|
|
8607
|
+
var import_node_path21 = require("path");
|
|
8196
8608
|
function toCommand(name) {
|
|
8197
8609
|
return name.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
8198
8610
|
}
|
|
@@ -8224,7 +8636,7 @@ function parseFrontmatter(content) {
|
|
|
8224
8636
|
}
|
|
8225
8637
|
function readSkill(skillMd, source) {
|
|
8226
8638
|
try {
|
|
8227
|
-
const content = (0,
|
|
8639
|
+
const content = (0, import_node_fs22.readFileSync)(skillMd, "utf8");
|
|
8228
8640
|
const { name: fmName, description } = parseFrontmatter(content);
|
|
8229
8641
|
const dirName = skillMd.split("/").slice(-2, -1)[0] || "skill";
|
|
8230
8642
|
const name = fmName || dirName;
|
|
@@ -8243,19 +8655,19 @@ function readSkill(skillMd, source) {
|
|
|
8243
8655
|
}
|
|
8244
8656
|
}
|
|
8245
8657
|
function scanSkillsDir(dir, source, out) {
|
|
8246
|
-
if (!(0,
|
|
8658
|
+
if (!(0, import_node_fs22.existsSync)(dir)) return;
|
|
8247
8659
|
let entries;
|
|
8248
8660
|
try {
|
|
8249
|
-
entries = (0,
|
|
8661
|
+
entries = (0, import_node_fs22.readdirSync)(dir);
|
|
8250
8662
|
} catch {
|
|
8251
8663
|
return;
|
|
8252
8664
|
}
|
|
8253
8665
|
for (const entry of entries) {
|
|
8254
8666
|
if (entry.startsWith(".")) continue;
|
|
8255
|
-
const skillMd = (0,
|
|
8256
|
-
if (!(0,
|
|
8667
|
+
const skillMd = (0, import_node_path21.join)(dir, entry, "SKILL.md");
|
|
8668
|
+
if (!(0, import_node_fs22.existsSync)(skillMd)) continue;
|
|
8257
8669
|
try {
|
|
8258
|
-
if (!(0,
|
|
8670
|
+
if (!(0, import_node_fs22.statSync)(skillMd).isFile()) continue;
|
|
8259
8671
|
} catch {
|
|
8260
8672
|
continue;
|
|
8261
8673
|
}
|
|
@@ -8264,24 +8676,24 @@ function scanSkillsDir(dir, source, out) {
|
|
|
8264
8676
|
}
|
|
8265
8677
|
}
|
|
8266
8678
|
function scanClaudePluginSkills(pluginsRoot, out) {
|
|
8267
|
-
if (!(0,
|
|
8679
|
+
if (!(0, import_node_fs22.existsSync)(pluginsRoot)) return;
|
|
8268
8680
|
const walk = (dir, depth, lookingForSkillsDir) => {
|
|
8269
8681
|
if (depth > 7) return;
|
|
8270
8682
|
let entries;
|
|
8271
8683
|
try {
|
|
8272
|
-
entries = (0,
|
|
8684
|
+
entries = (0, import_node_fs22.readdirSync)(dir);
|
|
8273
8685
|
} catch {
|
|
8274
8686
|
return;
|
|
8275
8687
|
}
|
|
8276
8688
|
if (lookingForSkillsDir && entries.includes("SKILL.md")) {
|
|
8277
|
-
const skill = readSkill((0,
|
|
8689
|
+
const skill = readSkill((0, import_node_path21.join)(dir, "SKILL.md"), "cli");
|
|
8278
8690
|
if (skill) out.push(skill);
|
|
8279
8691
|
}
|
|
8280
8692
|
for (const entry of entries) {
|
|
8281
8693
|
if (entry === "node_modules" || entry === ".git") continue;
|
|
8282
|
-
const full = (0,
|
|
8694
|
+
const full = (0, import_node_path21.join)(dir, entry);
|
|
8283
8695
|
try {
|
|
8284
|
-
if (!(0,
|
|
8696
|
+
if (!(0, import_node_fs22.statSync)(full).isDirectory()) continue;
|
|
8285
8697
|
} catch {
|
|
8286
8698
|
continue;
|
|
8287
8699
|
}
|
|
@@ -8299,17 +8711,17 @@ function discoverSkills(worktreePath) {
|
|
|
8299
8711
|
const home = (0, import_node_os9.homedir)();
|
|
8300
8712
|
const collected = [];
|
|
8301
8713
|
for (const rel of [".claude/skills", ".cursor/skills", ".sideboard/skills", ".brightsy/skills", "skills"]) {
|
|
8302
|
-
scanSkillsDir((0,
|
|
8714
|
+
scanSkillsDir((0, import_node_path21.join)(worktreePath, rel), "workspace", collected);
|
|
8303
8715
|
}
|
|
8304
8716
|
for (const abs of [
|
|
8305
|
-
(0,
|
|
8306
|
-
(0,
|
|
8307
|
-
(0,
|
|
8308
|
-
(0,
|
|
8717
|
+
(0, import_node_path21.join)(home, ".claude/skills"),
|
|
8718
|
+
(0, import_node_path21.join)(home, ".cursor/skills"),
|
|
8719
|
+
(0, import_node_path21.join)(home, ".sideboard/skills"),
|
|
8720
|
+
(0, import_node_path21.join)(home, ".brightsy/skills")
|
|
8309
8721
|
]) {
|
|
8310
8722
|
scanSkillsDir(abs, "user", collected);
|
|
8311
8723
|
}
|
|
8312
|
-
scanClaudePluginSkills((0,
|
|
8724
|
+
scanClaudePluginSkills((0, import_node_path21.join)(home, ".claude/plugins"), collected);
|
|
8313
8725
|
const rank = { workspace: 0, user: 1, cli: 2 };
|
|
8314
8726
|
const byCommand = /* @__PURE__ */ new Map();
|
|
8315
8727
|
for (const skill of collected) {
|
|
@@ -8321,7 +8733,7 @@ function discoverSkills(worktreePath) {
|
|
|
8321
8733
|
return [...byCommand.values()].sort((a, b) => a.command.localeCompare(b.command));
|
|
8322
8734
|
}
|
|
8323
8735
|
function readSkillBody(skillPath, maxChars = 12e3) {
|
|
8324
|
-
const raw = (0,
|
|
8736
|
+
const raw = (0, import_node_fs22.readFileSync)(skillPath, "utf8");
|
|
8325
8737
|
if (raw.startsWith("---")) {
|
|
8326
8738
|
const end = raw.indexOf("\n---", 3);
|
|
8327
8739
|
if (end >= 0) {
|
|
@@ -8414,9 +8826,9 @@ function expandComposerPrompt(worktreePath, prompt, opts) {
|
|
|
8414
8826
|
}
|
|
8415
8827
|
|
|
8416
8828
|
// src/composer/stage-files.ts
|
|
8417
|
-
var
|
|
8418
|
-
var
|
|
8419
|
-
var
|
|
8829
|
+
var import_node_fs23 = require("fs");
|
|
8830
|
+
var import_node_path22 = require("path");
|
|
8831
|
+
var import_node_crypto5 = require("crypto");
|
|
8420
8832
|
var IMAGE_EXTENSIONS2 = /* @__PURE__ */ new Set([
|
|
8421
8833
|
"png",
|
|
8422
8834
|
"jpg",
|
|
@@ -8445,7 +8857,7 @@ var ATTACHMENTS_GITIGNORE = `# Sideboard review / composer attachments (local on
|
|
|
8445
8857
|
var MAX_INLINE_BYTES = 4e5;
|
|
8446
8858
|
var MAX_PREVIEW_BYTES = 5e6;
|
|
8447
8859
|
function fileExtension(filePath) {
|
|
8448
|
-
const base = (0,
|
|
8860
|
+
const base = (0, import_node_path22.basename)(filePath).toLowerCase();
|
|
8449
8861
|
return base.includes(".") ? base.split(".").pop() || "" : "";
|
|
8450
8862
|
}
|
|
8451
8863
|
function isImageFilePath(filePath) {
|
|
@@ -8455,24 +8867,24 @@ function imageMimeType(filePath) {
|
|
|
8455
8867
|
return IMAGE_MIME_BY_EXT[fileExtension(filePath)] || "image/png";
|
|
8456
8868
|
}
|
|
8457
8869
|
function ensureAttachmentsDir(worktreePath) {
|
|
8458
|
-
const dir = (0,
|
|
8459
|
-
(0,
|
|
8460
|
-
const gi = (0,
|
|
8461
|
-
if (!(0,
|
|
8462
|
-
(0,
|
|
8870
|
+
const dir = (0, import_node_path22.join)(worktreePath, ATTACHMENTS_DIR);
|
|
8871
|
+
(0, import_node_fs23.mkdirSync)(dir, { recursive: true });
|
|
8872
|
+
const gi = (0, import_node_path22.join)(dir, ".gitignore");
|
|
8873
|
+
if (!(0, import_node_fs23.existsSync)(gi)) {
|
|
8874
|
+
(0, import_node_fs23.writeFileSync)(gi, ATTACHMENTS_GITIGNORE, "utf8");
|
|
8463
8875
|
}
|
|
8464
8876
|
return dir;
|
|
8465
8877
|
}
|
|
8466
8878
|
function uniqueAttachmentName(dir, originalName) {
|
|
8467
8879
|
const safe = originalName.replace(/[/\\]/g, "_") || "file";
|
|
8468
|
-
if (!(0,
|
|
8469
|
-
const ext = (0,
|
|
8880
|
+
if (!(0, import_node_fs23.existsSync)((0, import_node_path22.join)(dir, safe))) return safe;
|
|
8881
|
+
const ext = (0, import_node_path22.extname)(safe);
|
|
8470
8882
|
const stem = ext ? safe.slice(0, -ext.length) : safe;
|
|
8471
8883
|
for (let i = 1; i < 1e4; i++) {
|
|
8472
8884
|
const candidate = `${stem}-${i}${ext}`;
|
|
8473
|
-
if (!(0,
|
|
8885
|
+
if (!(0, import_node_fs23.existsSync)((0, import_node_path22.join)(dir, candidate))) return candidate;
|
|
8474
8886
|
}
|
|
8475
|
-
return `${stem}-${(0,
|
|
8887
|
+
return `${stem}-${(0, import_node_crypto5.randomUUID)()}${ext}`;
|
|
8476
8888
|
}
|
|
8477
8889
|
function previewDataUrlFromBuf(filePath, buf) {
|
|
8478
8890
|
if (!isImageFilePath(filePath)) return void 0;
|
|
@@ -8484,7 +8896,7 @@ function attachmentFromBuffer(name, buf, opts) {
|
|
|
8484
8896
|
if (isImageFilePath(name)) {
|
|
8485
8897
|
const pathHint = opts.path ? `\`${opts.path}\`` : opts.sourceLabel || name;
|
|
8486
8898
|
return {
|
|
8487
|
-
id: (0,
|
|
8899
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
8488
8900
|
name,
|
|
8489
8901
|
kind: "file",
|
|
8490
8902
|
path: opts.path,
|
|
@@ -8497,7 +8909,7 @@ function attachmentFromBuffer(name, buf, opts) {
|
|
|
8497
8909
|
}
|
|
8498
8910
|
if (buf.length > MAX_INLINE_BYTES) {
|
|
8499
8911
|
return {
|
|
8500
|
-
id: (0,
|
|
8912
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
8501
8913
|
name,
|
|
8502
8914
|
kind: "file",
|
|
8503
8915
|
path: opts.path,
|
|
@@ -8506,7 +8918,7 @@ function attachmentFromBuffer(name, buf, opts) {
|
|
|
8506
8918
|
}
|
|
8507
8919
|
if (buf.includes(0)) {
|
|
8508
8920
|
return {
|
|
8509
|
-
id: (0,
|
|
8921
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
8510
8922
|
name,
|
|
8511
8923
|
kind: "file",
|
|
8512
8924
|
path: opts.path,
|
|
@@ -8514,7 +8926,7 @@ function attachmentFromBuffer(name, buf, opts) {
|
|
|
8514
8926
|
};
|
|
8515
8927
|
}
|
|
8516
8928
|
return {
|
|
8517
|
-
id: (0,
|
|
8929
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
8518
8930
|
name,
|
|
8519
8931
|
kind: "file",
|
|
8520
8932
|
path: opts.path,
|
|
@@ -8526,19 +8938,19 @@ function stageAbsolutePathsAsAttachments(worktreePath, absolutePaths) {
|
|
|
8526
8938
|
const dir = ensureAttachmentsDir(worktreePath);
|
|
8527
8939
|
const out = [];
|
|
8528
8940
|
for (const abs of absolutePaths) {
|
|
8529
|
-
const originalName = (0,
|
|
8941
|
+
const originalName = (0, import_node_path22.basename)(abs);
|
|
8530
8942
|
try {
|
|
8531
|
-
const st = (0,
|
|
8943
|
+
const st = (0, import_node_fs23.statSync)(abs);
|
|
8532
8944
|
if (!st.isFile()) continue;
|
|
8533
8945
|
const name = uniqueAttachmentName(dir, originalName);
|
|
8534
|
-
const destAbs = (0,
|
|
8535
|
-
(0,
|
|
8946
|
+
const destAbs = (0, import_node_path22.join)(dir, name);
|
|
8947
|
+
(0, import_node_fs23.copyFileSync)(abs, destAbs);
|
|
8536
8948
|
const rel = `${ATTACHMENTS_DIR}/${name}`;
|
|
8537
|
-
const buf = (0,
|
|
8949
|
+
const buf = (0, import_node_fs23.readFileSync)(destAbs);
|
|
8538
8950
|
out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
|
|
8539
8951
|
} catch (err) {
|
|
8540
8952
|
out.push({
|
|
8541
|
-
id: (0,
|
|
8953
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
8542
8954
|
name: originalName,
|
|
8543
8955
|
kind: "file",
|
|
8544
8956
|
content: `(could not attach ${abs}: ${err instanceof Error ? err.message : String(err)})`
|
|
@@ -8556,13 +8968,13 @@ function stageBuffersAsAttachments(worktreePath, buffers) {
|
|
|
8556
8968
|
try {
|
|
8557
8969
|
const buf = Buffer.from(item.dataBase64, "base64");
|
|
8558
8970
|
const name = uniqueAttachmentName(dir, originalName);
|
|
8559
|
-
const destAbs = (0,
|
|
8560
|
-
(0,
|
|
8971
|
+
const destAbs = (0, import_node_path22.join)(dir, name);
|
|
8972
|
+
(0, import_node_fs23.writeFileSync)(destAbs, buf);
|
|
8561
8973
|
const rel = `${ATTACHMENTS_DIR}/${name}`;
|
|
8562
8974
|
out.push(attachmentFromBuffer(name, buf, { path: rel }));
|
|
8563
8975
|
} catch (err) {
|
|
8564
8976
|
out.push({
|
|
8565
|
-
id: (0,
|
|
8977
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
8566
8978
|
name: originalName,
|
|
8567
8979
|
kind: "file",
|
|
8568
8980
|
content: `(could not attach ${originalName}: ${err instanceof Error ? err.message : String(err)})`
|
|
@@ -8576,23 +8988,23 @@ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
|
|
|
8576
8988
|
for (const rel of relativePaths) {
|
|
8577
8989
|
if (!rel || rel.includes("..") || rel.startsWith("/")) {
|
|
8578
8990
|
out.push({
|
|
8579
|
-
id: (0,
|
|
8580
|
-
name: (0,
|
|
8991
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
8992
|
+
name: (0, import_node_path22.basename)(rel) || "file",
|
|
8581
8993
|
kind: "file",
|
|
8582
8994
|
content: `(invalid path: ${rel})`
|
|
8583
8995
|
});
|
|
8584
8996
|
continue;
|
|
8585
8997
|
}
|
|
8586
|
-
const name = (0,
|
|
8998
|
+
const name = (0, import_node_path22.basename)(rel);
|
|
8587
8999
|
try {
|
|
8588
|
-
const abs = (0,
|
|
8589
|
-
const st = (0,
|
|
9000
|
+
const abs = (0, import_node_path22.join)(worktreePath, rel);
|
|
9001
|
+
const st = (0, import_node_fs23.statSync)(abs);
|
|
8590
9002
|
if (!st.isFile()) continue;
|
|
8591
|
-
const buf = (0,
|
|
9003
|
+
const buf = (0, import_node_fs23.readFileSync)(abs);
|
|
8592
9004
|
out.push(attachmentFromBuffer(name, buf, { path: rel, sourceLabel: abs }));
|
|
8593
9005
|
} catch (err) {
|
|
8594
9006
|
out.push({
|
|
8595
|
-
id: (0,
|
|
9007
|
+
id: (0, import_node_crypto5.randomUUID)(),
|
|
8596
9008
|
name,
|
|
8597
9009
|
kind: "file",
|
|
8598
9010
|
content: `(could not read ${rel}: ${err instanceof Error ? err.message : String(err)})`
|
|
@@ -8603,8 +9015,8 @@ function attachmentsFromWorktreePaths(worktreePath, relativePaths) {
|
|
|
8603
9015
|
}
|
|
8604
9016
|
|
|
8605
9017
|
// src/agents/instructions.ts
|
|
8606
|
-
var
|
|
8607
|
-
var
|
|
9018
|
+
var import_node_fs24 = require("fs");
|
|
9019
|
+
var import_node_path23 = require("path");
|
|
8608
9020
|
init_worktree_labels();
|
|
8609
9021
|
function normPath2(p) {
|
|
8610
9022
|
return p.replace(/\/+$/, "");
|
|
@@ -8743,11 +9155,11 @@ function loadAgentInstructions(worktreePath, agent) {
|
|
|
8743
9155
|
const out = [];
|
|
8744
9156
|
for (const rel of candidates) {
|
|
8745
9157
|
if (seen.has(rel)) continue;
|
|
8746
|
-
const abs = (0,
|
|
8747
|
-
if (!(0,
|
|
9158
|
+
const abs = (0, import_node_path23.join)(worktreePath, rel);
|
|
9159
|
+
if (!(0, import_node_fs24.existsSync)(abs)) continue;
|
|
8748
9160
|
try {
|
|
8749
|
-
if (!(0,
|
|
8750
|
-
let content = (0,
|
|
9161
|
+
if (!(0, import_node_fs24.statSync)(abs).isFile()) continue;
|
|
9162
|
+
let content = (0, import_node_fs24.readFileSync)(abs, "utf8");
|
|
8751
9163
|
if (!content.trim()) continue;
|
|
8752
9164
|
if (content.length > MAX_CHARS_PER_FILE) {
|
|
8753
9165
|
content = `${content.slice(0, MAX_CHARS_PER_FILE)}
|
|
@@ -8848,6 +9260,8 @@ var Orchestrator = class {
|
|
|
8848
9260
|
haltDrain = /* @__PURE__ */ new Set();
|
|
8849
9261
|
/** WIP snapshot SHA at the start of the latest agent turn (per thread). */
|
|
8850
9262
|
turnBaselines = /* @__PURE__ */ new Map();
|
|
9263
|
+
/** Timers for orchestration session-quota auto-resume. */
|
|
9264
|
+
quotaResumeTimers = /* @__PURE__ */ new Map();
|
|
8851
9265
|
maxConcurrent;
|
|
8852
9266
|
runningCount = 0;
|
|
8853
9267
|
constructor(opts) {
|
|
@@ -8897,7 +9311,7 @@ var Orchestrator = class {
|
|
|
8897
9311
|
}
|
|
8898
9312
|
continue;
|
|
8899
9313
|
}
|
|
8900
|
-
if (!(0,
|
|
9314
|
+
if (!(0, import_node_fs26.existsSync)(thread.worktreePath)) {
|
|
8901
9315
|
setStatus(thread.id, "broken", "Worktree missing on disk");
|
|
8902
9316
|
this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
|
|
8903
9317
|
continue;
|
|
@@ -8927,6 +9341,113 @@ var Orchestrator = class {
|
|
|
8927
9341
|
void this.drainQueue(thread.id);
|
|
8928
9342
|
}
|
|
8929
9343
|
}
|
|
9344
|
+
this.schedulePendingQuotaResumes();
|
|
9345
|
+
}
|
|
9346
|
+
clearQuotaResumeTimer(threadId) {
|
|
9347
|
+
const timer = this.quotaResumeTimers.get(threadId);
|
|
9348
|
+
if (timer) clearTimeout(timer);
|
|
9349
|
+
this.quotaResumeTimers.delete(threadId);
|
|
9350
|
+
}
|
|
9351
|
+
/** Schedule (or fire) auto-retry after a provider session/usage limit reset. */
|
|
9352
|
+
scheduleQuotaResume(threadId, resumeAt) {
|
|
9353
|
+
this.clearQuotaResumeTimer(threadId);
|
|
9354
|
+
updateThread(threadId, { quotaResumeAt: resumeAt.toISOString() });
|
|
9355
|
+
const delay = Math.max(5e3, resumeAt.getTime() - Date.now());
|
|
9356
|
+
const capped = Math.min(delay, 2147483647);
|
|
9357
|
+
const timer = setTimeout(() => {
|
|
9358
|
+
this.quotaResumeTimers.delete(threadId);
|
|
9359
|
+
void this.resumeAfterQuotaWait(threadId);
|
|
9360
|
+
}, capped);
|
|
9361
|
+
this.quotaResumeTimers.set(threadId, timer);
|
|
9362
|
+
}
|
|
9363
|
+
schedulePendingQuotaResumes() {
|
|
9364
|
+
for (const thread of listThreads({ includeArchived: false })) {
|
|
9365
|
+
if (!thread.quotaResumeAt) continue;
|
|
9366
|
+
const at = new Date(thread.quotaResumeAt);
|
|
9367
|
+
if (Number.isNaN(at.getTime())) continue;
|
|
9368
|
+
if (at.getTime() <= Date.now()) {
|
|
9369
|
+
void this.resumeAfterQuotaWait(thread.id);
|
|
9370
|
+
} else if (!this.quotaResumeTimers.has(thread.id)) {
|
|
9371
|
+
this.scheduleQuotaResume(thread.id, at);
|
|
9372
|
+
}
|
|
9373
|
+
}
|
|
9374
|
+
}
|
|
9375
|
+
async resumeAfterQuotaWait(threadId) {
|
|
9376
|
+
const thread = findThreadByRef(threadId);
|
|
9377
|
+
if (!thread || thread.status === "archived") return;
|
|
9378
|
+
this.clearQuotaResumeTimer(threadId);
|
|
9379
|
+
try {
|
|
9380
|
+
updateThread(threadId, { quotaResumeAt: null });
|
|
9381
|
+
} catch {
|
|
9382
|
+
return;
|
|
9383
|
+
}
|
|
9384
|
+
if (thread.status === "running" || this.activeTurns.has(threadId) || this.startingTurns.has(threadId)) {
|
|
9385
|
+
return;
|
|
9386
|
+
}
|
|
9387
|
+
await this.send(threadId, QUOTA_RESUME_PROMPT);
|
|
9388
|
+
}
|
|
9389
|
+
/**
|
|
9390
|
+
* Host-side continue when an orchestration chat hits a provider session/usage
|
|
9391
|
+
* limit (not context size): switch agent (Auto) or wait until reset.
|
|
9392
|
+
*/
|
|
9393
|
+
async maybeHandleOrchestrationQuotaFailover(threadId, limitText) {
|
|
9394
|
+
const thread = findThreadByRef(threadId);
|
|
9395
|
+
if (!thread) return;
|
|
9396
|
+
const plan = planOrchestrationQuotaFailover(thread, limitText);
|
|
9397
|
+
if (!plan || plan.action === "none") return;
|
|
9398
|
+
if (plan.action === "wait_reset" && plan.resumeAt) {
|
|
9399
|
+
this.haltDrain.add(threadId);
|
|
9400
|
+
this.scheduleQuotaResume(threadId, plan.resumeAt);
|
|
9401
|
+
setStatus(threadId, "idle", null);
|
|
9402
|
+
appendMessage(threadId, {
|
|
9403
|
+
role: "agent",
|
|
9404
|
+
text: `Sideboard will auto-retry this orchestration around ${plan.resumeAt.toLocaleString()} when the session limit resets.`,
|
|
9405
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
9406
|
+
});
|
|
9407
|
+
this.emit({
|
|
9408
|
+
type: "quota_failover",
|
|
9409
|
+
threadId,
|
|
9410
|
+
action: "wait_reset",
|
|
9411
|
+
message: plan.reason,
|
|
9412
|
+
resumeAt: plan.resumeAt.toISOString()
|
|
9413
|
+
});
|
|
9414
|
+
this.emit({ type: "status_changed", threadId, status: "idle" });
|
|
9415
|
+
return;
|
|
9416
|
+
}
|
|
9417
|
+
if (plan.action === "switch_agent" && plan.fallbackAgent) {
|
|
9418
|
+
this.haltDrain.add(threadId);
|
|
9419
|
+
const next = createQuotaFailoverChat(
|
|
9420
|
+
thread,
|
|
9421
|
+
plan.fallbackAgent,
|
|
9422
|
+
plan.limitText
|
|
9423
|
+
);
|
|
9424
|
+
this.clearQuotaResumeTimer(threadId);
|
|
9425
|
+
try {
|
|
9426
|
+
updateThread(threadId, { quotaResumeAt: null });
|
|
9427
|
+
} catch {
|
|
9428
|
+
}
|
|
9429
|
+
appendMessage(threadId, {
|
|
9430
|
+
role: "agent",
|
|
9431
|
+
text: `Session limit on ${thread.agent}. Sideboard continued on ${plan.fallbackAgent} (Auto) in [${next.title}](sideboard://thread/${next.id}).`,
|
|
9432
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
9433
|
+
});
|
|
9434
|
+
this.emit({
|
|
9435
|
+
type: "quota_failover",
|
|
9436
|
+
threadId,
|
|
9437
|
+
action: "switch_agent",
|
|
9438
|
+
toThreadId: next.id,
|
|
9439
|
+
message: plan.reason
|
|
9440
|
+
});
|
|
9441
|
+
this.emit({
|
|
9442
|
+
type: "status_changed",
|
|
9443
|
+
threadId: next.id,
|
|
9444
|
+
status: next.status
|
|
9445
|
+
});
|
|
9446
|
+
await this.send(
|
|
9447
|
+
next.id,
|
|
9448
|
+
QUOTA_CONTINUE_PROMPT(thread.agent, plan.fallbackAgent)
|
|
9449
|
+
);
|
|
9450
|
+
}
|
|
8930
9451
|
}
|
|
8931
9452
|
getThreads(includeArchived = false) {
|
|
8932
9453
|
return listThreads({ includeArchived });
|
|
@@ -9285,11 +9806,16 @@ var Orchestrator = class {
|
|
|
9285
9806
|
}
|
|
9286
9807
|
}
|
|
9287
9808
|
}
|
|
9288
|
-
const
|
|
9289
|
-
|
|
9809
|
+
const lastStderr = summarizeTurnStderr(stderrTail);
|
|
9810
|
+
const detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
|
|
9811
|
+
let chatText = assistantText;
|
|
9812
|
+
if (exitCode !== 0 && !chatText && looksLikeAgentFailureMessage(detail)) {
|
|
9813
|
+
chatText = humanizeAgentFailDetail(detail);
|
|
9814
|
+
}
|
|
9815
|
+
if (chatText || parts.length > 0) {
|
|
9290
9816
|
appendMessage(threadId, {
|
|
9291
9817
|
role: "agent",
|
|
9292
|
-
text:
|
|
9818
|
+
text: chatText,
|
|
9293
9819
|
parts: parts.length > 0 ? parts : void 0,
|
|
9294
9820
|
durationMs: Math.max(0, Date.now() - turnStartedAt),
|
|
9295
9821
|
usage,
|
|
@@ -9308,13 +9834,12 @@ var Orchestrator = class {
|
|
|
9308
9834
|
this.emit({ type: "status_changed", threadId, status: "stopped" });
|
|
9309
9835
|
this.emit({ type: "turn_finished", threadId, exitCode });
|
|
9310
9836
|
} else {
|
|
9311
|
-
const lastStderr = summarizeTurnStderr(stderrTail);
|
|
9312
|
-
const detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
|
|
9313
9837
|
const failDetail = formatTurnExitError(exitCode, detail);
|
|
9838
|
+
const explainedInChat = exitCode !== 0 && Boolean(chatText) && (looksLikeAgentFailureMessage(chatText) || failDetail && chatText.includes(failDetail.replace(/^exit\s*\d+:\s*/i, "").trim()));
|
|
9314
9839
|
setStatus(
|
|
9315
9840
|
threadId,
|
|
9316
9841
|
exitCode === 0 ? "idle" : "error",
|
|
9317
|
-
exitCode === 0 ? null : failDetail
|
|
9842
|
+
exitCode === 0 || explainedInChat ? null : failDetail
|
|
9318
9843
|
);
|
|
9319
9844
|
this.emit({
|
|
9320
9845
|
type: "status_changed",
|
|
@@ -9322,6 +9847,10 @@ var Orchestrator = class {
|
|
|
9322
9847
|
status: exitCode === 0 ? "idle" : "error"
|
|
9323
9848
|
});
|
|
9324
9849
|
this.emit({ type: "turn_finished", threadId, exitCode });
|
|
9850
|
+
if (exitCode !== 0) {
|
|
9851
|
+
const blob = [chatText, detail].filter(Boolean).join("\n");
|
|
9852
|
+
void this.maybeHandleOrchestrationQuotaFailover(threadId, blob);
|
|
9853
|
+
}
|
|
9325
9854
|
}
|
|
9326
9855
|
} catch (err) {
|
|
9327
9856
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -9335,6 +9864,7 @@ var Orchestrator = class {
|
|
|
9335
9864
|
this.emit({ type: "error", threadId, message });
|
|
9336
9865
|
this.emit({ type: "status_changed", threadId, status: "error" });
|
|
9337
9866
|
this.emit({ type: "turn_finished", threadId, exitCode: 1 });
|
|
9867
|
+
void this.maybeHandleOrchestrationQuotaFailover(threadId, message);
|
|
9338
9868
|
}
|
|
9339
9869
|
} finally {
|
|
9340
9870
|
this.startingTurns.delete(threadId);
|
|
@@ -9795,6 +10325,15 @@ var Orchestrator = class {
|
|
|
9795
10325
|
setAutonomy(threadRef, autonomy) {
|
|
9796
10326
|
return this.setThreadOptions(threadRef, { autonomy });
|
|
9797
10327
|
}
|
|
10328
|
+
/**
|
|
10329
|
+
* Open a Review chat tab on a worktree thread (same as the desktop Review button)
|
|
10330
|
+
* and send the merge-readiness prefill.
|
|
10331
|
+
*/
|
|
10332
|
+
async requestReview(threadRef) {
|
|
10333
|
+
const { tab } = await requestReview(threadRef, (ref, prompt) => this.send(ref, prompt));
|
|
10334
|
+
this.emit({ type: "status_changed", threadId: tab.id, status: tab.status });
|
|
10335
|
+
return tab;
|
|
10336
|
+
}
|
|
9798
10337
|
setThreadOptions(threadRef, patch) {
|
|
9799
10338
|
const thread = this.requireThread(threadRef);
|
|
9800
10339
|
const next = {};
|
|
@@ -9919,7 +10458,7 @@ var Orchestrator = class {
|
|
|
9919
10458
|
updateThread(thread.id, { worktreePath: globalAgentCwd2() });
|
|
9920
10459
|
return setStatus(thread.id, "idle");
|
|
9921
10460
|
}
|
|
9922
|
-
if (!(0,
|
|
10461
|
+
if (!(0, import_node_fs26.existsSync)(thread.worktreePath)) {
|
|
9923
10462
|
const { createThreadWorktree: createThreadWorktree2 } = await Promise.resolve().then(() => (init_worktree(), worktree_exports));
|
|
9924
10463
|
const { execa: execa7 } = await import("execa");
|
|
9925
10464
|
const slug = thread.worktreePath.split("/").pop();
|
|
@@ -10092,6 +10631,7 @@ async function listIssues(repoPath) {
|
|
|
10092
10631
|
|
|
10093
10632
|
// src/mcp/server.ts
|
|
10094
10633
|
init_global_workspace();
|
|
10634
|
+
init_list_models();
|
|
10095
10635
|
|
|
10096
10636
|
// src/mcp/archive-guard.ts
|
|
10097
10637
|
init_global_workspace();
|
|
@@ -10137,7 +10677,7 @@ async function startMcpServer() {
|
|
|
10137
10677
|
async () => {
|
|
10138
10678
|
const threads = orch.getThreads(true);
|
|
10139
10679
|
const lines = threads.map((t) => {
|
|
10140
|
-
const repo = t.repoPath === GLOBAL_WORKSPACE_ID ? "Orchestration" : (0,
|
|
10680
|
+
const repo = t.repoPath === GLOBAL_WORKSPACE_ID ? "Orchestration" : (0, import_node_path25.basename)(t.repoPath) || t.repoPath;
|
|
10141
10681
|
return `${t.id.slice(0, 8)} ${t.status.padEnd(9)} ${t.agent.padEnd(8)} ${repo} ${t.sourceType}:${t.sourceRef} ${t.title} sideboard://thread/${t.id}${t.devPort ? ` http://localhost:${t.devPort}` : ""}`;
|
|
10142
10682
|
});
|
|
10143
10683
|
return {
|
|
@@ -10480,6 +11020,148 @@ async function startMcpServer() {
|
|
|
10480
11020
|
};
|
|
10481
11021
|
}
|
|
10482
11022
|
);
|
|
11023
|
+
server.tool(
|
|
11024
|
+
"request_review",
|
|
11025
|
+
"Start a merge-readiness Review on a worktree agent thread (same as the desktop Review button). Opens a new Review chat tab, optionally attaches .sideboard/attachments/Review request.md when present, and asks for 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.",
|
|
11026
|
+
{ ref: import_zod.z.string().describe("Worktree thread id/ref to review") },
|
|
11027
|
+
async ({ ref }) => {
|
|
11028
|
+
try {
|
|
11029
|
+
const tab = await orch.requestReview(ref);
|
|
11030
|
+
const from = orch.getThread(ref);
|
|
11031
|
+
return {
|
|
11032
|
+
content: [
|
|
11033
|
+
{
|
|
11034
|
+
type: "text",
|
|
11035
|
+
text: JSON.stringify({
|
|
11036
|
+
id: tab.id,
|
|
11037
|
+
title: tab.title,
|
|
11038
|
+
status: tab.status,
|
|
11039
|
+
fromThreadId: from?.id ?? ref,
|
|
11040
|
+
link: `sideboard://thread/${tab.id}`
|
|
11041
|
+
})
|
|
11042
|
+
}
|
|
11043
|
+
]
|
|
11044
|
+
};
|
|
11045
|
+
} catch (err) {
|
|
11046
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11047
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
11048
|
+
}
|
|
11049
|
+
}
|
|
11050
|
+
);
|
|
11051
|
+
const agentEnum = import_zod.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
|
|
11052
|
+
server.tool(
|
|
11053
|
+
"list_models",
|
|
11054
|
+
"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.",
|
|
11055
|
+
{
|
|
11056
|
+
agent: agentEnum.optional().describe("Limit to one agent; omit for all")
|
|
11057
|
+
},
|
|
11058
|
+
async ({ agent }) => {
|
|
11059
|
+
try {
|
|
11060
|
+
const catalogs = await listModelsForAgent(agent);
|
|
11061
|
+
return {
|
|
11062
|
+
content: [{ type: "text", text: JSON.stringify(catalogs, null, 2) }]
|
|
11063
|
+
};
|
|
11064
|
+
} catch (err) {
|
|
11065
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11066
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
11067
|
+
}
|
|
11068
|
+
}
|
|
11069
|
+
);
|
|
11070
|
+
server.tool(
|
|
11071
|
+
"fork_worktree",
|
|
11072
|
+
"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.",
|
|
11073
|
+
{
|
|
11074
|
+
ref: import_zod.z.string().describe("Worktree thread id/ref to fork"),
|
|
11075
|
+
through_index: import_zod.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
11076
|
+
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
11077
|
+
model: import_zod.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
11078
|
+
title: import_zod.z.string().optional()
|
|
11079
|
+
},
|
|
11080
|
+
async ({ ref, through_index, agent, model, title }) => {
|
|
11081
|
+
try {
|
|
11082
|
+
const source = orch.getThread(ref);
|
|
11083
|
+
if (source) await orch.reconcile(source.repoPath);
|
|
11084
|
+
const thread = await orch.forkThreadWorktree({
|
|
11085
|
+
threadId: ref,
|
|
11086
|
+
throughIndex: through_index,
|
|
11087
|
+
agent,
|
|
11088
|
+
model,
|
|
11089
|
+
title
|
|
11090
|
+
});
|
|
11091
|
+
return {
|
|
11092
|
+
content: [
|
|
11093
|
+
{
|
|
11094
|
+
type: "text",
|
|
11095
|
+
text: JSON.stringify({
|
|
11096
|
+
id: thread.id,
|
|
11097
|
+
title: thread.title,
|
|
11098
|
+
status: thread.status,
|
|
11099
|
+
agent: thread.agent,
|
|
11100
|
+
model: thread.model,
|
|
11101
|
+
branchName: thread.branchName,
|
|
11102
|
+
worktreePath: thread.worktreePath,
|
|
11103
|
+
fromThreadId: source?.id ?? ref,
|
|
11104
|
+
link: `sideboard://thread/${thread.id}`
|
|
11105
|
+
})
|
|
11106
|
+
}
|
|
11107
|
+
]
|
|
11108
|
+
};
|
|
11109
|
+
} catch (err) {
|
|
11110
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11111
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
11112
|
+
}
|
|
11113
|
+
}
|
|
11114
|
+
);
|
|
11115
|
+
server.tool(
|
|
11116
|
+
"fork_chat",
|
|
11117
|
+
"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. Remote coordinators 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.",
|
|
11118
|
+
{
|
|
11119
|
+
ref: import_zod.z.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
|
|
11120
|
+
through_index: import_zod.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
11121
|
+
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
11122
|
+
model: import_zod.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
11123
|
+
title: import_zod.z.string().optional()
|
|
11124
|
+
},
|
|
11125
|
+
async ({ ref, through_index, agent, model, title }) => {
|
|
11126
|
+
try {
|
|
11127
|
+
const source = orch.getThread(ref);
|
|
11128
|
+
if (!source) {
|
|
11129
|
+
return {
|
|
11130
|
+
content: [{ type: "text", text: `Thread not found: ${ref}` }],
|
|
11131
|
+
isError: true
|
|
11132
|
+
};
|
|
11133
|
+
}
|
|
11134
|
+
const tab = orch.forkChatTab({
|
|
11135
|
+
threadId: source.id,
|
|
11136
|
+
throughIndex: through_index,
|
|
11137
|
+
agent,
|
|
11138
|
+
model,
|
|
11139
|
+
title
|
|
11140
|
+
});
|
|
11141
|
+
return {
|
|
11142
|
+
content: [
|
|
11143
|
+
{
|
|
11144
|
+
type: "text",
|
|
11145
|
+
text: JSON.stringify({
|
|
11146
|
+
id: tab.id,
|
|
11147
|
+
title: tab.title,
|
|
11148
|
+
status: tab.status,
|
|
11149
|
+
agent: tab.agent,
|
|
11150
|
+
model: tab.model,
|
|
11151
|
+
sourceType: tab.sourceType,
|
|
11152
|
+
worktreePath: tab.worktreePath,
|
|
11153
|
+
fromThreadId: source.id,
|
|
11154
|
+
link: `sideboard://thread/${tab.id}`
|
|
11155
|
+
})
|
|
11156
|
+
}
|
|
11157
|
+
]
|
|
11158
|
+
};
|
|
11159
|
+
} catch (err) {
|
|
11160
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11161
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
11162
|
+
}
|
|
11163
|
+
}
|
|
11164
|
+
);
|
|
10483
11165
|
server.tool(
|
|
10484
11166
|
"run_dev_script",
|
|
10485
11167
|
"Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
|